diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 10653afb5a..1d1a6eec16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -75,7 +75,7 @@ { "label": "Build Immich CLI", "type": "shell", - "command": "pnpm --filter @immich/cli build:dev" + "command": "pnpm --filter cli build:dev" } ] } @@ -83,7 +83,7 @@ } }, "features": { - "ghcr.io/devcontainers/features/docker-in-docker:4": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { // https://github.com/devcontainers/features/issues/1466 "moby": false } diff --git a/.devcontainer/server/container-compose-overrides.yml b/.devcontainer/server/container-compose-overrides.yml index c5db2a6b32..5c312efd07 100644 --- a/.devcontainer/server/container-compose-overrides.yml +++ b/.devcontainer/server/container-compose-overrides.yml @@ -15,8 +15,8 @@ services: volumes: - ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data - /etc/localtime:/etc/localtime:ro - - build_cache:/buildcache - - ../packages/plugin-core:/build/plugins/immich-plugin-core + - pnpm_store_server:/buildcache/pnpm-store + - ../plugins:/build/corePlugin immich-web: env_file: !reset [] immich-machine-learning: diff --git a/.devcontainer/server/container-start-frontend.sh b/.devcontainer/server/container-start-frontend.sh index e7f1dcc1d0..9a0d617d41 100755 --- a/.devcontainer/server/container-start-frontend.sh +++ b/.devcontainer/server/container-start-frontend.sh @@ -8,8 +8,6 @@ log "Preparing Immich Web Frontend" log "" run_cmd pnpm --filter @immich/sdk install run_cmd pnpm --filter @immich/sdk build -run_cmd pnpm --filter @immich/plugin-sdk install -run_cmd pnpm --filter @immich/plugin-sdk build run_cmd pnpm --filter immich-web install log "Starting Immich Web Frontend" diff --git a/.dockerignore b/.dockerignore index 4d8e2160c2..f7efb5c56e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -30,7 +30,9 @@ machine-learning/ misc/ mobile/ -packages/sdk/build/ +open-api/typescript-sdk/build/ +!open-api/typescript-sdk/package.json +!open-api/typescript-sdk/package-lock.json server/upload/ server/src/queries diff --git a/.gitattributes b/.gitattributes index e96e0676ca..e3fb061bbc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,13 @@ -mobile/android/**/*.g.kt -diff -merge -mobile/android/**/*.g.kt linguist-generated=true +mobile/openapi/**/*.md -diff -merge +mobile/openapi/**/*.md linguist-generated=true +mobile/openapi/**/*.dart -diff -merge +mobile/openapi/**/*.dart linguist-generated=true -mobile/ios/**/*.g.swift -diff -merge -mobile/ios/**/*.g.swift linguist-generated=true +mobile/lib/**/*.g.dart -diff -merge +mobile/lib/**/*.g.dart linguist-generated=true + +mobile/lib/**/*.drift.dart -diff -merge +mobile/lib/**/*.drift.dart linguist-generated=true mobile/drift_schemas/main/drift_schema_*.json -diff -merge mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true @@ -10,7 +15,10 @@ mobile/drift_schemas/main/drift_schema_*.json linguist-generated=true mobile/lib/infrastructure/repositories/db.repository.steps.dart -diff -merge mobile/lib/infrastructure/repositories/db.repository.steps.dart linguist-generated=true -packages/sdk/fetch-client.ts -diff -merge -packages/sdk/fetch-client.ts linguist-generated=true +mobile/test/drift/main/generated/** -diff -merge +mobile/test/drift/main/generated/** linguist-generated=true + +open-api/typescript-sdk/fetch-client.ts -diff -merge +open-api/typescript-sdk/fetch-client.ts linguist-generated=true *.sh text eol=lf diff --git a/.github/.nvmrc b/.github/.nvmrc new file mode 100644 index 0000000000..8e35034890 --- /dev/null +++ b/.github/.nvmrc @@ -0,0 +1 @@ +24.14.1 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..acbb7c785b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://buy.immich.app', 'https://immich.store'] diff --git a/.github/labeler.yml b/.github/labeler.yml index 824bd5c775..d0e4a3097b 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,7 @@ cli: - changed-files: - any-glob-to-any-file: - - packages/cli/src/** + - cli/src/** documentation: - changed-files: diff --git a/.github/workflows/auto-close.yml b/.github/workflows/auto-close.yml index e87e1b96c7..dbbb404e9e 100644 --- a/.github/workflows/auto-close.yml +++ b/.github/workflows/auto-close.yml @@ -15,7 +15,7 @@ jobs: outputs: uses_template: ${{ steps.check.outputs.uses_template }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: sparse-checkout: .github/pull_request_template.md sparse-checkout-cone-mode: false @@ -51,7 +51,7 @@ jobs: run: | gh api graphql \ -f prId="$NODE_ID" \ - -f body="This PR has been automatically closed as the description doesn't follow [our template](https://github.com/immich-app/immich/blob/main/.github/pull_request_template.md). After you edit it to match the template, the PR will automatically be reopened." \ + -f body="This PR has been automatically closed as the description doesn't follow our template. After you edit it to match the template, the PR will automatically be reopened." \ -f query=' mutation CommentAndClosePR($prId: ID!, $body: String!) { addComment(input: { diff --git a/.github/workflows/build-mobile.yml b/.github/workflows/build-mobile.yml index 6a9a01d41d..0b10c681a1 100644 --- a/.github/workflows/build-mobile.yml +++ b/.github/workflows/build-mobile.yml @@ -12,10 +12,6 @@ on: default: 'development' type: string secrets: - PUSH_O_MATIC_APP_CLIENT_ID: - required: true - PUSH_O_MATIC_APP_KEY: - required: true KEY_JKS: required: true ALIAS: @@ -55,15 +51,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -78,64 +73,69 @@ jobs: needs: pre-job permissions: contents: read - pull-requests: write - if: ${{ github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} + # Skip when PR from a fork + if: ${{ !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} runs-on: mich steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - permission-pull-requests: write - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: ${{ inputs.ref }} + ref: ${{ inputs.ref || github.sha }} persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} - working_directory: ./mobile - - name: Create the Keystore - if: ${{ !github.event.pull_request.head.repo.fork }} env: KEY_JKS: ${{ secrets.KEY_JKS }} working-directory: ./mobile run: printf "%s" $KEY_JKS | base64 -d > android/key.jks - - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0 + - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: 'zulu' java-version: '17' - name: Restore Gradle Cache id: cache-gradle-restore - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: | ~/.gradle/caches ~/.gradle/wrapper ~/.android/sdk mobile/android/.gradle + mobile/.dart_tool key: build-mobile-gradle-${{ runner.os }}-main + - name: Setup Flutter SDK + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 + with: + channel: 'stable' + flutter-version-file: ./mobile/pubspec.yaml + cache: true + - name: Setup Android SDK - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1 + uses: android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407 # v3.2.2 with: packages: '' - - name: Install dependencies - run: mise //mobile:install:ci + - name: Get Packages + working-directory: ./mobile + run: flutter pub get - - name: Perform codegen - run: mise //mobile:codegen + - name: Generate translation file + run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + working-directory: ./mobile + + - name: Generate platform APIs + run: make pigeon + working-directory: ./mobile - name: Build Android App Bundle working-directory: ./mobile @@ -144,46 +144,23 @@ jobs: ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} IS_MAIN: ${{ github.ref == 'refs/heads/main' }} - PR_NUMBER: ${{ github.event.pull_request.number }} run: | if [[ $IS_MAIN == 'true' ]]; then flutter build apk --release flutter build apk --release --split-per-abi --target-platform android-arm,android-arm64,android-x64 else - flutter build apk --release + flutter build apk --debug --split-per-abi --target-platform android-arm64 fi - name: Publish Android Artifact - id: upload-apk - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: release-apk-signed path: mobile/build/app/outputs/flutter-apk/*.apk - - name: Comment APK download link on PR - if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork }} - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 - env: - HEAD_SHA: ${{ github.event.pull_request.head.sha }} - APK_URL: ${{ steps.upload-apk.outputs.artifact-url }} - with: - id: mobile-android-apk - token: ${{ steps.token.outputs.token }} - body: | - 📱 **Android release APK (universal)** — `${{ env.HEAD_SHA }}` - - Download: ${{ env.APK_URL }} - -
- QR code - QR code -
- - Installs as a separate app (applicationId `app.alextran.immich.pr${{ github.event.pull_request.number }}`), so it coexists with the Play Store version and any other PR builds. - - name: Save Gradle Cache id: cache-gradle-save - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 if: github.ref == 'refs/heads/main' with: path: | @@ -191,6 +168,7 @@ jobs: ~/.gradle/wrapper ~/.android/sdk mobile/android/.gradle + mobile/.dart_tool key: ${{ steps.cache-gradle-restore.outputs.cache-primary-key }} build-sign-ios: @@ -200,47 +178,48 @@ jobs: contents: read # Run on main branch or workflow_dispatch, or on PRs/other branches (build only, no upload) if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true }} - runs-on: macos-26 + runs-on: macos-15 steps: - - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 - with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} - private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - name: Select Xcode 26 run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ inputs.ref || github.sha }} persist-credentials: false - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup Flutter SDK + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 with: - github_token: ${{ steps.token.outputs.token }} - working_directory: ./mobile + channel: 'stable' + flutter-version-file: ./mobile/pubspec.yaml + cache: true + + - name: Install Flutter dependencies + working-directory: ./mobile + run: flutter pub get + + - name: Generate translation files + run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + working-directory: ./mobile + + - name: Generate platform APIs + run: make pigeon + working-directory: ./mobile - name: Setup Ruby - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0 + uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0 with: ruby-version: '3.3' bundler-cache: true working-directory: ./mobile/ios - - name: Install dependencies - run: mise //mobile:install:ci - - - name: Perform codegen - run: mise //mobile:codegen - - - name: Resolve iOS Swift Packages - working-directory: ./mobile - run: flutter build ios --config-only --no-codesign + - name: Install CocoaPods dependencies + working-directory: ./mobile/ios + run: | + pod install - name: Create API Key env: @@ -288,6 +267,7 @@ jobs: APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} ENVIRONMENT: ${{ inputs.environment || 'development' }} + BUNDLE_ID_SUFFIX: ${{ inputs.environment == 'production' && '' || 'development' }} GITHUB_REF: ${{ github.ref }} FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 6 @@ -311,7 +291,7 @@ jobs: security delete-keychain build.keychain || true - name: Upload IPA artifact - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: ios-release-ipa path: mobile/ios/Runner.ipa diff --git a/.github/workflows/cache-cleanup.yml b/.github/workflows/cache-cleanup.yml index 06f61f03fa..e093cf9bf0 100644 --- a/.github/workflows/cache-cleanup.yml +++ b/.github/workflows/cache-cleanup.yml @@ -19,15 +19,13 @@ jobs: actions: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - permission-actions: write - name: Check out code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/check-openapi.yml b/.github/workflows/check-openapi.yml index 3f5491ce96..cd05f320c6 100644 --- a/.github/workflows/check-openapi.yml +++ b/.github/workflows/check-openapi.yml @@ -4,7 +4,6 @@ on: pull_request: paths: - 'open-api/**' - - 'mobile/lib/utils/openapi_patching.dart' - '.github/workflows/check-openapi.yml' concurrency: @@ -20,46 +19,13 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Check for breaking API changes - uses: oasdiff/oasdiff-action/breaking@033c15c845bef10f148afb0fa781bf1b2a7fe1bf # v0.1.12 + uses: oasdiff/oasdiff-action/breaking@1f38ea5ea0b4a2e4e49901c3bcdf4386a05e9ea1 # v0.0.37 with: base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json revision: open-api/immich-openapi-specs.json fail-on: ERR - review: false - - check-mobile-patches: - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ github.token }} - working_directory: ./mobile - - - name: Install dependencies - run: mise //mobile:install:ci - - - name: Fetch base spec from main - run: | - curl -fsSL \ - "https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json" \ - -o /tmp/base-spec.json - - - name: Check newly-required fields have a backward-compat patch - working-directory: ./mobile - env: - OPENAPI_BASE_SPEC: /tmp/base-spec.json - OPENAPI_REVISION_SPEC: ../open-api/immich-openapi-specs.json - run: flutter test test/openapi_patches_coverage.dart diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 7cfa4bbc92..13e71491a6 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -3,11 +3,11 @@ on: push: branches: [main] paths: - - 'packages/cli/**' + - 'cli/**' - '.github/workflows/cli.yml' pull_request: paths: - - 'packages/cli/**' + - 'cli/**' - '.github/workflows/cli.yml' release: types: [published] @@ -28,31 +28,38 @@ jobs: packages: write defaults: run: - working-directory: ./packages/cli + working-directory: ./cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - - name: Publish + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: './cli/.nvmrc' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Setup typescript-sdk + run: pnpm install && pnpm run build + working-directory: ./open-api/typescript-sdk + + - run: pnpm install --frozen-lockfile + - run: pnpm build + - run: pnpm publish --provenance --no-git-checks if: ${{ github.event_name == 'release' }} - env: - NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }} - run: mise run ci-publish -- --tag "$NPM_TAG" docker: name: Docker @@ -64,26 +71,25 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - name: Set up QEMU - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 - name: Login to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: registry: ghcr.io @@ -93,12 +99,12 @@ jobs: - name: Get package version id: package-version run: | - version=$(jq -r '.version' packages/cli/package.json) + version=$(jq -r '.version' cli/package.json) echo "version=$version" >> "$GITHUB_OUTPUT" - name: Generate docker image tags id: metadata - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 with: flavor: | latest=false @@ -106,12 +112,12 @@ jobs: name=ghcr.io/${{ github.repository_owner }}/immich-cli tags: | type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }} - type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }} + type=raw,value=latest,enable=${{ github.event_name == 'release' }} - name: Build and push image - uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: - file: packages/cli/Dockerfile + file: cli/Dockerfile platforms: linux/amd64,linux/arm64 push: ${{ github.event_name == 'release' }} cache-from: type=gha diff --git a/.github/workflows/close-duplicates.yml b/.github/workflows/close-duplicates.yml index a1ec4e80c8..839e5b3ceb 100644 --- a/.github/workflows/close-duplicates.yml +++ b/.github/workflows/close-duplicates.yml @@ -14,11 +14,7 @@ jobs: should_run: ${{ steps.should_run.outputs.run }} steps: - id: should_run - run: | - echo "run=${{ - (github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request') - && !contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association || github.event.discussion.author_association) - }}" >> "$GITHUB_OUTPUT" + run: echo "run=${{ github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request' }}" >> $GITHUB_OUTPUT get_body: runs-on: ubuntu-latest @@ -39,7 +35,7 @@ jobs: needs: [get_body, should_run] if: ${{ needs.should_run.outputs.should_run == 'true' }} container: - image: ghcr.io/immich-app/mdq:main@sha256:74b93dfbf13f8ee73eb35d8d5195ff667f93f9bc55df5ae453dd43a461c821fa + image: ghcr.io/immich-app/mdq:main@sha256:557cca601891b8b7d78b940071d35aaf7aaeb9b327d19b22cf282118edbc5272 outputs: checked: ${{ steps.get_checkbox.outputs.checked }} steps: diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 36063f8c49..2378b032b6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -44,21 +44,20 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -71,7 +70,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -84,6 +83,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 90ba3f40d6..84509103be 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,10 +14,6 @@ concurrency: permissions: {} -env: - GHCR_NAMESPACE: ghcr.io/${{ github.repository_owner }} - DOCKERHUB_NAMESPACE: docker.io/altran1502 - jobs: pre-job: runs-on: ubuntu-latest @@ -27,15 +23,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -48,6 +43,8 @@ jobs: - 'machine-learning/**' force-filters: | - '.github/workflows/docker.yml' + - '.github/workflows/multi-runner-build.yml' + - '.github/actions/image-build' force-events: 'workflow_dispatch,release' retag_ml: @@ -60,33 +57,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - include: &ml-variants - - image: immich-machine-learning - device: cpu - - image: immich-machine-learning - device: cuda - suffixes: '-cuda' - platforms: linux/amd64 - - image: immich-machine-learning - device: openvino - suffixes: '-openvino' - platforms: linux/amd64 - - image: immich-machine-learning - device: armnn - suffixes: '-armnn' - platforms: linux/arm64 - - image: immich-machine-learning - device: rknn - suffixes: '-rknn' - platforms: linux/arm64 - - image: immich-machine-learning - device: rocm - suffixes: '-rocm' - platforms: linux/amd64 - runner-mapping: '{"linux/amd64": "pokedex-large"}' + suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn'] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -95,10 +69,10 @@ jobs: - name: Re-tag image env: REGISTRY_NAME: 'ghcr.io' - REPOSITORY: ${{ github.repository_owner }}/${{ matrix.image }} - TAG_OLD: main${{ matrix.suffixes }} - TAG_PR: ${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffixes }} - TAG_COMMIT: commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffixes }} + REPOSITORY: ${{ github.repository_owner }}/immich-machine-learning + TAG_OLD: main${{ matrix.suffix }} + TAG_PR: ${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} + TAG_COMMIT: commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} run: | docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_PR}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}" docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_COMMIT}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}" @@ -111,9 +85,12 @@ jobs: packages: write if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == false && !github.event.pull_request.head.repo.fork }} runs-on: ubuntu-latest + strategy: + matrix: + suffix: [''] steps: - name: Login to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -123,9 +100,9 @@ jobs: env: REGISTRY_NAME: 'ghcr.io' REPOSITORY: ${{ github.repository_owner }}/immich-server - TAG_OLD: main - TAG_PR: ${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }} - TAG_COMMIT: commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }} + TAG_OLD: main${{ matrix.suffix }} + TAG_PR: ${{ github.event.number == 0 && github.ref_name || format('pr-{0}', github.event.number) }}${{ matrix.suffix }} + TAG_COMMIT: commit-${{ github.event_name != 'pull_request' && github.sha || github.event.pull_request.head.sha }}${{ matrix.suffix }} run: | docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_PR}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}" docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_COMMIT}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}" @@ -137,27 +114,40 @@ jobs: strategy: fail-fast: false matrix: - include: *ml-variants - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@4d420cf8e5ea499f99b2c2849ff9a219d5f7b178 # multi-runner-build-workflow-v4.0.0 + include: + - device: cpu + - device: cuda + suffixes: '-cuda' + platforms: linux/amd64 + - device: openvino + suffixes: '-openvino' + platforms: linux/amd64 + - device: armnn + suffixes: '-armnn' + platforms: linux/arm64 + - device: rknn + suffixes: '-rknn' + platforms: linux/arm64 + - device: rocm + suffixes: '-rocm' + platforms: linux/amd64 + runner-mapping: '{"linux/amd64": "pokedex-large"}' + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 permissions: contents: read actions: read packages: write + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} with: - image: ${{ matrix.image }} + image: immich-machine-learning context: machine-learning dockerfile: machine-learning/Dockerfile platforms: ${{ matrix.platforms }} runner-mapping: ${{ matrix.runner-mapping }} suffixes: ${{ matrix.suffixes }} - tags: &tag-rules | - type=ref,event=branch - type=ref,event=pr - type=sha,format=long,prefix=commit- - type=semver,pattern={{version}},prefix=v - type=semver,pattern={{major}},prefix=v - type=match,pattern=v(\d+),group=1,prefix=v,suffix=-rc,enable=${{ github.event.release.prerelease == true }} - type=raw,value=release,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} + dockerhub-push: ${{ github.event_name == 'release' }} build-args: | DEVICE=${{ matrix.device }} @@ -165,87 +155,40 @@ jobs: name: Build and Push Server needs: pre-job if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }} - uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@4d420cf8e5ea499f99b2c2849ff9a219d5f7b178 # multi-runner-build-workflow-v4.0.0 + uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@61a0fc2b41524edcc7c9fffb8bb178e6b0ccf21d # multi-runner-build-workflow-v2.3.0 permissions: contents: read actions: read packages: write + secrets: + DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} with: image: immich-server context: . dockerfile: server/Dockerfile - tags: *tag-rules + dockerhub-push: ${{ github.event_name == 'release' }} build-args: | DEVICE=cpu - mirror: - name: Mirror to Docker Hub - needs: [machine-learning, server] - if: ${{ github.event_name == 'release' && github.event.release.prerelease == false }} - permissions: - packages: read - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - # Slightly hacky; we reuse the ml matrix but tack on immich-server too - image: [immich-server] - include: *ml-variants - env: - IMAGE: ${{ matrix.image }} - SUFFIX: ${{ matrix.suffixes }} - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Generate docker image tags - id: meta - uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 - with: - flavor: | - latest=false - images: ${{ env.GHCR_NAMESPACE }}/${{ env.IMAGE }} - tags: *tag-rules - - - name: Mirror tags to Docker Hub - env: - TAGS: ${{ steps.meta.outputs.tags }} - run: | - for tag in ${TAGS}; do - version="${tag##*:}" - docker buildx imagetools create \ - -t "${DOCKERHUB_NAMESPACE}/${IMAGE}:${version}${SUFFIX}" \ - "${GHCR_NAMESPACE}/${IMAGE}:${version}${SUFFIX}" - done - success-check-server: name: Docker Build & Push Server Success - needs: [server, retag_server, mirror] + needs: [server, retag_server] permissions: {} runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 + - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 with: needs: ${{ toJSON(needs) }} success-check-ml: name: Docker Build & Push ML Success - needs: [machine-learning, retag_ml, mirror] + needs: [machine-learning, retag_ml] permissions: {} runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 + - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 with: needs: ${{ toJSON(needs) }} diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index e0c5de3fab..2055bfce65 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -21,15 +21,14 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | @@ -55,22 +54,27 @@ jobs: steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} + fetch-depth: 0 - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} + node-version-file: './docs/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Run install run: pnpm install @@ -82,7 +86,7 @@ jobs: run: pnpm build - name: Upload build output - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: docs-build-output path: docs/build/ diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index b88e3f56dc..05c845ccd1 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -20,18 +20,16 @@ jobs: artifact: ${{ steps.get-artifact.outputs.result }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-actions: read - permission-pull-requests: read - if: ${{ github.event.workflow_run.conclusion != 'success' }} run: echo 'The triggering workflow did not succeed' && exit 1 - name: Get artifact id: get-artifact - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.token.outputs.token }} script: | @@ -50,7 +48,7 @@ jobs: return { found: true, id: matchArtifact.id }; - name: Determine deploy parameters id: parameters - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: HEAD_SHA: ${{ github.event.workflow_run.head_sha }} with: @@ -100,16 +98,9 @@ jobs: shouldDeploy: true }; } else if (eventType == "release") { - const tag = context.payload.workflow_run.head_branch; - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag, - }); parameters = { event: "release", - name: tag, - prerelease: release.prerelease, + name: context.payload.workflow_run.head_branch, shouldDeploy: !isFork }; } @@ -128,28 +119,23 @@ jobs: if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - permission-actions: read - permission-pull-requests: write - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} + uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 - name: Load parameters id: parameters - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: PARAM_JSON: ${{ needs.checks.outputs.parameters }} with: @@ -158,11 +144,10 @@ jobs: const parameters = JSON.parse(process.env.PARAM_JSON); core.setOutput("event", parameters.event); core.setOutput("name", parameters.name); - core.setOutput("prerelease", parameters.prerelease); core.setOutput("shouldDeploy", parameters.shouldDeploy); - name: Download artifact - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: ARTIFACT_JSON: ${{ needs.checks.outputs.artifact }} with: @@ -216,7 +201,7 @@ jobs: run: mise run //docs:deploy - name: Deploy Docs Release Domain - if: ${{ steps.parameters.outputs.event == 'release' && steps.parameters.outputs.prerelease != 'true' }} + if: ${{ steps.parameters.outputs.event == 'release' }} env: TF_VAR_prefix_name: ${{ steps.parameters.outputs.name}} CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} @@ -226,11 +211,12 @@ jobs: run: 'mise run //deployment:tf apply' - name: Comment - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 if: ${{ steps.parameters.outputs.event == 'pr' }} with: - id: docs-pr-url token: ${{ steps.token.outputs.token }} number: ${{ fromJson(needs.checks.outputs.parameters).pr_number }} body: | 📖 Documentation deployed to [${{ steps.docs-output.outputs.subdomain }}](https://${{ steps.docs-output.outputs.subdomain }}) + emojis: 'rocket' + body-include: '' diff --git a/.github/workflows/docs-destroy.yml b/.github/workflows/docs-destroy.yml index cdb3f83c96..bb24a017fe 100644 --- a/.github/workflows/docs-destroy.yml +++ b/.github/workflows/docs-destroy.yml @@ -17,23 +17,19 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - permission-pull-requests: write - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} + uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3 - name: Destroy Docs Subdomain env: @@ -46,8 +42,9 @@ jobs: run: 'mise run //deployment:tf destroy -- -refresh=false' - name: Comment - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + uses: actions-cool/maintain-one-comment@4b2dbf086015f892dcb5e8c1106f5fccd6c1476b # v3.2.0 with: - id: docs-pr-url token: ${{ steps.token.outputs.token }} + number: ${{ github.event.number }} delete: true + body-include: '' diff --git a/.github/workflows/fdroid.yml b/.github/workflows/fdroid.yml deleted file mode 100644 index 8744f21294..0000000000 --- a/.github/workflows/fdroid.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Update F-Droid Repo - -on: - release: - types: [published] - -permissions: {} - -jobs: - update-index: - runs-on: ubuntu-latest - if: ${{ !github.event.release.prerelease }} - permissions: - contents: read - steps: - - name: Checkout pubspec for versionCode - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.event.release.tag_name }} - persist-credentials: false - sparse-checkout: mobile/pubspec.yaml - sparse-checkout-cone-mode: false - - - name: Update F-Droid repo - env: - GITLAB_TOKEN: ${{ secrets.FDROID_REPO_TOKEN }} - RELEASE: ${{ toJSON(github.event.release) }} - TAG: ${{ github.event.release.tag_name }} - run: | - VERSION_CODE=$(yq '.version' mobile/pubspec.yaml | cut -d+ -f2) - export VERSION_CODE - - NEW_ENTRY=$(yq -P ' - [.assets[] | select(.name == "app-release.apk")] as $matches | - with(select($matches | length == 0); error("app-release.apk not found in release assets")) | - { - "url": $matches[0].browser_download_url, - "sha256sum": ($matches[0].digest | sub("^sha256:", "")), - "date": (.published_at | split("T") | .[0]), - "version-code": env(VERSION_CODE) - } | .date tag= "!!timestamp" - ' <<< "$RELEASE") - export NEW_ENTRY - - git clone --depth 1 "https://oauth2:${GITLAB_TOKEN}@gitlab.futo.org/fdroid/repo-v2.git" fdroid-repo - cd fdroid-repo - - yq -i ' - with(select([.apks[] | select(."version-code" == env(VERSION_CODE))] | length > 0); error("version-code already in index")) | - .apks = [env(NEW_ENTRY)] + .apks - ' apps/Immich/index.yml - - git config user.name "Immich Release Bot" - git config user.email "bot@immich.app" - git commit -am "Immich: add version-code ${VERSION_CODE} (${TAG})" - git push diff --git a/.github/workflows/fix-format.yml b/.github/workflows/fix-format.yml index 5b35793116..ae8e0b29ca 100644 --- a/.github/workflows/fix-format.yml +++ b/.github/workflows/fix-format.yml @@ -14,40 +14,44 @@ jobs: contents: write pull-requests: write steps: - - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: write - permission-pull-requests: write - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: 'Checkout' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: ${{ github.event.pull_request.head.ref }} + token: ${{ steps.generate-token.outputs.token }} persist-credentials: true - token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Fix formatting run: pnpm --recursive install && pnpm run --recursive --if-present --parallel format:fix - name: Commit and push - uses: EndBug/add-and-commit@645ecc0dd0a57f4d86d26c0aa5fc42c0a856fbca # v11.0.0 + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 with: default_author: github_actions message: 'chore: fix formatting' - name: Remove label - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 if: always() with: - github-token: ${{ steps.token.outputs.token }} + github-token: ${{ steps.generate-token.outputs.token }} script: | github.rest.issues.removeLabel({ issue_number: context.payload.pull_request.number, diff --git a/.github/workflows/merge-translations.yml b/.github/workflows/merge-translations.yml index 72edb0efd0..fcda857eda 100644 --- a/.github/workflows/merge-translations.yml +++ b/.github/workflows/merge-translations.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: workflow_call: secrets: - PUSH_O_MATIC_APP_CLIENT_ID: + PUSH_O_MATIC_APP_ID: required: true PUSH_O_MATIC_APP_KEY: required: true @@ -31,12 +31,10 @@ jobs: - name: Generate a token id: generate_token if: ${{ inputs.skip != true }} - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: write - permission-pull-requests: write - name: Find translation PR id: find_pr diff --git a/.github/workflows/org-zizmor.yml b/.github/workflows/org-zizmor.yml index 050e69b496..8510fd85b4 100644 --- a/.github/workflows/org-zizmor.yml +++ b/.github/workflows/org-zizmor.yml @@ -13,4 +13,3 @@ jobs: actions: read contents: read security-events: write - secrets: inherit diff --git a/.github/workflows/pr-label-validation.yml b/.github/workflows/pr-label-validation.yml index 5820a8b284..416e40df0d 100644 --- a/.github/workflows/pr-label-validation.yml +++ b/.github/workflows/pr-label-validation.yml @@ -14,14 +14,13 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-pull-requests: write - name: Require PR to have a changelog label - uses: mheap/github-action-required-labels@23e10fde7e062233401931a0eece796cd9bf3177 # v5.6.0 + uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2 with: token: ${{ steps.token.outputs.token }} mode: exactly diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index d924c84596..75ee750e9f 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -12,13 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - permission-pull-requests: write - - uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0 + - uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1 with: repo-token: ${{ steps.token.outputs.token }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 22a0d2494c..dec9b06d67 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -3,20 +3,20 @@ name: Prepare new release on: workflow_dispatch: inputs: - releaseType: - description: 'Release type' + serverBump: + description: 'Bump server version' required: true default: 'false' type: choice options: - 'false' + - major - minor - patch - - premajor - - preminor - - prepatch - - prerelease - - release + mobileBump: + description: 'Bump mobile build number' + required: false + type: boolean skipTranslations: description: 'Skip translations' required: false @@ -36,7 +36,7 @@ jobs: permissions: pull-requests: write secrets: - PUSH_O_MATIC_APP_CLIENT_ID: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }} PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }} WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} @@ -46,49 +46,47 @@ jobs: outputs: ref: ${{ steps.push-tag.outputs.commit_long_sha }} version: ${{ steps.output.outputs.version }} - rc: ${{ steps.output.outputs.rc }} permissions: {} # No job-level permissions are needed because it uses the app-token steps: - - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + - name: Generate a token + id: generate-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: write - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - token: ${{ steps.token.outputs.token }} + token: ${{ steps.generate-token.outputs.token }} persist-credentials: true ref: main - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} - - # TODO move to mise - name: Install uv - uses: astral-sh/setup-uv@ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d # v10.0.0 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Bump version env: - RELEASE_TYPE: ${{ inputs.releaseType }} - run: mise //:release --type "${RELEASE_TYPE}" + SERVER_BUMP: ${{ inputs.serverBump }} + MOBILE_BUMP: ${{ inputs.mobileBump }} + run: misc/release/pump-version.sh -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}" - id: output - run: | - echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT - if [[ "$IMMICH_VERSION" =~ -rc\.[0-9]+$ ]]; then - echo "rc=true" >> $GITHUB_OUTPUT - else - echo "rc=false" >> $GITHUB_OUTPUT - fi + run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT - name: Commit and tag id: push-tag - uses: EndBug/add-and-commit@645ecc0dd0a57f4d86d26c0aa5fc42c0a856fbca # v11.0.0 + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 with: default_author: github_actions message: 'chore: version ${{ steps.output.outputs.version }}' @@ -100,10 +98,7 @@ jobs: needs: bump_version permissions: contents: read - pull-requests: write secrets: - PUSH_O_MATIC_APP_CLIENT_ID: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} - PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }} KEY_JKS: ${{ secrets.KEY_JKS }} ALIAS: ${{ secrets.ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} @@ -129,15 +124,13 @@ jobs: steps: - name: Generate a token id: generate-token - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: write - permission-actions: read - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: false @@ -149,23 +142,18 @@ jobs: github-token: ${{ steps.generate-token.outputs.token }} - name: Create draft release - env: - GH_TOKEN: ${{ steps.generate-token.outputs.token }} - REPO: ${{ github.repository }} - TAG: ${{ needs.bump_version.outputs.version }} - PRERELEASE: ${{ needs.bump_version.outputs.rc }} - run: | - gh release create "${TAG}" \ - --repo "${REPO}" \ - --draft \ - --verify-tag \ - --prerelease="${PRERELEASE}" \ - --notes-file misc/release/notes.tmpl \ - --generate-notes \ - docker/docker-compose.yml \ - docker/docker-compose.rootless.yml \ - docker/example.env \ - docker/hwaccel.ml.yml \ - docker/hwaccel.transcoding.yml \ - docker/prometheus.yml \ + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + with: + draft: true + tag_name: ${{ needs.bump_version.outputs.version }} + token: ${{ steps.generate-token.outputs.token }} + generate_release_notes: true + body_path: misc/release/notes.tmpl + files: | + docker/docker-compose.yml + docker/docker-compose.rootless.yml + docker/example.env + docker/hwaccel.ml.yml + docker/hwaccel.transcoding.yml + docker/prometheus.yml *.apk diff --git a/.github/workflows/preview-label.yaml b/.github/workflows/preview-label.yaml index 95be3b88e2..c63ef17f20 100644 --- a/.github/workflows/preview-label.yaml +++ b/.github/workflows/preview-label.yaml @@ -14,17 +14,16 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-pull-requests: write - - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 with: - id: preview-status - token: ${{ steps.token.outputs.token }} - body: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' + github-token: ${{ steps.token.outputs.token }} + message-id: 'preview-status' + message: 'Deploying preview environment to https://pr-${{ github.event.pull_request.number }}.preview.internal.immich.build/' remove-label: runs-on: ubuntu-latest @@ -33,13 +32,12 @@ jobs: pull-requests: write steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-pull-requests: write - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: github-token: ${{ steps.token.outputs.token }} script: | @@ -50,16 +48,16 @@ jobs: name: 'preview' }) - - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 if: ${{ github.event.pull_request.head.repo.fork }} with: - id: preview-status - token: ${{ steps.token.outputs.token }} - body: 'PRs from forks cannot have preview environments.' + github-token: ${{ steps.token.outputs.token }} + message-id: 'preview-status' + message: 'PRs from forks cannot have preview environments.' - - uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0 + - uses: mshick/add-pr-comment@64b8e914979889d746c99dea15a76e77ef64580a # v3.10.0 if: ${{ !github.event.pull_request.head.repo.fork }} with: - id: preview-status - token: ${{ steps.token.outputs.token }} - body: 'Preview environment has been removed.' + github-token: ${{ steps.token.outputs.token }} + message-id: 'preview-status' + message: 'Preview environment has been removed.' diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 9c6101e4e1..d9b6ffb7f5 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -14,32 +14,34 @@ jobs: contents: read id-token: write packages: write + defaults: + run: + working-directory: ./open-api/typescript-sdk steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + + # Setup .npmrc file to publish to npm + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - + node-version-file: './open-api/typescript-sdk/.nvmrc' + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install deps - run: pnpm --filter @immich/sdk install --frozen-lockfile - + run: pnpm install --frozen-lockfile - name: Build - run: pnpm --filter @immich/sdk build - + run: pnpm build - name: Publish - env: - NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }} - run: pnpm --filter @immich/sdk publish --provenance --no-git-checks --tag "$NPM_TAG" + run: pnpm publish --provenance --no-git-checks diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 0a3c61668c..21e5e25bc6 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -20,21 +20,19 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | mobile: - 'mobile/**' - - 'i18n/en.json' force-filters: | - '.github/workflows/static_analysis.yml' force-events: 'workflow_dispatch,release' @@ -51,40 +49,78 @@ jobs: working-directory: ./mobile steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup Flutter SDK + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 with: - github_token: ${{ steps.token.outputs.token }} - working_directory: ./mobile + channel: 'stable' + flutter-version-file: ./mobile/pubspec.yaml - name: Install dependencies - run: mise //mobile:install:ci + run: dart pub get - name: Install dependencies for UI package - run: flutter pub get --enforce-lockfile + run: dart pub get working-directory: ./mobile/packages/ui - - name: Perform codegen - run: mise //mobile:codegen + - name: Install dependencies for UI Showcase + run: dart pub get + working-directory: ./mobile/packages/ui/showcase - - name: Run analyze - run: mise //mobile:analyze + - name: Install DCM + uses: CQLabs/setup-dcm@8697ae0790c0852e964a6ef1d768d62a6675481a # v2.0.1 + with: + github-token: ${{ steps.token.outputs.token }} + version: auto + working-directory: ./mobile - - name: Run format - run: mise //mobile:format + - name: Generate translation file + run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + + - name: Run Build Runner + run: make build + + - name: Generate platform API + run: make pigeon + + - name: Find file changes + uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 + id: verify-changed-files + with: + files: | + mobile/**/*.g.dart + mobile/**/*.gr.dart + mobile/**/*.drift.dart + + - name: Verify files have not changed + if: steps.verify-changed-files.outputs.files_changed == 'true' + env: + CHANGED_FILES: ${{ steps.verify-changed-files.outputs.changed_files }} + run: | + echo "ERROR: Generated files not up to date! Run 'make build' and 'make pigeon' inside the mobile directory" + echo "Changed files: ${CHANGED_FILES}" + exit 1 + + - name: Run dart analyze + run: dart analyze --fatal-infos + + - name: Run dart format + run: make format # TODO: Re-enable after upgrading custom_lint # - name: Run dart custom_lint # run: dart run custom_lint + + # TODO: Use https://github.com/CQLabs/dcm-action + - name: Run DCM + run: dcm analyze lib --fatal-style --fatal-warnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a4ecceb14..a606aba124 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,87 +17,40 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | - scripts: - - 'packages/scripts/**' - - 'pnpm-lock.yaml' - - 'mise.toml' i18n: - 'i18n/**' - - 'mise.toml' web: - 'web/**' - 'i18n/**' - - 'packages/sdk/**' - - 'pnpm-lock.yaml' - - 'mise.toml' + - 'open-api/typescript-sdk/**' server: - 'server/**' - - 'pnpm-lock.yaml' - - 'mise.toml' - - 'packages/plugin-core/**' - - 'packages/plugin-sdk/**' cli: - - 'packages/cli/**' - - 'packages/sdk/**' - - 'pnpm-lock.yaml' - - 'mise.toml' + - 'cli/**' + - 'open-api/typescript-sdk/**' e2e: - 'e2e/**' - - 'pnpm-lock.yaml' - - 'mise.toml' mobile: - 'mobile/**' - - 'mise.toml' machine-learning: - 'machine-learning/**' - - 'mise.toml' .github: - '.github/**' force-filters: | - '.github/workflows/test.yml' force-events: 'workflow_dispatch' - script-unit-tests: - name: Scripts unit tests - needs: pre-job - if: ${{ fromJSON(needs.pre-job.outputs.should_run).scripts == true }} - runs-on: ubuntu-latest - permissions: - contents: read - steps: - - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 - with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} - private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 - with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run unit tests - run: pnpm --filter @immich/scripts test - server-unit-tests: name: Test & Lint Server needs: pre-job @@ -105,28 +58,44 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + defaults: + run: + working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-unit - run: mise run //server:ci-unit - + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Run package manager install + run: pnpm install + - name: Run linter + run: pnpm lint + if: ${{ !cancelled() }} + - name: Run formatter + run: pnpm format + if: ${{ !cancelled() }} + - name: Run tsc + run: pnpm check + if: ${{ !cancelled() }} + - name: Run small tests & coverage + run: pnpm test + if: ${{ !cancelled() }} cli-unit-tests: name: Unit Test CLI needs: pre-job @@ -136,29 +105,44 @@ jobs: contents: read defaults: run: - working-directory: ./packages/cli + working-directory: ./cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-unit - run: mise run ci-unit - + node-version-file: './cli/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Setup typescript-sdk + run: pnpm install && pnpm run build + working-directory: ./open-api/typescript-sdk + - name: Install deps + run: pnpm install + - name: Run linter + run: pnpm lint + if: ${{ !cancelled() }} + - name: Run formatter + run: pnpm format + if: ${{ !cancelled() }} + - name: Run tsc + run: pnpm check + if: ${{ !cancelled() }} + - name: Run unit tests & coverage + run: pnpm test + if: ${{ !cancelled() }} cli-unit-tests-win: name: Unit Test CLI (Windows) needs: pre-job @@ -168,42 +152,39 @@ jobs: contents: read defaults: run: - working-directory: ./packages/cli + working-directory: ./cli steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run setup @immich/sdk - run: mise run //:sdk:install && mise run //:sdk:build - - - name: Run pnpm install + node-version-file: './cli/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk + - name: Install deps run: pnpm install --frozen-lockfile - # Skip linter & formatter in Windows test. - - name: Run tsc run: pnpm check if: ${{ !cancelled() }} - - name: Run unit tests & coverage run: pnpm test if: ${{ !cancelled() }} - web-lint: name: Lint Web needs: pre-job @@ -216,33 +197,38 @@ jobs: working-directory: ./web steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run setup @immich/sdk - run: mise run //:sdk:install && mise run //:sdk:build - + node-version-file: './web/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Run setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk - name: Run pnpm install - run: pnpm install --frozen-lockfile - + run: pnpm rebuild && pnpm install --frozen-lockfile - name: Run linter run: pnpm lint if: ${{ !cancelled() }} - + - name: Run formatter + run: pnpm format + if: ${{ !cancelled() }} + - name: Run svelte checks + run: pnpm check:svelte + if: ${{ !cancelled() }} web-unit-tests: name: Test Web needs: pre-job @@ -255,26 +241,35 @@ jobs: working-directory: ./web steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-unit - run: mise run ci-unit - + node-version-file: './web/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Run setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk + - name: Run npm install + run: pnpm install --frozen-lockfile + - name: Run tsc + run: pnpm check:typescript + if: ${{ !cancelled() }} + - name: Run unit tests & coverage + run: pnpm test + if: ${{ !cancelled() }} i18n-tests: name: Test i18n needs: pre-job @@ -284,36 +279,34 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - + node-version-file: './web/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install dependencies - run: pnpm -w install --frozen-lockfile - + run: pnpm --filter=immich-i18n install --frozen-lockfile - name: Format - run: pnpm format:fix - + run: pnpm --filter=immich-i18n format:fix - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | i18n/** - - name: Verify files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -322,7 +315,6 @@ jobs: echo "ERROR: i18n files not up to date!" echo "Changed files: ${CHANGED_FILES}" exit 1 - e2e-tests-lint: name: End-to-End Lint needs: pre-job @@ -335,27 +327,40 @@ jobs: working-directory: ./e2e steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-unit - run: mise run ci-unit + node-version-file: './e2e/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Run setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk + if: ${{ !cancelled() }} + - name: Install dependencies + run: pnpm install --frozen-lockfile + if: ${{ !cancelled() }} + - name: Run linter + run: pnpm lint + if: ${{ !cancelled() }} + - name: Run formatter + run: pnpm format + if: ${{ !cancelled() }} + - name: Run tsc + run: pnpm check if: ${{ !cancelled() }} - server-medium-tests: name: Medium Tests (Server) needs: pre-job @@ -368,28 +373,30 @@ jobs: working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-medium - run: mise run ci-medium + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + - name: Run pnpm install + run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile + - name: Run medium tests + run: pnpm test:medium if: ${{ !cancelled() }} - e2e-tests-server-cli: name: End-to-End Tests (Server & CLI) needs: pre-job @@ -405,70 +412,63 @@ jobs: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Setup Node - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: '.nvmrc' + node-version-file: './e2e/.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - - - name: Setup packages - run: pnpm --filter @immich/sdk --filter @immich/cli install --frozen-lockfile && pnpm --filter @immich/sdk --filter @immich/cli build - + - name: Run setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk + if: ${{ !cancelled() }} - name: Run setup web run: pnpm install --frozen-lockfile && pnpm exec svelte-kit sync working-directory: ./web if: ${{ !cancelled() }} - + - name: Run setup cli + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./cli + if: ${{ !cancelled() }} - name: Install dependencies run: pnpm install --frozen-lockfile if: ${{ !cancelled() }} - - name: Start Docker Compose - id: docker run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 if: ${{ !cancelled() }} - - name: Run e2e tests (api & cli) env: VITEST_DISABLE_DOCKER_SETUP: true run: pnpm test - if: ${{ !cancelled() && steps.docker.outcome == 'success' }} - + if: ${{ !cancelled() }} - name: Run e2e tests (maintenance) env: VITEST_DISABLE_DOCKER_SETUP: true run: pnpm test:maintenance - if: ${{ !cancelled() && steps.docker.outcome == 'success' }} - + if: ${{ !cancelled() }} - name: Capture Docker logs if: always() run: docker compose logs --no-color > docker-compose-logs.txt working-directory: ./e2e - - name: Archive Docker logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: always() with: name: e2e-server-docker-logs-${{ matrix.runner }} path: e2e/docker-compose-logs.txt - e2e-tests-web: name: End-to-End Tests (Web) needs: pre-job @@ -484,97 +484,81 @@ jobs: runner: [ubuntu-latest, ubuntu-24.04-arm] steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false submodules: 'recursive' token: ${{ steps.token.outputs.token }} - - name: Setup pnpm - uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10 - + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 - name: Setup Node - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - node-version-file: '.nvmrc' + node-version-file: './e2e/.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - - - name: Run setup @immich/sdk - run: pnpm --filter @immich/sdk install --frozen-lockfile && pnpm --filter @immich/sdk build - + - name: Run setup typescript-sdk + run: pnpm install --frozen-lockfile && pnpm build + working-directory: ./open-api/typescript-sdk if: ${{ !cancelled() }} - name: Install dependencies run: pnpm install --frozen-lockfile if: ${{ !cancelled() }} - - name: Install Playwright Browsers run: pnpm exec playwright install chromium --only-shell if: ${{ !cancelled() }} - - name: Docker build - id: docker run: docker compose up -d --build --renew-anon-volumes --force-recreate --remove-orphans --wait --wait-timeout 300 if: ${{ !cancelled() }} - - name: Run e2e tests (web) env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web - if: ${{ !cancelled() && steps.docker.outcome == 'success' }} - + if: ${{ !cancelled() }} - name: Archive e2e test (web) results - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: success() || failure() with: name: e2e-web-test-results-${{ matrix.runner }} path: e2e/playwright-report/ - - name: Run ui tests (web) env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web:ui - if: ${{ !cancelled() && steps.docker.outcome == 'success' }} - + if: ${{ !cancelled() }} - name: Archive ui test (web) results - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: success() || failure() with: name: e2e-ui-test-results-${{ matrix.runner }} path: e2e/playwright-report/ - - name: Run maintenance tests env: PLAYWRIGHT_DISABLE_WEBSERVER: true run: pnpm test:web:maintenance - if: ${{ !cancelled() && steps.docker.outcome == 'success' }} - + if: ${{ !cancelled() }} - name: Archive maintenance tests (web) results - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: success() || failure() with: name: e2e-maintenance-isolated-test-results-${{ matrix.runner }} path: e2e/playwright-report/ - - name: Capture Docker logs if: always() run: docker compose logs --no-color > docker-compose-logs.txt working-directory: ./e2e - - name: Archive Docker logs - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 if: always() with: name: e2e-web-docker-logs-${{ matrix.runner }} path: e2e/docker-compose-logs.txt - success-check-e2e: name: End-to-End Tests Success needs: [e2e-tests-server-cli, e2e-tests-web] @@ -582,7 +566,7 @@ jobs: runs-on: ubuntu-latest if: always() steps: - - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 + - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 with: needs: ${{ toJSON(needs) }} mobile-unit-tests: @@ -594,32 +578,26 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup Flutter SDK + uses: subosito/flutter-action@1a449444c387b1966244ae4d4f8c696479add0b2 # v2.23.0 with: - github_token: ${{ steps.token.outputs.token }} - working_directory: ./mobile - - - name: Install dependencies - run: mise //mobile:install:ci - - - name: Perform codegen - run: mise //mobile:codegen - + channel: 'stable' + flutter-version-file: ./mobile/pubspec.yaml + - name: Generate translation file + run: dart run easy_localization:generate -S ../i18n && dart run bin/generate_keys.dart + working-directory: ./mobile - name: Run tests - run: mise //mobile:test - + working-directory: ./mobile + run: flutter test -j 1 ml-unit-tests: name: Unit Test ML needs: pre-job @@ -632,25 +610,34 @@ jobs: working-directory: ./machine-learning steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Install uv + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 with: - github_token: ${{ steps.token.outputs.token }} - - - name: Run ci-unit - run: mise run ci-unit - + python-version: 3.11 + - name: Install dependencies + run: | + uv sync --extra cpu + - name: Lint with ruff + run: | + uv run ruff check --output-format=github immich_ml + - name: Format with ruff + run: | + uv run ruff format --check immich_ml + - name: Run mypy type checking + run: | + uv run mypy --strict immich_ml/ + - name: Run tests and coverage + run: | + uv run pytest --cov=immich_ml --cov-report term-missing github-files-formatting: name: .github Files Formatting needs: pre-job @@ -663,30 +650,29 @@ jobs: working-directory: ./.github steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - + node-version-file: './.github/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Run pnpm install run: pnpm install --frozen-lockfile - - name: Run formatter run: pnpm format if: ${{ !cancelled() }} - shellcheck: name: ShellCheck runs-on: ubuntu-latest @@ -694,13 +680,12 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} @@ -716,37 +701,39 @@ jobs: contents: read steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install server dependencies - run: pnpm --filter immich install --frozen-lockfile + run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm --filter immich install --frozen-lockfile + - name: Build the app + run: pnpm --filter immich build - name: Run API generation - run: mise //:open-api + run: ./bin/generate-open-api.sh working-directory: open-api - - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | - packages/sdk + mobile/openapi + open-api/typescript-sdk open-api/immich-openapi-specs.json - - name: Verify files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -755,7 +742,6 @@ jobs: echo "ERROR: Generated files not up to date!" echo "Changed files: ${CHANGED_FILES}" exit 1 - sql-schema-up-to-date: name: SQL Schema Checks runs-on: ubuntu-latest @@ -772,51 +758,46 @@ jobs: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 ports: - 5432:5432 + defaults: + run: + working-directory: ./server steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Checkout code - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false token: ${{ steps.token.outputs.token }} - - - name: Setup Mise - uses: immich-app/devtools/actions/use-mise@06a9ef925332c91be647d6256642b86b398592c8 # use-mise-action-v3.2.1 + - name: Setup pnpm + uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - name: Setup Node + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: - github_token: ${{ steps.token.outputs.token }} - + node-version-file: './server/.nvmrc' + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install server dependencies - run: pnpm install --frozen-lockfile - - - name: Build plugins - run: mise //:plugins - + run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile - name: Build the app - run: mise //server:build - + run: pnpm build - name: Run existing migrations - run: pnpm --filter immich migrations:run - + run: pnpm migrations:run - name: Test npm run schema:reset command works - run: pnpm --filter immich schema:reset - + run: pnpm schema:reset - name: Generate new migrations continue-on-error: true - run: pnpm --filter migrations:generate src/TestMigration - + run: pnpm migrations:generate src/TestMigration - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-files with: files: | server/src - - name: Verify migration files have not changed if: steps.verify-changed-files.outputs.files_changed == 'true' env: @@ -824,21 +805,18 @@ jobs: run: | echo "ERROR: Generated migration files not up to date!" echo "Changed files: ${CHANGED_FILES}" - cat ./server/src/*-TestMigration.ts + cat ./src/*-TestMigration.ts exit 1 - - name: Run SQL generation - run: mise //:sql + run: pnpm sync:sql env: DB_URL: postgres://postgres:postgres@localhost:5432/immich - - name: Find file changes uses: tj-actions/verify-changed-files@a1c6acee9df209257a246f2cc6ae8cb6581c1edf # v20.0.4 id: verify-changed-sql-files with: files: | server/src/queries - - name: Verify SQL files have not changed if: steps.verify-changed-sql-files.outputs.files_changed == 'true' env: @@ -920,6 +898,6 @@ jobs: # emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none # disable-animations: true # script: | -# flutter pub get --enforce-lockfile +# flutter pub get # flutter test integration_test # attempt_limit: 3 diff --git a/.github/workflows/weblate-lock.yml b/.github/workflows/weblate-lock.yml index 94a81f8f5d..09024063c0 100644 --- a/.github/workflows/weblate-lock.yml +++ b/.github/workflows/weblate-lock.yml @@ -24,20 +24,19 @@ jobs: should_run: ${{ steps.check.outputs.should_run }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-contents: read - name: Check what should run id: check - uses: immich-app/devtools/actions/pre-job@03514eceb4a27cd714976a866b350df5ce175720 # pre-job-action-v2.1.0 + uses: immich-app/devtools/actions/pre-job@f50e3b600b6ac1763ddb8f3dfc69093512b967a1 # pre-job-action-v2.0.3 with: github-token: ${{ steps.token.outputs.token }} filters: | i18n: - - modified: 'i18n/!(en)**\.json' + - modified: 'i18n/!(en|package)**\.json' skip-force-logic: 'true' enforce-lock: @@ -48,11 +47,10 @@ jobs: if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }} steps: - id: token - uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0 + uses: immich-app/devtools/actions/create-workflow-token@57ff6ebfd507b045514442683ff06ff1b2f6efbd # create-workflow-token-action-v1.0.2 with: - client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }} + app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - permission-pull-requests: read - name: Bot review status env: @@ -70,6 +68,6 @@ jobs: permissions: {} if: always() steps: - - uses: immich-app/devtools/actions/success-check@81113db03f6d743efee81e0058c0b43f6cd6f36d # success-check-action-v0.0.6 + - uses: immich-app/devtools/actions/success-check@53bb77345ee9f953f93bd6fd9980f07a2f24965e # success-check-action-v0.0.5 with: needs: ${{ toJSON(needs) }} diff --git a/.gitignore b/.gitignore index 9acfc8a879..e8fdfa266c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,13 +11,16 @@ docker/library uploads coverage -mobile/generated mobile/gradle.properties +mobile/openapi/pubspec.lock mobile/*.jks mobile/libisar.dylib +mobile/openapi/test +mobile/openapi/doc +mobile/openapi/.openapi-generator/FILES mobile/ios/build -packages/**/build +open-api/typescript-sdk/build mobile/android/fastlane/report.xml mobile/ios/fastlane/report.xml diff --git a/.gitmodules b/.gitmodules index 50a43933a9..d417dc5ba8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ +[submodule "mobile/.isar"] + path = mobile/.isar + url = https://github.com/isar/isar [submodule "e2e/test-assets"] path = e2e/test-assets url = https://github.com/immich-app/test-assets diff --git a/.nvmrc b/.nvmrc deleted file mode 100644 index 5bf4400f22..0000000000 --- a/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -24.15.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 782e0192f6..399fedae33 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -12,8 +12,6 @@ "editorconfig.editorconfig", "foxundermoon.shell-format", "timonwong.shellcheck", - "bluebrown.yamlfmt", - // TODO TS 7 while it's still in preview - "typescriptteam.native-preview" + "bluebrown.yamlfmt" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 6cdc408fa2..9ed2bb77b8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -23,17 +23,15 @@ "type": "node", "request": "launch", "name": "Immich CLI", - "program": "${workspaceFolder}/packages/cli/dist/index.js", + "program": "${workspaceFolder}/cli/dist/index.js", "args": ["upload", "--help"], "runtimeArgs": ["--enable-source-maps"], "console": "integratedTerminal", - "resolveSourceMapLocations": [ - "${workspaceFolder}/packages/cli/dist/**/*.js.map" - ], + "resolveSourceMapLocations": ["${workspaceFolder}/cli/dist/**/*.js.map"], "sourceMaps": true, - "outFiles": ["${workspaceFolder}/packages/cli/dist/**/*.js"], + "outFiles": ["${workspaceFolder}/cli/dist/**/*.js"], "skipFiles": ["/**"], - "preLaunchTask": "Build @immich/cli" + "preLaunchTask": "Build Immich CLI" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 23d4385964..496e7539e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,10 @@ "editor.wordBasedSuggestions": "off" }, "[javascript]": { + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.removeUnusedImports": "explicit" + }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, @@ -25,11 +29,18 @@ "editor.formatOnSave": true }, "[svelte]": { + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.removeUnusedImports": "explicit" + }, "editor.defaultFormatter": "svelte.svelte-vscode", - "editor.formatOnSave": true, - "tailwindCSS.lint.suggestCanonicalClasses": "ignore" + "editor.formatOnSave": true }, "[typescript]": { + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.removeUnusedImports": "explicit" + }, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true }, @@ -40,42 +51,36 @@ "eslint.useFlatConfig": true, "eslint.validate": ["javascript", "typescript", "svelte"], "eslint.workingDirectories": [ - { "changeProcessCWD": true, "directory": "cli" }, - { "changeProcessCWD": true, "directory": "e2e" }, - { "changeProcessCWD": true, "directory": "server" }, - { "changeProcessCWD": true, "directory": "web" } + { "directory": "cli", "changeProcessCWD": true }, + { "directory": "e2e", "changeProcessCWD": true }, + { "directory": "server", "changeProcessCWD": true }, + { "directory": "web", "changeProcessCWD": true } ], + "files.watcherExclude": { + "**/.jj/**": true, + "**/.git/**": true, + "**/node_modules/**": true, + "**/build/**": true, + "**/dist/**": true, + "**/.svelte-kit/**": true + }, "explorer.fileNesting.enabled": true, "explorer.fileNesting.patterns": { "*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart", - "*.js": "${capture}.spec.js,${capture}.mock.js", "*.ts": "${capture}.spec.ts,${capture}.mock.ts", "package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs" }, - "files.watcherExclude": { - "**/.git/**": true, - "**/.jj/**": true, - "**/.svelte-kit/**": true, - "**/build/**": true, - "**/dist/**": true, - "**/node_modules/**": true - }, - // TODO remove once the ts7 extension is stable - "js/ts.experimental.useTsgo": true, - "js/ts.preferences.importModuleSpecifier": "non-relative", "search.exclude": { - "**/.svelte-kit": true, + "**/node_modules": true, "**/build": true, "**/dist": true, - "**/node_modules": true, + "**/.svelte-kit": true, "**/open-api/typescript-sdk/src": true }, "svelte.enable-ts-plugin": true, - "svelte.plugin.svelte.compilerWarnings": { - "state_referenced_locally": "ignore" - }, "tailwindCSS.experimental.configFile": { "web/src/app.css": "web/src/**" }, + "js/ts.preferences.importModuleSpecifier": "non-relative", "vitest.maximumConfigs": 10 } diff --git a/CODEOWNERS b/CODEOWNERS index b5ddc605d2..8759cf2357 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -4,4 +4,4 @@ /web/ @danieldietzler /machine-learning/ @mertalev /e2e/ @danieldietzler -/mobile/ @shenlong-tanwen @santoshakil @agg23 +/mobile/ @shenlong-tanwen diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..1484498281 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation +in our community a harassment-free experience for everyone, regardless +of age, body size, visible or invisible disability, ethnicity, sex +characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, +welcoming, diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for +our community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our + mistakes, and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or + political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in + a professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our +standards of acceptable behavior and will take appropriate and fair +corrective action in response to any behavior that they deem +inappropriate, threatening, offensive, or harmful. + +Community leaders have the right and responsibility to remove, edit, +or reject comments, commits, code, wiki edits, issues, and other +contributions that are not aligned to this Code of Conduct, and will +communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also +applies when an individual is officially representing the community in +public spaces. Examples of representing our community include using an +official e-mail address, posting via an official social media account, +or acting as an appointed representative at an online or offline +event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior +may be reported to the community leaders responsible for enforcement +at our Discord channel. All complaints +will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and +security of the reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in +determining the consequences for any action they deem in violation of +this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior +deemed unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, +providing clarity around the nature of the violation and an +explanation of why the behavior was inappropriate. A public apology +may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued +behavior. No interaction with the people involved, including +unsolicited interaction with those enforcing the Code of Conduct, for +a specified period of time. This includes avoiding interactions in +community spaces as well as external channels like social +media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, +including sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or +public communication with the community for a specified period of +time. No public or private interaction with the people involved, +including unsolicited interaction with those enforcing the Code of +Conduct, is allowed during this period. Violating these terms may lead +to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of +community standards, including sustained inappropriate behavior, +harassment of an individual, or aggression toward or disparagement of +classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction +within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor +Covenant][homepage], version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of +conduct enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the +FAQ at https://www.contributor-covenant.org/faq. Translations are +available at https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5972872528..d04f89015e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -19,13 +19,7 @@ We usually do not assign issues to new contributors, since it happens often that ## Use of generative AI -We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time. Even a one line change can have significant impact. We cannot have any confidence in an LLM, so if it's non-trivial for us to verify it works and you don't show that you fully understand all implications of the change, reviewing the PR is not worth our time. If we want LLM-generated code, it's much faster for us to use an LLM ourselves than to go through an intermediary via a pull request. - -If you use an LLM to translate a PR description or title, that is fine. Please make sure however that you stick to the PR template and that the text is written concisely. - -LLMs must not be used to fix GitHub issues labelled good first issue. These issues are generally not urgent, and are intended to be learning opportunities for new contributors to get familiar with the codebase. Whether you are a newcomer or not, fully automating the process of fixing this issue squanders the learning opportunity and doesn’t add much value to the project. Using LLMs to fix issues labelled as “good first issue” is forbidden, and those PRs will be closed automatically. - -Misrepresenting LLM use, contribution farming (automated low-effort PRs), or repeatedly hitting auto-close rules may be grounds for a block at maintainer discretion. +We ask you not to open PRs generated with an LLM. We find that code generated like this tends to need a large amount of back-and-forth, which is a very inefficient use of our time. If we want LLM-generated code, it's much faster for us to use an LLM ourselves than to go through an intermediary via a pull request. ## Feature freezes @@ -44,7 +38,7 @@ All our translations are done through [Weblate](https://hosted.weblate.org/proje ### Datasets -Help us improve our [Immich Datasets](https://datasets.immich.app) by submitting photos and videos taken from a variety of devices, including smartphones, DSLRs, and action cameras, as well as photos with unique features, such as panoramas, burst photos, and photo spheres. These datasets will be publicly available for anyone to use, do not submit private/sensitive photos. +Help us improve our [Immich Datasets](https://datasets.immich.app) by submitting photos and videos taken from a variety of devices, including smartphones, DSLRs, and action cameras, as well as photos with unique features, such as panoramas, burst photos, and photo spheres. These datasets will be publically available for anyone to use, do not submit private/sensitive photos. ### Community support diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..4d76913d8f --- /dev/null +++ b/Makefile @@ -0,0 +1,152 @@ +dev: + @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans + +dev-down: + docker compose -f ./docker/docker-compose.dev.yml down --remove-orphans + +dev-update: + @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans + +dev-scale: + @trap 'make dev-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans + +dev-docs: + npm --prefix docs run start + +.PHONY: e2e +e2e: + @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --remove-orphans + +e2e-dev: + @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.dev.yml up --remove-orphans + +e2e-update: + @trap 'make e2e-down' EXIT; COMPOSE_BAKE=true docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans + +e2e-down: + docker compose -f ./e2e/docker-compose.yml down --remove-orphans + +prod: + @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --remove-orphans + +prod-down: + docker compose -f ./docker/docker-compose.prod.yml down --remove-orphans + +prod-scale: + @trap 'make prod-down' EXIT; COMPOSE_BAKE=true docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans + +.PHONY: open-api +open-api: + cd ./open-api && bash ./bin/generate-open-api.sh + +open-api-dart: + cd ./open-api && bash ./bin/generate-open-api.sh dart + +open-api-typescript: + cd ./open-api && bash ./bin/generate-open-api.sh typescript + +sql: + pnpm --filter immich run sync:sql + +attach-server: + docker exec -it docker_immich-server_1 sh + +renovate: + LOG_LEVEL=debug pnpm exec renovate --platform=local --repository-cache=reset + +# Directories that need to be created for volumes or build output +VOLUME_DIRS = \ + ./.pnpm-store \ + ./web/.svelte-kit \ + ./web/node_modules \ + ./web/coverage \ + ./e2e/node_modules \ + ./docs/node_modules \ + ./server/node_modules \ + ./open-api/typescript-sdk/node_modules \ + ./.github/node_modules \ + ./node_modules \ + ./cli/node_modules + +# Include .env file if it exists +-include docker/.env + +MODULES = e2e server web cli sdk docs .github + +# directory to package name mapping function +# cli = @immich/cli +# docs = documentation +# e2e = immich-e2e +# open-api/typescript-sdk = @immich/sdk +# server = immich +# web = immich-web +map-package = $(subst sdk,@immich/sdk,$(subst cli,@immich/cli,$(subst docs,documentation,$(subst e2e,immich-e2e,$(subst server,immich,$(subst web,immich-web,$1)))))) + +audit-%: + pnpm --filter $(call map-package,$*) audit fix +install-%: + pnpm --filter $(call map-package,$*) install $(if $(FROZEN),--frozen-lockfile) $(if $(OFFLINE),--offline) +build-cli: build-sdk +build-web: build-sdk +build-%: install-% + pnpm --filter $(call map-package,$*) run build +format-%: + pnpm --filter $(call map-package,$*) run format:fix +lint-%: + pnpm --filter $(call map-package,$*) run lint:fix +check-%: + pnpm --filter $(call map-package,$*) run check +check-web: + pnpm --filter immich-web run check:typescript + pnpm --filter immich-web run check:svelte +test-%: + pnpm --filter $(call map-package,$*) run test +test-e2e: + docker compose -f ./e2e/docker-compose.yml build + pnpm --filter immich-e2e run test + pnpm --filter immich-e2e run test:web +test-medium: + docker run \ + --rm \ + -v ./server/src:/usr/src/app/src \ + -v ./server/test:/usr/src/app/test \ + -v ./server/vitest.config.medium.mjs:/usr/src/app/vitest.config.medium.mjs \ + -v ./server/tsconfig.json:/usr/src/app/tsconfig.json \ + -e NODE_ENV=development \ + immich-server:latest \ + -c "pnpm test:medium -- --run" +test-medium-dev: + docker exec -it immich_server /bin/sh -c "pnpm run test:medium" + +install-all: + pnpm -r --filter '!documentation' install + +build-all: $(foreach M,$(filter-out e2e docs .github,$(MODULES)),build-$M) ; + +check-all: + pnpm -r --filter '!documentation' run "/^(check|check\:svelte|check\:typescript)$/" +lint-all: + pnpm -r --filter '!documentation' run lint:fix +format-all: + pnpm -r --filter '!documentation' run format:fix +audit-all: + pnpm -r --filter '!documentation' audit fix +hygiene-all: audit-all + pnpm -r --filter '!documentation' run "/(format:fix|check|check:svelte|check:typescript|sql)/" + +test-all: + pnpm -r --filter '!documentation' run "/^test/" + +clean: + find . -name "node_modules" -type d -prune -exec rm -rf {} + + find . -name "dist" -type d -prune -exec rm -rf '{}' + + find . -name "build" -type d -prune -exec rm -rf '{}' + + find . -name ".svelte-kit" -type d -prune -exec rm -rf '{}' + + find . -name "coverage" -type d -prune -exec rm -rf '{}' + + find . -name ".pnpm-store" -type d -prune -exec rm -rf '{}' + + command -v docker >/dev/null 2>&1 && docker compose -f ./docker/docker-compose.dev.yml down -v --remove-orphans || true + command -v docker >/dev/null 2>&1 && docker compose -f ./e2e/docker-compose.yml down -v --remove-orphans || true + + +setup-server-dev: install-server +setup-web-dev: install-sdk build-sdk install-web diff --git a/README.md b/README.md index 02ae01857e..7e06d9de4b 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,11 @@ æ­ŖéĢ”ä¸­æ–‡ ĐŖĐēŅ€Đ°Ņ—ĐŊҁҌĐēа Đ ŅƒŅŅĐēиК - Đ‘ŅŠĐģĐŗĐ°Ņ€ŅĐēи PortuguÃĒs Brasileiro Svenska Ø§Ų„ØšØąØ¨ŲŠØŠ Tiáēŋng Viáģ‡t ā¸ ā¸˛ā¸Šā¸˛āš„ā¸—ā¸ĸ - ā´Žā´˛ā´¯ā´žā´ŗā´‚

diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..1877440a8d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +Please report security issues to `security@immich.app` diff --git a/packages/cli/.editorconfig b/cli/.editorconfig similarity index 100% rename from packages/cli/.editorconfig rename to cli/.editorconfig diff --git a/packages/cli/.gitignore b/cli/.gitignore similarity index 100% rename from packages/cli/.gitignore rename to cli/.gitignore diff --git a/packages/cli/.npmignore b/cli/.npmignore similarity index 100% rename from packages/cli/.npmignore rename to cli/.npmignore diff --git a/cli/.nvmrc b/cli/.nvmrc new file mode 100644 index 0000000000..8e35034890 --- /dev/null +++ b/cli/.nvmrc @@ -0,0 +1 @@ +24.14.1 diff --git a/packages/cli/.prettierignore b/cli/.prettierignore similarity index 100% rename from packages/cli/.prettierignore rename to cli/.prettierignore diff --git a/packages/cli/.prettierrc b/cli/.prettierrc similarity index 100% rename from packages/cli/.prettierrc rename to cli/.prettierrc diff --git a/cli/Dockerfile b/cli/Dockerfile new file mode 100644 index 0000000000..d56190ee16 --- /dev/null +++ b/cli/Dockerfile @@ -0,0 +1,14 @@ +FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 AS core + +WORKDIR /usr/src/app +COPY package* pnpm* .pnpmfile.cjs ./ +COPY ./cli ./cli/ +COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/ +RUN corepack enable pnpm && \ + pnpm install --filter @immich/sdk --filter @immich/cli --frozen-lockfile && \ + pnpm --filter @immich/sdk build && \ + pnpm --filter @immich/cli build + +WORKDIR /import + +ENTRYPOINT ["node", "/usr/src/app/cli/dist"] diff --git a/packages/cli/LICENSE b/cli/LICENSE similarity index 100% rename from packages/cli/LICENSE rename to cli/LICENSE diff --git a/packages/cli/README.md b/cli/README.md similarity index 77% rename from packages/cli/README.md rename to cli/README.md index 92582fccc4..b9d61fce09 100644 --- a/packages/cli/README.md +++ b/cli/README.md @@ -4,9 +4,14 @@ Please see the [Immich CLI documentation](https://docs.immich.app/features/comma # For developers -Before building the CLI, you must build the immich server and the open-api client. You can use the following command: +Before building the CLI, you must build the immich server and the open-api client. To build the server run the following in the server folder: - $ mise //:open-api + $ pnpm install + $ pnpm run build + +Then, to build the open-api client run the following in the open-api folder: + + $ ./bin/generate-open-api.sh ## Run from build diff --git a/cli/bin/immich b/cli/bin/immich new file mode 100755 index 0000000000..924fff1230 --- /dev/null +++ b/cli/bin/immich @@ -0,0 +1,2 @@ +#!/usr/bin/env node +import '../dist/index.js'; diff --git a/packages/cli/eslint.config.mjs b/cli/eslint.config.mjs similarity index 69% rename from packages/cli/eslint.config.mjs rename to cli/eslint.config.mjs index d6f32a9abc..101d91bea4 100644 --- a/packages/cli/eslint.config.mjs +++ b/cli/eslint.config.mjs @@ -2,8 +2,13 @@ import js from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import eslintPluginUnicorn from 'eslint-plugin-unicorn'; import globals from 'globals'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import typescriptEslint from 'typescript-eslint'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + export default typescriptEslint.config([ eslintPluginUnicorn.configs.recommended, eslintPluginPrettierRecommended, @@ -24,7 +29,7 @@ export default typescriptEslint.config([ parserOptions: { project: 'tsconfig.json', - tsconfigRootDir: import.meta.dirname, + tsconfigRootDir: __dirname, }, }, @@ -35,18 +40,10 @@ export default typescriptEslint.config([ '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-floating-promises': 'error', 'unicorn/prefer-module': 'off', - 'unicorn/name-replacements': 'off', - 'unicorn/no-unreadable-for-of-expression': 'off', - 'unicorn/no-declarations-before-early-exit': 'off', + 'unicorn/prevent-abbreviations': 'off', 'unicorn/no-process-exit': 'off', 'unicorn/import-style': 'off', - 'unicorn/consistent-class-member-order': 'off', - 'unicorn/prefer-simple-condition-first': 'off', - 'unicorn/single-line-block-comment-style': ['error', 'single-line'], curly: 2, - // prefer the typescript-eslint type-aware version - 'unicorn/require-array-sort-compare': 'off', - '@typescript-eslint/require-array-sort-compare': 'error', 'prettier/prettier': 0, 'object-shorthand': ['error', 'always'], }, diff --git a/packages/cli/mise.toml b/cli/mise.toml similarity index 55% rename from packages/cli/mise.toml rename to cli/mise.toml index 320ce001d8..740184e03d 100644 --- a/packages/cli/mise.toml +++ b/cli/mise.toml @@ -7,7 +7,7 @@ run = "vite build" [tasks.test] env._.path = "./node_modules/.bin" -run = "vitest" +run = "vite" [tasks.lint] env._.path = "./node_modules/.bin" @@ -27,28 +27,3 @@ run = "prettier --write ." [tasks.check] env._.path = "./node_modules/.bin" run = "tsc --noEmit" - -[tasks.ci-publish] -run = [ - { task = "//:sdk:install" }, - { task = "//:sdk:build" }, - { task = ":install" }, - { task = ":build" }, - "pnpm publish --provenance --no-git-checks", -] - -[tasks.ci-unit] -run = [ - { task = "//:sdk:install" }, - { task = "//:sdk:build" }, - { task = ":install" }, - { task = ":format" }, - { task = ":lint" }, - { task = ":check" }, - { task = ":test --run" }, -] - -[tasks.checklist] -run = [ - { task = ":ci-unit" }, -] diff --git a/packages/cli/package.json b/cli/package.json similarity index 83% rename from packages/cli/package.json rename to cli/package.json index 2a0f0a76bf..4e3bd05db3 100644 --- a/packages/cli/package.json +++ b/cli/package.json @@ -1,12 +1,7 @@ { "name": "@immich/cli", - "version": "3.1.0", + "version": "2.7.3", "description": "Command Line Interface (CLI) for Immich", - "repository": { - "type": "git", - "url": "git+https://github.com/immich-app/immich.git", - "directory": "packages/cli" - }, "type": "module", "exports": "./dist/index.js", "bin": { @@ -25,21 +20,20 @@ "@types/lodash-es": "^4.17.12", "@types/micromatch": "^4.0.9", "@types/mock-fs": "^4.13.1", - "@types/node": "^24.13.3", + "@types/node": "^24.12.0", "@vitest/coverage-v8": "^4.0.0", "byte-size": "^9.0.0", "cli-progress": "^3.12.0", - "commander": "^15.0.0", + "commander": "^12.0.0", "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^63.0.0", "globals": "^17.0.0", "mock-fs": "^5.2.0", "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", - "@typescript/native": "npm:typescript@^7.0.2", - "typescript": "npm:@typescript/typescript6@^6.0.2", + "typescript": "^6.0.0", "typescript-eslint": "^8.58.0", "vite": "^8.0.0", "vitest": "^4.0.0", @@ -58,15 +52,22 @@ "format:fix": "prettier --cache --write --list-different .", "check": "tsc --noEmit" }, + "repository": { + "type": "git", + "url": "git+https://github.com/immich-app/immich.git", + "directory": "cli" + }, "engines": { - "node": ">=22.0.0" + "node": ">=20.0.0" }, "dependencies": { "chokidar": "^4.0.3", "fast-glob": "^3.3.2", "fastq": "^1.17.1", "lodash-es": "^4.17.21", - "micromatch": "^4.0.8", - "semver": "^7.8.1" + "micromatch": "^4.0.8" + }, + "volta": { + "node": "24.14.1" } } diff --git a/packages/cli/src/commands/asset.spec.ts b/cli/src/commands/asset.spec.ts similarity index 92% rename from packages/cli/src/commands/asset.spec.ts rename to cli/src/commands/asset.spec.ts index 7e582039ba..21700ef963 100644 --- a/packages/cli/src/commands/asset.spec.ts +++ b/cli/src/commands/asset.spec.ts @@ -4,14 +4,7 @@ import path from 'node:path'; import { setTimeout as sleep } from 'node:timers/promises'; import { describe, expect, it, MockedFunction, vi } from 'vitest'; -import { - AssetRejectReason, - AssetUploadAction, - AssetVisibility, - checkBulkUpload, - defaults, - getSupportedMediaTypes, -} from '@immich/sdk'; +import { Action, checkBulkUpload, defaults, getSupportedMediaTypes, Reason } from '@immich/sdk'; import createFetchMock from 'vitest-fetch-mock'; import { @@ -46,7 +39,7 @@ describe('uploadFiles', () => { const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); const testFilePath = path.join(testDir, 'test.png'); const testFileData = 'test'; - const baseUrl = 'https://example.com'; + const baseUrl = 'http://example.com'; const apiKey = 'key'; const retry = 3; @@ -109,20 +102,6 @@ describe('uploadFiles', () => { await expect(uploadFiles([testFilePath], { concurrency: 1 })).resolves.toEqual([]); }); - - it('uploads assets with the specified visibility', async () => { - fetchMocker.doMockIf(new RegExp(`${baseUrl}/assets$`), function () { - return { - status: 200, - body: JSON.stringify({ id: 'fc5621b1-86f6-44a1-9905-403e607df9f5', status: 'created' }), - }; - }); - - await uploadFiles([testFilePath], { concurrency: 1, visibility: AssetVisibility.Hidden }); - - const formData = fetchMocker.mock.calls[0]?.[1]?.body as FormData; - expect(formData.get('visibility')).toBe('hidden'); - }); }); describe('checkForDuplicates', () => { @@ -141,7 +120,7 @@ describe('checkForDuplicates', () => { vi.mocked(checkBulkUpload).mockResolvedValue({ results: [ { - action: AssetUploadAction.Accept, + action: Action.Accept, id: testFilePath, }, ], @@ -165,10 +144,10 @@ describe('checkForDuplicates', () => { vi.mocked(checkBulkUpload).mockResolvedValue({ results: [ { - action: AssetUploadAction.Reject, + action: Action.Reject, id: testFilePath, assetId: 'fc5621b1-86f6-44a1-9905-403e607df9f5', - reason: AssetRejectReason.Duplicate, + reason: Reason.Duplicate, }, ], }); @@ -188,7 +167,7 @@ describe('checkForDuplicates', () => { vi.mocked(checkBulkUpload).mockResolvedValue({ results: [ { - action: AssetUploadAction.Accept, + action: Action.Accept, id: testFilePath, }, ], @@ -208,7 +187,7 @@ describe('checkForDuplicates', () => { mocked.mockResolvedValue({ results: [ { - action: AssetUploadAction.Accept, + action: Action.Accept, id: testFilePath, }, ], diff --git a/packages/cli/src/commands/asset.ts b/cli/src/commands/asset.ts similarity index 95% rename from packages/cli/src/commands/asset.ts rename to cli/src/commands/asset.ts index f1dfdaec8e..7d4b09b69d 100644 --- a/packages/cli/src/commands/asset.ts +++ b/cli/src/commands/asset.ts @@ -1,10 +1,9 @@ import { + Action, AssetBulkUploadCheckItem, AssetBulkUploadCheckResult, AssetMediaResponseDto, AssetMediaStatus, - AssetUploadAction, - AssetVisibility, Permission, addAssetsToAlbum, checkBulkUpload, @@ -40,7 +39,6 @@ export interface UploadOptionsDto { deleteDuplicates?: boolean; album?: boolean; albumName?: string; - visibility?: AssetVisibility; includeHidden?: boolean; concurrency: number; progress?: boolean; @@ -56,7 +54,6 @@ class UploadFile extends File { super([], basename(filepath)); } - // @ts-expect-error size is already a property on the new File interface get size() { return this._size; } @@ -237,7 +234,7 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas const results = response.results as AssetBulkUploadCheckResults; for (const { id: filepath, assetId, action } of results) { - if (action === AssetUploadAction.Accept) { + if (action === Action.Accept) { newFiles.push(filepath); } else { // rejects are always duplicates @@ -309,8 +306,10 @@ export const checkForDuplicates = async (files: string[], { concurrency, skipHas return { newFiles, duplicates }; }; -export const uploadFiles = async (files: string[], options: UploadOptionsDto): Promise => { - const { dryRun, concurrency, progress } = options; +export const uploadFiles = async ( + files: string[], + { dryRun, concurrency, progress }: UploadOptionsDto, +): Promise => { if (files.length === 0) { console.log('All assets were already uploaded, nothing to do.'); return []; @@ -359,7 +358,7 @@ export const uploadFiles = async (files: string[], options: UploadOptionsDto): P throw new Error(`Stats not found for ${filepath}`); } - const response = await uploadFile(filepath, stats, options); + const response = await uploadFile(filepath, stats); newAssets.push({ id: response.id, filepath }); if (response.status === AssetMediaStatus.Duplicate) { duplicateCount++; @@ -401,22 +400,17 @@ export const uploadFiles = async (files: string[], options: UploadOptionsDto): P return newAssets; }; -const uploadFile = async ( - input: string, - stats: Stats, - { visibility }: UploadOptionsDto, -): Promise => { +const uploadFile = async (input: string, stats: Stats): Promise => { const { baseUrl, headers } = defaults; const formData = new FormData(); + formData.append('deviceAssetId', `${basename(input)}-${stats.size}`.replaceAll(/\s+/g, '')); + formData.append('deviceId', 'CLI'); formData.append('fileCreatedAt', stats.mtime.toISOString()); formData.append('fileModifiedAt', stats.mtime.toISOString()); formData.append('fileSize', String(stats.size)); formData.append('isFavorite', 'false'); formData.append('assetData', new UploadFile(input, stats.size)); - if (visibility) { - formData.append('visibility', visibility); - } const sidecarPath = findSidecar(input); if (sidecarPath) { @@ -434,14 +428,12 @@ const uploadFile = async ( redirect: 'error', headers: headers as Record, body: formData, - // eslint-disable-next-line unicorn/no-null - window: null, }); if (response.status !== 200 && response.status !== 201) { throw new Error(await response.text()); } - return response.json() as Promise; + return response.json(); }; export const findSidecar = (filepath: string): string | undefined => { @@ -578,7 +570,7 @@ const updateAlbums = async (assets: Asset[], options: UploadOptionsDto) => { albumUpdateProgress.start(assets.length, 0); try { - for (const [albumId, assets] of albumToAssets) { + for (const [albumId, assets] of albumToAssets.entries()) { for (const assetBatch of chunk(assets, Math.min(1000 * concurrency, 65_000))) { await addAssetsToAlbum({ id: albumId, bulkIdsDto: { ids: assetBatch } }); albumUpdateProgress.increment(assetBatch.length); diff --git a/packages/cli/src/commands/auth.ts b/cli/src/commands/auth.ts similarity index 100% rename from packages/cli/src/commands/auth.ts rename to cli/src/commands/auth.ts diff --git a/packages/cli/src/commands/server-info.ts b/cli/src/commands/server-info.ts similarity index 100% rename from packages/cli/src/commands/server-info.ts rename to cli/src/commands/server-info.ts diff --git a/packages/cli/src/index.ts b/cli/src/index.ts similarity index 90% rename from packages/cli/src/index.ts rename to cli/src/index.ts index deef7d8c8c..0bc2e5de37 100644 --- a/packages/cli/src/index.ts +++ b/cli/src/index.ts @@ -1,5 +1,4 @@ #! /usr/bin/env node -import { AssetVisibility } from '@immich/sdk'; import { Command, Option } from 'commander'; import os from 'node:os'; import path from 'node:path'; @@ -47,7 +46,7 @@ program .usage('[paths...] [options]') .addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false)) .addOption(new Option('-i, --ignore ', 'Pattern to ignore').env('IMMICH_IGNORE_PATHS')) - .addOption(new Option('--skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false)) + .addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false)) .addOption(new Option('-H, --include-hidden', 'Include hidden folders').env('IMMICH_INCLUDE_HIDDEN').default(false)) .addOption( new Option('-a, --album', 'Automatically create albums based on folder name') @@ -59,11 +58,6 @@ program .env('IMMICH_ALBUM_NAME') .conflicts('album'), ) - .addOption( - new Option('--visibility ', 'Set the visibility of uploaded assets') - .choices(Object.values(AssetVisibility)) - .env('IMMICH_VISIBILITY'), - ) .addOption( new Option('-n, --dry-run', "Don't perform any actions, just show what will be done") .env('IMMICH_DRY_RUN') diff --git a/packages/cli/src/queue.ts b/cli/src/queue.ts similarity index 97% rename from packages/cli/src/queue.ts rename to cli/src/queue.ts index 45e1c454f7..0b6d628146 100644 --- a/packages/cli/src/queue.ts +++ b/cli/src/queue.ts @@ -52,7 +52,10 @@ export class Queue { } get tasks(): Task[] { - const tasks: Task[] = this.store.values().toArray(); + const tasks: Task[] = []; + for (const task of this.store.values()) { + tasks.push(task); + } return tasks; } diff --git a/packages/cli/src/utils.spec.ts b/cli/src/utils.spec.ts similarity index 100% rename from packages/cli/src/utils.spec.ts rename to cli/src/utils.spec.ts diff --git a/packages/cli/src/utils.ts b/cli/src/utils.ts similarity index 93% rename from packages/cli/src/utils.ts rename to cli/src/utils.ts index d8a567a230..b60f5e3715 100644 --- a/packages/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -39,7 +39,6 @@ export const s = (count: number) => (count === 1 ? '' : 's'); let _apiKey: ApiKeyResponseDto; export const requirePermissions = async (permissions: Permission[]) => { if (!_apiKey) { - // eslint-disable-next-line unicorn/no-top-level-assignment-in-function _apiKey = await getMyApiKey(); } @@ -68,9 +67,8 @@ Please make sure your API key has the correct permissions.`, export const connect = async (url: string, key: string) => { const wellKnownUrl = new URL('.well-known/immich', url); try { - // eslint-disable-next-line unicorn/prefer-await - const wellKnown = (await fetch(wellKnownUrl).then((response) => response.json())) as { api: { endpoint: string } }; - const endpoint = new URL(wellKnown.api.endpoint, url).href; + const wellKnown = await fetch(wellKnownUrl).then((response) => response.json()); + const endpoint = new URL(wellKnown.api.endpoint, url).toString(); if (endpoint !== url) { console.debug(`Discovered API at ${endpoint}`); } @@ -180,7 +178,7 @@ export const crawl = async (options: CrawlOptions): Promise => { const searchPatterns = patterns.map((pattern) => { let escapedPattern = pattern.replaceAll("'", "[']").replaceAll('"', '["]').replaceAll('`', '[`]'); if (recursive) { - escapedPattern += '/**'; + escapedPattern = escapedPattern + '/**'; } return `${escapedPattern}/*.{${extensions.join(',')}}`; }); @@ -240,12 +238,10 @@ export class Batcher { } private clearDebounceTimer() { - if (!this.debounceTimer) { - return; + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); + this.debounceTimer = undefined; } - - clearTimeout(this.debounceTimer); - this.debounceTimer = undefined; } add(item: T) { diff --git a/packages/cli/tsconfig.json b/cli/tsconfig.json similarity index 88% rename from packages/cli/tsconfig.json rename to cli/tsconfig.json index 26331e32f5..780ef8ba5c 100644 --- a/packages/cli/tsconfig.json +++ b/cli/tsconfig.json @@ -10,7 +10,6 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "target": "es2023", - "lib": ["ESNext"], "sourceMap": true, "outDir": "./dist", "incremental": true, @@ -20,7 +19,6 @@ "paths": { "src/*": ["./src/*"], }, - "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", "types": ["vitest/globals"] }, "exclude": ["dist", "node_modules", "vite.config.ts"] diff --git a/packages/cli/vite.config.ts b/cli/vite.config.ts similarity index 100% rename from packages/cli/vite.config.ts rename to cli/vite.config.ts diff --git a/deployment/mise.lock b/deployment/mise.lock deleted file mode 100644 index 018620a4a5..0000000000 --- a/deployment/mise.lock +++ /dev/null @@ -1,79 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html - -[[tools.opentofu]] -version = "1.12.5" -backend = "aqua:opentofu/opentofu" - -[tools.opentofu."platforms.linux-arm64"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545" - -[tools.opentofu."platforms.linux-arm64-musl"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545" - -[tools.opentofu."platforms.linux-x64"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624" - -[tools.opentofu."platforms.linux-x64-musl"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624" - -[tools.opentofu."platforms.macos-arm64"] -checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544" - -[tools.opentofu."platforms.macos-x64"] -checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621" - -[tools.opentofu."platforms.windows-x64"] -checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547" - -[[tools.terragrunt]] -version = "1.0.3" -backend = "aqua:gruntwork-io/terragrunt" - -[tools.terragrunt."platforms.linux-arm64"] -checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253" - -[tools.terragrunt."platforms.linux-arm64-musl"] -checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253" - -[tools.terragrunt."platforms.linux-x64"] -checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196" - -[tools.terragrunt."platforms.linux-x64-musl"] -checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196" - -[tools.terragrunt."platforms.macos-arm64"] -checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084" - -[tools.terragrunt."platforms.macos-x64"] -checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970" - -[tools.terragrunt."platforms.windows-x64"] -checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412" diff --git a/deployment/mise.toml b/deployment/mise.toml index 7098b79c99..bda19c4c84 100644 --- a/deployment/mise.toml +++ b/deployment/mise.toml @@ -1,6 +1,6 @@ [tools] -terragrunt = "1.1.1" -opentofu = "1.12.5" +terragrunt = "0.99.5" +opentofu = "1.11.5" [tasks."tg:fmt"] run = "terragrunt hclfmt" diff --git a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl index 049d3c0fd9..0869dd28bc 100644 --- a/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs-release/.terraform.lock.hcl @@ -2,35 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.8" - constraints = "4.52.8" + version = "4.52.5" + constraints = "4.52.5" hashes = [ - "h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=", - "h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=", - "h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=", - "h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=", - "h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=", - "h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=", - "h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=", - "h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=", - "h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=", - "h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=", - "h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=", - "h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=", - "h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=", - "zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477", - "zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d", - "zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363", - "zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74", + "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", + "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", + "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", + "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", + "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", + "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", + "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", + "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", + "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", + "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", + "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", + "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", + "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", + "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", + "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", + "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", + "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", + "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", + "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", + "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", + "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", + "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f", - "zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0", - "zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b", - "zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30", - "zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6", - "zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91", - "zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29", - "zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb", - "zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11", + "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", + "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", + "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", + "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", + "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", + "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", ] } diff --git a/deployment/modules/cloudflare/docs-release/config.tf b/deployment/modules/cloudflare/docs-release/config.tf index ea1a441a4b..63347cf67e 100644 --- a/deployment/modules/cloudflare/docs-release/config.tf +++ b/deployment/modules/cloudflare/docs-release/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.8" + version = "4.52.5" } } } diff --git a/deployment/modules/cloudflare/docs/.terraform.lock.hcl b/deployment/modules/cloudflare/docs/.terraform.lock.hcl index 049d3c0fd9..0869dd28bc 100644 --- a/deployment/modules/cloudflare/docs/.terraform.lock.hcl +++ b/deployment/modules/cloudflare/docs/.terraform.lock.hcl @@ -2,35 +2,37 @@ # Manual edits may be lost in future updates. provider "registry.opentofu.org/cloudflare/cloudflare" { - version = "4.52.8" - constraints = "4.52.8" + version = "4.52.5" + constraints = "4.52.5" hashes = [ - "h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=", - "h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=", - "h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=", - "h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=", - "h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=", - "h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=", - "h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=", - "h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=", - "h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=", - "h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=", - "h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=", - "h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=", - "h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=", - "zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477", - "zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d", - "zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363", - "zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74", + "h1:+rfzF+16ZcWZWnTyW/p1HHTzYbPKX8Zt2nIFtR/+f+E=", + "h1:18bXaaOSq8MWKuMxo/4y7EB7/i7G90y5QsKHZRmkoDo=", + "h1:4vZVOpKeEQZsF2VrARRZFeL37Ed/gD4rRMtfnvWQres=", + "h1:BZOsTF83QPKXTAaYqxPKzdl1KRjk/L2qbPpFjM0w28A=", + "h1:CDuC+HXLvc1z6wkCRsSDcc/+QENIHEtssYshiWg3opA=", + "h1:DE+YFzLnqSe79pI2R4idRGx5QzLdrA7RXvngTkGfZ30=", + "h1:DfaJwH3Ml4yrRbdAY4AcDVy0QTQk5T3A622TXzS/u2E=", + "h1:EIDXP0W3kgIv2pecrFmqtK/DnlqkyckzBzhxKaXU+4A=", + "h1:EV4kYyaOnwGA0bh/3hU6Ezqnt1PFDxopH7i85e48IzY=", + "h1:M0iXabfzamU+MPDi0G9XACpbacFKMakmM+Z9HZ8HrsM=", + "h1:YWmCbGF/KbsrUzcYVBLscwLizidbp95TDQa0N2qpmVo=", + "h1:cxPcCB5gbrpUO1+IXkQYs1YTY50/0IlApCzGea0cwuQ=", + "h1:g6DldikTV2HXUu9uoeNY5FuLufgaYWF4ufgZg7wq62s=", + "h1:oi/Hrx9pwoQ+Z52CBC+rrowVH387EIj0qvnxQgDeI+0=", + "zh:1a3400cb38863b2585968d1876706bcfc67a148e1318a1d325c6c7704adc999b", + "zh:4c5062cb9e9da1676f06ae92b8370186d98976cc4c7030d3cd76df12af54282a", + "zh:52110f493b5f0587ef77a1cfd1a67001fd4c617b14c6502d732ab47352bdc2f7", + "zh:5aa536f9eaeb43823aaf2aa80e7d39b25ef2b383405ed034aa16a28b446a9238", + "zh:5cc39459a1c6be8a918f17054e4fbba573825ed5597dcada588fe99614d98a5b", + "zh:629ae6a7ba298815131da826474d199312d21cec53a4d5ded4fa56a692e6f072", + "zh:719cc7c75dc1d3eb30c22ff5102a017996d9788b948078c7e1c5b3446aeca661", + "zh:8698635a3ca04383c1e93b21d6963346bdae54d27177a48e4b1435b7f731731c", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", - "zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f", - "zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0", - "zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b", - "zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30", - "zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6", - "zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91", - "zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29", - "zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb", - "zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11", + "zh:8a9993f1dcadf1dd6ca43b23348abe374605d29945a2fafc07fb3457644e6a54", + "zh:b1b9a1e6bcc24d5863a664a411d2dc906373ae7a2399d2d65548ce7377057852", + "zh:b270184cdeec277218e84b94cb136fead753da717f9b9dc378e51907f3f00bb0", + "zh:dff2bc10071210181726ce270f954995fe42c696e61e2e8f874021fed02521e5", + "zh:e8e87b40b6a87dc097b0fdc20d3f725cec0d82abc9cc3755c1f89f8f6e8b0036", + "zh:ee964a6573d399a5dd22ce328fb38ca1207797a02248f14b2e4913ee390e7803", ] } diff --git a/deployment/modules/cloudflare/docs/config.tf b/deployment/modules/cloudflare/docs/config.tf index ea1a441a4b..63347cf67e 100644 --- a/deployment/modules/cloudflare/docs/config.tf +++ b/deployment/modules/cloudflare/docs/config.tf @@ -5,7 +5,7 @@ terraform { required_providers { cloudflare = { source = "cloudflare/cloudflare" - version = "4.52.8" + version = "4.52.5" } } } diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 3f807a1f17..bcfb86a2aa 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -20,15 +20,14 @@ services: - /tmp volumes: - ..:/usr/src/app - # - ../../ui:/usr/src/ui - - build_cache:/buildcache + - pnpm_cache:/buildcache/pnpm_cache - server_node_modules:/usr/src/app/server/node_modules - web_node_modules:/usr/src/app/web/node_modules - github_node_modules:/usr/src/app/.github/node_modules - - cli_node_modules:/usr/src/app/packages/cli/node_modules + - cli_node_modules:/usr/src/app/cli/node_modules - docs_node_modules:/usr/src/app/docs/node_modules - e2e_node_modules:/usr/src/app/e2e/node_modules - - sdk_node_modules:/usr/src/app/packages/sdk/node_modules + - sdk_node_modules:/usr/src/app/open-api/typescript-sdk/node_modules - app_node_modules:/usr/src/app/node_modules - sveltekit:/usr/src/app/web/.svelte-kit - coverage:/usr/src/app/web/coverage @@ -45,11 +44,11 @@ services: target: dev command: - | - mise install + pnpm install touch /tmp/init-complete exec tail -f /dev/null volumes: - - build_cache:/buildcache + - pnpm_store_server:/buildcache/pnpm-store restart: 'no' healthcheck: test: ['CMD', 'test', '-f', '/tmp/init-complete'] @@ -73,7 +72,8 @@ services: volumes: - ${UPLOAD_LOCATION}/photos:/data - /etc/localtime:/etc/localtime:ro - - ../packages/plugin-core:/build/plugins/immich-plugin-core + - pnpm_store_server:/buildcache/pnpm-store + - ../plugins:/build/corePlugin env_file: - .env environment: @@ -121,6 +121,8 @@ services: ports: - 3000:3000 - 24678:24678 + volumes: + - pnpm_store_web:/buildcache/pnpm-store restart: unless-stopped depends_on: immich-init: @@ -154,9 +156,9 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3acc0687f2a2e1091fae6450d7842dd658c941338cf0a873ddd9e14b9e4ea4dd + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 healthcheck: - test: redis-cli ping | grep -q PONG || exit 1 + test: redis-cli ping || exit 1 database: container_name: immich_postgres @@ -200,7 +202,9 @@ volumes: model_cache: prometheus_data: grafana_data: - build_cache: + pnpm_cache: + pnpm_store_server: + pnpm_store_web: server_node_modules: web_node_modules: github_node_modules: diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index c071ae282b..700f68ff19 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -56,9 +56,9 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3acc0687f2a2e1091fae6450d7842dd658c941338cf0a873ddd9e14b9e4ea4dd + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 healthcheck: - test: redis-cli ping | grep -q PONG || exit 1 + test: redis-cli ping || exit 1 restart: always database: @@ -85,7 +85,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:508729e0e2d18e11fd742a5a5ca70e557b940a93948c3c95fd0123a6fd538b69 + image: prom/prometheus@sha256:dda13e28bf95a5e5ca5b8ed56852006094c1c8e8871d9c9dbeed30aa6e55271f volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus @@ -97,7 +97,7 @@ services: command: ['./run.sh', '-disable-reporting'] ports: - 3000:3000 - image: grafana/grafana:12.4.8-ubuntu@sha256:473cfc1712694135a5d9dc012dc573f2a48e51efb9ac0da50418918f3cbd8ad3 + image: grafana/grafana:12.4.2-ubuntu@sha256:78839fe49e1425c02416fa8072591533a72bd9598e563b54a07d78f9e27fb5d3 volumes: - grafana-data:/var/lib/grafana diff --git a/docker/docker-compose.rootless.yml b/docker/docker-compose.rootless.yml index d92d6669bd..c16a623807 100644 --- a/docker/docker-compose.rootless.yml +++ b/docker/docker-compose.rootless.yml @@ -51,6 +51,8 @@ services: - NET_RAW volumes: - ./ml-model-cache:/cache + - ./ml-dotcache:/.cache + - ./ml-config:/.config env_file: - .env restart: always @@ -59,7 +61,7 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3acc0687f2a2e1091fae6450d7842dd658c941338cf0a873ddd9e14b9e4ea4dd + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 user: '1000:1000' security_opt: - no-new-privileges:true @@ -68,7 +70,7 @@ services: volumes: - ./redis:/data healthcheck: - test: redis-cli ping | grep -q PONG || exit 1 + test: redis-cli ping || exit 1 restart: always database: @@ -93,3 +95,6 @@ services: restart: always healthcheck: disable: false + +volumes: + model-cache: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b13add0998..610b375011 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -49,9 +49,9 @@ services: redis: container_name: immich_redis - image: docker.io/valkey/valkey:9@sha256:3acc0687f2a2e1091fae6450d7842dd658c941338cf0a873ddd9e14b9e4ea4dd + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 healthcheck: - test: redis-cli ping | grep -q PONG || exit 1 + test: redis-cli ping || exit 1 restart: always database: diff --git a/docker/example.env b/docker/example.env index 2403d69a85..6641cceaaa 100644 --- a/docker/example.env +++ b/docker/example.env @@ -10,7 +10,7 @@ DB_DATA_LOCATION=./postgres # TZ=Etc/UTC # The Immich version to use. You can pin this to a specific version like "v2.1.0" -IMMICH_VERSION=v3 +IMMICH_VERSION=v2 # Connection secret for postgres. You should change it to a random password # Please use only the characters `A-Za-z0-9`, without special characters or spaces diff --git a/docs/.nvmrc b/docs/.nvmrc new file mode 100644 index 0000000000..8e35034890 --- /dev/null +++ b/docs/.nvmrc @@ -0,0 +1 @@ +24.14.1 diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx index 869e84e377..7b7a265ddf 100644 --- a/docs/docs/FAQ.mdx +++ b/docs/docs/FAQ.mdx @@ -26,8 +26,6 @@ For organizations seeking to resell Immich, we have established the following gu When in doubt or if you have an edge case scenario, we encourage you to contact us directly via email to discuss the use of our trademark. We can provide clear guidance on what is acceptable and what is not. You can reach out at: questions@immich.app ---- - ## User ### How can I reset the admin password? @@ -38,10 +36,6 @@ The admin password can be reset by running the [reset-admin-password](/administr You can see the list of all users by running [list-users](/administration/server-commands.md) Command on the Immich-server. -### How can I change my profile picture? - -View a single photo, press the three dots in the top-right to show context menu, and select "Set as profile picture". In the pop-up, use your mouse scroll wheel to zoom in the picture until it completely fills the circle. Click and drag the picture to align it to your liking. Press "Save" to save your changes. - --- ## Mobile App @@ -409,10 +403,12 @@ To decrease Redis logs, you can add the following line to the `redis:` section o You can change the user in the container by setting the `user` argument in `docker-compose.yml` for each service. -[Example docker-compose.rootless.yml file](https://github.com/immich-app/immich/blob/main/docker/docker-compose.rootless.yml) +[Example docker-compose.yml file](https://github.com/immich-app/immich/blob/main/docker/docker-compose.rootless.yml) You may need to add mount points or docker volumes for the following internal container paths: +- `immich-machine-learning:/.config` +- `immich-machine-learning:/.cache` - `redis:/data` The non-root user/group needs read/write access to the volume mounts, including `UPLOAD_LOCATION` and `/cache` for machine-learning. diff --git a/docs/docs/administration/backup-and-restore.md b/docs/docs/administration/backup-and-restore.md index df34671a1b..ae605f8462 100644 --- a/docs/docs/administration/backup-and-restore.md +++ b/docs/docs/administration/backup-and-restore.md @@ -31,7 +31,7 @@ You can trigger a database backup manually: 1. Go to **Administration > Job Queues** 2. Click **Create job** in the top right -3. Select **Create Database Dump** and click **Confirm** +3. Select **Create Database Backup** and click **Confirm** The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit. @@ -210,7 +210,7 @@ The provided restore process ensures your database is never in a broken state by ## Filesystem -Immich does not handle filesystem backups for you. You have to arrange these yourself! Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders: +Immich stores two types of content in the filesystem: (a) original, unmodified assets (photos and videos), and (b) generated content. We recommend backing up the entire contents of `UPLOAD_LOCATION`, but only the original content is critical, which is stored in the following folders: 1. `UPLOAD_LOCATION/library` 2. `UPLOAD_LOCATION/upload` diff --git a/docs/docs/administration/email-notification.mdx b/docs/docs/administration/email-notification.mdx index 6273d03b08..0da132161f 100644 --- a/docs/docs/administration/email-notification.mdx +++ b/docs/docs/administration/email-notification.mdx @@ -14,8 +14,6 @@ Under Email, enter the required details to connect with an SMTP server. You can use [this guide](/guides/smtp-gmail) to use Gmail's SMTP server. -You can use [this guide](/guides/smtp-microsoft365) to use Microsoft's SMTP server. - ## User's notifications settings Users can manage their email notification settings from their account settings page on the web. They can choose to turn email notifications on or off for the following events: diff --git a/docs/docs/administration/img/reset-admin-password.webp b/docs/docs/administration/img/reset-admin-password.webp new file mode 100644 index 0000000000..5062d24a6b Binary files /dev/null and b/docs/docs/administration/img/reset-admin-password.webp differ diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md index 06fdda6997..3b1e8c729d 100644 --- a/docs/docs/administration/oauth.md +++ b/docs/docs/administration/oauth.md @@ -50,10 +50,6 @@ Before enabling OAuth in Immich, a new client application needs to be configured - `https://immich.example.com/auth/login` - `https://immich.example.com/user-settings` -3. Configure Backchannel logout URL - - If the authentication server supports it, the **Backchannel logout URL** can be specified, and it is of the form: `http://DOMAIN:PORT/api/oauth/backchannel-logout`. - ## Enable OAuth Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings). @@ -67,8 +63,6 @@ Once you have a new OAuth client application configured, Immich can be configure | `scope` | string | openid email profile | Full list of scopes to send with the request (space delimited) | | `id_token_signed_response_alg` | string | RS256 | The algorithm used to sign the id token (examples: RS256, HS256) | | `userinfo_signed_response_alg` | string | none | The algorithm used to sign the userinfo response (examples: RS256, HS256) | -| `prompt` | string | (empty) | Prompt parameter for authorization url (examples: select_account, login, consent) | -| `end_session_endpoint` | URL | (empty) | Http(s) alternative end session endpoint (logout URI) | | Request timeout | string | 30,000 (30 seconds) | Number of milliseconds to wait for http requests to complete before giving up | | Storage Label Claim | string | preferred_username | Claim mapping for the user's storage label**š** | | Role Claim | string | immich_role | Claim mapping for the user's role. (should return "user" or "admin")**š** | @@ -154,10 +148,9 @@ identity_providers: - client_id: 'immich' client_name: 'Immich' # https://www.authelia.com/integration/openid-connect/frequently-asked-questions/#how-do-i-generate-a-client-identifier-or-client-secret - client_secret: '$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' + client_secret: $pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng' public: false - require_pkce: true - pkce_challenge_method: 'S256' + require_pkce: false redirect_uris: - 'https://example.immich.app/auth/login' - 'https://example.immich.app/user-settings' @@ -181,14 +174,13 @@ Configuration of OAuth in Immich System Settings | Setting | Value | | ---------------------------------- | ------------------------------------------------------------------- | -| Issuer URL | `https://auth.example.com` | +| Issuer URL | `https://example.immich.app/.well-known/openid-configuration` | | Client ID | immich | | Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... | | Token Endpoint Auth Method | client_secret_post | | Scope | openid email profile immich_scope | | ID Token Signed Response Algorithm | RS256 | | Userinfo Signed Response Algorithm | RS256 | -| End Session Endpoint | https://auth.example.com/logout?rd=https://immich.example.com/ | | Storage Label Claim | uid | | Storage Quota Claim | immich_quota | | Default Storage Quota (GiB) | 0 (empty for unlimited quota) | @@ -213,21 +205,21 @@ Configuration of Authorised redirect URIs (Authentik OAuth2/OpenID Provider) Configuration of OAuth in Immich System Settings -| Setting | Value | -| ---------------------------- | ------------------------------------------------------------------- | -| Issuer URL | `https://authentik.example.com/application/o/immich/` | -| Client ID | AFCj2rM1f4rps**\*\*\*\***\***\*\*\*\***lCLEum6hH9... | -| Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... | -| Scope | openid email profile | -| Signing Algorithm | RS256 | -| Storage Label Claim | preferred_username | -| Storage Quota Claim | immich_quota | -| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | -| Button Text | Sign in with Authentik (optional) | -| Auto Register | Enabled (optional) | -| Auto Launch | Enabled (optional) | -| Mobile Redirect URI Override | Disable | -| Mobile Redirect URI | | +| Setting | Value | +| ---------------------------- | ---------------------------------------------------------------------------------- | +| Issuer URL | `https://example.immich.app/application/o/immich/.well-known/openid-configuration` | +| Client ID | AFCj2rM1f4rps**\*\*\*\***\***\*\*\*\***lCLEum6hH9... | +| Client Secret | 0v89FXkQOWO\***\*\*\*\*\***\*\*\***\*\*\*\*\***mprbvXD549HH6s1iw... | +| Scope | openid email profile | +| Signing Algorithm | RS256 | +| Storage Label Claim | preferred_username | +| Storage Quota Claim | immich_quota | +| Default Storage Quota (GiB) | 0 (empty for unlimited quota) | +| Button Text | Sign in with Authentik (optional) | +| Auto Register | Enabled (optional) | +| Auto Launch | Enabled (optional) | +| Mobile Redirect URI Override | Disable | +| Mobile Redirect URI | | @@ -281,7 +273,7 @@ Configuration of OAuth in Immich System Settings | ---------------------------- | ----------------------------------------------------- | | Issuer URL | `https:///realms/` | | Client ID | immich | -| Client Secret | can be obtained from Clients -> immich -> Credentials | +| Client Secret | can be optained from Clients -> immich -> Credentials | | Scope | openid email profile | | Signing Algorithm | RS256 | | Storage Label Claim | preferred_username | diff --git a/docs/docs/administration/postgres-standalone.md b/docs/docs/administration/postgres-standalone.md index fa108995c7..84681fdfa6 100644 --- a/docs/docs/administration/postgres-standalone.md +++ b/docs/docs/administration/postgres-standalone.md @@ -17,7 +17,7 @@ running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres versio You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`. :::note Supported versions -Immich is known to work with Postgres versions `>= 14, < 20`. +Immich is known to work with Postgres versions `>= 14, < 19`. VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`. @@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor ### Migrating from pgvecto.rs -Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so. +Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so. The easiest option is to have both extensions installed during the migration: @@ -144,7 +144,7 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
Migration steps -1. Ensure you have at least `0.7.0` of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client +1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client 2. Follow the Prerequisites to install VectorChord 3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;` 4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set diff --git a/docs/docs/administration/reverse-proxy.md b/docs/docs/administration/reverse-proxy.md index 3c90c26cf9..b53356139f 100644 --- a/docs/docs/administration/reverse-proxy.md +++ b/docs/docs/administration/reverse-proxy.md @@ -112,7 +112,7 @@ services: traefik.enable: true # increase readingTimeouts for the entrypoint used here traefik.http.routers.immich.entrypoints: websecure - traefik.http.routers.immich.rule: Host(`immich.example.com`) + traefik.http.routers.immich.rule: Host(`immich.your-domain.com`) traefik.http.services.immich.loadbalancer.server.port: 2283 ``` diff --git a/docs/docs/administration/server-commands.md b/docs/docs/administration/server-commands.md index 49bcf17700..cbd029296f 100644 --- a/docs/docs/administration/server-commands.md +++ b/docs/docs/administration/server-commands.md @@ -13,11 +13,8 @@ The `immich-server` docker image comes preinstalled with an administrative CLI ( | `enable-oauth-login` | Enable OAuth login | | `disable-oauth-login` | Disable OAuth login | | `list-users` | List Immich users | -| `grant-admin` | Grant admin privileges to a user (by email) | -| `revoke-admin` | Revoke admin privileges from a user (by email) | | `version` | Print Immich version | | `change-media-location` | Change database file paths to align with a new media location | -| `schema-check` | Verify database migrations and check for schema drift | ## How to run a command @@ -35,7 +32,6 @@ Found Admin: - Email=admin@example.com - Name=Immich Admin ? Please choose a new password (optional) immich-is-cool -? Invalidate existing sessions? Yes The admin password has been updated. ``` @@ -91,7 +87,7 @@ immich-admin list-users [ { id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53', - email: 'immich@example.com', + email: 'immich@example.com.com', name: 'Immich Admin', storageLabel: 'admin', externalPath: null, @@ -106,22 +102,6 @@ immich-admin list-users ] ``` -Grant Admin - -``` -immich-admin grant-admin -? Please enter the user email: user@example.com -Admin access has been granted to user@example.com -``` - -Revoke Admin - -``` -immich-admin revoke-admin -? Please enter the user email: user@example.com -Admin access has been revoked from user@example.com -``` - Print Immich Version ``` @@ -146,12 +126,3 @@ immich-admin change-media-location Database file paths updated successfully! 🎉 ... ``` - -Schema Check - -``` -immich-admin schema-check -Migrations are up to date - -No schema drift detected -``` diff --git a/docs/docs/administration/system-integrity.md b/docs/docs/administration/system-integrity.md index 71d1ada88b..2b373134a9 100644 --- a/docs/docs/administration/system-integrity.md +++ b/docs/docs/administration/system-integrity.md @@ -1,30 +1,5 @@ # System Integrity -## Integrity report - -At a [customizable interval](https://my.immich.app/admin/system-settings?isOpen=integrity-checks), Immich runs integrity checks to ensure that your library is still intact and there are no corrupt files. -There are three kind of issues Immich checks for: - -- Untracked files: the path was found in Immich's directories but it is not referenced in Immich's database -- Missing files: the path is found in the Immich internal database, but does not actually exist on disk -- Checksum mismatches: the checksum of the file stored in Immich's database does not match the actual file's checksum anymore - -All three run nightly at 3am by default. For the "Checksum files" check, there are additional time and progress limits, as those are the most taxing checks. With these additional limits, Immich can slowly check checksums of your files over the course of a couple of days. - -You can see the results of these checks on the [maintenance](https://my.immich.app/admin/maintenance) page. Here, you can also trigger a full scan (a _check_) for specific jobs, or all of them. In addition, you can also _refresh_ checks. This will only look at items that have currently been reported on, and check if those have been fixed. - -### Common causes - -Most common are untracked files. In many cases those are corrupted thumbnails or encoded videos that have been partially generated at some point and never got cleaned up properly. These are usually fine to delete, as both can always be regenerated at a later point. Other files will need to be investigated on a case-by-case basis by checking they already exist in Immich and thinking about how they might have gotten untracked. - -:::info -You might want to run the _missing_ jobs for thumbnails and encoded videos (https://my.immich.app/admin/queues) to make sure all your assets have proper thumbnails and encoded videos. Watch the server logs while running the jobs, in case there are any issues with some assets. -::: - -Missing files are files where Immich references them internally, but they don't actually exist on disk in that location. It could be that you deleted a file on disk from the internal library (don't do that, Immich doesn't support it). It could also be that there are issues with your file storage. Please carefully investigate missing files, and never hesitate to reach out on [our Discord](https://discord.immich.app) if you have any questions! - -Checksum mismatches are often indicative of file system corruption. It could also be that you previously edited a file from the internal library on the disk, which is also not supported and will cause a checksum mismatch. Again, the recommended action is to look at any reported item individually, check it out, try to remember if you changed it or some metadata on it at some point. If you edited the file, the supported resolution is to delete the mismatched asset from Immich and reupload it as a new asset. - ## Folder checks :::info @@ -65,7 +40,7 @@ The above error messages show that the server has previously (successfully) writ ### Ignoring the checks -:::danger +:::warning The checks are designed to catch common problems that we have seen users have in the past, and often indicate there's something wrong that you should solve. If you know what you're doing and you want to disable them you can set the following environment variable: ::: diff --git a/docs/docs/api.md b/docs/docs/api.md index debd75e4d4..edf58dc94d 100644 --- a/docs/docs/api.md +++ b/docs/docs/api.md @@ -7,7 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK. ```bash -mise open-api +make open-api ``` -You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/generated/openapi` for Dart SDK. +You can find the generated client SDK in the `open-api/typescript-sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK. diff --git a/docs/docs/developer/database-migrations.md b/docs/docs/developer/database-migrations.md index 05f5941f70..a73e7e747c 100644 --- a/docs/docs/developer/database-migrations.md +++ b/docs/docs/developer/database-migrations.md @@ -5,7 +5,7 @@ After making any changes in the `server/src/schema`, a database migration need t 1. Run the command ```bash -mise //server:migrations generate +pnpm run migrations:generate ``` 2. Check if the migration file makes sense. @@ -18,7 +18,7 @@ The server will automatically detect `*.ts` file changes and restart. Part of th If you need to undo the most recently applied migration—for example, when developing or testing on schema changes—run: ```bash -mise //server:migrations revert +pnpm run migrations:revert ``` This command rolls back the latest migration and brings the database schema back to its previous state. diff --git a/docs/docs/developer/devcontainers.md b/docs/docs/developer/devcontainers.md index 459bfa56c5..4bd60262ad 100644 --- a/docs/docs/developer/devcontainers.md +++ b/docs/docs/developer/devcontainers.md @@ -205,7 +205,7 @@ When the Dev Container starts, it automatically: 1. **Runs post-create script** (`container-server-post-create.sh`): - Adjusts file permissions for the `node` user - Installs dependencies: `pnpm install` in all packages - - Builds TypeScript SDK: `pnpm --filter @immich/sdk build` + - Builds TypeScript SDK: `pnpm run build` in `open-api/typescript-sdk` 2. **Starts development servers** via VS Code tasks: - `Immich API Server (Nest)` - API server with hot-reloading on port 2283 @@ -218,7 +218,7 @@ When the Dev Container starts, it automatically: - Debug ports: 9230 (workers), 9231 (API) :::info -The Dev Container setup replaces the `mise dev` command from the traditional setup. All services start automatically when you open the container. +The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container. ::: ### Accessing Services @@ -243,8 +243,8 @@ To connect the mobile app to your Dev Container: - **Server code** (`/server`): Changes trigger automatic restart - **Web code** (`/web`): Changes trigger hot module replacement -- **Database migrations**: Run `mise //:sql` -- **API changes**: Regenerate TypeScript SDK with `mise //:open-api` +- **Database migrations**: Run `pnpm run sync:sql` in the server directory +- **API changes**: Regenerate TypeScript SDK with `make open-api` ## Testing @@ -252,33 +252,85 @@ To connect the mobile app to your Dev Container: The Dev Container supports multiple ways to run tests: +#### Using Make Commands (Recommended) + ```bash -# Server -mise //server:test # unit tests -mise //server:test-medium # medium / integration tests +# Run tests for specific components +make test-server # Server unit tests +make test-web # Web unit tests +make test-e2e # End-to-end tests +make test-cli # CLI tests -# Web -mise //web:test # unit tests +# Run all tests +make test-all # Runs tests for all components -# E2E -mise //e2e:test # API tests -mise //e2e:test-web # web UI tests (Playwright) - -# Run all checks for a component -mise //server:checklist -mise //web:checklist +# Medium tests (integration tests) +make test-medium-dev # End-to-end tests ``` -### Additional Commands +#### Using PNPM Directly ```bash +# Server tests +cd /workspaces/immich/server +pnpm test # Run all tests +pnpm run test:medium # Medium tests (integration tests) +pnpm run test:watch # Watch mode +pnpm run test:cov # Coverage report + +# Web tests +cd /workspaces/immich/web +pnpm test # Run all tests +pnpm run test:watch # Watch mode + +# E2E tests +cd /workspaces/immich/e2e +pnpm run test # Run API tests +pnpm run test:web # Run web UI tests +``` + +### Code Quality Commands + +```bash +# Linting +make lint-server # Lint server code +make lint-web # Lint web code +make lint-all # Lint all components + +# Formatting +make format-server # Format server code +make format-web # Format web code +make format-all # Format all code + +# Type checking +make check-server # Type check server +make check-web # Type check web +make check-all # Check all components + +# Complete hygiene check +make hygiene-all # Run lint, format, check, SQL sync, and audit +``` + +### Additional Make Commands + +```bash +# Build commands +make build-server # Build server +make build-web # Build web app +make build-all # Build everything + # API generation -mise //:open-api # Generate OpenAPI specs -mise //:open-api-typescript # Generate TypeScript SDK -mise //:open-api-dart # Generate Dart SDK +make open-api # Generate OpenAPI specs +make open-api-typescript # Generate TypeScript SDK +make open-api-dart # Generate Dart SDK # Database -mise //server:sql # Sync database schema +make sql # Sync database schema + +# Dependencies +make install-server # Install server dependencies +make install-web # Install web dependencies +make install-all # Install all dependencies ``` ### Debugging diff --git a/docs/docs/developer/directories.md b/docs/docs/developer/directories.md index ef3d8d65a6..409353e2c4 100644 --- a/docs/docs/developer/directories.md +++ b/docs/docs/developer/directories.md @@ -10,13 +10,12 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht | :------------------ | :------------------------------------------------------------------- | | `.github/` | Github templates and action workflows | | `.vscode/` | VSCode debug launch profiles | -| `packages/cli` | Source code for the CLI | -| `packages/sdk` | Source code for the generated OpenAPI SDK | -| `packages/scripts` | Scripts for version bumps and draft releases | +| `cli/` | Source code for the work-in-progress CLI rewrite | | `docker/` | Docker compose resources for dev, test, production | | `design/` | Screenshots and logos for the README | | `docs/` | Source code for the [https://immich.app](https://immich.app) website | | `machine-learning/` | Source code for the `immich-machine-learning` docker image | +| `misc/release/` | Scripts for version bumps and draft releases | | `mobile/` | Source code for the mobile app, both Android and iOS | | `server/` | Source code for the `immich-server` docker image | | `web/` | Source code for the `web` | diff --git a/docs/docs/developer/pr-checklist.md b/docs/docs/developer/pr-checklist.md index 4ad232a312..e5dc6cc1e5 100644 --- a/docs/docs/developer/pr-checklist.md +++ b/docs/docs/developer/pr-checklist.md @@ -2,74 +2,53 @@ A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment. :::warning -The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`mise dev`, ....). Feel free to contribute! +The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute! ::: When contributing code through a pull request, please check the following: ## Web Checks -- [ ] `mise //web:lint` (linting via ESLint) -- [ ] `mise //web:format` (formatting via Prettier) -- [ ] `mise //web:check-svelte` (type checking via SvelteKit) -- [ ] `mise //web:check-typescript` (type checking via `tsc`) -- [ ] `mise //web:test` (unit tests) +- [ ] `pnpm run lint` (linting via ESLint) +- [ ] `pnpm run format` (formatting via Prettier) +- [ ] `pnpm run check:svelte` (Type checking via SvelteKit) +- [ ] `pnpm run check:typescript` (check typescript) +- [ ] `pnpm test` (unit tests) :::tip AIO -Run all web checks with `mise //web:checklist` -::: - -:::tip Auto Fix -Use `mise //web:lint-fix` and `mise //web:format-fix` to automatically correct some issues. +Run all web checks with `pnpm run check:all` ::: ## Documentation -- [ ] `mise //docs:format` (formatting via Prettier) +- [ ] `pnpm run format` (formatting via Prettier) - [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation. -:::tip Auto Fix -Use `mise //docs:format-fix` to automatically fix formatting. -::: - ## Server Checks -- [ ] `mise //server:lint` (linting via ESLint) -- [ ] `mise //server:format` (formatting via Prettier) -- [ ] `mise //server:check` (type checking via `tsc`) -- [ ] `mise //server:test` (unit tests) +- [ ] `pnpm run lint` (linting via ESLint) +- [ ] `pnpm run format` (formatting via Prettier) +- [ ] `pnpm run check` (Type checking via `tsc`) +- [ ] `pnpm test` (unit tests) :::tip AIO -Run all server checks with `mise //server:checklist` +Run all server checks with `pnpm run check:all` ::: -:::tip Auto Fix -Use `mise //server:lint-fix` and `mise //server:format-fix` to automatically correct some issues. +:::info Auto Fix +You can use `pnpm run __:fix` to potentially correct some issues automatically for `pnpm run format` and `lint`. ::: -## Mobile Checklist +## Mobile Checks -- [ ] `mise //mobile:codegen` (auto-generate files using build_runner) -- [ ] `mise //mobile:lint` (static analysis via Dart Analyzer and DCM) -- [ ] `mise //mobile:format` (formatting via Dart Formatter) -- [ ] `mise //mobile:test` (unit tests) +The following commands must be executed from within the mobile app directory of the codebase. -:::tip -Run all these commands at once with `mise //mobile:checklist` -::: +- [ ] `make build` (auto-generate files using build_runner) +- [ ] `make analyze` (static analysis via Dart Analyzer and DCM) +- [ ] `make format` (formatting via Dart Formatter) +- [ ] `make test` (unit tests) -:::tip Auto Fix -You can use `mise //mobile:lint-fix` to potentially correct some issues automatically for `mise //mobile:lint`. -::: - -## Machine Learning Checklist - -- [ ] `mise //machine-learning:lint` (linting via ruff) -- [ ] `mise //machine-learning:format` (formatting via ruff) -- [ ] `mise //machine-learning:check` (type checking via mypy) -- [ ] `mise //machine-learning:test` (unit tests via pytest) - -:::tip AIO -Run all machine learning checks with `mise //machine-learning:checklist` +:::info Auto Fix +You can use `dart fix --apply` and `dcm fix lib` to potentially correct some issues automatically for `make analyze`. ::: ## OpenAPI diff --git a/docs/docs/developer/setup.md b/docs/docs/developer/setup.md index de02ab3e54..4bbf71dd89 100644 --- a/docs/docs/developer/setup.md +++ b/docs/docs/developer/setup.md @@ -30,29 +30,25 @@ This environment includes the services below. Additional details are available i - Redis - PostgreSQL development database with exposed port `5432` so you can use any database client to access it -All the services are packaged to run with a single Docker Compose command. - -:::tip mise -[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`. -::: +All the services are packaged to run as with single Docker Compose command. ### Server and web apps 1. Clone the project repo. 2. Run `cp docker/example.env docker/.env`. 3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`. -4. Install dependencies - `mise x -- pnpm i` +4. Install dependencies - `pnpm i` 5. From the root directory, run: ```bash title="Start development server" -mise dev +make dev # required Makefile installed on the system. ``` 5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app. All the services will be started with hot-reloading enabled for a quick feedback loop. -You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000` +You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000/api` **Notes:** @@ -60,23 +56,22 @@ You can access the web from `http://your-machine-ip:3000` or `http://localhost:3 #### Connect web to a remote backend -If you only want to do web development connected to an existing, remote backend, run from the repo root: +If you only want to do web development connected to an existing, remote backend, follow these steps: + +1. Build the Immich SDK - `cd open-api/typescript-sdk && pnpm i && pnpm run build && cd -` +2. Enter the web directory - `cd web/` +3. Install web dependencies - `pnpm i` +4. Start the web development server ```bash -IMMICH_SERVER_URL=https://demo.immich.app/ mise //web:start -``` - -This will install all dependencies (including the SDK) and start the dev server in one step. To connect to the hosted demo server specifically, use the shorthand: - -```bash -mise //web:start-demo +IMMICH_SERVER_URL=https://demo.immich.app/ pnpm run dev ``` If you're using PowerShell on Windows you may need to set the env var separately like so: ```powershell $env:IMMICH_SERVER_URL = "https://demo.immich.app/" -mise //web:start +pnpm run dev ``` #### `@immich/ui` @@ -85,63 +80,34 @@ To see local changes to `@immich/ui` in Immich, do the following: 1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui` 2. Build the `@immich/ui` project via `pnpm run build` -3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`) -4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`) -5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';` -6. Start up the stack via `mise dev` +3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`) +4. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`) +5. Uncomment the import statement in `web/src/app.css` file `@import '/usr/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';` +6. Start up the stack via `make dev` 7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`) ### Mobile app #### Setup -1. Run `mise //mobile:install` to install Flutter dependencies. -2. Run `mise //mobile:translation` to generate the translation file. -3. Change to the `mobile/` directory and run `flutter run` to start the app. - -##### iOS Code Signing - -The Immich Apple Team ID and bundle IDs are specified in `mobile/ios/Signing.xcconfig`. For local development, we provide an override mechanism. - -Create `mobile/ios/Signing.local.xcconfig` and populate it with the necessary values needed to build and sign Immich yourself. This local override file is gitignored. - -``` -IMMICH_TEAM_ID = ABCDE12345 -IMMICH_BUNDLE_ID_PROD = com.customuniqueid.immich -IMMICH_BUNDLE_ID_DEV = com.customuniqueid.immichdev -IMMICH_GROUP_ID = group.com.customuniqueid.immich -``` - -The environment values are used across Immich's targets and schemes to prevent redundant edits by contributors. +1. [Install mise](https://mise.jdx.dev/installing-mise.html). +2. Change to the immich (root) directory and trust the mise config with `mise trust`. +3. Install tools with mise: `mise install`. +4. Change to the `mobile/` directory. +5. Run `flutter pub get` to install the dependencies. +6. Run `make translation` to generate the translation file. +7. Run `flutter run` to start the app. #### Translation -To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run: +To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run ```bash -mise //mobile:translation +make translation ``` The mobile app asks you what backend to connect to. You can utilize the demo backend (https://demo.immich.app/) if you don't need to change server code or upload photos. Alternatively, you can run the server yourself per the instructions above. -#### UI components and widget previews - -Shared design-system widgets (buttons, inputs, forms) live in the -[`immich_ui` package](https://github.com/immich-app/immich/tree/main/mobile/packages/ui/) -under `mobile/packages/ui/`. Components are defined in `lib/src/components/` -and have matching previews in `lib/src/previews/`. - -To inspect a component in isolation with a light/dark toggle and hot reload, -launch [Flutter's Widget Previewer](https://docs.flutter.dev/tools/widget-previewer): - -```bash -cd mobile/packages/ui -flutter widget-preview start -``` - -In VS Code or Android Studio with the Flutter plugin, the previewer -auto-starts when you open the **Flutter Widget Preview** tab in the sidebar. - ## IDE setup ### Lint / format extensions diff --git a/docs/docs/developer/testing.md b/docs/docs/developer/testing.md index d7224d311b..d7c9edcd31 100644 --- a/docs/docs/developer/testing.md +++ b/docs/docs/developer/testing.md @@ -4,26 +4,28 @@ ### Unit tests -Unit tests are run with `mise //server:test`. -You need to run `mise //server:install` before _once_. +Unit are run by calling `pnpm run test` from the `server/` directory. +You need to run `pnpm install` (in `server/`) before _once_. ### End to end tests The e2e tests can be run by first starting up a test production environment via: ```bash -mise e2e +make e2e ``` Before you can run the tests, you need to run the following commands _once_: -- `mise //e2e:ci-setup` (installs e2e, SDK, and CLI dependencies) -- `mise //:open-api` +- `pnpm install` (in `e2e/`) +- `pnpm run build` (in `cli/`) +- `make open-api` (in the project root `/`) Once the test environment is running, the e2e tests can be run via: ```bash -mise //e2e:test +cd e2e/ +pnpm test ``` The tests check various things including: diff --git a/docs/docs/features/command-line-interface.md b/docs/docs/features/command-line-interface.md index 965db81b07..03e96e5080 100644 --- a/docs/docs/features/command-line-interface.md +++ b/docs/docs/features/command-line-interface.md @@ -15,7 +15,7 @@ If you are looking to import your Google Photos takeout, we recommend this commu ## Requirements -- Node.js 22 or above +- Node.js 20 or above - Npm If you can't install node/npm, there is also a Docker version available below. @@ -99,7 +99,6 @@ Options: -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 Add all assets to specified album (env: IMMICH_ALBUM_NAME) - --visibility Set the visibility of uploaded assets (choices: "archive", "timeline", "hidden", "locked", env: IMMICH_VISIBILITY) -n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN) -c, --concurrency 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) @@ -177,12 +176,6 @@ By default, hidden files are skipped. If you want to include hidden files, use t immich upload --include-hidden --recursive directory/ ``` -You can set the visibility of uploaded assets to `archive`, `timeline`, `hidden`, or `locked` with the `--visibility` option: - -```bash -immich upload --visibility archive --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. diff --git a/docs/docs/features/duplicates-utility.md b/docs/docs/features/duplicates-utility.md index 5d68c0ace9..f790c42708 100644 --- a/docs/docs/features/duplicates-utility.md +++ b/docs/docs/features/duplicates-utility.md @@ -15,14 +15,14 @@ When using "Deduplicate All" or viewing suggestions, Immich automatically presel ### Synchronizing metadata -When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept asset. This synchronization only happens when **exactly one** asset is kept and at least one asset is trashed. When more than one asset is kept, metadata is not merged — the assets keep their own metadata and are simply removed from the duplicate group. The following metadata is synchronized: +When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept assets. The following metadata is synchronized: -| Name | Description | -| ----------- | ------------------------------------------------------------------------------------------------------------------------------ | -| Album | The kept asset will be added to _every_ album that the other assets in the group belong to. | -| Favorite | If any of the assets in the group have been added to favorites, the kept asset will also be added to favorites. | -| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept asset. | -| Description | Descriptions from each asset are combined together and synchronized to the kept asset. | -| Visibility | The most restrictive visibility is applied to the kept asset. | -| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. | -| Tag | Tags from all assets in the group are merged and applied to the kept asset. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Album | The kept assets will be added to _every_ album that the other assets in the group belong to. | +| Favorite | If any of the assets in the group have been added to favorites, every kept asset will also be added to favorites. | +| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept assets. | +| Description | Descriptions from each asset are combined together and synchronized to all the kept assets. | +| Visibility | The most restrictive visibility is applied to the kept assets. | +| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. | +| Tag | Tags from all assets in the group are merged and applied to every kept asset. | diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md index 62831ab089..6e8246b06c 100644 --- a/docs/docs/features/libraries.md +++ b/docs/docs/features/libraries.md @@ -50,8 +50,6 @@ Some basic examples: - `**/Raw/**` will exclude all files in any directory named `Raw` - `**/*.{tif,jpg}` will exclude all files with the extension `.tif` or `.jpg` -Note that `*` is a wildcard matching zero or more characters (i.e., withinin a filename or single directory name). `**` matches zero or more subdirectories, recursively. It also includes any/all files within a subdirectory, i.e., when used at the end of a pattern. For example, `**/exclude_me/**` will exclude all files in any directory named `exclude_me`, as well as all files in any subdirectories of `exclude_me`, recursively. - Special characters such as @ should be escaped, for instance: - `**/\@eaDir/**` will exclude all files in any directory named `@eaDir` diff --git a/docs/docs/features/ml-hardware-acceleration.md b/docs/docs/features/ml-hardware-acceleration.md index 5ad0bcd11f..bd4fe49e96 100644 --- a/docs/docs/features/ml-hardware-acceleration.md +++ b/docs/docs/features/ml-hardware-acceleration.md @@ -47,7 +47,6 @@ You do not need to redo any machine learning jobs after enabling hardware accele #### ROCm -- On Linux, The [AMDGPU driver module](https://rocm.docs.amd.com/projects/install-on-linux/en/latest/how-to/docker.html) needs to be installed on the server and, if secure boot is used, the signing key of DKMS [needs to be enrolled in UEFI BIOS](https://wiki.debian.org/SecureBoot) - The GPU must be supported by ROCm. If it isn't officially supported, you can attempt to use the `HSA_OVERRIDE_GFX_VERSION` environmental variable: `HSA_OVERRIDE_GFX_VERSION=`. If this doesn't work, you might need to also set `HSA_USE_SVM=0`. - The ROCm image is quite large and requires at least 35GiB of free disk space. However, pulling later updates to the service through Docker will generally only amount to a few hundred megabytes as the rest will be cached. - This backend is new and may experience some issues. For example, GPU power consumption can be higher than usual after running inference, even if the machine learning service is idle. In this case, it will only go back to normal after being idle for 5 minutes (configurable with the [MACHINE_LEARNING_MODEL_TTL](/install/environment-variables) setting). diff --git a/docs/docs/features/mobile-app.mdx b/docs/docs/features/mobile-app.mdx index 1b7c7e21d5..59a4844c46 100644 --- a/docs/docs/features/mobile-app.mdx +++ b/docs/docs/features/mobile-app.mdx @@ -13,9 +13,7 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md'; :::info Android verification Below are the SHA-256 fingerprints for the certificates signing the android applications. -- Google Play releases: - `5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6` -- GitHub releases: +- Playstore / Github releases: `86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20` - F-Droid releases: `FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF` diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md index 1bdfeca8ba..92eb01c39d 100644 --- a/docs/docs/features/searching.md +++ b/docs/docs/features/searching.md @@ -18,7 +18,6 @@ You can search the following types of content: | People | Faces that are recognized in your photos/videos. | | Contextual | Content of the photos and videos. | | File name or extension | Full or partial file's name, or file's extension | -| Full path or folder | Full or partial folder names from the original path. | | Description | Description added to assets. | | Optical Character Recognition (OCR) | Text in images | | Locations | Cities, states, and countries from reverse geocoding. | @@ -31,12 +30,6 @@ You can search the following types of content: -### Full path or folder - -Use this mode when you know a folder name or part of the original asset path. - -Example: for /John/Projects/3D_Printing/2026-07-01/IMG_0001.jpg, searches like Projects, 3D, Printing, or 2026 match the asset. - ## Configuration Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available. diff --git a/docs/docs/features/supported-formats.md b/docs/docs/features/supported-formats.md index 917950dd2a..86ac264cc3 100644 --- a/docs/docs/features/supported-formats.md +++ b/docs/docs/features/supported-formats.md @@ -18,7 +18,6 @@ For the full list, refer to the [Immich source code](https://github.com/immich-a | `JPEG 2000` | `.jp2` | :white_check_mark: | | | `JPEG` | `.jpeg` `.jpg` `.jpe` `.insp` | :white_check_mark: | | | `JPEG XL` | `.jxl` | :white_check_mark: | | -| `MPO` | `.mpo` | :white_check_mark: | Multi-Picture | | `PNG` | `.png` | :white_check_mark: | | | `PSD` | `.psd` | :white_check_mark: | Adobe Photoshop | | `RAW` | `.raw` | :white_check_mark: | | diff --git a/docs/docs/guides/database-gui.md b/docs/docs/guides/database-gui.md index f9e90c166c..67b658f838 100644 --- a/docs/docs/guides/database-gui.md +++ b/docs/docs/guides/database-gui.md @@ -17,7 +17,7 @@ services: ports: - "8888:80" environment: - PGADMIN_DEFAULT_EMAIL: admin@example.com + PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com PGADMIN_DEFAULT_PASSWORD: strong-password volumes: - pgadmin-data:/var/lib/pgadmin diff --git a/docs/docs/guides/img/email-ms-settings.webp b/docs/docs/guides/img/email-ms-settings.webp deleted file mode 100644 index 467e3712b0..0000000000 Binary files a/docs/docs/guides/img/email-ms-settings.webp and /dev/null differ diff --git a/docs/docs/guides/python-file-upload.md b/docs/docs/guides/python-file-upload.md index 6816924e6f..684524f9c4 100644 --- a/docs/docs/guides/python-file-upload.md +++ b/docs/docs/guides/python-file-upload.md @@ -20,6 +20,8 @@ def upload(file): } data = { + 'deviceAssetId': f'{file}-{stats.st_mtime}', + 'deviceId': 'python', 'fileCreatedAt': datetime.fromtimestamp(stats.st_mtime), 'fileModifiedAt': datetime.fromtimestamp(stats.st_mtime), 'isFavorite': 'false', diff --git a/docs/docs/guides/remote-access.md b/docs/docs/guides/remote-access.md index 09e34c5107..518b003c3a 100644 --- a/docs/docs/guides/remote-access.md +++ b/docs/docs/guides/remote-access.md @@ -39,7 +39,7 @@ You can learn how to set up Tailscale together with Immich with the [tutorial vi ### Cons - The Tailscale client usually needs to run as root on your devices and it increases the attack surface slightly compared to a minimal Wireguard server. e.g., an [RCE vulnerability](https://github.com/tailscale/tailscale/security/advisories/GHSA-vqp6-rc3h-83cp) was discovered in the Windows Tailscale client in November 2022. -- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) suitable for personal use. +- Tailscale is a paid service. However, there is a generous [free tier](https://tailscale.com/pricing/) that permits up to 3 users and up to 100 devices. - Tailscale needs to be installed and running on both server-side and client-side. ## Option 3: Reverse Proxy diff --git a/docs/docs/guides/smtp-microsoft365.md b/docs/docs/guides/smtp-microsoft365.md deleted file mode 100644 index 2ba78a4e18..0000000000 --- a/docs/docs/guides/smtp-microsoft365.md +++ /dev/null @@ -1,19 +0,0 @@ -# SMTP settings using Microsoft 365 - -This guide walks you through how to get the information you need to set up your Immich instance to send emails using Microsoft's SMTP server. - -## Create an app password - -You will need to generate an app password to use your Microsoft email in Immich. Depending on if you have a personal or business account, you can use https://go.microsoft.com/fwlink/?linkid=2274139 or https://myaccount.microsoft.com/securtiy-info respectively. - -## Entering the SMTP credential in Immich - -Entering your credential in Immich's email notification settings at `Administration -> Settings -> Notification Settings` - -Host: smtp-mail.outlook.com -Port: 587 -username: your mail address -Password: app password you created earlier -SMTPS: set it to disabled - - diff --git a/docs/docs/guides/template-backup-script.md b/docs/docs/guides/template-backup-script.md index dfec17a99a..19647d4ae1 100644 --- a/docs/docs/guides/template-backup-script.md +++ b/docs/docs/guides/template-backup-script.md @@ -52,9 +52,9 @@ REMOTE_BACKUP_PATH="/path/to/remote/backup/directory" ### Local # Backup Immich database -docker exec -t immich_postgres pg_dump --clean --if-exists --dbname --username= > "$UPLOAD_LOCATION"/database-backup/immich-database.sql +docker exec -t immich_postgres pg_dumpall --clean --if-exists --username= > "$UPLOAD_LOCATION"/database-backup/immich-database.sql # For deduplicating backup programs such as Borg or Restic, compressing the content can increase backup size by making it harder to deduplicate. If you are using a different program or still prefer to compress, you can use the following command instead: -# docker exec -t immich_postgres pg_dump --clean --if-exists --dbname --username= | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz +# docker exec -t immich_postgres pg_dumpall --clean --if-exists --username= | /usr/bin/gzip --rsyncable > "$UPLOAD_LOCATION"/database-backup/immich-database.sql.gz ### Append to local Borg repository borg create "$BACKUP_PATH/immich-borg::{now}" "$UPLOAD_LOCATION" --exclude "$UPLOAD_LOCATION"/thumbs/ --exclude "$UPLOAD_LOCATION"/encoded-video/ diff --git a/docs/docs/install/config-file.md b/docs/docs/install/config-file.md index 5c34acdd9d..3355750603 100644 --- a/docs/docs/install/config-file.md +++ b/docs/docs/install/config-file.md @@ -6,18 +6,14 @@ sidebar_position: 100 A config file can be provided as an alternative to the UI configuration. -:::note Interaction with the web UI -While the config file does not need to include all keys from the below example, specifying `IMMICH_CONFIG_FILE` will disable the ability to edit other properties from the Immich web UI. -::: - ### Step 1 - Create a new config file -In JSON format, create a new config file (e.g. `immich-config.json`) and put it in a location mounted in the container that can be accessed by Immich. +In JSON format, create a new config file (e.g. `immich.json`) and put it in a location mounted in the container that can be accessed by Immich. YAML-formatted config files are also supported. The default configuration looks like this:
-immich-config.json +immich.json ```json { @@ -30,7 +26,7 @@ The default configuration looks like this: }, "ffmpeg": { "accel": "disabled", - "accelDecode": true, + "accelDecode": false, "acceptedAudioCodecs": ["aac", "mp3", "opus"], "acceptedContainers": ["mov", "ogg", "webm"], "acceptedVideoCodecs": ["h264"], @@ -197,7 +193,6 @@ The default configuration looks like this: "defaultStorageQuota": null, "enabled": false, "issuerUrl": "", - "endSessionEndpoint": "", "mobileOverrideEnabled": false, "mobileRedirectUri": "", "profileSigningAlgorithm": "none", @@ -254,10 +249,6 @@ So you can just grab it from there, paste it into a file and you're pretty much ### Step 2 - Specify the file location -:::note -If you have any `microservices` workers, they will also need to have the config file mounted to their container. -::: - In your `.env` file, set the variable `IMMICH_CONFIG_FILE` to the path of your config. For more information, refer to the [Environment Variables](/install/environment-variables.md) section. @@ -269,7 +260,7 @@ It is recommended to reuse this variable in your `docker-compose.yml`: ```yaml volumes: - - ./immich-config.json:${IMMICH_CONFIG_FILE} + - ./configuration.yml:${IMMICH_CONFIG_FILE} ``` -::: +:: diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md index c10a858ed9..41068dee97 100644 --- a/docs/docs/install/environment-variables.md +++ b/docs/docs/install/environment-variables.md @@ -19,7 +19,7 @@ If this does not work, try running `docker compose up -d --force-recreate`. | Variable | Description | Default | Containers | | :----------------- | :------------------------------ | :-----: | :----------------------- | -| `IMMICH_VERSION` | Image tags | `v3` | server, machine learning | +| `IMMICH_VERSION` | Image tags | `v2` | server, machine learning | | `UPLOAD_LOCATION` | Host path for uploads | | server | | `DB_DATA_LOCATION` | Host path for Postgres database | | database | @@ -29,31 +29,29 @@ These environment variables are used by the `docker-compose.yml` file and do **N ## General -| Variable | Description | Default | Containers | Workers | -| :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | -| `TZ` | Timezone | \*1 | server | microservices | -| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | -| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | -| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | -| `IMMICH_MEDIA_LOCATION` | Media location inside the container âš ī¸**You probably shouldn't set this**\*2âš ī¸ | `/data` | server | api, microservices | -| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | -| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`\*3. | `false` | server | api | -| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | -| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | -| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | -| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | -| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | -| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | -| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | -| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` and `/admin/database-backups/start-restore` endpoints | `true` | server | api | +| Variable | Description | Default | Containers | Workers | +| :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :----------------------- | :----------------- | +| `TZ` | Timezone | \*1 | server | microservices | +| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices | +| `IMMICH_LOG_LEVEL` | Log level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices | +| `IMMICH_LOG_FORMAT` | Log output format (`console`, `json`) | `console` | server | api, microservices | +| `IMMICH_MEDIA_LOCATION` | Media location inside the container âš ī¸**You probably shouldn't set this**\*2âš ī¸ | `/data` | server | api, microservices | +| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices | +| `IMMICH_HELMET_FILE` | Path to a json file with [helmet](https://www.npmjs.com/package/helmet) options. Set to `false` to disable. Set to `true` to use `server/helmet.json`. | `false` | server | api, microservices | +| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | | +| `CPU_CORES` | Number of cores available to the Immich server | auto-detected CPU core count | server | | +| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api | +| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices | +| `IMMICH_PROCESS_INVALID_IMAGES` | When `true`, generate thumbnails for invalid images | | server | microservices | +| `IMMICH_TRUSTED_PROXIES` | List of comma-separated IPs set as trusted proxies | | server | api | +| `IMMICH_IGNORE_MOUNT_CHECK_ERRORS` | See [System Integrity](/administration/system-integrity) | | server | api, microservices | +| `IMMICH_ALLOW_SETUP` | When `false` disables the `/auth/admin-sign-up` endpoint | `true` | server | api | \*1: `TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`. `TZ` is used by `exiftool` as a fallback in case the timezone cannot be determined from the image metadata. It is also used for logfile timestamps and cron job execution. \*2: This path is where the Immich code looks for the files, which is internal to the docker container. Setting it to a path on your host will certainly break things, you should use the `UPLOAD_LOCATION` variable instead. -\*3: The [default configuration](https://helmetjs.github.io/#content-security-policy) sets `upgrade-insecure-requests`, which tells the browser to upgrade all requests to HTTPS. This breaks on HTTP-only deployments. If you cannot use HTTPS, you should use a custom helmet config file with `"upgrade-insecure-requests": null`. - ## Workers | Variable | Description | Default | Containers | @@ -83,13 +81,13 @@ Information on the current workers can be found [here](/administration/jobs-work | `DB_PASSWORD` | Database password | `postgres` | server, database\*1 | | `DB_DATABASE_NAME` | Database name | `immich` | server, database\*1 | | `DB_SSL_MODE` | Database SSL mode | | server | -| `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server | +| `DB_VECTOR_EXTENSION`\*2 | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server | | `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server | | `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])\*3 | `SSD` | database | \*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`. -\*2: If not provided, the appropriate extension to use is auto-detected at startup by inspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvector. +\*2: If not provided, the appropriate extension to use is auto-detected at startup by introspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvecto.rs, pgvector. \*3: Uses either [`postgresql.ssd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.ssd.conf) or [`postgresql.hdd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.hdd.conf) which mainly controls the Postgres `effective_io_concurrency` setting to allow for concurrenct IO on SSDs and sequential IO on HDDs. @@ -154,33 +152,33 @@ Redis (Sentinel) URL example JSON before encoding: ## Machine Learning -| Variable | Description | Default | Containers | -| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------: | :--------------- | -| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | -| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | -| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | -| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | -| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | -| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | -| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | -| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `300` (`900` if using ROCm) | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning | -| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | -| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | -| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | -| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | -| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | -| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | -| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | -| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | +| Variable | Description | Default | Containers | +| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- | +| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning | +| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning | +| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning | +| `MACHINE_LEARNING_REQUEST_THREADS`\*1 | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning | +| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning | +| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning | +| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning | +| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__OCR__RECOGNITION` | Comma-separated list of (recognition) OCR model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_PRELOAD__OCR__DETECTION` | Comma-separated list of (detection) OCR model(s) to preload and cache | | machine learning | +| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning | +| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning | +| `MACHINE_LEARNING_DEVICE_IDS`\*4 | Device IDs to use in multi-GPU environments | `0` | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning | +| `MACHINE_LEARNING_MAX_BATCH_SIZE__OCR` | Set the maximum number of boxes that will be processed at once by the OCR model | `6` | machine learning | +| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning | +| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spun up while inferencing. | `1` | machine learning | +| `MACHINE_LEARNING_MODEL_ARENA` | Pre-allocates CPU memory to avoid memory fragmentation | true | machine learning | +| `MACHINE_LEARNING_OPENVINO_PRECISION` | If set to FP16, uses half-precision floating-point operations for faster inference with reduced accuracy (one of [`FP16`, `FP32`], applies only to OpenVINO) | `FP32` | machine learning | \*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones. diff --git a/docs/docs/install/requirements.md b/docs/docs/install/requirements.md index 155aa1ae75..66f3033a43 100644 --- a/docs/docs/install/requirements.md +++ b/docs/docs/install/requirements.md @@ -20,11 +20,9 @@ Hardware and software requirements for Immich: - **RAM**: Minimum 6GB, recommended 8GB. - **CPU**: Minimum 2 cores, recommended 4 cores. - Immich runs on the `amd64` and `arm64` platforms. - Since `v3`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels). + Since `v2.6`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels). Most CPUs released since ~2012 support this microarchitecture. If you are using a virtual machine, ensure you have selected a [supported microarchitecture](https://pve.proxmox.com/pve-docs/chapter-qm.html#_qemu_cpu_types). - If you are unable to support this instruction set, the last version to support `x86-64-v1` is `v2.7.5`. - Note that this release is no longer supported, and you must run a matching `immich-server` version. - **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions. - The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average. diff --git a/docs/docs/install/synology.md b/docs/docs/install/synology.md index 69d8abf515..b86561dbbf 100644 --- a/docs/docs/install/synology.md +++ b/docs/docs/install/synology.md @@ -7,7 +7,7 @@ sidebar_position: 85 :::note This is a community contribution and not officially supported by the Immich team, but included here for convenience. -Community support should be directed to Synology-specific support platforms. +Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). ::: Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager. @@ -52,7 +52,7 @@ Scroll to the bottom of the "**Details**" section and find the `IP Address` list ## Step 4 - Configure Firewall Settings -Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS to allow communication between the Immich containers. +Once your project completes the build process, your containers will start. In order to be able to access Immich from your browser, you need to configure the firewall settings for your Synology NAS. Open "**Control Panel**" on your Synology NAS, and select "**Security**". Navigate to "**Firewall**" @@ -74,7 +74,6 @@ Read the [Post Installation](/install/post-install.mdx) steps and [upgrade instr
Updating Immich using Container Manager - Check the post installation and upgrade instructions at the links above before proceeding with this section. ## Step 1. Backup @@ -111,7 +110,7 @@ Go to **Project**, select **Action** then **Build**. This will download, unpack, ## Step 5. Update firewall rule -Without a fixed subnet, the default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. +The default behavior is to automatically start the containers once installed. If `immich_server` runs for a few seconds and then stops, it may be because the firewall rule no longer matches the server IP address. Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. ![Container IP](../../static/img/synology-container-ip.png) @@ -124,67 +123,4 @@ In this example, the IP addresses mismatch and the firewall rule needs to be edi ![Edit IP](../../static/img/synology-fw-ipedit.png) -To prevent future firewall issues, you may set a fixed subnet. [See Set Fixed Subnet](#set-fixed-subnet) for instructions. - -
- -
- Set Fixed Subnet - -Docker by default assigns dynamic subnets to bridge networks which can change when rebuilding containers and can cause firewall rules to break. To avoid this, define a fixed subnet in your `docker-compose.yml`: - -## Step 1. Determine current subnet - -Go to the **Container** section. Click on `immich_server` and scroll down on **General** to find the IP address. -![Container IP](../../static/img/synology-container-ip.png) - -## Step 2. Add network configuration - -Add the following network configuration at the end of your `docker-compose.yml` file: - -```yaml -networks: - immich-network: - driver: bridge - ipam: - config: - - subnet: 172.20.0.0/16 - gateway: 172.20.0.1 -``` - -If your docker container is running on a different subnet then update accordingly. - -## Step 3. Add network to each service - -Add the network to each service (immich-server, immich-machine-learning, redis, database): - -```yaml -services: - immich-server: - # other config options - networks: - - immich-network - - immich-machine-learning: - # other config options - networks: - - immich-network - - redis: - # other config options - networks: - - immich-network - - database: - # other config options - networks: - - immich-network -``` - -Save your changes. Synology will ask if you want to save changes only or rebuild containers. Select rebuild containers. - -## Step 4. Update Firewall Rules, if necessary - -If your firewall rules were not already set for this subnet, the firewall rules will need to be updated. See [Step 4 - Configure Firewall Settings](#step-4---configure-firewall-settings). -
diff --git a/docs/docs/install/unraid.md b/docs/docs/install/unraid.md index 3ce98dc2e1..ca7263a1e8 100644 --- a/docs/docs/install/unraid.md +++ b/docs/docs/install/unraid.md @@ -1,19 +1,13 @@ --- -sidebar_position: 70 +sidebar_position: 60 --- -# Unraid [ Community ] - -:::note -This is a community contribution and not officially supported by the Immich team, but included here for convenience. - -Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/). -::: +# Unraid Immich can easily be installed and updated on Unraid via: -1. Community made template on the Unraid Community Apps -2. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps +1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps +2. Community made template on the Unraid Community Apps ## Community Applications Template @@ -29,7 +23,7 @@ Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA, For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup) -## Docker-Compose Method +## Docker-Compose Method (Official) :::info @@ -127,7 +121,7 @@ alt="Go to Docker Tab and visit the address listed next to immich-web" width="90%" alt="Go to Docker Tab and visit the address listed next to immich-web" /> - +
:::tip diff --git a/docs/docs/install/upgrading.md b/docs/docs/install/upgrading.md index f302719c76..12e5c9c342 100644 --- a/docs/docs/install/upgrading.md +++ b/docs/docs/install/upgrading.md @@ -28,15 +28,11 @@ docker image prune ## Versioning Policy -Immich follows [semantic versioning][semver], which tags releases in the format `..`. -We intend for breaking changes, including those to the API or deployment, to be limited to major version releases. -You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`. These metatags do not follow release candidates. +Immich follows [semantic versioning][semver], which tags releases in the format `..`. We intend for breaking changes to be limited to major version releases. +You can configure your Docker image to point to the current major version by using a metatag, such as `:v2`. -The mobile app is typically compatible with the current and prior major version. However, the server is only compatible with the matching major version. -Thus, we recommend upgrading all mobile clients before upgrading the server to ensure compatibility. - -We do not backport patches to earlier versions. We encourage all users to run the most recent stable release of Immich. -Downgrading to an earlier version, even within the same minor version, is not supported. +Currently, we have no plans to backport patches to earlier versions. We encourage all users to run the most recent release of Immich. +Switching back to an earlier version, even within the same minor release tag, is not supported. [semver]: https://semver.org/ @@ -134,3 +130,7 @@ These storage mediums have different performance characteristics. As a result, t #### Can I use the new database image as a general PostgreSQL image outside of Immich? It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image. + +#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord? + +VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption. diff --git a/docs/docs/partials/_mobile-app-download.md b/docs/docs/partials/_mobile-app-download.md index 08540837a1..31cf62bf81 100644 --- a/docs/docs/partials/_mobile-app-download.md +++ b/docs/docs/partials/_mobile-app-download.md @@ -1,7 +1,7 @@ The mobile app can be downloaded from the following places: -- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652) -- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich) -- [GitHub Releases (APK)](https://github.com/immich-app/immich/releases) - Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities). -- [F-Droid](https://app.futo.org/fdroid/repo/) +- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich) +- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652) +- [F-Droid](https://f-droid.org/packages/app.alextran.immich) +- [GitHub Releases (apk)](https://github.com/immich-app/immich/releases) diff --git a/docs/docs/partials/_storage-template.md b/docs/docs/partials/_storage-template.md index 1cd9572c11..84236e0ac1 100644 --- a/docs/docs/partials/_storage-template.md +++ b/docs/docs/partials/_storage-template.md @@ -6,8 +6,6 @@ You can read more about the differences between storage template engine on and o The admin user can set the template by using the template builder in the `Administration -> Settings -> Storage Template`. Immich provides a set of variables that you can use in constructing the template, along with additional custom text. If the template produces [multiple files with the same filename, they won't be overwritten](https://github.com/immich-app/immich/discussions/3324) as a sequence number is appended to the filename. -Date and time variables in storage templates are rendered in the server's local timezone. - ```bash title="Default template" Year/Year-Month-Day/Filename.Extension ``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 58a07b901f..00a120b8b6 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -10,6 +10,7 @@ const config = { url: 'https://docs.immich.app', baseUrl: '/', onBrokenLinks: 'throw', + onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.png', // GitHub pages deployment config. @@ -28,9 +29,6 @@ const config = { // Mermaid diagrams markdown: { mermaid: true, - hooks: { - onBrokenMarkdownLinks: 'warn', - }, }, themes: ['@docusaurus/theme-mermaid'], @@ -191,7 +189,7 @@ const config = { }, { label: 'Privacy Policy', - href: 'https://immich.app/privacy-policy', + to: '/privacy-policy', }, ], }, diff --git a/docs/mise.lock b/docs/mise.lock deleted file mode 100644 index fe557086dd..0000000000 --- a/docs/mise.lock +++ /dev/null @@ -1,8 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html - -[[tools.wrangler]] -version = "4.110.0" -backend = "npm:wrangler" - -[tools.wrangler.options] -allow_builds = '["esbuild", "sharp", "workerd"]' diff --git a/docs/mise.toml b/docs/mise.toml index f376ed624a..32fcac5578 100644 --- a/docs/mise.toml +++ b/docs/mise.toml @@ -3,7 +3,7 @@ run = "pnpm install --filter documentation --frozen-lockfile" [tasks.start] env._.path = "./node_modules/.bin" -run = "docusaurus start --port 3005" +run = "docusaurus --port 3005" [tasks.build] env._.path = "./node_modules/.bin" @@ -28,4 +28,4 @@ run = "prettier --write ." run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}" [tools] -wrangler = "4.122.0" +wrangler = "4.66.0" diff --git a/docs/package.json b/docs/package.json index d291c385f9..f976791279 100644 --- a/docs/package.json +++ b/docs/package.json @@ -17,10 +17,10 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "~3.10.0", - "@docusaurus/preset-classic": "~3.10.0", - "@docusaurus/theme-common": "~3.10.0", - "@docusaurus/theme-mermaid": "~3.10.0", + "@docusaurus/core": "~3.9.0", + "@docusaurus/preset-classic": "~3.9.0", + "@docusaurus/theme-common": "~3.9.0", + "@docusaurus/theme-mermaid": "~3.9.0", "@mdi/js": "^7.3.67", "@mdi/react": "^1.6.1", "@mdx-js/react": "^3.0.0", @@ -36,12 +36,11 @@ "url": "^0.11.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "~3.10.0", + "@docusaurus/module-type-aliases": "~3.9.0", "@docusaurus/tsconfig": "^3.10.0", - "@docusaurus/types": "^3.10.0", + "@docusaurus/types": "^3.7.0", "prettier": "^3.7.4", - "@typescript/native": "npm:typescript@^7.0.2", - "typescript": "npm:@typescript/typescript6@^6.0.2" + "typescript": "^6.0.0" }, "browserslist": { "production": [ @@ -57,5 +56,8 @@ }, "engines": { "node": ">=20" + }, + "volta": { + "node": "24.14.1" } } diff --git a/docs/src/components/timeline.tsx b/docs/src/components/timeline.tsx new file mode 100644 index 0000000000..32b15edb59 --- /dev/null +++ b/docs/src/components/timeline.tsx @@ -0,0 +1,78 @@ +import useIsBrowser from '@docusaurus/useIsBrowser'; +import { mdiCheckboxBlankCircle, mdiCheckboxMarkedCircle } from '@mdi/js'; +import Icon from '@mdi/react'; +import React from 'react'; + +export type Item = { + icon: string; + iconColor: string; + title: string; + description?: string; + link?: { url: string; text: string }; + done?: false; + getDateLabel: (language: string) => string; +}; + +interface Props { + items: Item[]; +} + +export function Timeline({ items }: Props): JSX.Element { + const isBrowser = useIsBrowser(); + + return ( +
+ ); +} diff --git a/docs/src/components/version-switcher.tsx b/docs/src/components/version-switcher.tsx index 967c85c780..739d7bd001 100644 --- a/docs/src/components/version-switcher.tsx +++ b/docs/src/components/version-switcher.tsx @@ -2,8 +2,6 @@ import { useWindowSize } from '@docusaurus/theme-common'; import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; import React, { useEffect, useState } from 'react'; -const ARCHIVED_VERSIONS_URL = 'https://version.immich.cloud/v1/docs/versions'; - export default function VersionSwitcher(): JSX.Element { const [versions, setVersions] = useState([]); const [activeLabel, setLabel] = useState('Versions'); @@ -13,7 +11,12 @@ export default function VersionSwitcher(): JSX.Element { useEffect(() => { async function getVersions() { try { - const response = await fetch(ARCHIVED_VERSIONS_URL); + let baseUrl = 'https://docs.immich.app'; + if (window.location.origin === 'http://localhost:3005') { + baseUrl = window.location.origin; + } + + const response = await fetch(`${baseUrl}/archived-versions.json`); const archiveVersions = await response.json(); diff --git a/docs/src/pages/privacy-policy.tsx b/docs/src/pages/privacy-policy.tsx new file mode 100644 index 0000000000..36ac76945d --- /dev/null +++ b/docs/src/pages/privacy-policy.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +function HomepageHeader() { + return ( +
+
+
+

Privacy Policy

+

Last updated: July 31st 2024

+

+ Welcome to Immich. We are committed to respecting your privacy. This Privacy Policy sets out how we collect, + use, and share information when you use our Immich app. +

+
+ + {/* 1. Scope of This Policy */} +
+

1. Scope of This Policy

+

+ This Privacy Policy applies to the Immich app ("we", "our", or "us") and covers our collection, use, and + disclosure of your information. This Policy does not cover any third-party websites, services, or + applications that can be accessed through our app, or third-party services you may access through Immich. +

+
+ + {/* 2. Information We Collect */} +
+

2. Information We Collect

+
+

+ Locally Stored Data: Immich stores all your photos, albums, settings, and locally on your + device. We do not have access to this data, nor do we transmit or store it on any of our servers. +

+
+ +
+

+ Purchase Information: When you make a purchase within the{' '} + https://buy.immich.app, we collect the following information for tax + calculation purposes: +

+
    +
  • Country of origin
  • +
  • Postal code (if the user is from Canada or the United States)
  • +
+
+
+ + {/* 3. Use of Your Information */} +
+

3. Use of Your Information

+

+ Tax Calculation: The country of origin and postal code (for users from Canada or the United + States) are collected solely for determining the applicable tax rates on your purchase. +

+
+ + {/* 4. Sharing of Your Information */} +
+

4. Sharing of Your Information

+
    +
  • + Tax Authorities: The purchase information may be shared with tax authorities as required + by law. +
  • +
  • + Payment Providers: The purchase information may be shared with payment providers where + required. +
  • +
+
+ + {/* 5. Changes to This Policy */} +
+

5. Changes to This Policy

+

+ We may update our Privacy Policy from time to time. If we make any changes, we will notify you by revising + the "Last updated" date at the top of this policy. It's encouraged that users frequently check this page for + any changes to stay informed about how we are helping to protect the personal information we collect. +

+
+ + {/* 6. Contact Us */} +
+

6. Contact Us

+

+ If you have any questions about this Privacy Policy, please contact us at{' '} + immich@futo.org +

+
+
+
+ ); +} + +export default function Home(): JSX.Element { + return ( + + +
+

This project is available under GNU AGPL v3 license.

+

Privacy should not be a luxury

+
+
+ ); +} diff --git a/docs/static/_redirects b/docs/static/_redirects index e218124e59..218bb71d69 100644 --- a/docs/static/_redirects +++ b/docs/static/_redirects @@ -1,4 +1,3 @@ -/archived-versions.json https://version.immich.cloud/v1/docs/versions 307 / /overview/quick-start 307 /mobile-app-beta-program /features/mobile-app 307 /contribution-guidelines /overview/support-the-project#contributing 307 @@ -37,4 +36,3 @@ /overview/welcome /overview/quick-start 307 /docs/* /:splat 307 /features/automatic-backup /features/mobile-backup 307 -/privacy-policy https://immich.app/privacy-policy 307 diff --git a/docs/static/archived-versions.json b/docs/static/archived-versions.json new file mode 100644 index 0000000000..72e5487c9a --- /dev/null +++ b/docs/static/archived-versions.json @@ -0,0 +1,257 @@ +[ + { + "label": "v2.7.3", + "url": "https://docs.v2.7.3.archive.immich.app" + }, + { + "label": "v2.6.3", + "url": "https://docs.v2.6.3.archive.immich.app" + }, + { + "label": "v2.5.6", + "url": "https://docs.v2.5.6.archive.immich.app" + }, + { + "label": "v2.4.1", + "url": "https://docs.v2.4.1.archive.immich.app" + }, + { + "label": "v2.3.1", + "url": "https://docs.v2.3.1.archive.immich.app" + }, + { + "label": "v2.2.3", + "url": "https://docs.v2.2.3.archive.immich.app" + }, + { + "label": "v2.1.0", + "url": "https://docs.v2.1.0.archive.immich.app" + }, + { + "label": "v2.0.1", + "url": "https://docs.v2.0.1.archive.immich.app" + }, + { + "label": "v1.144.1", + "url": "https://docs.v1.144.1.archive.immich.app" + }, + { + "label": "v1.143.1", + "url": "https://docs.v1.143.1.archive.immich.app" + }, + { + "label": "v1.142.1", + "url": "https://v1.142.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.141.1", + "url": "https://v1.141.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.140.1", + "url": "https://v1.140.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.139.4", + "url": "https://v1.139.4.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.138.1", + "url": "https://v1.138.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.137.3", + "url": "https://v1.137.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.136.0", + "url": "https://v1.136.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.135.3", + "url": "https://v1.135.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.134.0", + "url": "https://v1.134.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.133.1", + "url": "https://v1.133.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.132.3", + "url": "https://v1.132.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.131.3", + "url": "https://v1.131.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.130.3", + "url": "https://v1.130.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.129.0", + "url": "https://v1.129.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.128.0", + "url": "https://v1.128.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.127.0", + "url": "https://v1.127.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.126.1", + "url": "https://v1.126.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.125.7", + "url": "https://v1.125.7.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.124.2", + "url": "https://v1.124.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.123.0", + "url": "https://v1.123.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.122.3", + "url": "https://v1.122.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.121.0", + "url": "https://v1.121.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.120.2", + "url": "https://v1.120.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.119.1", + "url": "https://v1.119.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.118.2", + "url": "https://v1.118.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.117.0", + "url": "https://v1.117.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.116.2", + "url": "https://v1.116.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.115.0", + "url": "https://v1.115.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.114.0", + "url": "https://v1.114.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.113.1", + "url": "https://v1.113.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.112.1", + "url": "https://v1.112.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.111.0", + "url": "https://v1.111.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.110.0", + "url": "https://v1.110.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.109.2", + "url": "https://v1.109.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.108.0", + "url": "https://v1.108.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.107.2", + "url": "https://v1.107.2.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.106.4", + "url": "https://v1.106.4.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.105.1", + "url": "https://v1.105.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.104.0", + "url": "https://v1.104.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.103.1", + "url": "https://v1.103.1.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.102.3", + "url": "https://v1.102.3.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.101.0", + "url": "https://v1.101.0.archive.immich.app", + "rootPath": "/docs" + }, + { + "label": "v1.100.0", + "url": "https://v1.100.0.archive.immich.app", + "rootPath": "/docs" + } +] diff --git a/docs/tailwind.config.js b/docs/tailwind.config.js index 95a1e4fa1b..9a654487cc 100644 --- a/docs/tailwind.config.js +++ b/docs/tailwind.config.js @@ -11,9 +11,15 @@ module.exports = { colors: { // Light Theme 'immich-primary': '#4250af', + 'immich-bg': '#f9f8fb', + 'immich-fg': 'black', + 'immich-gray': '#F6F6F4', // Dark Theme 'immich-dark-primary': '#adcbfa', + 'immich-dark-bg': '#000000', + 'immich-dark-fg': '#e5e7eb', + 'immich-dark-gray': '#111111', }, }, }, diff --git a/e2e-auth-server/Dockerfile b/e2e-auth-server/Dockerfile new file mode 100644 index 0000000000..aa7527c483 --- /dev/null +++ b/e2e-auth-server/Dockerfile @@ -0,0 +1,6 @@ +FROM node:24.1.0-alpine3.20@sha256:8fe019e0d57dbdce5f5c27c0b63d2775cf34b00e3755a7dea969802d7e0c2b25 +RUN corepack enable +ADD package.json *.ts ./ +RUN pnpm install +EXPOSE 2286 +CMD ["pnpm", "run", "start"] diff --git a/packages/e2e-auth-server/auth-server.ts b/e2e-auth-server/auth-server.ts similarity index 82% rename from packages/e2e-auth-server/auth-server.ts rename to e2e-auth-server/auth-server.ts index 15aaa71c1c..9aef56510d 100644 --- a/packages/e2e-auth-server/auth-server.ts +++ b/e2e-auth-server/auth-server.ts @@ -1,12 +1,5 @@ -import { - calculateJwkThumbprint, - exportJWK, - importPKCS8, - importSPKI, - SignJWT, -} from 'jose'; +import { exportJWK, generateKeyPair } from 'jose'; import Provider from 'oidc-provider'; -import { PRIVATE_KEY_PEM, PUBLIC_KEY_PEM } from './test-keys'; export enum OAuthClient { DEFAULT = 'client-default', @@ -51,29 +44,6 @@ const claims = [ }, ]; -const privateKey = await importPKCS8(PRIVATE_KEY_PEM, 'RS256', { - extractable: true, -}); -const publicKey = await importSPKI(PUBLIC_KEY_PEM, 'RS256', { - extractable: true, -}); -const kid = await calculateJwkThumbprint(await exportJWK(publicKey)); - -export async function generateLogoutToken(iss: string, sub: string) { - return await new SignJWT({ - iss: iss, - aud: OAuthClient.DEFAULT, - iat: Math.floor(Date.now() / 1000), - jti: crypto.randomUUID(), - sub: sub, - events: { - 'http://schemas.openid.net/event/backchannel-logout': {}, - }, - }) - .setProtectedHeader({ alg: 'RS256', typ: 'logout+jwt', kid: kid }) - .sign(privateKey); -} - const withDefaultClaims = (sub: string) => ({ sub, email: `${sub}@immich.app`, @@ -96,6 +66,8 @@ const getClaims = (sub: string, use?: string) => { }; const setup = async () => { + const { privateKey, publicKey } = await generateKeyPair('RS256'); + const redirectUris = [ 'http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect', diff --git a/packages/e2e-auth-server/package.json b/e2e-auth-server/package.json similarity index 77% rename from packages/e2e-auth-server/package.json rename to e2e-auth-server/package.json index d92f62d981..73ede1b7c4 100644 --- a/packages/e2e-auth-server/package.json +++ b/e2e-auth-server/package.json @@ -1,17 +1,15 @@ { "name": "@immich/e2e-auth-server", "version": "0.1.0", - "private": true, "type": "module", "main": "auth-server.ts", "scripts": { "start": "tsx startup.ts" }, "devDependencies": { - "jose": "^6.0.0", + "jose": "^5.6.3", "@types/oidc-provider": "^9.0.0", "oidc-provider": "^9.0.0", "tsx": "^4.20.6" - }, - "packageManager": "pnpm@11.21.0" + } } diff --git a/packages/e2e-auth-server/startup.ts b/e2e-auth-server/startup.ts similarity index 100% rename from packages/e2e-auth-server/startup.ts rename to e2e-auth-server/startup.ts diff --git a/e2e/.nvmrc b/e2e/.nvmrc new file mode 100644 index 0000000000..8e35034890 --- /dev/null +++ b/e2e/.nvmrc @@ -0,0 +1 @@ +24.14.1 diff --git a/e2e/docker-compose.dev.yml b/e2e/docker-compose.dev.yml index ac6973a0d6..b301ef8441 100644 --- a/e2e/docker-compose.dev.yml +++ b/e2e/docker-compose.dev.yml @@ -83,7 +83,9 @@ volumes: model_cache: prometheus_data: grafana_data: - build_cache: + pnpm_cache: + pnpm_store_server: + pnpm_store_web: server_node_modules: web_node_modules: github_node_modules: diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index dd7f52e0f3..c8a3b975d4 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -4,8 +4,7 @@ services: e2e-auth-server: container_name: immich-e2e-auth-server build: - context: ../ - dockerfile: packages/e2e-auth-server/Dockerfile + context: ../e2e-auth-server ports: - 2286:2286 @@ -16,7 +15,6 @@ services: context: ../ dockerfile: server/Dockerfile cache_from: - # hash is the sha256 of the build-args in docker.yml - type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-amd64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main - type=registry,ref=ghcr.io/immich-app/immich-server-build-cache:linux-arm64-cc099f297acd18c924b35ece3245215b53d106eb2518e3af6415931d055746cd-main args: @@ -46,7 +44,7 @@ services: redis: container_name: immich-e2e-redis - image: docker.io/valkey/valkey:9@sha256:3acc0687f2a2e1091fae6450d7842dd658c941338cf0a873ddd9e14b9e4ea4dd + image: docker.io/valkey/valkey:9@sha256:3b55fbaa0cd93cf0d9d961f405e4dfcc70efe325e2d84da207a0a8e6d8fde4f9 healthcheck: test: redis-cli ping || exit 1 diff --git a/e2e/eslint.config.mjs b/e2e/eslint.config.mjs index 1e57910b01..f73e31e48e 100644 --- a/e2e/eslint.config.mjs +++ b/e2e/eslint.config.mjs @@ -2,8 +2,13 @@ import js from '@eslint/js'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import eslintPluginUnicorn from 'eslint-plugin-unicorn'; import globals from 'globals'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import typescriptEslint from 'typescript-eslint'; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + export default typescriptEslint.config([ eslintPluginUnicorn.configs.recommended, eslintPluginPrettierRecommended, @@ -24,7 +29,7 @@ export default typescriptEslint.config([ parserOptions: { project: 'tsconfig.json', - tsconfigRootDir: import.meta.dirname, + tsconfigRootDir: __dirname, }, }, @@ -36,19 +41,9 @@ export default typescriptEslint.config([ '@typescript-eslint/no-floating-promises': 'error', 'unicorn/prefer-module': 'off', 'unicorn/import-style': 'off', - 'unicorn/consistent-boolean-name': 'off', - 'unicorn/no-non-function-verb-prefix': 'off', - 'unicorn/no-unreadable-for-of-expression': 'off', - 'unicorn/max-nested-calls': 'off', - 'unicorn/prefer-uint8array-base64': 'off', - 'unicorn/isolated-functions': 'off', - 'unicorn/prefer-promise-with-resolvers': 'off', - 'unicorn/no-declarations-before-early-exit': 'off', - 'unicorn/prefer-simple-condition-first': 'off', - 'unicorn/single-line-block-comment-style': ['error', 'single-line'], curly: 2, 'prettier/prettier': 0, - 'unicorn/name-replacements': 'off', + 'unicorn/prevent-abbreviations': 'off', 'unicorn/filename-case': 'off', 'unicorn/no-null': 'off', 'unicorn/prefer-top-level-await': 'off', diff --git a/e2e/mise.toml b/e2e/mise.toml index 487f4ebf46..c298115e40 100644 --- a/e2e/mise.toml +++ b/e2e/mise.toml @@ -1,21 +1,11 @@ [tasks.install] -run = "pnpm install --filter immich-e2e... --frozen-lockfile" - -[tasks.build] -dir = "{{ config_root }}" -run = "docker compose build" +run = "pnpm install --filter immich-e2e --frozen-lockfile" [tasks.test] -depends = ["//e2e:build", "//e2e:ci-setup"] env._.path = "./node_modules/.bin" run = "vitest --run" -[tasks.playwright-install] -env._.path = "./node_modules/.bin" -run = "playwright install" - [tasks."test-web"] -depends = ["//e2e:build", "//e2e:ci-setup", "//e2e:playwright-install"] env._.path = "./node_modules/.bin" run = "playwright test" @@ -37,24 +27,3 @@ run = { task = "lint --fix" } [tasks.check] env._.path = "./node_modules/.bin" run = "tsc --noEmit" - - -[tasks.ci-setup] -run = [ - { task = "//:sdk:install" }, - { task = "//:sdk:build" }, - { task = "//packages/cli:install" }, - { task = "//packages/cli:build" }, - { task = ":install" }, -] - - -[tasks.ci-unit] -run = [ - { task = "//:sdk:install" }, - { task = "//:sdk:build" }, - { task = ":install" }, - { task = ":format" }, - { task = ":lint" }, - { task = ":check" }, -] diff --git a/e2e/package.json b/e2e/package.json index 135e6c5f16..8b03ff16ef 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "3.1.0", + "version": "2.7.3", "description": "", "main": "index.js", "type": "module", @@ -32,7 +32,7 @@ "@playwright/test": "^1.44.1", "@socket.io/component-emitter": "^3.1.2", "@types/luxon": "^3.4.2", - "@types/node": "^24.13.3", + "@types/node": "^24.12.0", "@types/pg": "^8.15.1", "@types/pngjs": "^6.0.4", "@types/supertest": "^7.0.0", @@ -40,7 +40,7 @@ "eslint": "^10.0.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-unicorn": "^73.0.0", + "eslint-plugin-unicorn": "^63.0.0", "exiftool-vendored": "^35.0.0", "globals": "^17.0.0", "luxon": "^3.4.4", @@ -48,13 +48,16 @@ "pngjs": "^7.0.0", "prettier": "^3.7.4", "prettier-plugin-organize-imports": "^4.0.0", - "sharp": "^0.35.3", + "sharp": "^0.34.5", "socket.io-client": "^4.7.4", "supertest": "^7.0.0", - "@typescript/native": "npm:typescript@^7.0.2", - "typescript": "npm:@typescript/typescript6@^6.0.2", + "typescript": "^6.0.0", "typescript-eslint": "^8.28.0", "utimes": "^5.2.1", + "vite-tsconfig-paths": "^6.1.1", "vitest": "^4.0.0" + }, + "volta": { + "node": "24.14.1" } } diff --git a/e2e/src/api/specs/duplicate.e2e-spec.ts b/e2e/src/api/specs/duplicate.e2e-spec.ts new file mode 100644 index 0000000000..d6d0ec1394 --- /dev/null +++ b/e2e/src/api/specs/duplicate.e2e-spec.ts @@ -0,0 +1,651 @@ +import { LoginResponseDto } from '@immich/sdk'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; + +describe('/duplicates', () => { + let admin: LoginResponseDto; + let user1: LoginResponseDto; + let user2: LoginResponseDto; + + beforeAll(async () => { + await utils.resetDatabase(); + + admin = await utils.adminSetup(); + + [user1, user2] = await Promise.all([ + utils.userSetup(admin.accessToken, createUserDto.user1), + utils.userSetup(admin.accessToken, createUserDto.user2), + ]); + }); + + beforeEach(async () => { + // Reset assets, albums, tags, and stacks between tests to ensure clean state for repeated test runs + // Note: We don't reset users since they're set up once in beforeAll + // Stack must be reset before asset due to foreign key constraint + await utils.resetDatabase(['stack', 'asset', 'album', 'tag']); + }); + + describe('GET /duplicates', () => { + it('should return empty array when no duplicates', async () => { + const { status, body } = await request(app) + .get('/duplicates') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual([]); + }); + + it('should return duplicate groups with suggestedKeepAssetIds', async () => { + // Create assets with different file sizes for duplicate detection + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Manually set duplicateId on both assets to create a duplicate group + const duplicateId = '00000000-0000-4000-8000-000000000001'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .get('/duplicates') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual([ + { + duplicateId, + assets: expect.arrayContaining([ + expect.objectContaining({ id: asset1.id }), + expect.objectContaining({ id: asset2.id }), + ]), + suggestedKeepAssetIds: expect.any(Array), + }, + ]); + expect(body[0].suggestedKeepAssetIds.length).toBe(1); + }); + }); + + describe('POST /duplicates/resolve', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .post('/duplicates/resolve') + .send({ + groups: [{ duplicateId: uuidDto.dummy, keepAssetIds: [], trashAssetIds: [] }], + }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should return failure for non-existent duplicate group', async () => { + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId: uuidDto.dummy, keepAssetIds: [], trashAssetIds: [] }], + }); + + expect(status).toBe(200); + expect(body).toEqual({ + status: 'COMPLETED', + results: [ + { + duplicateId: uuidDto.dummy, + status: 'FAILED', + reason: expect.stringContaining('not found or access denied'), + }, + ], + }); + }); + + it('should resolve duplicate group with keepers', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000002'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body).toEqual({ + status: 'COMPLETED', + results: [ + { + duplicateId, + status: 'SUCCESS', + }, + ], + }); + + // Verify side effects: duplicateId cleared on kept asset + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.duplicateId).toBeNull(); + + // Verify side effects: trashed asset is trashed and duplicateId cleared + const trashedAsset = await utils.getAssetInfo(user1.accessToken, asset2.id); + expect(trashedAsset.isTrashed).toBe(true); + expect(trashedAsset.duplicateId).toBeNull(); + }); + + it('should reject when keepAssetIds and trashAssetIds overlap', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000003'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset1.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('FAILED'); + expect(body.results[0].reason).toContain('disjoint'); + }); + + it('should require keepAssetIds when partially trashing', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000004'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [], trashAssetIds: [asset1.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('FAILED'); + expect(body.results[0].reason).toContain('must cover all assets'); + }); + + it('should reject partial resolution (not all assets covered)', async () => { + const [asset1, asset2, asset3] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000010'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset3.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('FAILED'); + expect(body.results[0].reason).toContain('must cover all assets'); + }); + + it('should reject asset not in duplicate group', async () => { + const [asset1, asset2, outsideAsset] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000011'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [outsideAsset.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('FAILED'); + expect(body.results[0].reason).toContain('not a member of duplicate group'); + }); + + it('should allow trash-all without keepers', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000012'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [], trashAssetIds: [asset1.id, asset2.id] }], + }); + + expect(status).toBe(200); + expect(body).toEqual({ + status: 'COMPLETED', + results: [ + { + duplicateId, + status: 'SUCCESS', + }, + ], + }); + + // Verify both assets are trashed + const [asset1Info, asset2Info] = await Promise.all([ + utils.getAssetInfo(user1.accessToken, asset1.id), + utils.getAssetInfo(user1.accessToken, asset2.id), + ]); + + expect(asset1Info.isTrashed).toBe(true); + expect(asset1Info.duplicateId).toBeNull(); + expect(asset2Info.isTrashed).toBe(true); + expect(asset2Info.duplicateId).toBeNull(); + }); + + it('should reject cross-user duplicate group access', async () => { + const asset1 = await utils.createAsset(user1.accessToken); + const asset2 = await utils.createAsset(user2.accessToken); + + const duplicateId = '00000000-0000-4000-8000-000000000013'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user2.accessToken, asset2.id, duplicateId); + + // User1 tries to resolve a group containing user2's asset + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('FAILED'); + expect(body.results[0].reason).toContain('not a member of duplicate group'); + }); + + it('should synchronize favorites when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Mark one asset as favorite + await request(app) + .put('/assets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [asset2.id], isFavorite: true }); + + const duplicateId = '00000000-0000-4000-8000-000000000020'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify favorite was synchronized to keeper + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.isFavorite).toBe(true); + expect(keptAsset.duplicateId).toBeNull(); + }); + + it('should synchronize visibility when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Archive one asset + await utils.archiveAssets(user1.accessToken, [asset2.id]); + + const duplicateId = '00000000-0000-4000-8000-000000000021'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify visibility was synchronized to keeper + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.visibility).toBe('archive'); + expect(keptAsset.duplicateId).toBeNull(); + }); + + it('should synchronize rating when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Set rating on one asset + await request(app) + .put('/assets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [asset2.id], rating: 5 }); + + const duplicateId = '00000000-0000-4000-8000-000000000022'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify rating was synchronized to keeper + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.exifInfo?.rating).toBe(5); + expect(keptAsset.duplicateId).toBeNull(); + }); + + it('should synchronize description when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Set description on one asset + await request(app) + .put('/assets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [asset2.id], description: 'Test description for duplicate' }); + + const duplicateId = '00000000-0000-4000-8000-000000000023'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify description was synchronized to keeper + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.exifInfo?.description).toBe('Test description for duplicate'); + expect(keptAsset.duplicateId).toBeNull(); + }); + + it('should synchronize location when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Set location on one asset + await request(app) + .put('/assets') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ ids: [asset2.id], latitude: 40.7128, longitude: -74.006 }); + + const duplicateId = '00000000-0000-4000-8000-000000000024'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify location was synchronized to keeper + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.exifInfo?.latitude).toBe(40.7128); + expect(keptAsset.exifInfo?.longitude).toBe(-74.006); + expect(keptAsset.duplicateId).toBeNull(); + }); + + it('should synchronize albums when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Create albums and add assets to different albums + const album1 = await utils.createAlbum(user1.accessToken, { + albumName: 'Album 1', + assetIds: [asset1.id], + }); + const album2 = await utils.createAlbum(user1.accessToken, { + albumName: 'Album 2', + assetIds: [asset2.id], + }); + + const duplicateId = '00000000-0000-4000-8000-000000000025'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify keeper is now in both albums + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.duplicateId).toBeNull(); + + // Check albums directly + const { status: album1Status, body: album1Body } = await request(app) + .get(`/albums/${album1.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + const { status: album2Status, body: album2Body } = await request(app) + .get(`/albums/${album2.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(album1Status).toBe(200); + expect(album2Status).toBe(200); + expect(album1Body.assets.map((a: any) => a.id)).toContain(asset1.id); + expect(album2Body.assets.map((a: any) => a.id)).toContain(asset1.id); + }); + + it('should synchronize tags when enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + // Wait for metadata extraction to complete before adding tags + // Otherwise, metadata jobs will race and overwrite our tags + await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); + + // Create tags and tag assets differently + const tags = await utils.upsertTags(user1.accessToken, ['tag1', 'tag2']); + await utils.tagAssets(user1.accessToken, tags[0].id, [asset1.id]); + await utils.tagAssets(user1.accessToken, tags[1].id, [asset2.id]); + + const duplicateId = '00000000-0000-4000-8000-000000000026'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify keeper has both tags + const keptAsset = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(keptAsset.duplicateId).toBeNull(); + expect(keptAsset.tags).toBeDefined(); + const tagIds = keptAsset.tags?.map((t) => t.id) || []; + expect(tagIds).toContain(tags[0].id); + expect(tagIds).toContain(tags[1].id); + }); + + it('should handle batch resolve with mixed success and failure', async () => { + // Create first group that will succeed + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + const duplicateId1 = '00000000-0000-4000-8000-000000000027'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId1); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId1); + + // Create second group with non-existent duplicate ID (will fail) + const fakeId = '00000000-0000-4000-8000-000000000099'; + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [ + { duplicateId: duplicateId1, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }, + { duplicateId: fakeId, keepAssetIds: [], trashAssetIds: [] }, + ], + }); + + expect(status).toBe(200); + expect(body.status).toBe('COMPLETED'); + expect(body.results).toHaveLength(2); + + // First group should succeed + expect(body.results[0].duplicateId).toBe(duplicateId1); + expect(body.results[0].status).toBe('SUCCESS'); + + // Second group should fail + expect(body.results[1].duplicateId).toBe(fakeId); + expect(body.results[1].status).toBe('FAILED'); + expect(body.results[1].reason).toContain('not found or access denied'); + + // Verify first group was actually resolved despite second failure + const asset1Info = await utils.getAssetInfo(user1.accessToken, asset1.id); + expect(asset1Info.duplicateId).toBeNull(); + const asset2Info = await utils.getAssetInfo(user1.accessToken, asset2.id); + expect(asset2Info.isTrashed).toBe(true); + }); + + it('should trash assets when trash is enabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000028'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + // Ensure trash is enabled (default) + const config = await utils.getSystemConfig(admin.accessToken); + expect(config.trash.enabled).toBe(true); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Verify asset is trashed (not deleted) + const trashedAsset = await utils.getAssetInfo(user1.accessToken, asset2.id); + expect(trashedAsset.isTrashed).toBe(true); + }); + + it('should delete assets when trash is disabled', async () => { + const [asset1, asset2] = await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + const duplicateId = '00000000-0000-4000-8000-000000000029'; + await utils.setAssetDuplicateId(user1.accessToken, asset1.id, duplicateId); + await utils.setAssetDuplicateId(user1.accessToken, asset2.id, duplicateId); + + // Disable trash + await request(app) + .put('/system-config') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + trash: { enabled: false, days: 30 }, + }); + + const { status, body } = await request(app) + .post('/duplicates/resolve') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ + groups: [{ duplicateId, keepAssetIds: [asset1.id], trashAssetIds: [asset2.id] }], + }); + + expect(status).toBe(200); + expect(body.results[0].status).toBe('SUCCESS'); + + // Asset should be marked as deleted (force delete) + const { status: getStatus } = await request(app) + .get(`/assets/${asset2.id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + // Asset should still be accessible (soft deleted) but marked as deleted + expect(getStatus).toBe(200); + + // Re-enable trash for other tests + await utils.resetAdminConfig(admin.accessToken); + }); + }); +}); diff --git a/e2e/src/fixtures.ts b/e2e/src/fixtures.ts index a43e2c3e69..1e03ad6d24 100644 --- a/e2e/src/fixtures.ts +++ b/e2e/src/fixtures.ts @@ -1,7 +1,9 @@ export const uuidDto = { + invalid: 'invalid-uuid', // valid uuid v4 notFound: '00000000-0000-4000-a000-000000000000', dummy: '00000000-0000-4000-a000-000000000001', + dummy2: '00000000-0000-4000-a000-000000000002', }; const adminLoginDto = { @@ -55,6 +57,22 @@ export const createUserDto = { }; export const userDto = { + admin: { + name: signupDto.admin.name, + email: signupDto.admin.email, + password: signupDto.admin.password, + storageLabel: 'admin', + oauthId: '', + shouldChangePassword: false, + profileImagePath: '', + createdAt: new Date('2021-01-01'), + deletedAt: null, + updatedAt: new Date('2021-01-01'), + tags: [], + assets: [], + quotaSizeInBytes: null, + quotaUsageInBytes: 0, + }, user1: { name: createUserDto.user1.name, email: createUserDto.user1.email, diff --git a/e2e/src/responses.ts b/e2e/src/responses.ts index 54cfb814e8..3d7971d6f0 100644 --- a/e2e/src/responses.ts +++ b/e2e/src/responses.ts @@ -1,7 +1,130 @@ import { expect } from 'vitest'; export const errorDto = { - badRequest: (message: any = null) => ({ - message: message ?? expect.anything(), + unauthorized: { + error: 'Unauthorized', + statusCode: 401, + message: 'Authentication required', + correlationId: expect.any(String), + }, + unauthorizedWithMessage: (message: string) => ({ + error: 'Unauthorized', + statusCode: 401, + message, + correlationId: expect.any(String), }), + forbidden: { + error: 'Forbidden', + statusCode: 403, + message: expect.any(String), + correlationId: expect.any(String), + }, + missingPermission: (permission: string) => ({ + error: 'Forbidden', + statusCode: 403, + message: `Missing required permission: ${permission}`, + correlationId: expect.any(String), + }), + wrongPassword: { + error: 'Bad Request', + statusCode: 400, + message: 'Wrong password', + correlationId: expect.any(String), + }, + invalidToken: { + error: 'Unauthorized', + statusCode: 401, + message: 'Invalid user token', + correlationId: expect.any(String), + }, + invalidShareKey: { + error: 'Unauthorized', + statusCode: 401, + message: 'Invalid share key', + correlationId: expect.any(String), + }, + passwordRequired: { + error: 'Unauthorized', + statusCode: 401, + message: 'Password required', + correlationId: expect.any(String), + }, + badRequest: (message: any = null) => ({ + error: 'Bad Request', + statusCode: 400, + message: message ?? expect.anything(), + correlationId: expect.any(String), + }), + noPermission: { + error: 'Bad Request', + statusCode: 400, + message: expect.stringContaining('Not found or no'), + correlationId: expect.any(String), + }, + incorrectLogin: { + error: 'Unauthorized', + statusCode: 401, + message: 'Incorrect email or password', + correlationId: expect.any(String), + }, + alreadyHasAdmin: { + error: 'Bad Request', + statusCode: 400, + message: 'The server already has an admin', + correlationId: expect.any(String), + }, + invalidEmail: { + error: 'Bad Request', + statusCode: 400, + message: ['email must be an email'], + correlationId: expect.any(String), + }, +}; + +export const signupResponseDto = { + admin: { + avatarColor: expect.any(String), + id: expect.any(String), + name: 'Immich Admin', + email: 'admin@immich.cloud', + storageLabel: 'admin', + profileImagePath: '', + // why? lol + shouldChangePassword: true, + isAdmin: true, + createdAt: expect.any(String), + updatedAt: expect.any(String), + deletedAt: null, + oauthId: '', + quotaUsageInBytes: 0, + quotaSizeInBytes: null, + status: 'active', + license: null, + profileChangedAt: expect.any(String), + }, +}; + +export const loginResponseDto = { + admin: { + accessToken: expect.any(String), + name: 'Immich Admin', + isAdmin: true, + isOnboarded: false, + profileImagePath: '', + shouldChangePassword: true, + userEmail: 'admin@immich.cloud', + userId: expect.any(String), + }, +}; +export const deviceDto = { + current: { + id: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + current: true, + isPendingSyncReset: false, + deviceOS: '', + deviceType: '', + appVersion: null, + }, }; diff --git a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts index e757c721d6..b69bd099ed 100644 --- a/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts +++ b/e2e/src/specs/maintenance/server/database-backups.e2e-spec.ts @@ -2,7 +2,7 @@ import { LoginResponseDto, ManualJobName } from '@immich/sdk'; import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; -import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; describe('/admin/database-backups', () => { let cookie: string | undefined; @@ -13,9 +13,6 @@ describe('/admin/database-backups', () => { admin = await utils.adminSetup({ onboarding: false, }); - }); - - beforeEach(async () => { await utils.resetBackups(admin.accessToken); }); @@ -108,7 +105,7 @@ describe('/admin/database-backups', () => { const { status, body } = await request(app).post('/admin/database-backups/start-restore').send(); expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Admin setup is not available')); + expect(body).toEqual(errorDto.badRequest('The server already has an admin')); }); it.sequential('should enter maintenance mode in "database restore mode"', async () => { @@ -118,7 +115,7 @@ describe('/admin/database-backups', () => { expect(status).toBe(201); - cookie = headers['set-cookie'][0].split(';', 1)[0]; + cookie = headers['set-cookie'][0].split(';')[0]; await expect .poll( @@ -224,7 +221,7 @@ describe('/admin/database-backups', () => { }); expect(status).toBe(201); - cookie = headers['set-cookie'][0].split(';', 1)[0]; + cookie = headers['set-cookie'][0].split(';')[0]; await expect .poll( @@ -295,7 +292,7 @@ describe('/admin/database-backups', () => { }); expect(status).toBe(201); - cookie = headers['set-cookie'][0].split(';', 1)[0]; + cookie = headers['set-cookie'][0].split(';')[0]; await expect .poll( diff --git a/e2e/src/specs/maintenance/server/maintenance.e2e-spec.ts b/e2e/src/specs/maintenance/server/maintenance.e2e-spec.ts index bea5f58557..8e4e154328 100644 --- a/e2e/src/specs/maintenance/server/maintenance.e2e-spec.ts +++ b/e2e/src/specs/maintenance/server/maintenance.e2e-spec.ts @@ -1,4 +1,5 @@ import { LoginResponseDto } from '@immich/sdk'; +import { createUserDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; @@ -7,16 +8,18 @@ import { beforeAll, describe, expect, it } from 'vitest'; describe('/admin/maintenance', () => { let cookie: string | undefined; let admin: LoginResponseDto; + let nonAdmin: LoginResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); + nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1); await utils.resetBackups(admin.accessToken); }); // => outside of maintenance mode - describe('GET /server/config', async () => { + describe('GET ~/server/config', async () => { it('should indicate we are out of maintenance mode', async () => { const { status, body } = await request(app).get('/server/config'); expect(status).toBe(200); @@ -46,6 +49,24 @@ describe('/admin/maintenance', () => { // => enter maintenance mode describe.sequential('POST /', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/admin/maintenance').send({ + active: false, + action: 'end', + }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .post('/admin/maintenance') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send({ action: 'end' }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should be a no-op if try to exit maintenance mode', async () => { const { status } = await request(app) .post('/admin/maintenance') @@ -64,7 +85,7 @@ describe('/admin/maintenance', () => { expect(status).toBe(201); - cookie = headers['set-cookie'][0].split(';', 1)[0]; + cookie = headers['set-cookie'][0].split(';')[0]; expect(cookie).toEqual( expect.stringMatching(/^immich_maintenance_token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/), ); @@ -78,7 +99,7 @@ describe('/admin/maintenance', () => { }, { interval: 500, - timeout: 60_000, + timeout: 10_000, }, ) .toBeTruthy(); @@ -111,7 +132,7 @@ describe('/admin/maintenance', () => { it('should fail without cookie or token in body', async () => { const { status, body } = await request(app).post('/admin/maintenance/login').send({}); expect(status).toBe(401); - expect(body).toEqual({ message: 'Missing JWT Token' }); + expect(body).toEqual(errorDto.unauthorizedWithMessage('Missing JWT Token')); }); it('should succeed with cookie', async () => { @@ -128,7 +149,7 @@ describe('/admin/maintenance', () => { const { status, body } = await request(app) .post('/admin/maintenance/login') .send({ - token: cookie!.split('=', 2)[1].trim(), + token: cookie!.split('=')[1].trim(), }); expect(status).toBe(201); expect(body).toEqual( @@ -169,7 +190,7 @@ describe('/admin/maintenance', () => { }, { interval: 500, - timeout: 60_000, + timeout: 10_000, }, ) .toBeFalsy(); diff --git a/e2e/src/specs/maintenance/web/maintenance.e2e-spec.ts b/e2e/src/specs/maintenance/web/maintenance.e2e-spec.ts index 6b58f5a425..8b1631f0bf 100644 --- a/e2e/src/specs/maintenance/web/maintenance.e2e-spec.ts +++ b/e2e/src/specs/maintenance/web/maintenance.e2e-spec.ts @@ -27,7 +27,7 @@ test.describe('Maintenance', () => { test('maintenance shows no options to users until they authenticate', async ({ page }) => { const setCookie = await utils.enterMaintenance(admin.accessToken); const cookie = setCookie - ?.map((cookie) => cookie.split(';', 1)[0].split('=')) + ?.map((cookie) => cookie.split(';')[0].split('=')) ?.find(([name]) => name === 'immich_maintenance_token'); expect(cookie).toBeTruthy(); diff --git a/e2e/src/specs/server/api/activity.e2e-spec.ts b/e2e/src/specs/server/api/activity.e2e-spec.ts new file mode 100644 index 0000000000..9ce9b4b916 --- /dev/null +++ b/e2e/src/specs/server/api/activity.e2e-spec.ts @@ -0,0 +1,378 @@ +import { + ActivityCreateDto, + AlbumResponseDto, + AlbumUserRole, + AssetMediaResponseDto, + LoginResponseDto, + ReactionType, + createActivity as create, + createAlbum, + removeAssetFromAlbum, +} from '@immich/sdk'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; + +describe('/activities', () => { + let admin: LoginResponseDto; + let nonOwner: LoginResponseDto; + let asset: AssetMediaResponseDto; + let album: AlbumResponseDto; + + const createActivity = (dto: ActivityCreateDto, accessToken?: string) => + create({ activityCreateDto: dto }, { headers: asBearerAuth(accessToken || admin.accessToken) }); + + beforeAll(async () => { + await utils.resetDatabase(); + + admin = await utils.adminSetup(); + nonOwner = await utils.userSetup(admin.accessToken, createUserDto.user1); + asset = await utils.createAsset(admin.accessToken); + album = await createAlbum( + { + createAlbumDto: { + albumName: 'Album 1', + assetIds: [asset.id], + albumUsers: [{ userId: nonOwner.userId, role: AlbumUserRole.Editor }], + }, + }, + { headers: asBearerAuth(admin.accessToken) }, + ); + }); + + beforeEach(async () => { + await utils.resetDatabase(['activity']); + }); + + describe('GET /activities', () => { + it('should start off empty', async () => { + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(body).toEqual([]); + expect(status).toEqual(200); + }); + + it('should filter by album id', async () => { + const album2 = await createAlbum( + { + createAlbumDto: { + albumName: 'Album 2', + assetIds: [asset.id], + }, + }, + { headers: asBearerAuth(admin.accessToken) }, + ); + + const [reaction] = await Promise.all([ + createActivity({ albumId: album.id, type: ReactionType.Like }), + createActivity({ albumId: album2.id, type: ReactionType.Like }), + ]); + + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(200); + expect(body.length).toBe(1); + expect(body[0]).toEqual(reaction); + }); + + it('should filter by type=comment', async () => { + const [reaction] = await Promise.all([ + createActivity({ + albumId: album.id, + type: ReactionType.Comment, + comment: 'comment', + }), + createActivity({ albumId: album.id, type: ReactionType.Like }), + ]); + + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id, type: 'comment' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(200); + expect(body.length).toBe(1); + expect(body[0]).toEqual(reaction); + }); + + it('should filter by type=like', async () => { + const [reaction] = await Promise.all([ + createActivity({ albumId: album.id, type: ReactionType.Like }), + createActivity({ + albumId: album.id, + type: ReactionType.Comment, + comment: 'comment', + }), + ]); + + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id, type: 'like' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(200); + expect(body.length).toBe(1); + expect(body[0]).toEqual(reaction); + }); + + it('should filter by userId', async () => { + const reaction = await createActivity({ albumId: album.id, type: ReactionType.Like }); + + const response1 = await request(app) + .get('/activities') + .query({ albumId: album.id, userId: uuidDto.notFound }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(response1.status).toEqual(200); + expect(response1.body.length).toBe(0); + + const response2 = await request(app) + .get('/activities') + .query({ albumId: album.id, userId: admin.userId }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(response2.status).toEqual(200); + expect(response2.body.length).toBe(1); + expect(response2.body[0]).toEqual(reaction); + }); + + it('should filter by assetId', async () => { + const [reaction] = await Promise.all([ + createActivity({ + albumId: album.id, + assetId: asset.id, + type: ReactionType.Like, + }), + createActivity({ albumId: album.id, type: ReactionType.Like }), + ]); + + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id, assetId: asset.id }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(200); + expect(body.length).toBe(1); + expect(body[0]).toEqual(reaction); + }); + }); + + describe('POST /activities', () => { + it('should add a comment to an album', async () => { + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + albumId: album.id, + type: 'comment', + comment: 'This is my first comment', + }); + expect(status).toEqual(201); + expect(body).toEqual({ + id: expect.any(String), + assetId: null, + createdAt: expect.any(String), + type: 'comment', + comment: 'This is my first comment', + user: expect.objectContaining({ email: admin.userEmail }), + }); + }); + + it('should add a like to an album', async () => { + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, type: 'like' }); + expect(status).toEqual(201); + expect(body).toEqual({ + id: expect.any(String), + assetId: null, + createdAt: expect.any(String), + type: 'like', + comment: null, + user: expect.objectContaining({ email: admin.userEmail }), + }); + }); + + it('should return a 200 for a duplicate like on the album', async () => { + const reaction = await createActivity({ albumId: album.id, type: ReactionType.Like }); + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, type: 'like' }); + expect(status).toEqual(200); + expect(body).toEqual(reaction); + }); + + it('should not confuse an album like with an asset like', async () => { + const reaction = await createActivity({ + albumId: album.id, + assetId: asset.id, + type: ReactionType.Like, + }); + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, type: 'like' }); + expect(status).toEqual(201); + expect(body.id).not.toEqual(reaction.id); + }); + + it('should add a comment to an asset', async () => { + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + albumId: album.id, + assetId: asset.id, + type: 'comment', + comment: 'This is my first comment', + }); + expect(status).toEqual(201); + expect(body).toEqual({ + id: expect.any(String), + assetId: asset.id, + createdAt: expect.any(String), + type: 'comment', + comment: 'This is my first comment', + user: expect.objectContaining({ email: admin.userEmail }), + }); + }); + + it('should add a like to an asset', async () => { + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, assetId: asset.id, type: 'like' }); + expect(status).toEqual(201); + expect(body).toEqual({ + id: expect.any(String), + assetId: asset.id, + createdAt: expect.any(String), + type: 'like', + comment: null, + user: expect.objectContaining({ email: admin.userEmail }), + }); + }); + + it('should return a 200 for a duplicate like on an asset', async () => { + const reaction = await createActivity({ + albumId: album.id, + assetId: asset.id, + type: ReactionType.Like, + }); + + const { status, body } = await request(app) + .post('/activities') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ albumId: album.id, assetId: asset.id, type: 'like' }); + expect(status).toEqual(200); + expect(body).toEqual(reaction); + }); + }); + + describe('DELETE /activities/:id', () => { + it('should remove a comment from an album', async () => { + const reaction = await createActivity({ + albumId: album.id, + type: ReactionType.Comment, + comment: 'This is a test comment', + }); + const { status } = await request(app) + .delete(`/activities/${reaction.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(204); + }); + + it('should remove a like from an album', async () => { + const reaction = await createActivity({ + albumId: album.id, + type: ReactionType.Like, + }); + const { status } = await request(app) + .delete(`/activities/${reaction.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(204); + }); + + it('should let the owner remove a comment by another user', async () => { + const reaction = await createActivity({ + albumId: album.id, + type: ReactionType.Comment, + comment: 'This is a test comment', + }); + + const { status } = await request(app) + .delete(`/activities/${reaction.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toEqual(204); + }); + + it('should not let a user remove a comment by another user', async () => { + const reaction = await createActivity({ + albumId: album.id, + type: ReactionType.Comment, + comment: 'This is a test comment', + }); + + const { status, body } = await request(app) + .delete(`/activities/${reaction.id}`) + .set('Authorization', `Bearer ${nonOwner.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('Not found or no activity.delete access')); + }); + + it('should let a non-owner remove their own comment', async () => { + const reaction = await createActivity( + { + albumId: album.id, + type: ReactionType.Comment, + comment: 'This is a test comment', + }, + nonOwner.accessToken, + ); + + const { status } = await request(app) + .delete(`/activities/${reaction.id}`) + .set('Authorization', `Bearer ${nonOwner.accessToken}`); + + expect(status).toBe(204); + }); + + it('should return empty list when asset is removed', async () => { + const album3 = await createAlbum( + { + createAlbumDto: { + albumName: 'Album 3', + assetIds: [asset.id], + }, + }, + { headers: asBearerAuth(admin.accessToken) }, + ); + + await createActivity({ albumId: album3.id, assetId: asset.id, type: ReactionType.Like }); + + await removeAssetFromAlbum( + { + id: album3.id, + bulkIdsDto: { + ids: [asset.id], + }, + }, + { headers: asBearerAuth(admin.accessToken) }, + ); + + const { status, body } = await request(app) + .get('/activities') + .query({ albumId: album.id }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toEqual(200); + expect(body).toEqual([]); + }); + }); +}); diff --git a/e2e/src/specs/server/api/album.e2e-spec.ts b/e2e/src/specs/server/api/album.e2e-spec.ts index 2969d606fe..a9e90940ab 100644 --- a/e2e/src/specs/server/api/album.e2e-spec.ts +++ b/e2e/src/specs/server/api/album.e2e-spec.ts @@ -120,7 +120,6 @@ describe('/albums', () => { }), ]); - // eslint-disable-next-line unicorn/no-unreadable-array-destructuring [user2Albums[0]] = await Promise.all([ getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }), deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }), @@ -131,11 +130,12 @@ describe('/albums', () => { describe('GET /albums', () => { it("should not show other users' favorites", async () => { const { status, body } = await request(app) - .get(`/albums/${user1Albums[0].id}`) + .get(`/albums/${user1Albums[0].id}?withoutAssets=false`) .set('Authorization', `Bearer ${user2.accessToken}`); expect(status).toEqual(200); expect(body).toEqual({ ...user1Albums[0], + assets: [expect.objectContaining({ isFavorite: false })], contributorCounts: [{ userId: user1.userId, assetCount: 1 }], lastModifiedAssetTimestamp: expect.any(String), startDate: expect.any(String), @@ -147,7 +147,7 @@ describe('/albums', () => { it('should not return shared albums with a deleted owner', async () => { const { status, body } = await request(app) - .get('/albums?isShared=true') + .get('/albums?shared=true') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); @@ -155,31 +155,23 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedLink, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedEditorUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedViewerUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ + ownerId: user2.userId, albumName: user2SharedUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, - ]), shared: true, }), ]), @@ -189,164 +181,82 @@ describe('/albums', () => { it('should return the album collection including owned and shared', async () => { const { status, body } = await request(app).get('/albums').set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toHaveLength(5); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - albumName: user1SharedEditorUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), - shared: true, - }), - expect.objectContaining({ - albumName: user1SharedViewerUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), - shared: true, - }), - expect.objectContaining({ - albumName: user1SharedLink, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), - shared: true, - }), - expect.objectContaining({ - albumName: user1NotShared, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), - shared: false, - }), - expect.objectContaining({ - albumName: user2SharedUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, - ]), - shared: true, - }), - ]), - ); - }); - - it('should return the album collection filtered by isShared', async () => { - const { status, body } = await request(app) - .get('/albums?isShared=true') - .set('Authorization', `Bearer ${user1.accessToken}`); - expect(status).toBe(200); expect(body).toHaveLength(4); expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedEditorUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedViewerUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ + ownerId: user1.userId, albumName: user1SharedLink, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: true, }), expect.objectContaining({ - albumName: user2SharedUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, - ]), - shared: true, - }), - ]), - ); - }); - - it('should return the album collection filtered by NOT isShared', async () => { - const { status, body } = await request(app) - .get('/albums?isShared=false') - .set('Authorization', `Bearer ${user1.accessToken}`); - expect(status).toBe(200); - expect(body).toHaveLength(1); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ + ownerId: user1.userId, albumName: user1NotShared, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }, - ]), shared: false, }), ]), ); }); - it('should return only owned albums when filtered by isOwned=true', async () => { + it('should return the album collection filtered by shared', async () => { const { status, body } = await request(app) - .get('/albums?isOwned=true') + .get('/albums?shared=true') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(4); expect(body).toEqual( expect.arrayContaining([ - expect.objectContaining({ albumName: user1SharedEditorUser }), - expect.objectContaining({ albumName: user1SharedViewerUser }), - expect.objectContaining({ albumName: user1SharedLink }), - expect.objectContaining({ albumName: user1NotShared }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedEditorUser, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedViewerUser, + shared: true, + }), + expect.objectContaining({ + ownerId: user1.userId, + albumName: user1SharedLink, + shared: true, + }), + expect.objectContaining({ + ownerId: user2.userId, + albumName: user2SharedUser, + shared: true, + }), ]), ); }); - it('should return only shared-with-me albums when filtered by isOwned=false', async () => { + it('should return the album collection filtered by NOT shared', async () => { const { status, body } = await request(app) - .get('/albums?isOwned=false') + .get('/albums?shared=false') .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(1); expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ - albumName: user2SharedUser, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user2.userId }) }, - ]), + ownerId: user1.userId, + albumName: user1NotShared, + shared: false, }), ]), ); }); - it('should return owned shared-out albums when filtered by isOwned=true&ishared=true', async () => { - const { status, body } = await request(app) - .get('/albums?isOwned=true&isShared=true') - .set('Authorization', `Bearer ${user1.accessToken}`); - expect(status).toBe(200); - expect(body).toHaveLength(3); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ albumName: user1SharedEditorUser }), - expect.objectContaining({ albumName: user1SharedViewerUser }), - expect.objectContaining({ albumName: user1SharedLink }), - ]), - ); - }); - - it('should return empty list when filtered by isOwned=false&isShared=false', async () => { - const { status, body } = await request(app) - .get('/albums?isOwned=false&isShared=false') - .set('Authorization', `Bearer ${user1.accessToken}`); - expect(status).toBe(200); - expect(body).toHaveLength(0); - }); - it('should return the album collection filtered by assetId', async () => { const { status, body } = await request(app) .get(`/albums?assetId=${user1Asset2.id}`) @@ -355,17 +265,17 @@ describe('/albums', () => { expect(body).toHaveLength(2); }); - it('should return the album collection filtered by assetId and ignores isShared=true', async () => { + it('should return the album collection filtered by assetId and ignores shared=true', async () => { const { status, body } = await request(app) - .get(`/albums?isShared=true&assetId=${user1Asset1.id}`) + .get(`/albums?shared=true&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(5); }); - it('should return the album collection filtered by assetId and ignores isShared=false', async () => { + it('should return the album collection filtered by assetId and ignores shared=false', async () => { const { status, body } = await request(app) - .get(`/albums?isShared=false&assetId=${user1Asset1.id}`) + .get(`/albums?shared=false&assetId=${user1Asset1.id}`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toHaveLength(5); @@ -377,17 +287,13 @@ describe('/albums', () => { expect(body).toEqual( expect.arrayContaining([ expect.objectContaining({ + ownerId: user4.userId, albumName: user4DeletedAsset, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) }, - ]), shared: false, }), expect.objectContaining({ + ownerId: user4.userId, albumName: user4Empty, - albumUsers: expect.arrayContaining([ - { role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user4.userId }) }, - ]), shared: false, }), ]), @@ -398,12 +304,13 @@ describe('/albums', () => { describe('GET /albums/:id', () => { it('should return album info for own album', async () => { const { status, body } = await request(app) - .get(`/albums/${user1Albums[0].id}`) + .get(`/albums/${user1Albums[0].id}?withoutAssets=false`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); expect(body).toEqual({ ...user1Albums[0], + assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], contributorCounts: [{ userId: user1.userId, assetCount: 1 }], lastModifiedAssetTimestamp: expect.any(String), startDate: expect.any(String), @@ -415,7 +322,7 @@ describe('/albums', () => { it('should return album info for shared album (editor)', async () => { const { status, body } = await request(app) - .get(`/albums/${user2Albums[0].id}`) + .get(`/albums/${user2Albums[0].id}?withoutAssets=false`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); @@ -424,14 +331,14 @@ describe('/albums', () => { it('should return album info for shared album (viewer)', async () => { const { status, body } = await request(app) - .get(`/albums/${user1Albums[3].id}`) + .get(`/albums/${user1Albums[3].id}?withoutAssets=false`) .set('Authorization', `Bearer ${user2.accessToken}`); expect(status).toBe(200); expect(body).toMatchObject({ id: user1Albums[3].id }); }); - it('should return album info', async () => { + it('should return album info with assets when withoutAssets is undefined', async () => { const { status, body } = await request(app) .get(`/albums/${user1Albums[0].id}`) .set('Authorization', `Bearer ${user1.accessToken}`); @@ -439,6 +346,25 @@ describe('/albums', () => { expect(status).toBe(200); expect(body).toEqual({ ...user1Albums[0], + assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], + lastModifiedAssetTimestamp: expect.any(String), + startDate: expect.any(String), + endDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, + }); + }); + + it('should return album info without assets when withoutAssets is true', async () => { + const { status, body } = await request(app) + .get(`/albums/${user1Albums[0].id}?withoutAssets=true`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...user1Albums[0], + assets: [], contributorCounts: [{ userId: user1.userId, assetCount: 1 }], assetCount: 1, lastModifiedAssetTimestamp: expect.any(String), @@ -453,21 +379,21 @@ describe('/albums', () => { await utils.deleteAssets(user1.accessToken, [user1Asset2.id]); const { status, body } = await request(app) - .get(`/albums/${user2Albums[0].id}`) + .get(`/albums/${user2Albums[0].id}?withoutAssets=true`) .set('Authorization', `Bearer ${user1.accessToken}`); expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - contributorCounts: [{ userId: user1.userId, assetCount: 1 }], - assetCount: 1, - lastModifiedAssetTimestamp: expect.any(String), - endDate: expect.any(String), - startDate: expect.any(String), - albumUsers: expect.any(Array), - shared: true, - }), - ); + expect(body).toEqual({ + ...user2Albums[0], + assets: [], + contributorCounts: [{ userId: user1.userId, assetCount: 1 }], + assetCount: 1, + lastModifiedAssetTimestamp: expect.any(String), + endDate: expect.any(String), + startDate: expect.any(String), + albumUsers: expect.any(Array), + shared: true, + }); }); }); @@ -493,26 +419,28 @@ describe('/albums', () => { id: expect.any(String), createdAt: expect.any(String), updatedAt: expect.any(String), + ownerId: user1.userId, albumName: 'New album', description: '', albumThumbnailAssetId: null, shared: false, - albumUsers: [{ role: AlbumUserRole.Owner, user: expect.objectContaining({ id: user1.userId }) }], + albumUsers: [], hasSharedLink: false, + assets: [], assetCount: 0, + owner: expect.objectContaining({ email: user1.userEmail }), isActivityEnabled: true, order: AssetOrder.Desc, }); }); - it('should deduplicate owner from albumUsers on create', async () => { + 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(201); - expect(body.albumUsers).toHaveLength(1); - expect(body.albumUsers[0]).toMatchObject({ role: AlbumUserRole.Owner, user: { id: user1.userId } }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('Cannot share album with owner')); }); }); @@ -613,6 +541,16 @@ describe('/albums', () => { }); describe('DELETE /albums/:id/assets', () => { + it('should require authorization', async () => { + const { status, body } = await request(app) + .delete(`/albums/${user1Albums[1].id}/assets`) + .set('Authorization', `Bearer ${user2.accessToken}`) + .send({ ids: [user1Asset1.id] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + it('should be able to remove foreign asset from owned album', async () => { const { status, body } = await request(app) .delete(`/albums/${user2Albums[0].id}/assets`) @@ -706,11 +644,11 @@ describe('/albums', () => { expect(status).toBe(200); expect(body).toEqual( expect.objectContaining({ - albumUsers: expect.arrayContaining([ + albumUsers: [ expect.objectContaining({ user: expect.objectContaining({ id: user2.userId }), }), - ]), + ], }), ); }); @@ -721,8 +659,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(200); - expect(body.albumUsers.length).toEqual(1); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner')); }); it('should not be able to add existing user to shared album', async () => { @@ -736,8 +674,8 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`) .send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] }); - expect(status).toBe(200); - expect(body.albumUsers.length).toEqual(2); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('User already added')); }); }); @@ -748,7 +686,7 @@ describe('/albums', () => { albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], }); - expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); + expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); const { status } = await request(app) .put(`/albums/${album.id}/user/${user2.userId}`) @@ -763,10 +701,7 @@ describe('/albums', () => { .set('Authorization', `Bearer ${user1.accessToken}`); expect(body).toEqual( expect.objectContaining({ - albumUsers: [ - expect.objectContaining({ role: AlbumUserRole.Owner }), - expect.objectContaining({ role: AlbumUserRole.Editor }), - ], + albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })], }), ); }); @@ -777,7 +712,7 @@ describe('/albums', () => { albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }], }); - expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Viewer); + expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer); const { status, body } = await request(app) .put(`/albums/${album.id}/user/${user2.userId}`) @@ -787,22 +722,5 @@ describe('/albums', () => { expect(status).toBe(400); expect(body).toEqual(errorDto.badRequest('Not found or no album.share access')); }); - - it('should not allow an editor to change the role of an owner', async () => { - const album = await utils.createAlbum(user1.accessToken, { - albumName: 'testAlbum', - albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }], - }); - - expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Editor); - - const { status, body } = await request(app) - .put(`/albums/${album.id}/user/${user1.userId}`) - .set('Authorization', `Bearer ${user2.accessToken}`) - .send({ role: AlbumUserRole.Editor }); - - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('User is owner')); - }); }); }); diff --git a/e2e/src/specs/server/api/api-key.e2e-spec.ts b/e2e/src/specs/server/api/api-key.e2e-spec.ts index ec481cee01..28d134a664 100644 --- a/e2e/src/specs/server/api/api-key.e2e-spec.ts +++ b/e2e/src/specs/server/api/api-key.e2e-spec.ts @@ -24,6 +24,13 @@ describe('/api-keys', () => { }); describe('POST /api-keys', () => { + it('should not work without permission', async () => { + const { secret } = await create(user.accessToken, [Permission.ApiKeyRead]); + const { status, body } = await request(app).post('/api-keys').set('x-api-key', secret).send({ name: 'API Key' }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('apiKey.create')); + }); + it('should work with apiKey.create', async () => { const { secret } = await create(user.accessToken, [Permission.ApiKeyCreate, Permission.ApiKeyRead]); const { status, body } = await request(app) @@ -34,14 +41,14 @@ describe('/api-keys', () => { permissions: [Permission.ApiKeyRead], }); expect(body).toEqual({ - id: expect.any(String), - name: 'API Key', - permissions: [Permission.ApiKeyRead], - createdAt: expect.any(String), - updatedAt: expect.any(String), secret: expect.any(String), - // TODO: remove in v4 - apiKey: expect.any(Object), + apiKey: { + id: expect.any(String), + name: 'API Key', + permissions: [Permission.ApiKeyRead], + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, }); expect(status).toBe(201); }); @@ -72,14 +79,14 @@ describe('/api-keys', () => { .send({ name: 'API Key', permissions: [Permission.All] }) .set('Authorization', `Bearer ${admin.accessToken}`); expect(body).toEqual({ - id: expect.any(String), - name: 'API Key', - permissions: [Permission.All], - createdAt: expect.any(String), - updatedAt: expect.any(String), + apiKey: { + id: expect.any(String), + name: 'API Key', + permissions: [Permission.All], + createdAt: expect.any(String), + updatedAt: expect.any(String), + }, secret: expect.any(String), - // TODO: remove in v4 - apiKey: expect.any(Object), }); expect(status).toEqual(201); }); @@ -93,30 +100,32 @@ describe('/api-keys', () => { }); it('should return a list of api keys', async () => { - const [apiKey1, apiKey2, apiKey3] = await Promise.all([ + const [{ apiKey: apiKey1 }, { apiKey: apiKey2 }, { apiKey: apiKey3 }] = await Promise.all([ create(admin.accessToken, [Permission.All]), create(admin.accessToken, [Permission.All]), create(admin.accessToken, [Permission.All]), ]); - const { status, body } = await request(app).get('/api-keys').set('Authorization', `Bearer ${admin.accessToken}`); expect(body).toHaveLength(3); - expect(body).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: apiKey1.id }), - expect.objectContaining({ id: apiKey2.id }), - expect.objectContaining({ id: apiKey3.id }), - ]), - ); + expect(body).toEqual(expect.arrayContaining([apiKey1, apiKey2, apiKey3])); expect(status).toEqual(200); }); }); describe('GET /api-keys/:id', () => { - it('should get api key details', async () => { - const { id } = await create(user.accessToken, [Permission.All]); + it('should require authorization', async () => { + const { apiKey } = await create(user.accessToken, [Permission.All]); const { status, body } = await request(app) - .get(`/api-keys/${id}`) + .get(`/api-keys/${apiKey.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('API Key not found')); + }); + + it('should get api key details', async () => { + const { apiKey } = await create(user.accessToken, [Permission.All]); + const { status, body } = await request(app) + .get(`/api-keys/${apiKey.id}`) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(200); expect(body).toEqual({ @@ -130,10 +139,20 @@ describe('/api-keys', () => { }); describe('PUT /api-keys/:id', () => { - it('should update api key details', async () => { - const { id } = await create(user.accessToken, [Permission.All]); + it('should require authorization', async () => { + const { apiKey } = await create(user.accessToken, [Permission.All]); const { status, body } = await request(app) - .put(`/api-keys/${id}`) + .put(`/api-keys/${apiKey.id}`) + .send({ name: 'new name', permissions: [Permission.All] }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('API Key not found')); + }); + + it('should update api key details', async () => { + const { apiKey } = await create(user.accessToken, [Permission.All]); + const { status, body } = await request(app) + .put(`/api-keys/${apiKey.id}`) .send({ name: 'new name', permissions: [Permission.ActivityCreate, Permission.ActivityRead, Permission.ActivityUpdate], @@ -151,10 +170,19 @@ describe('/api-keys', () => { }); describe('DELETE /api-keys/:id', () => { + it('should require authorization', async () => { + const { apiKey } = await create(user.accessToken, [Permission.All]); + const { status, body } = await request(app) + .delete(`/api-keys/${apiKey.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest('API Key not found')); + }); + it('should delete an api key', async () => { - const { id } = await create(user.accessToken, [Permission.All]); + const { apiKey } = await create(user.accessToken, [Permission.All]); const { status } = await request(app) - .delete(`/api-keys/${id}`) + .delete(`/api-keys/${apiKey.id}`) .set('Authorization', `Bearer ${user.accessToken}`); expect(status).toBe(204); }); diff --git a/e2e/src/specs/server/api/asset.e2e-spec.ts b/e2e/src/specs/server/api/asset.e2e-spec.ts index ad7f102810..11e825a7cd 100644 --- a/e2e/src/specs/server/api/asset.e2e-spec.ts +++ b/e2e/src/specs/server/api/asset.e2e-spec.ts @@ -1,12 +1,14 @@ import { AssetMediaResponseDto, AssetMediaStatus, + AssetResponseDto, AssetTypeEnum, AssetVisibility, getAssetInfo, getMyUser, LoginResponseDto, SharedLinkType, + updateConfig, } from '@immich/sdk'; import { exiftool } from 'exiftool-vendored'; import { DateTime } from 'luxon'; @@ -17,12 +19,13 @@ import { Socket } from 'socket.io-client'; import { createUserDto, uuidDto } from 'src/fixtures'; import { makeRandomImage } from 'src/generators'; import { errorDto } from 'src/responses'; -import { app, asBearerAuth, tempDir, testAssetDir, utils } from 'src/utils'; +import { app, asBearerAuth, tempDir, TEN_TIMES, testAssetDir, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; const locationAssetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`; const ratingAssetFilepath = `${testAssetDir}/metadata/rating/mongolels.jpg`; +const facesAssetDir = `${testAssetDir}/metadata/faces`; const readTags = async (bytes: Buffer, filename: string) => { const filepath = join(tempDir, filename); @@ -92,8 +95,8 @@ describe('/asset', () => { utils.createAsset(user1.accessToken), utils.createAsset(user1.accessToken, { isFavorite: true, - fileCreatedAt: yesterday.toUTC().toISO(), - fileModifiedAt: yesterday.toUTC().toISO(), + fileCreatedAt: yesterday.toISO(), + fileModifiedAt: yesterday.toISO(), assetData: { filename: 'example.mp4' }, }), utils.createAsset(user1.accessToken), @@ -130,7 +133,7 @@ describe('/asset', () => { }); await utils.createFace({ assetId: user1Assets[0].id, - personGroupId: person1.id, + personId: person1.id, }); }; beforeAll(setupTests, 30_000); @@ -151,6 +154,14 @@ describe('/asset', () => { }); describe('GET /assets/:id', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .get(`/assets/${user2Assets[0].id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + it('should get the asset info', async () => { const { status, body } = await request(app) .get(`/assets/${user1Assets[0].id}`) @@ -175,6 +186,78 @@ describe('/asset', () => { }); }); + describe('faces', () => { + const metadataFaceTests = [ + { + description: 'without orientation', + filename: 'portrait.jpg', + }, + { + description: 'adjusting face regions to orientation', + filename: 'portrait-orientation-6.jpg', + }, + ]; + // should produce same resulting face region coordinates for any orientation + const expectedFaces = [ + { + name: 'Marie Curie', + birthDate: null, + isHidden: false, + faces: [ + { + imageHeight: 700, + imageWidth: 840, + boundingBoxX1: 261, + boundingBoxX2: 356, + boundingBoxY1: 146, + boundingBoxY2: 284, + sourceType: 'exif', + }, + ], + }, + { + name: 'Pierre Curie', + birthDate: null, + isHidden: false, + faces: [ + { + imageHeight: 700, + imageWidth: 840, + boundingBoxX1: 536, + boundingBoxX2: 618, + boundingBoxY1: 83, + boundingBoxY2: 252, + sourceType: 'exif', + }, + ], + }, + ]; + + it.each(metadataFaceTests)('should get the asset faces from $filename $description', async ({ filename }) => { + const config = await utils.getSystemConfig(admin.accessToken); + config.metadata.faces.import = true; + await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) }); + + const facesAsset = await utils.createAsset(admin.accessToken, { + assetData: { + filename, + bytes: await readFile(`${facesAssetDir}/${filename}`), + }, + }); + + await utils.waitForWebsocketEvent({ event: 'assetUpload', id: facesAsset.id }); + + const { status, body } = await request(app) + .get(`/assets/${facesAsset.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body.id).toEqual(facesAsset.id); + const sortedPeople = body.people.toSorted((a: any, b: any) => a.name.localeCompare(b.name)); + expect(sortedPeople).toMatchObject(expectedFaces); + }); + }); + it('should work with a shared link', async () => { const sharedLink = await utils.createSharedLink(user1.accessToken, { type: SharedLinkType.Individual, @@ -297,7 +380,66 @@ describe('/asset', () => { }); }); + describe('GET /assets/random', () => { + beforeAll(async () => { + await Promise.all([ + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + utils.createAsset(user1.accessToken), + ]); + + await utils.waitForQueueFinish(admin.accessToken, 'thumbnailGeneration'); + }); + + it.each(TEN_TIMES)('should return 1 random assets', async () => { + const { status, body } = await request(app) + .get('/assets/random') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + + const assets: AssetResponseDto[] = body; + expect(assets.length).toBe(1); + expect(assets[0].ownerId).toBe(user1.userId); + }); + + it.each(TEN_TIMES)('should return 2 random assets', async () => { + const { status, body } = await request(app) + .get('/assets/random?count=2') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(200); + + const assets: AssetResponseDto[] = body; + expect(assets.length).toBe(2); + + for (const asset of assets) { + expect(asset.ownerId).toBe(user1.userId); + } + }); + + it.skip('should return 1 asset if there are 10 assets in the database but user 2 only has 1', async () => { + const { status, body } = await request(app) + .get('/assets/random') + .set('Authorization', `Bearer ${user2.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual([expect.objectContaining({ id: user2Assets[0].id })]); + }); + }); + describe('PUT /assets/:id', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .put(`/assets/${user2Assets[0].id}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + it('should favorite an asset', async () => { const before = await utils.getAssetInfo(user1.accessToken, user1Assets[0].id); expect(before.isFavorite).toBe(false); @@ -764,7 +906,7 @@ describe('/asset', () => { exifImageWidth: 4032, exifImageHeight: 3024, latitude: 41.2203, - longitude: -96.071625, + longitude: -96.071_625, make: 'Apple', model: 'iPhone 7', lensModel: 'iPhone 7 back camera 3.99mm f/1.8', @@ -956,9 +1098,9 @@ describe('/asset', () => { fileSizeInByte: 31_175_472, focalLength: 18.3, iso: 100, - latitude: 36.61324, + latitude: 36.613_24, lensModel: '18.3mm F2.8', - longitude: -121.89785, + longitude: -121.897_85, make: 'RICOH IMAGING COMPANY, LTD.', model: 'RICOH GR III', orientation: '1', @@ -1000,6 +1142,8 @@ describe('/asset', () => { const { body, status } = await request(app) .post('/assets') .set('Authorization', `Bearer ${quotaUser.accessToken}`) + .field('deviceAssetId', 'example-image') + .field('deviceId', 'e2e') .field('fileCreatedAt', new Date().toISOString()) .field('fileModifiedAt', new Date().toISOString()) .attach('assetData', makeRandomImage(), 'example.jpg'); @@ -1016,6 +1160,8 @@ describe('/asset', () => { const { body, status } = await request(app) .post('/assets') .set('Authorization', `Bearer ${quotaUser.accessToken}`) + .field('deviceAssetId', 'example-image') + .field('deviceId', 'e2e') .field('fileCreatedAt', new Date().toISOString()) .field('fileModifiedAt', new Date().toISOString()) .attach('assetData', randomBytes(2014), 'example.jpg'); @@ -1069,4 +1215,29 @@ describe('/asset', () => { expect(video.checksum).toStrictEqual(checksum); }); }); + + describe('POST /assets/exist', () => { + it('ignores invalid deviceAssetIds', async () => { + const response = await utils.checkExistingAssets(user1.accessToken, { + deviceId: 'test-assets-exist', + deviceAssetIds: ['invalid', 'INVALID'], + }); + + expect(response.existingIds).toHaveLength(0); + }); + + it('returns the IDs of existing assets', async () => { + await utils.createAsset(user1.accessToken, { + deviceId: 'test-assets-exist', + deviceAssetId: 'test-asset-0', + }); + + const response = await utils.checkExistingAssets(user1.accessToken, { + deviceId: 'test-assets-exist', + deviceAssetIds: ['test-asset-0'], + }); + + expect(response.existingIds).toEqual(['test-asset-0']); + }); + }); }); diff --git a/e2e/src/specs/server/api/integrity.e2e-spec.ts b/e2e/src/specs/server/api/integrity.e2e-spec.ts deleted file mode 100644 index 8b2b4364ae..0000000000 --- a/e2e/src/specs/server/api/integrity.e2e-spec.ts +++ /dev/null @@ -1,669 +0,0 @@ -import { - AssetMediaResponseDto, - IntegrityReportResponseDto, - LoginResponseDto, - ManualJobName, - QueueCommand, - QueueName, -} from '@immich/sdk'; -import { readFile } from 'node:fs/promises'; -import { app, testAssetDir, utils } from 'src/utils'; -import request from 'supertest'; -import { afterEach, beforeAll, describe, expect, it } from 'vitest'; - -const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`; -const asset1Filepath = `${testAssetDir}/albums/nature/el_torcal_rocks.jpg`; -const asset2Filepath = `${testAssetDir}/albums/nature/wood_anemones.jpg`; - -describe('/admin/integrity', () => { - let admin: LoginResponseDto; - let asset: AssetMediaResponseDto; - - let user1: LoginResponseDto; - let asset1: AssetMediaResponseDto; - - let user2: LoginResponseDto; - let asset2: AssetMediaResponseDto; - - beforeAll(async () => { - await utils.resetDatabase(); - admin = await utils.adminSetup(); - - user1 = await utils.userSetup(admin.accessToken, { - email: '1@example.com', - name: '1', - password: '1', - }); - - user2 = await utils.userSetup(admin.accessToken, { - email: '2@example.com', - name: '2', - password: '2', - }); - - for (const queue of Object.values(QueueName)) { - if (queue === QueueName.IntegrityCheck) { - continue; - } - - await utils.queueCommand(admin.accessToken, queue, { - command: QueueCommand.Pause, - }); - } - - asset = await utils.createAsset(admin.accessToken, { - assetData: { - filename: 'asset.jpg', - bytes: await readFile(assetFilepath), - }, - }); - - asset1 = await utils.createAsset(user1.accessToken, { - assetData: { - filename: 'asset.jpg', - bytes: await readFile(asset1Filepath), - }, - }); - - asset2 = await utils.createAsset(user2.accessToken, { - assetData: { - filename: 'asset.jpg', - bytes: await readFile(asset2Filepath), - }, - }); - - await utils.mkFolder('/data/bak'); - await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/bak/${admin.userId}`); - - for (const queue of Object.values(QueueName)) { - if (queue === QueueName.IntegrityCheck) { - continue; - } - - await utils.queueCommand(admin.accessToken, queue, { - command: QueueCommand.Empty, - }); - - await utils.queueCommand(admin.accessToken, queue, { - command: QueueCommand.Resume, - }); - } - }); - - afterEach(async () => { - await utils.deleteFolder(`/data/upload/${admin.userId}`); - await utils.copyFolder(`/data/bak/${admin.userId}`, `/data/upload/${admin.userId}`); - }); - - describe('POST /summary (& jobs)', async () => { - it.sequential('reports no issues', async () => { - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFilesDeleteAll, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual({ - missing_file: 0, - untracked_file: 0, - checksum_mismatch: 0, - }); - }); - - it.sequential('should detect an untracked file (job: check untracked files)', async () => { - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - untracked_file: 1, - }), - ); - }); - - it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => { - // these should not be detected: - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`); - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFilesRefresh, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - untracked_file: 0, - }), - ); - }); - - it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => { - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFilesDeleteAll, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - untracked_file: 0, - }), - ); - }); - - it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => { - await utils.deleteFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - missing_file: 1, - checksum_mismatch: 0, - }), - ); - }); - - it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => { - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFilesRefresh, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - missing_file: 0, - checksum_mismatch: 0, - }), - ); - }); - - it.sequential('should delete assets with missing files (job: delete all missing file reports)', async () => { - await utils.deleteFolder(`/data/upload/${user1.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus, body: listBody } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus).toBe(200); - expect(listBody).toEqual( - expect.objectContaining({ - missing_file: 1, - }), - ); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFilesDeleteAll, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - missing_file: 0, - }), - ); - - await expect(utils.getAssetInfo(user1.accessToken, asset1.id)).resolves.toEqual( - expect.objectContaining({ - isTrashed: true, - }), - ); - }); - - it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => { - await utils.truncateFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - checksum_mismatch: 1, - }), - ); - }); - - it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => { - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatchRefresh, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - checksum_mismatch: 0, - }), - ); - }); - - it.sequential( - 'should delete assets with mismatched checksum (job: delete all checksum mismatch reports)', - async () => { - await utils.truncateFolder(`/data/upload/${user2.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus, body: listBody } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus).toBe(200); - expect(listBody).toEqual( - expect.objectContaining({ - checksum_mismatch: 1, - }), - ); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatchDeleteAll, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/summary') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - checksum_mismatch: 0, - }), - ); - - await expect(utils.getAssetInfo(user2.accessToken, asset2.id)).resolves.toEqual( - expect.objectContaining({ - isTrashed: true, - }), - ); - }, - ); - }); - - describe('POST /report', async () => { - it.sequential('reports untracked files', async () => { - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/report?type=untracked_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual({ - nextCursor: undefined, - items: expect.arrayContaining([ - { - id: expect.any(String), - type: 'untracked_file', - path: `/data/upload/${admin.userId}/untracked1.png`, - assetId: null, - fileAssetId: null, - createdAt: expect.any(String), - }, - ]), - }); - }); - - it.sequential('reports missing files', async () => { - await utils.deleteFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/report?type=missing_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual({ - nextCursor: undefined, - items: expect.arrayContaining([ - { - id: expect.any(String), - type: 'missing_file', - path: expect.any(String), - assetId: asset.id, - fileAssetId: null, - createdAt: expect.any(String), - }, - ]), - }); - }); - - it.sequential('reports checksum mismatched files', async () => { - await utils.truncateFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, body } = await request(app) - .get('/admin/integrity/report?type=checksum_mismatch') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(body).toEqual({ - nextCursor: undefined, - items: expect.arrayContaining([ - { - id: expect.any(String), - type: 'checksum_mismatch', - path: expect.any(String), - assetId: asset.id, - fileAssetId: null, - createdAt: expect.any(String), - }, - ]), - }); - }); - }); - - describe('DELETE /report/:id', async () => { - it.sequential('delete untracked files', async () => { - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus, body: listBody } = await request(app) - .get('/admin/integrity/report?type=untracked_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus).toBe(200); - - const report = (listBody as IntegrityReportResponseDto).items.find( - (item) => item.path === `/data/upload/${admin.userId}/untracked1.png`, - )!; - - const { status } = await request(app) - .delete(`/admin/integrity/report/${report.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus2, body: listBody2 } = await request(app) - .get('/admin/integrity/report?type=untracked_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus2).toBe(200); - expect(listBody2).not.toBe( - expect.objectContaining({ - items: expect.arrayContaining([ - expect.objectContaining({ - id: report.id, - }), - ]), - }), - ); - }); - - it.sequential('delete assets missing files', async () => { - await utils.deleteFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus, body: listBody } = await request(app) - .get('/admin/integrity/report?type=missing_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus).toBe(200); - expect(listBody.items.length).toBe(1); - - const report = (listBody as IntegrityReportResponseDto).items[0]; - - const { status } = await request(app) - .delete(`/admin/integrity/report/${report.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityMissingFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus2, body: listBody2 } = await request(app) - .get('/admin/integrity/report?type=missing_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus2).toBe(200); - expect(listBody2.items.length).toBe(0); - }); - - it.sequential('delete assets with failing checksum', async () => { - await utils.truncateFolder(`/data/upload/${admin.userId}`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus, body: listBody } = await request(app) - .get('/admin/integrity/report?type=checksum_mismatch') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus).toBe(200); - expect(listBody.items.length).toBe(1); - - const report = (listBody as IntegrityReportResponseDto).items[0]; - - const { status } = await request(app) - .delete(`/admin/integrity/report/${report.id}`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityChecksumMismatch, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status: listStatus2, body: listBody2 } = await request(app) - .get('/admin/integrity/report?type=checksum_mismatch') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(listStatus2).toBe(200); - expect(listBody2.items.length).toBe(0); - }); - }); - - describe('GET /report/:type/csv', () => { - it.sequential('exports untracked files as csv', async () => { - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { status, headers, text } = await request(app) - .get('/admin/integrity/report/untracked_file/csv') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - expect(status).toBe(200); - expect(headers['content-type']).toContain('text/csv'); - expect(headers['content-disposition']).toContain('.csv'); - expect(text).toContain('id,type,assetId,fileAssetId,path'); - expect(text).toContain(`untracked_file`); - expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`); - }); - }); - - describe('GET /report/:id/file', () => { - it.sequential('downloads untracked file', async () => { - await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - const { body: listBody } = await request(app) - .get('/admin/integrity/report?type=untracked_file') - .set('Authorization', `Bearer ${admin.accessToken}`) - .send(); - - const report = (listBody as IntegrityReportResponseDto).items.find( - (item) => item.path === `/data/upload/${admin.userId}/untracked1.png`, - )!; - - const { status, headers, body } = await request(app) - .get(`/admin/integrity/report/${report.id}/file`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .buffer(true) - .send(); - - expect(status).toBe(200); - expect(headers['content-type']).toContain('application/octet-stream'); - expect(body.toString()).toBe('untracked-content'); - }); - }); -}); diff --git a/e2e/src/specs/server/api/jobs.e2e-spec.ts b/e2e/src/specs/server/api/jobs.e2e-spec.ts index b4228eeb91..be7984404b 100644 --- a/e2e/src/specs/server/api/jobs.e2e-spec.ts +++ b/e2e/src/specs/server/api/jobs.e2e-spec.ts @@ -2,7 +2,9 @@ import { LoginResponseDto, QueueCommand, QueueName, updateConfig } from '@immich import { cpSync, rmSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { basename } from 'node:path'; -import { asBearerAuth, testAssetDir, utils } from 'src/utils'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, testAssetDir, utils } from 'src/utils'; +import request from 'supertest'; import { afterEach, beforeAll, describe, expect, it } from 'vitest'; describe('/jobs', () => { @@ -45,7 +47,13 @@ describe('/jobs', () => { config.machineLearning.enabled = false; config.metadata.faces.import = false; config.machineLearning.clip.enabled = false; - await updateConfig({ adminConfigDto: config }, { headers: asBearerAuth(admin.accessToken) }); + await updateConfig({ systemConfigDto: config }, { headers: asBearerAuth(admin.accessToken) }); + }); + + it('should require authentication', async () => { + const { status, body } = await request(app).put('/jobs/metadataExtraction'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); }); it('should queue metadata extraction for missing assets', async () => { diff --git a/e2e/src/specs/server/api/library.e2e-spec.ts b/e2e/src/specs/server/api/library.e2e-spec.ts index c3b83cf4e4..4d67a84647 100644 --- a/e2e/src/specs/server/api/library.e2e-spec.ts +++ b/e2e/src/specs/server/api/library.e2e-spec.ts @@ -1,20 +1,28 @@ -import { LoginResponseDto } from '@immich/sdk'; -import { cpSync } from 'node:fs'; +import { LibraryResponseDto, LoginResponseDto, getAllLibraries } from '@immich/sdk'; +import { cpSync, existsSync, rmSync, unlinkSync } from 'node:fs'; import { Socket } from 'socket.io-client'; -import { testAssetDir, testAssetDirInternal, utils } from 'src/utils'; +import { userDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'src/utils'; +import request from 'supertest'; import { utimes } from 'utimes'; import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest'; describe('/libraries', () => { let admin: LoginResponseDto; + let user: LoginResponseDto; + let library: LibraryResponseDto; let websocket: Socket; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); await utils.resetAdminConfig(admin.accessToken); + user = await utils.userSetup(admin.accessToken, userDto.user1); + library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); websocket = await utils.connectWebsocket(admin.accessToken); utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`); + utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`); }); afterAll(() => { @@ -26,7 +34,278 @@ describe('/libraries', () => { utils.resetEvents(); }); + describe('GET /libraries', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/libraries'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + }); + + describe('POST /libraries', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/libraries').send({}); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require admin authentication', async () => { + const { status, body } = await request(app) + .post('/libraries') + .set('Authorization', `Bearer ${user.accessToken}`) + .send({ ownerId: admin.userId }); + + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + it('should create an external library with defaults', async () => { + const { status, body } = await request(app) + .post('/libraries') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ ownerId: admin.userId }); + + expect(status).toBe(201); + expect(body).toEqual( + expect.objectContaining({ + ownerId: admin.userId, + name: 'New External Library', + refreshedAt: null, + assetCount: 0, + importPaths: [], + exclusionPatterns: expect.any(Array), + }), + ); + }); + + it('should create an external library with options', async () => { + const { status, body } = await request(app) + .post('/libraries') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + ownerId: admin.userId, + name: 'My Awesome Library', + importPaths: ['/path/to/import'], + exclusionPatterns: ['**/Raw/**'], + }); + + expect(status).toBe(201); + expect(body).toEqual( + expect.objectContaining({ + name: 'My Awesome Library', + importPaths: ['/path/to/import'], + }), + ); + }); + + it('should not create an external library with duplicate import paths', async () => { + const { status, body } = await request(app) + .post('/libraries') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + ownerId: admin.userId, + name: 'My Awesome Library', + importPaths: ['/path', '/path'], + exclusionPatterns: ['**/Raw/**'], + }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"])); + }); + + it('should not create an external library with duplicate exclusion patterns', async () => { + const { status, body } = await request(app) + .post('/libraries') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + ownerId: admin.userId, + name: 'My Awesome Library', + importPaths: ['/path/to/import'], + exclusionPatterns: ['**/Raw/**', '**/Raw/**'], + }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"])); + }); + }); + + describe('PUT /libraries/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/libraries/${uuidDto.notFound}`).send({}); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should change the library name', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ name: 'New Library Name' }); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + name: 'New Library Name', + }), + ); + }); + + it('should not set an empty name', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ name: '' }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['name should not be empty'])); + }); + + it('should change the import paths', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ importPaths: [testAssetDirInternal] }); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + importPaths: [testAssetDirInternal], + }), + ); + }); + + it('should reject an empty import path', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ importPaths: [''] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['each value in importPaths should not be empty'])); + }); + + it('should reject duplicate import paths', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ importPaths: ['/path', '/path'] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(["All importPaths's elements must be unique"])); + }); + + it('should change the exclusion pattern', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ exclusionPatterns: ['**/Raw/**'] }); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + exclusionPatterns: ['**/Raw/**'], + }), + ); + }); + + it('should reject duplicate exclusion patterns', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"])); + }); + + it('should reject an empty exclusion pattern', async () => { + const { status, body } = await request(app) + .put(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ exclusionPatterns: [''] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['each value in exclusionPatterns should not be empty'])); + }); + }); + + describe('GET /libraries/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/libraries/${uuidDto.notFound}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require admin access', async () => { + const { status, body } = await request(app) + .get(`/libraries/${uuidDto.notFound}`) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + it('should get library by id', async () => { + const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); + + const { status, body } = await request(app) + .get(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual( + expect.objectContaining({ + ownerId: admin.userId, + name: 'New External Library', + refreshedAt: null, + assetCount: 0, + importPaths: [], + exclusionPatterns: expect.any(Array), + }), + ); + }); + }); + + describe('GET /libraries/:id/statistics', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/libraries/${uuidDto.notFound}/statistics`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + }); + describe('POST /libraries/:id/scan', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/scan`).send({}); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should import new asset when scanning external library', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/directoryA`], + }); + + const { status } = await request(app) + .post(`/libraries/${library.id}/scan`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send(); + expect(status).toBe(204); + + await utils.waitForQueueFinish(admin.accessToken, 'library'); + await utils.waitForQueueFinish(admin.accessToken, 'sidecar'); + await utils.waitForQueueFinish(admin.accessToken, 'metadataExtraction'); + + const { assets } = await utils.searchAssets(admin.accessToken, { + originalPath: `${testAssetDirInternal}/temp/directoryA/assetA.png`, + libraryId: library.id, + }); + expect(assets.count).toBe(1); + }); + it('should process metadata and thumbnails for external asset', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, @@ -46,6 +325,337 @@ describe('/libraries', () => { expect(asset.thumbhash).not.toBe(null); }); + it('should scan external library with exclusion pattern', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + exclusionPatterns: ['**/directoryA/**'], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(1); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining('directoryB/assetB.png') }), + ]), + ); + }); + + it('should scan external library with multiple exclusion patterns', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + exclusionPatterns: ['**/directoryA/**', '**/directoryB/**'], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(0); + + expect(assets.items).toEqual([]); + }); + + it('should remove assets covered by a new exclusion pattern', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(2); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining('directoryA/assetA.png') }), + expect.objectContaining({ originalPath: expect.stringContaining('directoryB/assetB.png') }), + ]), + ); + } + + await utils.updateLibrary(admin.accessToken, library.id, { + exclusionPatterns: ['**/directoryA/**'], + }); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(1); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining('directoryB/assetB.png') }), + ]), + ); + } + + await utils.updateLibrary(admin.accessToken, library.id, { + exclusionPatterns: ['**/directoryA/**', '**/directoryB/**'], + }); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(0); + + expect(assets.items).toEqual([]); + } + }); + + it('should scan multiple import paths', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/directoryA`, `${testAssetDirInternal}/temp/directoryB`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(2); + expect(assets.items.find((asset) => asset.originalPath.includes('directoryA'))).toBeDefined(); + expect(assets.items.find((asset) => asset.originalPath.includes('directoryB'))).toBeDefined(); + }); + + it('should scan multiple import paths with commas', async () => { + // https://github.com/immich-app/immich/issues/10699 + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/folder, a`, `${testAssetDirInternal}/temp/folder, b`], + }); + + utils.createImageFile(`${testAssetDir}/temp/folder, a/assetA.png`); + utils.createImageFile(`${testAssetDir}/temp/folder, b/assetB.png`); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(2); + expect(assets.items.find((asset) => asset.originalPath.includes('folder, a'))).toBeDefined(); + expect(assets.items.find((asset) => asset.originalPath.includes('folder, b'))).toBeDefined(); + + utils.removeImageFile(`${testAssetDir}/temp/folder, a/assetA.png`); + utils.removeImageFile(`${testAssetDir}/temp/folder, b/assetB.png`); + }); + + it('should scan multiple import paths with braces', async () => { + // https://github.com/immich-app/immich/issues/10699 + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/folder{ a`, `${testAssetDirInternal}/temp/folder} b`], + }); + + utils.createImageFile(`${testAssetDir}/temp/folder{ a/assetA.png`); + utils.createImageFile(`${testAssetDir}/temp/folder} b/assetB.png`); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(2); + expect(assets.items.find((asset) => asset.originalPath.includes('folder{ a'))).toBeDefined(); + expect(assets.items.find((asset) => asset.originalPath.includes('folder} b'))).toBeDefined(); + + utils.removeImageFile(`${testAssetDir}/temp/folder{ a/assetA.png`); + utils.removeImageFile(`${testAssetDir}/temp/folder} b/assetB.png`); + }); + + const annoyingChars = [ + "'", + '"', + '`', + '*', + '{', + '}', + ',', + '(', + ')', + '[', + ']', + '?', + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '=', + '+', + '~', + '|', + '<', + '>', + ';', + ':', + '/', // We never got backslashes to work + ]; + + it.each(annoyingChars)('should scan multiple import paths with %s', async (char) => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/folder${char}1`, `${testAssetDirInternal}/temp/folder${char}2`], + }); + + utils.createImageFile(`${testAssetDir}/temp/folder${char}1/asset1.png`); + utils.createImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining(`folder${char}1/asset1.png`) }), + expect.objectContaining({ originalPath: expect.stringContaining(`folder${char}2/asset2.png`) }), + ]), + ); + + utils.removeImageFile(`${testAssetDir}/temp/folder${char}1/asset1.png`); + utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`); + }); + + it('should respect exclusion patterns when using multiple import paths', async () => { + // https://github.com/immich-app/immich/issues/17121 + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/exclusion/`, `${testAssetDirInternal}/temp/exclusion2/`], + }); + + const excludedFolder = `Raw`; + + utils.createImageFile(`${testAssetDir}/temp/exclusion/asset1.png`); + utils.createImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }), + ]), + ); + } + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }), + ]), + ); + } + + await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: [`**/${excludedFolder}/**`] }); + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + ]); + } + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + ]); + } + + utils.removeImageFile(`${testAssetDir}/temp/exclusion/asset1.png`); + utils.removeImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`); + }); + + const annoyingExclusionPatterns = ['@', '#', '$', '%', '^', '&', '=']; + + it.each(annoyingExclusionPatterns)('should support exclusion patterns with %s', async (char) => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/exclusion/`], + }); + + const excludedFolder = `${char}folder`; + + utils.createImageFile(`${testAssetDir}/temp/exclusion/asset1.png`); + utils.createImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }), + ]), + ); + } + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + expect.objectContaining({ originalPath: expect.stringContaining(`${excludedFolder}/asset2.png`) }), + ]), + ); + } + + await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: [`**/${excludedFolder}/**`] }); + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + ]); + } + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.items).toEqual([ + expect.objectContaining({ originalPath: expect.stringContaining(`/asset1.png`) }), + ]); + } + + utils.removeImageFile(`${testAssetDir}/temp/exclusion/asset1.png`); + utils.removeImageFile(`${testAssetDir}/temp/exclusion/${excludedFolder}/asset2.png`); + }); + it('should reimport a modified file', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, @@ -82,6 +692,42 @@ describe('/libraries', () => { utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`); }); + it('should not reimport a file with unchanged timestamp', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/reimport`], + }); + + utils.createImageFile(`${testAssetDir}/temp/reimport/asset.jpg`); + await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + cpSync(`${testAssetDir}/albums/nature/tanners_ridge.jpg`, `${testAssetDir}/temp/reimport/asset.jpg`); + await utimes(`${testAssetDir}/temp/reimport/asset.jpg`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { + libraryId: library.id, + }); + + expect(assets.count).toEqual(1); + + const asset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(asset).toEqual( + expect.objectContaining({ + originalFileName: 'asset.jpg', + exifInfo: expect.not.objectContaining({ + model: 'NIKON D750', + }), + }), + ); + + utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`); + }); + it('should not reimport a modified file more than once', async () => { const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, @@ -122,5 +768,681 @@ describe('/libraries', () => { utils.removeImageFile(`${testAssetDir}/temp/reimport/asset.jpg`); }); + + it('should set an asset offline if its file is missing', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assets.count).toBe(1); + + utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(trashedAsset.isOffline).toEqual(true); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(newAssets.items).toEqual([]); + }); + + it('should set an asset offline if its file is not in any import path', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assets.count).toBe(1); + + utils.createDirectory(`${testAssetDir}/temp/another-path/`); + + await utils.updateLibrary(admin.accessToken, library.id, { + importPaths: [`${testAssetDirInternal}/temp/another-path/`], + }); + + await utils.scan(admin.accessToken, library.id); + + const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(trashedAsset.isOffline).toBe(true); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([]); + + utils.removeImageFile(`${testAssetDir}/temp/offline/offline.png`); + utils.removeDirectory(`${testAssetDir}/temp/another-path/`); + }); + + it('should set an asset offline if its file is covered by an exclusion pattern', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { + libraryId: library.id, + originalFileName: 'assetB.png', + }); + expect(assets.count).toBe(1); + + await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/directoryB/**'] }); + + await utils.scan(admin.accessToken, library.id); + + const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(trashedAsset.isTrashed).toBe(true); + expect(trashedAsset.originalPath).toBe(`${testAssetDirInternal}/temp/directoryB/assetB.png`); + expect(trashedAsset.isOffline).toBe(true); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'assetA.png', + }), + ]); + }); + + it('should not set an asset offline if its file exists, is in an import path, and not covered by an exclusion pattern', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets: assetsBefore } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assetsBefore.count).toBeGreaterThan(1); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets).toEqual(assetsBefore); + }); + + describe('xmp metadata', async () => { + it('should import metadata from file.xmp', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2000-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should import metadata from file.ext.xmp', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2000-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should import metadata in file.ext.xmp before file.xmp if both exist', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2000-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file.xmp to file.ext.xmp when asset refreshes', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + unlinkSync(`${testAssetDir}/temp/xmp/glarus.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2010-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file metadata to file.xmp metadata when asset refreshes', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2000-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file metadata to file.ext.xmp metadata when asset refreshes', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2000-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file.ext.xmp to file.xmp when asset refreshes', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2010.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + unlinkSync(`${testAssetDir}/temp/xmp/glarus.nef.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2010-09-27T12:35:33.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file.ext.xmp to file metadata', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.nef.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + unlinkSync(`${testAssetDir}/temp/xmp/glarus.nef.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2010-07-20T17:27:12.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + + it('should switch from using file.xmp to file metadata', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/xmp`], + }); + + cpSync(`${testAssetDir}/metadata/xmp/dates/2000.xmp`, `${testAssetDir}/temp/xmp/glarus.xmp`); + cpSync(`${testAssetDir}/formats/raw/Nikon/D80/glarus.nef`, `${testAssetDir}/temp/xmp/glarus.nef`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_000); + + await utils.scan(admin.accessToken, library.id); + + unlinkSync(`${testAssetDir}/temp/xmp/glarus.xmp`); + await utimes(`${testAssetDir}/temp/xmp/glarus.nef`, 447_775_200_001); + + await utils.scan(admin.accessToken, library.id); + + const { assets: newAssets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(newAssets.items).toEqual([ + expect.objectContaining({ + originalFileName: 'glarus.nef', + fileCreatedAt: '2010-07-20T17:27:12.000Z', + }), + ]); + + rmSync(`${testAssetDir}/temp/xmp`, { recursive: true, force: true }); + }); + }); + + it('should set an offline asset to online if its file exists, is in an import path, and not covered by an exclusion pattern', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(1); + + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(offlineAsset.isTrashed).toBe(true); + expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(offlineAsset.isOffline).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const backOnlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(backOnlineAsset.isTrashed).toBe(false); + expect(backOnlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(backOnlineAsset.isOffline).toBe(false); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assets.count).toBe(1); + } + }); + + it('should set a trashed offline asset to online but keep it in trash', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + expect(assets.count).toBe(1); + + await utils.deleteAssets(admin.accessToken, [assets.items[0].id]); + + { + const trashedAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(trashedAsset.isTrashed).toBe(true); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + expect(offlineAsset.isTrashed).toBe(true); + expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(offlineAsset.isOffline).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const backOnlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(backOnlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(backOnlineAsset.isOffline).toBe(false); + expect(backOnlineAsset.isTrashed).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + }); + + it('should not set an offline asset to online if its file exists, is not covered by an exclusion pattern, but is outside of all import paths', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + + const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(offlineAsset.isTrashed).toBe(true); + expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(offlineAsset.isOffline).toBe(true); + + utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`); + + utils.createDirectory(`${testAssetDir}/temp/another-path/`); + + await utils.updateLibrary(admin.accessToken, library.id, { + importPaths: [`${testAssetDirInternal}/temp/another-path`], + }); + + await utils.scan(admin.accessToken, library.id); + + const stillOfflineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(stillOfflineAsset.isTrashed).toBe(true); + expect(stillOfflineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(stillOfflineAsset.isOffline).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + + utils.removeDirectory(`${testAssetDir}/temp/another-path/`); + }); + + it('should not set an offline asset to online if its file exists, is in an import path, but is covered by an exclusion pattern', async () => { + utils.createImageFile(`${testAssetDir}/temp/offline/offline.png`); + + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp/offline`], + }); + + await utils.scan(admin.accessToken, library.id); + + { + const { assets: assetsBefore } = await utils.searchAssets(admin.accessToken, { libraryId: library.id }); + expect(assetsBefore.count).toBe(1); + } + + utils.renameImageFile(`${testAssetDir}/temp/offline/offline.png`, `${testAssetDir}/temp/offline.png`); + + await utils.scan(admin.accessToken, library.id); + + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + + const offlineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(offlineAsset.isTrashed).toBe(true); + expect(offlineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(offlineAsset.isOffline).toBe(true); + + utils.renameImageFile(`${testAssetDir}/temp/offline.png`, `${testAssetDir}/temp/offline/offline.png`); + + await utils.updateLibrary(admin.accessToken, library.id, { exclusionPatterns: ['**/offline/**'] }); + + await utils.scan(admin.accessToken, library.id); + + const stillOfflineAsset = await utils.getAssetInfo(admin.accessToken, assets.items[0].id); + + expect(stillOfflineAsset.isTrashed).toBe(true); + expect(stillOfflineAsset.originalPath).toBe(`${testAssetDirInternal}/temp/offline/offline.png`); + expect(stillOfflineAsset.isOffline).toBe(true); + + { + const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id, withDeleted: true }); + expect(assets.count).toBe(1); + } + }); + }); + + describe('POST /libraries/:id/validate', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post(`/libraries/${uuidDto.notFound}/validate`).send({}); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should pass with no import paths', async () => { + const response = await utils.validateLibrary(admin.accessToken, library.id, { importPaths: [] }); + expect(response.importPaths).toEqual([]); + }); + + it('should fail if path does not exist', async () => { + const pathToTest = `${testAssetDirInternal}/does/not/exist`; + + const response = await utils.validateLibrary(admin.accessToken, library.id, { + importPaths: [pathToTest], + }); + + expect(response.importPaths?.length).toEqual(1); + const pathResponse = response?.importPaths?.at(0); + + expect(pathResponse).toEqual({ + importPath: pathToTest, + isValid: false, + message: `Path does not exist (ENOENT)`, + }); + }); + + it("should fail if path isn't absolute", async () => { + const pathToTest = `relative/path`; + + const cwd = process.cwd(); + // Create directory in cwd + utils.createDirectory(`${cwd}/${pathToTest}`); + + const response = await utils.validateLibrary(admin.accessToken, library.id, { + importPaths: [pathToTest], + }); + + utils.removeDirectory(`${cwd}/${pathToTest}`); + + expect(response.importPaths?.length).toEqual(1); + const pathResponse = response?.importPaths?.at(0); + + expect(pathResponse).toEqual({ + importPath: pathToTest, + isValid: false, + message: expect.stringMatching('Import path must be absolute, try /usr/src/app/relative/path'), + }); + }); + + it('should fail if path is a file', async () => { + const pathToTest = `${testAssetDirInternal}/albums/nature/el_torcal_rocks.jpg`; + + const response = await utils.validateLibrary(admin.accessToken, library.id, { + importPaths: [pathToTest], + }); + + expect(response.importPaths?.length).toEqual(1); + const pathResponse = response?.importPaths?.at(0); + + expect(pathResponse).toEqual({ + importPath: pathToTest, + isValid: false, + message: `Not a directory`, + }); + }); + }); + + describe('DELETE /libraries/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/libraries/${uuidDto.notFound}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should delete an external library', async () => { + const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId }); + + const { status, body } = await request(app) + .delete(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(204); + expect(body).toEqual({}); + + const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) }); + expect(libraries).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: library.id, + }), + ]), + ); + }); + + it('should delete an external library with assets', async () => { + const library = await utils.createLibrary(admin.accessToken, { + ownerId: admin.userId, + importPaths: [`${testAssetDirInternal}/temp`], + }); + + await utils.scan(admin.accessToken, library.id); + + const { status, body } = await request(app) + .delete(`/libraries/${library.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(204); + expect(body).toEqual({}); + + const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) }); + expect(libraries).not.toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: library.id, + }), + ]), + ); + + // ensure no files get deleted + expect(existsSync(`${testAssetDir}/temp/directoryA/assetA.png`)).toBe(true); + expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true); + }); }); }); diff --git a/e2e/src/specs/server/api/map.e2e-spec.ts b/e2e/src/specs/server/api/map.e2e-spec.ts index c7c504b3c5..977638aa24 100644 --- a/e2e/src/specs/server/api/map.e2e-spec.ts +++ b/e2e/src/specs/server/api/map.e2e-spec.ts @@ -2,58 +2,45 @@ import { AssetVisibility, LoginResponseDto } from '@immich/sdk'; import { readFile } from 'node:fs/promises'; import { basename, join } from 'node:path'; import { Socket } from 'socket.io-client'; -import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, testAssetDir, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; describe('/map', () => { let websocket: Socket; - let partnerWebsocket: Socket; let admin: LoginResponseDto; - let partner: LoginResponseDto; - let partnerArchivedAssetId: string; - let adminArchivedAssetId: string; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup({ onboarding: false }); - partner = await utils.userSetup(admin.accessToken, createUserDto.user1); websocket = await utils.connectWebsocket(admin.accessToken); - partnerWebsocket = await utils.connectWebsocket(partner.accessToken); - const adminFiles = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg']; - const adminArchivedFile = 'metadata/dates/datetimeoriginal-gps.jpg'; - const partnerFile = 'metadata/gps-position/thompson-springs.jpg'; + const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg']; utils.resetEvents(); - const uploadFile = async (accessToken: string, input: string) => { + const uploadFile = async (input: string) => { const filepath = join(testAssetDir, input); - const { id } = await utils.createAsset(accessToken, { + const { id } = await utils.createAsset(admin.accessToken, { assetData: { bytes: await readFile(filepath), filename: basename(filepath) }, }); await utils.waitForWebsocketEvent({ event: 'assetUpload', id }); - return id; }; - await Promise.all(adminFiles.map((f) => uploadFile(admin.accessToken, f))); - [adminArchivedAssetId, partnerArchivedAssetId] = await Promise.all([ - uploadFile(admin.accessToken, adminArchivedFile), - uploadFile(partner.accessToken, partnerFile), - ]); - - await Promise.all([ - utils.archiveAssets(admin.accessToken, [adminArchivedAssetId]), - utils.archiveAssets(partner.accessToken, [partnerArchivedAssetId]), - utils.createPartner(partner.accessToken, admin.userId), - ]); + await Promise.all(files.map((f) => uploadFile(f))); }); afterAll(() => { utils.disconnectWebsocket(websocket); - utils.disconnectWebsocket(partnerWebsocket); }); describe('GET /map/markers', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/map/markers'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + // TODO archive one of these assets it('should get map markers for all non-archived assets', async () => { const { status, body } = await request(app) .get('/map/markers') @@ -68,7 +55,7 @@ describe('/map', () => { country: 'United States of America', id: expect.any(String), lat: expect.closeTo(39.115), - lon: expect.closeTo(-108.400968), + lon: expect.closeTo(-108.400_968), state: 'Colorado', }, { @@ -76,34 +63,13 @@ describe('/map', () => { country: 'United States of America', id: expect.any(String), lat: expect.closeTo(41.2203), - lon: expect.closeTo(-96.071625), + lon: expect.closeTo(-96.071_625), state: 'Nebraska', }, ]); }); - it('should not expose partner archived asset locations', async () => { - const { status, body } = await request(app) - .get('/map/markers') - .query({ withPartners: true, isArchived: true }) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(200); - const ids = body.map((m: { id: string }) => m.id); - expect(ids).not.toContain(partnerArchivedAssetId); - expect(ids).toContain(adminArchivedAssetId); - }); - - it('should include own archived asset locations', async () => { - const { status, body } = await request(app) - .get('/map/markers') - .query({ isArchived: true }) - .set('Authorization', `Bearer ${admin.accessToken}`); - - expect(status).toBe(200); - expect(body.map((m: { id: string }) => m.id)).toContain(adminArchivedAssetId); - }); - + // TODO archive one of these assets it('should get all map markers', async () => { const { status, body } = await request(app) .get('/map/markers') @@ -116,7 +82,7 @@ describe('/map', () => { country: 'United States of America', id: expect.any(String), lat: expect.closeTo(39.115), - lon: expect.closeTo(-108.400968), + lon: expect.closeTo(-108.400_968), state: 'Colorado', }, { @@ -124,7 +90,7 @@ describe('/map', () => { country: 'United States of America', id: expect.any(String), lat: expect.closeTo(41.2203), - lon: expect.closeTo(-96.071625), + lon: expect.closeTo(-96.071_625), state: 'Nebraska', }, ]); @@ -132,23 +98,61 @@ describe('/map', () => { }); describe('GET /map/reverse-geocode', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/map/reverse-geocode'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should throw an error if a lat is not provided', async () => { + const { status, body } = await request(app) + .get('/map/reverse-geocode?lon=123') + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); + }); + + it('should throw an error if a lat is not a number', async () => { + const { status, body } = await request(app) + .get('/map/reverse-geocode?lat=abc&lon=123.456') + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); + }); + + it('should throw an error if a lat is out of range', async () => { + const { status, body } = await request(app) + .get('/map/reverse-geocode?lat=91&lon=123.456') + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['lat must be a number between -90 and 90'])); + }); + + it('should throw an error if a lon is not provided', async () => { + const { status, body } = await request(app) + .get('/map/reverse-geocode?lat=75') + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['lon must be a number between -180 and 180'])); + }); + const reverseGeocodeTestCases = [ { name: 'Vaucluse', - lat: -33.85897705866313, - lon: 151.27849073027048, + lat: -33.858_977_058_663_13, + lon: 151.278_490_730_270_48, results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }], }, { name: 'Ravenhall', - lat: -37.76573239917475, - lon: 144.7524531648833, + lat: -37.765_732_399_174_75, + lon: 144.752_453_164_883_3, results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }], }, { name: 'Scarborough', - lat: -31.894346156789997, - lon: 115.75761710390464, + lat: -31.894_346_156_789_997, + lon: 115.757_617_103_904_64, results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }], }, ]; diff --git a/e2e/src/specs/server/api/memory.e2e-spec.ts b/e2e/src/specs/server/api/memory.e2e-spec.ts new file mode 100644 index 0000000000..e5e2351738 --- /dev/null +++ b/e2e/src/specs/server/api/memory.e2e-spec.ts @@ -0,0 +1,175 @@ +import { + AssetMediaResponseDto, + LoginResponseDto, + MemoryResponseDto, + MemoryType, + createMemory, + getMemory, +} from '@immich/sdk'; +import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, describe, expect, it } from 'vitest'; + +describe('/memories', () => { + let admin: LoginResponseDto; + let user: LoginResponseDto; + let adminAsset: AssetMediaResponseDto; + let userAsset1: AssetMediaResponseDto; + let userMemory: MemoryResponseDto; + + beforeAll(async () => { + await utils.resetDatabase(); + + admin = await utils.adminSetup(); + user = await utils.userSetup(admin.accessToken, createUserDto.user1); + [adminAsset, userAsset1] = await Promise.all([ + utils.createAsset(admin.accessToken), + utils.createAsset(user.accessToken), + ]); + userMemory = await createMemory( + { + memoryCreateDto: { + type: MemoryType.OnThisDay, + memoryAt: new Date(2021).toISOString(), + data: { year: 2021 }, + assetIds: [], + }, + }, + { headers: asBearerAuth(user.accessToken) }, + ); + }); + + describe('GET /memories/:id', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .get(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should get the memory', async () => { + const { status, body } = await request(app) + .get(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ id: userMemory.id }); + }); + }); + + describe('PUT /memories/:id', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should update the memory', async () => { + const before = await getMemory({ id: userMemory.id }, { headers: asBearerAuth(user.accessToken) }); + expect(before.isSaved).toBe(false); + + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ + id: userMemory.id, + isSaved: true, + }); + }); + }); + + describe('PUT /memories/:id/assets', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require asset access', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [adminAsset.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ + id: adminAsset.id, + success: false, + error: 'no_permission', + }); + }); + + it('should add assets to the memory', async () => { + const { status, body } = await request(app) + .put(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ id: userAsset1.id, success: true }); + }); + }); + + describe('DELETE /memories/:id/assets', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should only remove assets in the memory', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [adminAsset.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ + id: adminAsset.id, + success: false, + error: 'not_found', + }); + }); + + it('should remove assets from the memory', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}/assets`) + .send({ ids: [userAsset1.id] }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(200); + expect(body).toHaveLength(1); + expect(body[0]).toEqual({ id: userAsset1.id, success: true }); + }); + }); + + describe('DELETE /memories/:id', () => { + it('should require access', async () => { + const { status, body } = await request(app) + .delete(`/memories/${userMemory.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should delete the memory', async () => { + const { status } = await request(app) + .delete(`/memories/${userMemory.id}`) + .send({ isSaved: true }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(204); + }); + }); +}); diff --git a/e2e/src/specs/server/api/oauth.e2e-spec.ts b/e2e/src/specs/server/api/oauth.e2e-spec.ts index 5bb569d5d2..ae9064375f 100644 --- a/e2e/src/specs/server/api/oauth.e2e-spec.ts +++ b/e2e/src/specs/server/api/oauth.e2e-spec.ts @@ -1,10 +1,9 @@ -import { OAuthClient, OAuthUser, generateLogoutToken } from '@immich/e2e-auth-server'; +import { OAuthClient, OAuthUser } from '@immich/e2e-auth-server'; import { - AdminConfigOAuthDto, LoginResponseDto, + SystemConfigOAuthDto, getConfigDefaults, getMyUser, - getSessions, startOAuth, updateConfig, } from '@immich/sdk'; @@ -44,7 +43,7 @@ const loginWithOAuth = async (sub: OAuthUser | string, redirectUri?: string) => }); // login - const response1 = await redirect(url.replace(authServer.internal, () => authServer.external)); + const response1 = await redirect(url.replace(authServer.internal, authServer.external)); const response2 = await request(authServer.external + response1.location) .post('') .set('Cookie', response1.cookies) @@ -70,18 +69,16 @@ const loginWithOAuth = async (sub: OAuthUser | string, redirectUri?: string) => return { url: redirectUrl, state, codeVerifier }; }; -const setupOAuth = async (token: string, dto: Partial) => { +const setupOAuth = async (token: string, dto: Partial) => { const options = { headers: asBearerAuth(token) }; const defaults = await getConfigDefaults(options); const merged = { ...defaults.oauth, buttonText: 'Login with Immich', issuerUrl: `${authServer.internal}/.well-known/openid-configuration`, - accountManagementUrl: authServer.internal, - allowInsecureRequests: true, ...dto, }; - await updateConfig({ adminConfigDto: { ...defaults, oauth: merged } }, options); + await updateConfig({ systemConfigDto: { ...defaults, oauth: merged } }, options); }; describe(`/oauth`, () => { @@ -90,17 +87,21 @@ describe(`/oauth`, () => { beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); + + await setupOAuth(admin.accessToken, { + enabled: true, + clientId: OAuthClient.DEFAULT, + clientSecret: OAuthClient.DEFAULT, + buttonText: 'Login with Immich', + storageLabelClaim: 'immich_username', + }); }); describe('POST /oauth/authorize', () => { - beforeAll(async () => { - await setupOAuth(admin.accessToken, { - enabled: true, - clientId: OAuthClient.DEFAULT, - clientSecret: OAuthClient.DEFAULT, - buttonText: 'Login with Immich', - storageLabelClaim: 'immich_username', - }); + it(`should throw an error if a redirect uri is not provided`, async () => { + const { status, body } = await request(app).post('/oauth/authorize').send({}); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['redirectUri must be a string', 'redirectUri should not be empty'])); }); it('should return a redirect uri', async () => { @@ -116,44 +117,19 @@ describe(`/oauth`, () => { expect(params.get('redirect_uri')).toBe('http://127.0.0.1:2285/auth/login'); expect(params.get('state')).toBeDefined(); }); - - it('should not include the prompt parameter when not configured', async () => { - const { status, body } = await request(app) - .post('/oauth/authorize') - .send({ redirectUri: 'http://127.0.0.1:2285/auth/login' }); - expect(status).toBe(201); - - const params = new URL(body.url).searchParams; - expect(params.get('prompt')).toBeNull(); - }); - - it('should include the prompt parameter when configured', async () => { - await setupOAuth(admin.accessToken, { - enabled: true, - clientId: OAuthClient.DEFAULT, - clientSecret: OAuthClient.DEFAULT, - prompt: 'select_account', - }); - - const { status, body } = await request(app) - .post('/oauth/authorize') - .send({ redirectUri: 'http://127.0.0.1:2285/auth/login' }); - expect(status).toBe(201); - - const params = new URL(body.url).searchParams; - expect(params.get('prompt')).toBe('select_account'); - }); }); describe('POST /oauth/callback', () => { - beforeAll(async () => { - await setupOAuth(admin.accessToken, { - enabled: true, - clientId: OAuthClient.DEFAULT, - clientSecret: OAuthClient.DEFAULT, - buttonText: 'Login with Immich', - storageLabelClaim: 'immich_username', - }); + it(`should throw an error if a url is not provided`, async () => { + const { status, body } = await request(app).post('/oauth/callback').send({}); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['url must be a string', 'url should not be empty'])); + }); + + it(`should throw an error if the url is empty`, async () => { + const { status, body } = await request(app).post('/oauth/callback').send({ url: '' }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['url should not be empty'])); }); it(`should throw an error if the state is not provided`, async () => { @@ -182,9 +158,10 @@ describe(`/oauth`, () => { it(`should throw an error if the codeVerifier doesn't match the challenge`, async () => { const callbackParams = await loginWithOAuth('oauth-auto-register'); const { codeVerifier } = await loginWithOAuth('oauth-auto-register'); - const { status } = await request(app) + const { status, body } = await request(app) .post('/oauth/callback') .send({ ...callbackParams, codeVerifier }); + console.log(body); expect(status).toBeGreaterThanOrEqual(400); }); @@ -281,7 +258,7 @@ describe(`/oauth`, () => { accessToken: expect.any(String), isAdmin: false, name: 'OAuth User', - userEmail: 'oauth-rs256-token@immich.app', + userEmail: 'oauth-RS256-token@immich.app', userId: expect.any(String), }); }); @@ -315,7 +292,9 @@ describe(`/oauth`, () => { const { status, body } = await request(app).post('/oauth/callback').send(callbackParams); expect(status).toBe(500); expect(body).toMatchObject({ + error: 'Internal Server Error', message: 'Failed to finish oauth', + statusCode: 500, }); }); @@ -334,7 +313,7 @@ describe(`/oauth`, () => { const callbackParams = await loginWithOAuth('oauth-no-auto-register'); const { status, body } = await request(app).post('/oauth/callback').send(callbackParams); expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('OAuth authentication failed')); + expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.')); }); it('should link to an existing user by email', async () => { @@ -354,44 +333,6 @@ describe(`/oauth`, () => { }); }); - describe(`POST /oauth/backchannel-logout`, () => { - it(`should throw an error if an invalid logout token is provided`, async () => { - const { status, body } = await request(app) - .post('/oauth/backchannel-logout') - .send({ logout_token: 'invalid token' }); - expect(status).toBe(400); - expect(body).toEqual(errorDto.badRequest('Error backchannel logout: token validation failed')); - }); - - it(`should logout user if a valid logout token is provided`, async () => { - await setupOAuth(admin.accessToken, { - enabled: true, - clientId: OAuthClient.DEFAULT, - clientSecret: OAuthClient.DEFAULT, - autoRegister: true, - signingAlgorithm: 'RS256', - buttonText: 'Login with Immich', - }); - - const callbackParams = await loginWithOAuth('backchannel-logout-user'); - const { status: callbackStatus, body: callbackBody } = await request(app) - .post('/oauth/callback') - .send(callbackParams); - expect(callbackStatus).toBe(201); - - await expect(getSessions({ headers: asBearerAuth(callbackBody.accessToken) })).resolves.toHaveLength(1); - - const logoutToken = await generateLogoutToken('http://0.0.0.0:2286', 'backchannel-logout-user'); - const { status, body } = await request(app).post('/oauth/backchannel-logout').send({ logout_token: logoutToken }); - expect(status).toBe(200); - expect(body).toMatchObject({}); - - await expect(getSessions({ headers: asBearerAuth(callbackBody.accessToken) })).rejects.toMatchObject({ - status: 401, - }); - }); - }); - describe('mobile redirect override', () => { beforeAll(async () => { await setupOAuth(admin.accessToken, { @@ -458,22 +399,4 @@ describe(`/oauth`, () => { }); }); }); - - describe('allowInsecureRequests: false', () => { - beforeAll(async () => { - await setupOAuth(admin.accessToken, { - enabled: true, - clientId: OAuthClient.DEFAULT, - clientSecret: OAuthClient.DEFAULT, - allowInsecureRequests: false, - }); - }); - - it('should reject OAuth discovery over HTTP', async () => { - const { status } = await request(app) - .post('/oauth/authorize') - .send({ redirectUri: 'http://127.0.0.1:2285/auth/login' }); - expect(status).toBe(500); - }); - }); }); diff --git a/e2e/src/specs/server/api/partner.e2e-spec.ts b/e2e/src/specs/server/api/partner.e2e-spec.ts new file mode 100644 index 0000000000..9047a97055 --- /dev/null +++ b/e2e/src/specs/server/api/partner.e2e-spec.ts @@ -0,0 +1,131 @@ +import { LoginResponseDto, createPartner } from '@immich/sdk'; +import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, asBearerAuth, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, describe, expect, it } from 'vitest'; + +describe('/partners', () => { + let admin: LoginResponseDto; + let user1: LoginResponseDto; + let user2: LoginResponseDto; + let user3: LoginResponseDto; + + beforeAll(async () => { + await utils.resetDatabase(); + + admin = await utils.adminSetup(); + + [user1, user2, user3] = await Promise.all([ + utils.userSetup(admin.accessToken, createUserDto.user1), + utils.userSetup(admin.accessToken, createUserDto.user2), + utils.userSetup(admin.accessToken, createUserDto.user3), + ]); + + await Promise.all([ + createPartner({ partnerCreateDto: { sharedWithId: user2.userId } }, { headers: asBearerAuth(user1.accessToken) }), + createPartner({ partnerCreateDto: { sharedWithId: user1.userId } }, { headers: asBearerAuth(user2.accessToken) }), + ]); + }); + + describe('GET /partners', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/partners'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should get all partners shared by user', async () => { + const { status, body } = await request(app) + .get('/partners') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ direction: 'shared-by' }); + + expect(status).toBe(200); + expect(body).toEqual([expect.objectContaining({ id: user2.userId })]); + }); + + it('should get all partners that share with user', async () => { + const { status, body } = await request(app) + .get('/partners') + .set('Authorization', `Bearer ${user1.accessToken}`) + .query({ direction: 'shared-with' }); + + expect(status).toBe(200); + expect(body).toEqual([expect.objectContaining({ id: user2.userId })]); + }); + }); + + describe('POST /partners/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post(`/partners/${user3.userId}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should share with new partner', async () => { + const { status, body } = await request(app) + .post(`/partners/${user3.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(201); + expect(body).toEqual(expect.objectContaining({ id: user3.userId })); + }); + + it('should not share with new partner if already sharing with this partner', async () => { + const { status, body } = await request(app) + .post(`/partners/${user2.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(expect.objectContaining({ message: 'Partner already exists' })); + }); + }); + + describe('PUT /partners/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/partners/${user2.userId}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should update partner', async () => { + const { status, body } = await request(app) + .put(`/partners/${user2.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ inTimeline: false }); + + expect(status).toBe(200); + expect(body).toEqual(expect.objectContaining({ id: user2.userId, inTimeline: false })); + }); + }); + + describe('DELETE /partners/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/partners/${user3.userId}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should delete partner', async () => { + const { status } = await request(app) + .delete(`/partners/${user3.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(204); + }); + + it('should throw a bad request if partner not found', async () => { + const { status, body } = await request(app) + .delete(`/partners/${user3.userId}`) + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(expect.objectContaining({ message: 'Partner not found' })); + }); + }); +}); diff --git a/e2e/src/specs/server/api/person.e2e-spec.ts b/e2e/src/specs/server/api/person.e2e-spec.ts index 7f5518862f..20290cd941 100644 --- a/e2e/src/specs/server/api/person.e2e-spec.ts +++ b/e2e/src/specs/server/api/person.e2e-spec.ts @@ -82,32 +82,32 @@ describe('/people', () => { const asset4 = await utils.createAsset(admin.accessToken); await Promise.all([ - utils.createFace({ assetId: asset1.id, personGroupId: visiblePerson.id }), - utils.createFace({ assetId: asset1.id, personGroupId: hiddenPerson.id }), - utils.createFace({ assetId: asset1.id, personGroupId: multipleAssetsPerson.id }), - utils.createFace({ assetId: asset1.id, personGroupId: multipleAssetsPerson.id }), - utils.createFace({ assetId: asset2.id, personGroupId: multipleAssetsPerson.id }), - utils.createFace({ assetId: asset3.id, personGroupId: multipleAssetsPerson.id }), // 4 assets + utils.createFace({ assetId: asset1.id, personId: visiblePerson.id }), + utils.createFace({ assetId: asset1.id, personId: hiddenPerson.id }), + utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), + utils.createFace({ assetId: asset1.id, personId: multipleAssetsPerson.id }), + utils.createFace({ assetId: asset2.id, personId: multipleAssetsPerson.id }), + utils.createFace({ assetId: asset3.id, personId: multipleAssetsPerson.id }), // 4 assets // Named persons - utils.createFace({ assetId: asset1.id, personGroupId: nameCharliePerson.id }), // 1 asset - utils.createFace({ assetId: asset1.id, personGroupId: nameBobPerson.id }), - utils.createFace({ assetId: asset2.id, personGroupId: nameBobPerson.id }), // 2 assets - utils.createFace({ assetId: asset1.id, personGroupId: nameAlicePerson.id }), // 1 asset + utils.createFace({ assetId: asset1.id, personId: nameCharliePerson.id }), // 1 asset + utils.createFace({ assetId: asset1.id, personId: nameBobPerson.id }), + utils.createFace({ assetId: asset2.id, personId: nameBobPerson.id }), // 2 assets + utils.createFace({ assetId: asset1.id, personId: nameAlicePerson.id }), // 1 asset // Null-named person 4 assets - utils.createFace({ assetId: asset1.id, personGroupId: nameNullPerson4Assets.id }), - utils.createFace({ assetId: asset2.id, personGroupId: nameNullPerson4Assets.id }), - utils.createFace({ assetId: asset3.id, personGroupId: nameNullPerson4Assets.id }), - utils.createFace({ assetId: asset4.id, personGroupId: nameNullPerson4Assets.id }), // 4 assets + utils.createFace({ assetId: asset1.id, personId: nameNullPerson4Assets.id }), + utils.createFace({ assetId: asset2.id, personId: nameNullPerson4Assets.id }), + utils.createFace({ assetId: asset3.id, personId: nameNullPerson4Assets.id }), + utils.createFace({ assetId: asset4.id, personId: nameNullPerson4Assets.id }), // 4 assets // Null-named person 3 assets - utils.createFace({ assetId: asset1.id, personGroupId: nameNullPerson3Assets.id }), - utils.createFace({ assetId: asset2.id, personGroupId: nameNullPerson3Assets.id }), - utils.createFace({ assetId: asset3.id, personGroupId: nameNullPerson3Assets.id }), // 3 assets + utils.createFace({ assetId: asset1.id, personId: nameNullPerson3Assets.id }), + utils.createFace({ assetId: asset2.id, personId: nameNullPerson3Assets.id }), + utils.createFace({ assetId: asset3.id, personId: nameNullPerson3Assets.id }), // 3 assets // Null-named person 1 asset - utils.createFace({ assetId: asset3.id, personGroupId: nameNullPerson1Asset.id }), + utils.createFace({ assetId: asset3.id, personId: nameNullPerson1Asset.id }), // Favourite People - utils.createFace({ assetId: asset1.id, personGroupId: nameFreddyPersonFavourite.id }), - utils.createFace({ assetId: asset2.id, personGroupId: nameFreddyPersonFavourite.id }), - utils.createFace({ assetId: asset1.id, personGroupId: nameBillPersonFavourite.id }), + utils.createFace({ assetId: asset1.id, personId: nameFreddyPersonFavourite.id }), + utils.createFace({ assetId: asset2.id, personId: nameFreddyPersonFavourite.id }), + utils.createFace({ assetId: asset1.id, personId: nameBillPersonFavourite.id }), ]); }); diff --git a/e2e/src/specs/server/api/search.e2e-spec.ts b/e2e/src/specs/server/api/search.e2e-spec.ts index 47c88c238c..2f6ea75f77 100644 --- a/e2e/src/specs/server/api/search.e2e-spec.ts +++ b/e2e/src/specs/server/api/search.e2e-spec.ts @@ -4,7 +4,6 @@ import { AssetVisibility, deleteAssets, LoginResponseDto, - SharedLinkType, updateAsset, } from '@immich/sdk'; import { DateTime } from 'luxon'; @@ -75,6 +74,7 @@ describe('/search', () => { const bytes = await readFile(join(testAssetDir, filename)); assets.push( await utils.createAsset(admin.accessToken, { + deviceAssetId: `test-${filename}`, assetData: { bytes, filename }, ...dto, }), @@ -87,23 +87,23 @@ describe('/search', () => { // note: the coordinates here are not the actual coordinates of the images and are random for most of them const coordinates = [ - { latitude: 48.85341, longitude: 2.3488 }, // paris - { latitude: 35.6895, longitude: 139.69171 }, // tokyo - { latitude: 52.52437, longitude: 13.41053 }, // berlin - { latitude: 1.3146631, longitude: 103.8454093 }, // singapore - { latitude: 41.01384, longitude: 28.94966 }, // istanbul - { latitude: 5.55602, longitude: -0.1969 }, // accra - { latitude: 37.5442706, longitude: -4.7277528 }, // andalusia - { latitude: 23.13302, longitude: -82.38304 }, // havana - { latitude: 41.69411, longitude: 44.83368 }, // tbilisi - { latitude: 31.22222, longitude: 121.45806 }, // shanghai + { latitude: 48.853_41, longitude: 2.3488 }, // paris + { latitude: 35.6895, longitude: 139.691_71 }, // tokyo + { latitude: 52.524_37, longitude: 13.410_53 }, // berlin + { latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore + { latitude: 41.013_84, longitude: 28.949_66 }, // istanbul + { latitude: 5.556_02, longitude: -0.1969 }, // accra + { latitude: 37.544_270_6, longitude: -4.727_752_8 }, // andalusia + { latitude: 23.133_02, longitude: -82.383_04 }, // havana + { latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi + { latitude: 31.222_22, longitude: 121.458_06 }, // shanghai { latitude: 38.9711, longitude: -109.7137 }, // thompson springs - { latitude: 40.71427, longitude: -74.00597 }, // new york - { latitude: 47.04057, longitude: 9.06804 }, // glarus - { latitude: 32.77152, longitude: -89.11673 }, // philadelphia - { latitude: 31.63416, longitude: -7.99994 }, // marrakesh - { latitude: 38.5237354, longitude: -78.4886194 }, // tanners ridge - { latitude: 59.93863, longitude: 30.31413 }, // st. petersburg + { latitude: 40.714_27, longitude: -74.005_97 }, // new york + { latitude: 47.040_57, longitude: 9.068_04 }, // glarus + { latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia + { latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh + { latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge + { latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg { latitude: 0, longitude: 0 }, // null island ]; @@ -112,7 +112,7 @@ describe('/search', () => { ); await Promise.all(updates); - for (const i of coordinates.keys()) { + for (const [i] of coordinates.entries()) { await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: assets[i].id }); } @@ -260,6 +260,17 @@ describe('/search', () => { assets: [assetHeic], }), }, + { + should: "should search city ('')", + deferred: () => ({ + dto: { + city: '', + visibility: AssetVisibility.Timeline, + includeNull: true, + }, + assets: [assetLast], + }), + }, { should: 'should search city (null)', deferred: () => ({ @@ -281,6 +292,18 @@ describe('/search', () => { assets: [assetDensity], }), }, + { + should: "should search state ('')", + deferred: () => ({ + dto: { + state: '', + visibility: AssetVisibility.Timeline, + withExif: true, + includeNull: true, + }, + assets: [assetLast, assetNotocactus], + }), + }, { should: 'should search state (null)', deferred: () => ({ @@ -302,6 +325,17 @@ describe('/search', () => { assets: [assetFalcon], }), }, + { + should: "should search country ('')", + deferred: () => ({ + dto: { + country: '', + visibility: AssetVisibility.Timeline, + includeNull: true, + }, + assets: [assetLast], + }), + }, { should: 'should search country (null)', deferred: () => ({ @@ -358,32 +392,6 @@ describe('/search', () => { expect(body.assets.items).toHaveLength(assets.length); }); } - - it('should reject shared link access without an album filter', async () => { - const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo' }); - const sharedLink = await utils.createSharedLink(admin.accessToken, { - type: SharedLinkType.Album, - albumId: album.id, - }); - const { status, body } = await request(app).post(`/search/metadata?key=${sharedLink.key}`).send({}); - expect(status).toBe(400); - expect(body).toEqual({ message: 'Shared link access is only allowed in combination with an albumIds filter' }); - }); - - it('should allow shared link access for albums', async () => { - const asset = await utils.createAsset(admin.accessToken); - const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo', assetIds: [asset.id] }); - const sharedLink = await utils.createSharedLink(admin.accessToken, { - type: SharedLinkType.Album, - albumId: album.id, - }); - - const { status, body } = await request(app) - .post(`/search/metadata?key=${sharedLink.key}`) - .send({ albumIds: [album.id] }); - expect(status).toBe(200); - expect(body.assets.items).toEqual([expect.objectContaining({ id: asset.id })]); - }); }); describe('POST /search/random', () => { @@ -434,18 +442,7 @@ describe('/search', () => { .get('/search/explore') .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(Array.isArray(body)).toBe(true); - expect(body).toEqual(expect.arrayContaining([{ fieldName: 'exifInfo.city', items: [] }])); - expect(body).toEqual( - expect.arrayContaining([ - { - fieldName: 'createdAt', - items: expect.arrayContaining([ - expect.objectContaining({ data: expect.objectContaining({ id: assetLast.id }) }), - ]), - }, - ]), - ); + expect(body).toEqual([{ fieldName: 'exifInfo.city', items: [] }]); }); }); @@ -461,7 +458,7 @@ describe('/search', () => { expect(Array.isArray(body)).toBe(true); if (Array.isArray(body)) { expect(body.length).toBeGreaterThan(10); - expect(body[0].name).toEqual(expect.stringContaining(name)); + expect(body[0].name).toEqual(name); expect(body[0].admin2name).toEqual(name); } }); diff --git a/e2e/src/specs/server/api/server.e2e-spec.ts b/e2e/src/specs/server/api/server.e2e-spec.ts index ea410186c9..3dd6f15e71 100644 --- a/e2e/src/specs/server/api/server.e2e-spec.ts +++ b/e2e/src/specs/server/api/server.e2e-spec.ts @@ -1,5 +1,6 @@ import { LoginResponseDto } from '@immich/sdk'; import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -21,6 +22,12 @@ describe('/server', () => { }); describe('GET /server/about', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/server/about'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should return about information', async () => { const { status, body } = await request(app) .get('/server/about') @@ -49,6 +56,12 @@ describe('/server', () => { }); describe('GET /server/storage', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/server/storage'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should return the disk information', async () => { const { status, body } = await request(app) .get('/server/storage') @@ -78,14 +91,11 @@ describe('/server', () => { it('should respond with the server version', async () => { const { status, body } = await request(app).get('/server/version'); expect(status).toBe(200); - expect(body).toEqual( - expect.objectContaining({ - major: expect.any(Number), - minor: expect.any(Number), - patch: expect.any(Number), - }), - ); - expect(Object.keys(body)).toEqual(expect.arrayContaining(['major', 'minor', 'patch', 'prerelease'])); + expect(body).toEqual({ + major: expect.any(Number), + minor: expect.any(Number), + patch: expect.any(Number), + }); }); }); @@ -105,7 +115,6 @@ describe('/server', () => { oauthAutoLaunch: false, ocr: false, passwordLogin: true, - realtimeTranscoding: false, search: true, sidecar: true, trash: true, @@ -121,7 +130,6 @@ describe('/server', () => { expect(body).toEqual({ loginPageMessage: '', oauthButtonText: 'Login with OAuth', - oauthAccountManagementUrl: '', trashDays: 30, userDeleteDelay: 7, isInitialized: true, @@ -131,12 +139,25 @@ describe('/server', () => { maintenanceMode: false, mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json', mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json', - minFaces: 3, }); }); }); describe('GET /server/statistics', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/server/statistics'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .get('/server/statistics') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should return the server stats', async () => { const { status, body } = await request(app) .get('/server/statistics') @@ -186,7 +207,31 @@ describe('/server', () => { }); }); + describe('GET /server/theme', () => { + it('should respond with the server theme', async () => { + const { status, body } = await request(app).get('/server/theme'); + expect(status).toBe(200); + expect(body).toEqual({ + customCss: '', + }); + }); + }); + describe('GET /server/license', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/server/license'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .get('/server/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should return the server license', async () => { await request(app).put('/server/license').set('Authorization', `Bearer ${admin.accessToken}`).send(serverLicense); const { status, body } = await request(app) @@ -201,6 +246,20 @@ describe('/server', () => { }); describe('DELETE /server/license', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete('/server/license'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .delete('/server/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should delete the server license', async () => { await request(app) .delete('/server/license') @@ -212,6 +271,20 @@ describe('/server', () => { }); describe('PUT /server/license', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put('/server/license'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .put('/server/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should set the server license', async () => { const { status, body } = await request(app) .put('/server/license') diff --git a/e2e/src/specs/server/api/session.e2e-spec.ts b/e2e/src/specs/server/api/session.e2e-spec.ts index 6f1599c65c..0b632f78ba 100644 --- a/e2e/src/specs/server/api/session.e2e-spec.ts +++ b/e2e/src/specs/server/api/session.e2e-spec.ts @@ -1,6 +1,6 @@ import { LoginResponseDto, getSessions, login, signUpAdmin } from '@immich/sdk'; import { loginDto, signupDto, uuidDto } from 'src/fixtures'; -import { errorDto } from 'src/responses'; +import { deviceDto, errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; import { beforeEach, describe, expect, it } from 'vitest'; @@ -15,25 +15,26 @@ describe('/sessions', () => { }); describe('GET /sessions', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/sessions'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should get a list of authorized devices', async () => { const { status, body } = await request(app).get('/sessions').set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); - expect(body).toEqual([ - { - id: expect.any(String), - createdAt: expect.any(String), - updatedAt: expect.any(String), - current: true, - isPendingSyncReset: false, - deviceOS: '', - deviceType: '', - appVersion: null, - }, - ]); + expect(body).toEqual([deviceDto.current]); }); }); describe('DELETE /sessions', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/sessions`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should logout all devices (except the current one)', async () => { for (let i = 0; i < 5; i++) { await login({ loginCredentialDto: loginDto.admin }); @@ -67,7 +68,7 @@ describe('/sessions', () => { const response = await request(app) .post('/auth/validateToken') .set('Authorization', `Bearer ${admin.accessToken}`); - expect(response.body).toEqual({ message: 'Invalid user token' }); + expect(response.body).toEqual(errorDto.invalidToken); expect(response.status).toBe(401); }); }); diff --git a/e2e/src/specs/server/api/shared-link.e2e-spec.ts b/e2e/src/specs/server/api/shared-link.e2e-spec.ts index 4e5988a167..00c455d6cb 100644 --- a/e2e/src/specs/server/api/shared-link.e2e-spec.ts +++ b/e2e/src/specs/server/api/shared-link.e2e-spec.ts @@ -137,6 +137,13 @@ describe('/shared-links', () => { }); describe('GET /shared-links', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/shared-links'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should get all shared links created by user', async () => { const { status, body } = await request(app) .get('/shared-links') @@ -193,6 +200,12 @@ describe('/shared-links', () => { }); describe('GET /shared-links/me', () => { + it('should not require admin authentication', async () => { + const { status } = await request(app).get('/shared-links/me').set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(403); + }); + it('should get data for correct shared link', async () => { const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithAlbum.key }); @@ -212,39 +225,27 @@ describe('/shared-links', () => { .query({ key: linkWithAlbum.key + 'foo' }); expect(status).toBe(401); - expect(body).toEqual({ message: 'Invalid share key' }); + expect(body).toEqual(errorDto.invalidShareKey); }); it('should return unauthorized if target has been soft deleted', async () => { const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithDeletedAlbum.key }); expect(status).toBe(401); - expect(body).toEqual({ message: 'Invalid share key' }); + expect(body).toEqual(errorDto.invalidShareKey); }); it('should return unauthorized for password protected link', async () => { const { status, body } = await request(app).get('/shared-links/me').query({ key: linkWithPassword.key }); expect(status).toBe(401); - expect(body).toEqual({ message: 'Password required' }); + expect(body).toEqual(errorDto.passwordRequired); }); it('should get data for correct password protected link', async () => { - const response = await request(app) - .post('/shared-links/login') - .send({ password: 'foo' }) - .query({ key: linkWithPassword.key }); - - expect(response.status).toBe(201); - - const cookies = response.get('Set-Cookie') ?? []; - expect(cookies).toHaveLength(1); - expect(cookies[0]).toContain('immich_shared_link_token'); - const { status, body } = await request(app) .get('/shared-links/me') - .query({ key: linkWithPassword.key }) - .set('Cookie', cookies); + .query({ key: linkWithPassword.key, password: 'foo' }); expect(status).toBe(200); expect(body).toEqual( @@ -280,6 +281,13 @@ describe('/shared-links', () => { }); describe('GET /shared-links/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/shared-links/${linkWithAlbum.id}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should get shared link by id', async () => { const { status, body } = await request(app) .get(`/shared-links/${linkWithAlbum.id}`) @@ -306,14 +314,42 @@ describe('/shared-links', () => { }); describe('POST /shared-links', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .post('/shared-links') + .send({ type: SharedLinkType.Album, albumId: uuidDto.notFound }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require a type and the correspondent asset/album id', async () => { + const { status, body } = await request(app) + .post('/shared-links') + .set('Authorization', `Bearer ${user1.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest()); + }); + + it('should require an asset/album id', async () => { + const { status, body } = await request(app) + .post('/shared-links') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ type: SharedLinkType.Album }); + + expect(status).toBe(400); + expect(body).toEqual(expect.objectContaining({ message: 'Invalid albumId' })); + }); + it('should require a valid asset id', async () => { const { status, body } = await request(app) .post('/shared-links') .set('Authorization', `Bearer ${user1.accessToken}`) - .send({ type: SharedLinkType.Individual, assetIds: [uuidDto.notFound] }); + .send({ type: SharedLinkType.Individual, assetId: uuidDto.notFound }); expect(status).toBe(400); - expect(body).toEqual(expect.objectContaining({ message: 'Not found or no asset.share access' })); + expect(body).toEqual(expect.objectContaining({ message: 'Invalid assetIds' })); }); it('should create a shared link', async () => { @@ -333,6 +369,15 @@ describe('/shared-links', () => { }); describe('PATCH /shared-links/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .patch(`/shared-links/${linkWithAlbum.id}`) + .send({ description: 'foo' }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should fail if invalid link', async () => { const { status, body } = await request(app) .patch(`/shared-links/${uuidDto.notFound}`) @@ -393,6 +438,16 @@ describe('/shared-links', () => { expect(body).toEqual(errorDto.badRequest('Invalid shared link type')); }); + it('should reject guests removing assets from an individual shared link', async () => { + const { status, body } = await request(app) + .delete(`/shared-links/${linkWithAssets.id}/assets`) + .query({ key: linkWithAssets.key }) + .send({ assetIds: [asset1.id] }); + + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should remove assets from a shared link (individual)', async () => { const { status, body } = await request(app) .delete(`/shared-links/${linkWithAssets.id}/assets`) @@ -405,6 +460,13 @@ describe('/shared-links', () => { }); describe('DELETE /shared-links/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/shared-links/${linkWithAlbum.id}`); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should fail if invalid link', async () => { const { status, body } = await request(app) .delete(`/shared-links/${uuidDto.notFound}`) diff --git a/e2e/src/specs/server/api/stack.e2e-spec.ts b/e2e/src/specs/server/api/stack.e2e-spec.ts index 21284dcf78..91dd0d2a8e 100644 --- a/e2e/src/specs/server/api/stack.e2e-spec.ts +++ b/e2e/src/specs/server/api/stack.e2e-spec.ts @@ -1,5 +1,6 @@ -import { LoginResponseDto, searchStacks } from '@immich/sdk'; -import { createUserDto } from 'src/fixtures'; +import { AssetMediaResponseDto, LoginResponseDto, searchStacks } from '@immich/sdk'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -7,16 +8,63 @@ import { beforeAll, describe, expect, it } from 'vitest'; describe('/stacks', () => { let admin: LoginResponseDto; let user1: LoginResponseDto; + let user2: LoginResponseDto; + let asset: AssetMediaResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup(); - user1 = await utils.userSetup(admin.accessToken, createUserDto.user1); + [user1, user2] = await Promise.all([ + utils.userSetup(admin.accessToken, createUserDto.user1), + utils.userSetup(admin.accessToken, createUserDto.user2), + ]); + + asset = await utils.createAsset(user1.accessToken); }); describe('POST /stacks', () => { + it('should require authentication', async () => { + const { status, body } = await request(app) + .post('/stacks') + .send({ assetIds: [asset.id] }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require at least two assets', async () => { + const { status, body } = await request(app) + .post('/stacks') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ assetIds: [asset.id] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest()); + }); + + it('should require a valid id', async () => { + const { status, body } = await request(app) + .post('/stacks') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ assetIds: [uuidDto.invalid, uuidDto.invalid] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest()); + }); + + it('should require access', async () => { + const user2Asset = await utils.createAsset(user2.accessToken); + const { status, body } = await request(app) + .post('/stacks') + .set('Authorization', `Bearer ${user1.accessToken}`) + .send({ assetIds: [asset.id, user2Asset.id] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + it('should create a stack', async () => { const [asset1, asset2] = await Promise.all([ utils.createAsset(user1.accessToken), diff --git a/e2e/src/specs/server/api/system-config.e2e-spec.ts b/e2e/src/specs/server/api/system-config.e2e-spec.ts index 91b747cf28..1bd7bdc489 100644 --- a/e2e/src/specs/server/api/system-config.e2e-spec.ts +++ b/e2e/src/specs/server/api/system-config.e2e-spec.ts @@ -21,18 +21,18 @@ describe('/system-config', () => { const response1 = await request(app) .put('/system-config') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } }); + .send({ ...config, newVersionCheck: { enabled: false } }); expect(response1.status).toBe(200); - expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } }); + expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false } }); const response2 = await request(app) .put('/system-config') .set('Authorization', `Bearer ${admin.accessToken}`) - .send({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } }); + .send({ ...config, newVersionCheck: { enabled: true } }); expect(response2.status).toBe(200); - expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } }); + expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true } }); }); it('should reject an invalid config entry', async () => { diff --git a/e2e/src/specs/server/api/system-metadata.e2e-spec.ts b/e2e/src/specs/server/api/system-metadata.e2e-spec.ts index 18589ed1d7..bd17bf2524 100644 --- a/e2e/src/specs/server/api/system-metadata.e2e-spec.ts +++ b/e2e/src/specs/server/api/system-metadata.e2e-spec.ts @@ -1,17 +1,36 @@ import { LoginResponseDto, getServerConfig } from '@immich/sdk'; +import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; describe('/server-info', () => { let admin: LoginResponseDto; + let nonAdmin: LoginResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup({ onboarding: false }); + nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1); }); describe('POST /system-metadata/admin-onboarding', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/system-metadata/admin-onboarding').send({ isOnboarded: true }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .post('/system-metadata/admin-onboarding') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send({ isOnboarded: true }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should set admin onboarding', async () => { const config = await getServerConfig({}); expect(config.isOnboarded).toBe(false); @@ -28,6 +47,20 @@ describe('/server-info', () => { }); describe('GET /system-metadata/reverse-geocoding-state', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/system-metadata/reverse-geocoding-state'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .get('/system-metadata/reverse-geocoding-state') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should get the reverse geocoding state', async () => { const { status, body } = await request(app) .get('/system-metadata/reverse-geocoding-state') diff --git a/e2e/src/specs/server/api/tag.e2e-spec.ts b/e2e/src/specs/server/api/tag.e2e-spec.ts index 40c8cd5114..d69536f3a3 100644 --- a/e2e/src/specs/server/api/tag.e2e-spec.ts +++ b/e2e/src/specs/server/api/tag.e2e-spec.ts @@ -9,7 +9,7 @@ import { tagAssets, upsertTags, } from '@immich/sdk'; -import { createUserDto } from 'src/fixtures'; +import { createUserDto, uuidDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; @@ -41,6 +41,19 @@ describe('/tags', () => { }); describe('POST /tags', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/tags').send({ name: 'TagA' }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization (api key)', async () => { + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app).post('/tags').set('x-api-key', secret).send({ name: 'TagA' }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.create')); + }); + it('should work with tag.create', async () => { const { secret } = await utils.createApiKey(user.accessToken, [Permission.TagCreate]); const { status, body } = await request(app).post('/tags').set('x-api-key', secret).send({ name: 'TagA' }); @@ -104,6 +117,19 @@ describe('/tags', () => { }); describe('GET /tags', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/tags'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization (api key)', async () => { + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app).get('/tags').set('x-api-key', secret); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.read')); + }); + it('should start off empty', async () => { const { status, body } = await request(app).get('/tags').set('Authorization', `Bearer ${admin.accessToken}`); expect(body).toEqual([]); @@ -143,6 +169,19 @@ describe('/tags', () => { }); describe('PUT /tags', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/tags`).send({ name: 'TagA/TagB' }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization (api key)', async () => { + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app).put('/tags').set('x-api-key', secret).send({ name: 'TagA' }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.create')); + }); + it('should upsert tags', async () => { const { status, body } = await request(app) .put(`/tags`) @@ -174,6 +213,22 @@ describe('/tags', () => { }); describe('PUT /tags/assets', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/tags/assets`).send({ tagIds: [], assetIds: [] }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization (api key)', async () => { + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app) + .put('/tags/assets') + .set('x-api-key', secret) + .send({ assetIds: [], tagIds: [] }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.asset')); + }); + it('should skip assets that are not owned by the user', async () => { const [tagA, tagB, tagC, assetA, assetB] = await Promise.all([ create(user.accessToken, { name: 'TagA' }), @@ -224,6 +279,39 @@ describe('/tags', () => { }); describe('GET /tags/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/tags/${uuidDto.notFound}`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { status, body } = await request(app) + .get(`/tags/${tag.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require authorization (api key)', async () => { + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app) + .get(`/tags/${uuidDto.notFound}`) + .set('x-api-key', secret) + .send({ assetIds: [], tagIds: [] }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.read')); + }); + + it('should require a valid uuid', async () => { + const { status, body } = await request(app) + .get(`/tags/${uuidDto.invalid}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + it('should get tag details', async () => { const tag = await create(user.accessToken, { name: 'TagA' }); const { status, body } = await request(app) @@ -261,6 +349,34 @@ describe('/tags', () => { }); describe('PUT /tags/:id', () => { + it('should require authentication', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { status, body } = await request(app).put(`/tags/${tag.id}`).send({ color: '#000000' }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const tag = await create(admin.accessToken, { name: 'tagA' }); + const { status, body } = await request(app) + .put(`/tags/${tag.id}`) + .send({ color: '#000000' }) + .set('Authorization', `Bearer ${user.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require authorization (api key)', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app) + .put(`/tags/${tag.id}`) + .set('x-api-key', secret) + .send({ color: '#000000' }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.update')); + }); + it('should update a tag', async () => { const tag = await create(user.accessToken, { name: 'tagA' }); const { status, body } = await request(app) @@ -283,6 +399,37 @@ describe('/tags', () => { }); describe('DELETE /tags/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/tags/${uuidDto.notFound}`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { status, body } = await request(app) + .delete(`/tags/${tag.id}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require authorization (api key)', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app).delete(`/tags/${tag.id}`).set('x-api-key', secret); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.delete')); + }); + + it('should require a valid uuid', async () => { + const { status, body } = await request(app) + .delete(`/tags/${uuidDto.invalid}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['id must be a UUID'])); + }); + it('should delete a tag', async () => { const tag = await create(user.accessToken, { name: 'TagA' }); const { status } = await request(app) @@ -316,6 +463,36 @@ describe('/tags', () => { }); describe('PUT /tags/:id/assets', () => { + it('should require authentication', async () => { + const tagA = await create(user.accessToken, { name: 'TagA' }); + const { status, body } = await request(app) + .put(`/tags/${tagA.id}/assets`) + .send({ ids: [userAsset.id] }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { status, body } = await request(app) + .put(`/tags/${tag.id}/assets`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ ids: [userAsset.id] }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require authorization (api key)', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app) + .put(`/tags/${tag.id}/assets`) + .set('x-api-key', secret) + .send({ ids: [userAsset.id] }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.asset')); + }); + it('should be able to tag own asset', async () => { const tagA = await create(user.accessToken, { name: 'TagA' }); const { status, body } = await request(app) @@ -354,6 +531,42 @@ describe('/tags', () => { }); describe('DELETE /tags/:id/assets', () => { + it('should require authentication', async () => { + const tagA = await create(admin.accessToken, { name: 'TagA' }); + const { status, body } = await request(app) + .delete(`/tags/${tagA}/assets`) + .send({ ids: [userAsset.id] }); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const tagA = await create(user.accessToken, { name: 'TagA' }); + await tagAssets( + { id: tagA.id, bulkIdsDto: { ids: [userAsset.id] } }, + { headers: asBearerAuth(user.accessToken) }, + ); + const { status, body } = await request(app) + .delete(`/tags/${tagA.id}/assets`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ ids: [userAsset.id] }); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.noPermission); + }); + + it('should require authorization (api key)', async () => { + const tag = await create(user.accessToken, { name: 'TagA' }); + const { secret } = await utils.createApiKey(user.accessToken, [Permission.AssetRead]); + const { status, body } = await request(app) + .delete(`/tags/${tag.id}/assets`) + .set('x-api-key', secret) + .send({ ids: [userAsset.id] }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.missingPermission('tag.asset')); + }); + it('should be able to remove own asset from own tag', async () => { const tagA = await create(user.accessToken, { name: 'TagA' }); await tagAssets( diff --git a/e2e/src/specs/server/api/trash.e2e-spec.ts b/e2e/src/specs/server/api/trash.e2e-spec.ts index e3ea72fdd2..7a1a61f946 100644 --- a/e2e/src/specs/server/api/trash.e2e-spec.ts +++ b/e2e/src/specs/server/api/trash.e2e-spec.ts @@ -1,6 +1,7 @@ import { LoginResponseDto, getAssetInfo, getAssetStatistics } from '@immich/sdk'; import { existsSync } from 'node:fs'; import { Socket } from 'socket.io-client'; +import { errorDto } from 'src/responses'; import { app, asBearerAuth, testAssetDir, testAssetDirInternal, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -20,6 +21,13 @@ describe('/trash', () => { }); describe('POST /trash/empty', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/trash/empty'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should empty the trash', async () => { const { id: assetId } = await utils.createAsset(admin.accessToken); await utils.deleteAssets(admin.accessToken, [assetId]); @@ -134,6 +142,13 @@ describe('/trash', () => { }); describe('POST /trash/restore', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/trash/restore'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should restore all trashed assets', async () => { const { id: assetId } = await utils.createAsset(admin.accessToken); await utils.deleteAssets(admin.accessToken, [assetId]); @@ -183,6 +198,13 @@ describe('/trash', () => { }); describe('POST /trash/restore/assets', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/trash/restore/assets'); + + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should restore a trashed asset by id', async () => { const { id: assetId } = await utils.createAsset(admin.accessToken); await utils.deleteAssets(admin.accessToken, [assetId]); diff --git a/e2e/src/specs/server/api/user-admin.e2e-spec.ts b/e2e/src/specs/server/api/user-admin.e2e-spec.ts index 37f3f2ea99..793c508a36 100644 --- a/e2e/src/specs/server/api/user-admin.e2e-spec.ts +++ b/e2e/src/specs/server/api/user-admin.e2e-spec.ts @@ -9,7 +9,8 @@ import { login, } from '@immich/sdk'; import { Socket } from 'socket.io-client'; -import { createUserDto } from 'src/fixtures'; +import { createUserDto, uuidDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; @@ -44,6 +45,20 @@ describe('/admin/users', () => { }); describe('GET /admin/users', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get(`/admin/users`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const { status, body } = await request(app) + .get(`/admin/users`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should hide deleted users by default', async () => { const { status, body } = await request(app) .get(`/admin/users`) @@ -78,6 +93,32 @@ describe('/admin/users', () => { }); describe('POST /admin/users', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post(`/admin/users`).send(createUserDto.user1); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const { status, body } = await request(app) + .post(`/admin/users`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send(createUserDto.user1); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + for (const key of ['password', 'email', 'name', 'quotaSizeInBytes', 'shouldChangePassword', 'notify']) { + it(`should not allow null ${key}`, async () => { + const { status, body } = await request(app) + .post(`/admin/users`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ ...createUserDto.user1, [key]: null }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest()); + }); + } + it('should accept `isAdmin`', async () => { const { status, body } = await request(app) .post(`/admin/users`) @@ -98,6 +139,31 @@ describe('/admin/users', () => { }); describe('PUT /admin/users/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/admin/users/${uuidDto.notFound}`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const { status, body } = await request(app) + .put(`/admin/users/${uuidDto.notFound}`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + for (const key of ['password', 'email', 'name', 'shouldChangePassword']) { + it(`should not allow null ${key}`, async () => { + const { status, body } = await request(app) + .put(`/admin/users/${uuidDto.notFound}`) + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ [key]: null }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest()); + }); + } + it('should allow a non-admin to become an admin', async () => { const user = await utils.userSetup(admin.accessToken, createUserDto.create('admin2')); const { status, body } = await request(app) @@ -167,6 +233,12 @@ describe('/admin/users', () => { }); describe('PUT /admin/users/:id/preferences', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).put(`/admin/users/${userToDelete.userId}/preferences`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + it('should update memories enabled', async () => { const before = await getUserPreferencesAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) }); expect(before).toMatchObject({ memories: { enabled: true } }); @@ -198,11 +270,24 @@ describe('/admin/users', () => { }); describe('DELETE /admin/users/:id', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).delete(`/admin/users/${userToDelete.userId}`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const { status, body } = await request(app) + .delete(`/admin/users/${userToDelete.userId}`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should delete user', async () => { const { status, body } = await request(app) .delete(`/admin/users/${userToDelete.userId}`) - .set('Authorization', `Bearer ${admin.accessToken}`) - .send({}); + .set('Authorization', `Bearer ${admin.accessToken}`); expect(status).toBe(200); expect(body).toMatchObject({ @@ -262,6 +347,20 @@ describe('/admin/users', () => { }); describe('POST /admin/users/:id/restore', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post(`/admin/users/${userToDelete.userId}/restore`); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should require authorization', async () => { + const { status, body } = await request(app) + .post(`/admin/users/${userToDelete.userId}/restore`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + it('should restore a user', async () => { const user = await utils.userSetup(admin.accessToken, createUserDto.create('restore')); diff --git a/e2e/src/specs/server/api/user.e2e-spec.ts b/e2e/src/specs/server/api/user.e2e-spec.ts index ce6236eabc..3f280dddf5 100644 --- a/e2e/src/specs/server/api/user.e2e-spec.ts +++ b/e2e/src/specs/server/api/user.e2e-spec.ts @@ -1,20 +1,96 @@ -import { LoginResponseDto, getMyUser, login } from '@immich/sdk'; +import { LoginResponseDto, SharedLinkType, deleteUserAdmin, getMyPreferences, getMyUser, login } from '@immich/sdk'; import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; import { app, asBearerAuth, utils } from 'src/utils'; import request from 'supertest'; import { beforeAll, describe, expect, it } from 'vitest'; +const userLicense = { + licenseKey: 'IMCL-FF69-TUK1-RWZU-V9Q8-QGQS-S5GC-X4R2-UFK4', + activationKey: + 'KuX8KsktrBSiXpQMAH0zLgA5SpijXVr_PDkzLdWUlAogCTMBZ0I3KCHXK0eE9EEd7harxup8_EHMeqAWeHo5VQzol6LGECpFv585U9asXD4Zc-UXt3mhJr2uhazqipBIBwJA2YhmUCDy8hiyiGsukDQNu9Rg9C77UeoKuZBWVjWUBWG0mc1iRqfvF0faVM20w53czAzlhaMxzVGc3Oimbd7xi_CAMSujF_2y8QpA3X2fOVkQkzdcH9lV0COejl7IyH27zQQ9HrlrXv3Lai5Hw67kNkaSjmunVBxC5PS0TpKoc9SfBJMaAGWnaDbjhjYUrm-8nIDQnoeEAidDXVAdPw', +}; + describe('/users', () => { let admin: LoginResponseDto; + let deletedUser: LoginResponseDto; let nonAdmin: LoginResponseDto; beforeAll(async () => { await utils.resetDatabase(); admin = await utils.adminSetup({ onboarding: false }); - nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user2); + + [deletedUser, nonAdmin] = await Promise.all([ + utils.userSetup(admin.accessToken, createUserDto.user1), + utils.userSetup(admin.accessToken, createUserDto.user2), + ]); + + await deleteUserAdmin( + { id: deletedUser.userId, userAdminDeleteDto: {} }, + { headers: asBearerAuth(admin.accessToken) }, + ); + }); + + describe('GET /users/me', () => { + it('should not work for shared links', async () => { + const album = await utils.createAlbum(admin.accessToken, { albumName: 'Album' }); + const sharedLink = await utils.createSharedLink(admin.accessToken, { + type: SharedLinkType.Album, + albumId: album.id, + }); + const { status, body } = await request(app).get(`/users/me?key=${sharedLink.key}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + it('should get my user', async () => { + const { status, body } = await request(app).get(`/users/me`).set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ + id: admin.userId, + email: 'admin@immich.cloud', + quotaUsageInBytes: 0, + }); + }); + + it('should get my user with license info', async () => { + const { status: licenseStatus } = await request(app) + .put(`/users/me/license`) + .send(userLicense) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(licenseStatus).toBe(200); + const { status, body } = await request(app) + .get(`/users/me`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ + id: nonAdmin.userId, + email: nonAdmin.userEmail, + quotaUsageInBytes: 0, + license: userLicense, + }); + }); }); describe('PUT /users/me', () => { + it('should update first and last name', async () => { + const before = await getMyUser({ headers: asBearerAuth(admin.accessToken) }); + + const { status, body } = await request(app) + .put(`/users/me`) + .send({ name: 'Name' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + ...before, + updatedAt: expect.any(String), + profileChangedAt: expect.any(String), + createdAt: expect.any(String), + name: 'Name', + }); + }); + /** @deprecated */ it('should allow a user to change their password (deprecated)', async () => { const user = await getMyUser({ headers: asBearerAuth(nonAdmin.accessToken) }); @@ -36,5 +112,199 @@ describe('/users', () => { expect(token.accessToken).toBeDefined(); }); + + it('should not allow user to change to a taken email', async () => { + const { status, body } = await request(app) + .put(`/users/me`) + .send({ email: 'admin@immich.cloud' }) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + + expect(status).toBe(400); + expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account')); + }); + + it('should update my email', async () => { + const before = await getMyUser({ headers: asBearerAuth(nonAdmin.accessToken) }); + const { status, body } = await request(app) + .put(`/users/me`) + .send({ email: 'non-admin@immich.cloud' }) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + + expect(status).toBe(200); + expect(body).toMatchObject({ + ...before, + email: 'non-admin@immich.cloud', + updatedAt: expect.anything(), + createdAt: expect.anything(), + profileChangedAt: expect.anything(), + }); + }); + + it('should update avatar color', async () => { + const { status, body } = await request(app) + .put(`/users/me`) + .send({ avatarColor: 'blue' }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toMatchObject({ avatarColor: 'blue' }); + + const after = await getMyUser({ headers: asBearerAuth(admin.accessToken) }); + expect(after).toMatchObject({ avatarColor: 'blue' }); + }); + }); + + describe('PUT /users/me/preferences', () => { + it('should update memories enabled', async () => { + const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(before).toMatchObject({ memories: { enabled: true } }); + + const { status, body } = await request(app) + .put(`/users/me/preferences`) + .send({ memories: { enabled: false } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toMatchObject({ memories: { enabled: false } }); + + const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(after).toMatchObject({ memories: { enabled: false } }); + }); + + it('should require an integer for download archive size', async () => { + const { status, body } = await request(app) + .put(`/users/me/preferences`) + .send({ download: { archiveSize: 1_234_567.89 } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['download.archiveSize must be an integer number'])); + }); + + it('should update download archive size', async () => { + const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(before).toMatchObject({ download: { archiveSize: 4 * 2 ** 30 } }); + + const { status, body } = await request(app) + .put(`/users/me/preferences`) + .send({ download: { archiveSize: 1_234_567 } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toMatchObject({ download: { archiveSize: 1_234_567 } }); + + const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(after).toMatchObject({ download: { archiveSize: 1_234_567 } }); + }); + + it('should require a boolean for download include embedded videos', async () => { + const { status, body } = await request(app) + .put(`/users/me/preferences`) + .send({ download: { includeEmbeddedVideos: 1_234_567.89 } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['download.includeEmbeddedVideos must be a boolean value'])); + }); + + it('should update download include embedded videos', async () => { + const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(before).toMatchObject({ download: { includeEmbeddedVideos: false } }); + + const { status, body } = await request(app) + .put(`/users/me/preferences`) + .send({ download: { includeEmbeddedVideos: true } }) + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toMatchObject({ download: { includeEmbeddedVideos: true } }); + + const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) }); + expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } }); + }); + }); + + describe('GET /users/:id', () => { + it('should get the user', async () => { + const { status, body } = await request(app) + .get(`/users/${admin.userId}`) + .set('Authorization', `Bearer ${admin.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ + id: admin.userId, + email: 'admin@immich.cloud', + }); + + expect(body).not.toMatchObject({ + shouldChangePassword: expect.anything(), + storageLabel: expect.anything(), + }); + }); + }); + + describe('GET /server/license', () => { + it('should return the user license', async () => { + await request(app) + .put('/users/me/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send(userLicense); + const { status, body } = await request(app) + .get('/users/me/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(200); + expect(body).toEqual({ + ...userLicense, + activatedAt: expect.any(String), + }); + }); + }); + + describe('PUT /users/me/license', () => { + it('should set the user license', async () => { + const { status, body } = await request(app) + .put(`/users/me/license`) + .send(userLicense) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(200); + expect(body).toMatchObject({ ...userLicense, activatedAt: expect.any(String) }); + expect(status).toBe(200); + expect(body).toEqual({ ...userLicense, activatedAt: expect.any(String) }); + const { body: licenseBody } = await request(app) + .get('/users/me/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(licenseBody).toEqual({ ...userLicense, activatedAt: expect.any(String) }); + }); + + it('should reject license not starting with IMCL-', async () => { + const { status, body } = await request(app) + .put('/users/me/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send({ licenseKey: 'IMSV-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD', activationKey: 'activationKey' }); + expect(status).toBe(400); + expect(body.message).toBe('Invalid license key'); + }); + + it('should reject license with invalid activation key', async () => { + const { status, body } = await request(app) + .put('/users/me/license') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send({ licenseKey: userLicense.licenseKey, activationKey: `invalid${userLicense.activationKey}` }); + expect(status).toBe(400); + expect(body.message).toBe('Invalid license key'); + }); + }); + + describe('DELETE /users/me/license', () => { + it('should require authentication', async () => { + const { status } = await request(app).put(`/users/me/license`); + expect(status).toEqual(401); + }); + + it('should delete the user license', async () => { + const { status } = await request(app) + .delete(`/users/me/license`) + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(204); + }); }); }); diff --git a/e2e/src/specs/server/cli/login.e2e-spec.ts b/e2e/src/specs/server/cli/login.e2e-spec.ts index 98a28be448..3bc3ebc9c2 100644 --- a/e2e/src/specs/server/cli/login.e2e-spec.ts +++ b/e2e/src/specs/server/cli/login.e2e-spec.ts @@ -30,8 +30,8 @@ describe(`immich login`, () => { it('should login and save auth.yml with 600', async () => { const admin = await utils.adminSetup(); - const apiKey = await utils.createApiKey(admin.accessToken, [Permission.All]); - const { stdout, stderr, exitCode } = await immichCli(['login', app, apiKey.secret]); + const key = await utils.createApiKey(admin.accessToken, [Permission.All]); + const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]); expect(stdout.split('\n')).toEqual([ 'Logging in to http://127.0.0.1:2285/api', 'Logged in as admin@immich.cloud', @@ -47,8 +47,8 @@ describe(`immich login`, () => { it('should login without /api in the url', async () => { const admin = await utils.adminSetup(); - const apiKey = await utils.createApiKey(admin.accessToken, [Permission.All]); - const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), apiKey.secret]); + const key = await utils.createApiKey(admin.accessToken, [Permission.All]); + const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]); expect(stdout.split('\n')).toEqual([ 'Logging in to http://127.0.0.1:2285', 'Discovered API at http://127.0.0.1:2285/api', diff --git a/e2e/src/specs/server/cli/upload.e2e-spec.ts b/e2e/src/specs/server/cli/upload.e2e-spec.ts index 6ed5030e83..b53b4403f8 100644 --- a/e2e/src/specs/server/cli/upload.e2e-spec.ts +++ b/e2e/src/specs/server/cli/upload.e2e-spec.ts @@ -119,9 +119,7 @@ describe(`immich upload`, () => { const baseDir = `/tmp/upload/`; const testPaths = Object.keys(files).map((filePath) => `${baseDir}/${filePath}`); - for (const filePath of testPaths) { - utils.createImageFile(filePath); - } + testPaths.map((filePath) => utils.createImageFile(filePath)); const commandLine = paths.map((argument) => `${baseDir}/${argument}`); @@ -137,9 +135,7 @@ describe(`immich upload`, () => { const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); expect(assets.total).toBe(expectedCount); - for (const filePath of testPaths) { - utils.removeImageFile(filePath); - } + testPaths.map((filePath) => utils.removeImageFile(filePath)); }); } }); @@ -650,7 +646,7 @@ describe(`immich upload`, () => { ]); expect(stdout).toBe(''); - expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '--skip-hash'`); + expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '-h, --skip-hash'`); expect(exitCode).not.toBe(0); const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) }); diff --git a/e2e/src/specs/server/cli/version.e2e-spec.ts b/e2e/src/specs/server/cli/version.e2e-spec.ts index de03fdf358..56a0d8b0b1 100644 --- a/e2e/src/specs/server/cli/version.e2e-spec.ts +++ b/e2e/src/specs/server/cli/version.e2e-spec.ts @@ -2,7 +2,7 @@ import { readFileSync } from 'node:fs'; import { immichCli } from 'src/utils'; import { describe, expect, it } from 'vitest'; -const pkg = JSON.parse(readFileSync('../packages/cli/package.json', 'utf8')); +const pkg = JSON.parse(readFileSync('../cli/package.json', 'utf8')); describe(`immich --version`, () => { describe('immich --version', () => { diff --git a/e2e/src/specs/server/immich-admin/immich-admin.e2e-spec.ts b/e2e/src/specs/server/immich-admin/immich-admin.e2e-spec.ts index ad0e43ff14..24699cda30 100644 --- a/e2e/src/specs/server/immich-admin/immich-admin.e2e-spec.ts +++ b/e2e/src/specs/server/immich-admin/immich-admin.e2e-spec.ts @@ -65,10 +65,6 @@ describe(`immich-admin`, () => { child.stdout.on('data', (chunk) => { data += chunk; if (data.includes('Please choose a new password (optional)')) { - child.stdin.write('\n'); - } - - if (data.includes('Invalidate existing sessions?')) { child.stdin.end('\n'); } }); diff --git a/e2e/src/specs/web/asset-viewer/detail-panel.e2e-spec.ts b/e2e/src/specs/web/asset-viewer/detail-panel.e2e-spec.ts index bbe0ef328f..2f90e4e3d8 100644 --- a/e2e/src/specs/web/asset-viewer/detail-panel.e2e-spec.ts +++ b/e2e/src/specs/web/asset-viewer/detail-panel.e2e-spec.ts @@ -1,9 +1,7 @@ import { AssetMediaResponseDto, LoginResponseDto, SharedLinkType } from '@immich/sdk'; import { expect, test } from '@playwright/test'; -import { readFile } from 'node:fs/promises'; -import { basename, join } from 'node:path'; import type { Socket } from 'socket.io-client'; -import { testAssetDir, utils } from 'src/utils'; +import { utils } from 'src/utils'; test.describe('Detail Panel', () => { let admin: LoginResponseDto; @@ -85,42 +83,4 @@ test.describe('Detail Panel', () => { await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id }); await expect(textarea).toHaveValue('new description'); }); - - test.describe('Date editor', () => { - test('displays inferred asset timezone', async ({ context, page }) => { - const test = { - filepath: 'metadata/dates/datetimeoriginal-gps.jpg', - expected: { - dateTime: '2025-12-01T11:30', - // Test with a timezone which is NOT the first among timezones with the same offset - // This is to check that the editor does not simply fall back to the first available timezone with that offset - // America/Denver (-07:00) is not the first among timezones with offset -07:00 - timeZoneWithOffset: 'America/Denver (-07:00)', - }, - }; - - const asset = await utils.createAsset(admin.accessToken, { - assetData: { - bytes: await readFile(join(testAssetDir, test.filepath)), - filename: basename(test.filepath), - }, - }); - - await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset.id }); - - // asset viewer -> detail panel -> date editor - await utils.setAuthCookies(context, admin.accessToken); - await page.goto(`/photos/${asset.id}`); - await page.waitForSelector('#immich-asset-viewer'); - - await page.getByRole('button', { name: 'Info' }).click(); - await page.getByTestId('detail-panel-edit-date-button').click(); - await page.waitForSelector('[role="dialog"]'); - - const datetime = page.locator('#datetime'); - await expect(datetime).toHaveValue(test.expected.dateTime); - const timezone = page.getByRole('combobox', { name: 'Timezone' }); - await expect(timezone).toHaveValue(test.expected.timeZoneWithOffset); - }); - }); }); diff --git a/e2e/src/specs/web/duplicates.e2e-spec.ts b/e2e/src/specs/web/duplicates.e2e-spec.ts index c39e9019d3..34f11cdf78 100644 --- a/e2e/src/specs/web/duplicates.e2e-spec.ts +++ b/e2e/src/specs/web/duplicates.e2e-spec.ts @@ -16,8 +16,8 @@ test.describe('Duplicates Utility', () => { test.beforeEach(async ({ context }) => { [firstAsset, secondAsset] = await Promise.all([ - utils.createAsset(admin.accessToken, {}), - utils.createAsset(admin.accessToken, {}), + utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-a' }), + utils.createAsset(admin.accessToken, { deviceAssetId: 'duplicate-b' }), ]); await updateAssets( diff --git a/e2e/src/specs/web/integrity.e2e-spec.ts b/e2e/src/specs/web/integrity.e2e-spec.ts deleted file mode 100644 index 615b1dc538..0000000000 --- a/e2e/src/specs/web/integrity.e2e-spec.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk'; -import { expect, test } from '@playwright/test'; -import { utils } from 'src/utils'; - -test.describe.configure({ mode: 'serial' }); - -test.describe.skip('Integrity', () => { - let admin: LoginResponseDto; - - test.beforeAll(async () => { - utils.initSdk(); - await utils.resetDatabase(); - admin = await utils.adminSetup(); - }); - - test('run integrity jobs to update stats', async ({ context, page }) => { - await utils.setAuthCookies(context, admin.accessToken); - - await utils.createJob(admin.accessToken, { - name: ManualJobName.IntegrityUntrackedFiles, - }); - - await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck); - - await page.goto('/admin/maintenance'); - - const count = page.getByText('Untracked Files').locator('..').locator('..').locator('div').nth(1); - - const previousCount = Number.parseInt((await count.textContent()) ?? ''); - - await utils.mkFolder(`/data/upload/${admin.userId}`); - await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`); - - const checkButton = page.getByText('Integrity Report').locator('..').getByRole('button', { name: 'Check All' }); - - await checkButton.click(); - await expect(checkButton).toBeEnabled(); - - await expect(count).toContainText((previousCount + 1).toString()); - }); -}); diff --git a/e2e/src/specs/web/photo-viewer.e2e-spec.ts b/e2e/src/specs/web/photo-viewer.e2e-spec.ts index 71f2145be8..76d9d61ed6 100644 --- a/e2e/src/specs/web/photo-viewer.e2e-spec.ts +++ b/e2e/src/specs/web/photo-viewer.e2e-spec.ts @@ -77,4 +77,18 @@ test.describe('Photo Viewer', () => { }); expect(tagAtCenter).toBe('IMG'); }); + + test('reloads photo when checksum changes', async ({ page }) => { + await page.goto(`/photos/${asset.id}`); + + const preview = page.getByTestId('preview').filter({ visible: true }); + await expect(preview).toHaveAttribute('src', /.+/); + const initialSrc = await preview.getAttribute('src'); + + const websocketEvent = utils.waitForWebsocketEvent({ event: 'assetUpdate', id: asset.id }); + await utils.replaceAsset(admin.accessToken, asset.id); + await websocketEvent; + + await expect(preview).not.toHaveAttribute('src', initialSrc!); + }); }); diff --git a/e2e/src/ui/generators/timeline/images.ts b/e2e/src/ui/generators/timeline/images.ts index 2eb141c557..9330cf137d 100644 --- a/e2e/src/ui/generators/timeline/images.ts +++ b/e2e/src/ui/generators/timeline/images.ts @@ -21,7 +21,7 @@ export const randomImageFromString = async ( let seedNumber = 0; for (let i = 0; i < seed.length; i++) { seedNumber = (seedNumber << 5) - seedNumber + (seed.codePointAt(i) ?? 0); - seedNumber &= seedNumber; // Convert to 32bit integer + seedNumber = seedNumber & seedNumber; // Convert to 32bit integer } return randomImage(new SeededRandom(Math.abs(seedNumber)), { width, height }); }; diff --git a/e2e/src/ui/generators/timeline/model-objects.ts b/e2e/src/ui/generators/timeline/model-objects.ts index 094e5133a3..e300de1161 100644 --- a/e2e/src/ui/generators/timeline/model-objects.ts +++ b/e2e/src/ui/generators/timeline/model-objects.ts @@ -32,12 +32,8 @@ export function generateThumbhash(rng: SeededRandom): string { return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join(''); } -export function generateDuration(rng: SeededRandom): number { - return ( - rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) * - 1000 + - rng.nextInt(0, 1000) - ); +export function generateDuration(rng: SeededRandom): string { + return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`; } export function generateUUID(): string { @@ -64,7 +60,7 @@ export function generateAsset( const asset: MockTimelineAsset = { id: assetId, ownerId, - ratio: Number(ratio.split(':', 1)[0]) / Number(ratio.split(':', 2)[1]), + ratio: Number.parseFloat(ratio.split(':')[0]) / Number.parseFloat(ratio.split(':')[1]), thumbhash: generateThumbhash(rng), localDateTime: date.toISOString(), fileCreatedAt: date.toISOString(), @@ -214,7 +210,7 @@ export function generateTimelineData(config: TimelineConfig): MockTimelineData { } // Create a mock album from random assets - const allAssets = buckets.values().toArray().flat(); + const allAssets = [...buckets.values()].flat(); // Select 10-30 random assets for the album (or all assets if less than 10) const albumSize = Math.min(allAssets.length, globalRng.nextInt(10, 31)); diff --git a/e2e/src/ui/generators/timeline/rest-response.ts b/e2e/src/ui/generators/timeline/rest-response.ts index d7cf7adf0a..0c4bd06dc3 100644 --- a/e2e/src/ui/generators/timeline/rest-response.ts +++ b/e2e/src/ui/generators/timeline/rest-response.ts @@ -3,7 +3,6 @@ */ import { - AlbumUserRole, AssetTypeEnum, AssetVisibility, UserAvatarColor, @@ -28,7 +27,6 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe ownerId: [], ratio: [], thumbhash: [], - createdAt: [], fileCreatedAt: [], localOffsetHours: [], isFavorite: [], @@ -55,8 +53,8 @@ export function toColumnarFormat(assets: MockTimelineAsset[]): TimeBucketAssetRe result.duration.push(asset.duration); result.projectionType.push(asset.projectionType); result.livePhotoVideoId.push(asset.livePhotoVideoId); - result.city?.push(asset.city); - result.country?.push(asset.country); + result.city.push(asset.city); + result.country.push(asset.country); result.visibility.push(asset.visibility); } @@ -172,7 +170,11 @@ function shouldIncludeAsset( if (isArchived !== undefined && actuallyArchived !== isArchived) { return false; } - return isFavorite === undefined || actuallyFavorited === isFavorite; + if (isFavorite !== undefined && actuallyFavorited !== isFavorite) { + return false; + } + + return true; } /** * Get summary for all buckets (mimics getTimeBuckets API) @@ -313,9 +315,11 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons return { id: asset.id, + deviceAssetId: `device-${asset.id}`, ownerId: asset.ownerId, owner: owner || defaultOwner, libraryId: `library-${asset.ownerId}`, + deviceId: `device-${asset.ownerId}`, type: asset.isVideo ? AssetTypeEnum.Video : AssetTypeEnum.Image, originalPath: `/original/${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`, originalFileName: `${asset.id}.${asset.isVideo ? 'mp4' : 'jpg'}`, @@ -330,11 +334,12 @@ export function toAssetResponseDto(asset: MockTimelineAsset, owner?: UserRespons isArchived: false, isTrashed: asset.isTrashed, visibility: asset.visibility, - duration: asset.duration, + duration: asset.duration || '0:00:00.00000', exifInfo, livePhotoVideoId: asset.livePhotoVideoId, tags: [], people: [], + unassignedFaces: [], stack: asset.stack, isOffline: false, hasMetadata: true, @@ -357,7 +362,7 @@ export function getAsset( owner?: UserResponseDto, ): AssetResponseDto | undefined { // Search through all buckets for the asset - const buckets = timelineData.buckets.values().toArray(); + const buckets = [...timelineData.buckets.values()]; for (const assets of buckets) { const asset = assets.find((a) => a.id === assetId); if (asset) { @@ -391,7 +396,7 @@ export function getAlbum( // Get the actual asset objects from the timeline data const albumAssets: AssetResponseDto[] = []; - const allAssets = timelineData.buckets.values().toArray().flat(); + const allAssets = [...timelineData.buckets.values()].flat(); for (const assetId of album.assetIds) { const assetConfig = allAssets.find((a) => a.id === assetId); @@ -417,11 +422,14 @@ export function getAlbum( albumThumbnailAssetId: album.thumbnailAssetId, createdAt: album.createdAt, updatedAt: album.updatedAt, - albumUsers: [{ user: albumOwner, role: AlbumUserRole.Owner }], + ownerId: albumOwner.id, + owner: albumOwner, + albumUsers: [], // Empty array for non-shared album shared: false, hasSharedLink: false, isActivityEnabled: true, assetCount: albumAssets.length, + assets: albumAssets, startDate: albumAssets.length > 0 ? albumAssets.at(-1)?.fileCreatedAt : undefined, endDate: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined, lastModifiedAssetTimestamp: albumAssets.length > 0 ? albumAssets[0].fileCreatedAt : undefined, diff --git a/e2e/src/ui/generators/timeline/timeline-config.ts b/e2e/src/ui/generators/timeline/timeline-config.ts index 5bfb2c1800..992480eef9 100644 --- a/e2e/src/ui/generators/timeline/timeline-config.ts +++ b/e2e/src/ui/generators/timeline/timeline-config.ts @@ -24,6 +24,13 @@ export const ASPECT_RATIO_WEIGHTS = { '3:1': 0.01, // 1% 3:1 panorama } as const; +export type AspectRatio = { + width: number; + height: number; + ratio: number; + name: string; +}; + // Mock configuration for asset generation - will be transformed to API response formats export type MockTimelineAsset = { id: string; @@ -36,7 +43,7 @@ export type MockTimelineAsset = { isTrashed: boolean; isVideo: boolean; isImage: boolean; - duration: number | null; + duration: string | null; projectionType: string | null; livePhotoVideoId: string | null; city: string | null; @@ -136,7 +143,7 @@ export function validateTimelineConfig(config: TimelineConfig): void { } // Validate seed if provided - if (config.seed !== undefined && (config.seed < 0 || !Number.isSafeInteger(config.seed))) { + if (config.seed !== undefined && (config.seed < 0 || !Number.isInteger(config.seed))) { throw new Error('Seed must be a non-negative integer'); } diff --git a/e2e/src/ui/generators/timeline/utils.ts b/e2e/src/ui/generators/timeline/utils.ts index fbb9630a3c..283f56c6f0 100644 --- a/e2e/src/ui/generators/timeline/utils.ts +++ b/e2e/src/ui/generators/timeline/utils.ts @@ -153,8 +153,11 @@ export function getMockAsset( const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => { if (unit === 'day') { return !date1.startOf('day').equals(date2.startOf('day')); + } else if (unit === 'month') { + return date1.year !== date2.year || date1.month !== date2.month; + } else { + return date1.year !== date2.year; } - return unit === 'month' ? date1.year !== date2.year || date1.month !== date2.month : date1.year !== date2.year; }; if (direction === 'next') { diff --git a/e2e/src/ui/mock-network/base-network.ts b/e2e/src/ui/mock-network/base-network.ts index af8d1dbfef..7c4aee59e3 100644 --- a/e2e/src/ui/mock-network/base-network.ts +++ b/e2e/src/ui/mock-network/base-network.ts @@ -82,9 +82,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI cast: { gCastEnabled: false, }, - recentlyAdded: { - sidebarWeb: false, - }, }, }); }); @@ -226,7 +223,6 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI '.jp2', '.jpe', '.jxl', - '.mpo', '.svg', '.tif', '.tiff', @@ -243,8 +239,7 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI }); }); await context.route('**/api/albums*', async (route, request) => { - const url = request.url(); - if (url.endsWith('albums?isShared=true') || url.endsWith('albums?isOwned=true') || url.endsWith('albums')) { + if (request.url().endsWith('albums?shared=true') || request.url().endsWith('albums')) { return route.fulfill({ status: 200, contentType: 'application/json', diff --git a/e2e/src/ui/mock-network/broken-asset-network.ts b/e2e/src/ui/mock-network/broken-asset-network.ts index 2137cdd90f..1494b40531 100644 --- a/e2e/src/ui/mock-network/broken-asset-network.ts +++ b/e2e/src/ui/mock-network/broken-asset-network.ts @@ -16,6 +16,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => { const now = new Date().toISOString(); return { id: assetId, + deviceAssetId: `device-${assetId}`, ownerId, owner: { id: ownerId, @@ -26,6 +27,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => { avatarColor: 'blue' as never, }, libraryId: `library-${ownerId}`, + deviceId: `device-${ownerId}`, type: AssetTypeEnum.Image, originalPath: `/original/${assetId}.jpg`, originalFileName: `${assetId}.jpg`, @@ -40,7 +42,7 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => { isArchived: false, isTrashed: false, visibility: AssetVisibility.Timeline, - duration: null, + duration: '0:00:00.00000', exifInfo: { make: null, model: null, @@ -66,7 +68,8 @@ export const createMockStackAsset = (ownerId: string): AssetResponseDto => { livePhotoVideoId: null, tags: [], people: [], - stack: undefined, + unassignedFaces: [], + stack: null, isOffline: false, hasMetadata: true, duplicateId: null, diff --git a/e2e/src/ui/mock-network/timeline-network.ts b/e2e/src/ui/mock-network/timeline-network.ts index 1a745b294a..6af2ebb7c1 100644 --- a/e2e/src/ui/mock-network/timeline-network.ts +++ b/e2e/src/ui/mock-network/timeline-network.ts @@ -40,8 +40,7 @@ export const setupTimelineMockApiRoutes = async ( contentType: 'application/json', json: getTimeBuckets(timelineRestData, isTrashed, isArchived, isFavorite, albumId, changes), }); - } - if (pathname === '/api/timeline/bucket') { + } else if (pathname === '/api/timeline/bucket') { const timeBucket = url.searchParams.get('timeBucket'); if (!timeBucket) { return route.continue(); diff --git a/e2e/src/ui/specs/asset-viewer/asset-viewer.e2e-spec.ts b/e2e/src/ui/specs/asset-viewer/asset-viewer.e2e-spec.ts index 85798f6be3..082ff1f7a1 100644 --- a/e2e/src/ui/specs/asset-viewer/asset-viewer.e2e-spec.ts +++ b/e2e/src/ui/specs/asset-viewer/asset-viewer.e2e-spec.ts @@ -38,7 +38,7 @@ test.describe('asset-viewer', () => { assets.push(...timeBucket); } for (const yearMonth of timelineRestData.buckets.keys()) { - const [year, month] = yearMonth.split('-', 2); + const [year, month] = yearMonth.split('-'); yearMonths.push(`${year}-${Number(month)}`); } }); diff --git a/e2e/src/ui/specs/asset-viewer/utils.ts b/e2e/src/ui/specs/asset-viewer/utils.ts index 8cc2ad711a..adaace8a34 100644 --- a/e2e/src/ui/specs/asset-viewer/utils.ts +++ b/e2e/src/ui/specs/asset-viewer/utils.ts @@ -110,7 +110,6 @@ export async function enableTagsPreference(context: BrowserContext) { download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false }, purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' }, cast: { gCastEnabled: false }, - recentlyAdded: { sidebarWeb: false }, }, }); }); diff --git a/e2e/src/ui/specs/memory/utils.ts b/e2e/src/ui/specs/memory/utils.ts index 378f649cff..cf99033e7e 100644 --- a/e2e/src/ui/specs/memory/utils.ts +++ b/e2e/src/ui/specs/memory/utils.ts @@ -1,4 +1,3 @@ -/* eslint-disable unicorn/no-this-outside-of-class */ import type { AssetResponseDto } from '@immich/sdk'; import { expect, Page } from '@playwright/test'; diff --git a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts index adbff0d3b8..6a7ce82672 100644 --- a/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts +++ b/e2e/src/ui/specs/timeline/timeline.e2e-spec.ts @@ -49,7 +49,7 @@ test.describe('Timeline', () => { assets.push(...timeBucket); } for (const yearMonth of timelineRestData.buckets.keys()) { - const [year, month] = yearMonth.split('-', 2); + const [year, month] = yearMonth.split('-'); yearMonths.push(`${year}-${Number(month)}`); } }); @@ -304,7 +304,7 @@ test.describe('Timeline', () => { await page.keyboard.down('Shift'); await thumbnailUtils.withAssetId(page, assets[2].id).hover(); await expect( - thumbnailUtils.locator(page).locator('.absolute.top-0.size-full.bg-immich-primary.opacity-40'), + thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'), ).toHaveCount(3); await thumbnailUtils.selectButton(page, assets[2].id).click(); await page.keyboard.up('Shift'); @@ -349,7 +349,7 @@ test.describe('Timeline', () => { expect(visibleMockAssetsYearMonths).toContain(month); } }); - test.skip('Deep link to last photo, scroll up', async ({ page }) => { + test('Deep link to last photo, scroll up', async ({ page }) => { const lastAsset = assets.at(-1)!; await pageUtils.deepLinkPhotosPage(page, lastAsset.id); @@ -361,7 +361,7 @@ test.describe('Timeline', () => { await thumbnailUtils.expectInViewport(page, '14e5901f-fd7f-40c0-b186-4d7e7fc67968'); }); - test.skip('Deep link to first bucket, scroll down', async ({ page }) => { + test('Deep link to first bucket, scroll down', async ({ page }) => { const lastAsset = assets.at(0)!; await pageUtils.deepLinkPhotosPage(page, lastAsset.id); await timelineUtils.locator(page).hover(); @@ -440,7 +440,7 @@ test.describe('Timeline', () => { await thumbnailUtils.expectInViewport(page, asset.id); await thumbnailUtils.expectSelectedDisabled(page, asset.id); }); - test.skip('Add photos to album', async ({ page }) => { + test('Add photos to album', async ({ page }) => { const album = timelineRestData.album; await pageUtils.openAlbumPage(page, album.id); await page.locator('nav button[aria-label="Add photos"]').click(); @@ -536,7 +536,7 @@ test.describe('Timeline', () => { force: false, ids: [assetToTrash.id], }); - await page.locator('#control-bar').getByLabel('Close').click(); + await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); await page.getByText('Trash', { exact: true }).click(); await timelineUtils.waitForTimelineLoad(page); await thumbnailUtils.expectInViewport(page, assetToTrash.id); @@ -676,7 +676,7 @@ test.describe('Timeline', () => { ids: [assetToArchive.id], }); await thumbnailUtils.expectThumbnailIsArchive(page, assetToArchive.id); - await page.locator('#control-bar').getByLabel('Close').click(); + await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); await page.getByRole('link').getByText('Archive').click(); await timelineUtils.waitForTimelineLoad(page); await thumbnailUtils.expectInViewport(page, assetToArchive.id); @@ -752,7 +752,7 @@ test.describe('Timeline', () => { await page.getByText('Photos', { exact: true }).click(); await thumbnailUtils.expectInViewport(page, assetToFavorite.id); }); - test.skip('open /favorites, archive photo, unarchive photo', async ({ page }) => { + test('open /favorites, archive photo, unarchive photo', async ({ page }) => { await pageUtils.openFavorites(page); const assetToArchive = getAsset(timelineRestData, 'ad31e29f-2069-4574-b9a9-ad86523c92cb')!; await thumbnailUtils.withAssetId(page, assetToArchive.id).hover(); @@ -823,7 +823,7 @@ test.describe('Timeline', () => { }); // ensure thumbnail still exists and has favorite icon await thumbnailUtils.expectThumbnailIsFavorite(page, assetToFavorite.id); - await page.locator('#control-bar').getByLabel('Close').click(); + await page.locator('#asset-selection-app-bar').getByLabel('Close').click(); await page.getByRole('link').getByText('Favorites').click(); await timelineUtils.waitForTimelineLoad(page); await pageUtils.goToAsset(page, assetToFavorite.fileCreatedAt); diff --git a/e2e/src/ui/specs/timeline/utils.ts b/e2e/src/ui/specs/timeline/utils.ts index 986f794fb6..e67229d3c9 100644 --- a/e2e/src/ui/specs/timeline/utils.ts +++ b/e2e/src/ui/specs/timeline/utils.ts @@ -1,4 +1,4 @@ -import { expect, Page } from '@playwright/test'; +import { BrowserContext, expect, Page } from '@playwright/test'; import { DateTime } from 'luxon'; import { TimelineAssetConfig } from 'src/ui/generators/timeline'; @@ -7,10 +7,22 @@ export const sleep = (ms: number) => { }; export const padYearMonth = (yearMonth: string) => { - const [year, month] = yearMonth.split('-', 2); + const [year, month] = yearMonth.split('-'); return `${year}-${month.padStart(2, '0')}`; }; +export async function throttlePage(context: BrowserContext, page: Page) { + const session = await context.newCDPSession(page); + await session.send('Network.emulateNetworkConditions', { + offline: false, + downloadThroughput: (1.5 * 1024 * 1024) / 8, + uploadThroughput: (750 * 1024) / 8, + latency: 40, + connectionType: 'cellular3g', + }); + await session.send('Emulation.setCPUThrottlingRate', { rate: 10 }); +} + export const poll = async ( page: Page, query: () => Promise, @@ -60,7 +72,6 @@ export const thumbnailUtils = { }, async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) { const assetIds: string[] = []; - // eslint-disable-next-line unicorn/no-this-outside-of-class for (const thumb of await this.locator(page).all()) { const box = await thumb.boundingBox(); if (box) { @@ -140,7 +151,6 @@ export const timelineUtils = { page.evaluate(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return document.querySelector('#asset-grid').scrollTop; }); await expect.poll(queryTop).toBeGreaterThan(0); @@ -167,7 +177,6 @@ export const assetViewerUtils = { page.evaluate(() => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - // eslint-disable-next-line unicorn/no-optional-chaining-on-undeclared-variable return document.activeElement?.dataset?.asset; }); await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId); @@ -211,7 +220,7 @@ export const pageUtils = { await section.locator('.w-8').click(); }, async pauseTestDebug() { - console.log('NOTE: pausing test indefinitely for debug'); + console.log('NOTE: pausing test indefinately for debug'); await new Promise(() => void 0); }, }; diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 131297bb89..4d44d99e2f 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -1,9 +1,9 @@ -/* eslint-disable unicorn/no-top-level-assignment-in-function */ import { AssetMediaCreateDto, AssetMediaResponseDto, AssetResponseDto, AssetVisibility, + CheckExistingAssetsDto, CreateAlbumDto, CreateLibraryDto, JobCreateDto, @@ -20,6 +20,7 @@ import { UserAdminCreateDto, UserPreferencesUpdateDto, ValidateLibraryDto, + checkExistingAssets, createAlbum, createApiKey, createJob, @@ -91,7 +92,7 @@ export const tempDir = tmpdir(); export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` }); export const asKeyAuth = (key: string) => ({ 'x-api-key': key }); export const immichCli = (args: string[]) => - executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../packages/cli' }).promise; + executeCommand('pnpm', ['exec', 'immich', '-d', `/${tempDir}/immich/`, ...args], { cwd: '../cli' }).promise; export const dockerExec = (args: string[]) => executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', args.join(' ')]); export const immichAdmin = (args: string[]) => dockerExec([`immich-admin ${args.join(' ')}`]); @@ -178,14 +179,12 @@ export const utils = { resetDatabase: async (tables?: string[]) => { client = await utils.connectDatabase(); - tables ||= [ + tables = tables || [ // TODO e2e test for deleting a stack, since it is quite complex 'stack', 'library', 'shared_link', 'person', - 'person_group', - 'cluster_group', 'album', 'asset', 'asset_face', @@ -195,7 +194,6 @@ export const utils = { 'user', 'system_metadata', 'tag', - 'integrity_report', ]; const truncateTables = tables.filter((table) => table !== 'system_metadata'); @@ -307,7 +305,7 @@ export const utils = { }, adminSetup: async (options?: AdminSetupOptions) => { - options ||= { onboarding: true }; + options = options || { onboarding: true }; await signUpAdmin({ signUpDto: signupDto.admin }); const response = await login({ loginCredentialDto: loginDto.admin }); @@ -345,6 +343,8 @@ export const utils = { }, ) => { const _dto = { + deviceAssetId: 'test-1', + deviceId: 'test', fileCreatedAt: new Date().toISOString(), fileModifiedAt: new Date().toISOString(), ...dto, @@ -375,6 +375,40 @@ export const utils = { return body as AssetMediaResponseDto; }, + replaceAsset: async ( + accessToken: string, + assetId: string, + dto?: Partial> & { assetData?: FileData }, + ) => { + const _dto = { + deviceAssetId: 'test-1', + deviceId: 'test', + fileCreatedAt: new Date().toISOString(), + fileModifiedAt: new Date().toISOString(), + ...dto, + }; + + const assetData = dto?.assetData?.bytes || makeRandomImage(); + const filename = dto?.assetData?.filename || 'example.png'; + + if (dto?.assetData?.bytes) { + console.log(`Uploading ${filename}`); + } + + const builder = request(app) + .put(`/assets/${assetId}/original`) + .attach('assetData', assetData, filename) + .set('Authorization', `Bearer ${accessToken}`); + + for (const [key, value] of Object.entries(_dto)) { + void builder.field(key, String(value)); + } + + const { body } = await builder; + + return body as AssetMediaResponseDto; + }, + createImageFile: (path: string) => { if (!existsSync(dirname(path))) { mkdirSync(dirname(path), { recursive: true }); @@ -416,6 +450,9 @@ export const utils = { getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }), + checkExistingAssets: (accessToken: string, checkExistingAssetsDto: CheckExistingAssetsDto) => + checkExistingAssets({ checkExistingAssetsDto }, { headers: asBearerAuth(accessToken) }), + searchAssets: async (accessToken: string, dto: MetadataSearchDto) => { return searchAssets({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) }); }, @@ -436,12 +473,12 @@ export const utils = { return person; }, - createFace: async ({ assetId, personGroupId }: { assetId: string; personGroupId: string }) => { + createFace: async ({ assetId, personId }: { assetId: string; personId: string }) => { if (!client) { return; } - await client.query('INSERT INTO asset_face ("assetId", "personGroupId") VALUES ($1, $2)', [assetId, personGroupId]); + await client.query('INSERT INTO asset_face ("assetId", "personId") VALUES ($1, $2)', [assetId, personId]); }, setPersonThumbnail: async (personId: string) => { @@ -449,9 +486,7 @@ export const utils = { return; } - await client.query(`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "personGroupId" = $1`, [ - personId, - ]); + await client.query(`UPDATE "person" set "thumbnailPath" = '/my/awesome/thumbnail.jpg' where "id" = $1`, [personId]); }, createSharedLink: (accessToken: string, dto: SharedLinkCreateDto) => @@ -550,7 +585,6 @@ export const utils = { { headers: asBearerAuth(accessToken), fetch: (...args: Parameters) => - // eslint-disable-next-line unicorn/no-invalid-argument-count, unicorn/prefer-await fetch(...args).then((response) => { setCookie = response.headers.getSetCookie(); return response; @@ -566,61 +600,15 @@ export const utils = { mkdirSync(`${testAssetDir}/temp`, { recursive: true }); }, - putFile(source: string, dest: string) { - return executeCommand('docker', ['cp', source, `immich-e2e-server:${dest}`]).promise; - }, - - async putTextFile(contents: string, dest: string) { - const dir = await mkdtemp(join(tmpdir(), 'test-')); - const fn = join(dir, 'file'); - await pipeline(Readable.from(contents), createWriteStream(fn)); - return executeCommand('docker', ['cp', fn, `immich-e2e-server:${dest}`]).promise; - }, - async move(source: string, dest: string) { return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise; }, - async copyFolder(source: string, dest: string) { - return executeCommand('docker', ['exec', 'immich-e2e-server', 'cp', '-r', source, dest]).promise; - }, - - async deleteFile(path: string) { - return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', path]).promise; - }, - - async deleteFolder(path: string) { - return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', '-r', path]).promise; - }, - - async truncateFolder(path: string) { - return executeCommand('docker', [ - 'exec', - 'immich-e2e-server', - 'find', - path, - '-type', - 'f', - '-exec', - 'truncate', - '-s', - '1', - '{}', - ';', - ]).promise; - }, - - async mkFolder(path: string) { - return executeCommand('docker', ['exec', 'immich-e2e-server', 'mkdir', '-p', path]).promise; - }, - createBackup: async (accessToken: string) => { await utils.createJob(accessToken, { name: ManualJobName.BackupDatabase, }); - await utils.waitForQueueFinish(accessToken, 'backupDatabase'); - return utils.poll( () => request(app).get('/admin/database-backups').set('Authorization', `Bearer ${accessToken}`), ({ status, body }) => status === 200 && body.backups.length === 1, @@ -630,8 +618,10 @@ export const utils = { resetBackups: async (accessToken: string) => { const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) }); + + const backupFiles = backups.map((b) => b.filename); await deleteDatabaseBackup( - { databaseBackupDeleteDto: { backups: backups.map((dto) => dto.filename) } }, + { databaseBackupDeleteDto: { backups: backupFiles } }, { headers: asBearerAuth(accessToken) }, ); }, @@ -651,7 +641,7 @@ export const utils = { resetAdminConfig: async (accessToken: string) => { const defaultConfig = await getConfigDefaults({ headers: asBearerAuth(accessToken) }); - await updateConfig({ adminConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) }); + await updateConfig({ systemConfigDto: defaultConfig }, { headers: asBearerAuth(accessToken) }); }, isQueueEmpty: async (accessToken: string, queue: keyof QueuesResponseLegacyDto) => { @@ -679,9 +669,9 @@ export const utils = { }, cliLogin: async (accessToken: string) => { - const { secret } = await utils.createApiKey(accessToken, [Permission.All]); - await immichCli(['login', app, secret]); - return secret; + const key = await utils.createApiKey(accessToken, [Permission.All]); + await immichCli(['login', app, `${key.secret}`]); + return key.secret; }, scan: async (accessToken: string, id: string) => { @@ -712,7 +702,6 @@ export const utils = { }, }; -// eslint-disable-next-line unicorn/no-top-level-side-effects utils.initSdk(); if (!existsSync(`${testAssetDir}/albums`)) { diff --git a/e2e/test-assets b/e2e/test-assets index 6742055402..163c251744 160000 --- a/e2e/test-assets +++ b/e2e/test-assets @@ -1 +1 @@ -Subproject commit 6742055402de1aa48f93d12ded7d18f4057f9d1f +Subproject commit 163c251744e0a35d7ecfd02682452043f149fc2b diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index eebe91e90c..61eefdac07 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -10,7 +10,6 @@ "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "target": "es2023", - "lib": ["dom", "ESNext"], "sourceMap": true, "outDir": "./dist", "incremental": true, diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts index 1f617481e4..17ece152d7 100644 --- a/e2e/vitest.config.ts +++ b/e2e/vitest.config.ts @@ -1,3 +1,4 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig } from 'vitest/config'; const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true'; @@ -23,7 +24,5 @@ export default defineConfig({ maxWorkers: 1, isolate: false, }, - resolve: { - tsconfigPaths: true, - }, + plugins: [tsconfigPaths()], }); diff --git a/e2e/vitest.maintenance.config.ts b/e2e/vitest.maintenance.config.ts index 908133f955..a6e96ccc0a 100644 --- a/e2e/vitest.maintenance.config.ts +++ b/e2e/vitest.maintenance.config.ts @@ -1,3 +1,4 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; import { defineConfig } from 'vitest/config'; const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true'; @@ -23,7 +24,5 @@ export default defineConfig({ maxWorkers: 1, isolate: false, }, - resolve: { - tsconfigPaths: true, - }, + plugins: [tsconfigPaths()], }); diff --git a/.prettierrc b/i18n/.prettierrc similarity index 100% rename from .prettierrc rename to i18n/.prettierrc diff --git a/i18n/af.json b/i18n/af.json index 0f104a0e64..95919bb1dc 100644 --- a/i18n/af.json +++ b/i18n/af.json @@ -5,35 +5,31 @@ "acknowledge": "Neem kennis", "action": "Aksie", "action_common_update": "Werk by", - "action_description": "’n Stel van aksies om op die gefiltreerde bates uit te voer", "actions": "Aksies", "active": "Aktief", - "active_count": "Aktief: {count}", "activity": "Aktiwiteite", + "activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}", "add": "Voeg toe", "add_a_description": "Voeg ’n beskrywing toe", "add_a_location": "Voeg ’n ligging toe", "add_a_name": "Voeg ’n naam toe", "add_a_title": "Voeg ’n titel toe", - "add_action": "Voeg aksie toe", - "add_assets": "Voeg bates by", "add_birthday": "Voeg ’n verjaarsdag toe", "add_endpoint": "Voeg eindpunt toe", "add_exclusion_pattern": "Voeg uitsluitingspatroon toe", "add_location": "Voeg ligging toe", + "add_more_users": "Voeg meer gebruikers toe", "add_partner": "Voeg vennoot toe", + "add_path": "Voeg pad toe", "add_photos": "Voeg foto’s toe", - "add_step": "Voeg stap by", "add_tag": "Voeg etiket toe", "add_to": "Voeg toe totâ€Ļ", "add_to_album": "Voeg toe tot album", "add_to_album_bottom_sheet_added": "Tot {album} toegevoeg", "add_to_album_bottom_sheet_already_exists": "Reeds in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Sommige plaaslike bates kon nie toe gevoeg word tot die album nie", "add_to_albums": "Voeg toe tot albums", "add_to_albums_count": "Voeg toe tot albums ({count})", - "add_to_bottom_bar": "Voeg toe", - "add_upload_to_stack": "Voeg oplaai by stapel", + "add_to_shared_album": "Voeg toe tot gedeelde album", "add_url": "Voeg bronadres toe", "added_to_archive": "Tot argief toegevoeg", "added_to_favorites": "Tot gunstelinge toegevoeg", @@ -50,8 +46,6 @@ "backup_database": "Skep DatabasisstortlÃĒer", "backup_database_enable_description": "Aktiveer databasisstortlÃĒers", "backup_keep_last_amount": "Aantal vorige stortlÃĒers om te hou", - "backup_onboarding_1_description": "kopie in die wolk of op 'n ander fisiese plek.", - "backup_onboarding_2_description": "plaaslike kopieÃĢ op verskillende toestelle. Dit sluit die hooflÃĒers en ’n rugsteun van daardie lÃĒers plaaslik in.", "backup_onboarding_3_description": "totale kopieÃĢ van u data, insluitend die oorspronklike lÃĒers. Dit sluit 1 kopie op ’n ander perseel en 2 lokale kopieÃĢ in.", "backup_onboarding_description": "’n 3-2-1-rugsteunstrategie word sterk aanbeveel om u data veilig te hou. Hou kopieÃĢ van u foto’s/video’s sowel as die Immich-databasis vir ’n volledige rugsteunoplossing.", "backup_onboarding_footer": "Lees hierdie dokument vir meer inligting oor hoe om ’n rugsteunkopie van Immich te maak.", @@ -67,17 +61,13 @@ "confirm_reprocess_all_faces": "Is u seker u wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.", "confirm_user_password_reset": "Is u seker u wil {user} se wagwoord terugstel?", "confirm_user_pin_code_reset": "Is u seker u wil {user} se PIN-kode herstel?", - "copy_config_to_clipboard_description": "Kopieer die huidige stelselkonfigurasie as ’n JSON‑objek na die klipbord", "create_job": "Skep taak", "cron_expression": "Cron-uitdrukking", "cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Kyk gerus na bv. Crontab Guru vir meer inligting", "cron_expression_presets": "Cron-uitdrukking voorafinstellings", "disable_login": "Deaktiveer aantekening", - "download_csv": "Laai CSV af", "duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek", "exclusion_pattern_description": "Met uitsluitingspatrone kan u lÃĒers en vouers ignoreer wanneer u u biblioteek skandeer. Dit is nuttig as u vouers het wat lÃĒers bevat wat u nie wil invoer nie, soos RAW-lÃĒers.", - "export_config_as_json_description": "Laai die huidige stelselkonfigurasie af as ’n JSON‑lÃĒer", - "external_libraries_page_description": "Admin eksterne biblioteekbladsy", "face_detection": "Gesigherkenning", "face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s word slegs die duimnael oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas items in die ry wat nog nie verwerk is nie. Geïdentifiseerde gesigte sal nÃĄ voltooiing van Gesigidentifikasie vir Gesigherkenning in die ry geplaas word om hulle in bestaande of nuwe persone te groepeer.", "facial_recognition_job_description": "Groepeer gesigte in mense. Die stap is vinniger nadat Gesigherkenning klaar is. “Herstel” (her-)groepeer alle gesigte. “Vermiste” plaas gesigte in ry wat nie ’n persoon gekoppel het nie.", @@ -87,18 +77,12 @@ "image_format_description": "WebP lewer kleiner lÃĒers as JPEG, maar is stadiger om te enkodeer.", "image_fullsize_description": "Volgrootte prent met geen metadata, gebruik wanner ingezoem", "image_fullsize_enabled": "Aktiveer spek van volgrootte prent", - "image_fullsize_enabled_description": "Genereer 'n volformaat-beeld vir formate wat nie webvriendelik is nie. Wanneer \"Verkies ingebedde voorskou\" geaktiveer is, word ingebedde voorskoue direk gebruik sonder omskakeling. Dit het geen invloed op webvriendelike formate soos JPEG nie.", - "image_fullsize_quality_description": "Beeldgehalte op volle grootte van 1 tot 100. HoÃĢr is beter, maar lewer groter lÃĒers op.", - "image_fullsize_title": "Instellings vir volformaat-beeld", "image_prefer_embedded_preview": "Verkies ingebedde voorskou", - "image_prefer_embedded_preview_setting_description": "Gebruik ingebedde voorskoue in RAW-foto's as invoer vir beeldverwerking wanneer dit beskikbaar is. Dit kan vir sommige beelde akkurater kleure lewer, maar die gehalte van die voorskou hang van die kamera af en die beeld kan meer kompressie-artefakte vertoon.", "image_prefer_wide_gamut": "Verkies breÃĢspektrum", "image_prefer_wide_gamut_setting_description": "Gebruik Display P3 vir duimnaels. Dit behou die lewendheid van beelde met wye kleurruimtes beter, maar beelde kan anders verskyn op ou toestelle met ’n ou blaaierweergawe. sRGB-beelde gebruik steeds sRGB om kleurverskuiwings te voorkom.", "image_preview_description": "Mediumgrootte prent met gestroopte metadata, wat gebruik word wanneer ’n enkele item bekyk word en vir masjienleer", "image_preview_quality_description": "Voorskoukwaliteit van 1-100. HoÃĢr is beter, maar lewer groter lÃĒers en kan die toep vertraag. Die stel van ’n lae waarde kan masjienleerkwaliteit beïnvloed.", "image_preview_title": "Voorskou-instellings", - "image_progressive": "Progresiewe", - "image_progressive_description": "Kodeer JPEG-beelde progressief vir geleidelike laai en vertoning. Dit het geen uitwerking op WebP-beelde nie.", "image_quality": "Kwaliteit", "image_resolution": "Resolusie", "image_resolution_description": "HoÃĢr resolusies kan meer detail bewaar, maar neem langer om te enkodeer, het groter lÃĒergroottes en kan die toep vertraag.", @@ -107,22 +91,6 @@ "image_thumbnail_description": "Klein duimnaels sonder metadata, gebruik om groepe foto’s soos die tydlyn te bekyk", "image_thumbnail_quality_description": "Duinmaelkwaliteit van 1-100. HoÃĢr is beter, maar lewer groter lÃĒers en kan die toep vertraag.", "image_thumbnail_title": "Duimnaelinstellings", - "import_config_from_json_description": "Voer stelselkonfigurasie in deur 'n JSON-konfigurasielÃĒer op te laai", - "integrity_checks_checksum_files": "kontrolesomlÃĒer", - "integrity_checks_checksum_files_description": "Konfigureer die kontrolesom-kontrole", - "integrity_checks_checksum_files_enable_description": "Aktiveer die kontrolesom-kontrolering", - "integrity_checks_checksum_files_percentage_limit": "Persentasie limiet", - "integrity_checks_checksum_files_percentage_limit_description": "Konfigureer die maksimum persentasie tussen 0.01 en 1 vir die mate waarin die kontrolesom-kontrole tydens elke interval uitgevoer moet word.", - "integrity_checks_checksum_files_time_limit": "Tyd limiet", - "integrity_checks_checksum_files_time_limit_description": "Konfigureer die maksimum duur waarvoor die kontrolesom-kontrole tydens elke interval moet loop. (ms)", - "integrity_checks_missing_files": "Ontbrekende lÃĒers", - "integrity_checks_missing_files_description": "Konfigureer die frekwensie en aktiveer of deaktiveer die kontrole vir ontbrekende lÃĒers", - "integrity_checks_missing_files_enable_description": "Aktiveer die kontrole vir ontbrekende lÃĒers", - "integrity_checks_settings": "integriteitskontroles", - "integrity_checks_settings_description": "Bestuur tussenposes vir integriteitskontroles", - "integrity_checks_untracked_files": "Ongevokgde lÃĒers", - "integrity_checks_untracked_files_description": "Konfigureer die frekwensie en aktiveer of deaktiveer die kontrole vir ongevolgde lÃĒers", - "integrity_checks_untracked_files_enable_description": "Aktiveer die kontrole van ongevolgde lÃĒers", "job_concurrency": "{job} gelyktydigheid", "job_created": "Taak geskep", "job_not_concurrency_safe": "Hierdie taak kan nie gelyktydig uitgevoer word nie.", @@ -161,6 +129,7 @@ "advanced": "Gevorderd", "albums": "Albums", "all": "Alle", + "anti_clockwise": "Linksom", "archive": "Argief", "asset_skipped": "Oorgeslaan", "asset_uploaded": "Opgelaai", @@ -172,6 +141,7 @@ "camera": "Kamera", "cancel": "Kanselleer", "city": "Stad", + "clockwise": "Regsom", "close": "Sluit", "color": "Kleur", "confirm": "Bevestig", @@ -212,12 +182,14 @@ "unsaved_change": "Onbewaarde verandering", "unselect_all": "Ontkies alles", "unselect_all_duplicates": "Ontkies alle duplikate", + "unselect_all_in": "Ontkies alles in {group}", "unstack": "Ontstapel", "unstack_action_prompt": "{count} ongestapel", "unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapel", "unsupported_field_type": "Onondersteunde veldtipe", "unsupported_file_type": "LÃĒer {file} kan nie opgelaai word nie omdat die lÃĒertipe {type} nie ondersteun word nie.", "untagged": "Sonder etiket", + "untitled_workflow": "Naamlose werkvloei", "up_next": "Volgende", "update_location_action_prompt": "Werk die ligging van {count} gekose items by met:", "updated_at": "Bygewerk", @@ -225,6 +197,8 @@ "upload": "Laai op", "upload_concurrency": "Aantal gelyktydige oplaaie", "upload_details": "Oplaaidetails", + "upload_dialog_info": "Wil u ’n rugsteun maak van die gekose item(s) op die bediener?", + "upload_dialog_title": "Laai item op", "upload_error_with_count": "Oplaaifout vir {count, plural, one {# item} other {# items}}", "upload_errors": "Oplaai voltooi met {count, plural, one {# fout} other {# foute}}, verfris die blad om die nuwe items te sien.", "upload_finished": "Klaar opgelaai", @@ -253,6 +227,7 @@ "user_privacy": "Gebruikersprivaatheid", "user_purchase_settings": "Koop", "user_purchase_settings_description": "Bestuur u aankoop", + "user_role_set": "Stel {user} in as {role}", "user_usage_detail": "Gedetailleerde gebruik van gebruikers", "user_usage_stats": "Statistieke vir rekeninggebruik", "user_usage_stats_description": "Bekyk statistieke van rekeninggebruik", @@ -262,6 +237,7 @@ "utilities": "Gereedskap", "validate": "Valideer", "validate_endpoint_error": "Voer asb. ’n geldige bronadres in", + "validation_error": "Valideerfout", "variables": "Veranderlikes", "version": "Weergawe", "version_announcement_closing": "Jou friend, Alex", @@ -273,6 +249,7 @@ "video_hover_setting_description": "Speel videoduimnael wanneer muis oor item hang. Selfs indien gedeaktiveer kan afspeel begin deur oor die afspeelknop te hang.", "videos": "Video’s", "videos_count": "{count, plural, one {# video} other {# video’s}}", + "videos_only": "Slegs video’s", "view": "Bekyk", "view_album": "Bekyk album", "view_all": "Bekyk alle", @@ -281,16 +258,21 @@ "view_details": "Bekyk detail", "view_in_timeline": "Bekyk in tydlyn", "view_link": "Bekyk skakel", + "view_links": "Bekyk skakels", "view_name": "Bekyk", "view_next_asset": "Bekyk volgende item", "view_previous_asset": "Bekyk vorige item", "view_qr_code": "Bekyk QR-kode", "view_similar_photos": "Bekyk soortgelyke foto’s", "view_stack": "Bekyk stapel", + "view_user": "Bekyk gebruiker", "viewer_remove_from_stack": "Verwyder van stapel", + "viewer_stack_use_as_main_asset": "Gebruik as hoofitem", + "viewer_unstack": "Ontstapel", "visibility": "Sigbaarheid", "visibility_changed": "Sigbaarheid verander vir {count, plural, one {# mens} other {# mense}}", "visual": "Visueel", + "visual_builder": "Visuele bouer", "waiting": "Wag", "waiting_count": "Wagtend: {count}", "warning": "Waarskuwing", @@ -309,6 +291,7 @@ "workflow_navigation_prompt": "Is u seker u wil verlaat sonder om u veranderinge te bewaar?", "workflow_summary": "Werkvloei-opsomming", "workflow_update_success": "Werkvloei suksesvol bygewerk", + "workflow_updated": "Werkvloei bygewerk", "workflows": "Werkvloeie", "workflows_help_text": "Werkvloeie outomatiseer aksies op u items gebaseer op snellers en filters", "wrong_pin_code": "Verkeerde PIN-kode", @@ -318,5 +301,6 @@ "you_dont_have_any_shared_links": "U het geen gedeelde skakels nie", "your_wifi_name": "U Wi-Fi-naam", "zero_to_clear_rating": "druk 0 om itemgradering te wis", - "zoom_image": "Zoem in" + "zoom_image": "Zoem in", + "zoom_to_bounds": "Zoem na rande" } diff --git a/i18n/ar.json b/i18n/ar.json index 84a0628066..c6899c61fc 100644 --- a/i18n/ar.json +++ b/i18n/ar.json @@ -1,40 +1,48 @@ { "about": "Ø­ŲˆŲ„", - "account": "Ø§Ų„Ø­ØŗØ§Ø¨", + "account": "Ø­ØŗØ§Ø¨", "account_settings": "ØĨؚداداØĒ Ø§Ų„Ø­ØŗØ§Ø¨", "acknowledge": "ØŖŲØ¯ØąŲƒ Ø°Ų„Ųƒ", - "action": "Ø§Ų„ØĨØŦØąØ§ØĄ", + "action": "ØšŲ…Ų„ŲŠØŠ", "action_common_update": "ØĒØ­Ø¯ŲŠØĢ", - "action_description": "Ų…ØŦŲ…ŲˆØšØŠ ØĨØŦØąØ§ØĄØ§ØĒ Ų„ØĒŲ†ŲŲŠØ°Ų‡Ø§ ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…ØĩŲØ§ØŠ", + "action_description": "Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† Ø§Ų„ŲØšØ§Ų„ŲŠØ§ØĒ Ø§Ų„ØĒ؊ ØŗØĒŲ†ŲØ° ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØĒ؊ ØĒŲ… ØĒØĩ؁؊ØĒŲ‡Ø§", "actions": "ØšŲ…Ų„ŲŠØ§ØĒ", "active": "Ų†Ø´Øˇ", - "active_count": "Ų†Ø´Øˇ: {count}", + "active_count": "ŲØšØ§Ų„: {count}", "activity": "Ų†Ø´Ø§Øˇ", + "activity_changed": "Ø§Ų„Ų†Ø´Ø§Øˇ {enabled, select, true {Ų…ŲŲŲ’ØšŲ„} other {Ų…ØšØˇŲ‘Ų„}}", "add": "ØĨØļØ§ŲØŠ", "add_a_description": "ØĨØļØ§ŲØŠ ؈Øĩ؁", "add_a_location": "ØĨØļØ§ŲØŠ Ų…ŲˆŲ‚Øš", "add_a_name": "ØĨØļØ§ŲØŠ ØĨØŗŲ…", "add_a_title": "ØĨØļØ§ŲØŠ ØšŲ†ŲˆØ§Ų†", "add_action": "اØļ؁ ŲØšØ§Ų„ŲŠØŠ", + "add_action_description": "اØļØēØˇ Ų„ØĨØļØ§ŲØŠ ŲØšØ§Ų„ŲŠØŠ Ų„ØĒŲ†ŲŲŠØ°Ų‡Ø§", "add_assets": "اØļ؁ اØĩŲˆŲ„", "add_birthday": "ØŖØļ؁ ØĒØ§ØąŲŠØŽ Ø§Ų„Ų…ŲŠŲ„Ø§Ø¯", "add_endpoint": "اØļ؁ Ų†Ų‚ØˇØŠ Ų†Ų‡Ø§ŲŠØŠ", "add_exclusion_pattern": "ØĨØļØ§ŲØŠ Ų†Ų…Øˇ ØĨØŗØĒØĢŲ†Ø§ØĄ", + "add_filter": "اØļ؁ ØĒØĩŲŲŠØŠ", + "add_filter_description": "اØļØēØˇ Ų„Ø§ØļØ§ŲØŠ Ø´ØąØˇ ØĒØĩŲŲŠØŠ", "add_location": "ØĨØļØ§ŲØŠ Ų…ŲˆŲ‚Øš", + "add_more_users": "ØĨØļØ§ŲØŠ Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† ØĸØŽØąŲŠŲ†", "add_partner": "ØŖØļ؁ Ø´ØąŲŠŲƒŲ‹Ø§", + "add_path": "ØĨØļØ§ŲØŠ Ų…ØŗØ§Øą", "add_photos": "ØĨØļØ§ŲØŠ ØĩŲˆØą", - "add_step": "اØļ؁ ØŽØˇŲˆØŠ", "add_tag": "اØļ؁ ØšŲ„Ø§Ų…ØŠ", "add_to": "ØĨØļØ§ŲØŠ ØĨŲ„Ų‰â€Ļ", "add_to_album": "ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ ØŖŲ„Ø¨ŲˆŲ…", "add_to_album_bottom_sheet_added": "ØĒŲ…ØĒ Ø§Ų„Ø§ØļØ§ŲØŠ Ø§Ų„Ų‰ {album}", "add_to_album_bottom_sheet_already_exists": "Ų…ŲˆØŦŲˆØ¯ Ų…ØŗØ¨Ų‚Ø§ ؁؊ {album}", "add_to_album_bottom_sheet_some_local_assets": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØļØ§ŲØŠ بؚØļ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "add_to_album_toggle": "ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„ØĒØ­Ø¯ŲŠØ¯ Ų„Ų€{album}", "add_to_albums": "ØĨØļØ§ŲØŠ Ø§Ų„Ų‰ Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "add_to_albums_count": "ØĨØļØ§ŲŲ‡ ØĨŲ„Ų‰ Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ ({count})", "add_to_bottom_bar": "اØļØ§ŲŲ‡ Ø§Ų„Ų‰", + "add_to_shared_album": "ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ ØŖŲ„Ø¨ŲˆŲ… Ų…Ø´Ø§ØąŲƒ", "add_upload_to_stack": "اØļ؁ ØąŲØš Ø§Ų„Ų‰ Ø­Ø˛Ų…ØŠ", "add_url": "ØĨØļØ§ŲØŠ ØąØ§Ø¨Øˇ", + "add_workflow_step": "اØļ؁ ØŽØˇŲˆØŠ ØŗŲŠØą ØšŲ…Ų„", "added_to_archive": "ØŖŲØļ؊؁ØĒ Ų„Ų„ØŖØąØ´ŲŠŲ", "added_to_favorites": "ØŖŲØļ؊؁ØĒ ؄؄؅؁ØļŲ„Ø§ØĒ", "added_to_favorites_count": "ØĒŲ… ØĨØļØ§ŲØŠ {count, number} ØĨŲ„Ų‰ Ø§Ų„Ų…ŲØļŲ„Ø§ØĒ", @@ -53,8 +61,8 @@ "backup_onboarding_1_description": "Ų†ØŗØŽØŠ ØŽØ§ØąØŦ Ø§Ų„Ų…ŲˆŲ‚Øš ؁؊ Ų…ŲˆŲ‚Øš ØĸØŽØą.", "backup_onboarding_2_description": "Ų†ØŗØŽ Ų…Ø­Ų„ŲŠØŠ ØšŲ„Ų‰ ØŖØŦŲ‡Ø˛ØŠ Ų…ØŽØĒŲ„ŲØŠ. ŲŠØ´Ų…Ų„ Ø°Ų„Ųƒ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ ŲˆŲ†ØŗØŽØŠ احØĒŲŠØ§ØˇŲŠØŠ Ų…Ø­Ų„ŲŠØŠ Ų…Ų†Ų‡Ø§.", "backup_onboarding_3_description": "ØĨØŦŲ…Ø§Ų„ŲŠ Ų†ŲØŗØŽ Ø¨ŲŠØ§Ų†Ø§ØĒŲƒØŒ Ø¨Ų…Ø§ ؁؊ Ø°Ų„Ųƒ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ØŖØĩŲ„ŲŠØŠ. ŲŠØ´Ų…Ų„ Ø°Ų„Ųƒ Ų†ØŗØŽØŠŲ‹ ŲˆØ§Ø­Ø¯ØŠŲ‹ ØŽØ§ØąØŦ Ø§Ų„Ų…ŲˆŲ‚Øš ŲˆŲ†ØŗØŽØĒŲŠŲ† Ų…Ø­Ų„ŲŠØĒŲŠŲ†.", - "backup_onboarding_description": "ŲŠŲŲ†ØĩØ­ باØĒباؚ Ø§ØŗØĒØąØ§ØĒ؊ØŦŲŠØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ 3-2-1 backup strategy Ų„Ø­Ų…Ø§ŲŠØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ؃. ؊ØŦب ØšŲ„ŲŠŲƒ Ø§Ų„Ø§Ø­ØĒŲØ§Ø¸ Ø¨Ų†ØŗØŽ Ų…Ų† Ø§Ų„ØĩŲˆØą/Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØąŲŲˆØšØŠ Ø¨Ø§Ų„ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ Immich Ų„Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ Ø­Ų„ Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ Ø´Ø§Ų…Ų„.", - "backup_onboarding_footer": "Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø­ŲˆŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų€ Immich، ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Ø§Ų„ŲˆØĢاØĻŲ‚.", + "backup_onboarding_description": "ŲŠŲŲ†ØĩØ­ باØĒباؚ Ø§ØŗØĒØąØ§ØĒ؊ØŦŲŠØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ 3-2-1 Ų„Ø­Ų…Ø§ŲŠØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ؃. احØĒŲØ¸ Ø¨Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠØŠ Ų…Ų† ØĩŲˆØąŲƒ/ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ؃ Ø§Ų„Ų…Ø­Ų…Ų‘Ų„ØŠØŒ Ø¨Ø§Ų„ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ Immich، Ų„ØļŲ…Ø§Ų† Ø­Ų„ Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ Ø´Ø§Ų…Ų„.", + "backup_onboarding_footer": "Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø­ŲˆŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų€ Immich، ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Ø§Ų„ØĒØšŲ„ŲŠŲ…Ø§ØĒ .", "backup_onboarding_parts_title": "؊ØĒØļŲ…Ų† Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ 3-2-1 Ų…Ø§ ŲŠŲ„ŲŠ:", "backup_onboarding_title": "Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠØŠ", "backup_settings": "ØĨؚداداØĒ ØĒŲØąŲŠØē Ų‚Ø§ØšØ¯ØŠ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ", @@ -73,7 +81,6 @@ "cron_expression_description": "اØļØ¨Øˇ Ø§Ų„ŲØ§ØĩŲ„ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ų„Ų„ŲØ­Øĩ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… ØĒŲ†ØŗŲŠŲ‚ cron. Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ ŲŠŲØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Crontab Guru ØšŲ„Ų‰ ØŗØ¨ŲŠŲ„ Ø§Ų„Ų…ØĢØ§Ų„", "cron_expression_presets": "Ø§Ų„ØĨؚداداØĒ Ø§Ų„Ų…ØŗØ¨Ų‚ØŠ Ų„ØĒØšØ¨ŲŠØą Cron", "disable_login": "ØĒØšØˇŲŠŲ„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", - "download_csv": "Ø­Ų…Ų„ CSV", "duplicate_detection_job_description": "Ø¨Ø¯ØĄ Ø§Ų„ØĒØšŲ„Ų… Ø§Ų„ØĸŲ„ŲŠ ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ų„Ų„ØšØĢŲˆØą ØšŲ„Ų‰ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØĒØ´Ø§Ø¨Ų‡ØŠ. ŲŠØšØĒŲ…Ø¯ ØšŲ„Ų‰ Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„Ø°ŲƒŲŠ", "exclusion_pattern_description": "ØĒØĒŲŠØ­ Ų„Ųƒ ØŖŲ†Ų…Ø§Øˇ Ø§Ų„Ø§ØŗØĒبؚاد ØĒØŦØ§Ų‡Ų„ Ø§Ų„Ų…Ų„ŲØ§ØĒ ŲˆØ§Ų„Ų…ØŦŲ„Ø¯Ø§ØĒ ØšŲ†Ø¯ ŲØ­Øĩ Ų…ŲƒØĒبØĒ؃. ŲŠØšØ¯ Ų‡Ø°Ø§ Ų…ŲŲŠØ¯Ų‹Ø§ ØĨذا ŲƒØ§Ų† Ų„Ø¯ŲŠŲƒ Ų…ØŦŲ„Ø¯Ø§ØĒ ØĒØ­ØĒ؈؊ ØšŲ„Ų‰ Ų…Ų„ŲØ§ØĒ Ų„Ø§ ØĒØąŲŠØ¯ Ø§ØŗØĒŲŠØąØ§Ø¯Ų‡Ø§ØŒ Ų…ØĢŲ„ Ų…Ų„ŲØ§ØĒ RAW.", "export_config_as_json_description": "ØĒØ­Ų…ŲŠŲ„ اؚداداØĒ Ø§Ų„Ų†Ø¸Ø§Ų… Ø§Ų„Ø­Ø§Ų„ŲŠØŠ ŲƒŲ…Ų„Ų بØĩ؊ØēØŠ JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ØĒØĒØąØ§ŲˆØ­ ØŦŲˆØ¯ØŠ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ų…Ų† 1 ØĨŲ„Ų‰ 100. ŲƒŲ„Ų…Ø§ ŲƒØ§Ų†ØĒ Ø§Ų„ØŦŲˆØ¯ØŠ ØŖØšŲ„Ų‰ ŲƒØ§Ų† Ø°Ų„Ųƒ ØŖŲØļŲ„ØŒ ŲˆŲ„ŲƒŲ†Ų‡Ø§ ØĒŲ†ØĒØŦ Ų…Ų„ŲØ§ØĒ ØŖŲƒØ¨Øą ŲˆŲ‚Ø¯ ØĒŲ‚Ų„Ų„ Ų…Ų† Ø§ØŗØĒØŦاب؊ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚.", "image_thumbnail_title": "ØĨؚداداØĒ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ", "import_config_from_json_description": "Ø§ØŗØĒŲŠØąØ§Ø¯ اؚداداØĒ Ø§Ų„Ų†Ø¸Ø§Ų… بØĒØ­Ų…ŲŠŲ„ ؅؄؁ اؚداداØĒ بØĩ؊ØēØŠ JSON", - "integrity_checks_checksum_files": "Ų…Ų„ŲØ§ØĒ Ø§Ų„ØŦŲ…Øš Ø§Ų„ØĒØ­Ų‚Ų‚ŲŠ", - "integrity_checks_checksum_files_description": "ØļØ¨Øˇ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŦŲ…Øš Ø§Ų„ØĒØ­Ų‚Ų‚ŲŠ", - "integrity_checks_checksum_files_enable_description": "ØĒŲØšŲŠŲ„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŦŲ…Øš Ø§Ų„ØĒØ­Ų‚Ų‚ŲŠ", - "integrity_checks_checksum_files_percentage_limit": "حد Ø§Ų„Ų†ØŗØ¨Ų‡ Ø§Ų„Ų…ØĻŲˆŲŠŲ‡", - "integrity_checks_checksum_files_percentage_limit_description": "ØļØ¨Øˇ Ø§Ų„Ø­Ø¯ Ø§Ų„Ø§ØšŲ„Ų‰ Ø§Ų„Ų†ØŗØ¨Ų‡ Ø§Ų„Ų…ØĻŲˆŲŠŲ‡ Ø¨ŲŠŲ† 0.01 ؈ 1 Ų„ØĒØ­Ø¯ŲŠØ¯ ŲƒŲ… ؊ØŦب Ø§Ų† ŲŠØšŲ…Ų„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŦŲ…Øš Ø§Ų„ØĒØ­Ų‚Ų‚ŲŠ Ø¨ŲƒŲ„ ؁ØĒØąŲ‡.", - "integrity_checks_checksum_files_time_limit": "Ø§Ų„Ø­Ø¯ Ø§Ų„Ø˛Ų…Ų†ŲŠ", - "integrity_checks_checksum_files_time_limit_description": "Ų‚Ų… بØļØ¨Øˇ Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖŲ‚ØĩŲ‰ Ų„Ų„Ų…Ø¯ØŠ Ø§Ų„ØĒ؊ ؊ØŦب ØŖŲ† ØĒØŗØĒØēØąŲ‚Ų‡Ø§ ØšŲ…Ų„ŲŠØŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„Ų…ØŦŲ…ŲˆØš Ø§Ų„Ø§ØŽØĒØ¨Ø§ØąŲŠ (checksum) ؁؊ ŲƒŲ„ ŲØ§ØĩŲ„ Ø˛Ų…Ų†ŲŠ. (Ø¨Ø§Ų„Ų…Ų„ŲŠ ØĢØ§Ų†ŲŠØŠ)", - "integrity_checks_missing_files": "Ų…Ų„ŲØ§ØĒ Ų…ŲŲ‚ŲˆØ¯ØŠ", - "integrity_checks_missing_files_description": "ØļØ¨Øˇ ؈ØĒŲŠØąØŠ Ø§Ų„ŲØ­Øĩ ؈ØĒŲØšŲŠŲ„ ØŖŲˆ ØĒØšØˇŲŠŲ„ ŲØ­Øĩ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", - "integrity_checks_missing_files_enable_description": "ØĒŲØšŲŠŲ„ ŲØ­Øĩ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", - "integrity_checks_settings": "ŲØ­ŲˆØĩاØĒ Ø§Ų„ØŗŲ„Ø§Ų…Ų‡", - "integrity_checks_settings_description": "ØĨØ¯Ø§ØąØŠ ؁ØĒØąØ§ØĒ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŗŲ„Ø§Ų…ØŠ", - "integrity_checks_untracked_files": "Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ų…ØĒØšŲ‚Ø¨Ų‡", - "integrity_checks_untracked_files_description": "Ų‚Ų… بØļØ¨Øˇ Ø§Ų„ØĒØąØ¯Ø¯ ؈ØĒŲØšŲŠŲ„ ØŖŲˆ ØĒØšØˇŲŠŲ„ ŲØ­Øĩ Ø§Ų„Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ø§Ų„Ų…ØĒØšŲ‚Ø¨ØŠ", - "integrity_checks_untracked_files_enable_description": "Ų‚Ų… بØĒŲ…ŲƒŲŠŲ† ŲØ­Øĩ Ø§Ų„Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ø§Ų„Ų…ØĒØšŲ‚Ø¨ØŠ", "job_concurrency": "ØĒØ˛Ø§Ų…Ų† {job}", "job_created": "ØĒŲ… ØĨŲ†Ø´Ø§ØĄ Ø§Ų„ŲˆØ¸ŲŠŲØŠ", "job_not_concurrency_safe": "Ų‡Ø°Ų‡ Ø§Ų„ŲˆØ¸ŲŠŲØŠ ØēŲŠØą ØĸŲ…Ų†ØŠ Ų„Ų„ØĒØ´ØēŲŠŲ„ Ø§Ų„Ų…ØĒØ˛Ø§Ų…Ų†.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "ØĒŲØšŲŠŲ„ Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„Ø°ŲƒŲŠ", "machine_learning_smart_search_enabled_description": "ØĨذا ØĒŲ… ØĒØšØˇŲŠŲ„Ų‡ØŒ ؁؄؆ ؊ØĒŲ… ØĒØąŲ…ŲŠØ˛ Ø§Ų„ØĩŲˆØą Ų„Ų„Ø¨Ø­ØĢ Ø§Ų„Ø°ŲƒŲŠ.", "machine_learning_url_description": "ØšŲ†ŲˆØ§Ų† URL Ų„ØŽØ§Ø¯Ų… Ø§Ų„ØĒØšŲ„Ų… Ø§Ų„ØĸŲ„ŲŠ. ØĨذا ØĒŲ… ØĒŲˆŲŲŠØą ØŖŲƒØĢØą Ų…Ų† ØšŲ†ŲˆØ§Ų† URL ŲˆØ§Ø­Ø¯ØŒ ØŗŲŠØĒŲ… Ų…Ø­Ø§ŲˆŲ„ØŠ Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨ŲƒŲ„ ØŽØ§Ø¯Ų… ØšŲ„Ų‰ حد؊ Ø­ØĒŲ‰ ŲŠØŗØĒØŦŲŠØ¨ ØŖØ­Ø¯Ų‡Ų… Ø¨Ų†ØŦاح، Ø¨Ø¯ØĄŲ‹Ø§ Ų…Ų† Ø§Ų„ØŖŲˆŲ„ ØĨŲ„Ų‰ Ø§Ų„ØŖØŽŲŠØą. ØŗŲŠØĒŲ… ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØŽŲˆØ§Ø¯Ų… Ø§Ų„ØĒ؊ Ų„Ø§ ØĒØŗØĒØŦŲŠØ¨ Ų…Ø¤Ų‚ØĒŲ‹Ø§ Ø­ØĒŲ‰ ØĒØšŲˆØ¯ Ų„Ų„ØšŲ…Ų„.", - "maintenance_backup_management": "ØĨØ¯Ø§ØąØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "maintenance_delete_backup": "Ø­Ø°Ų Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "maintenance_delete_backup_description": "Ų‡Ø°Ø§ Ø§Ų„Ų…Ų„Ų ØŗŲŠØĒŲ… Ø­Ø°ŲŲ‡ Ø¨Ø´ŲƒŲ„ Ų„Ø§ ØąØŦØšŲ‡ ŲŲŠŲ‡.", "maintenance_delete_error": "ŲØ´Ų„ Ø­Ø°Ų Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ.", - "maintenance_integrity_check": "ØĒØ­Ų‚Ų‚", - "maintenance_integrity_check_all": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„", - "maintenance_integrity_checksum_mismatch": "ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ ØąŲ…Ø˛ Ø§Ų„ØĒØ­Ų‚Ų‚", - "maintenance_integrity_checksum_mismatch_description": "Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ØĒ؊ Ų„Ø§ ؊ØĒØˇØ§Ø¨Ų‚ Ø§Ų„Ų…ØŦŲ…ŲˆØš Ø§Ų„ØĒØ¯Ų‚ŲŠŲ‚ŲŠ Ų„Ų‡Ø§ ØšŲ„Ų‰ Ø§Ų„Ų‚ØąØĩ Ų…Øš Ø§Ų„Ų…ØŦŲ…ŲˆØš Ø§Ų„ØĒØ¯Ų‚ŲŠŲ‚ŲŠ Ø§Ų„Ų…ØŽØ˛Ų† ؁؊ Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ Immich.", - "maintenance_integrity_checksum_mismatch_job": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ Ø§Ų„Ų…ØŦŲ…ŲˆØš Ø§Ų„ØĒØ¯Ų‚ŲŠŲ‚ŲŠ", - "maintenance_integrity_checksum_mismatch_refresh_job": "ØĒØ­Ø¯ŲŠØĢ ØĒŲ‚Ø§ØąŲŠØą ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ Ø§Ų„Ų…ØŦŲ…ŲˆØš Ø§Ų„ØĒØ¯Ų‚ŲŠŲ‚ŲŠ", - "maintenance_integrity_missing_file": "Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", - "maintenance_integrity_missing_file_description": "â€ŒØ§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ØĒ؊ ؊ØĒØĒØ¨ØšŲ‡Ø§ Immich ؁؊ Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒŲ‡ ŲˆŲ„ŲƒŲ†Ų‡Ø§ ØēŲŠØą Ų…ŲˆØŦŲˆØ¯ØŠ ؁؊ Ų†Ø¸Ø§Ų… Ø§Ų„Ų…Ų„ŲØ§ØĒ.", - "maintenance_integrity_missing_file_job": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", - "maintenance_integrity_missing_file_refresh_job": "ØĒØ­Ø¯ŲŠØĢ ØĒŲ‚Ø§ØąŲŠØą Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", - "maintenance_integrity_report": "ØĒŲ‚ØąŲŠØą Ø§Ų„ØŗŲ„Ø§Ų…ØŠ", - "maintenance_integrity_untracked_file": "Ø§Ų„Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ø§Ų„Ų…ØĒØĒبؚ؊", - "maintenance_integrity_untracked_file_description": "Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲˆØŦŲˆØ¯ØŠ ؁؊ Ų…ØŦŲ„Ø¯Ø§ØĒ Immich Ø¨Ø¯ŲˆŲ† ؈ØŦŲˆØ¯ ØŗØŦŲ„Ø§ØĒ Ų„Ų‡Ø§.", - "maintenance_integrity_untracked_file_job": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ø§Ų„Ų…ØĒØĒبؚ؊", - "maintenance_integrity_untracked_file_refresh_job": "ØĒØ­Ø¯ŲŠØĢ ØĒŲ‚Ø§ØąŲŠØą Ø§Ų„Ų…Ų„ŲØ§ØĒ ØēŲŠØą Ø§Ų„Ų…ØĒØĒبؚ؊", "maintenance_restore_backup": "Ø§ØŗØĒؚاد؊ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "maintenance_restore_backup_description": "ØŗŲŠØĒŲ… Ų…ØŗØ­ Ø¨ŲŠØ§Ų†Ø§ØĒ Immich ŲˆØ§ØŗØĒؚادØĒŲ‡Ø§ Ų…Ų† Ø§Ų„Ų†ØŗØŽØŠ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„Ų…ØŽØĒØ§Øą. ØŗŲŠØĒŲ… ØĨŲ†Ø´Ø§ØĄ Ų†ØŗØŽØŠ احØĒŲŠØ§ØˇŲŠØŠ Ų‚Ø¨Ų„ Ø§Ų„Ų…ØĒابؚ؊.", "maintenance_restore_backup_different_version": "Ų‡Ø°Ø§ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ØĒŲ… Ø§Ų†Ø´Ø§ØĻŲ‡ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… اØĩØ¯Ø§Øą Ų…ØŽØĒ؄؁ Ų…Ų† Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ØĒŲØšŲŠŲ„ ØĨØ´ØšØ§ØąØ§ØĒ Ø§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ", "notification_settings": "ØĨؚداداØĒ Ø§Ų„ØĨØ´ØšØ§ØąØ§ØĒ", "notification_settings_description": "ØĨØ¯Ø§ØąØŠ ØĨؚداداØĒ Ø§Ų„ØĨØ´ØšØ§ØąØ§ØĒ، Ø¨Ų…Ø§ ؁؊ Ø°Ų„Ųƒ Ø§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ", - "oauth_allow_insecure_requests": "Ø§Ų„ØŗŲ…Ø§Ø­ Ø¨Ø§Ų„ØˇŲ„Ø¨Ø§ØĒ Ø§Ų„ØēŲŠØą Ø§Ų„ØĸŲ…Ų†ØŠ", - "oauth_allow_insecure_requests_description": "ØĒØ­Ø°ŲŠØą: Ų‡Ø°Ø§ ŲŠØšØˇŲ„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Øĩح؊ Ø´Ų‡Ø§Ø¯ØŠ ØŖŲ…Ų† ØˇØ¨Ų‚ØŠ Ø§Ų„Ų†Ų‚Ų„ Ų„ØˇŲ„Ø¨Ø§ØĒ Ø§Ų„ØĒØąØŽŲŠØĩ Ø§Ų„Ų…ŲØĒŲˆØ­ ŲˆŲ‚Ø¯ ŲŠØšØąØļ؃ Ø§Ų„Ų‡ØŦŲ…Ø§ØĒ Ø§Ų„ŲˆØŗØˇŲŠØŠ.", "oauth_auto_launch": "Ø§Ų„ØĒØ´ØēŲŠŲ„ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊", "oauth_auto_launch_description": "Ø§Ø¨Ø¯ØŖ ØĒØ¯ŲŲ‚ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ OAuth ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§ ØšŲ†Ø¯ Ø§Ų„Ø§Ų†ØĒŲ‚Ø§Ų„ ØĨŲ„Ų‰ ØĩŲØ­ØŠ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", "oauth_auto_register": "Ø§Ų„ØĒØŗØŦŲŠŲ„ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊", @@ -300,11 +274,9 @@ "oauth_button_text": "Ų†Øĩ Ø§Ų„Ø˛Øą", "oauth_client_secret_description": "Ų…ØˇŲ„ŲˆØ¨ Ų„Ų„ØšŲ…ŲŠŲ„ Ø§Ų„ØŗØąŲŠØŒ Ø§Ųˆ اذا PKCE(؅؁ØĒاح Ø§Ų„Ø§ØĢباØĒ Ų„ØĒØ¨Ø§Ø¯Ų„ Ø§Ų„ŲƒŲˆØ¯) Ų„ŲŠØŗ Ų…Ø¯ØšŲˆŲ… Ų…Ų† Ø§Ų„ØšŲ…ŲŠŲ„ Ø§Ų„ØšØ§Ų….", "oauth_enable_description": "ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… OAuth", - "oauth_end_session_url_description": "ØĨؚاد؊ ØĒ؈ØŦŲŠŲ‡ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… ØĨŲ„Ų‰ Ų…ØšØąŲ Ø§Ų„Ų…ŲˆØ§ØąØ¯ Ø§Ų„Ų…ŲˆØ­Ø¯ (URI) Ų‡Ø°Ø§ ØšŲ†Ø¯ ØĒØŗØŦŲŠŲ„ Ø§Ų„ØŽØąŲˆØŦ.", "oauth_mobile_redirect_uri": "ØšŲ†ŲˆØ§Ų† URI Ų„ØĨؚاد؊ Ø§Ų„ØĒ؈ØŦŲŠŲ‡ ØšŲ„Ų‰ Ø§Ų„Ų‡Ø§ØĒ؁", "oauth_mobile_redirect_uri_override": "ØĒØŦØ§ŲˆØ˛ ØšŲ†ŲˆØ§Ų† URI Ų„ØĨؚاد؊ Ø§Ų„ØĒ؈ØŦŲŠŲ‡ ØšŲ„Ų‰ Ø§Ų„Ų‡Ø§ØĒ؁", "oauth_mobile_redirect_uri_override_description": "Ų‚Ų… بØĒŲØšŲŠŲ„Ų‡ ØšŲ†Ø¯Ų…Ø§ Ų„Ø§ ŲŠØŗŲ…Ø­ Ų…ŲˆŲØą OAuth Ø¨Ų…ØšØąŲ URI Ų„Ų„ØŦŲˆØ§Ų„ØŒ Ų…ØĢŲ„ ''{callback}''", - "oauth_prompt_description": "Ų…ŲØšØ§Ų…Ų„ Ø§Ų„ØĒ؈ØŦŲŠŲ‡ (Ų…ØĢŲ„select_account, login, consent)", "oauth_role_claim": "Ø§Ų„Ų…ØˇØ§Ų„Ø¨ØŠ Ø¨Ø§Ų„Ø¯ŲˆØą(ØĩŲ„Ø§Ø­ŲŠØ§ØĒ)", "oauth_role_claim_description": "Ų…Ų†Ø­ ؈ØĩŲˆŲ„ Ø§Ų„Ų…ØŗØ¤ŲˆŲ„ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§ Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ ؈ØŦŲˆØ¯ Ų‡Ø°Ø§ Ø§Ų„ØˇŲ„Ø¨. Ų‚Ø¯ ŲŠŲƒŲˆŲ† Ø§Ų„ØˇŲ„Ø¨ ØĨŲ…Ø§ 'Ų…ØŗØĒØŽØ¯Ų…' ØŖŲˆ 'Ų…ØŗØ¤ŲˆŲ„'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "ØĒØ­Ø¯ŲŠØĢ ŲƒØ§ŲØŠ Ø§Ų„Ų…ŲƒØĒباØĒ", "registration": "ØĒØŗØŦŲŠŲ„ Ø§Ų„Ų…Ø¯ŲŠØą", "registration_description": "Ø¨Ų…Ø§ ØŖŲ†Ųƒ ØŖŲˆŲ„ Ų…ØŗØĒØŽØ¯Ų… ؁؊ Ø§Ų„Ų†Ø¸Ø§Ų…ØŒ ØŗŲŠØĒŲ… ØĒØšŲŠŲŠŲ†Ųƒ ŲƒŲ…ØŗØ¤ŲˆŲ„ ŲˆØŗØĒŲƒŲˆŲ† Ų…ØŗØ¤ŲˆŲ„Ų‹Ø§ ØšŲ† Ø§Ų„Ų…Ų‡Ø§Ų… Ø§Ų„ØĨØ¯Ø§ØąŲŠØŠØŒ ŲˆØŗŲŠØĒŲ… ØĨŲ†Ø´Ø§ØĄ Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† ØĨØļØ§ŲŲŠŲŠŲ† Ø¨ŲˆØ§ØŗØˇØĒ؃.", - "release_channel_release_candidate": "ØĨØĩØ¯Ø§Øą Ų…ØąØ´Ø­", - "release_channel_stable": "Ų…ØŗØĒŲ‚Øą", "remove_failed_jobs": "Ø§Ø˛Ø§Ų„ØŠ Ø§Ų„ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„ØĒ؊ ŲØ´Ų„ØĒ", "require_password_change_on_login": "Ø§Ų„ØˇŲ„Ø¨ Ų…Ų† Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… ØĒØēŲŠŲŠØą ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą ØšŲ†Ø¯ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ø§Ų„ØŖŲˆŲ„", "reset_settings_to_default": "ØĨؚاد؊ ØļØ¨Øˇ Ø§Ų„ØĨؚداداØĒ ØĨŲ„Ų‰ Ø§Ų„ŲˆØļØš Ø§Ų„Ø§ŲØĒØąØ§Øļ؊", @@ -363,7 +333,7 @@ "storage_template_migration_description": "Ų‚Ų… بØĒØˇØ¨ŲŠŲ‚ Ø§Ų„Ų‚Ø§Ų„Ø¨ Ø§Ų„Ø­Ø§Ų„ŲŠ {template} ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØĒ؊ ØĒŲ… ØąŲØšŲ‡Ø§ ØŗØ§Ø¨Ų‚Ų‹Ø§", "storage_template_migration_info": "ØĒØēŲŠŲŠØąØ§ØĒ Ø§Ų„Ų†Ų…ŲˆØ°ØŦ Ø§Ų„ØŽØ˛Ų†ŲŠ ØŗØĒØēŲŠØą ØŦŲ…ŲŠØš Ø§Ų„Øĩ؊Øē Ø§Ų„Ų‰ Ø§Ø­ØąŲ ØĩØēŲŠØąØŠ. ØĒØēŲŠŲŠØąØ§ØĒ Ø§Ų„Ų†Ų…ŲˆØ°ØŦ ØŗØĒŲ†ØˇØ¨Ų‚ ŲŲ‚Øˇ ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ. Ų„ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„Ų†Ų…ŲˆØ°ØŦ ØšŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØĒ؊ ØĒŲ… ØąŲØšŲ‡Ø§ ØŗØ§Ø¨Ų‚Ų‹Ø§ØŒ Ų‚Ų… بØĒØ´ØēŲŠŲ„ {job}.", "storage_template_migration_job": "ŲˆØ¸ŲŠŲØŠ ØĒŲ‡ØŦŲŠØą Ų‚Ø§Ų„Ø¨ Ø§Ų„ØĒØŽØ˛ŲŠŲ†", - "storage_template_more_details": "Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ Ø­ŲˆŲ„ Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠØŒ ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Storage Template ؈ implications", + "storage_template_more_details": "Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ Ø­ŲˆŲ„ Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠØŒ ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Storage Template ؈implications", "storage_template_onboarding_description_v2": "ØšŲ†Ø¯ Ø§Ų„ØĒŲØšŲŠŲ„. Ų‡Ø°Ų‡ Ø§Ų„ØŽØ§ØĩŲŠØŠ ØŗØĒŲ‚ŲˆŲ… Ø¨Ø§Ų„ØĒØąØĒŲŠØ¨ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„Ų…Ų„ŲØ§ØĒ Ø¨Ų†Ø§ØĄ ØšŲ„Ų‰ Ų†Ų…ŲˆØ°ØŦ Ų…ØšØąŲ Ų…Ų† Ų‚Ø¨Ų„ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…. ØąØŦØ§ØĄ Ø§ØˇŲ„Øš ØšŲ„Ų‰ Ø§Ų„ØĒ؈ØĢŲŠŲ‚.", "storage_template_path_length": "Ø§Ų„Ø­Ø¯ Ø§Ų„ØĒŲ‚ØąŲŠØ¨ŲŠ Ų„ØˇŲˆŲ„ Ø§Ų„Ų…ØŗØ§Øą: {length, number}/{limit, number}", "storage_template_settings": "Ų‚Ø§Ų„Ø¨ Ø§Ų„ØĒØŽØ˛ŲŠŲ†", @@ -402,7 +372,7 @@ "transcoding_audio_codec": "ŲƒŲˆØ¯ Ø§Ų„Øĩ؈ØĒ", "transcoding_audio_codec_description": "Opus Ų‡Ųˆ Ø§Ų„ØŽŲŠØ§Øą Ø°Ųˆ ØŖØšŲ„Ų‰ ØŦŲˆØ¯ØŠØŒ ŲˆŲ„ŲƒŲ†Ų‡ ؊ØĒŲ…ØĒØš بØĒŲˆØ§ŲŲ‚ ØŖŲ‚Ų„ Ų…Øš Ø§Ų„ØŖØŦŲ‡Ø˛ØŠ ØŖŲˆ Ø§Ų„Ø¨ØąŲ…ØŦŲŠØ§ØĒ Ø§Ų„Ų‚Ø¯ŲŠŲ…ØŠ.", "transcoding_bitrate_description": "Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØĒ؊ ؊ØĒØŦØ§ŲˆØ˛ Ų…ØšØ¯Ų„ Ø§Ų„Ø¨ØĒ ØŖŲ‚ØĩŲ‰ Ų‚ŲŠŲ…ØŠ ØŖŲˆ Ø§Ų„ØĒ؊ Ų„Ø§ ØĒŲƒŲˆŲ† ؁؊ ØĒŲ†ØŗŲŠŲ‚ Ų…Ų‚Ø¨ŲˆŲ„", - "transcoding_codecs_learn_more": "Ų„Ų…ØšØąŲØŠ Ø§Ų„Ų…Ø˛ŲŠØ¯ Ø­ŲˆŲ„ Ø§Ų„Ų…ØĩØˇŲ„Ø­Ø§ØĒ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ØŠ Ų‡Ų†Ø§ØŒ ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ ؈ØĢاØĻŲ‚ FFmpeg Ų„Ų€ H.264 codec، ؈ HEVC codec ؈ VP9 codec.", + "transcoding_codecs_learn_more": "Ų„Ų…ØšØąŲØŠ Ø§Ų„Ų…Ø˛ŲŠØ¯ Ø­ŲˆŲ„ Ø§Ų„Ų…ØĩØˇŲ„Ø­Ø§ØĒ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ØŠ Ų‡Ų†Ø§ØŒ ŲŠØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ ؈ØĢاØĻŲ‚ FFmpeg Ų„Ų„H.264 codec, HEVC codec and VP9 codec.", "transcoding_constant_quality_mode": "؈ØļØš Ø§Ų„ØŦŲˆØ¯ØŠ Ø§Ų„ØĢابØĒØŠ", "transcoding_constant_quality_mode_description": "ICQ ØŖŲØļŲ„ Ų…Ų† CQP، ŲˆŲ„ŲƒŲ† بؚØļ ØŖØŦŲ‡Ø˛ØŠ ØšØĒاد Ø§Ų„ØĒØŗØąŲŠØš Ų„Ø§ ØĒØ¯ØšŲ… Ų‡Ø°Ø§ Ø§Ų„ŲˆØļØš. ØĒØšŲŠŲŠŲ† Ų‡Ø°Ø§ Ø§Ų„ØŽŲŠØ§Øą ŲŠØŗØŦØšŲ„ Ø§Ų„ØŖŲØļŲ„ŲŠØŠ Ų„Ų„ŲˆØļØš Ø§Ų„Ų…Ø­Ø¯Ø¯ ØšŲ†Ø¯ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØĒØąŲ…ŲŠØ˛ Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„ØŦŲˆØ¯ØŠ. ؊ØĒŲ… ØĒØŦØ§Ų‡Ų„Ų‡ Ø¨ŲˆØ§ØŗØˇØŠ NVENC Ų„ØŖŲ†Ų‡ Ų„Ø§ ŲŠØ¯ØšŲ… ICQ.", "transcoding_constant_rate_factor": "ØšØ§Ų…Ų„ Ų…ØšØ¯Ų„ Ø§Ų„ØŦŲˆØ¯ØŠ Ø§Ų„ØĢابØĒ (-crf)", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "ŲŠŲ†ØˇØ¨Ų‚ ŲŲ‚Øˇ ØšŲ„Ų‰ VAAPI ؈QSV. ؊ØļØ¨Øˇ ØšŲ‚Ø¯ØŠ dri Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ØŠ Ų„ØĒØ­ŲˆŲŠŲ„ ØĒØąŲ…ŲŠØ˛ Ø§Ų„ØŖØŦŲ‡Ø˛ØŠ.", "transcoding_preset_preset": "Ø§Ų„ØļØ¨Øˇ Ø§Ų„Ų…ŲØŗØ¨Ų‚ (-preset)", "transcoding_preset_preset_description": "ØŗØąØšØŠ Ø§Ų„ØļØēØˇ. ØĒØ¤Ø¯ŲŠ Ø§Ų„ØĨؚداداØĒ Ø§Ų„Ų…ØŗØ¨Ų‚ØŠ Ø§Ų„ØŖØ¨ØˇØŖ ØĨŲ„Ų‰ ØĨŲ†ØĒاØŦ Ų…Ų„ŲØ§ØĒ ØŖØĩØēØą Ø­ØŦŲ…Ų‹Ø§ØŒ ŲˆØ˛ŲŠØ§Ø¯ØŠ Ø§Ų„ØŦŲˆØ¯ØŠ ØšŲ†Ø¯ Ø§ØŗØĒŲ‡Ø¯Ø§Ų Ų…ØšØ¯Ų„ بØĒ Ų…ØšŲŠŲ†. ؊ØĒØŦØ§Ų‡Ų„ VP9 Ø§Ų„ØŗØąØšØ§ØĒ Ø§Ų„ØŖØšŲ„Ų‰ Ų…Ų† 'Ø§Ų„ØŖØŗØąØš'.", - "transcoding_realtime": "ØĒØ­ŲˆŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛ ؁؊ Ø§Ų„ŲˆŲ‚ØĒ Ø§Ų„ŲØšŲ„ŲŠ [ØĒØŦØąŲŠØ¨ŲŠ]", - "transcoding_realtime_description": "؊ØĒŲŠØ­ ØĨØŦØąØ§ØĄ ØĒØ­ŲˆŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛ ؁؊ Ø§Ų„ŲˆŲ‚ØĒ Ø§Ų„ŲØšŲ„ŲŠ ØŖØĢŲ†Ø§ØĄ بØĢ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ. ŲƒŲ…Ø§ ŲŠŲ…ŲƒŲ‘Ų† Ų…Ų† ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„ØŦŲˆØ¯ØŠØŒ ŲˆŲ„ŲƒŲ†Ų‡ Ų‚Ø¯ ؊ØĒØŗØ¨Ø¨ ؁؊ Ø˛ŲŠØ§Ø¯ØŠ Ø˛Ų…Ų† ØĒØŖØŽŲŠØą Ø§Ų„ØĒØ´ØēŲŠŲ„ ŲˆØ­Ø¯ŲˆØĢ ØĒŲ‚ØˇŲŠØšØŒ اؚØĒŲ…Ø§Ø¯Ø§Ų‹ ØšŲ„Ų‰ Ų‚Ø¯ØąØ§ØĒ Ø§Ų„ØŽØ§Ø¯Ų….", - "transcoding_realtime_enabled": "ØĒŲØšŲŠŲ„ ØĒØ­ŲˆŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛ ؁؊ Ø§Ų„ŲˆŲ‚ØĒ Ø§Ų„ŲØšŲ„ŲŠ", - "transcoding_realtime_enabled_description": "؁؊ Ø­Ø§Ų„ ØĒØšØˇŲŠŲ„Ų‡ØŒ ØŗŲŠØąŲØļ Ø§Ų„ØŽØ§Ø¯Ų… Ø¨Ø¯ØĄ ØŦŲ„ØŗØ§ØĒ ØĒØ­ŲˆŲŠŲ„ ØĒØąŲ…ŲŠØ˛ ØŦØ¯ŲŠØ¯ØŠ ؁؊ Ø§Ų„ŲˆŲ‚ØĒ Ø§Ų„ŲØšŲ„ŲŠ.", - "transcoding_realtime_resolutions": "Ø¯Ų‚Ų‚", - "transcoding_realtime_resolutions_description": "Ø§Ų„Ø¯Ų‚ØŠ Ø§Ų„Ų…ØĒاح؊ Ų„Ų„ØĒØ­ŲˆŲŠŲ„ Ø§Ų„ŲŲˆØąŲŠ Ų„Ų„ØĒØąŲ…ŲŠØ˛. Ų‚Ø¯ ØĒØĒØŗØ¨Ø¨ Ø§Ų„Ø¯Ų‚ØŠ Ø§Ų„ØŖØšŲ„Ų‰ ؁؊ Ų…Ø´Ø§ŲƒŲ„ ؁؊ Ø§Ų„ØĒØ´ØēŲŠŲ„ ØĨذا Ų„Ų… ؊ØĒŲ…ŲƒŲ† Ø§Ų„ØŽØ§Ø¯Ų… Ų…Ų† ØĒØ­ŲˆŲŠŲ„Ų‡Ø§ Ø¨Ø§Ų„ØŗØąØšØŠ Ø§Ų„ŲƒØ§ŲŲŠØŠ.", - "transcoding_realtime_video_codecs": "ØĒØąŲ…ŲŠØ˛Ø§ØĒ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", - "transcoding_realtime_video_codecs_description": "Ø¨ØąØ§Ų…ØŦ ØĒØąŲ…ŲŠØ˛ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ų‚Ø¯Ų…ØŠ Ų„ØĒØ­ŲˆŲŠŲ„ Ø§Ų„ØĒØąŲ…ŲŠØ˛ ؁؊ Ø§Ų„ŲˆŲ‚ØĒ Ø§Ų„Ø­Ų‚ŲŠŲ‚ŲŠ. ØŗŲŠØŽØĒØ§Øą Ø§Ų„ØšŲ…Ų„Ø§ØĄ Ø§Ų„ØŽŲŠØ§Øą Ø§Ų„ØŖŲØļŲ„ Ø§Ų„Ø°ŲŠ ŲŠØ¯ØšŲ…ŲˆŲ†Ų‡ ØŖØĢŲ†Ø§ØĄ Ø§Ų„ØĒØ´ØēŲŠŲ„. AV1 ØŖŲƒØĢØą ŲƒŲØ§ØĄØŠ Ų…Ų† HEVC، ŲˆŲ‡Ųˆ ØŖŲƒØĢØą ŲƒŲØ§ØĄØŠ Ų…Ų† H.264. ØšŲ†Ø¯ Ø§ØŗØĒØŽØ¯Ø§Ų… ØĒØŗØąŲŠØš Ø§Ų„ØŖØŦŲ‡Ø˛ØŠØŒ حدد ŲŲ‚Øˇ Ø¨ØąØ§Ų…ØŦ Ø§Ų„ØĒØąŲ…ŲŠØ˛ Ø§Ų„ØĒ؊ ŲŠŲ…ŲƒŲ† Ų„Ų„Ų…ØŗØąØš ØĒØ´ŲŲŠØąŲ‡Ø§. ØšŲ†Ø¯ Ø§ØŗØĒØŽØ¯Ø§Ų… ØĒØ­ŲˆŲŠŲ„ ØĒØąŲ…ŲŠØ˛ Ø§Ų„Ø¨ØąŲ†Ø§Ų…ØŦ، Ų„Ø§Ø­Ø¸ ØŖŲ† H.264 ØŖØŗØąØš Ų…Ų† AV1، ŲˆŲ‡Ųˆ ØŖØŗØąØš Ų…Ų† HEVC.", "transcoding_reference_frames": "Ø§Ų„ØĨØˇØ§ØąØ§ØĒ Ø§Ų„Ų…ØąØŦØšŲŠØŠ", "transcoding_reference_frames_description": "ؚدد Ø§Ų„ØĨØˇØ§ØąØ§ØĒ Ø§Ų„ØĒ؊ ؊ØŦب Ø§Ų„ØąØŦŲˆØš ØĨŲ„ŲŠŲ‡Ø§ ØšŲ†Ø¯ ØļØēØˇ ØĨØˇØ§Øą Ų…ØšŲŠŲ†. ØĒØšŲ…Ų„ Ø§Ų„Ų‚ŲŠŲ… Ø§Ų„ØŖØšŲ„Ų‰ ØšŲ„Ų‰ ØĒØ­ØŗŲŠŲ† ŲƒŲØ§ØĄØŠ Ø§Ų„ØļØēØˇØŒ ŲˆŲ„ŲƒŲ†Ų‡Ø§ ØĒØ¨ØˇØĻ ØšŲ…Ų„ŲŠØŠ Ø§Ų„ØĒØ´ŲŲŠØą. 0 ؊ØļØ¨Øˇ Ų‡Ø°Ų‡ Ø§Ų„Ų‚ŲŠŲ…ØŠ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§.", "transcoding_required_description": "ŲŲ‚Øˇ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ ذاØĒ Ø§Ų„ØĒŲ†ØŗŲŠŲ‚ ØēŲŠØą Ø§Ų„Ų…Ų‚Ø¨ŲˆŲ„", @@ -478,8 +440,6 @@ "user_settings_description": "ØĨØ¯Ø§ØąØŠ ØĨؚداداØĒ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "user_successfully_removed": "Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… {email} ØĒŲ…ØĒ Ø§Ø˛Ø§Ų„ØĒŲ‡ Ø¨Ų†ØŦاح.", "users_page_description": "ØĩŲØ­ØŠ Ø§Ø¯Ø§ØąØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ†", - "version_check_channel": "Ų‚Ų†Ø§ØŠ Ø§Ų„ØĨØĩØ¯Ø§Øą", - "version_check_channel_description": "ا؎ØĒØą Ų‚Ų†Ø§ØŠ Ø§Ų„ØĨØĩØ¯Ø§Øą Ø§Ų„ØĒ؊ ØĒØąØēب ؁؊ ØĒŲ„Ų‚ŲŠ ØĨØšŲ„Ø§Ų†Ø§ØĒ Ø§Ų„ØĨØĩØ¯Ø§ØąØ§ØĒ Ų„Ų‡Ø§", "version_check_enabled_description": "ØĒŲØšŲŠŲ„ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§ØąØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ", "version_check_implications": "ØĒØšØĒŲ…Ø¯ Ų…ŲŠØ˛ØŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą ØšŲ„Ų‰ Ø§Ų„ØĒŲˆØ§ØĩŲ„ Ø§Ų„Ø¯ŲˆØąŲŠ Ų…Øš {server}", "version_check_settings": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØĨØĩØ¯Ø§Øą", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Ų…ØŗØ­ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„ØĩŲˆØą", "advanced_settings_clear_image_cache_error": "ŲØ´Ų„ Ų…ØŗØ­ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„ØĩŲˆØą", "advanced_settings_clear_image_cache_success": "ØĒŲ… Ø§Ų„Ų…ØŗØ­ Ø¨Ų†ØŦاح {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Ø§ØŗØĒØŽØ¯Ų… Ų‡Ø°Ø§ Ø§Ų„ØŽŲŠØ§Øą Ų„ØĒØĩŲŲŠØŠ Ø§Ų„ŲˆØŗØ§ØĻØˇ اØĢŲ†Ø§ØĄ Ø§Ų„Ų…Ø˛Ø§Ų…Ų†Ų‡ Ø¨Ų†Ø§ØĄ ØšŲ„Ų‰ Ų…ØšØ§ŲŠŲŠØą Ø¨Ø¯ŲŠŲ„ØŠ. ØŦØąØ¨ Ų‡Ø°Ø§ Ø§Ų„ØŽŲŠØ§Øą ŲŲ‚Øˇ ŲƒØ§Ų† Ų„Ø¯ŲŠŲƒ Ų…Ø´Ø§ŲƒŲ„ Ų…Øš Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ø¨Ø§Ų„ŲƒØ´Ų ØšŲ† ØŦŲ…ŲŠØš Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ.", + "advanced_settings_enable_alternate_media_filter_title": "[ØĒØŦØąŲŠØ¨ŲŠ] Ø§ØŗØĒØŽØ¯Ų… ØŦŲ‡Ø§Ø˛ ØĒØĩŲŲŠØŠ Ų…Ø˛Ø§Ų…Ų†Ų‡ Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ Ø¨Ø¯ŲŠŲ„", "advanced_settings_log_level_title": "Ų…ØŗØĒŲˆŲ‰ Ø§Ų„ØŗØŦŲ„: {level}", "advanced_settings_prefer_remote_subtitle": "ØĒŲƒŲˆŲ† بؚØļ Ø§Ų„ØŖØŦŲ‡Ø˛ØŠ Ø¨ØˇŲŠØĻØŠ Ų„Ų„ØēØ§ŲŠØŠ ؁؊ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØĩØēØąØŠ Ų…Ų† Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ. Ų‚Ų… بØĒŲØšŲŠŲ„ Ų‡Ø°Ø§ Ø§Ų„ØŽŲŠØ§Øą Ų„ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØą Ø§Ų„Ø¨ØšŲŠØ¯ØŠ Ø¨Ø¯Ų„Ø§Ų‹ Ų…Ų† Ø°Ų„Ųƒ.", "advanced_settings_prefer_remote_title": "ØĒ؁ØļŲ„ Ø§Ų„ØĩŲˆØą Ø§Ų„Ø¨ØšŲŠØ¯ØŠ", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "ØšŲ†Ø§ŲˆŲŠŲ† Ø§Ų„ŲˆŲƒŲŠŲ„ Ø§Ų„Ų…ØŽØĩØĩØŠ [ØĒØŦØąŲŠØ¨ŲŠØŠ]", "advanced_settings_readonly_mode_subtitle": "ØĒØĒŲŠØ­ Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠ ؈ØļØš Ø§Ų„ØšØąØļ ŲŲ‚ØˇØŒ Ø­ŲŠØĢ ŲŠŲ…ŲƒŲ† Ų„Ų„Ų…ØŗØĒØŽØ¯Ų… Ų…ØšØ§ŲŠŲ†ØŠ Ø§Ų„ØĩŲˆØą ŲŲ‚ØˇØŒ Ø¨ŲŠŲ†Ų…Ø§ ؊ØĒŲ… ØĒØšØˇŲŠŲ„ ØŦŲ…ŲŠØš Ø§Ų„ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØŖØŽØąŲ‰ Ų…ØĢŲ„ ØĒØ­Ø¯ŲŠØ¯ ؚد؊ ØĩŲˆØąØŒ ØŖŲˆ Ų…Ø´Ø§ØąŲƒØĒŲ‡Ø§ØŒ ØŖŲˆ بØĢŲ‡Ø§ØŒ ØŖŲˆ Ø­Ø°ŲŲ‡Ø§. ŲŠŲ…ŲƒŲ† ØĒŲØšŲŠŲ„/ØĒØšØˇŲŠŲ„ ؈ØļØš Ø§Ų„ØšØąØļ ŲŲ‚Øˇ Ų…Ų† ØŽŲ„Ø§Ų„ ØĩŲˆØąØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… ؁؊ Ø§Ų„Ø´Ø§Ø´ØŠ Ø§Ų„ØąØĻŲŠØŗŲŠØŠ", "advanced_settings_readonly_mode_title": "؈ØļØš Ø§Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚Øˇ", + "advanced_settings_self_signed_ssl_subtitle": "ØĒØŽØˇŲŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø´Ų‡Ø§Ø¯ØŠ SSL Ų„ØŽØ§Ø¯Ų… Ø§Ų„Ų†Ų‚ØˇØŠ Ø§Ų„Ų†Ų‡Ø§ØĻ؊. Ų…ŲƒŲ„ŲˆØ¨ Ų„Ų„Ø´Ų‡Ø§Ø¯Ø§ØĒ Ø§Ų„Ų…ŲˆŲ‚ØšØŠ ذاØĒŲŠØ§.", + "advanced_settings_self_signed_ssl_title": "Ø§Ų„ØŗŲ…Ø§Ø­ Ø¨Ø´Ų‡Ø§Ø¯Ø§ØĒ SSL Ø§Ų„Ų…ŲˆŲ‚ØšØŠ ذاØĒŲŠŲ‹Ø§ [ØĒØŦØąŲŠØ¨ŲŠØŠ]", "advanced_settings_sync_remote_deletions_subtitle": "Ø­Ø°Ų Ø§Ųˆ Ø§ØŗØĒؚاد؊ ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„Ø§ØĩŲˆŲ„ ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ØŦŲ‡Ø§Ø˛ ØšŲ†Ø¯ ØĒŲ†ŲŲŠØ° Ø§Ų„ØšŲ…Ų„ŲŠØŠ ØšŲ„Ų‰ Ø§Ų„ŲˆŲŠØ¨", "advanced_settings_sync_remote_deletions_title": "Ų…Ø˛Ø§Ų…Ų†ØŠ ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„Ø­Ø°Ų ØšŲ† بؚد [ØĒØŦØąŲŠØ¨ŲŠ]", "advanced_settings_tile_subtitle": "ØĨؚداداØĒ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ø§Ų„Ų…ØĒŲ‚Ø¯Ų…ØŠ", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… {album}؟", "album_delete_confirmation_description": "ØĨذا ØĒŲ…ØĒ Ų…Ø´Ø§ØąŲƒØŠ Ų‡Ø°Ø§ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…ØŒ ؁؄؆ ؊ØĒŲ…ŲƒŲ† Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲˆŲ† Ø§Ų„ØĸØŽØąŲˆŲ† Ų…Ų† Ø§Ų„ŲˆØĩŲˆŲ„ ØĨŲ„ŲŠŲ‡ بؚد Ø§Ų„ØĸŲ†.", "album_deleted": "ØĒŲ… Ø­Ø°Ų Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…", + "album_info_card_backup_album_excluded": "Ų…ØŗØĒبؚد", + "album_info_card_backup_album_included": "Ų…ØĒØļŲ…Ų†ØŠ", "album_info_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_leave": "Ų‡Ų„ ØĒØąŲŠØ¯ Ų…ØēØ§Ø¯ØąØŠ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…ØŸ", + "album_leave_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ų…ØēØ§Ø¯ØąØŠ {album}؟", "album_name": "Ø§ØŗŲ… Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "album_options": "ØĨؚداداØĒ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "album_remove_user": "Ų‡Ų„ ØĒØąØēب ؁؊ ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ØŸ", "album_remove_user_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØĨØ˛Ø§Ų„ØŠ {user}؟", "album_search_not_found": "Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦاد Ø§Ų„Ø¨ŲˆŲ… Ų…ØˇØ§Ø¨Ų‚ Ų„Ø¨Ø­ØĢ؃", + "album_selected": "ا؎ØĒŲŠØą Ø§Ų„Ø¨ŲˆŲ…", "album_share_no_users": "ŲŠØ¨Ø¯Ųˆ ØŖŲ†Ųƒ Ų‚Ų…ØĒ Ø¨Ų…Ø´Ø§ØąŲƒØŠ Ų‡Ø°Ø§ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ų…Øš ØŦŲ…ŲŠØš Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† ØŖŲˆ Ų„ŲŠØŗ Ų„Ø¯ŲŠŲƒ ØŖŲŠ Ų…ØŗØĒØŽØ¯Ų… Ų„Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų…ØšŲ‡.", "album_summary": "Ų…Ų„ØŽØĩ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "album_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "album_updated_setting_description": "ØĒŲ„Ų‚ŲŠ ØĨØ´ØšØ§ØąŲ‹Ø§ ØšØ¨Øą Ø§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ ØšŲ†Ø¯Ų…Ø§ ŲŠØ­ØĒ؈؊ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ø§Ų„Ų…Ø´ØĒØąŲƒ ØšŲ„Ų‰ Ų…Ø­ØĒŲˆŲŠØ§ØĒ ØŦØ¯ŲŠØ¯ØŠ", "album_upload_assets": "ØąŲØš Ø§Ų„Ø§ØĩŲˆŲ„ Ų…Ų† ØŦŲ‡Ø§Ø˛ Ø§Ų„ŲƒŲˆŲ…Ø¨ŲŠŲˆØĒØą Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ؈ اØļØ§ŲØĒŲ‡Ø§ Ø§Ų„Ų‰ Ø§Ų„Ø¨ŲˆŲ…", + "album_user_left": "ØĒŲ… ØĒØąŲƒ {album}", + "album_user_removed": "ØĒŲ… ØĨØ˛Ø§Ų„ØŠ {user}", + "album_viewer_appbar_delete_confirm": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ų…Ų† Ø­ØŗØ§Ø¨ŲƒØŸ", "album_viewer_appbar_share_err_delete": "ŲØ´Ų„ ؁؊ Ø­Ø°Ų Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_viewer_appbar_share_err_leave": "ŲØ´Ų„ ؁؊ ØĒØąŲƒ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_viewer_appbar_share_err_remove": "Ų‡Ų†Ø§Ųƒ Ų…Ø´Ø§ŲƒŲ„ ؁؊ ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_viewer_appbar_share_err_title": "ŲØ´Ų„ ؁؊ ØĒØēŲŠŲŠØą ØšŲ†ŲˆØ§Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_viewer_appbar_share_leave": "ØĒØąŲƒ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "album_viewer_appbar_share_to": "Ø­ØĩØŠ Ų„", "album_viewer_page_share_add_users": "اØļØ§ŲØŠ Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ†", "album_with_link_access": "Ø§Ų„ØŗŲ…Ø§Ø­ Ų„ØŖŲŠ Ø´ØŽØĩ Ų„Ø¯ŲŠŲ‡ Ø§Ų„ØąØ§Ø¨Øˇ Ø¨ØąØ¤ŲŠØŠ Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ØŖØ´ØŽØ§Øĩ Ø§Ų„Ų…ŲˆØŦŲˆØ¯ŲŠŲ† ؁؊ Ų‡Ø°Ø§ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ….", "albums": "Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "ØĒØąØĒŲŠØ¨ ŲØąØ˛ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØŖŲˆŲ„ŲŠ ØšŲ†Ø¯ ØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ ØŦØ¯ŲŠØ¯ØŠ.", "albums_feature_description": "Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØĒ؊ ŲŠŲ…ŲƒŲ† Ų…Ø´Ø§ØąŲƒØĒŲ‡Ø§ Ų…Øš Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† ØĸØŽØąŲŠŲ†.", "albums_on_device_count": "ؚدد Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛ ({count})", + "albums_selected": "{count, plural, one {# Ø§Ų„Ø¨ŲˆŲ… Ų…ØŽØĒØ§Øą} other {# Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ Ų…ØŽØĒØ§ØąØŠ}}", "all": "Ø§Ų„ŲƒŲ„", "all_albums": "ØŦŲ…ŲŠØš Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "all_people": "ØŦŲ…ŲŠØš Ø§Ų„ØŖØ´ØŽØ§Øĩ", "all_photos": "ØŦŲ…ŲŠØš Ø§Ų„ØĩŲˆØą", "all_videos": "ØŦŲ…ŲŠØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ", "allow_dark_mode": "Ø§Ų„ØŗŲ…Ø§Ø­ Ø¨Ø§Ų„ŲˆØļØš Ø§Ų„Ų…ØšØĒŲ…", + "allow_edits": "ØĨØŗŲ…Ø­ Ø¨Ø§Ų„ØĒØšØ¯ŲŠŲ„", "allow_public_user_to_download": "Ø§Ų„ØŗŲ…Ø§Ø­ Ų„ØŖŲŠ Ų…ØŗØĒØŽØ¯Ų… ØšØ§Ų… Ø¨Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "allow_public_user_to_upload": "Ø§Ų„ØŗŲ…Ø§Ø­ Ų„Ų„Ų…ØŗØĒØŽØ¯Ų… Ø§Ų„ØšØ§Ų… Ø¨Ø§Ų„ØąŲØš", "allowed": "Ų…ØŗŲ…ŲˆØ­", @@ -549,12 +528,14 @@ "always_keep": "داØĻŲ…Ø§ Ø­Ø§ŲØ¸ ØšŲ„Ų‰", "always_keep_photos_hint": "ØŗŲŠØ­ØĒŲØ¸ ØĒØ­ØąŲŠØą Ø§Ų„Ų…ØŗØ§Ø­ØŠ بØŦŲ…ŲŠØš Ø§Ų„ØĩŲˆØą ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ØŦŲ‡Ø§Ø˛.", "always_keep_videos_hint": "ØŗŲŠØ­ØĒŲØ¸ ØĒØ­ØąŲŠØą Ø§Ų„Ų…ØŗØ§Ø­ØŠ بØŦŲ…ŲŠØš Ø§Ų„ŲØ¯ŲŠŲˆØ§ØĒ ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ØŦŲ‡Ø§Ø˛.", + "anti_clockwise": "ØšŲƒØŗ اØĒØŦØ§Ų‡ ØšŲ‚Ø§ØąØ¨ Ø§Ų„ØŗØ§ØšØŠ", "api_key": "؅؁ØĒاح API", "api_key_description": "ØŗŲŠØĒŲ… ØšØąØļ Ų‡Ø°Ų‡ Ø§Ų„Ų‚ŲŠŲ…ØŠ Ų…ØąØŠ ŲˆØ§Ø­Ø¯ØŠ ŲŲ‚Øˇ. ŲŠØąØŦŲ‰ Ø§Ų„ØĒØŖŲƒØ¯ Ų…Ų† Ų†ØŗØŽŲ‡Ø§ Ų‚Ø¨Ų„ ØĨØēŲ„Ø§Ų‚ Ø§Ų„Ų†Ø§ŲØ°ØŠ.", "api_key_empty": "؊ØŦب ØŖŲ„Ø§ ŲŠŲƒŲˆŲ† Ø§ØŗŲ… ؅؁ØĒاح API ŲØ§ØąØēŲ‹Ø§", "api_keys": "Ų…ŲØ§ØĒŲŠØ­ API", "app_architecture_variant": "Ų…ØĒØēŲŠØą (Ø§Ų„Ų‡Ų†Ø¯ØŗØŠ Ø§Ų„Ų…ØšŲ…Ø§ØąŲŠØŠ)", "app_bar_signout_dialog_content": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØĒØŗØŦŲŠŲ„ Ø§Ų„ØŽØąŲˆØŦ؟", + "app_bar_signout_dialog_ok": "Ų†ØšŲ…", "app_bar_signout_dialog_title": "ØŽØąŲˆØŦ", "app_download_links": "ØąŲˆØ§Ø¨Øˇ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", "app_settings": "ØĨؚداداØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", @@ -565,19 +546,27 @@ "archive": "Ø§Ų„ØŖØąØ´ŲŠŲ", "archive_action_prompt": "{count} اØļ؊؁ ØĨŲ„Ų‰ Ø§Ų„Ø§ØąØ´ŲŠŲ", "archive_or_unarchive_photo": "ØŖØąØ´ŲØŠ Ø§Ų„ØĩŲˆØąØŠ ØŖŲˆ ØĨŲ„ØēØ§ØĄ ØŖØąØ´ŲØĒŲ‡Ø§", + "archive_page_no_archived_assets": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø¤ØąØ´ŲØŠ", + "archive_page_title": "Ø§ØąØ´ŲŠŲ ({count})", "archive_size": "Ø­ØŦŲ… Ø§Ų„ØŖØąØ´ŲŠŲ", "archive_size_description": "ØĒŲƒŲˆŲŠŲ† Ø­ØŦŲ… Ø§Ų„ØŖØąØ´ŲŠŲ Ų„Ų„ØĒŲ†Ø˛ŲŠŲ„Ø§ØĒ (Ø¨Ø§Ų„ØŦ؊ØŦØ§Ø¨Ø§ŲŠØĒ)", "archived": "Ų…Ø¤ØąØ´ŲØŠ", "archived_count": "{count, plural, other {Ø§Ų„ØŖØąØ´ŲŠŲ #}}", "are_these_the_same_person": "Ų‡Ų„ Ų‡Ø¤Ų„Ø§ØĄ Ų‡Ų… Ų†ŲØŗ Ø§Ų„Ø´ØŽØĩ؟", "are_you_sure_to_do_this": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØŖŲ† ØĒŲØšŲ„ Ų‡Ø°Ø§ØŸ", + "array_field_not_fully_supported": "Ø­Ų‚ŲˆŲ„ Ø§Ų„Ų…ØĩŲŲˆŲØŠ ØĒØĒØˇŲ„Ø¨ ØĒØšØ¯ŲŠŲ„ ŲŠØ¯ŲˆŲŠ Ų„JSON", + "asset_action_delete_err_read_only": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø­Ø°Ų Ø§Ų„ØŖØĩŲˆŲ„ ذاØĒ Ų„Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚ØˇØŒ ŲˆØŗŲˆŲ ؊ØĒŲ… Ø§Ų„ØĒØŽØˇŲŠ", + "asset_action_share_err_offline": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØŦŲ„Ø¨ Ø§Ų„ØŖØĩŲˆŲ„ ØēŲŠØą Ø§Ų„Ų…ØĒØĩŲ„ØŠ Ø¨Ø§Ų„ØĨŲ†ØĒØąŲ†ØĒ، ŲˆØŗŲˆŲ ؊ØĒŲ… Ø§Ų„ØĒØŽØˇŲŠ", "asset_added_to_album": "ØĒŲ…ØĒ ØĨØļØ§ŲØĒŲ‡ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "asset_adding_to_album": "ØŦØ§ØąŲ Ø§Ų„ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…â€Ļ", "asset_created": "Ø§Ų†Ø´ØĻ اØĩŲ„", - "asset_day_count": "{date}: {count, plural, zero {Ų„Ø§ ØĒ؈ØŦد Ų…Ų„ŲØ§ØĒ} one {؅؄؁ ŲˆØ§Ø­Ø¯} two {Ų…Ų„ŲØ§Ų†} few {# Ų…Ų„ŲØ§ØĒ} many {# Ų…Ų„ŲŲ‹Ø§} other {# ؅؄؁}}", "asset_description_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ ؈Øĩ؁ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰", + "asset_filename_is_offline": "Ø§Ų„ØŖØĩŲ„ {filename} ØēŲŠØą Ų…ØĒØĩŲ„", + "asset_has_unassigned_faces": "ŲŠØ­ØĒ؈؊ Ø§Ų„ØŖØĩŲ„ ØšŲ„Ų‰ ؈ØŦŲˆŲ‡ ØēŲŠØą Ų…ØŽØĩØĩØŠ", "asset_hashing": "Ø§Ų„ØĒØŦØ˛ØĻØŠâ€Ļ", "asset_list_group_by_sub_title": "ØĒŲ†Ø¸ŲŠŲ… Ø¨ŲˆØ§ØŗØˇØŠ", + "asset_list_layout_settings_dynamic_layout_title": "ØĒØŽØˇŲŠØˇ Ø¯ŲŠŲ†Ø§Ų…ŲŠŲƒŲŠ", + "asset_list_layout_settings_group_automatically": "ØĒŲ„Ų‚Ø§ØĻ؊", "asset_list_layout_settings_group_by": "Ų…ØŦŲ…ŲˆØšØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø­ØŗØ¨", "asset_list_layout_settings_group_by_month_day": "Ø´Ų‡Øą + ŲŠŲˆŲ…", "asset_list_layout_sub_title": "ØĒØĩŲ…ŲŠŲ…", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Ø§Ų„ØŖØĩŲ„ Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦØ§Ø¯Ų‡ ؁؊ Ø§Ų„ØŦŲ‡Ø§Ø˛, Ø§Ų„ØŖØĩŲ„ Ų‚Ø¯ Ų„Ø§ ؊ØĒŲ… Ø§Ų„ŲˆØĩŲˆŲ„ Ų„Ų‡ Ø¨ØŗØ¨Ø¨ ؅؄؁ Ų…ØĒØļØ§ØąØ¨ Ų…ØŽØ˛ŲˆŲ† ؁؊ iCloud", "asset_offline": "Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ ØēŲŠØą اØĒØĩØ§Ų„", "asset_offline_description": "Ų„Ų… ŲŠØšØ¯ Ų‡Ø°Ø§ Ø§Ų„ØŖØĩŲ„ Ø§Ų„ØŽØ§ØąØŦ؊ Ų…ŲˆØŦŲˆØ¯Ų‹Ø§ ØšŲ„Ų‰ Ø§Ų„Ų‚ØąØĩ. ŲŠØąØŦŲ‰ Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨Ų…ØŗØ¤ŲˆŲ„ Immich Ų„Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ Ø§Ų„Ų…ØŗØ§ØšØ¯ØŠ.", + "asset_restored_successfully": "ØĒŲ… Ø§ØŗØĒؚاد؊ Ø§Ų„Ø§ØĩŲ„ Ø¨Ų†ØŦاح", "asset_skipped": "ØĒŲ… ØĒØŽØˇŲŠŲ‡", "asset_skipped_in_trash": "؁؊ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", + "asset_trashed": "اØĩŲˆŲ„ Ų…Ø­Ø°ŲˆŲØŠ", "asset_troubleshoot": "Ø§ØŗØĒŲƒØ´Ø§Ų Ų…Ø´Ø§ŲƒŲ„ Ø§Ų„ØŖØĩŲˆŲ„", "asset_uploaded": "ØĒŲ… Ø§Ų„ØąŲØš", "asset_uploading": "ØŦØ§ØąŲ Ø§Ų„ØąŲØšâ€Ļ", "asset_viewer_settings_subtitle": "ØĨØ¯Ø§ØąØŠ ØĨؚداداØĒ ØšØ§ØąØļ Ø§Ų„Ų…ØšØąØļ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "asset_viewer_settings_title": "ØšØ§ØąØļ Ø§Ų„ØŖØĩŲˆŲ„", "assets": "Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ", + "assets_added_count": "ØĒŲ…ØĒ ØĨØļØ§ŲØŠ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}}", "assets_added_to_album_count": "ØĒŲ…ØĒ ØĨØļØ§ŲØŠ {count, plural, one {# Ø§Ų„ØŖØĩŲ„} other {# Ø§Ų„ØŖØĩŲˆŲ„}} ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", - "assets_added_to_album_partial_count": "ØĒŲ…ØĒ ØĨØļØ§ŲØŠ {successCount} Ų…Ų† ØŖØĩŲ„ {totalCount} {totalCount, plural, one {asset} other {assets}} ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "assets_added_to_albums_count": "ØĒŲ…ØĒ اØļØ§ŲØŠ {assetTotal, plural, one {# اØĩŲ„} other {# اØĩŲˆŲ„}} to {albumTotal, plural, one {# Ø§Ų„Ø¨ŲˆŲ…} other {# Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} Ų„Ø§ŲŠŲ…ŲƒŲ† اØļØ§ŲØĒŲ‡ Ø§Ų„Ų‰ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…", "assets_cannot_be_added_to_albums": "{count, plural, one {اØĩŲ„} other {اØĩŲˆŲ„}} Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØļØ§ŲØĒŲ‡ ØĨŲ„Ų‰ ØŖŲŠ Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "assets_count": "{count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}}", + "assets_deleted_permanently": "{count} Ø§Ų„Ø§Øĩ(؈)Ų„ Ø§Ų„Ų…Ø­Ø°ŲˆŲ(Ų‡) Ø¨Ø´ŲƒŲ„ داØĻŲ…", + "assets_deleted_permanently_from_server": "{count} Ø§Ų„Ø§Øĩ(؈)Ų„ Ø§Ų„Ų…Ø­Ø°ŲˆŲ(Ų‡) Ø¨Ø´ŲƒŲ„ داØĻŲ…ŲŠ Ų…Ų† ØŽØ§Ø¯Ų… Immich", + "assets_downloaded_failed": "{count, plural, one {ØĒŲ… Ø§Ų„ØĒØ­Ų…ŲŠŲ„ # ؅؄؁ - {error} ؅؄؁ ŲØ´Ų„} other {ØĒŲ… Ø§Ų„ØĒØ­Ų…ŲŠŲ„ # Ų…Ų„ŲØ§ØĒ - {error} Ų…Ų„ŲØ§ØĒ ŲØ´Ų„ØĒ}}", + "assets_downloaded_successfully": "{count, plural, one {ØĒŲ… Ø§Ų„ØĒØ­Ų…ŲŠŲ„ # ؅؄؁ Ø¨Ų†ØŦاح} other {ØĒŲ… Ø§Ų„ØĒØ­Ų…ŲŠŲ„ # Ų…Ų„ŲØ§ØĒ Ø¨Ų†ØŦاح}}", "assets_moved_to_trash_count": "ØĒŲ… Ų†Ų‚Ų„ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "assets_permanently_deleted_count": "ØĒŲ… Ø­Ø°Ų {count, plural, one {# Ų‡Ø°Ø§ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {# Ų‡Ø°Ų‡ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} Ø¨Ø´ŲƒŲ„ داØĻŲ…", "assets_removed_count": "ØĒŲ…ØĒ ØĨØ˛Ø§Ų„ØŠ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}}", + "assets_removed_permanently_from_device": "{count} Ø§Ų„Ø§Øĩ(؈)Ų„ Ų…Ø­Ø°ŲˆŲ(Ų‡) Ų…Ų† Ø§Ų„ØŦŲ‡Ø§Ø˛", "assets_restore_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø§ØŗØĒؚاد؊ ØŦŲ…ŲŠØš Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ø°ŲˆŲØŠØŸ Ų„Ø§ ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„ØĒØąØ§ØŦØš ØšŲ† Ų‡Ø°Ø§ Ø§Ų„ØĨØŦØąØ§ØĄ! Ų„Ø§Ø­Ø¸ ØŖŲ†Ų‡ Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§ØŗØĒؚاد؊ ØŖŲŠ ØŖØĩŲˆŲ„ ØēŲŠØą Ų…ØĒØĩŲ„ØŠ Ø¨Ų‡Ø°Ų‡ Ø§Ų„ØˇØąŲŠŲ‚ØŠ.", "assets_restored_count": "ØĒŲ…ØĒ Ø§ØŗØĒؚاد؊ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}}", + "assets_restored_successfully": "{count} Ø§Ų„Ø§Øĩ(؈)Ų„ Ø§Ų„Ų…ØŗØĒؚاد(Ų‡) Ø¨Ų†ØŦاح", "assets_trashed": "{count} Ø§Ų„Ø§ØĩŲ„(؈) Ų„ Ø§Ų„Ų…Ų†Ų‚ŲˆŲ„Ų‡ Ø§Ų„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "assets_trashed_count": "ØĒŲ… ØĨØąØŗØ§Ų„ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", + "assets_trashed_from_server": "{count} Ø§Ų„Ø§Øĩ(؈)Ų„ Ø§Ų„Ų…Ų†Ų‚ŲˆŲ„ØŠ Ø§Ų„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ Ų…Ų† ØŽØ§Ø¯Ų… Immich", "assets_were_part_of_album_count": "{count, plural, one {Ų‡Ø°Ø§ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {Ų‡Ø°Ų‡ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} ؁؊ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ø¨Ø§Ų„ŲØšŲ„", "assets_were_part_of_albums_count": "{count, plural, one {اØĩŲ„ Ų‡Ųˆ} other {اØĩŲˆŲ„ Ų‡ŲŠ}}Ø¨Ø§Ų„ŲØšŲ„ ØŦØ˛ØĄ Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "authorized_devices": "Ø§Ų„ØŖØŦŲ‡Ø˛Ų‡ Ø§Ų„Ų…ØŽŲˆŲ„ØŠ", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ØĒØ´ØēŲŠŲ„ ØĒŲ„Ų‚Ø§ØĻ؊ Ų„ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", "back": "ØŽŲ„Ų", "back_close_deselect": "Ø§Ų„ØąØŦŲˆØš ØŖŲˆ Ø§Ų„ØĨØēŲ„Ø§Ų‚ ØŖŲˆ ØĨŲ„ØēØ§ØĄ Ø§Ų„ØĒØ­Ø¯ŲŠØ¯", + "background_backup_running_error": "؊ØĒŲ… ØĒØ´ØēŲŠŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ø­Ø§Ų„ŲŠŲ‹Ø§ØŒ ŲˆŲ„Ø§ ŲŠŲ…ŲƒŲ† Ø¨Ø¯ØĄ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„ŲŠØ¯ŲˆŲŠ", "background_location_permission": "Ø§Ø°Ų† Ø§Ų„ŲˆØĩŲˆŲ„ Ų„Ų„Ų…ŲˆŲ‚Øš ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ", "background_location_permission_content": "Ų„Ų„ØĒŲ…ŲƒŲ† Ų…Ų† ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„Ø´Ø¨ŲƒŲ‡ Ø¨Ø§Ų„ØŽŲ„ŲŲŠØŠØŒ Immich ŲŠØ­ØĒاØŦ*داØĻŲ…Ø§* Ų„Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ Ų…ŲˆŲ‚Øš Ø¯Ų‚ŲŠŲ‚ Ų„ŲŠØĒŲ…ŲƒŲ† Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ų…Ų† Ų‚ØąØ§ØĻØŠ Ø§ØŗŲ… Ø´Ø¨ŲƒØŠ Ø§Ų„Wi-Fi", "background_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØŽŲ„ŲŲŠØŠ", "backup": "Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ", + "backup_album_selection_page_albums_device": "Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛ ({count})", "backup_album_selection_page_albums_tap": "Ø§Ų†Ų‚Øą Ų„Ų„ØĒØļŲ…ŲŠŲ†ØŒ ŲˆØ§Ų†Ų‚Øą Ų†Ų‚ØąŲ‹Ø§ Ų…Ø˛Ø¯ŲˆØŦŲ‹Ø§ Ų„Ų„Ø§ØŗØĒØĢŲ†Ø§ØĄ", "backup_album_selection_page_assets_scatter": "ŲŠŲ…ŲƒŲ† ØŖŲ† ØĒŲ†ØĒØ´Øą Ø§Ų„ØŖØĩŲˆŲ„ ØšØ¨Øą ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ų…ØĒؚدد؊. ŲˆØ¨Ø§Ų„ØĒØ§Ų„ŲŠØŒ ŲŠŲ…ŲƒŲ† ØĒØļŲ…ŲŠŲ† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ ØŖŲˆ Ø§ØŗØĒØ¨ØšØ§Ø¯Ų‡Ø§ ØŖØĢŲ†Ø§ØĄ ØšŲ…Ų„ŲŠØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ.", "backup_album_selection_page_select_albums": "حدد Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "backup_album_selection_page_selection_info": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„Ø§ØŽØĒŲŠØ§Øą", + "backup_album_selection_page_total_assets": "ØĨØŦŲ…Ø§Ų„ŲŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ŲØąŲŠØ¯ØŠ", "backup_albums_sync": "Ų…Ø˛Ø§Ų…Ų†ØŠ ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", + "backup_all": "Ø§Ų„ØŦŲ…ŲŠØš", + "backup_background_service_backup_failed_message": "ŲØ´Ų„ ؁؊ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØŖØĩŲˆŲ„. ØŦØ§ØąŲ ØĨؚاد؊ Ø§Ų„Ų…Ø­Ø§ŲˆŲ„ØŠâ€Ļ", "backup_background_service_complete_notification": "ØĒŲ… Ø§Ų„Ø§Ų†ØĒŲ‡Ø§ØĄ Ų…Ų† Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØŖØĩŲˆŲ„", + "backup_background_service_connection_failed_message": "ŲØ´Ų„ ؁؊ Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨Ø§Ų„ØŽØ§Ø¯Ų…. ØŦØ§ØąŲ ØĨؚاد؊ Ø§Ų„Ų…Ø­Ø§ŲˆŲ„ØŠâ€Ļ", + "backup_background_service_current_upload_notification": "ØĒØ­Ų…ŲŠŲ„ {filename}", "backup_background_service_default_notification": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠâ€Ļ", + "backup_background_service_error_title": "ØŽØˇØŖ ؁؊ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "backup_background_service_in_progress_notification": "Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒâ€Ļ", + "backup_background_service_upload_failure_notification": "ŲØ´Ų„ ØĒØ­Ų…ŲŠŲ„ {filename}", "backup_controller_page_albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ احØĒŲŠØ§ØˇŲŠØŠ", + "backup_controller_page_background_app_refresh_disabled_content": "Ų‚Ų… بØĒŲ…ŲƒŲŠŲ† ØĒØ­Ø¯ŲŠØĢ ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØŽŲ„ŲŲŠØŠ ؁؊ Ø§Ų„ØĨؚداداØĒ > ØšØ§Ų… > ØĒØ­Ø¯ŲŠØĢ ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ų„Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ.", + "backup_controller_page_background_app_refresh_disabled_title": "ØĒŲ… ØĒØšØˇŲŠŲ„ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ", + "backup_controller_page_background_app_refresh_enable_button_text": "Ø§Ø°Ų‡Ø¨ Ų„Ų„Ø§ØšØ¯Ø§Ø¯Ø§ØĒ", "backup_controller_page_background_battery_info_link": "ØŖØąŲ†ŲŠ ؃؊؁", "backup_controller_page_background_battery_info_message": "Ų„Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ ØŖŲØļŲ„ ØĒØŦØąØ¨ØŠ Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠØŒ ŲŠØąØŦŲ‰ ØĒØšØˇŲŠŲ„ ØŖŲŠ ØĒØ­ØŗŲŠŲ†Ø§ØĒ Ų„Ų„Ø¨ØˇØ§ØąŲŠØŠ ØĒŲ‚ŲŠØ¯ Ų†Ø´Ø§Øˇ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ų„Ų€ ØĒØˇØ¨ŲŠŲ‚.\n\nŲ†Ø¸ØąŲ‹Ø§ Ų„ØŖŲ† Ų‡Ø°Ø§ ؎اØĩ Ø¨Ø§Ų„ØŦŲ‡Ø§Ø˛ØŒ ŲŠØąØŦŲ‰ Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…ØˇŲ„ŲˆØ¨ØŠ Ų„Ų„Ø´ØąŲƒØŠ Ø§Ų„Ų…ØĩŲ†ØšØŠ Ų„ØŦŲ‡Ø§Ø˛Ųƒ.", "backup_controller_page_background_battery_info_ok": "Ų†ØšŲ…", "backup_controller_page_background_battery_info_title": "ØĒØ­ØŗŲŠŲ† Ø§Ų„Ø¨ØˇØ§ØąŲŠØŠ", + "backup_controller_page_background_charging": "ŲŲ‚Øˇ ØŖØĢŲ†Ø§ØĄ Ø§Ų„Ø´Ø­Ų†", + "backup_controller_page_background_configure_error": "ŲØ´Ų„ ؁؊ ØĒŲƒŲˆŲŠŲ† ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ", "backup_controller_page_background_delay": "ØĒØ§ØŽŲŠØą Ø§Ų„ØŽØ˛Ų† Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„Ø§ØĩŲˆŲ„: {duration}", + "backup_controller_page_background_description": "Ų‚Ų… بØĒØ´ØēŲŠŲ„ ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ų„ØĨØŦØąØ§ØĄ Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ Ų„ØŖŲŠ ØŖØĩŲˆŲ„ ØŦØ¯ŲŠØ¯ØŠ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§ Ø¯ŲˆŲ† Ø§Ų„Ø­Ø§ØŦØŠ ØĨŲ„Ų‰ ؁ØĒØ­ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", + "backup_controller_page_background_is_off": "ØĒŲ… ØĨŲŠŲ‚Ø§Ų Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„ØŽŲ„ŲŲŠØŠ", + "backup_controller_page_background_is_on": "Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻ؊ Ų„Ų„ØŽŲ„ŲŲŠØŠ Ų‚ŲŠØ¯ Ø§Ų„ØĒØ´ØēŲŠŲ„", + "backup_controller_page_background_turn_off": "Ų‚Ų… بØĨŲŠŲ‚Ø§Ų ØĒØ´ØēŲŠŲ„ ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ", + "backup_controller_page_background_turn_on": "Ų‚Ų… بØĒØ´ØēŲŠŲ„ ØŽØ¯Ų…ØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ", + "backup_controller_page_background_wifi": "ŲŲ‚Øˇ ØšŲ„Ų‰ Wi-Fi", "backup_controller_page_backup": "Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠ", "backup_controller_page_backup_selected": "Ø§Ų„Ų…Ø­Ø¯Ø¯: ", "backup_controller_page_backup_sub": "Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", + "backup_controller_page_created": "Ø§Ų†Ø´ØĻ ؁؊ :{date}", + "backup_controller_page_desc_backup": "Ų‚Ų… بØĒØ´ØēŲŠŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„ØŖŲ…Ø§Ų…ŲŠ Ų„ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§ ØĨŲ„Ų‰ Ø§Ų„ØŽØ§Ø¯Ų… ØšŲ†Ø¯ ؁ØĒØ­ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚.", "backup_controller_page_excluded": "Ų…ØŗØĒبؚد: ", + "backup_controller_page_failed": "ŲØ´Ų„ ({count})", + "backup_controller_page_filename": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų : {filename} [{size}]", + "backup_controller_page_id": "Ų‡ŲˆŲŠØŠ: {id}", + "backup_controller_page_info": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "backup_controller_page_none_selected": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØĒØ­Ø¯ŲŠØ¯", "backup_controller_page_remainder": "Ø¨Ų‚ŲŠØŠ", "backup_controller_page_remainder_sub": "Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØĒØ¨Ų‚ŲŠØŠ Ų„Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų…Ų† Ø§Ų„ØĒØ­Ø¯ŲŠØ¯", "backup_controller_page_server_storage": "Ø°Ø§ŲƒØąØŠ Ø§Ų„ØŦŲ‡Ø§Ø˛", + "backup_controller_page_start_backup": "Ø¨Ø¯ØĄ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", + "backup_controller_page_status_off": "Ø§Ų„Ų†ØŗØŽØŠ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠØŠ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻŲŠØŠ ØēŲŠØą ŲØšØ§Ų„ØŠ", + "backup_controller_page_status_on": "Ø§Ų„Ų†ØŗØŽØŠ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠØŠ Ø§Ų„ØĒŲ„Ų‚Ø§ØĻŲŠØŠ ŲØšØ§Ų„ØŠ", "backup_controller_page_storage_format": "{used} Ų…Ų† {total} Ų…ØŗØĒØŽØ¯Ų…", "backup_controller_page_to_backup": "Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„ØĒ؊ ØŗŲŠØĒŲ… Ų†ØŗØŽŲ‡Ø§ احØĒŲŠØ§ØˇŲŠØ§", "backup_controller_page_total_sub": "ØŦŲ…ŲŠØš Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ŲØąŲŠØ¯ØŠ Ų…Ų† ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ų…ØŽØĒØ§ØąØŠ", + "backup_controller_page_turn_off": "Ų‚Ų… بØĨŲŠŲ‚Ø§Ų ØĒØ´ØēŲŠŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„Ų…Ų‚Ø¯Ų…ØŠ", + "backup_controller_page_turn_on": "Ų‚Ų… بØĒØ´ØēŲŠŲ„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„Ų…Ų‚Ø¯Ų…ØŠ", + "backup_controller_page_uploading_file_info": "ØĒØ­Ų…ŲŠŲ„ Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…Ų„Ų", + "backup_err_only_album": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ø§Ų„ŲˆØ­ŲŠØ¯", "backup_error_sync_failed": "ŲØ´Ų„ Ø§Ų„Ų…Ø˛Ø§Ų…Ų†ØŠ. Ų„Ø§ ŲŠŲ…ŲƒŲ† Ų…ØšØ§Ų„ØŦØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ.", "backup_info_card_assets": "ØŖØĩŲˆŲ„", + "backup_manual_cancelled": "Ų…Ų„Øē؊", + "backup_manual_in_progress": "Ų‚ŲŠØ¯ Ø§Ų„ØĒØ­Ų…ŲŠŲ„ Ø­Ø§ŲˆŲ„ Ų…ØąŲ‡ Ø§ØŽØąŲ‰", + "backup_manual_success": "Ų†ØŦاح", + "backup_manual_title": "Ø­Ø§Ų„ØŠ Ø§Ų„ØĒØ­Ų…ŲŠŲ„", "backup_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", + "backup_options_page_title": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", + "backup_setting_subtitle": "Ø§Ø¯Ø§ØąØŠ اؚداداØĒ Ø§Ų„ØĒØ­Ų…ŲŠŲ„ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ ŲˆØ§Ų„Ų…Ų‚Ø¯Ų…ØŠ", "backup_settings_subtitle": "ØĨØ¯Ø§ØąØŠ ØĨؚداداØĒ Ø§Ų„ØĒØ­Ų…ŲŠŲ„", + "backup_upload_details_page_more_details": "اØļØēØˇ Ų„ØĒŲØ§ØĩŲŠŲ„ اØļØ§ŲŲŠØŠ", "backward": "Ø§Ų„Ų‰ Ø§Ų„ŲˆØąØ§ØĄ", - "battery_optimization_backup_reliability": "ØĨŲŠŲ‚Ø§Ų ØĒØ­ØŗŲŠŲ† Ø§Ų„Ø¨ØˇØ§ØąŲŠØŠ ŲŠØ˛ŲŠØ¯ Ų…Ų† Ø§ØŗØĒŲ‚ØąØ§Øą Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ", "biometric_auth_enabled": "Ø§Ų„Ų…ØĩØ§Ø¯Ų‚ØŠ Ø§Ų„Ø¨Ø§ŲŠŲˆŲ…ØĒØąŲŠØŠ Ų…ŲØšŲ„Ų‡", "biometric_locked_out": "Ų„Ų‚Ø¯ ؂؁؄ØĒ ØšŲ†Ųƒ Ø§Ų„Ų…ØĩØ§Ø¯Ų‚ØŠ Ø§Ų„Ø¨ŲŠŲˆŲ…ØĒØąŲŠØŠ", "biometric_no_options": "Ų„Ø§ ØĒ؈ØŦد ØŽŲŠØ§ØąØ§ØĒ Ø¨Ø§ŲŠŲˆŲ…ØĒØąŲŠØŠ Ų…ØĒŲˆŲØąØŠ", "biometric_not_available": "Ø§Ø§Ų„Ų…ØĩØ§Ø¯Ų‚ØŠ Ø§Ų„Ø¨ŲŠŲˆŲ…ØĒØąŲŠØŠ ØēŲŠØą Ų…ØĒاح؊ ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ØŦŲ‡Ø§Ø˛", + "birthdate_saved": "ØĒŲ… Ø­ŲØ¸ ØĒØ§ØąŲŠØŽ Ø§Ų„Ų…ŲŠŲ„Ø§Ø¯ Ø¨Ų†ØŦاح", "birthdate_set_description": "؊ØĒŲ… Ø§ØŗØĒØŽØ¯Ø§Ų… ØĒØ§ØąŲŠØŽ Ø§Ų„Ų…ŲŠŲ„Ø§Ø¯ Ų„Ø­ØŗØ§Ø¨ ØšŲ…Øą Ų‡Ø°Ø§ Ø§Ų„Ø´ØŽØĩ ŲˆŲ‚ØĒ Ø§Ų„ØĒŲ‚Ø§Øˇ Ø§Ų„ØĩŲˆØąØŠ.", "blurred_background": "ØŽŲ„ŲŲŠØŠ Ų…Ø´ŲˆØ´ØŠ", - "browse_templates": "ØĒØĩŲØ­ Ø§Ų„Ų‚ŲˆØ§Ų„Ø¨", "bugs_and_feature_requests": "Ø§Ų„ØŖØŽØˇØ§ØĄ ŲˆØˇŲ„Ø¨Ø§ØĒ Ø§Ų„Ų…ŲŠØ˛Ø§ØĒ", "build": "ŲŠØ¨Ų†ŲŠ", "build_image": "Ø¨Ų†Ø§ØĄ Ø§Ų„ØĩŲˆØąØŠ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø§Ų„Ø§Ø­ØĒŲØ§Ø¸ Ø¨Ų€ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰ Ų…ŲƒØąØą} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ų…ŲƒØąØąØŠ}}؟ ØŗŲŠØ¤Ø¯ŲŠ Ų‡Ø°Ø§ ØĨŲ„Ų‰ Ø­Ų„ ØŦŲ…ŲŠØš Ų…ØŦŲ…ŲˆØšØ§ØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ŲƒØąØąØŠ Ø¯ŲˆŲ† Ø­Ø°Ų ØŖŲŠ Ø´ŲŠØĄ.", "bulk_trash_duplicates_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØĨØąØŗØ§Ų„ {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰ Ų…ŲƒØąØą} other {# Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ų…ŲƒØąØąØŠ}} ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ ؟ ØŗŲŠØ­ØĒŲØ¸ Ų‡Ø°Ø§ Ø¨ØŖŲƒØ¨Øą Ų…Ø­ØĒŲˆŲ‰ Ų…Ų† ŲƒŲ„ Ų…ØŦŲ…ŲˆØšØŠ ŲˆŲŠØąØŗŲ„ ØŦŲ…ŲŠØš Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ŲƒØąØąØŠ Ø§Ų„ØŖØŽØąŲ‰ ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ.", "buy": "Ø´ØąØ§ØĄ immich", + "cache_settings_clear_cache_button": "Ų…ØŗØ­ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ", + "cache_settings_clear_cache_button_title": "ŲŠŲ‚ŲˆŲ… Ø¨Ų…ØŗØ­ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„ØĒØˇØ¨ŲŠŲ‚.ØŗŲŠØ¤ØĢØą Ų‡Ø°Ø§ Ø¨Ø´ŲƒŲ„ ŲƒØ¨ŲŠØą ØšŲ„Ų‰ ØŖØ¯Ø§ØĄ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ø­ØĒŲ‰ ØĨؚاد؊ Ø¨Ų†Ø§ØĄ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ.", + "cache_settings_duplicated_assets_clear_button": "ŲˆØ§ØļØ­", + "cache_settings_duplicated_assets_subtitle": "Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Ø§Ų„Ų„ØĒ؊ ØĒŲ… ØĒØŦØ§Ų‡Ų„Ų‡Ø§ ؁؊ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", + "cache_settings_duplicated_assets_title": "Ø§Ų„Ø§ØĩŲˆŲ„ Ø§Ų„Ų…ŲƒØąØąØŠ ({count})", + "cache_settings_statistics_album": "Ų…ŲƒØĒØ¨Ų‡ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØĩØēØąŲ‡", + "cache_settings_statistics_full": "ØĩŲˆØą ŲƒØ§Ų…Ų„ØŠ", + "cache_settings_statistics_shared": "ØĩŲˆØąØŠ ØŖŲ„Ø¨ŲˆŲ… Ų…Ø´ØĒØąŲƒØŠ", + "cache_settings_statistics_thumbnail": "Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ", + "cache_settings_statistics_title": "Ø§ØŗØĒØŽØ¯Ø§Ų… Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ", + "cache_settings_subtitle": "ØĒØ­ŲƒŲ… ؁؊ ØŗŲ„ŲˆŲƒ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„ØĒØˇØ¨ŲŠŲ‚ Immich Ø§Ų„ØŦŲˆØ§Ų„", + "cache_settings_tile_subtitle": "Ø§Ų„ØĒØ­ŲƒŲ… ؁؊ ØŗŲ„ŲˆŲƒ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø­Ų„ŲŠ", + "cache_settings_tile_title": "Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø­Ų„ŲŠ", + "cache_settings_title": "ØĨؚداداØĒ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ", "camera": "Ø§Ų„ŲƒØ§Ų…ŲŠØąØ§", "camera_brand": "ØšŲ„Ø§Ų…ØŠ Ø§Ų„ŲƒØ§Ų…ŲŠØąØ§ Ø§Ų„ØĒØŦØ§ØąŲŠØŠ", "camera_model": "ØˇØąØ§Ø˛ Ø§Ų„ŲƒØ§Ų…ŲŠØąØ§", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲˆØĩ؁", "cast": "بØĢ", "cast_description": "ØļØ¨Øˇ ؈ØŦŲ‡Ø§ØĒ Ø§Ų„Ø¨ØĢ Ø§Ų„Ų…ØĒŲˆŲØąØŠ", - "change": "ØĒØēŲŠŲŠØą", "change_date": "ØēŲŠŲ‘Øą Ø§Ų„ØĒØ§ØąŲŠØŽ", "change_description": "ØĒØēŲŠŲŠØą Ø§Ų„ŲˆØĩ؁", "change_display_order": "ØĒØēŲŠŲŠØą ØĒØąØĒŲŠØ¨ Ø§Ų„ØšØąØļ", + "change_expiration_time": "ØĒØēŲŠŲŠØą ŲˆŲ‚ØĒ Ø§Ų†ØĒŲ‡Ø§ØĄ Ø§Ų„ØĩŲ„Ø§Ø­ŲŠØŠ", "change_location": "ØēŲŠŲ‘Øą Ø§Ų„Ų…ŲˆŲ‚Øš", "change_name": "ØĒØēŲŠŲŠØą Ø§Ų„ØĨØŗŲ…", "change_name_successfully": "ØĒŲ… ØĒØēŲŠŲŠØą Ø§Ų„Ø§ØŗŲ… Ø¨Ų†ØŦاح", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą ØēŲŠØą Ų…ØˇØ§Ø¨Ų‚ØŠ", "change_password_form_reenter_new_password": "ØŖØšØ¯ ØĨØ¯ØŽØ§Ų„ ŲƒŲ„Ų…ØŠ Ų…ØąŲˆØą ØŦØ¯ŲŠØ¯ØŠ", "change_pin_code": "ØĒØēŲŠŲŠØą ØąŲ…Ø˛ PIN", + "change_trigger": "ØĒØēŲŠŲŠØą Ø§Ų„Ų…ŲØšŲ„", + "change_trigger_prompt": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØ§ŲƒØ¯ Ø§Ų†Ųƒ ØĒØąŲŠØ¯ ØĒØēŲŠŲŠØą Ø§Ų„Ų…ŲØšŲ„ØŸ Ų‡Ø°Ø§ ØŗŲŠØ˛ŲŠŲ„ ŲƒŲ„ Ø§Ų„Ø§ØŦØąØ§ØĻاØĒ ŲˆØ§Ų„ØĒØĩŲŲŠØ§ØĒ.", "change_your_password": "ØēŲŠØą ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "changed_visibility_successfully": "ØĒŲ… ØĒØēŲŠŲŠØą Ø§Ų„ØąØ¤ŲŠØŠ Ø¨Ų†ØŦاح", "charging": "Ø§Ų„Ø´Ø­Ų†", "charging_requirement_mobile_backup": "؊ØĒØˇŲ„Ø¨ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ ØŖŲ† ŲŠŲƒŲˆŲ† Ø§Ų„ØŦŲ‡Ø§Ø˛ Ų‚ŲŠØ¯ Ø§Ų„Ø´Ø­Ų†", + "check_corrupt_asset_backup": "Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† ؈ØŦŲˆØ¯ Ų†ØŗØŽ احØĒŲŠØ§ØˇŲŠØŠ ŲØ§ØŗØ¯ØŠ Ų„Ų„Ø§ØĩŲˆŲ„", + "check_corrupt_asset_backup_button": "اØŦØąØ§ØĄ ŲØ­Øĩ", + "check_corrupt_asset_backup_description": "Ų‚Ų… بØĨØŦØąØ§ØĄ Ų‡Ø°Ø§ Ø§Ų„ŲØ­Øĩ ŲŲ‚Øˇ ØšØ¨Øą Ø´Ø¨ŲƒØŠ Wi-Fi ŲˆØ¨ØšØ¯ Ų†ØŗØŽ ØŦŲ…ŲŠØš Ø§Ų„ØŖØĩŲˆŲ„ احØĒŲŠØ§ØˇŲŠŲ‹Ø§. Ų‚Ø¯ ŲŠØŗØĒØēØąŲ‚ Ø§Ų„ØĨØŦØąØ§ØĄ بØļØš Ø¯Ų‚Ø§ØĻŲ‚.", "check_logs": "ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŗØŦŲ„Ø§ØĒ", "checksum": "Ų…ØŦŲ…ŲˆØš Ø§Ų„ØĒØ­Ų‚Ų‚", - "choose": "ا؎ØĒŲŠØ§Øą", "choose_matching_people_to_merge": "ا؎ØĒØą Ø§Ų„ØŖØ´ØŽØ§Øĩ Ø§Ų„Ų…ØĒØˇØ§Ø¨Ų‚ŲŠŲ† Ų„Ø¯Ų…ØŦŲ‡Ų…", "city": "Ø§Ų„Ų…Ø¯ŲŠŲ†ØŠ", "cleanup_confirm_description": "Immich ؈ØŦد {count} اØĩŲˆŲ„ (Ø§Ų†Ø´ØĻØĒ Ų‚Ø¨Ų„ {date}) ØĒŲ… ØŽØ˛Ų†Ų‡Ø§ احØĒŲŠØ§ØˇŲŠØ§ Ø§Ų„Ų‰ Ø§Ų„ØŽØ§Ø¯Ų…. Ø§Ø˛Ø§Ų„ØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ Ų…Ų† Ų‡Ø°Ø§ Ø§Ų„ØŦŲ‡Ø§Ø˛?", @@ -719,10 +774,11 @@ "clear": "ØĨØŽŲ„Ø§ØĄ", "clear_all": "ØĨØŽŲ„Ø§ØĄ Ø§Ų„ŲƒŲ„", "clear_all_recent_searches": "Ų…ØŗØ­ ØŦŲ…ŲŠØš ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„ØŖØŽŲŠØąØŠ", - "clear_failed_count": "ŲØ´Ų„ Ø§Ų„Ų…ØŗØ­ ({count})", "clear_file_cache": "Ų…ØŗØ­ Ø°Ø§ŲƒØąØŠ Ø§Ų„ØĒØŽØ˛ŲŠŲ† Ø§Ų„Ų…Ø¤Ų‚ØĒ Ų„Ų„Ų…Ų„ŲØ§ØĒ", "clear_message": "ØĨØŽŲ„Ø§ØĄ Ø§Ų„ØąØŗØ§Ų„ØŠ", "clear_value": "ØĨØŽŲ„Ø§ØĄ Ø§Ų„Ų‚ŲŠŲ…ØŠ", + "client_cert_dialog_msg_confirm": "Ø­ØŗŲ†Ø§", + "client_cert_enter_password": "Ø§Ø¯ØŽŲ„ ŲƒŲ„Ų…ØŠ ØŗØą", "client_cert_import": "Ø§ØŗØĒŲŠØąØ§Ø¯", "client_cert_import_success_msg": "ØĒŲ… Ø§ØŗØĒŲŠØąØ§Ø¯ Ø´Ų‡Ø§Ø¯ØŠ Ø§Ų„ØšŲ…ŲŠŲ„", "client_cert_invalid_msg": "؅؄؁ Ø´Ų‡Ø§Ø¯ØŠ ØšŲ…ŲŠŲ„ ØēŲŠØą ØĩØ§Ų„Ø­ØŠ Ø§Ųˆ ŲƒŲ„Ų…ØŠ ØŗØą ØēŲŠØą ØĩØ­ŲŠØ­ØŠ", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ Ø´Ų‡Ø§Ø¯ØŠ Ø§Ų„ØšŲ…ŲŠŲ„", "client_cert_subtitle": "ŲŠØ¯ØšŲ… Øĩ؊Øē PKCS12 (.p12, .pfx)ŲŲ‚Øˇ. Ø§ØŗØĒŲŠØąØ§Ø¯/Ø§Ø˛Ø§Ų„ØŠ Ø§Ų„Ø´Ų‡Ø§Ø¯Ø§ØĒ Ų…ØĒاح ŲŲ‚Øˇ Ų‚Ø¨Ų„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", "client_cert_title": "Ø´Ų‡Ø§Ø¯ØŠ Ų…ØŗØĒØŽØ¯Ų… SSL [ØĒØŦØąŲŠØ¨ŲŠØŠ]", + "clockwise": "باØĒØŦØ§Ų‡ ØšŲ‚Ø§ØąØ¨ Ø§Ų„ØŗØ§ØšØŠ", "close": "ØĨØēŲ„Ø§Ų‚", "collapse": "ØˇŲŠ", "collapse_all": "ØˇŲŠŲ‘ Ø§Ų„ŲƒŲ„", "color": "Ø§Ų„Ų„ŲˆŲ†", + "color_theme": "Ų†Ų…Øˇ Ø§Ų„ØŖŲ„ŲˆØ§Ų†", "command": "Ø§Ų…Øą", "command_palette_prompt": "اؚØĢØą Ø¨ØŗØąØšØŠ ØšŲ„Ų‰ Ø§Ų„ØĩŲØ­Ø§ØĒ ØŖŲˆ Ø§Ų„ØĨØŦØąØ§ØĄØ§ØĒ ØŖŲˆ Ø§Ų„ØŖŲˆØ§Ų…Øą", "command_palette_to_close": "Ų„Ų„Ø§ØēŲ„Ø§Ų‚", @@ -747,13 +805,12 @@ "comments_are_disabled": "Ø§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ Ų…ØšØˇŲ„ØŠ", "common_create_new_album": "ØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ… ØŦØ¯ŲŠØ¯", "completed": "Ø§ŲƒØĒŲ…Ų„", - "configuration": "اؚداداØĒ", "confirm": "ØĒØŖŲƒŲŠØ¯", "confirm_admin_password": "ØĒØŖŲƒŲŠØ¯ ŲƒŲ„Ų…ØŠ Ų…ØąŲˆØą Ø§Ų„Ų…ØŗØ¤ŲˆŲ„", "confirm_delete_face": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų ؈ØŦŲ‡ {name} Ų…Ų† Ø§Ų„ØŖØĩŲˆŲ„ØŸ", "confirm_delete_shared_link": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„ØąØ§Ø¨Øˇ Ø§Ų„Ų…Ø´ØĒØąŲƒØŸ", "confirm_keep_this_delete_others": "ØŗŲŠØĒŲ… Ø­Ø°Ų ØŦŲ…ŲŠØš Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„ØŖØŽØąŲ‰ ؁؊ Ø§Ų„Ų…ØŦŲ…ŲˆØšØŠ Ø¨Ø§ØŗØĒØĢŲ†Ø§ØĄ Ų‡Ø°Ø§ Ø§Ų„ØŖØĩŲ„. Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø§Ų„Ų…ØĒابؚ؊؟", - "confirm_new_pin_code": "ØĒØŖŲƒŲŠØ¯ ØąŲ…Ø˛ PIN Ø§Ų„ØŦØ¯ŲŠØ¯", + "confirm_new_pin_code": "ØĢبØĒ ØąŲ…Ø˛ PIN Ø§Ų„ØŦØ¯ŲŠØ¯", "confirm_password": "ØĒØŖŲƒŲŠØ¯ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", "confirm_tag_face": "Ų‡Ų„ ØĒØąŲŠØ¯ ؈ØļØš ØšŲ„Ø§Ų…ØŠ ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ŲˆØŦŲ‡ {name}؟", "confirm_tag_face_unnamed": "Ų‡Ų„ ØĒØąŲŠØ¯ ؈ØļØš ØšŲ„Ø§Ų…ØŠ ØšŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ŲˆØŦŲ‡ØŸ", @@ -762,16 +819,19 @@ "contain": "Ų…Ø­ØĒŲˆØ§ØŠ", "context": "Ø§Ų„ØŗŲŠØ§Ų‚", "continue": "Ų…ØĒابؚ؊", - "control_bottom_app_bar_add_tags": "اØļØ§ŲØŠ ØšŲ„Ø§Ų…Ø§ØĒ", + "control_bottom_app_bar_create_new_album": "ØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ… ØŦØ¯ŲŠØ¯", + "control_bottom_app_bar_delete_from_immich": "Ø­Ø°Ų Ų…Ų† Immich", "control_bottom_app_bar_delete_from_local": "Ø­Ø°Ų Ų…Ų† Ø§Ų„ØŦŲ‡Ø§Ø˛", "control_bottom_app_bar_edit_location": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲˆØŦŲ‡ØŠ", "control_bottom_app_bar_edit_time": "ØĒØ­ØąŲŠØą Ø§Ų„ØĒØ§ØąŲŠØŽ ŲˆØ§Ų„ŲˆŲ‚ØĒ", + "control_bottom_app_bar_share_link": "Ų…Ø´Ø§ØąŲƒØŠ ØąØ§Ø¨Øˇ", + "control_bottom_app_bar_share_to": "Ų…Ø´Ø§ØąŲƒØŠ ØĨŲ„Ų‰", "control_bottom_app_bar_trash_from_immich": "Ø­Ø°ŲŲ‡ ŲˆŲ†Ų‚Ų„Ų‡ ؁؊ ØŗŲ„Ų‡ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "copied_image_to_clipboard": "ØĒŲ… Ų†ØŗØŽ Ø§Ų„ØĩŲˆØąØŠ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø§ŲØ¸ØŠ.", "copied_to_clipboard": "Ų†ØŗØŽ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø§ŲØ¸ØŠ!", "copy_error": "Ų†ØŗØŽ Ø§Ų„ØŽØˇØŖ", + "copy_file_path": "Ų†ØŗØŽ Ų…ØŗØ§Øą Ø§Ų„Ų…Ų„Ų", "copy_image": "Ų†ØŗØŽ Ø§Ų„ØĩŲˆØąØŠ", - "copy_json": "Ų†ØŗØŽ JSON", "copy_link": "Ų†ØŗØŽ Ø§Ų„ØąØ§Ø¨Øˇ", "copy_link_to_clipboard": "Ø§Ų†ØŗØŽ Ø§Ų„ØąØ§Ø¨Øˇ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø§ŲØ¸ØŠ", "copy_password": "Ų†ØŗØŽ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", @@ -789,6 +849,7 @@ "create_link_to_share": "ØĨŲ†Ø´Ø§ØĄ ØąØ§Ø¨Øˇ Ų„Ų„Ų…Ø´Ø§ØąŲƒØŠ", "create_link_to_share_description": "Ø§Ų„ØŗŲ…Ø§Ø­ Ų„ØŖŲŠ Ø´ØŽØĩ Ų„Ø¯ŲŠŲ‡ Ø§Ų„ØąØ§Ø¨Øˇ Ø¨Ų…Ø´Ø§Ų‡Ø¯ØŠ Ø§Ų„ØĩŲˆØąØŠ (Ø§Ų„ØĩŲˆØą) Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ", "create_new": "Ø§Ų†Ø´Ø§ØĄ ØŦØ¯ŲŠØ¯", + "create_new_face": "ØĨŲ†Ø´Ø§ØĄ ؈ØŦŲ‡ ØŦØ¯ŲŠØ¯", "create_new_person": "ØĨŲ†Ø´Ø§ØĄ Ø´ØŽØĩ ØŦØ¯ŲŠØ¯", "create_new_person_hint": "ØĒØšŲŠŲŠŲ† Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ Ų„Ø´ØŽØĩ ØŦØ¯ŲŠØ¯", "create_new_user": "ØĨŲ†Ø´Ø§ØĄ Ų…ØŗØĒØŽØ¯Ų… ØŦØ¯ŲŠØ¯", @@ -805,33 +866,37 @@ "created_at": "Ų…ØŽŲ„ŲˆŲ‚", "creating_linked_albums": "ØŦØ§ØąŲŠ ØĨŲ†Ø´Ø§ØĄ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…ØąØĒØ¨ØˇØŠ...", "crop": "Ų‚Øĩ", + "crop_aspect_ratio_fixed": "ØĒŲ… Ø§Ų„Ø§ØĩŲ„Ø§Ø­", "crop_aspect_ratio_free": "Ø­Øą", "crop_aspect_ratio_original": "اØĩŲ„ŲŠ", "crop_aspect_ratio_square": "Ų…ØąØ¨Øš", + "curated_object_page_title": "ØŖØ´ŲŠØ§ØĄ", "current_device": "Ø§Ų„ØŦŲ‡Ø§Ø˛ Ø§Ų„Ø­Ø§Ų„ŲŠ", "current_pin_code": "ØąŲ…Ø˛ PIN Ø§Ų„Ø­Ø§Ų„ŲŠ", "current_server_address": "ØšŲ†ŲˆØ§Ų† Ø§Ų„ØŽØ§Ø¯Ų… Ø§Ų„Ø­Ø§Ų„ŲŠ", "custom_date": "ØĒØ§ØąŲŠØŽ Ų…ØŽØĩØĩ", "custom_locale": "Ų„ØēØŠ Ų…ØŽØĩØĩØŠ", "custom_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ, Ø§Ų„ØŖŲˆŲ‚Ø§ØĒ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„Ų„ØēØŠ ŲˆØ§Ų„Ų…Ų†ØˇŲ‚ØŠ Ø§Ų„Ų…ØŽØĒØ§ØąŲ‡", + "custom_url": "ØąØ§Ø¨Øˇ Ų…ØŽØĩØĩ", "cutoff_date_description": "احØĒŲØ¸ Ø¨Ø§Ų„ØĩŲˆØą Ų…Ų† ØĸØŽØąâ€Ļ", "cutoff_day": "{count, plural, one {ŲŠŲˆŲ…} other {Ø§ŲŠØ§Ų…}}", "cutoff_year": "{count, plural, one {ØŗŲ†ØŠ} other {ØŗŲ†ŲˆØ§ØĒ}}", + "daily_title_text_date": "E ، MMM DD", + "daily_title_text_date_year": "E ، MMM DD ، yyyy", "dark": "Ų…ØšØĒŲ…", "dark_theme": "ØĒØ¨Ø¯ŲŠŲ„ Ø§Ų„Ų…Ø¸Ų‡Øą ØĨŲ„Ų‰ Ø§Ų„Ø¯Ø§ŲƒŲ†", "date": "ØĒØ§ØąŲŠØŽ", "date_after": "Ø§Ų„ØĒØ§ØąØŽ بؚد", "date_and_time": "Ø§Ų„ØĒØ§ØąŲŠØŽ ؈ Ø§Ų„ŲˆŲ‚ØĒ", "date_before": "Ø§Ų„ØĒØ§ØąŲŠØŽ Ų‚Ø¨Ų„", - "date_of_birth": "ØĒØ§ØąŲŠØŽ Ø§Ų„Ų…ŲŠŲ„Ø§Ø¯", + "date_format": "E ، Lll D ، Y â€ĸ H: MM A", "date_of_birth_saved": "ØĒŲ… Ø­ŲØ¸ ØĒØ§ØąŲŠØŽ Ø§Ų„Ų…ŲŠŲ„Ø§Ø¯ Ø¨Ų†ØŦاح", "date_range": "Ų†ØˇØ§Ų‚ Ø§Ų„Ų…ŲˆØšØ¯", - "date_time_original": "ØĒØ§ØąŲŠØŽ/ŲˆŲ‚ØĒ Ø§Ų„Ø§ØĩŲ„ŲŠ", "day": "ŲŠŲˆŲ…", "days": "Ø§ŲŠØ§Ų…", "deduplicate_all": "ØĨŲ„ØēØ§ØĄ ØĒŲƒØąØ§Øą Ø§Ų„ŲƒŲ„", - "default_quality_subtitle": "Ø§Ų„ØŦŲˆØ¯ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ØŠ ØšŲ†Ø¯ Ø§Ų„ØļØēØˇ ØšŲ„Ų‰ \"Ų…Ø´Ø§ØąŲƒØŠ\". اØļØēØˇ Ų…ØˇŲˆŲ„Ø§Ų‹ ØšŲ„Ų‰ Ø˛Øą Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų„Ø§ØŽØĒŲŠØ§Øą Ø§Ų„ØŦŲˆØ¯ØŠ ؁؊ ŲƒŲ„ Ų…ØąØŠ.", - "default_share_quality": "ØŦŲˆØ¯ØŠ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„Ø§ŲØĒØąØ§ØļŲŠØŠ", + "default_locale": "Ø§Ų„ØĨؚداداØĒ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ Ø§Ų„Ø§ŲØĒØąØ§ØļŲŠØŠ", + "default_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„ØĨؚداداØĒ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ Ų„Ų„Ų…ØĒØĩŲØ­", "delete": "Ø­Ø°Ų", "delete_action_confirmation_message": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„Ų…Ų„ŲØŸ Ų‡Ø°Ø§ ØŗØ¤Ø¯ŲŠ Ø§Ų„Ų‰ Ų†Ų‚Ų„ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ų‰ ØŗŲ„ØŠ Ų…Ų‡Ų…Ų„Ø§ØĒ Ø§Ų„ØŽØ§Ø¯Ų… ŲˆØŗŲŠØĒŲ… Ø§Ø´ØšØ§ØąŲƒ Ø§Ų† ŲƒŲ†ØĒ ØĒØąŲŠØ¯ Ø­Ø°ŲŲ‡ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛", "delete_action_prompt": "ØĒŲ… Ø­Ø°Ų {count}", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Ų‡Ø°Ų‡ Ø§Ų„ØšŲ†Ø§ØĩØą ØŗŲŠØĒŲ… Ø­Ø°ŲŲ‡Ø§ Ø¨Ø´ŲƒŲ„ داØĻŲ… Ų…Ų† Immich ؈ Ų…Ų† ØŦŲ‡Ø§Ø˛Ųƒ", "delete_dialog_alert_local": "Ø§Ų„ØšŲ†Ø§ØĩØą Ø§Ų„ØĒ؊ ØŗŲŠØĒŲ… Ø­Ø°ŲŲ‡Ø§ Ų…Ų† ØŦŲ‡Ø§Ø˛Ųƒ ŲˆŲ„ŲƒŲ† ØĒØ¨Ų‚Ų‰ Ų…ŲˆØŦŲˆØ¯Ų‡ ؁؊ ØŽØ§Ø¯Ų… Immich", "delete_dialog_alert_local_non_backed_up": "بؚØļ Ø§Ų„ØšŲ†Ø§ØĩØą ØēŲŠØą Ų…Ø¯ØšŲˆŲ…ØŠ Ø¨Ų†ØŗØŽØŠ احØĒŲŠØ§ØˇŲŠØŠ ØšŲ„Ų‰ Immich ŲˆØŗŲŠØĒŲ… ØĨØ˛Ø§Ų„ØĒŲ‡Ø§ Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§ Ų…Ų† ØŦŲ‡Ø§Ø˛Ųƒ", + "delete_dialog_alert_remote": "Ø§Ų„ØšŲ†Ø§ØĩØą Ø§Ų„ØĒ؊ ØŗŲŠØĒŲ… Ø­Ø°ŲŲ‡Ø§ Ø¨Ø´ŲƒŲ„ داØĻŲ… Ų…Ų† ØĒØˇØ¨ŲŠŲ‚", + "delete_dialog_ok_force": "Ø§Ø­Ø°Ų ØšŲ„Ų‰ ØŖŲŠ Ø­Ø§Ų„", "delete_dialog_title": "Ø§Ų„Ø­Ø°Ų Ø¨Ø´ŲƒŲ„ Ų†Ų‡Ø§ØĻ؊", "delete_duplicates_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ų‡Ø°Ų‡ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§ØŸ", "delete_face": "Ø­Ø°Ų Ø§Ų„ŲˆØŦŲ‡", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ø§Ų„ØšŲ„Ø§Ų…ØŠ {tagName}؟", "delete_user": "Ø­Ø°Ų Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "deleted_shared_link": "ØĒŲ… Ø­Ø°Ų Ø§Ų„ØąØ§Ø¨Øˇ Ø§Ų„Ų…Ø´Ø§ØąŲƒ", + "deletes_missing_assets": "Ø­Ø°Ų Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ Ų…Ų† Ø§Ų„Ų‚ØąØĩ", "description": "؈Øĩ؁", + "description_input_hint_text": "اØļ؁ ؈ØĩŲØ§...", + "description_input_submit_error": "ØŽØˇØŖ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲˆØĩ؁ ، ØĒØ­Ų‚Ų‚ Ų…Ų† Ø§Ų„ØŗØŦŲ„ Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„ØĒŲØ§ØĩŲŠŲ„", "deselect_all": "Ø§Ų„ØēØ§ØĄ ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„", "details": "ØĒŲØ§ØĩŲŠŲ„", "direction": "Ø§Ų„ØĨØĒØŦØ§Ų‡", "disable": "Ø§Ø¨ØˇØ§Ų„", "disabled": "Ų…ØšØˇŲ„", + "disallow_edits": "Ų…Ų†Øš Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ", "discord": "Ø¯ØŗŲƒŲˆØąØ¯", "discover": "Ø§ŲƒØĒØ´Ų", "discovered_devices": "اØŦŲ‡Ø˛ØŠ Ų…ŲƒØĒØ´ŲØŠ", "dismiss_all_errors": "ØĒØŦØ§Ų‡Ų„ ŲƒØ§ŲØŠ Ø§Ų„ØŖØŽØˇØ§ØĄ", + "dismiss_error": "ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØŽØˇØŖ", "display_options": "ØšØąØļ Ø§Ų„ØŽŲŠØ§ØąØ§ØĒ", "display_order": "ØšØąØļ Ø§Ų„ØĒØąØĒŲŠØ¨", "display_original_photos": "ØšØąØļ Ø§Ų„ØĩŲˆØą Ø§Ų„ØŖØĩŲ„ŲŠØŠ", @@ -876,9 +948,11 @@ "documentation": "Ø§Ų„ŲˆØĢاØĻŲ‚", "done": "ØĒŲ…", "download": "ØĒŲ†Ø˛ŲŠŲ„", + "download_action_prompt": "؊ØĒŲ… ØĒŲ†Ø˛ŲŠŲ„ {count} ؅؄؁", "download_canceled": "Ø§Ų„Øē؊ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_complete": "Ø§ŲƒØĒŲ…Ų„ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_enqueue": "ØĒŲ†Ø˛ŲŠŲ„ ؁؊ Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„Ø§Ų†ØĒØ¸Ø§Øą", + "download_error": "ØŽØˇØ§ ؁؊ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_failed": "ŲØ´Ų„ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_finished": "Ø§Ų†ØĒŲ‡Ų‰ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_include_embedded_motion_videos": "Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø¯Ų…ØŦØŠ", @@ -888,15 +962,15 @@ "download_paused": "ØĒŲˆŲ‚Ų Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_settings": "Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "download_settings_description": "ØĨØ¯Ø§ØąØŠ Ø§Ų„ØĨؚداداØĒ Ø§Ų„Ų…ØĒØšŲ„Ų‚ØŠ بØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ", + "download_started": "Ø¨Ø¯ØŖ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", + "download_sucess": "Ų†ØŦØ­ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", + "download_sucess_android": "ØĒŲ… ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ Ø§Ų„Ų‰ DCIM/Immich", "download_waiting_to_retry": "Ø§Ų„Ø§Ų†ØĒØ¸Ø§Øą Ų„Ø§ØšØ§Ø¯ØŠ Ø§Ų„Ų…Ø­Ø§ŲˆŲ„ØŠ", "downloading": "ØŦØ§ØąŲ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", "downloading_asset_filename": "ØŦØ§ØąŲŠ ØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„Ø§ØĩŲ„ {filename}", "downloading_from_icloud": "Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„ Ų…Ų† iCloud", "downloading_media": "ØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ", - "drag_to_reorder": "Ø§ØŗØ­Ø¨ Ų„ØĨؚاد؊ Ø§Ų„ØĒØąØĒŲŠØ¨", "drop_files_to_upload": "Ų‚Ų… بØĨØŗŲ‚Ø§Øˇ Ø§Ų„Ų…Ų„ŲØ§ØĒ ؁؊ ØŖŲŠ Ų…ŲƒØ§Ų† Ų„ØąŲØšŲ‡Ø§", - "duplicate": "ØĒŲƒØąØ§Øą", - "duplicate_workflow": "ØĒŲƒØąØ§Øą ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "duplicates": "Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ", "duplicates_description": "Ų‚Ų… Ø¨Ø­Ų„ ŲƒŲ„ Ų…ØŦŲ…ŲˆØšØŠ Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„ØĨØ´Ø§ØąØŠ ØĨŲ„Ų‰ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ، ØĨŲ† ؈ØŦدØĒ.", "duration": "Ø§Ų„Ų…Ø¯ØŠ", @@ -908,7 +982,9 @@ "edit_date_and_time": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØĒØ§ØąŲŠØŽ ŲˆØ§Ų„ŲˆŲ‚ØĒ", "edit_date_and_time_action_prompt": "ØĒŲ… ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØĒØ§ØąŲŠØŽ ŲˆØ§Ų„ŲˆŲ‚ØĒ Ų„{count} ؅؄؁(اØĒ)", "edit_date_and_time_by_offset": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØĒØ§ØąŲŠØŽ Ø­ØŗØ¨ Ų‚ŲŠŲ…ØŠ Ø§Ø˛Ø§Ø­ØŠ Ų…ØšŲŠŲ†ØŠ", + "edit_date_and_time_by_offset_interval": "Ų†ØˇØ§Ų‚ Ø§Ų„ØĒØ§ØąŲŠØŽ Ø§Ų„ØŦØ¯ŲŠØ¯: {from} - {to}", "edit_description": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ŲˆØĩ؁", + "edit_description_prompt": "Ø§Ų„ØąØŦØ§ØĄ ا؎ØĒŲŠØ§Øą ؈Øĩ؁ ØŦØ¯ŲŠØ¯:", "edit_exclusion_pattern": "ØĒØšØ¯ŲŠŲ„ Ų†Ų…Øˇ Ø§Ų„Ø§ØŗØĒبؚاد", "edit_faces": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„ŲˆØŦŲˆŲ‡", "edit_key": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„Ų…ŲØĒاح", @@ -923,6 +999,9 @@ "edit_user": "ØĒØšØ¯ŲŠŲ„ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "edit_workflow": "ØĒØšØ¯ŲŠŲ„ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "editor": "Ų…Ø­ØąØą", + "editor_close_without_save_prompt": "Ų„Ų† ؊ØĒŲ… Ø­ŲØ¸ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ", + "editor_close_without_save_title": "ØĨØēŲ„Ø§Ų‚ Ø§Ų„Ų…Ø­ØąØąØŸ", + "editor_confirm_reset_all_changes": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØĨؚاد؊ ØļØ¨Øˇ ØŦŲ…ŲŠØš Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ؟", "editor_discard_edits_confirm": "ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ", "editor_discard_edits_prompt": "Ų„Ø¯ŲŠŲƒ ØĒØšØ¯ŲŠŲ„Ø§ØĒ ØēŲŠØą Ų…Ø­ŲŲˆØ¸ØŠ. Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† ØąØēبØĒ؃ ؁؊ ØĒØŦØ§Ų‡Ų„Ų‡Ø§ØŸ", "editor_discard_edits_title": "ØĒØŦØ§Ų‡Ų„ Ø§Ų„ØĒØšØ¯ŲŠŲ„Ø§ØĒ؟", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "ØŖØ¯ØŽŲ„ ØąŲ…Ø˛ PIN Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "enter_your_pin_code_subtitle": "ØŖØ¯ØŽŲ„ ØąŲ…Ø˛ PIN Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ Ų„Ų„ŲˆØĩŲˆŲ„ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„", "error": "ØŽØˇØŖ", + "error_change_sort_album": "ŲØ´Ų„ ؁؊ ØĒØēŲŠŲŠØą ØĒØąØĒŲŠØ¨ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "error_delete_face": "حدØĢ ØŽØˇØŖ ؁؊ Ø­Ø°Ų Ø§Ų„ŲˆØŦŲ‡ Ų…Ų† Ø§Ų„ØŖØĩŲˆŲ„", + "error_getting_places": "ØŽØˇØŖ ØŖØĢŲ†Ø§ØĄ Ø§ØŗØĒØąØŦاؚ Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„Ų…ŲˆØ§Ų‚Øš", "error_loading_albums": "ØŽØˇØŖ ؁؊ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "error_loading_image": "حدØĢ ØŽØˇØŖ ØŖØĢŲ†Ø§ØĄ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ", "error_loading_partners": "ØŽØˇØŖ بØĒØ­Ų…ŲŠŲ„ Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„Ø´ØąŲƒØ§ØĄ: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "ØĒØšØ°Øą ØĨØ˛Ø§Ų„ØŠ ؅؁ØĒاح Ø§Ų„Ų…Ų†ØĒØŦ", "failed_to_reset_pin_code": "ŲØ´Ų„ اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN", "failed_to_stack_assets": "ŲØ´Ų„ ؁؊ ØĒŲƒØ¯ŲŠØŗ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ", - "failed_to_tag_assets": "ŲØ´Ų„ ؁؊ ؈ØļØš ØšŲ„Ø§Ų…Ø§ØĒ ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„", "failed_to_unstack_assets": "ŲØ´Ų„ ؁؊ ؁ØĩŲ„ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ", "failed_to_update_notification_status": "ŲØ´Ų„ ؁؊ ØĒØ­Ø¯ŲŠØĢ Ø­Ø§Ų„ØŠ Ø§Ų„ØĨØ´ØšØ§Øą", "incorrect_email_or_password": "Ø¨ØąŲŠØ¯ ØŖŲˆ ŲƒŲ„Ų…ØŠ Ų…ØąŲˆØą ØēŲŠØą ØĩØ­ŲŠØ­ØŠ", @@ -1090,10 +1170,18 @@ "exif": "Exif (Øĩ؊ØēØŠ ؅؄؁ ØĩŲˆØąŲŠ Ų‚Ø§Ø¨Ų„ Ų„Ų„ØĒØ¨Ø§Ø¯Ų„)", "exif_bottom_sheet_description": "اØļ؁ ؈ØĩŲØ§...", "exif_bottom_sheet_description_error": "ØŽØˇØŖ ؁؊ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲˆØĩ؁", + "exif_bottom_sheet_details": "ØĒŲØ§ØĩŲŠŲ„", + "exif_bottom_sheet_location": "Ų…ŲˆŲ‚Øš", "exif_bottom_sheet_no_description": "Ų„Ø§ ؊؈ØŦد ؈Øĩ؁", + "exif_bottom_sheet_people": "Ø§Ų„Ų†Ø§Øŗ", + "exif_bottom_sheet_person_add_person": "اØļ؁ Ø§ØŗŲ…Ø§", "exit_slideshow": "ØŽØąŲˆØŦ Ų…Ų† Ø§Ų„ØšØąØļ Ø§Ų„ØĒŲ‚Ø¯ŲŠŲ…ŲŠ", "expand": "ØĒŲˆØŗØšØŠ", "expand_all": "ØĒŲˆØŗŲŠØš Ø§Ų„ŲƒŲ„", + "experimental_settings_new_asset_list_subtitle": "ØŖØšŲ…Ø§Ų„ ØŦØ§ØąŲŠØŠ", + "experimental_settings_new_asset_list_title": "ØĒŲ…ŲƒŲŠŲ† Ø´Ø¨ŲƒØŠ Ø§Ų„ØĩŲˆØą Ø§Ų„ØĒØŦØąŲŠØ¨ŲŠØŠ", + "experimental_settings_subtitle": "Ø§ØŗØĒØŽØ¯Ø§Ų… ØšŲ„Ų‰ Ų…ØŗØ¤ŲˆŲ„ŲŠØĒ؃ Ø§Ų„ØŽØ§ØĩØŠ!", + "experimental_settings_title": "ØĒØŦØąŲŠØ¨ŲŠ", "expire_after": "ØĒŲ†ØĒŲ‡ŲŠ بؚد", "expired": "Ų…Ų†ØĒŲ‡ŲŠ Ø§Ų„ØĩŲ„Ø§Ø­ŲŠØŠ", "expires_date": "ØĒŲ†ØĒŲ‡ŲŠ Ø§Ų„ØĩŲ„Ø§Ø­ŲŠØŠ ؁؊ {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "ØĒØĩØ¯ŲŠØą ŲƒŲ€ JSON", "export_database": "ØĒØĩØ¯ŲŠØą Ų‚Ø§ØšØ¯ØŠ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ", "export_database_description": "ØĒØĩØ¯ŲŠØą Ų‚Ø§ØšØ¯ØŠ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ų…Ų† Ų†ŲˆØš SQLite", - "exposure_time": "ŲˆŲ‚ØĒ Ø§Ų„ØĒØšØąØļ", "extension": "Ø§Ų„ØĨŲ…ØĒداد", "external": "ØŽØ§ØąØŦ؊", "external_libraries": "Ø§Ų„Ų…ŲƒØĒباØĒ Ø§Ų„ØŽØ§ØąØŦŲŠØŠ", "external_network": "Ø´Ø¨ŲƒØŠ ØŽØ§ØąØŦŲŠØŠ", "external_network_sheet_info": "ØšŲ†Ø¯Ų…Ø§ Ų„Ø§ ؊ØĒŲˆØ§ØŦد ØšŲ„Ų‰ Ø´Ø¨ŲƒØŠ Wi-Fi Ø§Ų„Ų…ŲØļŲ„ØŠØŒ ؁ØĨŲ†Ų‡ ØŗŲŠØĒØĩŲ„ Ø¨Ø§Ų„ØŽØ§Ø¯Ų… Ų…Ų† ØŽŲ„Ø§Ų„ ØŖŲˆŲ„ ØšŲ†Ø§ŲˆŲŠŲ† URL ØŖØ¯Ų†Ø§Ų‡ Ø§Ų„ØĒ؊ ŲŠŲ…ŲƒŲ†Ų‡ Ø§Ų„ŲˆØĩŲˆŲ„ ØĨŲ„ŲŠŲ‡Ø§ØŒ Ø¨Ø¯ØĄŲ‹Ø§ Ų…Ų† Ø§Ų„ØŖØšŲ„Ų‰ ØĨŲ„Ų‰ Ø§Ų„ØŖØŗŲŲ„", - "f_number": "Ø§Ų„ŲØĒح؊ Ø§Ų„Ø¨Ø¤ØąŲŠØŠ", "face_unassigned": "ØēŲŠØą Ų…ØšŲŠŲ†", "failed": "ŲØ´Ų„", "failed_count": "ŲØ´Ų„: {count}", "failed_to_authenticate": "ŲØ´Ų„ ؁؊ Ø§Ų„Ų…ØĩØ§Ø¯Ų‚ØŠ", - "failed_to_delete_file": "ŲØ´Ų„ Ø­Ø°Ų Ø§Ų„Ų…Ų„Ų", "failed_to_load_assets": "ŲØ´Ų„ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲˆŲ„", "failed_to_load_folder": "ŲØ´Ų„ ØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ų…ØŦŲ„Ø¯", "favorite": "؅؁ØļŲ„", "favorite_action_prompt": "{count} اØļ؊؁ ØĨŲ„Ų‰ Ø§Ų„Ų…ŲØļŲ„Ø§ØĒ", "favorite_or_unfavorite_photo": "ØĒ؁ØļŲŠŲ„ ØŖŲˆ ØĨŲ„ØēØ§ØĄ ØĒ؁ØļŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ", "favorites": "Ø§Ų„Ų…ŲØļŲ„ØŠ", + "favorites_page_no_favorites": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…ŲØļŲ„ØŠ", "feature_photo_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…Ų…ŲŠØ˛ØŠ", "features": "Ø§Ų„Ų…ŲŠØ˛Ø§ØĒ", + "features_in_development": "Ø§Ų„Ų…ŲŠØ˛Ø§ØĒ Ų‚ŲŠØ¯ Ø§Ų„ØĒØˇŲˆŲŠØą", "features_setting_description": "ØĨØ¯Ø§ØąØŠ Ų…ŲŠØ˛Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", "file_name_or_extension": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų ØŖŲˆ Ø§Ų…ØĒØ¯Ø§Ø¯Ų‡", "file_name_text": "ØŖØŗŲ… Ø§Ų„Ų…Ų„Ų", + "file_name_with_value": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų: {file_name}", "file_size": "Ø­ØŦŲ… Ø§Ų„Ų…Ų„Ų", "filename": "Ø§ØŗŲ… Ø§Ų„Ų…Ų„Ų", "filetype": "Ų†ŲˆØš Ø§Ų„Ų…Ų„Ų", "filter": "ØĒØĩŲŲŠØŠ", + "filter_description": "Ø´ØąŲˆØˇ ØĒØĩŲŲŠØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…ØŗØĒŲ‡Ø¯ŲØŠ", "filter_people": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ø´ØŽØ§Øĩ", "filter_places": "ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ų…Ø§ŲƒŲ†", "filter_tags": "ØĒØĩŲŲŠØŠ Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ", "filters": "Ø§Ų„ØĒØĩŲŲŠØ§ØĒ", + "find_them_fast": "ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„ØšØĢŲˆØą ØšŲ„ŲŠŲ‡Ø§ Ø¨ØŗØąØšØŠ Ø¨Ø§Ų„Ø§ØŗŲ… Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„Ø¨Ø­ØĢ", "first": "Ø§Ų„Ø§ŲˆŲ„", "fix_incorrect_match": "ØĨØĩŲ„Ø§Ø­ Ø§Ų„Ų…ØˇØ§Ø¨Ų‚ØŠ ØēŲŠØą Ø§Ų„ØĩØ­ŲŠØ­ØŠ", - "focal_length": "Ø§Ų„Ø¨ØšØ¯ Ø§Ų„Ø¨Ø¤ØąŲŠ", "folder": "Ų…ØŦŲ„Ø¯", "folder_not_found": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯", "folders": "Ø§Ų„Ų…ØŦŲ„Ø¯Ø§ØĒ", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Ų†Ų‚Ų„ Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲØ¯ŲŠŲˆØ§ØĒ Ø§Ų„ØĒ؊ ØĒŲ… ØŽØ˛Ų†Ų‡Ø§ احØĒŲŠØ§ØˇŲŠØ§Ø§Ų„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ Ø§Ų„ØŽØ§ØĩŲ‡ بØŦŲ‡Ø§Ø˛Ųƒ Ų„ØĒØ­ØąŲŠØą Ø§Ų„Ų…ØŗØ§Ø­ØŠ. Ų†ØŗØŽŲƒ ØšŲ„Ų‰ Ø§Ų‰ØŽØ§Ø¯Ų… ØŗØĒØ¨Ų‚Ų‰ Ø¨ØŖŲ…Ø§Ų†.", "free_up_space_settings_subtitle": "ØĒØ­ØąŲŠØą ØŽØ˛Ų† Ø§Ų„ØŦŲ‡Ø§Ø˛", "full_path": "Ų…ØŗØ§Øą ŲƒØ§Ų…Ų„:{path}", - "full_path_or_folder": "Ø§Ų„Ų…ØŗØ§Øą Ø§Ų„ŲƒØ§Ų…Ų„ Ø§Ųˆ Ø§Ų„Ų…ØŦŲ„Ø¯", "gcast_enabled": "ŲƒŲˆŲƒŲ„ ŲƒØ§ØŗØĒ", "gcast_enabled_description": "ØĒŲ‚ŲˆŲ… Ų‡Ø°Ų‡ Ø§Ų„Ų…ŲŠØ˛ØŠ بØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ų…ŲˆØ§ØąØ¯ Ø§Ų„ØŽØ§ØąØŦŲŠØŠ Ų…Ų† Google Ø­ØĒŲ‰ ØĒØšŲ…Ų„.", "general": "ØšØ§Ų…", @@ -1168,6 +1256,7 @@ "group_owner": "ØĒØŦŲ…ŲŠØš Ø­ØŗØ¨ Ø§Ų„Ų…Ø§Ų„Ųƒ", "group_places_by": "ØĒØŦŲ…ŲŠØš Ø§Ų„ØŖŲ…Ø§ŲƒŲ† Ø­ØŗØ¨...", "group_year": "ØĒØŦŲ…ŲŠØš Ø­ØŗØ¨ Ø§Ų„ØŗŲ†ØŠ", + "haptic_feedback_switch": "ØĒŲ…ŲƒŲŠŲ† ØąØ¯ŲˆØ¯ Ø§Ų„ŲØšŲ„ Ø§Ų„Ų„Ų…ØŗŲŠØŠ", "haptic_feedback_title": "ØąØ¯ŲˆØ¯ ŲØšŲ„ Ų„Ų…ØŗŲŠØŠ", "has_quota": "Ų…Ø­Ø¯Ø¯ بحØĩØŠ", "hash_asset": "ØšŲ…Ų„ Hash Ų„Ų„ØŖØĩŲ„ (؄؄؅؄؁)", @@ -1188,12 +1277,29 @@ "hide_schema": "Ø§ØŽŲØ§ØĄ Ø§Ų„Ų…ØŽØˇØˇ", "hide_text_recognition": "Ø§ØŽŲØ§ØĄ Ø§Ų„ØĒØšØąŲ ØšŲ„Ų‰ Ø§Ų„Ų†Øĩ", "hide_unnamed_people": "ØĨØŽŲØ§ØĄ Ø§Ų„ØŖØ´ØŽØ§Øĩ Ø¨Ø¯ŲˆŲ† ØĨØŗŲ…", + "home_page_add_to_album_conflicts": "ØĒŲ…ØĒ ØĨØļØ§ŲØŠ {added} ØŖØĩŲˆŲ„ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… {album}. {failed} ØŖØĩŲˆŲ„ Ų…ŲˆØŦŲˆØ¯ØŠ Ø¨Ø§Ų„ŲØšŲ„ ؁؊ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ….", + "home_page_add_to_album_err_local": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØļØ§ŲØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø­ØĒŲ‰ Ø§Ų„ØĸŲ† ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_add_to_album_success": "ØĒŲ…ØĒ ØĨØļØ§ŲØŠ {added} ØŖØĩŲˆŲ„ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… {album}.", + "home_page_album_err_partner": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨØļØ§ŲØŠ ØŖØĩŲˆŲ„ Ø´ØąŲŠŲƒØŠ ØĨŲ„Ų‰ ØŖŲ„Ø¨ŲˆŲ… Ø­ØĒŲ‰ Ø§Ų„ØĸŲ† ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_archive_err_local": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØŖØąØ´ŲØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ Ø­ØĒŲ‰ Ø§Ų„ØĸŲ† ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_archive_err_partner": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØŖØąØ´ŲØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ø´ØąŲŠŲƒØŠ ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", "home_page_building_timeline": "Ø¨Ų†Ø§ØĄ Ø§Ų„ØŦØ¯ŲˆŲ„ Ø§Ų„Ø˛Ų…Ų†ŲŠ", + "home_page_delete_err_partner": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø­Ø°Ų Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ø´ØąŲŠŲƒØŠ ,ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_delete_remote_err_local": "Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ ؁؊ Ø§Ų„ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„Ø¨ØšŲŠØ¯ Ø§Ų„Ų…Ø­Ø°ŲˆŲØŒ ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_favorite_err_local": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĒ؁ØļŲŠŲ„ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ بؚد، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_favorite_err_partner": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ø´ØąŲŠŲƒØŠ Ø§Ų„Ų…ŲØļŲ„ØŠ بؚد ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_first_time_notice": "ØĨذا ŲƒØ§Ų†ØĒ Ų‡Ø°Ų‡ Ų‡ŲŠ Ø§Ų„Ų…ØąØŠ Ø§Ų„ØŖŲˆŲ„Ų‰ Ø§Ų„ØĒ؊ ØĒØŗØĒØŽØ¯Ų… ŲŲŠŲ‡Ø§ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ØŒ ŲŲŠØąØŦŲ‰ Ø§Ų„ØĒØŖŲƒØ¯ Ų…Ų† ا؎ØĒŲŠØ§Øą ØŖŲ„Ø¨ŲˆŲ… (ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ) احØĒŲŠØ§ØˇŲŠØŠ Ø­ØĒŲ‰ ؊ØĒŲ…ŲƒŲ† Ø§Ų„Ų…ØŽØˇØˇ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ų…Ų† Ų…Ų„ØĄ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ ŲŲŠŲ‡", + "home_page_locked_error_local": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ų†Ų‚Ų„ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„ØŒ ؊ØĒŲ… Ø§Ų„ØĒØŽØˇŲŠ", + "home_page_locked_error_partner": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ų†Ų‚Ų„ ØŖØĩŲˆŲ„ Ø§Ų„Ø´ØąŲŠŲƒ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„ØŒ ؊ØĒŲ… Ø§Ų„ØĒØŽØˇŲŠ", + "home_page_share_err_local": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„ØŖØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ ØšØ¨Øą Ø§Ų„ØąØ§Ø¨Øˇ ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "home_page_upload_err_limit": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĨŲ„Ø§ ØĒØ­Ų…ŲŠŲ„ 30 ØŖØ­Ø¯ Ø§Ų„ØŖØĩŲˆŲ„ ؁؊ ŲˆŲ‚ØĒ ŲˆØ§Ø­Ø¯ ، ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", "host": "Ø§Ų„Ų…Øļ؊؁", "hour": "ØŗØ§ØšØŠ", "hours": "ØŗØ§ØšØ§ØĒ", "id": "Ø§Ų„Ų…ØšØąŲ", "idle": "ØŽØ§Ų…Ų„", + "ignore_icloud_photos": "ØĒØŦØ§Ų‡Ų„ ØĩŲˆØą iCloud", + "ignore_icloud_photos_description": "Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØŽØ˛Ų†ØŠ ؁؊ Cloud Ų„Ų† ؊ØĒŲ… ØĒØ­Ų…ŲŠŲ„Ų‡Ø§ ØĨŲ„Ų‰ ØŽØ§Ø¯Ų… Immich", "image": "ØĩŲˆØąØŠ", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ ؁؊ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ Ų…Øš {person1} ؁؊ {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ ؁؊ {city}، {country} Ų…Øš {person1} ؈{person2} ؁؊ {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ ؁؊ {city}، {country} Ų…Øš {person1}، {person2}، ؈{person3} ؁؊ {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ ؁؊ {city}, {country} with {person1}, {person2}, Ų…Øš {additionalCount, number} ØĸØŽØąŲŠŲ† ؁؊ {date}", + "image_saved_successfully": "Ø§Ų„ØĩŲˆØą Ø­ŲŲØ¸ØĒ", + "image_viewer_page_state_provider_download_started": "Ø¨Ø¯ØŖ Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„", + "image_viewer_page_state_provider_download_success": "ØĒŲ… Ø§Ų„ØĒŲ†Ø˛ŲŠŲ„ Ø¨Ų†ØŦاح", + "image_viewer_page_state_provider_share_error": "ØŽØˇØŖ ؁؊ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ", "immich_logo": "Ø´ØšØ§Øą immich", "immich_web_interface": "ŲˆØ§ØŦŲ‡ØŠ ŲˆŲŠØ¨ immich", "import_from_json": "Ø§ØŗØĒŲŠØąØ§Ø¯ Ų…Ų† JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Ø­ØĩØŠ ŲØąØ¯ŲŠØŠ", "individual_shares": "Ø§Ų„Ų…Ø´Ø§ØąŲƒØ§ØĒ Ø§Ų„ŲØąØ¯ŲŠØŠ", "info": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ", - "integrity_checks": "ŲØ­ŲˆØĩاØĒ Ø§Ų„ØŗŲ„Ø§Ų…ØŠ", "interval": { "day_at_onepm": "ŲƒŲ„ ŲŠŲˆŲ… Ø§Ų„ØŗØ§ØšØŠ Ø§Ų„ŲˆØ§Ø­Ø¯ØŠ Ø¸Ų‡ØąØ§", "hours": "ŲƒŲ„ {hours, plural, one {ØŗØ§ØšØŠ} other {{hours, number} ØŗØ§ØšØŠ}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Øĩ؊ØēØŠ ØĒØ§ØąŲŠØŽ ØēŲŠØą ØĩØ§Ų„Ø­ØŠ", "invite_people": "Ø¯ØšŲˆØŠ Ø§Ų„ØŖØ´ØŽØ§Øĩ", "invite_to_album": "Ø¯ØšŲˆØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "ØŦØąØĒ ØšŲ…Ų„ŲŠØŠ Ø§Ų„ØŦŲ„Ø¨ ؁؊ {dateTime}", + "ios_debug_info_last_sync_at": "Ø§ØŽØą Ų…Ø˛Ø§Ų…Ų†ØŠ {dateTime}", + "ios_debug_info_no_processes_queued": "Ų„Ø§ ØĒ؈ØŦد ØšŲ…Ų„ŲŠØ§ØĒ ØŽŲ„ŲŲŠØŠ ؁؊ Ų‚Ø§ØĻŲ…ØŠ Ø§Ų„Ø§Ų†ØĒØ¸Ø§Øą", + "ios_debug_info_no_sync_yet": "Ų„Ų… ؊ØĒŲ… ØĒØ´ØēŲŠŲ„ ØŖŲŠ Ų…Ų‡Ų…ØŠ Ų…Ø˛Ø§Ų…Ų†ØŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ø­ØĒŲ‰ Ø§Ų„ØĸŲ†", + "ios_debug_info_processes_queued": "{count, plural, one {{count} ØšŲ…Ų„ŲŠØŠ ØŽŲ„ŲŲŠØŠ Ø§Ø¯ØŽŲ„ØĒØŠŲŲŠ ØˇØ§Ø¨ŲˆØą} other {{count} ØšŲ…Ų„ŲŠØ§ØĒ ØŽŲ„ŲŲŠØŠ Ø§Ø¯ØŽŲ„ØĒ ؁؊ ØˇØ§Ø¨ŲˆØą}}", + "ios_debug_info_processing_ran_at": "Ø§Ų„Ų…ØšØ§Ų„ØŦØŠ ØŦØąØĒ ؁؊ {dateTime}", "items_count": "{count, plural, one {# ØšŲ†ØĩØą} other {# ØšŲ†Ø§ØĩØą}}", "jobs": "Ø§Ų„ŲˆØ¸Ø§ØĻ؁", + "json_editor": "Ų…Ø­ØąØą JSON", + "json_error": "ØŽØˇØŖ JSON", "keep": "احØĒŲØ¸", "keep_albums": "Ø§Ų„Ø§Ø­ØĒŲØ§Ø¸ Ø¨Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "keep_albums_count": "Ø§Ų„Ø§Ø­ØĒŲØ§Ø¸ ب{count} {count, plural, one {Ø§Ų„Ø¨ŲˆŲ…} other {Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ}}", @@ -1259,12 +1375,14 @@ "leave": "Ų…ØēØ§Ø¯ØąØŠ", "leave_album": "اØĒØąŲƒ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…", "lens_model": "Ų†Ų…ŲˆØ°ØŦ Ø§Ų„ØšØ¯ØŗØ§ØĒ", - "less": "ØŖŲ‚Ų„", "let_others_respond": "دؚ Ø§Ų„ØĸØŽØąŲŠŲ† ŲŠØŗØĒØŦŲŠØ¨ŲˆŲ†", "level": "Ø§Ų„Ų…ØŗØĒŲˆŲ‰", "library": "Ų…ŲƒØĒب؊", "library_add_folder": "اØļØ§ŲØŠ Ų…ØŦŲ„Ø¯", "library_edit_folder": "ØĒØšØ¯ŲŠŲ„ Ų…ØŦŲ„Ø¯", + "library_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ų…ŲƒØĒب؊", + "library_page_device_albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ ØšŲ„Ų‰ Ø§Ų„ØŦŲ‡Ø§Ø˛", + "library_page_new_album": "Ø§Ų„Ø¨ŲˆŲ… ØŦØ¯ŲŠØ¯", "library_page_sort_asset_count": "ؚدد Ø§Ų„ØŖØĩŲˆŲ„", "library_page_sort_created": "ØĒØ§ØąŲŠØŽ Ø§Ų„ØĨŲ†Ø´Ø§ØĄ", "library_page_sort_last_modified": "ØĸØŽØą ØĒØšØ¯ŲŠŲ„", @@ -1274,14 +1392,11 @@ "light_theme": "Ø§Ų„ØĒØ¨Ø¯ŲŠŲ„ ØĨŲ„Ų‰ Ø§Ų„Ų…Ø¸Ų‡Øą Ø§Ų„ŲØ§ØĒØ­", "like": "اؚØŦاب", "like_deleted": "ØĒŲ… Ø­Ø°Ų Ø§Ų„ØĨØšØŦاب", - "link": "ØąØ§Ø¨Øˇ", "link_motion_video": "ØąØ§Ø¨Øˇ ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ø­ØąŲƒØŠ", "link_to_docs": "Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ، ŲŠŲØąØŦŲ‰ Ø§Ų„ØąØŦŲˆØš ØĨŲ„Ų‰ Ø§Ų„ŲˆØĢاØĻŲ‚.", "link_to_oauth": "Ø§Ų„ØąØ¨Øˇ Ų…Øš OAuth", "linked_oauth_account": "Ø­ØŗØ§Ø¨ Ų…ØąØĒØ¨Øˇ Ø¨Ų€ OAuth", "list": "Ų‚Ø§ØĻŲ…ØŠ", - "live": "Ø­ŲŠ", - "load_more": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ų…Ø˛ŲŠØ¯", "loading": "ØĒØ­Ų…ŲŠŲ„", "loading_search_results_failed": "ŲØ´Ų„ ØĒØ­Ų…ŲŠŲ„ Ų†ØĒاØĻØŦ Ø§Ų„Ø¨Ø­ØĢ", "local": "Ų…Ø­Ų„Ų‘ŲŠ", @@ -1310,9 +1425,11 @@ "login": "ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", "login_disabled": "ØĒŲ… ØĒØšØˇŲŠŲ„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", "login_form_api_exception": "Ø§ØŗØĒØĢŲ†Ø§ØĄ API. ŲŠØąØŦŲ‰ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų…Ų† ØšŲ†ŲˆØ§Ų† URL Ø§Ų„ØŽØ§Ø¯Ų… ŲˆØ§Ų„Ų…Ø­Ø§ŲˆŲ„ØŠ Ų…ØąØŠ ØŖØŽØąŲ‰.", + "login_form_back_button_text": "Ø§Ų„ØąØŦŲˆØš Ų„Ų„ØŽŲ„Ų", "login_form_email_hint": "yoursemail@email.com", "login_form_endpoint_hint": "http://Ø§Ų„Ų…Ų†ŲØ°:ØšŲ†ŲˆØ§Ų†â€Ģ-ip-Ø§Ų„ØŽØ§Ø¯Ų…", "login_form_endpoint_url": "url Ų†Ų‚ØˇØŠ Ų†Ų‡Ø§ŲŠØŠ Ø§Ų„ØŽØ§Ø¯Ų…", + "login_form_err_http": "ŲŠØąØŦŲ‰ ØĒØ­Ø¯ŲŠØ¯ http:// ØŖŲˆ https://", "login_form_err_invalid_email": "Ø¨ØąŲŠØ¯ ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ ØŽØ§ØˇØĻ", "login_form_err_invalid_url": "URL ØēŲŠØą ØĩØ§Ų„Ø­", "login_form_err_leading_whitespace": "Ų‚ŲŠØ§Ø¯ØŠ Ø§Ų„Ų…ØŗØ§Ø­ØŠ Ø§Ų„Ø¨ŲŠØļØ§ØĄ", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "ØŽØˇØŖ ؁؊ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ ، ØĒØ­Ų‚Ų‚ Ų…Ų† ØšŲ†ŲˆØ§Ų† URL Ų„Ų„ØŽØ§Ø¯Ų… ŲˆØ§Ų„Ø¨ØąŲŠØ¯ Ø§Ų„ØĨŲ„ŲƒØĒØąŲˆŲ†ŲŠ ŲˆŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", "login_form_handshake_exception": "ŲƒØ§Ų† Ų‡Ų†Ø§Ųƒ Ø§ØŗØĒØĢŲ†Ø§ØĄ Ų…ØĩØ§ŲØ­ØŠ Ų…Øš Ø§Ų„ØŽØ§Ø¯Ų….ØĒŲ…ŲƒŲŠŲ† Ø¯ØšŲ… Ø§Ų„Ø´Ų‡Ø§Ø¯ØŠ Ø§Ų„Ų…ŲˆŲ‚ØšØŠ ذاØĒŲŠØ§ ؁؊ Ø§Ų„ØĨؚداداØĒ ØĨذا ŲƒŲ†ØĒ ØĒØŗØĒØŽØ¯Ų… Ø´Ų‡Ø§Ø¯ØŠ Ų…ŲˆŲ‚ØšØŠ ذاØĒŲŠØ§.", "login_form_password_hint": "ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", - "login_form_server_empty": "ØŖØ¯ØŽŲ„ ØšŲ†ŲˆØ§Ų† URL Ø§Ų„ØŽØ§Ø¯Ų…", - "login_form_server_error": "ØĒØšØ°Øą Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨Ø§Ų„ØŽØ§Ø¯Ų…", - "login_has_been_disabled": "ØĒŲ… ØĒØšØˇŲŠŲ„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„", + "login_form_save_login": "Ø§Ø¨Ų‚ Ų…ØĒØĩŲ„Ø§", + "login_form_server_empty": "ØŖØ¯ØŽŲ„ ØšŲ†ŲˆØ§Ų† URL Ø§Ų„ØŽØ§Ø¯Ų….", + "login_form_server_error": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø¨Ø§Ų„ØŽØ§Ø¯Ų….", + "login_has_been_disabled": "ØĒŲ… ØĒØšØˇŲŠŲ„ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„.", "login_password_changed_error": "ŲƒØ§Ų† Ų‡Ų†Ø§Ųƒ ØŽØˇØŖ ؁؊ ØĒØ­Ø¯ŲŠØĢ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "login_password_changed_success": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ ŲƒŲ„Ų…ØŠ Ø§Ų„ØŗØą Ø¨Ų†ØŦاح", "logout_all_device_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØĒØŗØŦŲŠŲ„ Ø§Ų„ØŽØąŲˆØŦ Ų…Ų† ØŦŲ…ŲŠØš Ø§Ų„ØŖØŦŲ‡Ø˛ØŠØŸ", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Ø§ØŗØĒؚاد؊ Ų‚Ø§ØšØ¯ØŠ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ", "maintenance_description": "؊ØŦب ؈ØļØš Immich ؁؊ ؈ØļØš Ø§Ų„ØĩŲŠØ§Ų†ØŠ ؈ØļØš Ø§Ų„ØĩŲŠØ§Ų†ØŠ.", "maintenance_end": "Ø§Ų†Ų‡Ø§ØĄ ؈ØļØš Ø§Ų„ØĩŲŠØ§Ų†ØŠ", + "maintenance_end_error": "ŲØ´Ų„ ؁؊ Ø§Ų†Ų‡Ø§ØĄ ؈ØļØš Ø§Ų„ØĩŲŠØ§Ų†ØŠ.", "maintenance_logged_in_as": "Ø­Ø§Ų„ŲŠØ§ Ų…ØŗØŦŲ„ Ø¨Ø§ØŗŲ… {user}", "maintenance_restore_from_backup": "Ø§ØŗØĒؚاد؊ Ų…Ų† Ø§Ų„ØŽØ˛Ų† Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ", "maintenance_restore_library": "Ø§ØŗØĒؚاد؊ Ø§Ų„Ų…ŲƒØĒØ¨Ų‡ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "؁ØĒØ­ Ø§Ų„Ø§ØšØ¯Ø§Ø¯Ø§ØĒ", "manage_media_access_subtitle": "Ø§Ų„ØŗŲ…Ø§Ø­ Ų„Ø¨ØąŲ†Ø§Ų…ØŦ Immich بØĨØ¯Ø§ØąØŠ ŲˆŲ†Ų‚Ų„ Ų…Ų„ŲØ§ØĒ Ø§Ų„ŲˆØŗØ§ØĻØˇ.", "manage_media_access_title": "؈ØĩŲˆŲ„ Ø§Ø¯Ø§ØąØŠ Ø§Ų„ŲˆØŗØ§ØĻØˇ", + "manage_shared_links": "ØĨØ¯Ø§ØąØŠ Ø§Ų„ØąŲˆØ§Ø¨Øˇ Ø§Ų„Ų…Ø´ØĒØąŲƒØŠ", "manage_sharing_with_partners": "ØĨØ¯Ø§ØąØŠ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų…Øš Ø§Ų„Ø´ØąŲƒØ§ØĄ", "manage_the_app_settings": "ØĨØ¯Ø§ØąØŠ ØĨؚداداØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", "manage_your_account": "ØĨØ¯Ø§ØąØŠ Ø­ØŗØ§Ø¨Ųƒ", @@ -1371,11 +1491,13 @@ "manage_your_devices": "ØĨØ¯Ø§ØąØŠ Ø§Ų„ØŖØŦŲ‡Ø˛ØŠ Ø§Ų„ØĒ؊ ØĒŲ… ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ ØĨŲ„ŲŠŲ‡Ø§", "manage_your_oauth_connection": "ØĨØ¯Ø§ØąØŠ اØĒØĩØ§Ų„ OAuth Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "map": "Ø§Ų„ØŽØąŲŠØˇØŠ", + "map_assets_in_bounds": "{count, plural, =0 {Ų„Ø§ŲŠŲˆØŦد ØĩŲˆØą ؁؊ Ų‡Ø°Ų‡ Ø§Ų„Ų…Ų†ØˇŲ‚ØŠ} one {# ØĩŲˆØąØŠ} other {# ØĩŲˆØą}}", "map_cannot_get_user_location": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§Ų„Ø­ØĩŲˆŲ„ ØšŲ„Ų‰ Ų…ŲˆŲ‚Øš Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", + "map_location_dialog_yes": "Ų†ØšŲ…", "map_location_picker_page_use_location": "Ø§ØŗØĒØŽØ¯Ų… Ų‡Ø°Ø§ Ø§Ų„Ų…ŲˆŲ‚Øš", "map_location_service_disabled_content": "؊ØŦب ØĒŲ…ŲƒŲŠŲ† ØŽØ¯Ų…ØŠ Ø§Ų„Ų…ŲˆŲ‚Øš Ų„ØšØąØļ Ø§Ų„ØŖØĩŲˆŲ„ Ų…Ų† Ų…ŲˆŲ‚ØšŲƒ Ø§Ų„Ø­Ø§Ų„ŲŠ.Ų‡Ų„ ØĒØąŲŠØ¯ ØĒŲ…ŲƒŲŠŲ†Ų‡ Ø§Ų„ØĸŲ†ØŸ", "map_location_service_disabled_title": "ØŽØ¯Ų…ØŠ Ø§Ų„Ų…ŲˆŲ‚Øš Ų…ØšØˇŲ„", - "map_marker_for_image": "ØšŲ„Ø§Ų…ØŠ ØšŲ„Ų‰ Ø§Ų„ØŽØąŲŠØˇØŠ Ų„Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…Ų„ØĒŲ‚ØˇØŠ ؁؊ {city}, {country}", + "map_marker_for_images": "ØšŲ„Ø§Ų…ØŠ Ø§Ų„ØŽØąŲŠØˇØŠ Ų„Ų„ØĩŲˆØą Ø§Ų„Ų…Ų„ØĒŲ‚ØˇØŠ ؁؊ {city}، {country}", "map_marker_with_image": "ØšŲ„Ø§Ų…ØŠ Ø§Ų„ØŽØąŲŠØˇØŠ Ų…Øš Ø§Ų„ØĩŲˆØąØŠ", "map_no_location_permission_content": "Ų‡Ų†Ø§Ųƒ حاØŦØŠ ØĨŲ„Ų‰ ØĨØ°Ų† Ø§Ų„Ų…ŲˆŲ‚Øš Ų„ØšØąØļ Ø§Ų„ØŖØĩŲˆŲ„ Ų…Ų† Ų…ŲˆŲ‚ØšŲƒ Ø§Ų„Ø­Ø§Ų„ŲŠ.Ų‡Ų„ ØĒØąŲŠØ¯ Ø§Ų„ØŗŲ…Ø§Ø­ Ø¨Ų‡ Ø§Ų„ØĸŲ†ØŸ", "map_no_location_permission_title": "ØĒŲ… ØąŲØļ ØĨØ°Ų† Ø§Ų„Ų…ŲˆŲ‚Øš", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Ø§Ų„Ø§ŲŠØ§Ų… {days} Ø§Ų„Ų…Ø§ØļŲŠØŠ", "map_settings_date_range_option_year": "Ø§Ų„ØŗŲ†ØŠ Ø§Ų„ŲØ§ØĻØĒØŠ", "map_settings_date_range_option_years": "Ø§Ų„ØŗŲ†ŲˆØ§ØĒ {years} Ø§Ų„Ų…Ø§ØļŲŠØŠ", + "map_settings_dialog_title": "ØĨؚداداØĒ Ø§Ų„ØŽØąŲŠØˇØŠ", "map_settings_include_show_archived": "ØĒØ´Ų…Ų„ Ø§Ų„ØŖØąØ´ŲØŠ", "map_settings_include_show_partners": "ØĒØļŲ…ŲŠŲ† Ø§Ų„Ø´ØąŲƒØ§ØĄ", "map_settings_only_show_favorites": "Ø§Ø¸Ų‡Ø§Øą Ø§Ų„Ų…ŲØļŲ„ØŠ ŲŲ‚Øˇ", "map_settings_theme_settings": "Ų…Ø¸Ų‡Øą Ø§Ų„ØŽØąŲŠØˇØŠ", + "map_zoom_to_see_photos": "Ų‚Ų… بØĒØĩØēŲŠØąŲ‡Ø§ Ų„ØąØ¤ŲŠØŠ Ø§Ų„ØĩŲˆØą", "mark_all_as_read": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„ ŲƒŲ…Ų‚ØąŲˆØĄ", + "mark_as_read": "ØĒØ­Ø¯ŲŠØ¯ ŲƒŲ…Ų‚ØąŲˆØĄ", "marked_all_as_read": "ØĒŲ… ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„ ŲƒŲ…Ų‚ØąŲˆØĄ", "matches": "ØĒØˇØ§Ø¨Ų‚Ø§ØĒ", "matching_assets": "â€Ø§Ų„Ø§ØĩŲˆŲ„ Ø§Ų„Ų…ØˇØ§Ø¨Ų‚ØŠ", - "media_chrome": { - "auto": "ØĒŲ„Ų‚Ø§ØĻ؊", - "captions": "Ø§Ų„ØĒØšŲ„ŲŠŲ‚Ø§ØĒ Ø§Ų„ØĒ؈ØļŲŠØ­ŲŠØŠ", - "captions_off": "Ø§ØˇŲØ§ØĄ", - "closed_captions": "Ø§Ų„ØĒØąØŦŲ…ØŠ Ø§Ų„Ų…ØąØĻŲŠØŠ Ø§Ų„Ų…ØēŲ„Ų‚ØŠ", - "decode_error": "ŲØ´Ų„ ؁؃ Ø§Ų„ØĒØ´ŲŲŠØą", - "disable_captions": "ØĒØšØˇŲŠŲ„ Ø§Ų„ØĒØąØŦŲ…ØŠ", - "enable_captions": "ØĒŲØšŲŠŲ„ Ø§Ų„ØĒØąØŦŲ…ØŠ", - "enter_fullscreen_mode": "Ø§Ø¯ØŽŲ„ ØĨŲ„Ų‰ ؈ØļØš Ų…Ų„ØĄ Ø§Ų„Ø´Ø§Ø´ØŠ", - "exit_fullscreen_mode": "Ø§ØŽØąØŦ Ų…Ų† ؈ØļØš Ų…Ų„ØĄ Ø§Ų„Ø´Ø§Ø´ØŠ", - "loop": "Ø­Ų„Ų‚ØŠ", - "media_error_description": "ØŽØˇØŖ ؁؊ Ø§Ų„ŲˆØŗØ§ØĻØˇ ØĒØŗØ¨Ø¨ ؁؊ Ø§ŲŠŲ‚Ø§Ų Ø§Ų„ØĒØ´ØēŲŠŲ„. Ų‚Ø¯ ØĒŲƒŲˆŲ† Ø§Ų„ŲˆØŗØ§ØĻØˇ ØĒØ§Ų„ŲØŠ Ø§Ųˆ Ø§Ų† Ų…ØĒØĩŲØ­Ųƒ Ų„Ø§ ŲŠØ¯ØšŲ… Ø§Ų„Øĩ؊ØēØŠ.", - "media_loading": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ", - "mute": "؃ØĒŲ…", - "network_error": "ØŽØˇØŖ ؁؊ Ø§Ų„Ø´Ø¨ŲƒØŠ", - "network_error_description": "ØŽØˇØŖ ؁؊ Ø§Ų„Ø´Ø¨ŲƒØŠ ØĒØŗØ¨Ø¨ ؁؊ ŲØ´Ų„ ØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„ŲˆØŗØ§ØĻØˇ.", - "not_supported_error": "Ø§Ų„Ų…ØĩØ¯Øą ØēŲŠØą Ų…Ø¯ØšŲˆŲ…", - "playback_rate": "Ų…ØšØ¯Ų„ Ø§Ų„ØĒØ´ØēŲŠŲ„", - "playback_rate_current": "Ų…ØšØ¯Ų„ Ø§Ų„ØĒØ´ØēŲŠŲ„ Ø§Ų„Ø­Ø§Ų„ŲŠ", - "playback_rate_value": "Ų…ØšØ¯Ų„ Ø§Ų„ØĒØ´ØēŲŠŲ„ {playbackRate}", - "playback_time": "ŲˆŲ‚ØĒ Ø§Ų„ØĒØ´ØēŲŠŲ„", - "quality": "ØŦŲˆØ¯ØŠ", - "second": "ØĢØ§Ų†ŲŠØŠ", - "seconds": "ØĢŲˆØ§Ų†ŲŠ", - "time_value_of_total_time": "{currentTime} Ų…Ų† {totalTime}", - "time_value_remaining": "{time} Ų…ØĒØ¨Ų‚ŲŠ", - "unmute": "Ø§Ų„ØēØ§ØĄ Ø§Ų„ŲƒØĒŲ…", - "unsupported_error_description": "حدØĢ ØŽØˇØŖ ØēŲŠØą Ų…Ø¯ØšŲˆŲ…. حدØĢ ŲØ´Ų„ ؁؊ Ø§Ų„ØŽØ§Ø¯Ų… Ø§Ųˆ Ø§Ų„Ø´Ø¨ŲƒØŠ, Ø§Ųˆ Ø§Ų„Ų…ØĒØĩŲØ­ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ Ų„Ø§ ŲŠØ¯ØšŲ… Ų‡Ø°Ų‡ Ø§Ų„Øĩ؊ØēØŠ.", - "video_not_loaded_unknown_time": "Ø§Ų„ŲŲŠØ¯ŲŠŲˆ ØēŲŠØą Ų…Ø­Ų…Ų„, Ø§Ų„ŲˆŲ‚ØĒ ØēŲŠØą Ų…ØšŲ„ŲˆŲ….", - "video_player": "Ų…Ø´ØēŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", - "volume": "Ø­ØŦŲ…" - }, "media_type": "Ų†ŲˆØš Ø§Ų„ŲˆØŗØ§ØĻØˇ", "memories": "Ø§Ų„Ø°ŲƒØąŲŠØ§ØĒ", "memories_all_caught_up": "ŲƒŲ„ Ø´ŲŠØĄ Ų…Ø­Ø¯ØĢ", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Ų‡Ų„ ØĒØąŲŠØ¯ Ø¯Ų…ØŦ Ų‡Ø¤Ų„Ø§ØĄ Ø§Ų„Ų†Ø§ØŗØŸ Ų‡Ø°Ø§ Ø§Ų„ØĨØŦØąØ§ØĄ Ų„Ø§ ØąØŦؚ؊ ŲŲŠŲ‡.", "merge_people_successfully": "ØĒŲ… Ø¯Ų…ØŦ Ø§Ų„ØŖØ´ØŽØ§Øĩ Ø¨Ų†ØŦاح", "merged_people_count": "Ø¯Ų…ØŦ {count, plural, one {Ø´ØŽØĩ ŲˆØ§Ø­Ø¯} other {# ØŖØ´ØŽØ§Øĩ}}", - "minFaces": "Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖØ¯Ų†Ų‰ Ų„Ų„ŲˆØŦŲˆŲ‡", - "minFaces_description": "Ø§Ų„Ø­Ø¯ Ø§Ų„ØŖØ¯Ų†Ų‰ Ų„ØšØ¯Ø¯ Ø§Ų„ŲˆØŦŲˆŲ‡ Ø§Ų„Ų…ØĒØšØąŲ ØšŲ„ŲŠŲ‡Ø§ Ų„ŲƒŲŠ ؊ØĒŲ… ØšØąØļ Ø§Ų„Ø´ØŽØĩ", "minimize": "ØĒØĩØēŲŠØą", "minute": "Ø¯Ų‚ŲŠŲ‚ØŠ", "minutes": "Ø¯Ų‚Ø§ØĻŲ‚", + "mirror_horizontal": "Ø§ŲŲ‚ŲŠ", + "mirror_vertical": "ØšŲ…ŲˆØ¯ŲŠ", "missing": "Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ", "mobile_app": "ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„ØŦŲˆØ§Ų„", "mobile_app_download_onboarding_note": "Ų‚Ų… بØĒŲ†Ø˛ŲŠŲ„ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„Ų…Øĩاحب Ų„Ų„Ų‡Ø§ØĒ؁ Ø§Ų„Ų…Ø­Ų…ŲˆŲ„ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØĒØ§Ų„ŲŠØŠ", "model": "Ų†Ų…ŲˆØ°ØŦ", - "modify_date": "ØĒØēŲŠŲŠØą Ø§Ų„ØĒØ§ØąŲŠØŽ", "month": "Ø´Ų‡Øą", + "monthly_title_text_date_format": "Øˇ Øˇ Øˇ", "more": "Ø§Ų„Ų…Ø˛ŲŠØ¯", - "motion": "Ø­ØąŲƒØŠ", "move": "ØĒØ­ØąŲŠŲƒ", + "move_down": "Ø§Ų†Ø˛Ų„ Ø§Ų„Ų‰ Ø§Ų„Ø§ØŗŲŲ„", "move_off_locked_folder": "ØĒØ­ØąŲŠŲƒ ØŽØ§ØąØŦ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„", "move_to": "Ų†Ų‚Ų„ Ø§Ų„Ų‰", "move_to_device_trash": "Ų†Ų‚Ų„ ØĨŲ„Ų‰ ØŗŲ„ØŠ Ų…Ų‡Ų…Ų„Ø§ØĒ Ø§Ų„ØŦŲ‡Ø§Ø˛", "move_to_lock_folder_action_prompt": "{count} اØļ؊؁ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„", "move_to_locked_folder": "Ø§Ų„Ų†Ų‚Ų„ Ø§Ų„Ų‰ Ų…ØŦŲ„Ø¯ Ų…ØēŲ„Ų‚", "move_to_locked_folder_confirmation": "Ų‡Ø°Ų‡ Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲØ¯ŲŠŲˆØ§ØĒ ØŗØĒØĒŲ… Ø§Ø˛Ø§Ų„ØĒŲ‡Ø§ Ų…Ų† ØŦŲ…ŲŠØš Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ، ŲˆŲŠŲ…ŲƒŲ†Ø§Ų† ØĒØĒŲ… Ų…Ø´Ø§Ų‡Ø¯ØĒŲ‡Ø§ ŲŲ‚Øˇ Ų…Ų† ØŽŲ„Ø§Ų„ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„", + "move_up": "ØĒØ­ØąŲƒ Ø§Ų„Ų‰ Ø§Ų„Ø§ØšŲ„Ų‰", + "moved_to_archive": "ØĒŲ… Ų†Ų‚Ų„ {count, plural, one {# اØĩŲ„} other {# اØĩŲˆŲ„}} Ø§Ų„Ų‰ Ø§Ų„Ø§ØąØ´ŲŠŲ", + "moved_to_library": "ØĒŲ… Ų†Ų‚Ų„ {count, plural, one {# اØĩŲ„} other {# اØĩŲˆŲ„}} Ø§Ų„Ų‰ Ø§Ų„Ų…ŲƒØĒب؊", "moved_to_trash": "ØĒŲ… Ø§Ų„Ų†Ų‚Ų„ ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", + "multiselect_grid_edit_date_time_err_read_only": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĒØšØ¯ŲŠŲ„ ØĒØ§ØąŲŠØŽ Ø§Ų„ØŖØĩŲˆŲ„ (Ø§Ų„Ų…ŲˆØ§Ø¯) Ų„Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚ØˇØŒ ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", + "multiselect_grid_edit_gps_err_read_only": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØĒØšØ¯ŲŠŲ„ Ų…ŲˆŲ‚Øš Ø§Ų„ØŖØĩŲˆŲ„ (Ø§Ų„Ų…ŲˆØ§Ø¯) Ų„Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚ØˇØŒ ØŗŲˆŲ ؊ØĒØŽØˇŲ‰", "mute_memories": "؃ØĒŲ… Ø§Ų„Ø°ŲƒØąŲŠØ§ØĒ", "my_albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ؊", - "my_immich_description": "Ų†ØŗØŽ Ø§Ų„ØĩŲØ­ØŠ Ø§Ų„Ø­Ø§Ų„ŲŠØŠ ŲƒØąØ§Ø¨Øˇ Immich Ø§Ų„ØŽØ§Øĩ Ø¨ŲŠ", - "my_immich_title": "ØąØ§Ø¨Øˇ Immich Ø§Ų„ØŽØ§Øĩ Ø¨ŲŠ", "name": "Ø§Ų„Ø§ØŗŲ…", "name_or_nickname": "Ø§Ų„Ø§ØŗŲ… ØŖŲˆ Ø§Ų„Ų„Ų‚Ø¨", "name_required": "Ø§Ų„Ø§ØŗŲ… Ų…ØˇŲ„ŲˆØ¨", @@ -1480,11 +1576,12 @@ "never": "ØŖØ¨Ø¯Ø§Ų‹", "new_album": "Ø§Ų„Ø¨ŲˆŲ… ØŦØ¯ŲŠØ¯", "new_api_key": "؅؁ØĒاح API ØŦØ¯ŲŠØ¯", - "new_feature": "Ų…ŲŠØ˛ØŠ ØŦØ¯ŲŠØ¯ØŠ", + "new_date_range": "Ų†ØˇØ§Ų‚ ØĒØ§ØąŲŠØŽ ØŦØ¯ŲŠØ¯", "new_password": "ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ", "new_person": "Ø´ØŽØĩ ØŦØ¯ŲŠØ¯", "new_pin_code": "ØąŲ…Ø˛ PIN Ø§Ų„ØŦØ¯ŲŠØ¯", "new_pin_code_subtitle": "Ų‡Ø°Ų‡ ØŖŲˆŲ„ Ų…ØąØŠ ØĒØ¯ØŽŲ„ ŲŲŠŲ‡Ø§ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„. ØŖŲ†Ø´ØĻ ØąŲ…Ø˛Ų‹Ø§ PIN Ų„Ų„ŲˆØĩŲˆŲ„ Ø¨Ø§Ų…Ø§Ų† ØĨŲ„Ų‰ Ų‡Ø°Ų‡ Ø§Ų„ØĩŲØ­ØŠ", + "new_timeline": "Ø§Ų„ØŽØˇ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„ØŦØ¯ŲŠØ¯", "new_update": "ØĒØ­Ø¯ŲŠØĢ ØŦØ¯ŲŠØ¯", "new_user_created": "ØĒŲ… ØĨŲ†Ø´Ø§ØĄ Ų…ØŗØĒØŽØ¯Ų… ØŦØ¯ŲŠØ¯", "new_version_available": "ØĨØĩØ¯Ø§Øą ØŦØ¯ŲŠØ¯ Ų…ØĒاح", @@ -1492,6 +1589,7 @@ "next": "Ø§Ų„ØĒØ§Ų„ŲŠ", "next_memory": "Ø§Ų„Ø°ŲƒØąŲ‰ Ø§Ų„ØĒØ§Ų„ŲŠØŠ", "no": "Ų„Ø§", + "no_actions_added": "Ų„Ų… ØĒØĒŲ… ØĨØļØ§ŲØŠ ØĨØŦØąØ§ØĄØ§ØĒ Ø­ØĒŲ‰ Ø§Ų„Ø§Ų†", "no_albums_found": "Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦاد Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "no_albums_message": "Ų‚Ų… بØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ… Ų„ØĒŲ†Ø¸ŲŠŲ… Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "no_albums_with_name_yet": "ŲŠØ¨Ø¯Ųˆ ØŖŲ†Ų‡ Ų„ŲŠØŗ Ų„Ø¯ŲŠŲƒ ØŖŲŠ ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø¨Ų‡Ø°Ø§ Ø§Ų„Ø§ØŗŲ… Ø­ØĒŲ‰ Ø§Ų„ØĸŲ†.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Ų„Ų… ؊ØĒŲ… Ø§ŲŠØŦاد ØŦŲ‡Ø§Ø˛ بØĢ", "no_checksum_local": "Ų„Ø§ ØĒ؈ØŦد Ø¨ŲŠØ§Ų†Ø§ØĒ ØĒØ­Ų‚Ų‚ Ų…ØĒاح؊ - ؊ØĒØšØ°Øą ØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ø§ØĩŲˆŲ„ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ", "no_checksum_remote": "Ų„Ø§ ؊؈ØŦد ØąŲ…Ø˛ ØĒØ­Ų‚Ų‚ Ų…ØĒاح - ؊ØĒØšØ°Øą ØĒØ­Ų…ŲŠŲ„ Ø§Ų„Ø§ØĩŲ„ Ų…Ų† Ø§Ų„Ų…ŲˆŲ‚Øš Ø§Ų„Ø¨ØšŲŠØ¯", + "no_configuration_needed": "Ų„Ø§ حاØŦØŠ ØĨŲ„Ų‰ ØŖŲŠ ØĨؚداداØĒ", "no_devices": "Ų„Ø§ ؊؈ØŦد اØŦŲ‡Ø˛ØŠ Ų…ØąØŽØĩØŠ", "no_duplicates_found": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ ØŖŲŠ ØĒŲƒØąØ§ØąØ§ØĒ.", "no_exif_info_available": "Ų„Ø§ ØĒØĒŲˆŲØą Ų…ØšŲ„ŲˆŲ…Ø§ØĒ exif", "no_explore_results_message": "Ų‚Ų… Ø¨ØąŲØš Ø§Ų„Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„ØĩŲˆØą Ų„Ø§ØŗØĒŲƒØ´Ø§Ų Ų…ØŦŲ…ŲˆØšØĒ؃.", "no_favorites_message": "ØŖØļ؁ Ø§Ų„Ų…ŲØļŲ„ØŠ Ų„Ų„ØšØĢŲˆØą Ø¨ØŗØąØšØŠ ØšŲ„Ų‰ ØŖŲØļŲ„ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", + "no_filters_added": "Ų„Ų… ØĒØĒŲ… ØĨØļØ§ŲØŠ ØŖŲŠ ؁؄ØĒØą بؚد", "no_libraries_message": "ØĨŲ†Ø´Ø§ØĄ Ų…ŲƒØĒب؊ ØŽØ§ØąØŦŲŠØŠ Ų„ØšØąØļ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "no_local_assets_found": "Ų„Ų… ؊ØĒŲ… Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ ØŖŲŠ اØĩŲˆŲ„ Ų…Ø­Ų„ŲŠØŠ ØĒØĒØˇØ§Ø¨Ų‚ Ų…Øš Ų‚ŲŠŲ…ØŠ Ø§Ų„ØĒØ­Ų‚Ų‚ Ų‡Ø°Ų‡", + "no_location_set": "Ų„Ų… ؊ØĒŲ… ØĒØ­Ø¯ŲŠØ¯ Ų…ŲˆŲ‚Øš", "no_locked_photos_message": "Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲØ¯ŲŠŲˆŲ‡Ø§ØĒ ؁؊ Ø§Ų„Ų…ØŦŲ„Ø¯ Ø§Ų„Ų…Ų‚ŲŲ„ Ų…ØŽŲŲŠØŠ ŲˆŲ„Ų† ØĒØĩŲ‡Øą ؁؊ Ø§Ų„ØĒØĩŲØ­ Ø§Ųˆ Ø§Ų„Ø¨Ø­ØĢ ؁؊ Ų…ŲƒØĒبØĒ؃.", "no_name": "Ų„Ø§ Ø§ØŗŲ…", "no_notifications": "Ų„Ø§ ØĒ؈ØŦد ØĒŲ†Ø¨ŲŠŲ‡Ø§ØĒ", @@ -1518,19 +1619,14 @@ "no_results": "Ų„Ø§ ؊؈ØŦد Ų†ØĒاØĻØŦ", "no_results_description": "ØŦØąØ¨ ŲƒŲ„Ų…ØŠ ØąØĻŲŠØŗŲŠØŠ Ų…ØąØ§Ø¯ŲØŠ ØŖŲˆ ØŖŲƒØĢØą ØšŲ…ŲˆŲ…ŲŠØŠ", "no_shared_albums_message": "Ų‚Ų… بØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ… Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ų…Øš Ø§Ų„ØŖØ´ØŽØ§Øĩ ؁؊ Ø´Ø¨ŲƒØĒ؃", - "no_steps": "Ų„Ų… ؊ØĒŲ… اØļØ§ŲØŠ ØŽØˇŲˆØ§ØĒ بؚد", + "no_uploads_in_progress": "Ų„Ø§ ؊؈ØŦد Ø§ŲŠ Ų…Ų„ŲØ§ØĒ Ų‚ŲŠØ¯ Ø§Ų„ØąŲØš", "none": "Ų„Ø§ ؊؈ØŦد", "not_allowed": "ØēŲŠØą Ų…ØŗŲ…ŲˆØ­", "not_available": "ØēŲŠØą Ų…ØĒاح", "not_in_any_album": "Ų„ŲŠØŗØĒ ؁؊ ØŖŲŠ ØŖŲ„Ø¨ŲˆŲ…", "not_selected": "Ų„Ų… ŲŠØŽØĒØ§Øą", - "not_set": "ØēŲŠØą Ų…Ø­Ø¯Ø¯", "notes": "Ų…Ų„Ø§Ø­Ø¸Ø§ØĒ", "nothing_here_yet": "Ų„Ø§ ؊؈ØŦد Ø´ŲŠØĄ Ų‡Ų†Ø§ بؚد", - "notification_backup_reliability": "ŲØšŲ„ Ø§Ų„ØĨØ´ØšØ§ØąØ§ØĒ Ų„Ų„Ø˛ŲŠØ§Ø¯ØŠ Ų…Ų† Ø§ØŗØĒŲ‚ØąØ§Øą Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ", - "notification_enabled_list_tile_content": "ŲŠØŗØĒØŽØ¯Ų… ØĒØˇØ¨ŲŠŲ‚ Immich Ø§Ų„ØĨØ´ØšØ§ØąØ§ØĒ Ų„ØĨØŦØąØ§ØĄ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ. ŲŠŲ…ŲƒŲ†Ųƒ ØĨØ¯Ø§ØąØĒŲ‡Ø§ Ų…Ų† ØĨؚداداØĒ ØŦŲ‡Ø§Ø˛Ųƒ.", - "notification_enabled_list_tile_open_button": "Ø§ŲØĒØ­ Ø§Ų„Ø§ØšØ¯Ø§Ø¯Ø§ØĒ", - "notification_enabled_list_tile_title": "ØĒŲ… ØĒŲØšŲŠŲ„ Ø§Ų„Ø§Ø´ØšØ§ØąØ§ØĒ", "notification_permission_dialog_content": "Ų„ØĒŲ…ŲƒŲŠŲ† Ø§Ų„ØĨØŽØˇØ§ØąØ§ØĒ ، Ø§Ų†ØĒŲ‚Ų„ ØĨŲ„Ų‰ Ø§Ų„ØĨؚداداØĒ ؈ ا؎ØĒØ§Øą Ø§Ų„ØŗŲ…Ø§Ø­.", "notification_permission_list_tile_content": "Ų…Ų†Ø­ ØĨØ°Ų† Ų„ØĒŲ…ŲƒŲŠŲ† Ø§Ų„ØĨØŽØˇØ§ØąØ§ØĒ.", "notification_permission_list_tile_enable_button": "ØĒŲ…ŲƒŲŠŲ† Ø§Ų„ØĨØŽØˇØ§ØąØ§ØĒ", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Ų…ŲŲ‡ŲŠØĻ Obtainium", "obtainium_configurator_instructions": "Ø§ØŗØĒØŽØ¯Ų… Obtainium Ų„ØĒØĢØ¨ŲŠØĒ ØĒØˇØ¨ŲŠŲ‚ Android ؈ØĒØ­Ø¯ŲŠØĢŲ‡ Ų…Ø¨Ø§Ø´ØąØŠŲ‹ Ų…Ų† ØĩŲØ­ØŠ ØĨØĩØ¯Ø§ØąØ§ØĒ Immich ØšŲ„Ų‰ GitHub. ØŖŲ†Ø´ØĻ ؅؁ØĒاح API ŲˆØ§ØŽØĒØą Ø§Ų„ØĨØĩØ¯Ø§Øą Ø§Ų„Ų…Ų†Ø§ØŗØ¨ Ų„ØĨŲ†Ø´Ø§ØĄ ØąØ§Ø¨Øˇ ØĒŲ‡ŲŠØĻØŠ Obtainium Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "ocr": "Ø§Ų„ØĒØšØąŲ Ø§Ų„Ø¨ØĩØąŲŠ ØšŲ„Ų‰ Ø§Ų„Ø­ØąŲˆŲ", - "ocr_body": "Immich Ø§Ų„ØĸŲ† Ø¨Ų‚ØąØ§ØĄØŠ Ø§Ų„Ų†Øĩ؈Øĩ Ø§Ų„Ų…ŲˆØŦŲˆØ¯ØŠ Ø¯Ø§ØŽŲ„ ØĩŲˆØąŲƒØŒ Ų„Ø°Ø§ ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„Ø¨Ø­ØĢ ØšŲ†Ų‡Ø§ Ų…Ų† ØŽŲ„Ø§Ų„ Ų…Ø§ ØĒŲ‚ŲˆŲ„Ų‡.", - "ocr_title": "ابحØĢ ØšŲ† Ø§Ų„Ų†Øĩ ؁؊ ØĩŲˆØąŲƒ", "official_immich_resources": "Ø§Ų„Ų…ŲˆØ§ØąØ¯ Ø§Ų„ØąØŗŲ…ŲŠØŠ Ų„Ø´ØąŲƒØŠ Immich", "offline": "ØēŲŠØą Ų…ØĒØĩŲ„", "offset": "Ø§Ø˛Ø§Ø­ØŠ", @@ -1562,8 +1656,6 @@ "open": "؁ØĒØ­", "open_calendar": "Ø§ŲØĒØ­ Ø§Ų„ØąØ˛Ų†Ø§Ų…ØŠ", "open_in_browser": "؁ØĒØ­ ؁؊ Ų…ØĒØĩŲØ­", - "open_in_immich_body": "Ų‚Ų… بØĒØšŲŠŲŠŲ† Immich ŲƒŲ…ØšØąØļ Ø§Ų„ØĩŲˆØą Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ØšŲ„Ų‰ Android ؄؁ØĒØ­ Ø§Ų„ØĩŲˆØą Ų…Ø¨Ø§Ø´ØąØŠ Ų…Ų† Ø§Ų„ØĒØˇØ¨ŲŠŲ‚Ø§ØĒ Ø§Ų„ØŖØŽØąŲ‰.", - "open_in_immich_title": "Ø§ŲØĒØ­ Ø§Ų„ØĩŲˆØą ؁؊ Immich", "open_in_map_view": "؁ØĒØ­ ؁؊ ØšØąØļ Ø§Ų„ØŽØąŲŠØˇØŠ", "open_in_openstreetmap": "؁ØĒØ­ ؁؊ OpenStreetMap", "open_the_search_filters": "Ø§ŲØĒØ­ Ų…ØąØ´Ø­Ø§ØĒ Ø§Ų„Ø¨Ø­ØĢ", @@ -1572,10 +1664,10 @@ "organize_into_albums": "ØĒØąØĒŲŠØ¨ ؁؊ ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "organize_into_albums_description": "ØŖØļ؁ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ŲˆØŦŲˆØ¯ØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… ØĨؚداداØĒ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ØĒØ˛Ø§Ų…Ų† Ø§Ų„Ø­Ø§Ų„ŲŠØŠ", "organize_your_library": "ØĒŲ†Ø¸ŲŠŲ… Ų…ŲƒØĒبØĒ؃", - "orientation": "اØĒØŦØ§Ų‡", "original": "ØŖØĩŲ„ŲŠ", "other": "ØŖØŽØąŲ‰", "other_devices": "ØŖØŦŲ‡Ø˛ØŠ ØŖØŽØąŲ‰", + "other_entities": "ŲƒŲŠØ§Ų†Ø§ØĒ ØŖØŽØąŲ‰", "other_variables": "Ų…ØĒØēŲŠØąØ§ØĒ ØŖØŽØąŲ‰", "owned": "Ų…Ų…Ų„ŲˆŲƒØŠ", "owner": "Ø§Ų„Ų…Ø§Ų„Ųƒ", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "ØŦŲ…ŲŠØš Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ Ø¨Ø§ØŗØĒØĢŲ†Ø§ØĄ ØĒŲ„Ųƒ Ø§Ų„Ų…ŲˆØŦŲˆØ¯ØŠ ؁؊ Ø§Ų„Ų…Ø¤ØąØ´ŲØŠ ŲˆØ§Ų„Ų…Ø­Ø°ŲˆŲØŠ", "partner_can_access_location": "Ø§Ų„Ų…ŲˆŲ‚Øš Ø§Ų„Ø°ŲŠ ØĒŲ… Ø§Ų„ØĒŲ‚Ø§Øˇ ØĩŲˆØąŲƒ ŲŲŠŲ‡", "partner_list_user_photos": "ØĩŲˆØą {user}", + "partner_list_view_all": "ØšØąØļ Ø§Ų„ŲƒŲ„", "partner_page_empty_message": "Ų„Ų… ؊ØĒŲ… Ų…Ø´Ø§ØąŲƒØŠ ØĩŲˆØąŲƒ بؚد Ų…Øš ØŖŲŠ Ø´ØąŲŠŲƒ.", "partner_page_no_more_users": "Ų„Ø§ Ų…Ø˛ŲŠØ¯ Ų…Ų† Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† Ų„ØĨØļØ§ŲØŠ", + "partner_page_partner_add_failed": "ŲØ´Ų„ ؁؊ ØĨØļØ§ŲØŠ Ø´ØąŲŠŲƒ", "partner_page_select_partner": "حدد Ø´ØąŲŠŲƒŲ‹Ø§", + "partner_page_shared_to_title": "Ų…Ø´ØĒØąŲƒ Ų„", "partner_page_stop_sharing_content": "{partner} Ų„Ų† ŲŠØšŲˆØ¯ Ų‚Ø§Ø¯ØąØ§ ØšŲ„Ų‰ Ø§Ų„ŲˆØĩ؈؁ Ø§Ų„Ų‰ ØĩŲˆØąŲƒ.", "partner_sharing": "Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„Ø´ØąŲƒØ§ØĄ", "partners": "Ø§Ų„Ø´ØąŲƒØ§ØĄ", @@ -1609,6 +1704,8 @@ "people": "Ø§Ų„ØŖØ´ØŽØ§Øĩ", "people_edits_count": "ØĒŲ… ØĒØšØ¯ŲŠŲ„ {count, plural, one {# Ø´ØŽØĩ } other {# ØŖØ´ØŽØ§Øĩ }}", "people_feature_description": "ØĒØĩŲØ­ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØŦŲ…ØšØŠ Ø­ØŗØ¨ Ø§Ų„ØŖØ´ØŽØ§Øĩ", + "people_selected": "{count, plural, one {# Ø´ØŽØĩ Ų…ØŽØĒØ§Øą} other {# اش؎اØĩ Ų…ØŽØĒØ§ØąŲŠŲ†}}", + "people_sidebar_description": "ØšØąØļ ØąØ§Ø¨Øˇ Ų„Ų„ØŖØ´ØŽØ§Øĩ ؁؊ Ø§Ų„Ø´ØąŲŠØˇ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠ", "permanent_deletion_warning": "ØĒØ­Ø°ŲŠØą Ø§Ų„Ø­Ø°Ų Ø§Ų„Ø¯Ø§ØĻŲ…", "permanent_deletion_warning_setting_description": "ØĨØ¸Ų‡Ø§Øą ØĒØ­Ø°ŲŠØą ØšŲ†Ø¯ Ø­Ø°Ų Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§", "permanently_delete": "Ø­Ø°Ų Ø¨Ø´ŲƒŲ„ داØĻŲ…", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "ØĒŲ… Ø­Ø°Ų {count, plural, one {# Ų…Ø­ØĒŲˆŲ‰} other {# Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§", "permission": "Ø§Ø°Ų†", "permission_empty": "Ø§Ų„Ø§Ø°Ų† Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ؊ØŦب Ø§Ų† Ų„Ø§ ŲŠŲƒŲˆŲ† ŲØ§ØąØēا", + "permission_onboarding_back": "ØŽŲ„Ų", + "permission_onboarding_continue_anyway": "ØĒŲˆØ§ØĩŲ„ ØšŲ„Ų‰ ØŖŲŠ Ø­Ø§Ų„", + "permission_onboarding_get_started": "Ø§Ų„Ø¨Ø¯ØĄ", + "permission_onboarding_go_to_settings": "Ø§Ø°Ų‡Ø¨ Ų„Ų„Ø§ØšØ¯Ø§Ø¯Ø§ØĒ", + "permission_onboarding_permission_denied": "ØĒŲ… ØąŲØļ Ø§Ų„ØĨØ°Ų†. Ų„Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ØŒ Ų‚Ų… Ø¨Ų…Ų†Ø­ ØŖØ°ŲˆŲ†Ø§ØĒ Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲŲŠØ¯ŲŠŲˆ ؁؊ Ø§Ų„ØĨؚداداØĒ.", + "permission_onboarding_permission_granted": "ØĒŲ… ØĒØŖŲ…ŲŠŲ† Ø§Ų„ØĒØĩØąŲŠØ­! ؈ØļØšŲƒ ØĒŲ…Ø§Ų….", + "permission_onboarding_permission_limited": "ØĨØ°Ų† Ų…Ø­Ø¯ŲˆØ¯. Ų„Ų„ØŗŲ…Ø§Ø­ Ø¨Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØĒØˇØ¨ŲŠŲ‚ ؈ØĨØ¯Ø§ØąØŠ Ų…ØŦŲ…ŲˆØšØŠ Ø§Ų„Ų…ØšØąØļ Ø¨Ø§Ų„ŲƒØ§Ų…Ų„ØŒ Ø§Ų…Ų†Ø­ ØŖØ°ŲˆŲ†Ø§ØĒ Ø§Ų„ØĩŲˆØą ŲˆØ§Ų„ŲŲŠØ¯ŲŠŲˆ ؁؊ Ø§Ų„ØĨؚداداØĒ.", + "permission_onboarding_request": "؊ØĒØˇŲ„Ø¨ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚ ØĨØ°Ų†Ų‹Ø§ Ų„ØšØąØļ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ.", "person": "Ø´ØŽØĩ", "person_age_months": "{months, plural, one {# Ø´Ų‡Øą} other {# Ø§Ø´Ų‡Øą}} Ų…Ų† Ø§Ų„ØšŲ…Øą", "person_age_year_months": "1 ØšØ§Ų…, {months, plural, one {# Ø´Ų‡Øą} other {# Ø§Ø´Ų‡Øą}} Ų…Ų† Ø§Ų„ØšŲ…Øą", @@ -1625,10 +1730,13 @@ "person_birthdate": "ŲˆŲ„Ø¯ ؁؊ {date}", "person_hidden": "{name}{hidden, select, true { (Ų…ØŽŲŲŠ)} other {}}", "person_recognized": "Ø´ØŽØĩ ØĒŲ… Ø§Ų„ØĒØšØąŲ ØšŲ„ŲŠŲ‡", + "person_selected": "Ø´ØŽØĩ Ų…ØŽØĒØ§Øą", "photo_shared_all_users": "ŲŠØ¨Ø¯Ųˆ ØŖŲ†Ųƒ Ø´Ø§ØąŲƒØĒ ØĩŲˆØąŲƒ Ų…Øš ØŦŲ…ŲŠØš Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† ØŖŲˆ Ų„ŲŠØŗ Ų„Ø¯ŲŠŲƒ ØŖŲŠ Ų…ØŗØĒØŽØ¯Ų… Ų„Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų…ØšŲ‡.", "photos": "Ø§Ų„ØĩŲˆØą", "photos_and_videos": "Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", + "photos_count": "{count, plural, one {{count, number} ØĩŲˆØąØŠ} other {{count, number} ØĩŲˆØą}}", "photos_from_previous_years": "ØĩŲˆØą Ų…Ų† Ø§Ų„ØŗŲ†ŲˆØ§ØĒ Ø§Ų„ØŗØ§Ø¨Ų‚ØŠ", + "photos_only": "ØĩŲˆØą ŲŲ‚Øˇ", "pick_a_location": "ا؎ØĒØą Ų…ŲˆŲ‚ØšŲ‹Ø§", "pick_custom_range": "Ų†ØˇØ§Ų‚ Ų…ØŽØĩØĩ", "pick_date_range": "حدد Ų†ØˇØ§Ų‚ Ø§Ų„ØĒØ§ØąŲŠØŽ", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "ØĒ؁ØļŲŠŲ„ ØĒØ´ØēŲŠŲ„ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„ØŖØĩŲ„ŲŠØŠ Ø¨Ø¯Ų„Ø§Ų‹ Ų…Ų† Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø­ŲˆŲ„ØŠ. ØĨذا Ų„Ų… ŲŠŲƒŲ† Ø§Ų„Ų…Ų„Ų Ø§Ų„ØŖØĩŲ„ŲŠ Ų…ØĒŲˆØ§ŲŲ‚Ų‹Ø§ØŒ ŲŲ‚Ø¯ Ų„Ø§ ؊ØĒŲ… ØĒØ´ØēŲŠŲ„Ų‡ Ø¨Ø´ŲƒŲ„ ØĩØ­ŲŠØ­.", "play_transcoded_video": "ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ŲØšØ§Ø¯ ØĒØąŲ…ŲŠØ˛Ų‡", "please_auth_to_access": "Ø§Ų„ØąØŦØ§ØĄ Ø§Ų„Ų‚ŲŠØ§Ų… Ø¨Ø§Ų„Ų…ØĩØ§Ø¯Ų‚ØŠ Ų„Ų„ŲˆØĩŲˆŲ„", - "plugin_method_filter_type": "ØĒØĩŲŲŠØŠ", - "plugin_method_filter_type_description": "Ų‡Ø°Ų‡ Ø§Ų„ŲˆØŗŲŠŲ„ØŠ ØĒØŗØĒØˇŲŠØš ØĒØĩŲŲŠØŠ Ø§Ų„Ø§Ø­Ø¯Ø§ØĢ ؈ ØĒŲ…Ų†Øš ØĒØ´ØēŲŠŲ„ Ø§Ų„ØŽØˇŲˆØ§ØĒ Ø§Ų„ØĒØ§Ų„ŲŠØŠ Ø´ØąØˇŲŠØ§", "port": "Ø§Ų„Ų…Ų†ŲØ°", "preferences_settings_subtitle": "Ø§Ø¯Ø§ØąØŠ ØĒ؁ØļŲŠŲ„Ø§ØĒ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", "preferences_settings_title": "Ø§Ų„ØĒ؁ØļŲŠŲ„Ø§ØĒ", @@ -1665,11 +1771,11 @@ "privacy": "Ø§Ų„ØŽØĩ؈ØĩŲŠØŠ", "profile": "Ø­ØŗØ§Ø¨ ØĒØšØąŲŠŲŲŠ", "profile_drawer_app_logs": "Ø§Ų„ØŗØŦŲ„Ø§ØĒ", + "profile_drawer_client_server_up_to_date": "Ø§Ų„ØšŲ…ŲŠŲ„ ŲˆØ§Ų„ØŽØ§Ø¯Ų… Ų…Ø­Ø¯ØĢØ§Ų†", "profile_drawer_github": "Github", "profile_drawer_readonly_mode": "ØĒŲ… ØĒŲØšŲŠŲ„ ؈ØļØš Ø§Ų„Ų‚ØąØ§ØĄØŠ ŲŲ‚Øˇ. اØļØēØˇ Ų…ØˇŲˆŲ„Ø§ ØšŲ„Ų‰ ØąŲ…Ø˛ ØĩŲˆØąØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ų„Ų„ØŽØąŲˆØŦ.", "profile_image_of_user": "ØĩŲˆØąØŠ Ø§Ų„Ų…Ų„Ų Ø§Ų„Ø´ØŽØĩ؊ Ų„Ų€ {user}", "profile_picture_set": "Ų…ØŦŲ…ŲˆØšØŠ Ø§Ų„ØĩŲˆØą Ø§Ų„Ø´ØŽØĩŲŠØŠ.", - "projection_type": "Ų†ŲˆØš Ø§Ų„ØšØąØļ", "public_album": "Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… Ø§Ų„ØšØ§Ų…", "public_share": "Ų…Ø´Ø§ØąŲƒØŠ ØšØ§Ų…ØŠ", "purchase_account_info": "Ø¯Ø§ØšŲ…", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Ø­Ø§Ų„ØŠ Ø§Ų„Ø¯Ø§ØšŲ…", "purchase_individual_title": "ŲØąØ¯ŲŠ", "purchase_input_suggestion": "Ų‡Ų„ Ų„Ø¯ŲŠŲƒ ؅؁ØĒاح Ø§Ų„Ų…Ų†ØĒØŦ؟ ØŖØ¯ØŽŲ„ Ø§Ų„Ų…ŲØĒاح ØŖØ¯Ų†Ø§Ų‡", + "purchase_license_subtitle": "Ų‚Ų… Ø¨Ø´ØąØ§ØĄ Immich Ų„Ø¯ØšŲ… Ø§Ų„ØĒØˇŲˆŲŠØą Ø§Ų„Ų…ØŗØĒŲ…Øą Ų„Ų„ØŽØ¯Ų…ØŠ", "purchase_lifetime_description": "Ø§Ų„Ø´ØąØ§ØĄ Ų„Ų…Ø¯Ų‰ Ø§Ų„Ø­ŲŠØ§ØŠ", "purchase_option_title": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ø´ØąØ§ØĄ", "purchase_panel_info_1": "؊ØĒØˇŲ„Ø¨ Ø¨Ų†Ø§ØĄ Immich Ø§Ų„ŲƒØĢŲŠØą Ų…Ų† Ø§Ų„ŲˆŲ‚ØĒ ŲˆØ§Ų„ØŦŲ‡Ø¯ØŒ ŲˆŲ„Ø¯ŲŠŲ†Ø§ Ų…Ų‡Ų†Ø¯ØŗŲˆŲ† ŲŠØšŲ…Ų„ŲˆŲ† Ø¨Ø¯ŲˆØ§Ų… ŲƒØ§Ų…Ų„ Ų„ØŦØšŲ„Ų‡ ØŖŲØļŲ„ Ų…Ø§ ŲŠŲ…ŲƒŲ†. Ų…Ų‡Ų…ØĒŲ†Ø§ Ų‡ŲŠ ØŖŲ† ØĒØĩبح Ø§Ų„Ø¨ØąŲ…ØŦŲŠØ§ØĒ ؅؁ØĒŲˆØ­ØŠ Ø§Ų„Ų…ØĩØ¯Øą ŲˆŲ…Ų…Ø§ØąØŗØ§ØĒ Ø§Ų„ØšŲ…Ų„ Ø§Ų„ØŖØŽŲ„Ø§Ų‚ŲŠØŠ Ų…ØĩØ¯Øą Ø¯ØŽŲ„ Ų…ØŗØĒØ¯Ø§Ų… Ų„Ų„Ų…ØˇŲˆØąŲŠŲ† ؈ØĨŲ†Ø´Ø§ØĄ Ų†Ø¸Ø§Ų… Ø¨ŲŠØĻ؊ ŲŠØ­ØĒØąŲ… Ø§Ų„ØŽØĩ؈ØĩŲŠØŠ Ų…Øš بداØĻŲ„ Ø­Ų‚ŲŠŲ‚ŲŠØŠ Ų„Ų„ØŽØ¯Ų…Ø§ØĒ Ø§Ų„ØŗØ­Ø§Ø¨ŲŠØŠ Ø§Ų„Ø§ØŗØĒØēŲ„Ø§Ų„ŲŠØŠ.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "ØĒŲ…ØĒ ØĨؚاد؊ ØĒØšŲŠŲŠŲ† {count, plural, one {# Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {# Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} ØĨŲ„Ų‰ Ø´ØŽØĩ ØŦØ¯ŲŠØ¯", "reassing_hint": "ØĒØšŲŠŲŠŲ† Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ Ų„Ø´ØŽØĩ Ų…ŲˆØŦŲˆØ¯", "recent": "Ø­Ø¯ŲŠØĢ", + "recent_albums": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ø­Ø¯ŲŠØĢØŠ", "recent_searches": "ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„ØŖØŽŲŠØąØŠ", "recently_added": "اØļ؊؁ Ų…Ø¤ØŽØąØ§", - "recently_added_body": "Ø§Ų†ØĒŲ‚Ų„ Ų…Ø¨Ø§Ø´ØąØŠŲ‹ ØĨŲ„Ų‰ ŲƒŲ„ Ų…Ø§ ØŖØļ؁ØĒŲ‡ Ų…Ø¤ØŽØąØ§Ų‹ ØšŲ„Ų‰ ØĩŲØ­ØŠ Ų…ØŽØĩØĩØŠ.", - "recently_added_description": "ØĒØĩŲØ­ Ų…Ų…ØĒŲ„ŲƒØ§ØĒ؃ Ų…ØąØĒب؊ Ø­ØŗØ¨ ØĒØ§ØąŲŠØŽ ØĒØ­Ų…ŲŠŲ„Ų‡Ø§ ØĨŲ„Ų‰ Immich", "recently_added_page_title": "ØŖØļ؊؁ Ų…Ø¤ØŽØąØ§", - "recently_added_title": "ØĨØļØ§Ų Ų…Ø¤ØŽØąØ§Ų‹", "recently_taken": "ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ Ų…Ø¤ØŽØąŲ‹Ø§", + "recently_taken_page_title": "ØĒŲ… Ø§Ų„ØĒŲ‚Ø§ØˇŲ‡Ø§ Ų…Ø¤ØŽØąŲ‹Ø§", "refresh": "ØĒØ­Ø¯ŲŠØĢ", "refresh_encoded_videos": "ØĒØ­Ø¯ŲŠØĢ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø´ŲØąØŠ", "refresh_faces": "ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲˆØŦŲˆŲ‡", "refresh_metadata": "ØĒØ­Ø¯ŲŠØĢ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ", "refresh_thumbnails": "ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØĩØēØąØŠ", "refreshed": "اؚاد؊ ØĒØ­Ų…ŲŠŲ„", + "refreshes_every_file": "ØĨؚاد؊ Ų‚ØąØ§ØĄØŠ ŲƒØ§ŲØŠ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲˆØŦŲˆØ¯ØŠ ŲˆØ§Ų„ØŦØ¯ŲŠØ¯ØŠ", "refreshing_encoded_video": "ØŦØ§ØąŲ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØąŲ…Ø˛", "refreshing_faces": "ØŦØ§ØąŲŠ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ŲˆØŦŲˆŲ‡", "refreshing_metadata": "ØŦØ§ØąŲ ØĒØ­Ø¯ŲŠØĢ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ ØŖŲ†Ųƒ ØĒØąŲŠØ¯ ØĨØ˛Ø§Ų„ØŠ {count, plural, one {# Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {# Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}} Ų…Ų† ØąØ§Ø¨Øˇ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų‡Ø°Ø§ØŸ", "remove_assets_title": "Ų‡Ų„ ØĒØąŲŠØ¯ ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ؟", "remove_custom_date_range": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„Ų…ØŽØĩØĩ", - "remove_filter": "Ø§Ø˛Ø§Ų„ØŠ Ø§Ų„ØĒØĩŲŲŠØŠ", + "remove_deleted_assets": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ØēŲŠØą Ų…ØĒØĩŲ„ØŠ", "remove_from_album": "ØĨØ˛Ø§Ų„ØŠ Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "remove_from_album_action_prompt": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ {count} Ų…Ų† Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…", "remove_from_favorites": "ØĨØ˛Ø§Ų„ØŠ Ų…Ų† Ø§Ų„Ų…ŲØļŲ„ØŠ", @@ -1756,22 +1863,29 @@ "remove_memory": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ø°Ø§ŲƒØąØŠ", "remove_photo_from_memory": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØĩŲˆØąØŠ Ų…Ų† Ų‡Ø°Ų‡ Ø§Ų„Ø°ŲƒØąŲ‰", "remove_tag": "Ø§Ø˛Ø§Ų„ØŠ ØšŲ„Ø§Ų…ØŠ", + "remove_url": "ØĨØ˛Ø§Ų„ØŠ ØšŲ†ŲˆØ§Ų† URL", "remove_user": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "removed_api_key": "ØĒŲ… ØĨØ˛Ø§ØŠ ؅؁ØĒاح API: â€Ēâ€Ģ{name}", "removed_from_archive": "ØĒŲ…ØĒ ØĨØ˛Ø§Ų„ØĒŲ‡Ø§ Ų…Ų† Ø§Ų„ØŖØąØ´ŲŠŲ", "removed_from_favorites": "ØĒŲ…ØĒ Ø§Ų„ØĨØ˛Ø§Ų„ØŠ Ų…Ų† Ø§Ų„Ų…ŲØļŲ„ØŠ", "removed_from_favorites_count": "{count, plural, other {ØŖŲØ˛ŲŠŲ„ØĒ #}} Ų…Ų† Ø§Ų„ØĒ؁ØļŲŠŲ„Ø§ØĒ", "removed_memory": "Ø§Ų„Ø°Ø§ŲƒØąØŠ Ø§Ų„Ų…Ø­Ø°ŲˆŲØŠ", + "removed_photo_from_memory": "ØĒŲ… ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØĩŲˆØąØŠ Ų…Ų† Ø§Ų„Ø°Ø§ŲƒØąØŠ", "removed_tagged_assets": "ØĒŲ…ØĒ ØĨØ˛Ø§Ų„ØŠ Ø§Ų„ØšŲ„Ø§Ų…ØŠ Ų…Ų† {count, plural, one {# Ø§Ų„ØŖØĩŲ„} other {# Ø§Ų„ØŖØĩŲˆŲ„}}", "rename": "ØĨؚاد؊ ØĒØŗŲ…ŲŠØŠ", + "repair": "ØĨØĩŲ„Ø§Ø­", + "repair_no_results_message": "ØŗØĒØ¸Ų‡Øą Ų‡Ų†Ø§ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…ŲŲ‚ŲˆØ¯ØŠ ŲˆØŖŲŠØļØ§Ų‹ Ø§Ų„ØĒ؊ Ų„Ų… ؊ØĒŲ… ØĒØšŲ‚Ø¨Ų‡Ø§", + "replace_with_upload": "Ø§ØŗØĒØ¨Ø¯Ø§Ų„ Ø¨Ø§Ų„ØąŲØš", "repository": "Ø§Ų„Ų…ØŗØĒŲˆØ¯Øš", "require_password": "؊ØĒØˇŲ„Ø¨ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", + "require_user_to_change_password_on_first_login": "Ų…ØˇØ§Ų„Ø¨ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… بØĒØēŲŠŲŠØą ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą ØšŲ†Ø¯ ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø¯ØŽŲˆŲ„ Ų„ØŖŲˆŲ„ Ų…ØąØŠ", "rescan": "ØĨؚاد؊ Ø§Ų„Ų…ØŗØ­", "reset": "ØĨؚاد؊ ØļØ¨Øˇ", "reset_password": "ØĨؚاد؊ ØĒØšŲŠŲŠŲ† ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", "reset_people_visibility": "ØĨؚاد؊ ØļØ¨Øˇ Ø¸Ų‡ŲˆØą Ø§Ų„ØŖØ´ØŽØ§Øĩ", "reset_pin_code": "اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ PIN", "reset_pin_code_description": "اذا Ų†ØŗŲŠØĒ ØąŲ…Ø˛ Ø§Ų„PIN Ø§Ų„ØŽØ§Øĩ Ø¨ŲƒØŒ Ø¨Ø§Ų…ŲƒØ§Ų†Ųƒ Ø§Ų„ØĒŲˆØ§ØĩŲ„ Ų…Øš Ų…Ø¯ŲŠØą Ø§Ų„ØŽØ§Ø¯Ų… Ų„Ø¯ŲŠŲƒ Ų„Ø§ØšØ§Ø¯ØŠ ØĒØšŲŠŲŠŲ†Ų‡", + "reset_pin_code_success": "ØĒŲ… اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø¨Ų†ØŦاح", "reset_pin_code_with_password": "ŲŠŲ…ŲƒŲ†Ųƒ داØĻŲ…Ø§ اؚاد؊ ØĒØšŲŠŲŠŲ† ØąŲ…Ø˛ Ø§Ų„PIN Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ ØšŲ† ØˇØąŲŠŲ‚ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", "reset_sqlite": "ØĨؚاد؊ ØĒØšŲŠŲŠŲ† Ų‚Ø§ØšØ¯ØŠ Ø¨ŲŠØ§Ų†Ø§ØĒ SQLite", "reset_sqlite_clear_app_data": "Ų…ØŗØ­ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "ØĒŲ… Ø­Ų„ ØŦŲ…ŲŠØš Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ", "restore": "Ø§Ų„Ø§ØŗØĒØšØ§Ø¯Ų‡ Ų…Ų† ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "restore_all": "Ø§ØŗØĒؚاد؊ Ø§Ų„ŲƒŲ„", + "restore_trash_action_prompt": "ØĒŲ… Ø§ØŗØĒؚاد؊ {count} Ų…Ų† Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "restore_user": "Ø§ØŗØĒؚاد؊ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "restored_asset": "Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…ØŗØĒؚاد؊", "resume": "Ø§ØŗØĒØĻŲ†Ø§Ų", "resume_paused_jobs": "Ø§ØŗØĒŲƒŲ…Ø§Ų„ {count, plural, one {# ŲˆØ¸ŲŠŲØŠ Ų…ØšŲ„Ų‚ØŠ} other {# ŲˆØ¸Ø§ØĻ؁ Ų…ØšŲ„Ų‚ØŠ}}", + "retry_upload": "ØŖØšØ¯ Ų…Ø­Ø§ŲˆŲ„ØŠ Ø§Ų„ØąŲØš", "review_duplicates": "Ų…ØąØ§ØŦؚ؊ Ø§Ų„ØĒŲƒØąØ§ØąØ§ØĒ", "review_large_files": "Ų…ØąØ§ØŦؚ؊ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„ŲƒØ¨ŲŠØąØŠ", "role": "Ø§Ų„Ø¯ŲˆØą", @@ -1796,6 +1912,7 @@ "role_viewer": "Ø§Ų„ØšØ§ØąØļ", "running": "Ų‚ŲŠØ¯ Ø§Ų„ØĒØ´ØēŲŠŲ„", "save": "Ø­ŲØ¸", + "save_to_gallery": "Ø­ŲØ¸ Ø§Ų„Ų‰ Ø§Ų„Ų…ØšØąØļ", "saved": "ØĒŲ… Ø§Ų„Ø­ŲØ¸", "saved_api_key": "ØĒŲ… Ø­ŲØ¸ ؅؁ØĒاح Ø§Ų„Ų€ API", "saved_profile": "ØĒŲ… Ø­ŲØ¸ Ø§Ų„Ų…Ų„Ų", @@ -1806,10 +1923,9 @@ "scan": "بحØĢ", "scan_all_libraries": "ŲØ­Øĩ ŲƒŲ„ Ø§Ų„Ų…ŲƒØĒباØĒ", "scan_library": "Ų…ØŗØ­", + "scan_settings": "ØĨؚداداØĒ Ø§Ų„ŲØ­Øĩ", "scanning": "ØŦØ§ØąŲŠ Ø§Ų„Ø¨Ø­ØĢ", "scanning_for_album": "ØŦØ§ØąŲ Ø§Ų„ŲØ­Øĩ ØšŲ† ØŖŲ„Ø¨ŲˆŲ…...", - "screencast_mode_description": "ØĨØ¸Ų‡Ø§Øą Ų…Ø¤Ø´ØąØ§ØĒ ØŖØ­Ø¯Ø§ØĢ Ų„ŲˆØ­ØŠ Ø§Ų„Ų…ŲØ§ØĒŲŠØ­ ŲˆØ§Ų„Ų…Ø§ŲˆØŗ ØšŲ„Ų‰ Ø§Ų„Ø´Ø§Ø´ØŠ", - "screencast_mode_title": "ØĒØ¨Ø¯ŲŠŲ„ ؈ØļØš ØĒØŗØŦŲŠŲ„ Ø§Ų„Ø´Ø§Ø´ØŠ", "search": "Ø§Ų„Ø¨Ø­ØĢ", "search_albums": "Ø§Ų„Ø¨Ø­ØĢ ؁؊ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", "search_by_context": "Ø§Ų„Ø¨Ø­ØĢ Ø­ØŗØ¨ Ø§Ų„ØŗŲŠØ§Ų‚", @@ -1817,8 +1933,6 @@ "search_by_description_example": "ŲŠŲˆŲ… Ø§Ų„Ų…Ø´ŲŠ Ų„Ų…ØŗØ§ŲØ§ØĒ ØˇŲˆŲŠŲ„ØŠ ؁؊ ØŗØ§Ø¨Ø§", "search_by_filename": "Ø§Ų„Ø¨Ø­ØĢ بØĨØŗŲ… Ø§Ų„Ų…Ų„Ų ØŖŲˆ Ų†ŲˆØšŲ‡", "search_by_filename_example": "ŲƒŲ€ IMG_1234.JPG ØŖŲˆ PNG", - "search_by_full_path": "بحØĢ Ø¨Ø§Ų„Ų…ØŗØ§Øą Ø§Ų„ŲƒØ§Ų…Ų„ Ø§Ųˆ Ø§Ų„Ų…ØŦŲ„Ø¯", - "search_by_full_path_example": "/Ø§Ø­Ų…Ø¯/Ų…Ø´Ø§ØąŲŠØš/ØˇØ¨Ø§ØšØŠ_ØĢŲ„Ø§ØĢŲŠØŠ_Ø§Ų„Ø§Ø¨ØšØ§Ø¯/2026-07-01 - ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ų…Ø´Ø§ØąŲŠØš, ØˇØ¨Ø§ØšØŠ_ØĢŲ„Ø§ØĢŲŠØŠ_Ø§Ų„Ø§Ø¨ØšØ§Ø¯, 2026 Ø§Ų„ØŽ.", "search_by_ocr": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„ØĒØšØąŲ Ø§Ų„Ø¨ØĩØąŲŠ ØšŲ„Ų‰ Ø§Ų„Ø­ØąŲˆŲ", "search_by_ocr_example": "Ų„Ø§ØĒŲŠŲ‡", "search_camera_lens_model": "بحØĢ Ų†Ų…ŲˆØ°ØŦ Ø§Ų„ØšØ¯ØŗØŠ...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "ا؎ØĒØą Ø§Ų„Ų…ŲˆŲ‚Øš", "search_filter_media_type": "Ų†ŲˆØš Ø§Ų„ŲˆØŗØ§ØĻØˇ", "search_filter_media_type_title": "ا؎ØĒØą Ų†ŲˆØš Ø§Ų„ŲˆØŗØ§ØĻØˇ", + "search_filter_ocr": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„ØĒØšØąŲ Ø§Ų„Ø¨ØĩØąŲŠ ØšŲ„Ų‰ Ø§Ų„Ø­ØąŲˆŲ", "search_filter_people_title": "ا؎ØĒØą Ø§Ų„Ø§Ø´ØŽØ§Øĩ", "search_filter_star_rating": "ØĒŲ‚ŲŠŲŠŲ… Ø§Ų„Ų†ØŦŲˆŲ…", "search_filter_tags_title": "â€ĒØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Ų„Ø§ ؊؈ØŦد ØŖØ´ØŽØ§Øĩ Ø¨Ø§Ų„Ø§ØŗŲ… \"{name}\"", "search_no_result": "Ų„Ø§ ØĒ؈ØŦد Ų†ØĒاØĻØŦ، Ø­Ø§ŲˆŲ„ Ø§ØŗØĒØŽØ¯Ø§Ų… Ų…ØĩØˇŲ„Ø­ بحØĢ Ų…ØŽØĒ؄؁ Ø§Ųˆ ØĒØąŲƒŲŠØ¨ØŠ", "search_options": "ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ø¨Ø­ØĢ", + "search_page_categories": "؁ØĻاØĒ", + "search_page_motion_photos": "Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…ØĒØ­ØąŲƒŲ‡", + "search_page_no_objects": "Ų„Ø§ ØĒ؈ØŦد Ų…ØšŲ„ŲˆŲ…Ø§ØĒ ØšŲ† ØŖØ´ŲŠØ§ØĄ Ų…ØĒاح؊", + "search_page_no_places": "Ų„Ø§ ØĒ؈ØŦد Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ų…ØĒŲˆŲØąØŠ Ų„Ų„ØŖŲ…Ø§ŲƒŲ†", + "search_page_screenshots": "Ų„Ų‚ØˇØ§ØĒ Ø§Ų„Ø´Ø§Ø´ØŠ", "search_page_search_photos_videos": "ابحØĢ ØšŲ† ØĩŲˆØąŲƒ Ø§Ųˆ ŲØ¯ŲŠŲˆŲ‡Ø§ØĒ؃", + "search_page_selfies": "ØĩŲˆØą ذاØĒŲŠŲ‡", + "search_page_things": "ØŖØ´ŲŠØ§ØĄ", "search_page_view_all_button": "ØšØąØļ Ø§Ų„ŲƒŲ„", + "search_page_your_activity": "Ų†Ø´Ø§ØˇŲƒ", + "search_page_your_map": "ØŽØąŲŠØˇØĒ؃", "search_people": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø§Ų„ØŖØ´ØŽØ§Øĩ", "search_places": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø§Ų„ØŖŲ…Ø§ŲƒŲ†", "search_rating": "Ø§Ų„Ø¨Ø­ØĢ Ø­ØŗØ¨ Ø§Ų„ØĒŲ‚ŲŠŲŠŲ…...", + "search_result_page_new_search_hint": "بحØĢ ØŦØ¯ŲŠØ¯", "search_settings": "ØĨؚداداØĒ Ø§Ų„Ø¨Ø­ØĢ", "search_state": "Ø§Ų„Ø¨Ø­ØĢ Ø­ØŗØ¨ Ø§Ų„ŲˆŲ„Ø§ŲŠØŠ...", + "search_suggestion_list_smart_search_hint_1": "؊ØĒŲ… ØĒŲ…ŲƒŲŠŲ† Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„Ø°ŲƒŲŠ Ø§ŲØĒØąØ§ØļŲŠŲ‹Ø§ ، Ų„Ų„Ø¨Ø­ØĢ ØšŲ† Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ ، Ø§ØŗØĒØŽØ¯Ų… Ø¨Ų†Ø§ØĄ Ø§Ų„ØŦŲ…Ų„ØŠ. ", + "search_suggestion_list_smart_search_hint_2": "Ų…: Ø§Ų„Ø¨Ø­ØĢ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "search_tags": "Ø§Ų„Ø¨Ø­ØĢ ØšŲ† Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ...", "search_timezone": "Ø§Ų„Ø¨Ø­ØĢ Ø­ØŗØ¨ Ø§Ų„Ų…Ų†ØˇŲ‚ØŠ Ø§Ų„Ø˛Ų…Ų†ŲŠØŠ...", "search_type": "Ų†ŲˆØš Ø§Ų„Ø¨Ø­ØĢ", @@ -1868,6 +1995,7 @@ "select_albums": "ا؎ØĒØą Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", "select_all": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„", "select_all_duplicates": "ØĒØ­Ø¯ŲŠØ¯ ØŦŲ…ŲŠØš Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ŲƒØąØąØŠ", + "select_all_in": "ا؎ØĒØą Ø§Ų„ŲƒŲ„ ؁؊ {group}", "select_avatar_color": "ØĒØ­Ø¯ŲŠØ¯ Ų„ŲˆŲ† Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ø´ØŽØĩŲŠØŠ", "select_count": "{count, plural, one {ا؎ØĒØą #} other {ا؎ØĒØą #}}", "select_cutoff_date": "حدد ØĒØ§ØąŲŠØŽ Ø§Ų„Ų‚ØˇØš", @@ -1875,13 +2003,14 @@ "select_featured_photo": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…Ų…ŲŠØ˛ØŠ", "select_from_computer": "ØĒØ­Ø¯ŲŠØ¯ Ų…Ų† Ø§Ų„Ø­Ø§ØŗØ¨ Ø§Ų„ØĸŲ„ŲŠ", "select_keep_all": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØŖØ­ØĒŲØ§Ø¸ Ø¨Ø§Ų„ŲƒŲ„", + "select_library_owner": "ØĒØ­Ø¯ŲŠØ¯ Ų…Ø§Ų„ŲŲƒ Ø§Ų„Ų…ŲƒØĒب؊", "select_new_face": "ØĒØ­Ø¯ŲŠØ¯ ؈ØŦŲ‡ ØŦØ¯ŲŠØ¯", "select_people": "ا؎ØĒØą Ø§Ų„Ø§Ø´ØŽØ§Øĩ", "select_person": "ا؎ØĒØą Ø´ØŽØĩ", "select_person_to_tag": "ا؎ØĒØą Ø´ØŽØĩ Ų„ŲˆØļØš ØšŲ„Ø§Ų…ØŠ", "select_photos": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ØĩŲˆØą", - "select_quality": "ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„Ø¯Ų‚ØŠ", "select_trash_all": "ØĒØ­Ø¯ŲŠØ¯ Ø­Ø°Ų Ø§Ų„ŲƒŲ„Ų", + "select_user_for_sharing_page_err_album": "ŲØ´Ų„ ؁؊ ØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ…", "selected": "Ø§Ų„ØĒØ­Ø¯ŲŠØ¯", "selected_count": "{count, plural, other {# Ų…Ø­Ø¯Ø¯ØŠ }}", "selected_gps_coordinates": "ØĨحداØĢŲŠØ§ØĒ Ų†Ø¸Ø§Ų… ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„Ų…ŲˆØ§Ų‚Øš Ø§Ų„Ų…ØŽØĒØ§ØąØŠ", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ", "setting_image_viewer_preview_subtitle": "ØĒŲ…ŲƒŲŠŲ† ØĒØ­Ų…ŲŠŲ„ ØĩŲˆØąØŠ Ų…ØĒŲˆØŗØˇØŠ Ø§Ų„Ø¯Ų‚ØŠ.ØĒØšØˇŲŠŲ„ ØĨŲ…Ø§ Ų„ØĒØ­Ų…ŲŠŲ„ Ų…Ø¨Ø§Ø´ØąØŠ ØŖŲˆ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ų…Ø¨Ø§Ø´ØąØŠ.", "setting_image_viewer_preview_title": "ØĒØ­Ų…ŲŠŲ„ ØĩŲˆØąØŠ Ų…ØšØ§ŲŠŲ†ØŠ", + "setting_image_viewer_title": "Ø§Ų„ØĩŲˆØą", "setting_languages_apply": "ØĒØēŲŠŲŠØą Ø§Ų„ØĨؚداداØĒ", "setting_languages_subtitle": "ØĒØēŲŠŲŠØą Ų„ØēØŠ Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", + "setting_notifications_notify_failures_grace_period": "Ø§Ų„ØĒŲ†Ø¨ŲŠŲ‡ Ø¨ŲØ´Ų„ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ: {duration}", + "setting_notifications_notify_hours": "{count} ØŗØ§ØšØ§ØĒ", + "setting_notifications_notify_immediately": "؁؊ Ø§Ų„Ø­Ø§Ų„", "setting_notifications_notify_minutes": "{count} Ø¯Ų‚Ø§ØĻŲ‚", + "setting_notifications_notify_never": "ØŖØ¨Ø¯Ø§Ų‹", "setting_notifications_notify_seconds": "{count} ØĢŲˆØ§Ų†ŲŠ", + "setting_notifications_single_progress_subtitle": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„ØĒŲ‚Ø¯Ų… Ø§Ų„ØĒ؁ØĩŲŠŲ„ŲŠØŠ ØĒØ­Ų…ŲŠŲ„ Ų„ŲƒŲ„ ØŖØĩŲ„", + "setting_notifications_single_progress_title": "ØĨØ¸Ų‡Ø§Øą ØĒŲ‚Ø¯Ų… Ø§Ų„ØĒŲØ§ØĩŲŠŲ„ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠØŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ", "setting_notifications_subtitle": "اØļØ¨Øˇ ØĒ؁ØļŲŠŲ„Ø§ØĒ Ø§Ų„ØĨØŽØˇØ§Øą", + "setting_notifications_total_progress_subtitle": "Ø§Ų„ØĒŲ‚Ø¯Ų… Ø§Ų„ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØšØ§Ų… (ØĒŲ… Ø§Ų„Ų‚ŲŠØ§Ų… Ø¨Ų‡/ØĨØŦŲ…Ø§Ų„ŲŠ Ø§Ų„ØŖØĩŲˆŲ„)", + "setting_notifications_total_progress_title": "ØĨØ¸Ų‡Ø§Øą Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„ØŽŲ„ŲŲŠØŠ Ø§Ų„ØĒŲ‚Ø¯Ų… Ø§Ų„Ų…Ø­ØąØ˛", "setting_video_viewer_auto_play_subtitle": "Ø¨Ø¯ØĄ ØĒØ´ØēŲŠŲ„ Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§ ØšŲ†Ø¯ ؁ØĒØ­Ų‡Ø§", "setting_video_viewer_auto_play_title": "ØĒØ´ØēŲŠŲ„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§", "setting_video_viewer_looping_title": "ØĒŲƒØąØ§Øą Ų…Ų‚ØˇØš ŲŲŠØ¯ŲŠŲˆ ØĒŲ„Ų‚Ø§ØĻŲŠŲ‹Ø§", "setting_video_viewer_original_video_subtitle": "ØšŲ†Ø¯ بØĢ ŲŲŠØ¯ŲŠŲˆ Ų…Ų† Ø§Ų„ØŽØ§Ø¯Ų…ØŒ Ø´ØēŲ‘Ų„ Ø§Ų„Ų†ØŗØŽØŠ Ø§Ų„ØŖØĩŲ„ŲŠØŠ Ø­ØĒŲ‰ Ų…Øš ØĒŲˆŲØą ØĒØąŲ…ŲŠØ˛ Ø¨Ø¯ŲŠŲ„. Ų‚Ø¯ ŲŠØ¤Ø¯ŲŠ Ø°Ų„Ųƒ ØĨŲ„Ų‰ ØĒŲ‚ØˇŲŠØš اØĢŲ†Ø§ØĄ Ø§Ų„ØšØąØļ . ØĒŲØ´ØēŲ‘Ų„ Ø§Ų„ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ Ø§Ų„Ų…ØĒŲˆŲØąØŠ Ų…Ø­Ų„ŲŠŲ‹Ø§ بØŦŲˆØ¯ØŠ ØŖØĩŲ„ŲŠØŠ بØēØļ Ø§Ų„Ų†Ø¸Øą ØšŲ† Ų‡Ø°Ø§ Ø§Ų„ØĨؚداد.", "setting_video_viewer_original_video_title": "اØŦØ¨Ø§Øą ØšØąØļ Ø§Ų„ŲØ¯ŲŠŲˆ Ø§Ų„Ø§ØĩŲ„ŲŠ", "settings": "Ø§Ų„ØĨؚداداØĒ", + "settings_require_restart": "ŲŠØąØŦŲ‰ ØĨؚاد؊ ØĒØ´ØēŲŠŲ„ Ų„ØĒØˇØ¨ŲŠŲ‚ Ų‡Ø°Ø§ Ø§Ų„ØĨؚداد", "settings_saved": "ØĒŲ… Ø­ŲØ¸ Ø§Ų„ØĨؚداداØĒ", "setup_pin_code": "ØĒØ­Ø¯ŲŠØ¯ ØąŲ…Ø˛ PIN", "share": "Ų…Ø´Ø§ØąŲƒØŠ", + "share_action_prompt": "ØĒŲ… Ų…Ø´Ø§ØąŲƒØŠ {count} ØŖØĩŲ„ (؅؄؁)", + "share_add_photos": "ØĨØļØ§ŲØŠ Ø§Ų„ØĩŲˆØą", + "share_assets_selected": "ا؎ØĒŲŠØ§Øą {count}", "share_dialog_preparing": "ØĒØ­ØļŲŠØą...", "share_link": "Ų…Ø´Ø§ØąŲƒØŠ ØąØ§Ø¨Øˇ", - "share_original": "Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ų…Ų„Ų Ø§Ų„ØŖØĩŲ„ŲŠ", - "share_preview": "Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ", - "share_quality_body": "اØļØēØˇ Ų…Øš Ø§Ų„Ø§ØŗØĒŲ…ØąØ§Øą ØšŲ„Ų‰ Ø˛Øą Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų„Ø§ØŽØĒŲŠØ§Øą ØŦŲˆØ¯ØŠ Ø§Ų„ØĩŲˆØąØŠ Ų‚Ø¨Ų„ Ų…Ø´Ø§ØąŲƒØĒŲ‡Ø§.", - "share_quality_title": "ا؎ØĒØą ØŦŲˆØ¯ØŠ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„ØĒ؊ ØĒŲ†Ø§ØŗØ¨Ųƒ", "shared": "Ų…ŲØ´ØĒŲŽØąŲƒ", "shared_album_activities_input_disable": "Ø§Ų„ØĒØšŲ„ŲŠŲ‚ Ų…ØšØˇŲ„", "shared_album_activity_remove_content": "Ų‡Ų„ ØĒØąŲŠØ¯ Ø­Ø°Ų Ų‡Ø°Ø§ Ø§Ų„Ų†Ø´Ø§ØˇØŸ", "shared_album_activity_remove_title": "Ø­Ø°Ų Ø§Ų„Ų†Ø´Ø§Øˇ", "shared_album_section_people_action_error": "ØŽØˇØŖ ØĒØąŲƒ/ØĨØ˛Ø§Ų„ØŠ Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "shared_album_section_people_action_leave": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", + "shared_album_section_people_action_remove_user": "ØĨØ˛Ø§Ų„ØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… Ų…Ų† Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "shared_album_section_people_title": "Ø§Ų„Ų†Ø§Øŗ", "shared_by": "ØĒŲ…ØĒ Ų…Ø´Ø§ØąŲƒØĒŲ‡ Ø¨ŲˆØ§ØŗØˇØŠ", "shared_by_user": "ØĒŲ…ØĒ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø¨ŲˆØ§ØŗØˇØŠ {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ØĒŲ… ØąŲØš", "shared_link_app_bar_title": "ØąŲˆØ§Ø¨Øˇ Ų…Ø´ØĒØąŲƒØŠ", "shared_link_clipboard_copied_massage": "Ų†ØŗØŽ ØĨŲ„Ų‰ Ø§Ų„Ø­Ø§ŲØ¸ØŠ", + "shared_link_clipboard_text": "ØąØ§Ø¨Øˇ: {link}\nŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą: {password}", "shared_link_create_error": "ØŽØˇØŖ ØŖØĢŲ†Ø§ØĄ ØĨŲ†Ø´Ø§ØĄ ØąØ§Ø¨Øˇ Ų…Ø´ØĒØąŲƒ", "shared_link_custom_url_description": "Ø§Ų„ŲˆØĩŲˆŲ„ ØĨŲ„Ų‰ Ų‡Ø°Ø§ Ø§Ų„ØąØ§Ø¨Øˇ Ø§Ų„Ų…Ø´ØĒØąŲƒ Ø¨Ø§ØŗØĒØŽØ¯Ø§Ų… ØšŲ†ŲˆØ§Ų† URL Ų…ØŽØĩØĩ", - "shared_link_custom_url_title": "ØšŲ†ŲˆØ§Ų† URL Ų…ØŽØĩØĩ", - "shared_link_custom_url_warning": "ØĒØ­Ø°ŲŠØą: Ų‚Ø¯ Ų„Ø§ ؊ØĒØĩØąŲ Ø§ØŗŲ… ØšŲ†ŲˆØ§Ų† URL Ø§Ų„Ų…ØŽØĩØĩ Ø§Ų„Ø°ŲŠ ŲŠØ­ØĒ؈؊ ØšŲ„Ų‰ Ø´ØąØˇØŠ Ų…Ø§ØĻŲ„ØŠ Ų„Ų„ØŖŲ…Ø§Ų… ŲƒŲ…Ø§ ØĒØĒŲˆŲ‚Øš.", "shared_link_edit_description_hint": "ØŖØ¯ØŽŲ„ ؈Øĩ؁ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ", "shared_link_edit_expire_after_option_day": "ŲŠŲˆŲ… 1", "shared_link_edit_expire_after_option_days": "{count} Ø§ŲŠØ§Ų…", @@ -1984,6 +2123,12 @@ "shared_with_partner": "ØĒŲ…ØĒ Ø§Ų„Ų…Ø´Ø§ØąŲƒØŠ Ų…Øš {partner}", "sharing": "Ų…Ø´Ø§ØąŲƒØŠ", "sharing_enter_password": "Ø§Ų„ØąØŦØ§ØĄ ØĨØ¯ØŽØ§Ų„ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą Ų„ØšØąØļ Ų‡Ø°Ų‡ Ø§Ų„ØĩŲØ­ØŠ.", + "sharing_page_album": "ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ų…Ø´ØĒØąŲƒØŠ", + "sharing_page_description": "Ų‚Ų… بØĨŲ†Ø´Ø§ØĄ ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ų…Ø´ØĒØąŲƒØŠ Ų„Ų…Ø´Ø§ØąŲƒØŠ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ų…Øš ØŖØ´ØŽØ§Øĩ ؁؊ Ø´Ø¨ŲƒØĒ؃.", + "sharing_page_empty_list": "Ų‚Ø§ØĻŲ…ØŠ ŲØ§ØąØēØŠ", + "sharing_sidebar_description": "Ø§ØšØąØļ ØąØ§Ø¨ØˇŲ‹Ø§ Ų„Ų„Ų…Ø´Ø§ØąŲƒØŠ ؁؊ Ø§Ų„Ø´ØąŲŠØˇ Ø§Ų„ØŦØ§Ų†Ø¨ŲŠ", + "sharing_silver_appbar_create_shared_album": "ØŖŲ„Ø¨ŲˆŲ… Ų…Ø´ØĒØąŲƒ ØŦØ¯ŲŠØ¯", + "sharing_silver_appbar_share_partner": "Ø´Ø§ØąŲƒ Ų…Øš Ø§Ų„Ø´ØąŲŠŲƒ", "shift_to_permanent_delete": "اØļØēØˇ ØšŲ„Ų‰ ⇧ Ų„Ø­Ø°Ų Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§", "show_album_options": "ØĨØ¸Ų‡Ø§Øą ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "show_albums": "ØĨØ¸Ų‡Ø§Øą Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ", @@ -1995,9 +2140,7 @@ "show_in_timeline": "ØĨØ¸Ų‡Ø§Øą ؁؊ Ø§Ų„Ų…ØŽØˇØˇ Ø§Ų„Ø˛Ų…Ų†ŲŠ", "show_in_timeline_setting_description": "ØĨØ¸Ų‡Ø§Øą Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ų…Ų† Ų‡Ø°Ø§ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… ؁؊ Ø§Ų„Ų…ØŽØˇØˇ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "show_keyboard_shortcuts": "ØĨØ¸Ų‡Ø§Øą ا؎ØĒØĩØ§ØąØ§ØĒ Ų„ŲˆØ­ØŠ Ø§Ų„Ų…ŲØ§ØĒŲŠØ­", - "show_less": "اØļŲ‡Øą Ø§Ų‚Ų„", "show_metadata": "ØĨØ¸Ų‡Ø§Øą Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ", - "show_more_fields": "{count, plural, one {اØļŲ‡Øą #Ø­Ų‚Ų„ Ø§ŲƒØĢØą} other {اØļŲ‡Øą # Ø­Ų‚ŲˆŲ„ Ø§ŲƒØĢØą}}", "show_or_hide_info": "ØĨØ¸Ų‡Ø§Øą ØŖŲˆ ØĨØŽŲØ§ØĄ Ø§Ų„Ų…ØšŲ„ŲˆŲ…Ø§ØĒ", "show_password": "ØĨØ¸Ų‡Ø§Øą ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", "show_person_options": "ØĨØ¸Ų‡Ø§Øą ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ø´ØŽØĩ", @@ -2005,7 +2148,6 @@ "show_schema": "ØŖØ¸Ų‡Øą Ø§Ų„Ų…ØŽØˇØˇ", "show_search_options": "ØĨØ¸Ų‡Ø§Øą ØŽŲŠØ§ØąØ§ØĒ Ø§Ų„Ø¨Ø­ØĢ", "show_shared_links": "ØšØąØļ Ø§Ų„ØąŲˆØ§Ø¨Øˇ Ø§Ų„Ų…Ø´ØĒØąŲƒØŠ", - "show_slideshow_metadata_overlay": "ØšØąØļ Ų…ØšŲ„ŲˆŲ…Ø§ØĒ Ø§Ų„ØĩŲˆØąØŠ", "show_slideshow_transition": "ØĨØ¸Ų‡Ø§Øą Ø§Ų†ØĒŲ‚Ø§Ų„ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", "show_supporter_badge": "Ø´Ø§ØąØŠ Ø§Ų„Ų…Ø¤ŲŠØ¯", "show_supporter_badge_description": "ØĨØ¸Ų‡Ø§Øą Ø´Ø§ØąØŠ Ø§Ų„Ų…Ø¤ŲŠØ¯", @@ -2017,25 +2159,18 @@ "sign_out": "ØŽØąŲˆØŦ", "sign_up": "Ø§Ų„ØĒØŗØŦŲŠŲ„", "size": "Ø§Ų„Ø­ØŦŲ…", - "skip": "ØĒØŽØˇŲŠ", "skip_to_content": "ØĒØŽØˇŲŠ ØĨŲ„Ų‰ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰", "skip_to_folders": "ØĒØŽØˇŲŠ ØĨŲ„Ų‰ Ø§Ų„Ų…ØŦŲ„Ø¯Ø§ØĒ", "skip_to_tags": "ØĒØŽØˇŲŠ ØĨŲ„Ų‰ Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ", "slideshow": "ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", - "slideshow_body": "Ø§ØŗØĒØąØŽŲ ŲˆØ´Ø§Ų‡Ø¯ ØĩŲˆØąŲƒ ØĒŲØšØąØļ ؁؊ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­ Ø¨Ų…Ų„ØĄ Ø§Ų„Ø´Ø§Ø´ØŠ.", - "slideshow_metadata_overlay_mode": "Ų…Ø­ØĒŲˆŲ‰ Ø§Ų„ØĒØąØ§ŲƒØ¨", - "slideshow_metadata_overlay_mode_description_only": "؈Øĩ؁ ŲŲ‚Øˇ", - "slideshow_metadata_overlay_mode_full": "ŲƒØ§Ų…Ų„", "slideshow_repeat": "اؚاد؊ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", "slideshow_repeat_description": "Ø§Ų„ØšŲˆØ¯ØŠ ØĨŲ„Ų‰ Ø§Ų„Ø¨Ø¯Ø§ŲŠØŠ ØšŲ†Ø¯ Ø§Ų†ØĒŲ‡Ø§ØĄ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", "slideshow_settings": "ØĨؚداداØĒ ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", - "slideshow_title": "ØšØąØļ Ø§Ų„Ø´ØąØ§ØĻØ­", - "smart_album": "ØŖŲ„Ø¨ŲˆŲ… Ø°ŲƒŲŠ", - "some_assets_already_have_a_location_warning": "بؚØļ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ ØĒØ­ØĒ؈؊ Ø¨Ø§Ų„ŲØšŲ„ ØšŲ„Ų‰ Ų…ŲˆŲ‚Øš ØŦØēØąØ§ŲŲŠ", "sort_albums_by": "ØąØĒب Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…Ø§ØĒ Ø­ØŗØ¨...", "sort_created": "ØĒØ§ØąŲŠØŽ Ø§Ų„ØĨŲ†Ø´Ø§ØĄ", "sort_items": "ؚدد Ø§Ų„ØšŲ†Ø§ØĩØą", "sort_modified": "ØĒŲ… ØĒØšØ¯ŲŠŲ„ Ø§Ų„ØĒØ§ØąŲŠØŽ", + "sort_newest": "احدØĢ ØĩŲˆØąØŠ", "sort_oldest": "ØŖŲ‚Ø¯Ų… ØĩŲˆØąØŠ", "sort_people_by_similarity": "ØąØĒب Ø§Ų„ØŖØ´ØŽØ§Øĩ Ø­ØŗØ¨ Ø§Ų„ØĒØ´Ø§Ø¨Ų‡", "sort_recent": "ØŖØ­Ø¯ØĢ ØĩŲˆØąØŠ", @@ -2044,6 +2179,7 @@ "stack": "ØĒØŦŲ…ŲŠØš", "stack_action_prompt": "{count} Ų…ŲƒØ¯ØŗØŠ", "stack_duplicates": "ØĒØŦŲ…ŲŠØš Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ŲƒØąØąØŠ", + "stack_select_one_photo": "حدد ØĩŲˆØąØŠ ØąØĻŲŠØŗŲŠØŠ ŲˆØ§Ø­Ø¯ØŠ Ų„Ų„Ų…ØŦŲ…ŲˆØšØŠ", "stack_selected_photos": "ŲƒØ¯Øŗ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ", "stacked_assets_count": "ØĒŲ… ØĒŲƒØ¯ŲŠØŗ {count, plural, one {# Ø§Ų„Ų…Ø­ØĒŲˆŲ‰} other {# Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ}}", "stacktrace": "ØĒØĒŲ‘Ø¨ŲØš Ø§Ų„ØĒŲƒØ¯ŲŠØŗ", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "؊ØŦب ØŖŲ† ŲŠŲƒŲˆŲ† ØĒØ§ØąŲŠØŽ Ø¨Ø¯ØĄ Ø§Ų„ŲØĒØąØŠ Ų‚Ø¨Ų„ ØĒØ§ØąŲŠØŽ Ų†Ų‡Ø§ŲŠØĒŲ‡Ø§", "state": "Ø§Ų„ŲˆŲ„Ø§ŲŠØŠ", "status": "Ø§Ų„Ø­Ø§Ų„ØŠ", - "step_delete": "Ø­Ø°Ų ØŽØˇŲˆØŠ", - "step_delete_confirm": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØ§ŲƒØ¯ Ų…Ų† Ø§Ų†Ųƒ ØĒØąŲŠØ¯ Ø­Ø°Ų Ų‡Ø°Ų‡ Ø§Ų„ØŽØˇŲˆØŠØŸ", - "step_details": "ØĒŲØ§ØĩŲŠŲ„ Ø§Ų„ØŽØˇŲˆØŠ", - "steps": "ØŽØˇŲˆØ§ØĒ", "stop_casting": "Ø§ŲŠŲ‚Ø§Ų Ø§Ų„Ø¨ØĢ", "stop_motion_photo": "ØĨŲŠŲ‚Ø§Ų Ø­ØąŲƒØŠ Ø§Ų„ØĩŲˆØąØŠ", "stop_photo_sharing": "ØĒŲˆŲ‚Ų ØšŲ† Ų…Ø´Ø§ØąŲƒØŠ ØĩŲˆØąŲƒØŸ", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "ØĒØ¨Ø¯ŲŠŲ„ اØĒØŦØ§Ų‡ Ø§Ų„Ø¯Ų…ØŦ", "sync": "Ų…Ø˛Ø§Ų…Ų†ØŠ", "sync_albums": "Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ", + "sync_albums_manual_subtitle": "Ų…Ø˛Ø§Ų…Ų†ØŠ ØŦŲ…ŲŠØš Ø§Ų„ŲØ¯ŲŠŲˆŲ‡Ø§ØĒ ŲˆØ§Ų„ØĩŲˆØą Ø§Ų„Ų…ØąŲŲˆØšØŠ Ø§Ų„Ų‰ Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„ØŽØ˛Ų† Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ø§Ų„Ų…ØŽØĒØ§ØąØŠ", "sync_local": "Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ų…Ø­Ų„ŲŠØŠ", "sync_remote": "Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„Ų…Ų„ŲØ§ØĒ Ø§Ų„Ø¨ØšŲŠØ¯ØŠ", "sync_status": "Ø­Ø§Ų„ØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ØĒØ˛Ø§Ų…Ų†", "sync_status_subtitle": "ØšØąØļ ؈ØĨØ¯Ø§ØąØŠ Ų†Ø¸Ø§Ų… Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ØĒØ˛Ø§Ų…Ų†", "sync_upload_album_setting_subtitle": "Ø§Ų†Ø´ØĻ ؈ Ø§ØąŲØš ØĩŲˆØąŲƒ ؈ ŲØ¯ŲŠŲˆŲ‡Ø§ØĒ؃ Ø§Ų„Ø§Ų„Ø¨ŲˆŲ…Ø§ØĒ Ø§Ų„Ų…ØŽØĒØ§ØąØŠ ؁؊ Immich", - "system_theme": "ØŗŲ…ØŠ Ø§Ų„Ų†Ø¸Ø§Ų…", - "system_theme_command_description": "Ø§ØŗØĒØšŲ…Ų„ ØŗŲ…ØŠ Ø§Ų„Ų†Ø¸Ø§Ų… ({value})", "tag": "Ø§Ų„ØšŲ„Ø§Ų…ØŠ", "tag_assets": "ØŖØĩŲˆŲ„ Ø§Ų„ØšŲ„Ø§Ų…ØŠ", "tag_created": "ØĒŲ… ØĨŲ†Ø´Ø§ØĄ Ø§Ų„ØšŲ„Ø§Ų…ØŠ: {tag}", "tag_face": "ØšŲ„Ų‘ŲŲ… Ø§Ų„ŲˆØŦŲ‡", "tag_feature_description": "ØĒØĩŲØ­ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…ØŦŲ…ØšØŠ Ø­ØŗØ¨ Ų…ŲˆØ§ØļŲŠØš Ø§Ų„ØšŲ„Ø§Ų…Ø§ØĒ Ø§Ų„Ų…Ų†ØˇŲ‚ŲŠØŠ", + "tag_not_found_question": "Ų„Ø§ ŲŠŲ…ŲƒŲ† Ø§Ų„ØšØĢŲˆØą ØšŲ„Ų‰ ØšŲ„Ø§Ų…ØŠØŸ Ų‚Ų… بØĨŲ†Ø´Ø§ØĄ ØšŲ„Ø§Ų…ØŠ ØŦØ¯ŲŠØ¯ØŠ.", "tag_people": "ØšŲ„ŲŲ‘Ų… Ø§Ų„ØŖØ´ØŽØ§Øĩ", "tag_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØšŲ„Ø§Ų…ØŠ: {tag}", "tagged_assets": "ØĒŲ… ؈ØļØš ØšŲ„Ø§Ų…ØŠ {count, plural, one {# asset} other {# assets}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ؚدد Ø§Ų„Ø§ØĩŲˆŲ„ ؁؊ ŲƒŲ„ Øĩ؁({count})", "theme_setting_colorful_interface_subtitle": "ØĒØˇØ¨ŲŠŲ‚ Ø§Ų„Ų„ŲˆŲ† Ø§Ų„ØŖØŗØ§ØŗŲŠ ØšŲ„Ų‰ Ø§Ų„ØŖØŗØˇØ­ ؁؊ Ø§Ų„ØŽŲ„ŲŲŠØŠ.", "theme_setting_colorful_interface_title": "ŲˆØ§ØŦŲ‡Ų‡ Ų…Ų„ŲˆŲ†ØŠ", + "theme_setting_image_viewer_quality_subtitle": "اØļØ¨Øˇ ØŦŲˆØ¯ØŠ ØšØ§ØąØļ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„ØĒ؁ØĩŲŠŲ„ŲŠØŠ", + "theme_setting_image_viewer_quality_title": "ØŦŲˆØ¯ØŠ ØšØ§ØąØļ Ø§Ų„ØĩŲˆØąØŠ", "theme_setting_primary_color_subtitle": "ا؎ØĒØą Ų„ŲˆŲ† Ų„Ų„ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„Ø§ØŗØ§ØŗŲŠØŠ ŲˆØ§Ų„Ų…ŲƒŲ…Ų„Ų‡.", "theme_setting_primary_color_title": "Ø§Ų„Ų„ŲˆŲ† Ø§Ų„Ø§ØŗØ§ØŗŲŠ", "theme_setting_system_primary_color_title": "Ø§ØŗØĒØŽØ¯Ų… Ų„ŲˆŲ† Ø§Ų„Ų†Ø¸Ø§Ų…", "theme_setting_system_theme_switch": "ØĒŲ„Ų‚Ø§ØĻ؊ (اØĒبؚ ØĨؚداد Ø§Ų„Ų†Ø¸Ø§Ų…)", + "theme_setting_theme_subtitle": "ا؎ØĒØą ØĨؚداداØĒ Ų…Ø¸Ų‡Øą Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", + "theme_setting_three_stage_loading_subtitle": "Ų‚Ø¯ ŲŠØ˛ŲŠØ¯ Ø§Ų„ØĒØ­Ų…ŲŠŲ„ Ų…Ų† ØĢŲ„Ø§ØĢ Ų…ØąØ§Ø­Ų„ Ų…Ų† ØŖØ¯Ø§ØĄ Ø§Ų„ØĒØ­Ų…ŲŠŲ„ ŲˆŲ„ŲƒŲ†Ų‡ ŲŠØŗØ¨Ø¨ ØĒØ­Ų…ŲŠŲ„ Ø´Ø¨ŲƒØŠ ØŖØšŲ„Ų‰ Ø¨ŲƒØĢŲŠØą", + "theme_setting_three_stage_loading_title": "ØĒŲ…ŲƒŲŠŲ† ØĒØ­Ų…ŲŠŲ„ ØĢŲ„Ø§ØĢ Ų…ØąØ§Ø­Ų„", "then": "ØĢŲ…", "they_will_be_merged_together": "ØŗŲŠØĒŲ… Ø¯Ų…ØŦŲ‡Ų… Ų…ØšŲ‹Ø§", "third_party_resources": "Ų…ŲˆØ§ØąØ¯ Ø§Ų„ØˇØąŲ Ø§Ų„ØĢØ§Ų„ØĢ", @@ -2131,17 +2268,23 @@ "trash_all": "Ų†Ų‚Ų„ Ø§Ų„ŲƒŲ„ ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", "trash_count": "ØŗŲ„ØŠ Ø§Ų„Ų…Ø­Ų…Ų„Ø§ØĒ {count, number}", "trash_delete_asset": "Ø­Ø°Ų/Ų†Ų‚Ų„ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ ØĨŲ„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", + "trash_emptied": "ØŗØ¨ØŠ Ų…Ų‡Ų…Ų„Ø§ Ų…ŲØąØēØŠ", "trash_no_results_message": "ØŗØĒØ¸Ų‡Øą Ų‡Ų†Ø§ Ø§Ų„ØĩŲˆØą ŲˆŲ…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ų…Ø­Ø°ŲˆŲØŠ.", "trash_page_delete_all": "Ø­Ø°Ų Ø§Ų„ŲƒŲ„", + "trash_page_empty_trash_dialog_content": "Ų‡Ų„ ØĒØąŲŠØ¯ ØĒŲØąŲŠØē ØŖØĩŲˆŲ„Ųƒ Ø§Ų„Ų…Ų‡Ų…Ų„ØŠØŸ ØŗØĒØĒŲ… ØĨØ˛Ø§Ų„ØŠ Ų‡Ø°Ų‡ Ø§Ų„ØšŲ†Ø§ØĩØą Ų†Ų‡Ø§ØĻŲŠŲ‹Ø§ Ų…Ų† Ø§Ų„ØĒØˇØ¨ŲŠŲ‚", "trash_page_info": "Ø§Ų„ØšŲ†Ø§ØĩØą Ø§Ų„Ų…Ų†Ų‚ŲˆŲ„ØŠ Ø§Ų„Ų‰ ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ ØŗŲŠØĒŲ… Ø­Ø°ŲŲ‡Ø§ Ø¨Ø´ŲƒŲ„ Ų†Ų‡Ø§ØĻ؊ بؚد {days} Ø§ŲŠØ§Ų…", + "trash_page_no_assets": "Ų„Ø§ ØĒ؈ØŦد اØĩŲˆŲ„ ؁؊ ØŗŲ„Ų‡ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ", + "trash_page_restore_all": "Ø§ØŗØĒؚاد؊ Ø§Ų„ŲƒŲ„", + "trash_page_select_assets_btn": "ا؎ØĒØą Ø§Ų„ØŖØĩŲˆŲ„", + "trash_page_title": "ØŗŲ„ØŠ Ø§Ų„Ų…Ų‡Ų…Ų„Ø§ØĒ ({count})", "trashed_items_will_be_permanently_deleted_after": "ØŗŲŠØĒŲ… Ø­Ø°ŲŲ Ø§Ų„ØšŲ†Ø§ØĩØą Ø§Ų„Ų…Ø­Ø°ŲˆŲØŠ Ų†ŲŲ‡Ø§ØĻŲŠŲ‹Ø§ بؚد {days, plural, one {# ŲŠŲˆŲ…} other {# ØŖŲŠØ§Ų… }}.", "trigger": "Ų…ŲØšŲŲ„", - "trigger_asset_metadata_extraction": "Ø§ØŗØĒØŽØąØ§ØŦ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ Ų„Ų„Ų…Ų„ŲØ§ØĒ", - "trigger_asset_metadata_extraction_description": "؊ØĒŲ… ØĒŲØšŲŠŲ„Ų‡ ØšŲ†Ø¯ Ø§ØŗØĒØŽØąØ§ØŦ Ø§Ų„Ø¨ŲŠØ§Ų†Ø§ØĒ Ø§Ų„ŲˆØĩŲŲŠØŠ (EXIF) ؄؄؅؄؁", - "trigger_asset_uploaded": "ØąŲØš Ø§Ų„Ø§ØĩŲ„", + "trigger_asset_uploaded": "ØĒŲ… ØąŲØš Ø§Ų„Ø§ØĩŲ„", "trigger_asset_uploaded_description": "؊ØĒŲ… ØĒŲØšŲŠŲ„Ų‡ ØšŲ†Ø¯ ØĒØ­Ų…ŲŠŲ„ ØŖØĩŲ„ ØŦØ¯ŲŠØ¯", + "trigger_description": "حدØĢ ŲŠØ¨Ø¯ØŖ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "trigger_person_recognized": "ØĒŲ… Ø§Ų„ØĒØšØąŲ ØšŲ„Ų‰ Ø´ØŽØĩ", - "trigger_person_recognized_description": "؊ØĒŲ… ØĒŲØšŲŠŲ„Ų‡ ØšŲ†Ø¯ Ø§Ų„ØĒØšØąŲ ØšŲ„Ų‰ Ø´ØŽØĩ", + "trigger_person_recognized_description": "؊ØĒŲ… ØĒŲØšŲŠŲ„Ų‡ ØšŲ†Ø¯ Ø§ŲƒØĒØ´Ø§Ų Ø´ØŽØĩ", + "trigger_type": "Ų†ŲˆØš Ø§Ų„Ų…ŲØšŲ„", "troubleshoot": "Ø§ØŗØĒŲƒØ´Ø§Ų Ø§Ų„Ų…Ø´Ø§ŲƒŲ„", "type": "Ø§Ų„Ų†ŲˆØš", "unable_to_change_pin_code": "ØĒŲŲŠŲŠØą ØąŲ…Ø˛ PIN ØēŲŠØą Ų…Ų…ŲƒŲ†", @@ -2157,7 +2300,6 @@ "unknown": "ØēŲŠØą Ų…ØšØąŲˆŲ", "unknown_country": "Ø¨Ų„Ø¯ ØēŲŠØą Ų…ØšØąŲˆŲ", "unknown_date": "ØĒØ§ØąŲŠØŽ ØēŲŠØą Ų…ØšØąŲˆŲ", - "unknown_schema": "Ų…ØŽØˇØˇ ØēŲŠØą Ų…ØšØąŲˆŲ", "unknown_year": "ØŗŲ†ØŠ ØēŲŠØą Ų…ØšØąŲˆŲØŠ", "unlimited": "ØēŲŠØą Ų…Ø­Ø¯ŲˆØ¯", "unlink_motion_video": "ØĨŲ„ØēØ§ØĄ ØąØ¨Øˇ ŲŲŠØ¯ŲŠŲˆ Ø§Ų„Ø­ØąŲƒØŠ", @@ -2170,20 +2312,23 @@ "unsaved_change": "ØĒØēŲŠŲŠØą ØēŲŠØą Ų…Ø­ŲŲˆØ¸", "unselect_all": "ØĨŲ„ØēØ§ØĄ ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„", "unselect_all_duplicates": "ØĨŲ„ØēØ§ØĄ ØĒØ­Ø¯ŲŠØ¯ ŲƒØ§ŲØŠ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ų…ŲƒØąØąØŠ", + "unselect_all_in": "ØĨŲ„ØēØ§ØĄ ØĒØ­Ø¯ŲŠØ¯ Ø§Ų„ŲƒŲ„ ؁؊ {group}", "unstack": "؁؃ Ø§Ų„ŲƒŲˆŲ…Ų‡", "unstack_action_prompt": "ØĒŲ… Ø§Ø˛Ø§Ų„ØŠ ØĒŲƒØ¯ŲŠØŗ {count}", "unstacked_assets_count": "ØĒŲ… ØĨØŽØąØ§ØŦ {count, plural, one {# Ø§Ų„ØŖØĩŲ„} other {# Ø§Ų„ØŖØĩŲˆŲ„}} Ų…Ų† Ø§Ų„ØĒŲƒØ¯ŲŠØŗ", "unsupported_field_type": "Ų†ŲˆØš Ø­Ų‚Ų„ ØēŲŠØą Ų…Ø¯ØšŲˆŲ…", "unsupported_file_type": "Ų„Ø§ ŲŠŲ…ŲƒŲ† ØąŲØš Ø§Ų„Ų…Ų„Ų {file} Ų„ØŖŲ† Ų†ŲˆØš Ø§Ų„Ų…Ų„Ų {type} ØēŲŠØą Ų…Ø¯ØšŲˆŲ….", "untagged": "ØēŲŠØą Ų…ŲØšŲŽŲ„ŲŽŲ‘Ų…", + "untitled_workflow": "ØŽØˇØŠ ØŗŲŠØą ØšŲ…Ų„ Ø¨Ø¯ŲˆŲ† ØšŲ†ŲˆØ§Ų†", "up_next": "Ø§Ų„ØĒØ§Ų„ŲŠ", "update_location_action_prompt": "ØĒØ­Ø¯ŲŠØĢ Ų…ŲˆŲ‚Øš {count} ØšŲ†Ø§ØĩØą Ų…Ø­Ø¯Ø¯ØŠ ØšŲ„Ų‰ Ø§Ų„Ų†Ø­Ųˆ Ø§Ų„ØĒØ§Ų„ŲŠ:", "updated_at": "ØĒŲ… Ø§Ų„ØĒØ­Ø¯ŲŠØĢ", "updated_password": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ ŲƒŲ„Ų…ØŠ Ø§Ų„Ų…ØąŲˆØą", "upload": "ØąŲØš", "upload_concurrency": "Ø§Ų„ØąŲØš Ø§Ų„Ų…ØĒØ˛Ø§Ų…Ų†", - "upload_day_count": "{date}: {count, plural, one {# ØąŲØš Ø§Ų„Ų…Ų„Ų} other {# ØąŲØš Ø§Ų„Ų…Ų„ŲØ§ØĒ}}", "upload_details": "ØĒŲØ§ØĩŲŠŲ„ Ø§Ų„ØąŲØš", + "upload_dialog_info": "Ų‡Ų„ ØĒØąŲŠØ¯ Ø§Ų„Ų†ØŗØŽ Ø§Ų„Ø§Ø­ØĒŲŠØ§ØˇŲŠ Ų„Ų„ØŖØĩŲˆŲ„ (Ø§Ų„ØŖØĩŲˆŲ„) Ø§Ų„Ų…Ø­Ø¯Ø¯ØŠ ØĨŲ„Ų‰ Ø§Ų„ØŽØ§Ø¯Ų…ØŸ", + "upload_dialog_title": "ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ØŖØĩŲˆŲ„", "upload_error_with_count": "ØŽØˇØŖ ؁؊ ØąŲØš {count, plural, one {# اØĩŲ„} other {# اØĩŲˆŲ„}}", "upload_errors": "ØĨ؃ØĒŲ…Ų„ Ø§Ų„ØąŲØš Ų…Øš {count, plural, one {# ØŽØˇØŖ} other {# ØŖØŽØˇØ§ØĄ}}, Ų‚Ų… بØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĩŲØ­ØŠ Ų„ØąØ¤ŲŠØŠ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ Ø§Ų„ØĒ؊ ØĒŲ… ØąŲØšŲ‡Ø§.", "upload_finished": "ØĒŲ… Ø§Ų„Ø§Ų†ØĒŲ‡Ø§ØĄ Ų…Ų† Ø§Ų„ØąŲØš", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Ø§Ų„ØŖØŽØˇØ§ØĄ", "upload_status_uploaded": "ØĒŲ… Ø§Ų„ØąŲØš", "upload_success": "ØĒŲ… Ø§Ų„ØąŲØš Ø¨Ų†ØŦاح، Ų‚Ų… بØĒØ­Ø¯ŲŠØĢ Ø§Ų„ØĩŲØ­ØŠ Ų„ØąØ¤ŲŠØŠ Ø§Ų„Ų…Ø­ØĒŲˆŲŠØ§ØĒ Ø§Ų„Ų…ØąŲŲˆØšØŠ Ø§Ų„ØŦØ¯ŲŠØ¯ØŠ.", - "upload_to_album_body": "Ø¨Ø§Ų„Ų†ØŗØ¨ØŠ Ų„Ų„Ų…ØŗØĒØŽØ¯Ų…ŲŠŲ† Ø§Ų„Ø°ŲŠŲ† Ų„Ø§ ŲŠØŗØĒØŽØ¯Ų…ŲˆŲ† Ų…ŲŠØ˛ØŠ Ø§Ų„ØĒØ­Ų…ŲŠŲ„ Ø§Ų„ŲŠØ¯ŲˆŲŠØŒ ŲŠŲ…ŲƒŲ†Ųƒ Ø§Ų„ØĸŲ† ا؎ØĒŲŠØ§Øą ØĨØļØ§ŲØŠ Ø§Ų„ØĩŲˆØą Ø§Ų„Ų…Ø­Ų„ŲŠØŠ Ų…Ø¨Ø§Ø´ØąØŠ ØĨŲ„Ų‰ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ… ØŖØĢŲ†Ø§ØĄ ØĒØ­Ų…ŲŠŲ„Ų‡Ø§ØŒ ŲˆŲ„Ø§ Ø¯Ø§ØšŲŠ Ų„Ų„ØĒØ­Ų…ŲŠŲ„ ØĢŲ… Ø§Ų„ØĨØļØ§ŲØŠ ØĨŲ„Ų‰ ØŖŲ„Ø¨ŲˆŲ… Ų„Ø§Ø­Ų‚Ų‹Ø§ بؚد Ø§Ų„ØĸŲ†.", - "upload_to_album_title": "Ų‚Ų… Ø¨Ø§Ų„ØĒØ­Ų…ŲŠŲ„ Ų…Ø¨Ø§Ø´ØąØŠ ØĨŲ„Ų‰ ØŖŲ„Ø¨ŲˆŲ…", "upload_to_immich": "Ø§Ų„ØąŲØš Ø§Ų„Ų‰Immich ‎ ‏ ({count})", "uploading": "ØŦØ§ØąŲŠ Ø§Ų„ØąŲØš", "uploading_media": "ØąŲØš Ø§Ų„ŲˆØŗØ§ØĻØˇ", - "uploads": "ØšŲ…Ų„ŲŠØ§ØĒ Ø§Ų„ØąŲØš", - "uploads_count": "{count, plural, one {# ØąŲØš Ø§Ų„Ų…Ų„Ų} other {# ØąŲØš Ø§Ų„Ų…Ų„ŲØ§ØĒ}}", "url": "ØšŲ†ŲˆØ§Ų† URL", "usage": "Ø§Ų„Ø§ØŗØĒØŽØ¯Ø§Ų…", "use_biometric": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø¨Ø§ŲŠŲˆŲ…ØĒØąŲŠ", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "ØĒŲ†ØŗŲŠŲ‚ Ø§Ų„ØĒŲˆØ§ØąŲŠØŽ ŲˆØ§Ų„ØŖŲˆŲ‚Ø§ØĒ ŲˆØ§Ų„ØŖØąŲ‚Ø§Ų… ŲˆŲŲ‚Ų‹Ø§ Ų„ØĨؚداداØĒ Ø§Ų„Ų„ØēØŠ ؁؊ Ų…ØĒØĩŲØ­Ųƒ", "use_current_connection": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø§Ų„Ø­Ø§Ų„ŲŠ", "use_custom_date_range": "Ø§ØŗØĒØŽØ¯Ų… Ø§Ų„Ų†ØˇØ§Ų‚ Ø§Ų„Ø˛Ų…Ų†ŲŠ Ø§Ų„Ų…ØŽØĩØĩ Ø¨Ø¯Ų„Ø§Ų‹ Ų…Ų† Ø°Ų„Ųƒ", - "use_template": "Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ų‚Ø§Ų„Ø¨", "user": "Ų…ØŗØĒØŽØ¯Ų…", "user_has_been_deleted": "Ų‡Ø°Ø§ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų… ØĒŲ… Ø­Ø°ŲŲ‡.", "user_id": "Ų…ØšØąŲ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", @@ -2217,6 +2357,7 @@ "user_privacy": "ØŽØĩ؈ØĩŲŠØŠ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "user_purchase_settings": "Ø§Ų„Ø´ØąØ§ØĄ", "user_purchase_settings_description": "ØĨØ¯Ø§ØąØŠ ØšŲ…Ų„ŲŠØŠ Ø§Ų„Ø´ØąØ§ØĄ Ø§Ų„ØŽØ§ØĩØŠ Ø¨Ųƒ", + "user_role_set": "Ų‚Ų… بØĒØšŲŠŲŠŲ† {user} ŲƒŲ€ {role}", "user_usage_detail": "ØĒŲØ§ØĩŲŠŲ„ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "user_usage_stats": "ØĨØ­ØĩاØĻŲŠØ§ØĒ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ø­ØŗØ§Ø¨", "user_usage_stats_description": "ØšØąØļ ØĨØ­ØĩاØĻŲŠØ§ØĒ Ø§ØŗØĒØŽØ¯Ø§Ų… Ø§Ų„Ø­ØŗØ§Ø¨", @@ -2226,6 +2367,7 @@ "utilities": "ØŖØ¯ŲˆØ§ØĒ", "validate": "ØĒØ­Ų‚Ų’Ų‚", "validate_endpoint_error": "Ø§Ų„ØąØŦØ§ØĄ Ø§Ø¯ØŽØ§Ų„ ØšŲ†ŲˆØ§Ų† URL ØĩØ§Ų„Ø­", + "validation_error": "ØŽØˇØŖ ؁؊ Ø§Ų„ØĒØ­Ų‚Ų‚", "variables": "Ø§Ų„Ų…ØĒØēŲŠØąØ§ØĒ", "version": "Ø§Ų„ØĨØĩØ¯Ø§Øą", "version_announcement_closing": "ØĩØ¯ŲŠŲ‚ŲƒØŒ ØŖŲ„ŲŠŲƒØŗ", @@ -2235,9 +2377,9 @@ "video": "ŲŲŠØ¯ŲŠŲˆ", "video_hover_setting": "ØĒØ´ØēŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ų„Ų„ŲŲŠØ¯ŲŠŲˆ ØšŲ†Ø¯ Ø§Ų„ØĒŲ…ØąŲŠØą", "video_hover_setting_description": "ØĒØ´ØēŲŠŲ„ Ø§Ų„ØĩŲˆØąØŠ Ø§Ų„Ų…ØĩØēØąØŠ Ų„Ų„ŲŲŠØ¯ŲŠŲˆ ØšŲ†Ø¯ ØĒØ­ØąŲŠŲƒ Ø§Ų„Ų…Ø§ŲˆØŗ ŲŲˆŲ‚ Ø§Ų„ØšŲ†ØĩØą. Ø­ØĒŲ‰ ØšŲ†Ø¯ Ø§Ų„ØĒØšØˇŲŠŲ„ØŒ ŲŠŲ…ŲƒŲ† Ø¨Ø¯ØĄ Ø§Ų„ØĒØ´ØēŲŠŲ„ ØšŲ† ØˇØąŲŠŲ‚ Ø§Ų„ØĒŲ…ØąŲŠØą ŲŲˆŲ‚ ØąŲ…Ø˛ Ø§Ų„ØĒØ´ØēŲŠŲ„.", - "video_quality": "ØŦŲˆØ¯ØŠ Ø§Ų„ŲŲŠØ¯ŲŠŲˆ", "videos": "ŲŲŠØ¯ŲŠŲˆŲ‡Ø§ØĒ", "videos_count": "{count, plural, one {# Ų…Ų‚ØˇØš ŲŲŠØ¯ŲŠŲˆ } other {# Ų…Ų‚Ø§ØˇØš Ø§Ų„ŲŲŠØ¯ŲŠŲˆ }}", + "videos_only": "Ø§Ų„ŲØ¯ŲŠŲˆØ§ØĒ ŲŲ‚Øˇ", "view": "ØšØąØļ", "view_album": "ØšØąØļ Ø§Ų„ØŖŲ„Ø¨ŲˆŲ…", "view_all": "ØšØąØļ Ø§Ų„ŲƒŲ„", @@ -2246,49 +2388,49 @@ "view_details": "ØąØ¤ŲŠØŠ Ø§Ų„ØĒŲØ§ØĩŲŠŲ„", "view_in_timeline": "ØšØąØļ ؁؊ Ø§Ų„ØŦØ¯ŲˆŲ„ Ø§Ų„Ø˛Ų…Ų†ŲŠ", "view_link": "ØšØąØļ Ø§Ų„ØąØ§Ø¨Øˇ", + "view_links": "ØšØąØļ Ø§Ų„ØąŲˆØ§Ø¨Øˇ", "view_name": "ØšØąØļ", "view_next_asset": "ØšØąØļ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ø§Ų„ØĒØ§Ų„ŲŠ", "view_previous_asset": "ØšØąØļ Ø§Ų„Ų…Ø­ØĒŲˆŲ‰ Ø§Ų„ØŗØ§Ø¨Ų‚", - "view_qr_code": "ØšØąØļ ØąŲ…Ø˛ Ø§Ų„Ø§ØŗØĒØŦاب؊ Ø§Ų„ØŗØąŲŠØšØŠ", + "view_qr_code": "Â­ØšØąØļ ØąŲ…Ø˛ Ø§Ų„Ø§ØŗØĒØŦاب؊ Ø§Ų„ØŗØąŲŠØšØŠ", "view_similar_photos": "ØšØąØļ ØĩŲˆØą Ų…Ø´Ø§Ø¨Ų‡ØŠ", "view_stack": "ØšØąØļ Ø§Ų„ØĒŲƒØ¯ŲŠØŗ", + "view_user": "ØšØąØļ Ø§Ų„Ų…ØŗØĒØŽØ¯Ų…", "viewer_remove_from_stack": "Ø­Ø°Ų Ų…Ų† Ø§Ų„ŲƒŲˆŲ…Ų‡ ØŖŲˆ Ø§Ų„Ų…ØŦŲ…ŲˆØšØŠ", + "viewer_stack_use_as_main_asset": "Ø§ØŗØĒØŽØ¯Ų… ŲƒØŖØĩŲ„ ØąØĻŲŠØŗŲŠ", + "viewer_unstack": "؁؃ Ø§Ų„ŲƒŲˆŲ…Ų‡", "visibility": "ØĨŲ…ŲƒØ§Ų†ŲŠØŠ Ø§Ų„ØąØ¤ŲŠØŠ", "visibility_changed": "Ø§Ų„ØąØ¤ŲŠØŠ ØĒØēŲŠØąØĒ Ų„Ų€ {count, plural, one {Ø´ØŽØĩ ŲˆØ§Ø­Ø¯} other {# ؚد؊ ØŖØ´ØŽØ§Øĩ}}", "visual": "Ų…ØąØĻ؊", + "visual_builder": "ادا؊ Ų†Ø´Ø§ØĄ Ų…ØąØĻŲŠØŠ", "waiting": "؁؊ Ø§Ų„Ø§Ų†ØĒØ¸Ø§Øą", "waiting_count": "Ø§Ų„Ø§Ų†ØĒØ¸Ø§Øą: {count}", "warning": "ØĒØ­Ø°ŲŠØą", "week": "ØŖØŗØ¨ŲˆØš", "welcome": "Ų…ØąØ­Ø¨Ø§Ų‹", "welcome_to_immich": "Ų…ØąØ­Ø¨Ø§Ų‹ Ø¨Ųƒ ؁؊ Immich", - "whats_new": "Ų…Ø§ Ø§Ų„ØŦØ¯ŲŠØ¯", - "whats_new_settings_subtitle": "ØĒØšØąŲ ØšŲ„Ų‰ ŲƒŲ„ Ų…Ø§ Ų‡Ųˆ ØŦØ¯ŲŠØ¯ ؁؊ Immich", - "whats_new_version": "Ø§Ų„ØĨØĩØ¯Ø§Øą {version}", - "when": "ØšŲ†Ø¯Ų…Ø§", "width": "ØšŲØąØļ", "wifi_name": "Ø§ØŗŲ… Ø´Ø¨ŲƒØŠ Wi-Fi", - "workflow": "ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", - "workflow_delete_prompt": "Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ Ų‡Ø°Ø§ØŸ", + "workflow_delete_prompt": "Ų‡Ų„ ØŖŲ†ØĒ Ų…ØĒØŖŲƒØ¯ Ų…Ų† Ø­Ø°Ų ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ Ų‡Ø°Ø§ØŸ", "workflow_deleted": "ØĒŲ… Ø­Ø°Ų ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflow_description": "؈Øĩ؁ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflow_info": "Ų…ØšŲ„ŲˆŲ…Ø§ØĒ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflow_json": "؅؄؁ JSON Ų„ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflow_json_help": "Ų‚Ų… بØĒØšØ¯ŲŠŲ„ ØĨؚداداØĒ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ بØĩ؊ØēØŠ JSON. ØŗØĒØĒŲ… Ų…Ø˛Ø§Ų…Ų†ØŠ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ Ų…Øš ØŖØ¯Ø§ØŠ Ø§Ų„ØĨŲ†Ø´Ø§ØĄ Ø§Ų„Ų…ØąØĻŲŠØŠ.", "workflow_name": "Ø§ØŗŲ… ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", - "workflow_navigation_prompt": "Ų…ØĒØ§ŲƒØ¯ Ų…Ų† Ø§Ų„Ų…ØēØ§Ø¯ØąØŠ Ø¨Ø¯ŲˆŲ† Ø­ŲØ¸ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ؟", + "workflow_navigation_prompt": "Ų‡Ų„ Ø§Ų†ØĒ Ų…ØĒØ§ŲƒØ¯ Ų…Ų† Ø§Ų„Ų…ØēØ§Ø¯ØąØŠ Ø¨Ø¯ŲˆŲ† Ø­ŲØ¸ Ø§Ų„ØĒØēŲŠŲŠØąØ§ØĒ؟", "workflow_summary": "Ų…Ų„ØŽØĩ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", - "workflow_templates": "Ų‚ŲˆØ§Ų„Ø¨ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflow_update_success": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ Ø¨Ų†ØŦاح", - "workflows": "ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ŲŠØ§ØĒ", + "workflow_updated": "ØĒŲ… ØĒØ­Ø¯ŲŠØĢ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", + "workflows": "ØŗŲŠØą Ø§Ų„ØšŲ…Ų„", "workflows_help_text": "ØĒØšŲ…Ų„ ØŗŲŠØą Ø§Ų„ØšŲ…Ų„ ØšŲ„Ų‰ ØŖØĒŲ…ØĒØŠ Ø§Ų„ØĨØŦØąØ§ØĄØ§ØĒ ØšŲ„Ų‰ ØŖØĩŲˆŲ„Ųƒ Ø¨Ų†Ø§ØĄŲ‹ ØšŲ„Ų‰ Ø§Ų„Ų…ŲØšŲ„Ø§ØĒ ŲˆØ§Ų„ŲŲ„Ø§ØĒØą", "wrong_pin_code": "ØąŲ…Ø˛ Ø§Ų„ØĒØšØąŲŠŲ Ø§Ų„Ø´ØŽØĩ؊ ØŽØ§ØˇØĻ", - "x_of_total": "{x}\\{total}", "year": "ØŗŲ†ØŠ", "years_ago": "{years, plural, one {# ØŗŲ†ØŠ} other {# ØŗŲ†ŲˆØ§ØĒ}} Ų…ØļØĒ", "yes": "Ų†ØšŲ…", "you_dont_have_any_shared_links": "Ų„ŲŠØŗ Ų„Ø¯ŲŠŲƒ ØŖŲŠ ØąŲˆØ§Ø¨Øˇ Ų…Ø´ØĒØąŲƒØŠ", "your_wifi_name": "Ø§ØŗŲ… Ø´Ø¨ŲƒØŠ Ø§Ų„Ø§ØĒØĩØ§Ų„ Ø§Ų„Ų„Ø§ØŗŲ„ŲƒŲŠ Ø§Ų„ØŽØ§Øĩ Ø¨Ųƒ", "zero_to_clear_rating": "اØļØēØˇ 0 Ų„Ų…ØŗØ­ ØĒØĩŲ†ŲŠŲ Ø§Ų„Ø§ØĩŲˆŲ„", - "zoom_image": "ØĒŲƒØ¨ŲŠØą Ø§Ų„ØĩŲˆØąØŠ" + "zoom_image": "ØĒŲƒØ¨ŲŠØą Ø§Ų„ØĩŲˆØąØŠ", + "zoom_to_bounds": "ØĒŲƒØ¨ŲŠØą Ø­ØĒŲ‰ Ø­Ø¯ŲˆØ¯ Ø§Ų„Ų…Ų†ØˇŲ‚ØŠ" } diff --git a/i18n/az.json b/i18n/az.json index 0cd83e01a8..310fe831dd 100644 --- a/i18n/az.json +++ b/i18n/az.json @@ -5,34 +5,35 @@ "acknowledge": "Təsdiq et", "action": "Əməliyyat", "action_common_update": "Yenilə", - "action_description": "Filtrlənmiş aktivliklər Ãŧzərində yerinə yetiriləcək əməliyyatlar toplusu", "actions": "Əməliyyatlar", "active": "Aktiv", "active_count": "Aktiv: {count}", "activity": "Fəaliyyət", + "activity_changed": "Fəaliyyət {enabled, select, true {aktivdir} other {aktiv deyil}}", "add": "Əlavə et", "add_a_description": "Təsviri əlavə et", "add_a_location": "Məkan əlavə et", "add_a_name": "Ad əlavə et", "add_a_title": "BaşlÄąq əlavə et", - "add_action": "Yeni əməliyyat əlavə et", - "add_assets": "Aktivlik əlavə et", "add_birthday": "Doğum gÃŧnÃŧ əlavə et", "add_endpoint": "Son nÃļqtə əlavə et", "add_exclusion_pattern": "Ã‡Äąxarma nÃŧmunəsi əlavə et", "add_location": "Məkan əlavə et", + "add_more_users": "Daha çox istifadəçi əlavə et", "add_partner": "Partnyor əlavə et", + "add_path": "Yol əlavə et", "add_photos": "Şəkillər əlavə et", - "add_step": "AddÄąm əlavə et", "add_tag": "Etiket əlavə et", "add_to": "Bura əlavə etâ€Ļ", "add_to_album": "Alboma əlavə et", "add_to_album_bottom_sheet_added": "{album} albomuna əlavə edildi", "add_to_album_bottom_sheet_already_exists": "ArtÄąq {album} albomunda var", "add_to_album_bottom_sheet_some_local_assets": "Bəzi lokal resurslar alboma əlavə edilə bilmədi", + "add_to_album_toggle": "{album} ÃŧçÃŧn seçimi dəyişin", "add_to_albums": "Albomlara əlavə et", "add_to_albums_count": "({count}) albomlarÄąna əlavə et", "add_to_bottom_bar": "Əlavə et", + "add_to_shared_album": "PaylaÅŸÄąlan alboma əlavə et", "add_upload_to_stack": "Yeni yÃŧklənmə əlavə et", "add_url": "URL əlavə et", "added_to_archive": "Arxivə əlavə edildi", @@ -45,7 +46,7 @@ "authentication_settings": "Səlahiyyətləndirmə parametrləri", "authentication_settings_description": "Şifrə, OAuth və digər səlahiyyətləndirmə parametrləri", "authentication_settings_disable_all": "BÃŧtÃŧn giriş etmə metodlarÄąnÄą sÃļndÃŧrmək istədiyinizdən əminsinizmi? Giriş etmə funksiyasÄą tamamilə sÃļndÃŧrÃŧləcəkdir.", - "authentication_settings_reenable": "Yenidən aktiv etmək ÃŧçÃŧn Server Əmri istifadə edin.", + "authentication_settings_reenable": "Yenidən aktiv etmək ÃŧçÃŧn Server Əmri -ni istifadə edin.", "background_task_job": "Arxa plan tapÅŸÄąrÄąqlarÄą", "backup_database": "Verilənlər bazasÄąnÄąn dump-ÄąnÄą yaradÄąn", "backup_database_enable_description": "Verilənlər bazasÄąnÄąn artÄąq nÃŧsxələrini aktiv et", @@ -53,76 +54,28 @@ "backup_onboarding_1_description": "buludda və ya başqa fiziki yerdə saytdan kənar surət.", "backup_onboarding_2_description": "mÃŧxtəlif cihazlarda yerli nÃŧsxələr. Bura əsas fayllar və həmin fayllarÄąn ehtiyat lokal nÃŧsxəsi daxildir.", "backup_onboarding_3_description": "orijinal fayllar da daxil olmaqla məlumatlarÄąnÄązÄąn Ãŧmumi surətləri. Buraya 1 kənar nÃŧsxə və 2 lokal nÃŧsxə daxildir.", - "backup_onboarding_description": "3-2-1 yedəkləmə strategiyasÄą məlumatlarÄąnÄązÄą qorumaq ÃŧçÃŧn tÃļvsiyə olunur. YÃŧklədiyiniz şəkil və videolarÄąn, həmçinin Immich verilənlər bazasÄąnÄąn surətlərini saxlamalÄąsÄąnÄąz ki, hərtərəfli yedəkləmə həlli əldə edəsiniz.", "backup_onboarding_footer": "Immich-in ehtiyat nÃŧsxəsini Ã§Äąxarmaq haqqÄąnda ətraflÄą məlumat ÃŧçÃŧn sənədlərə mÃŧraciət edin.", "backup_onboarding_parts_title": "3-2-1 ehtiyat nÃŧsxəsinə aşağıdakÄąlar daxildir:", "backup_onboarding_title": "Ehtiyat surətlər", "backup_settings": "BazanÄąn Dump Parametrləri", - "backup_settings_description": "Verilənlər bazasÄąnÄąn ehtiyat nÃŧsxə parametrləri.", + "backup_settings_description": "Verilənlər bazasÄąnÄąn ehtiyat nÃŧsxə parametrlərini idarə et", "cleared_jobs": "{job} ÃŧçÃŧn tapÅŸÄąrÄąqlar silindi", "config_set_by_file": "Konfiqurasiya hal-hazÄąrda konfiqurasiya faylÄą ilə təyin olunub", - "confirm_delete_library": "{library} kitabxanasÄąnÄą silmək istədiyinizə əminmisiniz?", - "confirm_delete_library_assets": "Bu kitabxananÄą silmək istədiyinizə əminsiniz? {count, plural, one {# contained asset} other {all # contained assets}} Immich-dən silinəcək və geri qaytarmaq mÃŧmkÃŧn olmayacaq. Fayllar diskdə qalacaq.", + "confirm_delete_library": "{library} kitabxanasÄąnÄą silmək istədiyinizdən əminmisiniz?", "confirm_email_below": "Təsdiqləmək ÃŧçÃŧn aşağıya {email} yazÄąn", - "confirm_reprocess_all_faces": "BÃŧtÃŧn Ãŧzləri yenidən emal etmək istədiyinizə əminsiniz? Bu, həmçinin adlandÄąrÄąlmÄąÅŸ şəxsləri siləcək.", "confirm_user_password_reset": "{user} adlÄą istifadəçinin şifrəsini sÄąfÄąrlamaq istədiyinizdən əminmisiniz?", - "confirm_user_pin_code_reset": "{user} istifadəçisinin PIN kodunu sÄąfÄąrlamaq istədiyinizə əminsiniz?", - "copy_config_to_clipboard_description": "Cari sistem konfiqurasiyasÄąnÄą JSON obyekt kimi mÃŧbadilə buferinə kopyalayÄąn", - "create_job": "İş yarat", - "cron_expression": "Cron ifadəsi", - "cron_expression_description": "Cron formatÄąndan istifadə edərək skan intervalÄąnÄą təyin edin. ƏtraflÄą məlumat ÃŧçÃŧn nÃŧmunələrə baxa bilərsiniz. Crontab Guru", - "cron_expression_presets": "Cron ifadəsi Ãļn ayarlarÄą", "disable_login": "Giriş etməni sÃļndÃŧr", - "download_csv": "CSV endir", "duplicate_detection_job_description": "Bənzər şəkilləri tapmaq ÃŧçÃŧn maÅŸÄąn Ãļyrənməsini işə salÄąn. Bu prosses Smart Search funksiyasÄąna əsaslanÄąr", - "exclusion_pattern_description": "İstisna nÃŧmunələri kitabxananÄązÄą skan edərkən fayllarÄą və qovluqlarÄą nəzərə almamağa imkan verir. Bu, RAW fayllarÄą kimi idxal etmək istəmədiyiniz fayllarÄą olan qovluqlarÄąnÄąz olduqda faydalÄądÄąr.", - "export_config_as_json_description": "Cari sistem konfiqurasiyasÄąnÄą JSON faylÄą kimi endirin", - "external_libraries_page_description": "Admin xarici kitabxana səhifəsi", "face_detection": "Üz tanÄąma", - "face_detection_description": "MaÅŸÄąn Ãļyrənməsindən istifadə edərək aktivlərdəki Ãŧzləri aşkar edilsin. Videolar ÃŧçÃŧn yalnÄąz eskiz nəzərə alÄąnÄąr. \"Yeniləyin\" bÃŧtÃŧn elementləri emal edir. \"SÄąfÄąrlayÄąn\" əlavə olaraq bÃŧtÃŧn cari Ãŧz məlumatlarÄąnÄą silir. \"YarÄąmÃ§Äąq\" hələ emal olunmamÄąÅŸ aktivləri nÃļvbəyə qoyur. Aşkar edilmiş Ãŧzlər Üz Aşkarlama tamamlandÄąqdan sonra Üz TanÄąma ÃŧçÃŧn nÃļvbəyə qoyulacaq və onlarÄą mÃļvcud və ya yeni insanlara qruplaşdÄąracaq.", - "facial_recognition_job_description": "AşkarlanmÄąÅŸ Ãŧzləri insanlara qruplaşdÄąrÄąn. Bu addÄąm Üz AşkarlanmasÄą tamamlandÄąqdan sonra icra olunur. \"SÄąfÄąrla\" bÃŧtÃŧn Ãŧzləri (simalarÄą) yenidən qruplaşdÄąrÄąr. \"ÇatÄąÅŸmayan\" nÃļvbələnmiş amma şəxs təyin olmayan Ãŧzləri gÃļstərir.", - "failed_job_command": "{command} əmri {job} işi ÃŧçÃŧn uğursuz oldu", "force_delete_user_warning": "XƏBƏRDARLIQ: Bu əməliyyat istifadəçi və bÃŧtÃŧn məlumatlarÄą siləcəkdir. Bu prossesi və silinən fayllarÄą geri qaytarmaq olmaz.", - "image_format": "Format", "image_format_description": "WebP, JPEG faylÄąna gÃļrə daha kiçik həcmə sahibdir, lakin onu kodlaşdÄąrmaq daha çox vaxt alÄąr.", - "image_fullsize_description": "BÃļyÃŧdÃŧlmÃŧş halda istifadə edilən, metadata-sÄą silinmiş tam ÃļlçÃŧlÃŧ şəkil", - "image_fullsize_enabled": "Tam ÃļlçÃŧlÃŧ şəkil generasiyasÄąnÄą aktiv et", - "image_fullsize_enabled_description": "Veb ÃŧçÃŧn uyğun olmayan formatlar ÃŧçÃŧn tam ÃļlçÃŧlÃŧ şəkil yaradÄąn. “Daxili Ãļnizləməyə ÃŧstÃŧnlÃŧk ver” aktiv olduqda, daxili Ãļnizləmələr çevrilmədən birbaşa istifadə olunur. JPEG kimi veb ÃŧçÃŧn uyğun formatlara təsir etmir.", - "image_fullsize_quality_description": "Tam ÃļlçÃŧlÃŧ şəkil keyfiyyəti (1-100). Daha yÃŧksək dəyər daha yaxÅŸÄą keyfiyyət verir, lakin daha bÃļyÃŧk ÃļlçÃŧlÃŧ fayl yaradÄąr.", - "image_fullsize_title": "Tam ÃļlçÃŧlÃŧ şəkil tənzimləmələri", - "image_prefer_embedded_preview": "Yerləşdirilmiş Ãļnizləməyə ÃŧstÃŧnlÃŧk ver", - "image_prefer_embedded_preview_setting_description": "RAW fotoşəkillərində təsvir emalÄą ÃŧçÃŧn giriş kimi və mÃŧmkÃŧn olduqda daxil edilmiş Ãļnizləmələrdən istifadə edin. Bu, bəzi şəkillər ÃŧçÃŧn daha dəqiq rənglər yarada bilər, lakin Ãļnizləmənin keyfiyyəti kameradan asÄąlÄądÄąr və təsvirdə daha çox sÄąxÄąlma artefaktlarÄą ola bilər.", - "image_prefer_wide_gamut": "Geniş qammaya ÃŧstÃŧnlÃŧk ver", - "image_prefer_wide_gamut_setting_description": "MiniatÃŧrlər ÃŧçÃŧn Display P3 istifadə edin. Bu, geniş rəng diapazonu ilə şəkillərin canlÄąlığınÄą daha yaxÅŸÄą qoruyur, lakin kÃļhnə brauzer versiyasÄą olan kÃļhnə cihazlarda şəkillər fərqli gÃļrÃŧnə bilər. Rəng dəyişikliklərinin qarÅŸÄąsÄąnÄą almaq ÃŧçÃŧn sRGB şəkilləri sRGB olaraq saxlanÄąlÄąr.", - "image_preview_description": "Tək bir elementə baxarkən və maÅŸÄąn Ãļyrənməsi ÃŧçÃŧn istifadə edilən, metaməlumatlarÄą silinmiş orta ÃļlçÃŧlÃŧ şəkil", - "image_preview_quality_description": "1-100 arasÄąnda Ãļnizləmə keyfiyyəti. Daha yÃŧksək olan daha yaxÅŸÄądÄąr, lakin daha bÃļyÃŧk fayllar yaradÄąr və tətbiqin cəldliyini zəiflədə bilər. Aşağı dəyərin təyin edilməsi maÅŸÄąn Ãļyrənməsinin keyfiyyətinə təsir gÃļstərə bilər.", "image_preview_title": "Önizləmə parametrləri", - "image_progressive": "Proqressiv", - "image_progressive_description": "Tədricən yÃŧklənmə ekranÄą ÃŧçÃŧn JPEG şəkillərini tədricən (proqressiv) kodlaşdÄąrÄąn. Bunun WebP şəkillərinə heç bir təsiri yoxdur.", "image_quality": "Keyfiyyət", "image_resolution": "ÇÃļzÃŧmlÃŧlÃŧk", "image_resolution_description": "YÃŧksək çÃļzÃŧmlÃŧlÃŧkdə daha çox detallar vardÄąr, lakin onlarÄą kodlaşdÄąrmaq da daha çox vaxt alÄąr, daha bÃļyÃŧk həcmə sahib olurlar və tətbiqin işləmə sÃŧrətini yavaşladÄąr.", "image_settings": "Şəklin parametrləri", "image_settings_description": "HazÄąrlanan şəkillərin keyfiyyətini və çÃļzÃŧmlÃŧlÃŧyÃŧnÃŧ idarə et", - "image_thumbnail_description": "Əsas zaman xətti kimi foto qruplarÄąna baxarkən istifadə edilən, metaməlumatlarÄą silinmiş kiçik eskiz", - "image_thumbnail_quality_description": "1-100 arasÄąnda eskiz keyfiyyəti. Daha yÃŧksək keyfiyyət daha yaxÅŸÄądÄąr, lakin daha bÃļyÃŧk fayllar yaradÄąr və tətbiqin cəldliyini zəiflədə bilər.", "image_thumbnail_title": "Önizləmə parametrləri", - "import_config_from_json_description": "JSON konfiqurasiya faylÄąnÄą yÃŧkləməklə sistem konfiqurasiyasÄąnÄą idxal edin", - "integrity_checks_checksum_files": "Yoxlama cəmi (checksum) fayllarÄą", - "integrity_checks_checksum_files_description": "Yoxlama cəmi (checksum) yoxlamasÄąnÄą konfiqurasiya et", - "integrity_checks_checksum_files_enable_description": "Yoxlama cəmi (checksum) yoxlamasÄąnÄą aktivləşdir", - "integrity_checks_checksum_files_percentage_limit": "Faiz limiti", - "integrity_checks_checksum_files_percentage_limit_description": "Yoxlama cəmi (checksum) yoxlamasÄąnÄąn hər intervalda nə qədər işləməli olduğunun maksimum faizini 0,01 ilə 1 arasÄąnda təyin edin.", - "integrity_checks_checksum_files_time_limit": "Zaman limiti", - "integrity_checks_checksum_files_time_limit_description": "Yoxlama cəmi yoxlamasÄąnÄąn hər intervalda işləməli olduğu maksimum mÃŧddəti təyin edin. (millisaniyə)", - "integrity_checks_missing_files": "ÇatÄąÅŸmayan fayllar", - "integrity_checks_missing_files_description": "Tezliyi təyin et və çatÄąÅŸmayan fayllarÄąn yoxlanÄąlmasÄąnÄą aktiv/deaktiv et", - "integrity_checks_missing_files_enable_description": "ÇatÄąÅŸmayan fayllarÄąn yoxlanmasÄąnÄą aktiv et", - "integrity_checks_settings": "TamlÄąq yoxlamalarÄą", - "integrity_checks_settings_description": "TamlÄąq yoxlamalarÄąnÄąn intervalÄąnÄą təyin et", - "integrity_checks_untracked_files": "İzlənməyən fayllar", - "integrity_checks_untracked_files_description": "İzlənməyən fayllarÄąn axtarÄąÅŸÄąnÄą aktiv/deaktiv et və tezliyini təyin et", - "integrity_checks_untracked_files_enable_description": "İzlənməyən fayllarÄąn axtarÄąÅŸÄąnÄą aktiv et", "job_concurrency": "{job}paralellik", "job_created": "TapÅŸÄąrÄąq yaradÄąldÄą", "job_not_concurrency_safe": "Bu iş eyni vaxtda icra ÃŧçÃŧn təhlÃŧkəsiz deyil.", @@ -130,35 +83,21 @@ "job_settings_description": "Parallel şəkildə fəaliyyət gÃļstərən tapÅŸÄąrÄąqlarÄą idarə et", "jobs_delayed": "{jobCount, plural, other {# gecikməli}}", "jobs_failed": "{jobCount, plural, other {# uğursuz}}", - "jobs_over_time": "TapÅŸÄąrÄąqlarÄąn qrafiki", "library_created": "{library} kitabxanasÄą yaradÄąldÄą", "library_deleted": "Kitabxana silindi", - "library_details": "Kitabxana parametrləri", - "library_folder_description": "İdxal qovluğunu gÃļstərin. Bu qovluqda, alt qovluqlarla birlikdə, şəkil və videolar axtarÄąlacaq.", - "library_remove_exclusion_pattern_prompt": "Bu istisna şablonunu silmək istədiyinizə əminsiniz?", - "library_remove_folder_prompt": "Bu idxal qovluğunu silmək istədiyinizə əminsiniz?", "library_scanning": "Periodik skan", "library_scanning_description": "Periodik kitabxana skanÄąnÄą confiqurasiya et", "library_scanning_enable_description": "Periodik kitabxana skanÄąnÄą aktivləşdir", "library_settings": "Xarici kitabxana", "library_settings_description": "Xarici kitabxana parametrlərini idarə et", - "library_tasks_description": "Xarici kitabxanalarda yenilikləri yoxla", - "library_updated": "Yenilənmiş kitabxana", + "library_tasks_description": "Kitabxana tapÅŸÄąrÄąqlarÄąnÄą yerinə yetir", "library_watching_enable_description": "Fayl dəyişiklikləri ÃŧçÃŧn xarici kitabxanalara baxÄąÅŸ keçirin", - "library_watching_settings": "Kitabxana nəzarəti [EKSPERİMENTAL]", + "library_watching_settings": "Kitabxana nəzarəti (EKSPERİMENTAL)", "library_watching_settings_description": "Dəyişdirilən fayllarÄą avtomatik olaraq yoxla", "logging_enable_description": "JurnalÄą aktivləşdir", "logging_level_description": "Aktiv edildikdə hansÄą jurnal səviyyəsi istifadə olunur.", - "logging_settings": "Tarixçə", - "machine_learning_availability_checks": "ƏlçatanlÄąq yoxlamalarÄą", - "machine_learning_availability_checks_description": "Əlçatan maÅŸÄąn Ãļyrənmə serverlərini avtomatik tap və təyin et", - "machine_learning_availability_checks_enabled": "ƏlçatanlÄąq yoxlamalarÄąnÄą aktiv et", - "machine_learning_availability_checks_interval": "Yoxlama intervalÄą", - "machine_learning_availability_checks_interval_description": "ƏlçatanlÄąq yoxlamalarÄą arasÄąnda millisaniyələrlə interval", - "machine_learning_availability_checks_timeout": "Sorğunun bitmə mÃŧddəti", - "machine_learning_availability_checks_timeout_description": "ƏlçatanlÄąq yoxlamalarÄą ÃŧçÃŧn millisaniyələrlə limit", "machine_learning_clip_model": "CLIP modeli", - "machine_learning_clip_model_description": "Burada qeyd olunan CLIP modelinin adÄą. Modeli dəyişdirdikdən sonra bÃŧtÃŧn şəkillər ÃŧçÃŧn 'AğıllÄą AxtarÄąÅŸ' funksiyasÄąnÄą yenidən işə salmalÄąsÄąnÄąz.", + "machine_learning_clip_model_description": "Buradaqeyd olunan CLIP modelinin adÄą. Modeli dəyişdirdikdən sonra bÃŧtÃŧn şəkillər ÃŧçÃŧn 'AğıllÄą AxtarÄąÅŸ' funksiyasÄąnÄą yenidən işə salmalÄąsÄąnÄąz.", "machine_learning_duplicate_detection": "Dublikat Aşkarlama", "machine_learning_duplicate_detection_enabled": "Dublikat aşkarlamanÄą aktiv etmək", "machine_learning_duplicate_detection_enabled_description": "Əgər deaktiv edilibsə, birə-bir eyni fayllar yenədə silinəcək.", @@ -169,6 +108,5 @@ "machine_learning_facial_recognition_description": "Şəkillərdəki Ãŧzləri aşkarla, tanÄą və qruplaşdÄąr", "machine_learning_facial_recognition_model": "Üz tanÄąma modeli" }, - "discord": "Discord", "timeline": "Zaman şkalasÄą" } diff --git a/i18n/be.json b/i18n/be.json index 95583ec646..ed66c4e1b3 100644 --- a/i18n/be.json +++ b/i18n/be.json @@ -5,36 +5,44 @@ "acknowledge": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†ŅŒ", "action": "ДзĐĩŅĐŊĐŊĐĩ", "action_common_update": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ", - "action_description": "ДзĐĩŅĐŊĐŊŅ–, ŅĐēŅ–Ņ Ņ‚Ņ€ŅĐąĐ° Đ˛Ņ‹ĐēаĐŊĐ°Ņ†ŅŒ С Đ°Đ´Ņ„Ņ–ĐģŅŒŅ‚Ņ€Đ°Đ˛Đ°ĐŊŅ‹ĐŧŅ– аб’ĐĩĐēŅ‚Đ°ĐŧŅ–", + "action_description": "ДзĐĩŅĐŊĐŊŅ–, ŅĐēŅ–Ņ Đ˛Ņ‹ĐēĐžĐŊĐ˛Đ°ŅŽŅ†Ņ†Đ° С Đ°Đ´Đ°ĐąŅ€Đ°ĐŊŅ‹ĐŧŅ– аб’ĐĩĐēŅ‚Đ°ĐŧŅ–", "actions": "ДзĐĩŅĐŊĐŊŅ–", "active": "АĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ°", "active_count": "АĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ°: {count}", "activity": "АĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ", + "activity_changed": "АĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ {enabled, select, true {҃ĐēĐģŅŽŅ‡Đ°ĐŊа} other {адĐēĐģŅŽŅ‡Đ°ĐŊа}}", "add": "Đ”Đ°Đ´Đ°Ņ†ŅŒ", "add_a_description": "Đ”Đ°Đ´Đ°Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", "add_a_location": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°", "add_a_name": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ–ĐŧŅ", "add_a_title": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐˇĐ°ĐŗĐ°ĐģОваĐē", "add_action": "Đ”Đ°Đ´Đ°Ņ†ŅŒ дСĐĩŅĐŊĐŊĐĩ", + "add_action_description": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ Đ´ĐģŅ дадаĐŊĐŊŅ дСĐĩŅĐŊĐŊŅ", "add_assets": "Đ”Đ°Đ´Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ", "add_birthday": "Đ”Đ°Đ´Đ°Ņ†ŅŒ дСĐĩĐŊҌ ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ", "add_endpoint": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐēŅ€ĐžĐŋĐē҃ Đ´ĐžŅŅ‚ŅƒĐŋ҃", "add_exclusion_pattern": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", + "add_filter": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҄ҖĐģŅŒŅ‚Ņ€", + "add_filter_description": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ Đ´ĐģŅ дадаĐŊĐŊŅ ŅžĐŧĐžĐ˛Ņ‹ Đ°Đ´ĐąĐžŅ€Ņƒ", "add_location": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°", + "add_more_users": "Đ”Đ°Đ´Đ°Ņ†ŅŒ йОĐģҌ҈ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž", "add_partner": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", + "add_path": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҈ĐģŅŅ…", "add_photos": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ°", - "add_step": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐēŅ€ĐžĐē", "add_tag": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ‚ŅĐŗ", "add_to": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃â€Ļ", "add_to_album": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧ", "add_to_album_bottom_sheet_added": "ДададзĐĩĐŊа да {album}", "add_to_album_bottom_sheet_already_exists": "ĐŖĐļĐž СĐŊĐ°Ņ…ĐžĐ´ĐˇŅ–Ņ†Ņ†Đ° Ņž {album}", "add_to_album_bottom_sheet_some_local_assets": "НĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ĐģаĐēаĐģҌĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ дададСĐĩĐŊŅ‹ Ņž аĐģŅŒĐąĐžĐŧ", + "add_to_album_toggle": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ˛Ņ‹ĐąĐ°Ņ€ Đ´ĐģŅ {album}", "add_to_albums": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧŅ‹", "add_to_albums_count": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧŅ‹ ({count})", "add_to_bottom_bar": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃", - "add_upload_to_stack": "Đ”Đ°Đ´Đ°Ņ†ŅŒ СаĐŋаĐŧĐŋаваĐŊаĐĩ Ņž ŅŅ‚ĐžŅ", + "add_to_shared_album": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃ Đ°ĐŗŅƒĐģҌĐŊŅ‹ аĐģŅŒĐąĐžĐŧ", + "add_upload_to_stack": "ЗаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ Ņ– Đ´Đ°Đ´Đ°Ņ†ŅŒ ҃ ĐŊĐ°ĐąĐžŅ€", "add_url": "Đ”Đ°Đ´Đ°Ņ†ŅŒ URL", + "add_workflow_step": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐēŅ€ĐžĐē ĐŋŅ€Đ°Ņ†ĐžŅžĐŊĐ°ĐŗĐ° ĐŋŅ€Đ°Ņ†ŅŅŅƒ", "added_to_archive": "ДададзĐĩĐŊа Ņž Đ°Ņ€Ņ…Ņ–Ņž", "added_to_favorites": "ДададзĐĩĐŊа Ņž Đ°ĐąŅ€Đ°ĐŊŅ‹Ņ", "added_to_favorites_count": "ДададзĐĩĐŊа {count, number} да Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°", @@ -43,7 +51,7 @@ "admin_user": "АдĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€", "asset_offline_description": "Đ“ŅŅ‚Ņ‹ СĐŊĐĩ҈ĐŊŅ– ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅŅ‡ĐŊŅ‹ аĐēŅ‚Ņ‹Ņž йОĐģҌ҈ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹ ĐŊа Đ´Ņ‹ŅĐē҃ Ņ– ĐąŅ‹Ņž ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ. КаĐģŅ– Ņ„Đ°ĐšĐģ ĐąŅ‹Ņž ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž ĐŧĐĩĐļĐ°Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–, ĐŋŅ€Đ°Đ˛ĐĩҀ҆Đĩ Đ˛Đ°ŅˆŅƒ Ņ…Ņ€ĐžĐŊŅ–Đē҃ Đ´ĐģŅ ĐŊĐžĐ˛Đ°ĐŗĐ° адĐŋавĐĩĐ´ĐŊĐ°ĐŗĐ° аĐēŅ‚Ņ‹Đ˛Đ°. Каб адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ аĐēŅ‚Ņ‹Ņž, ĐŋĐĩŅ€Đ°ĐēаĐŊĐ°ĐšŅ†ĐĩŅŅ, ŅˆŅ‚Đž ҈ĐģŅŅ… да Ņ„Đ°ĐšĐģа ĐŊŅ–ĐļŅĐš Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ Đ´ĐģŅ Immich Ņ– Đ°Đ´ŅĐēаĐŊŅƒĐšŅ†Đĩ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃.", "authentication_settings": "НаĐģĐ°Đ´Ņ‹ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ–", - "authentication_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ€ĐžĐģŅĐŧŅ–, OAuth Ņ– Ņ–ĐŊŅˆŅ‹ĐŧŅ– ĐŊаĐģадаĐŧŅ– Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ–", + "authentication_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ€ĐžĐģŅĐŧŅ–, OAuth Ņ– Ņ–ĐŊŅˆŅ‹Ņ ĐŊаĐģĐ°Đ´Ņ‹ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ–", "authentication_settings_disable_all": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ адĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ҃ҁĐĩ ҁĐŋĐžŅĐ°ĐąŅ‹ ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ? ĐŖĐ˛Đ°Ņ…ĐžĐ´ ĐąŅƒĐ´ĐˇĐĩ Ņ†Đ°ĐģĐēаĐŧ адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹.", "authentication_settings_reenable": "Каб СĐŊĐžŅž ҃ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐšŅ†Đĩ КаĐŧаĐŊĐ´Ņƒ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", "background_task_job": "ФОĐŊĐ°Đ˛Ņ‹Ņ СадаĐŊĐŊŅ–", @@ -53,11 +61,11 @@ "backup_onboarding_1_description": "СĐŊŅŅˆĐŊŅŅ ĐēĐžĐŋŅ–Ņ Ņž вОйĐģаĐē҃ айО Ņž Ņ–ĐŊŅˆŅ‹Đŧ Ņ„Ņ–ĐˇŅ–Ņ‡ĐŊŅ‹Đŧ ĐŧĐĩҁ҆ҋ.", "backup_onboarding_2_description": "ĐģаĐēаĐģҌĐŊŅ‹Ņ ĐēĐžĐŋŅ–Ņ– ĐŊа Ņ–ĐŊŅˆŅ‹Ņ… ĐŋҀҋĐģĐ°Đ´Đ°Ņ…. Đ“ŅŅ‚Đ° ŅžĐēĐģŅŽŅ‡Đ°Đĩ Ņž ŅŅĐąĐĩ Đ°ŅĐŊĐžŅžĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹ Ņ– ĐģаĐēаĐģҌĐŊŅƒŅŽ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ ĐŗŅŅ‚Ņ‹Ņ… Ņ„Đ°ĐšĐģĐ°Ņž.", "backup_onboarding_3_description": "ĐŋĐžŅžĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ ĐēĐžĐŋŅ–Đš Đ˛Đ°ŅˆŅ‹Ņ… даĐŊĐŊҋ҅, ҃ ҂ҋĐŧ ĐģŅ–Đē҃ ĐˇŅ‹Ņ…ĐžĐ´ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž. Đ“ŅŅ‚Đ° ŅžĐēĐģŅŽŅ‡Đ°Đĩ 1 ĐŋĐ°ĐˇĐ°ŅˆŅ‚Đ°Ņ‚ĐŊŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ Ņ– 2 ĐģаĐēаĐģҌĐŊŅ‹Ņ ĐēĐžĐŋŅ–Ņ–.", - "backup_onboarding_description": "ĐĄŅ‚Ņ€Đ°Ņ‚ŅĐŗŅ–Ņ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Ņ‹Ņ… ĐēĐžĐŋŅ–Đš 3-2-1 Ņ€ŅĐēаĐŧĐĩĐŊĐ´ŅƒĐĩŅ†Ņ†Đ° Đ´ĐģŅ Đ°Ņ…ĐžĐ˛Ņ‹ Đ˛Đ°ŅˆŅ‹Ņ… даĐŊҋ҅. Đ—Đ°Ņ…ĐžŅžĐ˛Đ°ĐšŅ†Đĩ ĐēĐžĐŋŅ–Ņ– СаĐŋаĐŧĐŋаваĐŊҋ҅ Ņ„ĐžŅ‚Đ°/Đ˛Ņ–Đ´ŅĐ°, а Ņ‚Đ°ĐēŅĐ°Đŧа ĐąĐ°ĐˇŅƒ даĐŊҋ҅ Immich Đ´ĐģŅ ĐŋĐžŅžĐŊĐ°ĐŗĐ° Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ.", + "backup_onboarding_description": " ŅŅ‚Ņ€Đ°Ņ‚ŅĐŗŅ–Ņ Ņ€ŅĐˇĐĩŅ€Đ˛Đ°Đ˛Đ°ĐŊĐŊŅ 3-2-1 Ņ€ŅĐēаĐŧĐĩĐŊдаваĐŊа Đ´ĐģŅ Đ°Ņ…ĐžĐ˛Ņ‹ Đ˛Đ°ŅˆŅ‹Ņ… даĐŊĐŊҋ҅. Đ’Ņ‹ ĐŋĐ°Đ˛Ņ–ĐŊĐŊŅ‹ ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐēĐžĐŋŅ–Ņ– Đ˛Đ°ŅˆŅ‹Ņ… ĐˇĐ°ĐŗŅ€ŅƒĐļаĐŊҋ҅ Ņ„ĐžŅ‚Đ° / Đ˛Ņ–Đ´ŅĐ° ĐŗŅŅ‚Đ°Đē Đļа Đ´ĐžĐąŅ€Đ°, ŅĐē ĐąĐ°ĐˇŅƒ даĐŊĐŊҋ҅ Immich Đ´ĐģŅ Đ˛Ņ‹Ņ‡Đ°Ņ€ĐŋаĐģҌĐŊа ĐŋĐžŅžĐŊĐ°ĐŗĐ° Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–ŅĐ˛Đ°ĐŊĐŊŅ.", "backup_onboarding_footer": "Каб Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ Đ´Đ°Đ´Đ°Ņ‚ĐēĐžĐ˛ŅƒŅŽ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹ŅŽ ĐŋŅ€Đ° Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Immich, ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да даĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ–.", "backup_onboarding_parts_title": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Ņ ĐēĐžĐŋŅ–Ņ ÂĢ3-2-1Âģ ҃ĐēĐģŅŽŅ‡Đ°Đĩ Ņž ŅŅĐąĐĩ:", "backup_onboarding_title": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Ņ‹Ņ ĐēĐžĐŋŅ–Ņ–", - "backup_settings": "НаĐģĐ°Đ´Ņ‹ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", + "backup_settings": "НаĐģĐ°Đ´Ņ‹ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–ŅĐ˛Đ°ĐŊĐŊŅ", "backup_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– Ņ€ŅĐˇĐĩŅ€Đ˛Đ°Đ˛Đ°ĐŊĐŊŅ ĐąĐ°ĐˇŅ‹ даĐŊҋ҅.", "cleared_jobs": "ĐŅ‡Ņ‹ŅˆŅ‡Đ°ĐŊŅ‹ СадаĐŊĐŊŅ– Đ´ĐģŅ: {job}", "config_set_by_file": "КаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ ĐˇĐ°Ņ€Đ°Đˇ ŅƒŅŅ‚Đ°ĐģŅĐ˛Đ°ĐŊа ĐŋŅ€Đ°Đˇ Ņ„Đ°ĐšĐģ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ–", @@ -70,83 +78,67 @@ "copy_config_to_clipboard_description": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐąŅĐŗŅƒŅ‡ŅƒŅŽ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹ŅŽ ŅŅ–ŅŅ‚ŅĐŧŅ‹ Ņž JSON ҃ ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃", "create_job": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ СадаĐŊĐŊĐĩ", "cron_expression": "Đ’Ņ‹Ņ€Đ°Đˇ Cron", - "cron_expression_description": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°Đģ ҁĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ‡Ņ‹ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚ cron. ДĐģŅ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐŊĐŊŅ Đ´Đ°Đ´Đ°Ņ‚ĐēОваК Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ–, ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ, ĐŊаĐŋҀҋĐēĐģад, да Crontab Guru", + "cron_expression_description": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°Đģ ҁĐēаĐŊаваĐŊĐŊŅ, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ‡Ņ‹ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚ cron. ДĐģŅ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐŊĐŊŅ Đ´Đ°Đ´Đ°Ņ‚ĐēОваК Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ–, ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ, ĐŊаĐŋҀҋĐēĐģад, да Crontab Guru", "cron_expression_presets": "ĐŸŅ€Đ°Đ´ŅƒŅŅ‚Đ°ĐŊĐžŅžĐēŅ– Đ˛Ņ‹Ņ€Đ°ĐˇĐ°Ņž Cron", "disable_login": "АдĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅƒĐ˛Đ°Ņ…ĐžĐ´", - "download_csv": "ĐĄĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ CSV", - "duplicate_detection_job_description": "ЗаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ ĐŊа аб’ĐĩĐēŅ‚Đ°Ņ… Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ ĐŋадОйĐŊҋ҅ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž. ЗаĐģĐĩĐļŅ‹Ņ†ŅŒ ад Smart Search", - "exclusion_pattern_description": "ШайĐģĐžĐŊŅ‹ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ даСваĐģŅŅŽŅ†ŅŒ Ņ–ĐŗĐŊĐ°Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Ņ„Đ°ĐšĐģŅ‹ Ņ– ĐŋаĐŋĐēŅ– ĐŋҀҋ ҁĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ– ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–. Đ“ŅŅ‚Đ° ĐēĐ°Ņ€Ņ‹ŅĐŊа, ĐēаĐģŅ– Ņž Đ˛Đ°Ņ Ņ‘ŅŅ†ŅŒ ĐŋаĐŋĐēŅ–, ŅĐēŅ–Ņ СĐŧŅŅˆŅ‡Đ°ŅŽŅ†ŅŒ Ņ„Đ°ĐšĐģŅ‹, ŅĐēŅ–Ņ Đ˛Ņ‹ ĐŊĐĩ Ņ…ĐžŅ‡Đ°Ņ†Đĩ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ, ĐŊаĐŋҀҋĐēĐģад, Ņ„Đ°ĐšĐģŅ‹ RAW.", + "duplicate_detection_job_description": "ЗаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ ĐŊа аĐēŅ‚Ņ‹Đ˛Đ°Ņ… Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ ĐŋадОйĐŊҋ҅ Đ˛Ņ‹ŅŅž. ЗаĐģĐĩĐļŅ‹Ņ†ŅŒ ад Smart Search", + "exclusion_pattern_description": "ШайĐģĐžĐŊŅ‹ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ даСваĐģŅŅŽŅ†ŅŒ Ņ–ĐŗĐŊĐ°Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Ņ„Đ°ĐšĐģŅ‹ Ņ– ĐŋаĐŋĐēŅ– ĐŋҀҋ ҁĐēаĐŊаваĐŊĐŊŅ– Đ˛Đ°ŅˆĐ°Đš ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–. Đ“ŅŅ‚Đ° ĐēĐ°Ņ€Ņ‹ŅĐŊа, ĐēаĐģŅ– Ņž Đ˛Đ°Ņ Ņ‘ŅŅ†ŅŒ ĐŋаĐŋĐēŅ–, ŅĐēŅ–Ņ СĐŧŅŅˆŅ‡Đ°ŅŽŅ†ŅŒ Ņ„Đ°ĐšĐģŅ‹, ŅĐēŅ–Ņ Đ˛Ņ‹ ĐŊĐĩ Ņ…ĐžŅ‡Đ°Ņ†Đĩ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ, ĐŊаĐŋҀҋĐēĐģад, Ņ„Đ°ĐšĐģŅ‹ RAW.", "export_config_as_json_description": "Đ—Đ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ ĐąŅĐŗŅƒŅ‡ŅƒŅŽ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹ŅŽ ŅŅ–ŅŅ‚ŅĐŧŅ‹ Ņž Ņ„Đ°ĐšĐģ JSON", "external_libraries_page_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ СĐŊĐĩ҈ĐŊŅ–ĐŧŅ– ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēаĐŧŅ–", "face_detection": "Đ’Ņ‹ŅŅžĐģĐĩĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", - "face_detection_description": "Đ’Ņ‹ŅŅžĐģŅŅ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹ ĐŊа Ņ„ĐžŅ‚Đ°ĐˇĐ´Ņ‹ĐŧĐēĐ°Ņ… Ņ– Đ˛Ņ–Đ´ŅĐ° С даĐŋаĐŧĐžĐŗĐ°Đš ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ. ДĐģŅ Đ˛Ņ–Đ´ŅĐ° ŅžĐģŅ–Ņ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Đ°. \"АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ\" (ĐŋĐĩŅ€Đ°)аĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°Đĩ ŅžŅĐĩ ĐŧĐĩĐ´Ņ‹Ņ. \"ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ\" Đ´Đ°Đ´Đ°Ņ‚ĐēОва Đ°Ņ‡Ņ‹ŅˆŅ‡Đ°Đĩ ŅžŅĐĩ ĐąŅĐŗŅƒŅ‡Ņ‹Ņ даĐŊŅ‹Ņ ĐŋŅ€Đ° Ņ‚Đ˛Đ°Ņ€Ņ‹. \"ĐĐ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°Đĩ\" ŅŅ‚Đ°Đ˛Ņ–Ņ†ŅŒ ҃ Ņ‡Đ°Ņ€ĐŗŅƒ ĐŧĐĩĐ´Ņ‹Ņ, ŅĐēŅ–Ņ ŅŅˆŅ‡Ņ ĐŊĐĩ ĐąŅ‹ĐģŅ– аĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°ĐŊŅ‹Ņ. Đ’Ņ‹ŅŅžĐģĐĩĐŊŅ‹Ņ Ņ‚Đ˛Đ°Ņ€Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋĐ°ŅŅ‚Đ°ŅžĐģĐĩĐŊŅ‹ Ņž Ņ‡Đ°Ņ€ĐŗŅƒ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ ĐŋĐ°ŅĐģŅ ĐˇĐ°Đ˛ŅŅ€ŅˆŅĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, С ĐŗŅ€ŅƒĐŋаваĐŊĐŊĐĩĐŧ Ņ–Ņ… Đŋа ҖҁĐŊŅƒŅŽŅ‡Ņ‹Ņ… айО ĐŊĐžĐ˛Ņ‹Ņ… ĐģŅŽĐ´ĐˇŅŅ….", - "facial_recognition_job_description": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ŅŅžĐģĐĩĐŊŅ‹Ņ Ņ‚Đ˛Đ°Ņ€Ņ‹ Đŋа ĐģŅŽĐ´ĐˇŅŅ…. Đ“ŅŅ‚Ņ‹ ŅŅ‚Đ°Đŋ Đ˛Ņ‹ĐēĐžĐŊваĐĩŅ†Ņ†Đ° ĐŋĐ°ŅĐģŅ ĐˇĐ°Đ˛ŅŅ€ŅˆŅĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž. \"ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ\" (ĐŋĐ°ŅžŅ‚ĐžŅ€ĐŊа) ĐŋĐĩŅ€Đ°ĐŗŅ€ŅƒĐŋĐžŅžĐ˛Đ°Đĩ ŅžŅĐĩ Ņ‚Đ˛Đ°Ņ€Ņ‹. \"ĐĐ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°Đĩ\" ŅŅ‚Đ°Đ˛Ņ–Ņ†ŅŒ ҃ Ņ‡Đ°Ņ€ĐŗŅƒ Ņ‚Đ˛Đ°Ņ€Ņ‹, ŅĐēŅ–Ņ ŅŅˆŅ‡Ņ ĐŊĐĩ ĐŋҀҋĐŋŅ–ŅĐ°ĐŊŅ‹Ņ да ŅĐēĐžĐŗĐ°-ĐŊĐĩĐąŅƒĐ´ĐˇŅŒ Ņ‡Đ°ĐģавĐĩĐēа.", + "face_detection_description": "Đ’Ņ‹ŅŅžĐģŅŅ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹ ĐŊа Ņ„ĐžŅ‚Đ°ĐˇĐ´Ņ‹ĐŧĐēĐ°Ņ… Ņ– Đ˛Ņ–Đ´ŅĐ° С даĐŋаĐŧĐžĐŗĐ°Đš ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ. ДĐģŅ Đ˛Ņ–Đ´ŅĐ° ŅžĐģŅ–Ņ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Đ°. \"АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ\" (ĐŋĐĩŅ€Đ°)аĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°Đĩ ŅžŅĐĩ ĐŧĐĩĐ´Ņ‹Ņ. \"ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ\" Đ´Đ°Đ´Đ°Ņ‚ĐēОва Đ°Ņ‡Ņ‹ŅˆŅ‡Đ°Đĩ ŅžŅĐĩ ĐąŅĐŗŅƒŅ‡Ņ‹Ņ даĐŊŅ‹Ņ ĐŋŅ€Đ° Ņ‚Đ˛Đ°Ņ€Ņ‹. \"ĐĐ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°Đĩ\" ŅŅ‚Đ°Đ˛Ņ–Ņ†ŅŒ ҃ Ņ‡Đ°Ņ€ĐŗŅƒ ĐŧĐĩĐ´Ņ‹Ņ, ŅĐēŅ–Ņ ŅŅˆŅ‡Ņ ĐŊĐĩ ĐąŅ‹ĐģŅ– аĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°ĐŊŅ‹Ņ. Đ’Ņ‹ŅŅžĐģĐĩĐŊŅ‹Ņ Ņ‚Đ˛Đ°Ņ€Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋĐ°ŅŅ‚Đ°ŅžĐģĐĩĐŊŅ‹ Ņž Ņ‡Đ°Ņ€ĐŗŅƒ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Đ°ŅĐžĐą ĐŋĐ°ŅĐģŅ ĐˇĐ°Đ˛ŅŅ€ŅˆŅĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, С ĐŗŅ€ŅƒĐŋаваĐŊĐŊĐĩĐŧ Ņ–Ņ… Đŋа ҖҁĐŊŅƒŅŽŅ‡Ņ‹Ņ… айО ĐŊĐžĐ˛Ņ‹Ņ… ĐģŅŽĐ´ĐˇŅŅ….", + "facial_recognition_job_description": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ŅŅžĐģĐĩĐŊŅ‹Ņ Ņ‚Đ˛Đ°Ņ€Ņ‹ Đŋа Đ°ŅĐžĐąĐ°Ņ…. Đ“ŅŅ‚Ņ‹ ŅŅ‚Đ°Đŋ Đ˛Ņ‹ĐēĐžĐŊваĐĩŅ†Ņ†Đ° ĐŋĐ°ŅĐģŅ ĐˇĐ°Đ˛ŅŅ€ŅˆŅĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž. \"ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ\" (ĐŋĐ°ŅžŅ‚ĐžŅ€ĐŊа) ĐŋĐĩŅ€Đ°ĐŗŅ€ŅƒĐŋĐžŅžĐ˛Đ°Đĩ ŅžŅĐĩ Ņ‚Đ˛Đ°Ņ€Ņ‹. \"ĐĐ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°Đĩ\" ŅŅ‚Đ°Đ˛Ņ–Ņ†ŅŒ ҃ Ņ‡Đ°Ņ€ĐŗŅƒ Ņ‚Đ˛Đ°Ņ€Ņ‹, ŅĐēŅ–Ņ ŅŅˆŅ‡Ņ ĐŊĐĩ ĐŋҀҋĐŋŅ–ŅĐ°ĐŊŅ‹Ņ да ŅĐēОК-ĐŊĐĩĐąŅƒĐ´ĐˇŅŒ Đ°ŅĐžĐąŅ‹.", "failed_job_command": "КаĐŧаĐŊда {command} ĐŊĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ Đ´ĐģŅ СадаĐŊĐŊŅ: {job}", "force_delete_user_warning": "ĐŸĐĐŸĐ¯Đ Đ­Đ”Đ–ĐĐĐĐ•: Đ“ŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩадĐēĐģадĐŊа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņ– ŅžŅĐĩ ай'ĐĩĐē҂ҋ. Đ“ŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ Đ°Đ´Ņ€ĐžĐąĐģĐĩĐŊа Ņ– Ņ„Đ°ĐšĐģŅ‹ ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐąŅƒĐ´ĐˇĐĩ адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ.", "image_format": "Đ¤Đ°Ņ€ĐŧĐ°Ņ‚", "image_format_description": "WebP ŅŅ‚Đ˛Đ°Ņ€Đ°Đĩ ĐŧĐĩĐŊŅˆŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹, ҇ҋĐŧ JPEG, аĐģĐĩ ĐŋавОĐģҌĐŊĐĩĐš ĐēĐ°Đ´ŅƒĐĩ.", - "image_fullsize_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ ҃ ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ ĐąĐĩС ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋавĐĩĐģŅ–Ņ‡ŅĐŊĐŊŅ–", - "image_fullsize_enabled": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ", - "image_fullsize_enabled_description": "ĐĄŅ‚Đ˛Đ°Ņ€Đ°Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ ҃ ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ Đ´ĐģŅ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Đ°Ņž, ŅˆŅ‚Đž ĐŊĐĩ ĐŋŅ€Ņ‹Đ´Đ°Ņ‚ĐŊŅ‹Ņ Đ´ĐģŅ Đ˛ŅĐą. КаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа ĐžĐŋŅ†Ņ‹Ņ \"ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ŅžĐąŅƒĐ´Đ°Đ˛Đ°ĐŊаК ĐŋŅ€Đ°ŅĐ˛Đĩ\", ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ° ĐŊĐĩĐŋĐ°ŅŅ€ŅĐ´ĐŊа ĐąĐĩС ĐēаĐŊвĐĩŅ€Ņ‚Đ°Ņ†Ņ‹Ņ–. НĐĩ ŅžĐŋĐģŅ‹Đ˛Đ°Đĩ ĐŊа Đ˛ŅĐą-ĐŋŅ€Ņ‹Đ´Đ°Ņ‚ĐŊŅ‹Ņ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Ņ‹, Ņ‚Đ°ĐēŅ–Ņ ŅĐē JPEG.", - "image_fullsize_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ ад 1 да 100. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēаĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐģĐĩĐŋŅˆĐ°Đĩ, аĐģĐĩ ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋавĐĩĐģŅ–Ņ‡ŅĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģа.", - "image_fullsize_title": "НаĐģĐ°Đ´Ņ‹ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ", + "image_fullsize_description": "Đ’Ņ‹ŅĐ˛Đ° Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ ĐąĐĩС ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋавĐĩĐģŅ–Ņ‡ŅĐŊĐŊŅ–", + "image_fullsize_enabled": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ Đ˛Ņ‹ŅĐ˛Ņ‹ Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ", + "image_fullsize_enabled_description": "ĐĄŅ‚Đ˛Đ°Ņ€Đ°Ņ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņƒ Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ Đ´ĐģŅ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Đ°Ņž, ŅˆŅ‚Đž ĐŊĐĩ ĐŋŅ€Ņ‹Đ´Đ°Ņ‚ĐŊŅ‹Ņ Đ´ĐģŅ Đ˛ŅĐą. КаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа ĐžĐŋŅ†Ņ‹Ņ \"ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ŅžĐąŅƒĐ´Đ°Đ˛Đ°ĐŊаК ĐŋŅ€Đ°ŅĐ˛Đĩ\", ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ° ĐŊĐĩĐŋĐ°ŅŅ€ŅĐ´ĐŊа ĐąĐĩС ĐēаĐŊвĐĩŅ€Ņ‚Đ°Ņ†Ņ‹Ņ–. НĐĩ ŅžĐŋĐģŅ‹Đ˛Đ°Đĩ ĐŊа Đ˛ŅĐą-ĐŋŅ€Ņ‹Đ´Đ°Ņ‚ĐŊŅ‹Ņ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Ņ‹, Ņ‚Đ°ĐēŅ–Ņ ŅĐē JPEG.", + "image_fullsize_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ‹ Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ ад 1 да 100. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēаĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐģĐĩĐŋŅˆĐ°Đĩ, аĐģĐĩ ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋавĐĩĐģŅ–Ņ‡ŅĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģа.", + "image_fullsize_title": "НаĐģĐ°Đ´Ņ‹ Đ˛Ņ‹ŅĐ˛Ņ‹ Ņž ĐŋĐžŅžĐŊŅ‹Đŧ ĐŋаĐŧĐĩҀҋ", "image_prefer_embedded_preview": "ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ŅžĐąŅƒĐ´Đ°Đ˛Đ°ĐŊаК ĐŋŅ€Đ°ŅĐ˛Đĩ", - "image_prefer_embedded_preview_setting_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ŅƒĐąŅƒĐ´Đ°Đ˛Đ°ĐŊŅ‹Ņ ĐŋŅ€Đ°ŅĐ˛Ņ‹ Ņž RAW-Ņ„ĐžŅ‚Đ°ĐˇĐ´Ņ‹ĐŧĐēĐ°Ņ… Ņž ŅĐēĐ°ŅŅ†Ņ– ŅžĐ˛Đ°Ņ…ĐžĐ´ĐŊҋ҅ даĐŊҋ҅ Đ´ĐģŅ аĐŋŅ€Đ°Ņ†ĐžŅžĐēŅ– Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž, ĐēаĐģŅ– ĐŧĐ°ĐŗŅ‡Ņ‹Đŧа. Đ“ŅŅ‚Đ° даСваĐģŅĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ йОĐģҌ҈ даĐēĐģадĐŊŅ‹Ņ ĐēĐžĐģĐĩҀҋ Đ´ĐģŅ ĐŊĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž, аĐģĐĩ Đļ ŅĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ŅŅž СаĐģĐĩĐļŅ‹Ņ†ŅŒ ад ĐēаĐŧĐĩҀҋ, Ņ– ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐĩ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ йОĐģҌ҈ Đ°Ņ€Ņ‚ŅŅ„Đ°ĐēŅ‚Đ°Ņž ҁ҆ҖҁĐē҃.", - "image_prefer_wide_gamut": "ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ŅˆŅ‹Ņ€ĐžĐēаК ĐŗĐ°ĐŧĐĩ", + "image_prefer_embedded_preview_setting_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ŅƒĐąŅƒĐ´Đ°Đ˛Đ°ĐŊŅ‹Ņ ĐŋŅ€Đ°ŅĐ˛Ņ‹ Ņž RAW-Ņ„ĐžŅ‚Đ°ĐˇĐ´Ņ‹ĐŧĐēĐ°Ņ… Ņž ŅĐēĐ°ŅŅ†Ņ– ŅžĐ˛Đ°Ņ…ĐžĐ´ĐŊҋ҅ даĐŊҋ҅ Đ´ĐģŅ аĐŋŅ€Đ°Ņ†ĐžŅžĐēŅ– ĐŧаĐģŅŽĐŊĐēĐ°Ņž, ĐēаĐģŅ– ĐŧĐ°ĐŗŅ‡Ņ‹Đŧа. Đ“ŅŅ‚Đ° даСваĐģŅĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ йОĐģҌ҈ даĐēĐģадĐŊŅ‹Ņ ĐēĐžĐģĐĩҀҋ Đ´ĐģŅ ĐŊĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž, аĐģĐĩ Đļ ŅĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ŅŅž СаĐģĐĩĐļŅ‹Ņ†ŅŒ ад ĐēаĐŧĐĩҀҋ, Ņ– ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐĩ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ йОĐģҌ҈ Đ°Ņ€Ņ‚ŅŅ„Đ°ĐēŅ‚Đ°Ņž ҁ҆ҖҁĐē҃.", + "image_prefer_wide_gamut": "ĐĐ´Đ´Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ŅˆŅ‹Ņ€ĐžĐēаК ĐŗĐ°ĐŧĐĩ", "image_prefer_wide_gamut_setting_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐšŅ†Đĩ Display P3 Đ´ĐģŅ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€. Đ“ŅŅ‚Đ° ĐģĐĩĐŋĐĩĐš ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°Đĩ ŅŅ€ĐēĐ°ŅŅ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž С ŅˆŅ‹Ņ€ĐžĐēаК ĐēĐžĐģĐĩŅ€Đ°Đ˛Đ°Đš ĐŋŅ€Đ°ŅŅ‚ĐžŅ€Đ°Đš, аĐģĐĩ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ ĐŧĐžĐŗŅƒŅ†ŅŒ Đ˛Ņ‹ĐŗĐģŅĐ´Đ°Ņ†ŅŒ Đŋа-Ņ–ĐŊŅˆĐ°Đŧ҃ ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ĐŋҀҋĐģĐ°Đ´Đ°Ņ… ŅĐ° ŅŅ‚Đ°Ņ€Đ°Đš вĐĩŅ€ŅŅ–ŅĐš ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đ°. Đ’Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ sRGB ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ° Ņž Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ sRGB, ŅˆŅ‚Đž даСваĐģŅĐĩ ĐŋаСйĐĩĐŗĐŊŅƒŅ†ŅŒ ĐēĐžĐģĐĩŅ€Đ°Đ˛Ņ‹Ņ… ĐˇŅ€ŅƒŅ…Đ°Ņž.", "image_preview_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ ŅŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ С Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹ĐŧŅ–, Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ Đ°ŅĐžĐąĐŊĐ°ĐŗĐ° Ņ€ŅŅŅƒŅ€ŅŅƒ Ņ– Đ´ĐģŅ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ", - "image_preview_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŋĐĩŅ€Đ°Đ´ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ Ņ„Đ°ĐšĐģŅ‹ ŅŅ‚Đ°ĐŊĐžĐ˛ŅŅ†Ņ†Đ° йОĐģŅŒŅˆŅ‹ĐŧŅ–, Ņ– ĐŗŅŅ‚Đ° ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°Ņ†Ņ‹ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹. ĐŅ–ĐˇĐēаĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐŧĐžĐļа ĐŋĐ°ŅžĐŋĐģŅ‹Đ˛Đ°Ņ†ŅŒ ĐŊа ŅĐēĐ°ŅŅ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ.", + "image_preview_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ŅĐ˛Ņ‹ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ŅŅ‚Đ˛Đ°Ņ€Đ°ŅŽŅ†Ņ†Đ° Ņ„Đ°ĐšĐģŅ‹ йОĐģŅŒŅˆĐ°ĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†Ņ†Đ° Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ ĐŋҀҋĐēĐģадаĐŊĐŊŅ. ĐŽŅŅ‚Đ°ĐŊĐžŅžĐēа ĐŊŅ–ĐˇĐēĐ°ĐŗĐ° СĐŊĐ°Ņ‡ŅĐŊĐŊŅ ĐŧĐžĐļа ĐŋĐ°ŅžĐŋĐģŅ‹Đ˛Đ°Ņ†ŅŒ ĐŊа ŅĐēĐ°ŅŅ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ.", "image_preview_title": "НаĐģĐ°Đ´Ņ‹ ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ", "image_progressive": "ĐŸŅ€Đ°ĐŗŅ€ŅŅŅ–ŅžĐŊŅ‹", - "image_progressive_description": "ĐšĐ°Đ´ĐˇŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ JPEG ĐŋŅ€Đ°ĐŗŅ€ŅŅŅ–ŅžĐŊа Đ´ĐģŅ ĐŋĐ°ŅŅ‚ŅƒĐŋОваК ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēŅ–. Đ“ŅŅ‚Đ° ĐŊĐĩ ŅžĐŋĐģŅ‹Đ˛Đ°Đĩ ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ WebP.", + "image_progressive_description": "Đ’Ņ‹ŅĐ˛Ņ‹ С ĐŋŅ€Đ°ĐŗŅ€ŅŅŅ–ŅžĐŊŅ‹Đŧ ĐēОдаваĐŊĐŊĐĩĐŧ ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°ŅŽŅ†Ņ†Đ° Ņ…ŅƒŅ‚Ņ‡ŅĐš, ĐŋĐ°ŅŅ‚ŅƒĐŋОва ĐŋаĐģŅĐŋŅˆĐ°ĐĩŅ†Ņ†Đ° ŅĐēĐ°ŅŅ†ŅŒ. НаĐģада ĐŊĐĩ ŅžĐŋĐģŅ‹Đ˛Đ°Đĩ ĐŊа Đ˛Ņ‹ŅĐ˛Ņƒ Ņž Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ WebP.", "image_quality": "Đ¯ĐēĐ°ŅŅ†ŅŒ", "image_resolution": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", - "image_resolution_description": "БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ даСваĐģŅĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ йОĐģҌ҈ Đ´ŅŅ‚Đ°ĐģŅŅž, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ, ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģĐ°Ņž Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹.", + "image_resolution_description": "БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ даСваĐģŅĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ йОĐģҌ҈ Đ´ŅŅ‚Đ°ĐģŅŅž, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ, ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐŊĐŊŅ ĐŋаĐŧĐĩŅ€Ņƒ Ņ„Đ°ĐšĐģĐ°Ņž Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ Đ´Đ°Đ´Đ°Ņ‚Đē҃.", "image_settings": "НаĐģĐ°Đ´Ņ‹ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°", - "image_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ŅĐēĐ°ŅŅ†ŅŽ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŽ ŅĐŗĐĩĐŊĐĩŅ€Ņ‹Ņ€Đ°Đ˛Đ°ĐŊҋ҅ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž", - "image_thumbnail_description": "МаĐģĐĩĐŊҌĐēĐ°Ņ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Đ° С Đ°Ņ‡Ņ‹ŅˆŅ‡Đ°ĐŊŅ‹ĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹ĐŧŅ–, ŅĐēĐ°Ņ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ ĐŗŅ€ŅƒĐŋ Ņ„Đ°Ņ‚Đ°ĐŗŅ€Đ°Ņ„Ņ–Đš, Ņ‚Đ°ĐēŅ–Ņ… ŅĐē Đ°ŅĐŊĐžŅžĐŊĐ°Ņ Ņ…Ņ€ĐžĐŊŅ–Đēа", - "image_thumbnail_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ Ņ„Đ°ĐšĐģŅ‹ ŅŅ‚Đ°ĐŊĐžĐ˛ŅŅ†Ņ†Đ° йОĐģŅŒŅˆŅ‹ĐŧŅ–, ŅˆŅ‚Đž ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°Ņ†Ņ‹ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹.", + "image_settings_description": "ĐšŅ–Ņ€ŅƒĐšŅ†Đĩ ŅĐēĐ°ŅŅ†ŅŽ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŽ ŅĐŗĐĩĐŊĐĩŅ€Ņ‹Ņ€Đ°Đ˛Đ°ĐŊҋ҅ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž", + "image_thumbnail_description": "МаĐģĐĩĐŊҌĐēĐ°Ņ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Đ° С Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°Đ´ĐˇĐĩĐŊŅ‹ĐŧŅ–, ŅĐēĐ°Ņ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ ĐŗŅ€ŅƒĐŋ Ņ„Đ°Ņ‚Đ°ĐŗŅ€Đ°Ņ„Ņ–Đš, Ņ‚Đ°ĐēŅ–Ņ… ŅĐē Đ°ŅĐŊĐžŅžĐŊĐ°Ņ Ņ…Ņ€ĐžĐŊŅ–Đēа", + "image_thumbnail_quality_description": "Đ¯ĐēĐ°ŅŅ†ŅŒ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ ад 1 да 100. Đ§Ņ‹Đŧ Đ˛Ņ‹ŅˆŅĐš ŅĐēĐ°ŅŅ†ŅŒ, ҂ҋĐŧ ĐģĐĩĐŋ҈, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ŅŅ‚Đ˛Đ°Ņ€Đ°ŅŽŅ†Ņ†Đ° Ņ„Đ°ĐšĐģŅ‹ йОĐģŅŒŅˆĐ°ĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ Ņ– ĐŧĐžĐļа СĐŊŅ–ĐˇŅ–Ņ†Ņ†Đ° Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ Đ˛ĐžĐ´ĐŗŅƒĐē҃ ĐŋҀҋĐēĐģадаĐŊĐŊŅ.", "image_thumbnail_title": "НаĐģĐ°Đ´Ņ‹ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€", "import_config_from_json_description": "ІĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹ŅŽ ŅŅ–ŅŅ‚ŅĐŧŅ‹ ĐŋŅ€Đ°Đˇ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ JSON Ņ„Đ°ĐšĐģа ĐŊĐ°ŅŅ‚Ņ€ĐžĐĩĐē", - "integrity_checks_checksum_files": "ФаКĐģŅ‹ ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃Đŧ", - "integrity_checks_checksum_files_description": "НаĐģадĐļваĐŊĐŊĐĩ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧŅ‹", - "integrity_checks_checksum_files_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧŅ‹", - "integrity_checks_checksum_files_percentage_limit": "ĐŸŅ€Đ°Ņ†ŅĐŊŅ‚ĐŊŅ‹ ĐģŅ–ĐŧŅ–Ņ‚", - "integrity_checks_checksum_files_percentage_limit_description": "НаĐģĐ°Đ´ĐˇŅŒŅ†Đĩ ĐŧаĐēҁҖĐŧаĐģҌĐŊŅ‹ ĐŋŅ€Đ°Ņ†ŅĐŊŅ‚ ад 0,01 да 1, ŅĐēŅ– Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°Đĩ, ĐēĐžĐģҌĐēŅ– Ņ€Đ°ĐˇĐžŅž Ņ‚Ņ€ŅĐąĐ° ĐŋŅ€Đ°Đ˛ŅŅ€Đ°Ņ†ŅŒ ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊŅƒŅŽ ҁ҃Đŧ҃ ĐēĐžĐļĐŊŅ‹ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°Đģ.", - "integrity_checks_checksum_files_time_limit": "АйĐŧĐĩĐļаваĐŊĐŊĐĩ Đŋа Ņ‡Đ°ŅĐĩ", - "integrity_checks_checksum_files_time_limit_description": "НаĐģадĐļваĐŊĐŊĐĩ ĐŧаĐēҁҖĐŧаĐģҌĐŊаК ĐŋŅ€Đ°Ņ†ŅĐŗĐģĐ°ŅŅ†Ņ–, ĐŊа ĐŋŅ€Đ°Ņ†ŅĐŗŅƒ ŅĐēОК ĐŋŅ€Đ°Đ˛ĐĩŅ€Đēа ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧŅ‹ ĐŋĐ°Đ˛Ņ–ĐŊĐŊа Đ˛Ņ‹ĐēĐžĐŊĐ˛Đ°Ņ†Ņ†Đ° ĐēĐžĐļĐŊŅ‹ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°Đģ. (Đŧҁ)", - "integrity_checks_missing_files": "ĐĐ´ŅŅƒŅ‚ĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹", - "integrity_checks_missing_files_description": "НаĐģадĐļваĐŊĐŊĐĩ Ņ‡Đ°ŅŅ‚Đ°Ņ‚Ņ‹ Ņ– ŅžĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ айО Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– Đ°Đ´ŅŅƒŅ‚ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", - "integrity_checks_missing_files_enable_description": "ĐŖĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– Đ°Đ´ŅŅƒŅ‚ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", - "integrity_checks_settings": "ĐŸŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– Ņ†ŅĐģĐ°ŅĐŊĐ°ŅŅ†Ņ–", - "integrity_checks_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°ĐģаĐŧŅ– ĐŋŅ€Đ°Đ˛ĐĩŅ€Đ°Đē Ņ†ŅĐģĐ°ŅĐŊĐ°ŅŅ†Ņ–", - "integrity_checks_untracked_files": "ФаКĐģŅ‹, ŅĐēŅ–Ņ ĐŊĐĩ Đ°Đ´ŅĐžŅ‡Đ˛Đ°ŅŽŅ†Ņ†Đ°", - "integrity_checks_untracked_files_description": "НаĐģадĐļваĐŊĐŊĐĩ Ņ‡Đ°ŅŅ‚Đ°Ņ‚Ņ‹ Ņ– ŅžĐēĐģŅŽŅ‡ŅĐŊĐŊŅ айО адĐēĐģŅŽŅ‡ŅĐŊĐŊŅ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đ°Đē ĐŊĐĩĐ°Đ´ŅĐžŅ‡Đ˛Đ°ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", - "integrity_checks_untracked_files_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ ĐŊĐĩĐ°Đ´ŅĐžŅ‡Đ˛Đ°ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", "job_concurrency": "КоĐģҌĐēĐ°ŅŅ†ŅŒ ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊҋ҅ ĐŋĐ°Ņ‚ĐžĐēĐ°Ņž СадаĐŊĐŊŅ {job}", "job_created": "ЗадаĐŊĐŊĐĩ ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊа", "job_not_concurrency_safe": "Đ“ŅŅ‚Đ° СадаĐŊĐŊĐĩ ĐŊĐĩĐąŅŅĐŋĐĩ҇ĐŊаĐĩ Đ´ĐģŅ ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ.", "job_settings": "НаĐģĐ°Đ´Ņ‹ СадаĐŊĐŊŅŅž", - "job_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ СадаĐŊĐŊŅŅž", + "job_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŊаĐģадаĐŧŅ– ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ СадаĐŊĐŊŅŅž", "jobs_delayed": "{jobCount, plural, other {# адĐēĐģадСĐĩĐŊа}}", "jobs_failed": "{jobCount, plural, other {# ĐŊĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ}}", "jobs_over_time": "Đ“Ņ€Đ°Ņ„Ņ–Đē аĐŋŅ€Đ°Ņ†ĐžŅžĐēŅ–", "library_created": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊа ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēа: {library}", "library_deleted": "Đ‘Ņ–ĐąĐģŅ–ŅŅ‚ŅĐēа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", - "library_details": "ĐŸĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ– ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "library_folder_description": "Đ’Ņ‹ĐˇĐŊĐ°Ņ‡Ņ†Đĩ ĐŋаĐŋĐē҃ Đ´ĐģŅ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ. Đ“ŅŅ‚Đ° ĐŋаĐŋĐēа Ņ€Đ°ĐˇĐ°Đŧ С ĐŋадĐŋаĐŋĐēаĐŧŅ– ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Đ°ŅĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊа ĐŊа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.", + "library_details": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", + "library_folder_description": "Đ’Ņ‹ĐˇĐŊĐ°Ņ‡Ņ†Đĩ ĐŋаĐŋĐē҃ Đ´ĐģŅ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ. Đ“ŅŅ‚Đ° ĐŋаĐŋĐēа, ҃ĐēĐģŅŽŅ‡Đ°ŅŽŅ‡Ņ‹ ĐŋадĐŋаĐŋĐēŅ–, ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Đ°ŅĐēаĐŊаваĐŊа ĐŊа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.", "library_remove_exclusion_pattern_prompt": "Đ’Ņ‹ ҃ĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ?", "library_remove_folder_prompt": "Đ’Ņ‹ ҃ĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņƒ ĐŋаĐŋĐē҃ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ?", - "library_scanning": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đŋа Ņ€Đ°ŅĐēĐģадСĐĩ", - "library_scanning_description": "НаĐģĐ°Đ´Ņ‹ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊĐ°ĐŗĐ° ҁĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "library_scanning_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊаĐĩ ҁĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", + "library_scanning": "ĐĄĐēаĐŊаваĐŊĐŊĐĩ Đŋа Ņ€Đ°ŅĐēĐģадСĐĩ", + "library_scanning_description": "НаĐģĐ°Đ´ĐˇŅŒŅ†Đĩ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ҁĐēаĐŊаваĐŊĐŊŅ Đ˛Đ°ŅˆĐ°Đš ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", + "library_scanning_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊаĐĩ ҁĐēаĐŊаваĐŊĐŊĐĩ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", "library_settings": "ЗĐŊĐĩ҈ĐŊŅŅ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēа", - "library_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– СĐŊĐĩ҈ĐŊŅĐš ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "library_tasks_description": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ СĐŊĐĩ҈ĐŊŅ–Ņ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ– ĐŊа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ… Ņ–/айО СĐŧĐĩĐŊĐĩĐŊҋ҅ Ņ€ŅŅŅƒŅ€ŅĐ°Ņž", + "library_settings_description": "НаĐģĐ°Đ´ĐˇŅŒŅ†Đĩ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ СĐŊĐĩ҈ĐŊŅĐš ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", + "library_tasks_description": "ĐĄĐēаĐŊĐ°Đ˛Đ°Ņ†ŅŒ СĐŊĐĩ҈ĐŊŅ–Ņ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ– ĐŊа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ… Ņ–/айО СĐŧĐĩĐŊĐĩĐŊҋ҅ Ņ€ŅŅŅƒŅ€ŅĐ°Ņž", "library_updated": "Đ‘Ņ–ĐąĐģŅ–ŅŅ‚ŅĐēа айĐŊĐžŅžĐģĐĩĐŊа", - "library_watching_enable_description": "ĐĄĐ°Ņ‡Ņ‹Ņ†ŅŒ Са СĐŧĐĩĐŊаĐŧŅ– Ņ„Đ°ĐšĐģĐ°Ņž ҃ СĐŊĐĩ҈ĐŊŅ–Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēĐ°Ņ…", + "library_watching_enable_description": "ĐĐ°ĐˇŅ–Ņ€Đ°Ņ†ŅŒ Са СĐŧĐĩĐŊаĐŧŅ– Ņ„Đ°ĐšĐģĐ°Ņž ҃ СĐŊĐĩ҈ĐŊŅ–Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēĐ°Ņ…", "library_watching_settings": "[ЭКСПЕРĐĢМЕНĐĸАЛĐŦНА] ĐĄĐ°Ņ‡Ņ‹Ņ†ŅŒ Са ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēаК", - "library_watching_settings_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ŅĐ°Ņ‡Ņ‹Ņ†ŅŒ Са СĐŧŅĐŊĐĩĐŊĐŊŅĐŧŅ– Ņž Ņ„Đ°ĐšĐģĐ°Ņ…", + "library_watching_settings_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ŅĐ°Ņ‡Ņ‹Ņ†ŅŒ Са СĐŧĐĩĐŊаĐŧŅ– Ņž Ņ„Đ°ĐšĐģĐ°Ņ…", "logging_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ˛ŅĐ´ĐˇĐĩĐŊĐŊĐĩ ĐļŅƒŅ€ĐŊаĐģа", "logging_level_description": "КаĐģŅ– ҃ĐēĐģŅŽŅ‡Đ°ĐŊа, ŅĐēŅ– ŅžĐˇŅ€ĐžĐ˛ĐĩĐŊҌ ĐļŅƒŅ€ĐŊаĐģŅĐ˛Đ°ĐŊĐŊŅ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ.", "logging_settings": "Đ’ŅĐ´ĐˇĐĩĐŊĐŊĐĩ ĐļŅƒŅ€ĐŊаĐģа", @@ -159,9 +151,9 @@ "machine_learning_availability_checks_timeout_description": "Đ§Đ°Ņ Ņ‡Đ°ĐēаĐŊĐŊŅ Ņž ĐŧŅ–ĐģҖҁĐĩĐē҃ĐŊĐ´Đ°Ņ… Đ´ĐģŅ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– Đ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°ŅŅ†Ņ–", "machine_learning_clip_model": "CLIP ĐŧĐ°Đ´ŅĐģҌ", "machine_learning_clip_model_description": "Назва CLIP ĐŧĐ°Đ´ŅĐģŅ– ĐŋаĐēаСаĐŊа Ņ‚ŅƒŅ‚. Đ—Đ˛ŅŅ€ĐŊҖ҆Đĩ ŅžĐ˛Đ°ĐŗŅƒ, ŅˆŅ‚Đž ĐŋҀҋ СĐŧĐĩĐŊĐĩ ĐŧĐ°Đ´ŅĐģŅ– ĐŊĐĩĐ°ĐąŅ…ĐžĐ´ĐŊа ĐŋĐ°ŅžŅ‚ĐžŅ€ĐŊа СаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ СадаĐŊĐŊĐĩ \"Smart Search\" Đ´ĐģŅ ŅžŅŅ–Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž.", - "machine_learning_duplicate_detection": "Đ’Ņ‹ŅŅžĐģĐĩĐŊĐŊĐĩ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž", + "machine_learning_duplicate_detection": "Đ’Ņ‹ŅŅžĐģĐĩĐŊĐŊĐĩ ĐŋадОйĐŊҋ҅", "machine_learning_duplicate_detection_enabled": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊĐĩ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž", - "machine_learning_duplicate_detection_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ°ĐąŅĐ°ĐģŅŽŅ‚ĐŊа Ņ–Đ´ŅĐŊ҂ҋ҇ĐŊŅ‹Ņ ĐēĐžĐŋŅ–Ņ– ŅžŅŅ‘ Ņ€ĐžŅžĐŊа ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģŅŅ†Ņ†Đ°.", + "machine_learning_duplicate_detection_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ°ĐąŅĐ°ĐģŅŽŅ‚ĐŊа Ņ–Đ´ŅĐŊ҂ҋ҇ĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹ ŅžŅŅ‘ Ņ€ĐžŅžĐŊа ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°Ņ†Ņ†Đ°.", "machine_learning_duplicate_detection_setting_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩ ŅžĐąŅƒĐ´Đ°Đ˛Đ°ĐŊĐŊŅŅž CLIP Đ´ĐģŅ ĐŋĐžŅˆŅƒĐē҃ вĐĩŅ€Đ°ĐŗĐžĐ´ĐŊҋ҅ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž", "machine_learning_enabled": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ", "machine_learning_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, ҃ҁĐĩ Ņ„ŅƒĐŊĐē҆ҋҖ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ ĐąŅƒĐ´ŅƒŅ†ŅŒ адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ ĐŊĐĩСаĐģĐĩĐļĐŊа ад ĐŊаĐģад ĐŊŅ–ĐļŅĐš.", @@ -172,2105 +164,430 @@ "machine_learning_facial_recognition_setting": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", "machine_learning_facial_recognition_setting_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēĐ°Đ´Đ°Đ˛Đ°Ņ†Ņ†Đ° Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, Ņ– ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ СаĐŋĐ°ŅžĐŊŅŅ†Ņ†Đ° Ņ€Đ°ĐˇĐ´ĐˇĐĩĐģ \"Đ›ŅŽĐ´ĐˇŅ–\" ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊ҆ҋ \"ĐĐŗĐģŅĐ´\".", "machine_learning_max_detection_distance": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ адĐģĐĩĐŗĐģĐ°ŅŅ†ŅŒ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ", - "machine_learning_max_detection_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€ĐžĐˇĐŊŅ–Ņ†Đ° ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°ĐŧŅ–, ŅĐēŅ–Ņ ĐģŅ–Ņ‡Đ°Ņ†Ņ†Đ° Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°ĐŧŅ–, ҁĐēĐģадаĐĩ ад 0,001 да 0,1. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– даСвОĐģŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ йОĐģҌ҈ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да ĐŊŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊҋ҅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅŅž.", - "machine_learning_max_recognition_distance": "ĐŸĐ°Ņ€ĐžĐŗ Ņ€Đ°cĐŋаСĐŊаваĐŊĐŊŅ", - "machine_learning_max_recognition_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊаĐĩ Đ°Đ´Ņ€ĐžĐˇĐŊĐĩĐŊĐŊĐĩ ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Ņ‚Đ˛Đ°Ņ€Đ°ĐŧŅ–, ŅĐēŅ–Ņ ĐŧĐžĐļĐŊа ĐģŅ–Ņ‡Ņ‹Ņ†ŅŒ адĐŊŅ‹Đŧ Ņ‡Đ°ĐģавĐĩĐēаĐŧ (҃ Đ´Ņ‹ŅĐŋаСОĐŊĐĩ ад 0 да 2).ЗĐŊŅ–ĐļŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Ņƒ ĐŧĐžĐļа ĐŋŅ€Đ°Đ´ŅƒŅ…Ņ–ĐģŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš ŅĐē адĐŊĐ°ĐŗĐž Ņ– Ņ‚Đ°ĐŗĐž Đļ Ņ‡Đ°ĐģавĐĩĐēа, а ĐŋĐ°Đ˛Ņ‹ŅˆŅĐŊĐŊĐĩ - ŅĐē Đ´Đ˛ŅƒŅ… Ņ€ĐžĐˇĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš. ĐœĐ°ĐšŅ†Đĩ ĐŊа ŅžĐ˛Đ°ĐˇĐĩ, ŅˆŅ‚Đž ĐŋŅ€Đ°ŅŅ†ĐĩĐš ай'ŅĐ´ĐŊĐ°Ņ†ŅŒ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš, ҇ҋĐŧ ĐŋĐ°Đ´ĐˇŅĐģŅ–Ņ†ŅŒ адĐŊĐ°ĐŗĐž Ņ‡Đ°ĐģавĐĩĐēа ĐŊа Đ´Đ˛Đ°Ņ–Ņ…, Ņ‚Đ°Đŧ҃ Đŋа ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†Ņ– Đ˛Ņ‹ĐąŅ–Ņ€Đ°ĐšŅ†Đĩ ĐŧĐĩĐŊŅˆŅ‹ ĐŋĐ°Ņ€ĐžĐŗ.", - "machine_learning_min_detection_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ", - "machine_learning_min_detection_score_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž (ад 0 да 1). ĐŅ–ĐļŅĐšŅˆĐ°Đĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ даСвОĐģŅ–Ņ†ŅŒ СĐŊĐ°Ņ…ĐžĐ´ĐˇŅ–Ņ†ŅŒ йОĐģҌ҈ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, аĐģĐĩ ĐŧĐžĐļа ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ…Ņ–ĐąĐŊҋ҅ ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", - "machine_learning_min_recognized_faces": "ĐœŅ–ĐŊŅ–Đŧ҃Đŧ Ņ€Đ°cĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", - "machine_learning_min_recognized_faces_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž Đ´ĐģŅ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ Ņ‡Đ°ĐģавĐĩĐēа. ĐŸĐ°Đ˛ŅĐģŅ–Ņ‡ŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Đ° Ņ€ĐžĐąŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž йОĐģҌ҈ даĐēĐģадĐŊŅ‹Đŧ, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° вĐĩŅ€Đ°ĐŗĐžĐ´ĐŊĐ°ŅŅ†ŅŒ Ņ‚Đ°ĐŗĐž, ŅˆŅ‚Đž Ņ‚Đ˛Đ°Ņ€ ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Ņ‹ŅĐ˛ĐžĐĩĐŊŅ‹ Ņ‡Đ°ĐģавĐĩĐē҃.", - "machine_learning_ocr": "Đ Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ (OCR)", - "machine_learning_ocr_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņ…", + "machine_learning_max_detection_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€ĐžĐˇĐŊŅ–Ņ†Đ° ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Đ˛Ņ‹ŅĐ˛Đ°ĐŧŅ–, ŅĐēŅ–Ņ ĐģŅ–Ņ‡Đ°Ņ†Ņ†Đ° Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°ĐŧŅ–, ҁĐēĐģадаĐĩ ад 0,001 да 0,1. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– даСвОĐģŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ йОĐģҌ҈ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да ĐŊŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊҋ҅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅŅž.", + "machine_learning_max_recognition_distance": "ĐŸĐ°Ņ€ĐžĐŗ Ņ€Đ°ĐˇĐŋаСĐŊаваĐŊĐŊŅ", + "machine_learning_max_recognition_distance_description": "МаĐēҁҖĐŧаĐģҌĐŊаĐĩ Đ°Đ´Ņ€ĐžĐˇĐŊĐĩĐŊĐŊĐĩ ĐŋаĐŧŅ–Đļ Đ´Đ˛ŅƒĐŧа Đ°ŅĐžĐąĐ°ĐŧŅ–, ŅĐēŅ–Ņ ĐŧĐžĐļĐŊа ĐģŅ–Ņ‡Ņ‹Ņ†ŅŒ адĐŊŅ‹Đŧ Ņ‡Đ°ĐģавĐĩĐēаĐŧ (҃ Đ´Ņ‹ŅĐŋаСОĐŊĐĩ ад 0 да 2).ЗĐŊŅ–ĐļŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Ņƒ ĐŧĐžĐļа ĐŋŅ€Đ°Đ´ŅƒŅ…Ņ–ĐģŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš ŅĐē адĐŊĐ°ĐŗĐž Ņ– Ņ‚Đ°ĐŗĐž Đļ Ņ‡Đ°ĐģавĐĩĐēа, а ĐŋĐ°Đ˛Ņ‹ŅˆŅĐŊĐŊĐĩ - ŅĐē Đ´Đ˛ŅƒŅ… Ņ€ĐžĐˇĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš. ĐœĐ°ĐšŅ†Đĩ ĐŊа ŅžĐ˛Đ°ĐˇĐĩ, ŅˆŅ‚Đž ĐŋŅ€Đ°ŅŅ†ĐĩĐš ай'ŅĐ´ĐŊĐ°Ņ†ŅŒ Đ´Đ˛ŅƒŅ… ĐģŅŽĐ´ĐˇĐĩĐš, ҇ҋĐŧ ĐŋĐ°Đ´ĐˇŅĐģŅ–Ņ†ŅŒ адĐŊĐ°ĐŗĐž Ņ‡Đ°ĐģавĐĩĐēа ĐŊа Đ´Đ˛Đ°Ņ–Ņ…, Ņ‚Đ°Đŧ҃ Đŋа ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†Ņ– Đ˛Ņ‹ĐąŅ–Ņ€Đ°ĐšŅ†Đĩ ĐŧĐĩĐŊŅˆŅ‹ ĐŋĐ°Ņ€ĐžĐŗ.", + "machine_learning_min_detection_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Ņ€Đ°ĐˇĐŋаСĐŊаваĐŊĐŊŅ", + "machine_learning_min_detection_score_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Đ°ŅĐžĐąŅ‹ (ад 0 да 1). ĐŅ–ĐļŅĐšŅˆĐ°Đĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ даСвОĐģŅ–Ņ†ŅŒ СĐŊĐ°Ņ…ĐžĐ´ĐˇŅ–Ņ†ŅŒ йОĐģҌ҈ Đ°ŅĐžĐą, аĐģĐĩ ĐŧĐžĐļа ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ–ĐģĐļŅ‹Đ˛Ņ‹Ņ… ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", + "machine_learning_min_recognized_faces": "ĐœŅ–ĐŊŅ–Đŧ҃Đŧ Ņ€Đ°ĐˇĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", + "machine_learning_min_recognized_faces_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž Đ´ĐģŅ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ Đ°ŅĐžĐąŅ‹. ĐŸĐ°Đ˛ŅĐģŅ–Ņ‡ŅĐŊĐŊĐĩ ĐŗŅŅ‚Đ°ĐŗĐ° ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Đ° Ņ€ĐžĐąŅ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊĐŊĐĩ Đ°ŅĐžĐą йОĐģҌ҈ даĐēĐģадĐŊŅ‹Đŧ, аĐģĐĩ ĐŋҀҋ ĐŗŅŅ‚Ņ‹Đŧ ĐŋĐ°Đ˛ŅĐģŅ–Ņ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° вĐĩŅ€Đ°ĐŗĐžĐ´ĐŊĐ°ŅŅ†ŅŒ Ņ‚Đ°ĐŗĐž, ŅˆŅ‚Đž Ņ‚Đ˛Đ°Ņ€ ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Ņ‹ŅĐ˛ĐžĐĩĐŊŅ‹ Đ°ŅĐžĐąĐĩ.", + "machine_learning_ocr": "РаСĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ (OCR)", + "machine_learning_ocr_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐšŅ†Đĩ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ĐŊа ĐŧаĐģŅŽĐŊĐēĐ°Ņ…", "machine_learning_ocr_enabled": "Đ”Đ°Đ´Đ°Ņ†ŅŒ OCR", - "machine_learning_ocr_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Ņ‚ŅĐēҁ҂ ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņ… ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ Ņ€Đ°ŅĐŋаСĐŊĐ°Đ˛Đ°Ņ†Ņ†Đ°.", + "machine_learning_ocr_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ‹ŅĐ˛Ņ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊĐ°Đ˛Đ°Ņ†Ņ†Đ° С Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩĐŧ Ņ‚ŅĐēŅŅ‚Ņƒ.", "machine_learning_ocr_max_resolution": "МаĐēҁҖĐŧаĐģҌĐŊĐ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "machine_learning_ocr_max_resolution_description": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ С Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŽ йОĐģҌ҈ ĐŗŅŅ‚Đ°Đš ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋаĐŧĐĩĐŊŅˆĐ°ĐŊŅ‹ С ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ ŅŅƒĐ°Đ´ĐŊĐžŅŅ–ĐŊŅ‹ йаĐēĐžŅž. БоĐģҌ҈ Đ˛Ņ‹ŅĐžĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– ĐŋĐ°Đ˛Ņ‹ŅˆĐ°ŅŽŅ†ŅŒ даĐēĐģадĐŊĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ ĐŊа аĐŋŅ€Đ°Ņ†ĐžŅžĐē҃ Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–.", - "machine_learning_ocr_min_detection_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ", - "machine_learning_ocr_min_detection_score_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ даĐēĐģадĐŊĐ°ŅŅ†Ņ– Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ҁĐēĐģадаĐĩ ад 0 да 1. БоĐģҌ҈ ĐŊŅ–ĐˇĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– даСвОĐģŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ йОĐģҌ҈ Ņ‚ŅĐēŅŅ‚Ņƒ, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ…Ņ–ĐąĐŊҋ҅ ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", - "machine_learning_ocr_min_recognition_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ", - "machine_learning_ocr_min_score_recognition_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐŗ даĐēĐģадĐŊĐ°ŅŅ†Ņ– Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐ°ĐŗĐ° Ņ‚ŅĐēŅŅ‚Ņƒ ҁĐēĐģадаĐĩ ад 0 да 1. БоĐģҌ҈ ĐŊŅ–ĐˇĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– Ņ€Đ°ŅĐŋаСĐŊĐ°ŅŽŅ†ŅŒ йОĐģҌ҈ Ņ‚ŅĐēŅŅ‚Ņƒ, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ…Ņ–ĐąĐŊҋ҅ ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", + "machine_learning_ocr_min_detection_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ йаĐģ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ", + "machine_learning_ocr_min_detection_score_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ йаĐģ давĐĩŅ€Ņƒ Đ´ĐģŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ҁĐēĐģадаĐĩ ад 0 да 1. БоĐģҌ҈ ĐŊŅ–ĐˇĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– даСвОĐģŅŅ†ŅŒ Đ˛Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ йОĐģҌ҈ Ņ‚ŅĐēŅŅ‚Ņƒ, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ…Ņ–ĐąĐŊҋ҅ ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", + "machine_learning_ocr_min_recognition_score": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ йаĐģ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ", + "machine_learning_ocr_min_score_recognition_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊŅ‹ йаĐģ давĐĩŅ€Ņƒ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Đ˛Ņ‹ŅŅžĐģĐĩĐŊĐ°ĐŗĐ° Ņ‚ŅĐēŅŅ‚Ņƒ ҁĐēĐģадаĐĩ ад 0 да 1. БоĐģҌ҈ ĐŊŅ–ĐˇĐēŅ–Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– Ņ€Đ°ŅĐŋаСĐŊĐ°ŅŽŅ†ŅŒ йОĐģҌ҈ Ņ‚ŅĐēŅŅ‚Ņƒ, аĐģĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋŅ€Ņ‹Đ˛Đĩҁ҆Җ да Ņ…Ņ–ĐąĐŊҋ҅ ҁĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ĐŊĐŊŅŅž.", "machine_learning_ocr_model": "ĐœĐ°Đ´ŅĐģҌ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ (OCR)", - "machine_learning_ocr_model_description": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ĐŊŅ‹Ņ ĐŧĐ°Đ´ŅĐģŅ– йОĐģҌ҈ даĐēĐģадĐŊŅ‹Ņ, ҇ҋĐŧ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅ‹Ņ, аĐģĐĩ аĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ даĐŊŅ‹Ņ Đ´Đ°ŅžĐļŅĐš Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–.", + "machine_learning_ocr_model_description": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ĐŊŅ‹Ņ ĐŧĐ°Đ´ŅĐģŅ– йОĐģҌ҈ даĐēĐģадĐŊŅ‹Ņ, ҇ҋĐŧ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅ‹Ņ, аĐģĐĩ аĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ дадСĐĩĐŊŅ‹Ņ Đ´Đ°ŅžĐļŅĐš Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ йОĐģҌ҈ ĐŋаĐŧŅŅ†Ņ–.", "machine_learning_settings": "НаĐģĐ°Đ´Ņ‹ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ", - "machine_learning_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Ņ„ŅƒĐŊĐēŅ†Ņ‹ŅĐŧŅ– Ņ– ĐŊаĐģадаĐŧŅ– ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ", - "machine_learning_smart_search": "Đ Đ°ĐˇŅƒĐŧĐŊŅ‹ ĐŋĐžŅˆŅƒĐē", - "machine_learning_smart_search_description": "ĐĄĐĩĐŧаĐŊ҂ҋ҇ĐŊŅ‹ ĐŋĐžŅˆŅƒĐē Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž С Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩĐŧ ŅƒĐąŅƒĐ´Đ°Đ˛Đ°ĐŊĐŊŅŅž CLIP", - "machine_learning_smart_search_enabled": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€Đ°ĐˇŅƒĐŧĐŊŅ‹ ĐŋĐžŅˆŅƒĐē", - "machine_learning_smart_search_enabled_description": "КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēĐ°Đ´ĐˇŅ–Ņ€Đ°Đ˛Đ°Ņ†Ņ†Đ° Đ´ĐģŅ Ņ€Đ°ĐˇŅƒĐŧĐŊĐ°ĐŗĐ° ĐŋĐžŅˆŅƒĐē҃.", - "machine_learning_url_description": "URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ. КаĐģŅ– ĐŋаСĐŊĐ°Ņ‡Đ°ĐŊа йОĐģҌ҈ Са Đ°Đ´ĐˇŅ–ĐŊ URL-Đ°Đ´Ņ€Đ°Ņ, ҁĐŋŅ€ĐžĐąŅ‹ ĐŋадĐēĐģŅŽŅ‡ŅĐŊĐŊŅ да ĐēĐžĐļĐŊĐ°ĐŗĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹ĐēĐžĐŊĐ˛Đ°Ņ†Ņ†Đ° Đŋа Ņ‡Đ°Ņ€ĐˇĐĩ, ĐŋаĐē҃ĐģҌ Đ°Đ´ĐˇŅ–ĐŊ С Ņ–Ņ… ĐŊĐĩ адĐēаĐļа ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ°, Đŋа ĐŋĐ°Ņ€Đ°Đ´Đē҃ ад ĐŋĐĩŅ€ŅˆĐ°ĐŗĐ° да аĐŋĐžŅˆĐŊŅĐŗĐ°. ĐĄĐĩŅ€Đ˛ĐĩҀҋ, ŅĐēŅ–Ņ ĐŊĐĩ адĐēĐ°ĐˇĐ˛Đ°ŅŽŅ†ŅŒ, ĐąŅƒĐ´ŅƒŅ†ŅŒ Ņ‡Đ°ŅĐžĐ˛Đ° Ņ–ĐŗĐŊĐ°Ņ€Đ°Đ˛Đ°Ņ†Ņ†Đ°, ĐŋаĐē҃ĐģҌ ŅĐŊŅ‹ ĐŊĐĩ вĐĩŅ€ĐŊŅƒŅ†Ņ†Đ° Ņž ҁĐĩŅ‚Đē҃.", - "maintenance_backup_management": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Ņ‹Đŧ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩĐŧ", - "maintenance_delete_backup": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ", - "maintenance_delete_backup_description": "Đ“ŅŅ‚Ņ‹ Ņ„Đ°ĐšĐģ ĐąŅƒĐ´ĐˇĐĩ ĐąĐĩĐˇĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹.", - "maintenance_delete_error": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ.", - "maintenance_integrity_check": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ", - "maintenance_integrity_check_all": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ ҃ҁĐĩ", - "maintenance_integrity_checksum_mismatch": "НĐĩҁ҃ĐŋадСĐĩĐŊĐŊĐĩ ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧŅ‹", - "maintenance_integrity_checksum_mismatch_description": "ФаКĐģŅ‹, ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃Đŧа ŅĐēŅ–Ņ… ĐŊа Đ´Ņ‹ŅĐē҃ ĐŊĐĩ ҁ҃ĐŋадаĐĩ С ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧаК, ŅĐēŅƒŅŽ Immich ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°Đĩ Ņž ŅĐ˛Đ°Ņ‘Đš йаСĐĩ даĐŊҋ҅.", - "maintenance_integrity_checksum_mismatch_job": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа ĐŊа ĐŊĐĩҁ҃ĐŋадСĐĩĐŊĐŊĐĩ ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃Đŧ", - "maintenance_integrity_checksum_mismatch_refresh_job": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҁĐŋŅ€Đ°Đ˛Đ°ĐˇĐ´Đ°Ņ‡Ņ‹ ай ĐŊĐĩҁ҃ĐŋадСĐĩĐŊĐŊŅ– ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃Đŧ", - "maintenance_integrity_missing_file": "ĐĐ´ŅŅƒŅ‚ĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_integrity_missing_file_description": "ФаКĐģŅ‹, ŅĐēŅ–Ņ Ņ‘ŅŅ†ŅŒ ҃ йаСĐĩ даĐŊҋ҅ Immich, аĐģĐĩ ĐŊĐĩ ҖҁĐŊŅƒŅŽŅ†ŅŒ ҃ Ņ„Đ°ĐšĐģаваК ŅŅ–ŅŅ‚ŅĐŧĐĩ.", - "maintenance_integrity_missing_file_job": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†Ņ– Đ°Đ´ŅŅƒŅ‚ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", - "maintenance_integrity_missing_file_refresh_job": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҁĐŋŅ€Đ°Đ˛Đ°ĐˇĐ´Đ°Ņ‡Ņ‹ ай Đ°Đ´ŅŅƒŅ‚ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņ…", - "maintenance_integrity_report": "ĐĄĐŋŅ€Đ°Đ˛Đ°ĐˇĐ´Đ°Ņ‡Đ° ай Ņ†ŅĐģĐ°ŅĐŊĐ°ŅŅ†Ņ–", - "maintenance_integrity_untracked_file": "НĐĩĐ°Đ´ŅĐžŅ‡Đ˛Đ°ĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_integrity_untracked_file_description": "ФаКĐģŅ‹ Ņž ĐēĐ°Ņ‚Đ°ĐģĐžĐŗĐ°Ņ… Immich, ĐŋŅ€Đ° ŅĐēŅ–Ņ Ņž Immich ĐŊŅĐŧа ĐŊŅ–ŅĐēŅ–Ņ… СаĐŋŅ–ŅĐ°Ņž.", - "maintenance_integrity_untracked_file_job": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ ĐŊĐĩĐ°Đ´ŅĐžŅ‡Đ˛Đ°ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņž", - "maintenance_integrity_untracked_file_refresh_job": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҁĐŋŅ€Đ°Đ˛Đ°ĐˇĐ´Đ°Ņ‡Ņ‹ ай ĐŊĐĩĐ°Đ´ŅĐžŅ‡Đ˛Đ°ĐŊҋ҅ Ņ„Đ°ĐšĐģĐ°Ņ…", - "maintenance_restore_backup": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ", - "maintenance_restore_backup_description": "Immich ĐąŅƒĐ´ĐˇĐĩ ҁ҆ґҀ҂ҋ Ņ– адĐŊĐžŅžĐģĐĩĐŊŅ‹ С Đ°ĐąŅ€Đ°ĐŊаК Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–. ПĐĩŅ€Đ°Đ´ ĐŋŅ€Đ°Ņ†ŅĐŗĐ°Đŧ ĐąŅƒĐ´ĐˇĐĩ ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊа Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Ņ ĐēĐžĐŋŅ–Ņ.", - "maintenance_restore_backup_different_version": "Đ“ŅŅ‚Đ°Ņ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Ņ ĐēĐžĐŋŅ–Ņ ĐąŅ‹Đģа ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊа С Ņ–ĐŊŅˆĐ°Đš вĐĩҀҁҖҖ Immich!", - "maintenance_restore_backup_unknown_version": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ вĐĩŅ€ŅŅ–ŅŽ.", - "maintenance_restore_database_backup": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "maintenance_restore_database_backup_description": "АдĐēĐ°Ņ‚ да ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ° ŅŅ‚Đ°ĐŊ҃ ĐąĐ°ĐˇŅ‹ даĐŊҋ҅ С Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩĐŧ Ņ„Đ°ĐšĐģа Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–", - "maintenance_settings": "ĐĸŅŅ…ĐŊҖ҇ĐŊаĐĩ Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊĐĩ", - "maintenance_settings_description": "ПĐĩŅ€Đ°Đ˛ŅĐ´ĐˇŅ–Ņ†Đĩ Immich y Ņ€ŅĐļŅ‹Đŧ Ņ‚ŅŅ…ĐŊҖ҇ĐŊĐ°ĐŗĐ° Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊŅ.", - "maintenance_start": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– Ņž Ņ€ŅĐļŅ‹Đŧ Ņ‚ŅŅ…ĐŊҖ҇ĐŊĐ°ĐŗĐ° Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "maintenance_start_error": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ СаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ Ņ€ŅĐļŅ‹Đŧ Ņ‚ŅŅ…ĐŊҖ҇ĐŊĐ°ĐŗĐ° Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊŅ.", - "maintenance_upload_backup": "Đ—Đ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ Ņ„Đ°ĐšĐģ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ– ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "maintenance_upload_backup_error": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ СаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛ŅƒŅŽ ĐēĐžĐŋŅ–ŅŽ, ҆Җ ĐŧаĐĩ Ņ„Đ°ĐšĐģ ĐŋĐ°ŅˆŅ‹Ņ€ŅĐŊĐŊĐĩ .sql/.sql.gz?", - "manage_concurrency": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊŅ‹Đŧ Đ˛Ņ‹ĐēаĐŊаĐŊĐŊĐĩĐŧ", - "manage_concurrency_description": "ПĐĩŅ€Đ°ĐšĐ´ĐˇŅ–Ņ†Đĩ ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊĐē҃ СадаĐŊĐŊŅŅž, Đēай ĐēŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊŅ‹Đŧ Đ˛Ņ‹ĐēаĐŊаĐŊĐŊĐĩĐŧ", - "manage_log_settings": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐļŅƒŅ€ĐŊаĐģа", "map_dark_style": "ĐĻŅ‘ĐŧĐŊŅ‹ ҁ҂ҋĐģҌ", "map_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ„ŅƒĐŊĐē҆ҋҖ ĐēĐ°Ņ€Ņ‚Ņ‹", "map_gps_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‚Ņ‹ Ņ– GPS", - "map_gps_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐēĐ°Ņ€Ņ‚Ņ‹ Ņ– GPS (Đ—Đ˛Đ°Ņ€ĐžŅ‚ĐŊаĐĩ ĐŗĐĩаĐēадаваĐŊĐŊĐĩ)", - "map_implications": "Đ¤ŅƒĐŊĐēŅ†Ņ‹Ņ ĐēĐ°Ņ€Ņ‚Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ СĐŊĐĩ҈ĐŊŅ– ŅŅŅ€Đ˛Ņ–Ņ Ņ‚Đ°ĐšĐģĐ°Ņž (tiles.immich.cloud)", "map_light_style": "ХвĐĩŅ‚ĐģŅ‹ ҁ҂ҋĐģҌ", - "map_manage_reverse_geocoding_settings": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– Đ—Đ˛Đ°Ņ€ĐžŅ‚ĐŊĐ°ĐŗĐ° ĐŗĐĩаĐēадаваĐŊĐŊŅ", - "map_reverse_geocoding": "Đ—Đ˛Đ°Ņ€ĐžŅ‚ĐŊаĐĩ ĐŗĐĩаĐēадаваĐŊĐŊĐĩ", - "map_reverse_geocoding_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊаĐĩ ĐŗĐĩаĐēадаваĐŊĐŊĐĩ", - "map_reverse_geocoding_settings": "НаĐģĐ°Đ´Ņ‹ ĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊĐ°ĐŗĐ° ĐŗĐĩаĐēадаваĐŊĐŊŅ", "map_settings": "ĐšĐ°Ņ€Ņ‚Đ°", "map_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐēĐ°Ņ€Ņ‚Ņ‹", "map_style_description": "URL-Đ°Đ´Ņ€Đ°Ņ style.json Ņ‚ŅĐŧŅ‹ ĐēĐ°Ņ€Ņ‚Ņ‹", - "memory_cleanup_job": "ĐŅ‡Ņ‹ŅŅ‚Đēа ŅžŅĐŋаĐŧŅ–ĐŊĐ°Ņž", - "memory_generate_job": "ГĐĩĐŊĐĩŅ€Đ°Ņ†Ņ‹Ņ ŅžŅĐŋаĐŧŅ–ĐŊĐ°Ņž", - "metadata_extraction_job": "Đ’Ņ‹ĐŊŅŅ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ", - "metadata_extraction_job_description": "Đ’Ņ‹ĐŊŅŅ†ŅŒ С Ņ„Đ°ĐšĐģĐ°Ņž ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ, Ņ‚Đ°ĐēŅ–Ņ ŅĐē ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, Ņ‚Đ˛Đ°Ņ€Ņ‹ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", - "metadata_faces_import_setting": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ–ĐŧĐŋĐ°Ņ€Ņ‚ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", - "metadata_faces_import_setting_description": "ІĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹ С EXIF-даĐŊҋ҅ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž Ņ– sidecar Ņ„Đ°ĐšĐģĐ°Ņž", + "metadata_extraction_job_description": "Đ’Ņ‹ĐŊŅŅ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ С Ņ„Đ°ĐšĐģĐ°Ņž, Ņ‚Đ°ĐēŅ–Ņ ŅĐē ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, Ņ‚Đ˛Đ°Ņ€Ņ‹ Ņ– Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "metadata_settings": "НаĐģĐ°Đ´Ņ‹ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅", - "metadata_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅", - "migration_job": "ĐœŅ–ĐŗŅ€Đ°Ņ†Ņ‹Ņ", - "migration_job_description": "ПĐĩŅ€Đ°ĐŊĐ°Ņ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ ай'ĐĩĐēŅ‚Đ°Ņž Ņ– Ņ‚Đ˛Đ°Ņ€Đ°Ņž Ņž аĐēŅ‚ŅƒĐ°ĐģҌĐŊŅƒŅŽ ŅŅ‚Ņ€ŅƒĐēŅ‚ŅƒŅ€Ņƒ ĐŋаĐŋаĐē", - "nightly_tasks_cluster_faces_setting_description": "ЗаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ ĐģŅŽĐ´ĐˇĐĩĐš Đŋа ĐŊŅĐ´Đ°ŅžĐŊа Đ˛Ņ‹ŅŅžĐģĐĩĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņ…", - "nightly_tasks_cluster_new_faces_setting": "Đ—ĐŗŅ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ Ņ‚Đ˛Đ°Ņ€Ņ‹", - "nightly_tasks_database_cleanup_setting": "ĐŅ‡Ņ‹ŅŅ‚Đēа ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "nightly_tasks_database_cleanup_setting_description": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ŅŅ‚Đ°Ņ€Ņ‹Ņ, ĐŋŅ€Đ°Ņ‚ŅŅ€ĐŧŅ–ĐŊаваĐŊŅ‹Ņ даĐŊŅ‹Ņ С ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "nightly_tasks_generate_memories_setting": "Đ—ĐŗĐĩĐŊĐĩŅ€Đ°Đ˛Đ°Ņ†ŅŒ ҃ҁĐŋаĐŧŅ–ĐŊ", - "nightly_tasks_generate_memories_setting_description": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹ ŅžŅĐŋаĐŧŅ–ĐŊ С ай'ĐĩĐēŅ‚Đ°Ņž", - "nightly_tasks_missing_thumbnails_setting": "Đ—ĐŗĐĩĐŊĐĩŅ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊŅ‹Ņ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹", - "nightly_tasks_missing_thumbnails_setting_description": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ҃ Ņ‡Đ°Ņ€ĐŗŅƒ ай'ĐĩĐē҂ҋ ĐąĐĩС ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ Đ´ĐģŅ Ņ–Ņ… ĐŗĐĩĐŊĐĩŅ€Đ°Ņ†Ņ‹Ņ–", - "nightly_tasks_settings": "НаĐģĐ°Đ´Ņ‹ ĐŊĐ°Ņ‡ĐŊҋ҅ СадаĐŊĐŊŅŅž", - "nightly_tasks_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊĐ°Ņ‡ĐŊŅ‹ĐŧŅ– СадаĐŊĐŊŅĐŧŅ–", - "nightly_tasks_start_time_setting": "Đ§Đ°Ņ ĐŋĐ°Ņ‡Đ°Ņ‚Đē҃", - "nightly_tasks_start_time_setting_description": "Đ§Đ°Ņ, ҃ ŅĐēŅ– ҁĐĩŅ€Đ˛ĐĩŅ€ ĐŋĐ°Ņ‡Ņ‹ĐŊаĐĩ Đ˛Ņ‹ĐēĐžĐŊĐ˛Đ°Ņ†ŅŒ ĐŊĐ°Ņ‡ĐŊŅ‹Ņ СадаĐŊĐŊŅ–", - "nightly_tasks_sync_quota_usage_setting": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ ĐēĐ˛ĐžŅ‚Ņ‹", - "nightly_tasks_sync_quota_usage_setting_description": "АйĐŊĐ°ŅžĐģŅŅ†ŅŒ ĐēĐ˛ĐžŅ‚Ņƒ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа ĐŊа Đ°ŅĐŊОвĐĩ ĐąŅĐŗŅƒŅ‡Đ°ĐŗĐ° Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ", - "no_paths_added": "НĐĩ дададСĐĩĐŊа аĐŊŅ–Đ˛ĐžĐ´ĐŊĐ°ĐŗĐ° ҈ĐģŅŅ…Ņƒ", - "no_pattern_added": "НĐĩ дададСĐĩĐŊа аĐŊŅ–Đ˛ĐžĐ´ĐŊĐ°ĐŗĐ° ĐŋĐ°Ņ‚ŅŅ€ĐŊ҃", - "note_apply_storage_label_previous_assets": "Đ—Đ°ŅžĐ˛Đ°ĐŗĐ°: Каб ҃ĐļŅ‹Ņ†ŅŒ ĐœĐ°Ņ€ĐēŅ–Ņ€ĐžŅžĐē҃ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° да Ņ€Đ°ĐŊĐĩĐš СаĐŋаĐŧĐŋаваĐŊҋ҅ ай'ĐĩĐēŅ‚Đ°Ņž, СаĐŋŅƒŅŅ†Ņ–Ņ†Đĩ", - "note_cannot_be_changed_later": "ĐŖĐ’ĐĐ“Đ: Đ“ŅŅ‚Đ° ĐŊĐĩĐģŅŒĐŗĐ° ĐąŅƒĐ´ĐˇĐĩ СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋаСĐŊĐĩĐš!", - "notification_email_from_address": "З Đ°Đ´Ņ€Đ°ŅĐžŅž", - "notification_email_from_address_description": "Đ­ĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹ Đ°Đ´Ņ€Đ°Ņ адĐŋŅ€Đ°ŅžĐŊŅ–Đēа, Đ´ĐģŅ ĐŋҀҋĐēĐģĐ°Đ´Ņƒ: \"Immich Photo Server \". ПĐĩŅ€Đ°ĐēаĐŊĐ°ĐšŅ†ĐĩŅŅ, ŅˆŅ‚Đž Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩĐĩŅ†Đ° Đ°Đ´Ņ€Đ°Ņ, ŅĐēĐžĐŧ҃ даСвОĐģĐĩĐŊа адĐŋŅ€Đ°ŅžĐģŅŅ†ŅŒ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹Ņ ĐģҖҁ҂ҋ.", - "notification_email_host_description": "ĐĐ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ‚Ņ‹ (ŅƒĐˇĐžŅ€: smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Đ†ĐŗĐŊĐ°Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋаĐŧŅ‹ĐģĐēŅ– ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņž", - "notification_email_ignore_certificate_errors_description": "Đ†ĐŗĐŊĐ°Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋаĐŧŅ‹ĐģĐēŅ– ĐˇĐ˛ŅĐˇĐ°ĐŊŅ‹Ņ С ваĐģŅ–Đ´Đ°Ņ†Ņ‹ŅĐš TLS ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņž (ĐŊĐĩ Ņ€ŅĐēаĐŧĐĩĐŊĐ´ŅƒĐĩŅ†Ņ†Đ°)", - "notification_email_password_description": "ĐŸĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ– ĐŊа ĐŋĐ°ŅˆŅ‚ĐžĐ˛Ņ‹Đŧ ҁĐĩŅ€Đ˛ĐĩҀҋ", "notification_email_port_description": "ĐŸĐžŅ€Ņ‚ ĐŋĐ°ŅˆŅ‚ĐžĐ˛Đ°ĐŗĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ° (ĐŊаĐŋҀҋĐēĐģад, 25, 465 айО 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ SMTPS (SMTP С Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩĐŧ TLS)", - "notification_email_sent_test_email_button": "АдĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ Ņ‚ŅŅŅ‚Đ°Đ˛Ņ‹ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹ ĐģҖҁ҂ Ņ– ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ", - "notification_email_setting_description": "НаĐģĐ°Đ´Ņ‹ адĐŋŅ€Đ°ŅžĐēŅ– аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž Đŋа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ†Đĩ", - "notification_email_test_email": "АдĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ Ņ‚ŅŅŅ‚Đ°Đ˛Ņ‹ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹ ĐģҖҁ҂", - "notification_email_test_email_failed": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ адĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ Ņ‚ŅŅŅ‚Đ°Đ˛Ņ‹ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹ ĐģҖҁ҂, ҁĐŋŅ€Đ°ŅžĐ´ĐˇŅ–Ņ†Đĩ Đ˛Đ°ŅˆŅ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ–", - "notification_email_test_email_sent": "ĐĸŅŅŅ‚Đ°Đ˛Ņ‹ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅ‹ ĐģҖҁ҂ адĐŋŅ€Đ°ŅžĐģĐĩĐŊŅ‹ Đŋа Đ°Đ´Ņ€Đ°ŅĐĩ {email}. ĐŸŅ€Đ°Đ˛ĐĩҀ҆Đĩ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅˆŅ‚Ņƒ.", - "notification_email_username_description": "ІĐŧŅ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Đ´ĐģŅ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ– ĐŊа ĐŋĐ°ŅˆŅ‚ĐžĐ˛Ņ‹Đŧ ҁĐĩŅ€Đ˛ĐĩҀҋ", - "notification_enable_email_notifications": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– Đŋа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ†Đĩ", - "notification_settings": "НаĐģĐ°Đ´Ņ‹ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž", - "notification_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž, ҃ ҂ҋĐŧ ĐģŅ–Đē҃ Đŋа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ†Đĩ", - "oauth_allow_insecure_requests": "ДазвоĐģŅ–Ņ†ŅŒ ĐŊĐĩĐ°ĐąĐ°Ņ€ĐžĐŊĐĩĐŊŅ‹Ņ СаĐŋҋ҂ҋ", - "oauth_allow_insecure_requests_description": "ĐŖĐ’ĐĐ“Đ: Đ“ŅŅ‚Đ° Đ˛Ņ‹ĐēĐģŅŽŅ‡Đ°Đĩ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ TLS-ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņž Đ´ĐģŅ OAuth-СаĐŋŅ‹Ņ‚Đ°Ņž Ņ– ĐŧĐžĐļа ĐˇŅ€Đ°ĐąŅ–Ņ†ŅŒ Đ˛Đ°Ņ ŅƒŅ€Đ°ĐˇĐģŅ–Đ˛Ņ‹Đŧ да Đ°Ņ‚Đ°Đē MITM (\"ЧаĐģавĐĩĐē ĐŋĐ°ŅŅŅ€ŅĐ´ĐˇŅ–ĐŊĐĩ\").", - "oauth_auto_launch": "ĐŅžŅ‚Đ°ĐˇĐ°Đŋ҃ҁĐē", - "oauth_auto_launch_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ĐŋĐ°Ņ‡Ņ‹ĐŊĐ°Ņ†ŅŒ ŅƒĐ˛Đ°Ņ…ĐžĐ´ ĐŋŅ€Đ°Đˇ OAuth ĐŋҀҋ ĐŋĐĩŅ€Đ°Ņ…ĐžĐ´ĐˇĐĩ ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊĐē҃ ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ", - "oauth_auto_register": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊĐ°Ņ Ņ€ŅĐŗŅ–ŅŅ‚Ņ€Đ°Ņ†Ņ‹Ņ", - "oauth_auto_register_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа Ņ€ŅĐŗŅ–ŅŅ‚Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ… ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž ĐŋĐ°ŅĐģŅ ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ ĐŋŅ€Đ°Đˇ OAuth", "oauth_button_text": "ĐĸŅĐēҁ҂ ĐēĐŊĐžĐŋĐēŅ–", - "oauth_client_secret_description": "ĐĐąĐ°Đ˛ŅĐˇĐēОва Đ´ĐģŅ ĐēаĐŊŅ„Ņ–Đ´ŅĐŊŅ†Ņ‹ŅĐģҌĐŊĐ°ĐŗĐ° ĐēĐģŅ–ĐĩĐŊŅ‚Đ°, айО ĐēаĐģŅ– PKCE (Proof Key for Code Exchange) ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ° Đ´ĐģŅ ĐŋŅƒĐąĐģҖ҇ĐŊĐ°ĐŗĐ° ĐēĐģŅ–ĐĩĐŊŅ‚Đ°.", - "oauth_enable_description": "ĐŖĐ˛Đ°ĐšŅŅ†Ņ– ĐŋŅ€Đ°Đˇ OAuth", - "oauth_end_session_url_description": "ПĐĩŅ€Đ°ĐŊаĐēŅ–Ņ€ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа ĐŊа ĐŗŅŅ‚Ņ‹ URI ĐŋĐ°ŅĐģŅ Đ˛Ņ‹Ņ…Đ°Đ´Ņƒ С ŅŅ–ŅŅ‚ŅĐŧŅ‹.", - "oauth_mobile_redirect_uri": "URI ĐŧĐ°ĐąŅ–ĐģҌĐŊĐ°ĐŗĐ° ĐŋĐĩŅ€Đ°ĐŊаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "oauth_mobile_redirect_uri_override": "ПĐĩŅ€Đ°Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ URI ĐŧĐ°ĐąŅ–ĐģҌĐŊĐ°ĐŗĐ° ĐŋĐĩŅ€Đ°ĐŊаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "oauth_mobile_redirect_uri_override_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ, ĐēаĐģŅ– OAuth-ĐŋŅ€Đ°Đ˛Đ°ĐšĐ´Đ°Ņ€ ĐŊĐĩ даСваĐģŅĐĩ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅ‹ URI, ŅĐē ''{callback}''", - "oauth_prompt_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ prompt (ĐŊаĐŋŅ€. select_account, login, consent)", - "oauth_role_claim": "Claim Ņ€ĐžĐģŅ–", - "oauth_role_claim_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа Đ˛Ņ‹Đ´Đ°Đ˛Đ°Ņ†ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ° ĐŊа Đ°ŅĐŊОвĐĩ ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†Ņ– ĐŗŅŅ‚Đ°ĐŗĐ° claim. Claim ĐŧĐžĐļа ĐŧĐĩŅ†ŅŒ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ 'user' айО 'admin'.", "oauth_settings": "OAuth", - "oauth_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ ĐŋŅ€Đ°Đˇ OAuth", - "oauth_settings_more_details": "ДĐģŅ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐŊĐŊŅ ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊаК Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ– ĐŋŅ€Đ° ĐŗŅŅ‚ŅƒŅŽ Ņ„ŅƒĐŊĐēŅ†Ņ‹ŅŽ, ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да даĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ–.", - "oauth_storage_label_claim": "Storage label claim", - "oauth_storage_label_claim_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ĐˇĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ Storage Label ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа ŅĐ° СĐŊĐ°Ņ‡ŅĐŊĐŊŅ ĐŗŅŅ‚Đ°ĐŗĐ° claim.", - "oauth_storage_quota_claim": "Storage quota claim", - "oauth_storage_quota_claim_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ĐˇĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ Storage Quota ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа С СĐŊĐ°Ņ‡ŅĐŊĐŊŅ ĐŗŅŅ‚Đ°ĐŗĐ° claim.", - "oauth_storage_quota_default": "ĐŸŅ€Đ°Đ´Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊĐ°Ņ ĐēĐ˛ĐžŅ‚Đ° ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° (Đ“Ņ–Đ‘)", - "oauth_storage_quota_default_description": "ĐšĐ˛ĐžŅ‚Đ° Ņž Đ“Ņ–Đ‘, ŅĐēĐ°Ņ ĐąŅƒĐ´ĐˇĐĩ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†Ņ†Đ°, ĐēаĐģŅ– claim ĐŊĐĩ ĐŋададСĐĩĐŊŅ‹.", - "oauth_timeout": "ĐĸаКĐŧ-Đ°ŅžŅ‚ СаĐŋŅ‹Ņ‚Ņƒ", - "oauth_timeout_description": "ĐĸаКĐŧ-Đ°ŅžŅ‚ СаĐŋŅ‹Ņ‚Đ°Ņž ҃ ĐŧŅ–ĐģҖҁĐĩĐē҃ĐŊĐ´Đ°Ņ…", - "ocr_job_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŧĐ°ŅˆŅ‹ĐŊĐŊĐ°ĐŗĐ° ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊŅ Đ´ĐģŅ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ Ņ‚ŅĐēŅŅ‚Ņƒ ĐŊа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņ…", - "password_enable_description": "ĐŖĐ˛Đ°Ņ…ĐžĐ´ С даĐŋаĐŧĐžĐŗĐ°Đš ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ‚Ņ‹ Ņ– ĐŋĐ°Ņ€ĐžĐģŅ", - "password_settings": "ĐŖĐ˛Đ°Ņ…ĐžĐ´ Đŋа ĐŋĐ°Ņ€ĐžĐģŅ–", - "password_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ Đŋа ĐŋĐ°Ņ€ĐžĐģŅ–", - "paths_validated_successfully": "ĐŖŅĐĩ ҈ĐģŅŅ…Ņ– ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ĐŋŅ€Đ°Đ˛ĐĩŅ€Đ°ĐŊŅ‹Ņ", - "person_cleanup_job": "ĐŅ‡Ņ‹ŅŅ‚Đēа даĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš", - "queue_details": "ЗвĐĩҁ҂ĐēŅ– ĐŋŅ€Đ° Ņ‡Đ°Ņ€ĐŗŅƒ", - "queues": "Đ§ŅŅ€ĐŗŅ– СадаĐŊĐŊŅŅž", - "queues_page_description": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа Ņ‡ŅŅ€ĐŗĐ°Ņž СадаĐŊĐŊŅŅž адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", - "quota_size_gib": "ПаĐŧĐĩŅ€ ĐēĐ˛ĐžŅ‚Ņ‹ (Đ“Ņ–Đ‘)", "refreshing_all_libraries": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ŅžŅŅ–Ņ… ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē", "registration": "Đ ŅĐŗŅ–ŅŅ‚Ņ€Đ°Ņ†Ņ‹Ņ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", "registration_description": "Đ’Ņ‹ С'ŅŅžĐģŅĐĩ҆ĐĩŅŅ ĐŋĐĩŅ€ŅˆŅ‹Đŧ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧ ŅŅ–ŅŅ‚ŅĐŧŅ‹, Ņ‚Đ°Đŧ҃ Đ˛Ņ‹ ĐąŅƒĐ´ĐˇĐĩ҆Đĩ ĐŋŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊŅ‹ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°Đŧ. Đ’Ņ‹ ĐąŅƒĐ´ĐˇĐĩ҆Đĩ адĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Са адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ†Ņ‹ĐšĐŊŅ‹Ņ ĐˇĐ°Đ´Đ°Ņ‡Ņ‹, а Ņ‚Đ°ĐēŅĐ°Đŧа ŅŅ‚Đ˛Đ°Ņ€Đ°Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ… ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž.", - "release_channel_release_candidate": "КаĐŊĐ´Ņ‹Đ´Đ°Ņ‚ ĐŊа Ņ€ŅĐģŅ–Đˇ", - "release_channel_stable": "ĐĄŅ‚Đ°ĐąŅ–ĐģҌĐŊŅ‹", - "remove_failed_jobs": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŊŅŅžĐ´Đ°ĐģŅ‹Ņ СадаĐŊĐŊŅ–", "require_password_change_on_login": "ĐŸĐ°Ņ‚Ņ€Đ°ĐąĐ°Đ˛Đ°Ņ†ŅŒ СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ ĐŋҀҋ ĐŋĐĩŅ€ŅˆŅ‹Đŧ ŅƒĐ˛Đ°Ņ…ĐžĐ´ĐˇĐĩ Ņž ŅŅ–ŅŅ‚ŅĐŧ҃", "reset_settings_to_default": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹ да ĐŋŅ€Đ°Đ´Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊҋ҅", "reset_settings_to_recent_saved": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹ да ĐŊŅĐ´Đ°ŅžĐŊа ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊҋ҅", "scanning_library": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "search_jobs": "ĐŸĐžŅˆŅƒĐē СадаĐŊĐŊŅŅžâ€Ļ", - "send_welcome_email": "Đ”Đ°ŅĐģĐ°Ņ†ŅŒ Đ˛Ņ–Ņ‚Đ°ĐģҌĐŊŅ‹ ĐģҖҁ҂", "server_external_domain_settings": "ЗĐŊĐĩ҈ĐŊŅ– даĐŧĐĩĐŊ", - "server_external_domain_settings_description": "ДаĐŧĐĩĐŊ, ŅĐēŅ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Đ´ĐģŅ СĐŊĐĩ҈ĐŊŅ–Ņ… ҁĐŋĐ°ŅŅ‹ĐģаĐē", - "server_public_users": "ĐŸŅƒĐąĐģҖ҇ĐŊŅ‹Ņ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ–", - "server_public_users_description": "ĐŖŅĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ– (Ņ–ĐŧŅ Ņ– ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐ°Ņ ĐŋĐžŅˆŅ‚Đ°) ĐŋаĐēĐ°ĐˇĐ˛Đ°ŅŽŅ†Ņ†Đ° ĐŋҀҋ дадаваĐŊĐŊŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņž Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ аĐģŅŒĐąĐžĐŧŅ‹. КаĐģŅ– адĐēĐģŅŽŅ‡Đ°ĐŊа, ҁĐŋҖҁ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž ĐąŅƒĐ´ĐˇĐĩ Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ Ņ‚ĐžĐģҌĐēŅ– адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°Đŧ.", "server_settings": "НаĐģĐ°Đ´Ņ‹ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "server_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_stats_page_description": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа ŅŅ‚Đ°Ņ‚Ņ‹ŅŅ‚Ņ‹ĐēŅ– ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Đ´ĐģŅ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", "server_welcome_message": "ĐŸŅ€Ņ‹Đ˛Ņ–Ņ‚Đ°ĐģҌĐŊаĐĩ ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ", "server_welcome_message_description": "ПавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ, ŅĐēĐžĐĩ адĐģŅŽŅŅ‚Ņ€ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŊа ŅŅ‚Đ°Ņ€ĐžĐŊ҆ҋ ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ.", - "settings_page_description": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа ĐŊаĐģад Đ´ĐģŅ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", - "sidecar_job": "МĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ sidecar-Ņ„Đ°ĐšĐģĐ°Ņž", - "sidecar_job_description": "Đ’Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ айО ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Đ˛Đ°Ņ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ sidecar-Ņ„Đ°ĐšĐģĐ°Ņž С Ņ„Đ°ĐšĐģаваК ŅŅ–ŅŅ‚ŅĐŧŅ‹", - "slideshow_duration_description": "КоĐģҌĐēĐ°ŅŅ†ŅŒ ҁĐĩĐē҃ĐŊĐ´ Đ´ĐģŅ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ ĐēĐžĐļĐŊĐ°ĐŗĐ° Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°", - "smart_search_job_description": "ЗаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ ĐŧĐ°ŅˆŅ‹ĐŊĐŊаĐĩ ĐŊĐ°Đ˛ŅƒŅ‡Đ°ĐŊĐŊĐĩ Đ´ĐģŅ аб’ĐĩĐēŅ‚Đ°Ņž Đ´ĐģŅ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐēŅ– Ņ€Đ°ĐˇŅƒĐŧĐŊĐ°ĐŗĐ° ĐŋĐžŅˆŅƒĐē҃", - "storage_template_date_time_description": "ДĐģŅ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ– ĐŋŅ€Đ° Đ´Đ°Ņ‚Ņƒ Ņ– Ņ‡Đ°Ņ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‡Đ°Ņ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ аб’ĐĩĐēŅ‚Đ°", - "storage_template_date_time_sample": "ĐŸŅ€Ņ‹ĐēĐģад Ņ‡Đ°ŅŅƒ {date}", - "storage_template_enable_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊĐŊŅ‹ ĐŧĐĩŅ…Đ°ĐŊŅ–ĐˇĐŧ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_template_hash_verification_enabled": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа Ņ…ŅŅˆĐ°Ņž ҃ĐēĐģŅŽŅ‡Đ°ĐŊа", - "storage_template_hash_verification_enabled_description": "ĐŖĐēĐģŅŽŅ‡Đ°Đĩ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ Ņ…ŅŅˆĐ°Ņž, ĐŊĐĩ адĐēĐģŅŽŅ‡Đ°ĐšŅ†Đĩ ĐŗŅŅ‚Đ°, ĐēаĐģŅ– ĐŊĐĩ Ņ€Đ°ĐˇŅƒĐŧĐĩĐĩ҆Đĩ ĐŊĐ°ŅŅ‚ŅƒĐŋŅŅ‚Đ˛Đ°Ņž", - "storage_template_migration": "ĐœŅ–ĐŗŅ€Đ°Ņ†Ņ‹Ņ ŅˆĐ°ĐąĐģĐžĐŊ҃ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_template_migration_description": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅ–Ņ†ŅŒ ĐąŅĐŗŅƒŅ‡Ņ‹ {template} да Ņ€Đ°ĐŊĐĩĐš СаĐŋаĐŧĐŋаваĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž", - "storage_template_migration_info": "ШайĐģĐžĐŊ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ĐŋĐĩŅ€Đ°ŅžŅ‚Đ˛ĐžŅ€Ņ‹Ņ†ŅŒ ҃ҁĐĩ ĐŋĐ°ŅˆŅ‹Ņ€ŅĐŊĐŊŅ– Ņž ĐŊŅ–ĐļĐŊŅ– Ņ€ŅĐŗŅ–ŅŅ‚Ņ€. ЗĐŧŅĐŊĐĩĐŊĐŊŅ– ŅˆĐ°ĐąĐģĐžĐŊ҃ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋҀҋĐŧŅĐŊŅŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– да ĐŊĐžĐ˛Ņ‹Ņ… аб’ĐĩĐēŅ‚Đ°Ņž. Каб ĐŋҀҋĐŧŅĐŊŅ–Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ да Ņ€Đ°ĐŊĐĩĐš СаĐŋаĐŧĐŋаваĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž, СаĐŋŅƒŅŅ†Ņ–Ņ†Đĩ {job}.", - "storage_template_migration_job": "ЗадаĐŊĐŊĐĩ ĐŧŅ–ĐŗŅ€Đ°Ņ†Ņ‹Ņ– ŅˆĐ°ĐąĐģĐžĐŊ҃ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_template_more_details": "ДĐģŅ ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊаК Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ– ĐŋŅ€Đ° ĐŗŅŅ‚ŅƒŅŽ Ņ„ŅƒĐŊĐēŅ†Ņ‹ŅŽ ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да Storage Template Ņ– ŅĐŗĐž ĐŊĐ°ŅŅ‚ŅƒĐŋŅŅ‚Đ˛Đ°Ņž", - "storage_template_onboarding_description_v2": "КаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа, ĐŗŅŅ‚Đ°Ņ Ņ„ŅƒĐŊĐēŅ†Ņ‹Ņ ĐąŅƒĐ´ĐˇĐĩ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа Đ°Ņ€ĐŗĐ°ĐŊŅ–ĐˇĐžŅžĐ˛Đ°Ņ†ŅŒ Ņ„Đ°ĐšĐģŅ‹ ĐŋавОдĐģĐĩ ŅˆĐ°ĐąĐģĐžĐŊ҃, Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊĐ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧ. ДĐģŅ ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊаК Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ– ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да даĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ–.", - "storage_template_path_length": "ĐŸŅ€Ņ‹ĐąĐģŅ–ĐˇĐŊŅ‹ ĐģŅ–ĐŧŅ–Ņ‚ Đ´Đ°ŅžĐļŅ‹ĐŊŅ– ҈ĐģŅŅ…Ņƒ: {length, number}/{limit, number}", - "storage_template_settings": "ШайĐģĐžĐŊ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_template_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ŅŅ‚Ņ€ŅƒĐēŅ‚ŅƒŅ€Đ°Đš ĐŋаĐŋаĐē Ņ– ĐŊаСваК Ņ„Đ°ĐšĐģа СаĐŋаĐŧĐŋаваĐŊĐ°ĐŗĐ° ай'ĐĩĐēŅ‚Đ°", - "storage_template_user_label": "{label} - ĐŗŅŅ‚Đ° ĐŋаСĐŊаĐēа ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "system_settings": "ĐĄŅ–ŅŅ‚ŅĐŧĐŊŅ‹Ņ ĐŊаĐģĐ°Đ´Ņ‹", "tag_cleanup_job": "ĐŅ‡Ņ‹ŅŅ‚Đēа Ņ‚ŅĐŗĐ°Ņž", - "template_email_available_tags": "ĐŖ ŅˆĐ°ĐąĐģĐžĐŊĐĩ ĐŧĐžĐļĐŊа Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ ĐŋĐĩŅ€Đ°ĐŧĐĩĐŊĐŊŅ‹Ņ: {tags}", - "template_email_if_empty": "КаĐģŅ– ŅˆĐ°ĐąĐģĐžĐŊ ĐŋŅƒŅŅ‚Ņ‹, ĐąŅƒĐ´ĐˇĐĩ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†Ņ†Đ° ŅŅ‚Đ°ĐŊĐ´Đ°Ņ€Ņ‚ĐŊŅ‹ ŅˆĐ°ĐąĐģĐžĐŊ ĐģŅ–ŅŅ‚Đ°.", - "template_email_invite_album": "ШайĐģĐžĐŊ СаĐŋŅ€Đ°ŅˆŅĐŊĐŊŅ Ņž аĐģŅŒĐąĐžĐŧ", "template_email_preview": "ПĐĩŅ€Đ°Đ´ĐŋŅ€Đ°ĐŗĐģŅĐ´", - "template_email_settings": "ШайĐģĐžĐŊŅ‹ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊҋ҅ ĐģŅ–ŅŅ‚ĐžŅž", - "template_email_update_album": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ аĐģŅŒĐąĐžĐŧа", - "template_email_welcome": "Đ’Ņ–Ņ‚Đ°ĐģҌĐŊŅ‹ ŅˆĐ°ĐąĐģĐžĐŊ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐ°ĐŗĐ° ĐģŅ–ŅŅ‚Đ°", - "template_settings": "ШайĐģĐžĐŊ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ", - "template_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēŅ–ĐŧŅ– ŅˆĐ°ĐąĐģĐžĐŊаĐŧŅ– аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž", - "theme_custom_css_settings": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēŅ– CSS", - "theme_custom_css_settings_description": "ĐšĐ°ŅĐēадĐŊŅ‹Ņ Ņ‚Đ°ĐąĐģҖ҆ҋ ҁ҂ҋĐģŅŅž даСваĐģŅŅŽŅ†ŅŒ ĐŊаĐģĐ°Đ´ĐˇŅ–Ņ†ŅŒ аСдайĐģĐĩĐŊĐŊĐĩ Immich.", "theme_settings": "НаĐģĐ°Đ´Ņ‹ Ņ‚ŅĐŧŅ‹", - "theme_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ аСдайĐģĐĩĐŊĐŊĐĩĐŧ Đ˛ŅĐą-Ņ–ĐŊŅ‚ŅŅ€Ņ„ĐĩĐšŅŅƒ Immich", - "thumbnail_generation_job": "ГĐĩĐŊĐĩŅ€Đ°Ņ†Ņ‹Ņ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€", - "thumbnail_generation_job_description": "ГĐĩĐŊĐĩŅ€Đ°Ņ†Ņ‹Ņ Đ˛ŅĐģŅ–ĐēŅ–Ņ…, ĐŧаĐģҋ҅ Ņ– Ņ€Đ°ĐˇĐŧҋ҂ҋ҅ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ Đ´ĐģŅ ĐēĐžĐļĐŊĐ°ĐŗĐ° аб’ĐĩĐēŅ‚Đ°, а Ņ‚Đ°ĐēŅĐ°Đŧа ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€ Đ´ĐģŅ ĐēĐžĐļĐŊĐ°ĐŗĐ° Ņ‡Đ°ĐģавĐĩĐēа", - "transcoding_acceleration_api": "API ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊŅ", - "transcoding_acceleration_api_description": "API, ŅĐēŅ– ĐąŅƒĐ´ĐˇĐĩ ŅžĐˇĐ°ĐĩĐŧадСĐĩĐšĐŊŅ–Ņ‡Đ°Ņ†ŅŒ С Đ˛Đ°ŅˆĐ°Đš ĐŋҀҋĐģадаК Đ´ĐģŅ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊŅ Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ. Đ“ŅŅ‚Đ°Ņ ĐŊаĐģада ĐŋŅ€Đ°Ņ†ŅƒĐĩ ÂĢĐŋа ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†Ņ–Âģ: ĐŋҀҋ ĐˇĐąĐžŅ– Đ°Đ´ĐąŅƒĐ´ĐˇĐĩŅ†Ņ†Đ° адĐēĐ°Ņ‚ ĐŊа ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧĐŊаĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊĐĩ. VP9 ĐŧĐžĐļа ĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°Ņ†ŅŒ ҆Җ ĐŊĐĩ — ҃ СаĐģĐĩĐļĐŊĐ°ŅŅ†Ņ– ад Đ˛Đ°ŅˆĐ°ĐŗĐ° Đ°ĐąŅŅ‚Đ°ĐģŅĐ˛Đ°ĐŊĐŊŅ.", "transcoding_acceleration_nvenc": "NVENC (ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩŅ†Ņ†Đ° Đ˛Ņ–Đ´ŅĐ°ĐēĐ°Ņ€Ņ‚Đ° NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ ĐŋŅ€Đ°Ņ†ŅŅĐ°Ņ€Ņ‹ Intel 7-ĐŗĐ° ĐŋаĐēаĐģĐĩĐŊĐŊŅ аĐģŅŒĐąĐž Đ˛Ņ‹ŅˆŅĐš)", - "transcoding_acceleration_rkmpp": "RKMPP (Ņ‚ĐžĐģҌĐēŅ– ĐŊа Rockchip SOC)", "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "ДазвоĐģĐĩĐŊŅ‹Ņ Đ°ŅžĐ´Ņ‹Ņ‘ ĐēĐžĐ´ŅĐēŅ–", - "transcoding_accepted_audio_codecs_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ, ŅĐēŅ–Ņ Đ°ŅžĐ´Ņ‹ŅĐēĐžĐ´ŅĐēŅ– ĐŊĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ. Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅŅžĐŊҋ҅ ĐŋаĐģҖ҂ҋĐē Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ.", "transcoding_accepted_containers": "ĐŸŅ€Ņ‹ĐŊŅŅ‚Ņ‹Ņ ĐēаĐŊŅ‚ŅĐšĐŊĐĩҀҋ", - "transcoding_accepted_containers_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ, ŅĐēŅ–Ņ ĐēаĐŊŅ‚ŅĐšĐŊĐĩŅ€ĐŊŅ‹Ņ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Ņ‹ ĐŊĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ ĐŋĐĩŅ€Đ°ĐēĐ°Đ´Ņ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ Ņž MP4. Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅŅžĐŊҋ҅ ĐŋаĐģҖ҂ҋĐē Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ.", "transcoding_accepted_video_codecs": "ĐŸŅ€Ņ‹ĐŊŅŅ‚Ņ‹Ņ Đ˛Ņ–Đ´ŅĐ°ĐēĐžĐ´ŅĐēŅ–", - "transcoding_accepted_video_codecs_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ, ŅĐēŅ–Ņ Đ˛Ņ–Đ´ŅĐ°ĐēĐžĐ´ŅĐēŅ– ĐŊĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ. Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅŅžĐŊҋ҅ ĐŋаĐģҖ҂ҋĐē Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ.", "transcoding_advanced_options_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ, ŅĐēŅ–Ņ йОĐģŅŒŅˆĐ°ŅŅ†Ņ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž ĐŊĐĩ Ņ‚Ņ€ŅĐąĐ° СĐŧŅĐŊŅŅ†ŅŒ", "transcoding_audio_codec": "ĐŅƒĐ´Ņ‹ŅĐēĐžĐ´ŅĐē", - "transcoding_audio_codec_description": "Opus — Đ˛Đ°Ņ€Ņ‹ŅĐŊŅ‚ С ĐŊĐ°ĐšĐ˛Ņ‹ŅˆŅĐšŅˆĐ°Đš ŅĐēĐ°ŅŅ†ŅŽ, аĐģĐĩ ĐŧаĐĩ ĐŗĐžŅ€ŅˆŅƒŅŽ ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊĐ°ŅŅ†ŅŒ ŅĐ° ŅŅ‚Đ°Ņ€Ņ‹ĐŧŅ– ĐŋҀҋĐģадаĐŧŅ– ҆Җ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧаĐŧŅ–.", - "transcoding_bitrate_description": "Đ’Ņ–Đ´ŅĐ° С ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚Đ°Đŧ Đ˛Ņ‹ŅˆŅĐš Са ĐŧаĐēҁҖĐŧаĐģҌĐŊŅ‹ ҆Җ Ņž ĐŊĐĩ даСвОĐģĐĩĐŊŅ‹Đŧ Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ", - "transcoding_codecs_learn_more": "Каб давĐĩĐ´Đ°Ņ†Ņ†Đ° йОĐģҌ҈ ĐŋŅ€Đ° Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊŅƒŅŽ Ņ‚ŅƒŅ‚ Ņ‚ŅŅ€ĐŧŅ–ĐŊаĐģĐžĐŗŅ–ŅŽ, ĐˇĐ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да даĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ– FFmpeg Đ´ĐģŅ ĐēĐžĐ´ŅĐēа H.264, ĐēĐžĐ´ŅĐēа HEVC Ņ– ĐēĐžĐ´ŅĐēа VP9.", - "transcoding_constant_quality_mode": "Đ ŅĐļŅ‹Đŧ ĐŊŅĐˇĐŧĐĩĐŊĐŊаК ŅĐēĐ°ŅŅ†Ņ–", - "transcoding_constant_quality_mode_description": "ICQ ĐģĐĩĐŋŅˆŅ‹ Са CQP, аĐģĐĩ ĐŊĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ĐŋҀҋĐģĐ°Đ´Ņ‹ С аĐŋĐ°Ņ€Đ°Ņ‚ĐŊŅ‹Đŧ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊĐĩĐŧ ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ŅŽŅ†ŅŒ ĐŗŅŅ‚Ņ‹ Ņ€ŅĐļŅ‹Đŧ. Đ“ŅŅ‚Đ°Ņ ĐžĐŋŅ†Ņ‹Ņ СадаĐĩ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐļĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ ĐŋҀҋ ĐēадаваĐŊĐŊŅ– ĐŊа Đ°ŅĐŊОвĐĩ ŅĐēĐ°ŅŅ†Ņ–. NVENC Ņ–ĐŗĐŊĐ°Ņ€ŅƒĐĩ ŅĐĩ, йО ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°Đĩ ICQ.", - "transcoding_constant_rate_factor": "ĐŅĐˇĐŧĐĩĐŊĐŊŅ‹ Ņ„Đ°ĐēŅ‚Đ°Ņ€ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†Ņ– (-crf)", - "transcoding_constant_rate_factor_description": "ĐŖĐˇŅ€ĐžĐ˛ĐĩĐŊҌ ŅĐēĐ°ŅŅ†Ņ– Đ˛Ņ–Đ´ŅĐ°. ĐĸŅ‹ĐŋĐžĐ˛Ņ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ–: 23 Đ´ĐģŅ H.264, 28 Đ´ĐģŅ HEVC, 31 Đ´ĐģŅ VP9 Ņ– 35 Đ´ĐģŅ AV1. МĐĩĐŊŅˆĐ°Đĩ — ĐģĐĩĐŋ҈, аĐģĐĩ даĐĩ йОĐģŅŒŅˆŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹.", - "transcoding_disabled_description": "НĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŊŅ–ŅĐēŅ–Ņ Đ˛Ņ–Đ´ŅĐ°, ĐŧĐžĐļа ĐŋĐ°Ņ€ŅƒŅˆŅ‹Ņ†ŅŒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊа ĐŊĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ… ĐēĐģŅ–ĐĩĐŊŅ‚Đ°Ņ…", "transcoding_encoding_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐēадаваĐŊĐŊŅ", "transcoding_encoding_options_description": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ ĐēĐžĐ´ŅĐēŅ–, Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ, ŅĐēĐ°ŅŅ†ŅŒ Ņ– Ņ–ĐŊŅˆŅ‹Ņ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ Đ˛Ņ–Đ´ŅĐ°", - "transcoding_hardware_acceleration": "АĐŋĐ°Ņ€Đ°Ņ‚ĐŊаĐĩ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊĐĩ", - "transcoding_hardware_acceleration_description": "Đ­ĐēҁĐŋĐĩҀҋĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊĐ°Ņ ĐžĐŋŅ†Ņ‹Ņ: Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊĐĩ Đ°Đ´ĐąŅ‹Đ˛Đ°ĐĩŅ†Ņ†Đ° Ņ…ŅƒŅ‚Ņ‡ŅĐš, аĐģĐĩ ĐŧĐžĐļа ĐŋаĐŧĐĩĐŊŅˆŅ‹Ņ†ŅŒ ŅĐēĐ°ŅŅ†ŅŒ ĐŋҀҋ ҂ҋĐŧ Đļа ĐąŅ–Ņ‚Ņ€ŅĐšŅ†Đĩ", - "transcoding_hardware_decoding": "АĐŋĐ°Ņ€Đ°Ņ‚ĐŊаĐĩ Đ´ŅĐēадаваĐŊĐŊĐĩ", - "transcoding_hardware_decoding_setting_description": "ĐŖĐēĐģŅŽŅ‡Đ°Đĩ ҁĐēŅ€Đ°ĐˇĐŊĐžĐĩ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊĐĩ СаĐŧĐĩҁ҂ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊŅ Ņ‚ĐžĐģҌĐēŅ– ĐēадаваĐŊĐŊŅ. МоĐļа ĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°Ņ†ŅŒ ĐŊĐĩ С ŅƒŅŅ–ĐŧŅ– Đ˛Ņ–Đ´ŅĐ°.", - "transcoding_max_b_frames": "МаĐēҁҖĐŧ҃Đŧ B-ĐēĐ°Đ´Ņ€Đ°Ņž", - "transcoding_max_b_frames_description": "БоĐģŅŒŅˆŅ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– ĐŋаĐģŅĐŋŅˆĐ°ŅŽŅ†ŅŒ ŅŅ„ĐĩĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ ҁ҆ҖҁĐēаĐŊĐŊŅ, аĐģĐĩ СаĐŋавОĐģŅŒĐ˛Đ°ŅŽŅ†ŅŒ ĐēадаваĐŊĐŊĐĩ. МоĐļа ĐąŅ‹Ņ†ŅŒ ĐŊĐĩҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊа С аĐŋĐ°Ņ€Đ°Ņ‚ĐŊŅ‹Đŧ ĐŋĐ°ŅĐēĐ°Ņ€ŅĐŊĐŊĐĩĐŧ ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ĐŋҀҋĐģĐ°Đ´Đ°Ņ…. 0 адĐēĐģŅŽŅ‡Đ°Đĩ B-ĐēĐ°Đ´Ņ€Ņ‹, -1 Đ˛Ņ‹ŅŅ‚Đ°ŅžĐģŅĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа.", - "transcoding_max_bitrate": "МаĐēҁҖĐŧаĐģҌĐŊŅ‹ ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚", - "transcoding_max_bitrate_description": "ЗадаваĐŊĐŊĐĩ ĐŧаĐēҁҖĐŧаĐģҌĐŊĐ°ĐŗĐ° ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚Ņƒ Ņ€ĐžĐąŅ–Ņ†ŅŒ ĐŋаĐŧĐĩҀҋ Ņ„Đ°ĐšĐģĐ°Ņž йОĐģҌ҈ ĐŋŅ€Đ°Đ´ĐēаСаĐģҌĐŊŅ‹ĐŧŅ– Са ĐēĐžŅˆŅ‚ ĐŊŅĐˇĐŊĐ°Ņ‡ĐŊаК ŅŅ‚Ņ€Đ°Ņ‚Ņ‹ ŅĐēĐ°ŅŅ†Ņ–. ДĐģŅ 720p ҂ҋĐŋĐžĐ˛Ņ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ–: 2600 kbit/s Đ´ĐģŅ VP9 ҆Җ HEVC, айО 4500 kbit/s Đ´ĐģŅ H.264. АдĐēĐģŅŽŅ‡Đ°ĐŊа ĐŋҀҋ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– 0. КаĐģŅ– Đ°Đ´ĐˇŅ–ĐŊĐēŅ– ĐŊĐĩ ĐŋаСĐŊĐ°Ņ‡Đ°ĐŊŅ‹, ĐŧŅŅ€Đē҃ĐĩŅ†Ņ†Đ° k (kbit/s); Ņ‚Đ°Đŧ҃ 5000, 5000k Ņ– 5M (Mbit/s) ŅĐēĐ˛Ņ–Đ˛Đ°ĐģĐĩĐŊŅ‚ĐŊŅ‹Ņ.", - "transcoding_max_keyframe_interval": "МаĐēҁҖĐŧаĐģҌĐŊŅ‹ Ņ–ĐŊŅ‚ŅŅ€Đ˛Đ°Đģ ĐēĐģŅŽŅ‡Đ°Đ˛Ņ‹Ņ… ĐēĐ°Đ´Ņ€Đ°Ņž", - "transcoding_max_keyframe_interval_description": "ЗадаĐĩ ĐŧаĐēҁҖĐŧаĐģҌĐŊŅƒŅŽ адĐģĐĩĐŗĐģĐ°ŅŅ†ŅŒ ĐŋаĐŧŅ–Đļ ĐēĐģŅŽŅ‡Đ°Đ˛Ņ‹ĐŧŅ– ĐēĐ°Đ´Ņ€Đ°ĐŧŅ–. МĐĩĐŊŅˆŅ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– ĐŋĐ°ĐŗĐ°Ņ€ŅˆĐ°ŅŽŅ†ŅŒ ŅŅ„ĐĩĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ ҁ҆ҖҁĐēаĐŊĐŊŅ, аĐģĐĩ ĐŋаĐģŅĐŋŅˆĐ°ŅŽŅ†ŅŒ Ņ‡Đ°Ņ ĐŋĐĩŅ€Đ°ĐŧĐžŅ‚ĐēŅ– Ņ– ĐŧĐžĐŗŅƒŅ†ŅŒ ĐŋаĐģŅĐŋŅˆĐ°Ņ†ŅŒ ŅĐēĐ°ŅŅ†ŅŒ ҃ ŅŅ†ŅĐŊĐ°Ņ… С Ņ…ŅƒŅ‚ĐēŅ–Đŧ Ņ€ŅƒŅ…Đ°Đŧ. 0 Đ˛Ņ‹ŅŅ‚Đ°ŅžĐģŅĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа.", "transcoding_optimal_description": "Đ’Ņ–Đ´ŅĐ° С Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŽ Đ˛Ņ‹ŅˆŅĐš ĐŧŅŅ‚Đ°Đ˛Đ°Đš ҆Җ Ņž ĐŊĐĩĐŋҀҋĐŊŅŅ‚Ņ‹Đŧ Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ", - "transcoding_policy": "ПаĐģҖ҂ҋĐēа Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ", - "transcoding_policy_description": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ, ĐēаĐģŅ– Đ˛Ņ–Đ´ŅĐ° ĐąŅƒĐ´ĐˇĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†Ņ†Đ°", - "transcoding_preferred_hardware_device": "ПĐĩŅ€Đ°Đ˛Đ°ĐļĐŊĐ°Ņ аĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐ°Ņ ĐŋҀҋĐģада", - "transcoding_preferred_hardware_device_description": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– да VAAPI Ņ– QSV. ЗадаĐĩ dri-Đ˛ŅƒĐˇĐĩĐģ Đ´ĐģŅ аĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐ°ĐŗĐ° Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ.", - "transcoding_preset_preset": "ĐŸŅ€ŅŅĐĩŅ‚ (-preset)", - "transcoding_preset_preset_description": "ĐĨŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ҁ҆ҖҁĐēаĐŊĐŊŅ. БоĐģҌ҈ ĐŋавОĐģҌĐŊŅ‹Ņ ĐŋŅ€ŅŅĐĩ҂ҋ Đ´Đ°ŅŽŅ†ŅŒ ĐŧĐĩĐŊŅˆŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹ Ņ– ĐŋĐ°Đ˛Ņ‹ŅˆĐ°ŅŽŅ†ŅŒ ŅĐēĐ°ŅŅ†ŅŒ ĐŋҀҋ ĐŧŅŅ‚Đ°Đ˛Ņ‹Đŧ ĐąŅ–Ņ‚Ņ€ŅĐšŅ†Đĩ. VP9 Ņ–ĐŗĐŊĐ°Ņ€ŅƒĐĩ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†Ņ– Đ˛Ņ‹ŅˆŅĐšŅˆŅ‹Ņ Са ÂĢfasterÂģ.", - "transcoding_realtime": "ПĐĩŅ€Đ°ĐēадаваĐŊĐŊĐĩ Ņž Ņ€ŅĐļŅ‹ĐŧĐĩ Ņ€ŅĐ°ĐģҌĐŊĐ°ĐŗĐ° Ņ‡Đ°ŅŅƒ [EXPERIMENTAL]]", - "transcoding_realtime_resolutions": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†Ņ–", - "transcoding_realtime_video_codecs": "Đ’Ņ–Đ´ŅĐ°ĐēĐžĐ´ŅĐēŅ–", - "transcoding_reference_frames": "АĐŋĐžŅ€ĐŊŅ‹Ņ ĐēĐ°Đ´Ņ€Ņ‹", - "transcoding_reference_frames_description": "КоĐģҌĐēĐ°ŅŅ†ŅŒ ĐēĐ°Đ´Ņ€Đ°Ņž, ŅĐēŅ–Ņ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ° ŅĐē ŅŅ‚Đ°ĐģĐžĐŊ ĐŋҀҋ ҁ҆ҖҁĐēаĐŊĐŊŅ– дадСĐĩĐŊĐ°ĐŗĐ° ĐēĐ°Đ´Ņ€Đ°. БоĐģŅŒŅˆŅ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– ĐŋаĐģŅĐŋŅˆĐ°ŅŽŅ†ŅŒ ŅŅ„ĐĩĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ ҁ҆ҖҁĐēаĐŊĐŊŅ, аĐģĐĩ СаĐŋавОĐģŅŒĐ˛Đ°ŅŽŅ†ŅŒ ĐēадаваĐŊĐŊĐĩ. 0 Đ˛Ņ‹ŅŅ‚Đ°ŅžĐģŅĐĩ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа.", - "transcoding_required_description": "ĐĸĐžĐģҌĐēŅ– Đ˛Ņ–Đ´ŅĐ° Ņž ĐŊĐĩдаĐŋŅƒŅˆŅ‡Đ°ĐģҌĐŊŅ‹Đŧ Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ", - "transcoding_settings": "НаĐģĐ°Đ´Ņ‹ Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ Đ˛Ņ–Đ´ŅĐ°", - "transcoding_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ҂ҋĐŧ, ŅĐēŅ–Ņ Đ˛Ņ–Đ´ŅĐ° Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ Ņ– ŅĐē Ņ–Ņ… аĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°Ņ†ŅŒ", "transcoding_target_resolution": "ĐœŅŅ‚Đ°Đ˛Đ°Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", "transcoding_target_resolution_description": "Đ’Ņ‹ŅˆŅĐšŅˆŅ‹Ņ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†Ņ– ĐŧĐžĐŗŅƒŅ†ŅŒ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ йОĐģҌ҈ Đ´ŅŅ‚Đ°ĐģĐĩĐš, аĐģĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒŅŽŅ†ŅŒ йОĐģҌ҈ Ņ‡Đ°ŅŅƒ Đ´ĐģŅ ĐēадаваĐŊĐŊŅ, ĐŧĐ°ŅŽŅ†ŅŒ йОĐģŅŒŅˆŅ‹ ĐŋаĐŧĐĩŅ€ Ņ„Đ°ĐšĐģĐ°Ņž Ņ– ĐŧĐžĐŗŅƒŅ†ŅŒ СĐŧĐĩĐŊŅˆŅ‹Ņ†ŅŒ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ адĐēĐ°ĐˇŅƒ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹.", - "transcoding_temporal_aq": "Đ§Đ°ŅĐ°Đ˛Ņ‹ AQ", - "transcoding_temporal_aq_description": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– да NVENC. Đ§Đ°ŅĐ°Đ˛Đ°Ņ адаĐŋŅ‚Ņ‹ŅžĐŊĐ°Ņ ĐēваĐŊŅ‚Ņ‹ĐˇĐ°Ņ†Ņ‹Ņ ĐŋĐ°Đ˛Ņ‹ŅˆĐ°Đĩ ŅĐēĐ°ŅŅ†ŅŒ ŅŅ†ŅĐŊ С Đ˛Ņ‹ŅĐžĐēаК Đ´ŅŅ‚Đ°ĐģŅ–ĐˇĐ°Ņ†Ņ‹ŅĐš Ņ– ĐŧаĐģŅ‹Đŧ Ņ€ŅƒŅ…Đ°Đŧ. МоĐļа ĐąŅ‹Ņ†ŅŒ ĐŊĐĩҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊа ŅĐ° ŅŅ‚Đ°Ņ€Ņ‹ĐŧŅ– ĐŋҀҋĐģадаĐŧŅ–.", - "transcoding_threads": "ĐŸĐ°Ņ‚ĐžĐēŅ–", - "transcoding_threads_description": "БоĐģŅŒŅˆŅ‹Ņ СĐŊĐ°Ņ‡ŅĐŊĐŊŅ– Đ´Đ°ŅŽŅ†ŅŒ Ņ…ŅƒŅ‚Ņ‡ŅĐšŅˆĐ°Đĩ ĐēадаваĐŊĐŊĐĩ, аĐģĐĩ ĐŋаĐēŅ–Đ´Đ°ŅŽŅ†ŅŒ ĐŧĐĩĐŊ҈ ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†ŅŅž Đ´ĐģŅ Ņ–ĐŊŅˆŅ‹Ņ… ĐˇĐ°Đ´Đ°Ņ‡ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°. ЗĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐŊĐĩ ĐŋĐ°Đ˛Ņ–ĐŊĐŊа ĐŋĐĩŅ€Đ°Đ˛Ņ‹ŅˆĐ°Ņ†ŅŒ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ ŅĐ´Ņ€Đ°Ņž ĐĻП. 0 ĐŧаĐēҁҖĐŧŅ–ĐˇŅƒĐĩ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩ.", - "transcoding_tone_mapping": "ĐĸаĐŊаĐģҌĐŊаĐĩ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ", - "transcoding_tone_mapping_description": "ĐĄĐŋŅ€Đ°ĐąŅƒĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ĐŗĐģŅĐ´ HDR-Đ˛Ņ–Đ´ŅĐ° ĐŋҀҋ ĐŋĐĩŅ€Đ°ŅžŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ– Ņž SDR. КоĐļĐŊŅ‹ аĐģĐŗĐ°Ņ€Ņ‹Ņ‚Đŧ Ņ€ĐžĐąŅ–Ņ†ŅŒ Ņ€ĐžĐˇĐŊŅ‹Ņ ĐēаĐŧĐŋŅ€Đ°ĐŧҖҁҋ ĐŋаĐŧŅ–Đļ ĐēĐžĐģĐĩŅ€Đ°Đŧ, Đ´ŅŅ‚Đ°ĐģŅ–ĐˇĐ°Ņ†Ņ‹ŅĐš Ņ– ŅŅ€ĐēĐ°ŅŅ†ŅŽ. Hable ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°Đĩ Đ´ŅŅ‚Đ°ĐģŅ–ĐˇĐ°Ņ†Ņ‹ŅŽ, Mobius — ĐēĐžĐģĐĩŅ€, Reinhard — ŅŅ€ĐēĐ°ŅŅ†ŅŒ.", - "transcoding_transcode_policy": "ПаĐģҖ҂ҋĐēа Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ", - "transcoding_transcode_policy_description": "ПаĐģҖ҂ҋĐēа Ņ‚Đ°ĐŗĐž, ĐēаĐģŅ– Đ˛Ņ–Đ´ŅĐ° ĐŋĐ°Đ˛Ņ–ĐŊĐŊа Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†Ņ†Đ°. HDR-Đ˛Ņ–Đ´ŅĐ° Ņ– Đ˛Ņ–Đ´ŅĐ° С Ņ„Đ°Ņ€ĐŧĐ°Ņ‚Đ°Đŧ ĐŋŅ–ĐēҁĐĩĐģŅŅž, Đ°Đ´Ņ€ĐžĐˇĐŊŅ‹Đŧ ад YUV 4:2:0, ĐˇĐ°ŅžŅŅ‘Đ´Ņ‹ Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´ŅƒŅŽŅ†Ņ†Đ° (аĐēŅ€Đ°ĐŧŅ Đ˛Ņ‹ĐŋадĐē҃, ĐēаĐģŅ– Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊĐĩ адĐēĐģŅŽŅ‡Đ°ĐŊа).", - "transcoding_two_pass_encoding": "Đ”Đ˛ŅƒŅ…ĐŋŅ€Đ°Ņ…ĐžĐ´ĐŊаĐĩ ĐēадаваĐŊĐŊĐĩ", - "transcoding_two_pass_encoding_setting_description": "ĐĸŅ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ ҃ два ĐŋŅ€Đ°Ņ…ĐžĐ´Ņ‹ Đ´ĐģŅ ĐģĐĩĐŋ҈ СаĐēадаваĐŊҋ҅ Đ˛Ņ–Đ´ŅĐ°. КаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ ĐŧаĐēҁҖĐŧаĐģҌĐŊŅ‹ ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚ (ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩŅ†Ņ†Đ° Đ´ĐģŅ ĐŋŅ€Đ°Ņ†Ņ‹ С H.264 Ņ– HEVC), ĐŗŅŅ‚Ņ‹ Ņ€ŅĐļŅ‹Đŧ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ Đ´Ņ‹ŅĐŋаСОĐŊ ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚Ņƒ ĐŊа Đ°ŅĐŊОвĐĩ ĐŧаĐēҁ. ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚Ņƒ Ņ– Ņ–ĐŗĐŊĐ°Ņ€ŅƒĐĩ CRF. ДĐģŅ VP9 CRF ĐŧĐžĐļа Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†Ņ†Đ°, ĐēаĐģŅ– ĐŧаĐēҁ. ĐąŅ–Ņ‚Ņ€ŅĐšŅ‚ адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹.", "transcoding_video_codec": "Đ’Ņ–Đ´ŅĐ°ĐēĐžĐ´ŅĐē", - "transcoding_video_codec_description": "VP9 ĐŧаĐĩ Đ˛Ņ‹ŅĐžĐēŅƒŅŽ ŅŅ„ĐĩĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ Ņ– ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊĐ°ŅŅ†ŅŒ С Đ˛ŅĐąĐ°Đŧ, аĐģĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐ°Đ´ŅƒĐĩŅ†Ņ†Đ° Đ´Đ°ŅžĐļŅĐš. HEVC ĐŋŅ€Đ°Ņ†ŅƒĐĩ ĐŋадОйĐŊа, аĐģĐĩ ĐŧаĐĩ ĐŧĐĩĐŊŅˆŅƒŅŽ Đ˛ŅĐą-ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊĐ°ŅŅ†ŅŒ. H.264 ŅˆŅ‹Ņ€ĐžĐēа ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊŅ‹ Ņ– Ņ…ŅƒŅ‚ĐēŅ– Ņž Ņ‚Ņ€Đ°ĐŊҁĐēадаваĐŊĐŊŅ–, аĐģĐĩ даĐĩ СĐŊĐ°Ņ‡ĐŊа йОĐģŅŒŅˆŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹. AV1 — ŅĐ°ĐŧŅ‹ ŅŅ„ĐĩĐēŅ‚Ņ‹ŅžĐŊŅ‹ ĐēĐžĐ´ŅĐē, аĐģĐĩ ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ĐŋҀҋĐģĐ°Đ´Đ°Ņ….", "trash_enabled_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ„ŅƒĐŊĐē҆ҋҖ ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", "trash_number_of_days": "КоĐģҌĐēĐ°ŅŅ†ŅŒ Đ´ĐˇŅ‘ĐŊ", - "trash_number_of_days_description": "КоĐģҌĐēĐ°ŅŅ†ŅŒ Đ´ĐˇŅ‘ĐŊ, Ņ†ŅĐŗĐ°Đŧ ŅĐēŅ–Ņ… аб’ĐĩĐē҂ҋ ĐˇĐ°Ņ…ĐžŅžĐ˛Đ°ŅŽŅ†Ņ†Đ° Ņž ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ ĐŋĐĩŅ€Đ°Đ´ ĐēаĐŊŅ‡Đ°Ņ‚ĐēĐžĐ˛Ņ‹Đŧ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩĐŧ", "trash_settings": "НаĐģĐ°Đ´Ņ‹ ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", "trash_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", - "unlink_all_oauth_accounts": "ĐĐ´Đ˛ŅĐˇĐ°Ņ†ŅŒ ҃ҁĐĩ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹Ņ СаĐŋҖҁҋ OAuth", - "unlink_all_oauth_accounts_description": "НĐĩ ĐˇĐ°ĐąŅƒĐ´ĐˇŅŒŅ†ĐĩŅŅ Đ°Đ´Đ˛ŅĐˇĐ°Ņ†ŅŒ ҃ҁĐĩ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹Ņ СаĐŋҖҁҋ OAuth ĐŋĐĩŅ€Đ°Đ´ ĐŧŅ–ĐŗŅ€Đ°Ņ†Ņ‹ŅĐš да ĐŊĐžĐ˛Đ°ĐŗĐ° ĐŋĐ°ŅŅ‚Đ°ŅžŅˆŅ‡Ņ‹Đēа.", - "unlink_all_oauth_accounts_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ°Đ´Đ˛ŅĐˇĐ°Ņ†ŅŒ ҃ҁĐĩ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹Ņ СаĐŋҖҁҋ OAuth? Đ‘ŅƒĐ´ĐˇĐĩ ҁĐēŅ–ĐŊŅƒŅ‚Ņ‹ OAuth ID ĐēĐžĐļĐŊĐ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņ– ĐŗŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐģŅŒĐŗĐ° Đ°Đ´Ņ€Đ°ĐąŅ–Ņ†ŅŒ.", "user_cleanup_job": "ĐŅ‡Ņ‹ŅŅ‚Đēа ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_delete_delay": "ĐŖĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ {user} Ņ– аĐēŅ‚Ņ‹Đ˛Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ СаĐŋĐģаĐŊаваĐŊŅ‹ Đ´ĐģŅ ĐēаĐŊŅ‡Đ°Ņ‚ĐēĐžĐ˛Đ°ĐŗĐ° Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ ĐŋŅ€Đ°Đˇ {delay, plural, one {# дСĐĩĐŊҌ} few {# Đ´ĐŊŅ–} many {# Đ´ĐˇŅ‘ĐŊ} other {# Đ´ĐˇŅ‘ĐŊ}}.", - "user_delete_delay_settings": "Đ—Đ°Ņ‚Ņ€Ņ‹ĐŧĐēа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ", - "user_delete_delay_settings_description": "КоĐģҌĐēĐ°ŅŅ†ŅŒ Đ´ĐˇŅ‘ĐŊ ĐŋĐ°ŅĐģŅ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ, Đŋа СаĐēаĐŊŅ‡ŅĐŊĐŊŅ– ŅĐēŅ–Ņ… ҃ĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņ– ŅĐŗĐž аĐēŅ‚Ņ‹Đ˛Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹Ņ ĐŊĐĩĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊа. ЗадаĐŊĐŊĐĩ ĐŊа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа СаĐŋ҃ҁĐēаĐĩŅ†Ņ†Đ° аĐŋĐžŅžĐŊĐ°Ņ‡Ņ‹ Đ´ĐģŅ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– ĐŗĐ°Ņ‚ĐžŅžĐŊĐ°ŅŅ†Ņ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž да Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ. ЗĐŧŅĐŊĐĩĐŊĐŊŅ– Ņž ĐŗŅŅ‚Ņ‹Đŧ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ҃ĐģŅ–Ņ‡Đ°ĐŊŅ‹Ņ ĐŋҀҋ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹Đŧ Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ–.", + "user_delete_delay_settings_description": "КоĐģҌĐēĐ°ŅŅ†ŅŒ Đ´ĐˇŅ‘ĐŊ ĐŋĐ°ŅĐģŅ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ, Đŋа СаĐēаĐŊŅ‡ŅĐŊĐŊŅ– ŅĐēŅ–Ņ… ҃ĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņ– ŅĐŗĐž аĐēŅ‚Ņ‹Đ˛Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹Ņ ĐŊĐĩĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊа. ЗадаĐŊĐŊĐĩ ĐŊа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа СаĐŋ҃ҁĐēаĐĩŅ†Ņ†Đ° аĐŋĐžŅžĐŊĐ°Ņ‡Ņ‹ Đ´ĐģŅ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– ĐŗĐ°Ņ‚ĐžŅžĐŊĐ°ŅŅ†Ņ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž да Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ. ЗĐŧĐĩĐŊŅ‹ Ņž ĐŗŅŅ‚Ņ‹Đŧ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ҃ĐģŅ–Ņ‡Đ°ĐŊŅ‹Ņ ĐŋҀҋ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹Đŧ Đ˛Ņ‹ĐēаĐŊаĐŊĐŊŅ–.", "user_delete_immediately": "ĐŖĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ {user} Ņ– аĐēŅ‚Ņ‹Đ˛Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŊĐĩадĐēĐģадĐŊа СĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž Ņ‡Đ°Ņ€ĐŗŅƒ ĐŊа ĐēаĐŊŅ‡Đ°Ņ‚ĐēОваĐĩ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ.", - "user_delete_immediately_checkbox": "ĐŸĐ°ŅŅ‚Đ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņ– аб’ĐĩĐē҂ҋ Ņž Ņ‡Đ°Ņ€ĐŗŅƒ ĐŊа ĐŊĐĩадĐēĐģадĐŊаĐĩ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ", - "user_details": "ЗвĐĩҁ҂ĐēŅ– ĐŋŅ€Đ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_management": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧŅ–", "user_password_has_been_reset": "ĐŸĐ°Ņ€ĐžĐģҌ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа ĐąŅ‹Ņž ҁĐēŅ–ĐŊŅƒŅ‚Ņ‹:", "user_password_reset_description": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē҃ Ņ‡Đ°ŅĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ Ņ– ĐŋавĐĩдаĐŧҖ҆Đĩ ŅĐŧ҃, ŅˆŅ‚Đž ĐŋҀҋ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹Đŧ ŅƒĐ˛Đ°Ņ…ĐžĐ´ĐˇĐĩ Ņž ŅŅ–ŅŅ‚ŅĐŧ҃ ŅĐŧ҃ Ņ‚Ņ€ŅĐąĐ° ĐąŅƒĐ´ĐˇĐĩ СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ.", "user_restore_description": "ĐŖĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа {user} ĐąŅƒĐ´ĐˇĐĩ адĐŊĐžŅžĐģĐĩĐŊŅ‹.", - "user_restore_scheduled_removal": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа — Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ СаĐŋĐģаĐŊаваĐŊа ĐŊа {date, date, long}", "user_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "user_successfully_removed": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē {email} ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹.", - "users_page_description": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°Ņž", "version_check_enabled_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃ вĐĩҀҁҖҖ", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Ņ‹Ņ ĐŋŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– вĐĩҀҁҖҖ ĐŋĐĩŅ€Ņ‹ŅĐ´Ņ‹Ņ‡ĐŊа ĐˇĐ˛ŅŅ€Ņ‚Đ°ĐĩŅ†Ņ†Đ° да {server}", "version_check_settings": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа вĐĩҀҁҖҖ", - "version_check_settings_description": "ĐŖĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ/адĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž ай ĐŊОваК вĐĩҀҁҖҖ", - "video_conversion_job": "ĐĸŅ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ°", - "video_conversion_job_description": "ĐĸŅ€Đ°ĐŊҁĐēĐ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ° Đ´ĐģŅ ĐģĐĩĐŋŅˆĐ°Đš ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊĐ°ŅŅ†Ņ– С ĐąŅ€Đ°ŅžĐˇĐĩŅ€Đ°ĐŧŅ– Ņ– ĐŋҀҋĐģадаĐŧŅ–" + "version_check_settings_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ/адĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– ай ĐŊОваК вĐĩҀҁҖҖ" }, "admin_email": "Đ­ĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐ°Ņ ĐŋĐžŅˆŅ‚Đ° адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", "admin_password": "ĐŸĐ°Ņ€ĐžĐģҌ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", "administration": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°Đŧ", "advanced": "ĐŸĐ°ŅˆŅ‹Ņ€Đ°ĐŊŅ‹Ņ", - "advanced_settings_clear_image_cache": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ĐēŅŅˆ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž", - "advanced_settings_clear_image_cache_error": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ĐēŅŅˆ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž", - "advanced_settings_clear_image_cache_success": "ĐŅ‡Ņ‹ŅˆŅ‡Đ°ĐŊа {size}", "advanced_settings_log_level_title": "ĐŖĐˇŅ€ĐžĐ˛ĐĩĐŊҌ Đ˛ŅĐ´ĐˇĐĩĐŊĐŊŅ ĐļŅƒŅ€ĐŊаĐģа: {level}", - "advanced_settings_prefer_remote_subtitle": "НĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ĐŋҀҋĐģĐ°Đ´Ņ‹ вĐĩĐģҌĐŧŅ– ĐŋавОĐģҌĐŊа ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°ŅŽŅ†ŅŒ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹ С ĐģаĐēаĐģҌĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž. ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ ĐŗŅŅ‚ŅƒŅŽ ĐŊаĐģĐ°Đ´Ņƒ, Đēай СаĐŧĐĩҁ҂ ĐŗŅŅ‚Đ°ĐŗĐ° ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅŅ‹ С ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", - "advanced_settings_prefer_remote_title": "ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ аддаĐģĐĩĐŊŅ‹Đŧ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Đŧ", - "advanced_settings_proxy_headers_subtitle": "Đ’Ņ‹ĐˇĐŊĐ°Ņ‡Ņ†Đĩ ĐˇĐ°ĐŗĐ°ĐģĐžŅžĐēŅ– ĐŋŅ€ĐžĐēҁҖ, ŅĐēŅ–Ņ Immich ĐŋĐ°Đ˛Ņ–ĐŊĐĩĐŊ адĐŋŅ€Đ°ŅžĐģŅŅ†ŅŒ С ĐēĐžĐļĐŊŅ‹Đŧ ҁĐĩŅ‚ĐēĐ°Đ˛Ņ‹Đŧ СаĐŋŅ‹Ņ‚Đ°Đŧ", "advanced_settings_proxy_headers_title": "[ЭКСПЕРĐĢМЕНĐĸАЛĐŦНА] ĐŖĐģĐ°ŅĐŊŅ‹Ņ ĐˇĐ°ĐŗĐ°ĐģĐžŅžĐēŅ– ĐŋŅ€ĐžĐēҁҖ", - "advanced_settings_readonly_mode_subtitle": "ĐŖĐēĐģŅŽŅ‡Đ°Đĩ Ņ€ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ, ҃ ŅĐēŅ–Đŧ Ņ„ĐžŅ‚Đ° ĐŧĐžĐļĐŊа Ņ‚ĐžĐģҌĐēŅ– ĐŋŅ€Đ°ĐŗĐģŅĐ´Đ°Ņ†ŅŒ; Đ˛Ņ‹ĐąĐ°Ņ€ ĐŊĐĩĐēаĐģҌĐēŅ–Ņ… Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņž, Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ, ĐēĐ°ŅŅ‚Ņ‹ĐŊĐŗ Ņ– Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹Ņ. ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ/Đ˛Ņ‹ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ ĐŧĐžĐļĐŊа ĐŋŅ€Đ°Đˇ Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа ĐŊа ĐŗĐ°ĐģĐžŅžĐŊŅ‹Đŧ ŅĐēŅ€Đ°ĐŊĐĩ", - "advanced_settings_readonly_mode_title": "Đ ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ Ņ‡Ņ‹Ņ‚Đ°ĐŊĐŊŅ", - "advanced_settings_sync_remote_deletions_subtitle": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа Đ˛Ņ‹Đ´Đ°ĐģŅŅ†ŅŒ айО адĐŊĐ°ŅžĐģŅŅ†ŅŒ аб’ĐĩĐēŅ‚ ĐŊа ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ, ĐēаĐģŅ– ĐŗŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ Đ˛Ņ‹ĐēĐžĐŊваĐĩŅ†Ņ†Đ° Ņž Đ˛ŅĐą-Ņ–ĐŊŅ‚ŅŅ€Ņ„ĐĩĐšŅĐĩ", - "advanced_settings_sync_remote_deletions_title": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Đ˛Đ°Ņ†ŅŒ аддаĐģĐĩĐŊŅ‹Ņ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– [ЭКСПЕРĐĢМЕНĐĸАЛĐŦНА]", "advanced_settings_tile_subtitle": "ĐŸĐ°ŅˆŅ‹Ņ€Đ°ĐŊŅ‹Ņ ĐŊаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "advanced_settings_troubleshooting_subtitle": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ´Đ°Đ´Đ°Ņ‚ĐēĐžĐ˛Ņ‹Ņ Ņ„ŅƒĐŊĐē҆ҋҖ Đ´ĐģŅ ĐŋĐžŅˆŅƒĐē҃ Ņ– Đ˛Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊŅ ĐŊĐĩĐŋаĐģадаĐē", - "advanced_settings_troubleshooting_title": "ĐŸĐžŅˆŅƒĐē Ņ– Đ˛Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŊĐĩĐŋаĐģадаĐē", + "advanced_settings_troubleshooting_subtitle": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ´Đ°Đ´Đ°Ņ‚ĐēĐžĐ˛Ņ‹Ņ Ņ„ŅƒĐŊĐē҆ҋҖ Đ´ĐģŅ Đ˛Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊŅ ĐŊĐĩĐŋаĐģадаĐē", + "advanced_settings_troubleshooting_title": "Đ’Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŊĐĩĐŋаĐģадаĐē", "age_months": "ĐŖĐˇŅ€ĐžŅŅ‚ {months, plural, one {# ĐŧĐĩŅŅŅ†} few {# ĐŧĐĩŅŅŅ†Ņ‹} many {# ĐŧĐĩŅŅŅ†Đ°Ņž} other {# ĐŧĐĩŅŅŅ†Đ°Ņž}}", "age_year_months": "ĐŖĐˇŅ€ĐžŅŅ‚ 1 ĐŗĐžĐ´, {months, plural, one {# ĐŧĐĩŅŅŅ†} few {# ĐŧĐĩŅŅŅ†Ņ‹} many {# ĐŧĐĩŅŅŅ†Đ°Ņž} other {# ĐŧĐĩŅŅŅ†Đ°Ņž}}", "age_years": "{years, plural, other {ĐŖĐˇŅ€ĐžŅŅ‚ #}}", - "album": "АĐģŅŒĐąĐžĐŧ", "album_added": "АĐģŅŒĐąĐžĐŧ дададСĐĩĐŊŅ‹", - "album_added_notification_setting_description": "ĐŅ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°Ņ†ŅŒ ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ ĐŊа ŅĐģĐĩĐēŅ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅˆŅ‚Ņƒ, ĐēаĐģŅ– Đ˛Đ°Ņ Đ´Đ°Đ´Đ°ŅŽŅ†ŅŒ ҃ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹ аĐģŅŒĐąĐžĐŧ", "album_cover_updated": "ВоĐēĐģадĐēа аĐģŅŒĐąĐžĐŧа айĐŊĐžŅžĐģĐĩĐŊа", "album_delete_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ {album}?", "album_delete_confirmation_description": "КаĐģŅ– ĐŗŅŅ‚Ņ‹ аĐģŅŒĐąĐžĐŧ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹, Ņ–ĐŊŅˆŅ‹Ņ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ– йОĐģҌ҈ ĐŊĐĩ СĐŧĐžĐŗŅƒŅ†ŅŒ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ да ŅĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋ.", "album_deleted": "АĐģŅŒĐąĐžĐŧ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", + "album_info_card_backup_album_excluded": "ВĐĢКЛЮЧАНĐĢ", + "album_info_card_backup_album_included": "ĐŖĐšĐ›ĐŽĐ§ĐĐĐĢ", "album_info_updated": "ІĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ ĐŋŅ€Đ° аĐģŅŒĐąĐžĐŧ айĐŊĐžŅžĐģĐĩĐŊа", + "album_leave": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ аĐģŅŒĐąĐžĐŧ?", + "album_leave_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŋаĐēŅ–ĐŊŅƒŅ†ŅŒ {album}?", "album_name": "Назва аĐģŅŒĐąĐžĐŧа", "album_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ аĐģŅŒĐąĐžĐŧа", "album_remove_user": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа?", "album_remove_user_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {user}?", "album_search_not_found": "Па Đ˛Đ°ŅˆŅ‹Đŧ СаĐŋҋ҆Đĩ ĐŊĐĩ СĐŊОКдСĐĩĐŊа аĐģŅŒĐąĐžĐŧĐ°Ņž", - "album_share_no_users": "ЗдаĐĩŅ†Ņ†Đ°, Đ˛Ņ‹ Đ°ĐąĐ°ĐŗŅƒĐģŅ–ĐģŅ– ĐŗŅŅ‚Ņ‹ аĐģŅŒĐąĐžĐŧ С ŅƒŅŅ–ĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧŅ–, айО Ņž Đ˛Đ°Ņ ĐŊŅĐŧа ĐŊŅ–Đ˛ĐžĐ´ĐŊĐ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа, С ŅĐēŅ–Đŧ ĐŧĐžĐļĐŊа ŅĐŗĐž Đ°ĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒ.", - "album_summary": "ЗводĐēа аĐģŅŒĐąĐžĐŧа", + "album_share_no_users": "ЗдаĐĩŅ†Ņ†Đ°, Đ˛Ņ‹ ĐŋĐ°Đ´ĐˇŅĐģŅ–ĐģŅ–ŅŅ ĐŗŅŅ‚Ņ‹Đŧ аĐģŅŒĐąĐžĐŧаĐŧ С ŅƒŅŅ–ĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧŅ–, айО Ņž Đ˛Đ°Ņ ĐŊŅĐŧа ĐŊŅ–Đ˛ĐžĐ´ĐŊĐ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа, С ŅĐēŅ–Đŧ ĐŧĐžĐļĐŊа ĐŋĐ°Đ´ĐˇŅĐģŅ–Ņ†Ņ†Đ°.", "album_updated": "АĐģŅŒĐąĐžĐŧ айĐŊĐžŅžĐģĐĩĐŊŅ‹", - "album_updated_setting_description": "ĐŅ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°Ņ†ŅŒ ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ ĐŊа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅˆŅ‚Ņƒ, ĐēаĐģŅ– Ņž Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Đŧ аĐģŅŒĐąĐžĐŧĐĩ Đˇâ€™ŅŅžĐģŅŅŽŅ†Ņ†Đ° ĐŊĐžĐ˛Ņ‹Ņ аб’ĐĩĐē҂ҋ", - "album_upload_assets": "ЗаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ С ĐēаĐŧĐŋâ€™ŅŽŅ‚Đ°Ņ€Đ° Ņ– Đ´Đ°Đ´Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧ", + "album_user_left": "Đ’Ņ‹ ĐŋаĐēŅ–ĐŊ҃ĐģŅ– {album}", + "album_user_removed": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē {user} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", + "album_viewer_appbar_delete_confirm": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ аĐģŅŒĐąĐžĐŧ ŅĐ° ŅĐ˛Đ°ĐšĐŗĐž ŅžĐģŅ–ĐēĐžĐ˛Đ°ĐŗĐ° СаĐŋŅ–ŅŅƒ?", "album_viewer_appbar_share_err_delete": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_err_leave": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐŋаĐēŅ–ĐŊŅƒŅ†ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_err_title": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŊĐ°ĐˇĐ˛Ņƒ аĐģŅŒĐąĐžĐŧа", + "album_viewer_appbar_share_leave": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_to": "ĐĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒ С", "album_viewer_page_share_add_users": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž", "album_with_link_access": "ДазвоĐģŅ–Ņ†ŅŒ ŅƒŅŅ–Đŧ, Ņ…Ņ‚Đž ĐŧаĐĩ ҁĐŋĐ°ŅŅ‹ĐģĐē҃, ĐąĐ°Ņ‡Ņ‹Ņ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– ĐģŅŽĐ´ĐˇĐĩĐš ҃ ĐŗŅŅ‚Ņ‹Đŧ аĐģŅŒĐąĐžĐŧĐĩ.", "albums": "АĐģŅŒĐąĐžĐŧŅ‹", "albums_count": "{count, plural, one {1 аĐģŅŒĐąĐžĐŧ} few {{count, number} аĐģŅŒĐąĐžĐŧŅ‹} many {{count, number} аĐģŅŒĐąĐžĐŧĐ°Ņž} other {{count, number} аĐģŅŒĐąĐžĐŧĐ°Ņž}}", "albums_default_sort_order": "ĐŸŅ€Đ°Đ´Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊŅ‹ ĐŋĐ°Ņ€Đ°Đ´Đ°Đē ŅĐ°Ņ€Ņ‚Đ°Đ˛Đ°ĐŊĐŊŅ аĐģŅŒĐąĐžĐŧĐ°Ņž", - "albums_default_sort_order_description": "ПĐĩŅ€ŅˆĐ°ĐŋĐ°Ņ‡Đ°Ņ‚ĐēĐžĐ˛Ņ‹ ĐŋĐ°Ņ€Đ°Đ´Đ°Đē ŅĐ°Ņ€Ņ‚Đ°Đ˛Đ°ĐŊĐŊŅ аб’ĐĩĐēŅ‚Đ°Ņž ĐŋҀҋ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ– ĐŊĐžĐ˛Ņ‹Ņ… аĐģŅŒĐąĐžĐŧĐ°Ņž.", - "albums_feature_description": "КаĐģĐĩĐē҆ҋҖ аб’ĐĩĐēŅ‚Đ°Ņž, ŅĐēŅ–Ņ ĐŧĐžĐļĐŊа Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°Ņ†ŅŒ С Ņ–ĐŊŅˆŅ‹ĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧŅ–.", "albums_on_device_count": "АĐģŅŒĐąĐžĐŧŅ‹ ĐŊа ĐŋҀҋĐģадСĐĩ ({count})", "all": "ĐŖŅĐĩ", "all_albums": "ĐŖŅĐĩ аĐģŅŒĐąĐžĐŧŅ‹", "all_people": "ĐŖŅĐĩ ĐģŅŽĐ´ĐˇŅ–", - "all_photos": "ĐŖŅĐĩ Ņ„ĐžŅ‚Đ°", "all_videos": "ĐŖŅĐĩ Đ˛Ņ–Đ´ŅĐ°", "allow_dark_mode": "ДазвоĐģŅ–Ņ†ŅŒ ҆ґĐŧĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ", - "allow_public_user_to_download": "ДазвоĐģŅ–Ņ†ŅŒ ĐŋŅƒĐąĐģҖ҇ĐŊаĐŧ҃ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē҃ ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°Ņ†ŅŒ", - "allow_public_user_to_upload": "ДазвоĐģŅ–Ņ†ŅŒ ĐŋŅƒĐąĐģҖ҇ĐŊаĐŧ҃ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē҃ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°Ņ†ŅŒ", - "allowed": "ДазвоĐģĐĩĐŊа", + "allow_edits": "ДазвоĐģŅ–Ņ†ŅŒ Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊĐŊĐĩ", "alt_text_qr_code": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ QR-ĐēОда", - "always_keep": "Đ—Đ°ŅžŅŅ‘Đ´Ņ‹ ĐŋаĐēŅ–Đ´Đ°Ņ†ŅŒ", - "always_keep_photos_hint": "ÂĢĐ’Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°Âģ ĐŋаĐēŅ–ĐŊĐĩ ŅžŅĐĩ Ņ„ĐžŅ‚Đ° ĐŊа ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ.", - "always_keep_videos_hint": "ÂĢĐ’Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°Âģ ĐŋаĐēŅ–ĐŊĐĩ ŅžŅĐĩ Đ˛Ņ–Đ´ŅĐ° ĐŊа ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ.", + "anti_clockwise": "ĐĄŅƒĐŋŅ€Đ°Ņ†ŅŒ ĐŗĐ°Đ´ĐˇŅ–ĐŊĐŊŅ–ĐēаваК ŅŅ‚Ņ€ŅĐģĐēŅ–", "api_key": "КĐģŅŽŅ‡ API", - "api_key_description": "Đ“ŅŅ‚Đ° СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐŋаĐēаСаĐŊа Ņ‚ĐžĐģҌĐēŅ– Đ°Đ´ĐˇŅ–ĐŊ Ņ€Đ°Đˇ. ĐĄĐēаĐŋŅ–Ņ€ŅƒĐšŅ†Đĩ ŅĐŗĐž ĐŋĐĩŅ€Đ°Đ´ СаĐēҀҋ҆҆ґĐŧ аĐēĐŊа.", "api_key_empty": "Назва ĐēĐģŅŽŅ‡Đ° API ĐŊĐĩ ĐŋĐ°Đ˛Ņ–ĐŊĐŊа ĐąŅ‹Ņ†ŅŒ ĐŋŅƒŅŅ‚ĐžĐš", "api_keys": "КĐģŅŽŅ‡Ņ‹ API", - "app_architecture_variant": "Đ’Đ°Ņ€Ņ‹ŅĐŊŅ‚ (ĐŅ€Ņ…Ņ–Ņ‚ŅĐēŅ‚ŅƒŅ€Đ°)", "app_bar_signout_dialog_content": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹ĐšŅŅ†Ņ–?", + "app_bar_signout_dialog_ok": "ĐĸаĐē", "app_bar_signout_dialog_title": "Đ’Ņ‹ĐšŅŅ†Ņ–", - "app_download_links": "ĐĄĐŋĐ°ŅŅ‹ĐģĐēŅ– ĐŊа ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", "app_settings": "НаĐģĐ°Đ´Ņ‹ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "app_stores": "ĐšŅ€Đ°ĐŧŅ‹ ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ", - "app_update_available": "Đ”Đ°ŅŅ‚ŅƒĐŋĐŊа айĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "appears_in": "Đ—â€™ŅŅžĐģŅĐĩŅ†Ņ†Đ° Ņž", - "apply_count": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅ–Ņ†ŅŒ ({count, number})", "archive": "ĐŅ€Ņ…Ņ–Ņž", - "archive_action_prompt": "{count} дададСĐĩĐŊа Ņž ĐŅ€Ņ…Ņ–Ņž", - "archive_or_unarchive_photo": "ĐŅ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°Ņ†ŅŒ айО Ņ€Đ°ĐˇĐ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ°", + "archive_page_title": "ĐŅ€Ņ…Ņ–Ņž ({count})", "archive_size": "ПаĐŧĐĩŅ€ Đ°Ņ€Ņ…Ņ–Đ˛Đ°", - "archive_size_description": "НаĐģĐ°Đ´ĐˇŅ–Ņ†ŅŒ ĐŋаĐŧĐĩŅ€ Đ°Ņ€Ņ…Ņ–Đ˛Đ° Đ´ĐģŅ ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅŅž (҃ Đ“Ņ–Đ‘)", - "archived": "ĐŅ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°ĐŊŅ‹Ņ", - "archived_count": "{count, plural, other {ĐŅ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°ĐŊа #}}", "are_these_the_same_person": "ĐĻŅ– ĐŗŅŅ‚Đ° Đ°Đ´ĐˇŅ–ĐŊ Ņ– Ņ‚ĐžĐš Đļа Ņ‡Đ°ĐģавĐĩĐē?", "are_you_sure_to_do_this": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŗŅŅ‚Đ° ĐˇŅ€Đ°ĐąŅ–Ņ†ŅŒ?", "asset_added_to_album": "ДададзĐĩĐŊа Ņž аĐģŅŒĐąĐžĐŧ", "asset_adding_to_album": "ДадаваĐŊĐŊĐĩ Ņž аĐģŅŒĐąĐžĐŧâ€Ļ", - "asset_created": "Аб’ĐĩĐēŅ‚ ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊŅ‹", - "asset_day_count": "{date}: {count, plural, one {# аб’ĐĩĐēŅ‚} few {# аб’ĐĩĐē҂ҋ} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}}", - "asset_description_updated": "АĐŋŅ–ŅĐ°ĐŊĐŊĐĩ аб’ĐĩĐēŅ‚Đ° айĐŊĐžŅžĐģĐĩĐŊа", - "asset_hashing": "ĐĨŅŅˆĐ°Đ˛Đ°ĐŊĐŊĐĩâ€Ļ", - "asset_list_group_by_sub_title": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đŋа", - "asset_list_layout_settings_group_by": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ Đŋа", - "asset_list_layout_settings_group_by_month_day": "МĐĩŅŅŅ†Ņ‹ + Đ´ĐŊŅ–", - "asset_list_layout_sub_title": "МаĐēĐĩŅ‚", - "asset_list_settings_subtitle": "НаĐģĐ°Đ´Ņ‹ ĐŧаĐēĐĩŅ‚Đ° ҁĐĩŅ‚ĐēŅ– Ņ„ĐžŅ‚Đ°", - "asset_list_settings_title": "ĐĄĐĩŅ‚Đēа Ņ„ĐžŅ‚Đ°", - "asset_not_found_on_device_android": "Аб’ĐĩĐēŅ‚ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹ ĐŊа ĐŋҀҋĐģадСĐĩ", - "asset_not_found_on_device_ios": "Аб’ĐĩĐēŅ‚ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹ ĐŊа ĐŋҀҋĐģадСĐĩ. КаĐģŅ– Đ˛Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Đĩ҆ĐĩŅŅ iCloud, аб’ĐĩĐēŅ‚ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ С-Са ĐŋĐ°ŅˆĐēОдĐļаĐŊĐ°ĐŗĐ° Ņ„Đ°ĐšĐģа Ņž iCloud", - "asset_not_found_on_icloud": "Аб’ĐĩĐēŅ‚ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹ Ņž iCloud. Аб’ĐĩĐēŅ‚ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ С-Са ĐŋĐ°ŅˆĐēОдĐļаĐŊĐ°ĐŗĐ° Ņ„Đ°ĐšĐģа Ņž iCloud", - "asset_offline": "Аб’ĐĩĐēŅ‚ Đŋа-Са ҁĐĩŅ‚ĐēаК", - "asset_offline_description": "Đ“ŅŅ‚Ņ‹ СĐŊĐĩ҈ĐŊŅ– аб’ĐĩĐēŅ‚ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹ ĐŊа Đ´Ņ‹ŅĐē҃. Đ—Đ˛ŅŅ€ĐŊҖ҆ĐĩŅŅ да адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ° Immich Đŋа даĐŋаĐŧĐžĐŗŅƒ.", "asset_skipped": "ĐŸŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊа", "asset_skipped_in_trash": "ĐŖ ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", - "asset_troubleshoot": "ĐŸĐžŅˆŅƒĐē Ņ– Đ˛Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŊĐĩĐŋаĐģадаĐē С аб’ĐĩĐēŅ‚Đ°Đŧ", "asset_uploaded": "ЗаĐŋаĐŧĐŋаваĐŊа", "asset_uploading": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩâ€Ļ", - "asset_viewer_settings_subtitle": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ ĐŗĐ°ĐģĐĩŅ€ŅŅ–", - "asset_viewer_settings_title": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ аб’ĐĩĐēŅ‚Đ°", - "assets": "Аб’ĐĩĐē҂ҋ", - "assets_added_to_album_count": "ĐŖ аĐģŅŒĐąĐžĐŧ {count, plural, one {дададСĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {дададСĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {дададСĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {дададСĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°}}", - "assets_added_to_albums_count": "{assetTotal, plural, one {ДададзĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚ ҃} few {ДададзĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ Ņž} many {ДададзĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž ҃} other {ДададзĐĩĐŊа # аб’ĐĩĐēŅ‚Đ° Ņž}} {albumTotal, plural, one {# аĐģŅŒĐąĐžĐŧ} few {# аĐģŅŒĐąĐžĐŧŅ‹} many {# аĐģŅŒĐąĐžĐŧĐ°Ņž} other {# аĐģŅŒĐąĐžĐŧа}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Аб’ĐĩĐēŅ‚ ĐŊĐĩ ĐŧĐžĐļа} few {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ} many {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ} other {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ}} ĐąŅ‹Ņ†ŅŒ дададСĐĩĐŊŅ‹ Ņž аĐģŅŒĐąĐžĐŧ", - "assets_cannot_be_added_to_albums": "{count, plural, one {Аб’ĐĩĐēŅ‚ ĐŊĐĩ ĐŧĐžĐļа} few {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ} many {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ} other {Аб’ĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ†ŅŒ}} ĐąŅ‹Ņ†ŅŒ дададСĐĩĐŊŅ‹ Ņž ĐŊŅ–Đ˛ĐžĐ´ĐŊŅ‹ С аĐģŅŒĐąĐžĐŧĐ°Ņž", - "assets_count": "{count, plural, one {# аб’ĐĩĐēŅ‚} few {# аб’ĐĩĐē҂ҋ} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}}", - "assets_moved_to_trash_count": "{count, plural, one {# аб’ĐĩĐēŅ‚ ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ} few {# аб’ĐĩĐē҂ҋ ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ} many {# аб’ĐĩĐēŅ‚Đ°Ņž ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ} other {# аб’ĐĩĐēŅ‚Đ° ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ}}", - "assets_permanently_deleted_count": "{count, plural, one {ĐĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {ĐĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {ĐĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {ĐĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°}}", - "assets_removed_count": "{count, plural, one {Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°}}", - "assets_restore_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҃ҁĐĩ аб’ĐĩĐē҂ҋ ŅĐ° ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ? Đ“ŅŅ‚Đ°Đĩ дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đ´Ņ€Đ°ĐąŅ–Ņ†ŅŒ! Аб’ĐĩĐē҂ҋ Đŋа-Са ҁĐĩŅ‚ĐēаК Ņ‚Đ°ĐēŅ–Đŧ ҁĐŋĐžŅĐ°ĐąĐ°Đŧ адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа.", - "assets_restored_count": "{count, plural, one {АдĐŊĐžŅžĐģĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {АдĐŊĐžŅžĐģĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {АдĐŊĐžŅžĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {АдĐŊĐžŅžĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°}}", - "assets_trashed": "{count} аб’ĐĩĐēŅ‚(Đ°Ņž) ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "assets_trashed_count": "{count, plural, one {# аб’ĐĩĐēŅ‚ ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹} few {# аб’ĐĩĐē҂ҋ ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹} many {# аб’ĐĩĐēŅ‚Đ°Ņž ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа} other {# аб’ĐĩĐēŅ‚Đ° ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа}} Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "assets_were_part_of_album_count": "{count, plural, one {Аб’ĐĩĐēŅ‚ ҃ĐļĐž ĐąŅ‹Ņž} few {Аб’ĐĩĐē҂ҋ ŅžĐļĐž ĐąŅ‹ĐģŅ–} many {Аб’ĐĩĐē҂ҋ ŅžĐļĐž ĐąŅ‹ĐģŅ–} other {Аб’ĐĩĐē҂ҋ ŅžĐļĐž ĐąŅ‹ĐģŅ–}} Ņ‡Đ°ŅŅ‚ĐēаК аĐģŅŒĐąĐžĐŧа", "assets_were_part_of_albums_count": "{count, plural, one {АĐēŅ‚Ņ‹Ņž ҃ĐļĐž ĐąŅ‹Ņž} other {АĐēŅ‚Ņ‹Đ˛Ņ‹ ҃ĐļĐž ĐąŅ‹ĐģŅ–}} Ņ‡Đ°ŅŅ‚ĐēаК аĐģŅŒĐąĐžĐŧ҃", "authorized_devices": "ĐŅžŅ‚Đ°Ņ€Ņ‹ĐˇĐ°Đ˛Đ°ĐŊŅ‹Ņ ĐŋҀҋĐģĐ°Đ´Ņ‹", "automatic_endpoint_switching_subtitle": "ПадĐēĐģŅŽŅ‡Đ°Ņ†Ņ†Đ° ĐģаĐēаĐģҌĐŊа Đŋа Đ˛Ņ‹ĐģŅƒŅ‡Đ°ĐŊŅ‹Đŧ Wi-Fi, ĐēаĐģŅ– ĐŗŅŅ‚Đ° ĐŧĐ°ĐŗŅ‡Ņ‹Đŧа, Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ аĐģŅŒŅ‚ŅŅ€ĐŊĐ°Ņ‚Ņ‹ŅžĐŊŅ‹Ņ ĐŋадĐēĐģŅŽŅ‡ŅĐŊĐŊŅ Ņž Ņ–ĐŊŅˆŅ‹Ņ… ĐŧĐĩŅŅ†Đ°Ņ…", "automatic_endpoint_switching_title": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊаĐĩ ĐŋĐĩŅ€Đ°ĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ URL", - "autoplay_slideshow": "ĐŅžŅ‚Đ°ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ҁĐģаКд-ŅˆĐžŅƒ", "back": "Назад", - "back_close_deselect": "Назад, СаĐēŅ€Ņ‹Ņ†ŅŒ айО ҁĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ĐąĐ°Ņ€", - "background_location_permission": "ДазвоĐģ ĐŊа Đ´ĐžŅŅ‚ŅƒĐŋ да ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊŅ Ņž Ņ„ĐžĐŊĐĩ", - "background_location_permission_content": "Каб ĐŋĐĩŅ€Đ°ĐēĐģŅŽŅ‡Đ°Ņ†ŅŒ ҁĐĩŅ‚ĐēŅ– ĐŋҀҋ ĐŋŅ€Đ°Ņ†Ņ‹ Ņž Ņ„ĐžĐŊĐĩ, Immich ĐŋĐ°Đ˛Ņ–ĐŊĐĩĐŊ *ĐˇĐ°ŅžŅŅ‘Đ´Ņ‹* ĐŧĐĩŅ†ŅŒ даĐēĐģадĐŊŅ‹ Đ´ĐžŅŅ‚ŅƒĐŋ да ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊŅ, Đēай ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧа ĐŧĐ°ĐŗĐģа ĐŋŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°Ņ†ŅŒ ĐŊĐ°ĐˇĐ˛Ņƒ ҁĐĩŅ‚ĐēŅ– Wi-Fi", - "background_options": "ОĐŋ҆ҋҖ Ņ„ĐžĐŊ҃", - "backup": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ", - "backup_album_selection_page_albums_tap": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ, Đēай ҃ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ, Đ´Đ˛ĐžĐšŅ‡Ņ‹ ĐŊĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ, Đēай Đ˛Ņ‹ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ", - "backup_album_selection_page_assets_scatter": "Аб’ĐĩĐē҂ҋ ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ Ņ€Đ°ŅĐēŅ–Đ´Đ°ĐŊŅ‹Ņ Đŋа ĐŊĐĩĐēаĐģҌĐēŅ–Ņ… аĐģŅŒĐąĐžĐŧĐ°Ņ…. ĐĸаĐŧ҃ аĐģŅŒĐąĐžĐŧŅ‹ ĐŧĐžĐļĐŊа ŅžĐēĐģŅŽŅ‡Đ°Ņ†ŅŒ айО Đ˛Ņ‹ĐēĐģŅŽŅ‡Đ°Ņ†ŅŒ ҃ ĐŋŅ€Đ°Ņ†ŅŅĐĩ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ.", - "backup_album_selection_page_select_albums": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", - "backup_album_selection_page_selection_info": "ЗвĐĩҁ҂ĐēŅ– ĐŋŅ€Đ° Đ˛Ņ‹ĐąĐ°Ņ€", - "backup_albums_sync": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ аĐģŅŒĐąĐžĐŧĐ°Ņž Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "backup_background_service_complete_notification": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ аб’ĐĩĐēŅ‚Đ°Ņž СавĐĩŅ€ŅˆĐ°ĐŊа", - "backup_background_service_default_notification": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа ĐŊĐžĐ˛Ņ‹Ņ… аб’ĐĩĐēŅ‚Đ°Ņžâ€Ļ", - "backup_background_service_in_progress_notification": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ аб’ĐĩĐēŅ‚Đ°Ņžâ€Ļ", - "backup_controller_page_albums": "АĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "backup_controller_page_background_battery_info_link": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅĐē", - "backup_controller_page_background_battery_info_message": "ДĐģŅ ĐŊаКĐģĐĩĐŋŅˆĐ°Đš ĐŋŅ€Đ°Ņ†Ņ‹ Ņ„ĐžĐŊĐ°Đ˛Đ°ĐŗĐ° Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ адĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ аĐŋ҂ҋĐŧŅ–ĐˇĐ°Ņ†Ņ‹Ņ– аĐē҃Đŧ҃ĐģŅŅ‚Đ°Ņ€Đ°, ŅĐēŅ–Ņ айĐŧŅĐļĐžŅžĐ˛Đ°ŅŽŅ†ŅŒ Ņ„ĐžĐŊĐ°Đ˛ŅƒŅŽ аĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ Immich.\n\nĐ“ŅŅ‚Đ° СаĐģĐĩĐļŅ‹Ņ†ŅŒ ад ĐŋҀҋĐģĐ°Đ´Ņ‹, Ņ‚Đ°Đŧ҃ СĐŊĐ°ĐšĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ‚Ņ€ŅĐąĐŊŅƒŅŽ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹ŅŽ Ņž Đ˛Ņ‹Ņ‚Đ˛ĐžŅ€Ņ†Ņ‹ Đ˛Đ°ŅˆĐ°Đš ĐŋҀҋĐģĐ°Đ´Ņ‹.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "АĐŋ҂ҋĐŧŅ–ĐˇĐ°Ņ†Ņ‹Ņ ĐąĐ°Ņ‚Đ°Ņ€ŅŅ–", - "backup_controller_page_background_delay": "Đ—Đ°Ņ‚Ņ€Ņ‹ĐŧĐēа Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ ĐŊĐžĐ˛Ņ‹Ņ… аб’ĐĩĐēŅ‚Đ°Ņž: {duration}", - "backup_controller_page_backup": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ", - "backup_controller_page_backup_selected": "Đ’Ņ‹ĐąŅ€Đ°ĐŊа: ", - "backup_controller_page_backup_sub": "Đ ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Ņ‹Ņ ĐēĐžĐŋŅ–Ņ– Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "backup_controller_page_excluded": "Đ’Ņ‹ĐēĐģŅŽŅ‡Đ°ĐŊŅ‹: ", - "backup_controller_page_none_selected": "ĐŅ–Ņ‡ĐžĐŗĐ° ĐŊĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊа", - "backup_controller_page_remainder": "Đ—Đ°ŅŅ‚Đ°ĐģĐžŅŅ", - "backup_controller_page_remainder_sub": "Đ¤ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° С Đ˛Ņ‹ĐąĐ°Ņ€Ņƒ, ŅĐēŅ–Ņ ĐˇĐ°ŅŅ‚Đ°ĐģŅ–ŅŅ Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "backup_controller_page_server_storage": "ĐĄŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "backup_controller_page_storage_format": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊа {used} С {total}", - "backup_controller_page_to_backup": "АĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "backup_controller_page_total_sub": "ĐŖŅĐĩ ŅžĐŊŅ–ĐēаĐģҌĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° С Đ˛Ņ‹ĐąŅ€Đ°ĐŊҋ҅ аĐģŅŒĐąĐžĐŧĐ°Ņž", - "backup_error_sync_failed": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Đ˛Đ°Ņ†ŅŒ. НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа аĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ.", - "backup_info_card_assets": "аб’ĐĩĐē҂ҋ", - "backup_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "backup_settings_subtitle": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "backward": "Назад", - "battery_optimization_backup_reliability": "АдĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩ аĐŋ҂ҋĐŧŅ–ĐˇĐ°Ņ†Ņ‹Ņ– ĐąĐ°Ņ‚Đ°Ņ€ŅŅ– ĐŧĐžĐļа ĐŋĐ°Đ˛Ņ‹ŅŅ–Ņ†ŅŒ ĐŊадСĐĩĐšĐŊĐ°ŅŅ†ŅŒ Ņ„ĐžĐŊĐ°Đ˛Đ°ĐŗĐ° Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "biometric_auth_enabled": "Đ‘Ņ–ŅĐŧĐĩ҂Ҁҋ҇ĐŊĐ°Ņ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ ŅžĐēĐģŅŽŅ‡Đ°ĐŊа", - "biometric_locked_out": "ĐŖ Đ˛Đ°Ņ ĐŊŅĐŧа Đ´ĐžŅŅ‚ŅƒĐŋ҃ да ĐąŅ–ŅĐŧĐĩ҂Ҁҋ҇ĐŊаК Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ–", - "biometric_no_options": "Đ‘Ņ–ŅĐŧĐĩ҂Ҁҋ҇ĐŊŅ‹Ņ Đ˛Đ°Ņ€Ņ‹ŅĐŊ҂ҋ ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ", - "biometric_not_available": "Đ‘Ņ–ŅĐŧĐĩ҂Ҁҋ҇ĐŊĐ°Ņ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹Ņ ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ ĐŊа ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ", - "birthdate_set_description": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° Đ´ĐģŅ Đ˛Ņ‹ĐģŅ–Ņ‡ŅĐŊĐŊŅ ŅžĐˇŅ€ĐžŅŅ‚Ņƒ ĐŗŅŅ‚Đ°ĐŗĐ° Ņ‡Đ°ĐģавĐĩĐēа ĐŊа ĐŧĐžĐŧаĐŊŅ‚ Ņ„ĐžŅ‚Đ°.", - "blurred_background": "РаСĐŧҋ҂ҋ Ņ„ĐžĐŊ", - "browse_templates": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊŅ‹", - "bugs_and_feature_requests": "ПаĐŧŅ‹ĐģĐēŅ– Ņ– СаĐŋҋ҂ҋ ĐŊа Ņ„ŅƒĐŊĐē҆ҋҖ", - "build": "Đ—ĐąĐžŅ€Đēа", - "build_image": "Đ’ĐžĐąŅ€Đ°Đˇ ĐˇĐąĐžŅ€ĐēŅ–", - "bulk_delete_duplicates_confirmation": "ХаĐŋŅ€Đ°ŅžĐ´Ņ‹ ĐŧĐ°ŅĐ°Đ˛Đ° Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹ аб’ĐĩĐēŅ‚} few {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ} many {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž} other {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž}}? ĐŖ ĐēĐžĐļĐŊаК ĐŗŅ€ŅƒĐŋĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹ ŅĐ°ĐŧŅ‹ Đ˛ŅĐģŅ–ĐēŅ– аб’ĐĩĐēŅ‚, а ŅžŅĐĩ Ņ–ĐŊŅˆŅ‹Ņ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžŅŅ‘Đ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹. Đ“ŅŅ‚Đ°Đĩ дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа адĐŧŅĐŊŅ–Ņ†ŅŒ!", - "bulk_keep_duplicates_confirmation": "ХаĐŋŅ€Đ°ŅžĐ´Ņ‹ ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ {count, plural, one {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹ аб’ĐĩĐēŅ‚} few {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ} many {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž} other {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž}}? Đ“ŅŅ‚Đ° Đ˛Ņ‹Ņ€Đ°ŅˆŅ‹Ņ†ŅŒ ҃ҁĐĩ ĐŗŅ€ŅƒĐŋŅ‹ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž ĐąĐĩС Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ.", - "bulk_trash_duplicates_confirmation": "ХаĐŋŅ€Đ°ŅžĐ´Ņ‹ ĐŧĐ°ŅĐ°Đ˛Đ° ĐŋĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ {count, plural, one {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹ аб’ĐĩĐēŅ‚} few {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ} many {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž} other {# Đ´ŅƒĐąĐģŅ–Ņ€Đ°Đ˛Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž}}? ĐŖ ĐēĐžĐļĐŊаК ĐŗŅ€ŅƒĐŋĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹ ŅĐ°ĐŧŅ‹ Đ˛ŅĐģŅ–ĐēŅ– аб’ĐĩĐēŅ‚, а ŅžŅĐĩ Ņ–ĐŊŅˆŅ‹Ņ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊŅ‹ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ.", + "backup_album_selection_page_albums_device": "АĐģŅŒĐąĐžĐŧŅ‹ ĐŊа ĐŋҀҋĐģадСĐĩ ({count})", + "backup_all": "ĐŖŅĐĩ", + "backup_controller_page_background_wifi": "ĐĸĐžĐģҌĐēŅ– ĐŋŅ€Đ°Đˇ Wi-Fi", "buy": "ĐšŅƒĐŋŅ–Ņ†ŅŒ Immich", - "camera": "КаĐŧĐĩŅ€Đ°", - "camera_brand": "ĐœĐ°Ņ€Đēа ĐēаĐŧĐĩҀҋ", - "camera_model": "ĐœĐ°Đ´ŅĐģҌ ĐēаĐŧĐĩҀҋ", + "cache_settings_clear_cache_button": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ĐēŅŅˆ", + "cache_settings_tile_title": "ЛаĐēаĐģҌĐŊаĐĩ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", "cancel": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ", "cancel_search": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ ĐŋĐžŅˆŅƒĐē", "canceled": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°ĐŊа", - "canceling": "ĐĄĐēĐ°ŅĐžŅžĐ˛Đ°ĐŊĐŊĐĩ", - "cannot_merge_people": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đąâ€™ŅĐ´ĐŊĐ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "cannot_undo_this_action": "Đ“ŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đ´Ņ€Đ°ĐąŅ–Ņ†ŅŒ!", - "cannot_update_the_description": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "cast": "ĐšĐ°ŅŅ‚Ņ‹ĐŊĐŗ", - "cast_description": "НаĐģĐ°Đ´ĐˇŅŒŅ†Đĩ Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ ĐŋŅ€Ņ‹ĐˇĐŊĐ°Ņ‡ŅĐŊĐŊŅ– Đ´ĐģŅ ĐēĐ°ŅŅ‚Ņ‹ĐŊĐŗŅƒ", - "change": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ", - "change_date": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ", - "change_description": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "change_display_order": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€Đ°Đ´Đ°Đē адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "change_location": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "change_name": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ Ņ–ĐŧŅ", - "change_name_successfully": "ІĐŧŅ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° СĐŧĐĩĐŊĐĩĐŊа", - "change_password": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "change_password_description": "АйО ĐŗŅŅ‚Đ° Đ˛Đ°Ņˆ ĐŋĐĩŅ€ŅˆŅ‹ ŅžĐ˛Đ°Ņ…ĐžĐ´ ҃ ŅŅ–ŅŅ‚ŅĐŧ҃, айО ĐąŅ‹Ņž ĐˇŅ€ĐžĐąĐģĐĩĐŊŅ‹ СаĐŋҋ҂ ĐŊа СĐŧĐĩĐŊ҃ ĐŋĐ°Ņ€ĐžĐģŅ. ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŊĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ ĐŊŅ–ĐļŅĐš.", - "change_password_form_confirm_password": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ", - "change_password_form_description": "ĐŸŅ€Ņ‹Đ˛Ņ–Ņ‚Đ°ĐŊĐŊĐĩ, {name},\n\nАйО ĐŗŅŅ‚Đ° Đ˛Đ°Ņˆ ĐŋĐĩŅ€ŅˆŅ‹ ŅžĐ˛Đ°Ņ…ĐžĐ´ ҃ ŅŅ–ŅŅ‚ŅĐŧ҃, айО ĐąŅ‹Ņž ĐˇŅ€ĐžĐąĐģĐĩĐŊŅ‹ СаĐŋҋ҂ ĐŊа СĐŧĐĩĐŊ҃ ĐŋĐ°Ņ€ĐžĐģŅ. ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŊĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ ĐŊŅ–ĐļŅĐš.", - "change_password_form_log_out": "Đ’Ņ‹ĐšŅŅ†Ņ– С ŅƒŅŅ–Ņ… Ņ–ĐŊŅˆŅ‹Ņ… ĐŋҀҋĐģад", - "change_password_form_log_out_description": "Đ ŅĐēаĐŧĐĩĐŊĐ´ŅƒĐĩŅ†Ņ†Đ° Đ˛Ņ‹ĐšŅŅ†Ņ– С ŅƒŅŅ–Ņ… Ņ–ĐŊŅˆŅ‹Ņ… ĐŋҀҋĐģад", - "change_password_form_new_password": "ĐĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ", - "change_password_form_password_mismatch": "ĐŸĐ°Ņ€ĐžĐģŅ– ĐŊĐĩ ҁ҃ĐŋĐ°Đ´Đ°ŅŽŅ†ŅŒ", - "change_password_form_reenter_new_password": "ĐŸĐ°ŅžŅ‚ĐžŅ€ĐŊа ŅžĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŊĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ", - "change_pin_code": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ PIN-ĐēОд", - "change_your_password": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "changed_visibility_successfully": "Đ‘Đ°Ņ‡ĐŊĐ°ŅŅ†ŅŒ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° СĐŧĐĩĐŊĐĩĐŊа", - "charging": "Đ—Đ°Ņ€Đ°Đ´Đēа", - "charging_requirement_mobile_backup": "ФОĐŊаваĐĩ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ, Đēай ĐŋҀҋĐģада ĐąŅ‹Đģа ĐŊа ĐˇĐ°Ņ€Đ°Đ´Ņ†Ņ‹", - "check_logs": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ ĐļŅƒŅ€ĐŊаĐģŅ‹", - "checksum": "КаĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃Đŧа", - "choose": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ", - "choose_matching_people_to_merge": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐģŅŽĐ´ĐˇĐĩĐš Đ´ĐģŅ Đ°Đąâ€™ŅĐ´ĐŊаĐŊĐŊŅ", "city": "Đ“ĐžŅ€Đ°Đ´", - "cleanup_confirm_description": "Immich СĐŊĐ°ĐšŅˆĐžŅž {count} аб’ĐĩĐēŅ‚Đ°Ņž (ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊҋ҅ да {date}), ŅĐēŅ–Ņ ĐąŅŅĐŋĐĩ҇ĐŊа ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹Ņ Ņž Ņ€ŅĐˇĐĩŅ€Đ˛ ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҋ. Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐģаĐēаĐģҌĐŊŅ‹Ņ ĐēĐžĐŋŅ–Ņ– С ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģĐ°Đ´Ņ‹?", - "cleanup_confirm_prompt_title": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģĐ°Đ´Ņ‹?", - "cleanup_deleted_assets": "{count} аб’ĐĩĐēŅ‚Đ°Ņž ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ ĐŋҀҋĐģĐ°Đ´Ņ‹", - "cleanup_deleting": "ПĐĩŅ€Đ°ĐŧŅŅˆŅ‡ŅĐŊĐŊĐĩ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ...", - "cleanup_found_assets": "ЗĐŊОКдСĐĩĐŊа {count} аб’ĐĩĐēŅ‚Đ°Ņž С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–ŅĐš", - "cleanup_found_assets_with_size": "ЗĐŊОКдСĐĩĐŊа {count} аб’ĐĩĐēŅ‚Đ°Ņž С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–ŅĐš ({size})", - "cleanup_icloud_shared_albums_excluded": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ аĐģŅŒĐąĐžĐŧŅ‹ iCloud Đ˛Ņ‹ĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ ŅĐ° ҁĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "cleanup_no_assets_found": "НĐĩ СĐŊОКдСĐĩĐŊа аб’ĐĩĐēŅ‚Đ°Ņž, ŅĐēŅ–Ņ адĐŋĐ°Đ˛ŅĐ´Đ°ŅŽŅ†ŅŒ ĐēŅ€Ņ‹Ņ‚ŅŅ€Ņ‹ŅĐŧ Đ˛Ņ‹ŅˆŅĐš. ÂĢĐ’Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°Âģ ĐŧĐžĐļа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚ĐžĐģҌĐēŅ– аб’ĐĩĐē҂ҋ, ŅĐēŅ–Ņ ŅžĐļĐž Ņ‘ŅŅ†ŅŒ ҃ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ– ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҋ", - "cleanup_preview_title": "Аб’ĐĩĐē҂ҋ Đ´ĐģŅ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ ({count})", - "cleanup_step3_description": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–ŅĐš, ŅĐēŅ–Ņ адĐŋĐ°Đ˛ŅĐ´Đ°ŅŽŅ†ŅŒ Đ˛Đ°ŅˆŅ‹Đŧ ĐŊаĐģадаĐŧ Đ´Đ°Ņ‚Ņ‹ Ņ– ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊĐŊŅ.", - "cleanup_step4_summary": "{count} аб’ĐĩĐēŅ‚Đ°Ņž (ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊҋ҅ да {date}) Đ´ĐģŅ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ С Đ˛Đ°ŅˆĐ°Đš ĐģаĐēаĐģҌĐŊаК ĐŋҀҋĐģĐ°Đ´Ņ‹. Đ¤ĐžŅ‚Đ° ĐˇĐ°ŅŅ‚Đ°ĐŊŅƒŅ†Ņ†Đ° Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ĐŧŅ– Ņž ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧĐĩ Immich.", - "cleanup_trash_hint": "Каб Ņ†Đ°ĐģĐēаĐŧ Đ˛Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŋŅ€Đ°ŅŅ‚ĐžŅ€Ņƒ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°, адĐēŅ€Ņ‹ĐšŅ†Đĩ ŅŅ–ŅŅ‚ŅĐŧĐŊŅƒŅŽ ĐŗĐ°ĐģĐĩŅ€ŅŅŽ Ņ– Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†Đĩ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", "clear": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ", "clear_all": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ŅƒŅŅ‘", - "clear_all_recent_searches": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ҃ҁĐĩ ĐŊŅĐ´Đ°ŅžĐŊŅ–Ņ ĐŋĐžŅˆŅƒĐēĐ°Đ˛Ņ‹Ņ СаĐŋҋ҂ҋ", - "clear_failed_count": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ({count})", - "clear_file_cache": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ĐēŅŅˆ Ņ„Đ°ĐšĐģĐ°Ņž", - "clear_message": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ", - "clear_value": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ СĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ", + "client_cert_dialog_msg_confirm": "ОК", + "client_cert_enter_password": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ", "client_cert_import": "ІĐŧĐŋĐ°Ņ€Ņ‚", - "client_cert_import_success_msg": "ĐĄĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚ ĐēĐģŅ–ĐĩĐŊŅ‚Đ° Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°ĐŊŅ‹", - "client_cert_invalid_msg": "ĐŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊŅ‹ Ņ„Đ°ĐšĐģ ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ° айО ĐŊŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊŅ‹ ĐŋĐ°Ņ€ĐžĐģҌ", - "client_cert_password_message": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ ĐŗŅŅ‚Đ°ĐŗĐ° ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°", - "client_cert_password_title": "ĐŸĐ°Ņ€ĐžĐģҌ ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°", - "client_cert_remove_msg": "ĐĄĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚ ĐēĐģŅ–ĐĩĐŊŅ‚Đ° Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", - "client_cert_subtitle": "ĐŸĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ° Ņ‚ĐžĐģҌĐēŅ– Ņ„Đ°Ņ€ĐŧĐ°Ņ‚ PKCS12 (.p12, .pfx). ІĐŧĐŋĐ°Ņ€Ņ‚/Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊĐĩ ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ° Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ Ņ‚ĐžĐģҌĐēŅ– да ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ", - "client_cert_title": "SSL ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚ ĐēĐģŅ–ĐĩĐŊŅ‚Đ° [ЭКСПЕРĐĢМЕНĐĸАЛĐŦНА]", "close": "ЗаĐēŅ€Ņ‹Ņ†ŅŒ", "collapse": "Đ—ĐŗĐ°Ņ€ĐŊŅƒŅ†ŅŒ", "collapse_all": "Đ—ĐŗĐ°Ņ€ĐŊŅƒŅ†ŅŒ ŅƒŅŅ‘", "color": "КоĐģĐĩŅ€", - "command": "КаĐŧаĐŊда", - "command_palette_prompt": "ĐĨŅƒŅ‚ĐēŅ– ĐŋĐžŅˆŅƒĐē ŅŅ‚Đ°Ņ€ĐžĐŊаĐē, дСĐĩŅĐŊĐŊŅŅž Ņ– ĐēаĐŧаĐŊĐ´", - "command_palette_to_close": "Đēай СаĐēŅ€Ņ‹Ņ†ŅŒ", - "command_palette_to_navigate": "Đēай ŅƒĐ˛Đ°ĐšŅŅ†Ņ–", - "command_palette_to_select": "Đēай Đ˛Ņ‹ĐąŅ€Đ°Ņ†ŅŒ", - "command_palette_to_show_all": "Đēай ĐŋаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅƒŅŅ‘", - "comment_deleted": "КаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹Đš Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", - "comment_options": "ДзĐĩŅĐŊĐŊŅ– С ĐēаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹ĐĩĐŧ", - "comments_and_likes": "КаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹Ņ– Ņ– ĐŋаСĐŊаĐēŅ– ÂĢĐŧĐŊĐĩ ĐŋадайаĐĩŅ†Ņ†Đ°Âģ", - "comments_are_disabled": "КаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹Ņ– адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", - "common_create_new_album": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹ аĐģŅŒĐąĐžĐŧ", - "completed": "Đ’Ņ‹ĐēаĐŊаĐŊа", - "configuration": "КаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ", - "confirm": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†ŅŒ", - "confirm_admin_password": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", - "confirm_delete_face": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€ {name} С аб’ĐĩĐēŅ‚Đ°?", - "confirm_delete_shared_link": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņƒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃?", - "confirm_keep_this_delete_others": "ĐŖŅĐĩ Ņ–ĐŊŅˆŅ‹Ņ аб’ĐĩĐē҂ҋ Ņž ŅŅ‚ĐžŅĐĩ, аĐēŅ€Đ°ĐŧŅ ĐŗŅŅ‚Đ°ĐŗĐ°, ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹. Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŋŅ€Đ°Ņ†ŅĐŗĐŊŅƒŅ†ŅŒ?", - "confirm_new_pin_code": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†Đĩ ĐŊĐžĐ˛Ņ‹ PIN-ĐēОд", - "confirm_password": "ĐŸĐ°Ņ†Đ˛ĐĩŅ€Đ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ", - "confirm_tag_face": "ĐĨĐžŅ‡Đ°Ņ†Đĩ ĐŋаСĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ Ņ‚Đ˛Đ°Ņ€ ŅĐē {name}?", - "confirm_tag_face_unnamed": "ĐĨĐžŅ‡Đ°Ņ†Đĩ ĐŋаСĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ Ņ‚Đ˛Đ°Ņ€?", - "connected_device": "ПадĐēĐģŅŽŅ‡Đ°ĐŊĐ°Ņ ĐŋҀҋĐģада", - "connected_to": "ПадĐēĐģŅŽŅ‡Đ°ĐŊа да", - "contain": "ĐŖĐŧŅŅŅ†Ņ–Ņ†ŅŒ", - "context": "КаĐŊŅ‚ŅĐēҁ҂", + "color_theme": "КоĐģĐĩŅ€Đ°Đ˛Đ°Ņ Ņ‚ŅĐŧа", "continue": "ĐŸŅ€Đ°Ņ†ŅĐŗĐŊŅƒŅ†ŅŒ", - "control_bottom_app_bar_add_tags": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ‚ŅĐŗŅ–", + "control_bottom_app_bar_create_new_album": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹ аĐģŅŒĐąĐžĐŧ", + "control_bottom_app_bar_delete_from_immich": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С Immich", "control_bottom_app_bar_delete_from_local": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С ĐŋҀҋĐģĐ°Đ´Ņ‹", "control_bottom_app_bar_edit_location": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "control_bottom_app_bar_edit_time": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ Ņ– Ņ‡Đ°Ņ", - "control_bottom_app_bar_trash_from_immich": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "copied_image_to_clipboard": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ ҁĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊŅ‹ Ņž ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃.", - "copied_to_clipboard": "ĐĄĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊа Ņž ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃!", - "copy_error": "ĐĄĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋаĐŧŅ‹ĐģĐē҃", - "copy_image": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ", - "copy_json": "ĐĄĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ JSON", - "copy_link": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "copy_link_to_clipboard": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃ Ņž ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃", - "copy_password": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "copy_to_clipboard": "КаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ҃ ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃", "country": "ĐšŅ€Đ°Ņ–ĐŊа", + "cover": "ВоĐēĐģадĐēа", "covers": "ВоĐēĐģадĐēŅ–", "create": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ", "create_album": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", "create_album_page_untitled": "БĐĩС ĐŊĐ°ĐˇĐ˛Ņ‹", - "create_api_key": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐēĐģŅŽŅ‡ API", - "create_first_workflow": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ ĐŋĐĩŅ€ŅˆŅ‹ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", "create_library": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", "create_link": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "create_link_to_share": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃ Đ´ĐģŅ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ", - "create_link_to_share_description": "ДазвоĐģŅ–Ņ†ŅŒ ŅƒŅŅ–Đŧ, Ņ…Ņ‚Đž ĐŧаĐĩ ҁĐŋĐ°ŅŅ‹ĐģĐē҃, ĐąĐ°Ņ‡Ņ‹Ņ†ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ°", - "create_new": "ĐĄĐĸВАРĐĢĐĻĐŦ", - "create_new_person": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Đ°ĐŗĐ° Ņ‡Đ°ĐģавĐĩĐēа", - "create_new_person_hint": "ĐŸŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ ĐŊОваĐŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃", - "create_new_user": "ĐĄŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ ĐŊĐžĐ˛Đ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "create_person": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Ņ‡Đ°ĐģавĐĩĐēа", - "create_person_subtitle": "Đ”Đ°Đ´Đ°ĐšŅ†Đĩ Ņ–ĐŧŅ да Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐ°ĐŗĐ° Ņ‚Đ˛Đ°Ņ€Ņƒ, Đēай ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Ņ– ĐŋаСĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Đ°ĐŗĐ° Ņ‡Đ°ĐģавĐĩĐēа", - "create_shared_album_page_share_add_assets": "ДАДАĐĻĐŦ АБ’ЕКĐĸĐĢ", - "create_shared_album_page_share_select_photos": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ°", - "create_shared_link": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", + "create_new_user": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Đ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "create_tag": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Ņ‚ŅĐŗ", - "create_tag_description": "ĐĄŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ ĐŊĐžĐ˛Đ°ĐŗĐ° Ņ‚ŅĐŗĐ°. ДĐģŅ ŅžĐēĐģадСĐĩĐŊҋ҅ Ņ‚ŅĐŗĐ°Ņž ŅƒĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐžŅžĐŊŅ‹ ҈ĐģŅŅ… Ņ‚ŅĐŗĐ° С ĐēĐ°ŅĐžĐš Ņ€Ņ‹ŅĐ°Đš.", "create_user": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "create_workflow": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "created": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊŅ‹", - "created_at": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊŅ‹", - "creating_linked_albums": "ĐĄŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ ĐˇĐ˛ŅĐˇĐ°ĐŊҋ҅ аĐģŅŒĐąĐžĐŧĐ°Ņžâ€Ļ", - "crop": "ĐĐąŅ€ŅĐˇĐēа", - "crop_aspect_ratio_free": "ХвайОдĐŊĐ°Ņ", - "crop_aspect_ratio_original": "Đ—Ņ‹Ņ…ĐžĐ´ĐŊĐ°Ņ", - "crop_aspect_ratio_square": "ĐšĐ˛Đ°Đ´Ņ€Đ°Ņ‚", - "current_device": "Đ‘ŅĐŗŅƒŅ‡Đ°Ņ ĐŋҀҋĐģада", - "current_pin_code": "Đ‘ŅĐŗŅƒŅ‡Ņ‹ PIN-ĐēОд", - "current_server_address": "Đ‘ŅĐŗŅƒŅ‡Ņ‹ Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "custom_date": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēĐ°Ņ Đ´Đ°Ņ‚Đ°", - "custom_locale": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēĐ°Ņ ĐģаĐēаĐģҌ", - "custom_locale_description": "Đ¤Đ°Ņ€ĐŧĐ°Ņ†Ņ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ´Đ°Ņ‚, Ņ‡Đ°ŅŅƒ Ņ– ĐģŅ–ĐēĐ°Ņž ĐŋавОдĐģĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊаК ĐŧĐžĐ˛Ņ‹ Ņ– Ņ€ŅĐŗŅ–Ņ‘ĐŊа", - "cutoff_date_description": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ Ņ„ĐžŅ‚Đ° Са аĐŋĐžŅˆĐŊŅ–Ņâ€Ļ", - "cutoff_day": "{count, plural, one {дСĐĩĐŊҌ} few {Đ´ĐŊŅ–} many {Đ´ĐˇŅ‘ĐŊ} other {Đ´ĐŊŅ}}", - "cutoff_year": "{count, plural, one {ĐŗĐžĐ´} few {ĐŗĐ°Đ´Ņ‹} many {ĐŗĐ°Đ´ĐžŅž} other {ĐŗĐžĐ´Đ°}}", "dark": "ĐĻŅ‘ĐŧĐŊĐ°Ņ", - "dark_theme": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†Ņ†Đ° ĐŊа ҆ґĐŧĐŊŅƒŅŽ Ņ‚ŅĐŧ҃", - "date": "Đ”Đ°Ņ‚Đ°", - "date_after": "Đ”Đ°Ņ‚Đ° ĐŋĐ°ŅĐģŅ", - "date_and_time": "Đ”Đ°Ņ‚Đ° Ņ– Ņ‡Đ°Ņ", - "date_before": "Đ”Đ°Ņ‚Đ° да", - "date_of_birth": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ", - "date_of_birth_saved": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊа", - "date_range": "Đ”Ņ‹ŅĐŋаСОĐŊ Đ´Đ°Ņ‚", "day": "ДзĐĩĐŊҌ", - "days": "ДĐŊŅ–", - "deduplicate_all": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҃ҁĐĩ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", - "default_quality_subtitle": "Đ¯ĐēĐ°ŅŅ†ŅŒ, ŅĐēĐ°Ņ ĐąŅƒĐ´ĐˇĐĩ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋҀҋ ĐŊĐ°Ņ†Ņ–ŅĐēаĐŊĐŊŅ– ĐŊа ĐēĐŊĐžĐŋĐē҃ ÂĢĐĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒÂģ. ĐŖŅ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐšŅ†Đĩ ĐēĐŊĐžĐŋĐē҃ ÂĢĐŸĐ°Đ´ĐˇŅĐģŅ–Ņ†Ņ†Đ°Âģ, Đēай Đ˛Ņ‹ĐąŅ–Ņ€Đ°Ņ†ŅŒ ŅĐēĐ°ŅŅ†ŅŒ ĐēĐžĐļĐŊŅ‹ Ņ€Đ°Đˇ.", - "default_share_quality": "ĐŸŅ€Đ°Đ´Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊĐ°Ņ ŅĐēĐ°ŅŅ†ŅŒ ĐŋҀҋ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ–", "delete": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ", - "delete_action_confirmation_message": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ аб’ĐĩĐēŅ‚? Đ“ŅŅ‚Đ° дСĐĩŅĐŊĐŊĐĩ ĐŋĐĩŅ€Đ°ĐŧĐĩŅŅ†Ņ–Ņ†ŅŒ аб’ĐĩĐēŅ‚ ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Ņ– СаĐŋŅ‹Ņ‚Đ°Đĩ, ҆Җ Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ŅĐŗĐž ĐģаĐēаĐģҌĐŊа", - "delete_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", "delete_album": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "delete_api_key_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ ĐēĐģŅŽŅ‡ API?", - "delete_dialog_alert": "Đ“ŅŅ‚Ņ‹Ņ ŅĐģĐĩĐŧĐĩĐŊ҂ҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ С Immich Ņ– С ĐŋҀҋĐģĐ°Đ´Ņ‹", - "delete_dialog_alert_local": "Đ“ŅŅ‚Ņ‹Ņ ŅĐģĐĩĐŧĐĩĐŊ҂ҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ С ĐŋҀҋĐģĐ°Đ´Ņ‹, аĐģĐĩ ĐˇĐ°ŅŅ‚Đ°ĐŊŅƒŅ†Ņ†Đ° Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ĐŧŅ– ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҋ Immich", - "delete_dialog_alert_local_non_backed_up": "НĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ ŅĐģĐĩĐŧĐĩĐŊ҂ҋ ĐŊĐĩ ĐŧĐ°ŅŽŅ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ– Ņž Immich Ņ– ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ С ĐŋҀҋĐģĐ°Đ´Ņ‹", + "delete_dialog_ok_force": "ĐŖŅŅ‘ адĐŊĐž Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ", "delete_dialog_title": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹", - "delete_duplicates_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹Ņ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹?", "delete_face": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€", "delete_key": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡", "delete_library": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", "delete_link": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "delete_local_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа ĐģаĐēаĐģҌĐŊа", - "delete_local_dialog_ok_backed_up_only": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚ĐžĐģҌĐēŅ– С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–ŅĐš", "delete_local_dialog_ok_force": "ĐŖŅŅ‘ адĐŊĐž Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ", "delete_others": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ–ĐŊŅˆŅ‹Ņ", - "delete_permanently": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹", - "delete_permanently_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа ĐŊĐĩĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊа", - "delete_shared_link": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "delete_shared_link_dialog_title": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", "delete_tag": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚ŅĐŗ", - "delete_tag_confirmation_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚ŅĐŗ {tagName}?", "delete_user": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "deleted_shared_link": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊĐ°Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēа Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", - "description": "АĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "deselect_all": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ĐąĐ°Ņ€ ŅƒŅŅ–Ņ…", - "details": "ĐŸĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ–", - "direction": "ĐšŅ–Ņ€ŅƒĐŊаĐē", - "disable": "АдĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ", - "disabled": "АдĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", "discord": "Discord", - "discover": "Đ’Ņ‹ŅĐ˛Ņ–Ņ†ŅŒ", - "discovered_devices": "ЗĐŊОКдСĐĩĐŊŅ‹Ņ ĐŋҀҋĐģĐ°Đ´Ņ‹", - "dismiss_all_errors": "ĐĐ´Ņ…Ņ–ĐģŅ–Ņ†ŅŒ ҃ҁĐĩ ĐŋаĐŧŅ‹ĐģĐēŅ–", - "display_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "display_order": "ĐŸĐ°Ņ€Đ°Đ´Đ°Đē адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "display_original_photos": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ°", - "display_original_photos_setting_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊаĐĩ Ņ„ĐžŅ‚Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ аб’ĐĩĐēŅ‚Đ° СаĐŧĐĩҁ҂ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€, ĐēаĐģŅ– Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ ҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊŅ‹ С Đ˛ŅĐąĐ°Đŧ. Đ“ŅŅ‚Đ° ĐŧĐžĐļа СаĐŋавОĐģŅ–Ņ†ŅŒ ĐŋаĐēаС Ņ„ĐžŅ‚Đ°.", - "do_not_show_again": "БоĐģҌ҈ ĐŊĐĩ ĐŋаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ ĐŗŅŅ‚Đ° ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ", "documentation": "ДаĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ", "done": "Đ“Đ°Ņ‚ĐžĐ˛Đ°", "download": "ĐĄĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ", "download_canceled": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ҁĐēĐ°ŅĐ°Đ˛Đ°ĐŊа", "download_complete": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ СавĐĩŅ€ŅˆĐ°ĐŊа", "download_enqueue": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ дададСĐĩĐŊа Ņž Ņ‡Đ°Ņ€ĐŗŅƒ", - "download_failed": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ҁĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ", - "download_finished": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ СавĐĩŅ€ŅˆĐ°ĐŊа", - "download_include_embedded_motion_videos": "ĐŖĐąŅƒĐ´Đ°Đ˛Đ°ĐŊŅ‹Ņ Đ˛Ņ–Đ´ŅĐ°", - "download_include_embedded_motion_videos_description": "ĐŖĐēĐģŅŽŅ‡Đ°Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ°, ŅƒĐąŅƒĐ´Đ°Đ˛Đ°ĐŊŅ‹Ņ Ņž Ņ€ŅƒŅ…ĐžĐŧŅ‹Ņ Ņ„ĐžŅ‚Đ°, ŅĐē Đ°ŅĐžĐąĐŊŅ‹ Ņ„Đ°ĐšĐģ", - "download_notfound": "ĐĄĐŋаĐŧĐŋĐžŅžĐēа ĐŊĐĩ СĐŊОКдСĐĩĐŊа", - "download_original": "ĐĄĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ", - "download_paused": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŋҀҋĐŋŅ‹ĐŊĐĩĐŊа", - "download_settings": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ", - "download_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ–, ĐˇĐ˛ŅĐˇĐ°ĐŊŅ‹ĐŧŅ– ŅĐ° ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩĐŧ аб’ĐĩĐēŅ‚Đ°Ņž", - "download_waiting_to_retry": "ЧаĐēаĐĩ ĐŋĐ°ŅžŅ‚ĐžŅ€ĐŊаК ҁĐŋŅ€ĐžĐąŅ‹", "downloading": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ", - "downloading_asset_filename": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ аб’ĐĩĐēŅ‚Đ° {filename}", - "downloading_from_icloud": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ С iCloud", - "downloading_media": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŧĐĩĐ´Ņ‹Ņ", - "drag_to_reorder": "ПĐĩŅ€Đ°Ņ†ŅĐŗĐŊҖ҆Đĩ, Đēай СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€Đ°Đ´Đ°Đē", - "drop_files_to_upload": "ПĐĩŅ€Đ°Ņ†ŅĐŗĐŊҖ҆Đĩ Ņ„Đ°ĐšĐģŅ‹ ĐēŅƒĐ´Ņ‹ ĐˇĐ°ŅžĐŗĐžĐ´ĐŊа Đ´ĐģŅ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "duplicate_workflow": "Đ”ŅƒĐąĐģŅĐ˛Đ°Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "duplicates": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", - "duplicates_description": "Đ’Ņ‹Ņ€Đ°ŅˆŅ‹Ņ†Đĩ ĐēĐžĐļĐŊŅƒŅŽ ĐŗŅ€ŅƒĐŋ҃, ĐŋаСĐŊĐ°Ņ‡Ņ‹ŅžŅˆŅ‹, ŅĐēŅ–Ņ Đˇâ€™ŅŅžĐģŅŅŽŅ†Ņ†Đ° Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°ĐŧŅ–, ĐēаĐģŅ– Ņ‚Đ°ĐēŅ–Ņ Ņ‘ŅŅ†ŅŒ.", - "duration": "ĐŸŅ€Đ°Ņ†ŅĐŗĐģĐ°ŅŅ†ŅŒ", "edit": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ", "edit_album": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", "edit_avatar": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Đ°Đ˛Đ°Ņ‚Đ°Ņ€", - "edit_birthday": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ дСĐĩĐŊҌ ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ", "edit_date": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ", "edit_date_and_time": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ŅŒ Đ´Đ°Ņ‚Ņƒ Ņ– Ņ‡Đ°Ņ", - "edit_date_and_time_action_prompt": "{count} Đ´Đ°Ņ‚Đ° Ņ– Ņ‡Đ°Ņ Đ°Đ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊŅ‹", - "edit_date_and_time_by_offset": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ ĐŊа ĐˇŅ€ŅƒŅ…", "edit_description": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "edit_exclusion_pattern": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", + "edit_description_prompt": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŊОваĐĩ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ:", "edit_faces": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹", "edit_key": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐēĐģŅŽŅ‡", "edit_link": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", "edit_location": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "edit_location_action_prompt": "{count} ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ Đ°Đ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊа", "edit_location_dialog_title": "МĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", "edit_name": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐŊĐ°ĐˇĐ˛Ņƒ", "edit_people": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", "edit_tag": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Ņ‚ŅĐŗ", "edit_title": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐˇĐ°ĐŗĐ°ĐģОваĐē", "edit_user": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "edit_workflow": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", "editor": "Đ ŅĐ´Đ°ĐēŅ‚Đ°Ņ€", - "editor_discard_edits_confirm": "ĐĐ´Ņ…Ņ–ĐģŅ–Ņ†ŅŒ ĐŋŅ€Đ°ŅžĐēŅ–", - "editor_discard_edits_prompt": "ĐŖ Đ˛Đ°Ņ Ņ‘ŅŅ†ŅŒ ĐŊĐĩĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹Ņ ĐŋŅ€Đ°ŅžĐēŅ–. Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Ņ–Ņ… Đ°Đ´Ņ…Ņ–ĐģŅ–Ņ†ŅŒ?", - "editor_discard_edits_title": "ĐĐ´Ņ…Ņ–ĐģŅ–Ņ†ŅŒ ĐŋŅ€Đ°ŅžĐēŅ–?", - "editor_edits_applied_error": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐŋҀҋĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋŅ€Đ°ŅžĐēŅ–", - "editor_edits_applied_success": "ĐŸŅ€Đ°ŅžĐēŅ– ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ĐŋҀҋĐŧĐĩĐŊĐĩĐŊŅ‹", - "editor_flip_horizontal": "АдĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŗĐ°Ņ€Ņ‹ĐˇĐ°ĐŊŅ‚Đ°ĐģҌĐŊа", - "editor_flip_vertical": "АдĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°Ņ†ŅŒ вĐĩҀ҂ҋĐēаĐģҌĐŊа", - "editor_handle_corner": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐēŅƒŅ‚Đ° {corner, select, top_left {ŅžĐ˛ĐĩҀҁĐĩ СĐģĐĩва} top_right {ŅžĐ˛ĐĩҀҁĐĩ ҁĐŋŅ€Đ°Đ˛Đ°} bottom_left {ŅžĐŊŅ–ĐˇĐĩ СĐģĐĩва} bottom_right {ŅžĐŊŅ–ĐˇĐĩ ҁĐŋŅ€Đ°Đ˛Đ°} other {}}", - "editor_handle_edge": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐēŅ€Đ°ŅŽ {edge, select, top {ŅžĐ˛ĐĩҀҁĐĩ} bottom {ŅžĐŊŅ–ĐˇĐĩ} left {СĐģĐĩва} right {ҁĐŋŅ€Đ°Đ˛Đ°} other {}}", - "editor_orientation": "ĐŅ€Ņ‹ĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ", - "editor_reset_all_changes": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ СĐŧŅĐŊĐĩĐŊĐŊŅ–", - "editor_rotate_left": "ĐŸĐ°Đ˛ŅŅ€ĐŊŅƒŅ†ŅŒ ĐŊа 90° ҁ҃ĐŋŅ€Đ°Ņ†ŅŒ ĐŗĐ°Đ´ĐˇŅ–ĐŊĐŊŅ–ĐēаваК ŅŅ‚Ņ€ŅĐģĐēŅ–", - "editor_rotate_right": "ĐŸĐ°Đ˛ŅŅ€ĐŊŅƒŅ†ŅŒ ĐŊа 90° Đŋа Ņ…ĐžĐ´Ņƒ ĐŗĐ°Đ´ĐˇŅ–ĐŊĐŊŅ–ĐēаваК ŅŅ‚Ņ€ŅĐģĐēŅ–", - "email": "Đ­ĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐ°Ņ ĐŋĐžŅˆŅ‚Đ°", - "email_notifications": "АĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– Đŋа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ†Đĩ", - "empty_folder": "Đ“ŅŅ‚Đ°Ņ ĐŋаĐŋĐēа ĐŋŅƒŅŅ‚Đ°Ņ", - "empty_trash": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "empty_trash_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ? Đ“ŅŅ‚Đ° ĐŊĐĩĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҃ҁĐĩ аб’ĐĩĐē҂ҋ ŅĐ° ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ.\nĐ“ŅŅ‚Đ°Đĩ дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐģŅŒĐŗĐ° Đ°Đ´Ņ€Đ°ĐąŅ–Ņ†ŅŒ!", - "enable": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ", - "enable_backup": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đĩ ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ", - "enable_biometric_auth_description": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ PIN-ĐēОд, Đēай ҃ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐąŅ–ŅĐŧĐĩ҂Ҁҋ҇ĐŊŅƒŅŽ Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹ŅŽ", - "enabled": "ĐŖĐēĐģŅŽŅ‡Đ°ĐŊа", - "end_date": "Đ”Đ°Ņ‚Đ° ĐˇĐ°Đ˛ŅŅ€ŅˆŅĐŊĐŊŅ", - "enqueued": "ĐŖ Ņ‡Đ°Ņ€ĐˇĐĩ", - "enter_wifi_name": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŊĐ°ĐˇĐ˛Ņƒ Wi-Fi", - "enter_your_pin_code": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ PIN-ĐēОд", - "enter_your_pin_code_subtitle": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ PIN-ĐēОд, Đēай Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ да СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–", + "editor_close_without_save_prompt": "ЗĐŧĐĩĐŊŅ‹ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹", + "editor_close_without_save_title": "ЗаĐēŅ€Ņ‹Ņ†ŅŒ Ņ€ŅĐ´Đ°ĐēŅ‚Đ°Ņ€?", "error": "ПаĐŧŅ‹ĐģĐēа", - "error_delete_face": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– Ņ‚Đ˛Đ°Ņ€Ņƒ С аб’ĐĩĐēŅ‚Đ°", - "error_loading_albums": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ†Ņ‹ аĐģŅŒĐąĐžĐŧĐ°Ņž", - "error_loading_image": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ†Ņ‹ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°", - "error_loading_partners": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ†Ņ‹ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°Ņž: {error}", - "error_retrieving_asset_information": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐŊĐŊŅ– Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ– ĐŋŅ€Đ° аб’ĐĩĐēŅ‚", "error_saving_image": "ПаĐŧŅ‹ĐģĐēа: {error}", - "error_tag_face_bounding_box": "ПаĐŧŅ‹ĐģĐēа ĐŋаСĐŊĐ°Ņ‡ŅĐŊĐŊŅ Ņ‚Đ˛Đ°Ņ€Ņƒ — ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐēĐ°Đ°Ņ€Đ´Ņ‹ĐŊĐ°Ņ‚Ņ‹ Ņ€Đ°ĐŧĐēŅ–", - "error_title": "ПаĐŧŅ‹ĐģĐēа — ĐŊĐĩŅˆŅ‚Đ° ĐŋĐ°ĐšŅˆĐģĐž ĐŊĐĩ Ņ‚Đ°Đē", - "error_while_navigating": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ĐŋĐĩŅ€Đ°Ņ…ĐžĐ´ĐˇĐĩ да аб’ĐĩĐēŅ‚Đ°", - "errors": { - "cannot_navigate_next_asset": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊĐ°ĐŗĐ° аб’ĐĩĐēŅ‚Đ°", - "cannot_navigate_previous_asset": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ° аб’ĐĩĐēŅ‚Đ°", - "cant_apply_changes": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋҀҋĐŧŅĐŊŅ–Ņ†ŅŒ СĐŧŅĐŊĐĩĐŊĐŊŅ–", - "cant_change_activity": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа {enabled, select, true {адĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ} other {ŅžĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ}} аĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ", - "cant_change_asset_favorite": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ Đ°ĐąŅ€Đ°ĐŊаĐĩ Đ´ĐģŅ аб’ĐĩĐēŅ‚Đ°", - "cant_change_metadata_assets_count": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ {count, plural, one {# аб’ĐĩĐēŅ‚Đ°} few {# аб’ĐĩĐēŅ‚Đ°Ņž} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}}", - "cant_get_faces": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹", - "cant_get_number_of_comments": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ ĐēаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹ŅŅž", - "cant_search_people": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŊĐ°ĐšŅŅ†Ņ– ĐģŅŽĐ´ĐˇĐĩĐš", - "cant_search_places": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŊĐ°ĐšŅŅ†Ņ– ĐŧĐĩҁ҆ҋ", - "error_adding_assets_to_album": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ дадаваĐŊĐŊŅ– аб’ĐĩĐēŅ‚Đ°Ņž ҃ аĐģŅŒĐąĐžĐŧ", - "error_adding_users_to_album": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ дадаваĐŊĐŊŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž ҃ аĐģŅŒĐąĐžĐŧ", - "error_deleting_shared_user": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа С Đ°ĐŗŅƒĐģҌĐŊŅ‹Đŧ Đ´ĐžŅŅ‚ŅƒĐŋаĐŧ", - "error_downloading": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ– {filename}", - "error_hiding_buy_button": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ŅŅ…Đ°Đ˛Đ°ĐŊĐŊŅ– ĐēĐŊĐžĐŋĐēŅ– Đē҃ĐŋĐģŅ–", - "error_removing_assets_from_album": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– аб’ĐĩĐēŅ‚Đ°Ņž С аĐģŅŒĐąĐžĐŧа, ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ– Ņž ĐēаĐŊŅĐžĐģŅ–", - "error_selecting_all_assets": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ˛Ņ‹ĐąĐ°Ņ€Ņ‹ ŅžŅŅ–Ņ… аб’ĐĩĐēŅ‚Đ°Ņž", - "exclusion_pattern_already_exists": "Đ“ŅŅ‚Ņ‹ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ ŅžĐļĐž ҖҁĐŊ҃Đĩ.", - "failed_to_create_album": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "failed_to_create_shared_link": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "failed_to_edit_shared_link": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Đ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "failed_to_get_people": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "failed_to_keep_this_delete_others": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐŋаĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŗŅŅ‚Ņ‹ аб’ĐĩĐēŅ‚ Ņ– Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Đ°ŅŅ‚Đ°Ņ‚ĐŊŅ–Ņ", - "failed_to_load_asset": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚", - "failed_to_load_assets": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ аб’ĐĩĐē҂ҋ", - "failed_to_load_notifications": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–", - "failed_to_load_people": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "failed_to_remove_product_key": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ", - "failed_to_reset_pin_code": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ҁĐēŅ–ĐŊŅƒŅ†ŅŒ PIN-ĐēОд", - "failed_to_stack_assets": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ С аб’ĐĩĐēŅ‚Đ°Ņž ŅŅ‚ĐžŅ", - "failed_to_tag_assets": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ´Đ°Đ´Đ°Ņ†ŅŒ Ņ‚ŅĐŗ да аб’ĐĩĐēŅ‚Đ°Ņž", - "failed_to_unstack_assets": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅ–Ņ†ŅŒ ŅŅ‚ĐžŅ аб’ĐĩĐēŅ‚Đ°Ņž", - "failed_to_update_notification_status": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅŅ‚Đ°Ņ‚ŅƒŅ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ", - "incorrect_email_or_password": "ĐŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊŅ‹ Đ°Đ´Ņ€Đ°Ņ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ‚Ņ‹ айО ĐŋĐ°Ņ€ĐžĐģҌ", - "library_folder_already_exists": "Đ“ŅŅ‚Ņ‹ ҈ĐģŅŅ… Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ ŅžĐļĐž ҖҁĐŊ҃Đĩ.", - "page_not_found": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа ĐŊĐĩ СĐŊОКдСĐĩĐŊа", - "paths_validation_failed": "{paths, plural, one {# ҈ĐģŅŅ… ĐŊĐĩ ĐŋŅ€Đ°ĐšŅˆĐžŅž ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃} few {# ҈ĐģŅŅ…Ņ– ĐŊĐĩ ĐŋŅ€Đ°ĐšŅˆĐģŅ– ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃} many {# ҈ĐģŅŅ…ĐžŅž ĐŊĐĩ ĐŋŅ€Đ°ĐšŅˆĐģĐž ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃} other {# ҈ĐģŅŅ…Ņƒ ĐŊĐĩ ĐŋŅ€Đ°ĐšŅˆĐģĐž ĐŋŅ€Đ°Đ˛ĐĩŅ€Đē҃}}", - "profile_picture_transparent_pixels": "ĐĐ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ ĐŊĐĩ ĐŧĐžĐļа ĐŧĐĩŅ†ŅŒ ĐŋŅ€Đ°ĐˇŅ€Ņ‹ŅŅ‚Ņ‹Ņ ĐŋŅ–ĐēҁĐĩĐģŅ–. ĐŸĐ°Đ˛ŅĐģҖ҇҆Đĩ ĐŧĐ°ŅˆŅ‚Đ°Đą Ņ–/айО ĐŋĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†Đĩ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ.", - "quota_higher_than_disk_size": "Đ’Ņ‹ СадаĐģŅ–Đ°ĐģŅ– ĐēĐ˛ĐžŅ‚Ņƒ, йОĐģŅŒŅˆŅƒŅŽ Са ĐŋаĐŧĐĩŅ€ Đ´Ņ‹ŅĐēа", - "something_went_wrong": "НĐĩŅˆŅ‚Đ° ĐŋĐ°ĐšŅˆĐģĐž ĐŊĐĩ Ņ‚Đ°Đē", - "unable_to_add_album_users": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ´Đ°Đ´Đ°Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž ҃ аĐģŅŒĐąĐžĐŧ", - "unable_to_add_assets_to_shared_link": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ´Đ°Đ´Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ Ņž Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "unable_to_add_comment": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ´Đ°Đ´Đ°Ņ†ŅŒ ĐēаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹Đš", - "unable_to_add_exclusion_pattern": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ´Đ°Đ´Đ°Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", - "unable_to_add_partners": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ´Đ°Đ´Đ°Ņ†ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°Ņž", - "unable_to_add_remove_archive": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа {archived, select, true {Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚ С Đ°Ņ€Ņ…Ņ–Đ˛Đ°} other {Đ´Đ°Đ´Đ°Ņ†ŅŒ аб’ĐĩĐēŅ‚ ҃ Đ°Ņ€Ņ…Ņ–Ņž}}", - "unable_to_add_remove_favorites": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа {favorite, select, true {Đ´Đ°Đ´Đ°Ņ†ŅŒ аб’ĐĩĐēŅ‚ ҃ Đ°ĐąŅ€Đ°ĐŊаĐĩ} other {Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚ С Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°}}", - "unable_to_archive_unarchive": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа {archived, select, true {Đ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°Ņ†ŅŒ} other {Ņ€Đ°ĐˇĐ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°Ņ†ŅŒ}}", - "unable_to_change_album_user_role": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ Ņ€ĐžĐģŅŽ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа аĐģŅŒĐąĐžĐŧа", - "unable_to_change_date": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ", - "unable_to_change_description": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "unable_to_change_favorite": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ Đ°ĐąŅ€Đ°ĐŊаĐĩ Đ´ĐģŅ аб’ĐĩĐēŅ‚Đ°", - "unable_to_change_location": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "unable_to_change_password": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "unable_to_change_visibility": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СĐŧŅĐŊŅ–Ņ†ŅŒ ĐąĐ°Ņ‡ĐŊĐ°ŅŅ†ŅŒ Đ´ĐģŅ {count, plural, one {# Ņ‡Đ°ĐģавĐĩĐēа} few {# Ņ‡Đ°ĐģавĐĩĐē} many {# Ņ‡Đ°ĐģавĐĩĐē} other {# Ņ‡Đ°ĐģавĐĩĐēа}}", - "unable_to_complete_oauth_login": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Đ˛ŅŅ€ŅˆŅ‹Ņ†ŅŒ ŅƒĐ˛Đ°Ņ…ĐžĐ´ ĐŋŅ€Đ°Đˇ OAuth", - "unable_to_connect": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋадĐēĐģŅŽŅ‡Ņ‹Ņ†Ņ†Đ°", - "unable_to_copy_to_clipboard": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ҁĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ҃ ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃, ĐŋĐĩŅ€Đ°ĐēаĐŊĐ°ĐšŅ†ĐĩŅŅ, ŅˆŅ‚Đž ŅŅ‚Đ°Ņ€ĐžĐŊĐēа адĐēŅ€Ņ‹Ņ‚Đ° ĐŋŅ€Đ°Đˇ https", - "unable_to_create": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "unable_to_create_admin_account": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ҃ĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°", - "unable_to_create_api_key": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹ ĐēĐģŅŽŅ‡ API", - "unable_to_create_library": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "unable_to_create_user": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "unable_to_delete_album": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "unable_to_delete_asset": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚", - "unable_to_delete_assets": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– аб’ĐĩĐēŅ‚Đ°Ņž", - "unable_to_delete_exclusion_pattern": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", - "unable_to_delete_shared_link": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "unable_to_delete_user": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "unable_to_delete_workflow": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "unable_to_download_files": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ҁĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ Ņ„Đ°ĐšĐģŅ‹", - "unable_to_edit_exclusion_pattern": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", - "unable_to_empty_trash": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "unable_to_enter_fullscreen": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐšŅŅ†Ņ– Ņž ĐŋĐžŅžĐŊĐ°ŅĐēŅ€Đ°ĐŊĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ", - "unable_to_exit_fullscreen": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹ĐšŅŅ†Ņ– С ĐŋĐžŅžĐŊĐ°ŅĐēŅ€Đ°ĐŊĐŊĐ°ĐŗĐ° Ņ€ŅĐļŅ‹Đŧ҃", - "unable_to_get_comments_number": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ ĐēаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹ŅŅž", - "unable_to_get_shared_link": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅƒŅŽ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "unable_to_hide_person": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ‡Đ°ĐģавĐĩĐēа", - "unable_to_link_motion_video": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ˛ŅĐˇĐ°Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "unable_to_link_oauth_account": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ˛ŅĐˇĐ°Ņ†ŅŒ ҃ĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ OAuth", - "unable_to_log_out_all_devices": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹ĐšŅŅ†Ņ– С ŅƒŅŅ–Ņ… ĐŋҀҋĐģад", - "unable_to_log_out_device": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹ĐšŅŅ†Ņ– С ĐŋҀҋĐģĐ°Đ´Ņ‹", - "unable_to_login_with_oauth": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ŅžĐ˛Đ°ĐšŅŅ†Ņ– ĐŋŅ€Đ°Đˇ OAuth", - "unable_to_play_video": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ°", - "unable_to_reassign_assets_existing_person": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ аб’ĐĩĐē҂ҋ {name, select, null {ҖҁĐŊŅƒŅŽŅ‡Đ°Đŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃} other {{name}}}", - "unable_to_reassign_assets_new_person": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ аб’ĐĩĐē҂ҋ ĐŊОваĐŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃", - "unable_to_refresh_user": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "unable_to_remove_album_users": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž С аĐģŅŒĐąĐžĐŧа", - "unable_to_remove_api_key": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡ API", - "unable_to_remove_assets_from_shared_link": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐē҂ҋ С Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "unable_to_remove_library": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "unable_to_remove_partner": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", - "unable_to_remove_reaction": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ€ŅĐ°ĐēŅ†Ņ‹ŅŽ", - "unable_to_reset_password": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ҁĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "unable_to_reset_pin_code": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ҁĐēŅ–ĐŊŅƒŅ†ŅŒ PIN-ĐēОд", - "unable_to_resolve_duplicate": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ˛Ņ‹Ņ€Đ°ŅˆŅ‹Ņ†ŅŒ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚", - "unable_to_restore_assets": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ аб’ĐĩĐē҂ҋ", - "unable_to_restore_trash": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅĐ° ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", - "unable_to_restore_user": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа адĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "unable_to_save_album": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "unable_to_save_api_key": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ ĐēĐģŅŽŅ‡ API", - "unable_to_save_date_of_birth": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ", - "unable_to_save_name": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ–ĐŧŅ", - "unable_to_save_profile": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ ĐŋŅ€ĐžŅ„Ņ–ĐģҌ", - "unable_to_save_settings": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹", - "unable_to_scan_libraries": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋŅ€Đ°ŅĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "unable_to_scan_library": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋŅ€Đ°ŅĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "unable_to_set_feature_photo": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Đ´Đ°Ņ†ŅŒ ĐŗĐ°ĐģĐžŅžĐŊаĐĩ Ņ„ĐžŅ‚Đ°", - "unable_to_set_profile_picture": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Đ´Đ°Ņ†ŅŒ Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ", - "unable_to_set_rating": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐˇĐ°Đ´Đ°Ņ†ŅŒ Ņ€ŅĐšŅ‚Ņ‹ĐŊĐŗ", - "unable_to_submit_job": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа адĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ СадаĐŊĐŊĐĩ", - "unable_to_trash_asset": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа ĐŋĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ аб’ĐĩĐēŅ‚ ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "unable_to_unlink_account": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đ´Đ˛ŅĐˇĐ°Ņ†ŅŒ ҃ĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ", - "unable_to_unlink_motion_video": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Đ´Đ˛ŅĐˇĐ°Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "unable_to_update_album_cover": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ вОĐēĐģадĐē҃ аĐģŅŒĐąĐžĐŧа", - "unable_to_update_album_info": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹ŅŽ ĐŋŅ€Đ° аĐģŅŒĐąĐžĐŧ", - "unable_to_update_library": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "unable_to_update_location": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "unable_to_update_settings": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹", - "unable_to_update_timeline_display_status": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅŅ‚Đ°Ņ‚ŅƒŅ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ Ņ…Ņ€ĐžĐŊŅ–ĐēŅ–", - "unable_to_update_user": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "unable_to_update_workflow": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа айĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "unable_to_upload_file": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа СаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ Ņ„Đ°ĐšĐģ" - }, - "errors_text": "ПаĐŧŅ‹ĐģĐēŅ–", - "exclusion_pattern": "ШайĐģĐžĐŊ Đ˛Ņ‹ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ", "exif": "Exif", "exif_bottom_sheet_description": "Đ”Đ°Đ´Đ°Ņ†ŅŒ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ...", - "exif_bottom_sheet_description_error": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ айĐŊĐ°ŅžĐģĐĩĐŊĐŊŅ– аĐŋŅ–ŅĐ°ĐŊĐŊŅ", - "exif_bottom_sheet_no_description": "ĐŅĐŧа аĐŋŅ–ŅĐ°ĐŊĐŊŅ", - "exit_slideshow": "Đ’Ņ‹ĐšŅŅ†Ņ– ŅĐ° ҁĐģаКд-ŅˆĐžŅƒ", - "expand": "Đ Đ°ĐˇĐŗĐ°Ņ€ĐŊŅƒŅ†ŅŒ", - "expand_all": "Đ Đ°ĐˇĐŗĐ°Ņ€ĐŊŅƒŅ†ŅŒ ŅƒŅŅ‘", - "expire_after": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ", - "expired": "ĐĸŅŅ€ĐŧŅ–ĐŊ ĐŧŅ–ĐŊŅƒŅž", - "expires_date": "ĐĸŅŅ€ĐŧŅ–ĐŊ ĐŧŅ–ĐŊаĐĩ {date}", "explore": "ĐĐŗĐģŅĐ´", - "explorer": "ĐĐŗĐģŅĐ´ ĐŋаĐŋаĐē", - "export": "Đ­ĐēҁĐŋĐ°Ņ€Ņ‚", - "export_as_json": "Đ­ĐēҁĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ ŅĐē JSON", - "export_database": "Đ­ĐēҁĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ ĐąĐ°ĐˇŅƒ даĐŊҋ҅", - "export_database_description": "Đ­ĐēҁĐŋĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ ĐąĐ°ĐˇŅƒ даĐŊҋ҅ SQLite", - "exposure_time": "Đ’Ņ‹Ņ‚Ņ€Ņ‹ĐŧĐēа", - "extension": "ĐŸĐ°ŅˆŅ‹Ņ€ŅĐŊĐŊĐĩ", - "external": "ЗĐŊĐĩ҈ĐŊŅ–", - "external_libraries": "ЗĐŊĐĩ҈ĐŊŅ–Ņ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "external_network": "ЗĐŊĐĩ҈ĐŊŅŅ ҁĐĩŅ‚Đēа", - "external_network_sheet_info": "КаĐģŅ– ĐŋҀҋĐģада ĐŊĐĩ ĐŋадĐēĐģŅŽŅ‡Đ°ĐŊа да ĐŋĐĩŅ€Đ°Đ˛Đ°ĐļĐŊаК ҁĐĩŅ‚ĐēŅ– Wi-Fi, ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧа ĐŋадĐēĐģŅŽŅ‡Ņ‹Ņ†Ņ†Đ° да ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŋŅ€Đ°Đˇ ĐŋĐĩŅ€ŅˆŅ‹ С ĐŋĐĩŅ€Đ°ĐģŅ–Ņ‡Đ°ĐŊҋ҅ ĐŊŅ–ĐļŅĐš URL-Đ°Đ´Ņ€Đ°ŅĐžŅž, ŅĐēŅ– ĐąŅƒĐ´ĐˇĐĩ Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹, ĐŋĐ°Ņ‡Ņ‹ĐŊĐ°ŅŽŅ‡Ņ‹ СвĐĩŅ€Ņ…Ņƒ ŅžĐŊŅ–Đˇ", - "f_number": "Đ”Ņ‹ŅŅ„Ņ€Đ°ĐŗĐŧа", - "face_unassigned": "НĐĩ ĐŋŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊŅ‹", - "failed": "НĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ", - "failed_count": "НĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ: {count}", - "failed_to_authenticate": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐŋŅ€Đ°ĐšŅŅ†Ņ– Đ°ŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ†Ņ‹ŅŽ", - "failed_to_delete_file": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ„Đ°ĐšĐģ", - "failed_to_load_assets": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ аб’ĐĩĐē҂ҋ", - "failed_to_load_folder": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ ĐŋаĐŋĐē҃", "favorite": "ĐŖ Đ°ĐąŅ€Đ°ĐŊŅ‹Đŧ", - "favorite_action_prompt": "{count} дададСĐĩĐŊа Ņž Đ°ĐąŅ€Đ°ĐŊаĐĩ", "favorite_or_unfavorite_photo": "Đ”Đ°Đ´Đ°Ņ†ŅŒ айО Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ„ĐžŅ‚Đ° С Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°", "favorites": "ĐĐąŅ€Đ°ĐŊŅ‹Ņ", - "feature_photo_updated": "ГаĐģĐžŅžĐŊаĐĩ Ņ„ĐžŅ‚Đ° айĐŊĐžŅžĐģĐĩĐŊа", - "features": "ĐœĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†Ņ–", - "features_setting_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†ŅĐŧŅ– ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "file_name_or_extension": "Назва Ņ„Đ°ĐšĐģа айО ĐŋĐ°ŅˆŅ‹Ņ€ŅĐŊĐŊĐĩ", - "file_name_text": "Назва Ņ„Đ°ĐšĐģа", - "file_size": "ПаĐŧĐĩŅ€ Ņ„Đ°ĐšĐģа", "filename": "Назва Ņ„Đ°ĐšĐģа", "filetype": "ĐĸŅ‹Đŋ Ņ„Đ°ĐšĐģа", "filter": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€", - "filter_people": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Đŋа ĐģŅŽĐ´ĐˇŅŅ…", - "filter_places": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Đŋа ĐŧĐĩŅŅ†Đ°Ņ…", - "filter_tags": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Đŋа Ņ‚ŅĐŗĐ°Ņ…", - "filters": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€Ņ‹", - "first": "ПĐĩŅ€ŅˆŅ‹", - "fix_incorrect_match": "Đ’Ņ‹ĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ ĐŊŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊаĐĩ ҁ҃ĐŋĐ°ŅŅ‚Đ°ŅžĐģĐĩĐŊĐŊĐĩ", - "focal_length": "ФОĐē҃ҁĐŊĐ°Ņ адĐģĐĩĐŗĐģĐ°ŅŅ†ŅŒ", - "folder": "ПаĐŋĐēа", - "folder_not_found": "ПаĐŋĐēа ĐŊĐĩ СĐŊОКдСĐĩĐŊа", - "folders": "ПаĐŋĐēŅ–", - "folders_feature_description": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ĐŋаĐŋаĐē С Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° Ņž Ņ„Đ°ĐšĐģаваК ŅŅ–ŅŅ‚ŅĐŧĐĩ", - "forgot_pin_code_question": "Đ—Đ°ĐąŅ‹ĐģŅ– PIN-ĐēОд?", "forward": "НаĐŋĐĩŅ€Đ°Đ´", - "free_up_space": "Đ’Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°", - "free_up_space_description": "ПĐĩŅ€Đ°ĐŊŅŅŅ–Ņ†Đĩ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°, ŅĐēŅ–Ņ Ņ‘ŅŅ†ŅŒ ҃ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–, ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ ĐŋҀҋĐģĐ°Đ´Ņ‹, Đēай Đ˛Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°. КоĐŋŅ–Ņ– ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҋ ĐˇĐ°ŅŅ‚Đ°ĐŊŅƒŅ†Ņ†Đ° Ņž ĐąŅŅĐŋĐĩ҆ҋ.", - "free_up_space_settings_subtitle": "Đ’Ņ‹ĐˇĐ˛Đ°ĐģŅ–Ņ†ŅŒ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ĐŋҀҋĐģĐ°Đ´Ņ‹", - "full_path": "ĐŸĐžŅžĐŊŅ‹ ҈ĐģŅŅ…: {path}", - "full_path_or_folder": "ĐŸĐžŅžĐŊŅ‹ ҈ĐģŅŅ… айО ĐŋаĐŋĐēа", "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Đ“ŅŅ‚Đ°Ņ Ņ„ŅƒĐŊĐēŅ†Ņ‹Ņ ĐˇĐ°ĐŗŅ€ŅƒĐļаĐĩ Đ´ĐģŅ ŅĐ˛Đ°Ņ‘Đš ĐŋŅ€Đ°Ņ†Ņ‹ СĐŊĐĩ҈ĐŊŅ–Ņ Ņ€ŅŅŅƒŅ€ŅŅ‹ ад Google.", "general": "ĐĐŗŅƒĐģҌĐŊŅ‹Ņ", - "geolocation_instruction_location": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ ĐŊа аб’ĐĩĐēŅ‚ С GPS-ĐēĐ°Đ°Ņ€Đ´Ņ‹ĐŊĐ°Ņ‚Đ°ĐŧŅ–, Đēай ҁĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Ņ†ŅŒ ŅĐŗĐž ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, айО Đ˛Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŧĐĩŅŅ†Đ° ĐŊĐ°ŅžĐŋŅ€ĐžŅŅ‚ С ĐēĐ°Ņ€Ņ‚Ņ‹", - "get_help": "ĐŅ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ даĐŋаĐŧĐžĐŗŅƒ", - "get_people_error": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐŊĐŊŅ– ĐģŅŽĐ´ĐˇĐĩĐš", - "get_wifiname_error": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐŊĐ°ĐˇĐ˛Ņƒ Wi-Fi. ПĐĩŅ€Đ°ĐēаĐŊĐ°ĐšŅ†ĐĩŅŅ, ŅˆŅ‚Đž Đ˛Ņ‹ даĐģŅ– ĐŊĐĩĐ°ĐąŅ…ĐžĐ´ĐŊŅ‹Ņ даСвОĐģŅ‹ Ņ– ĐŋадĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ да ҁĐĩŅ‚ĐēŅ– Wi-Fi", - "getting_started": "ĐŸĐ°Ņ‡Đ°Ņ‚Đ°Đē ĐŋŅ€Đ°Ņ†Ņ‹", "go_back": "Назад", "go_to_folder": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐŋаĐŋĐēŅ–", - "go_to_search": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐŋĐžŅˆŅƒĐē҃", - "gps": "GPS", - "gps_missing": "ĐŅĐŧа GPS", - "grant_permission": "Đ”Đ°Ņ†ŅŒ даСвОĐģ", - "group_albums_by": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹ Đŋа...", - "group_country": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đŋа ĐēŅ€Đ°Ņ–ĐŊĐĩ", - "group_no": "БĐĩС ĐŗŅ€ŅƒĐŋĐžŅžĐēŅ–", - "group_owner": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đŋа ŅžĐģадаĐģҌĐŊŅ–Đē҃", - "group_places_by": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ ĐŧĐĩҁ҆ҋ Đŋа...", - "group_year": "Đ“Ņ€ŅƒĐŋĐ°Đ˛Đ°Ņ†ŅŒ Đŋа ĐŗĐžĐ´ĐˇĐĩ", - "haptic_feedback_title": "ĐĸаĐē҂ҋĐģҌĐŊĐ°Ņ Đ°Đ´Đ´Đ°Ņ‡Đ°", - "has_quota": "МаĐĩ ĐēĐ˛ĐžŅ‚Ņƒ", - "hash_asset": "ĐĨŅŅˆĐ°Đ˛Đ°Ņ†ŅŒ аб’ĐĩĐēŅ‚", - "hashed_assets": "ĐĨŅŅˆĐ°Đ˛Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ", - "hashing": "ĐĨŅŅˆĐ°Đ˛Đ°ĐŊĐŊĐĩ", - "header_settings_add_header_tip": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐˇĐ°ĐŗĐ°ĐģОваĐē", - "header_settings_field_validator_msg": "ЗĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐŊĐĩ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ ĐŋŅƒŅŅ‚Ņ‹Đŧ", - "header_settings_header_name_input": "Назва ĐˇĐ°ĐŗĐ°ĐģĐžŅžĐēа", - "header_settings_header_value_input": "ЗĐŊĐ°Ņ‡ŅĐŊĐŊĐĩ ĐˇĐ°ĐŗĐ°ĐģĐžŅžĐēа", - "headers_settings_tile_title": "ĐŖĐģĐ°ŅĐŊŅ‹Ņ ĐˇĐ°ĐŗĐ°ĐģĐžŅžĐēŅ– ĐŋŅ€ĐžĐēҁҖ", - "height": "Đ’Ņ‹ŅˆŅ‹ĐŊŅ", "hi_user": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ, {name} ({email})", "hide_all_people": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ ŅƒŅŅ–Ņ… ĐģŅŽĐ´ĐˇĐĩĐš", "hide_gallery": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ ĐŗĐ°ĐģĐĩŅ€ŅŅŽ", "hide_named_person": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ {name}", "hide_password": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", "hide_person": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ‡Đ°ĐģавĐĩĐēа", - "hide_schema": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ ҁ҅ĐĩĐŧ҃", - "hide_text_recognition": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ", - "hide_unnamed_people": "ĐĄŅ…Đ°Đ˛Đ°Ņ†ŅŒ ĐąĐĩĐˇŅ‹ĐŧĐĩĐŊĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš", - "home_page_building_timeline": "ĐŸĐ°ĐąŅƒĐ´ĐžĐ˛Đ° ŅŅ‚ŅƒĐļĐēŅ– Ņ‡Đ°ŅŅƒ", - "host": "ĐĨĐžŅŅ‚", - "hour": "Đ“Đ°Đ´ĐˇŅ–ĐŊа", - "hours": "Đ“Đ°Đ´ĐˇŅ–ĐŊŅ‹", - "id": "Đ†Đ´ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņ€", - "idle": "ĐŸŅ€Đ°ŅŅ‚ĐžĐš", - "image": "Đ’Ņ–Đ´Đ°Ņ€Ņ‹Ņ", - "image_alt_text_date": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ С {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ С {person1} Ņ– {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ С {person1}, {person2} Ņ– {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ С {person1}, {person2} Ņ– ŅŅˆŅ‡Ņ {additionalCount, number} Ņ–ĐŊ҈. {date}", - "image_alt_text_date_place": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ Ņž {city}, {country} {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ Ņž {city}, {country} С {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ Ņž {city}, {country} С {person1} Ņ– {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ Ņž {city}, {country} С {person1}, {person2} Ņ– {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Đ’Ņ–Đ´ŅĐ°} other {Đ¤ĐžŅ‚Đ°}} ĐˇŅ€ĐžĐąĐģĐĩĐŊаĐĩ Ņž {city}, {country} С {person1}, {person2} Ņ– ŅŅˆŅ‡Ņ {additionalCount, number} Ņ–ĐŊ҈. {date}", + "image_viewer_page_state_provider_download_started": "ĐĄĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ‡Đ°ĐģĐžŅŅ", "immich_logo": "Đ›Đ°ĐŗĐ°Ņ‚Ņ‹Đŋ Immich", - "immich_web_interface": "Đ’ŅĐą-Ņ–ĐŊŅ‚ŅŅ€Ņ„ĐĩĐšŅ Immich", - "import_from_json": "ІĐŧĐŋĐ°Ņ€Ņ‚ С JSON", - "import_path": "ШĐģŅŅ… Ņ–ĐŧĐŋĐ°Ņ€Ņ‚Ņƒ", - "in_albums": "ĐŖ {count, plural, one {# аĐģŅŒĐąĐžĐŧĐĩ} few {# аĐģŅŒĐąĐžĐŧĐ°Ņ…} many {# аĐģŅŒĐąĐžĐŧĐ°Ņ…} other {# аĐģŅŒĐąĐžĐŧа}}", - "in_archive": "ĐŖ Đ°Ņ€Ņ…Ņ–Đ˛Đĩ", - "in_year": "ĐŖ {year}", - "in_year_selector": "ĐŖ", - "include_archived": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°ĐŊŅ‹Ņ", - "include_shared_albums": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ аĐģŅŒĐąĐžĐŧŅ‹", - "include_shared_partner_assets": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", - "individual_share": "ĐŅĐžĐąĐŊаĐĩ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ", - "individual_shares": "ĐŅĐžĐąĐŊŅ‹Ņ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ–", - "info": "ІĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ", - "integrity_checks": "ĐŸŅ€Đ°Đ˛ĐĩŅ€ĐēŅ– Ņ†ŅĐģĐ°ŅĐŊĐ°ŅŅ†Ņ–", "interval": { "day_at_onepm": "КоĐļĐŊŅ‹ дСĐĩĐŊҌ а 13-Đš ĐŗĐ°Đ´ĐˇŅ–ĐŊĐĩ", "hours": "{hours, plural, one {КоĐļĐŊŅƒŅŽ ĐŗĐ°Đ´ĐˇŅ–ĐŊ҃} few {КоĐļĐŊŅ‹Ņ {hours, number} ĐŗĐ°Đ´ĐˇŅ–ĐŊŅ‹} many {КоĐļĐŊŅ‹Ņ {hours, number} ĐŗĐ°Đ´ĐˇŅ–ĐŊ} other {КоĐļĐŊŅ‹Ņ {hours, number} ĐŗĐ°Đ´ĐˇŅ–ĐŊ}}", "night_at_midnight": "КоĐļĐŊŅƒŅŽ ĐŊĐžŅ‡ аĐŋĐžŅžĐŊĐ°Ņ‡Ņ‹", "night_at_twoam": "КоĐļĐŊŅƒŅŽ ĐŊĐžŅ‡ а 2-Đš ĐŗĐ°Đ´ĐˇŅ–ĐŊĐĩ" }, - "invalid_date": "НĐĩŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊĐ°Ņ Đ´Đ°Ņ‚Đ°", - "invalid_date_format": "НĐĩŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅ‹ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚ Đ´Đ°Ņ‚Ņ‹", - "invite_people": "ЗаĐŋŅ€Đ°ŅŅ–Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "invite_to_album": "ЗаĐŋŅ€Đ°ŅŅ–Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧ", - "iso": "ISO", - "items_count": "{count, plural, one {# ŅĐģĐĩĐŧĐĩĐŊŅ‚} few {# ŅĐģĐĩĐŧĐĩĐŊ҂ҋ} many {# ŅĐģĐĩĐŧĐĩĐŊŅ‚Đ°Ņž} other {# ŅĐģĐĩĐŧĐĩĐŊŅ‚Đ°}}", - "jobs": "ЗадаĐŊĐŊŅ–", - "keep": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ", - "keep_albums": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", - "keep_albums_count": "ПаĐēŅ–Đ´Đ°ĐĩĐŧ {count} {count, plural, one {аĐģŅŒĐąĐžĐŧ} few {аĐģŅŒĐąĐžĐŧŅ‹} many {аĐģŅŒĐąĐžĐŧĐ°Ņž} other {аĐģŅŒĐąĐžĐŧа}}", - "keep_all": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ ҃ҁĐĩ", - "keep_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ, ŅˆŅ‚Đž ĐˇĐ°ŅŅ‚Đ°ĐŊĐĩŅ†Ņ†Đ° ĐŊа ĐŋҀҋĐģадСĐĩ ĐŋҀҋ Đ˛Ņ‹ĐˇĐ˛Đ°ĐģĐĩĐŊĐŊŅ– ĐŧĐĩŅŅ†Đ°.", - "keep_favorites": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ Đ°ĐąŅ€Đ°ĐŊŅ‹Ņ", - "keep_on_device": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŊа ĐŋҀҋĐģадСĐĩ", - "keep_on_device_hint": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ŅĐģĐĩĐŧĐĩĐŊ҂ҋ, ŅĐēŅ–Ņ Ņ‚Ņ€ŅĐąĐ° ĐŋаĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŊа ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ", - "keep_this_delete_others": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŗŅŅ‚Ņ‹, Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ–ĐŊŅˆŅ‹Ņ", - "keeping": "ПаĐēŅ–Đ´Đ°ĐĩĐŧ: {items}", - "kept_this_deleted_others": "ПаĐēŅ–ĐŊŅƒŅ‚Ņ‹ ĐŗŅŅ‚Ņ‹ аб’ĐĩĐēŅ‚ Ņ– {count, plural, one {Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа # аб’ĐĩĐēŅ‚Đ°}}", - "keyboard_shortcuts": "ĐĄĐŋаĐģŅƒŅ‡ŅĐŊĐŊŅ– ĐēĐģĐ°Đ˛Ņ–Ņˆ", "language": "Мова", - "language_no_results_subtitle": "ĐŸĐ°ŅĐŋŅ€Đ°ĐąŅƒĐšŅ†Đĩ СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐžŅˆŅƒĐēĐ°Đ˛Ņ‹ СаĐŋҋ҂", - "language_no_results_title": "ĐœĐžĐ˛Ņ‹ ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹", - "language_search_hint": "ĐŸĐžŅˆŅƒĐē ĐŧĐžŅž...", - "language_setting_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŋŅ€Ņ‹ŅŅ€Ņ‹Ņ‚ŅŅ‚ĐŊŅƒŅŽ ĐŧĐžĐ˛Ņƒ", - "large_files": "Đ’ŅĐģŅ–ĐēŅ–Ņ Ņ„Đ°ĐšĐģŅ‹", - "last": "АĐŋĐžŅˆĐŊŅ–", - "last_months": "{count, plural, one {АĐŋĐžŅˆĐŊŅ– ĐŧĐĩŅŅŅ†} few {АĐŋĐžŅˆĐŊŅ–Ņ # ĐŧĐĩŅŅŅ†Ņ‹} many {АĐŋĐžŅˆĐŊŅ–Ņ # ĐŧĐĩŅŅŅ†Đ°Ņž} other {АĐŋĐžŅˆĐŊŅ–Ņ # ĐŧĐĩŅŅŅ†Đ°Ņž}}", - "last_seen": "АĐŋĐžŅˆĐŊŅĐĩ ĐŊавĐĩдваĐŊĐŊĐĩ", - "latest_version": "АĐŋĐžŅˆĐŊŅŅ вĐĩŅ€ŅŅ–Ņ", - "latitude": "Đ¨Ņ‹Ņ€Đ°Ņ‚Đ°", - "leave": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ", - "leave_album": "ПаĐēŅ–ĐŊŅƒŅ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "lens_model": "ĐœĐ°Đ´ŅĐģҌ аб’ĐĩĐēŅ‚Ņ‹Đ˛Đ°", - "less": "МĐĩĐŊ҈", - "let_others_respond": "ДазвоĐģŅ–Ņ†ŅŒ Ņ–ĐŊŅˆŅ‹Đŧ адĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ", - "level": "ĐŖĐˇŅ€ĐžĐ˛ĐĩĐŊҌ", "library": "Đ‘Ņ–ĐąĐģŅ–ŅŅ‚ŅĐēа", - "library_add_folder": "Đ”Đ°Đ´Đ°Ņ†ŅŒ ĐŋаĐŋĐē҃", - "library_edit_folder": "Đ ŅĐ´Đ°ĐŗĐ°Đ˛Đ°Ņ†ŅŒ ĐŋаĐŋĐē҃", - "library_page_sort_asset_count": "КоĐģҌĐēĐ°ŅŅ†ŅŒ аб’ĐĩĐēŅ‚Đ°Ņž", - "library_page_sort_created": "Đ”Đ°Ņ‚Đ° ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ", - "library_page_sort_last_modified": "АĐŋĐžŅˆĐŊŅĐĩ СĐŧŅĐŊĐĩĐŊĐŊĐĩ", - "library_page_sort_title": "Назва аĐģŅŒĐąĐžĐŧа", - "licenses": "Đ›Ņ–Ņ†ŅĐŊĐˇŅ–Ņ–", "light": "ХвĐĩŅ‚ĐģĐ°Ņ", - "light_theme": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†Ņ†Đ° ĐŊа ŅĐ˛ĐĩŅ‚ĐģŅƒŅŽ Ņ‚ŅĐŧ҃", - "like": "МĐŊĐĩ ĐŋадайаĐĩŅ†Ņ†Đ°", - "like_deleted": "ПазĐŊаĐēа ÂĢĐŧĐŊĐĩ ĐŋадайаĐĩŅ†Ņ†Đ°Âģ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", - "link": "ĐĄĐŋĐ°ŅŅ‹ĐģĐēа", - "link_motion_video": "Đ—Đ˛ŅĐˇĐ°Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "link_to_docs": "БоĐģҌ҈ ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°Ņ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ Ņž даĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ–.", - "link_to_oauth": "ĐŸŅ€Ņ‹Đ˛ŅĐˇĐ°Ņ†ŅŒ да OAuth", - "linked_oauth_account": "ĐŸŅ€Ņ‹Đ˛ŅĐˇĐ°ĐŊŅ‹ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ OAuth", - "list": "ĐĄĐŋҖҁ", - "live": "Đ–Ņ‹Đ˛ĐžĐĩ", - "load_more": "Đ—Đ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ ŅŅˆŅ‡Ņ", - "loading": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа", - "loading_search_results_failed": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇŅ–Ņ†ŅŒ Đ˛Ņ‹ĐŊŅ–ĐēŅ– ĐŋĐžŅˆŅƒĐē҃", - "local": "ЛаĐēаĐģҌĐŊŅ‹", - "local_asset_cast_failed": "НĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Ņ‚Ņ€Đ°ĐŊҁĐģiŅ€Đ°Đ˛Đ°Ņ†ŅŒ аб’ĐĩĐēŅ‚, ŅĐēŅ– ĐŊĐĩ СаĐŋаĐŧĐŋаваĐŊŅ‹ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€", - "local_assets": "ЛаĐēаĐģҌĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ", - "local_id": "ЛаĐēаĐģҌĐŊŅ‹ Ņ–Đ´ŅĐŊ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņ€", - "local_media_summary": "ЗводĐēа ĐģаĐēаĐģҌĐŊҋ҅ ĐŧĐĩĐ´Ņ‹Ņ", - "local_network": "ЛаĐēаĐģҌĐŊĐ°Ņ ҁĐĩŅ‚Đēа", - "local_network_sheet_info": "ĐŸŅ€Đ°ĐŗŅ€Đ°Đŧа ĐąŅƒĐ´ĐˇĐĩ ĐŋадĐēĐģŅŽŅ‡Đ°Ņ†Ņ†Đ° да ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŋŅ€Đ°Đˇ ĐŗŅŅ‚Ņ‹ URL ĐŋҀҋ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ– СададСĐĩĐŊаК ҁĐĩŅ‚ĐēŅ– Wi-Fi", - "location": "МĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "location_permission": "ДазвоĐģ ĐŊа ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "location_permission_content": "Каб ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Ņ†Ņ†Đ° Ņ„ŅƒĐŊĐēŅ†Ņ‹ŅĐš Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊĐ°ĐŗĐ° ĐŋĐĩŅ€Đ°ĐēĐģŅŽŅ‡ŅĐŊĐŊŅ, Immich ĐŋĐ°Ņ‚Ņ€ŅĐąĐŊŅ‹ даСвОĐģ ĐŊа даĐēĐģадĐŊаĐĩ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, Đēай Ņ‡Ņ‹Ņ‚Đ°Ņ†ŅŒ ĐŊĐ°ĐˇĐ˛Ņƒ ĐąŅĐŗŅƒŅ‡Đ°Đš ҁĐĩŅ‚ĐēŅ– Wi-Fi", - "location_picker_choose_on_map": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ĐŊа ĐēĐ°Ņ€Ņ†Đĩ", - "location_picker_latitude_error": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅƒŅŽ ŅˆŅ‹Ņ€Đ°Ņ‚Ņƒ", - "location_picker_latitude_hint": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ŅˆŅ‹Ņ€Đ°Ņ‚Ņƒ", - "location_picker_longitude_error": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅƒŅŽ Đ´Đ°ŅžĐŗĐ°Ņ‚Ņƒ", - "location_picker_longitude_hint": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ Đ´Đ°ŅžĐŗĐ°Ņ‚Ņƒ", - "lock": "ЗабĐģаĐēŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ", - "locked_folder": "ЗабĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊĐ°Ņ ĐŋаĐŋĐēа", - "log_detail_title": "ĐŸĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ– ĐļŅƒŅ€ĐŊаĐģа", - "log_out": "Đ’Ņ‹ĐšŅŅ†Ņ–", - "log_out_all_devices": "Đ’Ņ‹ĐšŅŅ†Ņ– С ŅƒŅŅ–Ņ… ĐŋҀҋĐģад", - "logged_in_as": "Đ’Ņ‹ ŅžĐ˛Đ°ĐšŅˆĐģŅ– ŅĐē {user}", - "logged_out_all_devices": "Đ’Ņ‹Ņ…Đ°Đ´ С ŅƒŅŅ–Ņ… ĐŋҀҋĐģад Đ˛Ņ‹ĐēаĐŊаĐŊŅ‹", - "logged_out_device": "Đ’Ņ‹Ņ…Đ°Đ´ С ĐŋҀҋĐģĐ°Đ´Ņ‹ Đ˛Ņ‹ĐēаĐŊаĐŊŅ‹", - "login": "ĐŖĐ˛Đ°ĐšŅŅ†Ņ–", - "login_disabled": "ĐŖĐ˛Đ°Ņ…ĐžĐ´ ĐąŅ‹Ņž адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", - "login_form_api_exception": "ПаĐŧŅ‹ĐģĐēа API. ĐŸŅ€Đ°Đ˛ĐĩҀ҆Đĩ URL-flhfc ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Ņ– ĐŋĐ°ŅĐŋŅ€Đ°ĐąŅƒĐšŅ†Đĩ ŅŅˆŅ‡Ņ Ņ€Đ°Đˇ.", + "login_form_back_button_text": "Назад", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "URL ĐēаĐŊŅ‡Đ°Ņ‚ĐēОваК ĐēŅ€ĐžĐŋĐēŅ– ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_form_err_invalid_email": "НĐĩŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅ‹ Email", - "login_form_err_invalid_url": "НĐĩŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅ‹ URL", - "login_form_err_leading_whitespace": "ĐŸŅ€Đ°ĐąĐĩĐģ ҃ ĐŋĐ°Ņ‡Đ°Ņ‚Đē҃", - "login_form_err_trailing_whitespace": "ĐŸŅ€Đ°ĐąĐĩĐģ ҃ ĐēаĐŊ҆ҋ", - "login_form_failed_get_oauth_server_config": "ПаĐŧŅ‹ĐģĐēа ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ ĐŋŅ€Đ°Đˇ OAuth, ĐŋŅ€Đ°Đ˛ĐĩҀ҆Đĩ URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_form_failed_get_oauth_server_disable": "Đ¤ŅƒĐŊĐēŅ†Ņ‹Ņ OAuth ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ ĐŊа ĐŗŅŅ‚Ņ‹Đŧ ҁĐĩŅ€Đ˛ĐĩҀҋ", - "login_form_failed_login": "ПаĐŧŅ‹ĐģĐēа ŅžĐ˛Đ°Ņ…ĐžĐ´Ņƒ, ĐŋŅ€Đ°Đ˛ĐĩҀ҆Đĩ URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅˆŅ‚Ņƒ Ņ– ĐŋĐ°Ņ€ĐžĐģҌ", - "login_form_handshake_exception": "ĐĐ´ĐąŅ‹ĐģĐ°ŅŅ ĐŋаĐŧŅ‹ĐģĐēа ŅžĐˇĐ°ĐĩĐŧадСĐĩŅĐŊĐŊŅ С ҁĐĩŅ€Đ˛ĐĩŅ€Đ°Đŧ. ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐē҃ ŅĐ°ĐŧаĐŋадĐŋŅ–ŅĐ°ĐŊҋ҅ ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Ņž ҃ ĐŊаĐģĐ°Đ´Đ°Ņ…, ĐēаĐģŅ– Đ˛Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Đĩ҆ĐĩŅŅ ŅĐ°ĐŧаĐŋадĐŋŅ–ŅĐ°ĐŊŅ‹Đŧ ҁĐĩҀ҂ҋ҄ҖĐēĐ°Ņ‚Đ°Đŧ.", "login_form_password_hint": "ĐŋĐ°Ņ€ĐžĐģҌ", - "login_form_server_empty": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_form_server_error": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ ĐŋадĐēĐģŅŽŅ‡Ņ‹Ņ†Ņ†Đ° да ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_has_been_disabled": "ĐŖĐ˛Đ°Ņ…ĐžĐ´ ĐąŅ‹Ņž адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", - "login_password_changed_error": "ĐĐ´ĐąŅ‹ĐģĐ°ŅŅ ĐŋаĐŧŅ‹ĐģĐēа ĐŋҀҋ айĐŊĐ°ŅžĐģĐĩĐŊĐŊŅ– ĐŋĐ°Ņ€ĐžĐģŅ", - "login_password_changed_success": "ĐŸĐ°Ņ€ĐžĐģҌ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° айĐŊĐžŅžĐģĐĩĐŊŅ‹", - "logout_all_device_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹ĐšŅŅ†Ņ– С ŅƒŅŅ–Ņ… ĐŋҀҋĐģад?", - "logout_this_device_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹ĐšŅŅ†Ņ– С ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģĐ°Đ´Ņ‹?", - "logs": "Đ–ŅƒŅ€ĐŊаĐģŅ‹", - "longitude": "Đ”Đ°ŅžĐŗĐ°Ņ‚Đ°", - "look": "Đ’Ņ‹ĐŗĐģŅĐ´", - "loop_videos": "Đ—Đ°Ņ†Ņ‹ĐēĐģŅ–Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ°", - "loop_videos_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ, Đēай Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ĐˇĐ°Ņ†Ņ‹ĐēĐģŅ–Đ˛Đ°Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ° Ņž Đ´ŅŅ‚Đ°ĐģŅ‘Đ˛Ņ‹Đŧ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ.", - "main_branch_warning": "Đ’Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Đĩ҆ĐĩŅŅ вĐĩŅ€ŅŅ–ŅĐš ҃ Ņ€Đ°ŅĐŋŅ€Đ°Ņ†ĐžŅžŅ†Ņ‹; ĐŊĐ°ŅŅ‚ĐžĐšĐģŅ–Đ˛Đ° Ņ€ŅĐēаĐŧĐĩĐŊĐ´ŅƒĐĩĐŧ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ Ņ€ŅĐģŅ–ĐˇĐŊŅƒŅŽ вĐĩŅ€ŅŅ–ŅŽ!", + "login_form_save_login": "Đ—Đ°ŅŅ‚Đ°Đ˛Đ°Ņ†Ņ†Đ° Ņž ŅŅ–ŅŅ‚ŅĐŧĐĩ", "main_menu": "ГаĐģĐžŅžĐŊаĐĩ ĐŧĐĩĐŊŅŽ", - "maintenance_action_restore": "АдĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "maintenance_description": "Immich ĐŋĐĩŅ€Đ°Đ˛ĐĩдСĐĩĐŊŅ‹ Ņž Ņ€ŅĐļŅ‹Đŧ Ņ‚ŅŅ…ĐŊҖ҇ĐŊĐ°ĐŗĐ° Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊŅ.", - "maintenance_end": "Đ’Ņ‹ĐšŅŅ†Ņ– С Ņ€ŅĐļŅ‹Đŧ҃ Ņ‚ŅŅ…ĐŊҖ҇ĐŊĐ°ĐŗĐ° Đ°ĐąŅĐģŅƒĐŗĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "maintenance_logged_in_as": "Đ’Ņ‹ ŅžĐ˛Đ°ĐšŅˆĐģŅ– ŅĐē {user}", - "maintenance_restore_from_backup": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–", - "maintenance_restore_library": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "maintenance_restore_library_confirm": "КаĐģŅ– ĐŗŅŅ‚Đ° Đ˛Ņ‹ĐŗĐģŅĐ´Đ°Đĩ ĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊа, ĐŋŅ€Đ°Ņ†ŅĐŗĐ˛Đ°ĐšŅ†Đĩ адĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ С Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–!", - "maintenance_restore_library_description": "АдĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ĐąĐ°ĐˇŅ‹ даĐŊҋ҅", - "maintenance_restore_library_folder_has_files": "ĐŖ ĐŋаĐŋ҆ҋ {folder} {count} ĐŋаĐŋаĐē(Ņ–)", - "maintenance_restore_library_folder_no_files": "ĐŖ ĐŋаĐŋ҆ҋ {folder} ĐŊŅĐŧа Ņ„Đ°ĐšĐģĐ°Ņž!", - "maintenance_restore_library_folder_pass": "Đ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ Đ´ĐģŅ Ņ‡Ņ‹Ņ‚Đ°ĐŊĐŊŅ Ņ– СаĐŋŅ–ŅŅƒ", - "maintenance_restore_library_folder_read_fail": "ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ Đ´ĐģŅ Ņ‡Ņ‹Ņ‚Đ°ĐŊĐŊŅ", - "maintenance_restore_library_folder_write_fail": "ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ Đ´ĐģŅ СаĐŋŅ–ŅŅƒ", - "maintenance_restore_library_hint_missing_files": "ĐœĐ°ĐŗŅ‡Ņ‹Đŧа, Đ°Đ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°ŅŽŅ†ŅŒ ваĐļĐŊŅ‹Ņ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_restore_library_hint_regenerate_later": "Đ’Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ĐŋĐĩŅ€Đ°ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ Ņ–Ņ… ĐŋаСĐŊĐĩĐš ҃ ĐŊаĐģĐ°Đ´Đ°Ņ…", - "maintenance_restore_library_hint_storage_template_missing_files": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°Đĩ҆ĐĩŅŅ ŅˆĐ°ĐąĐģĐžĐŊаĐŧ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°? ĐœĐ°ĐŗŅ‡Ņ‹Đŧа, Đ°Đ´ŅŅƒŅ‚ĐŊŅ–Ņ‡Đ°ŅŽŅ†ŅŒ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_restore_library_loading": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŋŅ€Đ°Đ˛ĐĩŅ€Đ°Đē Ņ†ŅĐģĐ°ŅĐŊĐ°ŅŅ†Ņ– Ņ– ŅŅžŅ€Ņ‹ŅŅ‚Ņ‹Đēâ€Ļ", - "maintenance_task_backup": "ĐĄŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ– ҖҁĐŊŅƒŅŽŅ‡Đ°Đš ĐąĐ°ĐˇŅ‹ даĐŊҋ҅â€Ļ", - "maintenance_task_migrations": "Đ’Ņ‹ĐēаĐŊаĐŊĐŊĐĩ ĐŧŅ–ĐŗŅ€Đ°Ņ†Ņ‹Ņ– ĐąĐ°ĐˇŅ‹ даĐŊҋ҅â€Ļ", - "maintenance_task_restore": "АдĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊаК Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°Đš ĐēĐžĐŋŅ–Ņ–â€Ļ", - "maintenance_task_rollback": "АдĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŊĐĩ Đ˛Ņ‹ĐēаĐŊаĐģĐ°ŅŅ, адĐēĐ°Ņ‚ да Đŋ҃ĐŊĐēŅ‚Đ° адĐŊĐ°ŅžĐģĐĩĐŊĐŊŅâ€Ļ", - "maintenance_title": "Đ§Đ°ŅĐžĐ˛Đ° ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊа", - "make": "Đ’Ņ‹Ņ‚Đ˛ĐžŅ€Ņ†Đ°", - "manage_geolocation": "ĐšŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩĐŧ", - "manage_media_access_rationale": "Đ“ŅŅ‚Ņ‹ даСвОĐģ ĐŊĐĩĐ°ĐąŅ…ĐžĐ´ĐŊŅ‹ Đ´ĐģŅ ĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊаК аĐŋŅ€Đ°Ņ†ĐžŅžĐēŅ– ĐŋĐĩŅ€Đ°ĐŧŅŅˆŅ‡ŅĐŊĐŊŅ аб’ĐĩĐēŅ‚Đ°Ņž ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ Ņ– адĐŊĐ°ŅžĐģĐĩĐŊĐŊŅ С ŅĐĩ.", - "manage_media_access_settings": "АдĐēŅ€Ņ‹Ņ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹", - "manage_media_access_subtitle": "ДазвоĐģŅ–Ņ†ŅŒ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧĐĩ Immich ĐēŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Ņ– ĐŋĐĩŅ€Đ°ĐŧŅŅˆŅ‡Đ°Ņ†ŅŒ ĐŧĐĩĐ´Ņ‹ŅŅ„Đ°ĐšĐģŅ‹.", - "manage_media_access_title": "Đ”ĐžŅŅ‚ŅƒĐŋ да ĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ ĐŧĐĩĐ´Ņ‹Ņ", - "manage_sharing_with_partners": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩĐŧ С ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°ĐŧŅ–", - "manage_the_app_settings": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŊаĐģадаĐŧŅ– ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "manage_your_account": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹Đŧ СаĐŋŅ–ŅĐ°Đŧ", - "manage_your_api_keys": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐēĐģŅŽŅ‡Đ°ĐŧŅ– API", - "manage_your_devices": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋҀҋĐģадаĐŧŅ–, ĐŊа ŅĐēŅ–Ņ… Đ˛Ņ‹ ŅžĐ˛Đ°ĐšŅˆĐģŅ– Ņž ŅŅ–ŅŅ‚ŅĐŧ҃", - "manage_your_oauth_connection": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋадĐēĐģŅŽŅ‡ŅĐŊĐŊĐĩĐŧ OAuth", - "map": "ĐšĐ°Ņ€Ņ‚Đ°", - "map_cannot_get_user_location": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "map_location_picker_page_use_location": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Ņ†ŅŒ ĐŗŅŅ‚Đ° ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "map_location_service_disabled_content": "Каб ĐŋаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ С Đ˛Đ°ŅˆĐ°ĐŗĐ° ĐąŅĐŗŅƒŅ‡Đ°ĐŗĐ° ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊŅ, Ņ‚Ņ€ŅĐąĐ° ŅžĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ҁĐģ҃ĐļĐąŅƒ ĐŗĐĩаĐģаĐēĐ°Ņ†Ņ‹Ņ–. ЖадаĐĩ҆Đĩ ŅžĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅĐĩ ĐˇĐ°Ņ€Đ°Đˇ?", - "map_location_service_disabled_title": "ĐĄĐģ҃Đļйа ĐŗĐĩаĐģаĐēĐ°Ņ†Ņ‹Ņ– адĐēĐģŅŽŅ‡Đ°ĐŊа", - "map_marker_for_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ†Đĩ Đ´ĐģŅ Ņ„ĐžŅ‚Đ°, ĐˇŅ€ĐžĐąĐģĐĩĐŊĐ°ĐŗĐ° Ņž {city}, {country}", - "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ†Đĩ С Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Đŧ", - "map_no_location_permission_content": "Каб ĐŋаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ аб’ĐĩĐē҂ҋ С Đ˛Đ°ŅˆĐ°ĐŗĐ° ĐąŅĐŗŅƒŅ‡Đ°ĐŗĐ° ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊŅ, ĐŋĐ°Ņ‚Ņ€ŅĐąĐĩĐŊ даСвОĐģ ĐŊа ĐŗĐĩаĐģаĐēĐ°Ņ†Ņ‹ŅŽ. ДазвоĐģŅ–Ņ†ŅŒ ĐˇĐ°Ņ€Đ°Đˇ?", - "map_no_location_permission_title": "АдĐŧĐžŅžĐģĐĩĐŊа Ņž даСвОĐģĐĩ ĐŊа ĐŗĐĩаĐģаĐēĐ°Ņ†Ņ‹ŅŽ", - "map_settings": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‚Ņ‹", + "map_location_dialog_yes": "ĐĸаĐē", "map_settings_dark_mode": "ĐĻŅ‘ĐŧĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ", "map_settings_date_range_option_day": "АĐŋĐžŅˆĐŊŅ–Ņ 24 ĐŗĐ°Đ´ĐˇŅ–ĐŊŅ‹", "map_settings_date_range_option_days": "АĐŋĐžŅˆĐŊŅ–Ņ… Đ´ĐˇŅ‘ĐŊ: {days}", "map_settings_date_range_option_year": "АĐŋĐžŅˆĐŊŅ– ĐŗĐžĐ´", "map_settings_date_range_option_years": "АĐŋĐžŅˆĐŊŅ–Ņ… ĐŗĐžĐ´: {years}", - "map_settings_include_show_archived": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Đ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°ĐŊŅ‹Ņ", - "map_settings_include_show_partners": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°Ņž", - "map_settings_only_show_favorites": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Ņ‚ĐžĐģҌĐēŅ– Đ°ĐąŅ€Đ°ĐŊаĐĩ", + "map_settings_dialog_title": "НаĐģĐ°Đ´Ņ‹ ĐēĐ°Ņ€Ņ‚Ņ‹", "map_settings_theme_settings": "ĐĸŅĐŧа ĐēĐ°Ņ€Ņ‚Ņ‹", - "mark_all_as_read": "ПазĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ ŅƒŅŅ‘ ŅĐē ĐŋŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°ĐŊаĐĩ", - "marked_all_as_read": "ĐŖŅŅ‘ ĐŋаСĐŊĐ°Ņ‡Đ°ĐŊа ŅĐē ĐŋŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°ĐŊаĐĩ", - "matches": "ĐĄŅƒĐŋадСĐĩĐŊĐŊŅ–", - "matching_assets": "Аб’ĐĩĐē҂ҋ, ŅˆŅ‚Đž ҁ҃ĐŋĐ°Đ´Đ°ŅŽŅ†ŅŒ", - "media_chrome": { - "auto": "ĐŅžŅ‚Đ°", - "captions": "ĐĄŅƒĐąŅ†Ņ–Ņ‚Ņ€Ņ‹", - "captions_off": "Đ’Ņ‹ĐēĐģ", - "closed_captions": "СаĐēŅ€Ņ‹Ņ‚Ņ‹Ņ ŅŅƒĐąŅ†Ņ–Ņ‚Ņ€Ņ‹", - "decode_error": "ПаĐŧŅ‹ĐģĐēа Đ´ŅĐēадаваĐŊĐŊŅ", - "disable_captions": "АдĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅŅƒĐąŅ†Ņ–Ņ‚Ņ€Ņ‹", - "enable_captions": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ŅŅƒĐąŅ†Ņ–Ņ‚Ņ€Ņ‹", - "enter_fullscreen_mode": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŋĐžŅžĐŊĐ°ŅĐēŅ€Đ°ĐŊĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ", - "exit_fullscreen_mode": "Đ’Ņ‹ĐšŅŅ†Ņ– С ĐŋĐžŅžĐŊĐ°ŅĐēŅ€Đ°ĐŊĐŊĐ°ĐŗĐ° Ņ€ŅĐļŅ‹Đŧ҃", - "loop": "ĐĻŅ‹ĐēĐģ", - "media_error_description": "ПаĐŧŅ‹ĐģĐēа ĐŧĐĩĐ´Ņ‹Ņ ĐŋŅ€Ņ‹Đ˛ŅĐģа да ĐŋĐĩŅ€Đ°ĐŋŅ‹ĐŊĐĩĐŊĐŊŅ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ. МĐĩĐ´Ņ‹Ņ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ ĐŋĐ°ŅˆĐēОдĐļаĐŊа, айО Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅžĐˇĐĩŅ€ ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°Đĩ ĐŗŅŅ‚Ņ‹ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚.", - "media_loading": "ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа ĐŧĐĩĐ´Ņ‹Ņ", - "mute": "АдĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŗŅƒĐē", - "network_error": "ПаĐŧŅ‹ĐģĐēа ҁĐĩŅ‚ĐēŅ–", - "network_error_description": "ПаĐŧŅ‹ĐģĐēа ҁĐĩŅ‚ĐēŅ– ĐŋŅ€Ņ‹Đ˛ŅĐģа да ĐˇĐąĐžŅŽ ҁĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ ĐŧĐĩĐ´Ņ‹Ņ.", - "not_supported_error": "ĐšŅ€Ņ‹ĐŊŅ–Ņ†Đ° ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ°", - "playback_rate": "ĐĨŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "playback_rate_current": "ĐąŅĐŗŅƒŅ‡Đ°Ņ Ņ…ŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "playback_rate_value": "ĐĨŅƒŅ‚ĐēĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ {playbackRate}", - "playback_time": "Ņ‡Đ°Ņ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "quality": "Đ¯ĐēĐ°ŅŅ†ŅŒ", - "second": "ҁĐĩĐē҃ĐŊда", - "seconds": "ҁĐĩĐē҃ĐŊĐ´Ņ‹", - "time_value_of_total_time": "{currentTime} С {totalTime}", - "time_value_remaining": "ĐˇĐ°ŅŅ‚Đ°ĐģĐžŅŅ {time}", - "unmute": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŗŅƒĐē", - "unsupported_error_description": "ĐĐ´ĐąŅ‹ĐģĐ°ŅŅ ĐŊĐĩĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩĐŧĐ°Ņ ĐŋаĐŧŅ‹ĐģĐēа. Збой ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ҆Җ ҁĐĩŅ‚ĐēŅ–, айО Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅžĐˇĐĩŅ€ ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°Đĩ ĐŗŅŅ‚Ņ‹ Ņ„Đ°Ņ€ĐŧĐ°Ņ‚.", - "video_not_loaded_unknown_time": "Đ˛Ņ–Đ´ŅĐ° ĐŊĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļаĐŊа, Ņ‡Đ°Ņ ĐŊĐĩĐ˛ŅĐ´ĐžĐŧŅ‹.", - "video_player": "Đ˛Ņ–Đ´ŅĐ°ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐģҌĐŊŅ–Đē", - "volume": "ĐŗŅƒŅ‡ĐŊĐ°ŅŅ†ŅŒ" - }, - "media_type": "ĐĸŅ‹Đŋ ĐŧĐĩĐ´Ņ‹Ņ", - "memories": "ĐŖŅĐŋаĐŧŅ–ĐŊŅ‹", - "memories_all_caught_up": "ĐŖŅŅ‘ ĐŋŅ€Đ°ĐŗĐģĐĩĐ´ĐļаĐŊа", - "memories_check_back_tomorrow": "Đ—Đ°ĐšĐ´ĐˇŅ–Ņ†Đĩ ĐˇĐ°ŅžŅ‚Ņ€Đ°, Đēай ŅƒĐąĐ°Ņ‡Ņ‹Ņ†ŅŒ йОĐģҌ҈ ҃ҁĐŋаĐŧŅ–ĐŊĐ°Ņž", - "memories_setting_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ҂ҋĐŧ, ŅˆŅ‚Đž Đ˛Ņ‹ ĐąĐ°Ņ‡Ņ‹Ņ†Đĩ ва ŅžŅĐŋаĐŧŅ–ĐŊĐ°Ņ…", - "memories_start_over": "ĐŸĐ°Ņ‡Đ°Ņ†ŅŒ ҁĐŋĐ°Ņ‡Đ°Ņ‚Đē҃", - "memories_swipe_to_close": "ĐŸŅ€Đ°Đ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ŅžĐ˛ĐĩҀ҅, Đēай СаĐēŅ€Ņ‹Ņ†ŅŒ", - "memory": "ĐŖŅĐŋаĐŧŅ–ĐŊ", - "memory_lane_title": "Đ”Đ°Ņ€ĐžĐŗĐ° ŅžŅĐŋаĐŧŅ–ĐŊĐ°Ņž {title}", "menu": "МĐĩĐŊŅŽ", - "merge": "ĐĐąâ€™ŅĐ´ĐŊĐ°Ņ†ŅŒ", - "merge_people": "ĐĐąâ€™ŅĐ´ĐŊĐ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "merge_people_limit": "МоĐļĐŊа Đ°Đąâ€™ŅĐ´ĐŊĐ°Ņ†ŅŒ ĐŊĐĩ йОĐģҌ҈ Са 5 Ņ‚Đ˛Đ°Ņ€Đ°Ņž адĐŊĐ°Ņ‡Đ°ŅĐžĐ˛Đ°", - "merge_people_prompt": "ĐĨĐžŅ‡Đ°Ņ†Đĩ Đ°Đąâ€™ŅĐ´ĐŊĐ°Ņ†ŅŒ ĐŗŅŅ‚Ņ‹Ņ… ĐģŅŽĐ´ĐˇĐĩĐš? Đ“ŅŅ‚Đ°Đĩ дСĐĩŅĐŊĐŊĐĩ ĐŊĐĩĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊаĐĩ.", - "merge_people_successfully": "Đ›ŅŽĐ´ĐˇŅ– ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° Đ°Đąâ€™ŅĐ´ĐŊаĐŊŅ‹Ņ", - "merged_people_count": "{count, plural, one {ĐĐąâ€™ŅĐ´ĐŊаĐŊŅ‹ # Ņ‡Đ°ĐģавĐĩĐē} few {ĐĐąâ€™ŅĐ´ĐŊаĐŊŅ‹ # Ņ‡Đ°ĐģавĐĩĐēŅ–} many {ĐĐąâ€™ŅĐ´ĐŊаĐŊа # Ņ‡Đ°ĐģавĐĩĐē} other {ĐĐąâ€™ŅĐ´ĐŊаĐŊа # Ņ‡Đ°ĐģавĐĩĐēа}}", - "minFaces": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", - "minFaces_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊĐ°Ņ ĐēĐžĐģҌĐēĐ°ŅŅ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаĐŊҋ҅ Ņ‚Đ˛Đ°Ņ€Đ°Ņž, ĐŊĐĩĐ°ĐąŅ…ĐžĐ´ĐŊĐ°Ņ Đ´ĐģŅ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ Ņ‡Đ°ĐģавĐĩĐēа", - "minimize": "Đ—ĐŗĐ°Ņ€ĐŊŅƒŅ†ŅŒ", "minute": "ĐĨĐ˛Ņ–ĐģŅ–ĐŊа", - "minutes": "ĐĨĐ˛Ņ–ĐģŅ–ĐŊŅ‹", - "missing": "ĐĐ´ŅŅƒŅ‚ĐŊŅ‹Ņ", - "mobile_app": "ĐœĐ°ĐąŅ–ĐģҌĐŊĐ°Ņ ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧа", - "mobile_app_download_onboarding_note": "ĐĄĐŋаĐŧĐŋŅƒĐšŅ†Đĩ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅƒŅŽ ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ҃ С даĐŋаĐŧĐžĐŗĐ°Đš ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊҋ҅ Đ˛Đ°Ņ€Ņ‹ŅĐŊŅ‚Đ°Ņž", - "model": "ĐœĐ°Đ´ŅĐģҌ", - "modify_date": "Đ”Đ°Ņ‚Đ° СĐŧŅĐŊĐĩĐŊĐŊŅ", "month": "МĐĩŅŅŅ†", - "more": "БоĐģҌ҈", - "motion": "Đ ŅƒŅ…", - "move": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ", - "move_off_locked_folder": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ С СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–", - "move_to": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҃", - "move_to_device_trash": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҃ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ ĐŋҀҋĐģĐ°Đ´Ņ‹", - "move_to_lock_folder_action_prompt": "{count} дададСĐĩĐŊа Ņž СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊŅƒŅŽ ĐŋаĐŋĐē҃", - "move_to_locked_folder": "ПĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҃ СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊŅƒŅŽ ĐŋаĐŋĐē҃", - "move_to_locked_folder_confirmation": "Đ“ŅŅ‚Ņ‹Ņ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ С ŅƒŅŅ–Ņ… аĐģŅŒĐąĐžĐŧĐ°Ņž Ņ– ĐˇĐ°ŅŅ‚Đ°ĐŊŅƒŅ†Ņ†Đ° Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ĐŧŅ– Đ´ĐģŅ ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ Ņ‚ĐžĐģҌĐēŅ– С СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–", - "moved_to_trash": "ПĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "mute_memories": "АдĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŗŅƒĐē ҃ҁĐŋаĐŧŅ–ĐŊĐ°Ņž", + "monthly_title_text_date_format": "MMMM y", "my_albums": "МаĐĩ аĐģŅŒĐąĐžĐŧŅ‹", - "my_immich_description": "ĐĄĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐąŅĐŗŅƒŅ‡ŅƒŅŽ ŅŅ‚Đ°Ņ€ĐžĐŊĐē҃ ŅĐē ҁĐŋĐ°ŅŅ‹ĐģĐē҃ My Immich", - "my_immich_title": "ĐĄĐŋĐ°ŅŅ‹ĐģĐēа My Immich", "name": "ІĐŧŅ", "name_or_nickname": "ІĐŧŅ айО ĐŋҁĐĩŅžĐ´Đ°ĐŊŅ–Đŧ", - "name_required": "ІĐŧŅ Đ°ĐąĐ°Đ˛ŅĐˇĐēОваĐĩ", - "navigate": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ–", - "navigate_to_time": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да Ņ‡Đ°ŅŅƒ", - "network_requirement_photos_upload": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅ‹Ņ даĐŊŅ‹Ņ Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ Ņ„ĐžŅ‚Đ°", - "network_requirement_videos_upload": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐŧĐ°ĐąŅ–ĐģҌĐŊŅ‹Ņ даĐŊŅ‹Ņ Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ Đ˛Ņ–Đ´ŅĐ°", - "network_requirements": "ĐĄĐĩŅ‚ĐēĐ°Đ˛Ņ‹Ņ ĐŋĐ°Ņ‚Ņ€Đ°ĐąĐ°Đ˛Đ°ĐŊĐŊŅ–", - "network_requirements_updated": "ĐĄĐĩŅ‚ĐēĐ°Đ˛Ņ‹Ņ ĐŋĐ°Ņ‚Ņ€Đ°ĐąĐ°Đ˛Đ°ĐŊĐŊŅ– СĐŧĐĩĐŊĐĩĐŊŅ‹, ҁĐēŅ–Đ´ Ņ‡Đ°Ņ€ĐŗŅ– Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "networking_settings": "ĐĄĐĩŅ‚Đēа", - "networking_subtitle": "ĐšŅ–Ņ€ŅƒĐšŅ†Đĩ ĐŊаĐģадаĐŧŅ– ĐēаĐŊŅ‡Đ°Ņ‚ĐēОваК ĐēŅ€ĐžĐŋĐēŅ– ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "never": "ĐŅ–ĐēĐžĐģŅ–", - "new_album": "ĐĐžĐ˛Ņ‹ аĐģŅŒĐąĐžĐŧ", - "new_api_key": "ĐĐžĐ˛Ņ‹ ĐēĐģŅŽŅ‡ API", - "new_feature": "ĐĐžĐ˛Đ°Ņ Ņ„ŅƒĐŊĐēŅ†Ņ‹Ņ", - "new_password": "ĐĐžĐ˛Ņ‹ ĐŋĐ°Ņ€ĐžĐģҌ", - "new_person": "ĐĐžĐ˛Ņ‹ Ņ‡Đ°ĐģавĐĩĐē", - "new_pin_code": "ĐĐžĐ˛Ņ‹ PIN-ĐēОд", - "new_pin_code_subtitle": "Đ“ŅŅ‚Đ° Đ˛Đ°Ņˆ ĐŋĐĩŅ€ŅˆŅ‹ Đ´ĐžŅŅ‚ŅƒĐŋ да СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–. ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ PIN-ĐēОд Đ´ĐģŅ ĐąŅŅĐŋĐĩ҇ĐŊĐ°ĐŗĐ° Đ´ĐžŅŅ‚ŅƒĐŋ҃ да ĐŗŅŅ‚Đ°Đš ŅŅ‚Đ°Ņ€ĐžĐŊĐēŅ–", - "new_update": "НоваĐĩ айĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ", - "new_user_created": "ĐĐžĐ˛Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē ŅŅ‚Đ˛ĐžŅ€Đ°ĐŊŅ‹", - "new_version_available": "ДАСĐĸĐŖĐŸĐĐ ĐĐžĐ’ĐĐ¯ Đ’Đ•Đ ĐĄĐ†Đ¯", - "newest_first": "ĐĄĐŋĐ°Ņ‡Đ°Ņ‚Đē҃ ĐŊĐžĐ˛Ņ‹Ņ", "next": "ДаĐģĐĩĐš", - "next_memory": "ĐĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹ ŅžŅĐŋаĐŧŅ–ĐŊ", "no": "НĐĩ", - "no_albums_found": "АĐģŅŒĐąĐžĐŧĐ°Ņž ĐŊĐĩ СĐŊОКдСĐĩĐŊа", - "no_albums_message": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ аĐģŅŒĐąĐžĐŧ, Đēай ҃ĐŋĐ°Ņ€Đ°Đ´ĐēĐ°Đ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "no_albums_with_name_yet": "ЗдаĐĩŅ†Ņ†Đ°, ҃ Đ˛Đ°Ņ ĐŋаĐē҃ĐģҌ ĐŊŅĐŧа аĐģŅŒĐąĐžĐŧĐ°Ņž С Ņ‚Đ°ĐēОК ĐŊаСваК.", - "no_albums_yet": "ЗдаĐĩŅ†Ņ†Đ°, ҃ Đ˛Đ°Ņ ĐŋаĐē҃ĐģҌ ĐŊŅĐŧа аĐģŅŒĐąĐžĐŧĐ°Ņž.", - "no_archived_assets_message": "ĐŅ€Ņ…Ņ–Đ˛ŅƒĐšŅ†Đĩ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°, Đēай ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ–Ņ… С Đ˛Ņ‹ĐŗĐģŅĐ´Ņƒ ÂĢĐ¤ĐžŅ‚Đ°Âģ", - "no_assets_message": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ, Đēай СаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€ŅˆĐ°Đĩ Ņ„ĐžŅ‚Đ°", - "no_assets_to_show": "ĐŅĐŧа аб’ĐĩĐēŅ‚Đ°Ņž Đ´ĐģŅ ĐŋаĐēĐ°ĐˇŅƒ", - "no_cast_devices_found": "ĐŸŅ€Ņ‹ĐģĐ°Đ´Ņ‹ Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ‹Ņ– ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹", - "no_checksum_local": "КаĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃Đŧа ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ — ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ ĐģаĐēаĐģҌĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ", - "no_checksum_remote": "КаĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃Đŧа ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ — ĐŊĐĩĐŧĐ°ĐŗŅ‡Ņ‹Đŧа Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ аддаĐģĐĩĐŊŅ‹ аб’ĐĩĐēŅ‚", - "no_devices": "ĐŅĐŧа Đ°ŅžŅ‚Đ°Ņ€Ņ‹ĐˇĐ°Đ˛Đ°ĐŊҋ҅ ĐŋҀҋĐģад", - "no_duplicates_found": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž ĐŊĐĩ СĐŊОКдСĐĩĐŊа.", - "no_exif_info_available": "ЗвĐĩҁ҂ĐēŅ– EXIF ĐŊĐĩĐ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ", - "no_explore_results_message": "ЗаĐŋаĐŧĐŋŅƒĐšŅ†Đĩ йОĐģҌ҈ Ņ„ĐžŅ‚Đ°, Đēай Đ´Đ°ŅĐģĐĩĐ´Đ°Đ˛Đ°Ņ†ŅŒ ŅĐ˛Đ°ŅŽ ĐēаĐģĐĩĐēŅ†Ņ‹ŅŽ.", - "no_favorites_message": "Đ”Đ°Đ´Đ°Đ˛Đ°ĐšŅ†Đĩ Ņž Đ°ĐąŅ€Đ°ĐŊаĐĩ, Đēай Ņ…ŅƒŅ‚Đēа СĐŊĐ°Ņ…ĐžĐ´ĐˇŅ–Ņ†ŅŒ ĐŊаКĐģĐĩĐŋŅˆŅ‹Ņ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "no_libraries_message": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ СĐŊĐĩ҈ĐŊŅŽŅŽ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃, Đēай ĐŋŅ€Đ°ĐŗĐģŅĐ´Đ°Ņ†ŅŒ ŅĐ˛Đ°Đĩ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "no_local_assets_found": "ЛаĐēаĐģҌĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ С Ņ‚Đ°ĐēОК ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧаК ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹", - "no_locked_photos_message": "Đ¤ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° Ņž СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋ҆ҋ ŅŅ…Đ°Đ˛Đ°ĐŊŅ‹Ņ Ņ– ĐŊĐĩ ĐŋаĐēĐ°ĐˇĐ˛Đ°ŅŽŅ†Ņ†Đ° ĐŋҀҋ ĐŋŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩ ҆Җ ĐŋĐžŅˆŅƒĐē҃ Ņž ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅŅ†Ņ‹.", - "no_name": "БĐĩС ĐŊĐ°ĐˇĐ˛Ņ‹", - "no_notifications": "ĐŅĐŧа аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž", - "no_people_found": "АдĐŋавĐĩĐ´ĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš ĐŊĐĩ СĐŊОКдСĐĩĐŊа", - "no_places": "ĐŅĐŧа ĐŧĐĩŅŅ†Đ°Ņž", - "no_remote_assets_found": "АддаĐģĐĩĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ С Ņ‚Đ°ĐēОК ĐēаĐŊŅ‚Ņ€ĐžĐģҌĐŊаК ҁ҃ĐŧаК ĐŊĐĩ СĐŊОКдСĐĩĐŊŅ‹", - "no_results": "ĐŅĐŧа Đ˛Ņ‹ĐŊŅ–ĐēĐ°Ņž", - "no_results_description": "ĐŸĐ°ŅĐŋŅ€Đ°ĐąŅƒĐšŅ†Đĩ ҁҖĐŊĐžĐŊŅ–Đŧ айО йОĐģҌ҈ Đ°ĐŗŅƒĐģҌĐŊаĐĩ ĐēĐģŅŽŅ‡Đ°Đ˛ĐžĐĩ ҁĐģОва", - "no_shared_albums_message": "ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ аĐģŅŒĐąĐžĐŧ, Đēай Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° С ĐģŅŽĐ´ĐˇŅŒĐŧŅ– Ņž Đ˛Đ°ŅˆĐ°Đš ҁĐĩ҂҆ҋ", - "no_steps": "ĐšŅ€ĐžĐēŅ– ĐŋаĐē҃ĐģҌ ĐŊĐĩ дададСĐĩĐŊŅ‹", - "none": "ĐŅĐŧа", - "not_allowed": "НĐĩ даСвОĐģĐĩĐŊа", - "not_available": "Н/Д", - "not_in_any_album": "ĐŅ– Ņž ŅĐēŅ–Đŧ аĐģŅŒĐąĐžĐŧĐĩ", - "not_selected": "НĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊа", - "not_set": "НĐĩ СададСĐĩĐŊа", - "notes": "Đ—Đ°ŅžĐ˛Đ°ĐŗŅ–", - "nothing_here_yet": "ĐĸŅƒŅ‚ ĐŋаĐē҃ĐģҌ ĐŊŅ–Ņ‡ĐžĐŗĐ° ĐŊŅĐŧа", - "notification_backup_reliability": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–, Đēай ĐŋĐ°Đ˛Ņ‹ŅŅ–Ņ†ŅŒ ĐŊадСĐĩĐšĐŊĐ°ŅŅ†ŅŒ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–ŅĐ˛Đ°ĐŊĐŊŅ Ņž Ņ„ĐžĐŊĐ°Đ˛Ņ‹Đŧ Ņ€ŅĐļŅ‹ĐŧĐĩ", - "notification_enabled_list_tile_content": "Immich Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– Đ´ĐģŅ Ņ€ŅĐˇĐĩŅ€Đ˛ĐžĐ˛Đ°ĐŗĐ° ĐēаĐŋŅ–ŅĐ˛Đ°ĐŊĐŊŅ Ņž Ņ„ĐžĐŊĐ°Đ˛Ņ‹Đŧ Ņ€ŅĐļŅ‹ĐŧĐĩ. ĐšŅ–Ņ€ŅƒĐšŅ†Đĩ Ņ–ĐŧŅ– Ņž ĐŊаĐģĐ°Đ´Đ°Ņ… ĐŋҀҋĐģĐ°Đ´Ņ‹.", - "notification_enabled_list_tile_open_button": "АдĐēŅ€Ņ‹Ņ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹", - "notification_enabled_list_tile_title": "АĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", - "notification_permission_dialog_content": "Каб ҃ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–, ĐŋĐĩŅ€Đ°ĐšĐ´ĐˇŅ–Ņ†Đĩ Ņž НаĐģĐ°Đ´Ņ‹ Ņ– Đ´Đ°ĐšŅ†Đĩ даСвОĐģ.", - "notification_permission_list_tile_content": "Đ”Đ°ĐšŅ†Đĩ даСвОĐģ, Đēай ҃ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–.", - "notification_permission_list_tile_enable_button": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–", - "notification_permission_list_tile_title": "ДазвоĐģ ĐŊа аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–", - "notification_toggle_setting_description": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ– Đŋа ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊаК ĐŋĐžŅˆŅ†Đĩ", - "notifications": "АĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅ–", - "notifications_setting_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅĐŧŅ–", - "oauth": "OAuth", - "obtainium_configurator": "КаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ‚Đ°Ņ€ Obtainium", - "obtainium_configurator_instructions": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°ĐšŅ†Đĩ Obtainium, Đēай ŅƒŅŅ‚Đ°ĐģŅ‘ŅžĐ˛Đ°Ņ†ŅŒ Ņ– айĐŊĐ°ŅžĐģŅŅ†ŅŒ Android-ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ҃ ĐŊĐĩĐŋĐ°ŅŅ€ŅĐ´ĐŊа С Ņ€ŅĐģŅ–ĐˇĐ°Ņž Immich ĐŊа GitHub. ĐĄŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†Đĩ ĐēĐģŅŽŅ‡ API Ņ– Đ°ĐąŅŅ€Ņ‹Ņ†Đĩ Đ˛Đ°Ņ€Ņ‹ŅĐŊŅ‚, Đēай ŅŅ‚Đ˛Đ°Ņ€Ņ‹Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ– Obtainium", - "ocr": "OCR", - "ocr_body": "ĐĻŅĐŋĐĩŅ€ Immich Ņ‡Ņ‹Ņ‚Đ°Đĩ Ņ‚ŅĐēҁ҂ ĐŊа Ņ„ĐžŅ‚Đ°, Ņ‚Đ°Đŧ҃ Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ҈҃ĐēĐ°Ņ†ŅŒ Ņ–Ņ… Đŋа ҂ҋĐŧ, ŅˆŅ‚Đž Ņ‚Đ°Đŧ ĐŊаĐŋŅ–ŅĐ°ĐŊа.", - "ocr_title": "ĐŸĐžŅˆŅƒĐē Ņ‚ŅĐēŅŅ‚Ņƒ ĐŊа Ņ„ĐžŅ‚Đ°", - "official_immich_resources": "ĐŅ„Ņ–Ņ†Ņ‹ĐšĐŊŅ‹Ņ Ņ€ŅŅŅƒŅ€ŅŅ‹ Immich", "offline": "Па-Са ҁĐĩŅ‚ĐēаК", - "offset": "Đ—Ņ€ŅƒŅ…", - "ok": "ОĐē", - "oldest_first": "ĐĄĐŋĐ°Ņ‡Đ°Ņ‚Đē҃ ŅŅ‚Đ°Ņ€Ņ‹Ņ", - "on_this_device": "На ĐŗŅŅ‚Đ°Đš ĐŋҀҋĐģадСĐĩ", - "onboarding": "ЗĐŊĐ°Ņ‘ĐŧŅŅ‚Đ˛Đ°", - "onboarding_locale_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŋŅ€Ņ‹ŅŅ€Ņ‹Ņ‚ŅŅ‚ĐŊŅƒŅŽ ĐŧĐžĐ˛Ņƒ. Đ’Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ СĐŧŅĐŊŅ–Ņ†ŅŒ ŅĐĩ ĐŋаСĐŊĐĩĐš ҃ ĐŊаĐģĐ°Đ´Đ°Ņ….", - "onboarding_privacy_description": "ĐĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ (ĐŊĐĩĐ°ĐąĐ°Đ˛ŅĐˇĐēĐžĐ˛Ņ‹Ņ) Ņ„ŅƒĐŊĐē҆ҋҖ СаĐģĐĩĐļĐ°Ņ†ŅŒ ад СĐŊĐĩ҈ĐŊŅ–Ņ… ŅŅŅ€Đ˛Ņ–ŅĐ°Ņž Ņ– ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ Ņž ĐģŅŽĐąŅ‹ Ņ‡Đ°Ņ ҃ ĐŊаĐģĐ°Đ´Đ°Ņ….", - "onboarding_server_welcome_description": "Đ”Đ°Đ˛Đ°ĐšŅ†Đĩ ĐŊаĐģĐ°Đ´ĐˇŅ–Đŧ ĐŊĐĩĐēĐ°Ņ‚ĐžŅ€Ņ‹Ņ Đ°ĐŗŅƒĐģҌĐŊŅ‹Ņ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", - "onboarding_theme_description": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐēĐžĐģĐĩŅ€Đ°Đ˛ŅƒŅŽ Ņ‚ŅĐŧ҃. Đ¯Đĩ ĐŧĐžĐļĐŊа СĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋаСĐŊĐĩĐš ҃ ĐŊаĐģĐ°Đ´Đ°Ņ….", - "onboarding_user_welcome_description": "Đ”Đ°Đ˛Đ°ĐšŅ†Đĩ ĐŋĐ°Ņ‡ĐŊŅ‘Đŧ!", - "onboarding_welcome_user": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ, {user}", + "ok": "ОК", "online": "ĐŖ ҁĐĩ҂҆ҋ", - "only_favorites": "ĐĸĐžĐģҌĐēŅ– Đ°ĐąŅ€Đ°ĐŊŅ‹Ņ", "open": "АдĐēŅ€Ņ‹Ņ†ŅŒ", - "open_calendar": "АдĐēŅ€Ņ‹Ņ†ŅŒ ĐēаĐģŅĐŊĐ´Đ°Ņ€", - "open_in_browser": "АдĐēŅ€Ņ‹Ņ†ŅŒ ҃ ĐąŅ€Đ°ŅžĐˇĐĩҀҋ", - "open_in_immich_body": "Đ—Đ°Đ´Đ°ĐšŅ†Đĩ Immich ŅĐē ĐŗĐ°ĐģĐĩŅ€ŅŅŽ ĐŊа Android, Đēай адĐēŅ€Ņ‹Đ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ° ĐŊĐĩĐŋĐ°ŅŅ€ŅĐ´ĐŊа С Ņ–ĐŊŅˆŅ‹Ņ… ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ.", - "open_in_immich_title": "АдĐēŅ€Ņ‹Đ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ° Ņž Immich", - "open_in_map_view": "АдĐēŅ€Ņ‹Ņ†ŅŒ ĐŊа ĐēĐ°Ņ€Ņ†Đĩ", - "open_in_openstreetmap": "АдĐēŅ€Ņ‹Ņ†ŅŒ ҃ OpenStreetMap", - "open_the_search_filters": "АдĐēŅ€Ņ‹Ņ†ŅŒ ҄ҖĐģŅŒŅ‚Ņ€Ņ‹ ĐŋĐžŅˆŅƒĐē҃", - "options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ", "or": "айО", - "organize_into_albums": "ĐŖĐŋĐ°Ņ€Đ°Đ´ĐēĐ°Đ˛Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧŅ‹", - "organize_into_albums_description": "РаСĐŧŅŅŅ†Ņ–Ņ†ŅŒ ҖҁĐŊŅƒŅŽŅ‡Ņ‹Ņ Ņ„ĐžŅ‚Đ° Ņž аĐģŅŒĐąĐžĐŧĐ°Ņ… ĐˇĐŗĐžĐ´ĐŊа С Ņ†ŅĐŋĐĩŅ€Đ°ŅˆĐŊŅ–ĐŧŅ– ĐŊаĐģадаĐŧŅ– ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ–", - "organize_your_library": "ĐŖĐŋĐ°Ņ€Đ°Đ´ĐēŅƒĐšŅ†Đĩ ŅĐ˛Đ°ŅŽ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐē҃", - "orientation": "ĐŅ€Ņ‹ĐĩĐŊŅ‚Đ°Ņ†Ņ‹Ņ", - "original": "Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ", - "other": "ІĐŊŅˆĐ°Đĩ", - "other_devices": "ІĐŊŅˆŅ‹Ņ ĐŋҀҋĐģĐ°Đ´Ņ‹", - "other_variables": "ІĐŊŅˆŅ‹Ņ ĐŋĐĩŅ€Đ°ĐŧĐĩĐŊĐŊŅ‹Ņ", - "owned": "ĐŖĐģĐ°ŅĐŊŅ‹Ņ", - "owner": "ĐŖĐģадаĐģҌĐŊŅ–Đē", - "page": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐēа", - "partner": "ĐŸĐ°Ņ€Ņ‚ĐŊґҀ", - "partner_can_access": "{partner} ĐŧаĐĩ Đ´ĐžŅŅ‚ŅƒĐŋ да", - "partner_can_access_assets": "ĐŖŅĐĩ Đ˛Đ°ŅˆŅ‹ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°, аĐēŅ€Đ°ĐŧŅ Đ°Ņ€Ņ…Ņ–Đ˛Đ°Đ˛Đ°ĐŊҋ҅ Ņ– Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊҋ҅", - "partner_can_access_location": "МĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ, дСĐĩ ĐąŅ‹ĐģŅ– ĐˇŅ€ĐžĐąĐģĐĩĐŊŅ‹Ņ Đ˛Đ°ŅˆŅ‹ Ņ„ĐžŅ‚Đ°", "partner_list_user_photos": "Đ¤ĐžŅ‚Đ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа {user}", - "partner_page_empty_message": "Đ’Đ°ŅˆŅ‹ Ņ„ĐžŅ‚Đ° ŅŅˆŅ‡Ņ ĐŊĐĩ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ ĐŊŅ– С адĐŊŅ‹Đŧ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°Đŧ.", - "partner_page_no_more_users": "БоĐģҌ҈ ĐŊŅĐŧа ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž Đ´ĐģŅ дадаваĐŊĐŊŅ", - "partner_page_select_partner": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", - "partner_page_stop_sharing_content": "{partner} йОĐģҌ҈ ĐŊĐĩ СĐŧĐžĐļа ĐŧĐĩŅ†ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ да Đ˛Đ°ŅˆŅ‹Ņ… Ņ„ĐžŅ‚Đ°.", - "partner_sharing": "ĐĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ С ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°Đŧ", - "partners": "ĐŸĐ°Ņ€Ņ‚ĐŊґҀҋ", - "password": "ĐŸĐ°Ņ€ĐžĐģҌ", - "password_does_not_match": "ĐŸĐ°Ņ€ĐžĐģŅ– ĐŊĐĩ ҁ҃ĐŋĐ°Đ´Đ°ŅŽŅ†ŅŒ", - "password_required": "ĐŸĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩŅ†Ņ†Đ° ĐŋĐ°Ņ€ĐžĐģҌ", - "password_reset_success": "ĐŸĐ°Ņ€ĐžĐģҌ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ҁĐēŅ–ĐŊŅƒŅ‚Ņ‹", - "past_durations": { - "days": "АĐŋĐžŅˆĐŊŅ–Ņ {days, plural, one {# дСĐĩĐŊҌ} few {# Đ´ĐŊŅ–} many {# Đ´ĐˇŅ‘ĐŊ} other {# Đ´ĐˇŅ‘ĐŊ}}", - "hours": "АĐŋĐžŅˆĐŊŅ–Ņ {hours, plural, one {# ĐŗĐ°Đ´ĐˇŅ–ĐŊа} few {# ĐŗĐ°Đ´ĐˇŅ–ĐŊŅ‹} many {# ĐŗĐ°Đ´ĐˇŅ–ĐŊ} other {# ĐŗĐ°Đ´ĐˇŅ–ĐŊ}}", - "years": "АĐŋĐžŅˆĐŊŅ–Ņ {years, plural, one {# ĐŗĐžĐ´} few {# ĐŗĐ°Đ´Ņ‹} many {# ĐŗĐ°Đ´ĐžŅž} other {# ĐŗĐ°Đ´ĐžŅž}}" - }, - "path": "ШĐģŅŅ…", - "pattern": "ĐŖĐˇĐžŅ€", "pause": "ĐŸŅ€Ņ‹ĐŋŅ‹ĐŊŅ–Ņ†ŅŒ", - "pause_memories": "ĐŸŅ€Ņ‹ĐŋŅ‹ĐŊŅ–Ņ†ŅŒ ҃ҁĐŋаĐŧŅ–ĐŊŅ‹", - "paused": "ĐŸŅ€Ņ‹ĐŋŅ‹ĐŊĐĩĐŊа", - "pending": "ЧаĐēаĐĩ", "people": "Đ›ŅŽĐ´ĐˇŅ–", - "people_edits_count": "{count, plural, one {ĐĐ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊŅ‹ # Ņ‡Đ°ĐģавĐĩĐē} few {ĐĐ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊа # Ņ‡Đ°ĐģавĐĩĐēŅ–} many {ĐĐ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊа # Ņ‡Đ°ĐģавĐĩĐē} other {ĐĐ´Ņ€ŅĐ´Đ°ĐŗĐ°Đ˛Đ°ĐŊа # Ņ‡Đ°ĐģавĐĩĐēа}}", - "people_feature_description": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°, ĐˇĐŗŅ€ŅƒĐŋаваĐŊҋ҅ Đŋа ĐģŅŽĐ´ĐˇŅŅ…", "permanent_deletion_warning": "ПаĐŋŅŅ€ŅĐ´ĐļаĐŊĐŊĐĩ ай ĐēаĐŊŅ‡Đ°Ņ‚ĐēĐžĐ˛Ņ‹Đŧ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ–", "permanent_deletion_warning_setting_description": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŋаĐŋŅŅ€ŅĐ´ĐļаĐŊĐŊĐĩ ĐŋҀҋ ĐēаĐŊŅ‡Đ°Ņ‚ĐēĐžĐ˛Ņ‹Đŧ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ– Ņ€ŅŅŅƒŅ€ŅĐ°Ņž", - "permanently_delete": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹", - "permanently_delete_assets_count": "ĐĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {аб’ĐĩĐēŅ‚} few {аб’ĐĩĐē҂ҋ} many {аб’ĐĩĐē҂ҋ} other {аб’ĐĩĐēŅ‚Đ°}}", - "permanently_delete_assets_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐēаĐŊŅ‡Đ°Ņ‚ĐēОва Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {ĐŗŅŅ‚Ņ‹ ай'ĐĩĐēŅ‚?} few {ĐŗŅŅ‚Ņ‹Ņ # ай'ĐĩĐē҂ҋ?} many {ĐŗŅŅ‚Ņ‹Ņ… # ай'ĐĩĐēŅ‚Đ°Ņž?} other {ĐŗŅŅ‚Ņ‹Ņ… # ай'ĐĩĐēŅ‚Đ°Ņž?}} Đ“ŅŅ‚Đ° Ņ‚Đ°ĐēŅĐ°Đŧа Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {ŅĐŗĐž} other {Ņ–Ņ…}} С аĐģŅŒĐąĐžĐŧа(Ņž).", - "permanently_deleted_asset": "Аб’ĐĩĐēŅ‚ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹", - "permanently_deleted_assets_count": "{count, plural, one {# аб’ĐĩĐēŅ‚ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹} few {# аб’ĐĩĐē҂ҋ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹} many {# аб’ĐĩĐēŅ‚Đ°Ņž Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа} other {# аб’ĐĩĐēŅ‚Đ° Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа }} ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹", - "permission": "ДазвоĐģ", - "permission_empty": "ДазвоĐģ ĐŊĐĩ ĐŧĐžĐļа ĐąŅ‹Ņ†ŅŒ ĐŋŅƒŅŅ‚Ņ‹Đŧ", - "person": "ЧаĐģавĐĩĐē", - "person_age_months": "ĐŖĐˇŅ€ĐžŅŅ‚ {months, plural, one {# ĐŧĐĩŅŅŅ†} few {# ĐŧĐĩŅŅŅ†Ņ‹} many {# ĐŧĐĩŅŅŅ†Đ°Ņž} other {# ĐŧĐĩŅŅŅ†Đ°}}", - "person_age_year_months": "ŅƒĐˇŅ€ĐžŅŅ‚ 1 ĐŗĐžĐ´, {months, plural, one {# ĐŧĐĩŅŅŅ†} few {# ĐŧĐĩŅŅŅ†Ņ‹} many {# ĐŧĐĩŅŅŅ†Đ°Ņž} other {# ĐŧĐĩŅŅŅ†Đ°}}", - "person_age_years": "ĐŖĐˇŅ€ĐžŅŅ‚ {years, plural, one {# ĐŗĐžĐ´} few {# ĐŗĐ°Đ´Ņ‹} many {# ĐŗĐ°Đ´ĐžŅž} other {# ĐŗĐžĐ´Đ°}}", - "person_birthdate": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ: {date}", - "person_hidden": "{name}{hidden, select, true { (ŅŅ…Đ°Đ˛Đ°ĐŊŅ‹)} other {}}", - "person_recognized": "ЧаĐģавĐĩĐē Ņ€Đ°ŅĐŋаСĐŊаĐŊŅ‹", - "photo_shared_all_users": "ПадобĐŊа, ŅˆŅ‚Đž Đ˛Ņ‹ ŅžĐļĐž Đ°ĐąĐ°ĐŗŅƒĐģŅ–ĐģŅ– ŅĐ˛Đ°Đĩ Ņ„ĐžŅ‚Đ° С ŅƒŅŅ–ĐŧŅ– ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧŅ– айО Ņž Đ˛Đ°Ņ ĐŊŅĐŧа ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž, С ŅĐēŅ–ĐŧŅ– ĐŧĐžĐļĐŊа Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°Ņ†ŅŒ.", + "permission_onboarding_back": "Назад", + "permission_onboarding_continue_anyway": "ĐŖŅŅ‘ адĐŊĐž ĐŋŅ€Đ°Ņ†ŅĐŗĐŊŅƒŅ†ŅŒ", "photos": "Đ¤ĐžŅ‚Đ°", "photos_and_videos": "Đ¤ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "photos_from_previous_years": "Đ¤ĐžŅ‚Đ° С ĐŧŅ–ĐŊ҃Đģҋ҅ ĐŗĐ°Đ´ĐžŅž", - "pick_a_location": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", - "pick_custom_range": "ĐŖĐģĐ°ŅĐŊŅ‹ Đ´Ņ‹ŅĐŋаСОĐŊ", - "pick_date_range": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Đ´Ņ‹ŅĐŋаСОĐŊ Đ´Đ°Ņ‚", - "pin_code_changed_successfully": "PIN-ĐēОд ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° СĐŧĐĩĐŊĐĩĐŊŅ‹", - "pin_code_reset_successfully": "PIN-ĐēОд ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ҁĐēŅ–ĐŊŅƒŅ‚Ņ‹", - "pin_code_setup_successfully": "PIN-ĐēОд ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ĐŊаĐģадĐļаĐŊŅ‹", - "pin_verification": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Đēа PIN-ĐēОда", "place": "МĐĩŅŅ†Đ°", "places": "МĐĩҁ҆ҋ", - "places_count": "{count, plural, one {{count, number} ĐŧĐĩŅŅ†Đ°} few {{count, number} ĐŧĐĩҁ҆ҋ} many {{count, number} ĐŧĐĩŅŅ†Đ°Ņž} other {{count, number} ĐŧĐĩŅŅ†Đ°}}", - "play": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ", - "play_memories": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ ҃ҁĐŋаĐŧŅ–ĐŊŅ‹", - "play_motion_photo": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Ņ„ĐžŅ‚Đ°", - "play_or_pause_video": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ айО ĐŋҀҋĐŋŅ‹ĐŊŅ–Ņ†ŅŒ Đ˛Ņ–Đ´ŅĐ°", - "play_original_video": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "play_original_video_setting_description": "ĐĐ´Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Đ˛Đ°ĐŗŅƒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅŽ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊҋ҅ Đ˛Ņ–Đ´ŅĐ° СаĐŧĐĩҁ҂ ĐŋĐĩŅ€Đ°ĐēадаваĐŊҋ҅. КаĐģŅ– Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊŅ‹ аб’ĐĩĐēŅ‚ ĐŊĐĩҁ҃ĐŧŅŅˆŅ‡Đ°ĐģҌĐŊŅ‹, Ņ‘ĐŊ ĐŧĐžĐļа ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°Ņ†Ņ†Đ° ĐŊĐĩĐēĐ°Ņ€ŅĐēŅ‚ĐŊа.", - "play_transcoded_video": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°ĐēадаваĐŊаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "please_auth_to_access": "ĐŅžŅ‚ŅĐŊ҂ҋ҄ҖĐēŅƒĐšŅ†ĐĩŅŅ Đ´ĐģŅ Đ´ĐžŅŅ‚ŅƒĐŋ҃", - "plugin_method_filter_type": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€", "port": "ĐŸĐžŅ€Ņ‚", - "preferences_settings_subtitle": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋĐ°Ņ€Đ°ĐŧĐĩŅ‚Ņ€Đ°ĐŧŅ– ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "preferences_settings_title": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ", - "preparing": "ĐŸĐ°Đ´Ņ€Ņ‹Ņ…Ņ‚ĐžŅžĐēа", - "preset": "ĐŸŅ€ŅŅĐĩŅ‚", - "preview": "ПĐĩŅ€Đ°Đ´ĐŋŅ€Đ°ĐŗĐģŅĐ´", "previous": "ПаĐŋŅŅ€ŅĐ´ĐŊŅĐĩ", - "previous_memory": "ПаĐŋŅŅ€ŅĐ´ĐŊŅ– ŅžŅĐŋаĐŧŅ–ĐŊ", - "previous_or_next_day": "ДзĐĩĐŊҌ ĐŊаĐŋĐĩŅ€Đ°Đ´/ĐŊаСад", - "previous_or_next_month": "МĐĩŅŅŅ† ĐŊаĐŋĐĩŅ€Đ°Đ´/ĐŊаСад", - "previous_or_next_photo": "Đ¤ĐžŅ‚Đ° ĐŊаĐŋĐĩŅ€Đ°Đ´/ĐŊаСад", - "previous_or_next_year": "Год ĐŊаĐŋĐĩŅ€Đ°Đ´/ĐŊаСад", - "primary": "ĐŅĐŊĐžŅžĐŊŅ‹", - "privacy": "ĐŸŅ€Ņ‹Đ˛Đ°Ņ‚ĐŊĐ°ŅŅ†ŅŒ", "profile": "ĐŸŅ€ĐžŅ„Ņ–ĐģҌ", "profile_drawer_app_logs": "Đ–ŅƒŅ€ĐŊаĐģŅ‹", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "ĐŖĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ. Đ”ĐžŅžĐŗĐ° ĐŊĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ ĐŊа СĐŊĐ°Ņ‡ĐžĐē Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа, Đēай Đ˛Ņ‹ĐšŅŅ†Ņ–.", - "profile_image_of_user": "ĐĐ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа {user}", - "profile_picture_set": "ĐĐ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ СададСĐĩĐŊŅ‹.", - "projection_type": "ĐĸŅ‹Đŋ ĐŋŅ€Đ°ĐĩĐē҆ҋҖ", - "public_album": "ĐŸŅƒĐąĐģҖ҇ĐŊŅ‹ аĐģŅŒĐąĐžĐŧ", - "public_share": "ĐŸŅƒĐąĐģҖ҇ĐŊаĐĩ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ", - "purchase_account_info": "ĐŸĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧаĐģҌĐŊŅ–Đē", - "purchase_activated_subtitle": "Đ”ĐˇŅĐē҃ĐĩĐŧ Са ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐē҃ Immich Ņ– ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧĐŊĐ°ĐŗĐ° СайĐĩҁĐŋŅŅ‡ŅĐŊĐŊŅ С адĐēҀҋ҂ҋĐŧ ĐˇŅ‹Ņ…ĐžĐ´ĐŊŅ‹Đŧ ĐēОдаĐŧ", - "purchase_activated_time": "АĐēŅ‚Ņ‹Đ˛Đ°Đ˛Đ°ĐŊа {date}", - "purchase_activated_title": "Đ’Đ°Ņˆ ĐēĐģŅŽŅ‡ ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° аĐēŅ‚Ņ‹Đ˛Đ°Đ˛Đ°ĐŊŅ‹", - "purchase_button_activate": "АĐēŅ‚Ņ‹Đ˛Đ°Đ˛Đ°Ņ†ŅŒ", "purchase_button_buy": "ĐšŅƒĐŋŅ–Ņ†ŅŒ", "purchase_button_buy_immich": "ĐšŅƒĐŋŅ–Ņ†ŅŒ Immich", - "purchase_button_never_show_again": "БоĐģҌ҈ ĐŊĐĩ ĐŋаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ", - "purchase_button_reminder": "ĐĐ°ĐŗĐ°Đ´Đ°Ņ†ŅŒ ĐŋŅ€Đ°Đˇ 30 Đ´ĐˇŅ‘ĐŊ", - "purchase_button_remove_key": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡", "purchase_button_select": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ", - "purchase_failed_activation": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ аĐēŅ‚Ņ‹Đ˛Đ°Đ˛Đ°Ņ†ŅŒ! ĐŸŅ€Đ°Đ˛ĐĩҀ҆Đĩ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅˆŅ‚Ņƒ ĐŊа ĐŊĐ°ŅŅžĐŊĐ°ŅŅ†ŅŒ ĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊĐ°ĐŗĐ° ĐēĐģŅŽŅ‡Đ° ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ!", - "purchase_individual_description_1": "ДĐģŅ Đ°ŅĐžĐąĐŊĐ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "purchase_individual_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧаĐģҌĐŊŅ–Đēа", - "purchase_individual_title": "ĐŅĐžĐąĐŊŅ‹", - "purchase_input_suggestion": "МаĐĩ҆Đĩ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ? ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐēĐģŅŽŅ‡ ĐŊŅ–ĐļŅĐš", - "purchase_lifetime_description": "ПаĐļŅ‹Ņ†Ņ†Ņ‘Đ˛Đ°Ņ ĐŋаĐē҃ĐŋĐēа", - "purchase_option_title": "ВАРĐĢĐ¯ĐĐĸĐĢ ĐŸĐĐšĐŖĐŸĐšĐ†", - "purchase_panel_info_1": "Đ Đ°ŅĐŋŅ€Đ°Ņ†ĐžŅžĐēа Immich ĐŋĐ°Ņ‚Ņ€Đ°ĐąŅƒĐĩ ҈ĐŧĐ°Ņ‚ Ņ‡Đ°ŅŅƒ Ņ– ĐŊаĐŧĐ°ĐŗĐ°ĐŊĐŊŅŅž, Ņ– Ņž ĐŊĐ°Ņ ĐŋŅ€Đ°Ņ†ŅƒŅŽŅ†ŅŒ Ņ–ĐŊĐļŅ‹ĐŊĐĩҀҋ ĐŊа ĐŋĐžŅžĐŊŅƒŅŽ ŅŅ‚Đ°ŅžĐē҃, Đēай ĐˇŅ€Đ°ĐąŅ–Ņ†ŅŒ ŅĐŗĐž ĐŊĐ°ŅŅ‚ĐžĐģҌĐēŅ– Đ´ĐžĐąŅ€Ņ‹Đŧ, ĐŊаĐēĐžĐģҌĐēŅ– ĐŗŅŅ‚Đ° ĐŧĐ°ĐŗŅ‡Ņ‹Đŧа. ĐĐ°ŅˆĐ° ĐŧŅ–ŅŅ–Ņ — Đēай ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧĐŊаĐĩ СайĐĩҁĐŋŅŅ‡ŅĐŊĐŊĐĩ С адĐēҀҋ҂ҋĐŧ ĐˇŅ‹Ņ…ĐžĐ´ĐŊŅ‹Đŧ ĐēОдаĐŧ Ņ– ŅŅ‚Ņ‹Ņ‡ĐŊŅ‹Ņ ĐąŅ–ĐˇĐŊĐĩҁ-ĐŋŅ€Đ°Đē҂ҋĐēŅ– ŅŅ‚Đ°ĐģŅ– ŅžŅŅ‚ĐžĐšĐģŅ–Đ˛Đ°Đš ĐēҀҋĐŊŅ–Ņ†Đ°Đš Đ´Đ°Ņ…ĐžĐ´Ņƒ Đ´ĐģŅ Ņ€Đ°ŅĐŋŅ€Đ°Ņ†ĐžŅžŅˆŅ‡Ņ‹ĐēĐ°Ņž Ņ– ŅŅ‚Đ˛Đ°Ņ€Đ°Ņ†ŅŒ ŅĐēĐ°ŅŅ–ŅŅ‚ŅĐŧ҃, ŅĐēĐ°Ņ ĐŋаваĐļаĐĩ ĐŋŅ€Ņ‹Đ˛Đ°Ņ‚ĐŊĐ°ŅŅ†ŅŒ, С Ņ€ŅĐ°ĐģҌĐŊŅ‹ĐŧŅ– аĐģŅŒŅ‚ŅŅ€ĐŊĐ°Ņ‚Ņ‹Đ˛Đ°ĐŧŅ– ŅĐēҁĐŋĐģŅƒĐ°Ņ‚Đ°Ņ‚Đ°Ņ€ŅĐēŅ–Đŧ вОйĐģĐ°Ņ‡ĐŊŅ‹Đŧ ŅŅŅ€Đ˛Ņ–ŅĐ°Đŧ.", - "purchase_panel_info_2": "ПаĐēĐžĐģҌĐēŅ– ĐŧŅ‹ ĐŋҀҋ҂ҀҋĐŧĐģŅ–Đ˛Đ°ĐĩĐŧŅŅ ĐŋҀҋĐŊ҆ҋĐŋ҃ ĐŊĐĩ Đ´Đ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐģĐ°Ņ‚ĐŊҋ҅ Ņ„ŅƒĐŊĐēŅ†Ņ‹Đš, ĐŗŅŅ‚Đ°Ņ ĐŋаĐē҃ĐŋĐēа ĐŊĐĩ Đ´Đ°ŅŅ†ŅŒ ваĐŧ ĐŊŅ–ŅĐēŅ–Ņ… Đ´Đ°Đ´Đ°Ņ‚ĐēĐžĐ˛Ņ‹Ņ… ĐŧĐ°ĐŗŅ‡Ņ‹ĐŧĐ°ŅŅ†ĐĩĐš ҃ Immich. ĐœŅ‹ Ņ€Đ°ĐˇĐģŅ–Ņ‡Đ˛Đ°ĐĩĐŧ ĐŊа ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐē҃ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž, Ņ‚Đ°ĐēŅ–Ņ… ŅĐē Đ˛Ņ‹, ҃ ĐąĐĩҁĐŋĐĩŅ€Đ°ĐŋŅ‹ĐŊĐŊаК Ņ€Đ°ŅĐŋŅ€Đ°Ņ†ĐžŅžŅ†Ņ‹ Immich.", - "purchase_panel_title": "ĐŸĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐēа ĐŋŅ€Đ°ĐĩĐēŅ‚Đ°", - "purchase_per_server": "За ҁĐĩŅ€Đ˛ĐĩŅ€", - "purchase_per_user": "За ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "purchase_remove_product_key": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ", - "purchase_remove_product_key_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ?", - "purchase_remove_server_product_key": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҁĐĩŅ€Đ˛ĐĩŅ€ĐŊŅ‹ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ", - "purchase_remove_server_product_key_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҁĐĩŅ€Đ˛ĐĩŅ€ĐŊŅ‹ ĐēĐģŅŽŅ‡ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ?", - "purchase_server_description_1": "ДĐģŅ ŅžŅŅĐŗĐž ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "purchase_server_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧаĐģҌĐŊŅ–Đēа", - "purchase_server_title": "ĐĄĐĩŅ€Đ˛ĐĩŅ€", - "purchase_settings_server_activated": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ĐŊŅ‹Đŧ ĐēĐģŅŽŅ‡ĐžĐŧ ĐŋŅ€Đ°Đ´ŅƒĐēŅ‚Ņƒ ĐēŅ–Ņ€ŅƒĐĩ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€", - "query_asset_id": "ID СаĐŋŅ‹Ņ‚Ņƒ аб’ĐĩĐēŅ‚Đ°", - "queue_status": "ĐŖ Ņ‡Đ°Ņ€ĐˇĐĩ {count}/{total}", - "rate_asset": "ĐŅ†Đ°ĐŊŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚", - "rating": "ĐŅ†ŅĐŊĐēа ĐˇĐžŅ€ĐēаĐŧŅ–", - "rating_clear": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ Đ°Ņ†ŅĐŊĐē҃", - "rating_count": "{count, plural, =0 {БĐĩС Đ°Ņ†ŅĐŊĐēŅ–} one {# ĐˇĐžŅ€Đēа} few {# ĐˇĐžŅ€ĐēŅ–} many {# ĐˇĐžŅ€Đ°Đē} other {# ĐˇĐžŅ€Đ°Đē}}", - "rating_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Đ°Ņ†ŅĐŊĐē҃ EXIF ҃ ĐŋаĐŊŅĐģŅ– СвĐĩŅŅ‚Đ°Đē", - "reaction_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Ņ€ŅĐ°Đē҆ҋҖ", - "read_changelog": "ĐŸŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°Ņ†ŅŒ ҁĐŋҖҁ СĐŧŅĐŊĐĩĐŊĐŊŅŅž", "readonly_mode_disabled": "Đ’Ņ‹ĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ Ņ‡Ņ‹Ņ‚Đ°ĐŊĐŊŅ", "readonly_mode_enabled": "ĐŖĐēĐģŅŽŅ‡Đ°ĐŊŅ‹ Ņ€ŅĐļŅ‹Đŧ Ņ‚ĐžĐģҌĐēŅ– Đ´ĐģŅ Ņ‡Ņ‹Ņ‚Đ°ĐŊĐŊŅ", - "ready_for_upload": "Đ“Đ°Ņ‚ĐžĐ˛Đ° да СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ", "reassign": "ПĐĩŅ€Đ°ĐŋŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ", - "reassigned_assets_to_existing_person": "{count, plural, one {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊа # аб’ĐĩĐēŅ‚Đ°}} {name, select, null {ҖҁĐŊŅƒŅŽŅ‡Đ°Đŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊŅ‹ # аб’ĐĩĐēŅ‚} few {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊŅ‹ # аб’ĐĩĐē҂ҋ} many {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊа # аб’ĐĩĐēŅ‚Đ°Ņž} other {ПĐĩŅ€Đ°ĐŊаСĐŊĐ°Ņ‡Đ°ĐŊа # аб’ĐĩĐēŅ‚Đ°}} ĐŊОваĐŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃", - "reassing_hint": "ĐŸŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ ҖҁĐŊŅƒŅŽŅ‡Đ°Đŧ҃ Ņ‡Đ°ĐģавĐĩĐē҃", + "reassing_hint": "ĐŸŅ€Ņ‹ĐŋŅ–ŅĐ°Ņ†ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ аĐēŅ‚Ņ‹Đ˛Ņ‹ ҖҁĐŊŅƒŅŽŅ‡Đ°Đš Đ°ŅĐžĐąĐĩ", "recent": "ĐŅĐ´Đ°ŅžĐŊŅ–", + "recent_albums": "ĐŅĐ´Đ°ŅžĐŊŅ–Ņ аĐģŅŒĐąĐžĐŧŅ‹", "recent_searches": "ĐŅĐ´Đ°ŅžĐŊŅ–Ņ ĐŋĐžŅˆŅƒĐēŅ–", "recently_added": "ĐŅĐ´Đ°ŅžĐŊа дададСĐĩĐŊа", - "recently_added_description": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ аб’ĐĩĐēŅ‚Đ°Ņž, Đ°Đ´ŅĐ°Ņ€Ņ‚Đ°Đ˛Đ°ĐŊŅ‹ Đŋа Đ´Đ°Ņ†Đĩ Ņ–Ņ… СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ Ņž Immich", - "recently_added_page_title": "ĐŅĐ´Đ°ŅžĐŊа дададСĐĩĐŊŅ‹Ņ", - "recently_added_title": "ĐŅĐ´Đ°ŅžĐŊа дададСĐĩĐŊŅ‹Ņ", - "recently_taken": "ĐŅĐ´Đ°ŅžĐŊа ĐˇŅ€ĐžĐąĐģĐĩĐŊŅ‹Ņ", - "refresh": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ", - "refresh_encoded_videos": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ СаĐēадаваĐŊŅ‹Ņ Đ˛Ņ–Đ´ŅĐ°", "refresh_faces": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€Ņ‹", - "refresh_metadata": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ", - "refresh_thumbnails": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹", - "refreshed": "АйĐŊĐžŅžĐģĐĩĐŊа", - "refreshing_encoded_video": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ СаĐēадаваĐŊĐ°ĐŗĐ° Đ˛Ņ–Đ´ŅĐ°", - "refreshing_faces": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ Ņ‚Đ˛Đ°Ņ€Đ°Ņž", - "refreshing_metadata": "АйĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊҋ҅", - "regenerating_thumbnails": "ПĐĩŅ€Đ°ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊĐĩ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€", - "remote": "АддаĐģĐĩĐŊа", - "remote_assets": "АддаĐģĐĩĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ", - "remote_media_summary": "ЗводĐēа аддаĐģĐĩĐŊҋ҅ ĐŧĐĩĐ´Ņ‹Ņ", "remove": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ", - "remove_assets_album_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {# аб’ĐĩĐēŅ‚} few {# аб’ĐĩĐē҂ҋ} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}} С аĐģŅŒĐąĐžĐŧа?", - "remove_assets_shared_link_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ {count, plural, one {# аб’ĐĩĐēŅ‚} few {# аб’ĐĩĐē҂ҋ} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}} С ĐŗŅŅ‚Đ°Đš Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–?", - "remove_assets_title": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐē҂ҋ?", - "remove_custom_date_range": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҃ĐģĐ°ŅĐŊŅ‹ Đ´Ņ‹ŅĐŋаСОĐŊ Đ´Đ°Ņ‚", - "remove_filter": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҄ҖĐģŅŒŅ‚Ņ€", "remove_from_album": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С аĐģŅŒĐąĐžĐŧа", - "remove_from_album_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа С аĐģŅŒĐąĐžĐŧа", "remove_from_favorites": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С Đ°ĐąŅ€Đ°ĐŊҋ҅", - "remove_from_lock_folder_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа С СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–", - "remove_from_locked_folder": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–", - "remove_from_locked_folder_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ ĐŋĐĩŅ€Đ°ĐŧŅŅŅ†Ņ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹Ņ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° С СайĐģаĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊаК ĐŋаĐŋĐēŅ–? Đ¯ĐŊŅ‹ ŅŅ‚Đ°ĐŊŅƒŅ†ŅŒ ĐąĐ°Ņ‡ĐŊŅ‹ĐŧŅ– Ņž ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅŅ†Ņ‹.", - "remove_from_shared_link": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "remove_memory": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҃ҁĐŋаĐŧŅ–ĐŊ", - "remove_photo_from_memory": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ„ĐžŅ‚Đ° С ĐŗŅŅ‚Đ°ĐŗĐ° ŅžŅĐŋаĐŧŅ–ĐŊ҃", "remove_tag": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ Ņ‚ŅĐŗ", + "remove_url": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ URL-Đ°Đ´Ņ€Đ°Ņ", "remove_user": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "removed_api_key": "Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ ĐēĐģŅŽŅ‡ API: {name}", - "removed_from_archive": "Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊа С Đ°Ņ€Ņ…Ņ–Đ˛Đ°", - "removed_from_favorites": "Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊа С Đ°ĐąŅ€Đ°ĐŊҋ҅", - "removed_from_favorites_count": "{count, plural, other {Đ’Ņ‹Đ´Đ°ĐģĐĩĐŊа #}} С Đ°ĐąŅ€Đ°ĐŊҋ҅", - "removed_memory": "ĐŖŅĐŋаĐŧŅ–ĐŊ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", - "removed_tagged_assets": "ĐĸŅĐŗ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ С {count, plural, one {# аб’ĐĩĐēŅ‚Đ°} few {# аб’ĐĩĐēŅ‚Đ°Ņž} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}}", "rename": "ПĐĩŅ€Đ°ĐšĐŧĐĩĐŊĐ°Đ˛Đ°Ņ†ŅŒ", "repository": "Đ ŅĐŋĐ°ĐˇŅ–Ņ‚ĐžŅ€Ņ‹Đš", - "require_password": "ĐŸĐ°Ņ‚Ņ€Đ°ĐąĐ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "rescan": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°ŅžŅ‚ĐžŅ€ĐŊа", "reset": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ", "reset_password": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "reset_people_visibility": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ ĐąĐ°Ņ‡ĐŊĐ°ŅŅ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "reset_pin_code": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ PIN-ĐēОд", - "reset_pin_code_description": "КаĐģŅ– Đ˛Ņ‹ ĐˇĐ°ĐąŅ‹ĐģŅ–ŅŅ ŅĐ˛ĐžĐš PIN-ĐēОд, Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ĐˇĐ˛ŅĐˇĐ°Ņ†Ņ†Đ° С адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ°Ņ€Đ°Đŧ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, Đēай ҁĐēŅ–ĐŊŅƒŅ†ŅŒ ŅĐŗĐž", - "reset_pin_code_with_password": "Đ’Ņ‹ ĐˇĐ°ŅžŅŅ‘Đ´Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ҁĐēŅ–ĐŊŅƒŅ†ŅŒ PIN-ĐēОд ĐŋҀҋ даĐŋаĐŧОСĐĩ ĐŋĐ°Ņ€ĐžĐģŅ", - "reset_sqlite": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ ĐąĐ°ĐˇŅƒ даĐŊҋ҅ SQLite", - "reset_sqlite_clear_app_data": "ĐŅ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ даĐŊŅ‹Ņ", - "reset_sqlite_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ даĐŊŅ‹Ņ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹? Đ‘ŅƒĐ´ŅƒŅ†ŅŒ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ ŅžŅĐĩ ĐŊаĐģĐ°Đ´Ņ‹, а Ņ‚Đ°ĐēŅĐ°Đŧа Đ°Đ´ĐąŅƒĐ´ĐˇĐĩŅ†Ņ†Đ° Đ˛Ņ‹Ņ…Đ°Đ´ С ҃ĐģŅ–ĐēĐžĐ˛Đ°ĐŗĐ° СаĐŋŅ–ŅŅƒ.", - "reset_sqlite_confirmation_note": "Đ—Đ°ŅžĐ˛Đ°ĐŗĐ°: ĐŋĐ°ŅĐģŅ Đ°Ņ‡Ņ‹ŅŅ‚ĐēŅ– Ņ‚Ņ€ŅĐąĐ° ĐąŅƒĐ´ĐˇĐĩ ĐŋĐĩŅ€Đ°ĐˇĐ°ĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ ĐŋŅ€Đ°ĐŗŅ€Đ°Đŧ҃.", - "reset_sqlite_done": "ДаĐŊŅ‹Ņ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹ Đ°Ņ‡Ņ‹ŅˆŅ‡Đ°ĐŊŅ‹Ņ. ПĐĩŅ€Đ°ĐˇĐ°ĐŋŅƒŅŅ†Ņ–Ņ†Đĩ Immich Ņ– ŅžĐ˛Đ°ĐšĐ´ĐˇŅ–Ņ†Đĩ СĐŊĐžŅž.", - "reset_sqlite_success": "База даĐŊҋ҅ SQLite ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° ҁĐēŅ–ĐŊŅƒŅ‚Đ°", - "reset_to_default": "ĐĄĐēŅ–ĐŊŅƒŅ†ŅŒ да ĐŋŅ€Đ°Đ´Đ˛Ņ‹ĐˇĐŊĐ°Ņ‡Đ°ĐŊҋ҅", "resolution": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†ŅŒ", - "resolve_duplicates": "Đ’Ņ‹Ņ€Đ°ŅˆŅ‹Ņ†ŅŒ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", - "resolved_all_duplicates": "ĐŖŅĐĩ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹ Đ˛Ņ‹Ņ€Đ°ŅˆĐ°ĐŊŅ‹", "restore": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ", "restore_all": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ŅƒŅŅ‘", "restore_user": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "restored_asset": "Аб’ĐĩĐēŅ‚ адĐŊĐžŅžĐģĐĩĐŊŅ‹", "resume": "ĐŖĐˇĐŊĐ°Đ˛Ņ–Ņ†ŅŒ", - "resume_paused_jobs": "ĐŖĐˇĐŊĐ°Đ˛Ņ–Ņ†ŅŒ {count, plural, one {# ĐŋҀҋĐŋŅ‹ĐŊĐĩĐŊаĐĩ СадаĐŊĐŊĐĩ} few {# ĐŋҀҋĐŋŅ‹ĐŊĐĩĐŊŅ‹Ņ СадаĐŊĐŊŅ–} many {# ĐŋҀҋĐŋŅ‹ĐŊĐĩĐŊҋ҅ СадаĐŊĐŊŅŅž} other {# ĐŋҀҋĐŋŅ‹ĐŊĐĩĐŊĐ°ĐŗĐ° СадаĐŊĐŊŅ}}", - "review_duplicates": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", - "review_large_files": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ Đ˛ŅĐģŅ–ĐēŅ–Ņ Ņ„Đ°ĐšĐģŅ‹", "role": "Đ ĐžĐģŅ", "role_editor": "Đ ŅĐ´Đ°ĐēŅ‚Đ°Ņ€", "role_viewer": "ГĐģŅĐ´Đ°Ņ‡", - "running": "Đ’Ņ‹ĐēĐžĐŊваĐĩŅ†Ņ†Đ°", "save": "Đ—Đ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ", - "saved": "Đ—Đ°Ņ…Đ°Đ˛Đ°ĐŊа", - "saved_api_key": "Đ—Đ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹Ņ ĐēĐģŅŽŅ‡Ņ‹ API", - "saved_profile": "Đ—Đ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹ ĐŋŅ€ĐžŅ„Ņ–ĐģҌ", - "saved_settings": "Đ—Đ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹Ņ ĐŊаĐģĐ°Đ´Ņ‹", - "say_something": "ĐĄĐēаĐļҋ҆Đĩ ŅˆŅ‚Đž-ĐŊĐĩĐąŅƒĐ´ĐˇŅŒ", - "scaffold_body_error_occurred": "ĐĐ´ĐąŅ‹ĐģĐ°ŅŅ ĐŋаĐŧŅ‹ĐģĐēа", - "scaffold_body_error_unrecoverable": "ĐĐ´ĐąŅ‹ĐģĐ°ŅŅ ĐŊĐĩĐŋаĐŋŅ€Đ°ŅžĐŊĐ°Ņ ĐŋаĐŧŅ‹ĐģĐēа. ĐŸĐ°Đ´ĐˇŅĐģҖ҆ĐĩŅŅ ĐŋаĐŧŅ‹ĐģĐēаК Ņ– Ņ‚Ņ€Đ°ŅŅ–Ņ€ĐžŅžĐēаК ŅŅ‚ŅĐēа Ņž Discord айО GitHub, Đēай ĐŧŅ‹ ĐŧĐ°ĐŗĐģŅ– даĐŋаĐŧĐ°ĐŗŅ‡Ņ‹. КаĐģŅ– ĐąŅƒĐ´ĐˇĐĩ ĐŋŅ€Đ°ĐŋаĐŊаваĐŊа, Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ Đ°Ņ‡Ņ‹ŅŅ†Ņ–Ņ†ŅŒ даĐŊŅ‹Ņ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹ ĐŊŅ–ĐļŅĐš.", - "scan": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ", - "scan_all_libraries": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ ҃ҁĐĩ ĐąŅ–ĐąĐģŅ–ŅŅ‚ŅĐēŅ–", - "scan_library": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ", - "scanning": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ", - "scanning_for_album": "ĐĄĐēаĐŊŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ аĐģŅŒĐąĐžĐŧа...", - "screencast_mode_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Ņ–ĐŊĐ´Ņ‹ĐēĐ°Ņ‚Đ°Ņ€Ņ‹ ĐŋадСĐĩĐš ĐēĐģĐ°Đ˛Ņ–ŅŅ‚ŅƒŅ€Ņ‹ Ņ– ĐŧŅ‹ŅˆŅ‹ ĐŊа ŅĐēŅ€Đ°ĐŊĐĩ", - "screencast_mode_title": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ€ŅĐļŅ‹Đŧ СаĐŋŅ–ŅŅƒ ŅĐēŅ€Đ°ĐŊа", - "search": "ĐŸĐžŅˆŅƒĐē", - "search_albums": "ĐŸĐžŅˆŅƒĐē аĐģŅŒĐąĐžĐŧĐ°Ņž", - "search_by_context": "ĐŸĐžŅˆŅƒĐē Đŋа ĐēаĐŊŅ‚ŅĐēҁ҆Đĩ", - "search_by_description": "ĐŸĐžŅˆŅƒĐē Đŋа аĐŋŅ–ŅĐ°ĐŊĐŊŅ–", - "search_by_description_example": "ДзĐĩĐŊҌ ĐŋĐ°Ņ…ĐžĐ´Ņƒ Ņž ХаĐŋĐĩ", - "search_by_filename": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŊаСвĐĩ Ņ„Đ°ĐšĐģа айО ĐŋĐ°ŅˆŅ‹Ņ€ŅĐŊĐŊŅ–", - "search_by_filename_example": "ĐŊаĐŋŅ€. IMG_1234.JPG айО PNG", - "search_by_full_path": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŋĐžŅžĐŊŅ‹Đŧ ҈ĐģŅŅ…Ņƒ айО ĐŋаĐŋ҆ҋ", - "search_by_full_path_example": "/Đ¯ĐŊ/ĐŸŅ€Đ°ĐĩĐē҂ҋ/3D_Đ”Ņ€ŅƒĐē/2026-07-01 — Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ҈҃ĐēĐ°Ņ†ŅŒ ĐŸŅ€Đ°ĐĩĐē҂ҋ, 3D, Đ”Ņ€ŅƒĐē, 2026 Ņ– Đŗ.Đ´.", - "search_by_ocr": "ĐŸĐžŅˆŅƒĐē Đŋа OCR", - "search_by_ocr_example": "Đ›Đ°Ņ‚Ņ", - "search_camera_lens_model": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŧĐ°Đ´ŅĐģŅ– аб’ĐĩĐēŅ‚Ņ‹Đ˛Đ°...", - "search_camera_make": "ĐŸĐžŅˆŅƒĐē Đŋа Đ˛Ņ‹Ņ‚Đ˛ĐžŅ€Ņ†Ņ‹ ĐēаĐŧĐĩҀҋ...", - "search_camera_model": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŧĐ°Đ´ŅĐģŅ– ĐēаĐŧĐĩҀҋ...", - "search_city": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŗĐžŅ€Đ°Đ´ĐˇĐĩ...", - "search_country": "ĐŸĐžŅˆŅƒĐē Đŋа ĐēŅ€Đ°Ņ–ĐŊĐĩ...", - "search_filter_apply": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅ–Ņ†ŅŒ ҄ҖĐģŅŒŅ‚Ņ€", - "search_filter_camera_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ҂ҋĐŋ ĐēаĐŧĐĩҀҋ", + "save_to_gallery": "Đ—Đ°Ņ…Đ°Đ˛Đ°Ņ†ŅŒ ҃ ĐŗĐ°ĐģĐĩŅ€ŅŅŽ", "search_filter_date": "Đ”Đ°Ņ‚Đ°", - "search_filter_date_interval": "З {start} Đŋа {end}", - "search_filter_date_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Đ´Ņ‹ŅĐŋаСОĐŊ Đ´Đ°Ņ‚", - "search_filter_display_option_not_in_album": "НĐĩ Ņž аĐģŅŒĐąĐžĐŧĐĩ", - "search_filter_display_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ", - "search_filter_filename": "ĐŸĐžŅˆŅƒĐē Đŋа ĐŊаСвĐĩ Ņ„Đ°ĐšĐģа", "search_filter_location": "МĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", "search_filter_location_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ", "search_filter_media_type": "ĐĸŅ‹Đŋ ĐŧĐĩĐ´Ņ‹Ņ", "search_filter_media_type_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ҂ҋĐŋ ĐŧĐĩĐ´Ņ‹Ņ", - "search_filter_people_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐģŅŽĐ´ĐˇĐĩĐš", - "search_filter_star_rating": "ĐŅ†ŅĐŊĐēа ĐˇĐžŅ€ĐēаĐŧŅ–", - "search_filter_tags_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Ņ‚ŅĐŗŅ–", - "search_for": "Đ¨ŅƒĐēĐ°Ņ†ŅŒ", - "search_for_existing_person": "ĐŸĐžŅˆŅƒĐē ҖҁĐŊŅƒŅŽŅ‡Đ°ĐŗĐ° Ņ‡Đ°ĐģавĐĩĐēа", - "search_no_more_result": "БоĐģҌ҈ Đ˛Ņ‹ĐŊŅ–ĐēĐ°Ņž ĐŊŅĐŧа", - "search_no_people": "ĐŅĐŧа ĐģŅŽĐ´ĐˇĐĩĐš", - "search_no_people_named": "ĐŅĐŧа ĐģŅŽĐ´ĐˇĐĩĐš С Ņ–ĐŧĐĩĐŧ ÂĢ{name}Âģ", - "search_no_result": "ĐŅ–Ņ‡ĐžĐŗĐ° ĐŊĐĩ СĐŊОКдСĐĩĐŊа, ĐŋĐ°ŅĐŋŅ€Đ°ĐąŅƒĐšŅ†Đĩ Ņ–ĐŊŅˆĐ°Đĩ ĐēĐģŅŽŅ‡Đ°Đ˛ĐžĐĩ ҁĐģОва айО ĐēаĐŧĐąŅ–ĐŊĐ°Ņ†Ņ‹ŅŽ", - "search_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐŋĐžŅˆŅƒĐē҃", - "search_page_search_photos_videos": "ĐŸĐžŅˆŅƒĐē Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°", - "search_page_view_all_button": "ĐŸĐ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ҃ҁĐĩ", - "search_people": "ĐŸĐžŅˆŅƒĐē ĐģŅŽĐ´ĐˇĐĩĐš", - "search_places": "ĐŸĐžŅˆŅƒĐē ĐŧĐĩŅŅ†Đ°Ņž", - "search_rating": "ĐŸĐžŅˆŅƒĐē Đŋа Đ°Ņ†ŅĐŊ҆ҋ...", - "search_settings": "ĐŸĐžŅˆŅƒĐē ҃ ĐŊаĐģĐ°Đ´Đ°Ņ…", - "search_state": "ĐŸĐžŅˆŅƒĐē Ņ€ŅĐŗŅ–Ņ‘ĐŊа...", - "search_tags": "ĐŸĐžŅˆŅƒĐē Ņ‚ŅĐŗĐ°Ņž...", - "search_timezone": "ĐŸĐžŅˆŅƒĐē Ņ‡Đ°ŅĐ°Đ˛Đ°ĐŗĐ° ĐŋĐžŅŅĐ°...", - "search_type": "ĐĸŅ‹Đŋ ĐŋĐžŅˆŅƒĐē҃", - "search_your_photos": "ĐŸĐžŅˆŅƒĐē Ņ„ĐžŅ‚Đ°", - "searching_locales": "ĐŸĐžŅˆŅƒĐē ĐŧĐžŅž...", + "search_page_screenshots": "Đ—Đ´Ņ‹ĐŧĐēŅ– ŅĐēŅ€Đ°ĐŊа", + "search_page_selfies": "ĐĄŅĐģ҄Җ", + "search_page_things": "Đ ŅŅ‡Ņ‹", + "search_page_your_map": "Đ’Đ°ŅˆĐ° ĐēĐ°Ņ€Ņ‚Đ°", "second": "ĐĄĐĩĐē҃ĐŊда", - "see_all_people": "ĐŸĐ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ŅƒŅŅ–Ņ… ĐģŅŽĐ´ĐˇĐĩĐš", - "select": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ", - "select_album": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧ", - "select_album_cover": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ вОĐēĐģадĐē҃ аĐģŅŒĐąĐžĐŧа", - "select_albums": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", - "select_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ŅƒŅŅ‘", - "select_all_duplicates": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ҃ҁĐĩ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", - "select_avatar_color": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ĐēĐžĐģĐĩŅ€ Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ°", - "select_count": "{count, plural, one {Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ #} few {Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ #} many {Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ #} other {Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ #}}", - "select_cutoff_date": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐēаĐŊŅ‡Đ°Ņ‚ĐēĐžĐ˛ŅƒŅŽ Đ´Đ°Ņ‚Ņƒ", - "select_face": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€", - "select_featured_photo": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ĐŗĐ°ĐģĐžŅžĐŊаĐĩ Ņ„ĐžŅ‚Đ°", - "select_from_computer": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ С ĐēаĐŧĐŋâ€™ŅŽŅ‚Đ°Ņ€Đ°", - "select_keep_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ÂĢĐŋаĐēŅ–ĐŊŅƒŅ†ŅŒ ŅƒŅŅ‘Âģ", - "select_new_face": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹ Ņ‚Đ˛Đ°Ņ€", - "select_people": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐģŅŽĐ´ĐˇĐĩĐš", - "select_person": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Ņ‡Đ°ĐģавĐĩĐēа", - "select_person_to_tag": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Ņ‡Đ°ĐģавĐĩĐēа Đ´ĐģŅ ĐŋаСĐŊĐ°Ņ‡ŅĐŊĐŊŅ", - "select_photos": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ Ņ„ĐžŅ‚Đ°", - "select_quality": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ŅĐēĐ°ŅŅ†ŅŒ", - "select_trash_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ŅƒŅŅ‘ Ņž ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ", - "selected": "Đ’Ņ‹ĐąŅ€Đ°ĐŊа", - "selected_count": "{count, plural, other {# Đ˛Ņ‹ĐąŅ€Đ°ĐŊа}}", - "selected_gps_coordinates": "Đ’Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ GPS-ĐēĐ°Đ°Ņ€Đ´Ņ‹ĐŊĐ°Ņ‚Ņ‹", "send_message": "АдĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ ĐŋавĐĩдаĐŧĐģĐĩĐŊĐŊĐĩ", - "send_welcome_email": "АдĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ Đ˛Ņ–Ņ‚Đ°ĐģҌĐŊŅ‹ ĐģҖҁ҂", - "server_endpoint": "КаĐŊŅ†Đ°Đ˛Ņ‹ Đŋ҃ĐŊĐēŅ‚ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_info_box_app_version": "ВĐĩŅ€ŅŅ–Ņ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "server_info_box_server_url": "URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_offline": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ Đŋа-Са ҁĐĩŅ‚ĐēаК", - "server_online": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ ҃ ҁĐĩ҂҆ҋ", - "server_privacy": "ĐŸŅ€Ņ‹Đ˛Đ°Ņ‚ĐŊĐ°ŅŅ†ŅŒ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_restarting_description": "Đ“ŅŅ‚Đ° ŅŅ‚Đ°Ņ€ĐžĐŊĐēа ĐŊĐĩŅžĐˇĐ°ĐąĐ°Đ˛Đĩ айĐŊĐžĐ˛Ņ–Ņ†Ņ†Đ°.", - "server_restarting_title": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ ĐŋĐĩŅ€Đ°ĐˇĐ°Đŋ҃ҁĐēаĐĩŅ†Ņ†Đ°", - "server_stats": "ĐĄŅ‚Đ°Ņ‚Ņ‹ŅŅ‚Ņ‹Đēа ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_update_available": "Đ”Đ°ŅŅ‚ŅƒĐŋĐŊа айĐŊĐ°ŅžĐģĐĩĐŊĐŊĐĩ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "server_version": "ВĐĩŅ€ŅŅ–Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "set": "Đ—Đ°Đ´Đ°Ņ†ŅŒ", - "set_as_album_cover": "Đ—Ņ€Đ°ĐąŅ–Ņ†ŅŒ вОĐēĐģадĐēаК аĐģŅŒĐąĐžĐŧа", - "set_as_featured_photo": "Đ—Ņ€Đ°ĐąŅ–Ņ†ŅŒ ĐŗĐ°ĐģĐžŅžĐŊŅ‹Đŧ Ņ„ĐžŅ‚Đ°", - "set_as_profile_picture": "Đ—Đ°Đ´Đ°Ņ†ŅŒ ŅĐē Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ", - "set_date_of_birth": "Đ—Đ°Đ´Đ°Ņ†ŅŒ Đ´Đ°Ņ‚Ņƒ ĐŊĐ°Ņ€Đ°Đ´ĐļŅĐŊĐŊŅ", - "set_profile_picture": "Đ—Đ°Đ´Đ°Ņ†ŅŒ Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ", - "set_slideshow_to_fullscreen": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ ҁĐģаКд-ŅˆĐžŅƒ ĐŊа ŅžĐ˛ĐĩҁҌ ŅĐēŅ€Đ°ĐŊ", - "set_stack_primary_asset": "Đ—Ņ€Đ°ĐąŅ–Ņ†ŅŒ Đ°ŅĐŊĐžŅžĐŊŅ‹Đŧ аб’ĐĩĐēŅ‚Đ°Đŧ", - "setting_image_navigation_enable_subtitle": "КаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа, Đ˛Ņ‹ ĐŧĐžĐļĐ°Ņ†Đĩ ĐŋĐĩŅ€Đ°Ņ…ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅĐŗĐ°/ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊĐ°ĐŗĐ° Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°, ĐŊĐ°Ņ†Ņ–ŅĐēĐ°ŅŽŅ‡Ņ‹ ĐŊа ĐģĐĩĐ˛ŅƒŅŽ/ĐŋŅ€Đ°Đ˛ŅƒŅŽ Ņ‡Đ˛ŅŅ€Ņ†ŅŒ ŅĐēŅ€Đ°ĐŊа.", - "setting_image_navigation_enable_title": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ Đ´ĐģŅ ĐŊĐ°Đ˛Ņ–ĐŗĐ°Ņ†Ņ‹Ņ–", - "setting_image_navigation_title": "ĐĐ°Đ˛Ņ–ĐŗĐ°Ņ†Ņ‹Ņ Đŋа Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°Ņ…", - "setting_image_viewer_help": "Đ”ŅŅ‚Đ°ĐģŅ‘Đ˛Ņ‹ ĐŋŅ€Đ°ĐŗĐģŅĐ´ ҁĐŋĐ°Ņ‡Đ°Ņ‚Đē҃ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°Đĩ ĐŧаĐģĐĩĐŊҌĐēŅƒŅŽ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņƒ, ĐŋĐžŅ‚Ņ‹Đŧ ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅ– ĐŋŅ€Đ°ĐŗĐģŅĐ´ ŅŅŅ€ŅĐ´ĐŊŅĐŗĐ° ĐŋаĐŧĐĩŅ€Ņƒ (ĐēаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа), Ņ– ĐŊĐ°Ņ€ŅŅˆŅ†Đĩ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ (ĐēаĐģŅ– ŅžĐēĐģŅŽŅ‡Đ°ĐŊа).", "setting_image_viewer_original_subtitle": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ Đ´ĐģŅ СаĐŋаĐŧĐŋаваĐŊĐŊŅ ĐˇŅ‹Ņ…ĐžĐ´ĐŊĐ°ĐŗĐ° Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° ҃ ĐŋĐžŅžĐŊаК Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†Ņ– (҈ĐŧĐ°Ņ‚!). АдĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ Đēай СĐŧĐĩĐŊŅˆŅ‹Ņ†ŅŒ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩ Ņ‚Ņ€Đ°Ņ„Ņ–Đēа (ŅĐē ҁĐĩŅ‚ĐēŅ–, Ņ‚Đ°Đē Ņ– ĐēŅŅˆĐ° ĐŋҀҋĐģĐ°Đ´Ņ‹).", - "setting_image_viewer_original_title": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊŅ‹ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ", "setting_image_viewer_preview_subtitle": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ Đ´ĐģŅ СаĐŋаĐŧĐŋаваĐŊĐŊŅ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° ŅŅŅ€ŅĐ´ĐŊŅĐš Ņ€Đ°ĐˇĐ´ĐˇŅĐģŅĐģҌĐŊĐ°ŅŅ†Ņ–. АдĐēĐģŅŽŅ‡Ņ‹Ņ†Đĩ, Đēай ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ†ŅŒ Ņ‚ĐžĐģҌĐēŅ– Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ ҆Җ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņƒ.", - "setting_image_viewer_preview_title": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ ĐŋĐĩŅ€Đ°Đ´ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ", "setting_languages_apply": "ĐŖĐļŅ‹Ņ†ŅŒ", - "setting_languages_subtitle": "ЗĐŧŅĐŊĐĩĐŊĐŊĐĩ ĐŧĐžĐ˛Ņ‹ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹", - "setting_notifications_notify_minutes": "{count} Ņ…Đ˛", - "setting_notifications_notify_seconds": "{count} ҁ", - "setting_notifications_subtitle": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ аĐŋĐ°Đ˛ŅŅˆŅ‡ŅĐŊĐŊŅŅž", - "setting_video_viewer_auto_play_subtitle": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа ĐŋĐ°Ņ‡Ņ‹ĐŊĐ°Ņ†ŅŒ ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ˛Ņ–Đ´ŅĐ° ĐŋҀҋ адĐēҀҋ҆҆Җ", - "setting_video_viewer_auto_play_title": "ĐŅžŅ‚Đ°ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ˛Ņ–Đ´ŅĐ°", - "setting_video_viewer_looping_title": "ĐĻŅ‹ĐēĐģ", - "setting_video_viewer_original_video_subtitle": "ĐŸŅ€Ņ‹ ҁ҂ҀҋĐŧŅ–ĐŊĐŗŅƒ Đ˛Ņ–Đ´ŅĐ° С ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°Ņ†ŅŒ Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģ, ĐŊĐ°Đ˛Đ°Ņ‚ ĐēаĐģŅ– Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹ Ņ‚Ņ€Đ°ĐŊҁĐēОд. МоĐļа ĐŋŅ€Ņ‹Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ да ĐąŅƒŅ„ĐĩŅ€Ņ‹ĐˇĐ°Ņ†Ņ‹Ņ–. ЛаĐēаĐģҌĐŊа Đ´Đ°ŅŅ‚ŅƒĐŋĐŊŅ‹Ņ Đ˛Ņ–Đ´ŅĐ° ĐŋŅ€Đ°ĐšĐŗŅ€Đ°ŅŽŅ†Ņ†Đ° Ņž Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊаК ŅĐēĐ°ŅŅ†Ņ– ĐŊĐĩСаĐģĐĩĐļĐŊа ад ĐŗŅŅ‚Đ°Đš ĐŊаĐģĐ°Đ´Ņ‹.", - "setting_video_viewer_original_video_title": "ĐŸŅ€Ņ‹ĐŧŅƒŅĐžĐ˛Đ° Đ°Ņ€Ņ‹ĐŗŅ–ĐŊаĐģҌĐŊаĐĩ Đ˛Ņ–Đ´ŅĐ°", + "setting_notifications_notify_never": "ĐŊŅ–ĐēĐžĐģŅ–", "settings": "НаĐģĐ°Đ´Ņ‹", - "settings_saved": "НаĐģĐ°Đ´Ņ‹ ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊŅ‹", - "setup_pin_code": "НаĐģĐ°Đ´ĐˇŅ–Ņ†ŅŒ PIN-ĐēОд", - "share": "ĐĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒ", - "share_dialog_preparing": "ĐŸĐ°Đ´Ņ€Ņ‹Ņ…Ņ‚ĐžŅžĐēаâ€Ļ", - "share_link": "ĐĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "share_original": "ĐŅ€Ņ‹ĐŗŅ–ĐŊаĐģ (Đ˛ŅĐģŅ–ĐēŅ– ĐŋаĐŧĐĩŅ€)", - "share_preview": "ĐœŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Đ° (ĐŧаĐģĐĩĐŊҌĐēŅ– ĐŋаĐŧĐĩŅ€)", - "share_quality_body": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ Ņ– ŅžŅ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐšŅ†Đĩ ĐēĐŊĐžĐŋĐē҃ ÂĢĐĐąĐ°ĐŗŅƒĐģŅ–Ņ†ŅŒÂģ, Đēай Đ˛Ņ‹ĐąŅ€Đ°Ņ†ŅŒ ŅĐēĐ°ŅŅ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ° ĐŋĐĩŅ€Đ°Đ´ адĐŋŅ€Đ°ŅžĐēаК.", - "share_quality_title": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ŅĐēĐ°ŅŅ†ŅŒ Đ´ĐģŅ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ", - "shared": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ", - "shared_album_activities_input_disable": "КаĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ‹Đš адĐēĐģŅŽŅ‡Đ°ĐŊŅ‹", - "shared_album_activity_remove_content": "Đ’Ņ‹ Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņƒ аĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ?", - "shared_album_activity_remove_title": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аĐēŅ‚Ņ‹ŅžĐŊĐ°ŅŅ†ŅŒ", - "shared_album_section_people_action_error": "ПаĐŧŅ‹ĐģĐēа Đ˛Ņ‹Ņ…Đ°Đ´Ņƒ/Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊĐŊŅ С аĐģŅŒĐąĐžĐŧа", + "share_add_photos": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ°", "shared_album_section_people_title": "ЛЮДЗІ", - "shared_by": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊа", - "shared_by_user": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊа {user}", - "shared_by_you": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊа ваĐŧŅ–", - "shared_from_partner": "Đ¤ĐžŅ‚Đ° ад {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} СаĐŋаĐŧĐŋаваĐŊа", - "shared_link_app_bar_title": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "shared_link_clipboard_copied_massage": "ĐĄĐēаĐŋŅ–Ņ€Đ°Đ˛Đ°ĐŊа Ņž ĐąŅƒŅ„ĐĩŅ€ айĐŧĐĩĐŊ҃", - "shared_link_create_error": "ПаĐŧŅ‹ĐģĐēа ĐŋҀҋ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ– Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "shared_link_custom_url_description": "Đ”ĐžŅŅ‚ŅƒĐŋ да ĐŗŅŅ‚Đ°Đš Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ– ĐŋŅ€Đ°Đˇ ҃ĐģĐ°ŅĐŊŅ‹ URL-Đ°Đ´Ņ€Đ°Ņ", - "shared_link_custom_url_title": "ĐŖĐģĐ°ŅĐŊŅ‹ URL-Đ°Đ´Ņ€Đ°Ņ", - "shared_link_custom_url_warning": "ПаĐŋŅŅ€ŅĐ´ĐļаĐŊĐŊĐĩ: ҃ĐģĐ°ŅĐŊŅ‹ URL-Đ°Đ´Ņ€Đ°ŅĐ° С ĐŋŅ€Đ°ĐŧОК ĐēĐ°ŅĐžĐš Ņ€Ņ‹ŅĐ°Đš ĐŧĐžĐļа ĐŋĐ°Đ˛ĐžĐ´ĐˇŅ–Ņ†ŅŒ ŅŅĐąĐĩ ĐŊĐĩ Ņ‚Đ°Đē, ŅĐē Đ˛Ņ‹ Ņ‡Đ°ĐēаĐĩ҆Đĩ.", - "shared_link_edit_description_hint": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ", - "shared_link_edit_expire_after_option_day": "1 дСĐĩĐŊҌ", - "shared_link_edit_expire_after_option_days": "{count} ŅŅƒŅ‚", - "shared_link_edit_expire_after_option_hour": "1 ĐŗĐ°Đ´ĐˇŅ–ĐŊа", - "shared_link_edit_expire_after_option_hours": "{count} ĐŗĐ°Đ´Đˇ", - "shared_link_edit_expire_after_option_minute": "1 Ņ…Đ˛Ņ–ĐģŅ–ĐŊа", - "shared_link_edit_expire_after_option_minutes": "{count} Ņ…Đ˛", - "shared_link_edit_expire_after_option_months": "{count} ĐŧĐĩҁ.", - "shared_link_edit_expire_after_option_year": "{count} Đŗ.", - "shared_link_edit_password_hint": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊŅ", - "shared_link_edit_submit_button": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "shared_link_error_server_url_fetch": "НĐĩ ŅžĐ´Đ°ĐģĐžŅŅ Đ°Ņ‚Ņ€Ņ‹ĐŧĐ°Ņ†ŅŒ URL-Đ°Đ´Ņ€Đ°Ņ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "shared_link_expires_day": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} дСĐĩĐŊҌ", - "shared_link_expires_days": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} ŅŅƒŅ‚", - "shared_link_expires_hour": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} ĐŗĐ°Đ´ĐˇŅ–ĐŊ҃", - "shared_link_expires_hours": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} ĐŗĐ°Đ´Đˇ", - "shared_link_expires_minute": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} Ņ…Đ˛Ņ–ĐģŅ–ĐŊ҃", - "shared_link_expires_minutes": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} Ņ…Đ˛", - "shared_link_expires_never": "ĐŅ–ĐēĐžĐģŅ– ĐŊĐĩ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ°", - "shared_link_expires_second": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} ҁĐĩĐē҃ĐŊĐ´Ņƒ", - "shared_link_expires_seconds": "ĐĸŅŅ€ĐŧŅ–ĐŊ дСĐĩŅĐŊĐŊŅ СаĐēаĐŊŅ‡Đ˛Đ°ĐĩŅ†Ņ†Đ° ĐŋŅ€Đ°Đˇ {count} ҁ", - "shared_link_individual_shared": "ĐŅĐžĐąĐŊаĐĩ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ", "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹ĐŧŅ– ҁĐŋĐ°ŅŅ‹ĐģĐēаĐŧŅ–", - "shared_link_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "shared_link_password_description": "ĐŸĐ°Ņ‚Ņ€Đ°ĐąĐ°Đ˛Đ°Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ Đ´ĐžŅŅ‚ŅƒĐŋ҃ да ĐŗŅŅ‚Đ°Đš Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаК ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "shared_links": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "shared_links_description": "ĐĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐšŅ†Đĩ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° ĐŋŅ€Đ°Đˇ ҁĐŋĐ°ŅŅ‹ĐģĐē҃", - "shared_photos_and_videos_count": "{assetCount, plural, one {# Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊаĐĩ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.} few {# Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.} many {# Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊҋ҅ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.} other {# Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊĐ°ĐŗĐ° Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°.}}", - "shared_with_me": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ ŅĐ° ĐŧĐŊОК", - "shared_with_partner": "ĐĐąĐ°ĐŗŅƒĐģĐĩĐŊа С {partner}", - "sharing": "ĐĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ", - "sharing_enter_password": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ ĐŋŅ€Đ°ĐŗĐģŅĐ´Ņƒ ĐŗŅŅ‚Đ°Đš ŅŅ‚Đ°Ņ€ĐžĐŊĐēŅ–.", - "shift_to_permanent_delete": "ĐŊĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ ⇧, Đēай ĐŊĐ°ĐˇĐ°ŅžĐļĐ´Ņ‹ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ай'ĐĩĐēŅ‚", - "show_album_options": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ аĐģŅŒĐąĐžĐŧа", - "show_albums": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", - "show_all_people": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅƒŅŅ–Ņ… ĐģŅŽĐ´ĐˇĐĩĐš", - "show_and_hide_people": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ Ņ– ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "show_file_location": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ Ņ€Đ°ĐˇĐŧŅŅˆŅ‡ŅĐŊĐŊĐĩ Ņ„Đ°ĐšĐģа", - "show_gallery": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŗĐ°ĐģĐĩŅ€ŅŅŽ", - "show_hidden_people": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅŅ…Đ°Đ˛Đ°ĐŊҋ҅ ĐģŅŽĐ´ĐˇĐĩĐš", - "show_in_timeline": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ҃ Ņ…Ņ€ĐžĐŊҖ҆ҋ", - "show_in_timeline_setting_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° ĐŗŅŅ‚Đ°ĐŗĐ° ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа Ņž Đ˛Đ°ŅˆĐ°Đš Ņ…Ņ€ĐžĐŊҖ҆ҋ", - "show_keyboard_shortcuts": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ҁĐŋаĐģŅƒŅ‡ŅĐŊĐŊŅ– ĐēĐģĐ°Đ˛Ņ–Ņˆ", - "show_less": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŧĐĩĐŊ҈", - "show_metadata": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ‹Ņ", - "show_more_fields": "{count, plural, one {ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅŅˆŅ‡Ņ # ĐŋĐžĐģĐĩ} few {ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅŅˆŅ‡Ņ # ĐŋĐžĐģŅ} many {ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅŅˆŅ‡Ņ # ĐŋаĐģŅ‘Ņž} other {ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ŅŅˆŅ‡Ņ # ĐŋĐžĐģŅ}}", - "show_or_hide_info": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ айО ŅŅ…Đ°Đ˛Đ°Ņ†ŅŒ Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹ŅŽ", - "show_password": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "show_person_options": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Ņ‡Đ°ĐģавĐĩĐēа", - "show_progress_bar": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Ņ€Đ°Đ´ĐžĐē ĐŋŅ€Đ°ĐŗŅ€ŅŅŅƒ", - "show_schema": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ҁ҅ĐĩĐŧ҃", - "show_search_options": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐŋĐžŅˆŅƒĐē҃", - "show_shared_links": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊŅ‹Ņ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", - "show_slideshow_metadata_overlay": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ ĐŊаĐēĐģадĐē҃ С Ņ–ĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹ŅĐš ĐŋŅ€Đ° Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ", - "show_slideshow_transition": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ ĐŋĐĩŅ€Đ°Ņ…ĐžĐ´ ҁĐģаКд-ŅˆĐžŅƒ", - "show_supporter_badge": "ЗĐŊĐ°Ņ‡ĐžĐē ĐŋҀҋ҅ҖĐģҌĐŊŅ–Đēа", - "show_supporter_badge_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ СĐŊĐ°Ņ‡ĐžĐē ĐŋҀҋ҅ҖĐģҌĐŊŅ–Đēа", - "show_text_recognition": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ", - "show_text_search_menu": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŧĐĩĐŊŅŽ Ņ‚ŅĐēŅŅ‚Đ°Đ˛Đ°ĐŗĐ° ĐŋĐžŅˆŅƒĐē҃", - "shuffle": "ПĐĩŅ€Đ°ĐŧŅŅˆĐ°Ņ†ŅŒ", - "sidebar": "БаĐēĐ°Đ˛Đ°Ņ ĐŋаĐŊŅĐģҌ", - "sidebar_display_description": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐē҃ ĐŊа ĐŋŅ€Đ°ĐŗĐģŅĐ´ ҃ йаĐēавОК ĐŋаĐŊŅĐģŅ–", + "sharing_page_empty_list": "ĐŸĐŖĐĄĐĸĐĢ ĐĄĐŸĐ†ĐĄ", "sign_out": "Đ’Ņ‹ĐšŅŅ†Ņ–", "sign_up": "Đ—Đ°Ņ€ŅĐŗŅ–ŅŅ‚Ņ€Đ°Đ˛Đ°Ņ†Ņ†Đ°", "size": "ПаĐŧĐĩŅ€", - "skip": "ĐŸŅ€Đ°ĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ", - "skip_to_content": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да СĐŧĐĩŅŅ†Ņ–Đ˛Đ°", - "skip_to_folders": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐŋаĐŋаĐē", - "skip_to_tags": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да Ņ‚ŅĐŗĐ°Ņž", - "slideshow": "ĐĄĐģаКд-ŅˆĐžŅƒ", - "slideshow_body": "ĐĄŅĐ´Đ°ĐšŅ†Đĩ ĐˇŅ€ŅƒŅ‡ĐŊĐĩĐš Ņ– Đ°Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐšŅ†Đĩ Đ°ŅĐ°ĐģĐžĐ´Ņƒ ад ҁĐģаКд-ŅˆĐžŅƒ ĐŊа ŅžĐ˛ĐĩҁҌ ŅĐēŅ€Đ°ĐŊ.", - "slideshow_metadata_overlay_mode": "ЗĐŧĐĩŅŅ†Ņ–Đ˛Đ° ĐŊаĐēĐģадĐēŅ–", - "slideshow_metadata_overlay_mode_description_only": "ĐĸĐžĐģҌĐēŅ– аĐŋŅ–ŅĐ°ĐŊĐŊĐĩ", - "slideshow_metadata_overlay_mode_full": "ĐŸĐžŅžĐŊаĐĩ", - "slideshow_repeat": "ĐŸĐ°ŅžŅ‚Đ°Ņ€Đ°Ņ†ŅŒ ҁĐģаКд-ŅˆĐžŅƒ", - "slideshow_repeat_description": "Đ’ŅŅ€Ņ‚Đ°Ņ†Ņ†Đ° Ņž ĐŋĐ°Ņ‡Đ°Ņ‚Đ°Đē ĐŋĐ°ŅĐģŅ СаĐēаĐŊŅ‡ŅĐŊĐŊŅ ҁĐģаКд-ŅˆĐžŅƒ", - "slideshow_settings": "НаĐģĐ°Đ´Ņ‹ ҁĐģаКд-ŅˆĐžŅƒ", - "slideshow_title": "ĐĄĐģаКд-ŅˆĐžŅƒ", - "sort_albums_by": "ĐĄĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹ Đŋаâ€Ļ", - "sort_created": "Đ”Đ°Ņ†Đĩ ŅŅ‚Đ˛Đ°Ņ€ŅĐŊĐŊŅ", - "sort_items": "КоĐģҌĐēĐ°ŅŅ†Ņ– ŅĐģĐĩĐŧĐĩĐŊŅ‚Đ°Ņž", - "sort_modified": "Đ”Đ°Ņ†Đĩ СĐŧŅĐŊĐĩĐŊĐŊŅ", - "sort_oldest": "ĐĐ°ĐšŅŅ‚Đ°Ņ€ŅĐšŅˆŅ‹Đŧ Ņ„ĐžŅ‚Đ°", - "sort_people_by_similarity": "ĐĄĐ°Ņ€Ņ‚Đ°Đ˛Đ°Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš Đŋа ĐŋадайĐĩĐŊŅŅ‚Đ˛Đĩ", - "sort_recent": "ХаĐŧŅ‹Đŧ ĐŊŅĐ´Đ°ŅžĐŊŅ–Đŧ Ņ„ĐžŅ‚Đ°", - "sort_title": "Đ—Đ°ĐŗĐ°ĐģĐžŅžĐē҃", + "sort_title": "Đ—Đ°ĐŗĐ°ĐģОваĐē", "source": "ĐšŅ€Ņ‹ĐŊŅ–Ņ†Đ°", - "stack": "ĐĄŅ‚ĐžŅ", - "stack_action_prompt": "{count} ҁĐēĐģадСĐĩĐŊа Ņž ŅŅ‚ĐžŅ", - "stack_duplicates": "ĐĄĐēĐģĐ°ŅŅ†Ņ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹ Ņž ŅŅ‚ĐžŅ", - "stack_selected_photos": "ĐĄĐēĐģĐ°ŅŅ†Ņ– Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ° Ņž ŅŅ‚ĐžŅ", - "stacked_assets_count": "{count, plural, one {# аб’ĐĩĐēŅ‚ ҁĐēĐģадСĐĩĐŊŅ‹} few {# аб’ĐĩĐē҂ҋ ҁĐēĐģадСĐĩĐŊŅ‹} many {# аб’ĐĩĐēŅ‚Đ°Ņž ҁĐēĐģадСĐĩĐŊа} other {# аб’ĐĩĐēŅ‚Đ° ҁĐēĐģадСĐĩĐŊа}} Ņž ŅŅ‚ĐžŅ", - "stacktrace": "ĐĸŅ€Đ°ŅŅ–Ņ€ĐžŅžĐēа ŅŅ‚ŅĐēа", - "start": "ĐŸĐ°Ņ‡Đ°Ņ†ŅŒ", - "start_date": "Đ”Đ°Ņ‚Đ° ĐŋĐ°Ņ‡Đ°Ņ‚Đē҃", - "start_date_before_end_date": "Đ”Đ°Ņ‚Đ° ĐŋĐ°Ņ‡Đ°Ņ‚Đē҃ ĐŋĐ°Đ˛Ņ–ĐŊĐŊа ĐąŅ‹Ņ†ŅŒ Ņ€Đ°ĐŊĐĩĐšŅˆĐ°Đš Са Đ´Đ°Ņ‚Ņƒ СаĐēаĐŊŅ‡ŅĐŊĐŊŅ", - "state": "Đ ŅĐŗŅ–Ņ‘ĐŊ", - "status": "ĐĄŅ‚Đ°ĐŊ", - "step_delete": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐēŅ€ĐžĐē", - "step_delete_confirm": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ ĐēŅ€ĐžĐē?", - "steps": "ĐšŅ€ĐžĐēŅ–", - "stop_casting": "ĐĄĐŋŅ‹ĐŊŅ–Ņ†ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ‹ŅŽ", - "stop_motion_photo": "ĐĄĐŋŅ‹ĐŊŅ–Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Ņ„ĐžŅ‚Đ°", - "stop_photo_sharing": "ĐĄĐŋŅ‹ĐŊŅ–Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ Đ˛Đ°ŅˆŅ‹Ņ… Ņ„ĐžŅ‚Đ°?", - "stop_photo_sharing_description": "{partner} йОĐģҌ҈ ĐŊĐĩ ĐąŅƒĐ´ĐˇĐĩ ĐŧĐĩŅ†ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ҃ да Đ˛Đ°ŅˆŅ‹Ņ… Ņ„ĐžŅ‚Đ°.", - "stop_sharing_photos_with_user": "ĐĄĐŋŅ‹ĐŊŅ–Ņ†ŅŒ Đ°ĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ Ņ„ĐžŅ‚Đ° С ĐŗŅŅ‚Ņ‹Đŧ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧ", - "storage": "ĐĄŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_label": "МĐĩŅ‚Đēа ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_quota": "ĐšĐ˛ĐžŅ‚Đ° ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ°", - "storage_usage": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊа {used} С {available}", - "submit": "АдĐŋŅ€Đ°Đ˛Ņ–Ņ†ŅŒ", - "success": "Đ“Đ°Ņ‚ĐžĐ˛Đ°", - "suggestions": "ĐŸŅ€Đ°ĐŋаĐŊĐžĐ˛Ņ‹", - "sunrise_on_the_beach": "ĐĄĐ˛Ņ–Ņ‚Đ°ĐŊаĐē ĐŊа ĐŋĐģŅĐļŅ‹", - "support": "ĐŸĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐēа", - "support_and_feedback": "ĐŸĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐēа Ņ– ĐˇĐ˛Đ°Ņ€ĐžŅ‚ĐŊĐ°Ņ ŅŅƒĐ˛ŅĐˇŅŒ", - "support_third_party_description": "Đ’Đ°Ņˆ ŅƒŅŅ‚Đ°ĐģŅ‘ŅžŅˆŅ‡Ņ‹Đē Immich ĐąŅ‹Ņž ŅĐ°ĐąŅ€Đ°ĐŊŅ‹ ŅŅ‚Đ°Ņ€ĐžĐŊĐŊŅ–Đŧ ŅŅƒĐąâ€™ĐĩĐēŅ‚Đ°Đŧ. ĐŸŅ€Đ°ĐąĐģĐĩĐŧŅ‹, С ŅĐēŅ–ĐŧŅ– Đ˛Ņ‹ ŅŅƒŅ‚Ņ‹ĐēаĐĩ҆ĐĩŅŅ, ĐŧĐžĐŗŅƒŅ†ŅŒ ĐąŅ‹Ņ†ŅŒ Đ˛Ņ‹ĐēĐģŅ–ĐēаĐŊŅ‹ ĐŗŅŅ‚Ņ‹Đŧ ĐŋаĐēĐĩŅ‚Đ°Đŧ, Ņ‚Đ°Đŧ҃ ҁĐŋĐ°Ņ‡Đ°Ņ‚Đē҃ ĐŋавĐĩдаĐŧĐģŅĐšŅ†Đĩ ĐŋŅ€Đ° ĐŋŅ€Đ°ĐąĐģĐĩĐŧŅ‹ Đŋа ҁĐŋĐ°ŅŅ‹ĐģĐēĐ°Ņ… ĐŊŅ–ĐļŅĐš.", - "supporter": "ĐŸŅ€Ņ‹Ņ…Ņ–ĐģҌĐŊŅ–Đē", - "swap_merge_direction": "ПаĐŧŅĐŊŅŅ†ŅŒ ĐŊаĐŋŅ€Đ°ĐŧаĐē Đ°Đąâ€™ŅĐ´ĐŊаĐŊĐŊŅ", - "sync": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ", - "sync_albums": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Đ˛Đ°Ņ†ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", - "sync_local": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ ĐģаĐēаĐģҌĐŊа", - "sync_remote": "ĐĄŅ–ĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа", - "sync_status": "ĐĄŅ‚Đ°ĐŊ ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ–", - "sync_status_subtitle": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ Ņ– ĐēŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ŅŅ–ŅŅ‚ŅĐŧаК ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇĐ°Ņ†Ņ‹Ņ–", - "sync_upload_album_setting_subtitle": "ĐĄŅ‚Đ˛Đ°Ņ€Đ°ĐšŅ†Đĩ Ņ– СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐšŅ†Đĩ Đ˛Đ°ŅˆŅ‹ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° Ņž Đ˛Ņ‹ĐąŅ€Đ°ĐŊŅ‹Ņ аĐģŅŒĐąĐžĐŧŅ‹ ĐŊа Immich", - "system_theme": "ĐĄŅ–ŅŅ‚ŅĐŧĐŊĐ°Ņ Ņ‚ŅĐŧа", - "system_theme_command_description": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ŅŅ–ŅŅ‚ŅĐŧĐŊŅƒŅŽ Ņ‚ŅĐŧ҃ ({value})", "tag": "ĐĸŅĐŗ", - "tag_assets": "Đ”Đ°Đ´Đ°Ņ†ŅŒ Ņ‚ŅĐŗŅ–", - "tag_created": "ĐĄŅ‚Đ˛ĐžŅ€Đ°ĐŊŅ‹ Ņ‚ŅĐŗ: {tag}", - "tag_face": "ПазĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ Ņ‚Đ˛Đ°Ņ€", - "tag_feature_description": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ Ņ„ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ°, ĐˇĐŗŅ€ŅƒĐŋаваĐŊҋ҅ Đŋа ĐģĐ°ĐŗŅ–Ņ‡ĐŊҋ҅ Ņ‚ŅĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊҋ҅ Ņ‚ŅĐŗĐ°Ņ…", - "tag_people": "ПазĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐģŅŽĐ´ĐˇĐĩĐš", - "tag_updated": "АйĐŊĐžŅžĐģĐĩĐŊŅ‹ Ņ‚ŅĐŗ: {tag}", - "tagged_assets": "{count, plural, one {ПазĐŊĐ°Ņ‡Đ°ĐŊŅ‹ Ņ‚ŅĐŗĐ°Đŧ # аб’ĐĩĐēŅ‚} few {ПазĐŊĐ°Ņ‡Đ°ĐŊŅ‹ Ņ‚ŅĐŗĐ°Đŧ # аб’ĐĩĐē҂ҋ} many {ПазĐŊĐ°Ņ‡Đ°ĐŊа Ņ‚ŅĐŗĐ°Đŧ # аб’ĐĩĐēŅ‚Đ°Ņž} other {ПазĐŊĐ°Ņ‡Đ°ĐŊа Ņ‚ŅĐŗĐ°Đŧ # аб’ĐĩĐēŅ‚Đ°}}", "tags": "ĐĸŅĐŗŅ–", - "tap_to_run_job": "ĐĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ, Đēай СаĐŋŅƒŅŅ†Ņ–Ņ†ŅŒ СадаĐŊĐŊĐĩ", - "template": "ШайĐģĐžĐŊ", - "text_recognition": "Đ Đ°ŅĐŋаСĐŊаваĐŊĐŊĐĩ Ņ‚ŅĐēŅŅ‚Ņƒ", "theme": "ĐĸŅĐŧа", "theme_selection": "Đ’Ņ‹ĐąĐ°Ņ€ Ņ‚ŅĐŧŅ‹", - "theme_selection_description": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа Đ˛Ņ‹ĐąŅ–Ņ€Đ°Ņ†ŅŒ ŅĐ˛ĐĩŅ‚ĐģŅƒŅŽ айО ҆ґĐŧĐŊŅƒŅŽ Ņ‚ŅĐŧ҃ ĐŊа ĐŋĐ°Đ´ŅŅ‚Đ°Đ˛Đĩ ŅŅ–ŅŅ‚ŅĐŧĐŊҋ҅ ĐŊаĐģад ĐąŅ€Đ°ŅžĐˇĐĩŅ€Đ°", - "theme_setting_asset_list_storage_indicator_title": "ПаĐēĐ°ĐˇĐ˛Đ°Ņ†ŅŒ Ņ–ĐŊĐ´Ņ‹ĐēĐ°Ņ‚Đ°Ņ€ ŅŅ…ĐžĐ˛Ņ–ŅˆŅ‡Đ° ĐŊа ĐŋĐģŅ–Ņ‚ĐēĐ°Ņ… аб’ĐĩĐēŅ‚Đ°Ņž", - "theme_setting_asset_list_tiles_per_row_title": "КоĐģҌĐēĐ°ŅŅ†ŅŒ аб’ĐĩĐēŅ‚Đ°Ņž ҃ Ņ€Đ°Đ´Đē҃ ({count})", - "theme_setting_colorful_interface_subtitle": "ĐŸŅ€Ņ‹ĐŧŅĐŊŅ–Ņ†ŅŒ Đ°ŅĐŊĐžŅžĐŊŅ‹ ĐēĐžĐģĐĩŅ€ да Ņ„ĐžĐŊĐ°Đ˛Ņ‹Ņ… ĐŋавĐĩҀ҅ĐŊŅŅž.", - "theme_setting_colorful_interface_title": "КаĐģŅŅ€ĐžĐ˛Ņ‹ Ņ–ĐŊŅ‚ŅŅ€Ņ„ĐĩĐšŅ", - "theme_setting_primary_color_subtitle": "Đ’Ņ‹ĐąĐĩҀҋ҆Đĩ ĐēĐžĐģĐĩŅ€ Đ´ĐģŅ Đ°ŅĐŊĐžŅžĐŊҋ҅ дСĐĩŅĐŊĐŊŅŅž Ņ– аĐēŅ†ŅĐŊŅ‚Đ°Ņž.", - "theme_setting_primary_color_title": "ĐŅĐŊĐžŅžĐŊŅ‹ ĐēĐžĐģĐĩŅ€", - "theme_setting_system_primary_color_title": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ŅŅ–ŅŅ‚ŅĐŧĐŊŅ‹ ĐēĐžĐģĐĩŅ€", - "theme_setting_system_theme_switch": "ĐŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа (ŅĐē ҃ ŅŅ–ŅŅ‚ŅĐŧĐŊҋ҅ ĐŊаĐģĐ°Đ´Đ°Ņ…)", - "then": "Đ—Đ°Ņ‚Ņ‹Đŧ", - "they_will_be_merged_together": "Đ¯ĐŊŅ‹ ĐąŅƒĐ´ŅƒŅ†ŅŒ Đ°Đąâ€™ŅĐ´ĐŊаĐŊŅ‹ Ņ€Đ°ĐˇĐ°Đŧ", - "third_party_resources": "ĐĄŅ‚Đ°Ņ€ĐžĐŊĐŊŅ–Ņ Ņ€ŅŅŅƒŅ€ŅŅ‹", - "time": "Đ§Đ°Ņ", - "time_based_memories": "ĐŖŅĐŋаĐŧŅ–ĐŊŅ‹ ĐŊа Đ°ŅĐŊОвĐĩ Ņ‡Đ°ŅŅƒ", - "time_based_memories_duration": "КоĐģҌĐēĐ°ŅŅ†ŅŒ ҁĐĩĐē҃ĐŊĐ´ Đ´ĐģŅ адĐģŅŽŅŅ‚Ņ€Đ°Đ˛Đ°ĐŊĐŊŅ ĐēĐžĐļĐŊĐ°ĐŗĐ° Đ˛Ņ–Đ´Đ°Ņ€Ņ‹ŅĐ°.", "timeline": "ĐĨŅ€ĐžĐŊŅ–Đēа", - "timezone": "Đ§Đ°ŅĐ°Đ˛Ņ‹ ĐŋĐžŅŅ", - "to_archive": "ĐŖ Đ°Ņ€Ņ…Ņ–Ņž", - "to_change_password": "ЗĐŧŅĐŊŅ–Ņ†ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", - "to_favorite": "ĐŖ Đ°ĐąŅ€Đ°ĐŊаĐĩ", - "to_login": "ĐŖĐ˛Đ°Ņ…ĐžĐ´", - "to_multi_select": "Đ´ĐģŅ ĐŧĐŊĐžĐļĐŊĐ°ĐŗĐ° Đ˛Ņ‹ĐąĐ°Ņ€Ņƒ", - "to_parent": "ПĐĩŅ€Đ°ĐšŅŅ†Ņ– да ĐąĐ°Ņ†ŅŒĐēĐžŅžŅĐēĐ°ĐŗĐ°", - "to_select": "Đ´ĐģŅ Đ˛Ņ‹ĐąĐ°Ņ€Ņƒ", - "to_trash": "ĐŖ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "toggle_settings": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŊаĐģĐ°Đ´Ņ‹", - "toggle_theme_description": "ПĐĩŅ€Đ°ĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ Ņ‚ŅĐŧ҃", "total": "ĐŖŅŅĐŗĐž", - "total_usage": "ĐĐŗŅƒĐģҌĐŊаĐĩ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩ", "trash": "ĐĄĐŧĐĩŅ‚ĐŊŅ–Ņ†Đ°", - "trash_action_prompt": "{count} ĐŋĐĩŅ€Đ°ĐŧĐĩŅˆŅ‡Đ°ĐŊа Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "trash_all": "ĐŖŅŅ‘ Ņž ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ", - "trash_count": "ĐĄĐŧĐĩŅ‚ĐŊŅ–Ņ†Đ° {count, number}", - "trash_delete_asset": "ĐŖ ҁĐŧĐĩŅ‚ĐŊŅ–Ņ†Ņƒ/Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ аб’ĐĩĐēŅ‚", - "trash_no_results_message": "Đ¤ĐžŅ‚Đ° Ņ– Đ˛Ņ–Đ´ŅĐ° ŅĐ° ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐŋаĐēаСаĐŊŅ‹ Ņ‚ŅƒŅ‚.", "trash_page_delete_all": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ҃ҁĐĩ", - "trash_page_info": "Đ­ĐģĐĩĐŧĐĩĐŊ҂ҋ Ņž ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēаĐŊŅ‡Đ°Ņ‚ĐēОва Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ ĐŋŅ€Đ°Đˇ {days} Đ´ĐˇŅ‘ĐŊ", - "trashed_items_will_be_permanently_deleted_after": "Đ­ĐģĐĩĐŧĐĩĐŊ҂ҋ Ņž ҁĐŧĐĩŅ‚ĐŊҖ҆ҋ ĐąŅƒĐ´ŅƒŅ†ŅŒ ĐēаĐŊŅ‡Đ°Ņ‚ĐēОва Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹ ĐŋŅ€Đ°Đˇ {days, plural, one {# дСĐĩĐŊҌ} few {# Đ´ĐŊŅ–} many {# Đ´ĐˇŅ‘ĐŊ} other {# Đ´ĐŊŅ}}.", - "trigger": "ĐĸŅ€Ņ‹ĐŗĐĩŅ€", - "trigger_asset_uploaded": "ЗаĐŋаĐŧĐŋаваĐŊŅ‹ аб’ĐĩĐēŅ‚", - "trigger_asset_uploaded_description": "ĐĄĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°Đĩ ĐŋҀҋ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ– ĐŊĐžĐ˛Đ°ĐŗĐ° аб’ĐĩĐēŅ‚Đ°", - "trigger_person_recognized": "Đ Đ°ŅĐŋаСĐŊаĐŊŅ‹ Ņ‡Đ°ĐģавĐĩĐē", - "trigger_person_recognized_description": "ĐĄĐŋŅ€Đ°Ņ†ĐžŅžĐ˛Đ°Đĩ ĐŋҀҋ Ņ€Đ°ŅĐŋаСĐŊаваĐŊĐŊŅ– Ņ‡Đ°ĐģавĐĩĐēа", - "troubleshoot": "ĐŸĐžŅˆŅƒĐē Ņ– Đ˛Ņ‹ĐŋŅ€Đ°ŅžĐģĐĩĐŊĐŊĐĩ ĐŊĐĩĐŋаĐģадаĐē", + "trash_page_restore_all": "АдĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ҃ҁĐĩ", + "trash_page_title": "ĐĄĐŧĐĩŅ‚ĐŊŅ–Ņ†Đ° ({count})", "type": "ĐĸŅ‹Đŋ", - "unable_to_change_pin_code": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ СĐŧŅĐŊŅ–Ņ†ŅŒ PIN-ĐēОд", - "unable_to_check_version": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ ĐŋŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ вĐĩŅ€ŅŅ–ŅŽ ĐŋŅ€Đ°ĐŗŅ€Đ°ĐŧŅ‹ айО ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "unable_to_setup_pin_code": "НĐĩ Đ°Ņ‚Ņ€Ņ‹ĐŧаĐģĐ°ŅŅ ĐŊаĐģĐ°Đ´ĐˇŅ–Ņ†ŅŒ PIN-ĐēОд", - "unarchive": "Đ’Ņ‹ĐŊŅŅ†ŅŒ С Đ°Ņ€Ņ…Ņ–Đ˛Đ°", - "unarchive_action_prompt": "{count} Đ˛Ņ‹ĐŊŅŅ‚Đ° С Đ°Ņ€Ņ…Ņ–Đ˛Đ°", - "unarchived_count": "{count, plural, other {Đ’Ņ‹ĐŊŅŅ‚Đ° С Đ°Ņ€Ņ…Ņ–Đ˛Đ° #}}", "undo": "ĐĐ´Ņ€Đ°ĐąŅ–Ņ†ŅŒ", - "unfavorite": "Đ’Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ С Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°", - "unfavorite_action_prompt": "{count} Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊа С Đ°ĐąŅ€Đ°ĐŊĐ°ĐŗĐ°", - "unhide_person": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ Ņ‡Đ°ĐģавĐĩĐēа", - "unknown": "НĐĩĐ˛ŅĐ´ĐžĐŧа", - "unknown_country": "НĐĩĐ˛ŅĐ´ĐžĐŧĐ°Ņ ĐēŅ€Đ°Ņ–ĐŊа", - "unknown_date": "НĐĩĐ˛ŅĐ´ĐžĐŧĐ°Ņ Đ´Đ°Ņ‚Đ°", - "unknown_schema": "НĐĩĐ˛ŅĐ´ĐžĐŧĐ°Ņ ҁ҅ĐĩĐŧа", - "unknown_year": "НĐĩĐ˛ŅĐ´ĐžĐŧŅ‹ ĐŗĐžĐ´", - "unlimited": "БĐĩС айĐŧĐĩĐļаваĐŊĐŊŅŅž", - "unlink_motion_video": "ĐĐ´Đ˛ŅĐˇĐ°Ņ†ŅŒ Ņ€ŅƒŅ…ĐžĐŧаĐĩ Đ˛Ņ–Đ´ŅĐ°", - "unlink_oauth": "ĐĐ´Đ˛ŅĐˇĐ°Ņ†ŅŒ OAuth", - "unlinked_oauth_account": "ĐĐ´Đ˛ŅĐˇĐ°ĐŊŅ‹ ŅžĐģŅ–ĐēĐžĐ˛Ņ‹ СаĐŋҖҁ OAuth", - "unmute_memories": "ĐŖĐēĐģŅŽŅ‡Ņ‹Ņ†ŅŒ ĐŗŅƒĐē Đ´ĐģŅ ŅžŅĐŋаĐŧŅ–ĐŊĐ°Ņž", - "unnamed_album": "АĐģŅŒĐąĐžĐŧ ĐąĐĩС ĐŊĐ°ĐˇĐ˛Ņ‹", - "unnamed_album_delete_confirmation": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ аĐģŅŒĐąĐžĐŧ?", - "unnamed_share": "ĐĐąĐ°ĐŗŅƒĐģŅŒĐ˛Đ°ĐŊĐŊĐĩ ĐąĐĩС ĐŊĐ°ĐˇĐ˛Ņ‹", - "unsaved_change": "НĐĩĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊаĐĩ СĐŧŅĐŊĐĩĐŊĐŊĐĩ", - "unselect_all": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ĐąĐ°Ņ€ ŅƒŅŅ–Ņ…", - "unselect_all_duplicates": "ĐĄĐēĐ°ŅĐ°Đ˛Đ°Ņ†ŅŒ Đ˛Ņ‹ĐąĐ°Ņ€ ŅƒŅŅ–Ņ… Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž", - "unstack": "Đ Đ°ĐˇĐ´ĐˇŅĐģŅ–Ņ†ŅŒ ŅŅ‚ĐžŅ", - "unstack_action_prompt": "{count} Đ˛Ņ‹ĐŊŅŅ‚Đ° ŅĐ° ŅŅ‚ĐžŅĐ°", - "unstacked_assets_count": "{count, plural, one {# аб’ĐĩĐēŅ‚ Đ˛Ņ‹ĐŊŅŅ‚Ņ‹} few {# аб’ĐĩĐē҂ҋ Đ˛Ņ‹ĐŊŅŅ‚Ņ‹} many {# аб’ĐĩĐēŅ‚Đ°Ņž Đ˛Ņ‹ĐŊŅŅ‚Đ°} other {# аб’ĐĩĐēŅ‚Đ° Đ˛Ņ‹ĐŊŅŅ‚Đ°}} ŅĐ° ŅŅ‚ĐžŅĐ°", - "unsupported_field_type": "ĐĸŅ‹Đŋ ĐŋĐžĐģŅ ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ°", - "unsupported_file_type": "ФаКĐģ {file} ĐŊĐĩĐģŅŒĐŗĐ° СаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ, йО ҂ҋĐŋ Ņ„Đ°ĐšĐģа {type} ĐŊĐĩ ĐŋĐ°Đ´Ņ‚Ņ€Ņ‹ĐŧĐģŅ–Đ˛Đ°ĐĩŅ†Ņ†Đ°.", - "untagged": "БĐĩС Ņ‚ŅĐŗĐ°", - "up_next": "ДаĐģĐĩĐš", - "update_location_action_prompt": "АйĐŊĐ°Đ˛Ņ–Ņ†ŅŒ ĐŧĐĩŅŅ†Đ°ĐˇĐŊĐ°Ņ…ĐžĐ´ĐļаĐŊĐŊĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊҋ҅ аб’ĐĩĐēŅ‚Đ°Ņž ({count}) ĐŊа:", - "updated_at": "АйĐŊĐžŅžĐģĐĩĐŊŅ‹", - "updated_password": "ĐŸĐ°Ņ€ĐžĐģҌ айĐŊĐžŅžĐģĐĩĐŊŅ‹", "upload": "ЗаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ", - "upload_concurrency": "ĐŸĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊĐ°ŅŅ†ŅŒ СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "upload_day_count": "{date}: {count, plural, one {# СаĐŋаĐŧĐŋĐžŅžĐēа} few {# СаĐŋаĐŧĐŋĐžŅžĐēŅ–} many {# СаĐŋаĐŧĐŋОваĐē} other {# СаĐŋаĐŧĐŋĐžŅžĐēŅ–}}", - "upload_details": "ĐŸĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ– СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ", - "upload_error_with_count": "ПаĐŧŅ‹ĐģĐēа СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ Đ´ĐģŅ {count, plural, one {# аб’ĐĩĐēŅ‚Đ°} few {# аб’ĐĩĐēŅ‚Đ°Ņž} many {# аб’ĐĩĐēŅ‚Đ°Ņž} other {# аб’ĐĩĐēŅ‚Đ°}}", - "upload_errors": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ СавĐĩŅ€ŅˆĐ°ĐŊа С {count, plural, one {# ĐŋаĐŧŅ‹ĐģĐēаК} few {# ĐŋаĐŧŅ‹ĐģĐēаĐŧŅ–} many {# ĐŋаĐŧŅ‹ĐģĐēаĐŧŅ–} other {# ĐŋаĐŧŅ‹ĐģĐēаК}}, айĐŊĐ°Đ˛Ņ–Ņ†Đĩ ŅŅ‚Đ°Ņ€ĐžĐŊĐē҃, Đēай ŅƒĐąĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ СаĐŋаĐŧĐŋаваĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ.", - "upload_finished": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ СавĐĩŅ€ŅˆĐ°ĐŊа", - "upload_progress": "Đ—Đ°ŅŅ‚Đ°ĐģĐžŅŅ {remaining, number} — аĐŋŅ€Đ°Ņ†Đ°Đ˛Đ°ĐŊа {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {ĐŸŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊŅ‹ # Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚} few {ĐŸŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊŅ‹ # Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹} many {ĐŸŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊа # Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Ņž} other {ĐŸŅ€Đ°ĐŋŅƒŅˆŅ‡Đ°ĐŊа # Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°}}", - "upload_status_duplicates": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ‹", "upload_status_errors": "ПаĐŧŅ‹ĐģĐēŅ–", - "upload_status_uploaded": "ЗаĐŋаĐŧĐŋаваĐŊа", - "upload_success": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ŅžĐ´Đ°ĐģаĐĩ, айĐŊĐ°Đ˛Ņ–Ņ†Đĩ ŅŅ‚Đ°Ņ€ĐžĐŊĐē҃, Đēай ŅƒĐąĐ°Ņ‡Ņ‹Ņ†ŅŒ ĐŊĐžĐ˛Ņ‹Ņ СаĐŋаĐŧĐŋаваĐŊŅ‹Ņ аб’ĐĩĐē҂ҋ.", - "upload_to_album_body": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ–, ŅĐēŅ–Ņ ĐŊĐĩ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ŅŽŅ†Ņ†Đ° Ņ„ŅƒĐŊĐēŅ†Ņ‹ŅĐš Ņ€ŅƒŅ‡ĐŊĐžĐŗĐ° СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ, Ņ†ŅĐŋĐĩŅ€ ĐŧĐžĐŗŅƒŅ†ŅŒ Đ´Đ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ ĐģаĐēаĐģҌĐŊŅ‹Ņ Ņ„ĐžŅ‚Đ° ĐŊĐĩĐŋĐ°ŅŅ€ŅĐ´ĐŊа Ņž аĐģŅŒĐąĐžĐŧ ĐŋĐ°Đ´Ņ‡Đ°Ņ Ņ–Ņ… СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊŅ. БоĐģҌ҈ ĐŊĐĩ Ņ‚Ņ€ŅĐąĐ° СаĐŋаĐŧĐŋĐžŅžĐ˛Đ°Ņ†ŅŒ Ņ–Ņ… Ņ– Đ´Đ°Đ´Đ°Đ˛Đ°Ņ†ŅŒ ҃ аĐģŅŒĐąĐžĐŧ ĐŋаСĐŊĐĩĐš.", - "upload_to_album_title": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°Ņ†ŅŒ Đ°Đ´Ņ€Đ°ĐˇŅƒ Ņž аĐģŅŒĐąĐžĐŧ", - "upload_to_immich": "ЗаĐŋаĐŧĐŋĐ°Đ˛Đ°Ņ†ŅŒ ҃ Immich ({count})", "uploading": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ", - "uploading_media": "ЗаĐŋаĐŧĐŋĐžŅžĐ˛Đ°ĐŊĐŊĐĩ ĐŧĐĩĐ´Ņ‹Ņ", - "uploads": "ЗаĐŋаĐŧĐŋĐžŅžĐēŅ–", - "uploads_count": "{count, plural, one {# СаĐŋаĐŧĐŋĐžŅžĐēа} few {# СаĐŋаĐŧĐŋĐžŅžĐēŅ–} many {# СаĐŋаĐŧĐŋОваĐē} other {# СаĐŋаĐŧĐŋĐžŅžĐēŅ–}}", "url": "URL-Đ°Đ´Ņ€Đ°Ņ", - "usage": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊĐĩ", - "use_biometric": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐąŅ–ŅĐŧĐĩŅ‚Ņ€Ņ‹ŅŽ", - "use_browser_locale": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐģаĐēаĐģҌ ĐąŅ€Đ°ŅžĐˇĐĩŅ€Đ°", - "use_browser_locale_description": "Đ¤Đ°Ņ€ĐŧĐ°Ņ†Ņ–Ņ€Đ°Đ˛Đ°Ņ†ŅŒ Đ´Đ°Ņ‚Ņ‹, Ņ‡Đ°Ņ Ņ– ĐģŅ–Ņ‡ĐąŅ‹ ĐŋавОдĐģĐĩ ĐģаĐēаĐģŅ– ĐąŅ€Đ°ŅžĐˇĐĩŅ€Đ°", - "use_current_connection": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐąŅĐŗŅƒŅ‡Đ°Đĩ СĐģŅƒŅ‡ŅĐŊĐŊĐĩ", - "use_custom_date_range": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊҖ҆ĐēŅ– Đ´Ņ‹ŅĐŋаСОĐŊ Đ´Đ°Ņ‚", - "use_template": "Đ’Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Ņ†ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ", "user": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē", "user_has_been_deleted": "Đ“ŅŅ‚Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē ĐąŅ‹Ņž Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹.", "user_id": "ID ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", - "user_liked": "{user} ҁĐŋадайаĐģĐ°ŅŅ {type, select, photo {ĐŗŅŅ‚Đ°Đĩ Ņ„ĐžŅ‚Đ°} video {ĐŗŅŅ‚Đ°Đĩ Đ˛Ņ–Đ´ŅĐ°} asset {ĐŗŅŅ‚Ņ‹ аб’ĐĩĐēŅ‚} other {ĐŗŅŅ‚Đ°}}", - "user_pin_code_settings": "PIN-ĐēОд", - "user_pin_code_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ PIN-ĐēОдаĐŧ", - "user_privacy": "ĐŸŅ€Ņ‹Đ˛Đ°Ņ‚ĐŊĐ°ŅŅ†ŅŒ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "user_purchase_settings": "ĐšŅƒĐŋĐģŅ", - "user_purchase_settings_description": "ĐšŅ–Ņ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŋаĐē҃ĐŋĐēаĐŧŅ–", + "user_purchase_settings_description": "ĐšŅ–Ņ€ŅƒĐšŅ†Đĩ ĐŋаĐē҃ĐŋĐēаĐŧŅ–", + "user_role_set": "ĐŸŅ€Ņ‹ĐˇĐŊĐ°Ņ‡Ņ‹Ņ†ŅŒ {user} ŅĐē {role}", "user_usage_detail": "ĐŸĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†Ņ– Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēаĐŧ", "user_usage_stats": "ĐĄŅ‚Đ°Ņ‚Ņ‹ŅŅ‚Ņ‹Đēа ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ ŅžĐģŅ–ĐēĐžĐ˛Đ°ĐŗĐ° СаĐŋŅ–ŅŅƒ", "user_usage_stats_description": "ĐŸŅ€Đ°ĐŗĐģĐĩдСĐĩŅ†ŅŒ ŅŅ‚Đ°Ņ‚Ņ‹ŅŅ‚Ņ‹Đē҃ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐŊĐŊŅ ŅžĐģŅ–ĐēĐžĐ˛Đ°ĐŗĐ° СаĐŋŅ–ŅŅƒ", "username": "ІĐŧŅ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа", "users": "ĐšĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ–", - "users_added_to_album_count": "{count, plural, one {ДададзĐĩĐŊŅ‹ # ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đē} few {ДададзĐĩĐŊŅ‹ # ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēŅ–} many {ДададзĐĩĐŊа # ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž} other {ДададзĐĩĐŊа # ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–Đēа}} ҃ аĐģŅŒĐąĐžĐŧ", "utilities": "ĐŖŅ‚Ņ‹ĐģҖ҂ҋ", "validate": "ĐŸŅ€Đ°Đ˛ĐĩŅ€Ņ‹Ņ†ŅŒ", - "validate_endpoint_error": "ĐŖĐ˛ŅĐ´ĐˇŅ–Ņ†Đĩ ŅĐ°ĐŋŅ€Đ°ŅžĐ´ĐŊŅ‹ URL", "variables": "ПĐĩŅ€Đ°ĐŧĐĩĐŊĐŊŅ‹Ņ", "version": "ВĐĩŅ€ŅŅ–Ņ", "version_announcement_closing": "ĐĸвОК ŅŅĐąĐ°Ņ€, АĐģĐĩĐēҁ", - "version_announcement_message": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ! Đ”Đ°ŅŅ‚ŅƒĐŋĐŊа ĐŊĐžĐ˛Đ°Ņ вĐĩŅ€ŅŅ–Ņ Immich. ĐŸŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°ĐšŅ†Đĩ ĐŊĐ°Ņ‚Đ°Ņ‚ĐēŅ– да Đ˛Ņ‹Đŋ҃ҁĐē҃, Đēай ĐŋĐĩŅ€Đ°ĐēаĐŊĐ°Ņ†Ņ†Đ°, ŅˆŅ‚Đž Đ˛Đ°ŅˆŅ‹ ĐŊаĐģĐ°Đ´Ņ‹ аĐēŅ‚ŅƒĐ°ĐģҌĐŊŅ‹Ņ Ņ– ĐŋаСйĐĩĐŗĐŊŅƒŅ†ŅŒ ĐŧĐ°ĐŗŅ‡Ņ‹Đŧҋ҅ ĐŋаĐŧŅ‹ĐģаĐē ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ–, Đ°ŅĐ°ĐąĐģŅ–Đ˛Đ° ĐēаĐģŅ– Đ˛Ņ‹ Đ˛Ņ‹ĐēĐ°Ņ€Ņ‹ŅŅ‚ĐžŅžĐ˛Đ°Đĩ҆Đĩ WatchTower айО Ņ–ĐŊŅˆŅ‹ ĐŧĐĩŅ…Đ°ĐŊŅ–ĐˇĐŧ Đ´ĐģŅ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊĐ°ĐŗĐ° айĐŊĐ°ŅžĐģĐĩĐŊĐŊŅ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Immich.", + "version_announcement_message": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ! Đ”Đ°ŅŅ‚ŅƒĐŋĐŊĐ°Ņ ĐŊĐžĐ˛Đ°Ņ вĐĩŅ€ŅŅ–Ņ Immich. КаĐģŅ– ĐģĐ°ŅĐēа, СĐŊĐ°ĐšĐ´ĐˇŅ–Ņ†Đĩ Ņ‡Đ°Ņ, Đēай ĐŋŅ€Đ°Ņ‡Ņ‹Ņ‚Đ°Ņ†ŅŒ ĐŊĐ°Ņ‚Đ°Ņ‚ĐēŅ– да Đ˛Ņ‹Đŋ҃ҁĐē҃, Đēай ĐŋĐĩŅ€Đ°ĐēаĐŊĐ°Ņ†Ņ†Đ°, ŅˆŅ‚Đž Đ˛Đ°ŅˆĐ° ĐŊаĐģада аĐēŅ‚ŅƒĐ°ĐģҌĐŊĐ°Ņ Ņ– ĐŋаСйĐĩĐŗĐŊŅƒŅ†ŅŒ ĐŧĐ°ĐŗŅ‡Ņ‹Đŧҋ҅ ĐŋаĐŧŅ‹ĐģаĐē ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹Ņ–, Đ°ŅĐ°ĐąĐģŅ–Đ˛Đ° ĐēаĐģŅ– Đ˛Ņ‹ ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°Đĩ҆ĐĩŅŅ WatchTower айО Ņ–ĐŊŅˆŅ‹ĐŧŅ– ĐŧĐĩŅ…Đ°ĐŊŅ–ĐˇĐŧаĐŧŅ–, ŅĐēŅ–Ņ Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹Ņ‡ĐŊа айĐŊĐ°ŅžĐģŅŅŽŅ†ŅŒ Đ˛Đ°ŅˆŅƒ Ņ–ĐŊŅŅ‚Đ°ĐŊŅ†Ņ‹ŅŽ Immich.", "version_history": "Đ“Ņ–ŅŅ‚ĐžŅ€Ņ‹Ņ вĐĩŅ€ŅŅ–Đš", "version_history_item": "ĐŖŅŅ‚Đ°ĐģŅĐ˛Đ°ĐŊа вĐĩŅ€ŅŅ–Ņ {version} ĐŊа {date}", "video": "Đ’Ņ–Đ´ŅĐ°", "video_hover_setting": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹ Đ˛Ņ–Đ´ŅĐ° ĐŋҀҋ ĐŊĐ°Đ˛ŅĐ´ĐˇĐĩĐŊĐŊŅ– ĐēŅƒŅ€ŅĐžŅ€Đ°", "video_hover_setting_description": "ĐŸŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŧŅ–ĐŊŅ–ŅŅ†ŅŽŅ€Ņ‹ Đ˛Ņ–Đ´ŅĐ° ĐŋҀҋ ĐŊĐ°Đ˛ŅĐ´ĐˇĐĩĐŊĐŊŅ– ĐēŅƒŅ€ŅĐžŅ€Đ° ĐŊа ŅĐģĐĩĐŧĐĩĐŊŅ‚. ĐĐ°Đ˛Đ°Ņ‚ ĐēаĐģŅ– Ņ„ŅƒĐŊĐēŅ†Ņ‹Ņ адĐēĐģŅŽŅ‡Đ°ĐŊа, ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊĐĩ ĐŧĐžĐļĐŊа ĐŋĐ°Ņ‡Đ°Ņ†ŅŒ, ĐŊĐ°Đ˛Ņ‘ŅžŅˆŅ‹ ĐēŅƒŅ€ŅĐžŅ€ ĐŊа СĐŊĐ°Ņ‡ĐžĐē ĐŋŅ€Đ°ĐšĐŗŅ€Đ°Đ˛Đ°ĐŊĐŊŅ.", - "video_quality": "Đ¯ĐēĐ°ŅŅ†ŅŒ Đ˛Ņ–Đ´ŅĐ°", "videos": "Đ’Ņ–Đ´ŅĐ°", "videos_count": "{count, plural, one {# Đ˛Ņ–Đ´ŅĐ°} other {# Đ˛Ņ–Đ´ŅĐ°}}", - "view": "Đ’Ņ‹ĐŗĐģŅĐ´", + "view": "ĐŸŅ€Đ°ĐŗĐģŅĐ´", "view_album": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ аĐģŅŒĐąĐžĐŧ", "view_all": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ŅƒŅŅ‘", "view_all_users": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ҃ҁĐĩŅ… ĐēĐ°Ņ€Ņ‹ŅŅ‚Đ°ĐģҌĐŊŅ–ĐēĐ°Ņž", - "view_asset_owners": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ҃ĐģадаĐģҌĐŊŅ–ĐēĐ°Ņž аб’ĐĩĐēŅ‚Đ°", - "view_details": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ĐŋĐ°Đ´Ņ€Đ°ĐąŅĐˇĐŊĐ°ŅŅ†ĐĩĐš", "view_in_timeline": "ĐŸĐ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ Ņ…Ņ€ĐžĐŊŅ–Đē҃", - "view_link": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", + "view_links": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ҁĐŋĐ°ŅŅ‹ĐģĐēŅ–", "view_name": "ĐŸŅ€Đ°ĐŗĐģŅĐ´", "view_next_asset": "ПаĐēĐ°ĐˇĐ°Ņ†ŅŒ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊŅ‹ ай'ĐĩĐēŅ‚", "view_previous_asset": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ĐŋаĐŋŅŅ€ŅĐ´ĐŊŅ– ай'ĐĩĐēŅ‚", - "view_qr_code": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ QR-ĐēОда", - "view_similar_photos": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ĐŋадОйĐŊҋ҅ Ņ„ĐžŅ‚Đ°", - "view_stack": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ĐˇĐĩŅ†ŅŒ ŅŅ‚ĐžŅ", - "viewer_remove_from_stack": "Đ’Ņ‹ĐŊŅŅ†ŅŒ ŅĐ° ŅŅ‚ĐžŅĐ°", - "visibility": "Đ‘Đ°Ņ‡ĐŊĐ°ŅŅ†ŅŒ", + "view_stack": "ĐŸŅ€Đ°ĐŗĐģŅĐ´ ŅŅ‚ŅĐēа", "visibility_changed": "Đ‘Đ°Ņ‡ĐŊĐ°ŅŅ†ŅŒ СĐŧŅĐŊŅ–ĐģĐ°ŅŅ Đ´ĐģŅ {count, plural, one {# Ņ‡Đ°ĐģавĐĩĐēа} other {# Ņ‡Đ°ĐģавĐĩĐē}}", - "visual": "Đ’Ņ–ĐˇŅƒĐ°ĐģҌĐŊŅ‹", "waiting": "ЧаĐēĐ°ŅŽŅ†ŅŒ", - "waiting_count": "ĐŖ Ņ‡Đ°ĐēаĐŊĐŊŅ–: {count}", "warning": "ПаĐŋŅŅ€ŅĐ´ĐļаĐŊĐŊĐĩ", "week": "ĐĸŅ‹Đ´ĐˇĐĩĐŊҌ", "welcome": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ", "welcome_to_immich": "Đ’Ņ–Ņ‚Đ°ĐĩĐŧ ҃ Immich", - "whats_new": "Đ¨Ņ‚Đž ĐŊĐžĐ˛Đ°ĐŗĐ°", - "whats_new_settings_subtitle": "ĐŸĐ°ĐŗĐģŅĐ´ĐˇŅ–Ņ†Đĩ, ŅˆŅ‚Đž ĐŊĐžĐ˛Đ°ĐŗĐ° Ņž Immich", - "whats_new_version": "ВĐĩŅ€ŅŅ–Ņ {version}", - "when": "КаĐģŅ–", - "width": "Đ¨Ņ‹Ņ€Ņ‹ĐŊŅ", - "wifi_name": "Назва Wi-Fi", - "workflow": "ĐĄŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "workflow_delete_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹Đ´Đ°ĐģŅ–Ņ†ŅŒ ĐŗŅŅ‚Ņ‹ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš?", - "workflow_deleted": "ĐĄŅ†ŅĐŊĐ°Ņ€Ņ‹Đš Đ˛Ņ‹Đ´Đ°ĐģĐĩĐŊŅ‹", - "workflow_description": "АĐŋŅ–ŅĐ°ĐŊĐŊĐĩ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ", - "workflow_info": "ІĐŊŅ„Đ°Ņ€ĐŧĐ°Ņ†Ņ‹Ņ ĐŋŅ€Đ° ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Đš", - "workflow_json": "JSON ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ", - "workflow_json_help": "Đ ŅĐ´Đ°ĐŗŅƒĐšŅ†Đĩ ĐēаĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ‹ŅŽ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ Ņž Ņ„Đ°Ņ€ĐŧĐ°Ņ†Đĩ JSON. ЗĐŧŅĐŊĐĩĐŊĐŊŅ– ҁҖĐŊŅ…Ņ€Đ°ĐŊŅ–ĐˇŅƒŅŽŅ†Ņ†Đ° С Đ˛Ņ–ĐˇŅƒĐ°ĐģҌĐŊŅ‹Đŧ ĐēаĐŊŅŅ‚Ņ€ŅƒĐēŅ‚Đ°Ņ€Đ°Đŧ.", - "workflow_name": "Назва ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ", - "workflow_navigation_prompt": "Đ’Ņ‹ ŅžĐŋŅŅžĐŊĐĩĐŊŅ‹, ŅˆŅ‚Đž Ņ…ĐžŅ‡Đ°Ņ†Đĩ Đ˛Ņ‹ĐšŅŅ†Ņ– ĐąĐĩС ĐˇĐ°Ņ…Đ°Đ˛Đ°ĐŊĐŊŅ СĐŧŅĐŊĐĩĐŊĐŊŅŅž?", - "workflow_summary": "ЗводĐēа ŅŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ", - "workflow_templates": "ШайĐģĐžĐŊŅ‹ ŅŅ†ŅĐŊĐ°Ņ€Ņ‹ŅŅž", - "workflow_update_success": "ĐĄŅ†ŅĐŊĐ°Ņ€Ņ‹Đš ĐŋĐ°ŅĐŋŅŅ…ĐžĐ˛Đ° айĐŊĐžŅžĐģĐĩĐŊŅ‹", - "workflows": "ĐĄŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ–", - "workflows_help_text": "ĐĄŅ†ŅĐŊĐ°Ņ€Ņ‹Ņ– Đ°ŅžŅ‚Đ°ĐŧĐ°Ņ‚Ņ‹ĐˇŅƒŅŽŅ†ŅŒ дСĐĩŅĐŊĐŊŅ– ĐŊад аб’ĐĩĐēŅ‚Đ°ĐŧŅ– ĐŊа ĐŋĐ°Đ´ŅŅ‚Đ°Đ˛Đĩ Ņ‚Ņ€Ņ‹ĐŗĐĩŅ€Đ°Ņž Ņ– ҄ҖĐģŅŒŅ‚Ņ€Đ°Ņž", - "wrong_pin_code": "ĐŅĐŋŅ€Đ°Đ˛Ņ–ĐģҌĐŊŅ‹ PIN-ĐēОд", - "x_of_total": "{x}/{total}", "year": "Год", "years_ago": "{years, plural, one {# ĐŗĐžĐ´} few {# ĐŗĐ°Đ´Ņ‹} many {# ĐŗĐ°Đ´ĐžŅž} other {# ĐŗĐ°Đ´ĐžŅž}} Ņ‚Đ°Đŧ҃", "yes": "ĐĸаĐē", "you_dont_have_any_shared_links": "ĐŖ Đ˛Đ°Ņ ĐŊŅĐŧа Đ°ĐąĐ°ĐŗŅƒĐģĐĩĐŊҋ҅ ҁĐŋĐ°ŅŅ‹ĐģаĐē", - "your_wifi_name": "Назва ҁĐĩŅ‚ĐēŅ– Wi-Fi", - "zero_to_clear_rating": "ĐŊĐ°Ņ†Ņ–ŅĐŊҖ҆Đĩ 0, Đēай ҁĐēŅ–ĐŊŅƒŅ†ŅŒ Đ°Ņ†ŅĐŊĐē҃ аб’ĐĩĐēŅ‚Đ°", "zoom_image": "ĐŸĐ°Đ˛ŅĐģŅ–Ņ‡Ņ‹Ņ†ŅŒ Đ˛Ņ–Đ´Đ°Ņ€Ņ‹Ņ" } diff --git a/i18n/bg.json b/i18n/bg.json index f7336d606c..024ba3502e 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -10,31 +10,39 @@ "active": "АĐēŅ‚Đ¸Đ˛ĐŊи", "active_count": "АĐēŅ‚Đ¸Đ˛ĐŊи: {count}", "activity": "ДĐĩĐšĐŊĐžŅŅ‚", + "activity_changed": "ДĐĩĐšĐŊĐžŅŅ‚Ņ‚Đ° Đĩ {enabled, select, true {вĐēĐģŅŽŅ‡ĐĩĐŊа} other {иСĐēĐģŅŽŅ‡ĐĩĐŊа}}", "add": "Добави", "add_a_description": "Добави ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", "add_a_location": "Добави ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "add_a_name": "Добави иĐŧĐĩ", "add_a_title": "Добaви ĐˇĐ°ĐŗĐģавиĐĩ", "add_action": "Добави Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ", + "add_action_description": "ĐĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ Са да Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚Đĩ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ", "add_assets": "Đ”ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸", "add_birthday": "Добави Đ´Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ", "add_endpoint": "Добави ĐēŅ€Đ°ĐšĐŊа Ņ‚ĐžŅ‡Đēа", "add_exclusion_pattern": "Добави ĐŧОдĐĩĐģ Са иСĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ", + "add_filter": "Добави Ņ„Đ¸ĐģŅ‚ŅŠŅ€", + "add_filter_description": "ĐĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ Са да Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚Đĩ ҃ҁĐģОвиĐĩ Са Ņ„Đ¸ĐģŅ‚ŅŠŅ€", "add_location": "Дoйави ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", + "add_more_users": "Добави ĐžŅ‰Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи", "add_partner": "Добави ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€", + "add_path": "Добави ĐŋŅŠŅ‚", "add_photos": "Добави ҁĐŊиĐŧĐēи", - "add_step": "Добави ŅŅ‚ŅŠĐŋĐēа", "add_tag": "Добави ĐŧĐ°Ņ€ĐēĐĩŅ€", "add_to": "Добави ĐēҊĐŧâ€Ļ", "add_to_album": "Добави ĐēҊĐŧ аĐģĐąŅƒĐŧ", "add_to_album_bottom_sheet_added": "ДобавĐĩĐŊĐž в {album}", "add_to_album_bottom_sheet_already_exists": "ВĐĩ҇Đĩ Đĩ в {album}", "add_to_album_bottom_sheet_some_local_assets": "ĐŅĐēОи ĐģĐžĐēаĐģĐŊи Ņ„Đ°ĐšĐģОвĐĩ ĐŊĐĩ ҃ҁĐŋŅŅ…Đ° да ҁĐĩ Đ´ĐžĐąĐ°Đ˛ŅŅ‚ ĐēҊĐŧ аĐģĐąŅƒĐŧа", + "add_to_album_toggle": "ĐĄĐŧĐĩĐŊĐĩŅ‚Đĩ Đ¸ĐˇĐąĐžŅ€Đ° Са {album}", "add_to_albums": "Đ”ĐžĐąĐ°Đ˛ŅĐŊĐĩ в аĐģĐąŅƒĐŧи", "add_to_albums_count": "Đ”ĐžĐąĐ°Đ˛ŅĐŊĐĩ в аĐģĐąŅƒĐŧи ({count})", "add_to_bottom_bar": "Добави ĐēҊĐŧ", + "add_to_shared_album": "Добави ĐēҊĐŧ ҁĐŋОдĐĩĐģĐĩĐŊ аĐģĐąŅƒĐŧ", "add_upload_to_stack": "Добави ĐēĐ°Ņ‡ĐĩĐŊĐ¸Ņ‚Đĩ в ĐŗŅ€ŅƒĐŋа", "add_url": "Добави URL", + "add_workflow_step": "Добави ŅŅ‚ŅŠĐŋĐēа ĐžŅ‚ Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", "added_to_archive": "ДобавĐĩĐŊĐž ĐēҊĐŧ Đ°Ņ€Ņ…Đ¸Đ˛Đ°", "added_to_favorites": "ДобавĐĩĐŊи ĐēҊĐŧ ĐģŅŽĐąĐ¸ĐŧĐ¸Ņ‚Đĩ ви", "added_to_favorites_count": "ДобавĐĩĐŊи {count, number} ĐēҊĐŧ ĐģŅŽĐąĐ¸Đŧи", @@ -73,7 +81,6 @@ "cron_expression_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐš иĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģа ĐŊа ҁĐēаĐŊĐ¸Ņ€Đ°ĐŊĐĩ иСĐŋĐžĐģСваКĐēи cron Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đ°. За ĐŋОвĐĩ҇Đĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Crontab Guru", "cron_expression_presets": "ĐŸŅ€Đ¸ĐŧĐĩŅ€ĐŊи Cron Đ¸ĐˇŅ€Đ°ĐˇĐ¸", "disable_login": "ИСĐēĐģŅŽŅ‡ĐĩŅ‚Đĩ вĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩŅ‚Đž", - "download_csv": "Đ˜ĐˇŅ‚ĐĩĐŗĐģи CSV", "duplicate_detection_job_description": "ĐĄŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ Đ˛ŅŠŅ€Ņ…Ņƒ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, Са ĐžŅ‚ĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋОдОйĐŊи Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ. Đ Đ°ĐˇŅ‡Đ¸Ņ‚Đ° ĐŊа ИĐŊŅ‚ĐĩĐģĐ¸ĐŗĐĩĐŊŅ‚ĐŊĐž ĐĸŅŠŅ€ŅĐĩĐŊĐĩ", "exclusion_pattern_description": "МодĐĩĐģи Са иСĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŋОСвОĐģŅĐ˛Đ°Ņ‚ да Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ°Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ и ĐŋаĐŋĐēи, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐēаĐŊĐ¸Ņ€Đ°Ņ‚Đĩ Đ˛Đ°ŅˆĐ°Ņ‚Đ° йийĐģĐ¸ĐžŅ‚ĐĩĐēа. ĐĸОва Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐŊĐž, аĐēĐž иĐŧĐ°Ņ‚Đĩ ĐŋаĐŋĐēи, ĐēĐžĐ¸Ņ‚Đž ŅŅŠĐ´ŅŠŅ€ĐļĐ°Ņ‚ Ņ„Đ°ĐšĐģОвĐĩ, ĐēĐžĐ¸Ņ‚Đž ĐŊĐĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°Ņ‚Đĩ. ĐŸŅ€Đ¸ĐŧĐĩŅ€ĐŊĐž - RAW Ņ„Đ°ĐšĐģОвĐĩ.", "export_config_as_json_description": "ЗаĐŋаСи Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ‚Đ° ŅĐ¸ŅŅ‚ĐĩĐŧĐŊа ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ ĐēĐ°Ņ‚Đž JSON Ņ„Đ°ĐšĐģ", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊа ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ°Ņ‚Đ° ĐžŅ‚ 1 Đ´Đž 100. По-Đ˛Đ¸ŅĐžĐēĐ°Ņ‚Đ° ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ Đĩ ĐŋĐž-Đ´ĐžĐąŅ€Đ°, ĐŊĐž вОди Đ´Đž ĐŋĐž-ĐŗĐžĐģĐĩĐŧи Ņ„Đ°ĐšĐģОвĐĩ и ĐŧĐžĐļĐĩ да ĐŊаĐŧаĐģи ĐąŅŠŅ€ĐˇĐžĐ´ĐĩĐšŅŅ‚Đ˛Đ¸ĐĩŅ‚Đž ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž.", "image_thumbnail_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸Ņ‚Đĩ", "import_config_from_json_description": "ИĐŧĐŋĐžŅ€Ņ‚ ĐŊа ŅĐ¸ŅŅ‚ĐĩĐŧĐŊа ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ ҇ҀĐĩС ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ ĐŊа JSON Ņ„Đ°ĐšĐģ", - "integrity_checks_checksum_files": "ФаКĐģОвĐĩ ҁ ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊи ҁ҃Đŧи", - "integrity_checks_checksum_files_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° ҁ ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊа ҁ҃Đŧа", - "integrity_checks_checksum_files_enable_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° ҁ ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊа ҁ҃Đŧа", - "integrity_checks_checksum_files_percentage_limit": "ĐŸŅ€ĐžŅ†ĐĩĐŊŅ‚ĐŊĐž ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ", - "integrity_checks_checksum_files_percentage_limit_description": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸Ņ€Đ°ĐšŅ‚Đĩ ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†ĐĩĐŊŅ‚ ĐŧĐĩĐļĐ´Ņƒ 0.01 и 1 Са Ņ‚ĐžĐ˛Đ° ĐēĐžĐģĐēĐž ҇ĐĩŅŅ‚Đž Ņ‚Ņ€ŅĐąĐ˛Đ° да ҁĐĩ иСĐŋҊĐģĐŊŅĐ˛Đ° ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° ĐŊа ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊĐ°Ņ‚Đ° ҁ҃Đŧа ĐŋŅ€ĐĩС Đ˛ŅĐĩĐēи иĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ.", - "integrity_checks_checksum_files_time_limit": "ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ ĐŊа Đ˛Ņ€ĐĩĐŧĐĩŅ‚Đž", - "integrity_checks_checksum_files_time_limit_description": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸Ņ€Đ°ĐšŅ‚Đĩ ĐŧаĐēŅĐ¸ĐŧаĐģĐŊĐ°Ņ‚Đ° ĐŋŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚, Са ĐēĐžŅŅ‚Đž Ņ‚Ņ€ŅĐąĐ˛Đ° да ҁĐĩ иСĐŋҊĐģĐŊŅĐ˛Đ° ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊĐ°Ņ‚Đ° ҁ҃Đŧа Са Đ˛ŅĐĩĐēи иĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ. (ms)", - "integrity_checks_missing_files": "ЛиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвe", - "integrity_checks_missing_files_description": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸Ņ€Đ°ĐšŅ‚Đĩ ҇ĐĩŅŅ‚ĐžŅ‚Đ°Ņ‚Đ° и аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ иĐģи Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Са ĐģиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ", - "integrity_checks_missing_files_enable_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Са ĐģиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ", - "integrity_checks_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа Са Ņ†ŅĐģĐžŅŅ‚", - "integrity_checks_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ°ĐšŅ‚Đĩ иĐŊŅ‚ĐĩŅ€Đ˛Đ°ĐģĐ¸Ņ‚Đĩ Са ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа Ņ†ŅĐģĐžŅŅ‚", - "integrity_checks_untracked_files": "НĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ŅĐ˛Đ°ĐŊи Ņ„Đ°ĐšĐģОвĐĩ", - "integrity_checks_untracked_files_description": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸Ņ€Đ°ĐšŅ‚Đĩ ҇ĐĩŅŅ‚ĐžŅ‚Đ°Ņ‚Đ° и аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ иĐģи Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐšŅ‚Đĩ ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Са ĐŊĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ŅĐ˛Đ°ĐŊи Ņ„Đ°ĐšĐģОвĐĩ", - "integrity_checks_untracked_files_enable_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Са ĐŊĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ŅĐ˛Đ°ĐŊи Ņ„Đ°ĐšĐģОвĐĩ", "job_concurrency": "ĐŸĐ°Ņ€Đ°ĐģĐĩĐģĐŊĐžŅŅ‚ ĐŊа {job}", "job_created": "Đ—Đ°Đ´Đ°Ņ‡Đ°Ņ‚Đ° Đĩ ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊа", "job_not_concurrency_safe": "ĐĸаСи ĐˇĐ°Đ´Đ°Ņ‡Đ° ĐŊĐĩ Đĩ ĐąĐĩСОĐŋĐ°ŅĐŊа Са ĐŋĐ°Ņ€Đ°ĐģĐĩĐģĐŊĐž иСĐŋҊĐģĐŊĐĩĐŊиĐĩ.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ИĐŊŅ‚ĐĩĐģĐ¸ĐŗĐĩĐŊŅ‚ĐŊĐž ĐĸŅŠŅ€ŅĐĩĐŊĐĩ", "machine_learning_smart_search_enabled_description": "АĐēĐž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž, Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸ŅŅ‚Đ° ĐŊŅĐŧа да ĐąŅŠĐ´Đ°Ņ‚ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи Са ИĐŊŅ‚ĐĩĐģĐ¸ĐŗĐĩĐŊŅ‚ĐŊĐž ĐĸŅŠŅ€ŅĐĩĐŊĐĩ.", "machine_learning_url_description": "URL ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° Са ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐž ĐžĐąŅƒŅ‡ĐĩĐŊиĐĩ. АĐēĐž ŅĐ° ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ĐĩĐŊи ĐŋОвĐĩ҇Đĩ ĐžŅ‚ ĐĩдиĐŊ URL, Đ˛ŅĐĩĐēи ŅŅŠŅ€Đ˛ŅŠŅ€ ҉Đĩ ĐąŅŠĐ´Đĩ ĐžĐŋĐ¸Ņ‚Đ°ĐŊ ĐĩдиĐŊ ĐŋĐž ĐĩдиĐŊ, Đ´ĐžĐēĐ°Ņ‚Đž ĐĩдиĐŊ ĐžŅ‚ĐŗĐžĐ˛ĐžŅ€Đ¸ ҃ҁĐŋĐĩ҈ĐŊĐž, в Ņ€Đĩда ĐžŅ‚ ĐŋŅŠŅ€Đ˛Đ¸Ņ Đ´Đž ĐŋĐžŅĐģĐĩĐ´ĐŊĐ¸Ņ. ĐĄŅŠŅ€Đ˛ŅŠŅ€Đ¸, ĐēĐžĐ¸Ņ‚Đž ĐŊĐĩ ĐžŅ‚ĐŗĐžĐ˛ĐžŅ€ŅŅ‚, ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐž Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ°ĐŊи, Đ´ĐžĐēĐ°Ņ‚Đž ĐŊĐĩ ҁĐĩ Đ˛ŅŠŅ€ĐŊĐ°Ņ‚ ĐžĐŊĐģаКĐŊ.", - "maintenance_backup_management": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž", "maintenance_delete_backup": "Đ˜ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛", "maintenance_delete_backup_description": "ĐĸОСи Ņ„Đ°ĐšĐģ ҉Đĩ ĐąŅŠĐ´Đĩ ĐąĐĩĐˇĐ˛ŅŠĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚.", "maintenance_delete_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛.", - "maintenance_integrity_check": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа", - "maintenance_integrity_check_all": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи", - "maintenance_integrity_checksum_mismatch": "НĐĩŅŅŠĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ¸Đĩ ĐŊа ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊĐ°Ņ‚Đ° ҁ҃Đŧа", - "maintenance_integrity_checksum_mismatch_description": "ФаКĐģОвĐĩ, Ņ‡Đ¸ŅŅ‚Đž ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊа ҁ҃Đŧа ĐŊĐĩ ŅŅŠĐ˛Đŋада ҁҊҁ СаĐŋаСĐĩĐŊĐ°Ņ‚Đ° в ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи ĐŊа Immich.", - "maintenance_integrity_checksum_mismatch_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊĐ¸Ņ‚Đĩ ҁ҃Đŧи", - "maintenance_integrity_checksum_mismatch_refresh_job": "ОбĐŊОви Đ´ĐžĐēĐģĐ°Đ´Đ¸Ņ‚Đĩ Са ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа ĐēĐžĐŊŅ‚ĐžŅ€ĐģĐŊĐ¸Ņ‚Đĩ ҁ҃Đŧи", - "maintenance_integrity_missing_file": "ЛиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ", - "maintenance_integrity_missing_file_description": "ФаКĐģОвĐĩ, ĐēĐžĐ¸Ņ‚Đž Immich ҁĐģĐĩди в ĐąĐ°ĐˇĐ°Ņ‚Đ° ŅĐ¸ даĐŊĐŊи, ĐŊĐž ĐŊĐĩ ŅĐ° ĐŊаĐģĐ¸Ņ‡ĐŊи Đ˛ŅŠĐ˛ Ņ„Đ°ĐšĐģĐžĐ˛Đ°Ņ‚Đ° ŅĐ¸ŅŅ‚ĐĩĐŧа.", - "maintenance_integrity_missing_file_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа Са ĐģиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ", - "maintenance_integrity_missing_file_refresh_job": "ОбĐŊОви Đ´ĐžĐēĐģĐ°Đ´Đ¸Ņ‚Đĩ Са ĐģиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ", - "maintenance_integrity_report": "ĐžŅ‚Ņ‡ĐĩŅ‚ Са ĐŊĐĩĐŋĐžĐēŅŠŅ‚ĐŊĐ°Ņ‚ĐžŅŅ‚", - "maintenance_integrity_untracked_file": "НĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ĐĩĐŊи Ņ„Đ°ĐšĐģОвĐĩ", - "maintenance_integrity_untracked_file_description": "ФаКĐģОвĐĩ в ĐŋаĐŋĐēĐ¸Ņ‚Đĩ ĐŊа Immich, Са ĐēĐžĐ¸Ņ‚Đž Immich ĐŊŅĐŧа ĐŊиĐēаĐēви СаĐŋĐ¸ŅĐ¸.", - "maintenance_integrity_untracked_file_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа Са ĐŊĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ĐĩĐŊи Ņ„Đ°ĐšĐģОвĐĩ", - "maintenance_integrity_untracked_file_refresh_job": "ОбĐŊОви Đ´ĐžĐēĐģĐ°Đ´Đ¸Ņ‚Đĩ Са ĐŊĐĩĐŋŅ€ĐžŅĐģĐĩĐ´ĐĩĐŊи Ņ„Đ°ĐšĐģОвĐĩ", "maintenance_restore_backup": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛", "maintenance_restore_backup_description": "Immich ҉Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Đĩ Đ˛ŅĐ¸Ņ‡Đēи Ņ‚ĐĩĐēŅƒŅ‰Đ¸ даĐŊĐŊи и ĐŋĐžŅĐģĐĩ ҉Đĩ Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОви даĐŊĐŊĐ¸Ņ‚Đĩ ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ Đ°Ņ€Ņ…Đ¸Đ˛. ĐŸŅŠŅ€Đ˛Đž ҉Đĩ ĐŊаĐŋŅ€Đ°Đ˛Đ¸ ĐŊОв Đ°Ņ€Ņ…Đ¸Đ˛.", "maintenance_restore_backup_different_version": "ĐĸОСи Đ°Ņ€Ņ…Đ¸Đ˛ Đĩ ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ҁ Ņ€Đ°ĐˇĐģĐ¸Ņ‡ĐŊа вĐĩŅ€ŅĐ¸Ņ ĐŊа Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ВĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа иĐŧĐĩĐšĐģ иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", "notification_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", "notification_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ Са иСвĐĩŅŅ‚Đ¸Ņ, вĐēĐģ. иĐŧĐĩĐšĐģ", - "oauth_allow_insecure_requests": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐŊĐĩŅĐ¸ĐŗŅƒŅ€ĐŊи ĐˇĐ°ŅĐ˛Đēи", - "oauth_allow_insecure_requests_description": "ĐŸĐ Đ•Đ”ĐŖĐŸĐ Đ•Đ–Đ”Đ•ĐĐ˜Đ•: ĐĸОва иСĐēĐģŅŽŅ‡Đ˛Đ° ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° Са ваĐģидĐŊĐžŅŅ‚ ĐŊа TLS ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° ĐŋŅ€Đ¸ OAuth ĐˇĐ°ŅĐ˛Đēи и ĐžŅ‚Đ˛Đ°Ņ€Ņ Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐžŅŅ‚ Са Đ°Ņ‚Đ°Đēа ĐžŅ‚ Ņ‚Đ¸Đŋа \"Ņ‡ĐžĐ˛ĐĩĐē ĐŋĐž ҁҀĐĩĐ´Đ°Ņ‚Đ°\".", "oauth_auto_launch": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ", "oauth_auto_launch_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛Ņ…ĐžĐ´ ҇ҀĐĩС OAuth, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐĩ ĐžŅ‚Đ˛ĐžŅ€Đ¸ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°Ņ‚Đ° Са Đ˛Ņ…ĐžĐ´", "oauth_auto_register": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊа Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ", @@ -300,11 +274,9 @@ "oauth_button_text": "ĐĸĐĩĐēҁ҂ ĐŊа ĐąŅƒŅ‚ĐžĐŊа", "oauth_client_secret_description": "Đ—Đ°Đ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐž Са ĐŋОвĐĩŅ€Đ¸Ņ‚ĐĩĐģĐĩĐŊ ĐēĐģиĐĩĐŊŅ‚ иĐģи ĐēĐžĐŗĐ°Ņ‚Đž ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа PKCE (Proof Key for Code Exchange) Са ĐŋŅƒĐąĐģĐ¸Ņ‡ĐĩĐŊ ĐēĐģиĐĩĐŊŅ‚.", "oauth_enable_description": "ВĐģиСаĐŊĐĩ ҁ OAuth", - "oauth_end_session_url_description": "ĐŸŅ€ĐĩĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ ĐēҊĐŧ Ņ‚ĐžĐˇĐ¸ URI Đ°Đ´Ņ€Đĩҁ, ĐēĐžĐŗĐ°Ņ‚Đž иСĐģĐĩСĐĩ ĐžŅ‚ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°.", "oauth_mobile_redirect_uri": "URI Са ĐŧОйиĐģĐŊĐž ĐŋŅ€ĐĩĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ", "oauth_mobile_redirect_uri_override": "URI ĐŋŅ€ĐĩĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ Са ĐŧОйиĐģĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "oauth_mobile_redirect_uri_override_description": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ ĐēĐžĐŗĐ°Ņ‚Đž Đ´ĐžŅŅ‚Đ°Đ˛Ņ‡Đ¸Đēа Са OAuth ŅƒĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€ŅĐ˛Đ°ĐŊĐĩ ĐŊĐĩ ĐŋОСвОĐģŅĐ˛Đ° Са ĐŧОйиĐģĐŊи URI идĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ĐžŅ€Đ¸, ĐēĐ°Ņ‚Đž ''{callback}''", - "oauth_prompt_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩŅ‚ŅŠŅ€ Са ĐŋОдĐēаĐŊа (ĐŊаĐŋŅ€. select_account, login, consent)", "oauth_role_claim": "ĐŸĐžŅ‚Đ˛ŅŠŅ€ĐļĐ´ĐĩĐŊиĐĩ ĐŊа Ņ€ĐžĐģŅ", "oauth_role_claim_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ŅĐŊĐĩ ĐŊа адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚Đ¸Đ˛ĐŊи ĐŋŅ€Đ°Đ˛Đ° ĐŋŅ€Đ¸ ĐŊаĐģĐ¸Ņ‡Đ¸Đĩ ĐŊа Ņ‚ĐžĐ˛Đ° ĐŋĐžŅ‚Đ˛ŅŠŅ€ĐļĐĩĐŊиĐĩ. ĐŸĐžŅ‚Đ˛ŅŠŅ€ĐļĐ´ĐĩĐŊиĐĩŅ‚Đž ĐŧĐžĐļĐĩ да иĐŧа ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ 'user' иĐģи 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "ОĐŋŅ€ĐĩҁĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "registration": "АдĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ŅĐēа Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ", "registration_description": "ĐĸŅŠĐš ĐēĐ°Ņ‚Đž ҁ҂Đĩ ĐŋŅŠŅ€Đ˛Đ¸ŅŅ‚ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ в ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°, ҉Đĩ ĐąŅŠĐ´ĐĩŅ‚Đĩ ĐŊаСĐŊĐ°Ņ‡ĐĩĐŊ ĐēĐ°Ņ‚Đž адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ и ҉Đĩ ĐžŅ‚ĐŗĐžĐ˛Đ°Ņ€ŅŅ‚Đĩ Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚Đ¸Đ˛ĐŊĐ¸Ņ‚Đĩ ĐˇĐ°Đ´Đ°Ņ‡Đ¸, а Đ´ĐžĐŋҊĐģĐŊĐ¸Ņ‚ĐĩĐģĐŊĐ¸Ņ‚Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊи ĐžŅ‚ Đ˛Đ°Ņ.", - "release_channel_release_candidate": "ĐŸŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊа вĐĩŅ€ŅĐ¸Ņ", - "release_channel_stable": "ĐĄŅ‚Đ°ĐąĐ¸ĐģĐŊа вĐĩŅ€ŅĐ¸Ņ", "remove_failed_jobs": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊи ĐˇĐ°Đ´Đ°Ņ‡Đ¸", "require_password_change_on_login": "Đ˜ĐˇĐ¸ŅĐēваĐŊĐĩ Са ĐŋŅ€ĐžĐŧŅĐŊа ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ° ĐŋŅ€Đ¸ ĐŋŅŠŅ€Đ˛Đž вĐģиСаĐŊĐĩ", "reset_settings_to_default": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐ° ҁĐĩ ŅĐ°ĐŧĐž Са VAAPI и QSV. Задава dri Đ˛ŅŠĐˇĐĩĐģа, иСĐŋĐžĐģСваĐŊ Са Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ.", "transcoding_preset_preset": "ĐŸŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐž СададĐĩĐŊи(-preset)", "transcoding_preset_preset_description": "ĐĄĐēĐžŅ€ĐžŅŅ‚ ĐŊа ĐēĐžĐŧĐŋŅ€ĐĩŅĐ¸Ņ. По-йавĐŊĐ¸Ņ‚Đĩ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģĐŊĐž СададĐĩĐŊи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ŅŅŠĐˇĐ´Đ°Đ˛Đ°Ņ‚ ĐŋĐž-ĐŧаĐģĐēи Ņ„Đ°ĐšĐģОвĐĩ и ĐŋĐžĐ˛Đ¸ŅˆĐ°Đ˛Đ°Ņ‚ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŋŅ€Đ¸ ĐŊĐ°ŅĐžŅ‡Đ˛Đ°ĐŊĐĩ ĐēҊĐŧ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚. VP9 Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ° ҁĐēĐžŅ€ĐžŅŅ‚Đ¸ ĐŊад „ĐŋĐž-ĐąŅŠŅ€ĐˇĐžâ€œ.", - "transcoding_realtime": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ в Ņ€ĐĩаĐģĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ [ЕКСПЕРИМЕНĐĸАЛНО]", - "transcoding_realtime_description": "ПозвоĐģŅĐ˛Đ° Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа видĐĩĐž ĐŋĐž Đ˛Ņ€ĐĩĐŧĐĩ ĐŊа Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ. Đ Đ°ĐˇŅ€ĐĩŅˆĐ°Đ˛Đ° ĐŋŅ€ĐĩвĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž, ĐŊĐž ĐŧĐžĐļĐĩ да ĐŋŅ€ĐĩдиСвиĐēа ĐŋĐž-ĐŗĐžĐģŅĐŧĐž ĐˇĐ°ĐąĐ°Đ˛ŅĐŊĐĩ иĐģи ĐŊаĐēŅŠŅĐ˛Đ°ĐŊĐĩ ĐŊа Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ҁĐŋĐžŅ€ĐĩĐ´ Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐžŅŅ‚Đ¸Ņ‚Đĩ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°.", - "transcoding_realtime_enabled": "ВĐēĐģŅŽŅ‡Đ¸ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ в Ņ€ĐĩаĐģĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ", - "transcoding_realtime_enabled_description": "АĐēĐž Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊĐž, ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҉Đĩ ĐžŅ‚ĐēаСва ĐŊОва ҁĐĩŅĐ¸Ņ Са Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ в Ņ€ĐĩаĐģĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ.", - "transcoding_realtime_resolutions": "Đ ĐĩСОĐģŅŽŅ†Đ¸Đ¸", - "transcoding_realtime_resolutions_description": "Đ’ŅŠĐˇĐŧĐžĐļĐŊĐ¸Ņ‚Đĩ Ņ€ĐĩСОĐģŅŽŅ†Đ¸Đ¸ Са Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ в Ņ€ĐĩаĐģĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ. Đ’Đ¸ŅĐžĐēĐ¸Ņ‚Đĩ Ņ€ĐĩСОĐģŅŽŅ†Đ¸Đ¸ ĐŧĐžĐļĐĩ да ĐŋŅ€ĐĩдиСвиĐēĐ°Ņ‚ ĐŋŅ€ĐžĐąĐģĐĩĐŧи ĐŋŅ€Đ¸ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ, аĐēĐž ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ĐŊĐĩ ҃ҁĐŋŅĐ˛Đ° да Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ° Đ´ĐžŅŅ‚Đ°Ņ‚ŅŠŅ‡ĐŊĐž ĐąŅŠŅ€ĐˇĐž.", - "transcoding_realtime_video_codecs": "ВидĐĩĐž ĐēОдĐĩŅ†Đ¸", - "transcoding_realtime_video_codecs_description": "ВидĐĩĐž ĐēОдĐĩŅ†Đ¸, ĐŊаĐģĐ¸Ņ‡ĐŊи Са Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ в Ņ€ĐĩаĐģĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ. ĐŸŅ€Đ¸ Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ ĐēĐģиĐĩĐŊŅ‚Đ¸Ņ‚Đĩ ҉Đĩ иСйĐĩŅ€Đ°Ņ‚ ĐŊаК-Đ´ĐžĐąŅ€Đ°Ņ‚Đ° ĐŋĐžĐ´Đ´ŅŠŅ€ĐļаĐŊа ĐžĐŋŅ†Đ¸Ņ. AV1 Đĩ ĐŋĐž-ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģĐŊа ĐžŅ‚ HEVC, ĐēĐžŅŅ‚Đž ĐŋҊĐē Đĩ ĐŋĐž-ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģĐŊа ĐžŅ‚ H.264. ĐŸŅ€Đ¸ ҃ĐŋĐžŅ‚Ņ€Đĩйа ĐŊа Ņ…Đ°Ņ€Đ´ŅƒĐĩŅ€ĐŊĐž ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ Đ¸ĐˇĐąĐ¸Ņ€Đ°ĐšŅ‚Đĩ ŅĐ°ĐŧĐž Ņ‚ĐĩСи, ĐēĐžĐ¸Ņ‚Đž ҃ҁĐēĐžŅ€Đ¸Ņ‚ĐĩĐģŅ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа. ĐŸŅ€Đ¸ ŅĐžŅ„Ņ‚ŅƒĐĩŅ€ĐŊĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ иĐŧĐ°ĐšŅ‚Đĩ ĐŋŅ€Đĩдвид, ҇Đĩ H.264 Đĩ ĐŋĐž-ĐąŅŠŅ€ĐˇĐž ĐžŅ‚ AV1, ĐēĐžĐĩŅ‚Đž Đĩ ĐŋĐž-ĐąŅŠŅ€ĐˇĐž ĐžŅ‚ HEVC.", "transcoding_reference_frames": "Đ ĐĩŅ„ĐĩŅ€ĐĩĐŊŅ‚ĐŊи ĐēĐ°Đ´Ņ€Đ¸", "transcoding_reference_frames_description": "Đ‘Ņ€ĐžŅŅ‚ ĐēĐ°Đ´Ņ€Đ¸ Са ĐŋŅ€ĐĩĐŋŅ€Đ°Ņ‚Đēа ĐŋŅ€Đ¸ ĐēĐžĐŧĐŋŅ€ĐĩŅĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа дадĐĩĐŊ ĐēĐ°Đ´ŅŠŅ€. По-Đ˛Đ¸ŅĐžĐēĐ¸Ņ‚Đĩ ŅŅ‚ĐžĐšĐŊĐžŅŅ‚Đ¸ ĐŋĐžĐ´ĐžĐąŅ€ŅĐ˛Đ°Ņ‚ ĐĩŅ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа ĐēĐžĐŧĐŋŅ€ĐĩŅĐ¸ŅŅ‚Đ°, ĐŊĐž ĐˇĐ°ĐąĐ°Đ˛ŅŅ‚ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž. 0 Садава Ņ‚Đ°ĐˇĐ¸ ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž.", "transcoding_required_description": "ХаĐŧĐž видĐĩа, ĐēĐžĐ¸Ņ‚Đž ĐŊĐĩ ŅĐ° в ĐŋŅ€Đ¸ĐĩŅ‚ Ņ„ĐžŅ€ĐŧĐ°Ņ‚", @@ -478,8 +440,6 @@ "user_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģҁĐēĐ¸Ņ‚Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "user_successfully_removed": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ {email} Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚.", "users_page_description": "ĐĄŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ° Са адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи", - "version_check_channel": "КаĐŊаĐģ Са ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐ¸Ņ", - "version_check_channel_description": "ĐŸĐžŅĐžŅ‡ĐĩŅ‚Đĩ ĐēаĐŊаĐģ, ĐŋĐž ĐēĐžĐšŅ‚Đž да ĐŋĐžĐģŅƒŅ‡Đ°Đ˛Đ°Ņ‚Đĩ иСвĐĩŅŅ‚Đ¸Ņ Са ĐŊОва вĐĩŅ€ŅĐ¸Ņ", "version_check_enabled_description": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Đš ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Đ¸ŅŅ‚Đ° Са ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ° Ņ€Đ°ĐˇŅ‡Đ¸Ņ‚Đ° ĐŊа ĐŋĐĩŅ€Đ¸ĐžĐ´Đ¸Ņ‡ĐŊа ĐēĐžĐŧ҃ĐŊиĐēĐ°Ņ†Đ¸Ņ ҁ {server}", "version_check_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊа вĐĩŅ€ŅĐ¸ŅŅ‚Đ°", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ĐēĐĩŅˆĐ° Са Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "advanced_settings_clear_image_cache_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‡Đ¸ŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ĐēĐĩŅˆĐ° Са Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "advanced_settings_clear_image_cache_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‡Đ¸ŅŅ‚ĐĩĐŊи {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "ĐŸŅ€Đ¸ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ, иСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ ĐēĐ°Ņ‚Đž Ņ„Đ¸ĐģŅ‚ŅŠŅ€, ĐžŅĐŊОваĐŊ ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа ĐŊа дадĐĩĐŊ ĐēŅ€Đ¸Ņ‚ĐĩŅ€Đ¸Đ¸. ОĐŋĐ¸Ņ‚Đ°ĐšŅ‚Đĩ ŅĐ°ĐŧĐž в ҁĐģŅƒŅ‡Đ°Đš, ҇Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž иĐŧа ĐŋŅ€ĐžĐąĐģĐĩĐŧ ҁ ĐžŅ‚ĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи аĐģĐąŅƒĐŧи.", + "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНĐĸАЛНО] ИСĐŋĐžĐģСваК Ņ„Đ¸ĐģŅ‚ŅŠŅ€Đ° ĐŊа аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊĐžŅ‚Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž Са ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐŊа аĐģĐąŅƒĐŧи", "advanced_settings_log_level_title": "Ниво ĐŊа СаĐŋĐ¸Ņ в Đ´ĐŊĐĩвĐŊиĐēа: {level}", "advanced_settings_prefer_remote_subtitle": "ĐŅĐēОи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° ŅĐ° Ņ‚Đ˛ŅŠŅ€Đ´Đĩ йавĐŊи Са да ĐŗĐĩĐŊĐĩŅ€Đ¸Ņ€Đ°Ņ‚ ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸. АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Đš Ņ‚Đ°ĐˇĐ¸ ĐžĐŋŅ†Đ¸Ņ Са да ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļĐ´Đ°Ņ‚ виĐŊĐ°ĐŗĐ¸ ĐžŅ‚ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°.", "advanced_settings_prefer_remote_title": "ĐŸŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°Đš Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸ŅŅ‚Đ° ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "ĐŸŅ€ĐžĐēŅĐ¸ Ņ…ĐĩĐ´ŅŠŅ€Đ¸ [ЕКСПЕРИМЕНĐĸАЛНО]", "advanced_settings_readonly_mode_subtitle": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ° Ņ€ĐĩĐļиĐŧа \"ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ\", ĐŋŅ€Đ¸ ĐēĐžĐšŅ‚Đž ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ Ņ€Đ°ĐˇĐŗĐģĐĩĐļдаĐŊи, ĐŊĐž ĐŊĐĩŅ‰Đ° ĐēĐ°Ņ‚Đž Đ¸ĐˇĐąĐžŅ€ ĐŊа ĐŊŅĐēĐžĐģĐēĐž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ, ҁĐŋОдĐĩĐģŅĐŊĐĩ, Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ŅĐ° ĐˇĐ°ĐąŅ€Đ°ĐŊĐĩĐŊи. АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ/Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩĐļиĐŧа ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ ŅŅ‚Đ°Đ˛Đ° ĐžŅ‚ ĐēĐ°Ņ€Ņ‚Đ¸ĐŊĐēĐ°Ņ‚Đ°-Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ ĐžŅ‚ ĐžŅĐŊОвĐŊĐ¸Ņ ĐĩĐēŅ€Đ°ĐŊ", "advanced_settings_readonly_mode_title": "Đ ĐĩĐļиĐŧ ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ", + "advanced_settings_self_signed_ssl_subtitle": "ĐŸŅ€ĐžĐŋ҃ҁĐēа ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐ°Ņ‚Đ° ĐŊа SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°. Đ˜ĐˇĐ¸ŅĐēва ҁĐĩ ĐŋŅ€Đ¸ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊи ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ¸.", + "advanced_settings_self_signed_ssl_title": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊи SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ¸ [ЕКСПЕРИМЕНĐĸАЛНО]", "advanced_settings_sync_remote_deletions_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ¸ иĐģи Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОви ОйĐĩĐēŅ‚ ĐŊа Ņ‚ĐžĐ˛Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž, ĐēĐžĐŗĐ°Ņ‚Đž Đ´ĐĩĐšŅŅ‚Đ˛Đ¸ĐĩŅ‚Đž Đĩ Đ¸ĐˇĐ˛ŅŠŅ€ŅˆĐĩĐŊĐž ĐŋŅ€ĐĩС ҃ĐĩĐą-иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅĐ°", "advanced_settings_sync_remote_deletions_title": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐŊа Đ´Đ¸ŅŅ‚Đ°ĐŊŅ†Đ¸ĐžĐŊĐŊи Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐ¸Ņ [ЕКСПЕРИМЕНĐĸАЛНО]", "advanced_settings_tile_subtitle": "Đ Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģҁĐēи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", @@ -516,18 +480,31 @@ "album_delete_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ аĐģĐąŅƒĐŧа {album}?", "album_delete_confirmation_description": "АĐēĐž Ņ‚ĐžĐˇĐ¸ аĐģĐąŅƒĐŧ Đĩ ҁĐŋОдĐĩĐģĐĩĐŊ, Đ´Ņ€ŅƒĐŗĐ¸ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи вĐĩ҇Đĩ ĐŊŅĐŧа да иĐŧĐ°Ņ‚ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ĐŊĐĩĐŗĐž.", "album_deleted": "АĐģĐąŅƒĐŧа Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚", + "album_info_card_backup_album_excluded": "ИЗКЛЮЧЕН", + "album_info_card_backup_album_included": "ВКЛЮЧЕН", "album_info_updated": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸ŅŅ‚Đ° Са аĐģĐąŅƒĐŧа Đĩ ОйĐŊОвĐĩĐŊа", + "album_leave": "Да ĐŊаĐŋ҃ҁĐŊа Đģи аĐģĐąŅƒĐŧа?", + "album_leave_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŊаĐŋ҃ҁĐŊĐĩŅ‚Đĩ {album}?", "album_name": "ИĐŧĐĩ ĐŊа аĐģĐąŅƒĐŧа", "album_options": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа аĐģĐąŅƒĐŧа", "album_remove_user": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ?", "album_remove_user_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩŅ‚Đĩ {user}?", "album_search_not_found": "ĐŅĐŧа ĐŊаĐŧĐĩŅ€ĐĩĐŊи аĐģĐąŅƒĐŧи, ĐžŅ‚ĐŗĐžĐ˛Đ°Ņ€ŅŅ‰Đ¸ ĐŊа Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž ви", + "album_selected": "АĐģĐąŅƒĐŧа Đĩ Đ¸ĐˇĐąŅ€Đ°ĐŊ", "album_share_no_users": "Đ˜ĐˇĐŗĐģĐĩĐļда, ҇Đĩ ҁ҂Đĩ ҁĐŋОдĐĩĐģиĐģи Ņ‚ĐžĐˇĐ¸ аĐģĐąŅƒĐŧ ҁ Đ˛ŅĐ¸Ņ‡Đēи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи иĐģи ĐŊŅĐŧĐ°Ņ‚Đĩ Đ´Ņ€ŅƒĐŗ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ, ҁ ĐēĐžĐŗĐžŅ‚Đž да ĐŗĐž ҁĐŋОдĐĩĐģĐ¸Ņ‚Đĩ.", "album_summary": "ĐžĐąĐžĐąŅ‰ĐĩĐŊиĐĩ ĐŊа аĐģĐąŅƒĐŧа", "album_updated": "АĐģĐąŅƒĐŧŅŠŅ‚ Đĩ ОйĐŊОвĐĩĐŊ", "album_updated_setting_description": "ПоĐģŅƒŅ‡Đ°Đ˛Đ°ĐšŅ‚Đĩ иСвĐĩŅŅ‚Đ¸Đĩ ĐŋĐž иĐŧĐĩĐšĐģ, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐŋОдĐĩĐģĐĩĐŊ аĐģĐąŅƒĐŧ иĐŧа ĐŊОви Ņ„Đ°ĐšĐģОвĐĩ", "album_upload_assets": "Đ—Đ°Ņ€ĐĩĐ´ĐĩŅ‚Đĩ ОйĐĩĐēŅ‚Đ¸ ĐžŅ‚ ĐēĐžĐŧĐŋŅŽŅ‚ŅŠŅ€Đ° в ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ĐŗĐ¸ дОйавĐĩŅ‚Đĩ в аĐģĐąŅƒĐŧ", + "album_user_left": "НаĐŋ҃ҁĐŊа {album}", + "album_user_removed": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚ {user}", + "album_viewer_appbar_delete_confirm": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚ĐžĐˇĐ¸ аĐģĐąŅƒĐŧ ĐžŅ‚ ŅĐ˛ĐžŅ ĐŋŅ€ĐžŅ„Đ¸Đģ?", "album_viewer_appbar_share_err_delete": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧ", + "album_viewer_appbar_share_err_leave": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŊаĐŋ҃ҁĐēаĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧ", + "album_viewer_appbar_share_err_remove": "ĐŸŅ€ĐžĐąĐģĐĩĐŧ ĐŋŅ€Đ¸ ĐŋĐĩŅ€ĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸ ĐžŅ‚ аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_err_title": "НĐĩ҃ҁĐŋĐĩ҈ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа ĐŊа иĐŧĐĩŅ‚Đž ĐŊа аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_leave": "НаĐŋ҃ҁĐēаĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_to": "ĐĄĐŋОдĐĩĐģи ҁ", "album_viewer_page_share_add_users": "Добави ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи", "album_with_link_access": "НĐĩĐēа Đ˛ŅĐĩĐēи ҁ ĐģиĐŊĐē виĐļда ҁĐŊиĐŧĐēи и Ņ…ĐžŅ€Đ° в Ņ‚ĐžĐˇĐ¸ аĐģĐąŅƒĐŧ.", "albums": "АĐģĐąŅƒĐŧи", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "ĐŸŅŠŅ€Đ˛ĐžĐŊĐ°Ņ‡Đ°ĐģĐĩĐŊ Ņ€ĐĩĐ´ ĐŊа ŅĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŋŅ€Đ¸ ŅŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐŊОв аĐģĐąŅƒĐŧ.", "albums_feature_description": "КоĐģĐĩĐēŅ†Đ¸Đ¸ ĐžŅ‚ ОйĐĩĐēŅ‚Đ¸, ĐēĐžĐ¸Ņ‚Đž ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ ҁĐŋОдĐĩĐģŅĐŊи ҁ Đ´Ņ€ŅƒĐŗĐ¸ ĐŋĐžŅ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи.", "albums_on_device_count": "АĐģĐąŅƒĐŧи ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž ({count})", + "albums_selected": "{count, plural, one {Đ˜ĐˇĐąŅ€Đ°ĐŊ Đĩ # аĐģĐąŅƒĐŧ} other {Đ˜ĐˇĐąŅ€Đ°ĐŊи ŅĐ° # аĐģĐąŅƒĐŧа}}", "all": "Đ’ŅĐ¸Ņ‡Đēи", "all_albums": "Đ’ŅĐ¸Ņ‡Đēи аĐģĐąŅƒĐŧи", "all_people": "Đ’ŅĐ¸Ņ‡Đēи Ņ…ĐžŅ€Đ°", "all_photos": "Đ’ŅĐ¸Ņ‡Đēи ҁĐŊиĐŧĐēи", "all_videos": "Đ’ŅĐ¸Ņ‡Đēи видĐĩĐžĐēĐģиĐŋОвĐĩ", "allow_dark_mode": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ Ņ‚ŅŠĐŧĐĩĐŊ Ņ€ĐĩĐļиĐŧ", + "allow_edits": "ПозвоĐģŅĐ˛Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩдаĐēŅ†Đ¸Đ¸", "allow_public_user_to_download": "ПозвоĐģĐĩŅ‚Đĩ ĐŊа ĐŋŅƒĐąĐģĐ¸Ņ‡ĐĩĐŊ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ да ĐŧĐžĐļĐĩ да Đ¸ĐˇŅ‚ĐĩĐŗĐģŅ", "allow_public_user_to_upload": "ПозвоĐģĐĩŅ‚Đĩ ĐŊа ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊĐ¸Ņ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ да ĐŧĐžĐļĐĩ да ĐēĐ°Ņ‡Đ˛Đ°", "allowed": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž", @@ -549,12 +528,14 @@ "always_keep": "ВиĐŊĐ°ĐŗĐ¸ ĐŋаСи", "always_keep_photos_hint": "ĐŸŅ€Đ¸ ĐžŅĐ˛ĐžĐąĐžĐļдаваĐŊĐĩ ĐŊа ĐŧŅŅŅ‚Đž ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи Đ˛ŅĐ¸Ņ‡Đēи ҁĐŊиĐŧĐēи ĐŊа Ņ‚ĐžĐ˛Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž.", "always_keep_videos_hint": "ĐŸŅ€Đ¸ ĐžŅĐ˛ĐžĐąĐžĐļдаваĐŊĐĩ ĐŊа ĐŧŅŅŅ‚Đž ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи Đ˛ŅĐ¸Ņ‡Đēи видĐĩа ĐŊа Ņ‚ĐžĐ˛Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž.", + "anti_clockwise": "ĐžĐąŅ€Đ°Ņ‚ĐŊĐž ĐŊа Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžĐ˛Đ°Ņ‚Đ° ҁ҂ҀĐĩĐģĐēа", "api_key": "API ĐēĐģŅŽŅ‡", "api_key_description": "ĐĸаСи ŅŅ‚ĐžĐšĐŊĐžŅŅ‚ ҉Đĩ ĐąŅŠĐ´Đĩ ĐŋĐžĐēаСаĐŊа ŅĐ°ĐŧĐž вĐĩĐ´ĐŊҊĐļ. МоĐģŅ, ĐŊĐĩ ĐˇĐ°ĐąŅ€Đ°Đ˛ŅĐšŅ‚Đĩ да ĐŗĐž ĐēĐžĐŋĐ¸Ņ€Đ°Ņ‚Đĩ, ĐŋŅ€Đĩди да ĐˇĐ°Ņ‚Đ˛ĐžŅ€Đ¸Ņ‚Đĩ ĐŋŅ€ĐžĐˇĐžŅ€ĐĩŅ†Đ°.", "api_key_empty": "ИĐŧĐĩŅ‚Đž ĐŊа Đ˛Đ°ŅˆĐ¸Ņ API ĐēĐģŅŽŅ‡ ĐŊĐĩ Ņ‚Ņ€ŅĐąĐ˛Đ° да Đĩ ĐŋŅ€Đ°ĐˇĐŊĐž", "api_keys": "API ĐēĐģŅŽŅ‡ĐžĐ˛Đĩ", "app_architecture_variant": "Đ’Đ°Ņ€Đ¸Đ°ĐŊŅ‚ (ĐŅ…Đ¸Ņ‚ĐĩĐēŅ‚ŅƒŅ€Đ°)", "app_bar_signout_dialog_content": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да иСĐģĐĩСĐĩŅ‚Đĩ?", + "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "ИСĐģĐĩС ĐžŅ‚ ĐŋŅ€ĐžŅ„Đ¸Đģа", "app_download_links": "ЛиĐŊĐēОвĐĩ Са ŅĐ˛Đ°ĐģŅĐŊĐĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", "app_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи Đŧа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", @@ -565,19 +546,27 @@ "archive": "ĐŅ€Ņ…Đ¸Đ˛", "archive_action_prompt": "{count} ŅĐ° дОйавĐĩĐŊи в ĐŅ€Ņ…Đ¸Đ˛Đ°", "archive_or_unarchive_photo": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ иĐģи Đ´ĐĩĐ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēа", + "archive_page_no_archived_assets": "НĐĩ ŅĐ° ĐŊаĐŧĐĩŅ€ĐĩĐŊи ОйĐĩĐēŅ‚Đ¸ в Đ°Ņ€Ņ…Đ¸Đ˛Đ°", + "archive_page_title": "ĐŅ€Ņ…Đ¸Đ˛ ({count})", "archive_size": "РаСĐŧĐĩŅ€ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ°", "archive_size_description": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸Ņ€Đ°ĐšŅ‚Đĩ Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ° Са Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐ¸Ņ (в GiB)", "archived": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи", "archived_count": "{count, plural, other {ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи #}}", "are_these_the_same_person": "ĐĸОва ĐĩĐ´ĐŊĐž и ŅŅŠŅ‰Đž ĐģĐ¸Ņ†Đĩ Đģи Đĩ?", "are_you_sure_to_do_this": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŊаĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đĩ Ņ‚ĐžĐ˛Đ°?", + "array_field_not_fully_supported": "ПоĐģĐĩŅ‚Đ°Ņ‚Đ° ĐŊа ĐŧĐ°ŅĐ¸Đ˛Đ° Đ¸ĐˇĐ¸ŅĐēĐ˛Đ°Ņ‚ Ņ€ŅŠŅ‡ĐŊĐž Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа JSON", + "asset_action_delete_err_read_only": "НĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸ ŅĐ°ĐŧĐž-Са-҇ĐĩŅ‚ĐĩĐŊĐĩ, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "asset_action_share_err_offline": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋĐžĐģŅƒŅ‡Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐžŅ„ĐģаКĐŊ ОйĐĩĐēŅ‚/и, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŧĐĩ", "asset_added_to_album": "ДобавĐĩĐŊĐž в аĐģĐąŅƒĐŧ", "asset_adding_to_album": "Đ”ĐžĐąĐ°Đ˛ŅĐŊĐĩ в аĐģĐąŅƒĐŧâ€Ļ", "asset_created": "ОбĐĩĐēŅ‚ŅŠŅ‚ Đĩ ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ", - "asset_day_count": "{date}: {count, plural, one {# ОйĐĩĐēŅ‚} other {# ОйĐĩĐēŅ‚Đ°}}", "asset_description_updated": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩŅ‚Đž ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ° Đĩ ОйĐŊОвĐĩĐŊĐž", + "asset_filename_is_offline": "АĐēŅ‚Đ¸Đ˛ŅŠŅ‚ {filename} Đĩ ĐžŅ„ĐģаКĐŊ", + "asset_has_unassigned_faces": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ŅŠŅ‚ иĐŧа ĐŊĐĩСададĐĩĐŊи ĐģĐ¸Ņ†Đ°", "asset_hashing": "ĐĨĐĩŅˆĐ¸Ņ€Đ°ĐŊĐĩâ€Ļ", "asset_list_group_by_sub_title": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŋĐž", + "asset_list_layout_settings_dynamic_layout_title": "ДиĐŊаĐŧĐ¸Ņ‡ĐŊĐž Ņ€Đ°ĐˇĐŋĐžĐģĐ°ĐŗĐ°ĐŊĐĩ", + "asset_list_layout_settings_group_automatically": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž", "asset_list_layout_settings_group_by": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Đš ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐŋĐž", "asset_list_layout_settings_group_by_month_day": "МĐĩҁĐĩ҆ и ДĐĩĐŊ", "asset_list_layout_sub_title": "РаСĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊ в iCloud. ОбĐĩĐēŅ‚ŅŠŅ‚ ĐŧĐžĐļĐĩ да Đĩ ĐŊĐĩĐ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ ĐŋĐžŅ€Đ°Đ´Đ¸ ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊ Ņ„Đ°ĐšĐģ, ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊ в iCloud", "asset_offline": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ŅŠŅ‚ Đĩ ĐžŅ„ĐģаКĐŊ", "asset_offline_description": "ĐĸОСи Đ˛ŅŠĐŊ҈ĐĩĐŊ аĐēŅ‚Đ¸Đ˛ вĐĩ҇Đĩ ĐŊĐĩ ҁĐĩ ĐŊаĐŧĐ¸Ņ€Đ° ĐŊа Đ´Đ¸ŅĐēа. МоĐģŅ, ŅĐ˛ŅŠŅ€ĐļĐĩŅ‚Đĩ ҁĐĩ ҁ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ĐŊа Immich Са ĐŋĐžĐŧĐžŅ‰.", + "asset_restored_successfully": "ĐŖŅĐŋĐĩ҈ĐŊĐž Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊ ОйĐĩĐēŅ‚", "asset_skipped": "ĐŸŅ€ĐžĐŋ҃ҁĐŊĐ°Ņ‚Đž", "asset_skipped_in_trash": "В ĐēĐžŅˆŅ‡ĐĩŅ‚Đž", + "asset_trashed": "ОбĐĩĐēŅ‚ŅŠŅ‚ Đĩ Đ¸ĐˇŅ…Đ˛ŅŠŅ€ĐģĐĩĐŊ", "asset_troubleshoot": "ПоĐŋŅ€Đ°Đ˛Đēа ĐŊа ĐŗŅ€Đĩ҈Đēи ҁ ОйĐĩĐēŅ‚Đ°", "asset_uploaded": "ĐšĐ°Ņ‡ĐĩĐŊĐž", "asset_uploading": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩâ€Ļ", "asset_viewer_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ Са Đ¸ĐˇĐŗĐģĐĩĐ´", "asset_viewer_settings_title": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "assets": "ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", + "assets_added_count": "ДобавĐĩĐŊĐž {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "ДобавĐĩĐŊ(и) ŅĐ° {count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}} в аĐģĐąŅƒĐŧа", - "assets_added_to_album_partial_count": "ДобавĐĩĐŊи ŅĐ° {successCount} ĐžŅ‚ ĐžĐąŅ‰Đž {totalCount} {totalCount, plural, one {ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}} ĐēҊĐŧ аĐģĐąŅƒĐŧа", "assets_added_to_albums_count": "{assetTotal, plural, one {# ОйĐĩĐēŅ‚ Đĩ дОйавĐĩĐŊ} other {# ОйĐĩĐēŅ‚Đ° ŅĐ° дОйавĐĩĐŊи}} в {albumTotal, plural, one {# аĐģĐąŅƒĐŧ} other {# аĐģĐąŅƒĐŧа}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ОбĐĩĐēŅ‚Đ° ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ дОйави} other {ОбĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ Đ´ĐžĐąĐ°Đ˛ŅŅ‚}} в аĐģĐąŅƒĐŧа", "assets_cannot_be_added_to_albums": "{count, plural, one {ОйĐĩĐēŅ‚ ĐŊĐĩ ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ дОйавĐĩĐŊ} other {ОйĐĩĐēŅ‚Đ° ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ дОйавĐĩĐŊи}} в ĐŊиĐēОК ĐžŅ‚ аĐģĐąŅƒĐŧĐ¸Ņ‚Đĩ", "assets_count": "{count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}}", + "assets_deleted_permanently": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸", + "assets_deleted_permanently_from_server": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° Đ¸ĐˇŅ‚Đ¸Ņ‚Đ¸ ĐžŅ‚ Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ° СавиĐŊĐ°ĐŗĐ¸", + "assets_downloaded_failed": "{count, plural, one {Đ—Đ°Ņ€ĐĩĐ´ĐĩĐŊ # Ņ„Đ°ĐšĐģ} many {Đ—Đ°Ņ€ĐĩĐ´ĐĩĐŊи # Ņ„Đ°ĐšĐģа} other {ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊи # Ņ„Đ°ĐšĐģа}}, {error} - ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊĐž", + "assets_downloaded_successfully": "ĐŖŅĐŋĐĩ҈ĐŊĐž {count, plural, one {Đĩ ĐēĐ°Ņ‡ĐĩĐŊ # Ņ„Đ°ĐšĐģ} many {ŅĐ° ĐēĐ°Ņ‡ĐĩĐŊи # Ņ„Đ°ĐšĐģа} other {ŅĐ° ĐēĐ°Ņ‡ĐĩĐŊи # Ņ„Đ°ĐšĐģа}}", "assets_moved_to_trash_count": "ĐŸŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊ(и) ŅĐ° {count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}} в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž", "assets_permanently_deleted_count": "ĐŸĐžŅŅ‚ĐžŅĐŊĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚(и) ŅĐ° {count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}}", "assets_removed_count": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚(и) ŅĐ° {count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}}", + "assets_removed_permanently_from_device": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸ ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", "assets_restore_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи аĐēŅ‚Đ¸Đ˛Đ¸ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž? НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚Đĩ Ņ‚ĐžĐ˛Đ° Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ! ИĐŧĐ°ĐšŅ‚Đĩ ĐŋŅ€Đĩдвид, ҇Đĩ аĐēŅ‚Đ¸Đ˛Đ¸, ĐēĐžĐ¸Ņ‚Đž ŅĐ° ĐžŅ„ĐģаКĐŊ, ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊи ĐŋĐž Ņ‚ĐžĐˇĐ¸ ĐŊĐ°Ņ‡Đ¸ĐŊ.", "assets_restored_count": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊ(и) ŅĐ° {count, plural, one {# аĐēŅ‚Đ¸Đ˛} other {# аĐēŅ‚Đ¸Đ˛Đ°}}", + "assets_restored_successfully": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° ҃ҁĐŋĐĩ҈ĐŊĐž Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊи", "assets_trashed": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи в ĐēĐžŅˆĐ°", - "assets_trashed_count": "{count, plural, one {# Ņ„Đ°ĐšĐģ Đĩ ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž} other {# Ņ„Đ°ĐšĐģа ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž}}", + "assets_trashed_count": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊ(и) ŅĐ° {count, plural, one {# Ņ„Đ°ĐšĐģ} other {# Ņ„Đ°ĐšĐģа}}", + "assets_trashed_from_server": "{count} ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи в ĐēĐžŅˆĐ° ĐŊа Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "assets_were_part_of_album_count": "{count, plural, one {ФаКĐģŅŠŅ‚ Đĩ} other {ФаКĐģОвĐĩŅ‚Đĩ ŅĐ°}} вĐĩ҇Đĩ Ņ‡Đ°ŅŅ‚ ĐžŅ‚ аĐģĐąŅƒĐŧа", "assets_were_part_of_albums_count": "{count, plural, one {ОйĐĩĐēŅ‚ вĐĩ҇Đĩ Đĩ} other {ОйĐĩĐēŅ‚Đ° вĐĩ҇Đĩ ŅĐ°}} Ņ‡Đ°ŅŅ‚ ĐžŅ‚ аĐģĐąŅƒĐŧĐ¸Ņ‚Đĩ", "authorized_devices": "ĐŖĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€ĐĩĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊа ҁĐŧŅĐŊа ĐŊа ҁĐģаКдОвĐĩŅ‚Đĩ", "back": "Назад", "back_close_deselect": "Назад, ĐˇĐ°Ņ‚Đ˛Đ°Ņ€ŅĐŊĐĩ иĐģи ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа Đ¸ĐˇĐąĐžŅ€Đ°", + "background_backup_running_error": "ĐĄŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐž Đĩ Ņ„ĐžĐŊОвО Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ, ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ Đŋ҃ҁĐŊĐĩ Ņ€ŅŠŅ‡ĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", "background_location_permission": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ Са Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", "background_location_permission_content": "За да ĐŧĐžĐļĐĩ да ҇ĐĩŅ‚Đĩ иĐŧĐĩĐŊĐ°Ņ‚Đ° ĐŊа Wi-Fi ĐŧŅ€ĐĩĐļĐ¸Ņ‚Đĩ и да ĐŗĐ¸ ĐŋŅ€ĐĩвĐēĐģŅŽŅ‡Đ˛Đ° ĐŋŅ€Đ¸ Ņ€Đ°ĐąĐžŅ‚Đ° Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ, Immich Ņ‚Ņ€ŅĐąĐ˛Đ° *виĐŊĐ°ĐŗĐ¸* да иĐŧа Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž Ņ‚ĐžŅ‡ĐŊĐžŅ‚Đž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "background_options": "ОĐŋŅ†Đ¸Đ¸ Са Ņ„ĐžĐŊĐžĐ˛Đ¸Ņ‚Đĩ ĐˇĐ°Đ´Đ°Ņ‡Đ¸", "backup": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", + "backup_album_selection_page_albums_device": "АĐģĐąŅƒĐŧи ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž ({count})", "backup_album_selection_page_albums_tap": "ĐĐ°Ņ‚Đ¸ŅĐŊи Са да вĐēĐģŅŽŅ‡Đ¸Ņˆ, двОКĐŊĐž Са да иСĐēĐģŅŽŅ‡Đ¸Ņˆ", "backup_album_selection_page_assets_scatter": "ОбĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ Ņ€Đ°ĐˇĐŋŅ€ŅŠŅĐŊĐ°Ņ‚Đ¸ в ĐŊŅĐēĐžĐģĐēĐž аĐģĐąŅƒĐŧа. По Ņ‚ĐžĐˇĐ¸ ĐŊĐ°Ņ‡Đ¸ĐŊ аĐģĐąŅƒĐŧĐ¸Ņ‚Đĩ ĐŧĐžĐŗĐ°Ņ‚ да ĐąŅŠĐ´Đ°Ņ‚ вĐēĐģŅŽŅ‡ĐĩĐŊи иĐģи иСĐēĐģŅŽŅ‡ĐĩĐŊи ĐŋĐž Đ˛Ņ€ĐĩĐŧĐĩ ĐŊа ĐŋŅ€ĐžŅ†ĐĩŅĐ° ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ.", "backup_album_selection_page_select_albums": "Đ˜ĐˇĐąĐžŅ€ ĐŊа аĐģĐąŅƒĐŧи", "backup_album_selection_page_selection_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са Đ¸ĐˇĐąŅ€Đ°ĐŊĐžŅ‚Đž", + "backup_album_selection_page_total_assets": "ĐŖĐŊиĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ ĐžĐąŅ‰Đž", "backup_albums_sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ‚Đĩ", + "backup_all": "Đ’ŅĐ¸Ņ‡ĐēĐž", + "backup_background_service_backup_failed_message": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ. Нов ĐžĐŋĐ¸Ņ‚â€Ļ", "backup_background_service_complete_notification": "Đ—Đ°Đ˛ŅŠŅ€ŅˆĐ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž ĐŊа ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ", + "backup_background_service_connection_failed_message": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐ˛ŅŠŅ€ĐˇĐ˛Đ°ĐŊĐĩ ĐēҊĐŧ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°. Нов ĐžĐŋĐ¸Ņ‚â€Ļ", + "backup_background_service_current_upload_notification": "Đ—Đ°Ņ€ĐĩĐļдаĐŧ {filename}", "backup_background_service_default_notification": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐŊОви ОйĐĩĐēŅ‚Đ¸â€Ļ", + "backup_background_service_error_title": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", "backup_background_service_in_progress_notification": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩâ€Ļ", + "backup_background_service_upload_failure_notification": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа {filename}", "backup_controller_page_albums": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи", + "backup_controller_page_background_app_refresh_disabled_content": "За да ĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ Ņ‚Ņ€ŅĐąĐ˛Đ° да Ņ€Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚Đĩ в ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи > ĐžĐąŅ‰Đ¸ > ОбĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ.", + "backup_controller_page_background_app_refresh_disabled_title": "ФОĐŊОвО ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_app_refresh_enable_button_text": "Иди в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "backup_controller_page_background_battery_info_link": "ПоĐēаĐļи Đŧи ĐēаĐē", "backup_controller_page_background_battery_info_message": "За ҃ҁĐŋĐĩ҈ĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ, ĐŧĐžĐģŅ иСĐēĐģŅŽŅ‡ĐĩŅ‚Đĩ ĐžĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Đ¸Ņ‚Đĩ ĐŊа ĐąĐ°Ņ‚ĐĩŅ€Đ¸ŅŅ‚Đ°, ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡Đ°Đ˛Đ°Ņ‰Đ¸ Ņ„ĐžĐŊĐžĐ˛Đ°Ņ‚Đ° аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ ĐŊа Immich.\n\nĐĸаСи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа Đĩ ҁĐŋĐžŅ€ĐĩĐ´ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž, ĐŧĐžĐģŅ ĐŋĐžŅ‚ŅŠŅ€ŅĐĩŅ‚Đĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ ҁĐŋĐžŅ€ĐĩĐ´ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģŅ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž.", "backup_controller_page_background_battery_info_ok": "ОĐē", "backup_controller_page_background_battery_info_title": "ОĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐŊа ĐąĐ°Ņ‚ĐĩŅ€Đ¸ŅŅ‚Đ°", + "backup_controller_page_background_charging": "ХаĐŧĐž ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ", + "backup_controller_page_background_configure_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа Ņ„ĐžĐŊĐžĐ˛Đ°Ņ‚Đ° ҃ҁĐģŅƒĐŗĐ°", "backup_controller_page_background_delay": "Đ˜ĐˇŅ‡Đ°ĐēваĐŊĐĩ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŊОв ОйĐĩĐēŅ‚: {duration}", + "backup_controller_page_background_description": "ВĐēĐģŅŽŅ‡Đ¸ Ņ„ĐžĐŊĐžĐ˛Đ°Ņ‚Đ° ҃ҁĐģŅƒĐŗĐ° Са Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŊОви ОйĐĩĐēŅ‚Đ¸ ĐąĐĩС ĐŊ҃Đļда да ĐžŅ‚Đ˛Đ°Ņ€ŅŅˆ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", + "backup_controller_page_background_is_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐžŅ‚Đž Ņ„ĐžĐŊОвО Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_is_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐžŅ‚Đž Ņ„ĐžĐŊОвО Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_turn_off": "ИСĐēĐģŅŽŅ‡Đ¸ Ņ„ĐžĐŊĐžĐ˛Đ°Ņ‚Đ° ҃ҁĐģŅƒĐŗĐ°", + "backup_controller_page_background_turn_on": "ВĐēĐģŅŽŅ‡Đ¸ Ņ„ĐžĐŊĐžĐ˛Đ°Ņ‚Đ° ҃ҁĐģŅƒĐŗĐ°", + "backup_controller_page_background_wifi": "ХаĐŧĐž ĐŋŅ€ĐĩС Wi-Fi", "backup_controller_page_backup": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", "backup_controller_page_backup_selected": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐž: ", "backup_controller_page_backup_sub": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐž", + "backup_controller_page_created": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐŊа: {date}", + "backup_controller_page_desc_backup": "ВĐēĐģŅŽŅ‡Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ Ņ€ĐĩĐļиĐŧ Са Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ĐēĐžĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŊОви ОйĐĩĐēŅ‚Đ¸ ĐŋŅ€Đ¸ ĐžŅ‚Đ˛Đ°Ņ€ŅĐŊĐĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž.", "backup_controller_page_excluded": "ИСĐēĐģŅŽŅ‡ĐĩĐŊи: ", + "backup_controller_page_failed": "НĐĩ҃ҁĐŋĐĩ҈ĐŊи ({count})", + "backup_controller_page_filename": "ФаКĐģ: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž", "backup_controller_page_none_selected": "ĐŅĐŧа Đ¸ĐˇĐąŅ€Đ°ĐŊĐž", "backup_controller_page_remainder": "ĐžŅŅ‚Đ°Đ˛Đ°Ņ‚", "backup_controller_page_remainder_sub": "ĐžŅŅ‚Đ°Đ˛Đ°Ņ‰Đ¸ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ", "backup_controller_page_server_storage": "ĐĨŅ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€", + "backup_controller_page_start_backup": "ЗаĐŋĐžŅ‡ĐŊи Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", + "backup_controller_page_status_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐžŅ‚Đž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ Ņ€ĐĩĐļиĐŧ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_status_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐžŅ‚Đž Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ Ņ€ĐĩĐļиĐŧ Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊĐž", "backup_controller_page_storage_format": "иСĐŋĐžĐģСваĐŊи {used} ĐžŅ‚ {total}", "backup_controller_page_to_backup": "АĐģĐąŅƒĐŧи Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", "backup_controller_page_total_sub": "Đ’ŅĐ¸Ņ‡Đēи ҃ĐŊиĐēаĐģĐŊи ҁĐŊиĐŧĐēи и видĐĩа ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ аĐģĐąŅƒĐŧи", + "backup_controller_page_turn_off": "ИСĐēĐģŅŽŅ‡Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ Ņ€ĐĩĐļиĐŧ", + "backup_controller_page_turn_on": "ВĐēĐģŅŽŅ‡Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ Ņ€ĐĩĐļиĐŧ", + "backup_controller_page_uploading_file_info": "ИĐŊŅ„Đž Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐ¸Ņ Ņ„Đ°ĐšĐģ", + "backup_err_only_album": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩ ĐĩдиĐŊŅŅ‚Đ˛ĐĩĐŊĐ¸Ņ аĐģĐąŅƒĐŧ", "backup_error_sync_failed": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸ŅŅ‚Đ° Đĩ ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊа. Đ ĐĩСĐĩŅ€Đ˛ĐŊĐžŅ‚Đž ĐēĐžĐŋиĐĩ ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ ĐžĐąŅ€Đ°ĐąĐžŅ‚Đ¸.", "backup_info_card_assets": "ОйĐĩĐēŅ‚Đ°", + "backup_manual_cancelled": "ĐžŅ‚ĐŧĐĩĐŊĐĩĐŊĐž", + "backup_manual_in_progress": "Đ’ŅŠŅ€Đ˛Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ. ОĐŋĐ¸Ņ‚Đ°Đš ҁĐģĐĩĐ´ ĐŧаĐģĐēĐž", + "backup_manual_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž", + "backup_manual_title": "ĐĄŅŠŅŅ‚ĐžŅĐŊиĐĩ ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩŅ‚Đž", "backup_options": "ОĐŋŅ†Đ¸Đ¸ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", + "backup_options_page_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ", + "backup_setting_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ°Đš ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ в аĐēŅ‚Đ¸Đ˛ĐĩĐŊ и Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", "backup_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ Са ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ", + "backup_upload_details_page_more_details": "ПовĐĩ҇Đĩ ĐŋĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸", "backward": "Назад", - "battery_optimization_backup_reliability": "ИСĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ĐžĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸ŅŅ‚Đ° ĐŊа иСĐŋĐžĐģСваĐŊĐĩ ĐŊа ĐąĐ°Ņ‚ĐĩŅ€Đ¸ŅŅ‚Đ° ĐŧĐžĐļĐĩ да ĐŋĐžĐ´ĐžĐąŅ€Đ¸ ĐŊадĐĩĐļĐ´ĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", "biometric_auth_enabled": "ВĐēĐģŅŽŅ‡ĐĩĐŊа йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊĐž ŅƒĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€ŅĐ˛Đ°ĐŊĐĩ", "biometric_locked_out": "ĐŅĐŧа Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊĐž ŅƒĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€ŅĐ˛Đ°ĐŊĐĩ", "biometric_no_options": "ĐŅĐŧа йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊа Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸ĐēĐ°Ņ†Đ¸Ņ", "biometric_not_available": "На Ņ‚ĐžĐ˛Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž ĐŊĐĩ Đĩ ĐŊаĐģĐ¸Ņ‡ĐŊĐž йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊĐž ŅƒĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€ŅĐ˛Đ°ĐŊĐĩ", + "birthdate_saved": "Đ”Đ°Ņ‚Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ Đĩ СаĐŋаСĐĩĐŊа ҃ҁĐŋĐĩ҈ĐŊĐž", "birthdate_set_description": "Đ”Đ°Ņ‚Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ ҁĐĩ иСĐŋĐžĐģСва Са Đ¸ĐˇŅ‡Đ¸ŅĐģŅĐ˛Đ°ĐŊĐĩ ĐŊа Đ˛ŅŠĐˇŅ€Đ°ŅŅ‚Ņ‚Đ° ĐŊа Ņ‚ĐžĐˇĐ¸ Ņ‡ĐžĐ˛ĐĩĐē ĐēҊĐŧ ĐŧĐžĐŧĐĩĐŊŅ‚Đ° ĐŊа ҁĐŊиĐŧĐēĐ°Ņ‚Đ°.", "blurred_background": "ЗаĐŧŅŠĐŗĐģĐĩĐŊ СадĐĩĐŊ Ņ„ĐžĐŊ", - "browse_templates": "Đ Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа ŅˆĐ°ĐąĐģĐžĐŊи", "bugs_and_feature_requests": "Đ‘ŅŠĐŗĐžĐ˛Đĩ и ĐˇĐ°ŅĐ˛Đēи Са Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸", "build": "ВĐĩŅ€ŅĐ¸Ņ", "build_image": "Docker вĐĩŅ€ŅĐ¸Ņ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да СаĐŋĐ°ĐˇĐ¸Ņ‚Đĩ {count, plural, one {# Đ´ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊ Ņ„Đ°ĐšĐģ} other {# Đ´ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊи Ņ„Đ°ĐšĐģа}}? ĐĸОва ҉Đĩ ĐŋĐžŅ‚Đ˛ŅŠŅ€Đ´Đ¸ Đ˛ŅĐ¸Ņ‡Đēи ĐŗŅ€ŅƒĐŋи Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸, ĐąĐĩС да Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ° ĐŊĐ¸Ņ‰Đž.", "bulk_trash_duplicates_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŋŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚Đĩ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž ĐŧĐ°ŅĐžĐ˛Đž {count, plural, one {# Đ´ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊ Ņ„Đ°ĐšĐģ} other {# Đ´ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊи Ņ„Đ°ĐšĐģа}}? ĐĸОва ҉Đĩ СаĐŋаСи ĐŊаК-ĐŗĐžĐģĐĩĐŧĐ¸Ņ Ņ„Đ°ĐšĐģ ĐžŅ‚ Đ˛ŅŅĐēа ĐŗŅ€ŅƒĐŋа и ҉Đĩ ĐŋŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž Đ˛ŅĐ¸Ņ‡Đēи Đ´Ņ€ŅƒĐŗĐ¸ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸.", "buy": "ĐšŅƒĐŋĐĩŅ‚Đĩ Immich", + "cache_settings_clear_cache_button": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ĐēĐĩŅˆĐ°", + "cache_settings_clear_cache_button_title": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ˛Đ° ĐēĐĩŅˆĐ° ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž. ĐĸОва ҉Đĩ ĐŋОвĐģĐ¸ŅĐĩ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž Đ´ĐžĐēĐ°Ņ‚Đž ĐēĐĩŅˆĐ° ĐŊĐĩ ĐąŅŠĐ´Đĩ ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐžŅ‚ĐŊОвО.", + "cache_settings_duplicated_assets_clear_button": "ИЗЧИСĐĸИ", + "cache_settings_duplicated_assets_subtitle": "ĐĄĐŊиĐŧĐēи и видĐĩа, ĐēĐžĐ¸Ņ‚Đž ŅĐ° в ĐĄĐŋĐ¸ŅŅŠĐēа Са Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€Đ°ĐŊĐĩ ĐžŅ‚ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", + "cache_settings_duplicated_assets_title": "Đ”ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊи ОйĐĩĐēŅ‚Đ¸ ({count})", + "cache_settings_statistics_album": "БибĐģĐ¸ĐžŅ‚ĐĩĐēа ҁ ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸", + "cache_settings_statistics_full": "ĐŸŅŠĐģĐŊи Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", + "cache_settings_statistics_shared": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊĐ¸Ņ‚Đĩ аĐģĐąŅƒĐŧи", + "cache_settings_statistics_thumbnail": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸", + "cache_settings_statistics_title": "ИСĐŋĐžĐģСваĐŊĐĩ ĐŊа ĐēĐĩŅˆĐ°", + "cache_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐēĐĩŅˆĐ¸Ņ€Đ°ĐŊĐĩŅ‚Đž в ĐŧОйиĐģĐŊĐžŅ‚Đž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ Immich", + "cache_settings_tile_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐģĐžĐēаĐģĐŊĐžŅ‚Đž Ņ…Ņ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ", + "cache_settings_tile_title": "ЛоĐēаĐģĐŊĐž Ņ…Ņ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ", + "cache_settings_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐēĐĩŅˆĐ¸Ņ€Đ°ĐŊĐĩ", "camera": "КаĐŧĐĩŅ€Đ°", "camera_brand": "ĐœĐ°Ņ€Đēа ĐŊа ĐēаĐŧĐĩŅ€Đ°Ņ‚Đ°", "camera_model": "МодĐĩĐģ ĐŊа ĐēаĐŧĐĩŅ€Đ°Ņ‚Đ°", @@ -678,10 +729,10 @@ "cannot_update_the_description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩŅ‚Đž ĐŊĐĩ ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ ОйĐŊОвĐĩĐŊĐž", "cast": "ĐŸĐžŅ‚ĐžŅ‡ĐŊĐž ĐŋŅ€ĐĩдаваĐŊĐĩ", "cast_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐŊаĐģĐ¸Ņ‡ĐŊĐ¸Ņ‚Đĩ ҆ĐĩĐģи Са ĐŋŅ€ĐĩдаваĐŊĐĩ", - "change": "ĐŸŅ€ĐžĐŧĐĩĐŊи", "change_date": "ĐŸŅ€ĐžĐŧĐĩĐŊи Đ´Đ°Ņ‚Đ°Ņ‚Đ°", "change_description": "ĐŸŅ€ĐžĐŧĐĩĐŊи ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩŅ‚Đž", "change_display_order": "ĐŸŅ€ĐžĐŧĐĩĐŊи Ņ€Đĩда ĐŊа ĐŋĐžĐēаСваĐŊĐĩ", + "change_expiration_time": "ĐŸŅ€ĐžĐŧĐĩĐŊĐĩŅ‚Đĩ Đ˛Ņ€ĐĩĐŧĐĩŅ‚Đž ĐŊа Đ¸ĐˇŅ‚Đ¸Ņ‡Đ°ĐŊĐĩ", "change_location": "ĐŸŅ€ĐžĐŧĐĩĐŊи ĐģĐžĐēĐ°Ņ†Đ¸ŅŅ‚Đ°", "change_name": "ĐŸŅ€ĐžĐŧĐĩĐŊи иĐŧĐĩŅ‚Đž", "change_name_successfully": "ĐŖŅĐŋĐĩ҈ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа ĐŊа иĐŧĐĩŅ‚Đž", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "ĐŸĐ°Ņ€ĐžĐģĐ¸Ņ‚Đĩ ĐŊĐĩ ŅŅŠĐ˛ĐŋĐ°Đ´Đ°Ņ‚", "change_password_form_reenter_new_password": "ĐŸĐžĐ˛Ņ‚ĐžŅ€Đ¸ ĐŊĐžĐ˛Đ°Ņ‚Đ° ĐŋĐ°Ņ€ĐžĐģа", "change_pin_code": "ĐĄĐŧĐĩĐŊи PIN ĐēОда", + "change_trigger": "ĐŸŅ€ĐžĐŧŅĐŊа ĐŊа Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Đ°", + "change_trigger_prompt": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Đ°? ĐĸОва ҉Đĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩ Đ˛ŅĐ¸Ņ‡Đēи ĐŊаĐģĐ¸Ņ‡ĐŊи Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ и Ņ„Đ¸ĐģŅ‚Ņ€Đ¸.", "change_your_password": "ĐŸŅ€ĐžĐŧĐĩĐŊĐĩŅ‚Đĩ ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ° ŅĐ¸", "changed_visibility_successfully": "ВидиĐŧĐžŅŅ‚Ņ‚Đ° Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊа ҃ҁĐŋĐĩ҈ĐŊĐž", "charging": "ĐŸŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ", "charging_requirement_mobile_backup": "ФОĐŊОвО Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ŅĐ°ĐŧĐž ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", + "check_corrupt_asset_backup": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸ Са ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊи Đ°Ņ€Ņ…Đ¸Đ˛ĐŊи ĐēĐžĐŋĐ¸Ņ", + "check_corrupt_asset_backup_button": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸", + "check_corrupt_asset_backup_description": "ИСĐŋҊĐģĐŊи Ņ‚Đ°ĐˇĐ¸ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēа ŅĐ°ĐŧĐž ĐŋŅ€Đ¸ Wi-Fi и ҁĐģĐĩĐ´ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи ОйĐĩĐēŅ‚Đ¸. ĐŸŅ€ĐžŅ†ĐĩĐ´ŅƒŅ€Đ°Ņ‚Đ° ĐŧĐžĐļĐĩ да ĐŋŅ€ĐžĐ´ŅŠĐģĐļи ĐŊŅĐēĐžĐģĐēĐž ĐŧиĐŊŅƒŅ‚Đ¸.", "check_logs": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸ ĐģĐžĐŗĐžĐ˛ĐĩŅ‚Đĩ", "checksum": "КоĐŊŅ‚Ņ€ĐžĐģĐŊа ҁ҃Đŧа", - "choose": "ИСйeŅ€Đ¸", "choose_matching_people_to_merge": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐŋĐžĐ´Ņ…ĐžĐ´ŅŅ‰Đ¸ Ņ…ĐžŅ€Đ° Са ҁĐģиваĐŊĐĩ", "city": "Đ“Ņ€Đ°Đ´", "cleanup_confirm_description": "Immich ĐŊаĐŧĐĩŅ€Đ¸ {count} ОйĐĩĐēŅ‚Đ° (ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŊи ĐŋŅ€Đĩди {date}), ĐēĐžĐ¸Ņ‚Đž ŅĐ° Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°. Да ҁĐĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚ Đģи ĐģĐžĐēаĐģĐŊĐ¸Ņ‚Đĩ ĐēĐžĐŋĐ¸Ņ ĐžŅ‚ Ņ‚ĐžĐ˛Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž?", @@ -719,10 +774,11 @@ "clear": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸", "clear_all": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ Đ˛ŅĐ¸Ņ‡ĐēĐž", "clear_all_recent_searches": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚ĐĩŅ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи ҁĐēĐžŅ€ĐžŅˆĐŊи Ņ‚ŅŠŅ€ŅĐĩĐŊĐ¸Ņ", - "clear_failed_count": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‡Đ¸ŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ({count})", "clear_file_cache": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ĐēĐĩŅˆĐ° ĐŊа Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ", "clear_message": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ŅŅŠĐžĐąŅ‰ĐĩĐŊиĐĩŅ‚Đž", "clear_value": "Đ˜ĐˇŅ‡Đ¸ŅŅ‚Đ¸ ŅŅ‚ĐžĐšĐŊĐžŅŅ‚Ņ‚Đ°", + "client_cert_dialog_msg_confirm": "ОК", + "client_cert_enter_password": "Đ’ŅŠĐ˛Đĩди ĐŋĐ°Ņ€ĐžĐģа", "client_cert_import": "ИĐŧĐŋĐžŅ€Ņ‚", "client_cert_import_success_msg": "КĐģиĐĩĐŊ҂ҁĐēĐ¸Ņ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ Đĩ иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊ", "client_cert_invalid_msg": "НĐĩваĐģидĐĩĐŊ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ иĐģи ĐŗŅ€Đĩ҈ĐŊа ĐŋĐ°Ņ€ĐžĐģа", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "КĐģиĐĩĐŊ҂ҁĐēĐ¸Ņ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ Đĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚", "client_cert_subtitle": "ĐŸĐžĐ´Đ´ŅŠŅ€Đļа ҁĐĩ ŅĐ°ĐŧĐž Ņ„ĐžŅ€ĐŧĐ°Ņ‚ PKCS12 (.p12, .pfx). ИĐŧĐŋĐžŅ€Ņ‚/ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ ĐŧĐžĐļĐĩ ŅĐ°ĐŧĐž ĐŋŅ€Đĩди вĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩ в ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°", "client_cert_title": "КĐģиĐĩĐŊ҂ҁĐēи SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ [ЕКСПЕРИМЕНĐĸАЛНО]", + "clockwise": "По Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžĐ˛Đ°Ņ‚Đ° ҁ҂ҀĐĩĐģĐēа", "close": "Đ—Đ°Ņ‚Đ˛ĐžŅ€Đ¸", "collapse": "ХвиваĐŊĐĩ", "collapse_all": "ХвиваĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡ĐēĐž", "color": "ĐĻĐ˛ŅŅ‚", + "color_theme": "ĐĻвĐĩŅ‚ĐžĐ˛Đ° Ņ‚ĐĩĐŧа", "command": "КоĐŧаĐŊда", "command_palette_prompt": "Đ‘ŅŠŅ€ĐˇĐž ĐŊаĐŧĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ¸, Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ иĐģи ĐēĐžĐŧаĐŊди", "command_palette_to_close": "ĐˇĐ°Ņ‚Đ˛ĐžŅ€Đ¸", @@ -747,7 +805,6 @@ "comments_are_disabled": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸Ņ‚Đĩ ŅĐ° Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи", "common_create_new_album": "ĐĄŅŠĐˇĐ´Đ°Đš ĐŊОв аĐģĐąŅƒĐŧ", "completed": "Đ—Đ°Đ˛ŅŠŅ€ŅˆĐĩĐŊĐž", - "configuration": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ", "confirm": "ĐŸĐžŅ‚Đ˛ŅŠŅ€Đ´Đ¸", "confirm_admin_password": "ĐŸĐžŅ‚Đ˛ŅŠŅ€ĐļдаваĐŊĐĩ ĐŊа ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ° ĐŊа адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "confirm_delete_face": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ ĐģĐ¸Ņ†ĐĩŅ‚Đž ĐŊа {name} ĐžŅ‚ аĐēŅ‚Đ¸Đ˛Đ°?", @@ -762,16 +819,19 @@ "contain": "В Ņ€Đ°ĐŧĐēĐ¸Ņ‚Đĩ ĐŊа", "context": "КоĐŊŅ‚ĐĩĐēҁ҂", "continue": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļи", - "control_bottom_app_bar_add_tags": "Đ”ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸", + "control_bottom_app_bar_create_new_album": "ĐĄŅŠĐˇĐ´Đ°Đš ĐŊОв аĐģĐąŅƒĐŧ", + "control_bottom_app_bar_delete_from_immich": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ Immich ŅŅŠŅ€ŅŠŅ€Đ°", "control_bottom_app_bar_delete_from_local": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", "control_bottom_app_bar_edit_location": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°Đš ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", "control_bottom_app_bar_edit_time": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°Đš Đ”Đ°Ņ‚Đ° и Đ§Đ°Ņ", + "control_bottom_app_bar_share_link": "ĐĄĐŋОдĐĩĐģи Đ˛Ņ€ŅŠĐˇĐēа", + "control_bottom_app_bar_share_to": "ĐĄĐŋОдĐĩĐģи ҁ", "control_bottom_app_bar_trash_from_immich": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ в ĐēĐžŅˆĐ°", "copied_image_to_clipboard": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž Đĩ ĐēĐžĐŋĐ¸Ņ€Đ°ĐŊĐž в ĐēĐģиĐŋĐąĐžŅ€Đ´Đ°.", "copied_to_clipboard": "КоĐŋĐ¸Ņ€Đ°ĐŊĐž в ĐēĐģиĐŋĐąĐžŅ€Đ´Đ°!", "copy_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐēĐžĐŋĐ¸Ņ€Đ°ĐŊĐĩŅ‚Đž", + "copy_file_path": "КоĐŋĐ¸Ņ€Đ°Đš ĐŋŅŠŅ‚Ņ ĐŊа Ņ„Đ°ĐšĐģа", "copy_image": "КоĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž", - "copy_json": "КоĐŋĐ¸Ņ€Đ°Đš JSON", "copy_link": "КоĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐģиĐŊĐē", "copy_link_to_clipboard": "КоĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ° в ĐēĐģиĐŋĐąĐžŅ€Đ´Đ°", "copy_password": "КоĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐ°Ņ€ĐžĐģа", @@ -789,6 +849,7 @@ "create_link_to_share": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐģиĐŊĐē Са ҁĐŋОдĐĩĐģŅĐŊĐĩ", "create_link_to_share_description": "ПозвоĐģĐĩŅ‚Đĩ ĐŊа Đ˛ŅĐĩĐēи, ĐēĐžĐšŅ‚Đž иĐŧа ĐģиĐŊĐē, да види Đ¸ĐˇĐąŅ€Đ°ĐŊĐ°Ņ‚Đ°(Đ¸Ņ‚Đĩ) ҁĐŊиĐŧĐēа(и)", "create_new": "ĐĄĐĒЗДАЙ НОВ", + "create_new_face": "ĐĄŅŠĐˇĐ´Đ°Đš ĐŊОвО ĐģĐ¸Ņ†Đĩ", "create_new_person": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐŊОвО ĐģĐ¸Ņ†Đĩ", "create_new_person_hint": "ĐŸŅ€Đ¸ŅĐ˛ĐžĐšŅ‚Đĩ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ ĐŊа ĐŊОв Ņ‡ĐžĐ˛ĐĩĐē", "create_new_user": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ĐŊОв ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", @@ -805,33 +866,37 @@ "created_at": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ", "creating_linked_albums": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ŅĐ˛ŅŠŅ€ĐˇĐ°ĐŊи аĐģĐąŅƒĐŧи...", "crop": "Đ˜ĐˇŅ€ĐĩĐļи", + "crop_aspect_ratio_fixed": "ФиĐēŅĐ¸Ņ€Đ°ĐŊ", "crop_aspect_ratio_free": "ХвОйОдĐĩĐŊ", "crop_aspect_ratio_original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐĩĐŊ", "crop_aspect_ratio_square": "ĐšĐ˛Đ°Đ´Ņ€Đ°Ņ‚", + "curated_object_page_title": "НĐĩŅ‰Đ°", "current_device": "ĐĸĐĩĐēŅƒŅ‰Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž", "current_pin_code": "ĐĄĐĩĐŗĐ°ŅˆĐĩĐŊ PIN ĐēОд", "current_server_address": "ĐĐ°ŅŅ‚ĐžŅŅ‰ Đ°Đ´Ņ€Đĩҁ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "custom_date": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊа Đ´Đ°Ņ‚Đ°", "custom_locale": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊи ĐĩСиĐēОви ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ´Đ°Ņ‚Đ°, Đ˛Ņ€ĐĩĐŧĐĩ и Ņ‡Đ¸ŅĐģа в ĐˇĐ°Đ˛Đ¸ŅĐ¸ĐŧĐžŅŅ‚ ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ ĐĩСиĐē и Ņ€ĐĩĐŗĐ¸ĐžĐŊ", + "custom_url": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL Đ°Đ´Ņ€Đĩҁ", "cutoff_date_description": "ЗаĐŋаСваĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи ĐžŅ‚ ĐŋĐžŅĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩâ€Ļ", "cutoff_day": "{count, plural, one {Đ´ĐĩĐŊ} other {Đ´ĐŊи}}", "cutoff_year": "{count, plural, one {ĐŗĐžĐ´Đ¸ĐŊа} other {ĐŗĐžĐ´Đ¸ĐŊи}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM yyyy", "dark": "ĐĸҊĐŧĐĩĐŊ", "dark_theme": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ Ņ‚ŅŠĐŧĐŊа Ņ‚ĐĩĐŧа", "date": "Đ”Đ°Ņ‚Đ°", "date_after": "Đ”Đ°Ņ‚Đ° ҁĐģĐĩĐ´", "date_and_time": "Đ”Đ°Ņ‚Đ° и Ņ‡Đ°Ņ", "date_before": "Đ”Đ°Ņ‚Đ° ĐŋŅ€Đĩди", - "date_of_birth": "Đ”Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ", + "date_format": "E, d LLL y â€ĸ h:mm a", "date_of_birth_saved": "Đ”Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ Đĩ СаĐŋĐ¸ŅĐ°ĐŊа ҃ҁĐŋĐĩ҈ĐŊĐž", "date_range": "ПĐĩŅ€Đ¸ĐžĐ´ ĐžŅ‚ Đ˛Ņ€ĐĩĐŧĐĩ", - "date_time_original": "Đ”Đ°Ņ‚Đ°/Đ§Đ°Ņ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģа", "day": "ДĐĩĐŊ", "days": "ДĐŊи", "deduplicate_all": "ДĐĩĐ´ŅƒĐŋĐģиĐēĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи", - "default_quality_subtitle": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€Đ¸ ҁĐŋОдĐĩĐģŅĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģОвĐĩ. Đ—Đ°Đ´Ņ€ŅŠĐļŅ‚Đĩ ĐąŅƒŅ‚ĐžĐŊа Са ҁĐŋОдĐĩĐģŅĐŊĐĩ, Са да иСйĐĩŅ€ĐĩŅ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž.", - "default_share_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ ĐŋŅ€Đ¸ ҁĐŋОдĐĩĐģŅĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģОвĐĩ", + "default_locale": "ЕзиĐē ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ", + "default_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ ĐŊа Đ´Đ°Ņ‚Đ° и Ņ‡Đ¸ŅĐģа ҁĐŋĐžŅ€ĐĩĐ´ ĐĩСиĐēĐžĐ˛Đ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°", "delete": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš", "delete_action_confirmation_message": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚ĐžĐˇĐ¸ ОйĐĩĐēŅ‚? ĐĄĐģĐĩдва ĐŋŅ€ĐĩĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ° в ĐēĐžŅˆĐ° Са ĐžŅ‚ĐŋĐ°Đ´ŅŠŅ†Đ¸ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ҉Đĩ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚Đĩ ĐŋŅ€ĐĩĐ´ĐģĐžĐļĐĩĐŊиĐĩ ОйĐĩĐēŅ‚Đ° да ĐąŅŠĐ´Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚ ĐģĐžĐēаĐģĐŊĐž", "delete_action_prompt": "{count} ŅĐ° Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸", @@ -840,6 +905,8 @@ "delete_dialog_alert": "ĐĸĐĩСи ОйĐĩĐēŅ‚Đ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸ и ĐžŅ‚ Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", "delete_dialog_alert_local": "ĐĸĐĩСи ОйĐĩĐēŅ‚Đ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž, ĐŊĐž ҉Đĩ ĐžŅŅ‚Đ°ĐŊĐ°Ņ‚ ĐŊа Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "delete_dialog_alert_local_non_backed_up": "ĐŅĐēОи ĐžŅ‚ Ņ‚ĐĩСи ОйĐĩĐēŅ‚Đ¸ ĐŊĐĩ ŅĐ° Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи ĐŊа Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸ ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", + "delete_dialog_alert_remote": "ĐĸĐĩСи ОйĐĩĐēŅ‚Đ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸ ĐžŅ‚ Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", + "delete_dialog_ok_force": "Đ’ŅŠĐŋŅ€ĐĩĐēи Ņ‚ĐžĐ˛Đ° Đ¸ĐˇŅ‚Ņ€Đ¸Đš", "delete_dialog_title": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš СавиĐŊĐ°ĐŗĐ¸", "delete_duplicates_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ ĐžĐēĐžĐŊŅ‡Đ°Ņ‚ĐĩĐģĐŊĐž Ņ‚ĐĩСи Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸?", "delete_face": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš ĐģĐ¸Ņ†Đĩ", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚Đ°ĐŗĐ° {tagName}?", "delete_user": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "deleted_shared_link": "Đ˜ĐˇŅ‚Ņ€Đ¸Ņ‚ ҁĐŋОдĐĩĐģĐĩĐŊ ĐģиĐŊĐē", + "deletes_missing_assets": "Đ˜ĐˇŅ‚Ņ€Đ¸Đ˛Đ° Ņ„Đ°ĐšĐģОвĐĩ, ĐēĐžĐ¸Ņ‚Đž ĐģиĐŋŅĐ˛Đ°Ņ‚ ĐŊа Đ´Đ¸ŅĐēа", "description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "description_input_hint_text": "Добави ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ...", + "description_input_submit_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩŅ‚Đž. За ĐŋĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ виĐļŅ‚Đĩ в Đ´ĐŊĐĩвĐŊиĐēа", "deselect_all": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи Đ¸ĐˇĐąĐžŅ€Đ° ĐžŅ‚ Đ˛ŅĐ¸Ņ‡Đēи", "details": "ДĐĩŅ‚Đ°ĐšĐģи", "direction": "ĐŸĐžŅĐžĐēа", "disable": "Đ—Đ°ĐąŅ€Đ°ĐŊи", "disabled": "ИСĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "disallow_edits": "Đ—Đ°ĐąŅ€Đ°ĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩдаĐēŅ†Đ¸Đ¸Ņ‚Đĩ", "discord": "НаĐŧĐĩŅ€Đ¸ ĐŊи в Discord", "discover": "ĐžŅ‚ĐēŅ€Đ¸Đš", "discovered_devices": "ĐžŅ‚ĐēŅ€Đ¸Ņ‚Đ¸ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "dismiss_all_errors": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģŅĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи ĐŗŅ€Đĩ҈Đēи", + "dismiss_error": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģŅĐŊĐĩ ĐŊа ĐŗŅ€Đĩ҈Đēа", "display_options": "ОĐŋŅ†Đ¸Đ¸ Са ĐŋĐžĐēаСваĐŊĐĩ", "display_order": "Đ ĐĩĐ´ ĐŊа ĐŋĐžĐēаСваĐŊĐĩ", "display_original_photos": "ПоĐēаСваĐŊĐĩ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊи ҁĐŊиĐŧĐēи", @@ -876,9 +948,11 @@ "documentation": "ДоĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ", "done": "Đ“ĐžŅ‚ĐžĐ˛Đž", "download": "Đ˜ĐˇŅ‚ĐĩĐŗĐģи", + "download_action_prompt": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа {count} ОйĐĩĐēŅ‚Đ°", "download_canceled": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž Đĩ ĐžŅ‚ĐŧĐĩĐŊĐĩĐŊĐž", "download_complete": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ¸", "download_enqueue": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž Đĩ дОйавĐĩĐŊĐž в ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", + "download_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", "download_failed": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", "download_finished": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ¸", "download_include_embedded_motion_videos": "Đ’ĐŗŅ€Đ°Đ´ĐĩĐŊи видĐĩа", @@ -888,15 +962,15 @@ "download_paused": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž Đĩ ĐŊа ĐŋĐ°ŅƒĐˇĐ°", "download_settings": "Đ˜ĐˇŅ‚ĐĩĐŗĐģи", "download_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ, ŅĐ˛ŅŠŅ€ĐˇĐ°ĐŊи ҁ Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž ĐŊа Ņ„Đ°ĐšĐģОвĐĩ", + "download_started": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩŅ‚Đž СаĐŋĐžŅ‡ĐŊа", + "download_sucess": "ĐŖŅĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", + "download_sucess_android": "ФаКĐģОвĐĩŅ‚Đĩ ŅĐ° Đ¸ĐˇŅ‚ĐĩĐŗĐģĐĩĐŊи в DCIM/Immich", "download_waiting_to_retry": "Đ˜ĐˇŅ‡Đ°ĐēваĐŊĐĩ Са ĐŋĐžĐ˛Ņ‚ĐžŅ€ĐĩĐŊиĐĩ", "downloading": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", "downloading_asset_filename": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģ {filename}", "downloading_from_icloud": "ХваĐģŅĐŊĐĩ ĐžŅ‚ iCloud", "downloading_media": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ ĐŊа ĐŧĐĩĐ´Đ¸Ņ", - "drag_to_reorder": "ПĐģŅŠĐˇĐŊĐĩŅ‚Đĩ, Са да ĐŋŅ€ĐĩĐŊĐ°Ņ€ĐĩĐ´Đ¸Ņ‚Đĩ", "drop_files_to_upload": "ĐŸŅƒŅĐŊĐĩŅ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩŅ‚Đĩ, Са да ĐŗĐ¸ ĐēĐ°Ņ‡Đ¸Ņ‚Đĩ", - "duplicate": "НаĐŋŅ€Đ°Đ˛Đ¸ ĐēĐžĐŋиĐĩ", - "duplicate_workflow": "Đ”ŅƒĐąĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ€Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ", "duplicates": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", "duplicates_description": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛ŅŅĐēа ĐŗŅ€ŅƒĐŋа, ĐēĐ°Ņ‚Đž ĐŋĐžŅĐžŅ‡Đ¸Ņ‚Đĩ ĐēОи, аĐēĐž иĐŧа Ņ‚Đ°Đēива, ŅĐ° Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸.", "duration": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚", @@ -908,7 +982,9 @@ "edit_date_and_time": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ´Đ°Ņ‚Đ° и Ņ‡Đ°Ņ", "edit_date_and_time_action_prompt": "{count} Đ´Đ°Ņ‚Đ° и Đ˛Ņ€ĐĩĐŧĐĩ ŅĐ° Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊи", "edit_date_and_time_by_offset": "ĐŸŅ€ĐžĐŧŅĐŊа ĐŊа Đ´Đ°Ņ‚Đ°Ņ‚Đ° ҇ҀĐĩС ĐžŅ‚ĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ", + "edit_date_and_time_by_offset_interval": "Нов ĐŋĐĩŅ€Đ¸ĐžĐ´ ĐžŅ‚ Đ˛Ņ€ĐĩĐŧĐĩ: {from} - {to}", "edit_description": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°Đš ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "edit_description_prompt": "МоĐģŅ, иСйĐĩŅ€Đ¸ ĐŊОвО ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ:", "edit_exclusion_pattern": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ŅˆĐ°ĐąĐģĐžĐŊ Са иСĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ", "edit_faces": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐģĐ¸Ņ†Đ°", "edit_key": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐēĐģŅŽŅ‡", @@ -923,6 +999,9 @@ "edit_user": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "edit_workflow": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ€Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ", "editor": "Đ ĐĩдаĐēŅ‚ĐžŅ€", + "editor_close_without_save_prompt": "ĐŸŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ ĐŊŅĐŧа да ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи", + "editor_close_without_save_title": "Đ—Đ°Ņ‚Đ˛Đ°Ņ€ŅĐŊĐĩ ĐŊа Ņ€ĐĩдаĐēŅ‚ĐžŅ€Đ°?", + "editor_confirm_reset_all_changes": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи ĐŋŅ€ĐžĐŧĐĩĐŊи?", "editor_discard_edits_confirm": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€Đģи ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "editor_discard_edits_prompt": "ИĐŧĐ°Ņ‚Đĩ ĐŊĐĩСаĐŋаСĐĩĐŊи ĐŋŅ€ĐžĐŧĐĩĐŊи. ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŗĐ¸ ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģĐ¸Ņ‚Đĩ?", "editor_discard_edits_title": "ĐžŅ‚Ņ…Đ˛ŅŠŅ€ĐģŅĐŧĐĩ Đģи ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Đ’ŅŠĐ˛ĐĩĐ´ĐĩŅ‚Đĩ Đ˛Đ°ŅˆĐ¸Ņ PIN ĐēОд", "enter_your_pin_code_subtitle": "Đ’ŅŠĐ˛ĐĩĐļдаĐŊĐĩ ĐŊа PIN ĐēОд, Са Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž СаĐēĐģŅŽŅ‡ĐĩĐŊа ĐŋаĐŋĐēа", "error": "Đ“Ņ€Đĩ҈Đēа", + "error_change_sort_album": "НĐĩ҃ҁĐŋĐĩ҈ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа ĐŊа Ņ€Đĩда ĐŊа ŅĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧ", "error_delete_face": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа ĐģĐ¸Ņ†Đĩ ĐžŅ‚ аĐēŅ‚Đ¸Đ˛Đ°", + "error_getting_places": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ŅŅŠĐąĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŧĐĩŅŅ‚Đ°Ņ‚Đ°", "error_loading_albums": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи", "error_loading_image": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž", "error_loading_partners": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚ĐžĐ˛Đ¸Ņ ĐēĐģŅŽŅ‡", "failed_to_reset_pin_code": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ПИН ĐēОда", "failed_to_stack_assets": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋĐžĐ´Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸", - "failed_to_tag_assets": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ĐĩŅ‚Đ¸ĐēĐĩŅ‚", "failed_to_unstack_assets": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐžĐ´Ņ€ĐĩĐ´ĐąĐ°Ņ‚Đ° ĐŊа ОйĐĩĐēŅ‚Đ¸", "failed_to_update_notification_status": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ŅŅŠŅŅ‚ĐžŅĐŊиĐĩŅ‚Đž ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", "incorrect_email_or_password": "НĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģĐĩĐŊ иĐŧĐĩĐšĐģ иĐģи ĐŋĐ°Ņ€ĐžĐģа", @@ -1090,10 +1170,18 @@ "exif": "Еxif", "exif_bottom_sheet_description": "Добави ОĐŋĐ¸ŅĐ°ĐŊиĐĩ...", "exif_bottom_sheet_description_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "exif_bottom_sheet_details": "ПОДРОБНОСĐĸИ", + "exif_bottom_sheet_location": "ĐœĐ¯ĐĄĐĸО", "exif_bottom_sheet_no_description": "ĐŅĐŧа ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "exif_bottom_sheet_people": "ĐĨОРА", + "exif_bottom_sheet_person_add_person": "Добави иĐŧĐĩ", "exit_slideshow": "Đ˜ĐˇŅ…ĐžĐ´ ĐžŅ‚ ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž", "expand": "Đ Đ°ĐˇĐŗŅŠĐŊи", "expand_all": "Đ Đ°ĐˇŅˆĐ¸Ņ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи", + "experimental_settings_new_asset_list_subtitle": "В Ņ€Đ°ĐˇĐ˛Đ¸Ņ‚Đ¸Đĩ", + "experimental_settings_new_asset_list_title": "ВĐēĐģŅŽŅ‡Đ¸ ĐĩĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊа ĐŋĐžĐ´Ņ€Đĩдйа ĐŊа ҁĐŊиĐŧĐēи", + "experimental_settings_subtitle": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ ĐŊа ŅĐ˛ĐžĐš Ņ€Đ¸ŅĐē!", + "experimental_settings_title": "ЕĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊи", "expire_after": "Đ˜ĐˇŅ‚Đ¸Ņ‡Đ° ҁĐģĐĩĐ´", "expired": "Đ˜ĐˇŅ‚ĐĩĐēĐģĐž", "expires_date": "Đ˜ĐˇŅ‚Đ¸Ņ‡Đ° {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "ЕĐēҁĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐēĐ°Ņ‚Đž JSON", "export_database": "ЕĐēҁĐŋĐžŅ€Ņ‚ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи", "export_database_description": "ЕĐēҁĐŋĐžŅ€Ņ‚ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite", - "exposure_time": "Đ’Ņ€ĐĩĐŧĐĩ ĐŊа ĐĩĐēҁĐŋĐžĐˇĐ¸Ņ†Đ¸Ņ", "extension": "Đ Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊиĐĩ", "external": "Đ’ŅŠĐŊ҈ĐŊĐž", "external_libraries": "Đ’ŅŠĐŊ҈ĐŊи йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "external_network": "Đ’ŅŠĐŊ҈ĐŊа ĐŧŅ€ĐĩĐļа", "external_network_sheet_info": "ĐšĐžĐŗĐ°Ņ‚Đž ĐŊŅĐŧа Đ˛Ņ€ŅŠĐˇĐēа ҁ ĐŋŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐ°Ņ‚Đ° Wi-Fi ĐŧŅ€ĐĩĐļа, ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž ҉Đĩ ĐžĐŋĐ¸Ņ‚Đ˛Đ° да ҁĐĩ ŅĐ˛ŅŠŅ€ĐļĐĩ ҁҊҁ ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҇ҀĐĩС ĐŋŅŠŅ€Đ˛Đ¸Ņ Đ´ĐžŅŅ‚ŅŠĐŋĐĩĐŊ URL Đ°Đ´Ņ€Đĩҁ, СаĐŋĐžŅ‡Đ˛Đ°ĐšĐēи ĐžŅ‚ĐŗĐžŅ€Đĩ ĐŊадОĐģ҃", - "f_number": "F-Ņ‡Đ¸ŅĐģĐž", "face_unassigned": "НĐĩСададĐĩĐŊĐž", "failed": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž", "failed_count": "НĐĩ҃ҁĐŋĐĩ҈ĐŊи: {count}", "failed_to_authenticate": "НĐĩ҃ҁĐŋĐĩ҈ĐŊа Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸ĐēĐ°Ņ†Đ¸Ņ", - "failed_to_delete_file": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģ", "failed_to_load_assets": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "failed_to_load_folder": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐŋаĐŋĐēа", "favorite": "Đ›ŅŽĐąĐ¸Đŧ", "favorite_action_prompt": "{count} ŅĐ° дОйавĐĩĐŊи в Đ›ŅŽĐąĐ¸Đŧи", "favorite_or_unfavorite_photo": "Добави иĐģи ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊи ҁĐŊиĐŧĐēа ĐžŅ‚ Đ›ŅŽĐąĐ¸Đŧи", "favorites": "Đ›ŅŽĐąĐ¸Đŧи", + "favorites_page_no_favorites": "НĐĩ ŅĐ° ĐŊаĐŧĐĩŅ€ĐĩĐŊи ĐģŅŽĐąĐ¸Đŧи ОйĐĩĐēŅ‚Đ¸", "feature_photo_updated": "ĐŸŅ€ĐĩĐ´ŅŅ‚Đ°Đ˛Đ¸Ņ‚ĐĩĐģĐŊĐ°Ņ‚Đ° ҁĐŊиĐŧĐēа Đĩ ОйĐŊОвĐĩĐŊа", "features": "Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸", + "features_in_development": "Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸ в ĐŋŅ€ĐžŅ†Đĩҁ ĐŊа Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚Đēа", "features_setting_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", "file_name_or_extension": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ иĐģи Ņ€Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊиĐĩ", "file_name_text": "ИĐŧe ĐŊа Ņ„Đ°ĐšĐģ", + "file_name_with_value": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ: {file_name}", "file_size": "РаСĐŧĐĩŅ€ ĐŊа Ņ„Đ°ĐšĐģа", "filename": "ИĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģ", "filetype": "ĐĸиĐŋ ĐŊа Ņ„Đ°ĐšĐģ", "filter": "ФиĐģŅ‚ŅŠŅ€", + "filter_description": "ĐŖŅĐģĐžĐ˛Đ¸Ņ Са Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ОйĐĩĐēŅ‚Đ¸", "filter_people": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ…ĐžŅ€Đ°", "filter_places": "ФиĐģŅ‚ŅŠŅ€ ĐŋĐž ĐŧŅŅŅ‚Đž", "filter_tags": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ĐŊĐĩ ĐŋĐž ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸", "filters": "ФиĐģŅ‚Ņ€Đ¸", + "find_them_fast": "НаĐŧĐĩŅ€ĐĩŅ‚Đĩ ĐŗĐ¸ ĐąŅŠŅ€ĐˇĐž ĐŋĐž иĐŧĐĩ ҁ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "first": "ĐŸŅŠŅ€Đ˛Đ¸", "fix_incorrect_match": "ПоĐŋŅ€Đ°Đ˛ŅĐŊĐĩ ĐŊа ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģĐŊĐž ŅŅŠĐ˛ĐŋадĐĩĐŊиĐĩ", - "focal_length": "ФОĐē҃ҁĐŊĐž Ņ€Đ°ĐˇŅŅ‚ĐžŅĐŊиĐĩ", "folder": "ПаĐŋĐēа", "folder_not_found": "ПаĐŋĐēĐ°Ņ‚Đ° ĐŊĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊа", "folders": "ПаĐŋĐēи", @@ -1147,7 +1236,6 @@ "free_up_space_description": "ĐŸŅ€ĐĩĐŧĐĩҁ҂ĐĩŅ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩа в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž, Са да ĐžŅĐ˛ĐžĐąĐžĐ´Đ¸Ņ‚Đĩ ĐŧŅŅŅ‚Đž. КоĐŋĐ¸ŅŅ‚Đ° ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ СаĐŋаСĐĩĐŊи.", "free_up_space_settings_subtitle": "ĐžŅĐ˛ĐžĐąĐžĐļдаваĐŊĐĩ ĐŊа ĐŧŅŅŅ‚Đž Са ŅŅŠŅ…Ņ€Đ°ĐŊĐĩĐŊиĐĩ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", "full_path": "ĐŸŅŠĐģĐĩĐŊ ĐŋŅŠŅ‚: {path}", - "full_path_or_folder": "ĐŸŅŠĐģĐĩĐŊ ĐŋŅŠŅ‚ иĐģи ĐŋаĐŋĐēа", "gcast_enabled": "GООgle Cast", "gcast_enabled_description": "За да Ņ€Đ°ĐąĐžŅ‚Đ¸ Ņ‚Đ°ĐˇĐ¸ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ ĐˇĐ°Ņ€ĐĩĐļда Đ˛ŅŠĐŊ҈ĐŊи Ņ€ĐĩŅŅƒŅ€ŅĐ¸ ĐžŅ‚ Google.", "general": "ĐžĐąŅ‰Đ¸", @@ -1168,6 +1256,7 @@ "group_owner": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŋĐž ŅĐžĐąŅŅ‚Đ˛ĐĩĐŊиĐē", "group_places_by": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Đš ĐŧĐĩŅŅ‚Đ° ĐŋĐžâ€Ļ", "group_year": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŋĐž ĐŗĐžĐ´Đ¸ĐŊа", + "haptic_feedback_switch": "ВĐēĐģŅŽŅ‡Đ¸ Ņ‚Đ°ĐēŅ‚Đ¸ĐģĐŊа ĐžĐąŅ€Đ°Ņ‚ĐŊа Đ˛Ņ€ŅŠĐˇĐēа", "haptic_feedback_title": "ĐĸаĐēŅ‚Đ¸ĐģĐŊа ĐžĐąŅ€Đ°Ņ‚ĐŊа Đ˛Ņ€ŅŠĐˇĐēа", "has_quota": "ЛиĐŧĐ¸Ņ‚", "hash_asset": "ОбĐĩĐēŅ‚ ҁ Ņ…Đĩ҈", @@ -1188,12 +1277,29 @@ "hide_schema": "ĐĄĐēŅ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа ҁ҅ĐĩĐŧĐ°Ņ‚Đ°", "hide_text_recognition": "ĐĄĐēŅ€Đ¸Đš Ņ€Đ°ĐˇĐŋОСĐŊĐ°Ņ‚Đ¸Ņ Ņ‚ĐĩĐēҁ҂", "hide_unnamed_people": "ĐĄĐēŅ€Đ¸Đš ĐŊĐĩĐŊаСОваĐŊи Ņ…ĐžŅ€Đ°", + "home_page_add_to_album_conflicts": "ДобавĐĩĐŊи ŅĐ° {added} ОйĐĩĐēŅ‚Đ° в аĐģĐąŅƒĐŧа {album}. ВĐĩ҇Đĩ иĐŧа {failed} ОйĐĩĐēŅ‚Đ°.", + "home_page_add_to_album_err_local": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ Đĩ Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐž да ҁĐĩ Đ´ĐžĐąĐ°Đ˛ŅŅ‚ ĐģĐžĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ в аĐģĐąŅƒĐŧĐ¸Ņ‚Đĩ, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_add_to_album_success": "ДобавĐĩĐŊи ŅĐ° {added} ОйĐĩĐēŅ‚Đ° в аĐģĐąŅƒĐŧа {album}.", + "home_page_album_err_partner": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ Đĩ Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐž да ҁĐĩ Đ´ĐžĐąĐ°Đ˛ŅŅ‚ ОйĐĩĐēŅ‚Đ¸ ĐŊа ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸ в аĐģĐąŅƒĐŧ, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_archive_err_local": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚ ĐģĐžĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_archive_err_partner": "НĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸ ĐŊа ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", "home_page_building_timeline": "Đ˜ĐˇĐŗŅ€Đ°ĐļдаĐŊĐĩ ĐŊа Đ˛Ņ€ĐĩĐŧĐĩĐ˛Đ°Ņ‚Đ° ĐģиĐŊĐ¸Ņ", + "home_page_delete_err_partner": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸ ĐŊа ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_delete_remote_err_local": "ЛоĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°Ņ‚ ĐžŅ‚ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_favorite_err_local": "ЛоĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ Đ˛ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ ĐŋŅ€Đ°Đ˛ŅŅ‚ ĐģŅŽĐąĐ¸Đŧи, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_favorite_err_partner": "ĐŸĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€ŅĐēи ОйĐĩĐēŅ‚Đ¸ Đ˛ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ ĐŋŅ€Đ°Đ˛ŅŅ‚ ĐģŅŽĐąĐ¸Đŧи, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_first_time_notice": "АĐēĐž Са ĐŋŅŠŅ€Đ˛Đ¸ ĐŋŅŠŅ‚ иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž, ĐŧĐžĐģŅ иСйĐĩŅ€ĐĩŅ‚Đĩ аĐģĐąŅƒĐŧ Са Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ, Са да ĐŧĐžĐļĐĩ ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Đ˛Ņ€ĐĩĐŧĐĩĐ˛Đ°Ņ‚Đ° ĐģиĐŊĐ¸Ņ да ҁĐĩ СаĐŋĐ¸ŅĐ˛Đ°Ņ‚ в ĐŊĐĩĐŗĐž", + "home_page_locked_error_local": "ЛоĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ ĐŋŅ€ĐĩĐŧĐĩŅŅ‚ŅŅ‚ в СаĐēĐģŅŽŅ‡ĐĩĐŊа ĐŋаĐŋĐēа, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_locked_error_partner": "ĐŸĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€ŅĐēи ОйĐĩĐēŅ‚Đ¸ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ ĐŋŅ€ĐĩĐŧĐĩŅŅ‚ŅŅ‚ в СаĐēĐģŅŽŅ‡ĐĩĐŊа ĐŋаĐŋĐēа, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_share_err_local": "ЛоĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸ ĐŊĐĩ ĐŧĐžĐŗĐ°Ņ‚ да ҁĐĩ ҁĐŋОдĐĩĐģŅŅ‚ ҇ҀĐĩС Đ˛Ņ€ŅŠĐˇĐēа, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "home_page_upload_err_limit": "МоĐļĐĩ да ĐēĐ°Ņ‡Đ˛Đ°Ņ‚Đĩ ĐŧаĐēŅĐ¸Đŧ҃Đŧ 30 ОйĐĩĐēŅ‚Đ° ĐĩĐ´ĐŊĐžĐ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐž, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", "host": "ĐĨĐžŅŅ‚", "hour": "Đ§Đ°Ņ", "hours": "Đ§Đ°ŅĐ°", "id": "ID", "idle": "БĐĩСдĐĩĐšŅŅ‚Đ˛Đ¸Đĩ", + "ignore_icloud_photos": "ĐŸŅ€ĐžĐŋ҃ҁĐŊи ҁĐŊиĐŧĐēи ĐžŅ‚ iCloud", + "ignore_icloud_photos_description": "ĐĄĐŊиĐŧĐēи, ĐēĐžĐ¸Ņ‚Đž ŅĐ° СаĐŋаСĐĩĐŊи в iCloud ĐŊŅĐŧа да ҁĐĩ ĐēĐ°Ņ‡Đ˛Đ°Ņ‚ в Immich ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "image": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", "image_alt_text_date": "{isVideo, select, true {ВидĐĩĐž} other {ĐĄĐŊиĐŧĐēа}} ĐˇĐ°ŅĐŊĐĩŅ‚Đž ĐŊа {date}", "image_alt_text_date_1_person": "{isVideo, select, true {ВидĐĩĐž} other {ĐĄĐŊиĐŧĐēа}} ĐˇĐ°ŅĐŊĐĩŅ‚Đž ҁ {person1} ĐŊа {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {ВидĐĩĐž} other {ĐĄĐŊиĐŧĐēа}} ĐˇĐ°ŅĐŊĐĩŅ‚Đž в {city}, {country} ҁ {person1} и {person2} ĐŊа {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {ВидĐĩĐž} other {ĐĄĐŊиĐŧĐēа}} ĐˇĐ°ŅĐŊĐĩŅ‚Đž ҁ {city}, {country} и {person1}, {person2}, и {person3} ĐŊа {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {ВидĐĩĐž} other {ĐĄĐŊиĐŧĐēа}} ĐˇĐ°ŅĐŊĐĩŅ‚Đž в {city}, {country} ҁ {person1}, {person2}, и {additionalCount, number} Đ´Ņ€ŅƒĐŗĐ¸ ĐŊа{date}", + "image_saved_successfully": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž Đĩ СаĐŋаСĐĩĐŊĐž", + "image_viewer_page_state_provider_download_started": "ЗаĐŋĐžŅ‡ĐŊа Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", + "image_viewer_page_state_provider_download_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž Đ¸ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ", + "image_viewer_page_state_provider_share_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ҁĐŋОдĐĩĐģŅĐŊĐĩ", "immich_logo": "Immich ĐģĐžĐŗĐž", "immich_web_interface": "ĐŖĐĩĐą иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ ĐŊа Immich", "import_from_json": "ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐžŅ‚ JSON", @@ -1219,7 +1329,6 @@ "individual_share": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģĐŊĐž ҁĐŋОдĐĩĐģŅĐŊĐĩ", "individual_shares": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģĐŊи ҁĐŋОдĐĩĐģŅĐŊĐ¸Ņ", "info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", - "integrity_checks": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа Са ĐŊĐĩĐŋĐžĐēŅŠŅ‚ĐŊĐ°Ņ‚ĐžŅŅ‚", "interval": { "day_at_onepm": "Đ’ŅĐĩĐēи Đ´ĐĩĐŊ в 13:00", "hours": "Đ’ŅĐĩĐēи {hours, plural, one {Ņ‡Đ°Ņ} other {{hours, number} Ņ‡Đ°ŅĐ°}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "НĐĩваĐģидĐĩĐŊ Ņ„ĐžŅ€ĐŧĐ°Ņ‚ Са Đ´Đ°Ņ‚Đ°", "invite_people": "ПоĐēаĐŊи Ņ…ĐžŅ€Đ°", "invite_to_album": "ПоĐēаĐŊи в аĐģĐąŅƒĐŧ", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Đ˜ĐˇŅ‚ĐĩĐŗĐģŅĐŊĐĩ ĐŊа {dateTime}", + "ios_debug_info_last_sync_at": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐž ĐŊа {dateTime}", + "ios_debug_info_no_processes_queued": "ĐŅĐŧа Ņ„ĐžĐŊОви ĐŋŅ€ĐžŅ†ĐĩŅĐ¸ в ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", + "ios_debug_info_no_sync_yet": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ Đĩ иСĐŋҊĐģĐŊŅĐ˛Đ°ĐŊа ĐˇĐ°Đ´Đ°Ņ‡Đ° Са Ņ„ĐžĐŊОва ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} Ņ„ĐžĐŊОв ĐŋŅ€ĐžŅ†Đĩҁ} many {{count} Ņ„ĐžĐŊОви ĐŋŅ€ĐžŅ†ĐĩŅĐ°} other {{count} Ņ„ĐžĐŊОви ĐŋŅ€ĐžŅ†ĐĩŅĐ°}} в ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", + "ios_debug_info_processing_ran_at": "ЗаĐŋĐžŅ‡ĐŊĐ°Ņ‚Đ° ĐžĐąŅ€Đ°ĐąĐžŅ‚Đēа ĐŊа {dateTime}", "items_count": "{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", "jobs": "Đ—Đ°Đ´Đ°Ņ‡Đ¸", + "json_editor": "JSON Ņ€ĐĩдаĐēŅ‚ĐžŅ€", + "json_error": "Đ“Ņ€Đĩ҈Đēа в JSON", "keep": "Đ—Đ°Đ´Ņ€ŅŠĐļ", "keep_albums": "ЗаĐŋаСи аĐģĐąŅƒĐŧи", "keep_albums_count": "ЗаĐŋаСваĐŊĐĩ ĐŊа {count} {count, plural, one {аĐģĐąŅƒĐŧ} other {аĐģĐąŅƒĐŧа}}", @@ -1259,12 +1375,14 @@ "leave": "ИСĐģĐĩС", "leave_album": "НаĐŋ҃ҁĐēаĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧа", "lens_model": "МодĐĩĐģ ĐģĐĩŅ‰Đ°", - "less": "По-ĐŧаĐģĐēĐž", "let_others_respond": "ПозвоĐģĐĩŅ‚Đĩ ĐŊа Đ´Ņ€ŅƒĐŗĐ¸Ņ‚Đĩ да ĐžŅ‚ĐŗĐžĐ˛ĐžŅ€ŅŅ‚", "level": "Ниво", "library": "БибĐģĐ¸ĐžŅ‚ĐĩĐēа", "library_add_folder": "Добави ĐŋаĐŋĐēа", "library_edit_folder": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋаĐŋĐēа", + "library_options": "ОĐŋŅ†Đ¸Đ¸ ĐŊа йийĐģĐ¸ĐžŅ‚ĐĩĐēĐ°Ņ‚Đ°", + "library_page_device_albums": "АĐģĐąŅƒĐŧи в ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", + "library_page_new_album": "Нов аĐģĐąŅƒĐŧ", "library_page_sort_asset_count": "Đ‘Ņ€ĐžĐš ОйĐĩĐēŅ‚Đ¸", "library_page_sort_created": "Đ”Đ°Ņ‚Đ° ĐŊа ŅŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ", "library_page_sort_last_modified": "ĐŸĐžŅĐģĐĩĐ´ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа", @@ -1274,14 +1392,11 @@ "light_theme": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ ŅĐ˛ĐĩŅ‚Đģа Ņ‚ĐĩĐŧа", "like": "ĐĨĐ°Ņ€ĐĩŅĐ°ĐšŅ‚Đĩ", "like_deleted": "ĐšĐ°Ņ‚Đž Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚", - "link": "Đ’Ņ€ŅŠĐˇĐēа", "link_motion_video": "ЛиĐŊĐē ĐēҊĐŧ видĐĩĐž", "link_to_docs": "За ĐŋОвĐĩ҇Đĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ виĐļŅ‚Đĩ Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸ŅŅ‚Đ°.", "link_to_oauth": "ЛиĐŊĐē ĐēҊĐŧ OAuth", "linked_oauth_account": "ĐĄĐ˛ŅŠŅ€ĐˇĐ°ĐŊ OAuth аĐēĐ°ŅƒĐŊŅ‚", "list": "Đ›Đ¸ŅŅ‚", - "live": "Đ–Đ¸Đ˛ĐžŅ‚", - "load_more": "Đ—Đ°Ņ€Đĩди ĐžŅ‰Đĩ", "loading": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ", "loading_search_results_failed": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŊа Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩŅ‚Đž Đĩ ĐŊĐĩ҃ҁĐŋĐĩ҈ĐŊĐž", "local": "ЛоĐēаĐģĐŊĐž", @@ -1310,9 +1425,11 @@ "login": "ВĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩ", "login_disabled": "ВĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩŅ‚Đž Đĩ ĐˇĐ°ĐąŅ€Đ°ĐŊĐĩĐŊĐž", "login_form_api_exception": "Đ“Ņ€Đĩ҈Đēа в ĐēĐžĐŧ҃ĐŊиĐēĐ°Ņ†Đ¸ŅŅ‚Đ°. МоĐģŅ, ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸ URL ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° и ĐžĐŋĐ¸Ņ‚Đ°Đš ĐŋаĐē.", + "login_form_back_button_text": "ĐžĐąŅ€Đ°Ņ‚ĐŊĐž", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://yĐžur-server-ip:port", "login_form_endpoint_url": "URL Đ°Đ´Ņ€Đĩҁ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", + "login_form_err_http": "МоĐģŅ, ĐžĐŋŅ€ĐĩĐ´ĐĩĐģи ĐŋŅ€ĐžŅ‚ĐžĐēĐžĐģа http:// иĐģи https://", "login_form_err_invalid_email": "НĐĩваĐģидĐĩĐŊ иĐŧĐĩĐšĐģ Đ°Đ´Ņ€Đĩҁ", "login_form_err_invalid_url": "НĐĩваĐģидĐĩĐŊ URL", "login_form_err_leading_whitespace": "ИĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ ĐžŅ‚ĐŋŅ€ĐĩĐ´", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ вĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩ, ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐĩŅ‚Đĩ URL, иĐŧĐĩĐšĐģа и ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ°", "login_form_handshake_exception": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ´ĐžĐŗĐžĐ˛Đ°Ņ€ŅĐŊĐĩ ĐŊа Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ° ҁҊҁ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°. АĐēĐž иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚, Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩŅ‚Đĩ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēŅ‚Đĩ иСĐŋĐžĐģСваĐŊĐĩ ĐŊа ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚.", "login_form_password_hint": "ĐŋĐ°Ņ€ĐžĐģа", - "login_form_server_empty": "Đ’ŅŠĐ˛Đĩди URL ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", - "login_form_server_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐ˛ŅŠŅ€ĐˇĐ˛Đ°ĐŊĐĩ ҁҊҁ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", - "login_has_been_disabled": "ВĐģиСаĐŊĐĩŅ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž", + "login_form_save_login": "ĐžŅŅ‚Đ°ĐŊи вĐŋĐ¸ŅĐ°ĐŊ", + "login_form_server_empty": "Đ’ŅŠĐ˛Đĩди URL ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ°.", + "login_form_server_error": "НĐĩ ĐŧĐžĐļĐ°Ņ… да ҁĐĩ ŅĐ˛ŅŠŅ€Đļа ҁҊҁ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°.", + "login_has_been_disabled": "ВĐģиСаĐŊĐĩŅ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž.", "login_password_changed_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ°", "login_password_changed_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ОйĐŊОвĐĩĐŊа ĐŋĐ°Ņ€ĐžĐģа", "logout_all_device_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да иСĐģĐĩСĐĩŅ‚Đĩ ĐžŅ‚ Đ˛ŅĐ¸Ņ‡Đēи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Đ’ŅŠĐˇĐ˛ŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи", "maintenance_description": "ĐĄŅŠŅ€Đ˛ŅŠŅ€Đ° Immich Đĩ ĐŋĐžŅŅ‚Đ°Đ˛ĐĩĐŊ в Ņ€ĐĩĐļиĐŧ ĐŊа ĐžĐąŅĐģ҃ĐļваĐŊĐĩ.", "maintenance_end": "ĐšŅ€Đ°Đš ĐŊа Ņ€ĐĩĐļиĐŧа ĐŊа ĐžĐąŅĐģ҃ĐļваĐŊĐĩ", + "maintenance_end_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ˛Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩĐļиĐŧа ĐŊа ĐžĐąŅĐģ҃ĐļваĐŊĐĩ.", "maintenance_logged_in_as": "ĐĸĐĩĐēŅƒŅ‰Đ¸Ņ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ Đĩ {user}", "maintenance_restore_from_backup": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐžŅ‚ Đ°Ņ€Ņ…Đ¸Đ˛", "maintenance_restore_library": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа йийĐģĐ¸ĐžŅ‚ĐĩĐēа", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "manage_media_access_subtitle": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩŅ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž Immich да ҃ĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ° и ĐŧĐĩŅŅ‚Đ¸ ĐŧĐĩдиКĐŊи Ņ„Đ°ĐšĐģОвĐĩ.", "manage_media_access_title": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŧĐĩдиКĐŊи Ņ„Đ°ĐšĐģОвĐĩ", + "manage_shared_links": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊи Đ˛Ņ€ŅŠĐˇĐēи", "manage_sharing_with_partners": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ҁĐŋОдĐĩĐģŅĐŊĐĩŅ‚Đž ҁ ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸", "manage_the_app_settings": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", "manage_your_account": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŋŅ€ĐžŅ„Đ¸Đģа ви", @@ -1371,11 +1491,13 @@ "manage_your_devices": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа вĐģĐĩСĐģĐ¸Ņ‚Đĩ в ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "manage_your_oauth_connection": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа OAuth Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ°", "map": "ĐšĐ°Ņ€Ņ‚Đ°", + "map_assets_in_bounds": "{count, plural, =0 {ĐŅĐŧа ҁĐŊиĐŧĐēи} one {# ҁĐŊиĐŧĐēа} other {# ҁĐŊиĐŧĐēи}}", "map_cannot_get_user_location": "НĐĩ ĐŧĐžĐļĐ°Ņ… да ĐŋĐžĐģŅƒŅ‡Đ° ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", + "map_location_dialog_yes": "Да", "map_location_picker_page_use_location": "ИСĐŋĐžĐģСваК Ņ‚ĐžĐ˛Đ° ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "map_location_service_disabled_content": "За да ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Ņ‚ĐĩĐēŅƒŅ‰ĐžŅ‚Đž ĐŧŅŅŅ‚Đž, Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊа ҃ҁĐģŅƒĐŗĐ°Ņ‚Đ° Са ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ. Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да Ņ вĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ ҁĐĩĐŗĐ°?", "map_location_service_disabled_title": "ĐŖŅĐģŅƒĐŗĐ°Ņ‚Đ° Са ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ Đĩ иСĐēĐģŅŽŅ‡ĐĩĐŊа", - "map_marker_for_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ° Са ҁĐŊиĐŧĐēа, ĐŊаĐŋŅ€Đ°Đ˛ĐĩĐŊа в {city}, {country}", + "map_marker_for_images": "ĐœĐ°Ņ€ĐēĐĩŅ€Đ¸ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ° Са ҁĐŊиĐŧĐēи ĐŊаĐŋŅ€Đ°Đ˛ĐĩĐŊи в {city}, {country}", "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ° ҁ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", "map_no_location_permission_content": "За да ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ ĐžŅ‚ Ņ‚ĐĩĐēŅƒŅ‰ĐžŅ‚Đž ĐŧŅŅŅ‚Đž, Ņ‚Ņ€ŅĐąĐ˛Đ° Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ Са ĐžĐŋŅ€ĐĩĐ´ĐĩĐģŅĐŊĐĩ ĐŊа ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž. Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ҁĐĩĐŗĐ°?", "map_no_location_permission_title": "ĐžŅ‚ĐēаСаĐŊ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "ĐŸĐžŅĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩ {days} Đ´ĐŊи", "map_settings_date_range_option_year": "ИСĐŧиĐŊаĐģĐ°Ņ‚Đ° ĐŗĐžĐ´Đ¸ĐŊа", "map_settings_date_range_option_years": "МиĐŊаĐģĐ¸Ņ‚Đĩ {years} ĐŗĐžĐ´Đ¸ĐŊи", + "map_settings_dialog_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ°", "map_settings_include_show_archived": "ПоĐēаĐļи Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ", "map_settings_include_show_partners": "ПоĐēаĐļи ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸Ņ‚Đĩ", "map_settings_only_show_favorites": "ПоĐēаĐļи ŅĐ°ĐŧĐž ĐģŅŽĐąĐ¸Đŧи", "map_settings_theme_settings": "ĐĻвĐĩŅ‚ĐžĐ˛Đ° Ņ‚ĐĩĐŧа ĐŊа ĐēĐ°Ņ€Ņ‚Đ°Ņ‚Đ°", + "map_zoom_to_see_photos": "НаĐŧаĐģи ĐŧĐ°Ņ‰Đ°ĐąĐ°, Са да Đ˛Đ¸Đ´Đ¸Ņˆ ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ", "mark_all_as_read": "ĐœĐ°Ņ€ĐēĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи ĐēĐ°Ņ‚Đž ҇ĐĩŅ‚ĐĩĐŊи", + "mark_as_read": "ĐœĐ°Ņ€ĐēĐ¸Ņ€Đ°Đš ĐēĐ°Ņ‚Đž ҇ĐĩŅ‚ĐĩĐŊĐž", "marked_all_as_read": "Đ’ŅĐ¸Ņ‡Đēи ĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°ĐŊи ĐēĐ°Ņ‚Đž ĐŋŅ€ĐžŅ‡ĐĩŅ‚ĐĩĐŊи", "matches": "ĐĄŅŠĐ˛ĐŋадĐĩĐŊĐ¸Ņ", "matching_assets": "ĐĄŅŠĐ˛ĐŋĐ°Đ´Đ°Ņ‰Đ¸ ОйĐĩĐēŅ‚Đ¸", - "media_chrome": { - "auto": "ĐĐ˛Ņ‚Đž", - "captions": "ĐĄŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "captions_off": "ИСĐēĐģŅŽŅ‡ĐĩĐŊo", - "closed_captions": "ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "decode_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ´ĐĩĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ", - "disable_captions": "ИСĐēĐģŅŽŅ‡Đ¸ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "enable_captions": "ВĐēĐģŅŽŅ‡Đ¸ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "enter_fullscreen_mode": "На Ņ†ŅĐģ ĐĩĐēŅ€Đ°ĐŊ", - "exit_fullscreen_mode": "Đ˜ĐˇŅ…ĐžĐ´ ĐžŅ‚ Ņ†ŅĐģ ĐĩĐēŅ€Đ°ĐŊ", - "loop": "ĐŸĐžĐ˛Ņ‚Đ°Ņ€ŅĐš", - "media_error_description": "Đ’ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž Đĩ ҁĐŋŅ€ŅĐŊĐž ĐŋĐžŅ€Đ°Đ´Đ¸ ĐŗŅ€Đĩ҈Đēа Đ˛ŅŠĐ˛ Ņ„Đ°ĐšĐģа. МоĐļĐĩ йи Ņ„Đ°ĐšĐģŅŠŅ‚ Đĩ ĐŋĐžĐ˛Ņ€ĐĩĐ´ĐĩĐŊ иĐģи ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ° ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа Ņ‚ĐžĐˇĐ¸ Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "media_loading": "ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐŧĐĩĐ´Đ¸Ņ", - "mute": "БĐĩС ĐˇĐ˛ŅƒĐē", - "network_error": "Đ“Ņ€Đĩ҈Đēа в ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ°", - "network_error_description": "ĐŸŅ€ĐĩĐēŅŠŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŋĐžŅ€Đ°Đ´Đ¸ ĐŗŅ€Đĩ҈Đēа в ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ°.", - "not_supported_error": "ĐĸОСи Đ¸ĐˇŅ‚ĐžŅ‡ĐŊиĐē ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа", - "playback_rate": "ĐĄĐēĐžŅ€ĐžŅŅ‚ ĐŊа Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ", - "playback_rate_current": "Ņ‚ĐĩĐēŅƒŅ‰Đ° ҁĐēĐžŅ€ĐžŅŅ‚ ĐŊа Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ", - "playback_rate_value": "ĐĄĐēĐžŅ€ĐžŅŅ‚ ĐŊа Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ {playbackRate}", - "playback_time": "ĐŋŅ€ĐžĐ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚", - "quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", - "second": "ҁĐĩĐē҃ĐŊда", - "seconds": "ҁĐĩĐē҃ĐŊди", - "time_value_of_total_time": "{currentTime} ĐžŅ‚ {totalTime}", - "time_value_remaining": "{time} ĐžŅŅ‚Đ°Đ˛Đ°Ņ‚", - "unmute": "ВĐēĐģŅŽŅ‡Đ¸ ĐˇĐ˛ŅƒĐē", - "unsupported_error_description": "Đ’ŅŠĐˇĐŊиĐēĐŊа ĐŊĐĩĐŋĐžĐŋŅ€Đ°Đ˛Đ¸Đŧа ĐŗŅ€Đĩ҈Đēа. ĐŸŅ€ĐžĐąĐģĐĩĐŧ в ŅŅŠŅ€Đ˛ŅŠŅ€Đ° иĐģи ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ°, Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐž Đĩ ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ° да ĐŊĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа Ņ‚ĐžĐˇĐ¸ Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "video_not_loaded_unknown_time": "ĐŊĐĩ Đĩ ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊĐž видĐĩĐž, ĐŊĐĩиСвĐĩҁ҂ĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ.", - "video_player": "видĐĩĐžĐŋĐģĐĩĐĩŅ€", - "volume": "ŅĐ¸Đģа ĐŊа ĐˇĐ˛ŅƒĐēа" - }, "media_type": "Вид ĐŧĐĩĐ´Đ¸Ņ", "memories": "ĐĄĐŋĐžĐŧĐĩĐŊи", "memories_all_caught_up": "ĐĸОва Đĩ Đ˛ŅĐ¸Ņ‡ĐēĐž Са Đ´ĐŊĐĩҁ", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да ҁĐģĐĩĐĩŅ‚Đĩ Ņ‚ĐĩСи Ņ…ĐžŅ€Đ°? ĐĸОва Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ Đĩ ĐŊĐĩĐžĐąŅ€Đ°Ņ‚Đ¸ĐŧĐž.", "merge_people_successfully": "ĐŖŅĐŋĐĩ҈ĐŊĐž ҁĐģиваĐŊĐĩ ĐŊа Ņ…ĐžŅ€Đ°", "merged_people_count": "ĐĄĐģŅŅ‚ {count, plural, one {# Ņ‡ĐžĐ˛ĐĩĐē} other {# Ņ‡ĐžĐ˛ĐĩĐēа}}", - "minFaces": "МиĐŊиĐŧ҃Đŧ ĐģĐ¸Ņ†Đ°", - "minFaces_description": "МиĐŊиĐŧаĐģĐŊĐ¸ŅŅ‚ ĐąŅ€ĐžĐš Ņ€Đ°ĐˇĐŋОСĐŊĐ°Ņ‚Đ¸ ĐģĐ¸Ņ†Đ°, Са да ĐąŅŠĐ´Đĩ ĐŋĐžĐēаСаĐŊ Ņ‡ĐžĐ˛ĐĩĐē ĐēĐ°Ņ‚Đž Ņ€Đ°ĐˇĐŋОСĐŊĐ°Ņ‚", "minimize": "МиĐŊиĐŧĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ", "minute": "МиĐŊŅƒŅ‚Đ°", "minutes": "МиĐŊŅƒŅ‚Đ¸", + "mirror_horizontal": "ĐĨĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģĐŊĐž", + "mirror_vertical": "ВĐĩŅ€Ņ‚Đ¸ĐēаĐģĐŊĐž", "missing": "ЛиĐŋŅĐ˛Đ°Ņ‰Đ¸", "mobile_app": "МобиĐģĐŊĐž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ", "mobile_app_download_onboarding_note": "ХваĐģĐĩŅ‚Đĩ ĐŧОйиĐģĐŊĐžŅ‚Đž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ Immich ҁ ĐŊŅĐēĐžŅ ĐžŅ‚ ҁĐģĐĩĐ´ĐŊĐ¸Ņ‚Đĩ ĐžĐŋŅ†Đ¸Đ¸", "model": "МодĐĩĐģ", - "modify_date": "Đ”Đ°Ņ‚Đ° ĐŊа ĐŋŅ€ĐžĐŧŅĐŊa", "month": "МĐĩҁĐĩ҆", + "monthly_title_text_date_format": "MMMM Đŗ", "more": "ĐžŅ‰Đĩ", - "motion": "ДвиĐļĐĩĐŊиĐĩ", "move": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸", + "move_down": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ ĐŊадОĐģ҃", "move_off_locked_folder": "ИСвади ĐžŅ‚ СаĐēĐģŅŽŅ‡ĐĩĐŊĐ°Ņ‚Đ° ĐŋаĐŋĐēа", "move_to": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ ĐēҊĐŧ", "move_to_device_trash": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ĐžŅ‚Đž", "move_to_lock_folder_action_prompt": "{count} ŅĐ° дОйавĐĩĐŊи в СаĐēĐģŅŽŅ‡ĐĩĐŊĐ°Ņ‚Đ° ĐŋаĐŋĐēа", "move_to_locked_folder": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ в СаĐēĐģŅŽŅ‡ĐĩĐŊа ĐŋаĐŋĐēа", "move_to_locked_folder_confirmation": "ĐĸĐĩСи ҁĐŊиĐŧĐēи и видĐĩа ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ ĐžŅ‚ Đ˛ŅĐ¸Ņ‡Đēи аĐģĐąŅƒĐŧи и ҉Đĩ ŅĐ° Đ´ĐžŅŅ‚ŅŠĐŋĐŊи ŅĐ°ĐŧĐž в СаĐēĐģŅŽŅ‡ĐĩĐŊĐ°Ņ‚Đ° ĐŋаĐŋĐēа", + "move_up": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ ĐŊĐ°ĐŗĐžŅ€Đĩ", + "moved_to_archive": "{count, plural, one {# ОйĐĩĐēŅ‚ Đĩ ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊ} many {# ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи} other {# ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи}} в Đ°Ņ€Ņ…Đ¸Đ˛Đ°", + "moved_to_library": "{count, plural, one {# ОйĐĩĐēŅ‚ Đĩ ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊ} many {# ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи} other {# ОйĐĩĐēŅ‚Đ° ŅĐ° ĐŋŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊи}} в йийĐģĐ¸ĐžŅ‚ĐĩĐēĐ°Ņ‚Đ°", "moved_to_trash": "ĐŸŅ€ĐĩĐŧĐĩҁ҂ĐĩĐŊĐž в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž", + "multiselect_grid_edit_date_time_err_read_only": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€Đ° Đ´Đ°Ņ‚Đ°Ņ‚Đ° ĐŊа ОйĐĩĐēŅ‚ ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", + "multiselect_grid_edit_gps_err_read_only": "НĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€Đ° ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž ĐŊа ОйĐĩĐēŅ‚ ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐŊĐĩ", "mute_memories": "ИСĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа ĐˇĐ˛ŅƒĐēа ĐŊа ҁĐŋĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "my_albums": "Мои аĐģĐąŅƒĐŧи", - "my_immich_description": "КоĐŋĐ¸Ņ€Đ°Đš Đ°Đ´Ņ€ĐĩŅĐ° ĐŊа Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ‚Đ° ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ° ĐēĐ°Ņ‚Đž Đ˛Ņ€ŅŠĐˇĐēа ĐēҊĐŧ ĐŧĐžŅ Immich", - "my_immich_title": "Đ’Ņ€ŅŠĐˇĐēа ĐēҊĐŧ ĐŧĐžŅ Immich", "name": "ИĐŧĐĩ", "name_or_nickname": "ИĐŧĐĩ иĐģи ĐŋŅ€ŅĐēĐžŅ€", "name_required": "Đ—Đ°Đ´ŅŠĐģĐļĐ¸Ņ‚ĐĩĐģĐŊĐž Đĩ ИĐŧĐĩ", @@ -1480,11 +1576,12 @@ "never": "НиĐēĐžĐŗĐ°", "new_album": "Нов АĐģĐąŅƒĐŧ", "new_api_key": "Нов API ĐēĐģŅŽŅ‡", - "new_feature": "Нова Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ", + "new_date_range": "Нов ĐŋĐĩŅ€Đ¸ĐžĐ´ ĐžŅ‚ Đ˛Ņ€ĐĩĐŧĐĩ", "new_password": "Нова ĐŋĐ°Ņ€ĐžĐģа", "new_person": "Нов Ņ‡ĐžĐ˛ĐĩĐē", "new_pin_code": "Нов PIN ĐēОд", "new_pin_code_subtitle": "ĐĸОва Đĩ ĐŋŅŠŅ€Đ˛Đ¸ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž СаĐēĐģŅŽŅ‡ĐĩĐŊа ĐŋаĐŋĐēа. ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ PIN ĐēОд Са ĐˇĐ°Ņ‰Đ¸Ņ‚ĐĩĐŊ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž Ņ‚Đ°ĐˇĐ¸ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°", + "new_timeline": "Нова Đ˛Ņ€ĐĩĐŧĐĩва ĐģиĐŊĐ¸Ņ", "new_update": "Ново ОйĐŊОвĐģĐĩĐŊиĐĩ", "new_user_created": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐŊОв ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "new_version_available": "НАЛИЧНА НОВА Đ’Đ•Đ ĐĄĐ˜Đ¯", @@ -1492,6 +1589,7 @@ "next": "ĐĄĐģĐĩĐ´Đ˛Đ°Ņ‰Đž", "next_memory": "ĐĄĐģĐĩĐ´Đ˛Đ°Ņ‰ ҁĐŋĐžĐŧĐĩĐŊ", "no": "НĐĩ", + "no_actions_added": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ŅĐ° дОйавĐĩĐŊи Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", "no_albums_found": "НĐĩ ŅĐ° ĐŊаĐŧĐĩŅ€ĐĩĐŊи аĐģĐąŅƒĐŧи", "no_albums_message": "ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ аĐģĐąŅƒĐŧ Са ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ", "no_albums_with_name_yet": "Đ˜ĐˇĐŗĐģĐĩĐļда, ҇Đĩ Đ˛ŅĐĩ ĐžŅ‰Đĩ ĐŊŅĐŧĐ°Ņ‚Đĩ аĐģĐąŅƒĐŧи ҁ Ņ‚ĐžĐ˛Đ° иĐŧĐĩ.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "ĐŅĐŧа ĐŊаĐŧĐĩŅ€ĐĩĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° Са ĐŋŅ€ĐĩдаваĐŊĐĩ", "no_checksum_local": "ЛиĐŋŅĐ˛Đ°Ņ‚ ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊи ҁ҃Đŧи - ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ ĐŋĐžĐģŅƒŅ‡Đ°Ņ‚ ĐģĐžĐēаĐģĐŊи ОйĐĩĐēŅ‚Đ¸", "no_checksum_remote": "ЛиĐŋŅĐ˛Đ°Ņ‚ ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊи ҁ҃Đŧи - ĐŊĐĩ ĐŧĐžĐļĐĩ да ҁĐĩ ĐŋĐžĐģŅƒŅ‡Đ°Ņ‚ ОйĐĩĐēŅ‚Đ¸ ĐžŅ‚ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", + "no_configuration_needed": "НĐĩ Đĩ ĐŊ҃ĐļĐŊа ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ", "no_devices": "ĐŅĐŧа ĐžŅ‚ĐžŅ€Đ¸ĐˇĐ¸Ņ€Đ°ĐŊи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "no_duplicates_found": "НĐĩ ĐąŅŅ…Đ° ĐžŅ‚ĐēŅ€Đ¸Ņ‚Đ¸ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸.", "no_exif_info_available": "ĐŅĐŧа exif иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", "no_explore_results_message": "ĐšĐ°Ņ‡ĐĩŅ‚Đĩ ĐžŅ‰Đĩ ҁĐŊиĐŧĐēи, Са да Ņ€Đ°ĐˇĐŗĐģĐĩĐ´Đ°Ņ‚Đĩ ĐēĐžĐģĐĩĐēŅ†Đ¸ŅŅ‚Đ° ŅĐ¸.", "no_favorites_message": "ДобавĐĩŅ‚Đĩ в ĐģŅŽĐąĐ¸Đŧи, Са да ĐŊаĐŧĐ¸Ņ€Đ°Ņ‚Đĩ ĐąŅŠŅ€ĐˇĐž ĐŊаК-Đ´ĐžĐąŅ€Đ¸Ņ‚Đĩ ŅĐ¸ ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ", + "no_filters_added": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ŅĐ° дОйавĐĩĐŊи Ņ„Đ¸ĐģŅ‚Ņ€Đ¸", "no_libraries_message": "ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ Đ˛ŅŠĐŊ҈ĐŊа йийĐģĐ¸ĐžŅ‚ĐĩĐēа Са да Ņ€Đ°ĐˇĐŗĐģĐĩĐļĐ´Đ°Ņ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ", "no_local_assets_found": "НĐĩ Đĩ ĐŊаĐŧĐĩŅ€ĐĩĐŊ ĐģĐžĐēаĐģĐĩĐŊ ОйĐĩĐēŅ‚ ҁ Ņ‚Đ°Đēава ĐēĐžĐŊŅ‚Ņ€ĐžĐģĐŊа ҁ҃Đŧа", + "no_location_set": "НĐĩ Đĩ СададĐĩĐŊĐž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "no_locked_photos_message": "ĐĄĐŊиĐŧĐēĐ¸Ņ‚Đĩ и видĐĩĐ°Ņ‚Đ° в СаĐēĐģŅŽŅ‡ĐĩĐŊĐ°Ņ‚Đ° ĐŋаĐŋĐēа ŅĐ° ҁĐēŅ€Đ¸Ņ‚Đ¸ и ĐŊĐĩ ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚ ĐŋŅ€Đ¸ Ņ€Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа йийĐģĐ¸ĐžŅ‚ĐĩĐēĐ°Ņ‚Đ°.", "no_name": "БĐĩС иĐŧĐĩ", "no_notifications": "ĐŅĐŧа иСвĐĩŅŅ‚Đ¸Ņ", @@ -1518,19 +1619,14 @@ "no_results": "ĐŅĐŧа Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸", "no_results_description": "ОĐŋĐ¸Ņ‚Đ°ĐšŅ‚Đĩ ҁҊҁ ŅĐ¸ĐŊĐžĐŊиĐŧ иĐģи ĐŋĐž-ĐžĐąŅ‰Đ° ĐēĐģŅŽŅ‡ĐžĐ˛Đ° Đ´ŅƒĐŧа", "no_shared_albums_message": "ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ аĐģĐąŅƒĐŧ, Са да ҁĐŋОдĐĩĐģŅŅ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ ҁ Ņ…ĐžŅ€Đ°Ņ‚Đ° в ĐŧŅ€ĐĩĐļĐ°Ņ‚Đ° ŅĐ¸", - "no_steps": "Đ’ŅĐĩ ĐžŅ‰Đĩ ĐŊŅĐŧа дОйавĐĩĐŊи ŅŅ‚ŅŠĐŋĐēи", + "no_uploads_in_progress": "ĐŅĐŧа ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ в ĐŧĐžĐŧĐĩĐŊŅ‚Đ°", "none": "ĐĐ¸Ņ‰Đž", "not_allowed": "НĐĩ Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž", "not_available": "НĐĩĐŊаĐģĐ¸Ņ‡ĐŊĐž", "not_in_any_album": "НĐĩ Đĩ в ĐŊиĐēОК аĐģĐąŅƒĐŧ", "not_selected": "НĐĩ Đĩ Đ¸ĐˇĐąŅ€Đ°ĐŊĐž", - "not_set": "НĐĩ Đĩ СададĐĩĐŊĐž", "notes": "БĐĩĐģĐĩĐļĐēи", "nothing_here_yet": "Đ—Đ°ŅĐĩĐŗĐ° Ņ‚ŅƒĐē ĐŊŅĐŧа ĐŊĐ¸Ņ‰Đž", - "notification_backup_reliability": "ПозвоĐģĐĩŅ‚Đĩ иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°, Са да ĐŋĐžĐ´ĐžĐąŅ€Đ¸Ņ‚Đĩ ĐŊадĐĩĐļĐ´ĐŊĐžŅŅ‚Ņ‚Đ° ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", - "notification_enabled_list_tile_content": "Immich иСĐŋĐžĐģСва ĐŧĐĩŅ…Đ°ĐŊиСĐŧа Са иСвĐĩŅŅ‚Đ¸Ņ ĐŋŅ€Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ. ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ°ĐšŅ‚Đĩ ĐŗĐ¸ ĐžŅ‚ ĐŧĐĩĐŊŅŽŅ‚Đž Са ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа Đ˛Đ°ŅˆĐĩŅ‚Đž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž.", - "notification_enabled_list_tile_open_button": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", - "notification_enabled_list_tile_title": "ИСвĐĩŅŅ‚Đ¸ŅŅ‚Đ° ŅĐ° аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи", "notification_permission_dialog_content": "За да вĐēĐģŅŽŅ‡Đ¸Ņˆ иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°, ĐžŅ‚Đ¸Đ´Đ¸ в ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи и иСйĐĩŅ€Đ¸ Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸.", "notification_permission_list_tile_content": "Дай Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ Са аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°.", "notification_permission_list_tile_enable_button": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ‚ĐžŅ€ Са ĐŋĐžĐģŅƒŅ‡Đ°Đ˛Đ°ĐŊĐĩ", "obtainium_configurator_instructions": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ Obtainium Са иĐŊŅŅ‚Đ°ĐģĐ°Ņ†Đ¸Ņ и ОйĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž Са Android Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž ĐžŅ‚ GitHub ĐŊа Immich. ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ API ĐēĐģŅŽŅ‡ и иСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛Đ°Ņ€Đ¸Đ°ĐŊŅ‚ Са да ŅŅŠĐˇĐ´Đ°Đ´ĐĩŅ‚Đĩ Obtainium ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸ĐžĐŊĐĩĐŊ ĐģиĐŊĐē", "ocr": "ОĐŋŅ‚Đ¸Ņ‡ĐŊĐž Ņ€Đ°ĐˇĐŋОСĐŊаваĐŊĐĩ ĐŊа Ņ‚ĐĩĐēҁ҂", - "ocr_body": "ĐĄĐĩĐŗĐ° Immich Ņ€Đ°ĐˇŅ‡Đ¸Ņ‚Đ° Ņ‚ĐĩĐēҁ҂ в ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ, Ņ‚Đ°Đēа ҇Đĩ ĐŧĐžĐļĐĩ да Ņ‚ŅŠŅ€ŅĐ¸Ņ‚Đĩ ĐŋĐž Ņ‚ĐžĐ˛Đ°, ĐēĐžĐĩŅ‚Đž ĐŋĐ¸ŅˆĐĩ.", - "ocr_title": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа Ņ‚ĐĩĐēҁ҂ в ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ", "official_immich_resources": "ĐžŅ„Đ¸Ņ†Đ¸Đ°ĐģĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са Immich", "offline": "ĐžŅ„ĐģаКĐŊ", "offset": "ĐžŅ‚ĐŧĐĩŅŅ‚Đ˛Đ°ĐŊĐĩ", @@ -1562,8 +1656,6 @@ "open": "ĐžŅ‚Đ˛ĐžŅ€Đ¸", "open_calendar": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ ĐēаĐģĐĩĐŊĐ´Đ°Ņ€", "open_in_browser": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ в ĐąŅ€Đ°ŅƒĐˇŅŠŅ€", - "open_in_immich_body": "Đ—Đ°Đ´Đ°ĐšŅ‚Đĩ Immich ĐēĐ°Ņ‚Đž ĐŗĐ°ĐģĐĩŅ€Đ¸Ņ ĐŊа Android, Са да ĐžŅ‚Đ˛Đ°Ņ€ŅŅ‚Đĩ ҁĐŊиĐŧĐēи Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž ĐžŅ‚ Đ´Ņ€ŅƒĐŗĐ¸ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ.", - "open_in_immich_title": "ĐžŅ‚Đ˛Đ°Ņ€ŅĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи в Immich", "open_in_map_view": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ Đ¸ĐˇĐŗĐģĐĩĐ´ ĐŊа ĐēĐ°Ņ€Ņ‚Đ°", "open_in_openstreetmap": "ĐžŅ‚Đ˛ĐžŅ€Đ¸ в OpenStreetMap", "open_the_search_filters": "ĐžŅ‚Đ˛Đ°Ņ€Đ¸ Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ‚Đĩ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", @@ -1572,10 +1664,10 @@ "organize_into_albums": "ĐŸĐžĐ´Ņ€ĐĩĐ´ĐĩŅ‚Đĩ в аĐģĐąŅƒĐŧи", "organize_into_albums_description": "ДобавĐĩŅ‚Đĩ ĐŊаĐģĐ¸Ņ‡ĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи в аĐģĐąŅƒĐŧи, ĐēĐ°Ņ‚Đž иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚Đĩ Ņ‚ĐĩĐēŅƒŅ‰Đ¸Ņ‚Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи Са ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ", "organize_your_library": "ĐžŅ€ĐŗĐ°ĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ˛Đ°ŅˆĐ°Ņ‚Đ° йийĐģĐ¸ĐžŅ‚ĐĩĐēа", - "orientation": "ĐžŅ€Đ¸ĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ", "original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ", "other": "Đ”Ņ€ŅƒĐŗĐ¸", "other_devices": "Đ”Ņ€ŅƒĐŗĐ¸ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", + "other_entities": "Đ”Ņ€ŅƒĐŗĐ¸ ОйĐĩĐēŅ‚Đ¸", "other_variables": "Đ”Ņ€ŅƒĐŗĐ¸ ĐŋŅ€ĐžĐŧĐĩĐŊĐģиви", "owned": "ĐœĐžĐ¸Ņ‚Đĩ", "owner": "ĐĄĐžĐąŅŅ‚Đ˛ĐĩĐŊиĐē", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Đ’ŅĐ¸Ņ‡Đēи Đ˛Đ°ŅˆĐ¸ ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ, ҁ иСĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ ĐŊа Ņ‚ĐĩСи в ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊи и Đ˜ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸", "partner_can_access_location": "ĐœŅŅŅ‚ĐžŅ‚Đž, ĐēŅŠĐ´ĐĩŅ‚Đž ŅĐ° ĐŊаĐŋŅ€Đ°Đ˛ĐĩĐŊи ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ", "partner_list_user_photos": "ĐĄĐŊиĐŧĐēи ĐŊа {user}", + "partner_list_view_all": "ПоĐēаĐļи Đ˛ŅĐ¸Ņ‡Đēи", "partner_page_empty_message": "Đ’Đ°ŅˆĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи Đ˛ŅĐĩ ĐžŅ‰Đĩ ĐŊĐĩ ŅĐ° ҁĐŋОдĐĩĐģĐĩĐŊи.", "partner_page_no_more_users": "ĐŅĐŧа ĐŋОвĐĩ҇Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи Са Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ", + "partner_page_partner_add_failed": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ ĐŊа ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€", "partner_page_select_partner": "ИСйĐĩŅ€Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€", + "partner_page_shared_to_title": "ĐĄĐŋОдĐĩĐģĐĩĐŊĐž ҁ", "partner_page_stop_sharing_content": "{partner} вĐĩ҇Đĩ ĐŊŅĐŧа да иĐŧа Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи.", "partner_sharing": "ĐĄĐŋОдĐĩĐģŅĐŊĐĩ ҁ ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸", "partners": "ĐŸĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€Đ¸", @@ -1609,6 +1704,8 @@ "people": "ĐĨĐžŅ€Đ°", "people_edits_count": "ĐŸŅ€ĐžĐŧĐĩĐŊи {count, plural, one {# Ņ‡ĐžĐ˛ĐĩĐē} other {# Ņ‡ĐžĐ˛ĐĩĐēа}}", "people_feature_description": "ĐŸŅ€ĐĩĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи ĐŋĐž Ņ…ĐžŅ€Đ°", + "people_selected": "{count, plural, one {Đ˜ĐˇĐąŅ€Đ°ĐŊ Đĩ # Ņ‡ĐžĐ˛ĐĩĐē} other {Đ˜ĐˇĐąŅ€Đ°ĐŊи ŅĐ° # Ņ‡ĐžĐ˛ĐĩĐēа}}", + "people_sidebar_description": "ПоĐēаСваĐŊĐĩ ĐŊа Đ˛Ņ€ŅŠĐˇĐēа ĐēҊĐŧ Ņ…ĐžŅ€Đ°Ņ‚Đ° в ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊĐ°Ņ‚Đ° ĐģĐĩĐŊŅ‚Đ°", "permanent_deletion_warning": "ĐŸŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ Са Ņ‚Ņ€Đ°ĐšĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ", "permanent_deletion_warning_setting_description": "ПоĐēаСваĐŊĐĩ ĐŊа ĐŋŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ ĐŋŅ€Đ¸ Ņ‚Ņ€Đ°ĐšĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ ĐŊа аĐēŅ‚Đ¸Đ˛Đ¸", "permanently_delete": "ĐĸŅ€Đ°ĐšĐŊĐž Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊĐĩ", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Đ˜ĐˇŅ‚Ņ€Đ¸Ņ‚(и) СавиĐŊĐ°ĐŗĐ¸{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}}", "permission": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ", "permission_empty": "ĐĸвОĐĩŅ‚Đž Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ĐŊĐĩ Ņ‚Ņ€ŅĐąĐ˛Đ° да Đĩ ĐŋŅ€Đ°ĐˇĐŊĐž", + "permission_onboarding_back": "Назад", + "permission_onboarding_continue_anyway": "Đ’ŅŠĐŋŅ€ĐĩĐēи Ņ‚ĐžĐ˛Đ° ĐŋŅ€ĐžĐ´ŅŠĐģĐļи", + "permission_onboarding_get_started": "Да СаĐŋĐžŅ‡Đ˛Đ°ĐŧĐĩ", + "permission_onboarding_go_to_settings": "ĐžŅ‚Đ¸Đ´Đ¸ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", + "permission_onboarding_permission_denied": "ĐžŅ‚ĐēаСаĐŊ Đ´ĐžŅŅ‚ŅŠĐŋ. За да ĐŋĐžĐģĐˇĐ˛Đ°Ņˆ Immich, Ņ€Đ°ĐˇŅ€ĐĩŅˆĐ¸ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ҁĐŊиĐŧĐēи и видĐĩĐž.", + "permission_onboarding_permission_granted": "ĐŸŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ĐĩĐŊĐž Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ! Đ’ŅĐ¸Ņ‡ĐēĐž Đĩ ĐŗĐžŅ‚ĐžĐ˛Đž.", + "permission_onboarding_permission_limited": "ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊ Đ´ĐžŅŅ‚ŅŠĐŋ. За да ĐŧĐžĐļĐĩ Immich да Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ° и ҃ĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ° ĐŗĐ°ĐģĐĩŅ€Đ¸ŅŅ‚Đ°, ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ĐĩŅ‚Đĩ Đ´ĐžŅŅ‚ŅŠĐŋ Đ´Đž ҁĐŊиĐŧĐēи и видĐĩĐž в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ.", + "permission_onboarding_request": "Immich ҁĐĩ ĐŊ҃ĐļдаĐĩ ĐžŅ‚ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ Са ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐž.", "person": "ЧОвĐĩĐē", "person_age_months": "{months, plural, one {# ĐŧĐĩҁĐĩ҆} other {# ĐŧĐĩҁĐĩŅ†Đ°}}", "person_age_year_months": "1 ĐŗĐžĐ´Đ¸ĐŊа и {months, plural, one {# ĐŧĐĩҁĐĩ҆} other {# ĐŧĐĩҁĐĩŅ†Đ°}}", - "person_age_years": "{years, plural, one {# ĐŗĐžĐ´Đ¸ĐŊа} other {# ĐŗĐžĐ´Đ¸ĐŊи}}", + "person_age_years": "{years, plural, other {# ĐŗĐžĐ´Đ¸ĐŊи}}", "person_birthdate": "Đ”Đ°Ņ‚Đ° ĐŊа Ņ€Đ°ĐļдаĐŊĐĩ {date}", "person_hidden": "{name}{hidden, select, true { (ҁĐēŅ€Đ¸Ņ‚)} other {}}", "person_recognized": "РаСĐŋОСĐŊĐ°Ņ‚Đž e ĐģĐ¸Ņ†Đĩ", + "person_selected": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐž Đĩ ĐģĐ¸Ņ†Đĩ", "photo_shared_all_users": "Đ˜ĐˇĐŗĐģĐĩĐļда, ҇Đĩ ҁ҂Đĩ ҁĐŋОдĐĩĐģиĐģи ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ ŅĐ¸ ҁ Đ˛ŅĐ¸Ņ‡Đēи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи иĐģи ĐŊŅĐŧĐ°Ņ‚Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи, ҁ ĐēĐžĐ¸Ņ‚Đž да ҁĐŋОдĐĩĐģŅŅ‚Đĩ.", "photos": "ĐĄĐŊиĐŧĐēи", "photos_and_videos": "ĐĄĐŊиĐŧĐēи и ВидĐĩа", + "photos_count": "{count, plural, one {{count, number} ĐĄĐŊиĐŧĐēа} other {{count, number} ĐĄĐŊиĐŧĐēи}}", "photos_from_previous_years": "ĐĄĐŊиĐŧĐēи ĐžŅ‚ ĐŋŅ€ĐĩĐ´Ņ…ĐžĐ´ĐŊи ĐŗĐžĐ´Đ¸ĐŊи", + "photos_only": "ХаĐŧĐž ҁĐŊиĐŧĐēи", "pick_a_location": "ИСйĐĩŅ€Đ¸ ĐģĐžĐēĐ°Ņ†Đ¸Ņ", "pick_custom_range": "ĐŸŅ€ĐžĐ¸ĐˇĐ˛ĐžĐģĐĩĐŊ ĐŋĐĩŅ€Đ¸ĐžĐ´", "pick_date_range": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐŋĐĩŅ€Đ¸ĐžĐ´", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "ĐŸŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐĩ ĐŊа ĐŋĐžĐēаСваĐŊĐĩ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐžŅ‚Đž видĐĩĐž, вĐŧĐĩŅŅ‚Đž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи. АĐēĐž Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đ° ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐ¸Ņ Ņ„Đ°ĐšĐģ ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа, Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģĐŊĐž.", "play_transcoded_video": "ПоĐēаĐļи Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐž видĐĩĐž", "please_auth_to_access": "МоĐģŅ, ŅƒĐ´ĐžŅŅ‚ĐžĐ˛ĐĩŅ€Đ¸ Са Đ´ĐžŅŅ‚ŅŠĐŋ", - "plugin_method_filter_type": "ФиĐģŅ‚ŅŠŅ€", - "plugin_method_filter_type_description": "ĐĸОСи ĐŧĐĩŅ‚ĐžĐ´ ĐŧĐžĐļĐĩ да Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ° ŅŅŠĐąĐ¸Ņ‚Đ¸Ņ и ĐŋĐž ҃ҁĐģОвиĐĩ да ҁĐŋĐ¸Ņ€Đ° иСĐŋҊĐģĐŊĐĩĐŊиĐĩŅ‚Đž ĐŊа ҁĐģĐĩĐ´Đ˛Đ°Ņ‰Đ¸ ŅŅ‚ŅŠĐŋĐēи", "port": "ĐŸĐžŅ€Ņ‚", "preferences_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ĐŋŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐ¸ŅŅ‚Đ° ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", "preferences_settings_title": "ĐŸŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐ¸Ņ", @@ -1665,11 +1771,11 @@ "privacy": "ПовĐĩŅ€Đ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚", "profile": "ĐŸŅ€ĐžŅ„Đ¸Đģ", "profile_drawer_app_logs": "ДĐŊĐĩвĐŊиĐē", + "profile_drawer_client_server_up_to_date": "КĐģиĐĩĐŊŅ‚Đ° и ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ŅĐ° ОйĐŊОвĐĩĐŊи", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Đ ĐĩĐļиĐŧа ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ Đĩ аĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊ. ĐĄ Đ´ŅŠĐģĐŗĐž ĐŊĐ°Ņ‚Đ¸ŅĐēаĐŊĐĩ Đ˛ŅŠŅ€Ņ…Ņƒ ĐēĐ°Ņ€Ņ‚Đ¸ĐēĐ°Ņ‚Đ°-Đ°Đ˛Đ°Ņ‚Đ°Ņ€ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ ҉Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚Đĩ ŅĐ°ĐŧĐž Са ҇ĐĩŅ‚ĐĩĐŊĐĩ.", "profile_image_of_user": "ĐŸŅ€ĐžŅ„Đ¸ĐģĐŊа ҁĐŊиĐŧĐēа ĐŊа {user}", "profile_picture_set": "ĐŸŅ€ĐžŅ„Đ¸ĐģĐŊĐ°Ņ‚Đ° ҁĐŊиĐŧĐēа Đĩ ҁĐģĐžĐļĐĩĐŊа.", - "projection_type": "ĐĸиĐŋ ĐŋŅ€ĐžĐĩĐēŅ†Đ¸Ņ", "public_album": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐĩĐŊ аĐģĐąŅƒĐŧ", "public_share": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊĐž ҁĐŋОдĐĩĐģŅĐŊĐĩ", "purchase_account_info": "ĐŸĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŊа ĐŋĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē", "purchase_individual_title": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģĐĩĐŊ", "purchase_input_suggestion": "ИĐŧĐ°Ņ‚Đĩ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚ĐžĐ˛ ĐēĐģŅŽŅ‡? Đ’ŅŠĐ˛ĐĩĐ´ĐĩŅ‚Đĩ ĐēĐģŅŽŅ‡Đ° ĐŋĐž-Đ´ĐžĐģ҃", + "purchase_license_subtitle": "ЗаĐē҃ĐŋĐĩŅ‚Đĩ Immich, Са да ĐŋОдĐēŅ€ĐĩĐŋĐ¸Ņ‚Đĩ ĐŋŅ€ĐžĐ´ŅŠĐģĐļĐ°Đ˛Đ°Ņ‰ĐžŅ‚Đž Ņ€Đ°ĐˇĐ˛Đ¸Ņ‚Đ¸Đĩ ĐŊа ҃ҁĐģŅƒĐŗĐ°Ņ‚Đ°", "purchase_lifetime_description": "ПоĐē҃ĐŋĐēа Са Ņ†ŅĐģ ĐļĐ¸Đ˛ĐžŅ‚", "purchase_option_title": "ОПĐĻИИ ЗА Đ—ĐĐšĐŖĐŸĐŖĐ’ĐĐĐ•", "purchase_panel_info_1": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩŅ‚Đž ĐŊа Immich ĐžŅ‚ĐŊĐĩĐŧа ĐŧĐŊĐžĐŗĐž Đ˛Ņ€ĐĩĐŧĐĩ и ŅƒŅĐ¸ĐģĐ¸Ņ, и иĐŧаĐŧĐĩ иĐŊĐļĐĩĐŊĐĩŅ€Đ¸ ĐŊа ĐŋҊĐģĐŊĐž Ņ€Đ°ĐąĐžŅ‚ĐŊĐž Đ˛Ņ€ĐĩĐŧĐĩ, ĐēĐžĐ¸Ņ‚Đž Ņ€Đ°ĐąĐžŅ‚ŅŅ‚ ĐŋĐž ĐŊĐĩĐŗĐž, Са да ĐŗĐž ĐŊаĐŋŅ€Đ°Đ˛Đ¸Đŧ Đ˛ŅŠĐˇĐŧĐžĐļĐŊĐž ĐŊаК-Đ´ĐžĐąŅ€Đž. ĐĐ°ŅˆĐ°Ņ‚Đ° ĐŧĐ¸ŅĐ¸Ņ Đĩ ŅĐžŅ„Ņ‚ŅƒĐĩŅ€ŅŠŅ‚ ҁ ĐžŅ‚Đ˛ĐžŅ€ĐĩĐŊ ĐēОд и ĐĩŅ‚Đ¸Ņ‡ĐŊĐ¸Ņ‚Đĩ йиСĐŊĐĩҁ ĐŋŅ€Đ°ĐēŅ‚Đ¸Đēи да ҁĐĩ ĐŋŅ€ĐĩĐ˛ŅŠŅ€ĐŊĐ°Ņ‚ в ŅƒŅŅ‚ĐžĐšŅ‡Đ¸Đ˛ Đ¸ĐˇŅ‚ĐžŅ‡ĐŊиĐē ĐŊа Đ´ĐžŅ…ĐžĐ´Đ¸ Са Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚Ņ‡Đ¸Ņ†Đ¸Ņ‚Đĩ и да ŅŅŠĐˇĐ´Đ°Đ´ĐĩĐŧ ĐĩĐēĐžŅĐ¸ŅŅ‚ĐĩĐŧа, ĐēĐžŅŅ‚Đž ŅƒĐ˛Đ°Đļава ĐģĐ¸Ņ‡ĐŊĐ°Ņ‚Đ° ĐŊĐĩĐŋŅ€Đ¸ĐēĐžŅĐŊОвĐĩĐŊĐžŅŅ‚ и ĐŋŅ€ĐĩĐ´ĐģĐ°ĐŗĐ° Đ¸ŅŅ‚Đ¸ĐŊҁĐēи аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛Đ¸ ĐŊа ĐĩĐēҁĐŋĐģĐžĐ°Ņ‚Đ°Ņ‚ĐžŅ€ŅĐēĐ¸Ņ‚Đĩ ОйĐģĐ°Ņ‡ĐŊи ҃ҁĐģŅƒĐŗĐ¸.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "ĐŸŅ€ĐĩĐŊаСĐŊĐ°Ņ‡ĐĩĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}} ĐŊа ĐŊОв Ņ‡ĐžĐ˛ĐĩĐē", "reassing_hint": "НазĐŊĐ°Ņ‡Đ¸ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊа ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°Ņ‰Đž ĐģĐ¸Ņ†Đĩ", "recent": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи", + "recent_albums": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи АĐģĐąŅƒĐŧи", "recent_searches": "ĐĄĐēĐžŅ€ĐžŅˆĐŊи Ņ‚ŅŠŅ€ŅĐĩĐŊĐ¸Ņ", "recently_added": "ĐĐ°ŅĐēĐžŅ€Đž дОйавĐĩĐŊĐž", - "recently_added_body": "ĐŸŅ€ĐĩĐŧиĐŊĐĩŅ‚Đĩ Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž ĐēҊĐŧ ҁĐŋĐĩŅ†Đ¸Đ°ĐģĐŊа ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°, ĐēŅŠĐ´ĐĩŅ‚Đž Đĩ Đ˛ŅĐ¸Ņ‡ĐēĐž, ĐēĐžĐĩŅ‚Đž ҁ҂Đĩ дОйавиĐģи ĐŊаĐŋĐžŅĐģĐĩĐ´ŅŠĐē.", - "recently_added_description": "Đ Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģОвĐĩ, ĐŋĐžĐ´Ņ€ĐĩĐ´ĐĩĐŊи ĐŋĐž Ņ€Đĩда ĐŊа Đ´ĐžĐąĐ°Đ˛ŅĐŊĐĩ в Immich", "recently_added_page_title": "ĐĐ°ŅĐēĐžŅ€Đž дОйавĐĩĐŊĐž", - "recently_added_title": "ДобавĐĩĐŊи ҁĐēĐžŅ€Đž", "recently_taken": "ĐĐ°ŅĐēĐžŅ€Đž ҁĐŊиĐŧаĐŊĐž", + "recently_taken_page_title": "ĐĐ°ŅĐēĐžŅ€Đž ҁĐŊиĐŧаĐŊĐž", "refresh": "ОĐŋŅ€ĐĩҁĐŊŅĐ˛Đ°ĐŊĐĩ", "refresh_encoded_videos": "ОĐŋŅ€ĐĩҁĐŊи ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊи видĐĩĐžĐēĐģиĐŋОвĐĩ", "refresh_faces": "ОĐŋŅ€ĐĩҁĐŊи ĐģĐ¸Ņ†Đ°Ņ‚Đ°", "refresh_metadata": "ОĐŋŅ€ĐĩҁĐŊи ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊĐ¸Ņ‚Đĩ", "refresh_thumbnails": "ОĐŋŅ€ĐĩҁĐŊи ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ¸Ņ‚Đĩ", "refreshed": "ОĐŋŅ€ĐĩҁĐŊĐĩĐŊĐž", + "refreshes_every_file": "ĐŸŅ€ĐžŅ‡Đ¸Ņ‚Đ° ĐžŅ‚ĐŊОвО Đ˛ŅĐ¸Ņ‡Đēи ŅŅŠŅ‰ĐĩŅŅ‚Đ˛ŅƒĐ˛Đ°Ņ‰Đ¸ и ĐŊОви Ņ„Đ°ĐšĐģОвĐĩ", "refreshing_encoded_video": "ОĐŋŅ€ĐĩҁĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐžŅ‚Đž видĐĩĐž", "refreshing_faces": "ОĐŋŅ€ĐĩҁĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐģĐ¸Ņ†Đ°Ņ‚Đ°", "refreshing_metadata": "ОĐŋŅ€ĐĩҁĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊĐ¸Ņ‚Đĩ", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩŅ‚Đĩ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}} ĐžŅ‚ Ņ‚ĐžĐˇĐ¸ ҁĐŋĐžĐĩĐ´ĐĩĐģĐĩĐŊ ĐģиĐŊĐē?", "remove_assets_title": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸Ņ‚Đĩ?", "remove_custom_date_range": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи СададĐĩĐŊĐ¸Ņ диаĐŋаСОĐŊ ĐžŅ‚ Đ´Đ°Ņ‚Đ¸", - "remove_filter": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи Ņ„Đ¸ĐģŅ‚ŅŠŅ€", + "remove_deleted_assets": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи Đ˜ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸Ņ‚Đĩ ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "remove_from_album": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ аĐģĐąŅƒĐŧа", "remove_from_album_action_prompt": "{count} ŅĐ° ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ ĐžŅ‚ аĐģĐąŅƒĐŧа", "remove_from_favorites": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ Đ›ŅŽĐąĐ¸Đŧи", @@ -1756,22 +1863,29 @@ "remove_memory": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ҁĐŋĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "remove_photo_from_memory": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ ĐžŅ‚ ҁĐŋĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ", "remove_tag": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐŧĐ°Ņ€ĐēĐĩŅ€Đ°", + "remove_url": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи URL", "remove_user": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ", "removed_api_key": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ°Ņ‚ API ĐēĐģŅŽŅ‡: {name}", "removed_from_archive": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ Đ°Ņ€Ņ…Đ¸Đ˛Đ°", "removed_from_favorites": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đž ĐžŅ‚ ĐģŅŽĐąĐ¸Đŧи", "removed_from_favorites_count": "{count, plural, other {ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ¸ #}} ĐžŅ‚ Đ›ŅŽĐąĐ¸Đŧи", "removed_memory": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚ ҁĐŋĐžĐŧĐĩĐŊ", + "removed_photo_from_memory": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚Đ° ҁĐŊиĐŧĐēа ĐžŅ‚ ҁĐŋĐžĐŧĐĩĐŊ", "removed_tagged_assets": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚ Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚ŅŠŅ‚ ĐžŅ‚ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°}}", "rename": "ĐŸŅ€ĐĩиĐŧĐĩĐŊŅƒĐ˛Đ°Đš", + "repair": "ПоĐŋŅ€Đ°Đ˛Đ¸", + "repair_no_results_message": "НĐĩҁĐģĐĩĐ´ĐĩĐŊĐ¸Ņ‚Đĩ и ĐģиĐŋŅĐ˛Đ°Ņ‰Đ¸ Ņ„Đ°ĐšĐģОвĐĩ ҉Đĩ ҁĐĩ ĐŋĐžĐēаĐļĐ°Ņ‚ Ņ‚ŅƒĐē", + "replace_with_upload": "ĐĄĐŧĐĩĐŊи ҁ ĐēĐ°Ņ‡ĐĩĐŊĐžŅ‚Đž", "repository": "ĐĨŅ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ", "require_password": "Đ˜ĐˇĐ¸ŅĐēаК ĐŋĐ°Ņ€ĐžĐģа", + "require_user_to_change_password_on_first_login": "Đ˜ĐˇĐ¸ŅĐēваК ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅŅ‚ да ĐŋŅ€ĐžĐŧĐĩĐŊи ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ° ŅĐ¸ ĐŋŅ€Đ¸ ĐŋŅŠŅ€Đ˛ĐžŅ‚Đž вĐģиСаĐŊĐĩ", "rescan": "ĐŸŅ€ĐĩҁĐēаĐŊĐ¸Ņ€Đ°ĐŊĐĩ", "reset": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ", "reset_password": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ°", "reset_people_visibility": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа видиĐŧĐžŅŅ‚Ņ‚Đ° ĐŊа Ņ…ĐžŅ€Đ°Ņ‚Đ°", "reset_pin_code": "ĐŅƒĐģĐ¸Ņ€Đ°Đš PIN ĐēОда", "reset_pin_code_description": "АĐēĐž ҁ҂Đĩ ŅĐ¸ ĐˇĐ°ĐąŅ€Đ°Đ˛Đ¸Đģи ПИН ĐēОда, ĐŧĐžĐļĐĩ да ҁĐĩ ĐžĐąŅŠŅ€ĐŊĐĩŅ‚Đĩ ĐēҊĐŧ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° Са да ĐŗĐž ĐŊ҃ĐģĐ¸Ņ€Đ°", + "reset_pin_code_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐŊ҃ĐģĐ¸Ņ€Đ°ĐŊ ПИН ĐēОд", "reset_pin_code_with_password": "ĐĄ Đ˛Đ°ŅˆĐ°Ņ‚Đ° ĐŋĐ°Ņ€ĐžĐģа ĐŧĐžĐļĐĩŅ‚Đĩ виĐŊĐ°ĐŗĐ¸ да ĐŊ҃ĐģĐ¸Ņ€Đ°Ņ‚Đĩ ŅĐ˛ĐžŅ ПИН ĐēОд", "reset_sqlite": "ĐŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐąĐ°ĐˇĐ°Ņ‚Đ° даĐŊĐŊи SQLite", "reset_sqlite_clear_app_data": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи даĐŊĐŊĐ¸Ņ‚Đĩ", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Đ’ŅĐ¸Ņ‡Đēи Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸ ŅĐ° Ņ€Đĩ҈ĐĩĐŊи", "restore": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ", "restore_all": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОви Đ˛ŅĐ¸Ņ‡Đēи", + "restore_trash_action_prompt": "{count} Đ˛ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊи ĐžŅ‚ ĐēĐžŅˆĐ°", "restore_user": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОви ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "restored_asset": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊОвĐĩĐŊ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "resume": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļаваĐŊĐĩ", "resume_paused_jobs": "ĐŸŅ€ĐžĐ´ŅŠĐģĐļи иСĐŋҊĐģĐŊĐĩĐŊиĐĩŅ‚Đž ĐŊа {count, plural, one {# ĐˇĐ°Đ´Đ°Ņ‡Đ°} other {# ĐˇĐ°Đ´Đ°Ņ‡Đ¸}}", + "retry_upload": "ОĐŋĐ¸Ņ‚Đ°Đš ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž ĐžŅ‚ĐŊОвО", "review_duplicates": "Đ Đ°ĐˇĐŗĐģĐĩдаК Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸Ņ‚Đĩ", "review_large_files": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ĐŗĐžĐģĐĩĐŧи Ņ„Đ°ĐšĐģОвĐĩ", "role": "Đ ĐžĐģŅ", @@ -1796,6 +1912,7 @@ "role_viewer": "Đ—Ņ€Đ¸Ņ‚ĐĩĐģ", "running": "ИСĐŋҊĐģĐŊŅĐ˛Đ°ĐŊĐĩ", "save": "ЗаĐŋаСи", + "save_to_gallery": "ЗаĐŋаСи в ĐŗĐ°ĐģĐĩŅ€Đ¸ŅŅ‚Đ°", "saved": "ЗаĐŋĐ¸ŅĐ°ĐŊĐž", "saved_api_key": "ЗаĐŋаСĐĩĐŊ API Key", "saved_profile": "ЗаĐŋаСĐĩĐŊ ĐŋŅ€ĐžŅ„Đ¸Đģ", @@ -1806,10 +1923,9 @@ "scan": "ĐĄĐēаĐŊĐ¸Ņ€Đ°ĐŊe", "scan_all_libraries": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "scan_library": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš", + "scan_settings": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ", "scanning": "ĐĄĐēаĐŊĐ¸Ņ€Đ°ĐŊĐĩ", "scanning_for_album": "ĐĄĐēаĐŊĐ¸Ņ€Đ°Đš Са аĐģĐąŅƒĐŧ...", - "screencast_mode_description": "ПоĐēаСваĐŊĐĩ ĐŊа ĐĩĐēŅ€Đ°ĐŊа ĐŊа иĐŊдиĐēĐ°Ņ‚ĐžŅ€Đ¸ Са ŅŅŠĐąĐ¸Ņ‚Đ¸Ņ ĐžŅ‚ ĐēĐģĐ°Đ˛Đ¸Đ°Ņ‚ŅƒŅ€Đ°Ņ‚Đ° и ĐŧĐ¸ŅˆĐēĐ°Ņ‚Đ°", - "screencast_mode_title": "ĐŸŅ€ĐĩвĐēĐģŅŽŅ‡Đ˛Đ°ĐŊĐĩ ĐŊа Ņ€ĐĩĐļиĐŧа ĐŊа ҁĐēŅ€Đ¸ĐšĐŊĐēĐ°ŅŅ‚", "search": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ", "search_albums": "ĐĸŅŠŅ€ŅĐ¸ аĐģĐąŅƒĐŧи", "search_by_context": "ĐĸŅŠŅ€ŅĐ¸ ĐŋĐž ĐēĐžĐŊŅ‚ĐĩĐēҁ҂", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Đ Đ°ĐˇŅ…ĐžĐ´Đēа в ХаĐŋа", "search_by_filename": "ĐĸŅŠŅ€ŅĐ¸ ĐŋĐž иĐŧĐĩ ĐŊа Ņ„Đ°ĐšĐģа иĐģи Ņ€Đ°ĐˇŅˆĐ¸Ņ€ĐĩĐŊиĐĩ", "search_by_filename_example": "ĐŊаĐŋŅ€Đ¸ĐŧĐĩŅ€ IMG_1234.JPG иĐģи PNG", - "search_by_full_path": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŋĐž ĐŋҊĐģĐĩĐŊ ĐŋŅŠŅ‚ иĐģи ĐŋаĐŋĐēа", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - Ņ‚ŅŠŅ€ŅĐĩĐŊа Са Projects, 3D, Printing, 2026 и Ņ‚.ĐŊ.", "search_by_ocr": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа Ņ‚ĐĩĐēҁ҂", "search_by_ocr_example": "LattĐĩ", "search_camera_lens_model": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐŧОдĐĩĐģ ĐŊа ОйĐĩĐēŅ‚Đ¸Đ˛Đ°...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "ИСйĐĩŅ€Đ¸ ĐŧŅŅŅ‚Đž", "search_filter_media_type": "ĐĸиĐŋ ĐŊа Ņ„Đ°ĐšĐģа", "search_filter_media_type_title": "ИСйĐĩŅ€Đ¸ Ņ‚Đ¸Đŋ ĐŊа Ņ„Đ°ĐšĐģа", + "search_filter_ocr": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊa Ņ‚ĐĩĐēҁ҂", "search_filter_people_title": "ИСйĐĩŅ€Đ¸ Ņ…ĐžŅ€Đ°", "search_filter_star_rating": "КĐģĐ°ŅĐ°Ņ†Đ¸Ņ ҁҊҁ СвĐĩСди", "search_filter_tags_title": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸", @@ -1848,13 +1963,25 @@ "search_no_people_named": "ĐŅĐŧа Ņ…ĐžŅ€Đ° ĐŊа иĐŧĐĩ \"{name}\"", "search_no_result": "ĐŅĐŧа ĐŊаĐŧĐĩŅ€ĐĩĐŊи Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸, ĐžĐŋĐ¸Ņ‚Đ°Đš ҁ Đ´Ņ€ŅƒĐŗ Ņ‚ĐĩŅ€ĐŧиĐŊ иĐģи ĐēĐžĐŧйиĐŊĐ°Ņ†Đ¸Ņ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "search_options": "ОĐŋŅ†Đ¸Đ¸ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", + "search_page_categories": "ĐšĐ°Ņ‚ĐĩĐŗĐžŅ€Đ¸Đ¸", + "search_page_motion_photos": "ĐĄĐŊиĐŧĐēи ҁ двиĐļĐĩĐŊиĐĩ", + "search_page_no_objects": "ĐŅĐŧа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са ОйĐĩĐēŅ‚Đ¸Ņ‚Đĩ", + "search_page_no_places": "ĐŅĐŧа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са ĐŧĐĩŅŅ‚Đ°Ņ‚Đ°", + "search_page_screenshots": "ЕĐēŅ€Đ°ĐŊĐŊи ҁĐŊиĐŧĐēи", "search_page_search_photos_videos": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ в ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ и видĐĩĐ°Ņ‚Đ°", + "search_page_selfies": "ĐĄĐĩĐģŅ„Đ¸Ņ‚Đ°", + "search_page_things": "ĐŸŅ€ĐĩĐ´ĐŧĐĩŅ‚Đ¸", "search_page_view_all_button": "ВиĐļ Đ˛ŅĐ¸Ņ‡Đēи", + "search_page_your_activity": "Đ’Đ°ŅˆĐ¸Ņ‚Đĩ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", + "search_page_your_map": "Đ’Đ°ŅˆĐ°Ņ‚Đ° ĐēĐ°Ņ€Ņ‚Đ°", "search_people": "ĐĸŅŠŅ€ŅĐĩŅ‚Đĩ ĐŊа Ņ…ĐžŅ€Đ°", "search_places": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐŧĐĩŅŅ‚Đ°", "search_rating": "ĐĸŅŠŅ€ŅĐ¸ ĐŋĐž Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗâ€Ļ", + "search_result_page_new_search_hint": "Ново Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "search_settings": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "search_state": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа Ņ‰Đ°Ņ‚...", + "search_suggestion_list_smart_search_hint_1": "ĐŖĐŧĐŊĐžŅ‚Đž Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ Đĩ вĐēĐģŅŽŅ‡ĐĩĐŊĐž ĐŋĐž ĐŋĐžĐ´Ņ€Đ°ĐˇĐąĐ¸Ņ€Đ°ĐŊĐĩ, Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ в ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊĐ¸Ņ‚Đĩ иСĐŋĐžĐģСваК ҁĐŋĐĩŅ†Đ¸Đ°ĐģĐĩĐŊ ŅĐ¸ĐŊŅ‚Đ°Đēҁ ", + "search_suggestion_list_smart_search_hint_2": "m:Đ˛Đ°ŅˆĐ¸Ņ-Ņ‚ĐĩŅ€ĐŧиĐŊ-Са-Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "search_tags": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸...", "search_timezone": "ĐĸŅŠŅ€ŅĐĩĐŊĐĩ ĐŊа Ņ‡Đ°ŅĐžĐ˛Đ° СОĐŊа...", "search_type": "ĐĸиĐŋ ĐŊа Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", @@ -1868,6 +1995,7 @@ "select_albums": "ИСйĐĩŅ€ĐĩŅ‚Đĩ аĐģĐąŅƒĐŧи", "select_all": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи", "select_all_duplicates": "ИСйĐĩŅ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", + "select_all_in": "ИСйĐĩŅ€Đ¸ Đ˛ŅĐ¸Ņ‡Đēи ĐžŅ‚ ĐŗŅ€ŅƒĐŋĐ°Ņ‚Đ° {group}", "select_avatar_color": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Ņ†Đ˛ŅŅ‚ ĐŊа Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ°", "select_count": "{count, plural, one {Đ˜ĐˇĐąŅ€Đ°ĐŊ Đĩ #} other {Đ˜ĐˇĐąŅ€Đ°ĐŊи ŅĐ° #}}", "select_cutoff_date": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐēŅ€Đ°ĐšĐŊа Đ´Đ°Ņ‚Đ°", @@ -1875,13 +2003,14 @@ "select_featured_photo": "ИСйĐĩŅ€Đ¸ ĐŋŅ€ĐĩĐ´ŅŅ‚Đ°Đ˛Đ¸Ņ‚ĐĩĐģĐŊа ҁĐŊиĐŧĐēа", "select_from_computer": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐžŅ‚ ĐēĐžĐŧĐŋŅŽŅ‚ŅŠŅ€Đ°", "select_keep_all": "ИСйĐĩŅ€Đ¸ \"ĐˇĐ°Đ´Ņ€ŅŠĐļ Đ˛ŅĐ¸Ņ‡Đēи\"", + "select_library_owner": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ŅĐžĐąŅŅ‚Đ˛ĐĩĐŊиĐē ĐŊа йийĐģĐ¸ĐžŅ‚ĐĩĐēа", "select_new_face": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐŊОвО ĐģĐ¸Ņ†Đĩ", "select_people": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐģĐ¸Ņ†Đ°", "select_person": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Ņ‡ĐžĐ˛ĐĩĐē", "select_person_to_tag": "ИСйĐĩŅ€Đ¸ ĐģĐ¸Ņ†Đĩ, ĐēĐžĐĩŅ‚Đž да ĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°Ņˆ", "select_photos": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ҁĐŊиĐŧĐēи", - "select_quality": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", "select_trash_all": "ИСйĐĩŅ€ĐĩŅ‚Đĩ Đ˛ŅĐ¸Ņ‡ĐēĐž Са ĐēĐžŅˆŅ‡ĐĩŅ‚Đž", + "select_user_for_sharing_page_err_album": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩŅ‚Đž ĐŊа аĐģĐąŅƒĐŧ ĐŊĐĩ ĐąĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž", "selected": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐž", "selected_count": "{count, plural, other {# Đ¸ĐˇĐąŅ€Đ°ĐŊи}}", "selected_gps_coordinates": "Đ˜ĐˇĐąŅ€Đ°ĐŊи GPS ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Đ¸", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", "setting_image_viewer_preview_subtitle": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸ Са да ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ҁҊҁ ҁҀĐĩĐ´ĐŊĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž (и Ņ€Đ°ĐˇĐŧĐĩŅ€). Đ—Đ°ĐąŅ€Đ°ĐŊи Са да ҁĐĩ ĐˇĐ°Ņ€ĐĩĐļда ŅĐ°ĐŧĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ и ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ°.", "setting_image_viewer_preview_title": "Đ—Đ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ°", + "setting_image_viewer_title": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "setting_languages_apply": "ĐŸŅ€Đ¸ĐģĐžĐļи", "setting_languages_subtitle": "ĐĄĐŧĐĩĐŊи ĐĩСиĐēа ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", + "setting_notifications_notify_failures_grace_period": "ПĐĩŅ€Đ¸ĐžĐ´ ĐąĐĩС иСвĐĩŅŅ‚Đ¸Ņ Са ĐŗŅ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ: {duration}", + "setting_notifications_notify_hours": "{count} Ņ‡Đ°ŅĐ°", + "setting_notifications_notify_immediately": "вĐĩĐ´ĐŊĐ°ĐŗĐ°", "setting_notifications_notify_minutes": "{count} ĐŧиĐŊŅƒŅ‚Đ¸", + "setting_notifications_notify_never": "ĐŊиĐēĐžĐŗĐ°", "setting_notifications_notify_seconds": "{count} ҁĐĩĐē҃ĐŊди", + "setting_notifications_single_progress_subtitle": "ПодобĐŊа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Са ĐŊаĐŋŅ€ĐĩĐ´ŅŠĐēа ĐŊа ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ Са Đ˛ŅĐĩĐēи ОйĐĩĐēŅ‚", + "setting_notifications_single_progress_title": "ПоĐēаСваĐŊĐĩ ĐŊа ĐŊаĐŋŅ€ĐĩĐ´ŅŠĐēа ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", "setting_notifications_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа иСвĐĩŅŅ‚Đ¸ŅŅ‚Đ°", + "setting_notifications_total_progress_subtitle": "ĐžĐąŅ‰ ĐŊаĐŋŅ€ĐĩĐ´ŅŠĐē ĐŊа ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ (ĐŗĐžŅ‚ĐžĐ˛Đž/Đ˛ŅĐ¸Ņ‡Đēи ОйĐĩĐēŅ‚Đ¸)", + "setting_notifications_total_progress_title": "ПоĐēаСваĐŊĐĩ ĐŊа ĐžĐąŅ‰Đ¸Ņ ĐŊаĐŋŅ€ĐĩĐ´ŅŠĐē ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐĩ Đ˛ŅŠĐ˛ Ņ„ĐžĐŊОв Ņ€ĐĩĐļиĐŧ", "setting_video_viewer_auto_play_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž СаĐŋĐžŅ‡ĐŊи Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ ĐŊа видĐĩĐž ĐŋŅ€Đ¸ ĐžŅ‚Đ˛Đ°Ņ€ŅĐŊĐĩ", "setting_video_viewer_auto_play_title": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ ĐŊа видĐĩĐž", "setting_video_viewer_looping_title": "ĐĻиĐēĐģĐ¸Ņ‡ĐŊĐž", "setting_video_viewer_original_video_subtitle": "ĐŸŅ€Đ¸ ĐŋĐžĐēаСваĐŊĐĩ ĐŊа видĐĩĐž ĐžŅ‚ ŅŅŠŅ€Đ˛ŅŠŅ€Đ° ĐŋĐžĐēаСваК ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐ¸Ņ Ņ„Đ°ĐšĐģ, Đ´ĐžŅ€Đ¸ и да иĐŧа Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ĐŊа вĐĩŅ€ŅĐ¸Ņ. МоĐļĐĩ да иСĐŋĐžĐģСва ĐąŅƒŅ„ĐĩŅ€Đ¸Ņ€Đ°ĐŊĐĩ. ЛоĐēаĐģĐŊĐž ĐŊаĐģĐ¸Ņ‡ĐŊĐ¸Ņ‚Đĩ видĐĩа ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚ виĐŊĐ°ĐŗĐ¸ в ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ, ĐŊĐĩĐˇĐ°Đ˛Đ¸ŅĐ¸ĐŧĐž ĐžŅ‚ Ņ‚Đ°ĐˇĐ¸ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа.", "setting_video_viewer_original_video_title": "ХаĐŧĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐž видĐĩĐž", "settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи", + "settings_require_restart": "МоĐģŅ, Са да ҁĐĩ ĐŋŅ€Đ¸ĐģĐžĐļи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ°Ņ‚Đ° Ņ€ĐĩŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°Đš Immich", "settings_saved": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ¸Ņ‚Đĩ ŅĐ° СаĐŋаСĐĩĐŊи", "setup_pin_code": "Задай PIN ĐēОд", "share": "ĐĄĐŋОдĐĩĐģŅĐŊĐĩ", + "share_action_prompt": "{count} ҁĐŋОдĐĩĐģĐĩĐŊи ОйĐĩĐēŅ‚Đ°", + "share_add_photos": "Добави ҁĐŊиĐŧĐēи", + "share_assets_selected": "{count} Đ¸ĐˇĐąŅ€Đ°ĐŊи", "share_dialog_preparing": "ĐŸĐžĐ´ĐŗĐžŅ‚ĐžĐ˛Đēа...", "share_link": "Đ’Ņ€ŅŠĐˇĐēа Са ҁĐŋОдĐĩĐģŅĐŊĐĩ", - "share_original": "ИСĐŋĐžĐģСваК ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģа (ĐŗĐžĐģŅĐŧ Ņ€Đ°ĐˇĐŧĐĩŅ€)", - "share_preview": "ИСĐŋĐžĐģСваК ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ° (ĐŊаĐŧаĐģĐĩĐŊ Ņ€Đ°ĐˇĐŧĐĩŅ€)", - "share_quality_body": "ĐĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ и ĐˇĐ°Đ´Ņ€ŅŠĐļŅ‚Đĩ ĐąŅƒŅ‚ĐžĐŊа Са ҁĐŋОдĐĩĐģŅĐŊĐĩ, Са да иСйĐĩŅ€ĐĩŅ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž, ĐŋŅ€Đĩди да ĐŗĐž ҁĐŋОдĐĩĐģĐ¸Ņ‚Đĩ.", - "share_quality_title": "ИСйĐĩŅ€ĐĩŅ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŋŅ€Đ¸ ҁĐŋОдĐĩĐģŅĐŊĐĩ", "shared": "ĐĄĐŋОдĐĩĐģĐĩĐŊĐž", "shared_album_activities_input_disable": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸Ņ‚Đĩ ŅĐ° иСĐēĐģŅŽŅ‡ĐĩĐŊи", "shared_album_activity_remove_content": "Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚Đ°ĐˇĐ¸ аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚?", "shared_album_activity_remove_title": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš", "shared_album_section_people_action_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐŊаĐŋ҃ҁĐēаĐŊĐĩ/ĐŋŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐžŅ‚ аĐģĐąŅƒĐŧа", + "shared_album_section_people_action_leave": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ ĐžŅ‚ аĐģĐąŅƒĐŧа", + "shared_album_section_people_action_remove_user": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ ĐžŅ‚ аĐģĐąŅƒĐŧа", "shared_album_section_people_title": "ĐĨОРА", "shared_by": "ĐĄĐŋОдĐĩĐģĐĩĐŊĐž ĐžŅ‚", "shared_by_user": "ĐĄĐŋОдĐĩĐģĐĩĐŊĐž ĐžŅ‚ {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Đ—Đ°Ņ€ĐĩĐ´ĐĩĐŊĐž", "shared_link_app_bar_title": "ĐĄĐŋОдĐĩĐģĐĩĐŊи Đ˛Ņ€ŅŠĐˇĐēи", "shared_link_clipboard_copied_massage": "КоĐŋĐ¸Ņ€Đ°ĐŊĐž в ĐēĐģиĐŋĐąĐžŅ€Đ´Đ°", + "shared_link_clipboard_text": "Đ’Ņ€ŅŠĐˇĐēа: {link}\nĐŸĐ°Ņ€ĐžĐģа: {password}", "shared_link_create_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ŅŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊа Đ˛Ņ€ŅŠĐˇĐēа", - "shared_link_custom_url_description": "ĐĄĐŋОдĐĩĐģĐĩŅ‚Đĩ Đ´ĐžŅŅ‚ŅŠĐŋ ҇ҀĐĩС Ņ‚ĐžĐˇĐ¸ ĐŋĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL ĐģиĐŊĐē", - "shared_link_custom_url_title": "ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL Đ°Đ´Ņ€Đĩҁ", - "shared_link_custom_url_warning": "ĐŸŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ: ПĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐž URL иĐŧĐĩ ҁ ĐŊаĐēĐģĐžĐŊĐĩĐŊи ĐŊаĐŋŅ€ĐĩĐ´ ĐŧĐžĐļĐĩ да ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ¸ ҁĐŋĐžŅ€ĐĩĐ´ ĐžŅ‡Đ°ĐēваĐŊĐ¸ŅŅ‚Đ°.", + "shared_link_custom_url_description": "Đ”ĐžŅŅ‚ŅŠĐŋĐĩŅ‚Đĩ ҁĐŋОдĐĩĐģĐĩĐŊĐ¸Ņ ĐģиĐŊĐē ҁ ĐŋĐĩŅ€ŅĐžĐŊаĐģĐ¸ĐˇĐ¸Ņ€Đ°ĐŊ URL Đ°Đ´Ņ€Đĩҁ", "shared_link_edit_description_hint": "Đ’ŅŠĐ˛Đĩди ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ ĐŊа ҁĐŋОдĐĩĐģĐĩĐŊĐžŅ‚Đž", "shared_link_edit_expire_after_option_day": "1 Đ´ĐĩĐŊ", "shared_link_edit_expire_after_option_days": "{count} Đ´ĐŊи", @@ -1984,6 +2123,12 @@ "shared_with_partner": "ĐĄĐŋОдĐĩĐģĐĩĐŊĐž ҁ {partner}", "sharing": "ĐĄĐŋОдĐĩĐģĐĩĐŊи", "sharing_enter_password": "МоĐģŅ, Đ˛ŅŠĐ˛ĐĩĐ´ĐĩŅ‚Đĩ ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ°, Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ Ņ‚Đ°ĐˇĐ¸ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°.", + "sharing_page_album": "ĐĄĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи", + "sharing_page_description": "ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ ҁĐŋОдĐĩĐģĐĩĐŊи аĐģĐąŅƒĐŧи Са да ҁĐŋОдĐĩĐģĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩа ҁ Ņ…ĐžŅ€Đ° ĐžŅ‚ Đ˛Đ°ŅˆĐ°Ņ‚Đ° ĐŧŅ€ĐĩĐļа.", + "sharing_page_empty_list": "ПРАЗЕН СПИСĐĒК", + "sharing_sidebar_description": "ПоĐēаĐļи Đ˛Ņ€ŅŠĐˇĐēа ĐēҊĐŧ ĐĄĐŋОдĐĩĐģŅĐŊĐĩ в ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ‡ĐŊĐ°Ņ‚Đ° ĐģĐĩĐŊŅ‚Đ°", + "sharing_silver_appbar_create_shared_album": "Нов ҁĐŋОдĐĩĐģĐĩĐŊ аĐģĐąŅƒĐŧ", + "sharing_silver_appbar_share_partner": "ĐĄĐŋОдĐĩĐģи ҁ ĐŋĐ°Ņ€Ņ‚ĐŊŅŒĐžŅ€", "shift_to_permanent_delete": "ĐĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ ⇧, Са да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ СавиĐŊĐ°ĐŗĐ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", "show_album_options": "ПоĐēаСваĐŊĐĩ ĐžĐŋŅ†Đ¸Đ¸ Са аĐģĐąŅƒĐŧ", "show_albums": "ПоĐēаĐļи аĐģĐąŅƒĐŧи", @@ -1995,9 +2140,7 @@ "show_in_timeline": "ПоĐēаСваĐŊĐĩ Đ˛ŅŠĐ˛ Đ˛Ņ€ĐĩĐŧĐĩĐ˛Đ°Ņ‚Đ° ĐģиĐŊĐ¸Ņ", "show_in_timeline_setting_description": "ПоĐēаСваĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩа ĐžŅ‚ Ņ‚ĐžĐˇĐ¸ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ Đ˛ŅŠĐ˛ Đ˛Ņ€ĐĩĐŧĐĩĐ˛Đ°Ņ‚Đ° ĐģиĐŊĐ¸Ņ", "show_keyboard_shortcuts": "ПоĐēаĐļи ĐēĐģĐ°Đ˛Đ¸ŅˆĐŊи ĐēĐžĐŧйиĐŊĐ°Ņ†Đ¸Đ¸", - "show_less": "ПоĐēаĐļи ĐŋĐž-ĐŧаĐģĐēĐž", "show_metadata": "ПоĐēаĐļи ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊи", - "show_more_fields": "{count, plural, one {ПоĐēаĐļи ĐžŅ‰Đĩ # ĐŋĐžĐģĐĩ} other {ПоĐēаĐļи ĐžŅ‰Đĩ # ĐŋĐžĐģĐĩŅ‚Đ°}}", "show_or_hide_info": "ПоĐēаĐļи иĐģи ҁĐēŅ€Đ¸Đš иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸ŅŅ‚Đ°", "show_password": "ПоĐēаĐļи ĐŋĐ°Ņ€ĐžĐģĐ°Ņ‚Đ°", "show_person_options": "ПоĐēаСваĐŊĐĩ ĐŊа ĐžĐŋŅ†Đ¸Đ¸ Са ĐģĐ¸Ņ†Đ°", @@ -2005,7 +2148,6 @@ "show_schema": "ПоĐēаĐļи ҁ҅ĐĩĐŧа", "show_search_options": "ПоĐēаСваĐŊĐĩ ĐŊа ĐžĐŋŅ†Đ¸Đ¸Ņ‚Đĩ Са Ņ‚ŅŠŅ€ŅĐĩĐŊĐĩ", "show_shared_links": "ПоĐēаĐļи ҁĐŋОдĐĩĐģĐĩĐŊи ĐģиĐŊĐēОвĐĩ", - "show_slideshow_metadata_overlay": "ПоĐēаĐļи иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸ĐžĐŊĐŊĐ¸Ņ ҁĐģОК", "show_slideshow_transition": "ПоĐēаĐļи ĐŋŅ€ĐĩŅ…ĐžĐ´Đ° ĐŊа ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž", "show_supporter_badge": "ЗĐŊĐ°Ņ‡Đēа ĐŋĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē", "show_supporter_badge_description": "ПоĐēаĐļи СĐŊĐ°Ņ‡Đēа ĐŋĐžĐ´Đ´Ņ€ŅŠĐļĐŊиĐē", @@ -2017,45 +2159,35 @@ "sign_out": "ĐžŅ‚ĐŋĐ¸ŅĐ˛Đ°ĐŊĐĩ", "sign_up": "ЗаĐŋĐ¸ŅˆĐ¸ ҁĐĩ", "size": "РаСĐŧĐĩŅ€", - "skip": "ĐŸŅ€ĐžĐŋ҃ҁĐŊи", "skip_to_content": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ ŅŅŠĐ´ŅŠŅ€ĐļаĐŊиĐĩŅ‚Đž", "skip_to_folders": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ ĐŋаĐŋĐēĐ¸Ņ‚Đĩ", "skip_to_tags": "ĐŸŅ€ĐĩĐŧиĐŊи ĐēҊĐŧ ĐĩŅ‚Đ¸ĐēĐĩŅ‚Đ¸Ņ‚Đĩ", "slideshow": "ĐĄĐģĐ°ĐšĐ´ŅˆĐžŅƒ", - "slideshow_body": "ĐžŅ‚Đŋ҃ҁĐŊĐĩŅ‚Đĩ ҁĐĩ и ĐŗĐģĐĩĐ´Đ°ĐšŅ‚Đĩ ҁĐŊиĐŧĐēĐ¸Ņ‚Đĩ ŅĐ¸, Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊи в ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒ ĐŊа Ņ†ŅĐģ ĐĩĐēŅ€Đ°ĐŊ.", - "slideshow_metadata_overlay_mode": "ĐĄŅŠĐ´ŅŠŅ€ĐļаĐŊиĐĩ ĐŊа ҁĐģĐžŅ ҁ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", - "slideshow_metadata_overlay_mode_description_only": "ХаĐŧĐž ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", - "slideshow_metadata_overlay_mode_full": "ĐŸŅŠĐģĐŊа", "slideshow_repeat": "ĐŸĐžĐ˛Ņ‚Đ°Ņ€ŅĐš ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž", "slideshow_repeat_description": "ЗаĐŋĐžŅ‡Đ˛Đ°Đš ĐžŅ‚ĐŊОвО, ĐēĐžĐŗĐ°Ņ‚Đž ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒŅ‚Đž ĐŋŅ€Đ¸ĐēĐģŅŽŅ‡Đ¸", "slideshow_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи Са ҁĐģĐ°ĐšĐ´ŅˆĐžŅƒ", - "slideshow_title": "ĐĄĐģĐ°ĐšĐ´ŅˆĐžŅƒ", - "smart_album": "ĐŖĐŧĐĩĐŊ аĐģĐąŅƒĐŧ", - "some_assets_already_have_a_location_warning": "ĐŅĐēОи ĐžŅ‚ Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ вĐĩ҇Đĩ иĐŧĐ°Ņ‚ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "sort_albums_by": "ĐĄĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи ĐŋĐž...", "sort_created": "Đ”Đ°Ņ‚Đ° ĐŊа ŅŅŠĐˇĐ´Đ°Đ˛Đ°ĐŊĐĩ", "sort_items": "Đ‘Ņ€ĐžĐš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "sort_modified": "Đ”Đ°Ņ‚Đ° ĐŊа ĐŋŅ€ĐžĐŧŅĐŊа", - "sort_oldest": "Най-ŅŅ‚Đ°Ņ€Đ° ҁĐŊиĐŧĐēа", + "sort_newest": "Най-ĐŊОви ҁĐŊиĐŧĐēи", + "sort_oldest": "Най-ŅŅ‚Đ°Ņ€Đ°Ņ‚Đ° ҁĐŊиĐŧĐēа", "sort_people_by_similarity": "ĐĄĐžŅ€Ņ‚Đ¸Ņ€Đ°ĐŊĐĩ ĐŊа Ņ…ĐžŅ€Đ° ĐŋĐž ĐŋŅ€Đ¸ĐģиĐēа", - "sort_recent": "Най-ҁĐēĐžŅ€ĐžŅˆĐŊа ҁĐŊиĐŧĐēа", + "sort_recent": "Най-ĐŊĐžĐ˛Đ°Ņ‚Đ° ҁĐŊиĐŧĐēа", "sort_title": "Đ—Đ°ĐŗĐģавиĐĩ", "source": "Код", - "stack": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Đš", + "stack": "ĐĄŅŠĐąĐĩŅ€Đ¸", "stack_action_prompt": "{count} ŅĐ° ĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи", - "stack_duplicates": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Đš Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", - "stack_selected_photos": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Đ¸ĐˇĐąŅ€Đ°ĐŊи ҁĐŊиĐŧĐēи", - "stacked_assets_count": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊ(и) {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", - "stacktrace": "ĐŸŅ€ĐžŅĐģĐĩĐ´ŅĐ˛Đ°ĐŊĐĩ ĐŊа ҁ҂ĐĩĐēа (stacktrace)", - "start": "ĐĐ°Ņ‡Đ°ĐģĐž", + "stack_duplicates": "ĐŸĐžĐ´Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", + "stack_select_one_photo": "ИСйĐĩŅ€Đ¸ ĐĩĐ´ĐŊа ĐŗĐģавĐŊа ҁĐŊиĐŧĐēа Са ŅŅŠĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи", + "stack_selected_photos": "ĐŸĐžĐ´Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа Đ¸ĐˇĐąŅ€Đ°ĐŊи ҁĐŊиĐŧĐēи", + "stacked_assets_count": "ĐĄŅŠĐąŅ€Đ°ĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", + "stacktrace": "ĐĄĐģĐĩда ĐŊа ŅŅŠĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ", + "start": "ĐĄŅ‚Đ°Ņ€Ņ‚", "start_date": "ĐĐ°Ņ‡Đ°ĐģĐŊа Đ´Đ°Ņ‚Đ°", "start_date_before_end_date": "ĐĐ°Ņ‡Đ°ĐģĐŊĐ°Ņ‚Đ° Đ´Đ°Ņ‚Đ° Ņ‚Ņ€ŅĐąĐ˛Đ° да ĐąŅŠĐ´Đĩ ĐŋŅ€Đĩди ĐēŅ€Đ°ĐšĐŊĐ°Ņ‚Đ° Đ´Đ°Ņ‚Đ°", - "state": "ОбĐģĐ°ŅŅ‚ (Ņ‰Đ°Ņ‚)", + "state": "ĐŠĐ°Ņ‚", "status": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ", - "step_delete": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ŅŅ‚ŅŠĐŋĐēĐ°Ņ‚Đ°", - "step_delete_confirm": "ĐĄĐ¸ĐŗŅƒŅ€ĐŊи Đģи ҁ҂Đĩ, ҇Đĩ Đ¸ŅĐēĐ°Ņ‚Đĩ да ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩŅ‚Đĩ Ņ‚Đ°ĐˇĐ¸ ŅŅ‚ŅŠĐŋĐēа?", - "step_details": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ Са ŅŅ‚ŅŠĐŋĐēĐ°Ņ‚Đ°", - "steps": "ĐĄŅ‚ŅŠĐŋĐēи", "stop_casting": "ĐĄĐŋŅ€Đ¸ ĐŋŅ€ĐĩдаваĐŊĐĩŅ‚Đž", "stop_motion_photo": "ĐĄĐŊиĐŧĐēа ҁҊҁ ŅŅ‚ĐžĐŋ ĐēĐ°Đ´ŅŠŅ€", "stop_photo_sharing": "Да ҁĐŋŅ€Đ° Đģи ҁĐŋОдĐĩĐģŅĐŊĐĩŅ‚Đž ĐŊа Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "РаСĐŧŅĐŊа ĐŋĐžŅĐžĐēĐ°Ņ‚Đ° ĐŊа ҁĐģиваĐŊĐĩ", "sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ", "sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа аĐģĐąŅƒĐŧи", + "sync_albums_manual_subtitle": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊи видĐĩа и ҁĐŊиĐŧĐēи в Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛ĐŊи аĐģĐąŅƒĐŧи", "sync_local": "ЛоĐēаĐģĐŊа ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ", "sync_remote": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ҁҊҁ ŅŅŠŅ€Đ˛ŅŠŅ€Đ°", "sync_status": "ĐĄŅŠŅŅ‚ĐžŅĐŊиĐĩ ĐŊа ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸ŅŅ‚Đ°", "sync_status_subtitle": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ и ҃ĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊа ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ° Са ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ", "sync_upload_album_setting_subtitle": "ĐĄŅŠĐˇĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ и ĐˇĐ°Ņ€ĐĩĐļĐ´Đ°ĐšŅ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩа в Đ¸ĐˇĐąŅ€Đ°ĐŊи аĐģĐąŅƒĐŧи в Immich", - "system_theme": "ĐĸĐĩĐŧа ĐžŅ‚ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°", - "system_theme_command_description": "ИСĐŋĐžĐģСваК ŅĐ¸ŅŅ‚ĐĩĐŧĐŊĐ°Ņ‚Đ° Ņ‚ĐĩĐŧа ({value})", "tag": "ĐĸĐ°Đŗ", "tag_assets": "ĐĸĐ°ĐŗĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "tag_created": "ĐĄŅŠĐˇĐ´Đ°Đ´ĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}", "tag_face": "ĐžŅ‚ĐąĐĩĐģĐĩĐļи ĐģĐ¸Ņ†Đĩ", "tag_feature_description": "Đ Đ°ĐˇĐŗĐģĐĩĐļдаĐŊĐĩ ĐŊа ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ, ĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи ĐŋĐž Ņ‚ĐĩĐŧи ҁ ĐģĐžĐŗĐ¸Ņ‡ĐĩҁĐēи Ņ‚Đ°ĐŗĐžĐ˛Đĩ", + "tag_not_found_question": "НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да ĐŊаĐŧĐĩŅ€Đ¸Ņ‚Đĩ ĐĩŅ‚Đ¸ĐēĐĩŅ‚? ĐĄŅŠĐˇĐ´Đ°ĐšŅ‚Đĩ ĐŊОв ĐĩŅ‚Đ¸ĐēĐĩŅ‚.", "tag_people": "ĐžŅ‚ĐąĐĩĐģĐĩĐļи ĐĨĐžŅ€Đ°", "tag_updated": "ОбĐŊОвĐĩĐŊ ĐĩŅ‚Đ¸ĐēĐĩŅ‚: {tag}", "tagged_assets": "ĐĸĐ°ĐŗĐŊĐ°Ņ‚Đ¸ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Đ‘Ņ€ĐžĐš ОйĐĩĐēŅ‚Đ¸ ĐŊа Ņ€ĐĩĐ´ ({count})", "theme_setting_colorful_interface_subtitle": "НаĐŊĐĩŅĐ¸ ĐžŅĐŊОвĐĩĐŊ Ņ†Đ˛ŅŅ‚ Đ˛ŅŠŅ€Ņ…Ņƒ Ņ„ĐžĐŊĐžĐ˛Đ¸Ņ‚Đĩ ĐŋĐžĐ˛ŅŠŅ€Ņ…ĐŊĐžŅŅ‚Đ¸.", "theme_setting_colorful_interface_title": "ĐĻвĐĩŅ‚ĐžĐ˛Đĩ ĐŊа иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅĐ°", + "theme_setting_image_viewer_quality_subtitle": "Đ ĐĩĐŗŅƒĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐžŅ‚Đž ĐŊа ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐ°Ņ‚Đ° Са ĐŋŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа Đ´ĐĩŅ‚Đ°ĐšĐģĐŊи Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", + "theme_setting_image_viewer_quality_title": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊа ĐŋŅ€ĐĩĐŗĐģĐĩда ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "theme_setting_primary_color_subtitle": "ИСйĐĩŅ€Đ¸ Ņ†Đ˛ŅŅ‚ Са ĐžŅĐŊОвĐŊĐ¸Ņ‚Đĩ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ и аĐē҆ĐĩĐŊŅ‚Đ¸.", "theme_setting_primary_color_title": "ĐžŅĐŊОвĐĩĐŊ Ņ†Đ˛ŅŅ‚", "theme_setting_system_primary_color_title": "ИСĐŋĐžĐģСваК ĐžŅ‚ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ°", "theme_setting_system_theme_switch": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž (ĐĄĐŋĐžŅ€ĐĩĐ´ ŅĐ¸ŅŅ‚ĐĩĐŧĐŊĐ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа)", + "theme_setting_theme_subtitle": "Задай ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŊа Ņ†Đ˛ĐĩŅ‚ĐžĐ˛Đ°Ņ‚Đ° Ņ‚ĐĩĐŧа ĐŊа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩŅ‚Đž", + "theme_setting_three_stage_loading_subtitle": "ĐĸŅ€Đ¸-ҁ҂ĐĩĐŋĐĩĐŊĐŊĐžŅ‚Đž ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŧĐžĐļĐĩ да ŅƒĐ˛ĐĩĐģĐ¸Ņ‡Đ¸ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚Ņ‚Đ°, ĐŊĐž ҉Đĩ ŅƒĐ˛ĐĩĐģĐ¸Ņ‡Đ¸ СĐŊĐ°Ņ‡Đ¸Ņ‚ĐĩĐģĐŊĐž и ĐŧŅ€ĐĩĐļĐžĐ˛Đ¸Ņ Ņ‚Ņ€Đ°Ņ„Đ¸Đē", + "theme_setting_three_stage_loading_title": "ВĐēĐģŅŽŅ‡Đ¸ Ņ‚Ņ€Đ¸-ҁ҂ĐĩĐŋĐĩĐŊĐŊĐž ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ", "then": "ĐĄĐģĐĩĐ´ Ņ‚ĐžĐ˛Đ°", "they_will_be_merged_together": "ĐĸĐĩ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ ОйĐĩдиĐŊĐĩĐŊи", "third_party_resources": "Đ ĐĩŅŅƒŅ€ŅĐ¸ ĐžŅ‚ ҂ҀĐĩŅ‚Đ¸ ŅŅ‚Ņ€Đ°ĐŊи", @@ -2131,17 +2268,23 @@ "trash_all": "Đ˜ĐˇŅ…Đ˛ŅŠŅ€Đģи Đ˛ŅĐ¸Ņ‡Đēи", "trash_count": "В ĐšĐžŅˆŅ‡ĐĩŅ‚Đž {count, number}", "trash_delete_asset": "ВĐēĐ°Ņ€Đ°Đš в ĐšĐžŅˆŅ‡ĐĩŅ‚Đž/Đ˜ĐˇŅ‚Ņ€Đ¸Đš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", + "trash_emptied": "ĐšĐžŅˆĐ° Đĩ иСĐŋŅ€Đ°ĐˇĐŊĐĩĐŊ", "trash_no_results_message": "Đ˜ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸Ņ‚Đĩ ҁĐŊиĐŧĐēи и видĐĩĐžĐēĐģиĐŋОвĐĩ ҉Đĩ ҁĐĩ ĐŋĐžĐēĐ°ĐˇĐ˛Đ°Ņ‚ Ņ‚ŅƒĐē.", "trash_page_delete_all": "Đ˜ĐˇŅ‚Ņ€Đ¸Đš Đ˛ŅĐ¸Ņ‡ĐēĐž", + "trash_page_empty_trash_dialog_content": "Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да иСĐŋŅ€Đ°ĐˇĐŊŅ ĐēĐžŅˆĐ°? ĐĸĐĩСи ОйĐĩĐēŅ‚Đ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ СавиĐŊĐ°ĐŗĐ¸ ĐžŅ‚ Immich", "trash_page_info": "ОбĐĩĐēŅ‚Đ¸Ņ‚Đĩ в ĐēĐžŅˆĐ° ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Đ˛Đ°ĐŊи СавиĐŊĐ°ĐŗĐ¸ ҁĐģĐĩĐ´ {days} Đ´ĐŊи", + "trash_page_no_assets": "ĐšĐžŅˆĐ° Đĩ ĐŋŅ€Đ°ĐˇĐĩĐŊ", + "trash_page_restore_all": "Đ’ŅŠĐˇŅŅ‚Đ°ĐŊĐžĐ˛ŅĐ˛Đ°ĐŊĐĩ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи", + "trash_page_select_assets_btn": "ИСйĐĩŅ€Đ¸ ОйĐĩĐēŅ‚Đ¸", + "trash_page_title": "В ĐēĐžŅˆĐ° ({count})", "trashed_items_will_be_permanently_deleted_after": "Đ˜ĐˇŅ…Đ˛ŅŠŅ€ĐģĐĩĐŊĐ¸Ņ‚Đĩ в ĐēĐžŅˆŅ‡ĐĩŅ‚Đž ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ҉Đĩ ĐąŅŠĐ´Đ°Ņ‚ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚Đ¸ Са ĐŋĐžŅŅ‚ĐžŅĐŊĐŊĐž ҁĐģĐĩĐ´ {days, plural, one {# Đ´ĐĩĐŊ} other {# Đ´ĐŊи}}.", "trigger": "ĐĸŅ€Đ¸ĐŗĐĩŅ€", - "trigger_asset_metadata_extraction": "ИСвĐģĐ¸Ņ‡Đ°ĐŊĐĩ ĐŊа ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊи ĐžŅ‚ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", - "trigger_asset_metadata_extraction_description": "ЗадĐĩĐšŅŅ‚Đ˛Đ°ĐŊ, ĐēĐžĐŗĐ°Ņ‚Đž EXIF ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊĐ¸Ņ‚Đĩ ĐžŅ‚ дадĐĩĐŊ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ŅĐ° иСвĐģĐĩ҇ĐĩĐŊи", - "trigger_asset_uploaded": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩ ĐŊа Ņ„Đ°ĐšĐģОвĐĩ", + "trigger_asset_uploaded": "ОбĐĩĐēŅ‚ŅŠŅ‚ Đĩ ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊ", "trigger_asset_uploaded_description": "ĐĄŅ€Đ°ĐąĐžŅ‚Đ˛Đ° ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа ĐŊОв ОйĐĩĐēŅ‚", + "trigger_description": "ĐĄŅŠĐąĐ¸Ņ‚Đ¸Đĩ, ĐēĐžĐĩŅ‚Đž ŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ° Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", "trigger_person_recognized": "РаСĐŋОСĐŊĐ°Ņ‚Đž Đĩ ĐģĐ¸Ņ†Đĩ", - "trigger_person_recognized_description": "ЗадĐĩĐšŅŅ‚Đ˛Đ°ĐŊ ĐŋŅ€Đ¸ Ņ€Đ°ĐˇĐŋОСĐŊаваĐŊĐĩ ĐŊа ĐģĐ¸Ņ†Đĩ", + "trigger_person_recognized_description": "ĐĄŅ€Đ°ĐąĐžŅ‚Đ˛Đ° ĐŋŅ€Đ¸ Ņ€Đ°ĐˇĐŋОСĐŊаваĐŊĐĩ ĐŊа ĐģĐ¸Ņ†Đĩ", + "trigger_type": "ĐĸиĐŋ ĐŊа Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Đ°", "troubleshoot": "ĐžŅ‚ŅŅ‚Ņ€Đ°ĐŊŅĐ˛Đ°ĐŊĐĩ ĐŊа ĐŋŅ€ĐžĐąĐģĐĩĐŧи", "type": "ĐĸиĐŋ", "unable_to_change_pin_code": "НĐĩĐ˛ŅŠĐˇĐŧĐžĐļĐŊа ĐŋŅ€ĐžĐŧŅĐŊа ĐŊа PIN ĐēОда", @@ -2157,7 +2300,6 @@ "unknown": "НĐĩиСвĐĩҁ҂ĐŊĐž", "unknown_country": "НĐĩĐŋОСĐŊĐ°Ņ‚Đ° Đ”ŅŠŅ€Đļава", "unknown_date": "НĐĩиСвĐĩҁ҂ĐŊа Đ´Đ°Ņ‚Đ°", - "unknown_schema": "НĐĩиСвĐĩҁ҂ĐŊа ҁ҅ĐĩĐŧа", "unknown_year": "НĐĩиСвĐĩҁ҂ĐŊа ĐŗĐžĐ´Đ¸ĐŊа", "unlimited": "НĐĩĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊĐž", "unlink_motion_video": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ° ҁ видĐĩĐž", @@ -2170,20 +2312,23 @@ "unsaved_change": "НĐĩСаĐŋаСĐĩĐŊа ĐŋŅ€ĐžĐŧŅĐŊа", "unselect_all": "ДĐĩҁĐĩĐģĐĩĐēŅ‚Đ¸Ņ€Đ°ĐšŅ‚Đĩ Đ˛ŅĐ¸Ņ‡Đēи", "unselect_all_duplicates": "ĐžŅ‚ ĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°Đš Đ˛ŅĐ¸Ņ‡Đēи Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Đ¸", + "unselect_all_in": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи Đ¸ĐˇĐąĐžŅ€Đ° ĐŊа Đ˛ŅĐ¸Ņ‡Đēи ĐžŅ‚ ĐŗŅ€ŅƒĐŋĐ°Ņ‚Đ° {group}", "unstack": "РаСĐēĐ°Ņ‡Đ¸", "unstack_action_prompt": "{count} ŅĐ° Ņ€Đ°ĐˇĐŗŅ€ŅƒĐŋĐ¸Ņ€Đ°ĐŊи", "unstacked_assets_count": "РаСĐēĐ°Ņ‡ĐĩĐŊи {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸}}", "unsupported_field_type": "ĐĸиĐŋа ĐŊа ĐŋĐžĐģĐĩŅ‚Đž ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа", "unsupported_file_type": "ФаКĐģŅŠŅ‚ {file} ĐŊĐĩ ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ ĐˇĐ°Ņ€ĐĩĐ´ĐĩĐŊ, ĐˇĐ°Ņ‰ĐžŅ‚Đž ĐŊĐĩĐŗĐžĐ˛Đ¸ŅŅ‚ Ņ‚Đ¸Đŋ {type} ĐŊĐĩ ҁĐĩ ĐŋĐžĐ´Đ´ŅŠŅ€Đļа.", "untagged": "НĐĩĐŧĐ°Ņ€ĐēĐ¸Ņ€Đ°ĐŊи", + "untitled_workflow": "Đ Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ ĐąĐĩС иĐŧĐĩ", "up_next": "ĐĄĐģĐĩĐ´Đ˛Đ°Ņ‰", "update_location_action_prompt": "ОбĐŊОви ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Đ¸Ņ‚Đĩ ĐŊа {count} Đ¸ĐˇĐąŅ€Đ°ĐŊи ОйĐĩĐēŅ‚Đ° ҁ:", "updated_at": "ОбĐŊОвĐĩĐŊĐž", "updated_password": "ĐŸĐ°Ņ€ĐžĐģĐ°Ņ‚Đ° Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊа", "upload": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩ", "upload_concurrency": "ĐŖŅĐŋĐžŅ€ĐĩĐ´ĐŊи ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐ¸Ņ", - "upload_day_count": "{date}: {count, plural, one {# ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ} other {# ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐ¸Ņ}}", "upload_details": "ДĐĩŅ‚Đ°ĐšĐģи Са ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž", + "upload_dialog_info": "Đ˜ŅĐēĐ°Ņ‚Đĩ Đģи да Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚Đĩ ĐŊа ŅŅŠŅ€Đ˛ŅŠŅ€Đ° Đ¸ĐˇĐąŅ€Đ°ĐŊĐ¸Ņ‚Đĩ ОйĐĩĐēŅ‚Đ¸?", + "upload_dialog_title": "ĐšĐ°Ņ‡Đ¸ ОйĐĩĐēŅ‚", "upload_error_with_count": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐˇĐ°Ņ€ĐĩĐļдаĐŊĐĩ ĐŊа {count, plural, one {# ОйĐĩĐēŅ‚} other {# ОйĐĩĐēŅ‚Đ°}}", "upload_errors": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž Đĩ ĐˇĐ°Đ˛ŅŠŅˆĐĩĐŊĐž ҁ {count, plural, one {# ĐŗŅ€Đĩ҈Đēа} other {# ĐŗŅ€Đĩ҈Đēи}}, ОйĐŊОвĐĩŅ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°Ņ‚Đ° Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ ĐŊĐžĐ˛Đ¸Ņ‚Đĩ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸.", "upload_finished": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž ĐˇĐ°Đ˛ŅŠŅ€ŅˆĐ¸", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Đ“Ņ€Đĩ҈Đēи", "upload_status_uploaded": "ĐšĐ°Ņ‡ĐĩĐŊĐž", "upload_success": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩŅ‚Đž Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž, ĐžĐŋŅ€ĐĩҁĐŊĐĩŅ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đ°Ņ‚Đ°, Са да Đ˛Đ¸Đ´Đ¸Ņ‚Đĩ ĐŊĐžĐ˛Đ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģОвĐĩ.", - "upload_to_album_body": "За ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģи, ĐēĐžĐ¸Ņ‚Đž ĐŊĐĩ иСĐŋĐžĐģĐˇĐ˛Đ°Ņ‚ Ņ„ŅƒĐŊĐēŅ†Đ¸ŅŅ‚Đ° Са Ņ€ŅŠŅ‡ĐŊĐž ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ, вĐĩ҇Đĩ ĐŧĐžĐļĐĩŅ‚Đĩ да иСйĐĩŅ€ĐĩŅ‚Đĩ да Đ´ĐžĐąĐ°Đ˛ŅŅ‚Đĩ ĐģĐžĐēаĐģĐŊи ҁĐŊиĐŧĐēи Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž в аĐģĐąŅƒĐŧ, Đ´ĐžĐēĐ°Ņ‚Đž ĐŗĐ¸ ĐēĐ°Ņ‡Đ˛Đ°Ņ‚Đĩ, вĐĩ҇Đĩ ĐŊĐĩ Đĩ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐž да ĐēĐ°Ņ‡Đ˛Đ°Ņ‚Đĩ и ҁĐģĐĩĐ´ Ņ‚ĐžĐ˛Đ° да Đ´ĐžĐąĐ°Đ˛ŅŅ‚Đĩ ĐēҊĐŧ аĐģĐąŅƒĐŧ.", - "upload_to_album_title": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩ Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž в аĐģĐąŅƒĐŧ", "upload_to_immich": "КазваĐŊĐĩ в Immich ({count})", "uploading": "ĐšĐ°Ņ‡Đ˛Đ°ĐŧĐĩ", "uploading_media": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐĩ ĐŊа ĐŧĐĩдиКĐŊи Ņ„Đ°ĐšĐģОвĐĩ", - "uploads": "ĐšĐ°Ņ‡Đ˛Đ°ĐŊĐ¸Ņ", - "uploads_count": "{count, plural, one {# ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐĩ} other {# ĐēĐ°Ņ‡Đ˛Đ°ĐŊĐ¸Ņ}}", "url": "URL", "usage": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐģĐĩĐŊиĐĩ", "use_biometric": "ИСĐŋĐžĐģСваК йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ ĐŊа Đ´Đ°Ņ‚Đ°, Đ˛Ņ€ĐĩĐŧĐĩ и Ņ‡Đ¸ŅĐģа ҁĐŋĐžŅ€ĐĩĐ´ ĐĩСиĐēĐžĐ˛Đ°Ņ‚Đ° ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŊа ĐąŅ€Đ°ŅƒĐˇŅŠŅ€Đ°", "use_current_connection": "ИСĐŋĐžĐģСваК Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ‚Đ° Đ˛Ņ€ŅŠĐˇĐēа", "use_custom_date_range": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ ŅĐžĐąŅŅ‚Đ˛ĐĩĐŊ диаĐŋаСОĐŊ ĐžŅ‚ Đ´Đ°Ņ‚Đ¸ вĐŧĐĩŅŅ‚Đž Ņ‚ĐžĐ˛Đ°", - "use_template": "ИСĐŋĐžĐģĐˇĐ˛Đ°ĐšŅ‚Đĩ ŅˆĐ°ĐąĐģĐžĐŊ", "user": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ", "user_has_been_deleted": "ĐĸОСи ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ Đĩ ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐ°Ņ‚.", "user_id": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģ ИД", @@ -2217,6 +2357,7 @@ "user_privacy": "ПовĐĩŅ€Đ¸Ņ‚ĐĩĐģĐŊĐžŅŅ‚ ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģĐ¸Ņ‚Đĩ", "user_purchase_settings": "ПоĐē҃ĐŋĐēа", "user_purchase_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐ˛Đ°ĐšŅ‚Đĩ ĐŋĐžĐē҃ĐŋĐēĐ°Ņ‚Đ° ŅĐ¸", + "user_role_set": "Задай {user} ĐēĐ°Ņ‚Đž {role}", "user_usage_detail": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ Са иСĐŋĐžĐģСваĐŊĐĩŅ‚Đž ĐŊа ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ", "user_usage_stats": "ĐĄŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đēа Са иСĐŋĐžĐģСваĐŊĐĩŅ‚Đž ĐŊа аĐēĐ°ŅƒĐŊŅ‚Đ°", "user_usage_stats_description": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ŅŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸ĐēĐ°Ņ‚Đ° Са иСĐŋĐžĐģСваĐŊĐĩŅ‚Đž ĐŊа аĐēĐ°ŅƒĐŊŅ‚Đ°", @@ -2226,6 +2367,7 @@ "utilities": "ИĐŊŅŅ‚Ņ€ŅƒĐŧĐĩĐŊŅ‚Đ¸", "validate": "ВаĐģĐ¸Đ´Đ¸Ņ€Đ°ĐŊĐĩ", "validate_endpoint_error": "МоĐģŅ, Đ˛ŅŠĐ˛Đĩди ĐŋŅ€Đ°Đ˛Đ¸ĐģĐĩĐŊ URL", + "validation_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ваĐģĐ¸Đ´Đ¸Ņ€Đ°ĐŊĐĩ", "variables": "ĐŸŅ€ĐžĐŧĐĩĐŊĐģиви", "version": "ВĐĩŅ€ŅĐ¸Ņ", "version_announcement_closing": "ĐĸвОК ĐŋŅ€Đ¸ŅŅ‚ĐĩĐģ, АĐģĐĩĐēҁ", @@ -2235,9 +2377,9 @@ "video": "ВидĐĩĐžĐēĐģиĐŋ", "video_hover_setting": "Đ’ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ ĐŊа видĐĩĐžĐēĐģиĐŋ ĐŋŅ€Đ¸ ĐŋĐžŅĐžŅ‡Đ˛Đ°ĐŊĐĩ ҁ ĐŧĐ¸ŅˆĐēĐ°Ņ‚Đ°", "video_hover_setting_description": "Đ’ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ ĐŊа видĐĩĐžĐēĐģиĐŋа, ĐēĐžĐŗĐ°Ņ‚Đž ĐŧĐ¸ŅˆĐēĐ°Ņ‚Đ° ҁĐĩ двиĐļи ĐŊад ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°. Đ”ĐžŅ€Đ¸ ĐēĐžĐŗĐ°Ņ‚Đž Đĩ Đ´ĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž, Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩŅ‚Đž ĐŧĐžĐļĐĩ да ĐąŅŠĐ´Đĩ ŅŅ‚Đ°Ņ€Ņ‚Đ¸Ņ€Đ°ĐŊĐž ҇ҀĐĩС ĐˇĐ°Đ´ŅŠŅ€ĐļаĐŊĐĩ ĐŊа ĐēŅƒŅ€ŅĐžŅ€Đ° ĐŊа ĐŧĐ¸ŅˆĐēĐ°Ņ‚Đ° Đ˛ŅŠŅ€Ņ…Ņƒ иĐēĐžĐŊĐ°Ņ‚Đ° Са Đ˛ŅŠĐˇĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐļдаĐŊĐĩ.", - "video_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊа видĐĩĐžŅ‚Đž", "videos": "ВидĐĩĐžĐēĐģиĐŋОвĐĩ", "videos_count": "{count, plural, one {# ВидĐĩĐž} other {# ВидĐĩа}}", + "videos_only": "ХаĐŧĐž видĐĩа", "view": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´", "view_album": "Đ Đ°ĐˇĐŗĐģĐĩдаК аĐģĐąŅƒĐŧа", "view_all": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа Đ˛ŅĐ¸Ņ‡Đēи", @@ -2246,29 +2388,29 @@ "view_details": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ Са Đ¸ĐˇĐŗĐģĐĩда", "view_in_timeline": "ПоĐēаĐļи Đ˛ŅŠĐ˛ Đ˛Ņ€ĐĩĐŧĐĩва ĐģиĐŊĐ¸Ņ", "view_link": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа Đ˛Ņ€ŅŠĐˇĐēĐ°Ņ‚Đ°", + "view_links": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа Đ˛Ņ€ŅŠĐˇĐēĐ¸Ņ‚Đĩ", "view_name": "ĐŸŅ€ĐĩĐŗĐģĐĩдаК", "view_next_asset": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ҁĐģĐĩĐ´Đ˛Đ°Ņ‰Đ¸Ņ Ņ„Đ°ĐšĐģ", "view_previous_asset": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´ ĐŊа ĐŋŅ€ĐĩĐ´Đ¸ŅˆĐŊĐ¸Ņ Ņ„Đ°ĐšĐģ", "view_qr_code": "ВиĐļ QR ĐēОда", "view_similar_photos": "ВиĐļ ĐŋОдОйĐŊи ҁĐŊиĐŧĐēи", "view_stack": "ПоĐēаĐļи в ҁ҂ĐĩĐē", + "view_user": "ВиĐļ ĐŋĐžŅ‚Ņ€ĐĩĐąĐ¸Ņ‚ĐĩĐģŅ", "viewer_remove_from_stack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…Đ˛Đ°ĐŊĐĩ ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", + "viewer_stack_use_as_main_asset": "ИСĐŋĐžĐģСваК ĐēĐ°Ņ‚Đž ĐžŅĐŊОвĐĩĐŊ", + "viewer_unstack": "ĐŸŅ€ĐĩĐŧĐ°Ņ…ĐŊи ĐžŅ‚ ĐžĐŋĐ°ŅˆĐēĐ°Ņ‚Đ°", "visibility": "ВидиĐŧĐžŅŅ‚", "visibility_changed": "ВидиĐŧĐžŅŅ‚Ņ‚Đ° Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐĩĐŊа Са {count, plural, one {# Ņ‡ĐžĐ˛ĐĩĐē} other {# Ņ‡ĐžĐ˛ĐĩĐēа}}", "visual": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ", + "visual_builder": "Đ’Đ¸ĐˇŅƒĐ°ĐģĐĩĐŊ ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€", "waiting": "в Đ¸ĐˇŅ‡Đ°ĐēваĐŊĐĩ", "waiting_count": "В Đ¸ĐˇŅ‡Đ°ĐēваĐŊĐĩ: {count}", "warning": "ВĐŊиĐŧаĐŊиĐĩ", "week": "ĐĄĐĩĐ´ĐŧĐ¸Ņ†Đ°", "welcome": "Đ”ĐžĐąŅ€Đĩ Đ´ĐžŅˆĐģи", "welcome_to_immich": "Đ”ĐžĐąŅ€Đĩ Đ´ĐžŅˆĐģи в Immich", - "whats_new": "КаĐēвО Đĩ ĐŊĐžĐ˛ĐžŅ‚Đž", - "whats_new_settings_subtitle": "ВиĐļŅ‚Đĩ ĐēаĐēвО ĐŊОвО иĐŧа в Immich", - "whats_new_version": "ВĐĩŅ€ŅĐ¸Ņ {version}", - "when": "ĐšĐžĐŗĐ°Ņ‚Đž", "width": "Đ¨Đ¸Ņ€Đ¸ĐŊa", "wifi_name": "Wi-Fi ĐŧŅ€ĐĩĐļа", - "workflow": "Đ Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ", "workflow_delete_prompt": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да Đ¸ĐˇŅ‚Ņ€Đ¸ĐĩŅ‚Đĩ Ņ‚ĐžĐˇĐ¸ Ņ€Đ°ĐąĐžŅ‚ĐĩĐŊ ĐŋŅ€ĐžŅ†Đĩҁ?", "workflow_deleted": "Đ Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ Đĩ Đ¸ĐˇŅ‚Ņ€Đ¸Ņ‚", "workflow_description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ ĐŊа Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", @@ -2278,17 +2420,17 @@ "workflow_name": "ИĐŧĐĩ ĐŊа Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", "workflow_navigation_prompt": "ĐĐ°Đ¸ŅŅ‚Đ¸ĐŊа Đģи Đ¸ŅĐēĐ°Ņ‚Đĩ да иСĐģĐĩСĐĩŅ‚Đĩ ĐąĐĩС да ŅŅŠŅ…Ņ€Đ°ĐŊĐ¸Ņ‚Đĩ ĐŋŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ?", "workflow_summary": "ĐžĐąĐžĐąŅ‰ĐĩĐŊиĐĩ Са Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", - "workflow_templates": "ШайĐģĐžĐŊи ĐŊа Ņ€Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ ĐŋŅ€ĐžŅ†Đĩҁ", "workflow_update_success": "Đ Đ°ĐąĐžŅ‚ĐŊĐ¸ŅŅ‚ ĐŋŅ€ĐžŅ†Đĩҁ Đĩ ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊОвĐĩĐŊ", + "workflow_updated": "Đ Đ°ĐąĐžŅ‚ĐŊĐ¸ŅŅ‚ ĐŋŅ€ĐžŅ†Đĩҁ Đĩ ОйĐŊОвĐĩĐŊ", "workflows": "Đ Đ°ĐąĐžŅ‚ĐŊи ĐŋŅ€ĐžŅ†ĐĩŅĐ¸", "workflows_help_text": "Đ Đ°ĐąĐžŅ‚ĐŊĐ¸Ņ‚Đĩ ĐŋŅ€ĐžŅ†ĐĩŅĐ¸ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ¸Ņ€Đ°Ņ‚ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸ŅŅ‚Đ° ҁ Đ˛Đ°ŅˆĐ¸Ņ‚Đĩ ОйĐĩĐēŅ‚Đ¸ ҇ҀĐĩС Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Đ¸ и Ņ„Đ¸ĐģŅ‚Ņ€Đ¸", "wrong_pin_code": "Đ“Ņ€Đĩ҈ĐĩĐŊ PIN ĐēОд", - "x_of_total": "{x}/{total}", "year": "ГодиĐŊа", "years_ago": "ĐŋŅ€Đĩди {years, plural, one {# ĐŗĐžĐ´Đ¸ĐŊа} other {# ĐŗĐžĐ´Đ¸ĐŊи}}", "yes": "Да", "you_dont_have_any_shared_links": "ĐŅĐŧĐ°Ņ‚Đĩ ҁĐŋОдĐĩĐģĐĩĐŊи Đ˛Ņ€ŅŠĐˇĐēи", "your_wifi_name": "Đ’Đ°ŅˆĐ°Ņ‚Đ° Wi-Fi ĐŧŅ€ĐĩĐļа", "zero_to_clear_rating": "ĐŊĐ°Ņ‚Đ¸ŅĐŊĐĩŅ‚Đĩ 0, Са да ĐŋŅ€ĐĩĐŧĐ°Ņ…ĐŊĐĩŅ‚Đĩ Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗĐ°", - "zoom_image": "ĐŖĐ˛ĐĩĐģĐ¸Ņ‡Đ°Đ˛Đ°ĐŊĐĩ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž" + "zoom_image": "ĐŖĐ˛ĐĩĐģĐ¸Ņ‡Đ°Đ˛Đ°ĐŊĐĩ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩŅ‚Đž", + "zoom_to_bounds": "ĐŸŅ€Đ¸ĐąĐģиĐļи Đ´Đž ŅŅŠĐąĐ¸Ņ€Đ°ĐŊĐĩ в ĐŗŅ€Đ°ĐŊĐ¸Ņ†Đ¸Ņ‚Đĩ" } diff --git a/i18n/bi.json b/i18n/bi.json index c65a0c126b..290b816cc6 100644 --- a/i18n/bi.json +++ b/i18n/bi.json @@ -5,6 +5,7 @@ "acknowledge": "Akcept", "active": "Stap Mekem", "activity": "Wanem hemi Mekem", + "activity_changed": "WAnem hemi Mekem hemi", "add": "Ad", "add_a_description": "Putem Description blo hem", "add_a_location": "Putem place blo hem", @@ -12,9 +13,11 @@ "add_a_title": "Putem wan name blo hem", "add_exclusion_pattern": "Putem wan paten wae hemi karem aot", "add_location": "Putem wan place blo hem", + "add_more_users": "Putem mor man", "readonly_mode_enabled": "Mod blo yu no save janjem i on", "reassigned_assets_to_new_person": "Janjem{count, plural, one {# asset} other {# assets}} blo nu man", "reassing_hint": "janjem ol sumtin yu bin joos i go blo wan man", + "recent_albums": "album i no old tu mas", "recent_searches": "lukabout wea i no old tu mas", "time_based_memories_duration": "hao mus second blo wan wan imij i stap lo scrin.", "timezone": "taemzon", @@ -30,6 +33,7 @@ "trash_action_prompt": "{count} igo lo plaes lo toti", "trash_all": "Putem ol i go lo toti", "trash_count": "Toti {count, number}", + "trash_emptied": "basket blo toti i empti nomo", "trash_no_results_message": "Foto mo video lo basket blo toti yu save lukem lo plaes ia.", "trash_page_delete_all": "Delete oli ol" } diff --git a/i18n/bn.json b/i18n/bn.json index 64fcee0b13..dcc6834323 100644 --- a/i18n/bn.json +++ b/i18n/bn.json @@ -8,33 +8,41 @@ "action_description": "āĻŦāĻžāĻ›āĻžāχāĻ•ā§ƒāϤ āϏāĻŽā§āĻĒāĻĻāϏāĻŽā§‚āĻšā§‡āϰ āωāĻĒāϰ āϏāĻŽā§āĻĒāĻžāĻĻāύāϝ⧋āĻ—ā§āϝ āĻ•āĻžāĻœā§‡āϰ āϤāĻžāϞāĻŋāĻ•āĻž", "actions": "āĻ•āĻ°ā§āĻŽ", "active": "āϏāϚāϞ", - "active_count": "āϏāϚāϞ: {count}", + "active_count": "Active: {count}", "activity": "āĻ•āĻžāĻ°ā§āϝāĻ•āϞāĻžāĻĒ", + "activity_changed": "āĻāĻ•āϟāĻŋāĻ­āĻŋāϟāĻŋ āĻāĻ–āύ {enabled, select, true {enabled} other {disabled}} āφāϛ⧇", "add": "āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_a_description": "āĻāĻ•āϟāĻŋ āĻŦāĻŋāĻŦāϰāĻŖ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_a_location": "āĻāĻ•āϟāĻŋ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_a_name": "āĻāĻ•āϟāĻŋ āύāĻžāĻŽ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_a_title": "āĻāĻ•āϟāĻŋ āĻļāĻŋāϰ⧋āύāĻžāĻŽ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_action": "āĻ•āĻ°ā§āĻŽ āϝ⧋āĻ— āĻ•āϰ⧁āύ", + "add_action_description": "āϏāĻŽā§āĻĒāĻžāĻĻāύ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻ•āĻžāϜ āϝ⧋āĻ— āĻ•āϰāϤ⧇ āĻ•ā§āϞāĻŋāĻ• āĻ•āϰ⧁āύ", "add_assets": "āϏāĻŽā§āĻĒāĻĻ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_birthday": "āϜāĻ¨ā§āĻŽāĻĻāĻŋāύ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_endpoint": "āĻāĻ¨ā§āĻĄāĻĒāϝāĻŧ⧇āĻ¨ā§āϟ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_exclusion_pattern": "āĻŦāĻšāĻŋāĻ°ā§āĻ­ā§‚āϤāĻ•āϰāĻŖ āύāĻŽā§āύāĻž", + "add_filter": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āϝ⧋āĻ— āĻ•āϰ⧁āύ", + "add_filter_description": "āĻāĻ•āϟāĻŋ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻļāĻ°ā§āϤ āϝ⧋āĻ— āĻ•āϰāϤ⧇ āĻ•ā§āϞāĻŋāĻ• āĻ•āϰ⧁āύ", "add_location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", + "add_more_users": "āφāϰ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", "add_partner": "āĻ…āĻ‚āĻļā§€āĻĻāĻžāϰ āϝ⧋āĻ— āĻ•āϰ⧁āύ", + "add_path": "āĻĒāĻžāĻĨ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", "add_photos": "āĻ›āĻŦāĻŋ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "add_step": "āϧāĻžāĻĒ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_tag": "āĻŸā§āϝāĻžāĻ— āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", "add_to": "āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύâ€Ļ", "add_to_album": "āĻāϞāĻŦāĻžāĻŽ āĻ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_to_album_bottom_sheet_added": "{album} āĻ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "add_to_album_bottom_sheet_already_exists": "{album} āĻ āφāϗ⧇ āĻĨ⧇āϕ⧇āχ āφāϛ⧇", "add_to_album_bottom_sheet_some_local_assets": "āĻ•āĻŋāϛ⧁ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ›āĻŦāĻŋ āĻŦāĻž āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", + "add_to_album_toggle": "{album} - āĻāϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", "add_to_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_to_albums_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰ⧁āύ ({count})", "add_to_bottom_bar": "āĻ āϝ⧋āĻ— āĻ•āϰ⧁āύ", + "add_to_shared_album": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_upload_to_stack": "āφāĻĒāϞ⧋āĻĄ āĻ¸ā§āĻŸā§āϝāĻžāϕ⧇ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "add_url": "āϞāĻŋāĻ™ā§āĻ• āϝ⧋āĻ— āĻ•āϰ⧁āύ", + "add_workflow_step": "āĻ•āĻžāĻœā§‡āϰ āϧāĻžāĻĒ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "added_to_archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "added_to_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāĻŸā§‡ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "added_to_favorites_count": "āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ āϤāĻžāϞāĻŋāĻ•āĻžā§Ÿ {count, number} āϝ⧋āĻ— āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇", @@ -56,7 +64,7 @@ "backup_onboarding_description": "āφāĻĒāύāĻžāϰ āĻĄā§‡āϟāĻž āϏ⧁āϰāĻ•ā§āώāĻŋāϤ āϰāĻžāĻ–āĻžāϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ 3-2-1 āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•ā§ŒāĻļāϞ āϏ⧁āĻĒāĻžāϰāĻŋāĻļ āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ āĻāĻ•āϟāĻŋ āĻŦāĻŋāĻ¸ā§āϤ⧃āϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻŽāĻžāϧāĻžāύ⧇āϰ āϜāĻ¨ā§āϝ āφāĻĒāύāĻžāϰ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻĢāĻŸā§‹/āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞāĻŋāϰ āĻ•āĻĒāĻŋ āĻāĻŦāĻ‚ Immich āĻĄāĻžāϟāĻžāĻŦ⧇āϏ āϰāĻžāĻ–āĻž āωāϚāĻŋāϤāĨ¤", "backup_onboarding_footer": "Immich āĻāϰ āĻŦā§āϝāĻžāĻ•āφāĻĒ āύ⧇āĻ“ā§ŸāĻžāϰ āĻŦāĻŋāĻˇā§Ÿā§‡ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ, āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ āĻĻ⧇āϖ⧁āύāĨ¤", "backup_onboarding_parts_title": "ā§Š-⧍-ā§§ āĻŦā§āϝāĻžāĻ•āφāĻĒ⧇āϰ āĻŽāĻ§ā§āϝ⧇ āϰāϝāĻŧ⧇āϛ⧇:", - "backup_onboarding_title": "āĻŦā§āϝāĻžāĻ•āφāĻĒāϏāĻŽā§‚āĻš", + "backup_onboarding_title": "āĻŦā§āϝāĻžāĻ•āφāĻĒ", "backup_settings": "āĻĄāĻžāϟāĻžāĻŦ⧇āϏ āĻĄāĻžāĻŽā§āĻĒ āϏ⧇āϟāĻŋāĻ‚āϏ", "backup_settings_description": "āĻĄāĻžāϟāĻžāĻŦ⧇āϏ āĻĄāĻžāĻŽā§āĻĒ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύāĨ¤", "cleared_jobs": "{job} āĻāϰ āϜāĻ¨ā§āϝ jobs āĻ–āĻžāϞāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", @@ -73,7 +81,6 @@ "cron_expression_description": "Cron āĻĢāϰāĻŽā§āϝāĻžāϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ¸ā§āĻ•ā§āϝāĻžāύāĻŋāĻ‚ āχāĻ¨ā§āϟāĻžāϰāĻ­ā§āϝāĻžāϞ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύāĨ¤ āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ āĻĻ⧟āĻž āĻ•āϰ⧇ Crontab Guru āĻĻ⧇āϖ⧁āύ", "cron_expression_presets": "Cron āĻāĻ•ā§āϏāĻĒā§āϰ⧇āĻļāύ āĻĒā§āϰāĻŋāϏ⧇āϟ", "disable_login": "āϞāĻ—āχāύ āĻ…āĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", - "download_csv": "CSV āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ", "duplicate_detection_job_description": "āϏāĻĻ⧃āĻļ āĻ›āĻŦāĻŋ āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϰ āωāĻĒāϰ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϚāĻžāϞāĻžāύāĨ¤ āĻāϟāĻŋ Smart Search-āĻāϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇", "exclusion_pattern_description": "āĻāĻ•ā§āϏāĻ•ā§āϞ⧁āĻļāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻĢāĻžāχāϞ āĻ“ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤ āĻāϟāĻŋ āϤāĻ–āύāχ āωāĻĒāĻ•āĻžāϰ⧀ āϝāĻ–āύ āĻ•āĻŋāϛ⧁ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āĻāĻŽāύ āĻĢāĻžāχāϞ āĻĨāĻžāϕ⧇ āϝāĻž āφāĻĒāύāĻŋ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰāϤ⧇ āϚāĻžāύ āύāĻž, āϝ⧇āĻŽāύ RAW āĻĢāĻžāχāϞāĨ¤", "export_config_as_json_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύāϟāĻŋāϕ⧇ āĻāĻ•āϟāĻŋ JSON āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ", @@ -98,33 +105,18 @@ "image_preview_quality_description": "ā§§-ā§§ā§Ļā§Ļ āĻāϰ āĻŽāĻ§ā§āϝ⧇ āĻĒā§āϰāĻŋāĻ­āĻŋāω āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āĻ•āĻŽ āĻŽāĻžāύ āϏ⧇āϟ āĻ•āϰāϞ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋāϰ āωāĻĒāϰ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "image_preview_title": "āĻĒā§āϰāĻŋāĻ­āĻŋāω āϏ⧇āϟāĻŋāĻ‚āϏ", "image_progressive": "āĻĒā§āϰāĻ—ā§āϰ⧇āϏāĻŋāĻ­", - "image_progressive_description": "JPEG āĻ›āĻŦāĻŋ āĻ•ā§āϰāĻŽāĻžāĻ¨ā§āĻŦāϝāĻŧ⧇ āϞ⧋āĻĄ āĻšāϝāĻŧ⧇ āĻĒā§āϰāĻĻāĻ°ā§āĻļāĻŋāϤ āĻšāĻ“āϝāĻŧāĻžāϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻ—ā§āϰ⧇āϏāĻŋāĻ­āĻ­āĻžāĻŦ⧇ āĻāύāϕ⧋āĻĄ āĻ•āϰ⧁āύāĨ¤ WebP āĻ›āĻŦāĻŋāϰ āωāĻĒāϰ āĻāϰ āϕ⧋āύ⧋ āĻĒā§āϰāĻ­āĻžāĻŦ āύ⧇āχāĨ¤", + "image_progressive_description": "āϧ⧀āϰ⧇ āϧ⧀āϰ⧇ āϞ⧋āĻĄ āĻšāĻ“ā§ŸāĻžāϰ āϏ⧁āĻŦāĻŋāϧāĻžāĻ°ā§āĻĨ⧇ JPEG āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻĒā§āϰāĻ—ā§āϰ⧇āϏāĻŋāĻ­āĻ­āĻžāĻŦ⧇ āĻāύāϕ⧋āĻĄ āĻ•āϰ⧁āύāĨ¤ WebP āĻ›āĻŦāĻŋāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻāϟāĻŋ āϕ⧋āύ⧋ āĻĒā§āϰāĻ­āĻžāĻŦ āĻĢ⧇āϞāĻŦ⧇ āύāĻž", "image_quality": "āϗ⧁āĻŖāĻŽāĻžāύ", - "image_resolution": "āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύāϏāĻŽā§‚āĻš", + "image_resolution": "āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ", "image_resolution_description": "āωāĻšā§āϚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ⧇āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āφāϰāĻ“ āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϤāĻĨā§āϝ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻāύāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦ⧇āĻļāĻŋ āϏāĻŽāϝāĻŧ āϞāĻžāϗ⧇, āĻĢāĻžāχāϞ⧇āϰ āφāĻ•āĻžāϰ āĻŦāĻĄāĻŧ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", - "image_settings": "āĻ›āĻŦāĻŋāϰ āϏ⧇āϟāĻŋāĻ‚āϏ", + "image_settings": "āϚāĻŋāĻ¤ā§āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", "image_settings_description": "āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻ›āĻŦāĻŋāϰ āĻŽāĻžāύ āĻāĻŦāĻ‚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "image_thumbnail_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻŦāĻžāĻĻ āĻĻ⧇āĻ“ā§ŸāĻž āϛ⧋āϟ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ, āĻŽā§‚āϞ āϟāĻžāχāĻŽāϞāĻžāχāύ⧇āϰ āĻŽāϤ⧋ āĻ›āĻŦāĻŋāϰ āĻ—ā§āϰ⧁āĻĒ āĻĻ⧇āĻ–āĻžāϰ āϏāĻŽāϝāĻŧ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšā§Ÿ", "image_thumbnail_quality_description": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ⧇āϰ āĻŽāĻžāύ ā§§-ā§§ā§Ļā§ĻāĨ¤ āĻŦ⧇āĻļāĻŋ āĻšāϞ⧇ āĻ­āĻžāϞ⧋, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ āĻāĻŦāĻ‚ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒā§āϰāϤāĻŋāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻļā§€āϞāϤāĻž āĻ•āĻŽāĻžāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "image_thumbnail_title": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āϞ āϏ⧇āϟāĻŋāĻ‚āϏ", - "import_config_from_json_description": "āĻāĻ•āϟāĻŋ JSON āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύ", - "integrity_checks_checksum_files": "āĻĢāĻžāχāϞ⧇āϰ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ", - "integrity_checks_checksum_files_description": "āĻĢāĻžāχāϞ⧇āϰ āϏāϤāϤāĻž āϝāĻžāϚāĻžāχāĻ•āϰāĻŖ āĻĒāĻĻā§āϧāϤāĻŋ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "integrity_checks_checksum_files_enable_description": "āĻĢāĻžāχāϞ⧇āϰ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "integrity_checks_checksum_files_percentage_limit": "āĻļāϤāĻžāĻ‚āĻļ āϏ⧀āĻŽāĻž", - "integrity_checks_checksum_files_percentage_limit_description": "ā§Ļ.ā§Ļā§§ āĻĨ⧇āϕ⧇ ā§§-āĻāϰ āĻŽāĻ§ā§āϝ⧇ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻļāϤāĻžāĻ‚āĻļ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ, āϝāĻž āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻŦāϧāĻžāύ⧇ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻ•āϤāϟ⧁āϕ⧁ āϚāϞāĻŦ⧇ āϤāĻž āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧇āĨ¤", - "integrity_checks_checksum_files_time_limit": "āϏāĻŽāϝāĻŧāϏ⧀āĻŽāĻž", - "integrity_checks_checksum_files_time_limit_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻŦāϧāĻžāύ⧇ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āĻ•āϤāĻ•ā§āώāĻŖ āϚāϞāĻŦ⧇, āϤāĻžāϰ āĻ¸ā§āĻĨāĻŋāϤāĻŋāĻ•āĻžāϞ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύāĨ¤ (āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡)", - "integrity_checks_missing_files": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ", - "integrity_checks_missing_files_description": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ āϝāĻžāϚāĻžāχāϝāĻŧ⧇āϰ āϏāĻŽāϝāĻŧāĻŦā§āϝāĻŦāϧāĻžāύ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ āϤāĻž āϚāĻžāϞ⧁ āĻŦāĻž āĻŦāĻ¨ā§āϧ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "integrity_checks_missing_files_enable_description": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ āϝāĻžāϚāĻžāχ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "integrity_checks_settings": "āϏāϤāϤāĻž āϝāĻžāϚāĻžāχ", - "integrity_checks_settings_description": "āϏāϤāϤāĻž āϝāĻžāϚāĻžāχāϝāĻŧ⧇āϰ āϏāĻŽāϝāĻŧāĻŦā§āϝāĻŦāϧāĻžāύ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "integrity_checks_untracked_files": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ", - "integrity_checks_untracked_files_description": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ āϝāĻžāϚāĻžāχāϝāĻŧ⧇āϰ āϏāĻŽāϝāĻŧāĻŦā§āϝāĻŦāϧāĻžāύ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ āϤāĻž āϚāĻžāϞ⧁ āĻŦāĻž āĻŦāĻ¨ā§āϧ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "integrity_checks_untracked_files_enable_description": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ āϝāĻžāϚāĻžāχ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "job_concurrency": "{job} āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞ āϏāĻ‚āĻ–ā§āϝāĻž", - "job_created": "āĻ•āĻžāϜ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", + "import_config_from_json_description": "āĻāĻ•āϟāĻŋ JSON āĻ•āύāĻĢāĻŋāĻ— āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤", + "job_concurrency": "{job} āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ", + "job_created": "Job āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", "job_not_concurrency_safe": "āĻāχ āĻ•āĻžāϜāϟāĻŋ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ⧋ āύāĻŋāϰāĻžāĻĒāĻĻ āύ⧟āĨ¤", "job_settings": "āĻ•āĻžāĻœā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", "job_settings_description": "āĻ•āĻžāĻœā§‡āϰ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞāϤāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", @@ -143,7 +135,7 @@ "library_settings": "āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ", "library_settings_description": "āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "library_tasks_description": "āύāϤ⧁āύ āĻāĻŦāĻ‚/āĻ…āĻĨāĻŦāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", - "library_updated": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", + "library_updated": "āφāĻĒāĻĄā§‡āϟāĻ•ā§ƒāϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāĨ¤", "library_watching_enable_description": "āĻĢāĻžāχāϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻšāĻŋāϰāĻžāĻ—āϤ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϗ⧁āϞāĻŋ āĻĻ⧇āϖ⧁āύ", "library_watching_settings": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒāĻ°ā§āϝāĻŦ⧇āĻ•ā§āώāĻŖ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", "library_watching_settings_description": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āĻĢāĻžāχāϞāϗ⧁āϞāĻŋāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāϜāϰ āϰāĻžāϖ⧁āύ", @@ -156,17 +148,17 @@ "machine_learning_availability_checks_interval": "āĻšā§‡āĻ• āĻŦā§āϝāĻŦāϧāĻžāύ", "machine_learning_availability_checks_interval_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āĻĒāϰ⧀āĻ•ā§āώāĻžāϗ⧁āϞāĻŋāϰ āĻŽāĻ§ā§āϝ⧇ āĻŦā§āϝāĻŦāϧāĻžāύ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡", "machine_learning_availability_checks_timeout": "āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āϏāĻŽā§ŸāϏ⧀āĻŽāĻž āĻļ⧇āώ", - "machine_learning_availability_checks_timeout_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻž āϝāĻžāϚāĻžāχāϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϟāĻžāχāĻŽāφāωāϟ", + "machine_learning_availability_checks_timeout_description": "āĻĒā§āϰāĻžāĻĒā§āϝāϤāĻžāϰ āĻĒāϰ⧀āĻ•ā§āώāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞāĻŋāϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āϏāĻŽā§ŸāϏ⧀āĻŽāĻžāĨ¤", "machine_learning_clip_model": "CLIP āĻŽāĻĄā§‡āϞ", - "machine_learning_clip_model_description": "CLIP āĻŽāĻĄā§‡āϞ⧇āϰ āύāĻžāĻŽ āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āφāϛ⧇ āĻāĻ–āĻžāύ⧇āĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ, āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϞ⧇ āϏāĻŦ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ 'Smart Search' āĻ•āĻžāϜāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤", + "machine_learning_clip_model_description": "āĻāĻ–āĻžāύ⧇ āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻāĻ•āϟāĻŋ CLIP āĻŽāĻĄā§‡āϞ⧇āϰ āύāĻžāĻŽāĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ, āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϞ⧇ āϏāĻŦ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ 'Smart Search' āϜāĻŦāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤", "machine_learning_duplicate_detection": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āϏāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ", "machine_learning_duplicate_detection_enabled": "āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", "machine_learning_duplicate_detection_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋ⧟ āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻšā§āĻŦāĻšā§ āĻāĻ•āχ āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋āϰ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāϰāĻŋā§Ÿā§‡ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤", - "machine_learning_duplicate_detection_setting_description": "āϏāĻŽā§āĻ­āĻžāĻŦā§āϝ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϖ⧁āρāĻœā§‡ āĻŦ⧇āϰ āĻ•āϰāϤ⧇ CLIP āĻāĻŽā§āĻŦ⧇āĻĄāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", + "machine_learning_duplicate_detection_setting_description": "āϏāĻŽā§āĻ­āĻžāĻŦā§āϝ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϖ⧁āρāĻœā§‡ āĻŦ⧇āϰ āĻ•āϰāϤ⧇ CLIP āĻāĻŽā§āĻŦ⧇āĻĄāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤", "machine_learning_enabled": "Machine Learning āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "machine_learning_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇ āύāĻŋāĻšā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āύāĻŋāĻ°ā§āĻŦāĻŋāĻļ⧇āώ⧇ āϏāĻŽāĻ¸ā§āϤ ML āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "machine_learning_facial_recognition": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ", - "machine_learning_facial_recognition_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻĨāĻžāĻ•āĻž āĻŽā§āĻ– āĻļāύāĻžāĻ•ā§āϤ, āϚāĻŋāĻšā§āύāĻŋāϤ āĻāĻŦāĻ‚ āĻĻāϞāĻŦāĻĻā§āϧ āĻ•āϰ⧁āύ", + "machine_learning_facial_recognition_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻŽā§āĻ– āϏāύāĻžāĻ•ā§āϤ āĻ•āϰ⧁āύ, āϚāĻŋāύ⧁āύ āĻāĻŦāĻ‚ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύāĨ¤", "machine_learning_facial_recognition_model": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āĻŽāĻĄā§‡āϞ", "machine_learning_facial_recognition_model_description": "āĻŽāĻĄā§‡āϞāϗ⧁āϞāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻ…āϧāσāĻ•ā§āϰāĻŽ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āϤāĻžāϞāĻŋāĻ•āĻžāϭ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āĻŦ⧜ āĻŽāĻĄā§‡āϞāϗ⧁āϞāĻŋ āϧ⧀āϰāĻ—āϤāĻŋāϰ āĻāĻŦāĻ‚ āĻŦ⧇āĻļāĻŋ āĻŽā§‡āĻŽāϰāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇, āϤāĻŦ⧇ āωāĻ¨ā§āύāϤ āĻĢāϞāĻžāĻĢāϞ āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰ⧇āĨ¤ āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ āϝ⧇ āĻāĻ•āϟāĻŋ āĻŽāĻĄā§‡āϞ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻžāϰ āĻĒāϰ āφāĻĒāύāĻžāϕ⧇ āϏāĻŽāĻ¸ā§āϤ āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ āĻĢ⧇āϏ āĻĄāĻŋāĻŸā§‡āĻ•āĻļāύ (Face Detection) āĻ•āĻžāϜāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞāĻžāϤ⧇ āĻšāĻŦ⧇āĨ¤", "machine_learning_facial_recognition_setting": "āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", @@ -180,7 +172,7 @@ "machine_learning_min_recognized_faces": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻ¸ā§āĻŦā§€āĻ•ā§ƒāϤ āĻŽā§āϖ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž", "machine_learning_min_recognized_faces_description": "āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϤ⧈āϰāĻŋ āĻšāĻ“āϝāĻŧāĻžāϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦā§€āĻ•ā§ƒāϤ āĻŽā§āϖ⧇āϰ āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āϏāĻ‚āĻ–ā§āϝāĻžāĨ¤ āĻāϟāĻŋ āĻŦāĻžāĻĄāĻŧāĻžāϞ⧇ āĻĢ⧇āϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āφāϰāĻ“ āύāĻŋāϖ⧁āρāϤ āĻšāϝāĻŧ, āϤāĻŦ⧇ āĻāϤ⧇ āϕ⧋āύ⧋ āĻŽā§āĻ– āϕ⧋āύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āύāĻž āĻšāĻ“āϝāĻŧāĻžāϰ āϏāĻŽā§āĻ­āĻžāĻŦāύāĻžāĻ“ āĻŦ⧃āĻĻā§āϧāĻŋ āĻĒāĻžāϝāĻŧāĨ¤", "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻĨāĻžāĻ•āĻž āϞ⧇āĻ–āĻž āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", + "machine_learning_ocr_description": "āĻ›āĻŦāĻŋāϤ⧇ āĻŸā§‡āĻ•ā§āϏāϟ (Text) āĻļāύāĻžāĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤", "machine_learning_ocr_enabled": "OCR āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "machine_learning_ocr_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇, āĻ›āĻŦāĻŋāϗ⧁āϞ⧋āϤ⧇ āĻŸā§‡āĻ•ā§āϏāϟ āĻļāύāĻžāĻ•ā§āϤāĻ•āϰāĻŖ āĻ•āϰāĻž āĻšāĻŦ⧇ āύāĻžāĨ¤", "machine_learning_ocr_max_resolution": "āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ(Resolution)", @@ -197,32 +189,16 @@ "machine_learning_smart_search_description": "CLIP āĻāĻŽāĻŦ⧇āĻĄāĻŋāĻ‚ (embeddings) āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻ›āĻŦāĻŋāϰ āĻŦāĻŋāώ⧟āĻŦāĻ¸ā§āϤ⧁ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", "machine_learning_smart_search_enabled": "āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āϚ āϏāĻ•ā§āώāĻŽ āĻ•āϰ⧁āύ", "machine_learning_smart_search_enabled_description": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇, āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āϏāĻžāĻ°ā§āĻšā§‡āϰ āϜāĻ¨ā§āϝ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻāύāϕ⧋āĻĄ (encode) āĻ•āϰāĻž āĻšāĻŦ⧇ āύāĻžāĨ¤", - "machine_learning_url_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ URLāĨ¤ āĻāĻ•āĻžāϧāĻŋāĻ• URL āĻĻ⧇āĻ“āϝāĻŧāĻž āĻĨāĻžāĻ•āϞ⧇, āĻĒā§āϰāĻĨāĻŽ āĻĨ⧇āϕ⧇ āĻļ⧇āώ āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇ āĻāĻ•āϟāĻŋ āĻ•āϰ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāĻŦ⧇, āϝāϤāĻ•ā§āώāĻŖ āύāĻž āϕ⧋āύ⧋āϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻžāĻĄāĻŧāĻž āĻĻ⧇āϝāĻŧāĨ¤ āϝ⧇āϏāĻŦ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϏāĻžāĻĄāĻŧāĻž āĻĻ⧇āϝāĻŧ āύāĻž, āϏ⧇āϗ⧁āϞ⧋āϕ⧇ āϏāĻžāĻŽāϝāĻŧāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇, āϝāϤāĻ•ā§āώāĻŖ āύāĻž āϤāĻžāϰāĻž āφāĻŦāĻžāϰ āϏāϚāϞ āĻšāϝāĻŧāĨ¤", - "maintenance_backup_management": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻžāĻĒāύāĻž", + "machine_learning_url_description": "āĻŽā§‡āĻļāĻŋāύ āϞāĻžāĻ°ā§āύāĻŋāĻ‚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ URLāĨ¤ āϝāĻĻāĻŋ āĻāϕ⧇āϰ āĻŦ⧇āĻļāĻŋ URL āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰāĻž āĻšā§Ÿ, āϤāĻŦ⧇ āĻāĻ•āϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻžā§œāĻž āύāĻž āĻĻ⧇āĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻāĻ• āĻāĻ• āĻ•āϰ⧇ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāĻŦ⧇ (āĻĒā§āϰāĻĨāĻŽ āĻĨ⧇āϕ⧇ āĻļ⧇āώ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇)āĨ¤ āϝ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰāϗ⧁āϞ⧋ āϏāĻžā§œāĻž āĻĻ⧇āĻŦ⧇ āύāĻž, āϏ⧇āϗ⧁āϞ⧋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāϚāϞ āĻšāĻ“ā§ŸāĻž āĻĒāĻ°ā§āϝāĻ¨ā§āϤ āϏāĻžāĻŽā§ŸāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "maintenance_delete_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ (Backup)āĻŽā§āϛ⧁āύ", "maintenance_delete_backup_description": "āĻāχ āĻĢāĻžāχāϞāϟāĻŋ āϚāĻŋāϰāϤāϰ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤", "maintenance_delete_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšā§Ÿā§‡āϛ⧇āĨ¤", - "maintenance_integrity_check": "āϝāĻžāϚāĻžāχ", - "maintenance_integrity_check_all": "āϏāĻŦ āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύ", - "maintenance_integrity_checksum_mismatch": "āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻ…āĻŽāĻŋāϞ", - "maintenance_integrity_checksum_mismatch_description": "āĻĄāĻŋāĻ¸ā§āϕ⧇ āĻĨāĻžāĻ•āĻž āĻĢāĻžāχāϞ⧇āϰ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻŽāĻžāύ, āχāĻŽāĻŋāĻšā§‡āϰ āĻĄā§‡āϟāĻžāĻŦ⧇āϏ⧇ āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻŽāĻžāύ⧇āϰ āϏāĻžāĻĨ⧇ āĻŽāĻŋāϞāϛ⧇ āύāĻžāĨ¤", - "maintenance_integrity_checksum_mismatch_job": "āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻ…āĻŽāĻŋāϞ āϖ⧁āρāϜ⧁āύ", - "maintenance_integrity_checksum_mismatch_refresh_job": "āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ āĻ…āĻŽāĻŋāϞ⧇āϰ āĻĒā§āϰāϤāĻŋāĻŦ⧇āĻĻāύ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύ", - "maintenance_integrity_missing_file": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ", - "maintenance_integrity_missing_file_description": "āϝ⧇āϏāĻŦ āĻĢāĻžāχāϞ āχāĻŽāĻŋāϚ āĻĄā§‡āϟāĻžāĻŦ⧇āϏ⧇ āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āĻ•āϰ⧇āϛ⧇, āĻ•āĻŋāĻ¨ā§āϤ⧁ āĻĢāĻžāχāϞ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āύ⧇āχāĨ¤", - "maintenance_integrity_missing_file_job": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ āϖ⧁āρāϜ⧁āύ", - "maintenance_integrity_missing_file_refresh_job": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĢāĻžāχāϞ⧇āϰ āĻĒā§āϰāϤāĻŋāĻŦ⧇āĻĻāύ āύāϤ⧁āύ āĻ•āϰ⧇ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "maintenance_integrity_report": "āϏāϤāϤāĻž āĻĒā§āϰāϤāĻŋāĻŦ⧇āĻĻāύ", - "maintenance_integrity_untracked_file": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ", - "maintenance_integrity_untracked_file_description": "āχāĻŽāĻŋāĻšā§‡āϰ āĻĄāĻŋāϰ⧇āĻ•ā§āϟāϰāĻŋāϤ⧇ āĻĨāĻžāĻ•āĻž āĻāĻŽāύ āĻĢāĻžāχāϞ, āϝ⧇āϗ⧁āϞ⧋āϰ āϕ⧋āύ⧋ āϤāĻĨā§āϝ āχāĻŽāĻŋāĻšā§‡āϰ āĻ•āĻžāϛ⧇ āύ⧇āχāĨ¤", - "maintenance_integrity_untracked_file_job": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ āϖ⧁āρāϜ⧁āύ", - "maintenance_integrity_untracked_file_refresh_job": "āύāĻĨāĻŋāϭ⧁āĻ•ā§āϤ āύāĻž āĻ•āϰāĻž āĻĢāĻžāχāϞ⧇āϰ āĻĒā§āϰāϤāĻŋāĻŦ⧇āĻĻāύ āύāϤ⧁āύ āĻ•āϰ⧇ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "maintenance_restore_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ(Restore) āĻ•āϰ⧁āύ", "maintenance_restore_backup_description": "Immich āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āĻ•āĻžāĻ°ā§āϝāĻ•ā§āϰāĻŽ āϚāĻžāϞāĻŋā§Ÿā§‡ āϝāĻžāĻ“ā§ŸāĻžāϰ āφāϗ⧇ āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤", "maintenance_restore_backup_different_version": "āĻāχ āĻŦā§āϝāĻžāĻ•āφāĻĒāϟāĻŋ Immich-āĻāϰ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻ¨ā§āύ āϏāĻ‚āĻ¸ā§āĻ•āϰāϪ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšā§Ÿā§‡āĻ›āĻŋāϞ!", "maintenance_restore_backup_unknown_version": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻ‚āĻ¸ā§āĻ•āϰāĻŖ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āĻšāϝāĻŧāύāĻŋāĨ¤", "maintenance_restore_database_backup": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "maintenance_restore_database_backup_description": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĢāĻžāχāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĄā§‡āϟāĻžāĻŦ⧇āϏāϕ⧇ āφāϗ⧇āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻŋāϝāĻŧ⧇ āύāĻŋāύ", + "maintenance_restore_database_backup_description": "āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĢāĻžāχāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĄā§‡āϟāĻžāĻŦ⧇āϏāϕ⧇ āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻŋā§Ÿā§‡ āφāύ⧁āύāĨ¤", "maintenance_settings": "āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ (Maintenance)", "maintenance_settings_description": "Immich-āϕ⧇ āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ āĻŽā§‹āĻĄā§‡ (maintenance mode) āϰāĻžāϖ⧁āύāĨ¤", "maintenance_start": "āϰāĻ•ā§āώāĻŖāĻžāĻŦ⧇āĻ•ā§āώāĻŖ āĻŽā§‹āĻĄā§‡ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", @@ -230,15 +206,15 @@ "maintenance_upload_backup": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύ", "maintenance_upload_backup_error": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžā§ŸāύāĻŋ, āĻāϟāĻŋ āĻ•āĻŋ āϕ⧋āύ⧋ .sql/.sql.gz āĻĢāĻžāχāϞ?", "manage_concurrency": "āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage Concurrency)", - "manage_concurrency_description": "āĻ•āĻžāĻœā§‡āϰ āϏāĻŽāĻžāĻ¨ā§āϤāϰāĻžāϞ āϏāĻ‚āĻ–ā§āϝāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰāϤ⧇ āϜāĻŦ āĻĒ⧇āĻœā§‡ āϝāĻžāύ", + "manage_concurrency_description": "āϜāĻŦ āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰāϤ⧇ 'āϜāĻŦāϏ' (Jobs) āĻĒāĻžāϤāĻžāϝāĻŧ āϝāĻžāύāĨ¤", "manage_log_settings": "āϞāĻ— āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "map_dark_style": "āĻĄāĻžāĻ°ā§āĻ• āĻ¸ā§āϟāĻžāχāϞ (Dark style)", "map_enable_description": "āĻŽā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰāϗ⧁āϞ⧋ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable map features)", "map_gps_settings": "āĻŽā§āϝāĻžāĻĒ āĻāĻŦāĻ‚ āϜāĻŋāĻĒāĻŋāĻāϏ āϏ⧇āϟāĻŋāĻ‚āϏ (Map & GPS Settings)", "map_gps_settings_description": "āĻŽā§āϝāĻžāĻĒ āĻāĻŦāĻ‚ āϜāĻŋāĻĒāĻŋāĻāϏ (āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚) āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage Map & GPS (Reverse Geocoding) Settings)", - "map_implications": "āĻŽā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻāĻ•āϟāĻŋ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϟāĻžāχāϞ āϏāĻžāĻ°ā§āĻ­āĻŋāϏ⧇āϰ (tiles.immich.cloud) āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇", + "map_implications": "āĻŽā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻāĻ•āϟāĻŋ āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āϟāĻžāχāϞ āϏāĻžāĻ°ā§āĻ­āĻŋāϏ⧇āϰ (tiles.immich.cloud) āĻ“āĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇āĨ¤", "map_light_style": "āϞāĻžāχāϟ āĻ¸ā§āϟāĻžāχāϞ (Light style)", - "map_manage_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏāĻ‚āĻ•ā§āϰāĻžāĻ¨ā§āϤ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", + "map_manage_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "map_reverse_geocoding": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ (Reverse Geocoding)", "map_reverse_geocoding_enable_description": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable reverse geocoding)", "map_reverse_geocoding_settings": "āϰāĻŋāĻ­āĻžāĻ°ā§āϏ āϜāĻŋāĻ“āϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ (Reverse Geocoding Settings)", @@ -248,9 +224,9 @@ "memory_cleanup_job": "āĻŽā§‡āĻŽāϰāĻŋ āĻ•ā§āϞāĻŋāύāφāĻĒ (Memory cleanup)", "memory_generate_job": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž(Memory generation)", "metadata_extraction_job": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ (Extract metadata)", - "metadata_extraction_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻŽā§āĻĒāĻĻ āĻĨ⧇āϕ⧇ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ, āĻšā§‡āĻšāĻžāϰāĻž āĻāĻŦāĻ‚ āĻŦāĻŋāϭ⧇āĻĻāύ āĻ•ā§āώāĻŽāϤāĻžāϰ āĻŽāϤ⧋ āĻ…āϧāĻŋ-āωāĻĒāĻžāĻ¤ā§āϤ āϤāĻĨā§āϝ āĻŦ⧇āϰ āĻ•āϰ⧁āύ", + "metadata_extraction_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ (Asset) āĻĨ⧇āϕ⧇ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϤāĻĨā§āϝ āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰ⧁āύ, āϝ⧇āĻŽāύ: āϜāĻŋāĻĒāĻŋāĻāϏ (GPS), āĻšā§‡āĻšāĻžāϰāĻž (faces) āĻāĻŦāĻ‚ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ (resolution)āĨ¤", "metadata_faces_import_setting": "āĻĢ⧇āϏ āχāĻŽā§āĻĒā§‹āĻ°ā§āϟ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύ (Enable face import)", - "metadata_faces_import_setting_description": "āĻ›āĻŦāĻŋāϰ EXIF āϤāĻĨā§āϝ āĻāĻŦāĻ‚ āϏāĻžāχāĻĄāĻ•āĻžāϰ āĻĢāĻžāχāϞ āĻĨ⧇āϕ⧇ āĻšā§‡āĻšāĻžāϰāĻž āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰ⧁āύ", + "metadata_faces_import_setting_description": "āĻ›āĻŦāĻŋāϰ EXIF āĻĄā§‡āϟāĻž āĻāĻŦāĻ‚ āϏāĻžāχāĻĄāĻ•āĻžāϰ (sidecar) āĻĢāĻžāχāϞ āĻĨ⧇āϕ⧇ āĻšā§‡āĻšāĻžāϰāĻž (faces) āχāĻŽā§āĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύāĨ¤", "metadata_settings": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ (Metadata Settings)", "metadata_settings_description": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ (Manage metadata settings)", "migration_job": "āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ (Migration)", @@ -268,7 +244,7 @@ "nightly_tasks_start_time_setting": "āĻļ⧁āϰ⧁ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ (Start time)", "nightly_tasks_start_time_setting_description": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϝāĻ–āύ āύāĻžāχāϟāϞāĻŋ āϟāĻžāĻ¸ā§āĻ• (nightly tasks) āϚāĻžāϞāĻžāύ⧋ āĻļ⧁āϰ⧁ āĻ•āϰ⧇ āϏ⧇āχ āϏāĻŽāϝāĻŧ", "nightly_tasks_sync_quota_usage_setting": "āϕ⧋āϟāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āϤāĻĨā§āϝ āϏāĻŋāĻ™ā§āĻ• āĻ•āϰ⧁āύ (Sync quota usage)", - "nightly_tasks_sync_quota_usage_setting_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋāϤ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", + "nightly_tasks_sync_quota_usage_setting_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧁āύāĨ¤", "no_paths_added": "āϕ⧋āύ⧋ āĻĒāĻžāĻĨ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ (No paths added)", "no_pattern_added": "āϕ⧋āύ⧋ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ (No pattern added)", "note_apply_storage_label_previous_assets": "āĻĻā§āϰāĻˇā§āϟāĻŦā§āϝ: āĻĒā§‚āĻ°ā§āĻŦ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋āϤ⧇ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ (Storage Label) āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāϤ⧇ āύāĻŋāĻšā§‡āϰ āĻ•āĻŽāĻžāĻ¨ā§āĻĄāϟāĻŋ āϰāĻžāύ āĻ•āϰ⧁āĻ¨â€”", @@ -291,20 +267,16 @@ "notification_enable_email_notifications": "āχāĻŽā§‡āϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āϏāύ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ", "notification_settings": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āϏāύ āϏ⧇āϟāĻŋāĻ‚āϏ", "notification_settings_description": "āχāĻŽā§‡āχāϞ āϏāĻš āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "oauth_allow_insecure_requests": "āĻā§āρāĻ•āĻŋāĻĒā§‚āĻ°ā§āĻŖ āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύ", - "oauth_allow_insecure_requests_description": "āϏāϤāĻ°ā§āĻ•āϤāĻž: āĻāϟāĻŋ OAuth āĻ…āύ⧁āϰ⧋āϧ⧇āϰ āϜāĻ¨ā§āϝ TLS āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻ­ā§āϝāĻžāϞāĻŋāĻĄā§‡āĻļāύ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧇ āĻĻ⧇āϝāĻŧ āĻāĻŦāĻ‚ āφāĻĒāύāĻžāϕ⧇ MITM āφāĻ•ā§āϰāĻŽāϪ⧇āϰ āĻā§āρāĻ•āĻŋāϤ⧇ āĻĢ⧇āϞāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "oauth_auto_launch": "āĻ…āĻŸā§‹ āϞāĻžā§āϚ", - "oauth_auto_launch_description": "āϞāĻ—āχāύ āĻĒ⧇āĻœā§‡ āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰāĻžāĻŽāĻžāĻ¤ā§āϰ OAuth āϞāĻ—āχāύ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāϟāĻŋ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", + "oauth_auto_launch_description": "āϞāĻ—āχāύ āĻĒ⧇āĻœā§‡ āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰāĻžāϰ āϏāĻžāĻĨ⧇ āϏāĻžāĻĨ⧇ OAuth āϞāĻ—āχāύ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāϟāĻŋ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", "oauth_auto_register": "āϏ⧟āĻ‚āĻ•ā§āϰāĻŋ⧟āĻ­āĻžāĻŦ⧇ āϰ⧇āϜāĻŋāĻ¸ā§āϟāĻžāϰ āĻ•āϰ⧁āύ", "oauth_auto_register_description": "OAuth āĻĻāĻŋāϝāĻŧ⧇ āϏāĻžāχāύ āχāύ āĻ•āϰāĻžāϰ āĻĒāϰ āύāϤ⧁āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āĻĻ⧇āϰ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āύāĻŋāĻŦāĻ¨ā§āϧāύ āĻ•āϰ⧁āύ", "oauth_button_text": "āĻŦāĻžāϟāύ āĻŸā§‡āĻ•ā§āϏāϟ", "oauth_client_secret_description": "āĻ—ā§‹āĻĒāύ⧀āϝāĻŧ āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāϝāĻŧā§‹āϜāύ, āĻ…āĻĨāĻŦāĻž āϝāĻĻāĻŋ āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āϰ āϜāĻ¨ā§āϝ PKCE (Proof Key for Code Exchange) āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āύāĻž āĻšāϝāĻŧāĨ¤", "oauth_enable_description": "OAuth-āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϞāĻ—āχāύ āĻ•āϰ⧁āύ", - "oauth_end_session_url_description": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϞāĻ—-āφāωāϟ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϤāĻžāϕ⧇ āĻāχ URI-āĻ āĻĒ⧁āύāσāύāĻŋāĻ°ā§āĻĻ⧇āĻļ āĻ•āϰ⧁āύāĨ¤", "oauth_mobile_redirect_uri": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI)", "oauth_mobile_redirect_uri_override": "āĻŽā§‹āĻŦāĻžāχāϞ āϰāĻŋāĻĄāĻžāχāϰ⧇āĻ•ā§āϟ āχāωāφāϰāφāχ (URI) āĻ“āĻ­āĻžāϰāϰāĻžāχāĻĄ", - "oauth_mobile_redirect_uri_override_description": "āϝāĻ–āύ OAuth āĻĒā§āϰ⧋āĻ­āĻžāχāĻĄāĻžāϰ āĻŽā§‹āĻŦāĻžāχāϞ āχāωāφāϰāφāχ (URI) āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻ⧇āϝāĻŧ āύāĻž, āϝ⧇āĻŽāύ ''{callback}'', āϤāĻ–āύ āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ", - "oauth_prompt_description": "āĻĒā§āϰāĻŽā§āĻĒāϟ āĻĒā§āϝāĻžāϰāĻžāĻŽāĻŋāϟāĻžāϰ (āϝ⧇āĻŽāύ: select_account, login, consent)", + "oauth_mobile_redirect_uri_override_description": "āϝāĻ–āύ OAuth āĻĒā§āϰ⧋āĻ­āĻžāχāĻĄāĻžāϰ āĻŽā§‹āĻŦāĻžāχāϞ āχāωāφāϰāφāχ (URI) āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻā§‡ā§Ÿ āύāĻž, āϝ⧇āĻŽāύ ''{callback}'', āϤāĻ–āύ āĻāϟāĻŋ āϏāĻ•ā§āϰāĻŋ⧟ āĻ•āϰ⧁āύāĨ¤", "oauth_role_claim": "āϰ⧋āϞ āĻ•ā§āϞ⧇āχāĻŽ (Role Claim)", "oauth_role_claim_description": "āĻāχ āĻ•ā§āϞ⧇āχāĻŽāϟāĻŋāϰ āωāĻĒāĻ¸ā§āĻĨāĻŋāϤāĻŋāϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻĒā§āϰāĻĻāĻžāύ āĻ•āϰ⧁āύāĨ¤ āĻ•ā§āϞ⧇āχāĻŽāϟāĻŋāϤ⧇ 'user' āĻ…āĻĨāĻŦāĻž 'admin' āϝ⧇āϕ⧋āύ⧋ āĻāĻ•āϟāĻŋ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "āϏāĻŦāϗ⧁āϞ⧋ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", "registration": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āϰ⧇āϜāĻŋāĻ¸ā§āĻŸā§āϰ⧇āĻļāύ (Admin Registration)", "registration_description": "āϝ⧇āĻšā§‡āϤ⧁ āφāĻĒāύāĻŋ āĻāχ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡āϰ āĻĒā§āϰāĻĨāĻŽ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀, āϤāĻžāχ āφāĻĒāύāĻžāϕ⧇ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ (Admin) āĻšāĻŋāϏ⧇āĻŦ⧇ āύāĻŋāϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāĻŦ⧇āĨ¤ āφāĻĒāύāĻŋ āϏāĻŽāĻ¸ā§āϤ āĻĒā§āϰāĻļāĻžāϏāύāĻŋāĻ• āĻ•āĻžāĻœā§‡āϰ āϜāĻ¨ā§āϝ āĻĻāĻžāϝāĻŧā§€ āĻĨāĻžāĻ•āĻŦ⧇āύ āĻāĻŦāĻ‚ āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰāĻž āφāĻĒāύāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡āχ āϤ⧈āϰāĻŋ āĻšāĻŦ⧇āĨ¤", - "release_channel_release_candidate": "āϰāĻŋāϞāĻŋāϜ āĻ•ā§āϝāĻžāĻ¨ā§āĻĄāĻŋāĻĄā§‡āϟ", - "release_channel_stable": "āĻ¸ā§āĻĨāĻŋāϤāĻŋāĻļā§€āϞ", "remove_failed_jobs": "āĻŦā§āϝāĻ°ā§āĻĨ āĻšāĻ“āϝāĻŧāĻž āĻ•āĻžāϜāϗ⧁āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞ⧁āύ (Remove failed jobs)", "require_password_change_on_login": "āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āϞāĻ—āχāύ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āĻŦāĻžāĻ§ā§āϝāϤāĻžāĻŽā§‚āϞāĻ• āĻ•āϰ⧁āύ", "reset_settings_to_default": "āϏ⧇āϟāĻŋāĻ‚āϏ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧇ āĻĄāĻŋāĻĢāĻ˛ā§āϟ āĻ…āĻŦāĻ¸ā§āĻĨāĻžā§Ÿ āĻĢāĻŋāϰāĻŋā§Ÿā§‡ āφāύ⧁āύ (Reset settings to default)", @@ -385,7 +355,7 @@ "theme_settings": "āĻĨā§€āĻŽ āϏ⧇āϟāĻŋāĻ‚āϏ", "theme_settings_description": "āχāĻŽāĻŋāϚ (Immich) āĻ“āϝāĻŧ⧇āĻŦ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ⧇āϰ āĻ•āĻžāĻ¸ā§āϟāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ", "thumbnail_generation_job": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ (Generate Thumbnails)", - "thumbnail_generation_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻŦāĻĄāĻŧ, āϛ⧋āϟ āĻ“ āĻāĻžāĻĒāϏāĻž āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ, āĻĒāĻžāĻļāĻžāĻĒāĻžāĻļāĻŋ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", + "thumbnail_generation_job_description": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻŦ⧜, āϛ⧋āϟ āĻāĻŦāĻ‚ āĻŦā§āϞāĻžāϰ (āĻ…āĻ¸ā§āĻĒāĻˇā§āϟ) āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ, āϏ⧇āχ āϏāĻžāĻĨ⧇ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤", "transcoding_acceleration_api": "āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āϟ āĻāĻĒāĻŋāφāχ (Acceleration API)", "transcoding_acceleration_api_description": "āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄāĻŋāĻ‚ (transcoding) āĻĻā§āϰ⧁āϤ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏāĻžāĻĨ⧇ āϝ⧇ API āχāĻ¨ā§āϟāĻžāϰāĻ…ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰāĻŦ⧇āĨ¤ āĻāχ āϏ⧇āϟāĻŋāĻ‚āϏāϟāĻŋ 'āϏāĻžāĻ§ā§āϝāĻŽāϤ⧋' (best effort) āĻ•āĻžāϜ āĻ•āϰāĻŦ⧇: āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϞ⧇ āĻāϟāĻŋ āĻĒ⧁āύāϰāĻžā§Ÿ āϏāĻĢāϟāĻ“ā§Ÿā§āϝāĻžāϰ āĻŸā§āϰāĻžāύāϏāϕ⧋āĻĄāĻŋāĻ‚ā§Ÿā§‡ āĻĢāĻŋāϰ⧇ āφāϏāĻŦ⧇āĨ¤ āĻšāĻžāĻ°ā§āĻĄāĻ“ā§Ÿā§āϝāĻžāϰ⧇āϰ āĻ“āĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ VP9 āĻ•āĻžāϜ āĻ•āϰāϤ⧇āĻ“ āĻĒāĻžāϰ⧇, āφāĻŦāĻžāϰ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU āĻĒā§āĻ°ā§Ÿā§‹āϜāύ)", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ VAAPI āĻāĻŦāĻ‚ QSV-āĻāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻĒā§āϰāϝ⧋āĻœā§āϝāĨ¤ āĻšāĻžāĻ°ā§āĻĄāĻ“āϝāĻŧā§āϝāĻžāϰ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšā§ƒāϤ dri āύ⧋āĻĄ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧇āĨ¤", "transcoding_preset_preset": "āĻĒā§āϰāĻŋāϏ⧇āϟ (-preset)", "transcoding_preset_preset_description": "āĻ•āĻŽā§āĻĒā§āϰ⧇āĻļāύ āĻ¸ā§āĻĒāĻŋāĻĄāĨ¤ āϧ⧀āϰāĻ—āϤāĻŋāϰ āĻĒā§āϰāĻŋāϏ⧇āϟāϗ⧁āϞ⧋ āϛ⧋āϟ āĻĢāĻžāχāϞ āϤ⧈āϰāĻŋ āĻ•āϰ⧇ āĻāĻŦāĻ‚ āĻāĻ•āϟāĻŋ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŦāĻŋāϟāϰ⧇āϟ āϞāĻ•ā§āĻˇā§āϝ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϗ⧁āĻŖāĻŽāĻžāύ āĻŦ⧃āĻĻā§āϧāĻŋ āĻ•āϰ⧇āĨ¤ VP9 'faster'-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻŦ⧇āĻļāĻŋ āĻ—āϤāĻŋ āωāĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰ⧇āĨ¤", - "transcoding_realtime": "āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", - "transcoding_realtime_description": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ¸ā§āĻŸā§āϰāĻŋāĻŽ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽā§‡ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āĻ•āϰāĻžāϰ āϏ⧁āϝ⧋āĻ— āĻĻ⧇āϝāĻŧāĨ¤ āĻāϟāĻŋ āϕ⧋āϝāĻŧāĻžāϞāĻŋāϟāĻŋ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϏ⧁āĻŦāĻŋāϧāĻž āĻĻ⧇āϝāĻŧ, āϤāĻŦ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻ•ā§āώāĻŽāϤāĻžāϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰ āĻ•āϰ⧇ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāϕ⧇ āĻŦ⧇āĻļāĻŋ āϞ⧇āĻŸā§‡āĻ¨ā§āϏāĻŋ āĻāĻŦāĻ‚ āĻ¸ā§āϟāĻžāϟāĻžāϰāĻŋāĻ‚ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", - "transcoding_realtime_enabled": "āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "transcoding_realtime_enabled_description": "āĻŦāĻ¨ā§āϧ āĻĨāĻžāĻ•āϞ⧇, āϏāĻžāĻ°ā§āĻ­āĻžāϰ āύāϤ⧁āύ āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āϏ⧇āĻļāύ āĻļ⧁āϰ⧁ āĻ•āϰāϤ⧇ āĻ…āĻ¸ā§āĻŦā§€āĻ•āĻžāϰ āĻ•āϰāĻŦ⧇āĨ¤", - "transcoding_realtime_resolutions": "āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ", - "transcoding_realtime_resolutions_description": "āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻĻāĻ¤ā§āϤ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύāϏāĻŽā§‚āĻšāĨ¤ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āϝāĻĨ⧇āĻˇā§āϟ āĻĻā§āϰ⧁āϤ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āύāĻž āĻĒāĻžāϰāϞ⧇ āωāĻšā§āϚāϤāϰ āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ⧇ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϏāĻŽāĻ¸ā§āϝāĻž āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", - "transcoding_realtime_video_codecs": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϏāĻŽā§‚āĻš", - "transcoding_realtime_video_codecs_description": "āϰāĻŋāϝāĻŧ⧇āϞ-āϟāĻžāχāĻŽ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚āϝāĻŧ⧇āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻĻāĻ¤ā§āϤ āĻ­āĻŋāĻĄāĻŋāĻ“ āϕ⧋āĻĄā§‡āĻ•āϏāĻŽā§‚āĻšāĨ¤ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāϕ⧇āϰ āϏāĻŽāϝāĻŧ āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āϟ āϤāĻžāĻĻ⧇āϰ āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āϏ⧇āϰāĻž āĻŦāĻŋāĻ•āĻ˛ā§āĻĒāϟāĻŋ āĻŦ⧇āϛ⧇ āύ⧇āĻŦ⧇āĨ¤ AV1, HEVC-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻŦ⧇āĻļāĻŋ āĻ•āĻžāĻ°ā§āϝāĻ•āϰ, āĻāĻŦāĻ‚ HEVC, H.264-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻŦ⧇āĻļāĻŋ āĻ•āĻžāĻ°ā§āϝāĻ•āϰāĨ¤ āĻšāĻžāĻ°ā§āĻĄāĻ“āϝāĻŧā§āϝāĻžāϰ āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āĻļāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ, āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āϏ⧇āχ āϕ⧋āĻĄā§‡āĻ•āϗ⧁āϞ⧋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ āϝāĻž āĻ…ā§āϝāĻžāĻ•ā§āϏāĻŋāϞāĻžāϰ⧇āϟāϰ āĻāύāϕ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āϏāĻĢāϟāĻ“āϝāĻŧā§āϝāĻžāϰ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ, āĻŽāύ⧇ āϰāĻžāĻ–āĻŦ⧇āύ H.264, AV1-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻĻā§āϰ⧁āϤ, āĻāĻŦāĻ‚ AV1, HEVC-āĻāϰ āĻšā§‡āϝāĻŧ⧇ āĻĻā§āϰ⧁āϤāĨ¤", "transcoding_reference_frames": "āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āĻĢā§āϰ⧇āĻŽ", "transcoding_reference_frames_description": "āĻāĻ•āϟāĻŋ āĻĢā§āϰ⧇āĻŽ āĻ•āĻŽā§āĻĒā§āϰ⧇āϏ āĻ•āϰāĻžāϰ āϏāĻŽā§Ÿ āĻ•āϤāϟāĻŋ āĻĢā§āϰ⧇āĻŽāϕ⧇ āϰ⧇āĻĢāĻžāϰ⧇āĻ¨ā§āϏ āĻšāĻŋāϏ⧇āĻŦ⧇ āύ⧇āĻ“ā§ŸāĻž āĻšāĻŦ⧇āĨ¤ āĻŽāĻžāύ āϝāϤ āĻŦ⧇āĻļāĻŋ āĻšāĻŦ⧇, āĻ•āĻŽāĻĒā§āϰ⧇āĻļāύ āĻĻāĻ•ā§āώāϤāĻž āϤāϤ āĻ­āĻžāϞ⧋ āĻšāĻŦ⧇, āϤāĻŦ⧇ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āϧ⧀āϰ āĻšāĻŦ⧇āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ āĻāχ āĻŽāĻžāύ āĻ¸ā§āĻŦ⧟āĻ‚āĻ•ā§āϰāĻŋ⧟āĻ­āĻžāĻŦ⧇ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻšāĻŦ⧇āĨ¤", "transcoding_required_description": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡ āύ⧇āχ āĻāĻŽāύ āĻ­āĻŋāĻĄāĻŋāĻ“", @@ -447,7 +409,7 @@ "transcoding_threads": "āĻĨā§āϰ⧇āĻĄ", "transcoding_threads_description": "āωāĻšā§āϚ āĻŽāĻžāύ⧇ āĻāύāϕ⧋āĻĄāĻŋāĻ‚ āĻĻā§āϰ⧁āϤ āĻšā§Ÿ, āĻ•āĻŋāĻ¨ā§āϤ⧁ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ•āĻŽ āĻ•āĻžāϜ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ CPU āϕ⧋āϰ⧇āϰ āĻŦ⧇āĻļāĻŋ āĻŽāĻžāύ āĻĻ⧇āĻ“ā§ŸāĻž āωāϚāĻŋāϤ āύ⧟āĨ¤ ā§Ļ āĻĻāĻŋāϞ⧇ āϏāĻ°ā§āĻŦāĻžāϧāĻŋāĻ• āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻšāĻŦ⧇āĨ¤", "transcoding_tone_mapping": "āĻŸā§‹āύ-āĻŽā§āϝāĻžāĻĒāĻŋāĻ‚", - "transcoding_tone_mapping_description": "āĻāχāϚāĻĄāĻŋāφāϰ (HDR) āĻ­āĻŋāĻĄāĻŋāĻ“āϕ⧇ āĻāϏāĻĄāĻŋāφāϰ (SDR)-āĻ āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻāϰ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϰ⧂āĻĒ āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāĻ–āĻžāϰ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϞāĻ—āϰāĻŋāĻĻāĻŽ āϰāĻ™, āĻĄāĻŋāĻŸā§‡āχāϞ āĻāĻŦāĻ‚ āωāĻœā§āĻœā§āĻŦāϞāϤāĻžāϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āĻ­āĻŋāĻ¨ā§āύāĻ­āĻžāĻŦ⧇ āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ āĻ•āϰ⧇āĨ¤ āĻšā§‡āĻŦāϞ āĻĄāĻŋāĻŸā§‡āχāϞ, āĻŽā§‹āĻŦāĻŋāϝāĻŧāĻžāϏ āϰāĻ™ āĻāĻŦāĻ‚ āϰāĻžāχāύāĻšāĻžāĻ°ā§āĻĄ āωāĻœā§āĻœā§āĻŦāϞāϤāĻž āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāϖ⧇āĨ¤", + "transcoding_tone_mapping_description": "āĻāχāϚāĻĄāĻŋāφāϰ (HDR) āĻ­āĻŋāĻĄāĻŋāĻ“āϕ⧇ āĻāϏāĻĄāĻŋāφāϰ (SDR)-āĻ āϰ⧂āĻĒāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻāϰ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϰ⧂āĻĒ āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāĻ–āĻžāϰ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰāĻž āĻšāϝāĻŧāĨ¤ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ…ā§āϝāĻžāϞāĻ—āϰāĻŋāĻĻāĻŽ āϰāĻ™, āĻĄāĻŋāĻŸā§‡āχāϞ āĻāĻŦāĻ‚ āωāĻœā§āĻœā§āĻŦāϞāϤāĻžāϰ āϜāĻ¨ā§āϝ āĻ­āĻŋāĻ¨ā§āύ āĻ­āĻŋāĻ¨ā§āύ āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ āĻ•āϰ⧇āĨ¤ āĻšā§‡āĻŦāϞ āĻĄāĻŋāĻŸā§‡āχāϞ, āĻŽā§‹āĻŦāĻŋāϝāĻŧāĻžāϏ āϰāĻ™ āĻāĻŦāĻ‚ āϰāĻžāχāύāĻšāĻžāĻ°ā§āĻĄ āωāĻœā§āĻœā§āĻŦāϞāϤāĻž āĻ…āĻ•ā§āώ⧁āĻŖā§āĻŖ āϰāĻžāϖ⧇āĨ¤", "transcoding_transcode_policy": "āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āύ⧀āϤāĻŋ", "transcoding_transcode_policy_description": "āĻ•āĻ–āύ āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ āϤāĻžāϰ āύ⧀āϤāĻŋāĻŽāĻžāϞāĻžāĨ¤ HDR āĻ­āĻŋāĻĄāĻŋāĻ“ āĻāĻŦāĻ‚ YUV 4:2:0 āĻŦā§āϝāϤ⧀āϤ āĻ…āĻ¨ā§āϝ āĻĒāĻŋāĻ•ā§āϏ⧇āϞ āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡āϰ āĻ­āĻŋāĻĄāĻŋāĻ“ āϏāĻ°ā§āĻŦāĻĻāĻž āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻšāĻŦ⧇ (āϝāĻĻāĻŋ āύāĻž āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄāĻŋāĻ‚ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻĨāĻžāϕ⧇)āĨ¤", "transcoding_two_pass_encoding": "āϟ⧁-āĻĒāĻžāϏ āĻāύāϕ⧋āĻĄāĻŋāĻ‚", @@ -477,11 +439,8 @@ "user_settings": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏ⧇āϟāĻŋāĻ‚āϏ", "user_settings_description": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏ⧇āϟāĻŋāĻ‚āϏ āĻŽā§āϝāĻžāύ⧇āϜ āĻ•āϰ⧁āύ", "user_successfully_removed": "āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āχāωāϜāĻžāϰ {email}-āϕ⧇ āϏāϰāĻŋā§Ÿā§‡ āĻĻ⧇āĻ“ā§ŸāĻž āĻšā§Ÿā§‡āϛ⧇āĨ¤", - "users_page_description": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĒ⧇āϜ", - "version_check_channel": "āϰāĻŋāϞāĻŋāϜ āĻšā§āϝāĻžāύ⧇āϞ", - "version_check_channel_description": "āφāĻĒāύāĻŋ āϕ⧋āύ āϰāĻŋāϞāĻŋāϜ āĻšā§āϝāĻžāύ⧇āϞ⧇āϰ āϜāĻ¨ā§āϝ āĻ­āĻžāĻ°ā§āϏāύ āĻ˜ā§‹āώāĻŖāĻž āĻĒ⧇āϤ⧇ āϚāĻžāύ āϤāĻž āĻŦ⧇āϛ⧇ āύāĻŋāύ", "version_check_enabled_description": "āĻ­āĻžāĻ°ā§āϏāύ āϝāĻžāϚāĻžāχ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "version_check_implications": "āĻ­āĻžāĻ°ā§āϏāύ āĻšā§‡āĻ• āĻĢāĻŋāϚāĻžāϰāϟāĻŋ {server}-āĻāϰ āϏāĻ™ā§āϗ⧇ āύāĻŋ⧟āĻŽāĻŋāϤ āϏāĻ‚āϝ⧋āϗ⧇āϰ āĻ“āĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰāĻļā§€āϞ", + "version_check_implications": "āĻ­āĻžāĻ°ā§āϏāύ āĻšā§‡āĻ• āĻĢāĻŋāϚāĻžāϰāϟāĻŋ github.com-āĻāϰ āϏāĻ™ā§āϗ⧇ āύāĻŋ⧟āĻŽāĻŋāϤ āϏāĻ‚āϝ⧋āϗ⧇āϰ āĻ“āĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰāĻļā§€āϞ", "version_check_settings": "āĻ­āĻžāĻ°ā§āϏāύ āϝāĻžāϚāĻžāχ", "version_check_settings_description": "āύāϤ⧁āύ āĻ­āĻžāĻ°ā§āϏāύ⧇āϰ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁/āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ", "video_conversion_job": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰ⧁āύ", @@ -491,174 +450,76 @@ "admin_password": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ⧇āϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ", "administration": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ", "advanced": "āĻ…ā§āϝāĻžāĻĄāĻ­āĻžāĻ¨ā§āϏāĻĄ", - "advanced_settings_clear_image_cache": "āĻ›āĻŦāĻŋāϰ āĻ•ā§āϝāĻžāĻļ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ", - "advanced_settings_clear_image_cache_error": "āĻ›āĻŦāĻŋāϰ āĻ•ā§āϝāĻžāĻļ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "advanced_settings_clear_image_cache_success": "āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ {size} āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "advanced_settings_log_level_title": "āϞāĻ— āϞ⧇āϭ⧇āϞ: {level}", - "advanced_settings_prefer_remote_subtitle": "āĻ•āĻŋāϛ⧁ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϞ⧋āĻ•āĻžāϞ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĨ⧇āϕ⧇ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϞ⧋āĻĄ āĻšāϤ⧇ āĻ…āĻ¤ā§āϝāĻ¨ā§āϤ āϧ⧀āϰāĻ—āϤāĻŋ āĻšāϝāĻŧāĨ¤ āϰāĻŋāĻŽā§‹āϟ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻāχ āϏ⧇āϟāĻŋāĻ‚ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύāĨ¤", - "advanced_settings_prefer_remote_title": "āϰāĻŋāĻŽā§‹āϟ āĻ›āĻŦāĻŋ āĻĒāĻ›āĻ¨ā§āĻĻ āĻ•āϰ⧁āύ", - "advanced_settings_proxy_headers_subtitle": "āχāĻŽāĻŋāϚ-āĻāϰ āĻĒā§āϰāϤāĻŋāϟāĻŋ āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āϰāĻŋāϕ⧋āϝāĻŧ⧇āĻ¸ā§āĻŸā§‡ āĻĒāĻžāĻ āĻžāύ⧋ āĻĒā§āϰāĻ•ā§āϏāĻŋ āĻšā§‡āĻĄāĻžāϰ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "advanced_settings_proxy_headers_title": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āĻĒā§āϰāĻ•ā§āϏāĻŋ āĻšā§‡āĻĄāĻžāϰ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", - "advanced_settings_readonly_mode_subtitle": "āϰāĻŋāĻĄ-āĻ…āύāϞāĻŋ āĻŽā§‹āĻĄ āϚāĻžāϞ⧁ āĻ•āϰ⧇, āϝ⧇āĻ–āĻžāύ⧇ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻļ⧁āϧ⧁ āĻĻ⧇āĻ–āĻž āϝāĻžāĻŦ⧇; āĻāĻ•āĻžāϧāĻŋāĻ• āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ, āĻļ⧇āϝāĻŧāĻžāϰ, āĻ•āĻžāĻ¸ā§āϟ, āĻĄāĻŋāϞāĻŋāϟ āĻ•āϰāĻžāϰ āĻŽāϤ⧋ āϏāĻŦāĻ•āĻŋāϛ⧁ āĻŦāĻ¨ā§āϧ āĻĨāĻžāĻ•āĻŦ⧇āĨ¤ āĻŽā§‚āϞ āĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻĨ⧇āϕ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ…ā§āϝāĻžāĻ­āĻžāϟāĻžāϰ āĻĻāĻŋāϝāĻŧ⧇ āϰāĻŋāĻĄ-āĻ…āύāϞāĻŋ āϚāĻžāϞ⧁/āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āϝāĻžāĻŦ⧇", - "advanced_settings_readonly_mode_title": "āĻļ⧁āϧ⧁ āĻĒāĻĄāĻŧāĻžāϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻž", - "advanced_settings_sync_remote_deletions_subtitle": "āĻ“āϝāĻŧ⧇āĻŦ⧇ āϕ⧋āύ⧋ āĻĢāĻžāχāϞ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻŦāĻž āĻĢāĻŋāϰāĻŋāϝāĻŧ⧇ āφāύāĻž āĻšāϞ⧇, āĻāχ āϝāĻ¨ā§āĻ¤ā§āϰ⧇āĻ“ āϤāĻž āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ•āϰāĻž āĻšāĻŦ⧇", - "advanced_settings_sync_remote_deletions_title": "āϏ⧁āĻĻā§‚āϰ⧇āϰ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϗ⧁āϞ⧋ āϏāĻ™ā§āĻ—āϤāĻŋāĻĒā§‚āĻ°ā§āĻŖ āĻ•āϰ⧁āύ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", - "advanced_settings_tile_subtitle": "āĻ…āĻ­āĻŋāĻœā§āĻž āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", - "advanced_settings_troubleshooting_subtitle": "āϏāĻŽāĻ¸ā§āϝāĻž āϏāĻŽāĻžāϧāĻžāύ⧇āϰ āϜāĻ¨ā§āϝ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤ āĻŦ⧈āĻļāĻŋāĻˇā§āĻŸā§āϝ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "advanced_settings_troubleshooting_title": "āĻ¤ā§āϰ⧁āϟāĻŋ āύāĻŋāϰāϏāύ", "age_months": "āĻŦ⧟āϏ {months, plural, one {# month} other {# months}}", "age_year_months": "āĻŦ⧟āϏ ā§§ āĻŦāĻ›āϰ, {months, plural, one {# month} other {# months}}", - "age_years": "{years, plural, other {āĻŦāϝāĻŧāϏ #}}", - "album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", "album_added": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "album_added_notification_setting_description": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻšāϞ⧇ āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻžāύ", "album_cover_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ•āĻ­āĻžāϰ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", "album_delete_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ {album} āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", "album_delete_confirmation_description": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϟāĻŋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻ…āĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰāĻž āφāϰ āĻāϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ āύāĻžāĨ¤", - "album_deleted": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", "album_info_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϤāĻĨā§āϝ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", + "album_leave": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰāĻŋā§Ÿā§‡ āϝ⧇āϤ⧇ āϚāĻžāύ ?", + "album_leave_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {album} āϛ⧇āĻĄāĻŧ⧇ āϝ⧇āϤ⧇ āϚāĻžāύ?", "album_name": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āύāĻžāĻŽ", "album_options": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ…āĻĒāĻļāύāϏāĻŽā§‚āĻš", "album_remove_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", "album_remove_user_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {user}-āϕ⧇ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", - "album_search_not_found": "āφāĻĒāύāĻžāϰ āĻ–ā§‹āρāĻœā§‡āϰ āϏāĻžāĻĨ⧇ āĻŽāĻŋāϞ⧇ āϝāĻžāϝāĻŧ āĻāĻŽāύ āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", "album_share_no_users": "āĻāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϟāĻŋ āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϏāĻ™ā§āϗ⧇ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āĻšā§Ÿā§‡āϛ⧇, āĻŦāĻž āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āύ⧇āχāĨ¤", - "album_summary": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϏāĻžāϰāϏāĻ‚āĻ•ā§āώ⧇āĻĒ", "album_updated": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "album_updated_setting_description": "āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧁āĻ•ā§āϤ āĻšāϞ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϜāĻ¨ā§āϝ āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻžāύ", - "album_upload_assets": "āφāĻĒāύāĻžāϰ āĻ•āĻŽā§āĻĒāĻŋāωāϟāĻžāϰ āĻĨ⧇āϕ⧇ āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧇ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "album_viewer_appbar_share_err_delete": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "album_viewer_page_share_add_users": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", + "album_user_left": "āĻŦāĻžāĻŽ {album}", + "album_user_removed": "{user} āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "album_with_link_access": "āϞāĻŋāĻ™ā§āĻ• āĻĨāĻžāĻ•āĻž āϝ⧇ āϕ⧇āω āĻāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ›āĻŦāĻŋ āĻ“ āĻŽāĻžāύ⧁āώāϜāύāϕ⧇ āĻĻ⧇āĻ–āϤ⧇ āĻĒāĻžāϰāĻŦ⧇āĨ¤", "albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϏāĻŽā§‚āĻš", - "albums_count": "{count, plural, one {{count, number}āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ} other {{count, number}āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ}}", - "albums_default_sort_order": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻžāϜāĻžāύ⧋āϰ āĻ¸ā§āĻŦāĻžāĻ­āĻžāĻŦāĻŋāĻ• āĻ•ā§āϰāĻŽ", - "albums_default_sort_order_description": "āύāϤ⧁āύ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻĢāĻžāχāϞ āϏāĻžāϜāĻžāύ⧋āϰ āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ• āĻ•ā§āϰāĻŽāĨ¤", - "albums_feature_description": "āĻāĻ•āĻžāϧāĻŋāĻ• āĻĢāĻžāχāϞ⧇āϰ āϏāĻ‚āĻ—ā§āϰāĻš, āϝāĻž āĻ…āĻ¨ā§āϝ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āĻĻ⧇āϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤", - "albums_on_device_count": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āĻĨāĻžāĻ•āĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ ({count})", "all": "āϏāĻŦ", "all_albums": "āϏāĻ•āϞ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϏāĻŽā§‚āĻš", "all_people": "āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀", - "all_photos": "āϏāĻŦ āĻ›āĻŦāĻŋ", "all_videos": "āϏāĻŦ āĻ­āĻŋāĻĄāĻŋāĻ“", "allow_dark_mode": "āĻĄāĻžāĻ°ā§āĻ• āĻŽā§‹āĻĄ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", + "allow_edits": "āĻāĻĄāĻŋāĻŸā§‡āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύ", "allow_public_user_to_download": "āϏāĻžāϧāĻžāϰāĻŖ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇", "allow_public_user_to_upload": "āϏāĻžāϧāĻžāϰāĻŖ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇", - "allowed": "āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ", - "alt_text_qr_code": "QR āϕ⧋āĻĄ āĻ›āĻŦāĻŋ", - "always_keep": "āϏāĻŦāϏāĻŽāϝāĻŧ āϰāĻžāϖ⧁āύ", - "always_keep_photos_hint": "āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰāĻžāϰ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏāĻŦ āĻ›āĻŦāĻŋ āϰ⧇āϖ⧇ āĻĻ⧇āĻŦ⧇āĨ¤", - "always_keep_videos_hint": "āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰāĻžāϰ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏāĻŦ āĻ­āĻŋāĻĄāĻŋāĻ“ āϰ⧇āϖ⧇ āĻĻ⧇āĻŦ⧇āĨ¤", + "anti_clockwise": "āĻŦāĻŋāĻĒāϰ⧀āϤ āĻĻāĻŋāĻ•", "api_key": "API āϕ⧀", "api_key_description": "āĻāχ āĻŽāĻžāύ āĻāĻ•āĻŦāĻžāϰāχ āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāĻŦ⧇āĨ¤ āωāχāĻ¨ā§āĻĄā§‹ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻžāϰ āφāϗ⧇ āĻ…āĻŦāĻļā§āϝāχ āĻāϟāĻŋ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύāĨ¤", "api_key_empty": "API āϕ⧀-āĻāϰ āύāĻžāĻŽ āĻ–āĻžāϞāĻŋ āϰāĻžāĻ–āĻž āϝāĻžāĻŦ⧇ āύāĻž", "api_keys": "API āϕ⧀ āϏāĻŽā§‚āĻš", - "app_architecture_variant": "āĻĒā§āϰāĻ•āϰāĻŖ (āĻ¸ā§āĻĨāĻžāĻĒāĻ¤ā§āϝāĻļ⧈āϞ⧀)", - "app_bar_signout_dialog_content": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻžāχāύ āφāωāϟ āĻ•āϰāϤ⧇ āύāĻŋāĻļā§āϚāĻŋāϤ?", - "app_bar_signout_dialog_title": "āϏāĻžāχāύ āφāωāϟ", - "app_download_links": "āĻ…ā§āϝāĻžāĻĒ āĻĄāĻžāωāύāϞ⧋āĻĄ āϞāĻŋāĻ‚āĻ•āϏāĻŽā§‚āĻš", "app_settings": "āĻ…ā§āϝāĻžāĻĒ āϏ⧇āϟāĻŋāĻ‚āϏ", - "app_stores": "āĻ…ā§āϝāĻžāĻĒ āĻ­āĻžāĻŖā§āĻĄāĻžāϰ", - "app_update_available": "āĻ…ā§āϝāĻžāĻĒ⧇āϰ āύāϤ⧁āύ āφāĻĒāĻĄā§‡āϟ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāĻšā§āϛ⧇", "appears_in": "v1.106.4 āĻĨ⧇āϕ⧇, āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāĻžāχāĻĄāĻŦāĻžāϰ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻšā§Ÿ ‘[albums]-āĻ āωāĻĒāĻ¸ā§āĻĨāĻŋāĻ¤â€™ āĻŦā§‹āĻāĻžāϤ⧇", - "apply_count": "āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ ({count, number})", "archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­", - "archive_action_prompt": "{count}āϟāĻŋ āφāĻ°ā§āĻ•āĻžāχāϭ⧇ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "archive_or_unarchive_photo": "āĻĢāĻŸā§‹ āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ…āĻĨāĻŦāĻž āφāύāφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰ⧁āύ", "archive_size": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āϏāĻžāχāϜ", "archive_size_description": "āĻĄāĻžāωāύāϞ⧋āĻĄā§‡āϰ āφāĻ°ā§āĻ•āĻžāχāĻ­ āϏāĻžāχāϜ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ (GiB)", - "archived": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "archived_count": "{count, plural, other {āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇ #}}", "are_these_the_same_person": "āĻāϰāĻž āĻ•āĻŋ āĻāĻ•āχ āĻŦā§āϝāĻ•ā§āϤāĻŋ?", "are_you_sure_to_do_this": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāϟāĻŋ āĻ•āϰāϤ⧇ āϚāĻžāύ?", "asset_added_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "asset_adding_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "asset_created": "āϏāĻŽā§āĻĒāĻĻ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "asset_day_count": "{date}: {count, plural, one {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ} other {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ}}", "asset_description_updated": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻŦāĻŋāĻŦāϰāĻŖ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", + "asset_filename_is_offline": "{filename} āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻ…āĻĢāϞāĻžāχāύ", + "asset_has_unassigned_faces": "āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋāϰ āĻ•āĻŋāϛ⧁ āĻŽā§āĻ– āĻ…āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āĻĢ⧇āϏ āϰāϝāĻŧ⧇āϛ⧇", "asset_hashing": "āĻšā§āϝāĻžāĻļāĻŋāĻ‚ āϚāϞāϛ⧇â€Ļ", - "asset_list_group_by_sub_title": "āĻĻāϞāϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "asset_list_layout_settings_group_by": "āϏāĻŽā§āĻĒāĻĻ āĻĻāϞāϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "asset_list_layout_settings_group_by_month_day": "āĻŽāĻžāϏ + āĻĻāĻŋāύ", - "asset_list_layout_sub_title": "āĻŦāĻŋāĻ¨ā§āϝāĻžāϏ", - "asset_list_settings_subtitle": "āĻ›āĻŦāĻŋāϰ āĻ—ā§āϰāĻŋāĻĄ āĻŦāĻŋāĻ¨ā§āϝāĻžāϏ āϏ⧇āϟāĻŋāĻ‚āϏ", - "asset_list_settings_title": "āĻ›āĻŦāĻŋāϰ āĻ—ā§āϰāĻŋāĻĄ", - "asset_not_found_on_device_android": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "asset_not_found_on_device_ios": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋāĨ¤ āφāĻĒāύāĻŋ āϝāĻĻāĻŋ iCloud āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĨāĻžāϕ⧇āύ, āϤāĻžāĻšāϞ⧇ iCloud-āĻ āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āĻĢāĻžāχāϞāϟāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋāĻĒā§‚āĻ°ā§āĻŖ āĻĨāĻžāĻ•āĻžāϰ āĻ•āĻžāϰāϪ⧇ āϏāĻŽā§āĻĒāĻĻāϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏāϝ⧋āĻ—ā§āϝ āύāĻžāĻ“ āĻšāϤ⧇ āĻĒāĻžāϰ⧇", - "asset_not_found_on_icloud": "iCloud-āĻ āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋāĨ¤ iCloud-āĻ āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āĻĢāĻžāχāϞāϟāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋāĻĒā§‚āĻ°ā§āĻŖ āĻĨāĻžāĻ•āĻžāϰ āĻ•āĻžāϰāϪ⧇ āϏāĻŽā§āĻĒāĻĻāϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏāϝ⧋āĻ—ā§āϝ āύāĻžāĻ“ āĻšāϤ⧇ āĻĒāĻžāϰ⧇", "asset_offline": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ āĻ…āĻĢāϞāĻžāχāύ", "asset_offline_description": "āĻāχ āĻāĻ•ā§āϏāϟāĻžāĻ°ā§āύāĻžāϞ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āĻāĻ–āύ āĻĄāĻŋāĻ¸ā§āϕ⧇ āύ⧇āχāĨ¤ āϏāĻšāĻžāϝāĻŧāϤāĻžāϰ āϜāĻ¨ā§āϝ Immich āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻŋāĻ¸ā§āĻŸā§āϰ⧇āϟāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϝ⧋āĻ—āĻžāϝ⧋āĻ— āĻ•āϰ⧁āύāĨ¤", "asset_skipped": "āĻāĻĄāĻŧāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "asset_skipped_in_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇", - "asset_troubleshoot": "āϏāĻŽā§āĻĒāĻĻ āĻ¤ā§āϰ⧁āϟāĻŋ āύāĻŋāϰāϏāύ", "asset_uploaded": "āφāĻĒāϞ⧋āĻĄ āϏāĻŽā§āĻĒāĻ¨ā§āύ", "asset_uploading": "āφāĻĒāϞ⧋āĻĄ āϚāϞāϛ⧇â€Ļ", - "asset_viewer_settings_subtitle": "āφāĻĒāύāĻžāϰ āĻ—ā§āϝāĻžāϞāĻžāϰāĻŋ āĻĒā§āϰāĻĻāĻ°ā§āĻļāĻ• āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "asset_viewer_settings_title": "āϏāĻŽā§āĻĒāĻĻ āĻĒā§āϰāĻĻāĻ°ā§āĻļāĻ•", "assets": "āĻ…ā§āϝāĻžāϏ⧇āϟāϏāĻŽā§‚āĻš", "assets_added_to_album_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ {count, plural, one {# asset} other {# assets}} āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "assets_added_to_album_partial_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ {totalCount}āϟāĻŋāϰ āĻŽāĻ§ā§āϝ⧇ {successCount}āϟāĻŋ {totalCount, plural, one {āϏāĻŽā§āĻĒāĻĻ} other {āϏāĻŽā§āĻĒāĻĻ}} āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "assets_added_to_albums_count": "{albumTotal, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡}} {assetTotal, plural, one {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ} other {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ}} āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "assets_cannot_be_added_to_album_count": "{count, plural, one {āϏāĻŽā§āĻĒāĻĻāϟāĻŋ} other {āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋}} āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āϝāĻžāĻšā§āϛ⧇ āύāĻž", - "assets_cannot_be_added_to_albums": "{count, plural, one {āϏāĻŽā§āĻĒāĻĻāϟāĻŋ} other {āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋}} āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āχ āϝ⧁āĻ•ā§āϤ āĻ•āϰāĻž āϝāĻžāĻšā§āϛ⧇ āύāĻž", - "assets_count": "{count, plural, one {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ} other {#āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ}}", "assets_moved_to_trash_count": "{count, plural, one {# asset} other {# assets}} āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "assets_permanently_deleted_count": "{count, plural, one {# asset} other {# assets}} āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", "assets_removed_count": "{count, plural, one {# asset} other {# assets}} āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "assets_restore_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āφāĻĒāύāĻžāϰ āϏāĻŦ āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤ āϤāĻŦ⧇ āĻ…āĻĢāϞāĻžāχāύ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻāχāĻ­āĻžāĻŦ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻšāĻŦ⧇ āύāĻžāĨ¤", "assets_restored_count": "{count, plural, one {# asset} other {# assets}} āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "assets_trashed": "{count}āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ āφāĻŦāĻ°ā§āϜāύāĻžāϝāĻŧ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "assets_trashed_count": "{count, plural, one {# asset} other {# assets}} āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĒāĻžāĻ āĻžāύ⧋ āĻšā§Ÿā§‡āϛ⧇", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} āφāϗ⧇āχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧁āĻ•ā§āϤ āĻ›āĻŋāϞ", - "assets_were_part_of_albums_count": "{count, plural, one {āϏāĻŽā§āĻĒāĻĻāϟāĻŋ āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ…āĻ‚āĻļ āĻ›āĻŋāϞ} other {āϏāĻŽā§āĻĒāĻĻāϗ⧁āϞ⧋ āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻ…āĻ‚āĻļ āĻ›āĻŋāϞ}}", "authorized_devices": "āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āĻĄāĻŋāĻ­āĻžāχāϏ", - "automatic_endpoint_switching_subtitle": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ Wi-Fi āωāĻĒāϞāĻŦā§āϧ āĻĨāĻžāĻ•āϞ⧇ āϤāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧāĻ­āĻžāĻŦ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āĻšāύ, āĻ…āĻ¨ā§āϝāĻ¤ā§āϰ āĻŦāĻŋāĻ•āĻ˛ā§āĻĒ āϏāĻ‚āϝ⧋āĻ— āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "automatic_endpoint_switching_title": "āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧ URL āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", - "autoplay_slideshow": "āĻ›āĻŦāĻŋāϰ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧀ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ", "back": "āĻĢāĻŋāϰ⧇ āϝāĻžāύ", "back_close_deselect": "āĻĢāĻŋāϰ⧇ āϝāĻžāύ, āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ āĻŦāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", - "background_location_permission": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āϞ⧋āϕ⧇āĻļāύ āĻ…āύ⧁āĻŽāϤāĻŋ", - "background_location_permission_content": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄā§‡ āϚāϞāĻžāϰ āϏāĻŽāϝāĻŧ āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϤ⧇ āĻšāϞ⧇, āχāĻŽāĻŋāϚ-āϕ⧇ āϏāĻŦāϏāĻŽāϝāĻŧ āϏ⧁āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āϞ⧋āϕ⧇āĻļāύ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻĻāĻŋāϤ⧇ āĻšāĻŦ⧇, āϝāĻžāϤ⧇ āĻ…ā§āϝāĻžāĻĒāϟāĻŋ Wi-Fi āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āϕ⧇āϰ āύāĻžāĻŽ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇", - "background_options": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ⧀", - "backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ", - "backup_album_selection_page_albums_tap": "āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰāϤ⧇ āĻŸā§āϝāĻžāĻĒ āĻ•āϰ⧁āύ, āĻŦāĻžāĻĻ āĻĻāĻŋāϤ⧇ āĻĄāĻžāĻŦāϞ āĻŸā§āϝāĻžāĻĒ āĻ•āϰ⧁āύ", - "backup_album_selection_page_assets_scatter": "āϏāĻŽā§āĻĒāĻĻ āĻāĻ•āĻžāϧāĻŋāĻ• āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āĻ›āĻĄāĻŧāĻŋāϝāĻŧ⧇ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āϤāĻžāχ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻŦāĻž āĻŦāĻžāĻĻ āĻĻ⧇āĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāĨ¤", - "backup_album_selection_page_select_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "backup_album_selection_page_selection_info": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ⧇āϰ āϤāĻĨā§āϝ", - "backup_albums_sync": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ", - "backup_background_service_complete_notification": "āϏāĻŽā§āĻĒāĻĻ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻšāϝāĻŧ⧇āϛ⧇", - "backup_background_service_default_notification": "āύāϤ⧁āύ āϏāĻŽā§āĻĒāĻĻ āĻ–ā§‹āρāϜāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "backup_background_service_in_progress_notification": "āφāĻĒāύāĻžāϰ āϏāĻŽā§āĻĒāĻĻ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "backup_controller_page_albums": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϏāĻŽā§‚āĻš", - "backup_controller_page_background_battery_info_link": "āϕ⧀āĻ­āĻžāĻŦ⧇ āĻ•āϰāĻŦ āĻĻ⧇āĻ–āĻžāύ", - "backup_controller_page_background_battery_info_message": "āϏāĻŦāĻšā§‡āϝāĻŧ⧇ āĻ­āĻžāϞ⧋ āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ…āĻ­āĻŋāĻœā§āĻžāϤāĻžāϰ āϜāĻ¨ā§āϝ, āχāĻŽāĻŋāϚ-āĻāϰ āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻ•āĻžāĻ°ā§āϝāĻ•ā§āϰāĻŽ āϏ⧀āĻŽāĻŋāϤ āĻ•āϰ⧇ āĻāĻŽāύ āϝ⧇āϕ⧋āύ⧋ āĻŦā§āϝāĻžāϟāĻžāϰāĻŋ āĻ…āĻĒā§āϟāĻŋāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύāĨ¤\n\nāĻāϟāĻŋ āĻĄāĻŋāĻ­āĻžāχāϏ-āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻŦāĻŋāώāϝāĻŧ āĻŦāϞ⧇, āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĒā§āϰāĻ¸ā§āϤ⧁āϤāĻ•āĻžāϰāϕ⧇āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāϝāĻŧā§‹āϜāύ⧀āϝāĻŧ āϤāĻĨā§āϝ āϖ⧁āρāĻœā§‡ āĻĻ⧇āϖ⧁āύāĨ¤", - "backup_controller_page_background_battery_info_ok": "āĻ āĻŋāĻ• āφāϛ⧇", - "backup_controller_page_background_battery_info_title": "āĻŦā§āϝāĻžāϟāĻžāϰāĻŋ āĻ…āĻĒā§āϟāĻŋāĻŽāĻžāχāĻœā§‡āĻļāύ", - "backup_controller_page_background_delay": "āύāϤ⧁āύ āϏāĻŽā§āĻĒāĻĻ⧇āϰ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻŦāĻŋāϞāĻŽā§āĻŦāĻŋāϤ āĻ•āϰ⧁āύ: {duration}", - "backup_controller_page_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ", - "backup_controller_page_backup_selected": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ: ", - "backup_controller_page_backup_sub": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“", - "backup_controller_page_excluded": "āĻŦāĻžāĻĻ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻšāϝāĻŧ⧇āϛ⧇: ", - "backup_controller_page_none_selected": "āĻ•āĻŋāϛ⧁āχ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻšāϝāĻŧāύāĻŋ", - "backup_controller_page_remainder": "āĻ…āĻŦāĻļāĻŋāĻˇā§āϟ", - "backup_controller_page_remainder_sub": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻĨ⧇āϕ⧇ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻžāϰ āĻŦāĻžāĻ•āĻŋ āĻĨāĻžāĻ•āĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“", - "backup_controller_page_server_storage": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻ‚āϰāĻ•ā§āώāĻŖāĻžāĻ—āĻžāϰ", - "backup_controller_page_storage_format": "{total}-āĻāϰ āĻŽāĻ§ā§āϝ⧇ {used} āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻšāϝāĻŧ⧇āϛ⧇", - "backup_controller_page_to_backup": "āϝ⧇āϏāĻŦ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āĻšāĻŦ⧇", - "backup_controller_page_total_sub": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϏāĻŦ āĻ…āύāĻ¨ā§āϝ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“", - "backup_error_sync_failed": "āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻž āĻ•āϰāĻž āϝāĻžāĻšā§āϛ⧇ āύāĻžāĨ¤", - "backup_info_card_assets": "āϏāĻŽā§āĻĒāĻĻāϏāĻŽā§‚āĻš", - "backup_options": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻ‚āĻ•ā§āϰāĻžāĻ¨ā§āϤ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻž", - "backup_settings_subtitle": "āφāĻĒāϞ⧋āĻĄ āϏāĻ‚āĻ•ā§āϰāĻžāĻ¨ā§āϤ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", "backward": "āĻĒāĻŋāĻ›āύ⧇", - "battery_optimization_backup_reliability": "āĻŦā§āϝāĻžāϟāĻžāϰāĻŋ āĻ…āĻĒā§āϟāĻŋāĻŽāĻžāχāĻœā§‡āĻļāύ āĻŦāĻ¨ā§āϧ āĻ•āϰāϞ⧇ āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻŦā§āϝāĻžāĻ•āφāĻĒ⧇āϰ āύāĻŋāĻ°ā§āĻ­āϰāϝ⧋āĻ—ā§āϝāϤāĻž āĻŦāĻžāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇", - "biometric_auth_enabled": "āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "biometric_locked_out": "āφāĻĒāύāĻŋ āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āĻĨ⧇āϕ⧇ āĻŦāĻžāĻĻ āĻĒāĻĄāĻŧ⧇ āϗ⧇āϛ⧇āύ", - "biometric_no_options": "āϕ⧋āύ⧋ āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻŦāĻŋāĻ•āĻ˛ā§āĻĒ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ", - "biometric_not_available": "āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ", + "birthdate_saved": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏāĻ‚āϰāĻ•ā§āώāĻŖ āϏāĻŽā§āĻĒāĻ¨ā§āύ", "birthdate_set_description": "āĻāĻ•āϟāĻŋ āĻ›āĻŦāĻŋāϰ āϏāĻŽā§Ÿā§‡ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻŦ⧟āϏ āĻ—āĻŖāύāĻžāϰ āϜāĻ¨ā§āϝ āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻž āĻšā§ŸāĨ¤", "blurred_background": "āĻŦā§āϞāĻžāϰāĻĄ āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ", - "browse_templates": "āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻĻ⧇āϖ⧁āύ", "bugs_and_feature_requests": "āĻŦāĻžāĻ— āĻ“ āĻĢāĻŋāϚāĻžāϰ āϰāĻŋāĻ•ā§‹ā§Ÿā§‡āĻ¸ā§āϟ", "build": "āĻŦāĻŋāĻ˛ā§āĻĄ", "build_image": "āĻŦāĻŋāĻ˛ā§āĻĄ āχāĻŽā§‡āϜ", @@ -671,107 +532,49 @@ "camera_model": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž āĻŽāĻĄā§‡āϞ", "cancel": "āĻŦāĻžāϤāĻŋāϞ", "cancel_search": "āϏāĻžāĻ°ā§āϚ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ", - "canceled": "āĻŦāĻžāϤāĻŋāϞ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "canceling": "āĻŦāĻžāϤāĻŋāϞ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", "cannot_merge_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻž āϏāĻŽā§āĻ­āĻŦ āύāϝāĻŧ", "cannot_undo_this_action": "āĻāχ āĻ•āĻžāϜ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž!", "cannot_update_the_description": "āĻŦāĻŋāĻŦāϰāĻŖ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āϏāĻŽā§āĻ­āĻŦ āύāϝāĻŧ", - "cast": "āĻ•āĻžāĻ¸ā§āϟ", - "cast_description": "āωāĻĒāϞāĻŦā§āϧ āĻ•āĻžāĻ¸ā§āϟ āĻ—āĻ¨ā§āϤāĻŦā§āϝāĻ¸ā§āĻĨāϞ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "change": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", "change_date": "āϤāĻžāϰāĻŋāĻ– āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", - "change_description": "āĻŦāĻŋāĻŦāϰāĻŖ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "change_display_order": "āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧇āϰ āĻ•ā§āϰāĻŽ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", + "change_expiration_time": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ⧇āϰ āϏāĻŽāϝāĻŧ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", "change_location": "āϞ⧋āϕ⧇āĻļāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", "change_name": "āύāĻžāĻŽ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", "change_name_successfully": "āύāĻžāĻŽ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻšāϝāĻŧ⧇āϛ⧇", "change_password": "āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", "change_password_description": "āφāĻĒāύāĻŋ āĻšāϝāĻŧāϤ⧋ āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āϞāĻ—āχāύ āĻ•āϰāϛ⧇āύ āĻŦāĻž āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻ…āύ⧁āϰ⧋āϧ āĻ•āϰ⧇āϛ⧇āύāĨ¤ āύāĻŋāĻšā§‡ āύāϤ⧁āύ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĻāĻŋāύāĨ¤", - "change_password_form_confirm_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύ", - "change_password_form_description": "āĻšā§āϝāĻžāϞ⧋ {name},\n\nāφāĻĒāύāĻŋ āĻšāϝāĻŧāϤ⧋ āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡ āϏāĻžāχāύ āχāύ āĻ•āϰāϛ⧇āύ āĻ…āĻĨāĻŦāĻž āφāĻĒāύāĻžāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āĻ…āύ⧁āϰ⧋āϧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āύāĻŋāĻšā§‡ āύāϤ⧁āύ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĻāĻŋāύāĨ¤", - "change_password_form_log_out": "āĻ…āĻ¨ā§āϝ āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰ⧁āύ", - "change_password_form_log_out_description": "āĻ…āĻ¨ā§āϝ āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāĻžāϰ āĻĒāϰāĻžāĻŽāĻ°ā§āĻļ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻšāĻšā§āϛ⧇", - "change_password_form_new_password": "āύāϤ⧁āύ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "change_password_form_password_mismatch": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻŽāĻŋāϞāϛ⧇ āύāĻž", - "change_password_form_reenter_new_password": "āύāϤ⧁āύ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻŦāĻžāϰ āĻĻāĻŋāύ", - "change_pin_code": "PIN āϕ⧋āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", "change_your_password": "āφāĻĒāύāĻžāϰ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", "changed_visibility_successfully": "āĻ­āĻŋāϏāĻŋāĻŦāĻŋāϞāĻŋāϟāĻŋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻšāϝāĻŧ⧇āϛ⧇", - "charging": "āϚāĻžāĻ°ā§āϜ āĻšāĻšā§āϛ⧇", - "charging_requirement_mobile_backup": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻŦā§āϝāĻžāĻ•āφāĻĒ⧇āϰ āϜāĻ¨ā§āϝ āĻĄāĻŋāĻ­āĻžāχāϏ āϚāĻžāĻ°ā§āĻœā§‡ āĻĨāĻžāĻ•āĻž āĻĒā§āϰāϝāĻŧā§‹āϜāύ", "check_logs": "āϞāĻ— āĻĻ⧇āϖ⧁āύ", - "checksum": "āϏāϤāϤāĻž-āϝāĻžāϚāĻžāχ", - "choose": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", "choose_matching_people_to_merge": "āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻŽāĻŋāϞ āĻĨāĻžāĻ•āĻž āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", "city": "āĻļāĻšāϰ", - "cleanup_confirm_description": "āχāĻŽāĻŋāϚ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āύāĻŋāϰāĻžāĻĒāĻĻ⧇ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻšāĻ“āϝāĻŧāĻž {count}āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ (āϝ⧇āϗ⧁āϞ⧋ {date}-āĻāϰ āφāϗ⧇ āϤ⧈āϰāĻŋ) āϖ⧁āρāĻœā§‡ āĻĒ⧇āϝāĻŧ⧇āϛ⧇āĨ¤ āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ•āĻĒāĻŋāϗ⧁āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻŦ⧇āύ?", - "cleanup_confirm_prompt_title": "āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻŦ⧇āύ?", - "cleanup_deleted_assets": "{count}āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "cleanup_deleting": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāĻšā§āϛ⧇..āĨ¤", - "cleanup_found_assets": "{count}āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϗ⧇āϛ⧇", - "cleanup_found_assets_with_size": "{count}āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϗ⧇āϛ⧇ ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻĨ⧇āϕ⧇ āĻŦāĻžāĻĻ āϰāĻžāĻ–āĻž āĻšāϝāĻŧ⧇āϛ⧇", - "cleanup_no_assets_found": "āωāĻĒāϰ⧇āϰ āĻļāĻ°ā§āϤ⧇āϰ āϏāĻžāĻĨ⧇ āĻŽāĻŋāϞ⧇ āϝāĻžāϝāĻŧ āĻāĻŽāύ āϕ⧋āύ⧋ āϏāĻŽā§āĻĒāĻĻ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋāĨ¤ āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰāĻžāϰ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻšāĻ“āϝāĻŧāĻž āϏāĻŽā§āĻĒāĻĻ āĻŽā§āĻ›āϤ⧇ āĻĒāĻžāϰ⧇", - "cleanup_preview_title": "āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ āϏāĻŽā§āĻĒāĻĻ ({count})", - "cleanup_step3_description": "āφāĻĒāύāĻžāϰ āϤāĻžāϰāĻŋāĻ– āĻ“ āϰāĻžāĻ–āĻžāϰ āϏ⧇āϟāĻŋāĻ‚āϏ⧇āϰ āϏāĻžāĻĨ⧇ āĻŽāĻŋāϞ⧇ āϝāĻžāϝāĻŧ āĻāĻŽāύ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āϏāĻŽā§āĻĒāĻĻ āϖ⧁āρāϜ⧁āύāĨ¤", - "cleanup_step4_summary": "āφāĻĒāύāĻžāϰ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϜāĻ¨ā§āϝ {count}āϟāĻŋ āϏāĻŽā§āĻĒāĻĻ ({date}-āĻāϰ āφāϗ⧇ āϤ⧈āϰāĻŋ)āĨ¤ āχāĻŽāĻŋāϚ āĻ…ā§āϝāĻžāĻĒ āĻĨ⧇āϕ⧇ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻĻ⧇āĻ–āĻž āϝāĻžāĻŦ⧇āĨ¤", - "cleanup_trash_hint": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖāĻ­āĻžāĻŦ⧇ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻ¸ā§āĻĒ⧇āϏ āĻĢāĻŋāϰ⧇ āĻĒ⧇āϤ⧇, āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻ—ā§āϝāĻžāϞāĻžāϰāĻŋ āĻ…ā§āϝāĻžāĻĒ āϖ⧁āϞ⧇ āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ–āĻžāϞāĻŋ āĻ•āϰ⧁āύ", "clear": "āĻŽā§āϛ⧁āύ", "clear_all": "āϏāĻŦ āĻŽā§āϛ⧁āύ", "clear_all_recent_searches": "āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ• āϏāĻŦ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ", - "clear_failed_count": "āĻŽā§āĻ›āϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇ ({count})", - "clear_file_cache": "āĻĢāĻžāχāϞ āĻ•ā§āϝāĻžāĻļ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ", "clear_message": "āĻŽā§‡āϏ⧇āϜ āĻĒāϰāĻŋāĻˇā§āĻ•āĻžāϰ āĻ•āϰ⧁āύ", "clear_value": "āĻ­ā§āϝāĻžāϞ⧁ āĻŽā§āϛ⧁āύ", - "client_cert_import": "āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰ⧁āύ", - "client_cert_import_success_msg": "āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āϟ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āφāĻŽāĻĻāĻžāύāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "client_cert_invalid_msg": "āϭ⧁āϞ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻĢāĻžāχāϞ āĻŦāĻž āϭ⧁āϞ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "client_cert_password_message": "āĻāχ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĻāĻŋāύ", - "client_cert_password_title": "āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "client_cert_remove_msg": "āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āϟ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "client_cert_subtitle": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ PKCS12 (.p12, .pfx) āĻĢāϰāĻŽā§āϝāĻžāϟ āϏāĻŽāĻ°ā§āĻĨāĻŋāϤāĨ¤ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āφāĻŽāĻĻāĻžāύāĻŋ/āĻ…āĻĒāϏāĻžāϰāĻŖ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āϞāĻ—āχāύ⧇āϰ āφāϗ⧇ āĻ•āϰāĻž āϝāĻžāĻŦ⧇", - "client_cert_title": "SSL āĻ•ā§āϞāĻžāϝāĻŧ⧇āĻ¨ā§āϟ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ [āĻĒāϰ⧀āĻ•ā§āώāĻžāĻŽā§‚āϞāĻ•]", + "clockwise": "āϘ⧜āĻŋāϰ āĻ•āĻžāρāϟāĻžāϰ āĻĻāĻŋāϕ⧇", "close": "āĻŦāĻ¨ā§āϧ", "collapse": "āϏāĻ‚āϕ⧁āϚāĻŋāϤ āĻ•āϰ⧁āύ", "collapse_all": "āϏāĻŦ āϏāĻ‚āϕ⧁āϚāĻŋāϤ", "color": "āϰāĻ‚", - "command": "āĻ•āĻŽāĻžāĻ¨ā§āĻĄ", - "command_palette_prompt": "āĻĒ⧇āϜ, āĻ…ā§āϝāĻžāĻ•āĻļāύ āĻŦāĻž āĻ•āĻŽāĻžāĻ¨ā§āĻĄ āĻĻā§āϰ⧁āϤ āϖ⧁āρāϜ⧁āύ", - "command_palette_to_close": "āĻŦāĻ¨ā§āϧ āĻ•āϰāϤ⧇", - "command_palette_to_navigate": "āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰāϤ⧇", - "command_palette_to_select": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇", - "command_palette_to_show_all": "āϏāĻŦ āĻĻ⧇āĻ–āĻžāϤ⧇", + "color_theme": "āĻ•āĻžāϞāĻžāϰ āĻĨāĻŋāĻŽ", "comment_deleted": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "comment_options": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āϏāĻ‚āĻ•ā§āϰāĻžāĻ¨ā§āϤ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻž", + "comment_options": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻ…āĻĒāĻļāύ", "comments_and_likes": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻ“ āϞāĻžāχāĻ•", "comments_are_disabled": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āĻŦāĻ¨ā§āϧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "common_create_new_album": "āύāϤ⧁āύ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "completed": "āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻšāϝāĻŧ⧇āϛ⧇", - "configuration": "āĻ—āĻ āύāĻŦāĻŋāĻ¨ā§āϝāĻžāϏ", "confirm": "āύāĻŋāĻļā§āϚāĻŋāϤ", "confirm_admin_password": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒ⧁āύāϰāĻžā§Ÿ āϞāĻŋāϖ⧁āύ", - "confirm_delete_face": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āϏāĻŽā§āĻĒāĻĻ āĻĨ⧇āϕ⧇ {name} āĻŽā§āĻ–āϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", "confirm_delete_shared_link": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ•āϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", "confirm_keep_this_delete_others": "āĻ¸ā§āĻŸā§āϝāĻžāϕ⧇āϰ āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ›āĻžā§œāĻž āϏāĻŦ āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇āĨ¤ āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϚāĻžāϞāĻŋāϝāĻŧ⧇ āϝ⧇āϤ⧇ āϚāĻžāύ?", - "confirm_new_pin_code": "āύāϤ⧁āύ PIN āϕ⧋āĻĄ āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύ", "confirm_password": "āĻĒāĻžāϏāĻ“ā§ŸāĻžāĻ°ā§āĻĄ āĻĒ⧁āύāϰāĻžā§Ÿ āϞāĻŋāϖ⧁āύ", - "confirm_tag_face": "āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāχ āĻŽā§āĻ–āϟāĻŋāϕ⧇ {name} āĻšāĻŋāϏ⧇āĻŦ⧇ āĻŸā§āϝāĻžāĻ— āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "confirm_tag_face_unnamed": "āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāχ āĻŽā§āĻ–āϟāĻŋ āĻŸā§āϝāĻžāĻ— āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "connected_device": "āϏāĻ‚āϝ⧁āĻ•ā§āϤ āĻĄāĻŋāĻ­āĻžāχāϏ", - "connected_to": "āϏāĻ‚āϝ⧁āĻ•ā§āϤ āφāϛ⧇", "contain": "āĻŽāĻžāĻĒāĻŽāϤ", "context": "āĻĒā§āϰāϏāĻ™ā§āĻ—", "continue": "āĻāĻ—āĻŋā§Ÿā§‡ āϝāĻžāύ", - "control_bottom_app_bar_add_tags": "āĻŸā§āϝāĻžāĻ—āϏāĻŽā§‚āĻš āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "control_bottom_app_bar_delete_from_local": "āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻŽā§āϛ⧁āύ", - "control_bottom_app_bar_edit_location": "āϞ⧋āϕ⧇āĻļāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "control_bottom_app_bar_edit_time": "āϤāĻžāϰāĻŋāĻ– āĻ“ āϏāĻŽāϝāĻŧ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "control_bottom_app_bar_trash_from_immich": "āφāĻŦāĻ°ā§āϜāύāĻžāϝāĻŧ āϏāϰāĻžāύ", "copied_image_to_clipboard": "āĻ›āĻŦāĻŋ āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻšāϝāĻŧ⧇āϛ⧇āĨ¤", "copied_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻšāϝāĻŧ⧇āϛ⧇!", "copy_error": "Error-āϟāĻŋ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ", + "copy_file_path": "āĻĢāĻžāχāϞ āĻĒāĻžāĻĨ āĻ•āĻĒāĻŋ", "copy_image": "āĻ›āĻŦāĻŋ āĻ•āĻĒāĻŋ", - "copy_json": "JSON āĻ•āĻĒāĻŋ", "copy_link": "āϞāĻŋāĻ™ā§āĻ• āĻ•āĻĒāĻŋ", "copy_link_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āϞāĻŋāĻ™ā§āĻ• āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ", "copy_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ", @@ -781,93 +584,48 @@ "covers": "āĻ•āĻ­āĻžāϰāϏ", "create": "āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "create_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ", - "create_album_page_untitled": "āĻļāĻŋāϰ⧋āύāĻžāĻŽāĻšā§€āύ", - "create_api_key": "āĻāĻĒāĻŋāφāχ āϕ⧀ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "create_first_workflow": "āĻĒā§āϰāĻĨāĻŽ āĻ•āĻ°ā§āĻŽāĻĒā§āϰāĻŦāĻžāĻš āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "create_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϤ⧈āϰāĻŋ", "create_link": "āϞāĻŋāĻ™ā§āĻ• āϤ⧈āϰāĻŋ", "create_link_to_share": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ™ā§āĻ• āϤ⧈āϰāĻŋ", "create_link_to_share_description": "āϞāĻŋāĻ™ā§āϕ⧇āϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϏāĻŦāĻžāχ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ›āĻŦāĻŋ āĻĻ⧇āĻ–āϤ⧇ āĻĒāĻžāϰāĻŦ⧇", - "create_new": "āύāϤ⧁āύ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "create_new_person": "āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϝ⧋āĻ— āĻ•āϰ⧁āύ", "create_new_person_hint": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϏ⧇āϟ āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϏāĻ™ā§āϗ⧇ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", "create_new_user": "āύāϤ⧁āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰ⧁āύ", - "create_person": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "create_person_subtitle": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻšā§‡āĻšāĻžāϰāĻžāϝāĻŧ āĻāĻ•āϟāĻŋ āύāĻžāĻŽ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧇ āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϤ⧈āϰāĻŋ āĻ“ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰ⧁āύ", - "create_shared_album_page_share_add_assets": "āϏāĻŽā§āĻĒāĻĻ āϝ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "create_shared_album_page_share_select_photos": "āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "create_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", "create_tag": "āĻŸā§āϝāĻžāĻ— āϤ⧈āϰāĻŋ", "create_tag_description": "āύāϤ⧁āύ āĻŸā§āϝāĻžāĻ— āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύāĨ¤ āύ⧇āĻ¸ā§āĻŸā§‡āĻĄ āĻŸā§āϝāĻžāϗ⧇āϰ āĻ•ā§āώ⧇āĻ¤ā§āϰ⧇ āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻĒāĻžāĻĨ - āĻĢāϰāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ¸ā§āĻ˛ā§āϝāĻžāĻļāϏāĻš āĻĻāĻŋāύāĨ¤", "create_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰ⧁āύ", - "create_workflow": "āĻ•āĻ°ā§āĻŽāĻĒā§āϰāĻŦāĻžāĻš āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "created": "āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "created_at": "āϤ⧈āϰāĻŋ āĻšāĻ“āϝāĻŧāĻžāϰ āϏāĻŽāϝāĻŧ", - "creating_linked_albums": "āϏāĻ‚āϝ⧁āĻ•ā§āϤ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇..āĨ¤", - "crop": "āĻ•āĻžāϟāĻ›āĻžāρāϟ", - "crop_aspect_ratio_free": "āĻŽā§āĻ•ā§āϤ", - "crop_aspect_ratio_original": "āĻŽā§‚āϞ", - "crop_aspect_ratio_square": "āĻŦāĻ°ā§āĻ—āĻžāĻ•āĻžāϰ", + "created": "āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", "current_device": "āϚāϞāϤāĻŋ āĻĄāĻŋāĻ­āĻžāχāϏ", - "current_pin_code": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ PIN āϕ⧋āĻĄ", - "current_server_address": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ āĻŋāĻ•āĻžāύāĻž", - "custom_date": "āĻ¸ā§āĻŦāύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āϤāĻžāϰāĻŋāĻ–", - "custom_locale": "āĻ¸ā§āĻŦāύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āϞ⧋āϕ⧇āϞ", + "custom_locale": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āϞ⧋āϕ⧇āϞ", "custom_locale_description": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ­āĻžāώāĻž āĻāĻŦāĻ‚ āĻ…āĻžā§āϚāϞ⧇āϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋāϤ⧇ āϤāĻžāϰāĻŋāĻ–, āϏāĻŽā§Ÿ āĻāĻŦāĻ‚ āϏāĻ‚āĻ–ā§āϝāĻž āĻĢāϰāĻŽā§āϝāĻžāϟ āĻ•āϰ⧁āύ", - "cutoff_date_description": "āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ• āĻāχ āϏāĻŽāϝāĻŧāĻ•āĻžāϞ⧇āϰ āĻ›āĻŦāĻŋ āϰāĻžāϖ⧁āύâ€Ļ", - "cutoff_day": "{count, plural, one {āĻĻāĻŋāύ} other {āĻĻāĻŋāύ}}", - "cutoff_year": "{count, plural, one {āĻŦāĻ›āϰ} other {āĻŦāĻ›āϰ}}", - "dark": "āĻ…āĻ¨ā§āϧāĻ•āĻžāϰ", - "dark_theme": "āĻ…āĻ¨ā§āϧāĻ•āĻžāϰ āĻĨāĻŋāĻŽā§‡ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "date": "āϤāĻžāϰāĻŋāĻ–", + "dark": "āĻĄāĻžāĻ°ā§āĻ•", "date_after": "āĻāϰ āĻĒāϰ⧇āϰ āϤāĻžāϰāĻŋāĻ–", "date_and_time": "āϤāĻžāϰāĻŋāĻ– āĻāĻŦāĻ‚ āϏāĻŽā§Ÿ", "date_before": "āĻāϰ āφāϗ⧇āϰ āϤāĻžāϰāĻŋāĻ–", - "date_of_birth": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ–", "date_of_birth_saved": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "date_range": "āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž", - "date_time_original": "āĻŽā§‚āϞ āϤāĻžāϰāĻŋāĻ–/āϏāĻŽāϝāĻŧ", - "day": "āĻĻāĻŋāύ", - "days": "āĻĻāĻŋāύ", - "deduplicate_all": "āϏāĻŦ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāϰāĻžāύ", - "default_quality_subtitle": "āĻļ⧇āϝāĻŧāĻžāϰ āĻŦāĻžāϟāύ⧇ āĻŸā§āϝāĻžāĻĒ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻŦā§āϝāĻŦāĻšā§ƒāϤ āĻŽāĻžāύāĨ¤ āĻĒā§āϰāϤāĻŋāĻŦāĻžāϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻŦāĻžāϟāύ⧇ āĻĻā§€āĻ°ā§āϘāĻ•ā§āώāĻŖ āϚāĻžāĻĒ āĻĻāĻŋāύāĨ¤", - "default_share_quality": "āĻĄāĻŋāĻĢāĻ˛ā§āϟ āĻļ⧇āϝāĻŧāĻžāϰ āĻŽāĻžāύ", "delete": "āĻŽā§āϛ⧁āύ", - "delete_action_confirmation_message": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āĻāχ āϏāĻŽā§āĻĒāĻĻāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ? āĻāχ āĻ•āĻžāϜāϟāĻŋ āϏāĻŽā§āĻĒāĻĻāϟāĻŋāϕ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āφāĻŦāĻ°ā§āϜāύāĻžāϝāĻŧ āϏāϰāĻŋāϝāĻŧ⧇ āĻĻ⧇āĻŦ⧇ āĻāĻŦāĻ‚ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧāĻ­āĻžāĻŦ⧇āĻ“ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ āĻ•āĻŋāύāĻž āϤāĻž āϜāĻŋāĻœā§āĻžāĻžāϏāĻž āĻ•āϰāĻŦ⧇", - "delete_action_prompt": "{count}āϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", "delete_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŽā§āϛ⧁āύ", "delete_api_key_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻāχ API key āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", - "delete_dialog_alert": "āĻāχ āφāχāĻŸā§‡āĻŽāϗ⧁āϞ⧋ Immich āĻāĻŦāĻ‚ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇", - "delete_dialog_alert_local": "āĻāχ āφāχāĻŸā§‡āĻŽāϗ⧁āϞ⧋ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āϏāϰāĻžāύ⧋ āĻšāĻŦ⧇, āϤāĻŦ⧇ Immich āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āωāĻĒāϞāĻŦā§āϧ āĻĨāĻžāĻ•āĻŦ⧇", - "delete_dialog_alert_local_non_backed_up": "āĻ•āĻŋāϛ⧁ āφāχāĻŸā§‡āĻŽ Immich-āĻ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āύ⧇āχ āĻāĻŦāĻ‚ āϏ⧇āϗ⧁āϞ⧋ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āϏāϰāĻžāύ⧋ āĻšāĻŦ⧇", - "delete_dialog_title": "āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧁āύ", "delete_duplicates_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āϏāĻ¤ā§āϝāĻŋāχ āĻāχ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟāϗ⧁āϞ⧋ āĻ¸ā§āĻĨāĻžā§Ÿā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āĻ›āϤ⧇ āϚāĻžāύ?", - "delete_face": "āĻŽā§āĻ– āĻŽā§āϛ⧁āύ", "delete_key": "key āĻŽā§āϛ⧁āύ", "delete_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŽā§āϛ⧁āύ", "delete_link": "āϞāĻŋāĻ™ā§āĻ• āĻŽā§āϛ⧁āύ", - "delete_local_action_prompt": "{count}āϟāĻŋ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧāĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "delete_local_dialog_ok_backed_up_only": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻžāϗ⧁āϞ⧋ āĻŽā§āϛ⧁āύ", - "delete_local_dialog_ok_force": "āϤāĻŦ⧁āĻ“ āĻŽā§āϛ⧁āύ", "delete_others": "āĻŦāĻžāĻ•āĻŋāϗ⧁āϞ⧋ āĻŽā§āϛ⧁āύ", - "delete_permanently": "āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧁āύ", - "delete_permanently_action_prompt": "{count}āϟāĻŋ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", "delete_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āĻŽā§āϛ⧁āύ", - "delete_shared_link_dialog_title": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻŽā§āϛ⧁āύ", "delete_tag": "āĻŸā§āϝāĻžāĻ— āĻŽā§āϛ⧁āύ", "delete_tag_confirmation_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤāĻ­āĻžāĻŦ⧇ {tagName} āĻŸā§āϝāĻžāĻ—āϟāĻŋ āĻŽā§āĻ›āϤ⧇ āϚāĻžāύ?", "delete_user": "āχāωāϜāĻžāϰ āĻŽā§āϛ⧁āύ", "deleted_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ•āϟāĻŋ āĻŽā§āϛ⧁āύ", + "deletes_missing_assets": "āĻĄāĻŋāĻ¸ā§āĻ• āĻĨ⧇āϕ⧇ āĻšāĻžāϰāĻžāύ⧋ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āĻŽā§āϛ⧇", "description": "āĻŦāĻŋāĻŦāϰāύ", - "deselect_all": "āϏāĻŦ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", "details": "āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ", "direction": "āĻĻāĻŋāĻ•āύāĻŋāĻ°ā§āĻĻ⧇āĻļāύāĻž", - "disable": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ", "disabled": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ", + "disallow_edits": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻžāϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻ⧇āĻŦ⧇āύ āύāĻž", "discord": "āĻĄāĻŋāϏāĻ•āĻ°ā§āĻĄ", "discover": "āĻĄāĻŋāϏāĻ•āĻ­āĻžāϰ", - "discovered_devices": "āĻļāύāĻžāĻ•ā§āϤ āĻšāĻ“āϝāĻŧāĻž āĻĄāĻŋāĻ­āĻžāχāϏ", "dismiss_all_errors": "āϏāĻŦ āĻ¤ā§āϰ⧁āϟāĻŋ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", + "dismiss_error": "āĻ¤ā§āϰ⧁āϟāĻŋ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", "display_options": "āĻĄāĻŋāϏāĻĒā§āϞ⧇ āĻ…āĻĒāĻļāύ", "display_order": "āĻĄāĻŋāϏāĻĒā§āϞ⧇ āĻ…āĻ°ā§āĻĄāĻžāϰ", "display_original_photos": "āĻ…āϰāĻŋāϜāĻŋāύāĻžāϞ āĻ›āĻŦāĻŋ āĻĻ⧇āĻ–āĻžāύ", @@ -876,1419 +634,17 @@ "documentation": "āϏāĻšāĻžā§ŸāĻ• āύāĻŋāĻ°ā§āĻĻ⧇āĻļāĻŋāĻ•āĻž", "done": "āϏāĻŽā§āĻĒāĻ¨ā§āύ", "download": "āĻĄāĻžāωāύāϞ⧋āĻĄ", - "download_canceled": "āĻĄāĻžāωāύāϞ⧋āĻĄ āĻŦāĻžāϤāĻŋāϞ āĻšāϝāĻŧ⧇āϛ⧇", - "download_complete": "āĻĄāĻžāωāύāϞ⧋āĻĄ āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻšāϝāĻŧ⧇āϛ⧇", - "download_enqueue": "āĻĄāĻžāωāύāϞ⧋āĻĄ āϏāĻžāϰāĻŋāϤ⧇ āϝ⧁āĻ•ā§āϤ āĻšāϝāĻŧ⧇āϛ⧇", - "download_failed": "āĻĄāĻžāωāύāϞ⧋āĻĄ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "download_finished": "āĻĄāĻžāωāύāϞ⧋āĻĄ āĻļ⧇āώ āĻšāϝāĻŧ⧇āϛ⧇", "download_include_embedded_motion_videos": "āĻāĻŽāĻŦ⧇āĻĄā§‡āĻĄ āĻ­āĻŋāĻĄāĻŋāĻ“", "download_include_embedded_motion_videos_description": "āĻŽā§‹āĻļāύ āĻĢāĻŸā§‹āϰ (motion photos) āĻŽāĻ§ā§āϝ⧇ āĻĨāĻžāĻ•āĻž āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞ⧋āϕ⧇ āφāϞāĻžāĻĻāĻž āĻĢāĻžāχāϞ āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "download_notfound": "āĻĄāĻžāωāύāϞ⧋āĻĄ āϖ⧁āρāĻœā§‡ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "download_original": "āĻŽā§‚āϞ āĻĢāĻžāχāϞ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "download_paused": "āĻĄāĻžāωāύāϞ⧋āĻĄ āĻĨāĻžāĻŽāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", "download_settings": "āĻĄāĻžāωāύāϞ⧋āĻĄ", "download_settings_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĄāĻžāωāύāϞ⧋āĻĄā§‡āϰ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "download_waiting_to_retry": "āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻšā§‡āĻˇā§āϟāĻžāϰ āϜāĻ¨ā§āϝ āĻ…āĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "downloading": "āĻĄāĻžāωāύāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "downloading_asset_filename": "{filename} āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "downloading_from_icloud": "iCloud āĻĨ⧇āϕ⧇ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "downloading_media": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĄāĻžāωāύāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "drag_to_reorder": "āĻ•ā§āϰāĻŽ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϤ⧇ āĻŸā§‡āύ⧇ āφāύ⧁āύ", - "drop_files_to_upload": "āφāĻĒāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āϝ⧇āϕ⧋āύ⧋ āϜāĻžāϝāĻŧāĻ—āĻžāϝāĻŧ āĻĢāĻžāχāϞ āĻĄā§āϰāĻĒ āĻ•āϰ⧁āύ", - "duplicate": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ", - "duplicate_workflow": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻ•āϰ⧁āύ", - "duplicates": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ", - "duplicates_description": "āϕ⧋āύāϗ⧁āϞ⧋, āϝāĻĻāĻŋ āĻĨāĻžāϕ⧇, āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϤāĻž āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰ⧇ āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ—ā§āϰ⧁āĻĒ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰ⧁āύāĨ¤", - "duration": "āϏāĻŽāϝāĻŧāĻ•āĻžāϞ", - "edit": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž", - "edit_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_avatar": "āĻ…ā§āϝāĻžāĻ­āĻžāϟāĻžāϰ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_birthday": "āϜāĻ¨ā§āĻŽāĻĻāĻŋāύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_date": "āϤāĻžāϰāĻŋāĻ– āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_date_and_time": "āϤāĻžāϰāĻŋāĻ– āĻ“ āϏāĻŽāϝāĻŧ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_date_and_time_action_prompt": "{count}āϟāĻŋ āϤāĻžāϰāĻŋāĻ– āĻ“ āϏāĻŽāϝāĻŧ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "edit_date_and_time_by_offset": "āĻ…āĻĢāϏ⧇āϟ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āϤāĻžāϰāĻŋāĻ– āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "edit_description": "āĻŦāĻŋāĻŦāϰāĻŖ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_exclusion_pattern": "āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_faces": "āĻŽā§āĻ– āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_key": "āϕ⧀ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_link": "āϞāĻŋāĻ‚āĻ• āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_location_action_prompt": "{count}āϟāĻŋ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "edit_location_dialog_title": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ", - "edit_name": "āύāĻžāĻŽ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_tag": "āĻŸā§āϝāĻžāĻ— āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_title": "āĻļāĻŋāϰ⧋āύāĻžāĻŽ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "edit_workflow": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "editor": "āĻāĻĄāĻŋāϟāϰ", - "editor_discard_edits_confirm": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", - "editor_discard_edits_prompt": "āφāĻĒāύāĻžāϰ āĻ•āĻŋāϛ⧁ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āĻšāϝāĻŧāύāĻŋāĨ¤ āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏ⧇āϗ⧁āϞ⧋ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "editor_discard_edits_title": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻŦāĻžāϤāĻŋāϞ āĻ•āϰāĻŦ⧇āύ?", - "editor_edits_applied_error": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "editor_edits_applied_success": "āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻšāϝāĻŧ⧇āϛ⧇", - "editor_flip_horizontal": "āĻ…āύ⧁āĻ­ā§‚āĻŽāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āĻĢā§āϞāĻŋāĻĒ āĻ•āϰ⧁āύ", - "editor_flip_vertical": "āωāϞāĻŽā§āĻŦāĻ­āĻžāĻŦ⧇ āĻĢā§āϞāĻŋāĻĒ āĻ•āϰ⧁āύ", - "editor_handle_corner": "{corner, select, top_left {āωāĻĒāϰ⧇-āĻŦāĻžāĻŽā§‡} top_right {āωāĻĒāϰ⧇-āĻĄāĻžāύ⧇} bottom_left {āύāĻŋāĻšā§‡-āĻŦāĻžāĻŽā§‡} bottom_right {āύāĻŋāĻšā§‡-āĻĄāĻžāύ⧇} other {āĻāĻ•āϟāĻŋ}} āϕ⧋āĻŖ āĻšā§āϝāĻžāĻ¨ā§āĻĄā§‡āϞ", - "editor_handle_edge": "{edge, select, top {āωāĻĒāϰ⧇āϰ} bottom {āύāĻŋāĻšā§‡āϰ} left {āĻŦāĻžāĻŽā§‡āϰ} right {āĻĄāĻžāύ⧇āϰ} other {āĻāĻ•āϟāĻŋ}} āĻĒā§āϰāĻžāĻ¨ā§āϤ āĻšā§āϝāĻžāĻ¨ā§āĻĄā§‡āϞ", - "editor_orientation": "āĻ“āϰāĻŋāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āĻļāύ", - "editor_reset_all_changes": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "editor_rotate_left": "⧝ā§Ļ° āϘāĻĄāĻŧāĻŋāϰ āĻ•āĻžāρāϟāĻžāϰ āĻŦāĻŋāĻĒāϰ⧀āϤ⧇ āĻ˜ā§‹āϰāĻžāύ", - "editor_rotate_right": "⧝ā§Ļ° āϘāĻĄāĻŧāĻŋāϰ āĻ•āĻžāρāϟāĻžāϰ āĻĻāĻŋāϕ⧇ āĻ˜ā§‹āϰāĻžāύ", - "email": "āχāĻŽā§‡āχāϞ", - "email_notifications": "āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ", - "empty_folder": "āĻāχ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰāϟāĻŋ āĻ–āĻžāϞāĻŋ", - "empty_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ–āĻžāϞāĻŋ āĻ•āϰ⧁āύ", - "empty_trash_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ–āĻžāϞāĻŋ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāϤ⧇ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĨāĻžāĻ•āĻž āϏāĻŦ āĻ…ā§āϝāĻžāϏ⧇āϟ Immich āĻĨ⧇āϕ⧇ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇āĨ¤\nāĻāχ āĻ•āĻžāϜāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻž!", - "enable": "āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "enable_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "enable_biometric_auth_description": "āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇ āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϞāĻŋāϖ⧁āύ", - "enabled": "āϚāĻžāϞ⧁ āφāϛ⧇", - "end_date": "āĻļ⧇āώ āϤāĻžāϰāĻŋāĻ–", - "enqueued": "āϏāĻžāϰāĻŋāϤ⧇ āϝ⧁āĻ•ā§āϤ āĻšāϝāĻŧ⧇āϛ⧇", - "enter_wifi_name": "Wi-Fi āύāĻžāĻŽ āϞāĻŋāϖ⧁āύ", - "enter_your_pin_code": "āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϞāĻŋāϖ⧁āύ", - "enter_your_pin_code_subtitle": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϞāĻŋāϖ⧁āύ", - "error": "āĻ¤ā§āϰ⧁āϟāĻŋ", - "error_delete_face": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĨ⧇āϕ⧇ āĻŽā§āĻ– āĻŽā§āĻ›āϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_loading_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_loading_image": "āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_loading_partners": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ: {error}", - "error_retrieving_asset_information": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϤāĻĨā§āϝ āφāύāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_saving_image": "āĻ¤ā§āϰ⧁āϟāĻŋ: {error}", - "error_tag_face_bounding_box": "āĻŽā§āĻ– āĻŸā§āϝāĻžāĻ— āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ - āĻŦāĻžāωāĻ¨ā§āĻĄāĻŋāĻ‚ āĻŦāĻ•ā§āϏ⧇āϰ āĻ¸ā§āĻĨāĻžāύāĻžāĻ™ā§āĻ• āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "error_title": "āĻ¤ā§āϰ⧁āϟāĻŋ - āĻ•āĻŋāϛ⧁ āĻāĻ•āϟāĻž āϭ⧁āϞ āĻšāϝāĻŧ⧇āϛ⧇", - "error_while_navigating": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āϝ⧇āϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "errors": { - "cannot_navigate_next_asset": "āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āϝāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cannot_navigate_previous_asset": "āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āϝāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_apply_changes": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_change_activity": "āĻ•āĻžāĻ°ā§āϝāĻ•āϞāĻžāĻĒ {enabled, select, true {āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ} other {āϚāĻžāϞ⧁}} āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_change_asset_favorite": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_change_metadata_assets_count": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}}-āĻāϰ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_get_faces": "āĻŽā§āĻ– āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_get_number_of_comments": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_search_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āϖ⧁āρāĻœā§‡ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "cant_search_places": "āĻ¸ā§āĻĨāĻžāύ āϖ⧁āρāĻœā§‡ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "error_adding_assets_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧋āĻ— āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_adding_users_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_deleting_shared_user": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āĻ›āϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_downloading": "{filename} āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_hiding_buy_button": "āĻ•ā§āϰāϝāĻŧ āĻŦāĻžāϟāύ āϞ⧁āĻ•āĻžāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "error_removing_assets_from_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāϰāĻžāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇, āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ āϜāĻžāύāϤ⧇ āĻ•āύāϏ⧋āϞ āĻĻ⧇āϖ⧁āύ", - "error_selecting_all_assets": "āϏāĻŦ āĻ…ā§āϝāĻžāϏ⧇āϟ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "exclusion_pattern_already_exists": "āĻāχ āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύāϟāĻŋ āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύāĨ¤", - "failed_to_create_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_create_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_edit_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_get_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āϤāĻĨā§āϝ āĻĒāĻžāĻ“āϝāĻŧāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_keep_this_delete_others": "āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āϰāĻžāĻ–āĻž āĻāĻŦāĻ‚ āĻ…āĻ¨ā§āϝāϗ⧁āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϞ⧋āĻĄ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āϞ⧋āĻĄ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_notifications": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϞ⧋āĻĄ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āϤāĻĨā§āϝ āϞ⧋āĻĄ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_remove_product_key": "āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āϏāϰāĻžāύ⧋ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_reset_pin_code": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_stack_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_tag_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŸā§āϝāĻžāĻ— āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_unstack_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āφāύāĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_update_notification_status": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ⧇āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻž āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "incorrect_email_or_password": "āχāĻŽā§‡āχāϞ āĻ…āĻĨāĻŦāĻž āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϭ⧁āϞ", - "library_folder_already_exists": "āĻāχ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻĒāĻžāĻĨāϟāĻŋ āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύāĨ¤", - "page_not_found": "āĻĒ⧇āϜ āϖ⧁āρāĻœā§‡ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "paths_validation_failed": "{paths, plural, one {#āϟāĻŋ āĻĒāĻžāĻĨ} other {#āϟāĻŋ āĻĒāĻžāĻĨ}} āϝāĻžāϚāĻžāχ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "profile_picture_transparent_pixels": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋāϤ⧇ āĻ¸ā§āĻŦāĻšā§āĻ› (transparent) āĻĒāĻŋāĻ•ā§āϏ⧇āϞ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰāĻŦ⧇ āύāĻžāĨ¤ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āϜ⧁āĻŽ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚/āĻ…āĻĨāĻŦāĻž āĻ›āĻŦāĻŋāϟāĻŋ āϏāϰāĻžāύāĨ¤", - "quota_higher_than_disk_size": "āφāĻĒāύāĻŋ āĻĄāĻŋāĻ¸ā§āϕ⧇āϰ āφāĻ•āĻžāϰ⧇āϰ āĻšā§‡āϝāĻŧ⧇ āĻŦ⧇āĻļāĻŋ āϕ⧋āϟāĻž āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧇āϛ⧇āύ", - "something_went_wrong": "āĻ•āĻŋāϛ⧁ āĻāĻ•āϟāĻž āϭ⧁āϞ āĻšāϝāĻŧ⧇āϛ⧇", - "unable_to_add_album_users": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_assets_to_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_comment": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_exclusion_pattern": "āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_partners": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_remove_archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­ {archived, select, true {āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāϰāĻžāύ⧋} other {āϤ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧋āĻ— āĻ•āϰāĻž}} āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_add_remove_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ {favorite, select, true {āϤ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϝ⧋āĻ— āĻ•āϰāĻž} other {āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāϰāĻžāύ⧋}} āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_archive_unarchive": "{archived, select, true {āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰāĻž} other {āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰ āĻ•āϰāĻž}} āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_album_user_role": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ­ā§‚āĻŽāĻŋāĻ•āĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_date": "āϤāĻžāϰāĻŋāĻ– āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_description": "āĻŦāĻŋāĻŦāϰāĻŖ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_favorite": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_change_visibility": "{count, plural, one {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ} other {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ}} āĻĻ⧃āĻļā§āϝāĻŽāĻžāύāϤāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_complete_oauth_login": "OAuth āϞāĻ—āχāύ āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_connect": "āϏāĻ‚āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_copy_to_clipboard": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ, āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύ āϝ⧇ āφāĻĒāύāĻŋ https āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āĻĒ⧇āϜāϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϛ⧇āύ", - "unable_to_create": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_create_admin_account": "āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_create_api_key": "āύāϤ⧁āύ API āϕ⧀ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_create_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_create_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§āĻ›āϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "unable_to_delete_exclusion_pattern": "āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_delete_workflow": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_download_files": "āĻĢāĻžāχāϞ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_edit_exclusion_pattern": "āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_empty_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻ–āĻžāϞāĻŋ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_enter_fullscreen": "āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ⧇ āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_exit_fullscreen": "āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰ āĻšāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_get_comments_number": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ⧇āϰ āϏāĻ‚āĻ–ā§āϝāĻž āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_get_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻĒāĻžāĻ“āϝāĻŧāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "unable_to_hide_person": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āϞ⧁āĻ•āĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_link_motion_video": "āĻŽā§‹āĻļāύ āĻ­āĻŋāĻĄāĻŋāĻ“ āϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_link_oauth_account": "OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_log_out_all_devices": "āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_log_out_device": "āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_login_with_oauth": "OAuth āĻĻāĻŋāϝāĻŧ⧇ āϞāĻ—āχāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_play_video": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_reassign_assets_existing_person": "{name, select, null {āĻāĻ•āϜāύ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ} other {{name}-āĻāϰ}} āĻ•āĻžāϛ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰāĻžāϝāĻŧ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_reassign_assets_new_person": "āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻ•āĻžāϛ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰāĻžāϝāĻŧ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_refresh_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_album_users": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_api_key": "API āϕ⧀ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_assets_from_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_partner": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_remove_reaction": "āϰāĻŋāĻ…ā§āϝāĻžāĻ•āĻļāύ āϏāϰāĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_reset_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_reset_pin_code": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_resolve_duplicate": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_restore_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_restore_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_restore_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_api_key": "API āϕ⧀ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_date_of_birth": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_name": "āύāĻžāĻŽ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_profile": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_save_settings": "āϏ⧇āϟāĻŋāĻ‚āϏ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_scan_libraries": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_scan_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_set_feature_photo": "āĻĢāĻŋāϚāĻžāϰ āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_set_profile_picture": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_set_rating": "āϰ⧇āϟāĻŋāĻ‚ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_submit_job": "āϜāĻŦ āϜāĻŽāĻž āĻĻ⧇āĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_trash_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĒāĻžāĻ āĻžāύ⧋ āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_unlink_account": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āφāύāϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_unlink_motion_video": "āĻŽā§‹āĻļāύ āĻ­āĻŋāĻĄāĻŋāĻ“ āφāύāϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_album_cover": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ•āĻ­āĻžāϰ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_album_info": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϤāĻĨā§āϝ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_settings": "āϏ⧇āϟāĻŋāĻ‚āϏ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_timeline_display_status": "āϟāĻžāχāĻŽāϞāĻžāχāύ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧇āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻž āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_update_workflow": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_upload_file": "āĻĢāĻžāχāϞ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ" - }, - "errors_text": "āĻ¤ā§āϰ⧁āϟāĻŋāϏāĻŽā§‚āĻš", - "exclusion_pattern": "āĻŦāĻ°ā§āϜāύ āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ", - "exif": "Exif", - "exif_bottom_sheet_description": "āĻŦāĻŋāĻŦāϰāĻŖ āϝ⧋āĻ— āĻ•āϰ⧁āύ..āĨ¤", - "exif_bottom_sheet_description_error": "āĻŦāĻŋāĻŦāϰāĻŖ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "exif_bottom_sheet_no_description": "āϕ⧋āύ⧋ āĻŦāĻŋāĻŦāϰāĻŖ āύ⧇āχ", - "exit_slideshow": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰ āĻšāύ", - "expand": "āϏāĻŽā§āĻĒā§āϰāϏāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "expand_all": "āϏāĻŦ āϏāĻŽā§āĻĒā§āϰāϏāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "expire_after": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "expired": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ", - "expires_date": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇ {date}", - "explore": "āĻ…āĻ¨ā§āĻŦ⧇āώāĻŖ", - "explorer": "āĻāĻ•ā§āϏāĻĒā§āϞ⧋āϰāĻžāϰ", - "export": "āĻāĻ•ā§āϏāĻĒā§‹āĻ°ā§āϟ", - "export_as_json": "JSON āĻšāĻŋāϏ⧇āĻŦ⧇ āĻāĻ•ā§āϏāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύ", - "export_database": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻāĻ•ā§āϏāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύ", - "export_database_description": "SQLite āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻāĻ•ā§āϏāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύ", - "exposure_time": "āĻāĻ•ā§āϏāĻĒā§‹āϜāĻžāϰ āϏāĻŽāϝāĻŧ", - "extension": "āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύ", - "external": "āĻŦāĻžāĻšā§āϝāĻŋāĻ•", - "external_libraries": "āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ", - "external_network": "āĻŦāĻžāĻšā§āϝāĻŋāĻ• āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ•", - "external_network_sheet_info": "āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ Wi-Fi āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āϕ⧇ āύāĻž āĻĨāĻžāĻ•āϞ⧇, āĻ…ā§āϝāĻžāĻĒāϟāĻŋ āύāĻŋāĻšā§‡āϰ URL-āϗ⧁āϞ⧋āϰ āĻŽāĻ§ā§āϝ⧇ āĻĨ⧇āϕ⧇ āωāĻĒāϰ⧇ āĻĨ⧇āϕ⧇ āύāĻŋāĻšā§‡ āĻ•ā§āϰāĻŽāĻžāύ⧁āϏāĻžāϰ⧇ āĻĒā§āϰāĻĨāĻŽ āϝ⧇āϟāĻŋāϤ⧇ āĻĒ⧌āρāĻ›āĻžāϤ⧇ āĻĒāĻžāϰ⧇ āϤāĻžāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āĻšāĻŦ⧇", - "f_number": "āĻāĻĢ-āύāĻžāĻŽā§āĻŦāĻžāϰ", - "face_unassigned": "āĻ…āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ", - "failed": "āĻŦā§āϝāĻ°ā§āĻĨ", - "failed_count": "āĻŦā§āϝāĻ°ā§āĻĨ: {count}", - "failed_to_authenticate": "āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_delete_file": "āĻĢāĻžāχāϞ āĻŽā§āĻ›āϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "failed_to_load_folder": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "favorite": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ", - "favorite_action_prompt": "{count}āϟāĻŋ āĻĢ⧇āĻ­āĻžāϰāĻŋāĻŸā§‡ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "favorite_or_unfavorite_photo": "āĻ›āĻŦāĻŋ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻ•āϰ⧁āύ āĻŦāĻž āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĨ⧇āϕ⧇ āĻŦāĻžāĻĻ āĻĻāĻŋāύ", - "favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ", - "feature_photo_updated": "āĻĢāĻŋāϚāĻžāϰ āĻ›āĻŦāĻŋ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", - "features": "āĻĢāĻŋāϚāĻžāϰ", - "features_setting_description": "āĻ…ā§āϝāĻžāĻĒ āĻĢāĻŋāϚāĻžāϰ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "file_name_or_extension": "āĻĢāĻžāχāϞ⧇āϰ āύāĻžāĻŽ āĻŦāĻž āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύ", - "file_name_text": "āĻĢāĻžāχāϞ⧇āϰ āύāĻžāĻŽ", - "file_size": "āĻĢāĻžāχāϞ⧇āϰ āφāĻ•āĻžāϰ", - "filename": "āĻĢāĻžāχāϞ⧇āϰ āύāĻžāĻŽ", - "filetype": "āĻĢāĻžāχāϞ⧇āϰ āϧāϰāύ", - "filter": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ", - "filter_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻ•āϰ⧁āύ", - "filter_places": "āĻ¸ā§āĻĨāĻžāύ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻ•āϰ⧁āύ", - "filter_tags": "āĻŸā§āϝāĻžāĻ— āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻ•āϰ⧁āύ", - "filters": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ", - "first": "āĻĒā§āϰāĻĨāĻŽ", - "fix_incorrect_match": "āϭ⧁āϞ āĻŽāĻŋāϞ āĻ āĻŋāĻ• āĻ•āϰ⧁āύ", - "focal_length": "āĻĢā§‹āĻ•āĻžāϞ āϞ⧇āĻ¨ā§āĻĨ", - "folder": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ", - "folder_not_found": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "folders": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ", - "folders_feature_description": "āĻĢāĻžāχāϞ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽā§‡ āĻĨāĻžāĻ•āĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āϜāĻ¨ā§āϝ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ­āĻŋāω āĻŦā§āϰāĻžāωāϜ āĻ•āϰāĻž", - "forgot_pin_code_question": "āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϭ⧁āϞ⧇ āϗ⧇āϛ⧇āύ?", - "forward": "āĻĢāϰāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "free_up_space": "āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰ⧁āύ", - "free_up_space_description": "āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰāϤ⧇ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύāĨ¤ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āĻĨāĻžāĻ•āĻž āφāĻĒāύāĻžāϰ āĻ•āĻĒāĻŋāϗ⧁āϞ⧋ āύāĻŋāϰāĻžāĻĒāĻĻ āĻĨāĻžāĻ•āĻŦ⧇āĨ¤", - "free_up_space_settings_subtitle": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏāĻ‚āϰāĻ•ā§āώāĻŖāĻžāĻ—āĻžāϰ āĻ–āĻžāϞāĻŋ āĻ•āϰ⧁āύ", - "full_path": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻĒāĻžāĻĨ: {path}", - "full_path_or_folder": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻĒāĻžāĻĨ āĻŦāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "āĻāχ āĻĢāĻŋāϚāĻžāϰāϟāĻŋ āĻ•āĻžāϜ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ Google āĻĨ⧇āϕ⧇ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϰāĻŋāϏ⧋āĻ°ā§āϏ āϞ⧋āĻĄ āĻ•āϰ⧇āĨ¤", - "general": "āϏāĻžāϧāĻžāϰāĻŖ", - "geolocation_instruction_location": "GPS āĻ¸ā§āĻĨāĻžāύāĻžāĻ™ā§āĻ•āϝ⧁āĻ•ā§āϤ āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āĻ•ā§āϞāĻŋāĻ• āĻ•āϰ⧁āύ āĻāϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇, āĻ…āĻĨāĻŦāĻž āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āĻĨ⧇āϕ⧇ āϏāϰāĻžāϏāϰāĻŋ āĻāĻ•āϟāĻŋ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "get_help": "āϏāĻšāĻžāϝāĻŧāϤāĻž āύāĻŋāύ", - "get_people_error": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āϤāĻĨā§āϝ āĻĒ⧇āϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "get_wifiname_error": "Wi-Fi āύāĻžāĻŽ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋāĨ¤ āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰ⧁āύ āϝ⧇ āφāĻĒāύāĻŋ āĻĒā§āϰāϝāĻŧā§‹āϜāύ⧀āϝāĻŧ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāϝāĻŧ⧇āϛ⧇āύ āĻāĻŦāĻ‚ āĻāĻ•āϟāĻŋ Wi-Fi āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āϕ⧇āϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āφāϛ⧇āύ", - "getting_started": "āĻļ⧁āϰ⧁ āĻ•āϰāĻž āϝāĻžāĻ•", - "go_back": "āĻĢāĻŋāϰ⧇ āϝāĻžāύ", - "go_to_folder": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āϝāĻžāύ", - "go_to_search": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇ āϝāĻžāύ", - "gps": "GPS", - "gps_missing": "GPS āύ⧇āχ", - "grant_permission": "āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύ", - "group_albums_by": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύ..āĨ¤", - "group_country": "āĻĻ⧇āĻļ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύ", - "group_no": "āϕ⧋āύ⧋ āĻ—ā§āϰ⧁āĻĒāĻŋāĻ‚ āύ⧇āχ", - "group_owner": "āĻŽāĻžāϞāĻŋāĻ• āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύ", - "group_places_by": "āĻ¸ā§āĻĨāĻžāύ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύ..āĨ¤", - "group_year": "āĻŦāĻ›āϰ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰ⧁āύ", - "haptic_feedback_title": "āĻšā§āϝāĻžāĻĒāϟāĻŋāĻ• āĻĢāĻŋāĻĄāĻŦā§āϝāĻžāĻ•", - "has_quota": "āϕ⧋āϟāĻž āφāϛ⧇", - "hash_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻšā§āϝāĻžāĻļ āĻ•āϰ⧁āύ", - "hashed_assets": "āĻšā§āϝāĻžāĻļ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ", - "hashing": "āĻšā§āϝāĻžāĻļāĻŋāĻ‚ āĻšāĻšā§āϛ⧇", - "header_settings_add_header_tip": "āĻšā§‡āĻĄāĻžāϰ āϝ⧋āĻ— āĻ•āϰ⧁āύ", - "header_settings_field_validator_msg": "āĻŽāĻžāύ āĻ–āĻžāϞāĻŋ āϰāĻžāĻ–āĻž āϝāĻžāĻŦ⧇ āύāĻž", - "header_settings_header_name_input": "āĻšā§‡āĻĄāĻžāϰ⧇āϰ āύāĻžāĻŽ", - "header_settings_header_value_input": "āĻšā§‡āĻĄāĻžāϰ⧇āϰ āĻŽāĻžāύ", - "headers_settings_tile_title": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āĻĒā§āϰāĻ•ā§āϏāĻŋ āĻšā§‡āĻĄāĻžāϰ", - "height": "āωāĻšā§āϚāϤāĻž", - "hi_user": "āĻšāĻžāχ {name} ({email})", - "hide_all_people": "āϏāĻŦ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϞ⧁āĻ•āĻžāύ", - "hide_gallery": "āĻ—ā§āϝāĻžāϞāĻžāϰāĻŋ āϞ⧁āĻ•āĻžāύ", - "hide_named_person": "{name} āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āϞ⧁āĻ•āĻžāύ", - "hide_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϞ⧁āĻ•āĻžāύ", - "hide_person": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āϞ⧁āĻ•āĻžāύ", - "hide_schema": "āĻ¸ā§āĻ•āĻŋāĻŽāĻž āϞ⧁āĻ•āĻžāύ", - "hide_text_recognition": "āĻŸā§‡āĻ•ā§āϏāϟ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āϞ⧁āĻ•āĻžāύ", - "hide_unnamed_people": "āύāĻžāĻŽ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻšāϝāĻŧāύāĻŋ āĻāĻŽāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϞ⧁āĻ•āĻžāύ", - "home_page_building_timeline": "āϟāĻžāχāĻŽāϞāĻžāχāύ āϤ⧈āϰāĻŋ āĻšāĻšā§āϛ⧇", - "host": "āĻšā§‹āĻ¸ā§āϟ", - "hour": "āϘāĻŖā§āϟāĻž", - "hours": "āϘāĻŖā§āϟāĻž", - "id": "ID", - "idle": "āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ", - "image": "āĻ›āĻŦāĻŋ", - "image_alt_text_date": "{date}-āĻ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_1_person": "{person1}-āĻāϰ āϏāĻžāĻĨ⧇ {date}-āĻ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_2_people": "{person1} āĻāĻŦāĻ‚ {person2}-āĻāϰ āϏāĻžāĻĨ⧇ {date}-āĻ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_3_people": "{person1}, {person2}, āĻāĻŦāĻ‚ {person3}-āĻāϰ āϏāĻžāĻĨ⧇ {date}-āĻ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_4_or_more_people": "{person1}, {person2}, āĻāĻŦāĻ‚ āφāϰāĻ“ {additionalCount, number} āϜāύ⧇āϰ āϏāĻžāĻĨ⧇ {date}-āĻ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_place": "{city}, {country}-āĻ {date} āϤāĻžāϰāĻŋāϖ⧇ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_place_1_person": "{city}, {country}-āĻ {person1}-āĻāϰ āϏāĻžāĻĨ⧇ {date} āϤāĻžāϰāĻŋāϖ⧇ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_place_2_people": "{city}, {country}-āĻ {person1} āĻāĻŦāĻ‚ {person2}-āĻāϰ āϏāĻžāĻĨ⧇ {date} āϤāĻžāϰāĻŋāϖ⧇ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_place_3_people": "{city}, {country}-āĻ {person1}, {person2}, āĻāĻŦāĻ‚ {person3}-āĻāϰ āϏāĻžāĻĨ⧇ {date} āϤāĻžāϰāĻŋāϖ⧇ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "image_alt_text_date_place_4_or_more_people": "{city}, {country}-āĻ {person1}, {person2}, āĻāĻŦāĻ‚ āφāϰāĻ“ {additionalCount, number} āϜāύ⧇āϰ āϏāĻžāĻĨ⧇ {date} āϤāĻžāϰāĻŋāϖ⧇ āϤ⧋āϞāĻž {isVideo, select, true {āĻ­āĻŋāĻĄāĻŋāĻ“} other {āĻ›āĻŦāĻŋ}}", - "immich_logo": "Immich āϞ⧋āĻ—ā§‹", - "immich_web_interface": "Immich āĻ“āϝāĻŧ⧇āĻŦ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ", - "import_from_json": "JSON āĻĨ⧇āϕ⧇ āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻ•āϰ⧁āύ", - "import_path": "āχāĻŽāĻĒā§‹āĻ°ā§āϟ āĻĒāĻžāĻĨ", - "in_albums": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡}}", - "in_archive": "āφāĻ°ā§āĻ•āĻžāχāϭ⧇", - "in_year": "{year}-āĻ", - "in_year_selector": "āĻŽāĻ§ā§āϝ⧇", - "include_archived": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "include_shared_albums": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "include_shared_partner_assets": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "individual_share": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻ—āϤ āĻļ⧇āϝāĻŧāĻžāϰ", - "individual_shares": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻ—āϤ āĻļ⧇āϝāĻŧāĻžāϰāϏāĻŽā§‚āĻš", - "info": "āϤāĻĨā§āϝ", - "integrity_checks": "āχāĻ¨ā§āϟāĻŋāĻ—ā§āϰāĻŋāϟāĻŋ āĻšā§‡āĻ•", - "interval": { - "day_at_onepm": "āĻĒā§āϰāϤāĻŋāĻĻāĻŋāύ āĻĻ⧁āĻĒ⧁āϰ ā§§āϟāĻžāϝāĻŧ", - "hours": "āĻĒā§āϰāϤāĻŋ {hours, plural, one {āϘāĻŖā§āϟāĻž} other {{hours, number} āϘāĻŖā§āϟāĻž}}", - "night_at_midnight": "āĻĒā§āϰāϤāĻŋ āϰāĻžāϤ⧇ āĻŽāĻ§ā§āϝāϰāĻžāϤ⧇", - "night_at_twoam": "āĻĒā§āϰāϤāĻŋ āϰāĻžāϤ⧇ ⧍āϟāĻžāϝāĻŧ" - }, - "invalid_date": "āĻ…āĻŦ⧈āϧ āϤāĻžāϰāĻŋāĻ–", - "invalid_date_format": "āĻ…āĻŦ⧈āϧ āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĢāϰāĻŽā§āϝāĻžāϟ", - "invite_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āφāĻŽāĻ¨ā§āĻ¤ā§āϰāĻŖ āϜāĻžāύāĻžāύ", - "invite_to_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āφāĻŽāĻ¨ā§āĻ¤ā§āϰāĻŖ āϜāĻžāύāĻžāύ", - "iso": "ISO", - "items_count": "{count, plural, one {#āϟāĻŋ āφāχāĻŸā§‡āĻŽ} other {#āϟāĻŋ āφāχāĻŸā§‡āĻŽ}}", - "jobs": "āϜāĻŦ", - "keep": "āϰāĻžāϖ⧁āύ", - "keep_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϰāĻžāϖ⧁āύ", - "keep_albums_count": "{count}āϟāĻŋ {count, plural, one {āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ} other {āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ}} āϰāĻžāĻ–āĻž āĻšāĻšā§āϛ⧇", - "keep_all": "āϏāĻŦ āϰāĻžāϖ⧁āύ", - "keep_description": "āĻ¸ā§āĻĒ⧇āϏ āĻ–āĻžāϞāĻŋ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϕ⧀ āĻĨāĻžāĻ•āĻŦ⧇ āϤāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤", - "keep_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āϰāĻžāϖ⧁āύ", - "keep_on_device": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϰāĻžāϖ⧁āύ", - "keep_on_device_hint": "āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ⧇ āϰāĻžāĻ–āĻžāϰ āϜāĻ¨ā§āϝ āφāχāĻŸā§‡āĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "keep_this_delete_others": "āĻāϟāĻŋ āϰāĻžāϖ⧁āύ, āĻŦāĻžāĻ•āĻŋāϗ⧁āϞ⧋ āĻŽā§āϛ⧁āύ", - "keeping": "āϰāĻžāĻ–āĻž āĻšāĻšā§āϛ⧇: {items}", - "kept_this_deleted_others": "āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ āϰāĻžāĻ–āĻž āĻšāϝāĻŧ⧇āϛ⧇ āĻāĻŦāĻ‚ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "keyboard_shortcuts": "āϕ⧀āĻŦā§‹āĻ°ā§āĻĄ āĻļāĻ°ā§āϟāĻ•āĻžāϟ", - "language": "āĻ­āĻžāώāĻž", - "language_no_results_subtitle": "āφāĻĒāύāĻžāϰ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇āϰ āĻļāĻŦā§āĻĻāϟāĻŋ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧇ āĻĻ⧇āϖ⧁āύ", - "language_no_results_title": "āϕ⧋āύ⧋ āĻ­āĻžāώāĻž āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "language_search_hint": "āĻ­āĻžāώāĻž āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "language_setting_description": "āφāĻĒāύāĻžāϰ āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ āĻ­āĻžāώāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "large_files": "āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ", - "last": "āĻļ⧇āώ", - "last_months": "{count, plural, one {āĻ—āϤ āĻŽāĻžāϏ} other {āĻ—āϤ #āĻŽāĻžāϏ}}", - "last_seen": "āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻĻ⧇āĻ–āĻž", - "latest_version": "āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻ­āĻžāĻ°ā§āϏāύ", - "latitude": "āĻ…āĻ•ā§āώāĻžāĻ‚āĻļ", - "leave": "āĻ¤ā§āϝāĻžāĻ— āĻ•āϰ⧁āύ", - "leave_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ¤ā§āϝāĻžāĻ— āĻ•āϰ⧁āύ", - "lens_model": "āϞ⧇āĻ¨ā§āϏ āĻŽāĻĄā§‡āϞ", - "less": "āĻ•āĻŽ", - "let_others_respond": "āĻ…āĻ¨ā§āϝāĻĻ⧇āϰ āϏāĻžāĻĄāĻŧāĻž āĻĻāĻŋāϤ⧇ āĻĻāĻŋāύ", - "level": "āĻ¸ā§āϤāϰ", - "library": "āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ", - "library_add_folder": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āϝ⧋āĻ— āĻ•āϰ⧁āύ", - "library_edit_folder": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύ", - "library_page_sort_asset_count": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϏāĻ‚āĻ–ā§āϝāĻž", - "library_page_sort_created": "āϤ⧈āϰāĻŋāϰ āϤāĻžāϰāĻŋāĻ–", - "library_page_sort_last_modified": "āϏāĻ°ā§āĻŦāĻļ⧇āώ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ", - "library_page_sort_title": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āĻļāĻŋāϰ⧋āύāĻžāĻŽ", - "licenses": "āϞāĻžāχāϏ⧇āĻ¨ā§āϏ", - "light": "āϞāĻžāχāϟ", - "light_theme": "āϞāĻžāχāϟ āĻĨāĻŋāĻŽā§‡ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "like": "āϞāĻžāχāĻ•", - "like_deleted": "āϞāĻžāχāĻ• āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "link": "āϞāĻŋāĻ‚āĻ•", - "link_motion_video": "āĻŽā§‹āĻļāύ āĻ­āĻŋāĻĄāĻŋāĻ“ āϞāĻŋāĻ‚āĻ• āĻ•āϰ⧁āύ", - "link_to_docs": "āφāϰāĻ“ āϤāĻĨā§āϝ⧇āϰ āϜāĻ¨ā§āϝ, āĻĄāϕ⧁āĻŽā§‡āĻ¨ā§āĻŸā§‡āĻļāύ āĻĻ⧇āϖ⧁āύāĨ¤", - "link_to_oauth": "OAuth-āĻ āϞāĻŋāĻ‚āĻ• āĻ•āϰ⧁āύ", - "linked_oauth_account": "āϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ", - "list": "āϤāĻžāϞāĻŋāĻ•āĻž", - "live": "āϞāĻžāχāĻ­", - "load_more": "āφāϰāĻ“ āϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "loading": "āϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "loading_search_results_failed": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇āϰ āĻĢāϞāĻžāĻĢāϞ āϞ⧋āĻĄ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇", - "local": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ", - "local_asset_cast_failed": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ āĻāĻŽāύ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ•āĻžāĻ¸ā§āϟ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "local_assets": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ…ā§āϝāĻžāϏ⧇āϟ", - "local_id": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ ID", - "local_media_summary": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āϏāĻžāϰāϏāĻ‚āĻ•ā§āώ⧇āĻĒ", - "local_network": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ•", - "local_network_sheet_info": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ Wi-Fi āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻ…ā§āϝāĻžāĻĒāϟāĻŋ āĻāχ URL-āĻāϰ āĻŽāĻžāĻ§ā§āϝāĻŽā§‡ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧁āĻ•ā§āϤ āĻšāĻŦ⧇", - "location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ", - "location_permission": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ", - "location_permission_content": "āĻ…āĻŸā§‹-āϏ⧁āχāϚāĻŋāĻ‚ āĻĢāĻŋāϚāĻžāϰ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ, Immich-āĻāϰ āύāĻŋāĻ°ā§āϭ⧁āϞ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĒā§āϰāϝāĻŧā§‹āϜāύ āϝāĻžāϤ⧇ āĻāϟāĻŋ āĻŦāĻ°ā§āϤāĻŽāĻžāύ Wi-Fi āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āϕ⧇āϰ āύāĻžāĻŽ āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇", - "location_picker_choose_on_map": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ⧇ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "location_picker_latitude_error": "āĻāĻ•āϟāĻŋ āĻŦ⧈āϧ āĻ…āĻ•ā§āώāĻžāĻ‚āĻļ āϞāĻŋāϖ⧁āύ", - "location_picker_latitude_hint": "āĻāĻ–āĻžāύ⧇ āφāĻĒāύāĻžāϰ āĻ…āĻ•ā§āώāĻžāĻ‚āĻļ āϞāĻŋāϖ⧁āύ", - "location_picker_longitude_error": "āĻāĻ•āϟāĻŋ āĻŦ⧈āϧ āĻĻā§āϰāĻžāϘāĻŋāĻŽāĻžāĻ‚āĻļ āϞāĻŋāϖ⧁āύ", - "location_picker_longitude_hint": "āĻāĻ–āĻžāύ⧇ āφāĻĒāύāĻžāϰ āĻĻā§āϰāĻžāϘāĻŋāĻŽāĻžāĻ‚āĻļ āϞāĻŋāϖ⧁āύ", - "lock": "āϞāĻ•", - "locked_folder": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ", - "log_detail_title": "āϞāĻ— āĻŦāĻŋāĻ¸ā§āϤāĻžāϰāĻŋāϤ", - "log_out": "āϞāĻ— āφāωāϟ", - "log_out_all_devices": "āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰ⧁āύ", - "logged_in_as": "{user} āĻšāĻŋāϏ⧇āĻŦ⧇ āϞāĻ— āχāύ āĻ•āϰāĻž āφāϛ⧇", - "logged_out_all_devices": "āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "logged_out_device": "āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "login": "āϞāĻ—āχāύ", - "login_disabled": "āϞāĻ—āχāύ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "login_form_api_exception": "API āĻŦā§āϝāϤāĻŋāĻ•ā§āϰāĻŽ āϘāĻŸā§‡āϛ⧇āĨ¤ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL āϝāĻžāϚāĻžāχ āĻ•āϰ⧇ āφāĻŦāĻžāϰ āĻšā§‡āĻˇā§āϟāĻž āĻ•āϰ⧁āύāĨ¤", - "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻāĻ¨ā§āĻĄāĻĒāϝāĻŧ⧇āĻ¨ā§āϟ URL", - "login_form_err_invalid_email": "āĻ…āĻŦ⧈āϧ āχāĻŽā§‡āχāϞ", - "login_form_err_invalid_url": "āĻ…āĻŦ⧈āϧ URL", - "login_form_err_leading_whitespace": "āĻļ⧁āϰ⧁āϤ⧇ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤ āĻ¸ā§āĻĒ⧇āϏ", - "login_form_err_trailing_whitespace": "āĻļ⧇āώ⧇ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤ āĻ¸ā§āĻĒ⧇āϏ", - "login_form_failed_get_oauth_server_config": "OAuth āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āϞāĻ—āχāύ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇, āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύ", - "login_form_failed_get_oauth_server_disable": "āĻāχ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇ OAuth āĻĢāĻŋāϚāĻžāϰ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ", - "login_form_failed_login": "āϞāĻ—āχāύ āĻ•āϰāϤ⧇ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇, āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL, āχāĻŽā§‡āχāϞ āĻāĻŦāĻ‚ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύ", - "login_form_handshake_exception": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āĻāĻ•āϟāĻŋ āĻšā§āϝāĻžāĻ¨ā§āĻĄāĻļ⧇āĻ• āĻŦā§āϝāϤāĻŋāĻ•ā§āϰāĻŽ āϘāĻŸā§‡āϛ⧇āĨ¤ āφāĻĒāύāĻŋ āϏ⧇āϞāĻĢ-āϏāĻžāχāύāĻĄ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϞ⧇ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āϏ⧇āϞāĻĢ-āϏāĻžāχāύāĻĄ āϏāĻžāĻ°ā§āϟāĻŋāĻĢāĻŋāϕ⧇āϟ āϏāĻžāĻĒā§‹āĻ°ā§āϟ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύāĨ¤", - "login_form_password_hint": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "login_form_server_empty": "āĻāĻ•āϟāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL āϞāĻŋāϖ⧁āύ", - "login_form_server_error": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϏāĻ‚āϝ⧋āĻ— āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "login_has_been_disabled": "āϞāĻ—āχāύ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "login_password_changed_error": "āφāĻĒāύāĻžāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāϤ⧇ āĻāĻ•āϟāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "login_password_changed_success": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", - "logout_all_device_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏāĻŦ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "logout_this_device_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĨ⧇āϕ⧇ āϞāĻ— āφāωāϟ āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "logs": "āϞāĻ—āϏāĻŽā§‚āĻš", - "longitude": "āĻĻā§āϰāĻžāϘāĻŋāĻŽāĻžāĻ‚āĻļ", - "look": "āĻĻ⧇āϖ⧁āύ", - "loop_videos": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϞ⧁āĻĒ āĻ•āϰ⧁āύ", - "loop_videos_description": "āĻĄāĻŋāĻŸā§‡āχāϞ āĻ­āĻŋāωāϝāĻŧāĻžāϰ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āϞ⧁āĻĒ āĻ•āϰāϤ⧇ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύāĨ¤", - "main_branch_warning": "āφāĻĒāύāĻŋ āĻāĻ•āϟāĻŋ āĻĄā§‡āϭ⧇āϞāĻĒāĻŽā§‡āĻ¨ā§āϟ āĻ­āĻžāĻ°ā§āϏāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϛ⧇āύ; āφāĻŽāϰāĻž āĻĻ⧃āĻĸāĻŧāĻ­āĻžāĻŦ⧇ āĻāĻ•āϟāĻŋ āϰāĻŋāϞāĻŋāϜ āĻ­āĻžāĻ°ā§āϏāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāĻžāϰ āĻĒāϰāĻžāĻŽāĻ°ā§āĻļ āĻĻāĻŋāĻšā§āĻ›āĻŋ!", - "main_menu": "āĻĒā§āϰāϧāĻžāύ āĻŽā§‡āύ⧁", - "maintenance_action_restore": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "maintenance_description": "Immich-āϕ⧇ āĻŽā§‡āχāύāĻŸā§‡āĻ¨ā§āϝāĻžāĻ¨ā§āϏ āĻŽā§‹āĻĄā§‡ āϰāĻžāĻ–āĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤", - "maintenance_end": "āĻŽā§‡āχāύāĻŸā§‡āĻ¨ā§āϝāĻžāĻ¨ā§āϏ āĻŽā§‹āĻĄ āĻļ⧇āώ āĻ•āϰ⧁āύ", - "maintenance_logged_in_as": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ⧇ {user} āĻšāĻŋāϏ⧇āĻŦ⧇ āϞāĻ— āχāύ āĻ•āϰāĻž āφāϛ⧇", - "maintenance_restore_from_backup": "āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "maintenance_restore_library": "āφāĻĒāύāĻžāϰ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "maintenance_restore_library_confirm": "āĻāϟāĻŋ āϏāĻ āĻŋāĻ• āĻŽāύ⧇ āĻšāϞ⧇, āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āϚāĻžāϞāĻŋāϝāĻŧ⧇ āϝāĻžāύ!", - "maintenance_restore_library_description": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "maintenance_restore_library_folder_has_files": "{folder}-āĻ {count}āϟāĻŋ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āφāϛ⧇", - "maintenance_restore_library_folder_no_files": "{folder}-āĻ āĻĢāĻžāχāϞ āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ!", - "maintenance_restore_library_folder_pass": "āĻĒāĻ āύāϝ⧋āĻ—ā§āϝ āĻāĻŦāĻ‚ āϞ⧇āĻ–āύāϝ⧋āĻ—ā§āϝ", - "maintenance_restore_library_folder_read_fail": "āĻĒāĻ āύāϝ⧋āĻ—ā§āϝ āύāϝāĻŧ", - "maintenance_restore_library_folder_write_fail": "āϞ⧇āĻ–āύāϝ⧋āĻ—ā§āϝ āύāϝāĻŧ", - "maintenance_restore_library_hint_missing_files": "āφāĻĒāύāĻžāϰ āϗ⧁āϰ⧁āĻ¤ā§āĻŦāĻĒā§‚āĻ°ā§āĻŖ āĻĢāĻžāχāϞ āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇", - "maintenance_restore_library_hint_regenerate_later": "āφāĻĒāύāĻŋ āĻĒāϰāĻŦāĻ°ā§āϤ⧀āϤ⧇ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āĻ—āĻŋāϝāĻŧ⧇ āĻāϗ⧁āϞ⧋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ", - "maintenance_restore_library_hint_storage_template_missing_files": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϛ⧇āύ? āφāĻĒāύāĻžāϰ āĻĢāĻžāχāϞ āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ āĻĨāĻžāĻ•āϤ⧇ āĻĒāĻžāϰ⧇", - "maintenance_restore_library_loading": "āχāĻ¨ā§āϟāĻŋāĻ—ā§āϰāĻŋāϟāĻŋ āĻšā§‡āĻ• āĻāĻŦāĻ‚ āĻšāĻŋāωāϰāĻŋāĻ¸ā§āϟāĻŋāĻ• āϞ⧋āĻĄ āĻšāĻšā§āϛ⧇â€Ļ", - "maintenance_task_backup": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻĄā§‡āϟāĻžāĻŦ⧇āϏ⧇āϰ āĻāĻ•āϟāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "maintenance_task_migrations": "āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āĻŽāĻžāχāĻ—ā§āϰ⧇āĻļāύ āϚāĻžāϞāĻžāύ⧋ āĻšāĻšā§āϛ⧇â€Ļ", - "maintenance_task_restore": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "maintenance_task_rollback": "āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇, āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻĒāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡ āĻĢāĻŋāϰāĻŋāϝāĻŧ⧇ āύ⧇āĻ“āϝāĻŧāĻž āĻšāĻšā§āϛ⧇â€Ļ", - "maintenance_title": "āϏāĻžāĻŽāϝāĻŧāĻŋāĻ•āĻ­āĻžāĻŦ⧇ āĻ…āύ⧁āĻĒāϞāĻŦā§āϧ", - "make": "āύāĻŋāĻ°ā§āĻŽāĻžāϤāĻž", - "manage_geolocation": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_media_access_rationale": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻāĻŦāĻ‚ āϏ⧇āĻ–āĻžāύ āĻĨ⧇āϕ⧇ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻžāϰ āϏāĻ āĻŋāĻ• āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻžāĻĒāύāĻžāϰ āϜāĻ¨ā§āϝ āĻāχ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĒā§āϰāϝāĻŧā§‹āϜāύāĨ¤", - "manage_media_access_settings": "āϏ⧇āϟāĻŋāĻ‚āϏ āϖ⧁āϞ⧁āύ", - "manage_media_access_subtitle": "Immich āĻ…ā§āϝāĻžāĻĒāϕ⧇ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĢāĻžāχāϞ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ“ āĻ¸ā§āĻĨāĻžāύāĻžāĻ¨ā§āϤāϰ āĻ•āϰāĻžāϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύāĨ¤", - "manage_media_access_title": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻžāĻĒāύāĻž āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ", - "manage_sharing_with_partners": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰāĻĻ⧇āϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰāĻŋāĻ‚ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_the_app_settings": "āĻ…ā§āϝāĻžāĻĒ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_your_account": "āφāĻĒāύāĻžāϰ āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_your_api_keys": "āφāĻĒāύāĻžāϰ API āϕ⧀ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_your_devices": "āφāĻĒāύāĻžāϰ āϞāĻ—-āχāύ āĻ•āϰāĻž āĻĄāĻŋāĻ­āĻžāχāϏāϗ⧁āϞ⧋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "manage_your_oauth_connection": "āφāĻĒāύāĻžāϰ OAuth āϏāĻ‚āϝ⧋āĻ— āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "map": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ", - "map_cannot_get_user_location": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "map_location_picker_page_use_location": "āĻāχ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "map_location_service_disabled_content": "āφāĻĒāύāĻžāϰ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āĻ–āĻžāϤ⧇ āϞ⧋āϕ⧇āĻļāύ āϏāĻžāĻ°ā§āĻ­āĻŋāϏ āϚāĻžāϞ⧁ āĻ•āϰāĻž āĻĒā§āϰāϝāĻŧā§‹āϜāύāĨ¤ āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāĻ–āύ āĻāϟāĻŋ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇ āϚāĻžāύ?", - "map_location_service_disabled_title": "āϞ⧋āϕ⧇āĻļāύ āϏāĻžāĻ°ā§āĻ­āĻŋāϏ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ", - "map_marker_for_image": "{city}, {country}-āĻ āϤ⧋āϞāĻž āĻ›āĻŦāĻŋāϰ āϜāĻ¨ā§āϝ āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āĻŽāĻžāĻ°ā§āĻ•āĻžāϰ", - "map_marker_with_image": "āĻ›āĻŦāĻŋāϏāĻš āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āĻŽāĻžāĻ°ā§āĻ•āĻžāϰ", - "map_no_location_permission_content": "āφāĻĒāύāĻžāϰ āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĨ⧇āϕ⧇ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āĻ–āĻžāϤ⧇ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĒā§āϰāϝāĻŧā§‹āϜāύāĨ¤ āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāĻ–āύ āĻāϟāĻŋ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāϤ⧇ āϚāĻžāύ?", - "map_no_location_permission_title": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĒā§āϰāĻ¤ā§āϝāĻžāĻ–ā§āϝāĻžāϤ", - "map_settings": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āϏ⧇āϟāĻŋāĻ‚āϏ", - "map_settings_dark_mode": "āĻĄāĻžāĻ°ā§āĻ• āĻŽā§‹āĻĄ", - "map_settings_date_range_option_day": "āĻ—āϤ ⧍ā§Ē āϘāĻŖā§āϟāĻž", - "map_settings_date_range_option_days": "āĻ—āϤ {days} āĻĻāĻŋāύ", - "map_settings_date_range_option_year": "āĻ—āϤ āĻŦāĻ›āϰ", - "map_settings_date_range_option_years": "āĻ—āϤ {years} āĻŦāĻ›āϰ", - "map_settings_include_show_archived": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "map_settings_include_show_partners": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āĻ…āĻ¨ā§āϤāĻ°ā§āϭ⧁āĻ•ā§āϤ āĻ•āϰ⧁āύ", - "map_settings_only_show_favorites": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĻ⧇āĻ–āĻžāύ", - "map_settings_theme_settings": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āĻĨāĻŋāĻŽ", - "mark_all_as_read": "āϏāĻŦ āĻĒāĻ āĻŋāϤ āĻšāĻŋāϏ⧇āĻŦ⧇ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰ⧁āύ", - "marked_all_as_read": "āϏāĻŦ āĻĒāĻ āĻŋāϤ āĻšāĻŋāϏ⧇āĻŦ⧇ āϚāĻŋāĻšā§āύāĻŋāϤ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "matches": "āĻŽāĻŋāϞ", - "matching_assets": "āĻŽāĻŋāϞ⧇ āϝāĻžāĻ“āϝāĻŧāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ", - "media_chrome": { - "auto": "āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧ", - "captions": "āĻ•ā§āϝāĻžāĻĒāĻļāύ", - "captions_off": "āĻŦāĻ¨ā§āϧ", - "closed_captions": "āĻ•ā§āϞ⧋āϜāĻĄ āĻ•ā§āϝāĻžāĻĒāĻļāύ", - "decode_error": "āĻĄāĻŋāϕ⧋āĻĄ āĻ¤ā§āϰ⧁āϟāĻŋ", - "disable_captions": "āĻ•ā§āϝāĻžāĻĒāĻļāύ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ", - "enable_captions": "āĻ•ā§āϝāĻžāĻĒāĻļāύ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "enter_fullscreen_mode": "āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻŽā§‹āĻĄā§‡ āĻĒā§āϰāĻŦ⧇āĻļ āĻ•āϰ⧁āύ", - "exit_fullscreen_mode": "āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻŽā§‹āĻĄ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰ āĻšāύ", - "loop": "āϞ⧁āĻĒ", - "media_error_description": "āĻāĻ•āϟāĻŋ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻ¤ā§āϰ⧁āϟāĻŋāϰ āĻ•āĻžāϰāϪ⧇ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻŦāĻ¨ā§āϧ āĻšāϝāĻŧ⧇ āϗ⧇āϛ⧇āĨ¤ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϟāĻŋ āύāĻˇā§āϟ āĻšāϤ⧇ āĻĒāĻžāϰ⧇ āĻ…āĻĨāĻŦāĻž āφāĻĒāύāĻžāϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ āĻāχ āĻĢāϰāĻŽā§āϝāĻžāϟ āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇ āύāĻžāĨ¤", - "media_loading": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "mute": "āĻŽāĻŋāωāϟ", - "network_error": "āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻ¤ā§āϰ⧁āϟāĻŋ", - "network_error_description": "āĻāĻ•āϟāĻŋ āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻ¤ā§āϰ⧁āϟāĻŋāϰ āĻ•āĻžāϰāϪ⧇ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āĻĄāĻžāωāύāϞ⧋āĻĄ āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇āĨ¤", - "not_supported_error": "āϏ⧋āĻ°ā§āϏ āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āύāϝāĻŧ", - "playback_rate": "āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϰ⧇āϟ", - "playback_rate_current": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϰ⧇āϟ", - "playback_rate_value": "āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϰ⧇āϟ {playbackRate}", - "playback_time": "āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āϏāĻŽāϝāĻŧ", - "quality": "āĻŽāĻžāύ", - "second": "āϏ⧇āϕ⧇āĻ¨ā§āĻĄ", - "seconds": "āϏ⧇āϕ⧇āĻ¨ā§āĻĄ", - "time_value_of_total_time": "{totalTime}-āĻāϰ āĻŽāĻ§ā§āϝ⧇ {currentTime}", - "time_value_remaining": "{time} āĻŦāĻžāĻ•āĻŋ", - "unmute": "āφāύāĻŽāĻŋāωāϟ", - "unsupported_error_description": "āĻāĻ•āϟāĻŋ āĻ…āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āĻ¤ā§āϰ⧁āϟāĻŋ āϘāĻŸā§‡āϛ⧇āĨ¤ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻŦāĻž āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇, āĻ…āĻĨāĻŦāĻž āφāĻĒāύāĻžāϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ āĻāχ āĻĢāϰāĻŽā§āϝāĻžāϟ āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧇ āύāĻžāĨ¤", - "video_not_loaded_unknown_time": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϞ⧋āĻĄ āĻšāϝāĻŧāύāĻŋ, āϏāĻŽāϝāĻŧ āĻ…āϜāĻžāύāĻžāĨ¤", - "video_player": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĒā§āϞ⧇āϝāĻŧāĻžāϰ", - "volume": "āĻ­āϞāĻŋāωāĻŽ" - }, - "media_type": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϰ āϧāϰāύ", - "memories": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ", - "memories_all_caught_up": "āϏāĻŦ āĻĻ⧇āĻ–āĻž āĻšāϝāĻŧ⧇ āϗ⧇āϛ⧇", - "memories_check_back_tomorrow": "āφāϰāĻ“ āĻ¸ā§āĻŽā§ƒāϤāĻŋāϰ āϜāĻ¨ā§āϝ āφāĻ—āĻžāĻŽā§€āĻ•āĻžāϞ āφāĻŦāĻžāϰ āĻĻ⧇āϖ⧁āύ", - "memories_setting_description": "āφāĻĒāύāĻžāϰ āĻ¸ā§āĻŽā§ƒāϤāĻŋāϤ⧇ āϕ⧀ āĻĻ⧇āĻ–āĻŦ⧇āύ āϤāĻž āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "memories_start_over": "āύāϤ⧁āύ āĻ•āϰ⧇ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", - "memories_swipe_to_close": "āĻŦāĻ¨ā§āϧ āĻ•āϰāϤ⧇ āωāĻĒāϰ⧇ āϏ⧋āϝāĻŧāĻžāχāĻĒ āĻ•āϰ⧁āύ", - "memory": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ", - "memory_lane_title": "āĻŽā§‡āĻŽā§‹āϰāĻŋ āϞ⧇āύ {title}", - "menu": "āĻŽā§‡āύ⧁", - "merge": "āĻāĻ•āĻ¤ā§āϰ āĻ•āϰ⧁āύ", - "merge_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻāĻ•āĻ¤ā§āϰ āĻ•āϰ⧁āύ", - "merge_people_limit": "āφāĻĒāύāĻŋ āĻāĻ•āĻŦāĻžāϰ⧇ āϏāĻ°ā§āĻŦā§‹āĻšā§āϚ ā§ĢāϟāĻŋ āĻŽā§āĻ– āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ", - "merge_people_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāχ āĻŦā§āϝāĻ•ā§āϤāĻŋāĻĻ⧇āϰ āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāϤ⧇ āϚāĻžāύ? āĻāχ āĻ•āĻžāϜāϟāĻŋ āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢ⧇āϰāĻžāύ⧋ āϝāĻžāĻŦ⧇ āύāĻžāĨ¤", - "merge_people_successfully": "āĻŦā§āϝāĻ•ā§āϤāĻŋāϰāĻž āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻāĻ•āĻ¤ā§āϰ āĻšāϝāĻŧ⧇āϛ⧇", - "merged_people_count": "{count, plural, one {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ} other {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ}} āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "minFaces": "āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āĻŽā§āĻ–", - "minFaces_description": "āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧇āϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻŦā§€āĻ•ā§ƒāϤ āĻŽā§āϖ⧇āϰ āϏāĻ°ā§āĻŦāύāĻŋāĻŽā§āύ āϏāĻ‚āĻ–ā§āϝāĻž", - "minimize": "āϛ⧋āϟ āĻ•āϰ⧁āύ", - "minute": "āĻŽāĻŋāύāĻŋāϟ", - "minutes": "āĻŽāĻŋāύāĻŋāϟ", - "missing": "āĻ…āύ⧁āĻĒāĻ¸ā§āĻĨāĻŋāϤ", - "mobile_app": "āĻŽā§‹āĻŦāĻžāχāϞ āĻ…ā§āϝāĻžāĻĒ", - "mobile_app_download_onboarding_note": "āύāĻŋāĻšā§‡āϰ āĻ…āĻĒāĻļāύāϗ⧁āϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āϏāĻšāϝ⧋āĻ—ā§€ āĻŽā§‹āĻŦāĻžāχāϞ āĻ…ā§āϝāĻžāĻĒāϟāĻŋ āĻĄāĻžāωāύāϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "model": "āĻŽāĻĄā§‡āϞ", - "modify_date": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϤāĻžāϰāĻŋāĻ–", - "month": "āĻŽāĻžāϏ", - "more": "āφāϰāĻ“", - "motion": "āĻŽā§‹āĻļāύ", - "move": "āϏāϰāĻžāύ", - "move_off_locked_folder": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "move_to": "āĻāĻ–āĻžāύ⧇ āϏāϰāĻžāύ", - "move_to_device_trash": "āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ", - "move_to_lock_folder_action_prompt": "{count}āϟāĻŋ āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "move_to_locked_folder": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āϏāϰāĻžāύ", - "move_to_locked_folder_confirmation": "āĻāχ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āϏāĻŦ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāĻŦ⧇ āĻāĻŦāĻ‚ āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨ⧇āϕ⧇ āĻĻ⧇āĻ–āĻž āϝāĻžāĻŦ⧇", - "moved_to_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "mute_memories": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āĻŽāĻŋāωāϟ āĻ•āϰ⧁āύ", - "my_albums": "āφāĻŽāĻžāϰ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", - "my_immich_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āĻĒ⧇āϜāϟāĻŋ My Immich āϞāĻŋāĻ‚āĻ• āĻšāĻŋāϏ⧇āĻŦ⧇ āĻ•āĻĒāĻŋ āĻ•āϰ⧁āύ", - "my_immich_title": "My Immich āϞāĻŋāĻ‚āĻ•", - "name": "āύāĻžāĻŽ", - "name_or_nickname": "āύāĻžāĻŽ āĻŦāĻž āĻĄāĻžāĻ•āύāĻžāĻŽ", - "name_required": "āύāĻžāĻŽ āφāĻŦāĻļā§āϝāĻ•", - "navigate": "āύ⧇āĻ­āĻŋāϗ⧇āϟ āĻ•āϰ⧁āύ", - "navigate_to_time": "āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āϏāĻŽāϝāĻŧ⧇ āϝāĻžāύ", - "network_requirement_photos_upload": "āĻ›āĻŦāĻŋ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāϤ⧇ āϏ⧇āϞ⧁āϞāĻžāϰ āĻĄā§‡āϟāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "network_requirement_videos_upload": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŦā§āϝāĻžāĻ•āφāĻĒ āĻ•āϰāϤ⧇ āϏ⧇āϞ⧁āϞāĻžāϰ āĻĄā§‡āϟāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "network_requirements": "āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻĒā§āϰāϝāĻŧā§‹āϜāύ⧀āϝāĻŧāϤāĻž", - "network_requirements_updated": "āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻĒā§āϰāϝāĻŧā§‹āϜāύ⧀āϝāĻŧāϤāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāĻŋāϤ āĻšāϝāĻŧ⧇āϛ⧇, āĻŦā§āϝāĻžāĻ•āφāĻĒ āϏāĻžāϰāĻŋ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "networking_settings": "āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻŋāĻ‚", - "networking_subtitle": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻāĻ¨ā§āĻĄāĻĒāϝāĻŧ⧇āĻ¨ā§āϟ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "never": "āĻ•āĻ–āύ⧋ āύāĻž", - "new_album": "āύāϤ⧁āύ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", - "new_api_key": "āύāϤ⧁āύ API āϕ⧀", - "new_feature": "āύāϤ⧁āύ āĻĢāĻŋāϚāĻžāϰ", - "new_password": "āύāϤ⧁āύ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "new_person": "āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋ", - "new_pin_code": "āύāϤ⧁āύ āĻĒāĻŋāύ āϕ⧋āĻĄ", - "new_pin_code_subtitle": "āĻāϟāĻŋ āφāĻĒāύāĻžāϰ āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āĻĒā§āϰāĻĨāĻŽāĻŦāĻžāϰ āĻĒā§āϰāĻŦ⧇āĻļāĨ¤ āĻāχ āĻĒ⧇āϜ āύāĻŋāϰāĻžāĻĒāĻĻ⧇ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻāĻ•āϟāĻŋ āĻĒāĻŋāύ āϕ⧋āĻĄ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "new_update": "āύāϤ⧁āύ āφāĻĒāĻĄā§‡āϟ", - "new_user_created": "āύāϤ⧁āύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "new_version_available": "āύāϤ⧁āύ āĻ­āĻžāĻ°ā§āϏāύ āωāĻĒāϞāĻŦā§āϧ", - "newest_first": "āϏāĻ°ā§āĻŦāĻĒā§āϰāĻĨāĻŽ āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ•", - "next": "āĻĒāϰāĻŦāĻ°ā§āϤ⧀", - "next_memory": "āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻ¸ā§āĻŽā§ƒāϤāĻŋ", - "no": "āύāĻž", - "no_albums_found": "āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_albums_message": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ—ā§‹āĻ›āĻžāϤ⧇ āĻāĻ•āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "no_albums_with_name_yet": "āĻŽāύ⧇ āĻšāĻšā§āϛ⧇ āφāĻĒāύāĻžāϰ āĻāχ āύāĻžāĻŽā§‡ āĻāĻ–āύ⧋ āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āύ⧇āχāĨ¤", - "no_albums_yet": "āĻŽāύ⧇ āĻšāĻšā§āϛ⧇ āφāĻĒāύāĻžāϰ āĻāĻ–āύ⧋ āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āύ⧇āχāĨ¤", - "no_archived_assets_message": "āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āϞ⧁āĻ•āĻžāϤ⧇ āϏ⧇āϗ⧁āϞ⧋ āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰ⧁āύ āϝāĻžāϤ⧇ āϏ⧇āϗ⧁āϞ⧋ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋāϰ āĻ­āĻŋāω āĻĨ⧇āϕ⧇ āϞ⧁āĻ•āĻžāύ⧋ āĻĨāĻžāϕ⧇", - "no_assets_message": "āφāĻĒāύāĻžāϰ āĻĒā§āϰāĻĨāĻŽ āĻ›āĻŦāĻŋ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻ•ā§āϞāĻŋāĻ• āĻ•āϰ⧁āύ", - "no_assets_to_show": "āĻĻ⧇āĻ–āĻžāύ⧋āϰ āĻŽāϤ⧋ āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϏ⧇āϟ āύ⧇āχ", - "no_cast_devices_found": "āϕ⧋āύ⧋ āĻ•āĻžāĻ¸ā§āϟ āĻĄāĻŋāĻ­āĻžāχāϏ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_checksum_local": "āϕ⧋āύ⧋ āĻšā§‡āĻ•āϏāĻžāĻŽ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ - āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ…ā§āϝāĻžāϏ⧇āϟ āφāύāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_checksum_remote": "āϕ⧋āύ⧋ āĻšā§‡āĻ•āϏāĻžāĻŽ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ - āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āϟ āφāύāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_devices": "āϕ⧋āύ⧋ āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āĻĄāĻŋāĻ­āĻžāχāϏ āύ⧇āχ", - "no_duplicates_found": "āϕ⧋āύ⧋ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋāĨ¤", - "no_exif_info_available": "āϕ⧋āύ⧋ Exif āϤāĻĨā§āϝ āωāĻĒāϞāĻŦā§āϧ āύ⧇āχ", - "no_explore_results_message": "āφāĻĒāύāĻžāϰ āϏāĻ‚āĻ—ā§āϰāĻš āĻ…āĻ¨ā§āĻŦ⧇āώāĻŖ āĻ•āϰāϤ⧇ āφāϰāĻ“ āĻ›āĻŦāĻŋ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύāĨ¤", - "no_favorites_message": "āφāĻĒāύāĻžāϰ āϏ⧇āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĻā§āϰ⧁āϤ āϖ⧁āρāĻœā§‡ āĻĒ⧇āϤ⧇ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āϝ⧋āĻ— āĻ•āϰ⧁āύ", - "no_libraries_message": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĻ⧇āĻ–āϤ⧇ āĻāĻ•āϟāĻŋ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "no_local_assets_found": "āĻāχ āĻšā§‡āĻ•āϏāĻžāĻŽ āĻĻāĻŋāϝāĻŧ⧇ āϕ⧋āύ⧋ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_locked_photos_message": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āĻĨāĻžāĻ•āĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āϞ⧁āĻ•āĻžāύ⧋ āĻĨāĻžāϕ⧇ āĻāĻŦāĻ‚ āφāĻĒāύāĻŋ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻŦā§āϰāĻžāωāϜ āĻŦāĻž āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏ⧇āϗ⧁āϞ⧋ āĻĻ⧇āĻ–āĻž āϝāĻžāĻŦ⧇ āύāĻžāĨ¤", - "no_name": "āϕ⧋āύ⧋ āύāĻžāĻŽ āύ⧇āχ", - "no_notifications": "āϕ⧋āύ⧋ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āύ⧇āχ", - "no_people_found": "āĻŽāĻŋāϞ⧇ āϝāĻžāĻ“āϝāĻŧāĻž āϕ⧋āύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_places": "āϕ⧋āύ⧋ āĻ¸ā§āĻĨāĻžāύ āύ⧇āχ", - "no_remote_assets_found": "āĻāχ āĻšā§‡āĻ•āϏāĻžāĻŽ āĻĻāĻŋāϝāĻŧ⧇ āϕ⧋āύ⧋ āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "no_results": "āϕ⧋āύ⧋ āĻĢāϞāĻžāĻĢāϞ āύ⧇āχ", - "no_results_description": "āĻāĻ•āϟāĻŋ āϏāĻŽāĻžāĻ°ā§āĻĨāĻ• āĻļāĻŦā§āĻĻ āĻ…āĻĨāĻŦāĻž āφāϰāĻ“ āϏāĻžāϧāĻžāϰāĻŖ āĻ•āĻŋāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĻ⧇āϖ⧁āύ", - "no_shared_albums_message": "āφāĻĒāύāĻžāϰ āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āϕ⧇āϰ āĻŽāĻžāύ⧁āώ⧇āϰ āϏāĻžāĻĨ⧇ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāϤ⧇ āĻāĻ•āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ", - "no_steps": "āĻāĻ–āύāĻ“ āϕ⧋āύ⧋ āϧāĻžāĻĒ āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ", - "none": "āϕ⧋āύ⧋āϟāĻŋāχ āύāĻž", - "not_allowed": "āĻ…āύ⧁āĻŽā§‹āĻĻāĻŋāϤ āύāϝāĻŧ", - "not_available": "N/A", - "not_in_any_album": "āϕ⧋āύ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āύ⧇āχ", - "not_selected": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āύāϝāĻŧ", - "not_set": "āύāĻŋāĻ°ā§āϧāĻžāϰāĻŋāϤ āύāϝāĻŧ", - "notes": "āύ⧋āϟ", - "nothing_here_yet": "āĻāĻ–āĻžāύ⧇ āĻāĻ–āύ⧋ āĻ•āĻŋāϛ⧁ āύ⧇āχ", - "notification_backup_reliability": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻŦā§āϝāĻžāĻ•āφāĻĒ⧇āϰ āύāĻŋāĻ°ā§āĻ­āϰāϝ⧋āĻ—ā§āϝāϤāĻž āĻŦāĻžāĻĄāĻŧāĻžāϤ⧇ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "notification_enabled_list_tile_content": "Immich āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻŦā§āϝāĻžāĻ•āφāĻĒ⧇āϰ āϜāĻ¨ā§āϝ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇āĨ¤ āφāĻĒāύāĻžāϰ āĻĄāĻŋāĻ­āĻžāχāϏ⧇āϰ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āϏ⧇āϗ⧁āϞ⧋ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύāĨ¤", - "notification_enabled_list_tile_open_button": "āϏ⧇āϟāĻŋāĻ‚āϏ āϖ⧁āϞ⧁āύ", - "notification_enabled_list_tile_title": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āφāϛ⧇", - "notification_permission_dialog_content": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇, āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āĻ—āĻŋāϝāĻŧ⧇ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤", - "notification_permission_list_tile_content": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇ āĻ…āύ⧁āĻŽāϤāĻŋ āĻĻāĻŋāύāĨ¤", - "notification_permission_list_tile_enable_button": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "notification_permission_list_tile_title": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ⧇āϰ āĻ…āύ⧁āĻŽāϤāĻŋ", - "notification_toggle_setting_description": "āχāĻŽā§‡āχāϞ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ", - "notifications": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ", - "notifications_setting_description": "āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āϟāϰ", - "obtainium_configurator_instructions": "Immich-āĻāϰ GitHub āϰāĻŋāϞāĻŋāϜ āĻĨ⧇āϕ⧇ āϏāϰāĻžāϏāϰāĻŋ Android āĻ…ā§āϝāĻžāĻĒ āχāύāĻ¸ā§āϟāϞ āĻ“ āφāĻĒāĻĄā§‡āϟ āĻ•āϰāϤ⧇ Obtainium āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύāĨ¤ āφāĻĒāύāĻžāϰ Obtainium āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āϞāĻŋāĻ‚āĻ• āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻāĻ•āϟāĻŋ API āϕ⧀ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ āĻāĻŦāĻ‚ āĻāĻ•āϟāĻŋ āĻ­ā§āϝāĻžāϰāĻŋāϝāĻŧāĻžāĻ¨ā§āϟ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "ocr": "OCR", - "ocr_body": "Immich āĻāĻ–āύ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋāϰ āϭ⧇āϤāϰ⧇āϰ āϞ⧇āĻ–āĻž āĻĒāĻĄāĻŧāϤ⧇ āĻĒāĻžāϰ⧇, āϤāĻžāχ āφāĻĒāύāĻŋ āϏ⧇āϗ⧁āϞ⧋āϰ āϞ⧇āĻ–āĻž āĻĻāĻŋāϝāĻŧ⧇ āϖ⧁āρāϜāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤", - "ocr_title": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋāϤ⧇ āϞ⧇āĻ–āĻž āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "official_immich_resources": "Immich-āĻāϰ āĻ…āĻĢāĻŋāϏāĻŋāϝāĻŧāĻžāϞ āϰāĻŋāϏ⧋āĻ°ā§āϏ", - "offline": "āĻ…āĻĢāϞāĻžāχāύ", - "offset": "āĻ…āĻĢāϏ⧇āϟ", - "ok": "āĻ āĻŋāĻ• āφāϛ⧇", - "oldest_first": "āϏāĻ°ā§āĻŦāĻĒā§āϰāĻĨāĻŽ āĻĒ⧁āϰāĻžāϤāύ", - "on_this_device": "āĻāχ āĻĄāĻŋāĻ­āĻžāχāϏ⧇", - "onboarding": "āĻ…āύāĻŦā§‹āĻ°ā§āĻĄāĻŋāĻ‚", - "onboarding_locale_description": "āφāĻĒāύāĻžāϰ āĻĒāĻ›āĻ¨ā§āĻĻ⧇āϰ āĻ­āĻžāώāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āφāĻĒāύāĻŋ āĻĒāϰāĻŦāĻ°ā§āϤ⧀āϤ⧇ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āĻāϟāĻŋ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤", - "onboarding_privacy_description": "āύāĻŋāĻšā§‡āϰ (āϐāĻšā§āĻ›āĻŋāĻ•) āĻĢāĻŋāϚāĻžāϰāϗ⧁āϞ⧋ āĻŦāĻžāĻšā§āϝāĻŋāĻ• āĻĒāϰāĻŋāώ⧇āĻŦāĻžāϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰāĻļā§€āϞ āĻāĻŦāĻ‚ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āϝ⧇āϕ⧋āύ⧋ āϏāĻŽāϝāĻŧ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤", - "onboarding_server_welcome_description": "āϚāϞ⧁āύ āĻ•āĻŋāϛ⧁ āϏāĻžāϧāĻžāϰāĻŖ āϏ⧇āϟāĻŋāĻ‚āϏ āĻĻāĻŋāϝāĻŧ⧇ āφāĻĒāύāĻžāϰ āχāύāĻ¸ā§āĻŸā§āϝāĻžāĻ¨ā§āϏ āϏ⧇āϟāφāĻĒ āĻ•āϰāĻŋāĨ¤", - "onboarding_theme_description": "āφāĻĒāύāĻžāϰ āχāύāĻ¸ā§āĻŸā§āϝāĻžāĻ¨ā§āϏ⧇āϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻ•āĻžāϞāĻžāϰ āĻĨāĻŋāĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤ āφāĻĒāύāĻŋ āĻĒāϰāĻŦāĻ°ā§āϤ⧀āϤ⧇ āϏ⧇āϟāĻŋāĻ‚āϏ⧇ āĻāϟāĻŋ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤", - "onboarding_user_welcome_description": "āϚāϞ⧁āύ āĻļ⧁āϰ⧁ āĻ•āϰāĻž āϝāĻžāĻ•!", - "onboarding_welcome_user": "āĻ¸ā§āĻŦāĻžāĻ—āϤāĻŽ, {user}", - "online": "āĻ…āύāϞāĻžāχāύ", - "only_favorites": "āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ", - "open": "āϖ⧁āϞ⧁āύ", - "open_calendar": "āĻ•ā§āϝāĻžāϞ⧇āĻ¨ā§āĻĄāĻžāϰ āϖ⧁āϞ⧁āύ", "open_in_browser": "āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇ āĻ“āĻĒ⧇āύ āĻ•āϰ⧁āύ", - "open_in_immich_body": "āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āĻ…ā§āϝāĻžāĻĒ āĻĨ⧇āϕ⧇ āϏāϰāĻžāϏāϰāĻŋ āĻ›āĻŦāĻŋ āϖ⧁āϞāϤ⧇ Android-āĻ Immich-āϕ⧇ āφāĻĒāύāĻžāϰ āĻ—ā§āϝāĻžāϞāĻžāϰāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύāĨ¤", - "open_in_immich_title": "Immich-āĻ āĻ›āĻŦāĻŋ āϖ⧁āϞ⧁āύ", - "open_in_map_view": "āĻŽāĻžāύāϚāĻŋāĻ¤ā§āϰ āĻ­āĻŋāωāϤ⧇ āϖ⧁āϞ⧁āύ", - "open_in_openstreetmap": "OpenStreetMap-āĻ āϖ⧁āϞ⧁āύ", - "open_the_search_filters": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āϖ⧁āϞ⧁āύ", - "options": "āĻ…āĻĒāĻļāύ", - "or": "āĻ…āĻĨāĻŦāĻž", - "organize_into_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϗ⧁āĻ›āĻŋāϝāĻŧ⧇ āϰāĻžāϖ⧁āύ", - "organize_into_albums_description": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻŋāĻ™ā§āĻ• āϏ⧇āϟāĻŋāĻ‚āϏ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϰāĻžāϖ⧁āύ", - "organize_your_library": "āφāĻĒāύāĻžāϰ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āϗ⧁āĻ›āĻŋāϝāĻŧ⧇ āϰāĻžāϖ⧁āύ", - "orientation": "āĻ“āϰāĻŋāϝāĻŧ⧇āĻ¨ā§āĻŸā§‡āĻļāύ", - "original": "āĻŽā§‚āϞ", - "other": "āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ", - "other_devices": "āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āĻĄāĻŋāĻ­āĻžāχāϏ", - "other_variables": "āĻ…āĻ¨ā§āϝāĻžāĻ¨ā§āϝ āϭ⧇āϰāĻŋāϝāĻŧ⧇āĻŦāϞ", - "owned": "āĻŽāĻžāϞāĻŋāĻ•āĻžāύāĻžāϧ⧀āύ", - "owner": "āĻŽāĻžāϞāĻŋāĻ•", - "page": "āĻĒ⧇āϜ", - "partner": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ", - "partner_can_access": "{partner} āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇", - "partner_can_access_assets": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰāĻž āĻāĻŦāĻ‚ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻŦā§āϝāϤ⧀āϤ āφāĻĒāύāĻžāϰ āϏāĻŦ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“", - "partner_can_access_location": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āϝ⧇āĻ–āĻžāύ⧇ āϤ⧋āϞāĻž āĻšāϝāĻŧ⧇āĻ›āĻŋāϞ āϏ⧇āχ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ", - "partner_list_user_photos": "{user}-āĻāϰ āĻ›āĻŦāĻŋ", - "partner_page_empty_message": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻāĻ–āύ⧋ āϕ⧋āύ⧋ āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ⧇āϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋāĨ¤", - "partner_page_no_more_users": "āϝ⧋āĻ— āĻ•āϰāĻžāϰ āĻŽāϤ⧋ āφāϰ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āύ⧇āχ", - "partner_page_select_partner": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "partner_page_stop_sharing_content": "{partner} āφāϰ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇ āύāĻžāĨ¤", - "partner_sharing": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ āĻļ⧇āϝāĻŧāĻžāϰāĻŋāĻ‚", - "partners": "āĻĒāĻžāĻ°ā§āϟāύāĻžāϰ", - "password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ", - "password_does_not_match": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻŽāĻŋāϞāϛ⧇ āύāĻž", - "password_required": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻŦāĻļā§āϝāĻ•", - "password_reset_success": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āϏāĻĢāϞ āĻšāϝāĻŧ⧇āϛ⧇", - "past_durations": { - "days": "āĻ—āϤ {days, plural, one {āĻĻāĻŋāύ} other {#āĻĻāĻŋāύ}}", - "hours": "āĻ—āϤ {hours, plural, one {āϘāĻŖā§āϟāĻž} other {#āϘāĻŖā§āϟāĻž}}", - "years": "āĻ—āϤ {years, plural, one {āĻŦāĻ›āϰ} other {#āĻŦāĻ›āϰ}}" - }, - "path": "āĻĒāĻžāĻĨ", - "pattern": "āĻĒā§āϝāĻžāϟāĻžāĻ°ā§āύ", - "pause": "āĻĨāĻžāĻŽāĻžāύ", - "pause_memories": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āĻĨāĻžāĻŽāĻžāύ", - "paused": "āĻĨāĻžāĻŽāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "pending": "āĻ…āĻĒ⧇āĻ•ā§āώāĻŽāĻžāĻŖ", - "people": "āĻŽāĻžāύ⧁āώ", - "people_edits_count": "{count, plural, one {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ} other {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ}} āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "people_feature_description": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŦā§āϰāĻžāωāϜ āĻ•āϰāĻž", - "permanent_deletion_warning": "āĻ¸ā§āĻĨāĻžāϝāĻŧā§€ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϏāϤāĻ°ā§āĻ•āϤāĻž", - "permanent_deletion_warning_setting_description": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āϏāĻŽāϝāĻŧ āĻāĻ•āϟāĻŋ āϏāϤāĻ°ā§āĻ•āϤāĻž āĻĻ⧇āĻ–āĻžāύ", - "permanently_delete": "āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧁āύ", - "permanently_delete_assets_count": "{count, plural, one {āĻ…ā§āϝāĻžāϏ⧇āϟ} other {āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧁āύ", - "permanently_delete_assets_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ {count, plural, one {āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ} other {āĻāχ #āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ? āĻāϤ⧇ {count, plural, one {āĻāϟāĻŋ āĻāϰ} other {āϏ⧇āϗ⧁āϞ⧋ āϤāĻžāĻĻ⧇āϰ}} āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇āĻ“ āϏāϰāĻžāύ⧋ āĻšāĻŦ⧇āĨ¤", - "permanently_deleted_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "permanently_deleted_assets_count": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "permission": "āĻ…āύ⧁āĻŽāϤāĻŋ", - "permission_empty": "āφāĻĒāύāĻžāϰ āĻ…āύ⧁āĻŽāϤāĻŋ āĻ–āĻžāϞāĻŋ āϰāĻžāĻ–āĻž āωāϚāĻŋāϤ āύāϝāĻŧ", - "person": "āĻŦā§āϝāĻ•ā§āϤāĻŋ", - "person_age_months": "{months, plural, one {#āĻŽāĻžāϏ} other {#āĻŽāĻžāϏ}} āĻŦāϝāĻŧāϏ", - "person_age_year_months": "ā§§ āĻŦāĻ›āϰ, {months, plural, one {#āĻŽāĻžāϏ} other {#āĻŽāĻžāϏ}} āĻŦāϝāĻŧāϏ", - "person_age_years": "{years, plural, one {#āĻŦāĻ›āϰ} other {#āĻŦāĻ›āϰ}} āĻŦāϝāĻŧāϏ", - "person_birthdate": "{date}-āĻ āϜāĻ¨ā§āĻŽ", - "person_hidden": "{name}{hidden, select, true { (āϞ⧁āĻ•āĻžāύ⧋)} other {}}", - "person_recognized": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤ āĻšāϝāĻŧ⧇āϛ⧇", - "photo_shared_all_users": "āĻŽāύ⧇ āĻšāĻšā§āϛ⧇ āφāĻĒāύāĻŋ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧇āϛ⧇āύ āĻ…āĻĨāĻŦāĻž āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻžāϰ āĻŽāϤ⧋ āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āύ⧇āχāĨ¤", - "photos": "āĻ›āĻŦāĻŋ", - "photos_and_videos": "āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“", - "photos_from_previous_years": "āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻŦāĻ›āϰāϗ⧁āϞ⧋āϰ āĻ›āĻŦāĻŋ", - "pick_a_location": "āĻāĻ•āϟāĻŋ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "pick_custom_range": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž", - "pick_date_range": "āĻāĻ•āϟāĻŋ āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "pin_code_changed_successfully": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "pin_code_reset_successfully": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "pin_code_setup_successfully": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏ⧇āϟāφāĻĒ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "pin_verification": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϝāĻžāϚāĻžāχāĻ•āϰāĻŖ", - "place": "āĻ¸ā§āĻĨāĻžāύ", - "places": "āĻ¸ā§āĻĨāĻžāύ", - "places_count": "{count, plural, one {{count, number}āϟāĻŋ āĻ¸ā§āĻĨāĻžāύ} other {{count, number}āϟāĻŋ āĻ¸ā§āĻĨāĻžāύ}}", - "play": "āϚāĻžāϞāĻžāύ", - "play_memories": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϚāĻžāϞāĻžāύ", - "play_motion_photo": "āĻŽā§‹āĻļāύ āĻĢāĻŸā§‹ āϚāĻžāϞāĻžāύ", - "play_or_pause_video": "āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύ āĻŦāĻž āĻĨāĻžāĻŽāĻžāύ", - "play_original_video": "āĻŽā§‚āϞ āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύ", - "play_original_video_setting_description": "āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤ⧇ āĻŽā§‚āϞ āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāϤ⧇ āĻ…āĻ—ā§āϰāĻžāϧāĻŋāĻ•āĻžāϰ āĻĻāĻŋāύāĨ¤ āĻŽā§‚āϞ āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝāĻĒā§‚āĻ°ā§āĻŖ āύāĻž āĻšāϞ⧇ āĻāϟāĻŋ āϏāĻ āĻŋāĻ•āĻ­āĻžāĻŦ⧇ āϚāϞāϤ⧇ āύāĻžāĻ“ āĻĒāĻžāϰ⧇āĨ¤", - "play_transcoded_video": "āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āĻ•āϰāĻž āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύ", - "please_auth_to_access": "āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻĒā§āϰāĻŽāĻžāĻŖā§€āĻ•āϰāĻŖ āĻ•āϰ⧁āύ", - "plugin_method_filter_type": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ", - "plugin_method_filter_type_description": "āĻāχ āĻĒāĻĻā§āϧāϤāĻŋāϟāĻŋ āχāϭ⧇āĻ¨ā§āϟ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇ āĻāĻŦāĻ‚ āĻļāĻ°ā§āϤāϏāĻžāĻĒ⧇āĻ•ā§āώ⧇ āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āϧāĻžāĻĒāϗ⧁āϞ⧋ āϚāϞāϤ⧇ āĻŦāĻžāϧāĻž āĻĻāĻŋāϤ⧇ āĻĒāĻžāϰ⧇", - "port": "āĻĒā§‹āĻ°ā§āϟ", - "preferences_settings_subtitle": "āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĒāĻ›āĻ¨ā§āĻĻāϏāĻŽā§‚āĻš āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "preferences_settings_title": "āĻĒāĻ›āĻ¨ā§āĻĻāϏāĻŽā§‚āĻš", - "preparing": "āĻĒā§āϰāĻ¸ā§āϤ⧁āϤ āĻšāĻšā§āϛ⧇", - "preset": "āĻĒā§āϰāĻŋāϏ⧇āϟ", - "preview": "āĻĒā§āϰāĻŋāĻ­āĻŋāω", - "previous": "āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀", - "previous_memory": "āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻ¸ā§āĻŽā§ƒāϤāĻŋ", - "previous_or_next_day": "āĻĻāĻŋāύ āϏāĻžāĻŽāύ⧇/āĻĒ⧇āĻ›āύ⧇", - "previous_or_next_month": "āĻŽāĻžāϏ āϏāĻžāĻŽāύ⧇/āĻĒ⧇āĻ›āύ⧇", - "previous_or_next_photo": "āĻ›āĻŦāĻŋ āϏāĻžāĻŽāύ⧇/āĻĒ⧇āĻ›āύ⧇", - "previous_or_next_year": "āĻŦāĻ›āϰ āϏāĻžāĻŽāύ⧇/āĻĒ⧇āĻ›āύ⧇", - "primary": "āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ•", - "privacy": "āĻ—ā§‹āĻĒāύ⧀āϝāĻŧāϤāĻž", - "profile": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ", - "profile_drawer_app_logs": "āϞāĻ—āϏāĻŽā§‚āĻš", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "āϰāĻŋāĻĄ-āĻ…āύāϞāĻŋ āĻŽā§‹āĻĄ āϚāĻžāϞ⧁ āφāϛ⧇āĨ¤ āĻŦ⧇āϰ āĻšāϤ⧇ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ…ā§āϝāĻžāĻ­āĻžāϟāĻžāϰ āφāχāĻ•āύ⧇ āĻĻā§€āĻ°ā§āϘāĻ•ā§āώāĻŖ āϚāĻžāĻĒ āĻĻāĻŋāύāĨ¤", - "profile_image_of_user": "{user}-āĻāϰ āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋ", - "profile_picture_set": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋ āϏ⧇āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤", - "projection_type": "āĻĒā§āϰāĻœā§‡āĻ•āĻļāύ āϟāĻžāχāĻĒ", - "public_album": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", - "public_share": "āĻĒāĻžāĻŦāϞāĻŋāĻ• āĻļ⧇āϝāĻŧāĻžāϰ", - "purchase_account_info": "āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ", - "purchase_activated_subtitle": "Immich āĻāĻŦāĻ‚ āĻ“āĻĒ⧇āύ-āϏ⧋āĻ°ā§āϏ āϏāĻĢāϟāĻ“āϝāĻŧā§āϝāĻžāϰāϕ⧇ āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āϧāĻ¨ā§āϝāĻŦāĻžāĻĻ", - "purchase_activated_time": "{date}-āĻ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "purchase_activated_title": "āφāĻĒāύāĻžāϰ āϕ⧀ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "purchase_button_activate": "āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύ", - "purchase_button_buy": "āĻ•āĻŋāύ⧁āύ", - "purchase_button_buy_immich": "Immich āĻ•āĻŋāύ⧁āύ", - "purchase_button_never_show_again": "āφāϰ āĻ•āĻ–āύ⧋ āĻĻ⧇āĻ–āĻžāĻŦ⧇āύ āύāĻž", - "purchase_button_reminder": "ā§Šā§Ļ āĻĻāĻŋāύ āĻĒāϰ āφāĻŽāĻžāϕ⧇ āĻŽāύ⧇ āĻ•āϰāĻŋāϝāĻŧ⧇ āĻĻāĻŋāύ", - "purchase_button_remove_key": "āϕ⧀ āϏāϰāĻžāύ", - "purchase_button_select": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "purchase_failed_activation": "āϏāĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āĻŦā§āϝāĻ°ā§āĻĨ āĻšāϝāĻŧ⧇āϛ⧇! āϏāĻ āĻŋāĻ• āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀-āĻāϰ āϜāĻ¨ā§āϝ āφāĻĒāύāĻžāϰ āχāĻŽā§‡āχāϞ āĻšā§‡āĻ• āĻ•āϰ⧁āύ!", - "purchase_individual_description_1": "āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āϜāĻ¨ā§āϝ", - "purchase_individual_description_2": "āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ āĻ¸ā§āĻŸā§āϝāĻžāϟāĻžāϏ", - "purchase_individual_title": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻ—āϤ", - "purchase_input_suggestion": "āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āφāϛ⧇? āύāĻŋāĻšā§‡ āϕ⧀-āϟāĻŋ āϞāĻŋāϖ⧁āύ", - "purchase_lifetime_description": "āϞāĻžāχāĻĢāϟāĻžāχāĻŽ āĻ•ā§āϰāϝāĻŧ", - "purchase_option_title": "āĻ•ā§āϰāϝāĻŧ⧇āϰ āĻ…āĻĒāĻļāύ", - "purchase_panel_info_1": "Immich āϤ⧈āϰāĻŋ āĻ•āϰāϤ⧇ āĻ…āύ⧇āĻ• āϏāĻŽāϝāĻŧ āĻ“ āĻĒāϰāĻŋāĻļā§āϰāĻŽ āϞāĻžāϗ⧇, āĻāĻŦāĻ‚ āφāĻŽāĻžāĻĻ⧇āϰ āĻĒā§‚āĻ°ā§āĻŖāĻ•āĻžāϞ⧀āύ āχāĻžā§āϜāĻŋāύāĻŋāϝāĻŧāĻžāϰāϰāĻž āĻāϟāĻŋāϕ⧇ āϝāϤāϟāĻž āϏāĻŽā§āĻ­āĻŦ āĻ­āĻžāϞ⧋ āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻ•āĻžāϜ āĻ•āϰāϛ⧇āύāĨ¤ āφāĻŽāĻžāĻĻ⧇āϰ āϞāĻ•ā§āĻˇā§āϝ āĻšāϞ⧋ āĻ“āĻĒ⧇āύ-āϏ⧋āĻ°ā§āϏ āϏāĻĢāϟāĻ“āϝāĻŧā§āϝāĻžāϰ āĻāĻŦāĻ‚ āύ⧈āϤāĻŋāĻ• āĻŦā§āϝāĻŦāϏāĻžāϝāĻŧāĻŋāĻ• āϚāĻ°ā§āϚāĻžāϕ⧇ āĻĄā§‡āϭ⧇āϞāĻĒāĻžāϰāĻĻ⧇āϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āĻŸā§‡āĻ•āϏāχ āφāϝāĻŧ⧇āϰ āĻ‰ā§ŽāϏ āĻ•āϰ⧇ āϤ⧋āϞāĻž āĻāĻŦāĻ‚ āĻļā§‹āώāĻŖāĻŽā§‚āϞāĻ• āĻ•ā§āϞāĻžāωāĻĄ āĻĒāϰāĻŋāώ⧇āĻŦāĻžāϰ āĻĒā§āϰāĻ•ā§ƒāϤ āĻŦāĻŋāĻ•āĻ˛ā§āĻĒāϏāĻš āĻāĻ•āϟāĻŋ āĻ—ā§‹āĻĒāύ⧀āϝāĻŧāϤāĻž-āϏāĻŽā§āĻŽāĻžāύāĻ•āĻžāϰ⧀ āχāϕ⧋āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āϤ⧈āϰāĻŋ āĻ•āϰāĻžāĨ¤", - "purchase_panel_info_2": "āϝ⧇āĻšā§‡āϤ⧁ āφāĻŽāϰāĻž āĻĒ⧇āĻ“āϝāĻŧāĻžāϞ āϝ⧋āĻ— āύāĻž āĻ•āϰāĻžāϰ āĻĒā§āϰāϤāĻŋāĻļā§āϰ⧁āϤāĻŋāĻŦāĻĻā§āϧ, āϤāĻžāχ āĻāχ āĻ•ā§āϰāϝāĻŧ āφāĻĒāύāĻžāϕ⧇ Immich-āĻ āϕ⧋āύ⧋ āĻ…āϤāĻŋāϰāĻŋāĻ•ā§āϤ āĻĢāĻŋāϚāĻžāϰ āĻĻ⧇āĻŦ⧇ āύāĻžāĨ¤ Immich-āĻāϰ āϚāϞāĻŽāĻžāύ āωāĻ¨ā§āύāϝāĻŧāύ⧇ āϏāĻŽāĻ°ā§āĻĨāύ āĻĻāĻŋāϤ⧇ āφāĻŽāϰāĻž āφāĻĒāύāĻžāϰ āĻŽāϤ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āĻĻ⧇āϰ āωāĻĒāϰ āύāĻŋāĻ°ā§āĻ­āϰāĻļā§€āϞāĨ¤", - "purchase_panel_title": "āĻĒā§āϰāĻœā§‡āĻ•ā§āϟāϕ⧇ āϏāĻŽāĻ°ā§āĻĨāύ āĻ•āϰ⧁āύ", - "purchase_per_server": "āĻĒā§āϰāϤāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ", - "purchase_per_user": "āĻĒā§āϰāϤāĻŋ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀", - "purchase_remove_product_key": "āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āϏāϰāĻžāύ", - "purchase_remove_product_key_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", - "purchase_remove_server_product_key": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āϏāϰāĻžāύ", - "purchase_remove_server_product_key_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", - "purchase_server_description_1": "āĻĒ⧁āϰ⧋ āϏāĻžāĻ°ā§āĻ­āĻžāϰ⧇āϰ āϜāĻ¨ā§āϝ", - "purchase_server_description_2": "āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ āĻ¸ā§āĻŸā§āϝāĻžāϟāĻžāϏ", - "purchase_server_title": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ", - "purchase_settings_server_activated": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĒā§āϰ⧋āĻĄāĻžāĻ•ā§āϟ āϕ⧀ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύ āĻĻā§āĻŦāĻžāϰāĻž āĻĒāϰāĻŋāϚāĻžāϞāĻŋāϤ āĻšāϝāĻŧ", - "query_asset_id": "āϕ⧋āϝāĻŧ⧇āϰāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ ID", - "queue_status": "āϏāĻžāϰāĻŋāĻŦāĻĻā§āϧ āĻšāĻšā§āϛ⧇ {count}/{total}", - "rate_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϰ⧇āϟ āĻ•āϰ⧁āύ", - "rating": "āĻ¸ā§āϟāĻžāϰ āϰ⧇āϟāĻŋāĻ‚", - "rating_clear": "āϰ⧇āϟāĻŋāĻ‚ āĻŽā§āϛ⧁āύ", - "rating_count": "{count, plural, =0 {āϰ⧇āϟ āĻ•āϰāĻž āĻšāϝāĻŧāύāĻŋ} one {#āĻ¸ā§āϟāĻžāϰ} other {#āĻ¸ā§āϟāĻžāϰ}}", - "rating_description": "āϤāĻĨā§āϝ āĻĒā§āϝāĻžāύ⧇āϞ⧇ EXIF āϰ⧇āϟāĻŋāĻ‚ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ āĻ•āϰ⧁āύ", - "reaction_options": "āϰāĻŋāĻ…ā§āϝāĻžāĻ•āĻļāύ āĻ…āĻĒāĻļāύ", - "read_changelog": "āĻšā§‡āĻžā§āϜāϞāĻ— āĻĒāĻĄāĻŧ⧁āύ", - "readonly_mode_disabled": "āϰāĻŋāĻĄ-āĻ…āύāϞāĻŋ āĻŽā§‹āĻĄ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ", - "readonly_mode_enabled": "āϰāĻŋāĻĄ-āĻ…āύāϞāĻŋ āĻŽā§‹āĻĄ āϏāĻ•ā§āϰāĻŋāϝāĻŧ", - "ready_for_upload": "āφāĻĒāϞ⧋āĻĄā§‡āϰ āϜāĻ¨ā§āϝ āĻĒā§āϰāĻ¸ā§āϤ⧁āϤ", - "reassign": "āĻĒ⧁āύāϰāĻžāϝāĻŧ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "reassigned_assets_to_existing_person": "{name, select, null {āĻāĻ•āϜāύ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ} other {{name}-āĻāϰ}} āĻ•āĻžāϛ⧇ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻĒ⧁āύāϰāĻžāϝāĻŧ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "reassigned_assets_to_new_person": "āύāϤ⧁āύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻ•āĻžāϛ⧇ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻĒ⧁āύāϰāĻžāϝāĻŧ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "reassing_hint": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϏ⧇āϟāϗ⧁āϞ⧋ āĻāĻ•āϜāύ āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻ•āĻžāϛ⧇ āύāĻŋāĻ°ā§āϧāĻžāϰāĻŖ āĻ•āϰ⧁āύ", - "recent": "āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ•", - "recent_searches": "āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ• āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ", - "recently_added": "āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϝ⧋āĻ— āĻšāϝāĻŧ⧇āϛ⧇", - "recently_added_body": "āφāĻĒāύāĻŋ āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϝāĻž āϝ⧋āĻ— āĻ•āϰ⧇āϛ⧇āύ āϤāĻžāϰ āϏāĻŦāĻ•āĻŋāϛ⧁āϤ⧇ āĻāĻ•āϟāĻŋ āύāĻŋāĻ°ā§āĻĻāĻŋāĻˇā§āϟ āĻĒ⧇āĻœā§‡ āϏāϰāĻžāϏāϰāĻŋ āϚāϞ⧇ āϝāĻžāύāĨ¤", - "recently_added_description": "Immich-āĻ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āϏāĻžāϜāĻžāύ⧋ āφāĻĒāύāĻžāϰ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŦā§āϰāĻžāωāϜ āĻ•āϰ⧁āύ", - "recently_added_page_title": "āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϝ⧋āĻ— āĻšāϝāĻŧ⧇āϛ⧇", - "recently_added_title": "āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϝ⧋āĻ— āĻšāϝāĻŧ⧇āϛ⧇", - "recently_taken": "āϏāĻŽā§āĻĒā§āϰāϤāĻŋ āϤ⧋āϞāĻž", - "refresh": "āϰāĻŋāĻĢā§āϰ⧇āĻļ", - "refresh_encoded_videos": "āĻāύāϕ⧋āĻĄ āĻ•āϰāĻž āĻ­āĻŋāĻĄāĻŋāĻ“ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύ", - "refresh_faces": "āĻŽā§āĻ– āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύ", - "refresh_metadata": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύ", - "refresh_thumbnails": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύ", - "refreshed": "āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "refreshing_encoded_video": "āĻāύāϕ⧋āĻĄ āĻ•āϰāĻž āĻ­āĻŋāĻĄāĻŋāĻ“ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "refreshing_faces": "āĻŽā§āĻ– āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "refreshing_metadata": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "regenerating_thumbnails": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϤ⧈āϰāĻŋ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "remote": "āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀", - "remote_assets": "āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āϟ", - "remote_media_summary": "āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀ āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āϏāĻžāϰāϏāĻ‚āĻ•ā§āώ⧇āĻĒ", - "remove": "āϏāϰāĻžāύ", - "remove_assets_album_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", - "remove_assets_shared_link_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻĨ⧇āϕ⧇ {count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āϏāϰāĻžāϤ⧇ āϚāĻžāύ?", - "remove_assets_title": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϏāϰāĻžāĻŦ⧇āύ?", - "remove_custom_date_range": "āĻ•āĻžāĻ¸ā§āϟāĻŽ āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž āϏāϰāĻžāύ", - "remove_filter": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āϏāϰāĻžāύ", - "remove_from_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "remove_from_album_action_prompt": "{count}āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "remove_from_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "remove_from_lock_folder_action_prompt": "{count}āϟāĻŋ āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "remove_from_locked_folder": "āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "remove_from_locked_folder_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“āϗ⧁āϞ⧋ āϞāĻ• āĻ•āϰāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻĨ⧇āϕ⧇ āϏāϰāĻžāϤ⧇ āϚāĻžāύ? āϏ⧇āϗ⧁āϞ⧋ āφāĻĒāύāĻžāϰ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋāϤ⧇ āĻĻ⧃āĻļā§āϝāĻŽāĻžāύ āĻšāĻŦ⧇āĨ¤", - "remove_from_shared_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "remove_memory": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϏāϰāĻžāύ", - "remove_photo_from_memory": "āĻāχ āĻ¸ā§āĻŽā§ƒāϤāĻŋ āĻĨ⧇āϕ⧇ āĻ›āĻŦāĻŋ āϏāϰāĻžāύ", - "remove_tag": "āĻŸā§āϝāĻžāĻ— āϏāϰāĻžāύ", - "remove_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āϏāϰāĻžāύ", - "removed_api_key": "API āϕ⧀ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇: {name}", - "removed_from_archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "removed_from_favorites": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "removed_from_favorites_count": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĨ⧇āϕ⧇ {count, plural, other {#āϟāĻŋ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇}}", - "removed_memory": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "removed_tagged_assets": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻĨ⧇āϕ⧇ āĻŸā§āϝāĻžāĻ— āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "rename": "āύāĻžāĻŽ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "repository": "āϰāĻŋāĻĒā§‹āϜāĻŋāϟāϰāĻŋ", - "require_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻŦāĻļā§āϝāĻ•", - "rescan": "āĻĒ⧁āύāϰāĻžāϝāĻŧ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", - "reset": "āϰāĻŋāϏ⧇āϟ", - "reset_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "reset_people_visibility": "āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻĻ⧃āĻļā§āϝāĻŽāĻžāύāϤāĻž āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "reset_pin_code": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "reset_pin_code_description": "āφāĻĒāύāĻŋ āϝāĻĻāĻŋ āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āϭ⧁āϞ⧇ āϝāĻžāύ, āϤāĻžāĻšāϞ⧇ āĻāϟāĻŋ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ…ā§āϝāĻžāĻĄāĻŽāĻŋāύāĻŋāĻ¸ā§āĻŸā§āϰ⧇āϟāϰ⧇āϰ āϏāĻžāĻĨ⧇ āϝ⧋āĻ—āĻžāϝ⧋āĻ— āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύ", - "reset_pin_code_with_password": "āφāĻĒāύāĻŋ āϏāĻŦāϏāĻŽāϝāĻŧ āφāĻĒāύāĻžāϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĻāĻŋāϝāĻŧ⧇ āĻĒāĻŋāύ āϕ⧋āĻĄ āϰāĻŋāϏ⧇āϟ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ", - "reset_sqlite": "SQLite āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "reset_sqlite_clear_app_data": "āĻĄā§‡āϟāĻž āĻŽā§āϛ⧁āύ", - "reset_sqlite_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĄā§‡āϟāĻž āĻŽā§āĻ›āϤ⧇ āϚāĻžāύ? āĻāϤ⧇ āϏāĻŦ āϏ⧇āϟāĻŋāĻ‚āϏ āĻŽā§āϛ⧇ āϝāĻžāĻŦ⧇ āĻāĻŦāĻ‚ āφāĻĒāύāĻžāϕ⧇ āϏāĻžāχāύ āφāωāϟ āĻ•āϰ⧇ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻšāĻŦ⧇āĨ¤", - "reset_sqlite_confirmation_note": "āύ⧋āϟ: āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻžāϰ āĻĒāϰ āφāĻĒāύāĻžāϕ⧇ āĻ…ā§āϝāĻžāĻĒāϟāĻŋ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞ⧁ āĻ•āϰāϤ⧇ āĻšāĻŦ⧇āĨ¤", - "reset_sqlite_done": "āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĄā§‡āϟāĻž āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ Immich āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞ⧁ āĻ•āϰ⧇ āφāĻŦāĻžāϰ āϞāĻ— āχāύ āĻ•āϰ⧁āύāĨ¤", - "reset_sqlite_success": "SQLite āĻĄā§‡āϟāĻžāĻŦ⧇āϏ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āϰāĻŋāϏ⧇āϟ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "reset_to_default": "āĻĄāĻŋāĻĢāĻ˛ā§āĻŸā§‡ āϰāĻŋāϏ⧇āϟ āĻ•āϰ⧁āύ", - "resolution": "āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ", - "resolve_duplicates": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰ⧁āύ", - "resolved_all_duplicates": "āϏāĻŦ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āϏāĻŽāĻžāϧāĻžāύ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "restore": "āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "restore_all": "āϏāĻŦ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "restore_user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰ⧁āύ", - "restored_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "resume": "āφāĻŦāĻžāϰ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", - "resume_paused_jobs": "{count, plural, one {#āϟāĻŋ āĻĨāĻžāĻŽāĻžāύ⧋ āϜāĻŦ} other {#āϟāĻŋ āĻĨāĻžāĻŽāĻžāύ⧋ āϜāĻŦ}} āφāĻŦāĻžāϰ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", - "review_duplicates": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻĒāĻ°ā§āϝāĻžāϞ⧋āϚāύāĻž āĻ•āϰ⧁āύ", - "review_large_files": "āĻŦāĻĄāĻŧ āĻĢāĻžāχāϞ āĻĒāĻ°ā§āϝāĻžāϞ⧋āϚāύāĻž āĻ•āϰ⧁āύ", - "role": "āĻ­ā§‚āĻŽāĻŋāĻ•āĻž", - "role_editor": "āĻāĻĄāĻŋāϟāϰ", - "role_viewer": "āĻ­āĻŋāωāϝāĻŧāĻžāϰ", - "running": "āϚāϞāϛ⧇", - "save": "āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰ⧁āύ", - "saved": "āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āĻšāϝāĻŧ⧇āϛ⧇", - "saved_api_key": "API āϕ⧀ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "saved_profile": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "saved_settings": "āϏ⧇āϟāĻŋāĻ‚āϏ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "say_something": "āĻ•āĻŋāϛ⧁ āĻŦāϞ⧁āύ", - "scaffold_body_error_occurred": "āĻ¤ā§āϰ⧁āϟāĻŋ āϘāĻŸā§‡āϛ⧇", - "scaffold_body_error_unrecoverable": "āĻāĻ•āϟāĻŋ āĻ…āĻĒ⧁āύāϰ⧁āĻĻā§āϧāĻžāϰāϝ⧋āĻ—ā§āϝ āĻ¤ā§āϰ⧁āϟāĻŋ āϘāĻŸā§‡āϛ⧇āĨ¤ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ Discord āĻ…āĻĨāĻŦāĻž GitHub-āĻ āĻ¤ā§āϰ⧁āϟāĻŋ āĻ“ āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻŸā§āϰ⧇āϏāϟāĻŋ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧁āύ āϝāĻžāϤ⧇ āφāĻŽāϰāĻž āϏāĻžāĻšāĻžāĻ¯ā§āϝ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŋāĨ¤ āĻĒāϰāĻžāĻŽāĻ°ā§āĻļ āĻĻ⧇āĻ“āϝāĻŧāĻž āĻšāϞ⧇, āφāĻĒāύāĻŋ āύāĻŋāĻšā§‡ āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻĄā§‡āϟāĻž āĻŽā§āĻ›āϤ⧇ āĻĒāĻžāϰ⧇āύāĨ¤", - "scan": "āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", - "scan_all_libraries": "āϏāĻŦ āϞāĻžāχāĻŦā§āϰ⧇āϰāĻŋ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", - "scan_library": "āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰ⧁āύ", - "scanning": "āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "scanning_for_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡āϰ āϜāĻ¨ā§āϝ āĻ¸ā§āĻ•ā§āϝāĻžāύ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇..āĨ¤", - "screencast_mode_description": "āĻ¸ā§āĻ•ā§āϰāĻŋāύ⧇ āϕ⧀āĻŦā§‹āĻ°ā§āĻĄ āĻ“ āĻŽāĻžāωāϏ āχāϭ⧇āĻ¨ā§āϟ āχāĻ¨ā§āĻĄāĻŋāϕ⧇āϟāϰ āĻĻ⧇āĻ–āĻžāύ", - "screencast_mode_title": "āĻ¸ā§āĻ•ā§āϰāĻŋāύāĻ•āĻžāĻ¸ā§āϟ āĻŽā§‹āĻĄ āϟāĻ—āϞ āĻ•āϰ⧁āύ", - "search": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ", - "search_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_context": "āĻĒā§āϰāϏāĻ™ā§āĻ— āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_description": "āĻŦāĻŋāĻŦāϰāĻŖ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_description_example": "āϏāĻžāĻĒāĻžāϝāĻŧ āĻšāĻžāχāĻ•āĻŋāĻ‚ āĻĻāĻŋāĻŦāϏ", - "search_by_filename": "āĻĢāĻžāχāϞ⧇āϰ āύāĻžāĻŽ āĻŦāĻž āĻāĻ•ā§āϏāĻŸā§‡āύāĻļāύ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_filename_example": "āϝ⧇āĻŽāύ IMG_1234.JPG āĻ…āĻĨāĻŦāĻž PNG", - "search_by_full_path": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ āĻĒāĻžāĻĨ āĻŦāĻž āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - āφāĻĒāύāĻŋ Projects, 3D, Printing, 2026 āχāĻ¤ā§āϝāĻžāĻĻāĻŋ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āύ", - "search_by_ocr": "OCR āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "āϞ⧇āĻ¨ā§āϏ āĻŽāĻĄā§‡āϞ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_camera_make": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž āύāĻŋāĻ°ā§āĻŽāĻžāϤāĻž āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_camera_model": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻž āĻŽāĻĄā§‡āϞ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_city": "āĻļāĻšāϰ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_country": "āĻĻ⧇āĻļ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_filter_apply": "āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ", - "search_filter_camera_title": "āĻ•ā§āϝāĻžāĻŽā§‡āϰāĻžāϰ āϧāϰāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_filter_date": "āϤāĻžāϰāĻŋāĻ–", - "search_filter_date_interval": "{start} āĻĨ⧇āϕ⧇ {end}", - "search_filter_date_title": "āĻāĻ•āϟāĻŋ āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_filter_display_option_not_in_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āύ⧇āχ", - "search_filter_display_options": "āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ āĻ…āĻĒāĻļāύ", - "search_filter_filename": "āĻĢāĻžāχāϞ⧇āϰ āύāĻžāĻŽ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_filter_location": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ", - "search_filter_location_title": "āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_filter_media_type": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϰ āϧāϰāύ", - "search_filter_media_type_title": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻžāϰ āϧāϰāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_filter_people_title": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_filter_star_rating": "āĻ¸ā§āϟāĻžāϰ āϰ⧇āϟāĻŋāĻ‚", - "search_filter_tags_title": "āĻŸā§āϝāĻžāĻ— āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "search_for": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_for_existing_person": "āĻŦāĻŋāĻĻā§āϝāĻŽāĻžāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_no_more_result": "āφāϰ āϕ⧋āύ⧋ āĻĢāϞāĻžāĻĢāϞ āύ⧇āχ", - "search_no_people": "āϕ⧋āύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋ āύ⧇āχ", - "search_no_people_named": "\"{name}\" āύāĻžāĻŽā§‡ āϕ⧋āύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋ āύ⧇āχ", - "search_no_result": "āϕ⧋āύ⧋ āĻĢāϞāĻžāĻĢāϞ āĻĒāĻžāĻ“āϝāĻŧāĻž āϝāĻžāϝāĻŧāύāĻŋ, āĻāĻ•āϟāĻŋ āĻ­āĻŋāĻ¨ā§āύ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻļāĻŦā§āĻĻ āĻŦāĻž āϏāĻŽāĻ¨ā§āĻŦāϝāĻŧ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĻ⧇āϖ⧁āύ", - "search_options": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ…āĻĒāĻļāύ", - "search_page_search_photos_videos": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_page_view_all_button": "āϏāĻŦ āĻĻ⧇āϖ⧁āύ", - "search_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_places": "āĻ¸ā§āĻĨāĻžāύ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "search_rating": "āϰ⧇āϟāĻŋāĻ‚ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_settings": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āϏ⧇āϟāĻŋāĻ‚āϏ", - "search_state": "āĻ¸ā§āĻŸā§‡āϟ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_tags": "āĻŸā§āϝāĻžāĻ— āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_timezone": "āϟāĻžāχāĻŽāĻœā§‹āύ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ..āĨ¤", - "search_type": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ⧇āϰ āϧāϰāύ", - "search_your_photos": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰ⧁āύ", - "searching_locales": "āϞ⧋āϕ⧇āϞ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇..āĨ¤", - "second": "āϏ⧇āϕ⧇āĻ¨ā§āĻĄ", - "see_all_people": "āϏāĻŦ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĻ⧇āϖ⧁āύ", - "select": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_album_cover": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ•āĻ­āĻžāϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_all": "āϏāĻŦ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_all_duplicates": "āϏāĻŦ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_avatar_color": "āĻ…ā§āϝāĻžāĻ­āĻžāϟāĻžāϰ⧇āϰ āϰāĻ™ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_count": "{count, plural, one {#āϟāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ} other {#āϟāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ}}", - "select_cutoff_date": "āĻ•āĻžāϟāĻ…āĻĢ āϤāĻžāϰāĻŋāĻ– āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_face": "āĻŽā§āĻ– āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_featured_photo": "āĻĢāĻŋāϚāĻžāĻ°ā§āĻĄ āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_from_computer": "āĻ•āĻŽā§āĻĒāĻŋāωāϟāĻžāϰ āĻĨ⧇āϕ⧇ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_keep_all": "āϏāĻŦ āϰāĻžāĻ–āĻž āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_new_face": "āύāϤ⧁āύ āĻŽā§āĻ– āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_person": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_person_to_tag": "āĻŸā§āϝāĻžāĻ— āĻ•āϰāĻžāϰ āϜāĻ¨ā§āϝ āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_photos": "āĻ›āĻŦāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_quality": "āĻŽāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "select_trash_all": "āϏāĻŦ āĻŸā§āĻ°ā§āϝāĻžāĻļ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "selected": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ", - "selected_count": "{count, plural, other {#āϟāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ}}", - "selected_gps_coordinates": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ GPS āĻ¸ā§āĻĨāĻžāύāĻžāĻ™ā§āĻ•", - "send_message": "āĻŦāĻžāĻ°ā§āϤāĻž āĻĒāĻžāĻ āĻžāύ", - "send_welcome_email": "āĻ¸ā§āĻŦāĻžāĻ—āϤāĻŽ āχāĻŽā§‡āχāϞ āĻĒāĻžāĻ āĻžāύ", - "server_endpoint": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻāĻ¨ā§āĻĄāĻĒāϝāĻŧ⧇āĻ¨ā§āϟ", - "server_info_box_app_version": "āĻ…ā§āϝāĻžāĻĒ āĻ­āĻžāĻ°ā§āϏāύ", - "server_info_box_server_url": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL", - "server_offline": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ…āĻĢāϞāĻžāχāύ", - "server_online": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ…āύāϞāĻžāχāύ", - "server_privacy": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ—ā§‹āĻĒāύ⧀āϝāĻŧāϤāĻž", - "server_restarting_description": "āĻāχ āĻĒ⧇āϜāϟāĻŋ āĻļā§€āĻ˜ā§āϰāχ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻšāĻŦ⧇āĨ¤", - "server_restarting_title": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĒ⧁āύāϰāĻžāϝāĻŧ āϚāĻžāϞ⧁ āĻšāĻšā§āϛ⧇", - "server_stats": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĒāϰāĻŋāϏāĻ‚āĻ–ā§āϝāĻžāύ", - "server_update_available": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āφāĻĒāĻĄā§‡āϟ āωāĻĒāϞāĻŦā§āϧ", - "server_version": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ­āĻžāĻ°ā§āϏāύ", - "set": "āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_as_album_cover": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ•āĻ­āĻžāϰ āĻšāĻŋāϏ⧇āĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_as_featured_photo": "āĻĢāĻŋāϚāĻžāĻ°ā§āĻĄ āĻ›āĻŦāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_as_profile_picture": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋ āĻšāĻŋāϏ⧇āĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_date_of_birth": "āϜāĻ¨ā§āĻŽ āϤāĻžāϰāĻŋāĻ– āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_profile_picture": "āĻĒā§āϰ⧋āĻĢāĻžāχāϞ āĻ›āĻŦāĻŋ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_slideshow_to_fullscreen": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "set_stack_primary_asset": "āĻĒā§āϰāĻžāĻĨāĻŽāĻŋāĻ• āĻ…ā§āϝāĻžāϏ⧇āϟ āĻšāĻŋāϏ⧇āĻŦ⧇ āϏ⧇āϟ āĻ•āϰ⧁āύ", - "setting_image_navigation_enable_subtitle": "āϚāĻžāϞ⧁ āĻĨāĻžāĻ•āϞ⧇, āφāĻĒāύāĻŋ āĻ¸ā§āĻ•ā§āϰāĻŋāύ⧇āϰ āĻŦāĻžāĻŽ/āĻĄāĻžāύ āĻĒā§āϰāĻžāĻ¨ā§āϤ⧇āϰ āϚāϤ⧁āĻ°ā§āĻĨāĻžāĻ‚āĻļ⧇ āĻŸā§āϝāĻžāĻĒ āĻ•āϰ⧇ āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀/āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻ›āĻŦāĻŋāϤ⧇ āϝ⧇āϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύāĨ¤", - "setting_image_navigation_enable_title": "āύ⧇āĻ­āĻŋāϗ⧇āϟ āĻ•āϰāϤ⧇ āĻŸā§āϝāĻžāĻĒ āĻ•āϰ⧁āύ", - "setting_image_navigation_title": "āĻ›āĻŦāĻŋ āύ⧇āĻ­āĻŋāϗ⧇āĻļāύ", - "setting_image_viewer_help": "āĻĄāĻŋāĻŸā§‡āχāϞ āĻ­āĻŋāωāϝāĻŧāĻžāϰ āĻĒā§āϰāĻĨāĻŽā§‡ āϛ⧋āϟ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϞ⧋āĻĄ āĻ•āϰ⧇, āϤāĻžāϰāĻĒāϰ āĻŽāĻžāĻāĻžāϰāĻŋ āφāĻ•āĻžāϰ⧇āϰ āĻĒā§āϰāĻŋāĻ­āĻŋāω āϞ⧋āĻĄ āĻ•āϰ⧇ (āϚāĻžāϞ⧁ āĻĨāĻžāĻ•āϞ⧇), āĻ…āĻŦāĻļ⧇āώ⧇ āĻŽā§‚āϞ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰ⧇ (āϚāĻžāϞ⧁ āĻĨāĻžāĻ•āϞ⧇)āĨ¤", - "setting_image_viewer_original_subtitle": "āĻŽā§‚āϞ āĻĒā§‚āĻ°ā§āĻŖ-āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύ (āĻŦāĻĄāĻŧ!)āĨ¤ āĻĄā§‡āϟāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āĻŽāĻžāϤ⧇ (āύ⧇āϟāĻ“āϝāĻŧāĻžāĻ°ā§āĻ• āĻ“ āĻĄāĻŋāĻ­āĻžāχāϏ āĻ•ā§āϝāĻžāĻļ āωāĻ­āϝāĻŧāχ) āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύāĨ¤", - "setting_image_viewer_original_title": "āĻŽā§‚āϞ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "setting_image_viewer_preview_subtitle": "āĻŽāĻžāĻāĻžāϰāĻŋ-āϰ⧇āĻœā§‹āϞāĻŋāωāĻļāύ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āϚāĻžāϞ⧁ āĻ•āϰ⧁āύāĨ¤ āϏāϰāĻžāϏāϰāĻŋ āĻŽā§‚āϞ āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰāϤ⧇ āĻŦāĻž āĻļ⧁āϧ⧁āĻŽāĻžāĻ¤ā§āϰ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰāϤ⧇ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧁āύāĨ¤", - "setting_image_viewer_preview_title": "āĻĒā§āϰāĻŋāĻ­āĻŋāω āĻ›āĻŦāĻŋ āϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "setting_languages_apply": "āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύ", - "setting_languages_subtitle": "āĻ…ā§āϝāĻžāĻĒ⧇āϰ āĻ­āĻžāώāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "setting_notifications_notify_minutes": "{count} āĻŽāĻŋāύāĻŋāϟ", - "setting_notifications_notify_seconds": "{count} āϏ⧇āϕ⧇āĻ¨ā§āĻĄ", - "setting_notifications_subtitle": "āφāĻĒāύāĻžāϰ āύ⧋āϟāĻŋāĻĢāĻŋāϕ⧇āĻļāύ āĻĒāĻ›āĻ¨ā§āĻĻ āϏāĻžāĻŽāĻžā§āϜāĻ¸ā§āϝ āĻ•āϰ⧁āύ", - "setting_video_viewer_auto_play_subtitle": "āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ–ā§‹āϞāĻžāϰ āϏāĻžāĻĨ⧇ āϏāĻžāĻĨ⧇ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āϚāĻžāϞāĻžāύ⧋ āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", - "setting_video_viewer_auto_play_title": "āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύ", - "setting_video_viewer_looping_title": "āϞ⧁āĻĒāĻŋāĻ‚", - "setting_video_viewer_original_video_subtitle": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻĨ⧇āϕ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻ¸ā§āĻŸā§āϰāĻŋāĻŽ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ, āĻŸā§āϰāĻžāĻ¨ā§āϏāϕ⧋āĻĄ āωāĻĒāϞāĻŦā§āϧ āĻĨāĻžāĻ•āϞ⧇āĻ“ āĻŽā§‚āϞ āĻ­āĻŋāĻĄāĻŋāĻ“ āϚāĻžāϞāĻžāύāĨ¤ āĻāϤ⧇ āĻŦāĻžāĻĢāĻžāϰāĻŋāĻ‚ āĻšāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧāĻ­āĻžāĻŦ⧇ āωāĻĒāϞāĻŦā§āϧ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻāχ āϏ⧇āϟāĻŋāĻ‚ āύāĻŋāĻ°ā§āĻŦāĻŋāĻļ⧇āώ⧇ āĻŽā§‚āϞ āĻŽāĻžāύ⧇ āϚāĻžāϞāĻžāύ⧋ āĻšāϝāĻŧāĨ¤", - "setting_video_viewer_original_video_title": "āĻŽā§‚āϞ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŦāĻžāĻ§ā§āϝāϤāĻžāĻŽā§‚āϞāĻ• āĻ•āϰ⧁āύ", - "settings": "āϏ⧇āϟāĻŋāĻ‚āϏ", - "settings_saved": "āϏ⧇āϟāĻŋāĻ‚āϏ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "setup_pin_code": "āĻāĻ•āϟāĻŋ āĻĒāĻŋāύ āϕ⧋āĻĄ āϏ⧇āϟāφāĻĒ āĻ•āϰ⧁āύ", - "share": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧁āύ", - "share_dialog_preparing": "āĻĒā§āϰāĻ¸ā§āϤ⧁āϤ āĻ•āϰāĻž āĻšāĻšā§āϛ⧇..āĨ¤", - "share_link": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ•", - "share_original": "āĻŽā§‚āϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ (āĻŦāĻĄāĻŧ)", - "share_preview": "āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ (āϛ⧋āϟ)", - "share_quality_body": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻžāϰ āφāϗ⧇ āĻ›āĻŦāĻŋāϰ āĻŽāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻŦāĻžāϟāύāϟāĻŋ āĻšā§‡āĻĒ⧇ āϧāϰ⧇ āϰāĻžāϖ⧁āύāĨ¤", - "share_quality_title": "āφāĻĒāύāĻžāϰ āĻļ⧇āϝāĻŧāĻžāϰ āĻŽāĻžāύ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύ", - "shared": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "shared_album_activities_input_disable": "āĻŽāĻ¨ā§āϤāĻŦā§āϝ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰāĻž āφāϛ⧇", - "shared_album_activity_remove_content": "āφāĻĒāύāĻŋ āĻ•āĻŋ āĻāχ āĻ•āĻžāĻ°ā§āϝāĻ•āϞāĻžāĻĒāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", - "shared_album_activity_remove_title": "āĻ•āĻžāĻ°ā§āϝāĻ•āϞāĻžāĻĒ āĻŽā§āϛ⧁āύ", - "shared_album_section_people_action_error": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĨ⧇āϕ⧇ āĻ¤ā§āϝāĻžāĻ—/āϏāϰāĻžāύ⧋āϰ āϏāĻŽāϝāĻŧ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "shared_album_section_people_title": "āĻŦā§āϝāĻ•ā§āϤāĻŋ", - "shared_by": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧇āϛ⧇āύ", - "shared_by_user": "{user} āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧇āϛ⧇āύ", - "shared_by_you": "āφāĻĒāύāĻŋ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧇āϛ⧇āύ", - "shared_from_partner": "{partner}-āĻāϰ āĻ›āĻŦāĻŋ", - "shared_intent_upload_button_progress_text": "{current} / {total} āφāĻĒāϞ⧋āĻĄ āĻšāϝāĻŧ⧇āϛ⧇", - "shared_link_app_bar_title": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ‚āĻ•", - "shared_link_clipboard_copied_massage": "āĻ•ā§āϞāĻŋāĻĒāĻŦā§‹āĻ°ā§āĻĄā§‡ āĻ•āĻĒāĻŋ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "shared_link_create_error": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āϤ⧈āϰāĻŋ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āĻ¤ā§āϰ⧁āϟāĻŋ āĻšāϝāĻŧ⧇āϛ⧇", - "shared_link_custom_url_description": "āĻāĻ•āϟāĻŋ āĻ•āĻžāĻ¸ā§āϟāĻŽ URL āύāĻžāĻŽ āĻĻāĻŋāϝāĻŧ⧇ āĻāχ āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ•āϟāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰ⧁āύ", - "shared_link_custom_url_title": "āĻ•āĻžāĻ¸ā§āϟāĻŽ URL", - "shared_link_custom_url_warning": "āϏāϤāĻ°ā§āĻ•āϤāĻž: āĻĢāϰāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻ¸ā§āĻ˛ā§āϝāĻžāĻļāϝ⧁āĻ•ā§āϤ āĻ•āĻžāĻ¸ā§āϟāĻŽ URL āύāĻžāĻŽ āφāĻĒāύāĻžāϰ āĻĒā§āϰāĻ¤ā§āϝāĻžāĻļāĻž āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ•āĻžāϜ āύāĻžāĻ“ āĻ•āϰāϤ⧇ āĻĒāĻžāϰ⧇āĨ¤", - "shared_link_edit_description_hint": "āĻļ⧇āϝāĻŧāĻžāϰ⧇āϰ āĻŦāĻŋāĻŦāϰāĻŖ āϞāĻŋāϖ⧁āύ", - "shared_link_edit_expire_after_option_day": "ā§§ āĻĻāĻŋāύ", - "shared_link_edit_expire_after_option_days": "{count} āĻĻāĻŋāύ", - "shared_link_edit_expire_after_option_hour": "ā§§ āϘāĻŖā§āϟāĻž", - "shared_link_edit_expire_after_option_hours": "{count} āϘāĻŖā§āϟāĻž", - "shared_link_edit_expire_after_option_minute": "ā§§ āĻŽāĻŋāύāĻŋāϟ", - "shared_link_edit_expire_after_option_minutes": "{count} āĻŽāĻŋāύāĻŋāϟ", - "shared_link_edit_expire_after_option_months": "{count} āĻŽāĻžāϏ", - "shared_link_edit_expire_after_option_year": "{count} āĻŦāĻ›āϰ", - "shared_link_edit_password_hint": "āĻļ⧇āϝāĻŧāĻžāϰ⧇āϰ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϞāĻŋāϖ⧁āύ", - "shared_link_edit_submit_button": "āϞāĻŋāĻ‚āĻ• āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧁āύ", - "shared_link_error_server_url_fetch": "āϏāĻžāĻ°ā§āĻ­āĻžāϰ URL āφāύāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "shared_link_expires_day": "{count} āĻĻāĻŋāύ⧇ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_days": "{count} āĻĻāĻŋāύ⧇ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_hour": "{count} āϘāĻŖā§āϟāĻžāϝāĻŧ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_hours": "{count} āϘāĻŖā§āϟāĻžāϝāĻŧ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_minute": "{count} āĻŽāĻŋāύāĻŋāĻŸā§‡ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_minutes": "{count} āĻŽāĻŋāύāĻŋāĻŸā§‡ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_never": "āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇ āύāĻž ∞", - "shared_link_expires_second": "{count} āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_expires_seconds": "{count} āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡ āĻŽā§‡āϝāĻŧāĻžāĻĻ āĻļ⧇āώ āĻšāĻŦ⧇", - "shared_link_individual_shared": "āĻŦā§āϝāĻ•ā§āϤāĻŋāĻ—āϤ āĻļ⧇āϝāĻŧāĻžāϰ", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ‚āĻ• āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "shared_link_options": "āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻ…āĻĒāĻļāύ", - "shared_link_password_description": "āĻāχ āĻļ⧇āϝāĻŧāĻžāϰ āϞāĻŋāĻ‚āĻ• āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻŦāĻļā§āϝāĻ• āĻ•āϰ⧁āύ", - "shared_links": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ‚āĻ•", - "shared_links_description": "āĻāĻ•āϟāĻŋ āϞāĻŋāĻ‚āĻ• āĻĻāĻŋāϝāĻŧ⧇ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰ⧁āύ", - "shared_photos_and_videos_count": "{assetCount, plural, other {#āϟāĻŋ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“āĨ¤}}", - "shared_with_me": "āφāĻŽāĻžāϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "shared_with_partner": "{partner}-āĻāϰ āϏāĻžāĻĨ⧇ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "sharing": "āĻļ⧇āϝāĻŧāĻžāϰāĻŋāĻ‚", - "sharing_enter_password": "āĻāχ āĻĒ⧇āϜāϟāĻŋ āĻĻ⧇āĻ–āϤ⧇ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āϞāĻŋāϖ⧁āύāĨ¤", - "shift_to_permanent_delete": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āĻ›āϤ⧇ ⇧ āϚāĻžāĻĒ⧁āύ", - "show_album_options": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻ…āĻĒāĻļāύ āĻĻ⧇āĻ–āĻžāύ", - "show_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĻ⧇āĻ–āĻžāύ", - "show_all_people": "āϏāĻŦ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĻ⧇āĻ–āĻžāύ", - "show_and_hide_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĻ⧇āĻ–āĻžāύ āĻ“ āϞ⧁āĻ•āĻžāύ", - "show_file_location": "āĻĢāĻžāχāϞ⧇āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āĻĻ⧇āĻ–āĻžāύ", - "show_gallery": "āĻ—ā§āϝāĻžāϞāĻžāϰāĻŋ āĻĻ⧇āĻ–āĻžāύ", - "show_hidden_people": "āϞ⧁āĻ•āĻžāύ⧋ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻĻ⧇āĻ–āĻžāύ", - "show_in_timeline": "āϟāĻžāχāĻŽāϞāĻžāχāύ⧇ āĻĻ⧇āĻ–āĻžāύ", - "show_in_timeline_setting_description": "āĻāχ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āφāĻĒāύāĻžāϰ āϟāĻžāχāĻŽāϞāĻžāχāύ⧇ āĻĻ⧇āĻ–āĻžāύ", - "show_keyboard_shortcuts": "āϕ⧀āĻŦā§‹āĻ°ā§āĻĄ āĻļāĻ°ā§āϟāĻ•āĻžāϟ āĻĻ⧇āĻ–āĻžāύ", - "show_less": "āĻ•āĻŽ āĻĻ⧇āĻ–āĻžāύ", - "show_metadata": "āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻĻ⧇āĻ–āĻžāύ", - "show_more_fields": "{count, plural, one {āφāϰāĻ“ #āϟāĻŋ āĻĢāĻŋāĻ˛ā§āĻĄ āĻĻ⧇āĻ–āĻžāύ} other {āφāϰāĻ“ #āϟāĻŋ āĻĢāĻŋāĻ˛ā§āĻĄ āĻĻ⧇āĻ–āĻžāύ}}", - "show_or_hide_info": "āϤāĻĨā§āϝ āĻĻ⧇āĻ–āĻžāύ āĻŦāĻž āϞ⧁āĻ•āĻžāύ", - "show_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĻ⧇āĻ–āĻžāύ", - "show_person_options": "āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ āĻ…āĻĒāĻļāύ āĻĻ⧇āĻ–āĻžāύ", - "show_progress_bar": "āĻĒā§āϰāĻ—ā§āϰ⧇āϏ āĻŦāĻžāϰ āĻĻ⧇āĻ–āĻžāύ", - "show_schema": "āĻ¸ā§āĻ•āĻŋāĻŽāĻž āĻĻ⧇āĻ–āĻžāύ", - "show_search_options": "āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻ…āĻĒāĻļāύ āĻĻ⧇āĻ–āĻžāύ", - "show_shared_links": "āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ‚āĻ• āĻĻ⧇āĻ–āĻžāύ", - "show_slideshow_metadata_overlay": "āĻ›āĻŦāĻŋāϰ āϤāĻĨā§āϝ āĻ“āĻ­āĻžāϰāϞ⧇ āĻĻ⧇āĻ–āĻžāύ", - "show_slideshow_transition": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āĻŸā§āϰāĻžāύāϜāĻŋāĻļāύ āĻĻ⧇āĻ–āĻžāύ", - "show_supporter_badge": "āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ āĻŦā§āϝāĻžāϜ", - "show_supporter_badge_description": "āĻāĻ•āϟāĻŋ āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ āĻŦā§āϝāĻžāϜ āĻĻ⧇āĻ–āĻžāύ", - "show_text_recognition": "āĻŸā§‡āĻ•ā§āϏāϟ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ āĻĻ⧇āĻ–āĻžāύ", - "show_text_search_menu": "āĻŸā§‡āĻ•ā§āϏāϟ āĻ…āύ⧁āϏāĻ¨ā§āϧāĻžāύ āĻŽā§‡āύ⧁ āĻĻ⧇āĻ–āĻžāύ", - "shuffle": "āĻļāĻžāĻĢāϞ", - "sidebar": "āϏāĻžāχāĻĄāĻŦāĻžāϰ", - "sidebar_display_description": "āϏāĻžāχāĻĄāĻŦāĻžāϰ⧇ āĻ­āĻŋāωāϝāĻŧ⧇āϰ āĻāĻ•āϟāĻŋ āϞāĻŋāĻ‚āĻ• āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ āĻ•āϰ⧁āύ", - "sign_out": "āϏāĻžāχāύ āφāωāϟ", - "sign_up": "āϏāĻžāχāύ āφāĻĒ", - "size": "āφāĻ•āĻžāϰ", - "skip": "āĻāĻĄāĻŧāĻŋāϝāĻŧ⧇ āϝāĻžāύ", - "skip_to_content": "āĻ•āĻ¨ā§āĻŸā§‡āĻ¨ā§āĻŸā§‡ āϝāĻžāύ", - "skip_to_folders": "āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āϝāĻžāύ", - "skip_to_tags": "āĻŸā§āϝāĻžāϗ⧇ āϝāĻžāύ", - "slideshow": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹", - "slideshow_body": "āφāϰāĻžāĻŽ āĻ•āϰ⧇ āĻŦāϏ⧁āύ āĻāĻŦāĻ‚ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋāϗ⧁āϞ⧋ āĻĢ⧁āϞāĻ¸ā§āĻ•ā§āϰāĻŋāύ āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹āϤ⧇ āĻĻ⧇āϖ⧁āύāĨ¤", - "slideshow_metadata_overlay_mode": "āĻ“āĻ­āĻžāϰāϞ⧇ āĻ•āĻ¨ā§āĻŸā§‡āĻ¨ā§āϟ", - "slideshow_metadata_overlay_mode_description_only": "āĻļ⧁āϧ⧁ āĻŦāĻŋāĻŦāϰāĻŖ", - "slideshow_metadata_overlay_mode_full": "āϏāĻŽā§āĻĒā§‚āĻ°ā§āĻŖ", - "slideshow_repeat": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āĻĒ⧁āύāϰāĻžāĻŦ⧃āĻ¤ā§āϤāĻŋ āĻ•āϰ⧁āύ", - "slideshow_repeat_description": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āĻļ⧇āώ āĻšāϞ⧇ āĻļ⧁āϰ⧁āϤ⧇ āĻĢāĻŋāϰ⧇ āϝāĻžāύ", - "slideshow_settings": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹ āϏ⧇āϟāĻŋāĻ‚āϏ", - "slideshow_title": "āĻ¸ā§āϞāĻžāχāĻĄāĻļā§‹", - "smart_album": "āĻ¸ā§āĻŽāĻžāĻ°ā§āϟ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", - "some_assets_already_have_a_location_warning": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ•āĻŋāϛ⧁ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āχāϤāĻŋāĻŽāĻ§ā§āϝ⧇ āĻāĻ•āϟāĻŋ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āϰāϝāĻŧ⧇āϛ⧇", - "sort_albums_by": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻžāϜāĻžāύ..āĨ¤", - "sort_created": "āϤ⧈āϰāĻŋāϰ āϤāĻžāϰāĻŋāĻ–", - "sort_items": "āφāχāĻŸā§‡āĻŽā§‡āϰ āϏāĻ‚āĻ–ā§āϝāĻž", - "sort_modified": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ⧇āϰ āϤāĻžāϰāĻŋāĻ–", - "sort_oldest": "āϏāĻŦāĻšā§‡āϝāĻŧ⧇ āĻĒ⧁āϰāĻžāϤāύ āĻ›āĻŦāĻŋ", - "sort_people_by_similarity": "āϏāĻžāĻĻ⧃āĻļā§āϝ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻŦā§āϝāĻ•ā§āϤāĻŋ āϏāĻžāϜāĻžāύ", - "sort_recent": "āϏāĻŦāĻšā§‡āϝāĻŧ⧇ āϏāĻžāĻŽā§āĻĒā§āϰāϤāĻŋāĻ• āĻ›āĻŦāĻŋ", - "sort_title": "āĻļāĻŋāϰ⧋āύāĻžāĻŽ", - "source": "āϏ⧋āĻ°ā§āϏ", - "stack": "āĻ¸ā§āĻŸā§āϝāĻžāĻ•", - "stack_action_prompt": "{count}āϟāĻŋ āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "stack_duplicates": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰ⧁āύ", - "stack_selected_photos": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ›āĻŦāĻŋ āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰ⧁āύ", - "stacked_assets_count": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "stacktrace": "āĻ¸ā§āĻŸā§āϝāĻžāĻ•āĻŸā§āϰ⧇āϏ", - "start": "āĻļ⧁āϰ⧁ āĻ•āϰ⧁āύ", - "start_date": "āĻļ⧁āϰ⧁āϰ āϤāĻžāϰāĻŋāĻ–", - "start_date_before_end_date": "āĻļ⧁āϰ⧁āϰ āϤāĻžāϰāĻŋāĻ– āĻ…āĻŦāĻļā§āϝāχ āĻļ⧇āώ āϤāĻžāϰāĻŋāϖ⧇āϰ āφāϗ⧇ āĻšāϤ⧇ āĻšāĻŦ⧇", - "state": "āĻ¸ā§āĻŸā§‡āϟ", - "status": "āĻ…āĻŦāĻ¸ā§āĻĨāĻž", - "step_delete": "āϧāĻžāĻĒ āĻŽā§āϛ⧁āύ", - "step_delete_confirm": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āϧāĻžāĻĒāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", - "step_details": "āϧāĻžāĻĒ⧇āϰ āĻŦāĻŋāĻŦāϰāĻŖ", - "steps": "āϧāĻžāĻĒāϏāĻŽā§‚āĻš", - "stop_casting": "āĻ•āĻžāĻ¸ā§āϟāĻŋāĻ‚ āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ", - "stop_motion_photo": "āĻŽā§‹āĻļāύ āĻĢāĻŸā§‹ āĻĨāĻžāĻŽāĻžāύ", - "stop_photo_sharing": "āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻŦāĻ¨ā§āϧ āĻ•āϰāĻŦ⧇āύ?", - "stop_photo_sharing_description": "{partner} āφāϰ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ…ā§āϝāĻžāĻ•ā§āϏ⧇āϏ āĻ•āϰāϤ⧇ āĻĒāĻžāϰāĻŦ⧇ āύāĻžāĨ¤", - "stop_sharing_photos_with_user": "āĻāχ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āϏāĻžāĻĨ⧇ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻļ⧇āϝāĻŧāĻžāϰ āĻ•āϰāĻž āĻŦāĻ¨ā§āϧ āĻ•āϰ⧁āύ", - "storage": "āϏāĻ‚āϰāĻ•ā§āώāĻŖāĻžāĻ—āĻžāϰ āĻ¸ā§āĻĨāĻžāύ", - "storage_label": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϞ⧇āĻŦ⧇āϞ", - "storage_quota": "āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āϕ⧋āϟāĻž", - "storage_usage": "{available}-āĻāϰ āĻŽāĻ§ā§āϝ⧇ {used} āĻŦā§āϝāĻŦāĻšā§ƒāϤ", - "submit": "āϜāĻŽāĻž āĻĻāĻŋāύ", - "success": "āϏāĻĢāϞ", - "suggestions": "āĻĒāϰāĻžāĻŽāĻ°ā§āĻļ", - "sunrise_on_the_beach": "āϏ⧈āĻ•āϤ⧇ āϏ⧂āĻ°ā§āϝ⧋āĻĻāϝāĻŧ", - "support": "āϏāĻšāĻžāϝāĻŧāϤāĻž", - "support_and_feedback": "āϏāĻšāĻžāϝāĻŧāϤāĻž āĻ“ āĻŽāϤāĻžāĻŽāϤ", - "support_third_party_description": "āφāĻĒāύāĻžāϰ Immich āχāύāĻ¸ā§āϟāϞ⧇āĻļāύāϟāĻŋ āĻāĻ•āϟāĻŋ āϤ⧃āϤ⧀āϝāĻŧ āĻĒāĻ•ā§āώ āĻĻā§āĻŦāĻžāϰāĻž āĻĒā§āϝāĻžāϕ⧇āϜ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āĻ›āĻŋāϞāĨ¤ āφāĻĒāύāĻŋ āϝ⧇ āϏāĻŽāĻ¸ā§āϝāĻžāϗ⧁āϞ⧋ āĻ…āύ⧁āĻ­āĻŦ āĻ•āϰāϛ⧇āύ āϤāĻž āϏ⧇āχ āĻĒā§āϝāĻžāϕ⧇āĻœā§‡āϰ āĻ•āĻžāϰāϪ⧇ āĻšāϤ⧇ āĻĒāĻžāϰ⧇, āϤāĻžāχ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āύāĻŋāĻšā§‡āϰ āϞāĻŋāĻ‚āĻ•āϗ⧁āϞ⧋ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇ āĻĒā§āϰāĻĨāĻŽā§‡ āϤāĻžāĻĻ⧇āϰ āĻ•āĻžāϛ⧇ āϏāĻŽāĻ¸ā§āϝāĻž āϜāĻžāύāĻžāύāĨ¤", - "supporter": "āϏāĻžāĻĒā§‹āĻ°ā§āϟāĻžāϰ", - "swap_merge_direction": "āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻžāϰ āĻĻāĻŋāĻ• āĻ…āĻĻāϞāĻŦāĻĻāϞ āĻ•āϰ⧁āύ", - "sync": "āϏāĻŋāĻ™ā§āĻ•", - "sync_albums": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āϏāĻŋāĻ™ā§āĻ• āĻ•āϰ⧁āύ", - "sync_local": "āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āϏāĻŋāĻ™ā§āĻ•", - "sync_remote": "āĻĻā§‚āϰāĻŦāĻ°ā§āϤ⧀ āϏāĻŋāĻ™ā§āĻ•", - "sync_status": "āϏāĻŋāĻ™ā§āĻ• āĻ¸ā§āĻŸā§āϝāĻžāϟāĻžāϏ", - "sync_status_subtitle": "āϏāĻŋāĻ™ā§āĻ• āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻĻ⧇āϖ⧁āύ āĻ“ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "sync_upload_album_setting_subtitle": "Immich-āĻ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āφāĻĒāύāĻžāϰ āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āϤ⧈āϰāĻŋ āĻ•āϰ⧁āύ āĻ“ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "system_theme": "āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻĨāĻŋāĻŽ", - "system_theme_command_description": "āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻĨāĻŋāĻŽ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ ({value})", - "tag": "āĻŸā§āϝāĻžāĻ—", - "tag_assets": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŸā§āϝāĻžāĻ— āĻ•āϰ⧁āύ", - "tag_created": "āĻŸā§āϝāĻžāĻ— āϤ⧈āϰāĻŋ āĻšāϝāĻŧ⧇āϛ⧇: {tag}", - "tag_face": "āĻŽā§āĻ– āĻŸā§āϝāĻžāĻ— āĻ•āϰ⧁āύ", - "tag_feature_description": "āϞāϜāĻŋāĻ•ā§āϝāĻžāϞ āĻŸā§āϝāĻžāĻ— āĻŦāĻŋāώāϝāĻŧ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ—ā§āϰ⧁āĻĒ āĻ•āϰāĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻŦā§āϰāĻžāωāϜ āĻ•āϰāĻž", - "tag_people": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻŸā§āϝāĻžāĻ— āĻ•āϰ⧁āύ", - "tag_updated": "āĻŸā§āϝāĻžāĻ— āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇: {tag}", - "tagged_assets": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻŸā§āϝāĻžāĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "tags": "āĻŸā§āϝāĻžāĻ—", - "tap_to_run_job": "āϜāĻŦ āϚāĻžāϞāĻžāϤ⧇ āĻŸā§āϝāĻžāĻĒ āĻ•āϰ⧁āύ", - "template": "āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ", - "text_recognition": "āĻŸā§‡āĻ•ā§āϏāϟ āϰāĻŋāĻ•āĻ—āύāĻŋāĻļāύ", - "theme": "āĻĨāĻŋāĻŽ", - "theme_selection": "āĻĨāĻŋāĻŽ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ", - "theme_selection_description": "āφāĻĒāύāĻžāϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ⧇āϰ āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āĻĒāĻ›āĻ¨ā§āĻĻ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āĻĨāĻŋāĻŽ āϞāĻžāχāϟ āĻŦāĻž āĻĄāĻžāĻ°ā§āĻ• āϏ⧇āϟ āĻ•āϰ⧁āύ", - "theme_setting_asset_list_storage_indicator_title": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϟāĻžāχāϞ⧇ āĻ¸ā§āĻŸā§‹āϰ⧇āϜ āχāĻ¨ā§āĻĄāĻŋāϕ⧇āϟāϰ āĻĻ⧇āĻ–āĻžāύ", - "theme_setting_asset_list_tiles_per_row_title": "āĻĒā§āϰāϤāĻŋ āϏāĻžāϰāĻŋāϤ⧇ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āϏāĻ‚āĻ–ā§āϝāĻž ({count})", - "theme_setting_colorful_interface_subtitle": "āĻŦā§āϝāĻžāĻ•āĻ—ā§āϰāĻžāωāĻ¨ā§āĻĄ āĻĒ⧃āĻˇā§āϠ⧇ āĻĒā§āϰāĻžāχāĻŽāĻžāϰāĻŋ āϰāĻ™ āĻĒā§āϰāϝāĻŧā§‹āĻ— āĻ•āϰ⧁āύāĨ¤", - "theme_setting_colorful_interface_title": "āϰāĻ™āĻŋāύ āχāĻ¨ā§āϟāĻžāϰāĻĢ⧇āϏ", - "theme_setting_primary_color_subtitle": "āĻĒā§āϰāĻžāχāĻŽāĻžāϰāĻŋ āĻ…ā§āϝāĻžāĻ•āĻļāύ āĻ“ āĻ…ā§āϝāĻžāĻ•āϏ⧇āĻ¨ā§āĻŸā§‡āϰ āϜāĻ¨ā§āϝ āĻāĻ•āϟāĻŋ āϰāĻ™ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰ⧁āύāĨ¤", - "theme_setting_primary_color_title": "āĻĒā§āϰāĻžāχāĻŽāĻžāϰāĻŋ āϰāĻ™", - "theme_setting_system_primary_color_title": "āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āϰāĻ™ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "theme_setting_system_theme_switch": "āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧ (āϏāĻŋāĻ¸ā§āĻŸā§‡āĻŽ āϏ⧇āϟāĻŋāĻ‚ āĻ…āύ⧁āϏāϰāĻŖ āĻ•āϰ⧁āύ)", - "then": "āϤāĻžāϰāĻĒāϰ", - "they_will_be_merged_together": "āϤāĻžāĻĻ⧇āϰ āĻāĻ•āϏāĻžāĻĨ⧇ āĻāĻ•āĻ¤ā§āϰ āĻ•āϰāĻž āĻšāĻŦ⧇", - "third_party_resources": "āϤ⧃āϤ⧀āϝāĻŧ-āĻĒāĻ•ā§āώ⧇āϰ āϰāĻŋāϏ⧋āĻ°ā§āϏ", - "time": "āϏāĻŽāϝāĻŧ", - "time_based_memories": "āϏāĻŽāϝāĻŧ-āĻ­āĻŋāĻ¤ā§āϤāĻŋāĻ• āĻ¸ā§āĻŽā§ƒāϤāĻŋ", - "time_based_memories_duration": "āĻĒā§āϰāϤāĻŋāϟāĻŋ āĻ›āĻŦāĻŋ āĻĒā§āϰāĻĻāĻ°ā§āĻļāύ⧇āϰ āϏāĻŽāϝāĻŧāĻ•āĻžāϞ (āϏ⧇āϕ⧇āĻ¨ā§āĻĄā§‡)āĨ¤", - "timeline": "āϟāĻžāχāĻŽāϞāĻžāχāύ", - "timezone": "āϟāĻžāχāĻŽāĻœā§‹āύ", - "to_archive": "āφāĻ°ā§āĻ•āĻžāχāĻ­", - "to_change_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰ⧁āύ", - "to_favorite": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ", - "to_login": "āϞāĻ—āχāύ", - "to_multi_select": "āĻāĻ•āĻžāϧāĻŋāĻ• āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇", - "to_parent": "āĻŽā§‚āϞ āĻĢā§‹āĻ˛ā§āĻĄāĻžāϰ⧇ āϝāĻžāύ", - "to_select": "āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻ•āϰāϤ⧇", - "to_trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ", - "toggle_settings": "āϏ⧇āϟāĻŋāĻ‚āϏ āϟāĻ—āϞ āĻ•āϰ⧁āύ", - "toggle_theme_description": "āĻĨāĻŋāĻŽ āϟāĻ—āϞ āĻ•āϰ⧁āύ", - "total": "āĻŽā§‹āϟ", - "total_usage": "āĻŽā§‹āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ", - "trash": "āĻŸā§āĻ°ā§āϝāĻžāĻļ", - "trash_action_prompt": "{count}āϟāĻŋ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "trash_all": "āϏāĻŦ āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĒāĻžāĻ āĻžāύ", - "trash_count": "āĻŸā§āĻ°ā§āϝāĻžāĻļ {count, number}", - "trash_delete_asset": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŸā§āĻ°ā§āϝāĻžāĻļ/āĻŽā§āϛ⧁āύ", - "trash_no_results_message": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĨāĻžāĻ•āĻž āĻ›āĻŦāĻŋ āĻ“ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻāĻ–āĻžāύ⧇ āĻĻ⧇āĻ–āĻžāύ⧋ āĻšāĻŦ⧇āĨ¤", - "trash_page_delete_all": "āϏāĻŦ āĻŽā§āϛ⧁āύ", - "trash_page_info": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĨāĻžāĻ•āĻž āφāχāĻŸā§‡āĻŽāϗ⧁āϞ⧋ {days} āĻĻāĻŋāύ āĻĒāϰ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇", - "trashed_items_will_be_permanently_deleted_after": "āĻŸā§āĻ°ā§āϝāĻžāĻļ⧇ āĻĨāĻžāĻ•āĻž āφāχāĻŸā§‡āĻŽāϗ⧁āϞ⧋ {days, plural, one {#āĻĻāĻŋāύ} other {#āĻĻāĻŋāύ}} āĻĒāϰ āĻ¸ā§āĻĨāĻžāϝāĻŧā§€āĻ­āĻžāĻŦ⧇ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāĻŦ⧇āĨ¤", - "trigger": "āĻŸā§āϰāĻŋāĻ—āĻžāϰ", - "trigger_asset_metadata_extraction": "āĻ…ā§āϝāĻžāϏ⧇āϟ āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•āĻļāύ", - "trigger_asset_metadata_extraction_description": "āĻāĻ•āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ EXIF āĻŽā§‡āϟāĻžāĻĄā§‡āϟāĻž āĻāĻ•ā§āϏāĻŸā§āĻ°ā§āϝāĻžāĻ•ā§āϟ āĻ•āϰāĻž āĻšāϞ⧇ āĻŸā§āϰāĻŋāĻ—āĻžāϰ āĻšāϝāĻŧ", - "trigger_asset_uploaded": "āĻ…ā§āϝāĻžāϏ⧇āϟ āφāĻĒāϞ⧋āĻĄ", - "trigger_asset_uploaded_description": "āĻāĻ•āϟāĻŋ āύāϤ⧁āύ āĻ…ā§āϝāĻžāϏ⧇āϟ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻšāϞ⧇ āĻŸā§āϰāĻŋāĻ—āĻžāϰ āĻšāϝāĻŧ", - "trigger_person_recognized": "āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤ āĻšāϝāĻŧ⧇āϛ⧇", - "trigger_person_recognized_description": "āĻāĻ•āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋ āĻļāύāĻžāĻ•ā§āϤ āĻšāϞ⧇ āĻŸā§āϰāĻŋāĻ—āĻžāϰ āĻšāϝāĻŧ", - "troubleshoot": "āϏāĻŽāĻ¸ā§āϝāĻž āϏāĻŽāĻžāϧāĻžāύ", - "type": "āϧāϰāύ", - "unable_to_change_pin_code": "āĻĒāĻŋāύ āϕ⧋āĻĄ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_check_version": "āĻ…ā§āϝāĻžāĻĒ āĻŦāĻž āϏāĻžāĻ°ā§āĻ­āĻžāϰ āĻ­āĻžāĻ°ā§āϏāύ āϝāĻžāϚāĻžāχ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unable_to_setup_pin_code": "āĻĒāĻŋāύ āϕ⧋āĻĄ āϏ⧇āϟāφāĻĒ āĻ•āϰāĻž āϝāĻžāϝāĻŧāύāĻŋ", - "unarchive": "āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻĨ⧇āϕ⧇ āĻŦ⧇āϰ āĻ•āϰ⧁āύ", - "unarchive_action_prompt": "{count}āϟāĻŋ āφāĻ°ā§āĻ•āĻžāχāĻ­ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "unarchived_count": "{count, plural, other {#āϟāĻŋ āφāύāφāĻ°ā§āĻ•āĻžāχāĻ­ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇}}", - "undo": "āĻĒā§‚āĻ°ā§āĻŦāĻžāĻŦāĻ¸ā§āĻĨāĻžāϝāĻŧ āĻĢāĻŋāϰ⧁āύ", - "unfavorite": "āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻŦāĻžāĻĻ āĻĻāĻŋāύ", - "unfavorite_action_prompt": "{count}āϟāĻŋ āĻĢ⧇āĻ­āĻžāϰāĻŋāϟ āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ⧋ āĻšāϝāĻŧ⧇āϛ⧇", - "unhide_person": "āĻŦā§āϝāĻ•ā§āϤāĻŋāϕ⧇ āĻĻ⧃āĻļā§āϝāĻŽāĻžāύ āĻ•āϰ⧁āύ", - "unknown": "āĻ…āϜāĻžāύāĻž", - "unknown_country": "āĻ…āϜāĻžāύāĻž āĻĻ⧇āĻļ", - "unknown_date": "āĻ…āϜāĻžāύāĻž āϤāĻžāϰāĻŋāĻ–", - "unknown_schema": "āĻ…āϜāĻžāύāĻž āĻ¸ā§āĻ•āĻŋāĻŽāĻž", - "unknown_year": "āĻ…āϜāĻžāύāĻž āĻŦāĻ›āϰ", - "unlimited": "āϏ⧀āĻŽāĻžāĻšā§€āύ", - "unlink_motion_video": "āĻŽā§‹āĻļāύ āĻ­āĻŋāĻĄāĻŋāĻ“ āφāύāϞāĻŋāĻ‚āĻ• āĻ•āϰ⧁āύ", - "unlink_oauth": "OAuth āφāύāϞāĻŋāĻ‚āĻ• āĻ•āϰ⧁āύ", - "unlinked_oauth_account": "āφāύāϞāĻŋāĻ‚āĻ• āĻ•āϰāĻž OAuth āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ", - "unmute_memories": "āĻ¸ā§āĻŽā§ƒāϤāĻŋ āφāύāĻŽāĻŋāωāϟ āĻ•āϰ⧁āύ", - "unnamed_album": "āύāĻžāĻŽāĻŦāĻŋāĻšā§€āύ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ", - "unnamed_album_delete_confirmation": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽāϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", - "unnamed_share": "āύāĻžāĻŽāĻŦāĻŋāĻšā§€āύ āĻļ⧇āϝāĻŧāĻžāϰ", - "unsaved_change": "āϏāĻ‚āϰāĻ•ā§āώāĻŋāϤ āĻšāϝāĻŧāύāĻŋ āĻāĻŽāύ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ", - "unselect_all": "āϏāĻŦ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", - "unselect_all_duplicates": "āϏāĻŦ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āĻŸā§‡āϰ āύāĻŋāĻ°ā§āĻŦāĻžāϚāύ āĻŦāĻžāϤāĻŋāϞ āĻ•āϰ⧁āύ", - "unstack": "āφāύāĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰ⧁āύ", - "unstack_action_prompt": "{count}āϟāĻŋ āφāύāĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "unstacked_assets_count": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āφāύāĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "unsupported_field_type": "āĻ…āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āĻĢāĻŋāĻ˛ā§āĻĄ āϧāϰāύ", - "unsupported_file_type": "{file} āĻĢāĻžāχāϞāϟāĻŋ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āϝāĻžāĻŦ⧇ āύāĻž āĻ•āĻžāϰāĻŖ āĻāϰ āĻĢāĻžāχāϞ⧇āϰ āϧāϰāύ {type} āϏāĻŽāĻ°ā§āĻĨāĻŋāϤ āύāϝāĻŧāĨ¤", - "untagged": "āĻŸā§āϝāĻžāĻ—āĻŦāĻŋāĻšā§€āύ", - "up_next": "āĻĒāϰāĻŦāĻ°ā§āϤ⧀āϤ⧇", - "update_location_action_prompt": "{count}āϟāĻŋ āύāĻŋāĻ°ā§āĻŦāĻžāϚāĻŋāϤ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻ…āĻŦāĻ¸ā§āĻĨāĻžāύ āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧁āύ āĻāϟāĻŋ āĻĻāĻŋāϝāĻŧ⧇:", - "updated_at": "āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", - "updated_password": "āĻĒāĻžāϏāĻ“āϝāĻŧāĻžāĻ°ā§āĻĄ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", - "upload": "āφāĻĒāϞ⧋āĻĄ", - "upload_concurrency": "āφāĻĒāϞ⧋āĻĄ āĻ•āύāĻ•āĻžāϰ⧇āĻ¨ā§āϏāĻŋ", - "upload_day_count": "{date}: {count, plural, one {#āϟāĻŋ āφāĻĒāϞ⧋āĻĄ} other {#āϟāĻŋ āφāĻĒāϞ⧋āĻĄ}}", - "upload_details": "āφāĻĒāϞ⧋āĻĄā§‡āϰ āĻŦāĻŋāĻŦāϰāĻŖ", - "upload_error_with_count": "{count, plural, one {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻ…ā§āϝāĻžāϏ⧇āϟ}}-āĻāϰ āϜāĻ¨ā§āϝ āφāĻĒāϞ⧋āĻĄ āĻ¤ā§āϰ⧁āϟāĻŋ", - "upload_errors": "āφāĻĒāϞ⧋āĻĄ {count, plural, one {#āϟāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋ} other {#āϟāĻŋ āĻ¤ā§āϰ⧁āϟāĻŋ}} āϏāĻš āϏāĻŽā§āĻĒāĻ¨ā§āύ āĻšāϝāĻŧ⧇āϛ⧇, āύāϤ⧁āύ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āĻ–āϤ⧇ āĻĒ⧇āϜāϟāĻŋ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύāĨ¤", - "upload_finished": "āφāĻĒāϞ⧋āĻĄ āĻļ⧇āώ āĻšāϝāĻŧ⧇āϛ⧇", - "upload_progress": "āĻŦāĻžāĻ•āĻŋ {remaining, number} - āĻĒā§āϰāĻ•ā§āϰāĻŋāϝāĻŧāĻžāĻ•ā§ƒāϤ {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {#āϟāĻŋ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻ…ā§āϝāĻžāϏ⧇āϟ} other {#āϟāĻŋ āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ āĻ…ā§āϝāĻžāϏ⧇āϟ}} āĻāĻĄāĻŧāĻŋāϝāĻŧ⧇ āϝāĻžāĻ“āϝāĻŧāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "upload_status_duplicates": "āĻĄā§āĻĒā§āϞāĻŋāϕ⧇āϟ", - "upload_status_errors": "āĻ¤ā§āϰ⧁āϟāĻŋ", - "upload_status_uploaded": "āφāĻĒāϞ⧋āĻĄ āĻšāϝāĻŧ⧇āϛ⧇", - "upload_success": "āφāĻĒāϞ⧋āĻĄ āϏāĻĢāϞ āĻšāϝāĻŧ⧇āϛ⧇, āύāϤ⧁āύ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻž āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āĻ–āϤ⧇ āĻĒ⧇āϜāϟāĻŋ āϰāĻŋāĻĢā§āϰ⧇āĻļ āĻ•āϰ⧁āύāĨ¤", - "upload_to_album_body": "āϝ⧇āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āϝāĻžāύ⧁āϝāĻŧāĻžāϞ āφāĻĒāϞ⧋āĻĄ āĻĢāĻŋāϚāĻžāϰ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇āύ āύāĻž, āϤāĻžāϰāĻž āĻāĻ–āύ āĻ¸ā§āĻĨāĻžāύ⧀āϝāĻŧ āĻ›āĻŦāĻŋ āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻžāϰ āϏāĻŽāϝāĻŧ āϏāϰāĻžāϏāϰāĻŋ āĻāĻ•āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰāĻžāϰ āĻ…āĻĒāĻļāύ āĻŦ⧇āϛ⧇ āύāĻŋāϤ⧇ āĻĒāĻžāϰāĻŦ⧇āύ, āφāĻĒāϞ⧋āĻĄ āĻ•āϰāĻžāϰ āĻĒāϰ āφāϞāĻžāĻĻāĻž āĻ•āϰ⧇ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āϝ⧋āĻ— āĻ•āϰāĻžāϰ āφāϰ āĻĒā§āϰāϝāĻŧā§‹āϜāύ āύ⧇āχāĨ¤", - "upload_to_album_title": "āϏāϰāĻžāϏāϰāĻŋ āĻāĻ•āϟāĻŋ āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύ", - "upload_to_immich": "Immich-āĻ āφāĻĒāϞ⧋āĻĄ āĻ•āϰ⧁āύ ({count})", - "uploading": "āφāĻĒāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "uploading_media": "āĻŽāĻŋāĻĄāĻŋāϝāĻŧāĻž āφāĻĒāϞ⧋āĻĄ āĻšāĻšā§āϛ⧇", - "uploads": "āφāĻĒāϞ⧋āĻĄ", - "uploads_count": "{count, plural, one {#āϟāĻŋ āφāĻĒāϞ⧋āĻĄ} other {#āϟāĻŋ āφāĻĒāϞ⧋āĻĄ}}", - "url": "URL", - "usage": "āĻŦā§āϝāĻŦāĻšāĻžāϰ", - "use_biometric": "āĻŦāĻžāϝāĻŧā§‹āĻŽā§‡āĻŸā§āϰāĻŋāĻ• āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "use_browser_locale": "āĻŦā§āϰāĻžāωāϜāĻžāϰ āϞ⧋āϕ⧇āϞ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "use_browser_locale_description": "āφāĻĒāύāĻžāϰ āĻŦā§āϰāĻžāωāϜāĻžāϰ āϞ⧋āϕ⧇āϞ āĻ…āύ⧁āϝāĻžāϝāĻŧā§€ āϤāĻžāϰāĻŋāĻ–, āϏāĻŽāϝāĻŧ āĻ“ āϏāĻ‚āĻ–ā§āϝāĻž āĻĢāϰāĻŽā§āϝāĻžāϟ āĻ•āϰ⧁āύ", - "use_current_connection": "āĻŦāĻ°ā§āϤāĻŽāĻžāύ āϏāĻ‚āϝ⧋āĻ— āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "use_custom_date_range": "āĻĒāϰāĻŋāĻŦāĻ°ā§āϤ⧇ āĻ•āĻžāĻ¸ā§āϟāĻŽ āϤāĻžāϰāĻŋāϖ⧇āϰ āĻĒāϰāĻŋāϏ⧀āĻŽāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "use_template": "āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧁āύ", - "user": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀", - "user_has_been_deleted": "āĻāχ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇āĨ¤", - "user_id": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ ID", - "user_liked": "{user} {type, select, photo {āĻāχ āĻ›āĻŦāĻŋāϟāĻŋ} video {āĻāχ āĻ­āĻŋāĻĄāĻŋāĻ“āϟāĻŋ} asset {āĻāχ āĻ…ā§āϝāĻžāϏ⧇āϟāϟāĻŋ} other {āĻāϟāĻŋ}} āϞāĻžāχāĻ• āĻ•āϰ⧇āϛ⧇āύ", - "user_pin_code_settings": "āĻĒāĻŋāύ āϕ⧋āĻĄ", - "user_pin_code_settings_description": "āφāĻĒāύāĻžāϰ āĻĒāĻŋāύ āϕ⧋āĻĄ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "user_privacy": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻ—ā§‹āĻĒāύ⧀āϝāĻŧāϤāĻž", - "user_purchase_settings": "āĻ•ā§āϰāϝāĻŧ", - "user_purchase_settings_description": "āφāĻĒāύāĻžāϰ āĻ•ā§āϰāϝāĻŧ āĻĒāϰāĻŋāϚāĻžāϞāύāĻž āĻ•āϰ⧁āύ", - "user_usage_detail": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āϰ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻŦāĻŋāĻŦāϰāĻŖ", "user_usage_stats": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻĒāϰāĻŋāϏāĻ‚āĻ–ā§āϝāĻžāύ", "user_usage_stats_description": "āĻ…ā§āϝāĻžāĻ•āĻžāωāĻ¨ā§āϟ āĻŦā§āϝāĻŦāĻšāĻžāϰ⧇āϰ āĻĒāϰāĻŋāϏāĻ‚āĻ–ā§āϝāĻžāύ āĻĻ⧇āϖ⧁āύ", - "username": "āχāωāϜāĻžāϰāύ⧇āĻŽ", - "users": "āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀āĻ—āĻŖ", - "users_added_to_album_count": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽā§‡ {count, plural, one {#āϜāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀} other {#āϜāύ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀}} āϝ⧋āĻ— āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "utilities": "āχāωāϟāĻŋāϞāĻŋāϟāĻŋ", - "validate": "āϝāĻžāϚāĻžāχ āĻ•āϰ⧁āύ", - "validate_endpoint_error": "āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻāĻ•āϟāĻŋ āĻŦ⧈āϧ URL āϞāĻŋāϖ⧁āύ", - "variables": "āϭ⧇āϰāĻŋāϝāĻŧ⧇āĻŦāϞ", - "version": "āĻ­āĻžāĻ°ā§āϏāύ", - "version_announcement_closing": "āφāĻĒāύāĻžāϰ āĻŦāĻ¨ā§āϧ⧁, Alex", - "version_announcement_message": "āĻšā§āϝāĻžāϞ⧋! Immich-āĻāϰ āĻāĻ•āϟāĻŋ āύāϤ⧁āύ āĻ­āĻžāĻ°ā§āϏāύ āωāĻĒāϞāĻŦā§āϧāĨ¤ āϕ⧋āύ⧋ āϭ⧁āϞ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āĻāĻĄāĻŧāĻžāϤ⧇ āφāĻĒāύāĻžāϰ āϏ⧇āϟāφāĻĒ āĻšāĻžāϞāύāĻžāĻ—āĻžāĻĻ āφāϛ⧇ āĻ•āĻŋāύāĻž āϤāĻž āύāĻŋāĻļā§āϚāĻŋāϤ āĻ•āϰāϤ⧇ āĻ…āύ⧁āĻ—ā§āϰāĻš āĻ•āϰ⧇ āĻ•āĻŋāϛ⧁āϟāĻž āϏāĻŽāϝāĻŧ āύāĻŋāϝāĻŧ⧇ āϰāĻŋāϞāĻŋāϜ āύ⧋āϟ āĻĒāĻĄāĻŧ⧁āύ, āĻŦāĻŋāĻļ⧇āώ āĻ•āϰ⧇ āφāĻĒāύāĻŋ āϝāĻĻāĻŋ WatchTower āĻŦāĻž āĻāĻŽāύ āϕ⧋āύ⧋ āĻŦā§āϝāĻŦāĻ¸ā§āĻĨāĻž āĻŦā§āϝāĻŦāĻšāĻžāϰ āĻ•āϰ⧇āύ āϝāĻž āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧāĻ­āĻžāĻŦ⧇ āφāĻĒāύāĻžāϰ Immich āχāύāĻ¸ā§āĻŸā§āϝāĻžāĻ¨ā§āϏ āφāĻĒāĻĄā§‡āϟ āĻ•āϰ⧇āĨ¤", - "version_history": "āĻ­āĻžāĻ°ā§āϏāύ āχāϤāĻŋāĻšāĻžāϏ", - "version_history_item": "{date}-āĻ {version} āχāύāĻ¸ā§āϟāϞ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "video": "āĻ­āĻŋāĻĄāĻŋāĻ“", - "video_hover_setting": "āĻšā§‹āĻ­āĻžāϰ āĻ•āϰāϞ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϚāĻžāϞāĻžāύ", - "video_hover_setting_description": "āĻŽāĻžāωāϏ āφāχāĻŸā§‡āĻŽā§‡āϰ āωāĻĒāϰ āĻšā§‹āĻ­āĻžāϰ āĻ•āϰāϞ⧇ āĻ­āĻŋāĻĄāĻŋāĻ“ āĻĨāĻžāĻŽā§āĻŦāύ⧇āχāϞ āϚāĻžāϞāĻžāύāĨ¤ āύāĻŋāĻˇā§āĻ•ā§āϰāĻŋāϝāĻŧ āĻĨāĻžāĻ•āϞ⧇āĻ“, āĻĒā§āϞ⧇ āφāχāĻ•āύ⧇āϰ āωāĻĒāϰ āĻšā§‹āĻ­āĻžāϰ āĻ•āϰ⧇ āĻĒā§āϞ⧇āĻŦā§āϝāĻžāĻ• āĻļ⧁āϰ⧁ āĻ•āϰāĻž āϝāĻžāϝāĻŧāĨ¤", - "video_quality": "āĻ­āĻŋāĻĄāĻŋāĻ“āϰ āĻŽāĻžāύ", - "videos": "āĻ­āĻŋāĻĄāĻŋāĻ“", - "videos_count": "{count, plural, one {#āϟāĻŋ āĻ­āĻŋāĻĄāĻŋāĻ“} other {#āϟāĻŋ āĻ­āĻŋāĻĄāĻŋāĻ“}}", - "view": "āĻĻ⧇āϖ⧁āύ", - "view_album": "āĻ…ā§āϝāĻžāϞāĻŦāĻžāĻŽ āĻĻ⧇āϖ⧁āύ", - "view_all": "āϏāĻŦ āĻĻ⧇āϖ⧁āύ", - "view_all_users": "āϏāĻŦ āĻŦā§āϝāĻŦāĻšāĻžāϰāĻ•āĻžāϰ⧀ āĻĻ⧇āϖ⧁āύ", - "view_asset_owners": "āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡āϰ āĻŽāĻžāϞāĻŋāĻ•āĻĻ⧇āϰ āĻĻ⧇āϖ⧁āύ", - "view_details": "āĻŦāĻŋāĻŦāϰāĻŖ āĻĻ⧇āϖ⧁āύ", - "view_in_timeline": "āϟāĻžāχāĻŽāϞāĻžāχāύ⧇ āĻĻ⧇āϖ⧁āύ", - "view_link": "āϞāĻŋāĻ‚āĻ• āĻĻ⧇āϖ⧁āύ", - "view_name": "āĻ­āĻŋāω", - "view_next_asset": "āĻĒāϰāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āϖ⧁āύ", - "view_previous_asset": "āĻĒā§‚āĻ°ā§āĻŦāĻŦāĻ°ā§āϤ⧀ āĻ…ā§āϝāĻžāϏ⧇āϟ āĻĻ⧇āϖ⧁āύ", - "view_qr_code": "QR āϕ⧋āĻĄ āĻĻ⧇āϖ⧁āύ", - "view_similar_photos": "āĻāĻ•āχ āϧāϰāύ⧇āϰ āĻ›āĻŦāĻŋ āĻĻ⧇āϖ⧁āύ", - "view_stack": "āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻĻ⧇āϖ⧁āύ", - "viewer_remove_from_stack": "āĻ¸ā§āĻŸā§āϝāĻžāĻ• āĻĨ⧇āϕ⧇ āϏāϰāĻžāύ", - "visibility": "āĻĻ⧃āĻļā§āϝāĻŽāĻžāύāϤāĻž", - "visibility_changed": "{count, plural, one {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ} other {#āϜāύ āĻŦā§āϝāĻ•ā§āϤāĻŋāϰ}} āĻĻ⧃āĻļā§āϝāĻŽāĻžāύāϤāĻž āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āĻ•āϰāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "visual": "āĻ­āĻŋāĻœā§āϝ⧁āϝāĻŧāĻžāϞ", - "waiting": "āĻ…āĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻšā§āϛ⧇", - "waiting_count": "āĻ…āĻĒ⧇āĻ•ā§āώāĻž āĻ•āϰāĻž āĻšāĻšā§āϛ⧇: {count}", - "warning": "āϏāϤāĻ°ā§āĻ•āϤāĻž", - "week": "āϏāĻĒā§āϤāĻžāĻš", - "welcome": "āĻ¸ā§āĻŦāĻžāĻ—āϤāĻŽ", - "welcome_to_immich": "Immich-āĻ āĻ¸ā§āĻŦāĻžāĻ—āϤāĻŽ", - "whats_new": "āϕ⧀ āύāϤ⧁āύ", - "whats_new_settings_subtitle": "Immich-āĻ āϕ⧀ āύāϤ⧁āύ āϤāĻž āĻĻ⧇āϖ⧁āύ", - "whats_new_version": "āĻ­āĻžāĻ°ā§āϏāύ {version}", - "when": "āĻ•āĻ–āύ", - "width": "āĻĒā§āϰāĻ¸ā§āĻĨ", - "wifi_name": "Wi-Fi āύāĻžāĻŽ", - "workflow": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋", - "workflow_delete_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āĻāχ āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋āϟāĻŋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ āϚāĻžāύ?", - "workflow_deleted": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻŽā§āϛ⧇ āĻĢ⧇āϞāĻž āĻšāϝāĻŧ⧇āϛ⧇", - "workflow_description": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋āϰ āĻŦāĻŋāĻŦāϰāĻŖ", - "workflow_info": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋āϰ āϤāĻĨā§āϝ", - "workflow_json": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ JSON", - "workflow_json_help": "JSON āĻĢāϰāĻŽā§āϝāĻžāĻŸā§‡ āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻ•āύāĻĢāĻŋāĻ—āĻžāϰ⧇āĻļāύ āϏāĻŽā§āĻĒāĻžāĻĻāύāĻž āĻ•āϰ⧁āύāĨ¤ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύāϗ⧁āϞ⧋ āĻ­āĻŋāĻœā§āϝ⧁āϝāĻŧāĻžāϞ āĻŦāĻŋāĻ˛ā§āĻĄāĻžāϰ⧇ āϏāĻŋāĻ™ā§āĻ• āĻšāĻŦ⧇āĨ¤", - "workflow_name": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋āϰ āύāĻžāĻŽ", - "workflow_navigation_prompt": "āφāĻĒāύāĻŋ āĻ•āĻŋ āύāĻŋāĻļā§āϚāĻŋāϤ āϝ⧇ āφāĻĒāύāĻŋ āφāĻĒāύāĻžāϰ āĻĒāϰāĻŋāĻŦāĻ°ā§āϤāύ āϏāĻ‚āϰāĻ•ā§āώāĻŖ āύāĻž āĻ•āϰ⧇āχ āϚāϞ⧇ āϝ⧇āϤ⧇ āϚāĻžāύ?", - "workflow_summary": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āϏāĻžāϰāϏāĻ‚āĻ•ā§āώ⧇āĻĒ", - "workflow_templates": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻŸā§‡āĻŽāĻĒā§āϞ⧇āϟ", - "workflow_update_success": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āϏāĻĢāϞāĻ­āĻžāĻŦ⧇ āφāĻĒāĻĄā§‡āϟ āĻšāϝāĻŧ⧇āϛ⧇", - "workflows": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋", - "workflows_help_text": "āĻ“āϝāĻŧāĻžāĻ°ā§āĻ•āĻĢā§āϞ⧋ āĻŸā§āϰāĻŋāĻ—āĻžāϰ āĻ“ āĻĢāĻŋāĻ˛ā§āϟāĻžāϰ⧇āϰ āωāĻĒāϰ āĻ­āĻŋāĻ¤ā§āϤāĻŋ āĻ•āϰ⧇ āφāĻĒāύāĻžāϰ āĻ…ā§āϝāĻžāϏ⧇āĻŸā§‡ āĻ…ā§āϝāĻžāĻ•āĻļāύ āĻ¸ā§āĻŦāϝāĻŧāĻ‚āĻ•ā§āϰāĻŋāϝāĻŧ āĻ•āϰ⧇", - "wrong_pin_code": "āϭ⧁āϞ āĻĒāĻŋāύ āϕ⧋āĻĄ", - "x_of_total": "{x}/{total}", - "year": "āĻŦāĻ›āϰ", - "years_ago": "{years, plural, one {#āĻŦāĻ›āϰ} other {#āĻŦāĻ›āϰ}} āφāϗ⧇", "yes": "āĻšā§āϝāĻžāρ", "you_dont_have_any_shared_links": "āφāĻĒāύāĻžāϰ āϕ⧋āύ⧋ āĻļā§‡ā§ŸāĻžāϰ āĻ•āϰāĻž āϞāĻŋāĻ™ā§āĻ• āύ⧇āχ (You don't have any shared links)", "your_wifi_name": "āφāĻĒāύāĻžāϰ āĻ“āϝāĻŧāĻžāχ-āĻĢāĻžāχ āĻāϰ āύāĻžāĻŽ (Your Wi-Fi name)", "zero_to_clear_rating": "āĻ…ā§āϝāĻžāϏ⧇āϟ āϰ⧇āϟāĻŋāĻ‚ āĻŽā§āϛ⧇ āĻĢ⧇āϞāϤ⧇ ā§Ļ āϚāĻžāĻĒ⧁āύ", - "zoom_image": "āĻ›āĻŦāĻŋ āϜ⧁āĻŽ āĻ•āϰ⧁āύ (Zoom Image)" + "zoom_image": "āĻ›āĻŦāĻŋ āϜ⧁āĻŽ āĻ•āϰ⧁āύ (Zoom Image)", + "zoom_to_bounds": "āĻŦāĻžāωāĻ¨ā§āĻĄāϏ āĻ…āύ⧁āϝāĻžā§Ÿā§€ āϜ⧁āĻŽ āĻ•āϰ⧁āύ (Zoom to bounds)" } diff --git a/i18n/bs.json b/i18n/bs.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/bs.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/ca.json b/i18n/ca.json index c88cfd8362..88c6b8a323 100644 --- a/i18n/ca.json +++ b/i18n/ca.json @@ -2,1956 +2,2095 @@ "about": "Quant a", "account": "Compte", "account_settings": "ConfiguraciÃŗ del compte", - "acknowledge": "Entesos", + "acknowledge": "Base de coneixement", "action": "AcciÃŗ", - "action_common_update": "Actualitza", - "action_description": "Un conjunt d’accions a executar sobre els recursos filtrats", + "action_common_update": "Actualitzar", + "action_description": "Un conjunt d'accions a realitzar sobre els recursos filtrats", "actions": "Accions", - "active": "Actives", - "active_count": "Actives: {count}", + "active": "Actiu", + "active_count": "Activat: {count}", "activity": "Activitat", - "add": "Afegeix", + "activity_changed": "L'activitat està {enabled, select, true {activada} other {desactivada}}", + "add": "Afegir", "add_a_description": "Afegiu una descripciÃŗ", - "add_a_location": "Afegeix una ubicaciÃŗ", - "add_a_name": "Afegeix un nom", - "add_a_title": "Afegiu un títol", - "add_action": "Afegeix una acciÃŗ", - "add_assets": "Afegeix recursos", + "add_a_location": "Afegiu una ubicaciÃŗ", + "add_a_name": "Afegir un nom", + "add_a_title": "Afegir un títol", + "add_action": "Afegir acciÃŗ", + "add_action_description": "Feu clic per afegir una acciÃŗ a realitzar", + "add_assets": "Afegir recursos", "add_birthday": "Afegeix la data de naixement", - "add_endpoint": "Afegeix un endpoint", - "add_exclusion_pattern": "Afegeix un patrÃŗ d’exclusiÃŗ", - "add_location": "Afegeix una ubicaciÃŗ", - "add_partner": "Afegeix un company", - "add_photos": "Afegeix fotos", - "add_step": "Afegeix un pas", - "add_tag": "Afegeix una etiqueta", - "add_to": "Afegeix aâ€Ļ", - "add_to_album": "Afegeix a un àlbum", - "add_to_album_bottom_sheet_added": "S’ha afegit a {album}", - "add_to_album_bottom_sheet_already_exists": "Ja Ês a {album}", - "add_to_album_bottom_sheet_some_local_assets": "No s’han pogut afegir alguns recursos locals a l’àlbum", - "add_to_albums": "Afegeix a àlbums", - "add_to_albums_count": "Afegeix a {count} àlbums", - "add_to_bottom_bar": "Afegeix a", - "add_upload_to_stack": "Afegeix una pujada a la pila", - "add_url": "Afegeix un URL", - "added_to_archive": "S’ha afegit a l’arxiu", - "added_to_favorites": "S’ha afegit als preferits", - "added_to_favorites_count": "Se n’han afegit {count, number} als preferits", + "add_endpoint": "afegir endpoint", + "add_exclusion_pattern": "Afegir un patrÃŗ d'exclusiÃŗ", + "add_filter": "Afegir filtre", + "add_filter_description": "Feu clic per afegir una condiciÃŗ de filtre", + "add_location": "Afegir la ubicaciÃŗ", + "add_more_users": "Afegir mÊs usuaris", + "add_partner": "Afegir company/a", + "add_path": "Afegir una ruta", + "add_photos": "Afegir fotografies", + "add_tag": "Afegir una etiqueta", + "add_to": "Afegir aâ€Ļ", + "add_to_album": "Afegir a un l'àlbum", + "add_to_album_bottom_sheet_added": "Afegit a {album}", + "add_to_album_bottom_sheet_already_exists": "Ja està a {album}", + "add_to_album_bottom_sheet_some_local_assets": "Alguns recursos locals no s'han pogut afegir a l'àlbum", + "add_to_album_toggle": "Commutar selecciÃŗ de {album}", + "add_to_albums": "Afegir als àlbums", + "add_to_albums_count": "Afegir als àlbums ({count})", + "add_to_bottom_bar": "Afegir a", + "add_to_shared_album": "Afegir a un àlbum compartit", + "add_upload_to_stack": "Afegeix la càrrega a la pila", + "add_url": "Afegir URL", + "add_workflow_step": "Afegeix un pas del flux de treball", + "added_to_archive": "Afegir a l'arxiu", + "added_to_favorites": "Afegit als preferits", + "added_to_favorites_count": "{count, number} afegits als preferits", "admin": { - "add_exclusion_pattern_description": "Afegiu patrons d’exclusiÃŗ. S’admet englobar fent Ãēs de *, **, i ?. Per a ignorar tots els fitxers de qualsevol directori anomenat ÂĢRawÂģ, utilitzeu ÂĢ**/Raw/**Âģ. Per a ignorar tots els fitxers acabats en ÂĢ.tifÂģ, utilitzeu ÂĢ**/*.tifÂģ. Per a ignorar un camí absolut, utilitzeu ÂĢ/camí/a/ignorar/**Âģ.", + "add_exclusion_pattern_description": "Afegeix patrons d'exclusiÃŗ. Es permet englobar fent Ãēs de *, **, i ?. Per a ignorar els fitxers de qualsevol directori anomenat \"Raw\" introduïu \"**/Raw/**\". Per a ignorar els fitxers acabats en \".tif\" introduïu \"**/*.tif\". Per a ignorar una ruta absoluta, utilitzeu \"/ruta/a/ignorar/**\".", "admin_user": "Administrador", - "asset_offline_description": "Aquest recurs d’una biblioteca externa ja no es troba al disc i s’ha mogut a la paperera. Si el fitxer s’ha mogut dins de la biblioteca, cerqueu el nou recurs corresponent a la vostra cronologia. Per a restaurar aquest recurs, assegureu-vos que l’Immich pugui accedir al camí de fitxers segÃŧent i escanegeu la biblioteca.", - "authentication_settings": "ConfiguraciÃŗ de l’autenticaciÃŗ", - "authentication_settings_description": "Gestioneu la contrasenya, l’OAuth i altres paràmetres de l’autenticaciÃŗ", - "authentication_settings_disable_all": "Segur que voleu desactivar tots els mètodes d’inici de sessiÃŗ? Es desactivarà completament l’inici de sessiÃŗ.", - "authentication_settings_reenable": "Per a tornar-lo a activar, utilitzeu una ordre del servidor.", + "asset_offline_description": "Aquest recurs de la biblioteca externa ja no es troba al disc i s'ha mogut a la paperera. Si el fitxer s'ha mogut dins de la biblioteca, comproveu la vostra línia de temps per trobar el nou recurs corresponent. Per restaurar aquest recurs, assegureu-vos que Immich pugui accedir a la ruta del fitxer segÃŧent i escanegeu la biblioteca.", + "authentication_settings": "ConfiguraciÃŗ de l'autenticaciÃŗ", + "authentication_settings_description": "Gestiona la contrasenya, OAuth i altres configuracions de l'autenticaciÃŗ", + "authentication_settings_disable_all": "Estàs segur que vols desactivar tots els mètodes d'inici de sessiÃŗ? L'inici de sessiÃŗ quedarà completament desactivat.", + "authentication_settings_reenable": "Per a tornar a habilitar, empra una Comanda de Servidor.", "background_task_job": "Tasques en segon pla", - "backup_database": "Crea un bolcat de la base de dades", - "backup_database_enable_description": "Activa els bolcats de la base de dades", - "backup_keep_last_amount": "Quantitat de bolcats anteriors a conservar", + "backup_database": "Fer un bolcat de la base de dades", + "backup_database_enable_description": "Habilitar bolcat de la base de dades", + "backup_keep_last_amount": "Quantitat de bolcats anteriors per conservar", "backup_onboarding_1_description": "cÃ˛pia externa al nÃēvol o en una altra ubicaciÃŗ física.", - "backup_onboarding_2_description": "cÃ˛pies locals en dispositius diferents. AixÃ˛ inclou els fitxers principals i una cÃ˛pia de seguretat local d’aquests fitxers.", - "backup_onboarding_3_description": "cÃ˛pies totals de les vostres dades, incloent-hi els fitxers originals. AixÃ˛ inclou 1 cÃ˛pia externa i 2 cÃ˛pies locals.", - "backup_onboarding_description": "És recomanable una estratègia de cÃ˛pia de seguretat 3-2-1 per a protegir les vostres dades. Per a tenir una soluciÃŗ de cÃ˛pia de seguretat completa, hauríeu de conservar cÃ˛pies de les vostres fotos i vídeos penjats, i tambÊ de la base de dades de l’Immich.", - "backup_onboarding_footer": "Per a mÊs informaciÃŗ sobre com fer la cÃ˛pia de seguretat de l’Immich, consulteu la documentaciÃŗ.", + "backup_onboarding_2_description": "cÃ˛pies locals en diferents dispositius. AixÃ˛ inclou els fitxers principals i una cÃ˛pia de seguretat d'aquests fitxers localment.", + "backup_onboarding_3_description": "cÃ˛pies totals de les vostres dades, inclosos els fitxers originals. AixÃ˛ inclou 1 cÃ˛pia externa i 2 cÃ˛pies locals.", + "backup_onboarding_description": "Es recomana una estratègia de cÃ˛pia de seguretat 3-2-1 per protegir les vostres dades. Hauríeu de conservar cÃ˛pies de les vostres fotos/vídeos penjats, així com de la base de dades Immich per obtenir una soluciÃŗ de cÃ˛pia de seguretat completa.", + "backup_onboarding_footer": "Per obtenir mÊs informaciÃŗ sobre com fer cÃ˛pies de seguretat d'Immich, consulteu la documentation.", "backup_onboarding_parts_title": "Una cÃ˛pia de seguretat 3-2-1 inclou:", "backup_onboarding_title": "CÃ˛pies de seguretat", - "backup_settings": "ConfiguraciÃŗ dels bolcats de la base de dades", - "backup_settings_description": "Gestioneu els paràmetres dels bolcats de la base de dades.", - "cleared_jobs": "S’han netejat els treballs de: {job}", + "backup_settings": "ConfiguraciÃŗ dels bolcats", + "backup_settings_description": "Gestionar la configuraciÃŗ de bolcats de la base de dades. Nota: els treballs no es monitoritzen ni es notifiquen els errors.", + "cleared_jobs": "Tasques esborrades per a: {job}", "config_set_by_file": "La configuraciÃŗ està definida per un fitxer de configuraciÃŗ", - "confirm_delete_library": "Segur que voleu suprimir la biblioteca {library}?", - "confirm_delete_library_assets": "Segur que voleu suprimir aquesta biblioteca? Se {count, plural, one {suprimirà # recurs} other {suprimiran tots els # recursos}} que contÊ de l’Immich i l’acciÃŗ no es podrà desfer. Els fitxers romandran al disc.", - "confirm_email_below": "Per a confirmar-ho, escriviu ÂĢ{email}Âģ a sota", - "confirm_reprocess_all_faces": "Segur que voleu tornar a processar totes les cares? TambÊ s’esborraran totes les persones anomenades.", - "confirm_user_password_reset": "Segur que voleu restablir la contrasenya de l’usuari {user}?", - "confirm_user_pin_code_reset": "Segur que voleu restablir el codi PIN de l’usuari {user}?", + "confirm_delete_library": "Esteu segurs que voleu eliminar la llibreria {library}?", + "confirm_delete_library_assets": "Esteu segurs que voleu esborrar aquesta llibreria? AixÃ˛ esborrarà {count, plural, one {# contained asset} other {all # contained assets}} d'Immich i no es podrà desfer. Els fitxers romandran al disc.", + "confirm_email_below": "Per a confirmar, escriviu \"{email}\" a sota", + "confirm_reprocess_all_faces": "Esteu segurs que voleu reprocessar totes les cares? AixÃ˛ tambÊ esborrarà la gent que heu anomenat.", + "confirm_user_password_reset": "Esteu segur que voleu reinicialitzar la contrasenya de l'usuari {user}?", + "confirm_user_pin_code_reset": "Esteu segur que voleu restablir el codi PIN de {user}?", "copy_config_to_clipboard_description": "Copia la configuraciÃŗ actual del sistema com a objecte JSON al porta-retalls", - "create_job": "Crea una tasca", - "cron_expression": "ExpressiÃŗ cron", - "cron_expression_description": "Definiu l’interval d’escaneig en format cron. Per a mÊs informaciÃŗ, consulteu Crontab Guru, per exemple", - "cron_expression_presets": "Expressions cron predefinides", - "disable_login": "Desactiva l’inici de sessiÃŗ", - "download_csv": "Baixa un CSV", - "duplicate_detection_job_description": "Executa aprenentatge automàtic sobre els recursos per a detectar imatges semblants. Es basa en la cerca intel¡ligent", - "exclusion_pattern_description": "Els patrons d’exclusiÃŗ us permeten ignorar fitxers i carpetes en escanejar la biblioteca. AixÃ˛ Ês Ãētil si teniu carpetes que contenen fitxers que no voleu importar, com ara fitxers RAW.", + "create_job": "Crear tasca", + "cron_expression": "ExpressiÃŗ Cron", + "cron_expression_description": "Estableix l'interval d'escaneig amb el format cron. Per obtenir mÊs informaciÃŗ, consulteu, p.e Crontab Guru", + "cron_expression_presets": "Ajustos predefinits d'expressions Cron", + "disable_login": "Deshabiliteu l'inici de sessiÃŗ", + "duplicate_detection_job_description": "Executa l'aprenentatge automàtic en els elements per a detectar imatges semblants. Fa servir la cerca intel¡ligent", + "exclusion_pattern_description": "Els patrons d'exclusiÃŗ permeten ignorar fitxers i carpetes quan escanegeu una llibreria. AixÃ˛ Ês Ãētil si teniu carpetes que contenen fitxer que no voleu importar, com els fitxers RAW.", "export_config_as_json_description": "Baixa la configuraciÃŗ actual del sistema com a fitxer JSON", - "external_libraries_page_description": "Pàgina de la biblioteca externa de l’administrador", + "external_libraries_page_description": "Pàgina de la biblioteca externa de l'administrador", "face_detection": "DetecciÃŗ de cares", - "face_detection_description": "Detecta cares als recursos fent servir aprenentatge automàtic. Per a vídeos, nomÊs es tÊ en compte la miniatura. ÂĢActualitzaÂģ torna a processar tots els recursos. ÂĢRestableixÂģ, a mÊs a mÊs, esborra tota la informaciÃŗ actual de cares. ÂĢPendentsÂģ afegeix a la cua els recursos que encara no s’han processat. Les cares detectades s’afegiran a la cua del reconeixement facial desprÊs de completar la detecciÃŗ de cares, tot agrupant-les en persones existents o noves.", - "facial_recognition_job_description": "Agrupa les cares detectades en persones. Aquest pas s’executa desprÊs de completar la detecciÃŗ de cares. ÂĢRestableixÂģ reagrupa totes les cares. ÂĢPendentsÂģ afegeix a la cua les cares que no tenen cap persona assignada.", - "failed_job_command": "L’ordre {command} ha fallat a la tasca: {job}", - "force_delete_user_warning": "ADVERTÈNCIA: Aquesta acciÃŗ suprimirà immediatament l’usuari i tots els recursos. Aquesta acciÃŗ no es pot desfer i els fitxers no es podran recuperar.", + "face_detection_description": "Detecta les cares fent servir aprenentatge automàtic. Per a videos nomÊs Ês tÊ en compte la miniatura. \"Actualitzar\" reprocessa tots els elements. \"Resetejar\" esborra tota la informaciÃŗ de cares actuals. \"Pendent\" afegeix a la cua els elements que encara no han estat processats. Les cares detectades s'afegiran a la cua per al Reconeixement Facial desprÊs de completar la DetecciÃŗ Facial, tot agrupant-les entre noves persones o les ja existents.", + "facial_recognition_job_description": "Agrupa les cares detectades per persona. Aquest pas s'executa desprÊs de completar la detecciÃŗ de cares. \"Resetejar\" reagrupa totes les cares. \"Pendent\" afegeix a la cua les cares que no tenen cap persona assignada.", + "failed_job_command": "La comanda {command} ha fallat per la tasca: {job}", + "force_delete_user_warning": "COMPTE: Aquesta acciÃŗ eliminara immediatament l'usuari i els seus elements. Aquesta acciÃŗ Ês irreversible i els fitxers no es poden recuperar.", "image_format": "Format", - "image_format_description": "WebP genera fitxers mÊs petits que JPEG, perÃ˛ es codifica mÊs lentament.", - "image_fullsize_description": "Imatge de mida completa amb les metadades eliminades. S’utilitza en ampliar", - "image_fullsize_enabled": "Activa la generaciÃŗ d’imatges de mida completa", - "image_fullsize_enabled_description": "Genera imatges de mida completa per a formats no aptes per al web. Quan s’activa ÂĢPrefereix la previsualitzaciÃŗ incrustadaÂģ, s’utilitzen directament previsualitzacions incrustades sense conversiÃŗ. No afecta els formats aptes per al web com JPEG.", - "image_fullsize_quality_description": "Qualitat de la imatge de mida completa de l’1 al 100. Un valor mÊs alt Ês millor, perÃ˛ produeix fitxers mÊs grans.", - "image_fullsize_title": "ConfiguraciÃŗ de les imatges de mida completa", - "image_prefer_embedded_preview": "Prefereix la previsualitzaciÃŗ incrustada", - "image_prefer_embedded_preview_setting_description": "Utilitzar la previsualitzaciÃŗ incrustada en les fotos RAW com a entrada per al processament d’imatges quan sigui possible. Aquesta acciÃŗ pot produir colors mÊs precisos en algunes imatges, perÃ˛ la qualitat de la previsualitzaciÃŗ depèn de la càmera i la imatge pot tenir mÊs artefactes de compressiÃŗ.", + "image_format_description": "WebP genera fitxers mÊs petits que JPEG, perÃ˛ codifica mÊs lentament.", + "image_fullsize_description": "Imatges a tamany complet sense metadades, utilitzades quan es fa zoom", + "image_fullsize_enabled": "Activa la generaciÃŗ d'imatges a tamany complet", + "image_fullsize_enabled_description": "Genera imatges a tamany complet per formats no compatibles amb la web. Quan \"Prefereix vista prèvia incrustada\" està activat, les vistes prèvies incrustades s'utilitzen directament sense conversiÃŗ. No afecta els formats compatibles amb la web com JPEG.", + "image_fullsize_quality_description": "De 1 a 100, qualitat de l'imatge a tamany complet. Un valor mÊs alt Ês millor, perÃ˛ resulta en fitxers de major tamany.", + "image_fullsize_title": "ConfiguraciÃŗ de les imatges a tamany complet", + "image_prefer_embedded_preview": "Prefereix vista prèvia incrustada", + "image_prefer_embedded_preview_setting_description": "Empra vista prèvia incrustada en les fotografies RAW com a entrada per al processament d'imatge, quan sigui possible. Aquesta acciÃŗ pot produir colors mÊs acurats en algunes imatges, perÃ˛ la qualitat de la vista prèvia depèn de la càmera i la imatge pot tenir mÊs artefactes de compressiÃŗ.", "image_prefer_wide_gamut": "Prefereix la gamma àmplia", - "image_prefer_wide_gamut_setting_description": "Utilitza Display P3 per a les miniatures. AixÃ˛ conserva millor la vitalitat de les imatges amb espais de color amplis, perÃ˛ les imatges es poden veure diferents en dispositius antics amb una versiÃŗ antiga del navegador. Les imatges sRGB romandran com a sRGB per a evitar canvis de colors.", - "image_preview_description": "Imatge de mida mitjana amb les metadades eliminades. S’utilitza quan es visualitza un sol recurs i per a l’aprenentatge automàtic", - "image_preview_quality_description": "Qualitat de la previsualitzaciÃŗ de l’1 al 100. Un valor mÊs alt Ês millor, perÃ˛ produeix fitxers mÊs grans i pot reduir la capacitat de resposta de l’aplicaciÃŗ. Establir-hi un valor baix pot afectar la qualitat de l’aprenentatge automàtic.", - "image_preview_title": "ConfiguraciÃŗ de les previsualitzacions", + "image_prefer_wide_gamut_setting_description": "Uitlitza Display P3 per a les miniatures. AixÃ˛ preserva mÊs bÊ la vitalitat de les imatges amb espais de color àmplis, perÃ˛ les imatges es poden veure diferent en aparells antics amb una versiÃŗ antiga del navegador. Les imatges sRGB romandran com a sRGB per a evitar canvis de color.", + "image_preview_description": "Imatge de mida mitjana amb metadades eliminades, que s'utilitza quan es visualitza un sol recurs i per a l'aprenentatge automàtic", + "image_preview_quality_description": "Vista prèvia de la qualitat de l'1 al 100. MÊs alt Ês millor, perÃ˛ produeix fitxers mÊs grans i pot reduir la capacitat de resposta de l'aplicaciÃŗ. Establir un valor baix pot afectar la qualitat de l'aprenentatge automàtic.", + "image_preview_title": "Paràmetres de previsualitzaciÃŗ", "image_progressive": "Progressiu", - "image_progressive_description": "Codifica les imatges JPEG en mode progressiu perquè es visualitzin amb una càrrega gradual. No tÊ cap efecte sobre les imatges WebP.", + "image_progressive_description": "Codifica les imatges JPEG progressivament per a una visualitzaciÃŗ amb càrrega gradual. AixÃ˛ no tÊ cap efecte sobre les imatges WebP.", "image_quality": "Qualitat", "image_resolution": "ResoluciÃŗ", - "image_resolution_description": "Les resolucions mÊs altes poden conservar mÊs detalls, perÃ˛ tarden mÊs temps a codificar-se, tenen mides de fitxer mÊs grans i poden reduir la capacitat de resposta de l’aplicaciÃŗ.", + "image_resolution_description": "Les resolucions mÊs altes poden conservar mÊs detalls perÃ˛ triguen mÊs a codificar-se, tenen mides de fitxer mÊs grans i poden reduir la capacitat de resposta de l'aplicaciÃŗ.", "image_settings": "ConfiguraciÃŗ de les imatges", - "image_settings_description": "Gestioneu la qualitat i la resoluciÃŗ de les imatges generades", - "image_thumbnail_description": "Miniatura petita amb les metadades eliminades. S’utilitza quan es visualitzen grups de fotos, com ara la cronologia principal", - "image_thumbnail_quality_description": "Qualitat de la miniatura de l’1 al 100. Un valor mÊs alt Ês millor, perÃ˛ produeix fitxers mÊs grans i pot reduir la capacitat de resposta de l’aplicaciÃŗ.", + "image_settings_description": "Gestiona la qualitat i resoluciÃŗ de les imatges generades", + "image_thumbnail_description": "Miniatura petita amb metadades eliminades, que s'utilitza quan es visualitzen grups de fotos com la línia de temps principal", + "image_thumbnail_quality_description": "Qualitat de miniatura d'1 a 100. MÊs alt Ês millor, perÃ˛ produeix fitxers mÊs grans i pot reduir la capacitat de resposta de l'aplicaciÃŗ.", "image_thumbnail_title": "ConfiguraciÃŗ de les miniatures", "import_config_from_json_description": "Importa la configuraciÃŗ del sistema pujant un fitxer de configuraciÃŗ JSON", - "integrity_checks_checksum_files": "Verifica la integritat dels fitxers", - "integrity_checks_checksum_files_description": "Configureu la verificaciÃŗ de la integritat", - "integrity_checks_checksum_files_enable_description": "Activa la verificaciÃŗ de la integritat", - "integrity_checks_checksum_files_percentage_limit": "Percentatge límit", - "integrity_checks_checksum_files_percentage_limit_description": "Configura el percentatge màxim, entre 0,01 i 1, de la durada de la comprovaciÃŗ de la suma de verificaciÃŗ en cada interval.", - "integrity_checks_checksum_files_time_limit": "Límit de temps", - "integrity_checks_checksum_files_time_limit_description": "Configura la durada màxima de la comprovaciÃŗ de la suma de verificaciÃŗ en cada interval. (ms)", - "integrity_checks_missing_files": "Fitxers absents", - "integrity_checks_missing_files_description": "Configureu la freqÃŧència i activeu o desactiveu la verificaciÃŗ dels fitxers absents", - "integrity_checks_missing_files_enable_description": "Activa la verificaciÃŗ dels fitxers absents", - "integrity_checks_settings": "Verificacions d’integritat", - "integrity_checks_settings_description": "Gestioneu els intervals de les verificacions d’integritat", - "integrity_checks_untracked_files": "Fitxers sense seguiment", - "integrity_checks_untracked_files_description": "Configureu la freqÃŧència i activeu o desactiveu la verificaciÃŗ dels fitxers sense seguiment", - "integrity_checks_untracked_files_enable_description": "Activa la verificaciÃŗ dels fitxers sense seguiment", - "job_concurrency": "Concurrència de la tasca {job}", - "job_created": "S’ha creat la tasca", - "job_not_concurrency_safe": "Aquesta tasca no admet concurrència de manera segura.", + "job_concurrency": "{job} simultàniament", + "job_created": "Tasca creada", + "job_not_concurrency_safe": "Aquesta tasca no Ês segura per a la conconcurrència.", "job_settings": "ConfiguraciÃŗ de les tasques", - "job_settings_description": "Gestioneu la concurrència de les tasques", - "jobs_delayed": "{jobCount, plural, one {# d’endarrerida} other {# d’endarrerides}}", - "jobs_failed": "{jobCount, plural, one {# de fallida} other {# de fallides}}", - "jobs_over_time": "Tasques al llarg del temps", - "library_created": "S’ha creat la biblioteca: {library}", - "library_deleted": "S’ha suprimit la biblioteca", - "library_details": "Detalls de la biblioteca", - "library_folder_description": "Especifiqueu una carpeta a importar. Aquesta carpeta, incloses les subcarpetes, s’escanejarà cercant-hi imatges i vídeos.", - "library_remove_exclusion_pattern_prompt": "Segur que voleu suprimir aquest patrÃŗ d’exclusiÃŗ?", - "library_remove_folder_prompt": "Segur que voleu suprimir aquesta carpeta d’importaciÃŗ?", + "job_settings_description": "Gestiona la concurrència de tasques", + "jobs_delayed": "{jobCount, plural, other {# posposades}}", + "jobs_failed": "{jobCount, plural, other {# fallides}}", + "jobs_over_time": "Feines al llarg del temps", + "library_created": "Bilbioteca creada: {library}", + "library_deleted": "Bilbioteca eliminada", + "library_details": "Detalls de la llibreria", + "library_folder_description": "Especifiqueu una carpeta per importar. Aquesta carpeta, incloses les subcarpetes, s'escanejarà a la recerca d'imatges i vídeos.", + "library_remove_exclusion_pattern_prompt": "Esteu segur que voleu eliminar aquest patrÃŗ d'exclusiÃŗ?", + "library_remove_folder_prompt": "Esteu segur que voleu eliminar aquesta carpeta d'importaciÃŗ?", "library_scanning": "Escaneig periÃ˛dic", - "library_scanning_description": "Configureu l’escaneig periÃ˛dic de la biblioteca", - "library_scanning_enable_description": "Activa l’escaneig periÃ˛dic de la biblioteca", - "library_settings": "Biblioteques externes", - "library_settings_description": "Gestioneu la configuraciÃŗ de les biblioteques externes", - "library_tasks_description": "Escaneja les biblioteques externes cercant-hi recursos nous i/o modificats", - "library_updated": "S’ha actualitzat la biblioteca", - "library_watching_enable_description": "Monitora si hi ha canvis de fitxers a les biblioteques externes", - "library_watching_settings": "Monitoratge de biblioteques (EXPERIMENTAL)", - "library_watching_settings_description": "Monitora automàticament els fitxers modificats", - "logging_enable_description": "Activa el registre", - "logging_level_description": "Quin nivell de registre voleu utilitzar quan estigui activat.", + "library_scanning_description": "Configurar l'escaneig periÃ˛dic de bilbioteques", + "library_scanning_enable_description": "Habilita l'escaneig periÃ˛dic de biblioteques", + "library_settings": "Llibreria externes", + "library_settings_description": "Gestiona la configuraciÃŗ de les llibreries externes", + "library_tasks_description": "Escaneja les biblioteques externes per trobar arxius nous o canviats", + "library_updated": "Llibreria actualitzada", + "library_watching_enable_description": "Consultar llibreries externes per detectar canvis en fitxers", + "library_watching_settings": "Monitoratge de la llibreria (EXPERIMENTAL)", + "library_watching_settings_description": "MonitoritzaciÃŗ automàtica de fitxers modificats", + "logging_enable_description": "Habilitar el registrament", + "logging_level_description": "Quan està habilitat, quin nivell de registre es vol emprar.", "logging_settings": "Registre", "machine_learning_availability_checks": "Comprovacions de disponibilitat", - "machine_learning_availability_checks_description": "Detecta i prefereix automàticament els servidors d’aprenentatge automàtic disponibles", - "machine_learning_availability_checks_enabled": "Activa les comprovacions de disponibilitat", + "machine_learning_availability_checks_description": "Detectar i preferir automàticament els servidors d'aprenentatge automàtic disponibles", + "machine_learning_availability_checks_enabled": "Habilita les comprovacions de disponibilitat", "machine_learning_availability_checks_interval": "Interval de comprovaciÃŗ", "machine_learning_availability_checks_interval_description": "Interval en mil¡lisegons entre comprovacions de disponibilitat", - "machine_learning_availability_checks_timeout": "Temps d’espera de la sol¡licitud", - "machine_learning_availability_checks_timeout_description": "Temps d’espera en mil¡lisegons per a les comprovacions de disponibilitat", + "machine_learning_availability_checks_timeout": "Temps d'espera de la sol¡licitud", + "machine_learning_availability_checks_timeout_description": "Temps d'espera en mil¡lisegons per a les comprovacions de disponibilitat", "machine_learning_clip_model": "Model CLIP", - "machine_learning_clip_model_description": "El nom d’un model CLIP que aparegui aquí. Tingueu en compte que si canvieu de model, cal que torneu a executar la tasca ÂĢCerca intel¡ligentÂģ per a totes les imatges.", + "machine_learning_clip_model_description": "El nom d'un model CLIP que apareix a aquí. Tingues en compte que has de tornar a executar la cerca intel¡ligent per a totes les imatges quan es canvia de model.", "machine_learning_duplicate_detection": "DetecciÃŗ de duplicats", "machine_learning_duplicate_detection_enabled": "Activa la detecciÃŗ de duplicats", "machine_learning_duplicate_detection_enabled_description": "Si està desactivada, els elements idèntics encara es desduplicaran.", - "machine_learning_duplicate_detection_setting_description": "Utilitza incrustacions CLIP per a trobar possibles duplicats", - "machine_learning_enabled": "Activa l’aprenentatge automàtic", - "machine_learning_enabled_description": "Si està desactivat, totes les funcions de ML es desactivaran, independentment de la configuraciÃŗ segÃŧent.", + "machine_learning_duplicate_detection_setting_description": "Usa incrustacions CLIP per a trobar prossibles duplicats", + "machine_learning_enabled": "Activa l'aprenentatge automàtic", + "machine_learning_enabled_description": "Si està desactivat, totes les funcions ML es deshabilitaran sense tenir en compte la configuraciÃŗ segÃŧent.", "machine_learning_facial_recognition": "Reconeixement facial", "machine_learning_facial_recognition_description": "Detecta, reconeix i agrupa cares a les imatges", "machine_learning_facial_recognition_model": "Model de reconeixement facial", - "machine_learning_facial_recognition_model_description": "Els models apareixen a la llista en ordre descent segons la mida. Els models mÊs grans sÃŗn mÊs lents i utilitzen mÊs memÃ˛ria, perÃ˛ produeixen millors resultats. Tingueu en compte que desprÊs de canviar un model haureu de tornar a executar la tasca de detecciÃŗ de cares per a totes les imatges.", + "machine_learning_facial_recognition_model_description": "Els models es llisten en ordre descent segons la mida. Els models mÊs grans sÃŗn mÊs lents i usen mÊs memÃ˛ria perÃ˛ produeixen millors resultats. Tingueu en compte que desprÊs de canviar un model haureu de tornar a executar la tasca de detecciÃŗ de cares per a totes les imatges.", "machine_learning_facial_recognition_setting": "Activa el reconeixement facial", - "machine_learning_facial_recognition_setting_description": "Si està desactivat, les imatges no es codificaran per al reconeixement facial i no s’omplirà la secciÃŗ Persones de la pàgina Explora.", + "machine_learning_facial_recognition_setting_description": "Si està desactivat, les imatges no es codificaran pel reconeixement facial i no s'afegiran a la secciÃŗ Persones de la pàgina Explorar.", "machine_learning_max_detection_distance": "Distància màxima de detecciÃŗ", - "machine_learning_max_detection_distance_description": "Distància màxima entre dues imatges per a considerar-les duplicades, en un rang entre 0,001 i 0,1. Els valors mÊs alts detectaran mÊs duplicats, perÃ˛ podeu provocar falsos positius.", - "machine_learning_max_recognition_distance": "Distància màxima de reconeixement", - "machine_learning_max_recognition_distance_description": "Distància màxima entre dues cares per a considerar-les la mateixa persona, entre 0 i 2. Reduir aquest valor pot evitar que s’etiquetin dues persones com a la mateixa, mentre que augmentar-lo pot evitar que s’etiqueti la mateixa persona com a dues de diferents. Tingueu en compte que Ês mÊs fàcil fusionar dues persones que dividir-ne una en dues, de manera que, si Ês possible, Ês millor optar per un llindar mÊs baix.", + "machine_learning_max_detection_distance_description": "Diferència màxima entre dues imatges per a considerar-les duplicades, en un rang d'entre 0.001-0.1. Com mÊs elevat el valor mÊs detecciÃŗ de duplicats, perÃ˛ pot resultar en falsos positius.", + "machine_learning_max_recognition_distance": "Màxima diferència de reconeixement", + "machine_learning_max_recognition_distance_description": "La distància màxima entre dues cares per considerar-les la mateixa persona, que oscil¡la entre 0-2. Reduir aquest valor pot evitar etiquetar dues persones com la mateixa, mentre que augmentar-lo pot evitar etiquetar la mateixa persona com dues diferents. Tingues en compte que Ês mÊs fàcil fusionar dues persones que dividir-ne una en dues, així que, si Ês possible, Ês millor optar per un llindar mÊs baix.", "machine_learning_min_detection_score": "PuntuaciÃŗ mínima de detecciÃŗ", - "machine_learning_min_detection_score_description": "PuntuaciÃŗ de confiança mínima perquè es detecti una cara entre 0 i 1. Els valors mÊs baixos detectaran mÊs cares, perÃ˛ poden provocar falsos positius.", - "machine_learning_min_recognized_faces": "Mínim de cares reconegudes", - "machine_learning_min_recognized_faces_description": "Nombre mínim de cares reconegudes perquè es creï una persona. Augmentar aquest valor fa que el reconeixement facial sigui mÊs precís, perÃ˛ augmenta la probabilitat que una cara no sigui assignada a una persona.", + "machine_learning_min_detection_score_description": "La puntuaciÃŗ mínima de confiança per detectar una cara Ês de 0 a 1. Valors mÊs baixos detectaran mÊs cares, perÃ˛ poden donar lloc a falsos positius.", + "machine_learning_min_recognized_faces": "Nombre mínim de cares reconegudes", + "machine_learning_min_recognized_faces_description": "El nombre mínim de cares reconegudes per crear una persona. Augmentar aquest valor fa que el reconeixement facial sigui mÊs precís, perÃ˛ augmenta la possibilitat que una cara no sigui assignada a una persona.", "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Utilitza aprenentatge automàtic per a reconèixer text a les imatges", - "machine_learning_ocr_enabled": "Activa l’OCR", - "machine_learning_ocr_enabled_description": "Si està desactivat, les imatges no se sotmetran al reconeixement de text.", - "machine_learning_ocr_max_resolution": "ResoluciÃŗ màxima", - "machine_learning_ocr_max_resolution_description": "Les previsualitzacions que estiguin per sobre d’aquesta resoluciÃŗ es redimensionaran mantenint-ne la relaciÃŗ d’aspecte. Els valors mÊs alts sÃŗn mÊs precisos, perÃ˛ tarden mÊs temps a processar-se i utilitzem mÊs memÃ˛ria.", + "machine_learning_ocr_description": "Fes servir machine learning per reconèixer text a imatges", + "machine_learning_ocr_enabled": "Activar OCR", + "machine_learning_ocr_enabled_description": "Si està desactivat, les imatges no seran objecte de reconeixement de text.", + "machine_learning_ocr_max_resolution": "Màxima resoluciÃŗ", + "machine_learning_ocr_max_resolution_description": "Vista prèvia per sobre d'aquesta resoluciÃŗ serà reescalada per preservar la relaciÃŗ d'aspecte. Resolucions altes sÃŗn mÊs precises, perÃ˛ triguen mÊs i gasten mÊs memÃ˛ria.", "machine_learning_ocr_min_detection_score": "PuntuaciÃŗ mínima de detecciÃŗ", - "machine_learning_ocr_min_detection_score_description": "PuntuaciÃŗ de confiança mínima perquè es detectiu text entre 0 i 1. Els valors baixos detectaran mÊs text, perÃ˛ poden provocar falsos positius.", - "machine_learning_ocr_min_recognition_score": "PuntuaciÃŗ de reconeixement mínima", - "machine_learning_ocr_min_score_recognition_description": "PuntuaciÃŗ de confiança mínima perquè el text detectat sigui reconegut entre 0 i 1. Els valors baixos reconeixeran mÊs text, perÃ˛ poden provocar falsos positius.", - "machine_learning_ocr_model": "Model d’OCR", - "machine_learning_ocr_model_description": "Els models de servidor sÃŗn mÊs precisos que els de mÃ˛bils, pero triguen mÊs temps a processar i utilitzen mÊs memÃ˛ria.", - "machine_learning_settings": "ConfiguraciÃŗ de l’aprenentatge automàtic", - "machine_learning_settings_description": "Gestioneu les funcions i la configuraciÃŗ de l’aprenentatge automàtic", + "machine_learning_ocr_min_detection_score_description": "PuntuaciÃŗ de mínima confiança per la detecciÃŗ del text entre 0-1. Valors baixos detectaran mÊs text pero pot donar falsos positius.", + "machine_learning_ocr_min_recognition_score": "PuntuaciÃŗ mínima de reconeixement", + "machine_learning_ocr_min_score_recognition_description": "PuntuaciÃŗ de confiança mínima pel reconeixement del text entre 0-1. Valors baixos reconeixen mÊs text perÃ˛ pot donar falsos positius.", + "machine_learning_ocr_model": "Model OCR", + "machine_learning_ocr_model_description": "Models de servidor sÃŗn mÊs precisos que els de mÃŗbil, pero triguen mÊs a processar i usen mÊs memÃ˛ria.", + "machine_learning_settings": "ConfiguraciÃŗ d'aprenentatge automàtic", + "machine_learning_settings_description": "Gestiona funcions i configuraciÃŗ d'aprenentatge automàtic", "machine_learning_smart_search": "Cerca intel¡ligent", - "machine_learning_smart_search_description": "Cerqueu imatges semànticament utilitzant incrustacions CLIP", + "machine_learning_smart_search_description": "Cerca imatges semànticament emprant incrustacions CLIP", "machine_learning_smart_search_enabled": "Activa la cerca intel¡ligent", - "machine_learning_smart_search_enabled_description": "Si està desactivada, les imatges no es codificaran per a la cerca intel¡ligent.", - "machine_learning_url_description": "L’URL del servidor d’aprenentatge automàtic. Si proporcioneu mÊs d’un URL, es provarà d’accedir a cada servidor en ordre fins que un respongui correctament. Els servidors que no responguin s’ignoraran temporalment fins que tornin a estar en línia.", - "maintenance_backup_management": "GestiÃŗ de cÃ˛pies de seguretat", - "maintenance_delete_backup": "Suprimeix la cÃ˛pia de seguretat", - "maintenance_delete_backup_description": "Aquest fitxer se suprimirà de manera permanent.", - "maintenance_delete_error": "No s’ha pogut suprimir la cÃ˛pia de seguretat.", - "maintenance_integrity_check": "Verifica", - "maintenance_integrity_check_all": "Verifica-ho tot", - "maintenance_integrity_checksum_mismatch": "VerificaciÃŗ incorrecta", - "maintenance_integrity_checksum_mismatch_description": "Fitxers la suma de verificaciÃŗ dels quals no coincideix amb la que l’Immich tÊ emmagatzemada a la base de dades.", - "maintenance_integrity_checksum_mismatch_job": "Comprova les incoherències de verificaciÃŗ", - "maintenance_integrity_checksum_mismatch_refresh_job": "Actualitza els informes de verificaciÃŗ incorrecta", - "maintenance_integrity_missing_file": "Fitxers absents", - "maintenance_integrity_missing_file_description": "Fitxers que l’Immich tÊ registrats a la base de dades, perÃ˛ que no existeixen al sistema de fitxers.", - "maintenance_integrity_missing_file_job": "Verifica els fitxers absents", - "maintenance_integrity_missing_file_refresh_job": "Actualitza els informes de fitxers absents", - "maintenance_integrity_report": "Informe d’integritat", - "maintenance_integrity_untracked_file": "Fitxers sense seguiment", - "maintenance_integrity_untracked_file_description": "Fitxers als directoris de l’Immich dels quals l’Immich no tÊ cap registre.", - "maintenance_integrity_untracked_file_job": "Revisa els fitxers sense seguiment", - "maintenance_integrity_untracked_file_refresh_job": "Actualitza els informes dels fitxers sense seguiment", - "maintenance_restore_backup": "Restaura una cÃ˛pia de seguretat", - "maintenance_restore_backup_description": "S’esborrarà el contingut de l’Immich i es restaurarà a partir de la cÃ˛pia de seguretat elegida. Abans de continuar, es crearà una cÃ˛pia de seguretat.", - "maintenance_restore_backup_different_version": "Aquesta cÃ˛pia de seguretat es va crear amb una versiÃŗ diferent de l’Immich!", - "maintenance_restore_backup_unknown_version": "No s’ha pogut determinar la versiÃŗ de la cÃ˛pia de seguretat.", - "maintenance_restore_database_backup": "Restaura una cÃ˛pia de seguretat de la base de dades", - "maintenance_restore_database_backup_description": "Revertiu a un estat anterior de la base de dades mitjançant un fitxer de cÃ˛pia de seguretat", - "maintenance_settings": "Manteniment", - "maintenance_settings_description": "Poseu l’Immich en mode de manteniment.", - "maintenance_start": "Canvia al mode de manteniment", - "maintenance_start_error": "S’ha produït un error en iniciar el mode de manteniment.", - "maintenance_upload_backup": "Pugeu un fitxer de cÃ˛pia de seguretat de la base de dades", - "maintenance_upload_backup_error": "No s’ha pogut pujar la cÃ˛pia de seguretat. És un fitxer .sql/.sql.gz?", + "machine_learning_smart_search_enabled_description": "Si està desactivada, les imatges no es codificaran per la cerca intel¡ligent.", + "machine_learning_url_description": "L'URL del servidor d'aprenentatge automàtic. Si es proporciona mÊs d'un URL, s'intentarà accedir a cada servidor en ordre fins que un d'ells respongui correctament.", + "maintenance_delete_backup": "Elimina la cÃ˛pia de seguretat", + "maintenance_delete_backup_description": "Aquest fitxer s'eliminarà de forma permanent.", + "maintenance_delete_error": "No s'ha pogut suprimir la cÃ˛pia de seguretat.", + "maintenance_restore_backup": "Restaura la cÃ˛pia de seguretat", + "maintenance_restore_backup_description": "Immich s'esborrarà i es restaurarà des de la cÃ˛pia de seguretat escollida. Es crearà una cÃ˛pia de seguretat abans de continuar.", + "maintenance_restore_backup_different_version": "Aquesta cÃ˛pia de seguretat s'ha creat amb una versiÃŗ diferent d'Immich!", + "maintenance_restore_backup_unknown_version": "No s'ha pogut determinar la versiÃŗ de la cÃ˛pia de seguretat.", + "maintenance_restore_database_backup": "Restaurar la cÃ˛pia de seguretat de la base de dades", + "maintenance_restore_database_backup_description": "Reverteix a un estat anterior de la base de dades mitjançant un fitxer de cÃ˛pia de seguretat", + "maintenance_settings": "En manteniment", + "maintenance_settings_description": "Posar Immich en mode de manteniment.", + "maintenance_start": "Canviar al mode de manteniment", + "maintenance_start_error": "Error en iniciar el mode de manteniment.", + "maintenance_upload_backup": "Puja el fitxer de cÃ˛pia de seguretat de la base de dades", + "maintenance_upload_backup_error": "No s'ha pogut carregar la cÃ˛pia de seguretat, Ês un fitxer .sql/.sql.gz?", "manage_concurrency": "Gestiona la concurrència", - "manage_concurrency_description": "Aneu a la pàgina de tasques per a gestionar la concurrència de tasques", - "manage_log_settings": "Gestioneu la configuraciÃŗ del registre", - "map_dark_style": "Estil fosc", - "map_enable_description": "Activa les funcionalitats del mapa", - "map_gps_settings": "ConfiguraciÃŗ del mapa i del GPS", - "map_gps_settings_description": "Gestioneu els paràmetres del mapa i del GPS (geocodificaciÃŗ inversa)", - "map_implications": "La funciÃŗ del mapa depèn del servei extern de tesel¡les (tiles.immich.cloud)", - "map_light_style": "Estil clar", - "map_manage_reverse_geocoding_settings": "Gestioneu els paràmetres de la geocodificaciÃŗ inversa", + "manage_concurrency_description": "Ves a la pàgina de tasques per gestionar la concurrència de tasques", + "manage_log_settings": "Gestiona la configuraciÃŗ del registre", + "map_dark_style": "Tema fosc", + "map_enable_description": "Habilita característiques del mapa", + "map_gps_settings": "ConfiguraciÃŗ del mapa i GPS", + "map_gps_settings_description": "Gestiona la configuraciÃŗ del mapa i GPS (GeocodificaciÃŗ inversa)", + "map_implications": "La funciÃŗ mapa depèn del servei extern de tesel¡les (tiles.immich.cloud)", + "map_light_style": "Tema clar", + "map_manage_reverse_geocoding_settings": "Gestiona els paràmetres de geocodificaciÃŗ inversa", "map_reverse_geocoding": "GeocodificaciÃŗ inversa", - "map_reverse_geocoding_enable_description": "Activa la geocodificaciÃŗ inversa", - "map_reverse_geocoding_settings": "ConfiguraciÃŗ de la geocodificaciÃŗ inversa", + "map_reverse_geocoding_enable_description": "Habilita la geocodificaciÃŗ inversa", + "map_reverse_geocoding_settings": "ConfiguraciÃŗ de GeocodificaciÃŗ Inversa", "map_settings": "Mapa", - "map_settings_description": "Gestioneu la configuraciÃŗ del mapa", - "map_style_description": "URL a un style.json de tema del mapa", - "memory_cleanup_job": "Neteja de records", - "memory_generate_job": "GeneraciÃŗ de records", - "metadata_extraction_job": "Extreu les metadades", - "metadata_extraction_job_description": "Extreu la informaciÃŗ de les metadades de cada recurs, com ara el GPS, les cares i la resoluciÃŗ", - "metadata_faces_import_setting": "Activa la importaciÃŗ de cares", - "metadata_faces_import_setting_description": "Importa les cares de les metadades EXIF i dels fitxers auxiliars de les imatges", + "map_settings_description": "Gestiona la configuraciÃŗ del mapa", + "map_style_description": "URL a un tema del mapa style.json", + "memory_cleanup_job": "Neteja de memÃ˛ries", + "memory_generate_job": "GeneraciÃŗ de memÃ˛ries", + "metadata_extraction_job": "Extreure metadades", + "metadata_extraction_job_description": "Extreu la informaciÃŗ de metadades de cada element, com per exemple el GPS i la resoluciÃŗ", + "metadata_faces_import_setting": "Activar la importaciÃŗ de cares", + "metadata_faces_import_setting_description": "Importar cares des de les metadades EXIF de les imatges i arxius auxiliars", "metadata_settings": "ConfiguraciÃŗ de les metadades", - "metadata_settings_description": "Gestioneu els paràmetres de les metadades", + "metadata_settings_description": "Administrar la configuraciÃŗ de les metadades", "migration_job": "MigraciÃŗ", - "migration_job_description": "Migra les miniatures dels recursos i les cares a la estructura de carpetes mÊs recent", - "nightly_tasks_cluster_faces_setting_description": "Executa el reconeixement facial a les cares detectades recentment", - "nightly_tasks_cluster_new_faces_setting": "Agrupa les cares noves", + "migration_job_description": "Migra les miniatures d'elements i cares cap a la nova estructura de carpetes", + "nightly_tasks_cluster_faces_setting_description": "Executar el reconeixement facial en cares recentment detectades", + "nightly_tasks_cluster_new_faces_setting": "Agrupa cares noves", "nightly_tasks_database_cleanup_setting": "Tasques de neteja de la base de dades", "nightly_tasks_database_cleanup_setting_description": "Netegeu les dades antigues i caducades de la base de dades", "nightly_tasks_generate_memories_setting": "Generar memÃ˛ries", "nightly_tasks_generate_memories_setting_description": "Crear nous records a partir de les fotos penjades", "nightly_tasks_missing_thumbnails_setting": "Generar les miniatures restants", - "nightly_tasks_missing_thumbnails_setting_description": "Posa en cua els recursos sense miniatures perquè se’n generin les miniatures", + "nightly_tasks_missing_thumbnails_setting_description": "Posar en cua les fotos penjades sense miniatures per a la generaciÃŗ de la seva miniatura", "nightly_tasks_settings": "ConfiguraciÃŗ de les tasques nocturnes", - "nightly_tasks_settings_description": "Gestioneu les tasques nocturnes", - "nightly_tasks_start_time_setting": "Hora d’inici", + "nightly_tasks_settings_description": "Gestionar les tasques nocturnes", + "nightly_tasks_start_time_setting": "Hora d'inici", "nightly_tasks_start_time_setting_description": "Hora en què el servidor comença a executar les tasques nocturnes", - "nightly_tasks_sync_quota_usage_setting": "Sincronitza la quota d’Ãēs", - "nightly_tasks_sync_quota_usage_setting_description": "Actualitza la quota d’emmagatzematge de l’usuari segons l’Ãēs actual", - "no_paths_added": "No s’ha afegit cap camí", - "no_pattern_added": "No s’ha afegit cap patrÃŗ", - "note_apply_storage_label_previous_assets": "Nota: Per a aplicar l’etiqueta d’emmagatzematge als recursos pujats prèviament, executeu la", - "note_cannot_be_changed_later": "NOTA: DesprÊs no es podrà canviar!", - "notification_email_from_address": "De l’adreça", - "notification_email_from_address_description": "Adreça electrÃ˛nica del remitent, per exemple, ÂĢImmich Photo Server Âģ. Assegureu-vos d’utilitzar una adreça amb la qual tingueu permís per a enviar correus electrÃ˛nics.", - "notification_email_host_description": "AmfitriÃŗ del servidor de correu electrÃ˛nic (p. ex. smtp.immich.app)", + "nightly_tasks_sync_quota_usage_setting": "Sincronitzar l'Ãēs de la quota", + "nightly_tasks_sync_quota_usage_setting_description": "Actualitzar la quota d'emmagatzematge de l'usuari segons l'Ãēs actual", + "no_paths_added": "No s'ha afegit cap ruta", + "no_pattern_added": "Cap patrÃŗ aplicat", + "note_apply_storage_label_previous_assets": "Nota: Per aplicar l'etiquetatge d'emmagatzematge a elements pujats prèviament, executeu la", + "note_cannot_be_changed_later": "NOTA: AixÃ˛ Ês irreversible!", + "notification_email_from_address": "Des de l'adreça", + "notification_email_from_address_description": "Adreça de correu electrÃ˛nic del remitent, per exemple: \"Immich Photo Server \". Assegureu-vos d'utilitzar una adreça des de la qual tingueu permís per enviar correus electrÃ˛nics.", + "notification_email_host_description": "AmfitriÃŗ del servidor de correu electrÃ˛nic (p.ex. smtp.immich.app)", "notification_email_ignore_certificate_errors": "Ignora els errors de certificat", "notification_email_ignore_certificate_errors_description": "Ignora els errors de validaciÃŗ de certificat TLS (no recomanat)", "notification_email_password_description": "Contrasenya per a autenticar-se amb el servidor de correu electrÃ˛nic", - "notification_email_port_description": "Port del servidor de correu electrÃ˛nic (p. ex. 25, 465 o 587)", + "notification_email_port_description": "Port del servidor de correu electrÃ˛nic (p.ex. 25, 465 o 587)", "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Utilitza SMTPS (SMTP sobre TLS)", - "notification_email_sent_test_email_button": "Envia un correu electrÃ˛nic de prova i desa", - "notification_email_setting_description": "ConfiguraciÃŗ per a l’enviament de notificacions per correu electrÃ˛nic", - "notification_email_test_email": "Envia un correu electrÃ˛nic de prova", - "notification_email_test_email_failed": "No s’ha pogut enviar el correu electrÃ˛nic de prova, reviseu els valors introduïts", - "notification_email_test_email_sent": "S’ha enviat un correu electrÃ˛nic de prova a {email}. Reviseu-ne la safata d’entrada.", - "notification_email_username_description": "Usuari que s’utilitzarà per a autenticar-se al servidor de correu electrÃ˛nic", - "notification_enable_email_notifications": "Activa les notificacions per correu electrÃ˛nic", - "notification_settings": "ConfiguraciÃŗ de les notificacions", - "notification_settings_description": "Gestioneu els paràmetres de les notificacions, incloent-hi el correu electrÃ˛nic", - "oauth_allow_insecure_requests": "Permet sol¡licituds no segures", - "oauth_allow_insecure_requests_description": "ADVERTÈNCIA: AixÃ˛ desactiva la validaciÃŗ de certificats TLS per a les sol¡licituds d’OAuth i us pot exposar a atacs MITM.", - "oauth_auto_launch": "Executa automàticament", - "oauth_auto_launch_description": "Inicia el flux d’inici de sessiÃŗ d’OAuth automàticament en accedir a la pàgina d’inici de sessiÃŗ", - "oauth_auto_register": "Registra automàticament", - "oauth_auto_register_description": "Registra els nous usuaris automàticament desprÊs d’iniciar sessiÃŗ amb OAuth", + "notification_email_secure_description": "Fes servir SMTPS (SMTP sobre TLS)", + "notification_email_sent_test_email_button": "Envia correu de prova i desa", + "notification_email_setting_description": "ConfiguraciÃŗ per l'enviament de notificacions per correu electrÃ˛nic", + "notification_email_test_email": "Envia correu de prova", + "notification_email_test_email_failed": "L'enviament del correu de prova ha fallat, comproveu els valors introduïts", + "notification_email_test_email_sent": "Un correu de prova ha estat enviat a {email}. Si us plau, comproveu la safata d'entrada.", + "notification_email_username_description": "Usuari a fer servir a l'autenticar-se al servidor de correu electrÃ˛nic", + "notification_enable_email_notifications": "Habilita les notificacions de correu electrÃ˛nic", + "notification_settings": "ConfiguraciÃŗ de notificacions", + "notification_settings_description": "Gestiona la configuraciÃŗ de notificacions, incloent-hi el correu electrÃ˛nic", + "oauth_auto_launch": "ExecuciÃŗ automàtica", + "oauth_auto_launch_description": "Inicia el flux d'inici de sessiÃŗ OAuth automàticament en accedir a la pàgina d'inici de sessiÃŗ", + "oauth_auto_register": "Registre automàtic", + "oauth_auto_register_description": "Registra nous usuaris automàticament desprÊs d'iniciar sessiÃŗ amb OAuth", "oauth_button_text": "Text del botÃŗ", - "oauth_client_secret_description": "Necessari per a clients confidencials, o si el client pÃēblic no admet PKCE (Proof Key for Code Exchange).", - "oauth_enable_description": "Inicia la sessiÃŗ amb OAuth", - "oauth_end_session_url_description": "Redirigeix l’usuari a aquest URI quan tanqui la sessiÃŗ.", + "oauth_client_secret_description": "Requerit per clients confidencials, o si PKCE (Proof Key for Code Exchange) no està suportat pel client pÃēblic.", + "oauth_enable_description": "Iniciar sessiÃŗ amb OAuth", "oauth_mobile_redirect_uri": "URI de redirecciÃŗ mÃ˛bil", - "oauth_mobile_redirect_uri_override": "Sobreescriu l’URI de redirecciÃŗ mÃ˛bil", - "oauth_mobile_redirect_uri_override_description": "Activeu-ho quan el proveïdor d’OAuth no permeti una URI mÃ˛bil, com ara ÂĢ{callback}Âģ", - "oauth_prompt_description": "Paràmetre de la sol¡licitud (per exemple, select_account, login, consent)", + "oauth_mobile_redirect_uri_override": "Sobreescriu l'URI de redirecciÃŗ mÃ˛bil", + "oauth_mobile_redirect_uri_override_description": "Habilita quan el proveïdor d'OAuth no permet una URI mÃ˛bil, com ara ''{callback}''", "oauth_role_claim": "ConcessiÃŗ de rol", - "oauth_role_claim_description": "Atorga l’accÊs d’administrador automàticament segons la presència d’aquesta concessiÃŗ. La concessiÃŗ pot tenir ÂĢuserÂģ o ÂĢadminÂģ.", + "oauth_role_claim_description": "Atorgar accÊs d'administrador automàticament segons la presència d'aquesta concessiÃŗ. La concessiÃŗ pot ser 'usuari' o 'admin'.", "oauth_settings": "OAuth", - "oauth_settings_description": "Gestioneu els paràmetres de l’inici de sessiÃŗ amb OAuth", + "oauth_settings_description": "Gestiona la configuraciÃŗ de l'inici de sessiÃŗ OAuth", "oauth_settings_more_details": "Per a mÊs detalls sobre aquesta funcionalitat, consulteu la documentaciÃŗ.", - "oauth_storage_label_claim": "ConcessiÃŗ d’etiqueta d’emmagatzematge", - "oauth_storage_label_claim_description": "Defineix automàticament l’etiqueta d’emmagatzematge de l’usuari al valor d’aquesta concessiÃŗ.", - "oauth_storage_quota_claim": "ConcessiÃŗ de quota d’emmagatzematge", - "oauth_storage_quota_claim_description": "Defineix automàticament la quota d’emmagatzematge de l’usuari al valor d’aquesta concessiÃŗ.", - "oauth_storage_quota_default": "Quota d’emmagatzematge per defecte (GiB)", - "oauth_storage_quota_default_description": "Quota en GiB que s’utilitzarà quan no es proporcioni cap concessiÃŗ específica.", - "oauth_timeout": "Temps d’espera de les sol¡licituds", - "oauth_timeout_description": "Temps d’espera de les sol¡licituds en mil¡lisegons", - "ocr_job_description": "Utilitza aprenentatge automàtica per a reconèixer text a les imatges", - "password_enable_description": "Inicia la sessiÃŗ amb correu electrÃ˛nic i contrasenya", + "oauth_storage_label_claim": "PeticiÃŗ d'etiquetatge d'emmagatzematge", + "oauth_storage_label_claim_description": "Estableix automàticament l'etiquetatge d'emmagatzematge de l'usuari a aquest valor.", + "oauth_storage_quota_claim": "Quota d'emmagatzematge reclamada", + "oauth_storage_quota_claim_description": "Estableix automàticament la quota d'emmagatzematge de l'usuari al valor d'aquest paràmetre.", + "oauth_storage_quota_default": "Quota d'emmagatzematge predeterminada (GiB)", + "oauth_storage_quota_default_description": "Quota en GiB que s'utilitzarà quan no es proporcioni cap valor específic.", + "oauth_timeout": "Solicitud caducada", + "oauth_timeout_description": "Timeout per a sol¡licituds en mil¡lisegons", + "ocr_job_description": "Fes servir machine learning per reconèixer text a les imatges", + "password_enable_description": "Inicia sessiÃŗ amb correu electrÃ˛nic i contrasenya", "password_settings": "Inici de sessiÃŗ amb contrasenya", - "password_settings_description": "Gestioneu els paràmetres de l’inici de sessiÃŗ amb contrasenya", - "paths_validated_successfully": "Tots els camins s’han validat correctament", - "person_cleanup_job": "Neteja de persones", - "queue_details": "Detalls de la cua", - "queues": "Cues de tasques", - "queues_page_description": "Pàgina de cues de treballs de l’administrador", - "quota_size_gib": "Mida de la quota (GiB)", - "refreshing_all_libraries": "S’estan actualitzant totes les biblioteques", - "registration": "Registre de l’administrador", - "registration_description": "Com que sou el primer usuari del sistema, sereu designat com a administrador, sereu responsable de les tasques administratives i creareu la resta d’usuaris.", - "release_channel_release_candidate": "VersiÃŗ candidata", - "release_channel_stable": "Estable", - "remove_failed_jobs": "Suprimeix les tasques fallides", - "require_password_change_on_login": "Requereix que l’usuari canviï la contrasenya en el primer inici de sessiÃŗ", - "reset_settings_to_default": "Restableix la configuraciÃŗ per defecte", - "reset_settings_to_recent_saved": "Restableix la configuraciÃŗ a la configuraciÃŗ desada mÊs recent", - "scanning_library": "S’està escanejant la biblioteca", - "search_jobs": "Cerqueu treballsâ€Ļ", - "send_welcome_email": "Envia un correu electrÃ˛nic de benvinguda", + "password_settings_description": "Gestiona la configuraciÃŗ de l'inici de sessiÃŗ amb contrasenya", + "paths_validated_successfully": "Totes les rutes han estat validades amb èxit", + "person_cleanup_job": "Neteja de persona", + "queue_details": "Detall de les cues", + "queues": "Cues de treball", + "queues_page_description": "Pàgina de cues de treballs d'administrador", + "quota_size_gib": "Tamany de la quota (GiB)", + "refreshing_all_libraries": "Actualitzant totes les biblioteques", + "registration": "Registre d'administrador", + "registration_description": "Com que ets el primer usuari del sistema, seràs designat com a administrador i seràs responsable de les tasques administratives. TambÊ seràs l'encarregat de crear usuaris addicionals.", + "remove_failed_jobs": "Eliminar treballs fallits", + "require_password_change_on_login": "Requerir que l'usuari canviï la contrasenya en el primer inici de sessiÃŗ", + "reset_settings_to_default": "Restablir configuracions per defecte", + "reset_settings_to_recent_saved": "Restablir la configuraciÃŗ guardada mÊs recent", + "scanning_library": "Escanejant biblioteca", + "search_jobs": "Cercar treballsâ€Ļ", + "send_welcome_email": "Enviar correu electrÃ˛nic de benvinguda", "server_external_domain_settings": "Domini extern", - "server_external_domain_settings_description": "Domini utilitzat per als enllaços externs", + "server_external_domain_settings_description": "Domini utilitzat per a enllaços externs", "server_public_users": "Usuaris pÃēblics", - "server_public_users_description": "Tots els usuaris (nom i adreça electrÃ˛nica) apareixen a la llista en afegir un usuari als àlbums compartits. Si es desactiva, la llista d’usuaris nomÊs serà disponible per als usuaris administradors.", + "server_public_users_description": "Tots els usuaris (nom i correu electrÃ˛nic) apareixen a la llista a l'afegir un usuari als àlbums compartits. Si es desactiva, la llista nomÊs serà disponible pels usuaris administradors.", "server_settings": "ConfiguraciÃŗ del servidor", - "server_settings_description": "Gestioneu els paràmetres del servidor", - "server_stats_page_description": "Pàgina d’estadístiques del servidor de l’administrador", + "server_settings_description": "Gestiona la configuraciÃŗ del servidor", + "server_stats_page_description": "Pàgina d'estadístiques del servidor de l'administrador", "server_welcome_message": "Missatge de benvinguda", - "server_welcome_message_description": "Missatge que es mostra a la pàgina d’inici de sessiÃŗ.", - "settings_page_description": "Pàgina de configuraciÃŗ de l’administrador", - "sidecar_job": "Metadades de fitxers auxiliars", - "sidecar_job_description": "Descobreix o sincronitza metadades dels fitxers auxiliars a partir del sistema de fitxers", - "slideshow_duration_description": "Nombre de segons que es mostrarà cada imatge", - "smart_search_job_description": "Executa aprenentatge automàtic sobre els recursos per a assistir la cerca intel¡ligent", - "storage_template_date_time_description": "La data i hora de creaciÃŗ del recurs s’utilitza per a la informaciÃŗ de la data i l’hora", - "storage_template_date_time_sample": "Data i hora d’exemple: {date}", - "storage_template_enable_description": "Activa el motor de plantilles d’emmagatzematge", - "storage_template_hash_verification_enabled": "VerificaciÃŗ de hash activada", - "storage_template_hash_verification_enabled_description": "Activa la verificaciÃŗ de hash. No la desactiveu llevat que n’entengueu les implicacions", - "storage_template_migration": "MigraciÃŗ de la plantilla d’emmagatzematge", - "storage_template_migration_description": "Aplica la {template} actual als recursos pujats anteriorment", - "storage_template_migration_info": "La plantella d’emmagatzematge convertirà totes les extensions a minÃēscules. Els canvis de la plantilla nomÊs s’aplicaran als nous recursos. Per a aplicar la plantilla de manera retroactiva als recursos pujats anteriorment, executeu la {job}.", - "storage_template_migration_job": "tasca de migraciÃŗ de la plantilla d’emmagatzematge", - "storage_template_more_details": "Per a mÊs detalls sobre aquesta funciÃŗ, consulteu la plantilla d’emmagatzematge i les seves implicacions", - "storage_template_onboarding_description_v2": "En activar aquesta funciÃŗ, s’organitzaran automàticament els fitxers a partir d’una plantilla definida per l’usuari. Per a mÊs informaciÃŗ, consulteu la documentaciÃŗ.", - "storage_template_path_length": "Límit aproximat de longitud del camí: {length, number}/{limit, number}", - "storage_template_settings": "Plantilla d’emmagatzematge", - "storage_template_settings_description": "Gestioneu l’estructura de les carpetes i el nom del fitxers dels recursos pujats", - "storage_template_user_label": "{label} Ês l’etiqueta d’emmagatzematge de l’usuari", + "server_welcome_message_description": "Missatge que es mostra a la pàgina d'inici de sessiÃŗ.", + "settings_page_description": "Pàgina de configuraciÃŗ de l'administrador", + "sidecar_job": "Metadades auxiliars", + "sidecar_job_description": "Descobreix o sincronitza metadades auxiliars des del sistema de fitxers", + "slideshow_duration_description": "Segons per mostrar cada imatge", + "smart_search_job_description": "Executa aprenentatge automàtic sobre els elements per donar suport a la cerca intel¡ligent", + "storage_template_date_time_description": "La data de creaciÃŗ del recurs s'utilitza per a la informaciÃŗ de la data i l'hora", + "storage_template_date_time_sample": "Temps d'exemple: {date}", + "storage_template_enable_description": "Habilitar el motor de plantilles d'emmagatzematge", + "storage_template_hash_verification_enabled": "VerificaciÃŗ de hash habilitada", + "storage_template_hash_verification_enabled_description": "Activa la verificaciÃŗ de hash. No la desactiveu a menys que estigueu segurs de les implicacions", + "storage_template_migration": "MigraciÃŗ de plantilles d'emmagatzematge", + "storage_template_migration_description": "Aplica la {template} actual als elements pujats prèviament", + "storage_template_migration_info": "Les extensions es convertiran a minÃēscules. Els canvis de plantilla nomÊs s'aplicaran a nous elements. Per aplicar la plantilla rectroactivament a elements pujats prèviament, executeu la {job}.", + "storage_template_migration_job": "Tasca de migraciÃŗ de la plantilla d'emmagatzematge", + "storage_template_more_details": "Per obtenir mÊs detalls sobre aquesta funciÃŗ, consulteu la Storage Template i les seves implications", + "storage_template_onboarding_description_v2": "Un cop habilitada, aquesta funciÃŗ organitzarà automàticament els fitxers a partir d'una plantilla definida per l'usuari. Per a mÊs informaciÃŗ, podeu consultar la documentaciÃŗ.", + "storage_template_path_length": "Límit aproximat de longitud de la ruta: {length, number}/{limit, number}", + "storage_template_settings": "Plantilla d'emmagatzematge", + "storage_template_settings_description": "Gestiona l'estructura de les carpetes i el nom del fitxers dels elements pujats", + "storage_template_user_label": "{label} Ês l'etiqueta d'emmagatzematge de l'usuari", "system_settings": "ConfiguraciÃŗ del sistema", - "tag_cleanup_job": "Neteja d’etiquetes", - "template_email_available_tags": "Podeu utilitzar les segÃŧents variables a la plantilla: {tags}", - "template_email_if_empty": "Si la plantilla Ês buida, es farà servir el correu electrÃ˛nic per defecte.", - "template_email_invite_album": "Plantilla d’invitaciÃŗ a l’àlbum", - "template_email_preview": "Previsualitza", + "tag_cleanup_job": "Neteja d'etiqueta", + "template_email_available_tags": "Pots fer servir les segÃŧents variables a la teva plantilla: {tags}", + "template_email_if_empty": "Si la plantilla està buida, es farà servir el correu electrÃ˛nic predeterminat.", + "template_email_invite_album": "Plantilla per l'àlbum d'invitacions", + "template_email_preview": "Vista prèvia", "template_email_settings": "Plantilles de correu electrÃ˛nic", - "template_email_update_album": "Plantilla d’actualitzaciÃŗ a l’àlbum", + "template_email_update_album": "Actualitzar la plantilla de l'àlbum", "template_email_welcome": "Plantilla del correu de benvinguda", "template_settings": "Plantilles de notificaciÃŗ", - "template_settings_description": "Gestioneu les plantilles personalitzades per a les notificacions", + "template_settings_description": "Gestiona les plantilles personalitzades per les notificacions", "theme_custom_css_settings": "CSS personalitzat", - "theme_custom_css_settings_description": "Els fulls d’estil en cascada permeten personalitzar el disseny de l’Immich.", + "theme_custom_css_settings_description": "Els fulls d'estil en cascada permeten personalitzar el disseny d'Immich.", "theme_settings": "ConfiguraciÃŗ del tema", - "theme_settings_description": "Gestioneu la personalitzaciÃŗ de la interfície web de l’Immich", - "thumbnail_generation_job": "Genera les miniatures", - "thumbnail_generation_job_description": "Genera les miniatures grans, petites i borroses per a cada element, així com miniatures per a cada persona", - "transcoding_acceleration_api": "API d’acceleraciÃŗ", - "transcoding_acceleration_api_description": "L’API que interactuarà amb el vostre dispositiu per a accelerar la transcodificaciÃŗ. Aquesta configuraciÃŗ Ês de l’estil ÂĢmillor esforçÂģ: en cas d’error, tornarà a la transcodificaciÃŗ per programari. El VP9 pot funcionar o no depenent del vostre maquinari.", - "transcoding_acceleration_nvenc": "NVENC (requereix GPU d’NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (requereix GPU d’Intel de 7a generaciÃŗ o posterior)", + "theme_settings_description": "Gestiona la personalitzaciÃŗ de la interfície web Immich", + "thumbnail_generation_job": "Generar miniatures", + "thumbnail_generation_job_description": "Genera miniatures grans, petites i borroses per a cada element, així com miniatures per a cada persona", + "transcoding_acceleration_api": "API d'acceleraciÃŗ", + "transcoding_acceleration_api_description": "L'API que interactuarà amb el vostre dispositiu per accelerar la transcodificaciÃŗ. Aquesta configuraciÃŗ Ês \"millor esforç\": tornarà a la transcodificaciÃŗ del programari en cas d'error. VP9 pot funcionar o no depenent del vostre maquinari.", + "transcoding_acceleration_nvenc": "NVENC (requereix GPU d'NVIDIA)", + "transcoding_acceleration_qsv": "Quick Sync (requereix GPU d'Intel de 7a generaciÃŗ o posterior)", "transcoding_acceleration_rkmpp": "RKMPP (requereix SoC de Rockchip)", "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "CÃ˛decs d’àudio acceptats", - "transcoding_accepted_audio_codecs_description": "Seleccioneu quins cÃ˛decs d’àudio no s’han de transcodificar. NomÊs s’utilitza per a determinades polítiques de transcodificaciÃŗ.", + "transcoding_accepted_audio_codecs": "CÃ˛decs d'àudio acceptats", + "transcoding_accepted_audio_codecs_description": "Seleccioneu quins cÃ˛decs d'àudio no s'han de transcodificar. NomÊs s'utilitza per a determinades polítiques de transcodificaciÃŗ.", "transcoding_accepted_containers": "Contenidors acceptats", - "transcoding_accepted_containers_description": "Seleccioneu quins formats de contenidor no s’han de remultiplexar a MP4. NomÊs s’utilitza per a determinades polítiques de transcodificaciÃŗ.", + "transcoding_accepted_containers_description": "Seleccioneu quins formats de contenidor no s'han de redistribuir a MP4. NomÊs s'utilitza per a determinades polítiques de transcodificaciÃŗ.", "transcoding_accepted_video_codecs": "CÃ˛decs de vídeo acceptats", - "transcoding_accepted_video_codecs_description": "Seleccioneu quins cÃ˛decs de vídeo no s’han de transcodificar. NomÊs s’utilitza per a determinades polítiques de transcodificaciÃŗ.", + "transcoding_accepted_video_codecs_description": "Seleccioneu quins cÃ˛decs de vídeo no s'han de transcodificar. NomÊs s'utilitza per a determinades polítiques de transcodificaciÃŗ.", "transcoding_advanced_options_description": "Opcions que la majoria dels usuaris no haurien de canviar", - "transcoding_audio_codec": "CÃ˛dec d’àudio", - "transcoding_audio_codec_description": "Opus Ês l’opciÃŗ de màxima qualitat, perÃ˛ tÊ menys compatibilitat amb dispositius o programari antics.", - "transcoding_bitrate_description": "Vídeos amb una taxa de bits superior a la màxima o que no tenen un format acceptat", - "transcoding_codecs_learn_more": "Per a mÊs informaciÃŗ sobre la terminologia utilitzada, consulteu la documentaciÃŗ d’FFmpeg per al cÃ˛dec H.264, el cÃ˛dec HEVC i el cÃ˛dec VP9.", + "transcoding_audio_codec": "CÃ˛dec d'àudio", + "transcoding_audio_codec_description": "Opus Ês l'opciÃŗ de màxima qualitat, perÃ˛ tÊ menor compatibilitat amb dispositius o programari antics.", + "transcoding_bitrate_description": "Vídeos superiors a la taxa de bits màxima o que no tenen un format acceptat", + "transcoding_codecs_learn_more": "Per obtenir mÊs informaciÃŗ sobre la terminologia utilitzada, consulteu la documentaciÃŗ de FFmpeg per al cÃ˛dec H.264, cÃ˛dec HEVC i cÃ˛dec VP9.", "transcoding_constant_quality_mode": "Mode de qualitat constant", - "transcoding_constant_quality_mode_description": "ICQ Ês millor que CQP, perÃ˛ alguns dispositius d’acceleraciÃŗ de maquinari no admeten aquest mode. Si configureu aquesta opciÃŗ, es preferirà el mode especificat quan s’utilitzi la codificaciÃŗ basada en la qualitat. Ignorat per NVENC perquè no admet ICQ.", + "transcoding_constant_quality_mode_description": "ICQ Ês millor que CQP, perÃ˛ alguns dispositius d'acceleraciÃŗ de maquinari no admeten aquest mode. Establir aquesta opciÃŗ preferirà el mode especificat quan utilitzeu la codificaciÃŗ basada en la qualitat. Ignorat per NVENC perquè no Ês compatible amb ICQ.", "transcoding_constant_rate_factor": "Factor de taxa constant (-crf)", - "transcoding_constant_rate_factor_description": "Nivell de qualitat del vídeo. Els valors típics sÃŗn 23 per a H.264, 28 per a HEVC, 31 per a VP9 i 35 per a AV1. Un valor mÊs baix Ês millor, perÃ˛ produeix fitxers mÊs grans.", - "transcoding_disabled_description": "No transcodifiquis cap vídeo (pot interrompre la reproducciÃŗ en alguns clients)", + "transcoding_constant_rate_factor_description": "Nivell de qualitat del vídeo. Els valors típics sÃŗn 23 per a H.264, 28 per a HEVC, 31 per a VP9 i 35 per a AV1. MÊs baix Ês millor, perÃ˛ produeix fitxers mÊs grans.", + "transcoding_disabled_description": "No transcodifiqueu cap vídeo, pot interrompre la reproducciÃŗ en alguns clients", "transcoding_encoding_options": "Opcions de codificaciÃŗ", - "transcoding_encoding_options_description": "Establiu els cÃ˛decs, la resoluciÃŗ, la qualitat i altres opcions per als vídeos codificats", - "transcoding_hardware_acceleration": "AcceleraciÃŗ per maquinari", - "transcoding_hardware_acceleration_description": "Experimental: transcodificaciÃŗ mÊs ràpida, perÃ˛ pot reduir la qualitat amb la mateixa taxa de bits", - "transcoding_hardware_decoding": "DescodificaciÃŗ per maquinari", - "transcoding_hardware_decoding_setting_description": "Activa l’acceleraciÃŗ d’extrem a extrem en lloc d’accelerar nomÊs la codificaciÃŗ. És possible que no funcioni en tots els vídeos.", + "transcoding_encoding_options_description": "Establiu cÃ˛decs, resoluciÃŗ, qualitat i altres opcions per als vídeos codificats", + "transcoding_hardware_acceleration": "AcceleraciÃŗ de maquinari", + "transcoding_hardware_acceleration_description": "Experimental: transcodificaciÃŗ mÊs ràpida perÃ˛ pot perdre qualitat amb la mateixa tasa de bits", + "transcoding_hardware_decoding": "DescodificaciÃŗ de maquinari", + "transcoding_hardware_decoding_setting_description": "Habilita l'acceleraciÃŗ d'extrem a extrem en lloc d'accelerar nomÊs la codificaciÃŗ. És possible que no funcioni en tots els vídeos.", "transcoding_max_b_frames": "Nombre màxim de B-frames", - "transcoding_max_b_frames_description": "Els valors mÊs alts milloren l’eficiència de la compressiÃŗ, perÃ˛ alenteixen la codificaciÃŗ. És possible que no sigui compatible amb l’acceleraciÃŗ de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.", + "transcoding_max_b_frames_description": "Els valors mÊs alts milloren l'eficiència de la compressiÃŗ, perÃ˛ alenteixen la codificaciÃŗ. És possible que no sigui compatible amb l'acceleraciÃŗ de maquinari en dispositius antics. 0 desactiva els B-frames, mentre que -1 estableix aquest valor automàticament.", "transcoding_max_bitrate": "Taxa de bits màxima", - "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin mÊs previsibles amb un cost menor per a la qualitat. A 720p, els valors típics sÃŗn 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Es desactiva si s’estableix a 0. Quan no s’especifica cap unitat, s’assumeix k (per a kbit/s); per tant, 5000, 5000k i 5M (per a Mbit/s) sÃŗn equivalents.", + "transcoding_max_bitrate_description": "Establir una taxa de bits màxima pot fer que les mides dels fitxers siguin mÊs previsibles amb un cost menor per a la qualitat. A 720p, els valors típics sÃŗn 2600 kbit/s per a VP9 o HEVC, o 4500 kbit/s per a H.264. Desactivat si s'estableix a 0. Quan no s'especifica, s'assumeix kbit/s; per tant 5000 i 5000k i 5M son equivalents.", "transcoding_max_keyframe_interval": "Interval màxim de fotogrames clau", - "transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors mÊs baixos empitjoren l’eficiència de la compressiÃŗ, perÃ˛ milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.", + "transcoding_max_keyframe_interval_description": "Estableix la distància màxima entre fotogrames clau. Els valors mÊs baixos empitjoren l'eficiència de la compressiÃŗ, perÃ˛ milloren els temps de cerca i poden millorar la qualitat en escenes amb moviment ràpid. 0 estableix aquest valor automàticament.", "transcoding_optimal_description": "Vídeos superiors a la resoluciÃŗ objectiu o que no tenen un format acceptat", "transcoding_policy": "Política de transcodificaciÃŗ", - "transcoding_policy_description": "Establiu quan es transcodificarà un vídeo", + "transcoding_policy_description": "Estableix quan es transcodificarà un vídeo", "transcoding_preferred_hardware_device": "Dispositiu de maquinari preferit", - "transcoding_preferred_hardware_device_description": "S’aplica nomÊs a VAAPI i QSV. Estableix el node dri utilitzat per a la transcodificaciÃŗ per maquinari.", - "transcoding_preset_preset": "ConfiguraciÃŗ predefinida (-preset)", - "transcoding_preset_preset_description": "Velocitat de compressiÃŗ. Les configuracions predefinides mÊs lents produeixen fitxers mÊs petits i augmenten la qualitat quan s’orienta a una taxa de bits determinada. El VP9 ignora les velocitats superiors a ÂĢfasterÂģ (ÂĢmÊs ràpidaÂģ).", - "transcoding_realtime": "TranscodificaciÃŗ en temps real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permet que la transcodificaciÃŗ es faci en temps real mentre es transmet el vídeo. Permet canvis de qualitat, perÃ˛ pot causar una latència de reproducciÃŗ mÊs alta i talls depenent de les capacitats del servidor.", - "transcoding_realtime_enabled": "Activa la transcodificaciÃŗ en temps real", - "transcoding_realtime_enabled_description": "Si està desactivat, el servidor rebutjarà iniciar noves sessions de transcodificaciÃŗ en temps real.", - "transcoding_realtime_resolutions": "Resolucions", - "transcoding_realtime_resolutions_description": "Resolucions que s’ofereixen per a la transcodificaciÃŗ en temps real. Les resolucions mÊs altes poden causar problemes de reproducciÃŗ si el servidor no les pot transcodificar prou ràpidament.", - "transcoding_realtime_video_codecs": "CÃ˛decs de vídeo", - "transcoding_realtime_video_codecs_description": "CÃ˛decs de vídeo oferts per a la transcodificaciÃŗ en temps real. Els clients triaran la millor opciÃŗ que admetin durant la reproducciÃŗ. AV1 Ês mÊs eficient que HEVC, que Ês mÊs eficient que H.264. Quan utilitzeu l’acceleraciÃŗ per maquinari, seleccioneu nomÊs els cÃ˛decs que l’accelerador pugui codificar. Quan utilitzeu la transcodificaciÃŗ per programari, tingueu en compte que H.264 Ês mÊs ràpid que AV1, que Ês mÊs ràpid que HEVC.", + "transcoding_preferred_hardware_device_description": "S'aplica nomÊs a VAAPI i QSV. Estableix el node dri utilitzat per a la transcodificaciÃŗ de maquinari.", + "transcoding_preset_preset": "Preestablert (-preset)", + "transcoding_preset_preset_description": "Velocitat de compressiÃŗ. Els valors predefinits mÊs lents produeixen fitxers mÊs petits i augmenten la qualitat quan s'orienta a una taxa de bits determinada. VP9 ignora les velocitats superiors a 'mÊs ràpides'.", "transcoding_reference_frames": "Fotogrames de referència", - "transcoding_reference_frames_description": "El nombre de fotogrames a referenciar en comprimir un fotograma determinat. Els valors mÊs alts milloren l’eficiència de la compressiÃŗ, perÃ˛ alenteixen la codificaciÃŗ. 0 estableix aquest valor automàticament.", + "transcoding_reference_frames_description": "El nombre de fotogrames a fer referència en comprimir un fotograma determinat. Els valors mÊs alts milloren l'eficiència de la compressiÃŗ, perÃ˛ alenteixen la codificaciÃŗ. 0 estableix aquest valor automàticament.", "transcoding_required_description": "NomÊs vídeos que no tenen un format acceptat", - "transcoding_settings": "ConfiguraciÃŗ de la transcodificaciÃŗ de vídeos", - "transcoding_settings_description": "Gestioneu quins vídeos transcodificar i com processar-los", + "transcoding_settings": "ConfiguraciÃŗ de transcodificaciÃŗ de vídeo", + "transcoding_settings_description": "Gestionar quins vídeos transcodificar i com processar-los", "transcoding_target_resolution": "ResoluciÃŗ objectiu", - "transcoding_target_resolution_description": "Les resolucions mÊs altes poden conservar mÊs detalls, perÃ˛ triguen mÊs temps a codificar-se, tenen mides de fitxer mÊs grans i poden reduir la capacitat de resposta de l’aplicaciÃŗ.", + "transcoding_target_resolution_description": "Les resolucions mÊs altes poden conservar mÊs detalls, perÃ˛ triguen mÊs temps a codificar-se, tenen mides de fitxer mÊs grans i poden reduir la capacitat de resposta de l'aplicaciÃŗ.", "transcoding_temporal_aq": "AQ temporal", - "transcoding_temporal_aq_description": "S’aplica nomÊs a NVENC. La quantitzaciÃŗ adaptativa temporal augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.", + "transcoding_temporal_aq_description": "S'aplica nomÊs a NVENC. QuantitzaciÃŗ adaptativa temporal augmenta la qualitat de les escenes de baix moviment i alt detall. És possible que no sigui compatible amb dispositius antics.", "transcoding_threads": "Fils", - "transcoding_threads_description": "Els valors mÊs alts causen una codificaciÃŗ mÊs ràpida, perÃ˛ deixen menys espai perquè el servidor processi altres tasques mentre està activa. Aquest valor no hauria de ser superior al nombre de nuclis de CPU. Maximitza la utilitzaciÃŗ si s’estableix a 0.", + "transcoding_threads_description": "Els valors mÊs alts condueixen a una codificaciÃŗ mÊs ràpida, perÃ˛ deixen menys espai perquè el servidor processi altres tasques mentre està actiu. Aquest valor no hauria de ser superior al nombre de nuclis de CPU. Maximitza la utilitzaciÃŗ si s'estableix a 0.", "transcoding_tone_mapping": "Mapeig de to", - "transcoding_tone_mapping_description": "Mira de preservar l’aspecte dels vídeos HDR quan es converteixen a SDR. Cada algorisme fa diferents compensacions de color, detall i brillantor. Hable conserva els detalls, Mobius conserva el color i Reinhard conserva la brillantor.", + "transcoding_tone_mapping_description": "Intenta preservar l'aspecte dels vídeos HDR quan es converteixen a SDR. Cada algorisme fa diferents compensacions pel color, el detall i la brillantor. Hable conserva els detalls, Mobius conserva el color i Reinhard conserva la brillantor.", "transcoding_transcode_policy": "Política de transcodificaciÃŗ", - "transcoding_transcode_policy_description": "Política que defineix quan s’ha de transcodificar un vídeo. Els vídeos HDR i els vídeos amb un format de píxels diferent de YUV 4:2:0 sempre es transcodificaran (excepte si la transcodificaciÃŗ està desactivada).", - "transcoding_two_pass_encoding": "CodificaciÃŗ en dues passades", - "transcoding_two_pass_encoding_setting_description": "Transcodifica en dues passades per a produir vídeos mÊs ben codificats. Quan la taxa de bits màxima està activada (necessari perquè funcioni amb H.264 i HEVC), aquest mode utilitza un interval de taxa de bits basat en la taxa de bits màxima i ignora el CRF. Per a VP9, es pot utilitzar el CRF si la taxa de bits màxima està desactivada.", + "transcoding_transcode_policy_description": "Política sobre quan s'ha de transcodificar un vídeo. Els vídeos HDR i els vídeos amb un format de píxel diferent a YUV 4:2:0 sempre es transcodificaran (excepte si la transcodificaciÃŗ està desactivada).", + "transcoding_two_pass_encoding": "CodificaciÃŗ de dues passades", + "transcoding_two_pass_encoding_setting_description": "Transcodifica en dos passos per produir vídeos millor codificats. Quan la taxa de bits màxima està habilitada (necessari perquè funcioni amb H.264 i HEVC), aquest mode utilitza un interval de velocitat de bits basat en la taxa de bits màxima i ignora CRF. Per a VP9, es pot utilitzar CRF si la taxa de bits màxima està desactivada.", "transcoding_video_codec": "CÃ˛dec de video", - "transcoding_video_codec_description": "VP9 tÊ una alta eficiència i compatibilitat amb web, perÃ˛ tarda mÊs temps a transcodificar-se. HEVC funciona de manera similar, perÃ˛ tÊ una compatibilitat amb web inferior. H.264 Ês àmpliament compatible i de transcodificaciÃŗ ràpida, perÃ˛ produeix fitxers molt mÊs grans. AV1 Ês el cÃ˛dec mÊs eficient, perÃ˛ no tÊ suport en dispositius antics.", - "trash_enabled_description": "Activa les funcionalitats de la paperera", + "transcoding_video_codec_description": "VP9 tÊ una alta eficiència i compatibilitat web, perÃ˛ triga mÊs a transcodificar-se. HEVC funciona de manera similar, perÃ˛ tÊ una compatibilitat web inferior. H.264 Ês àmpliament compatible i de transcodificaciÃŗ ràpida, perÃ˛ produeix fitxers molt mÊs grans. AV1 Ês el cÃ˛dec mÊs eficient, perÃ˛ no tÊ suport en dispositius antics.", + "trash_enabled_description": "Activa les funcions de la paperera", "trash_number_of_days": "Nombre de dies", - "trash_number_of_days_description": "Nombre de dies que es mantindran els recursos a la paperera abans de suprimir-los permanentment", + "trash_number_of_days_description": "Nombre de dies per mantenir els recursos a la paperera abans de suprimir-los permanentment", "trash_settings": "ConfiguraciÃŗ de la paperera", - "trash_settings_description": "Gestioneu els paràmetres de la paperera", - "unlink_all_oauth_accounts": "Desenllaça tots els comptes d’OAuth", - "unlink_all_oauth_accounts_description": "Recordeu desenllaçar tots els comptes d’OAuth abans de migrar a un proveïdor nou.", - "unlink_all_oauth_accounts_prompt": "Segur que voleu desvincular tots els comptes d’OAuth? Es restablirà l’identificador d’OAuth per a cada usuari. Aquesta acciÃŗ no es pot desfer.", - "user_cleanup_job": "Neteja d’usuaris", - "user_delete_delay": "El compte i els recursos de l’usuari {user} es programaran per a la supressiÃŗ permanent d’aquí a {delay, plural, one {# dia} other {# dies}}.", + "trash_settings_description": "Gestiona la configuraciÃŗ de la paperera", + "unlink_all_oauth_accounts": "Desvincula tots els comptes d'OAuth", + "unlink_all_oauth_accounts_description": "Recorda desvincular tots els comptes d'OAuth abans de migrar a un proveïdor nou.", + "unlink_all_oauth_accounts_prompt": "Estàs segur que vols desvincular tots els comptes d'OAuth? AixÃ˛ restablirà l'identificador d'OAuth per a cada usuari i no es pot tornar enrere.", + "user_cleanup_job": "Neteja d'usuari", + "user_delete_delay": "El compte i els recursos de {user} es programaran per a la supressiÃŗ permanent en {delay, plural, one {# dia} other {# dies}}.", "user_delete_delay_settings": "Retard de la supressiÃŗ", - "user_delete_delay_settings_description": "Nombre de dies desprÊs de la supressiÃŗ en què se suprimirà permanentment el compte i els recursos d’un usuari. La tasca de supressiÃŗ d’usuaris s’executa a mitjanit i comprova si hi ha usuaris preparats per a suprimir. Els canvis en aquesta configuraciÃŗ s’avaluaran en la segÃŧent execuciÃŗ.", - "user_delete_immediately": "El compte i els recursos de l’usuari {user} es posaran a la cua per a suprimir-los permanentment immediatament.", - "user_delete_immediately_checkbox": "Posa en cua l’usuari i els recursos per a suprimir-los immediatament", - "user_details": "Detalls de l’usuari", - "user_management": "GestiÃŗ d’usuaris", - "user_password_has_been_reset": "S’ha restablert la contrasenya de l’usuari:", - "user_password_reset_description": "Proporcioneu la contrasenya temporal a l’usuari i informeu-lo que haurà de canviar-la en el segÃŧent inici de sessiÃŗ.", - "user_restore_description": "Es restaurarà el compte de l’usuari {user} .", - "user_restore_scheduled_removal": "Restaura l’usuari - supressiÃŗ programada el {date, date, long}", - "user_settings": "ConfiguraciÃŗ dels usuaris", - "user_settings_description": "Gestioneu els paràmetres dels usuaris", - "user_successfully_removed": "L’usuari {email} s’ha suprimit correctament.", - "users_page_description": "Pàgina d’usuaris de l’administrador", - "version_check_channel": "Canal de publicaciÃŗ", - "version_check_channel_description": "Trieu el canal de publicaciÃŗ del qual voleu rebre avisos de noves versions", + "user_delete_delay_settings_description": "Nombre de dies desprÊs de la supressiÃŗ per eliminar permanentment el compte i els elements d'un usuari. El treball de supressiÃŗ d'usuaris s'executa a mitjanit per comprovar si hi ha usuaris preparats per eliminar. Els canvis en aquesta configuraciÃŗ s'avaluaran en la propera execuciÃŗ.", + "user_delete_immediately": "El compte i els recursos de {user} es posaran a la cua per suprimir-los permanentment immediatament.", + "user_delete_immediately_checkbox": "Posa en cua l'usuari i els recursos per suprimir-los immediatament", + "user_details": "Detalls d'usuari", + "user_management": "GestiÃŗ d'usuaris", + "user_password_has_been_reset": "La contrasenya de l'usuari ha estat restablida:", + "user_password_reset_description": "Si us plau, proporcioneu la contrasenya temporal a l'usuari i informeu-los que haurà de canviar la contrasenya en el proper inici de sessiÃŗ.", + "user_restore_description": "Es restaurarà el compte {user} .", + "user_restore_scheduled_removal": "Restaura l'usuari - eliminaciÃŗ programada el {date, date, long}", + "user_settings": "ConfiguraciÃŗ d'usuaris", + "user_settings_description": "Gestiona la configuraciÃŗ dels usuaris", + "user_successfully_removed": "L'usuari {email} s'ha eliminat correctament.", + "users_page_description": "Pàgina d'usuaris de l'administrador", "version_check_enabled_description": "Activa la comprovaciÃŗ de la versiÃŗ", - "version_check_implications": "La funciÃŗ de comprovaciÃŗ de versions funciona amb comunicacions periÃ˛diques amb {server}", - "version_check_settings": "ComprovaciÃŗ de la versiÃŗ", + "version_check_implications": "La funciÃŗ de comprovaciÃŗ de versions depèn de comunicacions periÃ˛diques amb {server}", + "version_check_settings": "ComprovaciÃŗ de versiÃŗ", "version_check_settings_description": "Activa/desactiva la notificaciÃŗ de nova versiÃŗ", - "video_conversion_job": "Transcodifica vídeos", - "video_conversion_job_description": "Transcodifica vídeos per a una major compatibilitat amb navegadors i dispositius" + "video_conversion_job": "TranscodificaciÃŗ de vídeos", + "video_conversion_job_description": "Transcodifica vídeos per a major compatibilitat amb navegadors i dispositius" }, - "admin_email": "Adreça electrÃ˛nica de l’administrador", - "admin_password": "Contrasenya de l’administrador", + "admin_email": "Correu de l'administrador", + "admin_password": "Contrasenya de l'administrador", "administration": "AdministraciÃŗ", "advanced": "Avançat", "advanced_settings_clear_image_cache": "Esborra la memÃ˛ria cau de les imatges", - "advanced_settings_clear_image_cache_error": "No s’ha pogut esborrar la memÃ˛ria cau de les imatges", - "advanced_settings_clear_image_cache_success": "S’han esborrat correctament {size}", - "advanced_settings_log_level_title": "Nivell del registre: {level}", - "advanced_settings_prefer_remote_subtitle": "Alguns dispositius sÃŗn molt lents en carregar miniatures dels elements locals. Activeu aquest paràmetre per a carregar imatges remotes en el seu lloc.", - "advanced_settings_prefer_remote_title": "Prefereix les imatges remotes", - "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de servidor intermediari que l’Immich ha d’enviar amb cada sol¡licitud de xarxa", - "advanced_settings_proxy_headers_title": "Capçaleres de servidor intermediari personalitzades [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Activa el mode de nomÊs lectura, en què les fotos nomÊs es poden veure. Accions com ara seleccionar mÃēltiples imatges, compartir, emetre o suprimir estan desactivades. Activeu o desactiveu el mode de nomÊs lectura a l’avatar d’usuari de la pantalla principal", + "advanced_settings_clear_image_cache_error": "No s'ha pogut esborrar la memÃ˛ria cau de les imatges", + "advanced_settings_clear_image_cache_success": "S'ha esborrat correctament {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Feu servir aquesta opciÃŗ per filtrar els continguts multimèdia durant la sincronitzaciÃŗ segons criteris alternatius. NomÊs proveu-ho si teniu problemes amb l'aplicaciÃŗ per detectar tots els àlbums.", + "advanced_settings_enable_alternate_media_filter_title": "Utilitza el filtre de sincronitzaciÃŗ d'àlbums de dispositius alternatius", + "advanced_settings_log_level_title": "Nivell de registre: {level}", + "advanced_settings_prefer_remote_subtitle": "Alguns dispositius sÃŗn molt lents en carregar miniatures dels elements locals. Activeu aquest paràmetre per carregar imatges remotes en el seu lloc.", + "advanced_settings_prefer_remote_title": "Prefereix imatges remotes", + "advanced_settings_proxy_headers_subtitle": "Definiu les capçaleres de proxy que Immich per enviar amb cada sol¡licitud de xarxa", + "advanced_settings_proxy_headers_title": "Capçaleres de proxy particulars [EXPERIMENTAL]", + "advanced_settings_readonly_mode_subtitle": "Habilita el nomÊs de lectura mode on les fotos poden ser nomÊs vist, a coses els agrada seleccionant imatges mÃēltiples, compartint, càsting, elimina Ês tot discapacitat. Habilita/Desactiva nomÊs de lectura via avatar d'usuari des de la pantalla major", "advanced_settings_readonly_mode_title": "Mode de nomÊs lectura", - "advanced_settings_sync_remote_deletions_subtitle": "Suprimeix o restaura automàticament un recurs en aquest dispositiu quan es faci l’acciÃŗ al web", - "advanced_settings_sync_remote_deletions_title": "Sincronitza les eliminacions remotes [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "ConfiguraciÃŗ avançada de l’usuari", - "advanced_settings_troubleshooting_subtitle": "Activa funcionalitats addicionals per a la resoluciÃŗ de problemes", + "advanced_settings_self_signed_ssl_subtitle": "Omet la verificaciÃŗ del certificat SSL del servidor. Requerit per a certificats autosignats.", + "advanced_settings_self_signed_ssl_title": "Permet certificats SSL autosignats [EXPERIMENTAL]", + "advanced_settings_sync_remote_deletions_subtitle": "Suprimeix o restaura automàticament un actiu en aquest dispositiu quan es realitzi aquesta acciÃŗ al web", + "advanced_settings_sync_remote_deletions_title": "Sincronitza les eliminacions remotes", + "advanced_settings_tile_subtitle": "ConfiguraciÃŗ avançada de l'usuari", + "advanced_settings_troubleshooting_subtitle": "Habilita funcions addicionals per a la resoluciÃŗ de problemes", "advanced_settings_troubleshooting_title": "ResoluciÃŗ de problemes", "age_months": "{months, plural, one {# mes} other {# mesos}}", - "age_year_months": "1 any i {months, plural, one {# mes} other {# mesos}}", + "age_year_months": "Un any i {months, plural, one {# mes} other {# mesos}}", "age_years": "{years, plural, one {# any} other {# anys}}", "album": "Àlbum", - "album_added": "Afegit a un àlbum", - "album_added_notification_setting_description": "Envia una notificaciÃŗ per correu electrÃ˛nic quan us afegeixen a un àlbum compartit", - "album_cover_updated": "Portada de l’àlbum actualitzada", - "album_delete_confirmation": "Segur que voleu suprimir l’àlbum {album}?", - "album_delete_confirmation_description": "Si aquest àlbum Ês compartit, els altres usuaris ja no hi podran accedir.", - "album_deleted": "S’ha suprimit l’àlbum", - "album_info_updated": "S’ha actualitzat la informaciÃŗ de l’àlbum", - "album_name": "Nom de l’àlbum", - "album_options": "Opcions de l’àlbum", - "album_remove_user": "Voleu eliminar l’usuari?", - "album_remove_user_confirmation": "Segur que voleu eliminar l’usuari {user}?", - "album_search_not_found": "No s’ha trobat cap àlbum que coincideixi amb la cerca", - "album_share_no_users": "Sembla que heu compartit aquest àlbum amb tots els usuaris o no teniu cap usuari amb qui compartir-lo.", - "album_summary": "Resum de l’àlbum", + "album_added": "Àlbum afegit", + "album_added_notification_setting_description": "Rep una notificaciÃŗ per correu quan siguis afegit a un àlbum compartit", + "album_cover_updated": "Portada de l'àlbum actualitzada", + "album_delete_confirmation": "Esteu segur que voleu suprimir l'àlbum {album}?", + "album_delete_confirmation_description": "Si aquest àlbum es comparteix, els altres usuaris ja no podran accedir-hi.", + "album_deleted": "S'ha suprimit l'àlbum", + "album_info_card_backup_album_excluded": "Exclosos", + "album_info_card_backup_album_included": "Inclosos", + "album_info_updated": "InformaciÃŗ de l'àlbum actualitzada", + "album_leave": "Sortir de l'àlbum?", + "album_leave_confirmation": "N'esteu segur que voleu sortir de {album}?", + "album_name": "Nom de l'àlbum", + "album_options": "Opcions de l'àlbum", + "album_remove_user": "Eliminar l'usuari?", + "album_remove_user_confirmation": "Esteu segurs que voleu eliminar {user}?", + "album_search_not_found": "No s'ha trobat cap àlbum que coincideixi amb la teva cerca", + "album_selected": "Àlbum seleccionat", + "album_share_no_users": "Sembla que has compartit aquest àlbum amb tots els usuaris o no tens cap usuari amb qui compartir-ho.", + "album_summary": "Resum de l'àlbum", "album_updated": "Àlbum actualitzat", - "album_updated_setting_description": "Envia una notificaciÃŗ per correu electrÃ˛nic quan un àlbum compartit tingui recursos nous", - "album_upload_assets": "Pengeu recursos del vostre ordinador i afegiu-los a l’àlbum", - "album_viewer_appbar_share_err_delete": "No s’ha pogut suprimir l’àlbum", + "album_updated_setting_description": "Rep una notificaciÃŗ per correu electrÃ˛nic quan un àlbum compartit tingui recursos nous", + "album_upload_assets": "Carrega recursos des del teu ordinador i afegeix-los a l'àlbum", + "album_user_left": "Surt de {album}", + "album_user_removed": "{user} eliminat", + "album_viewer_appbar_delete_confirm": "Confirmes que vols suprimir aquest àlbum del teu compte?", + "album_viewer_appbar_share_err_delete": "Error al esborrar l'àlbum", + "album_viewer_appbar_share_err_leave": "Error al sortir de l'àlbum", + "album_viewer_appbar_share_err_remove": "Hi ha hagut problemes al treure elements de l'àlbum", + "album_viewer_appbar_share_err_title": "Error al modificar el títol de l'àlbum", + "album_viewer_appbar_share_leave": "Surt de l'àlbum", + "album_viewer_appbar_share_to": "Comparteix amb", "album_viewer_page_share_add_users": "Afegeix usuaris", - "album_with_link_access": "Permet que qualsevol persona que tingui l’enllaç vegi les fotos i les persones d’aquest àlbum.", + "album_with_link_access": "Permet que qualsevol persona que tingui l'enllaç vegi fotos i persones d'aquest àlbum.", "albums": "Àlbums", - "albums_count": "{count, plural, one {{count, number} àlbum} other {{count, number} àlbums}}", - "albums_default_sort_order": "Ordre per defecte dels àlbums", - "albums_default_sort_order_description": "Ordre inicial dels recursos en crear àlbums nous.", - "albums_feature_description": "Col¡leccions de recursos que es poden compartir amb altres usuaris.", + "albums_count": "{count, plural, one {{count, number} Àlbum} other {{count, number} Àlbums}}", + "albums_default_sort_order": "Ordre per defecte de l'àlbum", + "albums_default_sort_order_description": "Ordre de classificaciÃŗ inicial dels recursos al crear àlbums nous.", + "albums_feature_description": "Col¡leccions d'actius que es poden compartir amb altres usuaris.", "albums_on_device_count": "Àlbums al dispositiu ({count})", + "albums_selected": "{count, plural, one {# àlbum seleccionat} other {# àlbums seleccionats}}", "all": "Tots", - "all_albums": "Tots els àlbums", - "all_people": "Totes les persones", - "all_photos": "Totes les fotos", + "all_albums": "Tots els àlbum", + "all_people": "Tota la gent", + "all_photos": "Totes les fotografies", "all_videos": "Tots els vídeos", - "allow_dark_mode": "Permet el mode fosc", - "allow_public_user_to_download": "Permet que l’usuari pÃēblic pugui baixar", - "allow_public_user_to_upload": "Permet que l’usuari pÃēblic pugui pujar", + "allow_dark_mode": "Permet el tema fosc", + "allow_edits": "Permet editar", + "allow_public_user_to_download": "Permet que l'usuari pÃēblic pugui descarregar", + "allow_public_user_to_upload": "Permet que l'usuari pÃēblic pugui carregar", "allowed": "Permès", - "alt_text_qr_code": "Imatge del codi QR", - "always_keep": "MantÊn sempre", - "always_keep_photos_hint": "L’alliberament d’espai mantindrà totes les fotos en aquest dispositiu.", - "always_keep_videos_hint": "L’alliberament d’espai mantindrà tots els vídeos en aquest dispositiu.", - "api_key": "Clau de l’API", + "alt_text_qr_code": "Codi QR", + "always_keep": "Mantenir sempre", + "always_keep_photos_hint": "Allibera espai mantÊ totes les fotos en aquest dispositiu.", + "always_keep_videos_hint": "Allibera espai mantÊ tots els vídeos en aquest dispositiu.", + "anti_clockwise": "En sentit antihorari", + "api_key": "Clau API", "api_key_description": "Aquest valor nomÊs es mostrarà una vegada. Assegureu-vos de copiar-lo abans de tancar la finestra.", - "api_key_empty": "El nom de la clau de l’API no pot estar buit", - "api_keys": "Claus de l’API", + "api_key_empty": "El nom de la clau de l'API no pot estar buit", + "api_keys": "Claus API", "app_architecture_variant": "Variant (Arquitectura)", - "app_bar_signout_dialog_content": "Segur que voleu tancar la sessiÃŗ?", + "app_bar_signout_dialog_content": "Estàs segur que vols tancar la sessiÃŗ?", + "app_bar_signout_dialog_ok": "Sí", "app_bar_signout_dialog_title": "Tanca la sessiÃŗ", - "app_download_links": "Enllaços de baixada de l’aplicaciÃŗ", - "app_settings": "ConfiguraciÃŗ de l’aplicaciÃŗ", - "app_stores": "Botigues d’aplicacions", - "app_update_available": "Hi ha disponible una actualitzaciÃŗ de l’aplicaciÃŗ", + "app_download_links": "Enllaços de descàrrega de l'App", + "app_settings": "ConfiguraciÃŗ de l'app", + "app_stores": "Botiga App", + "app_update_available": "ActualitzaciÃŗ App disponible", "appears_in": "Apareix a", - "apply_count": "Aplica ({count, number})", + "apply_count": "Aplicar ({count, number})", "archive": "Arxiu", - "archive_action_prompt": "{count, plural, one {S’ha afegit # recurs} other {S’han afegit # recursos}} a l’arxiu", - "archive_or_unarchive_photo": "Arxiva o desarxiva la foto", - "archive_size": "Mida de l’arxiu", - "archive_size_description": "Configureu la mida de l’arxiu de les baixades (en GiB)", + "archive_action_prompt": "{count} afegit a Arxiu", + "archive_or_unarchive_photo": "Arxivar o desarxivar fotografia", + "archive_page_no_archived_assets": "No s'ha trobat res arxivat", + "archive_page_title": "Arxiu({count})", + "archive_size": "Mida de l'arxiu", + "archive_size_description": "Configureu la mida de l'arxiu de les descàrregues (en GiB)", "archived": "Arxivat", - "archived_count": "{count, plural, one {# arxivat} other {# arxivats}}", + "archived_count": "{count, plural, one {Arxivat #} other {Arxivats #}}", "are_these_the_same_person": "SÃŗn la mateixa persona?", - "are_you_sure_to_do_this": "Segur que voleu fer aixÃ˛?", - "asset_added_to_album": "S’ha afegit a l’àlbum", - "asset_adding_to_album": "S’està afegint a l’àlbumâ€Ļ", + "are_you_sure_to_do_this": "Esteu segurs que voleu fer-ho?", + "array_field_not_fully_supported": "Els camps de matriu requereixen ediciÃŗ JSON manual", + "asset_action_delete_err_read_only": "No es poden esborrar el fitxer(s) de nomÊs lectura, ometent", + "asset_action_share_err_offline": "No s'ha pogut obtenir el fitxer(s) sense connexiÃŗ, ometent", + "asset_added_to_album": "Afegit a l'àlbum", + "asset_adding_to_album": "Afegint a l'àlbumâ€Ļ", "asset_created": "Recurs creat", - "asset_day_count": "{date}: {count, plural, one {# recurs} other {# recursos}}", - "asset_description_updated": "La descripciÃŗ del recurs s’ha actualitzat", - "asset_hashing": "S’està calculant el hashâ€Ļ", - "asset_list_group_by_sub_title": "Agrupa per", - "asset_list_layout_settings_group_by": "Agrupa els recursos per", + "asset_description_updated": "La descripciÃŗ del recurs s'ha actualitzat", + "asset_filename_is_offline": "L'element {filename} està fora de línia", + "asset_has_unassigned_faces": "L'element tÊ cares no assignades", + "asset_hashing": "Hasheantâ€Ļ", + "asset_list_group_by_sub_title": "Agrupar per", + "asset_list_layout_settings_dynamic_layout_title": "Disseny dinàmic", + "asset_list_layout_settings_group_automatically": "Automàtic", + "asset_list_layout_settings_group_by": "Agrupa elements per", "asset_list_layout_settings_group_by_month_day": "Mes + dia", "asset_list_layout_sub_title": "Disseny", "asset_list_settings_subtitle": "ConfiguraciÃŗ del disseny de la graella de fotos", "asset_list_settings_title": "Graella de fotos", - "asset_not_found_on_device_android": "No s’ha trobat el recurs al dispositiu", - "asset_not_found_on_device_ios": "No s’ha trobat el recurs al dispositiu. Si utilitzeu l’iCloud, Ês possible que no s’hi pugui accedir perquè el fitxer desat a l’iCloud sigui corrupte", - "asset_not_found_on_icloud": "No s’ha trobat el recurs a l’iCloud. És possible que no s’hi pugui accedir perquè el fitxer desat a l’iCloud sigui corrupte", - "asset_offline": "Recurs fora de línia", - "asset_offline_description": "Aquest recurs extern ja no Ês al disc. Poseu-vos en contacte amb l’administrador de l’Immich per a obtenir ajuda.", - "asset_skipped": "Omès", + "asset_not_found_on_device_android": "No s'ha trobat l'actiu al dispositiu", + "asset_not_found_on_device_ios": "No s'ha trobat l'element al dispositiu. Si utilitzes l'iCloud, pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud Ês corrupte", + "asset_not_found_on_icloud": "No s'ha trobat l'element a l'iCloud. Pot ser que no s'hi pugui accedir perquè el fitxer guardat a l'iCloud Ês corrupte", + "asset_offline": "Element fora de línia", + "asset_offline_description": "Aquest recurs extern ja no es troba al disc. Poseu-vos en contacte amb el vostre administrador d'Immich per obtenir ajuda.", + "asset_restored_successfully": "Element recuperat correctament", + "asset_skipped": "Saltat", "asset_skipped_in_trash": "A la paperera", - "asset_troubleshoot": "DiagnÃ˛stic del recurs", - "asset_uploaded": "Pujat", - "asset_uploading": "S’està pujantâ€Ļ", - "asset_viewer_settings_subtitle": "Gestioneu la configuraciÃŗ del visualitzador de la galeria", - "asset_viewer_settings_title": "Visualitzador de recursos", - "assets": "Recursos", - "assets_added_to_album_count": "{count, plural, one {S’ha afegit # recurs} other {S’han afegit # recursos}} a l’àlbum", - "assets_added_to_album_partial_count": "S’han afegit {successCount} {totalCount, plural, one {d’# recurs} other {de # recursos}} a l’àlbum", - "assets_added_to_albums_count": "{assetTotal, plural, one {S’ha afegit # recurs} other {S’han afegit # recursos}} a {albumTotal, plural, one {# àlbum} other {# àlbums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {El recurs no es pot} other {Els recursos no es poden}} afegir a l’àlbum", - "assets_cannot_be_added_to_albums": "{count, plural, one {El recurs} other {Els recursos}} no es poden afegir a cap dels àlbums", + "asset_trashed": "Recurs a la paperera", + "asset_troubleshoot": "DiagnÃ˛stic de l'element", + "asset_uploaded": "Carregat", + "asset_uploading": "S'està carregantâ€Ļ", + "asset_viewer_settings_subtitle": "Gestiona la configuraciÃŗ del visualitzador de la galeria", + "asset_viewer_settings_title": "Visor d'arxius", + "assets": "Elements", + "assets_added_count": "{count, plural, one {Afegit un element} other {Afegits # elements}}", + "assets_added_to_album_count": "{count, plural, one {Afegit un element} other {Afegits # elements}} a l'àlbum", + "assets_added_to_albums_count": "Afegits {assetTotal, plural, one {# recurs} other {# recursos}} a {albumTotal, plural, one {# album} other {# albums}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} no es pot afegir a l'àlbum", + "assets_cannot_be_added_to_albums": "{count, plural, one {El recurs} other {Els recursos}} no poden ser afegits a cap dels àlbums", "assets_count": "{count, plural, one {# recurs} other {# recursos}}", - "assets_moved_to_trash_count": "{count, plural, one {S’ha mogut # recurs} other {S’han mogut # recursos}} a la paperera", - "assets_permanently_deleted_count": "{count, plural, one {S’ha suprimit # recurs} other {S’ha suprimit # recursos}} permanentment", - "assets_removed_count": "{count, plural, one {S’ha eliminat # recurs} other {S’han eliminat # recursos}}", - "assets_restore_confirmation": "Segur que voleu restaurar tots els recursos de la paperera? Aquesta acciÃŗ no es pot desfer! Tingueu en compte que els recursos fora de línia no es poden restaurar d’aquesta manera.", - "assets_restored_count": "{count, plural, one {S’ha restaurat # recurs} other {S’han restaurat # recursos}}", - "assets_trashed": "{count, plural, one {S’ha enviat # element} other {S’ha enviat # elements}} a la paperera", - "assets_trashed_count": "{count, plural, one {S’ha enviat # recurs} other {S’han enviat # recursos}} a la paperera", - "assets_were_part_of_album_count": "{count, plural, one {El recurs ja era} other {Els recursos ja eren}} part de l’àlbum", - "assets_were_part_of_albums_count": "{count, plural, one {El recurs ja era} other {Els recursos ja eren}} part dels àlbums", + "assets_deleted_permanently": "{count} element(s) esborrats permanentment", + "assets_deleted_permanently_from_server": "{count} element(s) esborrats permanentment del servidor d'Immich", + "assets_downloaded_failed": "{count, plural, one {S'ha baixat un arxiu - {error} l'arxiu ha fallat} other {S'han baixat # arxius - {error} els arxius han fallat}}", + "assets_downloaded_successfully": "{count, plural, one {S'ha baixat un arxiu amb èxit} other {S'han baixat # arxius amb èxit}}", + "assets_moved_to_trash_count": "{count, plural, one {# recurs mogut} other {# recursos moguts}} a la paperera", + "assets_permanently_deleted_count": "{count, plural, one {# recurs esborrat} other {# recursos esborrats}} permanentment", + "assets_removed_count": "{count, plural, one {# element eliminat} other {# elements eliminats}}", + "assets_removed_permanently_from_device": "{count} element(s) esborrat permanentment del dispositiu", + "assets_restore_confirmation": "Esteu segurs que voleu restaurar tots els teus actius? Aquesta acciÃŗ no es pot desfer! Tingueu en compte que els recursos fora de línia no es poden restaurar d'aquesta manera.", + "assets_restored_count": "{count, plural, one {# element restaurat} other {# elements restaurats}}", + "assets_restored_successfully": "{count} element(s) recuperats correctament", + "assets_trashed": "{count} element(s) enviat a la paperera", + "assets_trashed_count": "{count, plural, one {# element enviat} other {# elements enviats}} a la paperera", + "assets_trashed_from_server": "{count} element(s) enviat a la paperera del servidor d'Immich", + "assets_were_part_of_album_count": "{count, plural, one {L'element ja Ês} other {Els elements ja sÃŗn}} part de l'àlbum", + "assets_were_part_of_albums_count": "{count, plural, one {El recurs ja formava} other {Els recursos ja formaven}} part dels àlbums", "authorized_devices": "Dispositius autoritzats", - "automatic_endpoint_switching_subtitle": "Connecta’t localment amb la Wi-Fi designada quan estigui disponible i utilitza connexions alternatives en altres llocs", - "automatic_endpoint_switching_title": "Canvi automàtic d’URL", + "automatic_endpoint_switching_subtitle": "Connecteu-vos localment a travÊs de la Wi-Fi designada quan estigui disponible i utilitzeu connexions alternatives en altres llocs", + "automatic_endpoint_switching_title": "Canvi automàtic d'URL", "autoplay_slideshow": "Reprodueix automàticament les diapositives", - "back": "Endarrere", - "back_close_deselect": "Endarrere, tanca o deselecciona", - "background_location_permission": "Permís d’ubicaciÃŗ en segon pla", - "background_location_permission_content": "Per a canviar de xarxa quan s’executa en segon pla, l’Immich *sempre* ha de tenir accÊs a la ubicaciÃŗ precisa perquè l’aplicaciÃŗ pugui llegir el nom de la xarxa Wi-Fi", - "background_options": "Opcions de segon pla", - "backup": "CÃ˛pia de seguretat", - "backup_album_selection_page_albums_tap": "Un toc per a incloure, doble toc per a excloure", - "backup_album_selection_page_assets_scatter": "Els recursos es poden dispersar en mÃēltiples àlbums. Per tant, els àlbums es poden incloure o excloure durant el procÊs de cÃ˛pia al servidor.", + "back": "Enrere", + "back_close_deselect": "Tornar, tancar o anul¡lar la selecciÃŗ", + "background_backup_running_error": "La cÃ˛pia de seguretat en segon pla s'està executant actualment, no es pot iniciar la cÃ˛pia de seguretat manual", + "background_location_permission": "Permís d'ubicaciÃŗ en segon pla", + "background_location_permission_content": "Per canviar de xarxa quan s'executa en segon pla, Immich ha de *sempre* tenir accÊs a la ubicaciÃŗ precisa perquè l'aplicaciÃŗ pugui llegir el nom de la xarxa Wi-Fi", + "background_options": "Opcions en segon pla", + "backup": "CÃ˛pia", + "backup_album_selection_page_albums_device": "Àlbums al dispositiu ({count})", + "backup_album_selection_page_albums_tap": "Un toc per incloure, doble toc per excloure", + "backup_album_selection_page_assets_scatter": "Els elements poden dispersar-se en diversos àlbums. Per tant, els àlbums es poden incloure o excloure durant el procÊs de cÃ˛pia de seguretat.", "backup_album_selection_page_select_albums": "Selecciona àlbums", "backup_album_selection_page_selection_info": "InformaciÃŗ de la selecciÃŗ", - "backup_albums_sync": "SincronitzaciÃŗ de la cÃ˛pia al servidor d’àlbums", - "backup_background_service_complete_notification": "S’ha completat la cÃ˛pia dels recursos al servidor", - "backup_background_service_default_notification": "S’estan cercant recursos nousâ€Ļ", - "backup_background_service_in_progress_notification": "S’estan copiant els vostres recursos al servidorâ€Ļ", - "backup_controller_page_albums": "CÃ˛pia al servidor dels àlbums", - "backup_controller_page_background_battery_info_link": "Mostra’m com es fa", - "backup_controller_page_background_battery_info_message": "Per a obtenir la millor experiència de cÃ˛pia al servidor en segon pla, desactiveu qualsevol optimitzaciÃŗ de la bateria que restringeixi l’activitat en segon pla de l’Immich.\n\nAtès que aixÃ˛ Ês específic de cada dispositiu, cerqueu la informaciÃŗ necessària per al fabricant del vostre dispositiu.", - "backup_controller_page_background_battery_info_ok": "D’acord", - "backup_controller_page_background_battery_info_title": "Optimitzacions de la bateria", - "backup_controller_page_background_delay": "Retard en la cÃ˛pia al servidor de recursos nous: {duration}", - "backup_controller_page_backup": "CÃ˛pia al servidor", + "backup_album_selection_page_total_assets": "Total d'elements Ãēnics", + "backup_albums_sync": "SincronitzaciÃŗ de la CÃ˛pia de Seguretat d'Àlbums", + "backup_all": "Tots", + "backup_background_service_backup_failed_message": "No s'ha pogut copiar els elements. Tornant a intentarâ€Ļ", + "backup_background_service_complete_notification": "Backup completat d'actius", + "backup_background_service_connection_failed_message": "No s'ha pogut connectar al servidor. Tornant a intentarâ€Ļ", + "backup_background_service_current_upload_notification": "Pujant {filename}", + "backup_background_service_default_notification": "Cercant nous elementsâ€Ļ", + "backup_background_service_error_title": "Error copiant", + "backup_background_service_in_progress_notification": "Copiant els teus elementsâ€Ļ", + "backup_background_service_upload_failure_notification": "Error en pujar {filename}", + "backup_controller_page_albums": "Copia els àlbums", + "backup_controller_page_background_app_refresh_disabled_content": "Activa l'actualitzaciÃŗ en segon pla de l'aplicaciÃŗ a ConfiguraciÃŗ > General > ActualitzaciÃŗ en segon pla per utilitzar la copia de seguretat en segon pla.", + "backup_controller_page_background_app_refresh_disabled_title": "ActualitzaciÃŗ en segon pla desactivada", + "backup_controller_page_background_app_refresh_enable_button_text": "VÊs a configuraciÃŗ", + "backup_controller_page_background_battery_info_link": "Mostra'm com", + "backup_controller_page_background_battery_info_message": "Per obtenir la millor experiència de cÃ˛pia de seguretat en segon pla, desactiveu qualsevol optimitzaciÃŗ de bateria que restringeixi l'activitat en segon pla per a Immich.\n\nAtès que aixÃ˛ Ês específic del dispositiu, busqueu la informaciÃŗ necessària per al fabricant del vostre dispositiu.", + "backup_controller_page_background_battery_info_ok": "D'acord", + "backup_controller_page_background_battery_info_title": "Optimitzacions de bateria", + "backup_controller_page_background_charging": "NomÊs mentre es carrega", + "backup_controller_page_background_configure_error": "No s'ha pogut configurar el servei en segon pla", + "backup_controller_page_background_delay": "Retard en la cÃ˛pia de seguretat de nous elements: {duration}", + "backup_controller_page_background_description": "Activeu el servei en segon pla per copiar automàticament tots els nous elements sense haver d'obrir l'aplicaciÃŗ", + "backup_controller_page_background_is_off": "La cÃ˛pia automàtica en segon pla està desactivada", + "backup_controller_page_background_is_on": "La cÃ˛pia automàtica en segon pla està activada", + "backup_controller_page_background_turn_off": "Desactiva el servei en segon pla", + "backup_controller_page_background_turn_on": "Activa el servei en segon pla", + "backup_controller_page_background_wifi": "NomÊs amb Wi-Fi", + "backup_controller_page_backup": "CÃ˛pia", "backup_controller_page_backup_selected": "Seleccionat: ", - "backup_controller_page_backup_sub": "Fotos i vídeos amb cÃ˛pia al servidor", - "backup_controller_page_excluded": "ExclÃ˛s: ", - "backup_controller_page_none_selected": "No s’ha seleccionat res", - "backup_controller_page_remainder": "Pendents", - "backup_controller_page_remainder_sub": "Fotos i vídeos pendents de fer-ne una cÃ˛pia al servidor dels seleccionats", + "backup_controller_page_backup_sub": "Fotografies i vídeos copiats", + "backup_controller_page_created": "Creat el: {date}", + "backup_controller_page_desc_backup": "Activeu la cÃ˛pia de seguretat per pujar automàticament els nous elements al servidor en obrir l'aplicaciÃŗ.", + "backup_controller_page_excluded": "Exclosos: ", + "backup_controller_page_failed": "Fallats ({count})", + "backup_controller_page_filename": "Nom de l'arxiu: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "InformaciÃŗ de la cÃ˛pia", + "backup_controller_page_none_selected": "Cap seleccionat", + "backup_controller_page_remainder": "Restant", + "backup_controller_page_remainder_sub": "Fotografies i vídeos restants per copiar de la selecciÃŗ", "backup_controller_page_server_storage": "Emmagatzematge del servidor", - "backup_controller_page_storage_format": "{used} de {total} en Ãēs", - "backup_controller_page_to_backup": "Àlbums dels quals es farà cÃ˛pia al servidor", - "backup_controller_page_total_sub": "Totes les fotos i vídeos Ãēnics dels àlbums seleccionats", - "backup_error_sync_failed": "No s’ha pogut completar la sincronitzaciÃŗ. No es pot processar la cÃ˛pia al servidor.", - "backup_info_card_assets": "recursos", - "backup_options": "Opcions de cÃ˛pia al servidor", - "backup_settings_subtitle": "Gestioneu la configuraciÃŗ de les pujades", - "backward": "Cap endarrere", - "battery_optimization_backup_reliability": "Si desactiveu les optimitzacions de la bateria, podeu millorar la fiabilitat de la cÃ˛pia al servidor en segon pla", + "backup_controller_page_start_backup": "Inicia la cÃ˛pia", + "backup_controller_page_status_off": "La copia de seguretat està desactivada", + "backup_controller_page_status_on": "La copia de seguretat està activada", + "backup_controller_page_storage_format": "{used} de {total} utilitzats", + "backup_controller_page_to_backup": "Àlbums a copiar", + "backup_controller_page_total_sub": "Totes les fotografies i vídeos dels àlbums seleccionats", + "backup_controller_page_turn_off": "Desactiva la cÃ˛pia de seguretat", + "backup_controller_page_turn_on": "Activa la cÃ˛pia de seguretat", + "backup_controller_page_uploading_file_info": "S'està pujant la informaciÃŗ del fitxer", + "backup_err_only_album": "No es pot eliminar l'Ãēnic àlbum", + "backup_error_sync_failed": "SincronitzaciÃŗ malament, No es pot fer backup.", + "backup_info_card_assets": "elements", + "backup_manual_cancelled": "Cancel¡lat", + "backup_manual_in_progress": "La pujada ja està en curs. Torneu-ho a provar mÊs tard", + "backup_manual_success": "Èxit", + "backup_manual_title": "Estat de pujada", + "backup_options": "Opcions de CÃ˛pia de Seguretat", + "backup_options_page_title": "Opcions de cÃ˛pia de seguretat", + "backup_setting_subtitle": "Gestiona la configuraciÃŗ de càrrega en segon pla i en primer pla", + "backup_settings_subtitle": "Administra la configuraciÃŗ de pujada", + "backup_upload_details_page_more_details": "Toqueu per obtenir mÊs detalls", + "backward": "Enrere", "biometric_auth_enabled": "AutentificaciÃŗ biomètrica activada", - "biometric_locked_out": "Esteu blocat de l’autenticaciÃŗ biomètrica", + "biometric_locked_out": "Esteu bloquejats fora de l'autenticaciÃŗ biomètrica", "biometric_no_options": "No hi ha opcions biomètriques disponibles", - "biometric_not_available": "L’autenticaciÃŗ biomètrica no està disponible en aquest dispositiu", - "birthdate_set_description": "La data de naixement s’utilitza per a calcular l’edat d’aquesta persona en el moment d’una foto.", + "biometric_not_available": "L'autenticaciÃŗ biomètrica no està disponible en aquest dispositiu", + "birthdate_saved": "Data de naixement guardada amb èxit", + "birthdate_set_description": "La data de naixement s'utilitza per calcular l'edat d'aquesta persona en el moment d'una foto.", "blurred_background": "Fons difuminat", - "browse_templates": "Explora les plantilles", "bugs_and_feature_requests": "Errors i sol¡licituds de funcions", - "build": "CompilaciÃŗ", - "build_image": "Imatge compilada", - "bulk_delete_duplicates_confirmation": "Segur que voleu suprimir en massa {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Es mantindrà el recurs mÊs gran de cada grup i se suprimiran permanentment tots els altres duplicats. Aquesta acciÃŗ no es pot desfer!", - "bulk_keep_duplicates_confirmation": "Segur que voleu mantenir {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Es resoldran tots els grups de duplicats sense suprimir res.", - "bulk_trash_duplicates_confirmation": "Segur que voleu enviar a la paperera en massa {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? Es mantindrà el recurs mÊs gran de cada grup i s’enviaran a la paperera la resta de duplicats.", - "buy": "Compra l’Immich", + "build": "Construeix", + "build_image": "Construeix la imatge", + "bulk_delete_duplicates_confirmation": "Esteu segurs que voleu suprimir de manera massiva {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? AixÃ˛ mantindrà el recurs mÊs gran de cada grup i esborrarà permanentment tots els altres duplicats. No podeu desfer aquesta acciÃŗ!", + "bulk_keep_duplicates_confirmation": "Esteu segur que voleu mantenir {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? AixÃ˛ resoldrà tots els grups duplicats sense eliminar res.", + "bulk_trash_duplicates_confirmation": "Esteu segur que voleu enviar a les escombraries {count, plural, one {# recurs duplicat} other {# recursos duplicats}}? AixÃ˛ mantindrà el recurs mÊs gran de cada grup i eliminarà la resta de duplicats.", + "buy": "Comprar Immich", + "cache_settings_clear_cache_button": "Neteja la memÃ˛ria cau", + "cache_settings_clear_cache_button_title": "Neteja la memÃ˛ria cau de l'aplicaciÃŗ. AixÃ˛ impactarà significativament el rendiment fins que la memÃ˛ria cau es torni a reconstruir.", + "cache_settings_duplicated_assets_clear_button": "NETEJA", + "cache_settings_duplicated_assets_subtitle": "Fotos i vídeos que estan a la llista ignorada de l'aplicaciÃŗ", + "cache_settings_duplicated_assets_title": "Elements duplicats ({count})", + "cache_settings_statistics_album": "Miniatures de la biblioteca", + "cache_settings_statistics_full": "Imatges completes", + "cache_settings_statistics_shared": "Miniatures d'àlbums compartits", + "cache_settings_statistics_thumbnail": "Miniatures", + "cache_settings_statistics_title": "Ús de memÃ˛ria cau", + "cache_settings_subtitle": "Controla el comportament de la memÃ˛ria cau de l'aplicaciÃŗ mÃ˛bil Immich", + "cache_settings_tile_subtitle": "Controla el comportament de l'emmagatzematge local", + "cache_settings_tile_title": "Emmagatzematge local", + "cache_settings_title": "ConfiguraciÃŗ de la memÃ˛ria cau", "camera": "Càmera", "camera_brand": "Marca de la càmera", - "camera_model": "Model de la càmera", + "camera_model": "Model de càmera", "cancel": "Cancel¡la", "cancel_search": "Cancel¡la la cerca", - "canceled": "S’ha cancel¡lat", - "canceling": "S’està cancel¡lant", - "cannot_merge_people": "No s’han pogut fusionar les persones", + "canceled": "Cancel¡lat", + "canceling": "Cancel¡lant", + "cannot_merge_people": "No es pot fusionar gent", "cannot_undo_this_action": "Aquesta acciÃŗ no es pot desfer!", - "cannot_update_the_description": "No s’ha pogut actualitzar la descripciÃŗ", + "cannot_update_the_description": "No es pot actualitzar la descripciÃŗ", "cast": "Emet", - "cast_description": "Configureu les destinacions d’emissiÃŗ disponibles", - "change": "Canvia", + "cast_description": "Configurar les destinacions de transmissiÃŗ disponibles", "change_date": "Canvia la data", "change_description": "Canvia la descripciÃŗ", - "change_display_order": "Canvia l’ordre de visualitzaciÃŗ", + "change_display_order": "Canvia l'ordre de visualitzaciÃŗ", + "change_expiration_time": "Canvia la data d'expiraciÃŗ", "change_location": "Canvia la ubicaciÃŗ", "change_name": "Canvia el nom", - "change_name_successfully": "S’ha canviat el nom correctament", + "change_name_successfully": "Nom canviat amb èxit", "change_password": "Canvia la contrasenya", - "change_password_description": "És la primera vegada que inicieu la sessiÃŗ al sistema o bÊ s’ha fet una sol¡licitud perquè canvieu la contrasenya. Introduïu la contrasenya nova a continuaciÃŗ.", - "change_password_form_confirm_password": "Confirmeu la contrasenya", - "change_password_form_description": "Bon dia, {name},\n\nÉs la primera vegada que inicieu la sessiÃŗ al sistema o bÊ s’ha fet una sol¡licitud perquè canvieu la contrasenya. Introduïu la contrasenya nova a continuaciÃŗ.", - "change_password_form_log_out": "Tanca la sessiÃŗ en tots els altres dispositius", - "change_password_form_log_out_description": "És recomanable tancar la sessiÃŗ en tots els altres dispositius", - "change_password_form_new_password": "Contrasenya nova", + "change_password_description": "Aquesta Ês la primera vegada que inicieu la sessiÃŗ al sistema o s'ha fet una sol¡licitud per canviar la contrasenya. Introduïu la nova contrasenya a continuaciÃŗ.", + "change_password_form_confirm_password": "Confirma la contrasenya", + "change_password_form_description": "Hola {name},\n\nAquesta Ês la primera vegada que inicies sessiÃŗ al sistema o bÊ s'ha sol¡licitat canviar la teva contrasenya. Si us plau, introdueix la nova contrasenya a continuaciÃŗ.", + "change_password_form_log_out": "Fer fora de tots els altres dispositius", + "change_password_form_log_out_description": "Es recomana fer fora de tots els altres dispositius", + "change_password_form_new_password": "Nova contrasenya", "change_password_form_password_mismatch": "Les contrasenyes no coincideixen", - "change_password_form_reenter_new_password": "Torneu a introduir la contrasenya nova", - "change_pin_code": "Canvieu el codi PIN", - "change_your_password": "Canvieu la contrasenya", - "changed_visibility_successfully": "S’ha canviat la visibilitat correctament", - "charging": "S’està carregant", - "charging_requirement_mobile_backup": "La cÃ˛pia al servidor en segon pla requereix que el dispositiu s’estigui carregant", - "check_logs": "Comprova els registres", - "checksum": "Suma de verificaciÃŗ", - "choose": "Tria", - "choose_matching_people_to_merge": "Trieu les persones que coincideixin per a fusionar-les", + "change_password_form_reenter_new_password": "Torna a introduir la nova contrasenya", + "change_pin_code": "Canviar el codi PIN", + "change_trigger": "Canvia el desencadenant", + "change_trigger_prompt": "Esteu segur que voleu canviar el disparador? AixÃ˛ eliminarà totes les accions i filtres existents.", + "change_your_password": "Canvia la teva contrasenya", + "changed_visibility_successfully": "Visibilitat canviada amb èxit", + "charging": "Carregant", + "charging_requirement_mobile_backup": "La cÃ˛pia de seguretat en segon pla requereix que el dispositiu estigui carregant", + "check_corrupt_asset_backup": "Comprovar les cÃ˛pies de seguretat corruptes", + "check_corrupt_asset_backup_button": "Realitzar comprovaciÃŗ", + "check_corrupt_asset_backup_description": "Executeu aquesta comprovaciÃŗ nomÊs mitjançant Wi-Fi i un cop s'hagi fet una cÃ˛pia de seguretat de tots els actius. El procediment pot trigar uns minuts.", + "check_logs": "Comprovar els registres", + "checksum": "Suma de control", + "choose_matching_people_to_merge": "Trieu les persones que coincideixin per combinar-les", "city": "Ciutat", - "cleanup_confirm_description": "L’Immich ha trobat {count} recursos (creats abans del {date}) dels quals s’ha fet cÃ˛pia al servidor correctament. Voleu eliminar-ne les cÃ˛pies locals d’aquest dispositiu?", - "cleanup_confirm_prompt_title": "Voleu eliminar-los d’aquest dispositiu?", - "cleanup_deleted_assets": "S’han mogut {count} recursos a la paperera del dispositiu", - "cleanup_deleting": "S’està movent a la paperera...", - "cleanup_found_assets": "S’han trobat {count} recursos amb cÃ˛pia al servidor", - "cleanup_found_assets_with_size": "S’han trobat {count} recursos amb cÃ˛pia al servidor ({size})", - "cleanup_icloud_shared_albums_excluded": "Els àlbums compartits de l’iCloud s’exclouen de l’escaneig", - "cleanup_no_assets_found": "No s’han trobat recursos que coincideixin amb els criteris anteriors. L’alliberament d’espai nomÊs pot esborrar elements dels quals s’hagi fet cÃ˛pia al servidor", + "cleanup_confirm_description": "Immich ha trobat {count} recursos (creats abans del {date}) carregats adequadament al servidor. Eliminar les cÃ˛pies locals d'aquest dispositiu?", + "cleanup_confirm_prompt_title": "Eliminar d'aquest dispositiu?", + "cleanup_deleted_assets": "S'han mogut {count} recursos a la paperera del dispositiu", + "cleanup_deleting": "Movent a la paperera...", + "cleanup_found_assets": "S'han trobat {count} recursos amb cÃ˛pia", + "cleanup_found_assets_with_size": "S'han trobat {count} elements copiats ({size})", + "cleanup_icloud_shared_albums_excluded": "Els àlbums compartits d'iCloud s'exclouen de la cerca", + "cleanup_no_assets_found": "No s'han trobat recursos que coincideixin amb el criteri de sobre. Allibera Espai nomÊs pot esborrar elements que s'hagin copiat al servidor", "cleanup_preview_title": "Recursos a eliminar ({count})", - "cleanup_step3_description": "Escaneja cercant recursos que ja tinguin una cÃ˛pia al servidor que encaixin amb la data de tall i la configuraciÃŗ de manteniment.", - "cleanup_step4_summary": "S’esborraran {count} recursos (creats abans del {date}) del dispositiu local. Les fotos romandran accessibles a l’aplicaciÃŗ Immich.", - "cleanup_trash_hint": "Per a recuperar completament l’espai d’emmagatzematge, obriu l’aplicaciÃŗ de la galeria del sistema i buideu la paperera", - "clear": "Neteja", + "cleanup_step3_description": "Cerca fotos i vídeos que ja tinguin una cÃ˛pia al servidor amb la data de tall i mantÊ els filtres seleccionats.", + "cleanup_step4_summary": "{count} recursos (creats abans del {date}) esborrats del dispositiu local. Les fotografies estaran disponibles a l'aplicaciÃŗ Immich.", + "cleanup_trash_hint": "Per a reclamar l'espai completament, obre la galeria del dispositiu i buida la paperera", + "clear": "Buida", "clear_all": "Neteja-ho tot", "clear_all_recent_searches": "Esborra totes les cerques recents", - "clear_failed_count": "No s’han pogut netejar ({count})", "clear_file_cache": "Buida la memÃ˛ria cau de fitxers", "clear_message": "Neteja el missatge", - "clear_value": "Esborra el valor", - "client_cert_import": "Importa", - "client_cert_import_success_msg": "S’ha importat el certificat del client", - "client_cert_invalid_msg": "El fitxer de certificat no Ês vàlid o la contrasenya Ês incorrecta", - "client_cert_password_message": "Introduïu la contrasenya per a aquest certificat", + "clear_value": "Neteja el valor", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Introdueix la contrasenya", + "client_cert_import": "Importar", + "client_cert_import_success_msg": "S'ha importat el certificat del client", + "client_cert_invalid_msg": "Fitxer de certificat no vàlid o contrasenya incorrecta", + "client_cert_password_message": "Introdueix la contrasenya per a aquest certificat", "client_cert_password_title": "Contrasenya del certificat", - "client_cert_remove_msg": "S’ha eliminat el certificat del client", - "client_cert_subtitle": "NomÊs admet el format PKCS12 (.p12, .pfx). La importaciÃŗ/eliminaciÃŗ de certificats nomÊs està disponible abans d’iniciar sessiÃŗ", - "client_cert_title": "Certificat de client SSL [EXPERIMENTAL]", + "client_cert_remove_msg": "S'ha eliminat el certificat del client", + "client_cert_subtitle": "NomÊs admet el format PKCS12 (.p12, .pfx). La importaciÃŗ/eliminaciÃŗ de certificats nomÊs està disponible abans d'iniciar sessiÃŗ", + "client_cert_title": "Certificat de client SSL", + "clockwise": "En sentit horari", "close": "Tanca", - "collapse": "Contrau", - "collapse_all": "Contrau-ho tot", + "collapse": "Tanca", + "collapse_all": "Redueix-ho tot", "color": "Color", + "color_theme": "Tema de color", "command": "Ordre", - "command_palette_prompt": "Trobeu ràpidament pàgines, accions o ordres", + "command_palette_prompt": "Trobar ràpidament pàgines, accions o comandes", "command_palette_to_close": "per a tancar", "command_palette_to_navigate": "per a introduir", "command_palette_to_select": "per a seleccionar", "command_palette_to_show_all": "per a mostrar-ho tot", - "comment_deleted": "Comentari suprimit", - "comment_options": "Opcions del comentari", + "comment_deleted": "Comentari esborrat", + "comment_options": "Opcions de comentari", "comments_and_likes": "Comentaris i agradaments", "comments_are_disabled": "Els comentaris estan desactivats", "common_create_new_album": "Crea un àlbum nou", "completed": "Completat", - "configuration": "ConfiguraciÃŗ", - "confirm": "Confirma", - "confirm_admin_password": "Confirmeu la contrasenya d’administrador", - "confirm_delete_face": "Segur que voleu suprimir la cara de {name} del recurs?", - "confirm_delete_shared_link": "Segur que voleu suprimir aquest enllaç compartit?", - "confirm_keep_this_delete_others": "Excepte aquest recurs, la resta de recursos de la pila se suprimiran. Segur que voleu continuar?", - "confirm_new_pin_code": "Confirmeu el nou codi PIN", - "confirm_password": "Confirmeu la contrasenya", - "confirm_tag_face": "Voleu etiquetar aquesta cara com a {name}?", - "confirm_tag_face_unnamed": "Voleu etiquetar aquesta cara?", + "confirm": "Confirmar", + "confirm_admin_password": "Confirmeu la contrasenya d'administrador", + "confirm_delete_face": "Estàs segur que vols eliminar la cara de {name} de les cares reconegudes?", + "confirm_delete_shared_link": "Esteu segurs que voleu eliminar aquest enllaç compartit?", + "confirm_keep_this_delete_others": "Excepte aquest element, tots els altres de la pila se suprimiran. Esteu segur que voleu continuar?", + "confirm_new_pin_code": "Confirma el nou codi PIN", + "confirm_password": "ConfirmaciÃŗ de contrasenya", + "confirm_tag_face": "Vols etiquetar aquesta cara com a {name}?", + "confirm_tag_face_unnamed": "Com vols etiquetar aquesta cara?", "connected_device": "Dispositiu connectat", "connected_to": "Connectat a", "contain": "Contingut", "context": "Context", - "continue": "Continua", - "control_bottom_app_bar_add_tags": "Afegeix etiquetes", + "continue": "Continuar", + "control_bottom_app_bar_create_new_album": "Crea un àlbum nou", + "control_bottom_app_bar_delete_from_immich": "Suprimeix del Immich", "control_bottom_app_bar_delete_from_local": "Suprimeix del dispositiu", "control_bottom_app_bar_edit_location": "Edita la ubicaciÃŗ", - "control_bottom_app_bar_edit_time": "Edita la data i l’hora", - "control_bottom_app_bar_trash_from_immich": "Mou a la paperera", - "copied_image_to_clipboard": "S’ha copiat la imatge al porta-retalls.", - "copied_to_clipboard": "S’ha copiat al porta-retalls!", - "copy_error": "S’ha produït un error en copiar", - "copy_image": "Copia la imatge", - "copy_json": "Copia el JSON", - "copy_link": "Copia l’enllaç", - "copy_link_to_clipboard": "Copia l’enllaç al porta-retalls", - "copy_password": "Copia la contrasenya", - "copy_to_clipboard": "Copia al porta-retalls", + "control_bottom_app_bar_edit_time": "Edita data i hora", + "control_bottom_app_bar_share_link": "Comparteix l'enllaç", + "control_bottom_app_bar_share_to": "Comparteix a", + "control_bottom_app_bar_trash_from_immich": "Mou a paperera", + "copied_image_to_clipboard": "Imatge copiada a porta-retalls.", + "copied_to_clipboard": "Copiada a porta-retalls!", + "copy_error": "Error de cÃ˛pia", + "copy_file_path": "Copia la ruta del fitxer", + "copy_image": "CÃ˛pia imatge", + "copy_link": "CÃ˛pia l'enllaç", + "copy_link_to_clipboard": "CÃ˛pia l'enllaç al porta-retalls", + "copy_password": "CÃ˛pia la contrasenya", + "copy_to_clipboard": "Copiar al porta-retalls", "country": "País", "cover": "Portada", "covers": "Portades", "create": "Crea", - "create_album": "Crea un àlbum", + "create_album": "Crear un àlbum", "create_album_page_untitled": "Sense títol", - "create_api_key": "Crea una clau de l’API", - "create_first_workflow": "Creeu el primer flux de treball", - "create_library": "Crea una biblioteca", - "create_link": "Crea un enllaç", - "create_link_to_share": "Crea un enllaç per a compartir", - "create_link_to_share_description": "Permet que qualsevol persona amb l’enllaç vegi les fotos seleccionades", - "create_new": "CREA’N UN DE NOU", - "create_new_person": "Crea una persona nova", - "create_new_person_hint": "Assigna els recursos seleccionats a una persona nova", + "create_api_key": "Crear clau API", + "create_first_workflow": "Crea el primer flux de treball", + "create_library": "Crea una llibreria", + "create_link": "Crear enllaç", + "create_link_to_share": "Crear enllaç per compartir", + "create_link_to_share_description": "Deixa que qualsevol persona amb l'enllaç vegi les fotos seleccionades", + "create_new": "CREAR NOU", + "create_new_face": "Crea una nova cara", + "create_new_person": "Crea una nova persona", + "create_new_person_hint": "Assigna els elements seleccionats a una persona nova", "create_new_user": "Crea un usuari nou", "create_person": "Crea una persona", - "create_person_subtitle": "Afegeix un nom a la cara seleccionada per a crear i etiquetar la persona nova", - "create_shared_album_page_share_add_assets": "AFEGEIX RECURSOS", - "create_shared_album_page_share_select_photos": "Selecciona fotos", + "create_person_subtitle": "Afegeix un nom a la cara seleccionada per crear i etiquetar la nova persona", + "create_shared_album_page_share_add_assets": "AFEGEIX ELEMENTS", + "create_shared_album_page_share_select_photos": "Escull fotografies", "create_shared_link": "Crea un enllaç compartit", - "create_tag": "Crear una etiqueta", - "create_tag_description": "Crear una etiqueta nova. Per a etiquetes aniuades, introduïu el camí complet de l’etiqueta, incloent-hi les barres.", + "create_tag": "Crear etiqueta", + "create_tag_description": "Crear una nova etiqueta. Per les etiquetes aniuades, escriu la ruta comperta de l'etiqueta, incloses les barres diagonals.", "create_user": "Crea un usuari", "create_workflow": "Crea un flux de treball", "created": "Creat", "created_at": "Creat", - "creating_linked_albums": "S’estan creant els àlbums enllaçats...", + "creating_linked_albums": "Creant àlbums enllaçats...", "crop": "Retalla", + "crop_aspect_ratio_fixed": "Fixat", "crop_aspect_ratio_free": "Lliure", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Quadrat", + "curated_object_page_title": "Coses", "current_device": "Dispositiu actual", "current_pin_code": "Codi PIN actual", "current_server_address": "Adreça actual del servidor", "custom_date": "Data personalitzada", "custom_locale": "LocalitzaciÃŗ personalitzada", - "custom_locale_description": "Formata les dates, les hores i els nÃēmeros en base a la llengua i la regiÃŗ seleccionades", - "cutoff_date_description": "MantÊn les fotos dels darrersâ€Ļ", + "custom_locale_description": "Format de dates i nÃēmeros segons la llengua i regiÃŗ seleccionades", + "custom_url": "URL personalitzada", + "cutoff_date_description": "MantÊ fotos des de l'Ãēltimâ€Ļ", "cutoff_day": "{count, plural, one {dia} other {dies}}", "cutoff_year": "{count, plural, one {any} other {anys}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Fosc", - "dark_theme": "Canvia al tema fosc", + "dark_theme": "Canvia a tema fosc", "date": "Data", "date_after": "Data posterior a", "date_and_time": "Data i hora", "date_before": "Data anterior a", - "date_of_birth": "Data de naixement", - "date_of_birth_saved": "S’ha desat la data de naixement correctament", + "date_format": "E, d LLL, y â€ĸ hh:mm", + "date_of_birth_saved": "Data de naixement guardada amb èxit", "date_range": "Interval de dates", - "date_time_original": "Data/hora original", "day": "Dia", "days": "Dies", "deduplicate_all": "Desduplica-ho tot", - "default_quality_subtitle": "Qualitat utilitzada quan es prem l’opciÃŗ de comparticiÃŗ. Mantingueu premut el botÃŗ de compartir per a triar-ho cada vegada.", - "default_share_quality": "Qualitat de comparticiÃŗ per defecte", - "delete": "Suprimeix", - "delete_action_confirmation_message": "Segur que voleu suprimir aquest recurs? Aquesta acciÃŗ el mourà a la paperera del servidor i us preguntarà si voleu eliminar-lo localment", - "delete_action_prompt": "{count} suprimits", - "delete_album": "Suprimeix l’àlbum", - "delete_api_key_prompt": "Segur que voleu suprimir aquesta clau de l’API?", - "delete_dialog_alert": "Aquests elements se suprimiran permanentment de l’Immich i del vostre dispositiu", - "delete_dialog_alert_local": "Aquests elements se suprimiran permanentment del vostre dispositiu, perÃ˛ romandran disponibles al servidor de l’Immich", - "delete_dialog_alert_local_non_backed_up": "Alguns elements no tenen cÃ˛pia al servidor de l’Immich i se suprimiran permanentment del dispositiu", - "delete_dialog_title": "Suprimeix permanentment", - "delete_duplicates_confirmation": "Segur que voleu suprimir permanentment aquests duplicats?", - "delete_face": "Suprimeix la cara", + "default_locale": "ConfiguraciÃŗ regional predeterminada", + "default_locale_description": "Format de dades i nÃēmeros en funciÃŗ de la configuraciÃŗ local", + "delete": "Esborrar", + "delete_action_confirmation_message": "Segur que vols eliminar aquest recurs? Aquesta acciÃŗ el mourà a la paperera del servidor, i et preguntarà si el vols eliminar localment", + "delete_action_prompt": "{count} eliminats", + "delete_album": "Esborra l'àlbum", + "delete_api_key_prompt": "Esteu segurs que voleu eliminar aquesta clau API?", + "delete_dialog_alert": "Aquests elements seran eliminats de manera permanent d'Immich i del vostre dispositiu", + "delete_dialog_alert_local": "Aquests elements s'eliminaran permanentment del vostre dispositiu, perÃ˛ encara estaran disponibles al servidor Immich", + "delete_dialog_alert_local_non_backed_up": "Alguns dels elements no tenen cÃ˛pia de seguretat a Immich i s'eliminaran permanentment del dispositiu", + "delete_dialog_alert_remote": "Aquests elements s'eliminaran permanentment del servidor Immich", + "delete_dialog_ok_force": "Suprimeix de totes maneres", + "delete_dialog_title": "Esborra permanentment", + "delete_duplicates_confirmation": "Esteu segurs que voleu eliminar aquests duplicats permanentment?", + "delete_face": "Esborrar cara", "delete_key": "Suprimeix la clau", - "delete_library": "Suprimeix la biblioteca", - "delete_link": "Suprimeix l’enllaç", - "delete_local_action_prompt": "{count} suprimits localment", - "delete_local_dialog_ok_backed_up_only": "Suprimeix nomÊs els que tinguin cÃ˛pia al servidor", - "delete_local_dialog_ok_force": "Suprimeix igualment", - "delete_others": "Suprimeix els altres", - "delete_permanently": "Suprimeix permanentment", - "delete_permanently_action_prompt": "{count} suprimits permanentment", - "delete_shared_link": "Suprimeix l’enllaç compartit", - "delete_shared_link_dialog_title": "Suprimeix l’enllaç compartit", - "delete_tag": "Suprimeix l’etiqueta", - "delete_tag_confirmation_prompt": "Segur que voleu suprimir l’etiqueta {tagName}?", - "delete_user": "Suprimeix l’usuari", - "deleted_shared_link": "Suprimeix l’enllaç compartit", + "delete_library": "Suprimeix la Llibreria", + "delete_link": "Esborra l'enllaç", + "delete_local_action_prompt": "{count} eliminats localment", + "delete_local_dialog_ok_backed_up_only": "Esborrar nomÊs les que tinguin cÃ˛pia de seguretat", + "delete_local_dialog_ok_force": "Suprimeix de totes maneres", + "delete_others": "Suprimeix altres", + "delete_permanently": "Eliminar permanentment", + "delete_permanently_action_prompt": "{count} eliminats permanentment", + "delete_shared_link": "Odstranit sdílenÃŊ odkaz", + "delete_shared_link_dialog_title": "Suprimeix l'enllaç compartit", + "delete_tag": "Eliminar etiqueta", + "delete_tag_confirmation_prompt": "Estàs segur que vols eliminar l'etiqueta {tagName}?", + "delete_user": "Suprimeix l'usuari", + "deleted_shared_link": "Suprimeix l'enllaç compartit", + "deletes_missing_assets": "Elimina els actius que falten del disc", "description": "DescripciÃŗ", - "deselect_all": "Deselecciona-ho tot", + "description_input_hint_text": "Afegeix descripciÃŗ...", + "description_input_submit_error": "S'ha produït un error en actualitzar la descripciÃŗ, comproveu el registre per a mÊs detalls", + "deselect_all": "Deseleccionar Tots", "details": "Detalls", "direction": "DirecciÃŗ", "disable": "Desactiva", "disabled": "Desactivat", + "disallow_edits": "No permetre les edicions", "discord": "Discord", "discover": "Descobreix", "discovered_devices": "Dispositius descoberts", "dismiss_all_errors": "Descarta tots els errors", + "dismiss_error": "Descarta l'error", "display_options": "Opcions de visualitzaciÃŗ", "display_order": "Ordre de visualitzaciÃŗ", - "display_original_photos": "Mostra les fotos originals", - "display_original_photos_setting_description": "Prefereix mostrar la foto original en visualitzar un recurs en lloc de miniatures quan el recurs original Ês compatible amb el web. AixÃ˛ pot provocar una velocitat de visualitzaciÃŗ de fotos mÊs lenta.", + "display_original_photos": "Mostra les fotografies originals", + "display_original_photos_setting_description": "Preferiu mostrar la foto original quan visualitzeu un recurs en lloc de miniatures quan el recurs original Ês compatible amb el web. AixÃ˛ pot provocar una velocitat de visualitzaciÃŗ de fotos mÊs lenta.", "do_not_show_again": "No tornis a mostrar aquest missatge", "documentation": "DocumentaciÃŗ", "done": "Fet", - "download": "Baixa", - "download_canceled": "S’ha cancel¡lat la baixada", - "download_complete": "S’ha completat la baixada", - "download_enqueue": "S’ha afegit la baixada a la cua", - "download_failed": "S’ha produït un error en la baixada", - "download_finished": "S’ha finalitzat la baixada", + "download": "Descarregar", + "download_action_prompt": "Baixant {count} recursos", + "download_canceled": "Descàrrega cancel¡lada", + "download_complete": "Descàrrega completada", + "download_enqueue": "Descàrrega en cua", + "download_error": "Error de descàrrega", + "download_failed": "Descàrrega ha fallat", + "download_finished": "Descàrrega acabada", "download_include_embedded_motion_videos": "Vídeos incrustats", - "download_include_embedded_motion_videos_description": "Inclou els vídeos incrustats en fotos en moviment com a fitxers separats", - "download_notfound": "No s’ha trobat la baixada", - "download_original": "Baixa l’original", - "download_paused": "S’ha pausat la baixada", - "download_settings": "Baixades", - "download_settings_description": "Gestioneu la configuraciÃŗ relacionada amb les baixades de recursos", - "download_waiting_to_retry": "S’està esperant per a tornar-ho a provar", - "downloading": "S’està baixant", - "downloading_asset_filename": "S’està baixant el recurs {filename}", - "downloading_from_icloud": "S’està baixant de l’iCloud", - "downloading_media": "S’està baixant el mitjà", - "drag_to_reorder": "Arrossegueu per a reordenar", - "drop_files_to_upload": "Deixeu anar fitxers a qualsevol lloc per a pujar-los", - "duplicate": "Duplica", - "duplicate_workflow": "Duplica el flux de treball", + "download_include_embedded_motion_videos_description": "Incloure vídeos incrustats en fotografies en moviment com un arxiu separat", + "download_notfound": "No s'ha trobat la descàrrega", + "download_original": "Descarregar original", + "download_paused": "Descàrrega pausada", + "download_settings": "Descarregar", + "download_settings_description": "Gestioneu la configuraciÃŗ relacionada amb la descàrrega de recursos", + "download_started": "Descàrrega ha començat", + "download_sucess": "Descarregat amb èxit", + "download_sucess_android": "El multimedia s'ha descarregat a DCIM/Immich", + "download_waiting_to_retry": "Esperant per tornar-ho a intentar", + "downloading": "Baixant", + "downloading_asset_filename": "Descarregant l'element {filename}", + "downloading_from_icloud": "Descarregant des d'iCloud", + "downloading_media": "Descàrrega multimèdia", + "drop_files_to_upload": "Deixeu els fitxers a qualsevol lloc per pujar-los", "duplicates": "Duplicats", - "duplicates_description": "Resoleu cada grup indicant quins sÃŗn els duplicats, si n’hi ha.", + "duplicates_description": "Resol cada grup indicant, si n'hi ha, quins sÃŗn duplicats.", "duration": "Durada", - "edit": "Edita", - "edit_album": "Edita l’àlbum", - "edit_avatar": "Edita l’avatar", - "edit_birthday": "Edita la data de naixement", + "edit": "Editar", + "edit_album": "Edita l'àlbum", + "edit_avatar": "Edita l'avatar", + "edit_birthday": "Edita l'aniversari", "edit_date": "Edita la data", - "edit_date_and_time": "Edita la data i l’hora", + "edit_date_and_time": "Edita la data i l'hora", "edit_date_and_time_action_prompt": "{count} dates i hores editades", - "edit_date_and_time_by_offset": "Canvia la data de manera relativa", + "edit_date_and_time_by_offset": "Canvia la data mitjançant diferència", + "edit_date_and_time_by_offset_interval": "Nou rang de dates: {from}-{to}", "edit_description": "Edita la descripciÃŗ", - "edit_exclusion_pattern": "Edita el patrÃŗ d’exclusiÃŗ", + "edit_description_prompt": "Si us plau, selecciona una nova descripciÃŗ:", + "edit_exclusion_pattern": "Edita patrÃŗ d'exclusiÃŗ", "edit_faces": "Edita les cares", - "edit_key": "Edita la clau", - "edit_link": "Edita l’enllaç", - "edit_location": "Edita la ubicaciÃŗ", + "edit_key": "Edita clau", + "edit_link": "Edita enllaç", + "edit_location": "Edita ubicaciÃŗ", "edit_location_action_prompt": "{count} ubicacions editades", "edit_location_dialog_title": "UbicaciÃŗ", "edit_name": "Edita el nom", - "edit_people": "Edita les persones", - "edit_tag": "Edita l’etiqueta", - "edit_title": "Edita el títol", - "edit_user": "Edita l’usuari", + "edit_people": "Edita la gent", + "edit_tag": "Edita etiqueta", + "edit_title": "Edita títol", + "edit_user": "Edita l'usuari", "edit_workflow": "Edita el flux de treball", "editor": "Editor", + "editor_close_without_save_prompt": "No es desaran els canvis", + "editor_close_without_save_title": "Tancar l'editor?", + "editor_confirm_reset_all_changes": "Segur que vols reiniciar tots els canvis?", "editor_discard_edits_confirm": "Descarta les modificacions", - "editor_discard_edits_prompt": "Teniu modificacions sense desar. Segur que les voleu descartar?", - "editor_discard_edits_title": "Voleu descartar les modificacions?", - "editor_edits_applied_error": "No s’han pogut aplicar les modificacions", - "editor_edits_applied_success": "Les modificacions s’han aplicat correctament", + "editor_discard_edits_prompt": "Tens modificacions sense desar. Estàs segur que les vols descartar?", + "editor_discard_edits_title": "Vols descartar les modificacions?", + "editor_edits_applied_error": "No s'han pogut aplicar les modificacions", + "editor_edits_applied_success": "Les modificacions s'han aplicat correctament", "editor_flip_horizontal": "Capgira horitzontalment", "editor_flip_vertical": "Capgira verticalment", - "editor_handle_corner": "CantÃŗ per a agafar{corner, select, top_left { superior esquerre} top_right { superior dret} bottom_left { inferior esquerre} bottom_right { inferior dret} other {}}", - "editor_handle_edge": "Vora{edge, select, top { superior} bottom { inferior} left { esquerra} right { dreta} other {}} per a agafar", + "editor_handle_corner": "{corner, select, top_left {Top-left} top_right {Top-right} bottom_left {Bottom-left} bottom_right {Bottom-right} other {A}} cantÃŗ per agafar", + "editor_handle_edge": "{edge, select, top {Top} bottom {Bottom} left {Left} right {Right} other {An}} cantÃŗ per agafar", "editor_orientation": "OrientaciÃŗ", - "editor_reset_all_changes": "Restableix els canvis", - "editor_rotate_left": "Gira 90° en sentit antihorari", - "editor_rotate_right": "Gira 90° en sentit horari", - "email": "Adreça electrÃ˛nica", - "email_notifications": "Notificacions per correu electrÃ˛nic", + "editor_reset_all_changes": "Reiniciar canvis", + "editor_rotate_left": "Rota 90Âē al contrari de les agulles", + "editor_rotate_right": "Rota 90Âē en el sentit de les agulles", + "email": "Correu electrÃ˛nic", + "email_notifications": "Correu electrÃ˛nic de notificacions", "empty_folder": "Aquesta carpeta Ês buida", - "empty_trash": "Buida la paperera", - "empty_trash_confirmation": "Segur que voleu buidar la paperera? Se suprimiran permanentment de l’Immich tots els recursos de la paperera.\nAquesta acciÃŗ no es pot desfer!", - "enable": "Activa", - "enable_backup": "Activa la cÃ˛pia al servidor", - "enable_biometric_auth_description": "Introduïu el codi PIN per a activar l’autenticaciÃŗ biomètrica", + "empty_trash": "Buidar la paperera", + "empty_trash_confirmation": "Esteu segur que voleu buidar la paperera? AixÃ˛ eliminarà tots els recursos a la paperera permanentment d'Immich.\nNo podeu desfer aquesta acciÃŗ!", + "enable": "Activar", + "enable_backup": "CÃ˛pia de Seguretat", + "enable_biometric_auth_description": "Introduïu el codi PIN per a habilitar l'autenticaciÃŗ biomètrica", "enabled": "Activat", - "end_date": "Data de final", - "enqueued": "A la cua", - "enter_wifi_name": "Introdueix el nom de la Wi-Fi", + "end_date": "Data final", + "enqueued": "En cua", + "enter_wifi_name": "Introdueix el nom de Wi-Fi", "enter_your_pin_code": "Introduïu el codi PIN", "enter_your_pin_code_subtitle": "Introduïu el codi PIN per a accedir a la carpeta protegida", "error": "Error", - "error_delete_face": "S’ha produït un error en suprimir la cara del recurs", - "error_loading_albums": "S’ha produït un error en carregar els àlbums", - "error_loading_image": "S’ha produït un error en carregar la imatge", - "error_loading_partners": "S’ha produït un error en carregar els companys: {error}", - "error_retrieving_asset_information": "S’ha produït un error en obtenir la informaciÃŗ del recurs", - "error_saving_image": "S’ha produït un error: {error}", - "error_tag_face_bounding_box": "S’ha produït un error en etiquetar la cara: no s’han pogut obtenir les coordenades de l’àrea", - "error_title": "Error - Alguna cosa ha anat malament", - "error_while_navigating": "S’ha produït un error en navegar fins al recurs", + "error_change_sort_album": "No s'ha pogut canviar l'ordre d'ordenaciÃŗ dels àlbums", + "error_delete_face": "Error esborrant cara de les cares reconegudes", + "error_getting_places": "S'ha produït un error en obtenir els llocs", + "error_loading_albums": "Error en carregar àlbums", + "error_loading_image": "Error carregant la imatge", + "error_loading_partners": "No s'han pogut carregar les parelles: {error}", + "error_retrieving_asset_information": "Error en recuperar la informaciÃŗ de l'actiu", + "error_saving_image": "Error: {error}", + "error_tag_face_bounding_box": "Error a l'etiquetar la cara - no s'han pogut obtenir les coordenades de l'àrea", + "error_title": "Error - Quelcom ha anat malament", + "error_while_navigating": "Error en navegar fins a l'actiu", "errors": { - "cannot_navigate_next_asset": "No es pot navegar al recurs segÃŧent", - "cannot_navigate_previous_asset": "No es pot navegar al recurs anterior", + "cannot_navigate_next_asset": "No es pot navegar a l'element segÃŧent", + "cannot_navigate_previous_asset": "No es pot navegar a l'element anterior", "cant_apply_changes": "No es poden aplicar els canvis", "cant_change_activity": "No es pot {enabled, select, true {desactivar} other {activar}} aquesta activitat", - "cant_change_asset_favorite": "No es pot canviar l’estat de preferit d’aquest recurs", - "cant_change_metadata_assets_count": "No es poden canviar les metadades {count, plural, one {del recurs} other {dels # recursos}}", + "cant_change_asset_favorite": "No es pot canviar el favorit per a aquest recurs", + "cant_change_metadata_assets_count": "No es poden canviar les metadades {count, plural, one {de l'element} other {dels # elements}}", "cant_get_faces": "No es poden obtenir les cares", "cant_get_number_of_comments": "No es pot obtenir el nombre de comentaris", "cant_search_people": "No es poden cercar persones", "cant_search_places": "No es poden cercar llocs", - "error_adding_assets_to_album": "S’ha produït un error en afegir els recursos a l’àlbum", - "error_adding_users_to_album": "S’ha produït un error en afegir els usuaris a l’àlbum", - "error_deleting_shared_user": "S’ha produït un error en suprimir l’usuari compartit", - "error_downloading": "S’ha produït un error en baixar {filename}", - "error_hiding_buy_button": "S’ha produït un error en amagar el botÃŗ de compra", - "error_removing_assets_from_album": "S’ha produït un error en eliminar els recursos l’àlbum; consulteu la consola per a mÊs detalls", - "error_selecting_all_assets": "S’ha produït un error en seleccionar tots els recursos", + "error_adding_assets_to_album": "Error afegint elements a l'àlbum", + "error_adding_users_to_album": "Error afegint usuaris a l'àlbum", + "error_deleting_shared_user": "S'ha produït un error en suprimir l'usuari compartit", + "error_downloading": "Error descarregant {filename}", + "error_hiding_buy_button": "S'ha produït un error en amagar el botÃŗ de compra", + "error_removing_assets_from_album": "Error eliminant els elements de l'àlbum, consulteu la consola per obtenir mÊs detalls", + "error_selecting_all_assets": "Error seleccionant tots els elements", "exclusion_pattern_already_exists": "Aquest patrÃŗ d’exclusiÃŗ ja existeix.", - "failed_to_create_album": "No s’ha pogut crear l’àlbum", - "failed_to_create_shared_link": "No s’ha pogut crear l’enllaç compartit", - "failed_to_edit_shared_link": "No s’ha pogut editar l’enllaç compartit", - "failed_to_get_people": "No s’han pogut obtenir les persones", - "failed_to_keep_this_delete_others": "No s’ha pogut conservar aquest recurs i suprimir la resta", - "failed_to_load_asset": "No s’ha pogut carregar el recurs", - "failed_to_load_assets": "No s’han pogut carregar els recursos", - "failed_to_load_notifications": "S’ha produït un error en carregar les notificacions", - "failed_to_load_people": "No s’han pogut carregar les persones", - "failed_to_remove_product_key": "No s’ha pogut suprimir la clau del producte", - "failed_to_reset_pin_code": "No s’ha pogut restablir el codi PIN", - "failed_to_stack_assets": "No s’han pogut apilar els recursos", - "failed_to_tag_assets": "No s’han pogut etiquetar els recursos", - "failed_to_unstack_assets": "No s’han pogut desapilar els recursos", - "failed_to_update_notification_status": "No s’ha pogut actualitzar l’estat de les notificacions", - "incorrect_email_or_password": "Adreça electrÃ˛nica o contrasenya incorrectes", - "library_folder_already_exists": "Aquest camí d’importaciÃŗ ja existeix.", - "page_not_found": "No s’ha trobat la pàgina", - "paths_validation_failed": "No {paths, plural, one {s’ha pogut validar # camí} other {s’han pogut validar # camins}}", - "profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Amplieu i/o moveu la imatge.", - "quota_higher_than_disk_size": "Heu definit quota mÊs gran que la mida del disc", + "failed_to_create_album": "No s'ha pogut crear l'àlbum", + "failed_to_create_shared_link": "No s'ha pogut crear l'enllaç compartit", + "failed_to_edit_shared_link": "No s'ha pogut editar l'enllaç compartit", + "failed_to_get_people": "No s'han pogut aconseguir persones", + "failed_to_keep_this_delete_others": "No s'ha pogut conservar aquest element i suprimir els altres", + "failed_to_load_asset": "No s'ha pogut carregar l'element", + "failed_to_load_assets": "No s'han pogut carregar els elements", + "failed_to_load_notifications": "Error en carregar les notificacions", + "failed_to_load_people": "No s'han pogut carregar les persones", + "failed_to_remove_product_key": "No s'ha pogut eliminar la clau del producte", + "failed_to_reset_pin_code": "No s'ha pogut reiniciar el codi PIN", + "failed_to_stack_assets": "No s'han pogut apilar els elements", + "failed_to_unstack_assets": "No s'han pogut desapilar els elements", + "failed_to_update_notification_status": "Error en actualitzar l'estat de les notificacions", + "incorrect_email_or_password": "Correu electrÃ˛nic o contrasenya incorrectes", + "library_folder_already_exists": "Aquesta ruta d'importaciÃŗ ja existeix.", + "page_not_found": "Pàgina no trobada", + "paths_validation_failed": "{paths, plural, one {# ruta} other {# rutes}} no ha pogut validar", + "profile_picture_transparent_pixels": "Les fotos de perfil no poden tenir píxels transparents. Per favor, feu zoom in, mogueu la imatge o ambdues.", + "quota_higher_than_disk_size": "Heu establert una quota mÊs gran que la mida de disc", "something_went_wrong": "Alguna cosa ha anat malament", - "unable_to_add_album_users": "No s’han pogut afegir els usuaris a l’àlbum", - "unable_to_add_assets_to_shared_link": "No s’han pogut afegir els recursos a l’enllaç compartit", - "unable_to_add_comment": "No s’ha pogut afegir el comentari", - "unable_to_add_exclusion_pattern": "No s’ha pogut afegir el patrÃŗ d’exclusiÃŗ", - "unable_to_add_partners": "No s’han pogut afegir companys", - "unable_to_add_remove_archive": "No s’ha pogut {archived, select, true {eliminar l’element de} other {afegir l’element a}} l’arxiu", - "unable_to_add_remove_favorites": "No s’ha pogut {favorite, select, true {afegir l’element als} other {eliminar l’element dels}} preferits", - "unable_to_archive_unarchive": "No s’ha pogut {archived, select, true {arxivar} other {desarxivar}}", - "unable_to_change_album_user_role": "No s’ha pogut canviar el rol de l’usuari de l’àlbum", - "unable_to_change_date": "No s’ha pogut canviar la data", - "unable_to_change_description": "No s’ha pogut canviar la descripciÃŗ", - "unable_to_change_favorite": "No s’ha pogut canviar l’estat de favorit d’aquest recurs", - "unable_to_change_location": "No s’ha pogut canviar la ubicaciÃŗ", - "unable_to_change_password": "No s’ha pogut canviar la contrasenya", - "unable_to_change_visibility": "No s’ha pogut canviar la visibilitat {count, plural, one {d’# persona} other {de # persones}}", - "unable_to_complete_oauth_login": "No s’ha pogut completar l’inici de sessiÃŗ amb OAuth", - "unable_to_connect": "No s’ha pogut connectar", - "unable_to_copy_to_clipboard": "No s’ha pogut copiar al porta-retalls; assegureu-vos que accediu a la pàgina mitjançant https", - "unable_to_create": "No s’ha pogut crear el flux de treball", - "unable_to_create_admin_account": "No s’ha pogut crear el compte d’administrador", - "unable_to_create_api_key": "No s’ha pogut crear una clau de l’API nova", - "unable_to_create_library": "No s’ha pogut crear la biblioteca", - "unable_to_create_user": "No s’ha pogut crear l’usuari", - "unable_to_delete_album": "No s’ha pogut suprimir l’àlbum", - "unable_to_delete_asset": "No s’ha pogut suprimir el recurs", - "unable_to_delete_assets": "S’ha produït un error en suprimir els recursos", - "unable_to_delete_exclusion_pattern": "No s’ha pogut suprimir el patrÃŗ d’exclusiÃŗ", - "unable_to_delete_shared_link": "No s’ha pogut suprimir l’enllaç compartit", - "unable_to_delete_user": "No s’ha pogut suprimir l’usuari", - "unable_to_delete_workflow": "No s’ha pogut suprimir el flux de treball", - "unable_to_download_files": "No s’han pogut baixar els fitxers", - "unable_to_edit_exclusion_pattern": "No s’ha pogut editar el patrÃŗ d’exclusiÃŗ", - "unable_to_empty_trash": "No s’ha pogut buidar la paperera", - "unable_to_enter_fullscreen": "No s’ha pogut posar en pantalla completa", - "unable_to_exit_fullscreen": "No s’ha pogut sortir de la pantalla completa", - "unable_to_get_comments_number": "No s’ha pogut obtenir el nombre de comentaris", - "unable_to_get_shared_link": "No s’ha pogut obtenir l’enllaç compartit", - "unable_to_hide_person": "No s’ha pogut amagar la persona", - "unable_to_link_motion_video": "No s’ha pogut enllaçar el vídeo en moviment", - "unable_to_link_oauth_account": "No s’ha pogut enllaçar el compte d’OAuth", - "unable_to_log_out_all_devices": "No s’ha pogut tancar la sessiÃŗ en tots els dispositius", - "unable_to_log_out_device": "No s’ha pogut tancar la sessiÃŗ del dispositiu", - "unable_to_login_with_oauth": "No s’ha pogut iniciar sessiÃŗ amb OAuth", - "unable_to_play_video": "No s’ha pogut reproduir el vídeo", - "unable_to_reassign_assets_existing_person": "No s’han pogut reassignar els recursos a {name, select, null {una persona existent} other {{name}}}", - "unable_to_reassign_assets_new_person": "No s’han pogut reassignar els recursos a una persona nova", - "unable_to_refresh_user": "No s’ha pogut actualitzar l’usuari", - "unable_to_remove_album_users": "No s’han pogut eliminar els usuaris de l’àlbum", - "unable_to_remove_api_key": "No s’ha pogut suprimir la clau de l’API", - "unable_to_remove_assets_from_shared_link": "No s’han pogut eliminar els recursos de l’enllaç compartit", - "unable_to_remove_library": "No s’ha pogut suprimir la biblioteca", - "unable_to_remove_partner": "No s’ha pogut suprimir el company", - "unable_to_remove_reaction": "No s’ha pogut suprimir la reacciÃŗ", - "unable_to_reset_password": "No s’ha pogut restablir la contrasenya", - "unable_to_reset_pin_code": "No s’ha pogut restablir el codi PIN", - "unable_to_resolve_duplicate": "No s’ha pogut resoldre el duplicat", - "unable_to_restore_assets": "No s’han pogut restaurar els recursos", - "unable_to_restore_trash": "No s’ha pogut restaurar la paperera", - "unable_to_restore_user": "No s’ha pogut restaurar l’usuari", - "unable_to_save_album": "No s’ha pogut desar l’àlbum", - "unable_to_save_api_key": "No s’ha pogut desar la clau de l’API", - "unable_to_save_date_of_birth": "No s’ha pogut desar la data de naixement", - "unable_to_save_name": "No s’ha pogut desar el nom", - "unable_to_save_profile": "No s’ha pogut desar el perfil", - "unable_to_save_settings": "No s’ha pogut desar la configuraciÃŗ", - "unable_to_scan_libraries": "No s’han pogut escanejar les biblioteques", - "unable_to_scan_library": "No s’ha pogut escanejar la biblioteca", - "unable_to_set_feature_photo": "No s’ha pogut definir la foto destacada", - "unable_to_set_profile_picture": "No s’ha pogut definir la foto de perfil", - "unable_to_set_rating": "No s’ha pogut establir la puntuaciÃŗ", - "unable_to_submit_job": "No s’ha pogut enviar la tasca", - "unable_to_trash_asset": "No s’ha pogut enviar el recurs a la paperera", - "unable_to_unlink_account": "No s’ha pogut desenllaçar el compte", - "unable_to_unlink_motion_video": "No s’ha pogut desenllaçar el vídeo en moviment", - "unable_to_update_album_cover": "No s’ha pogut actualitzar la portada de l’àlbum", - "unable_to_update_album_info": "No s’ha pogut actualitzar la informaciÃŗ de l’àlbum", - "unable_to_update_library": "No s’ha pogut actualitzar la biblioteca", - "unable_to_update_location": "No s’ha pogut actualitzar la ubicaciÃŗ", - "unable_to_update_settings": "No s’ha pogut actualitzar la configuraciÃŗ", - "unable_to_update_timeline_display_status": "No s’ha pogut actualitzar l’estat de visualitzaciÃŗ de la cronologia", - "unable_to_update_user": "No s’ha pogut actualitzar l’usuari", + "unable_to_add_album_users": "No es poden afegir usuaris a l'àlbum", + "unable_to_add_assets_to_shared_link": "No s'han pogut afegir els elements a l'enllaç compartit", + "unable_to_add_comment": "No es pot afegir el comentari", + "unable_to_add_exclusion_pattern": "No s'ha pogut afegir el patrÃŗ d’exclusiÃŗ", + "unable_to_add_partners": "No es poden afegir companys", + "unable_to_add_remove_archive": "No s'ha pogut {archived, select, true {eliminar l'element de} other {afegir l'element a}} l'arxiu", + "unable_to_add_remove_favorites": "No s'ha pogut {favorite, select, true {afegir l'element als} other {eliminar l'element dels}} preferits", + "unable_to_archive_unarchive": "No es pot {archived, select, true {arxivar} other {desarxivar}}", + "unable_to_change_album_user_role": "No es pot canviar el rol d'usuari de l'àlbum", + "unable_to_change_date": "No es pot canviar la data", + "unable_to_change_description": "No s'ha pogut canviar la descripciÃŗ", + "unable_to_change_favorite": "No es pot canviar el favorit per a aquest recurs", + "unable_to_change_location": "No es pot canviar la ubicaciÃŗ", + "unable_to_change_password": "No es pot canviar la contrasenya", + "unable_to_change_visibility": "No es pot canviar la visibilitat de {count, plural, one {# persona} other {# persones}}", + "unable_to_complete_oauth_login": "No es pot completar l'inici de sessiÃŗ OAuth", + "unable_to_connect": "No pot connectar", + "unable_to_copy_to_clipboard": "No es pot copiar al porta-retalls, assegureu-vos que esteu accedint a la pàgina mitjançant https", + "unable_to_create": "No s'ha pogut crear el flux de treball", + "unable_to_create_admin_account": "No es pot crear un compte d'administrador", + "unable_to_create_api_key": "No es pot crear una clau d'API nova", + "unable_to_create_library": "No es pot crear la llibreria", + "unable_to_create_user": "No es pot crear l'usuari", + "unable_to_delete_album": "No es pot eliminar l'àlbum", + "unable_to_delete_asset": "No es pot suprimir el recurs", + "unable_to_delete_assets": "S'ha produït un error en suprimir recursos", + "unable_to_delete_exclusion_pattern": "No es pot suprimir el patrÃŗ d'exclusiÃŗ", + "unable_to_delete_shared_link": "No es pot suprimir l'enllaç compartit", + "unable_to_delete_user": "No es pot eliminar l'usuari", + "unable_to_delete_workflow": "No es pot suprimir el flux de treball", + "unable_to_download_files": "No es poden descarregar fitxers", + "unable_to_edit_exclusion_pattern": "No es pot editar el patrÃŗ d'exclusiÃŗ", + "unable_to_empty_trash": "No es pot buidar la paperera", + "unable_to_enter_fullscreen": "No es pot entrar a la pantalla completa", + "unable_to_exit_fullscreen": "No es pot sortir de la pantalla completa", + "unable_to_get_comments_number": "No es pot obtenir el nombre de comentaris", + "unable_to_get_shared_link": "No s'ha pogut obtenir l'enllaç compartit", + "unable_to_hide_person": "No es pot amagar la persona", + "unable_to_link_motion_video": "No es pot enllaçar el vídeo en moviment", + "unable_to_link_oauth_account": "No es pot enllaçar el compte OAuth", + "unable_to_log_out_all_devices": "No es poden tancar la sessiÃŗ de tots els dispositius", + "unable_to_log_out_device": "No es pot tancar la sessiÃŗ del dispositiu", + "unable_to_login_with_oauth": "No es pot iniciar sessiÃŗ amb OAuth", + "unable_to_play_video": "No es pot reproduir el vídeo", + "unable_to_reassign_assets_existing_person": "No es poden reassignar recursos a {name, select, null {una persona existent} other {{name}}}", + "unable_to_reassign_assets_new_person": "No es poden reassignar recursos a una persona nova", + "unable_to_refresh_user": "No es pot actualitzar l'usuari", + "unable_to_remove_album_users": "No es poden eliminar usuaris de l'àlbum", + "unable_to_remove_api_key": "No es pot eliminar la clau de l'API", + "unable_to_remove_assets_from_shared_link": "No es poden eliminar recursos de l'enllaç compartit", + "unable_to_remove_library": "No es pot eliminar la biblioteca", + "unable_to_remove_partner": "No es pot eliminar company/a", + "unable_to_remove_reaction": "No es pot eliminar la reacciÃŗ", + "unable_to_reset_password": "No es pot restablir la contrasenya", + "unable_to_reset_pin_code": "No es pot restablir el codi PIN", + "unable_to_resolve_duplicate": "No es pot resoldre el duplicat", + "unable_to_restore_assets": "No es poden restaurar els recursos", + "unable_to_restore_trash": "No es pot restaurar la paperera", + "unable_to_restore_user": "No es pot restaurar l'usuari", + "unable_to_save_album": "No es pot desar l'àlbum", + "unable_to_save_api_key": "No es pot desar la clau de l'API", + "unable_to_save_date_of_birth": "No es pot desar la data de naixement", + "unable_to_save_name": "No es pot desar el nom", + "unable_to_save_profile": "No es pot desar el perfil", + "unable_to_save_settings": "No es pot desar la configuraciÃŗ", + "unable_to_scan_libraries": "No es poden escanejar les biblioteques", + "unable_to_scan_library": "No es pot escanejar la biblioteca", + "unable_to_set_feature_photo": "No s'ha pogut configurar la foto destacada", + "unable_to_set_profile_picture": "No es pot configurar la foto de perfil", + "unable_to_set_rating": "No s'ha pogut establir la valoraciÃŗ", + "unable_to_submit_job": "No es pot enviar la tasca", + "unable_to_trash_asset": "No es pot eliminar el recurs a la paperera", + "unable_to_unlink_account": "No es pot desenllaçar el compte", + "unable_to_unlink_motion_video": "No es pot desvincular el vídeo en moviment", + "unable_to_update_album_cover": "No es pot actualitzar la portada de l'àlbum", + "unable_to_update_album_info": "No es pot actualitzar la informaciÃŗ de l'àlbum", + "unable_to_update_library": "No es pot actualitzar la biblioteca", + "unable_to_update_location": "No es pot actualitzar la ubicaciÃŗ", + "unable_to_update_settings": "No es pot actualitzar la configuraciÃŗ", + "unable_to_update_timeline_display_status": "No es pot actualitzar l'estat de visualitzaciÃŗ de la cronologia", + "unable_to_update_user": "No es pot actualitzar l'usuari", "unable_to_update_workflow": "No es pot actualitzar el flux de treball", "unable_to_upload_file": "No es pot carregar el fitxer" }, "errors_text": "Errors", - "exclusion_pattern": "PatrÃŗ d’exclusiÃŗ", + "exclusion_pattern": "PatrÃŗ d'exclusiÃŗ", "exif": "Exif", - "exif_bottom_sheet_description": "Afegiu una descripciÃŗ...", - "exif_bottom_sheet_description_error": "No s’ha pogut actualitzar la descripciÃŗ", + "exif_bottom_sheet_description": "Afegeix descripciÃŗ...", + "exif_bottom_sheet_description_error": "No s'ha pogut actualitzar la descripciÃŗ", + "exif_bottom_sheet_details": "DETALLS", + "exif_bottom_sheet_location": "UBICACIÓ", "exif_bottom_sheet_no_description": "Sense descripciÃŗ", - "exit_slideshow": "Surt de la presentaciÃŗ", - "expand": "Expandeix", - "expand_all": "Expandeix-ho tot", + "exif_bottom_sheet_people": "PERSONES", + "exif_bottom_sheet_person_add_person": "Afegir nom", + "exit_slideshow": "Surt de la presentaciÃŗ de diapositives", + "expand": "Ampliar-ho", + "expand_all": "Ampliar-ho tot", + "experimental_settings_new_asset_list_subtitle": "Treball en curs", + "experimental_settings_new_asset_list_title": "Habilita la graella de fotos experimental", + "experimental_settings_subtitle": "Utilitzeu-ho sota la vostra responsabilitat!", + "experimental_settings_title": "Experimental", "expire_after": "Caduca desprÊs de", "expired": "Caducat", "expires_date": "Caduca el {date}", - "explore": "Explora", + "explore": "Explorar", "explorer": "Explorador", "export": "Exporta", - "export_as_json": "Exporta com a JSON", - "export_database": "Exporta la base de dades", - "export_database_description": "Exporta la base de dades SQLite", - "exposure_time": "Temps d’exposiciÃŗ", + "export_as_json": "Exportar com a JSON", + "export_database": "Exportar base de dades", + "export_database_description": "Exportar la base de dades SQLite", "extension": "ExtensiÃŗ", "external": "Extern", - "external_libraries": "Biblioteques externes", + "external_libraries": "Llibreries externes", "external_network": "Xarxa externa", - "external_network_sheet_info": "Quan no sigueu a la xarxa Wi-Fi preferida, l’aplicaciÃŗ es connectarà al servidor mitjançant el primer dels URL segÃŧents a què pugui arribar, començant de dalt a baix", - "f_number": "Obertura", - "face_unassigned": "No assignada", - "failed": "Fallit", + "external_network_sheet_info": "Quan no estigui a la xarxa Wi-Fi preferida, l'aplicaciÃŗ es connectarà al servidor mitjançant el primer dels URL segÃŧents a què pot arribar, començant de dalt a baix", + "face_unassigned": "Sense assignar", + "failed": "Fallat", "failed_count": "Fallits: {count}", - "failed_to_authenticate": "No s’ha pogut autenticar", - "failed_to_delete_file": "No s’ha pogut suprimir el fitxer", - "failed_to_load_assets": "No s’han pogut carregar els recursos", - "failed_to_load_folder": "No s’ha pogut carregar la carpeta", + "failed_to_authenticate": "No s'ha pogut autenticar", + "failed_to_load_assets": "Error carregant recursos", + "failed_to_load_folder": "No s'ha pogut carregar la carpeta", "favorite": "Preferit", - "favorite_action_prompt": "{count} afegit a preferits", - "favorite_or_unfavorite_photo": "Afegeix o elimina la foto dels preferits", + "favorite_action_prompt": "{count} afegit a Favorits", + "favorite_or_unfavorite_photo": "Foto preferida o no preferida", "favorites": "Preferits", - "feature_photo_updated": "S’ha actualitzat la foto destacada", - "features": "Funcionalitats", - "features_setting_description": "Gestioneu les funcionalitats de l’aplicaciÃŗ", - "file_name_or_extension": "Nom del fitxer o extensiÃŗ", + "favorites_page_no_favorites": "No s'han trobat preferits", + "feature_photo_updated": "Foto destacada actualitzada", + "features": "Característiques", + "features_in_development": "Funcions en desenvolupament", + "features_setting_description": "Administrar les funcions de l'aplicaciÃŗ", + "file_name_or_extension": "Nom de l'arxiu o extensiÃŗ", "file_name_text": "Nom del fitxer", + "file_name_with_value": "Nom del fitxer: {file_name}", "file_size": "Mida del fitxer", "filename": "Nom del fitxer", - "filetype": "Tipus de fitxer", - "filter": "Filtre", + "filetype": "Tipus d'arxiu", + "filter": "Filtrar", + "filter_description": "Condicions per filtrar els actius de destinaciÃŗ", "filter_people": "Filtra persones", - "filter_places": "Filtra llocs", - "filter_tags": "Filtra etiquetes", + "filter_places": "Filtrar per llocs", + "filter_tags": "Filtrar etiquetes", "filters": "Filtres", + "find_them_fast": "Trobeu-los ràpidament pel nom amb la cerca", "first": "Primer", - "fix_incorrect_match": "Corregeix la coincidència incorrecta", - "focal_length": "Distància focal", + "fix_incorrect_match": "Corregiu la coincidència incorrecta", "folder": "Carpeta", - "folder_not_found": "No s’ha trobat la carpeta", + "folder_not_found": "Carpeta no trobada", "folders": "Carpetes", - "folders_feature_description": "NavegaciÃŗ per la vista de carpetes de les fotos i vídeos al sistema de fitxers", - "forgot_pin_code_question": "Heu oblidat el PIN?", - "forward": "Cap endavant", - "free_up_space": "Allibera espai", - "free_up_space_description": "Mou les fotos i vídeos que ja tenen cÃ˛pia al servidor a la paperera del vostre dispositiu per a alliberar espai. Les cÃ˛pies del servidor romandran sense canvis.", - "free_up_space_settings_subtitle": "Allibera espai del dispositiu", - "full_path": "Camí complet: {path}", - "full_path_or_folder": "Camí complet o carpeta", + "folders_feature_description": "Explorar la vista de carpetes per les fotos i vídeos del sistema d'arxius", + "forgot_pin_code_question": "Has oblidat el teu PIN?", + "forward": "Endavant", + "free_up_space": "Alliberar Espai", + "free_up_space_description": "Mou fotos i videos que ja tinguen cÃ˛pia al servidor a la paperera del teu dispositiu per alliberar espai. Les cÃ˛pies del servidor no es modificaran.", + "free_up_space_settings_subtitle": "Alliberar espai del dispositiu", + "full_path": "Ruta completa: {path}", "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Aquesta funcionalitat carrega recursos externs de Google per a funcionar.", + "gcast_enabled_description": "Aquesta funciÃŗ carrega recursos externs de Google per funcionar.", "general": "General", - "geolocation_instruction_location": "Feu clic en un recurs amb coordenades GPS per a utilitzar-ne la ubicaciÃŗ o seleccioneu una ubicaciÃŗ directament al mapa", - "get_help": "ObtÊn ajuda", - "get_people_error": "S’ha produït un error en obtenir les persones", - "get_wifiname_error": "No s’ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi", - "getting_started": "Primers passos", - "go_back": "Torna endarrere", - "go_to_folder": "VÊs a la carpeta", - "go_to_search": "VÊs a la cerca", + "geolocation_instruction_location": "Fes click en un element amb coordinades GPS per utilitzar la seva ubicaciÃŗ o selecciona una ubicaciÃŗ des del mapa", + "get_help": "Aconseguir ajuda", + "get_people_error": "S'ha produït un error en aconseguir persones", + "get_wifiname_error": "No s'ha pogut obtenir el nom de la Wi-Fi. Assegureu-vos que heu concedit els permisos necessaris i que esteu connectat a una xarxa Wi-Fi", + "getting_started": "Començant", + "go_back": "Torna", + "go_to_folder": "Anar al directori", + "go_to_search": "VÊs a cercar", "gps": "GPS", "gps_missing": "Sense GPS", - "grant_permission": "Concediu el permís", - "group_albums_by": "Agrupa els àlbums per...", - "group_country": "Agrupa per país", - "group_no": "Sense agrupaciÃŗ", - "group_owner": "Agrupa per propietari", - "group_places_by": "Agrupa els llocs per...", - "group_year": "Agrupa per any", - "haptic_feedback_title": "Resposta hàptica", - "has_quota": "TÊ quota", - "hash_asset": "Genera el hash del recurs", + "grant_permission": "Concedir permís", + "group_albums_by": "Agrupa àlbums per...", + "group_country": "Agrupar per país", + "group_no": "Cap agrupaciÃŗ", + "group_owner": "Agrupar per propietari", + "group_places_by": "Agrupar llocs per...", + "group_year": "Agrupar per any", + "haptic_feedback_switch": "Activa la resposta hàptica", + "haptic_feedback_title": "Resposta Hàptica", + "has_quota": "Quota", + "hash_asset": "Hash del recurs", "hashed_assets": "Recursos amb hash", - "hashing": "S’està generant el hash", - "header_settings_add_header_tip": "Afegeix una capçalera", + "hashing": "Generant el hash", + "header_settings_add_header_tip": "Afegeix Capçalera", "header_settings_field_validator_msg": "El valor no pot estar buit", "header_settings_header_name_input": "Nom de la capçalera", "header_settings_header_value_input": "Valor de la capçalera", - "headers_settings_tile_title": "Capçaleres de servidor intermediari personalitzades", + "headers_settings_tile_title": "Capçaleres proxy personalitzades", "height": "Alçada", - "hi_user": "Bon dia, {name} ({email})", + "hi_user": "Hola {name} ({email})", "hide_all_people": "Amaga totes les persones", "hide_gallery": "Amaga la galeria", "hide_named_person": "Amaga la persona {name}", "hide_password": "Amaga la contrasenya", "hide_person": "Amaga la persona", - "hide_schema": "Amaga l’esquema", - "hide_text_recognition": "Amaga el reconeixement de text", - "hide_unnamed_people": "Amaga les persones sense nom", - "home_page_building_timeline": "S’està construint la cronologia", + "hide_schema": "Amaga l'esquema", + "hide_text_recognition": "Oculta el reconeixement de text", + "hide_unnamed_people": "Amaga persones sense nom", + "home_page_add_to_album_conflicts": "S'han afegit {added} elements a l'àlbum {album}. {failed} elements ja existeixen a l'àlbum.", + "home_page_add_to_album_err_local": "Encara no es poden afegir elements locals als àlbums, ometent", + "home_page_add_to_album_success": "S'han afegit {added} elements a l'àlbum {album}.", + "home_page_album_err_partner": "Encara no es poden afegir elements dels companys als àlbums, ometent", + "home_page_archive_err_local": "Encara no es poden arxivar elements locals, ometent", + "home_page_archive_err_partner": "No es poden arxivar els elements de companys, ometent", + "home_page_building_timeline": "Construint la línia de temps", + "home_page_delete_err_partner": "No es poden suprimir els elements de companys, ometent", + "home_page_delete_remote_err_local": "Elements locals a la selecciÃŗ d'eliminaciÃŗ remota, ometent", + "home_page_favorite_err_local": "Encara no es pot afegir a preferits elements locals, ometent", + "home_page_favorite_err_partner": "Encara no es pot afegir a preferits elements de companys, ometent", + "home_page_first_time_notice": "Si Ês la primera vegada que utilitzes l'app, si us plau, assegura't d'escollir un àlbum de cÃ˛pia de seguretat perquè la línia de temps pugui carregar fotos i vídeos als àlbums", + "home_page_locked_error_local": "No s'han pogut moure els recursos locals a la carpeta bloquejada, saltant", + "home_page_locked_error_partner": "No s'han pogut moure els recursos de la parella a la carpeta bloquejada, saltant", + "home_page_share_err_local": "No es poden compartir els elements locals a travÊs d'un enllaç, ometent", + "home_page_upload_err_limit": "NomÊs es poden pujar un màxim de 30 elements alhora, ometent", "host": "AmfitriÃŗ", "hour": "Hora", "hours": "Hores", - "id": "Identificador", - "idle": "Inactiu", + "id": "ID", + "idle": "En espera", + "ignore_icloud_photos": "Ignora fotos d'iCloud", + "ignore_icloud_photos_description": "Les fotos emmagatzemades a iCloud no es penjaran al servidor Immich", "image": "Imatge", - "image_alt_text_date": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} el {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} amb {person1} el {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} amb {person1} i {person2} el {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} amb {person1}, {person2} i {person3} el {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} amb {person1}, {person2} i {additionalCount, number} mÊs el {date}", - "image_alt_text_date_place": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} a {city}, {country} el {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} a {city}, {country} amb {person1} el {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} a {city}, {country} amb {person1} i {person2} el {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} a {city}, {country} amb {person1}, {person2} i {person3} el {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo enregistrat} other {Imatge capturada}} a {city}, {country} amb {person1}, {person2} i {additionalCount, number} mÊs el {date}", - "immich_logo": "Logo de l’Immich", - "immich_web_interface": "Interfície web de l’Immich", - "import_from_json": "Importa d’un JSON", - "import_path": "Camí d’importaciÃŗ", - "in_albums": "En {count, plural, one {# àlbum} other {# àlbums}}", - "in_archive": "A l’arxiu", - "in_year": "El {year}", - "in_year_selector": "El", - "include_archived": "Inclou els arxivats", - "include_shared_albums": "Inclou els àlbums compartits", - "include_shared_partner_assets": "Inclou els recursos compartits dels companys", - "individual_share": "ComparticiÃŗ individual", - "individual_shares": "Comparticions individuals", + "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} presa el {date}", + "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1} el {date}", + "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} pres/a {person1} amb {person2} el {date}", + "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {person3} el {date}", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a amb {person1}, {person2}, i {additionalCount, number} altres el {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} el {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} el {date}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1} i {person2} el {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {person3} el {date}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} pres/a a {city}, {country} amb {person1}, {person2}, i {additionalCount, number} altres el {date}", + "image_saved_successfully": "Imatge desada", + "image_viewer_page_state_provider_download_started": "Baixada començada", + "image_viewer_page_state_provider_download_success": "Baixada amb èxit", + "image_viewer_page_state_provider_share_error": "Error en compartir", + "immich_logo": "Logotip d'Immich", + "immich_web_interface": "Interfície web Immich", + "import_from_json": "Importar des de JSON", + "import_path": "Ruta d'importaciÃŗ", + "in_albums": "A {count, plural, one {# àlbum} other {# àlbums}}", + "in_archive": "En arxiu", + "in_year": "En {year}", + "in_year_selector": "En", + "include_archived": "Incloure arxivats", + "include_shared_albums": "Inclou àlbums compartits", + "include_shared_partner_assets": "Incloure elements dels companys", + "individual_share": "Compartit individualment", + "individual_shares": "Espais individuals", "info": "InformaciÃŗ", - "integrity_checks": "Verificacions d’integritat", "interval": { - "day_at_onepm": "Cada dia a les 13 h", - "hours": "Cada {hours, plural, one {hora} other {{hours, number} hores}}", - "night_at_midnight": "Cada nit a mitjanit", - "night_at_twoam": "Cada nit a les 2 h" + "day_at_onepm": "Cada dia a les 13h", + "hours": "Cada {hours, plural, one {hour} other {{hours, number} hours}}", + "night_at_midnight": "Cada mitjanit", + "night_at_twoam": "Cada nit a les 2 del matí" }, - "invalid_date": "La data no Ês vàlida", - "invalid_date_format": "El format de data no Ês vàlid", - "invite_people": "Convida persones", - "invite_to_album": "Convida a l’àlbum", - "iso": "ISO", + "invalid_date": "Data invàlida", + "invalid_date_format": "Format de data invàlid", + "invite_people": "Convida gent", + "invite_to_album": "Convida a l'àlbum", + "ios_debug_info_fetch_ran_at": "La recuperaciÃŗ s'ha executat {dateTime}", + "ios_debug_info_last_sync_at": "Darrera sincronitzaciÃŗ {dateTime}", + "ios_debug_info_no_processes_queued": "No hi ha processos en segon pla en cua", + "ios_debug_info_no_sync_yet": "Encara no s'ha executat cap tasca de sincronitzaciÃŗ en segon pla", + "ios_debug_info_processes_queued": "{count, plural, one {Un procÊs en segon pla a la cua} other {{count} processos en segon pla a la cua}}", + "ios_debug_info_processing_ran_at": "El processament s'ha executat {dateTime}", "items_count": "{count, plural, one {# element} other {# elements}}", "jobs": "Tasques", - "keep": "Conserva", + "json_editor": "Editor JSON", + "json_error": "Error en el JSON", + "keep": "Mantenir", "keep_albums": "Conserva els àlbums", - "keep_albums_count": "Es {count, plural, one {conservarà # àlbum} other {conservaran # àlbums}}", - "keep_all": "Conserva-ho tot", - "keep_description": "Trieu què es conserva al vostre dispositiu en alliberar espai.", - "keep_favorites": "Conserva els preferits", - "keep_on_device": "Conserva al dispositiu", - "keep_on_device_hint": "Seleccioneu els elements que vulgueu conservar en aquest dispositiu", - "keep_this_delete_others": "Conserva aixÃ˛, suprimeix la resta", - "keeping": "Es conservarà: {items}", - "kept_this_deleted_others": "S’ha conservat aquest recurs i se {count, plural, one {n’ha suprimit #} other {n’han suprimit #}}", + "keep_albums_count": "Conservant {count} {count, plural, one {àlbum} other {àlbums}}", + "keep_all": "Mantenir-ho tot", + "keep_description": "Tria què es conserva al dispositiu quan s'allibera espai.", + "keep_favorites": "Mantindre els preferits", + "keep_on_device": "MantÊn al dispositiu", + "keep_on_device_hint": "Selecciona els elements que vulguis conservar en aquest dispositiu", + "keep_this_delete_others": "Conserveu-ho, suprimiu-ne els altres", + "keeping": "Mantenint: {items}", + "kept_this_deleted_others": "S'ha conservat aquest element i s'han suprimit {count, plural, one {# asset} other {# assets}}", "keyboard_shortcuts": "Dreceres de teclat", - "language": "Llengua", - "language_no_results_subtitle": "Proveu d’ajustar el terme de cerca", - "language_no_results_title": "No s’ha trobat cap llengua", - "language_search_hint": "Cerqueu llengÃŧes...", - "language_setting_description": "Seleccioneu la vostra llengua preferida", - "large_files": "Fitxers grans", - "last": "Darrer", - "last_months": "{count, plural, one {Darrer mes} other {Darrers # mesos}}", - "last_seen": "Vist per darrera vegada", - "latest_version": "Darrera versiÃŗ", + "language": "Idioma", + "language_no_results_subtitle": "Prova d'ajustar el terme de cerca", + "language_no_results_title": "No s'han trobat idiomes", + "language_search_hint": "Cerca idiomes...", + "language_setting_description": "Seleccioneu el vostre idioma", + "large_files": "Fitxers Grans", + "last": "Últim", + "last_months": "{count, plural, one {Últim mes} other {Últims # mesos}}", + "last_seen": "Vist per Ãēltim cop", + "latest_version": "Última versiÃŗ", "latitude": "Latitud", - "leave": "Surt", - "leave_album": "Surt de l’àlbum", - "lens_model": "Model de l’objectiu", - "less": "Menys", - "let_others_respond": "Permet que els altres responguin", + "leave": "Marxar", + "leave_album": "Abandonar àlbum", + "lens_model": "Model de lents", + "let_others_respond": "Deixa que els altres responguin", "level": "Nivell", "library": "Bibilioteca", - "library_add_folder": "Afegeix una carpeta", - "library_edit_folder": "Edita la carpeta", - "library_page_sort_asset_count": "Nombre de recursos", - "library_page_sort_created": "Data de creaciÃŗ", + "library_add_folder": "Afegir carpeta", + "library_edit_folder": "Editar carpeta", + "library_options": "Opcions de biblioteca", + "library_page_device_albums": "Àlbums al Dispositiu", + "library_page_new_album": "Nou àlbum", + "library_page_sort_asset_count": "Quantitat d'elements", + "library_page_sort_created": "Creat mÊs recentment", "library_page_sort_last_modified": "Darrera modificaciÃŗ", - "library_page_sort_title": "Títol de l’àlbum", + "library_page_sort_title": "Títol de l'àlbum", "licenses": "Llicències", - "light": "Clar", - "light_theme": "Canvia al tema clar", - "like": "M’agrada", - "like_deleted": "S’ha suprimit l’agradament", - "link": "Enllaç", - "link_motion_video": "Enllaça un vídeo en moviment", - "link_to_docs": "Per a mÊs informaciÃŗ, consulteu la documentaciÃŗ.", - "link_to_oauth": "Enllaça amb l’OAuth", - "linked_oauth_account": "Compte d’OAuth enllaçat", + "light": "Llum", + "light_theme": "Canviar a tema clar", + "like": "M'agrada", + "like_deleted": "M'agrada suprimit", + "link_motion_video": "Enllaçar vídeo en moviment", + "link_to_docs": "Per mÊs informaciÃŗ, mirar la documentation.", + "link_to_oauth": "Enllaç a OAuth", + "linked_oauth_account": "Compte OAuth enllaçat", "list": "Llista", - "live": "En directe", - "load_more": "Carrega’n mÊs", - "loading": "S’està carregant", - "loading_search_results_failed": "No s’han pogut carregar els resultats de la cerca", + "loading": "Carregant", + "loading_search_results_failed": "No s'han pogut carregar els resultats de la cerca", "local": "Local", - "local_asset_cast_failed": "No es pot emetre un recurs que no s’ha penjat al servidor", - "local_assets": "Recursos locals", - "local_id": "Identificador local", - "local_media_summary": "Resum dels mitjans locals", + "local_asset_cast_failed": "No es pot convertir un actiu que no s'ha penjat al servidor", + "local_assets": "Recursos Locals", + "local_id": "ID local", + "local_media_summary": "Resum de Mitjans Locals", "local_network": "Xarxa local", - "local_network_sheet_info": "L’aplicaciÃŗ es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", - "location": "UbicaciÃŗ", - "location_permission": "Permís d’ubicaciÃŗ", - "location_permission_content": "Per a poder utilitzar la funciÃŗ de canvi automàtic, l’Immich necessita el permís d’ubicaciÃŗ precisa per a poder llegir el nom de la xarxa Wi-Fi actual", - "location_picker_choose_on_map": "Tria al mapa", - "location_picker_latitude_error": "Introduïu una latitud vàlida", - "location_picker_latitude_hint": "Introduïu aquí la latitud", - "location_picker_longitude_error": "Introduïu una longitud vàlida", - "location_picker_longitude_hint": "Introduïu aquí la longitud", - "lock": "Protegeix", - "locked_folder": "Carpeta protegida", - "log_detail_title": "Detall del registre", + "local_network_sheet_info": "L'aplicaciÃŗ es connectarà al servidor mitjançant aquest URL quan utilitzeu la xarxa Wi-Fi especificada", + "location": "LocalitzaciÃŗ", + "location_permission": "Permís d'ubicaciÃŗ", + "location_permission_content": "Per utilitzar la funciÃŗ de canvi automàtic, Immich necessita un permís d'ubicaciÃŗ precisa perquè pugui llegir el nom de la xarxa Wi-Fi actual", + "location_picker_choose_on_map": "Escollir en el mapa", + "location_picker_latitude_error": "Introdueix una latitud vàlida", + "location_picker_latitude_hint": "Introdueix aquí la latitud", + "location_picker_longitude_error": "Introdueix una longitud vàlida", + "location_picker_longitude_hint": "Introdueix aquí la longitud", + "lock": "Bloqueja", + "locked_folder": "Carpeta bloquejada", + "log_detail_title": "Detall de Registres", "log_out": "Tanca la sessiÃŗ", - "log_out_all_devices": "Tanca la sessiÃŗ en tots els dispositius", + "log_out_all_devices": "Tanqueu la sessiÃŗ de tots els dispositius", "logged_in_as": "SessiÃŗ iniciada com a {user}", - "logged_out_all_devices": "S’ha tancat la sessiÃŗ en tots els dispositius", - "logged_out_device": "Dispositiu amb la sessiÃŗ tancada", - "login": "Inicia la sessiÃŗ", - "login_disabled": "S’ha desactivat l’inici de sessiÃŗ", - "login_form_api_exception": "ExcepciÃŗ de l’API. Comproveu l’URL del servidor i torneu-ho a provar.", - "login_form_email_hint": "elvostrecorreu@correu.com", - "login_form_endpoint_hint": "http://ip-del-vostre-servidor:port", - "login_form_endpoint_url": "URL de l’endpoint del servidor", - "login_form_err_invalid_email": "L’adreça electrÃ˛nica no Ês vàlida", - "login_form_err_invalid_url": "L’URL no Ês vàlid", + "logged_out_all_devices": "S'ha tancat la sessiÃŗ de tots els dispositius", + "logged_out_device": "Dispositiu tancat", + "login": "Iniciar sessiÃŗ", + "login_disabled": "S'ha desactivat l'inici de sessiÃŗ", + "login_form_api_exception": "ExcepciÃŗ de l'API. Comproveu l'URL del servidor i torneu-ho a provar.", + "login_form_back_button_text": "Enrere", + "login_form_email_hint": "elteu@correu.cat", + "login_form_endpoint_hint": "http://ip-del-servidor:port", + "login_form_endpoint_url": "URL del punt final del servidor", + "login_form_err_http": "Especifica http:// o https://", + "login_form_err_invalid_email": "Adreça de correu electrÃ˛nic no vàlida", + "login_form_err_invalid_url": "URL no vàlid", "login_form_err_leading_whitespace": "Espai en blanc al principi", "login_form_err_trailing_whitespace": "Espai en blanc al final", - "login_form_failed_get_oauth_server_config": "S’ha produït un error en iniciar la sessiÃŗ amb OAuth, reviseu l’URL del servidor", - "login_form_failed_get_oauth_server_disable": "La funcionalitat d’OAuth no està disponible en aquest servidor", - "login_form_failed_login": "S’ha produït un error en iniciar la sessiÃŗ, reviseu l’URL del servidor, l’adreça electrÃ˛nica i la contrasenya", - "login_form_handshake_exception": "S’ha produït una excepciÃŗ de handshake amb el servidor. Si utilitzeu un certificat autosignat, activeu el suport per a certificats autosignats a la configuraciÃŗ.", + "login_form_failed_get_oauth_server_config": "Error en iniciar sessiÃŗ amb OAuth, comprova l'URL del servidor", + "login_form_failed_get_oauth_server_disable": "La funcionalitat OAuth no està disponible en aquest servidor", + "login_form_failed_login": "Error en iniciar sessiÃŗ, comprova l'URL del servidor, el correu electrÃ˛nic i la contrasenya", + "login_form_handshake_exception": "S'ha produït una excepciÃŗ de handshake amb el servidor. Activa el suport per certificats autofirmats a la configuraciÃŗ si estàs fent servir un certificat autofirmat.", "login_form_password_hint": "contrasenya", - "login_form_server_empty": "Introduïu l’URL del servidor", - "login_form_server_error": "No s’ha pogut connectar amb el servidor", - "login_has_been_disabled": "S’ha desactivat l’inici de sessiÃŗ", - "login_password_changed_error": "S’ha produït un error en actualitzar la contrasenya", - "login_password_changed_success": "La contrasenya s’ha canviat correctament", - "logout_all_device_confirmation": "Segur que voleu tancar la sessiÃŗ en tots els dispositius?", - "logout_this_device_confirmation": "Segur que voleu tancar la sessiÃŗ d’aquest dispositiu?", + "login_form_save_login": "Mantingues identificat", + "login_form_server_empty": "Introdueix l'URL del servidor.", + "login_form_server_error": "No s'ha pogut connectar al servidor.", + "login_has_been_disabled": "L'inici de sessiÃŗ s'ha desactivat.", + "login_password_changed_error": "S'ha produït un error en actualitzar la contrasenya", + "login_password_changed_success": "La contrasenya s'ha canviat correctament", + "logout_all_device_confirmation": "Esteu segur que voleu tancar la sessiÃŗ de tots els dispositius?", + "logout_this_device_confirmation": "Esteu segur que voleu tancar la sessiÃŗ d'aquest dispositiu?", "logs": "Registres", "longitude": "Longitud", "look": "Aspecte", "loop_videos": "Vídeos en bucle", - "loop_videos_description": "Activa la reproducciÃŗ automàtica en bucle del vídeo al visualitzador dels detalls.", - "main_branch_warning": "Utilitzeu una versiÃŗ en desenvolupament; Ês recomanable utilitzar una versiÃŗ de llançament!", + "loop_videos_description": "Habilita la reproducciÃŗ en bucle del vídeo en els detalls.", + "main_branch_warning": "Esteu utilitzant una versiÃŗ en desenvolupament; Recomanem fer servir una versiÃŗ publicada!", "main_menu": "MenÃē principal", - "maintenance_action_restore": "S’està restaurant la base de dades", - "maintenance_description": "L’Immich està en mode de manteniment.", - "maintenance_end": "Finalitza el mode de manteniment", - "maintenance_logged_in_as": "SessiÃŗ iniciada com a {user}", - "maintenance_restore_from_backup": "Restaura d’una cÃ˛pia de seguretat", - "maintenance_restore_library": "Restaureu la vostra biblioteca", - "maintenance_restore_library_confirm": "Si tot sembla correcte, continueu restaurant una cÃ˛pia de seguretat!", - "maintenance_restore_library_description": "S’està restaurant la base de dades", - "maintenance_restore_library_folder_has_files": "{folder} contÊ {count, plural, one {# carpeta} other {# carpetes}}", - "maintenance_restore_library_folder_no_files": "Manquen fitxers a {folder}!", + "maintenance_action_restore": "Restaurant la base de dades", + "maintenance_description": "Immich ha estat posat en mode de manteniment.", + "maintenance_end": "Finalitzar el mode de manteniment", + "maintenance_end_error": "Error al finalitzar el mode de manteniment.", + "maintenance_logged_in_as": "Actualment la sessiÃŗ esta iniciada per {user}", + "maintenance_restore_from_backup": "Restaurar des d'una cÃ˛pia de seguretat", + "maintenance_restore_library": "Restaura la teva biblioteca", + "maintenance_restore_library_confirm": "Si aixÃ˛ sembla correcte, continua restaurant una cÃ˛pia de seguretat!", + "maintenance_restore_library_description": "Restaurant la cÃ˛pia de seguretat", + "maintenance_restore_library_folder_has_files": "{folder} contÊ {count} carpeta/es", + "maintenance_restore_library_folder_no_files": "A {folder} li falten fitxers!", "maintenance_restore_library_folder_pass": "llegible i escrivible", "maintenance_restore_library_folder_read_fail": "no llegible", "maintenance_restore_library_folder_write_fail": "no escrivible", - "maintenance_restore_library_hint_missing_files": "És possible que us manquin fitxers importants", - "maintenance_restore_library_hint_regenerate_later": "Podeu regenerar-los mÊs tard a la configuraciÃŗ", - "maintenance_restore_library_hint_storage_template_missing_files": "Utilitzeu una plantilla d’emmagatzematge? És possible que us manquin fitxers", - "maintenance_restore_library_loading": "S’estan carregant les verificacions d’integritat i les heurístiquesâ€Ļ", - "maintenance_task_backup": "S’està creant una cÃ˛pia de seguretat de la base de dades existentâ€Ļ", - "maintenance_task_migrations": "S’estan executant les migracions de la base de dadesâ€Ļ", - "maintenance_task_restore": "S’està restaurant la cÃ˛pia de seguretat elegidaâ€Ļ", - "maintenance_task_rollback": "No s’ha pogut restaurar, s’està tornant al punt de restauraciÃŗâ€Ļ", - "maintenance_title": "No disponible temporalment", + "maintenance_restore_library_hint_missing_files": "Potser et falten fitxers importants", + "maintenance_restore_library_hint_regenerate_later": "Pots regenerar-los mÊs tard a la configuraciÃŗ", + "maintenance_restore_library_hint_storage_template_missing_files": "Fas servir una plantilla d'emmagatzematge? Potser et falten fitxers", + "maintenance_restore_library_loading": "S'estan carregant les comprovacions d'integritat i heurístiquesâ€Ļ", + "maintenance_task_backup": "Creant una cÃ˛pia de seguretat de la base de dades existentâ€Ļ", + "maintenance_task_migrations": "Executant migracions de bases de dadesâ€Ļ", + "maintenance_task_restore": "Restaurant la cÃ˛pia de seguretat escollidaâ€Ļ", + "maintenance_task_rollback": "La restauraciÃŗ ha fallat, s'està tornant al punt de restauraciÃŗâ€Ļ", + "maintenance_title": "Temporalment inaccessible", "make": "Fabricant", - "manage_geolocation": "Gestiona la ubicaciÃŗ", - "manage_media_access_rationale": "Aquest permís Ês necessari per a la correcta gestiÃŗ dels recursos que es mouen a la paperera o se’n restauren.", - "manage_media_access_settings": "Obre la configuraciÃŗ", - "manage_media_access_subtitle": "Permeteu que l’Immich gestioni i mogui fitxers de mitjans.", + "manage_geolocation": "Gestioneu la vostra ubicaciÃŗ", + "manage_media_access_rationale": "Aquest permís es necessari per a la correcta gestiÃŗ dels actius que es mouen a la paperera i es restauren d'ella.", + "manage_media_access_settings": "ConfiguraciÃŗ oberta", + "manage_media_access_subtitle": "Permet a l'Immich gestionar i moure fitxers multimèdia.", "manage_media_access_title": "AccÊs a la gestiÃŗ de mitjans", - "manage_sharing_with_partners": "Gestioneu la comparticiÃŗ amb els companys", - "manage_the_app_settings": "Gestioneu la configuraciÃŗ de l’aplicaciÃŗ", - "manage_your_account": "Gestioneu el vostre compte", - "manage_your_api_keys": "Gestioneu les vostres claus de l’API", + "manage_shared_links": "Administrar enllaços compartits", + "manage_sharing_with_partners": "Gestiona la comparticiÃŗ amb els companys", + "manage_the_app_settings": "Gestioneu la configuraciÃŗ de l'aplicaciÃŗ", + "manage_your_account": "Gestiona el teu compte", + "manage_your_api_keys": "Gestioneu les vostres claus API", "manage_your_devices": "Gestioneu els vostres dispositius connectats", - "manage_your_oauth_connection": "Gestioneu la vostra connexiÃŗ amb l’OAuth", + "manage_your_oauth_connection": "Gestioneu la vostra connexiÃŗ OAuth", "map": "Mapa", - "map_cannot_get_user_location": "No s’ha pogut obtenir la ubicaciÃŗ de l’usuari", - "map_location_picker_page_use_location": "Utilitza aquesta ubicaciÃŗ", - "map_location_service_disabled_content": "Cal activar el servei d’ubicaciÃŗ per a mostrar els recursos de la vostra ubicaciÃŗ actual. Voleu activar-lo ara?", - "map_location_service_disabled_title": "Servei d’ubicaciÃŗ desactivat", - "map_marker_for_image": "Marcador del mapa per a la imatge capturada a {city}, {country}", - "map_marker_with_image": "Marcador del mapa amb imatge", - "map_no_location_permission_content": "Cal el permís d’ubicaciÃŗ per a mostrar els recursos de la vostra ubicaciÃŗ actual. Voleu concedir-lo ara?", - "map_no_location_permission_title": "Permís d’ubicaciÃŗ denegat", - "map_settings": "ConfiguraciÃŗ del mapa", + "map_assets_in_bounds": "{count, plural, =0 {No hi ha fotos en aquesta àrea} one {# foto} other {# fotos}}", + "map_cannot_get_user_location": "No es pot obtenir la ubicaciÃŗ de l'usuari", + "map_location_dialog_yes": "Sí", + "map_location_picker_page_use_location": "Utilitzar aquesta ubicaciÃŗ", + "map_location_service_disabled_content": "El servei de localitzaciÃŗ s'ha d'activar per mostrar els elements de la teva ubicaciÃŗ actual. Vols activar-lo ara?", + "map_location_service_disabled_title": "Servei de localitzaciÃŗ desactivat", + "map_marker_for_images": "Marcador de mapa per a imatges fetes a {city}, {country}", + "map_marker_with_image": "Marcador de mapa amb imatge", + "map_no_location_permission_content": "Es necessita el permís de localitzaciÃŗ per mostrar els elements de la teva ubicaciÃŗ actual. Vols permetre-ho ara?", + "map_no_location_permission_title": "Permís de localitzaciÃŗ denegat", + "map_settings": "Paràmetres de mapa", "map_settings_dark_mode": "Mode fosc", - "map_settings_date_range_option_day": "Les darreres 24 hores", - "map_settings_date_range_option_days": "Els darrers {days} dies", - "map_settings_date_range_option_year": "L’any passat", - "map_settings_date_range_option_years": "Els darrers {years} anys", - "map_settings_include_show_archived": "Inclou els arxivats", - "map_settings_include_show_partners": "Inclou els companys", - "map_settings_only_show_favorites": "Mostra nomÊs els preferits", - "map_settings_theme_settings": "Tema del mapa", - "mark_all_as_read": "Marca-ho tot com a llegit", - "marked_all_as_read": "S’ha marcat tot com a llegit", + "map_settings_date_range_option_day": "Últimes 24 hores", + "map_settings_date_range_option_days": "Darrers {days} dies", + "map_settings_date_range_option_year": "Any passat", + "map_settings_date_range_option_years": "Darrers {years} anys", + "map_settings_dialog_title": "ConfiguraciÃŗ del mapa", + "map_settings_include_show_archived": "Incloure arxivats", + "map_settings_include_show_partners": "Incloure companys", + "map_settings_only_show_favorites": "Mostra nomÊs preferits", + "map_settings_theme_settings": "Tema del Mapa", + "map_zoom_to_see_photos": "Allunya per veure fotos", + "mark_all_as_read": "Marcar-ho tot com a llegit", + "mark_as_read": "Marcar com ha llegit", + "marked_all_as_read": "Marcat tot com a llegit", "matches": "Coincidències", - "matching_assets": "Recursos coincidents", - "media_chrome": { - "auto": "Automàtic", - "captions": "Subtítols", - "captions_off": "Desactivat", - "closed_captions": "subtítols", - "decode_error": "S’ha produït un error de descodificaciÃŗ", - "disable_captions": "Desactiva els subtítols", - "enable_captions": "Activa els subtítols", - "enter_fullscreen_mode": "Activa el mode de pantalla completa", - "exit_fullscreen_mode": "Surt del mode de pantalla completa", - "loop": "Bucle", - "media_error_description": "Un error del mitjà ha provocat l’aturada de la reproducciÃŗ. És possible que el mitjà estigui corromput o que el navegador no admeti aquest format.", - "media_loading": "càrrega de mitjans", - "mute": "Silencia", - "network_error": "S’ha produït un error de xarxa", - "network_error_description": "Un error de xarxa ha provocat que falli la baixada del mitjà.", - "not_supported_error": "Font no admesa", - "playback_rate": "Velocitat de reproducciÃŗ", - "playback_rate_current": "velocitat actual de reproducciÃŗ", - "playback_rate_value": "Velocitat de reproducciÃŗ {playbackRate}", - "playback_time": "temps de reproducciÃŗ", - "quality": "Qualitat", - "second": "segon", - "seconds": "segons", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} restant", - "unmute": "Deixa de silenciar", - "unsupported_error_description": "S’ha produït un error no admès. El servidor o la xarxa han fallat o el vostre navegador no admet aquest format.", - "video_not_loaded_unknown_time": "vídeo no carregat, temps desconegut.", - "video_player": "reproductor de vídeo", - "volume": "volum" - }, + "matching_assets": "Recursos Coincidents", "media_type": "Tipus de mitjà", "memories": "Records", - "memories_all_caught_up": "Esteu al dia", - "memories_check_back_tomorrow": "Torneu demà per a veure mÊs records", - "memories_setting_description": "Gestioneu què veieu als records", + "memories_all_caught_up": "Posat al dia", + "memories_check_back_tomorrow": "Torna demà per veure mÊs records", + "memories_setting_description": "Gestiona el que veus als teus records", "memories_start_over": "Torna a començar", - "memories_swipe_to_close": "Llisqueu per a tancar", + "memories_swipe_to_close": "Llisca per tancar", "memory": "Record", "memory_lane_title": "Línia de records {title}", "menu": "MenÃē", - "merge": "Fusiona", - "merge_people": "Fusiona persones", - "merge_people_limit": "NomÊs podeu combinar fins a 5 cares alhora", - "merge_people_prompt": "Voleu combinar aquestes persones? Aquesta acciÃŗ no es pot desfer.", - "merge_people_successfully": "Les persones s’han fusionat correctament", - "merged_people_count": "{count, plural, one {S’ha combinat # persona} other {S’han combinat # persones}}", - "minFaces": "Mínim de cares", - "minFaces_description": "El nombre mínim de cares reconegudes perquè es mostri una persona", + "merge": "Combinar", + "merge_people": "Combinar persones", + "merge_people_limit": "NomÊs pots combinar fins a 5 cares alhora", + "merge_people_prompt": "Vols combinar aquestes persones? Aquesta acciÃŗ Ês irreversible.", + "merge_people_successfully": "Persones combinades amb èxit", + "merged_people_count": "Combinades {count, plural, one {# persona} other {# persones}}", "minimize": "Minimitza", "minute": "Minut", "minutes": "Minuts", - "missing": "Pendents", + "mirror_horizontal": "Horitzontal", + "mirror_vertical": "Vertical", + "missing": "Restants", "mobile_app": "AplicaciÃŗ mÃ˛bil", - "mobile_app_download_onboarding_note": "Baixeu l’aplicaciÃŗ auxiliar per a mÃ˛bils utilitzant les segÃŧents opcions", + "mobile_app_download_onboarding_note": "Descarregar la App de mÃ˛bil fent servir les seguents opcions", "model": "Model", - "modify_date": "Modifica la data", "month": "Mes", + "monthly_title_text_date_format": "MMMM a", "more": "MÊs", - "motion": "Moviment", - "move": "Mou", - "move_off_locked_folder": "Treu de la carpeta protegida", - "move_to": "Mou a", + "move": "Moure", + "move_down": "Moure cap avall", + "move_off_locked_folder": "Moure fora de la carpeta bloquejada", + "move_to": "Moure a", "move_to_device_trash": "Mou a la paperera del dispositiu", - "move_to_lock_folder_action_prompt": "{count, plural, one {# afegit} other {# afegits}} a la carpeta protegida", - "move_to_locked_folder": "Mou a la carpeta protegida", - "move_to_locked_folder_confirmation": "Aquestes fotos i vídeos s’eliminaran de tots els àlbums i nomÊs es podran veure a la carpeta protegida", - "moved_to_trash": "S’ha mogut a la paperera", - "mute_memories": "Silencia els records", + "move_to_lock_folder_action_prompt": "{count} afegides a la carpeta protegida", + "move_to_locked_folder": "Moure a la carpeta bloquejada", + "move_to_locked_folder_confirmation": "Aquestes fotos i vídeos seran eliminades de tots els àlbums, i nomÊs podran ser vistes des de la carpeta bloquejada", + "move_up": "Puja", + "moved_to_archive": "S'han mogut {count, plural, one {# asset} other {# assets}} a l'arxiu", + "moved_to_library": "S'ha mogut {count, plural, one {# asset} other {# assets}} a la llibreria", + "moved_to_trash": "S'ha mogut a la paperera", + "multiselect_grid_edit_date_time_err_read_only": "No es pot canviar la data del fitxer(s) de nomÊs lectura, ometent", + "multiselect_grid_edit_gps_err_read_only": "No es pot canviar la localitzaciÃŗ de fitxers de nomÊs lectura, saltant", + "mute_memories": "Silenciar records", "my_albums": "Els meus àlbums", - "my_immich_description": "Copia la pàgina actual com a enllaç a My Immich", - "my_immich_title": "Enllaç a My Immich", "name": "Nom", "name_or_nickname": "Nom o sobrenom", "name_required": "El nom Ês obligatori", - "navigate": "Navega", - "navigate_to_time": "Navega a un punt en el temps", - "network_requirement_photos_upload": "Utilitza dades mÃ˛bils per a copiar les fotos al servidor", - "network_requirement_videos_upload": "Utilitza dades mÃ˛bils per a copiar els vídeos al servidor", - "network_requirements": "Requeriments de xarxa", - "network_requirements_updated": "Han canviat els requeriments de xarxa, es renicia la cua de cÃ˛pia al servidor", + "navigate": "Navegar", + "navigate_to_time": "Navegar a un punt en el temps", + "network_requirement_photos_upload": "Fes servir dades mÃ˛bils per a cÃ˛pies de seguretat de fotos", + "network_requirement_videos_upload": "Fes servir dades mÃ˛bils per a cÃ˛pies de seguretat de videos", + "network_requirements": "Requeriments de Xarxa", + "network_requirements_updated": "Han canviat els requeriments de xarxa, reiniciant la cua", "networking_settings": "Xarxes", - "networking_subtitle": "Gestioneu la configuraciÃŗ de l’endpoint del servidor", + "networking_subtitle": "Gestiona la configuraciÃŗ del endpoint del servidor", "never": "Mai", - "new_album": "Àlbum nou", - "new_api_key": "Clau de l’API nova", - "new_feature": "Funcionalitat nova", - "new_password": "Contrasenya nova", + "new_album": "Nou Àlbum", + "new_api_key": "Nova clau de l'API", + "new_date_range": "Navegar a un reng de dates", + "new_password": "Nova contrasenya", "new_person": "Persona nova", - "new_pin_code": "Codi PIN nou", - "new_pin_code_subtitle": "És la primera vegada que accediu a la carpeta protegida. Creeu un codi PIN per a accedir de manera segura a aquesta pàgina", - "new_update": "ActualitzaciÃŗ nova", - "new_user_created": "S’ha creat l’usuari nou", + "new_pin_code": "Nou codi PIN", + "new_pin_code_subtitle": "Aquesta Ês la primera vegada que accedeixes a la carpeta bloquejada. Crea una codi PIN i accedeix de manera segura a aquesta pàgina", + "new_timeline": "Nova Línia de Temps", + "new_update": "Nova actualitzaciÃŗ", + "new_user_created": "Nou usuari creat", "new_version_available": "NOVA VERSIÓ DISPONIBLE", "newest_first": "El mÊs nou primer", "next": "SegÃŧent", "next_memory": "SegÃŧent record", "no": "No", - "no_albums_found": "No s’ha trobat cap àlbum", - "no_albums_message": "Creeu un àlbum per a organitzar les vostres fotos i vídeos", - "no_albums_with_name_yet": "Sembla que encara no teniu cap àlbum amb aquest nom.", - "no_albums_yet": "Sembla que encara no teniu cap àlbum.", - "no_archived_assets_message": "Arxiveu fotos i vídeos per a amagar-los de la visualitzaciÃŗ Fotos", - "no_assets_message": "Feu clic per a pujar la vostra primera foto", - "no_assets_to_show": "No hi ha recursos a mostrar", - "no_cast_devices_found": "No s’ha trobat cap dispositiu on emetre", - "no_checksum_local": "No hi ha cap suma de verificaciÃŗ disponible; no s’han pogut obtenir els recursos locals", - "no_checksum_remote": "No hi ha cap suma de verificaciÃŗ disponible; no s’ha pogut obtenir el recurs remot", - "no_devices": "No hi ha cap dispositiu autoritzat", - "no_duplicates_found": "No s’ha trobat cap duplicat.", - "no_exif_info_available": "No hi ha informaciÃŗ Exif disponible", - "no_explore_results_message": "Pugeu mÊs fotos per a explorar la vostra col¡lecciÃŗ.", - "no_favorites_message": "Afegiu preferits per a trobar les vostres millors fotos i vídeos ràpidament", - "no_libraries_message": "Creeu una biblioteca externa per a veure les vostres fotos i vídeos", - "no_local_assets_found": "No s’ha trobat cap recurs local amb aquesta suma de verificaciÃŗ", - "no_locked_photos_message": "Les fotos i vídeos de la carpeta protegida carpeta estan amagades i no es mostraran quan navegueu o cerqueu a la vostra biblioteca.", + "no_actions_added": "Encara no s'han afegit accions", + "no_albums_found": "No s'han trobat àlbums", + "no_albums_message": "Creeu un àlbum per organitzar les vostres fotos i vídeos", + "no_albums_with_name_yet": "Sembla que encara no tens cap àlbum amb aquest nom.", + "no_albums_yet": "Sembla que encara no tens cap àlbum.", + "no_archived_assets_message": "Arxiveu fotos i vídeos per ocultar-los de Fotos", + "no_assets_message": "Fes clic per pujar la teva primera foto", + "no_assets_to_show": "No hi ha elements per mostrar", + "no_cast_devices_found": "No s'han trobat dispositius per transmetre", + "no_checksum_local": "Cap checksum disponible - no s'han pogut carregar els recursos locals", + "no_checksum_remote": "Cap checksum disponible - no s'ha pogut obtenir el recurs remot", + "no_configuration_needed": "No cal configuraciÃŗ", + "no_devices": "No hi ha dispositius autoritzats", + "no_duplicates_found": "No s'han trobat duplicats.", + "no_exif_info_available": "No hi ha informaciÃŗ d'exif disponible", + "no_explore_results_message": "Penja mÊs fotos per explorar la teva col¡lecciÃŗ.", + "no_favorites_message": "Afegiu preferits per trobar les millors fotos i vídeos a l'instant", + "no_filters_added": "Encara no s'han afegit filtres", + "no_libraries_message": "Creeu una llibreria externa per veure les vostres fotos i vídeos", + "no_local_assets_found": "No s'ha trobat cap recurs local amb aquest checksum", + "no_location_set": "No s'ha definit cap ubicaciÃŗ", + "no_locked_photos_message": "Les fotos i vídeos d'aquesta carpeta estan ocultes, i no es mostraran a mesura que navegues o cerques a la teva biblioteca.", "no_name": "Sense nom", - "no_notifications": "No hi ha cap notificaciÃŗ", - "no_people_found": "No s’ha trobat cap persona coincident", - "no_places": "No hi ha cap lloc", - "no_remote_assets_found": "No s’ha trobat cap recurs remot amb aquesta suma de verificaciÃŗ", - "no_results": "No hi ha resultats", - "no_results_description": "Proveu amb un sinÃ˛nim o una paraula clau mÊs general", - "no_shared_albums_message": "Creeu un àlbum per a compartir fotos i vídeos amb persones de la vostra xarxa", - "no_steps": "Encara no s’ha afegit cap pas", + "no_notifications": "No hi ha notificacions", + "no_people_found": "No s'han trobat coincidències de persones", + "no_places": "No hi ha llocs", + "no_remote_assets_found": "No s'ha trobat cap recurs remot amb aquest checksum", + "no_results": "Sense resultats", + "no_results_description": "Proveu un sinÃ˛nim o una paraula clau mÊs general", + "no_shared_albums_message": "Creeu un àlbum per compartir fotos i vídeos amb persones a la vostra xarxa", + "no_uploads_in_progress": "Cap pujada en progrÊs", "none": "Cap", "not_allowed": "No permès", - "not_available": "N/D", + "not_available": "N/A", "not_in_any_album": "En cap àlbum", "not_selected": "No seleccionat", - "not_set": "No definit", "notes": "Notes", - "nothing_here_yet": "Encara no hi ha res", - "notification_backup_reliability": "Activeu les notificacions per a millorar la fiabilitat de les cÃ˛pies al servidor en segon pla", - "notification_enabled_list_tile_content": "L’Immich utilitza les notificacions per a les cÃ˛pies al servidor en segon pla. Gestioneu-les a la configuraciÃŗ del vostre dispositiu.", - "notification_enabled_list_tile_open_button": "Obre la configuraciÃŗ", - "notification_enabled_list_tile_title": "Notificacions activades", - "notification_permission_dialog_content": "Per a activar les notificacions, aneu a ConfiguraciÃŗ i seleccioneu Permet.", - "notification_permission_list_tile_content": "Concediu el permís per a activar les notificacions.", + "nothing_here_yet": "No hi ha res encara", + "notification_permission_dialog_content": "Per activar les notificacions, aneu a ConfiguraciÃŗ i seleccioneu permet.", + "notification_permission_list_tile_content": "Atorga permís per a activar les notificacions.", "notification_permission_list_tile_enable_button": "Activa les notificacions", "notification_permission_list_tile_title": "Permís de notificacions", "notification_toggle_setting_description": "Activa les notificacions per correu electrÃ˛nic", "notifications": "Notificacions", "notifications_setting_description": "Gestiona les notificacions", "oauth": "OAuth", - "obtainium_configurator": "Configurador de l’Obtainium", - "obtainium_configurator_instructions": "Utilitzeu l’Obtainium per a instal¡lar i actualitzar l’aplicaciÃŗ d’Android directament del llançament a GitHub de l’Immich. Creeu una clau de l’API i seleccioneu una variant per a crear un enllaç de configuraciÃŗ de l’Obtainium", + "obtainium_configurator": "Configurador Obtainium", + "obtainium_configurator_instructions": "Utilitza Obtainium per instal¡lar una actualitzaciÃŗ a la app directament des de Github-Immich. Crear una clau API i seleccionar una variant per crear un enllaç a la configuraciÃŗ Obtainium", "ocr": "OCR", - "ocr_body": "Ara l’Immich llegeix el text dins de les vostres fotos, de manera que podeu cercar-les pel que hi ha escrit.", - "ocr_title": "Cerqueu text dins de les vostres fotos", - "official_immich_resources": "Recursos oficials de l’Immich", + "official_immich_resources": "Recursos oficials d'Immich", "offline": "Fora de línia", "offset": "Diferència", - "ok": "D’acord", - "oldest_first": "El mÊs antic primer", + "ok": "D'acord", + "oldest_first": "El mÊs vell primer", "on_this_device": "En aquest dispositiu", - "onboarding": "IntroducciÃŗ", - "onboarding_locale_description": "Seleccioneu la vostra llengua preferida. Podeu canviar-la mÊs tard a la configuraciÃŗ.", - "onboarding_privacy_description": "Les segÃŧents funcionalitats opcionals depenen de serveis externs i es poden desactivar en qualsevol moment a la configuraciÃŗ.", - "onboarding_server_welcome_description": "Configureu la vostra instància amb alguns paràmetres comuns.", - "onboarding_theme_description": "Trieu un tema de colors per a la vostra instància. Podeu canviar-lo mÊs tard a la configuraciÃŗ.", + "onboarding": "IncorporaciÃŗ", + "onboarding_locale_description": "Tria el teu llenguatge preferit. Pots canviar aquesta opciÃŗ mes tard a la configuraciÃŗ.", + "onboarding_privacy_description": "Les segÃŧents funcions (opcionals) depenen de serveis externs i poden desactivarse en qualsevol moment des de la configuraciÃŗ.", + "onboarding_server_welcome_description": "Configurem la instància amb alguns paràmetres comuns.", + "onboarding_theme_description": "Trieu un tema de color per a la vostra instància. Podeu canviar-ho mÊs endavant a la vostra configuraciÃŗ.", "onboarding_user_welcome_description": "Comencem!", - "onboarding_welcome_user": "Us donem la benvinguda, {user}", + "onboarding_welcome_user": "Benvingut, {user}", "online": "En línia", - "only_favorites": "NomÊs els preferits", - "open": "Obre", - "open_calendar": "Obre el calendari", + "only_favorites": "NomÊs preferits", + "open": "Obrir", + "open_calendar": "Obrir el calendari", "open_in_browser": "Obre al navegador", - "open_in_immich_body": "Definiu l’Immich com a la vostra galeria de fotos d’Android per a obrir les fotos directament d’altres aplicacions.", - "open_in_immich_title": "Obre les fotos a l’Immich", - "open_in_map_view": "Obre a la visualitzaciÃŗ del mapa", - "open_in_openstreetmap": "Obre a l’OpenStreetMap", - "open_the_search_filters": "Obre els filtres de cerca", + "open_in_map_view": "Obrir a la vista del mapa", + "open_in_openstreetmap": "Obre a OpenStreetMap", + "open_the_search_filters": "Obriu els filtres de cerca", "options": "Opcions", "or": "o", - "organize_into_albums": "Organitza en àlbums", - "organize_into_albums_description": "Posa les fotos existents en àlbums utilitzant la configuraciÃŗ de sincronitzaciÃŗ actual", - "organize_your_library": "Organitza la biblioteca", - "orientation": "OrientaciÃŗ", + "organize_into_albums": "Organitzar en àlbums", + "organize_into_albums_description": "Posar fotos existents en àlbums utilitzant la configuraciÃŗ de sincronitzaciÃŗ actual", + "organize_your_library": "Organitzeu la llibreria", "original": "original", "other": "Altres", "other_devices": "Altres dispositius", + "other_entities": "Altres entitats", "other_variables": "Altres variables", - "owned": "Propis", + "owned": "Propi", "owner": "Propietari", "page": "Pàgina", - "partner": "Company", + "partner": "Company/a", "partner_can_access": "{partner} hi tÊ accÊs", - "partner_can_access_assets": "Totes les vostres fotos i vídeos excepte les arxivades i les suprimides", - "partner_can_access_location": "UbicaciÃŗ on s’han fet les fotos", - "partner_list_user_photos": "Fotos de l’usuari {user}", - "partner_page_empty_message": "Les vostres fotos encara no s’han compartit amb cap company.", + "partner_can_access_assets": "Totes les vostres fotos i vídeos excepte les arxivades i eliminades", + "partner_can_access_location": "UbicaciÃŗ en què s'han fet les fotos", + "partner_list_user_photos": "fotos de {user}", + "partner_list_view_all": "Veure tot", + "partner_page_empty_message": "Les teves fotos encara no estan compartides amb cap company.", "partner_page_no_more_users": "No hi ha mÊs usuaris a afegir", - "partner_page_select_partner": "Selecciona un company", - "partner_page_stop_sharing_content": "{partner} ja no podrà accedir a les vostres fotos.", + "partner_page_partner_add_failed": "No s'ha pogut afegir el company", + "partner_page_select_partner": "Escull company", + "partner_page_shared_to_title": "Compartit amb", + "partner_page_stop_sharing_content": "{partner} ja no podrà accedir a les teves fotos.", "partner_sharing": "ComparticiÃŗ amb companys", "partners": "Companys", "password": "Contrasenya", "password_does_not_match": "La contrasenya no coincideix", - "password_required": "La contrasenya Ês obligatÃ˛ria", - "password_reset_success": "S’ha restablert la contrasenya correctament", + "password_required": "Contrasenya requerida", + "password_reset_success": "El restabliment de la contrasenya ha estat correcte", "past_durations": { - "days": "{days, plural, one {El darrer dia} other {Els darrers # dies}}", - "hours": "{hours, plural, one {La darrera hora} other {Les darreres # hores}}", - "years": "{years, plural, one {L’any passat} other {Els darrers # anys}}" + "days": "{days, plural, one {El dia anterior} other {Els # dies anteriors}}", + "hours": "{hours, plural, one {L'Ãēltima hora} other {Les darreres # hours}}", + "years": "{years, plural, one {L'any passat} other {Els passats # anys}}" }, - "path": "Camí", + "path": "Ruta", "pattern": "PatrÃŗ", "pause": "Pausa", "pause_memories": "Pausa els records", - "paused": "Pausat", + "paused": "En pausa", "pending": "Pendent", "people": "Persones", - "people_edits_count": "{count, plural, one {S’ha editat # persona} other {S’han editat # persones}}", - "people_feature_description": "Exploreu les fotos i els vídeos agrupats per persona", - "permanent_deletion_warning": "Advertència de supressiÃŗ permanent", - "permanent_deletion_warning_setting_description": "Mostra una advertència quan se suprimeixin recursos permanentment", - "permanently_delete": "Suprimeix permanentment", - "permanently_delete_assets_count": "Suprimeix permanentment {count, plural, one {el recurs} other {els recursos}}", - "permanently_delete_assets_prompt": "Segur que voleu suprimir permanentment {count, plural, one {aquest recurs?} other {aquests # recursos?}} AixÃ˛ tambÊ {count, plural, one {el} other {els}} suprimirà dels àlbums.", - "permanently_deleted_asset": "S’ha suprimit el recurs permanentment", - "permanently_deleted_assets_count": "{count, plural, one {S’ha suprimit # recurs} other {S’han suprimit # recursos}} permanentment", + "people_edits_count": "{count, plural, one {# persona editada} other {# persones editades}}", + "people_feature_description": "Explorar fotos i vídeos agrupades per persona", + "people_selected": "{count, plural, one {# persona seleccionada} other {# persones seleccionades}}", + "people_sidebar_description": "Mostrar un enllaç a Persones a la barra lateral", + "permanent_deletion_warning": "Avís d'eliminaciÃŗ permanent", + "permanent_deletion_warning_setting_description": "Mostrar un avís quan s'eliminin els elements permanentment", + "permanently_delete": "Eliminar permanentment", + "permanently_delete_assets_count": "Eliminar permanentment {count, plural, one {l'element} other {els elements}}", + "permanently_delete_assets_prompt": "Esteu segur que voleu suprimir permanentment {count, plural, one {aquest recurs?} other {aquests # recursos?}} AixÃ˛ tambÊ {count, plural, one {el} other {els}} suprimirà del seu àlbum.", + "permanently_deleted_asset": "Element eliminat permanentment", + "permanently_deleted_assets_count": "{count, plural, one {S'ha eliminat # element} other {S'han eliminat # elements}} permanentment", "permission": "Permís", - "permission_empty": "El vostre permís no hauria d’estar buit", + "permission_empty": "El seu permís no hauria d'estar buit", + "permission_onboarding_back": "Torna", + "permission_onboarding_continue_anyway": "Continua de totes maneres", + "permission_onboarding_get_started": "Comença", + "permission_onboarding_go_to_settings": "Ves a la configuraciÃŗ", + "permission_onboarding_permission_denied": "S'ha denegat el permís. Per utilitzar Immich, concediu permisos de fotos i vídeos a ConfiguraciÃŗ.", + "permission_onboarding_permission_granted": "Permís concedit! Tot a punt.", + "permission_onboarding_permission_limited": "Permís limitat. Per a permetre que Immich faci cÃ˛pies de seguretat i gestioni tota la col¡lecciÃŗ de la galeria, concediu permisos de fotos i vídeos a ConfiguraciÃŗ.", + "permission_onboarding_request": "Immich requereix permís per veure les vostres fotos i vídeos.", "person": "Persona", - "person_age_months": "{months, plural, one {# mes} other {# mesos}}", - "person_age_year_months": "1 any i {months, plural, one {# mes} other {# mesos}}", - "person_age_years": "{years, plural, one {# any} other {# anys}}", - "person_birthdate": "Nascut el {date}", - "person_hidden": "{name}{hidden, select, true { (amagat)} other {}}", + "person_age_months": "{months, plural, one {# mes} other {# mesos}} d'antiguitat", + "person_age_year_months": "1 any, {months, plural, one {# mes} other {# mesos}} d'antiguitat", + "person_age_years": "{years, plural, other {# anys}} d'antiguitat", + "person_birthdate": "Nascut a {date}", + "person_hidden": "{name}{hidden, select, true { (ocultat)} other {}}", "person_recognized": "Persona reconeguda", - "photo_shared_all_users": "Sembla que heu compartit les vostres fotos amb tots els usuaris o no teniu cap usuari amb qui compartir-les.", + "person_selected": "Persona seleccionada", + "photo_shared_all_users": "Sembla que has compartit les teves fotos amb tots els usuaris o no tens cap usuari amb qui compartir-les.", "photos": "Fotos", "photos_and_videos": "Fotos i vídeos", - "photos_from_previous_years": "Fotos d’anys anteriors", - "pick_a_location": "Tria una ubicaciÃŗ", - "pick_custom_range": "Interval personalitzat", - "pick_date_range": "Seleccioneu un interval de dates", - "pin_code_changed_successfully": "S’ha canviat el codi PIN correctament", - "pin_code_reset_successfully": "S’ha restablert el codi PIN correctament", - "pin_code_setup_successfully": "S’ha configurat el codi PIN correctament", - "pin_verification": "VerificaciÃŗ del codi PIN", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", + "photos_from_previous_years": "Fotos d'anys anteriors", + "photos_only": "NomÊs fotos", + "pick_a_location": "Triar una ubicaciÃŗ", + "pick_custom_range": "Rang personalitzat", + "pick_date_range": "Seleccioni un rang de dates", + "pin_code_changed_successfully": "Codi PIN canviat correctament", + "pin_code_reset_successfully": "S'ha restablert correctament el codi PIN", + "pin_code_setup_successfully": "S'ha configurat correctament un codi PIN", + "pin_verification": "VerificaciÃŗ de codi PIN", "place": "Lloc", "places": "Llocs", - "places_count": "{count, plural, one {{count, number} lloc} other {{count, number} llocs}}", + "places_count": "{count, plural, one {{count, number} Lloc} other {{count, number} Llocs}}", "play": "Reprodueix", - "play_memories": "Reprodueix els records", - "play_motion_photo": "Reprodueix la foto en moviment", - "play_or_pause_video": "Reprodueix o pausa el vídeo", - "play_original_video": "Reprodueix el vídeo original", - "play_original_video_setting_description": "Prefereix la reproducciÃŗ dels vídeos originals abans que el vídeo transcodificat. Si el recurs original no Ês compatible, Ês possible que no es reprodueixi correctament.", - "play_transcoded_video": "Reprodueix el vídeo transcodificat", - "please_auth_to_access": "Autentiqueu-vos per a accedir-hi", - "plugin_method_filter_type": "Filtre", - "plugin_method_filter_type_description": "Aquest mètode pot filtrar esdeveniments i evitar condicionalment que s’executin els passos segÃŧents", + "play_memories": "Reproduir records", + "play_motion_photo": "Reproduir Fotos en Moviment", + "play_or_pause_video": "Reproduir o posar en pausa el vídeo", + "play_original_video": "Veure el video original", + "play_original_video_setting_description": "Preferir la reproducciÃŗ del video original sobre el video recodificat. Si el video original no es compatible potser no es reprodueixi correctament.", + "play_transcoded_video": "Veure el video recodificat", + "please_auth_to_access": "Per favor, autentica't per accedir", "port": "Port", - "preferences_settings_subtitle": "Gestiona les preferències de l’aplicaciÃŗ", + "preferences_settings_subtitle": "Gestiona les preferències de l'aplicaciÃŗ", "preferences_settings_title": "Preferències", - "preparing": "S’està preparant", - "preset": "Predefinit", + "preparing": "Preparant", + "preset": "Preestablert", "preview": "PrevisualitzaciÃŗ", "previous": "Anterior", - "previous_memory": "Record anterior", - "previous_or_next_day": "Dia segÃŧent/anterior", - "previous_or_next_month": "Mes segÃŧent/anterior", - "previous_or_next_photo": "Foto segÃŧent/anterior", - "previous_or_next_year": "Any segÃŧent/anterior", + "previous_memory": "MemÃ˛ria anterior", + "previous_or_next_day": "Dia endavant/enrere", + "previous_or_next_month": "Mes endavant/enrere", + "previous_or_next_photo": "Foto endavant/enrere", + "previous_or_next_year": "Any endavant/enrere", "primary": "Primària", "privacy": "Privacitat", "profile": "Perfil", "profile_drawer_app_logs": "Registres", + "profile_drawer_client_server_up_to_date": "El client i el servidor estan actualitzats", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Mode de nomÊs lectura. Mantingueu premuda la icona de l’avatar d’usuari per a sortir-ne.", - "profile_image_of_user": "Imatge de perfil de l’usuari {user}", - "profile_picture_set": "S’ha definit la imatge de perfil.", - "projection_type": "Tipus de projecciÃŗ", + "profile_drawer_readonly_mode": "Mode nomÊs lectura. Feu pulsaciÃŗ llarga a la icona de l'avatar d'usuari per sortir.", + "profile_image_of_user": "Imatge de perfil de {user}", + "profile_picture_set": "Imatge de perfil configurada.", "public_album": "Àlbum pÃēblic", - "public_share": "ComparticiÃŗ pÃēblica", - "purchase_account_info": "Col¡laborador", - "purchase_activated_subtitle": "Gràcies per donar suport a l’Immich i al programari de codi obert", + "public_share": "Compartit pÃēblicament", + "purchase_account_info": "Contribuent", + "purchase_activated_subtitle": "Gràcies per donar suport a Immich i al programari de codi obert", "purchase_activated_time": "Activat el {date}", - "purchase_activated_title": "La vostra clau s’ha activat correctament", - "purchase_button_activate": "Activa", - "purchase_button_buy": "Compra", - "purchase_button_buy_immich": "Compra l’Immich", - "purchase_button_never_show_again": "No ho tornis a mostrar", - "purchase_button_reminder": "Recorda-m’ho d’aquí a 30 dies", + "purchase_activated_title": "La teva clau s'ha activat correctament", + "purchase_button_activate": "Activar", + "purchase_button_buy": "Comprar", + "purchase_button_buy_immich": "Compra Immich", + "purchase_button_never_show_again": "No mostrar mai mÊs", + "purchase_button_reminder": "Recordar en 30 dies", "purchase_button_remove_key": "Elimina la clau", - "purchase_button_select": "Selecciona", - "purchase_failed_activation": "No s’ha pogut activar! Reviseu el vostre correu electrÃ˛nic per a obtenir la clau del producte correcta!", + "purchase_button_select": "Seleccioneu", + "purchase_failed_activation": "No s'ha pogut activar! Si us plau, comproveu el vostre correu electrÃ˛nic per trobar la clau de producte correcta!", "purchase_individual_description_1": "Per a un particular", - "purchase_individual_description_2": "Estatus de col¡laborador", + "purchase_individual_description_2": "Estat de la contribuciÃŗ", "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Teniu una clau del producte? Introduïu-la a continuaciÃŗ", + "purchase_input_suggestion": "Tens una clau de producte? Introduïu la clau a continuaciÃŗ", + "purchase_license_subtitle": "Compra Immich per donar suport al desenvolupament continuat del servei", "purchase_lifetime_description": "Compra de per vida", "purchase_option_title": "OPCIONS DE COMPRA", - "purchase_panel_info_1": "Desenvolupar l’Immich requereix molt de temps i esforç i tenim enginyers treballant-hi a temps complet per a fer-lo tan bo com sigui possible. La nostra missiÃŗ Ês que el programari de codi obert i les pràctiques empresarials ètiques esdevinguin una font d’ingressos sostenible per als desenvolupadors i creïn un ecosistema que respecti la privacitat amb alternatives reals als serveis al nÃēvol explotadors.", - "purchase_panel_info_2": "Com que estem compromesos a no afegir murs de pagament, aquesta compra no us atorgarà cap funcionalitat addicional a l’Immich. Confiem en els usuaris com vÃŗs perquè doneu suport al desenvolupament continuat de l’Immich.", - "purchase_panel_title": "DÃŗna suport al projecte", + "purchase_panel_info_1": "Crear Immich requereix molt de temps i esforç, tenim enginyers a temps complet treballant-hi per fer-ho tan bo com sigui possible. La nostra missiÃŗ Ês que el programari de codi obert i les pràctiques empresarials ètiques es converteixin en una font d'ingressos sostenible per als desenvolupadors i creïn un ecosistema que respecti la privacitat amb alternatives reals als serveis cloud explotadors.", + "purchase_panel_info_2": "Estem compromesos a no afegir murs de pagament, aquesta compra no us otorgarà cap funciÃŗ addicional a Immich. Confiem en usuaris com tu per donar suport al desenvolupament continuat d'Immich.", + "purchase_panel_title": "Donar suport al projecte", "purchase_per_server": "Per servidor", "purchase_per_user": "Per usuari", "purchase_remove_product_key": "Elimina la clau del producte", - "purchase_remove_product_key_prompt": "Segur que voleu eliminar la clau del producte?", - "purchase_remove_server_product_key": "Elimina la clau del producte del servidor", - "purchase_remove_server_product_key_prompt": "Segur que voleu eliminar la clau del producte del servidor?", + "purchase_remove_product_key_prompt": "Esteu segur que voleu eliminar la clau del producte?", + "purchase_remove_server_product_key": "Elimina la clau de producte del servidor", + "purchase_remove_server_product_key_prompt": "Esteu segur que voleu eliminar la clau de producte del servidor?", "purchase_server_description_1": "Per a tot el servidor", - "purchase_server_description_2": "Estatus de col¡laborador", + "purchase_server_description_2": "Estat del contribuent", "purchase_server_title": "Servidor", - "purchase_settings_server_activated": "La clau del producte del servidor la gestiona l’administrador", - "query_asset_id": "Consulta l’identificador del recurs", - "queue_status": "S’està afegint a la cua {count}/{total}", - "rate_asset": "Puntua el recurs", - "rating": "PuntuaciÃŗ en estrelles", - "rating_clear": "Esborra la puntuaciÃŗ", - "rating_count": "{count, plural, =0 {No puntuat} one {# estrella} other {# estrelles}}", - "rating_description": "Mostra la puntuaciÃŗ Exif al tauler d’informaciÃŗ", - "reaction_options": "Opcions de la reacciÃŗ", - "read_changelog": "Mostra el registre de canvis", - "readonly_mode_disabled": "S’ha desactivat el mode de nomÊs lectura", - "readonly_mode_enabled": "S’ha activat el mode de nomÊs lectura", + "purchase_settings_server_activated": "La clau de producte del servidor la gestiona l'administrador", + "query_asset_id": "Consulta d'identificaciÃŗ d'actius", + "queue_status": "En cua {count}/{total}", + "rate_asset": "Valorar Recurs", + "rating": "ValoraciÃŗ", + "rating_clear": "Esborrar valoraciÃŗ", + "rating_count": "{count, plural, =0 {Unrated} one {# estrella} other {# estrelles}}", + "rating_description": "Mostrar la valoraciÃŗ EXIF al panell d'informaciÃŗ", + "reaction_options": "Opcions de reacciÃŗ", + "read_changelog": "Llegeix el registre de canvis", + "readonly_mode_disabled": "Mode de nomÊs lectura desactivat", + "readonly_mode_enabled": "Mode de nomÊs lectura activat", "ready_for_upload": "Llest per a pujar", - "reassign": "Reassigna", - "reassigned_assets_to_existing_person": "{count, plural, one {S’ha reassignat # recurs} other {S’han reassignat # recursos}} a {name, select, null {una persona existent} other {{name}}}", - "reassigned_assets_to_new_person": "{count, plural, one {S’ha reassignat # recurs} other {S’han reassignat # recursos}} a una persona nova", - "reassing_hint": "Assigna els recursos seleccionats a una persona existent", - "recent": "Recents", + "reassign": "Reassignar", + "reassigned_assets_to_existing_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a {name, select, null {una persona existent} other {{name}}}", + "reassigned_assets_to_new_person": "{count, plural, one {S'ha reassignat # recurs} other {S'han reassignat # recursos}} a una persona nova", + "reassing_hint": "Assignar els elements seleccionats a una persona existent", + "recent": "Recent", + "recent_albums": "Àlbums recents", "recent_searches": "Cerques recents", "recently_added": "Afegit recentment", - "recently_added_body": "Accediu directament a tot el que heu afegit darrerament en una pàgina dedicada.", - "recently_added_description": "Exploreu els vostres recursos ordenats per la data de pujada a l’Immich", - "recently_added_page_title": "Afegits recentment", - "recently_added_title": "Afegits recentment", - "recently_taken": "Capturada recentment", - "refresh": "Actualitza", - "refresh_encoded_videos": "Actualitza els vídeos codificats", - "refresh_faces": "Actualitza les cares", - "refresh_metadata": "Actualitza les metadades", - "refresh_thumbnails": "Actualitza les miniatures", - "refreshed": "S’ha actualitzat", - "refreshing_encoded_video": "S’està actualitzant el vídeo codificat", - "refreshing_faces": "S’estan actualitzant les cares", - "refreshing_metadata": "S’estan actualitzant les metadades", - "regenerating_thumbnails": "S’estan regenerant les miniatures", + "recently_added_page_title": "Afegit recentment", + "recently_taken": "Fet recentment", + "recently_taken_page_title": "Fet recentment", + "refresh": "Actualitzar", + "refresh_encoded_videos": "Actualitza vídeos codificats", + "refresh_faces": "Actualitzar cares", + "refresh_metadata": "Actualitzar les metadades", + "refresh_thumbnails": "Actualitzar la miniatura", + "refreshed": "Actualitzat", + "refreshes_every_file": "Torna a llegir tots els fitxers existents i nous", + "refreshing_encoded_video": "S'està actualitzant el vídeo codificat", + "refreshing_faces": "Refrescant cares", + "refreshing_metadata": "Actualitzant les metadades", + "regenerating_thumbnails": "Regenerant les miniatures", "remote": "Remot", - "remote_assets": "Recursos remots", - "remote_media_summary": "Resum de mitjans remots", - "remove": "Elimina", - "remove_assets_album_confirmation": "Segur que voleu eliminar {count, plural, one {# recurs} other {# recursos}} de l’àlbum?", - "remove_assets_shared_link_confirmation": "Segur que voleu eliminar {count, plural, one {# recurs} other {# recursos}} d’aquest enllaç compartit?", - "remove_assets_title": "Voleu eliminar els recursos?", - "remove_custom_date_range": "Suprimeix l’interval de dates personalitzat", - "remove_filter": "Elimina el filtre", - "remove_from_album": "Elimina de l’àlbum", - "remove_from_album_action_prompt": "{count} eliminats de l’àlbum", - "remove_from_favorites": "Elimina dels preferits", - "remove_from_lock_folder_action_prompt": "{count} eliminats de la carpeta protegida", - "remove_from_locked_folder": "Elimina de la carpeta protegida", - "remove_from_locked_folder_confirmation": "Segur que voleu moure aquestes fotos i vídeos fora de la carpeta bloquejada? Seran visibles a la vostra biblioteca.", - "remove_from_shared_link": "Elimina de l’enllaç compartit", - "remove_memory": "Elimina el record", - "remove_photo_from_memory": "Elimina la foto d’aquest record", - "remove_tag": "Elimina l’etiqueta", - "remove_user": "Elimina l’usuari", - "removed_api_key": "S’ha suprimit la clau de l’API: {name}", - "removed_from_archive": "S’ha eliminat de l’arxiu", - "removed_from_favorites": "S’ha eliminat dels preferits", + "remote_assets": "Recursos Remots", + "remote_media_summary": "Resum de Mitjans Remots", + "remove": "Eliminar", + "remove_assets_album_confirmation": "Confirmes que vols eliminar {count, plural, one {# recurs} other {# recursos}} de l'àlbum?", + "remove_assets_shared_link_confirmation": "Esteu segur que voleu eliminar {count, plural, one {# recurs} other {# recursos}} d'aquest enllaç compartit?", + "remove_assets_title": "Eliminar els elements?", + "remove_custom_date_range": "Elimina l'interval de dates personalitzat", + "remove_deleted_assets": "Suprimeix fitxers fora de línia", + "remove_from_album": "Treu de l'àlbum", + "remove_from_album_action_prompt": "{count} eliminats de l'àlbum", + "remove_from_favorites": "Eliminar dels preferits", + "remove_from_lock_folder_action_prompt": "{count} eliminades de la carpeta protegida", + "remove_from_locked_folder": "Elimina de la carpeta bloquejada", + "remove_from_locked_folder_confirmation": "Segur que vols moure aquestes fotos i vídeos fora de la carpeta bloquejada? Seran visibles a la teva biblioteca.", + "remove_from_shared_link": "Eliminar de l'enllaç compartit", + "remove_memory": "Eliminar memÃ˛ria", + "remove_photo_from_memory": "Traieu la foto d'aquesta memÃ˛ria", + "remove_tag": "Elimina l'etiqueta", + "remove_url": "Eliminar URL", + "remove_user": "Eliminar l'usuari", + "removed_api_key": "Eliminada la clau d'API: {name}", + "removed_from_archive": "Eliminat de l'arxiu", + "removed_from_favorites": "Eliminat dels preferits", "removed_from_favorites_count": "{count, plural, other {# eliminats}} dels preferits", - "removed_memory": "S’ha eliminat la memÃ˛ria", - "removed_tagged_assets": "S’ha eliminat l’etiqueta {count, plural, one {d’# recurs} other {de # recursos}}", - "rename": "Canvia de nom", + "removed_memory": "MemÃ˛ria esborrada", + "removed_photo_from_memory": "Eliminat foto de la memÃ˛ria", + "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# actiu} other {# actius}}", + "rename": "Canviar nom", + "repair": "ReparaciÃŗ", + "repair_no_results_message": "Els fitxers sense seguiment o que falten es mostraran aquí", + "replace_with_upload": "Substitueix amb una pujada", "repository": "Repositori", "require_password": "Requereix contrasenya", - "rescan": "Torna a escanejar", - "reset": "Restableix", - "reset_password": "Restableix la contrasenya", - "reset_people_visibility": "Restableix la visibilitat de les persones", - "reset_pin_code": "Restableix el codi PIN", - "reset_pin_code_description": "Si heu oblidat el codi PIN, podeu contactar amb l’administrador del servidor per a restablir-lo", - "reset_pin_code_with_password": "Sempre podeu restablir el codi PIN amb la vostra contrasenya", - "reset_sqlite": "Restableix la base de dades SQLite", - "reset_sqlite_clear_app_data": "Esborra les dades", - "reset_sqlite_confirmation": "Segur que voleu esborrar les dades de l’aplicaciÃŗ? S’eliminarà tota la configuraciÃŗ i es tancarà la sessiÃŗ.", - "reset_sqlite_confirmation_note": "Nota: Caldrà que reinicieu l’aplicaciÃŗ desprÊs d’esborrar-les.", - "reset_sqlite_done": "S’han esborrat les dades de l’aplicaciÃŗ. Reinicieu l’Immich i torneu a iniciar la sessiÃŗ.", - "reset_sqlite_success": "S’ha restablert la base de dades correctament", - "reset_to_default": "Restableix els valors per defecte", + "require_user_to_change_password_on_first_login": "Demanar que l'usuari canviï la contrasenya en el primer inici de sessiÃŗ", + "rescan": "Tornar a escanejar", + "reset": "Restablir", + "reset_password": "Restablir contrasenya", + "reset_people_visibility": "Restablir la visibilitat de les persones", + "reset_pin_code": "Restablir el codi PIN", + "reset_pin_code_description": "Si has oblidat el teu codi PIN, pots contactar amb l'administrador del servidor per a reiniciar-lo", + "reset_pin_code_success": "Codi PIN reiniciat correctament", + "reset_pin_code_with_password": "Sempre pots reiniciar el codi PIN amb la teva contrasenya", + "reset_sqlite": "Reiniciar base de dades SQLite", + "reset_sqlite_clear_app_data": "Netejar dada", + "reset_sqlite_confirmation": "Segur que vols esborrar les dades de l'aplicaciÃŗ? AixÃ˛ eliminarà tota la configuraciÃŗ i tancarà la sessiÃŗ.", + "reset_sqlite_confirmation_note": "Nota: Hauràs de reiniciar l'app desprÊs d'eliminar.", + "reset_sqlite_done": "Les dades de l'app s'han netejat. Si us plau, reinicia l'app Immich i inicia sessiÃŗ de nou.", + "reset_sqlite_success": "S'ha reiniciat la base de dades correctament", + "reset_to_default": "Restableix els valors predeterminats", "resolution": "ResoluciÃŗ", - "resolve_duplicates": "Resol els duplicats", - "resolved_all_duplicates": "S’han resolt tots els duplicats", - "restore": "Restaura", - "restore_all": "Restaura-ho tot", - "restore_user": "Restaura l’usuari", - "restored_asset": "S’ha restaurat el recurs", - "resume": "Reprèn", - "resume_paused_jobs": "Reprèn {count, plural, one {# tasca pausada} other {# tasques pausades}}", - "review_duplicates": "Revisa els duplicats", - "review_large_files": "Revisa els fitxers grans", + "resolve_duplicates": "Resoldre duplicats", + "resolved_all_duplicates": "Tots els duplicats resolts", + "restore": "Recupera", + "restore_all": "Restaurar-ho tot", + "restore_trash_action_prompt": "{count} recuperats de la paperera", + "restore_user": "Restaurar l'usuari", + "restored_asset": "Element restaurat", + "resume": "Reprendre", + "resume_paused_jobs": "Reprèn {count, plural, one {# treball pausat} other {# treballs pausats}}", + "retry_upload": "Torna a provar de pujar", + "review_duplicates": "Revisar duplicats", + "review_large_files": "Revisar fitxers grans", "role": "Rol", "role_editor": "Editor", - "role_viewer": "Visualitzador", + "role_viewer": "Visor", "running": "En execuciÃŗ", "save": "Desa", - "saved": "S’ha desat", - "saved_api_key": "S’ha desat la clau de l’API", - "saved_profile": "S’ha desat el perfil", - "saved_settings": "S’ha desat la configuraciÃŗ", - "say_something": "Digueu alguna cosa", - "scaffold_body_error_occurred": "S’ha produït un error", - "scaffold_body_error_unrecoverable": "S’ha produït un error irrecuperable. Compartiu l’error i la traça de la pila al Discord o al GitHub perquè us puguem ajudar. Si us ho aconsellen, podeu esborrar les dades de l’aplicaciÃŗ a continuaciÃŗ.", + "save_to_gallery": "Desa a galeria", + "saved": "Guardat", + "saved_api_key": "Clau d'API guardada", + "saved_profile": "Perfil guardat", + "saved_settings": "ConfiguraciÃŗ guardada", + "say_something": "Digues quelcom", + "scaffold_body_error_occurred": "S'ha produït un error", + "scaffold_body_error_unrecoverable": "S'ha produït un error irrecuperable. Comparteix l'error i el rastre de la pila a Discord o GitHub perquè puguem ajudar-te. Si us ho aconsella, podeu esborrar les dades de l'aplicaciÃŗ a continuaciÃŗ.", "scan": "Escaneja", - "scan_all_libraries": "Escaneja totes les biblioteques", + "scan_all_libraries": "Escanejar totes les llibreries", "scan_library": "Escaneja", - "scanning": "S’està escanejant", - "scanning_for_album": "S’està escanejant l’àlbum...", - "screencast_mode_description": "Mostra els indicadors d’esdeveniments del teclat i del ratolí a la pantalla", - "screencast_mode_title": "Commuta el mode de gravaciÃŗ", + "scan_settings": "ConfiguraciÃŗ d'escaneig", + "scanning": "Escanejant", + "scanning_for_album": "S'està buscant l'àlbum...", "search": "Cerca", - "search_albums": "Cerqueu àlbums", - "search_by_context": "Cerca per context", + "search_albums": "Buscar àlbums", + "search_by_context": "Buscar per context", "search_by_description": "Cercar per descripciÃŗ", - "search_by_description_example": "Dia de senderisme a Sapa", + "search_by_description_example": "Jornada de senderisme a Sapa", "search_by_filename": "Cerca per nom de fitxer o extensiÃŗ", - "search_by_filename_example": "per exemple, IMG_1234.JPG o PNG", - "search_by_full_path": "Cerca per camí complet o carpeta", - "search_by_full_path_example": "/Joan/Projectes/ImpressiÃŗ_3D/2026-07-01: podeu cercar Projectes, 3D, ImpressiÃŗ, 2026, etc.", - "search_by_ocr": "Cerca per OCR", - "search_by_ocr_example": "Cafè amb llet", - "search_camera_lens_model": "Cerqueu models d’objectiu...", - "search_camera_make": "Cerqueu fabricants de càmera...", - "search_camera_model": "Cerqueu models de càmera...", - "search_city": "Cerqueu ciutats...", - "search_country": "Cerqueu països...", - "search_filter_apply": "Aplica el filtre", - "search_filter_camera_title": "Seleccioneu el tipus de càmera", + "search_by_filename_example": "per exemple IMG_1234.JPG o PNG", + "search_by_ocr": "Buscar per OCR", + "search_by_ocr_example": "DesprÊs", + "search_camera_lens_model": "Buscar model de lents....", + "search_camera_make": "Buscar per fabricant de càmara...", + "search_camera_model": "Buscar per model de càmera...", + "search_city": "Buscar per ciutat...", + "search_country": "Buscar per país...", + "search_filter_apply": "Aplicar filtre", + "search_filter_camera_title": "Selecciona el tipus de càmera", "search_filter_date": "Data", - "search_filter_date_interval": "de {start} a {end}", - "search_filter_date_title": "Seleccioneu un interval de dates", - "search_filter_display_option_not_in_album": "En cap àlbum", - "search_filter_display_options": "Opcions de visualitzaciÃŗ", - "search_filter_filename": "Cerca per nom del fitxer", + "search_filter_date_interval": "{start} a {end}", + "search_filter_date_title": "Selecciona un rang de dates", + "search_filter_display_option_not_in_album": "No en àlbum", + "search_filter_display_options": "Opcions de VisualitzaciÃŗ", + "search_filter_filename": "Cerca pel nom del fitxer", "search_filter_location": "UbicaciÃŗ", - "search_filter_location_title": "Seleccioneu una ubicaciÃŗ", - "search_filter_media_type": "Tipus de mitjà", - "search_filter_media_type_title": "Seleccioneu un tipus de mitjà", - "search_filter_people_title": "Seleccioneu persones", - "search_filter_star_rating": "PuntuaciÃŗ per estrelles", - "search_filter_tags_title": "Seleccioneu etiquetes", - "search_for": "Cerca", - "search_for_existing_person": "Cerca una persona existent", - "search_no_more_result": "No hi ha mÊs resultats", - "search_no_people": "No hi ha cap persona", - "search_no_people_named": "No hi ha cap persona anomenada ÂĢ{name}Âģ", - "search_no_result": "No s’ha trobat cap resultat; proveu un terme de cerca o una combinaciÃŗ diferents", + "search_filter_location_title": "Selecciona l'ubicaciÃŗ", + "search_filter_media_type": "Tipus de multimèdia", + "search_filter_media_type_title": "Selecciona tipus de multimèdia", + "search_filter_ocr": "Buscar per OCR", + "search_filter_people_title": "Selecciona persones", + "search_filter_star_rating": "ClassificaciÃŗ per estrelles", + "search_filter_tags_title": "Seleccionar etiquetes", + "search_for": "Cercar", + "search_for_existing_person": "Busca una persona existent", + "search_no_more_result": "No mÊs resultats", + "search_no_people": "Cap persona", + "search_no_people_named": "Cap persona anomenada \"{name}\"", + "search_no_result": "No s'han trobat resultats, proveu un terme de cerca o una combinaciÃŗ diferents", "search_options": "Opcions de cerca", - "search_page_search_photos_videos": "Cerqueu les vostres fotos i vídeos", - "search_page_view_all_button": "Mostra-ho tot", - "search_people": "Cerca persones", - "search_places": "Cerca llocs", - "search_rating": "Cerca per puntuaciÃŗ...", - "search_settings": "ConfiguraciÃŗ de la cerca", - "search_state": "Cerqueu regions...", - "search_tags": "Cerqueu etiquetes...", - "search_timezone": "Cerqueu zones horàries...", - "search_type": "Tipus de cerca", - "search_your_photos": "Cerqueu les vostres fotos", - "searching_locales": "S’estan cercant localitzacions...", + "search_page_categories": "Categories", + "search_page_motion_photos": "Fotografies animades", + "search_page_no_objects": "No hi ha informaciÃŗ d'objectes disponibles", + "search_page_no_places": "No hi ha informaciÃŗ de llocs disponibles", + "search_page_screenshots": "Captures de pantalla", + "search_page_search_photos_videos": "Cerca les teves fotos i vídeos", + "search_page_selfies": "Autofotos", + "search_page_things": "Coses", + "search_page_view_all_button": "Veure tot", + "search_page_your_activity": "La teva activitat", + "search_page_your_map": "El teu mapa", + "search_people": "Buscar persones", + "search_places": "Buscar llocs", + "search_rating": "Buscar per qualificaciÃŗ...", + "search_result_page_new_search_hint": "Cerca nova", + "search_settings": "ConfiguraciÃŗ de cerca", + "search_state": "Buscar per regiÃŗ...", + "search_suggestion_list_smart_search_hint_1": "La cerca intel¡ligent està habilitada per defecte, per a cercar metadades utilitzeu la sintaxi ", + "search_suggestion_list_smart_search_hint_2": "m:el-teu-terme-de-cerca", + "search_tags": "Cercant etiquetes...", + "search_timezone": "Buscar per fus horari...", + "search_type": "Buscar per tipus", + "search_your_photos": "Cerca les teves fotos", + "searching_locales": "S'estan cercant localitzacions...", "second": "Segon", - "see_all_people": "Mostra totes les persones", + "see_all_people": "Veure totes les persones", "select": "Selecciona", - "select_album": "Selecciona l’àlbum", - "select_album_cover": "Selecciona la portada de l’àlbum", - "select_albums": "Selecciona àlbums", + "select_album": "Seleccionar àlbum", + "select_album_cover": "Seleccionar la portada de l'àlbum", + "select_albums": "Seleccionar àlbums", "select_all": "Selecciona-ho tot", - "select_all_duplicates": "Selecciona tots els duplicats", - "select_avatar_color": "Selecciona el color de l’avatar", - "select_count": "{count, plural, one {Selecciona’n #} other {Selecciona’n #}}", - "select_cutoff_date": "Seleccioneu la data de tall", - "select_face": "Selecciona una cara", - "select_featured_photo": "Selecciona la foto principal", - "select_from_computer": "Selecciona de l’ordinador", - "select_keep_all": "Selecciona conservar-ho tot", - "select_new_face": "Selecciona una cara nova", - "select_people": "Selecciona persones", - "select_person": "Selecciona una persona", - "select_person_to_tag": "Selecciona una persona a etiquetar", - "select_photos": "Selecciona fotos", - "select_quality": "Selecciona qualitat", - "select_trash_all": "Selecciona enviar-ho tot a la paperera", + "select_all_duplicates": "Seleccioneu tots els duplicats", + "select_all_in": "Selecciona tot en {group}", + "select_avatar_color": "Tria color de l'avatar", + "select_count": "{count, plural, one {Selecciona #} other {Selecciona #}}", + "select_cutoff_date": "Seleccionar data de tall", + "select_face": "Selecciona cara", + "select_featured_photo": "Selecciona foto principal", + "select_from_computer": "Seleccionar des de l'ordinador", + "select_keep_all": "MantÊn tota la selecciÃŗ", + "select_library_owner": "Selecciona el propietari de la bilbioteca", + "select_new_face": "Selecciona nova cara", + "select_people": "Seleccionar persones", + "select_person": "Seleccionar persona", + "select_person_to_tag": "Selecciona una persona per etiquetar", + "select_photos": "Tria fotografies", + "select_trash_all": "Envia la selecciÃŗ a la paperera", + "select_user_for_sharing_page_err_album": "Error al crear l'àlbum", "selected": "Seleccionat", "selected_count": "{count, plural, one {# seleccionat} other {# seleccionats}}", - "selected_gps_coordinates": "Coordenades GPS seleccionades", - "send_message": "Envia el missatge", - "send_welcome_email": "Envia el correu electrÃ˛nic de benvinguda", - "server_endpoint": "Endpoint del servidor", - "server_info_box_app_version": "VersiÃŗ de l’aplicaciÃŗ", + "selected_gps_coordinates": "Seleccio de coordinades GPS", + "send_message": "Envia missatge", + "send_welcome_email": "Envia correu de benvinguda", + "server_endpoint": "Endpoint de Servidor", + "server_info_box_app_version": "VersiÃŗ de l'aplicaciÃŗ", "server_info_box_server_url": "URL del servidor", "server_offline": "Servidor fora de línia", "server_online": "Servidor en línia", - "server_privacy": "Privacitat del servidor", - "server_restarting_description": "Aquesta pàgina s’actualitzarà aviat.", - "server_restarting_title": "S’està reiniciant el servidor", + "server_privacy": "Privadesa del servidor", + "server_restarting_description": "Aquesta pàgina es refrescarà momentàniament.", + "server_restarting_title": "El servidor s'està reiniciant", "server_stats": "Estadístiques del servidor", - "server_update_available": "Hi ha disponible una actualitzaciÃŗ del servidor", + "server_update_available": "ActualitzaciÃŗ del servidor disponible", "server_version": "VersiÃŗ del servidor", - "set": "Defineix", - "set_as_album_cover": "Defineix com a portada de l’àlbum", - "set_as_featured_photo": "Defineix com a foto destacada", - "set_as_profile_picture": "Defineix com a imatge de perfil", - "set_date_of_birth": "Defineix la data de naixement", - "set_profile_picture": "Defineix la imatge de perfil", - "set_slideshow_to_fullscreen": "Mostra les diapositives en pantalla completa", - "set_stack_primary_asset": "Defineix com a recurs principal", - "setting_image_navigation_enable_subtitle": "Si està activat, podeu navegar a la imatge anterior/segÃŧent tocant la quarta part mÊs a l’esquerra/dreta de la pantalla.", - "setting_image_navigation_enable_title": "Toqueu per a navegar", - "setting_image_navigation_title": "NavegaciÃŗ per les imatges", - "setting_image_viewer_help": "El visualitzador de detalls carrega primer la miniatura petita, desprÊs carrega la previsualitzaciÃŗ de mida mitjana (si està activada), i finalment carrega l’original (si està activat).", - "setting_image_viewer_original_subtitle": "Activeu-ho per a carregar la imatge en resoluciÃŗ original (molt gran!). Desactiveu-ho per a reduir el consum de dades (tant de xarxa com de memÃ˛ria cau del dispositiu).", + "set": "Establir", + "set_as_album_cover": "Establir com a portada de l'àlbum", + "set_as_featured_photo": "Estableix com a foto destacada", + "set_as_profile_picture": "Establir com a imatge de perfil", + "set_date_of_birth": "Establir data de naixement", + "set_profile_picture": "Establir imatge de perfil", + "set_slideshow_to_fullscreen": "Mostra Diapositives en pantalla completa", + "set_stack_primary_asset": "Estableix com a actiu principal", + "setting_image_navigation_enable_subtitle": "Si està activat, pots navegar a la imatge anterior/segÃŧent tocant la quarta part mÊs esquerra/dreta de la pantalla.", + "setting_image_navigation_enable_title": "Toca per navegar", + "setting_image_navigation_title": "NavegaciÃŗ d'imatges", + "setting_image_viewer_help": "El visor de detalls carrega primer la miniatura petita, desprÊs carrega la vista prèvia de mida mitjana (si està habilitada), finalment carrega l'original (si està habilitada).", + "setting_image_viewer_original_subtitle": "Activa per carregar la imatge en resoluciÃŗ original (molt gran!). Desactiva per reduir el consum de dades (tant de xarxa com de memÃ˛ria cau).", "setting_image_viewer_original_title": "Carrega la imatge original", - "setting_image_viewer_preview_subtitle": "Activeu-ho per a carregar una imatge de resoluciÃŗ mitjana. Desactiveu-ho per carregar directament la imatge original, o bÊ utilitzar nomÊs la miniatura.", - "setting_image_viewer_preview_title": "Carrega la imatge de previsualitzaciÃŗ", - "setting_languages_apply": "Aplica", - "setting_languages_subtitle": "Canvia la llengua de l’aplicaciÃŗ", + "setting_image_viewer_preview_subtitle": "Activa per carregar una imatge de resoluciÃŗ mitjana. Desactiva per carregar directament la imatge original, o bÊ utilitzar nomÊs la miniatura.", + "setting_image_viewer_preview_title": "Carrega la imatge de vista prèvia", + "setting_image_viewer_title": "Imatges", + "setting_languages_apply": "Aplicar", + "setting_languages_subtitle": "Canvia el llenguatge de l'aplicaciÃŗ", + "setting_notifications_notify_failures_grace_period": "Notifica les fallades de la cÃ˛pia de seguretat en segon pla: {duration}", + "setting_notifications_notify_hours": "{count} hores", + "setting_notifications_notify_immediately": "immediatament", "setting_notifications_notify_minutes": "{count} minuts", + "setting_notifications_notify_never": "mai", "setting_notifications_notify_seconds": "{count} segons", + "setting_notifications_single_progress_subtitle": "InformaciÃŗ detallada del progrÊs de la pujada de cada fitxer", + "setting_notifications_single_progress_title": "Mostra el progrÊs detallat de la cÃ˛pia de seguretat en segon pla", "setting_notifications_subtitle": "Ajusta les preferències de notificaciÃŗ", - "setting_video_viewer_auto_play_subtitle": "Comença a reproduir videos automàticament en obrir-los", - "setting_video_viewer_auto_play_title": "Reprodueix els vídeos automàticament", + "setting_notifications_total_progress_subtitle": "ProgrÊs general de la pujada (elements completats/total)", + "setting_notifications_total_progress_title": "Mostra el progrÊs total de la cÃ˛pia de seguretat en segon pla", + "setting_video_viewer_auto_play_subtitle": "Comença a veure videos quan s'obrin", + "setting_video_viewer_auto_play_title": "Veure videos automàticament", "setting_video_viewer_looping_title": "Bucle", - "setting_video_viewer_original_video_subtitle": "Quan mireu un vídeo del servidor, reprodueix l’original encara que hi hagi una transcodificaciÃŗ disponible. Pot provocar que la càrrega del vídeo s’alenteixi. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d’aquesta configuraciÃŗ.", + "setting_video_viewer_original_video_subtitle": "Quan reproduïu un vídeo des del servidor, reproduïu l'original encara que hi hagi una transcodificaciÃŗ disponible. Pot conduir a l'amortitzaciÃŗ. Els vídeos disponibles localment es reprodueixen en qualitat original independentment d'aquesta configuraciÃŗ.", "setting_video_viewer_original_video_title": "Força el vídeo original", "settings": "ConfiguraciÃŗ", - "settings_saved": "S’ha desat la configuraciÃŗ", - "setup_pin_code": "Configureu un codi PIN", - "share": "Comparteix", - "share_dialog_preparing": "S’està preparant...", - "share_link": "Comparteix l’enllaç", - "share_original": "Utilitza l’original (gran)", - "share_preview": "Utilitza la miniatura (petita)", - "share_quality_body": "Mantingueu premut el botÃŗ de comparticiÃŗ per a triar la qualitat de la imatge abans de compartir-la.", - "share_quality_title": "Trieu la qualitat de la comparticiÃŗ", - "shared": "Compartits", + "settings_require_restart": "Si us plau, reinicieu Immich per a aplicar aquest canvi", + "settings_saved": "ConfiguraciÃŗ desada", + "setup_pin_code": "Configurar un codi PIN", + "share": "Compartir", + "share_action_prompt": "Compartits {count} recursos", + "share_add_photos": "Afegeix fotografies", + "share_assets_selected": "{count} seleccionats", + "share_dialog_preparing": "S'està preparant...", + "share_link": "Compartir Enllaç", + "shared": "Compartit", "shared_album_activities_input_disable": "Els comentaris estan desactivats", "shared_album_activity_remove_content": "Voleu eliminar aquesta activitat?", - "shared_album_activity_remove_title": "Elimina l’activitat", - "shared_album_section_people_action_error": "S’ha produït un error en retirar/eliminar de l’àlbum", + "shared_album_activity_remove_title": "Elimina l'activitat", + "shared_album_section_people_action_error": "S'ha produït un error en retirar-se/eliminar l'àlbum", + "shared_album_section_people_action_leave": "Elimina l'usuari de l'àlbum", + "shared_album_section_people_action_remove_user": "Elimina l'usuari de l'àlbum", "shared_album_section_people_title": "PERSONES", "shared_by": "Compartit per", "shared_by_user": "Compartit per {user}", - "shared_by_you": "Compartit per vÃŗs", + "shared_by_you": "Compartit per tu", "shared_from_partner": "Fotos de {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} pujats", + "shared_intent_upload_button_progress_text": "{current} / {total} Pujat", "shared_link_app_bar_title": "Enllaços compartits", - "shared_link_clipboard_copied_massage": "S’ha copiat al porta-retalls", - "shared_link_create_error": "S’ha produït un error en crear l’enllaç compartit", - "shared_link_custom_url_description": "Accediu a aquest enllaç compartit amb un URL personalitzat", - "shared_link_custom_url_title": "URL personalitzat", - "shared_link_custom_url_warning": "Advertència: un URL personalitzat que contingui una barra pot comportar-se de manera imprevista.", - "shared_link_edit_description_hint": "Introduïu la descripciÃŗ de la comparticiÃŗ", + "shared_link_clipboard_copied_massage": "S'ha copiat al porta-retalls", + "shared_link_clipboard_text": "Enllaç: {link}\nContrasenya: {password}", + "shared_link_create_error": "S'ha produït un error en crear l'enllaç compartit", + "shared_link_custom_url_description": "Accedeix a aquest enllaç compartit amb una URL personalitzada", + "shared_link_edit_description_hint": "Introduïu la descripciÃŗ de comparticiÃŗ", "shared_link_edit_expire_after_option_day": "1 dia", "shared_link_edit_expire_after_option_days": "{count} dies", "shared_link_edit_expire_after_option_hour": "1 hora", @@ -1959,336 +2098,339 @@ "shared_link_edit_expire_after_option_minute": "1 minut", "shared_link_edit_expire_after_option_minutes": "{count} minuts", "shared_link_edit_expire_after_option_months": "{count} mesos", - "shared_link_edit_expire_after_option_year": "{count} any", - "shared_link_edit_password_hint": "Introduïu la contrasenya de la comparticiÃŗ", - "shared_link_edit_submit_button": "Actualitza l’enllaç", - "shared_link_error_server_url_fetch": "No s’ha pogut obtenir l’URL del servidor", - "shared_link_expires_day": "Caduca d’aquí a {count} dia", - "shared_link_expires_days": "Caduca d’aquí a {count} dies", - "shared_link_expires_hour": "Caduca d’aquí a {count} hora", - "shared_link_expires_hours": "Caduca d’aquí a {count} hores", - "shared_link_expires_minute": "Caduca d’aquí a {count} minut", - "shared_link_expires_minutes": "Caduca d’aquí a {count} minuts", + "shared_link_edit_expire_after_option_year": "any {count}", + "shared_link_edit_password_hint": "Introduïu la contrasenya de comparticiÃŗ", + "shared_link_edit_submit_button": "Actualitza l'enllaç", + "shared_link_error_server_url_fetch": "No s'ha pogut obtenir l'URL del servidor", + "shared_link_expires_day": "Caduca d'aquí a {count} dia", + "shared_link_expires_days": "Caduca d'aquí a {count} dies", + "shared_link_expires_hour": "Caduca d'aquí a {count} hora", + "shared_link_expires_hours": "Caduca d'aquí a {count} hores", + "shared_link_expires_minute": "Caduca d'aquí a {count} minut", + "shared_link_expires_minutes": "Caduca d'aquí a {count} minuts", "shared_link_expires_never": "Caduca ∞", - "shared_link_expires_second": "Caduca d’aquí a {count} segon", - "shared_link_expires_seconds": "Caduca d’aquí a {count} segons", - "shared_link_individual_shared": "Compartit individualment", + "shared_link_expires_second": "Caduca d'aquí a {count} segon", + "shared_link_expires_seconds": "Caduca d'aquí a {count} segons", + "shared_link_individual_shared": "Individual compartit", "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "Gestiona els enllaços compartits", - "shared_link_options": "Opcions dels enllaços compartits", - "shared_link_password_description": "Requereix una contrasenya per a accedir a aquest enllaç compartit", + "shared_link_options": "Opcions d'enllaços compartits", + "shared_link_password_description": "Requereix una contrasenya per accedir a aquest enllaç compartit", "shared_links": "Enllaços compartits", - "shared_links_description": "Compartiu fotos i vídeos amb un enllaç", + "shared_links_description": "Comparteix fotos i vídeos amb un enllaç", "shared_photos_and_videos_count": "{assetCount, plural, other {# fotos i vídeos compartits.}}", "shared_with_me": "Compartit amb mi", "shared_with_partner": "Compartit amb {partner}", - "sharing": "Compartits", - "sharing_enter_password": "Introduïu la contrasenya per a veure aquesta pàgina.", - "shift_to_permanent_delete": "premeu ⇧ per a suprimir el recurs permanentment", - "show_album_options": "Mostra les opcions de l’àlbum", - "show_albums": "Mostra els àlbums", - "show_all_people": "Mostra totes les persones", + "sharing": "Compartit", + "sharing_enter_password": "Introduïu la contrasenya per veure aquesta pàgina.", + "sharing_page_album": "Àlbums compartits", + "sharing_page_description": "Crea àlbums compartits per compartir fotos i vídeos amb persones de la teva xarxa.", + "sharing_page_empty_list": "LLISTA BUIDA", + "sharing_sidebar_description": "Mostra un enllaç a Compartit a la barra lateral", + "sharing_silver_appbar_create_shared_album": "Crea àlbum compartit", + "sharing_silver_appbar_share_partner": "Comparteix amb un company", + "shift_to_permanent_delete": "premeu ⇧ per suprimir el recurs permanentment", + "show_album_options": "Mostra les opcions d'àlbum", + "show_albums": "Mostrar àlbums", + "show_all_people": "Veure totes les persones", "show_and_hide_people": "Mostra i amaga persones", - "show_file_location": "Mostra la ubicaciÃŗ del fitxer", + "show_file_location": "Mostra l'ubicaciÃŗ del fitxer", "show_gallery": "Mostra la galeria", - "show_hidden_people": "Mostra les persones amagades", + "show_hidden_people": "Mostra persones ocultes", "show_in_timeline": "Mostra a la cronologia", - "show_in_timeline_setting_description": "Mostra les fotos i els vídeos d’aquest usuari a la cronologia", - "show_keyboard_shortcuts": "Mostra les dreceres del teclat", - "show_less": "Mostra’n menys", - "show_metadata": "Mostra les metadades", - "show_more_fields": "{count, plural, one {Mostra # camp mÊs} other {Mostra # camps mÊs}}", - "show_or_hide_info": "Mostra o amaga la informaciÃŗ", - "show_password": "Mostra la contrasenya", - "show_person_options": "Mostra les opcions de la persona", - "show_progress_bar": "Mostra la barra de progrÊs", - "show_schema": "Mostra l’esquema", - "show_search_options": "Mostra les opcions de cerca", + "show_in_timeline_setting_description": "Mostra fotos i vídeos d'aquest usuari a la cronologia", + "show_keyboard_shortcuts": "Mostra dreceres de teclat", + "show_metadata": "Mostra metadades", + "show_or_hide_info": "Mostra o amaga informaciÃŗ", + "show_password": "Mostra contrasenya", + "show_person_options": "Mostra opcions de la persona", + "show_progress_bar": "Mostra barra de progrÊs", + "show_schema": "Mostrar esquema", + "show_search_options": "Mostra opcions de cerca", "show_shared_links": "Mostra els enllaços compartits", - "show_slideshow_metadata_overlay": "Mostra la informaciÃŗ sobre la imatge", "show_slideshow_transition": "Mostra la transiciÃŗ de la presentaciÃŗ de diapositives", - "show_supporter_badge": "Insígnia de col¡laborador", - "show_supporter_badge_description": "Mostra una insígnia de col¡laborador", + "show_supporter_badge": "Insígnia de contribuent", + "show_supporter_badge_description": "Mostra una insígnia de contributor", "show_text_recognition": "Mostra el reconeixement de text", - "show_text_search_menu": "Mostra el menÃē de cerca per text", - "shuffle": "Mescla aleatÃ˛riament", + "show_text_search_menu": "Mostra el menÃē de cerca amb text", + "shuffle": "Mescla", "sidebar": "Barra lateral", "sidebar_display_description": "Mostra un enllaç a la vista a la barra lateral", - "sign_out": "Tanca la sessiÃŗ", - "sign_up": "Registra-m’hi", + "sign_out": "Tanca sessiÃŗ", + "sign_up": "Registrar-se", "size": "Mida", - "skip": "Omet", "skip_to_content": "Salta al contingut", - "skip_to_folders": "VÊs a les carpetes", - "skip_to_tags": "VÊs a les etiquetes", - "slideshow": "PresentaciÃŗ de diapositives", - "slideshow_body": "Relaxeu-vos i mireu com es reprodueixen les vostres fotos en una presentaciÃŗ de diapositives a pantalla completa.", - "slideshow_metadata_overlay_mode": "Contingut superposat", - "slideshow_metadata_overlay_mode_description_only": "NomÊs la descripciÃŗ", - "slideshow_metadata_overlay_mode_full": "Tot", + "skip_to_folders": "Anar a carpetes", + "skip_to_tags": "Anar a etiquetes", + "slideshow": "Diapositives", "slideshow_repeat": "Repeteix la presentaciÃŗ de diapositives", - "slideshow_repeat_description": "Torna al principi quan acabi la presentaciÃŗ de diapositives", - "slideshow_settings": "ConfiguraciÃŗ de la presentaciÃŗ de diapositives", - "slideshow_title": "PresentaciÃŗ de diapositives", - "smart_album": "Àlbum inteŀligent", - "some_assets_already_have_a_location_warning": "Alguns dels recursos seleccionats ja tenen una ubicaciÃŗ", - "sort_albums_by": "Ordena els àlbums per...", + "slideshow_repeat_description": "Torna al principi quan acaba la presentaciÃŗ de diapositives", + "slideshow_settings": "ConfiguraciÃŗ de diapositives", + "sort_albums_by": "Ordena àlbums per...", "sort_created": "Data de creaciÃŗ", - "sort_items": "Nombre d’elements", + "sort_items": "Quantitat d'elements", "sort_modified": "Data de modificaciÃŗ", + "sort_newest": "Foto mÊs nova", "sort_oldest": "Foto mÊs antiga", - "sort_people_by_similarity": "Ordena les persones per similitud", + "sort_people_by_similarity": "Ordenar personar per semblança", "sort_recent": "Foto mÊs recent", "sort_title": "Títol", "source": "Font", "stack": "Apila", "stack_action_prompt": "{count} apilats", - "stack_duplicates": "Apila els duplicats", + "stack_duplicates": "Aplica duplicats", + "stack_select_one_photo": "Selecciona una imatge principal per la pila", "stack_selected_photos": "Apila les fotos seleccionades", - "stacked_assets_count": "{count, plural, one {S’ha apilat # recurs} other {S’han apilat # recursos}}", - "stacktrace": "Traça de la pila", + "stacked_assets_count": "Apilats {count, plural, one {# element} other {# elements}}", + "stacktrace": "Traça de pila", "start": "Inicia", "start_date": "Data inicial", - "start_date_before_end_date": "La data inicial ha de ser abans de la data final", + "start_date_before_end_date": "La data d'inici ha de ser abans de la data de fi", "state": "RegiÃŗ", "status": "Estat", - "step_delete": "Suprimeix el pas", - "step_delete_confirm": "Segur que voleu suprimir aquest pas?", - "step_details": "Detalls del pas", - "steps": "Passos", - "stop_casting": "Atura l’emissiÃŗ", - "stop_motion_photo": "Atura la foto en moviment", - "stop_photo_sharing": "Voleu deixar de compartir les fotos?", - "stop_photo_sharing_description": "{partner} ja no podrà accedir a les vostres fotos.", + "stop_casting": "Atura la transmisiÃŗ", + "stop_motion_photo": "Atura foto en moviment", + "stop_photo_sharing": "Deixar de compartir les teves fotos?", + "stop_photo_sharing_description": "{partner} no podrà tornar a accedir a les vostres fotos.", "stop_sharing_photos_with_user": "Deixa de compartir les fotos amb aquest usuari", - "storage": "Espai d’emmagatzematge", - "storage_label": "Etiqueta d’emmagatzematge", - "storage_quota": "Quota d’emmagatzematge", + "storage": "Emmagatzematge", + "storage_label": "Etiquetatge d'emmagatzematge", + "storage_quota": "Quota d'emmagatzematge", "storage_usage": "{used} de {available} en Ãēs", "submit": "Envia", - "success": "Completat", + "success": "Amb èxit", "suggestions": "Suggeriments", "sunrise_on_the_beach": "Albada a la platja", - "support": "Assistència", - "support_and_feedback": "Assistència i comentaris", - "support_third_party_description": "La vostra instal¡laciÃŗ de l’Immich està empaquetada per un tercer. Els problemes que experimenteu poden ser causats per aquest paquet, de manera que comuniqueu els problemes en primer lloc amb ells mitjançant els enllaços segÃŧents.", - "supporter": "Col¡laborador", - "swap_merge_direction": "Inverteix la direcciÃŗ de la fusiÃŗ", + "support": "Suport", + "support_and_feedback": "Suport i comentaris", + "support_third_party_description": "La vostra instal¡laciÃŗ immich la va empaquetar un tercer. Els problemes que experimenteu poden ser causats per aquest paquet així que, si us plau, plantegeu els poblemes amb ells en primer lloc mitjançant els enllaços segÃŧents.", + "supporter": "Contribuïdor", + "swap_merge_direction": "Intercanvia la direcciÃŗ d'uniÃŗ", "sync": "Sincronitza", - "sync_albums": "Sincronitza els àlbums", - "sync_local": "SincronitzaciÃŗ local", - "sync_remote": "SincronitzaciÃŗ remota", - "sync_status": "Estat de la sincronitzaciÃŗ", - "sync_status_subtitle": "Vegeu i gestioneu el sistema de sincronitzaciÃŗ", - "sync_upload_album_setting_subtitle": "Creeu i pugeu les vostres fotos i vídeos als àlbums seleccionats a l’Immich", - "system_theme": "Tema del sistema", - "system_theme_command_description": "Utilitza el tema del sistema ({value})", + "sync_albums": "Sincronitza àlbums", + "sync_albums_manual_subtitle": "Sincronitza tots els vídeos i fotos penjats amb els àlbums de cÃ˛pia de seguretat seleccionats", + "sync_local": "Sincronitza localment", + "sync_remote": "Sincronitza remotament", + "sync_status": "Estat de la incronitzaciÃŗ", + "sync_status_subtitle": "Observa i administra el sistema de sincronitzaciÃŗ", + "sync_upload_album_setting_subtitle": "Creeu i pugeu les seves fotos i vídeos als àlbums seleccionats a Immich", "tag": "Etiqueta", - "tag_assets": "Etiqueta els recursos", - "tag_created": "S’ha creat l’etiqueta: {tag}", + "tag_assets": "Etiquetar actius", + "tag_created": "Etiqueta creada: {tag}", "tag_face": "Etiqueta una cara", - "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d’etiquetes lÃ˛giques", - "tag_people": "Etiqueta persones", - "tag_updated": "S’ha actualitzat l’etiqueta: {tag}", - "tagged_assets": "{count, plural, one {S’ha etiquetat # recurs} other {S’han etiquetat # recursos}}", + "tag_feature_description": "Exploreu fotos i vídeos agrupats per temes d'etiquetes lÃ˛giques", + "tag_not_found_question": "No trobeu una etiqueta? Crear una nova etiqueta.", + "tag_people": "Etiquetar personas", + "tag_updated": "Etiqueta actualizada: {tag}", + "tagged_assets": "{count, plural, one {#Etiquetat} other {#Etiquetats}} {count, plural, one {# actiu} other {# actius}}", "tags": "Etiquetes", - "tap_to_run_job": "Toqueu per a executar la tasca", + "tap_to_run_job": "Toca per executar el treball", "template": "Plantilla", "text_recognition": "Reconeixement de text", "theme": "Tema", - "theme_selection": "SelecciÃŗ del tema", + "theme_selection": "SelecciÃŗ de tema", "theme_selection_description": "Activa automàticament el tema fosc o clar en funciÃŗ de les preferències del sistema del navegador", - "theme_setting_asset_list_storage_indicator_title": "Mostra l’indicador d’emmagatzematge a les tessel¡les dels recursos", - "theme_setting_asset_list_tiles_per_row_title": "Nombre de recursos per fila ({count})", - "theme_setting_colorful_interface_subtitle": "Aplica el color primari a les superfícies de fons.", - "theme_setting_colorful_interface_title": "Interfície acolorida", + "theme_setting_asset_list_storage_indicator_title": "Mostra l'indicador d'emmagatzematge als títols dels elements", + "theme_setting_asset_list_tiles_per_row_title": "Nombre d'elements per fila ({count})", + "theme_setting_colorful_interface_subtitle": "Apliqueu color primari a les superfícies de fons.", + "theme_setting_colorful_interface_title": "Interfície colorida", + "theme_setting_image_viewer_quality_subtitle": "Ajusta la qualitat del visor de detalls d'imatges", + "theme_setting_image_viewer_quality_title": "Qualitat del visor d'imatges", "theme_setting_primary_color_subtitle": "Trieu un color per a les accions i els accents principals.", "theme_setting_primary_color_title": "Color primari", - "theme_setting_system_primary_color_title": "Utilitza el color de sistema", - "theme_setting_system_theme_switch": "Automàtic (segueix la configuraciÃŗ del sistema)", + "theme_setting_system_primary_color_title": "Utilitza color de sistema", + "theme_setting_system_theme_switch": "Automàtic (Segueix la configuraciÃŗ del sistema)", + "theme_setting_theme_subtitle": "Trieu la configuraciÃŗ del tema de l'aplicaciÃŗ", + "theme_setting_three_stage_loading_subtitle": "La càrrega en tres etapes podria augmentar el rendiment de càrrega, perÃ˛ causa un consum de xarxa significativament mÊs alt", + "theme_setting_three_stage_loading_title": "Activa la càrrega en tres etapes", "then": "Aleshores", - "they_will_be_merged_together": "Es fusionaran", + "they_will_be_merged_together": "Es combinaran", "third_party_resources": "Recursos de tercers", "time": "Temps", "time_based_memories": "Records basats en el temps", - "time_based_memories_duration": "Nombre de segons que es mostrarà cada imatge.", + "time_based_memories_duration": "Quants segons es mostrarà cada imatge.", "timeline": "Cronologia", - "timezone": "Zona horària", - "to_archive": "Arxiva", - "to_change_password": "Canvia la contrasenya", - "to_favorite": "Afegeix als preferits", - "to_login": "Inicia la sessiÃŗ", - "to_multi_select": "per a seleccionar-ne mÃēltiples", - "to_parent": "VÊs al pare", - "to_select": "per a seleccionar", + "timezone": "Fus horari", + "to_archive": "Arxivar", + "to_change_password": "Canviar la contrasenya", + "to_favorite": "Prefereix", + "to_login": "Iniciar sessiÃŗ", + "to_multi_select": "per multi-seleccionar", + "to_parent": "Anar als pares", + "to_select": "per seleccionar", "to_trash": "Paperera", - "toggle_settings": "Commuta la configuraciÃŗ", + "toggle_settings": "Canvia configuraciÃŗ", "toggle_theme_description": "Commuta el tema", "total": "Total", "total_usage": "Ús total", "trash": "Paperera", - "trash_action_prompt": "{count, plural, one {S’ha mogut # recurs} other {S’han mogut # recursos}} a la paperera", - "trash_all": "Tot a la paperera", - "trash_count": "{count, number} a la paperera", - "trash_delete_asset": "Mou a la paperera/Suprimeix el recurs", - "trash_no_results_message": "Les imatges i vídeos que es moguin a la paperera es mostraran aquí.", - "trash_page_delete_all": "Suprimeix-ho tot", - "trash_page_info": "Els elements moguts a la paperera se suprimiran permanentment desprÊs de {days} dies", - "trashed_items_will_be_permanently_deleted_after": "Els elements moguts a la paperera se suprimiran permanentment desprÊs {days, plural, one {d’# dia} other {de # dies}}.", + "trash_action_prompt": "{count} mogudes a la brossa", + "trash_all": "Envia-ho tot a la paperera", + "trash_count": "Paperera {count, number}", + "trash_delete_asset": "Esborra/Elimina element", + "trash_emptied": "Paperera buidada", + "trash_no_results_message": "Les imatges i vídeos que s'enviïn a la paperera es mostraran aquí.", + "trash_page_delete_all": "Eliminar-ho tot", + "trash_page_empty_trash_dialog_content": "Segur que voleu eliminar els elements? Aquests elements seran eliminats permanentment de Immich", + "trash_page_info": "Els elements que s'enviïn a la paperera s'eliminaran permanentment desprÊs de {days} dies", + "trash_page_no_assets": "No hi ha elements a la paperera", + "trash_page_restore_all": "Restaura-ho tot", + "trash_page_select_assets_btn": "Selecciona elements", + "trash_page_title": "Paperera ({count})", + "trashed_items_will_be_permanently_deleted_after": "Els elements que s'enviïn a la paperera s'eliminaran permanentment desprÊs de {days, plural, one {# dia} other {# dies}}.", "trigger": "Disparador", - "trigger_asset_metadata_extraction": "ExtracciÃŗ de metadades dels recursos", - "trigger_asset_metadata_extraction_description": "Es dispara quan s’extreuen les metadades EXIF d’un recurs", - "trigger_asset_uploaded": "Pujada de recursos", - "trigger_asset_uploaded_description": "Es dispara quan es puja un recurs nou", - "trigger_person_recognized": "Persona reconeguda", - "trigger_person_recognized_description": "Es dispara quan es reconeix una persona", + "trigger_asset_uploaded": "Mitjà Carregat", + "trigger_asset_uploaded_description": "Es dispara quan un nou mitjà es puge al servidor", + "trigger_description": "L'esdeveniment que inicia l'automatitzaciÃŗ", + "trigger_person_recognized": "Persona identificada", + "trigger_person_recognized_description": "Es dispara quan es detecta una persona", + "trigger_type": "Tipus de disparador", "troubleshoot": "SoluciÃŗ de problemes", "type": "Tipus", - "unable_to_change_pin_code": "No s’ha pogut canviar el codi PIN", - "unable_to_check_version": "No s’ha pogut comprovar la versiÃŗ de l’aplicaciÃŗ ni del servidor", - "unable_to_setup_pin_code": "No s’ha pogut definir el codi PIN", - "unarchive": "Desarxiva", - "unarchive_action_prompt": "{count, plural, one {S’ha eliminat # recurs de l’arxiu} other {S’han eliminat # recursos de l’arxiu}}", - "unarchived_count": "{count, plural, one {S’ha desarxivat # recurs} other {S’han desarxivat # recursos}}", - "undo": "DesfÊs", - "unfavorite": "Elimina dels preferits", - "unfavorite_action_prompt": "{count, plural, one {S’ha eliminat # recurs dels preferits} other {S’han eliminat # recursos dels preferits}}", - "unhide_person": "Deixa d’amagar la persona", + "unable_to_change_pin_code": "No es pot canviar el codi PIN", + "unable_to_check_version": "No es pot comprovar la versiÃŗ de l'aplicaciÃŗ ni del servidor", + "unable_to_setup_pin_code": "No s'ha pogut configurar el codi PIN", + "unarchive": "Desarxivar", + "unarchive_action_prompt": "{count} eliminades de l'arxiu", + "unarchived_count": "{count, plural, other {# elements desarxivats}}", + "undo": "Desfer", + "unfavorite": "Reverteix preferit", + "unfavorite_action_prompt": "{count} eliminades de preferits", + "unhide_person": "Mostra persona", "unknown": "Desconegut", - "unknown_country": "País desconegut", + "unknown_country": "País Desconegut", "unknown_date": "Data desconeguda", - "unknown_schema": "Esquema desconegut", "unknown_year": "Any desconegut", - "unlimited": "Il¡limitada", - "unlink_motion_video": "Desenllaça el vídeo en moviment", - "unlink_oauth": "Desenllaça l’OAuth", - "unlinked_oauth_account": "S’ha desenllaçat el compte d’Oauth", - "unmute_memories": "Deixa de silenciar els records", + "unlimited": "Il¡limitat", + "unlink_motion_video": "Desvincular vídeo en moviment", + "unlink_oauth": "Desvincula OAuth", + "unlinked_oauth_account": "Compte Oauth desvinculat", + "unmute_memories": "Activar el so dels records", "unnamed_album": "Àlbum sense nom", - "unnamed_album_delete_confirmation": "Segur que voleu suprimir aquest àlbum?", - "unnamed_share": "ComparticiÃŗ sense nom", + "unnamed_album_delete_confirmation": "Segur que voleu esborrar aquest àlbum?", + "unnamed_share": "Compartit sense nom", "unsaved_change": "Canvi no desat", "unselect_all": "Deselecciona-ho tot", - "unselect_all_duplicates": "Deselecciona tots els duplicats", + "unselect_all_duplicates": "Desmarqueu tots els duplicats", + "unselect_all_in": "Desseleccionar tots els elements de {group}", "unstack": "Desapila", - "unstack_action_prompt": "{count, plural, one {S’ha desapilat # recurs} other {S’han desapilat # recursos}}", - "unstacked_assets_count": "{count, plural, one {S’ha desapilat # recurs} other {S’han desapilat # recursos}}", - "unsupported_field_type": "Tipus de camp no admès", - "unsupported_file_type": "No es pot carregar el fitxer {file} perquè no s’admet el seu tipus de fitxer {type}.", + "unstack_action_prompt": "{count} sense apilar", + "unstacked_assets_count": "No apilat {count, plural, one {# recurs} other {# recursos}}", + "unsupported_field_type": "Tipus de camp no suportat", + "unsupported_file_type": "No es pot carregar el fitxer {file} perquè el seu tipus de fitxer {type} no Ês compatible.", "untagged": "Sense etiqueta", - "up_next": "A continuaciÃŗ", - "update_location_action_prompt": "Actualitza la ubicaciÃŗ dels {count} recursos seleccionats a:", + "untitled_workflow": "AutomatitzaciÃŗ sense títol", + "up_next": "PrÃ˛xim", + "update_location_action_prompt": "Actualitza la ubicaciÃŗ de {count} elements seleccionats amb:", "updated_at": "Actualitzat", - "updated_password": "S’ha actualitzat la contrasenya", - "upload": "Puja", + "updated_password": "Contrasenya actualitzada", + "upload": "Pujar", "upload_concurrency": "Concurrència de pujades", - "upload_day_count": "{date}: {count, plural, one {# pujada} other {# pujades}}", - "upload_details": "Detalls de la pujada", - "upload_error_with_count": "S’ha produït un error en la pujada {count, plural, one {d’# recurs} other {de # recursos}}", - "upload_errors": "S’ha completat la pujada amb {count, plural, one {# error} other {# errors}}; actualitzeu la pàgina per a veure els nous recursos pujats.", - "upload_finished": "Ha finalitzat la pujada", - "upload_progress": "Restants {remaining, number} - Processats {processed, number}/{total, number}", - "upload_skipped_duplicates": "{count, plural, one {S’ha omès # recurs duplicat} other {S’han omès # recursos duplicats}}", + "upload_details": "Detalls de la Pujada", + "upload_dialog_info": "Vols fer cÃ˛pia de seguretat dels elements seleccionats al servidor?", + "upload_dialog_title": "Puja elements", + "upload_error_with_count": "Error en la càrrega de {count, plural, one {# actiu} other {# actius}}", + "upload_errors": "Càrrega completada amb {count, plural, one {# error} other {# errors}}, actualitzeu la pàgina per veure els nous elements carregats.", + "upload_finished": "Pujada finalitzada", + "upload_progress": "Restant {remaining, number} - Processat {processed, number}/{total, number}", + "upload_skipped_duplicates": "{count, plural, one {S'ha omès # recurs duplicat} other {S'han omès # recursos duplicats}}", "upload_status_duplicates": "Duplicats", "upload_status_errors": "Errors", - "upload_status_uploaded": "Pujat", - "upload_success": "La pujada s’ha completat; actualitzeu la pàgina per a veure els nous recursos pujats.", - "upload_to_album_body": "Per als usuaris que no utilitzen la funciÃŗ de pujada manual, ara Ês possible triar afegir fotos locals directament a un àlbum a l’hora de penjar-les: ja no cal penjar-les i desprÊs afegir-nes a un àlbum.", - "upload_to_album_title": "Puja directament a un àlbum", - "upload_to_immich": "Puja a l’Immich ({count})", - "uploading": "S’està pujant", - "uploading_media": "S’estan pujant els mitjans", - "uploads": "Pujades", - "uploads_count": "{count, plural, one {# pujada} other {# pujades}}", + "upload_status_uploaded": "Carregat", + "upload_success": "Pujada correcta, actualitza la pàgina per veure nous recursos de pujada.", + "upload_to_immich": "Puja a Immich ({count})", + "uploading": "Pujant", + "uploading_media": "Pujant mitjans", "url": "URL", "usage": "Ús", - "use_biometric": "Utilitza la biometria", - "use_browser_locale": "Utilitza la localitzaciÃŗ del navegador", - "use_browser_locale_description": "Formata les dates, les hores i els nÃēmeros segons la llengua i la regiÃŗ del navegador", + "use_biometric": "Empra biometria", + "use_browser_locale": "Fer servir la localitzaciÃŗ del navegador", + "use_browser_locale_description": "Formatejar dates, hores i nÃēmeros segons la llengua i regiÃŗ del navegador", "use_current_connection": "Utilitza la connexiÃŗ actual", - "use_custom_date_range": "Utilitza un interval de dates personalitzat", - "use_template": "Utilitza la plantilla", + "use_custom_date_range": "Fes servir un rang de dates personalitzat", "user": "Usuari", - "user_has_been_deleted": "Aquest usuari s’ha eliminat.", - "user_id": "Identificador d’usuari", - "user_liked": "A {user} li ha agradat {type, select, photo {aquesta foto} video {aquest vídeo} asset {aquest recurs} other {aixÃ˛}}", + "user_has_been_deleted": "Aquest usuari ha sigut eliminat.", + "user_id": "ID d'usuari", + "user_liked": "A {user} li ha agradat {type, select, photo {aquesta foto} video {aquest vídeo} asset {aquest recurs} other {}}", "user_pin_code_settings": "Codi PIN", - "user_pin_code_settings_description": "Gestioneu el vostre codi PIN", - "user_privacy": "Privacitat de l’usuari", + "user_pin_code_settings_description": "Gestiona el teu codi PIN", + "user_privacy": "Privadesa d'Usuari", "user_purchase_settings": "Compra", - "user_purchase_settings_description": "Gestioneu la vostra compra", - "user_usage_detail": "Detall d’Ãēs de l’usuari", - "user_usage_stats": "Estadístiques d’Ãēs del compte", - "user_usage_stats_description": "Mostra les estadístiques d’Ãēs del compte", - "username": "Nom d’usuari", + "user_purchase_settings_description": "Gestiona la teva compra", + "user_role_set": "Establir {user} com a {role}", + "user_usage_detail": "Detall d'Ãēs d'usuari", + "user_usage_stats": "Estadístiques d'Ãēs de del compte", + "user_usage_stats_description": "Veure les estadístiques d'Ãēs del compte", + "username": "Nom d'usuari", "users": "Usuaris", - "users_added_to_album_count": "{count, plural, one {S’ha afegit # usuari} other {S’han afegit # usuaris}} a l’àlbum", + "users_added_to_album_count": "{count, plural, one {S'ha afegit # usuari} other {S'han afegit # usuaris}} a l'àlbum", "utilities": "Utilitats", "validate": "Valida", - "validate_endpoint_error": "Introduïu un URL vàlid", + "validate_endpoint_error": "Per favor introdueix un URL vàlid", + "validation_error": "Error de validaciÃŗ", "variables": "Variables", "version": "VersiÃŗ", - "version_announcement_closing": "El vostre amic Alex", - "version_announcement_message": "Bon dia! Hi ha disponible una nova versiÃŗ de l’Immich. Preneu-vos una estona per a llegir les notes del llançament i assegurar-vos que la vostra configuraciÃŗ estigui actualitzada per a evitar qualsevol error de configuraciÃŗ, especialment si utilitzeu WatchTower o qualsevol mecanisme que gestioni l’actualitzaciÃŗ automàtica de la vostra instància de l’Immich.", + "version_announcement_closing": "El teu amic Alex", + "version_announcement_message": "Hola! Hi ha una nova versiÃŗ d'Immich, si us plau, preneu-vos una estona per llegir les notes de llançament per assegurar que la teva configuraciÃŗ estigui actualitzada per evitar qualsevol error de configuraciÃŗ, especialment si utilitzeu WatchTower o qualsevol mecanisme que gestioni l'actualitzaciÃŗ automàtica de la vostra instància Immich.", "version_history": "Historial de versions", - "version_history_item": "S’ha instal¡lat {version} el {date}", + "version_history_item": "Instal¡lat {version} el {date}", "video": "Vídeo", - "video_hover_setting": "Reprodueix la miniatura del vídeo en posar-s’hi", - "video_hover_setting_description": "Reprodueix la miniatura del vídeo en passar-hi el ratolí per sobre. Fins i tot quan estigui desactivat, la reproducciÃŗ s’iniciarà si es passa el ratolí per sobre de la icona de reproducciÃŗ.", - "video_quality": "Qualitat del vídeo", + "video_hover_setting": "Reprodueix la miniatura en passar el ratolí", + "video_hover_setting_description": "Reprodueix la miniatura quan el ratolí plana sobre l'element. Fins i tot quan estigui deshabilitat, la reproducciÃŗ s'iniciarà planant sobre el botÃŗ de reproducciÃŗ.", "videos": "Vídeos", "videos_count": "{count, plural, one {# vídeo} other {# vídeos}}", - "view": "VisualitzaciÃŗ", - "view_album": "Mostra l’àlbum", - "view_all": "Mostra-ho tot", - "view_all_users": "Mostra tots els usuaris", - "view_asset_owners": "Mostra els propietaris dels recursos", - "view_details": "Mostra els detalls", - "view_in_timeline": "Mostra a la cronologia", - "view_link": "Mostra l’enllaç", - "view_name": "Mostra", - "view_next_asset": "Mostra el recurs segÃŧent", - "view_previous_asset": "Mostra el recurs anterior", - "view_qr_code": "Mostra el codi QR", - "view_similar_photos": "Mostra les fotos similars", - "view_stack": "Mostra la pila", + "videos_only": "NomÊs videos", + "view": "Veure", + "view_album": "Veure l'àlbum", + "view_all": "Veure tot", + "view_all_users": "Mostra tot els usuaris", + "view_asset_owners": "Veure els propietaris dels actius", + "view_details": "Veure Detalls", + "view_in_timeline": "Mostrar a la línia de temps", + "view_link": "Veure enllaç", + "view_links": "Mostra enllaços", + "view_name": "Veure", + "view_next_asset": "Mostra el segÃŧent element", + "view_previous_asset": "Mostra l'element anterior", + "view_qr_code": "Veure codi QR", + "view_similar_photos": "Veure fotos similars", + "view_stack": "Veure la pila", + "view_user": "Veure Usuari", "viewer_remove_from_stack": "Elimina de la pila", + "viewer_stack_use_as_main_asset": "Fes servir com a element principal", + "viewer_unstack": "Desapila", "visibility": "Visibilitat", - "visibility_changed": "S’ha canviat la visibilitat {count, plural, one {d’# persona} other {de # persones}}", + "visibility_changed": "La visibilitat ha canviat per {count, plural, one {# persona} other {# persones}}", "visual": "Visual", - "waiting": "En espera", - "waiting_count": "En espera: {count}", - "warning": "Advertència", + "visual_builder": "Constructor visual", + "waiting": "Esperant", + "waiting_count": "Esperant: {count}", + "warning": "Avís", "week": "Setmana", - "welcome": "Us donem la benvinguda", - "welcome_to_immich": "Us donem la benvinguda a l’Immich", - "whats_new": "Novetats", - "whats_new_settings_subtitle": "Vegeu les novetats de l’Immich", - "whats_new_version": "VersiÃŗ {version}", - "when": "Quan", + "welcome": "Benvingut", + "welcome_to_immich": "Benvingut a immich", "width": "Amplada", - "wifi_name": "Nom de la Wi-Fi", - "workflow": "Flux de treball", - "workflow_delete_prompt": "Segur que voleu suprimir aquest flux de treball?", - "workflow_deleted": "S’ha suprimit el flux de treball", - "workflow_description": "DescripciÃŗ del flux de treball", - "workflow_info": "InformaciÃŗ del flux de treball", - "workflow_json": "JSON del flux de treball", - "workflow_json_help": "Editeu la configuraciÃŗ del flux de treball en format JSON. Els canvis se sincronitzaran amb el constructor visual.", - "workflow_name": "Nom del flux de treball", - "workflow_navigation_prompt": "Segur que voleu sortir sense desar els canvis?", - "workflow_summary": "Resum del flux de treball", - "workflow_templates": "Plantilles de fluxos de treball", - "workflow_update_success": "S’ha actualitzat el flux de treball correctament", - "workflows": "Fluxos de treball", - "workflows_help_text": "Els fluxos de treball automatitzen accions sobre els vostres recursos basant-se en disparadors i filtres", - "wrong_pin_code": "El codi PIN no Ês correcte", - "x_of_total": "{x}/{total}", + "wifi_name": "Nom Wi-Fi", + "workflow_delete_prompt": "Segur que vols eliminar aquesta automatitzaciÃŗ?", + "workflow_deleted": "AutomatitzaciÃŗ eliminada", + "workflow_description": "DescripciÃŗ de l'automatitzaciÃŗ", + "workflow_info": "InformaciÃŗ de l'automatitzaciÃŗ", + "workflow_json": "JSON de l'automatitzaciÃŗ", + "workflow_json_help": "Edita la configuraciÃŗ de l'automatitzaciÃŗ en format JSON. Els canvis es sincronitzaran amb el constructor visual.", + "workflow_name": "Nom de l'automatitzaciÃŗ", + "workflow_navigation_prompt": "Segur que vols sortir sense desar els canvis?", + "workflow_summary": "Resum de l'automatitzaciÃŗ", + "workflow_update_success": "AutomatitzaciÃŗ actualitzada amb èxit", + "workflow_updated": "AutomatitzaciÃŗ actualitzada", + "workflows": "Automatitzacions", + "workflows_help_text": "Les automatitzacions realitzen accions automàticament sobre els teus mitjans basant-se en disparadors i filtres", + "wrong_pin_code": "Codi PIN incorrecte", "year": "Any", "years_ago": "Fa {years, plural, one {# any} other {# anys}}", "yes": "Sí", - "you_dont_have_any_shared_links": "No teniu cap enllaç compartit", - "your_wifi_name": "Nom de la vostra Wi-Fi", - "zero_to_clear_rating": "premeu 0 per a esborrar la puntuaciÃŗ del recurs", - "zoom_image": "Amplia la imatge" + "you_dont_have_any_shared_links": "No tens cap enllaç compartit", + "your_wifi_name": "Nom del teu Wi-Fi", + "zero_to_clear_rating": "prem 0 per a buidar la valoraciÃŗ", + "zoom_image": "Ampliar Imatge", + "zoom_to_bounds": "Amplia als límits" } diff --git a/i18n/cs.json b/i18n/cs.json index ccf5b931b7..0c333532f3 100644 --- a/i18n/cs.json +++ b/i18n/cs.json @@ -10,31 +10,39 @@ "active": "Aktivní", "active_count": "Aktivní: {count}", "activity": "Aktivita", + "activity_changed": "Aktivita je {enabled, select, true {povolena} other {zakÃĄzÃĄna}}", "add": "Přidat", "add_a_description": "Přidat popis", "add_a_location": "Přidat polohu", "add_a_name": "Přidat jmÊno", "add_a_title": "Přidat nÃĄzev", "add_action": "Přidat akci", + "add_action_description": "Kliknutím přidejte akci, kterou chcete provÊst", "add_assets": "Přidat poloÅžky", "add_birthday": "Přidat datum narození", "add_endpoint": "Přidat koncovÃŊ bod", "add_exclusion_pattern": "Přidat vzor vyloučení", + "add_filter": "Přidat filtr", + "add_filter_description": "Kliknutím přidejte podmínku filtru", "add_location": "Přidat polohu", + "add_more_users": "Přidat dalÅĄÃ­ uÅživatele", "add_partner": "Přidat partnera", + "add_path": "Přidat cestu", "add_photos": "Přidat fotky", - "add_step": "Přidat krok", "add_tag": "Přidat značku", "add_to": "Přidat doâ€Ļ", "add_to_album": "Přidat do alba", "add_to_album_bottom_sheet_added": "PřidÃĄno do {album}", "add_to_album_bottom_sheet_already_exists": "Je jiÅž v {album}", "add_to_album_bottom_sheet_some_local_assets": "NěkterÊ místní poloÅžky nebylo moÅžnÊ přidat do alba", + "add_to_album_toggle": "Přepnout vÃŊběr pro {album}", "add_to_albums": "Přidat do alb", "add_to_albums_count": "Přidat do alb ({count})", "add_to_bottom_bar": "Přidat do", + "add_to_shared_album": "Přidat do sdílenÊho alba", "add_upload_to_stack": "Přidat nahranÊ do seskupení", "add_url": "Přidat URL", + "add_workflow_step": "Přidat krok pracovního postupu", "added_to_archive": "PřidÃĄno do archivu", "added_to_favorites": "PřidÃĄno do oblíbenÃŊch", "added_to_favorites_count": "PřidÃĄno {count, number} do oblíbenÃŊch", @@ -73,7 +81,6 @@ "cron_expression_description": "Nastavte interval prohledÃĄvÃĄní pomocí cron formÃĄtu. DalÅĄÃ­ informace naleznete např. v Crontab Guru", "cron_expression_presets": "Předvolby vÃŊrazů cron", "disable_login": "ZakÃĄzat přihlÃĄÅĄení", - "download_csv": "StÃĄhnout CSV", "duplicate_detection_job_description": "SpuÅĄtění strojovÊho učení na poloÅžkÃĄch za Ãēčelem detekce podobnÃŊch obrÃĄzků. SpolÊhÃĄ na ChytrÊ vyhledÃĄvÃĄní", "exclusion_pattern_description": "Vzory vyloučení umoŞňují při prohledÃĄvÃĄní knihovny ignorovat soubory a sloÅžky. To je uÅžitečnÊ, pokud mÃĄte sloÅžky obsahující soubory, kterÊ nechcete importovat, například RAW soubory.", "export_config_as_json_description": "StÃĄhněte si aktuÃĄlní konfiguraci systÊmu jako JSON soubor", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Kvalita miniatur od 1 do 100. VyÅĄÅĄÃ­ je lepÅĄÃ­, ale vytvÃĄÅ™Ã­ větÅĄÃ­ soubory a můŞe sníŞit odezvu aplikace.", "image_thumbnail_title": "Miniatury", "import_config_from_json_description": "Importujte konfiguraci systÊmu nahrÃĄním konfiguračního JSON souboru", - "integrity_checks_checksum_files": "Kontrolní součty souborů", - "integrity_checks_checksum_files_description": "Nastavení kontroly kontrolního součtu", - "integrity_checks_checksum_files_enable_description": "Zapnout kontrolu kontrolního součtu", - "integrity_checks_checksum_files_percentage_limit": "ProcentuÃĄlní limit", - "integrity_checks_checksum_files_percentage_limit_description": "Nastavte maximÃĄlní procentuÃĄlní hodnotu v rozmezí 0.01 aÅž 1, kterÃĄ určuje, jak často se mÃĄ v danÊm intervalu provÃĄdět kontrola kontrolního součtu.", - "integrity_checks_checksum_files_time_limit": "ČasovÃŊ limit", - "integrity_checks_checksum_files_time_limit_description": "Nastavte maximÃĄlní dobu, po kterou by měla v kaÅždÊm intervalu probíhat kontrola kontrolního součtu. (ms)", - "integrity_checks_missing_files": "Chybějící soubory", - "integrity_checks_missing_files_description": "Nastavte frekvenci a zapněte nebo vypněte kontrolu chybějících souborů", - "integrity_checks_missing_files_enable_description": "Zapnout kontrolu chybějících souborů", - "integrity_checks_settings": "Kontroly integrity", - "integrity_checks_settings_description": "SprÃĄva intervalů kontrol integrity", - "integrity_checks_untracked_files": "NesledovanÊ soubory", - "integrity_checks_untracked_files_description": "Nastavte frekvenci a zapněte nebo vypněte kontrolu nesledovanÃŊch souborů", - "integrity_checks_untracked_files_enable_description": "Zapnout kontrolu nesledovanÃŊch souborů", "job_concurrency": "SouběŞnost Ãēlohy {job}", "job_created": "Úloha vytvořena", "job_not_concurrency_safe": "Tato Ãēloha není bezpečenÃĄ pro souběh.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Povolit chytrÊ vyhledÃĄvÃĄní", "machine_learning_smart_search_enabled_description": "Pokud je vypnuto, obrÃĄzky nebudou kÃŗdovÃĄny pro inteligentní vyhledÃĄvÃĄní.", "machine_learning_url_description": "URL serveru strojovÊho učení. Pokud je zadÃĄno více URL adres, budou jednotlivÊ servery zkouÅĄeny postupně, dokud jeden z nich neodpoví ÃēspÄ›ÅĄně, a to v pořadí od prvního k poslednímu. Servery, kterÊ neodpoví, budou dočasně ignorovÃĄny, dokud nebudou opět online.", - "maintenance_backup_management": "SprÃĄva zÃĄloh", "maintenance_delete_backup": "Smazat zÃĄlohu", "maintenance_delete_backup_description": "Tento soubor bude trvale smazÃĄn.", "maintenance_delete_error": "Nepodařilo se smazat zÃĄlohu.", - "maintenance_integrity_check": "Zkontrolovat", - "maintenance_integrity_check_all": "Zkontrolovat vÅĄe", - "maintenance_integrity_checksum_mismatch": "Neshoda kontrol. součtu", - "maintenance_integrity_checksum_mismatch_description": "Soubory, u nichÅž se kontrolní součet na disku neshoduje s kontrolním součtem, kterÃŊ Immich uloÅžil do svÊ databÃĄze.", - "maintenance_integrity_checksum_mismatch_job": "Kontrola shod kontrolních součtů", - "maintenance_integrity_checksum_mismatch_refresh_job": "Obnovit hlÃĄÅĄení o neshodÃĄch kontrolních součtů", - "maintenance_integrity_missing_file": "Chybějící soubory", - "maintenance_integrity_missing_file_description": "Soubory, kterÊ Immich sleduje ve svÊ databÃĄzi, ale kterÊ neexistují v souborovÊm systÊmu.", - "maintenance_integrity_missing_file_job": "Kontrola chybějících souborů", - "maintenance_integrity_missing_file_refresh_job": "Obnovit hlÃĄÅĄení o chybějících souborech", - "maintenance_integrity_report": "HlÃĄÅĄení o integritě", - "maintenance_integrity_untracked_file": "NesledovanÊ soubory", - "maintenance_integrity_untracked_file_description": "Soubory v adresÃĄÅ™Ã­ch sluÅžby Immich, o nichÅž nemÃĄ Immich ÅžÃĄdnÃŊ zÃĄznam.", - "maintenance_integrity_untracked_file_job": "Kontrola nesledovanÃŊch souborů", - "maintenance_integrity_untracked_file_refresh_job": "Obnovit hlÃĄÅĄení o nesledovanÃŊch souborech", "maintenance_restore_backup": "Obnovit zÃĄlohu", "maintenance_restore_backup_description": "Immich bude vymazÃĄn a obnoven z vybranÊ zÃĄlohy. Před pokračovÃĄním bude vytvořena zÃĄloha.", "maintenance_restore_backup_different_version": "Tato zÃĄloha byla vytvořena pomocí jinÊ verze aplikace Immich!", @@ -256,7 +232,7 @@ "migration_job": "Migrace", "migration_job_description": "Migrace miniatur snímků a obličejů do nejnovějÅĄÃ­ struktury sloÅžek", "nightly_tasks_cluster_faces_setting_description": "Spustit rozpoznÃĄvÃĄní obličeje na nově nalezenÃŊch obličejích", - "nightly_tasks_cluster_new_faces_setting": "Seskupit novÊ obličeje", + "nightly_tasks_cluster_new_faces_setting": "Seskupit novÊ tvÃĄÅ™e", "nightly_tasks_database_cleanup_setting": "Úlohy čiÅĄtění databÃĄze", "nightly_tasks_database_cleanup_setting_description": "Vyčistit databÃĄzi od starÃŊch dat, jejichÅž platnost vyprÅĄela", "nightly_tasks_generate_memories_setting": "VytvÃĄÅ™ení vzpomínek", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Povolení e-mailovÃŊch oznÃĄmení", "notification_settings": "OznÃĄmení", "notification_settings_description": "SprÃĄva nastavení oznÃĄmení včetně e-mailu", - "oauth_allow_insecure_requests": "Povolit nezabezpečenÊ poÅžadavky", - "oauth_allow_insecure_requests_description": "VAROVÁNÍ: Toto zakÃĄÅže ověřovÃĄní TLS certifikÃĄtů u poÅžadavků OAuth, coÅž vÃĄs můŞe vystavit riziku Ãētoků typu MITM.", "oauth_auto_launch": "AutomatickÊ zahÃĄjení", "oauth_auto_launch_description": "AutomatickÊ zahÃĄjení přihlaÅĄovacího toku OAuth po přechodu na přihlaÅĄovací strÃĄnku", "oauth_auto_register": "AutomatickÃĄ registrace", @@ -300,11 +274,9 @@ "oauth_button_text": "Text tlačítka", "oauth_client_secret_description": "VyÅžadovÃĄno pro důvěrnÊ klienty nebo pokud PKCE (Proof Key for Code Exchange) není podporovÃĄno pro veřejnÊ klienty.", "oauth_enable_description": "PřihlÃĄsit pomocí OAuth", - "oauth_end_session_url_description": "Přesměrovat uÅživatele po odhlÃĄÅĄení na tuto adresu.", "oauth_mobile_redirect_uri": "Mobilní přesměrovÃĄní URI", "oauth_mobile_redirect_uri_override": "Přepsat mobilní přesměrovÃĄní URI", "oauth_mobile_redirect_uri_override_description": "Povolit, pokud poskytovatel OAuth nepovoluje mobilní URI, například ''{callback}''", - "oauth_prompt_description": "Parametr dotazu (např. select_account, login, consent)", "oauth_role_claim": "Deklarace Role", "oauth_role_claim_description": "Automaticky udělit přístup sprÃĄvce na zÃĄkladě přítomnosti tÊto deklarace. Deklarace můŞe mít hodnotu 'user' nebo 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Obnovení vÅĄech knihoven", "registration": "Registrace sprÃĄvce", "registration_description": "Vzhledem k tomu, Åže jste prvním uÅživatelem v systÊmu, budete přiřazen jako sprÃĄvce a budete zodpovědnÃŊ za Ãēkoly sprÃĄvy a dalÅĄÃ­ uÅživatelÊ budou vytvořeni vÃĄmi.", - "release_channel_release_candidate": "KandidÃĄt na vydÃĄní", - "release_channel_stable": "Stabilní", "remove_failed_jobs": "Odebrat neÃēspÄ›ÅĄnÊ Ãēlohy", "require_password_change_on_login": "PoÅžadovat, aby si uÅživatel při prvním přihlÃĄÅĄení změnil heslo", "reset_settings_to_default": "Obnovení vÃŊchozího nastavení", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Platí pouze pro VAAPI a QSV. Nastaví dri uzel pouÅžitÃŊ pro hardwarovÊ překÃŗdovÃĄní.", "transcoding_preset_preset": "Předvolba (-preset)", "transcoding_preset_preset_description": "Rychlost komprese. PomalejÅĄÃ­ předvolby vytvÃĄÅ™ejí menÅĄÃ­ soubory a zvyÅĄují kvalitu při dosaÅžení určitÊho datovÊho toku. VP9 ignoruje rychlosti vyÅĄÅĄÃ­ neÅž 'faster'.", - "transcoding_realtime": "PřekÃŗdovÃĄní v reÃĄlnÊm čase [EXPERIMENTÁLNÍ]", - "transcoding_realtime_description": "UmoŞňuje provÃĄdět překÃŗdovÃĄní v reÃĄlnÊm čase během přenosu videa. Zpřístupní přepínÃĄní kvality, ale v zÃĄvislosti na vÃŊkonu serveru můŞe dochÃĄzet k delÅĄÃ­m zpoÅžděním při přehrÃĄvÃĄní a k trhanÊmu přehrÃĄvÃĄní.", - "transcoding_realtime_enabled": "Povolit překÃŗdovÃĄní v reÃĄlnÊm čase", - "transcoding_realtime_enabled_description": "Pokud je zakÃĄzÃĄno, server odmítne spustit novÊ relace překÃŗdovÃĄní v reÃĄlnÊm čase.", - "transcoding_realtime_resolutions": "RozliÅĄení", - "transcoding_realtime_resolutions_description": "NabízenÃĄ rozliÅĄení pro transkÃŗdovÃĄní v reÃĄlnÊm čase. VyÅĄÅĄÃ­ rozliÅĄení mohou způsobit problÊmy s přehrÃĄvÃĄním, pokud je server nedokÃĄÅže dostatečně rychle transkÃŗdovat.", - "transcoding_realtime_video_codecs": "Videokodeky", - "transcoding_realtime_video_codecs_description": "Videokodeky nabízenÊ pro transkÃŗdovÃĄní v reÃĄlnÊm čase. Klienti si při přehrÃĄvÃĄní vyberou nejlepÅĄÃ­ variantu, kterou podporují. AV1 je efektivnějÅĄÃ­ neÅž HEVC, kterÃŊ je zase efektivnějÅĄÃ­ neÅž H.264. Při pouÅžití hardwarovÊ akcelerace vyberte pouze ty kodeky, kterÊ akcelerÃĄtor dokÃĄÅže kÃŗdovat. Při pouÅžití softwarovÊho transkÃŗdovÃĄní mějte na paměti, Åže H.264 je rychlejÅĄÃ­ neÅž AV1, kterÃŊ je zase rychlejÅĄÃ­ neÅž HEVC.", "transcoding_reference_frames": "Referenční snímky", "transcoding_reference_frames_description": "Počet referenčních snímků při kompresi danÊho snímku. VyÅĄÅĄÃ­ hodnoty zvyÅĄují Ãēčinnost komprese, ale zpomalují kÃŗdovÃĄní. Hodnota 0 toto nastavuje automaticky.", "transcoding_required_description": "Pouze videa, kterÃĄ nejsou v akceptovanÊm formÃĄtu", @@ -478,8 +440,6 @@ "user_settings_description": "SprÃĄva nastavení uÅživatelů", "user_successfully_removed": "UÅživatel {email} byl ÃēspÄ›ÅĄně odstraněn.", "users_page_description": "StrÃĄnka sprÃĄvců", - "version_check_channel": "KanÃĄl vydÃĄní", - "version_check_channel_description": "Vyberte si kanÃĄl vydÃĄní, pro kterÃŊ chcete dostÃĄvat oznÃĄmení o novÃŊch verzích", "version_check_enabled_description": "Povolit kontrolu verzí", "version_check_implications": "Kontrola verze je zaloÅžena na pravidelnÊ komunikaci s {server}", "version_check_settings": "Kontrola verze", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Vyčistit mezipaměÅĨ obrÃĄzků", "advanced_settings_clear_image_cache_error": "Chyba při čiÅĄtění mezipaměti obrÃĄzků", "advanced_settings_clear_image_cache_success": "ÚspÄ›ÅĄně vyčiÅĄtěno {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Tuto moÅžnost pouÅžijte k filtrovÃĄní mÊdií během synchronizace na zÃĄkladě alternativních kritÊrií. Tuto moÅžnost vyzkouÅĄejte pouze v případě, Åže mÃĄte problÊmy s detekcí vÅĄech alb v aplikaci.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTÁLNÍ] PouŞít alternativní filtr pro synchronizaci alb zařízení", "advanced_settings_log_level_title": "Úroveň protokolovÃĄní: {level}", "advanced_settings_prefer_remote_subtitle": "U některÃŊch zařízení je načítÃĄní miniatur z lokÃĄlních prostředků velmi pomalÊ. Aktivujte toto nastavení, aby se místo toho načítaly vzdÃĄlenÊ obrÃĄzky.", "advanced_settings_prefer_remote_title": "Preferovat vzdÃĄlenÊ obrÃĄzky", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Vlastní proxy hlavičky [EXPERIMENTÁLNÍ]", "advanced_settings_readonly_mode_subtitle": "Povoluje reÅžim pouze pro čtení, ve kterÊm lze fotografie pouze prohlíŞet, ale funkce jako vÃŊběr více obrÃĄzků, sdílení, přenos, mazÃĄní jsou zakÃĄzÃĄny. Povolení/zakÃĄzÃĄní reÅžimu pouze pro čtení pomocí avatara uÅživatele na hlavní obrazovce", "advanced_settings_readonly_mode_title": "ReÅžim pouze pro čtení", + "advanced_settings_self_signed_ssl_subtitle": "VynechÃĄ ověření SSL certifikÃĄtu serveru. VyÅžadovÃĄno pro self-signed certifikÃĄty.", + "advanced_settings_self_signed_ssl_title": "Povolit self-signed SSL certifikÃĄty [EXPERIMENTÁLNÍ]", "advanced_settings_sync_remote_deletions_subtitle": "Automaticky odstranit nebo obnovit poloÅžku v tomto zařízení, kdyÅž je tato akce provedena na webu", "advanced_settings_sync_remote_deletions_title": "Synchronizace vzdÃĄlenÊho mazÃĄní [EXPERIMENTÁLNÍ]", "advanced_settings_tile_subtitle": "PokročilÊ uÅživatelskÊ nastavení", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Opravdu chcete album {album} odstranit?", "album_delete_confirmation_description": "Pokud je toto album sdíleno, ostatní uÅživatelÊ k němu jiÅž nebudou mít přístup.", "album_deleted": "Album smazÃĄno", + "album_info_card_backup_album_excluded": "VYLOUČENO", + "album_info_card_backup_album_included": "ZAHRNUTO", "album_info_updated": "Informace o albu aktualizovÃĄny", + "album_leave": "Opustit album?", + "album_leave_confirmation": "Opravdu chcete opustit {album}?", "album_name": "NÃĄzev alba", "album_options": "MoÅžnosti alba", "album_remove_user": "Odebrat uÅživatele?", "album_remove_user_confirmation": "Opravdu chcete odebrat uÅživatele {user}?", "album_search_not_found": "Nebyla nalezena ÅžÃĄdnÃĄ alba odpovídající vaÅĄemu hledÃĄní", + "album_selected": "Album vybrÃĄno", "album_share_no_users": "Zřejmě jste toto album sdíleli se vÅĄemi uÅživateli, nebo nemÃĄte ÅžÃĄdnÊho uÅživatele, se kterÃŊm byste ho mohli sdílet.", "album_summary": "Souhrn alba", "album_updated": "Album aktualizovÃĄno", "album_updated_setting_description": "DostÃĄvat e-mailovÃĄ oznÃĄmení o novÃŊch poloÅžkÃĄch sdílenÊho alba", "album_upload_assets": "Nahrajte soubory z počítače a přidejte je do alba", + "album_user_left": "Opustil {album}", + "album_user_removed": "UÅživatel {user} odebrÃĄn", + "album_viewer_appbar_delete_confirm": "Opravdu chcete toto album odstranit ze svÊho Ãēčtu?", "album_viewer_appbar_share_err_delete": "Nepodařilo se smazat album", + "album_viewer_appbar_share_err_leave": "Nepodařilo se opustit album", + "album_viewer_appbar_share_err_remove": "Při odstraňovÃĄní poloÅžek z alba se vyskytly problÊmy", + "album_viewer_appbar_share_err_title": "Nepodařilo se změnit nÃĄzev alba", + "album_viewer_appbar_share_leave": "Opustit album", + "album_viewer_appbar_share_to": "Sdílet na", "album_viewer_page_share_add_users": "Přidat uÅživatele", "album_with_link_access": "Nechte kohokoli s odkazem zobrazit fotografie a lidi v tomto albu.", "albums": "Alba", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "VÃŊchozí řazení poloÅžek při vytvÃĄÅ™ení novÃŊch alb.", "albums_feature_description": "Sbírky poloÅžek, kterÊ lze sdílet s ostatními uÅživateli.", "albums_on_device_count": "Alba v zařízení ({count})", + "albums_selected": "{count, plural, one {# album vybrÃĄno} few {# alba vybrÃĄny} other {# alb vybrÃĄno}}", "all": "VÅĄe", "all_albums": "VÅĄechna alba", "all_people": "VÅĄichni lidÊ", "all_photos": "VÅĄechny fotky", "all_videos": "VÅĄechna videa", "allow_dark_mode": "Povolit tmavÃŊ reÅžim", + "allow_edits": "Povolit Ãēpravy", "allow_public_user_to_download": "Povolit veřejnosti stahovat", "allow_public_user_to_upload": "Povolit veřejnosti nahrÃĄvat", "allowed": "Povoleno", @@ -549,12 +528,14 @@ "always_keep": "PokaÅždÊ ponechat", "always_keep_photos_hint": "Uvolnění místa ponechÃĄ vÅĄechny fotky na tomto zařízení.", "always_keep_videos_hint": "Uvolnění místa ponechÃĄ vÅĄechny videa na tomto zařízení.", + "anti_clockwise": "Proti směru hodinovÃŊch ručiček", "api_key": "API klíč", "api_key_description": "Tato hodnota se zobrazí pouze jednou. Před zavřením okna ji nezapomeňte zkopírovat.", "api_key_empty": "NÃĄzev klíče API by neměl bÃŊt prÃĄzdnÃŊ", "api_keys": "API klíče", "app_architecture_variant": "Varianta (architektura)", "app_bar_signout_dialog_content": "Určitě se chcete odhlÃĄsit?", + "app_bar_signout_dialog_ok": "Ano", "app_bar_signout_dialog_title": "OdhlÃĄsit se", "app_download_links": "Odkazy ke staÅžení aplikace", "app_settings": "Aplikace", @@ -565,19 +546,27 @@ "archive": "Archiv", "archive_action_prompt": "{count} přidanÃŊch do archivu", "archive_or_unarchive_photo": "Přidat nebo odebrat fotku z archivu", + "archive_page_no_archived_assets": "Nebyla nalezena ÅžÃĄdnÃĄ archivovanÃĄ mÊdia", + "archive_page_title": "Archiv ({count})", "archive_size": "Velikost archivu", "archive_size_description": "Nastavte velikost archivu pro stahovÃĄní (v GiB)", "archived": "Archiv", "archived_count": "{count, plural, other {ArchivovÃĄno #}}", "are_these_the_same_person": "JednÃĄ se o stejnou osobu?", "are_you_sure_to_do_this": "Opravdu to chcete udělat?", + "array_field_not_fully_supported": "Prvky pole vyÅžadují ruční Ãēpravy JSON", + "asset_action_delete_err_read_only": "Nelze odstranit poloÅžky pouze pro čtení, přeskakuji", + "asset_action_share_err_offline": "Nelze načíst offline poloÅžky, přeskakuji", "asset_added_to_album": "PřidÃĄno do alba", "asset_adding_to_album": "PřidÃĄvÃĄní do albaâ€Ļ", "asset_created": "PoloÅžka vytvořena", - "asset_day_count": "{date}: {count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžek}}", "asset_description_updated": "Popis poloÅžky byl aktualizovÃĄn", + "asset_filename_is_offline": "PoloÅžka {filename} je offline", + "asset_has_unassigned_faces": "PoloÅžka mÃĄ nepřiřazenÊ obličeje", "asset_hashing": "HashovÃĄníâ€Ļ", "asset_list_group_by_sub_title": "Seskupit podle", + "asset_list_layout_settings_dynamic_layout_title": "DynamickÊ rozloÅžení", + "asset_list_layout_settings_group_automatically": "Automaticky", "asset_list_layout_settings_group_by": "Seskupit poloÅžky podle", "asset_list_layout_settings_group_by_month_day": "Měsíc + den", "asset_list_layout_sub_title": "RozloÅžení", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "PoloÅžka nebyla nalezena na iCloudu. PoloÅžka můŞe bÃŊt nepřístupnÃĄ kvůli poÅĄkozenÊmu souboru uloÅženÊmu na iCloudu", "asset_offline": "Offline poloÅžka", "asset_offline_description": "Toto externí poloÅžka se jiÅž na disku nenachÃĄzí. ObraÅĨte se na sprÃĄvce Immich a poÅžÃĄdejte o pomoc.", + "asset_restored_successfully": "PoloÅžka ÃēspÄ›ÅĄně obnovena", "asset_skipped": "Přeskočeno", "asset_skipped_in_trash": "V koÅĄi", + "asset_trashed": "PoloÅžka vyhozena", "asset_troubleshoot": "ŘeÅĄení problÊmů s poloÅžkami", "asset_uploaded": "NahrÃĄno", "asset_uploading": "NahrÃĄvÃĄníâ€Ļ", "asset_viewer_settings_subtitle": "SprÃĄva nastavení prohlíŞeče galerie", "asset_viewer_settings_title": "ProhlíŞeč", "assets": "PoloÅžky", + "assets_added_count": "{count, plural, one {PřidÃĄna # poloÅžka} few {PřidÃĄny # poloÅžky} other {PřidÃĄno # poloÅžek}}", "assets_added_to_album_count": "Do alba {count, plural, one {byla přidÃĄna # poloÅžka} few {byly přidÃĄny # poloÅžky} other {bylo přidÃĄno # poloÅžek}}", - "assets_added_to_album_partial_count": "Do alba bylo přidÃĄno {successCount} z {totalCount} {totalCount, plural, one {aktiv} other {aktiv}}", "assets_added_to_albums_count": "{assetTotal, plural, one {PřidÃĄna # poloÅžka} few{PřidÃĄny # poloÅžky} other {PřidÃĄno # poloÅžek}} do {albumTotal, plural, one {# alba} other {# alb}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {PoloÅžku} other {PoloÅžky}} nelze přidat do alba", "assets_cannot_be_added_to_albums": "{count, plural, one {PoloÅžku} other {PoloÅžky}} nelze přidat do ÅžÃĄdnÊho z alb", "assets_count": "{count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžek}}", + "assets_deleted_permanently": "{count} poloÅžek trvale odstraněno", + "assets_deleted_permanently_from_server": "{count} poloÅžek trvale odstraněno z Immich serveru", + "assets_downloaded_failed": "{count, plural, one {StaÅžen # soubor - {error} souborů selhalo} few {StaÅženy # soubory - {error} souborů selhalo} other {StaÅženo # souborů - {error} souborů selhalo}}", + "assets_downloaded_successfully": "{count, plural, one {ÚspÄ›ÅĄně staÅžen # soubor} few {ÚspÄ›ÅĄně staÅženy # soubory} other {ÚspÄ›ÅĄně staÅženo # souborů}}", "assets_moved_to_trash_count": "{count, plural, one {# poloÅžka přesunuta} few {# poloÅžky přesunuty} other {# poloÅžek přesunuto}} do koÅĄe", "assets_permanently_deleted_count": "Trvale {count, plural, one {smazÃĄna # poloÅžka} few {smazÃĄny # poloÅžky} other {smazÃĄno # poloÅžek}}", "assets_removed_count": "{count, plural, one {Odstraněna # poloÅžka} few {Odstraněny # poloÅžky} other {Odstraněno # poloÅžek}}", + "assets_removed_permanently_from_device": "{count} poloÅžek trvale odstraněno z vaÅĄeho zařízení", "assets_restore_confirmation": "Opravdu chcete obnovit vÅĄechny vyhozenÊ poloÅžky? Tuto akci nelze vrÃĄtit zpět! Upozorňujeme, Åže tímto způsobem nelze obnovit ÅžÃĄdnÊ offline poloÅžky.", "assets_restored_count": "{count, plural, one {Obnovena # poloÅžka} few {Obnoveny # poloÅžky} other {Obnoveno # poloÅžek}}", + "assets_restored_successfully": "{count} poloÅžek ÃēspÄ›ÅĄně obnoveno", "assets_trashed": "{count} poloÅžek vyhozeno do koÅĄe", "assets_trashed_count": "{count, plural, one {Vyhozena # poloÅžka} few {Vyhozeny # poloÅžky} other {Vyhozeno # poloÅžek}}", + "assets_trashed_from_server": "{count} poloÅžek vyhozeno do koÅĄe na Immich serveru", "assets_were_part_of_album_count": "{count, plural, one {PoloÅžka byla} other {PoloÅžky byly}} souÄÃĄstí alba", "assets_were_part_of_albums_count": "{count, plural, one {PoloÅžka jiÅž byla} other {PoloÅžky jiÅž byly}} souÄÃĄstí alb", "authorized_devices": "AutorizovanÃĄ zařízení", @@ -617,48 +615,87 @@ "autoplay_slideshow": "AutomatickÊ přehrÃĄvÃĄní prezentace", "back": "Zpět", "back_close_deselect": "Zpět, zavřít nebo zruÅĄit vÃŊběr", + "background_backup_running_error": "PrÃĄvě probíhÃĄ zÃĄlohovÃĄní na pozadí, nelze spustit ruční zÃĄlohovÃĄní", "background_location_permission": "Povolení polohy na pozadí", "background_location_permission_content": "Aby bylo moÅžnÊ přepínat sítě při běhu na pozadí, musí mít Immich *vÅždy* přístup k přesnÊ poloze, aby mohl zjistit nÃĄzev Wi-Fi sítě", "background_options": "MoÅžnosti běhu na pozadí", "backup": "ZÃĄloha", + "backup_album_selection_page_albums_device": "Alba v zařízení ({count})", "backup_album_selection_page_albums_tap": "Klepnutím na poloÅžku ji zahrnete, opětovnÃŊm klepnutím ji vyloučíte", "backup_album_selection_page_assets_scatter": "PoloÅžky mohou bÃŊt roztrouÅĄeny ve více albech. To umoŞňuje zahrnout nebo vyloučit alba během procesu zÃĄlohovÃĄní.", "backup_album_selection_page_select_albums": "VybranÃĄ alba", "backup_album_selection_page_selection_info": "Informace o vÃŊběru", + "backup_album_selection_page_total_assets": "CelkovÃŊ počet jedinečnÃŊch poloÅžek", "backup_albums_sync": "Synchronizace zÃĄlohovanÃŊch alb", + "backup_all": "VÅĄe", + "backup_background_service_backup_failed_message": "ZÃĄlohovÃĄní mÊdií selhalo. ZkouÅĄÃ­m to znovuâ€Ļ", "backup_background_service_complete_notification": "ZÃĄlohovÃĄní poloÅžek dokončeno", + "backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. ZkouÅĄÃ­m to znovuâ€Ļ", + "backup_background_service_current_upload_notification": "NahrÃĄvÃĄní {filename}", "backup_background_service_default_notification": "Kontrola novÃŊch mÊdiíâ€Ļ", + "backup_background_service_error_title": "Chyba zÃĄlohovÃĄní", "backup_background_service_in_progress_notification": "ZÃĄlohovÃĄní vaÅĄich mÊdiíâ€Ļ", + "backup_background_service_upload_failure_notification": "Nepodařilo se nahrÃĄt {filename}", "backup_controller_page_albums": "ZÃĄlohovanÃĄ alba", + "backup_controller_page_background_app_refresh_disabled_content": "Povolte obnovení aplikace na pozadí v Nastavení > ObecnÊ > Obnovení aplikace na pozadí, abyste mohli pouŞívat zÃĄlohovÃĄní na pozadí.", + "backup_controller_page_background_app_refresh_disabled_title": "ObnovovÃĄní aplikací na pozadí je vypnutÊ", + "backup_controller_page_background_app_refresh_enable_button_text": "Přejít do nastavení", "backup_controller_page_background_battery_info_link": "UkaÅž mi jak", "backup_controller_page_background_battery_info_message": "Chcete-li dosÃĄhnout nejlepÅĄÃ­ch vÃŊsledků při zÃĄlohovÃĄní na pozadí, vypněte vÅĄechny optimalizace baterie, kterÊ omezují aktivitu na pozadí pro Immich ve vaÅĄem zařízení. \n\nJelikoÅž je to zÃĄvislÊ na typu zařízení, vyhledejte poÅžadovanÊ informace pro vÃŊrobce vaÅĄeho zařízení.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Optimalizace baterie", + "backup_controller_page_background_charging": "Pouze během nabíjení", + "backup_controller_page_background_configure_error": "Nepodařilo se nakonfigurovat sluÅžbu na pozadí", "backup_controller_page_background_delay": "ZpoÅždění zÃĄlohovÃĄní novÃŊch mÊdií: {duration}", + "backup_controller_page_background_description": "Povolte sluÅžbu na pozadí pro automatickÊ zÃĄlohovÃĄní vÅĄech novÃŊch poloÅžek bez nutnosti otevření aplikace", + "backup_controller_page_background_is_off": "AutomatickÊ zÃĄlohovÃĄní na pozadí je vypnuto", + "backup_controller_page_background_is_on": "AutomatickÊ zÃĄlohovÃĄní na pozadí je zapnuto", + "backup_controller_page_background_turn_off": "Vypnout zÃĄlohovÃĄní na pozadí", + "backup_controller_page_background_turn_on": "Povolit zÃĄlohovÃĄní na pozadí", + "backup_controller_page_background_wifi": "Jen na Wi-Fi", "backup_controller_page_backup": "ZÃĄlohovÃĄní", "backup_controller_page_backup_selected": "VybranÊ: ", "backup_controller_page_backup_sub": "ZÃĄlohovanÊ fotografie a videa", + "backup_controller_page_created": "Vytvořeno: {date}", + "backup_controller_page_desc_backup": "Zapněte zÃĄlohovÃĄní na popředí, aby se novÊ poloÅžky automaticky nahrÃĄvaly na server při otevření aplikace.", "backup_controller_page_excluded": "Vyloučeno: ", + "backup_controller_page_failed": "Nepodařilo se ({count})", + "backup_controller_page_filename": "NÃĄzev souboru: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informace o zÃĄlohovÃĄní", "backup_controller_page_none_selected": "ÅŊÃĄdnÊ vybranÊ", "backup_controller_page_remainder": "ZbÃŊvÃĄ", "backup_controller_page_remainder_sub": "ZbÃŊvající fotografie a videa, kterÃĄ se mají zÃĄlohovat z vybranÃŊch alb", "backup_controller_page_server_storage": "ServerovÊ ÃēloÅžiÅĄtě", + "backup_controller_page_start_backup": "Spustit zÃĄlohovÃĄní", + "backup_controller_page_status_off": "AutomatickÊ zÃĄlohovÃĄní na popředí je vypnuto", + "backup_controller_page_status_on": "AutomatickÊ zÃĄlohovÃĄní na popředí je zapnuto", "backup_controller_page_storage_format": "{used} z {total} pouÅžitÃŊch", "backup_controller_page_to_backup": "Alba, kterÃĄ mají bÃŊt zÃĄlohovÃĄna", "backup_controller_page_total_sub": "VÅĄechny jedinečnÊ fotografie a videa z vybranÃŊch alb", + "backup_controller_page_turn_off": "Vypnout zÃĄlohovÃĄní na popředí", + "backup_controller_page_turn_on": "Povolit zÃĄlohovÃĄní na popředí", + "backup_controller_page_uploading_file_info": "Informace o nahranÊm souboru", + "backup_err_only_album": "Nelze odstranit jedinÊ vybranÊ album", "backup_error_sync_failed": "Synchronizace selhala. Nelze zpracovat zÃĄlohu.", "backup_info_card_assets": "poloÅžek", + "backup_manual_cancelled": "ZruÅĄeno", + "backup_manual_in_progress": "NahrÃĄvÃĄní jiÅž probíhÃĄ. Zkuste to znovu později", + "backup_manual_success": "Úspěch", + "backup_manual_title": "Stav nahrÃĄvÃĄní", "backup_options": "MoÅžnosti zÃĄlohovÃĄní", + "backup_options_page_title": "Nastavení zÃĄloh", + "backup_setting_subtitle": "SprÃĄva nastavení zÃĄlohovÃĄní na pozadí a na popředí", "backup_settings_subtitle": "SprÃĄva nastavení nahrÃĄvÃĄní", + "backup_upload_details_page_more_details": "Klepněte pro více informací", "backward": "PozpÃĄtku", - "battery_optimization_backup_reliability": "ZakÃĄzÃĄní optimalizací baterie můŞe zlepÅĄit spolehlivost zÃĄlohovÃĄní na pozadí", "biometric_auth_enabled": "BiometrickÊ ověřovÃĄní je povoleno", "biometric_locked_out": "Jste vyloučeni z biometrickÊho ověřovÃĄní", "biometric_no_options": "BiometrickÊ moÅžnosti nejsou k dispozici", "biometric_not_available": "BiometrickÊ ověřovÃĄní není na tomto zařízení k dispozici", + "birthdate_saved": "Datum narození ÃēspÄ›ÅĄně uloÅženo", "birthdate_set_description": "Datum narození se pouŞívÃĄ k vÃŊpočtu věku osoby v době pořízení fotografie.", "blurred_background": "RozmazanÊ pozadí", - "browse_templates": "ProchÃĄzet ÅĄablony", "bugs_and_feature_requests": "Chyby a nÃĄvrhy na funkce", "build": "Sestavení", "build_image": "Sestavení obrazu", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Opravdu si chcete ponechat {count, plural, one {# duplicitní poloÅžku} few {# duplicitní poloÅžky} other {# duplicitních poloÅžek}}? Tím se vyřeÅĄÃ­ vÅĄechny duplicitní skupiny, aniÅž by se cokoli odstranilo.", "bulk_trash_duplicates_confirmation": "Opravdu chcete hromadně vyhodit {count, plural, one {# duplicitní poloÅžku} few {# duplicitní poloÅžky} other {# duplicitních poloÅžek}}? Tím se zachovÃĄ největÅĄÃ­ poloÅžka z kaÅždÊ skupiny a vÅĄechny ostatní duplikÃĄty se vyhodí.", "buy": "Zakoupit Immich", + "cache_settings_clear_cache_button": "Vymazat vyrovnÃĄvací paměÅĨ", + "cache_settings_clear_cache_button_title": "VymaÅže vyrovnÃĄvací paměÅĨ aplikace. To vÃŊrazně ovlivní vÃŊkon aplikace, dokud se vyrovnÃĄvací paměÅĨ neobnoví.", + "cache_settings_duplicated_assets_clear_button": "VYMAZAT", + "cache_settings_duplicated_assets_subtitle": "Fotografie a videa, kterÊ aplikace ignoruje", + "cache_settings_duplicated_assets_title": "Duplicitní poloÅžky ({count})", + "cache_settings_statistics_album": "Knihovna nÃĄhledů", + "cache_settings_statistics_full": "Kompletní fotografie", + "cache_settings_statistics_shared": "SdílenÊ nÃĄhledy alb", + "cache_settings_statistics_thumbnail": "NÃĄhledy", + "cache_settings_statistics_title": "PouÅžití vyrovnÃĄvací paměti", + "cache_settings_subtitle": "OvlÃĄdÃĄní chovÃĄní mobilní aplikace Immich v mezipaměti", + "cache_settings_tile_subtitle": "OvlÃĄdÃĄní chovÃĄní místního ÃēloÅžiÅĄtě", + "cache_settings_tile_title": "Místní ÃēloÅžiÅĄtě", + "cache_settings_title": "Nastavení vyrovnÃĄvací paměti", "camera": "FotoaparÃĄt", "camera_brand": "Značka fotoaparÃĄtu", "camera_model": "Model fotoaparÃĄtu", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Nelze aktualizovat popis", "cast": "Odeslat do zařízení", "cast_description": "Nastavení dostupnÃŊch cílů přenosu", - "change": "Změnit", "change_date": "Změnit datum", "change_description": "Změnit popis", "change_display_order": "Změnit pořadí zobrazení", + "change_expiration_time": "Změna konce platnosti", "change_location": "Změna polohy", "change_name": "Změnit jmÊno", "change_name_successfully": "JmÊno bylo ÃēspÄ›ÅĄně změněno", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Hesla se neshodují", "change_password_form_reenter_new_password": "Znovu zadejte novÊ heslo", "change_pin_code": "Změnit PIN kÃŗd", + "change_trigger": "SpouÅĄtěč změny", + "change_trigger_prompt": "Opravdu chcete změnit spouÅĄtěč? Tím se odstraní vÅĄechny existující akce a filtry.", "change_your_password": "Změna vaÅĄeho hesla", "changed_visibility_successfully": "Změna viditelnosti proběhla ÃēspÄ›ÅĄně", "charging": "Nabíjení", "charging_requirement_mobile_backup": "ZÃĄlohovÃĄní na pozadí vyÅžaduje, aby bylo zařízení nabíjeno", + "check_corrupt_asset_backup": "Kontrola poÅĄkozenÃŊch zÃĄloh poloÅžek", + "check_corrupt_asset_backup_button": "ProvÊst kontrolu", + "check_corrupt_asset_backup_description": "Tuto kontrolu provÃĄdějte pouze přes Wi-Fi a po zÃĄlohovÃĄní vÅĄech prostředků. Takto operace můŞe trvat několik minut.", "check_logs": "Zkontrolujte protokoly", "checksum": "Kontrolní součet", - "choose": "Vybrat", "choose_matching_people_to_merge": "Zvolte odpovídající osoby ke sloučení", "city": "Město", "cleanup_confirm_description": "Immich naÅĄel {count} poloÅžek (vytvořenÃŊch před {date}), kterÊ jsou bezpečně zÃĄlohovÃĄny na serveru. Chcete odstranit místní kopie z tohoto zařízení?", @@ -719,10 +774,11 @@ "clear": "Vymazat", "clear_all": "Vymazat vÅĄe", "clear_all_recent_searches": "Vymazat vÅĄechna nedÃĄvnÃĄ vyhledÃĄvÃĄní", - "clear_failed_count": "VymazÃĄní selhalo ({count})", "clear_file_cache": "Vymazat mezipaměÅĨ souborů", "clear_message": "Vymazat zprÃĄvu", "clear_value": "Vymazat hodnotu", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Zadejte heslo", "client_cert_import": "Importovat", "client_cert_import_success_msg": "KlientskÃŊ certifikÃĄt je importovÃĄn", "client_cert_invalid_msg": "NeplatnÃŊ soubor certifikÃĄtu nebo ÅĄpatnÊ heslo", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "KlientskÃŊ certifikÃĄt je odstraněn", "client_cert_subtitle": "Podporuje pouze formÃĄt PKCS12 (.p12, .pfx). Import/odstranění certifikÃĄtu je moÅžnÊ pouze před přihlÃĄÅĄením", "client_cert_title": "KlientskÃŊ SSL certifikÃĄt [EXPERIMENTÁLNÍ]", + "clockwise": "Po směru hodinovÃŊch ručiček", "close": "Zavřít", "collapse": "Sbalit", "collapse_all": "Sbalit vÅĄe", "color": "Barva", + "color_theme": "BarevnÃŊ motiv", "command": "Příkaz", "command_palette_prompt": "RychlÊ vyhledÃĄvÃĄní strÃĄnek, akcí nebo příkazů", "command_palette_to_close": "zavřít", @@ -747,7 +805,6 @@ "comments_are_disabled": "KomentÃĄÅ™e jsou vypnuty", "common_create_new_album": "Vytvořit novÊ album", "completed": "Dokončeno", - "configuration": "Nastavení", "confirm": "Potvrdit", "confirm_admin_password": "Potvrzení hesla sprÃĄvce", "confirm_delete_face": "Opravdu chcete z poloÅžky odstranit obličej osoby {name}?", @@ -762,16 +819,19 @@ "contain": "Obsah", "context": "Kontext", "continue": "Pokračovat", - "control_bottom_app_bar_add_tags": "Přidat značky", + "control_bottom_app_bar_create_new_album": "Vytvořit novÊ album", + "control_bottom_app_bar_delete_from_immich": "Smazat ze serveru Immich", "control_bottom_app_bar_delete_from_local": "Smazat ze zařízení", "control_bottom_app_bar_edit_location": "Upravit polohu", "control_bottom_app_bar_edit_time": "Upravit datum a čas", + "control_bottom_app_bar_share_link": "Sdílet odkaz", + "control_bottom_app_bar_share_to": "Sdílet v", "control_bottom_app_bar_trash_from_immich": "Přesunout do koÅĄe", "copied_image_to_clipboard": "ObrÃĄzek zkopírovÃĄn do schrÃĄnky.", "copied_to_clipboard": "ZkopírovÃĄno do schrÃĄnky!", "copy_error": "Chyba kopírovÃĄní", + "copy_file_path": "Kopírovat cestu k souboru", "copy_image": "Kopírovat obrÃĄzek", - "copy_json": "Kopírovat JSON", "copy_link": "Kopírovat odkaz", "copy_link_to_clipboard": "Kopírovat odkaz do schrÃĄnky", "copy_password": "Kopírovat heslo", @@ -789,6 +849,7 @@ "create_link_to_share": "Vytvořit odkaz pro sdílení", "create_link_to_share_description": "UmoÅžnit kaÅždÊmu, kdo mÃĄ odkaz, zobrazit vybranÊ fotografie", "create_new": "VYTVOŘIT NOVÉ", + "create_new_face": "Vytvořit novÃŊ obličej", "create_new_person": "Vytvořit novou osobu", "create_new_person_hint": "Přiřadit vybranÊ poloÅžky novÊ osobě", "create_new_user": "Vytvořit novÊho uÅživatele", @@ -805,33 +866,37 @@ "created_at": "Vytvořeno", "creating_linked_albums": "VytvÃĄÅ™ení propojenÃŊch alb...", "crop": "Oříznout", + "crop_aspect_ratio_fixed": "PevnÃŊ", "crop_aspect_ratio_free": "VolnÃŊ", "crop_aspect_ratio_original": "Původní", "crop_aspect_ratio_square": "Čtverec", + "curated_object_page_title": "Věci", "current_device": "SoučasnÊ zařízení", "current_pin_code": "AktuÃĄlní PIN kÃŗd", "current_server_address": "AktuÃĄlní adresa serveru", "custom_date": "Vlastní datum", "custom_locale": "Vlastní lokalizace", "custom_locale_description": "FormÃĄtovat datumy, časy a čísla podle vybranÊho jazyka a oblasti", + "custom_url": "Vlastní URL", "cutoff_date_description": "Zanechat fotografie a videa z posledníchâ€Ļ", "cutoff_day": "{count, plural, one {den} few {dny} other {dnů}}", "cutoff_year": "{count, plural, one {rok} few {roky} other {let}}", + "daily_title_text_date": "EEEE, d. MMMM", + "daily_title_text_date_year": "EEEE, d. MMMM y", "dark": "TmavÃŊ", "dark_theme": "Přepnout na tmavÃŊ motiv", "date": "Datum", "date_after": "Datum po", "date_and_time": "Datum a čas", "date_before": "Datum před", - "date_of_birth": "Datum narození", + "date_format": "EEEE, d. MMMM y â€ĸ H:mm", "date_of_birth_saved": "Datum narození ÃēspÄ›ÅĄně uloÅženo", "date_range": "Rozsah dat", - "date_time_original": "Původní datum/čas", "day": "Den", "days": "Dnů", "deduplicate_all": "Odstranit vÅĄechny duplicity", - "default_quality_subtitle": "Kvalita pouÅžitÃĄ při sdílení. Pro vÃŊběr pokaÅždÊ dlouze stiskněte tlačítko sdílení.", - "default_share_quality": "VÃŊchozí kvalita sdílení", + "default_locale": "VÃŊchozí nÃĄrodní prostředí", + "default_locale_description": "FormÃĄtovÃĄní datumu a čísel podle místního nastavení prohlíŞeče", "delete": "Smazat", "delete_action_confirmation_message": "Opravdu chcete odstranit tuto poloÅžku? Tato akce přesune poloÅžku do serverovÊho koÅĄe a zeptÃĄ se vÃĄs, zda ji chcete odstranit lokÃĄlně", "delete_action_prompt": "{count} smazÃĄno", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Tyto poloÅžky budou trvale smazÃĄny z aplikace Immich i z vaÅĄeho zařízení", "delete_dialog_alert_local": "Tyto poloÅžky budou z vaÅĄeho zařízení trvale smazÃĄny, ale budou stÃĄle k dispozici na Immich serveru", "delete_dialog_alert_local_non_backed_up": "NěkterÊ poloÅžky nejsou zÃĄlohovÃĄny na Immich server a budou ze zařízení trvale smazÃĄny", + "delete_dialog_alert_remote": "Tyto poloÅžky budou trvale smazÃĄny z Immich serveru", + "delete_dialog_ok_force": "Přesto smazat", "delete_dialog_title": "Smazat trvale", "delete_duplicates_confirmation": "Opravdu chcete tyto duplicity trvale odstranit?", "delete_face": "Odstranit obličej", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Opravdu chcete odstranit značku {tagName}?", "delete_user": "Odstranit uÅživatele", "deleted_shared_link": "Smazat sdílenÃŊ odkaz", + "deletes_missing_assets": "Odstraní poloÅžky chybějící na disku", "description": "Popis", + "description_input_hint_text": "Přidat popis...", + "description_input_submit_error": "Chyba aktualizace popisu, dalÅĄÃ­ podrobnosti najdete v logu", "deselect_all": "ZruÅĄit vÃŊběr vÅĄech", "details": "Podrobnosti", "direction": "Směr", "disable": "ZakÃĄzat", "disabled": "ZakÃĄzÃĄno", + "disallow_edits": "ZakÃĄzat Ãēpravy", "discord": "Discord", "discover": "Objevit", "discovered_devices": "NalezenÃĄ zařízení", "dismiss_all_errors": "ZruÅĄit vÅĄechny chyby", + "dismiss_error": "ZruÅĄit chybu", "display_options": "MoÅžnosti zobrazení", "display_order": "Pořadí zobrazení", "display_original_photos": "Zobrazit originÃĄlní fotky", @@ -876,9 +948,11 @@ "documentation": "Dokumentace", "done": "Hotovo", "download": "StÃĄhnout", + "download_action_prompt": "StahovÃĄní {count} poloÅžek", "download_canceled": "StahovÃĄní zruÅĄeno", "download_complete": "StahovÃĄní kompletní", "download_enqueue": "StahovÃĄní ve frontě", + "download_error": "Chyba při stahovÃĄní", "download_failed": "StahovÃĄní selhalo", "download_finished": "StahovÃĄní dokončeno", "download_include_embedded_motion_videos": "VloÅženÃĄ videa", @@ -888,15 +962,15 @@ "download_paused": "StahovÃĄní pozastaveno", "download_settings": "StahovÃĄní", "download_settings_description": "SprÃĄva nastavení souvisejících se stahovÃĄním", + "download_started": "StahovÃĄní zahÃĄjeno", + "download_sucess": "StaÅžení ÃēspÄ›ÅĄnÊ", + "download_sucess_android": "MÊdia byla staÅžena do DCIM/Immich", "download_waiting_to_retry": "ČekÃĄní na opakovanÃŊ pokus", "downloading": "StahovÃĄní", "downloading_asset_filename": "StahovÃĄní poloÅžky {filename}", "downloading_from_icloud": "StahovÃĄní z iCloudu", "downloading_media": "StahovÃĄní mÊdia", - "drag_to_reorder": "Posuňte pro změnu pořadí", "drop_files_to_upload": "Pro nahrÃĄní sem přetÃĄhněte soubory", - "duplicate": "Duplikovat", - "duplicate_workflow": "Duplikovat pracovní postup", "duplicates": "Duplicity", "duplicates_description": "VyřeÅĄte kaÅždou skupinu tak, Åže uvedete, kterÊ skupiny jsou duplicitní.", "duration": "Doba trvÃĄní", @@ -908,7 +982,9 @@ "edit_date_and_time": "Upravit datum a čas", "edit_date_and_time_action_prompt": "{count} časovÃŊch Ãēdajů upraveno", "edit_date_and_time_by_offset": "Posunout datum", + "edit_date_and_time_by_offset_interval": "NovÃŊ rozsah dat: {from} - {to}", "edit_description": "Upravit popis", + "edit_description_prompt": "Vyberte novÃŊ popis:", "edit_exclusion_pattern": "Upravit vzor vyloučení", "edit_faces": "Upravit obličeje", "edit_key": "Upravit klíč", @@ -923,6 +999,9 @@ "edit_user": "Upravit uÅživatele", "edit_workflow": "Upravit pracovní postup", "editor": "Editor", + "editor_close_without_save_prompt": "Změny nebudou uloÅženy", + "editor_close_without_save_title": "Zavřít editor?", + "editor_confirm_reset_all_changes": "Opravdu chcete zruÅĄit vÅĄechny změny?", "editor_discard_edits_confirm": "ZruÅĄit Ãēpravy", "editor_discard_edits_prompt": "MÃĄte neuloÅženÊ Ãēpravy. Opravdu je chcete zahodit?", "editor_discard_edits_title": "ZruÅĄit Ãēpravy?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Zadejte PIN kÃŗd", "enter_your_pin_code_subtitle": "Zadejte PIN kÃŗd pro přístup k uzamčenÊ sloÅžce", "error": "Chyba", + "error_change_sort_album": "Nepodařilo se změnit pořadí alba", "error_delete_face": "Chyba při odstraňovÃĄní obličeje z poloÅžky", + "error_getting_places": "Chyba při zjiÅĄÅĨovÃĄní míst", "error_loading_albums": "Chyba načítaní alb", "error_loading_image": "Chyba při načítÃĄní obrÃĄzku", "error_loading_partners": "Chyba při načítÃĄní partnerů: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Nepodařilo se odebrat klíč produktu", "failed_to_reset_pin_code": "Nepodařilo se resetovat PIN kÃŗd", "failed_to_stack_assets": "Nepodařilo se seskupit poloÅžky", - "failed_to_tag_assets": "Nepodařilo se přidat značky k poloÅžkÃĄm", "failed_to_unstack_assets": "Nepodařilo se zruÅĄit seskupení poloÅžek", "failed_to_update_notification_status": "Nepodařilo se aktualizovat stav oznÃĄmení", "incorrect_email_or_password": "NesprÃĄvnÃŊ e-mail nebo heslo", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Přidat popis...", "exif_bottom_sheet_description_error": "Chyba při aktualizaci popisu", + "exif_bottom_sheet_details": "PODROBNOSTI", + "exif_bottom_sheet_location": "POLOHA", "exif_bottom_sheet_no_description": "ÅŊÃĄdnÃŊ popisek", + "exif_bottom_sheet_people": "LIDÉ", + "exif_bottom_sheet_person_add_person": "Přidat jmÊno", "exit_slideshow": "Ukončit prezentaci", "expand": "Rozbalit", "expand_all": "Rozbalit vÅĄe", + "experimental_settings_new_asset_list_subtitle": "ZpracovÃĄvÃĄm", + "experimental_settings_new_asset_list_title": "Povolení experimentÃĄlní mříŞky fotografií", + "experimental_settings_subtitle": "PouŞívejte na vlastní riziko!", + "experimental_settings_title": "ExperimentÃĄlní", "expire_after": "Platnost končí", "expired": "VyprÅĄela platnost", "expires_date": "Platnost končí {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Exportovat jako JSON", "export_database": "Exportovat databÃĄzi", "export_database_description": "Exportovat databÃĄzi SQLite", - "exposure_time": "Expoziční čas", "extension": "Přípona", "external": "Externí", "external_libraries": "Externí knihovny", "external_network": "Externí síÅĨ", "external_network_sheet_info": "Pokud nejste v preferovanÊ síti Wi-Fi, aplikace se připojí k serveru prostřednictvím první z níŞe uvedenÃŊch adres URL, kterÊ můŞe dosÃĄhnout, počínaje shora dolů", - "f_number": "ClonovÊ číslo", "face_unassigned": "Nepřiřazena", "failed": "Selhalo", "failed_count": "Selhalo: {count}", "failed_to_authenticate": "Ověření se nezdařilo", - "failed_to_delete_file": "Odstranění souboru se nezdařilo", "failed_to_load_assets": "Nepodařilo se načíst poloÅžky", "failed_to_load_folder": "Nepodařilo se načíst sloÅžku", "favorite": "Oblíbit", "favorite_action_prompt": "{count} přidÃĄno do OblíbenÃŊch", "favorite_or_unfavorite_photo": "Oblíbit nebo zruÅĄit oblíbení fotky", "favorites": "OblíbenÊ", + "favorites_page_no_favorites": "Nebyla nalezena ÅžÃĄdnÃĄ oblíbenÃĄ mÊdia", "feature_photo_updated": "Hlavní fotka aktualizovÃĄna", "features": "Funkce", + "features_in_development": "Funkce ve vÃŊvoji", "features_setting_description": "SprÃĄva funkcí aplikace", "file_name_or_extension": "NÃĄzev nebo přípona souboru", "file_name_text": "NÃĄzev souboru", + "file_name_with_value": "NÃĄzev souboru: {file_name}", "file_size": "Velikost souboru", "filename": "NÃĄzev souboru", "filetype": "Typ souboru", "filter": "Filtr", + "filter_description": "Podmínky pro filtrovÃĄní cílovÃŊch poloÅžek", "filter_people": "Filtrovat lidi", "filter_places": "Filtrovat místa", "filter_tags": "Filtrovat značky", "filters": "Filtry", + "find_them_fast": "Najděte je rychle vyhledÃĄním jejich jmÊna", "first": "První", "fix_incorrect_match": "Opravit nesprÃĄvnou shodu", - "focal_length": "OhniskovÃĄ vzdÃĄlenost", "folder": "SloÅžka", "folder_not_found": "SloÅžka nebyla nalezena", "folders": "SloÅžky", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Přesunout zÃĄlohovanÊ fotografie a videa do koÅĄe zařízení, abyste uvolnili místo. VaÅĄe kopie na serveru zůstanou v bezpečí.", "free_up_space_settings_subtitle": "Uvolnit ÃēloÅžiÅĄtě zařízení", "full_path": "ÚplnÃĄ cesta: {path}", - "full_path_or_folder": "CelÃĄ cesta nebo sloÅžka", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Tato funkce načítÃĄ externí zdroje z Googlu, aby mohla fungovat.", "general": "ObecnÊ", @@ -1168,6 +1256,7 @@ "group_owner": "Seskupit podle uÅživatele", "group_places_by": "Seskupit místa podle...", "group_year": "Seskupit podle roku", + "haptic_feedback_switch": "Povolit dotykovou zpětnou vazbu", "haptic_feedback_title": "DotykovÃĄ zpětnÃĄ vazba", "has_quota": "MÃĄ kvÃŗtu", "hash_asset": "Hash poloÅžky", @@ -1188,12 +1277,29 @@ "hide_schema": "SkrÃŊt schÊma", "hide_text_recognition": "SkrÃŊt rozpoznÃĄvÃĄní textu", "hide_unnamed_people": "SkrÃŊt nejmenovanÊ lidi", + "home_page_add_to_album_conflicts": "PřidÃĄno {added} poloÅžek do alba {album}. {failed} poloÅžek je jiÅž v albu.", + "home_page_add_to_album_err_local": "Zatím není moÅžnÊ přidat lokÃĄlní mÊdia do alb, přeskakuji", + "home_page_add_to_album_success": "PřidÃĄno {added} poloÅžek do alba {album}.", + "home_page_album_err_partner": "PoloÅžky partnera nelze zatím přidat do alba, přeskakuji", + "home_page_archive_err_local": "Zatím nemohu archivovat lokÃĄlní mÊdia, přeskakuji", + "home_page_archive_err_partner": "PoloÅžky partnera nelze archivovat, přeskakuji", "home_page_building_timeline": "VytvÃĄÅ™ení časovÊ osy", + "home_page_delete_err_partner": "PoloÅžky partnera nelze smazat, přeskakuji", + "home_page_delete_remote_err_local": "Místní poloÅžky ve vzdÃĄlenÊm vÃŊběru pro smazÃĄní, přeskakuji", + "home_page_favorite_err_local": "Zatím není moÅžnÊ zařadit lokÃĄlní mÊdia mezi oblíbenÃĄ, přeskakuji", + "home_page_favorite_err_partner": "PoloÅžky partnera nelze označit jako oblíbenÊ, přeskakuji", + "home_page_first_time_notice": "Pokud aplikaci pouŞívÃĄte poprvÊ, nezapomeňte si vybrat zÃĄlohovanÃĄ alba, aby se na časovÊ ose mohly nachÃĄzet fotografie a videa z vybranÃŊch alb", + "home_page_locked_error_local": "Místní poloÅžky nelze přesunout do uzamčenÊ sloÅžky, přeskočí se", + "home_page_locked_error_partner": "PoloÅžky partnera nelze přesunout do uzamčenÊ sloÅžky, přeskočí se", + "home_page_share_err_local": "Nelze sdílet místní poloÅžky prostřednictvím odkazu, přeskakuji", + "home_page_upload_err_limit": "Lze nahrÃĄt nejvÃŊÅĄe 30 poloÅžek najednou, přeskakuji", "host": "Hostitel", "hour": "Hodina", "hours": "Hodin", "id": "ID", "idle": "Nečinnost", + "ignore_icloud_photos": "Ignorovat fotografie na iCloudu", + "ignore_icloud_photos_description": "Fotografie uloÅženÊ na iCloudu se nebudou nahrÃĄvat na Immich server", "image": "ObrÃĄzek", "image_alt_text_date": "{isVideo, select, true {Video pořízeno} other {ObrÃĄzek pořízen}} {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video pořízeno} other {ObrÃĄzek pořízen}} {date} uÅživatelem {person1}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video pořízeno} other {ObrÃĄzek poŞízen}} {date} v místě {city}, {country} uÅživateli {person1} a {person2}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video pořízeno} other {ObrÃĄzek poŞízen}} {date} v místě {city}, {country} uÅživateli {person1}, {person2} a {person3}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video pořízeno} other {ObrÃĄzek poŞízen}} {date} v místě {city}, {country} uÅživateli {person1}, {person2} a {additionalCount, plural, one {dalÅĄÃ­m # uÅživatelem} other {dalÅĄÃ­mi # uÅživateli}}", + "image_saved_successfully": "ObrÃĄzek uloÅžen", + "image_viewer_page_state_provider_download_started": "StahovÃĄní zahÃĄjeno", + "image_viewer_page_state_provider_download_success": "StahovÃĄní bylo ÃēspÄ›ÅĄnÊ", + "image_viewer_page_state_provider_share_error": "Chyba sdílení", "immich_logo": "Immich Logo", "immich_web_interface": "WebovÊ rozhraní Immich", "import_from_json": "Import z JSONu", @@ -1219,7 +1329,6 @@ "individual_share": "Sdílení jednotlivÃŊch poloÅžek", "individual_shares": "Sdílení jednotlivÃŊch poloÅžek", "info": "Informace", - "integrity_checks": "Kontroly integrity", "interval": { "day_at_onepm": "KaÅždÃŊ den ve 13:00", "hours": "{hours, plural, one {KaÅždou hodinu} few {KaÅždÊ {hours, number} hodiny} other {KaÅždÃŊch {hours, number} hodin}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "ChybnÃŊ formÃĄt data", "invite_people": "Pozvat lidi", "invite_to_album": "Pozvat do alba", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Data načtena {dateTime}", + "ios_debug_info_last_sync_at": "Poslední synchronizace {dateTime}", + "ios_debug_info_no_processes_queued": "ÅŊÃĄdnÊ procesy na pozadí ve frontě", + "ios_debug_info_no_sync_yet": "Dosud nebyla spuÅĄtěna ÅžÃĄdnÃĄ Ãēloha synchronizace na pozadí", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proces na pozadí ve frontě} few {{count} procesy na pozadí ve frontě} other {{count} procesů na pozadí ve frontě}}", + "ios_debug_info_processing_ran_at": "ZpracovÃĄní spuÅĄtěno {dateTime}", "items_count": "{count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžek}}", "jobs": "Úlohy", + "json_editor": "JSON editor", + "json_error": "Chyba JSON", "keep": "Ponechat", "keep_albums": "Ponechat alba", "keep_albums_count": "PonechÃĄní {count} {count, plural, one {alba} other {alb}}", @@ -1259,12 +1375,14 @@ "leave": "Opustit", "leave_album": "Opustit album", "lens_model": "Model objektivu", - "less": "MÊně", "let_others_respond": "Nechte ostatní reagovat", "level": "Úroveň", "library": "Knihovna", "library_add_folder": "Přidat sloÅžku", "library_edit_folder": "Upravit sloÅžku", + "library_options": "MoÅžnosti knihovny", + "library_page_device_albums": "Alba v zařízení", + "library_page_new_album": "NovÊ album", "library_page_sort_asset_count": "Počet poloÅžek", "library_page_sort_created": "Naposledy vytvořenÊ", "library_page_sort_last_modified": "Naposledy upraveno", @@ -1274,14 +1392,11 @@ "light_theme": "Přepnout na světlÃŊ motiv", "like": "Líbí se mi", "like_deleted": "Oblíbení smazÃĄno", - "link": "Odkaz", "link_motion_video": "Připojit pohyblivÊ video", "link_to_docs": "DalÅĄÃ­ informace najdete v dokumentaci.", "link_to_oauth": "Propojit s OAuth", "linked_oauth_account": "PropojenÃŊ OAuth Ãēčet", "list": "Seznam", - "live": "ÅŊivÃŊ", - "load_more": "Načíst dalÅĄÃ­", "loading": "NačítÃĄní", "loading_search_results_failed": "NačítÃĄní vÃŊsledků vyhledÃĄvÃĄní se nezdařilo", "local": "Místní", @@ -1310,9 +1425,11 @@ "login": "PřihlÃĄÅĄení", "login_disabled": "PřihlÃĄÅĄení bylo zakÃĄzÃĄno", "login_form_api_exception": "VÃŊjimka API. Zkontrolujte URL serveru a zkuste to znovu.", + "login_form_back_button_text": "Zpět", "login_form_email_hint": "tvůje-mail@email.com", "login_form_endpoint_hint": "http://ip-tvÊho-serveru:port", "login_form_endpoint_url": "URL adresa serveru", + "login_form_err_http": "Prosím, uveďte http:// nebo https://", "login_form_err_invalid_email": "NeplatnÃŊ e-mail", "login_form_err_invalid_url": "NeplatnÃĄ URL", "login_form_err_leading_whitespace": "Úvodní mezera", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Chyba přihlÃĄÅĄení, zkontrolujte URL adresu serveru, e-mail a heslo", "login_form_handshake_exception": "DoÅĄlo k vÃŊjimce Handshake se serverem. Pokud pouŞívÃĄte self-signed certifikÃĄt, povolte v nastavení podporu self-signed certifikÃĄtu.", "login_form_password_hint": "heslo", - "login_form_server_empty": "Zadejte URL serveru", - "login_form_server_error": "Není moÅžnÊ se připojit k serveru", - "login_has_been_disabled": "PřihlÃĄÅĄení bylo zakÃĄzÃĄno", + "login_form_save_login": "Zůstat přihlÃĄÅĄen", + "login_form_server_empty": "Zadejte URL serveru.", + "login_form_server_error": "Není moÅžnÊ se připojit k serveru.", + "login_has_been_disabled": "PřihlÃĄÅĄení bylo zakÃĄzÃĄno.", "login_password_changed_error": "Při aktualizaci vaÅĄeho hesla doÅĄlo k chybě", "login_password_changed_success": "Heslo bylo ÃēspÄ›ÅĄně aktualizovÃĄno", "logout_all_device_confirmation": "Opravdu chcete odhlÃĄsit vÅĄechna zařízení?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Obnovení databÃĄze", "maintenance_description": "Immich byl přepnut do reÅžimu ÃēdrÅžby.", "maintenance_end": "Ukončit reÅžim ÃēdrÅžby", + "maintenance_end_error": "Nepodařilo se ukončit reÅžim ÃēdrÅžby.", "maintenance_logged_in_as": "AktuÃĄlně přihlÃĄÅĄen jako {user}", "maintenance_restore_from_backup": "Obnovit ze zÃĄlohy", "maintenance_restore_library": "Obnovte svou knihovnu", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Otevřít nastavení", "manage_media_access_subtitle": "Povolte aplikaci Immich spravovat a přesouvat soubory mÊdií.", "manage_media_access_title": "Přístup ke sprÃĄvě mÊdií", + "manage_shared_links": "Spravovat sdílenÊ odkazy", "manage_sharing_with_partners": "SprÃĄva sdílení s partnery", "manage_the_app_settings": "SprÃĄva nastavení aplikace", "manage_your_account": "SprÃĄva Ãēčtu", @@ -1371,11 +1491,13 @@ "manage_your_devices": "SprÃĄva přihlÃĄÅĄenÃŊch zařízení", "manage_your_oauth_connection": "SprÃĄva OAuth propojení", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {ÅŊÃĄdnÃĄ fotka v tÊto oblasti} one {# fotka} few{# fotky} other {# fotek}}", "map_cannot_get_user_location": "Nelze zjistit polohu uÅživatele", + "map_location_dialog_yes": "Ano", "map_location_picker_page_use_location": "PouŞít tuto polohu", "map_location_service_disabled_content": "Pro zobrazení fotek z vaÅĄÃ­ aktuÃĄlní polohy musí bÃŊt povolena sluÅžba určovÃĄní polohy. Chcete ji nyní povolit?", "map_location_service_disabled_title": "SluÅžba určovÃĄní polohy je zakÃĄzÃĄna", - "map_marker_for_image": "Značka na mapě pro obrÃĄzek pořízenÃŊ ve městě {city}, {country}", + "map_marker_for_images": "Značka na mapě pro snímky pořízenÊ v {city}, {country}", "map_marker_with_image": "Značka mapy s obrÃĄzkem", "map_no_location_permission_content": "OprÃĄvnění polohy je nutnÊ pro zobrazení fotek z vaÅĄÃ­ aktuÃĄlní polohy. Chcete oprÃĄvnění nyní povolit?", "map_no_location_permission_title": "OprÃĄvnění polohy zamítnuto", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Posledních {days} dní", "map_settings_date_range_option_year": "Poslední rok", "map_settings_date_range_option_years": "Poslední {years} roky", + "map_settings_dialog_title": "Nastavení map", "map_settings_include_show_archived": "Zahrnout archivovanÊ", "map_settings_include_show_partners": "Včetně partnerů", "map_settings_only_show_favorites": "Zobrazit pouze oblíbenÊ", "map_settings_theme_settings": "Motiv mapy", + "map_zoom_to_see_photos": "OddÃĄlit pro zobrazení fotografií", "mark_all_as_read": "Označit vÅĄe jako přečtenÊ", + "mark_as_read": "Označit jako přečtenÊ", "marked_all_as_read": "VÅĄe označeno jako přečtenÊ", "matches": "Shody", "matching_assets": "Odpovídající poloÅžky", - "media_chrome": { - "auto": "Automaticky", - "captions": "Titulky", - "captions_off": "Vypnuto", - "closed_captions": "skrytÊ titulky", - "decode_error": "Chyba dekÃŗdovÃĄní", - "disable_captions": "Vypnout titulky", - "enable_captions": "Zapnout titulky", - "enter_fullscreen_mode": "Přepnout do reÅžimu celÊ obrazovky", - "exit_fullscreen_mode": "Ukončit reÅžim celÊ obrazovky", - "loop": "Smyčka", - "media_error_description": "K přeruÅĄení přehrÃĄvÃĄní doÅĄlo kvůli chybě souboru. Soubor můŞe bÃŊt poÅĄkozenÃŊ, případně vÃĄÅĄ prohlíŞeč tento formÃĄt nepodporuje.", - "media_loading": "načítÃĄní mÊdií", - "mute": "Ztlumit", - "network_error": "Chyba sítě", - "network_error_description": "K selhÃĄní stahovÃĄní mÊdií doÅĄlo kvůli síÅĨovÊ chybě.", - "not_supported_error": "Zdroj není podporovÃĄn", - "playback_rate": "Rychlost přehrÃĄvÃĄní", - "playback_rate_current": "aktuÃĄlní rychlost přehrÃĄvÃĄní", - "playback_rate_value": "Rychlost přehrÃĄvÃĄní {playbackRate}", - "playback_time": "doba přehrÃĄvÃĄní", - "quality": "Kvalita", - "second": "sekunda", - "seconds": "sekund", - "time_value_of_total_time": "{currentTime} z {totalTime}", - "time_value_remaining": "zbÃŊvÃĄ {time}", - "unmute": "ZruÅĄit ztlumení", - "unsupported_error_description": "DoÅĄlo k chybě způsobenÊ nepodporovanÃŊm formÃĄtem. DoÅĄlo k selhÃĄní serveru nebo sítě, případně vÃĄÅĄ prohlíŞeč tento formÃĄt nepodporuje.", - "video_not_loaded_unknown_time": "video se nenačetlo, znÃĄmÃŊ čas.", - "video_player": "videopřehrÃĄvač", - "volume": "hlasitost" - }, "media_type": "Typ mÊdia", "memories": "Vzpomínky", "memories_all_caught_up": "To je vÅĄechno", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Chcete tyto lidi sloučit? Tato akce je nevratnÃĄ.", "merge_people_successfully": "Sloučení osob proběhlo ÃēspÄ›ÅĄně", "merged_people_count": "{count, plural, one {Sloučena # osoba} few {Sloučeny # osoby} other {Sloučeno # lidí}}", - "minFaces": "MinimÃĄlní počet obličejů", - "minFaces_description": "MinimÃĄlní počet rozpoznanÃŊch obličejů, aby byla danÃĄ osoba zobrazena", "minimize": "Minimalizovat", "minute": "Minuta", "minutes": "Minut", + "mirror_horizontal": "Vodorovně", + "mirror_vertical": "Svisle", "missing": "Chybějící", "mobile_app": "Mobilní aplikace", "mobile_app_download_onboarding_note": "StÃĄhněte si doprovodnou mobilní aplikaci pomocí nÃĄsledujících moÅžností", "model": "Model", - "modify_date": "Datum Ãēpravy", "month": "Měsíc", + "monthly_title_text_date_format": "LLLL y", "more": "Více", - "motion": "Pohyb", "move": "Přesunout", + "move_down": "Přesunout dolů", "move_off_locked_folder": "Přesunout z uzamčenÊ sloÅžky", "move_to": "Přesunout do", "move_to_device_trash": "Přesunout do koÅĄe zařízení", "move_to_lock_folder_action_prompt": "{count} přidanÃŊch do uzamčenÊ sloÅžky", "move_to_locked_folder": "Přesunout do uzamčenÊ sloÅžky", "move_to_locked_folder_confirmation": "Tyto fotky a videa budou odstraněny ze vÅĄech alb a bude je moÅžnÊ zobrazit pouze v uzamčenÊ sloÅžce", + "move_up": "Přesunout nahoru", + "moved_to_archive": "{count, plural, one {# poloÅžka přesunuta} few {# poloÅžky přesunuty} other {# poloÅžek přesunuto}} do archivu", + "moved_to_library": "{count, plural, one {# poloÅžka přesunuta} few {# poloÅžky přesunuty} other {# poloÅžek přesunuto}} do knihovny", "moved_to_trash": "Přesunuto do koÅĄe", + "multiselect_grid_edit_date_time_err_read_only": "Nelze upravit datum poloÅžek pouze pro čtení, přeskakuji", + "multiselect_grid_edit_gps_err_read_only": "Nelze upravit polohu poloÅžek pouze pro čtení, přeskakuji", "mute_memories": "Ztlumit vzpomínky", "my_albums": "Moje alba", - "my_immich_description": "Zkopírovat aktuÃĄlní strÃĄnku jako odkaz na Můj Immich", - "my_immich_title": "Odkaz na Můj Immich", "name": "JmÊno", "name_or_nickname": "JmÊno nebo přezdívka", "name_required": "JmÊno je povinnÊ", @@ -1480,11 +1576,12 @@ "never": "Nikdy", "new_album": "NovÊ album", "new_api_key": "NovÃŊ API klíč", - "new_feature": "NovÃĄ funkce", + "new_date_range": "NovÃŊ rozsah dat", "new_password": "NovÊ heslo", "new_person": "NovÃĄ osoba", "new_pin_code": "NovÃŊ PIN kÃŗd", "new_pin_code_subtitle": "PoprvÊ přistupujete k uzamčenÊ sloÅžce. Vytvořte si kÃŗd PIN pro bezpečnÃŊ přístup na tuto strÃĄnku", + "new_timeline": "NovÃĄ časovÃĄ osa", "new_update": "NovÃĄ aktualizace", "new_user_created": "Vytvořen novÃŊ uÅživatel", "new_version_available": "NOVÁ VERZE K DISPOZICI", @@ -1492,6 +1589,7 @@ "next": "DalÅĄÃ­", "next_memory": "DalÅĄÃ­ vzpomínka", "no": "Ne", + "no_actions_added": "Zatím nebyly přidÃĄny ÅžÃĄdnÊ akce", "no_albums_found": "ÅŊÃĄdnÃĄ alba nenalezena", "no_albums_message": "Vytvořte si album pro uspoÅ™ÃĄdÃĄní fotografií a videí", "no_albums_with_name_yet": "VypadÃĄ to, Åže zatím nemÃĄte ÅžÃĄdnÃĄ alba s tímto nÃĄzvem.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Nebyla nalezena ÅžÃĄdnÃĄ zařízení", "no_checksum_local": "Není k dispozici kontrolní součet - nelze načíst místní poloÅžky", "no_checksum_remote": "Není k dispozici kontrolní součet - nelze načíst vzdÃĄlenou poloÅžku", + "no_configuration_needed": "Není nutnÃĄ ÅžÃĄdnÃĄ konfigurace", "no_devices": "ÅŊÃĄdnÃĄ autorizovanÃĄ zařízení", "no_duplicates_found": "Nebyly nalezeny ÅžÃĄdnÊ duplicity.", "no_exif_info_available": "Exif není k dispozici", "no_explore_results_message": "Nahrajte dalÅĄÃ­ fotografie a prozkoumejte svou sbírku.", "no_favorites_message": "Přidejte si oblíbenÊ poloÅžky a rychle najděte svÊ nejlepÅĄÃ­ obrÃĄzky a videa", + "no_filters_added": "Zatím nebyly přidÃĄny ÅžÃĄdnÊ filtry", "no_libraries_message": "Vytvořte si externí knihovnu pro zobrazení fotografií a videí", "no_local_assets_found": "Nebyly nalezeny ÅžÃĄdnÊ místní poloÅžky s tímto kontrolním součtem", + "no_location_set": "Není nastavena poloha", "no_locked_photos_message": "Fotky a videa v uzamčenÊ sloÅžce jsou skrytÊ a při prochÃĄzení nebo vyhledÃĄvÃĄní v knihovně se nezobrazují.", "no_name": "Bez jmÊna", "no_notifications": "ÅŊÃĄdnÃĄ oznÃĄmení", @@ -1518,19 +1619,14 @@ "no_results": "ÅŊÃĄdnÊ vÃŊsledky", "no_results_description": "Zkuste pouŞít synonymum nebo obecnějÅĄÃ­ klíčovÊ slovo", "no_shared_albums_message": "Vytvořte si album a sdílejte fotografie a videa s lidmi ve svÊ síti", - "no_steps": "Zatím nebyly přidÃĄny ÅžÃĄdnÊ kroky", + "no_uploads_in_progress": "NeprobíhÃĄ ÅžÃĄdnÊ nahrÃĄvÃĄní", "none": "ÅŊÃĄdnÊ", "not_allowed": "Nepovoleno", "not_available": "Není k dispozici", "not_in_any_album": "Bez alba", "not_selected": "Není vybrÃĄno", - "not_set": "Nenastaveno", "notes": "PoznÃĄmky", "nothing_here_yet": "Zatím zde nic není", - "notification_backup_reliability": "Povolte oznÃĄmení pro zlepÅĄení spolehlivosti zÃĄlohovÃĄní na pozadí", - "notification_enabled_list_tile_content": "Immich pouŞívÃĄ notifikace pro zÃĄlohovÃĄní na pozadí. Spravujte je v nastavení vaÅĄeho zařízení.", - "notification_enabled_list_tile_open_button": "Otevřít nastavení", - "notification_enabled_list_tile_title": "Notifikace zapnuty", "notification_permission_dialog_content": "Chcete-li povolit oznÃĄmení, přejděte do nastavení a vyberte moÅžnost povolit.", "notification_permission_list_tile_content": "Udělte oprÃĄvnění k aktivaci oznÃĄmení.", "notification_permission_list_tile_enable_button": "Povolit oznÃĄmení", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "KonfigurÃĄtor Obtainium", "obtainium_configurator_instructions": "Pomocí aplikace Obtainium nainstalujte a aktualizujte aplikaci pro Android přímo z vydÃĄní na GitHubu Immich. Vytvořte API klíč a vyberte variantu pro vytvoření konfiguračního odkazu pro Obtainium", "ocr": "OCR", - "ocr_body": "Immich nyní rozpoznÃĄvÃĄ text na vaÅĄich fotografiích, takÅže je můŞete vyhledÃĄvat podle toho, co je na nich napsÃĄno.", - "ocr_title": "VyhledÃĄvÃĄní textu ve vaÅĄich fotografiích", "official_immich_resources": "OficiÃĄlní zdroje Immich", "offline": "Offline", "offset": "Posun", @@ -1562,8 +1656,6 @@ "open": "Otevřít", "open_calendar": "Otevřít kalendÃĄÅ™", "open_in_browser": "Otevřít v prohlíŞeči", - "open_in_immich_body": "Nastavte si v systÊmu Android aplikaci Immich jako svou galerii, abyste mohli otevírat fotografie přímo z jinÃŊch aplikací.", - "open_in_immich_title": "Otevřít fotografie v Immichi", "open_in_map_view": "Otevřít v zobrazení mapy", "open_in_openstreetmap": "Otevřít v OpenStreetMap", "open_the_search_filters": "Otevřít vyhledÃĄvací filtry", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organizovat do alb", "organize_into_albums_description": "Umístit existující fotky do alb s pouÅžitím aktuÃĄlního nastavení synchronizace", "organize_your_library": "UspoÅ™ÃĄdejte si knihovnu", - "orientation": "Orientace", "original": "originÃĄl", "other": "Ostatní", "other_devices": "Ostatní zařízení", + "other_entities": "Ostatní entity", "other_variables": "DalÅĄÃ­ proměnnÊ", "owned": "Vlastní", "owner": "Vlastník", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "VÅĄechny vaÅĄe fotky a videa kromě těch, kterÊ jsou v sekcích ArchivovÃĄno a SmazÃĄno", "partner_can_access_location": "Místo, kde byly vaÅĄe fotografie pořízeny", "partner_list_user_photos": "Fotografie uÅživatele {user}", + "partner_list_view_all": "Zobrazit vÅĄechny", "partner_page_empty_message": "VaÅĄe fotografie zatím nejsou sdíleny s ÅžÃĄdnÃŊm partnerem.", "partner_page_no_more_users": "ÅŊÃĄdní dalÅĄÃ­ uÅživatelÊ k přidÃĄní", + "partner_page_partner_add_failed": "Nepodařilo se přidat partnera", "partner_page_select_partner": "Vyberte partnera", + "partner_page_shared_to_title": "Sdíleno", "partner_page_stop_sharing_content": "{partner} jiÅž nebude mít přístup k vaÅĄim fotografiím.", "partner_sharing": "Sdílení mezi partnery", "partners": "Partneři", @@ -1609,6 +1704,8 @@ "people": "LidÊ", "people_edits_count": "Upraveno {count, plural, one {# osoba} few {# osoby} other {# lidí}}", "people_feature_description": "ProchÃĄzení fotografií a videí seskupenÃŊch podle osob", + "people_selected": "{count, plural, one {# osoba vybrÃĄna} few {# osob vybrÃĄno} other {# lidí vybrÃĄno}}", + "people_sidebar_description": "Zobrazit sekci LidÊ v postranním panelu", "permanent_deletion_warning": "Upozornění na trvalÊ smazÃĄní", "permanent_deletion_warning_setting_description": "Zobrazit varovÃĄní při trvalÊm odstranění poloÅžek", "permanently_delete": "Trvale odstranit", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "{count, plural, one {PoloÅžka trvale vymazÃĄna} other {PoloÅžky trvale vymazÃĄny}}", "permission": "OprÃĄvnění", "permission_empty": "VaÅĄe oprÃĄvnění by nemělo bÃŊt prÃĄzdnÊ", + "permission_onboarding_back": "Zpět", + "permission_onboarding_continue_anyway": "Přesto pokračovat", + "permission_onboarding_get_started": "Začít", + "permission_onboarding_go_to_settings": "Přejít do nastavení", + "permission_onboarding_permission_denied": "Přístup odepřen. Pro pouŞívÃĄní Immich je nutnÊ povolit přístup k fotkÃĄm a videím v nastavení.", + "permission_onboarding_permission_granted": "Přístup povolen! VÅĄe je připraveno.", + "permission_onboarding_permission_limited": "Přístup omezen. Chcete-li pouŞívat Immich k zÃĄlohovÃĄní a sprÃĄvě celÊ vaÅĄÃ­ kolekce galerií, povolte v nastavení přístup k fotkÃĄm a videím.", + "permission_onboarding_request": "Immich potřebuje přístup k zobrazení vaÅĄich fotek a videí.", "person": "Osoba", "person_age_months": "{months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", "person_age_year_months": "1 rok a {months, plural, one {# měsíc} few {# měsíce} other {# měsíců}}", - "person_age_years": "{years, plural, one {# rok} other {# let}} starÃŊ/ÃĄ", + "person_age_years": "{years, plural, one {# rok} few {# roky} other {# let}}", "person_birthdate": "Narozen(a) {date}", "person_hidden": "{name}{hidden, select, true { (skryto)} other {}}", "person_recognized": "Osoba rozpoznÃĄna", + "person_selected": "Osoba vybrÃĄna", "photo_shared_all_users": "VypadÃĄ to, Åže jste fotky sdíleli se vÅĄemi uÅživateli, nebo nemÃĄte ÅžÃĄdnÊho uÅživatele, se kterÃŊm byste je mohli sdílet.", "photos": "Fotky", "photos_and_videos": "Fotky a videa", + "photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotek}}", "photos_from_previous_years": "Fotky z předchozích let", + "photos_only": "Pouze fotografie", "pick_a_location": "Vyberte polohu", "pick_custom_range": "Vlastní rozsah", "pick_date_range": "Vyberte rozsah dat", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Upřednostňujte přehrÃĄvÃĄní originÃĄlních videí před překÃŗdovanÃŊmi videi. Pokud originÃĄlní soubor není kompatibilní, nemusí se přehrÃĄvat sprÃĄvně.", "play_transcoded_video": "PřehrÃĄt překÃŗdovanÊ video", "please_auth_to_access": "Pro přístup se prosím ověřte", - "plugin_method_filter_type": "Filtr", - "plugin_method_filter_type_description": "Tato metoda umoŞňuje filtrovat udÃĄlosti a podmíněně zabrÃĄnit spuÅĄtění dalÅĄÃ­ch kroků", "port": "Port", "preferences_settings_subtitle": "SprÃĄva předvoleb aplikace", "preferences_settings_title": "Předvolby", @@ -1665,11 +1771,11 @@ "privacy": "Soukromí", "profile": "Profil", "profile_drawer_app_logs": "Logy", + "profile_drawer_client_server_up_to_date": "Klient a server jsou aktuÃĄlní", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "ReÅžim jen pro čtení. Ukončíte ho dlouhÃŊm podrÅžením ikony avataru.", "profile_image_of_user": "ProfilovÃŊ obrÃĄzek uÅživatele {user}", "profile_picture_set": "ProfilovÃŊ obrÃĄzek nastaven.", - "projection_type": "Typ projekce", "public_album": "VeřejnÊ album", "public_share": "VeřejnÊ sdílení", "purchase_account_info": "Podporovatel", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Stav podporovatele", "purchase_individual_title": "IndividuÃĄlní", "purchase_input_suggestion": "MÃĄte produktovÃŊ klíč? Zadejte ho níŞe", + "purchase_license_subtitle": "Koupit Immich a podpořit dalÅĄÃ­ rozvoj sluÅžby", "purchase_lifetime_description": "DoÅživotní platnost", "purchase_option_title": "MOÅŊNOSTI ZAKOUPENÍ", "purchase_panel_info_1": "Tvorba aplikace Immich vyÅžaduje spoustu času a Ãēsilí, a proto na ní pracují vÃŊvojÃĄÅ™i na plnÃŊ Ãēvazek, aby byla co nejlepÅĄÃ­. NaÅĄÃ­m cílem je, aby se software s otevřenÃŊm zdrojovÃŊm kÃŗdem a etickÊ obchodní postupy staly udrÅžitelnÃŊm zdrojem příjmů pro vÃŊvojÃĄÅ™e a aby vznikl ekosystÊm respektující soukromí se skutečnÃŊmi alternativami k ziskuchtivÃŊm sluÅžbÃĄm.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {Přeřazena # poloÅžka} few {Přeřazeny # poloÅžky} other {Přeřazeno # poloÅžek}} na novou osobu", "reassing_hint": "Přiřazení vybranÃŊch poloÅžek existující osobě", "recent": "NedÃĄvnÊ", + "recent_albums": "NedÃĄvnÃĄ alba", "recent_searches": "NedÃĄvnÃĄ vyhledÃĄvÃĄní", "recently_added": "NedÃĄvno přidanÊ", - "recently_added_body": "Přejděte přímo na speciÃĄlní strÃĄnku, kde najdete vÅĄe, co jste v poslední době přidali.", - "recently_added_description": "ProhlíŞejte si svÃĄ mÊdia seřazenÃĄ podle data, kdy byla nahrÃĄna do Immiche", "recently_added_page_title": "NedÃĄvno přidanÊ", - "recently_added_title": "NedÃĄvno přidanÊ", "recently_taken": "NedÃĄvno pořízenÊ", + "recently_taken_page_title": "NedÃĄvno pořízenÊ", "refresh": "Obnovit", "refresh_encoded_videos": "Obnovit kÃŗdovanÃĄ videa", "refresh_faces": "Obnovit obličeje", "refresh_metadata": "Obnovit metadata", "refresh_thumbnails": "Obnovit miniatury", "refreshed": "Obnoveno", + "refreshes_every_file": "Znovu načte vÅĄechny stÃĄvající a novÊ soubory", "refreshing_encoded_video": "ObnovovÃĄní kÃŗdovanÊho videa", "refreshing_faces": "ObnovovÃĄní obličejů", "refreshing_metadata": "ObnovovÃĄní metadat", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Opravdu chcete ze sdílenÊho odkazu odstranit {count, plural, one {# poloÅžku} few {# poloÅžky} other {# poloÅžek}}?", "remove_assets_title": "Odstranit poloÅžky?", "remove_custom_date_range": "Odstranit vlastní rozsah dat", - "remove_filter": "Odstranit filtr", + "remove_deleted_assets": "Odstranit offline soubory", "remove_from_album": "Odstranit z alba", "remove_from_album_action_prompt": "{count} odstraněnÃŊch z alba", "remove_from_favorites": "Odstranit z oblíbenÃŊch", @@ -1756,22 +1863,29 @@ "remove_memory": "Odstranit vzpomínku", "remove_photo_from_memory": "Odstranit fotografii z tÊto vzpomínky", "remove_tag": "Odstranit značku", + "remove_url": "Odstranit URL", "remove_user": "Odebrat uÅživatele", "removed_api_key": "Odstraněn API klíč: {name}", "removed_from_archive": "Odstraněno z archivu", "removed_from_favorites": "Odstraněno z oblíbenÃŊch", "removed_from_favorites_count": "{count, plural, one {Odstraněn #} few {Odstraněny #} other {Odstraněno #}} z oblíbenÃŊch", "removed_memory": "Vzpomínka odstraněna", + "removed_photo_from_memory": "Fotografie odstraněna ze vzpomínky", "removed_tagged_assets": "OdstraněnÃĄ značka z {count, plural, one {# poloÅžky} other {# poloÅžek}}", "rename": "Přejmenovat", + "repair": "Opravy", + "repair_no_results_message": "Zde se zobrazí neznÃĄmÊ a chybějící soubory", + "replace_with_upload": "Nahradit nahrÃĄním", "repository": "RepozitÃĄÅ™", "require_password": "PoÅžadovat heslo", + "require_user_to_change_password_on_first_login": "PoÅžadovat, aby si uÅživatel při prvním přihlÃĄÅĄení změnil heslo", "rescan": "Znovu prohledat", "reset": "VÃŊchozí", "reset_password": "Obnovit heslo", "reset_people_visibility": "Obnovit viditelnost lidí", "reset_pin_code": "Resetovat PIN kÃŗd", "reset_pin_code_description": "Pokud jste zapomněli svůj PIN kÃŗd, obraÅĨte se na sprÃĄvce serveru pro jeho resetovÃĄní", + "reset_pin_code_success": "PIN kÃŗd ÃēspÄ›ÅĄně resetovÃĄn", "reset_pin_code_with_password": "Svůj PIN kÃŗd můŞete vÅždy resetovat pomocí hesla", "reset_sqlite": "Obnovit databÃĄzi SQLite", "reset_sqlite_clear_app_data": "Vymazat data", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "VyřeÅĄeny vÅĄechny duplicity", "restore": "Obnovit", "restore_all": "Obnovit vÅĄe", + "restore_trash_action_prompt": "{count} obnoveno z koÅĄe", "restore_user": "Obnovit uÅživatele", "restored_asset": "PoloÅžka obnovena", "resume": "Pokračovat", "resume_paused_jobs": "Pokračovat {count, plural, one {v # pozastavenÊ Ãēloze} few {ve # pozastavenÃŊch ÃēlohÃĄch} other {v # pozastavenÃŊch ÃēlohÃĄch}}", + "retry_upload": "OpakovÃĄní nahrÃĄvÃĄní", "review_duplicates": "Kontrola duplicit", "review_large_files": "Kontrola velkÃŊch souborů", "role": "Role", @@ -1796,6 +1912,7 @@ "role_viewer": "DivÃĄk", "running": "ProbíhÃĄ", "save": "UloÅžit", + "save_to_gallery": "UloÅžit do galerie", "saved": "UloÅženo", "saved_api_key": "API klíč uloÅžen", "saved_profile": "Profil uloÅžen", @@ -1806,10 +1923,9 @@ "scan": "Prohledat", "scan_all_libraries": "Prohledat vÅĄechny knihovny", "scan_library": "Prohledat", + "scan_settings": "Nastavení prohledÃĄvÃĄní", "scanning": "ProhlÃĄdÃĄvÃĄ se", "scanning_for_album": "ProhledÃĄvÃĄní alba...", - "screencast_mode_description": "Zobrazit na obrazovce indikÃĄtory udÃĄlostí klÃĄvesnice a myÅĄi", - "screencast_mode_title": "Přepnout reÅžim screencastu", "search": "Hledat", "search_albums": "VyhledÃĄvejte alba", "search_by_context": "VyhledÃĄvÃĄní podle obsahu", @@ -1817,8 +1933,6 @@ "search_by_description_example": "PÄ›ÅĄÃ­ turistika v Sapě", "search_by_filename": "VyhledÃĄvÃĄní podle nÃĄzvu nebo přípony souboru", "search_by_filename_example": "např. IMG_1234.JPG nebo PNG", - "search_by_full_path": "Hledat podle celÊ cesty nebo sloÅžky", - "search_by_full_path_example": "/Jan/Projekty/3D_tisk/2026-07-01 – můŞete hledat Projekty, 3D, tisk, 2026 apod.", "search_by_ocr": "Hledat pomocí OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Vyhledat model objektivu...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "VÃŊběr polohy", "search_filter_media_type": "Typ mÊdia", "search_filter_media_type_title": "VÃŊběr typu mÊdia", + "search_filter_ocr": "Hledat pomocí OCR", "search_filter_people_title": "VÃŊběr lidí", "search_filter_star_rating": "Hodnocení hvězdičkami", "search_filter_tags_title": "Vybrat značky", @@ -1848,13 +1963,25 @@ "search_no_people_named": "ÅŊÃĄdní lidÊ se jmÊnem \"{name}\"", "search_no_result": "Nebyly nalezeny ÅžÃĄdnÊ vÃŊsledky, zkuste zadat jinÃŊ hledanÃŊ vÃŊraz nebo kombinaci", "search_options": "MoÅžnosti vyhledÃĄvÃĄní", + "search_page_categories": "Kategorie", + "search_page_motion_photos": "PohyblivÊ fotky", + "search_page_no_objects": "Informace o objektech nejsou k dispozici", + "search_page_no_places": "Informace o místě nejsou k dispozici", + "search_page_screenshots": "Snímky obrazovky", "search_page_search_photos_videos": "VyhledÃĄvejte svoje fotky a videa", + "search_page_selfies": "AutoportrÊty", + "search_page_things": "Věci", "search_page_view_all_button": "Zobrazit vÅĄe", + "search_page_your_activity": "VaÅĄe aktivita", + "search_page_your_map": "VaÅĄe mapa", "search_people": "Vyhledat lidi", "search_places": "Vyhledat místa", "search_rating": "VyhledÃĄvÃĄní podle hodnocení...", + "search_result_page_new_search_hint": "NovÊ vyhledÃĄvÃĄní", "search_settings": "Hledat nastavení", "search_state": "Vyhledat stÃĄt...", + "search_suggestion_list_smart_search_hint_1": "Ve vÃŊchozím nastavení je chytrÊ vyhledÃĄvÃĄní zapnuto, pro vyhledÃĄvÃĄní metadat pouÅžijte syntaxi ", + "search_suggestion_list_smart_search_hint_2": "m:vaÅĄe-vyhledÃĄvanÃĄ-frÃĄze", "search_tags": "VyhledÃĄvat značky...", "search_timezone": "Vyhledat časovÊ pÃĄsmo...", "search_type": "Typ vyhledÃĄvÃĄní", @@ -1868,6 +1995,7 @@ "select_albums": "Vybrat alba", "select_all": "Vybrat vÅĄe", "select_all_duplicates": "Vybrat vÅĄechny duplicity", + "select_all_in": "Vybrat vÅĄe ve skupině {group}", "select_avatar_color": "Vyberte barvu avatara", "select_count": "{count, plural, one {Vybrat #} other {Vybrat #}}", "select_cutoff_date": "Vybrat mezní datum", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Vybrat hlavní fotografii", "select_from_computer": "Vybrat z počítače", "select_keep_all": "Vybrat ponechat vÅĄe", + "select_library_owner": "Vyberte vlastníka knihovny", "select_new_face": "VÃŊběr novÊho obličeje", "select_people": "Vybrat lidi", "select_person": "Vybrat osobu", "select_person_to_tag": "Vyberte osobu, kterou chcete označit", "select_photos": "Vybrat fotky", - "select_quality": "Vybrat kvalitu", "select_trash_all": "Vybrat vyhodit vÅĄe", + "select_user_for_sharing_page_err_album": "Nepodařilo se vytvořit album", "selected": "VybrÃĄno", "selected_count": "{count, plural, one {# vybranÃŊ} few {# vybranÊ} other {# vybranÃŊch}}", "selected_gps_coordinates": "VybranÊ GPS souřadnice", @@ -1907,38 +2036,49 @@ "set_slideshow_to_fullscreen": "Nastavit prezentaci na celou obrazovku", "set_stack_primary_asset": "Nastavit jako hlavní poloÅžku", "setting_image_navigation_enable_subtitle": "Pokud je zapnuto, budete moci přejít na předchozí/dalÅĄÃ­ obrÃĄzek klepnutím do levÊ/pravÊ čtvrtiny obrazovky.", - "setting_image_navigation_enable_title": "Klepnutí pro navigaci", + "setting_image_navigation_enable_title": "Klepněte pro navigaci", "setting_image_navigation_title": "Navigace mezi obrÃĄzky", "setting_image_viewer_help": "V prohlíŞeči detailů se nejprve načte malÃĄ miniatura, potÊ se načte nÃĄhled střední velikosti (je-li povolen) a nakonec se načte originÃĄl (je-li povolen).", "setting_image_viewer_original_subtitle": "UmoŞňuje načíst původní obrÃĄzek v plnÊm rozliÅĄení (velkÃŊ!). ZakaÅžte pro sníŞení vyuÅžití dat (v síti i v mezipaměti zařízení).", "setting_image_viewer_original_title": "Načíst původní obrÃĄzek", "setting_image_viewer_preview_subtitle": "UmoŞňuje načíst obrÃĄzek se středním rozliÅĄením. ZakaÅžte, pokud chcete přímo načíst originÃĄl nebo pouŞít pouze miniaturu.", "setting_image_viewer_preview_title": "Načíst nÃĄhled obrÃĄzku", + "setting_image_viewer_title": "ObrÃĄzky", "setting_languages_apply": "PouŞít", "setting_languages_subtitle": "Změna jazyka aplikace", + "setting_notifications_notify_failures_grace_period": "OznÃĄmení o selhÃĄní zÃĄlohovÃĄní na pozadí: {duration}", + "setting_notifications_notify_hours": "{count} hodin", + "setting_notifications_notify_immediately": "okamÅžitě", "setting_notifications_notify_minutes": "{count} minut", + "setting_notifications_notify_never": "nikdy", "setting_notifications_notify_seconds": "{count} sekund", + "setting_notifications_single_progress_subtitle": "PodrobnÊ informace o průběhu nahrÃĄvÃĄní poloÅžky", + "setting_notifications_single_progress_title": "Zobrazit průběh detailů zÃĄlohovÃĄní na pozadí", "setting_notifications_subtitle": "Přizpůsobení předvoleb oznÃĄmení", + "setting_notifications_total_progress_subtitle": "CelkovÃŊ průběh nahrÃĄní (hotovo/celkově)", + "setting_notifications_total_progress_title": "Zobrazit celkovÃŊ průběh zÃĄlohovÃĄní na pozadí", "setting_video_viewer_auto_play_subtitle": "Automaticky spustit přehrÃĄvÃĄní videí při jejich otevření", "setting_video_viewer_auto_play_title": "AutomatickÊ přehrÃĄvÃĄní videí", "setting_video_viewer_looping_title": "Smyčka", "setting_video_viewer_original_video_subtitle": "Při streamovÃĄní videa ze serveru přehrÃĄvat originÃĄl, i kdyÅž je k dispozici překÃŗdovanÃĄ verze. MůŞe vÊst k bufferovÃĄní. Videa dostupnÃĄ lokÃĄlně se přehrÃĄvají v původní kvalitě bez ohledu na toto nastavení.", "setting_video_viewer_original_video_title": "Vynutit původní video", "settings": "Nastavení", + "settings_require_restart": "Pro pouÅžití tohoto nastavení restartujte Immich", "settings_saved": "Nastavení uloÅženo", "setup_pin_code": "Nastavení PIN kÃŗdu", "share": "Sdílet", + "share_action_prompt": "Sdíleno {count} poloÅžek", + "share_add_photos": "Přidat fotografie", + "share_assets_selected": "{count} vybrÃĄno", "share_dialog_preparing": "Připravuji...", "share_link": "Sdílet odkaz", - "share_original": "PouŞít originÃĄl (velkÃŊ)", - "share_preview": "PouŞít miniaturu (malÃŊ)", - "share_quality_body": "Při sdílení stiskněte a podrÅžte tlačítko sdílení pro vÃŊběr kvality obrÃĄzku.", - "share_quality_title": "Vyberte si kvalitu sdílení", "shared": "SdílenÊ", "shared_album_activities_input_disable": "KomentÃĄÅ™ je vypnutÃŊ", "shared_album_activity_remove_content": "Chcete odstranit tuto aktivitu?", "shared_album_activity_remove_title": "Odstranit aktivitu", "shared_album_section_people_action_error": "Chyba při opuÅĄtění/odebrÃĄní z alba", + "shared_album_section_people_action_leave": "Odebrat uÅživatele z alba", + "shared_album_section_people_action_remove_user": "Odebrat uÅživatele z alba", "shared_album_section_people_title": "LIDÉ", "shared_by": "Sdílel(a)", "shared_by_user": "Sdílel(a) {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} nahrÃĄno", "shared_link_app_bar_title": "SdílenÊ odkazy", "shared_link_clipboard_copied_massage": "ZkopírovÃĄno do schrÃĄnky", + "shared_link_clipboard_text": "Odkaz: {link}\nHeslo: {password}", "shared_link_create_error": "Chyba při vytvÃĄÅ™ení sdílenÊho odkazu", - "shared_link_custom_url_description": "Otevřete tento sdílenÃŊ odkaz pomocí vlastního nÃĄzvu URL", - "shared_link_custom_url_title": "Vlastní URL", - "shared_link_custom_url_warning": "Upozornění: vlastní nÃĄzev URL obsahující lomítko se nemusí chovat tak, jak očekÃĄvÃĄte.", + "shared_link_custom_url_description": "Přístup k tomuto sdílenÊmu odkazu pomocí vlastního URL", "shared_link_edit_description_hint": "Zadejte popis sdílení", "shared_link_edit_expire_after_option_day": "1 den", "shared_link_edit_expire_after_option_days": "{count} dní", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Sdíleno s {partner}", "sharing": "Sdílení", "sharing_enter_password": "Pro zobrazení tÊto strÃĄnky zadejte heslo.", + "sharing_page_album": "SdílenÃĄ alba", + "sharing_page_description": "VytvÃĄÅ™ejte sdílenÃĄ alba a sdílejte fotografie a videa s lidmi ve vaÅĄÃ­ síti.", + "sharing_page_empty_list": "PRÁZDNÝ SEZNAM", + "sharing_sidebar_description": "Zobrazit sekci Sdílení v postranním panelu", + "sharing_silver_appbar_create_shared_album": "Vytvořit sdílenÊ album", + "sharing_silver_appbar_share_partner": "Sdílet s partnerem", "shift_to_permanent_delete": "stiskněte ⇧ pro trvalÊ odstranění poloÅžky", "show_album_options": "Zobrazit moÅžnosti alba", "show_albums": "Zobrazit alba", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Zobrazit na časovÊ ose", "show_in_timeline_setting_description": "Zobrazit fotky a videa tohoto uÅživatele na časovÊ ose", "show_keyboard_shortcuts": "Zobrazit klÃĄvesovÊ zkratky", - "show_less": "Zobrazit mÊně", "show_metadata": "Zobrazit metadata", - "show_more_fields": "{count, plural, one {Zobrazit # dalÅĄÃ­ pole} few {Zobrazit # dalÅĄÃ­ pole} other {Zobrazit # dalÅĄÃ­ch polí}}", "show_or_hide_info": "Zobrazit nebo skrÃŊt informace", "show_password": "Zobrazit heslo", "show_person_options": "Zobrazit moÅžnosti osoby", @@ -2005,7 +2148,6 @@ "show_schema": "Zobrazit schÊma", "show_search_options": "Zobrazit moÅžnosti vyhledÃĄvÃĄní", "show_shared_links": "Zobrazit sdílenÊ odkazy", - "show_slideshow_metadata_overlay": "Zobrazit překryvnÊ informace k obrÃĄzku", "show_slideshow_transition": "Zobrazit přechod prezentace", "show_supporter_badge": "Odznak podporovatele", "show_supporter_badge_description": "Zobrazit odznak podporovatele", @@ -2017,25 +2159,18 @@ "sign_out": "OdhlÃĄsit se", "sign_up": "Zaregistrovat se", "size": "Velikost", - "skip": "Přeskočit", "skip_to_content": "Přejít na obsah", "skip_to_folders": "Přeskočit na sloÅžky", "skip_to_tags": "Přeskočit na značky", "slideshow": "Prezentace", - "slideshow_body": "Posaďte se a sledujte, jak se vaÅĄe fotografie promítají v prezentaci na celÊ obrazovce.", - "slideshow_metadata_overlay_mode": "Obsah překryvnÊho panelu", - "slideshow_metadata_overlay_mode_description_only": "Pouze popis", - "slideshow_metadata_overlay_mode_full": "ÚplnÃŊ", "slideshow_repeat": "Opakovat prezentaci", "slideshow_repeat_description": "Po skončení prezentace se vrÃĄtit na zaÄÃĄtek", "slideshow_settings": "Nastavení prezentace", - "slideshow_title": "Prezentace", - "smart_album": "ChytrÊ album", - "some_assets_already_have_a_location_warning": "NěkterÊ z vybranÃŊch poloÅžek jiÅž mají polohu", "sort_albums_by": "Seřadit alba podle...", "sort_created": "Datum vytvoření", "sort_items": "Počet poloÅžek", "sort_modified": "Datum modifikace", + "sort_newest": "NejnovějÅĄÃ­ fotka", "sort_oldest": "NejstarÅĄÃ­ fotka", "sort_people_by_similarity": "Seřadit lidi podle podobnosti", "sort_recent": "NejnovějÅĄÃ­ fotka", @@ -2044,6 +2179,7 @@ "stack": "Seskupit", "stack_action_prompt": "{count} seskupeno", "stack_duplicates": "Seskupit duplicity", + "stack_select_one_photo": "Vyberte jednu hlavní fotografii pro seskupení", "stack_selected_photos": "Seskupení vybranÃŊch fotografií", "stacked_assets_count": "{count, plural, one {Seskupena # poloÅžka} few {Seskupeny # poloÅžky} other {Seskupeno # poloÅžek}}", "stacktrace": "VÃŊpis zÃĄsobníku", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "PoÄÃĄteční datum se musí nachÃĄzet před konečnÃŊm datem", "state": "StÃĄt", "status": "Stav", - "step_delete": "Odstranit krok", - "step_delete_confirm": "Opravdu chcete odstranit tento krok?", - "step_details": "Podrobnosti o kroku", - "steps": "Kroky", "stop_casting": "Zastavit odesílÃĄní", "stop_motion_photo": "Zastavit pohyblivou fotografii", "stop_photo_sharing": "Přestat sdílet svÊ fotografie?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "ObrÃĄtit směr sloučení", "sync": "Synchronizovat", "sync_albums": "Synchronizovat alba", + "sync_albums_manual_subtitle": "Synchronizovat vÅĄechna nahranÃĄ videa a fotografie do vybranÃŊch zÃĄloÅžních alb", "sync_local": "Synchronizovat místní", "sync_remote": "Synchronizovat vzdÃĄlenÊ", "sync_status": "Stav synchronizace", "sync_status_subtitle": "Zobrazit a spravovat synchronizační systÊm", "sync_upload_album_setting_subtitle": "Vytvořit a nahrÃĄt fotografie a videa do vybranÃŊch alb na Immich", - "system_theme": "Vzhled systÊmu", - "system_theme_command_description": "PouŞít systÊmovÊ tÊma ({value})", "tag": "Značka", "tag_assets": "Přiřadit značku", "tag_created": "Vytvořena značka: {tag}", "tag_face": "Označit obličej", "tag_feature_description": "ProchÃĄzení fotografií a videí seskupenÃŊch podle tÊmat logickÃŊch značek", + "tag_not_found_question": "NemůŞete najít značku? Vytvořte novou.", "tag_people": "Označit lidi", "tag_updated": "AktualizovÃĄna značka: {tag}", "tagged_assets": "Přiřazena značka {count, plural, one {# poloÅžce} other {# poloÅžkÃĄm}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Počet poloÅžek na Å™ÃĄdek ({count})", "theme_setting_colorful_interface_subtitle": "PouŞít hlavní barvu na povrchy pozadí.", "theme_setting_colorful_interface_title": "BarevnÊ rozhraní", + "theme_setting_image_viewer_quality_subtitle": "Přizpůsobení kvality detailů prohlíŞeče obrÃĄzků", + "theme_setting_image_viewer_quality_title": "Kvalita prohlíŞeče obrÃĄzků", "theme_setting_primary_color_subtitle": "Zvolte barvu pro hlavní akce a zvÃŊraznění.", "theme_setting_primary_color_title": "Hlavní barva", "theme_setting_system_primary_color_title": "PouŞít systÊmovou barvu", - "theme_setting_system_theme_switch": "Automaticky (podle systÊmu)", + "theme_setting_system_theme_switch": "Automaticky (podle systemovÊho nastavení)", + "theme_setting_theme_subtitle": "Vyberte nastavení tÊmatu aplikace", + "theme_setting_three_stage_loading_subtitle": "TřístupňovÊ načítÃĄní můŞe zvÃŊÅĄit vÃŊkonnost načítÃĄní, ale vede k vÃŊrazně vyÅĄÅĄÃ­mu zatíŞení sítě", + "theme_setting_three_stage_loading_title": "Povolení třístupňovÊho načítÃĄní", "then": "Pak", "they_will_be_merged_together": "Budou sloučeny dohromady", "third_party_resources": "Zdroje třetích stran", @@ -2131,17 +2268,23 @@ "trash_all": "Vyhodit vÅĄe", "trash_count": "Vyhodit {count, number}", "trash_delete_asset": "Vyhodit/Smazat poloÅžku", + "trash_emptied": "KoÅĄ vyprÃĄzdněn", "trash_no_results_message": "Zde se zobrazí odstraněnÊ fotky a videa.", "trash_page_delete_all": "Smazat vÅĄechny", + "trash_page_empty_trash_dialog_content": "Chcete vyprÃĄzdnit svoje vyhozenÊ poloÅžky? Tyto poloÅžky budou trvale odstraněny z aplikace", "trash_page_info": "VyhozenÊ poloÅžky budou trvale smazÃĄny po {days} dnech", + "trash_page_no_assets": "ÅŊÃĄdnÊ vyhozenÊ poloÅžky", + "trash_page_restore_all": "Obnovit vÅĄechny", + "trash_page_select_assets_btn": "Vybrat poloÅžky", + "trash_page_title": "KoÅĄ ({count})", "trashed_items_will_be_permanently_deleted_after": "SmazanÊ poloÅžky budou trvale odstraněny po {days, plural, one {# dni} other {# dnech}}.", "trigger": "SpouÅĄtěč", - "trigger_asset_metadata_extraction": "Extrakce metadat poloÅžek", - "trigger_asset_metadata_extraction_description": "Spustí se při extrakci EXIF metadat z mediÃĄlního souboru", - "trigger_asset_uploaded": "NahrÃĄní poloÅžky", + "trigger_asset_uploaded": "PoloÅžka nahrÃĄna", "trigger_asset_uploaded_description": "Spustí se při nahrÃĄní novÊho souboru", + "trigger_description": "UdÃĄlost, kterÃĄ spustí pracovní postup", "trigger_person_recognized": "Osoba rozpoznÃĄna", - "trigger_person_recognized_description": "Spustí se, kdyÅž je rozpoznÃĄna osoba", + "trigger_person_recognized_description": "Spustí se, kdyÅž je objevena osoba", + "trigger_type": "Typ spouÅĄtěče", "troubleshoot": "Diagnostika", "type": "Typ", "unable_to_change_pin_code": "Nelze změnit PIN kÃŗd", @@ -2157,7 +2300,6 @@ "unknown": "NeznÃĄmÃŊ", "unknown_country": "NeznÃĄmÃĄ země", "unknown_date": "NeznÃĄmÊ datum", - "unknown_schema": "NeznÃĄmÊ schÊma", "unknown_year": "NeznÃĄmÃŊ rok", "unlimited": "Neomezeně", "unlink_motion_video": "Odpojit pohyblivÊ video", @@ -2170,20 +2312,23 @@ "unsaved_change": "NeuloÅženÃĄ změna", "unselect_all": "ZruÅĄit vÃŊběr vÅĄech", "unselect_all_duplicates": "ZruÅĄit vÃŊběr vÅĄech duplicit", + "unselect_all_in": "ZruÅĄit vÃŊběr ve skupině {group}", "unstack": "ZruÅĄit seskupení", "unstack_action_prompt": "{count} seskupenÃŊch zruÅĄeno", "unstacked_assets_count": "{count, plural, one {RozloÅženÃĄ # poloÅžka} few {RozloÅženÊ # poloÅžky} other {RozloÅženÃŊch # poloÅžek}}", "unsupported_field_type": "NepodporovanÃŊ typ pole", "unsupported_file_type": "Soubor {file} nelze nahrÃĄt, protoÅže jeho typ {type} není podporovÃĄn.", "untagged": "Neoznačeno", + "untitled_workflow": "Pracovní postup bez nÃĄzvu", "up_next": "To je prozatím vÅĄe", "update_location_action_prompt": "Aktualizovat polohu {count} vybranÃŊch poloÅžek pomocí:", "updated_at": "AktualizovÃĄno", "updated_password": "Heslo aktualizovÃĄno", "upload": "NahrÃĄt", "upload_concurrency": "SouběŞnost nahrÃĄvÃĄní", - "upload_day_count": "{date}: {count, plural, one {# nahranÃĄ} few {# nahranÊ} other {# nahranÃŊch}}", "upload_details": "Detaily nahrÃĄvÃĄní", + "upload_dialog_info": "Chcete zÃĄlohovat vybranÊ poloÅžky na server?", + "upload_dialog_title": "NahrÃĄt poloÅžku", "upload_error_with_count": "Chyba při nahrÃĄvÃĄní {count, plural, one {# poloÅžky} other {# poloÅžek}}", "upload_errors": "NahrÃĄvÃĄní bylo dokončeno s {count, plural, one {# chybou} other {# chybami}}, obnovte strÃĄnku pro zobrazení novÃŊch poloÅžek.", "upload_finished": "NahrÃĄvÃĄní dokončeno", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Chyby", "upload_status_uploaded": "NahrÃĄno", "upload_success": "NahrÃĄní proběhlo ÃēspÄ›ÅĄně, obnovením strÃĄnky se zobrazí nově nahranÊ poloÅžky.", - "upload_to_album_body": "UÅživatelÊ, kteří nevyuŞívají funkci ručního nahrÃĄvÃĄní, si nyní mohou zvolit, zda chtějí lokÃĄlní fotografie přidÃĄvat přímo do alba jiÅž při jejich nahrÃĄvÃĄní, jiÅž není nutnÊ je nejprve nahrÃĄt a teprve potÊ přidat do alba.", - "upload_to_album_title": "NahrÃĄt přímo do alba", "upload_to_immich": "NahrÃĄt do Immich ({count})", "uploading": "NahrÃĄvÃĄní", "uploading_media": "NahrÃĄvÃĄní mÊdií", - "uploads": "NahranÊ", - "uploads_count": "{count, plural, one {# nahranÃĄ} few {# nahranÊ} other {# nahranÃŊch}}", "url": "URL", "usage": "VyuÅžití", "use_biometric": "PouŞít biometrickÊ Ãēdaje", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "FormÃĄtujte data, časy a čísla podle nastavení místního formÃĄtu vaÅĄeho prohlíŞeče", "use_current_connection": "PouŞít aktuÃĄlní připojení", "use_custom_date_range": "PouŞít vlastní rozsah dat", - "use_template": "PouŞít ÅĄablonu", "user": "UÅživatel", "user_has_been_deleted": "Tento uÅživatel byl smazÃĄn.", "user_id": "ID uÅživatele", @@ -2217,6 +2357,7 @@ "user_privacy": "Ochrana soukromí uÅživatelů", "user_purchase_settings": "NÃĄkup", "user_purchase_settings_description": "SprÃĄva vaÅĄeho nÃĄkupu", + "user_role_set": "UÅživatel {user} nastaven jako {role}", "user_usage_detail": "Podrobnosti vyuÅžití uÅživatelů", "user_usage_stats": "Statistiky pouŞívÃĄní Ãēčtu", "user_usage_stats_description": "Zobrazit statistiky pouŞívÃĄní Ãēčtu", @@ -2226,6 +2367,7 @@ "utilities": "NÃĄstroje", "validate": "Ověřit", "validate_endpoint_error": "Zadejte platnÊ URL", + "validation_error": "Chyba ověření", "variables": "ProměnnÊ", "version": "Verze", "version_announcement_closing": "VÃĄÅĄ přítel Alex", @@ -2235,9 +2377,9 @@ "video": "Video", "video_hover_setting": "PřehrÃĄvat miniaturu videa po najetí myÅĄÃ­", "video_hover_setting_description": "PřehrÃĄt miniaturu videa při najetí myÅĄÃ­ na poloÅžku. I kdyÅž je přehrÃĄvÃĄní vypnuto, lze jej spustit najetím na ikonu přehrÃĄvÃĄní.", - "video_quality": "Kvalita videa", "videos": "Videa", "videos_count": "{count, plural, one {# video} few {# videa} other {# videí}}", + "videos_only": "Pouze videa", "view": "Zobrazit", "view_album": "Zobrazit album", "view_all": "Zobrazit vÅĄe", @@ -2246,29 +2388,29 @@ "view_details": "Zobrazit podrobnosti", "view_in_timeline": "Zobrazit na časovÊ ose", "view_link": "Zobrazit odkaz", + "view_links": "Zobrazit odkazy", "view_name": "Zobrazit", "view_next_asset": "Zobrazit dalÅĄÃ­ poloÅžku", "view_previous_asset": "Zobrazit předchozí poloÅžku", "view_qr_code": "Zobrazit QR kÃŗd", "view_similar_photos": "Zobrazit podobnÊ fotky", "view_stack": "Zobrazit seskupení", + "view_user": "Zobrazit uÅživatele", "viewer_remove_from_stack": "Odstranit ze seskupení", + "viewer_stack_use_as_main_asset": "PouŞít jako hlavní poloÅžku", + "viewer_unstack": "ZruÅĄit seskupení", "visibility": "Viditelnost", "visibility_changed": "Viditelnost změněna u {count, plural, one {# osoby} few {# osob} other {# lidí}}", "visual": "VizuÃĄlní", + "visual_builder": "VizuÃĄlní nÃĄvrhÃĄÅ™", "waiting": "Čekající", "waiting_count": "Čekající: {count}", "warning": "Upozornění", "week": "TÃŊden", "welcome": "Vítejte", "welcome_to_immich": "Vítejte v Immichi", - "whats_new": "Co je novÊho", - "whats_new_settings_subtitle": "Podívejte se, co je novÊho v Immichi", - "whats_new_version": "Verze {version}", - "when": "Kdy", "width": "Šířka", "wifi_name": "NÃĄzev Wi-Fi", - "workflow": "Workflow", "workflow_delete_prompt": "Opravdu chcete tento pracovní postup smazat?", "workflow_deleted": "Pracovní postup smazÃĄn", "workflow_description": "Popis pracovního postupu", @@ -2278,17 +2420,17 @@ "workflow_name": "NÃĄzev pracovního postupu", "workflow_navigation_prompt": "Opravdu chcete odejít bez uloÅžení změn?", "workflow_summary": "Shrnutí pracovního postupu", - "workflow_templates": "Å ablony pracovních postupů", "workflow_update_success": "Pracovní postup byl ÃēspÄ›ÅĄně aktualizovÃĄn", + "workflow_updated": "Pracovní postup aktualizovÃĄn", "workflows": "Pracovní postupy", "workflows_help_text": "Pracovní postupy automatizují akce tÃŊkající se vaÅĄich poloÅžek na zÃĄkladě spouÅĄtěčů a filtrů", "wrong_pin_code": "ChybnÃŊ PIN kÃŗd", - "x_of_total": "{x}/{total}", "year": "Rok", "years_ago": "Před {years, plural, one {rokem} other {# lety}}", "yes": "Ano", "you_dont_have_any_shared_links": "NemÃĄte ÅžÃĄdnÊ sdílenÊ odkazy", "your_wifi_name": "NÃĄzev vaÅĄÃ­ Wi-Fi", "zero_to_clear_rating": "stiskněte 0 pro vymazÃĄní hodnocení poloÅžky", - "zoom_image": "ZvětÅĄit obrÃĄzek" + "zoom_image": "ZvětÅĄit obrÃĄzek", + "zoom_to_bounds": "PřiblíŞit na okraje" } diff --git a/i18n/cv.json b/i18n/cv.json index 283b4ec587..52008a176f 100644 --- a/i18n/cv.json +++ b/i18n/cv.json @@ -8,6 +8,7 @@ "actions": "Ķ–ŌĢҁĐĩĐŧ", "active": "ĐĨĐ°ŅŅ‚Đ°Ņ€", "activity": "ĐĨĐ°ŅŅ‚Đ°Ņ€Đģ͑҅", + "activity_changed": "ĐĨĐ°ŅŅ‚Đ°Ņ€ĐģĶ‘Ņ…Đ° {enabled, select, true {Đē͗Ҁ҂ĐŊĶ—} other {ҁͺĐŊŅ‚ĐĩŅ€ĐŊĶ—}}", "add": "ĐĨ҃҈", "add_a_description": "ŌĒŅ‹Ņ€ŅĐ° ĐēĶ‘Ņ‚Đ°Ņ€Ņ‚ĐŊи Ņ…ŅƒŅˆ", "add_a_location": "Đ’Ņ‹Ņ€Ķ‘ĐŊ Ņ…ŅƒŅˆ", @@ -17,11 +18,14 @@ "add_endpoint": "Đ’Ķ—ŌĢĶ—ĐŧĐģĶ— ĐŋĶ‘ĐŊ҇͑ Ņ…ŅƒŅˆĐ°Ņ€", "add_exclusion_pattern": "ĐšĶ‘ĐģĐ°Ņ€ŅĐ° ĐŋĶ‘Ņ€Đ°Ņ…ĐŧаĐģĐģи ĐšĶ—Ņ€ĐēĐĩ Ņ…ŅƒŅˆ", "add_location": "Đ’Ņ‹Ņ€Ķ‘ĐŊ Ņ…ŅƒŅˆ", + "add_more_users": "ĐŖŅĶ‘ŌĢҁĐĩĐŧ ҋ҂ĐģĐ°Ņ€Đ°Ņ… Ņ…ŅƒŅˆ", "add_partner": "ĐœĶ‘ŅˆĶ‘Ņ€ Ņ…ŅƒŅˆ", + "add_path": "ŌĒ҃ĐģĐŊĐĩ Ņ…ŅƒŅˆ", "add_photos": "ĐĄĶ‘ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ Ņ…ŅƒŅˆ", "add_tag": "ĐĸĐĩĐŗ Ņ…ŅƒŅˆ", "add_to": "ĐœĶ—ĐŊ Ņ‚Đĩ Đŋ҃ĐģиĐŊ Ņ…ŅƒŅˆâ€Ļ", "add_to_album": "АĐģŅŒĐąĐžĐŧа Ņ…ŅƒŅˆ", + "add_to_shared_album": "ĐŸĶ—Ņ€ĐģĐĩŅ…Đ¸ аĐģŅŒĐąĐžĐŧа Ņ…ŅƒŅˆ", "add_url": "URL Ņ…ŅƒŅˆĶ‘Ņ€", "added_to_archive": "ĐŅ€Ņ…Đ¸Đ˛Đ° Ņ…ŅƒŅˆĐŊĶ‘", "added_to_favorites": "ĐĄŅƒĐšĐģĐ°ŅĐ° иĐģĐŊиĐŊĐĩ Ņ…ŅƒŅˆĐŊĶ‘", @@ -66,6 +70,7 @@ "feature_photo_updated": "ĐŖĐšŅ€Ķ‘Đŧ ҁ͑ĐŊĶŗĐēĐĩҀ҇͗Đē ŌĢĶ—ĐŊĐĩŅ‚ĐŊĶ—", "manage_sharing_with_partners": "ĐŸĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐĩĐŧĐŋĐĩ ĐŋаКĐģаĐŊĐ°ŅŅĐ¸ĐŊĐĩ ĐšĶ—Ņ€ĐēĐĩĐģĐĩҁĐĩ ĐŋҋҀ͑Ҁ", "map": "ĐšĐ°Ņ€Ņ‚Ņ‚Ķ‘", + "map_marker_for_images": "{city}, {country} ŌĢиĐŊ҇Đĩ ĶŗĐēĐĩŅ€ĐŊĶ— ĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ ваĐģĐģи ĐēĐ°Ņ€Ņ‚Ņ‚Ķ‘ ĐŧĐ°Ņ€ĐēĐĩҀ͗", "map_marker_with_image": "ĐšĐ°Ņ€Ņ‚Ņ‚Ķ‘ ĐŧĐ°Ņ€ĐēĐĩҀ͗ ĶŗĐēĐĩҀ҇͗ĐēĐŋĐĩ", "map_settings": "ĐšĐ°Ņ€Ņ‚Ņ‚Ķ‘ ĕĐŊĐĩŅ€ĐģĐĩĐŊĕвĕ", "no_explore_results_message": "ĐĨĶ‘Đ˛Ķ‘Ņ€ ĐēĐžĐģĐģĐĩĐēŅ†Đ¸ĐŋĐĩ ĐēиĐģĐĩĐŊĐŧĐĩ҈ĐēĶ—ĐŊ ҁ͑ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ ҋ҂ĐģĐ°Ņ€Đ°Ņ… Ņ‚Đ¸ĐšĶ—Ņ€.", @@ -75,6 +80,7 @@ "people": "ŌĒŅ‹ĐŊҁĐĩĐŧ", "photos": "ĐĄĶ‘ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ", "photos_and_videos": "ĐĄĶ‘ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ Ņ‚ĐĩŅ‚Đĩ ВидĐĩĐžŅĐĩĐŧ", + "photos_count": "{count, plural, one {{count, number} ĐĄĶ‘ĐŊĶŗĐēĐĩҀ҇͗Đē} other {{count, number} ĐĄĶ‘ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ}}", "photos_from_previous_years": "Đ˜Ņ€Ņ‚ĐŊĶ— ŌĢ҃ĐģҁĐĩĐŊŅ‡Đ¸ ҁ͑ĐŊĶŗĐēĐĩҀ҇͗ĐēҁĐĩĐŧ", "place": "ĐĸĶ—Đģ", "places": "ĐĸĶ—ĐģҁĐĩĐŧ", diff --git a/i18n/da.json b/i18n/da.json index 9daed30b52..ba186826fd 100644 --- a/i18n/da.json +++ b/i18n/da.json @@ -10,31 +10,39 @@ "active": "Aktiv", "active_count": "Aktiv: {count}", "activity": "Aktivitet", + "activity_changed": "Aktivitet er {enabled, select, true {aktiveret} other {deaktiveret}}", "add": "Tilføj", "add_a_description": "Tilføj en beskrivelse", "add_a_location": "Tilføj en placering", "add_a_name": "Tilføj et navn", "add_a_title": "Tilføj en titel", "add_action": "Tilføj handling", + "add_action_description": "Klik for at tilføje en handling, der skal udføres", "add_assets": "Tilføj ressourcer", "add_birthday": "Tilføj en fødselsdag", "add_endpoint": "Tilføj endepunkt", "add_exclusion_pattern": "Tilføj udelukkelsesmønster", + "add_filter": "Tilføj filter", + "add_filter_description": "Klik for at tilføje en filterbetingelse", "add_location": "Tilføj placering", + "add_more_users": "Tilføj flere brugere", "add_partner": "Tilføj partner", + "add_path": "Tilføj sti", "add_photos": "Tilføj billeder", - "add_step": "Tilføj trin", "add_tag": "Tilføj tag", "add_to": "Tilføj tilâ€Ļ", "add_to_album": "Tilføj til album", "add_to_album_bottom_sheet_added": "Tilføjet til {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", "add_to_album_bottom_sheet_some_local_assets": "Nogle lokale elementer kunne ikke føjes til albummet", + "add_to_album_toggle": "Skift selektion for {album}", "add_to_albums": "Tilføj til albummer", "add_to_albums_count": "Tilføj til albummer({count})", "add_to_bottom_bar": "Tilføj til", + "add_to_shared_album": "Tilføj til delt album", "add_upload_to_stack": "Tilføj upload til stack", "add_url": "Tilføj URL", + "add_workflow_step": "Tilføj workflow-trin", "added_to_archive": "Tilføjet til arkiv", "added_to_favorites": "Tilføjet til favoritter", "added_to_favorites_count": "Tilføjede {count, number} til favoritter", @@ -73,7 +81,6 @@ "cron_expression_description": "Indstil skannings intervallet i cron format. For mere information se: Crontab Guru", "cron_expression_presets": "cron predefinerede indstillinger", "disable_login": "Deaktiver login", - "download_csv": "Hent CSV", "duplicate_detection_job_description": "Kør maskinlÃĻring pÃĨ mediefiler for at opdage lignende billeder. Er afhÃĻngig af Smart Søgning", "exclusion_pattern_description": "Ekskluderingsmønstre lader dig ignorere filer og mapper, nÃĨr du scanner dit bibliotek. Dette er nyttigt, hvis du har mapper, der indeholder filer, du ikke vil importere, sÃĨsom RAW-filer.", "export_config_as_json_description": "Download den aktuelle systemkonfiguration som en JSON-fil", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Miniaturer kvaliteten indstilles fra 1 til 100. Nu højre, nu bedre kvalitet. Men giver større filer og pÃĨvirker programmets svartider.", "image_thumbnail_title": "Thumbnail-indstillinger", "import_config_from_json_description": "Importer systemkonfiguration ved at uploade en JSON-konfigurationsfil", - "integrity_checks_checksum_files": "Checksum-filer", - "integrity_checks_checksum_files_description": "Konfigurer checksum-filerne", - "integrity_checks_checksum_files_enable_description": "Aktiver checksum tjek", - "integrity_checks_checksum_files_percentage_limit": "Procent grÃĻnse", - "integrity_checks_checksum_files_percentage_limit_description": "Konfigurer den maksimano procent mellem 0.001 og 1 for hvor meget checksum tjek skal kører hver omgang.", - "integrity_checks_checksum_files_time_limit": "TidsgrÃĻnse", - "integrity_checks_checksum_files_time_limit_description": "Konfigurer den maksimale periode for hvilken checksum tjekket skal nÃĨ pÃĨ hvert interval. (ms)", - "integrity_checks_missing_files": "Mangler filer", - "integrity_checks_missing_files_description": "Konfigurer frekvensen og tÃĻdn eller sluk for manglende filer tjek", - "integrity_checks_missing_files_enable_description": "AktivÊr tjekket for manglende filer", - "integrity_checks_settings": "Integritetstjek", - "integrity_checks_settings_description": "Administrer integritetstjeks intervaller", - "integrity_checks_untracked_files": "Ikke-registreret filer", - "integrity_checks_untracked_files_description": "Konfigurer hyppigheden og aktiver eller deaktiver kontrollen af ikke-registreret filer", - "integrity_checks_untracked_files_enable_description": "AktivÊr kontrol af ikke-registreret filer", "job_concurrency": "{job} samtidighed", "job_created": "opgaven er skabt", "job_not_concurrency_safe": "Denne opgave er ikke sikker at køre samtidigt med andre.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Aktiver smart søgning", "machine_learning_smart_search_enabled_description": "Hvis deaktiveret, vil billeder ikke blive kodet til smart søgning.", "machine_learning_url_description": "URL’en for maskinlÃĻringsserveren. Hvis mere end Ên URL angives, vil hver server blive forsøgt Ên ad gangen, indtil en svarer succesfuldt, i rÃĻkkefølge fra første til sidste. Servere, der ikke svarer, vil midlertidigt blive ignoreret, indtil de kommer online igen.", - "maintenance_backup_management": "Backup administration", "maintenance_delete_backup": "Slet Backup", "maintenance_delete_backup_description": "Denne fil vil blive slettet permanent.", "maintenance_delete_error": "Sletning af backup fejlede.", - "maintenance_integrity_check": "Kontroller", - "maintenance_integrity_check_all": "Kontroller alle", - "maintenance_integrity_checksum_mismatch": "Checksum uoverensstemmelse", - "maintenance_integrity_checksum_mismatch_description": "Filer hvis checksum pÃĨ disken ikke stemmer overens med den checksum, Immich har gemt i sin database.", - "maintenance_integrity_checksum_mismatch_job": "Kontroller for uoverensstemmelser i checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Opdater rapporter om uoverensstemmelser i checksum", - "maintenance_integrity_missing_file": "Manglende filer", - "maintenance_integrity_missing_file_description": "Filer som Immich har i sin database, men som ikke findes i filsystemet.", - "maintenance_integrity_missing_file_job": "Tjek for manglende filer", - "maintenance_integrity_missing_file_refresh_job": "Opdater rapporter om manglende filer", - "maintenance_integrity_report": "Integritetsrapport", - "maintenance_integrity_untracked_file": "Ikke-registreret filer", - "maintenance_integrity_untracked_file_description": "Filer i Immichs mapper, som Immich ikke har nogen registrering af.", - "maintenance_integrity_untracked_file_job": "Tjek for ikke-registreret filer", - "maintenance_integrity_untracked_file_refresh_job": "Opdater rapporter om ikke-registreret filer", "maintenance_restore_backup": "Genskab backup", "maintenance_restore_backup_description": "Immich bliver slettet og genskabt fra den valgte backup. Der vil blive taget en backup før du fortsÃĻtter.", "maintenance_restore_backup_different_version": "Denne backup blev lavet med en anden version af Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "SlÃĨ emailnotifikationer til", "notification_settings": "Notifikationsindstillinger", "notification_settings_description": "Administrer notifikationsindstillinger, inklusiv email", - "oauth_allow_insecure_requests": "Tillad usikre anmodninger", - "oauth_allow_insecure_requests_description": "ADVARSEL: Dette deaktiverer TLS-certificering for OAuth anmodninger og kan udsÃĻtte dig for MITM angreb.", "oauth_auto_launch": "Auto-opstart", "oauth_auto_launch_description": "PÃĨbegynd OAuth login-flow automatisk nÃĨr loginsiden tilgÃĨs", "oauth_auto_register": "AutoregistrÊr", @@ -300,11 +274,9 @@ "oauth_button_text": "Knaptekst", "oauth_client_secret_description": "PÃĨkrÃĻvet for en fortrolig klient eller hvis PKCE (Proof Key for Code Exchange) ikke understøttes for en offentlig klient.", "oauth_enable_description": "Log ind med OAuth", - "oauth_end_session_url_description": "Omdiriger brugeren til denne URI, nÃĨr de logger ud.", "oauth_mobile_redirect_uri": "Mobilomdiregerings-URL", "oauth_mobile_redirect_uri_override": "TilsidesÃĻttelse af mobil omdiregerings-URL", "oauth_mobile_redirect_uri_override_description": "Aktiver, nÃĨr OAuth-udbyderen ikke tillader en mobil URI, som ''{callback}''", - "oauth_prompt_description": "Prompt-parameter (f.eks. select_account, login, consent)", "oauth_role_claim": "Rolle attribut", "oauth_role_claim_description": "Tildel automatisk admin adgang pÃĨ basis af forekomst af denne pÃĨstand. DÊn kan vÃĻre enten 'user' eller 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Opdaterer alle biblioteker", "registration": "Administratorregistrering", "registration_description": "Da du er den første bruger i systemet, fÃĨr du tildelt rollen som administrator og ansvar for administration og oprettelsen af nye brugere.", - "release_channel_release_candidate": "Udgivelseskandidat", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Fjern mislykkede opgaver", "require_password_change_on_login": "KrÃĻv at brugeren skifter adgangskode ved første login", "reset_settings_to_default": "Nulstil indstillingerne til standard", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "GÃĻlder kun VAAPI og QSV. SÃĻtter dri node'n som bruges til hardware-transkodning.", "transcoding_preset_preset": "Forudindstilling (-preset)", "transcoding_preset_preset_description": "Kompressionshastighed. Langsommere forudindstillinger producerer mindre filer, og øger kvalitet nÃĨr der gÃĨs efter en specifik bitrate. VP9 ignorerer hastigheder hurtigere end \"hurtigere\".", - "transcoding_realtime": "Realtids-transkodning [EKSPERIMENTEL]", - "transcoding_realtime_description": "Tillader transkodning i realtid, mens videoen streames. Muliggør kvalitetsskifte, men kan forÃĨrsage højere afspilningsforsinkelse og hakken afhÃĻngigt af serverens kapacitet.", - "transcoding_realtime_enabled": "Aktiver real-time transkodning", - "transcoding_realtime_enabled_description": "Hvis deaktiveret, kan serveren ikke starte nye realtids transkodningssessioner.", - "transcoding_realtime_resolutions": "Opløsninger", - "transcoding_realtime_resolutions_description": "Opløsninger tilgÃĻngelig for realtidstranskodning. Højere opløsninger kan forÃĨrsage afspilningsproblemer, hvis serveren ikke kan transkode dem hurtigt nok.", - "transcoding_realtime_video_codecs": "Video codecs", - "transcoding_realtime_video_codecs_description": "De video codecs, tilgÃĻngelig for realtidstranskodning. Klienter vÃĻlger den bedste mulighed, de understøtter under afspilning. AV1 er mere effektiv end HEVC, som er mere effektiv end H.264. NÃĨr du bruger hardwareacceleration, skal du kun vÃĻlge de codecs, som acceleratoren kan enkode. NÃĨr du bruger softwaretranskodning, skal du bemÃĻrke, at H.264 er hurtigere end AV1, som er hurtigere end HEVC.", "transcoding_reference_frames": "Referencerammer", "transcoding_reference_frames_description": "Antallet af frames, der skal refereres til, nÃĨr en given frame komprimeres. Højere vÃĻrdier forbedrer kompressionseffektiviteten, men gør indkodning langsommere. 0 sÃĻtter denne vÃĻrdi automatisk.", "transcoding_required_description": "Kun videoer ikke i et godkendt format", @@ -478,8 +440,6 @@ "user_settings_description": "AdministrÊr brugerindstillinger", "user_successfully_removed": "Bruger {email} er blevet fjernet med succes.", "users_page_description": "Admin-brugere side", - "version_check_channel": "Udgivelseskanal", - "version_check_channel_description": "VÃĻlg den udgivelseskanal, du vil modtage versionsmeddelelser for", "version_check_enabled_description": "AktivÊr versionstjek", "version_check_implications": "Funktionen til versionstjek er afhÃĻngig af periodisk kommunikation med {server}", "version_check_settings": "Versionstjek", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Ryd billedcache", "advanced_settings_clear_image_cache_error": "Billedcachen kunne ikke ryddes", "advanced_settings_clear_image_cache_success": "Ryddet {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Brug denne valgmulighed for at filtrere media under synkronisering baseret pÃĨ alternative kriterier. Prøv kun denne, hvis du har problemer med, at appen ikke opdager alle albums.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTEL] Brug alternativ enheds album synkroniserings filter", "advanced_settings_log_level_title": "Logniveau: {level}", "advanced_settings_prefer_remote_subtitle": "Nogle enheder er meget lang tid om at indlÃĻse miniaturebilleder af lokale elementer. Aktiver denne indstilling for at indlÃĻse elementer fra serveren i stedet.", "advanced_settings_prefer_remote_title": "ForetrÃĻk elementer pÃĨ serveren", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Tilpasset proxy headere [EKSPERIMENTALT]", "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet tilstand, hvor billederne alene kan vises. Ting som at vÃĻlge flere billeder, dele, caste og slette er alle deaktiveret. Aktiver skrivebeskyttet tilstand via en bruger avatar fra hovedskÃĻrmen", "advanced_settings_readonly_mode_title": "Skrivebeskyttet tilstand", + "advanced_settings_self_signed_ssl_subtitle": "Spring verificering af SSL-certifikat over for serverens endelokation. KrÃĻves for selvsignerede certifikater.", + "advanced_settings_self_signed_ssl_title": "Tillad selvsignerede SSL certifikater [EKSPERIMENTALT]", "advanced_settings_sync_remote_deletions_subtitle": "Slet eller gendan automatisk en mediefil pÃĨ denne enhed, nÃĨr denne handling foretages pÃĨ Immich webinterface", "advanced_settings_sync_remote_deletions_title": "Synkroniser fjernsletninger [EKSPERIMENTELT]", "advanced_settings_tile_subtitle": "Avancerede brugerindstillinger", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Er du sikker pÃĨ at du vil slette albummet {album}?", "album_delete_confirmation_description": "Hvis dette album er delt, vil andre brugere ikke lÃĻngere kunne fÃĨ adgang til det.", "album_deleted": "Album slettet", + "album_info_card_backup_album_excluded": "EKSKLUDERET", + "album_info_card_backup_album_included": "INKLUDERET", "album_info_updated": "Albuminfo opdateret", + "album_leave": "Forlad albummet?", + "album_leave_confirmation": "Er du sikker pÃĨ at du vil forlade {album}?", "album_name": "Albumnavn", "album_options": "Albumindstillinger", "album_remove_user": "Fjern bruger?", "album_remove_user_confirmation": "Er du sikker pÃĨ at du vil fjerne {user}?", "album_search_not_found": "Ingen album fundet som matcher din søgning", + "album_selected": "Album valgt", "album_share_no_users": "Det ser ud til at du har delt denne album med alle brugere, eller du har ikke nogen brugere til at dele med.", "album_summary": "Albumoversigt", "album_updated": "Album opdateret", "album_updated_setting_description": "Modtag en emailnotifikation nÃĨr et delt album fÃĨr nye mediefiler", "album_upload_assets": "Upload filer fra din computer og tilføj dem til album", + "album_user_left": "Forlod {album}", + "album_user_removed": "Fjernede {user}", + "album_viewer_appbar_delete_confirm": "Er du sikker pÃĨ, du vil slette dette album fra din bruger?", "album_viewer_appbar_share_err_delete": "Fejlede sletning af album", + "album_viewer_appbar_share_err_leave": "Fejlede i at forlade album", + "album_viewer_appbar_share_err_remove": "Der er problemer med at fjerne elementer fra album", + "album_viewer_appbar_share_err_title": "Fejlede i at ÃĻndre albumtitel", + "album_viewer_appbar_share_leave": "Forlad album", + "album_viewer_appbar_share_to": "Del til", "album_viewer_page_share_add_users": "Tilføj brugere", "album_with_link_access": "Lad alle med linket se billeder og personer i dette album.", "albums": "Albummer", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "GrundlÃĻggende sortering ved oprettelse af nyt album.", "albums_feature_description": "Samling af billeder der kan deles med andre brugere.", "albums_on_device_count": "Albummer pÃĨ enheden ({count})", + "albums_selected": "{count, plural, one {# album valgt} other {# valgte albummer}}", "all": "Alt", "all_albums": "Alle albummer", "all_people": "Alle personer", "all_photos": "Alle billeder", "all_videos": "Alle videoer", "allow_dark_mode": "Tillad mørk tilstand", + "allow_edits": "Tillad redigeringer", "allow_public_user_to_download": "Tillad offentlige brugere til at hente", "allow_public_user_to_upload": "Tillad offentlige brugere til at uploade", "allowed": "Tilladt", @@ -549,12 +528,14 @@ "always_keep": "Opbevar altid", "always_keep_photos_hint": "Frigør plads vil bevare alle billeder pÃĨ denne enhed.", "always_keep_videos_hint": "Frigør plads vil bevare alle videoer pÃĨ denne enhed.", + "anti_clockwise": "Mod uret", "api_key": "API-nøgle", "api_key_description": "Denne vÃĻrdi vises kun Ên gang. Venligst kopiÊr den før du lukker vinduet.", "api_key_empty": "Din API-nøgle-navn burde ikke vÃĻre tom", "api_keys": "API-nøgler", "app_architecture_variant": "Variant (Arkitektur)", "app_bar_signout_dialog_content": "Er du sikker pÃĨ, du vil logge ud?", + "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Log ud", "app_download_links": "Links til app download", "app_settings": "Appindstillinger", @@ -565,19 +546,27 @@ "archive": "Arkiv", "archive_action_prompt": "{count} føjet til arkiv", "archive_or_unarchive_photo": "ArkivÊr eller fjern billede fra arkiv", + "archive_page_no_archived_assets": "Ingen arkiverede elementer blev fundet", + "archive_page_title": "ArkivÊr ({count})", "archive_size": "Arkivstørrelse", "archive_size_description": "Konfigurer arkivstørrelsen for downloads (i GiB)", "archived": "Arkiveret", "archived_count": "{count, plural, other {# arkiveret}}", "are_these_the_same_person": "Er disse den samme person?", "are_you_sure_to_do_this": "Er du sikker pÃĨ, at du vil gøre det her?", + "array_field_not_fully_supported": "Arrayfelter krÃĻver manuel JSON-redigering", + "asset_action_delete_err_read_only": "Kan ikke slette kun lÃĻselige elementer. Springer over", + "asset_action_share_err_offline": "Kan ikke hente offline element(er). Springer over", "asset_added_to_album": "Tilføjet til album", "asset_adding_to_album": "Tilføjer til albumâ€Ļ", "asset_created": "Mediefil oprettet", - "asset_day_count": "{date}: {count, plural, one {# mediefil} other {# mediefiler}}", "asset_description_updated": "Mediefilsbeskrivelse er blevet opdateret", + "asset_filename_is_offline": "Mediefil {filename} er offline", + "asset_has_unassigned_faces": "Aktivet har ikke-tildelte ansigter", "asset_hashing": "Hasherâ€Ļ", "asset_list_group_by_sub_title": "GruppÊr efter", + "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", + "asset_list_layout_settings_group_automatically": "Automatisk", "asset_list_layout_settings_group_by": "Grupper elementer efter", "asset_list_layout_settings_group_by_month_day": "MÃĨned + dag", "asset_list_layout_sub_title": "Udseende", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Mediet blev ikke fundet pÃĨ iCloud. Det kan vÃĻre utilgÃĻngeligt, hvis det er en fejlagtig fil, der ligger pÃĨ iCloud", "asset_offline": "Mediefil offline", "asset_offline_description": "Denne eksterne mediefil kan ikke lÃĻngere findes pÃĨ drevet. Kontakt venligst din Immich-administrator for hjÃĻlp.", + "asset_restored_successfully": "Elementet blev gendannet succesfuldt", "asset_skipped": "Sprunget over", "asset_skipped_in_trash": "I papirkurv", + "asset_trashed": "Objekt kasseret", "asset_troubleshoot": "Fejlsøg pÃĨ objekt", "asset_uploaded": "Uploadet", "asset_uploading": "Uploaderâ€Ļ", "asset_viewer_settings_subtitle": "Administrer indstillinger for gallerifremviser", "asset_viewer_settings_title": "Billedviser", "assets": "Objekter", + "assets_added_count": "Tilføjet {count, plural, one {# mediefil} other {# mediefiler}}", "assets_added_to_album_count": "{count, plural, one {# mediefil} other {# mediefiler}} tilføjet til albummet", - "assets_added_to_album_partial_count": "Tilføjede {successCount} ud af {totalCount} {totalCount, plural, one {mediefil} other {mediefiler}} til albummet", "assets_added_to_albums_count": "Tilføjet {assetTotal, plural, one {# asset} other {# assets}} til {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Billed} other {Billeder}} kan ikke blive tilføjet til album", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan ikke føjes til i nogen af albummerne", "assets_count": "{count, plural, one {# mediefil} other {# mediefiler}}", + "assets_deleted_permanently": "{count} element(er) blev fjernet permanent", + "assets_deleted_permanently_from_server": "{count} element(er) blev fjernet permanent fra Immich serveren", + "assets_downloaded_failed": "{count, plural, one {Downloaded # fil - {error} fil fejlede} other {Downloaded # filer - {error} filer fejlede}}", + "assets_downloaded_successfully": "{count, plural, one {Downloaded # fil med succes} other {Downloaded # filer med succes}}", "assets_moved_to_trash_count": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til papirkurven", "assets_permanently_deleted_count": "{count, plural, one {# mediefil} other {# mediefiler}} slettet permanent", "assets_removed_count": "Fjernede {count, plural, one {# mediefil} other {# mediefiler}}", + "assets_removed_permanently_from_device": "{count} element(er) blev fjernet permanent fra din enhed", "assets_restore_confirmation": "Er du sikker pÃĨ, at du vil gendanne alle dine mediafiler i papirkurven? Du kan ikke fortryde denne handling! BemÃĻrk, at offline mediefiler ikke kan gendannes pÃĨ denne mÃĨde.", "assets_restored_count": "{count, plural, one {# mediefil} other {# mediefiler}} gendannet", + "assets_restored_successfully": "{count} element(er) blev gendannet succesfuldt", "assets_trashed": "{count} element(er) blev smidt i papirkurven", "assets_trashed_count": "{count, plural, one {# mediefil} other {# mediefiler}} smidt i papirkurven", + "assets_trashed_from_server": "{count} element(er) blev smidt i Immich serverens papirkurv", "assets_were_part_of_album_count": "mediefil{count, plural, one {mediefil} other {mediefiler}} er allerede en del af albummet", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} er allerede en del af albummerne", "authorized_devices": "Tilladte enheder", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Afspil slideshow automatisk", "back": "Tilbage", "back_close_deselect": "Tilbage, luk eller fravÃĻlg", + "background_backup_running_error": "Backup kører lige nu i baggrund; kan ikke starte manuel backup", "background_location_permission": "Tilladelse til baggrundsplacering", "background_location_permission_content": "For at skifte netvÃĻrk, nÃĨr appen kører i baggrunden, skal Immich *altid* have prÃĻcis placeringsadgang, sÃĨ appen kan lÃĻse WiFi-netvÃĻrkets navn", "background_options": "Baggrundsmuligheder", "backup": "Sikkerhedskopier", + "backup_album_selection_page_albums_device": "Albummer pÃĨ enheden ({count})", "backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere", "backup_album_selection_page_assets_scatter": "Elementer kan vÃĻre spredt pÃĨ tvÃĻrs af flere albummer. Albummer kan sÃĨledes inkluderes eller udelukkes under sikkerhedskopieringsprocessen.", "backup_album_selection_page_select_albums": "VÃĻlg albummer", "backup_album_selection_page_selection_info": "Oplysninger om valgte", + "backup_album_selection_page_total_assets": "Samlede unikke elementer", "backup_albums_sync": "Synkronisering af backupalbums", + "backup_all": "Alt", + "backup_background_service_backup_failed_message": "Sikkerhedskopiering af elementer fejlede. Forsøger igenâ€Ļ", "backup_background_service_complete_notification": "Sikkerhedskopiering af aktiver fuldført", + "backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igenâ€Ļ", + "backup_background_service_current_upload_notification": "Uploader {filename}", "backup_background_service_default_notification": "Søger efter nye elementerâ€Ļ", + "backup_background_service_error_title": "Fejl med sikkerhedskopiering", "backup_background_service_in_progress_notification": "Tager sikkerhedskopi af dine elementerâ€Ļ", + "backup_background_service_upload_failure_notification": "Fejlede med uploade af {filename}", "backup_controller_page_albums": "SikkerhedskopiÊr albummer", + "backup_controller_page_background_app_refresh_disabled_content": "SlÃĨ baggrundsopdatering af applikationen til i Indstillinger > Generelt > Baggrundsopdatering af applikationer, for at bruge sikkerhedskopi i baggrunden.", + "backup_controller_page_background_app_refresh_disabled_title": "Baggrundsopdatering af app er slÃĨet fra", + "backup_controller_page_background_app_refresh_enable_button_text": "GÃĨ til indstillinger", "backup_controller_page_background_battery_info_link": "Vis mig hvordan", "backup_controller_page_background_battery_info_message": "For den bedste oplevelse med sikkerhedskopiering i baggrunden, bør du slÃĨ batterioptimering, der begrÃĻnder baggrundsaktivitet, fra.\n\nSiden dette er afhÃĻngigt af enheden, bør du undersøge denne information leveret af din enheds producent.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Batterioptimering", + "backup_controller_page_background_charging": "Kun under opladning", + "backup_controller_page_background_configure_error": "Fejlede konfigureringen af sikkerhedskopiering i baggrunden", "backup_controller_page_background_delay": "Udskyd sikkerhedskopi af nye elementer: {duration}", + "backup_controller_page_background_description": "SlÃĨ sikkerhedskopiering i baggrunden til, for automatisk at tage sikkerhedskopi af nye elementer, uden at skulle ÃĨbne appen", + "backup_controller_page_background_is_off": "Automatisk sikkerhedskopiering i baggrunden er slÃĨet fra", + "backup_controller_page_background_is_on": "Automatisk sikkerhedskopiering i baggrunden er slÃĨet til", + "backup_controller_page_background_turn_off": "SlÃĨ sikkerhedskopiering i baggrunden fra", + "backup_controller_page_background_turn_on": "SlÃĨ sikkerhedskopiering i baggrunden til", + "backup_controller_page_background_wifi": "Kun med Wi-Fi", "backup_controller_page_backup": "Sikkerhedskopier", "backup_controller_page_backup_selected": "Valgte: ", "backup_controller_page_backup_sub": "Sikkerhedskopierede billeder og videoer", + "backup_controller_page_created": "Oprettet den: {date}", + "backup_controller_page_desc_backup": "SlÃĨ sikkerhedskopiering til automatisk at uploade nye elementer til serveren.", "backup_controller_page_excluded": "Ekskluderet: ", + "backup_controller_page_failed": "Fejlet ({count})", + "backup_controller_page_filename": "Filnavn: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Sikkerhedskopieringsinformation", "backup_controller_page_none_selected": "Ingen valgte", "backup_controller_page_remainder": "TilbagevÃĻrende", "backup_controller_page_remainder_sub": "TilbagevÃĻrende billeder og albummer, at sikkerhedskopiere, fra valgte", "backup_controller_page_server_storage": "Serverplads", + "backup_controller_page_start_backup": "Start sikkerhedskopiering", + "backup_controller_page_status_off": "Sikkerhedskopiering er slÃĨet fra", + "backup_controller_page_status_on": "Sikkerhedskopiering er slÃĨet til", "backup_controller_page_storage_format": "{used} af {total} brugt", "backup_controller_page_to_backup": "Albummer at sikkerhedskopiere", "backup_controller_page_total_sub": "Alle unikke billeder og videoer fra valgte albummer", + "backup_controller_page_turn_off": "SlÃĨ sikkerhedskopiering fra", + "backup_controller_page_turn_on": "SlÃĨ sikkerhedskopiering til", + "backup_controller_page_uploading_file_info": "Uploader filinformation", + "backup_err_only_album": "Kan ikke slette det eneste album", "backup_error_sync_failed": "Synkroniseringen mislykkedes. Sikkerhedskopieringen kunne ikke behandles.", "backup_info_card_assets": "objekter", + "backup_manual_cancelled": "Annulleret", + "backup_manual_in_progress": "Upload er allerede undervejs. Prøv igen efter noget tid", + "backup_manual_success": "Succes", + "backup_manual_title": "Uploadstatus", "backup_options": "Backup indstillinger", + "backup_options_page_title": "Backupindstillinger", + "backup_setting_subtitle": "Administrer indstillnger for upload i forgrund og baggrund", "backup_settings_subtitle": "HÃĨndtere upload indstillinger", + "backup_upload_details_page_more_details": "Tryk for flere detaljer", "backward": "BaglÃĻns", - "battery_optimization_backup_reliability": "Deaktivering af batterioptimering kan forbedre pÃĨlideligheden af baggrundsbackup", "biometric_auth_enabled": "Biometrisk adgangskontrol slÃĨet til", "biometric_locked_out": "Du er lÃĨst ude af biometrisk adgangskontrol", "biometric_no_options": "Ingen biometrisk adgangskontrol tilgÃĻngelig", "biometric_not_available": "Biometrisk adgangskontrol er ikke tilgÃĻngelig pÃĨ denne enhed", + "birthdate_saved": "Fødselsdatoen blev gemt", "birthdate_set_description": "Fødselsdato bruges til at beregne denne persons alder pÃĨ det tidspunkt, et billede er taget.", "blurred_background": "Sløret baggrund", - "browse_templates": "Gennemse skabeloner", "bugs_and_feature_requests": "Fejl & forbedringsønsker", "build": "Byg", "build_image": "Byggefil", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Er du sikker pÃĨ, at du vil beholde {count, plural, one {# duplicate asset} other {# duplicate assets}}? Dette vil løse alle dubletgrupper uden at slette noget.", "bulk_trash_duplicates_confirmation": "Er du sikker pÃĨ, at du vil masseslette {count, plural, one {# duplikeret objekt} other {# duplikerede objekter}}? Dette vil beholde det største objekt i hver gruppe og slette alle andre dubletter.", "buy": "Køb Immich", + "cache_settings_clear_cache_button": "Fjern cache", + "cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad pÃĨvirke appens ydeevne indtil cachen er genopbygget.", + "cache_settings_duplicated_assets_clear_button": "RYD", + "cache_settings_duplicated_assets_subtitle": "Billeder og videoer der er ignoreres af appen", + "cache_settings_duplicated_assets_title": "Dublikerede elementer ({count})", + "cache_settings_statistics_album": "Biblioteksminiaturer", + "cache_settings_statistics_full": "Fulde billeder", + "cache_settings_statistics_shared": "Miniaturebilleder til delte albummer", + "cache_settings_statistics_thumbnail": "Miniaturebilleder", + "cache_settings_statistics_title": "Cacheforbrug", + "cache_settings_subtitle": "HÃĨndter cache-adfÃĻrden for Immich-appen", + "cache_settings_tile_subtitle": "Kontroller den lokale lagerplads", + "cache_settings_tile_title": "Lokal lagerplads", + "cache_settings_title": "Cache-indstillinger", "camera": "Kamera", "camera_brand": "KameramÃĻrke", "camera_model": "Kameramodel", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Kan ikke opdatere beskrivelsen", "cast": "Caste", "cast_description": "Konfigurer tilgÃĻngelige cast destinationer", - "change": "Ændr", "change_date": "Ændr dato", "change_description": "Ændr beskrivelse", "change_display_order": "Ændrer visningsrÃĻkkefølge", + "change_expiration_time": "Ændr udløbstidspunkt", "change_location": "Ændr sted", "change_name": "Ændr navn", "change_name_successfully": "NavneÃĻndring lykkedes", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Kodeord er ikke ens", "change_password_form_reenter_new_password": "Gentag nyt kodeord", "change_pin_code": "Skift PIN kode", + "change_trigger": "Skift udløser", + "change_trigger_prompt": "Er du sikker pÃĨ, at du vil ÃĻndre udløseren? Dette vil fjerne alle eksisterende handlinger og filtre.", "change_your_password": "Skift dit kodeord", "changed_visibility_successfully": "Synlighed blev ÃĻndret", "charging": "Lader", "charging_requirement_mobile_backup": "Baggrundsbackup krÃĻver, at enheden er tilsluttet oplader", + "check_corrupt_asset_backup": "Tjek for korrupte sikkerhedskopier af elementer", + "check_corrupt_asset_backup_button": "Foretag kontrol", + "check_corrupt_asset_backup_description": "Kør kun denne kontrol via Wi-Fi, og nÃĨr alle elementer er blevet sikkerhedskopieret. Proceduren kan tage et par minutter.", "check_logs": "Tjek logfiler", "checksum": "Checksum", - "choose": "VÃĻlg", "choose_matching_people_to_merge": "VÃĻlg matchende personer til sammenfletning", "city": "By", "cleanup_confirm_description": "Immich fandt {count} assets (oprettet før {date}) sikkert sikkerhedskopieret til serveren. Fjern de lokale kopier fra denne enhed?", @@ -719,10 +774,11 @@ "clear": "Ryd", "clear_all": "Ryd alle", "clear_all_recent_searches": "Ryd alle seneste søgninger", - "clear_failed_count": "Ryd mislykkede ({count})", "clear_file_cache": "Ryd filcache", "clear_message": "Ryd bedsked", "clear_value": "Ryd vÃĻrdi", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Indtast Kodeord", "client_cert_import": "Importer", "client_cert_import_success_msg": "Klient certifikat er importeret", "client_cert_invalid_msg": "Invalid certifikat fil eller forkert adgangskode", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Klient certifikat er fjernet", "client_cert_subtitle": "Supportere kun PKCS12 (.p12, .pfx) format. Certifikat importering/fjernelse er kun tilgÃĻngeligt før login", "client_cert_title": "SSL Klient Certifikat [EKSPERIMENTAL]", + "clockwise": "Med uret", "close": "Luk", "collapse": "Klap sammen", "collapse_all": "Klap alle sammen", "color": "Farve", + "color_theme": "Farvetema", "command": "Kommando", "command_palette_prompt": "Find hurtigt sider, handlinger eller kommandoer", "command_palette_to_close": "for at lukke", @@ -747,7 +805,6 @@ "comments_are_disabled": "Kommentarer er slÃĨet fra", "common_create_new_album": "Opret et nyt album", "completed": "Fuldført", - "configuration": "Konfiguration", "confirm": "BekrÃĻft", "confirm_admin_password": "BekrÃĻft administratoradgangskode", "confirm_delete_face": "Er du sikker pÃĨ, du vil slette {name}s ansigt fra denne mediefil?", @@ -762,16 +819,19 @@ "contain": "InddÃĻm", "context": "Kontekst", "continue": "FortsÃĻt", - "control_bottom_app_bar_add_tags": "Tilføj Tags", + "control_bottom_app_bar_create_new_album": "Opret nyt album", + "control_bottom_app_bar_delete_from_immich": "Slet fra Immich", "control_bottom_app_bar_delete_from_local": "Slet fra enhed", "control_bottom_app_bar_edit_location": "Rediger placering", "control_bottom_app_bar_edit_time": "Rediger tid og dato", + "control_bottom_app_bar_share_link": "Del Link", + "control_bottom_app_bar_share_to": "Del til", "control_bottom_app_bar_trash_from_immich": "Flyt til papirkurv", "copied_image_to_clipboard": "Kopierede billede til clipboard.", "copied_to_clipboard": "Kopieret til udklipsholder!", "copy_error": "Kopifejl", + "copy_file_path": "KopiÊr filsti", "copy_image": "KopiÊr billede", - "copy_json": "Kopier JSON", "copy_link": "KopiÊr link", "copy_link_to_clipboard": "KopiÊr link til udklipsholder", "copy_password": "Kopier adgangskode", @@ -792,8 +852,6 @@ "create_new_person": "Opret ny person", "create_new_person_hint": "Tildel valgte aktiver til en ny person", "create_new_user": "Opret ny bruger", - "create_person": "Opret person", - "create_person_subtitle": "Tilføj et navn til det valgte ansigt for at oprette og tagge den nye person", "create_shared_album_page_share_add_assets": "TILFØJ ELEMENT", "create_shared_album_page_share_select_photos": "VÃĻlg Billeder", "create_shared_link": "Opret delt link", @@ -805,33 +863,36 @@ "created_at": "Oprettet", "creating_linked_albums": "Opretter sammenkÃĻdede albums...", "crop": "BeskÃĻr", + "crop_aspect_ratio_fixed": "Fast", "crop_aspect_ratio_free": "Fri", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Kvadrat", + "curated_object_page_title": "Ting", "current_device": "NuvÃĻrende enhed", "current_pin_code": "NuvÃĻrende PIN kode", "current_server_address": "NuvÃĻrende serveraddresse", "custom_date": "Brugerdefineret dato", "custom_locale": "Brugerdefineret lokale", "custom_locale_description": "FormatÊr datoer, klokkeslÃĻt og tal baseret pÃĨ det valgte sprog og den valgte region", + "custom_url": "Tilpasset URL", "cutoff_date_description": "Behold fotos fra den sidsteâ€Ļ", "cutoff_day": "{count, plural, one {dag} other {dage}}", "cutoff_year": "{count, plural, one {ÃĨr} other {ÃĨr}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Mørk", "dark_theme": "Skift til mørkt tema", "date": "Dato", "date_after": "Dato efter", "date_and_time": "Dato og klokkeslÃĻt", "date_before": "Dato før", - "date_of_birth": "Fødselsdag", + "date_format": "E d. LLL y â€ĸ hh:mm", "date_of_birth_saved": "Fødselsdatoen blev gemt korrekt", "date_range": "Datointerval", - "date_time_original": "Dato/Tid Original", "day": "Dag", "days": "Dage", "deduplicate_all": "Dedubliker alle", - "default_quality_subtitle": "Kvalitet brugt ved tryk pÃĨ 'del'. Tryk lÃĻnge pÃĨ dele-knappen for at vÃĻlge hver gang.", - "default_share_quality": "Standard delingskvalitet", + "default_locale_description": "FormatÊr datoer og tal baseret pÃĨ din browsers landestandard", "delete": "Slet", "delete_action_confirmation_message": "Er du sikker pÃĨ, at du vil slette dette objekt? Denne handling vil flytte objektet til serverens papirkurv, og vil spørge dig, om du vil slette den lokalt", "delete_action_prompt": "{count} slettet", @@ -840,6 +901,8 @@ "delete_dialog_alert": "Disse elementer vil blive slettet permanent fra Immich og din enhed", "delete_dialog_alert_local": "Disse elementer slettes permanent fra din enhed, men vil stadig vÃĻre tilgÃĻngelige pÃĨ serveren", "delete_dialog_alert_local_non_backed_up": "Nogle af elementerne har ingen backup pÃĨ serveren og vil blive slettet permanent fra din enhed", + "delete_dialog_alert_remote": "Disse elementer slettes permanent fra serveren", + "delete_dialog_ok_force": "Slet alligevel", "delete_dialog_title": "Slet permanent", "delete_duplicates_confirmation": "Er du sikker pÃĨ, at du vil slette disse dubletter permanent?", "delete_face": "Slet ansigt", @@ -858,16 +921,21 @@ "delete_tag_confirmation_prompt": "Er du sikker pÃĨ, at du vil slette {tagName}-tagget?", "delete_user": "Slet bruger", "deleted_shared_link": "Slettede delt link", + "deletes_missing_assets": "Sletter aktiver, der mangler fra disken", "description": "Beskrivelse", + "description_input_hint_text": "Tilføj en beskrivelse...", + "description_input_submit_error": "Fejl med at opdatere beskrivelsen. Tjek loggen for flere detaljer", "deselect_all": "AfmarkÊr alt", "details": "Detaljer", "direction": "Retning", "disable": "Deaktiver", "disabled": "Deaktiveret", + "disallow_edits": "DeaktivÊr redigeringer", "discord": "Discord", "discover": "Opdag", "discovered_devices": "Opdaget enheder", "dismiss_all_errors": "Afvis alle fejl", + "dismiss_error": "Afvis fejl", "display_options": "Display-indstillinger", "display_order": "Display-rÃĻkkefølge", "display_original_photos": "Vis originale billeder", @@ -876,9 +944,11 @@ "documentation": "Dokumentation", "done": "FÃĻrdig", "download": "Hent", + "download_action_prompt": "Downloader {count} objekter", "download_canceled": "Download annulleret", "download_complete": "Download fuldført", "download_enqueue": "Donload sat i kø", + "download_error": "Fejl med download", "download_failed": "Download mislykkes", "download_finished": "Download afsluttet", "download_include_embedded_motion_videos": "Indlejrede videoer", @@ -888,17 +958,17 @@ "download_paused": "Download pauset", "download_settings": "Download", "download_settings_description": "Administrer indstillinger relateret til mediefil-downloads", + "download_started": "Download startet", + "download_sucess": "Download fÃĻrdig", + "download_sucess_android": "Mediet er blevet downloadet til DCIM/Immich", "download_waiting_to_retry": "Afventer at prøve igen", "downloading": "Downloader", "downloading_asset_filename": "Downloader mediefil {filename}", "downloading_from_icloud": "Downloading fra iCloud", "downloading_media": "Download medier", - "drag_to_reorder": "TrÃĻk for at ÃĻndre rÃĻkkefølgen", "drop_files_to_upload": "Slip filer hvor som helst for at uploade dem", - "duplicate": "Duplikere", - "duplicate_workflow": "Duplikeret arbejdsgang", "duplicates": "Duplikater", - "duplicates_description": "Løs hver gruppe ved at angive hvilke, hvis nogen, er dubletter.", + "duplicates_description": "Løs hver gruppe ved at angive hvilke, hvis nogen, er dubletter", "duration": "Varighed", "edit": "Rediger", "edit_album": "RedigÊr album", @@ -908,7 +978,9 @@ "edit_date_and_time": "RedigÊr dato og tid", "edit_date_and_time_action_prompt": "{count} dato og tid redigeret", "edit_date_and_time_by_offset": "Forskyde dato med offset", + "edit_date_and_time_by_offset_interval": "Nyt datointerval: {from} - {to}", "edit_description": "Rediger beskrivelse", + "edit_description_prompt": "VÃĻlg venligst en ny beskrivelse:", "edit_exclusion_pattern": "RedigÊr udelukkelsesmønster", "edit_faces": "RedigÊr ansigter", "edit_key": "RedigÊr nøgle", @@ -922,7 +994,10 @@ "edit_title": "RedigÊr titel", "edit_user": "RedigÊr bruger", "edit_workflow": "Rediger workflow", - "editor": "Rediger", + "editor": "Redaktør", + "editor_close_without_save_prompt": "Ændringerne vil ikke blive gemt", + "editor_close_without_save_title": "Luk editor?", + "editor_confirm_reset_all_changes": "Er du sikker pÃĨ, at du vil nulstille alle ÃĻndringer?", "editor_discard_edits_confirm": "KassÊr redigeringer", "editor_discard_edits_prompt": "Du har ikke‑gemte redigeringer. Er du sikker pÃĨ, at du vil kassere dem?", "editor_discard_edits_title": "KassÊr ÃĻndringer?", @@ -951,7 +1026,9 @@ "enter_your_pin_code": "Indtast din PIN kode", "enter_your_pin_code_subtitle": "Indtast din PIN kode for at tilgÃĨ den lÃĨste mappe", "error": "Fejl", + "error_change_sort_album": "Ændring af sorteringsrÃĻkkefølgen mislykkedes", "error_delete_face": "Fejl ved sletning af ansigt fra mediefil", + "error_getting_places": "Fejl ved hentning af steder", "error_loading_albums": "Fejl ved indlÃĻsning af album", "error_loading_image": "Fejl ved indlÃĻsning af billede", "error_loading_partners": "Fejl ved indlÃĻsning af partnere: {error}", @@ -991,7 +1068,6 @@ "failed_to_remove_product_key": "Fjernelse af produktnøgle mislykkedes", "failed_to_reset_pin_code": "Kunne ikke resette PIN-koden", "failed_to_stack_assets": "Det lykkedes ikke at stable mediefiler", - "failed_to_tag_assets": "Kunne ikke tagge mediefiler", "failed_to_unstack_assets": "Det lykkedes ikke at fjerne gruperingen af mediefiler", "failed_to_update_notification_status": "Kunne ikke uploade notifikations status", "incorrect_email_or_password": "Forkert email eller kodeord", @@ -1090,10 +1166,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Tilføj beskrivelse...", "exif_bottom_sheet_description_error": "Fejl ved opdatering af beskrivelsen", + "exif_bottom_sheet_details": "DETALJER", + "exif_bottom_sheet_location": "LOKATION", "exif_bottom_sheet_no_description": "Ingen beskrivelse", + "exif_bottom_sheet_people": "PERSONER", + "exif_bottom_sheet_person_add_person": "Tilføj navn", "exit_slideshow": "Afslut slideshow", "expand": "Udvid", "expand_all": "Udvid alle", + "experimental_settings_new_asset_list_subtitle": "Under udarbejdelse", + "experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter", + "experimental_settings_subtitle": "Brug pÃĨ eget ansvar!", + "experimental_settings_title": "Eksperimentelle", "expire_after": "Udløb efter", "expired": "Udløbet", "expires_date": "Udløber {date}", @@ -1103,40 +1187,41 @@ "export_as_json": "EksportÊr som JSON", "export_database": "Eksporter database", "export_database_description": "Eksporter SQLite databasen", - "exposure_time": "Eksponeringstid", "extension": "Udvidelse", "external": "Ekstern", "external_libraries": "Eksterne biblioteker", "external_network": "Eksternt netvÃĻrk", "external_network_sheet_info": "NÃĨ der er ikke er forbundet til det foretrukne Wi-Fi netvÃĻrk, vil appen forbinde til den første URL den kan forbinde til, pÃĨ listen nedenfor. Startende fra toppen", - "f_number": "F-Nummer", "face_unassigned": "Ikke tildelt", "failed": "Fejlet", "failed_count": "Fejlede: {count}", "failed_to_authenticate": "Kunne ikke godkendes", - "failed_to_delete_file": "Kunne ikke slette filen", "failed_to_load_assets": "Kunne ikke indlÃĻse mediefiler", "failed_to_load_folder": "Kunne ikke indlÃĻse mappe", "favorite": "Favorit", "favorite_action_prompt": "{count} føjet til favoritter", "favorite_or_unfavorite_photo": "Tilføj eller fjern fra yndlingsbilleder", "favorites": "Favoritter", + "favorites_page_no_favorites": "Ingen favoritter blev fundet", "feature_photo_updated": "Forsidebillede uploadet", "features": "Funktioner", + "features_in_development": "Funktioner under udvikling", "features_setting_description": "Administrer app-funktioner", "file_name_or_extension": "Filnavn eller filtype", "file_name_text": "Filnavn", + "file_name_with_value": "Filnavn: {file_name}", "file_size": "Fil størrelse", "filename": "Filnavn", "filetype": "Filtype", "filter": "Filter", + "filter_description": "Betingelser for filtrering af valgte mediefiler", "filter_people": "FiltrÊr personer", "filter_places": "Filtrer steder", "filter_tags": "Filtrer tags", "filters": "Filtre", + "find_them_fast": "Find dem hurtigt med søgning via navn", "first": "Første", "fix_incorrect_match": "Fix forkert match", - "focal_length": "BrÃĻndvidde", "folder": "Mappe", "folder_not_found": "Mappe ikke fundet", "folders": "Mapper", @@ -1147,7 +1232,6 @@ "free_up_space_description": "Flyt sikkerhedskopierede fotos og videoer til din enheds skraldespand for at frigøre plads. Dine kopier pÃĨ serveren forbliver sikre.", "free_up_space_settings_subtitle": "Frigør enhedslagerplads", "full_path": "Fuld sti: {path}", - "full_path_or_folder": "Fuld sti eller mappe", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Denne funktion indlÃĻser eksterne ressourcer fra Google for at virke.", "general": "Generel", @@ -1168,6 +1252,7 @@ "group_owner": "Grupper efter ejer", "group_places_by": "GruppÊr steder efter...", "group_year": "Grupper efter ÃĨr", + "haptic_feedback_switch": "SlÃĨ haptisk feedback til", "haptic_feedback_title": "Haptisk feedback", "has_quota": "Har kvote", "hash_asset": "Hash objekter", @@ -1188,23 +1273,44 @@ "hide_schema": "Skjul skema", "hide_text_recognition": "Skjul tekstgenkendelse", "hide_unnamed_people": "Skjul unavngivne personer", + "home_page_add_to_album_conflicts": "Tilføjede {added} elementer til album {album}. {failed} elementer er allerede i albummet.", + "home_page_add_to_album_err_local": "Kan endnu ikke tilføje lokale elementer til album. Springer over", + "home_page_add_to_album_success": "Tilføjede {added} elementer til album {album}.", + "home_page_album_err_partner": "Kan endnu ikke tilføje partners elementer til album. Springer over", + "home_page_archive_err_local": "Kan ikke arkivere lokalt element endnu. Springer over", + "home_page_archive_err_partner": "Kan endnu ikke arkivere partners elementer. Springer over", "home_page_building_timeline": "Bygger tidslinjen", + "home_page_delete_err_partner": "Kan endnu ikke slette partners elementer. Springer over", + "home_page_delete_remote_err_local": "Lokale elementer i fjernsletningssektion. Springer over", + "home_page_favorite_err_local": "Det er ikke muligt at gøre lokale elementer til favoritter endnu, springer over", + "home_page_favorite_err_partner": "Kan endnu ikke tilføje partners elementer som favoritter. Springer over", + "home_page_first_time_notice": "Hvis det er din første gang i appen, bedes du vÃĻlge en sikkerhedskopi af albummer sÃĨ tidlinjen kan blive fyldt med billeder og videoer fra albummerne", + "home_page_locked_error_local": "Kan ikke flytte lokale mediefiler til lÃĨst mappe, springer over", + "home_page_locked_error_partner": "Kan ikke flytte partners mediefiler til lÃĨst mappe, springer over", + "home_page_share_err_local": "Kan ikke dele lokale elementer via link, springer over", + "home_page_upload_err_limit": "Det er kun muligt at lave sikkerhedskopi af 30 elementer ad gangen. Springer over", "host": "Host", "hour": "Time", "hours": "Timer", "id": "ID", "idle": "Inaktiv", + "ignore_icloud_photos": "Ignorer iCloud-billeder", + "ignore_icloud_photos_description": "Billeder der er gemt pÃĨ iCloud vil ikke blive uploadet til Immich-serveren", "image": "Billede", - "image_alt_text_date": "{isVideo, select, true {Video} other {Billede}} taget den {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Billede}} taget med {person1} den {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Billede}} taget med {person1} og {person2} den {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Billede}} taget med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Billede}} taget med {person1}, {person2}, og {additionalCount, number} andre den {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Billede}} taget i {city}, {country} den {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Billede}} taget i {city}, {country} med {person1} den {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Billede}} taget i {city}, {country} med {person1} og {person2} den {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Billede}} taget i {city}, {country} med {person1}, {person2}, og {person3} den {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Billede}} taget i {city}, {country} med {person1}, {person2}, og {additionalCount, number} andre den {date}", + "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taget den {date}", + "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taget med {person1} den {date}", + "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} taget med {person1} og {person2} den {date}", + "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} taget med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taget med {person1}, {person2}, og {additionalCount, number} andre den {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} den {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1} den {date}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1} og {person2} den {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1}, {person2}, og {person3} den {date}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taget i {city}, {country} med {person1}, {person2}, og {additionalCount, number} andre den {date}", + "image_saved_successfully": "Billede gemt", + "image_viewer_page_state_provider_download_started": "Download startet", + "image_viewer_page_state_provider_download_success": "Download succesfuld", + "image_viewer_page_state_provider_share_error": "Delingsfejl", "immich_logo": "Immich logo", "immich_web_interface": "Immich webinterface", "import_from_json": "ImportÊr fra JSON", @@ -1219,7 +1325,6 @@ "individual_share": "Individuel andel", "individual_shares": "Individuelle delinger", "info": "Info", - "integrity_checks": "Integritetstjek", "interval": { "day_at_onepm": "Hver dag kl. 13", "hours": "Hver {hours, plural, one {time} other {{hours, number} timer}}", @@ -1230,9 +1335,16 @@ "invalid_date_format": "Ugyldigt dato format", "invite_people": "Inviter personer", "invite_to_album": "Inviter til album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Hent kørte {dateTime}", + "ios_debug_info_last_sync_at": "Sidste sync skete {dateTime}", + "ios_debug_info_no_processes_queued": "Ingen baggrundsprocesser i kø", + "ios_debug_info_no_sync_yet": "Der er endnu ikke kørt noget baggrundssynkroniseringsjob", + "ios_debug_info_processes_queued": "{count, plural, one {{count} baggrundsproces i kø} other {{count} baggrundsprocesser i kø}}", + "ios_debug_info_processing_ran_at": "Behandlingen kørte {dateTime}", "items_count": "{count, plural, one {# element} other {# elementer}}", "jobs": "Opgaver", + "json_editor": "JSON editor", + "json_error": "JSON fejl", "keep": "Behold", "keep_albums": "Behold albums", "keep_albums_count": "Beholder {count} {count, plural, one {album} other {albums}}", @@ -1259,12 +1371,14 @@ "leave": "Forlad", "leave_album": "Forlad album", "lens_model": "Objektivmodel", - "less": "Mindre", "let_others_respond": "Lad andre svare", "level": "Niveau", "library": "Bibliotek", "library_add_folder": "Tilføj mappe", "library_edit_folder": "Rediger mappe", + "library_options": "Biblioteksindstillinger", + "library_page_device_albums": "Albummer pÃĨ enhed", + "library_page_new_album": "Nyt album", "library_page_sort_asset_count": "Antal af elementer", "library_page_sort_created": "Senest oprettet", "library_page_sort_last_modified": "Sidst redigeret", @@ -1274,14 +1388,11 @@ "light_theme": "Skift til lyst tema", "like": "Synes om", "like_deleted": "Ligesom slettet", - "link": "Link", "link_motion_video": "Link bevÃĻgelsesvideo", "link_to_docs": "For yderligere information, se dokumentationen.", "link_to_oauth": "Link til OAuth", "linked_oauth_account": "Tilsluttet OAuth-konto", "list": "Liste", - "live": "Live", - "load_more": "IndlÃĻs flere", "loading": "IndlÃĻser", "loading_search_results_failed": "IndlÃĻsning af søgeresultater fejlede", "local": "Lokal", @@ -1310,9 +1421,11 @@ "login": "Log ind", "login_disabled": "Login er blevet deaktiveret", "login_form_api_exception": "API-undtagelse. Tjek serverens URL og prøv igen.", + "login_form_back_button_text": "Tilbage", "login_form_email_hint": "din-e-mail@e-mail.com", "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Server endepunkt URL", + "login_form_err_http": "Angiv venligst http:// eller https://", "login_form_err_invalid_email": "Ugyldig e-mail", "login_form_err_invalid_url": "Ugyldig webadresse", "login_form_err_leading_whitespace": "Mellemrum før", @@ -1322,9 +1435,10 @@ "login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server webadressen, e-mailen og kodeordet", "login_form_handshake_exception": "Der opstod en fejl med at oprette forbindelse til serveren. Aktiver selvsignerede certifikater i indstillingerne, hvis du bruger et selv signeret certifikat.", "login_form_password_hint": "kodeord", - "login_form_server_empty": "Indtast server-URL", - "login_form_server_error": "Kunne ikke forbinde til serveren", - "login_has_been_disabled": "Login er blevet deaktiveret", + "login_form_save_login": "Forbliv logget ind", + "login_form_server_empty": "Indtast server-URL.", + "login_form_server_error": "Kunne ikke forbinde til serveren.", + "login_has_been_disabled": "Login er blevet deaktiveret.", "login_password_changed_error": "Der opstod en fejl i opdateringen af dit kodeord", "login_password_changed_success": "Kodeordet blev opdateret", "logout_all_device_confirmation": "Er du sikker pÃĨ, at du vil logge ud af alle enheder?", @@ -1339,6 +1453,7 @@ "maintenance_action_restore": "Genopretter database", "maintenance_description": "Immich er blevet sat i vedligeholdelsestilstand.", "maintenance_end": "Afslut vedligeholdelsestilstand", + "maintenance_end_error": "Vedligeholdelsestilstand kunne ikke afsluttes.", "maintenance_logged_in_as": "Aktuelt logget ind som {user}", "maintenance_restore_from_backup": "Genskab fra sikkerhedskopi", "maintenance_restore_library": "Genskab dit bibliotek", @@ -1364,6 +1479,7 @@ "manage_media_access_settings": "Åben instillinger", "manage_media_access_subtitle": "Tillad Immich appen at administrere og flytte mediefiler.", "manage_media_access_title": "Mediestyringsadgang", + "manage_shared_links": "HÃĨndter delte links", "manage_sharing_with_partners": "AdministrÊr deling med partnere", "manage_the_app_settings": "Administrer appindstillinger", "manage_your_account": "AdministrÊr din konto", @@ -1371,11 +1487,13 @@ "manage_your_devices": "AdministrÊr dine enheder der er logget ind", "manage_your_oauth_connection": "AdministrÊr din OAuth-tilslutning", "map": "Kort", + "map_assets_in_bounds": "{count, plural, =0 {Ingen billeder i dette omrÃĨde} one {# billede} other {# billeder}}", "map_cannot_get_user_location": "Kan ikke finde brugerens placering", + "map_location_dialog_yes": "Ja", "map_location_picker_page_use_location": "Brug denne placering", "map_location_service_disabled_content": "Placeringstjenesten skal aktiveres for at vise elementer fra din nuvÃĻrende placering. Vil du aktivere den nu?", "map_location_service_disabled_title": "Placeringstjenesten er deaktiveret", - "map_marker_for_image": "Kortmarkør for billede taget i {city}, {country}", + "map_marker_for_images": "Kortmarkør for billeder taget i {city}, {country}", "map_marker_with_image": "Kortmarkør med billede", "map_no_location_permission_content": "Der krÃĻves tilladelse til placeringen for at vise elementer fra din nuvÃĻrende placering. Vil du give tilladelse?", "map_no_location_permission_title": "Placeringstilladelse blev afvist", @@ -1385,47 +1503,18 @@ "map_settings_date_range_option_days": "Sidste {days} dage", "map_settings_date_range_option_year": "Sidste ÃĨr", "map_settings_date_range_option_years": "Sidste {years} ÃĨr", + "map_settings_dialog_title": "Kortindstillinger", "map_settings_include_show_archived": "Inkluder arkiverede", "map_settings_include_show_partners": "Inkluder partnere", "map_settings_only_show_favorites": "Vis kun favoritter", "map_settings_theme_settings": "Korttema", + "map_zoom_to_see_photos": "Zoom ud for at vise billeder", "mark_all_as_read": "Marker alle som lÃĻst", + "mark_as_read": "Marker som lÃĻst", "marked_all_as_read": "Markerede alle som lÃĻst", "matches": "Parringer", "matching_assets": "Matchende objekter", - "media_chrome": { - "auto": "Auto", - "captions": "Undertekster", - "captions_off": "Fra", - "closed_captions": "Undertekster for hørehÃĻmmede", - "decode_error": "Fejl ved dekodning", - "disable_captions": "SlÃĨ undertekster fra", - "enable_captions": "SlÃĨ undertekster til", - "enter_fullscreen_mode": "Fuld skÃĻrm", - "exit_fullscreen_mode": "Luk fuld skÃĻrm", - "loop": "Gentag", - "media_error_description": "En mediefejl stoppede afspilningen. Filen kan vÃĻre korrupt eller browseren understøtter ikke filtypen.", - "media_loading": "Loader medie", - "mute": "Sluk lyd", - "network_error": "NetvÃĻrksfejl", - "network_error_description": "En netvÃĻrksfejl fik download til at fejle.", - "not_supported_error": "Kilde er ikke understøttet", - "playback_rate": "Afspilningshastighed", - "playback_rate_current": "nuvÃĻrende afspilningshastighed", - "playback_rate_value": "Afspilningshastighed {playbackRate}", - "playback_time": "afspilnings varighed", - "quality": "Kvalitet", - "second": "sekund", - "seconds": "sekunder", - "time_value_of_total_time": "{currentTime} af {totalTime}", - "time_value_remaining": "{time} tilbage", - "unmute": "Lyd til", - "unsupported_error_description": "En ukendt fejl opstod. Fejl pÃĨ server, netvÃĻrk eller din browser understøtter ikke formatet.", - "video_not_loaded_unknown_time": "video er ikke indlÃĻst, ukendt tidspunkt.", - "video_player": "videoafspiller", - "volume": "lydstyrke" - }, - "media_type": "Medieformat", + "media_type": "Medietype", "memories": "Minder", "memories_all_caught_up": "Ajour", "memories_check_back_tomorrow": "Kom tilbage i morgen for at se nye minder", @@ -1441,31 +1530,34 @@ "merge_people_prompt": "Vil du flette disse mennesker sammen? Denne handling er uigenkaldelig.", "merge_people_successfully": "Personer sammenflettet med succes", "merged_people_count": "{count, plural, one {# person} other {# personer}} lagt sammen", - "minFaces": "Minimum ansigter", - "minFaces_description": "Det mindste antal ansigter der skal genkendes for at en person vises", "minimize": "MinimÊr", "minute": "Minut", "minutes": "Minutter", + "mirror_horizontal": "Horisontalt", + "mirror_vertical": "Vertikal", "missing": "Mangler", "mobile_app": "Mobil App", "mobile_app_download_onboarding_note": "Hent den tilhørende mobilapp via en af følgende muligheder", "model": "Model", - "modify_date": "Ændre dato", "month": "MÃĨned", + "monthly_title_text_date_format": "MMMM ÃĨ", "more": "Mere", - "motion": "BevÃĻgelse", "move": "Flyt", + "move_down": "Flyt ned", "move_off_locked_folder": "Flyt ud af lÃĨst mappe", "move_to": "Flyt til", "move_to_device_trash": "Flyt til enheds skraldespand", "move_to_lock_folder_action_prompt": "{count} føjet til den lÃĨste mappe", "move_to_locked_folder": "Flyt til lÃĨst mappe", "move_to_locked_folder_confirmation": "Disse billeder og videoer vil blive fjernet fra alle albums, og vil kun vÃĻre synlig fra den lÃĨste mappe", + "move_up": "Flyt op", + "moved_to_archive": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til arkivet", + "moved_to_library": "Flyttede {count, plural, one {# mediefil} other {# mediefiler}} til biblioteket", "moved_to_trash": "Flyttet til papirkurv", + "multiselect_grid_edit_date_time_err_read_only": "Kan ikke redigere datoen pÃĨ skrivebeskyttet elementer. Springer over", + "multiselect_grid_edit_gps_err_read_only": "Kan ikke redigere lokation af skrivebeskyttet elementer. Springer over", "mute_memories": "DÃĻmp minder", "my_albums": "Mine albummer", - "my_immich_description": "Kopier aktuel side som et Mit Immich link", - "my_immich_title": "Mit Immich link", "name": "Navn", "name_or_nickname": "Navn eller kaldenavn", "name_required": "Navn er pÃĨkrÃĻvet", @@ -1480,11 +1572,12 @@ "never": "Aldrig", "new_album": "Nyt album", "new_api_key": "Ny API-nøgle", - "new_feature": "Ny Feature", + "new_date_range": "Nyt datointerval", "new_password": "Ny adgangskode", "new_person": "Ny person", "new_pin_code": "Ny PIN kode", "new_pin_code_subtitle": "Dette er første gang du tilgÃĨr den lÃĨste mappe. Lav en PIN kode for sikkert at tilgÃĨ denne side", + "new_timeline": "Ny tidslinje", "new_update": "Ny opdatering", "new_user_created": "Ny bruger oprettet", "new_version_available": "NY VERSION TILGÆNGELIG", @@ -1492,6 +1585,7 @@ "next": "NÃĻste", "next_memory": "NÃĻste minde", "no": "Nej", + "no_actions_added": "Ingen handlinger tilføjet endnu", "no_albums_found": "Ingen album fundet", "no_albums_message": "Opret et album for at organisere dine billeder og videoer", "no_albums_with_name_yet": "Det ser ud til, at du ikke har noget album med dette navn endnu.", @@ -1502,13 +1596,16 @@ "no_cast_devices_found": "Ingen Cast-enheder fundet", "no_checksum_local": "Ingen checksum tilgÃĻngelig – kan ikke hente lokale objekter", "no_checksum_remote": "Ingen checksum tilgÃĻngelig – kan ikke hente eksterne objekter", + "no_configuration_needed": "Ingen konfiguration nødvendig", "no_devices": "Ingen godkendte enheder", "no_duplicates_found": "Ingen duplikater fundet.", "no_exif_info_available": "Ingen tilgÃĻngelig exif information", "no_explore_results_message": "Upload flere billeder for at udforske din samling.", "no_favorites_message": "Tilføj favoritter for hurtigt at finde dine bedst billeder og videoer", + "no_filters_added": "Ingen filtre tilføjet endnu", "no_libraries_message": "Opret et eksternt bibliotek for at se dine billeder og videoer", "no_local_assets_found": "Ingen lokale objekter fundet med denne checksum", + "no_location_set": "Ingen placering sat", "no_locked_photos_message": "Billeder og videoer i den lÃĨste mappe er skjulte og vil ikke blive vist i dit bibliotek.", "no_name": "Intet navn", "no_notifications": "Ingen notifikationer", @@ -1518,19 +1615,14 @@ "no_results": "Ingen resultater", "no_results_description": "Prøv et synonym eller et mere generelt søgeord", "no_shared_albums_message": "Opret et album for at dele billeder og videoer med personer i dit netvÃĻrk", - "no_steps": "Ingen trin tilføjet endnu", + "no_uploads_in_progress": "Ingen upload i gang", "none": "Ingen", "not_allowed": "Ikke tilladt", "not_available": "ikke tilgÃĻngelig", "not_in_any_album": "Ikke i noget album", "not_selected": "Ikke valgt", - "not_set": "Ikke angivet", "notes": "Noter", "nothing_here_yet": "Intet her endnu", - "notification_backup_reliability": "AktivÊr notifikationer for at forbedre pÃĨlideligheden af backup i baggrunden", - "notification_enabled_list_tile_content": "Immich bruger notifikationer til baggrundsbackup. Administrer dem i dine enhedsindstillinger.", - "notification_enabled_list_tile_open_button": "Åbn indstillinger", - "notification_enabled_list_tile_title": "Notifikationer aktiveret", "notification_permission_dialog_content": "GÃĨ til indstillinger for at slÃĨ notifikationer til.", "notification_permission_list_tile_content": "Tillad at bruge notifikationer.", "notification_permission_list_tile_enable_button": "SlÃĨ notifikationer til", @@ -1542,8 +1634,6 @@ "obtainium_configurator": "Obtainium-konfigurator", "obtainium_configurator_instructions": "Brug Obtainium til at installere og opdatere Android-appen direkte fra Immich-udgivelsen pÃĨ GitHub. Opret en API-nøgle, og vÃĻlg en variant for at generere dit Obtainium-konfigurationslink", "ocr": "OCR", - "ocr_body": "Immich lÃĻser nu teksten i dine billeder, sÃĨ du kan søge efter dem ud fra, hvad de siger.", - "ocr_title": "Søg tekst i dine fotos", "official_immich_resources": "Officielle Immich-ressourcer", "offline": "Offline", "offset": "Forskydning", @@ -1562,8 +1652,6 @@ "open": "Åben", "open_calendar": "Åbn kalender", "open_in_browser": "Åbn i browser", - "open_in_immich_body": "SÃĻt Immich som dit galleri pÃĨ Android for at ÃĨbne billeder direkte fra andre apps.", - "open_in_immich_title": "Åbne billeder i Immich", "open_in_map_view": "Åben i kortvisning", "open_in_openstreetmap": "Åben i OpenStreetMap", "open_the_search_filters": "Åbn søgefiltre", @@ -1572,10 +1660,10 @@ "organize_into_albums": "Organiser i album", "organize_into_albums_description": "SÃĻt eksisterende billeder i albummer ved hjÃĻlp af aktuelle synkroniseringsindstillinger", "organize_your_library": "OrganisÊr dit bibliotek", - "orientation": "Orientering", "original": "original", "other": "Andet", "other_devices": "Andre enheder", + "other_entities": "Andre enheder", "other_variables": "Andre variable", "owned": "Egne", "owner": "Ejer", @@ -1585,9 +1673,12 @@ "partner_can_access_assets": "Alle dine billeder og videoer, bortset fra dem i Arkiv og Slettet", "partner_can_access_location": "Stedet, hvor dine billeder blev taget", "partner_list_user_photos": "{user}s billeder", + "partner_list_view_all": "Se alle", "partner_page_empty_message": "Dine billeder er endnu ikke delt med en partner.", "partner_page_no_more_users": "Der er ikke flere brugere at tilføje", + "partner_page_partner_add_failed": "Kunne ikke tilføje en partner", "partner_page_select_partner": "VÃĻlg partner", + "partner_page_shared_to_title": "Delt til", "partner_page_stop_sharing_content": "{partner} vil ikke lÃĻngere have adgang til dine billeder.", "partner_sharing": "Partnerdeling", "partners": "Partnere", @@ -1609,6 +1700,8 @@ "people": "Personer", "people_edits_count": "Redigeret {count, plural, one {# person} other {# people}}", "people_feature_description": "Gennemse billeder og videoer grupperet efter personer", + "people_selected": "{count, plural, one {# person vagt} other {# personer valgt}}", + "people_sidebar_description": "Vis et link til Personer i sidepanelet", "permanent_deletion_warning": "Advarsel om permanent sletning", "permanent_deletion_warning_setting_description": "Vis en advarsel, nÃĨr medier slettes permanent", "permanently_delete": "Slet permanent", @@ -1618,17 +1711,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# aktiv} other {# aktiver}} permanent slettet", "permission": "Tilladelse", "permission_empty": "Din tilladelse mÃĨ ikke vÃĻre tom", + "permission_onboarding_back": "Tilbage", + "permission_onboarding_continue_anyway": "FortsÃĻt alligevel", + "permission_onboarding_get_started": "Kom i gang", + "permission_onboarding_go_to_settings": "GÃĨ til indstillinger", + "permission_onboarding_permission_denied": "Tilladelse afvist. For at bruge Immich, skal der gives tilladelse til at se billeder og videoer i indstillinger.", + "permission_onboarding_permission_granted": "Tilladelse givet! Du er nu klar.", + "permission_onboarding_permission_limited": "Tilladelse begrÃĻnset. For at lade Immich lave sikkerhedskopi og styre hele dit galleri, skal der gives tilladelse til billeder og videoer i indstillinger.", + "permission_onboarding_request": "Immich krÃĻver tilladelse til at se dine billeder og videoer.", "person": "Person", "person_age_months": "{months, plural, one {# month} other {# months}} gammel", "person_age_year_months": "1 ÃĨr, {months, plural, one {# month} other {# months}} gammel", - "person_age_years": "{years, plural, other {# ÃĨr}} gammel", + "person_age_years": "{years, plural, other {# years}} gammel", "person_birthdate": "Født den {date}", "person_hidden": "{name}{hidden, select, true { (skjult)} other {}}", "person_recognized": "Person genkendt", + "person_selected": "Person valgt", "photo_shared_all_users": "Det ser ud til, at du har delt dine billeder med alle brugere, eller ogsÃĨ har du ikke nogen bruger at dele med.", "photos": "Billeder", "photos_and_videos": "Billeder og videoer", + "photos_count": "{count, plural, one {{count, number} Billede} other {{count, number} Billeder}}", "photos_from_previous_years": "Billeder fra tidligere ÃĨr", + "photos_only": "Kun fotos", "pick_a_location": "VÃĻlg et sted", "pick_custom_range": "Brugerdefineret interval", "pick_date_range": "VÃĻlg et datointerval", @@ -1647,8 +1751,6 @@ "play_original_video_setting_description": "ForetrÃĻkker afspilning af originale videoer frem for transkodede videoer. Hvis det originale element ikke er kompatibelt, afspilles det muligvis ikke korrekt.", "play_transcoded_video": "Afspil transkodet video", "please_auth_to_access": "Log venligst ind for at tilgÃĨ", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Denne metode kan filtrere hÃĻndelser og betinget forhindre efterfølgende trin i at køre", "port": "Port", "preferences_settings_subtitle": "Administrer appens indstillinger", "preferences_settings_title": "PrÃĻferencer", @@ -1665,11 +1767,11 @@ "privacy": "Privatliv", "profile": "Profil", "profile_drawer_app_logs": "Log", + "profile_drawer_client_server_up_to_date": "Klient og server er ajour", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivebeskyttet tilstand aktiveret. Lang tryk pÃĨ bruger avatar ikonet for at afslutte.", "profile_image_of_user": "Profilbillede af {user}", "profile_picture_set": "Profilbillede indstillet.", - "projection_type": "Projektionstype", "public_album": "Offentligt album", "public_share": "Offentlig deling", "purchase_account_info": "Supporter", @@ -1688,6 +1790,7 @@ "purchase_individual_description_2": "Supporterstatus", "purchase_individual_title": "Individuel", "purchase_input_suggestion": "Har du en produktnøgle? Indtast nøglen nedenfor", + "purchase_license_subtitle": "Køb Immich for at understøtte den fortsatte udvikling af tjenesten", "purchase_lifetime_description": "Livsvarigt køb", "purchase_option_title": "KØBSMULIGHEDER", "purchase_panel_info_1": "At bygge Immich tager meget tid og krÃĻfter, og vi har fuldtidsudviklere, der arbejder pÃĨ det for at gøre det sÃĨ godt, som vi overhovedet kan. Vores mission er, at open source-software og etisk forretningspraksis bliver en bÃĻredygtig indtÃĻgtskilde for udviklere og at skabe et privatlivsrespekterende økosystem med reelle alternativer til udnyttende cloud-tjenester.", @@ -1720,19 +1823,19 @@ "reassigned_assets_to_new_person": "Gentildelt {count, plural, one {# aktiv} other {# aktiver}} til en ny person", "reassing_hint": "Tildel valgte mediefiler til en eksisterende person", "recent": "For nylig", + "recent_albums": "Seneste albums", "recent_searches": "Seneste søgninger", "recently_added": "Senest tilføjet", - "recently_added_body": "GÃĨ direkte til alt, hvad du for nylig har tilføjet, pÃĨ en dedikeret side.", - "recently_added_description": "Gennemse dine mediefiler sorteret efter, hvornÃĨr de blev uploadet til Immich", "recently_added_page_title": "Nyligt tilføjet", - "recently_added_title": "Senest tilføjet", "recently_taken": "Taget for nylig", + "recently_taken_page_title": "Taget For nylig", "refresh": "OpdatÊr", "refresh_encoded_videos": "Opdater kodede videoer", "refresh_faces": "Opdater ansigter", "refresh_metadata": "Opdater metadata", "refresh_thumbnails": "Opdater forhÃĨndsvisning", "refreshed": "Opdateret", + "refreshes_every_file": "Opdaterer alle filer", "refreshing_encoded_video": "Opdaterer kodet video", "refreshing_faces": "Opdaterer ansigter", "refreshing_metadata": "Opdaterer metadata", @@ -1745,7 +1848,7 @@ "remove_assets_shared_link_confirmation": "Er du sikker pÃĨ, at du vil fjerne {count, plural, one {# aktiv} other {# aktiver}} fra dette delte link?", "remove_assets_title": "Fjern mediefiler?", "remove_custom_date_range": "Fjern tilpasset datointerval", - "remove_filter": "Fjern filter", + "remove_deleted_assets": "Fjern slettede mediefiler", "remove_from_album": "Fjern fra album", "remove_from_album_action_prompt": "{count} fjernet fra albummet", "remove_from_favorites": "Fjern fra favoritter", @@ -1756,22 +1859,29 @@ "remove_memory": "Fjern minde", "remove_photo_from_memory": "Fjern foto fra dette minde", "remove_tag": "Fjern tag", + "remove_url": "Fjern URL", "remove_user": "Fjern bruger", "removed_api_key": "Fjernede API-nøgle: {name}", "removed_from_archive": "Fjernet fra arkiv", "removed_from_favorites": "Fjernet fra favoritter", "removed_from_favorites_count": "{count, plural, other {Fjernede #}} fra favoritter", "removed_memory": "Fjernede minde", + "removed_photo_from_memory": "Fjernede foto fra minde", "removed_tagged_assets": "Fjernede tag fra {count, plural, one {# aktiv} other {# aktiver}}", "rename": "Omdøb", + "repair": "ReparÊr", + "repair_no_results_message": "Usporede og manglende filer vil blive vist her", + "replace_with_upload": "Erstat med upload", "repository": "Depot", "require_password": "KrÃĻv adgangskode", + "require_user_to_change_password_on_first_login": "KrÃĻv at bruger skifter adgangskode ved første login", "rescan": "Genopfrisk", "reset": "Nulstil", "reset_password": "Nulstil adgangskode", "reset_people_visibility": "Nulstil personsynlighed", "reset_pin_code": "Nulstil PIN kode", "reset_pin_code_description": "Hvis du har glemt din PIN-kode, kan du kontakte serveradministratoren for at fÃĨ den nulstillet", + "reset_pin_code_success": "PIN-koden er Nulstillet", "reset_pin_code_with_password": "Du kan altid nulstille din PIN-kode med dit password", "reset_sqlite": "Reset SQLite Databasen", "reset_sqlite_clear_app_data": "Ryd data", @@ -1785,17 +1895,20 @@ "resolved_all_duplicates": "Alle dubletter løst", "restore": "Gendan", "restore_all": "Gendan alle", + "restore_trash_action_prompt": "{count} genskabt fra papirkurven", "restore_user": "Gendan bruger", "restored_asset": "Gendannet mediefilen", "resume": "Genoptag", "resume_paused_jobs": "FortsÃĻt {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "Forsøg upload igen", "review_duplicates": "GennemgÃĨ dubletter", "review_large_files": "GennemgÃĨ store filer", "role": "Rolle", - "role_editor": "Rediger", + "role_editor": "Redaktør", "role_viewer": "Seer", "running": "Kører", "save": "Gem", + "save_to_gallery": "Gem til galleri", "saved": "Gemt", "saved_api_key": "Gemt API-nøgle", "saved_profile": "Gemt profil", @@ -1806,10 +1919,9 @@ "scan": "Skan", "scan_all_libraries": "Skan alle biblioteker", "scan_library": "Skan", + "scan_settings": "Skanningsindstillinger", "scanning": "Skanner", "scanning_for_album": "Skanner efter albummer...", - "screencast_mode_description": "Vis indikatorer for tastatur- og musehÃĻndelse pÃĨ skÃĻrmen", - "screencast_mode_title": "Skift skÃĻrmcast-tilstand", "search": "Søg", "search_albums": "Søg i albummer", "search_by_context": "Søg efter kontekst", @@ -1817,8 +1929,6 @@ "search_by_description_example": "Vandredag i Paris", "search_by_filename": "Søg efter filnavn eller filtypenavn", "search_by_filename_example": "dvs. IMG_1234.JPG eller PNG", - "search_by_full_path": "søg efter fuld sti eller mappe", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - du kan søge efter Projects, 3D, Printing, 2026 osv.", "search_by_ocr": "Søg via OCR", "search_by_ocr_example": "Søg efter tekst i dine billeder", "search_camera_lens_model": "Søg objektiv model...", @@ -1838,6 +1948,7 @@ "search_filter_location_title": "VÃĻlg lokation", "search_filter_media_type": "Medietype", "search_filter_media_type_title": "VÃĻlg medietype", + "search_filter_ocr": "Søg via OCR", "search_filter_people_title": "VÃĻlg personer", "search_filter_star_rating": "Stjerne Vurdering", "search_filter_tags_title": "VÃĻlg tags", @@ -1848,13 +1959,25 @@ "search_no_people_named": "Ingen personer med navnet \"{name}\"", "search_no_result": "Ingen resultater fundet, prøv en anden søgestreng eller kombination", "search_options": "Søgemuligheder", + "search_page_categories": "Kategorier", + "search_page_motion_photos": "BevÃĻgelsesbilleder", + "search_page_no_objects": "Ingen elementinfomation er tilgÃĻngelig", + "search_page_no_places": "Ingen placeringsinformation er tilgÃĻngelig", + "search_page_screenshots": "SkÃĻrmbilleder", "search_page_search_photos_videos": "Søg i dine billeder og videoer", + "search_page_selfies": "Selfier", + "search_page_things": "Ting", "search_page_view_all_button": "Vis alt", + "search_page_your_activity": "Din aktivitet", + "search_page_your_map": "Dit kort", "search_people": "Søg i personer", "search_places": "Søg i steder", "search_rating": "Søg efter vurdering...", + "search_result_page_new_search_hint": "Ny søgning", "search_settings": "søgeindstillinger", "search_state": "Søg efter lansdel...", + "search_suggestion_list_smart_search_hint_1": "Smart søgnining er slÃĨet til som standard. For at søge efter metadata brug syntaksen ", + "search_suggestion_list_smart_search_hint_2": "m:dit-søgeord", "search_tags": "Søg tags...", "search_timezone": "Søg i tidszone...", "search_type": "Søg efter type", @@ -1868,6 +1991,7 @@ "select_albums": "VÃĻlg albummer", "select_all": "VÃĻlg alle", "select_all_duplicates": "VÃĻlg alle dubletter", + "select_all_in": "VÃĻlg alt i {group}", "select_avatar_color": "VÃĻlg avatarfarve", "select_count": "{count, plural, one {VÃĻlg #} other {VÃĻlg #}}", "select_cutoff_date": "VÃĻlg stop-dato", @@ -1875,13 +1999,14 @@ "select_featured_photo": "VÃĻlg forsidebillede", "select_from_computer": "VÃĻlg fra computer", "select_keep_all": "VÃĻlg gem alle", + "select_library_owner": "VÃĻlg biblioteksejer", "select_new_face": "VÃĻlg nyt ansigt", "select_people": "VÃĻlg personer", "select_person": "VÃĻlg person", "select_person_to_tag": "VÃĻlg en person at tagge", "select_photos": "VÃĻlg billeder", - "select_quality": "VÃĻlg kvalitet", "select_trash_all": "VÃĻlg smid alle ud", + "select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album", "selected": "Valgt", "selected_count": "{count, plural, one {# valgt} other {# valgte}}", "selected_gps_coordinates": "Udvalgte GPS Koordinater", @@ -1914,31 +2039,42 @@ "setting_image_viewer_original_title": "IndlÃĻs originalbillede", "setting_image_viewer_preview_subtitle": "SlÃĨ indlÃĻsning af et mediumstørrelse billede til. SlÃĨ fra for enten direkte at indlÃĻse originalen eller kun at bruge miniaturebilledet.", "setting_image_viewer_preview_title": "IndlÃĻs forhÃĨndsvisning af billedet", + "setting_image_viewer_title": "Billeder", "setting_languages_apply": "Anvend", "setting_languages_subtitle": "Ændr app-sprog", + "setting_notifications_notify_failures_grace_period": "Giv besked om fejl med sikkerhedskopiering i baggrunden: {duration}", + "setting_notifications_notify_hours": "{count} timer", + "setting_notifications_notify_immediately": "med det samme", "setting_notifications_notify_minutes": "{count} minutter", + "setting_notifications_notify_never": "aldrig", "setting_notifications_notify_seconds": "{count} sekunder", + "setting_notifications_single_progress_subtitle": "Detaljeret uploadstatus pr. element", + "setting_notifications_single_progress_title": "Vis detaljeret baggrundsuploadstatus", "setting_notifications_subtitle": "Tilpas dine notifikationsprÃĻferencer", + "setting_notifications_total_progress_subtitle": "Samlet uploadstatus (fÃĻrdige/samlet antal elementer)", + "setting_notifications_total_progress_title": "Vis samlet baggrunds upload status", "setting_video_viewer_auto_play_subtitle": "Begynd automatisk at afspille videoer, nÃĨr de ÃĨbnes", "setting_video_viewer_auto_play_title": "Automatisk afspilning af videoer", "setting_video_viewer_looping_title": "Genafspilning", "setting_video_viewer_original_video_subtitle": "NÃĨr der streames video fra serveren, afspil da den originale selv nÃĨr en omkodet udgave er tilgÃĻngelig. Kan føre til buffering. Videoer, der er tilgÃĻngelige lokalt, afspilles i original kvalitet uanset denne indstilling.", "setting_video_viewer_original_video_title": "Tving original video", "settings": "Indstillinger", + "settings_require_restart": "Genstart venligst Immich for at anvende denne ÃĻndring", "settings_saved": "Indstillinger er gemt", "setup_pin_code": "Indstil en PIN kode", "share": "Del", + "share_action_prompt": "Delte {count} objekter", + "share_add_photos": "Tilføj billeder", + "share_assets_selected": "{count} valgt", "share_dialog_preparing": "Forbereder...", "share_link": "Del link", - "share_original": "Brug original (stor)", - "share_preview": "Brug miniaturebillede (lille)", - "share_quality_body": "Tryk og hold delingsknappen nede for at vÃĻlge billedkvaliteten, før du deler.", - "share_quality_title": "VÃĻlg din delingskvalitet", "shared": "Delt", "shared_album_activities_input_disable": "Kommentarer er deaktiveret", "shared_album_activity_remove_content": "Vil du slette denne aktivitet?", "shared_album_activity_remove_title": "Slet aktivitet", "shared_album_section_people_action_error": "Der opstod en fejl i fjernelsen fra albummet", + "shared_album_section_people_action_leave": "Fjern brugere fra albummet", + "shared_album_section_people_action_remove_user": "Fjern brugere fra albummet", "shared_album_section_people_title": "PERSONER", "shared_by": "Delt af", "shared_by_user": "Delt af {user}", @@ -1947,10 +2083,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Uploadet", "shared_link_app_bar_title": "Delte links", "shared_link_clipboard_copied_massage": "Kopieret til udklipsholderen", + "shared_link_clipboard_text": "Link: {link}\nAdgangskode: {password}", "shared_link_create_error": "Der opstod en fejl i oprettelsen af et delt link", - "shared_link_custom_url_description": "FÃĨ adgang til dette delte link med et brugerdefineret URL-navn", - "shared_link_custom_url_title": "Tilpasset URL", - "shared_link_custom_url_warning": "Advarsel: Et brugerdefineret URL-navn med en skrÃĨstreg fungerer muligvis ikke som forventet.", + "shared_link_custom_url_description": "Adgang til dette delte link med en selvdefineret URL", "shared_link_edit_description_hint": "Indtast beskrivelse", "shared_link_edit_expire_after_option_day": "1 dag", "shared_link_edit_expire_after_option_days": "{count} dage", @@ -1962,7 +2097,7 @@ "shared_link_edit_expire_after_option_year": "{count} ÃĨr", "shared_link_edit_password_hint": "Indtast kodeordet", "shared_link_edit_submit_button": "Opdater link", - "shared_link_error_server_url_fetch": "Kan ikke hente server url", + "shared_link_error_server_url_fetch": "Kan ikke hente server URL", "shared_link_expires_day": "Udløber om {count} dag", "shared_link_expires_days": "Udløber om {count} dage", "shared_link_expires_hour": "Udløber om {count} time", @@ -1984,6 +2119,12 @@ "shared_with_partner": "Delt med {partner}", "sharing": "Delte", "sharing_enter_password": "Indtast venligst adgangskoden for at se denne side.", + "sharing_page_album": "Delt albums", + "sharing_page_description": "Opret delte albummer for at dele billeder og video med personer pÃĨ dit netvÃĻrk.", + "sharing_page_empty_list": "TOM LISTE", + "sharing_sidebar_description": "Vis et link til deling i sidemenuen", + "sharing_silver_appbar_create_shared_album": "Opret delt album", + "sharing_silver_appbar_share_partner": "Del med partner", "shift_to_permanent_delete": "tryk pÃĨ ⇧ for at slette aktiv permanent", "show_album_options": "Vis albumindstillinger", "show_albums": "Vis albummer", @@ -1995,9 +2136,7 @@ "show_in_timeline": "Vis pÃĨ tidslinje", "show_in_timeline_setting_description": "Vis billeder og videoer fra denne bruger pÃĨ din tidslinje", "show_keyboard_shortcuts": "Vis tastaturgenveje", - "show_less": "Vis mindre", "show_metadata": "Vis metadata", - "show_more_fields": "{count, plural, one {Vis # mere felt} other {Vis # flere felter}}", "show_or_hide_info": "Vis eller skjul info", "show_password": "Vis adgangskode", "show_person_options": "Vis personindstillinger", @@ -2005,7 +2144,6 @@ "show_schema": "Vis skema", "show_search_options": "Vis søgeindstillinger", "show_shared_links": "Vis delte links", - "show_slideshow_metadata_overlay": "Vis billedinfo-overlay", "show_slideshow_transition": "Vis overgang til diasshow", "show_supporter_badge": "Supporter skilt", "show_supporter_badge_description": "Vis et supporter ikon", @@ -2017,25 +2155,18 @@ "sign_out": "Log af", "sign_up": "Tilmeld", "size": "Størrelse", - "skip": "Spring over", "skip_to_content": "Spring frem til indhold", "skip_to_folders": "Spring til mapper", "skip_to_tags": "Spring til tags", "slideshow": "Diasshow", - "slideshow_body": "LÃĻn dig tilbage, og se dine fotos afspilles i et slideshow i fuld skÃĻrm.", - "slideshow_metadata_overlay_mode": "Overlay indhold", - "slideshow_metadata_overlay_mode_description_only": "Kun beskrivelse", - "slideshow_metadata_overlay_mode_full": "Fuld", "slideshow_repeat": "Gentag diasshow", "slideshow_repeat_description": "Hop tilbage til begyndelsen nÃĨr diasshow stopper", "slideshow_settings": "Diasshowindstillinger", - "slideshow_title": "Slideshow", - "smart_album": "Smart album", - "some_assets_already_have_a_location_warning": "Nogle af de valgte aktiver har allerede en placering", "sort_albums_by": "SortÊr albummer efter...", "sort_created": "Dato oprettet", "sort_items": "Antal genstande", "sort_modified": "Ændret dato", + "sort_newest": "Nyeste foto", "sort_oldest": "Ældste foto", "sort_people_by_similarity": "Sorter efter personer der ligner hinanden", "sort_recent": "Seneste foto", @@ -2044,6 +2175,7 @@ "stack": "Stak", "stack_action_prompt": "{count} stakket", "stack_duplicates": "Stak dubletter", + "stack_select_one_photo": "VÃĻlg Êt hovedbillede til stakken", "stack_selected_photos": "Stak valgte billeder", "stacked_assets_count": "Stablet {count, plural, one {# aktiv} other {# aktiver}}", "stacktrace": "Stacktrace", @@ -2052,10 +2184,6 @@ "start_date_before_end_date": "Startdato skal ligge før slutdato", "state": "Stat", "status": "Status", - "step_delete": "Slet trin", - "step_delete_confirm": "Er du sikker pÃĨ, at du vil slette dette trin?", - "step_details": "Trin detaljer", - "steps": "Trin", "stop_casting": "Stop med at caste", "stop_motion_photo": "Stopmotionbillede", "stop_photo_sharing": "Stop med at dele dine billeder?", @@ -2076,18 +2204,17 @@ "swap_merge_direction": "Byt retning for sammenfletning", "sync": "SynkronisÊr", "sync_albums": "Synkroniser albummer", + "sync_albums_manual_subtitle": "Synkroniser alle uploadet billeder og videoer til de valgte backupalbummer", "sync_local": "Synkroniser lokalt", "sync_remote": "Synkroniser eksternt", "sync_status": "Synkroniserings Status", "sync_status_subtitle": "Se og administrÊr synkroniseringssystemet", "sync_upload_album_setting_subtitle": "Opret og upload dine billeder og videoer til de valgte albummer i Immich", - "system_theme": "Systemtema", - "system_theme_command_description": "Brug systemtemaet ({value})", "tag": "Tag", "tag_assets": "Tag mediefiler", "tag_created": "Oprettet tag: {tag}", - "tag_face": "Tag ansigt", "tag_feature_description": "Gennemse billeder og videoer grupperet efter logiske tag-emner", + "tag_not_found_question": "Kan du ikke finde et tag? Opret et nyt tag.", "tag_people": "Tag personer", "tag_updated": "Opdateret tag: {tag}", "tagged_assets": "Tagget {count, plural, one {# aktiv} other {# aktiver}}", @@ -2102,10 +2229,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Antal elementer per rÃĻkke ({count})", "theme_setting_colorful_interface_subtitle": "Tilføj primÃĻr farve til baggrundsoverflader.", "theme_setting_colorful_interface_title": "Farverig grÃĻnseflade", + "theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren", + "theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet", "theme_setting_primary_color_subtitle": "VÃĻlg en farve til primÃĻre handlinger og accenter.", "theme_setting_primary_color_title": "PrimÃĻr farve", "theme_setting_system_primary_color_title": "Brug systemfarver", "theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)", + "theme_setting_theme_subtitle": "VÃĻlg appens temaindstilling", + "theme_setting_three_stage_loading_subtitle": "Tre-trins indlÃĻsning kan øge ydeevnen, men kan ligeledes føre til højere netvÃĻrksbelastning", + "theme_setting_three_stage_loading_title": "SlÃĨ tre-trins indlÃĻsning til", "then": "Siden", "they_will_be_merged_together": "De vil blive slÃĨet sammen", "third_party_resources": "Tredjepartsressourcer", @@ -2131,17 +2263,23 @@ "trash_all": "Smid alle ud", "trash_count": "Slet {count, number}", "trash_delete_asset": "Flyt mediefil til Papirkurv", + "trash_emptied": "Tømte papirkurven", "trash_no_results_message": "Billeder og videoer markeret til sletning vil blive vist her.", "trash_page_delete_all": "Slet alt", + "trash_page_empty_trash_dialog_content": "Vil du tømme papirkurven? Disse elementer vil blive permanent fjernet fra Immich", "trash_page_info": "Slettede elementer vil blive slettet permanent efter {days} dage", + "trash_page_no_assets": "Ingen slettede elementer", + "trash_page_restore_all": "Gendan alt", + "trash_page_select_assets_btn": "VÃĻlg elementer", + "trash_page_title": "Papirkurv ({count})", "trashed_items_will_be_permanently_deleted_after": "Mediefiler i papirkurven vil blive slettet permanent efter {days, plural, one {# dag} other {# dage}}.", "trigger": "Udløser", - "trigger_asset_metadata_extraction": "UdtrÃĻkning af metadata for mediefil", - "trigger_asset_metadata_extraction_description": "Udløses, nÃĨr EXIF-metadataene for en mediefil udtrÃĻkkes", - "trigger_asset_uploaded": "Mediefil upload", + "trigger_asset_uploaded": "Mediefil uploaded", "trigger_asset_uploaded_description": "Udløses, nÃĨr et nyt asset bliver uploaded", + "trigger_description": "En begivenhed, der starter en arbejdsgang", "trigger_person_recognized": "Peron genkendt", - "trigger_person_recognized_description": "Udløses, nÃĨr en person er genkendt", + "trigger_person_recognized_description": "Udløses, nÃĨr en person er detekteret", + "trigger_type": "Udløsertype", "troubleshoot": "Fejlfinding", "type": "Type", "unable_to_change_pin_code": "Kunne ikke ÃĻndre PIN kode", @@ -2157,7 +2295,6 @@ "unknown": "Ukendt", "unknown_country": "Ukendt land", "unknown_date": "Ukendt dato", - "unknown_schema": "Ukendt skema", "unknown_year": "Ukendt ÃĨr", "unlimited": "UbegrÃĻnset", "unlink_motion_video": "Fjern link til bevÃĻgelsesvideo", @@ -2170,20 +2307,23 @@ "unsaved_change": "Ændring, der ikke er gemt", "unselect_all": "FravÃĻlg alle", "unselect_all_duplicates": "Fjern markeringen af alle dubletter", + "unselect_all_in": "AfmarkÊr alle i {group}", "unstack": "Fjern fra stak", "unstack_action_prompt": "{count} ustakket", "unstacked_assets_count": "Ikke-stablet {count, plural, one {# aktiv} other {# aktiver}}", "unsupported_field_type": "Ikke-understøttet felttype", "unsupported_file_type": "Filen {file} kan ikke uploades, fordi filtypen {type} ikke understøttes.", "untagged": "UmÃĻrket", + "untitled_workflow": "Unavngivet arbejdsgang", "up_next": "NÃĻste", "update_location_action_prompt": "Opdater lokationen for {count} valgte objekter med:", "updated_at": "Opdateret", "updated_password": "Opdaterede adgangskode", "upload": "Upload", "upload_concurrency": "Upload samtidighed", - "upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}", "upload_details": "Upload detaljer", + "upload_dialog_info": "Vil du sikkerhedskopiere de(t) valgte element(er) til serveren?", + "upload_dialog_title": "Upload element", "upload_error_with_count": "Upload-fejl for {count, plural, one {# objekt} other {# objekter}}", "upload_errors": "Upload afsluttet med {count, plural, one {# fejl} other {# fejl}}. Opdater siden for at se nye uploadaktiver.", "upload_finished": "Upload fuldført", @@ -2193,13 +2333,9 @@ "upload_status_errors": "Fejl", "upload_status_uploaded": "Uploadet", "upload_success": "Upload gennemført. Opdater siden for at se nye uploadaktiver.", - "upload_to_album_body": "For brugere, der ikke bruger den manuelle uploadfunktion, kan du nu vÃĻlge at tilføje lokale fotos direkte til et album, nÃĨr du uploader dem. Du behøver ikke lÃĻngere at uploade og derefter føje dem til et album senere.", - "upload_to_album_title": "Upload direkte til et album", "upload_to_immich": "Upload til Immich ({count})", "uploading": "Uploader", "uploading_media": "Uploader media", - "uploads": "Uploads", - "uploads_count": "{count, plural, one {# upload} other {# uploads}}", "url": "URL", "usage": "Forbrug", "use_biometric": "Brug biometrisk", @@ -2207,7 +2343,6 @@ "use_browser_locale_description": "FormatÊr datoer, klokkeslÃĻt og tal baseret pÃĨ din browsers lokalitet", "use_current_connection": "Brug nuvÃĻrende forbindelse", "use_custom_date_range": "Brug tilpasset datointerval i stedet", - "use_template": "Brug skabelon", "user": "Bruger", "user_has_been_deleted": "Denne bruger er slettet.", "user_id": "Bruger-ID", @@ -2217,6 +2352,7 @@ "user_privacy": "Brugerprivatliv", "user_purchase_settings": "Køb", "user_purchase_settings_description": "Administrer dit køb", + "user_role_set": "Indstil {user} som {role}", "user_usage_detail": "Detaljer om brugers forbrug", "user_usage_stats": "Kontoens anvendelsesstatistik", "user_usage_stats_description": "Vis konto anvendelsesstatistik", @@ -2226,6 +2362,7 @@ "utilities": "VÃĻrktøjer", "validate": "ValidÊr", "validate_endpoint_error": "Indtast en gyldig URL", + "validation_error": "Validerings fejl", "variables": "Variabler", "version": "Version", "version_announcement_closing": "Din ven, Alex", @@ -2235,9 +2372,9 @@ "video": "Video", "video_hover_setting": "Afspil miniaturevisning af video nÃĨr musemarkøren er over den", "video_hover_setting_description": "Afspil miniaturevisning for videoer nÃĨr musemarkøren holdes over elementet. Selv nÃĨr det er deaktiveret, kan afspilning startes ved at holde musen over afspilningsikonet.", - "video_quality": "Videokvalitet", "videos": "Videoer", "videos_count": "{count, plural, one {# Video} other {# Videoer}}", + "videos_only": "Kun videoer", "view": "Se", "view_album": "Se album", "view_all": "Se alle", @@ -2246,29 +2383,29 @@ "view_details": "Vis detaljer", "view_in_timeline": "Se pÃĨ tidslinjen", "view_link": "Vis Link", + "view_links": "Vis links", "view_name": "Se", "view_next_asset": "Se nÃĻste medie", "view_previous_asset": "Se forrige medie", "view_qr_code": "Vis QR kode", "view_similar_photos": "Se lignende billeder", "view_stack": "Vis stak", + "view_user": "Vis bruger", "viewer_remove_from_stack": "Fjern fra stak", + "viewer_stack_use_as_main_asset": "Brug som hovedelement", + "viewer_unstack": "Fjern fra stak", "visibility": "Synlighed", "visibility_changed": "Synlighed ÃĻndret for {count, plural, one {# person} other {# personer}}", "visual": "Visuel", + "visual_builder": "Visuel builder", "waiting": "Venter", "waiting_count": "Venter: {count}", "warning": "Advarsel", "week": "Uge", "welcome": "Velkommen", "welcome_to_immich": "Velkommen til Immich", - "whats_new": "Hvad er nyt", - "whats_new_settings_subtitle": "Se, hvad der er nyt i Immich", - "whats_new_version": "Version {version}", - "when": "HvornÃĨr", "width": "Bredde", "wifi_name": "Wi-Fi navn", - "workflow": "Arbejdsgang", "workflow_delete_prompt": "Er du sikker pÃĨ, at du vil slette denne arbejdsgang?", "workflow_deleted": "Arbejdsgang slettet", "workflow_description": "Arbejdsgangsbeskrivelse", @@ -2278,17 +2415,17 @@ "workflow_name": "Navn pÃĨ arbejdsgang", "workflow_navigation_prompt": "Er du sikker pÃĨ, at du vil forlade uden at gemme dine ÃĻndringer?", "workflow_summary": "Arbejdsgangsoversigt", - "workflow_templates": "Arbejdsgangsskabeloner", "workflow_update_success": "Arbejdsgang opdateret korrekt", + "workflow_updated": "Arbejdsgang opdateret", "workflows": "Arbejdsgange", "workflows_help_text": "Arbejdsgange automatiserer handlinger pÃĨ dine filer baseret pÃĨ udløsere og filtre", "wrong_pin_code": "Forkert PIN kode", - "x_of_total": "{x}/{total}", "year": "År", "years_ago": "{years, plural, one {# ÃĨr} other {# ÃĨr}} siden", "yes": "Ja", "you_dont_have_any_shared_links": "Du har ikke nogen delte links", "your_wifi_name": "Dit Wi-Fi navn", "zero_to_clear_rating": "Tryk pÃĨ 0 for at fjerne fil vurderingen", - "zoom_image": "Zoom billede" + "zoom_image": "Zoom billede", + "zoom_to_bounds": "Zoom til grÃĻnserne" } diff --git a/i18n/de.json b/i18n/de.json index 910166a54c..9816055023 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -1,8 +1,8 @@ { - "about": "Über Immich", - "account": "Mein Konto", + "about": "Über", + "account": "Konto", "account_settings": "Kontoeinstellungen", - "acknowledge": "Schließen", + "acknowledge": "Verstanden", "action": "Aktion", "action_common_update": "Aktualisieren", "action_description": "Eine Reihe von Aktionen, die an den gefilterten Assets ausgefÃŧhrt werden sollen", @@ -10,31 +10,39 @@ "active": "Aktiv", "active_count": "Aktive: {count}", "activity": "Aktivität", + "activity_changed": "Aktivität ist {enabled, select, true {aktiviert} other {deaktiviert}}", "add": "HinzufÃŧgen", "add_a_description": "Beschreibung hinzufÃŧgen", "add_a_location": "Standort hinzufÃŧgen", "add_a_name": "Name hinzufÃŧgen", "add_a_title": "Titel hinzufÃŧgen", "add_action": "Aktion hinzufÃŧgen", - "add_assets": "Elemente hinzufÃŧgen", + "add_action_description": "Klicken um eine Aktion hinzuzufÃŧgen", + "add_assets": "Assets hinzufÃŧgen", "add_birthday": "Geburtsdatum hinzufÃŧgen", - "add_endpoint": "Ziel hinzufÃŧgen", + "add_endpoint": "Endpunkt hinzufÃŧgen", "add_exclusion_pattern": "Ausschlussmuster hinzufÃŧgen", + "add_filter": "Filter hinzufÃŧgen", + "add_filter_description": "Klicken um eine Filterbedingung hinzuzufÃŧgen", "add_location": "Standort hinzufÃŧgen", + "add_more_users": "Weitere Nutzer hinzufÃŧgen", "add_partner": "Partner hinzufÃŧgen", + "add_path": "Pfad hinzufÃŧgen", "add_photos": "Fotos hinzufÃŧgen", - "add_step": "Schritt hinzufÃŧgen", "add_tag": "Tag hinzufÃŧgen", "add_to": "HinzufÃŧgen zu â€Ļ", "add_to_album": "Zu Album hinzufÃŧgen", "add_to_album_bottom_sheet_added": "Zu {album} hinzugefÃŧgt", "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", "add_to_album_bottom_sheet_some_local_assets": "Einige lokale Dateien konnten nicht zum Album hinzugefÃŧgt werden", + "add_to_album_toggle": "Auswahl umschalten fÃŧr {album}", "add_to_albums": "Zu Alben hinzufÃŧgen", "add_to_albums_count": "Zu Alben hinzufÃŧgen ({count})", "add_to_bottom_bar": "HinzufÃŧgen zu", + "add_to_shared_album": "Zu geteiltem Album hinzufÃŧgen", "add_upload_to_stack": "Upload zum Stapel hinzufÃŧgen", "add_url": "URL hinzufÃŧgen", + "add_workflow_step": "Workflow-Schritt hinzufÃŧgen", "added_to_archive": "Zum Archiv hinzugefÃŧgt", "added_to_favorites": "Zu Favoriten hinzugefÃŧgt", "added_to_favorites_count": "{count, number} zu Favoriten hinzugefÃŧgt", @@ -47,9 +55,9 @@ "authentication_settings_disable_all": "Bist du sicher, dass du alle Anmeldemethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.", "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", "background_task_job": "Hintergrundaufgaben", - "backup_database": "Datenbankexport erstellen", + "backup_database": "Datenbanksicherung erstellen", "backup_database_enable_description": "Datenbank regelmäßig sichern", - "backup_keep_last_amount": "Anzahl der zu speichernden vorherigen Sicherungen", + "backup_keep_last_amount": "Anzahl der aufzubewahrenden frÃŧheren Sicherungen", "backup_onboarding_1_description": "Offsite-Kopie in der Cloud oder an einem anderen physischen Ort.", "backup_onboarding_2_description": "Lokale Kopien auf verschiedenen Geräten. Dazu gehÃļren die Hauptdateien und eine lokale Sicherung dieser Dateien.", "backup_onboarding_3_description": "Kopien deiner Daten inklusive Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopien.", @@ -57,7 +65,7 @@ "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der Dokumentation.", "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:", "backup_onboarding_title": "Sicherungen", - "backup_settings": "Einstellungen zum Datenbankexport", + "backup_settings": "Einstellungen fÃŧr Datenbanksicherung", "backup_settings_description": "Einstellungen zur regelmäßigen Sicherung der Datenbank. Hinweis: Diese Jobs werden nicht Ãŧberwacht und du wirst nicht Ãŧber Fehler informiert.", "cleared_jobs": "Folgende Aufgaben zurÃŧckgesetzt: {job}", "config_set_by_file": "Ist derzeit in einer Konfigurationsdatei festgelegt", @@ -70,10 +78,9 @@ "copy_config_to_clipboard_description": "Kopieren Sie die aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage", "create_job": "Aufgabe erstellen", "cron_expression": "Cron-Ausdruck", - "cron_expression_description": "Legen Sie das Scanintervall im Cron-Format fest. Weitere Informationen finden Sie z. B. bei Crontab Guru", + "cron_expression_description": "Setze das Scanintervall im Cron-Format. Hilfe mit dem Format bietet dir dabei z. B. der Crontab Guru", "cron_expression_presets": "Vorlagen fÃŧr Cron-Zeitangabe", "disable_login": "Login deaktivieren", - "download_csv": "CSV herunterladen", "duplicate_detection_job_description": "Diese Aufgabe fÃŧhrt das maschinelle Lernen fÃŧr jede Datei aus, um Duplikate zu finden. Diese Aufgabe beruht auf der intelligenten Suche", "exclusion_pattern_description": "Mit Ausschlussmustern kÃļnnen Dateien und Ordner beim Scannen Ihrer Bibliothek ignoriert werden. Dies ist nÃŧtzlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren mÃļchtest, wie z. B. RAW-Dateien.", "export_config_as_json_description": "Laden Sie die aktuelle Systemkonfiguration als JSON-Datei herunter", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Qualität der Miniaturansicht von 1-100. HÃļher ist besser, erzeugt aber grÃļßere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen.", "image_thumbnail_title": "Miniaturansicht-Einstellungen", "import_config_from_json_description": "Importieren Sie die Systemkonfiguration, indem Sie eine JSON-Konfigurationsdatei hochladen", - "integrity_checks_checksum_files": "PrÃŧfsummendateien", - "integrity_checks_checksum_files_description": "Anpassung der PrÃŧfsummen Tests", - "integrity_checks_checksum_files_enable_description": "Aktiviere den PrÃŧfsummen Test", - "integrity_checks_checksum_files_percentage_limit": "Prozentuales Limit", - "integrity_checks_checksum_files_percentage_limit_description": "Setze die maximale Prozentzahl zwischen 0.01 und 1, um zu bestimmen wie viel der PrÃŧfsummen Test jedes Intervall prÃŧfen soll.", - "integrity_checks_checksum_files_time_limit": "Zeitlimit", - "integrity_checks_checksum_files_time_limit_description": "Setze die maximale Dauer, fÃŧr die der PrÃŧfsummen Test jedes Intervall ausgefÃŧhrt werden soll. (ms)", - "integrity_checks_missing_files": "Fehlende Dateien", - "integrity_checks_missing_files_description": "Setze die Frequenz und (de)aktiviere den Test fÃŧr fehlende Dateien", - "integrity_checks_missing_files_enable_description": "Aktiviere den Test fÃŧr fehlende Dateien", - "integrity_checks_settings": "Integritätstests", - "integrity_checks_settings_description": "Verwalten der Intervalle fÃŧr die Integritätstests", - "integrity_checks_untracked_files": "Nicht getrackte Dateien", - "integrity_checks_untracked_files_description": "Setze die Frequenz und (de)aktiviere den Test fÃŧr ungetrackte Dateien", - "integrity_checks_untracked_files_enable_description": "Aktiviere den Test fÃŧr ungetrackte Dateien", "job_concurrency": "{job} (Anzahl gleichzeitiger Prozesse)", "job_created": "Aufgabe erstellt", "job_not_concurrency_safe": "Diese Aufgabe ist nicht parallelisierungssicher.", @@ -162,7 +154,7 @@ "machine_learning_duplicate_detection": "Duplikaterkennung", "machine_learning_duplicate_detection_enabled": "Duplikaterkennung aktivieren", "machine_learning_duplicate_detection_enabled_description": "Falls diese Option deaktiviert ist, werden exakt identische Dateien dennoch de-dupliziert.", - "machine_learning_duplicate_detection_setting_description": "Verwendung von CLIP-Einbettungen zum Erkennen mÃļglicher Duplikate", + "machine_learning_duplicate_detection_setting_description": "Verwendung von CLIP-Embeddings zum Erkennen mÃļglicher Duplikate", "machine_learning_enabled": "Maschinelles Lernen aktivieren", "machine_learning_enabled_description": "Wenn diese Option deaktiviert ist, werden alle ML-Funktionen unabhängig von den unten aufgefÃŧhrten Einstellungen deaktiviert.", "machine_learning_facial_recognition": "Gesichtserkennung", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Intelligente Suche aktivieren", "machine_learning_smart_search_enabled_description": "Ist diese Option deaktiviert, werden die Bilder nicht fÃŧr die intelligente Suche verwendet.", "machine_learning_url_description": "Die URL des Servers fÃŧr maschinelles Lernen. Wenn mehr als eine URL angegeben wird, wird jeder Server einzeln ausprobiert, bis einer erfolgreich antwortet, und zwar in der Reihenfolge vom ersten bis zum letzten. Server die nicht antworten werden temporär ignoriert, bis sie wieder verfÃŧgbar sind.", - "maintenance_backup_management": "Backup Management", "maintenance_delete_backup": "Backup lÃļschen", "maintenance_delete_backup_description": "Diese Datei wird irreversibel gelÃļscht.", "maintenance_delete_error": "Die LÃļschung der Sicherungskopie ist fehlgeschlagen.", - "maintenance_integrity_check": "ÜberprÃŧfe", - "maintenance_integrity_check_all": "ÜberprÃŧfe alle", - "maintenance_integrity_checksum_mismatch": "PrÃŧfsummenfehler", - "maintenance_integrity_checksum_mismatch_description": "Dateien, deren PrÃŧfsumme auf dem Datenträger nicht mit der Ãŧbereinstimmt, die Immich in seiner Datenbank gespeichert hat.", - "maintenance_integrity_checksum_mismatch_job": "Auf PrÃŧfsummenfehler ÃŧberprÃŧfen", - "maintenance_integrity_checksum_mismatch_refresh_job": "Aktualisieren Sie Berichte Ãŧber PrÃŧfsummenkonflikte", - "maintenance_integrity_missing_file": "Fehlende Dateien", - "maintenance_integrity_missing_file_description": "Dateien, die Immich in seiner Datenbank erfasst hat, die aber im Dateisystem nicht vorhanden sind.", - "maintenance_integrity_missing_file_job": "Auf fehlende Dateien prÃŧfen", - "maintenance_integrity_missing_file_refresh_job": "Berichte Ãŧber fehlende Dateien aktualisieren", - "maintenance_integrity_report": "Integritätsbericht", - "maintenance_integrity_untracked_file": "Nicht getrackte Dateien", - "maintenance_integrity_untracked_file_description": "Dateien in Immichs Verzeichnissen, Ãŧber die Immich keine Aufzeichnungen hat.", - "maintenance_integrity_untracked_file_job": "Auf nicht verfolgte Dateien prÃŧfen", - "maintenance_integrity_untracked_file_refresh_job": "Berichte Ãŧber nicht getrackte Dateien aktualisieren", "maintenance_restore_backup": "Sicherungskopie wiederherstellen", "maintenance_restore_backup_description": "Immich wird zurÃŧckgesetzt und von der ausgewählten Sicherungskopie wiederhergestellt. Ein Backup wird erstellt, bevor es weitergeht.", "maintenance_restore_backup_different_version": "Diese Sicherungskopie wurde mit einer anderen Version von Immich erstellt!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "E-Mail-Benachrichtigungen aktivieren", "notification_settings": "Benachrichtigungseinstellungen", "notification_settings_description": "Benachrichtigungseinstellungen (inkl. E-Mail) verwalten", - "oauth_allow_insecure_requests": "Unsichere Anfragen erlauben", - "oauth_allow_insecure_requests_description": "WARNUNG: Dies deaktiviert die TLS-Zertifikatsvalidierung fÃŧr OAuth-Anfragen und kann Sie MITM-Angriffen aussetzen.", "oauth_auto_launch": "Auto-Start", "oauth_auto_launch_description": "Automatischer Start des OAuth-Anmeldevorgangs beim Aufrufen der Anmeldeseite", "oauth_auto_register": "Automatische Registrierung", @@ -300,11 +274,9 @@ "oauth_button_text": "Button-Text", "oauth_client_secret_description": "Erforderlich fÃŧr Confidential Clients oder wenn PKCE (Proof Key for Code Exchange) nicht fÃŧr Public Clients unterstÃŧtzt wird.", "oauth_enable_description": "Anmeldung mit OAuth", - "oauth_end_session_url_description": "Leite den Benutzer nach dem Abmelden zu dieser URI weiter.", "oauth_mobile_redirect_uri": "Mobile Umleitungs-URI", "oauth_mobile_redirect_uri_override": "Mobile Umleitungs-URI Ãŧberschreiben", "oauth_mobile_redirect_uri_override_description": "Einschalten, wenn der OAuth-Anbieter keine mobile URI wie ''{callback}'' erlaubt", - "oauth_prompt_description": "Eingabe Parameter (z. B. Konto auswählen, Login, Einwilligung)", "oauth_role_claim": "Rollen-Claim", "oauth_role_claim_description": "Gewähre automatisch Admin-Zugriff basierend auf dem Vorhandensein dieses Claims. Der Claim kann entweder 'user' oder 'admin' sein.", "oauth_settings": "OAuth", @@ -325,14 +297,12 @@ "paths_validated_successfully": "Alle Pfade erfolgreich ÃŧberprÃŧft", "person_cleanup_job": "Personen aufräumen", "queue_details": "Warteschlangendetails", - "queues": "Auftrags-Schlangen", - "queues_page_description": "Auftragsschlangen-Adminseite", + "queues": "Auftrags-Warteschlangen", + "queues_page_description": "Auftragswarteschlangen-Adminseite", "quota_size_gib": "Kontingent (GiB)", "refreshing_all_libraries": "Alle Bibliotheken aktualisieren", "registration": "Admin-Registrierung", "registration_description": "Da du der erste Benutzer im System bist, wird dir die Rolle des Administrators zugewiesen, womit du fÃŧr die Verwaltungsaufgaben verantwortlich bist. Weitere Benutzer werden von dir erstellt.", - "release_channel_release_candidate": "Release Candidate", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Entferne fehlgeschlagene Aufgaben", "require_password_change_on_login": "Benutzer muss das Passwort beim ersten Login ändern", "reset_settings_to_default": "Einstellungen auf Standard zurÃŧcksetzen", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Gilt nur fÃŧr VAAPI und QSV. Legt den fÃŧr die Hardware-Transkodierung verwendeten dri-Node fest.", "transcoding_preset_preset": "Voreinstellung (-preset)", "transcoding_preset_preset_description": "Komprimierungsgeschwindigkeit. Eine langsamere Voreinstellungen erzeugt kleinere Dateien und erhÃļht die Qualität, wenn man eine gewisse Bitrate anstrebt. VP9 ignoriert Geschwindigkeiten Ãŧber „Schneller“.", - "transcoding_realtime": "Echtzeit-Transkodierung [EXPERIMENTELL]", - "transcoding_realtime_description": "ErmÃļglicht die Transkodierung in Echtzeit während des Videostreams. ErmÃļglicht einen Qualitätswechsel, kann jedoch je nach Serverkapazität zu einer hÃļheren Wiedergabelatenz und Rucklern fÃŧhren.", - "transcoding_realtime_enabled": "Echtzeit-Transkodierung aktivieren", - "transcoding_realtime_enabled_description": "Wenn diese Option deaktiviert ist, lehnt der Server den Start neuer Echtzeit-Transkodierungssitzungen ab.", - "transcoding_realtime_resolutions": "AuflÃļsungen", - "transcoding_realtime_resolutions_description": "Die fÃŧr das Echtzeit-Transcoding angebotenen AuflÃļsungen. HÃļhere AuflÃļsungen kÃļnnen zu Wiedergabeproblemen fÃŧhren, wenn der Server sie nicht schnell genug transcodieren kann.", - "transcoding_realtime_video_codecs": "Video-Codecs", - "transcoding_realtime_video_codecs_description": "Die fÃŧr die Echtzeit-Transkodierung angebotenen Videocodecs. Clients wählen bei der Wiedergabe die beste von ihnen unterstÃŧtzte Option aus. AV1 ist effizienter als HEVC und HEVC ist effizienter als H.264. Wählen Sie bei Verwendung von Hardwarebeschleunigung nur die Codecs aus, die der Beschleuniger kodieren kann. Beachten Sie bei der Software-Transkodierung, dass H.264 schneller ist als AV1 und AV1 schneller als HEVC.", "transcoding_reference_frames": "Referenz-Frames", "transcoding_reference_frames_description": "Die Anzahl der Bilder, auf die bei der Komprimierung eines bestimmten Bildes Bezug genommen wird. HÃļhere Werte verbessern die Komprimierungseffizienz, verlangsamen aber die Kodierung. 0 setzt diesen Wert automatisch.", "transcoding_required_description": "Nur Videos in einem nicht akzeptierten Format", @@ -478,8 +440,6 @@ "user_settings_description": "Benutzereinstellungen verwalten", "user_successfully_removed": "Der Benutzer {email} wurde erfolgreich entfernt.", "users_page_description": "Administrator-Benutzerseite", - "version_check_channel": "Release Kanal", - "version_check_channel_description": "Wählen Sie den Release-Kanal aus, fÃŧr den Sie VersionsankÃŧndigungen erhalten mÃļchten", "version_check_enabled_description": "VersionsprÃŧfung aktivieren", "version_check_implications": "Die Funktion zur VersionsprÃŧfung basiert auf regelmäßiger Kommunikation mit {server}", "version_check_settings": "VersionsprÃŧfung", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "LÃļsche Bildercache", "advanced_settings_clear_image_cache_error": "LÃļschung des Bildercaches misslungen", "advanced_settings_clear_image_cache_success": "Erfolgreich gelÃļscht {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Verwende diese Option, um Medien während der Synchronisierung nach anderen Kriterien zu filtern. Versuchen dies nur, wenn Probleme mit der Erkennung aller Alben durch die App auftreten.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELL] Benutze alternativen Filter fÃŧr Synchronisierung der Gerätealben", "advanced_settings_log_level_title": "Log-Level: {level}", "advanced_settings_prefer_remote_subtitle": "Einige Geräte sind sehr langsam beim Laden von lokalen Vorschaubildern. Aktivieren Sie diese Einstellung, um stattdessen die Server-Bilder zu laden.", "advanced_settings_prefer_remote_title": "Server-Bilder bevorzugen", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Benutzerdefinierte Proxy-Header [Experimentell]", "advanced_settings_readonly_mode_subtitle": "Aktiviert den schreibgeschÃŧtzten Modus, in dem die Fotos nur angezeigt werden kÃļnnen. Funktionen wie das Auswählen mehrerer Bilder, das Teilen, das Übertragen und das LÃļschen sind deaktiviert. Aktivieren/Deaktiviere den schreibgeschÃŧtzten Modus Ãŧber den Benutzer-Avatar auf dem Hauptbildschirm", "advanced_settings_readonly_mode_title": "SchreibgeschÃŧtzter Modus", + "advanced_settings_self_signed_ssl_subtitle": "Verifizierung von SSL-Zertifikaten vom Server Ãŧberspringen. Notwendig bei selbstsignierten Zertifikaten.", + "advanced_settings_self_signed_ssl_title": "Selbstsignierte SSL-Zertifikate erlauben [Experimentell]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisches LÃļschen oder Wiederherstellen einer Datei auf diesem Gerät, wenn diese Aktion im Web durchgefÃŧhrt wird", "advanced_settings_sync_remote_deletions_title": "Mit Server-LÃļschungen synchronisieren [Experimentell]", "advanced_settings_tile_subtitle": "Erweiterte Benutzereinstellungen", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Bist du sicher, dass du das Album {album} lÃļschen willst?", "album_delete_confirmation_description": "Falls dieses Album geteilt wurde, kÃļnnen andere Benutzer nicht mehr darauf zugreifen.", "album_deleted": "Album gelÃļscht", + "album_info_card_backup_album_excluded": "AUSGESCHLOSSEN", + "album_info_card_backup_album_included": "EINGESCHLOSSEN", "album_info_updated": "Album-Infos aktualisiert", + "album_leave": "Album verlassen?", + "album_leave_confirmation": "Bist du sicher, dass du das Album {album} verlassen willst?", "album_name": "Albumname", "album_options": "Albumoptionen", "album_remove_user": "Nutzer entfernen?", "album_remove_user_confirmation": "Bist du sicher, dass du {user} entfernen willst?", "album_search_not_found": "Keine Alben gefunden, die zur Suche passen", + "album_selected": "Album ausgewählt", "album_share_no_users": "Es sieht so aus, als hättest du dieses Album mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", "album_summary": "Album Zusammenfassung", "album_updated": "Album aktualisiert", "album_updated_setting_description": "Erhalte eine E-Mail-Benachrichtigung, wenn ein freigegebenes Album neue Dateien enthält", "album_upload_assets": "Assets vom Computer hochladen und zu Album hinzufÃŧgen", + "album_user_left": "{album} verlassen", + "album_user_removed": "{user} entfernt", + "album_viewer_appbar_delete_confirm": "Bist du sicher, dass du dieses Album aus deinem Konto lÃļschen mÃļchtest?", "album_viewer_appbar_share_err_delete": "Album konnte nicht gelÃļscht werden", + "album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden", + "album_viewer_appbar_share_err_remove": "Beim LÃļschen von Elementen aus dem Album ist ein Problem aufgetreten", + "album_viewer_appbar_share_err_title": "Der Titel konnte nicht geändert werden", + "album_viewer_appbar_share_leave": "Album verlassen", + "album_viewer_appbar_share_to": "Teile Ãŧber", "album_viewer_page_share_add_users": "Nutzer hinzufÃŧgen", "album_with_link_access": "Lass jeden mit dem Link die Fotos und Personen in diesem Album sehen.", "albums": "Alben", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Sortierreihenfolge der Dateien bei der Erstellung neuer Alben.", "albums_feature_description": "Sammlung an Alben die mit anderen Benutzern geteilt werden kÃļnnen.", "albums_on_device_count": "Alben auf dem Gerät ({count})", + "albums_selected": "{count, plural, one {# Album ausgewählt} other {# Alben ausgewählt}}", "all": "Alle", "all_albums": "Alle Alben", "all_people": "Alle Personen", "all_photos": "Alle Fotos", "all_videos": "Alle Videos", "allow_dark_mode": "Dunkel-Modus erlauben", + "allow_edits": "Bearbeiten erlauben", "allow_public_user_to_download": "Erlaube Ãļffentlichen Benutzern, herunterzuladen", "allow_public_user_to_upload": "Erlaube Ãļffentlichen Benutzern, hochzuladen", "allowed": "Erlaubt", @@ -549,35 +528,45 @@ "always_keep": "Immer behalten", "always_keep_photos_hint": "Speicherfreigabe wird alle Fotos auf dem Gerät behalten.", "always_keep_videos_hint": "Speicherfreigabe wird alle Videos auf dem Gerät behalten.", + "anti_clockwise": "Gegen den Uhrzeigersinn", "api_key": "API-SchlÃŧssel", "api_key_description": "Dieser Wert wird nur einmal angezeigt. Bitte kopiere ihn, bevor du das Fenster schließt.", "api_key_empty": "Dein API-SchlÃŧssel-Name darf nicht leer sein", "api_keys": "API-SchlÃŧssel", "app_architecture_variant": "Variante (Architektur)", "app_bar_signout_dialog_content": "Bist du dir sicher, dass du dich abmelden mÃļchtest?", + "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Abmelden", "app_download_links": "App Download-Links", "app_settings": "App-Einstellungen", "app_stores": "App Stores", - "app_update_available": "App-Update verfÃŧgbar", + "app_update_available": "App Update verfÃŧgbar", "appears_in": "Enthalten in", "apply_count": "Anwenden ({count, number})", "archive": "Archiv", "archive_action_prompt": "{count} zum Archiv hinzugefÃŧgt", "archive_or_unarchive_photo": "Foto archivieren bzw. Archivierung aufheben", + "archive_page_no_archived_assets": "Keine archivierten Inhalte gefunden", + "archive_page_title": "Archiv ({count})", "archive_size": "ArchivgrÃļße", "archive_size_description": "ArchivgrÃļße fÃŧr Downloads konfigurieren (in GiB)", "archived": "Archiviert", "archived_count": "{count, plural, other {# archiviert}}", "are_these_the_same_person": "Ist das dieselbe Person?", "are_you_sure_to_do_this": "Bist du sicher, dass du das tun willst?", + "array_field_not_fully_supported": "Array-Felder erfordern manuelle JSON-Bearbeitung", + "asset_action_delete_err_read_only": "SchreibgeschÃŧtzte Inhalte kÃļnnen nicht gelÃļscht werden, Ãŧberspringen", + "asset_action_share_err_offline": "Die Offline-Inhalte konnten nicht gelesen werden, Ãŧberspringen", "asset_added_to_album": "Zum Album hinzugefÃŧgt", "asset_adding_to_album": "HinzufÃŧgen zum Albumâ€Ļ", "asset_created": "Datei erstellt", - "asset_day_count": "{date}: {count, plural, one {# Datei} other {# Dateien}}", "asset_description_updated": "Die Beschreibung der Datei wurde aktualisiert", + "asset_filename_is_offline": "Datei {filename} ist offline", + "asset_has_unassigned_faces": "Datei hat nicht zugewiesene Gesichter", "asset_hashing": "Berechne PrÃŧfsummeâ€Ļ", "asset_list_group_by_sub_title": "Gruppieren nach", + "asset_list_layout_settings_dynamic_layout_title": "Dynamisches Layout", + "asset_list_layout_settings_group_automatically": "Automatisch", "asset_list_layout_settings_group_by": "Gruppiere Elemente nach", "asset_list_layout_settings_group_by_month_day": "Monat + Tag", "asset_list_layout_sub_title": "Layout", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Datei in iCloud nicht gefunden. Die Datei kann mÃļglicherweise aufgrund schlechter Dateispeicherung in iCloud nicht auffindbar sein", "asset_offline": "Datei offline", "asset_offline_description": "Diese externe Datei ist nicht mehr auf dem Datenträger vorhanden. Bitte wende dich an deinen Immich-Administrator, um Hilfe zu erhalten.", + "asset_restored_successfully": "Datei erfolgreich wiederhergestellt", "asset_skipped": "Übersprungen", "asset_skipped_in_trash": "Im Papierkorb", + "asset_trashed": "Datei gelÃļscht", "asset_troubleshoot": "Datei Fehlerbehebung", "asset_uploaded": "Hochgeladen", "asset_uploading": "Hochladenâ€Ļ", "asset_viewer_settings_subtitle": "Verwaltung der Einstellungen fÃŧr die Fotoanzeige", "asset_viewer_settings_title": "Fotoanzeige", "assets": "Dateien", + "assets_added_count": "{count, plural, one {# Datei} other {# Dateien}} hinzugefÃŧgt", "assets_added_to_album_count": "{count, plural, one {# Datei} other {# Dateien}} zum Album hinzugefÃŧgt", - "assets_added_to_album_partial_count": "{successCount} von {totalCount} {totalCount, plural, one {Datei} other {Dateien}} zum Album hinzugefÃŧgt", "assets_added_to_albums_count": "{assetTotal, plural, one {# Datei} other {# Dateien}} zu {albumTotal, plural, one {# Album} other {# Alben}} hinzugefÃŧgt", "assets_cannot_be_added_to_album_count": "{count, plural, one {Datei kann}other {Dateien kÃļnnen}} nicht zum Album hinzugefÃŧgt werden", "assets_cannot_be_added_to_albums": "{count, plural, one {Datei kann} other {Dateien kÃļnnen}} nicht zu den Alben hinzugefÃŧgt werden", "assets_count": "{count, plural, one {# Datei} other {# Dateien}}", + "assets_deleted_permanently": "{count} Element(e) permanent gelÃļscht", + "assets_deleted_permanently_from_server": "{count} Element(e) permanent vom Immich-Server gelÃļscht", + "assets_downloaded_failed": "{count, plural, one {# Datei heruntergeladen - {error} fehlgeschlagen} other {# Dateien heruntergeladen - {error} fehlgeschlagen}}", + "assets_downloaded_successfully": "{count, plural, one {# Datei erfolgreich heruntergeladen} other {# Dateien erfolgreich heruntergeladen}}", "assets_moved_to_trash_count": "{count, plural, one {# Datei} other {# Dateien}} in den Papierkorb verschoben", "assets_permanently_deleted_count": "{count, plural, one {# Datei} other {# Dateien}} endgÃŧltig gelÃļscht", "assets_removed_count": "{count, plural, one {# Datei} other {# Dateien}} entfernt", + "assets_removed_permanently_from_device": "{count} Element(e) permanent von Ihrem Gerät gelÃļscht", "assets_restore_confirmation": "Bist du sicher, dass du alle Dateien aus dem Papierkorb wiederherstellen willst? Diese Aktion kann nicht rÃŧckgängig gemacht werden! Beachte, dass Offline-Dateien auf diese Weise nicht wiederhergestellt werden kÃļnnen.", "assets_restored_count": "{count, plural, one {# Datei} other {# Dateien}} wiederhergestellt", + "assets_restored_successfully": "{count} Element(e) erfolgreich wiederhergestellt", "assets_trashed": "{count} Element(e) gelÃļscht", "assets_trashed_count": "{count, plural, one {# Datei} other {# Dateien}} in den Papierkorb verschoben", + "assets_trashed_from_server": "{count} Element(e) vom Immich-Server gelÃļscht", "assets_were_part_of_album_count": "{count, plural, one {# Datei ist} other {# Dateien sind}} bereits im Album vorhanden", "assets_were_part_of_albums_count": "{count, plural, one {Datei war} other {Dateien waren}} bereits in den Alben", "authorized_devices": "Verwendete Geräte", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Automatische Diashow", "back": "ZurÃŧck", "back_close_deselect": "ZurÃŧck, Schließen oder Abwählen", + "background_backup_running_error": "Sicherung läuft im Hintergrund. Manuelle Sicherung kann nicht gestartet werden", "background_location_permission": "Hintergrund Standortfreigabe", "background_location_permission_content": "Um im Hintergrund zwischen den Netzwerken wechseln zu kÃļnnen, muss Immich *immer* Zugriff auf den genauen Standort haben, damit die App den Namen des WLAN-Netzwerks ermitteln kann", "background_options": "Hintergrund Optionen", "backup": "Sicherung", + "backup_album_selection_page_albums_device": "Alben auf dem Gerät ({count})", "backup_album_selection_page_albums_tap": "Antippen zum sichern, erneut antippen zum Ausschließen", "backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) kÃļnnen sich Ãŧber mehrere Alben verteilen. Daher kÃļnnen diese vor der Sicherung eingeschlossen oder ausgeschlossen werden.", "backup_album_selection_page_select_albums": "Alben auswählen", "backup_album_selection_page_selection_info": "Auswahlinformation", + "backup_album_selection_page_total_assets": "Elemente gesamt", "backup_albums_sync": "Synchronisation der Sicherungsalben", + "backup_all": "Alle", + "backup_background_service_backup_failed_message": "Es trat ein Fehler bei der Sicherung auf. Erneuter Versuchâ€Ļ", "backup_background_service_complete_notification": "Datei Backup abgeschlossen", + "backup_background_service_connection_failed_message": "Es konnte keine Verbindung zum Server hergestellt werden. Erneuter Versuchâ€Ļ", + "backup_background_service_current_upload_notification": "Lädt {filename} hoch", "backup_background_service_default_notification": "Suche nach neuen Elementenâ€Ļ", + "backup_background_service_error_title": "Fehler bei der Sicherung", "backup_background_service_in_progress_notification": "Elemente werden gesichertâ€Ļ", + "backup_background_service_upload_failure_notification": "Konnte {filename} nicht hochladen", "backup_controller_page_albums": "Gesicherte Alben", + "backup_controller_page_background_app_refresh_disabled_content": "Aktiviere Hintergrundaktualisierungen in Einstellungen -> Allgemein -> Hintergrundaktualisierungen um Sicherungen im Hintergrund zu ermÃļglichen.", + "backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierungen sind deaktiviert", + "backup_controller_page_background_app_refresh_enable_button_text": "Gehe zu Einstellungen", "backup_controller_page_background_battery_info_link": "Zeige mir wie", "backup_controller_page_background_battery_info_message": "FÃŧr die besten Ergebnisse fÃŧr Sicherungen im Hintergrund, deaktiviere alle Batterieoptimierungen und Einschränkungen fÃŧr die Hintergrundaktivitäten von Immich.\n\nDa dies gerätespezifisch ist, schlage diese Informationen fÃŧr deinen Gerätehersteller nach.", "backup_controller_page_background_battery_info_ok": "Ok", "backup_controller_page_background_battery_info_title": "Batterieoptimierungen", + "backup_controller_page_background_charging": "Nur während des Ladens", + "backup_controller_page_background_configure_error": "Konnte Hintergrundservice nicht konfigurieren", "backup_controller_page_background_delay": "Sicherung neuer Elemente verzÃļgern um: {duration}", + "backup_controller_page_background_description": "Schalte den Hintergrundservice ein, um neue Elemente automatisch im Hintergrund zu sichern ohne die App zu Ãļffnen", + "backup_controller_page_background_is_off": "Automatische Sicherung im Hintergrund ist deaktiviert", + "backup_controller_page_background_is_on": "Automatische Sicherung im Hintergrund ist aktiviert", + "backup_controller_page_background_turn_off": "Hintergrundservice ausschalten", + "backup_controller_page_background_turn_on": "Hintergrundservice einschalten", + "backup_controller_page_background_wifi": "Nur im WLAN", "backup_controller_page_backup": "Sicherung", "backup_controller_page_backup_selected": "Ausgewählt: ", "backup_controller_page_backup_sub": "Gesicherte Fotos und Videos", + "backup_controller_page_created": "Erstellt am: {date}", + "backup_controller_page_desc_backup": "Aktiviere die Sicherung, um Elemente immer automatisch auf den Server zu laden, während du die App benutzt.", "backup_controller_page_excluded": "Ausgeschlossen: ", + "backup_controller_page_failed": "Fehlgeschlagen ({count})", + "backup_controller_page_filename": "Dateiname: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informationen zur Sicherung", "backup_controller_page_none_selected": "Keine ausgewählt", "backup_controller_page_remainder": "Verbleibend", "backup_controller_page_remainder_sub": "Noch zu sichernde Fotos und Videos", "backup_controller_page_server_storage": "Server-Speicher", + "backup_controller_page_start_backup": "Sicherung starten", + "backup_controller_page_status_off": "Sicherung im Vordergrund ist inaktiv", + "backup_controller_page_status_on": "Sicherung im Vordergrund ist aktiv", "backup_controller_page_storage_format": "{used} von {total} genutzt", "backup_controller_page_to_backup": "Zu sichernde Alben", "backup_controller_page_total_sub": "Alle Fotos und Videos", + "backup_controller_page_turn_off": "Sicherung im Vordergrund ausschalten", + "backup_controller_page_turn_on": "Sicherung im Vordergrund einschalten", + "backup_controller_page_uploading_file_info": "Informationen", + "backup_err_only_album": "Das einzige Album kann nicht entfernt werden", "backup_error_sync_failed": "Synchronisierung fehlgeschlagen. Sicherung kann nicht verarbeitet werden.", "backup_info_card_assets": "Elemente", + "backup_manual_cancelled": "Abgebrochen", + "backup_manual_in_progress": "Sicherung läuft bereits. Bitte versuche es später erneut", + "backup_manual_success": "Erfolgreich", + "backup_manual_title": "Sicherungsstatus", "backup_options": "Backup-Optionen", + "backup_options_page_title": "Sicherungsoptionen", + "backup_setting_subtitle": "Verwaltung der Upload-Einstellungen im Hintergrund und im Vordergrund", "backup_settings_subtitle": "Upload-Einstellungen verwalten", + "backup_upload_details_page_more_details": "Tippe fÃŧr weitere Details", "backward": "RÃŧckwärts", - "battery_optimization_backup_reliability": "Das Deaktivieren der Akkuoptimierung kann die Zuverlässigkeit der Sicherung im Hintergrund verbessern", "biometric_auth_enabled": "Biometrische Authentifizierung aktiviert", "biometric_locked_out": "Du bist von der biometrischen Authentifizierung ausgeschlossen", "biometric_no_options": "Keine biometrischen Optionen verfÃŧgbar", "biometric_not_available": "Die biometrische Authentifizierung ist auf diesem Gerät nicht verfÃŧgbar", + "birthdate_saved": "Geburtsdatum erfolgreich gespeichert", "birthdate_set_description": "Das Geburtsdatum wird verwendet, um das Alter dieser Person zum Zeitpunkt eines Fotos zu berechnen.", "blurred_background": "Unscharfer Hintergrund", - "browse_templates": "Vorlagen durchsuchen", "bugs_and_feature_requests": "Fehler & Verbesserungsvorschläge", "build": "Build", "build_image": "Abbildversion", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien}} behalten mÃļchtest? Dies wird alle Duplikat-Gruppen auflÃļsen ohne etwas zu lÃļschen.", "bulk_trash_duplicates_confirmation": "Bist du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateien gemeinsam}} in den Papierkorb verschieben mÃļchtest? Dies wird die grÃļßte Datei jeder Gruppe behalten und alle anderen Duplikate in den Papierkorb verschieben.", "buy": "Immich erwerben", + "cache_settings_clear_cache_button": "Zwischenspeicher lÃļschen", + "cache_settings_clear_cache_button_title": "LÃļscht den Zwischenspeicher der App. Dies wird die Leistungsfähigkeit der App deutlich einschränken, bis der Zwischenspeicher wieder aufgebaut wurde.", + "cache_settings_duplicated_assets_clear_button": "LEEREN", + "cache_settings_duplicated_assets_subtitle": "Fotos und Videos, die von der App blockiert werden", + "cache_settings_duplicated_assets_title": "Duplikate ({count})", + "cache_settings_statistics_album": "Vorschaubilder der Bibliothek", + "cache_settings_statistics_full": "Originalbilder", + "cache_settings_statistics_shared": "Vorschaubilder geteilter Alben", + "cache_settings_statistics_thumbnail": "Vorschaubilder", + "cache_settings_statistics_title": "Zwischenspeicher-Nutzung", + "cache_settings_subtitle": "Kontrollieren, wie Immich den Zwischenspeicher nutzt", + "cache_settings_tile_subtitle": "Lokalen Speicher verwalten", + "cache_settings_tile_title": "Lokaler Speicher", + "cache_settings_title": "Zwischenspeicher Einstellungen", "camera": "Kamera", "camera_brand": "Kamera-Marke", "camera_model": "Kamera-Modell", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Beschreibung kann nicht aktualisiert werden", "cast": "Übertragen", "cast_description": "VerfÃŧgbare Cast-Ziele konfigurieren", - "change": "Ändern", "change_date": "Datum ändern", "change_description": "Beschreibung anpassen", "change_display_order": "Anzeigereihenfolge ändern", + "change_expiration_time": "Verfallszeitpunkt ändern", "change_location": "Standort ändern", "change_name": "Name ändern", "change_name_successfully": "Name wurde erfolgreich geändert", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "PasswÃļrter stimmen nicht Ãŧberein", "change_password_form_reenter_new_password": "Passwort erneut eingeben", "change_pin_code": "PIN-Code ändern", + "change_trigger": "AuslÃļser ändern", + "change_trigger_prompt": "Bist du sicher, dass du den AuslÃļser ändern willst? Dies entfernt alle bestehenden Aktionen und Filter.", "change_your_password": "Ändere dein Passwort", "changed_visibility_successfully": "Die Sichtbarkeit wurde erfolgreich geändert", "charging": "Aufladen", "charging_requirement_mobile_backup": "Backup im Hintergrund erfordert Aufladen des Geräts", + "check_corrupt_asset_backup": "Auf beschädigte Asset-Backups ÃŧberprÃŧfen", + "check_corrupt_asset_backup_button": "ÜberprÃŧfung durchfÃŧhren", + "check_corrupt_asset_backup_description": "FÃŧhre diese PrÃŧfung nur mit aktivierten WLAN durch, nachdem alle Dateien gesichert worden sind. Dieser Vorgang kann ein paar Minuten dauern.", "check_logs": "Logs prÃŧfen", "checksum": "PrÃŧfsumme", - "choose": "Wählen", "choose_matching_people_to_merge": "Wähle passende Personen zum ZusammenfÃŧhren", "city": "Stadt", "cleanup_confirm_description": "Immich hat {count} Dateien (vor dem {date} erstellt) sicher auf dem Server gefunden. Sollen die lokalen Kopien von diesem Gerät gelÃļscht werden?", @@ -719,10 +774,11 @@ "clear": "Leeren", "clear_all": "Alles leeren", "clear_all_recent_searches": "Alle letzten Suchvorgänge lÃļschen", - "clear_failed_count": "({count}) LÃļschungen fehlgeschlagen", "clear_file_cache": "Dateien-Cache leeren", "clear_message": "Nachrichten leeren", "clear_value": "Wert leeren", + "client_cert_dialog_msg_confirm": "Ok", + "client_cert_enter_password": "Passwort eingeben", "client_cert_import": "Importieren", "client_cert_import_success_msg": "Client Zertifikat wurde importiert", "client_cert_invalid_msg": "UngÃŧltige Zertifikatsdatei oder falsches Passwort", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Client Zertifikat wurde entfernt", "client_cert_subtitle": "UnterstÃŧtzt nur das PKCS12 (.p12, .pfx) Format. Zertifikatsimporte oder -entfernungen sind nur vor dem Login mÃļglich", "client_cert_title": "SSL-Client-Zertifikat [Experimentell]", + "clockwise": "Im Uhrzeigersinn", "close": "Schließen", "collapse": "Zusammenklappen", "collapse_all": "Alle zusammenklappen", "color": "Farbe", + "color_theme": "Farb-Theme", "command": "Befehl", "command_palette_prompt": "Finde schnell Seiten, Aktionen oder Befehle", "command_palette_to_close": "Schließen", @@ -747,7 +805,6 @@ "comments_are_disabled": "Kommentare sind deaktiviert", "common_create_new_album": "Neues Album erstellen", "completed": "Abgeschlossen", - "configuration": "Konfiguration", "confirm": "Bestätigen", "confirm_admin_password": "Administrator Passwort bestätigen", "confirm_delete_face": "Bist du sicher, dass du das Gesicht von {name} aus der Datei entfernen willst?", @@ -762,16 +819,19 @@ "contain": "Vollständig", "context": "Kontext", "continue": "Fortsetzen", - "control_bottom_app_bar_add_tags": "Tags hinzufÃŧgen", + "control_bottom_app_bar_create_new_album": "Neues Album erstellen", + "control_bottom_app_bar_delete_from_immich": "Aus Immich lÃļschen", "control_bottom_app_bar_delete_from_local": "Vom Gerät lÃļschen", "control_bottom_app_bar_edit_location": "Standort bearbeiten", "control_bottom_app_bar_edit_time": "Datum und Uhrzeit bearbeiten", + "control_bottom_app_bar_share_link": "Link teilen", + "control_bottom_app_bar_share_to": "Teilen mit", "control_bottom_app_bar_trash_from_immich": "in den Papierkorb schieben", "copied_image_to_clipboard": "Das Bild wurde in die Zwischenablage kopiert.", "copied_to_clipboard": "In die Zwischenablage kopiert!", "copy_error": "Kopier-Fehler", + "copy_file_path": "Dateipfad kopieren", "copy_image": "Bild kopieren", - "copy_json": "JSON kopieren", "copy_link": "Link kopieren", "copy_link_to_clipboard": "Link in die Zwischenablage kopieren", "copy_password": "Passwort kopieren", @@ -789,6 +849,7 @@ "create_link_to_share": "Link zum Teilen erstellen", "create_link_to_share_description": "Lass jeden mit dem Link die ausgewählten Fotos sehen", "create_new": "NEUES ERSTELLEN", + "create_new_face": "Neues Gesicht erstellen", "create_new_person": "Neue Person anlegen", "create_new_person_hint": "Ausgewählte Dateien einer neuen Person zuweisen", "create_new_user": "Neuen Nutzer erstellen", @@ -805,33 +866,37 @@ "created_at": "Erstellt", "creating_linked_albums": "Erstelle verknÃŧpfte Alben...", "crop": "Zuschneiden", + "crop_aspect_ratio_fixed": "Fixiert", "crop_aspect_ratio_free": "Frei", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Quadratisch", + "curated_object_page_title": "Dinge", "current_device": "Aktuelles Gerät", "current_pin_code": "Aktueller PIN-Code", "current_server_address": "Aktuelle Serveradresse", "custom_date": "Benutzerdefiniertes Datum", "custom_locale": "Benutzerdefiniertes Gebietsschema", "custom_locale_description": "Datumsangaben, Uhrzeiten und Zahlen je nach Sprache und Land formatieren", + "custom_url": "Benutzerdefinierte URL", "cutoff_date_description": "Behalte Fotos der letztenâ€Ļ", "cutoff_day": "{count, plural, one {Tag} other {Tage}}", "cutoff_year": "{count, plural, one {Jahr} other {Jahre}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Dunkel", "dark_theme": "Auf dunkle Ansicht umschalten", "date": "Datum", "date_after": "Datum nach", "date_and_time": "Datum und Zeit", "date_before": "Datum vor", - "date_of_birth": "Geburtstag", - "date_of_birth_saved": "Der Geburtstag wurde erfolgreich gespeichert", + "date_format": "E d. LLL y â€ĸ hh:mm", + "date_of_birth_saved": "Das Geburtsdatum wurde erfolgreich gespeichert", "date_range": "Datumsbereich", - "date_time_original": "Datum/Uhrzeit Original", "day": "Tag", "days": "Tage", "deduplicate_all": "Alle Duplikate entfernen", - "default_quality_subtitle": "Die beim Teilen verwendete Qualität. Halten Sie die Teilen-Taste gedrÃŧckt, um die Auswahl jedes Mal neu zu treffen.", - "default_share_quality": "Standardqualität beim Teilen", + "default_locale": "Standardgebietsschema", + "default_locale_description": "Datumsangaben und Zahlen werden entsprechend Ihrer Browsereinstellungen formatiert", "delete": "LÃļschen", "delete_action_confirmation_message": "Bist du sicher, dass du dieses Objekt lÃļschen willst? Diese Aktion wird das Objekt in den Papierkorb des Servers verschieben und fragen, ob du es lokal lÃļschen willst", "delete_action_prompt": "{count} gelÃļscht", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Diese Elemente werden unwiderruflich von Immich und dem Gerät entfernt", "delete_dialog_alert_local": "Diese Inhalte werden vom Gerät gelÃļscht, bleiben aber auf dem Immich-Server", "delete_dialog_alert_local_non_backed_up": "Einige Inhalte sind nicht in Immich gesichert und werden dauerhaft vom Gerät gelÃļscht", + "delete_dialog_alert_remote": "Diese Inhalte werden dauerhaft vom Immich-Server gelÃļscht", + "delete_dialog_ok_force": "Trotzdem lÃļschen", "delete_dialog_title": "EndgÃŧltig lÃļschen", "delete_duplicates_confirmation": "Bist du sicher, dass du diese Duplikate endgÃŧltig lÃļschen willst?", "delete_face": "Gesicht lÃļschen", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Bist du sicher, dass der Tag {tagName} gelÃļscht werden soll?", "delete_user": "Nutzer lÃļschen", "deleted_shared_link": "Geteilten Link gelÃļscht", + "deletes_missing_assets": "LÃļscht Dateien, die auf der Festplatte fehlen", "description": "Beschreibung", + "description_input_hint_text": "Beschreibung hinzufÃŧgen...", + "description_input_submit_error": "Beschreibung konnte nicht geändert werden, bitte im Log fÃŧr mehr Details nachsehen", "deselect_all": "Alle abwählen", "details": "Details", "direction": "Richtung", "disable": "Deaktivieren", "disabled": "Deaktiviert", + "disallow_edits": "Bearbeitungen verbieten", "discord": "Discord", "discover": "Entdecken", "discovered_devices": "Gefundene Geräte", "dismiss_all_errors": "Alle Fehler ignorieren", + "dismiss_error": "Fehler ignorieren", "display_options": "Anzeigeoptionen", "display_order": "Anzeigereihenfolge", "display_original_photos": "Originale Fotos anzeigen", @@ -876,9 +948,11 @@ "documentation": "Dokumentation", "done": "Fertig", "download": "Herunterladen", + "download_action_prompt": "Herunterladen von {count} Dateien", "download_canceled": "Download abgebrochen", "download_complete": "Download vollständig", "download_enqueue": "Download in die Warteschlange gesetzt", + "download_error": "Download fehlerhaft", "download_failed": "Download fehlerhaft", "download_finished": "Download abgeschlossen", "download_include_embedded_motion_videos": "Eingebettete Videos", @@ -888,15 +962,15 @@ "download_paused": "Download pausiert", "download_settings": "Download", "download_settings_description": "Einstellungen fÃŧr das Herunterladen von Dateien verwalten", + "download_started": "Download gestartet", + "download_sucess": "Download erfolgreich", + "download_sucess_android": "Die Datei wurde nach DCIM/Immich heruntergeladen", "download_waiting_to_retry": "Warte auf erneuten Versuch", "downloading": "Herunterladen", "downloading_asset_filename": "Datei {filename} wird heruntergeladen", "downloading_from_icloud": "von iCloud herunterladen", "downloading_media": "Medien werden heruntergeladen", - "drag_to_reorder": "Ziehen um Reihenfolge zu ändern", "drop_files_to_upload": "Lade Dateien hoch, indem du sie hierhin ziehst", - "duplicate": "Duplikat", - "duplicate_workflow": "Workflow duplizieren", "duplicates": "Duplikate", "duplicates_description": "LÃļse jede Gruppe auf, indem du angibst, welche, wenn Ãŧberhaupt, Duplikate sind.", "duration": "Dauer", @@ -908,7 +982,9 @@ "edit_date_and_time": "Datum und Uhrzeit bearbeiten", "edit_date_and_time_action_prompt": "{count} Daten und Zeiten geändert", "edit_date_and_time_by_offset": "Datum ändern um Versatz", + "edit_date_and_time_by_offset_interval": "Neuer Datumsbereich: {from} - {to}", "edit_description": "Beschreibung bearbeiten", + "edit_description_prompt": "Bitte wähle eine neue Beschreibung:", "edit_exclusion_pattern": "Ausschlussmuster bearbeiten", "edit_faces": "Gesichter bearbeiten", "edit_key": "SchlÃŧssel bearbeiten", @@ -923,8 +999,11 @@ "edit_user": "Nutzer bearbeiten", "edit_workflow": "Workflow bearbeiten", "editor": "Bearbeiten", + "editor_close_without_save_prompt": "Die Änderungen werden nicht gespeichert", + "editor_close_without_save_title": "Editor schließen?", + "editor_confirm_reset_all_changes": "Alle Änderungen zurÃŧcksetzen?", "editor_discard_edits_confirm": "Änderungen verwerfen", - "editor_discard_edits_prompt": "Es sind ungespeicherte Änderungen vorhanden. Sicher, dass diese verworfen werden sollen?", + "editor_discard_edits_prompt": "Es liegen ungespeicherte Änderungen vorhanden. Sicher, dass diese verworfen werden sollen?", "editor_discard_edits_title": "Änderungen verwerfen?", "editor_edits_applied_error": "Änderungen konnten nicht angewendet werden", "editor_edits_applied_success": "Änderungen erfolgreich angewendet", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "PIN-Code eingeben", "enter_your_pin_code_subtitle": "Gib deinen PIN-Code ein, um auf den gesperrten Ordner zuzugreifen", "error": "Fehler", + "error_change_sort_album": "Ändern der Anzeigereihenfolge fehlgeschlagen", "error_delete_face": "Fehler beim LÃļschen des Gesichts", + "error_getting_places": "Fehler beim Abrufen der Orte", "error_loading_albums": "Fehler beim Laden der Alben", "error_loading_image": "Fehler beim Laden des Bildes", "error_loading_partners": "Fehler beim Laden der Partner: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Fehler beim Entfernen des ProduktschlÃŧssels", "failed_to_reset_pin_code": "ZurÃŧcksetzen des PIN-Codes fehlgeschlagen", "failed_to_stack_assets": "Dateien konnten nicht gestapelt werden", - "failed_to_tag_assets": "Dateien konnten nicht getaggt werden", "failed_to_unstack_assets": "Dateien konnten nicht entstapelt werden", "failed_to_update_notification_status": "Benachrichtigungsstatus aktualisieren fehlgeschlagen", "incorrect_email_or_password": "UngÃŧltige E-Mail oder Passwort", @@ -1087,13 +1167,21 @@ }, "errors_text": "Fehler", "exclusion_pattern": "Ausschlussmuster", - "exif": "Exif", + "exif": "EXIF", "exif_bottom_sheet_description": "Beschreibung hinzufÃŧgen...", "exif_bottom_sheet_description_error": "Fehler bei der Aktualisierung der Beschreibung", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "STANDORT", "exif_bottom_sheet_no_description": "Keine Beschreibung", + "exif_bottom_sheet_people": "PERSONEN", + "exif_bottom_sheet_person_add_person": "Namen hinzufÃŧgen", "exit_slideshow": "Diashow beenden", "expand": "Erweitern", "expand_all": "Alle aufklappen", + "experimental_settings_new_asset_list_subtitle": "In Arbeit", + "experimental_settings_new_asset_list_title": "Experimentelles Fotogitter aktivieren", + "experimental_settings_subtitle": "Benutzung auf eigene Gefahr!", + "experimental_settings_title": "Experimentell", "expire_after": "Verfällt nach", "expired": "Verfallen", "expires_date": "Läuft ab: {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Als JSON exportieren", "export_database": "Datenbank exportieren", "export_database_description": "Exportiert die SQLite Datenbank", - "exposure_time": "Belichtungszeit", "extension": "Erweiterung", "external": "Extern", "external_libraries": "Externe Bibliotheken", "external_network": "Externes Netzwerk", "external_network_sheet_info": "Wenn sich die App nicht im bevorzugten WLAN-Netzwerk befindet, verbindet sie sich mit dem Server Ãŧber die erste der folgenden URLs, die sie erreichen kann (von oben nach unten)", - "f_number": "F-Nummer", "face_unassigned": "Nicht zugewiesen", "failed": "Fehlgeschlagen", "failed_count": "Fehlgeschlagen: {count}", "failed_to_authenticate": "Authentifizierung fehlgeschlagen", - "failed_to_delete_file": "LÃļschen der Datei fehlgeschlagen", "failed_to_load_assets": "Laden der Assets fehlgeschlagen", "failed_to_load_folder": "Fehler beim Laden des Ordners", "favorite": "Favorit", "favorite_action_prompt": "{count} zu den Favoriten hinzugefÃŧgt", "favorite_or_unfavorite_photo": "Favorisiertes oder nicht favorisiertes Foto", "favorites": "Favoriten", + "favorites_page_no_favorites": "Keine favorisierten Inhalte gefunden", "feature_photo_updated": "Profilbild aktualisiert", "features": "Funktionen", + "features_in_development": "Feature in Entwicklung", "features_setting_description": "Funktionen der App verwalten", "file_name_or_extension": "Dateiname oder -erweiterung", "file_name_text": "Dateiname", + "file_name_with_value": "Dateiname: {file_name}", "file_size": "DateigrÃļße", "filename": "Dateiname", "filetype": "Dateityp", "filter": "Filter", + "filter_description": "Bedingungen zur Filterung der betreffenden Dateien", "filter_people": "Personen filtern", "filter_places": "Orte filtern", "filter_tags": "Tags filtern", "filters": "Filter", + "find_them_fast": "Finde sie schneller mit der Suche nach Namen", "first": "Erste", "fix_incorrect_match": "Fehlerhafte Übereinstimmung beheben", - "focal_length": "Brennweite", "folder": "Ordner", "folder_not_found": "Ordner nicht gefunden", "folders": "Ordner", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Bewege Fotos und Videos, die bereits gesichert wurden, in den Papierkorb auf deinem Gerät. Die Kopie auf dem Server bleibt unberÃŧhrt.", "free_up_space_settings_subtitle": "Gerätespeicher freigeben", "full_path": "Vollständiger Pfad: {path}", - "full_path_or_folder": "Voller Ordnerpfad oder Ordner", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Diese Funktion lädt externe Quellen von Google, um zu funktionieren.", "general": "Allgemein", @@ -1168,6 +1256,7 @@ "group_owner": "Gruppierung nach Besitzer", "group_places_by": "Orte gruppieren nach...", "group_year": "Gruppierung nach Jahr", + "haptic_feedback_switch": "Haptisches Feedback aktivieren", "haptic_feedback_title": "Haptisches Feedback", "has_quota": "Kontingent", "hash_asset": "Dateihash", @@ -1188,12 +1277,29 @@ "hide_schema": "Schema ausblenden", "hide_text_recognition": "Texterkennung verbergen", "hide_unnamed_people": "Unbenannte Personen verbergen", + "home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugefÃŧgt. {failed} Elemente sind bereits vorhanden.", + "home_page_add_to_album_err_local": "Es kÃļnnen lokale Elemente noch nicht zu Alben hinzugefÃŧgt werden, Ãŧberspringen", + "home_page_add_to_album_success": "{added} Elemente zu {album} hinzugefÃŧgt.", + "home_page_album_err_partner": "Inhalte von Partnern kÃļnnen derzeit nicht zu Alben hinzugefÃŧgt werden", + "home_page_archive_err_local": "Kann lokale Elemente nicht archivieren, Ãŧberspringen", + "home_page_archive_err_partner": "Inhalte von Partnern kÃļnnen nicht archiviert werden", "home_page_building_timeline": "Zeitachse wird erstellt", + "home_page_delete_err_partner": "Inhalte von Partnern kÃļnnen nicht gelÃļscht werden, Ãŧberspringe", + "home_page_delete_remote_err_local": "Lokale Elemente in der Auswahl zum Entfernen von Remote-Elementen, Überspringe", + "home_page_favorite_err_local": "Kann lokale Elemente noch nicht favorisieren, Ãŧberspringen", + "home_page_favorite_err_partner": "Inhalte von Partnern kÃļnnen nicht favorisiert werden, Ãŧberspringe", + "home_page_first_time_notice": "Wenn Sie die App zum ersten Mal verwenden, wählen Sie bitte ein Album zur Sicherung aus, damit die Zeitachse mit Fotos und Videos gefÃŧllt werden kann", + "home_page_locked_error_local": "Lokale Dateien kÃļnnen nicht in den gesperrten Ordner verschoben werden, Ãŧberspringe", + "home_page_locked_error_partner": "Dateien von Partnern kÃļnnen nicht in den gesperrten Ordner verschoben werden, Ãŧberspringe", + "home_page_share_err_local": "Lokale Inhalte kÃļnnen nicht per Link geteilt werden, Ãŧberspringe", + "home_page_upload_err_limit": "Es kÃļnnen max. 30 Elemente gleichzeitig hochgeladen werden, Ãŧberspringen", "host": "Host", "hour": "Stunde", "hours": "Stunden", "id": "ID", "idle": "Untätig", + "ignore_icloud_photos": "iCloud Fotos ignorieren", + "ignore_icloud_photos_description": "Fotos, die in der iCloud gespeichert sind, werden nicht auf den immich Server hochgeladen", "image": "Bild", "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} aufgenommen am {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} aufgenommen mit {person1} am {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1} und {person2} am {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {person3} am {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} aufgenommen in {city}, {country} mit {person1}, {person2}, und {additionalCount, number} anderen am {date}", + "image_saved_successfully": "Bild gespeichert", + "image_viewer_page_state_provider_download_started": "Download gestartet", + "image_viewer_page_state_provider_download_success": "Erfolgreich heruntergeladen", + "image_viewer_page_state_provider_share_error": "Fehler beim Teilen", "immich_logo": "Immich-Logo", "immich_web_interface": "Immich-Web-Oberfläche", "import_from_json": "Aus JSON importieren", @@ -1219,7 +1329,6 @@ "individual_share": "Individuelle Freigabe", "individual_shares": "Individuelles Teilen", "info": "Info", - "integrity_checks": "IntegritätsprÃŧfungen", "interval": { "day_at_onepm": "Täglich um 13:00 Uhr", "hours": "{hours, plural, one {Jede Stunde} other {Alle {hours, number} Stunden}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "UngÃŧltiges Datumsformat", "invite_people": "Personen einladen", "invite_to_album": "Zum Album einladen", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Abruf läuft {dateTime}", + "ios_debug_info_last_sync_at": "Zuletzt aktualisiert {dateTime}", + "ios_debug_info_no_processes_queued": "Keine Hintergrundprozesse in der Warteschlange", + "ios_debug_info_no_sync_yet": "Noch kein Hintergrundsynchronisierungsauftrag ausgefÃŧhrt", + "ios_debug_info_processes_queued": "{count, plural, one {{count} Hintergrundprozess in der Warteschlange} other {{count} Hintergrundprozesse in der Warteschlange}}", + "ios_debug_info_processing_ran_at": "Prozess läuft {dateTime}", "items_count": "{count, plural, one {# Eintrag} other {# Einträge}}", "jobs": "Aufgaben", + "json_editor": "JSON-Editor", + "json_error": "JSON-Fehler", "keep": "Behalten", "keep_albums": "Alben behalten", "keep_albums_count": "Behalte {count} {count, plural, one {album} other {albums}}", @@ -1254,17 +1370,19 @@ "last": "Letzte", "last_months": "{count, plural, one {Letzter Monat} other {Letzte # Monate}}", "last_seen": "Zuletzt gesehen", - "latest_version": "Neuste Version", + "latest_version": "Aktuelle Version", "latitude": "Breitengrad", "leave": "Verlassen", "leave_album": "Album verlassen", "lens_model": "Objektivmodell", - "less": "Weniger", "let_others_respond": "Antworten zulassen", "level": "Level", "library": "Bibliothek", "library_add_folder": "Ordner hinzufÃŧgen", "library_edit_folder": "Ordner bearbeiten", + "library_options": "Bibliotheksoptionen", + "library_page_device_albums": "Alben auf dem Gerät", + "library_page_new_album": "Neues Album", "library_page_sort_asset_count": "Anzahl der Inhalte", "library_page_sort_created": "Zuletzt erstellt", "library_page_sort_last_modified": "Zuletzt bearbeitet", @@ -1274,14 +1392,11 @@ "light_theme": "Auf helle Ansicht umschalten", "like": "Gefällt mir", "like_deleted": "Like gelÃļscht", - "link": "Link", "link_motion_video": "Bewegungsvideo verknÃŧpfen", "link_to_docs": "Weitere Informationen finden Sie in der Dokumentation.", "link_to_oauth": "Mit OAuth verknÃŧpfen", "linked_oauth_account": "VerknÃŧpftes OAuth-Konto", "list": "Liste", - "live": "Live", - "load_more": "Weitere laden", "loading": "Laden", "loading_search_results_failed": "Laden von Suchergebnissen fehlgeschlagen", "local": "Lokal", @@ -1310,9 +1425,11 @@ "login": "Anmelden", "login_disabled": "Login ist deaktiviert", "login_form_api_exception": "API Fehler. Bitte die Serveradresse ÃŧberprÃŧfen und erneut versuchen.", + "login_form_back_button_text": "ZurÃŧck", "login_form_email_hint": "deine@email.de", "login_form_endpoint_hint": "http://deine-server-ip:port", "login_form_endpoint_url": "Server-URL", + "login_form_err_http": "Bitte gebe http:// oder https:// an", "login_form_err_invalid_email": "UngÃŧltige E-Mail", "login_form_err_invalid_url": "UngÃŧltige URL", "login_form_err_leading_whitespace": "Leerzeichen am Anfang", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Fehler beim Login, bitte ÃŧberprÃŧfe die Server-URL, deine E-Mail oder das Passwort", "login_form_handshake_exception": "Fehler beim Verbindungsaufbau mit dem Server. Falls du ein selbstsigniertes Zertifikat verwendest, aktiviere die UnterstÃŧtzung dafÃŧr in den Einstellungen.", "login_form_password_hint": "Passwort", - "login_form_server_empty": "Serveradresse eingeben", - "login_form_server_error": "Verbindung zum Server konnte nicht hergestellt werden", - "login_has_been_disabled": "Die Anmeldung wurde deaktiviert", + "login_form_save_login": "Angemeldet bleiben", + "login_form_server_empty": "Serveradresse eingeben.", + "login_form_server_error": "Es Konnte sich nicht mit dem Server verbunden werden.", + "login_has_been_disabled": "Die Anmeldung wurde deaktiviert.", "login_password_changed_error": "Fehler beim Ändern deines Passwortes", "login_password_changed_success": "Passwort erfolgreich geändert", "logout_all_device_confirmation": "Bist du sicher, dass du alle Geräte abmelden willst?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Datenbank wird wiederhergestellt", "maintenance_description": "Immich wurde in den Wartungsmodus versetzt.", "maintenance_end": "Wartungsmodus beenden", + "maintenance_end_error": "Wartungsmodus konnte nicht beendet werden.", "maintenance_logged_in_as": "Aktuell angemeldet als {user}", "maintenance_restore_from_backup": "Von Datenbank wiederherstellen", "maintenance_restore_library": "Deine Bibliothek wiederherstellen", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Einstellungen Ãļffnen", "manage_media_access_subtitle": "Erlaube Immich, Mediendateien zu verwalten und zu verschieben.", "manage_media_access_title": "Verwaltung von Mediendateien", + "manage_shared_links": "Freigegebene Links verwalten", "manage_sharing_with_partners": "Gemeinsame Nutzung mit Partnern verwalten", "manage_the_app_settings": "App-Einstellungen verwalten", "manage_your_account": "Dein Konto verwalten", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Deine eingeloggten Geräte verwalten", "manage_your_oauth_connection": "Deine OAuth-VerknÃŧpfung verwalten", "map": "Karte", + "map_assets_in_bounds": "{count, plural, =0 {Keine Fotos in diesem Gebiet} one {# Foto} other {# Fotos}}", "map_cannot_get_user_location": "Standort konnte nicht ermittelt werden", - "map_location_picker_page_use_location": "Diesen Standort verwenden", + "map_location_dialog_yes": "Ja", + "map_location_picker_page_use_location": "Aufnahmeort verwenden", "map_location_service_disabled_content": "Ortungsdienste mÃŧssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", "map_location_service_disabled_title": "Ortungsdienste deaktiviert", - "map_marker_for_image": "Kartenmarkierung fÃŧr in {city}, {country} aufgenommenes Bild", + "map_marker_for_images": "Kartenmarkierung fÃŧr Bilder, die in {city}, {country} aufgenommen wurden", "map_marker_with_image": "Kartenmarkierung mit Bild", "map_no_location_permission_content": "Ortungsdienste mÃŧssen aktiviert sein, um Inhalte am aktuellen Standort anzuzeigen. Willst du die Ortungsdienste jetzt aktivieren?", "map_no_location_permission_title": "Kein Zugriff auf den Standort", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Letzten {days} Tage", "map_settings_date_range_option_year": "Letztes Jahr", "map_settings_date_range_option_years": "Letzten {years} Jahre", + "map_settings_dialog_title": "Karteneinstellungen", "map_settings_include_show_archived": "Archivierte anzeigen", "map_settings_include_show_partners": "Partner einbeziehen", "map_settings_only_show_favorites": "Nur Favoriten anzeigen", "map_settings_theme_settings": "Kartendesign", + "map_zoom_to_see_photos": "Ansicht verkleinern um Fotos zu sehen", "mark_all_as_read": "Alle als gelesen markieren", + "mark_as_read": "Als gelesen markieren", "marked_all_as_read": "Alle als gelesen markiert", "matches": "Treffer", "matching_assets": "Passende Dateien", - "media_chrome": { - "auto": "Auto", - "captions": "Untertitel", - "captions_off": "Aus", - "closed_captions": "Untertitel", - "decode_error": "Dekodierungsfehler", - "disable_captions": "Untertitel deaktivieren", - "enable_captions": "Untertitel aktivieren", - "enter_fullscreen_mode": "Vollbildmodus aktivieren", - "exit_fullscreen_mode": "Vollbildmodus beenden", - "loop": "Endlosschleife", - "media_error_description": "Ein Medienfehler hat die Wiedergabe abgebrochen. Das Medium kÃļnnte beschädigt sein oder Ihr Browser unterstÃŧtzt dieses Format nicht.", - "media_loading": "Medien werden geladen", - "mute": "Stumm schalten", - "network_error": "Netzwerkfehler", - "network_error_description": "Ein Netzwerkfehler hat dazu gefÃŧhrt, dass der Medien-Download fehlgeschlagen ist.", - "not_supported_error": "Quelle nicht unterstÃŧtzt", - "playback_rate": "Wiedergabegeschwindigkeit", - "playback_rate_current": "aktuelle Wiedergabegeschwindigkeit", - "playback_rate_value": "Wiedergabegeschwindigkeit {playbackRate}", - "playback_time": "Wiedergabezeit", - "quality": "Qualität", - "second": "Sekunde", - "seconds": "Sekunden", - "time_value_of_total_time": "{currentTime} von {totalTime}", - "time_value_remaining": "{time} verbleibend", - "unmute": "Stummschaltung aufheben", - "unsupported_error_description": "Ein nicht unterstÃŧtzter Fehler ist aufgetreten. Der Server oder das Netzwerk ist fehlgeschlagen, oder Ihr Browser unterstÃŧtzt dieses Format nicht.", - "video_not_loaded_unknown_time": "Video nicht geladen, unbekannte Zeit.", - "video_player": "Videoplayer", - "volume": "Lautstärke" - }, "media_type": "Medientyp", "memories": "Erinnerungen", "memories_all_caught_up": "Alles aufgeholt", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Willst du diese Personen zusammenfÃŧhren? Diese Aktion kann nicht rÃŧckgängig gemacht werden.", "merge_people_successfully": "Personen erfolgreich zusammengefÃŧhrt", "merged_people_count": "{count, plural, one {# Person} other {# Personen}} zusammengefÃŧgt", - "minFaces": "Mindestanzahl Gesichter", - "minFaces_description": "Die Mindestanzahl an erkannten Gesichtern, damit eine Person angezeigt wird", "minimize": "Minimieren", "minute": "Minute", "minutes": "Minuten", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertikal", "missing": "Fehlende", "mobile_app": "Mobile App", - "mobile_app_download_onboarding_note": "Herunterladen der mobilen Begleiter-App Ãŧber eine der folgenden MÃļglichkeiten", + "mobile_app_download_onboarding_note": "Herunterladen der mobilen Begleiter-App Ãŧber einen der folgenden MÃļglichkeiten", "model": "Modell", - "modify_date": "Änderungsdatum", "month": "Monat", + "monthly_title_text_date_format": "MMMM y", "more": "Mehr", - "motion": "Bewegung", "move": "Verschieben", + "move_down": "Nach unten", "move_off_locked_folder": "Aus dem gesperrten Ordner verschieben", "move_to": "Verschieben nach", "move_to_device_trash": "In Papierkorb verschieben", "move_to_lock_folder_action_prompt": "{count} zum gesperrten Ordner hinzugefÃŧgt", "move_to_locked_folder": "In den gesperrten Ordner verschieben", "move_to_locked_folder_confirmation": "Diese Fotos und Videos werden aus allen Alben entfernt und kÃļnnen nur noch im gesperrten Ordner angezeigt werden", + "move_up": "Nach oben", + "moved_to_archive": "{count, plural, one {# Datei} other {# Dateien}} archiviert", + "moved_to_library": "{count, plural, one {# Datei} other {# Dateien}} in die Bibliothek verschoben", "moved_to_trash": "In den Papierkorb verschoben", + "multiselect_grid_edit_date_time_err_read_only": "Das Datum und die Uhrzeit von schreibgeschÃŧtzten Inhalten kann nicht verändert werden, Ãŧberspringen", + "multiselect_grid_edit_gps_err_read_only": "Der Aufnahmeort von schreibgeschÃŧtzten Inhalten kann nicht verändert werden, Ãŧberspringen", "mute_memories": "Erinnerungen stumm schalten", "my_albums": "Meine Alben", - "my_immich_description": "Diese Seite als „Mein Immich“-Link kopieren", - "my_immich_title": "Mein Immich-Link", "name": "Name", "name_or_nickname": "Name oder Nickname", "name_required": "Name ist erforderlich", @@ -1480,11 +1576,12 @@ "never": "Niemals", "new_album": "Neues Album", "new_api_key": "Neuer API-SchlÃŧssel", - "new_feature": "Neue Funktion", + "new_date_range": "Neuer Datumsbereich", "new_password": "Neues Passwort", "new_person": "Neue Person", "new_pin_code": "Neuer PIN-Code", "new_pin_code_subtitle": "Dies ist dein erster Zugriff auf den gesperrten Ordner. Erstelle einen PIN-Code fÃŧr den sicheren Zugriff auf diese Seite", + "new_timeline": "Neue Zeitleiste", "new_update": "Neues Update", "new_user_created": "Neuer Benutzer wurde erstellt", "new_version_available": "NEUE VERSION VERFÜGBAR", @@ -1492,6 +1589,7 @@ "next": "Weiter", "next_memory": "Nächste Erinnerung", "no": "Nein", + "no_actions_added": "Noch keine Aktionen hinzugefÃŧgt", "no_albums_found": "Keine Alben gefunden", "no_albums_message": "Erstelle ein Album, um deine Fotos und Videos zu organisieren", "no_albums_with_name_yet": "Es sieht so aus, als hättest du noch keine Alben mit diesem Namen.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Keine Geräte zum Übertragen gefunden", "no_checksum_local": "PrÃŧfsumme nicht verfÃŧgbar - kann lokale Datei/en nicht laden", "no_checksum_remote": "PrÃŧfsumme nicht verfÃŧgbar - kann entfernte Datei/en nicht laden", + "no_configuration_needed": "Keine Konfiguration benÃļtigt", "no_devices": "Keine verwendeten Geräte", "no_duplicates_found": "Es wurden keine Duplikate gefunden.", - "no_exif_info_available": "Keine Exif-Informationen vorhanden", + "no_exif_info_available": "Keine EXIF-Informationen vorhanden", "no_explore_results_message": "Lade weitere Fotos hoch, um deine Sammlung zu erkunden.", "no_favorites_message": "FÃŧge Favoriten hinzu, um deine besten Bilder und Videos schnell zu finden", + "no_filters_added": "Noch keine Filter hinzugefÃŧgt", "no_libraries_message": "Eine externe Bibliothek erstellen, um deine Fotos und Videos anzusehen", "no_local_assets_found": "Keine lokale Datei mit dieser PrÃŧfsumme gefunden", + "no_location_set": "Kein Standort festgelegt", "no_locked_photos_message": "Fotos und Videos im gesperrten Ordner sind versteckt und werden nicht angezeigt, wenn du deine Bibliothek durchsuchst.", "no_name": "Kein Name", "no_notifications": "Keine Benachrichtigungen", @@ -1518,19 +1619,14 @@ "no_results": "Keine Ergebnisse", "no_results_description": "Versuche es mit einem Synonym oder einem allgemeineren Stichwort", "no_shared_albums_message": "Erstelle ein Album, um Fotos und Videos mit Personen in deinem Netzwerk zu teilen", - "no_steps": "Noch keine Schritte hinzugefÃŧgt", + "no_uploads_in_progress": "Kein Upload in Bearbeitung", "none": "Keine", "not_allowed": "Nicht erlaubt", "not_available": "N/A", "not_in_any_album": "In keinem Album", "not_selected": "Nicht ausgewählt", - "not_set": "Nicht gesetzt", "notes": "Hinweise", "nothing_here_yet": "Noch nichts hier", - "notification_backup_reliability": "Benachrichtigungen aktivieren, um die Zuverlässigkeit der Sicherung im Hintergrund zu verbessern", - "notification_enabled_list_tile_content": "Immich nutzt Benachrichtigungen fÃŧr die Sicherung im Hintergund. Diese kÃļnnen in den Geräteeinstellungen verwaltet werden.", - "notification_enabled_list_tile_open_button": "Einstellungen Ãļffnen", - "notification_enabled_list_tile_title": "Benachrichtigungen aktiviert", "notification_permission_dialog_content": "Um Benachrichtigungen zu aktivieren, navigiere zu Einstellungen und klicke \"Erlauben\".", "notification_permission_list_tile_content": "Erlaube Berechtigung fÃŧr Benachrichtigungen.", "notification_permission_list_tile_enable_button": "Aktiviere Benachrichtigungen", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Obtainium Konfigurator", "obtainium_configurator_instructions": "Du kannst Obtainium benutzen, um die App direkt aus den Github Releases zu installieren oder zu aktualisieren. Bitte erstelle dazu einen API-SchlÃŧssel und wähle eine Variante aus um einen Obtainium-Konfigurationslink zu erstellen", "ocr": "OCR", - "ocr_body": "Immich liest jetzt den Text in deinen Fotos, sodass du nach ihrem Inhalt suchen kannst.", - "ocr_title": "Suche Text in deinen Fotos", "official_immich_resources": "Offizielle Immich Quellen", "offline": "Offline", "offset": "Verschiebung", @@ -1562,8 +1656,6 @@ "open": "Öffnen", "open_calendar": "Kalender Ãļffnen", "open_in_browser": "Im Browser Ãļffnen", - "open_in_immich_body": "Setze Immich als deine Galerie auf Android um auf Fotos von anderen Apps zugreifen zu kÃļnnen.", - "open_in_immich_title": "Öffne Fotos in Immich", "open_in_map_view": "In Kartenansicht Ãļffnen", "open_in_openstreetmap": "In OpenStreetMap Ãļffnen", "open_the_search_filters": "Die Suchfilter Ãļffnen", @@ -1572,10 +1664,10 @@ "organize_into_albums": "In Alben organisieren", "organize_into_albums_description": "Aktuelle Synchronisationseinstellungen verwenden, um existierende Fotos in Alben zu laden", "organize_your_library": "Organisiere deine Bibliothek", - "orientation": "Orientierung", "original": "Original", "other": "Sonstiges", "other_devices": "Andere Geräte", + "other_entities": "Andere Entitäten", "other_variables": "Sonstige Variablen", "owned": "Eigene", "owner": "Besitzer", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "auf alle deine Fotos und Videos, außer die Archivierten und GelÃļschten", "partner_can_access_location": "auf den Ort, an dem deine Fotos aufgenommen wurden", "partner_list_user_photos": "{user}s Fotos", + "partner_list_view_all": "Alle anzeigen", "partner_page_empty_message": "Deine Fotos sind noch nicht mit einem Partner geteilt.", "partner_page_no_more_users": "Keine weiteren Nutzer", + "partner_page_partner_add_failed": "Fehler beim Partner hinzufÃŧgen", "partner_page_select_partner": "Partner auswählen", + "partner_page_shared_to_title": "Geteilt mit", "partner_page_stop_sharing_content": "{partner} wird nicht mehr auf deine Fotos zugreifen kÃļnnen.", "partner_sharing": "Partner-Sharing", "partners": "Partner", @@ -1609,6 +1704,8 @@ "people": "Personen", "people_edits_count": "{count, plural, one {# Person} other {# Personen}} bearbeitet", "people_feature_description": "Fotos und Videos nach Personen gruppiert durchsuchen", + "people_selected": "{count, plural, one {# Person ausgewählt} other {# Personen ausgewählt}}", + "people_sidebar_description": "Eine VerknÃŧpfung zu Personen in der Seitenleiste anzeigen", "permanent_deletion_warning": "Warnung vor endgÃŧltiger LÃļschung", "permanent_deletion_warning_setting_description": "Anzeige einer Warnung beim endgÃŧltigen LÃļschen von Objekten", "permanently_delete": "EndgÃŧltig lÃļschen", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {# Datei} other {# Dateien}} endgÃŧltig gelÃļscht", "permission": "Berechtigung", "permission_empty": "Ihre Berechtigung sollte nicht leer sein", + "permission_onboarding_back": "ZurÃŧck", + "permission_onboarding_continue_anyway": "Trotzdem fortfahren", + "permission_onboarding_get_started": "Jetzt starten", + "permission_onboarding_go_to_settings": "Gehe zu Einstellungen", + "permission_onboarding_permission_denied": "Berechtigung verweigert. Um Immich zu benutzen, muss Zugriff auf Fotos und Videos in Einstellungen erlaubt werden.", + "permission_onboarding_permission_granted": "Berechtigung erteilt! Du bist startklar.", + "permission_onboarding_permission_limited": "Berechtigungen unzureichend. Um Immich das Sichern von ganzen Sammlungen zu ermÃļglichen, muss der Zugriff auf alle Fotos und Videos in den Einstellungen erlaubt werden.", + "permission_onboarding_request": "Immich benÃļtigt Berechtigung um auf deine Fotos und Videos zuzugreifen.", "person": "Person", "person_age_months": "{months, plural, one {# Monat} other {# Monate}} alt", "person_age_year_months": "1 Jahr, {months, plural, one {# Monat} other {# Monate}} alt", @@ -1625,10 +1730,13 @@ "person_birthdate": "Geboren am {date}", "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", "person_recognized": "Person erkannt", + "person_selected": "Person ausgewählt", "photo_shared_all_users": "Es sieht so aus, als hättest du deine Fotos mit allen Benutzern geteilt oder du hast keine Benutzer, mit denen du teilen kannst.", "photos": "Fotos", "photos_and_videos": "Fotos & Videos", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos von vorherigen Jahren", + "photos_only": "Nur Fotos", "pick_a_location": "Wähle einen Ort", "pick_custom_range": "Benutzerdefinierter Zeitraum", "pick_date_range": "Wähle einen Zeitraum", @@ -1641,14 +1749,12 @@ "places_count": "{count, plural, one {{count, number} Ort} other {{count, number} Orte}}", "play": "Abspielen", "play_memories": "Erinnerungen abspielen", - "play_motion_photo": "Live-Foto abspielen", + "play_motion_photo": "Bewegte Bilder abspielen", "play_or_pause_video": "Video abspielen oder pausieren", "play_original_video": "Originales Video abspielen", "play_original_video_setting_description": "Bevorzugen die Wiedergabe von Originalvideos gegenÃŧber transkodierten Videos. Wenn das Original nicht kompatibel ist, wird es mÃļglicherweise nicht korrekt wiedergegeben.", "play_transcoded_video": "Transkodiertes Video abspielen", "please_auth_to_access": "FÃŧr den Zugriff bitte Authentifizieren", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Mit dieser Methode lassen sich Ereignisse filtern und die AusfÃŧhrung nachfolgender Schritte bedingt verhindern", "port": "Port", "preferences_settings_subtitle": "App-Einstellungen verwalten", "preferences_settings_title": "Voreinstellungen", @@ -1665,11 +1771,11 @@ "privacy": "Privatsphäre", "profile": "Profil", "profile_drawer_app_logs": "Logs", + "profile_drawer_client_server_up_to_date": "Die App- und Server-Versionen sind aktuell", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "SchreibgeschÃŧtzter Modus aktiviert. Halte das Benutzer-Avatar-Symbol gedrÃŧckt, um den Modus zu verlassen.", "profile_image_of_user": "Profilbild von {user}", "profile_picture_set": "Profilbild gesetzt.", - "projection_type": "Projektionstyp", "public_album": "Öffentliches Album", "public_share": "Öffentliche Freigabe", "purchase_account_info": "UnterstÃŧtzer", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "UnterstÃŧtzerstatus", "purchase_individual_title": "Einzelperson", "purchase_input_suggestion": "Besitzen Sie bereits einen ProduktschlÃŧssel? Bitte geben Sie diesen unten ein", + "purchase_license_subtitle": "Kaufe Immich, um die fortlaufende Entwicklung zu unterstÃŧtzen", "purchase_lifetime_description": "Lebenslange GÃŧltigkeit", "purchase_option_title": "KAUFOPTIONEN", "purchase_panel_info_1": "Die Entwicklung von Immich erfordert viel Zeit und MÃŧhe und wir haben Vollzeit-Entwickler, die daran arbeiten Immich mÃļglichst perfekt zu machen. Unser Ziel ist es, Open-Source-Software und ethische Geschäftspraktiken zu einer verlässlichen Einkommensquelle fÃŧr Entwickler zu machen und ein datenschutzfreundliches Ökosystem mit echten Alternativen zu ausbeuterischen Cloud-Diensten zu schaffen.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# Datei wurde} other {# Dateien wurden}} einer neuen Person zugewiesen", "reassing_hint": "Markierte Dateien einer vorhandenen Person zuweisen", "recent": "Neueste", + "recent_albums": "Neueste Alben", "recent_searches": "Letzte Suchen", "recently_added": "KÃŧrzlich hinzugefÃŧgt", - "recently_added_body": "Öffne direkt deine neusten Änderungen direkt auf einer Seite.", - "recently_added_description": "Durchsuche deine Medien nachdem Sie auf Immich hochgeladen wurden", "recently_added_page_title": "Zuletzt hinzugefÃŧgt", - "recently_added_title": "Zuletzt hinzugefÃŧgt", "recently_taken": "KÃŧrzlich aufgenommen", + "recently_taken_page_title": "KÃŧrzlich aufgenommen", "refresh": "Aktualisieren", "refresh_encoded_videos": "Kodierte Videos aktualisieren", "refresh_faces": "Gesichter aktualisieren", "refresh_metadata": "Metadaten aktualisieren", "refresh_thumbnails": "Miniaturansichten aktualisieren", "refreshed": "Aktualisiert", + "refreshes_every_file": "Alle bestehenden und neuen Dateien erneut einlesen", "refreshing_encoded_video": "Kodierte Videos werden aktualisiert", "refreshing_faces": "Gesichter werden aktualisiert", "refreshing_metadata": "Metadaten werden aktualisiert", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Bist du sicher, dass du {count, plural, one {# Datei} other {# Dateien}} von diesem geteilten Link entfernen willst?", "remove_assets_title": "Dateien entfernen?", "remove_custom_date_range": "Benutzerdefinierten Datumsbereich entfernen", - "remove_filter": "Filter entfernen", + "remove_deleted_assets": "Offline-Dateien entfernen", "remove_from_album": "Aus Album entfernen", "remove_from_album_action_prompt": "{count} vom Album entfernt", "remove_from_favorites": "Aus Favoriten entfernen", @@ -1756,22 +1863,29 @@ "remove_memory": "Erinnerung entfernen", "remove_photo_from_memory": "Foto aus dieser Erinnerung entfernen", "remove_tag": "Tag entfernen", + "remove_url": "URL entfernen", "remove_user": "Nutzer entfernen", "removed_api_key": "API-SchlÃŧssel {name} wurde entfernt", "removed_from_archive": "Aus dem Archiv entfernt", "removed_from_favorites": "Aus den Favoriten entfernt", "removed_from_favorites_count": "{count, plural, other {#}} aus den Favoriten entfernt", "removed_memory": "Erinnerung entfernt", + "removed_photo_from_memory": "Foto aus Erinnerung entfernt", "removed_tagged_assets": "Tag von {count, plural, one {# Datei} other {# Dateien}} entfernt", "rename": "Umbenennen", + "repair": "Reparatur", + "repair_no_results_message": "Nicht auffindbare und fehlende Dateien werden hier angezeigt", + "replace_with_upload": "Durch Upload ersetzen", "repository": "Repository", "require_password": "Passwort erforderlich", + "require_user_to_change_password_on_first_login": "Benutzer muss das Passwort beim ersten Login ändern", "rescan": "Erneut scannen", "reset": "ZurÃŧcksetzen", "reset_password": "Passwort zurÃŧcksetzen", "reset_people_visibility": "Sichtbarkeit von Personen zurÃŧcksetzen", "reset_pin_code": "PIN-Code zurÃŧcksetzen", "reset_pin_code_description": "Falls du deinen PIN-Code vergessen hast, kannst du dich an den Server-Administrator wenden, um ihn zurÃŧckzusetzen", + "reset_pin_code_success": "PIN-Code erfolgreich zurÃŧckgesetzt", "reset_pin_code_with_password": "Mit deinem Passwort kannst du jederzeit deinen PIN-Code zurÃŧcksetzen", "reset_sqlite": "SQLite Datenbank zurÃŧcksetzen", "reset_sqlite_clear_app_data": "Daten lÃļschen", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Alle Duplikate aufgelÃļst", "restore": "Wiederherstellen", "restore_all": "Alle wiederherstellen", + "restore_trash_action_prompt": "{count} aus dem Papierkorb wiederhergestellt", "restore_user": "Nutzer wiederherstellen", "restored_asset": "Datei wiederhergestellt", "resume": "Fortsetzen", "resume_paused_jobs": "{count, plural, one {# Aufgabe fortsetzen } other {# Aufgaben fortsetzen}}", + "retry_upload": "Upload wiederholen", "review_duplicates": "Duplikate ÃŧberprÃŧfen", "review_large_files": "Große Dateien ÃŧberprÃŧfen", "role": "Rolle", @@ -1796,6 +1912,7 @@ "role_viewer": "Betrachter", "running": "Läuft", "save": "Speichern", + "save_to_gallery": "In Galerie speichern", "saved": "Gespeichert", "saved_api_key": "API-SchlÃŧssel wurde gespeichert", "saved_profile": "Profil gespeichert", @@ -1806,10 +1923,9 @@ "scan": "Scannen", "scan_all_libraries": "Alle Bibliotheken scannen", "scan_library": "Scannen", + "scan_settings": "Scan-Einstellungen", "scanning": "Scanne", "scanning_for_album": "Nach Alben scannen...", - "screencast_mode_description": "Tastatur- und Mausereignisindikatoren auf dem Bildschirm anzeigen", - "screencast_mode_title": "BildschirmÃŧbertragungsmodus umschalten", "search": "Suche", "search_albums": "Album suchen", "search_by_context": "Suche nach Kontext", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Wandern in Sapa", "search_by_filename": "Suche nach Dateiname oder -erweiterung", "search_by_filename_example": "z.B. IMG_1234.JPG oder PNG", - "search_by_full_path": "Suchen nach Ordnerpfad oder Ordner", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - Sie kÃļnnen nach Projekten, 3D, Drucken, 2026 usw. suchen.", "search_by_ocr": "Suche per OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Suche nach Kameralinse...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Ort auswählen", "search_filter_media_type": "Medientyp", "search_filter_media_type_title": "Medientyp auswählen", + "search_filter_ocr": "Suche per OCR", "search_filter_people_title": "Personen auswählen", "search_filter_star_rating": "Sternebewertung", "search_filter_tags_title": "Tags auswählen", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Keine Person mit dem Namen \"{name}\"", "search_no_result": "Keine Ergebnisse gefunden, probiere eine andere Kombination oder einen anderen Suchbegriff", "search_options": "Suchoptionen", + "search_page_categories": "Kategorien", + "search_page_motion_photos": "Live-Fotos", + "search_page_no_objects": "Keine Objektinformationen verfÃŧgbar", + "search_page_no_places": "Keine Informationen Ãŧber Orte verfÃŧgbar", + "search_page_screenshots": "Bildschirmfotos", "search_page_search_photos_videos": "Nach deinen Fotos und Videos suchen", + "search_page_selfies": "Selfies", + "search_page_things": "Gegenstände und Tiere", "search_page_view_all_button": "Alle anzeigen", + "search_page_your_activity": "Deine Aktivität", + "search_page_your_map": "Deine Karte", "search_people": "Suche nach Personen", "search_places": "Suche nach Orten", "search_rating": "Suche nach Bewertung...", + "search_result_page_new_search_hint": "Neue Suche", "search_settings": "Suche nach Einstellungen", "search_state": "Suche nach Bundesland / Provinz...", + "search_suggestion_list_smart_search_hint_1": "Intelligente Suche ist standardmäßig aktiviert; um nach Metadaten zu suchen, folgenden Syntax benutzen: ", + "search_suggestion_list_smart_search_hint_2": "m:dein-suchbegriff", "search_tags": "Suche nach Tags...", "search_timezone": "Suche nach Zeitzone...", "search_type": "Suche nach Typ", @@ -1868,6 +1995,7 @@ "select_albums": "Alben auswählen", "select_all": "Alles auswählen", "select_all_duplicates": "Alle Duplikate auswählen", + "select_all_in": "Alle in {group} auswählen", "select_avatar_color": "Avatar-Farbe auswählen", "select_count": "{count, plural, one {Wähle #} other {Wähle #}}", "select_cutoff_date": "Stichtag auswählen", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Anzeigebild auswählen", "select_from_computer": "Vom Computer auswählen", "select_keep_all": "Alle behalten", + "select_library_owner": "Bibliotheksbesitzer auswählen", "select_new_face": "Neues Gesicht auswählen", "select_people": "Personen auswählen", "select_person": "Person auswählen", "select_person_to_tag": "Wählen Sie eine Person zum Markieren aus", "select_photos": "Fotos auswählen", - "select_quality": "Qualität auswählen", "select_trash_all": "Alle lÃļschen", + "select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden", "selected": "Ausgewählt", "selected_count": "{count, plural, other {# ausgewählt}}", "selected_gps_coordinates": "Ausgewählte GPS-Koordinaten", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Original laden", "setting_image_viewer_preview_subtitle": "Aktivieren, um ein Bild mit mittlerer AuflÃļsung zu laden. Deaktivieren, um entweder das Original direkt zu laden oder nur das Vorschaubild zu verwenden.", "setting_image_viewer_preview_title": "Vorschaubild laden", + "setting_image_viewer_title": "Bilder", "setting_languages_apply": "Anwenden", "setting_languages_subtitle": "App-Sprache ändern", + "setting_notifications_notify_failures_grace_period": "Benachrichtigung bei Fehler(n) in der Hintergrundsicherung: {duration}", + "setting_notifications_notify_hours": "{count} Stunden", + "setting_notifications_notify_immediately": "sofort", "setting_notifications_notify_minutes": "{count} Minuten", + "setting_notifications_notify_never": "niemals", "setting_notifications_notify_seconds": "{count} Sekunden", + "setting_notifications_single_progress_subtitle": "Detaillierter Upload-Fortschritt fÃŧr jedes Element", + "setting_notifications_single_progress_title": "Zeige den detaillierten Fortschritt der Hintergrundsicherung", "setting_notifications_subtitle": "Benachrichtigungen anpassen", + "setting_notifications_total_progress_subtitle": "Gesamter Upload-Fortschritt (abgeschlossen/Anzahl Elemente)", + "setting_notifications_total_progress_title": "Zeige den Gesamtfortschritt der Hintergrundsicherung", "setting_video_viewer_auto_play_subtitle": "Videos automatisch wiedergeben sobald sie geÃļffnet werden", "setting_video_viewer_auto_play_title": "Videos automatisch wiedergeben", "setting_video_viewer_looping_title": "Video-Wiederholung", "setting_video_viewer_original_video_subtitle": "Beim Streaming eines Videos vom Server wird das Original abgespielt, auch wenn eine Transkodierung verfÃŧgbar ist. Kann zu Pufferung fÃŧhren. Lokal verfÃŧgbare Videos werden unabhängig von dieser Einstellung in Originalqualität wiedergegeben.", "setting_video_viewer_original_video_title": "Originalvideo erzwingen", "settings": "Einstellungen", + "settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden", "settings_saved": "Einstellungen gespeichert", "setup_pin_code": "Einen PIN-Code festlegen", "share": "Teilen", + "share_action_prompt": "{count} Dateien geteilt", + "share_add_photos": "Fotos hinzufÃŧgen", + "share_assets_selected": "{count} ausgewählt", "share_dialog_preparing": "Vorbereiten...", "share_link": "Link teilen", - "share_original": "Original verwenden (groß)", - "share_preview": "Thumbnail verwenden (klein)", - "share_quality_body": "Halte den Teilen Button gedrÃŧckt um die Bildqualität vor dem Teilen auszuwählen.", - "share_quality_title": "Wähle deine Qualität zum Teilen aus", "shared": "Geteilt", "shared_album_activities_input_disable": "Kommentare sind deaktiviert", "shared_album_activity_remove_content": "MÃļchtest du diese Aktivität entfernen?", "shared_album_activity_remove_title": "Aktivität entfernen", "shared_album_section_people_action_error": "Fehler beim Verlassen oder Entfernen aus dem Album", + "shared_album_section_people_action_leave": "Benutzer vom Album entfernen", + "shared_album_section_people_action_remove_user": "Benutzer von Album entfernen", "shared_album_section_people_title": "PERSONEN", "shared_by": "Geteilt von", "shared_by_user": "Von {user} geteilt", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} hochgeladen", "shared_link_app_bar_title": "Geteilte Links", "shared_link_clipboard_copied_massage": "Link kopiert", + "shared_link_clipboard_text": "Link: {link}\nPasswort: {password}", "shared_link_create_error": "Fehler beim Erstellen der Linkfreigabe", "shared_link_custom_url_description": "Greife Ãŧber eine benutzerdefinierte URL auf diesen Freigabelink zu", - "shared_link_custom_url_title": "Benutzerdefinierte URL", - "shared_link_custom_url_warning": "Warnung: eine benutzerdefinierte URL mit einem Schrägstrich verhält sich mÃļglicherweise nicht wie erwartet.", "shared_link_edit_description_hint": "Beschreibung eingeben", "shared_link_edit_expire_after_option_day": "1 Tag", "shared_link_edit_expire_after_option_days": "{count} Tagen", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Geteilt mit {partner}", "sharing": "Geteiltes", "sharing_enter_password": "Bitte gib das Passwort ein, um diese Seite anzuzeigen.", + "sharing_page_album": "Geteilte Alben", + "sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.", + "sharing_page_empty_list": "LEERE LISTE", + "sharing_sidebar_description": "Eine VerknÃŧpfung zu Geteiltem in der Seitenleiste anzeigen", + "sharing_silver_appbar_create_shared_album": "Neues geteiltes Album", + "sharing_silver_appbar_share_partner": "Mit Partner teilen", "shift_to_permanent_delete": "DrÃŧcke ⇧, um die Datei endgÃŧltig zu lÃļschen", "show_album_options": "Album-Optionen anzeigen", "show_albums": "Alben anzeigen", @@ -1995,9 +2140,7 @@ "show_in_timeline": "In Zeitleiste anzeigen", "show_in_timeline_setting_description": "Fotos und Videos dieses Benutzers in deiner Zeitleiste anzeigen", "show_keyboard_shortcuts": "TastaturkÃŧrzel anzeigen", - "show_less": "Weniger zeigen", "show_metadata": "Metadaten anzeigen", - "show_more_fields": "{count, plural, one {Zeige # weiteres Feld} other {Zeige # weitere Felder}}", "show_or_hide_info": "Informationen ein- oder ausblenden", "show_password": "Passwort anzeigen", "show_person_options": "Personen-Optionen anzeigen", @@ -2005,7 +2148,6 @@ "show_schema": "Schema anzeigen", "show_search_options": "Suchoptionen anzeigen", "show_shared_links": "Zeige geteilte Links", - "show_slideshow_metadata_overlay": "Bildinformationen anzeigen", "show_slideshow_transition": "Slideshow-Übergang anzeigen", "show_supporter_badge": "UnterstÃŧtzerabzeichen", "show_supporter_badge_description": "Zeige UnterstÃŧtzerabzeichen", @@ -2017,25 +2159,18 @@ "sign_out": "Abmelden", "sign_up": "Registrieren", "size": "GrÃļße", - "skip": "Überspringen", "skip_to_content": "Zum Inhalt springen", "skip_to_folders": "Springe zu Ordnern", "skip_to_tags": "Springe zu Tags", "slideshow": "Diashow", - "slideshow_body": "Lehn' dich zurÃŧck und genieße deine Fotos in einer Vollbild Diashow.", - "slideshow_metadata_overlay_mode": "Overlay Inhalt", - "slideshow_metadata_overlay_mode_description_only": "Nur Beschreibung", - "slideshow_metadata_overlay_mode_full": "Alles", "slideshow_repeat": "Slideshow wiederholen", "slideshow_repeat_description": "Wenn Slideshow beendet, zum Anfang zurÃŧckkehren", "slideshow_settings": "Diashow-Einstellungen", - "slideshow_title": "Diashow", - "smart_album": "Smart Album", - "some_assets_already_have_a_location_warning": "Einige der ausgewählten Dateien haben bereits einen Standort", "sort_albums_by": "Alben sortieren nach...", "sort_created": "Erstellungsdatum", "sort_items": "Anzahl der Einträge", - "sort_modified": "Datum geändert", + "sort_modified": "Änderungsdatum", + "sort_newest": "Neuestes Foto", "sort_oldest": "Ältestes Foto", "sort_people_by_similarity": "Personen nach Ähnlichkeit sortieren", "sort_recent": "Neuestes Foto", @@ -2044,6 +2179,7 @@ "stack": "Stapel", "stack_action_prompt": "{count} gestapelt", "stack_duplicates": "Duplikate stapeln", + "stack_select_one_photo": "Hauptfoto fÃŧr den Stapel auswählen", "stack_selected_photos": "Ausgewählte Fotos stapeln", "stacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} gestapelt", "stacktrace": "Stapelaufgaben", @@ -2052,12 +2188,8 @@ "start_date_before_end_date": "Anfangsdatum muss vor dem Enddatum liegen", "state": "Bundesland / Provinz", "status": "Status", - "step_delete": "Schritt lÃļschen", - "step_delete_confirm": "MÃļchten Sie diesen Schritt wirklich lÃļschen?", - "step_details": "Details zum Schritt", - "steps": "Schritte", "stop_casting": "Übertragung stoppen", - "stop_motion_photo": "Live-Foto stoppen", + "stop_motion_photo": "Stop-Motion-Foto", "stop_photo_sharing": "Deine Fotos nicht mehr teilen?", "stop_photo_sharing_description": "{partner} wird keinen Zugriff mehr auf deine Fotos haben.", "stop_sharing_photos_with_user": "AufhÃļren Fotos mit diesem Benutzer zu teilen", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Vertauschen der ZusammenfÃŧhrungsrichtung", "sync": "Synchronisieren", "sync_albums": "Alben synchronisieren", + "sync_albums_manual_subtitle": "Synchronisiere alle hochgeladenen Videos und Fotos in die ausgewählten Backup-Alben", "sync_local": "Lokal synchronisieren", "sync_remote": "mit Server synchronisieren", "sync_status": "Synchronisierungstatus", "sync_status_subtitle": "Synchronisierungssystem anzeigen und bearbeiten", "sync_upload_album_setting_subtitle": "Erstelle und lade deine ausgewählten Fotos und Videos in die ausgewählten Alben auf Immich hoch", - "system_theme": "Systemthema", - "system_theme_command_description": "Systemdesign verwenden ({value})", "tag": "Tag", "tag_assets": "Dateien taggen", "tag_created": "Tag erstellt: {tag}", "tag_face": "Gesicht taggen", "tag_feature_description": "Durchsuchen von Fotos und Videos, gruppiert nach logischen Tag-Themen", + "tag_not_found_question": "Kein Tag vorhanden? Erstelle einen neuen Tag.", "tag_people": "Personen taggen", "tag_updated": "Tag aktualisiert: {tag}", "tagged_assets": "{count, plural, one {# Datei} other {# Dateien}} getagged", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Anzahl der Elemente pro Reihe ({count})", "theme_setting_colorful_interface_subtitle": "Primärfarbe auf App-Hintergrund anwenden.", "theme_setting_colorful_interface_title": "Farbige Benutzeroberfläche", + "theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters", + "theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters", "theme_setting_primary_color_subtitle": "Farbauswahl fÃŧr primäre Aktionen und Akzente.", "theme_setting_primary_color_title": "Primärfarbe", "theme_setting_system_primary_color_title": "Systemfarbe verwenden", "theme_setting_system_theme_switch": "Automatisch (Systemeinstellung)", + "theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App", + "theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhÃļht allerdings den Datenverbrauch deutlich", + "theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren", "then": "Dann", "they_will_be_merged_together": "Sie werden zusammengefÃŧhrt", "third_party_resources": "Drittanbieter-Quellen", @@ -2131,17 +2268,23 @@ "trash_all": "Alle lÃļschen", "trash_count": "Papierkorb {count, number}", "trash_delete_asset": "Datei lÃļschen/in den Papierkorb verschieben", + "trash_emptied": "Geleerter Papierkorb", "trash_no_results_message": "GelÃļschte Fotos und Videos werden hier angezeigt.", "trash_page_delete_all": "Alle lÃļschen", + "trash_page_empty_trash_dialog_content": "Elemente im Papierkorb lÃļschen? Diese Elemente werden dauerhaft aus Immich entfernt", "trash_page_info": "Elemente im Papierkorb werden nach {days} Tagen endgÃŧltig gelÃļscht", + "trash_page_no_assets": "Es gibt keine Daten im Papierkorb", + "trash_page_restore_all": "Alle wiederherstellen", + "trash_page_select_assets_btn": "Elemente auswählen", + "trash_page_title": "Papierkorb ({count})", "trashed_items_will_be_permanently_deleted_after": "Objekte im Papierkorb werden nach {days, plural, one {# Tag} other {# Tagen}} endgÃŧltig gelÃļscht.", "trigger": "AuslÃļser", - "trigger_asset_metadata_extraction": "Extraktion von Asset-Metadaten", - "trigger_asset_metadata_extraction_description": "Wird ausgelÃļst, wenn die EXIF-Daten eines Assets extrahiert werden", - "trigger_asset_uploaded": "Datei hochladen", + "trigger_asset_uploaded": "Datei hochgeladen", "trigger_asset_uploaded_description": "LÃļst aus, wenn eine neue Datei hochgeladen wurde", + "trigger_description": "Ein Ereignis, das den Workflow startet", "trigger_person_recognized": "Person erkannt", - "trigger_person_recognized_description": "LÃļst aus, wenn eine Person erkannt wurde", + "trigger_person_recognized_description": "LÃļst aus, wenn eine Person erkannt wird", + "trigger_type": "AuslÃļser-Typ", "troubleshoot": "Fehler beheben", "type": "Typ", "unable_to_change_pin_code": "PIN-Code konnte nicht geändert werden", @@ -2157,7 +2300,6 @@ "unknown": "Unbekannt", "unknown_country": "Unbekanntes Land", "unknown_date": "Unbekanntes Datum", - "unknown_schema": "Unbekanntes Schema", "unknown_year": "Unbekanntes Jahr", "unlimited": "Unlimitiert", "unlink_motion_video": "VerknÃŧpfung zum Bewegungsvideo aufheben", @@ -2170,20 +2312,23 @@ "unsaved_change": "Ungespeicherte Änderung", "unselect_all": "Alles abwählen", "unselect_all_duplicates": "Alle Duplikate abwählen", + "unselect_all_in": "Alle in {group} abwählen", "unstack": "Entstapeln", "unstack_action_prompt": "{count} entstapelt", "unstacked_assets_count": "{count, plural, one {# Datei} other {# Dateien}} entstapelt", "unsupported_field_type": "Nicht unterstÃŧtzter Feldtyp", "unsupported_file_type": "Die Datei {file} kann nicht hochgeladen werden, da der Dateityp {type} nicht unterstÃŧtzt wird.", "untagged": "Ohne Tag", + "untitled_workflow": "Unbenannter Workflow", "up_next": "Weiter", "update_location_action_prompt": "Aktualisiere den Ort von {count} ausgewählten Dateien mit:", "updated_at": "Aktualisiert", "updated_password": "Passwort aktualisiert", "upload": "Hochladen", "upload_concurrency": "Parallelität beim Hochladen", - "upload_day_count": "{date}: {count, plural, one {# Upload} other {# Uploads}}", "upload_details": "Upload Details", + "upload_dialog_info": "Willst du die ausgewählten Elemente auf dem Server sichern?", + "upload_dialog_title": "Element hochladen", "upload_error_with_count": "Uploadfehler fÃŧr {count, plural, one {# asset} other {# assets}}", "upload_errors": "Hochladen mit {count, plural, one {# Fehler} other {# Fehlern}} abgeschlossen, aktualisiere die Seite, um neu hochgeladene Dateien zu sehen.", "upload_finished": "Upload fertig", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Fehler", "upload_status_uploaded": "Hochgeladen", "upload_success": "Hochladen erfolgreich. Aktualisiere die Seite, um neue hochgeladene Dateien zu sehen.", - "upload_to_album_body": "FÃŧr Nutzer*innen die nicht den manuellen Upload verwenden, du kannst auswählen neue lokale Fotos direkt in ein Album hinzufÃŧgen, sodass du sie nicht erst hochladen und dann zu einem Album hinzufÃŧgen musst.", - "upload_to_album_title": "Direkt in ein Album hochladen", "upload_to_immich": "Auf Immich hochladen ({count})", "uploading": "Wird hochgeladen", "uploading_media": "Medien werden hochgeladen", - "uploads": "Uploads", - "uploads_count": "{count, plural, one {# Upload} other {# Uploads}}", "url": "URL", "usage": "Verwendung", "use_biometric": "Biometrie verwenden", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Datum, Uhrzeit und Zahlen werden entsprechend den Einstellungen Ihres Browsers formatiert", "use_current_connection": "Aktuelle Verbindung verwenden", "use_custom_date_range": "Stattdessen einen benutzerdefinierten Datumsbereich verwenden", - "use_template": "Vorlage verwenden", "user": "Nutzer", "user_has_been_deleted": "Dieser Benutzer wurde gelÃļscht.", "user_id": "Nutzer-ID", @@ -2217,6 +2357,7 @@ "user_privacy": "Datenschutzeinstellungen Nutzer", "user_purchase_settings": "Kauf", "user_purchase_settings_description": "Kauf verwalten", + "user_role_set": "{user} als {role} festlegen", "user_usage_detail": "Nutzungsdetails der Nutzer", "user_usage_stats": "Statistiken zur Kontonutzung", "user_usage_stats_description": "Statistiken zur Kontonutzung anzeigen", @@ -2226,6 +2367,7 @@ "utilities": "Werkzeuge", "validate": "Validieren", "validate_endpoint_error": "Bitte gib eine gÃŧltige URL ein", + "validation_error": "Validierungsfehler", "variables": "Variablen", "version": "Version", "version_announcement_closing": "Dein Freund, Alex", @@ -2235,9 +2377,9 @@ "video": "Video", "video_hover_setting": "Videovorschau beim Hovern abspielen", "video_hover_setting_description": "Spiele die Miniaturansicht des Videos ab, wenn sich die Maus Ãŧber dem Element befindet. Auch wenn die Funktion deaktiviert ist, kann die Wiedergabe gestartet werden, indem du mit der Maus Ãŧber das Wiedergabesymbol fährst.", - "video_quality": "Videoqualität", "videos": "Videos", "videos_count": "{count, plural, one {# Video} other {# Videos}}", + "videos_only": "Nur Videos", "view": "Ansicht", "view_album": "Album anzeigen", "view_all": "Alles anzeigen", @@ -2246,29 +2388,29 @@ "view_details": "Details ansehen", "view_in_timeline": "In Zeitleiste anzeigen", "view_link": "Link anzeigen", + "view_links": "Links anzeigen", "view_name": "Ansicht", "view_next_asset": "Nächste Datei anzeigen", "view_previous_asset": "Vorherige Datei anzeigen", "view_qr_code": "QR code anzeigen", "view_similar_photos": "Zeige ähnliche Fotos an", "view_stack": "Stapel anzeigen", + "view_user": "Benutzer anzeigen", "viewer_remove_from_stack": "Aus Stapel entfernen", + "viewer_stack_use_as_main_asset": "An Stapelanfang", + "viewer_unstack": "Stapel aufheben", "visibility": "Sichtbarkeit", "visibility_changed": "Sichtbarkeit fÃŧr {count, plural, one {# Person} other {# Personen}} geändert", "visual": "Visuell", + "visual_builder": "Visueller Editor", "waiting": "Wartend", "waiting_count": "In Warteschlage: {count}", "warning": "Warnung", "week": "Woche", "welcome": "Willkommen", "welcome_to_immich": "Willkommen bei Immich", - "whats_new": "Neuigkeiten", - "whats_new_settings_subtitle": "Neues in Immich", - "whats_new_version": "Version {version}", - "when": "Wann", "width": "Breite", "wifi_name": "WLAN-Netzwerk", - "workflow": "Workflow", "workflow_delete_prompt": "Bist du sicher, dass du diesen Workflow lÃļschen willst?", "workflow_deleted": "Workflow gelÃļscht", "workflow_description": "Workflow-Beschreibung", @@ -2278,17 +2420,17 @@ "workflow_name": "Workflow-Name", "workflow_navigation_prompt": "Bist du sicher, dass du den Editor ohne zu speichern verlassen willst?", "workflow_summary": "Workflow-Zusammenfassung", - "workflow_templates": "Vorlagen fÃŧr Workflows", "workflow_update_success": "Workflow erfolgreich aktualisiert", + "workflow_updated": "Workflow aktualisiert", "workflows": "Workflows", "workflows_help_text": "Workflows automatisieren Aktionen auf deinen Dateien, basierend auf AuslÃļsern und Filtern", "wrong_pin_code": "PIN-Code falsch", - "x_of_total": "{x}/{total}", "year": "Jahr", "years_ago": "Vor {years, plural, one {einem Jahr} other {# Jahren}}", "yes": "Ja", "you_dont_have_any_shared_links": "Du hast keine geteilten Links", "your_wifi_name": "Dein WLAN-Netzwerk", "zero_to_clear_rating": "drÃŧcke 0 um die Dateibewertung zurÃŧckzusetzen", - "zoom_image": "Bild vergrÃļßern" + "zoom_image": "Bild vergrÃļßern", + "zoom_to_bounds": "Auf Grenzen zoomen" } diff --git a/i18n/de_CH.json b/i18n/de_CH.json index 90bd308514..5d25d2a142 100644 --- a/i18n/de_CH.json +++ b/i18n/de_CH.json @@ -1,170 +1,132 @@ { "about": "Über", "account": "Konto", - "account_settings": "Kontoistellige", - "acknowledge": "Bestätigä", + "account_settings": "Konto Istelligä", + "acknowledge": "Bestätige", "action": "Aktion", "action_common_update": "Update", - "action_description": "Aktionä, wo uf de gfilterte Medie usgfÃŧhrt wärde sÃļlled", - "actions": "Aktionä", + "action_description": "Es paar Aktione, wo a de gfilterete Assets usgfÃŧhrt wärde sÃļlled", + "actions": "Aktione", "active": "Aktiv", - "active_count": "Aktiv: {count}", + "active_count": "Aktivi: {count}", "activity": "Aktivität", - "add": "HinzuefÃŧge", - "add_a_description": "Beschribig afÃŧege", - "add_a_location": "Standort afÃŧege", - "add_a_name": "Namä hinzefÃŧgä", - "add_a_title": "Titel hinzufeÃŧgä", - "add_action": "Aktion hinzuefÃŧgä", - "add_assets": "Mediä hinzuefÃŧge", - "add_birthday": "Geburtstag hinzuefÃŧge", + "activity_changed": "Aktivität isch {enabled, select, true {aktiviert} other {deaktiviert}}", + "add": "HinzuefÃŧegä", + "add_a_description": "Beschriibig hinzuefÃŧege", + "add_a_location": "Standort hinzuefÃŧege", + "add_a_name": "Name hinzuefÃŧege", + "add_a_title": "Titel hinzuefÃŧege", + "add_action": "Aktion hinzuefÃŧege", + "add_action_description": "Aklicke um en Aktion dure zfÃŧehre", + "add_assets": "Assets hinzufÃŧege", + "add_birthday": "Geburtstag hinzuefÃŧege", "add_endpoint": "Endpunkt hinzuefÃŧge", - "add_exclusion_pattern": "Ausschlussmuster hinzufÃŧgen", - "add_location": "Standort hinzufÃŧgen", - "add_partner": "Partner hinzufÃŧgen", - "add_photos": "Fotos hinzufÃŧgen", - "add_step": "Schritt hinzuefÃŧege", - "add_tag": "Tag hinzufÃŧgen", - "add_to": "HinzufÃŧgen zuâ€Ļ", - "add_to_album": "Zu Album hinzufÃŧgen", - "add_to_album_bottom_sheet_added": "Zu {album} hinzugefÃŧgt", - "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", - "add_to_album_bottom_sheet_some_local_assets": "Einige lokale Dateien konnten nicht zum Album hinzugefÃŧgt werden", - "add_to_albums": "Zu Alben hinzufÃŧgen", - "add_to_albums_count": "Zu Alben hinzufÃŧgen ({count})", - "add_to_bottom_bar": "DäzuefÃŧege zu", - "add_upload_to_stack": "Upload zum Stapel hinzufÃŧgen", - "add_url": "URL däzuefÃŧege", - "added_to_archive": "Zum Archiv hinzugefÃŧgt", - "added_to_favorites": "Zu Favoriten hinzugefÃŧgt", - "added_to_favorites_count": "{count, number} zu Favoriten hinzugefÃŧgt", + "add_exclusion_pattern": "Uuschlussmuster hinzuefÃŧege", + "add_filter": "Filter hinzuefÃŧge", + "add_filter_description": "Klicke, um e Filterbedingig hinzuezfÃŧege", + "add_location": "Standort hinzuefÃŧege", + "add_more_users": "Meh Benutzer hinzuefÃŧege", + "add_partner": "Partner hinzuefÃŧege", + "add_path": "Pfad hinzuefÃŧege", + "add_photos": "FÃļteli hinzuefÃŧege", + "add_tag": "Tag hinzuefÃŧege", + "add_to": "HinzuefÃŧege zu â€Ļ", + "add_to_album": "Zum Album hinzuefÃŧege", + "add_to_album_bottom_sheet_added": "Zu {album} hinzuegfÃŧegt", + "add_to_album_bottom_sheet_already_exists": "Scho in {album}", + "add_to_album_bottom_sheet_some_local_assets": "Es hend es paar lokali Dateie nÃļd chÃļne im Album hinzuegfÃŧegt werde", + "add_to_album_toggle": "Uuswahl umschalte fÃŧr {album}", + "add_to_albums": "Zu Albe hinzuefÃŧege", + "add_to_albums_count": "Zu Albe hinzuefÃŧege ({count})", + "add_to_bottom_bar": "HinzuefÃŧege zu", + "add_to_shared_album": "Zum teilte Album hinzuefÃŧege", + "add_upload_to_stack": "Upload zum Stack hinzuefÃŧege", + "add_url": "URL hinzuefÃŧege", + "add_workflow_step": "Workflow-Schritt hinzuefÃŧege", + "added_to_archive": "Is Archiv verschobe", + "added_to_favorites": "Zu dine Favoritä hinzuegfÃŧegt", + "added_to_favorites_count": "{count, number} zu Favorite hinzuegfÃŧegt", "admin": { - "add_exclusion_pattern_description": "Ausschlussmuster hinzufÃŧgen. Platzhalter, wie *, **, und ? werden unterstÃŧtzt. Um alle Dateien in einem Verzeichnis namens „Raw“ zu ignorieren, „**/Raw/**“ verwenden. Um alle Dateien zu ignorieren, die auf „.tif“ enden, „**/*.tif“ verwenden. Um einen absoluten Pfad zu ignorieren, „/pfad/zum/ignorieren/**“ verwenden.", - "admin_user": "Administrator", - "asset_offline_description": "Diese Datei einer externen Bibliothek befindet sich nicht mehr auf der Festplatte und wurde in den Papierkorb verschoben. Falls die Datei innerhalb der Bibliothek verschoben wurde, ÃŧberprÃŧfe deine Zeitleiste auf die neue entsprechende Datei. Um diese Datei wiederherzustellen, stelle bitte sicher, dass Immich auf den unten stehenden Dateipfad zugreifen kann und scanne die Bibliothek.", - "authentication_settings": "Authentifizierungseinstellungen", - "authentication_settings_description": "Passwort-, OAuth- und andere Authentifizierungseinstellungen verwalten", - "authentication_settings_disable_all": "Bist du sicher, dass du alle Loginmethoden deaktivieren willst? Die Anmeldung wird vollständig deaktiviert.", - "authentication_settings_reenable": "Nutze einen Server-Befehl zur Reaktivierung.", - "background_task_job": "Hintergrundfarbä", - "backup_database": "Datenbanksicherung erstellen", - "backup_database_enable_description": "Datenbank regelmässig sichern", - "backup_keep_last_amount": "Anzahl der aufzubewahrenden frÃŧheren Sicherungen", - "backup_onboarding_1_description": "Offsite-Kopie in der Cloud oder an einem anderen physischen Ort.", - "backup_onboarding_2_description": "lokale Kopien auf verschiedenen Geräten. Dazu gehÃļren die Hauptdateien und eine lokale Sicherung dieser Dateien.", - "backup_onboarding_3_description": "Kopien deiner Daten inklusive Originaldateien. Dies umfasst 1 Kopie an einem anderen Ort und 2 lokale Kopien.", - "backup_onboarding_description": "Eine 3-2-1 Sicherungsstrategie wird empfohlen, um deine Daten zu schÃŧtzen. Du solltest sowohl Kopien deiner hochgeladenen Fotos/Videos als auch der Immich-Datenbank aufbewahren, um eine umfassende SicherungslÃļsung zu haben.", - "backup_onboarding_footer": "Weitere Informationen zum Sichern von Immich findest du in der Dokumentation.", - "backup_onboarding_parts_title": "Eine 3-2-1-Sicherung umfasst:", - "backup_onboarding_title": "Sicherige", - "backup_settings": "Einstellungen fÃŧr Datenbanksicherung", - "backup_settings_description": "Einstellungen zur regelmässigen Sicherung der Datenbank.", - "cleared_jobs": "Folgende Aufgaben zurÃŧckgesetzt: {job}", - "config_set_by_file": "Die Konfiguration ist aktuell durch eine Konfigurationsdatei gsetzt", - "confirm_delete_library": "Bist du sicher, dass du die Bibliothek {library} lÃļschen willst?", - "confirm_delete_library_assets": "Bist du sicher, dass du diese Bibliothek lÃļschen willst? Dies lÃļscht {count, plural, one {# enthaltene Datei} other {alle # enthaltenen Dateien}} aus Immich und kann nicht rÃŧckgängig gemacht werden. Die Dateien bleiben auf der Festplatte erhalten.", - "confirm_email_below": "Zum Bestätigen, tippe unten \"{email}\" ein", - "confirm_reprocess_all_faces": "Bist du sicher, dass du alle Gesichter erneut verarbeiten mÃļchtest? Dies lÃļscht auch alle bereits benannten Personen.", - "confirm_user_password_reset": "Bist du sicher, dass du das Passwort fÃŧr {user} zurÃŧcksetzen mÃļchtest?", - "confirm_user_pin_code_reset": "Bist du sicher, dass du den PIN-Code von {user} zurÃŧcksetzen mÃļchtest?", - "copy_config_to_clipboard_description": "Aktuelle Systemkonfiguration als JSON-Objekt in die Zwischenablage kopieren", - "create_job": "Ufgab erstelle", - "cron_expression": "Cron-Ausdruck", - "cron_expression_description": "Setze das Scanintervall im Cron-Format. FÃŧr mehr Informationen, siehe z. B. Crontab Guru", - "cron_expression_presets": "Vorlagen fÃŧr Cron-AusdrÃŧcke", - "disable_login": "Login deaktivierä", - "download_csv": "CSV herunterladen", - "duplicate_detection_job_description": "Verwendet maschinelles Lernen auf den Dateien, um Duplikate zu finden. Baut auf der intelligenten Suche auf", - "exclusion_pattern_description": "Mit Ausschlussmustern kÃļnnen Dateien und Ordner beim Scannen deiner Bibliothek ignoriert werden. Dies ist nÃŧtzlich, wenn du Ordner hast, die Dateien enthalten, die du nicht importieren mÃļchtest, wie z. B. RAW-Dateien.", - "export_config_as_json_description": "Aktuelle Systemkonfiguration als JSON-Datei herunterladen", - "external_libraries_page_description": "Externe Bibliotheksseite fÃŧr Administratoren", + "add_exclusion_pattern_description": "Uusschlussmuster hinzuefÃŧge. Platzhalter, wie *, **, und ? wärded understÃŧtzt. Zum all Dateie i eim Verzeichnis namens „Raw\" ignoriere, „**/Raw/**“ verwände. Zum all Dateien ignorieren, wo uf „.tif“ änded, „**/*.tif“ verwände. Zum en absolute Pfad ignoriere, „/pfad/zum/ignoriere/**“ verwände.", + "admin_user": "Admin Benutzer", + "asset_offline_description": "Die Datei vonere externe Bibliothek isch nÃŧmme uf de Festplatte und isch in Papierchorb verschobe worde. Falls die Datei innerhalb vo de Bibliothek verschoben worde isch, ÃŧberprÃŧf dini Ziitleiste uf die neui entsprechendi Datei. Zum die Datei wiederherstelle, stell bitte sicher, dass Immich uf de unde stehendi Dateipfad chan zuegriife und scann d'Bibliothek.", + "authentication_settings": "Authentifizierigs Iistellige", + "authentication_settings_description": "Passwort, OAuth und anderi Authentifizierigseinstellige verwalte", + "authentication_settings_disable_all": "Bisch sicher, dass du alli Login-Methodä wotsch deaktivierä? S Login isch denn komplett deaktiviert.", + "authentication_settings_reenable": "Bruuch ein Server-Befehl zum reaktiviere.", + "background_task_job": "Hintergrund Ufgabä", + "backup_database": "Datenbank-Dump aalege", + "backup_database_enable_description": "Datenbank-Dumps aktiviere", + "backup_keep_last_amount": "Aazahl vo de vorherige Dumps, wo bhalte werde sÃļlle", + "backup_onboarding_1_description": "Offsite-Kopie i dä Cloud oder amene andere physische Standort.", + "backup_onboarding_2_description": "Lokali Kopie uf verschiedene Grät. Das beinhaltet d Hauptdateie und e lokali Sicherig vo dene Dateie.", + "backup_onboarding_3_description": "Total aazahl vo dine Dateikopie, inklusiv d Originaldateie. Das beinhaltet 1 Offsite-Kopie und 2 lokali Kopie.", + "backup_onboarding_description": "E 3-2-1-Backup-Strategie wird empfohle, zum dini Dateie z schÃŧtze. Du sÃļttsch sowohl Kopie vo dine ufgeladene Fotos/Videos wie au d Immich-Datenbank bhalte, fÃŧr e rundum sauberi Backup-LÃļsig.", + "backup_onboarding_footer": "FÃŧr meh Infos zum Backup vo Immich lueg bitte i d Dokumentation.", + "backup_onboarding_parts_title": "Es 3-2-1-Backup beinhaltet:", + "backup_onboarding_title": "Backups", + "backup_settings": "Iistellige fÃŧr Datenbank-Dumps", + "backup_settings_description": "Datenbank-Dump-Iistellige verwalte.", + "cleared_jobs": "Jobs glÃļscht fÃŧr: {job}", + "config_set_by_file": "D Konfiguration isch aktuell dur e Konfigurationsdatei gsetzt", + "confirm_delete_library": "Bisch sicher, dass du d Bibliothek {library} wotsch lÃļsche?", + "confirm_delete_library_assets": "Bisch sicher, dass du die Bibliothek wotsch lÃļsche? Das lÃļscht {count, plural, one {# enthaltenes Asset} other {alli # enthaltene Assets}} us Immich und chan nÃļd rÃŧckgängig gmacht werde. D Dateie bliibed uf em Dateträger.", + "confirm_email_below": "Zum bestätige bitte \"{email}\" une iitippe", + "confirm_reprocess_all_faces": "Bisch sicher, dass du alli Gsichter neu verarbeite wotsch? Däbii werde au benannti Persone glÃļscht.", + "confirm_user_password_reset": "Bisch sicher, dass du s Passwort fÃŧr {user} mÃļchtisch zruggsetze?", + "confirm_user_pin_code_reset": "Bisch sicher, dass du de PIN-Code vo {user} mÃļchtisch zruggsetze?", + "copy_config_to_clipboard_description": "Kopier die aktuelli Systemkonfiguration als JSON-Objekt i d'ZwÃŧschenablage", + "create_job": "Uufgabe erstelle", + "cron_expression": "Cron-Ziitagabe", + "cron_expression_description": "Setz s Scanintervall im Cron-Format. Hilf mit däm Format bÃŧtet z. B. der Crontab Guru", + "cron_expression_presets": "Vorlage fÃŧr Cron-Uusdruck", + "disable_login": "Login deaktiviere", + "duplicate_detection_job_description": "Die Uufgab fÃŧehrt s maschinelle Lärne fÃŧr jedi Datei us, zum Duplikat finde. Die Uufgabe berueht uf de intelligente Suechi", + "exclusion_pattern_description": "Mit Uusschlussmuster chÃļnnd Dateie und Ordner bim Scanne vo dinere Bibliothek ignoriert wärde. Das isch nÃŧtzlich, wenn du Ordner häsch, wo Dateien drin händ, wo d nÃļd wotsch importiere, wie z. B. RAW-Dateie.", + "export_config_as_json_description": "Lad die aktuelli Systemkonfiguration als JSON-Datei abe", + "external_libraries_page_description": "Externi Bibliothekssiite fÃŧr Administratore", "face_detection": "Gsichtserkennig", - "face_detection_description": "Diese Aufgabe erkennt mit maschinellem Lernen Gesichter in Dateien. Bei Videos wird nur das Vorschaubild verwendet. „Aktualisieren“ verarbeitet alle Dateien neu. „ZurÃŧcksetzen“ setzt zusätzlich alle Gesichter zurÃŧck. „Fehlende“ fÃŧgt nur nicht verarbeitete Dateien in die Warteschlange ein. Erfasste Gesichter werden zur Gesichtsidentifizierung in die Warteschlange eingefÃŧgt, um sie in bestehende oder neue Personen zu gruppieren.", - "facial_recognition_job_description": "Diese Aufgabe gruppiert im Anschluss an die Gesichtserkennung die erkannten Gesichter zu Personen. „ZurÃŧcksetzen“ gruppiert alle Gesichter neu, während „Fehlende“ Gesichter ohne Zuordnung in die Warteschlange stellt.", - "failed_job_command": "Befehl {command} ist fÃŧr Aufgabe {job} fehlgeschlagen", - "force_delete_user_warning": "WARNUNG: Diese Aktion lÃļscht sofort den Benutzer und all seine Dateien. Dies kann nicht rÃŧckgängig gemacht werden und die Dateien kÃļnnen nicht wiederhergestellt werden.", + "face_detection_description": "Die Uufgab erfasst Gsichter in Dateien dur maschinells Lerne. Bi Video wird nur d'Miniaturasicht brucht. „Aktualisiere“ verarbeitet all Dateie neu. „Zruggsetze“ setzt au no all Gsichter zrugg. „Fehlendi“ stellt nur nÃļd verarbeiteti Dateie in d'Warteschlange. Erfassti Gsichter wärdet zur Gsichtsidentifizierig in diWarteschlange gstellt, damit sie i bestehendi oder neui Persone z'gruppiere.", + "facial_recognition_job_description": "Die Uufgabe gruppiert im Anschluss an d'Gsichtserfassig die erfasste Gsichter zu Persone. „Zruggsetze“ gruppiert alli Gsichter neu und mit „Fehlendi“ werdet Gsichter ohni Zuordnig i d'Warteschlange gstellt.", + "failed_job_command": "Befehl {command} hät fÃŧr d'Uufgabe {job} nÃļd funktioniert", + "force_delete_user_warning": "WARNIG: Die Aktion lÃļscht dä Benutzer und all sini Dateie. Das chann nÃļd rÃŧckgängig gmacht wärde und d'Dateie chÃļnnd nÃļd wiederhergstellt wärde.", "image_format": "Format", - "image_format_description": "WebP erzeugt kleinere Dateien als JPEG, ist aber etwas langsamer in der Erstellung.", - "image_fullsize_description": "HochauflÃļsendes Bild mit entfernten Metadaten, das beim Zoomen verwendet wird", - "image_fullsize_enabled": "HochauflÃļsende Vorschaubilder aktivieren", - "image_fullsize_enabled_description": "Generiere Vorschaubilder in OriginalauflÃļsung fÃŧr nicht web-kompatible Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.", + "image_format_description": "WebP erzeugt chlineri Dateie we JPEG, isch aber es bitz langsamer i de Erstellig.", + "image_fullsize_description": "HochuflÃļsends Bild mit glÃļschte Metadate, wo bim Zoome brucht wird", + "image_fullsize_enabled": "HochuflÃļsendi Vorschaubilder aktiviere", + "image_fullsize_enabled_description": "Generiere hochauflÃļsende Vorschaubilder in OriginalauflÃļsung fÃŧr nicht web-kompatibel Formate. Wenn \"Eingebettete Vorschau bevorzugen\" aktiviert ist, werden eingebettete Vorschaubilder direkt verwendet. Hat keinen Einfluss auf web-kompatible Formate wie JPEG.", "image_fullsize_quality_description": "Qualität der hochauflÃļsenden Vorschaubilder von 1-100. HÃļher ist besser, erzeugt aber grÃļssere Dateien.", "image_fullsize_title": "HochauflÃļsende Vorschaueinstellungen", "image_prefer_embedded_preview": "Eingebettete Vorschau bevorzugen", "image_prefer_embedded_preview_setting_description": "Verwende eingebettete Vorschaubilder in RAW-Fotos als Grundlage fÃŧr die Bildverarbeitung, sofern diese zur VerfÃŧgung stehen. Dies kann bei einigen Bildern genauere Farben erzeugen, allerdings ist die Qualität der Vorschau kameraabhängig und das Bild kann mehr Kompressionsartefakte aufweisen.", - "image_prefer_wide_gamut": "weiterer Farbraum bevorzugen", - "image_prefer_wide_gamut_setting_description": "Display P3 (DCI-P3) fÃŧr Vorschaubilder verwenden. Dadurch bleibt die Lebendigkeit von Bildern mit breiten Farbräumen besser erhalten, aber die Bilder kÃļnnen auf älteren Geräten mit einer älteren Browserversion etwas anders aussehen. sRGB-Bilder werden im sRGB-Format belassen, um Farbverschiebungen zu vermeiden.", - "image_preview_description": "Mittelgrosses Bild mit entfernten Metadaten, das bei der Betrachtung einer einzelnen Datei und fÃŧr maschinelles Lernen verwendet wird", - "image_preview_quality_description": "Vorschauqualität von 1-100. Ein hÃļherer Wert ist besser, erzeugt dadurch aber grÃļssere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen. Ein niedriger Wert kann dafÃŧr aber die Qualität des maschinellen Lernens beeinträchtigen.", - "image_preview_title": "Vorschaueinstellungen", + "image_prefer_wide_gamut": "Breites Spektrum bevorzugen", + "image_prefer_wide_gamut_setting_description": "Bruuch Display P3 fÃŧr Vorschaubildli. Das erhaltet d'Vitalität von Bildli mit grossem Farbruum besser. Uf alte Grät mit alte Browser chann das aber andersch uusgseh. sRGB-Bildli wärdet als sRGB bhalte zum Farbänderige vermiide.", + "image_preview_description": "Mittelgrossi Bildli ohni Metadate, bruuchts fÃŧr Einzelaasichte und fÃŧrs maschinelle Lärne", + "image_preview_quality_description": "Vorschauqualität vo 1-100. HÃļcher isch besser, git aber grÃļsseri Dateie und chan d'App Schwuppdizität reduziere. Z tÃŧffi Wert chÃļnnd s maschinelle Lärne beiträchtige.", + "image_preview_title": "Vorschauiistellige", "image_progressive": "Fortlaufend", - "image_progressive_description": "JPEG-Bilder schrittweise kodieren, um ein stufenweises Laden zu ermÃļglichen. Dies hat keine Auswirkungen auf WebP-Bilder.", + "image_progressive_description": "Codier fortlaufendi JPEG-Bildi: Sie wärdet bim Lade aufbauend aazeiget. Das hät kei WÃŧrkig uf WebP-Bildi.", "image_quality": "Qualität", - "image_resolution": "UflÃļsig", - "image_resolution_description": "HÃļhere AuflÃļsungen kÃļnnen mehr Details erhalten, benÃļtigen aber mehr Zeit fÃŧr die Kodierung, haben grÃļssere DateigrÃļssen und kÃļnnen die Reaktionsfähigkeit der App beeinträchtigen.", - "image_settings": "Bildeinstellungen", - "image_settings_description": "Qualität und AuflÃļsung der generierten Bilder verwalten", - "image_thumbnail_description": "Kleines Vorschaubild mit entfernten Metadaten, die bei der Anzeige von Sammlungen von Fotos wie der Zeitleiste verwendet wird", - "image_thumbnail_quality_description": "Qualität der Vorschaubilder von 1-100. HÃļher ist besser, erzeugt aber grÃļssere Dateien und kann die Reaktionsfähigkeit der App beeinträchtigen.", - "image_thumbnail_title": "Einstellungen fÃŧr Vorschaubilder", - "import_config_from_json_description": "Systemkonfiguration von hochgeladener JSON-Konfigurationsdatei importieren", - "job_concurrency": "{job} (Anzahl gleichzeitig laufende Prozesse)", - "job_created": "Aufgabe erstellt", - "job_not_concurrency_safe": "Diese Aufgabe kann nicht mehrmals parallel laufen gelassen werden.", - "job_settings": "Aufgabeneinstellungen", - "job_settings_description": "Gleichzeitige AusfÃŧhrung von Aufgaben verwalten", - "jobs_delayed": "{jobCount, plural, other {# verzÃļgert}}", - "jobs_failed": "{jobCount, plural, other {# fehlgeschlagen}}", - "jobs_over_time": "Jobs im Laufe der Zeit", + "image_resolution": "UuflÃļsig", + "image_resolution_description": "HÃļcheri UuflÃļsig erhaltet meh Detail, gaht aber länger zum codiere, macht grÃļsseri Dateie und chan d'App Schuppdizität reduziere.", + "image_settings": "Bild-Iistellige", + "image_settings_description": "Qualität und UuflÃļsig von erstellte Bildli verwalte", + "image_thumbnail_description": "Chlini Vorschaubildli ohni Metadate, bruuchts fÃŧr Aasichte mit Gruppe vo FÃļteli wie i de Hauptziitachse", + "image_thumbnail_quality_description": "Vorschauqualität vo 1-100. HÃļcher isch besser, git aber grÃļsseri Dateie und chan d'App Schwuppdizität reduziere.", + "image_thumbnail_title": "Iistellige fÃŧr Vorschaubildli", + "import_config_from_json_description": "Systemkonfiguration importiere durs Ufelade vonere JSON-Datei", + "job_concurrency": "{job} Näbeläufigkeit", + "job_created": "Uufgab erstellt", + "job_not_concurrency_safe": "Die Uufgabe ist nÃļd fÃŧr ParalleluusfÃŧhrig gmacht.", + "job_settings": "Uufgabe-Iistellige", + "job_settings_description": "Uufgabe-Näbeläufigkeit verwalte", + "jobs_over_time": "Uufgabe in ziitliche Verlauf", "library_created": "Bibliothek erstellt: {library}", - "library_deleted": "Bibliothek gelÃļscht", - "library_details": "Bibliotheksdetails", - "library_folder_description": "Wähle einen Ordner zum Importieren. Dieser Ordner wird inklusive Unterordnern nach Bildern und Videos durchsucht.", - "library_remove_exclusion_pattern_prompt": "Bilst du sicher, dass du dieses Ausschlussmuster entfernen mÃļchtest?", - "library_remove_folder_prompt": "Bist du sicher, dass du diesen Import-Ordner entfernen mÃļchtest?", - "library_scanning": "Regelmässiges Scannen", - "library_scanning_description": "Regelmässiges Scannen der Bibliothek konfigurieren", - "library_scanning_enable_description": "Regelmässiges Scannen der Bibliothek aktivieren", - "library_settings": "Externe Bibliothek", - "library_settings_description": "Einstellungen fÃŧr externe Bibliotheken verwalten", - "library_tasks_description": "Externe Bibliotheken nach neuen und/oder geänderten Assets durchsuchen", - "library_updated": "Aktualisierte Bibliothek", - "library_watching_enable_description": "Änderungen an Dateien in externen Bibliotheken Ãŧberwachen", - "library_watching_settings": "Bibliothek beobachten [EXPERIMENTELL]", - "library_watching_settings_description": "Automatisch nach geänderten Dateien suchen", - "logging_enable_description": "Logging aktivieren", - "logging_level_description": "Wenn aktiviert, welches Logging-Level soll verwendet werden.", - "logging_settings": "Logging", - "machine_learning_availability_checks": "VerfÃŧgbarkeitsÃŧberprÃŧfungen", - "machine_learning_availability_checks_description": "VerfÃŧgbare Machine-Learning-Server automatisch erkennen und bevorzugen", - "machine_learning_availability_checks_enabled": "VerfÃŧgbarkeitsÃŧberprÃŧfungen aktivieren", - "machine_learning_availability_checks_interval": "ÜberprÃŧfungsintervall", - "machine_learning_availability_checks_interval_description": "Intervall in Millisekunden zwischen VerfÃŧgbarkeitsÃŧberprÃŧfungen", - "machine_learning_availability_checks_timeout": "ZeitÃŧberschreitung der Anfrage", - "machine_learning_availability_checks_timeout_description": "Timeout in Millisekunden fÃŧr VerfÃŧgbarkeitsÃŧberprÃŧfungen", - "machine_learning_clip_model": "CLIP Model", - "machine_learning_clip_model_description": "Der Name eines der hier gelisteten CLIP-Modelle. Hinweis: Nach dem Ändern eines Modells muss der ‚Smart Search‘-Vorgang fÃŧr alle Bilder erneut gestartet werden.", - "machine_learning_duplicate_detection": "Duplikatserkennung", - "machine_learning_duplicate_detection_enabled": "Duplikatserkennung aktivieren", - "machine_learning_duplicate_detection_enabled_description": "Falls deaktiviert, werden exakt identische Assets weiterhin dedupliziert.", - "machine_learning_duplicate_detection_setting_description": "Verwende CLIP embeddings um wahrscheinliche Dublikate zu finden", - "machine_learning_enabled": "Maschinelles Lernen aktivieren", - "machine_learning_enabled_description": "Falls deaktiviert, werden alle ML-Funktionen deaktiviert, unabhängig von den untenstehenden Einstellungen.", - "machine_learning_facial_recognition": "Gesichtserkennung", - "machine_learning_facial_recognition_description": "Gesichter in Bildern erkennen, identifizieren und gruppieren", - "machine_learning_facial_recognition_model": "Model fÃŧr die Gesichtserkennung", - "machine_learning_facial_recognition_model_description": "Modell sind noch abstigender GrÃļssi ufglitet. GrÃļsseri Modell sind langsamer und bruched meh Arbeitsspeicher, aber produziered besseri Resultat. Gsichterkennig muss fÃŧr alli Fotis neu usgfÃŧehrt wärde, nochdem s Modell gwächslet worde esch.", - "machine_learning_facial_recognition_setting": "Gsichtserkennig ischalte", - "machine_learning_facial_recognition_setting_description": "Wenn usgschalte wärded Fotis ned fÃŧr Gsichtserkennig enkodiert und wärded ned ide Personesektion uf de", - "machine_learning_max_detection_distance": "Maximali Erkennigsdistanz", - "machine_learning_max_detection_distance_description": "Maximali Distanz zwÃŧsche zwei Bilder, um si als Duplikat z erkenne, zwische 0.001 - 0.1. HÃļcheri Wärt erkenned meh Duplikat, aber chÃļnd Falschpositivi erzÃŧge." - }, - "video_quality": "Videoqualität", - "videos": "Videos", - "view": "Aasicht", - "view_album": "Album aazeige", - "view_all": "Alles aazeige", - "view_all_users": "Alli Nutzer aazeige", - "view_details": "Details aaluege", - "view_link": "Link aazeige" + "library_deleted": "Bibliothek glÃļscht", + "library_details": "Bibliotheks-Details", + "library_folder_description": "Gib en Order zum Importiere a. Dä Order mit sine Underordner wird nach Bildli und Videos durchsucht.", + "library_remove_exclusion_pattern_prompt": "Bisch sicher, dass das Uuschluss-Muster wotsch lÃļsche?", + "library_remove_folder_prompt": "Bisch sicher, dass dä Import-Ordner wotsch lÃļsche?", + "library_scanning": "Regelmässigi ÜberprÃŧefig" + } } diff --git a/i18n/el.json b/i18n/el.json index c3505dbabb..8cd20d04a4 100644 --- a/i18n/el.json +++ b/i18n/el.json @@ -10,31 +10,39 @@ "active": "ΕÎŊÎĩĪÎŗÎŦ", "active_count": "ΕÎŊÎĩĪÎŗÎŦ: {count}", "activity": "Î”ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„Îą", + "activity_changed": "Η Î´ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„Îą ÎĩίÎŊιΚ {enabled, select, true {ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ} other {ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ}}", "add": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ", "add_a_description": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚", "add_a_location": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚", "add_a_name": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎĩÎŊĪŒĪ‚ ÎŋÎŊΌÎŧÎąĪ„ÎŋĪ‚", "add_a_title": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī„Î¯Ī„ÎģÎŋĪ…", "add_action": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎĩÎŊÎ­ĪÎŗÎĩÎšÎąĪ‚", + "add_action_description": "ΚÎŦÎŊĪ„Îĩ ÎēÎģΚÎē ÎŗÎšÎą ÎŊÎą ΀΁ÎŋĪƒÎ¸Î­ĪƒÎĩĪ„Îĩ ÎĩÎŊÎ­ĪÎŗÎĩΚι", "add_assets": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "add_birthday": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎŗÎĩÎŊÎĩθÎģÎ¯Ī‰ÎŊ", "add_endpoint": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī„ÎĩÎģΚÎēÎŋĪ ĪƒÎˇÎŧÎĩίÎŋĪ…", "add_exclusion_pattern": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎŧÎŋĪ„Î¯Î˛ÎŋĪ… ÎąĪ€ÎŋÎēÎģÎĩÎšĪƒÎŧÎŋĪ", + "add_filter": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī†Î¯Îģ΄΁ÎŋĪ…", + "add_filter_description": "ΚÎŦÎŊĪ„Îĩ ÎēÎģΚÎē ÎŗÎšÎą ÎŊÎą ΀΁ÎŋĪƒÎ¸Î­ĪƒÎĩĪ„Îĩ ĪƒĪ…ÎŊθΎÎēΡ Ī†Î¯Îģ΄΁ÎŋĪ…", "add_location": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚", + "add_more_users": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎĩĪ€ÎšĪ€ÎģέÎŋÎŊ Ī‡ĪÎˇĪƒĪ„ĪŽÎŊ", "add_partner": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ…ÎŊÎĩĪÎŗÎŦĪ„Îˇ", + "add_path": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Î´ÎšÎąÎ´ĪÎŋÎŧÎŽĪ‚", "add_photos": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", - "add_step": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ βΎÎŧÎąĪ„ÎŋĪ‚", "add_tag": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎĩĪ„ÎšÎēÎ­Ī„ÎąĪ‚", "add_to": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΃Îĩâ€Ļ", "add_to_album": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "add_to_album_bottom_sheet_added": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ ĪƒĪ„Îŋ {album}", "add_to_album_bottom_sheet_already_exists": "Ήδη ĪƒĪ„Îŋ {album}", "add_to_album_bottom_sheet_some_local_assets": "ÎŸĪÎšĪƒÎŧέÎŊÎą Ī„ÎŋĪ€ÎšÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą δÎĩÎŊ ÎŧĪ€ĪŒĪÎĩĪƒÎąÎŊ ÎŊÎą ΀΁ÎŋĪƒĪ„ÎĩθÎŋĪÎŊ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "add_to_album_toggle": "ΕÎŊÎąÎģÎģÎąÎŗÎŽ ÎĩĪ€ÎšÎģÎŋÎŗÎŽĪ‚ ÎŗÎšÎą Ī„Îŋ {album}", "add_to_albums": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "add_to_albums_count": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ({count})", "add_to_bottom_bar": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΃Îĩ", + "add_to_shared_album": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΃Îĩ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "add_upload_to_stack": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎąĪĪ‡ÎĩίÎŋĪ… ĪƒĪ„ÎˇÎŊ Îŋ΅΁ÎŦ", "add_url": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎŖĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…", + "add_workflow_step": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ βΎÎŧÎąĪ„ÎŋĪ‚ ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "added_to_archive": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ ĪƒĪ„Îŋ ÎąĪĪ‡ÎĩίÎŋ", "added_to_favorites": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ ĪƒĪ„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", "added_to_favorites_count": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {count, number} ĪƒĪ„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", @@ -73,7 +81,6 @@ "cron_expression_description": "ÎŸĪÎ¯ĪƒĪ„Îĩ Ī„Îŋ δΚÎŦĪƒĪ„ÎˇÎŧÎą ΃ÎŦĪĪ‰ĪƒÎˇĪ‚ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšĪŽÎŊĪ„ÎąĪ‚ Ī„Îˇ ÎŧÎŋĪĪ†ÎŽ cron. Για Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚, ÎąÎŊÎąĪ„ĪÎ­ÎžĪ„Îĩ Ī€.·. ĪƒĪ„Îŋ Crontab Guru", "cron_expression_presets": "Î ĪÎŋÎēιθÎŋĪÎšĪƒÎŧέÎŊÎĩĪ‚ ÎĩÎēΆ΁ÎŦ΃ÎĩÎšĪ‚ cron", "disable_login": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", - "download_csv": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ CSV", "duplicate_detection_job_description": "ΕÎēĪ„ÎĩÎģÎ­ĪƒĪ„Îĩ ÎŧÎˇĪ‡ÎąÎŊΚÎēÎŽ ÎŧÎŦÎ¸ÎˇĪƒÎˇ ΃Îĩ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŗÎšÎą ÎŊÎą ÎĩÎŊĪ„ÎŋĪ€Î¯ĪƒÎĩĪ„Îĩ Ī€ÎąĪĪŒÎŧÎŋΚÎĩĪ‚ ÎĩΚÎēΌÎŊÎĩĪ‚. Î’ÎąĪƒÎ¯ÎļÎĩĪ„ÎąÎš ĪƒĪ„ÎˇÎŊ ÎˆÎžĪ…Ī€ÎŊΡ ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ", "exclusion_pattern_description": "Τι ÎŧÎŋĪ„Î¯Î˛Îą ÎąĪ€ÎŋÎēÎģÎĩÎšĪƒÎŧÎŋĪ ĪƒÎąĪ‚ ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎŋĪ…ÎŊ ÎŊÎą ÎąÎŗÎŊÎŋÎĩÎ¯Ī„ÎąÎš ÎąĪĪ‡ÎĩÎ¯Îą ÎēιΚ Ī†ÎąÎēέÎģÎŋĪ…Ī‚ ÎēÎąĪ„ÎŦ Ī„Îˇ ΃ÎŦĪĪ‰ĪƒÎˇ Ī„ÎˇĪ‚ βΚβÎģΚÎŋθΎÎēÎˇĪ‚ ĪƒÎąĪ‚. Î‘Ī…Ī„ĪŒ ÎĩίÎŊιΚ Ī‡ĪÎŽĪƒÎšÎŧÎŋ ÎĩÎŦÎŊ Îĩ·ÎĩĪ„Îĩ Ī†ÎąÎēέÎģÎŋĪ…Ī‚ Ī€ÎŋĪ… Ī€ÎĩĪÎšÎ­Ī‡ÎŋĪ…ÎŊ ÎąĪĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… δÎĩÎŊ θέÎģÎĩĪ„Îĩ ÎŊÎą ÎĩÎšĪƒÎŦÎŗÎĩĪ„Îĩ, ĪŒĪ€Ī‰Ī‚ ÎąĪĪ‡ÎĩÎ¯Îą RAW.", "export_config_as_json_description": "ÎšÎąĪ„Î­Î˛ÎąĪƒÎĩ Ī„ÎˇÎŊ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎą δΚιÎŧĪŒĪĪ†Ī‰ĪƒÎˇ ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚ Ή΂ ÎąĪĪ‡ÎĩίÎŋ JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ΠÎŋÎšĪŒĪ„ÎˇĪ„Îą ÎŧΚÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚ ÎąĪ€ĪŒ 1 Î­Ī‰Ī‚ 100. ÎĨĪˆÎˇÎģĪŒĪ„Îĩ΁ÎĩĪ‚ Ī„ÎšÎŧÎ­Ī‚ ÎĩίÎŊιΚ ÎēÎąÎģĪĪ„Îĩ΁ÎĩĪ‚, ÎąÎģÎģÎŦ Ī€ÎąĪÎŦÎŗÎŋĪ…ÎŊ ÎŧÎĩÎŗÎąÎģĪĪ„ÎĩĪÎą ÎąĪĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎŧÎĩÎšĪŽĪƒÎŋĪ…ÎŊ Ī„ÎˇÎŊ Ī„ÎąĪ‡ĪĪ„ÎˇĪ„Îą ÎąĪ€ĪŒÎēĪÎšĪƒÎˇĪ‚ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚.", "image_thumbnail_title": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ΜιÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚", "import_config_from_json_description": "Î•ÎšĪƒÎąÎŗĪ‰ÎŗÎŽ δΚιÎŧĪŒĪĪ†Ī‰ĪƒÎˇĪ‚ ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚ ÎŧÎĩ ÎąÎŊÎ­Î˛ÎąĪƒÎŧÎą ÎąĪĪ‡ÎĩίÎŋĪ… δΚιÎŧĪŒĪĪ†Ī‰ĪƒÎˇĪ‚ JSON", - "integrity_checks_checksum_files": "Î‘ĪĪ‡ÎĩÎ¯Îą ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "integrity_checks_checksum_files_description": "ÎĄĪÎ¸ÎŧÎšĪƒÎˇ Ī„ÎŋĪ… ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "integrity_checks_checksum_files_enable_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī„ÎŋĪ… ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "integrity_checks_checksum_files_percentage_limit": "ÎŒĪÎšÎŋ Ī€Îŋ΃ÎŋĪƒĪ„ÎŋĪ", - "integrity_checks_checksum_files_percentage_limit_description": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒĪ„Îĩ Ī„Îŋ ÎŧÎ­ÎŗÎšĪƒĪ„Îŋ Ī€Îŋ΃ÎŋĪƒĪ„ĪŒ ÎŧÎĩĪ„ÎąÎžĪ 0,01 ÎēιΚ 1 ÎŗÎšÎą Ī„Îŋ Ī€ĪŒĪƒÎŋ θι ÎĩÎēĪ„ÎĩÎģÎĩÎ¯Ī„ÎąÎš Îŋ έÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚ ΃Îĩ ÎēÎŦθÎĩ δΚÎŦĪƒĪ„ÎˇÎŧÎą.", - "integrity_checks_checksum_files_time_limit": "Î§ĪÎŋÎŊΚÎēΌ ĪŒĪÎšÎŋ", - "integrity_checks_checksum_files_time_limit_description": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒĪ„Îĩ Ī„Îˇ ÎŧÎ­ÎŗÎšĪƒĪ„Îˇ δΚÎŦ΁ÎēÎĩΚι ÎŗÎšÎą Ī„ÎˇÎŊ ÎŋĪ€ÎŋÎ¯Îą θι ÎĩÎēĪ„ÎĩÎģÎĩÎ¯Ī„ÎąÎš Îŋ έÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚ ΃Îĩ ÎēÎŦθÎĩ δΚÎŦĪƒĪ„ÎˇÎŧÎą. (ms)", - "integrity_checks_missing_files": "ΕÎģÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą", - "integrity_checks_missing_files_description": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒĪ„Îĩ Ī„Îˇ ĪƒĪ…Ī‡ÎŊĪŒĪ„ÎˇĪ„Îą ÎēιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎŽ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎŋÎŊ έÎģÎĩÎŗĪ‡Îŋ ÎŗÎšÎą Ī„Îą ÎĩÎģÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą", - "integrity_checks_missing_files_enable_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎŗÎšÎą Ī„Îą ÎĩÎģÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą", - "integrity_checks_settings": "ΈÎģÎĩÎŗĪ‡ÎŋΚ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "integrity_checks_settings_description": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ Î´ÎšÎąĪƒĪ„ÎˇÎŧÎŦ΄ΉÎŊ ÎĩÎģÎ­ÎŗĪ‡Ī‰ÎŊ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "integrity_checks_untracked_files": "Î‘ĪĪ‡ÎĩÎ¯Îą Ī‡Ī‰ĪÎ¯Ī‚ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪÎ¸ÎˇĪƒÎˇ", - "integrity_checks_untracked_files_description": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒĪ„Îĩ Ī„Îˇ ĪƒĪ…Ī‡ÎŊĪŒĪ„ÎˇĪ„Îą ÎēιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎŽ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎŋÎŊ έÎģÎĩÎŗĪ‡Îŋ ÎŗÎšÎą Ī„Îą ÎŧΡ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŧÎĩÎŊÎą ÎąĪĪ‡ÎĩÎ¯Îą", - "integrity_checks_untracked_files_enable_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī„ÎŋĪ… ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎŗÎšÎą Ī„Îą ÎŧΡ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŧÎĩÎŊÎą ÎąĪĪ‡ÎĩÎ¯Îą", "job_concurrency": "Î¤ÎąĪ…Ī„ĪŒĪ‡ĪÎŋÎŊΡ ÎĩÎēĪ„Î­ÎģÎĩĪƒÎˇ {job}", "job_created": "Î•ĪÎŗÎąĪƒÎ¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ", "job_not_concurrency_safe": "Î‘Ī…Ī„ÎŽ Ρ ÎĩĪÎŗÎąĪƒÎ¯Îą δÎĩÎŊ ÎĩίÎŊιΚ ÎąĪƒĪ†ÎąÎģÎŽĪ‚ ÎŗÎšÎą Ī„ÎąĪ…Ī„ĪŒĪ‡ĪÎŋÎŊΡ ÎĩÎēĪ„Î­ÎģÎĩĪƒÎˇ.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Î­ÎžĪ…Ī€ÎŊÎˇĪ‚ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", "machine_learning_smart_search_enabled_description": "ΑÎŊ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡθÎĩί, ÎŋΚ ÎĩΚÎēΌÎŊÎĩĪ‚ δÎĩÎŊ θι ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋΚÎŋĪÎŊĪ„ÎąÎš ÎŗÎšÎą Î­ÎžĪ…Ī€ÎŊΡ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ.", "machine_learning_url_description": "Η δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ÎŧÎˇĪ‡ÎąÎŊΚÎēÎŽĪ‚ ÎŧÎŦÎ¸ÎˇĪƒÎˇĪ‚. ΑÎŊ δÎŋθÎŋĪÎŊ Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ ÎąĪ€ĪŒ ÎŧÎ¯Îą δΚÎĩĪ…Î¸ĪÎŊ΃ÎĩÎšĪ‚ URL, ÎēÎŦθÎĩ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽĪ‚ θι δÎŋÎēΚÎŧÎŦÎļÎĩĪ„ÎąÎš δΚιδÎŋĪ‡ÎšÎēÎŦ ÎŧÎ­Ī‡ĪÎš ÎŊÎą ÎąÎŊĪ„ÎąĪ€ÎŋÎēĪÎšÎ¸Îĩί έÎŊÎąĪ‚ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą, ÎŧÎĩ Ī„Îˇ ΃ÎĩÎšĪÎŦ ÎąĪ€ĪŒ Ī„ÎˇÎŊ Ī€ĪĪŽĪ„Îˇ Î­Ī‰Ī‚ Ī„ÎˇÎŊ Ī„ÎĩÎģÎĩĪ…Ī„ÎąÎ¯Îą. Οι δΚιÎēÎŋÎŧÎšĪƒĪ„Î­Ī‚ Ī€ÎŋĪ… δÎĩÎŊ ÎąÎŊĪ„ÎąĪ€ÎŋÎēĪÎ¯ÎŊÎŋÎŊĪ„ÎąÎš θι ÎąÎŗÎŊÎŋÎŋĪÎŊĪ„ÎąÎš ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŦ ÎŧÎ­Ī‡ĪÎš ÎŊÎą ÎĩĪ€ÎąÎŊέÎģθÎŋĪ…ÎŊ ΃Îĩ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą.", - "maintenance_backup_management": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "maintenance_delete_backup": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "maintenance_delete_backup_description": "Î‘Ī…Ī„ĪŒ Ī„Îŋ ÎąĪĪ‡ÎĩίÎŋ θι Î´ÎšÎąÎŗĪÎąĪ†Îĩί ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎēιΚ Ī‡Ī‰ĪÎ¯Ī‚ Î´Ī…ÎŊÎąĪ„ĪŒĪ„ÎˇĪ„Îą ÎĩĪ€ÎąÎŊÎąĪ†Îŋ΁ÎŦĪ‚.", "maintenance_delete_error": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Î´ÎšÎąÎŗĪÎąĪ†ÎŽĪ‚ Ī„ÎŋĪ… ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚.", - "maintenance_integrity_check": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚", - "maintenance_integrity_check_all": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ΌÎģΉÎŊ", - "maintenance_integrity_checksum_mismatch": "Î‘ĪƒĪ…ÎŧΆΉÎŊÎ¯Îą ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "maintenance_integrity_checksum_mismatch_description": "Î‘ĪĪ‡ÎĩÎ¯Îą ΄ΉÎŊ ÎŋĪ€ÎŋÎ¯Ī‰ÎŊ Îŋ έÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚ ĪƒĪ„Îŋ Î´Î¯ĪƒÎēÎŋ δÎĩÎŊ Ī„ÎąÎšĪÎšÎŦÎļÎĩΚ ÎŧÎĩ ÎĩÎēÎĩίÎŊÎŋÎŊ Ī€ÎŋĪ… Î­Ī‡ÎĩΚ ÎąĪ€ÎŋθΡÎēÎĩĪĪƒÎĩΚ Ī„Îŋ Immich ĪƒĪ„Îˇ βÎŦĪƒÎˇ δÎĩδÎŋÎŧέÎŊΉÎŊ Ī„ÎŋĪ….", - "maintenance_integrity_checksum_mismatch_job": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎŗÎšÎą ÎąĪƒĪ…ÎŧΆΉÎŊίÎĩĪ‚ ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "maintenance_integrity_checksum_mismatch_refresh_job": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎąÎŊÎąĪ†ÎŋĪĪŽÎŊ ÎąĪƒĪ…ÎŧΆΉÎŊÎšĪŽÎŊ ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "maintenance_integrity_missing_file": "ΕÎģÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą Î‘ĪĪ‡ÎĩÎ¯Îą", - "maintenance_integrity_missing_file_description": "Î‘ĪĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… Ī„Îŋ Immich Î­Ī‡ÎĩΚ ÎēÎąĪ„ÎąĪ‡Ī‰ĪÎ¯ĪƒÎĩΚ ĪƒĪ„Îˇ βÎŦĪƒÎˇ δÎĩδÎŋÎŧέÎŊΉÎŊ Ī„ÎŋĪ… ÎąÎģÎģÎŦ δÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ ĪƒĪ„Îŋ ĪƒĪĪƒĪ„ÎˇÎŧÎą ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ.", - "maintenance_integrity_missing_file_job": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎŗÎšÎą ÎĩÎģÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą", - "maintenance_integrity_missing_file_refresh_job": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎąÎŊÎąĪ†ÎŋĪĪŽÎŊ ÎĩÎģÎģÎĩÎšĪ€ĪŒÎŊ΄ΉÎŊ ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ", - "maintenance_integrity_report": "ΑÎŊÎąĪ†Îŋ΁ÎŦ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "maintenance_integrity_untracked_file": "Μη Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŧÎĩÎŊÎą ÎąĪĪ‡ÎĩÎ¯Îą", - "maintenance_integrity_untracked_file_description": "Î‘ĪĪ‡ÎĩÎ¯Îą ĪƒĪ„ÎŋĪ…Ī‚ Ī†ÎąÎēέÎģÎŋĪ…Ī‚ Ī„ÎŋĪ… Immich ÎŗÎšÎą Ī„Îą ÎŋĪ€ÎŋÎ¯Îą Ī„Îŋ Immich δÎĩÎŊ Î­Ī‡ÎĩΚ ÎēÎąÎŧÎ¯Îą ÎēÎąĪ„ÎąĪ‡ĪŽĪÎšĪƒÎˇ.", - "maintenance_integrity_untracked_file_job": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎŗÎšÎą ÎŧΡ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŧÎĩÎŊÎą ÎąĪĪ‡ÎĩÎ¯Îą", - "maintenance_integrity_untracked_file_refresh_job": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎąÎŊÎąĪ†ÎŋĪĪŽÎŊ ÎŗÎšÎą ÎŧΡ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŧÎĩÎŊÎą ÎąĪĪ‡ÎĩÎ¯Îą", "maintenance_restore_backup": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "maintenance_restore_backup_description": "ΤÎŋ Immich θι Î´ÎšÎąÎŗĪÎąĪ†Îĩί Ī€ÎģÎŽĪĪ‰Ī‚ ÎēιΚ θι ÎĩĪ€ÎąÎŊÎąĪ†ÎĩĪÎ¸Îĩί ÎąĪ€ĪŒ Ī„Îŋ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎŋ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚. Θι δΡÎŧΚÎŋĪ…ĪÎŗÎˇÎ¸Îĩί ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ Ī€ĪÎšÎŊ Ī„Îˇ ĪƒĪ…ÎŊÎ­Ī‡ÎĩΚι.", "maintenance_restore_backup_different_version": "Î‘Ī…Ī„ĪŒ Ī„Îŋ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ δΡÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ ÎŧÎĩ Î´ÎšÎąĪ†Îŋ΁ÎĩĪ„ÎšÎēÎŽ έÎēδÎŋĪƒÎˇ Ī„ÎŋĪ… Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ ÎŧÎ­ĪƒĪ‰ email", "notification_settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ", "notification_settings_description": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎˇĪƒÎˇ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ, ĪƒĪ…ÎŧĪ€ÎĩĪÎšÎģÎąÎŧβιÎŊÎŋÎŧέÎŊÎŋĪ… Ī„ÎŋĪ… email", - "oauth_allow_insecure_requests": "Να ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎŋÎŊĪ„ÎąÎš ÎŧΡ ÎąĪƒĪ†ÎąÎģÎŽ ÎąÎšĪ„ÎŽÎŧÎąĪ„Îą", - "oauth_allow_insecure_requests_description": "Î ÎĄÎŸÎ•Î™Î”ÎŸÎ ÎŸÎ™Î—ÎŖÎ—: Î‘Ī…Ī„ĪŒ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩί Ī„ÎˇÎŊ ÎĩĪ€ÎąÎģΎθÎĩĪ…ĪƒÎˇ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēĪŽÎŊ TLS ÎŗÎšÎą ÎąÎšĪ„ÎŽÎŧÎąĪ„Îą OAuth ÎēιΚ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ĪƒÎąĪ‚ ÎĩÎēÎ¸Î­ĪƒÎĩΚ ΃Îĩ ÎĩĪ€ÎšÎ¸Î­ĪƒÎĩÎšĪ‚ MITM.", "oauth_auto_launch": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎĩÎēÎēίÎŊÎˇĪƒÎˇ", "oauth_auto_launch_description": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎĩÎēÎēίÎŊÎšĪƒÎˇ Ī„ÎˇĪ‚ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎąĪ‚ OAuth ÎŧÎĩ Ī„ÎˇÎŊ Ī€ÎģÎŋÎŽÎŗÎˇĪƒÎˇ ĪƒĪ„ÎˇÎŊ ΃ÎĩÎģÎ¯Î´Îą ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", "oauth_auto_register": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎēÎąĪ„ÎąĪ‡ĪŽĪÎˇĪƒÎˇ", @@ -300,11 +274,9 @@ "oauth_button_text": "ΚÎĩίÎŧÎĩÎŊÎŋ ÎēÎŋĪ…ÎŧĪ€ÎšÎŋĪ", "oauth_client_secret_description": "Î‘Ī€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ÎŗÎšÎą έÎŧĪ€ÎšĪƒĪ„Îŋ Ī€ĪĪŒÎŗĪÎąÎŧÎŧÎą Ī€ÎĩÎģÎŦĪ„Îˇ ÎŽ ÎąÎŊ δÎĩÎŊ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩĪ„ÎąÎš PKCE (Proof Key for Code Exchange) ΃Îĩ δΡÎŧĪŒĪƒÎšÎŋ Ī€ĪĪŒÎŗĪÎąÎŧÎŧÎą Ī€ÎĩÎģÎŦĪ„Îˇ.", "oauth_enable_description": "ÎŖĪÎŊδÎĩĪƒÎˇ ÎŧÎĩ OAuth", - "oauth_end_session_url_description": "ΑÎŊÎąÎēÎąĪ„ÎĩĪÎ¸Ī…ÎŊĪƒÎˇ Ī„ÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ ΃Îĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ URI ĪŒĪ„ÎąÎŊ ÎąĪ€ÎŋĪƒĪ…ÎŊδέÎĩĪ„ÎąÎš.", "oauth_mobile_redirect_uri": "URI ΑÎŊÎąÎēÎąĪ„ÎĩĪÎ¸Ī…ÎŊĪƒÎˇĪ‚ ÎŗÎšÎą ÎēΚÎŊÎˇĪ„ÎŦ Ī„ÎˇÎģÎ­Ī†Ī‰ÎŊÎą", "oauth_mobile_redirect_uri_override": "Î ĪÎŋĪƒĪ€Î­ÎģÎąĪƒÎˇ URI ÎąÎŊÎąÎēÎąĪ„ÎĩĪÎ¸Ī…ÎŊĪƒÎˇĪ‚ ÎŗÎšÎą ÎēΚÎŊÎˇĪ„ÎŦ Ī„ÎˇÎģÎ­Ī†Ī‰ÎŊÎą", "oauth_mobile_redirect_uri_override_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îŋ ĪŒĪ„ÎąÎŊ Îŋ Ī€ÎŦ΁Îŋ·ÎŋĪ‚ OAuth δÎĩÎŊ ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎĩΚ ÎŧΚι URI ÎŗÎšÎą ÎēΚÎŊÎˇĪ„ÎŦ, ĪŒĪ€Ī‰Ī‚ Ī„Îŋ ''{callback}''", - "oauth_prompt_description": "Î ÎąĪÎŦÎŧÎĩ΄΁ÎŋĪ‚ ΀΁Îŋ΄΁ÎŋĪ€ÎŽĪ‚ (Ī€.·. ÎĩĪ€ÎšÎģÎŋÎŗÎŽ_ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧÎŋĪ, ĪƒĪÎŊδÎĩĪƒÎˇ, ĪƒĪ…ÎŊÎąÎ¯ÎŊÎĩĪƒÎˇ)", "oauth_role_claim": "ΑÎŊÎŦθÎĩĪƒÎˇ ΁ΌÎģÎŋĪ…", "oauth_role_claim_description": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ Ī€ÎąĪÎąĪ‡ĪŽĪÎˇĪƒÎˇ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ ÎŧÎĩ βÎŦĪƒÎˇ Ī„ÎˇÎŊ ĪĪ€ÎąĪÎžÎˇ ÎąĪ…Ī„ÎŽĪ‚ Ī„ÎˇĪ‚ ÎąÎŊÎŦθÎĩĪƒÎˇĪ‚. Η ÎąÎŊÎŦθÎĩĪƒÎˇ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎĩίÎŊιΚ ÎĩÎ¯Ī„Îĩ 'Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚' ÎĩÎ¯Ī„Îĩ 'Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽĪ‚'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Î•Ī€ÎąÎŊÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ΌÎģΉÎŊ ΄ΉÎŊ βΚβÎģΚÎŋθΡÎēĪŽÎŊ", "registration": "Î•ÎŗÎŗĪÎąĪ†ÎŽ Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ", "registration_description": "ΔÎĩδÎŋÎŧέÎŊÎŋĪ… ĪŒĪ„Îš ÎĩÎ¯ĪƒĪ„Îĩ Îŋ Ī€ĪĪŽĪ„ÎŋĪ‚ Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚ ĪƒĪ„Îŋ ĪƒĪĪƒĪ„ÎˇÎŧÎą, θι ÎąÎŊÎąĪ„ÎĩθÎĩÎ¯Ī„Îĩ Ή΂ Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽĪ‚ ÎēιΚ θι ÎĩÎ¯ĪƒĪ„Îĩ Ī…Ī€ÎĩĪÎ¸Ī…ÎŊÎŋĪ‚ ÎŗÎšÎą Ī„ÎšĪ‚ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎšÎēÎ­Ī‚ ÎĩĪÎŗÎąĪƒÎ¯ÎĩĪ‚, ÎĩÎŊĪŽ ÎŋΚ ÎĩĪ€ÎšĪ€ÎģέÎŋÎŊ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ θι δΡÎŧΚÎŋĪ…ĪÎŗÎŋĪÎŊĪ„ÎąÎš ÎąĪ€ĪŒ Îĩ΃ÎŦĪ‚.", - "release_channel_release_candidate": "ÎĨĪ€ÎŋĪˆÎŽĪ†ÎšÎą έÎēδÎŋĪƒÎˇ", - "release_channel_stable": "ÎŖĪ„ÎąÎ¸ÎĩĪÎŽ", "remove_failed_jobs": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎąĪ€ÎŋĪ„Ī…Ī‡ÎˇÎŧέÎŊΉÎŊ ÎĩĪÎŗÎąĪƒÎšĪŽÎŊ", "require_password_change_on_login": "Î‘Ī€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ÎąĪ€ĪŒ Ī„ÎŋÎŊ Ī‡ĪÎŽĪƒĪ„Îˇ ÎŊÎą ÎąÎģÎģÎŦΞÎĩΚ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚ ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ Ī€ĪĪŽĪ„Îˇ ĪƒĪÎŊδÎĩĪƒÎˇ", "reset_settings_to_default": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ΀΁ÎŋÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΉÎŊ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Î™ĪƒĪ‡ĪÎĩΚ ÎŧΌÎŊÎŋ ÎŗÎšÎą VAAPI ÎēιΚ QSV. ÎŸĪÎ¯ÎļÎĩΚ Ī„ÎŋÎŊ ÎēΌÎŧβÎŋ DRI Ī€ÎŋĪ… Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ÎŗÎšÎą Ī„ÎˇÎŊ ÎĩĪ€ÎšĪ„ÎŦ·΅ÎŊĪƒÎˇ Ī…ÎģΚÎēÎŋĪ ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ.", "transcoding_preset_preset": "Î ĪÎŋÎēιθÎŋĪÎšĪƒÎŧέÎŊΡ ĪĪÎ¸ÎŧÎšĪƒÎˇ (-preset)", "transcoding_preset_preset_description": "Î¤ÎąĪ‡ĪĪ„ÎˇĪ„Îą ĪƒĪ…ÎŧĪ€Î¯ÎĩĪƒÎˇĪ‚. Οι Ī€ÎšÎŋ ÎąĪÎŗÎ­Ī‚ ΀΁ÎŋÎēιθÎŋĪÎšĪƒÎŧέÎŊÎĩĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ Ī€ÎąĪÎŦÎŗÎŋĪ…ÎŊ ÎŧΚÎēĪĪŒĪ„ÎĩĪÎą ÎąĪĪ‡ÎĩÎ¯Îą ÎēιΚ βÎĩÎģĪ„ÎšĪŽÎŊÎŋĪ…ÎŊ Ī„ÎˇÎŊ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą ĪŒĪ„ÎąÎŊ ĪƒĪ„Îŋ·ÎĩĪÎŋĪ…ÎŊ ΃Îĩ έÎŊÎąÎŊ ĪƒĪ…ÎŗÎēÎĩÎēĪÎšÎŧέÎŊÎŋ ĪĪ…Î¸ÎŧΌ ÎŧÎĩĪ„ÎŦδÎŋĪƒÎˇĪ‚ (bitrate). Ο VP9 ÎąÎŗÎŊÎŋÎĩί Ī„ÎšĪ‚ Ī„ÎąĪ‡ĪĪ„ÎˇĪ„ÎĩĪ‚ Ī€ÎŦÎŊΉ ÎąĪ€ĪŒ Ī„ÎˇÎŊ 'Ī€ÎšÎŋ ÎŗĪÎŽÎŗÎŋĪÎˇ' (faster).", - "transcoding_realtime": "ΜÎĩĪ„ÎąĪ„ĪÎŋĪ€ÎŽ ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ [ΠΕΙΡΑΜΑΤΙΚΟ]", - "transcoding_realtime_description": "Î•Ī€ÎšĪ„ĪÎ­Ī€ÎĩΚ Ī„Îˇ ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŊÎą ÎĩÎēĪ„ÎĩÎģÎĩÎ¯Ī„ÎąÎš ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ ÎēÎąÎ¸ĪŽĪ‚ ÎŗÎ¯ÎŊÎĩĪ„ÎąÎš Ρ ΁ÎŋÎŽ Ī„ÎŋĪ… Î˛Î¯ÎŊĪ„ÎĩÎŋ. ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩί Ī„ÎˇÎŊ ÎąÎģÎģÎąÎŗÎŽ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„ÎąĪ‚, ÎąÎģÎģÎŦ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ΀΁ÎŋÎēÎąÎģÎ­ĪƒÎĩΚ ÎŧÎĩÎŗÎąÎģĪĪ„ÎĩĪÎˇ ÎēÎąÎ¸Ī…ĪƒĪ„Î­ĪÎˇĪƒÎˇ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚ ÎēιΚ ÎēÎŋÎģÎģÎŽÎŧÎąĪ„Îą, ÎąÎŊÎŦÎģÎŋÎŗÎą ÎŧÎĩ Ī„ÎšĪ‚ Î´Ī…ÎŊÎąĪ„ĪŒĪ„ÎˇĪ„ÎĩĪ‚ Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ.", - "transcoding_realtime_enabled": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚ ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ", - "transcoding_realtime_enabled_description": "ΑÎŊ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋ, Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽĪ‚ θι ÎąĪÎŊÎĩÎ¯Ī„ÎąÎš Ī„ÎˇÎŊ ÎĩÎēÎēίÎŊÎˇĪƒÎˇ ÎŊÎ­Ī‰ÎŊ ĪƒĪ…ÎŊÎĩÎ´ĪÎšĪŽÎŊ ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚ ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ.", - "transcoding_realtime_resolutions": "ΑÎŊÎąÎģĪĪƒÎĩÎšĪ‚", - "transcoding_realtime_resolutions_description": "Οι ÎąÎŊÎąÎģĪĪƒÎĩÎšĪ‚ Ī€ÎŋĪ… ΀΁ÎŋĪƒĪ†Î­ĪÎŋÎŊĪ„ÎąÎš ÎŗÎšÎą ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ. Οι Ī…ĪˆÎˇÎģĪŒĪ„Îĩ΁ÎĩĪ‚ ÎąÎŊÎąÎģĪĪƒÎĩÎšĪ‚ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ΀΁ÎŋÎēÎąÎģÎ­ĪƒÎŋĪ…ÎŊ ΀΁ÎŋβÎģÎŽÎŧÎąĪ„Îą ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚, ÎĩÎŦÎŊ Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽĪ‚ δÎĩÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą Ī„ÎšĪ‚ ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎšÎŽĪƒÎĩΚ ÎąĪÎēÎĩĪ„ÎŦ ÎŗĪÎŽÎŗÎŋĪÎą.", - "transcoding_realtime_video_codecs": "ÎšĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎšÎˇĪ„Î­Ī‚ Î˛Î¯ÎŊĪ„ÎĩÎŋ", - "transcoding_realtime_video_codecs_description": "Οι ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎšÎˇĪ„Î­Ī‚ Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… ΀΁ÎŋĪƒĪ†Î­ĪÎŋÎŊĪ„ÎąÎš ÎŗÎšÎą ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΃Îĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēΌ Ī‡ĪĪŒÎŊÎŋ. Οι ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚-Ī€ÎĩÎģÎŦĪ„ÎĩĪ‚ θι ÎĩĪ€ÎšÎģÎ­ÎŗÎŋĪ…ÎŊ Ī„ÎˇÎŊ ÎēÎąÎģĪĪ„ÎĩĪÎˇ ÎĩĪ€ÎšÎģÎŋÎŗÎŽ Ī€ÎŋĪ… Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎŋĪ…ÎŊ ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ. ΤÎŋ AV1 ÎĩίÎŊιΚ Ī€ÎšÎŋ ÎąĪ€ÎŋδÎŋĪ„ÎšÎēΌ ÎąĪ€ĪŒ Ī„Îŋ HEVC, Ī„Îŋ ÎŋĪ€ÎŋίÎŋ ÎĩίÎŊιΚ Ī€ÎšÎŋ ÎąĪ€ÎŋδÎŋĪ„ÎšÎēΌ ÎąĪ€ĪŒ Ī„Îŋ H.264. ÎŒĪ„ÎąÎŊ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„Îĩ ÎĩĪ€ÎšĪ„ÎŦ·΅ÎŊĪƒÎˇ Ī…ÎģΚÎēÎŋĪ, ÎĩĪ€ÎšÎģÎ­ÎžĪ„Îĩ ÎŧΌÎŊÎŋ Ī„ÎŋĪ…Ī‚ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎšÎˇĪ„Î­Ī‚ Ī€ÎŋĪ… ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎšÎŽĪƒÎĩΚ Îŋ ÎĩĪ€ÎšĪ„ÎąĪ‡Ī…ÎŊĪ„ÎŽĪ‚. ÎŒĪ„ÎąÎŊ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„Îĩ ÎĩĪ€ÎąÎŊÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŧÎ­ĪƒĪ‰ ÎģÎŋÎŗÎšĪƒÎŧΚÎēÎŋĪ, ĪƒÎˇÎŧÎĩÎšĪŽĪƒĪ„Îĩ ĪŒĪ„Îš Ī„Îŋ H.264 ÎĩίÎŊιΚ Ī„ÎąĪ‡ĪĪ„Îĩ΁Îŋ ÎąĪ€ĪŒ Ī„Îŋ AV1, Ī„Îŋ ÎŋĪ€ÎŋίÎŋ ÎĩίÎŊιΚ Ī„ÎąĪ‡ĪĪ„Îĩ΁Îŋ ÎąĪ€ĪŒ Ī„Îŋ HEVC.", "transcoding_reference_frames": "ÎšÎąĪÎ­ ÎąÎŊÎąĪ†Îŋ΁ÎŦĪ‚", "transcoding_reference_frames_description": "Ο ÎąĪÎšÎ¸ÎŧĪŒĪ‚ ΄ΉÎŊ ÎēÎąĪÎ­ Ī€ÎŋĪ… Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎŋĪÎŊĪ„ÎąÎš Ή΂ ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎēÎąĪ„ÎŦ Ī„Îˇ ĪƒĪ…ÎŧĪ€Î¯ÎĩĪƒÎˇ ÎĩÎŊĪŒĪ‚ δÎĩδÎŋÎŧέÎŊÎŋĪ… ÎēÎąĪÎ­. ÎĨĪˆÎˇÎģĪŒĪ„Îĩ΁ÎĩĪ‚ Ī„ÎšÎŧÎ­Ī‚ βÎĩÎģĪ„ÎšĪŽÎŊÎŋĪ…ÎŊ Ī„ÎˇÎŊ ÎąĪ€ÎŋδÎŋĪ„ÎšÎēĪŒĪ„ÎˇĪ„Îą Ī„ÎˇĪ‚ ĪƒĪ…ÎŧĪ€Î¯ÎĩĪƒÎˇĪ‚, ÎąÎģÎģÎŦ ÎĩĪ€ÎšÎ˛ĪÎąÎ´ĪÎŊÎŋĪ…ÎŊ Ī„ÎˇÎŊ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ. Η Ī„ÎšÎŧÎŽ 0 ĪĪ…Î¸ÎŧίÎļÎĩΚ ÎąĪ…Ī„ĪŒ Ī„ÎŋÎŊ ÎąĪÎšÎ¸ÎŧΌ, ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îą.", "transcoding_required_description": "ÎœĪŒÎŊÎŋ Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… δÎĩÎŊ ÎĩίÎŊιΚ ΃Îĩ ÎąĪ€ÎŋδÎĩÎēĪ„ÎŽ ÎŧÎŋĪĪ†ÎŽ", @@ -478,8 +440,6 @@ "user_settings_description": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ Ī‡ĪÎŽĪƒĪ„Îˇ", "user_successfully_removed": "Ο Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚ {email} ÎąĪ†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą.", "users_page_description": "ÎŖÎĩÎģÎ¯Î´Îą Ī‡ĪÎˇĪƒĪ„ĪŽÎŊ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ", - "version_check_channel": "ΚαÎŊÎŦÎģΚ έÎēδÎŋĪƒÎˇĪ‚", - "version_check_channel_description": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„Îŋ ÎēÎąÎŊÎŦÎģΚ έÎēδÎŋĪƒÎˇĪ‚ ÎŗÎšÎą Ī„Îŋ ÎŋĪ€ÎŋίÎŋ θέÎģÎĩĪ„Îĩ ÎŊÎą ÎģÎąÎŧβÎŦÎŊÎĩĪ„Îĩ ÎąÎŊÎąÎēÎŋΚÎŊĪŽĪƒÎĩÎšĪ‚ ÎĩÎēÎ´ĪŒĪƒÎĩΉÎŊ", "version_check_enabled_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… έÎēδÎŋĪƒÎˇĪ‚", "version_check_implications": "Η ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ… έÎēδÎŋĪƒÎˇĪ‚, ÎĩÎžÎąĪĪ„ÎŦĪ„ÎąÎš ÎąĪ€ĪŒ Ī„ÎˇÎŊ Ī€ÎĩĪÎšÎŋδΚÎēÎŽ ÎĩĪ€ÎšÎēÎŋΚÎŊΉÎŊÎ¯Îą ÎŧÎĩ Ī„Îŋ {server}", "version_check_settings": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎĩÎēδÎŋĪƒÎˇĪ‚", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "ÎšÎąÎ¸ÎąĪÎšĪƒÎŧĪŒĪ‚ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ÎŧÎŊÎŽÎŧÎˇĪ‚ ÎĩΚÎēΌÎŊΉÎŊ", "advanced_settings_clear_image_cache_error": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎēÎąÎ¸ÎąĪÎšĪƒÎŧÎŋĪ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ÎŧÎŊÎŽÎŧÎˇĪ‚ ÎĩΚÎēΌÎŊΉÎŊ", "advanced_settings_clear_image_cache_success": "Î•Ī€ÎšĪ„Ī…Ī‡ÎŽĪ‚ ÎĩÎēÎēιθÎŦĪÎšĪƒÎˇ {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Î§ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„ÎˇÎŊ ÎĩĪ€ÎšÎģÎŋÎŗÎŽ ÎŗÎšÎą ÎŊÎą Ī†ÎšÎģ΄΁ÎŦ΁ÎĩĪ„Îĩ Ī„Îą ÎŧÎ­ĪƒÎą ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇĪ‚ ÎēÎąĪ„ÎŦ Ī„ÎŋÎŊ ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧΌ ÎŧÎĩ βÎŦĪƒÎˇ ÎĩÎŊÎąÎģÎģÎąÎēĪ„ÎšÎēÎŦ ÎēĪÎšĪ„ÎŽĪÎšÎą. ΔÎŋÎēΚÎŧÎŦĪƒĪ„Îĩ ÎąĪ…Ī„ÎŽ Ī„Îˇ Î´Ī…ÎŊÎąĪ„ĪŒĪ„ÎˇĪ„Îą ÎŧΌÎŊÎŋ ÎąÎŊ Î­Ī‡ÎĩĪ„Îĩ ΀΁ÎŋβÎģÎŽÎŧÎąĪ„Îą ÎŧÎĩ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ Ī€ÎŋĪ… ÎĩÎŊĪ„ÎŋĪ€Î¯ÎļÎĩΚ ΌÎģÎą Ī„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ.", + "advanced_settings_enable_alternate_media_filter_title": "[ΠΕΙΡΑΜΑΤΙΚΟ] Î§ĪÎŽĪƒÎˇ ÎĩÎŊÎąÎģÎģÎąÎēĪ„ÎšÎēÎŋĪ Ī†Î¯Îģ΄΁ÎŋĪ… ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧÎŋĪ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽĪ‚", "advanced_settings_log_level_title": "Î•Ī€Î¯Ī€ÎĩδÎŋ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚: {level}", "advanced_settings_prefer_remote_subtitle": "ΜÎĩĪÎšÎēÎ­Ī‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚ ÎąĪÎŗÎŋĪÎŊ Ī€ÎŋÎģĪ ÎŊÎą ΆÎŋĪĪ„ĪŽĪƒÎŋĪ…ÎŊ ÎŧΚÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎąĪ€ĪŒ Ī„ÎŋĪ€ÎšÎēÎŦ ÎąĪĪ‡ÎĩÎ¯Îą. ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ĪĪÎ¸ÎŧÎšĪƒÎˇ ÎŗÎšÎą ÎŊÎą ΆÎŋĪĪ„ĪŽÎŊÎŋÎŊĪ„ÎąÎš ÎąÎŊĪ„Î¯ ÎąĪ…Ī„ÎŋĪ ÎąĪ€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊÎĩĪ‚ ÎĩΚÎēΌÎŊÎĩĪ‚.", "advanced_settings_prefer_remote_title": "Î ĪÎŋĪ„Î¯ÎŧÎˇĪƒÎˇ ÎąĪ€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊΉÎŊ ÎĩΚÎēΌÎŊΉÎŊ", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋΚ proxy headers [ΠΕΙΡΑΜΑΤΙΚΟ]", "advanced_settings_readonly_mode_subtitle": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩί Ī„Îˇ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą ÎŧΌÎŊÎŋ-ÎŗÎšÎą-ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ, ĪŒĪ€ÎŋĪ… ÎŋΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŧΌÎŊÎŋ ÎŊÎą ΀΁ÎŋβÎģΡθÎŋĪÎŊ. ΕÎŊÎ­ĪÎŗÎĩΚÎĩĪ‚ ĪŒĪ€Ī‰Ī‚ ÎĩĪ€ÎšÎģÎŋÎŗÎŽ Ī€ÎŋÎģÎģĪŽÎŊ ÎĩΚÎēΌÎŊΉÎŊ, ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ, ÎąĪ€ÎŋĪƒĪ„ÎŋÎģÎŽ (casting) ÎēιΚ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎĩĪ‚. Η ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ/ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī„ÎˇĪ‚ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎŧΌÎŊÎŋ-ÎŗÎšÎą-ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ ÎŗÎ¯ÎŊÎĩĪ„ÎąÎš ÎŧÎ­ĪƒĪ‰ Ī„ÎˇĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚ Ī„ÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ÎēÎĩÎŊĪ„ĪÎšÎēÎŽ ÎŋÎ¸ĪŒÎŊΡ", "advanced_settings_readonly_mode_title": "ÎœĪŒÎŊÎŋ-ÎŗÎšÎą-ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ", + "advanced_settings_self_signed_ssl_subtitle": "Î ÎąĪÎąÎēÎŦÎŧ΀΄ÎĩΚ Ī„ÎŋÎŊ έÎģÎĩÎŗĪ‡Îŋ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŋĪ SSL Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ. Î‘Ī€ÎąĪÎąÎ¯Ī„ÎˇĪ„Îŋ ÎŗÎšÎą ÎąĪ…Ī„Îŋ-Ī…Ī€ÎŋÎŗÎĩÎŗĪÎąÎŧÎŧέÎŊÎą Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŦ.", + "advanced_settings_self_signed_ssl_title": "Να ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎŋÎŊĪ„ÎąÎš ÎąĪ…Ī„Îŋ-Ī…Ī€ÎŋÎŗÎĩÎŗĪÎąÎŧÎŧέÎŊÎą Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŦ SSL [ΠΕΙΡΑΜΑΤΙΚΟ]", "advanced_settings_sync_remote_deletions_subtitle": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ÎŽ ÎĩĪ€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎĩÎŊĪŒĪ‚ Ī€ÎĩĪÎšÎŋĪ…ĪƒÎšÎąÎēÎŋĪ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ… ΃Îĩ ÎąĪ…Ī„ÎŽ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ, ĪŒĪ„ÎąÎŊ Ρ ÎĩÎŊÎ­ĪÎŗÎĩΚι ÎąĪ…Ī„ÎŽ Ī€ĪÎąÎŗÎŧÎąĪ„ÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ĪƒĪ„Îŋ Î´ÎšÎąÎ´Î¯Îē΄΅Îŋ", "advanced_settings_sync_remote_deletions_title": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚ ÎąĪ€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊΉÎŊ Î´ÎšÎąÎŗĪÎąĪ†ĪŽÎŊ [ΠΕΙΡΑΜΑΤΙΚΟ]", "advanced_settings_tile_subtitle": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ΀΁ÎŋĪ‡Ī‰ĪÎˇÎŧέÎŊÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ {album};", "album_delete_confirmation_description": "ΕÎŦÎŊ ÎąĪ…Ī„ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎĩίÎŊιΚ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ, ÎŋΚ ÎŦÎģÎģÎŋΚ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ δÎĩÎŊ θι ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą Î­Ī‡ÎŋĪ…ÎŊ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ.", "album_deleted": "ΤÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎĩ", + "album_info_card_backup_album_excluded": "ΕΞΑΙΡΟÎĨΜΕΝΟ", + "album_info_card_backup_album_included": "ÎŖÎĨΜΠΕΡΙΛΑΜΒΑΝΟΜΕΝΟ", "album_info_updated": "Οι Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ Ī„ÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ, ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎąÎŊ", + "album_leave": "ΘέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ€ÎŋĪ‡Ī‰ĪÎŽĪƒÎĩĪ„Îĩ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ;", + "album_leave_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ€ÎŋĪ‡Ī‰ĪÎŽĪƒÎĩĪ„Îĩ ÎąĪ€ĪŒ Ī„Îŋ {album};", "album_name": "ΟÎŊÎŋÎŧÎąĪƒÎ¯Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "album_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "album_remove_user": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ Ī‡ĪÎŽĪƒĪ„Îˇ;", "album_remove_user_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ†ÎąÎšĪÎ­ĪƒÎĩĪ„Îĩ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {user};", "album_search_not_found": "ΔÎĩ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ Ī€ÎŋĪ… ÎŊÎą Ī„ÎąÎšĪÎšÎŦÎļÎŋĪ…ÎŊ ÎŧÎĩ Ī„ÎˇÎŊ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎŽ ĪƒÎąĪ‚", + "album_selected": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎŋ", "album_share_no_users": "ÎĻÎąÎ¯ÎŊÎĩĪ„ÎąÎš ĪŒĪ„Îš Î­Ī‡ÎĩĪ„Îĩ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎšÎŽĪƒÎĩΚ ÎąĪ…Ī„ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ΃Îĩ ΌÎģÎŋĪ…Ī‚ Ī„ÎŋĪ…Ī‚ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ ÎŽ δÎĩÎŊ Î­Ī‡ÎĩĪ„Îĩ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ ÎŗÎšÎą ÎŊÎą Ī„Îŋ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ.", "album_summary": "ΠÎĩĪÎ¯ÎģÎˇĪˆÎˇ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "album_updated": "ΤÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ, ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ", "album_updated_setting_description": "ΛÎŦβÎĩĪ„Îĩ ÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŧÎ­ĪƒĪ‰ email ĪŒĪ„ÎąÎŊ έÎŊÎą ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ Î­Ī‡ÎĩΚ ÎŊέι ÎąĪĪ‡ÎĩÎ¯Îą", "album_upload_assets": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąĪ€ĪŒ Ī„ÎŋÎŊ Ī…Ī€ÎŋÎģÎŋÎŗÎšĪƒĪ„ÎŽ ĪƒÎąĪ‚ ÎēιΚ ΀΁ÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_user_left": "Î‘Ī€ÎŋĪ‡Ī‰ĪÎŽĪƒÎąĪ„Îĩ ÎąĪ€ĪŒ Ī„Îŋ {album}", + "album_user_removed": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ Îŋ/Ρ {user}", + "album_viewer_appbar_delete_confirm": "Î•Î¯ĪƒĪ„Îĩ βέβιΚÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎąĪ€ĪŒ Ī„ÎŋÎŊ ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧΌ ĪƒÎąĪ‚;", "album_viewer_appbar_share_err_delete": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Î´ÎšÎąÎŗĪÎąĪ†ÎŽĪ‚ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_viewer_appbar_share_err_leave": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎąĪ€ÎŋĪ‡ĪŽĪÎˇĪƒÎˇĪ‚ ÎąĪ€ĪŒ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_viewer_appbar_share_err_remove": "ÎĨĪ€ÎŦ΁·ÎŋĪ…ÎŊ ΀΁ÎŋβÎģÎŽÎŧÎąĪ„Îą ĪƒĪ„ÎˇÎŊ ÎąĪ†ÎąÎ¯ĪÎĩĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_viewer_appbar_share_err_title": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎąÎģÎģÎąÎŗÎŽĪ‚ Ī„Î¯Ī„ÎģÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_viewer_appbar_share_leave": "Î‘Ī€ÎŋĪ‡ĪŽĪÎˇĪƒÎˇ ÎąĪ€ĪŒ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "album_viewer_appbar_share_to": "ΚÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΃Îĩ", "album_viewer_page_share_add_users": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī‡ĪÎˇĪƒĪ„ĪŽÎŊ", "album_with_link_access": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ ΃Îĩ ÎŋĪ€ÎŋΚÎŋÎŊÎ´ÎŽĪ€ÎŋĪ„Îĩ Î­Ī‡ÎĩΚ Ī„ÎŋÎŊ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ, ÎŊÎą δÎĩΚ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą ÎŦĪ„ÎŋÎŧÎą ΃Îĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ.", "albums": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Î‘ĪĪ‡ÎšÎēÎŽ Ī„ÎąÎžÎšÎŊΌÎŧÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ Ī„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊÎ­Ī‰ÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ.", "albums_feature_description": "ÎŖĪ…ÎģÎģÎŋÎŗÎ­Ī‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ Ī€ÎŋĪ… ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą ÎēÎŋΚÎŊÎŋĪ€ÎŋΚΡθÎŋĪÎŊ ΃Îĩ ÎŦÎģÎģÎŋĪ…Ī‚ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚.", "albums_on_device_count": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ ĪƒĪ„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ({count})", + "albums_selected": "{count, plural, one {# ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎĩ} other {# ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎąÎŊ}}", "all": "ΌÎģÎą", "all_albums": "ΌÎģÎą Ī„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "all_people": "ΌÎģÎą Ī„Îą ÎŦĪ„ÎŋÎŧÎą", "all_photos": "ΌÎģÎĩĪ‚ ÎŋΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", "all_videos": "ΌÎģÎą Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ", "allow_dark_mode": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ Ī„Îˇ ΃ÎēÎŋĪ„ÎĩΚÎŊÎŽ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą", + "allow_edits": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ Ī„ÎšĪ‚ ΄΁ÎŋĪ€ÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚", "allow_public_user_to_download": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ ΃Îĩ δΡÎŧĪŒĪƒÎšÎŋ Ī‡ĪÎŽĪƒĪ„Îˇ ÎŊÎą ÎēÎąĪ„ÎĩβÎŦ΃ÎĩΚ", "allow_public_user_to_upload": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ ĪƒĪ„ÎŋÎŊ δΡÎŧĪŒĪƒÎšÎŋ Ī‡ĪÎŽĪƒĪ„Îˇ ÎŊÎą ÎąÎŊÎĩβÎŦ΃ÎĩΚ", "allowed": "Î•Ī€ÎšĪ„ĪÎĩĪ€ĪŒÎŧÎĩÎŊÎŋ", @@ -549,12 +528,14 @@ "always_keep": "Î”ÎšÎąĪ„ÎŽĪÎˇĪƒÎˇ Ī€ÎŦÎŊĪ„Îą", "always_keep_photos_hint": "Η ÂĢÎ‘Ī€ÎĩÎģÎĩĪ…Î¸Î­ĪĪ‰ĪƒÎˇ Ī‡ĪŽĪÎŋĪ…Âģ θι ÎēĪÎąĪ„ÎŽĪƒÎĩΚ ΌÎģÎĩĪ‚ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ΃Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ.", "always_keep_videos_hint": "Η ÂĢÎ‘Ī€ÎĩÎģÎĩĪ…Î¸Î­ĪĪ‰ĪƒÎˇ Ī‡ĪŽĪÎŋĪ…Âģ θι ÎēĪÎąĪ„ÎŽĪƒÎĩΚ ΌÎģÎą Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ ΃Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ.", + "anti_clockwise": "ΑÎŊĪ„Î¯Î¸ÎĩĪ„Îą ÎŧÎĩ Ī„Îˇ ΆÎŋ΁ÎŦ Ī„ÎŋĪ… ΁ÎŋÎģÎŋÎŗÎšÎŋĪ", "api_key": "ΚÎģÎĩΚδί API", "api_key_description": "Î‘Ī…Ī„ÎŽ Ρ Ī„ÎšÎŧÎŽ θι ÎĩÎŧĪ†ÎąÎŊÎšĪƒĪ„Îĩί ÎŧΌÎŊÎŋ ÎŧÎ¯Îą ΆÎŋ΁ÎŦ. Î ÎąĪÎąÎēÎąÎģĪŽ βÎĩÎ˛ÎąÎšĪ‰Î¸ÎĩÎ¯Ī„Îĩ ĪŒĪ„Îš Ī„ÎˇÎŊ Î­Ī‡ÎĩĪ„Îĩ ÎąÎŊĪ„ÎšÎŗĪÎŦΈÎĩΚ Ī€ĪÎšÎŊ ÎēÎģÎĩÎ¯ĪƒÎĩĪ„Îĩ Ī„Îŋ Ī€ÎąĪÎŦÎ¸Ī…ĪÎŋ.", "api_key_empty": "ΤÎŋ ΌÎŊÎŋÎŧÎą Ī„ÎŋĪ… ÎēÎģÎĩΚδΚÎŋĪ API, δÎĩÎŊ Ī€ĪÎ­Ī€ÎĩΚ ÎŊÎą ÎĩίÎŊιΚ ÎēÎĩÎŊΌ", "api_keys": "ΚÎģÎĩΚδΚÎŦ API", "app_architecture_variant": "Î ÎąĪÎąÎģÎģÎąÎŗÎŽ (Î‘ĪĪ‡ÎšĪ„ÎĩÎēĪ„ÎŋÎŊΚÎēÎŽ)", "app_bar_signout_dialog_content": "Î•Î¯ĪƒĪ„Îĩ βέβιΚÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ€ÎŋĪƒĪ…ÎŊδÎĩθÎĩÎ¯Ī„Îĩ;", + "app_bar_signout_dialog_ok": "Ναι", "app_bar_signout_dialog_title": "Î‘Ī€ÎŋĪƒĪÎŊδÎĩĪƒÎˇ", "app_download_links": "ÎŖĪÎŊδÎĩ΃ÎŧÎŋΚ Î›ÎŽĪˆÎˇĪ‚ Î•Ī†ÎąĪÎŧÎŋÎŗÎŽĪ‚", "app_settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", @@ -565,19 +546,27 @@ "archive": "Î‘ĪĪ‡ÎĩίÎŋ", "archive_action_prompt": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {count} ĪƒĪ„Îŋ Î‘ĪĪ‡ÎĩίÎŋ", "archive_or_unarchive_photo": "Î‘ĪĪ‡ÎĩΚÎŋÎ¸Î­Ī„ÎˇĪƒÎˇ ÎŽ ÎąĪ€ÎŋÎąĪĪ‡ÎĩΚÎŋÎ¸Î­Ī„ÎˇĪƒÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚", + "archive_page_no_archived_assets": "ΔÎĩ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ÎąĪĪ‡ÎĩΚÎŋθÎĩĪ„ÎˇÎŧέÎŊÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", + "archive_page_title": "Î‘ĪĪ‡ÎĩίÎŋ ({count})", "archive_size": "ÎœÎ­ÎŗÎĩθÎŋĪ‚ Î‘ĪĪ‡ÎĩίÎŋĪ…", "archive_size_description": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒĪ„Îĩ Ī„Îŋ ÎŧÎ­ÎŗÎĩθÎŋĪ‚ Ī„ÎŋĪ… ÎąĪĪ‡ÎĩίÎŋĪ… ÎŗÎšÎą ÎģÎŽĪˆÎĩÎšĪ‚ (΃Îĩ GiB)", "archived": "Î‘ĪĪ‡ÎĩίÎŋ", "archived_count": "{count, plural, other {Î‘ĪĪ‡ÎĩΚÎŋθÎĩĪ„ÎŽÎ¸ÎˇÎēÎąÎŊ #}}", "are_these_the_same_person": "ΕίÎŊιΚ Ī„Îŋ ίδΚÎŋ ÎŦĪ„ÎŋÎŧÎŋ;", "are_you_sure_to_do_this": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Ī„Îŋ ÎēÎŦÎŊÎĩĪ„Îĩ ÎąĪ…Ī„ĪŒ;", + "array_field_not_fully_supported": "Τι Ī€ÎĩÎ´Î¯Îą Ī€Î¯ÎŊÎąÎēÎą ÎąĪ€ÎąÎšĪ„ÎŋĪÎŊ ·ÎĩÎšĪÎŋÎēίÎŊÎˇĪ„Îˇ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą JSON", + "asset_action_delete_err_read_only": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎŧΌÎŊÎŋ ÎŗÎšÎą ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "asset_action_share_err_offline": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎąÎŊÎŦÎēĪ„ÎˇĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎĩÎēĪ„ĪŒĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", "asset_added_to_album": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "asset_adding_to_album": "Î ĪÎŋĪƒĪ„Î¯Î¸ÎĩĪ„ÎąÎš ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧâ€Ļ", "asset_created": "ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ δΡÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ", - "asset_day_count": "{date}: {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", "asset_description_updated": "Η Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽ Ī„ÎŋĪ… ÎąÎŊĪ„ÎšÎēÎĩΚÎŧέÎŊÎŋĪ… Î­Ī‡ÎĩΚ ÎĩÎŊΡÎŧÎĩĪĪ‰Î¸Îĩί", + "asset_filename_is_offline": "ΤÎŋ ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎŋ {filename} ÎĩίÎŊιΚ ÎĩÎēĪ„ĪŒĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", + "asset_has_unassigned_faces": "ΤÎŋ ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎŋ Î­Ī‡ÎĩΚ ÎŧΡ ÎąÎŊÎąĪ„ÎĩθÎĩΚÎŧέÎŊÎą Ī€ĪĪŒĪƒĪ‰Ī€Îą", "asset_hashing": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎēÎąĪ„ÎąÎēÎĩ΁ÎŧÎąĪ„ÎšĪƒÎŧÎŋĪâ€Ļ", "asset_list_group_by_sub_title": "ΟÎŧιδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ", + "asset_list_layout_settings_dynamic_layout_title": "Î”Ī…ÎŊÎąÎŧΚÎēÎŽ δΚÎŦĪ„ÎąÎžÎˇ", + "asset_list_layout_settings_group_automatically": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îą", "asset_list_layout_settings_group_by": "ΟÎŧιδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąÎŊÎŦ", "asset_list_layout_settings_group_by_month_day": "ΜήÎŊÎąĪ‚ + ΡÎŧÎ­ĪÎą", "asset_list_layout_sub_title": "ΔιÎŦĪ„ÎąÎžÎˇ", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ δÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎĩ ĪƒĪ„Îŋ iCloud. ÎœĪ€Îŋ΁Îĩί ÎŊÎą ÎŧΡÎŊ ÎĩίÎŊιΚ ΀΁ÎŋĪƒÎ˛ÎŦĪƒÎšÎŧÎŋ ÎģĪŒÎŗĪ‰ ΀΁ÎŋβÎģΡÎŧÎąĪ„ÎšÎēÎŋĪ ÎąĪĪ‡ÎĩίÎŋĪ… Ī€ÎŋĪ… ÎĩίÎŊιΚ ÎąĪ€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎŋ ĪƒĪ„Îŋ iCloud", "asset_offline": "ΑÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎŋ ÎĩÎēĪ„ĪŒĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", "asset_offline_description": "Î‘Ī…Ī„ĪŒ Ī„Îŋ ÎĩÎžĪ‰Ī„ÎĩĪÎšÎēΌ ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎŋ δÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎĩ Ī€ÎģέÎŋÎŊ ĪƒĪ„ÎŋÎŊ Î´Î¯ĪƒÎēÎŋ. Î ÎąĪÎąÎēÎąÎģĪŽ ÎĩĪ€ÎšÎēÎŋΚÎŊΉÎŊÎŽĪƒĪ„Îĩ ÎŧÎĩ Ī„ÎŋÎŊ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ Ī„ÎŋĪ… Immich ÎŗÎšÎą βÎŋΎθÎĩΚι.", + "asset_restored_successfully": "ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ ÎąĪ€ÎŋÎēÎąĪ„ÎąĪƒĪ„ÎŦθΡÎēÎĩ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", "asset_skipped": "Î ÎąĪÎąÎģÎĩÎ¯Ī†Î¸ÎˇÎēÎĩ", "asset_skipped_in_trash": "ÎŖĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ", + "asset_trashed": "ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎĩ", "asset_troubleshoot": "ΑÎŊĪ„ÎšÎŧÎĩĪ„ĪŽĪ€ÎšĪƒÎˇ ΀΁ÎŋβÎģÎŽÎŧÎąĪ„ÎŋĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", "asset_uploaded": "ΑÎŊÎĩβÎŦĪƒĪ„ÎˇÎēÎĩ", "asset_uploading": "ΑÎŊÎĩβÎŦÎļÎĩĪ„ÎąÎšâ€Ļ", "asset_viewer_settings_subtitle": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ ΀΁ÎŋβÎŋÎģÎŽĪ‚ ĪƒĪ…ÎģÎģÎŋÎŗÎŽĪ‚", "asset_viewer_settings_title": "Î ĪÎŋβÎŋÎģÎŽ ÎŖĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "assets": "ΑÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą", + "assets_added_count": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}}", "assets_added_to_album_count": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎĩ {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}} ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", - "assets_added_to_album_partial_count": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {successCount} ÎąĪ€ĪŒ Ī„Îą {totalCount} {totalCount, plural, one {ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "assets_added_to_albums_count": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {assetTotal, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}} ΃Îĩ {albumTotal, plural, one {# ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ} other {# ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ÎŖĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {ÎŖĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} δÎĩÎŊ ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą ΀΁ÎŋĪƒĪ„ÎĩθÎŋĪÎŊ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "assets_cannot_be_added_to_albums": "ΔÎĩÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ΀΁ÎŋĪƒĪ„ÎĩθÎĩί ÎēÎąÎŊέÎŊÎą {count, plural, one {ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} ΃Îĩ ÎēÎąÎŊέÎŊÎą ÎąĪ€ĪŒ Ī„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "assets_count": "{count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}}", + "assets_deleted_permanently": "{count} Ī„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎąÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ", + "assets_deleted_permanently_from_server": "{count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎąÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ Immich", + "assets_downloaded_failed": "{count, plural, one {ÎˆÎŗÎšÎŊÎĩ ÎģÎŽĪˆÎˇ # ÎąĪĪ‡ÎĩίÎŋĪ… - {error} ÎąĪĪ‡ÎĩίÎŋ ÎąĪ€Î­Ī„Ī…Ī‡Îĩ} other {ÎˆÎŗÎšÎŊÎĩ ÎģÎŽĪˆÎˇ # ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ - {error} ÎąĪĪ‡ÎĩÎ¯Îą ÎąĪ€Î­Ī„Ī…Ī‡ÎąÎŊ}}", + "assets_downloaded_successfully": "{count, plural, one {ÎˆÎŗÎšÎŊÎĩ ÎģÎŽĪˆÎˇ # ÎąĪĪ‡ÎĩίÎŋĪ… ÎĩĪ€ÎšĪ„Ī…Ī‡ĪŽĪ‚} other {ÎˆÎŗÎšÎŊÎĩ ÎģÎŽĪˆÎˇ # ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ ÎĩĪ€ÎšĪ„Ī…Ī‡ĪŽĪ‚}}", "assets_moved_to_trash_count": "ΜÎĩĪ„ÎąÎēΚÎŊΎθΡÎēÎąÎŊ {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}} ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ", "assets_permanently_deleted_count": "Î”ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎąÎŊ ÎŧΌÎŊΚÎŧÎą {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}}", "assets_removed_count": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎąÎŊ {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}}", + "assets_removed_permanently_from_device": "{count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎēÎąĪ„ÎąĪÎŗÎŽÎ¸ÎˇÎēÎąÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ĪƒÎąĪ‚", "assets_restore_confirmation": "Î•Î¯ĪƒĪ„Îĩ βέβιΚÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎĩĪ€ÎąÎŊÎąĪ†Î­ĪÎĩĪ„Îĩ ΌÎģÎą Ī„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… Î˛ĪÎ¯ĪƒÎēÎŋÎŊĪ„ÎąÎš ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ; Î‘Ī…Ī„ÎŽ Ρ ÎĩÎŊÎ­ĪÎŗÎĩΚι δÎĩÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎąÎŊÎąÎšĪÎĩθÎĩί! ΛÎŦβÎĩĪ„Îĩ Ī…Ī€ĪŒĪˆÎˇ ĪŒĪ„Îš δÎĩÎŊ θι ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎĩĪ€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎĩÎēĪ„ĪŒĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚.", "assets_restored_count": "ÎˆÎŗÎšÎŊÎĩ ÎĩĪ€ÎąÎŊÎąĪ†Îŋ΁ÎŦ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ}}", + "assets_restored_successfully": "{count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎąĪ€ÎŋÎēÎąĪ„ÎąĪƒĪ„ÎŦθΡÎēÎąÎŊ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", "assets_trashed": "{count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧÎĩĪ„ÎąĪ†Î­ĪÎ¸ÎˇÎēÎąÎŊ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ", "assets_trashed_count": "ΜÎĩĪ„ÎąÎēΚÎŊ. ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŦ΄ΉÎŊ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", + "assets_trashed_from_server": "{count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧÎĩĪ„ÎąĪ†Î­ĪÎ¸ÎˇÎēÎąÎŊ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ ÎąĪ€ĪŒ Ī„Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ Immich", "assets_were_part_of_album_count": "{count, plural, one {ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ ÎąÎŊÎŽÎēÎĩΚ} other {Τι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎąÎŊÎŽÎēÎŋĪ…ÎŊ}} ΎδΡ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "assets_were_part_of_albums_count": "ΤÎŋ/Îą {count, plural, one {ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ ÎŽĪ„ÎąÎŊ} other {ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŽĪ„ÎąÎŊ}} ΎδΡ ÎŧÎ­ĪÎŋĪ‚ ΄ΉÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "authorized_devices": "ΕξÎŋĪ…ĪƒÎšÎŋδÎŋĪ„ÎˇÎŧέÎŊÎĩĪ‚ ÎŖĪ…ĪƒÎēÎĩĪ…Î­Ī‚", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇĪ‚", "back": "Î Î¯ĪƒĪ‰", "back_close_deselect": "Î Î¯ĪƒĪ‰, ÎēÎģÎĩÎ¯ĪƒÎšÎŧÎŋ ÎŽ ÎąĪ€ÎŋÎĩĪ€ÎšÎģÎŋÎŗÎŽ", + "background_backup_running_error": "Η δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎŦÎģÎĩÎšÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎĩÎēĪ„ÎĩÎģÎĩÎ¯Ī„ÎąÎš ΎδΡ, δÎĩÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ΞÎĩÎēΚÎŊÎŽĪƒÎĩĪ„Îĩ ·ÎĩÎšĪÎŋÎēίÎŊÎˇĪ„Îŋ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎŦÎģÎĩÎšÎąĪ‚", "background_location_permission": "ΆδÎĩΚι Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ", "background_location_permission_content": "ΤÎŋ Immich ÎŗÎšÎą ÎŊÎą ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎąÎģÎģÎŦÎļÎĩΚ δίÎēĪ„Ī…Îą ĪŒĪ„ÎąÎŊ Ī„ĪÎ­Ī‡ÎĩΚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ, Ī€ĪÎ­Ī€ÎĩΚ *Ī€ÎŦÎŊĪ„Îą* ÎŊÎą Î­Ī‡ÎĩΚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ ĪƒĪ„ÎˇÎŊ ÎąÎēĪÎšÎ˛ÎŽ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą ĪŽĪƒĪ„Îĩ Ρ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ ÎŊÎą ÎŧĪ€Îŋ΁Îĩί ÎŊÎą δΚιβÎŦÎļÎĩΚ Ī„Îŋ ΌÎŊÎŋÎŧÎą Ī„ÎŋĪ… δΚÎēĪ„ĪÎŋĪ… Wi-Fi", "background_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", "backup": "ΑÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", + "backup_album_selection_page_albums_device": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ ĪƒĪ„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ({count})", "backup_album_selection_page_albums_tap": "ΠÎŦĪ„ÎˇÎŧÎą ÎŗÎšÎą ĪƒĪ…ÎŧĪ€ÎĩĪÎ¯ÎģÎˇĪˆÎˇ, Î´ÎšĪ€ÎģΌ Ī€ÎŦĪ„ÎˇÎŧÎą ÎŗÎšÎą ÎĩÎžÎąÎ¯ĪÎĩĪƒÎˇ", "backup_album_selection_page_assets_scatter": "Τι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧĪ€Îŋ΁Îĩί ÎŊÎą Î´ÎšÎąĪƒÎēÎŋĪĪ€ÎšĪƒĪ„ÎŋĪÎŊ ΃Îĩ Ī€ÎŋÎģÎģÎŦ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ. ÎˆĪ„ĪƒÎš, Ī„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą Ī€ÎĩĪÎšÎģÎˇĪ†Î¸ÎŋĪÎŊ ÎŽ ÎŊÎą ÎĩÎžÎąÎšĪÎĩθÎŋĪÎŊ ÎēÎąĪ„ÎŦ Ī„Îˇ δΚιδΚÎēÎąĪƒÎ¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚.", "backup_album_selection_page_select_albums": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "backup_album_selection_page_selection_info": "ΠÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ÎĩĪ€ÎšÎģÎŋÎŗÎŽĪ‚", + "backup_album_selection_page_total_assets": "ÎŖĪ…ÎŊÎŋÎģΚÎēÎŦ ÎŧÎŋÎŊιδΚÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", "backup_albums_sync": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚ ΑÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ Î‘ĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ΆÎģÎŧĪ€ÎŋĪ…Îŧ", + "backup_all": "ΌÎģÎą", + "backup_background_service_backup_failed_message": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚. Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇâ€Ļ", "backup_background_service_complete_notification": "ΟÎģÎŋÎēÎģÎŽĪĪ‰ĪƒÎˇ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", + "backup_background_service_connection_failed_message": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ĪƒĪÎŊδÎĩĪƒÎˇĪ‚ ÎŧÎĩ Ī„Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ. Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇâ€Ļ", + "backup_background_service_current_upload_notification": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ {filename}", "backup_background_service_default_notification": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎŗÎšÎą ÎŊέι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îąâ€Ļ", + "backup_background_service_error_title": "ÎŖĪ†ÎŦÎģÎŧÎą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "backup_background_service_in_progress_notification": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒÎąĪ‚â€Ļ", + "backup_background_service_upload_failure_notification": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ {filename}", "backup_controller_page_albums": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "backup_controller_page_background_app_refresh_disabled_content": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎˇÎŊ ÎąÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ĪƒĪ„ÎšĪ‚ ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ > ΓÎĩÎŊΚÎēÎŦ > ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ Î•Ī†ÎąĪÎŧÎŋÎŗÎŽĪ‚ ĪƒĪ„Îŋ Î ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎŗÎšÎą ÎŊÎą Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ Ī„ÎˇÎŊ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ.", + "backup_controller_page_background_app_refresh_disabled_title": "Η ÎąÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ", + "backup_controller_page_background_app_refresh_enable_button_text": "ΜÎĩĪ„ÎŦÎ˛ÎąĪƒÎˇ ĪƒĪ„ÎšĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚", "backup_controller_page_background_battery_info_link": "ΔÎĩίΞÎĩ ÎŧÎŋĪ… ΀Ή΂", "backup_controller_page_background_battery_info_message": "Για Ī„ÎˇÎŊ ÎēÎąÎģĪĪ„ÎĩĪÎˇ ÎĩÎŧĪ€ÎĩÎšĪÎ¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ, ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎŋĪ€ÎŋÎšÎąÎ´ÎŽĪ€ÎŋĪ„Îĩ βÎĩÎģĪ„ÎšĪƒĪ„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŧĪ€ÎąĪ„ÎąĪÎ¯ÎąĪ‚ Ī€ÎĩĪÎšÎŋĪÎ¯ÎļÎĩΚ Ī„Îˇ Î´ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„Îą ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎŗÎšÎą Ī„Îŋ Immich. \n\nΔÎĩδÎŋÎŧέÎŊÎŋĪ… ĪŒĪ„Îš Îŋ Ī„ĪĪŒĪ€ÎŋĪ‚ ÎĩÎžÎąĪĪ„ÎŦĪ„ÎąÎš ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ĪƒÎąĪ‚, Ī€ÎąĪÎąÎēÎąÎģÎŋĪÎŧÎĩ ΈÎŦÎžĪ„Îĩ Ī„ÎšĪ‚ ÎąĪ€ÎąĪÎąÎ¯Ī„ÎˇĪ„ÎĩĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ÎŗÎšÎą Ī„ÎŋÎŊ ÎēÎąĪ„ÎąĪƒÎēÎĩĪ…ÎąĪƒĪ„ÎŽ Ī„ÎˇĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽĪ‚ ĪƒÎąĪ‚.", "backup_controller_page_background_battery_info_ok": "ΟΚ", "backup_controller_page_background_battery_info_title": "ΒÎĩÎģĪ„ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚ ÎŧĪ€ÎąĪ„ÎąĪÎ¯ÎąĪ‚", + "backup_controller_page_background_charging": "ÎœĪŒÎŊÎŋ ÎēÎąĪ„ÎŦ Ī„Îˇ Ī†ĪŒĪĪ„ÎšĪƒÎˇ", + "backup_controller_page_background_configure_error": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ĪĪÎ¸ÎŧÎšĪƒÎˇĪ‚ Ī„ÎˇĪ‚ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎąĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", "backup_controller_page_background_delay": "ÎšÎąÎ¸Ī…ĪƒĪ„Î­ĪÎˇĪƒÎˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ÎŊÎ­Ī‰ÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ: {duration}", + "backup_controller_page_background_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎˇÎŊ Ī…Ī€ÎˇĪÎĩĪƒÎ¯Îą Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ… ÎŗÎšÎą ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ÎŊÎ­Ī‰ÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ Ī‡Ī‰ĪÎ¯Ī‚ ÎŊÎą ·΁ÎĩΚÎŦÎļÎĩĪ„ÎąÎš ÎŊÎą ÎąÎŊÎŋίΞÎĩĪ„Îĩ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ", + "backup_controller_page_background_is_off": "Η ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ", + "backup_controller_page_background_is_on": "Η ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎĩίÎŊιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ", + "backup_controller_page_background_turn_off": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎąĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", + "backup_controller_page_background_turn_on": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎąĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", + "backup_controller_page_background_wifi": "ÎœĪŒÎŊÎŋ ΃Îĩ ĪƒĪÎŊδÎĩĪƒÎˇ Wi-Fi", "backup_controller_page_backup": "ΑÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "backup_controller_page_backup_selected": "Î•Ī€ÎšÎģÎĩÎŗÎŧέÎŊÎą: ", "backup_controller_page_backup_sub": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŗÎšÎą Ī„Îą ÎŋĪ€ÎŋÎ¯Îą Î­Ī‡ÎŋĪ…ÎŊ δΡÎŧΚÎŋĪ…ĪÎŗÎˇÎ¸Îĩί ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", + "backup_controller_page_created": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ ĪƒĪ„ÎšĪ‚: {date}", + "backup_controller_page_desc_backup": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎˇÎŊ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ ÎŗÎšÎą ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎŊÎ­Ī‰ÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ĪŒĪ„ÎąÎŊ ÎąÎŊÎŋÎ¯ÎŗÎĩĪ„Îĩ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ.", "backup_controller_page_excluded": "Î•ÎžÎąÎšĪÎŋĪÎŧÎĩÎŊÎą: ", + "backup_controller_page_failed": "Î‘Ī€ÎŋĪ„Ī…Ī‡ÎˇÎŧέÎŊÎą ({count})", + "backup_controller_page_filename": "ΌÎŊÎŋÎŧÎą ÎąĪĪ‡ÎĩίÎŋĪ…: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ΠÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "backup_controller_page_none_selected": "ΚαÎŊέÎŊÎą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎŋ", "backup_controller_page_remainder": "ÎĨĪ€ĪŒÎģÎŋÎšĪ€Îŋ", "backup_controller_page_remainder_sub": "ÎĨĪ€ĪŒÎģÎŋÎšĪ€ÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŗÎšÎą ÎąÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ÎĩĪ€ÎšÎģÎŋÎŗÎŽ", "backup_controller_page_server_storage": "Î§Ī‰ĪÎˇĪ„ÎšÎēĪŒĪ„ÎˇĪ„Îą ΔιαÎēÎŋÎŧÎšĪƒĪ„ÎŽ", + "backup_controller_page_start_backup": "ΈÎŊÎąĪÎžÎˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", + "backup_controller_page_status_off": "Η ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ, ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ", + "backup_controller_page_status_on": "Η ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ ÎĩίÎŊιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ", "backup_controller_page_storage_format": "{used} ÎąĪ€ĪŒ {total} ΃Îĩ Ī‡ĪÎŽĪƒÎˇ", "backup_controller_page_to_backup": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ ÎŗÎšÎą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "backup_controller_page_total_sub": "ΌÎģÎĩĪ‚ ÎŋΚ ÎŧÎŋÎŊιδΚÎēÎ­Ī‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎąĪ€ĪŒ Ī„Îą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "backup_controller_page_turn_off": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ", + "backup_controller_page_turn_on": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ", + "backup_controller_page_uploading_file_info": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī€ÎģÎˇĪÎŋΆÎŋĪÎšĪŽÎŊ ÎąĪĪ‡ÎĩίÎŋĪ…", + "backup_err_only_album": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎąĪ†ÎąÎ¯ĪÎĩĪƒÎˇ Ī„ÎŋĪ… ÎŧÎŋÎŊιδΚÎēÎŋĪ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "backup_error_sync_failed": "Ο ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚ ÎąĪ€Î­Ī„Ī…Ī‡Îĩ. ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī„ÎŋĪ… ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚.", "backup_info_card_assets": "ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", + "backup_manual_cancelled": "ΑÎēĪ…ĪĪŽÎ¸ÎˇÎēÎĩ", + "backup_manual_in_progress": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ΃Îĩ ÎĩΞέÎģΚΞΡ. ΔÎŋÎēΚÎŧÎŦĪƒĪ„Îĩ ÎąĪÎŗĪŒĪ„ÎĩĪÎą", + "backup_manual_success": "Î•Ī€ÎšĪ„Ī…Ī‡Î¯Îą", + "backup_manual_title": "ÎšÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚", "backup_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", + "backup_options_page_title": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", + "backup_setting_subtitle": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎēιΚ ĪƒĪ„Îŋ ΀΁Îŋ΃ÎēÎŽÎŊΚÎŋ", "backup_settings_subtitle": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ΄ΉÎŊ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚", + "backup_upload_details_page_more_details": "Î ÎąĪ„ÎŽĪƒĪ„Îĩ ÎŗÎšÎą Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ ÎģÎĩ΀΄ÎŋÎŧÎ­ĪÎĩΚÎĩĪ‚", "backward": "Î ĪÎŋĪ‚ Ī„Îą Ī€Î¯ĪƒĪ‰", - "battery_optimization_backup_reliability": "Η ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΄ΉÎŊ βÎĩÎģĪ„ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ ÎŧĪ€ÎąĪ„ÎąĪÎ¯ÎąĪ‚ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą βÎĩÎģĪ„ÎšĪŽĪƒÎĩΚ Ī„ÎˇÎŊ ιΞΚÎŋĪ€ÎšĪƒĪ„Î¯Îą ΄ΉÎŊ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ", "biometric_auth_enabled": "ΒιÎŋÎŧÎĩĪ„ĪÎšÎēÎŽ Ī„ÎąĪ…Ī„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΎθΡÎēÎĩ", "biometric_locked_out": "Î•Î¯ĪƒĪ„Îĩ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋΚ ÎĩÎēĪ„ĪŒĪ‚ Ī„ÎˇĪ‚ βΚÎŋÎŧÎĩĪ„ĪÎšÎēÎŽĪ‚ Ī„ÎąĪ…Ī„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", "biometric_no_options": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋΚ Ī„ĪĪŒĪ€ÎŋΚ βΚÎŋÎŧÎĩĪ„ĪÎšÎēÎŽĪ‚ Ī„ÎąĪ…Ī„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", "biometric_not_available": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎĩΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧΡ βΚÎŋÎŧÎĩĪ„ĪÎšÎēÎŽ Ī„ÎąĪ…Ī„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΃Îĩ ÎąĪ…Ī„ÎŽ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ", + "birthdate_saved": "Η ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŗÎ­ÎŊÎŊÎˇĪƒÎˇĪ‚ ÎąĪ€ÎŋθΡÎēÎĩĪĪ„ÎˇÎēÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡ĪŽĪ‚", "birthdate_set_description": "Η ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŗÎ­ÎŊÎŊÎˇĪƒÎˇĪ‚ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ÎŗÎšÎą Ī„ÎŋÎŊ Ī…Ī€ÎŋÎģÎŋÎŗÎšĪƒÎŧΌ Ī„ÎˇĪ‚ ΡÎģΚÎēÎ¯ÎąĪ‚ ÎąĪ…Ī„ÎŋĪ Ī„ÎŋĪ… ÎąĪ„ĪŒÎŧÎŋĪ…, Ī„Îˇ ·΁ÎŋÎŊΚÎēÎŽ ĪƒĪ„ÎšÎŗÎŧÎŽ ÎŧÎšÎąĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚.", "blurred_background": "ΘÎŋÎģΌ Ī†ĪŒÎŊĪ„Îŋ", - "browse_templates": "ΠÎĩĪÎšÎŽÎŗÎˇĪƒÎˇ ΃Îĩ Ī€ĪĪŒĪ„Ī…Ī€Îą", "bugs_and_feature_requests": "ÎŖĪ†ÎŦÎģÎŧÎąĪ„Îą & Î‘ÎšĪ„ÎŽÎŧÎąĪ„Îą ΛÎĩÎšĪ„ÎŋĪ…ĪÎŗÎšĪŽÎŊ", "build": "ÎšÎąĪ„ÎąĪƒÎēÎĩĪ…ÎŽ", "build_image": "ÎšÎąĪ„ÎąĪƒÎēÎĩĪ…ÎŽ ΕιÎēΌÎŊÎąĪ‚", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎēĪÎąĪ„ÎŽĪƒÎĩĪ„Îĩ {count, plural, one {# Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îŋ ÎąĪĪ‡ÎĩίÎŋ} other {# Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą ÎąĪĪ‡ÎĩÎ¯Îą}}; Î‘Ī…Ī„ĪŒ θι ÎĩĪ€ÎšÎģĪĪƒÎĩΚ ΌÎģÎĩĪ‚ Ī„ÎšĪ‚ ÎŋÎŧÎŦδÎĩĪ‚ Î´ÎšĪ€ÎģÎŋĪ„ĪĪ€Ī‰ÎŊ Ī‡Ī‰ĪÎ¯Ī‚ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩΚ Ī„Î¯Ī€ÎŋĪ„Îą.", "bulk_trash_duplicates_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą βÎŦÎģÎĩĪ„Îĩ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ {count, plural, one {# Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îŋ ÎąĪĪ‡ÎĩίÎŋ} other {# Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą ÎąĪĪ‡ÎĩÎ¯Îą}}; Î‘Ī…Ī„ĪŒ θι ÎēĪÎąĪ„ÎŽĪƒÎĩΚ Ī„Îŋ ÎŧÎĩÎŗÎąÎģĪĪ„Îĩ΁Îŋ ÎąĪĪ‡ÎĩίÎŋ ÎąĪ€ĪŒ ÎēÎŦθÎĩ ÎŋÎŧÎŦδι ÎēιΚ θι βÎŦÎģÎĩΚ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ ΌÎģÎą Ī„Îą ÎŦÎģÎģÎą Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą.", "buy": "Î‘ÎŗÎŋ΁ÎŦĪƒĪ„Îĩ Ī„Îŋ Immich", + "cache_settings_clear_cache_button": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ÎŧÎŊÎŽÎŧÎˇĪ‚", + "cache_settings_clear_cache_button_title": "ÎšÎąÎ¸ÎąĪÎ¯ÎļÎĩΚ Ī„Îˇ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽ ÎŧÎŊÎŽÎŧΡ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚. Î‘Ī…Ī„ĪŒ θι ÎĩĪ€ÎˇĪÎĩÎŦ΃ÎĩΚ ĪƒÎˇÎŧÎąÎŊĪ„ÎšÎēÎŦ Ī„ÎˇÎŊ ÎąĪ€ĪŒÎ´ÎŋĪƒÎˇ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚ ÎŧÎ­Ī‡ĪÎš ÎŊÎą ÎąÎŊιδΡÎŧΚÎŋĪ…ĪÎŗÎˇÎ¸Îĩί Ρ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽ ÎŧÎŊÎŽÎŧΡ.", + "cache_settings_duplicated_assets_clear_button": "Î•ÎšÎšÎ‘Î˜Î‘ÎĄÎ™ÎŖÎ—", + "cache_settings_duplicated_assets_subtitle": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… Î­Ī‡ÎŋĪ…ÎŊ ÎŧĪ€ÎĩΚ ĪƒĪ„Îˇ ÎŧÎąĪĪÎˇ ÎģÎ¯ĪƒĪ„Îą ÎąĪ€ĪŒ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ", + "cache_settings_duplicated_assets_title": "Î”ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ({count})", + "cache_settings_statistics_album": "ΜιÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ βΚβÎģΚÎŋθΎÎēÎˇĪ‚", + "cache_settings_statistics_full": "ΠÎģÎŽĪÎĩÎšĪ‚ ÎĩΚÎēΌÎŊÎĩĪ‚", + "cache_settings_statistics_shared": "ΜιÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēÎŋΚÎŊÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "cache_settings_statistics_thumbnail": "ΜιÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", + "cache_settings_statistics_title": "Î§ĪÎŽĪƒÎˇ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ÎŧÎŊÎŽÎŧÎˇĪ‚", + "cache_settings_subtitle": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎˇĪƒÎˇ ĪƒĪ…ÎŧĪ€ÎĩĪÎšĪ†Îŋ΁ÎŦĪ‚ Ī„ÎˇĪ‚ ΀΁ÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ÎŧÎŊÎŽÎŧÎˇĪ‚", + "cache_settings_tile_subtitle": "ΧÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„Îˇ ĪƒĪ…ÎŧĪ€ÎĩĪÎšĪ†Îŋ΁ÎŦ Ī„ÎˇĪ‚ Ī„ÎŋĪ€ÎšÎēÎŽĪ‚ ÎąĪ€ÎŋθΎÎēÎĩĪ…ĪƒÎˇĪ‚", + "cache_settings_tile_title": "ΤÎŋĪ€ÎšÎēÎŽ Î‘Ī€ÎŋθΎÎēÎĩĪ…ĪƒÎˇ", + "cache_settings_title": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ Î ĪÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ΜÎŊÎŽÎŧÎˇĪ‚", "camera": "ΚÎŦÎŧÎĩĪÎą", "camera_brand": "ΜÎŦ΁ÎēÎą ÎēÎŦÎŧÎĩĪÎąĪ‚", "camera_model": "ΜÎŋÎŊĪ„Î­ÎģÎŋ ÎēÎŦÎŧÎĩĪÎąĪ‚", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Î‘Î´ĪÎŊÎąĪ„Îˇ Ρ ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ Ī„ÎˇĪ‚ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚", "cast": "Î ĪÎŋβÎŋÎģÎŽ", "cast_description": "ÎĄĪÎ¸ÎŧÎšĪƒÎˇ ΄ΉÎŊ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧΉÎŊ ΀΁ÎŋÎŋĪÎšĪƒÎŧĪŽÎŊ casting", - "change": "ΑÎģÎģÎąÎŗÎŽ", "change_date": "ΑÎģÎģÎąÎŗÎŽ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚", "change_description": "ΑÎģÎģÎąÎŗÎŽ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚", "change_display_order": "ΑÎģÎģÎąÎŗÎŽ ΃ÎĩÎšĪÎŦĪ‚ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇĪ‚", + "change_expiration_time": "ΑÎģÎģÎąÎŗÎŽ Ī‡ĪĪŒÎŊÎŋĪ… ÎģÎŽÎžÎˇĪ‚", "change_location": "ΑÎģÎģÎąÎŗÎŽ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚", "change_name": "ΑÎģÎģÎąÎŗÎŽ ÎŋÎŊÎŋÎŧÎąĪƒÎ¯ÎąĪ‚", "change_name_successfully": "Î•Ī€ÎšĪ„Ī…Ī‡ÎŽĪ‚ ÎąÎģÎģÎąÎŗÎŽ ÎŋÎŊΌÎŧÎąĪ„ÎŋĪ‚", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Οι ÎēĪ‰Î´ÎšÎēÎŋί δÎĩÎŊ Ī„ÎąÎšĪÎšÎŦÎļÎŋĪ…ÎŊ", "change_password_form_reenter_new_password": "Î•Ī€ÎąÎŊÎĩÎšĪƒÎąÎŗĪ‰ÎŗÎŽ ΝέÎŋĪ… ÎšĪ‰Î´ÎšÎēÎŋĪ", "change_pin_code": "ΑÎģÎģÎąÎŗÎŽ ÎēĪ‰Î´ÎšÎēÎŋĪ PIN", + "change_trigger": "ΑÎģÎģÎąÎŗÎŽ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎˇĪ„ÎŽ", + "change_trigger_prompt": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąÎģÎģÎŦΞÎĩĪ„Îĩ Ī„ÎŋÎŊ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎˇĪ„ÎŽ; Î‘Ī…Ī„ĪŒ θι Î´ÎšÎąÎŗĪÎŦΈÎĩΚ ΌÎģÎĩĪ‚ Ī„ÎšĪ‚ Ī…Ī€ÎŦ΁·ÎŋĪ…ĪƒÎĩĪ‚ ÎĩÎŊÎ­ĪÎŗÎĩΚÎĩĪ‚ ÎēιΚ Ī†Î¯ÎģĪ„ĪÎą.", "change_your_password": "ΑÎģÎģÎŦÎžĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ ĪƒÎąĪ‚", "changed_visibility_successfully": "Η ΀΁ÎŋβÎŋÎģÎŽ, ÎŦÎģÎģιΞÎĩ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", "charging": "ÎĻĪŒĪĪ„ÎšĪƒÎˇ", "charging_requirement_mobile_backup": "Η δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎŦÎģÎĩÎšÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ ÎąĪ€ÎąÎšĪ„Îĩί Ρ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ÎŊÎą ΆÎŋĪĪ„Î¯ÎļÎĩΚ", + "check_corrupt_asset_backup": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎŗÎšÎą ÎēÎąĪ„ÎĩĪƒĪ„ĪÎąÎŧÎŧέÎŊÎą ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", + "check_corrupt_asset_backup_button": "ΕÎēĪ„Î­ÎģÎĩĪƒÎˇ ÎĩÎģÎ­ÎŗĪ‡ÎŋĪ…", + "check_corrupt_asset_backup_description": "ΕÎēĪ„Î­ÎģÎĩ΃Îĩ ÎąĪ…Ī„ĪŒÎŊ Ī„ÎŋÎŊ έÎģÎĩÎŗĪ‡Îŋ ÎŧΌÎŊÎŋ ÎŧÎ­ĪƒĪ‰ Wi-Fi ÎēιΚ ÎąĪ†ÎŋĪ Î­Ī‡ÎŋĪ…ÎŊ ÎąĪ€ÎŋθΡÎēÎĩĪ…Ī„Îĩί ΌÎģÎą Ī„Îą ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ. Η δΚιδΚÎēÎąĪƒÎ¯Îą ÎŧĪ€Îŋ΁Îĩί ÎŊÎą Î´ÎšÎąĪÎēÎ­ĪƒÎĩΚ ÎŧÎĩĪÎšÎēÎŦ ÎģÎĩ΀΄ÎŦ.", "check_logs": "ΕÎģÎ­ÎŗÎžĪ„Îĩ Ī„Îą ÎąĪĪ‡ÎĩÎ¯Îą ÎēÎąĪ„ÎąÎŗĪÎąĪ†ÎŽĪ‚", "checksum": "ΈÎģÎĩÎŗĪ‡ÎŋĪ‚ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", - "choose": "Î•Ī€ÎšÎģÎŋÎŗÎŽ", "choose_matching_people_to_merge": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„Îą ÎąÎŊĪ„Î¯ĪƒĪ„ÎŋÎšĪ‡Îą ÎŦĪ„ÎŋÎŧÎą ÎŗÎšÎą ĪƒĪ…ÎŗĪ‡ĪŽÎŊÎĩĪ…ĪƒÎˇ", "city": "Î ĪŒÎģΡ", "cleanup_confirm_description": "ΤÎŋ Immich ÎĩÎŊĪ„ĪŒĪ€ÎšĪƒÎĩ {count} ÎąĪĪ‡ÎĩÎ¯Îą (δΡÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎąÎŊ Ī€ĪÎšÎŊ ÎąĪ€ĪŒ {date}) Ī€ÎŋĪ… Î­Ī‡ÎŋĪ…ÎŊ ÎąĪƒĪ†ÎąÎģĪŽĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎąĪ†Îĩί ĪƒĪ„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ. Να Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ Ī„Îą Ī„ÎŋĪ€ÎšÎēÎŦ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪ€ĪŒ ÎąĪ…Ī„ÎŽ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ;", @@ -719,10 +774,11 @@ "clear": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ", "clear_all": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ ΌÎģΉÎŊ", "clear_all_recent_searches": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ ΌÎģΉÎŊ ΄ΉÎŊ Ī€ĪĪŒĪƒĪ†ÎąĪ„Ī‰ÎŊ ÎąÎŊÎąÎļÎˇĪ„ÎŽĪƒÎĩΉÎŊ", - "clear_failed_count": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎĩÎēÎēιθÎŦĪÎšĪƒÎˇĪ‚ ({count})", "clear_file_cache": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ Ī„ÎˇĪ‚ Î ĪÎŋĪƒĪ‰ĪÎšÎŊÎŽĪ‚ ΜÎŊÎŽÎŧÎˇĪ‚ Î‘ĪĪ‡ÎĩÎ¯Ī‰ÎŊ", "clear_message": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ ÎŧΡÎŊĪÎŧÎąĪ„ÎŋĪ‚", "clear_value": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ Ī„ÎšÎŧÎŽĪ‚", + "client_cert_dialog_msg_confirm": "ΟΚ", + "client_cert_enter_password": "Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", "client_cert_import": "Î•ÎšĪƒÎąÎŗĪ‰ÎŗÎŽ", "client_cert_import_success_msg": "ΤÎŋ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēΌ Ī€ÎĩÎģÎŦĪ„Îˇ ÎĩÎšĪƒÎŦÎŗÎĩĪ„ÎąÎš", "client_cert_invalid_msg": "Μη Î­ÎŗÎē΅΁Îŋ ÎąĪĪ‡ÎĩίÎŋ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŋĪ ÎŽ ÎģÎŦθÎŋĪ‚ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "ΤÎŋ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēΌ Ī€ÎĩÎģÎŦĪ„Îˇ ÎēÎąĪ„ÎąĪÎŗÎŽÎ¸ÎˇÎēÎĩ", "client_cert_subtitle": "ÎĨĪ€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩΚ ÎŧΌÎŊÎŋ Ī„Îˇ ÎŧÎŋĪĪ†ÎŽ PKCS12 (.p12, .pfx). Η ÎĩÎšĪƒÎąÎŗĪ‰ÎŗÎŽ/ÎąĪ†ÎąÎ¯ĪÎĩĪƒÎˇ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŋĪ ÎĩίÎŊιΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧΡ ÎŧΌÎŊÎŋ Ī€ĪÎšÎŊ ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪÎŊδÎĩĪƒÎˇ", "client_cert_title": "Î ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēΌ SSL Ī€ÎĩÎģÎŦĪ„Îˇ [ΠΕΙΡΑΜΑΤΙΚΟ]", + "clockwise": "ΔÎĩÎžÎšĪŒĪƒĪ„ĪÎŋĪ†Îą", "close": "ΚÎģÎĩÎ¯ĪƒÎšÎŧÎŋ", "collapse": "ÎŖĪÎŧĪ€Ī„Ī…ÎžÎˇ", "collapse_all": "ÎŖĪÎŧĪ€Ī„Ī…ÎžÎˇ ΌÎģΉÎŊ", "color": "Î§ĪĪŽÎŧÎą", + "color_theme": "Î§ĪĪŽÎŧÎą θέÎŧÎąĪ„ÎŋĪ‚", "command": "ΕÎŊĪ„ÎŋÎģÎŽ", "command_palette_prompt": "Î“ĪÎŽÎŗÎŋĪÎˇ ÎĩĪĪÎĩĪƒÎˇ ΃ÎĩÎģÎ¯Î´Ī‰ÎŊ, ÎĩÎŊÎĩĪÎŗÎĩÎšĪŽÎŊ ÎŽ ÎĩÎŊĪ„ÎŋÎģĪŽÎŊ", "command_palette_to_close": "ÎŗÎšÎą ÎēÎģÎĩÎ¯ĪƒÎšÎŧÎŋ", @@ -747,7 +805,6 @@ "comments_are_disabled": "Τι ĪƒĪ‡ĪŒÎģΚι ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎą", "common_create_new_album": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊέÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "completed": "ΟÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ", - "configuration": "ÎĄĪÎ¸ÎŧÎšĪƒÎˇ", "confirm": "Î•Ī€ÎšÎ˛ÎĩÎ˛ÎąÎ¯Ī‰ĪƒÎˇ", "confirm_admin_password": "Î•Ī€ÎšÎ˛ÎĩÎ˛ÎąÎ¯Ī‰ĪƒÎˇ ÎēĪ‰Î´ÎšÎēÎŋĪ Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ", "confirm_delete_face": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ Ī„Îŋ Ī€ĪĪŒĪƒĪ‰Ī€Îŋ Ī„ÎŋĪ…/Ī„ÎˇĪ‚ {name} ÎąĪ€ĪŒ Ī„Îŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ;", @@ -762,16 +819,19 @@ "contain": "ΠÎĩĪÎšÎ­Ī‡ÎĩΚ", "context": "ÎŖĪ…ÎŧĪ†ĪÎąÎļΌÎŧÎĩÎŊÎą", "continue": "ÎŖĪ…ÎŊÎ­Ī‡ÎĩΚι", - "control_bottom_app_bar_add_tags": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Î•Ī„ÎšÎēÎĩĪ„ĪŽÎŊ", + "control_bottom_app_bar_create_new_album": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊέÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "control_bottom_app_bar_delete_from_immich": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ÎąĪ€ĪŒ Ī„Îŋ Immich", "control_bottom_app_bar_delete_from_local": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ", "control_bottom_app_bar_edit_location": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ΤÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚", "control_bottom_app_bar_edit_time": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚ & ÎĪÎąĪ‚", + "control_bottom_app_bar_share_link": "ΚÎŋΚÎŊÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îŋ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ", + "control_bottom_app_bar_share_to": "ΚÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŖÎĩ", "control_bottom_app_bar_trash_from_immich": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ĪƒĪ„Îą Î‘Ī€ÎŋĪĪÎ¯ÎŧÎŧÎąĪ„Îą", "copied_image_to_clipboard": "Η ÎĩΚÎēΌÎŊÎą ÎąÎŊĪ„ÎšÎŗĪÎŦĪ†ÎˇÎēÎĩ ĪƒĪ„Îŋ Ī€ĪĪŒĪ‡ÎĩÎšĪÎŋ.", "copied_to_clipboard": "ΑÎŊĪ„ÎšÎŗĪÎŦĪ†ÎˇÎēÎĩ ĪƒĪ„Îŋ Ī€ĪĪŒĪ‡ÎĩÎšĪÎŋ!", "copy_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎąÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽĪ‚", + "copy_file_path": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ Î´ÎšÎąÎ´ĪÎŋÎŧÎŽĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", "copy_image": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ ΕιÎēΌÎŊÎąĪ‚", - "copy_json": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ JSON", "copy_link": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…", "copy_link_to_clipboard": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ… ĪƒĪ„Îŋ Ī€ĪĪŒĪ‡ÎĩÎšĪÎŋ", "copy_password": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ ÎēĪ‰Î´ÎšÎēÎŋĪ", @@ -789,6 +849,7 @@ "create_link_to_share": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ… ÎŗÎšÎą δΚιÎŧÎŋÎšĪÎąĪƒÎŧΌ", "create_link_to_share_description": "Î•Ī€ÎšĪ„ĪÎ­ĪˆĪ„Îĩ ΃Îĩ ÎŋĪ€ÎŋΚÎŋÎŊÎ´ÎŽĪ€ÎŋĪ„Îĩ Î­Ī‡ÎĩΚ Ī„ÎŋÎŊ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ ÎŊÎą δÎĩΚ Ī„Îˇ/Ī„ÎšĪ‚ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΡ/ÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą/ÎĩĪ‚", "create_new": "ΔΗΜΙΟÎĨΡΓΙΑ ΝΕΟÎĨ", + "create_new_face": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊέÎŋĪ… ΀΁ÎŋĪƒĪŽĪ€ÎŋĪ…", "create_new_person": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊέÎŋĪ… ÎąĪ„ĪŒÎŧÎŋĪ…", "create_new_person_hint": "ΑÎŊĪ„ÎšĪƒĪ„ÎŋÎ¯Ī‡ÎšĪƒÎˇ ΄ΉÎŊ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΉÎŊ ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ ΃Îĩ έÎŊÎą ÎŊέÎŋ Ī€ĪĪŒĪƒĪ‰Ī€Îŋ", "create_new_user": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎŊέÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ", @@ -805,33 +866,37 @@ "created_at": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ", "creating_linked_albums": "ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ĪƒĪ…ÎŊδÎĩδÎĩÎŧέÎŊΉÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ...", "crop": "Î‘Ī€ÎŋÎēÎŋĪ€ÎŽ", + "crop_aspect_ratio_fixed": "ΔιÎŋĪÎ¸ĪŽÎ¸ÎˇÎēÎĩ", "crop_aspect_ratio_free": "ΕÎģÎĩĪÎ¸Îĩ΁Îŋ", "crop_aspect_ratio_original": "Î‘Ī…Î¸ÎĩÎŊĪ„ÎšÎēΌ", "crop_aspect_ratio_square": "ΤÎĩ΄΁ÎŦÎŗĪ‰ÎŊÎŋ", + "curated_object_page_title": "Î ĪÎŦÎŗÎŧÎąĪ„Îą", "current_device": "Î¤ĪÎ­Ī‡ÎŋĪ…ĪƒÎą ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ", "current_pin_code": "Î¤ĪÎ­Ī‡Ī‰ÎŊ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ PIN", "current_server_address": "Î¤ĪÎ­Ī‡ÎŋĪ…ĪƒÎą δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ", "custom_date": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊΡ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą", "custom_locale": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊΡ Ī„ÎŋĪ€ÎšÎēÎŽ ĪĪÎ¸ÎŧÎšĪƒÎˇ", "custom_locale_description": "ΜÎŋ΁ΆÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎšĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊίÎĩĪ‚, Ī„ÎŋĪ…Ī‚ Ī‡ĪĪŒÎŊÎŋĪ…Ī‚ ÎēιΚ Ī„ÎŋĪ…Ī‚ ÎąĪÎšÎ¸ÎŧÎŋĪĪ‚, ĪƒĪÎŧΆΉÎŊÎą ÎŧÎĩ Ī„ÎˇÎŊ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΡ ÎŗÎģĪŽĪƒĪƒÎą ÎēιΚ Ī€ÎĩĪÎšÎŋĪ‡ÎŽ", + "custom_url": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊΡ δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL", "cutoff_date_description": "Î”ÎšÎąĪ„ÎŽĪÎˇĪƒÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ ÎąĪ€ĪŒ Ī„ÎšĪ‚ Ī„ÎĩÎģÎĩĪ…Ī„ÎąÎ¯ÎĩĪ‚â€Ļ", "cutoff_day": "{count, plural, one {ΡÎŧÎ­ĪÎą} other {ΡÎŧÎ­ĪÎĩĪ‚}}", "cutoff_year": "{count, plural, one {Î­Ī„ÎŋĪ‚} other {Î­Ī„Îˇ}}", + "daily_title_text_date": "Ε, MMM dd", + "daily_title_text_date_year": "Ε, MMM dd, yyyy", "dark": "ÎŖÎēÎŋĪĪÎŋ", "dark_theme": "ΜÎĩĪ„ÎŦÎ˛ÎąĪƒÎˇ ΃Îĩ ΃ÎēÎŋĪ„ÎĩΚÎŊΌ θέÎŧÎą", "date": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą", "date_after": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŧÎĩĪ„ÎŦ", "date_and_time": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎēιΚ ĪŽĪÎą", "date_before": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą Ī€ĪÎšÎŊ", - "date_of_birth": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŗÎ­ÎŊÎŊÎˇĪƒÎˇĪ‚", + "date_format": "Ε, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Η ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŗÎ­ÎŊÎŊÎˇĪƒÎˇĪ‚ ÎąĪ€ÎŋθΡÎēÎĩĪĪ„ÎˇÎēÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡ĪŽĪ‚", "date_range": "Î•ĪĪÎŋĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎšĪŽÎŊ", - "date_time_original": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą/ÎĪÎą ÎģÎŽĪˆÎˇĪ‚", "day": "ΗÎŧÎ­ĪÎą", "days": "ΗÎŧÎ­ĪÎĩĪ‚", "deduplicate_all": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ΌÎģΉÎŊ ΄ΉÎŊ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ", - "default_quality_subtitle": "Η Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą Ī€ÎŋĪ… Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ĪŒĪ„ÎąÎŊ Ī€ÎąĪ„ÎŦĪ„Îĩ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ. ÎšĪÎąĪ„ÎŽĪƒĪ„Îĩ Ī€ÎąĪ„ÎˇÎŧέÎŊÎŋ Ī„Îŋ ÎēÎŋĪ…ÎŧĪ€Î¯ ÎēÎŋΚÎŊÎŽĪ‚ Ī‡ĪÎŽĪƒÎˇĪ‚ ÎŗÎšÎą ÎŊÎą ÎĩĪ€ÎšÎģÎ­ÎŗÎĩĪ„Îĩ ÎēÎŦθÎĩ ΆÎŋ΁ÎŦ.", - "default_share_quality": "Î ĪÎŋÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΡ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą δΚιÎŧÎŋÎšĪÎąĪƒÎŧÎŋĪ", + "default_locale": "Î ĪÎŋÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΡ ÎŗÎģĪŽĪƒĪƒÎą", + "default_locale_description": "ΜÎŋ΁ΆÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎšĪŽÎŊ ÎēιΚ ÎąĪÎšÎ¸ÎŧĪŽÎŊ, βÎŦĪƒÎˇ Ī„ÎˇĪ‚ ÎŗÎģĪŽĪƒĪƒÎąĪ‚ Ī„ÎŋĪ… ΀΁ÎŋÎŗĪÎŦÎŧÎŧÎąĪ„ÎŋĪ‚ Ī€ÎĩĪÎšÎŽÎŗÎˇĪƒÎˇĪ‚", "delete": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ", "delete_action_confirmation_message": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ ÎąĪĪ‡ÎĩίÎŋ; Î‘Ī…Ī„ÎŽ Ρ ÎĩÎŊÎ­ĪÎŗÎĩΚι θι Ī„Îŋ ÎŧÎĩĪ„ÎąÎēΚÎŊÎŽĪƒÎĩΚ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ÎēιΚ θι ÎĩÎŧĪ†ÎąÎŊÎšĪƒĪ„Îĩί ÎŧÎŽÎŊĪ…ÎŧÎą ÎŗÎšÎą Ī„Îŋ ÎąÎŊ θέÎģÎĩĪ„Îĩ ÎŊÎą Ī„Îŋ Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎēιΚ Ī„ÎŋĪ€ÎšÎēÎŦ", "delete_action_prompt": "{count} Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎąÎŊ", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Î‘Ī…Ī„ÎŦ Ī„Îą ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„Îŋ Immich ÎēιΚ ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ĪƒÎąĪ‚", "delete_dialog_alert_local": "Î‘Ī…Ī„ÎŦ Ī„Îą ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ĪƒÎąĪ‚ ÎąÎģÎģÎą θι Ī€ÎąĪÎąÎŧÎĩίÎŊÎŋĪ…ÎŊ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎą ĪƒĪ„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ Immich", "delete_dialog_alert_local_non_backed_up": "ΚÎŦĪ€ÎŋΚι ÎąĪ€ĪŒ Ī„Îą ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą δÎĩÎŊ Î­Ī‡ÎŋĪ…ÎŊ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Immich ÎēιΚ θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ ĪƒÎąĪ‚", + "delete_dialog_alert_remote": "Î‘Ī…Ī„ÎŦ Ī„Îą ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ Immich", + "delete_dialog_ok_force": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ĪŒĪ€Ī‰Ī‚ ÎēιΚ ÎŊÎą Î­Ī‡ÎĩΚ", "delete_dialog_title": "ÎŸĪÎšĪƒĪ„ÎšÎēÎŽ Î”ÎšÎąÎŗĪÎąĪ†ÎŽ", "delete_duplicates_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš ÎĩĪ€ÎšÎ¸Ī…ÎŧÎĩÎ¯Ī„Îĩ Ī„Îˇ ÎŧΌÎŊΚÎŧΡ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ÎąĪ…Ī„ĪŽÎŊ ΄ΉÎŊ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ;", "delete_face": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ΀΁ÎŋĪƒĪŽĪ€ÎŋĪ…", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ Ī„ÎˇÎŊ ÎĩĪ„ÎšÎēÎ­Ī„Îą {tagName};", "delete_user": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ Ī‡ĪÎŽĪƒĪ„Îˇ", "deleted_shared_link": "Ο ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„ÎŋĪ‚ ĪƒĪÎŊδÎĩ΃ÎŧÎŋĪ‚, Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎĩ", + "deletes_missing_assets": "Î”ÎšÎąÎŗĪÎŦΆÎĩΚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… ÎģÎĩÎ¯Ī€ÎŋĪ…ÎŊ ÎąĪ€ĪŒ Ī„Îŋ Î´Î¯ĪƒÎēÎŋ", "description": "ΠÎĩĪÎšÎŗĪÎąĪ†ÎŽ", + "description_input_hint_text": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚...", + "description_input_submit_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ Ī„ÎˇĪ‚ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚, ÎĩÎģÎ­ÎŗÎžĪ„Îĩ Ī„Îŋ ÎąĪĪ‡ÎĩίÎŋ ÎēÎąĪ„ÎąÎŗĪÎąĪ†ÎŽĪ‚ ÎŗÎšÎą Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ ÎģÎĩ΀΄ÎŋÎŧÎ­ĪÎĩΚÎĩĪ‚", "deselect_all": "ΑÎēĪĪĪ‰ĪƒÎˇ ΌÎģΉÎŊ ΄ΉÎŊ ÎĩĪ€ÎšÎģÎŋÎŗĪŽÎŊ", "details": "ΛÎĩ΀΄ÎŋÎŧÎ­ĪÎĩΚÎĩĪ‚", "direction": "ÎšÎąĪ„ÎĩĪÎ¸Ī…ÎŊĪƒÎˇ", "disable": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", "disabled": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋ", + "disallow_edits": "Î‘Ī€ÎąÎŗĪŒĪÎĩĪ…ĪƒÎˇ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎšĪŽÎŊ", "discord": "ΠÎģÎąĪ„Ī†ĪŒĪÎŧÎą Discord", "discover": "ΑÎŊÎ¯Ī‡ÎŊÎĩĪ…ĪƒÎˇ", "discovered_devices": "Î”ÎšÎąÎ¸Î­ĪƒÎšÎŧÎĩĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚", "dismiss_all_errors": "Î ÎąĪÎŦβÎģÎĩĪˆÎˇ ΌÎģΉÎŊ ΄ΉÎŊ ĪƒĪ†ÎąÎģÎŧÎŦ΄ΉÎŊ", + "dismiss_error": "Î ÎąĪÎŦβÎģÎĩĪˆÎˇ ĪƒĪ†ÎŦÎģÎŧÎąĪ„ÎŋĪ‚", "display_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇĪ‚", "display_order": "ÎŖÎĩÎšĪÎŦ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇĪ‚", "display_original_photos": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ Ī€ĪĪ‰Ī„ĪŒĪ„Ī…Ī€Ī‰ÎŊ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", @@ -876,9 +948,11 @@ "documentation": "ΤÎĩÎēÎŧÎˇĪÎ¯Ī‰ĪƒÎˇ", "done": "ÎˆÎŗÎšÎŊÎĩ", "download": "Î›ÎŽĪˆÎˇ", + "download_action_prompt": "ÎšÎąĪ„Î­Î˛ÎąĪƒÎŧÎą {count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "download_canceled": "Η ÎģÎŽĪˆÎˇ ÎąÎēĪ…ĪĪŽÎ¸ÎˇÎēÎĩ", "download_complete": "Η ÎģÎŽĪˆÎˇ ÎŋÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ", "download_enqueue": "Η ÎģÎŽĪˆÎˇ Ī„Î­Î¸ÎˇÎēÎĩ ΃Îĩ Îŋ΅΁ÎŦ", + "download_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎģÎŽĪˆÎˇĪ‚", "download_failed": "Η ÎģÎŽĪˆÎˇ ÎąĪ€Î­Ī„Ī…Ī‡Îĩ", "download_finished": "Η ÎģÎŽĪˆÎˇ ÎŋÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ", "download_include_embedded_motion_videos": "ΕÎŊĪƒĪ‰ÎŧÎąĪ„Ī‰ÎŧέÎŊÎą Î˛Î¯ÎŊĪ„ÎĩÎŋ", @@ -888,15 +962,15 @@ "download_paused": "Η ÎģÎŽĪˆÎˇ δΚιÎēĪŒĪ€ÎˇÎēÎĩ", "download_settings": "Î›ÎŽĪˆÎˇ", "download_settings_description": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ Ī€ÎŋĪ… ĪƒĪ‡ÎĩĪ„Î¯ÎļÎŋÎŊĪ„ÎąÎš ÎŧÎĩ Ī„Îˇ ÎģÎŽĪˆÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", + "download_started": "Η ÎģÎŽĪˆÎˇ ΞÎĩÎēίÎŊÎˇĪƒÎĩ", + "download_sucess": "Î•Ī€ÎšĪ„Ī…Ī‡Î¯Îą ÎģÎŽĪˆÎˇĪ‚", + "download_sucess_android": "ΤÎŋ ÎŧÎ­ĪƒÎŋ Î­Ī‡ÎĩΚ ÎģÎˇĪ†Î¸Îĩί ĪƒĪ„Îŋ DCIM/Immich", "download_waiting_to_retry": "ΑÎŊÎąÎŧÎŋÎŊÎŽ ÎŗÎšÎą ÎĩĪ€ÎąÎŊÎŦÎģÎˇĪˆÎˇ", "downloading": "ΓίÎŊÎĩĪ„ÎąÎš ÎģÎŽĪˆÎˇ", "downloading_asset_filename": "Î›ÎŽĪˆÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ… {filename}", "downloading_from_icloud": "Î›ÎŽĪˆÎˇ ÎąĪ€ĪŒ Ī„Îŋ iCloud", "downloading_media": "Î›ÎŽĪˆÎˇ Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ", - "drag_to_reorder": "ÎŖĪĪÎĩĪ„Îĩ ÎŗÎšÎą ÎąÎŊιδΚÎŦĪ„ÎąÎžÎˇ", "drop_files_to_upload": "ÎŖĪĪÎĩĪ„Îĩ ÎąĪĪ‡ÎĩÎ¯Îą ÎĩÎ´ĪŽ ÎŗÎšÎą ÎŊÎą Ī„Îą ÎąÎŊÎĩβÎŦ΃ÎĩĪ„Îĩ", - "duplicate": "Î”ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îŋ", - "duplicate_workflow": "Î”ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îˇ ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "duplicates": "Î”ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą", "duplicates_description": "Î•Ī€ÎšÎģĪĪƒĪ„Îĩ ÎēÎŦθÎĩ ÎŋÎŧÎŦδι Ī…Ī€ÎŋδÎĩΚÎēÎŊĪÎŋÎŊĪ„ÎąĪ‚ Ī€ÎŋΚÎĩĪ‚, ÎĩÎŦÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ, ÎĩίÎŊιΚ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€ÎĩĪ‚.", "duration": "ΔιÎŦ΁ÎēÎĩΚι", @@ -908,7 +982,9 @@ "edit_date_and_time": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚ ÎēιΚ ĪŽĪÎąĪ‚", "edit_date_and_time_action_prompt": "{count} ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎēιΚ ĪŽĪÎą ÎĩĪ€ÎĩΞÎĩĪÎŗÎŦĪƒĪ„ÎˇÎēÎąÎŊ", "edit_date_and_time_by_offset": "ΑÎģÎģÎąÎŗÎŽ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚ ÎŧÎĩ ÎŧÎĩĪ„ÎąĪ„ĪŒĪ€ÎšĪƒÎˇ", + "edit_date_and_time_by_offset_interval": "ΝέÎŋ ÎĩĪĪÎŋĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎšĪŽÎŊ: {from} - {to}", "edit_description": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚", + "edit_description_prompt": "Î ÎąĪÎąÎēÎąÎģĪŽ ÎĩĪ€ÎšÎģÎ­ÎžĪ„Îĩ ÎŊέι Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽ:", "edit_exclusion_pattern": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ÎŧÎŋĪ„Î¯Î˛ÎŋĪ… ÎąĪ€ÎŋÎēÎģÎĩÎšĪƒÎŧÎŋĪ", "edit_faces": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ΀΁ÎŋĪƒĪŽĪ€Ī‰ÎŊ", "edit_key": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ÎēÎģÎĩΚδΚÎŋĪ", @@ -923,6 +999,9 @@ "edit_user": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī‡ĪÎŽĪƒĪ„Îˇ", "edit_workflow": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "editor": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒĪ„ÎŽĪ‚", + "editor_close_without_save_prompt": "Î‘Ī…Ī„Î­Ī‚ ÎŋΚ ÎąÎģÎģÎąÎŗÎ­Ī‚ δÎĩÎŊ θι ÎąĪ€ÎŋθΡÎēÎĩĪ…Ī„ÎŋĪÎŊ", + "editor_close_without_save_title": "ΚÎģÎĩÎ¯ĪƒÎšÎŧÎŋ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒĪ„ÎŽ;", + "editor_confirm_reset_all_changes": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎĩĪ€ÎąÎŊÎąĪ†Î­ĪÎĩĪ„Îĩ ΌÎģÎĩĪ‚ Ī„ÎšĪ‚ ÎąÎģÎģÎąÎŗÎ­Ī‚;", "editor_discard_edits_confirm": "Î‘Ī€ĪŒĪĪÎšĪˆÎˇ ÎąÎģÎģÎąÎŗĪŽÎŊ", "editor_discard_edits_prompt": "ÎˆĪ‡ÎĩĪ„Îĩ ÎŧΡ ÎąĪ€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎĩĪ‚ ÎąÎģÎģÎąÎŗÎ­Ī‚. Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Ī„ÎšĪ‚ ÎąĪ€ÎŋĪĪÎ¯ĪˆÎĩĪ„Îĩ;", "editor_discard_edits_title": "Î‘Ī€ĪŒĪĪÎšĪˆÎˇ ÎąÎģÎģÎąÎŗĪŽÎŊ;", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Î•ÎšĪƒÎŦÎŗÎĩĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ PIN ĪƒÎąĪ‚", "enter_your_pin_code_subtitle": "Î•ÎšĪƒÎŦÎŗÎĩĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ PIN ĪƒÎąĪ‚ ÎŗÎšÎą ÎŊÎą ÎĩÎšĪƒÎ­ÎģθÎĩĪ„Îĩ ĪƒĪ„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ", "error": "ÎŖĪ†ÎŦÎģÎŧÎą", + "error_change_sort_album": "Î‘Ī€Î­Ī„Ī…Ī‡Îĩ Ρ ÎąÎģÎģÎąÎŗÎŽ ΃ÎĩÎšĪÎŦĪ‚ Ī„ÎŋĪ… ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "error_delete_face": "ÎŖĪ†ÎŦÎģÎŧÎą Î´ÎšÎąÎŗĪÎąĪ†ÎŽĪ‚ ΀΁ÎŋĪƒĪŽĪ€ÎŋĪ… ÎąĪ€ĪŒ Ī„Îŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ", + "error_getting_places": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎąÎŊÎŦÎēĪ„ÎˇĪƒÎˇ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎšĪŽÎŊ", "error_loading_albums": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„Îˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ ΄ΉÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "error_loading_image": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„Îˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī„ÎˇĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚", "error_loading_partners": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„Îˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ ĪƒĪ…ÎŊÎĩĪÎŗÎąĪ„ĪŽÎŊ: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎąĪ†ÎąÎ¯ĪÎĩĪƒÎˇĪ‚ ÎēÎģÎĩΚδΚÎŋĪ ΀΁ÎŋΊΌÎŊĪ„ÎŋĪ‚", "failed_to_reset_pin_code": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎĩĪ€ÎąÎŊÎąĪ†Îŋ΁ÎŦĪ‚ Ī„ÎŋĪ… PIN", "failed_to_stack_assets": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ĪƒĪ„ÎˇÎŊ ĪƒĪ…ÎŧĪ€Î¯ÎĩĪƒÎˇ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", - "failed_to_tag_assets": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ΀΁ÎŋĪƒÎ¸ÎŽÎēÎˇĪ‚ ÎĩĪ„ÎšÎēÎĩĪ„ĪŽÎŊ ĪƒĪ„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", "failed_to_unstack_assets": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ĪƒĪ„ÎˇÎŊ ÎąĪ€ÎŋĪƒĪ…ÎŧĪ€Î¯ÎĩĪƒÎˇ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "failed_to_update_notification_status": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇĪ‚ Ī„ÎˇĪ‚ ÎēÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇĪ‚ ÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", "incorrect_email_or_password": "ΛαÎŊÎ¸ÎąĪƒÎŧέÎŊÎŋ email ÎŽ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", @@ -1090,10 +1170,18 @@ "exif": "ΜÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊÎą Exif", "exif_bottom_sheet_description": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΠÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚...", "exif_bottom_sheet_description_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ Ī„ÎˇĪ‚ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽĪ‚", + "exif_bottom_sheet_details": "Î›Î•Î Î¤ÎŸÎœÎ•ÎĄÎ•Î™Î•ÎŖ", + "exif_bottom_sheet_location": "Î¤ÎŸÎ ÎŸÎ˜Î•ÎŖÎ™Î‘", "exif_bottom_sheet_no_description": "ΚαÎŧÎ¯Îą Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽ", + "exif_bottom_sheet_people": "ΑΤΟΜΑ", + "exif_bottom_sheet_person_add_person": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ÎŋÎŊΌÎŧÎąĪ„ÎŋĪ‚", "exit_slideshow": "ΈΞÎŋδÎŋĪ‚ ÎąĪ€ĪŒ Ī„ÎˇÎŊ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇ", "expand": "ΑÎŊÎŦĪ€Ī„Ī…ÎžÎˇ", "expand_all": "ΑÎŊÎŦĪ€Ī„Ī…ÎžÎˇ ΌÎģΉÎŊ", + "experimental_settings_new_asset_list_subtitle": "ÎŖÎĩ ÎĩΞέÎģΚΞΡ", + "experimental_settings_new_asset_list_title": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī€ÎĩÎšĪÎąÎŧÎąĪ„ÎšÎēÎŋĪ Ī€ÎģÎ­ÎŗÎŧÎąĪ„ÎŋĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", + "experimental_settings_subtitle": "Î§ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„Îĩ ÎŧÎĩ δΚÎēÎŽ ĪƒÎąĪ‚ ÎĩĪ…Î¸ĪÎŊΡ!", + "experimental_settings_title": "ΠÎĩÎšĪÎąÎŧÎąĪ„ÎšÎēΌ", "expire_after": "Î›ÎŽÎŗÎĩΚ ÎŧÎĩĪ„ÎŦ ÎąĪ€ĪŒ", "expired": "ΈÎģΡΞÎĩ", "expires_date": "Î›ÎŽÎŗÎĩΚ {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Î•ÎžÎąÎŗĪ‰ÎŗÎŽ Ή΂ JSON", "export_database": "Î•ÎžÎąÎŗĪ‰ÎŗÎŽ βÎŦĪƒÎˇĪ‚ δÎĩδÎŋÎŧέÎŊΉÎŊ", "export_database_description": "Î•ÎžÎąÎŗĪ‰ÎŗÎŽ Ī„ÎˇĪ‚ SQLite βÎŦĪƒÎˇĪ‚ δÎĩδÎŋÎŧέÎŊΉÎŊ", - "exposure_time": "Î§ĪĪŒÎŊÎŋĪ‚ ΈÎēθÎĩĪƒÎˇĪ‚", "extension": "Î•Ī€Î­ÎēĪ„ÎąĪƒÎˇ", "external": "Î•ÎžĪ‰Ī„ÎĩĪÎšÎēĪŒĪ‚", "external_libraries": "Î•ÎžĪ‰Ī„ÎĩĪÎšÎēÎ­Ī‚ βΚβÎģΚÎŋθΎÎēÎĩĪ‚", "external_network": "Î•ÎžĪ‰Ī„ÎĩĪÎšÎēΌ δίÎē΄΅Îŋ", "external_network_sheet_info": "ÎŒĪ„ÎąÎŊ δÎĩÎŊ ÎĩÎ¯ĪƒĪ„Îĩ ĪƒĪ…ÎŊδÎĩδÎĩÎŧέÎŊÎŋΚ ĪƒĪ„Îŋ ΀΁ÎŋĪ„ÎšÎŧĪŽÎŧÎĩÎŊÎŋ δίÎē΄΅Îŋ Wi-Fi, Ρ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ θι ĪƒĪ…ÎŊδÎĩθÎĩί ÎŧÎĩ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ÎŧÎ­ĪƒĪ‰ Ī„ÎŋĪ… Ī€ĪĪŽĪ„ÎŋĪ… ÎąĪ€ĪŒ Ī„Îą Ī€ÎąĪÎąÎēÎŦ΄Ή URLs Ī€ÎŋĪ… ÎŧĪ€Îŋ΁Îĩί ÎŊÎą Î˛ĪÎĩΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋ, ΞÎĩÎēΚÎŊĪŽÎŊĪ„ÎąĪ‚ ÎąĪ€ĪŒ Ī„Îŋ Ī€ÎŦÎŊΉ ΀΁ÎŋĪ‚ Ī„Îŋ ÎēÎŦ΄Ή", - "f_number": "ΔιÎŦĪ†ĪÎąÎŗÎŧÎą", "face_unassigned": "Μη ÎąÎŊÎąĪ„ÎĩθÎĩΚÎŧέÎŊÎŋ", "failed": "Î‘Ī€Î­Ī„Ī…Ī‡Îĩ", "failed_count": "Î‘Ī€ÎŋĪ„Ī…Ī‡ÎˇÎŧέÎŊÎą: {count}", "failed_to_authenticate": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Ī„ÎąĪ…Ī„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", - "failed_to_delete_file": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Î´ÎšÎąÎŗĪÎąĪ†ÎŽĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", "failed_to_load_assets": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Ī†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "failed_to_load_folder": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Ī†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ Ī†ÎąÎēέÎģÎŋĪ…", "favorite": "Î‘ÎŗÎąĪ€ÎˇÎŧέÎŊÎŋ", "favorite_action_prompt": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {count} ĪƒĪ„Îą Î‘ÎŗÎąĪ€ÎˇÎŧέÎŊÎą", "favorite_or_unfavorite_photo": "ÎŸĪÎ¯ĪƒĪ„Îĩ ÎŧÎ¯Îą ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą Ή΂ ÎąÎŗÎąĪ€ÎˇÎŧέÎŊΡ ÎŽ ÎąĪ†ÎąÎšĪÎ­ĪƒĪ„Îĩ Ī„ÎˇÎŊ ÎąĪ€ĪŒ Ī„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", "favorites": "Î‘ÎŗÎąĪ€ÎˇÎŧέÎŊÎą", + "favorites_page_no_favorites": "ΔÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", "feature_photo_updated": "Η ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ΀΁ÎŋβÎŋÎģÎŽĪ‚ ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ", "features": "Î§ÎąĪÎąÎēĪ„ÎˇĪÎšĪƒĪ„ÎšÎēÎŦ", + "features_in_development": "ΛÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯ÎĩĪ‚ Ī…Ī€ĪŒ ΑÎŊÎŦĪ€Ī„Ī…ÎžÎˇ", "features_setting_description": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„Îą Ī‡ÎąĪÎąÎēĪ„ÎˇĪÎšĪƒĪ„ÎšÎēÎŦ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", "file_name_or_extension": "ΌÎŊÎŋÎŧÎą ÎąĪĪ‡ÎĩίÎŋĪ… ÎŽ ÎĩĪ€Î­ÎēĪ„ÎąĪƒÎˇ", "file_name_text": "ΌÎŊÎŋÎŧÎą ÎąĪĪ‡ÎĩίÎŋĪ…", + "file_name_with_value": "ΌÎŊÎŋÎŧÎą ÎąĪĪ‡ÎĩίÎŋĪ…: {file_name}", "file_size": "ÎœÎ­ÎŗÎĩθÎŋĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", "filename": "ΟÎŊÎŋÎŧÎąĪƒÎ¯Îą ÎąĪĪ‡ÎĩίÎŋĪ…", "filetype": "Î¤ĪĪ€ÎŋĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", "filter": "ÎĻίÎģ΄΁Îŋ", + "filter_description": "ÎŖĪ…ÎŊθΎÎēÎĩĪ‚ ÎŗÎšÎą Ī†ÎšÎģ΄΁ÎŦĪÎšĪƒÎŧÎą ΄ΉÎŊ ĪƒĪ„Îŋ·ÎĩĪ…ÎŧέÎŊΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "filter_people": "ÎĻΚÎģ΄΁ÎŦĪÎšĪƒÎŧÎą ÎąĪ„ĪŒÎŧΉÎŊ", "filter_places": "ÎĻΚÎģ΄΁ÎŦĪÎšĪƒÎŧÎą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎšĪŽÎŊ", "filter_tags": "ÎĻΚÎģ΄΁ÎŦĪÎšĪƒÎŧÎą ÎĩĪ„ÎšÎēÎĩĪ„ĪŽÎŊ", "filters": "ÎĻίÎģĪ„ĪÎą", + "find_them_fast": "Î’ĪÎĩÎ¯Ī„Îĩ Ī„ÎŋĪ…Ī‚ ÎŗĪÎŽÎŗÎŋĪÎą ÎŧÎĩ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ ΌÎŊÎŋÎŧÎą", "first": "Î‘ĪĪ‡ÎšÎēÎŦ", "fix_incorrect_match": "Î”ÎšĪŒĪÎ¸Ī‰ĪƒÎˇ ÎģÎąÎŊÎ¸ÎąĪƒÎŧέÎŊÎˇĪ‚ ÎąÎŊĪ„ÎšĪƒĪ„ÎŋÎ¯Ī‡ÎšĪƒÎˇĪ‚", - "focal_length": "Î•ĪƒĪ„ÎšÎąÎēÎŽ Î‘Ī€ĪŒĪƒĪ„ÎąĪƒÎˇ", "folder": "ÎĻÎŦÎēÎĩÎģÎŋĪ‚", "folder_not_found": "Ο ΆÎŦÎēÎĩÎģÎŋĪ‚ δÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎĩ", "folders": "ÎĻÎŦÎēÎĩÎģÎŋΚ", @@ -1147,7 +1236,6 @@ "free_up_space_description": "ΜÎĩĪ„ÎąÎēΚÎŊÎŽĪƒĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… Î­Ī‡ÎŋĪ…ÎŊ ÎąÎŊĪ„ÎšÎŗĪÎąĪ†Îĩί ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ Ī„ÎˇĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽĪ‚ ĪƒÎąĪ‚ ÎŗÎšÎą ÎŊÎą ÎąĪ€ÎĩÎģÎĩĪ…Î¸ÎĩĪĪŽĪƒÎĩĪ„Îĩ Ī‡ĪŽĪÎŋ. Τι ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†ÎŦ ĪƒÎąĪ‚ ĪƒĪ„Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ, Ī€ÎąĪÎąÎŧέÎŊÎŋĪ…ÎŊ ÎąĪƒĪ†ÎąÎģÎŽ.", "free_up_space_settings_subtitle": "Î‘Ī€ÎĩÎģÎĩĪ…Î¸Î­ĪĪ‰ĪƒÎˇ Ī‡ĪŽĪÎŋĪ… ĪƒĪ„Îˇ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽ", "full_path": "ΠÎģÎŽĪÎˇĪ‚ Î´ÎšÎąÎ´ĪÎŋÎŧÎŽ: {path}", - "full_path_or_folder": "ΠÎģÎŽĪÎˇĪ‚ Î´ÎšÎąÎ´ĪÎŋÎŧÎŽ ÎŽ ΆÎŦÎēÎĩÎģÎŋĪ‚", "gcast_enabled": "ΜÎĩĪ„ÎŦδÎŋĪƒÎˇ Ī€ÎĩĪÎšÎĩ·ÎŋÎŧέÎŊÎŋĪ… Google Cast", "gcast_enabled_description": "Î‘Ī…Ī„ĪŒ Ī„Îŋ Ī‡ÎąĪÎąÎēĪ„ÎˇĪÎšĪƒĪ„ÎšÎēΌ ΆÎŋĪĪ„ĪŽÎŊÎĩΚ ÎĩÎžĪ‰Ī„ÎĩĪÎšÎēÎŋĪĪ‚ Ī€ĪŒĪÎŋĪ…Ī‚ ÎąĪ€ĪŒ Ī„Îˇ Google ÎŗÎšÎą ÎŊÎą ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎŽĪƒÎĩΚ.", "general": "ΓÎĩÎŊΚÎēÎŦ", @@ -1168,6 +1256,7 @@ "group_owner": "ΟÎŧιδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ ΚδΚÎŋÎēĪ„ÎŽĪ„Îˇ", "group_places_by": "ΟÎŧÎŋδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎšĪŽÎŊ ÎēÎąĪ„ÎŦ...", "group_year": "ΟÎŧιδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ Î­Ī„ÎŋĪ‚", + "haptic_feedback_switch": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎąĪ€Ī„ÎšÎēÎŽĪ‚ ÎąÎŊÎŦÎ´ĪÎąĪƒÎˇĪ‚", "haptic_feedback_title": "Î‘Ī€Ī„ÎšÎēÎŽ ΑÎŊÎŦÎ´ĪÎąĪƒÎˇ", "has_quota": "ÎˆĪ‡ÎĩΚ Ī€ÎŋĪƒĪŒĪƒĪ„Ī‰ĪƒÎˇ", "hash_asset": "ÎšÎąĪ„ÎąÎēÎĩ΁ÎŧÎąĪ„ÎšĪƒÎŧĪŒĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", @@ -1188,12 +1277,29 @@ "hide_schema": "Î‘Ī€ĪŒÎēĪĪ…ĪˆÎˇ ĪƒĪ‡ÎŽÎŧÎąĪ„ÎŋĪ‚", "hide_text_recognition": "Î‘Ī€ĪŒÎēĪĪ…ĪˆÎˇ ÎąÎŊÎąÎŗÎŊĪŽĪÎšĪƒÎˇĪ‚ ÎēÎĩΚÎŧέÎŊÎŋĪ…", "hide_unnamed_people": "Î‘Ī€ĪŒÎēĪĪ…ĪˆÎˇ ÎąĪ„ĪŒÎŧΉÎŊ Ī‡Ī‰ĪÎ¯Ī‚ ΌÎŊÎŋÎŧÎą", + "home_page_add_to_album_conflicts": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {added} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ {album}. {failed} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ ΎδΡ ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ.", + "home_page_add_to_album_err_local": "ΔÎĩÎŊ ÎĩίÎŊιΚ ÎąÎēΌÎŧΡ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ΀΁ÎŋĪƒÎ¸ÎŽÎēΡ Ī„ÎŋĪ€ÎšÎēĪŽÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ, Ī€ÎąĪÎŦβÎģÎĩĪˆÎˇ", + "home_page_add_to_album_success": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {added} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ {album}.", + "home_page_album_err_partner": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ΀΁ÎŋĪƒÎ¸ÎŽÎēΡ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ… ΃Îĩ έÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎąÎēΌÎŧÎą, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_archive_err_local": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎąĪĪ‡ÎĩΚÎŋÎ¸Î­Ī„ÎˇĪƒÎˇ Ī„ÎŋĪ€ÎšÎēĪŽÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąÎēΌÎŧÎą, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_archive_err_partner": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎąĪĪ‡ÎĩΚÎŋÎ¸Î­Ī„ÎˇĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ…, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", "home_page_building_timeline": "Î§Ī„Î¯ÎļÎĩĪ„ÎąÎš Ī„Îŋ ·΁ÎŋÎŊÎŋδΚÎŦÎŗĪÎąÎŧÎŧÎą", + "home_page_delete_err_partner": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ…, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_delete_remote_err_local": "ΤÎŋĪ€ÎšÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒĪ„Îˇ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ÎąĪ€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊÎˇĪ‚ ÎĩĪ€ÎšÎģÎŋÎŗÎŽĪ‚, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_favorite_err_local": "ΔÎĩÎŊ ÎŧĪ€ÎŋĪĪŽ ÎąÎēΌÎŧÎą ÎŊÎą ÎąÎŗÎąĪ€ÎŽĪƒĪ‰ Ī„Îą Ī„ÎŋĪ€ÎšÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_favorite_err_partner": "ΔÎĩÎŊ ÎĩίÎŊιΚ ÎąÎēΌÎŧÎą Î´Ī…ÎŊÎąĪ„ÎŽ Ρ Ī€ĪĪŒĪƒÎ¸ÎĩĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ… ĪƒĪ„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_first_time_notice": "ΕÎŦÎŊ ÎąĪ…Ī„ÎŽ ÎĩίÎŊιΚ Ρ Ī€ĪĪŽĪ„Îˇ ΆÎŋ΁ÎŦ Ī€ÎŋĪ… Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„Îĩ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ, βÎĩÎ˛ÎąÎšĪ‰Î¸ÎĩÎ¯Ī„Îĩ ĪŒĪ„Îš Î­Ī‡ÎĩĪ„Îĩ ÎĩĪ€ÎšÎģέΞÎĩΚ έÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†ÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚, ĪŽĪƒĪ„Îĩ Ī„Îŋ ·΁ÎŋÎŊÎŋδΚÎŦÎŗĪÎąÎŧÎŧÎą ÎŊÎą ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ĪƒĪ…ÎŧĪ€ÎģÎˇĪĪŽĪƒÎĩΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ĪƒĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "home_page_locked_error_local": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎŧÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ Ī„ÎŋĪ€ÎšÎēĪŽÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ĪƒĪ„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ, Ī€ÎąĪÎŦβÎģÎĩĪˆÎˇ", + "home_page_locked_error_partner": "ΔÎĩÎŊ ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą ÎŧÎĩĪ„ÎąÎēΚÎŊÎŽĪƒÎĩĪ„Îĩ Ī„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒĪ…ÎŊÎĩĪÎŗÎŦĪ„Îˇ ΃Îĩ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ, Ī€ÎąĪÎŦÎģÎĩÎšĪˆÎˇ", + "home_page_share_err_local": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ Ī„ÎŋĪ€ÎšÎēĪŽÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎŧÎ­ĪƒĪ‰ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "home_page_upload_err_limit": "ÎœĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą ÎąÎŊÎĩβÎŦ΃ÎĩĪ„Îĩ ÎŧΌÎŊÎŋ 30 ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎēÎŦθÎĩ ΆÎŋ΁ÎŦ, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", "host": "ÎĻΚÎģÎŋΞÎĩÎŊÎ¯Îą", "hour": "ÎĪÎą", "hours": "ÎĪÎĩĪ‚", "id": "ID", "idle": "Î‘Î´ĪÎŦÎŊÎĩΚι", + "ignore_icloud_photos": "Î‘ÎŗÎŊÎŋÎŽĪƒĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ iCloud", + "ignore_icloud_photos_description": "Οι ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ Ī€ÎŋĪ… ÎĩίÎŊιΚ ÎąĪ€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎĩĪ‚ ĪƒĪ„Îŋ iCloud δÎĩÎŊ θι ÎŧÎĩĪ„ÎąĪ†ÎŋĪĪ„Ī‰Î¸ÎŋĪÎŊ ĪƒĪ„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ Immich", "image": "ΕιÎēΌÎŊÎą", "image_alt_text_date": "{isVideo, select, true {ΒίÎŊĪ„ÎĩÎŋ} other {ΕιÎēΌÎŊÎą}} Ī€ÎŋĪ… Ī„ĪÎąÎ˛ÎŽĪ‡Ī„ÎˇÎēÎĩ ĪƒĪ„ÎšĪ‚ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {ΒίÎŊĪ„ÎĩÎŋ} other {ΕιÎēΌÎŊÎą}} Ī€ÎŋĪ… Ī„ĪÎąÎ˛ÎŽĪ‡Ī„ÎˇÎēÎĩ ÎŧÎĩ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person1} ĪƒĪ„ÎšĪ‚ {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {ΒίÎŊĪ„ÎĩÎŋ} other {ΕιÎēΌÎŊÎą}} Ī„ĪÎąÎ˛ÎˇÎŗÎŧέÎŊÎŋ ĪƒĪ„ÎˇÎŊ/ĪƒĪ„ÎŋÎŊ {city}, {country} ÎŧÎĩ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person1} ÎēιΚ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person2} ĪƒĪ„ÎšĪ‚ {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {ΒίÎŊĪ„ÎĩÎŋ} other {ΕιÎēΌÎŊÎą}} Ī„ĪÎąÎ˛ÎˇÎŗÎŧέÎŊÎŋ ĪƒĪ„ÎˇÎŊ/ĪƒĪ„ÎŋÎŊ {city}, {country} ÎŧÎĩ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person1}, Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person2} ÎēιΚ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person3} ĪƒĪ„ÎšĪ‚ {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {ΒίÎŊĪ„ÎĩÎŋ} other {ΕιÎēΌÎŊÎą}} Ī„ĪÎąÎ˛ÎˇÎŗÎŧέÎŊÎŋ ĪƒĪ„ÎŋÎŊ/ĪƒĪ„ÎˇÎŊ {city}, {country} ÎŧÎĩ Ī„ÎŋÎŊ/Ī„ÎˇÎŊ {person1}, {person2}, ÎēιΚ {additionalCount, number} ÎŦÎģÎģÎŋĪ…Ī‚ ĪƒĪ„ÎšĪ‚ {date}", + "image_saved_successfully": "Η ÎĩΚÎēΌÎŊÎą ÎąĪ€ÎŋθΡÎēÎĩĪĪ„ÎˇÎēÎĩ", + "image_viewer_page_state_provider_download_started": "ΞÎĩÎēίÎŊÎˇĪƒÎĩ Î›ÎŽĪˆÎˇ", + "image_viewer_page_state_provider_download_success": "Î•Ī€ÎšĪ„Ī…Ī‡Î¯Îą Î›ÎŽĪˆÎˇĪ‚", + "image_viewer_page_state_provider_share_error": "ÎŖĪ†ÎŦÎģÎŧÎą ΚÎŋΚÎŊÎŽĪ‚ Î§ĪÎŽĪƒÎˇĪ‚", "immich_logo": "ΛÎŋÎŗĪŒĪ„Ī…Ī€Îŋ Immich", "immich_web_interface": "Î™ĪƒĪ„ĪŒĪ„ÎŋĪ€ÎŋĪ‚ Immich", "import_from_json": "Î•ÎšĪƒÎąÎŗĪ‰ÎŗÎŽ ÎąĪ€ĪŒ ÎąĪĪ‡ÎĩίÎŋ JSON", @@ -1219,7 +1329,6 @@ "individual_share": "ΜÎĩÎŧÎŋÎŊΉÎŧέÎŊÎŋĪ‚ δΚιÎŧÎŋÎšĪÎąĪƒÎŧĪŒĪ‚", "individual_shares": "ΜÎĩÎŧÎŋÎŊΉÎŧέÎŊÎĩĪ‚ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚", "info": "ΠÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚", - "integrity_checks": "ΈÎģÎĩÎŗĪ‡ÎŋΚ ΑÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", "interval": { "day_at_onepm": "ΚÎŦθÎĩ ÎŧÎ­ĪÎą ĪƒĪ„Îˇ 1ÎŧÎŧ", "hours": "ΚÎŦθÎĩ {hours, plural, one {ĪŽĪÎą} other {{hours, number} ĪŽĪÎĩĪ‚}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Μη Î­ÎŗÎēĪ…ĪÎˇ ÎŧÎŋĪĪ†ÎŽ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚", "invite_people": "Î ĪĪŒĪƒÎēÎģÎˇĪƒÎˇ Î‘Ī„ĪŒÎŧΉÎŊ", "invite_to_album": "Î ĪĪŒĪƒÎēÎģÎˇĪƒÎˇ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Η ÎąÎŊÎŦÎēĪ„ÎˇĪƒÎˇ ÎĩÎēĪ„ÎĩÎģÎ­ĪƒĪ„ÎˇÎēÎĩ ĪƒĪ„ÎšĪ‚ {dateTime}", + "ios_debug_info_last_sync_at": "ΤÎĩÎģÎĩĪ…Ī„ÎąÎ¯ÎŋĪ‚ ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚ ĪƒĪ„ÎšĪ‚ {dateTime}", + "ios_debug_info_no_processes_queued": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ δΚÎĩĪÎŗÎąĪƒÎ¯ÎĩĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ… ΃Îĩ Îŋ΅΁ÎŦ", + "ios_debug_info_no_sync_yet": "ΔÎĩÎŊ Î­Ī‡ÎĩΚ ÎĩÎēĪ„ÎĩÎģÎĩĪƒĪ„Îĩί, ÎŗÎšÎą Ī„ÎˇÎŊ ĪŽĪÎą, ÎēÎąÎŧÎ¯Îą ÎĩĪÎŗÎąĪƒÎ¯Îą ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧÎŋĪ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} δΚÎĩĪÎŗÎąĪƒÎ¯Îą Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ… ΃Îĩ ÎąÎŊÎąÎŧÎŋÎŊÎŽ} other {{count} δΚÎĩĪÎŗÎąĪƒÎ¯ÎĩĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ… ΃Îĩ ÎąÎŊÎąÎŧÎŋÎŊÎŽ}}", + "ios_debug_info_processing_ran_at": "Η ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą ÎĩÎēĪ„ÎĩÎģÎ­ĪƒĪ„ÎˇÎēÎĩ ĪƒĪ„ÎšĪ‚ {dateTime}", "items_count": "{count, plural, one {# ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎŋ} other {# ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą}}", "jobs": "Î•ĪÎŗÎąĪƒÎ¯ÎĩĪ‚", + "json_editor": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒĪ„ÎŽĪ‚ JSON", + "json_error": "ÎŖĪ†ÎŦÎģÎŧÎą JSON", "keep": "Î”ÎšÎąĪ„ÎŽĪÎˇĪƒÎˇ", "keep_albums": "Î”ÎšÎąĪ„ÎŽĪÎˇĪƒÎˇ ΄ΉÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "keep_albums_count": "Î”ÎšÎąĪ„ÎˇĪÎŋĪÎŊĪ„ÎąÎš {count} {count, plural, one {ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ} other {ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ}}", @@ -1259,12 +1375,14 @@ "leave": "Î•ÎŗÎēÎąĪ„ÎŦÎģÎĩÎšĪˆÎˇ", "leave_album": "Î‘Ī€ÎŋĪ‡ĪŽĪÎˇĪƒÎˇ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "lens_model": "ΜÎŋÎŊĪ„Î­ÎģÎŋ Ī†ÎąÎēÎŋĪ", - "less": "Î›ÎšÎŗĪŒĪ„ÎĩĪÎą", "let_others_respond": "Î•Ī€Î­Ī„ĪÎĩΈÎĩ ΃Îĩ ÎŦÎģÎģÎŋĪ…Ī‚ ÎŊÎą ÎąĪ€ÎąÎŊĪ„ÎŽĪƒÎŋĪ…ÎŊ", "level": "Î•Ī€Î¯Ī€ÎĩδÎŋ", "library": "ΒιβÎģΚÎŋθΎÎēΡ", "library_add_folder": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ Ī†ÎąÎēέÎģÎŋĪ…", "library_edit_folder": "Î•Ī€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī†ÎąÎēέÎģÎŋĪ…", + "library_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ βΚβÎģΚÎŋθΎÎēÎˇĪ‚", + "library_page_device_albums": "ΆÎģÎŧĪ€ÎŋĪ…Îŧ ĪƒĪ„Îˇ ÎŖĪ…ĪƒÎēÎĩĪ…ÎŽ", + "library_page_new_album": "ΝέÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "library_page_sort_asset_count": "Î‘ĪÎšÎ¸ÎŧĪŒĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "library_page_sort_created": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚", "library_page_sort_last_modified": "ΤÎĩÎģÎĩĪ…Ī„ÎąÎ¯Îą ΄΁ÎŋĪ€ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", @@ -1274,14 +1392,11 @@ "light_theme": "ΜÎĩĪ„ÎŦÎ˛ÎąĪƒÎˇ ΃Îĩ ΆΉ΄ÎĩΚÎŊΌ θέÎŧÎą", "like": "ΜÎŋĪ… ÎąĪÎ­ĪƒÎĩΚ", "like_deleted": "ΤÎŋ \"ÎŧÎŋĪ… ÎąĪÎ­ĪƒÎĩΚ\" Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎĩ", - "link": "ÎŖĪÎŊδÎĩ΃ÎŧÎŋĪ‚", "link_motion_video": "ÎŖĪÎŊδÎĩ΃Îĩ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎēίÎŊÎˇĪƒÎˇĪ‚", "link_to_docs": "Για Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚, ÎąÎŊÎąĪ„ĪÎ­ÎžĪ„Îĩ ĪƒĪ„ÎˇÎŊ Ī„ÎĩÎēÎŧÎˇĪÎ¯Ī‰ĪƒÎˇ.", "link_to_oauth": "ÎŖĪÎŊδÎĩĪƒÎˇ ĪƒĪ„ÎŋÎŊ OAuth", "linked_oauth_account": "Ο OAuth ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧĪŒĪ‚ ĪƒĪ…ÎŊδέθΡÎēÎĩ", "list": "Î›Î¯ĪƒĪ„Îą", - "live": "Î–Ī‰ÎŊĪ„ÎąÎŊÎŦ", - "load_more": "ÎĻĪŒĪĪ„Ī‰ĪƒÎˇ ΠÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ΉÎŊ", "loading": "ÎĻĪŒĪĪ„Ī‰ĪƒÎˇ", "loading_search_results_failed": "Η Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎąĪ€ÎŋĪ„ÎĩÎģÎĩ΃ÎŧÎŦ΄ΉÎŊ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚ ÎąĪ€Î­Ī„Ī…Ī‡Îĩ", "local": "ΤÎŋĪ€ÎšÎēÎŦ", @@ -1310,9 +1425,11 @@ "login": "Î•Î¯ĪƒÎŋδÎŋĪ‚", "login_disabled": "Η ĪƒĪÎŊδÎĩĪƒÎˇ Î­Ī‡ÎĩΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡθÎĩί", "login_form_api_exception": "Î•ÎžÎąÎ¯ĪÎĩĪƒÎˇ API. ΕÎģÎ­ÎŗÎžĪ„Îĩ Ī„Îˇ δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ÎēιΚ δÎŋÎēΚÎŧÎŦĪƒĪ„Îĩ ΞιÎŊÎŦ.", + "login_form_back_button_text": "Î Î¯ĪƒĪ‰", "login_form_email_hint": "to-email-sou@email.com", "login_form_endpoint_hint": "http://ip-tou-server-sou:porta", "login_form_endpoint_url": "URL Ī„ÎĩÎģΚÎēÎŋĪ ĪƒÎˇÎŧÎĩίÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ", + "login_form_err_http": "Î ĪÎŋĪƒÎ´ÎšÎŋĪÎ¯ĪƒĪ„Îĩ http:// ÎŽ https://", "login_form_err_invalid_email": "Μη Î­ÎŗÎē΅΁Îŋ email", "login_form_err_invalid_url": "Μη Î­ÎŗÎēĪ…ĪÎˇ δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL", "login_form_err_leading_whitespace": "ΚÎĩÎŊΌ δΚÎŦĪƒĪ„ÎˇÎŧÎą ĪƒĪ„ÎˇÎŊ ÎąĪĪ‡ÎŽ", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„Îˇ ĪƒĪÎŊδÎĩĪƒÎŽ ĪƒÎąĪ‚, ÎĩÎģÎ­ÎŗÎžĪ„Îĩ Ī„Îˇ δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ, Ī„Îŋ email ÎēιΚ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", "login_form_handshake_exception": "ÎĨĪ€ÎŽĪÎžÎĩ ĪƒĪ†ÎŦÎģÎŧÎą ·ÎĩÎšĪÎąĪˆÎ¯ÎąĪ‚ ÎŧÎĩ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ. ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎˇÎŊ Ī…Ī€ÎŋĪƒĪ„ÎŽĪÎšÎžÎˇ ÎąĪ…Ī„Îŋ-Ī…Ī€ÎŋÎŗÎĩÎŗĪÎąÎŧÎŧέÎŊÎŋĪ… Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēÎŋĪ ĪƒĪ„ÎšĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎĩÎŦÎŊ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„Îĩ ÎąĪ…Ī„Îŋ-Ī…Ī€ÎŋÎŗÎĩÎŗĪÎąÎŧÎŧέÎŊÎŋ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋÎšÎˇĪ„ÎšÎēΌ.", "login_form_password_hint": "ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", - "login_form_server_empty": "Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ ÎŧΚι δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ", - "login_form_server_error": "ΔÎĩÎŊ ÎŽĪ„ÎąÎŊ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ĪƒĪÎŊδÎĩĪƒÎˇ ÎŧÎĩ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ", - "login_has_been_disabled": "Η ĪƒĪÎŊδÎĩĪƒÎˇ Î­Ī‡ÎĩΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡθÎĩί", + "login_form_save_login": "ΜÎĩίÎŊÎĩĪ„Îĩ ĪƒĪ…ÎŊδÎĩδÎĩÎŧέÎŊÎŋΚ", + "login_form_server_empty": "Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ ÎŧΚι δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ.", + "login_form_server_error": "ΔÎĩÎŊ ÎŽĪ„ÎąÎŊ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ĪƒĪÎŊδÎĩĪƒÎˇ ÎŧÎĩ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ.", + "login_has_been_disabled": "Η ĪƒĪÎŊδÎĩĪƒÎˇ Î­Ī‡ÎĩΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡθÎĩί.", "login_password_changed_error": "Î ÎąĪÎŋĪ…ĪƒÎšÎŦĪƒĪ„ÎˇÎēÎĩ ĪƒĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ Ī„ÎŋĪ… ÎēĪ‰Î´ÎšÎēÎŋĪ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎŽĪ‚ ĪƒÎąĪ‚", "login_password_changed_success": "Ο ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚ ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", "logout_all_device_confirmation": "Î•Î¯ĪƒĪ„Îĩ βέβιΚÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ€ÎŋĪƒĪ…ÎŊδÎĩθÎĩÎ¯Ī„Îĩ ÎąĪ€ĪŒ ΌÎģÎĩĪ‚ Ī„ÎšĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚;", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ βÎŦĪƒÎˇĪ‚ δÎĩδÎŋÎŧέÎŊΉÎŊ", "maintenance_description": "ΤÎŋ Immich Î­Ī‡ÎĩΚ Ī„ÎĩθÎĩί ΃Îĩ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą ĪƒĪ…ÎŊĪ„ÎŽĪÎˇĪƒÎˇĪ‚.", "maintenance_end": "ΤÎĩ΁ÎŧÎąĪ„ÎšĪƒÎŧĪŒĪ‚ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ĪƒĪ…ÎŊĪ„ÎŽĪÎˇĪƒÎˇĪ‚", + "maintenance_end_error": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą Ī„Îĩ΁ÎŧÎąĪ„ÎšĪƒÎŧÎŋĪ Ī„ÎˇĪ‚ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ĪƒĪ…ÎŊĪ„ÎŽĪÎˇĪƒÎˇĪ‚.", "maintenance_logged_in_as": "Î‘Ī…Ī„ÎŽÎŊ Ī„Îˇ ĪƒĪ„ÎšÎŗÎŧÎŽ ÎĩÎ¯ĪƒĪ„Îĩ ĪƒĪ…ÎŊδÎĩδÎĩÎŧέÎŊÎŋĪ‚ Ή΂ {user}", "maintenance_restore_from_backup": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎąĪ€ĪŒ ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îŋ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚", "maintenance_restore_library": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ Ī„ÎˇĪ‚ βΚβÎģΚÎŋθΎÎēÎˇĪ‚ ĪƒÎąĪ‚", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "ΆÎŊÎŋÎšÎŗÎŧÎą ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ", "manage_media_access_subtitle": "Î•Ī€Î¯Ī„ĪÎĩĪˆĪ„Îĩ ĪƒĪ„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ Immich ÎŊÎą Î´ÎšÎąĪ‡ÎĩÎšĪÎ¯ÎļÎĩĪ„ÎąÎš ÎēιΚ ÎŊÎą ÎŧÎĩĪ„ÎąÎēΚÎŊÎĩί ÎąĪĪ‡ÎĩÎ¯Îą Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ.", "manage_media_access_title": "Î ĪĪŒĪƒÎ˛ÎąĪƒÎˇ Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇĪ‚ ΠÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ", + "manage_shared_links": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Ī‰ÎŊ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧΉÎŊ", "manage_sharing_with_partners": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„ÎˇÎŊ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ ÎŧÎĩ ĪƒĪ…ÎŊÎĩĪÎŗÎŦĪ„ÎĩĪ‚", "manage_the_app_settings": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„ÎšĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", "manage_your_account": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„ÎŋÎŊ ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧΌ ĪƒÎąĪ‚", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„ÎšĪ‚ ĪƒĪ…ÎŊδÎĩδÎĩÎŧέÎŊÎĩĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚ ĪƒÎąĪ‚", "manage_your_oauth_connection": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„Îˇ ĪƒĪÎŊδÎĩĪƒÎŽ ĪƒÎąĪ‚ OAuth", "map": "ΧÎŦĪĪ„ÎˇĪ‚", + "map_assets_in_bounds": "{count, plural, =0 {ΚαÎŧÎ¯Îą ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ΃Îĩ ÎąĪ…Ī„ÎŽ Ī„ÎˇÎŊ Ī€ÎĩĪÎšÎŋĪ‡ÎŽ} one {# ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą} other {# ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚}}", "map_cannot_get_user_location": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎģÎŽĪˆÎˇ Ī„ÎˇĪ‚ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ Ī„ÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ", + "map_location_dialog_yes": "Ναι", "map_location_picker_page_use_location": "Î§ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„ÎˇÎŊ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą", "map_location_service_disabled_content": "Η Ī…Ī€ÎˇĪÎĩĪƒÎ¯Îą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ Ī€ĪÎ­Ī€ÎĩΚ ÎŊÎą ÎĩίÎŊιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ ÎŗÎšÎą Ī„ÎˇÎŊ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąĪ€ĪŒ Ī„ÎˇÎŊ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą ĪƒÎąĪ‚. ΘέÎģÎĩĪ„Îĩ ÎŊÎą Ī„Îŋ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ Ī„ĪŽĪÎą;", "map_location_service_disabled_title": "Η Ī…Ī€ÎˇĪÎĩĪƒÎ¯Îą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΎθΡÎēÎĩ", - "map_marker_for_image": "ÎŖÎŽÎŧÎąÎŊĪƒÎˇ ĪƒĪ„ÎŋÎŊ ·ÎŦĪĪ„Îˇ ÎŗÎšÎą ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą Ī€ÎŋĪ… Ī„ĪÎąÎ˛ÎŽĪ‡Ī„ÎˇÎēÎĩ ĪƒĪ„Îŋ {city}, {country}", + "map_marker_for_images": "ΔÎĩίÎēĪ„ÎˇĪ‚ ·ÎŦĪĪ„Îˇ ÎŗÎšÎą ÎĩΚÎēΌÎŊÎĩĪ‚ Ī€ÎŋĪ… Ī„ĪÎąÎ˛ÎŽĪ‡Ī„ÎˇÎēÎąÎŊ ΃Îĩ {city}, {country}", "map_marker_with_image": "ΧÎŦĪĪ„ÎˇĪ‚ δÎĩίÎēĪ„Îˇ ÎŧÎĩ ÎĩΚÎēΌÎŊÎą", "map_no_location_permission_content": "Î‘Ī€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ÎŦδÎĩΚι Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ÎŗÎšÎą Ī„ÎˇÎŊ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąĪ€ĪŒ Ī„ÎˇÎŊ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą ĪƒÎąĪ‚. ΘέÎģÎĩĪ„Îĩ ÎŊÎą Ī„Îŋ ÎĩĪ€ÎšĪ„ĪÎ­ĪˆÎĩĪ„Îĩ Ī„ĪŽĪÎą;", "map_no_location_permission_title": "Η ÎŦδÎĩΚι Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ÎąĪ€ÎŋĪĪÎ¯Ī†Î¸ÎˇÎēÎĩ", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Î ĪÎŋÎˇÎŗÎŋĪÎŧÎĩÎŊÎĩĪ‚ {days} ΡÎŧÎ­ĪÎĩĪ‚", "map_settings_date_range_option_year": "Î ĪÎŋÎˇÎŗÎŋĪÎŧÎĩÎŊÎŋ Î­Ī„ÎŋĪ‚", "map_settings_date_range_option_years": "Î ĪÎŋÎˇÎŗÎŋĪÎŧÎĩÎŊÎą {years} Î­Ī„Îˇ", + "map_settings_dialog_title": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ΧÎŦĪĪ„Îˇ", "map_settings_include_show_archived": "ÎŖĪ…ÎŧĪ€ÎĩĪÎšÎģÎŦβÎĩĪ„Îĩ Î‘ĪĪ‡ÎĩΚÎŋθÎĩĪ„ÎˇÎŧέÎŊÎą", "map_settings_include_show_partners": "ÎŖĪ…ÎŧĪ€ÎĩĪÎšÎģÎŦβÎĩĪ„Îĩ ÎŖĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ…Ī‚", "map_settings_only_show_favorites": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎŧΌÎŊÎŋ ÎąÎŗÎąĪ€ÎˇÎŧέÎŊΉÎŊ", "map_settings_theme_settings": "ΘέÎŧÎą ·ÎŦĪĪ„Îˇ", + "map_zoom_to_see_photos": "ÎŖÎŧΚÎēĪĪÎŊÎĩĪ„Îĩ ÎŗÎšÎą ÎŊÎą δÎĩÎ¯Ī„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", "mark_all_as_read": "Î•Ī€ÎšĪƒÎŽÎŧÎąÎŊĪƒÎˇ ΌÎģΉÎŊ Ή΂ ÎąÎŊÎąÎŗÎŊĪ‰ĪƒÎŧέÎŊÎą", + "mark_as_read": "Î•Ī€ÎšĪƒÎŽÎŧÎąÎŊĪƒÎˇ Ή΂ ÎąÎŊÎąÎŗÎŊĪ‰ĪƒÎŧέÎŊÎŋ", "marked_all_as_read": "ΌÎģÎą ÎĩĪ€ÎšĪƒÎˇÎŧÎŦÎŊθΡÎēÎąÎŊ Ή΂ ÎąÎŊÎąÎŗÎŊĪ‰ĪƒÎŧέÎŊÎą", "matches": "ΑÎŊĪ„ÎšĪƒĪ„ÎŋÎšĪ‡Î¯ÎĩĪ‚", "matching_assets": "ΑÎŊĪ„ÎšĪƒĪ„ÎŋÎšĪ‡Î¯Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", - "media_chrome": { - "auto": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îą", - "captions": "ΛÎĩÎļÎŦÎŊĪ„ÎĩĪ‚", - "captions_off": "ΚÎģÎĩÎšĪƒĪ„ĪŒ", - "closed_captions": "Ī…Ī€ĪŒĪ„ÎšĪ„ÎģÎŋΚ ÎŗÎšÎą ÎēΉΆÎŋĪĪ‚/Î˛ÎąĪÎŽÎēÎŋÎŋĪ…Ī‚", - "decode_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎąĪ€ÎŋÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", - "disable_captions": "Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī…Ī€ĪŒĪ„ÎšĪ„ÎģΉÎŊ", - "enable_captions": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ Ī…Ī€ĪŒĪ„ÎšĪ„ÎģΉÎŊ", - "enter_fullscreen_mode": "Î•Î¯ĪƒÎŋδÎŋĪ‚ ΃Îĩ Ī€ÎģÎŽĪÎˇ ÎŋÎ¸ĪŒÎŊΡ", - "exit_fullscreen_mode": "ΈΞÎŋδÎŋĪ‚ ÎąĪ€ĪŒ Ī€ÎģÎŽĪÎˇ ÎŋÎ¸ĪŒÎŊΡ", - "loop": "Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇ", - "media_error_description": "ΈÎŊÎą ĪƒĪ†ÎŦÎģÎŧÎą Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ ΀΁ÎŋÎēÎŦÎģÎĩ΃Îĩ Ī„Îˇ δΚιÎēÎŋĪ€ÎŽ Ī„ÎˇĪ‚ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚. ΤÎŋ ÎąĪĪ‡ÎĩίÎŋ Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎĩίÎŊιΚ ÎēÎąĪ„ÎĩĪƒĪ„ĪÎąÎŧÎŧέÎŊÎŋ ÎŽ Ī„Îŋ Ī€ĪĪŒÎŗĪÎąÎŧÎŧÎą Ī€ÎĩĪÎšÎŽÎŗÎˇĪƒÎŽĪ‚ ĪƒÎąĪ‚ δÎĩÎŊ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩΚ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ÎŧÎŋĪĪ†ÎŽ.", - "media_loading": "Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ", - "mute": "ÎŖÎ¯ÎŗÎąĪƒÎˇ", - "network_error": "ÎŖĪ†ÎŦÎģÎŧÎą δΚÎēĪ„ĪÎŋĪ…", - "network_error_description": "ΈÎŊÎą ĪƒĪ†ÎŦÎģÎŧÎą δΚÎēĪ„ĪÎŋĪ… ΀΁ÎŋÎēÎŦÎģÎĩ΃Îĩ Ī„ÎˇÎŊ ÎąĪ€ÎŋĪ„Ī…Ī‡Î¯Îą ÎģÎŽĪˆÎˇĪ‚ Ī„ÎŋĪ… ÎąĪĪ‡ÎĩίÎŋĪ… Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ.", - "not_supported_error": "Η Î ÎˇÎŗÎŽ ΔÎĩÎŊ ÎĨĪ€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩĪ„ÎąÎš", - "playback_rate": "Î¤ÎąĪ‡ĪĪ„ÎˇĪ„Îą ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚", - "playback_rate_current": "Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎą Ī„ÎąĪ‡ĪĪ„ÎˇĪ„Îą ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚", - "playback_rate_value": "Î¤ÎąĪ‡ĪĪ„ÎˇĪ„Îą ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚ {playbackRate}", - "playback_time": "Ī‡ĪĪŒÎŊÎŋĪ‚ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚", - "quality": "ΠÎŋÎšĪŒĪ„ÎˇĪ„Îą", - "second": "δÎĩĪ…Ī„Îĩ΁ΌÎģÎĩ΀΄Îŋ", - "seconds": "δÎĩĪ…Ī„Îĩ΁ΌÎģÎĩĪ€Ī„Îą", - "time_value_of_total_time": "{currentTime} ÎąĪ€ĪŒ {totalTime}", - "time_value_remaining": "{time} Ī…Ī€ÎŋÎģÎĩÎšĪ€ĪŒÎŧÎĩÎŊÎŋĪ‚ Ī‡ĪĪŒÎŊÎŋĪ‚", - "unmute": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŽĪ‡ÎŋĪ…", - "unsupported_error_description": "Î ÎąĪÎŋĪ…ĪƒÎšÎŦĪƒĪ„ÎˇÎēÎĩ έÎŊÎą ÎŧΡ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎšÎļΌÎŧÎĩÎŊÎŋ ĪƒĪ†ÎŦÎģÎŧÎą. Ο δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽĪ‚ ÎŽ Ī„Îŋ δίÎē΄΅Îŋ ÎąĪ€Î­Ī„Ī…Ī‡ÎąÎŊ ÎŽ Îŋ Ī€ÎĩĪÎšÎˇÎŗÎˇĪ„ÎŽĪ‚ ĪƒÎąĪ‚ δÎĩÎŊ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩΚ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ÎŧÎŋĪĪ†ÎŽ.", - "video_not_loaded_unknown_time": "Ī„Îŋ Î˛Î¯ÎŊĪ„ÎĩÎŋ δÎĩÎŊ ΆÎŋĪĪ„ĪŽÎ¸ÎˇÎēÎĩ, ÎŦÎŗÎŊĪ‰ĪƒĪ„Îˇ δΚÎŦ΁ÎēÎĩΚι.", - "video_player": "Ī€ĪĪŒÎŗĪÎąÎŧÎŧÎą ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚ Î˛Î¯ÎŊĪ„ÎĩÎŋ", - "volume": "έÎŊĪ„ÎąĪƒÎˇ" - }, "media_type": "Î¤ĪĪ€ÎŋĪ‚ Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒÎŋĪ…", "memories": "ΑÎŊÎąÎŧÎŊÎŽĪƒÎĩÎšĪ‚", "memories_all_caught_up": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧέÎŊÎą", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "ΘέÎģÎĩĪ„Îĩ ÎŊÎą ĪƒĪ…ÎŗĪ‡Ī‰ÎŊÎĩĪĪƒÎĩĪ„Îĩ ÎąĪ…Ī„ÎŦ Ī„Îą ÎŦĪ„ÎŋÎŧÎą; Î‘Ī…Ī„ÎŽ Ρ ÎĩÎŊÎ­ĪÎŗÎĩΚι ÎĩίÎŊιΚ ÎŧΡ ÎąÎŊÎąĪƒĪ„ĪÎ­ĪˆÎšÎŧΡ.", "merge_people_successfully": "Τι ÎŦĪ„ÎŋÎŧÎą ĪƒĪ…ÎŗĪ‡Ī‰ÎŊÎĩĪÎ¸ÎˇÎēÎąÎŊ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", "merged_people_count": "ÎˆÎŗÎšÎŊÎĩ ĪƒĪ…ÎŗĪ‡ĪŽÎŊÎĩĪ…ĪƒÎˇ {count, plural, one {# ÎąĪ„ĪŒÎŧÎŋĪ…} other {# ÎąĪ„ĪŒÎŧΉÎŊ}}", - "minFaces": "ΕÎģÎŦĪ‡ÎšĪƒĪ„ÎŋĪ‚ ÎąĪÎšÎ¸ÎŧĪŒĪ‚ ΀΁ÎŋĪƒĪŽĪ€Ī‰ÎŊ", - "minFaces_description": "Ο ÎĩÎģÎŦĪ‡ÎšĪƒĪ„ÎŋĪ‚ ÎąĪÎšÎ¸ÎŧĪŒĪ‚ ÎąÎŊÎąÎŗÎŊĪ‰ĪÎšĪƒÎŧέÎŊΉÎŊ ΀΁ÎŋĪƒĪŽĪ€Ī‰ÎŊ ÎŗÎšÎą Ī„ÎˇÎŊ ÎĩÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩÎŊĪŒĪ‚ ÎąĪ„ĪŒÎŧÎŋĪ…", "minimize": "ΕÎģÎąĪ‡ÎšĪƒĪ„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", "minute": "ΛÎĩĪ€Ī„ĪŒ", "minutes": "ΛÎĩ΀΄ÎŦ", + "mirror_horizontal": "ÎŸĪÎšÎļΌÎŊĪ„ÎšÎą", + "mirror_vertical": "ΚÎŦθÎĩĪ„Îą", "missing": "ÎŒĪƒÎą ΛÎĩÎ¯Ī€ÎŋĪ…ÎŊ", "mobile_app": "Î•Ī†ÎąĪÎŧÎŋÎŗÎŽ ÎŗÎšÎą ÎēΚÎŊÎˇĪ„ÎŦ", "mobile_app_download_onboarding_note": "ÎšÎąĪ„Î­Î˛ÎąĪƒÎĩ Ī„ÎˇÎŊ ĪƒĪ…ÎŊÎŋδÎĩĪ…Ī„ÎšÎēÎŽ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ ÎŗÎšÎą ÎēΚÎŊÎˇĪ„ÎŦ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšĪŽÎŊĪ„ÎąĪ‚ Ī„ÎšĪ‚ Ī€ÎąĪÎąÎēÎŦ΄Ή ÎĩĪ€ÎšÎģÎŋÎŗÎ­Ī‚", "model": "ΜÎŋÎŊĪ„Î­ÎģÎŋ", - "modify_date": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą Î¤ĪÎŋĪ€ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", "month": "ΜήÎŊÎąĪ‚", + "monthly_title_text_date_format": "ΜΜΜΜ y", "more": "ΠÎĩĪÎšĪƒĪƒĪŒĪ„ÎĩĪÎą", - "motion": "ΚίÎŊÎˇĪƒÎˇ", "move": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ", + "move_down": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ΀΁ÎŋĪ‚ Ī„Îą ÎēÎŦ΄Ή", "move_off_locked_folder": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ Î­ÎžĪ‰ ÎąĪ€ĪŒ Ī„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ", "move_to": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ΃Îĩ", "move_to_device_trash": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ Ī„ÎˇĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽĪ‚", "move_to_lock_folder_action_prompt": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ {count} ĪƒĪ„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ", "move_to_locked_folder": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ΃Îĩ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ", "move_to_locked_folder_confirmation": "Î‘Ī…Ī„Î­Ī‚ ÎŋΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ θι ÎąĪ†ÎąÎšĪÎĩθÎŋĪÎŊ ÎąĪ€ĪŒ ΌÎģÎą Ī„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎēιΚ θι ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą ΀΁ÎŋβÎģΡθÎŋĪÎŊ ÎŧΌÎŊÎŋ ÎąĪ€ĪŒ Ī„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ", + "move_up": "ΜÎĩĪ„ÎąÎēίÎŊÎˇĪƒÎˇ ΀΁ÎŋĪ‚ Ī„Îą Ī€ÎŦÎŊΉ", + "moved_to_archive": "ΜÎĩĪ„ÎąÎēΚÎŊΎθΡÎēÎąÎŊ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} ĪƒĪ„Îŋ ÎąĪĪ‡ÎĩίÎŋ", + "moved_to_library": "ΜÎĩĪ„ÎąÎēΚÎŊΎθΡÎēÎĩ/ÎąÎŊ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} ĪƒĪ„Îˇ βΚβÎģΚÎŋθΎÎēΡ", "moved_to_trash": "ΜÎĩĪ„ÎąÎēΚÎŊΎθΡÎēÎĩ ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ", + "multiselect_grid_edit_date_time_err_read_only": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī„ÎˇĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎŧΌÎŊÎŋ ÎŗÎšÎą ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", + "multiselect_grid_edit_gps_err_read_only": "ΔÎĩÎŊ ÎĩίÎŊιΚ Î´Ī…ÎŊÎąĪ„ÎŽ Ρ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą Ī„ÎˇĪ‚ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ΄ΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎŧΌÎŊÎŋ ÎŗÎšÎą ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ, Ī€ÎąĪÎąÎģÎĩÎ¯Ī€ÎĩĪ„ÎąÎš", "mute_memories": "ÎŖÎ¯ÎŗÎąĪƒÎˇ ΑÎŊÎąÎŧÎŊÎŽĪƒÎĩΉÎŊ", "my_albums": "Τι ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŧÎŋĪ…", - "my_immich_description": "ΑÎŊĪ„ÎšÎŗĪÎąĪ†ÎŽ Ī„ÎˇĪ‚ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎąĪ‚ ΃ÎĩÎģÎ¯Î´ÎąĪ‚ Ή΂ ĪƒĪÎŊδÎĩ΃ÎŧÎŋĪ‚ ΤÎŋ Immich ÎŧÎŋĪ…", - "my_immich_title": "ÎŖĪÎŊδÎĩ΃ÎŧÎŋĪ‚ ΤÎŋ Immich ÎŧÎŋĪ…", "name": "ΌÎŊÎŋÎŧÎą", "name_or_nickname": "ΌÎŊÎŋÎŧÎą ÎŽ ΈÎĩĪ…Î´ĪŽÎŊĪ…ÎŧÎŋ", "name_required": "Î‘Ī€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ΌÎŊÎŋÎŧÎą", @@ -1480,11 +1576,12 @@ "never": "ΠÎŋĪ„Î­", "new_album": "ΝέÎŋ ΆÎģÎŧĪ€ÎŋĪ…Îŧ", "new_api_key": "ΝέÎŋ API Key", - "new_feature": "Νέα ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą", + "new_date_range": "Î•ĪĪÎŋĪ‚ ÎŊÎ­ÎąĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯ÎąĪ‚", "new_password": "ΝέÎŋĪ‚ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", "new_person": "ΝέÎŋ ÎŦĪ„ÎŋÎŧÎŋ", "new_pin_code": "ΝέÎŋĪ‚ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ PIN", "new_pin_code_subtitle": "Î‘Ī…Ī„ÎŽ ÎĩίÎŊιΚ Ρ Ī€ĪĪŽĪ„Îˇ ΆÎŋ΁ÎŦ Ī€ÎŋĪ… ÎąĪ€ÎŋÎēĪ„ÎŦĪ„Îĩ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ ĪƒĪ„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ. ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ έÎŊÎąÎŊ ÎēĪ‰Î´ÎšÎēΌ PIN ÎŗÎšÎą ÎąĪƒĪ†ÎąÎģÎŽ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ ΃Îĩ ÎąĪ…Ī„ÎŽ Ī„Îˇ ΃ÎĩÎģÎ¯Î´Îą", + "new_timeline": "ΝέÎŋ Î§ĪÎŋÎŊÎŋÎģĪŒÎŗÎšÎŋ", "new_update": "Νέα ÎĩÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ", "new_user_created": "Ο ÎŊέÎŋĪ‚ Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚ δΡÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ", "new_version_available": "Î”Î™Î‘Î˜Î•ÎŖÎ™ÎœÎ— ΝΕΑ Î•ÎšÎ”ÎŸÎŖÎ—", @@ -1492,6 +1589,7 @@ "next": "Î•Ī€ĪŒÎŧÎĩÎŊÎŋ", "next_memory": "Î•Ī€ĪŒÎŧÎĩÎŊΡ ÎąÎŊÎŦÎŧÎŊÎˇĪƒÎˇ", "no": "ÎŒĪ‡Îš", + "no_actions_added": "ΔÎĩÎŊ Î­Ī‡ÎŋĪ…ÎŊ ΀΁ÎŋĪƒĪ„ÎĩθÎĩί ÎąÎēΌÎŧÎą ÎĩÎŊÎ­ĪÎŗÎĩΚÎĩĪ‚", "no_albums_found": "ΔÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "no_albums_message": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ έÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŗÎšÎą ÎŊÎą ÎŋĪÎŗÎąÎŊĪŽĪƒÎĩĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚", "no_albums_with_name_yet": "ÎĻÎąÎ¯ÎŊÎĩĪ„ÎąÎš ĪŒĪ„Îš δÎĩÎŊ Î­Ī‡ÎĩĪ„Îĩ ÎēÎąÎŊέÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŧÎĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ ΌÎŊÎŋÎŧÎą ÎąÎēΌÎŧÎą.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "ΔÎĩ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚ ÎŧÎĩĪ„ÎŦδÎŋĪƒÎˇĪ‚", "no_checksum_local": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎĩΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋ checksum ÎŗÎšÎą έÎģÎĩÎŗĪ‡Îŋ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚ – δÎĩÎŊ ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą ÎąÎŊÎąÎēĪ„ÎˇÎ¸ÎŋĪÎŊ Ī„Îą Ī„ÎŋĪ€ÎšÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", "no_checksum_remote": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎĩΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋ checksum ÎŗÎšÎą έÎģÎĩÎŗĪ‡Îŋ ÎąÎēÎĩĪÎąÎšĪŒĪ„ÎˇĪ„ÎąĪ‚ – δÎĩÎŊ ÎŧĪ€Îŋ΁ÎŋĪÎŊ ÎŊÎą ÎąÎŊÎąÎēĪ„ÎˇÎ¸ÎŋĪÎŊ Ī„Îą ÎąĪ€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", + "no_configuration_needed": "ΔÎĩÎŊ ÎąĪ€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ĪĪÎ¸ÎŧÎšĪƒÎˇ", "no_devices": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ ÎĩΞÎŋĪ…ĪƒÎšÎŋδÎŋĪ„ÎˇÎŧέÎŊÎĩĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚", "no_duplicates_found": "ΔÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą.", "no_exif_info_available": "ΚαÎŧÎ¯Îą Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯Îą exif Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧΡ", "no_explore_results_message": "ΑÎŊÎĩβÎŦĪƒĪ„Îĩ Ī€ÎĩĪÎšĪƒĪƒĪŒĪ„Îĩ΁ÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎŗÎšÎą ÎŊÎą Ī€ÎĩĪÎšÎˇÎŗÎˇÎ¸ÎĩÎ¯Ī„Îĩ ĪƒĪ„Îˇ ĪƒĪ…ÎģÎģÎŋÎŗÎŽ ĪƒÎąĪ‚.", "no_favorites_message": "Î ĪÎŋĪƒÎ¸Î­ĪƒĪ„Îĩ ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą ÎŗÎšÎą ÎŊÎą Î˛ĪÎĩÎ¯Ī„Îĩ ÎŗĪÎŽÎŗÎŋĪÎą Ī„ÎšĪ‚ ÎēÎąÎģĪĪ„Îĩ΁ÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚", + "no_filters_added": "ΔÎĩÎŊ Î­Ī‡ÎŋĪ…ÎŊ ΀΁ÎŋĪƒĪ„ÎĩθÎĩί ÎąÎēΌÎŧÎą Ī†Î¯ÎģĪ„ĪÎą", "no_libraries_message": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ ÎŧΚι ÎĩÎžĪ‰Ī„ÎĩĪÎšÎēÎŽ βΚβÎģΚÎŋθΎÎēΡ ÎŗÎšÎą ÎŊÎą ΀΁ÎŋβÎŦÎģÎĩĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚", "no_local_assets_found": "ΔÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ Ī„ÎŋĪ€ÎšÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧÎĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ checksum", + "no_location_set": "Η Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą δÎĩÎŊ Î­Ī‡ÎĩΚ ÎŋĪÎšĪƒĪ„Îĩί", "no_locked_photos_message": "Οι ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ ĪƒĪ„ÎŋÎŊ ÎēÎģÎĩÎšÎ´Ī‰ÎŧέÎŊÎŋ ΆÎŦÎēÎĩÎģÎŋ, ÎĩίÎŊιΚ Îē΁΅ÎŧÎŧέÎŊÎĩĪ‚ ÎēιΚ δÎĩÎŊ θι ÎĩÎŧĪ†ÎąÎŊίÎļÎŋÎŊĪ„ÎąÎš ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ Ī€ÎĩĪÎšÎŽÎŗÎˇĪƒÎˇ ÎŽ Ī„ÎˇÎŊ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ĪƒĪ„Îˇ βΚβÎģΚÎŋθΎÎēΡ ĪƒÎąĪ‚.", "no_name": "Î§Ī‰ĪÎ¯Ī‚ ΌÎŊÎŋÎŧÎą", "no_notifications": "ΚαÎŧÎ¯Îą ÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", @@ -1518,7 +1619,7 @@ "no_results": "ΚαÎŊέÎŊÎą ÎąĪ€ÎŋĪ„Î­ÎģÎĩ΃ÎŧÎą", "no_results_description": "ΔÎŋÎēΚÎŧÎŦĪƒĪ„Îĩ έÎŊÎą ĪƒĪ…ÎŊĪŽÎŊĪ…ÎŧÎŋ ÎŽ Ī€ÎšÎŋ ÎŗÎĩÎŊΚÎēÎŽ ÎģέΞΡ-ÎēÎģÎĩΚδί", "no_shared_albums_message": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ έÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŗÎšÎą ÎŊÎą ÎŧÎŋÎšĪÎŦÎļÎĩĪƒĪ„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŧÎĩ ÎŦĪ„ÎŋÎŧÎą ĪƒĪ„Îŋ δίÎēĪ„Ī…ĪŒ ĪƒÎąĪ‚", - "no_steps": "ΔÎĩÎŊ Î­Ī‡ÎŋĪ…ÎŊ ΀΁ÎŋĪƒĪ„ÎĩθÎĩί ÎąÎēΌÎŧÎą βΎÎŧÎąĪ„Îą", + "no_uploads_in_progress": "ΚαÎŧÎ¯Îą ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ΃Îĩ ÎĩΞέÎģΚΞΡ", "none": "ΚαÎŊέÎŊÎą", "not_allowed": "ΔÎĩÎŊ ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎĩĪ„ÎąÎš", "not_available": "Μ/Δ (Μη Î”ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋ)", @@ -1526,10 +1627,6 @@ "not_selected": "ΔÎĩÎŊ ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎĩ", "notes": "ÎŖÎˇÎŧÎĩÎšĪŽĪƒÎĩÎšĪ‚", "nothing_here_yet": "Î¤Î¯Ī€ÎŋĪ„Îą ÎĩÎ´ĪŽ ÎąÎēΌÎŧÎą", - "notification_backup_reliability": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎšĪ‚ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚ ÎŗÎšÎą ÎŊÎą βÎĩÎģĪ„ÎšĪŽĪƒÎĩĪ„Îĩ Ī„ÎˇÎŊ ιΞΚÎŋĪ€ÎšĪƒĪ„Î¯Îą Ī„ÎŋĪ… ÎąÎŊĪ„ÎšÎŗĪÎŦΆÎŋĪ… ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ", - "notification_enabled_list_tile_content": "ΤÎŋ Immich Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩί ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚ ÎŗÎšÎą Ī„ÎˇÎŊ ÎąÎžÎšĪŒĪ€ÎšĪƒĪ„Îˇ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą ΄ΉÎŊ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ. ÎœĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą Ī„ÎšĪ‚ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ ÎąĪ€ĪŒ Ī„ÎšĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ Ī„ÎˇĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…ÎŽĪ‚ ĪƒÎąĪ‚.", - "notification_enabled_list_tile_open_button": "ΆÎŊÎŋÎšÎŗÎŧÎą ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩΉÎŊ", - "notification_enabled_list_tile_title": "ΕιδÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎĩĪ‚", "notification_permission_dialog_content": "Για ÎŊÎą ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ Ī„ÎšĪ‚ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩÎšĪ‚, ÎŧÎĩĪ„ÎąÎ˛ÎĩÎ¯Ī„Îĩ ĪƒĪ„ÎšĪ‚ ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎēιΚ ÎĩĪ€ÎšÎģÎ­ÎžĪ„Îĩ ÎŊÎą ÎĩĪ€ÎšĪ„ĪÎ­Ī€ÎĩĪ„ÎąÎš.", "notification_permission_list_tile_content": "Î ÎąĪÎąĪ‡Ī‰ĪÎŽĪƒĪ„Îĩ ÎŦδÎĩΚι ÎŗÎšÎą ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎĩΚδÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ.", "notification_permission_list_tile_enable_button": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ΕιδÎŋĪ€ÎŋÎšÎŽĪƒÎĩΉÎŊ", @@ -1541,8 +1638,6 @@ "obtainium_configurator": "ÎĄĪ…Î¸ÎŧÎšĪƒĪ„ÎŽĪ‚ Obtainium", "obtainium_configurator_instructions": "Î§ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎ¯ÎˇĪƒÎĩ Ī„Îŋ Obtainium ÎŗÎšÎą ÎŊÎą ÎĩÎŗÎēÎąĪ„ÎąĪƒĪ„ÎŽĪƒÎĩÎšĪ‚ ÎēιΚ ÎŊÎą ÎĩÎŊΡÎŧÎĩĪĪŽĪƒÎĩÎšĪ‚ ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ Ī„ÎˇÎŊ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ Android ÎąĪ€ĪŒ Ī„Îą ÎēĪ…ÎēÎģÎŋΆÎŋĪÎ¯ÎĩĪ‚ Ī„ÎŋĪ… Immich ĪƒĪ„Îŋ GitHub. ΔηÎŧΚÎŋĪĪÎŗÎˇĪƒÎĩ έÎŊÎą API key ÎēιΚ ÎĩĪ€Î­ÎģÎĩΞÎĩ ÎŧΚι Ī€ÎąĪÎąÎģÎģÎąÎŗÎŽ ÎŗÎšÎą ÎŊÎą δΡÎŧΚÎŋĪ…ĪÎŗÎŽĪƒÎĩÎšĪ‚ Ī„Îŋ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ ĪĪÎ¸ÎŧÎšĪƒÎˇĪ‚ Ī„ÎŋĪ… Obtainium", "ocr": "OCR", - "ocr_body": "ΤÎŋ Immich δΚιβÎŦÎļÎĩΚ Ī€ÎģέÎŋÎŊ Ī„Îŋ ÎēÎĩίÎŧÎĩÎŊÎŋ ÎŧÎ­ĪƒÎą ĪƒĪ„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚, ĪŽĪƒĪ„Îĩ ÎŊÎą ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą Ī„ÎšĪ‚ ÎąÎŊÎąÎļÎˇĪ„ÎŦĪ„Îĩ ÎŧÎĩ βÎŦĪƒÎˇ Ī„Îŋ Ī€ÎĩĪÎšÎĩĪ‡ĪŒÎŧÎĩÎŊΌ Ī„ÎŋĪ…Ī‚.", - "ocr_title": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎēÎĩΚÎŧέÎŊÎŋĪ… ĪƒĪ„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚", "official_immich_resources": "Î•Ī€Î¯ĪƒÎˇÎŧÎŋΚ Î ĪŒĪÎŋΚ Ī„ÎŋĪ… Immich", "offline": "ΕÎēĪ„ĪŒĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", "offset": "ΜÎĩĪ„ÎąĪ„ĪŒĪ€ÎšĪƒÎˇ", @@ -1561,8 +1656,6 @@ "open": "ΆÎŊÎŋÎšÎŗÎŧÎą", "open_calendar": "ΆÎŊÎŋÎšÎŗÎŧÎą ΡÎŧÎĩ΁ÎŋÎģÎŋÎŗÎ¯ÎŋĪ…", "open_in_browser": "ΆÎŊÎŋÎšÎŗÎŧÎą ĪƒĪ„Îŋ Ī€ĪĪŒÎŗĪÎąÎŧÎŧÎą Ī€ÎĩĪÎšÎŽÎŗÎˇĪƒÎˇĪ‚", - "open_in_immich_body": "ÎŸĪÎ¯ĪƒĪ„Îĩ Ī„Îŋ Immich Ή΂ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽ ĪƒĪ…ÎģÎģÎŋÎŗÎŽĪ‚ ĪƒĪ„Îŋ Android, ĪŽĪƒĪ„Îĩ ÎŊÎą ÎąÎŊÎŋÎ¯ÎŗÎĩĪ„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ ÎąĪ€ĪŒ ÎŦÎģÎģÎĩĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎ­Ī‚.", - "open_in_immich_title": "ΆÎŊÎŋÎšÎŗÎŧÎą ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ ĪƒĪ„Îŋ Immich", "open_in_map_view": "ΆÎŊÎŋÎšÎŗÎŧÎą ΃Îĩ ΀΁ÎŋβÎŋÎģÎŽ ·ÎŦĪĪ„Îˇ", "open_in_openstreetmap": "ΆÎŊÎŋÎšÎŗÎŧÎą ĪƒĪ„Îŋ OpenStreetMap", "open_the_search_filters": "ΑÎŊÎŋÎ¯ÎžĪ„Îĩ Ī„Îą Ī†Î¯ÎģĪ„ĪÎą ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", @@ -1571,10 +1664,10 @@ "organize_into_albums": "ÎŸĪÎŗÎŦÎŊĪ‰ĪƒÎˇ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "organize_into_albums_description": "ΤÎŋĪ€ÎŋθÎĩĪ„ÎĩÎ¯ĪƒĪ„Îĩ Ī„ÎšĪ‚ Ī…Ī€ÎŦ΁·ÎŋĪ…ĪƒÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšĪŽÎŊĪ„ÎąĪ‚ Ī„ÎšĪ‚ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎĩĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧÎŋĪ", "organize_your_library": "ÎŸĪÎŗÎąÎŊĪŽĪƒĪ„Îĩ Ī„Îˇ βΚβÎģΚÎŋθΎÎēΡ ĪƒÎąĪ‚", - "orientation": "Î ĪÎŋĪƒÎąÎŊÎąĪ„ÎŋÎģÎšĪƒÎŧĪŒĪ‚", "original": "Ī€ĪĪ‰Ī„ĪŒĪ„Ī…Ī€Îŋ", "other": "ΆÎģÎģÎĩĪ‚", "other_devices": "ΆÎģÎģÎĩĪ‚ ĪƒĪ…ĪƒÎēÎĩĪ…Î­Ī‚", + "other_entities": "ΆÎģÎģÎĩĪ‚ ÎŋÎŊĪ„ĪŒĪ„ÎˇĪ„ÎĩĪ‚", "other_variables": "ΆÎģÎģÎĩĪ‚ ÎŧÎĩĪ„ÎąÎ˛ÎģÎˇĪ„Î­Ī‚", "owned": "ΔιÎēÎŦ ÎŧÎŋĪ…", "owner": "ΚÎŦĪ„Îŋ·ÎŋĪ‚", @@ -1584,9 +1677,12 @@ "partner_can_access_assets": "ΌÎģÎĩĪ‚ ÎŋΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚ ÎĩÎēĪ„ĪŒĪ‚ ÎąĪ€ĪŒ ÎąĪ…Ī„ÎŦ Ī€ÎŋĪ… Î˛ĪÎ¯ĪƒÎēÎŋÎŊĪ„ÎąÎš ĪƒĪ„Îŋ Î‘ĪĪ‡ÎĩίÎŋ ÎēιΚ Ī„Îą ΔιÎĩÎŗĪÎąÎŧÎŧέÎŊÎą", "partner_can_access_location": "Η Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą ĪŒĪ€ÎŋĪ… Ī„ĪÎąÎ˛ÎŽĪ‡Ī„ÎˇÎēÎąÎŊ ÎŋΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚", "partner_list_user_photos": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ Ī„ÎŋĪ…/Ī„ÎˇĪ‚ {user}", + "partner_list_view_all": "Î ĪÎŋβÎŋÎģÎŽ ΌÎģΉÎŊ", "partner_page_empty_message": "Οι ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚ δÎĩÎŊ δΚιÎŧÎŋÎšĪÎŦÎļÎŋÎŊĪ„ÎąÎš ÎąÎēΌÎŧÎą ÎŧÎĩ ÎēÎąÎŊέÎŊÎąÎŊ.", "partner_page_no_more_users": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ ÎŦÎģÎģÎŋΚ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ ÎŗÎšÎą ΀΁ÎŋĪƒÎ¸ÎŽÎēΡ", + "partner_page_partner_add_failed": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą ΀΁ÎŋĪƒÎ¸ÎŽÎēÎˇĪ‚ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ…", "partner_page_select_partner": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ĪƒĪ…ÎŊĪ„ĪĪŒĪ†ÎŋĪ…", + "partner_page_shared_to_title": "ΔιαÎŧÎŋÎšĪÎŦÎļÎĩĪ„ÎąÎš ÎŧÎĩ", "partner_page_stop_sharing_content": "Ο/Η {partner} δÎĩÎŊ θι ÎŧĪ€Îŋ΁Îĩί Ī€ÎģέÎŋÎŊ ÎŊÎą δÎĩΚ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚.", "partner_sharing": "ΚÎŋΚÎŊÎŽ Î§ĪÎŽĪƒÎˇ ÎŖĪ…ÎŊÎĩĪÎŗÎąĪ„ĪŽÎŊ", "partners": "ÎŖĪ…ÎŊÎĩĪÎŗÎŦĪ„ÎĩĪ‚", @@ -1608,6 +1704,8 @@ "people": "Î†Ī„ÎŋÎŧÎą", "people_edits_count": "ÎˆÎŗÎšÎŊÎĩ ÎĩĪ€ÎĩΞÎĩĪÎŗÎąĪƒÎ¯Îą {count, plural, one {# ÎąĪ„ĪŒÎŧÎŋĪ…} other {# ÎąĪ„ĪŒÎŧΉÎŊ}}", "people_feature_description": "ΠÎĩĪÎšÎŽÎŗÎˇĪƒÎˇ ΃Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŋÎŧιδÎŋĪ€ÎŋΚΡÎŧέÎŊÎą ÎąÎŊÎŦ ÎŦĪ„ÎŋÎŧÎŋ", + "people_selected": "{count, plural, one {# ÎŦĪ„ÎŋÎŧÎŋ ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎĩ} other {# ÎŦĪ„ÎŋÎŧÎą ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎąÎŊ}}", + "people_sidebar_description": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ Î‘Ī„ĪŒÎŧΉÎŊ ĪƒĪ„ÎˇÎŊ Ī€ÎģÎąĪŠÎŊÎŽ ÎŗĪÎąÎŧÎŧÎŽ", "permanent_deletion_warning": "Î ĪÎŋÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎŋĪÎšĪƒĪ„ÎšÎēÎŽĪ‚ Î´ÎšÎąÎŗĪÎąĪ†ÎŽĪ‚", "permanent_deletion_warning_setting_description": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ΀΁ÎŋÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚ ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŽ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "permanently_delete": "ÎŸĪÎšĪƒĪ„ÎšÎēÎŽ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ", @@ -1617,17 +1715,28 @@ "permanently_deleted_assets_count": "ÎŸĪÎšĪƒĪ„ÎšÎēÎŽ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ}}", "permission": "ΆδÎĩΚι", "permission_empty": "Η ÎŦδÎĩΚÎŦ ΃ÎŋĪ… δÎĩÎŊ Ī€ĪÎ­Ī€ÎĩΚ ÎŊÎą ÎĩίÎŊιΚ ÎēÎĩÎŊÎŽ", + "permission_onboarding_back": "Î Î¯ĪƒĪ‰", + "permission_onboarding_continue_anyway": "ÎŖĪ…ÎŊÎ­Ī‡ÎĩΚι", + "permission_onboarding_get_started": "ΞÎĩÎēΚÎŊÎŽĪƒĪ„Îĩ", + "permission_onboarding_go_to_settings": "ΜÎĩĪ„ÎąÎ˛ÎĩÎ¯Ī„Îĩ ĪƒĪ„ÎšĪ‚ ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚", + "permission_onboarding_permission_denied": "Η ÎŦδÎĩΚι ÎąĪ€ÎŋĪĪÎ¯Ī†Î¸ÎˇÎēÎĩ. Για ÎŊÎą Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ Ī„Îŋ Immich, Ī€ÎąĪÎąĪ‡Ī‰ĪÎŽĪƒĪ„Îĩ δΚÎēÎąÎšĪŽÎŧÎąĪ„Îą ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ĪƒĪ„ÎšĪ‚ ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚.", + "permission_onboarding_permission_granted": "Î”ĪŒÎ¸ÎˇÎēÎĩ ÎŦδÎĩΚι! Î•Î¯ĪƒĪ„Îĩ Î­Ī„ÎŋΚÎŧÎŋΚ.", + "permission_onboarding_permission_limited": "ΠÎĩĪÎšÎŋĪÎšĪƒÎŧέÎŊΡ ÎŦδÎĩΚι. Για ÎŊÎą ÎĩĪ€ÎšĪ„ĪÎ­ĪˆÎĩĪ„Îĩ ĪƒĪ„Îŋ Immich ÎŊÎą δΡÎŧΚÎŋĪ…ĪÎŗÎĩί ÎąÎŊĪ„Î¯ÎŗĪÎąĪ†Îą ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ÎēιΚ ÎŊÎą Î´ÎšÎąĪ‡ÎĩÎšĪÎ¯ÎļÎĩĪ„ÎąÎš ÎŋÎģΌÎēÎģÎˇĪÎˇ Ī„Îˇ ĪƒĪ…ÎģÎģÎŋÎŗÎŽ ĪƒÎąĪ‚, Ī€ÎąĪÎąĪ‡Ī‰ĪÎŽĪƒĪ„Îĩ ÎŦδÎĩΚÎĩĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ĪƒĪ„ÎšĪ‚ ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚.", + "permission_onboarding_request": "ΤÎŋ Immich ÎąĪ€ÎąÎšĪ„Îĩί ÎŦδÎĩΚι Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎĩÎšĪ‚ ĪƒĪ„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚.", "person": "Î†Ī„ÎŋÎŧÎŋ", "person_age_months": "{months, plural, one {# ÎŧÎŽÎŊÎąĪ‚} other {# ÎŧÎŽÎŊÎĩĪ‚}} Ī€ÎąÎģΚÎŦ", "person_age_year_months": "1 Ī‡ĪĪŒÎŊÎŋĪ‚, {months, plural, one {# ÎŧÎŽÎŊÎąĪ‚} other {# ÎŧÎŽÎŊÎĩĪ‚}} Ī€ÎąÎģΚÎŦ", - "person_age_years": "{years, plural, one {# Î­Ī„ÎŋĪ…Ī‚} other {# ÎĩĪ„ĪŽÎŊ}}", + "person_age_years": "{years, plural, other {# Ī‡ĪĪŒÎŊΚι}} Ī€ÎąÎģΚÎŦ", "person_birthdate": "ΓÎĩÎŊÎŊΡθÎĩÎ¯Ī‚ ĪƒĪ„ÎšĪ‚ {date}", "person_hidden": "{name}{hidden, select, true { (ÎēĪĪ…Ī†ĪŒ)} other {}}", "person_recognized": "Î†Ī„ÎŋÎŧÎŋ ÎąÎŊÎąÎŗÎŊĪ‰ĪÎ¯ĪƒĪ„ÎˇÎēÎĩ", + "person_selected": "Î†Ī„ÎŋÎŧÎŋ ÎĩĪ€ÎšÎģÎ­Ī‡Î¸ÎˇÎēÎĩ", "photo_shared_all_users": "ÎĻÎąÎ¯ÎŊÎĩĪ„ÎąÎš ĪŒĪ„Îš ÎŧÎŋÎšĪÎąĪƒĪ„ÎŽÎēÎąĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚ ÎŧÎĩ ΌÎģÎŋĪ…Ī‚ Ī„ÎŋĪ…Ī‚ Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ ÎŽ δÎĩÎŊ Î­Ī‡ÎĩĪ„Îĩ ÎēÎąÎŊέÎŊÎąÎŊ Ī‡ĪÎŽĪƒĪ„Îˇ ÎŗÎšÎą ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ.", "photos": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", "photos_and_videos": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ & ΒίÎŊĪ„ÎĩÎŋ", + "photos_count": "{count, plural, one {{count, number} ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą} other {{count, number} ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚}}", "photos_from_previous_years": "ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ΀΁ÎŋÎˇÎŗÎŋĪÎŧÎĩÎŊΉÎŊ ÎĩĪ„ĪŽÎŊ", + "photos_only": "ÎœĪŒÎŊÎŋ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", "pick_a_location": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎŧΚι Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą", "pick_custom_range": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋ ÎĩĪĪÎŋĪ‚", "pick_date_range": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎĩĪĪÎŋĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎšĪŽÎŊ", @@ -1646,8 +1755,6 @@ "play_original_video_setting_description": "Î ĪÎŋĪ„Î¯ÎŧÎˇĪƒÎĩ Ī„ÎˇÎŊ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ ΄ΉÎŊ ÎąĪĪ‡ÎšÎēĪŽÎŊ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎąÎŊĪ„Î¯ ΄ΉÎŊ ÎŧÎĩĪ„ÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋΚΡÎŧέÎŊΉÎŊ. ΑÎŊ Ī„Îŋ ÎąĪĪ‡ÎšÎēΌ ÎąĪĪ‡ÎĩίÎŋ δÎĩÎŊ ÎĩίÎŊιΚ ĪƒĪ…ÎŧÎ˛ÎąĪ„ĪŒ, Î¯ĪƒĪ‰Ī‚ ÎŊÎą ÎŧΡÎŊ ÎąÎŊÎąĪ€ÎąĪÎąĪ‡Î¸Îĩί ĪƒĪ‰ĪƒĪ„ÎŦ.", "play_transcoded_video": "ΑÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ ÎŧÎĩĪ„ÎąÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋĪ… Î˛Î¯ÎŊĪ„ÎĩÎŋ", "please_auth_to_access": "Î ÎąĪÎąÎēÎąÎģĪŽ Ī€ÎšĪƒĪ„ÎŋĪ€ÎŋΚΡθÎĩÎ¯Ī„Îĩ ÎŗÎšÎą ÎŊÎą ÎąĪ€ÎŋÎēĪ„ÎŽĪƒÎĩĪ„Îĩ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ", - "plugin_method_filter_type": "ÎĻίÎģ΄΁Îŋ", - "plugin_method_filter_type_description": "Î‘Ī…Ī„ÎŽ Ρ ÎŧέθÎŋδÎŋĪ‚ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą Ī†ÎšÎģ΄΁ÎŦ΁ÎĩΚ ĪƒĪ…ÎŧβÎŦÎŊĪ„Îą ÎēιΚ, ÎąÎŊÎŦÎģÎŋÎŗÎą ÎŧÎĩ Ī„ÎšĪ‚ ĪƒĪ…ÎŊθΎÎēÎĩĪ‚, ÎŊÎą ÎĩÎŧĪ€ÎŋδίÎļÎĩΚ Ī„ÎˇÎŊ ÎĩÎēĪ„Î­ÎģÎĩĪƒÎˇ ΄ΉÎŊ ÎĩĪ€ĪŒÎŧÎĩÎŊΉÎŊ βΡÎŧÎŦ΄ΉÎŊ", "port": "Î˜ĪĪÎą", "preferences_settings_subtitle": "Î”ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎĩÎ¯Ī„Îĩ Ī„ÎšĪ‚ ΀΁ÎŋĪ„ÎšÎŧÎŽĪƒÎĩÎšĪ‚ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", "preferences_settings_title": "Î ĪÎŋĪ„ÎšÎŧÎŽĪƒÎĩÎšĪ‚", @@ -1664,11 +1771,11 @@ "privacy": "Î™Î´ÎšĪ‰Ī„ÎšÎēĪŒĪ„ÎˇĪ„Îą", "profile": "Î ĪÎŋĪ†Î¯Îģ", "profile_drawer_app_logs": "ÎšÎąĪ„ÎąÎŗĪÎąĪ†Î­Ī‚", + "profile_drawer_client_server_up_to_date": "Ο Ī€ÎĩÎģÎŦĪ„ÎˇĪ‚ ÎēιΚ Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽĪ‚ ÎĩίÎŊιΚ ÎĩÎŊΡÎŧÎĩ΁ΉÎŧέÎŊÎŋΚ", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Η ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą ÎŧΌÎŊÎŋ-ÎŗÎšÎą-ÎąÎŊÎŦÎŗÎŊĪ‰ĪƒÎˇ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΎθΡÎēÎĩ. ÎšĪÎąĪ„ÎŽĪƒĪ„Îĩ Ī€ÎąĪ„ÎˇÎŧέÎŊÎŋ Ī„Îŋ ÎĩΚÎēÎŋÎŊίδΚÎŋ Ī„ÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ ÎŗÎšÎą ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ.", "profile_image_of_user": "ΕιÎēΌÎŊÎą ΀΁ÎŋĪ†Î¯Îģ Ī„ÎŋĪ… Ī‡ĪÎŽĪƒĪ„Îˇ {user}", "profile_picture_set": "ÎŸĪÎšĪƒÎŧĪŒĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚ ΀΁ÎŋĪ†Î¯Îģ.", - "projection_type": "Î¤ĪĪ€ÎŋĪ‚ Î ĪÎŋβÎŋÎģÎŽĪ‚", "public_album": "ΔηÎŧĪŒĪƒÎšÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "public_share": "ΔηÎŧĪŒĪƒÎšÎą ΚÎŋΚÎŊÎŽ Î§ĪÎŽĪƒÎˇ", "purchase_account_info": "ÎĨĪ€ÎŋĪƒĪ„ÎˇĪÎšÎēĪ„ÎŽĪ‚", @@ -1687,6 +1794,7 @@ "purchase_individual_description_2": "ÎšÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎšÎēĪ„ÎŽ", "purchase_individual_title": "Î‘Ī„ÎŋÎŧÎŋ", "purchase_input_suggestion": "ÎˆĪ‡ÎĩĪ„Îĩ έÎŊÎą ÎēÎģÎĩΚδί ΀΁ÎŋΊΌÎŊĪ„ÎŋĪ‚; Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ Ī„Îŋ ÎēÎģÎĩΚδί Ī€ÎąĪÎąÎēÎŦ΄Ή", + "purchase_license_subtitle": "Î‘ÎŗÎŋ΁ÎŦĪƒĪ„Îĩ Ī„Îŋ Immich ÎŗÎšÎą ÎŊÎą Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎžÎĩĪ„Îĩ Ī„Îˇ ĪƒĪ…ÎŊÎĩĪ‡ÎŽ ÎąÎŊÎŦĪ€Ī„Ī…ÎžÎˇ Ī„ÎˇĪ‚ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎąĪ‚", "purchase_lifetime_description": "Î‘ÎŗÎŋ΁ÎŦ ÎĩΆ' Ό΁ÎŋĪ… ÎļĪ‰ÎŽĪ‚", "purchase_option_title": "Î•Î Î™Î›ÎŸÎ“Î•ÎŖ Î‘Î“ÎŸÎĄÎ‘ÎŖ", "purchase_panel_info_1": "Η ÎąÎŊÎŦĪ€Ī„Ī…ÎžÎˇ Ī„ÎŋĪ… Immich ÎąĪ€ÎąÎšĪ„Îĩί Ī€ÎŋÎģĪ Ī‡ĪĪŒÎŊÎŋ ÎēιΚ ΀΁ÎŋĪƒĪ€ÎŦθÎĩΚι, ÎēιΚ Î­Ī‡ÎŋĪ…ÎŧÎĩ ÎŧÎˇĪ‡ÎąÎŊΚÎēÎŋĪĪ‚ Ī€ÎģÎŽĪÎŋĪ…Ī‚ ÎąĪ€ÎąĪƒĪ‡ĪŒÎģÎˇĪƒÎˇĪ‚ Ī€ÎŋĪ… ÎĩĪÎŗÎŦÎļÎŋÎŊĪ„ÎąÎš ΃Îĩ ÎąĪ…Ī„ĪŒ ÎŗÎšÎą ÎŊÎą Ī„Îŋ ÎēÎŦÎŊÎŋĪ…ÎŧÎĩ Ό΃Îŋ Ī„Îŋ Î´Ī…ÎŊÎąĪ„ĪŒÎŊ ÎēÎąÎģĪĪ„Îĩ΁Îŋ. Η ÎąĪ€ÎŋĪƒĪ„ÎŋÎģÎŽ ÎŧÎąĪ‚ ÎĩίÎŊιΚ Ī„Îŋ ÎģÎŋÎŗÎšĪƒÎŧΚÎēΌ ÎąÎŊÎŋÎšĪ‡Ī„ÎŋĪ ÎēĪŽÎ´ÎšÎēÎą ÎēιΚ ÎŋΚ ΡθΚÎēÎ­Ī‚ ÎĩĪ€ÎšĪ‡ÎĩÎšĪÎˇÎŧÎąĪ„ÎšÎēÎ­Ī‚ Ī€ĪÎąÎēĪ„ÎšÎēÎ­Ī‚ ÎŊÎą ÎŗÎ¯ÎŊÎŋĪ…ÎŊ Î˛ÎšĪŽĪƒÎšÎŧΡ Ī€ÎˇÎŗÎŽ ÎĩÎšĪƒÎŋδΎÎŧÎąĪ„ÎŋĪ‚ ÎŗÎšÎą ΀΁ÎŋÎŗĪÎąÎŧÎŧÎąĪ„ÎšĪƒĪ„Î­Ī‚ ÎēιΚ ÎŊÎą δΡÎŧΚÎŋĪ…ĪÎŗÎŽĪƒÎŋĪ…ÎŧÎĩ έÎŊÎą ÎŋΚÎēÎŋĪƒĪĪƒĪ„ÎˇÎŧÎą Ī€ÎŋĪ… ĪƒÎ­Î˛ÎĩĪ„ÎąÎš Ī„Îŋ ÎąĪ€ĪŒĪĪÎˇĪ„Îŋ, ÎŧÎĩ Ī€ĪÎąÎŗÎŧÎąĪ„ÎšÎēÎ­Ī‚ ÎĩÎŊÎąÎģÎģÎąÎēĪ„ÎšÎēÎ­Ī‚ ÎģĪĪƒÎĩÎšĪ‚ ĪƒĪ„ÎšĪ‚ Ī…Ī€ÎˇĪÎĩĪƒÎ¯ÎĩĪ‚ cloud Ī€ÎŋĪ… Ī€ÎąĪÎŋĪ…ĪƒÎšÎŦÎļÎŋĪ…ÎŊ ĪƒĪ…ÎŧĪ€ÎĩĪÎšĪ†ÎŋĪÎ­Ī‚ ÎĩÎēÎŧÎĩĪ„ÎŦÎģÎģÎĩĪ…ĪƒÎˇĪ‚.", @@ -1719,19 +1827,19 @@ "reassigned_assets_to_new_person": "Η ÎąÎŊÎŦθÎĩĪƒÎˇ {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋĪ…} other {# ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ}} ΃Îĩ ÎŊέÎŋ ÎŦĪ„ÎŋÎŧÎŋ", "reassing_hint": "ΑÎŊÎŦθÎĩĪƒÎˇ ΄ΉÎŊ ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ΃Îĩ Ī…Ī€ÎŦ΁·ÎŋÎŊ ÎŦĪ„ÎŋÎŧÎŋ", "recent": "Î ĪĪŒĪƒĪ†ÎąĪ„Îą", + "recent_albums": "Î ĪĪŒĪƒĪ†ÎąĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "recent_searches": "Î ĪĪŒĪƒĪ†ÎąĪ„ÎĩĪ‚ ÎąÎŊÎąÎļÎˇĪ„ÎŽĪƒÎĩÎšĪ‚", "recently_added": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ Ī€ĪĪŒĪƒĪ†ÎąĪ„Îą", - "recently_added_body": "ΜÎĩĪ„ÎąÎ˛ÎĩÎ¯Ī„Îĩ ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ ΃Îĩ ΌÎģÎą ĪŒĪƒÎą Î­Ī‡ÎĩĪ„Îĩ ΀΁ÎŋĪƒÎ¸Î­ĪƒÎĩΚ Ī€ĪĪŒĪƒĪ†ÎąĪ„Îą, ΃Îĩ ÎŧΚι ÎąĪ€ÎŋÎēÎģÎĩÎšĪƒĪ„ÎšÎēÎŽ ΃ÎĩÎģÎ¯Î´Îą.", - "recently_added_description": "ΠÎĩĪÎšÎˇÎŗÎˇÎ¸ÎĩÎ¯Ī„Îĩ ĪƒĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą ĪƒÎąĪ‚, Ī„ÎąÎžÎšÎŊÎŋÎŧΡÎŧέÎŊÎą ÎēÎąĪ„ÎŦ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎŽĪ‚ Ī„ÎŋĪ…Ī‚ ĪƒĪ„Îŋ Immich", "recently_added_page_title": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ Î ĪĪŒĪƒĪ†ÎąĪ„Îą", - "recently_added_title": "Î ĪÎŋĪƒĪ„Î­Î¸ÎˇÎēÎąÎŊ Ī€ĪĪŒĪƒĪ†ÎąĪ„Îą", "recently_taken": "Î›ÎŽĪ†Î¸ÎˇÎēÎąÎŊ Ī€ĪĪŒĪƒĪ†ÎąĪ„Îą", + "recently_taken_page_title": "Î›ÎŽĪ†Î¸ÎˇÎēÎąÎŊ Î ĪĪŒĪƒĪ†ÎąĪ„Îą", "refresh": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ", "refresh_encoded_videos": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋΚΡÎŧέÎŊΉÎŊ Î˛Î¯ÎŊĪ„ÎĩÎŋ", "refresh_faces": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ΀΁ÎŋĪƒĪŽĪ€Ī‰ÎŊ", "refresh_metadata": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊΉÎŊ", "refresh_thumbnails": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎŧΚÎē΁ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", "refreshed": "ΑÎŊÎąÎŊÎĩΉÎŧέÎŊÎą", + "refreshes_every_file": "Î•Ī€ÎąÎŊιδΚιβÎŦÎļÎĩΚ ΌÎģÎą Ī„Îą Ī…Ī€ÎŦ΁·ÎŋÎŊĪ„Îą ÎēιΚ ÎŊέι ÎąĪĪ‡ÎĩÎ¯Îą", "refreshing_encoded_video": "ΑÎŊÎąÎŊÎ­Ī‰ĪƒÎˇ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋĪ… Î˛Î¯ÎŊĪ„ÎĩÎŋ", "refreshing_faces": "ΑÎŊÎąÎŊÎĩĪŽÎŊÎŋÎŊĪ„ÎąÎš Ī€ĪĪŒĪƒĪ‰Ī€Îą", "refreshing_metadata": "Τι ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊÎą ÎąÎŊÎąÎŊÎĩĪŽÎŊÎŋÎŊĪ„ÎąÎš", @@ -1744,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą ÎąĪ†ÎąÎšĪÎ­ĪƒÎĩĪ„Îĩ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}} ÎąĪ€ĪŒ ÎąĪ…Ī„ĪŒÎŊ Ī„ÎŋÎŊ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ;", "remove_assets_title": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ;", "remove_custom_date_range": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ΀΁ÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎˇĪ‚ ·΁ÎŋÎŊΚÎēÎŽĪ‚ Ī€ÎĩĪÎšĪŒÎ´ÎŋĪ…", - "remove_filter": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ Ī†Î¯Îģ΄΁ÎŋĪ…", + "remove_deleted_assets": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ΔιÎĩÎŗĪÎąÎŧÎŧέÎŊΉÎŊ ÎŖĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "remove_from_album": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "remove_from_album_action_prompt": "{count} ÎąĪ†ÎąÎšĪÎ­Î¸ÎˇÎēÎąÎŊ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "remove_from_favorites": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎąĪ€ĪŒ Ī„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", @@ -1755,22 +1863,29 @@ "remove_memory": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎąÎŊÎŦÎŧÎŊÎˇĪƒÎˇĪ‚", "remove_photo_from_memory": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ÎąÎŊÎŦÎŧÎŊÎˇĪƒÎˇ", "remove_tag": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎĩĪ„ÎšÎēÎ­Ī„ÎąĪ‚", + "remove_url": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ ÎŖĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…", "remove_user": "Î‘Ī†ÎąÎ¯ĪÎĩĪƒÎˇ Ī‡ĪÎŽĪƒĪ„Îˇ", "removed_api_key": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ Ī„Îŋ API Key: {name}", "removed_from_archive": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ/ÎēÎąÎŊ ÎąĪ€ĪŒ Ī„Îŋ Î‘ĪĪ‡ÎĩίÎŋ", "removed_from_favorites": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ ÎąĪ€ĪŒ Ī„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", "removed_from_favorites_count": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎąÎŊ {count, plural, other {#}} ÎąĪ€ĪŒ Ī„Îą ÎąÎŗÎąĪ€ÎˇÎŧέÎŊÎą", "removed_memory": "Î”ÎšÎąÎŗĪÎąÎŧÎŧέÎŊΡ ÎŧÎŊÎŽÎŧΡ", + "removed_photo_from_memory": "Î”ÎšÎąÎŗĪÎąÎŧÎŧέÎŊΡ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ÎąĪ€ĪŒ Ī„Îˇ ÎŧÎŊÎŽÎŧΡ", "removed_tagged_assets": "Î‘Ī†ÎąÎšĪÎ­Î¸ÎˇÎēÎĩ Ρ ÎĩĪ„ÎšÎēÎ­Ī„Îą ÎąĪ€ĪŒ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", "rename": "ΜÎĩĪ„ÎŋÎŊÎŋÎŧÎąĪƒÎ¯Îą", + "repair": "Î•Ī€ÎšĪƒÎēÎĩĪ…ÎŽ", + "repair_no_results_message": "Τι ÎąĪĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… δÎĩÎŊ Ī€ÎąĪÎąÎēÎŋÎģÎŋĪ…Î¸ÎŋĪÎŊĪ„ÎąÎš ÎēιΚ Ī„Îą ÎģÎĩÎ¯Ī€ÎŋÎŊĪ„Îą ÎąĪĪ‡ÎĩÎ¯Îą θι ÎĩÎŧĪ†ÎąÎŊÎšĪƒĪ„ÎŋĪÎŊ ÎĩÎ´ĪŽ", + "replace_with_upload": "ΑÎŊĪ„ÎšÎēÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇ ÎŧÎĩ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ", "repository": "Î‘Ī€ÎŋθÎĩĪ„ÎŽĪÎšÎŋ", "require_password": "Î‘Ī€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", + "require_user_to_change_password_on_first_login": "Ο Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚ ÎąĪ€ÎąÎšĪ„ÎĩÎ¯Ī„ÎąÎš ÎŊÎą ÎąÎģÎģÎŦΞÎĩΚ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎŽĪ‚ Ī„ÎŋĪ… ÎēÎąĪ„ÎŦ Ī„ÎˇÎŊ Ī€ĪĪŽĪ„Îˇ ĪƒĪÎŊδÎĩĪƒÎˇ", "rescan": "ΕÎē ÎŊέÎŋĪ… ΃ÎŦĪĪ‰ĪƒÎˇ", "reset": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ", "reset_password": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎēĪ‰Î´ÎšÎēÎŋĪ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚", "reset_people_visibility": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ΀΁ÎŋβÎŋÎģÎŽĪ‚ ÎąĪ„ĪŒÎŧΉÎŊ", "reset_pin_code": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ÎēĪ‰Î´ÎšÎēÎŋĪ PIN", "reset_pin_code_description": "ΑÎŊ ΞÎĩ·ÎŦĪƒÎąĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ PIN ĪƒÎąĪ‚, ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą ÎĩĪ€ÎšÎēÎŋΚÎŊΉÎŊÎŽĪƒÎĩĪ„Îĩ ÎŧÎĩ Ī„ÎŋÎŊ Î´ÎšÎąĪ‡ÎĩÎšĪÎšĪƒĪ„ÎŽ Ī„ÎŋĪ… δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ ÎŗÎšÎą ÎŊÎą Ī„ÎŋÎŊ ÎĩĪ€ÎąÎŊÎąĪ†Î­ĪÎĩΚ", + "reset_pin_code_success": "Ο ÎēĪ‰Î´ÎšÎēĪŒĪ‚ PIN ÎĩĪ€ÎąÎŊÎąĪ†Î­ĪÎ¸ÎˇÎēÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡ĪŽĪ‚", "reset_pin_code_with_password": "ÎœĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ Ī€ÎŦÎŊĪ„Îą ÎŊÎą ÎĩĪ€ÎąÎŊÎąĪ†Î­ĪÎĩĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ PIN Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšĪŽÎŊĪ„ÎąĪ‚ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎŽĪ‚ ĪƒÎąĪ‚", "reset_sqlite": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ SQLite βÎŦĪƒÎˇĪ‚ δÎĩδÎŋÎŧέÎŊΉÎŊ", "reset_sqlite_clear_app_data": "ΕÎēÎēιθÎŦĪÎšĪƒÎˇ δÎĩδÎŋÎŧέÎŊΉÎŊ", @@ -1784,10 +1899,12 @@ "resolved_all_duplicates": "Î•Ī€ÎšÎģĪÎ¸ÎˇÎēÎąÎŊ ΌÎģÎą Ī„Îą Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Îą", "restore": "ΑÎŊÎŦÎēĪ„ÎˇĪƒÎˇ", "restore_all": "ΑÎŊÎŦÎēĪ„ÎˇĪƒÎˇ ΌÎģΉÎŊ", + "restore_trash_action_prompt": "{count} ÎąÎŊÎąÎēĪ„ÎŽÎ¸ÎˇÎēÎąÎŊ ÎąĪ€ĪŒ Ī„Îą ÎąĪ€ÎŋĪĪÎ¯ÎŧÎŧÎąĪ„Îą", "restore_user": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ Ī‡ĪÎŽĪƒĪ„Îˇ", "restored_asset": "ΑÎŊÎąÎēĪ„ÎŽÎ¸ÎˇÎēÎĩ Ī„Îŋ ÎąĪĪ‡ÎĩίÎŋ", "resume": "ÎŖĪ…ÎŊÎ­Ī‡ÎšĪƒÎˇ", "resume_paused_jobs": "ÎŖĪ…ÎŊÎ­Ī‡ÎšĪƒÎˇ {count, plural, one {# ΃Îĩ Ī€ÎąĪĪƒÎˇ ÎĩĪÎŗÎąĪƒÎ¯Îą} other {# ΃Îĩ Ī€ÎąĪĪƒÎˇ ÎĩĪÎŗÎąĪƒÎ¯ÎĩĪ‚}}", + "retry_upload": "Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇ ÎąÎŊÎĩβÎŦ΃ÎŧÎąĪ„ÎŋĪ‚", "review_duplicates": "Î ĪÎŋβÎŋÎģÎŽ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ", "review_large_files": "Î•Ī€ÎšĪƒÎēĪŒĪ€ÎˇĪƒÎˇ ÎŧÎĩÎŗÎŦÎģΉÎŊ ÎąĪĪ‡ÎĩÎ¯Ī‰ÎŊ", "role": "ÎĄĪŒÎģÎŋĪ‚", @@ -1795,6 +1912,7 @@ "role_viewer": "ΘÎĩÎąĪ„ÎŽĪ‚", "running": "ÎŖÎĩ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯Îą", "save": "Î‘Ī€ÎŋθΎÎēÎĩĪ…ĪƒÎˇ", + "save_to_gallery": "Î‘Ī€ÎŋθΎÎēÎĩĪ…ĪƒÎˇ ĪƒĪ„Îˇ ĪƒĪ…ÎģÎģÎŋÎŗÎŽ", "saved": "Î‘Ī€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎŋ", "saved_api_key": "Î‘Ī€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎŋ API key", "saved_profile": "Î‘Ī€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊÎŋ ΀΁ÎŋĪ†Î¯Îģ", @@ -1805,10 +1923,9 @@ "scan": "ÎŖÎŦĪĪ‰ĪƒÎˇ", "scan_all_libraries": "ÎŖÎŦĪĪ‰ĪƒÎˇ ΌÎģΉÎŊ ΄ΉÎŊ ΒιβÎģΚÎŋθΡÎēĪŽÎŊ", "scan_library": "ÎŖÎŦĪĪ‰ĪƒÎˇ", + "scan_settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎŖÎŦĪĪ‰ĪƒÎˇĪ‚", "scanning": "ÎŖÎąĪĪŽÎŊÎĩĪ„ÎąÎš", "scanning_for_album": "ÎŖÎŦĪĪ‰ĪƒÎˇ ÎŗÎšÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ...", - "screencast_mode_description": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩÎŊδÎĩίΞÎĩΉÎŊ ĪƒĪ…ÎŧβÎŦÎŊ΄ΉÎŊ Ī€ÎģΡÎē΄΁ÎŋÎģÎŋÎŗÎ¯ÎŋĪ… ÎēιΚ Ī€ÎŋÎŊĪ„ÎšÎēΚÎŋĪ ĪƒĪ„ÎˇÎŊ ÎŋÎ¸ĪŒÎŊΡ", - "screencast_mode_title": "ΕÎŊÎąÎģÎģÎąÎŗÎŽ ÎģÎĩÎšĪ„ÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎēÎąĪ„ÎąÎŗĪÎąĪ†ÎŽĪ‚ ÎŋÎ¸ĪŒÎŊÎˇĪ‚", "search": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ", "search_albums": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "search_by_context": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŧÎĩ βÎŦĪƒÎˇ Ī„Îŋ Ī€ÎģÎąÎ¯ĪƒÎšÎŋ", @@ -1816,8 +1933,6 @@ "search_by_description_example": "ΗÎŧÎĩĪÎŽĪƒÎšÎą Ī€ÎĩÎļÎŋĪ€ÎŋĪÎ¯Îą ĪƒĪ„Îŋ ΠÎŦĪ€ÎšÎŗÎēÎŋ", "search_by_filename": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ βÎŦ΃ÎĩΚ ÎŋÎŊΌÎŧÎąĪ„ÎŋĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ… ÎŽ ÎĩĪ€Î­ÎēĪ„ÎąĪƒÎˇĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", "search_by_filename_example": "Ī€.·. IMG_1234.JPG ÎŽ PNG", - "search_by_full_path": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŧÎĩ βÎŦĪƒÎˇ Ī„ÎˇÎŊ Ī€ÎģÎŽĪÎˇ Î´ÎšÎąÎ´ĪÎŋÎŧÎŽ ÎŽ Ī„Îŋ ΆÎŦÎēÎĩÎģÎŋ", - "search_by_full_path_example": "/ΓιÎŦÎŊÎŊÎˇĪ‚/Projects/3D_Printing/2026-07-01 — ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą ÎēÎŦÎŊÎĩĪ„Îĩ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŗÎšÎą Ό΁ÎŋĪ…Ī‚ ĪŒĪ€Ī‰Ī‚ Projects, 3D, Printing, 2026 Îē.ÎģĪ€.", "search_by_ocr": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ OCR", "search_by_ocr_example": "ΛÎŦĪ„Îĩ", "search_camera_lens_model": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŧÎŋÎŊĪ„Î­ÎģÎŋĪ… Ī†ÎąÎēÎŋĪ...", @@ -1837,6 +1952,7 @@ "search_filter_location_title": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯Îą", "search_filter_media_type": "Î¤ĪĪ€ÎŋĪ‚ ÎœÎ­ĪƒÎŋĪ…", "search_filter_media_type_title": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„ĪĪ€Îŋ ÎŧÎ­ĪƒÎŋĪ…", + "search_filter_ocr": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ OCR", "search_filter_people_title": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎŦĪ„ÎŋÎŧÎą", "search_filter_star_rating": "ΒαθÎŧÎŋÎģÎŋÎŗÎ¯Îą ÎŧÎĩ ÎąĪƒĪ„Î­ĪÎšÎą", "search_filter_tags_title": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ÎĩĪ„ÎšÎēÎĩĪ„ĪŽÎŊ", @@ -1847,13 +1963,25 @@ "search_no_people_named": "ΚαÎŊέÎŊÎą ÎŦĪ„ÎŋÎŧÎŋ ÎŧÎĩ ΌÎŊÎŋÎŧÎą \"{name}\"", "search_no_result": "ΔÎĩÎŊ Î˛ĪÎ­Î¸ÎˇÎēÎąÎŊ ÎąĪ€ÎŋĪ„ÎĩÎģÎ­ĪƒÎŧÎąĪ„Îą, ΀΁ÎŋĪƒĪ€ÎąÎ¸ÎŽĪƒĪ„Îĩ ÎŧÎĩ Î´ÎšÎąĪ†Îŋ΁ÎĩĪ„ÎšÎēÎ­Ī‚ Îŋ΁ÎŋÎģÎŋÎŗÎ¯ÎĩĪ‚ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚ ÎŽ ĪƒĪ…ÎŊÎ´Ī…ÎąĪƒÎŧÎŋĪĪ‚", "search_options": "Î•Ī€ÎšÎģÎŋÎŗÎ­Ī‚ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", + "search_page_categories": "ÎšÎąĪ„ÎˇÎŗÎŋĪÎ¯ÎĩĪ‚", + "search_page_motion_photos": "ΚιÎŊÎŋĪÎŧÎĩÎŊÎĩĪ‚ ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", + "search_page_no_objects": "Μη Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎĩĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ÎąÎŊĪ„ÎšÎēÎĩΚÎŧέÎŊΉÎŊ", + "search_page_no_places": "Μη Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎĩĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ÎŗÎšÎą ÎŧÎ­ĪÎˇ", + "search_page_screenshots": "ÎŖĪ„ÎšÎŗÎŧÎšĪŒĪ„Ī…Ī€Îą ÎŋÎ¸ĪŒÎŊÎˇĪ‚", "search_page_search_photos_videos": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŗÎšÎą Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚", + "search_page_selfies": "ÎŖÎ­ÎģĪ†Îš", + "search_page_things": "Î ĪÎŦÎŗÎŧÎąĪ„Îą", "search_page_view_all_button": "Î ĪÎŋβÎŋÎģÎŽ ΌÎģΉÎŊ", + "search_page_your_activity": "Η Î´ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„ÎŦ ĪƒÎąĪ‚", + "search_page_your_map": "Ο ·ÎŦĪĪ„ÎˇĪ‚ ĪƒÎąĪ‚", "search_people": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎąĪ„ĪŒÎŧΉÎŊ", "search_places": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎšĪŽÎŊ", "search_rating": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎēÎąĪ„ÎŦ βιθÎŧÎŋÎģÎŋÎŗÎ¯Îą...", + "search_result_page_new_search_hint": "Νέα ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ", "search_settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", "search_state": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŊÎŋÎŧÎŋĪ...", + "search_suggestion_list_smart_search_hint_1": "Η Î­ÎžĪ…Ī€ÎŊΡ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎĩίÎŊιΚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ ÎąĪ€ĪŒ ΀΁ÎŋÎĩĪ€ÎšÎģÎŋÎŗÎŽ, ÎŗÎšÎą ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊΉÎŊ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îŋ ĪƒĪ…ÎŊĪ„ÎąÎēĪ„ÎšÎēΌ ", + "search_suggestion_list_smart_search_hint_2": "m:Ό΁ÎŋĪ‚-ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", "search_tags": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎĩĪ„ÎšÎēÎĩĪ„ĪŽÎŊ...", "search_timezone": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ ÎļĪŽÎŊÎˇĪ‚ ĪŽĪÎąĪ‚...", "search_type": "Î¤ĪĪ€ÎŋĪ‚ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", @@ -1861,12 +1989,13 @@ "searching_locales": "ΑÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎšĪŽÎŊ...", "second": "ΔÎĩĪ…Ī„Îĩ΁ΌÎģÎĩ΀΄Îŋ", "see_all_people": "Î ĪÎŋβÎŋÎģÎŽ ΌÎģΉÎŊ ΄ΉÎŊ ÎąĪ„ĪŒÎŧΉÎŊ", - "select": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ", + "select": "Î•Ī€ÎšÎģÎŋÎŗÎŽ", "select_album": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "select_album_cover": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎĩÎžĪŽĪ†Ī…ÎģÎģÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "select_albums": "Î•Ī€ÎšÎģÎŋÎŗÎŽ Ī€ÎŋÎģÎģĪŽÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "select_all": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ", "select_all_duplicates": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ ΄ΉÎŊ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ", + "select_all_in": "Î•Ī€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ ĪƒĪ„Îŋ {group}", "select_avatar_color": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī‡ĪĪŽÎŧÎą avatar", "select_count": "{count, plural, one {Î•Ī€Î¯ÎģÎĩΞÎĩ #} other {Î•Ī€Î¯ÎģÎĩΞÎĩ #}}", "select_cutoff_date": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎēÎŋĪ€ÎŽĪ‚", @@ -1874,13 +2003,14 @@ "select_featured_photo": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ÎŗÎšÎą ΀΁ÎŋβÎŋÎģÎŽ", "select_from_computer": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎąĪ€ĪŒ Ī…Ī€ÎŋÎģÎŋÎŗÎšĪƒĪ„ÎŽ", "select_keep_all": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Î´ÎšÎąĪ„ÎŽĪÎˇĪƒÎˇ ΌÎģΉÎŊ", + "select_library_owner": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎēÎŦĪ„Îŋ·Îŋ βΚβÎģΚÎŋθΎÎēÎˇĪ‚", "select_new_face": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎŊέÎŋ Ī€ĪĪŒĪƒĪ‰Ī€Îŋ", "select_people": "Î•Ī€Î¯ÎģÎĩΞÎĩ ÎŦĪ„ÎŋÎŧÎą", "select_person": "Î•Ī€Î¯ÎģÎĩΞÎĩ ÎŦĪ„ÎŋÎŧÎŋ", "select_person_to_tag": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ έÎŊÎą ÎŦĪ„ÎŋÎŧÎŋ ÎŗÎšÎą ÎĩĪ€ÎšĪƒÎŽÎŧÎąÎŊĪƒÎˇ", "select_photos": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚", - "select_quality": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą", "select_trash_all": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ΌÎģΉÎŊ", + "select_user_for_sharing_page_err_album": "Î‘Ī€ÎŋĪ„Ī…Ī‡Î¯Îą δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎŦÎģĪ€ÎŋĪ…Îŧ", "selected": "Î•Ī€ÎšÎģÎĩÎŗÎŧέÎŊÎŋΚ", "selected_count": "{count, plural, other {# ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎŋΚ}}", "selected_gps_coordinates": "Î•Ī€ÎšÎģÎĩÎŗÎŧέÎŊÎĩĪ‚ ĪƒĪ…ÎŊĪ„ÎĩĪ„ÎąÎŗÎŧέÎŊÎĩĪ‚ GPS", @@ -1913,31 +2043,42 @@ "setting_image_viewer_original_title": "ÎĻĪŒĪĪ„Ī‰ĪƒÎˇ Ī€ĪĪ‰Ī„ĪŒĪ„Ī…Ī€ÎˇĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚", "setting_image_viewer_preview_subtitle": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎŧÎšÎąĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚ ÎŧÎ­ĪƒÎˇĪ‚ ÎąÎŊÎŦÎģĪ…ĪƒÎˇĪ‚. Î‘Ī€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ ÎĩÎ¯Ī„Îĩ ÎŗÎšÎą ÎŊÎą ΆÎŋĪĪ„ĪŽÎŊÎĩĪ„ÎąÎš ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ Ī„Îŋ Ī€ĪĪ‰Ī„ĪŒĪ„Ī…Ī€Îŋ ÎĩÎ¯Ī„Îĩ ÎŗÎšÎą ÎŊÎą Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ÎŧΌÎŊÎŋ Ρ ÎŧΚÎē΁ÎŋÎŗĪÎąĪ†Î¯Îą.", "setting_image_viewer_preview_title": "ÎĻĪŒĪĪ„Ī‰ĪƒÎˇ ÎĩΚÎēΌÎŊÎąĪ‚ ΀΁ÎŋÎĩĪ€ÎšĪƒÎēĪŒĪ€ÎˇĪƒÎˇĪ‚", + "setting_image_viewer_title": "ΕιÎēΌÎŊÎĩĪ‚", "setting_languages_apply": "Î•Ī†ÎąĪÎŧÎŋÎŗÎŽ", "setting_languages_subtitle": "ΑÎģÎģÎŦÎžĪ„Îĩ Ī„Îˇ ÎŗÎģĪŽĪƒĪƒÎą Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", + "setting_notifications_notify_failures_grace_period": "ΕιδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ÎąĪ€ÎŋĪ„Ī…Ī‡ÎšĪŽÎŊ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ ĪƒĪ„Îŋ Ī€ÎąĪÎąĪƒÎēÎŽÎŊΚÎŋ: {duration}", + "setting_notifications_notify_hours": "{count} ĪŽĪÎĩĪ‚", + "setting_notifications_notify_immediately": "ÎąÎŧÎ­ĪƒĪ‰Ī‚", "setting_notifications_notify_minutes": "{count} ÎģÎĩ΀΄ÎŦ", + "setting_notifications_notify_never": "Ī€ÎŋĪ„Î­", "setting_notifications_notify_seconds": "{count} δÎĩĪ…Ī„Îĩ΁ΌÎģÎĩĪ€Ī„Îą", + "setting_notifications_single_progress_subtitle": "ΛÎĩ΀΄ÎŋÎŧÎĩ΁ÎĩÎ¯Ī‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ΀΁ÎŋĪŒÎ´ÎŋĪ… ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ ÎąÎŊÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ", + "setting_notifications_single_progress_title": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ΀΁ÎŋĪŒÎ´ÎŋĪ… ÎģÎĩ΀΄ÎŋÎŧÎĩ΁ÎĩÎšĪŽÎŊ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", "setting_notifications_subtitle": "Î ĪÎŋĪƒÎąĪÎŧĪŒĪƒĪ„Îĩ Ī„ÎšĪ‚ ΀΁ÎŋĪ„ÎšÎŧÎŽĪƒÎĩÎšĪ‚ ÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", + "setting_notifications_total_progress_subtitle": "ÎŖĪ…ÎŊÎŋÎģΚÎēÎŽ Ī€ĪĪŒÎŋδÎŋĪ‚ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ (ÎŋÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ/ĪƒĪÎŊÎŋÎģÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ)", + "setting_notifications_total_progress_title": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ…ÎŊÎŋÎģΚÎēÎŽĪ‚ ΀΁ÎŋĪŒÎ´ÎŋĪ… δΡÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚ ÎąÎŊĪ„ÎšÎŗĪÎŦΆΉÎŊ ÎąĪƒĪ†ÎąÎģÎĩÎ¯ÎąĪ‚ Ī€ÎąĪÎąĪƒÎēΡÎŊίÎŋĪ…", "setting_video_viewer_auto_play_subtitle": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎēÎąĪ„ÎŦ Ī„Îŋ ÎŦÎŊÎŋÎšÎŗÎŧÎŦ Ī„ÎŋĪ…Ī‚", "setting_video_viewer_auto_play_title": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îˇ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ Î˛Î¯ÎŊĪ„ÎĩÎŋ", "setting_video_viewer_looping_title": "ÎŖĪ…ÎŊÎĩĪ‡ÎŽĪ‚ Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇ", "setting_video_viewer_original_video_subtitle": "ÎŒĪ„ÎąÎŊ ÎŧÎĩĪ„ÎąÎ´Î¯Î´ÎĩĪ„Îĩ έÎŊÎą Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎąĪ€ĪŒ Ī„ÎŋÎŊ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ, ÎąÎŊÎąĪ€ÎąĪÎŦÎŗÎĩĪ„Îĩ Ī„Îŋ ÎąĪ…Î¸ÎĩÎŊĪ„ÎšÎēΌ ÎąÎēΌÎŧΡ ÎēιΚ ĪŒĪ„ÎąÎŊ Ī…Ī€ÎŦ΁·ÎĩΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎŋ ÎŧÎĩ Î´ÎšÎąĪ†Îŋ΁ÎĩĪ„ÎšÎēÎŽ ÎēĪ‰Î´ÎšÎēÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ. ÎœĪ€Îŋ΁Îĩί ÎŊÎą ΀΁ÎŋÎēÎąÎģÎ­ĪƒÎĩΚ ÎēÎąÎ¸Ī…ĪƒĪ„Î­ĪÎˇĪƒÎˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚. Τι Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… ÎĩίÎŊιΚ Î´ÎšÎąÎ¸Î­ĪƒÎšÎŧÎą Ī„ÎŋĪ€ÎšÎēÎŦ, ÎąÎŊÎąĪ€ÎąĪÎŦÎŗÎŋÎŊĪ„ÎąÎš ĪƒĪ„ÎˇÎŊ ÎąĪ…Î¸ÎĩÎŊĪ„ÎšÎēÎŽ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą, ÎąÎŊÎĩÎžÎąĪĪ„ÎŽĪ„Ī‰Ī‚ ÎąĪ…Ī„ÎŽĪ‚ Ī„ÎˇĪ‚ ĪĪÎ¸ÎŧÎšĪƒÎˇĪ‚.", "setting_video_viewer_original_video_title": "ΑÎŊÎąÎŗÎēÎąĪƒĪ„ÎšÎēÎŽ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ ÎąĪ…Î¸ÎĩÎŊĪ„ÎšÎēÎŋĪ Î˛Î¯ÎŊĪ„ÎĩÎŋ", "settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚", + "settings_require_restart": "Î•Ī€ÎąÎŊÎĩÎēÎēΚÎŊÎŽĪƒĪ„Îĩ Ī„Îŋ Immich ÎŗÎšÎą ÎŊÎą ÎĩĪ†ÎąĪÎŧΌ΃ÎĩĪ„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ĪĪÎ¸ÎŧÎšĪƒÎˇ", "settings_saved": "Οι ĪĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ ÎąĪ€ÎŋθΡÎēÎĩĪĪ„ÎˇÎēÎąÎŊ", "setup_pin_code": "ÎĄĪÎ¸ÎŧÎšĪƒÎˇ ÎēĪ‰Î´ÎšÎēÎŋĪ PIN", "share": "ΚÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", + "share_action_prompt": "ΚÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ {count} ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", + "share_add_photos": "Î ĪÎŋĪƒÎ¸ÎŽÎēΡ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", + "share_assets_selected": "{count} ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą", "share_dialog_preparing": "Î ĪÎŋÎĩĪ„ÎŋΚÎŧÎąĪƒÎ¯Îą...", "share_link": "ÎŖĪÎŊδÎĩ΃ÎŧÎŋĪ‚ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", - "share_original": "Î§ĪÎŽĪƒÎˇ ÎąĪĪ‡ÎšÎēÎŋĪ (ÎŧÎĩÎŗÎŦÎģÎŋ ÎŧÎ­ÎŗÎĩθÎŋĪ‚)", - "share_preview": "Î§ĪÎŽĪƒÎˇ ÎŧΚÎē΁ÎŋÎŗĪÎąĪ†Î¯ÎąĪ‚ (ÎŧΚÎē΁Ό ÎŧÎ­ÎŗÎĩθÎŋĪ‚)", - "share_quality_body": "ÎšĪÎąĪ„ÎŽĪƒĪ„Îĩ Ī€ÎąĪ„ÎˇÎŧέÎŊÎŋ Ī„Îŋ ÎēÎŋĪ…ÎŧĪ€Î¯ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚ ÎŗÎšÎą ÎŊÎą ÎĩĪ€ÎšÎģέΞÎĩĪ„Îĩ Ī„ÎˇÎŊ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą Ī„ÎˇĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚ Ī€ĪÎšÎŊ Ī„ÎˇÎŊ ÎēÎŋΚÎŊÎŋĪ€ÎŋÎšÎŽĪƒÎĩĪ„Îĩ.", - "share_quality_title": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„ÎˇÎŊ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą ÎēÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", "shared": "ÎŖÎĩ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ", "shared_album_activities_input_disable": "ΤÎŋ ĪƒĪ‡ĪŒÎģΚÎŋ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋ", "shared_album_activity_remove_content": "ΘέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ Î´ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„Îą;", "shared_album_activity_remove_title": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ Î”ĪÎąĪƒĪ„ÎˇĪÎšĪŒĪ„ÎˇĪ„ÎąĪ‚", "shared_album_section_people_action_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎąĪ€ÎŋĪ‡ĪŽĪÎˇĪƒÎˇĪ‚/ÎēÎąĪ„ÎŦĪÎŗÎˇĪƒÎˇĪ‚ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "shared_album_section_people_action_leave": "Î‘Ī€ÎŋĪ‡ĪŽĪÎˇĪƒÎˇ Ī‡ĪÎŽĪƒĪ„Îˇ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "shared_album_section_people_action_remove_user": "ÎšÎąĪ„ÎŦĪÎŗÎˇĪƒÎˇ Ī‡ĪÎŽĪƒĪ„Îˇ ÎąĪ€ĪŒ Ī„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "shared_album_section_people_title": "ΑΤΟΜΑ", "shared_by": "ÎŖÎĩ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ ÎąĪ€ĪŒ", "shared_by_user": "ÎŖÎĩ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ ÎąĪ€ĪŒ {user}", @@ -1946,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ΜÎĩĪ„ÎąĪ†Îŋ΁΄ΉÎŧέÎŊÎą", "shared_link_app_bar_title": "ΚÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„ÎŋΚ ÎŖĪÎŊδÎĩ΃ÎŧÎŋΚ", "shared_link_clipboard_copied_massage": "ΑÎŊĪ„ÎšÎŗĪÎŦĪ†ÎˇÎēÎĩ ĪƒĪ„Îŋ Ī€ĪĪŒĪ‡ÎĩÎšĪÎŋ", + "shared_link_clipboard_text": "ÎŖĪÎŊδÎĩ΃ÎŧÎŋĪ‚: {link}\nÎšĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚: {password}", "shared_link_create_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎēÎąĪ„ÎŦ Ī„Îˇ δΡÎŧΚÎŋĪ…ĪÎŗÎ¯Îą ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„ÎŋĪ… ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…", - "shared_link_custom_url_description": "Î‘Ī€ÎŋÎēĪ„ÎŽĪƒĪ„Îĩ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ ΃Îĩ ÎąĪ…Ī„ĪŒÎŊ Ī„ÎŋÎŊ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ ÎŧÎĩ έÎŊÎą ΀΁ÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋ URL", - "shared_link_custom_url_title": "Î ĪÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋ URL", - "shared_link_custom_url_warning": "Î ĪÎŋÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ: Ρ Ī‡ĪÎŽĪƒÎˇ ÎēÎŦθÎĩĪ„ÎŋĪ… Ī‡ÎąĪÎąÎēĪ„ÎŽĪÎą (/) ΃Îĩ ΀΁ÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋ ΌÎŊÎŋÎŧÎą URL ÎĩÎŊÎ´Î­Ī‡ÎĩĪ„ÎąÎš ÎŊÎą ΀΁ÎŋÎēÎąÎģÎ­ĪƒÎĩΚ ÎąĪ€ĪĪŒÎ˛ÎģÎĩĪ€Ī„Îˇ ĪƒĪ…ÎŧĪ€ÎĩĪÎšĪ†Îŋ΁ÎŦ.", + "shared_link_custom_url_description": "Î‘Ī€ÎŋÎēĪ„ÎŽĪƒĪ„Îĩ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇ ΃Îĩ ÎąĪ…Ī„ĪŒÎŊ Ī„ÎŋÎŊ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ ÎŧÎĩ ÎŧΚι ΀΁ÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊΡ δΚÎĩĪÎ¸Ī…ÎŊĪƒÎˇ URL", "shared_link_edit_description_hint": "Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ Ī„ÎˇÎŊ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽ Ī„ÎˇĪ‚ ÎēÎŋΚÎŊÎŽĪ‚ Ī‡ĪÎŽĪƒÎˇĪ‚", "shared_link_edit_expire_after_option_day": "1 ΡÎŧÎ­ĪÎą", "shared_link_edit_expire_after_option_days": "{count} ΡÎŧÎ­ĪÎĩĪ‚", @@ -1983,6 +2123,12 @@ "shared_with_partner": "ÎŖÎĩ ÎēÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ ÎŧÎĩ {partner}", "sharing": "ΚÎŋΚÎŊÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", "sharing_enter_password": "Î•ÎšĪƒÎąÎŗÎŦÎŗÎĩĪ„Îĩ Ī„ÎŋÎŊ ÎēĪ‰Î´ÎšÎēΌ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚ ÎŗÎšÎą ÎŊÎą δÎĩÎ¯Ī„Îĩ ÎąĪ…Ī„ÎŽÎŊ Ī„Îˇ ΃ÎĩÎģÎ¯Î´Îą.", + "sharing_page_album": "ΚÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "sharing_page_description": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎŗÎšÎą ÎŊÎą ÎŧÎŋÎšĪÎŦÎļÎĩĪƒĪ„Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŧÎĩ ÎŦĪ„ÎŋÎŧÎą ĪƒĪ„Îŋ δίÎēĪ„Ī…ĪŒ ĪƒÎąĪ‚.", + "sharing_page_empty_list": "ΚΕΝΗ Î›Î™ÎŖÎ¤Î‘", + "sharing_sidebar_description": "ΕÎŧĪ†ÎąÎŊÎ¯ĪƒĪ„Îĩ έÎŊÎąÎŊ ĪƒĪÎŊδÎĩ΃ÎŧÎŋ ÎŗÎšÎą ΚÎŋΚÎŊÎŽ Ī‡ĪÎŽĪƒÎˇ ĪƒĪ„ÎˇÎŊ Ī€ÎģÎąĪŠÎŊÎŽ ÎŗĪÎąÎŧÎŧÎŽ", + "sharing_silver_appbar_create_shared_album": "ΝέÎŋ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„Îŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "sharing_silver_appbar_share_partner": "ΜÎŋÎšĪÎąĪƒĪ„ÎĩÎ¯Ī„Îĩ ÎŧÎĩ Ī„ÎŋÎŊ ĪƒĪ…ÎŊÎĩĪÎŗÎŦĪ„Îˇ", "shift_to_permanent_delete": "Ī€ÎąĪ„ÎŽĪƒĪ„Îĩ ⇧ ÎŗÎšÎą ÎŋĪÎšĪƒĪ„ÎšÎēÎŽ Î´ÎšÎąÎŗĪÎąĪ†ÎŽ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", "show_album_options": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩĪ€ÎšÎģÎŋÎŗĪŽÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "show_albums": "Î ĪÎŋβÎŋÎģÎŽ ΄ΉÎŊ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", @@ -1994,9 +2140,7 @@ "show_in_timeline": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ„Îŋ ·΁ÎŋÎŊÎŋÎģĪŒÎŗÎšÎŋ", "show_in_timeline_setting_description": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎąĪ€ĪŒ ÎąĪ…Ī„ĪŒÎŊ Ī„ÎŋÎŊ Ī‡ĪÎŽĪƒĪ„Îˇ ĪƒĪ„Îŋ ·΁ÎŋÎŊÎŋÎģĪŒÎŗÎšĪŒ ĪƒÎąĪ‚", "show_keyboard_shortcuts": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ…ÎŊĪ„ÎŋÎŧÎĩĪĪƒÎĩΉÎŊ Ī€ÎģΡÎē΄΁ÎŋÎģÎŋÎŗÎ¯ÎŋĪ…", - "show_less": "Î ĪÎŋβÎŋÎģÎŽ ÎģÎšÎŗĪŒĪ„Îĩ΁ΉÎŊ", "show_metadata": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊΉÎŊ", - "show_more_fields": "{count, plural, one {ΕÎŧΆÎŦÎŊÎšĪƒÎˇ # ÎĩĪ€ÎšĪ€ÎģέÎŋÎŊ Ī€ÎĩδίÎŋĪ…} other {ΕÎŧΆÎŦÎŊÎšĪƒÎˇ # ÎĩĪ€ÎšĪ€ÎģέÎŋÎŊ Ī€ÎĩÎ´Î¯Ī‰ÎŊ}}", "show_or_hide_info": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎŽ ÎąĪ€ĪŒÎēĪĪ…ĪˆÎˇ Ī€ÎģÎˇĪÎŋΆÎŋĪÎšĪŽÎŊ", "show_password": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎēĪ‰Î´ÎšÎēÎŋĪ", "show_person_options": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩĪ€ÎšÎģÎŋÎŗĪŽÎŊ ÎąĪ„ĪŒÎŧÎŋĪ…", @@ -2004,7 +2148,6 @@ "show_schema": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒĪ‡ÎŽÎŧÎąĪ„ÎŋĪ‚", "show_search_options": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩĪ€ÎšÎģÎŋÎŗĪŽÎŊ ÎąÎŊÎąÎļÎŽĪ„ÎˇĪƒÎˇĪ‚", "show_shared_links": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎēÎŋΚÎŊĪŽÎŊ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧΉÎŊ", - "show_slideshow_metadata_overlay": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎĩĪ€ÎšÎēÎŦÎģĪ…ĪˆÎˇĪ‚ Ī€ÎģÎˇĪÎŋΆÎŋĪÎšĪŽÎŊ ÎĩΚÎēΌÎŊÎąĪ‚", "show_slideshow_transition": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ÎŧÎĩĪ„ÎŦÎ˛ÎąĪƒÎˇĪ‚ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇĪ‚", "show_supporter_badge": "ÎŖÎŽÎŧÎą Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎšÎēĪ„ÎŽ", "show_supporter_badge_description": "ΕÎŧΆÎŦÎŊÎšĪƒÎˇ ĪƒÎŽÎŧÎąĪ„ÎŋĪ‚ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎšÎēĪ„ÎŽ", @@ -2016,25 +2159,18 @@ "sign_out": "Î‘Ī€ÎŋĪƒĪÎŊδÎĩĪƒÎˇ", "sign_up": "Î•ÎŗÎŗĪÎąĪ†ÎŽ", "size": "ÎœÎ­ÎŗÎĩθÎŋĪ‚", - "skip": "Î ÎąĪÎŦÎģÎĩÎšĪˆÎˇ", "skip_to_content": "ΜÎĩĪ„ÎŦÎ˛ÎąĪƒÎˇ ĪƒĪ„Îŋ Ī€ÎĩĪÎšÎĩĪ‡ĪŒÎŧÎĩÎŊÎŋ", "skip_to_folders": "Î ÎąĪÎŦÎēÎąÎŧĪˆÎˇ ĪƒĪ„ÎŋĪ…Ī‚ Ī†ÎąÎēέÎģÎŋĪ…Ī‚", "skip_to_tags": "Î ÎąĪÎŦÎēÎąÎŧĪˆÎˇ ĪƒĪ„ÎšĪ‚ ÎĩĪ„ÎšÎēÎ­Ī„ÎĩĪ‚", "slideshow": "Î ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇ", - "slideshow_body": "ÎšÎąÎ¸Î¯ĪƒĪ„Îĩ ÎąÎŊÎąĪ€ÎąĪ…Ī„ÎšÎēÎŦ ÎēιΚ ÎąĪ€ÎŋÎģÎąĪĪƒĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ĪƒÎąĪ‚ ÎŊÎą ΀΁ÎŋβÎŦÎģÎģÎŋÎŊĪ„ÎąÎš ΃Îĩ ÎŧΚι Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇ Ī€ÎģÎŽĪÎŋĪ…Ī‚ ÎŋÎ¸ĪŒÎŊÎˇĪ‚.", - "slideshow_metadata_overlay_mode": "ΠÎĩĪÎšÎĩĪ‡ĪŒÎŧÎĩÎŊÎŋ ÎĩĪ€ÎšÎēÎŦÎģĪ…ĪˆÎˇĪ‚", - "slideshow_metadata_overlay_mode_description_only": "ÎœĪŒÎŊÎŋ Ī€ÎĩĪÎšÎŗĪÎąĪ†ÎŽ", - "slideshow_metadata_overlay_mode_full": "ΠÎģÎŽĪÎˇĪ‚", "slideshow_repeat": "Î•Ī€ÎąÎŊÎŦÎģÎˇĪˆÎˇ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇĪ‚", "slideshow_repeat_description": "ΕÎēÎēίÎŊÎˇĪƒÎˇ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ÎąĪĪ‡ÎŽ ĪŒĪ„ÎąÎŊ Ī„ÎĩÎģÎĩÎšĪŽĪƒÎĩΚ Ρ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇ", "slideshow_settings": "ÎĄĪ…Î¸ÎŧÎ¯ĪƒÎĩÎšĪ‚ Ī€ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇĪ‚", - "slideshow_title": "Î ÎąĪÎŋĪ…ĪƒÎ¯ÎąĪƒÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", - "smart_album": "ÎˆÎžĪ…Ī€ÎŊÎŋ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", - "some_assets_already_have_a_location_warning": "ÎŸĪÎšĪƒÎŧέÎŊÎą ÎąĪ€ĪŒ Ī„Îą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Î­Ī‡ÎŋĪ…ÎŊ ΎδΡ δÎĩδÎŋÎŧέÎŊÎą Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚", "sort_albums_by": "ΤιΞΚÎŊΌÎŧÎˇĪƒÎˇ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎēÎąĪ„ÎŦ...", "sort_created": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ΔηÎŧΚÎŋĪ…ĪÎŗÎ¯ÎąĪ‚", "sort_items": "Î‘ĪÎšÎ¸ÎŧĪŒĪ‚ ÎąÎŊĪ„ÎšÎēÎĩΚÎŧέÎŊΉÎŊ", "sort_modified": "ΗÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ΄΁ÎŋĪ€ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇĪ‚", + "sort_newest": "ΝÎĩĪŒĪ„ÎĩĪÎˇ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą", "sort_oldest": "Η Ī€ÎšÎŋ Ī€ÎąÎģΚÎŦ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą", "sort_people_by_similarity": "ΤιΞΚÎŊΌÎŧÎˇĪƒÎˇ ÎąĪ„ĪŒÎŧΉÎŊ ÎēÎąĪ„ÎŦ ÎŋÎŧÎŋÎšĪŒĪ„ÎˇĪ„Îą", "sort_recent": "Η Ī€ÎšÎŋ Ī€ĪĪŒĪƒĪ†ÎąĪ„Îˇ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą", @@ -2043,6 +2179,7 @@ "stack": "ÎŖĪ„ÎŋÎ¯Î˛Îą", "stack_action_prompt": "{count} ĪƒĪ…ĪƒĪƒĪ‰ĪÎĩĪĪ„ÎˇÎēÎąÎŊ", "stack_duplicates": "ÎŖĪ„ÎŋÎ¯Î˛ÎąÎžÎˇ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ", + "stack_select_one_photo": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ÎŧΚι ÎēĪĪÎšÎą ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ÎŗÎšÎą Ī„Îˇ ĪƒĪ„ÎŋÎ¯Î˛ÎąÎžÎˇ", "stack_selected_photos": "ÎŖĪ„ÎŋÎ¯Î˛ÎąÎŗÎŧÎą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊΉÎŊ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", "stacked_assets_count": "ÎŖĪ„ÎŋΚβÎŦĪ‡Ī„ÎˇÎēÎąÎŊ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", "stacktrace": "ÎšÎąĪ„ÎąÎŗĪÎąĪ†ÎŽ ĪƒĪ„ÎŋÎ¯Î˛ÎąĪ‚", @@ -2051,10 +2188,6 @@ "start_date_before_end_date": "Η ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą έÎŊÎąĪÎžÎˇĪ‚ Ī€ĪÎ­Ī€ÎĩΚ ÎŊÎą ÎĩίÎŊιΚ Ī€ĪÎšÎŊ ÎąĪ€ĪŒ Ī„ÎˇÎŊ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą ÎģÎŽÎžÎˇĪ‚", "state": "ΝÎŋÎŧĪŒĪ‚", "status": "ÎšÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇ", - "step_delete": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ βΎÎŧÎąĪ„ÎŋĪ‚", - "step_delete_confirm": "ΘέÎģÎĩĪ„Îĩ ĪƒÎ¯ÎŗÎŋĪ…ĪÎą ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ĪŒ Ī„Îŋ βΎÎŧÎą;", - "step_details": "ΛÎĩ΀΄ÎŋÎŧÎ­ĪÎĩΚÎĩĪ‚ βΎÎŧÎąĪ„ÎŋĪ‚", - "steps": "ΒήÎŧÎąĪ„Îą", "stop_casting": "ΔιαÎēÎŋĪ€ÎŽ ÎŧÎĩĪ„ÎŦδÎŋĪƒÎˇĪ‚", "stop_motion_photo": "ΔιέÎēÎŋΈÎĩ Ī„ÎˇÎŊ ÎĻΉ΄ÎŋÎŗĪÎąĪ†Î¯Îą ΚίÎŊÎˇĪƒÎˇĪ‚", "stop_photo_sharing": "ΔιαÎēÎŋĪ€ÎŽ ÎēÎŋΚÎŊÎŽĪ‚ Ī‡ĪÎŽĪƒÎˇĪ‚ ΄ΉÎŊ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ ĪƒÎąĪ‚;", @@ -2075,18 +2208,18 @@ "swap_merge_direction": "ΕÎŊÎąÎģÎģÎąÎŗÎŽ ÎēÎąĪ„ÎĩĪÎ¸Ī…ÎŊĪƒÎˇĪ‚ ĪƒĪ…ÎŗĪ‡ĪŽÎŊÎĩĪ…ĪƒÎˇĪ‚", "sync": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚", "sync_albums": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", + "sync_albums_manual_subtitle": "ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎ¯ĪƒĪ„Îĩ ΌÎģÎą Ī„Îą ÎŧÎĩĪ„ÎąĪ†Îŋ΁΄ΉÎŧέÎŊÎą Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎēιΚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎŧÎĩ Ī„Îą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą ÎĩΆÎĩÎ´ĪÎšÎēÎŦ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "sync_local": "ΤÎŋĪ€ÎšÎēĪŒĪ‚ ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚", "sync_remote": "Î‘Ī€ÎŋÎŧÎąÎēĪĪ…ĪƒÎŧέÎŊÎŋĪ‚ ÎŖĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧĪŒĪ‚", "sync_status": "ÎšÎąĪ„ÎŦĪƒĪ„ÎąĪƒÎˇ ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧÎŋĪ", "sync_status_subtitle": "Î ĪÎŋβÎŋÎģÎŽ ÎēιΚ Î´ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ Ī„ÎŋĪ… ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚ ĪƒĪ…ÎŗĪ‡ĪÎŋÎŊÎšĪƒÎŧÎŋĪ", "sync_upload_album_setting_subtitle": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ ÎēιΚ ÎąÎŊÎĩβÎŦĪƒĪ„Îĩ Ī„ÎšĪ‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩΌ ĪƒÎąĪ‚ ĪƒĪ„Îą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ĪƒĪ„Îŋ Immich", - "system_theme": "ΘέÎŧÎą ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚", - "system_theme_command_description": "Î§ĪÎŽĪƒÎˇ θέÎŧÎąĪ„ÎŋĪ‚ ÎąĪ€ĪŒ Ī„Îŋ ĪƒĪĪƒĪ„ÎˇÎŧÎą ({value})", "tag": "Î•Ī„ÎšÎēÎ­Ī„Îą", "tag_assets": "Î•Ī„ÎšÎēÎĩĪ„ÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", "tag_created": "ΔηÎŧΚÎŋĪ…ĪÎŗÎŽÎ¸ÎˇÎēÎĩ ÎĩĪ„ÎšÎēÎ­Ī„Îą: {tag}", "tag_face": "Î•Ī€ÎšĪƒÎŽÎŧÎąÎŊĪƒÎˇ ΀΁ÎŋĪƒĪŽĪ€ÎŋĪ…", "tag_feature_description": "ΠÎĩĪÎšÎŽÎŗÎˇĪƒÎˇ ΃Îĩ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… ÎĩίÎŊιΚ ÎŋĪÎŗÎąÎŊΉÎŧέÎŊÎą ĪƒĪÎŧΆΉÎŊÎą ÎŧÎĩ ÎģÎŋÎŗÎšÎēÎŦ θέÎŧÎąĪ„Îą ÎĩĪ„ÎšÎēÎĩĪ„ĪŽÎŊ", + "tag_not_found_question": "ΔÎĩÎŊ ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ ÎŊÎą Î˛ĪÎĩÎ¯Ī„Îĩ ÎŧΚι ÎĩĪ„ÎšÎēÎ­Ī„Îą; ΔηÎŧΚÎŋĪ…ĪÎŗÎŽĪƒĪ„Îĩ ÎŧΚι ÎŊέι ÎĩĪ„ÎšÎēÎ­Ī„Îą.", "tag_people": "Î•Ī€ÎšĪƒÎŽÎŧÎąÎŊĪƒÎˇ ÎąĪ„ĪŒÎŧΉÎŊ", "tag_updated": "ΕÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ Ρ ÎĩĪ„ÎšÎēÎ­Ī„Îą: {tag}", "tagged_assets": "Î•Ī„ÎšÎēÎĩĪ„ÎŋĪ€ÎŋΚΡÎŧέÎŊÎŋ/Îą {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", @@ -2101,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Î‘ĪÎšÎ¸ÎŧĪŒĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ ÎąÎŊÎŦ ΃ÎĩÎšĪÎŦ ({count})", "theme_setting_colorful_interface_subtitle": "Î•Ī†ÎąĪÎŧĪŒĪƒĪ„Îĩ Î˛ÎąĪƒÎšÎēΌ Ī‡ĪĪŽÎŧÎą ΃Îĩ ÎĩĪ€ÎšĪ†ÎŦÎŊÎĩΚÎĩĪ‚ Ī†ĪŒÎŊĪ„ÎŋĪ….", "theme_setting_colorful_interface_title": "ΠÎŋÎģĪĪ‡ĪĪ‰ÎŧΡ δΚÎĩĪ€ÎąĪ†ÎŽ", + "theme_setting_image_viewer_quality_subtitle": "Î ĪÎŋĪƒÎąĪÎŧĪŒĪƒĪ„Îĩ Ī„ÎˇÎŊ Ī€ÎŋÎšĪŒĪ„ÎˇĪ„Îą Ī„ÎŋĪ… ΀΁ÎŋÎŗĪÎŦÎŧÎŧÎąĪ„ÎŋĪ‚ ΀΁ÎŋβÎŋÎģÎŽĪ‚ ÎĩΚÎēΌÎŊÎąĪ‚ ÎģÎĩ΀΄ÎŋÎŧÎĩ΁ÎĩÎšĪŽÎŊ", + "theme_setting_image_viewer_quality_title": "ΠÎŋÎšĪŒĪ„ÎˇĪ„Îą ΀΁ÎŋβÎŋÎģÎŽĪ‚ ÎĩΚÎēΌÎŊΉÎŊ", "theme_setting_primary_color_subtitle": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ έÎŊÎą Ī‡ĪĪŽÎŧÎą ÎŗÎšÎą ÎēĪĪÎšÎĩĪ‚ ÎĩÎŊÎ­ĪÎŗÎĩΚÎĩĪ‚ ÎēιΚ Ī„ĪŒÎŊÎŋĪ…Ī‚.", "theme_setting_primary_color_title": "Î ĪĪ‰Ī„ÎąĪĪ‡ÎšÎēΌ Ī‡ĪĪŽÎŧÎą", "theme_setting_system_primary_color_title": "Î§ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îŋ Ī‡ĪĪŽÎŧÎą ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚", "theme_setting_system_theme_switch": "Î‘Ī…Ī„ĪŒÎŧÎąĪ„Îŋ (ΑÎēÎŋÎģÎŋĪ…Î¸ÎŽĪƒĪ„Îĩ Ī„Îˇ ĪĪÎ¸ÎŧÎšĪƒÎˇ ĪƒĪ…ĪƒĪ„ÎŽÎŧÎąĪ„ÎŋĪ‚)", + "theme_setting_theme_subtitle": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ Ī„Îˇ ĪĪÎ¸ÎŧÎšĪƒÎˇ θέÎŧÎąĪ„ÎŋĪ‚ Ī„ÎˇĪ‚ ÎĩĪ†ÎąĪÎŧÎŋÎŗÎŽĪ‚", + "theme_setting_three_stage_loading_subtitle": "Η Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī„ĪÎšĪŽÎŊ ĪƒĪ„ÎąÎ´Î¯Ī‰ÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎąĪ…ÎžÎŽĪƒÎĩΚ Ī„ÎˇÎŊ ÎąĪ€ĪŒÎ´ÎŋĪƒÎˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚, ÎąÎģÎģÎŦ ΀΁ÎŋÎēÎąÎģÎĩί ĪƒÎˇÎŧÎąÎŊĪ„ÎšÎēÎŦ Ī…ĪˆÎˇÎģĪŒĪ„Îĩ΁Îŋ Ī†ĪŒĪĪ„Îŋ δΚÎēĪ„ĪÎŋĪ…", + "theme_setting_three_stage_loading_title": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„Îˇ Ī†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī„ĪÎšĪŽÎŊ ĪƒĪ„ÎąÎ´Î¯Ī‰ÎŊ", "then": "Î¤ĪŒĪ„Îĩ", "they_will_be_merged_together": "Θι ĪƒĪ…ÎŗĪ‡Ī‰ÎŊÎĩĪ…Î¸ÎŋĪÎŊ ÎŧÎąÎļί", "third_party_resources": "Î ĪŒĪÎŋΚ Ī„ĪÎ¯Ī„Ī‰ÎŊ", @@ -2130,17 +2268,23 @@ "trash_all": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ΌÎģΉÎŊ", "trash_count": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ {count, number}", "trash_delete_asset": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ/ÎŸĪÎšĪƒĪ„. Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ΑÎŊĪ„ÎšÎēÎĩΚÎŧέÎŊÎŋĪ…", + "trash_emptied": "ΑδÎĩΚÎŦĪƒĪ„ÎˇÎēÎąÎŊ Ī„Îą ΃ÎēÎŋĪ…Ī€Î¯Î´ÎšÎą", "trash_no_results_message": "Οι ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ÎēιΚ Ī„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ Ī€ÎŋĪ… Î˛ĪÎ¯ĪƒÎēÎŋÎŊĪ„ÎąÎš ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ θι ÎĩÎŧĪ†ÎąÎŊίÎļÎŋÎŊĪ„ÎąÎš ÎĩÎ´ĪŽ.", "trash_page_delete_all": "Î”ÎšÎąÎŗĪÎąĪ†ÎŽ ΌÎģΉÎŊ", + "trash_page_empty_trash_dialog_content": "ΘέÎģÎĩĪ„Îĩ ÎŊÎą ιδÎĩΚÎŦ΃ÎĩĪ„Îĩ Ī„Îą Ī€ÎĩĪÎšÎŋĪ…ĪƒÎšÎąÎēÎŦ ĪƒÎąĪ‚ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ; Î‘Ī…Ī„ÎŦ Ī„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą θι ÎēÎąĪ„ÎąĪÎŗÎˇÎ¸ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎąĪ€ĪŒ Ī„Îŋ Immich", "trash_page_info": "Τι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… Î­Ī‡ÎŋĪ…ÎŊ ÎąĪ€ÎŋĪĪÎšĪ†Î¸Îĩί θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎŧÎĩĪ„ÎŦ ÎąĪ€ĪŒ {days} ΡÎŧÎ­ĪÎĩĪ‚", + "trash_page_no_assets": "ΔÎĩÎŊ Ī…Ī€ÎŦ΁·ÎŋĪ…ÎŊ Ī€ÎĩĪÎšÎŋĪ…ĪƒÎšÎąÎēÎŦ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… Î­Ī‡ÎŋĪ…ÎŊ ÎąĪ€ÎŋĪĪÎšĪ†Î¸Îĩί", + "trash_page_restore_all": "Î•Ī€ÎąÎŊÎąĪ†Îŋ΁ÎŦ ΌÎģΉÎŊ", + "trash_page_select_assets_btn": "Î•Ī€ÎšÎģÎ­ÎžĪ„Îĩ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą", + "trash_page_title": "ΚÎŦδÎŋĪ‚ Î‘Ī€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ ({count})", "trashed_items_will_be_permanently_deleted_after": "Τι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą Ī€ÎŋĪ… Î˛ĪÎ¯ĪƒÎēÎŋÎŊĪ„ÎąÎš ĪƒĪ„ÎŋÎŊ ÎēÎŦδÎŋ ÎąĪ€ÎŋĪĪÎšÎŧÎŧÎŦ΄ΉÎŊ θι Î´ÎšÎąÎŗĪÎąĪ†ÎŋĪÎŊ ÎŋĪÎšĪƒĪ„ÎšÎēÎŦ ÎŧÎĩĪ„ÎŦ ÎąĪ€ĪŒ {days, plural, one {# ΡÎŧÎ­ĪÎą} other {# ΡÎŧÎ­ĪÎĩĪ‚}}.", "trigger": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎˇĪ„ÎŽĪ‚", - "trigger_asset_metadata_extraction": "Î•ÎžÎąÎŗĪ‰ÎŗÎŽ ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊΉÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", - "trigger_asset_metadata_extraction_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îą ĪŒĪ„ÎąÎŊ ÎĩΞÎŦÎŗÎŋÎŊĪ„ÎąÎš Ī„Îą ÎŧÎĩĪ„ÎąÎ´ÎĩδÎŋÎŧέÎŊÎą EXIF ÎĩÎŊĪŒĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ…", - "trigger_asset_uploaded": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎŖĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", + "trigger_asset_uploaded": "ΤÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ ÎąÎŊέβΡÎēÎĩ", "trigger_asset_uploaded_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ĪŒĪ„ÎąÎŊ ÎąÎŊÎĩÎ˛ÎąÎ¯ÎŊÎĩΚ έÎŊÎą ÎŊέÎŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ", + "trigger_description": "ΈÎŊÎą ĪƒĪ…ÎŧβÎŦÎŊ Ī€ÎŋĪ… ΞÎĩÎēΚÎŊÎŦ Ī„Îˇ ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "trigger_person_recognized": "Î†Ī„ÎŋÎŧÎŋ ΑÎŊÎąÎŗÎŊĪ‰ĪÎ¯ĪƒĪ„ÎˇÎēÎĩ", - "trigger_person_recognized_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ÎąĪ…Ī„ĪŒÎŧÎąĪ„Îą ĪŒĪ„ÎąÎŊ ÎąÎŊÎšĪ‡ÎŊÎĩĪÎĩĪ„ÎąÎš έÎŊÎą ÎŦĪ„ÎŋÎŧÎŋ", + "trigger_person_recognized_description": "ΕÎŊÎĩĪÎŗÎŋĪ€ÎŋΚÎĩÎ¯Ī„ÎąÎš ĪŒĪ„ÎąÎŊ ÎąÎŊÎšĪ‡ÎŊÎĩĪÎĩĪ„ÎąÎš ÎŦĪ„ÎŋÎŧÎŋ", + "trigger_type": "Î¤ĪĪ€ÎŋĪ‚ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎˇĪ„ÎŽ", "troubleshoot": "Î•Ī€Î¯ÎģĪ…ĪƒÎˇ ΀΁ÎŋβÎģΡÎŧÎŦ΄ΉÎŊ", "type": "Î¤ĪĪ€ÎŋĪ‚", "unable_to_change_pin_code": "Î‘Î´Ī…ÎŊÎąÎŧÎ¯Îą ÎąÎģÎģÎąÎŗÎŽĪ‚ ÎēĪ‰Î´ÎšÎēÎŋĪ PIN", @@ -2156,7 +2300,6 @@ "unknown": "Î†ÎŗÎŊĪ‰ĪƒĪ„Îŋ", "unknown_country": "Î†ÎŗÎŊĪ‰ĪƒĪ„Îˇ Î§ĪŽĪÎą", "unknown_date": "Î†ÎŗÎŊĪ‰ĪƒĪ„Îˇ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎ¯Îą", - "unknown_schema": "Î†ÎŗÎŊĪ‰ĪƒĪ„Îˇ δÎŋÎŧÎŽ", "unknown_year": "Î†ÎŗÎŊĪ‰ĪƒĪ„Îŋ ÎˆĪ„ÎŋĪ‚", "unlimited": "Î‘Ī€ÎĩĪÎšĪŒĪÎšĪƒĪ„Îŋ", "unlink_motion_video": "Î‘Ī€ÎŋĪƒĪ…ÎŊÎ´Î­ĪƒĪ„Îĩ Ī„Îŋ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎēίÎŊÎˇĪƒÎˇĪ‚", @@ -2169,20 +2312,23 @@ "unsaved_change": "Μη ÎąĪ€ÎŋθΡÎēÎĩĪ…ÎŧέÎŊΡ ÎąÎģÎģÎąÎŗÎŽ", "unselect_all": "Î‘Ī€ÎŋÎĩĪ€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ", "unselect_all_duplicates": "Î‘Ī€ÎŋÎĩĪ€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ ΄ΉÎŊ Î´ÎšĪ€ÎģĪŒĪ„Ī…Ī€Ī‰ÎŊ", + "unselect_all_in": "Î‘Ī€ÎŋÎĩĪ€ÎšÎģÎŋÎŗÎŽ ΌÎģΉÎŊ ĪƒĪ„Îŋ {group}", "unstack": "Î‘Ī€ÎŋĪƒĪ„ÎŋÎ¯Î˛ÎąÎžÎˇ", "unstack_action_prompt": "{count} ÎąĪ€ÎŋĪƒĪ…ĪƒĪƒĪ‰ĪÎĩĪĪ„ÎˇÎēÎąÎŊ", "unstacked_assets_count": "Î‘Ī€ÎŋĪƒĪ„ÎŋΚβÎŦÎžÎąĪ„Îĩ {count, plural, one {# ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ} other {# ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą}}", "unsupported_field_type": "Μη Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎšÎļΌÎŧÎĩÎŊÎŋĪ‚ Ī„ĪĪ€ÎŋĪ‚ Ī€ÎĩδίÎŋĪ…", "unsupported_file_type": "ΤÎŋ ÎąĪĪ‡ÎĩίÎŋ {file} δÎĩÎŊ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ÎŧÎĩĪ„ÎąĪ†ÎŋĪĪ„Ī‰Î¸Îĩί ÎĩĪ€ÎĩΚδΎ Îŋ Ī„ĪĪ€ÎŋĪ‚ ÎąĪĪ‡ÎĩίÎŋĪ… {type} δÎĩÎŊ Ī…Ī€ÎŋĪƒĪ„ÎˇĪÎ¯ÎļÎĩĪ„ÎąÎš.", "untagged": "Î§Ī‰ĪÎ¯Ī‚ ÎĩĪ„ÎšÎēÎ­Ī„Îą", + "untitled_workflow": "Νέα ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "up_next": "ΑÎēÎŋÎģÎŋĪ…Î¸Îĩί", "update_location_action_prompt": "ΕÎŊΡÎŧÎ­ĪĪ‰ĪƒÎˇ Ī„ÎŋĪ€ÎŋθÎĩĪƒÎ¯ÎąĪ‚ ÎŗÎšÎą {count} ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧÎĩ:", "updated_at": "ΕÎŊΡÎŧÎĩ΁ΉÎŧέÎŊÎŋ", "updated_password": "Ο ÎēĪ‰Î´ÎšÎēĪŒĪ‚ Ī€ĪĪŒĪƒÎ˛ÎąĪƒÎˇĪ‚ ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ", "upload": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ", "upload_concurrency": "Î¤ÎąĪ…Ī„ĪŒĪ‡ĪÎŋÎŊΡ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ", - "upload_day_count": "{date}: {count, plural, one {# ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ} other {# ÎŧÎĩĪ„ÎąĪ†ÎŋĪĪ„ĪŽĪƒÎĩÎšĪ‚}}", "upload_details": "ΛÎĩ΀΄ÎŋÎŧÎ­ĪÎĩΚÎĩĪ‚ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚", + "upload_dialog_info": "ΘέÎģÎĩĪ„Îĩ ÎŊÎą ÎąÎŊĪ„ÎšÎŗĪÎŦΈÎĩĪ„Îĩ (ÎēÎŦÎŊÎĩĪ„Îĩ backup) Ī„Îą ÎĩĪ€ÎšÎģÎĩÎŗÎŧέÎŊo(Îą) ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ(Îą) ĪƒĪ„Îŋ δΚιÎēÎŋÎŧÎšĪƒĪ„ÎŽ;", + "upload_dialog_title": "ΑÎŊÎ­Î˛ÎąĪƒÎŧÎą ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", "upload_error_with_count": "ÎŖĪ†ÎŦÎģÎŧÎą ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚ ÎŗÎšÎą {count, plural, one {# ÎąĪĪ‡ÎĩίÎŋ} other {# ÎąĪĪ‡ÎĩÎ¯Îą}}", "upload_errors": "Η ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎŋÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ ÎŧÎĩ {count, plural, one {# ĪƒĪ†ÎŦÎģÎŧÎą} other {# ĪƒĪ†ÎŦÎģÎŧÎąĪ„Îą}}, ÎąÎŊÎąÎŊÎĩĪŽĪƒĪ„Îĩ Ī„Îˇ ΃ÎĩÎģÎ¯Î´Îą ÎŗÎšÎą ÎŊÎą δÎĩÎ¯Ī„Îĩ ÎŊέι ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚.", "upload_finished": "ΟÎģÎŋÎēÎģÎŽĪĪ‰ĪƒÎˇ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇĪ‚", @@ -2192,13 +2338,9 @@ "upload_status_errors": "ÎŖĪ†ÎŦÎģÎŧÎąĪ„Îą", "upload_status_uploaded": "ΜÎĩĪ„ÎąĪ†ÎŋĪĪ„ĪŽÎ¸ÎˇÎēÎąÎŊ", "upload_success": "Η ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎŋÎģÎŋÎēÎģÎˇĪĪŽÎ¸ÎˇÎēÎĩ, ÎąÎŊÎąÎŊÎĩĪŽĪƒĪ„Îĩ Ī„Îˇ ΃ÎĩÎģÎ¯Î´Îą ÎŗÎšÎą ÎŊÎą δÎĩÎ¯Ī„Îĩ Ī„Îą ÎŊέι ÎąÎŊĪ„ÎšÎēÎĩίÎŧÎĩÎŊÎą.", - "upload_to_album_body": "Για Ī‡ĪÎŽĪƒĪ„ÎĩĪ‚ Ī€ÎŋĪ… δÎĩÎŊ Ī‡ĪÎˇĪƒÎšÎŧÎŋĪ€ÎŋΚÎŋĪÎŊ Ī„Îˇ ·ÎĩÎšĪÎŋÎēίÎŊÎˇĪ„Îˇ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ, ÎŧĪ€Îŋ΁ÎĩÎ¯Ī„Îĩ Ī€ÎģέÎŋÎŊ ÎŊÎą ΀΁ÎŋĪƒÎ¸Î­Ī„ÎĩĪ„Îĩ ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ Ī„ÎŋĪ€ÎšÎēÎ­Ī‚ ΆΉ΄ÎŋÎŗĪÎąĪ†Î¯ÎĩĪ‚ ΃Îĩ έÎŊÎą ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎēÎąĪ„ÎŦ Ī„Îˇ ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎŽ Ī„ÎŋĪ…Ī‚. ΔÎĩÎŊ ·΁ÎĩΚÎŦÎļÎĩĪ„ÎąÎš Ī€ÎšÎą ÎŊÎą Ī„ÎšĪ‚ ÎąÎŊÎĩβÎŦÎļÎĩĪ„Îĩ Ī€ĪĪŽĪ„Îą ÎēιΚ ÎŊÎą Ī„ÎšĪ‚ ΀΁ÎŋĪƒÎ¸Î­Ī„ÎĩĪ„Îĩ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ ÎąĪÎŗĪŒĪ„ÎĩĪÎą.", - "upload_to_album_title": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ÎąĪ€ÎĩĪ…Î¸ÎĩÎ¯ÎąĪ‚ ΃Îĩ ÎŦÎģÎŧĪ€ÎŋĪ…Îŧ", "upload_to_immich": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ ĪƒĪ„Îŋ Immich ({count})", "uploading": "ΜÎĩĪ„ÎąĪ†ÎŋĪĪ„ĪŽÎŊÎĩĪ„ÎąÎš", "uploading_media": "ΜÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ Ī€ÎŋÎģĪ…ÎŧÎ­ĪƒĪ‰ÎŊ", - "uploads": "ΜÎĩĪ„ÎąĪ†ÎŋĪĪ„ĪŽĪƒÎĩÎšĪ‚", - "uploads_count": "{count, plural, one {# ÎŧÎĩĪ„ÎąĪ†ĪŒĪĪ„Ī‰ĪƒÎˇ} other {# ÎŧÎĩĪ„ÎąĪ†ÎŋĪĪ„ĪŽĪƒÎĩÎšĪ‚}}", "url": "URL", "usage": "Î§ĪÎŽĪƒÎˇ", "use_biometric": "Î§ĪÎŽĪƒÎˇ βΚÎŋÎŧÎĩĪ„ĪÎšÎēĪŽÎŊ ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Ī‰ÎŊ", @@ -2206,7 +2348,6 @@ "use_browser_locale_description": "ΜÎŋ΁ΆÎŋĪ€ÎŋÎšÎŽĪƒĪ„Îĩ Ī„ÎšĪ‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊίÎĩĪ‚, Ī„ÎŋĪ…Ī‚ Ī‡ĪĪŒÎŊÎŋĪ…Ī‚ ÎēιΚ Ī„ÎŋĪ…Ī‚ ÎąĪÎšÎ¸ÎŧÎŋĪĪ‚ ĪƒĪÎŧΆΉÎŊÎą ÎŧÎĩ Ī„Îˇ ÎŗÎģĪŽĪƒĪƒÎą Ī„ÎŋĪ… ΀΁ÎŋÎŗĪÎŦÎŧÎŧÎąĪ„ÎŋĪ‚ Ī€ÎĩĪÎšÎŽÎŗÎˇĪƒÎŽĪ‚ ĪƒÎąĪ‚", "use_current_connection": "Î§ĪÎŽĪƒÎˇ Ī„ĪÎ­Ī‡ÎŋĪ…ĪƒÎąĪ‚ ĪƒĪÎŊδÎĩĪƒÎˇĪ‚", "use_custom_date_range": "Î§ĪÎŽĪƒÎˇ ΀΁ÎŋĪƒÎąĪÎŧÎŋ΃ÎŧέÎŊÎŋĪ… ÎĩĪĪÎŋĪ…Ī‚ ΡÎŧÎĩ΁ÎŋÎŧΡÎŊÎšĪŽÎŊ", - "use_template": "Î§ĪÎŽĪƒÎˇ Ī€ĪĪŒĪ„Ī…Ī€ÎŋĪ…", "user": "Î§ĪÎŽĪƒĪ„ÎˇĪ‚", "user_has_been_deleted": "Î‘Ī…Ī„ĪŒĪ‚ Îŋ Ī‡ĪÎŽĪƒĪ„ÎˇĪ‚ Î­Ī‡ÎĩΚ δΚÎĩÎŗĪÎąĪ†Îĩί.", "user_id": "ID Î§ĪÎŽĪƒĪ„Îˇ", @@ -2216,6 +2357,7 @@ "user_privacy": "Î‘Ī€ĪŒĪĪÎˇĪ„Îŋ Î§ĪÎŽĪƒĪ„Îˇ", "user_purchase_settings": "Î‘ÎŗÎŋ΁ÎŦ", "user_purchase_settings_description": "Î”ÎšÎąĪ‡ÎĩÎ¯ĪÎšĪƒÎˇ Î‘ÎŗÎŋ΁ÎŦĪ‚", + "user_role_set": "ÎŸĪÎšĪƒÎŧĪŒĪ‚ {user} Ή΂ {role}", "user_usage_detail": "ΛÎĩ΀΄ÎŋÎŧÎĩ΁ÎĩΚÎĩĪ‚ Ī‡ĪÎˇĪƒÎˇĪ‚ Ī„ÎŋĪ… Ī‡ĪÎˇĪƒĪ„Îˇ", "user_usage_stats": "ÎŖĪ„ÎąĪ„ÎšĪƒĪ„ÎšÎēÎŦ Ī‡ĪÎŽĪƒÎˇĪ‚ ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧÎŋĪ", "user_usage_stats_description": "Î ĪÎŋβÎŋÎģÎŽ ĪƒĪ„ÎąĪ„ÎšĪƒĪ„ÎšÎēĪŽÎŊ Ī‡ĪÎŽĪƒÎˇĪ‚ ÎģÎŋÎŗÎąĪÎšÎąĪƒÎŧÎŋĪ", @@ -2225,6 +2367,7 @@ "utilities": "ΒÎŋÎˇÎ¸ÎˇĪ„ÎšÎēÎŦ ΀΁ÎŋÎŗĪÎŦÎŧÎŧÎąĪ„Îą", "validate": "Î•Ī€ÎšÎēĪĪĪ‰ĪƒÎˇ", "validate_endpoint_error": "Î ÎąĪÎąÎēÎąÎģĪŽ ÎĩÎšĪƒÎŦÎŗÎĩĪ„Îĩ έÎŊÎą Î­ÎŗÎē΅΁Îŋ URL", + "validation_error": "ÎŖĪ†ÎŦÎģÎŧÎą ÎĩĪ€ÎšÎēĪĪĪ‰ĪƒÎˇĪ‚", "variables": "ΜÎĩĪ„ÎąÎ˛ÎģÎˇĪ„Î­Ī‚", "version": "ΈÎēδÎŋĪƒÎˇ", "version_announcement_closing": "Ο Ī†Î¯ÎģÎŋĪ‚ ΃ÎŋĪ…, Alex", @@ -2234,9 +2377,9 @@ "video": "ΒίÎŊĪ„ÎĩÎŋ", "video_hover_setting": "Î ĪÎŋÎĩĪ€ÎšĪƒÎēĪŒĪ€ÎˇĪƒÎˇ Î˛Î¯ÎŊĪ„ÎĩÎŋ ÎŧÎĩ Ī„Îŋ δÎĩίÎēĪ„Îˇ Ī„ÎŋĪ… Ī€ÎŋÎŊĪ„ÎšÎēΚÎŋĪ", "video_hover_setting_description": "Î ĪÎŋÎĩĪ€ÎšĪƒÎēĪŒĪ€ÎˇĪƒÎˇ Î˛Î¯ÎŊĪ„ÎĩÎŋ ĪŒĪ„ÎąÎŊ Ī„Îŋ Ī€ÎŋÎŊĪ„Î¯ÎēΚ Î˛ĪÎ¯ĪƒÎēÎĩĪ„ÎąÎš Ī€ÎŦÎŊΉ ÎąĪ€ĪŒ Ī„Îŋ ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋ. ΑÎēΌÎŧΡ ÎēιΚ ĪŒĪ„ÎąÎŊ ÎĩίÎŊιΚ ÎąĪ€ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋΚΡÎŧέÎŊΡ, Ρ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽ ÎŧĪ€Îŋ΁Îĩί ÎŊÎą ΞÎĩÎēΚÎŊÎŽĪƒÎĩΚ Ī„ÎŋĪ€ÎŋθÎĩĪ„ĪŽÎŊĪ„ÎąĪ‚ Ī„Îŋ δÎĩίÎēĪ„Îˇ Ī„ÎŋĪ… Ī€ÎŋÎŊĪ„ÎšÎēΚÎŋĪ Ī€ÎŦÎŊΉ ÎąĪ€ĪŒ Ī„Îŋ ÎĩΚÎēÎŋÎŊίδΚÎŋ ÎąÎŊÎąĪ€ÎąĪÎąÎŗĪ‰ÎŗÎŽĪ‚.", - "video_quality": "ΠÎŋÎšĪŒĪ„ÎˇĪ„Îą Î˛Î¯ÎŊĪ„ÎĩÎŋ", "videos": "ΒίÎŊĪ„ÎĩÎŋ", "videos_count": "{count, plural, one {# ΒίÎŊĪ„ÎĩÎŋ} other {# ΒίÎŊĪ„ÎĩÎŋ}}", + "videos_only": "ÎœĪŒÎŊÎŋ Î˛Î¯ÎŊĪ„ÎĩÎŋ", "view": "Î ĪÎŋβÎŋÎģÎŽ", "view_album": "Î ĪÎŋβÎŋÎģÎŽ ΆÎģÎŧĪ€ÎŋĪ…Îŧ", "view_all": "Î ĪÎŋβÎŋÎģÎŽ ΌÎģΉÎŊ", @@ -2245,30 +2388,30 @@ "view_details": "Î ĪÎŋβÎŋÎģÎŽ ΛÎĩ΀΄ÎŋÎŧÎĩ΁ÎĩÎšĪŽÎŊ", "view_in_timeline": "Î ĪÎŋβÎŋÎģÎŽ ĪƒĪ„Îŋ ·΁ÎŋÎŊÎŋδΚÎŦÎŗĪÎąÎŧÎŧÎą", "view_link": "Î ĪÎŋβÎŋÎģÎŽ ĪƒĪÎŊδÎĩ΃ÎŧÎŋĪ…", + "view_links": "Î ĪÎŋβÎŋÎģÎŽ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧΉÎŊ", "view_name": "Î ĪÎŋβÎŋÎģÎŽ", "view_next_asset": "Î ĪÎŋβÎŋÎģÎŽ ÎĩĪ€ĪŒÎŧÎĩÎŊÎŋĪ… ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", "view_previous_asset": "Î ĪÎŋβÎŋÎģÎŽ ΀΁ÎŋÎˇÎŗÎŋĪÎŧÎĩÎŊÎŋĪ… ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", "view_qr_code": "Î ĪÎŋβÎŋÎģÎŽ ÎēĪ‰Î´ÎšÎēÎŋĪ QR", "view_similar_photos": "Î ĪÎŋβÎŋÎģÎŽ Ī€ÎąĪĪŒÎŧÎŋÎšĪ‰ÎŊ ΆΉ΄ÎŋÎŗĪÎąĪ†ÎšĪŽÎŊ", "view_stack": "Î ĪÎŋβÎŋÎģÎŽ Ī„ÎˇĪ‚ ĪƒĪ„ÎŋÎ¯Î˛ÎąĪ‚", + "view_user": "Î ĪÎŋβÎŋÎģÎŽ Î§ĪÎŽĪƒĪ„Îˇ", "viewer_remove_from_stack": "ÎšÎąĪ„ÎŦĪÎŗÎˇĪƒÎˇ ÎąĪ€ĪŒ Ī„Îˇ ÎŖĪ„ÎŋÎ¯Î˛Îą", + "viewer_stack_use_as_main_asset": "Î§ĪÎŽĪƒÎˇ Ή΂ ÎšĪĪÎšÎŋ ÎŖĪ„ÎŋÎšĪ‡ÎĩίÎŋ", + "viewer_unstack": "Î‘Ī€ÎŋĪƒĪ„ÎŋÎ¯Î˛ÎąÎžÎĩ", "visibility": "ÎŸĪÎąĪ„ĪŒĪ„ÎˇĪ„Îą", "visibility_changed": "Η ÎŋĪÎąĪ„ĪŒĪ„ÎˇĪ„Îą ÎŦÎģÎģιΞÎĩ ÎŗÎšÎą {count, plural, one {# ÎŦĪ„ÎŋÎŧÎŋ} other {# ÎŦĪ„ÎŋÎŧÎą}}", "visual": "ÎŸĪ€Ī„ÎšÎēΌ", + "visual_builder": "ÎŸĪ€Ī„ÎšÎēĪŒĪ‚ δΡÎŧΚÎŋĪ…ĪÎŗĪŒĪ‚", "waiting": "ÎŖĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ΃Îĩ ÎąÎŊÎąÎŧÎŋÎŊÎŽ", "waiting_count": "ÎŖÎĩ ÎąÎŊÎąÎŧÎŋÎŊÎŽ: {count}", "warning": "Î ĪÎŋÎĩΚδÎŋĪ€ÎŋÎ¯ÎˇĪƒÎˇ", "week": "ΕβδÎŋÎŧÎŦδι", "welcome": "ΚαÎģĪ‰ĪƒÎŋĪÎ¯ĪƒÎąĪ„Îĩ", "welcome_to_immich": "ΚαÎģĪ‰ĪƒÎŋĪÎ¯ĪƒÎąĪ„Îĩ ĪƒĪ„Îŋ Ιmmich", - "whats_new": "ΤΚ ÎŊέÎŋ Ī…Ī€ÎŦ΁·ÎĩΚ", - "whats_new_settings_subtitle": "ΔÎĩÎ¯Ī„Îĩ Ī„Îš ÎŊέÎŋ Ī…Ī€ÎŦ΁·ÎĩΚ ĪƒĪ„Îŋ Immich", - "whats_new_version": "ΈÎēδÎŋĪƒÎˇ {version}", - "when": "Î ĪŒĪ„Îĩ", "width": "ΠÎģÎŦĪ„ÎŋĪ‚", "wifi_name": "ΌÎŊÎŋÎŧÎą Wi-Fi", - "workflow": "ÎĄÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", - "workflow_delete_prompt": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ÎŽĪ‰ Ī„Îˇ ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚;", + "workflow_delete_prompt": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ ÎąĪ…Ī„ÎŽ Ī„Îˇ ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚;", "workflow_deleted": "Η ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚ Î´ÎšÎąÎŗĪÎŦĪ†ÎˇÎēÎĩ", "workflow_description": "ΠÎĩĪÎšÎŗĪÎąĪ†ÎŽ ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "workflow_info": "ΠÎģÎˇĪÎŋΆÎŋĪÎ¯ÎĩĪ‚ ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", @@ -2277,17 +2420,17 @@ "workflow_name": "ΌÎŊÎŋÎŧÎą ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "workflow_navigation_prompt": "Î•Î¯ĪƒĪ„Îĩ ĪƒÎ¯ÎŗÎŋ΅΁ÎŋΚ ĪŒĪ„Îš θέÎģÎĩĪ„Îĩ ÎŊÎą Ī†ĪÎŗÎĩĪ„Îĩ Ī‡Ī‰ĪÎ¯Ī‚ ÎŊÎą ÎąĪ€ÎŋθΡÎēÎĩĪĪƒÎĩĪ„Îĩ Ī„ÎšĪ‚ ÎąÎģÎģÎąÎŗÎ­Ī‚ ĪƒÎąĪ‚;", "workflow_summary": "ÎŖĪÎŊÎŋĪˆÎˇ ΁ÎŋÎŽĪ‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", - "workflow_templates": "Î ĪĪŒĪ„Ī…Ī€Îą ΁ÎŋĪŽÎŊ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "workflow_update_success": "Η ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚ ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ ÎŧÎĩ ÎĩĪ€ÎšĪ„Ī…Ī‡Î¯Îą", + "workflow_updated": "Η ΁ÎŋÎŽ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚ ÎĩÎŊΡÎŧÎĩĪĪŽÎ¸ÎˇÎēÎĩ", "workflows": "ÎĄÎŋÎ­Ī‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚", "workflows_help_text": "Οι ΁ÎŋÎ­Ī‚ ÎĩĪÎŗÎąĪƒÎ¯ÎąĪ‚ ÎąĪ…Ī„ÎŋÎŧÎąĪ„ÎŋĪ€ÎŋΚÎŋĪÎŊ ÎĩÎŊÎ­ĪÎŗÎĩΚÎĩĪ‚ ĪƒĪ„Îą ĪƒĪ„ÎŋÎšĪ‡ÎĩÎ¯Îą ĪƒÎąĪ‚ ÎŧÎĩ βÎŦĪƒÎˇ ÎĩÎŊÎĩĪÎŗÎŋĪ€ÎŋÎšÎˇĪ„Î­Ī‚ ÎēιΚ Ī†Î¯ÎģĪ„ĪÎą", "wrong_pin_code": "ΛÎŦθÎŋĪ‚ ÎēĪ‰Î´ÎšÎēĪŒĪ‚ PIN", - "x_of_total": "{x}/{total}", "year": "ÎˆĪ„ÎŋĪ‚", "years_ago": "Ī€ĪÎšÎŊ ÎąĪ€ĪŒ {years, plural, one {# Ī‡ĪĪŒÎŊÎŋ} other {# Ī‡ĪĪŒÎŊΚι}}", "yes": "Ναι", "you_dont_have_any_shared_links": "ΔÎĩÎŊ Î­Ī‡ÎĩĪ„Îĩ ÎēÎŋΚÎŊĪŒĪ‡ĪÎˇĪƒĪ„ÎŋĪ…Ī‚ ĪƒĪ…ÎŊÎ´Î­ĪƒÎŧÎŋĪ…Ī‚", "your_wifi_name": "ΤÎŋ ΌÎŊÎŋÎŧÎą Ī„ÎŋĪ… Wi-Fi ĪƒÎąĪ‚", "zero_to_clear_rating": "Ī€ÎąĪ„ÎŽĪƒĪ„Îĩ 0 ÎŗÎšÎą ÎŊÎą Î´ÎšÎąÎŗĪÎŦΈÎĩĪ„Îĩ Ī„Îˇ βιθÎŧÎŋÎģÎŋÎŗÎ¯Îą Ī„ÎŋĪ… ĪƒĪ„ÎŋÎšĪ‡ÎĩίÎŋĪ…", - "zoom_image": "ΖÎŋĪ…Îŧ ΕιÎēΌÎŊÎąĪ‚" + "zoom_image": "ΖÎŋĪ…Îŧ ΕιÎēΌÎŊÎąĪ‚", + "zoom_to_bounds": "Î•ĪƒĪ„Î¯ÎąĪƒÎˇ ĪƒĪ„Îą ĪŒĪÎšÎą" } diff --git a/i18n/en.json b/i18n/en.json index 1a9d2fa3c7..4f2922f35d 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1,6 +1,5 @@ { "about": "About", - "accept": "Accept", "account": "Account", "account_settings": "Account Settings", "acknowledge": "Acknowledge", @@ -11,33 +10,39 @@ "active": "Active", "active_count": "Active: {count}", "activity": "Activity", + "activity_changed": "Activity is {enabled, select, true {enabled} other {disabled}}", "add": "Add", "add_a_description": "Add a description", "add_a_location": "Add a location", "add_a_name": "Add a name", "add_a_title": "Add a title", "add_action": "Add action", + "add_action_description": "Click to add an action to perform", "add_assets": "Add assets", "add_birthday": "Add a birthday", "add_endpoint": "Add endpoint", "add_exclusion_pattern": "Add exclusion pattern", + "add_filter": "Add filter", + "add_filter_description": "Click to add a filter condition", "add_location": "Add location", + "add_more_users": "Add more users", "add_partner": "Add partner", + "add_path": "Add path", "add_photos": "Add photos", - "add_step": "Add step", "add_tag": "Add tag", "add_to": "Add toâ€Ļ", "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_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album", - "add_to_album_item_count": "Add {count, plural, one {# item} other {# items}} to album", + "add_to_album_toggle": "Toggle selection for {album}", "add_to_albums": "Add to albums", "add_to_albums_count": "Add to albums ({count})", "add_to_bottom_bar": "Add to", + "add_to_shared_album": "Add to shared album", "add_upload_to_stack": "Add upload to stack", "add_url": "Add URL", - "add_user": "Add user", + "add_workflow_step": "Add workflow step", "added_to_archive": "Added to archive", "added_to_favorites": "Added to favorites", "added_to_favorites_count": "Added {count, number} to favorites", @@ -76,7 +81,6 @@ "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", "cron_expression_presets": "Cron expression presets", "disable_login": "Disable login", - "download_csv": "Download CSV", "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.", "export_config_as_json_description": "Download the current system config as a JSON file", @@ -111,21 +115,6 @@ "image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.", "image_thumbnail_title": "Thumbnail Settings", "import_config_from_json_description": "Import system config by uploading a JSON config file", - "integrity_checks_checksum_files": "Checksum files", - "integrity_checks_checksum_files_description": "Configure the checksum check", - "integrity_checks_checksum_files_enable_description": "Enable the checksum check", - "integrity_checks_checksum_files_percentage_limit": "Percentage limit", - "integrity_checks_checksum_files_percentage_limit_description": "Configure the maximum percentage between 0.01 and 1 for how much the checksum check should run each interval.", - "integrity_checks_checksum_files_time_limit": "Time limit", - "integrity_checks_checksum_files_time_limit_description": "Configure the maximum duration for which the checksum check should run each interval. (ms)", - "integrity_checks_missing_files": "Missing files", - "integrity_checks_missing_files_description": "Configure the frequency and enable or disable the missing files check", - "integrity_checks_missing_files_enable_description": "Enable the missing files check", - "integrity_checks_settings": "Integrity checks", - "integrity_checks_settings_description": "Manage integrity checks intervals", - "integrity_checks_untracked_files": "Untracked files", - "integrity_checks_untracked_files_description": "Configure the frequency and enable or disable the untracked files check", - "integrity_checks_untracked_files_enable_description": "Enable the untracked files check", "job_concurrency": "{job} concurrency", "job_created": "Job created", "job_not_concurrency_safe": "This job is not concurrency-safe.", @@ -201,25 +190,9 @@ "machine_learning_smart_search_enabled": "Enable smart search", "machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.", "machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.", - "maintenance_backup_management": "Backup management", "maintenance_delete_backup": "Delete Backup", "maintenance_delete_backup_description": "This file will be irrevocably deleted.", "maintenance_delete_error": "Failed to delete backup.", - "maintenance_integrity_check": "Check", - "maintenance_integrity_check_all": "Check All", - "maintenance_integrity_checksum_mismatch": "Checksum Mismatch", - "maintenance_integrity_checksum_mismatch_description": "Files whose on-disk checksum mismatches the checksum Immich has stored in its database.", - "maintenance_integrity_checksum_mismatch_job": "Check for checksum mismatches", - "maintenance_integrity_checksum_mismatch_refresh_job": "Refresh checksum mismatch reports", - "maintenance_integrity_missing_file": "Missing Files", - "maintenance_integrity_missing_file_description": "Files that Immich has tracked in its database but do not exist on the file system.", - "maintenance_integrity_missing_file_job": "Check for missing files", - "maintenance_integrity_missing_file_refresh_job": "Refresh missing file reports", - "maintenance_integrity_report": "Integrity Report", - "maintenance_integrity_untracked_file": "Untracked Files", - "maintenance_integrity_untracked_file_description": "Files in Immich's directories that Immich does not have any record of.", - "maintenance_integrity_untracked_file_job": "Check for untracked files", - "maintenance_integrity_untracked_file_refresh_job": "Refresh untracked file reports", "maintenance_restore_backup": "Restore Backup", "maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.", "maintenance_restore_backup_different_version": "This backup was created with a different version of Immich!", @@ -294,10 +267,6 @@ "notification_enable_email_notifications": "Enable email notifications", "notification_settings": "Notification Settings", "notification_settings_description": "Manage notification settings, including email", - "oauth_account_management_url": "Account Management URL", - "oauth_account_management_url_description": "Location in the external identity provider where a user can manage their settings or profile.", - "oauth_allow_insecure_requests": "Allow insecure requests", - "oauth_allow_insecure_requests_description": "WARNING: This disables TLS certificate validation for OAuth requests and may expose you to MITM attacks.", "oauth_auto_launch": "Auto launch", "oauth_auto_launch_description": "Start the OAuth login flow automatically upon navigating to the login page", "oauth_auto_register": "Auto register", @@ -305,11 +274,9 @@ "oauth_button_text": "Button text", "oauth_client_secret_description": "Required for confidential client, or if PKCE (Proof Key for Code Exchange) is not supported for public client.", "oauth_enable_description": "Login with OAuth", - "oauth_end_session_url_description": "Redirect the user to this URI when they log out.", "oauth_mobile_redirect_uri": "Mobile redirect URI", "oauth_mobile_redirect_uri_override": "Mobile redirect URI override", "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''", - "oauth_prompt_description": "Prompt parameter (e.g. select_account, login, consent)", "oauth_role_claim": "Role Claim", "oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.", "oauth_settings": "OAuth", @@ -336,8 +303,6 @@ "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.", - "release_channel_release_candidate": "Release candidate", - "release_channel_stable": "Stable", "remove_failed_jobs": "Remove failed jobs", "require_password_change_on_login": "Require user to change password on first login", "reset_settings_to_default": "Reset settings to default", @@ -432,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Applies only to VAAPI and QSV. Sets the dri node used for hardware transcoding.", "transcoding_preset_preset": "Preset (-preset)", "transcoding_preset_preset_description": "Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above 'faster'.", - "transcoding_realtime": "Real-time Transcoding [EXPERIMENTAL]", - "transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.", - "transcoding_realtime_enabled": "Enable real-time transcoding", - "transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.", - "transcoding_realtime_resolutions": "Resolutions", - "transcoding_realtime_resolutions_description": "The resolutions offered for real-time transcoding. Higher resolutions may cause playback issues if the server cannot transcode them quickly enough.", - "transcoding_realtime_video_codecs": "Video codecs", - "transcoding_realtime_video_codecs_description": "The video codecs offered for real-time transcoding. Clients will choose the best option they support during playback. AV1 is more efficient than HEVC, which is more efficient than H.264. When using hardware acceleration, only select the codecs the accelerator can encode. When using software transcoding, note that H.264 is faster than AV1, which is faster than HEVC.", "transcoding_reference_frames": "Reference frames", "transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.", "transcoding_required_description": "Only videos not in an accepted format", @@ -483,8 +440,6 @@ "user_settings_description": "Manage user settings", "user_successfully_removed": "User {email} has been successfully removed.", "users_page_description": "Admin users page", - "version_check_channel": "Release channel", - "version_check_channel_description": "Pick the release channel you want to get version announcements for", "version_check_enabled_description": "Enable version check", "version_check_implications": "The version check feature relies on periodic communication with {server}", "version_check_settings": "Version Check", @@ -496,10 +451,11 @@ "admin_password": "Admin Password", "administration": "Administration", "advanced": "Advanced", - "advanced_filters": "Advanced filters", "advanced_settings_clear_image_cache": "Clear Image Cache", "advanced_settings_clear_image_cache_error": "Failed to clear image cache", "advanced_settings_clear_image_cache_success": "Successfully cleared {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Use this option to filter media during sync based on alternate criteria. Only try this if you have issues with the app detecting all albums.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Use alternate device album sync filter", "advanced_settings_log_level_title": "Log level: {level}", "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.", "advanced_settings_prefer_remote_title": "Prefer remote images", @@ -507,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen", "advanced_settings_readonly_mode_title": "Read-only mode", + "advanced_settings_self_signed_ssl_subtitle": "Skips SSL certificate verification for the server endpoint. Required for self-signed certificates.", + "advanced_settings_self_signed_ssl_title": "Allow self-signed SSL certificates [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web", "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Advanced user's settings", @@ -522,18 +480,31 @@ "album_delete_confirmation": "Are you sure you want to delete the album {album}?", "album_delete_confirmation_description": "If this album is shared, other users will not be able to access it anymore.", "album_deleted": "Album deleted", + "album_info_card_backup_album_excluded": "EXCLUDED", + "album_info_card_backup_album_included": "INCLUDED", "album_info_updated": "Album info updated", + "album_leave": "Leave album?", + "album_leave_confirmation": "Are you sure you want to leave {album}?", "album_name": "Album Name", "album_options": "Album options", "album_remove_user": "Remove user?", "album_remove_user_confirmation": "Are you sure you want to remove {user}?", "album_search_not_found": "No albums found matching your search", + "album_selected": "Album selected", "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_summary": "Album summary", "album_updated": "Album updated", "album_updated_setting_description": "Receive an email notification when a shared album has new assets", "album_upload_assets": "Upload assets from your computer and add to album", + "album_user_left": "Left {album}", + "album_user_removed": "Removed {user}", + "album_viewer_appbar_delete_confirm": "Are you sure you want to delete this album from your account?", "album_viewer_appbar_share_err_delete": "Failed to delete album", + "album_viewer_appbar_share_err_leave": "Failed to leave album", + "album_viewer_appbar_share_err_remove": "There are problems in removing assets from album", + "album_viewer_appbar_share_err_title": "Failed to change album title", + "album_viewer_appbar_share_leave": "Leave album", + "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", "album_with_link_access": "Let anyone with the link see photos and people in this album.", "albums": "Albums", @@ -542,12 +513,14 @@ "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.", "albums_on_device_count": "Albums on device ({count})", + "albums_selected": "{count, plural, one {# album selected} other {# albums selected}}", "all": "All", "all_albums": "All albums", "all_people": "All people", "all_photos": "All photos", "all_videos": "All videos", "allow_dark_mode": "Allow dark mode", + "allow_edits": "Allow edits", "allow_public_user_to_download": "Allow public user to download", "allow_public_user_to_upload": "Allow public user to upload", "allowed": "Allowed", @@ -555,12 +528,14 @@ "always_keep": "Always keep", "always_keep_photos_hint": "Free Up Space will keep all photos on this device.", "always_keep_videos_hint": "Free Up Space will keep all videos on this device.", + "anti_clockwise": "Anti-clockwise", "api_key": "API Key", "api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.", "api_key_empty": "Your API Key name shouldn't be empty", "api_keys": "API Keys", "app_architecture_variant": "Variant (Architecture)", "app_bar_signout_dialog_content": "Are you sure you want to sign out?", + "app_bar_signout_dialog_ok": "Yes", "app_bar_signout_dialog_title": "Sign out", "app_download_links": "App Download Links", "app_settings": "App Settings", @@ -571,19 +546,27 @@ "archive": "Archive", "archive_action_prompt": "{count} added to Archive", "archive_or_unarchive_photo": "Archive or unarchive photo", + "archive_page_no_archived_assets": "No archived assets found", + "archive_page_title": "Archive ({count})", "archive_size": "Archive size", "archive_size_description": "Configure the archive size for downloads (in GiB)", "archived": "Archived", "archived_count": "{count, plural, other {Archived #}}", "are_these_the_same_person": "Are these the same person?", "are_you_sure_to_do_this": "Are you sure you want to do this?", + "array_field_not_fully_supported": "Array fields require manual JSON editing", + "asset_action_delete_err_read_only": "Cannot delete read only asset(s), skipping", + "asset_action_share_err_offline": "Cannot fetch offline asset(s), skipping", "asset_added_to_album": "Added to album", "asset_adding_to_album": "Adding to albumâ€Ļ", "asset_created": "Asset created", - "asset_day_count": "{date}: {count, plural, one {# asset} other {# assets}}", "asset_description_updated": "Asset description has been updated", + "asset_filename_is_offline": "Asset {filename} is offline", + "asset_has_unassigned_faces": "Asset has unassigned faces", "asset_hashing": "Hashingâ€Ļ", "asset_list_group_by_sub_title": "Group by", + "asset_list_layout_settings_dynamic_layout_title": "Dynamic layout", + "asset_list_layout_settings_group_automatically": "Automatic", "asset_list_layout_settings_group_by": "Group assets by", "asset_list_layout_settings_group_by_month_day": "Month + day", "asset_list_layout_sub_title": "Layout", @@ -594,27 +577,36 @@ "asset_not_found_on_icloud": "Asset not found on iCloud. the asset may be inaccessible due to bad file stored on iCloud", "asset_offline": "Asset Offline", "asset_offline_description": "This external asset is no longer found on disk. Please contact your Immich administrator for help.", + "asset_restored_successfully": "Asset restored successfully", "asset_skipped": "Skipped", "asset_skipped_in_trash": "In trash", + "asset_trashed": "Asset trashed", "asset_troubleshoot": "Asset Troubleshoot", "asset_uploaded": "Uploaded", "asset_uploading": "Uploadingâ€Ļ", "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", "asset_viewer_settings_title": "Asset Viewer", "assets": "Assets", + "assets_added_count": "Added {count, plural, one {# asset} other {# assets}}", "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", - "assets_added_to_album_partial_count": "Added {successCount} out of {totalCount} {totalCount, plural, one {asset} other {assets}} to the album", "assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums", "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}}", + "assets_removed_permanently_from_device": "{count} asset(s) removed permanently from your device", "assets_restore_confirmation": "Are you sure you want to restore all your trashed assets? You cannot undo this action! Note that any offline assets cannot be restored this way.", "assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}", + "assets_restored_successfully": "{count} asset(s) restored successfully", "assets_trashed": "{count} asset(s) trashed", "assets_trashed_count": "Trashed {count, plural, one {# asset} other {# assets}}", + "assets_trashed_from_server": "{count} asset(s) trashed from the Immich server", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} already part of the albums", "authorized_devices": "Authorized Devices", @@ -623,48 +615,87 @@ "autoplay_slideshow": "Autoplay slideshow", "back": "Back", "back_close_deselect": "Back, close, or deselect", + "background_backup_running_error": "Background backup is currently running, cannot start manual backup", "background_location_permission": "Background location permission", "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", "background_options": "Background Options", "backup": "Backup", + "backup_album_selection_page_albums_device": "Albums on device ({count})", "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", "backup_album_selection_page_select_albums": "Select albums", "backup_album_selection_page_selection_info": "Selection Info", + "backup_album_selection_page_total_assets": "Total unique assets", "backup_albums_sync": "Backup Albums Synchronization", + "backup_all": "All", + "backup_background_service_backup_failed_message": "Failed to backup assets. Retryingâ€Ļ", "backup_background_service_complete_notification": "Asset backup complete", + "backup_background_service_connection_failed_message": "Failed to connect to the server. Retryingâ€Ļ", + "backup_background_service_current_upload_notification": "Uploading {filename}", "backup_background_service_default_notification": "Checking for new assetsâ€Ļ", + "backup_background_service_error_title": "Backup error", "backup_background_service_in_progress_notification": "Backing up your assetsâ€Ļ", + "backup_background_service_upload_failure_notification": "Failed to upload {filename}", "backup_controller_page_albums": "Backup Albums", + "backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.", + "backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled", + "backup_controller_page_background_app_refresh_enable_button_text": "Go to settings", "backup_controller_page_background_battery_info_link": "Show me how", "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Battery optimizations", + "backup_controller_page_background_charging": "Only while charging", + "backup_controller_page_background_configure_error": "Failed to configure the background service", "backup_controller_page_background_delay": "Delay new assets backup: {duration}", + "backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app", + "backup_controller_page_background_is_off": "Automatic background backup is off", + "backup_controller_page_background_is_on": "Automatic background backup is on", + "backup_controller_page_background_turn_off": "Turn off background service", + "backup_controller_page_background_turn_on": "Turn on background service", + "backup_controller_page_background_wifi": "Only on Wi-Fi", "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selected: ", "backup_controller_page_backup_sub": "Backed up photos and videos", + "backup_controller_page_created": "Created on: {date}", + "backup_controller_page_desc_backup": "Turn on foreground backup to automatically upload new assets to the server when opening the app.", "backup_controller_page_excluded": "Excluded: ", + "backup_controller_page_failed": "Failed ({count})", + "backup_controller_page_filename": "File name: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Backup Information", "backup_controller_page_none_selected": "None selected", "backup_controller_page_remainder": "Remainder", "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", "backup_controller_page_server_storage": "Server Storage", + "backup_controller_page_start_backup": "Start Backup", + "backup_controller_page_status_off": "Automatic foreground backup is off", + "backup_controller_page_status_on": "Automatic foreground backup is on", "backup_controller_page_storage_format": "{used} of {total} used", "backup_controller_page_to_backup": "Albums to be backed up", "backup_controller_page_total_sub": "All unique photos and videos from selected albums", + "backup_controller_page_turn_off": "Turn off foreground backup", + "backup_controller_page_turn_on": "Turn on foreground backup", + "backup_controller_page_uploading_file_info": "Uploading file info", + "backup_err_only_album": "Cannot remove the only album", "backup_error_sync_failed": "Sync failed. Cannot process backup.", "backup_info_card_assets": "assets", + "backup_manual_cancelled": "Cancelled", + "backup_manual_in_progress": "Upload already in progress. Try after sometime", + "backup_manual_success": "Success", + "backup_manual_title": "Upload status", "backup_options": "Backup Options", + "backup_options_page_title": "Backup options", + "backup_setting_subtitle": "Manage background and foreground upload settings", "backup_settings_subtitle": "Manage upload settings", + "backup_upload_details_page_more_details": "Tap for more details", "backward": "Backward", - "battery_optimization_backup_reliability": "Disabling battery optimizations can improve the reliability of background backup", "biometric_auth_enabled": "Biometric authentication enabled", "biometric_locked_out": "You are locked out of biometric authentication", "biometric_no_options": "No biometric options available", "biometric_not_available": "Biometric authentication is not available on this device", + "birthdate_saved": "Date of birth saved successfully", "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", "blurred_background": "Blurred background", - "browse_templates": "Browse templates", "bugs_and_feature_requests": "Bugs & Feature Requests", "build": "Build", "build_image": "Build Image", @@ -672,6 +703,20 @@ "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_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 ignore listed by the app", + "cache_settings_duplicated_assets_title": "Duplicated Assets ({count})", + "cache_settings_statistics_album": "Library thumbnails", + "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_tile_subtitle": "Control the local storage behaviour", + "cache_settings_tile_title": "Local Storage", + "cache_settings_title": "Caching Settings", "camera": "Camera", "camera_brand": "Camera brand", "camera_model": "Camera model", @@ -684,10 +729,10 @@ "cannot_update_the_description": "Cannot update the description", "cast": "Cast", "cast_description": "Configure available cast destinations", - "change": "Change", "change_date": "Change date", "change_description": "Change description", "change_display_order": "Change display order", + "change_expiration_time": "Change expiration time", "change_location": "Change location", "change_name": "Change name", "change_name_successfully": "Changed name successfully", @@ -701,13 +746,17 @@ "change_password_form_password_mismatch": "Passwords do not match", "change_password_form_reenter_new_password": "Re-enter New Password", "change_pin_code": "Change PIN code", + "change_trigger": "Change trigger", + "change_trigger_prompt": "Are you sure you want to change the trigger? This will remove all existing actions and filters.", "change_your_password": "Change your password", "changed_visibility_successfully": "Changed visibility successfully", "charging": "Charging", "charging_requirement_mobile_backup": "Background backup requires the device to be charging", + "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.", "check_logs": "Check Logs", "checksum": "Checksum", - "choose": "Choose", "choose_matching_people_to_merge": "Choose matching people to merge", "city": "City", "cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?", @@ -725,10 +774,11 @@ "clear": "Clear", "clear_all": "Clear all", "clear_all_recent_searches": "Clear all recent searches", - "clear_failed_count": "Clear failed ({count})", "clear_file_cache": "Clear File Cache", "clear_message": "Clear message", "clear_value": "Clear value", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Enter Password", "client_cert_import": "Import", "client_cert_import_success_msg": "Client certificate is imported", "client_cert_invalid_msg": "Invalid certificate file or wrong password", @@ -737,13 +787,12 @@ "client_cert_remove_msg": "Client certificate is removed", "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login", "client_cert_title": "SSL client certificate [EXPERIMENTAL]", + "clockwise": "ĐĄlockwise", "close": "Close", - "cluster_group": "Cluster group", - "cluster_group_description": "People are recognized across the photos of everyone in the group", - "cluster_group_invite_description": "You have been invited to join this cluster group.", "collapse": "Collapse", "collapse_all": "Collapse all", "color": "Color", + "color_theme": "Color theme", "command": "Command", "command_palette_prompt": "Quickly find pages, actions, or commands", "command_palette_to_close": "to close", @@ -756,7 +805,6 @@ "comments_are_disabled": "Comments are disabled", "common_create_new_album": "Create new album", "completed": "Completed", - "configuration": "Configuration", "confirm": "Confirm", "confirm_admin_password": "Confirm Admin Password", "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?", @@ -771,16 +819,19 @@ "contain": "Contain", "context": "Context", "continue": "Continue", - "control_bottom_app_bar_add_tags": "Add Tags", + "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", "control_bottom_app_bar_edit_location": "Edit Location", "control_bottom_app_bar_edit_time": "Edit Date & Time", + "control_bottom_app_bar_share_link": "Share Link", + "control_bottom_app_bar_share_to": "Share To", "control_bottom_app_bar_trash_from_immich": "Move to Trash", "copied_image_to_clipboard": "Copied image to clipboard.", "copied_to_clipboard": "Copied to clipboard!", "copy_error": "Copy error", + "copy_file_path": "Copy file path", "copy_image": "Copy Image", - "copy_json": "Copy JSON", "copy_link": "Copy link", "copy_link_to_clipboard": "Copy link to clipboard", "copy_password": "Copy password", @@ -798,6 +849,7 @@ "create_link_to_share": "Create link to share", "create_link_to_share_description": "Let anyone with the link see the selected photo(s)", "create_new": "CREATE NEW", + "create_new_face": "Create new face", "create_new_person": "Create new person", "create_new_person_hint": "Assign selected assets to a new person", "create_new_user": "Create new user", @@ -807,41 +859,44 @@ "create_shared_album_page_share_select_photos": "Select Photos", "create_shared_link": "Create shared link", "create_tag": "Create tag", - "create_tag_description": "Create a new tag. For nested tags, the full path where your new tag will be placed is printed below.", + "create_tag_description": "Create a new tag. For nested tags, please enter the full path of the tag including forward slashes.", "create_user": "Create user", "create_workflow": "Create workflow", "created": "Created", "created_at": "Created", "creating_linked_albums": "Creating linked albums...", "crop": "Crop", + "crop_aspect_ratio_fixed": "Fixed", "crop_aspect_ratio_free": "Free", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Square", + "curated_object_page_title": "Things", "current_device": "Current device", "current_pin_code": "Current PIN code", "current_server_address": "Current server address", "custom_date": "Custom date", "custom_locale": "Custom locale", "custom_locale_description": "Format dates, times, and numbers based on the selected language and region", + "custom_url": "Custom URL", "cutoff_date_description": "Keep photos from the lastâ€Ļ", "cutoff_day": "{count, plural, one {day} other {days}}", "cutoff_year": "{count, plural, one {year} other {years}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Dark", "dark_theme": "Switch to dark theme", "date": "Date", "date_after": "Date after", "date_and_time": "Date and Time", "date_before": "Date before", - "date_of_birth": "Date of birth", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Date of birth saved successfully", "date_range": "Date range", - "date_time_original": "Date/Time Original", "day": "Day", "days": "Days", - "decline": "Decline", "deduplicate_all": "Deduplicate All", - "default_quality_subtitle": "Quality used when tapping share. Long press the share button to choose each time.", - "default_share_quality": "Default share quality", + "default_locale": "Default Locale", + "default_locale_description": "Format dates and numbers based on your browser locale", "delete": "Delete", "delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's trash and will prompt if you want to delete it locally", "delete_action_prompt": "{count} deleted", @@ -849,8 +904,9 @@ "delete_api_key_prompt": "Are you sure you want to delete this API key?", "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", - "delete_dialog_alert_local_ios": "These items will be deleted from Photos, but will still be available on the Immich server. They will be in Recently Deleted for 30 days.", "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", + "delete_dialog_alert_remote": "These items will be permanently deleted from the Immich server", + "delete_dialog_ok_force": "Delete Anyway", "delete_dialog_title": "Delete Permanently", "delete_duplicates_confirmation": "Are you sure you want to permanently delete these duplicates?", "delete_face": "Delete face", @@ -869,16 +925,21 @@ "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", "delete_user": "Delete user", "deleted_shared_link": "Deleted shared link", + "deletes_missing_assets": "Deletes assets missing from disk", "description": "Description", + "description_input_hint_text": "Add description...", + "description_input_submit_error": "Error updating description, check the log for more details", "deselect_all": "Deselect All", "details": "Details", "direction": "Direction", "disable": "Disable", "disabled": "Disabled", + "disallow_edits": "Disallow edits", "discord": "Discord", "discover": "Discover", "discovered_devices": "Discovered devices", "dismiss_all_errors": "Dismiss all errors", + "dismiss_error": "Dismiss error", "display_options": "Display options", "display_order": "Display order", "display_original_photos": "Display original photos", @@ -887,9 +948,11 @@ "documentation": "Documentation", "done": "Done", "download": "Download", + "download_action_prompt": "Downloading {count} assets", "download_canceled": "Download canceled", "download_complete": "Download complete", "download_enqueue": "Download enqueued", + "download_error": "Download Error", "download_failed": "Download failed", "download_finished": "Download finished", "download_include_embedded_motion_videos": "Embedded videos", @@ -899,17 +962,15 @@ "download_paused": "Download paused", "download_settings": "Download", "download_settings_description": "Manage settings related to asset download", + "download_started": "Download started", + "download_sucess": "Download success", + "download_sucess_android": "The media has been downloaded to DCIM/Immich", "download_waiting_to_retry": "Waiting to retry", "downloading": "Downloading", - "downloading_archive_filename_size": "Downloading archive {filename} (approximately {size})", "downloading_asset_filename": "Downloading asset {filename}", - "downloading_filename": "Downloading {filename}", "downloading_from_icloud": "Downloading from iCloud", "downloading_media": "Downloading media", - "drag_to_reorder": "Drag to reorder", "drop_files_to_upload": "Drop files anywhere to upload", - "duplicate": "Duplicate", - "duplicate_workflow": "Duplicate workflow", "duplicates": "Duplicates", "duplicates_description": "Resolve each group by indicating which, if any, are duplicates.", "duration": "Duration", @@ -921,7 +982,9 @@ "edit_date_and_time": "Edit date and time", "edit_date_and_time_action_prompt": "{count} date and time edited", "edit_date_and_time_by_offset": "Change date by offset", + "edit_date_and_time_by_offset_interval": "New date range: {from} - {to}", "edit_description": "Edit description", + "edit_description_prompt": "Please select a new description:", "edit_exclusion_pattern": "Edit exclusion pattern", "edit_faces": "Edit faces", "edit_key": "Edit key", @@ -936,6 +999,9 @@ "edit_user": "Edit user", "edit_workflow": "Edit workflow", "editor": "Editor", + "editor_close_without_save_prompt": "The changes will not be saved", + "editor_close_without_save_title": "Close editor?", + "editor_confirm_reset_all_changes": "Are you sure you want to reset all changes?", "editor_discard_edits_confirm": "Discard edits", "editor_discard_edits_prompt": "You have unsaved edits. Are you sure you want to discard them?", "editor_discard_edits_title": "Discard edits?", @@ -964,7 +1030,9 @@ "enter_your_pin_code": "Enter your PIN code", "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder", "error": "Error", + "error_change_sort_album": "Failed to change album sort order", "error_delete_face": "Error deleting face from asset", + "error_getting_places": "Error getting places", "error_loading_albums": "Error loading albums", "error_loading_image": "Error loading image", "error_loading_partners": "Error loading partners: {error}", @@ -1004,7 +1072,6 @@ "failed_to_remove_product_key": "Failed to remove product key", "failed_to_reset_pin_code": "Failed to reset PIN code", "failed_to_stack_assets": "Failed to stack assets", - "failed_to_tag_assets": "Failed to tag assets", "failed_to_unstack_assets": "Failed to un-stack assets", "failed_to_update_notification_status": "Failed to update notification status", "incorrect_email_or_password": "Incorrect email or password", @@ -1052,12 +1119,8 @@ "unable_to_get_comments_number": "Unable to get number of comments", "unable_to_get_shared_link": "Failed to get shared link", "unable_to_hide_person": "Unable to hide person", - "unable_to_leave_cluster_group": "Unable to leave cluster group", "unable_to_link_motion_video": "Unable to link motion video", "unable_to_link_oauth_account": "Unable to link OAuth account", - "unable_to_load_cluster_group": "Unable to load cluster group", - "unable_to_load_map": "Unable to load map", - "unable_to_load_map_description": "The map requires WebGL to work properly.", "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", @@ -1107,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Add Description...", "exif_bottom_sheet_description_error": "Error updating description", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "LOCATION", "exif_bottom_sheet_no_description": "No description", + "exif_bottom_sheet_people": "PEOPLE", + "exif_bottom_sheet_person_add_person": "Add name", "exit_slideshow": "Exit Slideshow", "expand": "Expand", "expand_all": "Expand all", + "experimental_settings_new_asset_list_subtitle": "Work in progress", + "experimental_settings_new_asset_list_title": "Enable experimental photo grid", + "experimental_settings_subtitle": "Use at your own risk!", + "experimental_settings_title": "Experimental", "expire_after": "Expire after", "expired": "Expired", "expires_date": "Expires {date}", @@ -1120,41 +1191,41 @@ "export_as_json": "Export as JSON", "export_database": "Export Database", "export_database_description": "Export the SQLite database", - "exposure_time": "Exposure Time", "extension": "Extension", "external": "External", "external_libraries": "External Libraries", "external_network": "External network", "external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", - "f_number": "F-Number", "face_unassigned": "Unassigned", "failed": "Failed", "failed_count": "Failed: {count}", "failed_to_authenticate": "Failed to authenticate", - "failed_to_delete_file": "Failed to delete file", "failed_to_load_assets": "Failed to load assets", "failed_to_load_folder": "Failed to load folder", "favorite": "Favorite", "favorite_action_prompt": "{count} added to Favorites", "favorite_or_unfavorite_photo": "Favorite or unfavorite photo", "favorites": "Favorites", + "favorites_page_no_favorites": "No favorite assets found", "feature_photo_updated": "Feature photo updated", "features": "Features", + "features_in_development": "Features in Development", "features_setting_description": "Manage the app features", "file_name_or_extension": "File name or extension", "file_name_text": "File name", + "file_name_with_value": "File name: {file_name}", "file_size": "File size", "filename": "Filename", "filetype": "Filetype", "filter": "Filter", - "filter_by": "Filter by", + "filter_description": "Conditions to filter the target assets", "filter_people": "Filter people", "filter_places": "Filter places", "filter_tags": "Filter tags", "filters": "Filters", + "find_them_fast": "Find them fast by name with search", "first": "First", "fix_incorrect_match": "Fix incorrect match", - "focal_length": "Focal Length", "folder": "Folder", "folder_not_found": "Folder not found", "folders": "Folders", @@ -1165,7 +1236,6 @@ "free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.", "free_up_space_settings_subtitle": "Free up device storage", "full_path": "Full path: {path}", - "full_path_or_folder": "Full path or folder", "gcast_enabled": "Google Cast", "gcast_enabled_description": "This feature loads external resources from Google in order to work.", "general": "General", @@ -1186,6 +1256,7 @@ "group_owner": "Group by owner", "group_places_by": "Group places by...", "group_year": "Group by year", + "haptic_feedback_switch": "Enable haptic feedback", "haptic_feedback_title": "Haptic Feedback", "has_quota": "Has quota", "hash_asset": "Hash asset", @@ -1206,12 +1277,29 @@ "hide_schema": "Hide schema", "hide_text_recognition": "Hide text recognition", "hide_unnamed_people": "Hide unnamed people", + "home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.", + "home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping", + "home_page_add_to_album_success": "Added {added} assets to album {album}.", + "home_page_album_err_partner": "Can not add partner assets to an album yet, skipping", + "home_page_archive_err_local": "Can not archive local assets yet, skipping", + "home_page_archive_err_partner": "Can not archive partner assets, skipping", "home_page_building_timeline": "Building the timeline", + "home_page_delete_err_partner": "Can not delete partner assets, skipping", + "home_page_delete_remote_err_local": "Local assets in delete remote selection, skipping", + "home_page_favorite_err_local": "Can not favorite local assets yet, skipping", + "home_page_favorite_err_partner": "Can not favorite partner assets yet, skipping", + "home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album so that the timeline can populate photos and videos in it", + "home_page_locked_error_local": "Can not move local assets to locked folder, skipping", + "home_page_locked_error_partner": "Can not move partner assets to locked folder, skipping", + "home_page_share_err_local": "Can not share local assets via link, skipping", + "home_page_upload_err_limit": "Can only upload a maximum of 30 assets at a time, skipping", "host": "Host", "hour": "Hour", "hours": "Hours", "id": "ID", "idle": "Idle", + "ignore_icloud_photos": "Ignore iCloud photos", + "ignore_icloud_photos_description": "Photos that are stored on iCloud will not be uploaded to the Immich server", "image": "Image", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taken on {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taken with {person1} on {date}", @@ -1223,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}", + "image_saved_successfully": "Image saved", + "image_viewer_page_state_provider_download_started": "Download Started", + "image_viewer_page_state_provider_download_success": "Download Success", + "image_viewer_page_state_provider_share_error": "Share Error", "immich_logo": "Immich Logo", "immich_web_interface": "Immich Web Interface", "import_from_json": "Import from JSON", @@ -1237,7 +1329,6 @@ "individual_share": "Individual share", "individual_shares": "Individual shares", "info": "Info", - "integrity_checks": "Integrity Checks", "interval": { "day_at_onepm": "Every day at 1pm", "hours": "Every {hours, plural, one {hour} other {{hours, number} hours}}", @@ -1248,9 +1339,16 @@ "invalid_date_format": "Invalid date format", "invite_people": "Invite People", "invite_to_album": "Invite to album", - "iso": "ISO", + "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", + "json_editor": "JSON editor", + "json_error": "JSON error", "keep": "Keep", "keep_albums": "Keep albums", "keep_albums_count": "Keeping {count} {count, plural, one {album} other {albums}}", @@ -1276,15 +1374,15 @@ "latitude": "Latitude", "leave": "Leave", "leave_album": "Leave album", - "leave_group": "Leave group", - "leave_group_description": "People will no longer be recognized across the photos of everyone in the group. Are you sure you want to continue?", "lens_model": "Lens model", - "less": "Less", "let_others_respond": "Let others respond", "level": "Level", "library": "Library", "library_add_folder": "Add folder", "library_edit_folder": "Edit folder", + "library_options": "Library options", + "library_page_device_albums": "Albums on Device", + "library_page_new_album": "New album", "library_page_sort_asset_count": "Number of assets", "library_page_sort_created": "Created date", "library_page_sort_last_modified": "Last modified", @@ -1294,14 +1392,11 @@ "light_theme": "Switch to light theme", "like": "Like", "like_deleted": "Like deleted", - "link": "Link", "link_motion_video": "Link motion video", "link_to_docs": "For more information, refer to the documentation.", "link_to_oauth": "Link to OAuth", "linked_oauth_account": "Linked OAuth account", "list": "List", - "live": "Live", - "load_more": "Load More", "loading": "Loading", "loading_search_results_failed": "Loading search results failed", "local": "Local", @@ -1330,9 +1425,11 @@ "login": "Login", "login_disabled": "Login has been disabled", "login_form_api_exception": "API exception. Please check the server URL and try again.", + "login_form_back_button_text": "Back", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "Server Endpoint URL", + "login_form_err_http": "Please specify http:// or https://", "login_form_err_invalid_email": "Invalid Email", "login_form_err_invalid_url": "Invalid URL", "login_form_err_leading_whitespace": "Leading whitespace", @@ -1342,9 +1439,10 @@ "login_form_failed_login": "Error logging you in, check server URL, email and password", "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", "login_form_password_hint": "password", - "login_form_server_empty": "Enter a server URL", - "login_form_server_error": "Could not connect to server", - "login_has_been_disabled": "Login has been disabled", + "login_form_save_login": "Stay logged in", + "login_form_server_empty": "Enter a server URL.", + "login_form_server_error": "Could not connect to server.", + "login_has_been_disabled": "Login has been disabled.", "login_password_changed_error": "There was an error updating your password", "login_password_changed_success": "Password updated successfully", "logout_all_device_confirmation": "Are you sure you want to log out all devices?", @@ -1359,6 +1457,7 @@ "maintenance_action_restore": "Restoring Database", "maintenance_description": "Immich has been put into maintenance mode.", "maintenance_end": "End maintenance mode", + "maintenance_end_error": "Failed to end maintenance mode.", "maintenance_logged_in_as": "Currently logged in as {user}", "maintenance_restore_from_backup": "Restore From Backup", "maintenance_restore_library": "Restore Your Library", @@ -1384,8 +1483,7 @@ "manage_media_access_settings": "Open settings", "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.", "manage_media_access_title": "Media Management Access", - "manage_oauth_account": "Manage OAuth Account", - "manage_sharing_with_other_users": "Manage sharing with other users", + "manage_shared_links": "Manage shared links", "manage_sharing_with_partners": "Manage sharing with partners", "manage_the_app_settings": "Manage the app settings", "manage_your_account": "Manage your account", @@ -1393,11 +1491,13 @@ "manage_your_devices": "Manage your logged-in devices", "manage_your_oauth_connection": "Manage your OAuth connection", "map": "Map", + "map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Cannot get user's location", + "map_location_dialog_yes": "Yes", "map_location_picker_page_use_location": "Use this location", "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", "map_location_service_disabled_title": "Location Service disabled", - "map_marker_for_image": "Map marker for image taken in {city}, {country}", + "map_marker_for_images": "Map marker for images taken in {city}, {country}", "map_marker_with_image": "Map marker with image", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_title": "Location Permission denied", @@ -1407,49 +1507,18 @@ "map_settings_date_range_option_days": "Past {days} days", "map_settings_date_range_option_year": "Past year", "map_settings_date_range_option_years": "Past {years} years", + "map_settings_dialog_title": "Map Settings", "map_settings_include_show_archived": "Include Archived", "map_settings_include_show_partners": "Include Partners", "map_settings_only_show_favorites": "Show Favorite Only", "map_settings_theme_settings": "Map Theme", + "map_zoom_to_see_photos": "Zoom out to see photos", "mark_all_as_read": "Mark all as read", + "mark_as_read": "Mark as read", "marked_all_as_read": "Marked all as read", "matches": "Matches", "matching_assets": "Matching Assets", - "media": "Media", - "media_chrome": { - "auto": "Auto", - "captions": "Captions", - "captions_off": "Off", - "closed_captions": "closed captions", - "decode_error": "Decode error", - "disable_captions": "Disable captions", - "enable_captions": "Enable captions", - "enter_fullscreen_mode": "Enter fullscreen mode", - "exit_fullscreen_mode": "Exit fullscreen mode", - "loop": "Loop", - "media_error_description": "A media error caused playback to be aborted. The media could be corrupt or your browser does not support this format.", - "media_loading": "media loading", - "mute": "Mute", - "network_error": "Network error", - "network_error_description": "A network error caused the media download to fail.", - "not_supported_error": "Source Not Supported", - "playback_rate": "Playback rate", - "playback_rate_current": "current playback rate", - "playback_rate_value": "Playback rate {playbackRate}", - "playback_time": "playback time", - "quality": "Quality", - "second": "second", - "seconds": "seconds", - "time_value_of_total_time": "{currentTime} of {totalTime}", - "time_value_remaining": "{time} remaining", - "unmute": "Unmute", - "unsupported_error_description": "An unsupported error occurred. The server or network failed, or your browser does not support this format.", - "video_not_loaded_unknown_time": "video not loaded, unknown time.", - "video_player": "video player", - "volume": "volume" - }, "media_type": "Media type", - "media_type_description": "Show only photos or videos from your library.", "memories": "Memories", "memories_all_caught_up": "All caught up", "memories_check_back_tomorrow": "Check back tomorrow for more memories", @@ -1465,35 +1534,36 @@ "merge_people_prompt": "Do you want to merge these people? This action is irreversible.", "merge_people_successfully": "Merge people successfully", "merged_people_count": "Merged {count, plural, one {# person} other {# people}}", - "minFaces": "Minimum faces", - "minFaces_description": "The minimum number of recognized faces for a person to be displayed", "minimize": "Minimize", "minute": "Minute", "minutes": "Minutes", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Missing", "mobile_app": "Mobile App", "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", "model": "Model", - "modify_date": "Modify Date", "month": "Month", + "monthly_title_text_date_format": "MMMM y", "more": "More", - "motion": "Motion", "move": "Move", + "move_down": "Move down", "move_off_locked_folder": "Move out of locked folder", "move_to": "Move to", "move_to_device_trash": "Move to device trash", "move_to_lock_folder_action_prompt": "{count} added to the locked folder", - "move_to_lock_folder_partial_prompt": "{count} added to the locked folder, some local copies were kept", "move_to_locked_folder": "Move to locked folder", "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", + "move_up": "Move up", + "moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive", + "moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library", "moved_to_trash": "Moved to trash", + "multiselect_grid_edit_date_time_err_read_only": "Cannot edit date of read only asset(s), skipping", + "multiselect_grid_edit_gps_err_read_only": "Cannot edit location of read only asset(s), skipping", "mute_memories": "Mute Memories", "my_albums": "My albums", - "my_immich_description": "Copy current page as a My Immich link", - "my_immich_title": "My Immich link", "name": "Name", "name_or_nickname": "Name or nickname", - "name_plus_more_people": "{name} + {count, plural, one {# person} other {# people}}", "name_required": "Name is required", "navigate": "Navigate", "navigate_to_time": "Navigate to Time", @@ -1506,11 +1576,12 @@ "never": "Never", "new_album": "New Album", "new_api_key": "New API Key", - "new_feature": "New Feature", + "new_date_range": "New date range", "new_password": "New password", "new_person": "New person", "new_pin_code": "New PIN code", "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page", + "new_timeline": "New Timeline", "new_update": "New update", "new_user_created": "New user created", "new_version_available": "NEW VERSION AVAILABLE", @@ -1518,6 +1589,7 @@ "next": "Next", "next_memory": "Next memory", "no": "No", + "no_actions_added": "No actions added yet", "no_albums_found": "No albums found", "no_albums_message": "Create an album to organize your photos and videos", "no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.", @@ -1528,13 +1600,16 @@ "no_cast_devices_found": "No cast devices found", "no_checksum_local": "No checksum available - cannot fetch local assets", "no_checksum_remote": "No checksum available - cannot fetch remote asset", + "no_configuration_needed": "No configuration needed", "no_devices": "No authorized devices", "no_duplicates_found": "No duplicates were found.", "no_exif_info_available": "No exif info available", "no_explore_results_message": "Upload more photos to explore your collection.", "no_favorites_message": "Add favorites to quickly find your best pictures and videos", + "no_filters_added": "No filters added yet", "no_libraries_message": "Create an external library to view your photos and videos", "no_local_assets_found": "No local assets found with this checksum", + "no_location_set": "No location set", "no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.", "no_name": "No Name", "no_notifications": "No notifications", @@ -1544,19 +1619,14 @@ "no_results": "No results", "no_results_description": "Try a synonym or more general keyword", "no_shared_albums_message": "Create an album to share photos and videos with people in your network", - "no_steps": "No steps added yet", + "no_uploads_in_progress": "No uploads in progress", "none": "None", "not_allowed": "Not allowed", "not_available": "N/A", "not_in_any_album": "Not in any album", "not_selected": "Not selected", - "not_set": "Not set", "notes": "Notes", "nothing_here_yet": "Nothing here yet", - "notification_backup_reliability": "Enable notifications to improve background backup reliability", - "notification_enabled_list_tile_content": "Immich uses notifications for background backup. Manage them in your device settings.", - "notification_enabled_list_tile_open_button": "Open settings", - "notification_enabled_list_tile_title": "Notifications enabled", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", @@ -1568,8 +1638,6 @@ "obtainium_configurator": "Obtainium Configurator", "obtainium_configurator_instructions": "Use Obtainium to install and update the Android app directly from Immich GitHub's release. Create an API key and select a variant to create your Obtainium configuration link", "ocr": "OCR", - "ocr_body": "Immich now reads the text inside your photos, so you can search for them by what they say.", - "ocr_title": "Search text in your photos", "official_immich_resources": "Official Immich Resources", "offline": "Offline", "offset": "Offset", @@ -1588,8 +1656,6 @@ "open": "Open", "open_calendar": "Open calendar", "open_in_browser": "Open in browser", - "open_in_immich_body": "Set Immich as your gallery on Android to open photos straight from other apps.", - "open_in_immich_title": "Open photos in Immich", "open_in_map_view": "Open in map view", "open_in_openstreetmap": "Open in OpenStreetMap", "open_the_search_filters": "Open the search filters", @@ -1598,10 +1664,10 @@ "organize_into_albums": "Organize into albums", "organize_into_albums_description": "Put existing photos into albums using current sync settings", "organize_your_library": "Organize your library", - "orientation": "Orientation", "original": "original", "other": "Other", "other_devices": "Other devices", + "other_entities": "Other entities", "other_variables": "Other variables", "owned": "Owned", "owner": "Owner", @@ -1611,9 +1677,12 @@ "partner_can_access_assets": "All your photos and videos except those in Archived and Deleted", "partner_can_access_location": "The location where your photos were taken", "partner_list_user_photos": "{user}'s photos", + "partner_list_view_all": "View all", "partner_page_empty_message": "Your photos are not yet shared with any partner.", "partner_page_no_more_users": "No more users to add", + "partner_page_partner_add_failed": "Failed to add partner", "partner_page_select_partner": "Select partner", + "partner_page_shared_to_title": "Shared to", "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.", "partner_sharing": "Partner Sharing", "partners": "Partners", @@ -1633,10 +1702,10 @@ "paused": "Paused", "pending": "Pending", "people": "People", - "people_count": "{count, plural, one {# person} other {# people}}", "people_edits_count": "Edited {count, plural, one {# person} other {# people}}", "people_feature_description": "Browsing photos and videos grouped by people", - "people_search_description": "Select one or more people to find photos they appear in.", + "people_selected": "{count, plural, one {# person selected} other {# people selected}}", + "people_sidebar_description": "Display a link to People in the sidebar", "permanent_deletion_warning": "Permanent deletion warning", "permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets", "permanently_delete": "Permanently delete", @@ -1646,17 +1715,28 @@ "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", "permission": "Permission", "permission_empty": "Your permission shouldn't be empty", + "permission_onboarding_back": "Back", + "permission_onboarding_continue_anyway": "Continue anyway", + "permission_onboarding_get_started": "Get started", + "permission_onboarding_go_to_settings": "Go to settings", + "permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.", + "permission_onboarding_permission_granted": "Permission granted! You are all set.", + "permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.", + "permission_onboarding_request": "Immich requires permission to view your photos and videos.", "person": "Person", "person_age_months": "{months, plural, one {# month} other {# months}} old", "person_age_year_months": "1 year, {months, plural, one {# month} other {# months}} old", - "person_age_years": "{years, plural, one {# year} other {# years}} old", + "person_age_years": "{years, plural, other {# years}} old", "person_birthdate": "Born on {date}", "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", "person_recognized": "Person recognized", + "person_selected": "Person selected", "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any user to share with.", "photos": "Photos", "photos_and_videos": "Photos & Videos", + "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", "photos_from_previous_years": "Photos from previous years", + "photos_only": "Photos only", "pick_a_location": "Pick a location", "pick_custom_range": "Custom range", "pick_date_range": "Select a date range", @@ -1675,12 +1755,9 @@ "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", "play_transcoded_video": "Play transcoded video", "please_auth_to_access": "Please authenticate to access", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running", "port": "Port", "preferences_settings_subtitle": "Manage the app's preferences", "preferences_settings_title": "Preferences", - "prepared_archives": "Prepared Archives", "preparing": "Preparing", "preset": "Preset", "preview": "Preview", @@ -1694,11 +1771,11 @@ "privacy": "Privacy", "profile": "Profile", "profile_drawer_app_logs": "Logs", + "profile_drawer_client_server_up_to_date": "Client and Server are up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", "profile_image_of_user": "Profile image of {user}", "profile_picture_set": "Profile picture set.", - "projection_type": "Projection Type", "public_album": "Public album", "public_share": "Public Share", "purchase_account_info": "Supporter", @@ -1717,6 +1794,7 @@ "purchase_individual_description_2": "Supporter status", "purchase_individual_title": "Individual", "purchase_input_suggestion": "Have a product key? Enter the key below", + "purchase_license_subtitle": "Buy Immich to support the continued development of the service", "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.", @@ -1749,19 +1827,19 @@ "reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person", "reassing_hint": "Assign selected assets to an existing person", "recent": "Recent", + "recent_albums": "Recent albums", "recent_searches": "Recent searches", "recently_added": "Recently added", - "recently_added_body": "Jump straight to everything you've added lately on a dedicated page.", - "recently_added_description": "Browse your assets sorted by when they were uploaded to Immich", "recently_added_page_title": "Recently Added", - "recently_added_title": "Recently added", "recently_taken": "Recently taken", + "recently_taken_page_title": "Recently Taken", "refresh": "Refresh", "refresh_encoded_videos": "Refresh encoded videos", "refresh_faces": "Refresh faces", "refresh_metadata": "Refresh metadata", "refresh_thumbnails": "Refresh thumbnails", "refreshed": "Refreshed", + "refreshes_every_file": "Re-reads all existing and new files", "refreshing_encoded_video": "Refreshing encoded video", "refreshing_faces": "Refreshing faces", "refreshing_metadata": "Refreshing metadata", @@ -1774,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from this shared link?", "remove_assets_title": "Remove assets?", "remove_custom_date_range": "Remove custom date range", - "remove_filter": "Remove filter", + "remove_deleted_assets": "Remove Deleted Assets", "remove_from_album": "Remove from album", "remove_from_album_action_prompt": "{count} removed from the album", "remove_from_favorites": "Remove from favorites", @@ -1785,23 +1863,29 @@ "remove_memory": "Remove memory", "remove_photo_from_memory": "Remove photo from this memory", "remove_tag": "Remove tag", + "remove_url": "Remove URL", "remove_user": "Remove user", "removed_api_key": "Removed API Key: {name}", "removed_from_archive": "Removed from archive", "removed_from_favorites": "Removed from favorites", "removed_from_favorites_count": "{count, plural, other {Removed #}} from favorites", "removed_memory": "Removed memory", + "removed_photo_from_memory": "Removed photo from memory", "removed_tagged_assets": "Removed tag from {count, plural, one {# asset} other {# assets}}", "rename": "Rename", + "repair": "Repair", + "repair_no_results_message": "Untracked and missing files will show up here", + "replace_with_upload": "Replace with upload", "repository": "Repository", - "request_received_description": "You have been invited to another group", "require_password": "Require password", + "require_user_to_change_password_on_first_login": "Require user to change password on first login", "rescan": "Rescan", "reset": "Reset", "reset_password": "Reset password", "reset_people_visibility": "Reset people visibility", "reset_pin_code": "Reset PIN code", "reset_pin_code_description": "If you forgot your PIN code, you can contact the server administrator to reset it", + "reset_pin_code_success": "Successfully reset PIN code", "reset_pin_code_with_password": "You can always reset your PIN code with your password", "reset_sqlite": "Reset SQLite Database", "reset_sqlite_clear_app_data": "Clear Data", @@ -1815,21 +1899,20 @@ "resolved_all_duplicates": "Resolved all duplicates", "restore": "Restore", "restore_all": "Restore all", + "restore_trash_action_prompt": "{count} restored from trash", "restore_user": "Restore user", "restored_asset": "Restored asset", - "result": "Result", "resume": "Resume", "resume_paused_jobs": "Resume {count, plural, one {# paused job} other {# paused jobs}}", - "retry": "Retry", + "retry_upload": "Retry upload", "review_duplicates": "Review duplicates", "review_large_files": "Review large files", "role": "Role", "role_editor": "Editor", "role_viewer": "Viewer", - "rotate_api_key_prompt": "Are you sure you want to rotate this API key? The current key will stop working immediately.", - "rotate_key": "Rotate key", "running": "Running", "save": "Save", + "save_to_gallery": "Save to gallery", "saved": "Saved", "saved_api_key": "Saved API Key", "saved_profile": "Saved profile", @@ -1840,20 +1923,16 @@ "scan": "Scan", "scan_all_libraries": "Scan All Libraries", "scan_library": "Scan", + "scan_settings": "Scan Settings", "scanning": "Scanning", "scanning_for_album": "Scanning for album...", - "screencast_mode_description": "Show keyboard and mouse event indicators on the screen", - "screencast_mode_title": "Toggle screencast mode", "search": "Search", "search_albums": "Search albums", "search_by_context": "Search by context", - "search_by_context_example": "Describe the photo you want to find, e.g. \"Children jumping on a trampoline\"", "search_by_description": "Search by description", "search_by_description_example": "Hiking day in Sapa", "search_by_filename": "Search by file name or extension", "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", - "search_by_full_path": "Search by full path or folder", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.", "search_by_ocr": "Search by OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Search lens model...", @@ -1864,24 +1943,18 @@ "search_filter_apply": "Apply filter", "search_filter_camera_title": "Select camera type", "search_filter_date": "Date", - "search_filter_date_custom": "Custom", - "search_filter_date_description": "Filter by when your photos were taken using a preset range or custom date window.", "search_filter_date_interval": "{start} to {end}", - "search_filter_date_last_30_days": "Last 30 days", - "search_filter_date_last_year": "Last year", - "search_filter_date_this_year": "This year", "search_filter_date_title": "Select a date range", "search_filter_display_option_not_in_album": "Not in album", "search_filter_display_options": "Display Options", "search_filter_filename": "Search by file name", "search_filter_location": "Location", - "search_filter_location_description": "Search for photos taken in a specific city, country, or location.", "search_filter_location_title": "Select location", "search_filter_media_type": "Media Type", "search_filter_media_type_title": "Select media type", + "search_filter_ocr": "Search by OCR", "search_filter_people_title": "Select people", "search_filter_star_rating": "Star Rating", - "search_filter_tags_description": "Filter by tags you've added to organize your library.", "search_filter_tags_title": "Select tags", "search_for": "Search for", "search_for_existing_person": "Search for existing person", @@ -1890,17 +1963,28 @@ "search_no_people_named": "No people named \"{name}\"", "search_no_result": "No results found, try a different search term or combination", "search_options": "Search options", + "search_page_categories": "Categories", + "search_page_motion_photos": "Motion Photos", + "search_page_no_objects": "No Objects Info Available", + "search_page_no_places": "No Places Info Available", + "search_page_screenshots": "Screenshots", "search_page_search_photos_videos": "Search for your photos and videos", + "search_page_selfies": "Selfies", + "search_page_things": "Things", "search_page_view_all_button": "View all", + "search_page_your_activity": "Your activity", + "search_page_your_map": "Your Map", "search_people": "Search people", "search_places": "Search places", "search_rating": "Search by rating...", + "search_result_page_new_search_hint": "New Search", "search_settings": "Search settings", "search_state": "Search state...", + "search_suggestion_list_smart_search_hint_1": "Smart search is enabled by default, to search for metadata use the syntax ", + "search_suggestion_list_smart_search_hint_2": "m:your-search-term", "search_tags": "Search tags...", "search_timezone": "Search timezone...", "search_type": "Search type", - "search_type_description": "Choose how Immich interprets your search: by visual content, file details, or embedded text.", "search_your_photos": "Search your photos", "searching_locales": "Searching locales...", "second": "Second", @@ -1911,6 +1995,7 @@ "select_albums": "Select albums", "select_all": "Select all", "select_all_duplicates": "Select all duplicates", + "select_all_in": "Select all in {group}", "select_avatar_color": "Select avatar color", "select_count": "{count, plural, one {Select #} other {Select #}}", "select_cutoff_date": "Select cutoff date", @@ -1918,13 +2003,14 @@ "select_featured_photo": "Select featured photo", "select_from_computer": "Select from computer", "select_keep_all": "Select keep all", + "select_library_owner": "Select library owner", "select_new_face": "Select new face", "select_people": "Select people", "select_person": "Select person", "select_person_to_tag": "Select a person to tag", "select_photos": "Select photos", - "select_quality": "Select quality", "select_trash_all": "Select trash all", + "select_user_for_sharing_page_err_album": "Failed to create album", "selected": "Selected", "selected_count": "{count, plural, other {# selected}}", "selected_gps_coordinates": "Selected GPS Coordinates", @@ -1957,31 +2043,42 @@ "setting_image_viewer_original_title": "Load original image", "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", "setting_image_viewer_preview_title": "Load preview image", + "setting_image_viewer_title": "Images", "setting_languages_apply": "Apply", "setting_languages_subtitle": "Change the app's language", + "setting_notifications_notify_failures_grace_period": "Notify background backup failures: {duration}", + "setting_notifications_notify_hours": "{count} hours", + "setting_notifications_notify_immediately": "immediately", "setting_notifications_notify_minutes": "{count} minutes", + "setting_notifications_notify_never": "never", "setting_notifications_notify_seconds": "{count} seconds", + "setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset", + "setting_notifications_single_progress_title": "Show background backup detail progress", "setting_notifications_subtitle": "Adjust your notification preferences", + "setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)", + "setting_notifications_total_progress_title": "Show background backup total progress", "setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened", "setting_video_viewer_auto_play_title": "Auto play videos", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", "setting_video_viewer_original_video_title": "Force original video", "settings": "Settings", + "settings_require_restart": "Please restart Immich to apply this setting", "settings_saved": "Settings saved", "setup_pin_code": "Setup a PIN code", "share": "Share", + "share_action_prompt": "Shared {count} assets", + "share_add_photos": "Add photos", + "share_assets_selected": "{count} selected", "share_dialog_preparing": "Preparing...", "share_link": "Share Link", - "share_original": "Use original (large)", - "share_preview": "Use thumbnail (small)", - "share_quality_body": "Press and hold the share button to choose the image quality before you share.", - "share_quality_title": "Choose your share quality", "shared": "Shared", "shared_album_activities_input_disable": "Comment is disabled", "shared_album_activity_remove_content": "Do you want to delete this activity?", "shared_album_activity_remove_title": "Delete Activity", "shared_album_section_people_action_error": "Error leaving/removing from album", + "shared_album_section_people_action_leave": "Remove user from album", + "shared_album_section_people_action_remove_user": "Remove user from album", "shared_album_section_people_title": "PEOPLE", "shared_by": "Shared by", "shared_by_user": "Shared by {user}", @@ -1990,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Uploaded", "shared_link_app_bar_title": "Shared Links", "shared_link_clipboard_copied_massage": "Copied to clipboard", + "shared_link_clipboard_text": "Link: {link}\nPassword: {password}", "shared_link_create_error": "Error while creating shared link", - "shared_link_custom_url_description": "Access this shared link with a custom URL name", - "shared_link_custom_url_title": "Custom URL", - "shared_link_custom_url_warning": "Warning: a custom URL name with a forward slash may not behave as you expect.", + "shared_link_custom_url_description": "Access this shared link with a custom URL", "shared_link_edit_description_hint": "Enter the share description", "shared_link_edit_expire_after_option_day": "1 day", "shared_link_edit_expire_after_option_days": "{count} days", @@ -2027,6 +2123,12 @@ "shared_with_partner": "Shared with {partner}", "sharing": "Sharing", "sharing_enter_password": "Please enter the password to view this page.", + "sharing_page_album": "Shared albums", + "sharing_page_description": "Create shared albums to share photos and videos with people in your network.", + "sharing_page_empty_list": "EMPTY LIST", + "sharing_sidebar_description": "Display a link to Sharing in the sidebar", + "sharing_silver_appbar_create_shared_album": "New shared album", + "sharing_silver_appbar_share_partner": "Share with partner", "shift_to_permanent_delete": "press ⇧ to permanently delete asset", "show_album_options": "Show album options", "show_albums": "Show albums", @@ -2038,9 +2140,7 @@ "show_in_timeline": "Show in timeline", "show_in_timeline_setting_description": "Show photos and videos from this user in your timeline", "show_keyboard_shortcuts": "Show keyboard shortcuts", - "show_less": "Show less", "show_metadata": "Show metadata", - "show_more_fields": "{count, plural, one {Show # more field} other {Show # more fields}}", "show_or_hide_info": "Show or hide info", "show_password": "Show password", "show_person_options": "Show person options", @@ -2048,7 +2148,6 @@ "show_schema": "Show schema", "show_search_options": "Show search options", "show_shared_links": "Show shared links", - "show_slideshow_metadata_overlay": "Show image info overlay", "show_slideshow_transition": "Show slideshow transition", "show_supporter_badge": "Supporter badge", "show_supporter_badge_description": "Show a supporter badge", @@ -2060,25 +2159,18 @@ "sign_out": "Sign Out", "sign_up": "Sign up", "size": "Size", - "skip": "Skip", "skip_to_content": "Skip to content", "skip_to_folders": "Skip to folders", "skip_to_tags": "Skip to tags", "slideshow": "Slideshow", - "slideshow_body": "Sit back and watch your photos play in a full-screen slideshow.", - "slideshow_metadata_overlay_mode": "Overlay content", - "slideshow_metadata_overlay_mode_description_only": "Description only", - "slideshow_metadata_overlay_mode_full": "Full", "slideshow_repeat": "Repeat slideshow", "slideshow_repeat_description": "Loop back to beginning when slideshow ends", "slideshow_settings": "Slideshow settings", - "slideshow_title": "Slideshow", - "smart_album": "Smart album", - "some_assets_already_have_a_location_warning": "Some of the selected assets already have a location", "sort_albums_by": "Sort albums by...", "sort_created": "Date created", "sort_items": "Number of items", "sort_modified": "Date modified", + "sort_newest": "Newest photo", "sort_oldest": "Oldest photo", "sort_people_by_similarity": "Sort people by similarity", "sort_recent": "Most recent photo", @@ -2087,6 +2179,7 @@ "stack": "Stack", "stack_action_prompt": "{count} stacked", "stack_duplicates": "Stack duplicates", + "stack_select_one_photo": "Select one main photo for the stack", "stack_selected_photos": "Stack selected photos", "stacked_assets_count": "Stacked {count, plural, one {# asset} other {# assets}}", "stacktrace": "Stacktrace", @@ -2095,10 +2188,6 @@ "start_date_before_end_date": "Start date must be before end date", "state": "State", "status": "Status", - "step_delete": "Delete step", - "step_delete_confirm": "Are you sure you want to delete this step?", - "step_details": "Step details", - "steps": "Steps", "stop_casting": "Stop casting", "stop_motion_photo": "Stop Motion Photo", "stop_photo_sharing": "Stop sharing your photos?", @@ -2119,22 +2208,19 @@ "swap_merge_direction": "Swap merge direction", "sync": "Sync", "sync_albums": "Sync albums", + "sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums", "sync_local": "Sync Local", "sync_remote": "Sync Remote", "sync_status": "Sync Status", "sync_status_subtitle": "View and manage the sync system", "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", - "system_theme": "System theme", - "system_theme_command_description": "Use the system theme ({value})", "tag": "Tag", "tag_assets": "Tag assets", "tag_created": "Created tag: {tag}", "tag_face": "Tag face", "tag_feature_description": "Browsing photos and videos grouped by logical tag topics", - "tag_full_path": "Full path: {tag}", "tag_not_found_question": "Cannot find a tag? Create a new tag.", "tag_people": "Tag People", - "tag_plus_more_tags": "{tag} + {count, plural, one {# tag} other {# tags}}", "tag_updated": "Updated tag: {tag}", "tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}", "tags": "Tags", @@ -2148,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})", "theme_setting_colorful_interface_subtitle": "Apply primary color to background surfaces.", "theme_setting_colorful_interface_title": "Colorful interface", + "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer", + "theme_setting_image_viewer_quality_title": "Image viewer quality", "theme_setting_primary_color_subtitle": "Pick a color for primary actions and accents.", "theme_setting_primary_color_title": "Primary color", "theme_setting_system_primary_color_title": "Use system color", "theme_setting_system_theme_switch": "Automatic (Follow system setting)", + "theme_setting_theme_subtitle": "Choose the app's theme setting", + "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load", + "theme_setting_three_stage_loading_title": "Enable three-stage loading", "then": "Then", "they_will_be_merged_together": "They will be merged together", "third_party_resources": "Third-Party Resources", @@ -2177,19 +2268,23 @@ "trash_all": "Trash All", "trash_count": "Trash {count, number}", "trash_delete_asset": "Trash/Delete Asset", + "trash_emptied": "Emptied trash", "trash_no_results_message": "Trashed photos and videos will show up here.", "trash_page_delete_all": "Delete All", + "trash_page_empty_trash_dialog_content": "Do you want to empty your trashed assets? These items will be permanently removed from Immich", "trash_page_info": "Trashed items will be permanently deleted after {days} days", + "trash_page_no_assets": "No trashed assets", + "trash_page_restore_all": "Restore All", + "trash_page_select_assets_btn": "Select assets", + "trash_page_title": "Trash ({count})", "trashed_items_will_be_permanently_deleted_after": "Trashed items will be permanently deleted after {days, plural, one {# day} other {# days}}.", "trigger": "Trigger", - "trigger_asset_metadata_extraction": "Asset Metadata Extraction", - "trigger_asset_metadata_extraction_description": "Triggered when the EXIF metadata of an asset is extracted", - "trigger_asset_tagged": "Asset Tagged", - "trigger_asset_tagged_description": "Triggered when a tag is added to an asset", - "trigger_asset_uploaded": "Asset Upload", + "trigger_asset_uploaded": "Asset Uploaded", "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", + "trigger_description": "An event that kicks off the workflow", "trigger_person_recognized": "Person Recognized", - "trigger_person_recognized_description": "Triggered when a person is recognized", + "trigger_person_recognized_description": "Triggered when a person is detected", + "trigger_type": "Trigger type", "troubleshoot": "Troubleshoot", "type": "Type", "unable_to_change_pin_code": "Unable to change PIN code", @@ -2205,7 +2300,6 @@ "unknown": "Unknown", "unknown_country": "Unknown Country", "unknown_date": "Unknown date", - "unknown_schema": "Unknown schema", "unknown_year": "Unknown Year", "unlimited": "Unlimited", "unlink_motion_video": "Unlink motion video", @@ -2218,22 +2312,25 @@ "unsaved_change": "Unsaved change", "unselect_all": "Unselect all", "unselect_all_duplicates": "Unselect all duplicates", + "unselect_all_in": "Unselect all in {group}", "unstack": "Un-stack", "unstack_action_prompt": "{count} unstacked", "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", "unsupported_field_type": "Unsupported field type", "unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.", "untagged": "Untagged", + "untitled_workflow": "Untitled workflow", "up_next": "Up next", "update_location_action_prompt": "Update the location of {count} selected assets with:", "updated_at": "Updated", "updated_password": "Updated password", "upload": "Upload", "upload_concurrency": "Upload concurrency", - "upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}", "upload_details": "Upload Details", + "upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?", + "upload_dialog_title": "Upload Asset", "upload_error_with_count": "Upload error for {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}.", + "upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.", "upload_finished": "Upload finished", "upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}", "upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}", @@ -2241,13 +2338,9 @@ "upload_status_errors": "Errors", "upload_status_uploaded": "Uploaded", "upload_success": "Upload success, refresh the page to see new upload assets.", - "upload_to_album_body": "For users that don't utilize the manual upload feature, you can now choose to add local photos directly into an album as you upload them, no need to upload then add to an album later anymore.", - "upload_to_album_title": "Upload straight to an album", "upload_to_immich": "Upload to Immich ({count})", "uploading": "Uploading", "uploading_media": "Uploading media", - "uploads": "Uploads", - "uploads_count": "{count, plural, one {# upload} other {# uploads}}", "url": "URL", "usage": "Usage", "use_biometric": "Use biometric", @@ -2255,7 +2348,6 @@ "use_browser_locale_description": "Format dates, times, and numbers based on your browser locale", "use_current_connection": "Use current connection", "use_custom_date_range": "Use custom date range instead", - "use_template": "Use template", "user": "User", "user_has_been_deleted": "This user has been deleted.", "user_id": "User ID", @@ -2265,6 +2357,7 @@ "user_privacy": "User Privacy", "user_purchase_settings": "Purchase", "user_purchase_settings_description": "Manage your purchase", + "user_role_set": "Set {user} as {role}", "user_usage_detail": "User usage detail", "user_usage_stats": "Account usage statistics", "user_usage_stats_description": "View account usage statistics", @@ -2274,6 +2367,7 @@ "utilities": "Utilities", "validate": "Validate", "validate_endpoint_error": "Please enter a valid URL", + "validation_error": "Validation error", "variables": "Variables", "version": "Version", "version_announcement_closing": "Your friend, Alex", @@ -2283,69 +2377,60 @@ "video": "Video", "video_hover_setting": "Play video thumbnail on hover", "video_hover_setting_description": "Play video thumbnail when mouse is hovering over item. Even when disabled, playback can be started by hovering over the play icon.", - "video_quality": "Video quality", "videos": "Videos", "videos_count": "{count, plural, one {# Video} other {# Videos}}", + "videos_only": "Videos only", "view": "View", "view_album": "View Album", "view_all": "View All", "view_all_users": "View all users", "view_asset_owners": "View asset owners", "view_details": "View Details", - "view_group": "View group", "view_in_timeline": "View in timeline", "view_link": "View link", + "view_links": "View links", "view_name": "View", "view_next_asset": "View next asset", "view_previous_asset": "View previous asset", "view_qr_code": "View QR code", "view_similar_photos": "View similar photos", "view_stack": "View Stack", + "view_user": "View User", "viewer_remove_from_stack": "Remove from Stack", + "viewer_stack_use_as_main_asset": "Use as Main Asset", + "viewer_unstack": "Un-Stack", "visibility": "Visibility", "visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}", "visual": "Visual", + "visual_builder": "Visual builder", "waiting": "Waiting", "waiting_count": "Waiting: {count}", "warning": "Warning", "week": "Week", "welcome": "Welcome", "welcome_to_immich": "Welcome to Immich", - "whats_new": "What's new", - "whats_new_settings_subtitle": "See what's new in Immich", - "whats_new_version": "Version {version}", - "when": "When", "width": "Width", "wifi_name": "Wi-Fi Name", - "workflow": "Workflow", "workflow_delete_prompt": "Are you sure you want to delete this workflow?", "workflow_deleted": "Workflow deleted", "workflow_description": "Workflow description", "workflow_info": "Workflow info", "workflow_json": "Workflow JSON", "workflow_json_help": "Edit the workflow configuration in JSON format. Changes will sync to the visual builder.", - "workflow_logging_completed": "Completed", - "workflow_logging_disable": "Disable logging", - "workflow_logging_disabled_description": "Logging is currently disabled for this workflow.", - "workflow_logging_enable": "Enable logging", - "workflow_logging_error_step": "Error on step #{step}", - "workflow_logging_halted": "Halted", - "workflow_logging_halted_step": "Halted on step #{step}", "workflow_name": "Workflow name", "workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?", "workflow_summary": "Workflow summary", - "workflow_templates": "Workflow templates", "workflow_update_success": "Workflow updated successfully", + "workflow_updated": "Workflow updated", "workflows": "Workflows", "workflows_help_text": "Workflows automate actions on your assets based on triggers and filters", "wrong_pin_code": "Wrong PIN code", - "x_of_total": "{x}/{total}", "year": "Year", "years_ago": "{years, plural, one {# year} other {# years}} ago", "yes": "Yes", - "you": "You", "you_dont_have_any_shared_links": "You don't have any shared links", "your_wifi_name": "Your Wi-Fi name", "zero_to_clear_rating": "press 0 to clear asset rating", - "zoom_image": "Zoom Image" + "zoom_image": "Zoom Image", + "zoom_to_bounds": "Zoom to bounds" } diff --git a/i18n/en_GB.json b/i18n/en_GB.json deleted file mode 100644 index a077b444d3..0000000000 --- a/i18n/en_GB.json +++ /dev/null @@ -1,2294 +0,0 @@ -{ - "about": "About", - "account": "Account", - "account_settings": "Account Settings", - "acknowledge": "Acknowledge", - "action": "Action", - "action_common_update": "Update", - "action_description": "A set of action to perform on the filtered assets", - "actions": "Actions", - "active": "Active", - "active_count": "Active: {count}", - "activity": "Activity", - "add": "Add", - "add_a_description": "Add a description", - "add_a_location": "Add a location", - "add_a_name": "Add a name", - "add_a_title": "Add a title", - "add_action": "Add action", - "add_assets": "Add assets", - "add_birthday": "Add a birthday", - "add_endpoint": "Add endpoint", - "add_exclusion_pattern": "Add exclusion pattern", - "add_location": "Add location", - "add_partner": "Add partner", - "add_photos": "Add photos", - "add_step": "Add step", - "add_tag": "Add tag", - "add_to": "Add toâ€Ļ", - "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_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album", - "add_to_albums": "Add to albums", - "add_to_albums_count": "Add to albums ({count})", - "add_to_bottom_bar": "Add to", - "add_upload_to_stack": "Add upload to stack", - "add_url": "Add URL", - "added_to_archive": "Added to archive", - "added_to_favorites": "Added to favourites", - "added_to_favorites_count": "Added {count, number} to favourites", - "admin": { - "add_exclusion_pattern_description": "Add exclusion patterns. Globbing using *, **, and ? is supported. To ignore all files in any directory named \"Raw\", use \"**/Raw/**\". To ignore all files ending in \".tif\", use \"**/*.tif\". To ignore an absolute path, use \"/path/to/ignore/**\".", - "admin_user": "Admin User", - "asset_offline_description": "This external library asset is no longer found on disk and has been moved to the bin. If the file was moved within the library, check your timeline for the new corresponding asset. To restore this asset, please ensure that the file path below can be accessed by Immich and scan the library.", - "authentication_settings": "Authentication Settings", - "authentication_settings_description": "Manage password, OAuth, and other authentication settings", - "authentication_settings_disable_all": "Are you sure you want to disable all login methods? Login will be completely disabled.", - "authentication_settings_reenable": "To re-enable, use a Server Command.", - "background_task_job": "Background Tasks", - "backup_database": "Create Database Dump", - "backup_database_enable_description": "Enable database dumps", - "backup_keep_last_amount": "Amount of previous dumps to keep", - "backup_onboarding_1_description": "offsite copy in the cloud or at another physical location.", - "backup_onboarding_2_description": "local copies on different devices. This includes the main files and a backup of those files locally.", - "backup_onboarding_3_description": "total copies of your data, including the original files. This includes 1 offsite copy and 2 local copies.", - "backup_onboarding_description": "A 3-2-1 backup strategy is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution.", - "backup_onboarding_footer": "For more information about backing up Immich, please refer to the documentation.", - "backup_onboarding_parts_title": "A 3-2-1 backup includes:", - "backup_onboarding_title": "Backups", - "backup_settings": "Database Dump Settings", - "backup_settings_description": "Manage database dump settings.", - "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?", - "confirm_delete_library_assets": "Are you sure you want to delete this library? This will delete {count, plural, one {# contained asset} other {all # contained assets}} from Immich and cannot be undone. Files will remain on disk.", - "confirm_email_below": "To confirm, type \"{email}\" below", - "confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.", - "confirm_user_password_reset": "Are you sure you want to reset {user}'s password?", - "confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?", - "copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard", - "create_job": "Create job", - "cron_expression": "Cron expression", - "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", - "cron_expression_presets": "Cron expression presets", - "disable_login": "Disable login", - "download_csv": "Download CSV", - "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.", - "export_config_as_json_description": "Download the current system config as a JSON file", - "external_libraries_page_description": "Admin external library page", - "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.", - "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", - "image_fullsize_enabled": "Enable full-size image generation", - "image_fullsize_enabled_description": "Generate full-size image for non-web-friendly formats. When \"Prefer embedded preview\" is enabled, embedded previews are used directly without conversion. Does not affect web-friendly formats like JPEG.", - "image_fullsize_quality_description": "Full-size image quality from 1-100. Higher is better, but produces larger files.", - "image_fullsize_title": "Full-size Image Settings", - "image_prefer_embedded_preview": "Prefer embedded preview", - "image_prefer_embedded_preview_setting_description": "Use embedded previews in RAW photos as the input to image processing and when available. This can produce more accurate colours for some images, but the quality of the preview is camera-dependent and the image may have more compression artifacts.", - "image_prefer_wide_gamut": "Prefer wide gamut", - "image_prefer_wide_gamut_setting_description": "Use Display P3 for thumbnails. This better preserves the vibrance of images with wide colourspaces, but images may appear differently on old devices with an old browser version. sRGB images are kept as sRGB to avoid colour shifts.", - "image_preview_description": "Medium-size image with stripped metadata, used when viewing a single asset and for machine learning", - "image_preview_quality_description": "Preview quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness. Setting a low value may affect machine learning quality.", - "image_preview_title": "Preview Settings", - "image_progressive": "Progressive", - "image_progressive_description": "Encode JPEG images progressively for gradual loading display. This has no effect on WebP images.", - "image_quality": "Quality", - "image_resolution": "Resolution", - "image_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes and can reduce app responsiveness.", - "image_settings": "Image Settings", - "image_settings_description": "Manage the quality and resolution of generated images", - "image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline", - "image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.", - "image_thumbnail_title": "Thumbnail Settings", - "import_config_from_json_description": "Import system config by uploading a JSON config file", - "integrity_checks_checksum_files": "Checksum files", - "integrity_checks_checksum_files_description": "Configure the checksum check", - "integrity_checks_checksum_files_enable_description": "Enable the checksum check", - "integrity_checks_checksum_files_percentage_limit": "Percentage limit", - "integrity_checks_checksum_files_percentage_limit_description": "Configure the maximum percentage between 0.01 and 1 for how much the checksum check should run each interval.", - "integrity_checks_checksum_files_time_limit": "Time limit", - "integrity_checks_checksum_files_time_limit_description": "Configure the maximum duration for which the checksum check should run each interval. (ms)", - "integrity_checks_missing_files": "Missing files", - "integrity_checks_missing_files_description": "Configure the frequency and enable or disable the missing files check", - "integrity_checks_missing_files_enable_description": "Enable the missing files check", - "integrity_checks_settings": "Integrity checks", - "integrity_checks_settings_description": "Manage integrity checks intervals", - "integrity_checks_untracked_files": "Untracked files", - "integrity_checks_untracked_files_description": "Configure the frequency and enable or disable the untracked files check", - "integrity_checks_untracked_files_enable_description": "Enable the untracked files check", - "job_concurrency": "{job} concurrency", - "job_created": "Job created", - "job_not_concurrency_safe": "This job is not concurrency-safe.", - "job_settings": "Job Settings", - "job_settings_description": "Manage job concurrency", - "jobs_delayed": "{jobCount, plural, other {# delayed}}", - "jobs_failed": "{jobCount, plural, other {# failed}}", - "jobs_over_time": "Jobs over time", - "library_created": "Created library: {library}", - "library_deleted": "Library deleted", - "library_details": "Library details", - "library_folder_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.", - "library_remove_exclusion_pattern_prompt": "Are you sure you want to remove this exclusion pattern?", - "library_remove_folder_prompt": "Are you sure you want to remove this import folder?", - "library_scanning": "Periodic Scanning", - "library_scanning_description": "Configure periodic library scanning", - "library_scanning_enable_description": "Enable periodic library scanning", - "library_settings": "External Library", - "library_settings_description": "Manage external library settings", - "library_tasks_description": "Scan external libraries for new and/or changed assets", - "library_updated": "Updated library", - "library_watching_enable_description": "Watch external libraries for file changes", - "library_watching_settings": "Library watching [EXPERIMENTAL]", - "library_watching_settings_description": "Automatically watch for changed files", - "logging_enable_description": "Enable logging", - "logging_level_description": "When enabled, what log level to use.", - "logging_settings": "Logging", - "machine_learning_availability_checks": "Availability checks", - "machine_learning_availability_checks_description": "Automatically detect and prefer available machine learning servers", - "machine_learning_availability_checks_enabled": "Enable availability checks", - "machine_learning_availability_checks_interval": "Check interval", - "machine_learning_availability_checks_interval_description": "Interval in milliseconds between availability checks", - "machine_learning_availability_checks_timeout": "Request timeout", - "machine_learning_availability_checks_timeout_description": "Timeout in milliseconds for availability checks", - "machine_learning_clip_model": "CLIP model", - "machine_learning_clip_model_description": "The name of a CLIP model listed here. Note that you must re-run the 'Smart Search' job for all images upon changing a model.", - "machine_learning_duplicate_detection": "Duplicate Detection", - "machine_learning_duplicate_detection_enabled": "Enable duplicate detection", - "machine_learning_duplicate_detection_enabled_description": "If disabled, exactly identical assets will still be de-duplicated.", - "machine_learning_duplicate_detection_setting_description": "Use CLIP embeddings to find likely duplicates", - "machine_learning_enabled": "Enable machine learning", - "machine_learning_enabled_description": "If disabled, all ML features will be disabled regardless of the below settings.", - "machine_learning_facial_recognition": "Facial Recognition", - "machine_learning_facial_recognition_description": "Detect, recognise and group faces in images", - "machine_learning_facial_recognition_model": "Facial recognition model", - "machine_learning_facial_recognition_model_description": "Models are listed in descending order of size. Larger models are slower and use more memory, but produce better results. Note that you must re-run the Face Detection job for all images upon changing a model.", - "machine_learning_facial_recognition_setting": "Enable facial recognition", - "machine_learning_facial_recognition_setting_description": "If disabled, images will not be encoded for facial recognition and will not populate the People section in the Explore page.", - "machine_learning_max_detection_distance": "Maximum detection distance", - "machine_learning_max_detection_distance_description": "Maximum distance between two images to consider them duplicates, ranging from 0.001-0.1. Higher values will detect more duplicates, but may result in false positives.", - "machine_learning_max_recognition_distance": "Maximum recognition distance", - "machine_learning_max_recognition_distance_description": "Maximum distance between two faces to be considered the same person, ranging from 0-2. Lowering this can prevent labializing two people as the same person, while raising it can prevent labelling the same person as two different people. Note that it is easier to merge two people than to split one person in two, so err on the side of a lower threshold when possible.", - "machine_learning_min_detection_score": "Minimum detection score", - "machine_learning_min_detection_score_description": "Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives.", - "machine_learning_min_recognized_faces": "Minimum recognised faces", - "machine_learning_min_recognized_faces_description": "The minimum number of recognised faces for a person to be created. Increasing this makes Facial Recognition more precise at the cost of increasing the chance that a face is not assigned to a person.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Use machine learning to recognise text in images", - "machine_learning_ocr_enabled": "Enable OCR", - "machine_learning_ocr_enabled_description": "If disabled, images will not undergo text recognition.", - "machine_learning_ocr_max_resolution": "Maximum resolution", - "machine_learning_ocr_max_resolution_description": "Previews above this resolution will be resized while preserving aspect ratio. Higher values are more accurate, but take longer to process and use more memory.", - "machine_learning_ocr_min_detection_score": "Minimum detection score", - "machine_learning_ocr_min_detection_score_description": "Minimum confidence score for text to be detected from 0-1. Lower values will detect more text but may result in false positives.", - "machine_learning_ocr_min_recognition_score": "Minimum recognition score", - "machine_learning_ocr_min_score_recognition_description": "Minimum confidence score for detected text to be recognised from 0-1. Lower values will recognise more text but may result in false positives.", - "machine_learning_ocr_model": "OCR model", - "machine_learning_ocr_model_description": "Server models are more accurate than mobile models, but take longer to process and use more memory.", - "machine_learning_settings": "Machine Learning Settings", - "machine_learning_settings_description": "Manage machine learning features and settings", - "machine_learning_smart_search": "Smart Search", - "machine_learning_smart_search_description": "Search for images semantically using CLIP embeddings", - "machine_learning_smart_search_enabled": "Enable smart search", - "machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.", - "machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.", - "maintenance_backup_management": "Backup management", - "maintenance_delete_backup": "Delete Backup", - "maintenance_delete_backup_description": "This file will be irrevocably deleted.", - "maintenance_delete_error": "Failed to delete backup.", - "maintenance_integrity_check": "Check", - "maintenance_integrity_check_all": "Check All", - "maintenance_integrity_checksum_mismatch": "Checksum Mismatch", - "maintenance_integrity_checksum_mismatch_description": "Files whose on-disk checksum mismatches the checksum which Immich had stored in its database.", - "maintenance_integrity_checksum_mismatch_job": "Check for checksum mismatches", - "maintenance_integrity_checksum_mismatch_refresh_job": "Refresh checksum mismatch reports", - "maintenance_integrity_missing_file": "Missing Files", - "maintenance_integrity_missing_file_description": "Files that Immich has tracked in its database but do not exist on the file system.", - "maintenance_integrity_missing_file_job": "Check for missing files", - "maintenance_integrity_missing_file_refresh_job": "Refresh missing file reports", - "maintenance_integrity_report": "Integrity Report", - "maintenance_integrity_untracked_file": "Untracked Files", - "maintenance_integrity_untracked_file_description": "Files in Immich's directories that Immich does not have any record of.", - "maintenance_integrity_untracked_file_job": "Check for untracked files", - "maintenance_integrity_untracked_file_refresh_job": "Refresh untracked file reports", - "maintenance_restore_backup": "Restore Backup", - "maintenance_restore_backup_description": "Immich will be wiped and restored from the chosen backup. A backup will be created before continuing.", - "maintenance_restore_backup_different_version": "This backup was created with a different version of Immich!", - "maintenance_restore_backup_unknown_version": "Couldn't determine backup version.", - "maintenance_restore_database_backup": "Restore database backup", - "maintenance_restore_database_backup_description": "Rollback to an earlier database state using a backup file", - "maintenance_settings": "Maintenance", - "maintenance_settings_description": "Put Immich into maintenance mode.", - "maintenance_start": "Switch to maintenance mode", - "maintenance_start_error": "Failed to start maintenance mode.", - "maintenance_upload_backup": "Upload database backup file", - "maintenance_upload_backup_error": "Could not upload backup, is it an .sql/.sql.gz file?", - "manage_concurrency": "Manage Concurrency", - "manage_concurrency_description": "Navigate to the jobs page to manage job concurrency", - "manage_log_settings": "Manage log settings", - "map_dark_style": "Dark style", - "map_enable_description": "Enable map features", - "map_gps_settings": "Map & GPS Settings", - "map_gps_settings_description": "Manage Map & GPS (Reverse Geocoding) Settings", - "map_implications": "The map feature relies on an external tile service (tiles.immich.cloud)", - "map_light_style": "Light style", - "map_manage_reverse_geocoding_settings": "Manage Reverse Geocoding settings", - "map_reverse_geocoding": "Reverse Geocoding", - "map_reverse_geocoding_enable_description": "Enable reverse geocoding", - "map_reverse_geocoding_settings": "Reverse Geocoding Settings", - "map_settings": "Map", - "map_settings_description": "Manage map settings", - "map_style_description": "URL to a style.json map theme", - "memory_cleanup_job": "Memory cleanup", - "memory_generate_job": "Memory generation", - "metadata_extraction_job": "Extract metadata", - "metadata_extraction_job_description": "Extract metadata information from each asset, such as GPS, faces and resolution", - "metadata_faces_import_setting": "Enable face import", - "metadata_faces_import_setting_description": "Import faces from image EXIF data and sidecar files", - "metadata_settings": "Metadata Settings", - "metadata_settings_description": "Manage metadata settings", - "migration_job": "Migration", - "migration_job_description": "Migrate thumbnails for assets and faces to the latest folder structure", - "nightly_tasks_cluster_faces_setting_description": "Run facial recognition on newly detected faces", - "nightly_tasks_cluster_new_faces_setting": "Cluster new faces", - "nightly_tasks_database_cleanup_setting": "Database cleanup tasks", - "nightly_tasks_database_cleanup_setting_description": "Clean up old, expired data from the database", - "nightly_tasks_generate_memories_setting": "Generate memories", - "nightly_tasks_generate_memories_setting_description": "Create new memories from assets", - "nightly_tasks_missing_thumbnails_setting": "Generate missing thumbnails", - "nightly_tasks_missing_thumbnails_setting_description": "Queue assets without thumbnails for thumbnail generation", - "nightly_tasks_settings": "Nightly Tasks Settings", - "nightly_tasks_settings_description": "Manage nightly tasks", - "nightly_tasks_start_time_setting": "Start time", - "nightly_tasks_start_time_setting_description": "The time at which the server starts running the nightly tasks", - "nightly_tasks_sync_quota_usage_setting": "Sync quota usage", - "nightly_tasks_sync_quota_usage_setting_description": "Update user storage quota, based on current usage", - "no_paths_added": "No paths added", - "no_pattern_added": "No pattern added", - "note_apply_storage_label_previous_assets": "Note: To apply the Storage Label to previously uploaded assets, run the", - "note_cannot_be_changed_later": "NOTE: This cannot be changed later!", - "notification_email_from_address": "From address", - "notification_email_from_address_description": "Sender email address, for example: \"Immich Photo Server \". Make sure to use an address you're allowed to send emails from.", - "notification_email_host_description": "Address of the email server (e.g. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ignore certificate errors", - "notification_email_ignore_certificate_errors_description": "Ignore TLS certificate validation errors (not recommended)", - "notification_email_password_description": "Password to use when authenticating with the email server", - "notification_email_port_description": "Port of the email server (e.g 25, 465, or 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Use SMTPS (SMTP over TLS)", - "notification_email_sent_test_email_button": "Send test email and save", - "notification_email_setting_description": "Settings for sending email notifications", - "notification_email_test_email": "Send test email", - "notification_email_test_email_failed": "Failed to send test email, check your values", - "notification_email_test_email_sent": "A test email has been sent to {email}. Please check your inbox.", - "notification_email_username_description": "Username to use when authenticating with the email server", - "notification_enable_email_notifications": "Enable email notifications", - "notification_settings": "Notification Settings", - "notification_settings_description": "Manage notification settings, including email", - "oauth_allow_insecure_requests": "Allow insecure requests", - "oauth_allow_insecure_requests_description": "WARNING: This disables TLS certificate validation for OAuth requests and may expose you to MITM attacks.", - "oauth_auto_launch": "Auto launch", - "oauth_auto_launch_description": "Start the OAuth login flow automatically upon navigating to the login page", - "oauth_auto_register": "Auto register", - "oauth_auto_register_description": "Automatically register new users after signing in with OAuth", - "oauth_button_text": "Button text", - "oauth_client_secret_description": "Required for confidential client, or if PKCE (Proof Key for Code Exchange) is not supported for public client.", - "oauth_enable_description": "Login with OAuth", - "oauth_end_session_url_description": "Redirect the user to this URI when they log out.", - "oauth_mobile_redirect_uri": "Mobile redirect URI", - "oauth_mobile_redirect_uri_override": "Mobile redirect URI override", - "oauth_mobile_redirect_uri_override_description": "Enable when OAuth provider does not allow a mobile URI, like ''{callback}''", - "oauth_prompt_description": "Prompt parameter (e.g. select_account, login, consent)", - "oauth_role_claim": "Role Claim", - "oauth_role_claim_description": "Automatically grant admin access based on the presence of this claim. The claim may have either 'user' or 'admin'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Manage OAuth login settings", - "oauth_settings_more_details": "For more details about this feature, refer to the docs.", - "oauth_storage_label_claim": "Storage label claim", - "oauth_storage_label_claim_description": "Automatically set the user's storage label to the value of this claim.", - "oauth_storage_quota_claim": "Storage quota claim", - "oauth_storage_quota_claim_description": "Automatically set the user's storage quota to the value of this claim.", - "oauth_storage_quota_default": "Default storage quota (GiB)", - "oauth_storage_quota_default_description": "Quota in GiB to be used when no claim is provided.", - "oauth_timeout": "Request Timeout", - "oauth_timeout_description": "Timeout for requests in milliseconds", - "ocr_job_description": "Use machine learning to recognise text in images", - "password_enable_description": "Login with email and password", - "password_settings": "Password Login", - "password_settings_description": "Manage password login settings", - "paths_validated_successfully": "All paths validated successfully", - "person_cleanup_job": "Person cleanup", - "queue_details": "Queue Details", - "queues": "Job Queues", - "queues_page_description": "Admin job queues page", - "quota_size_gib": "Quota Size (GiB)", - "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.", - "release_channel_release_candidate": "Release candidate", - "release_channel_stable": "Stable", - "remove_failed_jobs": "Remove failed jobs", - "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", - "scanning_library": "Scanning library", - "search_jobs": "Search jobsâ€Ļ", - "send_welcome_email": "Send welcome email", - "server_external_domain_settings": "External domain", - "server_external_domain_settings_description": "Domain used for external links", - "server_public_users": "Public Users", - "server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.", - "server_settings": "Server Settings", - "server_settings_description": "Manage server settings", - "server_stats_page_description": "Admin server statistics page", - "server_welcome_message": "Welcome message", - "server_welcome_message_description": "A message that is displayed on the login page.", - "settings_page_description": "Admin settings page", - "sidecar_job": "Sidecar metadata", - "sidecar_job_description": "Discover or synchronise sidecar metadata from the filesystem", - "slideshow_duration_description": "Number of seconds to display each image", - "smart_search_job_description": "Run machine learning on assets to support smart search", - "storage_template_date_time_description": "Asset's creation timestamp is used for the datetime information", - "storage_template_date_time_sample": "Sample time {date}", - "storage_template_enable_description": "Enable storage template engine", - "storage_template_hash_verification_enabled": "Hash verification enabled", - "storage_template_hash_verification_enabled_description": "Enables hash verification, don't disable this unless you're certain of the implications", - "storage_template_migration": "Storage template migration", - "storage_template_migration_description": "Apply the current {template} to previously uploaded assets", - "storage_template_migration_info": "The storage template will convert all extensions to lowercase. Template changes will only apply to new assets. To retroactively apply the template to previously uploaded assets, run the {job}.", - "storage_template_migration_job": "Storage Template Migration Job", - "storage_template_more_details": "For more details about this feature, refer to the Storage Template and its implications", - "storage_template_onboarding_description_v2": "When enabled, this feature will auto-organise files based on a user-defined template. For more information, please see the documentation.", - "storage_template_path_length": "Approximate path length limit: {length, number}/{limit, number}", - "storage_template_settings": "Storage Template", - "storage_template_settings_description": "Manage the folder structure and file name of the upload asset", - "storage_template_user_label": "{label} is the user's Storage Label", - "system_settings": "System Settings", - "tag_cleanup_job": "Tag cleanup", - "template_email_available_tags": "You can use the following variables in your template: {tags}", - "template_email_if_empty": "If the template is empty, the default email will be used.", - "template_email_invite_album": "Invite Album Template", - "template_email_preview": "Preview", - "template_email_settings": "Email Templates", - "template_email_update_album": "Update Album Template", - "template_email_welcome": "Welcome email template", - "template_settings": "Notification Templates", - "template_settings_description": "Manage custom templates for notifications", - "theme_custom_css_settings": "Custom CSS", - "theme_custom_css_settings_description": "Cascading Style Sheets allow the design of Immich to be customised.", - "theme_settings": "Theme Settings", - "theme_settings_description": "Manage customisation of the Immich web interface", - "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", - "transcoding_acceleration_api_description": "The API that will interact with your device to accelerate transcoding. This setting is 'best effort': it will fallback to software transcoding on failure. VP9 may or may not work depending on your hardware.", - "transcoding_acceleration_nvenc": "NVENC (requires NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (requires 7th gen Intel CPU or later)", - "transcoding_acceleration_rkmpp": "RKMPP (only on Rockchip SOCs)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Accepted audio codecs", - "transcoding_accepted_audio_codecs_description": "Select which audio codecs do not need to be transcoded. Only used for certain transcode policies.", - "transcoding_accepted_containers": "Accepted containers", - "transcoding_accepted_containers_description": "Select which container formats do not need to be remuxed to MP4. Only used for certain transcode policies.", - "transcoding_accepted_video_codecs": "Accepted video codecs", - "transcoding_accepted_video_codecs_description": "Select which video codecs do not need to be transcoded. Only used for certain transcode policies.", - "transcoding_advanced_options_description": "Options most users should not need to change", - "transcoding_audio_codec": "Audio codec", - "transcoding_audio_codec_description": "Opus is the highest quality option, but has lower compatibility with old devices or software.", - "transcoding_bitrate_description": "Videos higher than max bitrate or not in an accepted format", - "transcoding_codecs_learn_more": "To learn more about the terminology used here, refer to FFmpeg documentation for H.264 codec, HEVC codec and VP9 codec.", - "transcoding_constant_quality_mode": "Constant quality mode", - "transcoding_constant_quality_mode_description": "ICQ is better than CQP, but some hardware acceleration devices do not support this mode. Setting this option will prefer the specified mode when using quality-based encoding. Ignored by NVENC as it does not support ICQ.", - "transcoding_constant_rate_factor": "Constant rate factor (-crf)", - "transcoding_constant_rate_factor_description": "Video quality level. Typical values are 23 for H.264, 28 for HEVC, 31 for VP9, and 35 for AV1. Lower is better, but produces larger files.", - "transcoding_disabled_description": "Don't transcode any videos, may break playback on some clients", - "transcoding_encoding_options": "Encoding Options", - "transcoding_encoding_options_description": "Set codecs, resolution, quality and other options for the encoded videos", - "transcoding_hardware_acceleration": "Hardware Acceleration", - "transcoding_hardware_acceleration_description": "Experimental: faster transcoding but may reduce quality at same bitrate", - "transcoding_hardware_decoding": "Hardware decoding", - "transcoding_hardware_decoding_setting_description": "Enables end-to-end acceleration instead of only accelerating encoding. May not work on all videos.", - "transcoding_max_b_frames": "Maximum B-frames", - "transcoding_max_b_frames_description": "Higher values improve compression efficiency, but slow down encoding. May not be compatible with hardware acceleration on older devices. 0 disables B-frames, while -1 sets this value automatically.", - "transcoding_max_bitrate": "Maximum bitrate", - "transcoding_max_bitrate_description": "Setting a max bitrate can make file sizes more predictable at a minor cost to quality. At 720p, typical values are 2600 kbit/s for VP9 or HEVC, or 4500 kbit/s for H.264. Disabled if set to 0. When no unit is specified, k (for kbit/s) is assumed; therefore 5000, 5000k, and 5M (for Mbit/s) are equivalent.", - "transcoding_max_keyframe_interval": "Maximum keyframe interval", - "transcoding_max_keyframe_interval_description": "Sets the maximum frame distance between keyframes. Lower values worsen compression efficiency, but improve seek times and may improve quality in scenes with fast movement. 0 sets this value automatically.", - "transcoding_optimal_description": "Videos higher than target resolution or not in an accepted format", - "transcoding_policy": "Transcode Policy", - "transcoding_policy_description": "Set when a video will be transcoded", - "transcoding_preferred_hardware_device": "Preferred hardware device", - "transcoding_preferred_hardware_device_description": "Applies only to VAAPI and QSV. Sets the dri node used for hardware transcoding.", - "transcoding_preset_preset": "Preset (-preset)", - "transcoding_preset_preset_description": "Compression speed. Slower presets produce smaller files, and increase quality when targeting a certain bitrate. VP9 ignores speeds above 'faster'.", - "transcoding_realtime": "Real-time Transcoding [EXPERIMENTAL]", - "transcoding_realtime_description": "Allows transcoding to be performed in real-time as the video is being streamed. Enables quality switching, but may cause higher playback latency and stuttering depending on server capabilities.", - "transcoding_realtime_enabled": "Enable real-time transcoding", - "transcoding_realtime_enabled_description": "If disabled, the server will refuse to start new real-time transcoding sessions.", - "transcoding_realtime_resolutions": "Resolutions", - "transcoding_realtime_resolutions_description": "The resolutions offered for real-time transcoding. Higher resolutions may cause playback issues if the server cannot transcode them quickly enough.", - "transcoding_realtime_video_codecs": "Video codecs", - "transcoding_realtime_video_codecs_description": "The video codecs offered for real-time transcoding. Clients will choose the best option they support during playback. AV1 is more efficient than HEVC, which is more efficient than H.264. When using hardware acceleration, only select the codecs the accelerator can encode. When using software transcoding, note that H.264 is faster than AV1, which is faster than HEVC.", - "transcoding_reference_frames": "Reference frames", - "transcoding_reference_frames_description": "The number of frames to reference when compressing a given frame. Higher values improve compression efficiency, but slow down encoding. 0 sets this value automatically.", - "transcoding_required_description": "Only videos not in an accepted format", - "transcoding_settings": "Video Transcoding Settings", - "transcoding_settings_description": "Manage which videos to transcode and how to process them", - "transcoding_target_resolution": "Target resolution", - "transcoding_target_resolution_description": "Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Applies only to NVENC. Temporal Adaptive Quantisation increases quality of high-detail, low-motion scenes. May not be compatible with older devices.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Higher values lead to faster encoding, but leave less room for the server to process other tasks while active. This value should not be more than the number of CPU cores. Maximises utilisation if set to 0.", - "transcoding_tone_mapping": "Tone-mapping", - "transcoding_tone_mapping_description": "Attempts to preserve the appearance of HDR videos when converted to SDR. Each algorithm makes different trade-offs for colour, detail and brightness. Hable preserves detail, Mobius preserves colour, and Reinhard preserves brightness.", - "transcoding_transcode_policy": "Transcode policy", - "transcoding_transcode_policy_description": "Policy for when a video should be transcoded. HDR videos and videos with a pixel format other than YUV 4:2:0 will always be transcoded (except if transcoding is disabled).", - "transcoding_two_pass_encoding": "Two-pass encodingy", - "transcoding_two_pass_encoding_setting_description": "Transcode in two passes to produce better encoded videos. When max bitrate is enabled (required for it to work with H.264 and HEVC), this mode uses a bitrate range based on the max bitrate and ignores CRF. For VP9, CRF can be used if max bitrate is disabled.", - "transcoding_video_codec": "Video codec", - "transcoding_video_codec_description": "VP9 has high efficiency and web compatibility, but takes longer to transcode. HEVC performs similarly, but has lower web compatibility. H.264 is widely compatible and quick to transcode, but produces much larger files. AV1 is the most efficient codec but lacks support on older devices.", - "trash_enabled_description": "Enable Trash features", - "trash_number_of_days": "Number of days", - "trash_number_of_days_description": "Number of days to keep the assets in the bin before permanently removing them", - "trash_settings": "Bin Settings", - "trash_settings_description": "Manage bin settings", - "unlink_all_oauth_accounts": "Unlink all OAuth accounts", - "unlink_all_oauth_accounts_description": "Remember to unlink all OAuth accounts before migrating to a new provider.", - "unlink_all_oauth_accounts_prompt": "Are you sure you want to unlink all OAuth accounts? This will reset the OAuth ID for each user and cannot be undone.", - "user_cleanup_job": "User cleanup", - "user_delete_delay": "{user}'s account and assets will be scheduled for permanent deletion in {delay, plural, one {# day} other {# days}}.", - "user_delete_delay_settings": "Delete delay", - "user_delete_delay_settings_description": "Number of days after removal to permanently delete a user's account and assets. The user deletion job runs at midnight to check for users that are ready for deletion. Changes to this setting will be evaluated at the next execution.", - "user_delete_immediately": "{user}'s account and assets will be queued for permanent deletion immediately.", - "user_delete_immediately_checkbox": "Queue user and assets for immediate deletion", - "user_details": "User Details", - "user_management": "User Management", - "user_password_has_been_reset": "The user's password has been reset:", - "user_password_reset_description": "Please provide the temporary password to the user and inform them they will need to change the password at their next login.", - "user_restore_description": "{user}'s account will be restored.", - "user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}", - "user_settings": "User Settings", - "user_settings_description": "Manage user settings", - "user_successfully_removed": "User {email} has been successfully removed.", - "users_page_description": "Admin users page", - "version_check_channel": "Release channel", - "version_check_channel_description": "Pick the release channel you want to get version announcements for", - "version_check_enabled_description": "Enable version check", - "version_check_implications": "The version check feature relies on periodic communication with {server}", - "version_check_settings": "Version Check", - "version_check_settings_description": "Enable/disable the new version notification", - "video_conversion_job": "Transcode videos", - "video_conversion_job_description": "Transcode videos for wider compatibility with browsers and devices" - }, - "admin_email": "Admin Email", - "admin_password": "Admin Password", - "administration": "Administration", - "advanced": "Advanced", - "advanced_settings_clear_image_cache": "Clear Image Cache", - "advanced_settings_clear_image_cache_error": "Failed to clear image cache", - "advanced_settings_clear_image_cache_success": "Successfully cleared {size}", - "advanced_settings_log_level_title": "Log level: {level}", - "advanced_settings_prefer_remote_subtitle": "Some devices are painfully slow to load thumbnails from local assets. Activate this setting to load remote images instead.", - "advanced_settings_prefer_remote_title": "Prefer remote images", - "advanced_settings_proxy_headers_subtitle": "Define proxy headers Immich should send with each network request", - "advanced_settings_proxy_headers_title": "Custom proxy headers [EXPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Enables the read-only mode where the photos can be only viewed, things like selecting multiple images, sharing, casting, delete are all disabled. Enable/Disable read-only via user avatar from the main screen", - "advanced_settings_readonly_mode_title": "Read-only mode", - "advanced_settings_sync_remote_deletions_subtitle": "Automatically delete or restore an asset on this device when that action is taken on the web", - "advanced_settings_sync_remote_deletions_title": "Sync remote deletions [EXPERIMENTAL]", - "advanced_settings_tile_subtitle": "Advanced user's settings", - "advanced_settings_troubleshooting_subtitle": "Enable additional features for troubleshooting", - "advanced_settings_troubleshooting_title": "Troubleshooting", - "age_months": "Age {months, plural, one {# month} other {# months}}", - "age_year_months": "Age 1 year, {months, plural, one {# month} other {# months}}", - "age_years": "{years, plural, other {Age #}}", - "album": "Album", - "album_added": "Album added", - "album_added_notification_setting_description": "Receive an email notification when you are added to a shared album", - "album_cover_updated": "Album cover updated", - "album_delete_confirmation": "Are you sure you want to delete the album {album}?", - "album_delete_confirmation_description": "If this album is shared, other users will not be able to access it any more.", - "album_deleted": "Album deleted", - "album_info_updated": "Album info updated", - "album_name": "Album Name", - "album_options": "Album options", - "album_remove_user": "Remove user?", - "album_remove_user_confirmation": "Are you sure you want to remove {user}?", - "album_search_not_found": "No albums found matching your search", - "album_share_no_users": "Looks like you have shared this album with all users or you don't have any users to share with.", - "album_summary": "Album summary", - "album_updated": "Album updated", - "album_updated_setting_description": "Receive an email notification when a shared album has new assets", - "album_upload_assets": "Upload assets from your computer and add to album", - "album_viewer_appbar_share_err_delete": "Failed to delete album", - "album_viewer_page_share_add_users": "Add users", - "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.", - "albums_on_device_count": "Albums on device ({count})", - "all": "All", - "all_albums": "All albums", - "all_people": "All people", - "all_photos": "All photos", - "all_videos": "All videos", - "allow_dark_mode": "Allow dark mode", - "allow_public_user_to_download": "Allow public user to download", - "allow_public_user_to_upload": "Allow public user to upload", - "allowed": "Allowed", - "alt_text_qr_code": "QR code image", - "always_keep": "Always keep", - "always_keep_photos_hint": "Free Up Space will keep all photos on this device.", - "always_keep_videos_hint": "Free Up Space will keep all videos on this device.", - "api_key": "API Key", - "api_key_description": "This value will only be shown once. Please be sure to copy it before closing the window.", - "api_key_empty": "Your API Key name shouldn't be empty", - "api_keys": "API Keys", - "app_architecture_variant": "Variant (Architecture)", - "app_bar_signout_dialog_content": "Are you sure you want to sign out?", - "app_bar_signout_dialog_title": "Sign out", - "app_download_links": "App Download Links", - "app_settings": "App Settings", - "app_stores": "App Stores", - "app_update_available": "App update is available", - "appears_in": "Appears in", - "apply_count": "Apply ({count, number})", - "archive": "Archive", - "archive_action_prompt": "{count} added to Archive", - "archive_or_unarchive_photo": "Archive or unarchive photo", - "archive_size": "Archive size", - "archive_size_description": "Configure the archive size for downloads (in GiB)", - "archived": "Archived", - "archived_count": "{count, plural, other {Archived #}}", - "are_these_the_same_person": "Are these the same person?", - "are_you_sure_to_do_this": "Are you sure you want to do this?", - "asset_added_to_album": "Added to album", - "asset_adding_to_album": "Adding to albumâ€Ļ", - "asset_created": "Asset created", - "asset_day_count": "{date}: {count, plural, one {# asset} other {# assets}}", - "asset_description_updated": "Asset description has been updated", - "asset_hashing": "Hashingâ€Ļ", - "asset_list_group_by_sub_title": "Group by", - "asset_list_layout_settings_group_by": "Group assets by", - "asset_list_layout_settings_group_by_month_day": "Month + day", - "asset_list_layout_sub_title": "Layout", - "asset_list_settings_subtitle": "Photo grid layout settings", - "asset_list_settings_title": "Photo Grid", - "asset_not_found_on_device_android": "Asset not found on device", - "asset_not_found_on_device_ios": "Asset not found on device. If you are using iCloud, the asset may be inaccessible due to bad file stored on iCloud", - "asset_not_found_on_icloud": "Asset not found on iCloud. the asset may be inaccessible due to bad file stored on iCloud", - "asset_offline": "Asset Offline", - "asset_offline_description": "This external asset is no longer found on disk. Please contact your Immich administrator for help.", - "asset_skipped": "Skipped", - "asset_skipped_in_trash": "In trash", - "asset_troubleshoot": "Asset Troubleshoot", - "asset_uploaded": "Uploaded", - "asset_uploading": "Uploadingâ€Ļ", - "asset_viewer_settings_subtitle": "Manage your gallery viewer settings", - "asset_viewer_settings_title": "Asset Viewer", - "assets": "Assets", - "assets_added_to_album_count": "Added {count, plural, one {# asset} other {# assets}} to the album", - "assets_added_to_album_partial_count": "Added {successCount} out of {totalCount} {totalCount, plural, one {asset} other {assets}} to the album", - "assets_added_to_albums_count": "Added {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} cannot be added to the album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} cannot be added to any of the albums", - "assets_count": "{count, plural, one {# asset} other {# assets}}", - "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}}", - "assets_restore_confirmation": "Are you sure you want to restore all your binned assets? You cannot undo this action! Note that any offline assets cannot be restored this way.", - "assets_restored_count": "Restored {count, plural, one {# asset} other {# assets}}", - "assets_trashed": "{count} asset(s) binned", - "assets_trashed_count": "Binned {count, plural, one {# asset} other {# assets}}", - "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} already part of the album", - "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} already part of the albums", - "authorized_devices": "Authorised 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", - "background_location_permission_content": "In order to switch networks when running in the background, Immich must *always* have precise location access so the app can read the Wi-Fi network's name", - "background_options": "Background Options", - "backup": "Backup", - "backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude", - "backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.", - "backup_album_selection_page_select_albums": "Select albums", - "backup_album_selection_page_selection_info": "Selection Info", - "backup_albums_sync": "Backup Albums Synchronisation", - "backup_background_service_complete_notification": "Asset backup complete", - "backup_background_service_default_notification": "Checking for new assetsâ€Ļ", - "backup_background_service_in_progress_notification": "Backing up your assetsâ€Ļ", - "backup_controller_page_albums": "Backup Albums", - "backup_controller_page_background_battery_info_link": "Show me how", - "backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimisations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "Battery optimisations", - "backup_controller_page_background_delay": "Delay new assets backup: {duration}", - "backup_controller_page_backup": "Backup", - "backup_controller_page_backup_selected": "Selected: ", - "backup_controller_page_backup_sub": "Backed up photos and videos", - "backup_controller_page_excluded": "Excluded: ", - "backup_controller_page_none_selected": "None selected", - "backup_controller_page_remainder": "Remainder", - "backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection", - "backup_controller_page_server_storage": "Server Storage", - "backup_controller_page_storage_format": "{used} of {total} used", - "backup_controller_page_to_backup": "Albums to be backed up", - "backup_controller_page_total_sub": "All unique photos and videos from selected albums", - "backup_error_sync_failed": "Sync failed. Cannot process backup.", - "backup_info_card_assets": "assets", - "backup_options": "Backup Options", - "backup_settings_subtitle": "Manage upload settings", - "backward": "Backward", - "battery_optimization_backup_reliability": "Disabling battery optimisations can improve the reliability of background backup", - "biometric_auth_enabled": "Biometric authentication enabled", - "biometric_locked_out": "You are locked out of biometric authentication", - "biometric_no_options": "No biometric options available", - "biometric_not_available": "Biometric authentication is not available on this device", - "birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.", - "blurred_background": "Blurred background", - "browse_templates": "Browse templates", - "bugs_and_feature_requests": "Bugs & Feature Requests", - "build": "Build", - "build_image": "Build Image", - "bulk_delete_duplicates_confirmation": "Are you sure you want to bulk delete {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and permanently delete all other duplicates. You cannot undo this action!", - "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", - "camera": "Camera", - "camera_brand": "Camera brand", - "camera_model": "Camera model", - "cancel": "Cancel", - "cancel_search": "Cancel search", - "canceled": "Cancelled", - "canceling": "Cancelling", - "cannot_merge_people": "Cannot merge people", - "cannot_undo_this_action": "You cannot undo this action!", - "cannot_update_the_description": "Cannot update the description", - "cast": "Cast", - "cast_description": "Configure available cast destinations", - "change": "Change", - "change_date": "Change date", - "change_description": "Change description", - "change_display_order": "Change display order", - "change_location": "Change location", - "change_name": "Change name", - "change_name_successfully": "Changed name successfully", - "change_password": "Change Password", - "change_password_description": "This is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", - "change_password_form_confirm_password": "Confirm Password", - "change_password_form_description": "Hi {name},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.", - "change_password_form_log_out": "Log out all other devices", - "change_password_form_log_out_description": "It is recommended to log out of all other devices", - "change_password_form_new_password": "New Password", - "change_password_form_password_mismatch": "Passwords do not match", - "change_password_form_reenter_new_password": "Re-enter New Password", - "change_pin_code": "Change PIN code", - "change_your_password": "Change your password", - "changed_visibility_successfully": "Changed visibility successfully", - "charging": "Charging", - "charging_requirement_mobile_backup": "Background backup requires the device to be charging", - "check_logs": "Check Logs", - "checksum": "Checksum", - "choose": "Choose", - "choose_matching_people_to_merge": "Choose matching people to merge", - "city": "City", - "cleanup_confirm_description": "Immich found {count} assets (created before {date}) safely backed up to the server. Remove the local copies from this device?", - "cleanup_confirm_prompt_title": "Remove from this device?", - "cleanup_deleted_assets": "Moved {count} assets to device trash", - "cleanup_deleting": "Moving to bin...", - "cleanup_found_assets": "Found {count} backed up assets", - "cleanup_found_assets_with_size": "Found {count} backed up assets ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud Shared Albums are excluded from the scan", - "cleanup_no_assets_found": "No assets found matching the criteria above. Free Up Space can only remove assets that have been backed up to the server", - "cleanup_preview_title": "Assets to remove ({count})", - "cleanup_step3_description": "Scan for backed up assets matching your date and keep settings.", - "cleanup_step4_summary": "{count} assets (created before {date}) to remove from your local device. Photos will remain accessible from the Immich app.", - "cleanup_trash_hint": "To fully reclaim storage space, open the system gallery app and empty the bin", - "clear": "Clear", - "clear_all": "Clear all", - "clear_all_recent_searches": "Clear all recent searches", - "clear_failed_count": "Clear failed ({count})", - "clear_file_cache": "Clear File Cache", - "clear_message": "Clear message", - "clear_value": "Clear value", - "client_cert_import": "Import", - "client_cert_import_success_msg": "Client certificate is imported", - "client_cert_invalid_msg": "Invalid certificate file or wrong password", - "client_cert_password_message": "Enter the password for this certificate", - "client_cert_password_title": "Certificate Password", - "client_cert_remove_msg": "Client certificate is removed", - "client_cert_subtitle": "Supports PKCS12 (.p12, .pfx) format only. Certificate import/removal is available only before login", - "client_cert_title": "SSL client certificate [EXPERIMENTAL]", - "close": "Close", - "collapse": "Collapse", - "collapse_all": "Collapse all", - "color": "Colour", - "command": "Command", - "command_palette_prompt": "Quickly find pages, actions, or commands", - "command_palette_to_close": "to close", - "command_palette_to_navigate": "to enter", - "command_palette_to_select": "to select", - "command_palette_to_show_all": "to show all", - "comment_deleted": "Comment deleted", - "comment_options": "Comment options", - "comments_and_likes": "Comments & likes", - "comments_are_disabled": "Comments are disabled", - "common_create_new_album": "Create new album", - "completed": "Completed", - "configuration": "Configuration", - "confirm": "Confirm", - "confirm_admin_password": "Confirm Admin Password", - "confirm_delete_face": "Are you sure you want to delete {name} face from the asset?", - "confirm_delete_shared_link": "Are you sure you want to delete this shared link?", - "confirm_keep_this_delete_others": "All other assets in the stack will be deleted except for this asset. Are you sure you want to continue?", - "confirm_new_pin_code": "Confirm new PIN code", - "confirm_password": "Confirm password", - "confirm_tag_face": "Do you want to tag this face as {name}?", - "confirm_tag_face_unnamed": "Do you want to tag this face?", - "connected_device": "Connected device", - "connected_to": "Connected to", - "contain": "Contain", - "context": "Context", - "continue": "Continue", - "control_bottom_app_bar_add_tags": "Add Tags", - "control_bottom_app_bar_delete_from_local": "Delete from device", - "control_bottom_app_bar_edit_location": "Edit Location", - "control_bottom_app_bar_edit_time": "Edit Date & Time", - "control_bottom_app_bar_trash_from_immich": "Move to Bin", - "copied_image_to_clipboard": "Copied image to clipboard.", - "copied_to_clipboard": "Copied to clipboard!", - "copy_error": "Copy error", - "copy_image": "Copy Image", - "copy_json": "Copy JSON", - "copy_link": "Copy link", - "copy_link_to_clipboard": "Copy link to clipboard", - "copy_password": "Copy password", - "copy_to_clipboard": "Copy to Clipboard", - "country": "Country", - "cover": "Cover", - "covers": "Covers", - "create": "Create", - "create_album": "Create album", - "create_album_page_untitled": "Untitled", - "create_api_key": "Create API key", - "create_first_workflow": "Create first workflow", - "create_library": "Create Library", - "create_link": "Create link", - "create_link_to_share": "Create link to share", - "create_link_to_share_description": "Let anyone with the link see the selected photo(s)", - "create_new": "CREATE NEW", - "create_new_person": "Create new person", - "create_new_person_hint": "Assign selected assets to a new person", - "create_new_user": "Create new user", - "create_person": "Create person", - "create_person_subtitle": "Add a name to the selected face to create and tag the new person", - "create_shared_album_page_share_add_assets": "ADD ASSETS", - "create_shared_album_page_share_select_photos": "Select Photos", - "create_shared_link": "Create shared link", - "create_tag": "Create tag", - "create_tag_description": "Create a new tag. For nested tags, please enter the full path of the tag including forward slashes.", - "create_user": "Create user", - "create_workflow": "Create workflow", - "created": "Created", - "created_at": "Created", - "creating_linked_albums": "Creating linked albums...", - "crop": "Crop", - "crop_aspect_ratio_free": "Free", - "crop_aspect_ratio_original": "Original", - "crop_aspect_ratio_square": "Square", - "current_device": "Current device", - "current_pin_code": "Current PIN code", - "current_server_address": "Current server address", - "custom_date": "Custom date", - "custom_locale": "Custom locale", - "custom_locale_description": "Format dates, times, and numbers based on the selected language and region", - "cutoff_date_description": "Keep photos from the lastâ€Ļ", - "cutoff_day": "{count, plural, one {day} other {days}}", - "cutoff_year": "{count, plural, one {year} other {years}}", - "dark": "Dark", - "dark_theme": "Switch to dark theme", - "date": "Date", - "date_after": "Date after", - "date_and_time": "Date and Time", - "date_before": "Date before", - "date_of_birth": "Date of birth", - "date_of_birth_saved": "Date of birth saved successfully", - "date_range": "Date range", - "date_time_original": "Date/Time Original", - "day": "Day", - "days": "Days", - "deduplicate_all": "Deduplicate All", - "default_quality_subtitle": "Quality used when tapping share. Long press the share button to choose each time.", - "default_share_quality": "Default share quality", - "delete": "Delete", - "delete_action_confirmation_message": "Are you sure you want to delete this asset? This action will move the asset to the server's bin and will prompt if you want to delete it locally", - "delete_action_prompt": "{count} deleted", - "delete_album": "Delete album", - "delete_api_key_prompt": "Are you sure you want to delete this API key?", - "delete_dialog_alert": "These items will be permanently deleted from Immich and from your device", - "delete_dialog_alert_local": "These items will be permanently removed from your device but still be available on the Immich server", - "delete_dialog_alert_local_non_backed_up": "Some of the items aren't backed up to Immich and will be permanently removed from your device", - "delete_dialog_title": "Delete Permanently", - "delete_duplicates_confirmation": "Are you sure you want to permanently delete these duplicates?", - "delete_face": "Delete face", - "delete_key": "Delete key", - "delete_library": "Delete Library", - "delete_link": "Delete link", - "delete_local_action_prompt": "{count} deleted locally", - "delete_local_dialog_ok_backed_up_only": "Delete Backed Up Only", - "delete_local_dialog_ok_force": "Delete Anyway", - "delete_others": "Delete others", - "delete_permanently": "Delete permanently", - "delete_permanently_action_prompt": "{count} deleted permanently", - "delete_shared_link": "Delete shared link", - "delete_shared_link_dialog_title": "Delete Shared Link", - "delete_tag": "Delete tag", - "delete_tag_confirmation_prompt": "Are you sure you want to delete {tagName} tag?", - "delete_user": "Delete user", - "deleted_shared_link": "Deleted shared link", - "description": "Description", - "deselect_all": "Deselect All", - "details": "Details", - "direction": "Direction", - "disable": "Disable", - "disabled": "Disabled", - "discord": "Discord", - "discover": "Discover", - "discovered_devices": "Discovered devices", - "dismiss_all_errors": "Dismiss all errors", - "display_options": "Display options", - "display_order": "Display order", - "display_original_photos": "Display original photos", - "display_original_photos_setting_description": "Prefer to display the original photo when viewing an asset rather than thumbnails when the original asset is web-compatible. This may result in slower photo display speeds.", - "do_not_show_again": "Do not show this message again", - "documentation": "Documentation", - "done": "Done", - "download": "Download", - "download_canceled": "Download cancelled", - "download_complete": "Download complete", - "download_enqueue": "Download enqueued", - "download_failed": "Download failed", - "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", - "download_notfound": "Download not found", - "download_original": "Download original", - "download_paused": "Download paused", - "download_settings": "Download", - "download_settings_description": "Manage settings related to asset download", - "download_waiting_to_retry": "Waiting to retry", - "downloading": "Downloading", - "downloading_asset_filename": "Downloading asset {filename}", - "downloading_from_icloud": "Downloading from iCloud", - "downloading_media": "Downloading media", - "drag_to_reorder": "Drag to reorder", - "drop_files_to_upload": "Drop files anywhere to upload", - "duplicate": "Duplicate", - "duplicate_workflow": "Duplicate workflow", - "duplicates": "Duplicates", - "duplicates_description": "Resolve each group by indicating which, if any, are duplicates.", - "duration": "Duration", - "edit": "Edit", - "edit_album": "Edit album", - "edit_avatar": "Edit avatar", - "edit_birthday": "Edit birthday", - "edit_date": "Edit date", - "edit_date_and_time": "Edit date and time", - "edit_date_and_time_action_prompt": "{count} date and time edited", - "edit_date_and_time_by_offset": "Change date by offset", - "edit_description": "Edit description", - "edit_exclusion_pattern": "Edit exclusion pattern", - "edit_faces": "Edit faces", - "edit_key": "Edit key", - "edit_link": "Edit link", - "edit_location": "Edit location", - "edit_location_action_prompt": "{count} location edited", - "edit_location_dialog_title": "Location", - "edit_name": "Edit name", - "edit_people": "Edit people", - "edit_tag": "Edit tag", - "edit_title": "Edit Title", - "edit_user": "Edit user", - "edit_workflow": "Edit workflow", - "editor": "Editor", - "editor_discard_edits_confirm": "Discard edits", - "editor_discard_edits_prompt": "You have unsaved edits. Are you sure you want to discard them?", - "editor_discard_edits_title": "Discard edits?", - "editor_edits_applied_error": "Failed to apply edits", - "editor_edits_applied_success": "Edits applied successfully", - "editor_flip_horizontal": "Flip horizontally", - "editor_flip_vertical": "Flip vertically", - "editor_handle_corner": "{corner, select, top_left {Top-left} top_right {Top-right} bottom_left {Bottom-left} bottom_right {Bottom-right} other {A}} corner handle", - "editor_handle_edge": "{edge, select, top {Top} bottom {Bottom} left {Left} right {Right} other {An}} edge handle", - "editor_orientation": "Orientation", - "editor_reset_all_changes": "Reset changes", - "editor_rotate_left": "Rotate 90° anticlockwise", - "editor_rotate_right": "Rotate 90° clockwise", - "email": "Email", - "email_notifications": "Email notifications", - "empty_folder": "This folder is empty", - "empty_trash": "Empty bin", - "empty_trash_confirmation": "Are you sure you want to empty the bin? This will remove all the assets in the bin permanently from Immich.\nYou cannot undo this action!", - "enable": "Enable", - "enable_backup": "Enable Backup", - "enable_biometric_auth_description": "Enter your PIN code to enable biometric authentication", - "enabled": "Enabled", - "end_date": "End date", - "enqueued": "Enqueued", - "enter_wifi_name": "Enter Wi-Fi name", - "enter_your_pin_code": "Enter your PIN code", - "enter_your_pin_code_subtitle": "Enter your PIN code to access the locked folder", - "error": "Error", - "error_delete_face": "Error deleting face from asset", - "error_loading_albums": "Error loading albums", - "error_loading_image": "Error loading image", - "error_loading_partners": "Error loading partners: {error}", - "error_retrieving_asset_information": "Error retrieving asset information", - "error_saving_image": "Error: {error}", - "error_tag_face_bounding_box": "Error tagging face - cannot get bounding box coordinates", - "error_title": "Error - Something went wrong", - "error_while_navigating": "Error while navigating to asset", - "errors": { - "cannot_navigate_next_asset": "Cannot navigate to the next asset", - "cannot_navigate_previous_asset": "Cannot navigate to previous asset", - "cant_apply_changes": "Can't apply changes", - "cant_change_activity": "Can't {enabled, select, true {disable} other {enable}} activity", - "cant_change_asset_favorite": "Can't change favourite for asset", - "cant_change_metadata_assets_count": "Can't change metadata of {count, plural, one {# asset} other {# assets}}", - "cant_get_faces": "Can't get faces", - "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", - "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", - "error_downloading": "Error downloading {filename}", - "error_hiding_buy_button": "Error hiding buy button", - "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_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", - "failed_to_get_people": "Failed to get people", - "failed_to_keep_this_delete_others": "Failed to keep this asset and delete the other assets", - "failed_to_load_asset": "Failed to load asset", - "failed_to_load_assets": "Failed to load assets", - "failed_to_load_notifications": "Failed to load notifications", - "failed_to_load_people": "Failed to load people", - "failed_to_remove_product_key": "Failed to remove product key", - "failed_to_reset_pin_code": "Failed to reset PIN code", - "failed_to_stack_assets": "Failed to stack assets", - "failed_to_tag_assets": "Failed to tag assets", - "failed_to_unstack_assets": "Failed to un-stack assets", - "failed_to_update_notification_status": "Failed to update notification status", - "incorrect_email_or_password": "Incorrect email or password", - "library_folder_already_exists": "This import path already exists.", - "page_not_found": "Page not found", - "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", - "something_went_wrong": "Something went wrong", - "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", - "unable_to_add_exclusion_pattern": "Unable to add exclusion pattern", - "unable_to_add_partners": "Unable to add partners", - "unable_to_add_remove_archive": "Unable to {archived, select, true {remove asset from} other {add asset to}} archive", - "unable_to_add_remove_favorites": "Unable to {favorite, select, true {add asset to} other {remove asset from}} favourites", - "unable_to_archive_unarchive": "Unable to {archived, select, true {archive} other {unarchive}}", - "unable_to_change_album_user_role": "Unable to change the album user's role", - "unable_to_change_date": "Unable to change date", - "unable_to_change_description": "Unable to change description", - "unable_to_change_favorite": "Unable to change favourite for asset", - "unable_to_change_location": "Unable to change location", - "unable_to_change_password": "Unable to change password", - "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_copy_to_clipboard": "Cannot copy to clipboard, make sure you are accessing the page through https", - "unable_to_create": "Unable to create workflow", - "unable_to_create_admin_account": "Unable to create admin account", - "unable_to_create_api_key": "Unable to create a new API Key", - "unable_to_create_library": "Unable to create library", - "unable_to_create_user": "Unable to create user", - "unable_to_delete_album": "Unable to delete album", - "unable_to_delete_asset": "Unable to delete asset", - "unable_to_delete_assets": "Error deleting assets", - "unable_to_delete_exclusion_pattern": "Unable to delete exclusion pattern", - "unable_to_delete_shared_link": "Unable to delete shared link", - "unable_to_delete_user": "Unable to delete user", - "unable_to_delete_workflow": "Unable to delete workflow", - "unable_to_download_files": "Unable to download files", - "unable_to_edit_exclusion_pattern": "Unable to edit exclusion pattern", - "unable_to_empty_trash": "Unable to empty bin", - "unable_to_enter_fullscreen": "Unable to enter fullscreen", - "unable_to_exit_fullscreen": "Unable to exit fullscreen", - "unable_to_get_comments_number": "Unable to get number of comments", - "unable_to_get_shared_link": "Failed to get shared link", - "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_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_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", - "unable_to_refresh_user": "Unable to refresh user", - "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_library": "Unable to remove library", - "unable_to_remove_partner": "Unable to remove partner", - "unable_to_remove_reaction": "Unable to remove reaction", - "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", - "unable_to_restore_assets": "Unable to restore assets", - "unable_to_restore_trash": "Unable to restore bin", - "unable_to_restore_user": "Unable to restore user", - "unable_to_save_album": "Unable to save album", - "unable_to_save_api_key": "Unable to save API Key", - "unable_to_save_date_of_birth": "Unable to save date of birth", - "unable_to_save_name": "Unable to save name", - "unable_to_save_profile": "Unable to save profile", - "unable_to_save_settings": "Unable to save settings", - "unable_to_scan_libraries": "Unable to scan libraries", - "unable_to_scan_library": "Unable to scan library", - "unable_to_set_feature_photo": "Unable to set feature photo", - "unable_to_set_profile_picture": "Unable to set profile picture", - "unable_to_set_rating": "Unable to set rating", - "unable_to_submit_job": "Unable to submit job", - "unable_to_trash_asset": "Unable to bin asset", - "unable_to_unlink_account": "Unable to unlink account", - "unable_to_unlink_motion_video": "Unable to unlink motion video", - "unable_to_update_album_cover": "Unable to update album cover", - "unable_to_update_album_info": "Unable to update album info", - "unable_to_update_library": "Unable to update library", - "unable_to_update_location": "Unable to update location", - "unable_to_update_settings": "Unable to update settings", - "unable_to_update_timeline_display_status": "Unable to update timeline display status", - "unable_to_update_user": "Unable to update user", - "unable_to_update_workflow": "Unable to update workflow", - "unable_to_upload_file": "Unable to upload file" - }, - "errors_text": "Errors", - "exclusion_pattern": "Exclusion pattern", - "exif": "Exif", - "exif_bottom_sheet_description": "Add Description...", - "exif_bottom_sheet_description_error": "Error updating description", - "exif_bottom_sheet_no_description": "No description", - "exit_slideshow": "Exit Slideshow", - "expand": "Expand", - "expand_all": "Expand all", - "expire_after": "Expire after", - "expired": "Expired", - "expires_date": "Expires {date}", - "explore": "Explore", - "explorer": "Explorer", - "export": "Export", - "export_as_json": "Export as JSON", - "export_database": "Export Database", - "export_database_description": "Export the SQLite database", - "exposure_time": "Exposure Time", - "extension": "Extension", - "external": "External", - "external_libraries": "External Libraries", - "external_network": "External network", - "external_network_sheet_info": "When not on the preferred Wi-Fi network, the app will connect to the server through the first of the below URLs it can reach, starting from top to bottom", - "f_number": "F-Number", - "face_unassigned": "Unassigned", - "failed": "Failed", - "failed_count": "Failed: {count}", - "failed_to_authenticate": "Failed to authenticate", - "failed_to_delete_file": "Failed to delete file", - "failed_to_load_assets": "Failed to load assets", - "failed_to_load_folder": "Failed to load folder", - "favorite": "Favourite", - "favorite_action_prompt": "{count} added to Favourites", - "favorite_or_unfavorite_photo": "Favourite or unfavourite photo", - "favorites": "Favourites", - "feature_photo_updated": "Feature photo updated", - "features": "Features", - "features_setting_description": "Manage the app features", - "file_name_or_extension": "File name or extension", - "file_name_text": "File name", - "file_size": "File size", - "filename": "Filename", - "filetype": "Filetype", - "filter": "Filter", - "filter_people": "Filter people", - "filter_places": "Filter places", - "filter_tags": "Filter tags", - "filters": "Filters", - "first": "First", - "fix_incorrect_match": "Fix incorrect match", - "focal_length": "Focal Length", - "folder": "Folder", - "folder_not_found": "Folder not found", - "folders": "Folders", - "folders_feature_description": "Browsing the folder view for the photos and videos on the file system", - "forgot_pin_code_question": "Forgotten your PIN?", - "forward": "Forward", - "free_up_space": "Free Up Space", - "free_up_space_description": "Move backed-up photos and videos to your device's bin to free up space. Your copies on the server remain safe.", - "free_up_space_settings_subtitle": "Free up device storage", - "full_path": "Full path: {path}", - "full_path_or_folder": "Full path or folder", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "This feature loads external resources from Google in order to work.", - "general": "General", - "geolocation_instruction_location": "Click on an asset with GPS coordinates to use its location, or select a location directly from the map", - "get_help": "Get Help", - "get_people_error": "Error getting people", - "get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network", - "getting_started": "Getting Started", - "go_back": "Go back", - "go_to_folder": "Go to folder", - "go_to_search": "Go to search", - "gps": "GPS", - "gps_missing": "No GPS", - "grant_permission": "Grant permission", - "group_albums_by": "Group albums by...", - "group_country": "Group by country", - "group_no": "No grouping", - "group_owner": "Group by owner", - "group_places_by": "Group places by...", - "group_year": "Group by year", - "haptic_feedback_title": "Haptic Feedback", - "has_quota": "Has quota", - "hash_asset": "Hash asset", - "hashed_assets": "Hashed assets", - "hashing": "Hashing", - "header_settings_add_header_tip": "Add header", - "header_settings_field_validator_msg": "Value cannot be empty", - "header_settings_header_name_input": "Header name", - "header_settings_header_value_input": "Header value", - "headers_settings_tile_title": "Custom proxy headers", - "height": "Height", - "hi_user": "Hi {name} ({email})", - "hide_all_people": "Hide all people", - "hide_gallery": "Hide gallery", - "hide_named_person": "Hide person {name}", - "hide_password": "Hide password", - "hide_person": "Hide person", - "hide_schema": "Hide schema", - "hide_text_recognition": "Hide text recognition", - "hide_unnamed_people": "Hide unnamed people", - "home_page_building_timeline": "Building the timeline", - "host": "Host", - "hour": "Hour", - "hours": "Hours", - "id": "ID", - "idle": "Idle", - "image": "Image", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} taken on {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} taken with {person1} on {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} taken with {person1} and {person2} on {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {person3} on {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken with {person1}, {person2}, and {additionalCount, number} others on {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} on {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} on {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1} and {person2} on {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {person3} on {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} taken in {city}, {country} with {person1}, {person2}, and {additionalCount, number} others on {date}", - "immich_logo": "Immich Logo", - "immich_web_interface": "Immich Web Interface", - "import_from_json": "Import from JSON", - "import_path": "Import path", - "in_albums": "In {count, plural, one {# album} other {# albums}}", - "in_archive": "In archive", - "in_year": "In {year}", - "in_year_selector": "In", - "include_archived": "Include archived", - "include_shared_albums": "Include shared albums", - "include_shared_partner_assets": "Include shared partner assets", - "individual_share": "Individual share", - "individual_shares": "Individual shares", - "info": "Info", - "integrity_checks": "Integrity Checks", - "interval": { - "day_at_onepm": "Every day at 1pm", - "hours": "Every {hours, plural, one {hour} other {{hours, number} hours}}", - "night_at_midnight": "Every night at midnight", - "night_at_twoam": "Every night at 2am" - }, - "invalid_date": "Invalid date", - "invalid_date_format": "Invalid date format", - "invite_people": "Invite People", - "invite_to_album": "Invite to album", - "iso": "ISO", - "items_count": "{count, plural, one {# item} other {# items}}", - "jobs": "Jobs", - "keep": "Keep", - "keep_albums": "Keep albums", - "keep_albums_count": "Keeping {count} {count, plural, one {album} other {albums}}", - "keep_all": "Keep All", - "keep_description": "Choose what stays on your device when freeing up space.", - "keep_favorites": "Keep favourites", - "keep_on_device": "Keep on device", - "keep_on_device_hint": "Select items to keep on this device", - "keep_this_delete_others": "Keep this, delete others", - "keeping": "Keeping: {items}", - "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", - "large_files": "Large Files", - "last": "Last", - "last_months": "{count, plural, one {Last month} other {Last # months}}", - "last_seen": "Last seen", - "latest_version": "Latest Version", - "latitude": "Latitude", - "leave": "Leave", - "leave_album": "Leave album", - "lens_model": "Lens model", - "less": "Less", - "let_others_respond": "Let others respond", - "level": "Level", - "library": "Library", - "library_add_folder": "Add folder", - "library_edit_folder": "Edit folder", - "library_page_sort_asset_count": "Number of assets", - "library_page_sort_created": "Created date", - "library_page_sort_last_modified": "Last modified", - "library_page_sort_title": "Album title", - "licenses": "Licences", - "light": "Light", - "light_theme": "Switch to light theme", - "like": "Like", - "like_deleted": "Like deleted", - "link": "Link", - "link_motion_video": "Link motion video", - "link_to_docs": "For more information, refer to the documentation.", - "link_to_oauth": "Link to OAuth", - "linked_oauth_account": "Linked OAuth account", - "list": "List", - "live": "Live", - "load_more": "Load More", - "loading": "Loading", - "loading_search_results_failed": "Loading search results failed", - "local": "Local", - "local_asset_cast_failed": "Unable to cast an asset that is not uploaded to the server", - "local_assets": "Local Assets", - "local_id": "Local ID", - "local_media_summary": "Local Media Summary", - "local_network": "Local network", - "local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network", - "location": "Location", - "location_permission": "Location permission", - "location_permission_content": "In order to use the auto-switching feature, Immich needs precise location permission so it can read the current Wi-Fi network's name", - "location_picker_choose_on_map": "Choose on map", - "location_picker_latitude_error": "Enter a valid latitude", - "location_picker_latitude_hint": "Enter your latitude here", - "location_picker_longitude_error": "Enter a valid longitude", - "location_picker_longitude_hint": "Enter your longitude here", - "lock": "Lock", - "locked_folder": "Locked Folder", - "log_detail_title": "Log Detail", - "log_out": "Log out", - "log_out_all_devices": "Log Out All Devices", - "logged_in_as": "Logged in as {user}", - "logged_out_all_devices": "Logged out all devices", - "logged_out_device": "Logged out device", - "login": "Login", - "login_disabled": "Login has been disabled", - "login_form_api_exception": "API exception. Please check the server URL and try again.", - "login_form_email_hint": "youremail@email.uk", - "login_form_endpoint_hint": "http://your-server-ip:port", - "login_form_endpoint_url": "Server Endpoint URL", - "login_form_err_invalid_email": "Invalid Email", - "login_form_err_invalid_url": "Invalid URL", - "login_form_err_leading_whitespace": "Leading whitespace", - "login_form_err_trailing_whitespace": "Trailing whitespace", - "login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL", - "login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server", - "login_form_failed_login": "Error logging you in, check server URL, email and password", - "login_form_handshake_exception": "There was an Handshake Exception with the server. Enable self-signed certificate support in the settings if you are using a self-signed certificate.", - "login_form_password_hint": "password", - "login_form_server_empty": "Enter a server URL", - "login_form_server_error": "Could not connect to server", - "login_has_been_disabled": "Login has been disabled", - "login_password_changed_error": "There was an error updating your password", - "login_password_changed_success": "Password updated successfully", - "logout_all_device_confirmation": "Are you sure you want to log out all devices?", - "logout_this_device_confirmation": "Are you sure you want to log out this device?", - "logs": "Logs", - "longitude": "Longitude", - "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_menu": "Main menu", - "maintenance_action_restore": "Restoring Database", - "maintenance_description": "Immich has been put into maintenance mode.", - "maintenance_end": "End maintenance mode", - "maintenance_logged_in_as": "Currently logged in as {user}", - "maintenance_restore_from_backup": "Restore From Backup", - "maintenance_restore_library": "Restore Your Library", - "maintenance_restore_library_confirm": "If this looks correct, continue to restoring a backup!", - "maintenance_restore_library_description": "Restoring Database", - "maintenance_restore_library_folder_has_files": "{folder} has {count} folder(s)", - "maintenance_restore_library_folder_no_files": "{folder} is missing files!", - "maintenance_restore_library_folder_pass": "readable and writable", - "maintenance_restore_library_folder_read_fail": "not readable", - "maintenance_restore_library_folder_write_fail": "not writable", - "maintenance_restore_library_hint_missing_files": "You may be missing important files", - "maintenance_restore_library_hint_regenerate_later": "You can regenerate these later in settings", - "maintenance_restore_library_hint_storage_template_missing_files": "Using storage template? You may be missing files", - "maintenance_restore_library_loading": "Loading integrity checks and heuristicsâ€Ļ", - "maintenance_task_backup": "Creating a backup of the existing databaseâ€Ļ", - "maintenance_task_migrations": "Running database migrationsâ€Ļ", - "maintenance_task_restore": "Restoring the chosen backupâ€Ļ", - "maintenance_task_rollback": "Restore failed, rolling back to restore pointâ€Ļ", - "maintenance_title": "Temporarily Unavailable", - "make": "Make", - "manage_geolocation": "Manage location", - "manage_media_access_rationale": "This permission is required for proper handling of moving assets to the trash and restoring them from it.", - "manage_media_access_settings": "Open settings", - "manage_media_access_subtitle": "Allow the Immich app to manage and move media files.", - "manage_media_access_title": "Media Management Access", - "manage_sharing_with_partners": "Manage sharing with partners", - "manage_the_app_settings": "Manage the app settings", - "manage_your_account": "Manage your account", - "manage_your_api_keys": "Manage your API keys", - "manage_your_devices": "Manage your logged-in devices", - "manage_your_oauth_connection": "Manage your OAuth connection", - "map": "Map", - "map_cannot_get_user_location": "Cannot get user's location", - "map_location_picker_page_use_location": "Use this location", - "map_location_service_disabled_content": "Location service needs to be enabled to display assets from your current location. Do you want to enable it now?", - "map_location_service_disabled_title": "Location Service disabled", - "map_marker_for_image": "Map marker for image taken in {city}, {country}", - "map_marker_with_image": "Map marker with image", - "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", - "map_no_location_permission_title": "Location Permission denied", - "map_settings": "Map settings", - "map_settings_dark_mode": "Dark mode", - "map_settings_date_range_option_day": "Past 24 hours", - "map_settings_date_range_option_days": "Past {days} days", - "map_settings_date_range_option_year": "Past year", - "map_settings_date_range_option_years": "Past {years} years", - "map_settings_include_show_archived": "Include Archived", - "map_settings_include_show_partners": "Include Partners", - "map_settings_only_show_favorites": "Show Favourite Only", - "map_settings_theme_settings": "Map Theme", - "mark_all_as_read": "Mark all as read", - "marked_all_as_read": "Marked all as read", - "matches": "Matches", - "matching_assets": "Matching Assets", - "media_chrome": { - "auto": "Auto", - "captions": "Captions", - "captions_off": "Off", - "closed_captions": "closed captions", - "decode_error": "Decode error", - "disable_captions": "Disable captions", - "enable_captions": "Enable captions", - "enter_fullscreen_mode": "Enter fullscreen mode", - "exit_fullscreen_mode": "Exit fullscreen mode", - "loop": "Loop", - "media_error_description": "A media error caused playback to be aborted. The media could be corrupt or your browser does not support this format.", - "media_loading": "media loading", - "mute": "Mute", - "network_error": "Network error", - "network_error_description": "A network error caused the media download to fail.", - "not_supported_error": "Source Not Supported", - "playback_rate": "Playback rate", - "playback_rate_current": "current playback rate", - "playback_rate_value": "Playback rate {playbackRate}", - "playback_time": "playback time", - "quality": "Quality", - "second": "second", - "seconds": "seconds", - "time_value_of_total_time": "{currentTime} of {totalTime}", - "time_value_remaining": "{time} remaining", - "unmute": "Unmute", - "unsupported_error_description": "An unsupported error occurred. The server or network failed, or your browser does not support this format.", - "video_not_loaded_unknown_time": "video not loaded, unknown time.", - "video_player": "video player", - "volume": "volume" - }, - "media_type": "Media type", - "memories": "Memories", - "memories_all_caught_up": "All caught up", - "memories_check_back_tomorrow": "Check back tomorrow for more memories", - "memories_setting_description": "Manage what you see in your memories", - "memories_start_over": "Start Over", - "memories_swipe_to_close": "Swipe up to close", - "memory": "Memory", - "memory_lane_title": "Memory Lane {title}", - "menu": "Menu", - "merge": "Merge", - "merge_people": "Merge people", - "merge_people_limit": "You can only merge up to 5 faces at a time", - "merge_people_prompt": "Do you want to merge these people? This action is irreversible.", - "merge_people_successfully": "Merge people successfully", - "merged_people_count": "Merged {count, plural, one {# person} other {# people}}", - "minFaces": "Minimum faces", - "minFaces_description": "The minimum number of recognised faces for a person to be displayed", - "minimize": "Minimise", - "minute": "Minute", - "minutes": "Minutes", - "missing": "Missing", - "mobile_app": "Mobile App", - "mobile_app_download_onboarding_note": "Download the companion mobile app using the following options", - "model": "Model", - "modify_date": "Modify Date", - "month": "Month", - "more": "More", - "motion": "Motion", - "move": "Move", - "move_off_locked_folder": "Move out of locked folder", - "move_to": "Move to", - "move_to_device_trash": "Move to device bin", - "move_to_lock_folder_action_prompt": "{count} added to the locked folder", - "move_to_locked_folder": "Move to locked folder", - "move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder", - "moved_to_trash": "Moved to bin", - "mute_memories": "Mute Memories", - "my_albums": "My albums", - "my_immich_description": "Copy current page as a My Immich link", - "my_immich_title": "My Immich link", - "name": "Name", - "name_or_nickname": "Name or nickname", - "name_required": "Name is required", - "navigate": "Navigate", - "navigate_to_time": "Navigate to Time", - "network_requirement_photos_upload": "Use mobile data to backup photos", - "network_requirement_videos_upload": "Use mobile data to backup videos", - "network_requirements": "Network Requirements", - "network_requirements_updated": "Network requirements changed, resetting backup queue", - "networking_settings": "Networking", - "networking_subtitle": "Manage the server endpoint settings", - "never": "Never", - "new_album": "New Album", - "new_api_key": "New API Key", - "new_feature": "New Feature", - "new_password": "New password", - "new_person": "New person", - "new_pin_code": "New PIN code", - "new_pin_code_subtitle": "This is your first time accessing the locked folder. Create a PIN code to securely access this page", - "new_update": "New update", - "new_user_created": "New user created", - "new_version_available": "NEW VERSION AVAILABLE", - "newest_first": "Newest first", - "next": "Next", - "next_memory": "Next memory", - "no": "No", - "no_albums_found": "No albums found", - "no_albums_message": "Create an album to organise your photos and videos", - "no_albums_with_name_yet": "It looks like you do not have any albums with this name yet.", - "no_albums_yet": "It looks like you do not have any albums yet.", - "no_archived_assets_message": "Archive photos and videos to hide them from your Photos view", - "no_assets_message": "Click to upload your first photo", - "no_assets_to_show": "No assets to show", - "no_cast_devices_found": "No cast devices found", - "no_checksum_local": "No checksum available - cannot fetch local assets", - "no_checksum_remote": "No checksum available - cannot fetch remote asset", - "no_devices": "No authorised devices", - "no_duplicates_found": "No duplicates were found.", - "no_exif_info_available": "No exif info available", - "no_explore_results_message": "Upload more photos to explore your collection.", - "no_favorites_message": "Add favourites to quickly find your best pictures and videos", - "no_libraries_message": "Create an external library to view your photos and videos", - "no_local_assets_found": "No local assets found with this checksum", - "no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.", - "no_name": "No Name", - "no_notifications": "No notifications", - "no_people_found": "No matching people found", - "no_places": "No places", - "no_remote_assets_found": "No remote assets found with this checksum", - "no_results": "No results", - "no_results_description": "Try a synonym or more general keyword", - "no_shared_albums_message": "Create an album to share photos and videos with people in your network", - "no_steps": "No steps added yet", - "none": "None", - "not_allowed": "Not allowed", - "not_available": "N/A", - "not_in_any_album": "Not in any album", - "not_selected": "Not selected", - "not_set": "Not set", - "notes": "Notes", - "nothing_here_yet": "Nothing here yet", - "notification_backup_reliability": "Enable notifications to improve background backup reliability", - "notification_enabled_list_tile_content": "Immich uses notifications for background backup. Manage them in your device settings.", - "notification_enabled_list_tile_open_button": "Open settings", - "notification_enabled_list_tile_title": "Notifications enabled", - "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", - "notification_permission_list_tile_content": "Grant permission to enable notifications.", - "notification_permission_list_tile_enable_button": "Enable Notifications", - "notification_permission_list_tile_title": "Notification Permission", - "notification_toggle_setting_description": "Enable email notifications", - "notifications": "Notifications", - "notifications_setting_description": "Manage notifications", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium Configurator", - "obtainium_configurator_instructions": "Use Obtainium to install and update the Android app directly from Immich GitHub's release. Create an API key and select a variant to create your Obtainium configuration link", - "ocr": "OCR", - "ocr_body": "Immich now reads the text inside your photos, so you can search for them by what they say.", - "ocr_title": "Search text in your photos", - "official_immich_resources": "Official Immich Resources", - "offline": "Offline", - "offset": "Offset", - "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_theme_description": "Choose a colour theme for your instance. You can change this later in your settings.", - "onboarding_user_welcome_description": "Let's get you started!", - "onboarding_welcome_user": "Welcome, {user}", - "online": "Online", - "only_favorites": "Only favourites", - "open": "Open", - "open_calendar": "Open calendar", - "open_in_browser": "Open in browser", - "open_in_immich_body": "Set Immich as your gallery on Android to open photos straight from other apps.", - "open_in_immich_title": "Open photos in Immich", - "open_in_map_view": "Open in map view", - "open_in_openstreetmap": "Open in OpenStreetMap", - "open_the_search_filters": "Open the search filters", - "options": "Options", - "or": "or", - "organize_into_albums": "Organise into albums", - "organize_into_albums_description": "Put existing photos into albums using current sync settings", - "organize_your_library": "Organise your library", - "orientation": "Orientation", - "original": "original", - "other": "Other", - "other_devices": "Other devices", - "other_variables": "Other variables", - "owned": "Owned", - "owner": "Owner", - "page": "Page", - "partner": "Partner", - "partner_can_access": "{partner} can access", - "partner_can_access_assets": "All your photos and videos except those in Archived and Deleted", - "partner_can_access_location": "The location where your photos were taken", - "partner_list_user_photos": "{user}'s photos", - "partner_page_empty_message": "Your photos are not yet shared with any partner.", - "partner_page_no_more_users": "No more users to add", - "partner_page_select_partner": "Select partner", - "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.", - "partner_sharing": "Partner Sharing", - "partners": "Partners", - "password": "Password", - "password_does_not_match": "Password does not match", - "password_required": "Password Required", - "password_reset_success": "Password reset success", - "past_durations": { - "days": "Past {days, plural, one {day} other {# days}}", - "hours": "Past {hours, plural, one {hour} other {# hours}}", - "years": "Past {years, plural, one {year} other {# years}}" - }, - "path": "Path", - "pattern": "Pattern", - "pause": "Pause", - "pause_memories": "Pause memories", - "paused": "Paused", - "pending": "Pending", - "people": "People", - "people_edits_count": "Edited {count, plural, one {# person} other {# people}}", - "people_feature_description": "Browsing photos and videos grouped by people", - "permanent_deletion_warning": "Permanent deletion warning", - "permanent_deletion_warning_setting_description": "Show a warning when permanently deleting assets", - "permanently_delete": "Permanently delete", - "permanently_delete_assets_count": "Permanently delete {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "Are you sure you want to permanently delete {count, plural, one {this asset?} other {these # assets?}} This will also remove {count, plural, one {it from its} other {them from their}} album(s).", - "permanently_deleted_asset": "Permanently deleted asset", - "permanently_deleted_assets_count": "Permanently deleted {count, plural, one {# asset} other {# assets}}", - "permission": "Permission", - "permission_empty": "Your permission shouldn't be empty", - "person": "Person", - "person_age_months": "{months, plural, one {# month} other {# months}} old", - "person_age_year_months": "1 year, {months, plural, one {# month} other {# months}} old", - "person_age_years": "{years, plural, one {# year} other {# years}} old", - "person_birthdate": "Born on {date}", - "person_hidden": "{name}{hidden, select, true { (hidden)} other {}}", - "person_recognized": "Person recognised", - "photo_shared_all_users": "Looks like you shared your photos with all users or you don't have any users to share with.", - "photos": "Photos", - "photos_and_videos": "Photos & Videos", - "photos_from_previous_years": "Photos from previous years", - "pick_a_location": "Pick a location", - "pick_custom_range": "Custom range", - "pick_date_range": "Select a date range", - "pin_code_changed_successfully": "Successfully changed PIN code", - "pin_code_reset_successfully": "Successfully reset PIN code", - "pin_code_setup_successfully": "Successfully setup a PIN code", - "pin_verification": "PIN code verification", - "place": "Place", - "places": "Places", - "places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}", - "play": "Play", - "play_memories": "Play memories", - "play_motion_photo": "Play Motion Photo", - "play_or_pause_video": "Play or pause video", - "play_original_video": "Play original video", - "play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.", - "play_transcoded_video": "Play transcoded video", - "please_auth_to_access": "Please authenticate to access", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "This method can filter events and conditionally prevent subsequent steps from running", - "port": "Port", - "preferences_settings_subtitle": "Manage the app's preferences", - "preferences_settings_title": "Preferences", - "preparing": "Preparing", - "preset": "Preset", - "preview": "Preview", - "previous": "Previous", - "previous_memory": "Previous memory", - "previous_or_next_day": "Day forward/back", - "previous_or_next_month": "Month forward/back", - "previous_or_next_photo": "Photo forward/back", - "previous_or_next_year": "Year forward/back", - "primary": "Primary", - "privacy": "Privacy", - "profile": "Profile", - "profile_drawer_app_logs": "Logs", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Read-only mode enabled. Long-press the user avatar icon to exit.", - "profile_image_of_user": "Profile image of {user}", - "profile_picture_set": "Profile picture set.", - "projection_type": "Projection Type", - "public_album": "Public album", - "public_share": "Public Share", - "purchase_account_info": "Supporter", - "purchase_activated_subtitle": "Thank you for supporting Immich and open-source software", - "purchase_activated_time": "Activated on {date}", - "purchase_activated_title": "Your key has been successfully activated", - "purchase_button_activate": "Activate", - "purchase_button_buy": "Buy", - "purchase_button_buy_immich": "Buy Immich", - "purchase_button_never_show_again": "Never show again", - "purchase_button_reminder": "Remind me in 30 days", - "purchase_button_remove_key": "Remove key", - "purchase_button_select": "Select", - "purchase_failed_activation": "Failed to activate! Please check your email for the correct product key!", - "purchase_individual_description_1": "For an individual", - "purchase_individual_description_2": "Supporter status", - "purchase_individual_title": "Individual", - "purchase_input_suggestion": "Have a product key? Enter the key below", - "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_title": "Support the project", - "purchase_per_server": "Per server", - "purchase_per_user": "Per user", - "purchase_remove_product_key": "Remove Product Key", - "purchase_remove_product_key_prompt": "Are you sure you want to remove the product key?", - "purchase_remove_server_product_key": "Remove Server product key", - "purchase_remove_server_product_key_prompt": "Are you sure you want to remove the Server product key?", - "purchase_server_description_1": "For the whole server", - "purchase_server_description_2": "Supporter status", - "purchase_server_title": "Server", - "purchase_settings_server_activated": "The server product key is managed by the admin", - "query_asset_id": "Query Asset ID", - "queue_status": "Queuing {count}/{total}", - "rate_asset": "Rate Asset", - "rating": "Star rating", - "rating_clear": "Clear rating", - "rating_count": "{count, plural, =0 {Unrated} one {# star} other {# stars}}", - "rating_description": "Display the EXIF rating in the info panel", - "reaction_options": "Reaction options", - "read_changelog": "Read Changelog", - "readonly_mode_disabled": "Read-only mode disabled", - "readonly_mode_enabled": "Read-only mode enabled", - "ready_for_upload": "Ready for upload", - "reassign": "Reassign", - "reassigned_assets_to_existing_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "Re-assigned {count, plural, one {# asset} other {# assets}} to a new person", - "reassing_hint": "Assign selected assets to an existing person", - "recent": "Recent", - "recent_searches": "Recent searches", - "recently_added": "Recently added", - "recently_added_body": "Jump straight to everything you've added lately on a dedicated page.", - "recently_added_description": "Browse your assets sorted by when they were uploaded to Immich", - "recently_added_page_title": "Recently Added", - "recently_added_title": "Recently added", - "recently_taken": "Recently taken", - "refresh": "Refresh", - "refresh_encoded_videos": "Refresh encoded videos", - "refresh_faces": "Refresh faces", - "refresh_metadata": "Refresh metadata", - "refresh_thumbnails": "Refresh thumbnails", - "refreshed": "Refreshed", - "refreshing_encoded_video": "Refreshing encoded video", - "refreshing_faces": "Refreshing faces", - "refreshing_metadata": "Refreshing metadata", - "regenerating_thumbnails": "Regenerating thumbnails", - "remote": "Remote", - "remote_assets": "Remote Assets", - "remote_media_summary": "Remote Media Summary", - "remove": "Remove", - "remove_assets_album_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from the album?", - "remove_assets_shared_link_confirmation": "Are you sure you want to remove {count, plural, one {# asset} other {# assets}} from this shared link?", - "remove_assets_title": "Remove assets?", - "remove_custom_date_range": "Remove custom date range", - "remove_filter": "Remove filter", - "remove_from_album": "Remove from album", - "remove_from_album_action_prompt": "{count} removed from the album", - "remove_from_favorites": "Remove from favourites", - "remove_from_lock_folder_action_prompt": "{count} removed from the locked folder", - "remove_from_locked_folder": "Remove from locked folder", - "remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of the locked folder? They will be visible in your library.", - "remove_from_shared_link": "Remove from shared link", - "remove_memory": "Remove memory", - "remove_photo_from_memory": "Remove photo from this memory", - "remove_tag": "Remove tag", - "remove_user": "Remove user", - "removed_api_key": "Removed API Key: {name}", - "removed_from_archive": "Removed from archive", - "removed_from_favorites": "Removed from favourites", - "removed_from_favorites_count": "{count, plural, other {Removed #}} from favourites", - "removed_memory": "Removed memory", - "removed_tagged_assets": "Removed tag from {count, plural, one {# asset} other {# assets}}", - "rename": "Rename", - "repository": "Repository", - "require_password": "Require password", - "rescan": "Rescan", - "reset": "Reset", - "reset_password": "Reset password", - "reset_people_visibility": "Reset people visibility", - "reset_pin_code": "Reset PIN code", - "reset_pin_code_description": "If you forgot your PIN code, you can contact the server administrator to reset it", - "reset_pin_code_with_password": "You can always reset your PIN code with your password", - "reset_sqlite": "Reset SQLite Database", - "reset_sqlite_clear_app_data": "Clear Data", - "reset_sqlite_confirmation": "Are you sure you want to clear the app data? This will remove all settings and sign you out.", - "reset_sqlite_confirmation_note": "Note: You will need to restart the app after clearing.", - "reset_sqlite_done": "App data has been cleared. Please restart Immich and log in again.", - "reset_sqlite_success": "Successfully reset the SQLite database", - "reset_to_default": "Reset to default", - "resolution": "Resolution", - "resolve_duplicates": "Resolve duplicates", - "resolved_all_duplicates": "Resolved all duplicates", - "restore": "Restore", - "restore_all": "Restore all", - "restore_user": "Restore user", - "restored_asset": "Restored asset", - "resume": "Resume", - "resume_paused_jobs": "Resume {count, plural, one {# paused job} other {# paused jobs}}", - "review_duplicates": "Review duplicates", - "review_large_files": "Review large files", - "role": "Role", - "role_editor": "Editor", - "role_viewer": "Viewer", - "running": "Running", - "save": "Save", - "saved": "Saved", - "saved_api_key": "Saved API Key", - "saved_profile": "Saved profile", - "saved_settings": "Saved settings", - "say_something": "Say something", - "scaffold_body_error_occurred": "Error occurred", - "scaffold_body_error_unrecoverable": "An unrecoverable error has occurred. Please share the error and stack trace on Discord or GitHub so we can help. If advised, you can clear the app data below.", - "scan": "Scan", - "scan_all_libraries": "Scan All Libraries", - "scan_library": "Scan", - "scanning": "Scanning", - "scanning_for_album": "Scanning for album...", - "screencast_mode_description": "Show keyboard and mouse event indicators on the screen", - "screencast_mode_title": "Toggle screencast mode", - "search": "Search", - "search_albums": "Search albums", - "search_by_context": "Search by context", - "search_by_description": "Search by description", - "search_by_description_example": "Hiking day in Sapa", - "search_by_filename": "Search by file name or extension", - "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", - "search_by_full_path": "Search by full path or folder", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.", - "search_by_ocr": "Search by OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Search lens model...", - "search_camera_make": "Search camera make...", - "search_camera_model": "Search camera model...", - "search_city": "Search city...", - "search_country": "Search country...", - "search_filter_apply": "Apply filter", - "search_filter_camera_title": "Select camera type", - "search_filter_date": "Date", - "search_filter_date_interval": "{start} to {end}", - "search_filter_date_title": "Select a date range", - "search_filter_display_option_not_in_album": "Not in album", - "search_filter_display_options": "Display Options", - "search_filter_filename": "Search by file name", - "search_filter_location": "Location", - "search_filter_location_title": "Select location", - "search_filter_media_type": "Media Type", - "search_filter_media_type_title": "Select media type", - "search_filter_people_title": "Select people", - "search_filter_star_rating": "Star Rating", - "search_filter_tags_title": "Select tags", - "search_for": "Search for", - "search_for_existing_person": "Search for existing person", - "search_no_more_result": "No more results", - "search_no_people": "No people", - "search_no_people_named": "No people named \"{name}\"", - "search_no_result": "No results found, try a different search term or combination", - "search_options": "Search options", - "search_page_search_photos_videos": "Search for your photos and videos", - "search_page_view_all_button": "View all", - "search_people": "Search people", - "search_places": "Search places", - "search_rating": "Search by rating...", - "search_settings": "Search settings", - "search_state": "Search state...", - "search_tags": "Search tags...", - "search_timezone": "Search timezone...", - "search_type": "Search type", - "search_your_photos": "Search your photos", - "searching_locales": "Searching locales...", - "second": "Second", - "see_all_people": "See all people", - "select": "Select", - "select_album": "Select album", - "select_album_cover": "Select album cover", - "select_albums": "Select albums", - "select_all": "Select all", - "select_all_duplicates": "Select all duplicates", - "select_avatar_color": "Select avatar colour", - "select_count": "{count, plural, one {Select #} other {Select #}}", - "select_cutoff_date": "Select cutoff date", - "select_face": "Select face", - "select_featured_photo": "Select featured photo", - "select_from_computer": "Select from computer", - "select_keep_all": "Select keep all", - "select_new_face": "Select new face", - "select_people": "Select people", - "select_person": "Select person", - "select_person_to_tag": "Select a person to tag", - "select_photos": "Select photos", - "select_quality": "Select quality", - "select_trash_all": "Select trash all", - "selected": "Selected", - "selected_count": "{count, plural, other {# selected}}", - "selected_gps_coordinates": "Selected GPS Coordinates", - "send_message": "Send message", - "send_welcome_email": "Send welcome email", - "server_endpoint": "Server Endpoint", - "server_info_box_app_version": "App Version", - "server_info_box_server_url": "Server URL", - "server_offline": "Server Offline", - "server_online": "Server Online", - "server_privacy": "Server Privacy", - "server_restarting_description": "This page will refresh momentarily.", - "server_restarting_title": "Server is restarting", - "server_stats": "Server Stats", - "server_update_available": "Server update is available", - "server_version": "Server Version", - "set": "Set", - "set_as_album_cover": "Set as album cover", - "set_as_featured_photo": "Set as featured photo", - "set_as_profile_picture": "Set as profile picture", - "set_date_of_birth": "Set date of birth", - "set_profile_picture": "Set profile picture", - "set_slideshow_to_fullscreen": "Set Slideshow to fullscreen", - "set_stack_primary_asset": "Set as primary asset", - "setting_image_navigation_enable_subtitle": "If enabled, you can navigate to the previous/next image by tapping the leftmost/rightmost quarter of the screen.", - "setting_image_navigation_enable_title": "Tap to Navigate", - "setting_image_navigation_title": "Image Navigation", - "setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).", - "setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).", - "setting_image_viewer_original_title": "Load original image", - "setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.", - "setting_image_viewer_preview_title": "Load preview image", - "setting_languages_apply": "Apply", - "setting_languages_subtitle": "Change the app's language", - "setting_notifications_notify_minutes": "{count} minutes", - "setting_notifications_notify_seconds": "{count} seconds", - "setting_notifications_subtitle": "Adjust your notification preferences", - "setting_video_viewer_auto_play_subtitle": "Automatically start playing videos when they are opened", - "setting_video_viewer_auto_play_title": "Auto play videos", - "setting_video_viewer_looping_title": "Looping", - "setting_video_viewer_original_video_subtitle": "When streaming a video from the server, play the original even when a transcode is available. May lead to buffering. Videos available locally are played in original quality regardless of this setting.", - "setting_video_viewer_original_video_title": "Force original video", - "settings": "Settings", - "settings_saved": "Settings saved", - "setup_pin_code": "Setup a PIN code", - "share": "Share", - "share_dialog_preparing": "Preparing...", - "share_link": "Share Link", - "share_original": "Use original (large)", - "share_preview": "Use thumbnail (small)", - "share_quality_body": "Press and hold the share button to choose the image quality before you share.", - "share_quality_title": "Choose your share quality", - "shared": "Shared", - "shared_album_activities_input_disable": "Comment is disabled", - "shared_album_activity_remove_content": "Do you want to delete this activity?", - "shared_album_activity_remove_title": "Delete Activity", - "shared_album_section_people_action_error": "Error leaving/removing from album", - "shared_album_section_people_title": "PEOPLE", - "shared_by": "Shared by", - "shared_by_user": "Shared by {user}", - "shared_by_you": "Shared by you", - "shared_from_partner": "Photos from {partner}", - "shared_intent_upload_button_progress_text": "{current} / {total} Uploaded", - "shared_link_app_bar_title": "Shared Links", - "shared_link_clipboard_copied_massage": "Copied to clipboard", - "shared_link_create_error": "Error while creating shared link", - "shared_link_custom_url_description": "Access this shared link with a custom URL name", - "shared_link_custom_url_title": "Custom URL", - "shared_link_custom_url_warning": "Warning: a custom URL name with a forward slash may not behave as you expect.", - "shared_link_edit_description_hint": "Enter the share description", - "shared_link_edit_expire_after_option_day": "1 day", - "shared_link_edit_expire_after_option_days": "{count} days", - "shared_link_edit_expire_after_option_hour": "1 hour", - "shared_link_edit_expire_after_option_hours": "{count} hours", - "shared_link_edit_expire_after_option_minute": "1 minute", - "shared_link_edit_expire_after_option_minutes": "{count} minutes", - "shared_link_edit_expire_after_option_months": "{count} months", - "shared_link_edit_expire_after_option_year": "{count} year", - "shared_link_edit_password_hint": "Enter the share password", - "shared_link_edit_submit_button": "Update link", - "shared_link_error_server_url_fetch": "Cannot fetch the server url", - "shared_link_expires_day": "Expires in {count} day", - "shared_link_expires_days": "Expires in {count} days", - "shared_link_expires_hour": "Expires in {count} hour", - "shared_link_expires_hours": "Expires in {count} hours", - "shared_link_expires_minute": "Expires in {count} minute", - "shared_link_expires_minutes": "Expires in {count} minutes", - "shared_link_expires_never": "Expires ∞", - "shared_link_expires_second": "Expires in {count} second", - "shared_link_expires_seconds": "Expires in {count} seconds", - "shared_link_individual_shared": "Individual shared", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Manage Shared links", - "shared_link_options": "Shared link options", - "shared_link_password_description": "Require a password to access this shared link", - "shared_links": "Shared links", - "shared_links_description": "Share photos and videos with a link", - "shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}", - "shared_with_me": "Shared with me", - "shared_with_partner": "Shared with {partner}", - "sharing": "Sharing", - "sharing_enter_password": "Please enter the password to view this page.", - "shift_to_permanent_delete": "press ⇧ to permanently delete asset", - "show_album_options": "Show album options", - "show_albums": "Show albums", - "show_all_people": "Show all people", - "show_and_hide_people": "Show & hide people", - "show_file_location": "Show file location", - "show_gallery": "Show gallery", - "show_hidden_people": "Show hidden people", - "show_in_timeline": "Show in timeline", - "show_in_timeline_setting_description": "Show photos and videos from this user in your timeline", - "show_keyboard_shortcuts": "Show keyboard shortcuts", - "show_less": "Show less", - "show_metadata": "Show metadata", - "show_more_fields": "{count, plural, one {Show # more field} other {Show # more fields}}", - "show_or_hide_info": "Show or hide info", - "show_password": "Show password", - "show_person_options": "Show person options", - "show_progress_bar": "Show Progress Bar", - "show_schema": "Show schema", - "show_search_options": "Show search options", - "show_shared_links": "Show shared links", - "show_slideshow_metadata_overlay": "Show image info overlay", - "show_slideshow_transition": "Show slideshow transition", - "show_supporter_badge": "Supporter badge", - "show_supporter_badge_description": "Show a supporter badge", - "show_text_recognition": "Show text recognition", - "show_text_search_menu": "Show text search menu", - "shuffle": "Shuffle", - "sidebar": "Sidebar", - "sidebar_display_description": "Display a link to the view in the sidebar", - "sign_out": "Sign Out", - "sign_up": "Sign up", - "size": "Size", - "skip": "Skip", - "skip_to_content": "Skip to content", - "skip_to_folders": "Skip to folders", - "skip_to_tags": "Skip to tags", - "slideshow": "Slideshow", - "slideshow_body": "Sit back and watch your photos play in a full-screen slideshow.", - "slideshow_metadata_overlay_mode": "Overlay content", - "slideshow_metadata_overlay_mode_description_only": "Description only", - "slideshow_metadata_overlay_mode_full": "Full", - "slideshow_repeat": "Repeat slideshow", - "slideshow_repeat_description": "Loop back to beginning when slideshow ends", - "slideshow_settings": "Slideshow settings", - "slideshow_title": "Slideshow", - "smart_album": "Smart album", - "some_assets_already_have_a_location_warning": "Some of the selected assets already have a location", - "sort_albums_by": "Sort albums by...", - "sort_created": "Date created", - "sort_items": "Number of items", - "sort_modified": "Date modified", - "sort_oldest": "Oldest photo", - "sort_people_by_similarity": "Sort people by similarity", - "sort_recent": "Most recent photo", - "sort_title": "Title", - "source": "Source", - "stack": "Stack", - "stack_action_prompt": "{count} stacked", - "stack_duplicates": "Stack duplicates", - "stack_selected_photos": "Stack selected photos", - "stacked_assets_count": "Stacked {count, plural, one {# asset} other {# assets}}", - "stacktrace": "Stacktrace", - "start": "Start", - "start_date": "Start date", - "start_date_before_end_date": "Start date must be before end date", - "state": "State", - "status": "Status", - "step_delete": "Delete step", - "step_delete_confirm": "Are you sure you want to delete this step?", - "step_details": "Step details", - "steps": "Steps", - "stop_casting": "Stop casting", - "stop_motion_photo": "Stop Motion Photo", - "stop_photo_sharing": "Stop sharing your photos?", - "stop_photo_sharing_description": "{partner} will no longer be able to access your photos.", - "stop_sharing_photos_with_user": "Stop sharing your photos with this user", - "storage": "Storage space", - "storage_label": "Storage label", - "storage_quota": "Storage Quota", - "storage_usage": "{used} of {available} used", - "submit": "Submit", - "success": "Success", - "suggestions": "Suggestions", - "sunrise_on_the_beach": "Sunrise on the beach", - "support": "Support", - "support_and_feedback": "Support & Feedback", - "support_third_party_description": "Your Immich installation was packaged by a third-party. Issues you experience may be caused by that package, so please raise issues with them in the first instance using the links below.", - "supporter": "Supporter", - "swap_merge_direction": "Swap merge direction", - "sync": "Sync", - "sync_albums": "Sync albums", - "sync_local": "Sync Local", - "sync_remote": "Sync Remote", - "sync_status": "Sync Status", - "sync_status_subtitle": "View and manage the sync system", - "sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich", - "system_theme": "System theme", - "system_theme_command_description": "Use the system theme ({value})", - "tag": "Tag", - "tag_assets": "Tag assets", - "tag_created": "Created tag: {tag}", - "tag_face": "Tag face", - "tag_feature_description": "Browsing photos and videos grouped by logical tag topics", - "tag_people": "Tag People", - "tag_updated": "Updated tag: {tag}", - "tagged_assets": "Tagged {count, plural, one {# asset} other {# assets}}", - "tags": "Tags", - "tap_to_run_job": "Tap to run job", - "template": "Template", - "text_recognition": "Text recognition", - "theme": "Theme", - "theme_selection": "Theme selection", - "theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference", - "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles", - "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({count})", - "theme_setting_colorful_interface_subtitle": "Apply primary colour to background surfaces.", - "theme_setting_colorful_interface_title": "Colourful interface", - "theme_setting_primary_color_subtitle": "Pick a colour for primary actions and accents.", - "theme_setting_primary_color_title": "Primary colour", - "theme_setting_system_primary_color_title": "Use system colour", - "theme_setting_system_theme_switch": "Automatic (Follow system setting)", - "then": "Then", - "they_will_be_merged_together": "They will be merged together", - "third_party_resources": "Third-Party Resources", - "time": "Time", - "time_based_memories": "Time-based memories", - "time_based_memories_duration": "Number of seconds to display each image.", - "timeline": "Timeline", - "timezone": "Timezone", - "to_archive": "Archive", - "to_change_password": "Change password", - "to_favorite": "Favourite", - "to_login": "Login", - "to_multi_select": "to multi-select", - "to_parent": "Go to parent", - "to_select": "to select", - "to_trash": "Bin", - "toggle_settings": "Toggle settings", - "toggle_theme_description": "Toggle theme", - "total": "Total", - "total_usage": "Total usage", - "trash": "Bin", - "trash_action_prompt": "{count} moved to bin", - "trash_all": "Bin All", - "trash_count": "Bin {count, number}", - "trash_delete_asset": "Bin/Delete Asset", - "trash_no_results_message": "Binned photos and videos will show up here.", - "trash_page_delete_all": "Delete All", - "trash_page_info": "Binned items will be permanently deleted after {days} days", - "trashed_items_will_be_permanently_deleted_after": "Binned items will be permanently deleted after {days, plural, one {# day} other {# days}}.", - "trigger": "Trigger", - "trigger_asset_metadata_extraction": "Asset Metadata Extraction", - "trigger_asset_metadata_extraction_description": "Triggered when the EXIF metadata of an asset is extracted", - "trigger_asset_uploaded": "Asset Upload", - "trigger_asset_uploaded_description": "Triggered when a new asset is uploaded", - "trigger_person_recognized": "Person Recognised", - "trigger_person_recognized_description": "Triggered when a person is recognised", - "troubleshoot": "Troubleshoot", - "type": "Type", - "unable_to_change_pin_code": "Unable to change PIN code", - "unable_to_check_version": "Unable to check app or server version", - "unable_to_setup_pin_code": "Unable to setup PIN code", - "unarchive": "Unarchive", - "unarchive_action_prompt": "{count} removed from Archive", - "unarchived_count": "{count, plural, other {Unarchived #}}", - "undo": "Undo", - "unfavorite": "Unfavourite", - "unfavorite_action_prompt": "{count} removed from Favourites", - "unhide_person": "Unhide person", - "unknown": "Unknown", - "unknown_country": "Unknown Country", - "unknown_date": "Unknown date", - "unknown_schema": "Unknown schema", - "unknown_year": "Unknown Year", - "unlimited": "Unlimited", - "unlink_motion_video": "Unlink motion video", - "unlink_oauth": "Unlink OAuth", - "unlinked_oauth_account": "Unlinked OAuth account", - "unmute_memories": "Unmute Memories", - "unnamed_album": "Unnamed Album", - "unnamed_album_delete_confirmation": "Are you sure you want to delete this album?", - "unnamed_share": "Unnamed Share", - "unsaved_change": "Unsaved change", - "unselect_all": "Unselect all", - "unselect_all_duplicates": "Unselect all duplicates", - "unstack": "Un-stack", - "unstack_action_prompt": "{count} unstacked", - "unstacked_assets_count": "Un-stacked {count, plural, one {# asset} other {# assets}}", - "unsupported_field_type": "Unsupported field type", - "unsupported_file_type": "File {file} can't be uploaded because its file type {type} is not supported.", - "untagged": "Untagged", - "up_next": "Up next", - "update_location_action_prompt": "Update the location of {count} selected assets with:", - "updated_at": "Updated", - "updated_password": "Updated password", - "upload": "Upload", - "upload_concurrency": "Upload concurrency", - "upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}", - "upload_details": "Upload Details", - "upload_error_with_count": "Upload error for {count, plural, one {# asset} other {# assets}}", - "upload_errors": "Upload completed with {count, plural, one {# error} other {# errors}}, refresh the page to see new upload assets.", - "upload_finished": "Upload finished", - "upload_progress": "Remaining {remaining, number} - Processed {processed, number}/{total, number}", - "upload_skipped_duplicates": "Skipped {count, plural, one {# duplicate asset} other {# duplicate assets}}", - "upload_status_duplicates": "Duplicates", - "upload_status_errors": "Errors", - "upload_status_uploaded": "Uploaded", - "upload_success": "Upload success, refresh the page to see new upload assets.", - "upload_to_album_body": "For users that don't use the manual upload feature, you can now choose to add local photos directly into an album as you upload them, no need to upload then add to an album later anymore.", - "upload_to_album_title": "Upload straight to an album", - "upload_to_immich": "Upload to Immich ({count})", - "uploading": "Uploading", - "uploading_media": "Uploading media", - "uploads": "Uploads", - "uploads_count": "{count, plural, one {# upload} other {# uploads}}", - "url": "URL", - "usage": "Usage", - "use_biometric": "Use biometric", - "use_browser_locale": "Use browser locale", - "use_browser_locale_description": "Format dates, times, and numbers based on your browser locale", - "use_current_connection": "Use current connection", - "use_custom_date_range": "Use custom date range instead", - "use_template": "Use template", - "user": "User", - "user_has_been_deleted": "This user has been deleted.", - "user_id": "User ID", - "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_usage_detail": "User usage detail", - "user_usage_stats": "Account usage statistics", - "user_usage_stats_description": "View account usage statistics", - "username": "Username", - "users": "Users", - "users_added_to_album_count": "Added {count, plural, one {# user} other {# users}} to the album", - "utilities": "Utilities", - "validate": "Validate", - "validate_endpoint_error": "Please enter a valid URL", - "variables": "Variables", - "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 release notes 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_history": "Version History", - "version_history_item": "Installed {version} on {date}", - "video": "Video", - "video_hover_setting": "Play video thumbnail on hover", - "video_hover_setting_description": "Play video thumbnail when mouse is hovering over item. Even when disabled, playback can be started by hovering over the play icon.", - "video_quality": "Video quality", - "videos": "Videos", - "videos_count": "{count, plural, one {# Video} other {# Videos}}", - "view": "View", - "view_album": "View Album", - "view_all": "View All", - "view_all_users": "View all users", - "view_asset_owners": "View asset owners", - "view_details": "View Details", - "view_in_timeline": "View in timeline", - "view_link": "View link", - "view_name": "View", - "view_next_asset": "View next asset", - "view_previous_asset": "View previous asset", - "view_qr_code": "View QR code", - "view_similar_photos": "View similar photos", - "view_stack": "View Stack", - "viewer_remove_from_stack": "Remove from Stack", - "visibility": "Visibility", - "visibility_changed": "Visibility changed for {count, plural, one {# person} other {# people}}", - "visual": "Visual", - "waiting": "Waiting", - "waiting_count": "Waiting: {count}", - "warning": "Warning", - "week": "Week", - "welcome": "Welcome", - "welcome_to_immich": "Welcome to Immich", - "whats_new": "What's new", - "whats_new_settings_subtitle": "See what's new in Immich", - "whats_new_version": "Version {version}", - "when": "When", - "width": "Width", - "wifi_name": "Wi-Fi Name", - "workflow": "Workflow", - "workflow_delete_prompt": "Are you sure you want to delete this workflow?", - "workflow_deleted": "Workflow deleted", - "workflow_description": "Workflow description", - "workflow_info": "Workflow info", - "workflow_json": "Workflow JSON", - "workflow_json_help": "Edit the workflow configuration in JSON format. Changes will sync to the visual builder.", - "workflow_name": "Workflow name", - "workflow_navigation_prompt": "Are you sure you want to leave without saving your changes?", - "workflow_summary": "Workflow summary", - "workflow_templates": "Workflow templates", - "workflow_update_success": "Workflow updated successfully", - "workflows": "Workflows", - "workflows_help_text": "Workflows automate actions on your assets based on triggers and filters", - "wrong_pin_code": "Wrong PIN code", - "x_of_total": "{x}/{total}", - "year": "Year", - "years_ago": "{years, plural, one {# year} other {# years}} ago", - "yes": "Yes", - "you_dont_have_any_shared_links": "You don't have any shared links", - "your_wifi_name": "Your Wi-Fi name", - "zero_to_clear_rating": "press 0 to clear asset rating", - "zoom_image": "Zoom Image" -} diff --git a/i18n/eo.json b/i18n/eo.json index b0283449a0..087b2dcc5e 100644 --- a/i18n/eo.json +++ b/i18n/eo.json @@ -10,31 +10,39 @@ "active": "Aktivaj", "active_count": "Aktivaj: {count}", "activity": "OkazaÄĩoj", + "activity_changed": "AktivaÄĩoj estas {enabled,select,true {ŝaltitaj} other {malŝaltitaj}}", "add": "Aldoni", "add_a_description": "Aldoni priskribon", "add_a_location": "Aldoni lokon", "add_a_name": "Aldoni nomon", "add_a_title": "Aldoni titolon", "add_action": "Aldoni agon", + "add_action_description": "Klaku por aldoni agon por fari", "add_assets": "Aldoni elementojn", "add_birthday": "Aldoni naskiĝtagon", "add_endpoint": "Aldoni finpunkton", "add_exclusion_pattern": "Aldoni skemon de ekskludo", + "add_filter": "Aldoni filtrilon", + "add_filter_description": "Klaku por aldoni kondiĉon por filtri", "add_location": "Aldoni lokon", + "add_more_users": "Aldoni pli da uzantoj", "add_partner": "Aldoni partneron", + "add_path": "Aldoni vojon", "add_photos": "Aldoni fotojn", - "add_step": "Aldoni paŝon", "add_tag": "Aldoni etikedon", "add_to": "Aldoni alâ€Ļ", "add_to_album": "Aldoni al albumo", "add_to_album_bottom_sheet_added": "Aldonita(j) al {album}", "add_to_album_bottom_sheet_already_exists": "Jam en {album}", "add_to_album_bottom_sheet_some_local_assets": "Ne eblis aldoni kelkajn lokajn elementojn al la albumo", + "add_to_album_toggle": "Baskuli elekton por {album}", "add_to_albums": "Aldoni al albumoj", "add_to_albums_count": "Aldoni al albumoj ({count})", "add_to_bottom_bar": "Aldoni al", + "add_to_shared_album": "Aldoni al dividita albumo", "add_upload_to_stack": "Aldoni alŝutitajn elementojn al stako", "add_url": "Aldoni URL-on", + "add_workflow_step": "Aldoni paŝon al laborfluo", "added_to_archive": "Aldonita(j) al arÄĨivo", "added_to_favorites": "Aldonita(j) al preferataÄĩoj", "added_to_favorites_count": "Adonis {count, number} al preferataÄĩoj", @@ -57,7 +65,7 @@ "backup_onboarding_footer": "Por pli da informoj pri savkopioj kun Immich, bonvolu legi la dokumentaron.", "backup_onboarding_parts_title": "Sekur-kopioj laÅ­ strategio 3-2-1 inkluzivas:", "backup_onboarding_title": "Savkopioj", - "backup_settings": "Agordoj pri kopiado de datumbazo", + "backup_settings": "AgordaÄĩoj de kopiado de datumbazo", "backup_settings_description": "Administri agordojn pri datumbazo-nekropsio.", "cleared_jobs": "Taskoj forigitaj por: {job}", "config_set_by_file": "La agordoj estas aktuale regitaj de agordo-dosiero", @@ -73,7 +81,6 @@ "cron_expression_description": "Agordu la intervalon de analizado pere de la formato de cron. Por pli da informoj, legu ekzemple Crontab Guru", "cron_expression_presets": "AntaÅ­agordoj pri la cron-esprimo", "disable_login": "Malebligi ensalutadon", - "download_csv": "Elŝuti .csv-dosieron", "duplicate_detection_job_description": "Komenci permaŝin-lernadon por trovi similajn bildojn. Uzas 'inteligentan serĉadon'", "exclusion_pattern_description": "Per skemo de ekskludo, vi povas ignori dosierojn kaj dosierujojn dum analizado de la biblioteko. Tio estas utila se vi havas ekz. RAW-dosierojn, kiujn vi ne volas importi.", "export_config_as_json_description": "Elŝuti la aktualan sistem-agordaÄĩaron kiel JSON-dosieron", @@ -108,25 +115,10 @@ "image_thumbnail_quality_description": "Kvalito de bildeto, inter 1 kaj 100. Pli alta cifero indikas pli altkvalitan bildon, sed donas pli grandajn dosierojn kaj povas malrapidigi uzadon de la apo.", "image_thumbnail_title": "Agordoj pri bildetoj", "import_config_from_json_description": "Importi sistem-agordaÄĩaron de JSON-dosiero", - "integrity_checks_checksum_files": "Kontrolsum-dosieroj", - "integrity_checks_checksum_files_description": "Konfiguri la kontroladon per kontrolsumo", - "integrity_checks_checksum_files_enable_description": "Ŝalti kontroladon per kontrolsumoj", - "integrity_checks_checksum_files_percentage_limit": "Elcenta limo", - "integrity_checks_checksum_files_percentage_limit_description": "Konfiguri la maksimuman elcentaÄĩon, inter 0.01 kaj 1, kiom estu kontrolata je ĉiu intervalo.", - "integrity_checks_checksum_files_time_limit": "Tempolimo", - "integrity_checks_checksum_files_time_limit_description": "Konfiguri la maksimuman daÅ­ron (milisekundoj) de kontrolado je ĉiu intervalo.", - "integrity_checks_missing_files": "Mankantaj dosieroj", - "integrity_checks_missing_files_description": "Konfiguri la oftecon kaj ŝalti/malŝalti kontrolon por mankantaj dosieroj", - "integrity_checks_missing_files_enable_description": "Ŝalti kontroladon por mankantaj dosieroj", - "integrity_checks_settings": "Kontrolo de integreco", - "integrity_checks_settings_description": "Administri intervalojn inter kontroloj de integreco", - "integrity_checks_untracked_files": "Senspuraj dosieroj", - "integrity_checks_untracked_files_description": "Konfiguri la oftecon kaj ŝalti/malŝalti kontrolon por senspuraj dosieroj", - "integrity_checks_untracked_files_enable_description": "Ŝalti kontroladon por senspuraj dosieroj", "job_concurrency": "{job}: nombro de samtempaj taskoj", "job_created": "Tasko kreita", "job_not_concurrency_safe": "Estas nesekure fari tiun ĉi taskon samtempe kun aliaj.", - "job_settings": "Agordoj pri taskoj", + "job_settings": "Agordoj pri tasko", "job_settings_description": "Administri samtempajn taskojn", "jobs_delayed": "{jobCount, plural, other {# prokrastitaj}}", "jobs_failed": "{jobCount, plural, other {# malsukesis}}", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Ŝalti inteligentan serĉadon", "machine_learning_smart_search_enabled_description": "Se malŝaltita, tiam bildoj ne estos kodigitaj por inteligenta serĉado.", "machine_learning_url_description": "La URL-o de la maŝin-lerna servilo. Se vi donas pli ol unu URL-o, la sistemo provos ĉiun servilon unu post la alia ĝis kiam unu sukcese respondas, de la unua ĝis la lasta. Serviloj, kiuj ne respondas, estos dumtempe ignoritaj.", - "maintenance_backup_management": "Administrado de savkopioj", "maintenance_delete_backup": "Forigi savkopion", "maintenance_delete_backup_description": "La dosiero estos por ĉiam forigita.", "maintenance_delete_error": "Malsukcesis forigi savkopion.", - "maintenance_integrity_check": "Kontroli", - "maintenance_integrity_check_all": "Kontroli ĉiujn", - "maintenance_integrity_checksum_mismatch": "Nekongruaj kontrolsumoj", - "maintenance_integrity_checksum_mismatch_description": "Dosieroj pri kiuj la kontrolsumo stokita sur disko ne kongruas kun tiu en la datumbazo de Immich.", - "maintenance_integrity_checksum_mismatch_job": "Kontroli pri nekongruaj kontrolsumoj", - "maintenance_integrity_checksum_mismatch_refresh_job": "Refreŝigi la raporton pri nekongruaj kontrolsumoj", - "maintenance_integrity_missing_file": "Dosieroj mankantaj", - "maintenance_integrity_missing_file_description": "Dosieroj menciitaj en la datumbazo de Immich, sed kiuj ne (plu) ekzistas en la dosiersistemo.", - "maintenance_integrity_missing_file_job": "Detekti mankantajn dosierojn", - "maintenance_integrity_missing_file_refresh_job": "Refreŝigi raporton pri mankantaj dosieroj", - "maintenance_integrity_report": "Raporto pri integreco", - "maintenance_integrity_untracked_file": "Senspuraj dosieroj", - "maintenance_integrity_untracked_file_description": "Dosieroj en la dosierujoj de Immich, sed pri kiuj Immich havas neniun spuron.", - "maintenance_integrity_untracked_file_job": "Detekti senspurajn dosierojn", - "maintenance_integrity_untracked_file_refresh_job": "Refreŝigi raporton pri senspuraj dosieroj", "maintenance_restore_backup": "RestaÅ­ri savkopion", "maintenance_restore_backup_description": "Immich estos forigita kaj reinstalita de la elektita savkopio. Nova savkopio estos kreita antaÅ­e.", "maintenance_restore_backup_different_version": "Tiu ĉi savkopio estis kreita per alia versio de Immich!", @@ -236,7 +212,7 @@ "map_enable_description": "Ŝalti map-funkciojn", "map_gps_settings": "AgordaÄĩoj pri mapoj kaj GPS", "map_gps_settings_description": "Administri agordojn pri mapoj kaj GPS", - "map_implications": "Montri mapojn dependas de ekstera servo (tiles.immich.cloud)", + "map_implications": "Montri mapojn de dependas de ekstera servo (tiles.immich.cloud)", "map_light_style": "Hela stilo", "map_manage_reverse_geocoding_settings": "Administri agordojn pri inversa geo-kodigo", "map_reverse_geocoding": "Inversa geo-kodigo", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Ŝalti sciigojn per retmesaĝo", "notification_settings": "Agordoj pri sciigoj", "notification_settings_description": "Administri agordojn pri sciigoj, inkluzive tiujn per retmesaĝoj", - "oauth_allow_insecure_requests": "Permesi nesekurajn petojn", - "oauth_allow_insecure_requests_description": "ATENTU: tio ĉi malŝaltas validigon de TLS-atestiloj por OAuth-petoj, kaj lasas vin sendefenda kontraÅ­ kaŝa interkaptado de komunikado (man-in-the-middle).", "oauth_auto_launch": "Startigi aÅ­tomate", "oauth_auto_launch_description": "AÅ­tomate startigi la OAuth-procezon tuj ĉe la ensaluta paĝo", "oauth_auto_register": "Registri aÅ­tomate", @@ -300,11 +274,9 @@ "oauth_button_text": "Teksto de butono", "oauth_client_secret_description": "Bezonata por privata kliento, aÅ­ se PKCE (Proof Key for Code Exchange) ne estas subtenata de publika kliento.", "oauth_enable_description": "Ensaluti per OAuth", - "oauth_end_session_url_description": "Resendi uzanton al tiu ĉi URI post elsaluto.", "oauth_mobile_redirect_uri": "Resenda URI por poŝ-aparatoj", "oauth_mobile_redirect_uri_override": "Insisti pri resenda URI por poŝ-aparatoj", "oauth_mobile_redirect_uri_override_description": "Ŝaltu tion ĉi kiam la provizanto de OAuth ne permesas URI-on por poŝ-aparatoj, kiel \"{callback}\"", - "oauth_prompt_description": "Parametro de invito (ekz. select_account, login, consent)", "oauth_role_claim": "Petita rolo", "oauth_role_claim_description": "AÅ­tomate doni rolon de administranto laÅ­ tiu ĉi peto. La peto povas esti aÅ­ 'user' (uzanto) aÅ­ 'admin' (administranto).", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Aktualigado de ĉiuj bibliotekoj", "registration": "Registrado de administranto", "registration_description": "Vi estas la unua uzanto de tiu ĉi sistemo, do vi aÅ­tomate havos la rolon de administranto. Vi respondecos pri administraj taskoj, kaj vi povos krei pliajn uzantojn.", - "release_channel_release_candidate": "Lanĉkandidato", - "release_channel_stable": "Stabila", "remove_failed_jobs": "Forigi malsukcesajn taskojn", "require_password_change_on_login": "Devigi al uzantoj ŝanĝi pasvorton post unua ensaluto", "reset_settings_to_default": "Restarigi agordaÄĩojn al defaÅ­ltoj", @@ -344,7 +314,7 @@ "server_external_domain_settings_description": "Domajno por eksteraj ligiloj", "server_public_users": "Publikaj uzantoj", "server_public_users_description": "Nomo kaj retadreso de ĉiuj uzantoj estas listigitaj kiam oni aldonas uzanton al dividita albumo. Kiam malŝaltita, la listo de uzantoj estos videbla nur por administrantoj.", - "server_settings": "Agordoj pri servilo", + "server_settings": "Agordoj de servilo", "server_settings_description": "Administri agordojn pri servilo", "server_stats_page_description": "Paĝo de statistikoj pri la servilo", "server_welcome_message": "Bonvena mesaĝo", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Aplikiĝas nur al VAAPI kaj QSV. Indikas la DRI-nodoj uzataj por transkodado per aparato.", "transcoding_preset_preset": "AntaÅ­elekto (-preset)", "transcoding_preset_preset_description": "Rapideco de densigo. Malplia rapideco rezultas je pli malgrandaj dosieroj, kaj plibonigas kvaliton por donita bitrapido. VP9 ignoras rapidecojn pli grandajn ol 'faster'.", - "transcoding_realtime": "Realtempa transkodado [EKSPERIMENTA]", - "transcoding_realtime_description": "Ebligas transkodadon samtempe kun spektado per elsendfluo. Tio ebligas ŝanĝon de kvalito laÅ­vole, sed povas kaÅ­zi pli da atendotempo kaj interrompoj, depende de la kapacito de la servilo.", - "transcoding_realtime_enabled": "Ŝalti realtempan transkodadon", - "transcoding_realtime_enabled_description": "Se malŝaltita, la servilo rifuzas komenci novajn sesiojn de realtempa transkodado.", - "transcoding_realtime_resolutions": "Distingivoj", - "transcoding_realtime_resolutions_description": "La distingivoj proponitaj por realtempa transkodado. Pli altaj distingivoj povas okazigi problemojn sed la servilo ne povas sufiĉe rapide transkodigi ilin.", - "transcoding_realtime_video_codecs": "Videokodekoj", - "transcoding_realtime_video_codecs_description": "La video-kodekoj proponitaj por realtempa transkodado. Klientoj povos elekti sian plej bonan opcion dum ludado. AV1 estas pli efika ol HEVC, kiu estas pli efika ol H.264. Se vi uzas aparataran plirapidigon, uzu nur kodekojn, kiujn la akcelilo povas kodigi. Por softvara transkodado, notu ke H.264 estas pli rapida ol AV1, kiu estas pli rapida ol HEVC.", "transcoding_reference_frames": "Referencaj kadroj", "transcoding_reference_frames_description": "La nombro da apudaj kadroj uzataj dum densigo de iu kadro. Pli granda valoro rezultas je pli bona densigo, sed malpli rapida laboro. Valoro de 0 indikas aÅ­tomatan agordon.", "transcoding_required_description": "Nur videoj kun neakceptataj formatoj", @@ -474,12 +436,10 @@ "user_password_reset_description": "Bonvolu sendi la dumtempan pasvorton al la uzanto, kaj informu ke ĝi devos esti ŝanĝita je la sekva ensaluto.", "user_restore_description": "La konto de {user} estos restaÅ­rita.", "user_restore_scheduled_removal": "RestaÅ­ri uzanton - forviŝo planita je {date, date, long}", - "user_settings": "Agordoj pri uzantoj", + "user_settings": "Agordoj de uzanto", "user_settings_description": "Administri agordojn pri uzantoj", "user_successfully_removed": "La uzanto {email} estas forigita.", "users_page_description": "Paĝo por administri uzantojn", - "version_check_channel": "Eldono-kanalo", - "version_check_channel_description": "Elektu la eldono-kanalon pri kiu vi volas ricevi anoncojn de novaj versioj", "version_check_enabled_description": "Ebligi kontrolon de versio", "version_check_implications": "La funkcio de kontrolado de versio bezonas de temp' al tempan komunikadon kun {server}", "version_check_settings": "Kontrolo de versio", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Forviŝi kaŝmemoron de bildoj", "advanced_settings_clear_image_cache_error": "Malsukcesis forviŝi kaŝmemoron", "advanced_settings_clear_image_cache_success": "Sukcesis liberigi {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Uzu tiun ĉi agordon por filtri elementojn dum sinkronigo laÅ­ alternativaj kriterioj. Uzu tion ĉi nur se vi vidas, ke la apo ne sukcesas trovi ĉiujn albumojn.", + "advanced_settings_enable_alternate_media_filter_title": "[TESTATA] Uzi alternativan filtrilon por sinkronigi albumojn", "advanced_settings_log_level_title": "Nivelo de protokolado: {level}", "advanced_settings_prefer_remote_subtitle": "Kelkaj aparatoj ege malrapide ŝargas bildetojn el lokaj elementoj. Ŝaltu tiun ĉi agordon por anstataÅ­e ŝargi bildetojn de la servilo.", "advanced_settings_prefer_remote_title": "Preferi bildojn el servilo", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Tajloritaj paĝokapoj pri prokurilo [EKSPERIMENTA]", "advanced_settings_readonly_mode_subtitle": "Ŝaltas nurlegan reĝimon, kie oni povas nur rigardi fotojn. Funkcioj kiel elekti plurajn bildojn, dividi kun aliaj, forigi k.a. estas ĉiuj neeblaj. Vi povas ŝalti/malŝalti tiun reĝimon per la profilbildo de uzanto ĉe la hejmpaĝo", "advanced_settings_readonly_mode_title": "Nurlega reĝimo", + "advanced_settings_self_signed_ssl_subtitle": "Ignoras kontrolon de SSL-atestilo ĉe la servilo. Bezonata por memsubskribita atestilo.", + "advanced_settings_self_signed_ssl_title": "Permesi memsubskribitajn SSL-atestilojn [EKSPERIMENTA]", "advanced_settings_sync_remote_deletions_subtitle": "AÅ­tomate forigi aÅ­ malforigi elementojn en tiu ĉi aparato kiam oni faras tiun agon per retumilo", "advanced_settings_sync_remote_deletions_title": "Sinkronigi forigojn [EKSPERIMENTA]", "advanced_settings_tile_subtitle": "Altnivelaj agordaÄĩoj", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ĉu vi certas, ke vi volas forigi la albumon {album}?", "album_delete_confirmation_description": "Se temas pri dividita albumo, aliaj uzantoj ne plu povos vidi ĝin.", "album_deleted": "Albumo forigita", + "album_info_card_backup_album_excluded": "EKSKLUDITA", + "album_info_card_backup_album_included": "INKLUZIVITA", "album_info_updated": "Informoj pri albumo ĝisdatigitaj", + "album_leave": "Foriri de albumo?", + "album_leave_confirmation": "Ĉu vi certas, ke vi volas forlasi la albumon {album}?", "album_name": "Nomo de albumo", "album_options": "Agordoj de albumo", "album_remove_user": "Ĉu forigi uzanton?", "album_remove_user_confirmation": "Ĉu vi certas, ke vi volas forigi la uzanton {user}?", "album_search_not_found": "Neniu albumo kongruas kun via serĉo", + "album_selected": "Albumo elektita", "album_share_no_users": "Ŝajne vi jam dividis tiun albumon kun ĉiuj uzantoj AÅŦ ne ekzistas uzantoj kun kiuj vi povus dividi ĝin.", "album_summary": "Resumo de albumo", "album_updated": "Albumo ĝisdatigita", "album_updated_setting_description": "Ricevi retmesaĝon kiam dividita albumo havas novajn elementojn", "album_upload_assets": "Alŝuti elementojn el via komputilo kaj aldoni ilin al albumo", + "album_user_left": "Foriris de {album}", + "album_user_removed": "Uzanto {user} forigita", + "album_viewer_appbar_delete_confirm": "Ĉu vi certas, ke vi volas forigi tiun ĉi albumon de via konto?", "album_viewer_appbar_share_err_delete": "Malsukcesis forigi albumon", + "album_viewer_appbar_share_err_leave": "Malsukcesis foriri de albumo", + "album_viewer_appbar_share_err_remove": "Okazis problemo dum forigo de elementoj el albumo", + "album_viewer_appbar_share_err_title": "Malsukcesis ŝanĝi titolon de albumo", + "album_viewer_appbar_share_leave": "Foriri de albumo", + "album_viewer_appbar_share_to": "Dividi al", "album_viewer_page_share_add_users": "Aldoni uzantojn", "album_with_link_access": "Permesi, ke iu ajn kun la ligilo povu vidi la fotojn kaj homojn en la albumo.", "albums": "Albumoj", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Metodo por vicordigi elementojn defaÅ­lte uzata en nova albumo.", "albums_feature_description": "Kolektoj de elementoj, kiujn vi povas dividi kun aliaj uzantoj.", "albums_on_device_count": "Albumoj ĉe la aparato ({count})", + "albums_selected": "{count, plural, one {# albumo elektita} other {# albumoj elektitaj}}", "all": "Ĉiuj", "all_albums": "Ĉiuj albumoj", "all_people": "Ĉiuj homoj", "all_photos": "Ĉiuj fotoj", "all_videos": "Ĉiuj videoj", "allow_dark_mode": "Permesi malhelan reĝimon", + "allow_edits": "Permesi redaktojn", "allow_public_user_to_download": "Permesu, ke publikano elŝutu el la albumo", "allow_public_user_to_upload": "Permesu, ke publikano alŝutu al la albumo", "allowed": "Permesita", @@ -549,12 +528,14 @@ "always_keep": "Ĉiam konservi", "always_keep_photos_hint": "La funkcio 'Liberigi spacon' konservos ĉiujn fotojn en tiu ĉi aparato.", "always_keep_videos_hint": "La funkcio 'Liberigi spacon' konservos ĉiujn videojn en tiu ĉi aparato.", + "anti_clockwise": "KontraÅ­-horloĝdirekte", "api_key": "API-ŝlosilo", "api_key_description": "Tio ĉi montriĝos nur unufoje. Certiĝu, ke vi kopiis ĝin antaÅ­ ol fermi la fenestron.", "api_key_empty": "La nomo de via API-ŝlosilo ne devus esti malplena", "api_keys": "API-ŝlosiloj", "app_architecture_variant": "Varianto (arkitekturo)", "app_bar_signout_dialog_content": "Ĉu vi certas, ke vi volas elsaluti?", + "app_bar_signout_dialog_ok": "Jes", "app_bar_signout_dialog_title": "Elsaluti", "app_download_links": "Ligiloj por elŝuti la apon", "app_settings": "Agordoj pri apo", @@ -565,19 +546,27 @@ "archive": "ArÄĨivo", "archive_action_prompt": "{count} aldonita(j) al arÄĨivo", "archive_or_unarchive_photo": "EnarÄĨivigi aÅ­ elarÄĨivigi foton", + "archive_page_no_archived_assets": "Neniuj elementoj trovitaj en arÄĨivo", + "archive_page_title": "ArÄĨivigi ({count})", "archive_size": "Grandeco de arÄĨivo", "archive_size_description": "Agordu la grandecon de arÄĨivaj dosieroj por elŝuti (en GiB)", "archived": "EnarÄĨivigita(j)", "archived_count": "{count, plural, one {# enarÄĨivigita} other {# enarÄĨivigitaj}}", "are_these_the_same_person": "Ĉu la sama homo?", "are_you_sure_to_do_this": "Ĉu vi certas, ke vi volas fari tion?", + "array_field_not_fully_supported": "Tablaj kampoj postulas permanan redakton de JSON", + "asset_action_delete_err_read_only": "Ne eblas forigi nurlegajn elementojn, ili estos lasitaj senŝanĝaj", + "asset_action_share_err_offline": "Ne eblis repreni nekonektitajn elementojn, ili estos ignoritaj", "asset_added_to_album": "Aldonita al albumo", "asset_adding_to_album": "Aldonas al albumoâ€Ļ", "asset_created": "Elemento kreita", - "asset_day_count": "{date}: {count, plural, one {# elemento} other {# elementoj}}", "asset_description_updated": "Priskribo de elemento ĝisdatigita", + "asset_filename_is_offline": "Elemento {filename} estas nedisponebla", + "asset_has_unassigned_faces": "Elemento enhavas nekonatajn vizaĝojn", "asset_hashing": "Haketadoâ€Ļ", "asset_list_group_by_sub_title": "Grupigi laÅ­", + "asset_list_layout_settings_dynamic_layout_title": "Dinamika enpaĝigo", + "asset_list_layout_settings_group_automatically": "AÅ­tomata", "asset_list_layout_settings_group_by": "Ĝrupigi elementojn laÅ­", "asset_list_layout_settings_group_by_month_day": "Monato + tago", "asset_list_layout_sub_title": "Enpaĝigo", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Elemento ne trovita en iCloud. Povus esti, ke la kopio ĉe iCloud estas rompita", "asset_offline": "Elemento nedisponebla", "asset_offline_description": "Ne eblis trovi tiun eksteran elementon. Kontaktu la administranton de via Immich por helpo pri tio.", + "asset_restored_successfully": "Elemento sukcese riparita", "asset_skipped": "Preterlasita(j)", "asset_skipped_in_trash": "En rubujo", + "asset_trashed": "Elemento enrubujigita", "asset_troubleshoot": "Problemsolvi pri elemento", "asset_uploaded": "Alŝutita(j)", "asset_uploading": "Alŝutadoâ€Ļ", "asset_viewer_settings_subtitle": "Administri agordojn pri vidilo de galerioj", "asset_viewer_settings_title": "Montrilo de elemento", "assets": "Elementoj", + "assets_added_count": "Sukcese aldonis {count, plural, one {# elementon} other {# elementojn}}", "assets_added_to_album_count": "Sukcese aldonis {count, plural, one {# elementon} other {# elementojn}} al la albumo", - "assets_added_to_album_partial_count": "Aldonis {successCount} el {totalCount} {totalCount, plural, one {elemento} other {elementoj}} al la albumo", "assets_added_to_albums_count": "Sukcese aldonis {assetTotal, plural, one {# elementon} other {# elementojn}} al {albumTotal, plural, one {# albumo} other {# albumoj}}", "assets_cannot_be_added_to_album_count": "Ne eblis aldoni {count, plural, one {tiun elementon} other {tiujn elementojn}} al la albumo", "assets_cannot_be_added_to_albums": "Ne eblis aldoni {count, plural, one {tiun elementon} other {tiujn elementojn}} al iu ajn el la albumoj", "assets_count": "{count, plural, one {# elemento} other {# elementoj}}", + "assets_deleted_permanently": "{count} elemento(j) porĉiam forigita(j)", + "assets_deleted_permanently_from_server": "{count} elemento(j) porĉiam forigita(j) de la Immich-servilo", + "assets_downloaded_failed": "{count, plural, one {Elŝutis # dosieron - dosiero {error} malsukcesis} other {Elŝutis # dosierojn - dosieroj {error} malsukcesis}}", + "assets_downloaded_successfully": "{count, plural, one {Sukcese elŝutis # dosieron} other {Sukcese elŝutis # dosierojn}}", "assets_moved_to_trash_count": "Movis {count, plural, one {# elementon} other {# elementojn}} al la rubujo", "assets_permanently_deleted_count": "Porĉiam forigis {count, plural, one {# elementon} other {# elementojn}}", "assets_removed_count": "Forigis {count, plural, one {# elementon} other {# elementojn}}", + "assets_removed_permanently_from_device": "{count} elemento(j) porĉiam forigita(j) de via aparato", "assets_restore_confirmation": "Ĉu vi certas, ke vi volas restaÅ­ri ĉion el la rubujo? Ne eblos poste malfari tion. Notu, ke ne eblas tiel restaÅ­ri eksterretajn elementojn.", "assets_restored_count": "Sukcese restaÅ­ris {count, plural, one {# elementon} other {# elementojn}}", + "assets_restored_successfully": "{count} elemento(j) sukcese restaÅ­rita(j)", "assets_trashed": "{count} elemento(j) enrubujigita(j)", "assets_trashed_count": "Sukcese enrubujigis {count, plural, one {# elementon} other {# elementojn}}", + "assets_trashed_from_server": "{count} elemento(j) forigitaj de la immich-servilo", "assets_were_part_of_album_count": "{count, plural, one {# Tiu elemento} other {# Tiuj elementoj}} jam estis en la albumo", "assets_were_part_of_albums_count": "{count, plural, one {# Tiu elemento} other {# Tiuj elementoj}} jam estis en la albumoj", "authorized_devices": "Aprobitaj aparatoj", @@ -617,58 +615,111 @@ "autoplay_slideshow": "AÅ­tomate vidigi bildserion", "back": "MalantaÅ­en", "back_close_deselect": "MalantaÅ­en, fermi, aÅ­ malelekti", + "background_backup_running_error": "Savkopiado jam estas fone okazanta, do ne eblas nun lanĉi alian savkopiadon", "background_location_permission": "Rajtigo fone uzi geografian lokon", "background_location_permission_content": "Por ŝanĝi retaliron dum fona funkciado, Immich devas *ĉiam* havi atingorajton al lokiga informo, por povi legi nomojn de vifiaj retoj", "background_options": "Agordoj pri fonaj funkcioj", "backup": "Savkopio", + "backup_album_selection_page_albums_device": "Albumoj en la aparato ({count})", "backup_album_selection_page_albums_tap": "Tuŝeti por inkluzivi, duoble tuŝeti por ekskludi", "backup_album_selection_page_assets_scatter": "Foje elementoj troviĝas disÄĩetitaj al pluraj albumoj, do albumoj povas esti inkluzivitaj aÅ­ ekskluzivitaj de la savkopiado.", "backup_album_selection_page_select_albums": "Elekti albumojn", "backup_album_selection_page_selection_info": "Info pri la elektitaÄĩoj", + "backup_album_selection_page_total_assets": "Nombro da unikaj elementoj", "backup_albums_sync": "Sinkronigo de la savkopioj de albumoj", + "backup_all": "Ĉiuj", + "backup_background_service_backup_failed_message": "Malsukcesis krei savkopion de tiuj elementoj. Reprovanteâ€Ļ", "backup_background_service_complete_notification": "Savkopiado finita", + "backup_background_service_connection_failed_message": "Malsukcesis konektiĝi al la servilo. Reprovanteâ€Ļ", + "backup_background_service_current_upload_notification": "Alŝutiĝas {filename}", "backup_background_service_default_notification": "Serĉas novajn elementojnâ€Ļ", + "backup_background_service_error_title": "Eraro de savkopiado", "backup_background_service_in_progress_notification": "Kreado de savkopio de viaj elementojâ€Ļ", + "backup_background_service_upload_failure_notification": "Malsukcesis alŝuti {filename}", "backup_controller_page_albums": "Savkopiado de albumoj", + "backup_controller_page_background_app_refresh_disabled_content": "Por aktivigi la fonan savkopiadon, ŝaltu fonan aktualigon de la apo en Agordoj > Ĝeneralaj > Fona aktualigo de apo.", + "backup_controller_page_background_app_refresh_disabled_title": "Fona aktualigo de apo malŝaltita", + "backup_controller_page_background_app_refresh_enable_button_text": "Iri al Agordoj", "backup_controller_page_background_battery_info_link": "Montri al mi", "backup_controller_page_background_battery_info_message": "Por bona funkciado de la fona savkopiado, bonvolu malŝalti optimumigon de bateria uzo por Immich.\n\nTiu funkcio dependas de via aparato, do vi devos mem serĉi helpon pri kiel atingi tion.", "backup_controller_page_background_battery_info_ok": "Okej", "backup_controller_page_background_battery_info_title": "Optimumigo de bateria uzo", + "backup_controller_page_background_charging": "Nur dum ŝargado", + "backup_controller_page_background_configure_error": "Malsukcesis agordi la fonan servon", "backup_controller_page_background_delay": "Prokrasti savkopiadon de novaj elementoj: {duration}", + "backup_controller_page_background_description": "Ŝaltu la fonan servon por aÅ­tomate krei savkopion de novaj elementoj sen malfermi la apon", + "backup_controller_page_background_is_off": "AÅ­tomata fona savkopiado estas malŝaltita", + "backup_controller_page_background_is_on": "AÅ­tomata fona savkopiado estas ŝaltita", + "backup_controller_page_background_turn_off": "Malŝalti fonan servon", + "backup_controller_page_background_turn_on": "Ŝalti fonan servon", + "backup_controller_page_background_wifi": "Nur per vifio", "backup_controller_page_backup": "Savkopiado", "backup_controller_page_backup_selected": "Elektita(j): ", "backup_controller_page_backup_sub": "Fotoj kaj videoj kun jama savkopio", + "backup_controller_page_created": "Kreita(j) je: {date}", + "backup_controller_page_desc_backup": "Ŝaltu malfonan savkopiadon por aÅ­tomate alŝuti novajn elementojn al la servilo kiam la apo estas malfermita.", "backup_controller_page_excluded": "Ekskluzivita(j): ", + "backup_controller_page_failed": "Malsukcesis ({count})", + "backup_controller_page_filename": "Dosiernomo: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informoj pri savkopio", "backup_controller_page_none_selected": "Neniuj elektitaj", "backup_controller_page_remainder": "Restas", "backup_controller_page_remainder_sub": "Fotoj kaj videoj ankoraÅ­ ne savkopiitaj el la elektitaj", "backup_controller_page_server_storage": "Stokado ĉe servilo", + "backup_controller_page_start_backup": "Komenci savkopiadon", + "backup_controller_page_status_off": "AÅ­tomata malfona savkopiado estas malŝaltita", + "backup_controller_page_status_on": "AÅ­tomata malfona savkopiado estas ŝaltita", "backup_controller_page_storage_format": "{used} el {total} uzita", "backup_controller_page_to_backup": "Albumoj savkopiotaj", "backup_controller_page_total_sub": "Ĉiuj unikaj fotoj kaj videoj el elektitaj albumoj", + "backup_controller_page_turn_off": "Malŝalti malfonan savkopiadon", + "backup_controller_page_turn_on": "Ŝalti malfonan savkopiadon", + "backup_controller_page_uploading_file_info": "Alŝutiĝas informoj pri dosiero", + "backup_err_only_album": "Ne eblas forigi la solan albumon", "backup_error_sync_failed": "Sinkronigo malsukcesis.", "backup_info_card_assets": "elementoj", + "backup_manual_cancelled": "Nuligita", + "backup_manual_in_progress": "Alŝuto jam progresas. Provu poste", + "backup_manual_success": "Sukceso", + "backup_manual_title": "Statuso de alŝuto", "backup_options": "Agordoj pri savkopioj", + "backup_options_page_title": "Agordoj pri savkopioj", + "backup_setting_subtitle": "Administri agordojn pri fona kaj malfona alŝutado", "backup_settings_subtitle": "Administri agordojn pri alŝutado", + "backup_upload_details_page_more_details": "Tuŝu ĉi tie por pli da detaloj", "backward": "MalantaÅ­en", - "battery_optimization_backup_reliability": "Malŝalti baterian optimumigon povas igi la fonan savkopiadon pli fidinda", "biometric_auth_enabled": "Biometria ensaluto ŝaltita", "biometric_locked_out": "Via biometria ensalutkapablo estas blokita", "biometric_no_options": "Neniuj biometriaj ebloj estas disponeblaj", "biometric_not_available": "Tiu ĉi aparato ne havas funkcion por biometria ensaluto", + "birthdate_saved": "Naskiĝdato ŝukcese konservita", "birthdate_set_description": "La naskiĝdato estas uzita por kalkuli la aĝon de la homo je la momento de iu foto.", "blurred_background": "Malklarigita fono", - "browse_templates": "Foliumi ŝablonojn", "bugs_and_feature_requests": "Cimoj kaj petoj por novaj funkcioj", "build": "Versio", - "build_image": "PakaÄĩo por instali tiun ĉi softvaron", + "build_image": "Bildo de la versio", "bulk_delete_duplicates_confirmation": "Ĉu vi certas, ke vi volas amase forigi {count, plural, one {# duoblaÄĩon} other {# duoblaÄĩojn}}? Tiel, vi konservos la plej grandan elementon el ĉiu grupo kaj porĉiame forigos duoblaÄĩojn. Ne eblas malfari tion!", "bulk_keep_duplicates_confirmation": "Ĉu vi certas, ke vi volas konservi {count, plural, one {# duoblaÄĩon} other {# duoblaÄĩojn}}? Tio solvos ĉiujn duoblajn grupojn sen forigi ion ajn.", "bulk_trash_duplicates_confirmation": "Ĉu vi certas, ke vi volas amase forigi {count, plural, one {# duoblaÄĩon} other {# duoblaÄĩojn}}? Tiel, vi konservos la plej grandan elementon el ĉiu grupo kaj porĉiame forigos duoblaÄĩojn.", "buy": "Aĉeti Immich", + "cache_settings_clear_cache_button": "Forviŝi kaŝmemoron", + "cache_settings_clear_cache_button_title": "Forviŝas la kaŝmemoron de la apo. Tio malrapidigos la apon ĝis kiam ĝi finos rekonstrui la kaŝon.", + "cache_settings_duplicated_assets_clear_button": "FORVIŜI", + "cache_settings_duplicated_assets_subtitle": "Fotoj kaj videoj ignoritaj de la apo", + "cache_settings_duplicated_assets_title": "DuoblaÄĩoj ({count})", + "cache_settings_statistics_album": "Bildetoj de la biblioteko", + "cache_settings_statistics_full": "Plenaj bildoj", + "cache_settings_statistics_shared": "Bildetoj de dividitaj albumoj", + "cache_settings_statistics_thumbnail": "Bildetoj", + "cache_settings_statistics_title": "Uzo de kaŝmemoro", + "cache_settings_subtitle": "Regas la uzadon de kaŝmemoro fare de la apo", + "cache_settings_tile_subtitle": "Regas konduton pri loka stokado", + "cache_settings_tile_title": "Loka stokado", + "cache_settings_title": "Agordoj pri kaŝmemoro", "camera": "Fotilo", "camera_brand": "Fabrikanto de fotilo", - "camera_model": "Tipo de fotilo", + "camera_model": "Modelo de fotilo", "cancel": "Nuligi", "cancel_search": "Nuligi serĉon", "canceled": "Nuligita", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Ne eblas ĝisdatigi la priskribon", "cast": "Elsendi", "cast_description": "Agordi disponeblajn celojn por elsendoj", - "change": "Ŝanĝi", "change_date": "Ŝanĝi daton", "change_description": "Ŝanĝi priskribon", "change_display_order": "Ŝanĝi vicordon de vidigo", + "change_expiration_time": "Ŝanĝi horon de eksvalidiĝo", "change_location": "Ŝanĝi lokon", "change_name": "Ŝanĝi nomon", "change_name_successfully": "Nomo sukcese ŝanĝita", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Pasvortoj ne kongruas", "change_password_form_reenter_new_password": "Re-tajpu novan pasvorton", "change_pin_code": "Ŝanĝi PIN-kodon", + "change_trigger": "Ŝanĝi ekagilon", + "change_trigger_prompt": "Ĉu vi certas, ke vi volas ŝanĝi la ekagilon? Tio forigos ĉiujn ekzistantajn agojn kaj filtrilojn.", "change_your_password": "Ŝanĝi vian pasvorton", "changed_visibility_successfully": "Sukcese ŝanĝis videblecon", "charging": "Ŝargado", "charging_requirement_mobile_backup": "Por fona savkopiado, vi devas konekti la aparaton al ŝargilo", + "check_corrupt_asset_backup": "Kontroli por koruptitaj savkopioj de elementoj", + "check_corrupt_asset_backup_button": "Kontroli", + "check_corrupt_asset_backup_description": "Fari tiun ĉi kontrolon nur per vifio kaj post kiam ĉiuj elementoj havas savkopion. La kontrolo povas daÅ­ri kelkajn minutojn.", "check_logs": "Kontroli protokolojn", "checksum": "Kontrolsumo", - "choose": "Elekti", "choose_matching_people_to_merge": "Elekti duobligitajn homojn por kunfandi", "city": "Urbo", "cleanup_confirm_description": "Immich trovis savkopion en la servilo de {count} elementoj (kreitajn antaÅ­ {date}). Ĉu vi volas forigi la kopiojn de el tiu ĉi aparato?", @@ -722,6 +777,8 @@ "clear_file_cache": "Forviŝi dosier-kaŝon", "clear_message": "Forviŝi mesaĝon", "clear_value": "Forviŝi valoron", + "client_cert_dialog_msg_confirm": "Bone", + "client_cert_enter_password": "Tajpu pasvorton", "client_cert_import": "Importi", "client_cert_import_success_msg": "Atestilo sukcese importita", "client_cert_invalid_msg": "Nevalida atestilo-dosiero, aÅ­ malĝusta pasvorto", @@ -730,10 +787,12 @@ "client_cert_remove_msg": "Klient-atestilo forigita", "client_cert_subtitle": "Nur la formato PKCS12 (.p12, .pfx) estas akceptita. Eblas importi/forigi atestilon nur antaÅ­ ol ensaluti", "client_cert_title": "Klient-atestilo SSL [EKSPERIMENTA]", + "clockwise": "Horloĝdirekte", "close": "Fermi", "collapse": "Maletendi", "collapse_all": "Maletendi ĉiujn", "color": "Koloro", + "color_theme": "Kolor-temo", "command": "Komando", "command_palette_prompt": "Rapide trovi paĝojn, agojn aÅ­ komandojn", "command_palette_to_close": "por fermi", @@ -746,7 +805,6 @@ "comments_are_disabled": "Komentoj estas malebligitaj", "common_create_new_album": "Krei novan albumon", "completed": "Finfarita", - "configuration": "Konfiguro", "confirm": "Konfirmi", "confirm_admin_password": "Konfirmi administran pasvorton", "confirm_delete_face": "Ĉu vi certas ke vi volas forigi la vizaĝon de {name} de tiu elemento?", @@ -761,16 +819,19 @@ "contain": "Alĝustigi", "context": "Kunteksto", "continue": "DaÅ­rigi", - "control_bottom_app_bar_add_tags": "Aldoni etikedojn", + "control_bottom_app_bar_create_new_album": "Krei novan albumon", + "control_bottom_app_bar_delete_from_immich": "Forigi el Immich", "control_bottom_app_bar_delete_from_local": "Forigi el aparato", "control_bottom_app_bar_edit_location": "Redakti lokon", "control_bottom_app_bar_edit_time": "Redakti daton kaj horon", + "control_bottom_app_bar_share_link": "Dividi ligilon", + "control_bottom_app_bar_share_to": "Dividi al", "control_bottom_app_bar_trash_from_immich": "Movi al rubujo", "copied_image_to_clipboard": "Bildo kopiita al tondujo.", "copied_to_clipboard": "Kopiita al tondujo!", "copy_error": "Kopii eraron", + "copy_file_path": "Kopii dosiervojon", "copy_image": "Kopii bildon", - "copy_json": "Kopii JSON-on", "copy_link": "Kopii ligilon", "copy_link_to_clipboard": "Kopii ligilon al tondujo", "copy_password": "Kopii pasvorton", @@ -789,10 +850,8 @@ "create_link_to_share_description": "Permesi, ke iu ajn kun la ligilo povu vidi la elektita(j)n foto(j)n", "create_new": "KREI NOVAN", "create_new_person": "Krei novan homon", - "create_new_person_hint": "Konekti elektitajn elementojn al nova homo", + "create_new_person_hint": "Atribui elektitajn elementojn al nova homo", "create_new_user": "Krei novan uzanton", - "create_person": "Krei homon", - "create_person_subtitle": "Aldoni nomon al la elektita vizaĝo por krei kaj etikedi novan homon", "create_shared_album_page_share_add_assets": "ALDONI ELEMENTOJN", "create_shared_album_page_share_select_photos": "Elekti fotojn", "create_shared_link": "Krei dividitan ligilon", @@ -804,31 +863,37 @@ "created_at": "Kreita", "creating_linked_albums": "Kreado de ligitaj albumoj...", "crop": "Stuci", + "crop_aspect_ratio_fixed": "Fiksita", "crop_aspect_ratio_free": "Libera", "crop_aspect_ratio_original": "Originala", "crop_aspect_ratio_square": "Kvadrata", + "curated_object_page_title": "Objektoj", "current_device": "Aktuala aparato", "current_pin_code": "Aktuala PIN-kodo", "current_server_address": "Aktuala adreso de servilo", "custom_date": "Elekti propran daton", "custom_locale": "Propra lokaÄĩaro", "custom_locale_description": "Prezenti datojn, horojn kaj numerojn laÅ­ la elektita lingvo kaj regiono", + "custom_url": "Propra URL", "cutoff_date_description": "Konservi fotojn el la lastajâ€Ļ", "cutoff_day": "{count, plural, one {tago} other {tagoj}}", "cutoff_year": "{count, plural, one {jaro} other {jaroj}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Malhela", - "dark_theme": "Ŝanĝi al malhela reĝimo", + "dark_theme": "Ŝanĝi al hela reĝimo", "date": "Dato", "date_after": "Dato post", "date_and_time": "Dato kaj horo", "date_before": "Dato antaÅ­", - "date_of_birth": "Naskiĝdato", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Naskiĝdato sukcese registrita", "date_range": "Dato-intervalo", - "date_time_original": "Originalaj dato/horo", "day": "Tago", "days": "Tagoj", "deduplicate_all": "SenduoblaÄĩigi ĉion", + "default_locale": "DefaÅ­lta lokaÄĩaro", + "default_locale_description": "Prezenti datojn kaj numerojn laÅ­ la lokaÄĩaro de via retumilo", "delete": "Forigi", "delete_action_confirmation_message": "Ĉu vi certas, ke vi volas forigi tiun ĉi elementon? Tiu ago movos ĝin al la rubujo ĉe la servilo, kaj demandos ĉu vi volas forigi ĝin de via aparato", "delete_action_prompt": "{count} forigita(j)", @@ -837,6 +902,8 @@ "delete_dialog_alert": "Tiuj elementoj estos porĉiame forigitaj de Immich kaj de via aparato", "delete_dialog_alert_local": "Tiuj ĉi elementoj estos forigitaj de via aparato, sed restos disponeblaj ĉe la servilo de Immich", "delete_dialog_alert_local_non_backed_up": "Kelkaj el tiuj elementoj ne havas savkopion ĉe Immich kaj estos porĉiame forigitaj de via aparato", + "delete_dialog_alert_remote": "Tiuj elementoj estos porĉiame forigitaj de la Immich-servilo", + "delete_dialog_ok_force": "Forigi ĉiuokaze", "delete_dialog_title": "Forigi por ĉiam", "delete_duplicates_confirmation": "Ĉu vi certas, ke vi volas porĉiame forigi tiujn ĉi duoblaÄĩojn?", "delete_face": "Forigi vizaĝon", @@ -855,16 +922,21 @@ "delete_tag_confirmation_prompt": "Ĉu vi certas, ke vi volas forigi la etikedon {tagName}?", "delete_user": "Forigi uzanton", "deleted_shared_link": "Dividita ligilo nun forigita", + "deletes_missing_assets": "Forigas elementojn, kiuj mankas ĉe la disko", "description": "Priskribo", + "description_input_hint_text": "Aldoni priskribon...", + "description_input_submit_error": "Eraro okazis dum ĝisdatigo de priskribo. Kontrolu protokolon por pli da detaloj", "deselect_all": "Malelekti ĉion", "details": "Detaloj", "direction": "Direkto", "disable": "Malebligi", "disabled": "Malebligita", + "disallow_edits": "Malpermesi redaktojn", "discord": "Discord", "discover": "Malkovri", "discovered_devices": "Malkovritaj aparatoj", "dismiss_all_errors": "Ignori ĉiujn erarojn", + "dismiss_error": "Ignori eraron", "display_options": "Vidigi tiajn elementojn", "display_order": "Vicordo de vidigo", "display_original_photos": "Montri originalajn fotojn", @@ -873,9 +945,11 @@ "documentation": "Dokumentaro", "done": "Finite", "download": "Elŝuti", + "download_action_prompt": "Elŝutado de {count} elementoj", "download_canceled": "Elŝuto nuligita", "download_complete": "Elŝuto finita", "download_enqueue": "Elŝuto en atendovico", + "download_error": "Eraro de elŝuto", "download_failed": "Elŝuto malsukcesis", "download_finished": "Elŝuto finiĝis", "download_include_embedded_motion_videos": "Enkorpigitaj videoj", @@ -885,15 +959,15 @@ "download_paused": "Elŝuto paÅ­zita", "download_settings": "Elŝutado", "download_settings_description": "Administri agordojn pri elŝutado de elementoj", + "download_started": "Elŝuto komenciĝis", + "download_sucess": "Elŝuto sukcesis", + "download_sucess_android": "La elemento estas elŝutita al DCIM/Immich", "download_waiting_to_retry": "BaldaÅ­ reprovos elŝuton", "downloading": "Elŝutado", "downloading_asset_filename": "Elŝutado de elemento {filename}", "downloading_from_icloud": "Elŝutado el iCloud", "downloading_media": "Elŝutado de elementoj", - "drag_to_reorder": "Ŝovi por reordigi", "drop_files_to_upload": "Demetu dosierojn ĉi tien por alŝuti", - "duplicate": "Duobligi", - "duplicate_workflow": "Duobligi laborfluon", "duplicates": "DuoblaÄĩoj", "duplicates_description": "Solvu ĉiun grupon indikante tiujn, kiuj estas eventualaj duoblaÄĩoj.", "duration": "DaÅ­ro", @@ -905,7 +979,9 @@ "edit_date_and_time": "Redakti daton kaj horon", "edit_date_and_time_action_prompt": "{count} datoj kaj horoj redaktitaj", "edit_date_and_time_by_offset": "Deŝovi daton", + "edit_date_and_time_by_offset_interval": "Nova intervalo: de {from} ĝis {to}", "edit_description": "Redakti priskribon", + "edit_description_prompt": "Bonvolu elekti novan priskribon:", "edit_exclusion_pattern": "Redakti skemon de ekskludo", "edit_faces": "Redakti vizaĝojn", "edit_key": "Redakti ŝlosilon", @@ -920,6 +996,9 @@ "edit_user": "Redakti uzanton", "edit_workflow": "Redakti laborfluon", "editor": "Redaktilo", + "editor_close_without_save_prompt": "La ŝanĝoj ne konserviĝos", + "editor_close_without_save_title": "Ĉu fermi redaktilon?", + "editor_confirm_reset_all_changes": "Ĉu vi certas, ke vi volas forÄĩeti ĉiujn ŝanĝojn?", "editor_discard_edits_confirm": "ForÄĩeti ŝanĝojn", "editor_discard_edits_prompt": "Vi havas nekonservitajn ŝanĝojn. Ĉu vi certas, ke vi volas forigi ilin?", "editor_discard_edits_title": "ForÄĩeti ŝanĝojn?", @@ -948,7 +1027,9 @@ "enter_your_pin_code": "Tajpu vian PIN-kodon", "enter_your_pin_code_subtitle": "Tajpu vian PIN-kodon por atingi la ŝlositan dosierujon", "error": "Eraro", + "error_change_sort_album": "Malsukcesis ŝanĝi vicordon de album-elementoj", "error_delete_face": "Eraro dum forigo de vizaĝo el elemento", + "error_getting_places": "Eraro dum serĉo de lokoj", "error_loading_albums": "Eraro dum ŝargado de albumoj", "error_loading_image": "Eraro dum ŝargado de bildo", "error_loading_partners": "Eraro dum ŝargado de partneroj: {error}", @@ -962,7 +1043,7 @@ "cannot_navigate_previous_asset": "Ne eblas navigi al antaÅ­a elemento", "cant_apply_changes": "Ne eblas apliki ŝanĝojn", "cant_change_activity": "Ne eblas {enabled, select, true {malŝalti} other {ŝalti}} tiun agon", - "cant_change_asset_favorite": "Ne eblas ŝanĝi preferon por tiu elemento", + "cant_change_asset_favorite": "Ne eblas ŝanĝi preferaton por tiu elemento", "cant_change_metadata_assets_count": "Ne eblas ŝanĝi metadatumojn de {count, plural, one {# elemento} other {# elementoj}}", "cant_get_faces": "Ne eblas trovi vizaĝojn", "cant_get_number_of_comments": "Ne eblas trovi nombron da komentoj", @@ -988,536 +1069,36 @@ "failed_to_remove_product_key": "Malsukcesis forigi var-ŝalosilon", "failed_to_reset_pin_code": "Malsukcesis restarigi PIN-kodon", "failed_to_stack_assets": "Malsukcesis staki elementojn", - "failed_to_tag_assets": "Malsukesis etikedi elementojn", "failed_to_unstack_assets": "Malsukcesis malstaki elementojn", "failed_to_update_notification_status": "Malsukcesis ĝisdatigi statuson de sciigoj", "incorrect_email_or_password": "Neĝusta retadreso aÅ­ pasvorto", "library_folder_already_exists": "Tiu ĉi import-vojo jam ekzistas.", "page_not_found": "Paĝo ne trovita", - "paths_validation_failed": "Evidentiĝis, ke {paths, plural, one {# vojo estas nevalida} other {# vojoj estas nevalidaj}}", - "profile_picture_transparent_pixels": "Ne eblas havi travideblaj bilderoj en profilbildo. Bonvolu zomi kaj/aÅ­ ŝovi la bildon al loko sen tiaj bilderoj.", - "quota_higher_than_disk_size": "Vi donis kvoton pli grandan ol la disko mem", - "something_went_wrong": "Io misis", - "unable_to_add_album_users": "Ne eblas aldoni uzantojn al la albumo", - "unable_to_add_assets_to_shared_link": "Ne eblas aldoni elementojn al la dividita ligilo", - "unable_to_add_comment": "Ne eblas aldoni komenton", "unable_to_add_exclusion_pattern": "Ne eblas aldoni skemon de ekskludo", - "unable_to_add_partners": "Ne eblas aldoni partnerojn", - "unable_to_add_remove_archive": "Ne eblas {archived, select, true {forigi elementon de} other {aldoni elementon al}} la arÄĨivo", - "unable_to_add_remove_favorites": "Ne eblas {favorite, select, true {aldoni elementon al} other {forigi elementon de}} preferataÄĩoj", - "unable_to_archive_unarchive": "Ne eblis {archived, select, true {enarÄĨivigi} other {elarÄĨivigi}}", - "unable_to_change_album_user_role": "Ne eblis ŝanĝi la rolon de la album-uzanto", - "unable_to_change_date": "Ne eblis ŝanĝi daton", - "unable_to_change_description": "Ne eblis ŝanĝi priskribon", - "unable_to_change_favorite": "Ne eblas ŝanĝi preferon por tiu elemento", - "unable_to_change_location": "Ne eblis ŝanĝi lokon", - "unable_to_change_password": "Ne eblis ŝanĝi pasvorton", - "unable_to_change_visibility": "Ne eblis ŝanĝi la videblecon por {count, plural, one {# homo} other {# homoj}}", - "unable_to_complete_oauth_login": "Ne eblis fini la ensaluto per OAuth", - "unable_to_connect": "Ne eblis konektiĝi", - "unable_to_copy_to_clipboard": "Ne eblas kopii al la tondujo; kontrolu ĉu vi konektiĝas per https", "unable_to_create": "Ne eblis krei laborfluon", - "unable_to_create_admin_account": "Ne eblis krei konton de administranto", - "unable_to_create_api_key": "Ne eblis krei novan API-ŝlosilon", - "unable_to_create_library": "Ne eblis krei bibliotekon", - "unable_to_create_user": "Ne eblis krei uzanton", - "unable_to_delete_album": "Ne eblis forigi albumon", - "unable_to_delete_asset": "Ne eblis forigi elementon", - "unable_to_delete_assets": "Eraro okazis dum forigo de elementoj", "unable_to_delete_exclusion_pattern": "Ne eblas forigi skemon de ekskludo", - "unable_to_delete_shared_link": "Ne eblis forigi dividitan ligilon", - "unable_to_delete_user": "Ne eblis forigi uzanton", "unable_to_delete_workflow": "Ne eblis forigi laborfluon", - "unable_to_download_files": "Ne eblis elŝuti dosierojn", "unable_to_edit_exclusion_pattern": "Ne eblas redakti skemon de ekskludo", - "unable_to_empty_trash": "Ne eblis malplenigi rubujon", - "unable_to_enter_fullscreen": "Ne eblis ŝalti plenekranan reĝimon", - "unable_to_exit_fullscreen": "Ne eblis eliĝi el plenekrana reĝimo", - "unable_to_get_comments_number": "Ne eblis trovi nombron da komentoj", - "unable_to_get_shared_link": "Ne eblis ekhavi dividitan ligilon", - "unable_to_hide_person": "Ne eblis kaŝi tiun homon", - "unable_to_link_motion_video": "Ne eblis ligi videon", - "unable_to_link_oauth_account": "Ne eblis ligi al OAuth-konto", - "unable_to_log_out_all_devices": "Ne eblis elsaluti el ĉiuj aparatoj", - "unable_to_log_out_device": "Ne eblis elsaluti el tiu aparato", - "unable_to_login_with_oauth": "Ne eblis ensaluti per OAuth", - "unable_to_play_video": "Ne eblis ludi videon", - "unable_to_reassign_assets_existing_person": "Ne eblis rekonekti elementojn al {name, select, null {ekzistanta homo} other {{name}}}", - "unable_to_reassign_assets_new_person": "Ne eblis konekti elementojn al nova homo", - "unable_to_refresh_user": "Ne eblis renovigi uzanton", - "unable_to_remove_album_users": "Ne eblis forigi uzantojn de albumo", - "unable_to_remove_api_key": "Ne eblis forigi API-ŝlosilo", - "unable_to_remove_assets_from_shared_link": "Ne eblis forigi elementojn el dividita ligilo", - "unable_to_remove_library": "Ne eblis forigi bibliotekon", - "unable_to_remove_partner": "Ne eblis forigi partneron", - "unable_to_remove_reaction": "Ne eblis forigi reagon", - "unable_to_reset_password": "Ne eblis restarigi pasvorton", - "unable_to_reset_pin_code": "Ne eblis restarigi PIN-kodon", - "unable_to_resolve_duplicate": "Ne eblis solvi tiun ĉi duoblaÄĩon", - "unable_to_restore_assets": "Ne eblis restaÅ­ri elementojn", - "unable_to_restore_trash": "Ne eblis restaÅ­ri rubaÄĩojn", - "unable_to_restore_user": "Ne eblis restaÅ­ri uzanton", - "unable_to_save_album": "Ne eblis konservi albumon", - "unable_to_save_api_key": "Ne eblis konservi API-ŝlosilon", - "unable_to_save_date_of_birth": "Ne eblis konservi naskiĝdaton", - "unable_to_save_name": "Ne eblis konservi nomon", - "unable_to_save_profile": "Ne eblis konservi profilon", - "unable_to_save_settings": "Ne eblis konservi agordojn", "unable_to_scan_libraries": "Ne eblas analizi biblitekojn", "unable_to_scan_library": "Ne eblas analizi biblitekon", - "unable_to_set_feature_photo": "Ne eblis agordi ĉap-foton", - "unable_to_set_profile_picture": "Ne eblis agordi profilbildon", - "unable_to_set_rating": "Ne eblis konservi pritakson", - "unable_to_submit_job": "Ne eblis plenumi taskon", - "unable_to_trash_asset": "Ne eblis forigi elementon", - "unable_to_unlink_account": "Ne eblis malligi konton", - "unable_to_unlink_motion_video": "Ne eblis malligi movbildon", - "unable_to_update_album_cover": "Ne eblis ŝanĝi kovrilon de albumo", - "unable_to_update_album_info": "Ne eblis ŝanĝi informojn pri albumo", - "unable_to_update_library": "Ne eblis ĝisdatigi bibliotekon", - "unable_to_update_location": "Ne eblis ŝanĝi lokon", - "unable_to_update_settings": "Ne eblis ĝisdatigi agordojn", - "unable_to_update_timeline_display_status": "Ne eblis ŝanĝi tiun agordon por la partnero", - "unable_to_update_user": "Ne eblis ĝisdatigi uzanton", - "unable_to_update_workflow": "Ne eblis ĝisdatigi laborfluon", - "unable_to_upload_file": "Ne eblis alŝuti dosieron" + "unable_to_update_workflow": "Ne eblis ĝisdatigi laborfluon" }, - "errors_text": "Eraroj", "exclusion_pattern": "Skemo de ekskludo", - "exif": "Exif", - "exif_bottom_sheet_description": "Aldoni priskribon...", - "exif_bottom_sheet_description_error": "Eraro dum ĝisdatigo de priskribo", - "exif_bottom_sheet_no_description": "Neniu priskribo", - "exit_slideshow": "Fini bildserion", "expand": "Etendi", "expand_all": "Etendi ĉiujn", - "expire_after": "Eksvalidigi post", - "expired": "Eksvalida", - "expires_date": "Eksvalidiĝos je {date}", "explore": "Esplori", "explorer": "Foliumilo", - "export": "Eksporti", - "export_as_json": "Eksporti kiel JSON", - "export_database": "Eksporti datumbazon", - "export_database_description": "Eksporti la SQLite-datumbazon", - "exposure_time": "Ekspono-tempo", - "extension": "Sufikso", - "external": "Ekstera", - "external_libraries": "Eksteraj bibliotekoj", - "external_network": "Ekstera reto", - "external_network_sheet_info": "Kiam la preferata vifio estas netrovebla, la apo provos konektiĝi al la servilo per la ĉi-suba listo de URL-oj, ekde la unua", - "f_number": "F-numero", - "face_unassigned": "Ne konektitaj", - "failed": "Malsukcesis", - "failed_count": "Malsukcesis: {count}", - "failed_to_authenticate": "Malsukcesis ensaluti", - "failed_to_load_assets": "Malsukcesis ŝargi elementojn", - "failed_to_load_folder": "Malsukcesis ŝargi dosierujon", - "favorite": "PreferataÄĩo", - "favorite_action_prompt": "{count} aldonita(j) al PreferataÄĩoj", - "favorite_or_unfavorite_photo": "Aldoni/forigi foton al/de preferataÄĩoj", - "favorites": "PreferataÄĩoj", - "feature_photo_updated": "Ĉap-foto sukcese ŝanĝita", - "features": "Funkcioj", - "features_setting_description": "Administri funkciojn de la apo", - "file_name_or_extension": "Dosiernomo aÅ­ sufikso", - "file_name_text": "Dosiernomo", - "file_size": "Grandeco de dosiero", - "filename": "Dosiernomo", - "filetype": "Tipo de dosiero", - "filter": "Filtri", - "filter_people": "Filtri laÅ­ homoj", - "filter_places": "Filtri laÅ­ lokoj", - "filter_tags": "Filtri laÅ­ etikedoj", - "filters": "Filtriloj", - "first": "Unua", - "fix_incorrect_match": "Korekti malĝustan kongruiĝon", - "focal_length": "Interfokusa distanco", - "folder": "Dosierujo", - "folder_not_found": "Dosierujo netrovita", - "folders": "Dosierujoj", - "folders_feature_description": "Foliumado en dosierujoj por la fotoj kaj videoj en la sistemo", - "forgot_pin_code_question": "Ĉu vi forgesis vian PIN-kodon?", - "forward": "AntaÅ­en", "free_up_space": "Liberigi spacon", "free_up_space_description": "Vi forigos fotojn kaj/aÅ­ videojn, kiuj havas savkopiojn en la servilo, por liberigi spacon en via aparato. La kopioj en la servilo restos.", - "free_up_space_settings_subtitle": "Liberigi spacon en aparato", - "full_path": "Plena dosiervojo: {path}", - "full_path_or_folder": "Plena vojo aÅ­ dosierujo", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Tiu ĉi funkcio ŝargas eksterajn rimedojn de Google por sia funkciado.", "general": "Ĝeneralaj", - "geolocation_instruction_location": "Elektu elementon kun GPS-koordinatoj por uzi ĝian lokon, aÅ­ elektu lokon rekte sur la mapo", - "get_help": "Trovi helpon", - "get_people_error": "Eraro dum serĉo de homoj", - "get_wifiname_error": "Ne eblis trovi nomon de vifio. Kontrolu, ke vi donis bezonatan permeson, kaj estas konektita al vifia reto", - "getting_started": "Unuaj paŝoj", - "go_back": "Reiri", - "go_to_folder": "Vidigi dosierujon", - "go_to_search": "Serĉilo", - "gps": "GPS", - "gps_missing": "Neniu GPS", - "grant_permission": "Doni permeson", - "group_albums_by": "Grupigi albumojn laÅ­...", - "group_country": "Grupigi laÅ­ lando", - "group_no": "Neniu grupigo", - "group_owner": "Grupigi laÅ­ posedanto", - "group_places_by": "Grupigi lokojn laÅ­...", - "group_year": "Grupigi laÅ­ jaro", - "haptic_feedback_title": "Tuŝ-retrokuplo", - "has_quota": "Kvoto", - "hash_asset": "Haketi elementon", - "hashed_assets": "Haketitaj elementoj", - "hashing": "Haketado", - "header_settings_add_header_tip": "Aldoni kapon", - "header_settings_field_validator_msg": "Ne eblas lasi tion malplena", - "header_settings_header_name_input": "Nomo de kapo", - "header_settings_header_value_input": "Valoro de kapo", - "headers_settings_tile_title": "Propraj kapoj de prokurilo", - "height": "Alto", - "hi_user": "Saluton {name} ({email})", - "hide_all_people": "Kaŝi ĉiujn homojn", - "hide_gallery": "Kaŝi galerion", - "hide_named_person": "Kaŝi homon {name}", - "hide_password": "Kaŝi pasvorton", - "hide_person": "Kaŝi homon", - "hide_schema": "Kaŝi skemon", - "hide_text_recognition": "Kaŝi rekonadon de teksto", - "hide_unnamed_people": "Kaŝi nenomitajn homojn", - "home_page_building_timeline": "Konstruado de tempolinio", - "host": "Gastiganto", - "hour": "Horo", - "hours": "Horoj", - "id": "ID", - "idle": "Senokupa", - "image": "Bildo", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} farita je {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} farita kun {person1} je {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} farita kun {person1} kaj {person2} je {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} farita kun {person1}, {person2}, kaj {person3} je {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} farita kun {person1}, {person2}, kaj {additionalCount, number} aliaj homoj je {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} farita en {city}, {country} je {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} farita en {city}, {country} kun {person1} je {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} farita en {city}, {country} kun {person1} kaj {person2} je {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} farita en {city}, {country} kun {person1}, {person2} kaj {person3} je {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} farita en {city}, {country} kun {person1}, {person2}, kaj {additionalCount, number} aliaj homoj je {date}", - "immich_logo": "Bildsimbolo de Immich", - "immich_web_interface": "Interfaco de Immich", - "import_from_json": "Importi el JSON-dosiero", - "in_albums": "En {count, plural, one {# albumo} other {# albumoj}}", - "in_archive": "En arÄĨivo", - "in_year": "En {year}", - "in_year_selector": "En", - "include_archived": "Inkluzivi elementojn el arÄĨivo", - "include_shared_albums": "Inkluzivi dividitajn albumojn", - "include_shared_partner_assets": "Inkluzivi elementojn dividitajn de partnero", - "individual_share": "Divido de unuopa elemento", - "individual_shares": "Dividoj de unuopaj elementoj", - "info": "Informoj", - "interval": { - "day_at_onepm": "Ĉiutage je 13:00", - "hours": "{hours, plural, one {ĉiuhore} other {ĉiujn {hours, number} horojn}}", - "night_at_midnight": "Ĉiunokte je noktomezo", - "night_at_twoam": "Ĉiunokte je 02:00" - }, - "invalid_date": "Nevalida dato", - "invalid_date_format": "Nevalida dat-formato", - "invite_people": "Inviti homojn", - "invite_to_album": "Inviti al albumo", - "iso": "ISO", - "items_count": "{count, plural, one {# elemento} other {# elementoj}}", - "jobs": "Taskoj", - "keep": "Konservi", - "keep_albums": "Konservi albumojn", - "keep_albums_count": "Konserviĝas: {count} {count, plural, one {albumo} other {albumoj}}", - "keep_all": "Konservi ĉion", - "keep_description": "Elektu tion, kio konserviĝos en via aparato kiam vi liberigas spacon.", - "keep_favorites": "Konservi preferataÄĩojn", - "keep_on_device": "Konservi en aparato", - "keep_on_device_hint": "Elektu elementojn por konservi en tiu ĉi aparato", - "keep_this_delete_others": "Konservi tion ĉi, forigi aliajn", - "keeping": "Konserviĝas: {items}", - "kept_this_deleted_others": "Konservi tiun ĉi elementon kune kun {count, plural, one {# forigita elemento} other {# forigitaj elementoj}}", - "keyboard_shortcuts": "Fulmoklavoj", - "language": "Lingvo", - "language_no_results_subtitle": "Provu serĉi denove per aliaj vortoj", - "language_no_results_title": "Neniuj lingvoj trovitaj", - "language_search_hint": "Serĉi lingvojn...", - "language_setting_description": "Elektu preferatan lingvon", - "large_files": "Grandaj dosieroj", - "last": "Lasta", - "last_months": "{count, plural, one {Lasta monato} other {Lastaj # monatoj}}", - "last_seen": "Lastfoje vidita", - "latest_version": "Lasta versio", - "latitude": "Latitudo", - "leave_album": "Foriri el albumo", - "lens_model": "Tipo de objektivo", - "less": "Malpli", - "let_others_respond": "Permesi ke aliaj homoj respondu", - "level": "Nivelo", - "library": "Bibliteko", - "library_add_folder": "Aldoni dosierujon", - "library_edit_folder": "Redakti dosierujon", - "library_page_sort_asset_count": "Nombro da elementoj", - "library_page_sort_created": "Dato de kreo", - "library_page_sort_last_modified": "Laste modifita", - "library_page_sort_title": "Album-titolo", - "licenses": "Permesiloj", - "light": "Hela", - "light_theme": "Ŝanĝi al hela reĝimo", - "like": "Mi ŝatas", - "like_deleted": "Reago \"mi ŝatas\" forigita", - "link": "Ligilo", - "link_motion_video": "Ligi videon", - "link_to_docs": "Por pli da informoj, vidu la dokumentaron.", - "link_to_oauth": "Ligi al OAuth", - "linked_oauth_account": "Ligita OAuth-konto", - "list": "Listo", - "loading": "Ŝargado", - "loading_search_results_failed": "Malsukcesis ŝargi rezultojn de serĉo", - "local": "Loka", - "local_asset_cast_failed": "Ne eblas elsendi elementon, kiu ne troviĝas en la servilo", - "local_assets": "Lokaj elementoj", - "local_id": "Loka ID", - "local_media_summary": "Resumo de lokaj elementoj", - "local_network": "Loka reto", - "local_network_sheet_info": "La apo uzas tiun ĉi URL-on por konekti al la servilo kiam ĝi estas konektita al la nomita vifia reto", - "location": "Loko", - "location_permission": "Permeso pri lokado", - "location_permission_content": "Por povi aÅ­tomate uzi la ĝustan URL-on, Immich bezonas permeson uzi precizan lokon, por ke ĝi povu vidi la nomon de la aktuala vifia reto", - "location_picker_choose_on_map": "Elekti per mapo", - "location_picker_latitude_error": "Tajpu validan latitudon", - "location_picker_latitude_hint": "Tajpu vian latitudon ĉi tie", - "location_picker_longitude_error": "Tajpu validan longitudon", - "location_picker_longitude_hint": "Tajpu vian longitudon ĉi tie", - "lock": "Ŝlosi", - "locked_folder": "Ŝlosita dosierujo", - "log_detail_title": "Detala protokolado", - "log_out": "Elsaluti", - "log_out_all_devices": "Elsalutigi ĉiujn aparatojn", - "logged_in_as": "Ensalutita kiel {user}", - "logged_out_all_devices": "Ĉiuj aparatoj elsalutigitaj", - "logged_out_device": "Elsalutigita aparato", - "login": "Ensaluti", - "login_disabled": "Ensalutado malebligita", - "login_form_api_exception": "Eraro de API. Bonvolu kontroli la URL-on de la servilo, kaj reprovi.", - "login_form_email_hint": "via_adreso@example.com", - "login_form_endpoint_hint": "http://ip-adreso-de-via-servilo:porda_numero", - "login_form_endpoint_url": "URL de finpunkto de servilo", - "login_form_err_invalid_email": "Nevalida retadreso", - "login_form_err_invalid_url": "Nevalida URL-o", - "login_form_err_leading_whitespace": "AntaÅ­a blankspaco", - "login_form_err_trailing_whitespace": "Posta blankspaco", - "login_form_failed_get_oauth_server_config": "Eraro de protokolado kun OAuth, bonvolu kontroli la URL-on de la servilo", - "login_form_failed_get_oauth_server_disable": "La OAuth-funkcio estas nedisponebla ĉe tiu ĉi servilo", - "login_form_failed_login": "Eraro dum ensaluto, bonvolu kontroli la URL-on de la servilo, la retadreson kaj pasvorton", - "login_form_handshake_exception": "Okazis eraro de kvitanco (\"handshake\") kun la servilo. Ŝaltu la agordaÄĩon por memsubskribitaj atestiloj, se tian vi uzas.", - "login_form_password_hint": "pasvorto", - "login_form_server_empty": "Tajpu la URL-on de la servilo.", - "login_form_server_error": "Ne eblis konektiĝi al la servilo.", - "login_has_been_disabled": "Ensalutado malebligita.", - "login_password_changed_error": "Okazis eraro dum ŝanĝo de via pasvorto", - "login_password_changed_success": "Pasvorto sukcese ŝanĝita", - "logout_all_device_confirmation": "Ĉu vi certas, ke vi volas elsalutigi ĉiujn aparatojn?", - "logout_this_device_confirmation": "Ĉu vi certas, ke vi volas elsalutigi tiun ĉi aparaton?", - "logs": "Protokoloj", - "longitude": "Longitudo", - "look": "Aspekto", - "loop_videos": "AÅ­tomate ripeti videojn", - "loop_videos_description": "Ŝalti por aÅ­tomate rekomenci videon post la fino en la detala montrilo.", - "main_branch_warning": "Vi uzas prilaboratan version; ni forte rekomendas eldonitan version!", - "main_menu": "Ĉefmenuo", - "maintenance_action_restore": "RestaÅ­rado de datumbazo", - "maintenance_description": "Immich estas aktuale en funkcitena reĝimo.", - "maintenance_end": "Malŝalti funkcitenan reĝimon", - "maintenance_logged_in_as": "Aktuale ensalutita kiel {user}", - "maintenance_restore_from_backup": "RestaÅ­ri el savkopio", - "maintenance_restore_library": "RestaÅ­ri vian bibliotekon", - "maintenance_restore_library_confirm": "Se tio ŝajne bona, daÅ­rigu kaj restaÅ­ru savkopion!", - "maintenance_restore_library_description": "RestaÅ­rado de datumbazo", - "maintenance_restore_library_folder_has_files": "{folder} enhavas {count} dosiero(j)n", - "maintenance_restore_library_folder_no_files": "{folder} ne enhavas ajnan dosieron!", - "maintenance_restore_library_folder_pass": "legebla(j) kaj skribebla(j)", - "maintenance_restore_library_folder_read_fail": "nelegebla(j)", - "maintenance_restore_library_folder_write_fail": "ne skribebla(j)", - "maintenance_restore_library_hint_missing_files": "Povus esti, ke mankas gravaj dosieroj", - "maintenance_restore_library_hint_regenerate_later": "Eblas poste regeneri ilin en Agordoj", - "maintenance_restore_library_hint_storage_template_missing_files": "Ĉu vi uzas ŝablonojn de stokado? Povus esti, ke mankas dosieroj", - "maintenance_restore_library_loading": "Ŝargiĝas heÅ­ristikoj kaj kontroloj de integrecoâ€Ļ", - "maintenance_task_backup": "Kreado de savkopio de ekzistanta datumbazoâ€Ļ", - "maintenance_task_migrations": "Ruliĝas migrado de datumbazoâ€Ļ", - "maintenance_task_restore": "RestaÅ­rado de elektita savkopioâ€Ļ", - "maintenance_task_rollback": "Malsukcesis la restaÅ­rado; malfariĝas la ŝanĝojâ€Ļ", - "maintenance_title": "Dumtempe nedisponebla", - "make": "Fabrikanto", - "manage_geolocation": "Administri lokojn", - "manage_media_access_rationale": "Tiu ĉi permeso estas bezonata por sukcesa movo de elementojn al kaj el la rubujo.", "manage_media_access_settings": "Malfermi agordaÄĩaron", - "manage_media_access_subtitle": "Permesi al la Immich apo administri kaj movi fotojn kaj videojn.", - "manage_media_access_title": "Administri atingon al aÅ­dvidaÄĩoj", - "manage_sharing_with_partners": "Administri dividadon kun partneroj", "manage_the_app_settings": "Agordi la apon", - "manage_your_account": "Administri vian konton", - "manage_your_api_keys": "Administri viajn API-ŝlosilojn", - "manage_your_devices": "Administri viajn ensalutitajn aparatojn", - "manage_your_oauth_connection": "Administri OAuth-konekton", - "map": "Mapo", - "map_cannot_get_user_location": "Ne eblas trovi lokon de uzanto", - "map_location_picker_page_use_location": "Uzi tiun ĉi lokon", - "map_location_service_disabled_content": "Vi devas ŝalti la lokadan servon de via aparato por vidi elementojn de via aktuala loko. Ĉu vi volas nun ŝalti tion?", - "map_location_service_disabled_title": "Servo de lokado malŝaltita", - "map_marker_with_image": "Map-markilo kun bildo", - "map_no_location_permission_content": "Vi devas permesi al la apo detekti vian aktualan lokon por vidi tieajn elementojn. Ĉu vi volas nun permesi tion?", - "map_no_location_permission_title": "Detektado de loko ne permesita", - "map_settings": "Agordoj pri mapo", - "map_settings_dark_mode": "Malhela reĝimo", - "map_settings_date_range_option_day": "Lastaj 24 horoj", - "map_settings_date_range_option_days": "Lastaj {days} tagoj", - "map_settings_date_range_option_year": "Lasta jaro", - "map_settings_date_range_option_years": "Lastaj {years} jaroj", - "map_settings_include_show_archived": "Inkluzivi la arÄĨivon", - "map_settings_include_show_partners": "Inkluzivi partnerojn", - "map_settings_only_show_favorites": "Montri nur preferataÄĩojn", - "map_settings_theme_settings": "Temo de la mapo", - "mark_all_as_read": "Marki ĉiujn kiel legitaj", - "marked_all_as_read": "Ĉiuj markitaj kiel legitaj", - "matches": "Kongruoj", - "matching_assets": "Kongruaj elementoj", - "media_chrome": { - "auto": "AÅ­tomate", - "captions": "Subtekstoj", - "captions_off": "Malŝaltitaj", - "closed_captions": "subtekstoj", - "decode_error": "Eraro de malĉifrado", - "disable_captions": "Malŝalti subtekstojn", - "enable_captions": "Ŝalti subtekstojn", - "enter_fullscreen_mode": "Plen-ekrana reĝimo", - "exit_fullscreen_mode": "Malŝalti plen-ekranan reĝimon", - "loop": "Ripeti post fino", - "media_error_description": "Eraro haltigis ludadon. Eble la dosiero estas koruptita, aÅ­ eble ĝia formato konfuzas vian retumilon.", - "media_loading": "ŝargado", - "mute": "Silentigi", - "network_error": "Eraro de reto", - "network_error_description": "Reta eraro haltigis la elŝuton.", - "not_supported_error": "Fonto ne subtenita", - "playback_rate": "Rapido de ludado", - "playback_rate_current": "aktuala rapido de ludado", - "playback_rate_value": "Rapido de ludado {playbackRate}", - "playback_time": "tempo de ludado", - "quality": "Kvalito", - "second": "sekundo", - "seconds": "sekundoj", - "time_value_of_total_time": "{currentTime} el {totalTime}", - "time_value_remaining": "restas {time}", - "unmute": "Malsilentigi", - "unsupported_error_description": "Okazis nekonata eraro. Eble la servilo aÅ­ reto misfunkcias, aÅ­ la formato konfuzis vian retumilon.", - "video_not_loaded_unknown_time": "video ne ŝargita, nekonata tempo.", - "video_player": "video-legilo", - "volume": "laÅ­teco" - }, - "media_type": "Typo de elemento", - "memories": "MemoraÄĩoj", - "memories_all_caught_up": "Vi estas ĝisdatigita", - "memories_check_back_tomorrow": "Revenu morgaÅ­ por pli da memoraÄĩoj", - "memories_setting_description": "Administri tion, kio aperas en viaj memoraÄĩoj", - "memories_start_over": "Rekomenci", - "memories_swipe_to_close": "Ŝovumi supren por fermi", - "memory": "MemoraÄĩo", - "memory_lane_title": "Viaj memoraÄĩoj {title}", - "menu": "Menuo", - "merge": "Kunfandi", - "merge_people": "Kunfandi homojn", - "merge_people_limit": "Eblas kunfandi nur 5 vizaĝojn samtempe", - "merge_people_prompt": "Ĉu vi volas kunfandi tiujn homojn? Ne eblos malfari tion poste.", - "merge_people_successfully": "Homoj sukcese kunfanditaj", - "merged_people_count": "Kunfandiĝis {count, plural, one {# homo} other {# homoj}}", - "minFaces": "Minimumaj vizaĝoj", - "minFaces_description": "La minimuma nombro da rekonataj vizaĝoj por videbligi homon", - "minimize": "Minimumigi", - "minute": "Minuto", - "minutes": "Minutoj", "missing": "Netraktitaj", - "mobile_app": "Apo por poŝtelefono", - "mobile_app_download_onboarding_note": "Elŝutu la poŝtelefonan apon de Immich per tiuj ebloj", - "model": "Modelo", - "modify_date": "Ŝanĝi la daton", - "month": "Monato", - "more": "Pli", - "motion": "Movo", - "move": "Movi", - "move_off_locked_folder": "Eligi el ŝlosita dosierujo", - "move_to": "Movi al", - "move_to_device_trash": "Movi al rubujon de via aparato", - "move_to_lock_folder_action_prompt": "{count} aldonita(j) al la ŝlosita dosierujo", - "move_to_locked_folder": "Movi al ŝlosita dosierujo", - "move_to_locked_folder_confirmation": "Tiuj fotoj/videoj estos forigitaj de ĉiuj albumoj, kaj estos videblaj nur en la ŝlosita dosierujo", - "moved_to_trash": "Movita al rubujo", - "mute_memories": "Silentigi memoraÄĩojn", - "my_albums": "Miaj albumoj", - "my_immich_description": "Kopii aktualan paĝon kiel ligilon de Mia Immich", - "my_immich_title": "Ligilo al Mia Immich", - "name": "Nomo", - "name_or_nickname": "Nomo aÅ­ kromnomo", - "name_required": "Nomo estas bezonata", - "navigate": "Navigi", - "navigate_to_time": "Navigi al dato/horo", - "network_requirement_photos_upload": "Uzi datumojn de poŝtelefona reto por savkopii fotojn", - "network_requirement_videos_upload": "Uzi datumojn de poŝtelefona reto por savkopii videojn", - "network_requirements": "Retaj postuloj", - "network_requirements_updated": "Retaj postuloj ŝanĝiĝis, savkopia vico restarigita", - "networking_settings": "Retkonektoj", "networking_subtitle": "Administri agordojn pri finpunktoj de la servilo", - "never": "Neniam", - "new_album": "Nova albumo", - "new_api_key": "Nova API-ŝlosilo", - "new_password": "Nova pasvorto", - "new_person": "Nova homo", - "new_pin_code": "Nova PIN-kodo", - "new_pin_code_subtitle": "Jen via unua vizito al la ŝlosita dosierujo. Kreu PIN-kodon por sekurigi ĝin", - "new_update": "Nova ĝisdatigo", - "new_user_created": "Nova uzanto kreita", - "new_version_available": "NOVA VERSIO DISPONEBLA", - "newest_first": "Plej novaj unue", - "next": "Sekva", - "next_memory": "Sekva memoraÄĩo", - "no": "Ne", - "no_albums_found": "Neniuj albumoj trovitaj", - "no_albums_message": "Krei albumon por organizi viajn fotojn kaj videojn", - "no_albums_with_name_yet": "Ŝajne vi ankoraÅ­ ne havas albumon kun tiu nomo.", - "no_albums_yet": "Ŝajne vi ankoraÅ­ ne havas albumojn.", - "no_archived_assets_message": "EnarÄĨivigi fotojn kaj videojn por kaŝi ilin en la biblioteko", - "no_assets_message": "Klaku por alŝuti vian unuan foton", - "no_assets_to_show": "Neniuj elementoj por montri", - "no_cast_devices_found": "Neniu aparato trovita por la elsendo", - "no_checksum_local": "Neniu kontrolsumo disponebla – ne eblas ŝargi lokajn elementojn", - "no_checksum_remote": "Neniu kontrolsumo disponebla – ne eblas ŝargi foran elementon", "no_devices": "Neniuj aprobitaj aparatoj", - "no_duplicates_found": "Neniuj duoblaÄĩoj trovitaj.", - "no_exif_info_available": "Neniuj exif-datumoj disponeblaj", "no_explore_results_message": "Alŝutu pli da fotoj por esplori vian kolekton.", - "no_favorites_message": "Aldoni al PreferataÄĩoj por rapide retrovi viajn plej bonajn bildojn kaj videojn", - "no_libraries_message": "Krei eksteran biblitekon por vidi viajn fotojn kaj videojn", - "no_local_assets_found": "Neniu loka elemento trovita kun tiu kontrolsumo", - "no_locked_photos_message": "Fotoj kaj videoj en la ŝlosita dosierujo estas kaŝitaj, kaj ne montriĝos dum vi foliumas aÅ­ serĉas en via biblioteko.", - "no_name": "Neniu nomo", "no_notifications": "Neniuj sciigoj", - "no_people_found": "Neniuj kongruaj homoj trovitaj", - "no_places": "Neniuj lokoj", - "no_remote_assets_found": "Neniu fora elemento trovita kun tiu kontrolsumo", - "no_results": "Neniuj rezultoj", "no_results_description": "Provu sinonimon aÅ­ pli ĝeneralan ŝlosilvorton", - "no_shared_albums_message": "Krei albumon por dividi fotojn kaj videojn kun konatoj", - "no_steps": "Neniuj paŝoj aldonitaj ĝis nun", - "none": "Neniu", - "not_allowed": "Ne permesita", - "not_available": "nedisponebla", - "not_in_any_album": "Ne en ajna albumo", - "not_selected": "Ne elektita", - "notes": "Notoj", - "nothing_here_yet": "AnkoraÅ­ nenio ĉi tie", - "notification_backup_reliability": "Ŝalti atentigilojn por igi pli fidinda la fonan savkopiadon", - "notification_enabled_list_tile_open_button": "Malfermi agordojn", - "notification_enabled_list_tile_title": "Atentigoj ŝaltitaj", "notification_permission_dialog_content": "Por ŝalti sciigojn, iru al Agordoj kaj elektu 'permesi'.", "notification_permission_list_tile_content": "Donu permeson por ŝalti sciigojn.", "notification_permission_list_tile_enable_button": "Ŝalti sciigojn", @@ -1525,533 +1106,33 @@ "notification_toggle_setting_description": "Ŝalti sciigojn per retmesaĝo", "notifications": "Sciigoj", "notifications_setting_description": "Administri sciigojn", - "oauth": "OAuth", - "obtainium_configurator": "Konfigurilo pri Obtainium", - "obtainium_configurator_instructions": "Uzu Obtainium por instali kaj ĝisdatigi la apon por Android rekte el github. Kreu API-ŝlosilon kaj elektu variaÄĩon por krei ligilon por konfiguri Obtainium", - "ocr": "Optika signo-rekono", - "ocr_body": "Immich nun legas tekston en viaj fotoj, do vi povas serĉi kaj trovi fotojn laÅ­ tio.", - "ocr_title": "Serĉi tekston en fotoj", - "official_immich_resources": "Oficialaj rimedoj por Immich", - "offline": "Nekonektita", - "offset": "Deŝovo", - "ok": "Bone", - "oldest_first": "Plej malnovaj unue", - "on_this_device": "Ĉe tiu ĉi aparato", - "onboarding": "Bonvenigo", - "onboarding_locale_description": "Elektu vian preferatan lingvon. Vi povas ŝanĝi tion post per la agordaro.", - "onboarding_privacy_description": "La ĉi-subaj (nedevigaj) funkcioj dependas de eksteraj servoj. Vi povas (mal)ŝalti ilin iam ajn.", - "onboarding_server_welcome_description": "Ni lanĉu vian instancon kun plej oftaj agordaÄĩoj.", - "onboarding_theme_description": "Elektu kolor-temon por via instanco. Vi povas ŝanĝi tion poste per la agordaro.", - "onboarding_user_welcome_description": "Ni komencu!", - "onboarding_welcome_user": "Bonvenon, {user}", - "online": "Konektita", - "only_favorites": "Nur preferataÄĩoj", - "open": "Malfermi", - "open_calendar": "Malfermi kalendaron", - "open_in_browser": "Malfermi en retumilo", - "open_in_immich_body": "Agordi Immich kiel galerion en Android-aparato por ke fotoj malfermiĝu rekte de aliaj apoj en Immich.", - "open_in_immich_title": "Malfermi fotojn en Immich", - "open_in_map_view": "Montri sur mapo", - "open_in_openstreetmap": "Malfermi en OpenStreetMap", - "open_the_search_filters": "Malfermi la serĉ-filtrilojn", - "options": "Opcioj", - "or": "aÅ­", - "organize_into_albums": "Organizi en albumojn", - "organize_into_albums_description": "Meti ekzistantajn fotojn en albumojn laÅ­ aktualaj sinkronig-agordoj", - "organize_your_library": "Organizi vian bibliotekon", - "orientation": "Orientiĝo", - "original": "originala", - "other": "Alia", - "other_devices": "Aliaj aparatoj", - "other_variables": "Aliaj variabloj", - "owned": "Propraj", - "owner": "Posedanto", - "page": "Paĝo", - "partner": "Partnero", - "partner_can_access": "{partner} havas atingorajton", - "partner_can_access_assets": "Ĉiuj fotoj kaj videoj krom tiuj en ArÄĨivo kaj Rubujo", - "partner_can_access_location": "La geografia loko, kie la fotoj estis faritaj", - "partner_list_user_photos": "Fotoj de {user}", - "partner_page_empty_message": "Viaj fotoj ankoraÅ­ ne estas dividitaj kun ajna partnero.", - "partner_page_no_more_users": "Neniuj pliaj uzantojn por aldoni", - "partner_page_select_partner": "Elekti partneron", - "partner_page_stop_sharing_content": "{partner} ne plu rajtas atingi viajn fotojn.", - "partner_sharing": "Dividado kun partneroj", - "partners": "Partneroj", - "password": "Pasvorto", - "password_does_not_match": "Pasvorto ne kongruas", - "password_required": "Pasvorto estas bezonata", - "password_reset_success": "Sukcesis restarigi pasvorton", - "past_durations": { - "days": "{days, plural, one {Lasta tago} other {Lastaj # tagoj}}", - "hours": "{hours, plural, one {Lasta horo} other {Lastaj # horoj}}", - "years": "{years, plural, one {Lasta jaro} other {Lastaj # jaroj}}" - }, - "path": "Vojo", - "pause": "PaÅ­zi", - "pause_memories": "PaÅ­zi memoraÄĩojn", - "paused": "PaÅ­zita", - "pending": "Pritraktota", - "people": "Homoj", - "people_edits_count": "Redaktis {count, plural, one {# homon} other {# homojn}}", - "people_feature_description": "Foliumas fotojn kaj videojn grupigitajn laÅ­ homoj", - "permanent_deletion_warning": "Averto pri porĉiama forigo", - "permanent_deletion_warning_setting_description": "Montri averton antaÅ­ porĉiama forigo de iu elemento", - "permanently_delete": "Forigi por ĉiam", - "permanently_delete_assets_count": "Forigi {count, plural, one {elementon} other {elementojn}} por ĉiam", - "permanently_delete_assets_prompt": "Ĉu vi certas, ke vi volas por ĉiam forigi {count, plural, one {tiun elementon?} other {tiujn # elementojn?}} Tio krome forigos {count, plural, one {ĝin el ĝia(j)} other {ilin el ilia(j)}} albumo(j).", - "permanently_deleted_asset": "Elemento por ĉiam forigita", - "permanently_deleted_assets_count": "Forigis {count, plural, one {# elementon} other {# elementojn}} por ĉiam", - "permission": "Permeso", - "permission_empty": "Via permeso devus ne esti malplena", - "person": "Homo", - "person_age_months": "{months, plural, one {# monat-aĝa} other {# monat-aĝa}}", - "person_age_year_months": "aĝas 1 jaron, {months, plural, one {# monato} other {# monatojn}}", - "person_age_years": "{years, plural, other {# jaraĝa}}", - "person_birthdate": "Naskiĝis je {date}", - "person_hidden": "{name}{hidden, select, true { (kaŝita)} other {}}", - "person_recognized": "Homo rekonita", - "photo_shared_all_users": "Ŝajne vi dividis viajn fotojn kun ĉiuj uzantoj, aÅ­ povus esti ke ne ekzistas aliaj uzantoj.", - "photos": "Fotoj", - "photos_and_videos": "Fotoj kaj videoj", - "photos_from_previous_years": "Fotoj el antaÅ­aj jaroj", - "pick_a_location": "Elekti lokon", - "pick_custom_range": "Propra intervalo", - "pick_date_range": "Elekti dat-intervalon", - "pin_code_changed_successfully": "Sukcesis ŝanĝi PIN-kodon", - "pin_code_reset_successfully": "Sukcesis restarigi PIN-kodon", - "pin_code_setup_successfully": "Sukcesis krei PIN-kodon", - "pin_verification": "Kontrolo de PIN-kodo", - "place": "Loko", - "places": "Lokoj", - "places_count": "{count, plural, one {{count, number} Loko} other {{count, number} Lokoj}}", - "play": "Vidigi", - "play_memories": "Vidigi memoraÄĩojn", - "play_motion_photo": "Vidigi mov-bildon", - "play_or_pause_video": "Ludi aÅ­ paÅ­zi videon", - "play_original_video": "Ludi originalan videon", - "play_original_video_setting_description": "Preferi ludadon de originalaj videoj anstataÅ­ transkoditaj versioj. Se la formato de la originalo ne kongruas, ĝi eble ne bone funkcios.", - "play_transcoded_video": "Ludi transkoditan videon", - "please_auth_to_access": "Bonvolu ensaluti por atingi tion", - "plugin_method_filter_type": "Filtrilo", - "plugin_method_filter_type_description": "Tiu ĉi metodo povas filtri eventojn kaj, laÅ­ kondiĉoj, haltigi sekvajn eventojn", - "port": "Pordo", "preferences_settings_subtitle": "Administri agordojn pri la apo", - "preferences_settings_title": "Preferoj", - "preparing": "Preparado", - "preset": "AntaÅ­agordo", - "preview": "AntaÅ­vido", - "previous": "AntaÅ­a", - "previous_memory": "AntaÅ­a memoraÄĩo", - "previous_or_next_day": "Postan/antaÅ­an tagon", - "previous_or_next_month": "Postan/antaÅ­an monaton", - "previous_or_next_photo": "Posta/antaÅ­a foto", - "previous_or_next_year": "Postan/antaÅ­an jaron", - "privacy": "Privateco", - "profile": "Profilo", - "profile_drawer_app_logs": "Protokoloj", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Nurlega reĝimo ŝaltita. Longe tuŝu la profilbildon por malŝalti.", - "profile_image_of_user": "Profilbildo de {user}", - "profile_picture_set": "Profilbildo agordita.", - "public_album": "Publika albumo", - "public_share": "Public dividaÄĩo", - "purchase_account_info": "Kontribuinto", - "purchase_activated_subtitle": "Dankon, ke vi subtenis Immich kaj la movadon de malfermfontaj programoj", - "purchase_activated_time": "Aktivigita je {date}", - "purchase_activated_title": "Via ŝlosilo estas sukcese aktivigita", - "purchase_button_activate": "Aktivigi", - "purchase_button_buy": "Aĉeti", - "purchase_button_buy_immich": "Aĉeti Immich", - "purchase_button_never_show_again": "Neniam plu montri", - "purchase_button_reminder": "Memorigu min post 30 tagoj", - "purchase_button_remove_key": "Forigi ŝlosilon", - "purchase_button_select": "Elekti", - "purchase_failed_activation": "Malsukcesis aktivigi! Bonvolu serĉi la ĝustan ŝlosilon en via retpoŝtkesto!", - "purchase_individual_description_1": "Por unuopulo", - "purchase_individual_description_2": "Statuso de subtenanto", - "purchase_individual_title": "Unuopulo", - "purchase_input_suggestion": "Ĉu vi havas var-ŝlosilon? Tajpu ĝin sube", - "purchase_lifetime_description": "Dumviva aĉeto", - "purchase_option_title": "OPCIOJ POR AĈETO", - "purchase_panel_info_1": "Konstrui Immich postulas multe da tempo kaj strebo, kaj ni havas plentempajn inĝenierojn, kiuj kreas kiel eble plej bonan produkton por vi. Nia celas, ke malfermfontaj programoj kaj etika komerco iĝos daÅ­ripovaj, fidindaj fontoj de enspezo por programistoj, kaj ni celas krei ekosistemon de alternativoj kontraÅ­ la ekspluatemaj servoj de korporacioj.", - "purchase_panel_info_2": "Ni promesis ne uzi pagomurojn, do via aĉeto aldonos por vi neniujn novajn funkciojn al Immich. Ni dependas de uzantoj kiel vi, kiuj subtenas la pluan evoluigon de Immich.", - "purchase_panel_title": "Subtenu la projekton", - "purchase_per_server": "por ĉiu servilo", - "purchase_per_user": "por ĉiu uzanto", - "purchase_remove_product_key": "Forigi produkt-ŝlosilon", - "purchase_remove_product_key_prompt": "Ĉu vi certas, ke vi volas forigi la produkt-ŝlosilon?", - "purchase_remove_server_product_key": "Forigi la produkt-ŝlosilon por servilo", - "purchase_remove_server_product_key_prompt": "Ĉu vi certas, ke vi volas forigi la produkt-ŝlosilon por servilo?", - "purchase_server_description_1": "Por la tuta servilo", - "purchase_server_description_2": "Statuso de subtenanto", - "purchase_server_title": "Servilo", "purchase_settings_server_activated": "La administranto respondecas pri la ŝlosilo de aÅ­tentikeco por la servilo", - "queue_status": "{count} el {total} en atendovico", - "rate_asset": "Pritaksi elementon", - "rating": "Pritakso", "rating_clear": "Forviŝi pritakson", - "rating_count": "{count, plural, =0 {Netaksita} one {# stelo} other {# steloj}}", - "rating_description": "Montri pritakson de EXIF en la fenestreto de informoj", - "reaction_options": "Reag-ebloj", - "read_changelog": "Legi protokolon de ŝanĝoj", - "readonly_mode_disabled": "Nurlega reĝimo malŝaltita", - "readonly_mode_enabled": "Nurlega reĝimo ŝaltita", - "ready_for_upload": "Preta por alŝuto", - "reassign": "Reatribui", - "reassigned_assets_to_existing_person": "Reatribuis {count, plural, one {# elementon} other {# elementojn}} al {name, select, null {ekzistanta homo} other {{name}}}", - "reassigned_assets_to_new_person": "Reatribuis {count, plural, one {# elementon} other {# elementojn}} al nova homo", - "reassing_hint": "Reatribuis elektitajn elementojn al ekzistanta homo", - "recent": "Lastatempa(j)", - "recent_searches": "Lastatempaj serĉoj", - "recently_added": "Lastatempe aldonita(j)", - "recently_added_body": "Iri rekte al ĉio, kion vi aldonis lastatempe en unu paĝo.", - "recently_added_description": "Foliumi elementojn ordigitajn laÅ­ dato de alŝuto al Immich", - "recently_added_page_title": "Lastatempe aldonita(j)", - "recently_added_title": "Aldonitaj lastatempe", - "recently_taken": "Lastatempe fotita(j)", "refresh": "Denove", - "refresh_encoded_videos": "Renovigi koditajn videojn", - "refresh_faces": "Renovigi vizaĝojn", - "refresh_metadata": "Renovigi metadatumojn", - "refresh_thumbnails": "Renovigi bildetojn", - "refreshed": "Renovigita(j)", - "refreshing_encoded_video": "Renovigado de koditaj videoj", - "refreshing_faces": "Renovigado de vizaĝoj", - "refreshing_metadata": "Renovigado de metadatumoj", - "regenerating_thumbnails": "Rekreado de bildetoj", - "remote": "Fora", - "remote_assets": "Foraj elementoj", - "remote_media_summary": "Resumo de foraj aÅ­dvideaÄĩoj", - "remove": "Forigi", - "remove_assets_album_confirmation": "Ĉu vi certas, ke vi volas forigi {count, plural, one {# elementon} other {# elementojn}} el la albumo?", - "remove_assets_shared_link_confirmation": "Ĉu vi certas, ke vi volas forigi {count, plural, one {# elementon} other {# elementojn}} de tiu dividita ligilo?", - "remove_assets_title": "Ĉu forigi elementojn?", - "remove_custom_date_range": "Forigi la dat-intervalon", - "remove_filter": "Forigi filtron", - "remove_from_album": "Forpreni de albumo", - "remove_from_album_action_prompt": "{count} forprenitaj de la albumo", - "remove_from_favorites": "Forigi el preferataÄĩoj", - "remove_from_lock_folder_action_prompt": "{count} forprenitaj de la ŝlosita dosierujo", - "remove_from_locked_folder": "Forpreni de la ŝlosita dosierujo", - "remove_from_locked_folder_confirmation": "Ĉu vi certas, ke vi volas forpreni tiujn fotojn/videojn el la ŝlosita dosierujo? Ili poste estos videblaj en via biblioteko.", - "remove_from_shared_link": "Forigi el dividita ligilo", - "remove_memory": "Forigi memoraÄĩon", - "remove_photo_from_memory": "Forpreni foton el tiu memoraÄĩo", - "remove_tag": "Forigi etikedon", - "remove_user": "Forigi uzanton", - "removed_api_key": "Forigita API-ŝlosilo: {name}", - "removed_from_archive": "Forigita de la arÄĨivo", - "removed_from_favorites": "Forigita(j) el preferataÄĩoj", - "removed_from_favorites_count": "{count, plural, other {Forigis #}} el PreferataÄĩoj", - "removed_memory": "MemoraÄĩo forigita", - "removed_tagged_assets": "Forigis etikedon de {count, plural, one {# elemento} other {# elementoj}}", - "rename": "Renomi", - "repository": "Deponejo", - "require_password": "Postuli pasvorton", "rescan": "Reanalizi", "reset": "Restartigi", - "reset_password": "Restarigi pasvorton", - "reset_people_visibility": "Restarigi videblecon de homoj", - "reset_pin_code": "Restarigi PIN-kodon", - "reset_pin_code_description": "Se vi forgesis vian PIN-kodon, vi povas kontakti la administranto de via servilo por restarigi ĝin", - "reset_pin_code_with_password": "Vi povas restarigi vian PIN-kodon pere de via pasvorto", - "reset_sqlite": "Restarigi la SQLite-datumbazon", "reset_sqlite_clear_app_data": "Forviŝi datumojn", "reset_sqlite_confirmation": "Ĉu vi certas, ke vi volas forviŝi la datumojn de la apo? Tio forigos ĉiujn agordojn kaj elsalutigos vin.", "reset_sqlite_confirmation_note": "Noto: vi devos relanĉi la apon por la forviŝo.", "reset_sqlite_done": "Datumoj de la apo estas forviŝitaj. Bonvolu relanĉi Immich kaj ensalutu denove.", - "reset_sqlite_success": "Sukcese restarigis la SQLite-datumbazon", - "reset_to_default": "Restarigi la defaÅ­ltojn", - "resolution": "Distingivo", - "resolve_duplicates": "Solvi duoblaÄĩojn", - "resolved_all_duplicates": "Solvis ĉiujn duoblaÄĩojn", - "restore": "RestaÅ­ri", - "restore_all": "RestaÅ­ri ĉiujn", - "restore_user": "RestaÅ­ri uzanton", - "restored_asset": "RestaÅ­ri elementon", - "resume": "DaÅ­rigi", - "resume_paused_jobs": "DaÅ­rigi {count, plural, one {# paÅ­zitan taskon} other {# paÅ­zitajn taskojn}}", - "review_duplicates": "Kontroli duoblaÄĩojn", - "review_large_files": "Kontroli grandajn dosierojn", - "role": "Rolo", - "role_editor": "Redaktanto", - "role_viewer": "Spektanto", - "running": "Aktuale plenumata(j)", - "save": "Konservi", - "saved": "Konservita(j)", - "saved_api_key": "Konservis API-ŝlosilon", - "saved_profile": "Konservis profilon", - "saved_settings": "Konservis agordojn", - "say_something": "Skribu ion", - "scaffold_body_error_occurred": "Eraro okazis", "scaffold_body_error_unrecoverable": "Neriparebla eraro okazis. Bonvolu sendi al ni la eraron kaj la stakspuron per Discord aÅ­ per Github por ke ni povu helpi. Vi povas forviŝi la ĉi-subajn datumojn de la apo se vi volas.", "scan": "Analizi", "scan_all_libraries": "Analizi ĉiujn bibliotekojn", "scan_library": "Analizi", + "scan_settings": "Agordoj pri analizado", "scanning": "Analizado", "scanning_for_album": "Serĉado de albumo...", - "screencast_mode_description": "Montri indikilojn surekrane pri tuŝoj de klavaro kaj muso", - "screencast_mode_title": "Baskuligi reĝimon de elsendo", - "search": "Serĉi", - "search_albums": "Serĉi albumojn", - "search_by_context": "Serĉi laÅ­ kunteksto", - "search_by_description": "Serĉi laÅ­ priskribo", - "search_by_description_example": "Promenado en Poznań", - "search_by_filename": "Serĉi laÅ­ dosiernomo aÅ­ sufikso", - "search_by_filename_example": "ekz. IMG_1234.jpg aÅ­ png", - "search_by_full_path": "Serĉi laÅ­ dosier-vojo aÅ­ dosierujo", - "search_by_full_path_example": "/Silvja/Projektoj/3D_Printado/2026-07-01 - vi povas serĉi 'Projektoj', '3D', 'Printado', '2026', ktp.", - "search_by_ocr": "Serĉi per optika signo-rekono", - "search_by_ocr_example": "Invitilo", - "search_camera_lens_model": "Serĉi tipon de objektivo...", - "search_camera_make": "Serĉi fabrikanton de fotilo...", - "search_camera_model": "Serĉi tipon de fotilo...", - "search_city": "Serĉi urbon...", - "search_country": "Serĉi landon...", - "search_filter_apply": "Apliki filtrilon", - "search_filter_camera_title": "Elektu tipon de fotilo", - "search_filter_date": "Dato", - "search_filter_date_interval": "de {start} ĝis {end}", - "search_filter_date_title": "Elektu intervalon de datoj", - "search_filter_display_option_not_in_album": "Ne en albumo", - "search_filter_display_options": "Opcioj pri aranĝo sur ekrano", - "search_filter_filename": "Serĉi laÅ­ dosiernomo", - "search_filter_location": "Loko", - "search_filter_location_title": "Elekti lokon", - "search_filter_media_type": "Tipo de aÅ­dvidaÄĩo", - "search_filter_media_type_title": "Elekti tipon de aÅ­dvidaÄĩo", - "search_filter_people_title": "Elekti homojn", - "search_filter_star_rating": "Pritakso", - "search_filter_tags_title": "Elekti etikedojn", - "search_for": "Serĉi", - "search_for_existing_person": "Serĉi ekzistantan homon", - "search_no_more_result": "Neniuj pliaj rezultoj", - "search_no_people": "Neniuj homoj", - "search_no_people_named": "Neniu homo nomita \"{name}\"", - "search_no_result": "Nenio trovita; provu alian serĉon aÅ­ kombinon de serĉ-vortoj", - "search_options": "Opcioj pri serĉado", - "search_page_search_photos_videos": "Serĉi viajn fotojn kaj videojn", - "search_page_view_all_button": "Vidi ĉiujn", - "search_people": "Serĉi homojn", - "search_places": "Serĉi lokojn", - "search_rating": "Serĉi laÅ­ pritaksto...", - "search_settings": "Agordoj pri serĉado", - "search_state": "Serĉi laÅ­ ŝtato/regiono...", - "search_tags": "Serĉi laÅ­ etikedo...", - "search_timezone": "Serĉi laÅ­ horzono...", - "search_type": "Serĉi laÅ­ tipo", - "search_your_photos": "Serĉi viajn fotojn", - "searching_locales": "Serĉado de lokaj parametroj...", - "second": "Sekundo", - "see_all_people": "Vidi ĉiujn homojn", - "select": "Elekti", - "select_album": "Elekti albumon", - "select_album_cover": "Elekti album-kovrilon", - "select_albums": "Elekti albumojn", - "select_all": "Elekti ĉiujn", - "select_all_duplicates": "Elekti ĉiujn duoblaÄĩojn", - "select_avatar_color": "Elekti koloron de profilbildo", - "select_count": "{count, plural, one {Elekti #} other {Elekti #}}", - "select_cutoff_date": "Elekti limdaton", - "select_face": "Elekti vizaĝon", - "select_featured_photo": "Elekti elstarigitan foton", - "select_from_computer": "Elekti el komputilo", - "select_keep_all": "Konservi ĉiujn", - "select_new_face": "Elekti novan vizaĝon", - "select_people": "Elekti homojn", - "select_person": "Elekti homon", - "select_person_to_tag": "Elekti homon por etikedi", - "select_photos": "Elekti fotojn", - "select_quality": "Elekti kvaliton", - "select_trash_all": "Forigi ĉiujn", - "selected": "Elektita", - "selected_count": "{count, plural, one {# elektita} other {# elektitaj}}", - "selected_gps_coordinates": "GPS-koordinatoj elektitaj", - "send_message": "Sendi mesaĝon", - "send_welcome_email": "Sendi bonvenan retmesaĝon", - "server_endpoint": "Finpunkto ĉe servilo", - "server_info_box_app_version": "Versio de apo", - "server_info_box_server_url": "URL-o de servilo", - "server_offline": "Servilo nedisponebla", - "server_online": "Servilo disponebla", - "server_privacy": "Privateco de servilo", - "server_restarting_description": "La paĝo baldaÅ­ reŝargiĝos.", - "server_restarting_title": "Servilo restartas", - "server_stats": "Statistikoj de servilo", - "server_update_available": "Ĝisdatigo disponeblas por la servilo", - "server_version": "Versio de servilo", - "set": "Agordi", - "set_as_album_cover": "Agordi kiel album-kovrilo", - "set_as_featured_photo": "Agordi kiel elstarigitan foton", - "set_as_profile_picture": "Elekti kiel profilbildon", - "set_date_of_birth": "Agordi naskiĝdaton", - "set_profile_picture": "Elekti profilbildon", - "set_slideshow_to_fullscreen": "Vidigi bildserion plenekrane", - "set_stack_primary_asset": "Agordi kiel unuarangan elementon", - "setting_image_navigation_enable_subtitle": "Se ŝaltita, vi povas salti al antaÅ­a/sekva bildo per tuŝeto en la maldekstra/dekstra kvarono de la ekrano.", - "setting_image_navigation_enable_title": "Tuŝeti por navigi", - "setting_image_navigation_title": "Navigado de bildoj", - "setting_image_viewer_help": "La detala montrilo unue montras la malgrandan bildeton, poste ŝargas la mezgrandan antaÅ­vidon (se ŝaltita), kaj fine ŝargas la originalan (se ŝaltita).", - "setting_image_viewer_original_subtitle": "Ŝalti por ŝargi la originalan bildon (povas esti tre granda!) Malŝalti por ŝpareme uzi datumojn (de retkonekto kaj memoro en via aparato).", - "setting_image_viewer_original_title": "Ŝargi originalan bildon", - "setting_image_viewer_preview_subtitle": "Ŝalti por ŝargi mez-distingivan bildon. Malŝalti por ŝargi aÅ­ la originalon aÅ­ la bildeton.", - "setting_image_viewer_preview_title": "Ŝargi antaÅ­vidan bildon", - "setting_languages_apply": "Validigi", - "setting_languages_subtitle": "Ŝanĝi lingvon de la apo", - "setting_notifications_notify_minutes": "{count} minutoj", - "setting_notifications_notify_seconds": "{count} sekundoj", + "search_suggestion_list_smart_search_hint_1": "Inteligenta serĉado defaÅ­lte estas ŝaltita. Por serĉi metadatumojn, uzu sintakson tiel ", "setting_notifications_subtitle": "Redakti viajn preferojn pri sciigoj", - "setting_video_viewer_auto_play_subtitle": "AÅ­tomate ekludi videojn tuj je malfermo", - "setting_video_viewer_auto_play_title": "AÅ­tomate ludi videojn", - "setting_video_viewer_looping_title": "Ripeti", - "settings": "Agordoj", - "settings_saved": "Agordoj konservitaj", - "setup_pin_code": "Krei PIN-kodon", - "share": "Dividi", - "share_dialog_preparing": "Preparado...", - "share_link": "Dividi ligilon", - "share_original": "Uzi originalon (grandan)", - "share_preview": "Uzi bildeton (malgrandan)", - "share_quality_body": "Premteni la dividi-butonon por elekti la kvaliton de bildo dividota.", - "share_quality_title": "Elekti kvaliton dividotan", - "shared": "Dividitaj", - "shared_album_activities_input_disable": "Komentado estas malŝaltita", - "shared_album_activity_remove_content": "Ĉu vi volas forigi tiun ĉi agon?", - "shared_album_activity_remove_title": "Forigi agon", - "shared_album_section_people_action_error": "Okazis eraro dum forigo el la albumo", - "shared_album_section_people_title": "HOMOJ", - "shared_by": "Dividita de", - "shared_by_user": "Dividita de {user}", - "shared_by_you": "Dividita de vi", - "shared_from_partner": "Fotoj de {partner}", - "shared_intent_upload_button_progress_text": "{current} el {total} alŝutitaj", - "shared_link_app_bar_title": "Dividitaj ligiloj", - "shared_link_clipboard_copied_massage": "Kopiita al tondujo", - "shared_link_create_error": "Okazis eraro dum kreo de dividita ligilo", - "shared_link_custom_url_description": "Atingi la dividita ligilo per propra URL-o", - "shared_link_custom_url_title": "Propra URL", - "shared_link_custom_url_warning": "Atentu: propra URL, kiu inkluzivas suprenstrekon povus havi neatenditan rezulton.", - "shared_link_edit_description_hint": "Tajpu priskribon de la dividaÄĩaro", - "shared_link_edit_expire_after_option_day": "1 tago", - "shared_link_edit_expire_after_option_days": "{count} tagoj", - "shared_link_edit_expire_after_option_hour": "1 horo", - "shared_link_edit_expire_after_option_hours": "{count} horoj", - "shared_link_edit_expire_after_option_minute": "1 minuto", - "shared_link_edit_expire_after_option_minutes": "{count} minutoj", - "shared_link_edit_expire_after_option_months": "{count} monatoj", - "shared_link_edit_expire_after_option_year": "{count} jaro", - "shared_link_edit_password_hint": "Tajpu pasvorton por la dividaÄĩaro", - "shared_link_edit_submit_button": "Ĝisdatigi ligilon", - "shared_link_error_server_url_fetch": "Ne eblis trovi la URL-on de la servilo", - "shared_link_expires_day": "Eksvalidiĝos post {count} tago", - "shared_link_expires_days": "Eksvalidiĝos post {count} tagoj", - "shared_link_expires_hour": "Eksvalidiĝos post {count} horo", - "shared_link_expires_hours": "Eksvalidiĝos post {count} horoj", - "shared_link_expires_minute": "Eksvalidiĝos post {count} minuto", - "shared_link_expires_minutes": "Eksvalidiĝos post {count} minutoj", - "shared_link_expires_never": "Neniam eksvalidiĝos", - "shared_link_expires_second": "Eksvalidiĝos post {count} sekundo", - "shared_link_expires_seconds": "Eksvalidiĝos post {count} sekundoj", - "shared_link_individual_shared": "Unuope dividita", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Administri dividitajn ligilojn", - "shared_link_options": "Agordoj pri dividitaj ligiloj", - "shared_link_password_description": "Devigi uzon de pasvorto por uzi la ligilon", - "shared_links": "Dividitaj ligiloj", - "shared_links_description": "Dividi fotojn kaj videojn per ligilo", - "shared_photos_and_videos_count": "{assetCount, plural, other {# dividitaj fotoj kaj videoj.}}", - "shared_with_me": "Dividitaj kun mi", - "shared_with_partner": "Dividitaj kun {partner}", - "sharing": "Dividado", - "sharing_enter_password": "Bonvolu tajpi la pasvorton por vidi tiun ĉi paĝon.", - "shift_to_permanent_delete": "Tuŝu ⇧ por forigi porĉiame la elementon", - "show_album_options": "Montri opciojn de albumo", - "show_albums": "Montri albumojn", - "show_all_people": "Montri ĉiujn homojn", - "show_and_hide_people": "Montri kaj kaŝi homojn", - "show_file_location": "Montri dosier-lokon", - "show_gallery": "Montri galerion", - "show_hidden_people": "Montri kaŝitajn homojn", - "show_in_timeline": "Montri en tempolinio", - "show_in_timeline_setting_description": "Montri fotojn kaj videojn de tiu uzanto en via tempolinio", - "show_keyboard_shortcuts": "Montri fulmklavojn", - "show_less": "Montri malpli", - "show_metadata": "Montri metadatumojn", - "show_more_fields": "{count, plural, one {Montri # plian kampon} other {Montri # pliajn kampojn}}", - "show_or_hide_info": "Montri aÅ­ kaŝi informojn", - "show_password": "Montri pasvorton", - "show_person_options": "Montri opciojn pri homo", - "show_progress_bar": "Montri plenumskalon", - "show_schema": "Montri skemon", - "show_search_options": "Montri opciojn pri serĉado", - "show_shared_links": "Montri dividitajn ligilojn", - "show_slideshow_metadata_overlay": "Montri pribildajn informojn super la bildo", - "show_slideshow_transition": "Montri transpason dum bildserio", - "show_supporter_badge": "Ŝildo de subtenanto", - "show_supporter_badge_description": "Montri ŝildon de subtenanto", - "show_text_recognition": "Montri rekonadon de teksto", - "show_text_search_menu": "Montri menuon pri teksta serĉo", - "shuffle": "Hazarda vicordo", - "sidebar": "Flankpanelo", - "sidebar_display_description": "Montri ligilon al la vido en la flankpanelo", - "sign_out": "Elsaluti", - "sign_up": "Registriĝi", - "size": "Grandeco", - "skip": "Preterlasi", - "skip_to_content": "Salti rekte al enhavo", - "skip_to_folders": "Salti rekte al dosierujoj", - "skip_to_tags": "Salti rekte al etikedoj", - "slideshow": "Bildserio", - "slideshow_body": "Spekti viajn fotojn en plenekrana bildserio.", - "slideshow_metadata_overlay_mode": "Supermeti elementojn", - "slideshow_metadata_overlay_mode_description_only": "Nur priskribo", - "slideshow_metadata_overlay_mode_full": "Plena", - "slideshow_repeat": "Ripeti bildserion", - "slideshow_repeat_description": "Remontri ekde la komenco post fino de bildserio", - "slideshow_settings": "Agordoj pri bildserioj", - "slideshow_title": "Bildserio", - "smart_album": "Aktiva albumo", - "some_assets_already_have_a_location_warning": "Kelkaj el la elektitaj elementoj jam havas lokon", - "sort_albums_by": "Ordigi albumojn laÅ­...", - "sort_created": "Dato de kreo", - "sort_items": "Nombro da elementoj", - "sort_modified": "Dato de lasta ŝanĝo", - "sort_oldest": "Plej malnova foto", - "sort_people_by_similarity": "Ordigi homojn laÅ­ simileco", - "sort_recent": "Plej lasta foto", - "sort_title": "Titolo", - "source": "Fonto", - "stack": "Staki", - "stack_action_prompt": "{count} stakitaj", - "stack_duplicates": "Staki duoblaÄĩojn", - "stack_selected_photos": "Staki elektitajn fotojn", - "stacked_assets_count": "Stakis {count, plural, one {# elementon} other {# elementojn}}", - "stacktrace": "Stak-spuro", - "start": "Komenci", "start_date": "Komenca dato", "start_date_before_end_date": "Komenca dato devas esti antaÅ­ fina dato", - "state": "Ŝtato/Regiono", - "status": "Stato", - "step_delete": "Forigi paŝon", - "step_delete_confirm": "Ĉu vi certas, ke vi volas forigi tiun paŝon?", - "step_details": "Detaloj pri paŝo", - "steps": "Paŝoj", - "stop_casting": "Ĉesi elsendi", - "stop_photo_sharing": "Ĉu ĉesi dividi viajn fotojn?", - "stop_photo_sharing_description": "{partner} ne plu rajtos atingi viajn fotojn.", - "stop_sharing_photos_with_user": "Ĉesi dividi fotojn kun tiu ĉi uzanto", - "to_favorite": "Aldoni al preferataÄĩoj", - "unfavorite": "Forigi el preferataÄĩoj", - "unfavorite_action_prompt": "{count} forigita(j) el PreferataÄĩoj", + "trigger_description": "Evento, kiu ekfunkciigas la laborfluon", + "untitled_workflow": "Sentitola laborfluo", "upload_concurrency": "Nombro da samtempaj alŝutoj", - "upload_errors": "Alŝuto finita kun {count, plural, one {# eraro} other {# eraroj}}. Reŝargu la paĝon por vidi novajn elementojn.", - "upload_success": "Alŝuto sukcese finita. Reŝargu la paĝon por vidi novajn elementojn.", "user_pin_code_settings_description": "Administri vian PIN-kodon", - "user_privacy": "Privateco de uzanto", "user_purchase_settings_description": "Administri vian aĉeton", - "waiting": "En atendovico", - "waiting_count": "En atendovico: {count}", + "view_links": "Vidi ligilojn", "week": "Semajno", "wifi_name": "Nomo de Vifireto", "workflow_delete_prompt": "Ĉu vi certas, ke vi volas forigi tiun ĉi laborfluon?", @@ -2064,10 +1145,10 @@ "workflow_navigation_prompt": "Ĉu vi certas, ke vi volas foriri sen konservi viajn ŝanĝojn?", "workflow_summary": "Resumo de laborfluo", "workflow_update_success": "Laborfluo sukcese ĝisdatigita", + "workflow_updated": "Laborfluo ĝisdatigita", "workflows": "Laborfluoj", "workflows_help_text": "Laborfluo aÅ­tomatigas agojn pri elementoj, laÅ­ ekigiloj kaj filtriloj", "year": "Jaro", "yes": "Jes", - "you_dont_have_any_shared_links": "Vi ne havas ajnan dividitan ligilon", "zero_to_clear_rating": "tuŝu 0 por forviŝi la pritakson de la elemento" } diff --git a/i18n/es.json b/i18n/es.json index 3d019ea36a..722c8fd98c 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -10,31 +10,39 @@ "active": "Activo", "active_count": "Activo: {count}", "activity": "Actividad", + "activity_changed": "La actividad estÃĄ {enabled, select, true {habilitada} other {deshabilitada}}", "add": "AÃąadir", "add_a_description": "AÃąadir descripciÃŗn", "add_a_location": "AÃąadir una ubicaciÃŗn", "add_a_name": "AÃąadir un nombre", "add_a_title": "AÃąadir título", "add_action": "AÃąadir acciÃŗn", + "add_action_description": "Haz clic para aÃąadir una acciÃŗn a realizar", "add_assets": "AÃąadir recursos", "add_birthday": "AÃąadir un cumpleaÃąos", "add_endpoint": "AÃąadir punto final", "add_exclusion_pattern": "AÃąadir patrÃŗn de exclusiÃŗn", + "add_filter": "AÃąadir filtro", + "add_filter_description": "Haz clic para aÃąadir una condiciÃŗn de filtro", "add_location": "AÃąadir ubicaciÃŗn", + "add_more_users": "AÃąadir mÃĄs usuarios", "add_partner": "AÃąadir miembro", + "add_path": "AÃąadir ruta", "add_photos": "AÃąadir fotos", - "add_step": "AÃąadir paso", "add_tag": "AÃąadir etiqueta", "add_to": "AÃąadir aâ€Ļ", "add_to_album": "AÃąadir al ÃĄlbum", "add_to_album_bottom_sheet_added": "AÃąadido a {album}", "add_to_album_bottom_sheet_already_exists": "Ya se encuentra en {album}", "add_to_album_bottom_sheet_some_local_assets": "Algunos recursos locales no se pudieron aÃąadir al ÃĄlbum", + "add_to_album_toggle": "Alternar selecciÃŗn para el {album}", "add_to_albums": "AÃąadir a ÃĄlbumes", "add_to_albums_count": "AÃąadir a {count} ÃĄlbumes", "add_to_bottom_bar": "AÃąadir a", + "add_to_shared_album": "AÃąadir al ÃĄlbum compartido", "add_upload_to_stack": "AÃąadir subida a la cola", "add_url": "AÃąadir URL", + "add_workflow_step": "AÃąadir paso al flujo de trabajo", "added_to_archive": "AÃąadido al archivo", "added_to_favorites": "AÃąadido a favoritos", "added_to_favorites_count": "AÃąadido {count, number} a favoritos", @@ -53,7 +61,7 @@ "backup_onboarding_1_description": "Copia en un lugar externo, en la nube u otra ubicaciÃŗn física.", "backup_onboarding_2_description": "copias locales en diferentes dispositivos. Incluye los archivos principales y una copia de seguridad local de dichos archivos.", "backup_onboarding_3_description": "copias totales de tu data, incluyendo los archivos originales. Incluye 1 copia fuera de sitio y 2 copias locales.", - "backup_onboarding_description": "Una estrategia de copia de seguridad 3-2-1 es recomendada para proteger tus datos. Deberías mantener copias de las fotos y vídeos que subas, así como de la base de datos de Immich, para contar con una soluciÃŗn de copia de seguridad completa.", + "backup_onboarding_description": "Se recomienda una estrategia de copia de seguridad 3-2-1 para proteger tus datos. Deberías mantener copias de las fotos y vídeos que subas, así como de la base de datos de Immich, para contar con una soluciÃŗn de copia de seguridad completa.", "backup_onboarding_footer": "Para obtener mÃĄs informaciÃŗn sobre cÃŗmo hacer una copia de seguridad de Immich, consulta la documentaciÃŗn.", "backup_onboarding_parts_title": "Una copia de seguridad 3-2-1 incluye:", "backup_onboarding_title": "Copias de seguridad", @@ -73,7 +81,6 @@ "cron_expression_description": "Establece el intervalo de escaneo utilizando el formato cron. Para mÃĄs informaciÃŗn puedes consultar, por ejemplo, Crontab Guru", "cron_expression_presets": "Valores predefinidos de expresiones cron", "disable_login": "Deshabilitar inicio de sesiÃŗn", - "download_csv": "Descargar CSV", "duplicate_detection_job_description": "Ejecuta el aprendizaje automÃĄtico en los recursos para detectar imÃĄgenes similares. Se basa en la bÃēsqueda inteligente", "exclusion_pattern_description": "Los patrones de exclusiÃŗn te permiten ignorar archivos y carpetas al escanear tu biblioteca. Es Ãētil si tienes carpetas que contienen archivos que no deseas importar, por ejemplo archivos RAW.", "export_config_as_json_description": "Descargar la configuraciÃŗn actual del sistema como un archivo JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Calidad de miniatura de 1 a 100. Es mejor cuanto mÃĄs alto es el valor pero genera archivos mÃĄs grandes y puede reducir la capacidad de respuesta de la aplicaciÃŗn.", "image_thumbnail_title": "Ajustes de las miniaturas", "import_config_from_json_description": "Importar la configuraciÃŗn del sistema subiendo un archivo JSON de configuraciÃŗn", - "integrity_checks_checksum_files": "Archivos de suma de comprobaciÃŗn", - "integrity_checks_checksum_files_description": "Configurar la comprobaciÃŗn de la suma de comprobaciÃŗn", - "integrity_checks_checksum_files_enable_description": "Activar la comprobaciÃŗn de la suma de comprobaciÃŗn", - "integrity_checks_checksum_files_percentage_limit": "Límite porcentual", - "integrity_checks_checksum_files_percentage_limit_description": "Configura el porcentaje mÃĄximo, entre 0,01 y 1, que indica la frecuencia con la que debe ejecutarse la comprobaciÃŗn de la suma de comprobaciÃŗn en cada intervalo.", - "integrity_checks_checksum_files_time_limit": "Límite de tiempo", - "integrity_checks_checksum_files_time_limit_description": "Configura la duraciÃŗn mÃĄxima durante la cual debe ejecutarse la comprobaciÃŗn de la suma de comprobaciÃŗn en cada intervalo. (ms)", - "integrity_checks_missing_files": "Archivos faltantes", - "integrity_checks_missing_files_description": "Configura la frecuencia y activa o desactiva la comprobaciÃŗn de archivos faltantes", - "integrity_checks_missing_files_enable_description": "Activar la comprobaciÃŗn de archivos faltantes", - "integrity_checks_settings": "Comprobaciones de integridad", - "integrity_checks_settings_description": "Gestionar los intervalos de las comprobaciones de integridad", - "integrity_checks_untracked_files": "Archivos sin seguimiento", - "integrity_checks_untracked_files_description": "Configure la frecuencia y active o desactive la comprobaciÃŗn de archivos sin seguimiento", - "integrity_checks_untracked_files_enable_description": "Habilitar la comprobaciÃŗn de archivos sin seguimiento", "job_concurrency": "{job}: Procesos simultÃĄneos", "job_created": "Tarea creada", "job_not_concurrency_safe": "Esta tarea no es segura para la simultaneidad.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Habilitar bÃēsqueda inteligente", "machine_learning_smart_search_enabled_description": "Al desactivarlo las imÃĄgenes no se procesarÃĄn para usar la bÃēsqueda inteligente.", "machine_learning_url_description": "La URL del servidor de aprendizaje automÃĄtico. Si se proporciona mÃĄs de una URL se intentarÃĄ acceder a cada servidor sucesivamente hasta que uno responda correctamente en el orden especificado. Los servidores que no respondan serÃĄn ignorados temporalmente hasta que vuelvan a estar en línea.", - "maintenance_backup_management": "GestiÃŗn de respaldos", "maintenance_delete_backup": "Eliminar copia de seguridad", "maintenance_delete_backup_description": "Este archivo serÃĄ eliminado de forma permanente.", "maintenance_delete_error": "Fallo al eliminar la copia de seguridad.", - "maintenance_integrity_check": "Verificar", - "maintenance_integrity_check_all": "Comprobar todo", - "maintenance_integrity_checksum_mismatch": "Discrepancia de checksum", - "maintenance_integrity_checksum_mismatch_description": "Archivos cuya suma de verificaciÃŗn en disco no coincide con la suma de verificaciÃŗn que Immich tiene almacenada en su base de datos.", - "maintenance_integrity_checksum_mismatch_job": "Comprobar las discrepancias de checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Actualizar reportes de discrepancia de checksum", - "maintenance_integrity_missing_file": "Archivos faltantes", - "maintenance_integrity_missing_file_description": "Archivos que Immich ha rastreado en su base de datos pero que no existen en el sistema de archivos.", - "maintenance_integrity_missing_file_job": "Comprueba archivos faltantes", - "maintenance_integrity_missing_file_refresh_job": "Actualizar informes de archivos faltantes", - "maintenance_integrity_report": "Informe de integridad", - "maintenance_integrity_untracked_file": "Archivos no rastreados", - "maintenance_integrity_untracked_file_description": "Archivos en los directorios de Immich de los que Immich no tiene ningÃēn registro.", - "maintenance_integrity_untracked_file_job": "Comprobar archivos no rastreados", - "maintenance_integrity_untracked_file_refresh_job": "Actualizar informes de archivos no rastreados", "maintenance_restore_backup": "Restaurar copia de seguridad", "maintenance_restore_backup_description": "Se borrarÃĄ el historial de Immich y se restaurarÃĄ desde la copia de seguridad seleccionada. Se crearÃĄ una copia de seguridad antes de continuar.", "maintenance_restore_backup_different_version": "ÂĄEsta copia de seguridad se creÃŗ con una versiÃŗn diferente de Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Habilitar notificaciones por correo electrÃŗnico", "notification_settings": "ConfiguraciÃŗn de las notificaciones", "notification_settings_description": "Administrar la configuraciÃŗn de notificaciones, incluido el correo electrÃŗnico", - "oauth_allow_insecure_requests": "Permitir solicitudes inseguras", - "oauth_allow_insecure_requests_description": "ADVERTENCIA: Esto deshabilita la validaciÃŗn de certificado TLS para peticiones OAuth y le puede exponer a ataques MITM.", "oauth_auto_launch": "Lanzamiento automÃĄtico", "oauth_auto_launch_description": "Inicie el flujo de inicio de sesiÃŗn de OAuth automÃĄticamente al acceder a la pÃĄgina de inicio de sesiÃŗn", "oauth_auto_register": "Registro automÃĄtico", @@ -300,11 +274,9 @@ "oauth_button_text": "Texto del botÃŗn", "oauth_client_secret_description": "Requerido para clientes confidenciales, o si PKCE (Prueba de clave para el intercambio de cÃŗdigos) no es compatible con clientes pÃēblicos.", "oauth_enable_description": "Iniciar sesiÃŗn con OAuth", - "oauth_end_session_url_description": "Redirigir al usuario a esta URI cuando cierra sesiÃŗn.", "oauth_mobile_redirect_uri": "URI de redireccionamiento mÃŗvil", "oauth_mobile_redirect_uri_override": "Sobreescribir URI de redirecciÃŗn mÃŗvil", "oauth_mobile_redirect_uri_override_description": "Habilitar cuando el proveedor de OAuth no permite una URI mÃŗvil, como ''{callback}''", - "oauth_prompt_description": "ParÃĄmetro de solicitud (p. eje. select_account, login, consent)", "oauth_role_claim": "ConcesiÃŗn de rol", "oauth_role_claim_description": "Otorgar acceso de administrador automÃĄticamente segÃēn la presencia de esta concesiÃŗn. La concesiÃŗn puede tener \"usuario\" o \"admin\".", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Actualizar todas las bibliotecas", "registration": "Registrar administrador", "registration_description": "Dado que eres el primer usuario del sistema, se te designarÃĄ como administrador, tendrÃĄs a tu cargo las tareas administrativas y deberÃĄs crear los demÃĄs usuarios.", - "release_channel_release_candidate": "versiÃŗn candidata", - "release_channel_stable": "Estable", "remove_failed_jobs": "Eliminar trabajos fallidos", "require_password_change_on_login": "Requerir que el usuario cambie la contraseÃąa en el primer inicio de sesiÃŗn", "reset_settings_to_default": "Restablecer la configuraciÃŗn predeterminada", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Se aplica Ãēnicamente a VAAPI y QSV. Establece el nodo dri utilizado para la transcodificaciÃŗn de hardware.", "transcoding_preset_preset": "ConfiguraciÃŗn predefinida (-preset)", "transcoding_preset_preset_description": "Velocidad de compresiÃŗn. Los preajustes mÃĄs lentos producen archivos mÃĄs pequeÃąos y aumentan la calidad cuando se apunta a una tasa de bits determinada. VP9 ignora las velocidades superiores al valor \"faster\" (\"mÃĄs rÃĄpido\").", - "transcoding_realtime": "TranscodificaciÃŗn en tiempo real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permite que la transcodificaciÃŗn se efectua en tiempo real al retransmitirse el video. Permite cambios de calidad, pero quizÃĄs causa mayor latencia de reproducciÃŗn y entrecortamiento dependiendo de las capacidades del servidor.", - "transcoding_realtime_enabled": "Activar transcodificaciÃŗn en tiempo real", - "transcoding_realtime_enabled_description": "Si se desactiva, el servidor se niega a comenzar nuevas sesiones de transcodificaciÃŗn en tiempo real.", - "transcoding_realtime_resolutions": "Resoluciones", - "transcoding_realtime_resolutions_description": "Las resoluciones ofrecidas para el la transcodificaciÃŗn en tiempo real. Resoluciones altas pueden causar problemas al reproducir si el servidor no es capaz de transcodificarlas lo suficientemente rÃĄpido.", - "transcoding_realtime_video_codecs": "CÃŗdecs de video", - "transcoding_realtime_video_codecs_description": "Los cÃŗdecs de video disponibles para la transcodificaciÃŗn en tiempo real. Durante la reproducciÃŗn, los clientes elegirÃĄn la mejor opciÃŗn que admitan. AV1 es mÃĄs eficiente que HEVC, y HEVC es mÃĄs eficiente que H.264. Al usar aceleraciÃŗn por hardware, selecciona Ãēnicamente los cÃŗdecs que el acelerador pueda codificar. Al usar transcodificaciÃŗn por software, ten en cuenta que H.264 es mÃĄs rÃĄpido que AV1, y AV1 es mÃĄs rÃĄpido que HEVC.", "transcoding_reference_frames": "Frames de referencia", "transcoding_reference_frames_description": "El nÃēmero de fotogramas a los que hacer referencia al comprimir un fotograma determinado. Los valores mÃĄs altos mejoran la eficiencia de la compresiÃŗn, pero ralentizan la codificaciÃŗn. 0 establece este valor automÃĄticamente.", "transcoding_required_description": "SÃŗlo vídeos que no estÊn en un formato soportado", @@ -478,8 +440,6 @@ "user_settings_description": "Administrar la configuraciÃŗn del usuario", "user_successfully_removed": "El usuario {email} ha sido eliminado con Êxito.", "users_page_description": "PÃĄgina de usuarios administradores", - "version_check_channel": "Canal de versiones", - "version_check_channel_description": "Elige el canal de versiones para el que deseas recibir anuncios", "version_check_enabled_description": "Activar la comprobaciÃŗn de la versiÃŗn", "version_check_implications": "La funciÃŗn de comprobaciÃŗn de versiones depende de la comunicaciÃŗn periÃŗdica con {server}", "version_check_settings": "Verificar versiÃŗn", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Borrar cachÊ de imÃĄgenes", "advanced_settings_clear_image_cache_error": "No se pudo borrar la cachÊ de imÃĄgenes", "advanced_settings_clear_image_cache_success": "Limpiado con Êxito {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Usa esta opciÃŗn para filtrar medios durante la sincronizaciÃŗn segÃēn criterios alternativos. Intenta esto solo si tienes problemas con que la aplicaciÃŗn detecte todos los ÃĄlbumes.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Usar filtro alternativo de sincronizaciÃŗn de ÃĄlbumes del dispositivo", "advanced_settings_log_level_title": "Nivel de registro: {level}", "advanced_settings_prefer_remote_subtitle": "Algunos dispositivos tardan mucho en cargar las miniaturas desde los recursos locales. Activa esta opciÃŗn para cargar imÃĄgenes remotas en su lugar.", "advanced_settings_prefer_remote_title": "Preferir imÃĄgenes remotas", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Cabeceras proxy personalizadas [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Habilita el modo de solo lectura donde las fotografías sÃŗlo pueden ser vistas, funciones como seleccionar mÃēltiples imÃĄgenes, compartir, transmitir, eliminar son deshabilitadas. Habilita/Deshabilita solo lectura vía el avatar del usuario en la pantalla principal", "advanced_settings_readonly_mode_title": "Modo solo lectura", + "advanced_settings_self_signed_ssl_subtitle": "Omitir verificaciÃŗn del certificado SSL del servidor. Requerido para certificados autofirmados.", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Eliminar o restaurar automÃĄticamente un recurso en este dispositivo cuando se realice esa acciÃŗn en la web", "advanced_settings_sync_remote_deletions_title": "Sincronizar eliminaciones remotas [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Configuraciones avanzadas del usuario", @@ -516,18 +480,31 @@ "album_delete_confirmation": "ÂŋEstÃĄs seguro de que deseas eliminar el ÃĄlbum {album}?", "album_delete_confirmation_description": "Si este ÃĄlbum se comparte, otros usuarios ya no podrÃĄn acceder a Êl.", "album_deleted": "Álbum eliminado", + "album_info_card_backup_album_excluded": "EXCLUIDOS", + "album_info_card_backup_album_included": "INCLUIDOS", "album_info_updated": "InformaciÃŗn del ÃĄlbum actualizada", + "album_leave": "ÂŋAbandonar el ÃĄlbum?", + "album_leave_confirmation": "ÂŋEstÃĄs seguro de que quieres dejar {album}?", "album_name": "Nombre del ÃĄlbum", "album_options": "Opciones del ÃĄlbum", "album_remove_user": "ÂŋEliminar usuario?", "album_remove_user_confirmation": "ÂŋEstÃĄs seguro de que quieres eliminar a {user}?", "album_search_not_found": "No se encontraron ÃĄlbumes que coincidan con tu bÃēsqueda", + "album_selected": "Álbum seleccionado", "album_share_no_users": "Parece que has compartido este ÃĄlbum con todos los usuarios o no tienes ningÃēn usuario con quien compartirlo.", "album_summary": "Resumen del ÃĄlbum", "album_updated": "Álbum actualizado", "album_updated_setting_description": "Reciba una notificaciÃŗn por correo electrÃŗnico cuando un ÃĄlbum compartido tenga nuevos recursos", "album_upload_assets": "AÃąadir recursos desde tu computadora y aÃąadir a un ÃĄlbum", + "album_user_left": "AbandonÃŗ {album}", + "album_user_removed": "Eliminado a {user}", + "album_viewer_appbar_delete_confirm": "ÂŋEstÃĄs seguro/a que quieres borrar este ÃĄlbum de tu cuenta?", "album_viewer_appbar_share_err_delete": "No ha podido eliminar el ÃĄlbum", + "album_viewer_appbar_share_err_leave": "No se ha podido abandonar el ÃĄlbum", + "album_viewer_appbar_share_err_remove": "Hay problemas para eliminar los recursos del ÃĄlbum", + "album_viewer_appbar_share_err_title": "Error al cambiar el título del ÃĄlbum", + "album_viewer_appbar_share_leave": "Abandonar ÃĄlbum", + "album_viewer_appbar_share_to": "Compartir con", "album_viewer_page_share_add_users": "AÃąadir usuarios", "album_with_link_access": "Permitir que cualquiera que tenga el enlace vea las fotos y las personas en este ÃĄlbum.", "albums": "Álbumes", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Orden de clasificaciÃŗn inicial de los recursos al crear nuevos ÃĄlbumes.", "albums_feature_description": "Colecciones de recursos que pueden ser compartidos con otros usuarios.", "albums_on_device_count": "Álbumes en el dispositivo ({count})", + "albums_selected": "{count, plural, one {# ÃĄlbum seleccionado} other {# ÃĄlbumes seleccionados}}", "all": "Todos", "all_albums": "Todos los ÃĄlbumes", "all_people": "Todas las personas", "all_photos": "Todas las fotos", "all_videos": "Todos los videos", "allow_dark_mode": "Permitir modo oscuro", + "allow_edits": "Permitir ediciÃŗn", "allow_public_user_to_download": "Permitir descargas a los usuarios pÃēblicos", "allow_public_user_to_upload": "Permitir a los usuarios pÃēblicos subir fotos", "allowed": "Permitido", @@ -549,12 +528,14 @@ "always_keep": "Mantener siempre", "always_keep_photos_hint": "El liberador de espacio en disco mantendrÃĄ todas las fotos en este dispositivo.", "always_keep_videos_hint": "El liberador de espacio en disco mantendrÃĄ todos las vídeos en este dispositivo.", + "anti_clockwise": "En sentido antihorario", "api_key": "Clave API", "api_key_description": "Este valor sÃŗlo se mostrarÃĄ una vez. AsegÃērese de copiarlo antes de cerrar la ventana.", "api_key_empty": "El nombre de su clave API no debe estar vacío", "api_keys": "Claves API", "app_architecture_variant": "Variante (Arquitectura)", "app_bar_signout_dialog_content": "ÂŋEstÃĄs seguro que quieres cerrar sesiÃŗn?", + "app_bar_signout_dialog_ok": "Sí", "app_bar_signout_dialog_title": "Cerrar sesiÃŗn", "app_download_links": "Enlaces de descarga de la aplicaciÃŗn", "app_settings": "Ajustes de la aplicaciÃŗn", @@ -565,19 +546,27 @@ "archive": "Archivo", "archive_action_prompt": "{count} aÃąadido(s) al archivo", "archive_or_unarchive_photo": "Archivar o restaurar foto", + "archive_page_no_archived_assets": "No se encontraron recursos archivados", + "archive_page_title": "Archivo ({count})", "archive_size": "TamaÃąo de archivo comprimido", "archive_size_description": "Configure el tamaÃąo del archivo para descargas (en GiB)", "archived": "Archivado", "archived_count": "{count, plural, one {# archivado} other {# archivados}}", "are_these_the_same_person": "ÂŋSon la misma persona?", "are_you_sure_to_do_this": "ÂŋEstÃĄs seguro de que quieres hacer esto?", + "array_field_not_fully_supported": "Los campos de la matriz requieren ediciÃŗn manual de JSON", + "asset_action_delete_err_read_only": "No se puede borrar recurso(s) de solo lectura, omitiendo", + "asset_action_share_err_offline": "No se pudo obtener recurso(s) sin conexiÃŗn, omitiendo", "asset_added_to_album": "AÃąadido al ÃĄlbum", "asset_adding_to_album": "AÃąadiendo al ÃĄlbumâ€Ļ", "asset_created": "Recurso creado", - "asset_day_count": "{date}: {count, plural, one {# elemento} other {# elementos}}", "asset_description_updated": "La descripciÃŗn del recurso ha sido actualizada", + "asset_filename_is_offline": "El recurso {filename} estÃĄ desconectado", + "asset_has_unassigned_faces": "El recurso no tiene rostros asignados", "asset_hashing": "Calculando hashâ€Ļ", "asset_list_group_by_sub_title": "Agrupar por", + "asset_list_layout_settings_dynamic_layout_title": "DiseÃąo dinÃĄmico", + "asset_list_layout_settings_group_automatically": "AutomÃĄtico", "asset_list_layout_settings_group_by": "Agrupar recursos por", "asset_list_layout_settings_group_by_month_day": "Mes + día", "asset_list_layout_sub_title": "DisposiciÃŗn", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "No se ha encontrado el recurso en iCloud. Es posible que no se pueda acceder al recurso debido a un archivo defectuoso almacenado en iCloud", "asset_offline": "Recurso sin conexiÃŗn", "asset_offline_description": "Este recurso externo ya no se encuentra en el disco. Por favor, pÃŗngase en contacto con su administrador de Immich para obtener ayuda.", + "asset_restored_successfully": "Recursos restaurados exitosamente", "asset_skipped": "Omitido", "asset_skipped_in_trash": "En la papelera", + "asset_trashed": "Recurso eliminado", "asset_troubleshoot": "DiagnÃŗstico del recurso", "asset_uploaded": "Subido", "asset_uploading": "Subiendoâ€Ļ", "asset_viewer_settings_subtitle": "Administra las configuraciones de tu visor de fotos", "asset_viewer_settings_title": "Visor de recursos", "assets": "Recursos", + "assets_added_count": "{count, plural, one {# recurso aÃąadido} other {# recursos aÃąadidos}}", "assets_added_to_album_count": "{count, plural, one {# recurso aÃąadido} other {# recurso aÃąadidos}} al ÃĄlbum", - "assets_added_to_album_partial_count": "Agregados {successCount} de {totalCount} {totalCount, plural, one {asset} other {assets}} al album", "assets_added_to_albums_count": "{assetTotal, plural, one {# aÃąadido} other {# aÃąadidos}} {albumTotal, plural, one {# al ÃĄlbum} other {# a los ÃĄlbumes}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {El recurso no se puede aÃąadir al ÃĄlbum} other {Los recursos no se pueden aÃąadir al ÃĄlbum}}", "assets_cannot_be_added_to_albums": "{count, plural, one {El recurso} other {Los recursos}} no se {count, plural, one {puede} other {pueden}} aÃąadir a ninguno de los ÃĄlbumes", "assets_count": "{count, plural, one {# recurso} other {# recursos}}", + "assets_deleted_permanently": "{count} recurso(s) eliminado(s) permanentemente", + "assets_deleted_permanently_from_server": "{count} recurso(s) eliminado(s) de forma permanente del servidor de Immich", + "assets_downloaded_failed": "{count, plural, one {# archivo descargado - {error} archivo fallido} other {# archivos descargados - {error} archivos fallidos}}", + "assets_downloaded_successfully": "{count, plural, one {# archivo descargado exitosamente} other {# archivos descargados exitosamente}}", "assets_moved_to_trash_count": "{count, plural, one {# recurso movido} other {# recursos movidos}} a la papelera", "assets_permanently_deleted_count": "Eliminado permanentemente {count, plural, one {# recurso} other {# recursos}}", "assets_removed_count": "Eliminado {count, plural, one {# recurso} other {# recursos}}", + "assets_removed_permanently_from_device": "{count} recurso(s) eliminado(s) permanentemente de su dispositivo", "assets_restore_confirmation": "ÂŋEstÃĄs seguro de que quieres restaurar todos tus recursos eliminados? ÂĄNo puede deshacer esta acciÃŗn! Tenga en cuenta que los recursos sin conexiÃŗn no se pueden restaurar de esta manera.", "assets_restored_count": "Restaurado {count, plural, one {# recurso} other {# recursos}}", + "assets_restored_successfully": "{count} recurso(s) restaurado(s) exitosamente", "assets_trashed": "{count} recurso(s) eliminado(s)", "assets_trashed_count": "Borrado {count, plural, one {# recurso} other {# recursos}}", + "assets_trashed_from_server": "{count} recurso(s) enviado(s) a la papelera desde el servidor de Immich", "assets_were_part_of_album_count": "{count, plural, one {El recurso ya forma} other {Los recursos ya forman}} parte del ÃĄlbum", "assets_were_part_of_albums_count": "{count, plural, one {El recurso ya es} other {Los recursos ya son}} parte de los ÃĄlbumes", "authorized_devices": "Dispositivos autorizados", @@ -617,48 +615,87 @@ "autoplay_slideshow": "PresentaciÃŗn con reproducciÃŗn automÃĄtica", "back": "AtrÃĄs", "back_close_deselect": "AtrÃĄs, cerrar o anular la selecciÃŗn", + "background_backup_running_error": "Ya se estÃĄ ejecutando la copia de seguridad en segundo plano, no se puede iniciar la copia de seguridad manual", "background_location_permission": "Permiso de ubicaciÃŗn en segundo plano", "background_location_permission_content": "Para poder cambiar de red mientras se ejecuta en segundo plano, Immich debe tener *siempre* acceso a la ubicaciÃŗn precisa para que la aplicaciÃŗn pueda leer el nombre de la red Wi-Fi", "background_options": "Opciones de segundo plano", "backup": "Copia de seguridad", + "backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({count})", "backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir", "backup_album_selection_page_assets_scatter": "Los recursos pueden dispersarse en varios ÃĄlbumes. De este modo, los ÃĄlbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.", "backup_album_selection_page_select_albums": "Seleccionar ÃĄlbumes", "backup_album_selection_page_selection_info": "InformaciÃŗn sobre la selecciÃŗn", + "backup_album_selection_page_total_assets": "Total de recursos Ãēnicos", "backup_albums_sync": "SincronizaciÃŗn de ÃĄlbumes de respaldo", + "backup_all": "Todos", + "backup_background_service_backup_failed_message": "Error al copiar recursos. Reintentandoâ€Ļ", "backup_background_service_complete_notification": "Copia de seguridad de recursos completada", + "backup_background_service_connection_failed_message": "Error al conectar con el servidor. Reintentandoâ€Ļ", + "backup_background_service_current_upload_notification": "Subiendo {filename}", "backup_background_service_default_notification": "Comprobando nuevos recursosâ€Ļ", + "backup_background_service_error_title": "Error de copia de seguridad", "backup_background_service_in_progress_notification": "Creando copia de seguridad de tus recursosâ€Ļ", + "backup_background_service_upload_failure_notification": "Error al subir {filename}", "backup_controller_page_albums": "Álbumes de copia de seguridad", + "backup_controller_page_background_app_refresh_disabled_content": "Activa la actualizaciÃŗn en segundo plano de la aplicaciÃŗn en ConfiguraciÃŗn > General > ActualizaciÃŗn en segundo plano para usar la copia de seguridad en segundo plano.", + "backup_controller_page_background_app_refresh_disabled_title": "ActualizaciÃŗn en segundo plano desactivada", + "backup_controller_page_background_app_refresh_enable_button_text": "Ir a configuraciÃŗn", "backup_controller_page_background_battery_info_link": "MuÊstrame cÃŗmo", "backup_controller_page_background_battery_info_message": "Para obtener la mejor experiencia de copia de seguridad en segundo plano, desactiva cualquier optimizaciÃŗn de batería que restrinja la actividad en segundo plano para Immich.\n\nDado que esto es específico en cada dispositivo, busca la informaciÃŗn necesaria de el fabricante de tu dispositivo.", "backup_controller_page_background_battery_info_ok": "Aceptar", "backup_controller_page_background_battery_info_title": "Optimizaciones de batería", + "backup_controller_page_background_charging": "Solo mientras se carga", + "backup_controller_page_background_configure_error": "Error al configurar el servicio en segundo plano", "backup_controller_page_background_delay": "Retrasar la copia de seguridad de los nuevos recursos: {duration}", + "backup_controller_page_background_description": "Activa el servicio en segundo plano para copiar automÃĄticamente cualquier recurso nuevo sin necesidad de abrir la aplicaciÃŗn", + "backup_controller_page_background_is_off": "La copia de seguridad en segundo plano automÃĄtica estÃĄ desactivada", + "backup_controller_page_background_is_on": "La copia de seguridad en segundo plano automÃĄtica estÃĄ activada", + "backup_controller_page_background_turn_off": "Desactivar el servicio en segundo plano", + "backup_controller_page_background_turn_on": "Activar el servicio en segundo plano", + "backup_controller_page_background_wifi": "Solo en Wi-Fi", "backup_controller_page_backup": "Copia de seguridad", "backup_controller_page_backup_selected": "Seleccionado: ", "backup_controller_page_backup_sub": "Fotos y videos respaldados", + "backup_controller_page_created": "Creado el: {date}", + "backup_controller_page_desc_backup": "Active la copia de seguridad para subir automÃĄticamente los nuevos recursos al servidor cuando se abre la aplicaciÃŗn.", "backup_controller_page_excluded": "Excluido: ", + "backup_controller_page_failed": "Fallidos ({count})", + "backup_controller_page_filename": "Nombre del archivo: {filename} [{size}]", + "backup_controller_page_id": "Id.: {id}", + "backup_controller_page_info": "InformaciÃŗn de la copia de seguridad", "backup_controller_page_none_selected": "Ninguno seleccionado", "backup_controller_page_remainder": "Restante", "backup_controller_page_remainder_sub": "Fotos y videos restantes para hacer una copia de seguridad de la selecciÃŗn", "backup_controller_page_server_storage": "Almacenamiento en el servidor", + "backup_controller_page_start_backup": "Iniciar copia de seguridad", + "backup_controller_page_status_off": "La copia de seguridad estÃĄ desactivada", + "backup_controller_page_status_on": "La copia de seguridad estÃĄ activada", "backup_controller_page_storage_format": "{used} de {total} usadas", "backup_controller_page_to_backup": "Álbumes a respaldar", "backup_controller_page_total_sub": "Todas las fotos y vídeos Ãēnicos de los ÃĄlbumes seleccionados", + "backup_controller_page_turn_off": "Apagar la copia de seguridad", + "backup_controller_page_turn_on": "Activar la copia de seguridad", + "backup_controller_page_uploading_file_info": "Subiendo informaciÃŗn del archivo", + "backup_err_only_album": "No se puede eliminar el Ãēnico ÃĄlbum", "backup_error_sync_failed": "La sincronizaciÃŗn fallÃŗ. No es posible procesar la copia de seguridad.", "backup_info_card_assets": "recursos", + "backup_manual_cancelled": "Cancelado", + "backup_manual_in_progress": "Subida ya en progreso. Vuelve a intentarlo mÃĄs tarde", + "backup_manual_success": "Éxito", + "backup_manual_title": "Estado de la subida", "backup_options": "Opciones de copia de seguridad", + "backup_options_page_title": "Opciones de copia de seguridad", + "backup_setting_subtitle": "Administra las configuraciones de respaldo en segundo y primer plano", "backup_settings_subtitle": "Configura las opciones de subida", + "backup_upload_details_page_more_details": "Toca para mÃĄs detalles", "backward": "Retroceder", - "battery_optimization_backup_reliability": "Desactivar las optimizaciones de batería puede mejorar la fiabilidad de las copias de seguridad en segundo plano", "biometric_auth_enabled": "AutentificaciÃŗn biomÊtrica habilitada", "biometric_locked_out": "EstÃĄs bloqueado de la autentificaciÃŗn biomÊtrica", "biometric_no_options": "Sin opciones biomÊtricas disponibles", "biometric_not_available": "AutentificaciÃŗn biomÊtrica no disponible en este dispositivo", + "birthdate_saved": "Fecha de nacimiento guardada con Êxito", "birthdate_set_description": "La fecha de nacimiento se utiliza para calcular la edad de esta persona en el momento de la fotografía.", "blurred_background": "Fondo borroso", - "browse_templates": "Explorar Plantillas", "bugs_and_feature_requests": "Errores y solicitudes de funciones", "build": "CompilaciÃŗn", "build_image": "Imagen de compilaciÃŗn", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "ÂŋEstas seguro de que desea mantener {count, plural, one {# recurso duplicado} other {# recursos duplicados}}? Esto resolverÃĄ todos los grupos duplicados sin borrar nada.", "bulk_trash_duplicates_confirmation": "ÂŋEstas seguro de que desea eliminar masivamente {count, plural, one {# recurso duplicado} other {# recursos duplicados}}? Esto mantendrÃĄ el recurso mÃĄs grande de cada grupo y eliminarÃĄ todos los demÃĄs duplicados.", "buy": "Comprar Immich", + "cache_settings_clear_cache_button": "Borrar cachÊ", + "cache_settings_clear_cache_button_title": "Borra la cachÊ de la aplicaciÃŗn. Esto afectarÃĄ significativamente el rendimiento de la aplicaciÃŗn hasta que se reconstruya la cachÊ.", + "cache_settings_duplicated_assets_clear_button": "LIMPIAR", + "cache_settings_duplicated_assets_subtitle": "Fotos y vídeos ignorados por la aplicaciÃŗn", + "cache_settings_duplicated_assets_title": "Recursos duplicados ({count})", + "cache_settings_statistics_album": "Miniaturas de la biblioteca", + "cache_settings_statistics_full": "ImÃĄgenes completas", + "cache_settings_statistics_shared": "Miniaturas de ÃĄlbumes compartidos", + "cache_settings_statistics_thumbnail": "Miniaturas", + "cache_settings_statistics_title": "Uso de cachÊ", + "cache_settings_subtitle": "Controla el comportamiento del almacenamiento en cachÊ de la aplicaciÃŗn mÃŗvil Immich", + "cache_settings_tile_subtitle": "Controla el comportamiento del almacenamiento local", + "cache_settings_tile_title": "Almacenamiento local", + "cache_settings_title": "ConfiguraciÃŗn de la cachÊ", "camera": "CÃĄmara", "camera_brand": "Fabricante de cÃĄmara", "camera_model": "Modelo de cÃĄmara", @@ -678,10 +729,10 @@ "cannot_update_the_description": "No se puede actualizar la descripciÃŗn", "cast": "Transmitir", "cast_description": "Configura los posibles destinos de retransmisiÃŗn", - "change": "Cambiar", "change_date": "Cambiar fecha", "change_description": "Cambiar descripciÃŗn", "change_display_order": "Cambiar orden de visualizaciÃŗn", + "change_expiration_time": "Cambiar fecha de caducidad", "change_location": "Cambiar ubicaciÃŗn", "change_name": "Cambiar nombre", "change_name_successfully": "Nombre cambiado exitosamente", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Las contraseÃąas no coinciden", "change_password_form_reenter_new_password": "Vuelve a ingresar la nueva contraseÃąa", "change_pin_code": "Cambiar PIN", + "change_trigger": "Cambiar disparador", + "change_trigger_prompt": "ÂŋSeguro que quieres cambiar el disparador? Esto eliminarÃĄ todas las acciones y filtros existentes.", "change_your_password": "Cambia tu contraseÃąa", "changed_visibility_successfully": "Visibilidad cambiada correctamente", "charging": "Cargando", "charging_requirement_mobile_backup": "La copia de seguridad en segundo plano requiere que el dispositivo se estÊ cargando", + "check_corrupt_asset_backup": "Comprobar copias de seguridad de recursos corruptos", + "check_corrupt_asset_backup_button": "Realizar comprobaciÃŗn", + "check_corrupt_asset_backup_description": "Ejecutar esta comprobaciÃŗn solo por Wi-Fi y una vez que todos los recursos hayan sido respaldados. El procedimiento puede tardar unos minutos.", "check_logs": "Comprobar Registros", "checksum": "Suma de comprobaciÃŗn", - "choose": "Elegir", "choose_matching_people_to_merge": "Elija ocurrencias duplicadas de la misma persona para fusionar", "city": "Ciudad", "cleanup_confirm_description": "Immich encontrÃŗ {count} recursos (creados antes de {date}) respaldados de manera segura en el servidor. ÂŋDesea eliminar las copias locales de este dispositivo?", @@ -712,17 +767,18 @@ "cleanup_found_assets_with_size": "Se encontraron {count} recursos respaldados ({size})", "cleanup_icloud_shared_albums_excluded": "Los ÃĄlbumes compartidos de iCloud estÃĄn excluidos del escaneo", "cleanup_no_assets_found": "No se encontraron recursos que coincidan con los criterios anteriores. Liberar espacio solo puede eliminar recursos respaldados en el servidor", - "cleanup_preview_title": "{count} recursos a eliminar", + "cleanup_preview_title": "{count} recursos a remover", "cleanup_step3_description": "Busque recursos respaldados que coincidan con su fecha y conserve la configuraciÃŗn.", "cleanup_step4_summary": "{count} recursos (creados antes del {date}) para eliminar de tu dispositivo local. Las fotos seguirÃĄn accesibles desde la app de Immich.", "cleanup_trash_hint": "Para completar la liberaciÃŗn de espacio, abra la aplicaciÃŗn de fotos y vacíe la papelera", "clear": "Limpiar", "clear_all": "Limpiar todo", "clear_all_recent_searches": "Borrar bÃēsquedas recientes", - "clear_failed_count": "Limpieza fallida ({count})", "clear_file_cache": "Limpiar la cachÊ de archivos", "clear_message": "Limpiar mensaje", "clear_value": "Limpiar valor", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Introduzca la contraseÃąa", "client_cert_import": "Importar", "client_cert_import_success_msg": "El certificado de cliente estÃĄ importado", "client_cert_invalid_msg": "Archivo de certificado no vÃĄlido o contraseÃąa incorrecta", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "El certificado de cliente se ha eliminado", "client_cert_subtitle": "Solo se admite el formato PKCS12 (.p12, .pfx). La importaciÃŗn/eliminaciÃŗn de certificados solo estÃĄ disponible antes de iniciar sesiÃŗn", "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", + "clockwise": "En el sentido de las agujas del reloj", "close": "Cerrar", "collapse": "Agrupar", "collapse_all": "Desplegar todo", "color": "Color", + "color_theme": "Color del tema", "command": "Comando", "command_palette_prompt": "Encuentra rÃĄpidamente pÃĄginas, acciones o comandos", "command_palette_to_close": "para cerrar", @@ -747,7 +805,6 @@ "comments_are_disabled": "Los comentarios estÃĄn deshabilitados", "common_create_new_album": "Crear nuevo ÃĄlbum", "completed": "Completado", - "configuration": "ConfiguraciÃŗn", "confirm": "Confirmar", "confirm_admin_password": "Confirmar contraseÃąa del administrador", "confirm_delete_face": "ÂŋEstÃĄs seguro que deseas eliminar la cara de {name} del recurso?", @@ -762,16 +819,19 @@ "contain": "Incluido", "context": "Contexto", "continue": "Continuar", - "control_bottom_app_bar_add_tags": "AÃąadir etiquetas", + "control_bottom_app_bar_create_new_album": "Crear nuevo ÃĄlbum", + "control_bottom_app_bar_delete_from_immich": "Borrar de Immich", "control_bottom_app_bar_delete_from_local": "Borrar del dispositivo", "control_bottom_app_bar_edit_location": "Editar ubicaciÃŗn", "control_bottom_app_bar_edit_time": "Editar fecha y hora", + "control_bottom_app_bar_share_link": "Enlace para compartir", + "control_bottom_app_bar_share_to": "Compartir a", "control_bottom_app_bar_trash_from_immich": "Mover a la papelera", "copied_image_to_clipboard": "Imagen copiada al portapapeles.", "copied_to_clipboard": "ÂĄCopiado al portapapeles!", "copy_error": "Error de copiado", + "copy_file_path": "Copiar ruta del archivo", "copy_image": "Copiar Imagen", - "copy_json": "Copiar JSON", "copy_link": "Copiar enlace", "copy_link_to_clipboard": "Copiar enlace al portapapeles", "copy_password": "Copiar contraseÃąa", @@ -789,6 +849,7 @@ "create_link_to_share": "Crear enlace compartido", "create_link_to_share_description": "Permitir que cualquier persona con el enlace vea la(s) foto(s) seleccionada(s)", "create_new": "CREAR NUEVO", + "create_new_face": "Crear nueva cara", "create_new_person": "Crear nueva persona", "create_new_person_hint": "Asignar los recursos seleccionados a una nueva persona", "create_new_user": "Crear nuevo usuario", @@ -805,33 +866,37 @@ "created_at": "Creado", "creating_linked_albums": "Creando ÃĄlbumes vinculados...", "crop": "Recortar", + "crop_aspect_ratio_fixed": "Fijado", "crop_aspect_ratio_free": "Libre", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Cuadrado", + "curated_object_page_title": "Objetos", "current_device": "Dispositivo actual", "current_pin_code": "PIN actual", "current_server_address": "DirecciÃŗn actual del servidor", "custom_date": "Fecha personalizada", "custom_locale": "ConfiguraciÃŗn regional personalizada", "custom_locale_description": "Dar formato a fechas, horas y nÃēmeros segÃēn el idioma y regiÃŗn seleccionados", + "custom_url": "URL personalizada", "cutoff_date_description": "Conserva fotos del Ãēltimoâ€Ļ", "cutoff_day": "{count, plural, one {día} other {días}}", "cutoff_year": "{count, plural, one {aÃąo} other {aÃąos}}", + "daily_title_text_date": "E dd, MMM", + "daily_title_text_date_year": "E dd de MMM, yyyy", "dark": "Oscuro", "dark_theme": "Cambiar a tema oscuro", "date": "Fecha", "date_after": "Fecha posterior", "date_and_time": "Fecha y hora", "date_before": "Fecha anterior", - "date_of_birth": "Fecha de nacimiento", + "date_format": "E d, LLL y â€ĸ h:mm a", "date_of_birth_saved": "Guardada con Êxito la fecha de nacimiento", "date_range": "Rango de fechas", - "date_time_original": "Fecha/Hora Original", "day": "Día", "days": "Días", "deduplicate_all": "Deduplicar todo", - "default_quality_subtitle": "Calidad utilizada al presionar compartir. MantÊn presionado el botÃŗn de compartir para elegir cada vez.", - "default_share_quality": "Calidad al compartir por defecto", + "default_locale": "ConfiguraciÃŗn regional predeterminada", + "default_locale_description": "Formatear fechas y nÃēmeros segÃēn la configuraciÃŗn regional del navegador", "delete": "Eliminar", "delete_action_confirmation_message": "ÂŋEstÃĄ seguro que desea eliminar este recurso? Esta acciÃŗn lo moverÃĄ a la papelera del servidor y le preguntarÃĄ si desea eliminarlo localmente", "delete_action_prompt": "{count} eliminados", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Estos elementos serÃĄn eliminados permanentemente de Immich y de tu dispositivo", "delete_dialog_alert_local": "Estos elementos se eliminarÃĄn permanente de tu dispositivo pero seguirÃĄn disponibles en el servidor de Immich", "delete_dialog_alert_local_non_backed_up": "Algunos de los elementos no tienen copia de seguridad en Immich y serÃĄn borrados permanentemente de tu dispositivo", + "delete_dialog_alert_remote": "Estas imÃĄgenes van a ser borradas permanentemente del servidor de Immich", + "delete_dialog_ok_force": "Borrar de todos modos", "delete_dialog_title": "Eliminar permanentemente", "delete_duplicates_confirmation": "ÂŋEstÃĄ seguro de que desea eliminar permanentemente estos duplicados?", "delete_face": "Eliminar cara", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "ÂŋEstÃĄs seguro de que deseas eliminar la etiqueta {tagName} ?", "delete_user": "Eliminar usuario", "deleted_shared_link": "Enlace compartido eliminado", + "deletes_missing_assets": "Elimina recursos que faltan en el disco duro", "description": "DescripciÃŗn", + "description_input_hint_text": "AÃąadir descripciÃŗn...", + "description_input_submit_error": "Error al actualizar la descripciÃŗn, comprueba el registro para obtener mÃĄs detalles", "deselect_all": "Deseleccionar todo", "details": "Detalles", "direction": "DirecciÃŗn", "disable": "Desactivar", "disabled": "Deshabilitado", + "disallow_edits": "Bloquear ediciÃŗn", "discord": "Discord", "discover": "Descubrir", "discovered_devices": "Dispositivos descubiertos", "dismiss_all_errors": "Descartar todos los errores", + "dismiss_error": "Descartar error", "display_options": "Opciones de pantalla", "display_order": "Orden de visualizaciÃŗn", "display_original_photos": "Mostrar fotos originales", @@ -876,9 +948,11 @@ "documentation": "DocumentaciÃŗn", "done": "Hecho", "download": "Descargar", + "download_action_prompt": "Descargando {count} recursos", "download_canceled": "Descarga cancelada", "download_complete": "Descarga completada", "download_enqueue": "Descarga en cola", + "download_error": "Error al descargar", "download_failed": "Descarga fallida", "download_finished": "Descarga completada", "download_include_embedded_motion_videos": "Vídeos incrustados", @@ -888,15 +962,15 @@ "download_paused": "Descarga en pausa", "download_settings": "Descargar", "download_settings_description": "Administrar configuraciones relacionadas con la descarga de recursos", + "download_started": "Descarga iniciada", + "download_sucess": "Descarga exitosa", + "download_sucess_android": "Los archivos se han descargado en DCIM/Immich", "download_waiting_to_retry": "Esperando para reintentar", "downloading": "Descargando", "downloading_asset_filename": "Descargando recurso {filename}", "downloading_from_icloud": "Descargando desde iCloud", "downloading_media": "Descargando medios", - "drag_to_reorder": "Arrastra a reorder", "drop_files_to_upload": "Suelta los archivos en cualquier lugar para subirlos", - "duplicate": "Duplicar", - "duplicate_workflow": "Duplicar el flujo de trabajo", "duplicates": "Duplicados", "duplicates_description": "Resuelve cada grupo indicando cuÃĄles son duplicados, si los hay.", "duration": "DuraciÃŗn", @@ -908,7 +982,9 @@ "edit_date_and_time": "Editar fecha y hora", "edit_date_and_time_action_prompt": "{count} fecha y hora editadas", "edit_date_and_time_by_offset": "Cambiar fecha usando una desviaciÃŗn", + "edit_date_and_time_by_offset_interval": "Nuevo intervalo de fechas: {from} - {to}", "edit_description": "Editar descripciÃŗn", + "edit_description_prompt": "Por favor selecciona una nueva descripciÃŗn:", "edit_exclusion_pattern": "Editar patrÃŗn de exclusiÃŗn", "edit_faces": "Editar rostros", "edit_key": "Editar clave", @@ -923,6 +999,9 @@ "edit_user": "Editar usuario", "edit_workflow": "Editar flujo de trabajo", "editor": "Editor", + "editor_close_without_save_prompt": "No se guardarÃĄn los cambios", + "editor_close_without_save_title": "ÂŋCerrar el editor?", + "editor_confirm_reset_all_changes": "ÂŋSeguro que quieres restablecer los cambios?", "editor_discard_edits_confirm": "Descartar ediciones", "editor_discard_edits_prompt": "Tiene ediciones sin guardar. ÂŋEstÃĄ seguro de que quieres descartarlas?", "editor_discard_edits_title": "ÂŋDescartar ediciones?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Introduce tu cÃŗdigo PIN", "enter_your_pin_code_subtitle": "Introduce tu cÃŗdigo PIN para acceder a la carpeta protegida", "error": "Error", + "error_change_sort_album": "No se pudo cambiar el orden de visualizaciÃŗn del ÃĄlbum", "error_delete_face": "Error al eliminar la cara del recurso", + "error_getting_places": "Error obteniendo lugares", "error_loading_albums": "Error al cargar ÃĄlbumes", "error_loading_image": "Error al cargar la imagen", "error_loading_partners": "Error al cargar miembros: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "No se pudo eliminar la clave del producto", "failed_to_reset_pin_code": "No se pudo restablecer el cÃŗdigo PIN", "failed_to_stack_assets": "No se pudieron agrupar los recursos", - "failed_to_tag_assets": "Error al etiquetar recursos", "failed_to_unstack_assets": "Error al desagrupar los recursos", "failed_to_update_notification_status": "Error al actualizar el estado de la notificaciÃŗn", "incorrect_email_or_password": "ContraseÃąa o email incorrecto", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "AÃąadir descripciÃŗnâ€Ļ", "exif_bottom_sheet_description_error": "Error al actualizar la descripciÃŗn", + "exif_bottom_sheet_details": "DETALLES", + "exif_bottom_sheet_location": "UBICACIÓN", "exif_bottom_sheet_no_description": "Sin descripciÃŗn", + "exif_bottom_sheet_people": "PERSONAS", + "exif_bottom_sheet_person_add_person": "AÃąadir nombre", "exit_slideshow": "Salir de la presentaciÃŗn", "expand": "Expandir", "expand_all": "Expandir todo", + "experimental_settings_new_asset_list_subtitle": "Trabajo en progreso", + "experimental_settings_new_asset_list_title": "Habilitar cuadrícula fotogrÃĄfica experimental", + "experimental_settings_subtitle": "ÂĄÃšsalo bajo tu propia responsabilidad!", + "experimental_settings_title": "Experimental", "expire_after": "Caducar despuÊs de", "expired": "Caducado", "expires_date": "Caduca el {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Exportar a JSON", "export_database": "Exportar base de datos", "export_database_description": "Exportar la base de datos SQLite", - "exposure_time": "Tiempo de exposiciÃŗn", "extension": "ExtensiÃŗn", "external": "Externo", "external_libraries": "Bibliotecas externas", "external_network": "Red externa", "external_network_sheet_info": "Cuando no tengas conexiÃŗn con tu red Wi-Fi preferida, la aplicaciÃŗn se conectarÃĄ al servidor utilizando la primera de las URL siguientes a la que pueda acceder, empezando de arriba hacia abajo", - "f_number": "Obertura", "face_unassigned": "Sin asignar", "failed": "Fallido", "failed_count": "Fallido: {count}", "failed_to_authenticate": "Fallo al autentificar", - "failed_to_delete_file": "Error al eliminar el archivo", "failed_to_load_assets": "Error al cargar los recursos", "failed_to_load_folder": "No se pudo cargar la carpeta", "favorite": "Favorito", "favorite_action_prompt": "{count} aÃąadido(s) a Favoritos", "favorite_or_unfavorite_photo": "Foto favorita o no favorita", "favorites": "Favoritos", + "favorites_page_no_favorites": "No se encontraron recursos marcados como favoritos", "feature_photo_updated": "Foto destacada actualizada", "features": "Características", + "features_in_development": "Características en desarrollo", "features_setting_description": "Administrar las características de la aplicaciÃŗn", "file_name_or_extension": "Nombre del archivo o extensiÃŗn", "file_name_text": "Nombre del archivo", + "file_name_with_value": "Nombre del archivo: {file_name}", "file_size": "TamaÃąo del archivo", "filename": "Nombre del archivo", "filetype": "Tipo de archivo", "filter": "Filtro", + "filter_description": "Condiciones para filtrar los recursos objetivo", "filter_people": "Filtrar personas", "filter_places": "Filtrar lugares", "filter_tags": "Filtrar etiquetas", "filters": "Filtros", + "find_them_fast": "EncuÊntrelos rÃĄpidamente por nombre con la bÃēsqueda", "first": "Primero", "fix_incorrect_match": "Corregir coincidencia incorrecta", - "focal_length": "Distancia focal", "folder": "Carpeta", "folder_not_found": "Carpeta no encontrada", "folders": "Carpetas", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Elimina tus fotos y videos de tu dispositivo para liberar espacio. Los respaldos en el servidor se mantendrÃĄn seguros.", "free_up_space_settings_subtitle": "Liberar espacio del dispositivo", "full_path": "Ruta completa: {path}", - "full_path_or_folder": "Ruta completa o carpeta", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidad carga recursos externos desde Google para poder funcionar.", "general": "General", @@ -1168,6 +1256,7 @@ "group_owner": "Agrupar por propietario", "group_places_by": "Agrupar lugares por...", "group_year": "Agrupar por aÃąo", + "haptic_feedback_switch": "Activar respuesta hÃĄptica", "haptic_feedback_title": "Respuesta hÃĄptica", "has_quota": "Cuota asignada", "hash_asset": "Generar hash del recurso", @@ -1188,12 +1277,29 @@ "hide_schema": "Ocultar esquema", "hide_text_recognition": "Ocultar reconocimiento de texto", "hide_unnamed_people": "Ocultar personas anÃŗnimas", + "home_page_add_to_album_conflicts": "{added} recursos aÃąadidos al ÃĄlbum {album}.{failed} recursos ya existen en el ÃĄlbum.", + "home_page_add_to_album_err_local": "AÃēn no se pueden aÃąadir recursos locales a ÃĄlbumes, omitiendo", + "home_page_add_to_album_success": "Se aÃąadieron {added} recursos al ÃĄlbum {album}.", + "home_page_album_err_partner": "AÃēn no se pueden aÃąadir recursos de un compaÃąero a un ÃĄlbum, omitiendo", + "home_page_archive_err_local": "Los recursos locales no pueden ser archivados, omitiendo", + "home_page_archive_err_partner": "No se pueden archivar los recursos de un compaÃąero, omitiendo", "home_page_building_timeline": "Construyendo la línea de tiempo", + "home_page_delete_err_partner": "No se pueden eliminar los recursos de un compaÃąero, omitiendo", + "home_page_delete_remote_err_local": "Recursos locales en la selecciÃŗn de eliminaciÃŗn remota, omitiendo", + "home_page_favorite_err_local": "AÃēn no se pueden marcar como favoritos los recursos locales, omitiendo", + "home_page_favorite_err_partner": "AÃēn no se pueden marcar como favoritos los recursos de un compaÃąero, omitiendo", + "home_page_first_time_notice": "Si es la primera vez que usas la aplicaciÃŗn, asegÃērate de elegir un ÃĄlbum como copia de seguridad para que la línea de tiempo pueda mostrar fotos y vídeos en Êl", + "home_page_locked_error_local": "No se pueden mover recursos locales a una carpeta protegida, omitiendo", + "home_page_locked_error_partner": "No se pueden mover los recursos de un compaÃąero a una carpeta protegida, omitiendo", + "home_page_share_err_local": "No se pueden compartir recursos locales a travÊs de un enlace, omitiendo", + "home_page_upload_err_limit": "Solo se pueden subir 30 recursos simultÃĄneamente, omitiendo", "host": "Host", "hour": "Hora", "hours": "Horas", "id": "ID", "idle": "Inactivo", + "ignore_icloud_photos": "Ignorar fotos de iCloud", + "ignore_icloud_photos_description": "Las fotos almacenadas en iCloud no se subirÃĄn a Immich", "image": "Imagen", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tomada el {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tomada con {person1} el {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1} y {person2} el {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {person3} el {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tomada en {city}, {country} con {person1}, {person2}, y {additionalCount, number} mÃĄs el {date}", + "image_saved_successfully": "ImÃĄgenes guardas", + "image_viewer_page_state_provider_download_started": "Descarga iniciada", + "image_viewer_page_state_provider_download_success": "Descarga exitosa", + "image_viewer_page_state_provider_share_error": "Error al compartir", "immich_logo": "Logo de Immich", "immich_web_interface": "Interfaz web de Immich", "import_from_json": "Importar desde JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Compartir individualmente", "individual_shares": "Acciones individuales", "info": "InformaciÃŗn", - "integrity_checks": "Verificaciones de integridad", "interval": { "day_at_onepm": "Todos los días a las 1pm", "hours": "Cada {hours, plural, one {hora} other {{hours, number} horas}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Formato de fecha incorrecto", "invite_people": "Invitar a personas", "invite_to_album": "Invitar al ÃĄlbum", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Busca ejecuciÃŗn en {dateTime}", + "ios_debug_info_last_sync_at": "Última sincronizaciÃŗn en {dateTime}", + "ios_debug_info_no_processes_queued": "NingÃēn proceso de fondo encolado", + "ios_debug_info_no_sync_yet": "Todavía no se ha ejecutado ningÃēn trabajo de sincronizaciÃŗn en segundo plano", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proceso encolado de fondo} other {{count} procesos encolados de fondo}}", + "ios_debug_info_processing_ran_at": "El procesamiento se ejecutÃŗ el {dateTime}", "items_count": "{count, plural, one {# elemento} other {# elementos}}", "jobs": "Tareas", + "json_editor": "Editor JSON", + "json_error": "Error JSON", "keep": "Conservar", "keep_albums": "Conservar ÃĄlbumes", "keep_albums_count": "Mantener {count} {count, plural, one {ÃĄlbum} other {ÃĄlbumes}}", @@ -1259,12 +1375,14 @@ "leave": "Abandonar", "leave_album": "Abandonar ÃĄlbum", "lens_model": "Modelo de objetivo", - "less": "Menos", "let_others_respond": "Permitir que otros respondan", "level": "Nivel", "library": "Biblioteca", "library_add_folder": "AÃąadir carpeta", "library_edit_folder": "Editar carpeta", + "library_options": "Opciones de biblioteca", + "library_page_device_albums": "Álbumes en el dispositivo", + "library_page_new_album": "Nuevo ÃĄlbum", "library_page_sort_asset_count": "NÃēmero de recursos", "library_page_sort_created": "Creado mÃĄs recientemente", "library_page_sort_last_modified": "Última modificaciÃŗn", @@ -1274,14 +1392,11 @@ "light_theme": "Cambiar a tema claro", "like": "Me gusta", "like_deleted": "Me gusta eliminado", - "link": "Enlace", "link_motion_video": "Enlazar vídeo en movimiento", "link_to_docs": "Para mÃĄs informaciÃŗn, consulta la documentaciÃŗn.", "link_to_oauth": "Enlace a OAuth", "linked_oauth_account": "Cuenta OAuth vinculada", "list": "Lista", - "live": "En vivo", - "load_more": "Cargar mÃĄs", "loading": "Cargando", "loading_search_results_failed": "Error al cargar los resultados de la bÃēsqueda", "local": "Local", @@ -1310,9 +1425,11 @@ "login": "Inicio de sesiÃŗn", "login_disabled": "El inicio de sesiÃŗn ha sido desactivado", "login_form_api_exception": "ExcepciÃŗn producida por API. Por favor, comprueba el URL del servidor e intÊntalo de nuevo.", + "login_form_back_button_text": "AtrÃĄs", "login_form_email_hint": "tucorreo@correo.com", "login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto", "login_form_endpoint_url": "Enlace del punto de acceso (endpoint) del servidor", + "login_form_err_http": "Por favor, especifique http:// o https://", "login_form_err_invalid_email": "Correo electrÃŗnico no vÃĄlido", "login_form_err_invalid_url": "URL no vÃĄlida", "login_form_err_leading_whitespace": "Espacio en blanco inicial", @@ -1322,16 +1439,17 @@ "login_form_failed_login": "Error al iniciar sesiÃŗn, comprueba la URL del servidor, el correo electrÃŗnico y la contraseÃąa", "login_form_handshake_exception": "Hubo una excepciÃŗn de handshake con el servidor. Activa la compatibilidad con certificados autofirmados en la configuraciÃŗn si estÃĄs utilizando un certificado autofirmado.", "login_form_password_hint": "contraseÃąa", - "login_form_server_empty": "Introduce la URL del servidor", - "login_form_server_error": "No se pudo conectar al servidor", - "login_has_been_disabled": "El inicio de sesiÃŗn ha sido deshabilitado", + "login_form_save_login": "Mantener la sesiÃŗn iniciada", + "login_form_server_empty": "Introduce la URL del servidor.", + "login_form_server_error": "No se pudo conectar al servidor.", + "login_has_been_disabled": "El inicio de sesiÃŗn ha sido deshabilitado.", "login_password_changed_error": "Hubo un error actualizando la contraseÃąa", "login_password_changed_success": "ContraseÃąa cambiado con Êxito", "logout_all_device_confirmation": "ÂŋEstÃĄs seguro de que quieres cerrar sesiÃŗn en todos los dispositivos?", "logout_this_device_confirmation": "ÂŋEstÃĄs seguro de que quieres cerrar sesiÃŗn en este dispositivo?", "logs": "Registros", "longitude": "Longitud", - "look": "Aspecto", + "look": "Mirar", "loop_videos": "Vídeos en bucle", "loop_videos_description": "Habilite la reproducciÃŗn automÃĄtica de un video en el visor de detalles.", "main_branch_warning": "EstÃĄ utilizando una versiÃŗn de desarrollo; ÂĄle recomendamos encarecidamente que utilice una versiÃŗn de lanzamiento!", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Restaurando base de datos", "maintenance_description": "Immich se ha puesto en modo de mantenimiento.", "maintenance_end": "Finalizar el modo de mantenimiento", + "maintenance_end_error": "Error al finalizar el modo de mantenimiento.", "maintenance_logged_in_as": "SesiÃŗn iniciada actualmente como {user}", "maintenance_restore_from_backup": "Restaurar desde una copia de seguridad", "maintenance_restore_library": "Restaura tu biblioteca", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Abrir configuraciÃŗn", "manage_media_access_subtitle": "Permitir a la app Immich gestionar y mover archivos multimedia.", "manage_media_access_title": "Acceso a gestiÃŗn de archivos multimedia", + "manage_shared_links": "Administrar enlaces compartidos", "manage_sharing_with_partners": "Gestionar el uso compartido con miembros", "manage_the_app_settings": "Administrar la configuraciÃŗn de la aplicaciÃŗn", "manage_your_account": "Gestiona tu cuenta", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Administre sus dispositivos conectados", "manage_your_oauth_connection": "Administra tu conexiÃŗn OAuth", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {No hay fotos en esta ÃĄrea} one {# foto} other {# fotos}}", "map_cannot_get_user_location": "No se pudo obtener la posiciÃŗn del usuario", + "map_location_dialog_yes": "Sí", "map_location_picker_page_use_location": "Usar esta ubicaciÃŗn", "map_location_service_disabled_content": "Los servicios de ubicaciÃŗn deben estar activados para mostrar recursos de tu ubicaciÃŗn actual. ÂŋDeseas activarlos ahora?", "map_location_service_disabled_title": "Servicios de ubicaciÃŗn desactivados", - "map_marker_for_image": "Marcador del mapa para la imagen tomada en {city}, {country}", + "map_marker_for_images": "Marcador de mapa para imÃĄgenes tomadas en {city}, {country}", "map_marker_with_image": "Marcador de mapa con imagen", "map_no_location_permission_content": "Se necesitan permisos de ubicaciÃŗn para mostrar recursos de tu ubicaciÃŗn actual. ÂŋDeseas activarlos ahora?", "map_no_location_permission_title": "Permisos de ubicaciÃŗn denegados", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Últimos {days} días", "map_settings_date_range_option_year": "Último aÃąo", "map_settings_date_range_option_years": "Últimos {years} aÃąos", + "map_settings_dialog_title": "Ajustes del mapa", "map_settings_include_show_archived": "Incluir archivados", "map_settings_include_show_partners": "Incluir miembros", "map_settings_only_show_favorites": "Mostrar solo favoritas", "map_settings_theme_settings": "Tema del mapa", + "map_zoom_to_see_photos": "Alejar para ver fotos", "mark_all_as_read": "Marcar todo como leído", + "mark_as_read": "Marcar como leído", "marked_all_as_read": "Todos marcados como leídos", "matches": "Coincidencias", "matching_assets": "Recursos coincidentes", - "media_chrome": { - "auto": "Auto", - "captions": "Subtítulos", - "captions_off": "Apagado", - "closed_captions": "subtítulos", - "decode_error": "Error de decodificaciÃŗn", - "disable_captions": "Desactivar subtítulos", - "enable_captions": "Activar subtítulos", - "enter_fullscreen_mode": "Entrar en modo pantalla completa", - "exit_fullscreen_mode": "Salir del modo pantalla completa", - "loop": "Bucle", - "media_error_description": "Un error de medios causÃŗ la interrupciÃŗn de la reproducciÃŗn. El contenido podría estar daÃąado o tu navegador no admite este formato.", - "media_loading": "cargando medios", - "mute": "Silenciar", - "network_error": "Error de red", - "network_error_description": "Un error de red causÃŗ la falla en la descarga del contenido.", - "not_supported_error": "Fuente no compatible", - "playback_rate": "Velocidad de reproducciÃŗn", - "playback_rate_current": "velocidad de reproducciÃŗn actual", - "playback_rate_value": "Velocidad de reproducciÃŗn {playbackRate}", - "playback_time": "tiempo de reproducciÃŗn", - "quality": "Calidad", - "second": "segundo", - "seconds": "segundos", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} restante", - "unmute": "Reactivar sonido", - "unsupported_error_description": "OcurriÃŗ un error de incompatibilidad. El servidor o la red fallaron, o tu navegador no admite este formato.", - "video_not_loaded_unknown_time": "video no cargado, tiempo desconocido.", - "video_player": "reproductor de video", - "volume": "volumen" - }, "media_type": "Tipo de medio", "memories": "Recuerdos", "memories_all_caught_up": "Puesto al día", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "ÂŋQuieres fusionar a estas personas? Esta acciÃŗn es irreversible.", "merge_people_successfully": "Personas fusionadas correctamente", "merged_people_count": "Fusionada {count, plural, one {# persona} other {# personas}}", - "minFaces": "Cantidad de caras mínima", - "minFaces_description": "El nÃēmero mínimo de caras reconocidas para que se muestre una persona", "minimize": "Minimizar", "minute": "Minuto", "minutes": "Minutos", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Faltante", "mobile_app": "AplicaciÃŗn mÃŗvil", "mobile_app_download_onboarding_note": "Descarga la aplicaciÃŗn mÃŗvil utilizando las siguientes opciones", "model": "Modelo", - "modify_date": "Modificar fecha", "month": "Mes", + "monthly_title_text_date_format": "MMMM a", "more": "Mas", - "motion": "En movimiento", "move": "Mover", + "move_down": "Bajar", "move_off_locked_folder": "Sacar de la carpeta protegida", "move_to": "Mover a", "move_to_device_trash": "Mover a la papelera del dispositivo", "move_to_lock_folder_action_prompt": "{count} aÃąadido(s) a la carpeta protegida", "move_to_locked_folder": "Mover a la carpeta protegida", "move_to_locked_folder_confirmation": "Estas fotos y vídeos se eliminarÃĄn de todos los ÃĄlbumes; solo se podrÃĄn ver en la carpeta protegida", + "move_up": "Subir", + "moved_to_archive": "Movido(s) {count, plural, one {# recurso} other {# recursos}} a archivo", + "moved_to_library": "Movido(s) {count, plural, one {# recurso} other {# recursos}} a biblioteca", "moved_to_trash": "Movido a la papelera", + "multiselect_grid_edit_date_time_err_read_only": "No se puede cambiar la fecha del recurso(s) de solo lectura, omitiendo", + "multiselect_grid_edit_gps_err_read_only": "No se puede editar la ubicaciÃŗn de recursos de solo lectura, omitiendo", "mute_memories": "Silenciar recuerdos", "my_albums": "Mis ÃĄlbumes", - "my_immich_description": "Copiar la pÃĄgina actual como enlace de Mi Immich", - "my_immich_title": "Enlace de Mi Immich", "name": "Nombre", "name_or_nickname": "Nombre o apodo", "name_required": "El nombre es obligatorio", @@ -1480,11 +1576,12 @@ "never": "Nunca", "new_album": "Nuevo ÃĄlbum", "new_api_key": "Nueva clave API", - "new_feature": "Nueva característica", + "new_date_range": "Nuevo rango de fechas", "new_password": "Nueva contraseÃąa", "new_person": "Nueva persona", "new_pin_code": "Nuevo PIN", "new_pin_code_subtitle": "Esta es la primera vez que accedes a la carpeta protegida. Crea un cÃŗdigo PIN seguro para acceder a esta pÃĄgina", + "new_timeline": "Nueva línea de tiempo", "new_update": "Nueva actualizaciÃŗn", "new_user_created": "Nuevo usuario creado", "new_version_available": "NUEVA VERSIÓN DISPONIBLE", @@ -1492,6 +1589,7 @@ "next": "Siguiente", "next_memory": "Siguiente recuerdo", "no": "No", + "no_actions_added": "No hay acciones aÃąadidas aÃēn", "no_albums_found": "No se encontraron ÃĄlbumes", "no_albums_message": "Crea un ÃĄlbum para organizar tus fotos y vídeos", "no_albums_with_name_yet": "Parece que todavía no tienes ningÃēn ÃĄlbum con este nombre.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "No se encontraron dispositivos de transmisiÃŗn", "no_checksum_local": "Suma de verificaciÃŗn no disponible. No se pueden obtener los recursos locales", "no_checksum_remote": "Suma de verificaciÃŗn no disponible. No se puede obtener el recurso remoto", + "no_configuration_needed": "No se necesita configuraciÃŗn", "no_devices": "Dispositivos no autorizados", "no_duplicates_found": "No se encontraron duplicados.", "no_exif_info_available": "No hay informaciÃŗn exif disponible", "no_explore_results_message": "Sube mÃĄs fotos para explorar tu colecciÃŗn.", "no_favorites_message": "AÃąade favoritos para encontrar rÃĄpidamente sus mejores fotos y videos", + "no_filters_added": "AÃēn no se han aÃąadido filtros", "no_libraries_message": "Crea una biblioteca externa para ver tus fotos y vídeos", "no_local_assets_found": "No se encontraron recursos locales con esta suma de comprobaciÃŗn", + "no_location_set": "No se ha establecido ninguna ubicaciÃŗn", "no_locked_photos_message": "Las fotos y los vídeos de la carpeta protegida se mantienen ocultos; no aparecerÃĄn cuando veas o busques elementos en tu biblioteca.", "no_name": "Sin nombre", "no_notifications": "Ninguna notificaciÃŗn", @@ -1518,19 +1619,14 @@ "no_results": "Sin resultados", "no_results_description": "Pruebe con un sinÃŗnimo o una palabra clave mÃĄs general", "no_shared_albums_message": "Crea un ÃĄlbum para compartir fotos y vídeos con personas de tu red", - "no_steps": "AÃēn no se han aÃąadido pasos", + "no_uploads_in_progress": "No hay cargas en progreso", "none": "Ninguno", "not_allowed": "No permitido", "not_available": "N/D", "not_in_any_album": "Sin ÃĄlbum", "not_selected": "No seleccionado", - "not_set": "No establecido", "notes": "Notas", "nothing_here_yet": "Sin nada aÃēn", - "notification_backup_reliability": "Activa las notificaciones para mejorar el funcionamiento de las copias de seguridad en segundo plano", - "notification_enabled_list_tile_content": "Immich usa notificaciones para las copias de seguridad en segundo plano. GestiÃŗnelas en los ajustes de su dispositivo.", - "notification_enabled_list_tile_open_button": "Abrir ajustes", - "notification_enabled_list_tile_title": "Notificaciones activadas", "notification_permission_dialog_content": "Para activar las notificaciones, ve a ConfiguraciÃŗn y selecciona permitir.", "notification_permission_list_tile_content": "Concede permiso para habilitar las notificaciones.", "notification_permission_list_tile_enable_button": "Permitir notificaciones", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Configurador de Obtainium", "obtainium_configurator_instructions": "Usa Obtainium para instalar y actualizar la aplicaciÃŗn de Android directamente desde las versiones publicadas en el GitHub de Immich. Crea una clave API y selecciona una variante para generar tu enlace de configuraciÃŗn de Obtainium", "ocr": "OCR", - "ocr_body": "Ahora Immich lee el texto que hay dentro de tus fotos, así podrÃĄs buscarlas por lo que dicen.", - "ocr_title": "Buscar texto en tus fotos", "official_immich_resources": "Recursos oficiales de Immich", "offline": "Desconectado", "offset": "DesviaciÃŗn", @@ -1562,8 +1656,6 @@ "open": "Abierto", "open_calendar": "Abrir calendario", "open_in_browser": "Abrir en el navegador", - "open_in_immich_body": "Establece Immich como tu galería en Android para abrir fotos directamente desde otras aplicaciones.", - "open_in_immich_title": "Abrir fotos en Immich", "open_in_map_view": "Abrir en la vista del mapa", "open_in_openstreetmap": "Abrir en OpenStreetMap", "open_the_search_filters": "Abre los filtros de bÃēsqueda", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organizar en ÃĄlbumes", "organize_into_albums_description": "AÃąade fotos existentes en ÃĄlbumes usando la configuraciÃŗn actual de sincronizaciÃŗn", "organize_your_library": "Organiza tu biblioteca", - "orientation": "OrientaciÃŗn", "original": "original", "other": "Otro", "other_devices": "Otros dispositivos", + "other_entities": "Otras entidades", "other_variables": "Otras variables", "owned": "Propios", "owner": "Propietario", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Todas tus fotos y vídeos excepto los archivados y eliminados", "partner_can_access_location": "UbicaciÃŗn donde fueron realizadas tus fotos", "partner_list_user_photos": "Fotos de {user}", + "partner_list_view_all": "Ver todas", "partner_page_empty_message": "Tus fotos aÃēn no se han compartido con ningÃēn compaÃąero.", "partner_page_no_more_users": "No hay mÃĄs usuarios para aÃąadir", + "partner_page_partner_add_failed": "No se pudo aÃąadir al miembro", "partner_page_select_partner": "Seleccionar compaÃąero", + "partner_page_shared_to_title": "Compartido con", "partner_page_stop_sharing_content": "{partner} ya no podrÃĄ acceder a tus fotos.", "partner_sharing": "Compartir con compaÃąeros", "partners": "Miembros", @@ -1609,6 +1704,8 @@ "people": "Personas", "people_edits_count": "Editada {count, plural, one {# persona} other {# personas}}", "people_feature_description": "Explorar fotos y vídeos agrupados por personas", + "people_selected": "{count, plural, one {# persona seleccionada} other {# personas seleccionadas}}", + "people_sidebar_description": "Mostrar un enlace a personas en la barra lateral", "permanent_deletion_warning": "Advertencia de eliminaciÃŗn permanente", "permanent_deletion_warning_setting_description": "Mostrar una advertencia al eliminar recursos permanentemente", "permanently_delete": "Borrar permanentemente", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Eliminado permanentemente {count, plural, one {# recurso} other {# recursos}}", "permission": "Permiso", "permission_empty": "Tus permisos no deben estar vacíos", + "permission_onboarding_back": "Volver", + "permission_onboarding_continue_anyway": "Continuar de todos modos", + "permission_onboarding_get_started": "Empezar", + "permission_onboarding_go_to_settings": "Ir a configuraciÃŗn", + "permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, concede permisos de fotos y videos en ConfiguraciÃŗn.", + "permission_onboarding_permission_granted": "ÂĄPermiso concedido! Todo listo.", + "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich haga copia de seguridad y gestione toda tu colecciÃŗn de galería, concede permisos de fotos y videos en ConfiguraciÃŗn.", + "permission_onboarding_request": "Immich requiere permiso para ver tus fotos y videos.", "person": "Persona", "person_age_months": "hace {months, plural, one {# mes} other {# meses}}", "person_age_year_months": "1 aÃąo y {months, plural, one {# mes} other {# meses}}", - "person_age_years": "{years, plural, one {# aÃąo} other {# aÃąos}}", + "person_age_years": "{years, plural, other {# aÃąos}}", "person_birthdate": "Nacido el {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", "person_recognized": "Persona reconocida", + "person_selected": "Persona seleccionada", "photo_shared_all_users": "Parece que compartiste tus fotos con todos los usuarios o no tienes ningÃēn usuario con quien compartirlas.", "photos": "Fotos", "photos_and_videos": "Fotos y vídeos", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de aÃąos anteriores", + "photos_only": "Solo fotos", "pick_a_location": "Elige una ubicaciÃŗn", "pick_custom_range": "Rango personalizado", "pick_date_range": "Seleccione un rango de fechas", @@ -1647,14 +1755,12 @@ "play_original_video_setting_description": "Preferir la reproducciÃŗn de videos originales en lugar de videos transcodificados. Si el recurso original no es compatible, es posible que no se reproduzca correctamente.", "play_transcoded_video": "Reproducir video transcodificado", "please_auth_to_access": "Por favor, autentícate para acceder", - "plugin_method_filter_type": "Filtro", - "plugin_method_filter_type_description": "Este mÊtodo puede filtrar eventos y evitar que se ejecuten los pasos siguientes cuando se cumplan ciertas condiciones", "port": "Puerto", "preferences_settings_subtitle": "Configuraciones de la aplicaciÃŗn", "preferences_settings_title": "Preferencias", "preparing": "Preparando", "preset": "Preestablecido", - "preview": "Previsualizar", + "preview": "Posterior", "previous": "Anterior", "previous_memory": "Recuerdo anterior", "previous_or_next_day": "Día posterior/anterior", @@ -1665,11 +1771,11 @@ "privacy": "Privacidad", "profile": "Perfil", "profile_drawer_app_logs": "Registros", + "profile_drawer_client_server_up_to_date": "Cliente y servidor estÃĄn actualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo solo lectura habilitado. MantÊn pulsado el icono del avatar del usuario para salir.", "profile_image_of_user": "Foto de perfil de {user}", "profile_picture_set": "Conjunto de imÃĄgenes de perfil.", - "projection_type": "Tipo de proyecciÃŗn", "public_album": "Álbum pÃēblico", "public_share": "Compartir pÃēblicamente", "purchase_account_info": "Colaborador", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Estatus de colaborador", "purchase_individual_title": "Individual", "purchase_input_suggestion": "ÂŋTiene una clave de producto? IntrodÃēzcala a continuaciÃŗn", + "purchase_license_subtitle": "Compre Immich para apoyar el desarrollo continuo del servicio", "purchase_lifetime_description": "Compra de por vida", "purchase_option_title": "OPCIONES DE COMPRA", "purchase_panel_info_1": "Desarrollar Immich requiere mucho tiempo y esfuerzo, y contamos con ingenieros a tiempo completo que trabajan en Êl para que sea lo mejor posible. Nuestra misiÃŗn es que el software de cÃŗdigo abierto y las prÃĄcticas comerciales Êticas se conviertan en una fuente de ingresos sostenibles para los desarrolladores y crear un ecosistema que respete la privacidad con alternativas reales a los servicios en la nube de pago.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Reasignado {count, plural, one {# recurso} other {# recursos}} a un nuevo usuario", "reassing_hint": "Asignar recursos seleccionados a una persona existente", "recent": "Reciente", + "recent_albums": "Últimos ÃĄlbumes", "recent_searches": "BÃēsquedas recientes", "recently_added": "AÃąadidos recientemente", - "recently_added_body": "Acceda directamente a todo lo que ha aÃąadido recientemente en una pÃĄgina dedicada.", - "recently_added_description": "Explora tus archivos ordenados por fecha de subida a Immich", "recently_added_page_title": "ReciÊn aÃąadidos", - "recently_added_title": "AÃąadido recientemente", "recently_taken": "Tomadas recientemente", + "recently_taken_page_title": "Tomadas recientemente", "refresh": "Actualizar", "refresh_encoded_videos": "Recargar los vídeos codificados", "refresh_faces": "Actualizar caras", "refresh_metadata": "Recargar metadatos", "refresh_thumbnails": "Recargar miniaturas", "refreshed": "Recargado", + "refreshes_every_file": "Recargar todos los archivos nuevos y existentes", "refreshing_encoded_video": "Recargando los videos codificados", "refreshing_faces": "Recargando caras", "refreshing_metadata": "Recargando metadatos", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "ÂŋEstÃĄs seguro que quieres eliminar {count, plural, one {# recurso} other {# recursos}} del enlace compartido?", "remove_assets_title": "ÂŋEliminar recursos?", "remove_custom_date_range": "Eliminar intervalo de fechas personalizado", - "remove_filter": "Eliminar filtro", + "remove_deleted_assets": "Eliminar recursos sin conexiÃŗn", "remove_from_album": "Eliminar del ÃĄlbum", "remove_from_album_action_prompt": "{count} eliminado del ÃĄlbum", "remove_from_favorites": "Quitar de favoritos", @@ -1756,22 +1863,29 @@ "remove_memory": "Quitar recuerdo", "remove_photo_from_memory": "Quitar foto de este recuerdo", "remove_tag": "Quitar etiqueta", + "remove_url": "Eliminar URL", "remove_user": "Eliminar usuario", "removed_api_key": "Clave API eliminada: {name}", "removed_from_archive": "Eliminado del archivo", "removed_from_favorites": "Eliminado de favoritos", "removed_from_favorites_count": "{count, plural, other {Eliminados #}} de favoritos", "removed_memory": "Recuerdo eliminado", + "removed_photo_from_memory": "Foto eliminada del recuerdo", "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# recurso} other {# recursos}}", "rename": "Renombrar", + "repair": "Reparar", + "repair_no_results_message": "Los archivos perdidos y sin seguimiento aparecerÃĄn aquí", + "replace_with_upload": "Reemplazar con subida", "repository": "Repositorio", "require_password": "ContraseÃąa requerida", + "require_user_to_change_password_on_first_login": "Requerir que el usuario cambie la contraseÃąa en el primer inicio de sesiÃŗn", "rescan": "Volver a escanear", "reset": "Restablecer", "reset_password": "Restablecer la contraseÃąa", "reset_people_visibility": "Restablecer la visibilidad de las personas", "reset_pin_code": "Restablecer PIN", "reset_pin_code_description": "Si olvidaste tu cÃŗdigo PIN, puedes comunicarte con el administrador del servidor para restablecerlo", + "reset_pin_code_success": "CÃŗdigo PIN restablecido correctamente", "reset_pin_code_with_password": "Siempre puedes restablecer tu cÃŗdigo PIN usando tu contraseÃąa", "reset_sqlite": "Restablecer la base de datos SQLite", "reset_sqlite_clear_app_data": "Limpiar datos", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Todos los duplicados resueltos", "restore": "Restaurar", "restore_all": "Restaurar todo", + "restore_trash_action_prompt": "{count} restaurado de la papelera", "restore_user": "Restaurar usuario", "restored_asset": "Recurso restaurado", "resume": "Continuar", "resume_paused_jobs": "Reanudar {count, plural, one {# tarea en pausa} other {# tareas en pausa}}", + "retry_upload": "Reintentar subida", "review_duplicates": "Revisar duplicados", "review_large_files": "Revisar archivos grandes", "role": "Rol", @@ -1796,6 +1912,7 @@ "role_viewer": "Visor", "running": "En ejecuciÃŗn", "save": "Guardar", + "save_to_gallery": "Guardar en la galería", "saved": "Guardado", "saved_api_key": "Clave API guardada", "saved_profile": "Perfil guardado", @@ -1806,10 +1923,9 @@ "scan": "Escanear", "scan_all_libraries": "Escanear todas las bibliotecas", "scan_library": "Escanear", + "scan_settings": "ConfiguraciÃŗn de escaneo", "scanning": "Escaneando", "scanning_for_album": "Buscando ÃĄlbum...", - "screencast_mode_description": "Mostrar los indicadores de eventos del teclado y del ratÃŗn en la pantalla", - "screencast_mode_title": "Alternar modo de grabaciÃŗn de pantalla", "search": "Buscar", "search_albums": "Buscar ÃĄlbumes", "search_by_context": "Buscar por contexto", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Día de senderismo en Sapa", "search_by_filename": "Buscar por nombre de archivo o extensiÃŗn", "search_by_filename_example": "es decir IMG_1234.JPG o PNG", - "search_by_full_path": "Buscar por ruta completa o carpeta", - "search_by_full_path_example": "John/Proyectos/ImpresiÃŗn_3D/2026-07-01 Puedes buscar Proyectos, 3D, ImpresiÃŗn, 2026, etc.", "search_by_ocr": "Buscar por OCR", "search_by_ocr_example": "CafÊ con leche", "search_camera_lens_model": "Buscar modelo de lente...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Seleccionar una ubicaciÃŗn", "search_filter_media_type": "Tipo de archivo", "search_filter_media_type_title": "Seleccionar el tipo de archivo", + "search_filter_ocr": "Buscar por OCR", "search_filter_people_title": "Seleccionar personas", "search_filter_star_rating": "ClasificaciÃŗn de estrellas", "search_filter_tags_title": "Seleccionar etiquetas", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Ninguna persona llamada \"{name}\"", "search_no_result": "No se encontraron resultados, prueba con un tÊrmino o combinaciÃŗn de bÃēsqueda diferente", "search_options": "Opciones de bÃēsqueda", + "search_page_categories": "Categorías", + "search_page_motion_photos": "Fotos en movimiento", + "search_page_no_objects": "No hay informaciÃŗn de objetos disponibles", + "search_page_no_places": "No hay informaciÃŗn de lugares disponibles", + "search_page_screenshots": "Capturas de pantalla", "search_page_search_photos_videos": "Busca tus fotos y videos", + "search_page_selfies": "Selfies", + "search_page_things": "Cosas", "search_page_view_all_button": "Ver todo", + "search_page_your_activity": "Tu actividad", + "search_page_your_map": "Tu mapa", "search_people": "Buscar personas", "search_places": "Buscar lugar", "search_rating": "Buscar por calificaciÃŗn...", - "search_settings": "Buscar ajustes", + "search_result_page_new_search_hint": "Nueva BÃēsqueda", + "search_settings": "Ajustes de bÃēsqueda", "search_state": "Buscar regiÃŗn/estado...", + "search_suggestion_list_smart_search_hint_1": "La bÃēsqueda inteligente estÃĄ habilitada por defecto, para buscar metadatos utiliza esta sintaxis ", + "search_suggestion_list_smart_search_hint_2": "m:tu-tÊrmino-de-bÃēsqueda", "search_tags": "Buscar etiquetas...", "search_timezone": "Buscar zona horaria...", "search_type": "Tipo de bÃēsqueda", @@ -1868,6 +1995,7 @@ "select_albums": "Seleccionar ÃĄlbumes", "select_all": "Seleccionar todo", "select_all_duplicates": "Seleccionar todos los duplicados", + "select_all_in": "Seleccionar todos en {group}", "select_avatar_color": "Seleccionar color del avatar", "select_count": "{count, plural, one {Seleccionar #} other {Seleccionar #}}", "select_cutoff_date": "Seleccione fecha límite", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Seleccionar foto principal", "select_from_computer": "Seleccionar desde el equipo", "select_keep_all": "Conservar todo", + "select_library_owner": "Seleccionar propietario de la biblioteca", "select_new_face": "Seleccionar nueva cara", "select_people": "Seleccionar gente", "select_person": "Seleccionar persona", "select_person_to_tag": "Elija una persona a etiquetar", "select_photos": "Seleccionar fotos", - "select_quality": "Seleccionar calidad", "select_trash_all": "Seleccionar eliminar todo", + "select_user_for_sharing_page_err_album": "Fallo al crear el ÃĄlbum", "selected": "Seleccionado", "selected_count": "{count, plural, one {# seleccionado} other {# seleccionados}}", "selected_gps_coordinates": "Coordenadas GPS seleccionadas", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Cargar imagen original", "setting_image_viewer_preview_subtitle": "Activar para cargar una imagen de resoluciÃŗn media. Deshabilitar para cargar directamente la imagen original o usar una miniatura.", "setting_image_viewer_preview_title": "Cargar imagen de previsualizaciÃŗn", + "setting_image_viewer_title": "ImÃĄgenes", "setting_languages_apply": "Aplicar", "setting_languages_subtitle": "Cambia el idioma de la aplicaciÃŗn", + "setting_notifications_notify_failures_grace_period": "Notificar fallos de copia de seguridad en segundo plano: {duration}", + "setting_notifications_notify_hours": "{count} horas", + "setting_notifications_notify_immediately": "inmediatamente", "setting_notifications_notify_minutes": "{count} minutos", + "setting_notifications_notify_never": "nunca", "setting_notifications_notify_seconds": "{count} segundos", + "setting_notifications_single_progress_subtitle": "InformaciÃŗn detallada del progreso de subida de cada recurso", + "setting_notifications_single_progress_title": "Mostrar progreso detallado de copia de seguridad en segundo plano", "setting_notifications_subtitle": "Ajusta tus preferencias de notificaciÃŗn", + "setting_notifications_total_progress_subtitle": "Progreso general de subida (recursos completados/total)", + "setting_notifications_total_progress_title": "Mostrar progreso total de copia de seguridad en segundo plano", "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automÃĄticamente al abrirlos", "setting_video_viewer_auto_play_title": "Reproducir vídeos automÃĄticamente", "setting_video_viewer_looping_title": "Bucle", "setting_video_viewer_original_video_subtitle": "Al reproducir un video en streaming desde el servidor, reproducir el original incluso cuando haya una transcodificaciÃŗn disponible. Puede causar buffering. Los videos disponibles localmente se reproducen en calidad original independientemente de esta configuraciÃŗn.", "setting_video_viewer_original_video_title": "Forzar vídeo original", "settings": "Ajustes", + "settings_require_restart": "Por favor, reinicia Immich para aplicar este ajuste", "settings_saved": "Ajustes guardados", "setup_pin_code": "Establecer un PIN", "share": "Compartir", + "share_action_prompt": "{count} recursos compartidos", + "share_add_photos": "AÃąadir fotos", + "share_assets_selected": "{count} seleccionado(s)", "share_dialog_preparing": "Preparando...", "share_link": "Compartir enlace", - "share_original": "Usar original (grande)", - "share_preview": "Usar vista previa (pequeÃąo)", - "share_quality_body": "MantÊn pulsado el botÃŗn de compartir para elegir la calidad de la imagen antes de compartirla.", - "share_quality_title": "Elige la calidad de comparticiÃŗn", "shared": "Compartidos", "shared_album_activities_input_disable": "Los comentarios estÃĄn deshabilitados", "shared_album_activity_remove_content": "ÂŋDeseas eliminar esta actividad?", "shared_album_activity_remove_title": "Eliminar actividad", "shared_album_section_people_action_error": "Error retirando/eliminando del ÃĄlbum", + "shared_album_section_people_action_leave": "Eliminar usuario del ÃĄlbum", + "shared_album_section_people_action_remove_user": "Eliminar usuario del ÃĄlbum", "shared_album_section_people_title": "PERSONAS", "shared_by": "Compartido por", "shared_by_user": "Compartido por {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} cargado(s)", "shared_link_app_bar_title": "Enlaces compartidos", "shared_link_clipboard_copied_massage": "Copiado al portapapeles", + "shared_link_clipboard_text": "Enlace: {link}\nContraseÃąa: {password}", "shared_link_create_error": "Error creando el enlace compartido", - "shared_link_custom_url_description": "Accede a este enlace compartido mediante una URL personalizada", - "shared_link_custom_url_title": "URL personalizada", - "shared_link_custom_url_warning": "Advertencia: un nombre de URL personalizado con una barra inclinada puede no comportarse como esperas.", + "shared_link_custom_url_description": "Acceder a este enlace compartido con una URL personalizada", "shared_link_edit_description_hint": "Introduce la descripciÃŗn del enlace", "shared_link_edit_expire_after_option_day": "1 día", "shared_link_edit_expire_after_option_days": "{count} días", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Compartido con {partner}", "sharing": "Compartidos", "sharing_enter_password": "Por favor, introduce la contraseÃąa para ver esta pÃĄgina.", + "sharing_page_album": "Álbumes compartidos", + "sharing_page_description": "Crea ÃĄlbumes compartidos para compartir fotos y vídeos con las personas de tu red.", + "sharing_page_empty_list": "LISTA VACIA", + "sharing_sidebar_description": "Muestra un enlace a \"Compartido\" en el menÃē lateral", + "sharing_silver_appbar_create_shared_album": "Crear un ÃĄlbum compartido", + "sharing_silver_appbar_share_partner": "Compartir con compaÃąero", "shift_to_permanent_delete": "presiona ⇧ para eliminar permanentemente el recurso", "show_album_options": "Mostrar opciones del ÃĄlbum", "show_albums": "Mostrar ÃĄlbumes", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Mostrar en la línea de tiempo", "show_in_timeline_setting_description": "Mostrar fotos y vídeos de este usuario en tu línea de tiempo", "show_keyboard_shortcuts": "Mostrar atajos del teclado", - "show_less": "Mostrar menos", "show_metadata": "Mostrar metadatos", - "show_more_fields": "{count, plural, one {Muestra # campo mÃĄs} other {Muestra # mÃĄs campos}}", "show_or_hide_info": "Mostrar u ocultar informaciÃŗn", "show_password": "Mostrar contraseÃąa", "show_person_options": "Mostrar opciones de la persona", @@ -2005,7 +2148,6 @@ "show_schema": "Mostrar esquema", "show_search_options": "Mostrar opciones de bÃēsqueda", "show_shared_links": "Mostrar enlaces compartidos", - "show_slideshow_metadata_overlay": "Mostrar informaciÃŗn de la imagen por superposiciÃŗn", "show_slideshow_transition": "Mostrar la transiciÃŗn de las diapositivas", "show_supporter_badge": "Insignia de colaborador", "show_supporter_badge_description": "Mostrar una insignia de colaborador", @@ -2017,25 +2159,18 @@ "sign_out": "Salir", "sign_up": "Registrarse", "size": "TamaÃąo", - "skip": "Saltar", "skip_to_content": "Saltar al contenido", "skip_to_folders": "Ir a las carpetas", "skip_to_tags": "Ir a las etiquetas", "slideshow": "Pase de diapositivas", - "slideshow_body": "RelÃĄjate y disfruta viendo tus fotos en una presentaciÃŗn de diapositivas a pantalla completa.", - "slideshow_metadata_overlay_mode": "Contenido superpuesto", - "slideshow_metadata_overlay_mode_description_only": "Solo descripciÃŗn", - "slideshow_metadata_overlay_mode_full": "Lleno", "slideshow_repeat": "Repetir presentaciÃŗn de diapositivas", "slideshow_repeat_description": "Volver al inicio cuando finaliza la presentaciÃŗn de diapositivas", "slideshow_settings": "Ajustes de diapositivas", - "slideshow_title": "PresentaciÃŗn de diapositivas", - "smart_album": "Álbum inteligente", - "some_assets_already_have_a_location_warning": "Algunos de los elementos seleccionados ya tienen una ubicaciÃŗn", "sort_albums_by": "Ordenar ÃĄlbumes porâ€Ļ", "sort_created": "Fecha de creaciÃŗn", "sort_items": "NÃēmero de archivos", "sort_modified": "Fecha de modificaciÃŗn", + "sort_newest": "Foto mÃĄs nueva", "sort_oldest": "Foto mÃĄs antigua", "sort_people_by_similarity": "Ordenar personas por similitud", "sort_recent": "Foto mÃĄs reciente", @@ -2044,6 +2179,7 @@ "stack": "Apilar", "stack_action_prompt": "{count} apilados", "stack_duplicates": "Apilar duplicados", + "stack_select_one_photo": "Selecciona una imagen principal para la pila", "stack_selected_photos": "Apilar fotos seleccionadas", "stacked_assets_count": "Apilado(s) {count, plural, one {# recurso} other {# recursos}}", "stacktrace": "Seguimiento de pila", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Fecha de inicio debe ser antes de fecha final", "state": "Estado", "status": "Estado", - "step_delete": "Eliminar paso", - "step_delete_confirm": "ÂŋEstÃĄs seguro de que quieres eliminar este paso?", - "step_details": "Detalles de paso", - "steps": "Pasos", "stop_casting": "Detener transmisiÃŗn", "stop_motion_photo": "Parar foto en movimiento", "stop_photo_sharing": "ÂŋDejar de compartir tus fotos?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Alternar direcciÃŗn de mezcla", "sync": "Sincronizar", "sync_albums": "Sincronizar ÃĄlbumes", + "sync_albums_manual_subtitle": "Sincroniza todos los videos y fotos subidos con los ÃĄlbumes seleccionados a respaldar", "sync_local": "SincronizaciÃŗn local", "sync_remote": "SincronizaciÃŗn remota", "sync_status": "Estado de la sincronizaciÃŗn", "sync_status_subtitle": "Ver y gestionar el estado de la sincronizaciÃŗn", "sync_upload_album_setting_subtitle": "Crea y sube tus fotos y videos a los ÃĄlbumes seleccionados en Immich", - "system_theme": "Tema del sistema", - "system_theme_command_description": "Usar el tema del sistema ({value})", "tag": "Etiqueta", "tag_assets": "Etiquetar recursos", "tag_created": "Etiqueta creada: {tag}", "tag_face": "Etiquetar cara", "tag_feature_description": "Explore fotos y videos agrupados por temas de etiquetas lÃŗgicas", + "tag_not_found_question": "ÂŋNo encuentra una etiqueta? Crea una nueva etiqueta.", "tag_people": "Etiquetar personas", "tag_updated": "Etiqueta actualizada: {tag}", "tagged_assets": "Etiquetado(s) {count, plural, one {# recurso} other {# recursos}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "NÃēmero de recursos por fila ({count})", "theme_setting_colorful_interface_subtitle": "Aplicar el color primario a las superficies de fondo.", "theme_setting_colorful_interface_title": "Color de Interfaz", + "theme_setting_image_viewer_quality_subtitle": "Ajustar la calidad del visor de detalles de imÃĄgenes", + "theme_setting_image_viewer_quality_title": "Calidad del visor de imÃĄgenes", "theme_setting_primary_color_subtitle": "Elige un color para las acciones principales y los acentos.", "theme_setting_primary_color_title": "Color primario", "theme_setting_system_primary_color_title": "Usar color del sistema", "theme_setting_system_theme_switch": "AutomÃĄtico (seguir ajuste del sistema)", + "theme_setting_theme_subtitle": "Elige la configuraciÃŗn del tema de la aplicaciÃŗn", + "theme_setting_three_stage_loading_subtitle": "La carga en tres etapas puede aumentar el rendimiento de carga pero provoca un consumo de red significativamente mayor", + "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", "then": "Entonces", "they_will_be_merged_together": "Se fusionarÃĄn entre sí", "third_party_resources": "Recursos de terceros", @@ -2131,17 +2268,23 @@ "trash_all": "Descartar todo", "trash_count": "Descartar {count, number}", "trash_delete_asset": "Borrar/Eliminar recurso", + "trash_emptied": "Papelera vaciada", "trash_no_results_message": "Las fotos y videos que se envíen a la papelera aparecerÃĄn aquí.", "trash_page_delete_all": "Eliminar todos", + "trash_page_empty_trash_dialog_content": "ÂŋEstÃĄ seguro que quiere eliminar los recursos? Estos recursos serÃĄn eliminados de Immich permanentemente", "trash_page_info": "Los archivos en la papelera serÃĄn eliminados automÃĄticamente de forma permanente despuÊs de {days} días", + "trash_page_no_assets": "No hay recursos en la papelera", + "trash_page_restore_all": "Restaurar todos", + "trash_page_select_assets_btn": "Seleccionar recursos", + "trash_page_title": "Papelera ({count})", "trashed_items_will_be_permanently_deleted_after": "Los elementos en la papelera serÃĄn eliminados permanentemente tras {days, plural, one {# día} other {# días}}.", "trigger": "Disparador", - "trigger_asset_metadata_extraction": "ExtracciÃŗn de metadatos de los elementos", - "trigger_asset_metadata_extraction_description": "Se activa cuando se extraigan los datos EXIF de un elemento", "trigger_asset_uploaded": "Recurso subido", "trigger_asset_uploaded_description": "Se activa cuando se carga un nuevo recurso", + "trigger_description": "Un evento que inicia el flujo de trabajo", "trigger_person_recognized": "Persona reconocida", - "trigger_person_recognized_description": "Se activa cuando se reconoce una persona", + "trigger_person_recognized_description": "Se activa cuando se detecta una persona", + "trigger_type": "Tipo de disparador", "troubleshoot": "Solucionar problemas", "type": "Tipo", "unable_to_change_pin_code": "No se ha podido cambiar el PIN", @@ -2157,7 +2300,6 @@ "unknown": "Desconocido", "unknown_country": "País desconocido", "unknown_date": "Fecha desconocida", - "unknown_schema": "Esquema desconocido", "unknown_year": "AÃąo desconocido", "unlimited": "Sin límites", "unlink_motion_video": "Desvincular vídeo en movimiento", @@ -2170,20 +2312,23 @@ "unsaved_change": "Cambio no guardado", "unselect_all": "Limpiar selecciÃŗn", "unselect_all_duplicates": "Deseleccionar todos los duplicados", + "unselect_all_in": "Deselecciona todos en {group}", "unstack": "Desapilar", "unstack_action_prompt": "{count} desapilado(s)", "unstacked_assets_count": "Desapilado(s) {count, plural, one {# recurso} other {# recursos}}", "unsupported_field_type": "Tipo de campo no soportado", "unsupported_file_type": "El archivo {file} no puede ser cargado porque su tipo de archivo {type} no es soportado.", "untagged": "Sin etiqueta", + "untitled_workflow": "Flujo de trabajo sin título", "up_next": "A continuaciÃŗn", "update_location_action_prompt": "Actualiza la ubicaciÃŗn de {count} recursos seleccionados con:", "updated_at": "Actualizado", "updated_password": "ContraseÃąa actualizada", "upload": "Subir", "upload_concurrency": "Subidas simultÃĄneas", - "upload_day_count": "{date}: {count, plural, one {# subida} other {# subidas}}", "upload_details": "Cargar detalles", + "upload_dialog_info": "ÂŋQuieres hacer una copia de seguridad al servidor de los recursos seleccionados?", + "upload_dialog_title": "Subir recursos", "upload_error_with_count": "Error al cargar {count, plural, one {# el recurso} other {# los recursos}}", "upload_errors": "Subida completada con {count, plural, one {# error} other {# errores}}, actualice la pÃĄgina para ver los nuevos recursos de la subida.", "upload_finished": "Carga finalizada", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Errores", "upload_status_uploaded": "Subido", "upload_success": "Subida realizada correctamente, actualice la pÃĄgina para ver los nuevos recursos de subida.", - "upload_to_album_body": "Los usuarios que no utilicen la funciÃŗn de subida manual ya pueden aÃąadir fotos locales directamente a un ÃĄlbum a medida que las suben, sin necesidad de subirlas primero para luego aÃąadirlas al ÃĄlbum.", - "upload_to_album_title": "Subir directamente a un ÃĄlbum", "upload_to_immich": "Subir a Immich ({count})", "uploading": "Subiendo", "uploading_media": "Subiendo medios", - "uploads": "Subidas", - "uploads_count": "{count, plural, one {# subida} other {# subidas}}", "url": "URL", "usage": "Uso", "use_biometric": "Uso biomÊtrico", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Dar formato a fechas, horas y nÃēmeros segÃēn la localizaciÃŗn de su navegador", "use_current_connection": "Utilice la conexiÃŗn actual", "use_custom_date_range": "Usa un intervalo de fechas personalizado", - "use_template": "Utiliza plantilla", "user": "Usuario", "user_has_been_deleted": "Este usuario ha sido eliminado.", "user_id": "Id. de usuario", @@ -2217,6 +2357,7 @@ "user_privacy": "Privacidad del usuario", "user_purchase_settings": "Compra", "user_purchase_settings_description": "Gestiona tu compra", + "user_role_set": "Establecer {user} como {role}", "user_usage_detail": "Detalle del uso del usuario", "user_usage_stats": "Estadísticas de uso de la cuenta", "user_usage_stats_description": "Ver estadísticas de uso de la cuenta", @@ -2226,6 +2367,7 @@ "utilities": "Utilidades", "validate": "Validar", "validate_endpoint_error": "Por favor, introduce una URL vÃĄlida", + "validation_error": "Error de validaciÃŗn", "variables": "Variables", "version": "VersiÃŗn", "version_announcement_closing": "Tu amigo, Alex", @@ -2235,9 +2377,9 @@ "video": "Vídeo", "video_hover_setting": "Iniciar vídeo al pasar por encima", "video_hover_setting_description": "Reproducir el vídeo cuando el ratÃŗn estÃĄ encima de un vídeo. Aunque estÊ desactivado, se iniciarÃĄ cuando el cursor del ratÃŗn estÊ sobre el icono de \"reproducir\".", - "video_quality": "Calidad de video", "videos": "Vídeos", "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", + "videos_only": "Solo vídeos", "view": "Ver", "view_album": "Ver ÃĄlbum", "view_all": "Ver todas", @@ -2246,29 +2388,29 @@ "view_details": "Ver detalles", "view_in_timeline": "Ver en la línea de tiempo", "view_link": "Ver enlace", + "view_links": "Mostrar enlaces", "view_name": "Ver", "view_next_asset": "Mostrar siguiente recurso", "view_previous_asset": "Mostrar recurso anterior", "view_qr_code": "Ver cÃŗdigo QR", "view_similar_photos": "Ver fotografías similares", "view_stack": "Ver pila", + "view_user": "Ver usuario", "viewer_remove_from_stack": "Quitar de la pila", + "viewer_stack_use_as_main_asset": "Usar como recurso principal", + "viewer_unstack": "Desapilar", "visibility": "Visibilidad", "visibility_changed": "Visibilidad cambiada para {count, plural, one {# persona} other {# personas}}", "visual": "Visual", + "visual_builder": "Constructor visual", "waiting": "Esperando", "waiting_count": "Esperando: {count}", "warning": "Advertencia", "week": "Semana", "welcome": "Bienvenido/a", "welcome_to_immich": "Bienvenido/a a Immich", - "whats_new": "Novedades", - "whats_new_settings_subtitle": "Descubre las novedades de Immich", - "whats_new_version": "VersiÃŗn {version}", - "when": "CuÃĄndo", "width": "Ancho", "wifi_name": "Nombre del Wi-Fi", - "workflow": "Flujo de trabajo", "workflow_delete_prompt": "ÂŋEstÃĄs seguro de que quieres eliminar este flujo de trabajo?", "workflow_deleted": "Flujo de trabajo eliminado", "workflow_description": "DescripciÃŗn del flujo de trabajo", @@ -2278,17 +2420,17 @@ "workflow_name": "Nombre del flujo de trabajo", "workflow_navigation_prompt": "ÂŋEstÃĄs seguro que deseas salir sin guardar los cambios?", "workflow_summary": "Resumen del flujo de trabajo", - "workflow_templates": "Plantillas de flujo de trabajo", "workflow_update_success": "Flujo de trabajo actualizado con Êxito", + "workflow_updated": "Flujo de trabajo actualizado", "workflows": "Flujos de trabajo", "workflows_help_text": "Los flujos de trabajo automatizan acciones en sus recursos segÃēn activadores y filtros", "wrong_pin_code": "CÃŗdigo PIN incorrecto", - "x_of_total": "{x}/{total}", "year": "AÃąo", "years_ago": "Hace {years, plural, one {# aÃąo} other {# aÃąos}}", "yes": "Sí", "you_dont_have_any_shared_links": "No tienes ningÃēn enlace compartido", "your_wifi_name": "El nombre de tu Wi-Fi", "zero_to_clear_rating": "presione 0 para borrar la calificaciÃŗn del recurso", - "zoom_image": "Acercar imagen" + "zoom_image": "Acercar imagen", + "zoom_to_bounds": "Ajustar a los límites" } diff --git a/i18n/et.json b/i18n/et.json index 8941dbbb0e..201b341d15 100644 --- a/i18n/et.json +++ b/i18n/et.json @@ -10,31 +10,39 @@ "active": "Aktiivne", "active_count": "Aktiivsed: {count}", "activity": "Aktiivsus", + "activity_changed": "Aktiivsus on {enabled, select, true {lubatud} other {keelatud}}", "add": "Lisa", "add_a_description": "Lisa kirjeldus", "add_a_location": "Lisa asukoht", "add_a_name": "Lisa nimi", "add_a_title": "Lisa pealkiri", "add_action": "Lisa tegevus", + "add_action_description": "KlÃĩpsa, et lisada teostatav tegevus", "add_assets": "Lisa Ãŧksuseid", "add_birthday": "Lisa sÃŧnnipäev", "add_endpoint": "Lisa lÃĩpp-punkt", "add_exclusion_pattern": "Lisa välistamismuster", + "add_filter": "Lisa filter", + "add_filter_description": "KlÃĩpsa, et lisada filtreerimistingimus", "add_location": "Lisa asukoht", + "add_more_users": "Lisa rohkem kasutajaid", "add_partner": "Lisa partner", + "add_path": "Lisa tee", "add_photos": "Lisa fotosid", - "add_step": "Lisa samm", "add_tag": "Lisa silt", "add_to": "Lisa kohtaâ€Ļ", "add_to_album": "Lisa albumisse", "add_to_album_bottom_sheet_added": "Lisatud albumisse {album}", "add_to_album_bottom_sheet_already_exists": "On juba albumis {album}", "add_to_album_bottom_sheet_some_local_assets": "KÃĩiki lokaalseid Ãŧksuseid ei Ãĩnnestunud albumisse lisada", + "add_to_album_toggle": "Muuda albumi {album} valikut", "add_to_albums": "Lisa albumitesse", "add_to_albums_count": "Lisa albumitesse ({count})", "add_to_bottom_bar": "Lisa", + "add_to_shared_album": "Lisa jagatud albumisse", "add_upload_to_stack": "Virnasta Ãŧleslaaditud Ãŧksus", "add_url": "Lisa URL", + "add_workflow_step": "Lisa tÃļÃļvoo samm", "added_to_archive": "Lisatud arhiivi", "added_to_favorites": "Lisatud lemmikutesse", "added_to_favorites_count": "{count, number} pilti lisatud lemmikutesse", @@ -52,7 +60,7 @@ "backup_keep_last_amount": "Eelmiste tÃĩmmiste arv, mida alles hoida", "backup_onboarding_1_description": "asukohaväline koopia pilves vÃĩi teises fÃŧÃŧsilises asukohas.", "backup_onboarding_2_description": "lokaalset koopiat erinevatel seadmetel. See hÃĩlmab pÃĩhifaile ja nende failide lokaalsed varundust.", - "backup_onboarding_3_description": "koopiat su andmetest, kaasa arvatud algsed failid. See hÃĩlmab Ãŧht asukohavälist ja kaht lokaalset koopiat.", + "backup_onboarding_3_description": "koopiat su andmetest, kaasa arvatud originaalfailid. See hÃĩlmab Ãŧht asukohavälist ja kaht lokaalset koopiat.", "backup_onboarding_description": "Andmete kaitsmiseks on soovituslik 3-2-1 varundusstrateegia. PÃĩhjaliku varunduse jaoks peaksid talletama koopiaid nii oma Ãŧleslaaditud fotodest ja videotest kui ka Immich'i andmebaasist.", "backup_onboarding_footer": "Rohkem informatsiooni Immich'i varundamise kohta leiad dokumentatsioonist.", "backup_onboarding_parts_title": "3-2-1 varundus hÃĩlmab:", @@ -73,7 +81,6 @@ "cron_expression_description": "Määra skaneerimise intervall cron formaadis. Rohkema info jaoks vaata nt. Crontab Guru", "cron_expression_presets": "Eelseadistatud cron avaldised", "disable_login": "Keela sisselogimine", - "download_csv": "Laadi alla CSV", "duplicate_detection_job_description": "Rakenda Ãŧksustele masinÃĩpet, et leida sarnaseid pilte. Kasutab nutiotsingut", "exclusion_pattern_description": "Välistamismustrid vÃĩimaldavad ignoreerida faile ja kaustu selle kogu skaneerimisel. See on kasulik, kui sul on kaustu, mis sisaldavad faile, mida sa ei soovi importida, nagu RAW failid.", "export_config_as_json_description": "Laadi praegune sÃŧsteemi seadistus JSON-failina alla", @@ -83,11 +90,11 @@ "facial_recognition_job_description": "Grupeeri avastatud näod inimesteks. See samm käivitub siis, kui näoavastus on lÃĩppenud. \"Lähtesta\" grupeerib kÃĩik näod uuesti. \"Puuduvad\" vÃĩtab ette näod, mida pole isikuga seostatud.", "failed_job_command": "Käsk {command} ebaÃĩnnestus tÃļÃļtes: {job}", "force_delete_user_warning": "HOIATUS: See kustutab koheselt kasutaja ja kÃĩik tema Ãŧksused. Tegevust ei saa tagasi vÃĩtta ja faile ei saa taastada.", - "image_format": "Vorming", + "image_format": "Formaat", "image_format_description": "WebP failid on väiksemad kui JPEG, aga kodeerimine on aeglasem.", "image_fullsize_description": "TäismÃĩÃĩdus pilt ilma metaandmeteta, kasutatakse sisse suumimisel", "image_fullsize_enabled": "Luba täismÃĩÃĩdus piltide genereerimine", - "image_fullsize_enabled_description": "Genereeri mitte-veebisÃĩbralike vormingute jaoks täismÃĩÃĩdus pilt. Kui \"Eelista manustatud eelvaadet\" on lubatud, kasutatakse manustatud eelvaateid otse ilma teisendamiseta. Ei mÃĩjuta veebisÃĩbralikke vorminguid nagu JPEG.", + "image_fullsize_enabled_description": "Genereeri mitte-veebisÃĩbralike formaatide jaoks täismÃĩÃĩdus pilt. Kui \"Eelista manustatud eelvaadet\" on lubatud, kasutatakse manustatud eelvaateid otse ilma teisendamiseta. Ei mÃĩjuta veebisÃĩbralikke formaate nagu JPEG.", "image_fullsize_quality_description": "TäismÃĩÃĩdus pildi kvaliteet vahemikus 1-100. KÃĩrgem väärtus on parem, aga tulemuseks on suuremad failid.", "image_fullsize_title": "TäismÃĩÃĩdus pildi seaded", "image_prefer_embedded_preview": "Eelista manustatud eelvaadet", @@ -108,18 +115,6 @@ "image_thumbnail_quality_description": "Pisipildi kvaliteet vahemikus 1-100. KÃĩrgem väärtus on parem, aga tekitab suuremaid faile ning vÃĩib mÃĩjutada rakenduse tÃļÃļkiirust.", "image_thumbnail_title": "Pisipildi seaded", "import_config_from_json_description": "Impordi sÃŧsteemi seadistus JSON-faili Ãŧleslaadimise teel", - "integrity_checks_checksum_files_description": "Seadista kontrollsumma kontroll", - "integrity_checks_checksum_files_enable_description": "Luba kontrollsumma kontroll", - "integrity_checks_checksum_files_percentage_limit": "Protsendi piirang", - "integrity_checks_checksum_files_percentage_limit_description": "Seadista maksimaalne protsent 0.01 ja 1 vahel, kui palju peaks kontrollsumma kontroll iga intervalli jooksul käivituma.", - "integrity_checks_checksum_files_time_limit": "Ajaline piirang", - "integrity_checks_checksum_files_time_limit_description": "Seadista maksimaalne kestus, kui kaua peaks kontrollsumma kontroll iga intervalli jooksul käivituma. (ms)", - "integrity_checks_missing_files": "Puuduvad failid", - "integrity_checks_missing_files_description": "Luba vÃĩi keela puuduvate failide kontroll ning seadista selle sagedus", - "integrity_checks_missing_files_enable_description": "Luba puuduvate failide kontroll", - "integrity_checks_untracked_files": "Mittejälgitud failid", - "integrity_checks_untracked_files_description": "Luba vÃĩi keela mittejälgitud failide kontroll ning seadista selle sagedus", - "integrity_checks_untracked_files_enable_description": "Luba mittejälgitud failide kontroll", "job_concurrency": "{job} samaaegsus", "job_created": "TÃļÃļde lisatud", "job_not_concurrency_safe": "Seda tÃļÃļdet pole ohutu samaaegselt käivitada.", @@ -195,23 +190,9 @@ "machine_learning_smart_search_enabled": "Luba nutiotsing", "machine_learning_smart_search_enabled_description": "Kui keelatud, siis ei kodeerita pilte nutiotsingu jaoks.", "machine_learning_url_description": "MasinÃĩppe serveri URL. Kui ette on antud rohkem kui Ãŧks URL, proovitakse neid järjest Ãŧkshaaval, kuni Ãŧks edukalt vastab. Servereid, mis ei vasta, ignoreeritakse ajutiselt, kuni Ãŧhendus taastub.", - "maintenance_backup_management": "Varunduse haldus", "maintenance_delete_backup": "Kustuta varukoopia", "maintenance_delete_backup_description": "See fail kustutatakse jäädavalt.", "maintenance_delete_error": "Varukoopia kustutamine ebaÃĩnnestus.", - "maintenance_integrity_check_all": "Märgi kÃĩik", - "maintenance_integrity_checksum_mismatch": "Kontrollsumma ebakÃĩla", - "maintenance_integrity_checksum_mismatch_description": "Failid, mille kontrollsumma ei klapi sellega, mis on Immich'i andmebaasis.", - "maintenance_integrity_checksum_mismatch_job": "Otsi kontrollsumma ebakÃĩlasid", - "maintenance_integrity_checksum_mismatch_refresh_job": "Värskenda kontrollsumma ebakÃĩlade aruanne", - "maintenance_integrity_missing_file": "Puuduvad failid", - "maintenance_integrity_missing_file_description": "Failid, mida Immich jälgib andmebaasis, kuid mida ei eksisteeri failisÃŧsteemis.", - "maintenance_integrity_missing_file_job": "Otsi puuduvaid faile", - "maintenance_integrity_missing_file_refresh_job": "Värskenda puuduvate failide aruanne", - "maintenance_integrity_untracked_file": "Mittejälgitavad failid", - "maintenance_integrity_untracked_file_description": "Failid Immich'i kataloogides, mille kohta Immich'il kirjeid ei ole.", - "maintenance_integrity_untracked_file_job": "Otsi mittejälgitavaid faile", - "maintenance_integrity_untracked_file_refresh_job": "Värskenda mittejälgitavate failide aruanne", "maintenance_restore_backup": "Taasta varukoopia", "maintenance_restore_backup_description": "Immich lähtestatakse ning taastatakse valitud varukoopiast. Enne jätkamist tehakse uus varukoopia.", "maintenance_restore_backup_different_version": "See varukoopia loodi erineva Immich'i versiooniga!", @@ -286,8 +267,6 @@ "notification_enable_email_notifications": "Luba e-posti teel teavitused", "notification_settings": "Teavituse seaded", "notification_settings_description": "Halda teavituste seadeid, sh. e-posti teel", - "oauth_allow_insecure_requests": "Luba ebaturvalised päringud", - "oauth_allow_insecure_requests_description": "HOIATUS: See lÃŧlitab välja OAuth päringute TLS-sertifikaadi valideerimise ja vÃĩib jätta sind kaitseta vahendusrÃŧnnete ees.", "oauth_auto_launch": "Automaatne käivitamine", "oauth_auto_launch_description": "Alusta OAuth autentimist automaatselt sisselogimise lehele jÃĩudmisel", "oauth_auto_register": "Automaatne registreerimine", @@ -295,11 +274,9 @@ "oauth_button_text": "Nupu tekst", "oauth_client_secret_description": "NÃĩutud konfidentsiaalse kliendi jaoks, vÃĩi avaliku kliendi jaoks, kui PKCE (Proof Key for Code Exchange) ei ole toetatud.", "oauth_enable_description": "Sisene OAuth abil", - "oauth_end_session_url_description": "Suuna kasutaja välja logimisel sellele URI-le.", "oauth_mobile_redirect_uri": "Mobiilne Ãŧmbersuunamise URI", "oauth_mobile_redirect_uri_override": "Mobiilse Ãŧmbersuunamise URI Ãŧlekirjutamine", "oauth_mobile_redirect_uri_override_description": "LÃŧlita sisse, kui OAuth pakkuja ei luba mobiilset URI-d, näiteks ''{callback}''", - "oauth_prompt_description": "Prompt parameeter (nt. select_account, login, consent)", "oauth_role_claim": "Rolli väide", "oauth_role_claim_description": "Anna selle väite olemasolul automaatselt administraatori ligipääs. Väite väärtus vÃĩib olla 'user' vÃĩi 'admin'.", "oauth_settings": "OAuth", @@ -326,8 +303,6 @@ "refreshing_all_libraries": "KÃĩikide kogude värskendamine", "registration": "Administraatori registreerimine", "registration_description": "Kuna sa oled sÃŧsteemis esimene kasutaja, määratakse sind administraatoriks, ning sa saad lisada täiendavaid kasutajaid.", - "release_channel_release_candidate": "Kandidaatversioon", - "release_channel_stable": "Stabiilne", "remove_failed_jobs": "Eemalda ebaÃĩnnestunud tÃļÃļted", "require_password_change_on_login": "NÃĩua kasutajalt esmakordsel sisenemisel parooli muutmist", "reset_settings_to_default": "Lähtesta seaded", @@ -390,13 +365,13 @@ "transcoding_accepted_audio_codecs": "Lubatud audiokoodekid", "transcoding_accepted_audio_codecs_description": "Vali, millised audiokoodekid ei vaja transkodeerimist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", "transcoding_accepted_containers": "Lubatud konteinerid", - "transcoding_accepted_containers_description": "Vali, millised konteinerivormingud ei vaja MP4-ks teisendamist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", + "transcoding_accepted_containers_description": "Vali, millised konteineriformaadid ei vaja MP4-ks teisendamist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", "transcoding_accepted_video_codecs": "Lubatud videokoodekid", "transcoding_accepted_video_codecs_description": "Vali, millised videokoodekid ei vaja transkodeerimist. Kasutusel ainult teatud transkodeerimisreeglite puhul.", "transcoding_advanced_options_description": "Valikud, mida enamik kasutajaid ei pea muutma", "transcoding_audio_codec": "Audiokoodek", "transcoding_audio_codec_description": "Opus on kÃĩrgeima kvaliteediga valik, aga on vähem Ãŧhilduv vanade seadmete vÃĩi tarkvaraga.", - "transcoding_bitrate_description": "KÃĩrgema kui lubatud bitisagedusega vÃĩi mittelubatud vormingus videod", + "transcoding_bitrate_description": "KÃĩrgema kui lubatud bitisagedusega vÃĩi mittelubatud formaadis videod", "transcoding_codecs_learn_more": "Siin kasutatud terminoloogia kohta rohkem teada saamiseks loe FFmpeg-i dokumentatsiooni H.264, HEVC ja VP9 koodekite kohta.", "transcoding_constant_quality_mode": "PÃŧsiva kvaliteedi reÅžiim", "transcoding_constant_quality_mode_description": "ICQ on parem kui CQP, aga mÃĩned riistvaralise kiirenduse seadmed ei toeta seda reÅžiimi. Selle valiku seadmisel eelistatakse kvaliteedipÃĩhise kodeerimise puhul valitud reÅžiimi. NVENC puhul valikut ignoreeritakse, kuna see ei toeta ICQ-d.", @@ -415,24 +390,16 @@ "transcoding_max_bitrate_description": "Maksimaalse bitisageduse määramine teeb failisuurused ennustatavamaks, väikese kvaliteedikao hinnaga. 720p resolutsiooni puhul on tÃŧÃŧpilised väärtused 2600 kbit/s (VP9 ja HEVC) vÃĩi 4500 kbit/s (H.264). Väärtus 0 eemaldab piirangu. Kui Ãŧhikut pole määratud, eeldatakse k (kbit/s); seega 5000, 5000k ja 5M (Mbit/s) on samaväärsed.", "transcoding_max_keyframe_interval": "Maksimaalne vÃĩtmekaadri intervall", "transcoding_max_keyframe_interval_description": "Määrab maksimaalse kauguse vÃĩtmekaadrite vahel. Madalamad väärtused vähendavad pakkimise efektiivsust, aga parandavad otsimiskiirust ning vÃĩivad tÃĩsta kiire liikumisega stseenide kvaliteeti. 0 määrab väärtuse automaatselt.", - "transcoding_optimal_description": "KÃĩrgema kui lubatud resolutsiooniga vÃĩi mittelubatud vormingus videod", + "transcoding_optimal_description": "KÃĩrgema kui lubatud resolutsiooniga vÃĩi mittelubatud formaadis videod", "transcoding_policy": "Transkodeerimise reegel", "transcoding_policy_description": "Määra, millal video transkodeeritakse", "transcoding_preferred_hardware_device": "Eelistatud riistvaraseade", "transcoding_preferred_hardware_device_description": "Rakendub ainult VAAPI ja QSV puhul. Määrab dri seadme, mida kasutatakse riistvaraliseks transkodeerimiseks.", "transcoding_preset_preset": "Eelseadistus (-preset)", "transcoding_preset_preset_description": "Pakkimiskiirus. Aeglasemad eelseadistused tekitavad väiksemaid faile ja annavad sama bitisageduse juures parema kvaliteedi. VP9 ignoreerib kiiruseid Ãŧle 'faster' taseme.", - "transcoding_realtime": "Reaalajas transkodeerimine [EKSPERIMENTAALNE]", - "transcoding_realtime_description": "Lubab video voogedastuse ajal reaalajas transkodeerimist. VÃĩimaldab kvaliteedi vahetamist, aga vÃĩib pÃĩhjustada taasesituse kÃĩrgemat latentsust ja katkestusi, sÃĩltuvalt serveri vÃĩimekusest.", - "transcoding_realtime_enabled": "Luba reaalajas transkodeerimine", - "transcoding_realtime_enabled_description": "Kui keelatud, keeldub server uusi reaalajas transkodeerimise sessioone alustamast.", - "transcoding_realtime_resolutions": "Resolutsioonid", - "transcoding_realtime_resolutions_description": "Reaalajas transkodeerimiseks saadaval olevad resolutsioonid. KÃĩrgemad resolutsioonid vÃĩivad pÃĩhjustada taasesituse probleeme, kui server ei suuda piisavalt kiiresti transkodeerida.", - "transcoding_realtime_video_codecs": "Videokoodekid", - "transcoding_realtime_video_codecs_description": "Reaalajas transkodeerimiseks saadaval olevad videkoodekid. Kliendid valivad taasesituse ajal parima toetatud variandi. AV1 on efektiivsem kui HEVC, mis on omakorda efektiivsem kui H.264. Riistvaralise kiirenduse kasutamisel vali ainult koodekid, mida kiirendi suudab kodeerida. Tarkvaralise transkodeerimise puhul pane tähele, et H.264 on kiirem kui AV1, mis on omakorda kiirem kui HEVC.", "transcoding_reference_frames": "Viitekaadrid", "transcoding_reference_frames_description": "Kaadrite arv, millele viidata jooksva kaadri pakkimisel. Suuremad väärtused parandavad pakkimise tÃĩhusust, aga muudavad kodeerimise aeglasemaks. 0 määrab väärtuse automaatselt.", - "transcoding_required_description": "Ainult mittelubatud vormingus videod", + "transcoding_required_description": "Ainult mittelubatud formaadis videod", "transcoding_settings": "Video transkodeerimise seaded", "transcoding_settings_description": "Määra, millised videod transkodeerida ja kuidas neid tÃļÃļdelda", "transcoding_target_resolution": "Sihtresolutsioon", @@ -444,7 +411,7 @@ "transcoding_tone_mapping": "Toonivastendus", "transcoding_tone_mapping_description": "Üritab säilitada HDR videote kvaliteeti SDR-iks teisendamisel. Iga algoritm teeb värvi, detailide ja ereduse osas erinevaid kompromisse. Hable säilitab detaile, Mobius säilitab värve ning Reinhard säilitab eredust.", "transcoding_transcode_policy": "Transkodeerimise reegel", - "transcoding_transcode_policy_description": "Reegel, millal tuleks videot transkodeerida. HDR-videod ja muu pikslivorminguga kui YUV 4:2:0 videod transkodeeritakse alati (v.a. kui transkodeerimine on keelatud).", + "transcoding_transcode_policy_description": "Reegel, millal tuleks videot transkodeerida. HDR-videod ja muu piksliformaadiga kui YUV 4:2:0 videod transkodeeritakse alati (v.a. kui transkodeerimine on keelatud).", "transcoding_two_pass_encoding": "Kahekäiguline kodeerimine", "transcoding_two_pass_encoding_setting_description": "Transkodeeri kahes osas, et parandada kodeeritud videote kvaliteeti. Maksimaalse bitisageduse puhul (mis on vajalik H.264 ja HEVC jaoks) kasutab see reÅžiim bitisageduse vahemikku ja ignoreerib CRF-i. VP9 puhul saab kasutada CRF-i, kui maksimaalset bitisagedust pole määratud.", "transcoding_video_codec": "Videokoodek", @@ -473,8 +440,6 @@ "user_settings_description": "Halda kasutajate seadeid", "user_successfully_removed": "Kasutaja {email} edukalt eemaldatud.", "users_page_description": "Kasutajate haldamise leht", - "version_check_channel": "Väljalaske kanal", - "version_check_channel_description": "Vali väljalaske kanal, mille kohta soovid uute versioonide teadaandeid saada", "version_check_enabled_description": "Luba versioonikontroll", "version_check_implications": "Versioonikontroll vajab perioodilist Ãŧhendumist {server}-iga", "version_check_settings": "Versioonikontroll", @@ -489,6 +454,8 @@ "advanced_settings_clear_image_cache": "TÃŧhjenda pildipuhver", "advanced_settings_clear_image_cache_error": "Pildipuhvri tÃŧhjendamine ebaÃĩnnestus", "advanced_settings_clear_image_cache_success": "{size} edukalt tÃŧhjendatud", + "advanced_settings_enable_alternate_media_filter_subtitle": "Kasuta seda valikut, et filtreerida sÃŧnkroonimise ajal Ãŧksuseid alternatiivsete kriteeriumite alusel. Proovi seda ainult siis, kui rakendusel on probleeme kÃĩigi albumite tuvastamisega.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAALNE] Kasuta alternatiivset seadme albumi sÃŧnkroonimise filtrit", "advanced_settings_log_level_title": "Logimistase: {level}", "advanced_settings_prefer_remote_subtitle": "MÃĩned seadmed laadivad lokaalsete Ãŧksuste pisipilte piinavalt aeglaselt. Aktiveeri see seadistus, et laadida selle asemel kaugpilte.", "advanced_settings_prefer_remote_title": "Eelista kaugpilte", @@ -496,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Kohandatud vaheserveri päised [EKSPERIMENTAALNE]", "advanced_settings_readonly_mode_subtitle": "LÃŧlitab sisse kirjutuskaitsereÅžiimi, milles saab fotosid ainult vaadata ning toimingud nagu mitme pildi valimine, jagamine, edastamine ja kustutamine on keelatud. LÃŧlita kirjutuskaitsereÅžiim sisse/välja pÃĩhiekraanil oleva avatari kaudu", "advanced_settings_readonly_mode_title": "KirjutuskaitsereÅžiim", + "advanced_settings_self_signed_ssl_subtitle": "Jätab serveri lÃĩpp-punkti SSL-sertifikaadi kontrolli vahele. NÃĩutud endasigneeritud sertifikaatide jaoks.", + "advanced_settings_self_signed_ssl_title": "Luba endasigneeritud SSL-sertifikaadid [EKSPERIMENTAALNE]", "advanced_settings_sync_remote_deletions_subtitle": "Kustuta vÃĩi taasta Ãŧksus selles seadmes automaatself, kui sama tegevus toimub veebis", "advanced_settings_sync_remote_deletions_title": "SÃŧnkrooni kaugkustutamised [EKSPERIMENTAALNE]", "advanced_settings_tile_subtitle": "EdasijÃĩudnud kasutajate seaded", @@ -511,18 +480,31 @@ "album_delete_confirmation": "Kas oled kindel, et soovid albumi {album} kustutada?", "album_delete_confirmation_description": "Kui see album on jagatud, ei pääse teised kasutajad sellele enam ligi.", "album_deleted": "Album kustutatud", + "album_info_card_backup_album_excluded": "VÄLJA JÄETUD", + "album_info_card_backup_album_included": "LISATUD", "album_info_updated": "Albumi info muudetud", + "album_leave": "Lahku albumist?", + "album_leave_confirmation": "Kas oled kindel, et soovid albumist {album} lahkuda?", "album_name": "Albumi nimi", "album_options": "Albumi valikud", "album_remove_user": "Eemalda kasutaja?", "album_remove_user_confirmation": "Kas oled kindel, et soovid kasutaja {user} eemaldada?", "album_search_not_found": "Otsingule vastavaid albumeid ei leitud", + "album_selected": "Album valitud", "album_share_no_users": "Paistab, et oled seda albumit kÃĩikide kasutajatega jaganud, vÃĩi pole Ãŧhtegi kasutajat, kellega jagada.", "album_summary": "Albumi kokkuvÃĩte", "album_updated": "Album muudetud", "album_updated_setting_description": "Saa teavitus e-posti teel, kui jagatud albumis on uusi Ãŧksuseid", "album_upload_assets": "Laadi Ãŧksused oma arvutist Ãŧles ja lisa albumisse", + "album_user_left": "Lahkutud albumist {album}", + "album_user_removed": "Kasutaja {user} eemaldatud", + "album_viewer_appbar_delete_confirm": "Kas oled kindel, et soovid selle albumi oma kontolt kustutada?", "album_viewer_appbar_share_err_delete": "Albumi kustutamine ebaÃĩnnestus", + "album_viewer_appbar_share_err_leave": "Albumist lahkumine ebaÃĩnnestus", + "album_viewer_appbar_share_err_remove": "Üksuste albumist eemaldamisel tekkis probleeme", + "album_viewer_appbar_share_err_title": "Albumi pealkirja muutmine ebaÃĩnnestus", + "album_viewer_appbar_share_leave": "Lahku albumist", + "album_viewer_appbar_share_to": "Jaga", "album_viewer_page_share_add_users": "Lisa kasutajaid", "album_with_link_access": "Luba kÃĩigil, kellel on link, näha selle albumi fotosid ja isikuid.", "albums": "Albumid", @@ -531,12 +513,14 @@ "albums_default_sort_order_description": "Uute albumite lisamisel Ãŧksuste esialgne järjekord.", "albums_feature_description": "Üksuste kollektsioonid, mida saab teiste kasutajatega jagada.", "albums_on_device_count": "Albumid seadmel ({count})", + "albums_selected": "{count, plural, one {# album valitud} other {# albumit valitud}}", "all": "KÃĩik", "all_albums": "KÃĩik albumid", "all_people": "KÃĩik isikud", "all_photos": "KÃĩik fotod", "all_videos": "KÃĩik videod", "allow_dark_mode": "Luba tume teema", + "allow_edits": "Luba muutmine", "allow_public_user_to_download": "Luba avalikul kasutajal alla laadida", "allow_public_user_to_upload": "Luba avalikul kasutajal Ãŧles laadida", "allowed": "Lubatud", @@ -544,12 +528,14 @@ "always_keep": "Jäta alati alles", "always_keep_photos_hint": "Talletusruumi vabastamine jätab kÃĩik fotod selles seadmes alles.", "always_keep_videos_hint": "Talletusruumi vabastamine jätab kÃĩik videod selles seadmes alles.", + "anti_clockwise": "Vastupäeva", "api_key": "API vÃĩti", "api_key_description": "Seda väärtust kuvatakse ainult Ãŧks kord. Kopeeri see enne akna sulgemist.", "api_key_empty": "Su API vÃĩtme nimi ei tohiks olla tÃŧhi", "api_keys": "API vÃĩtmed", "app_architecture_variant": "Variant (arhitektuur)", "app_bar_signout_dialog_content": "Kas oled kindel, et soovid välja logida?", + "app_bar_signout_dialog_ok": "Jah", "app_bar_signout_dialog_title": "Logi välja", "app_download_links": "Rakenduse allalaadimise lingid", "app_settings": "Rakenduse seaded", @@ -560,19 +546,27 @@ "archive": "Arhiiv", "archive_action_prompt": "{count} lisatud arhiivi", "archive_or_unarchive_photo": "Arhiveeri vÃĩi taasta foto", + "archive_page_no_archived_assets": "Arhiveeritud Ãŧksuseid ei leitud", + "archive_page_title": "Arhiveeri ({count})", "archive_size": "Arhiivi suurus", "archive_size_description": "Seadista arhiivi suurus allalaadimiseks (GiB)", "archived": "Arhiveeritud", "archived_count": "{count, plural, other {# arhiveeritud}}", "are_these_the_same_person": "Kas need on sama isik?", "are_you_sure_to_do_this": "Kas oled kindel, et soovid seda teha?", + "array_field_not_fully_supported": "Massiivi väljad vajavad JSON-i käsitsi muutmist", + "asset_action_delete_err_read_only": "Kirjutuskaitstud Ãŧksuseid ei saa kustutada, jäetakse vahele", + "asset_action_share_err_offline": "Ühenduseta Ãŧksuseid ei saa pärida, jäetakse vahele", "asset_added_to_album": "Lisatud albumisse", "asset_adding_to_album": "Albumisse lisamineâ€Ļ", "asset_created": "Üksus loodud", - "asset_day_count": "{date}: {count, plural, one {# Ãŧksus} other {# Ãŧksust}}", "asset_description_updated": "Üksuse kirjeldus on muudetud", + "asset_filename_is_offline": "Üksus {filename} ei ole kättesaadav", + "asset_has_unassigned_faces": "Üksusel on seostamata nägusid", "asset_hashing": "Räsimineâ€Ļ", "asset_list_group_by_sub_title": "Grupeeri", + "asset_list_layout_settings_dynamic_layout_title": "DÃŧnaamiline asetus", + "asset_list_layout_settings_group_automatically": "Automaatne", "asset_list_layout_settings_group_by": "Grupeeri Ãŧksused", "asset_list_layout_settings_group_by_month_day": "Kuu + päev", "asset_list_layout_sub_title": "Asetus", @@ -583,27 +577,36 @@ "asset_not_found_on_icloud": "Üksust ei leitud iCloud'ist. Üksus vÃĩib olla iCloud'is oleva vigase faili tÃĩttu kättesaamatu", "asset_offline": "Üksus pole kättesaadav", "asset_offline_description": "Seda välise kogu Ãŧksust ei leitud kettalt. Abi saamiseks palun vÃĩta Ãŧhendust oma Immich'i administraatoriga.", + "asset_restored_successfully": "Üksus edukalt taastatud", "asset_skipped": "Vahele jäetud", "asset_skipped_in_trash": "PrÃŧgikastis", + "asset_trashed": "Üksus liigutatud prÃŧgikasti", "asset_troubleshoot": "Üksuse tÃĩrkeotsing", "asset_uploaded": "Üleslaaditud", "asset_uploading": "Üleslaadimineâ€Ļ", "asset_viewer_settings_subtitle": "Halda galeriivaaturi seadeid", "asset_viewer_settings_title": "Üksuste vaatur", "assets": "Üksused", + "assets_added_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} lisatud", "assets_added_to_album_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} albumisse lisatud", - "assets_added_to_album_partial_count": "{successCount}/{totalCount} {totalCount, plural, one {Ãŧksus} other {Ãŧksust}} lisatud albumisse", "assets_added_to_albums_count": "{assetTotal, plural, one {# Ãŧksus} other {# Ãŧksust}} lisatud {albumTotal, plural, one {# albumisse} other {# albumisse}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Üksust} other {Üksuseid}} ei saa albumisse lisada", "assets_cannot_be_added_to_albums": "{count, plural, one {Üksust} other {Üksuseid}} ei saa lisada Ãŧhtegi albumisse", "assets_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}}", + "assets_deleted_permanently": "{count} Ãŧksus(t) jäädavalt kustutatud", + "assets_deleted_permanently_from_server": "{count} Ãŧksus(t) Immich'i serverist jäädavalt kustutatud", + "assets_downloaded_failed": "{count, plural, one {# fail allalaaditud - {error} fail ebaÃĩnnestus} other {# faili allalaaditud - {error} faili ebaÃĩnnestus}}", + "assets_downloaded_successfully": "{count, plural, one {# fail edukalt allalaaditud} other {# faili edukalt allalaaditud}}", "assets_moved_to_trash_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} liigutatud prÃŧgikasti", "assets_permanently_deleted_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} jäädavalt kustutatud", "assets_removed_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} eemaldatud", + "assets_removed_permanently_from_device": "{count} Ãŧksus(t) seadmest jäädavalt eemaldatud", "assets_restore_confirmation": "Kas oled kindel, et soovid oma prÃŧgikasti liigutatud Ãŧksused taastada? Seda ei saa tagasi vÃĩtta! Pane tähele, et sel meetodil ei saa taastada Ãŧhenduseta Ãŧksuseid.", "assets_restored_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} taastatud", + "assets_restored_successfully": "{count} Ãŧksus(t) edukalt taastatud", "assets_trashed": "{count} Ãŧksus(t) liigutatud prÃŧgikasti", "assets_trashed_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} liigutatud prÃŧgikasti", + "assets_trashed_from_server": "{count} Ãŧksus(t) liigutatud Immich'i serveris prÃŧgikasti", "assets_were_part_of_album_count": "{count, plural, one {Üksus oli} other {Üksused olid}} juba osa albumist", "assets_were_part_of_albums_count": "{count, plural, one {Üksus oli} other {Üksused olid}} juba nendes albumites", "authorized_devices": "Autoriseeritud seadmed", @@ -612,48 +615,87 @@ "autoplay_slideshow": "Esita slaidiesitlus automaatselt", "back": "Tagasi", "back_close_deselect": "Tagasi, sulge vÃĩi tÃŧhista valik", + "background_backup_running_error": "Taustvarundus on käimas, ei saa käsitsi varundust alustada", "background_location_permission": "Taustal asukoha luba", "background_location_permission_content": "Et taustal tÃļÃļtades vÃĩrguÃŧhendust vahetada, peab Immich'il *alati* olema täpse asukoha luba, et rakendus saaks WiFi-vÃĩrgu nime lugeda", "background_options": "Taustavalikud", "backup": "Varundamine", + "backup_album_selection_page_albums_device": "Albumid seadmel ({count})", "backup_album_selection_page_albums_tap": "Puuduta kaasamiseks, topeltpuuduta välistamiseks", "backup_album_selection_page_assets_scatter": "Üksused vÃĩivad olla jaotatud mitme albumi vahel. Seega saab albumeid varundamise protsessi kaasata vÃĩi välistada.", "backup_album_selection_page_select_albums": "Vali albumid", "backup_album_selection_page_selection_info": "Valiku info", + "backup_album_selection_page_total_assets": "Unikaalseid Ãŧksuseid kokku", "backup_albums_sync": "Varundusalbumite sÃŧnkroniseerimine", + "backup_all": "KÃĩik", + "backup_background_service_backup_failed_message": "Üksuste varundamine ebaÃĩnnestus. Uuesti proovimineâ€Ļ", "backup_background_service_complete_notification": "Üksuste varundamine lÃĩppenud", + "backup_background_service_connection_failed_message": "Serveriga Ãŧhendumine ebaÃĩnnestus. Uuesti proovimineâ€Ļ", + "backup_background_service_current_upload_notification": "{filename} Ãŧleslaadimine", "backup_background_service_default_notification": "Uute Ãŧksuste kontrollimineâ€Ļ", + "backup_background_service_error_title": "Varundamise viga", "backup_background_service_in_progress_notification": "Sinu Ãŧksuste varundamineâ€Ļ", + "backup_background_service_upload_failure_notification": "Faili {filename} Ãŧleslaadimine ebaÃĩnnestus", "backup_controller_page_albums": "Varunduse albumid", + "backup_controller_page_background_app_refresh_disabled_content": "Taustal varundamise kasutamiseks luba rakenduse taustal värskendamine: Seaded > Üldine > Rakenduse taustal värskendamine.", + "backup_controller_page_background_app_refresh_disabled_title": "Rakenduse taustal värskendamine keelatud", + "backup_controller_page_background_app_refresh_enable_button_text": "Mine seadetesse", "backup_controller_page_background_battery_info_link": "Näita mulle, kuidas", "backup_controller_page_background_battery_info_message": "Parima taustal varundamise kogemuse jaoks palun keela Immich'i puhul kÃĩik taustategevust piiravad aku optimeerimised.\n\nKuna see on seadmespetsiifiline, otsi vajalikku teavet oma seadme tootja kohta.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Aku optimeerimised", + "backup_controller_page_background_charging": "Ainult laadimise ajal", + "backup_controller_page_background_configure_error": "Taustateenuse seadistamine ebaÃĩnnestus", "backup_controller_page_background_delay": "Oota uute Ãŧksuste varundamisega: {duration}", + "backup_controller_page_background_description": "LÃŧlita taustateenus sisse, et uusi Ãŧksuseid automaatselt varundada, ilma et peaks rakendust avama", + "backup_controller_page_background_is_off": "Automaatne taustal varundamine on välja lÃŧlitatud", + "backup_controller_page_background_is_on": "Automaatne taustal varundamine on sisse lÃŧlitatud", + "backup_controller_page_background_turn_off": "LÃŧlita taustateenus välja", + "backup_controller_page_background_turn_on": "LÃŧlita taustateenus sisse", + "backup_controller_page_background_wifi": "Ainult WiFi-vÃĩrgus", "backup_controller_page_backup": "Varundamine", "backup_controller_page_backup_selected": "Valitud: ", "backup_controller_page_backup_sub": "Varundatud fotod ja videod", + "backup_controller_page_created": "Lisatud: {date}", + "backup_controller_page_desc_backup": "LÃŧlita sisse esiplaanil varundamine, et rakenduse avamisel uued Ãŧksused automaatselt serverisse Ãŧles laadida.", "backup_controller_page_excluded": "Välistatud: ", + "backup_controller_page_failed": "EbaÃĩnnestunud ({count})", + "backup_controller_page_filename": "Failinimi: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Varunduse info", "backup_controller_page_none_selected": "Ühtegi pole valitud", "backup_controller_page_remainder": "Ootel", "backup_controller_page_remainder_sub": "Valitud fotod ja videod, mis on veel varundamise ootel", "backup_controller_page_server_storage": "Serveri talletusruum", + "backup_controller_page_start_backup": "Alusta varundamist", + "backup_controller_page_status_off": "Automaatne esiplaanil varundamine on välja lÃŧlitatud", + "backup_controller_page_status_on": "Automaatne esiplaanil varundamine on sisse lÃŧlitatud", "backup_controller_page_storage_format": "{used}/{total} kasutusel", "backup_controller_page_to_backup": "Albumid, mida varundada", "backup_controller_page_total_sub": "KÃĩik unikaalsed fotod ja videod valitud albumitest", + "backup_controller_page_turn_off": "LÃŧlita esiplaanil varundus välja", + "backup_controller_page_turn_on": "LÃŧlita esiplaanil varundus sisse", + "backup_controller_page_uploading_file_info": "Faili info Ãŧleslaadimine", + "backup_err_only_album": "Ei saa ainsat albumit eemaldada", "backup_error_sync_failed": "SÃŧnkroonimine ebaÃĩnnestus. Varundust ei saa tÃļÃļdelda.", "backup_info_card_assets": "Ãŧksused", + "backup_manual_cancelled": "TÃŧhistatud", + "backup_manual_in_progress": "Üleslaadimine juba käib. Proovi hiljem uuesti", + "backup_manual_success": "Õnnestus", + "backup_manual_title": "Üleslaadimise staatus", "backup_options": "Varunduse valikud", + "backup_options_page_title": "Varundamise valikud", + "backup_setting_subtitle": "Halda taustal ja esiplaanil Ãŧleslaadimise seadeid", "backup_settings_subtitle": "Halda Ãŧleslaadimise seadeid", + "backup_upload_details_page_more_details": "Puuduta rohkema info saamiseks", "backward": "Tagasi", - "battery_optimization_backup_reliability": "Akukasutuse optimeerimise keelamine vÃĩib parandada taustal varundamise tÃļÃļkindlust", "biometric_auth_enabled": "Biomeetriline autentimine lubatud", "biometric_locked_out": "Biomeetriline autentimine on blokeeritud", "biometric_no_options": "Biomeetrilisi valikuid ei ole", "biometric_not_available": "Biomeetriline autentimine ei ole selles seadmes saadaval", + "birthdate_saved": "SÃŧnnikuupäev salvestatud", "birthdate_set_description": "SÃŧnnikuupäeva kasutatakse isiku vanuse arvutamiseks foto tegemise hetkel.", "blurred_background": "Udustatud taust", - "browse_templates": "Sirvi malle", "bugs_and_feature_requests": "Vearaportid ja täiendussoovid", "build": "Kooste", "build_image": "KoostetÃĩmmis", @@ -661,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Kas oled kindel, et soovid {count, plural, one {# dubleeritud Ãŧksuse} other {# dubleeritud Ãŧksust}} alles jätta? Sellega märgitakse kÃĩik duplikaadigrupid lahendatuks ilma midagi kustutamata.", "bulk_trash_duplicates_confirmation": "Kas oled kindel, et soovid {count, plural, one {# dubleeritud Ãŧksuse} other {# dubleeritud Ãŧksust}} masskustutada? Sellega jäetakse alles iga grupi suurim Ãŧksus ning duplikaadid liigutatakse prÃŧgikasti.", "buy": "Osta Immich", + "cache_settings_clear_cache_button": "TÃŧhjenda puhver", + "cache_settings_clear_cache_button_title": "TÃŧhjendab rakenduse puhvri. See mÃĩjutab oluliselt rakenduse jÃĩudlust, kuni puhver uuesti täidetakse.", + "cache_settings_duplicated_assets_clear_button": "TÜHJENDA", + "cache_settings_duplicated_assets_subtitle": "Fotod ja videod, mis on rakenduse poolt ignoreeritud", + "cache_settings_duplicated_assets_title": "Dubleeritud Ãŧksused ({count})", + "cache_settings_statistics_album": "Kogu pisipildid", + "cache_settings_statistics_full": "TäismÃĩÃĩdus pildid", + "cache_settings_statistics_shared": "Jagatud albumite pisipildid", + "cache_settings_statistics_thumbnail": "Pisipildid", + "cache_settings_statistics_title": "Puhvri kasutus", + "cache_settings_subtitle": "Juhi Immich'i rakenduse puhverdamist", + "cache_settings_tile_subtitle": "Juhi lokaalse talletuse käitumist", + "cache_settings_tile_title": "Lokaalne talletus", + "cache_settings_title": "Puhverdamise seaded", "camera": "Kaamera", "camera_brand": "Kaamera mark", "camera_model": "Kaamera mudel", @@ -673,10 +729,10 @@ "cannot_update_the_description": "Kirjelduse muutmine ebaÃĩnnestus", "cast": "Edasta", "cast_description": "Seadista saadavalolevaid voogedastuse sihtpunkte", - "change": "Muuda", "change_date": "Muuda kuupäeva", "change_description": "Muuda kirjeldust", "change_display_order": "Muuda kuva järjekorda", + "change_expiration_time": "Muuda aegumisaega", "change_location": "Muuda asukohta", "change_name": "Muuda nime", "change_name_successfully": "Nimi edukalt muudetud", @@ -690,13 +746,17 @@ "change_password_form_password_mismatch": "Paroolid ei klapi", "change_password_form_reenter_new_password": "Korda uut parooli", "change_pin_code": "Muuda PIN-koodi", + "change_trigger": "Muuda päästikut", + "change_trigger_prompt": "Kas oled kindel, et soovid päästikut muuta? See eemaldab kÃĩik olemasolevad tegevused ja filtrid.", "change_your_password": "Muuda oma parooli", "changed_visibility_successfully": "Nähtavus muudetud", "charging": "Laadimine", "charging_requirement_mobile_backup": "Taustal varundus vajab, et seade oleks laadimas", + "check_corrupt_asset_backup": "Otsi riknenud Ãŧksuste varukoopiaid", + "check_corrupt_asset_backup_button": "Teosta kontroll", + "check_corrupt_asset_backup_description": "Käivita see kontroll ainult WiFi-vÃĩrgus ja siis, kui kÃĩik Ãŧksused on varundatud. See protseduur vÃĩib kesta mÃĩne minuti.", "check_logs": "Vaata logisid", "checksum": "Kontrollsumma", - "choose": "Vali", "choose_matching_people_to_merge": "Vali kattuvad isikud, mida Ãŧhendada", "city": "Linn", "cleanup_confirm_description": "Immich leidis {count} Ãŧksus(t) (lisatud enne {date}), mis on turvaliselt serverisse varundatud. Kas eemaldada sellest seadmest lokaalsed koopiad?", @@ -714,22 +774,25 @@ "clear": "TÃŧhjenda", "clear_all": "TÃŧhjenda kÃĩik", "clear_all_recent_searches": "TÃŧhjenda hiljutised otsingud", - "clear_failed_count": "Eemalda ebaÃĩnnestunud ({count})", "clear_file_cache": "TÃŧhjenda failipuhver", "clear_message": "TÃŧhjenda sÃĩnum", "clear_value": "TÃŧhjenda väärtus", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Sisesta parool", "client_cert_import": "Impordi", "client_cert_import_success_msg": "Klientsertifikaat on imporditud", "client_cert_invalid_msg": "Vigane sertifikaadi fail vÃĩi vale parool", "client_cert_password_message": "Sisesta sertifikaadi salasÃĩna", "client_cert_password_title": "Sertifikaadi salasÃĩna", "client_cert_remove_msg": "Klientsertifikaat on eemaldatud", - "client_cert_subtitle": "Toetab ainult PKCS12 (.p12, .pfx) vormingut. Sertifikaadi importimine/eemaldamine on saadaval ainult enne sisselogimist", + "client_cert_subtitle": "Toetab ainult PKCS12 (.p12, .pfx) formaati. Sertifikaadi importimine/eemaldamine on saadaval ainult enne sisselogimist", "client_cert_title": "SSL klientsertifikaat [EKSPERIMENTAALNE]", + "clockwise": "Päripäeva", "close": "Sulge", "collapse": "Peida", "collapse_all": "Peida kÃĩik", "color": "Värv", + "color_theme": "Värviteema", "command": "Käsk", "command_palette_prompt": "Leia kiirelt lehti, tegevusi vÃĩi käske", "command_palette_to_close": "sulge", @@ -742,7 +805,6 @@ "comments_are_disabled": "Kommentaarid on keelatud", "common_create_new_album": "Lisa uus album", "completed": "LÃĩpetatud", - "configuration": "Seadistus", "confirm": "Kinnita", "confirm_admin_password": "Kinnita administraatori parool", "confirm_delete_face": "Kas oled kindel, et soovid isiku {name} näo Ãŧksuselt kustutada?", @@ -757,16 +819,19 @@ "contain": "Mahuta ära", "context": "Kontekst", "continue": "Jätka", - "control_bottom_app_bar_add_tags": "Lisa silte", + "control_bottom_app_bar_create_new_album": "Lisa uus album", + "control_bottom_app_bar_delete_from_immich": "Kustuta Immich'ist", "control_bottom_app_bar_delete_from_local": "Kustuta seadmest", "control_bottom_app_bar_edit_location": "Muuda asukohta", "control_bottom_app_bar_edit_time": "Muuda kuupäeva ja aega", + "control_bottom_app_bar_share_link": "Jaga linki", + "control_bottom_app_bar_share_to": "Jaga", "control_bottom_app_bar_trash_from_immich": "Liiguta prÃŧgikasti", "copied_image_to_clipboard": "Pilt kopeeritud lÃĩikelauale.", "copied_to_clipboard": "Kopeeritud lÃĩikelauale!", "copy_error": "Kopeeri viga", + "copy_file_path": "Kopeeri failitee", "copy_image": "Kopeeri pilt", - "copy_json": "Kopeeri JSON", "copy_link": "Kopeeri link", "copy_link_to_clipboard": "Kopeeri link lÃĩikelauale", "copy_password": "Kopeeri parool", @@ -784,6 +849,7 @@ "create_link_to_share": "Lisa jagamiseks link", "create_link_to_share_description": "Luba kÃĩigil, kellel on link, valitud pilte näha", "create_new": "LISA UUS", + "create_new_face": "Lisa uus nägu", "create_new_person": "Lisa uus isik", "create_new_person_hint": "Seosta valitud Ãŧksused uue isikuga", "create_new_user": "Lisa uus kasutaja", @@ -800,33 +866,37 @@ "created_at": "Lisatud", "creating_linked_albums": "Lingitud albumite loomine...", "crop": "Kärpimine", + "crop_aspect_ratio_fixed": "Fikseeritud", "crop_aspect_ratio_free": "Vaba", - "crop_aspect_ratio_original": "Algne", + "crop_aspect_ratio_original": "Originaalne", "crop_aspect_ratio_square": "Ruut", + "curated_object_page_title": "Asjad", "current_device": "Praegune seade", "current_pin_code": "Praegune PIN-kood", "current_server_address": "Praegune serveri aadress", "custom_date": "Muu kuupäev", "custom_locale": "Kohandatud lokaat", "custom_locale_description": "Vorminda kuupäevad, kellaajad ja arvud vastavalt valitud keelele ja regioonile", + "custom_url": "Kohandatud URL", "cutoff_date_description": "Jäta alles fotod ja videod viimasestâ€Ļ", "cutoff_day": "{count, plural, one {päev} other {päeva}}", "cutoff_year": "{count, plural, one {aasta} other {aastat}}", + "daily_title_text_date": "d. MMMM", + "daily_title_text_date_year": "d. MMMM yyyy", "dark": "Tume", "dark_theme": "Vali tume teema", "date": "Kuupäev", "date_after": "Kuupäev pärast", "date_and_time": "Kuupäev ja kellaaeg", "date_before": "Kuupäev enne", - "date_of_birth": "SÃŧnnikuupäev", + "date_format": "d. MMMM y â€ĸ HH:mm", "date_of_birth_saved": "SÃŧnnikuupäev salvestatud", "date_range": "Kuupäevavahemik", - "date_time_original": "Algne aeg ja kuupäev", "day": "Päev", "days": "Päeva", "deduplicate_all": "Dedubleeri kÃĩik", - "default_quality_subtitle": "Kvaliteet, mida jagamisel kasutada. Hoia jagamise nuppu all, et iga kord valida.", - "default_share_quality": "Vaikimisi jagamise kvaliteet", + "default_locale": "Vaikimisi lokaat", + "default_locale_description": "Vorminda kuupäevad ja arvud vastavalt brauseri lokaadile", "delete": "Kustuta", "delete_action_confirmation_message": "Kas oled kindel, et soovid selle Ãŧksuse kustutada? See toiming liigutab Ãŧksuse serveri prÃŧgikasti ja kÃŧsib, kas soovid selle lokaalselt kustutada", "delete_action_prompt": "{count} kustutatud", @@ -835,6 +905,8 @@ "delete_dialog_alert": "Need Ãŧksused kustutatakse jäädavalt Immich'ist ja sinu seadmest", "delete_dialog_alert_local": "Need Ãŧksused kustutatakse jäädavalt sinu seadmest, aga jäävad Immich'i serverisse alles", "delete_dialog_alert_local_non_backed_up": "MÃĩned Ãŧksustest ei ole Immich'isse varundatud ning kustutatakse su seadmest jäädavalt", + "delete_dialog_alert_remote": "Need Ãŧksused kustutatakse jäädavalt Immich'i serverist", + "delete_dialog_ok_force": "Kustuta sellegipoolest", "delete_dialog_title": "Kustuta jäädavalt", "delete_duplicates_confirmation": "Kas oled kindel, et soovid need duplikaadid jäädavalt kustutada?", "delete_face": "Kustuta nägu", @@ -853,45 +925,52 @@ "delete_tag_confirmation_prompt": "Kas oled kindel, et soovid sildi {tagName} kustutada?", "delete_user": "Kustuta kasutaja", "deleted_shared_link": "Jagatud link kustutatud", + "deletes_missing_assets": "Kustutab Ãŧksused, mis on kettalt puudu", "description": "Kirjeldus", + "description_input_hint_text": "Lisa kirjeldus...", + "description_input_submit_error": "Viga kirjelduse muutmisel, rohkem infot leiad logist", "deselect_all": "Eemalda kÃĩik valikust", "details": "Üksikasjad", "direction": "Suund", - "disable": "Peata", + "disable": "Keela", "disabled": "Välja lÃŧlitatud", + "disallow_edits": "Keela muutmine", "discord": "Discord", "discover": "Avasta", "discovered_devices": "Avastatud seadmed", "dismiss_all_errors": "Peida kÃĩik veateated", + "dismiss_error": "Peida veateade", "display_options": "Kuva valikud", "display_order": "Kuvamise järjekord", - "display_original_photos": "Kuva algsed pildid", + "display_original_photos": "Kuva originaalpildid", "display_original_photos_setting_description": "Eelista Ãŧksuse vaatamisel pisipildile algset fotot, kui see on veebiga Ãŧhilduv. See vÃĩib mÃĩjutada fotode kuvamise kiirust.", "do_not_show_again": "Ära näita enam seda teadet", "documentation": "Dokumentatsioon", "done": "Tehtud", "download": "Laadi alla", + "download_action_prompt": "{count} Ãŧksust laaditakse alla", "download_canceled": "Allalaadimine katkestatud", "download_complete": "Allalaadimine lÃĩpetatud", "download_enqueue": "Allalaadimine ootel", + "download_error": "Allalaadimise viga", "download_failed": "Allalaadimine ebaÃĩnnestus", "download_finished": "Allalaadimine lÃĩpetatud", "download_include_embedded_motion_videos": "Manustatud videod", "download_include_embedded_motion_videos_description": "Lisa liikuvatesse fotodesse manustatud videod eraldi failidena", "download_notfound": "Allalaadimist ei leitud", - "download_original": "Laadi algne Ãŧksus alla", + "download_original": "Laadi originaal alla", "download_paused": "Allalaadimine peatatud", "download_settings": "Allalaadimine", "download_settings_description": "Halda Ãŧksuste allalaadimise seadeid", + "download_started": "Allalaadimine alustatud", + "download_sucess": "Allalaadimine Ãĩnnestus", + "download_sucess_android": "Üksused laaditi alla kataloogi DCIM/Immich", "download_waiting_to_retry": "Uuesti proovimise ootel", "downloading": "Allalaadimine", "downloading_asset_filename": "Üksuse {filename} allalaadimine", "downloading_from_icloud": "iCloud'ist allalaadimine", "downloading_media": "Üksuste allalaadimine", - "drag_to_reorder": "Lohista järjestamiseks", "drop_files_to_upload": "Failide Ãŧleslaadimiseks sikuta need ÃŧkskÃĩik kuhu", - "duplicate": "Dubleeri", - "duplicate_workflow": "Dubleeri tÃļÃļvoog", "duplicates": "Duplikaadid", "duplicates_description": "Lahenda iga grupp, valides duplikaadid, kui neid on.", "duration": "Kestus", @@ -903,7 +982,9 @@ "edit_date_and_time": "Muuda kuupäeva ja kellaaega", "edit_date_and_time_action_prompt": "{count} päev ja kellaaeg muudetud", "edit_date_and_time_by_offset": "Nihuta kuupäeva", + "edit_date_and_time_by_offset_interval": "Uus kuupäevavahemik: {from} - {to}", "edit_description": "Muuda kirjeldust", + "edit_description_prompt": "Palun vali uus kirjeldus:", "edit_exclusion_pattern": "Muuda välistamismustrit", "edit_faces": "Muuda nägusid", "edit_key": "Muuda vÃĩtit", @@ -918,6 +999,9 @@ "edit_user": "Muuda kasutajat", "edit_workflow": "Muuda tÃļÃļvoogu", "editor": "Redaktor", + "editor_close_without_save_prompt": "Muudatusi ei salvestata", + "editor_close_without_save_title": "Sulge redaktor?", + "editor_confirm_reset_all_changes": "Kas oled kindel, et soovid kÃĩik muudatused tÃŧhistada?", "editor_discard_edits_confirm": "TÃŧhista muudatused", "editor_discard_edits_prompt": "Sul on salvestamata muudatusi. Kas oled kindel, et soovid need tÃŧhistada?", "editor_discard_edits_title": "TÃŧhista muudatused?", @@ -946,7 +1030,9 @@ "enter_your_pin_code": "Sisesta oma PIN-kood", "enter_your_pin_code_subtitle": "Sisesta oma PIN-kood, et lukustatud kaustale ligi pääseda", "error": "Viga", + "error_change_sort_album": "Albumi sorteerimisjärjestuse muutmine ebaÃĩnnestus", "error_delete_face": "Viga näo kustutamisel", + "error_getting_places": "Viga kohtade pärimisel", "error_loading_albums": "Viga albumite laadimisel", "error_loading_image": "Viga pildi laadimisel", "error_loading_partners": "Viga partnerite laadimisel: {error}", @@ -986,7 +1072,6 @@ "failed_to_remove_product_key": "TootevÃĩtme eemaldamine ebaÃĩnnestus", "failed_to_reset_pin_code": "PIN-koodi lähestamine ebaÃĩnnestus", "failed_to_stack_assets": "Üksuste virnastamine ebaÃĩnnestus", - "failed_to_tag_assets": "Üksuste sildistamine ebaÃĩnnestus", "failed_to_unstack_assets": "Üksuste eraldamine ebaÃĩnnestus", "failed_to_update_notification_status": "Teavituste seisundi uuendamine ebaÃĩnnestus", "incorrect_email_or_password": "Vale e-posti aadress vÃĩi parool", @@ -1085,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Lisa kirjeldus...", "exif_bottom_sheet_description_error": "Viga kirjelduse muutmisel", + "exif_bottom_sheet_details": "ÜKSIKASJAD", + "exif_bottom_sheet_location": "ASUKOHT", "exif_bottom_sheet_no_description": "Kirjeldus puudub", + "exif_bottom_sheet_people": "ISIKUD", + "exif_bottom_sheet_person_add_person": "Lisa nimi", "exit_slideshow": "Sulge slaidiesitlus", "expand": "Laienda", "expand_all": "Näita kÃĩik", + "experimental_settings_new_asset_list_subtitle": "TÃļÃļs", + "experimental_settings_new_asset_list_title": "Luba eksperimentaalne fotoruudistik", + "experimental_settings_subtitle": "Kasuta omal vastutusel!", + "experimental_settings_title": "Eksperimentaalne", "expire_after": "Aegub", "expired": "Aegunud", "expires_date": "Aegub {date}", @@ -1098,40 +1191,41 @@ "export_as_json": "Ekspordi JSON-formaati", "export_database": "Ekspordi andmebaas", "export_database_description": "Ekspordi SQLite andmebaas", - "exposure_time": "Säriaeg", "extension": "Laiend", "external": "Väline", "external_libraries": "Välised kogud", "external_network": "Väline vÃĩrk", "external_network_sheet_info": "Kui seade ei ole eelistatud WiFi-vÃĩrgus, Ãŧhendub rakendus serveriga allolevatest URL-idest esimese kättesaadava kaudu, alustades Ãŧlevalt", - "f_number": "F-arv", "face_unassigned": "Seostamata", "failed": "EbaÃĩnnestus", "failed_count": "EbaÃĩnnestunud: {count}", "failed_to_authenticate": "Autentimine ebaÃĩnnestus", - "failed_to_delete_file": "Faili kustutamine ebaÃĩnnestus", "failed_to_load_assets": "Üksuste laadimine ebaÃĩnnestus", "failed_to_load_folder": "Kausta laadimine ebaÃĩnnestus", "favorite": "Lemmik", "favorite_action_prompt": "{count} lisatud lemmikutesse", "favorite_or_unfavorite_photo": "Lisa foto lemmikutesse vÃĩi eemalda lemmikutest", "favorites": "Lemmikud", + "favorites_page_no_favorites": "Lemmikuid Ãŧksuseid ei leitud", "feature_photo_updated": "EsiletÃĩstetud foto muudetud", "features": "Funktsioonid", + "features_in_development": "Arendusjärgus olevad funktsioonid", "features_setting_description": "Halda rakenduse funktsioone", "file_name_or_extension": "Failinimi vÃĩi -laiend", "file_name_text": "Faili nimi", + "file_name_with_value": "Faili nimi: {file_name}", "file_size": "Failisuurus", "filename": "Failinimi", "filetype": "FailitÃŧÃŧp", "filter": "Filter", + "filter_description": "Tingimused, mille alusel Ãŧksuseid filtreerida", "filter_people": "Filtreeri isikuid", "filter_places": "Filtreeri kohti", "filter_tags": "Filtreeri silte", "filters": "Filtrid", + "find_them_fast": "Leia teda kiiresti nime järgi otsides", "first": "Esimene", "fix_incorrect_match": "Paranda ebaÃĩige vaste", - "focal_length": "Fookuskaugus", "folder": "Kaust", "folder_not_found": "Kausta ei leitud", "folders": "Kaustad", @@ -1142,7 +1236,6 @@ "free_up_space_description": "Liiguta varundatud fotod ja videod prÃŧgikasti, et talletusruumi vabastada. Serveris olevad koopiad jäävad alles.", "free_up_space_settings_subtitle": "Vabasta seadme talletusruumi", "full_path": "Täielik tee: {path}", - "full_path_or_folder": "Täielik tee vÃĩi kaust", "gcast_enabled": "Google Cast", "gcast_enabled_description": "See funktsionaalsus laadib tÃļÃļtamiseks Google'st väliseid ressursse.", "general": "Üldine", @@ -1163,6 +1256,7 @@ "group_owner": "Grupeeri omaniku kaupa", "group_places_by": "Grupeeri kohad...", "group_year": "Grupeeri aasta kaupa", + "haptic_feedback_switch": "Luba haptiline tagasiside", "haptic_feedback_title": "Haptiline tagasiside", "has_quota": "On kvoot", "hash_asset": "Arvuta Ãŧksuse räsi", @@ -1183,12 +1277,29 @@ "hide_schema": "Peida skeem", "hide_text_recognition": "Peida tekstituvastus", "hide_unnamed_people": "Peida nimetud isikud", + "home_page_add_to_album_conflicts": "{added} Ãŧksust lisati albumisse {album}. {failed} Ãŧksust oli juba albumis.", + "home_page_add_to_album_err_local": "Lokaalseid Ãŧksuseid ei saa veel albumisse lisada, jäetakse vahele", + "home_page_add_to_album_success": "{added} Ãŧksust lisati albumisse {album}.", + "home_page_album_err_partner": "Partneri Ãŧksuseid ei saa veel albumisse lisada, jäetakse vahele", + "home_page_archive_err_local": "Lokaalseid Ãŧksuseid ei saa veel arhiveerida, jäetakse vahele", + "home_page_archive_err_partner": "Partneri Ãŧksuseid ei saa arhiveerida, jäetakse vahele", "home_page_building_timeline": "Ajajoone koostamine", + "home_page_delete_err_partner": "Partneri Ãŧksuseid ei saa kustutada, jäetakse vahele", + "home_page_delete_remote_err_local": "Kaugkustutamise valikus on lokaalsed Ãŧksused, jäetakse vahele", + "home_page_favorite_err_local": "Lokaalseid Ãŧksuseid ei saa lemmikuks märkida, jäetakse vahele", + "home_page_favorite_err_partner": "Partneri Ãŧksuseid ei saa lemmikuks märkida, jäetakse vahele", + "home_page_first_time_notice": "Kui see on su esimene kord rakendust kasutada, vali varunduse album, et ajajoon saaks sellest fotosid ja videosid kuvada", + "home_page_locked_error_local": "Lokaalseid Ãŧksuseid ei saa lukustatud kausta liigutada, jäetakse vahele", + "home_page_locked_error_partner": "Partneri Ãŧksuseid ei saa lukustatud kausta lisada, jäetakse vahele", + "home_page_share_err_local": "Lokaalseid Ãŧksuseid ei saa lingiga jagada, jäetakse vahele", + "home_page_upload_err_limit": "Korraga saab Ãŧles laadida ainult 30 Ãŧksust, jäetakse vahele", "host": "Host", "hour": "Tund", "hours": "Tundi", "id": "ID", "idle": "JÃĩude", + "ignore_icloud_photos": "Ignoreeri iCloud fotosid", + "ignore_icloud_photos_description": "Fotosid, mis on iCloud'is, ei laadita Ãŧles Immich'i serverisse", "image": "Pilt", "image_alt_text_date": "{isVideo, select, true {Video} other {Pilt}} tehtud {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} koos isikuga {person1}", @@ -1200,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos isikutega {person1} ja {person2}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos isikutega {person1}, {person2} ja {person3}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Pilt}} tehtud {date} kohas {city}, {country} koos {person1}, {person2} ja veel {additionalCount, number} isikuga", + "image_saved_successfully": "Pilt salvestatud", + "image_viewer_page_state_provider_download_started": "Allalaadimine alustatud", + "image_viewer_page_state_provider_download_success": "Allalaadimine Ãĩnnestus", + "image_viewer_page_state_provider_share_error": "Jagamise viga", "immich_logo": "Immich'i logo", "immich_web_interface": "Immich'i veebiliides", "import_from_json": "Impordi JSON-formaadist", @@ -1221,12 +1336,19 @@ "night_at_twoam": "Iga ÃļÃļ kell 2" }, "invalid_date": "Vigane kuupäev", - "invalid_date_format": "Vigane kuupäevavorming", + "invalid_date_format": "Vigane kuupäevaformaat", "invite_people": "Kutsu inimesi", "invite_to_album": "Kutsu albumisse", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Andmed laaditi {dateTime}", + "ios_debug_info_last_sync_at": "Viimane sÃŧnkroonimine {dateTime}", + "ios_debug_info_no_processes_queued": "Taustaprotsesse pole järjekorras", + "ios_debug_info_no_sync_yet": "Taustal sÃŧnkroonimise tÃļÃļde pole veel käinud", + "ios_debug_info_processes_queued": "{count, plural, one {{count} taustaprotsess järjekorras} other {{count} taustaprotsessi järjekorras}}", + "ios_debug_info_processing_ran_at": "TÃļÃļtlemine toimus {dateTime}", "items_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}}", "jobs": "TÃļÃļted", + "json_editor": "JSON-redaktor", + "json_error": "JSON-i viga", "keep": "Jäta alles", "keep_albums": "Jäta albumid alles", "keep_albums_count": "{count} {count, plural, one {album} other {albumit}} jäetakse alles", @@ -1253,12 +1375,14 @@ "leave": "Lahku", "leave_album": "Lahku albumist", "lens_model": "Läätse mudel", - "less": "Vähem", "let_others_respond": "Luba teistel vastata", "level": "Tase", "library": "Kogu", "library_add_folder": "Lisa kaust", "library_edit_folder": "Muuda kausta", + "library_options": "Kogu seaded", + "library_page_device_albums": "Albumid seadmes", + "library_page_new_album": "Uus album", "library_page_sort_asset_count": "Üksuste arv", "library_page_sort_created": "Loomise aeg", "library_page_sort_last_modified": "Viimase muutmise aeg", @@ -1268,13 +1392,11 @@ "light_theme": "Vali hele teema", "like": "Meeldib", "like_deleted": "Meeldimine kustutatud", - "link": "Link", "link_motion_video": "Lingi liikuv video", "link_to_docs": "Rohkema info saamiseks vaata dokumentatsiooni.", "link_to_oauth": "Ühenda OAuth", "linked_oauth_account": "OAuth konto Ãŧhendatud", "list": "Loend", - "load_more": "Laadi rohkem", "loading": "Laadimine", "loading_search_results_failed": "Otsitulemuste laadimine ebaÃĩnnestus", "local": "Lokaalsed", @@ -1303,9 +1425,11 @@ "login": "Logi sisse", "login_disabled": "Sisselogimine on keelatud", "login_form_api_exception": "API viga. Kontrolli serveri URL-i ja proovi uuesti.", + "login_form_back_button_text": "Tagasi", "login_form_email_hint": "sinunimi@email.com", "login_form_endpoint_hint": "http://serveri-ip:port", "login_form_endpoint_url": "Serveri lÃĩpp-punkti URL", + "login_form_err_http": "Palun täpsusta http:// vÃĩi https://", "login_form_err_invalid_email": "Vigane e-posti aadress", "login_form_err_invalid_url": "Vigane URL", "login_form_err_leading_whitespace": "Eelnevad tÃŧhikud", @@ -1315,9 +1439,10 @@ "login_form_failed_login": "Viga sisselogimisel, kontrolli serveri URL-i, e-posti aadressi ja parooli", "login_form_handshake_exception": "Serveriga suhtlemisel tekkis kätlemise viga. Kui kasutad endasigneeritud sertifikaati, luba seadetes endasigneeritud sertifikaatide tugi.", "login_form_password_hint": "parool", - "login_form_server_empty": "Sisesta serveri URL", - "login_form_server_error": "Serveriga Ãŧhendumine ebaÃĩnnestus", - "login_has_been_disabled": "Sisselogimine on keelatud", + "login_form_save_login": "Jää sisselogituks", + "login_form_server_empty": "Sisesta serveri URL.", + "login_form_server_error": "Serveriga Ãŧhendumine ebaÃĩnnestus.", + "login_has_been_disabled": "Sisselogimine on keelatud.", "login_password_changed_error": "Parooli muutmisel tekkis viga", "login_password_changed_success": "Parool edukalt uuendatud", "logout_all_device_confirmation": "Kas oled kindel, et soovid kÃĩigist seadmetest välja logida?", @@ -1332,6 +1457,7 @@ "maintenance_action_restore": "Andmebaasi taastamine", "maintenance_description": "Immich on hooldusreÅžiimis.", "maintenance_end": "LÃĩpeta hooldusreÅžiim", + "maintenance_end_error": "HooldusreÅžiimi lÃĩpetamine ebaÃĩnnestus.", "maintenance_logged_in_as": "Logitud sisse kasutajana {user}", "maintenance_restore_from_backup": "Taasta varukoopiast", "maintenance_restore_library": "Taasta oma kogu", @@ -1357,6 +1483,7 @@ "manage_media_access_settings": "Ava seaded", "manage_media_access_subtitle": "Luba Immich'i rakendusel multimeediafaile hallata ja liigutada.", "manage_media_access_title": "Üksuste haldamise ligipääs", + "manage_shared_links": "Halda jagatud linke", "manage_sharing_with_partners": "Halda partneritega jagamist", "manage_the_app_settings": "Halda rakenduse seadeid", "manage_your_account": "Halda oma kontot", @@ -1364,11 +1491,13 @@ "manage_your_devices": "Halda oma autenditud seadmeid", "manage_your_oauth_connection": "Halda oma OAuth Ãŧhendust", "map": "Kaart", + "map_assets_in_bounds": "{count, plural, =0 {Selles piirkonnas fotosid pole} one {# foto} other {# fotot}}", "map_cannot_get_user_location": "Ei saa kasutaja asukohta tuvastada", + "map_location_dialog_yes": "Jah", "map_location_picker_page_use_location": "Kasuta seda asukohta", "map_location_service_disabled_content": "Praeguse asukoha Ãŧksuste kuvamiseks tuleb lubada asukoha teenus. Kas soovid seda praegu lubada?", "map_location_service_disabled_title": "Asukoha teenus keelatud", - "map_marker_for_image": "Kaardimarker pildile, mis on tehtud kohas {city}, {country}", + "map_marker_for_images": "Kaardimarker kohas {city}, {country} tehtud piltide jaoks", "map_marker_with_image": "Kaardimarker pildiga", "map_no_location_permission_content": "Praeguse asukoha Ãŧksuste kuvamiseks on vaja asukoha luba. Kas soovid seda praegu lubada?", "map_no_location_permission_title": "Asukoha luba keelatud", @@ -1378,43 +1507,17 @@ "map_settings_date_range_option_days": "Viimased {days} päeva", "map_settings_date_range_option_year": "Viimane aasta", "map_settings_date_range_option_years": "Viimased {years} aastat", + "map_settings_dialog_title": "Kaardi seaded", "map_settings_include_show_archived": "Kaasa arhiveeritud", "map_settings_include_show_partners": "Kaasa partnerid", "map_settings_only_show_favorites": "Kuva ainult lemmikud", "map_settings_theme_settings": "Kaardi teema", + "map_zoom_to_see_photos": "Fotode nägemiseks suumi välja", "mark_all_as_read": "Märgi kÃĩik loetuks", + "mark_as_read": "Märgi loetuks", "marked_all_as_read": "KÃĩik märgiti loetuks", "matches": "Ühtivad failid", "matching_assets": "Ühtivad Ãŧksused", - "media_chrome": { - "auto": "Automaatne", - "captions": "Subtiitrid", - "decode_error": "Dekodeerimise viga", - "disable_captions": "Keela subtiitrid", - "enable_captions": "Luba subtiitrid", - "enter_fullscreen_mode": "Mine täisekraanvaatesse", - "exit_fullscreen_mode": "Lahku täisekraanvaatest", - "loop": "Taasesita", - "media_error_description": "Üksuse viga pÃĩhjustas taasesituse katkestamise. Üksus vÃĩib olla rikutud vÃĩi su brauser ei toeta seda vormingut.", - "media_loading": "Ãŧksuse laadimine", - "mute": "Vaigista", - "network_error": "VÃĩrgu tÃĩrge", - "network_error_description": "VÃĩrguÃŧhenduse tÃĩrge pÃĩhjustas Ãŧksuse allalaadimise ebaÃĩnnestumise.", - "not_supported_error": "Allikas pole toetatud", - "playback_rate": "Taasesituse kiirus", - "playback_rate_current": "praegune taasesituse kiirus", - "playback_rate_value": "Taasesituse kiirus {playbackRate}", - "playback_time": "taasesituse aeg", - "quality": "Kvaliteet", - "second": "sekund", - "seconds": "sekundit", - "time_value_of_total_time": "{currentTime} / {totalTime}", - "time_value_remaining": "{time} jäänud", - "unsupported_error_description": "Esines tundmatu viga. Vea pÃĩhjustas kas serveri vÃĩi vÃĩrguÃŧhenduse tÃĩrge, vÃĩi su brauser ei toeta seda vormingut.", - "video_not_loaded_unknown_time": "video pole laaditud, aeg teadmata.", - "video_player": "videomängija", - "volume": "helitugevus" - }, "media_type": "Üksuse tÃŧÃŧp", "memories": "Mälestused", "memories_all_caught_up": "Ongi kÃĩik", @@ -1431,30 +1534,34 @@ "merge_people_prompt": "Kas soovid need isikud Ãŧhendada? Seda tegevust ei saa tagasi vÃĩtta.", "merge_people_successfully": "Isikud Ãŧhendatud", "merged_people_count": "Ühendatud {count, plural, one {# isik} other {# isikut}}", - "minFaces": "Minimaalne nägude arv", - "minFaces_description": "Minimaalne tuvastatud nägude arv isiku kuvamiseks", "minimize": "Minimeeri", "minute": "Minut", "minutes": "Minutit", + "mirror_horizontal": "Horisontaalne", + "mirror_vertical": "Vertikaalne", "missing": "Puuduvad", "mobile_app": "Mobiilirakendus", "mobile_app_download_onboarding_note": "Mobiilirakenduse allalaadimiseks kasuta järgnevaid valikuid", "model": "Mudel", - "modify_date": "Muutmisaeg", "month": "Kuu", + "monthly_title_text_date_format": "MMMM y", "more": "Rohkem", "move": "Liiguta", + "move_down": "Liiguta alla", "move_off_locked_folder": "Liiguta lukustatud kaustast välja", "move_to": "Liiguta", "move_to_device_trash": "Liiguta seadme prÃŧgikasti", "move_to_lock_folder_action_prompt": "{count} lisatud lukustatud kausta", "move_to_locked_folder": "Liiguta lukustatud kausta", "move_to_locked_folder_confirmation": "Need fotod ja videod eemaldatakse kÃĩigist albumitest ning nad on nähtavad ainult lukustatud kaustas", + "move_up": "Liiguta Ãŧles", + "moved_to_archive": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} liigutatud arhiivi", + "moved_to_library": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} liigutatud kogusse", "moved_to_trash": "Liigutatud prÃŧgikasti", + "multiselect_grid_edit_date_time_err_read_only": "Kirjutuskaitsega Ãŧksus(t)e kuupäeva ei saa muuta, jäetakse vahele", + "multiselect_grid_edit_gps_err_read_only": "Kirjutuskaitsega Ãŧksus(t)e asukohta ei saa muuta, jäetakse vahele", "mute_memories": "Vaigista mälestused", "my_albums": "Minu albumid", - "my_immich_description": "Kopeeri praegune leht kui Minu Immich link", - "my_immich_title": "Minu Immich link", "name": "Nimi", "name_or_nickname": "Nimi vÃĩi hÃŧÃŧdnimi", "name_required": "Nimi on nÃĩutud", @@ -1469,11 +1576,12 @@ "never": "Mitte kunagi", "new_album": "Uus album", "new_api_key": "Uus API vÃĩti", - "new_feature": "Uus funktsionaalsus", + "new_date_range": "Uus kuupäevavahemik", "new_password": "Uus parool", "new_person": "Uus isik", "new_pin_code": "Uus PIN-kood", "new_pin_code_subtitle": "See on sul esimene kord lukustatud kausta kasutada. Turvaliseks ligipääsuks loo PIN-kood", + "new_timeline": "Uus ajajoon", "new_update": "Uus versioon", "new_user_created": "Uus kasutaja lisatud", "new_version_available": "UUS VERSIOON SAADAVAL", @@ -1481,6 +1589,7 @@ "next": "Järgmine", "next_memory": "Järgmine mälestus", "no": "Ei", + "no_actions_added": "Ühtegi tegevust pole veel lisatud", "no_albums_found": "Albumeid ei leitud", "no_albums_message": "Lisa album fotode ja videote organiseerimiseks", "no_albums_with_name_yet": "Paistab, et sul pole veel Ãŧhtegi selle nimega albumit.", @@ -1491,13 +1600,16 @@ "no_cast_devices_found": "Edastamise seadmeid ei leitud", "no_checksum_local": "Kontrollsumma pole saadaval - lokaalse Ãŧksuse pärimine ebaÃĩnnestus", "no_checksum_remote": "Kontrollsumma pole saadaval - kaugÃŧksuse pärimine ebaÃĩnnestus", + "no_configuration_needed": "Seadistus pole vajalik", "no_devices": "Autoriseeritud seadmeid pole", "no_duplicates_found": "Ühtegi duplikaati ei leitud.", "no_exif_info_available": "Exif info pole saadaval", "no_explore_results_message": "Oma kogu avastamiseks laadi Ãŧles rohkem fotosid.", "no_favorites_message": "Lisa lemmikud, et oma parimaid fotosid ja videosid kiiresti leida", + "no_filters_added": "Ühtegi filtrit pole veel lisatud", "no_libraries_message": "Lisa väline kogu oma fotode ja videote vaatamiseks", "no_local_assets_found": "Selle kontrollsummaga lokaalseid Ãŧksuseid ei leitud", + "no_location_set": "Asukoht pole määratud", "no_locked_photos_message": "Lukustatud kaustas olevad fotod ja videod on peidetud ning need pole kogu sirvimisel ja otsimisel nähtavad.", "no_name": "Nimetu", "no_notifications": "Teavitusi pole", @@ -1507,7 +1619,7 @@ "no_results": "Vasteid pole", "no_results_description": "Proovi sÃŧnonÃŧÃŧmi vÃĩi Ãŧldisemat märksÃĩna", "no_shared_albums_message": "Lisa album, et fotosid ja videosid teistega jagada", - "no_steps": "Ühtegi sammu pole lisatud", + "no_uploads_in_progress": "Üleslaadimisi käimas ei ole", "none": "Puudub", "not_allowed": "Keelatud", "not_available": "Pole saadaval", @@ -1515,10 +1627,6 @@ "not_selected": "Ei ole valitud", "notes": "Märkused", "nothing_here_yet": "Siin pole veel midagi", - "notification_backup_reliability": "Taustal varunduse tÃļÃļkindluse parandamiseks luba teavitused", - "notification_enabled_list_tile_content": "Immich kasutab taustal varunduse jaoks teavitusi. Halda neid oma seadme seadetes.", - "notification_enabled_list_tile_open_button": "Ava seaded", - "notification_enabled_list_tile_title": "Teavitused lubatud", "notification_permission_dialog_content": "Teavituste lubamiseks mine Seadetesse ja vali lubamine.", "notification_permission_list_tile_content": "Anna luba teavituste saatmiseks.", "notification_permission_list_tile_enable_button": "Luba teavitused", @@ -1530,8 +1638,6 @@ "obtainium_configurator": "Obtainiumi seadistamine", "obtainium_configurator_instructions": "Androidi rakenduse otse GitHub'ist paigaldamiseks ja uuendamiseks kasuta Obtainiumit. Seadistamise lingi loomiseks lisa API vÃĩti ja vali rakenduse variant", "ocr": "OCR", - "ocr_body": "Immich loeb nÃŧÃŧd teksti sinu fotodel, nii et saad selle järgi neid otsida.", - "ocr_title": "Otsi teksti oma fotodel", "official_immich_resources": "Ametlikud Immich'i ressursid", "offline": "Ühendus puudub", "offset": "Nihe", @@ -1550,8 +1656,6 @@ "open": "Ava", "open_calendar": "Ava kalender", "open_in_browser": "Ava brauseris", - "open_in_immich_body": "Sea Immich oma Androidi galeriirakenduseks, et fotosid otse teistest rakendustest avada.", - "open_in_immich_title": "Ava fotod Immich'is", "open_in_map_view": "Ava kaardi vaates", "open_in_openstreetmap": "Ava OpenStreetMap", "open_the_search_filters": "Ava otsingufiltrid", @@ -1560,10 +1664,10 @@ "organize_into_albums": "Organiseeri albumitesse", "organize_into_albums_description": "Pane olemasolevad fotod albumitesse, kasutades jooksvaid sÃŧnkroonimise seadeid", "organize_your_library": "Korrasta oma kogu", - "orientation": "Orientatsioon", - "original": "algne", + "original": "originaal", "other": "Muud", "other_devices": "Muud seadmed", + "other_entities": "Muud objektid", "other_variables": "Muud muutujad", "owned": "Minu omad", "owner": "Omanik", @@ -1573,9 +1677,12 @@ "partner_can_access_assets": "KÃĩik su fotod ja videod, välja arvatud arhiveeritud ja kustutatud", "partner_can_access_location": "Asukohad, kus su fotod tehti", "partner_list_user_photos": "Kasutaja {user} fotod", + "partner_list_view_all": "Vaata kÃĩiki", "partner_page_empty_message": "Su fotod pole veel Ãŧhegi partneriga jagatud.", "partner_page_no_more_users": "Pole rohkem kasutajaid, keda lisada", + "partner_page_partner_add_failed": "Partneri lisamine ebaÃĩnnestus", "partner_page_select_partner": "Vali partner", + "partner_page_shared_to_title": "Jagatud", "partner_page_stop_sharing_content": "{partner} ei pääse rohkem su fotodele ligi.", "partner_sharing": "Partneriga jagamine", "partners": "Partnerid", @@ -1597,6 +1704,8 @@ "people": "Isikud", "people_edits_count": "{count, plural, one {# isik} other {# isikut}} muudetud", "people_feature_description": "Fotode ja videote sirvimine inimeste kaupa grupeeritult", + "people_selected": "{count, plural, one {# isik valitud} other {# isikut valitud}}", + "people_sidebar_description": "Kuva kÃŧlgmenÃŧÃŧs Isikute link", "permanent_deletion_warning": "Jäädavalt kustutamise hoiatus", "permanent_deletion_warning_setting_description": "Kuva hoiatust Ãŧksuste jäädaval kustutamisel", "permanently_delete": "Kustuta jäädavalt", @@ -1606,17 +1715,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} jäädavalt kustutatud", "permission": "Luba", "permission_empty": "Luba ei tohiks olla tÃŧhi", + "permission_onboarding_back": "Tagasi", + "permission_onboarding_continue_anyway": "Jätka sellegipoolest", + "permission_onboarding_get_started": "Alusta", + "permission_onboarding_go_to_settings": "Mine seadetesse", + "permission_onboarding_permission_denied": "Luba keelatud. Immich'i kasutamiseks anna Seadetes fotode ja videote load.", + "permission_onboarding_permission_granted": "Luba antud! Oled valmis.", + "permission_onboarding_permission_limited": "Piiratud luba. Et Immich saaks tervet su galeriid varundada ja hallata, anna Seadetes luba fotodele ja videotele.", + "permission_onboarding_request": "Immich'il on vaja luba su fotode ja videote vaatamiseks.", "person": "Isik", "person_age_months": "{months, plural, one {# kuu} other {# kuud}} vana", "person_age_year_months": "1 aasta {months, plural, one {# kuu} other {# kuud}} vana", - "person_age_years": "{years, plural, one {# aasta} other {# aastat}} vana", + "person_age_years": "{years, plural, other {# aastat}} vana", "person_birthdate": "SÃŧndinud {date}", "person_hidden": "{name}{hidden, select, true { (peidetud)} other {}}", "person_recognized": "Isik tuvastatud", + "person_selected": "Isik valitud", "photo_shared_all_users": "Paistab, et oled oma fotosid kÃĩigi kasutajatega jaganud, vÃĩi pole Ãŧhtegi kasutajat, kellega jagada.", "photos": "Fotod", "photos_and_videos": "Fotod ja videod", + "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} fotot}}", "photos_from_previous_years": "Fotod varasematest aastatest", + "photos_only": "Ainult fotod", "pick_a_location": "Vali asukoht", "pick_custom_range": "Kohandatud vahemik", "pick_date_range": "Vali kuupäevavahemik", @@ -1635,8 +1755,6 @@ "play_original_video_setting_description": "Eelista transkodeeritud video asemel algse video taasesitamist. Kui algne Ãŧksus ei ole Ãŧhilduv, vÃĩib taasesitamine ebaÃĩnnestuda.", "play_transcoded_video": "Taasesita transkodeeritud video", "please_auth_to_access": "Ligipääsemiseks palun autendi", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "See meetod suudab sÃŧndmuseid filtreerida ja tingimuslikult vältida järgnevate sammude käivitamist", "port": "Port", "preferences_settings_subtitle": "Halda rakenduse eelistusi", "preferences_settings_title": "Eelistused", @@ -1653,6 +1771,7 @@ "privacy": "Privaatsus", "profile": "Profiil", "profile_drawer_app_logs": "Logid", + "profile_drawer_client_server_up_to_date": "Klient ja server on uuendatud", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "KirjutuskaitsereÅžiim sisse lÃŧlitatud. Väljumiseks puuduta pikalt avatari ikooni.", "profile_image_of_user": "Kasutaja {user} profiilipilt", @@ -1675,6 +1794,7 @@ "purchase_individual_description_2": "Toetaja staatus", "purchase_individual_title": "Individuaalne", "purchase_input_suggestion": "Sul on juba tootevÃĩti? Sisesta see allpool", + "purchase_license_subtitle": "Osta Immich, et toetada selle jätkuvat arendust", "purchase_lifetime_description": "Eluaegne ost", "purchase_option_title": "OSTMISE VALIKUD", "purchase_panel_info_1": "Immich'i arendamine nÃĩuab palju aega ja vaeva ning meie täiskohaga insenerid tÃļÃļtavad selle nimel, et teha see nii heaks kui vähegi vÃĩimalik. Meie missiooniks on muuta avatud lähtekoodiga tarkvara ja eetilised äritavad arendajatele jätkusuutlikuks sissetulekuallikaks ning luua privaatsust austav ÃļkosÃŧsteem, mis pakub tÃĩelisi alternatiive ekspluatatiivsetele pilveteenustele.", @@ -1707,18 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} seostatud uue isikuga", "reassing_hint": "Seosta valitud Ãŧksused olemasoleva isikuga", "recent": "Hiljutine", + "recent_albums": "Hiljutised albumid", "recent_searches": "Hiljutised otsingud", "recently_added": "Hiljuti lisatud", - "recently_added_description": "Sirvi oma Ãŧksuseid sorteerituna Immich'isse Ãŧleslaadimise järgi", "recently_added_page_title": "Hiljuti lisatud", - "recently_added_title": "Hiljuti lisatud", "recently_taken": "Hiljuti tehtud", + "recently_taken_page_title": "Hiljuti tehtud", "refresh": "Värskenda", "refresh_encoded_videos": "Värskenda kodeeritud videod", "refresh_faces": "Värskenda näod", "refresh_metadata": "Värskenda metaandmed", "refresh_thumbnails": "Värskenda pisipildid", "refreshed": "Värskendatud", + "refreshes_every_file": "Loeb kÃĩik olemasolevad ja uued failid uuesti", "refreshing_encoded_video": "Kodeeritud videote värskendamine", "refreshing_faces": "Nägude värskendamine", "refreshing_metadata": "Metaandmete värskendamine", @@ -1731,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Kas oled kindel, et soovid eemaldada {count, plural, one {# Ãŧksuse} other {# Ãŧksust}} sellelt jagatud lingilt?", "remove_assets_title": "Eemalda Ãŧksused?", "remove_custom_date_range": "Eemalda kohandatud kuupäevavahemik", - "remove_filter": "Eemalda filter", + "remove_deleted_assets": "Eemalda kustutatud Ãŧksused", "remove_from_album": "Eemalda albumist", "remove_from_album_action_prompt": "{count} eemaldatud albumist", "remove_from_favorites": "Eemalda lemmikutest", @@ -1742,22 +1863,29 @@ "remove_memory": "Eemalda mälestus", "remove_photo_from_memory": "Eemalda foto sellest mälestusest", "remove_tag": "Eemalda silt", + "remove_url": "Eemalda URL", "remove_user": "Eemalda kasutaja", "removed_api_key": "API vÃĩti eemaldatud: {name}", "removed_from_archive": "Arhiivist eemaldatud", "removed_from_favorites": "Lemmikutest eemaldatud", "removed_from_favorites_count": "{count, plural, other {# eemaldatud}} lemmikutest", "removed_memory": "Mäletus eemaldatud", + "removed_photo_from_memory": "Foto mälestustest eemaldatud", "removed_tagged_assets": "Silt eemaldatud {count, plural, one {# Ãŧksuselt} other {# Ãŧksuselt}}", "rename": "Nimeta Ãŧmber", + "repair": "Parandus", + "repair_no_results_message": "Mittejälgitavad ja puuduvad failid kuvatakse siin", + "replace_with_upload": "Asenda Ãŧleslaadimisega", "repository": "Koodihoidla", "require_password": "NÃĩua parooli", + "require_user_to_change_password_on_first_login": "NÃĩua kasutajalt esmakordsel sisenemisel parooli muutmist", "rescan": "Skaneeri uuesti", "reset": "Lähtesta", "reset_password": "Lähtesta parool", "reset_people_visibility": "Lähtesta isikute nähtavus", "reset_pin_code": "Lähtesta PIN-kood", "reset_pin_code_description": "Kui unustasid oma PIN-koodi, vÃĩta selle lähtestamiseks Ãŧhendust serveri administraatoriga", + "reset_pin_code_success": "PIN-kood edukalt lähtestatud", "reset_pin_code_with_password": "Saad oma PIN-koodi alati oma parooli abil lähtestada", "reset_sqlite": "Lähtesta SQLite andmebaas", "reset_sqlite_clear_app_data": "Kustuta andmed", @@ -1771,10 +1899,12 @@ "resolved_all_duplicates": "KÃĩik duplikaadid lahendatud", "restore": "Taasta", "restore_all": "Taasta kÃĩik", + "restore_trash_action_prompt": "{count} prÃŧgikastust taastatud", "restore_user": "Taasta kasutaja", "restored_asset": "Üksus taastatud", "resume": "Jätka", "resume_paused_jobs": "Jätka {count, plural, one {# peatatud tÃļÃļde} other {# peatatud tÃļÃļdet}}", + "retry_upload": "Proovi Ãŧleslaadimist uuesti", "review_duplicates": "Vaata duplikaadid läbi", "review_large_files": "Vaata suured failid läbi", "role": "Roll", @@ -1782,6 +1912,7 @@ "role_viewer": "Vaataja", "running": "Käimas", "save": "Salvesta", + "save_to_gallery": "Salvesta galeriisse", "saved": "Salvestatud", "saved_api_key": "API vÃĩti salvestatud", "saved_profile": "Profiil salvestatud", @@ -1792,10 +1923,9 @@ "scan": "Otsi", "scan_all_libraries": "Skaneeri kÃĩik kogud", "scan_library": "Skaneeri", + "scan_settings": "Skaneerimise seaded", "scanning": "Otsimine", "scanning_for_album": "Albumi skaneerimine...", - "screencast_mode_description": "Kuva ekraanil klaviatuuri- ja hiiresÃŧndmuste indikaatoreid", - "screencast_mode_title": "LÃŧlita kuvalevi reÅžiim", "search": "Otsi", "search_albums": "Otsi albumeid", "search_by_context": "Otsi konteksti alusel", @@ -1803,8 +1933,6 @@ "search_by_description_example": "Matkapäev Sapas", "search_by_filename": "Otsi failinime vÃĩi -laiendi järgi", "search_by_filename_example": "st. IMG_1234.JPG vÃĩi PNG", - "search_by_full_path": "Otsi täieliku tee vÃĩi kausta alusel", - "search_by_full_path_example": "/Juhan/Projektid/3D_printimine/2026-07-01 - saad otsida märksÃĩnu Projektid, 3D, printimine, 2026 jne.", "search_by_ocr": "Otsi OCR-i abil", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Otsi läätse mudelit...", @@ -1824,6 +1952,7 @@ "search_filter_location_title": "Vali asukoht", "search_filter_media_type": "Üksuse tÃŧÃŧp", "search_filter_media_type_title": "Vali Ãŧksuse tÃŧÃŧp", + "search_filter_ocr": "Otsi OCR-i abil", "search_filter_people_title": "Vali isikud", "search_filter_star_rating": "Hinnang", "search_filter_tags_title": "Vali sildid", @@ -1834,13 +1963,25 @@ "search_no_people_named": "Ei ole isikuid nimega \"{name}\"", "search_no_result": "Vasteid ei leitud, proovi muud otsinguterminit vÃĩi kombinatsiooni", "search_options": "Otsingu valikud", + "search_page_categories": "Kategooriad", + "search_page_motion_photos": "Liikuvad fotod", + "search_page_no_objects": "Objektide info pole saadaval", + "search_page_no_places": "Kohtade info pole saadaval", + "search_page_screenshots": "Ekraanipildid", "search_page_search_photos_videos": "Otsi oma fotosid ja videosid", + "search_page_selfies": "Selfid", + "search_page_things": "Asjad", "search_page_view_all_button": "Vaata kÃĩiki", + "search_page_your_activity": "Sinu aktiivsus", + "search_page_your_map": "Sinu kaart", "search_people": "Otsi inimesi", "search_places": "Otsi kohti", "search_rating": "Otsi hinnangu järgi...", + "search_result_page_new_search_hint": "Uus otsing", "search_settings": "Otsi seadeid", "search_state": "Otsi osariiki...", + "search_suggestion_list_smart_search_hint_1": "Nutiotsing on vaikimisi lubatud, metaandmete otsimiseks kasuta sÃŧntaksit ", + "search_suggestion_list_smart_search_hint_2": "m:sinu-otsingu-termin", "search_tags": "Otsi silte...", "search_timezone": "Otsi ajavÃļÃļndit...", "search_type": "Otsingu tÃŧÃŧp", @@ -1854,6 +1995,7 @@ "select_albums": "Vali albumid", "select_all": "Vali kÃĩik", "select_all_duplicates": "Vali kÃĩik duplikaadid", + "select_all_in": "Vali kÃĩik grupis {group}", "select_avatar_color": "Vali avatari värv", "select_count": "{count, plural, one {Vali #} other {Vali #}}", "select_cutoff_date": "Vali kuupäev", @@ -1861,13 +2003,14 @@ "select_featured_photo": "Vali esiletÃĩstetud foto", "select_from_computer": "Vali arvutist", "select_keep_all": "Vali jäta kÃĩik alles", + "select_library_owner": "Vali kogu omanik", "select_new_face": "Vali uus nägu", "select_people": "Vali isikud", "select_person": "Vali isik", "select_person_to_tag": "Vali sildistamiseks isik", "select_photos": "Vali fotod", - "select_quality": "Vali kvaliteet", "select_trash_all": "Vali kÃĩik prÃŧgikasti", + "select_user_for_sharing_page_err_album": "Albumi lisamine ebaÃĩnnestus", "selected": "Valitud", "selected_count": "{count, plural, other {# valitud}}", "selected_gps_coordinates": "Valitud GPS-koordinaadid", @@ -1895,36 +2038,47 @@ "setting_image_navigation_enable_subtitle": "Kui lubatud, saad liikuda eelmise/järgmise pildi juurde ekraani vasakut/paremat serva puudutades.", "setting_image_navigation_enable_title": "Puudutusega navigeerimine", "setting_image_navigation_title": "Piltide navigeerimine", - "setting_image_viewer_help": "Detailivaatur laadib kÃĩigepealt väikese pisipildi, seejärel keskmises mÃĩÃĩdus eelvaate (kui lubatud) ja lÃĩpuks algse pildi (kui lubatud).", + "setting_image_viewer_help": "Detailivaatur laadib kÃĩigepealt väikese pisipildi, seejärel keskmises mÃĩÃĩdus eelvaate (kui lubatud) ja lÃĩpuks originaalpildi (kui lubatud).", "setting_image_viewer_original_subtitle": "LÃŧlita sisse, et laadida algne täisresolutsiooniga pilt (suur!). LÃŧlita välja, et vähendada andmekasutust (nii vÃĩrgu kui seadme puhvri).", "setting_image_viewer_original_title": "Laadi algne pilt", - "setting_image_viewer_preview_subtitle": "Luba keskmise resolutsiooniga pildi laadimine. Keela, et laadida kohe algne pilt vÃĩi kasutada ainult pisipilti.", + "setting_image_viewer_preview_subtitle": "Luba keskmise resolutsiooniga pildi laadimine. Keela, et laadida kohe originaalpilt vÃĩi kasutada ainult pisipilti.", "setting_image_viewer_preview_title": "Laadi pildi eelvaade", + "setting_image_viewer_title": "Pildid", "setting_languages_apply": "Rakenda", "setting_languages_subtitle": "Muuda rakenduse keelt", + "setting_notifications_notify_failures_grace_period": "Teavita taustal varundamise vigadest: {duration}", + "setting_notifications_notify_hours": "{count} tundi", + "setting_notifications_notify_immediately": "kohe", "setting_notifications_notify_minutes": "{count} minutit", + "setting_notifications_notify_never": "mitte kunagi", "setting_notifications_notify_seconds": "{count} sekundit", + "setting_notifications_single_progress_subtitle": "Detailne Ãŧleslaadimise edenemise info Ãŧksuse kohta", + "setting_notifications_single_progress_title": "Kuva taustal varundamise detailset edenemist", "setting_notifications_subtitle": "Halda oma teavituste eelistusi", + "setting_notifications_total_progress_subtitle": "Üldine Ãŧleslaadimise edenemine (Ãŧksuseid tehtud/kokku)", + "setting_notifications_total_progress_title": "Kuva taustal varundamise Ãŧldist edenemist", "setting_video_viewer_auto_play_subtitle": "Alusta videote avamisel automaatselt taasesitust", "setting_video_viewer_auto_play_title": "Esita videod automaatselt", "setting_video_viewer_looping_title": "Taasesitus", - "setting_video_viewer_original_video_subtitle": "Esita serverist video voogedastamisel algne video, isegi kui transkodeeritud video on saadaval. VÃĩib pÃĩhjustada puhverdamist. Lokaalselt saadaolevad videod mängitakse originaalkvaliteediga sÃĩltumata sellest seadest.", - "setting_video_viewer_original_video_title": "Sunni algne video", + "setting_video_viewer_original_video_subtitle": "Esita serverist video voogedastamisel originaal, isegi kui transkodeeritud video on saadaval. VÃĩib pÃĩhjustada puhverdamist. Lokaalselt saadaolevad videod mängitakse originaalkvaliteediga sÃĩltumata sellest seadest.", + "setting_video_viewer_original_video_title": "Sunni originaalvideo", "settings": "Seaded", + "settings_require_restart": "Selle seade rakendamiseks palun taaskäivita Immich", "settings_saved": "Seaded salvestatud", "setup_pin_code": "Seadista PIN-kood", "share": "Jaga", + "share_action_prompt": "Jagatud {count} Ãŧksust", + "share_add_photos": "Lisa fotosid", + "share_assets_selected": "{count} valitud", "share_dialog_preparing": "Ettevalmistamine...", "share_link": "Jaga linki", - "share_original": "Kasuta algset (suur)", - "share_preview": "Kasuta pisipilti (väike)", - "share_quality_body": "Vajuta ja hoia jagamise nuppu, et valida enne jagamist pildifaili kvaliteet.", - "share_quality_title": "Vali jagamise kvaliteet", "shared": "Jagatud", "shared_album_activities_input_disable": "Kommentaarid on keelatud", "shared_album_activity_remove_content": "Kas soovid selle tegevuse kustutada?", "shared_album_activity_remove_title": "Kustuta tegevus", "shared_album_section_people_action_error": "Viga albumist eemaldamisel/lahkumisel", + "shared_album_section_people_action_leave": "Eemalda kasutaja albumist", + "shared_album_section_people_action_remove_user": "Eemalda kasutaja albumist", "shared_album_section_people_title": "ISIKUD", "shared_by": "Jagas", "shared_by_user": "Jagas {user}", @@ -1933,6 +2087,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Ãŧles laaditud", "shared_link_app_bar_title": "Jagatud lingid", "shared_link_clipboard_copied_massage": "Kopeeritud lÃĩikelauale", + "shared_link_clipboard_text": "Link: {link}\nParool: {password}", "shared_link_create_error": "Viga jagatud lingi loomisel", "shared_link_custom_url_description": "Ligipääs jagatud lingile kohandatud URL-i kaudu", "shared_link_edit_description_hint": "Sisesta jagatud lingi kirjeldus", @@ -1968,6 +2123,12 @@ "shared_with_partner": "Jagatud partneriga {partner}", "sharing": "Jagamine", "sharing_enter_password": "Palun sisesta selle lehe vaatamiseks salasÃĩna.", + "sharing_page_album": "Jagatud albumid", + "sharing_page_description": "Loo jagatud albumeid, et jagada fotosid ja videosid inimestega oma vÃĩrgustikus.", + "sharing_page_empty_list": "TÜHI LOEND", + "sharing_sidebar_description": "Kuva kÃŧlgmenÃŧÃŧs Jagamise linki", + "sharing_silver_appbar_create_shared_album": "Uus jagatud album", + "sharing_silver_appbar_share_partner": "Jaga partneriga", "shift_to_permanent_delete": "vajuta ⇧, et Ãŧksus jäädavalt kustutada", "show_album_options": "Näita albumi valikuid", "show_albums": "Näita albumeid", @@ -1979,9 +2140,7 @@ "show_in_timeline": "Näita ajajoonel", "show_in_timeline_setting_description": "Kuva oma ajajoonel selle kasutaja fotosid ja videosid", "show_keyboard_shortcuts": "Kuva kiirklahvid", - "show_less": "Kuva vähem", "show_metadata": "Kuva metaandmed", - "show_more_fields": "{count, plural, one {Kuva veel # väli} other {Kuva veel # välja}}", "show_or_hide_info": "Kuva vÃĩi peida info", "show_password": "Kuva parooli", "show_person_options": "Näita isiku valikuid", @@ -1989,7 +2148,6 @@ "show_schema": "Kuva skeem", "show_search_options": "Kuva otsingu valikud", "show_shared_links": "Näita jagatud linke", - "show_slideshow_metadata_overlay": "Kuva pildi info Ãŧlekatet", "show_slideshow_transition": "Kuva slaidiesitluse Ãŧleminekud", "show_supporter_badge": "Toetaja märk", "show_supporter_badge_description": "Kuva toetaja märki", @@ -2001,24 +2159,18 @@ "sign_out": "Logi välja", "sign_up": "Registreeru", "size": "Suurus", - "skip": "Jäta vahele", "skip_to_content": "Sisu juurde", "skip_to_folders": "Kaustade juurde", "skip_to_tags": "Siltide juurde", "slideshow": "Slaidiesitlus", - "slideshow_metadata_overlay_mode": "Ülekatte sisu", - "slideshow_metadata_overlay_mode_description_only": "Ainult kirjeldus", - "slideshow_metadata_overlay_mode_full": "Täielik", "slideshow_repeat": "Korda slaidiesitlust", "slideshow_repeat_description": "Mine slaidiesitluse lÃĩppedes tagasi algusesse", "slideshow_settings": "Slaidiesitluse seaded", - "slideshow_title": "Slaidiesitlus", - "smart_album": "Nutikas album", - "some_assets_already_have_a_location_warning": "Osadel valitud Ãŧksustest on juba asukoht määratud", "sort_albums_by": "Järjesta albumid...", "sort_created": "Loomise aeg", "sort_items": "Üksuste arv", "sort_modified": "Muutmise aeg", + "sort_newest": "Uusim foto", "sort_oldest": "Vanim foto", "sort_people_by_similarity": "Sorteeri isikud sarnasuse järgi", "sort_recent": "Uusim foto", @@ -2027,6 +2179,7 @@ "stack": "Virnasta", "stack_action_prompt": "{count} virnastatud", "stack_duplicates": "Virnasta duplikaadid", + "stack_select_one_photo": "Vali virnale kaanefoto", "stack_selected_photos": "Virnasta valitud fotod", "stacked_assets_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} virnastatud", "stacktrace": "Pinujälg", @@ -2035,10 +2188,6 @@ "start_date_before_end_date": "Alguskuupäev peab olema varasem kui lÃĩppkuupäev", "state": "Osariik", "status": "Staatus", - "step_delete": "Kustuta samm", - "step_delete_confirm": "Kas oled kindel, et soovid selle sammu kustutada?", - "step_details": "Sammu Ãŧksikasjad", - "steps": "Sammud", "stop_casting": "LÃĩpeta edastamine", "stop_motion_photo": "Peata liikuv foto", "stop_photo_sharing": "LÃĩpeta oma fotode jagamine?", @@ -2059,18 +2208,18 @@ "swap_merge_direction": "Muuda Ãŧhendamise suunda", "sync": "SÃŧnkrooni", "sync_albums": "SÃŧnkrooni albumid", + "sync_albums_manual_subtitle": "SÃŧnkrooni kÃĩik Ãŧleslaaditud videod ja fotod valitud varundusalbumitesse", "sync_local": "SÃŧnkrooni lokaalsed Ãŧksused", "sync_remote": "SÃŧnkrooni kaugÃŧksused", "sync_status": "SÃŧnkroonimise staatus", "sync_status_subtitle": "Vaata ja halda sÃŧnkroonimissÃŧsteemi", "sync_upload_album_setting_subtitle": "Loo ja laadi oma pildid ja videod Ãŧles Immich'isse valitud albumitesse", - "system_theme": "SÃŧsteemi teema", - "system_theme_command_description": "Kasuta sÃŧsteemi teemat ({value})", "tag": "Silt", "tag_assets": "Sildista Ãŧksuseid", "tag_created": "Lisatud silt: {tag}", "tag_face": "Sildista nägu", "tag_feature_description": "Fotode ja videote lehitsemine siltide kaupa grupeeritult", + "tag_not_found_question": "Ei leia silti? Lisa uus silt.", "tag_people": "Sildista inimesi", "tag_updated": "Muudetud silt: {tag}", "tagged_assets": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} sildistatud", @@ -2085,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Üksuste arv reas ({count})", "theme_setting_colorful_interface_subtitle": "Rakenda taustapindadele pÃĩhivärv.", "theme_setting_colorful_interface_title": "Värviline kasutajaliides", + "theme_setting_image_viewer_quality_subtitle": "Kohanda detailvaaturi kvaliteeti", + "theme_setting_image_viewer_quality_title": "Pildivaaturi kvaliteet", "theme_setting_primary_color_subtitle": "Vali värv pÃĩhitegevuste ja aktsentide jaoks.", "theme_setting_primary_color_title": "PÃĩhivärv", "theme_setting_system_primary_color_title": "Kasuta sÃŧsteemset värvi", "theme_setting_system_theme_switch": "Automaatne (järgi sÃŧsteemi seadet)", + "theme_setting_theme_subtitle": "Vali rakenduse teema seade", + "theme_setting_three_stage_loading_subtitle": "Kolmeastmeline laadimine vÃĩib parandada laadimise jÃĩudlust, aga pÃĩhjustab oluliselt suuremat vÃĩrgukoormust", + "theme_setting_three_stage_loading_title": "Luba kolmeastmeline laadimine", "then": "Siis", "they_will_be_merged_together": "Nad Ãŧhendatakse kokku", "third_party_resources": "Kolmanda osapoole ressursid", @@ -2114,17 +2268,23 @@ "trash_all": "KÃĩik prÃŧgikasti", "trash_count": "Liiguta {count, number} prÃŧgikasti", "trash_delete_asset": "Kustuta Ãŧksus", + "trash_emptied": "PrÃŧgikast tÃŧhjendatud", "trash_no_results_message": "Siia ilmuvad prÃŧgikasti liigutatud fotod ja videod.", "trash_page_delete_all": "Kustuta kÃĩik", + "trash_page_empty_trash_dialog_content": "Kas soovid prÃŧgikasti liigutatud Ãŧksused kustutada? Need eemaldatakse Immich'ist jäädavalt", "trash_page_info": "PrÃŧgikasti liigutatud Ãŧksused kustutatakse jäädavalt {days} päeva pärast", + "trash_page_no_assets": "PrÃŧgikastis Ãŧksuseid pole", + "trash_page_restore_all": "Taasta kÃĩik", + "trash_page_select_assets_btn": "Vali Ãŧksused", + "trash_page_title": "PrÃŧgikast ({count})", "trashed_items_will_be_permanently_deleted_after": "PrÃŧgikasti tÃĩstetud Ãŧksused kustutatakse jäädavalt {days, plural, one {# päeva} other {# päeva}} pärast.", "trigger": "Päästik", - "trigger_asset_metadata_extraction": "Üksuste metaandmete eraldamine", - "trigger_asset_metadata_extraction_description": "Käivitub, kui Ãŧksusest eraldatakse EXIF metaandmed", - "trigger_asset_uploaded": "Üksuse Ãŧleslaadimine", + "trigger_asset_uploaded": "Üksus Ãŧles laaditud", "trigger_asset_uploaded_description": "Käivitub uue Ãŧksuse Ãŧleslaadimisel", + "trigger_description": "SÃŧndmus, mis käivitab tÃļÃļvoo", "trigger_person_recognized": "Isik tuvastatud", "trigger_person_recognized_description": "Käivitub isiku tuvastamisel", + "trigger_type": "Päästiku tÃŧÃŧp", "troubleshoot": "TÃĩrkeotsing", "type": "TÃŧÃŧp", "unable_to_change_pin_code": "PIN-koodi muutmine ebaÃĩnnestus", @@ -2140,7 +2300,6 @@ "unknown": "Teadmata", "unknown_country": "Tundmatu riik", "unknown_date": "Tundmatu kuupäev", - "unknown_schema": "Tundmatu skeem", "unknown_year": "Teadmata aasta", "unlimited": "Piiramatu", "unlink_motion_video": "TÃŧhista liikuva video linkimine", @@ -2153,20 +2312,23 @@ "unsaved_change": "Salvestamata muudatus", "unselect_all": "Ära vali Ãŧhtegi", "unselect_all_duplicates": "Ära vali duplikaate", + "unselect_all_in": "Ära vali Ãŧhtegi grupis {group}", "unstack": "Eralda", "unstack_action_prompt": "{count} eraldatud", "unstacked_assets_count": "{count, plural, one {# Ãŧksus} other {# Ãŧksust}} eraldatud", "unsupported_field_type": "Mittetoetatud välja tÃŧÃŧp", "unsupported_file_type": "Faili {file} ei saa Ãŧles laadida, kuna selle tÃŧÃŧp {type} ei ole toetatud.", "untagged": "Sildistamata", + "untitled_workflow": "Pealkirjata tÃļÃļvoog", "up_next": "Järgmine", "update_location_action_prompt": "Uuenda {count} valitud Ãŧksuse asukoht:", "updated_at": "Uuendatud", "updated_password": "Parool muudetud", "upload": "Laadi Ãŧles", "upload_concurrency": "Üleslaadimise samaaegsus", - "upload_day_count": "{date}: {count, plural, one {# Ãŧleslaadimine} other {# Ãŧleslaadimist}}", "upload_details": "Üleslaadimise Ãŧksikasjad", + "upload_dialog_info": "Kas soovid valitud Ãŧksuse(d) serverisse varundada?", + "upload_dialog_title": "Üksuse Ãŧleslaadimine", "upload_error_with_count": "Viga {count, plural, one {# Ãŧksuse} other {# Ãŧksuse}} Ãŧleslaadimisel", "upload_errors": "Üleslaadimine lÃĩpetatud {count, plural, one {# veaga} other {# veaga}}, uute Ãŧksuste nägemiseks värskenda lehte.", "upload_finished": "Üleslaadimine lÃĩpetatud", @@ -2176,13 +2338,9 @@ "upload_status_errors": "Vead", "upload_status_uploaded": "Üleslaaditud", "upload_success": "Üleslaadimine Ãĩnnestus, uute Ãŧksuste nägemiseks värskenda lehte.", - "upload_to_album_body": "Kui sa ei kasuta käsitsi Ãŧleslaadimise funktsionaalsust, saad nÃŧÃŧd fotode Ãŧleslaadimisel need otse albumisse lisada, pole vaja kÃĩigepealt Ãŧles laadida ja seejärel albumisse lisada.", - "upload_to_album_title": "Laadi Ãŧles otse albumisse", "upload_to_immich": "Laadi Immich'isse ({count})", "uploading": "Üleslaadimine", "uploading_media": "Üksuste Ãŧleslaadimine", - "uploads": "Üleslaadimised", - "uploads_count": "{count, plural, one {# Ãŧleslaadimine} other {# Ãŧleslaadimist}}", "url": "URL", "usage": "Kasutus", "use_biometric": "Kasuta biomeetriat", @@ -2190,7 +2348,6 @@ "use_browser_locale_description": "Vorminda kuupäevad, kellaajad ja arvud vastavalt brauseri lokaadile", "use_current_connection": "Kasuta praegust Ãŧhendust", "use_custom_date_range": "Kasuta kohandatud kuupäevavahemikku", - "use_template": "Kasuta malli", "user": "Kasutaja", "user_has_been_deleted": "See kasutaja on kustutatud.", "user_id": "Kasutaja ID", @@ -2200,6 +2357,7 @@ "user_privacy": "Kasutaja privaatsus", "user_purchase_settings": "Ost", "user_purchase_settings_description": "Halda oma ostu", + "user_role_set": "Määra kasutajale {user} roll {role}", "user_usage_detail": "Kasutajate kasutusandmed", "user_usage_stats": "Konto kasutuse statistika", "user_usage_stats_description": "Vaata konto kasutuse statistikat", @@ -2209,6 +2367,7 @@ "utilities": "TÃļÃļriistad", "validate": "Valideeri", "validate_endpoint_error": "Sisesta korrektne URL", + "validation_error": "Valideerimise viga", "variables": "Muutujad", "version": "Versioon", "version_announcement_closing": "Sinu sÃĩber Alex", @@ -2218,9 +2377,9 @@ "video": "Video", "video_hover_setting": "Esita hÃĩljutamisel video eelvaade", "video_hover_setting_description": "Esita video eelvaade, kui hiirt selle kohal hÃĩljutada. Isegi kui keelatud, saab taasesituse alustada taasesitusnupu kohal hÃĩljutades.", - "video_quality": "Video kvaliteet", "videos": "Videod", "videos_count": "{count, plural, one {# video} other {# videot}}", + "videos_only": "Ainult videod", "view": "Vaata", "view_album": "Vaata albumit", "view_all": "Vaata kÃĩiki", @@ -2229,48 +2388,49 @@ "view_details": "Vaata Ãŧksikasju", "view_in_timeline": "Vaata ajajoonel", "view_link": "Vaata linki", + "view_links": "Vaata linke", "view_name": "Vaade", "view_next_asset": "Vaata järgmist Ãŧksust", "view_previous_asset": "Vaata eelmist Ãŧksust", "view_qr_code": "Vaata QR-koodi", "view_similar_photos": "Vaata sarnaseid fotosid", "view_stack": "Vaata virna", + "view_user": "Vaata kasutajat", "viewer_remove_from_stack": "Eemalda virnast", + "viewer_stack_use_as_main_asset": "Kasuta peamise Ãŧksusena", + "viewer_unstack": "Eralda", "visibility": "Nähtavus", "visibility_changed": "{count, plural, one {# isiku} other {# isiku}} nähtavus muudetud", "visual": "Visuaalne", + "visual_builder": "Visuaalne koostaja", "waiting": "Ootel", "waiting_count": "Ootel: {count}", "warning": "Hoiatus", "week": "Nädal", "welcome": "Tere tulemast", "welcome_to_immich": "Tere tulemast Immich'isse", - "whats_new": "Mis on uut", - "whats_new_settings_subtitle": "Vaata, mis on Immich'is uut", - "whats_new_version": "Versioon {version}", "width": "Laius", "wifi_name": "WiFi-vÃĩrgu nimi", - "workflow": "TÃļÃļvoog", "workflow_delete_prompt": "Kas oled kindel, et soovid selle tÃļÃļvoo kustutada?", "workflow_deleted": "TÃļÃļvoog kustutatud", "workflow_description": "TÃļÃļvoo kirjeldus", "workflow_info": "TÃļÃļvoo info", "workflow_json": "TÃļÃļvoo JSON", - "workflow_json_help": "Muuda tÃļÃļvoo seadistust JSON-kujul. Muudatused sÃŧnkroonitakse visuaalsesse koostajasse.", + "workflow_json_help": "Muuda tÃļÃļvoo seadistust JSON-formaadis. Muudatused sÃŧnkroonitakse visuaalsesse koostajasse.", "workflow_name": "TÃļÃļvoo nimi", "workflow_navigation_prompt": "Kas oled kindel, et soovid lahkuda ilma muudatusi salvestamata?", "workflow_summary": "TÃļÃļvoo kokkuvÃĩte", - "workflow_templates": "TÃļÃļvoo mallid", "workflow_update_success": "TÃļÃļvoog edukalt uuendatud", + "workflow_updated": "TÃļÃļvoog uuendatud", "workflows": "TÃļÃļvood", "workflows_help_text": "TÃļÃļvood automatiseerivad tegevusi Ãŧksustega päästikute ja filtrite alusel", "wrong_pin_code": "Vale PIN-kood", - "x_of_total": "{x}/{total}", "year": "Aasta", "years_ago": "{years, plural, one {# aasta} other {# aastat}} tagasi", "yes": "Jah", "you_dont_have_any_shared_links": "Sul pole Ãŧhtegi jagatud linki", "your_wifi_name": "Sinu WiFi-vÃĩrgu nimi", "zero_to_clear_rating": "Ãŧksuse hinnangu tÃŧhistamiseks vajuta 0", - "zoom_image": "Suumi pilti" + "zoom_image": "Suumi pilti", + "zoom_to_bounds": "Suumi piiridesse" } diff --git a/i18n/eu.json b/i18n/eu.json index 49eac5f4fa..2ac0bc6e32 100644 --- a/i18n/eu.json +++ b/i18n/eu.json @@ -10,31 +10,39 @@ "active": "Aktibo", "active_count": "Aktibo: {count}", "activity": "Jarduera", + "activity_changed": "Jarduera {enabled, select, true {ezarrita dago} other {ez dago ezarrita}}", "add": "Gehitu", "add_a_description": "Azalpena gehitu", "add_a_location": "Kokapena gehitu", "add_a_name": "Izena gehitu", "add_a_title": "Izenburua gehitu", "add_action": "Ekintza gehitu", + "add_action_description": "Egin klik gauzatu beharreko ekintza bat gehitzeko", "add_assets": "Aktiboak gehitu", "add_birthday": "Urtebetetzea gehitu", "add_endpoint": "Endpoint-a gehitu", "add_exclusion_pattern": "Bazterketa eredua gehitu", + "add_filter": "Gehitu iragazkia", + "add_filter_description": "Klik egin iragazki baldintza bat gehitzeko", "add_location": "Kokapena gehitu", + "add_more_users": "Erabiltzaile gehiago gehitu", "add_partner": "Kidea gehitu", + "add_path": "Bidea gehitu", "add_photos": "Argazkiak gehitu", - "add_step": "Urratsa gehitu", "add_tag": "Etiketa gehitu", "add_to": "Hona gehituâ€Ļ", "add_to_album": "Albumera gehitu", "add_to_album_bottom_sheet_added": "{album} -(e)ra gehitu", "add_to_album_bottom_sheet_already_exists": "Dagoeneko {album} albumenean", "add_to_album_bottom_sheet_some_local_assets": "Aktibo lokal batzuk ezin izan dira albumera gehitu", + "add_to_album_toggle": "Txandakatu aukeraketa {album}-arentzat", "add_to_albums": "Albumetara gehitu", "add_to_albums_count": "Albumetara gehitu ({count})", "add_to_bottom_bar": "Gehitu hona", + "add_to_shared_album": "Gehitu partekatutako albumera", "add_upload_to_stack": "Gehitu karga pilara", "add_url": "URL-a gehitu", + "add_workflow_step": "Gehitu fluxu pausoa", "added_to_archive": "Artxibategira gehituta", "added_to_favorites": "Gogokoetara gehituta", "added_to_favorites_count": "{count, number} gogokoetara gehituta", @@ -62,7 +70,6 @@ "cleared_jobs": "Garbitutako lanak honentzak: {job}", "config_set_by_file": "Konfigurazioa konfigurazio-fitxategi baten bidez dago ezarria", "confirm_delete_library": "Seguru zaude {library} ezabatu nahi duzula?", - "confirm_delete_library_assets": "Ziur zaude liburutegi hau ezabatu nahi duzula? Honek {count, plural, one {# contained asset} other {all # contained assets}} Immichetik ezabatuko du eta ezin da desegin. Fitxategiak diskoan mantenduko dira.", "confirm_email_below": "Konfirmatzeko, idatzi \"{email}\" azpian", "confirm_reprocess_all_faces": "Seguru zaude aurpegi guztiak berriro prozesatu nahi dituzula? Erabakiak jendearen izenak ere borratuko ditu.", "confirm_user_password_reset": "Seguru zaude {user}-ren pasahitza berrezarri nahi duzula?", @@ -73,2210 +80,72 @@ "cron_expression_description": "Ezarri eskaneatzeko tartea cron formatua erabiliz. Informazio gehiago lortzeko, jo mesedez Crontab Guru adibidera", "cron_expression_presets": "Cron adierazpenaren aurrezarpenak", "disable_login": "Desgaitu saio hastea", - "download_csv": "CSVa deskargatu", "duplicate_detection_job_description": "Exekutatu ikasketa automatikoa aktiboetan antzeko irudiak detektatzeko. Bilaketa Adimendunean oinarritzen da", - "exclusion_pattern_description": "Bazterketa patroiek fitxategiak eta karpetak alde batera uzteko aukera ematen dizute liburutegia eskaneatzean. Oso erabilgarria da inportatu nahi ez dituzun fitxategiak dituzten karpetak badituzu, adibidez RAW fitxategiak.", "export_config_as_json_description": "Deskargatu momentuko sistema konfigurazioa JSON fitxategi moduan", "external_libraries_page_description": "Administratzailearen kanpo liburutegi orrialdea", "face_detection": "Aurpegi detekzioa", - "face_detection_description": "Hauteman aurpegiak elementuetan ikasketa automatikoa erabiliz. Bideoen kasuan, miniatura baino ez da kontuan hartuko. \"Freskatu\" aukerak elementu guztiak (bir-)prozesatzen ditu. \"Berrezarri\" aukerak horrez gain, uneko aurpegi-datu guztiak ezabatzen ditu. \"Falta direnak\" aukerak oraindik prozesatu ez diren elementuak jartzen ditu ilaran. Detektatutako aurpegiak Aurpegiaren Ezagutzarako ilaran jarriko dira Aurpegiaren Detekzioa amaitu ondoren, lehendik dauden edo berriak diren pertsonetan multzokatuz.", - "facial_recognition_job_description": "Detektatutako aurpegiak pertsonetan multzokatu. Urrats hau Aurpegi Detekzioaren ondoren egikaritzen da. \"Berrezarri\" aukerak aurpegi guztiak (bir-)taldekatzen ditu. \"Faltan\" aukerak izendatu gabeko aurpegiak ilaran jartzen ditu.", "failed_job_command": "{command} komandoak hutsegin du {job} zereginerako", - "force_delete_user_warning": "ADI: Honek erabiltzailea eta baliabide guztiak ezabatuko lituzke. Akzio hau ezin da atzera bota eta fitxategiak ezin izango dira berreskuratu.", "image_format": "Formatua", "image_format_description": "WebP ereduak JPEG baino fitxategi txikiagoak sortzen ditu, baina motelagoa da kodifikatzen.", - "image_fullsize_description": "Tamaina osoko irudia metadaturik gabe, zoom egiterakoan erabiltzen da", - "image_fullsize_enabled": "Tamaina osoko irudien sorrera ezarri", - "image_fullsize_enabled_description": "Sortu tamaina osoko irudia web-erako egokiak ez diren formatuetan. \"Nahiago aurrebista txertatua\" gaituta dagoenean, aurrebista txertatuak zuzenean erabiltzen dira, bihurtu gabe. Ez die eragiten web-erako egokiak diren formatuei (JPEG, adibidez).", - "image_fullsize_quality_description": "Tamaina osoko irudiaren kalitatea (1-100). Zenbat eta altuagoa, orduan eta hobea, baina fitxategi handiagoak sortzen ditu.", - "image_fullsize_title": "Tamaina osoko irudien ezarpenak", "image_prefer_embedded_preview": "Nahiago aurrebista txertatua", - "image_prefer_embedded_preview_setting_description": "Erabili RAW argazkien aurrebista txertatuak irudiak prozesatzeko abiapuntu gisa (eskuragarri daudenean). Honek kolore zehatzagoak eman ditzake irudi batzuetan, baina aurrebistaren kalitatea kameraren araberakoa da eta irudiak konpresio-artefaktu gehiago izan ditzake.", "image_prefer_wide_gamut": "Nahiago gamut zabala", - "image_prefer_wide_gamut_setting_description": "Erabili Display P3 miniaturentzako (thumbnails). Honek hobeto mantentzen du kolore-espazio zabaleko irudien bizitasuna, baina irudiak ezberdin ikus daitezke arakatzaile bertsio zaharra duten gailu zaharkituetan. sRGB irudiak sRGB gisa mantentzen dira kolore-aldaketak saihesteko.", - "image_preview_description": "Tamaina baxuko irudia metadaturik gabe, baliabide bakarra bistaratzerakoan eta ikasketa automatikoan erabiltzeko", - "image_preview_quality_description": "Aurrebisten kalitatea (1-100). Zenbat eta altuagoa, orduan eta hobea, baina fitxategi handiagoak sortzen ditu eta aplikazioaren jariakortasuna moteldu dezake. Balio baxu bat ezartzeak ikasketa automatikoaren kalitatea kaltetu dezake.", "image_preview_title": "Aurreikusiaen Konfigurazioa", "image_progressive": "Progresiboa", - "image_progressive_description": "Kodetu JPEG irudiak progresiboki, pixkanaka kargatzen joan daitezen. Honek ez du eraginik WebP irudietan.", "image_quality": "Kalitatea", "image_resolution": "Erresoluzioa", - "image_resolution_description": "Bereizmen handiagoek xehetasun gehiago gorde ditzakete, baina denbora gehiago behar dute kodetzeko, fitxategi tamaina handiagoak izan eta aplikazioaren jariakortasuna murriztu dezake.", "image_settings": "Argazkien Konfigurazioa", "image_settings_description": "Kudeatu sortutako irudien kalitatea eta erresoluzioa", - "image_thumbnail_description": "Metadaturik gabeko miniatura, argazki-taldeak denbora-lerro nagusia bezala ikusten direnean erabiltzen da", - "image_thumbnail_quality_description": "Miniaturaren kalitatea 1-100. Handia hobea da, baina fitxategi handiagoak sortzen ditu eta aplikazioen jariakortasuna murriztu dezake.", "image_thumbnail_title": "Argazki Txikien Konfigurazioa", "import_config_from_json_description": "Inportatu sistema konfigurazioa JSON konfigurazio fitxategia kargatuz", - "integrity_checks_checksum_files": "Egiaztapeneko baturen fitxategiak", - "integrity_checks_checksum_files_description": "Egiaztapeneko baturen egiaztapena kudeatu", - "integrity_checks_checksum_files_enable_description": "Egiaztapeneko baturen egiaztapena gaitu", - "integrity_checks_checksum_files_percentage_limit": "Ehunekoaren muga", - "integrity_checks_checksum_files_percentage_limit_description": "Konfiguratu gehienezko ehunekoa 0,01 eta 1 arteko egiaztapeneko baturak tarte bakoitzean zenbateraino exekutatu behar duen.", - "integrity_checks_checksum_files_time_limit": "Denbora muga", - "integrity_checks_checksum_files_time_limit_description": "Konfiguratu egiaztapeneko baturak tarte bakoitzean exekutatu behar duen gehienezko iraupena. (ms)", - "integrity_checks_missing_files": "Falta diren fitxategiak", - "integrity_checks_missing_files_description": "Konfiguratu maiztasuna eta gaitu edo desgaitu falta diren fitxategien egiaztapena", - "integrity_checks_missing_files_enable_description": "Gaitu falta diren fitxategien egiaztapena", - "integrity_checks_settings": "Osotasun egiaztapenak", - "integrity_checks_settings_description": "Kudeatu osotasuna egiaztatzeko tarteak", - "integrity_checks_untracked_files": "Jarraitu gabeko fitxategiak", - "integrity_checks_untracked_files_description": "Konfiguratu maiztasuna eta gaitu edo desgaitu jarraitu gabeko fitxategien egiaztapena", - "integrity_checks_untracked_files_enable_description": "Gaitu jarraitu gabeko fitxategien egiaztapena", "job_concurrency": "{job} konkurrentzia", "job_created": "Zeregina sortuta", - "job_not_concurrency_safe": "Ataza hau ez da segurua aldiberekotasunerako.", "job_settings": "Zereginaren konfigurazioa", "job_settings_description": "Kudeatu lanen konkurrentzia", - "jobs_delayed": "{jobCount, plural, one {Atzeratutako bat} other {# atzeratutako}}", - "jobs_failed": "{jobCount, plural, one {Oker bat} other {# oker}}", "jobs_over_time": "Lanak denboran zehar", "library_created": "Sortutako liburutegia: {library}", "library_deleted": "Liburutegia ezabatuta", "library_details": "Liburutegiaren xehetasunak", - "library_folder_description": "Zehaztu inportatzeko karpeta bat. Karpeta honetan,eta haren azpikarpetetan irudiak eta bideoak bilatuko dira.", - "library_remove_exclusion_pattern_prompt": "Ziur bazterketa-eredu hau kendu nahi duzula?", "library_remove_folder_prompt": "Ziur zaude inportazio karpeta hau ezabatu nahi duzula?", - "library_scanning": "Aldizkako eskaneatzea", - "library_scanning_description": "Konfiguratu liburutegiko aldizkako eskaneatzea", - "library_scanning_enable_description": "Aldizkako liburutegiaren eskaneoa gaitu", - "library_settings": "Kanpoko liburutegia", - "library_settings_description": "Kudeatu kanpoko liburutegiaren ezarpenak", - "library_tasks_description": "Kanpoko liburutegietan aldatutakoak baliabideak edo baliabide berriak edo bilatu", - "library_updated": "Liburutegia eguneratuta", - "library_watching_enable_description": "Behatu kanpoko liburutegien fitxategi aldaketak", - "library_watching_settings": "Liburutegien behaketa", - "library_watching_settings_description": "Fitxategi aldaketak behatu automatikoki", "logging_enable_description": "Gaitu erregistroak", "logging_level_description": "Erregistroak gaituta daudenean, nolako erregistro maila erabili.", "logging_settings": "Erregistroak", - "machine_learning_availability_checks": "Eskuragarritasun egiaztapenak", - "machine_learning_availability_checks_description": "Automatikoki detektatu eta hobetsi eskuragarri dauden ikaskuntza automatikoko zerbitzariak", - "machine_learning_availability_checks_enabled": "Eskuragarritasun egiaztapenak gaitu", - "machine_learning_availability_checks_interval": "Egiaztapen denbora-tartea", - "machine_learning_availability_checks_interval_description": "Denbora-tartea milisegundotan eskuragarritasun egiaztapenen artean", - "machine_learning_availability_checks_timeout": "Eskakizun denbora-muga", - "machine_learning_availability_checks_timeout_description": "Eskakizun denbora-muga milisegundotan eskuragarritasun egiaztapenentzat", - "machine_learning_clip_model": "CLIP Modeloa (Kontrastezko hizkuntza-irudi aurre-prestakuntza)", - "machine_learning_clip_model_description": "hemen zerrendatutako CLIP eredu baten izena. Kontuan izan 'Bilaketa adimentsua' lana berriro exekutatu behar duzula irudi guztientzat modelo bat aldatzen duzunean.", "machine_learning_duplicate_detection": "Bizkoizketa Detekzioa", "machine_learning_duplicate_detection_enabled": "Gaitu bikoizketa detekezioa", - "machine_learning_duplicate_detection_enabled_description": "Desgaituta badago, berdin-berdinak diren baliabideak desbikoiztu egingo dira.", - "machine_learning_duplicate_detection_setting_description": "Erabili CLIP txertaketak bikoiztuak aurkitzeko", - "machine_learning_enabled": "Gaitu ikaskuntza automatikoa", - "machine_learning_enabled_description": "Desgaituta badago, ML ezaugarri guztiak desgaitu egingo dira beheko ezarpenak kontuan hartu gabe.", "machine_learning_facial_recognition": "Aurpegi-Ezagutza", "machine_learning_facial_recognition_description": "Detektatu, ezagutu eta aurpegiak banatu argazkietan", "machine_learning_facial_recognition_model": "Aurpegi-Ezagutza eredua", - "machine_learning_facial_recognition_model_description": "Modeloak tamainaren beheranzko ordenan zerrendatzen dira. Modelo handiagoek motelagoak dira eta memoria gehiago erabiltzen dute, baina emaitza hobeak ematen dituzte. Kontuan izan eredu bat aldatzen duzunean aurpegiak hautemateko lana berriro exekutatu behar duzula irudi guztientzat.", "machine_learning_facial_recognition_setting": "Aurpegi-Ezagutza Gaitu", - "machine_learning_facial_recognition_setting_description": "Desgaituta badago, irudiak ez dira kodetuko aurpegia ezagutzeko eta ez dute Arakatu orriko Pertsonak atala beteko.", - "machine_learning_max_detection_distance": "Detekzio distantzia maximoa", - "machine_learning_max_detection_distance_description": "Bi irudien arteko gehienezko distantzia bikoiztutzat hartzeko, 0,001-0,1 bitartekoa. Balio altuagoek bikoiztu gehiago detektatuko dituzte, baina positibo faltsuak sor ditzakete.", - "machine_learning_max_recognition_distance": "Antzemateko distantzia maximoa", - "machine_learning_max_recognition_distance_description": "Bi aurpegiren arteko gehienezko distantzia pertsona berdintzat hartzeko, 0-2 bitartekoa. Hori gutxitzeak bi pertsona pertsona bera bezala etiketatzea ekidin dezake, eta igotzeak, berriz, pertsona bera bi pertsona ezberdin gisa etiketatzea ekidin dezake. Kontuan izan errazagoa dela bi pertsona batzea pertsona bat bitan zatitzea baino, beraz, huts egin atalase baxuago baten alde, ahal denean.", - "machine_learning_min_detection_score": "Gutxieneko detekzio puntuazioa", - "machine_learning_min_detection_score_description": "0-1etik detektatu beharreko aurpegi baten gutxieneko konfiantza puntuazioa. Balio baxuagoek aurpegi gehiago detektatuko dituzte, baina positibo faltsuak sor ditzakete.", - "machine_learning_min_recognized_faces": "Gutxieneko antzemandako aurpegiak", - "machine_learning_min_recognized_faces_description": "Sortu beharreko pertsona batek aitortutako aurpegien gutxieneko kopurua. Hau handitzeak Aurpegi-ezagutza zehatzagoa bihurtzen du aurpegia pertsona bati esleitzeko aukera areagotzearen truke.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Erabili ikaskuntza automatikoa irudietan testua ezagutzeko", - "machine_learning_ocr_enabled": "OCR gaitu", - "machine_learning_ocr_enabled_description": "Desgaituta badago, irudiek ez dute testu-ezagutzarik izango.", - "machine_learning_ocr_max_resolution": "Gehienezko bereizmena", - "machine_learning_ocr_max_resolution_description": "Bereizmen honen gaineko aurrebistak tamaina aldatuko dira aspektu-erlazioa mantenduz. Balio altuagoak zehatzagoak dira, baina denbora gehiago behar dute prozesatzeko eta memoria gehiago behar dute.", - "machine_learning_ocr_min_detection_score": "Gutxieneko detekzio puntuazioa", - "machine_learning_ocr_min_detection_score_description": "Detektatu beharreko testuaren gutxieneko konfiantza puntuazioa 0-1etik. Balio baxuagoek testu gehiago detektatuko dute, baina positibo faltsuak sor ditzakete.", - "machine_learning_ocr_min_recognition_score": "Antzemateko gutxieneko puntuazioa", - "machine_learning_ocr_min_score_recognition_description": "0-1etik antzeman beharreko detektaturiko testuaren gutxieneko konfiantza-puntuazioa. Balio baxuagoek testu gehiago ezagutuko dute, baina positibo faltsuak sor ditzakete.", - "machine_learning_ocr_model": "OCR modeloa", - "machine_learning_ocr_model_description": "Zerbitzari-modeloak mugikorren modeloak baino zehatzagoak dira, baina denbora gehiago eta memoria gehiago behar dute prozesatzeko.", - "machine_learning_settings": "Ikaskuntza automatikoaren ezarpenak", - "machine_learning_settings_description": "Kudeatu ikaskuntza automatikoaren ezaugarri eta ezarpenak", - "machine_learning_smart_search": "Bilaketa adimentsua", - "machine_learning_smart_search_description": "Bilatu irudiak semantikoki CLIP txertaketak erabiliz", "machine_learning_smart_search_enabled": "Gaitu bilaketa arina", - "machine_learning_smart_search_enabled_description": "Desgaituta badago, irudiak ez dira kodetuko bilaketa adimentsurako.", - "machine_learning_url_description": "Ikaskuntza automatikoko zerbitzariaren URLa. URL bat baino gehiago ematen bada, zerbitzari bakoitza banan-banan saiatuko da batek behar bezala erantzun arte, lehenengotik azkenera arte. Erantzuten ez duten zerbitzariei aldi baterako ez ikusi egingo zaie sarera itzuli arte.", - "maintenance_backup_management": "Segurtasun-kopien kudeaketa", - "maintenance_delete_backup": "Segurtasun-kopia ezabatu", - "maintenance_delete_backup_description": "Fitxategi hau behin-betiko ezabatuko da.", - "maintenance_delete_error": "Akatsa segurtasun-kopia ezabatzerakoan.", - "maintenance_integrity_check": "Egiaztatu", - "maintenance_integrity_check_all": "Denak egiaztatu", - "maintenance_integrity_checksum_mismatch": "Egiaztapeneko baturaren desakordioa", - "maintenance_integrity_checksum_mismatch_description": "Fitxategiak non diskoko egiaztapeneko batura Immichek bere datu-basean gordeta duenarekin desakordioan dago.", - "maintenance_integrity_checksum_mismatch_job": "Egiaztapeneko baturen desakordioak egiaztatu", - "maintenance_integrity_checksum_mismatch_refresh_job": "Freskatu kontrol-sumen desadoztasun txostenak", - "maintenance_integrity_missing_file": "Falta diren fitxategiak", - "maintenance_integrity_missing_file_description": "Immich-ek bere datu-basean jarraitu dituen baina fitxategi-sisteman existitzen ez diren fitxategiak.", - "maintenance_integrity_missing_file_job": "Egiaztatu falta diren fitxategiak", - "maintenance_integrity_missing_file_refresh_job": "Freskatu falta diren fitxategien txostenak", - "maintenance_integrity_report": "Osotasun txostena", - "maintenance_integrity_untracked_file": "Jarraitu gabeko fitxategiak", - "maintenance_integrity_untracked_file_description": "Immich-ek bere direktorioetako fitxategien erregistrorik ez duen fitxategiak.", - "maintenance_integrity_untracked_file_job": "Egiaztatu jarraitu gabeko fitxategiak", - "maintenance_integrity_untracked_file_refresh_job": "Freskatu jarraitu gabeko fitxategien txostenak", - "maintenance_restore_backup": "Segurtasun-kopia berreskuratu", - "maintenance_restore_backup_description": "Immich aukeratutako babeskopiatik ezabatu eta berreskuratu egingo da. Jarraitu aurretik, babeskopia bat sortuko da.", - "maintenance_restore_backup_different_version": "Babeskopia hau Immich-en beste bertsio batekin sortu da!", - "maintenance_restore_backup_unknown_version": "Ezin izan da babeskopiaren bertsioa zehaztu.", - "maintenance_restore_database_backup": "Berreskuratu datu-basearen babeskopia", - "maintenance_restore_database_backup_description": "Berreskuratu datu-basearen aurreko egoera babeskopia batetik", - "maintenance_settings": "Mantentzea", - "maintenance_settings_description": "Jarri Immich mantentze moduan.", - "maintenance_start": "Aldatu mantentze modura", - "maintenance_start_error": "Aldatu mantentze modura.", - "maintenance_upload_backup": "Kargatu datu-basearen babeskopia fitxategia", - "maintenance_upload_backup_error": "Ezin izan da babeskopia kargatu, .sql/.sql.gz fitxategia al da?", - "manage_concurrency": "Aldiberekotasuna kudeatu", - "manage_concurrency_description": "Nabigatu atazen orrira atazen aldiberekotasuna kudeatzeko", "manage_log_settings": "Kudeatu erregistroen konfigurazioa", "map_dark_style": "Beltz estiloa", - "map_enable_description": "Gaitu maparen ezaugarriak", "map_gps_settings": "Mapa eta GPS Konfigurazioa", - "map_gps_settings_description": "Kudeatu mapa eta GPS (alderantzizko geokodeketa) ezarpenak", - "map_implications": "Maparen funtzioak kanpoko fitxa-zerbitzu batean oinarritzen da (tiles.immich.cloud)", "map_light_style": "Zuri estiloa", - "map_manage_reverse_geocoding_settings": "Kudeatu Alderantzizko geokodeketa ezarpenak", - "map_reverse_geocoding": "Alderantziko geokodeketa", - "map_reverse_geocoding_enable_description": "Alderantzizko geokodeketa gaitu", - "map_reverse_geocoding_settings": "Alderantzizko geokodeketaren ezarpenak", "map_settings": "Mapa", - "map_settings_description": "Kudeatu maparen ezarpenak", - "map_style_description": "URL-a style.json Maparen itxura fitxategi batera", - "memory_cleanup_job": "Gogorapen garbiketa", - "memory_generate_job": "Gogorapen sorkuntza", - "metadata_extraction_job": "Metadatuen erauzketa", - "metadata_extraction_job_description": "Atera metadatuen informazioa baliabide bakoitzetik, hala nola GPSa, aurpegiak eta bereizmena", "metadata_faces_import_setting": "Gaitu aurpegien inportazioa", - "metadata_faces_import_setting_description": "Inportatu aurpegiak irudien EXIF datuetatik eta Sidecar fitxategietatik", "metadata_settings": "Metadata Konfigurazioa", "metadata_settings_description": "Kudeatu metadaten konfigurazioa", "migration_job": "Migrazio", - "migration_job_description": "Migratu baliabideen eta aurpegien miniaturak karpeten egitura berrienera", - "nightly_tasks_cluster_faces_setting_description": "Exekutatu aurpegi-ezagutza detektatu berri diren aurpegietan", - "nightly_tasks_cluster_new_faces_setting": "Aurpegi berriak multzokatu", - "nightly_tasks_database_cleanup_setting": "Datu-base garbiketa zereginak", - "nightly_tasks_database_cleanup_setting_description": "Garbitu datu zaharrak eta iraungitako datuak datu-basetik", - "nightly_tasks_generate_memories_setting": "Gogorapenak sortu", - "nightly_tasks_generate_memories_setting_description": "Gogorapen berriak sortu balibadeetatik", - "nightly_tasks_missing_thumbnails_setting": "Falta diren miniaturak sortu", - "nightly_tasks_missing_thumbnails_setting_description": "Jarri miniaturarik gabeko baliabideak ilaran miniaturak sortzeko", - "nightly_tasks_settings": "Gaueko atazen ezarpenak", - "nightly_tasks_settings_description": "Gaueko atazak kudeatu", - "nightly_tasks_start_time_setting": "Hasiera denbora", - "nightly_tasks_start_time_setting_description": "Zerbitzaria gaueko atazak exekutatzen hasten den ordua", - "nightly_tasks_sync_quota_usage_setting": "Sinkronizazio kuotaren erabilera", - "nightly_tasks_sync_quota_usage_setting_description": "Eguneratu erabiltzaileen biltegiratze-kuota, egungo erabileraren arabera", - "no_paths_added": "Ez da biderik sartu", - "no_pattern_added": "Ez da eredurik sartu", - "note_apply_storage_label_previous_assets": "Oharra: Aurretik kargatutako baliabideei biltegiratze-etiketa aplikatzeko, exekutatu", - "note_cannot_be_changed_later": "OHARRA: Ezin da geroago aldatu!", - "notification_email_from_address": "Nondik", - "notification_email_from_address_description": "Bidaltzailearen helbide elektronikoa, adibidez: \"Immich Photo Server \". Ziurtatu mezu elektronikoak bidaltzeko baimena daukazun helbide bat erabiltzen duzula.", - "notification_email_host_description": "Posta elektronikoko zerbitzariaren ostalaria (adibidez, smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Ziurtagiri akatsak kontuan ez hartu", - "notification_email_ignore_certificate_errors_description": "TLS ziurtagiri egiaztapen akatsak kontuan ez hartu (ez da gomendatzen)", - "notification_email_password_description": "E-mail zerbitzariarekin autentikatzerakoan erabiltzeko pasahitza", - "notification_email_port_description": "E-mail zerbitzariaren portua (adibidez, 25, 465 edo 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "SMTPS erabili (SMTP TLS bitartez)", - "notification_email_sent_test_email_button": "Bidali E-mail froga mezua eta gorde", - "notification_email_setting_description": "E-mail bidezko jakinarazpenak bidaltzeko ezarpenak", - "notification_email_test_email": "E-mail froga mezua bidali", - "notification_email_test_email_failed": "Ezin izan dira probako e-maila bidali, egiaztatu zure balioak", - "notification_email_test_email_sent": "Proba-mezu bat bidali da {email} helbidera. Mesedez, egiaztatu zure sarrera-ontzia.", - "notification_email_username_description": "E-mail zerbitzariarekin autentifikatzean erabili beharreko erabiltzaile-izena", - "notification_enable_email_notifications": "E-mail jakinarazpenak gaitu", - "notification_settings": "Jakinarazpenen ezarpenak", - "notification_settings_description": "Kudeatu jakinarazpen-ezarpenak, posta elektronikoa barne", - "oauth_allow_insecure_requests": "Onartu segurtasunik gabeko eskaerak", - "oauth_allow_insecure_requests_description": "OHARRA: honek OAuth eskaeretarako TLS ziurtagirien baliozkotzea desgaitzen du eta MITM erasoak jasan ditzakezu.", - "oauth_auto_launch": "Hasieratze automatikoa", - "oauth_auto_launch_description": "Hasi OAuth saio-hasiera-fluxua automatikoki saioa hasteko orrira nabigatzean", - "oauth_auto_register": "Erregistro automatikoa", - "oauth_auto_register_description": "Erregistratu automatikoki erabiltzaile berriak OAuth-ekin saioa hasi ondoren", - "oauth_button_text": "Botoiaren testua", - "oauth_client_secret_description": "Bezero konfidentzialarentzat beharrezkoa da, edo bezero publikoarentzat PKCE (Kodeak trukatzeko froga-gakoa) onartzen ez bada.", - "oauth_enable_description": "Saioa hasi OAuth erabiliz", - "oauth_end_session_url_description": "Birbideratu erabiltzailea URI honetara saioa amaitzean.", - "oauth_mobile_redirect_uri": "Mugikorren birbideratze URIa", - "oauth_mobile_redirect_uri_override": "Mugikorreko birbideratze URIa gainidatzi", - "oauth_mobile_redirect_uri_override_description": "Gaitu OAuth hornitzaileak mugikorren URIrik onartzen ez duenean, adibidez, ''{callback}''", - "oauth_prompt_description": "Prompt parametroa", - "oauth_role_claim": "Rol aldarrikapena", - "oauth_role_claim_description": "Eman automatikoki administratzaile sarbidea erreklamazio honen presentzian oinarrituta. Erreklamazioak 'erabiltzailea' edo 'administratzailea' izan dezake.", "oauth_settings": "OAuth", - "oauth_settings_description": "OAuth saioaren hasierako konfigurazioa kudeatu.", - "oauth_settings_more_details": "Ezaugarri horri buruzko xehetasun gehiago nahi izanez gero, ikusi docs.", - "oauth_storage_label_claim": "Memoriaren etiketa eskaera", - "oauth_storage_label_claim_description": "Erabiltzailearen memoria-etiketa automatikoki finkatzea, eskatutako balioan.", - "oauth_storage_quota_claim": "Eskatutako memoriaren kuota", - "oauth_storage_quota_claim_description": "Ezarri automatikoki erabiltzailearen biltegiratze-kuota balio honekin.", - "oauth_storage_quota_default": "Biltegiratze-kuota lehenetsia (GiB)", - "oauth_storage_quota_default_description": "Erreklamaziorik ematen ez denean erabili beharreko biltegiratze-kuota (GiB).", - "oauth_timeout": "Eskaera denbora-muga", - "oauth_timeout_description": "Eskaeren denbora-muga milisegundotan", - "ocr_job_description": "Erabili ikaskuntza automatikoa irudietan testua antzemateko", - "password_enable_description": "Saioa hasi E-mail eta pasahitza erabiliz", - "password_settings": "Pasahitz bidezko saio hasiera", - "password_settings_description": "Kudeatu pasahitz bidezko saio hasiera", - "paths_validated_successfully": "Bide guztiak arrakastaz balioetsita", - "person_cleanup_job": "Pertsonen garbiketa", - "queue_details": "Ilararen xehetasunak", - "queues": "Ataza ilara", - "queues_page_description": "Ataza ilaren kudeaketa orria", - "quota_size_gib": "Kuota tamaina (GiB)", - "refreshing_all_libraries": "Liburutegi guztiak freskatu", - "registration": "Kudeatzaile erregistroa", - "registration_description": "Sistemako lehen erabiltzailea zarenez, Administratzaile gisa esleituko zara eta administrazio-zereginez arduratuko zara, eta erabiltzaile gehigarriak zuk sortuko dituzu.", - "release_channel_release_candidate": "Bertsio kandidatua", - "release_channel_stable": "Egonkorra", - "remove_failed_jobs": "Akatsdun atazak ezabatu", - "require_password_change_on_login": "Eskatu erabiltzaileari pasahitza aldatzea lehen saio hasieran", - "reset_settings_to_default": "Berrezarri ezarpenak lehenespenera", - "reset_settings_to_recent_saved": "Berrezarri ezarpenak azken gordetako ezarpenetara", - "scanning_library": "Liburutegia eskaneatzen", - "search_jobs": "Atazak bilatuâ€Ļ", - "send_welcome_email": "Ongietorri e-maila bidali", - "server_external_domain_settings": "Kanpoko domeinua", - "server_external_domain_settings_description": "Kanpoko estekentzako erabiltzen den domeinua", - "server_public_users": "Erabiltzaile publikoak", - "server_public_users_description": "Erabiltzaile guztiak (izena eta helbide elektronikoa) zerrendatzen dira erabiltzaile bat partekatutako albumetan gehitzean. Desgaituta dagoenean, erabiltzaileen zerrenda administratzaileentzako soilik egongo da erabilgarri.", - "server_settings": "Zerbizariaren ezarpenak", - "server_settings_description": "Zerbitzariaren ezarpenak kudeatu", - "server_stats_page_description": "Administrarien zerbitzariaren estatistikak", - "server_welcome_message": "Ongietorri mezua", - "server_welcome_message_description": "Saioa hasteko orrian bistaratzen den mezua.", - "settings_page_description": "Administrari ezarpenen orria", - "sidecar_job": "Sidecar metadatuak", - "sidecar_job_description": "Ezagutu edo sinkronizatu sidecar metadatuak fitxategi-sistematik", - "slideshow_duration_description": "Irudi bakoitza bistaratzeko segundo kopurua", - "smart_search_job_description": "Exekutatu ikasketa automatikoa baliabideetan bilaketa adimenduna laguntzeko", - "storage_template_date_time_description": "Baliabidearen sorreraren ordu-zigilua data-orduaren informazioarako erabiltzen da", - "storage_template_date_time_sample": "Denbora eredua {date}", - "storage_template_enable_description": "Biltegiratze txantiloien motorra gaitu", - "storage_template_hash_verification_enabled": "Hash egiaztapena gaituta dago", - "storage_template_hash_verification_enabled_description": "Hash egiaztapena gaitzen du, ez desgaitu hau ondorioen berri jakin ezean", - "storage_template_migration": "Biltegiratze txantiloi migrazioa", - "storage_template_migration_description": "Aplikatu uneko {template} aldez aurretik igotako baliabideei", - "storage_template_migration_info": "Biltegiratzeko txantiloiak luzapen guztiak letra xehetara bihurtuko ditu. Txantiloi-aldaketak baliabide berriei soilik aplikatuko zaizkie. Txantiloia aurretik kargatutako baliabideei atzeraeraginean aplikatzeko, exekutatu {job}.", - "storage_template_migration_job": "Biltegiratze-txantiloia migratzeko ataza", - "storage_template_more_details": "Ezaugarri honi buruzko xehetasun gehiago lortzeko, ikusi Biltegiratzeko txantiloia eta bere inplikazioak", - "storage_template_onboarding_description_v2": "Gaituta dagoenean, ezaugarri honek fitxategiak automatikoki antolatuko ditu erabiltzaileak definitutako txantiloi batean oinarrituta. Informazio gehiago lortzeko, ikusi dokumentazioa.", - "storage_template_path_length": "Gutxi gorabeherako bide-luzeraren muga: {length, number}/{limit, number}", - "storage_template_settings": "Biltegiratze txantiloia", - "storage_template_settings_description": "Kudeatu igotako baliabidearen karpeta-egitura eta fitxategi-izena", - "storage_template_user_label": "{label} da erabiltzailearen biltegiratze etiketa", - "system_settings": "Sistema ezarpenak", - "tag_cleanup_job": "Etiketak garbitu", - "template_email_available_tags": "Aldagai hauek erabil ditzakezu txantiloian: {tags}", - "template_email_if_empty": "Txantiloia hutsik badago, posta elektroniko lehenetsia erabiliko da.", - "template_email_invite_album": "Album gonbidapen txantiloia", - "template_email_preview": "Aurrebista", - "template_email_settings": "E-mail txantiloiak", - "template_email_update_album": "Eguneratu album txantiloia", - "template_email_welcome": "Ongietorri e-mail mezuaren txantiloia", - "template_settings": "Jakinarazpen txantiloiak", - "template_settings_description": "Kudeatu jakinarazpenetarako txantiloi pertsonalizatuak", - "theme_custom_css_settings": "CSS pertsonalizatua", - "theme_custom_css_settings_description": "Estilo-orriek Immich diseinua pertsonalizatzeko aukera ematen dute.", - "theme_settings": "Gaiaren ezarpenak", - "theme_settings_description": "Kudeatu Immich web interfazearen pertsonalizazioa", - "thumbnail_generation_job": "Sortu miniaturak", - "thumbnail_generation_job_description": "Sortu miniatura handiak, txikiak eta lausoak baliabide bakoitzeko, baita pertsona bakoitzarentzako miniaturak ere", - "transcoding_acceleration_api": "Azelerazioaren APIa", - "transcoding_acceleration_api_description": "Transkodifikazioa bizkortzeko zure gailuarekin elkarreragingo duen APIa. Ezarpen hau 'ahalegin onena' da: huts egiten badu software bidezko transkodifikaziora itzuliko da. VP9-k funtziona dezake edo ez zure hardwarearen arabera.", - "transcoding_acceleration_nvenc": "NVENC (NVIDIA GPU-a behar du)", - "transcoding_acceleration_qsv": "Quick Sync (7. belaunaldiko Intel prozesadorea edo handiagoa behar du)", - "transcoding_acceleration_rkmpp": "RKMPP (Rockchip-en SOC-an soilik)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Onartutako audio-kodekak", - "transcoding_accepted_audio_codecs_description": "Hautatu zein audio-kodek transkodetu behar ez diren. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", - "transcoding_accepted_containers": "Onartutako kontenedoreak", - "transcoding_accepted_containers_description": "Hautatu zein kontenedore formatu ez diren MP4ra birmixatu behar. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", - "transcoding_accepted_video_codecs": "Onartutako bideo-kodekak", - "transcoding_accepted_video_codecs_description": "Hautatu zein bideo-kodek transkodetu behar ez diren. Transkodetze-politika jakin batzuetarako bakarrik erabiltzen da.", - "transcoding_advanced_options_description": "Erabiltzaile gehienek aldatu behar ez dituzten aukerak", - "transcoding_audio_codec": "Audio-kodeka", - "transcoding_audio_codec_description": "Opus kalitate goreneko aukera da, baina gailu edo software zaharrekin bateragarritasun txikiagoa du.", - "transcoding_bitrate_description": "Gehienezko bit-tasa baino handiagoa edo onartutako formatuan ez dauden bideoak", - "transcoding_codecs_learn_more": "Hemen erabiltzen den terminologiari buruz gehiago jakiteko, jo FFmpeg-en dokumentaziora. H.264 kodeka, HEVC kodeka eta VP9 kodeka .", - "transcoding_constant_quality_mode": "Kalitate iraunkorreko modua", - "transcoding_constant_quality_mode_description": "ICQ CQP baino hobea da, baina hardware-azelerazio-gailu batzuek ez dute modu hau onartzen. Aukera hau ezartzeak zehaztutako modua hobetsiko du kalitatean oinarritutako kodeketa erabiltzean. NVENC-ek ez du jaramonik egingo ICQ onartzen ez duelako.", - "transcoding_constant_rate_factor": "Tasa konstantearen faktorea (-crf)", - "transcoding_constant_rate_factor_description": "Bideoaren kalitate maila. Balio tipikoak H.264rako 23, HEVCrako 28, VP9rako 31 eta AV1erako 35 dira. Baxuagoa hobea da, baina fitxategi handiagoak sortzen ditu.", - "transcoding_disabled_description": "Ez transkodetu bideorik, baliteke bezero batzuen erreprodukzioa haustea", - "transcoding_encoding_options": "Kodetze aukerak", - "transcoding_encoding_options_description": "Ezarri kodekak, bereizmena, kalitatea eta beste aukera batzuk kodetutako bideoetarako", - "transcoding_hardware_acceleration": "Hardware bidezko azelerazioa", - "transcoding_hardware_acceleration_description": "Esperimentala: transkodeketa azkarragoa dakar baina kalitatea murriztu dezake bit-tasa berean", - "transcoding_hardware_decoding": "Hardware bidezko deskodetzea", - "transcoding_hardware_decoding_setting_description": "Mutur-muturreko azelerazioa gaitzen du soilik kodeketa bizkortu beharrean. Baliteke bideo guztietan ez funtzionatzea.", - "transcoding_max_b_frames": "Gehienezko B-fotogramak", - "transcoding_max_b_frames_description": "Balio altuagoek konpresioaren eraginkortasuna hobetzen dute, baina kodeketa moteldu egiten dute. Baliteke gailu zaharretako hardware-azelerazioarekin bateragarria ez izatea. 0 balioak B-koardoak desgaitzen ditu, eta -1 balioak balio hau automatikoki ezartzen du.", - "transcoding_max_bitrate": "Gehienezko bit-tasa", - "transcoding_max_bitrate_description": "Gehienezko bit-tasa ezartzeak fitxategien tamainak aurreikusgarriagoak egite ditu kalitatearen kostu txikiarekin. 720p-n, balio tipikoak VP9 edo HEVCrako 2600 kbit/s edo H.264rako 4500 kbit/s dira. Desgaituta dago 0 moduan ezartzen bada. Unitaterik zehazten ez denean, k (kbit/s-rako) suposatzen da; beraz, 5000, 5000k eta 5M (Mbit/s-rako) baliokideak dira.", - "transcoding_max_keyframe_interval": "Gehieneko gako-fotograma tartea", - "transcoding_max_keyframe_interval_description": "Gako-fotogramen arteko gehienezko fotograma-distantzia ezartzen du. Balio baxuagoek konpresio-eraginkortasuna okertzen dute, baina bilaketa-denborak hobetzen dituzte eta baliteke kalitatea hobetzea mugimendu azkarra duten eszenetan. 0-k balio hau automatikoki ezartzen du.", - "transcoding_optimal_description": "Helburuko bereizmenetik gorako bideoak edo onartutako formatuan ez daudenak", - "transcoding_policy": "Transkodetze politika", - "transcoding_policy_description": "Ezartzen du bideo bat noiz transkodetuko den", - "transcoding_preferred_hardware_device": "Hardware-gailu hobetsia", - "transcoding_preferred_hardware_device_description": "VAAPI-ri eta QSV-i soilik aplikatzen zaie. Hardware transkodetzeko erabilitako dri nodoa ezartzen du.", - "transcoding_preset_preset": "Txantiloia (-preset)", - "transcoding_preset_preset_description": "Konpresioaren abiadura. Txantiloi motelagoak fitxategi txikiagoak sortzen ditu eta kalitatea handitzen du bit-tasa jakin batera bideratzen denean. VP9-k \"azkarrago\"-tik gorako abiadura baztertzen du.", - "transcoding_realtime": "Denbora errealeko transkodeketa [ESPERIMENTALA]", - "transcoding_realtime_description": "Bideoa erreproduzitzen ari den heinean transkodetzea denbora errealean egiteko aukera ematen du. Kalitate aldaketa gaitzen du, baina zerbitzariaren gaitasunen arabera erreprodukzio-latentzia eta toteltze handiagoak sor ditzake.", - "transcoding_realtime_enabled": "Gaitu denbora errealeko transkodeketa", - "transcoding_realtime_enabled_description": "Desgaituta badago, zerbitzariak uko egingo dio denbora errealeko transkodetze saio berriei.", - "transcoding_realtime_resolutions": "Bereizmenak", - "transcoding_realtime_resolutions_description": "Denbora errealean transkodetzeko eskaintzen diren bereizmenak. Bereizmen handiagoek erreprodukzio-arazoak sor ditzakete zerbitzariak ezin baditu behar bezain azkar transkodetzen.", - "transcoding_realtime_video_codecs": "Bideo kodetzaileak", - "transcoding_realtime_video_codecs_description": "Denbora errealean transkodetzeko eskaintzen diren bideo-kodetzaileak. Erreprodukzioan onartzen duten aukerarik onena aukeratuko dute bezeroek. AV1 HEVC baino eraginkorragoa da, baita H.264 baino eraginkorragoa. Hardware-azelerazioa erabiltzean, hautatu azeleragailuak kode ditzakeen kodetzaileak soilik. Software transkodeketa erabiltzean, kontuan izan H.264 AV1 baino azkarragoa dela, baita HEVC baino azkarragoa.", - "transcoding_reference_frames": "Erreferentzia fotogramak", - "transcoding_reference_frames_description": "Forograma jakin bat konprimitzean erreferentzia beharreko fotograma kopurua. Balio altuagoek konpresioaren eraginkortasuna hobetzen dute, baina kodeketa moteltzen dute. 0-k balio hau automatikoki ezartzen du.", - "transcoding_required_description": "Onartutako formatu batean ez dauden bideoak bakarrik", - "transcoding_settings": "Bideo transkodetze ezarpenak", - "transcoding_settings_description": "Kudeatu zein bideo transkodetu eta nola prozesatu", - "transcoding_target_resolution": "Bereizmen helburua", - "transcoding_target_resolution_description": "Bereizmen handiagoek xehetasun gehiago gorde ditzakete, baina denbora gehiago behar dute kodetzeko, fitxategi-tamaina handiagoak sortu eta aplikazioen jariakortasuna murriztu dezake.", - "transcoding_temporal_aq": "Aldi baterako AQ-a", - "transcoding_temporal_aq_description": "NVENC-i soilik aplikatzen da. Denboraldiaren Kuantizazio Egokigarriak (AQak) xehetasun handiko eta mugimendu baxuko eszenen kalitatea areagotzen du. Baliteke gailu zaharrekin bateragarria ez izatea.", - "transcoding_threads": "Hariak", - "transcoding_threads_description": "Balio altuagoek kodeketa azkarragoa dakar, baina leku gutxiago uzten diote zerbitzariari beste zeregin batzuk prozesatzeko aktibo dagoen bitartean. Balio honek ez luke CPU-nukleoen kopurua baino handiagoa izan behar. Erabilera maximizatzen du 0 gisa ezartzen bada.", - "transcoding_tone_mapping": "Tonoen mapeoa", - "transcoding_tone_mapping_description": "SDR bihurtzean HDR bideoen itxura mantentzen saiatzen da. Algoritmo bakoitzak kolore, xehetasun eta distira konpromezu desberdinak egiten ditu. Hablek xehetasunak gordetzen ditu, Mobiusek kolorea eta Reinhardek distira.", - "transcoding_transcode_policy": "Transkodetze politika", - "transcoding_transcode_policy_description": "Bideo bat transkodetu behar den politika. YUV 4:2:0 ez den pixel formatua duten bideoak eta HDR bideoak beti transkodetuko dira (transkodetzea desgaituta badago izan ezik).", - "transcoding_two_pass_encoding": "Bi pasatako kodeketa", - "transcoding_two_pass_encoding_setting_description": "Transkodetu bi pasetan hobeto kodetutako bideoak sortzeko. Gehienezko bit-abiadura gaituta dagoenean (beharrezkoa da H.264 eta HEVC-ekin funtzionatzeko), modu honek bit-tasa maximoan oinarritutako bit-abiadura-tarte bat erabiltzen du eta CRF-a ez du aintzat hartzen. VP9rako, CRF erabil daiteke bit-tasa maximoa desgaituta badago.", - "transcoding_video_codec": "Bideo-kodekak", - "transcoding_video_codec_description": "VP9-k eraginkortasun eta web bateragarritasun handia du, baina denbora gehiago behar da transkodetzeko. HEVC antzera funtzionatzen du, baina web bateragarritasun txikiagoa du. H.264 oso bateragarria da eta transkodetzeko azkarra da, baina askoz fitxategi handiagoak sortzen ditu. AV1 kodeka eraginkorrena da, baina ez dira onartzen gailu zaharretan.", - "trash_enabled_description": "Zakarrontzia gaitu", - "trash_number_of_days": "Egun kopurua", - "trash_number_of_days_description": "Baliabideak zakarrontzian gorde behar diren egun kopurua behin betiko ezabatu aurretik", - "trash_settings": "Zakarrontziaren ezarpenak", - "trash_settings_description": "Kudeatu zakarrontziaren ezarpenak", - "unlink_all_oauth_accounts": "OAuth kontu guztietatik saioa itxi", - "unlink_all_oauth_accounts_description": "Gogoratu hornitzaile berri batera migratu aurretik OAuth-eko kontu guztietatik saioa ixtea.", - "unlink_all_oauth_accounts_prompt": "Ziur OAuth kontu guztiak deskonektatu nahi dituzula? Honek erabiltzaile bakoitzaren OAuth IDa berrezarriko du eta ezin da desegin.", - "user_cleanup_job": "Erabiltzaile garbiketa", - "user_delete_delay": "{user} erabiltzailearen kontua eta baliabideak {delay, plural, one {egun bat} other {# egun}} barru behin betiko ezabatzeko programatuko dira.", - "user_delete_delay_settings": "Atzerapena ezabatu", - "user_delete_delay_settings_description": "Erabiltzaile baten kontua eta baliabideak behin betiko ezabatzeko egun kopurua. Erabiltzaileak ezabatzeko ataza gauerdian abiarazten da ezabatzeko prest dauden erabiltzaileak egiaztatzeko. Ezarpen honen aldaketak hurrengo exekuzioan ebaluatuko dira.", - "user_delete_immediately": "{user}-(r)en kontua eta baliabideak behin betiko ezabatzeko ilaran jarriko dira berehala.", - "user_delete_immediately_checkbox": "Erabiltzailea eta baliabideak berehala ezabatzeko ilaran jarri", - "user_details": "Erabiltzailearen xehetasunak", - "user_management": "Erabiltzaileen kudeaketa", - "user_password_has_been_reset": "Erabiltzailearen pasahitza eguneratu da:", - "user_password_reset_description": "Mesedez, ezarri aldi baterako pasahitza erabiltzaileari eta jakinarazi hurrengo saioa hasteko pasahitza aldatu beharko duela.", - "user_restore_description": "{user} erabiltzailearen kontua berreskuratu egingo da.", - "user_restore_scheduled_removal": "Berreskuratu erabiltzailea - programatutako kentzea {date, date, long} egunean", - "user_settings": "Erabiltzailearen ezarpenak", - "user_settings_description": "Erabiltzailearen ezarpenak kudeatu", - "user_successfully_removed": "{email} erabiltzailea behar bezala ezabatu da.", - "users_page_description": "Administrari erabiltzaileen orria", - "version_check_channel": "Argitalpen kanala", - "version_check_channel_description": "Aukeratu bertsio-iragarpenak jaso nahi dituzun bertsio-kanalak", - "version_check_enabled_description": "Bertsio baliozkotzea gaitu", - "version_check_implications": "Bertsio baliozkotzea {server}-(a)rekin aldizkako komunikazioaren beharra du", - "version_check_settings": "Bertsio baliozkotzea", - "version_check_settings_description": "Bertsio berriaren jakinarazpena gaitu/desgaitu", - "video_conversion_job": "Bideoak transkodeatu", - "video_conversion_job_description": "Bideoak arakatzaile eta gailuekin bateragarritasun handiagoa lortzeko transkodetu" + "transcoding_acceleration_vaapi": "VAAPI" }, - "admin_email": "Administrariaren e-maila", - "admin_password": "Administrari pasahitza", - "administration": "Administrazioa", "advanced": "Aurreratua", - "advanced_settings_clear_image_cache": "Irudien cahcea garbitu", - "advanced_settings_clear_image_cache_error": "Ezin izan da argazkien cachea garbitu", - "advanced_settings_clear_image_cache_success": "{size} arrakastaz garbituta", - "advanced_settings_log_level_title": "Erregistro nibela: {level}", - "advanced_settings_prefer_remote_subtitle": "Gailu batzuk oso motelak dira tokiko aktiboetatik miniaturak sortzeko. Aktibatu ezarpen hau urruneko irudiak kargatzeko.", - "advanced_settings_prefer_remote_title": "Urruneko irudiak hobetsi", - "advanced_settings_proxy_headers_subtitle": "Definitu Immich-ek sareko eskaera bakoitzarekin bidali beharko lituzkeen proxy goiburuak", - "advanced_settings_proxy_headers_title": "Proxy goiburu pertsonalizatuak [ESPERIMENTALA]", - "advanced_settings_readonly_mode_subtitle": "Argazkiak soilik ikus daitezkeen irakurtzeko modua gaitzen du, hainbat irudi hautatzea, partekatzea, igortzea, ezabatzea bezalako gauzak desgaituta daude. Gaitu/Desgaitu irakurketa modua erabiltzailearen avatar bidez pantaila nagusitik", - "advanced_settings_readonly_mode_title": "Irakurri-soilik modua", - "advanced_settings_sync_remote_deletions_subtitle": "Ezabatu edo berreskuratu automatikoki gailu honetako baliabide bat ekintza hori sarean egiten denean", - "advanced_settings_sync_remote_deletions_title": "Sinkronizatu urruneko ezabaketak [ESPERIMENTALA]", - "advanced_settings_tile_subtitle": "Erabiltzailearen ezarpen aurreratuak", - "advanced_settings_troubleshooting_subtitle": "Gaitu ezaugarri gehigarriak arazoak konpontzeko", - "advanced_settings_troubleshooting_title": "Arazoak detektatzea eta konpontzea", - "age_months": "Denbora {months, plural, one {Hilabete #} other {# hilabete}}", - "age_year_months": "Denbora urte bat, {months, plural, one {hilabete #} other {# hilabete}}", - "age_years": "Adina {years, plural, one {urte #} other {# urte}}", - "album": "Album", - "album_added": "Albuma gehituta", - "album_added_notification_setting_description": "Jaso jakinarazpen elektroniko bat partekatutako album batera gehitzen zarenean", - "album_cover_updated": "Albumaren azala eguneratu da", - "album_delete_confirmation": "Ziur zaude {album} albuma ezabatu nahi duzula?", - "album_delete_confirmation_description": "Album hau partekatzen bada, beste erabiltzaileek ezin izango dute atzitu.", - "album_deleted": "Albuma ezabatuta", - "album_info_updated": "Albumaren informazioa eguneratu da", - "album_name": "Albumaren izena", - "album_options": "Albumaren ezarpenak", - "album_remove_user": "Erabiltzailea kendu?", - "album_remove_user_confirmation": "Ziur zaude {user} erabiltzailea kendu nahi duzula?", - "album_search_not_found": "Ez da aurkitu zure bilaketarekin bat datorren albumik", - "album_share_no_users": "Album hau erabiltzaile guztiekin partekatu duzula dirudi edo ez duzula erabiltzailerik partekatzeko.", - "album_summary": "Albumaren laburpena", - "album_updated": "Albuma eguneratu da", - "album_updated_setting_description": "Jaso e-posta bidezko jakinarazpena partekatutako album batek baliabide berriak dituenean", - "album_upload_assets": "Kargatu baliabideak ordenagailutik eta gehitu albumera", - "album_viewer_appbar_share_err_delete": "Ezin izan da albuma ezabatu", - "album_viewer_page_share_add_users": "Erabiltzaileak gehitu", - "album_with_link_access": "Utzi esteka duen edonori album honetako argazkiak eta jendea ikustea.", - "albums": "Albumak", - "albums_count": "{count, plural, one {Album bat} other {{count, number} Album}}", - "albums_default_sort_order": "Albumen ordena lehenetsia", - "albums_default_sort_order_description": "Album berriak sortzean baliabideak ordenatzeko hasierako ordena.", - "albums_feature_description": "Beste erabiltzaile batzuekin parteka daitezkeen baliabide bildumak.", - "albums_on_device_count": "Albumak gailuan ({count})", - "all": "Denak", - "all_albums": "Album guztiak", - "all_people": "Pertsona guztiak", - "all_photos": "Argazki guztiak", - "all_videos": "Bideo guztiak", - "allow_dark_mode": "Modu iluna baimendu", - "allow_public_user_to_download": "Erabiltzaile publikoei deskargatzea baimendu", - "allow_public_user_to_upload": "Erabiltzaile publikoei kargatzea baimendu", - "allowed": "Baimenduta", - "alt_text_qr_code": "QR-kodea", - "always_keep": "Beti mantendu", - "always_keep_photos_hint": "Biltegiratze garbitzaileak argazki guztiak mantenduko ditu gailu honetan.", - "always_keep_videos_hint": "Biltegiratze garbitzaileak bideo guztiak mantenduko ditu gailu honetan.", - "api_key": "API Gakoa", - "api_key_description": "Balio hau behin bakarrik erakutsiko da. Mesedez, ziurtatu leihoa itxi aurretik kopiatu duzula.", - "api_key_empty": "Zure API gakoaren izenak ez du hutsik egon behar", - "api_keys": "API gakoak", - "app_architecture_variant": "Aldaera (Arkitektura)", - "app_bar_signout_dialog_content": "Ziur saioa amaitu nahi duzula?", - "app_bar_signout_dialog_title": "Saioa itxi", - "app_download_links": "Aplikazioa deskargatzeko estekak", - "app_settings": "Aplikazio ezarpenak", - "app_stores": "Aplikazio dendak", - "app_update_available": "Aplikazioaren eguneratzea eskuragarri dago", - "appears_in": "Hemen agertzen da", + "advanced_settings_readonly_mode_title": "Irakurri-bakarrik Modua", "apply_count": "Ezarri ({count, number})", - "archive": "Artxibategia", - "archive_action_prompt": "{count} artxibategira bidalita", - "archive_or_unarchive_photo": "Argazkia artxibatu edo berrezarri", - "archive_size": "Fitxategi konprimatuaren tamaina", - "archive_size_description": "Konfiguratu konprimitatutako fitxategiaren tamaina deskargak egiteko (GiB-tan)", - "archived": "Artxibatua", - "archived_count": "{count, plural, one {artxibatu bat} other {# artxibatu}}", - "are_these_the_same_person": "Pertsona bera al dira hauek?", - "are_you_sure_to_do_this": "Ziur hau egin nahi duzula?", - "asset_added_to_album": "Albumera gehituta", - "asset_adding_to_album": "Albumera gehitzenâ€Ļ", - "asset_created": "Baliabidea sortuta", - "asset_day_count": "{date}: {count, plural, one {baliabide bat} other {# baliabide}}", - "asset_description_updated": "Baliabidearen deskribapena eguneratu da", - "asset_hashing": "Hasha kalkulatzenâ€Ļ", - "asset_list_group_by_sub_title": "Elkartu", - "asset_list_layout_settings_group_by": "Honen bidez taldekatu", - "asset_list_layout_settings_group_by_month_day": "Hilabetea + eguna", - "asset_list_layout_sub_title": "Antolaketa", - "asset_list_settings_subtitle": "Argazki-sarearen diseinu-ezarpenak", - "asset_list_settings_title": "Irudi lauki-sarea", - "asset_not_found_on_device_android": "Baliabidea ez da gailuan aurkitu", - "asset_not_found_on_device_ios": "Ez da baliabidea aurkitu gailuan. iCloud erabiltzen ari bazara, baliteke baliabidea eskuraezin izatea iCloud-en gordetako fitxategi txarra dela eta", - "asset_not_found_on_icloud": "Ez da aurkitu baliabidea iCloud-en. baliteke baliabidea eskuraezin izatea iCloud-en gordetako fitxategi txarra dela eta", - "asset_offline": "Baliabidea lineaz kanpo", - "asset_offline_description": "Kanpoko baliabide hau jada ez da diskoan aurkitzen. Mesedez, jarri harremanetan Immich-eko administratzailearekin laguntza eskatzeko.", - "asset_skipped": "Alde batera utzita", - "asset_skipped_in_trash": "Zakarrontzian", - "asset_troubleshoot": "Baliabidearen diagnosia", - "asset_uploaded": "Igota", - "asset_uploading": "Igotzenâ€Ļ", - "asset_viewer_settings_subtitle": "Kudeatu galeria-ikuslearen ezarpenak", - "asset_viewer_settings_title": "Baliabide ikuslea", - "assets": "Baliabideak", - "assets_added_to_album_count": "{count, plural, one {Baliabide bat} other {# baliabide}} albumera gehituta", - "assets_added_to_album_partial_count": "{totalCount, number}-(e)tik baliabide {successCount, number, plural, one {bat gehitu da albumera} other {# gehitu dira albumera}}", "assets_added_to_albums_count": "Gehituta {assetTotal, plural, one {# asset} other {# assets}} to {albumTotal, plural, one {# album} other {# albums}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Baliabidea ezin izan da albumera gehitu} other {Baliabideak ezin izan dira albumera gehitu}}", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} ezin izan da albumetara gehitu", - "assets_count": "{count, plural, one {Baliabide bat} other {# baliabide}}", - "assets_moved_to_trash_count": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", - "assets_permanently_deleted_count": "{count, plural, one {Baliabidea behin-betiko ezabatu da} other {# baliabide behin-betiko ezabatu dira}}", - "assets_removed_count": "{count, plural, one {Baliabidea ezabatu da} other {# baliabide ezabatu dira}}", - "assets_restore_confirmation": "Ziur zaude zaborrontziko baliabide guztiak berreskuratu nahi dituzula? Ezin duzu ekintza hau desegin! Kontuan izan konexiorik gabeko baliabideak ezin direla modu honetan berreskuratu.", - "assets_restored_count": "{count, plural, one {Baliabidea berreskuratu da} other {# baliabide berreskuratu dira}}", - "assets_trashed": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", - "assets_trashed_count": "{count, plural, one {Baliabidea zakarrontzira eraman da} other {# baliabide zakarrontzira eraman dira}}", - "assets_were_part_of_album_count": "{count, plural, one {Baliabidea iada albumaren parte da} other {Baliabideak iada albumaren parte dira}}", - "assets_were_part_of_albums_count": "{count, plural, one {Baliabidea iada albumen parte da} other {Baliabideak iada albumrn parte dira}}", - "authorized_devices": "Baimendutako gailuak", - "automatic_endpoint_switching_subtitle": "Konektatu lokalean izendatutako Wi-Fi bidez erabilgarri dagoenean eta erabili beste nonbait konexio alternatiboak", - "automatic_endpoint_switching_title": "URL aldaketa automatikoa", - "autoplay_slideshow": "Erreproduzitu automatikoki diapositiba-aurkezpena", - "back": "Atzera", - "back_close_deselect": "Atzera, itxi edo desautatu", - "background_location_permission": "Atzeko planoko kokapenaren baimena", - "background_location_permission_content": "Atzeko planoan exekutatzen denean sarez aldatzeko, Immichek *beti* izan behar du kokapen zehatzaren sarbidea, aplikazioak Wi-Fi sarearen izena irakur dezan", - "background_options": "Atzeko planoko aukerak", - "backup": "Babes-kopia", - "backup_album_selection_page_albums_tap": "Sakatu sartzeko, sakatu birritan baztertzeko", - "backup_album_selection_page_assets_scatter": "Baliabideak hainbat albumetan barreiatu daitezke. Horrela, albumak babeskopia prozesuan sartu edo baztertu daitezke.", - "backup_album_selection_page_select_albums": "Albumak aukeratu", - "backup_album_selection_page_selection_info": "Aukeraren informazioa", - "backup_albums_sync": "Segurtasun-kopia albumen sinkronizazioa", - "backup_background_service_complete_notification": "Baliabideen babes-kopia arrakastaz osatua", - "backup_background_service_default_notification": "Baliabide berririk dauden egiaztatzenâ€Ļ", - "backup_background_service_in_progress_notification": "Zure baliabideen segurtasun-kopia sortzenâ€Ļ", - "backup_controller_page_albums": "Seguratsun-kopia albumak", - "backup_controller_page_background_battery_info_link": "Erakutsi nazazu nola", - "backup_controller_page_background_battery_info_message": "Atzeko planoko babeskopien esperientzia onena lortzeko, desgaitu Immich-en atzeko planoko jarduera murrizten duten bateriaren optimizazio guztiak.\n\nGailuari espezifikoa denez, mesedez bilatu behar den informazioa zure gailuaren fabrikatzailearentzat.", - "backup_controller_page_background_battery_info_ok": "Onartu", - "backup_controller_page_background_battery_info_title": "Bateria optimizazioak", - "backup_controller_page_background_delay": "Atzeratu baliabide berrien babeskopia: {duration}", - "backup_controller_page_backup": "Babes-kopia", - "backup_controller_page_backup_selected": "Aukeratutakoa: ", - "backup_controller_page_backup_sub": "Argazkien eta bideoen segurtasun-kopiak egin dira", - "backup_controller_page_excluded": "Baztertutakoa: ", - "backup_controller_page_none_selected": "Aukerarik ez", - "backup_controller_page_remainder": "Gainerakoak", - "backup_controller_page_remainder_sub": "Hautapenetik babeskopiak egiteko gainerako argazki eta bideoak", - "backup_controller_page_server_storage": "Zerbitzariko memoria", - "backup_controller_page_storage_format": "{total}-tik {used} erabilita", - "backup_controller_page_to_backup": "Segurtasun-kopia egiteko albumak", - "backup_controller_page_total_sub": "Hautatutako albumetako argazki eta bideo guztiak", - "backup_error_sync_failed": "Sinkronizazioak huts egin du. Ezin da segurtasun-kopia prozesatu.", - "backup_info_card_assets": "baliabide", - "backup_options": "Segurtasun-kopien ezarpenak", - "backup_settings_subtitle": "Kudeatu karga-ezarpenak", - "backward": "Atzeruntz", - "battery_optimization_backup_reliability": "Bateriaren optimizazioak desgaitzeak atzeko planoko babeskopien fidagarritasuna hobetu dezake", - "biometric_auth_enabled": "Autentifikazio biometrikoa gaituta", - "biometric_locked_out": "Autentifikazio biometrikotik kanpo geratu zara", - "biometric_no_options": "Ez dago aukera biometrikorik eskuragarri", - "biometric_not_available": "Gailu honetan ez dago erabilgarri autentifikazio biometrikoa", - "birthdate_set_description": "Jaiotze-data argazki baten unean pertsona horren adina kalkulatzeko erabiltzen da.", - "blurred_background": "Atzeko plano lausoa", - "browse_templates": "Arakatu txantiloiak", - "bugs_and_feature_requests": "Akatsak eta ezaugarri-eskaerak", - "build": "Bertsioa", - "build_image": "Konpilazio irudia", - "bulk_delete_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat ezabatu nahi duzula} other {bikoiztutako # baliabide ezabatu nahi dituzula}}? Honek talde bakoitzaren baliabiderik handiena gordeko du eta betiko ezabatuko ditu gainerako bikoiztuak. Ezin duzu ekintza hau desegin!", - "bulk_keep_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat mantendu nahi duzula} other {bikoiztutako # baliabide mantendu nahi dituzula}}? Horrek bikoiztutako talde guztiak konponduko ditu ezer ezabatu gabe.", - "bulk_trash_duplicates_confirmation": "Ziur zaude {count, plural, one {bikoiztutako baliabide bat zakarrontzira bildu nahi duzula} other {bikoiztutako # baliabide zakarrontzira bildu nahi dituzula}}? Honek talde bakoitzaren aktibo handiena gordeko du eta gainerako bikoiztu guztiak zakarrontzira eramango ditu.", - "buy": "Immich erosi", - "camera": "Kamera", - "camera_brand": "Kamera fabrikatzailea", - "camera_model": "Kamera modeloa", - "cancel": "Ezeztatu", - "cancel_search": "Bilaketa ezeztatu", - "canceled": "Ezeztatua", - "canceling": "Desgaitzen", - "cannot_merge_people": "Ezin izan dira pertsonak batu", - "cannot_undo_this_action": "Ezin duzu ekintza hau desegin!", - "cannot_update_the_description": "Ezin izan da deskribapena eguneratu", - "cast": "Transmititu", - "cast_description": "Konfiguratu eskuragarri dauden igorpen-helmugak", - "change": "Aldatu", - "change_date": "Data aldatu", - "change_description": "Deskribapena aldatu", - "change_display_order": "Bistaratzeko ordena aldatu", - "change_location": "Kokapena aldatu", - "change_name": "Izena eguneratu", - "change_name_successfully": "Izena arrakastaz eguneratuta", - "change_password": "Pasahitza eguneratu", - "change_password_description": "Hau da sisteman saioa hasten duzun lehen aldia edo pasahitza aldatzeko eskaera egin den. Mesedez, sartu pasahitz berria behean.", - "change_password_form_confirm_password": "Pasahitza baieztu", - "change_password_form_description": "Kaixo {name},\n\nHau da sisteman saioa hasten duzun lehen aldia edo pasahitza aldatzeko eskaera egin den. Mesedez, sartu pasahitz berria behean.", - "change_password_form_log_out": "Amaitu saioa gainerako gailu guztietan", - "change_password_form_log_out_description": "Beste gailu guztietan saioa amaitzea gomendatzen da", - "change_password_form_new_password": "Pasahitza berria", - "change_password_form_password_mismatch": "Pasahitzak ez datoz bat", - "change_password_form_reenter_new_password": "Sartu berriro pasahitz berria", - "change_pin_code": "PIN kodea eguneratu", - "change_your_password": "Eguneratu zure pasahitza", - "changed_visibility_successfully": "Ikusgarritasuna arrakastaz aldatuta", - "charging": "Kargatzen", - "charging_requirement_mobile_backup": "Atzeko planoko segurtasun-kopia sortzeko gailua kargatzen egon behar du", - "check_logs": "Erregistroak egiaztatu", - "checksum": "Egiaztapeneko batura", - "choose": "Aukeratu", - "choose_matching_people_to_merge": "Aukeratu pertsona beraren agerraldi bikoiztuak bateratzeko", - "city": "Hiria", - "cleanup_confirm_description": "Immich-ek zerbitzarian kargatuta {date} baino lehenago sortutako {count, plural, one {baliabide bat aurkitu du} other {# baliabide aurkitu ditu}}. Gailu honetatik tokiko kopiak kendu nahi dituzu?", - "cleanup_confirm_prompt_title": "Gailu honetatik kendu?", - "cleanup_deleted_assets": "{count, plural, one {baliabide bat eraman da} other {# baliabide eraman dira}} gailuaren zakarrontzira", - "cleanup_deleting": "Zakarrontzira eramaten...", - "cleanup_found_assets": "Babeskopia egin {count, plural, one {duen baliabide bat aurkitu da} other {duten # baliabide aurkitu dira}}", - "cleanup_found_assets_with_size": "Babeskopia egin {count, plural, one {duen baliabide bat aurkitu da} other {duten # baliabide aurkitu dira}} ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloudeko partekatutako albumak eskaneotik kanpo geratzen dira", - "cleanup_no_assets_found": "Ez da aurkitu goiko irizpideekin bat datorren baliabiderik. Biltegiratze garbitzaileak zerbitzarian babeskopia duten baliabideak soilik kendu ditzake", - "cleanup_preview_title": "Kendu beharreko aktiboak ({count})", - "cleanup_step3_description": "Bilatu zure datarekin bat datozen babeskopiak dituzten baliabideak eta gorde ezarpenak.", - "cleanup_step4_summary": "{date} baino lehenagoko {count, plural, one {baliabide bat} other {# baliabide}} zure gailutik kentzeko. Argazkiak Immich aplikaziotik eskuragarri egongo dira.", - "cleanup_trash_hint": "Biltegiratzeko lekua guztiz berreskuratzeko, ireki sistemaren galeria aplikazioa eta hustu zakarrontzia", - "clear": "Garbitu", - "clear_all": "Denak garbitu", - "clear_all_recent_searches": "Garbitu azken bilaketa guztiak", - "clear_failed_count": "{count, plural, one {Garbitetak huts egin du} other {# garbiketek huts egin dute}}", - "clear_file_cache": "Garbitu fitxategien cachea", - "clear_message": "Mezua garbitu", - "clear_value": "Balioa garbitu", - "client_cert_import": "Inportatu", - "client_cert_import_success_msg": "Bezeroaren ziurtagiria inportatu da", - "client_cert_invalid_msg": "Ziurtagiri fitxategi baliogabea edo pasahitz okerra", - "client_cert_password_message": "Sartu ziurtagiri honen pasahitza", - "client_cert_password_title": "Ziurtagiriaren pasahitza", - "client_cert_remove_msg": "Bezeroaren ziurtagiria kendu da", - "client_cert_subtitle": "PKCS12 (.p12, .pfx) formatua soilik onartzen du. Ziurtagiriaren inportazioa/kentzea saioa hasi aurretik bakarrik dago erabilgarri", - "client_cert_title": "SSL bezero-ziurtagiria [ESPERIMENTALA]", - "close": "Itxi", - "collapse": "Taldekatu", - "collapse_all": "Dena tolestu", - "color": "Kolorea", - "command": "Agindua", - "command_palette_prompt": "Orriak, ekintzak edo komandoak azkar aurkitu", - "command_palette_to_close": "ixteko", - "command_palette_to_navigate": "sartzeko", - "command_palette_to_select": "hautatzeko", - "command_palette_to_show_all": "dena erakusteko", - "comment_deleted": "Iruzkina ezabatuta", - "comment_options": "Iruzkinaren aukerak", - "comments_and_likes": "Iruzkinak eta gustokoak", - "comments_are_disabled": "Iruzkinak desgaituta daude", - "common_create_new_album": "Album berria sortu", - "completed": "Burututa", - "configuration": "Konfigurazioa", - "confirm": "Onartu", - "confirm_admin_password": "Administrari pasahitza baieztatu", - "confirm_delete_face": "Ziur zaude {name}-(r)en aurpegia baliabidetik kendu nahi duzula?", - "confirm_delete_shared_link": "Ziur zaude partekatutako esteka hau ezabatu nahi duzula?", - "confirm_keep_this_delete_others": "Pilako gainerako baliabide guztiak kenduko dira baliabide hau izan ezik. Ziur zaude jarraitu nahi duzula?", - "confirm_new_pin_code": "PIK gako berria baieztatu", - "confirm_password": "Pasahitza baieztatu", - "confirm_tag_face": "Aurpegi hau {name} gisa etiketatu nahi duzu?", - "confirm_tag_face_unnamed": "Aurpegi hau etiketatu nahi duzu?", - "connected_device": "Konektatutako gailua", - "connected_to": "Hona konektatuta", - "contain": "Egokitu", - "context": "Kontextua", - "continue": "Jarraitu", - "control_bottom_app_bar_add_tags": "Etiketak gehitu", - "control_bottom_app_bar_delete_from_local": "Gailutik ezabatu", - "control_bottom_app_bar_edit_location": "Kokapena eguneratu", - "control_bottom_app_bar_edit_time": "Data eta denbora eguneratu", - "control_bottom_app_bar_trash_from_immich": "Zakarrontzira bidali", - "copied_image_to_clipboard": "Irudia arbelean kopiatu da .", - "copied_to_clipboard": "Kopiatu da arbelean!", - "copy_error": "Kopiatzerakoan errorea", - "copy_image": "Irudia kopiatu", - "copy_json": "JSON kopiatu", - "copy_link": "Esteka kopiatu", - "copy_link_to_clipboard": "Esteka arbelera kopiatu", - "copy_password": "Pasahitza kopiatu", - "copy_to_clipboard": "Arbelera kopiatu", - "country": "Herrialdea", - "cover": "Portada", - "covers": "Portada", - "create": "Sortu", - "create_album": "Albuma sortu", - "create_album_page_untitled": "Izengabea", - "create_api_key": "API Gakoa sortu", - "create_first_workflow": "Lehenengo lan-fluxua sortu", - "create_library": "Liburutegia sortu", - "create_link": "Esteka sortu", - "create_link_to_share": "Partekatzeko esteka sortu", - "create_link_to_share_description": "Utzi esteka duen edonori aukeratutako argazkiak ikusten", - "create_new": "BERRIA SORTU", - "create_new_person": "Pertsona berria sortu", - "create_new_person_hint": "Esleitu hautatutako baliabideak pertsona berri bati", - "create_new_user": "Sortu erabiltzaile berria", - "create_person": "Pertsona sortu", - "create_person_subtitle": "Gehitu izen bat hautatutako aurpegiari pertsona berria sortzeko eta etiketatzeko", - "create_shared_album_page_share_add_assets": "BALIABIDEAK GEHITU", - "create_shared_album_page_share_select_photos": "Argazkiak aukeratu", - "create_shared_link": "Sortu partekatutako esteka", - "create_tag": "Sortu etiketa", - "create_tag_description": "Sortu etiketa berri bat. Habiaratutako etiketetarako, idatzi etiketaren bide osoa, barrak barne.", - "create_user": "Sortu erabiltzailea", - "create_workflow": "Sortu lan-fluxua", - "created": "Sortuta", - "created_at": "Sortze-data", - "creating_linked_albums": "Lotutako albumak sortzen...", - "crop": "Ebaki", - "crop_aspect_ratio_free": "Askea", - "crop_aspect_ratio_original": "Originala", - "crop_aspect_ratio_square": "Laukia", - "current_device": "Gailu hau", - "current_pin_code": "Egungo PIN kodea", - "current_server_address": "Egungo zerbitzariaren helbidea", - "custom_date": "Data pertsonalizatua", - "custom_locale": "Lokalizazio pertsonalizatua", - "custom_locale_description": "Formateatu datak, orduak eta zenbakiak hautatutako hizkuntza eta eskualdearen arabera", - "cutoff_date_description": "Mantendu epe honetan sartzen diren argazkiakâ€Ļ", - "cutoff_day": "{count, plural, one {egun} other {egun}}", - "cutoff_year": "{count, plural, one {urte} other {urte}}", - "dark": "Iluna", - "dark_theme": "Gai iluna gaitu", - "date": "Data", - "date_after": "Data hau baino berriago", - "date_and_time": "Data eta denbora", - "date_before": "Data hau baino zaharrago", - "date_of_birth": "Jaiotze-data", - "date_of_birth_saved": "Jaiotze-data arrakastaz gordeta", - "date_range": "Data tartea", - "date_time_original": "Data/Debora Originala", - "day": "Eguna", - "days": "Egunak", - "deduplicate_all": "Denak desbikoiztu", - "default_quality_subtitle": "Partekatzerakoan erabiltzen den kalitatea. Luze sakatu partekatzeko botoia aldi bakoitzean aukeratzeko.", - "default_share_quality": "Partekatze-kalitate lehenetsia", - "delete": "Ezabatu", - "delete_action_confirmation_message": "Ziur zaude baliabide hau ezabatu nahi duzula? Ekintza honek baliabidea zerbitzariaren zakarrontzira eramango du eta lokalean ezabatu nahi duzun galdetuko dizu", - "delete_action_prompt": "{count} ezabatuta", - "delete_album": "Albuma ezabtu", - "delete_api_key_prompt": "Ziur zaude API gako hau ezabatu nahi duzula?", - "delete_dialog_alert": "Elementu hauek betiko ezabatuko dira Immich-etik eta zure gailutik", - "delete_dialog_alert_local": "Elementu hauek betiko kenduko dira zure gailutik, baina oraindik eskuragarri egongo dira Immich zerbitzarian", - "delete_dialog_alert_local_non_backed_up": "Elementu batzuen babeskopia ez dago Immich-en eta betiko kenduko dira zure gailutik", - "delete_dialog_title": "Behin betiko ezabatu", - "delete_duplicates_confirmation": "Ziur zaude bikoiztu hauek behin betiko ezabatu nahi dituzula?", - "delete_face": "Aurpegia ezabatu", - "delete_key": "Gakoa ezabatu", - "delete_library": "Liburutegia ezabatu", - "delete_link": "Esteka ezabatu", - "delete_local_action_prompt": "{count} gailutik ezabatuta", - "delete_local_dialog_ok_backed_up_only": "Ezabatu soilik segurtasun kopia duten baliabideak", - "delete_local_dialog_ok_force": "Hala ere ezabatu", - "delete_others": "Besteak ezabatu", - "delete_permanently": "Behin betiko ezabatu", - "delete_permanently_action_prompt": "{count} behin betiko ezabatuta", - "delete_shared_link": "Partekatutako esteka ezabatu", - "delete_shared_link_dialog_title": "Partekatutako esteka ezabatu", - "delete_tag": "Etiketa ezabatu", - "delete_tag_confirmation_prompt": "Ziur zaude {tagName} etiketa ezabatu nahi duzula?", - "delete_user": "Erabiltzailea ezabatu", - "deleted_shared_link": "Partekatutako esteka ezabatuta", - "description": "Deskribapena", - "deselect_all": "Deshautatu guztiak", - "details": "Xehetasunak", - "direction": "Norabidea", - "disable": "Desgaitu", - "disabled": "Desgaituta", - "discord": "Discord", - "discover": "Aurkitu", - "discovered_devices": "Aurkitutako gailuak", - "dismiss_all_errors": "Errore guztiak baztertu", - "display_options": "Bistaratzeko aukerak", - "display_order": "Bistaratzeko ordena", - "display_original_photos": "Erakutsi jatorrizko argazkiak", - "display_original_photos_setting_description": "Irudi bat ikustean, jatorrizko argazkia hobesten da miniaturaren ordez, jatorrizkoa web bateragarria bada. Baliteke argazkiak kargatzeko denbora gehiago behar izatea.", - "do_not_show_again": "Ez erakutsi mezu hau berriz", - "documentation": "Dokumentazioa", - "done": "Amaitu", - "download": "Deskargatu", - "download_canceled": "Deskarga ezeztatuta", - "download_complete": "Deskarga burututa", - "download_enqueue": "Deskarga ilaran", - "download_failed": "Akatsa deskargatzerakoan", - "download_finished": "Deskarga burututa", - "download_include_embedded_motion_videos": "Txertaturiko bideoak", - "download_include_embedded_motion_videos_description": "Sartu argazki bizidunetan txertatutako bideoak fitxategi bereizi gisa", - "download_notfound": "Ez da deskarga aurkitu", - "download_original": "Jatorrizkoa deskargatu", - "download_paused": "Deskarga geldituta", - "download_settings": "Deskargak", - "download_settings_description": "Kudeatu baliabideak deskargatzearekin lotutako ezarpenak", - "download_waiting_to_retry": "Berriro saiatzeko zain", - "downloading": "Deskargatzen", - "downloading_asset_filename": "{filename} baliabidea deskargatzen", - "downloading_from_icloud": "iCloud-etik deskargatzen", - "downloading_media": "Baliabideak deskargatzen", - "drag_to_reorder": "Arrastatu berrantolatzeko", - "drop_files_to_upload": "Jaregin fitxategiak edonon kargatzeko", - "duplicate": "Kopia egin", - "duplicate_workflow": "Lan-fluxuaren kopia sortu", - "duplicates": "Kopiak", - "duplicates_description": "Ebatzi talde bakoitza, bikoiztuak dauden adieraziz.", - "duration": "Iraupena", - "edit": "Editatu", - "edit_album": "Albuma editatu", - "edit_avatar": "Abatarra editatu", - "edit_birthday": "Urtebetetze-eguna editatu", - "edit_date": "Data editatu", - "edit_date_and_time": "Data eta ordua editatu", - "edit_date_and_time_action_prompt": "{count, plural, one {Datu eta ordua eguneratu da} other {# datu eta ordu eguneratu dira}}", - "edit_date_and_time_by_offset": "Aldatu data desplazamendu bat erabiliz", - "edit_description": "Deskribapena editatu", - "edit_exclusion_pattern": "Editatu bazterketa eredua", - "edit_faces": "Aurpegiak kudeatu", - "edit_key": "Gakoa editatu", - "edit_link": "Esteka editatu", - "edit_location": "Kokapena editatu", - "edit_location_action_prompt": "{count, plural, one {Kokapena eguneratuta} other {# kokapen eguneratuta}}", - "edit_location_dialog_title": "Kokapena", - "edit_name": "Izena editatu", - "edit_people": "Pertsonak editatu", - "edit_tag": "Etiketa editatu", - "edit_title": "Izenburua editatu", - "edit_user": "Erabiltzailea editatu", - "edit_workflow": "Lan-fluxua editatu", - "editor": "Editorea", - "editor_discard_edits_confirm": "Aldaketak baztertu", - "editor_discard_edits_prompt": "Gorde gabeko aldaketak dituzu. Ziur baztertu nahi dituzula?", - "editor_discard_edits_title": "Aldaketak baztertu?", - "editor_edits_applied_error": "Ezin izan dira aldaketak aplikatu", - "editor_edits_applied_success": "Aldaketak arrakastaz aplikatuak", - "editor_flip_horizontal": "Horizontalki islatu", - "editor_flip_vertical": "Bertikalki islatu", - "editor_handle_corner": "{corner, select, top_left {Goiko ezkerreko} top_right {Goiko eskuineko} bottom_left {Beheko ezkerreko} bottom_right {Beheko eskuineko} other {Izkinako} } kontrolatzailea", - "editor_handle_edge": "{edge, select, top {Goiko} bottom {Beheko} left {Ezkerreko} right {Eskuineko} other {Ertzeko} } kontrolatzailea", - "editor_orientation": "Orientazioa", - "editor_reset_all_changes": "Aldaketak berrezarri", - "editor_rotate_left": "Biratu 90Âē erlojuaren kontra", - "editor_rotate_right": "Biratu 90Âē erlojuaren alde", - "email": "E-mail", - "email_notifications": "E-mail jakinarazpenak", - "empty_folder": "Karpeta hau hutsik dago", - "empty_trash": "Zakarrontzia hustu", - "empty_trash_confirmation": "Ziur zaude zakarrontzia hustu nahi duzula? Horrela, zakarrontziko baliabide guztiak behin betiko ezabatuko dira Immich-etik.\nEzin duzu ekintza hau desegin!", - "enable": "Gaitu", - "enable_backup": "Babeskopia gaitu", - "enable_biometric_auth_description": "Sartu zure PIN kodea autentifikazio biometrikoa gaitzeko", - "enabled": "Gaituta", - "end_date": "Amaiera data", - "enqueued": "Ilaran gehituta", - "enter_wifi_name": "Sartu Wi-Fi izena", - "enter_your_pin_code": "Sartu zure PIN kodea", - "enter_your_pin_code_subtitle": "Sartu zure PIN kodea blokeatutako karpetara sartzeko", - "error": "Akatsa", - "error_delete_face": "Ezin izan da aurpegia baliabidetik ezabatu", - "error_loading_albums": "Ezin izan dira albumak kargatu", - "error_loading_image": "Ezin izan da argazkia kargatu", - "error_loading_partners": "Ezin izan dira kideak kargatu: {error}", - "error_retrieving_asset_information": "Ezin izan da baliabidearen informazioa berreskuratu", - "error_saving_image": "Akatsa: {error}", - "error_tag_face_bounding_box": "Ezin izan da irudia etiketatu - ezin dira marko koordenatuak lortu", - "error_title": "Akatsa - Zerbaitek huts egin du", - "error_while_navigating": "Ezin izan da baliabidera nabigatu", - "errors": { - "cannot_navigate_next_asset": "Ezin duzu hurrengo baliabidera nabigatu", - "cannot_navigate_previous_asset": "Ezin duzu aurreko baliabidera nabigatu", - "cant_apply_changes": "Ezin dira aldaketak aplikatu", - "cant_change_activity": "Ezin izan da jarduera {enabled, select, true {desaktibatu} other {aktibatu}}", - "cant_change_asset_favorite": "Ezin izan da gogokoa aldatu baliabide honetan", - "cant_change_metadata_assets_count": "Ezin izan da {count, plural, one {baliabidearen} other {# baliabideren}} metadatuak aldatu", - "cant_get_faces": "Ezin izan dira aurpegiak jaso", - "cant_get_number_of_comments": "Ezin izan da iruzkin kopurua jaso", - "cant_search_people": "Ezin izan dira pertsonak bilatu", - "cant_search_places": "Ezin izan dira tokiak bilatu", - "error_adding_assets_to_album": "Ezin izan dira baliabideak albumean sartu", - "error_adding_users_to_album": "Ezin izan dira erabiltzaileak albumean sartu", - "error_deleting_shared_user": "Ezin izan da partekatutako erabiltzailea ezabatu", - "error_downloading": "Ezin izan da {filename} deskargatu", - "error_hiding_buy_button": "Ezin izan da erosi botoia ezkutatu", - "error_removing_assets_from_album": "Errore bat gertatu da albumetik baliabideak ezabatzean; ikus kontsola xehetasunetarako", - "error_selecting_all_assets": "Ezin izan dira baliabide guztiak hautatu", - "exclusion_pattern_already_exists": "Bazterketa eradu hau iada existitzen da.", - "failed_to_create_album": "Ezin izan da albuma sortu", - "failed_to_create_shared_link": "Ezin izan da partekatutako esteka sortu", - "failed_to_edit_shared_link": "Ezin izan da partekatutako esteka editatu", - "failed_to_get_people": "Ezin izan dira pertsonak jaso", - "failed_to_keep_this_delete_others": "Ezin izan da baliabide hau gorde eta besteak ezabatu", - "failed_to_load_asset": "Ezin izan da baliabidea kargatu", - "failed_to_load_assets": "Ezin izan dira baliabideak kargatu", - "failed_to_load_notifications": "Ezin izan dira jakinarazpenak jaso", - "failed_to_load_people": "Ezin izan dira pertsonak kargatu", - "failed_to_remove_product_key": "Ezin izan da produktu gakoa ezabatu", - "failed_to_reset_pin_code": "Ezin izan da PIN gakoa berrezarri", - "failed_to_stack_assets": "Ezin izan dira baliabideak multzokatu", - "failed_to_tag_assets": "Ezin izan dira baliabideak etiketatu", - "failed_to_unstack_assets": "Ezin izan dira baliabideak desmultzokatu", - "failed_to_update_notification_status": "Errore bat gertatu da jakinarazpen-egoera eguneratzean", - "incorrect_email_or_password": "Pasahitz edo e-mail okerra", - "library_folder_already_exists": "Inportazio bide hau jada existitzen da.", - "page_not_found": "Ez da orria aurkitu", - "paths_validation_failed": "{paths, plural, one {Bideak} other {# bidek}} baliozkotzean huts egin {paths, plural, one {du} other {dute}}", - "profile_picture_transparent_pixels": "Profileko irudiek ezin du pixel gardenik izan. Mesedez, handitu eta/edo mugitu irudia.", - "quota_higher_than_disk_size": "Diskoaren tamaina baino kuota handiagoa ezarri da", - "something_went_wrong": "Zerbait gaizki joan da", - "unable_to_add_album_users": "Ezin izan dira erabiltzaileak albumean gehitu", - "unable_to_add_assets_to_shared_link": "Ezin dira baliabideak partekatutako estekan gehitu", - "unable_to_add_comment": "Ezin izan da iruzkina gehitu", - "unable_to_add_exclusion_pattern": "Ezin izan da bazterketa eredua gehitu", - "unable_to_add_partners": "Ezin izan dira kideak gehitu", - "unable_to_add_remove_archive": "Ezin izan da baliabidea {archived, select, true {artxibategitik kendu} other {artxibatu}}", - "unable_to_add_remove_favorites": "Ezin izan da baliabidea {favorite, select, true {gogokoetan gehitu} other {gogokoetatik kendu}}", - "unable_to_archive_unarchive": "Ezin izan da {archived, select, true {artxibatu} other {artxibotik kendu}}", - "unable_to_change_album_user_role": "Ezin izan da albumaren erabiltzailearen rola aldatu", - "unable_to_change_date": "Ezin izan da data aldatu", - "unable_to_change_description": "Ezin da deskribapena eguneratu", - "unable_to_change_favorite": "Ezin da baliabidearen gogoko egoera aldatu", - "unable_to_change_location": "Ezin izan da kokapena aldatu", - "unable_to_change_password": "Ezin izan da pasahitza eguneratu", - "unable_to_change_visibility": "Ezin izan da {count, plural, one {pertsonaren} other {# pertsonen}} ikusgarritasuna aldatu", - "unable_to_complete_oauth_login": "Ezin izan da OAuth saioa hasi", - "unable_to_connect": "Akatsa konexioan", - "unable_to_copy_to_clipboard": "Ezin izan da arbelera kopiatu, ziurtatu webgunea https bitartez atzitzen duzula", - "unable_to_create": "Ezin izan da lan-fluxua sortu", - "unable_to_create_admin_account": "Ezin izan da administrari kontua sortu", - "unable_to_create_api_key": "Ezin izan da API gako berri bat sortu", - "unable_to_create_library": "Ezin izan da liburutegia sortu", - "unable_to_create_user": "Ezin izan da erabiltzailea sortu", - "unable_to_delete_album": "Ezin da albuma ezabatu", - "unable_to_delete_asset": "Ezin izan da baliabidea ezabatu", - "unable_to_delete_assets": "Akatsa baliabideak ezabatzerakoan", - "unable_to_delete_exclusion_pattern": "Ezin izan da bazterketa eradua ezabatu", - "unable_to_delete_shared_link": "Ezin izan da partekatutako esteka ezabatu", - "unable_to_delete_user": "Ezin izan da erabiltzailea ezabatu", - "unable_to_delete_workflow": "Ezin izan da lan-fluxua ezabatu", - "unable_to_download_files": "Ezin izan da fitxategirik kargatu", - "unable_to_edit_exclusion_pattern": "Ezin izan da bazterketa eredua editatu", - "unable_to_empty_trash": "Ezin izan da zakarrontzia hustu", - "unable_to_enter_fullscreen": "Ezin izan da pantaila osoko modua gaitu", - "unable_to_exit_fullscreen": "Ezin izan da pantaila osoko modua desgaitu", - "unable_to_get_comments_number": "Ezin izan da iruzkin kopurua jaso", - "unable_to_get_shared_link": "Ezin izan da partekatutako esteka jaso", - "unable_to_hide_person": "Ezin izan da pertsona ezkutatu", - "unable_to_link_motion_video": "Ezin izan da bideo biziduna lotu", - "unable_to_link_oauth_account": "Ezin izan da OAuth kontua lotu", - "unable_to_log_out_all_devices": "Ezin izan da gailu guztietatik saioa itxi", - "unable_to_log_out_device": "Ezin izan da gailutik saioa itxi", - "unable_to_login_with_oauth": "Ezin izan da OAuth bitartez saioa hasi", - "unable_to_play_video": "Ezin izan da bideoa erreproduzitu", - "unable_to_reassign_assets_existing_person": "Ezin dira baliabideak {name, select, null {lehendik dagoen pertsona bati} other {{name}-(r)i}} berresleitu", - "unable_to_reassign_assets_new_person": "Ezin dira baliabideak pertsona berri bateri berresleitu", - "unable_to_refresh_user": "Ezin izan da erabiltzailea freskatu", - "unable_to_remove_album_users": "Ezin izan dira erabiltzaileak albumetik kendu", - "unable_to_remove_api_key": "Ezin izan da API gakoa kendu", - "unable_to_remove_assets_from_shared_link": "Ezin izan da baliabideak partekatutako estekatik ezabatu", - "unable_to_remove_library": "Ezin izan da liburutegia ezabatu", - "unable_to_remove_partner": "Ezin izan da kidea ezabatu", - "unable_to_remove_reaction": "Ezin izan da erantzuna kendu", - "unable_to_reset_password": "Ezin izan da pasahitza berrezarri", - "unable_to_reset_pin_code": "Ezin izan da PIN gakoa berrezarri", - "unable_to_resolve_duplicate": "Ezin izan dira kopiak ebatzi", - "unable_to_restore_assets": "Ezin izan dira baliabideak berreskuratu", - "unable_to_restore_trash": "Ezin izan da zaborrontzia berreskuratu", - "unable_to_restore_user": "Ezin izan da erabiltzailea berreskuratu", - "unable_to_save_album": "Ezin izan da albuma gorde", - "unable_to_save_api_key": "Ezin izan da API gakoa gorde", - "unable_to_save_date_of_birth": "Ezin izan da jaiotze-data gorde", - "unable_to_save_name": "Ezin izan da izena gorde", - "unable_to_save_profile": "Ezin izan da profila gorde", - "unable_to_save_settings": "Ezin izan dira ezarpenak gorde", - "unable_to_scan_libraries": "Ezin izan dira liburutegiak eskaneatu", - "unable_to_scan_library": "Ezin izan da liburutegia eskaneatu", - "unable_to_set_feature_photo": "Ezin izan da argazki nagusia ezarri", - "unable_to_set_profile_picture": "Ezin izan da profil-argazkia ezarri", - "unable_to_set_rating": "Ezin izan da puntuazioa ezarri", - "unable_to_submit_job": "Ezin izan da ataza gehitu", - "unable_to_trash_asset": "Ezin izan da baliabidea zakarrontzira eraman", - "unable_to_unlink_account": "Ezin izan da kontua deslotu", - "unable_to_unlink_motion_video": "Ezin izan da bideo biziduna deslotu", - "unable_to_update_album_cover": "Ezin izan da albumaren portada eguneratu", - "unable_to_update_album_info": "Ezin izan da alburaren informazioa eguneratu", - "unable_to_update_library": "Ezin izan da liburutegia eguneratu", - "unable_to_update_location": "Ezin izan da kokapena eguneratu", - "unable_to_update_settings": "Ezin izan dira ezarpenak eguneratu", - "unable_to_update_timeline_display_status": "Ezin izan da denbora-lerroaren bistaratze egoera eguneratu", - "unable_to_update_user": "Ezin izan da erabiltzailea eguneratu", - "unable_to_update_workflow": "Ezin izan da lan-fluxua eguneratu", - "unable_to_upload_file": "Ezin izan da fitxategia gora kargatu" - }, - "errors_text": "Akatsak", - "exclusion_pattern": "Bazterketa eredua", - "exif": "Exif", - "exif_bottom_sheet_description": "Deskribapena ezarriâ€Ļ", - "exif_bottom_sheet_description_error": "Ezin izan da deskribapena eguneratu", - "exif_bottom_sheet_no_description": "Deskribapenik ez", - "exit_slideshow": "Aurkezpena itxi", - "expand": "Zabaldu", - "expand_all": "Denak hedatu", - "expire_after": "Iraungitze epea", - "expired": "Iraungita", - "expires_date": "{date} -(a)n iraungiko da", - "explore": "Arakatu", - "explorer": "Arakatzailea", - "export": "Esportatu", - "export_as_json": "JSON formatuan esportatu", - "export_database": "Datu-basea esportatu", - "export_database_description": "SQLite datu-basea esportatu", - "exposure_time": "Espozisio denbora", - "extension": "Hedapena", - "external": "Kanpokoa", - "external_libraries": "Kanpoko liburutegiak", - "external_network": "Kanpoko sarea", - "external_network_sheet_info": "Wi-Fi sare hobetsian ez dagoenean, aplikazioak zerbitzarira konektatuko da beheko URLetatik lehenengoaren bidez, goitik behera hasita", - "f_number": "Obertura", - "face_unassigned": "Ezarri gabea", - "failed": "Akatsduna", - "failed_count": "Akatsdunak: {count}", - "failed_to_authenticate": "Ezin izan da autentifikatu", - "failed_to_delete_file": "Ezin izan da fitxategia ezabatu", - "failed_to_load_assets": "Ezin izan dira baliabideak kargatu", - "failed_to_load_folder": "Ezin izan da karpeta jaso", - "favorite": "Gogokoa", - "favorite_action_prompt": "{count} gogokoetara gehituta", - "favorite_or_unfavorite_photo": "Argazkia gogoko edo ez ezarri", - "favorites": "Gogokoenak", - "feature_photo_updated": "Argazki nagusia eguneratuta", - "features": "Ezaugarriak", - "features_setting_description": "Aplikazioaren ezaugarriak kudeatu", - "file_name_or_extension": "Fitxategia edo luzapena", - "file_name_text": "Fitxategiaren izena", - "file_size": "Fitxategiaren tamaina", - "filename": "Fitxategia", - "filetype": "Fitxategi mota", - "filter": "Iragazkia", - "filter_people": "Pertsonak iragazi", - "filter_places": "Tokiak iragazi", - "filter_tags": "Etiketak iragazi", - "filters": "Iragazkiak", + "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} dagoeneko albumean dago", "first": "Lehenengo ÂĢLehenikÂģ", - "fix_incorrect_match": "Konpondu bat-etortze okerra", - "focal_length": "Foku-distantzia", - "folder": "Karpeta", - "folder_not_found": "Ez da karpeta aurkitu", - "folders": "Karpetak", - "folders_feature_description": "Fitxategi-sistemako argazki eta bideoen karpeta-ikuspegian arakatzea", - "forgot_pin_code_question": "PIN kodea ahaztu al duzu?", - "forward": "Aurreruntz", - "free_up_space": "Biltegiratze garbitzailea", - "free_up_space_description": "Mugitu babeskopia eginda dituzun argazkiak eta bideoak zure gailuko zakarrontzira. Zerbitzarian dagoen kopia ukitu gabe geratuko da.", - "free_up_space_settings_subtitle": "Gailuaren biltegiratzea garbitu", - "full_path": "Bide osoa: {path}", - "full_path_or_folder": "Bide osoa edo karpeta", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Ezaugarri honek Google-en kanpoko baliabideak kargatzen ditu funtziona dezan.", - "general": "General", - "geolocation_instruction_location": "Egin klik GPS koordenatuak dituen baliabide batean bere kokapena erabiltzeko, edo hautatu kokapen bat zuzenean mapatik", - "get_help": "Laguntza eskatu", - "get_people_error": "Ezin izan da pertsonarik jaso", - "get_wifiname_error": "Ezin izan da Wi-Fi izena lortu. Ziurtatu beharrezko baimenak eman dituzula eta Wi-Fi sare batera konektatuta zaudela", - "getting_started": "Hasi gaitezen", - "go_back": "Atzera bueltatu", - "go_to_folder": "Karpetara joan", - "go_to_search": "Bilaketara joan", "gps": "GPS", "gps_missing": "Ez dago GPS", - "grant_permission": "Baimendu", - "group_albums_by": "Albumak honen arabera multzokatu...", - "group_country": "Herrialdearen arabera multzokatu", - "group_no": "Multzokatzerik ez", - "group_owner": "Jabearen arabera multzokatu", - "group_places_by": "Lekuan honen arabera multzokatu...", - "group_year": "Urtearen arabera multzokatu", - "haptic_feedback_title": "Ukipen-feedbacka", - "has_quota": "Ezarritako kuota", - "hash_asset": "Baliabidea hasheatu", - "hashed_assets": "Hasheatutako baliabideak", - "hashing": "Hasha sortzen", - "header_settings_add_header_tip": "Goiburua gehitu", - "header_settings_field_validator_msg": "Balioa ezin da hutsik egon", - "header_settings_header_name_input": "Goiburu izena", - "header_settings_header_value_input": "Goiburu balioa", - "headers_settings_tile_title": "Proxy-goiburu pertsonalizatuak", - "height": "Altuera", - "hi_user": "Kaixo {name} ({email})", - "hide_all_people": "Pertsona guztiak ezkutatu", - "hide_gallery": "Galeria ezkutatu", - "hide_named_person": "{name} pertsona ezkutatu", - "hide_password": "Pasahitza ezkutatu", - "hide_person": "Pertsona ezkutatu", - "hide_schema": "Eskema ezkutatu", - "hide_text_recognition": "Testu-ezagutza ezkutatu", - "hide_unnamed_people": "Izengabeko pertsonak ezkutatu", - "home_page_building_timeline": "Denbora-lerroa sortzen", - "host": "Ostalaria", - "hour": "Ordua", - "hours": "Orduak", - "id": "ID", - "idle": "Jarduerarik gabe", - "image": "Irudia", - "image_alt_text_date": "{isVideo, select, true {Bideoa} other {Argazkia}} {date}-n aterata", - "image_alt_text_date_1_person": "{isVideo, select, true {Bideoa} other {Irudia}} {person1}-(r)ekin hartua {date}-(e)an", - "image_alt_text_date_2_people": "{isVideo, select, true {Bideoa} other {Irudia}} {person1} eta {person2}-(r)ekin hartua {date}-(e)an", - "image_alt_text_date_3_people": "{isVideo, select, true {Bideoa} other {Irudia}} {person1}, {person2} eta {person3}-(r)ekin hartua {date}-(e)an", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Bideoa} other {Irudia}} {person1}, {person2} eta beste {additionalCount, number}-(r)ekin hartua {date}-(e)an", - "image_alt_text_date_place": "{isVideo, select, true {Bideoa} other {Irudia}} {city}-(e)n hartua, {country}-(e)n, {date}-(e)an", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Bideoa} other {Irudia}} {city}-(e)n hartua, {country}-(e)n, {person1}-(r)ekin {date}-(e)an", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Bideoa} other {Irudia}} {city}-(e)n hartua, {country}-(e)n, {person1} eta {person2}-(r)ekin {date}-(e)an", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Bideoa} other {Irudia}} {city}-(e)n hartua, {country}-(e)n, {person1}, {person2} eta {person3}-(r)ekin {date}-(e)an", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Bideoa} other {Irudia}} {city}, {country}-(e)n hartua {person1}, {person2} eta beste {additionalCount, number}-(r)ekin hartua {date}-(e)an", - "immich_logo": "Immich-en logoa", - "immich_web_interface": "Immich Web interfazea", - "import_from_json": "JSON-etik inportatu", - "import_path": "Inportatzeko bidea", - "in_albums": "{count, plural, one {Album batean} other {# albumetan}}", - "in_archive": "Artxibatuta", - "in_year": "{year}-(e)an", - "in_year_selector": "Urtea", - "include_archived": "Artxibatuta barne hartu", - "include_shared_albums": "Partekatutako albumak barne hartu", - "include_shared_partner_assets": "Sartu kideek partekatutako baliabideak", - "individual_share": "Share indibiduala", - "individual_shares": "Share indibidualak", - "info": "Informazioa", - "integrity_checks": "Osotasun ikuskapenak", - "interval": { - "day_at_onepm": "Egunero 13:00etan", - "hours": "{hours, plural, one {Orduro} other {# orduro}}", - "night_at_midnight": "Gauero gauerdian", - "night_at_twoam": "Gauero goizaldeko 2:00etan" - }, - "invalid_date": "Data okerra", - "invalid_date_format": "Datu formatu okerra", - "invite_people": "Pertsonak gonbidatu", - "invite_to_album": "Albumera gonbidatu", - "iso": "ISO-a", - "items_count": "{count, plural, one {Elementu bat} other {# elementu}}", - "jobs": "Atazak", - "keep": "Mantendu", - "keep_albums": "Albumak mantendu", - "keep_albums_count": "{count, plural, one {Album bat} other {# albums}} mantentzen", - "keep_all": "Denak mantendu", - "keep_description": "Aukeratu zer geratzen den zure gailuan biltegiratzea garbitzen duzunean.", - "keep_favorites": "Gogokoak mantendu", - "keep_on_device": "Gailuan mantendu", - "keep_on_device_hint": "Hautatu gailu honetan mantendu beharreko elementuak", - "keep_this_delete_others": "Hau mantendu, besteak ezabatu", - "keeping": "Mantentzeko: {items}", - "kept_this_deleted_others": "Baliabide hau mantendu da eta {count, plural, one {baliabide bat ezabatu da} other {# baliabide ezabatu dira}}", - "keyboard_shortcuts": "Lasterbideak", - "language": "Hizkuntza", - "language_no_results_subtitle": "Saiatu bilaketa-terminoa doitzen", - "language_no_results_title": "Ez da hizkuntzarik aurkitu", - "language_search_hint": "Hizkuntzak bilatu...", - "language_setting_description": "Aukeratu zure gogoko hizkuntza", - "large_files": "Fitxategi handiak", "last": "Azkena", - "last_months": "{count, plural, one {Azken hilabetea} other {Azken # hilabeteak}}", - "last_seen": "Azkenez ikusita", - "latest_version": "Azken bertsioa", - "latitude": "Latitudea", - "leave": "Bertan behera utzi", - "leave_album": "Albuma utzi", - "lens_model": "Objektibo modeloa", - "less": "Gutxiago", - "let_others_respond": "Baimendu beste erabiltzaileak erantzutea", - "level": "Maila", - "library": "Liburutegia", - "library_add_folder": "Karpeta gehitu", - "library_edit_folder": "Karpeta editatu", - "library_page_sort_asset_count": "Baliabide kopurua", - "library_page_sort_created": "Duela gutxi sortuta", - "library_page_sort_last_modified": "Duela gutxi aldatuta", - "library_page_sort_title": "Izenburuaren arabera", - "licenses": "Lizentziak", - "light": "Argia", - "light_theme": "Modu argia gaitu", "like": "Gustoko", - "like_deleted": "Gustokoa ezabatuta", - "link": "Esteka", - "link_motion_video": "Estekatu bideo biziduna", - "link_to_docs": "Informazio gehiago lortzeko, jo dokumentaziora.", - "link_to_oauth": "OAuth-era esteka", - "linked_oauth_account": "OAuth kontua lotuta", - "list": "Zerrenda", - "live": "Zuzenean", - "load_more": "Gehiago kargatu", - "loading": "Kargatzen", - "loading_search_results_failed": "Errore bat gertatu da bilaketa-emaitzak kargatzean", - "local": "Lokal", - "local_asset_cast_failed": "Ezin da zerbitzarian kargatu ez den baliabide bat igorri", - "local_assets": "Bertako baliabideak", - "local_id": "Tokiko ID-a", - "local_media_summary": "Bertako multimedien laburpena", - "local_network": "Bertako sarea", - "local_network_sheet_info": "Aplikazioa URL honen bidez konektatuko da zerbitzariarekin zehaztutako Wi-Fi sarea erabiltzean", - "location": "Kokapena", - "location_permission": "Kokapen baimena", - "location_permission_content": "Aldaketa automatikoa erabiltzeko, Immich-ek kokapen-baimen zehatza behar du, uneko Wi-Fi sarearen izena irakur dezan", - "location_picker_choose_on_map": "Mapan aukeratu", - "location_picker_latitude_error": "Baliozko latitude bat sartu", - "location_picker_latitude_hint": "Zure latitudea sartu hemen", - "location_picker_longitude_error": "Baliozko longitude bat sartu", - "location_picker_longitude_hint": "Zure longitudea sartu hemen", - "lock": "Blokeatu", - "locked_folder": "Giltzapeko karpeta", - "log_detail_title": "Erregistroen zehestasuna", - "log_out": "Saioa itxi", - "log_out_all_devices": "Gailu guztietatik saioa itxi", - "logged_in_as": "Saioa {user} bezala hasita", - "logged_out_all_devices": "Gailu guztietatik saioa itxi da", - "logged_out_device": "Gailua deskonektatua", - "login": "Saioa hasi", - "login_disabled": "Saioa hastea desgaitu egin da", - "login_form_api_exception": "API akatsa. Egiaztatu zerbitzariaren URLa eta saiatu berriro.", - "login_form_email_hint": "zure@emaila.com", - "login_form_endpoint_hint": "http://zerbitzariaren-ip-a:portua", - "login_form_endpoint_url": "Zerbitzariaren URL endpoint-a", - "login_form_err_invalid_email": "E-mail okerra", - "login_form_err_invalid_url": "URL okerra", - "login_form_err_leading_whitespace": "Zuriunea hasieran", - "login_form_err_trailing_whitespace": "Zuriunea amaieran", - "login_form_failed_get_oauth_server_config": "Errore bat gertatu da OAuth erabiliz saioa hasterakoan, egiaztatu zerbitzariaren URLa", - "login_form_failed_get_oauth_server_disable": "OAuth ezaugarria ez dago erabilgarri zerbitzari honetan", - "login_form_failed_login": "Errore bat gertatu da saioa hastean, egiaztatu zerbitzariaren URLa, e-maila eta pasahitza", - "login_form_handshake_exception": "Handshake salbuespen bat egon da zerbitzariarekin. Gaitu autosinatutako ziurtagiriak ezarpenetan, autosinatutako ziurtagiri bat erabiltzen ari bazara.", - "login_form_password_hint": "pasahitza", - "login_form_server_empty": "Zerbitzari URL bat sartu", - "login_form_server_error": "Ezin izan da zerbitzarira konektatu", - "login_has_been_disabled": "Saioa hastea desgaitu da", - "login_password_changed_error": "Errore bat gertatu da pasahitza eguneratzean", - "login_password_changed_success": "Pasahitza arrakastaz eguneratu", - "logout_all_device_confirmation": "Ziur zaude gailu guztietan saioa amaitu nahi duzula?", - "logout_this_device_confirmation": "Ziur zaude gailu honetan saioa amaitu nahi duzula?", - "logs": "Erregistroak", - "longitude": "Longitudea", - "look": "Itxura", - "loop_videos": "Bideoak errepikatu", - "loop_videos_description": "Gaitu bideoak xehetasun-ikuslean automatikoki begiztatzeko.", - "main_branch_warning": "Garapen-bertsio bat erabiltzen ari zara; kaleratze-bertsio bat erabiltzea gomendatzen dugu!", - "main_menu": "Menu nagusia", - "maintenance_action_restore": "Datu-basea berreskuratzen", - "maintenance_description": "Immich mantentze-moduan jarri da.", - "maintenance_end": "Mantentze modua itxi", - "maintenance_logged_in_as": "Oraingoz saioa {user} bezala hasita", - "maintenance_restore_from_backup": "Segurtasun-kopiatik berrezarri", - "maintenance_restore_library": "Zure liburutegia berrezarri", - "maintenance_restore_library_confirm": "Hau zuzena badirudi, jarraitu babeskopiatik berreskuratzen!", - "maintenance_restore_library_description": "Datu-basea berreskuratzen", - "maintenance_restore_library_folder_has_files": "{folder} karpetak {count, plural, one {karpeta bat du} other {# karpeta du}}", - "maintenance_restore_library_folder_no_files": "{folder} karpetari fitxategiak falta zaizkio!", - "maintenance_restore_library_folder_pass": "Irakurgarria eta idazgarria", - "maintenance_restore_library_folder_read_fail": "irakurtezina", - "maintenance_restore_library_folder_write_fail": "idatziezina", - "maintenance_restore_library_hint_missing_files": "Baliteke fitxategi garrantzitsuak falta izatea", - "maintenance_restore_library_hint_regenerate_later": "Geroago birsor ditzakezu ezarpenetan", - "maintenance_restore_library_hint_storage_template_missing_files": "Biltegiratze txantiloia erabiltzen? Fitxategiren bat falta zaizu", - "maintenance_restore_library_loading": "Osotasun-egiaztapenak eta heuristikoak kargatzenâ€Ļ", - "maintenance_task_backup": "Lehendik dagoen datu-basearen babeskopia bat sortzenâ€Ļ", - "maintenance_task_migrations": "Datu-basearen migrazioak exekutatzen", - "maintenance_task_restore": "Aukeratutako babeskopia berreskuratzenâ€Ļ", - "maintenance_task_rollback": "Berreskuraketak huts egin du. Berrezartzeko puntura atzera egitenâ€Ļ", - "maintenance_title": "Aldi baterako ez-erabilgarri", - "make": "Marka", "manage_geolocation": "Kudeatu kokapena", - "manage_media_access_rationale": "Baimen hau beharrezkoa da baliabideak behar bezala zaborrontzira eramateko eta bertatik berreskuratzeako.", - "manage_media_access_settings": "Ezarpenak zabaldu", - "manage_media_access_subtitle": "Baimendu Immich aplikazioari multimedia fitxategiak kudeatu eta mugitzea.", - "manage_media_access_title": "Multimedia sarbidea kudeatu", - "manage_sharing_with_partners": "Kudeatu kideekin partekatzea", - "manage_the_app_settings": "Aplikazio ezarpenak kudeatu", - "manage_your_account": "Zure kontua kudeatu", - "manage_your_api_keys": "Zure API gakoak kudeatu", - "manage_your_devices": "Kudeatu konektatutako gailuak", - "manage_your_oauth_connection": "Kudeatu zure OAuth konexioa", - "map": "Mapa", - "map_cannot_get_user_location": "Ezin izan da erabiltzailearen kokapena jaso", - "map_location_picker_page_use_location": "Kokapen hau erabili", - "map_location_service_disabled_content": "Kokapen-zerbitzua gaituta egon behar da zure uneko kokapeneko baliabideak bistaratzeko. Orain gaitu?", - "map_location_service_disabled_title": "Kokapen zerbitzua desgaitua", - "map_marker_for_image": "{city}, {country} -(a)n ateratako irudiaren mapa-markatzailea", - "map_marker_with_image": "Mapa markatzailea irudiarekin", - "map_no_location_permission_content": "Kokapena baimenduta egon behar da zure uneko kokapeneko baliabideak bistaratzeko. Orain gaitu?", - "map_no_location_permission_title": "Kokapen baimena ukatuta", - "map_settings": "Mapa ezarpenak", - "map_settings_dark_mode": "Gai iluna", - "map_settings_date_range_option_day": "Azken 24 orduak", - "map_settings_date_range_option_days": "Azken {days} egunak", - "map_settings_date_range_option_year": "Iaz", - "map_settings_date_range_option_years": "Past {years} urteak", - "map_settings_include_show_archived": "Artxibatuak barne", - "map_settings_include_show_partners": "Kideak barne", - "map_settings_only_show_favorites": "Gogokoak soilik erakutsi", - "map_settings_theme_settings": "Maparen gaia", - "mark_all_as_read": "Denak irakurrita gisa markatu", - "marked_all_as_read": "Denak irakurrita gisa markatu dira", - "matches": "Bat etorritakoak", - "matching_assets": "Bat etortzen diren baliabideak", - "media_chrome": { - "auto": "Auto", - "captions": "Azpitituluak", - "captions_off": "Desgaituta", - "closed_captions": "Azpitituluak", - "decode_error": "Dekodetze akatsa", - "disable_captions": "Azpitituluak desgaitu", - "enable_captions": "Azpitituluak gaitu", - "enter_fullscreen_mode": "Pantaila osoko moduan sartu", - "exit_fullscreen_mode": "Pantaila osoko modua utzi", - "loop": "Bulkea", - "media_error_description": "Multimedia-errore batek erreprodukzioa bertan behera utzi du. Baliteke euskarria hondatuta egotea edo zure arakatzaileak formatu onartu ez izana.", - "media_loading": "Multimedia kargatzen", - "mute": "Isilarazi", - "network_error": "Sare akatsa", - "network_error_description": "Sareko errore batek multimedia-deskarga gelditu du.", - "not_supported_error": "Iturria ez da bateragarria", - "playback_rate": "Erreprodukzio-abiadura", - "playback_rate_current": "oraingo erreprodukzio-abiadura", - "playback_rate_value": "Erreprodukzio abiadura {playbackRate}", - "playback_time": "Erreprodukzio denbora", - "quality": "Kalitatea", - "second": "segundoa", - "seconds": "segundu", - "time_value_of_total_time": "{currentTime} {totalTime}-tik", - "time_value_remaining": "{time} falta dira", - "unmute": "Soinua gaitu", - "unsupported_error_description": "Onartu gabeko errore bat gertatu da. Zerbitzariak edo sareak huts egin du, edo zure arakatzaileak ez du formatu hau onartzen.", - "video_not_loaded_unknown_time": "bideoa ez da kargatu, ordu ezezaguna.", - "video_player": "Bideo erreproduzitzailea", - "volume": "bolumena" - }, - "media_type": "Multimedia mota", - "memories": "Gogorapenak", - "memories_all_caught_up": "Egunean zaude", - "memories_check_back_tomorrow": "Bueltatu bihar oroitzapen gehiago ikusteko", - "memories_setting_description": "Kudeatu zure oroitzapenetan ikusten duzuna", - "memories_start_over": "Berriro hasi", - "memories_swipe_to_close": "Goruntz lerratu ixteko", - "memory": "Gogorapena", - "memory_lane_title": "Gogorapen kutxa {title}", - "menu": "Menua", - "merge": "Batu", - "merge_people": "Pertsonak batu", - "merge_people_limit": "5 aurpegi gehienez batu ditzakezu aldi berean", - "merge_people_prompt": "Pertsona hauek batu nahi dituzu? Ekintza hau ezin da desegin.", - "merge_people_successfully": "Pertsonak arrakastaz batuta", - "merged_people_count": "{count, plural, one {Pertsona bat fusionatu da} other {# pertsona fusionatu dira}}", - "minFaces": "Gutxieneko aurpegi kopurua", - "minFaces_description": "Pertsona batek antzemandako aurpegi gutxieneko kopurua behar duena hura bistaratzeko", - "minimize": "Txikitu", - "minute": "Minutua", - "minutes": "Minutuak", - "missing": "Hutsegiteak", - "mobile_app": "Mugikorreko aplikazioa", - "mobile_app_download_onboarding_note": "Deskargatu mugikorretarako aplikazio osagarria aukera hauek erabiliz", - "model": "Modeloa", - "modify_date": "Data aldatu", - "month": "Hilabetea", - "more": "Gehiago", - "motion": "Mugimenduan", - "move": "Mugitu", - "move_off_locked_folder": "Giltzapeko karpetatik atera", - "move_to": "Hona eraman", - "move_to_device_trash": "Gailuko zakarrontzira eraman", - "move_to_lock_folder_action_prompt": "{count, plural, one {Baliabide bat gehitu da} other {# baliabide gehitu dira}} giltzapeko karpetara", - "move_to_locked_folder": "Giltzapeko karpetara eraman", - "move_to_locked_folder_confirmation": "Argazki eta bideo hauek album guztietatik kenduko dira eta giltzapeko karpetatik soilik ikusi ahal izango dira", - "moved_to_trash": "Zakarrontzira eraman da", - "mute_memories": "Gogorapenak isilarazi", - "my_albums": "Nire albumak", - "my_immich_description": "Kopiatu uneko orria Nire Immich esteka gisa", - "my_immich_title": "Nire Immich esteka", - "name": "Izena", - "name_or_nickname": "Izena edo ezizena", - "name_required": "Izena derrigorrezkoa da", - "navigate": "Nabigatu", - "navigate_to_time": "Denborara nabigatu", - "network_requirement_photos_upload": "Erabili datu mugikorrak argazkien segurtasun-kopiak sortu eta igotzeko", - "network_requirement_videos_upload": "Erabili datu mugikorrak bideoen segurtasun-kopiak sortu eta igotzeko", - "network_requirements": "Sarearen eskakizunak", - "network_requirements_updated": "Sareko eskakizunak aldatu dira, babeskopien ilara berrezartzen", - "networking_settings": "Sarea", - "networking_subtitle": "Zerbitzariaren endpoint ezarpenak kudeatu", - "never": "Inoiz ez", - "new_album": "Album berria", - "new_api_key": "API gako berria", - "new_feature": "Ezaugarri berria", - "new_password": "Pasahitz berria", - "new_person": "Pertsona berria", - "new_pin_code": "PIN gako berria", - "new_pin_code_subtitle": "Hau da giltzapeko karpetara sartzen zaren lehen aldia. Sortu PIN kodea orri honetara modu seguruan sartzeko", - "new_update": "Eguneraketa berria", - "new_user_created": "Erabiltzaile berria sortuta", - "new_version_available": "BERTSIO BERRIA ESKURAGARRI", - "newest_first": "Berriena lehenen", - "next": "Hurrengoa", - "next_memory": "Hurrengo gogorapena", - "no": "Ez", - "no_albums_found": "Ez da albumik aurkitu", - "no_albums_message": "Sortu album bat zure argazki eta bideoak antolatzeko", - "no_albums_with_name_yet": "Badirudi oraindik ez duzula izen hau duen albumik.", - "no_albums_yet": "Badirudi oraindik ez duzula albumik.", - "no_archived_assets_message": "Artxibatu argazkiak eta bideoak Argazkiak ikuspegitik ezkutatzeko", - "no_assets_message": "Egin klik zure lehen argazkia igotzeko", - "no_assets_to_show": "Erakusteko baliabiderik ez", - "no_cast_devices_found": "Ez da igortzeko gailurik aurkitu", - "no_checksum_local": "Ez dago egiaztapenerako baturarik. Ezin da tokiko baliabiderik jaso", - "no_checksum_remote": "Ez dago egiaztapenerako baturarik. Ezin da kanpoko baliabiderik jaso", - "no_devices": "Baimendutako gailurik ez", - "no_duplicates_found": "Ez da bikoizturik aurkitu.", - "no_exif_info_available": "Ez dago exif daturik eskuragarri", - "no_explore_results_message": "Kargatu argazki gehiago zure bilduma arakatzeko.", - "no_favorites_message": "Gogoko bezala ezarri zure argazki eta bideo onenak azkar aurkitzeko", - "no_libraries_message": "Sortu kanpoko liburutegi bat zure argazkiak eta bideoak ikusteko", - "no_local_assets_found": "Ez da tokiko baliabiderik aurkitu egiaztapeneko batura honekin", - "no_locked_photos_message": "Giltzapeko karpetako argazkiak eta bideoak ezkutatuta daude eta ez dira agertuko liburutegian arakatzen edo bilatzen duzun bitartean.", - "no_name": "Izenik ez", - "no_notifications": "Jakinarazpenik ez", - "no_people_found": "Ez da bat datorren pertsonarik aurkitu", - "no_places": "Tokirik ez", - "no_remote_assets_found": "Ez da kanpoko baliabiderik aurkitu egiaztapeneko batura honekin", - "no_results": "Emaitzarik ez", - "no_results_description": "Saiatu sinonimo edo gako-hitz orokorrago bat", - "no_shared_albums_message": "Sortu album bat zure sareko jendearekin argazkiak eta bideoak partekatzeko", - "no_steps": "Ez da pausurik gehitu", - "none": "Bat ere ez", - "not_allowed": "Debekatuta", - "not_available": "Ez dago eskuragarri", - "not_in_any_album": "Album baten ere ez", - "not_selected": "Hautatu gabe", - "notes": "Oharra", - "nothing_here_yet": "Momentuz hutsik", - "notification_backup_reliability": "Gaitu jakinarazpenak atzeko planoko babeskopien fidagarritasuna hobetzeko", - "notification_enabled_list_tile_content": "Immich-ek jakinarazpenak erabiltzen ditu atzeko planoko babeskopiak egiteko. Kudeatu itzazu gailuaren ezarpenetan.", - "notification_enabled_list_tile_open_button": "Ezarpenak erakutsi", - "notification_enabled_list_tile_title": "Jakinarazpenak gaituta", - "notification_permission_dialog_content": "Jakinarazpenak gaitzeko, joan Ezarpenak atalera eta hautatu baimendu.", - "notification_permission_list_tile_content": "Jakinarazpenak erakusteko baimendu.", - "notification_permission_list_tile_enable_button": "Jakinarazpenak gaitu", - "notification_permission_list_tile_title": "Jakinarazpen baimena", - "notification_toggle_setting_description": "E-mail jakinarazpenak gaitu", - "notifications": "Jakinarazpenak", - "notifications_setting_description": "Jakinarazpenak kudeatu", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium kudeatzailea", - "obtainium_configurator_instructions": "Erabili Obtainium Immich GitHub-en bertsiotik zuzenean Android aplikazioa instalatzeko eta eguneratzeko. Sortu API gako bat eta hautatu aldaera bat zure Obtainium konfigurazio esteka sortzeko", - "ocr": "OCR", - "ocr_body": "Immichek orain zure argazkien barruan dagoen testua irakurtzen du, haiek esaten dutenaren arabera bilatu ahal izateko.", - "ocr_title": "Bilatu testua zure argazkietan", - "official_immich_resources": "Immich-en baliabide ofizialak", - "offline": "Lineaz kanpo", - "offset": "Desbiderapena", - "ok": "Bai", - "oldest_first": "Zaharrena lehenengo", - "on_this_device": "Gailu honetan", - "onboarding": "Ezartzen", - "onboarding_locale_description": "Aukeratu nahi duzun hizkuntza. Geroago alda dezakezu zure ezarpenetan.", - "onboarding_privacy_description": "Honako (aukerako) ezaugarri hauek kanpoko zerbitzuetan oinarritzen dira, eta edozein unetan desgaitu daitezke ezarpenetan.", - "onboarding_server_welcome_description": "Jar dezagun zure instantzia ezarpen arrunt batzuekin.", - "onboarding_theme_description": "Aukeratu kolore-gai bat zure instantziarako. Geroago alda dezakezu zure ezarpenetan.", - "onboarding_user_welcome_description": "Hasi gaitezen!", - "onboarding_welcome_user": "Ongi etorri, {user}", - "online": "Linean", - "only_favorites": "Gogokoak soilik", - "open": "Zabalik", - "open_calendar": "Egutegia zabaldu", - "open_in_browser": "Arakatzailean zabaldu", - "open_in_immich_body": "Ezarri Immich zure galeria gisa Android-en argazkiak zuzenean irekitzeko beste aplikazioetatik.", - "open_in_immich_title": "Ireki argazkiak Immich-en", - "open_in_map_view": "Ireki maparen ikuspegian", - "open_in_openstreetmap": "OpenStreetMap-en zabaldu", - "open_the_search_filters": "Bilaketa-iragazkiak ireki", - "options": "Ezarpenak", - "or": "edo", "organize_into_albums": "Albumetan antolatu", - "organize_into_albums_description": "Jarri lehendik dauden argazkiak albumetan uneko sinkronizazio ezarpenak erabiliz", - "organize_your_library": "Zure liburutegia antolatu", - "orientation": "Orientazioa", - "original": "Originala", - "other": "Beste batzuk", - "other_devices": "Beste gailuak", - "other_variables": "Beste aldagaiak", - "owned": "Berezkoak", - "owner": "Jabea", - "page": "Orria", - "partner": "Kidea", - "partner_can_access": "{partner}-ek sarbidea du", - "partner_can_access_assets": "Zure argazki eta bideo guztiak Artxibategian eta Zakarrontzian daudenak izan ezik", - "partner_can_access_location": "Zure argazkiak atera diren tokia", - "partner_list_user_photos": "{user}-(r)en argazkiak", - "partner_page_empty_message": "Zure argazkiak oraindik ez dira partekatu inongo kiderekin.", - "partner_page_no_more_users": "Ez dago gehitezko erabiltzaile gehiagorik", - "partner_page_select_partner": "Kidea hautatu", - "partner_page_stop_sharing_content": "{partner}-k ezingo ditu zure argazkiak atzitu.", - "partner_sharing": "Kideekin partekatu", - "partners": "Kideak", - "password": "Pasahitza", - "password_does_not_match": "Pasahitza ez dator bat", - "password_required": "Pasahitza derrigorrezkoa da", - "password_reset_success": "Pasahitz eguneraketa arrakaztatsua", - "past_durations": { - "days": "Azken {days, plural, one {eguna} other {# egunak}}", - "hours": "Azken {hours, plural, one {ordua} other {# orduak}}", - "years": "Azken {years, plural, one {urtea} other {# urteak}}" - }, - "path": "Bidea", - "pattern": "Patroia", - "pause": "Gelditu", - "pause_memories": "Gogorapenak gelditu", - "paused": "Geldituta", - "pending": "Itxarotzen", - "people": "Pertsonak", - "people_edits_count": "{count, plural, one {Pertsona bat eguneratu da} other {# pertsona eguneratu dira}}", - "people_feature_description": "Argazkiak eta bideoak arakatu pertsonen arabera multzokatuta", - "permanent_deletion_warning": "Behin betiko ezabaketa oharra", - "permanent_deletion_warning_setting_description": "Erakutsi abisu bat baliabideak betiko ezabatzean", - "permanently_delete": "Behin betiko ezabatu", - "permanently_delete_assets_count": "{count, plural, one {Baliabide bat} other {# baliabide}} behin betiko ezabatu", - "permanently_delete_assets_prompt": "Ziur betiko ezabatu nahi dituzula {count, plural, one {baliabide hau?} other {# baliabide hauek?}} Honek {count, plural, one {bere albumetik ere kenduko du} other {haien albumetatik ere kenduko ditu}}.", - "permanently_deleted_asset": "Baliabidea behin betiko ezabatuta", - "permanently_deleted_assets_count": "{count, plural, one {Elementu bat betiko ezabatu da} other {# elementu betiko ezabatu dira}}", - "permission": "Baimena", - "permission_empty": "Zure baimenak ez du hutsik egon behar", - "person": "Pertsona", - "person_age_months": "Duela {months, plural, one {hilabete bat} other {# hilabete}}", - "person_age_year_months": "Urte bat eta {months, plural, one {hilabete bat} other {# hilabete}}", - "person_age_years": "{years, plural, one{Urte bat} other {# urte}}", - "person_birthdate": "{date} -(a)n jaiota", - "person_hidden": "{name}{hidden, select, true { (ezkutatuta)} other {}}", - "person_recognized": "Pertsona ezagutua", - "photo_shared_all_users": "Argazkiak erabiltzaile guztiekin partekatu dituzula dirudi edo ez duzula erabiltzailerik partekatzeko.", - "photos": "Argazkiak", - "photos_and_videos": "Argazkiak eta bideoak", - "photos_from_previous_years": "Aurreko urteetako argazkiak", - "pick_a_location": "Toki bat aukeratu", - "pick_custom_range": "Tarte pertsonalizatua", - "pick_date_range": "Datu tarte bat aukeratu", - "pin_code_changed_successfully": "PIN kodea arrakstaz aldatu da", - "pin_code_reset_successfully": "PIN kodea arrakstaz eguneratu da", - "pin_code_setup_successfully": "PIN gakoa arrakastaz ezarri da", - "pin_verification": "PIN kodearen egiaztapena", - "place": "Tokia", - "places": "Tokiak", - "places_count": "{count, plural, one {Toki bat} other {# toki}}", - "play": "Erreproduzitu", - "play_memories": "Gogorapenak erreproduzitu", - "play_motion_photo": "Argazki biziduna erreproduzitu", - "play_or_pause_video": "Bideoa gelditu edo erreproduzitu", - "play_original_video": "Jatorrizko bideoa erreproduzitu", - "play_original_video_setting_description": "Hobetsi jatorrizko bideoak erreproduzitzea transkodetutako bideoen ordez. Jatorrizko baliabidea onartzen ez bada, baliteke behar bezala ez erreproduzitzea.", - "play_transcoded_video": "Transkodetutako bideoa erreproduzitu", - "please_auth_to_access": "Mesedez autentifikatu zaitez hona sarbidea lortzeko", - "plugin_method_filter_type": "Iragazkia", - "plugin_method_filter_type_description": "Metodo honek gertaerak iragazi ditzake eta baldintzapean ondorengo urratsak exekutatzea eragotzi dezake", - "port": "Portua", - "preferences_settings_subtitle": "Aplikazioaren ezarpenak kudeatu", - "preferences_settings_title": "Ezarpenak", - "preparing": "Prestatzen", - "preset": "Txantiloia", - "preview": "Aurrebista", - "previous": "Aurrekoa", - "previous_memory": "Aurreko gogorapena", - "previous_or_next_day": "Eguna aurrera/atzera", - "previous_or_next_month": "Hilabetea aurrera/atzera", - "previous_or_next_photo": "Aurreko/hurrengo irudia", - "previous_or_next_year": "Urtea aurrera/atzera", - "primary": "Nagusia", - "privacy": "Pribatutasuna", - "profile": "Profila", - "profile_drawer_app_logs": "Erregistroak", - "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Irakurtzeko soilik modua gaituta. Luze sakatu erabiltzailearen abatarraren ikonoa irteteko.", - "profile_image_of_user": "{user}-(r)en profil-argazkia", - "profile_picture_set": "Profil-argazkia ezarrita.", - "projection_type": "Proiekzio mota", - "public_album": "Album publikoa", - "public_share": "Share publikoa", - "purchase_account_info": "Babeslea", - "purchase_activated_subtitle": "Eskerrik asko Immich eta kode-libreko software-a babestearren", - "purchase_activated_time": "{date}-(a)n aktibatuta", - "purchase_activated_title": "Zure gakoa behar bezala aktibatu da", - "purchase_button_activate": "Aktibatu", - "purchase_button_buy": "Erosi", - "purchase_button_buy_immich": "Immich erosi", - "purchase_button_never_show_again": "Ez erakutsi berriz", - "purchase_button_reminder": "Gogorarazi 30 egun barru", - "purchase_button_remove_key": "Gakoa kendu", - "purchase_button_select": "Aukeratu", - "purchase_failed_activation": "Ezin izan da aktibatu! Mesedez, egiaztatu zure posta elektronikoa produktuaren gako zuzena ikusteko!", - "purchase_individual_description_1": "Erabiltzaile batentzako", - "purchase_individual_description_2": "Babesle ikurra", - "purchase_individual_title": "Banakakoa", - "purchase_input_suggestion": "Produktu gako bat duzu? Idatzi ezazu behan", - "purchase_lifetime_description": "Bizitza osoko ordainketa", - "purchase_option_title": "ORDAINKETA AUKERAK", - "purchase_panel_info_1": "Immich eraikitzeak denbora eta esfortzu handia eskatzen du, eta lanaldi osoko ingeniariak ditugu lanean, ahal bezain ondo egiteko. Gure eginkizuna da kode irekiko softwarea eta negozio praktika etikoak garatzaileentzako diru-sarrera-iturri iraunkor bihurtzea eta pribatutasuna errespetatuko duen ekosistema sortzea, hodeiko zerbitzu ustiagarrien alternatiba errealekin.", - "purchase_panel_info_2": "Ordain-hormarik ez gehitzeko konpromisoa hartzen dugunez, erosketa honek ez dizu Immich-en ezaugarri gehigarririk eskeiniko. Zu bezalako erabiltzaileengan oinarritzen gara Immich-en etengabeko garapenean laguntzeko.", - "purchase_panel_title": "Proiektua babestu", - "purchase_per_server": "Zerbitzari bakoitzeko", - "purchase_per_user": "Erabiltzaile bakoitzeko", - "purchase_remove_product_key": "Produktu gakoa ezabatu", - "purchase_remove_product_key_prompt": "Zirur zaude produktu gakoa kendu nahi duzula?", - "purchase_remove_server_product_key": "Zerbitzariaren produktu gakoa kendu", - "purchase_remove_server_product_key_prompt": "Ziur zaude zerbitzariaren produktu gakoa kendu nahi duzula?", - "purchase_server_description_1": "Zerbitzari osorako", - "purchase_server_description_2": "Babesle ikurra", - "purchase_server_title": "Zerbitzaria", - "purchase_settings_server_activated": "Zerbitzariaren produktu-gakoa administratzaileak kudeatzen du", "query_asset_id": "Aztertu aukeratutako ID-a", - "queue_status": "Ilaran jartzen {count}/{total}", - "rate_asset": "Baliabidea puntuatu", - "rating": "Izar puntuazioa", - "rating_clear": "Puntuazioa ezabatu", - "rating_count": "{count, plural, =0 {Baloratu gabea} one {Izar bat} other {# izar}}", - "rating_description": "EXIF puntuazioa informazio panelean bistaratu", - "reaction_options": "Erreakzio aukerak", - "read_changelog": "Aldaketa erregistroa irakurri", "readonly_mode_disabled": "Irakurri-bakarrik modua desgaituta", "readonly_mode_enabled": "Irakurri-bakarrik modua gaituta", - "ready_for_upload": "Kargatzeko prest", - "reassign": "Berrezarri", - "reassigned_assets_to_existing_person": "{count, plural, one {Elementu bat berresleitu zaio} other {# elementu berresleitu zaizkio}} {name, select, null {lehendik dagoen pertsona bati} other {{name}-(r)i}}", - "reassigned_assets_to_new_person": "{count, plural, one {Baliabide bat} other {# baliabide}} pertsona berri bati berresleitu zaizkio", - "reassing_hint": "Esleitu hautatutako baliabideak lehendik dagoen pertsona bati", - "recent": "Berria", - "recent_searches": "Duela gutxiko bilaketak", - "recently_added": "Duela gutxi gehitutakoak", - "recently_added_body": "Joan zuzenean azkenaldian gehitu duzun guztiari eskainitako orrialde batean.", - "recently_added_description": "Arakatu zure fitxategiak Immich-en kargatzeko dataren arabera ordenatuta", - "recently_added_page_title": "Duela gutxi gehitutakoak", - "recently_added_title": "Duela gutxi gehituta", - "recently_taken": "Duela gutxi ateratakoak", - "refresh": "Freskatu", - "refresh_encoded_videos": "Kodetutako bideoak freskatu", - "refresh_faces": "Aurpegiak freskatu", - "refresh_metadata": "Metadatuak freskatu", - "refresh_thumbnails": "Miniaturak freskatu", - "refreshed": "Freskatuta", - "refreshing_encoded_video": "Kodetutako bideoak freskatzen", - "refreshing_faces": "Aurpegiak freskatzen", - "refreshing_metadata": "Metadatuak freskatzen", - "regenerating_thumbnails": "Miniaturak leheneratu", - "remote": "Urruneko zerbitzaria", - "remote_assets": "Kanpoko baliabideak", - "remote_media_summary": "Kanpoko multimedien laburpena", - "remove": "Kendu", - "remove_assets_album_confirmation": "Ziur zaude {count, plural, one {baliabide bat albumetik kendu nahi duzula} other {# baliabide albumetik kendu nahi ditzula}}?", - "remove_assets_shared_link_confirmation": "Ziur partekatutako esteka honetatik {count, plural, one {baliabide bat kendu nahi duzula} other {# baliabide kendu nahi dituzula}}?", - "remove_assets_title": "Baliabideak ezabatu?", - "remove_custom_date_range": "Data tarte pertsonalizatua kendu", - "remove_filter": "Iragazkia ezabatu", - "remove_from_album": "Ezabatu albumetik", - "remove_from_album_action_prompt": "{count} albumetik ezabatuta", - "remove_from_favorites": "Gogokoetatik ezabatu", - "remove_from_lock_folder_action_prompt": "{count, plural, one {Baliabide bat kendu da} other {# baliabide kendu dira}} giltzapeko karpetatik", - "remove_from_locked_folder": "Giltzapeko karpetatik atera", - "remove_from_locked_folder_confirmation": "Ziur zaude argazki eta bideo hauek giltzapeko karpetatik atera nahi dituzula? Zure liburutegian ikusgai egongo dira.", - "remove_from_shared_link": "Partekatutako estekatik kendu", - "remove_memory": "Gogorapena kendu", - "remove_photo_from_memory": "Argazkia gogorapen honetatik kendu", - "remove_tag": "Etiketa kendu", - "remove_user": "Erabiltzailea kendu", - "removed_api_key": "API gakoa ezabatua: {name}", - "removed_from_archive": "Artxibategitik ezabatuta", - "removed_from_favorites": "Gogokoetatik ezabatuta", - "removed_from_favorites_count": "Gogokoetatik {count, plural, one {ezabatuta} other {# ezabatuta}}", - "removed_memory": "Gogorapena kenduta", - "removed_tagged_assets": "Etiketa {count, plural, one {elementu batetik} other {# elementutatik}} kendu da", - "rename": "Izena aldatu", - "repository": "Errepositorioa", - "require_password": "Pasahitza derrigortu", - "rescan": "Berreskaneatu", - "reset": "Berrasieratu", - "reset_password": "Pasahitza leheneratu", - "reset_people_visibility": "Pertsonen ikusgarritasuna berrezarri", - "reset_pin_code": "PIN gakoa berrezarri", - "reset_pin_code_description": "PIN gakoa ahaztu baduzu, zerbitzariaren administratzailearekin harremanetan jar zaitezke berrezartzeko", - "reset_pin_code_with_password": "Zure PIN kodea beti berrezarri dezakezu pasahitzarekin", - "reset_sqlite": "SQLite datu-basea berrasieratu", - "reset_sqlite_clear_app_data": "Datuak garbitu", - "reset_sqlite_confirmation": "Ziur zaude aplikazioaren datuak garbitu nahi dituzula? Honek ezarpen guztiak kendu eta saioa amaituko duzu.", - "reset_sqlite_confirmation_note": "Oharra: Garbitu ondoren aplikazioa berrabiarazi beharko duzu.", - "reset_sqlite_done": "Aplikazioaren datuak garbitu dira. Mesedez, berrabiarazi Immich eta hasi saioa berriro.", - "reset_sqlite_success": "SQLite datu-basea arrakastak berrezarri da", - "reset_to_default": "Balio lehenetsitara berrezarri", - "resolution": "Bereizmena", - "resolve_duplicates": "Kopiak ebatzi", - "resolved_all_duplicates": "Kopia guztiak ebatzita", - "restore": "Zaharberritu", - "restore_all": "Denak berrezarri", - "restore_user": "Erabiltzailea berrezarri", - "restored_asset": "Baliabidea berrezarri", - "resume": "Jarraitu", - "resume_paused_jobs": "Berrekin {count, plural, one {gelditutako ataza} other {# gelditutako ataza}}", - "review_duplicates": "Kopiak berrikusi", - "review_large_files": "Fitxategi handiak berrikusi", - "role": "Rola", - "role_editor": "Editorea", - "role_viewer": "Ikuslea", - "running": "Martxan", - "save": "Gorde", - "saved": "Gordeta", - "saved_api_key": "API gakoa arrakastaz gorde da", - "saved_profile": "Profila gordeta", - "saved_settings": "Ezarpenak gordeta", - "say_something": "Zerbait esan", - "scaffold_body_error_occurred": "Akats bat gertatu da", - "scaffold_body_error_unrecoverable": "Errore berreskuraezina suertatu da. Mesedez, partekatu errorea eta pila jarraipena Discord edo GitHub-en laguntza jaso ahal izateko. Posible bada, behean aplikazioaren datuak garbi ditzakezu.", - "scan": "Eskaneatu", - "scan_all_libraries": "Liburutegi guztiak eskaneatu", - "scan_library": "Eskaneatu", - "scanning": "Eskaneatzen", - "scanning_for_album": "Albumak bilatzen...", - "screencast_mode_description": "Erakutsi teklatuaren eta saguaren gertaeren adierazleak pantailan", - "screencast_mode_title": "Aldizkatu screencast modua", - "search": "Bilatu", - "search_albums": "Albumak bilatu", - "search_by_context": "Testuinguruaren arabera bilatu", - "search_by_description": "Deskribapenaren arabera bilatu", - "search_by_description_example": "Sapan txango eguna", - "search_by_filename": "Bilatu fitxategiaren izenaren edo luzapenaren arabera", - "search_by_filename_example": "hau da, IMG_1234.JPG edo PNG", - "search_by_full_path": "Bilatu bide osoaren edo karpetaren arabera", - "search_by_full_path_example": "/John/Proiektuak/3D_Inprimaketa/2026-07-01 - Proiektuak, 3D, Inprimaketa, 2026 eta abar arakatu ditzakezu.", - "search_by_ocr": "OCR bitartez bilatu", - "search_by_ocr_example": "Kafesnea", - "search_camera_lens_model": "Bilatu lente modeloa...", - "search_camera_make": "Kamera fabrikatzailea bilatu...", - "search_camera_model": "Kamara modeloa bilatu...", - "search_city": "Hiria bilatuâ€Ļ", - "search_country": "Herrialdea bailatu...", - "search_filter_apply": "Iragazkiak aplikatu", - "search_filter_camera_title": "Aukeratu kamera mota", - "search_filter_date": "Data", - "search_filter_date_interval": "{start}-(e)tik {end}-ra", - "search_filter_date_title": "Data tarte bat ezarri", - "search_filter_display_option_not_in_album": "Ez dago albumean", - "search_filter_display_options": "Bistaratzeko ezarpenak", - "search_filter_filename": "Fitxategi izenaren arabera bilatu", - "search_filter_location": "Kokapena", - "search_filter_location_title": "Kokapena aukeratu", - "search_filter_media_type": "Multimedia mota", - "search_filter_media_type_title": "Aukeratu multimedia mota", - "search_filter_people_title": "Pertsonak hautatu", - "search_filter_star_rating": "Izar puntuazioa", - "search_filter_tags_title": "Etiketak aukeratu", - "search_for": "Bilatu", - "search_for_existing_person": "Bilatu lehendik dagoen pertsona", - "search_no_more_result": "Emaitza gehiagorik ez", - "search_no_people": "Pertsonarik ez", - "search_no_people_named": "Ez dago \"{name}\" izeneko pertsonarik", - "search_no_result": "Ez da emaitzarik aurkitu, saiatu beste termino edo bilaketa-konbinazio batekin", - "search_options": "Bilaketa ezarpenak", - "search_page_search_photos_videos": "Zure argazkiak eta bideoak bilatu", - "search_page_view_all_button": "Denak ikusi", - "search_people": "Pertsonak bilatu", - "search_places": "Tokiak bilatu", - "search_rating": "Puntuazioaren arabera bilatu...", - "search_settings": "Ezarpenak bilatu", - "search_state": "Probintziaren arabera bilatuâ€Ļ", - "search_tags": "Etiketak bilatuâ€Ļ", - "search_timezone": "Ordu-eremua bilatuâ€Ļ", - "search_type": "Bilaketa mota", - "search_your_photos": "Zure argazkiak bilatu", - "searching_locales": "Tokiak bilatzenâ€Ļ", - "second": "Segundua", - "see_all_people": "Pertsona guztiak ikusi", - "select": "Hautatu", - "select_album": "Albuma aukeratu", - "select_album_cover": "Albumaren portada aukeratu", - "select_albums": "Albumak aukeratu", - "select_all": "Dena aukeratu", - "select_all_duplicates": "Bikoiztutako guztiak aukeratu", - "select_avatar_color": "Abatar kolorea aukeratu", - "select_count": "{count, plural, one {# aukeratu} other {# aukeratu}}", - "select_cutoff_date": "Data muga ezarri", - "select_face": "Aurpegia hautatu", - "select_featured_photo": "Argazki nagusia aukeratu", - "select_from_computer": "Ordenagailutik aukeratu", - "select_keep_all": "Denak mantendu", - "select_new_face": "Aurpegi berria aukeratu", - "select_people": "Pertsonak aukeratu", - "select_person": "Pertsona aukeratu", - "select_person_to_tag": "Pertsona bat aukeratu hura etiketatzeko", - "select_photos": "Argazkiak aukeratu", - "select_quality": "Kalitatea aukeratu", - "select_trash_all": "Denak ezabatu", - "selected": "Aukeratuta", - "selected_count": "{count, plural, one {Aukeratutako bat} other {# aukeratutako}}", "selected_gps_coordinates": "GPS Koordenadak Aukeratuta", - "send_message": "Mezua bidali", - "send_welcome_email": "Ongietorri e-mail mezua bidali", - "server_endpoint": "Zerbitzariaren endpoint-a", - "server_info_box_app_version": "Aplikazio bertsioa", - "server_info_box_server_url": "Zerbitzariaren URLa", - "server_offline": "Zerbitzaria lineaz kanpo", - "server_online": "Zerbitzaria linean", - "server_privacy": "Zerbitzariaren pribatutasuna", - "server_restarting_description": "Orri hau berehala freskatuko da.", - "server_restarting_title": "Zerbitzaria berrabiarazten ari da", - "server_stats": "Zerbitzariaren estatistikak", - "server_update_available": "Zerbitzariaren eguneraketa eskuragarri dago", - "server_version": "Zerbitzariaren bertsioa", - "set": "Ezarri", - "set_as_album_cover": "Album portada bezala ezarri", - "set_as_featured_photo": "Argazki nagusi bezala ezarri", - "set_as_profile_picture": "Profil-argazki bezala ezarri", - "set_date_of_birth": "Jaiotze-data ezarri", - "set_profile_picture": "Ezarri profil-argazkia", - "set_slideshow_to_fullscreen": "Ezarri diapositibak pantaila osoko moduan", - "set_stack_primary_asset": "Baliabide nagusi gisa ezarri", - "setting_image_navigation_enable_subtitle": "Gaituta badago, aurreko/hurrengo irudira nabiga dezakezu pantailaren ezkerreko/eskuineko laurdena sakatuta.", - "setting_image_navigation_enable_title": "Ikutu nabigatzeko", - "setting_image_navigation_title": "Argazkien nabigazioa", - "setting_image_viewer_help": "Xehetasun-ikusleak miniatura kargatzen du lehenik, gero tamaina ertaineko aurrebista kargatzen du (gaituta badago), azkenik jatorrizkoa kargatzen du (gaituta badago).", - "setting_image_viewer_original_subtitle": "Gaitu bereizmen osoko jatorrizko irudia kargatzeko (handia!). Desgaitu datuen erabilera murrizteko (sarean eta gailuaren cachean).", - "setting_image_viewer_original_title": "Jatorrizko irudia kargatu", - "setting_image_viewer_preview_subtitle": "Gaitu bereizmen ertaineko irudiak kargatzeko. Desgaitu jatorrizkoa zuzenean kargatzeko edo miniatura soilik erabiltzeko.", - "setting_image_viewer_preview_title": "Aurrebista irudia kargatu", - "setting_languages_apply": "Ezarri", - "setting_languages_subtitle": "Aplikazioaren hizkuntza aldatu", - "setting_notifications_notify_minutes": "{count, plural, one {Minutu bat} other {# minutu}}", - "setting_notifications_notify_seconds": "{count, plural, one {Segundu bat} other {# segundu}}", - "setting_notifications_subtitle": "Zure jakinarazpen ezarpenak doitu", - "setting_video_viewer_auto_play_subtitle": "Automatikoko erreproduzitu bideoak zabaltzen direnean", - "setting_video_viewer_auto_play_title": "Bideoak automatikoki erreproduzitu", - "setting_video_viewer_looping_title": "Errepikatu", - "setting_video_viewer_original_video_subtitle": "Zerbitzaritik bideo bat igortzen duzunean, jatorrizkoa erreproduzitu transkode bat erabilgarri badago ere. Buffering-a ekar dezake. Lokalean eskuragarri dauden bideoak jatorrizko kalitatean erreproduzitzen dira ezarpen hau edozein dela ere.", - "setting_video_viewer_original_video_title": "Jatorrizko bideoa behartu", - "settings": "Ezarpenak", - "settings_saved": "Ezarpenak gordeta", - "setup_pin_code": "PIN gako bat ezarri", - "share": "Partekatu", - "share_dialog_preparing": "Prestatzen...", - "share_link": "Esteka partekatu", - "share_original": "Jatorrizkoa erabili (handia)", - "share_preview": "Miniatura erabili (txikia)", - "share_quality_body": "Eduki sakatuta partekatzeko botoia partekatu aurretik irudiaren kalitatea aukeratzeko.", - "share_quality_title": "Aukeratu zure partekatzeko kalitatea", - "shared": "Partekatuta", - "shared_album_activities_input_disable": "Iruzkinak desgaituta daude", - "shared_album_activity_remove_content": "Jarduera hau ezabatu nahi duzu?", - "shared_album_activity_remove_title": "Jarduera ezabatu", - "shared_album_section_people_action_error": "Ezin izan da albumetik irtetzerakoan / albuma ezabatzerakoan", - "shared_album_section_people_title": "PERTSONAK", - "shared_by": "Honek partekatuta", - "shared_by_user": "{user}-(e)k partekatuta", - "shared_by_you": "Zuk partekatuta", - "shared_from_partner": "{partner}-(e)n argazkiak", - "shared_intent_upload_button_progress_text": "{current} / {total} gora kargatuta", - "shared_link_app_bar_title": "Partekatutako estekak", - "shared_link_clipboard_copied_massage": "Esteka arbelera kopiatuta", - "shared_link_create_error": "Partekatutako esteka sortzeak huts egin du", - "shared_link_custom_url_description": "Sartu partekatutako esteka honetara URL pertsonalizatu batekin", - "shared_link_edit_description_hint": "Deskribapena sartu", - "shared_link_edit_expire_after_option_day": "Egun bat", - "shared_link_edit_expire_after_option_days": "{count} egun", - "shared_link_edit_expire_after_option_hour": "Ordu bat", - "shared_link_edit_expire_after_option_hours": "{count} ordu", - "shared_link_edit_expire_after_option_minute": "Minutu bat", - "shared_link_edit_expire_after_option_minutes": "{count} minutu", - "shared_link_edit_expire_after_option_months": "{count} hilabete", - "shared_link_edit_expire_after_option_year": "{count} urte", - "shared_link_edit_password_hint": "Pasahitza sartu", - "shared_link_edit_submit_button": "Esteka eguneratu", - "shared_link_error_server_url_fetch": "Ezin izan da zerbitzariaren url-a jaso", - "shared_link_expires_day": "{count, plural, one {Egun bat} other {# egun} } barru irangiko da", - "shared_link_expires_days": "{count, plural, one {Egun bat} other {# egun} } barru irangiko da", - "shared_link_expires_hour": "{count, plural, one {Ordu bat} other {# ordu} } barru irangiko da", - "shared_link_expires_hours": "{count, plural, one {Ordu bat} other {# ordu} } barru irangiko da", - "shared_link_expires_minute": "{count, plural, one {Minutu bat} other {# minutu} } barru irangiko da", - "shared_link_expires_minutes": "{count, plural, one {Minutu bat} other {# minutu} } barru irangiko da", - "shared_link_expires_never": "Iraungiezina", - "shared_link_expires_second": "{count, plural, one {Segundu bat} other {# segundu} } barru irangiko da", - "shared_link_expires_seconds": "{count, plural, one {Segundu bat} other {# segundu} } barru irangiko da", - "shared_link_individual_shared": "Indibidualki partekatuta", - "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Partekatutako estekak kudeatu", - "shared_link_options": "Partekatutako esteken ezarpenak", - "shared_link_password_description": "Eskatu pasahitz bat partekatutako esteka honetara sartzeko", - "shared_links": "Partekatutako estekak", - "shared_links_description": "Partekatu argazkiak eta bideoak esteka batekin", - "shared_photos_and_videos_count": "{assetCount, plural, one {Partekatutako argazki eta bideo bat.} other {# partekatutako argazki eta bideo.}}", - "shared_with_me": "Nirekin partekatutakoak", - "shared_with_partner": "{partner}-(r)ekin partekatuta", - "sharing": "Partekatzen", - "sharing_enter_password": "Mesedez, sartu pasahitza orri hau ikusteko.", - "shift_to_permanent_delete": "sakatu ⇧ baliabidea betiko ezabatzeko", - "show_album_options": "Erakutsi albumaren azarpenak", - "show_albums": "Albumak erakutsi", - "show_all_people": "Pertsona guztiak erakutsi", - "show_and_hide_people": "Pertsonak erakutsi eta ezkutatu", - "show_file_location": "Erakutsi fitxategiaren kokapena", - "show_gallery": "Galeria erakutsi", - "show_hidden_people": "Erakutsi ezkutatutako pertsonak", - "show_in_timeline": "Erakutsi denbora-lerroan", - "show_in_timeline_setting_description": "Erakutsi erabiltzaile honen argazkiak eta bideoak zure denbora-lerroan", - "show_keyboard_shortcuts": "Erakutsi lasterbideak", - "show_less": "Gutxiago erakutsi", - "show_metadata": "Metadatuak erakutsi", - "show_more_fields": "{count, plural, one {Erakutsi eremu bat gehiago} other {Erakutsi # eremu gehiago}}", - "show_or_hide_info": "Informazioa erakutsi edo ezkutatu", - "show_password": "Pasahitza erakutsi", - "show_person_options": "Erakutsi pertsonaren ezarpenak", - "show_progress_bar": "Aurrerapen-barra erakutsi", - "show_schema": "Eskema erakutsi", - "show_search_options": "Erakutsi bilaketa ezarpenak", - "show_shared_links": "Erakutsi partekatutako estekak", - "show_slideshow_metadata_overlay": "Gainjarri irudiaren informazioa", - "show_slideshow_transition": "Erakutsi diapositiba-trantsizioak", - "show_supporter_badge": "Babesle insignia", - "show_supporter_badge_description": "Erakutsi babesle insignia", - "show_text_recognition": "Testu-ezagutza erakutsi", - "show_text_search_menu": "Erakutsi bilaketa menua", - "shuffle": "Nahasi", - "sidebar": "Alboko panela", - "sidebar_display_description": "Erakutsi bistarako esteka alboko barran", - "sign_out": "Saioa itxi", - "sign_up": "Erregistratu", - "size": "Tamaina", - "skip_to_content": "Edukira salto egin", - "skip_to_folders": "Karpetetara salto egin", - "skip_to_tags": "Etiketetara salto egin", - "slideshow": "Diapositibak", - "slideshow_metadata_overlay_mode": "Gainjartzearen edukia", - "slideshow_metadata_overlay_mode_description_only": "Deskribapena soilik", - "slideshow_metadata_overlay_mode_full": "Osorik", - "slideshow_repeat": "Diapositiba-aurkezpena errepikatu", - "slideshow_repeat_description": "Itzuli hasierara diaporama aurkezpena amaitzen denean", - "slideshow_settings": "Diapositiba ezarpenak", - "smart_album": "Album adimentsua", - "some_assets_already_have_a_location_warning": "Hautatutako baliabide batzuek dagoeneko badute kokapena", - "sort_albums_by": "Albumak honen bidez ordenatu...", - "sort_created": "Sortze data", - "sort_items": "Baliabide kopurua", - "sort_modified": "Egunatze data", - "sort_oldest": "Argazkirik zaharrena", - "sort_people_by_similarity": "Pertsonak antzekotasunaren arabera ordenatu", - "sort_recent": "Argazkirik berriena", - "sort_title": "Izenburua", - "source": "Iturburua", - "stack": "Multzokatu", - "stack_action_prompt": "{count} multzokatuta", - "stack_duplicates": "Kopiak multzokatu", - "stack_selected_photos": "Aukeratutako argazkiak multzokatu", - "stacked_assets_count": "{count, plural, one {Baliabide bat multzokatu da} other {# baliabide multzokatu dira}}", - "stacktrace": "Pila jarraipena", - "start": "Hasi", - "start_date": "Hasiera data", - "start_date_before_end_date": "Hasiera-data amaiera-data baino lehenagokoa izan behar da", - "state": "Estatua / Probintzia", - "status": "Egoera", - "step_delete": "Pausua ezabatu", - "step_delete_confirm": "Ziur zaude urrats hau ezabatu nahi duzula?", - "step_details": "Pausoaren xehetasunak", - "steps": "Pausuak", - "stop_casting": "Igortzeari utzi", - "stop_motion_photo": "Gelditu argazki biziduna", - "stop_photo_sharing": "Zure argazkiak partekatzeari utzi?", - "stop_photo_sharing_description": "{partner}-(e)k ezingo ditu zure argazkiak atzitu.", - "stop_sharing_photos_with_user": "Utzi zure argazkiak erabiltzaile honekin partekatzeari", - "storage": "Memoria edukiera", - "storage_label": "Memoria etiketa", - "storage_quota": "Biltegiratze-kuota", - "storage_usage": "{used} erabilita {available} -etik", - "submit": "Bidali", - "success": "Arrakastatsua", - "suggestions": "Iradokizunak", - "sunrise_on_the_beach": "Iluntzea hondartzan (Sunrise on the beach)", - "support": "Babesa", - "support_and_feedback": "Laguntza eta feedback-a", - "support_third_party_description": "Zure Immich-en instalazioa hirugarren batek paketatu du. Jasotako arazoak pakete horrek sor ditzake, beraz, mesedez planteatu arazoak lehenbailehen beheko estekak erabiliz.", - "supporter": "Babeslea", - "swap_merge_direction": "Trukatu bateratze-norabidea", - "sync": "Sinkronizazioa", - "sync_albums": "Albumak sinkronizatu", - "sync_local": "Bertako sinkronizatua", - "sync_remote": "Kanpoko sinkronizazioa", - "sync_status": "Sinkronizazioaren egoera", - "sync_status_subtitle": "Erakutsi eta kudeatu sinkronizazio sistema", - "sync_upload_album_setting_subtitle": "Sortu eta igo zure argazkiak eta bideoak Immich-en hautatutako albumetara", - "system_theme": "Sistemaren gaia", - "system_theme_command_description": "Erabili sistemaren gaia ({value})", - "tag": "Etiketa", - "tag_assets": "Baliabideak etiketatu", - "tag_created": "Etiketa sortuta: {tag}", - "tag_face": "Aurpegia etiketatu", - "tag_feature_description": "Arakatu argazkiak eta bideoak etiketa logikoen gaien arabera taldekatuta", - "tag_people": "Pertsonak etiketatu", - "tag_updated": "Etiketa eguneratuta: {tag}", - "tagged_assets": "{count, plural, one {Baliabidea etiketatu da} other {# baliabide etiketatu dira}}", - "tags": "Etiketak", - "tap_to_run_job": "Ikutu ataza exekutatzeko", - "template": "Txantiloia", - "text_recognition": "Testu-ezagutza", - "theme": "Gaia", - "theme_selection": "Gaien aukeraketa", - "theme_selection_description": "Ezarri gaia automatikoki argia edo iluna zure arakatzailearen sistemaren hobespenen arabera", - "theme_setting_asset_list_storage_indicator_title": "Erakutsi biltegiratze-adierazlea baliabideen miniaturetan", - "theme_setting_asset_list_tiles_per_row_title": "Ilara bakoitzeko baliabide kopurua ({count})", - "theme_setting_colorful_interface_subtitle": "Aplikatu kolore primarioa atzeko gainazaletan.", - "theme_setting_colorful_interface_title": "Interfaze koloreduna", - "theme_setting_primary_color_subtitle": "Aukeratu kolore bat ekintza nagusietarako eta azentuetarako.", - "theme_setting_primary_color_title": "Kolore nagusia", - "theme_setting_system_primary_color_title": "Erabili sistemaren kolorea", - "theme_setting_system_theme_switch": "Automatikoa (sistemaren ezarpena jarraitu)", - "then": "Orduan", - "they_will_be_merged_together": "Batera fusionatuko dira", - "third_party_resources": "Bitartekoen baliabideak", - "time": "Denbora", - "time_based_memories": "Denboran oinarritutako gogorapenak", - "time_based_memories_duration": "Irudi bakoitza bistaratzeko segundo kopurua.", - "timeline": "Kronologia", - "timezone": "Ordu-eremua", - "to_archive": "Artxibatu", - "to_change_password": "Pasahitza aldatu", - "to_favorite": "Gogokotzat ezarri", - "to_login": "Saioa hasi", - "to_multi_select": "aukeraketa anitzera", - "to_parent": "Gurasoetara jo", + "sort_newest": "Argazkirik berriena", "to_select": "aukeratzeko", - "to_trash": "Baztertu", - "toggle_settings": "Ezarpenak aldizkatu", - "toggle_theme_description": "Gaia aldizkatu", - "total": "Guztira", - "total_usage": "Erabilpen osoa", - "trash": "Zakarrontzia", - "trash_action_prompt": "{count} zakarrontzira eraman dira", - "trash_all": "Denak ezabatu", - "trash_count": "Zakarrontzia {count, number}", - "trash_delete_asset": "Baliabidea ezabatu/zakarrontzira eraman", - "trash_no_results_message": "Zakarrontziko argazkiak eta bideoak hemen agertuko dira.", - "trash_page_delete_all": "Denak ezabatu", - "trash_page_info": "Zakarrontziko elementuak betiko ezabatuko dira {days} egun igaro ondoren", - "trashed_items_will_be_permanently_deleted_after": "Zakarontziko elementuak betiko ezabatuko dira {days, plural, one {egun #} other {# egun}} igarotakoan.", - "trigger": "Abiarazlea", - "trigger_asset_metadata_extraction": "Baliabideen metadatuen erauzketa", - "trigger_asset_metadata_extraction_description": "Baliabide baten EXIF metadatuak erauzten direnean abiarazten da", - "trigger_asset_uploaded": "Baliabidea gora kargatuta", - "trigger_asset_uploaded_description": "Baliabide berri bat kargatzen denean abiarazten da", - "trigger_person_recognized": "Pertsona ezagutua", - "trigger_person_recognized_description": "Pertsona bat ezagutzen denean abiarazten da", - "troubleshoot": "Arazoak konpontzea", - "type": "Mota", - "unable_to_change_pin_code": "Ezin izan da PIN gakoa aldatu", - "unable_to_check_version": "Ezin da egiaztatu aplikazioaren edo zerbitzariaren bertsioa", - "unable_to_setup_pin_code": "Ezin izan da PIN kodea ezarri", - "unarchive": "Desartxibatu", - "unarchive_action_prompt": "{count, plural, one {Artxibotik kendu da} other {# artxibotik kendu dira}}", - "unarchived_count": "{count, plural, one {Artxibatu gabeko bat} other {# artxibatu gabeko}}", - "undo": "Desegin", - "unfavorite": "Gogokoetatik kendu", - "unfavorite_action_prompt": "{count} gogokoetatik kendu dira", - "unhide_person": "Pertsona erakutsi", - "unknown": "Ezezaguna", - "unknown_country": "Herrialde ezezaguna", - "unknown_date": "Data ezezaguna", - "unknown_year": "Urte ezezaguna", - "unlimited": "Mugagabea", - "unlink_motion_video": "Argazki biziduna deslotu", - "unlink_oauth": "OAuth deslotu", - "unlinked_oauth_account": "OAuth kontua deslotuta", - "unmute_memories": "Gogorapenen soinua gaitu", - "unnamed_album": "Izengabeko Albuma", - "unnamed_album_delete_confirmation": "Ziur zaude album hau ezabatu nahi duzula?", - "unnamed_share": "Izengabeko baliabide partekatua", - "unsaved_change": "Gordegabeko aldaketa", - "unselect_all": "Aukeraketak garbitu", - "unselect_all_duplicates": "Bikoiztutakoak deshautatu", - "unstack": "Multzotik kendu", - "unstack_action_prompt": "{count} multzotik kenduta", - "unstacked_assets_count": "{count, plural, one {Baliabidea multzotik kendu da} other {# baliabide multzotik kendu dira}}", - "unsupported_field_type": "Eremu mota ez da bateragarria", - "unsupported_file_type": "Ezin da {file} fitxategia kargatu bere fitxategi mota {type} onartzen ez delako.", - "untagged": "Etiketagabea", - "up_next": "Hurrengoa", - "update_location_action_prompt": "Hautatutako {count, plural, one {baliabidearen} other {# baliabideen}} kokapena honekin eguneratu:", - "updated_at": "Eguneratze-data", - "updated_password": "Pasahitza eguneratuta", - "upload": "Igo", - "upload_concurrency": "Igoera paraleloak", - "upload_day_count": "{date}: {count, plural, one {Karga bat} other {# karga}}", - "upload_details": "Kargaren xehetasunak", - "upload_error_with_count": "Errorea {count, plural, one {elementua} other {# elementu}} kargatzean", - "upload_errors": "Karga {count, plural, one {huts batekin} other {# hutsekin}} amaitu da, orria freskatu kargatutako baliabide berriak ikusteko.", - "upload_finished": "Karga amaitu da", - "upload_progress": "Bestelakoak {remaining, number} - {processed, number}/{total, number} prozesatuta", - "upload_skipped_duplicates": "{count, plural, one {Bikoiztutako baliabide bat saltatu da} other {# bikoiztutako baliabide saltatu dira}}", - "upload_status_duplicates": "Kopiak", - "upload_status_errors": "Akatsak", - "upload_status_uploaded": "Igota", - "upload_success": "Karga arrakaztatsua, freskatu orria kargatutako baliabide berriak ikusteko.", - "upload_to_immich": "Immich-era igo ({count})", - "uploading": "Igotzen", - "uploading_media": "Multimedia gora kargatzen", - "uploads": "Igoerak", - "uploads_count": "{count, plural, one {Igoera bat} other {# igoera}}", - "url": "URL-a", - "usage": "Erabilera", - "use_biometric": "Erabili autentifikazio biometrikoa", - "use_browser_locale": "Arakatzailearen lokalizazioa erabili", - "use_browser_locale_description": "Formateatu datak, orduak eta zenbakiak zure arakatzailearen lokalizazioan oinarrituta", - "use_current_connection": "Erabili oraingo konexioa", - "use_custom_date_range": "Erabili data tarte pertsonalizatu bat", - "use_template": "Txantiloia erabili", - "user": "Erabiltzailea", - "user_has_been_deleted": "Erabiltzaile hau ezabatu da.", - "user_id": "Erabiltzaile ID-a", - "user_liked": "{user}-(e)i {type, select, photo {argazki hau} video {bideo hau} asset {elementu hau} other {hau}} gustatu zaio", - "user_pin_code_settings": "PIN gakoa", - "user_pin_code_settings_description": "Zure PIN gakoa kudeatu", - "user_privacy": "Erabiltzailearen pribatutasuna", - "user_purchase_settings": "Erosketa", - "user_purchase_settings_description": "Zure erosketa kudeatu", - "user_usage_detail": "Erabiltzailearen erabileraren xehetasunak", - "user_usage_stats": "Kontuaren erabilera estatistikak", - "user_usage_stats_description": "Kontuaren erabilera estatistikak erakutsi", - "username": "Erabiltzaile izena", - "users": "Erabiltzaileak", - "users_added_to_album_count": "{count, plural, one {Erabiltzaile bat gehitu da albumera} other {# erabiltzaile gehitu dira albumera}}", - "utilities": "Tresnak", - "validate": "Balioetsi", - "validate_endpoint_error": "Mesedez sartu URL zuzen bat", - "variables": "Aldagaiak", - "version": "Bertsioa", - "version_announcement_closing": "Zure laguna, Alex", - "version_announcement_message": "Kaixo! Immich-en bertsio berri bat dago eskuragarri. Mesedez, hartu denbora pixka bat argitalpen-oharrak irakurtzeko, zure konfigurazioa eguneratuta dagoela ziurtatzeko, konfigurazio okerrak saihesteko, batez ere WatchTower edo Immich instantzia automatikoki eguneratzea kudeatzen duen edozein mekanismo erabiltzen baduzu.", - "version_history": "Bertsio-historia", - "version_history_item": "{version} instalatuta {date}-(a)n", - "video": "Bideoa", - "video_hover_setting": "Bideoaren miniatura erreproduzitu gainetik pasatzerakoan", - "video_hover_setting_description": "Erreproduzitu bideoaren miniatura sagua elementuaren gainean pasatzen denean. Nahiz eta desgaituta egon, erreprodukzioa has daiteke erreproduzitzeko ikonoaren gainean pasatuta.", - "video_quality": "Bideo kalitatea", - "videos": "Bideoak", - "videos_count": "{count, plural, one {Bideo bat} other {# bideo}}", - "view": "Bista", - "view_album": "Albuma ikusi", - "view_all": "Denak ikusi", - "view_all_users": "Erabiltzaile guztiak erakutsi", - "view_asset_owners": "Baliabidearen jabeak erakutsi", - "view_details": "Xehetasunak ikusi", - "view_in_timeline": "Denbora-lerroan ikusi", - "view_link": "Esteka ikusi", - "view_name": "Bista", - "view_next_asset": "Hurrengo baliabidea erakutsi", - "view_previous_asset": "Aurreko baliabidea erakutsi", - "view_qr_code": "QR kodea erakutsi", - "view_similar_photos": "Ikusi antzeko argazkiak", - "view_stack": "Pila ikusi", - "viewer_remove_from_stack": "Multzotik kendu", - "visibility": "Ikusgarritasuna", - "visibility_changed": "Ikusgarritasuna {count, plural, one {pertsona batentzat} other {# pertsonentzat}} aldatu da", - "visual": "Bisuala", - "waiting": "Itxarotzen", - "waiting_count": "Itxarotzen: {count}", - "warning": "Oharra", - "week": "Astea", - "welcome": "Ongi etorri", - "welcome_to_immich": "Ongi egorri Immich-era", - "when": "Noiz", - "width": "Zabalera", - "wifi_name": "Wi-Fi izena", - "workflow": "Lan-fluxua", - "workflow_delete_prompt": "Ziur zaude lan-fluxu hau ezabatu nahi duzula?", - "workflow_deleted": "Lan-fluxua ezabatua", - "workflow_description": "Lan-fluxuaren deskribapena", - "workflow_info": "Lan-fluxuaren informazioa", - "workflow_json": "Lan-fluxuaren JSON-a", - "workflow_json_help": "Editatu lan-fluxuaren konfigurazioa JSON formatuan. Aldaketak eraikitzaile bisualarekin sinkronizatuko dira.", - "workflow_name": "Lan-fluxuaren izena", - "workflow_navigation_prompt": "Ziur zaude aldaketak gorde gabe irten nahi duzula?", - "workflow_summary": "Lan-fluxuaren laburpena", - "workflow_templates": "Lan-fluxu txantiloiak", - "workflow_update_success": "Lan-fluxua arrakastaz eguneratua", - "workflows": "Lan-fluxuak", - "workflows_help_text": "Lan-fluxuek zure baliabideetan ekintzak automatizatzen dituzte abiarazleen eta iragazkien arabera", - "wrong_pin_code": "PIN kode okerra", - "x_of_total": "{total}-(e)tik {x}", - "year": "Urtea", - "years_ago": "Duela {years, plural, one {urte bat} other {# urte}}", - "yes": "Bai", - "you_dont_have_any_shared_links": "Ez duzu partekatutako estekarik", - "your_wifi_name": "Zure Wi-Fi izena", - "zero_to_clear_rating": "sakatu 0 baliabidearen balorazioa garbitzeko", - "zoom_image": "Irudia handitu" + "view_similar_photos": "Ikusi antzeko argazkiak" } diff --git a/i18n/fa.json b/i18n/fa.json index 3f2be10f4f..0a29a09d83 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -16,25 +16,32 @@ "add_a_name": "Ø§ŲØ˛ŲˆØ¯Ų† Ų†Ø§Ų…", "add_a_title": "Ø§ŲØ˛ŲˆØ¯Ų† ØšŲ†ŲˆØ§Ų†", "add_action": "Ø§ŲØ˛ŲˆØ¯Ų† ØšŲ…Ų„ÛŒØ§ØĒ", + "add_action_description": "Ø¨ØąØ§ÛŒ Ø§ŲØ˛ŲˆØ¯Ų† ؈ Ø§ØšŲ…Ø§Ų„ یڊ ØšŲ…Ų„ÛŒØ§ØĒ ÚŠŲ„ÛŒÚŠ ÚŠŲ†ÛŒØ¯", "add_assets": "Ø§ŲØ˛ŲˆØ¯Ų† ØšÚŠØŗ یا ŲÛŒŲ„Ų…", "add_birthday": "Ø§ŲØ˛ŲˆØ¯Ų† ØĒØ§ØąÛŒØŽ ØĒŲˆŲ„Ø¯", "add_endpoint": "Ø§ŲØ˛ŲˆØ¯Ų† ŲžØ§ÛŒØ§Ų†Ų‡", "add_exclusion_pattern": "Ø§ŲØ˛ŲˆØ¯Ų† Ø§Ų„Ú¯ŲˆÛŒ Ø§ØŗØĒØĢŲ†Ø§", + "add_filter": "Ø§ŲØ˛ŲˆØ¯Ų† ŲÛŒŲ„ØĒØą", + "add_filter_description": "Ø¨ØąØ§ÛŒ Ø§ŲØ˛ŲˆØ¯Ų† یڊ Ø´ØąØˇ ŲÛŒŲ„ØĒØą ÚŠŲ„ÛŒÚŠ ÚŠŲ†ÛŒØ¯", "add_location": "Ø§ŲØ˛ŲˆØ¯Ų† Ų…ÚŠØ§Ų†", + "add_more_users": "Ø§ŲØ˛ŲˆØ¯Ų† ÚŠØ§ØąØ¨ØąŲ‡Ø§ÛŒ بیشØĒØą", "add_partner": "Ø§ŲØ˛ŲˆØ¯Ų† Ø´ØąÛŒÚŠ", + "add_path": "Ø§ŲØ˛ŲˆØ¯Ų† Ų…ØŗÛŒØą", "add_photos": "Ø§ŲØ˛ŲˆØ¯Ų† ØšÚŠØŗ Ų‡Ø§", - "add_step": "Ø§ŲØ˛ŲˆØ¯Ų† Ú¯Ø§Ų…", "add_tag": "Ø§ŲØ˛ŲˆØ¯Ų† ØĒÚ¯", "add_to": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ â€Ļ", "add_to_album": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ…", "add_to_album_bottom_sheet_added": "Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… {album} اØļØ§ŲŲ‡ شد", "add_to_album_bottom_sheet_already_exists": "Ų‚Ø¨Ų„Ø§ Ø¯Øą ØĸŲ„Ø¨ŲˆŲ… {album} Ų…ŲˆØŦŲˆØ¯ Ø§ØŗØĒ", "add_to_album_bottom_sheet_some_local_assets": "Ø¨ØąØŽÛŒ Ø§Ø˛ Ų…Ø­ØĒŲˆØ§Ų‡Ø§ÛŒ Ų…Ø­Ų„ÛŒ ØąØ§ Ų†Ø´Ø¯ Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… اØļØ§ŲŲ‡ ÚŠØąØ¯", + "add_to_album_toggle": "ØĒØēÛŒÛŒØą ؈ØļØšÛŒØĒ Ø§Ų†ØĒ؎اب Ø¨ØąØ§ÛŒ {album}", "add_to_albums": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ…", "add_to_albums_count": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… Ų‡Ø§ {count}", "add_to_bottom_bar": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡", + "add_to_shared_album": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… اشØĒØąØ§ÚŠÛŒ", "add_upload_to_stack": "Ø§ŲØ˛ŲˆØ¯Ų† ŲØ§ÛŒŲ„ Ø§ØąØŗØ§Ų„ÛŒ Ø¨Ų‡ Ų…ØŦŲ…ŲˆØšŲ‡", "add_url": "Ø§ŲØ˛ŲˆØ¯Ų† ØĸØ¯ØąØŗ URL", + "add_workflow_step": "Ø§ŲØ˛ŲˆØ¯Ų† یڊ Ų…ØąØ­Ų„Ų‡ Ø¨Ų‡ ØąŲˆŲ†Ø¯ ÚŠØ§Øą", "added_to_archive": "Ø¨Ų‡ ØĸØąØ´ÛŒŲˆ اØļØ§ŲŲ‡ شد", "added_to_favorites": "Ø¨Ų‡ ØšŲ„Ø§Ų‚Ų‡ Ų…Ų†Ø¯ÛŒ Ų‡Ø§ اØļØ§ŲŲ‡ شد", "added_to_favorites_count": "{count, number} ØĒا Ø¨Ų‡ ØšŲ„Ø§Ų‚Ų‡ Ų…Ų†Ø¯ÛŒ Ų‡Ø§ اØļØ§ŲŲ‡ شد", @@ -45,7 +52,7 @@ "authentication_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§Ø­ØąØ§Ø˛ Ų‡ŲˆÛŒØĒ", "authentication_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØąŲ…Ø˛ ØšØ¨ŲˆØąØŒ OAuth، ؈ ØŗØ§ÛŒØą ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§Ø­ØąØ§Ø˛ Ų‡ŲˆÛŒØĒ", "authentication_settings_disable_all": "Øĸیا Ų…ØˇŲ…ØĻŲ† Ų‡ØŗØĒید ÚŠŲ‡ Ų…ÛŒâ€ŒØŽŲˆØ§Ų‡ÛŒØ¯ ØĒŲ…Ø§Ų… ØąŲˆØ´â€ŒŲ‡Ø§ÛŒ ŲˆØąŲˆØ¯ ØąØ§ ØēÛŒØąŲØšØ§Ų„ ÚŠŲ†ÛŒØ¯ØŸ ŲˆØąŲˆØ¯ Ø¨Ų‡ ØˇŲˆØą ÚŠØ§Ų…Ų„ ØēÛŒØąŲØšØ§Ų„ ØŽŲˆØ§Ų‡Ø¯ شد.", - "authentication_settings_reenable": "Ø¨ØąØ§ÛŒ ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ų…ØŦدد، Ø§Ø˛ Ø¯ØŗØĒŲˆØą ØŗØąŲˆØą Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯.", + "authentication_settings_reenable": "Ø¨ØąØ§ÛŒ ŲØšØ§Ų„ ØŗØ§Ø˛ÛŒ Ų…ØŦدد Ø§Ø˛ Ø¯ØŗØĒŲˆØą ØŗØąŲˆØą Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯.", "background_task_job": "ŲˆØ¸Ø§ÛŒŲ ŲžØŗâ€ŒØ˛Ų…ÛŒŲ†Ų‡", "backup_database": "اØļØ§ŲŲ‡ ÚŠØąØ¯Ų† یڊ Ų†ØŗØŽŲ‡ ÚŠŲžÛŒ Ø§Ø˛ دیØĒØ§Ø¨ÛŒØŗ", "backup_database_enable_description": "ŲØšØ§Ų„ ÚŠØąØ¯Ų† ÚŠŲžÛŒ Ø§Ø˛ دیØĒØ§Ø¨ÛŒØŗ", @@ -53,7 +60,7 @@ "backup_onboarding_1_description": "ÚŠŲžÛŒ ØŽØ§ØąØŦی ØąŲˆÛŒ ؁Øļای Ø§Ø¨ØąÛŒ یا یڊ Ų…Ø­Ų„ ŲÛŒØ˛ÛŒÚŠÛŒ Ø¯ÛŒÚ¯Øą.", "backup_onboarding_2_description": "ÚŠŲžÛŒâ€ŒŲ‡Ø§ÛŒ Ų…Ø­Ų„ÛŒ ØąŲˆÛŒ Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ Ø¯ÛŒÚ¯Øą. Ø§ÛŒŲ† Ø´Ø§Ų…Ų„ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ اØĩŲ„ÛŒ ؈ ŲžØ´ØĒÛŒØ¨Ø§Ų†â€ŒŲ‡Ø§ÛŒ Ų…Ø­Ų„ÛŒ Ø§Ø˛ ØĸŲ† ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ų…ÛŒâ€ŒØ¨Ø§Ø´Ø¯.", "backup_onboarding_3_description": "Ų…ØŦŲ…ŲˆØš ÚŠŲžÛŒâ€ŒŲ‡Ø§ÛŒ Ø¯Ø§Ø¯Ų‡â€ŒŲ‡Ø§ÛŒ Ø´Ų…Ø§ØŒ Ø¨Ų‡ Ų‡Ų…ØąØ§Ų‡ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ اØĩŲ„ÛŒ. Ø§ÛŒŲ† Ø´Ø§Ų…Ų„ Ûą ÚŠŲžÛŒ ØŽØ§ØąØŦی ؈ Û˛ ÚŠŲžÛŒ Ų…Ø­Ų„ÛŒ Ų…ÛŒâ€ŒØ¨Ø§Ø´Ø¯.", - "backup_onboarding_description": "Ø¨ØąØ§ÛŒ Ų…Ø­Ø§ŲØ¸ØĒ Ø§Ø˛ Ø¯Ø§Ø¯Ų‡â€ŒŲ‡Ø§ÛŒ Ø´Ų…Ø§ØŒ یڊ Ø§ØŗØĒØąØ§ØĒژی ŲžØ´ØĒÛŒØ¨Ø§Ų†â€ŒÚ¯ÛŒØąÛŒ Ûŗ-Û˛-Ûą ØĒ؈ØĩÛŒŲ‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ø´Ų…Ø§ باید Ų†ØŗØŽŲ‡â€ŒŲ‡Ø§ÛŒÛŒ Ø§Ø˛ ØĒØĩØ§ŲˆÛŒØą/ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ ØĸŲžŲ„ŲˆØ¯Ø´Ø¯Ų‡ ؈ Ų‡Ų…Ú†Ų†ÛŒŲ† ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡ Immich ØąØ§ Ø¨ØąØ§ÛŒ یڊ ØąØ§Ų‡ÚŠØ§Øą ŲžØ´ØĒÛŒØ¨Ø§Ų†â€ŒÚ¯ÛŒØąÛŒ ØŦØ§Ų…Øš Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ ÚŠŲ†ÛŒØ¯.", + "backup_onboarding_description": "Ø¨ØąØ§ÛŒ Ø­ŲØ§Ø¸ØĒ Ø§Ø˛ Ø§ØˇŲ„Ø§ØšØ§ØĒ Ø´Ų…Ø§ یڊ ØąŲˆØ´ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ûŗ-Û˛-Ûą ŲžÛŒØ´Ų†Ų‡Ø§Ø¯ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ø¨ØąØ§ÛŒ یڊ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ ØŦØ§Ų…ØšØŒ Ø´Ų…Ø§ باید ÚŠŲžÛŒâ€ŒŲ‡Ø§ÛŒÛŒ Ø§Ø˛ ØšÚŠØŗâ€ŒŲ‡Ø§/ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ ØĸŲžŲ„ŲˆØ¯ Ø´Ø¯Ų‡ ØŽŲˆØ¯ Ø¨Ų‡ Ų‡Ų…ØąØ§Ų‡ دیØĒØ§Ø¨ÛŒØŗ Immich Ų†Ú¯Ų‡ Ø¯Ø§ØąÛŒØ¯.", "backup_onboarding_footer": "Ø¨ØąØ§ÛŒ Ø§ØˇŲ„Ø§ØšØ§ØĒ بیشØĒØą Ø¯ØąØ¨Ø§ØąŲ‡ بڊ ØĸŲž Ú¯ÛŒØąÛŒ Ø§Ø˛ Immich، Ų„ØˇŲØ§ Ø¨Ų‡ Ų…ØŗØĒŲ†Ø¯Ø§ØĒ Ų…ØąØ§ØŦØšŲ‡ ÚŠŲ†ÛŒØ¯.", "backup_onboarding_parts_title": "ØąŲˆØ´ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ûŗ-Û˛-Ûą Ø´Ø§Ų…Ų„:", "backup_onboarding_title": "بڊ ØĸŲž Ų‡Ø§", @@ -69,21 +76,15 @@ "confirm_user_pin_code_reset": "Øĸیا Ų…ØˇŲ…ØĻŲ† Ų‡ØŗØĒید ÚŠŲ‡ Ų…ÛŒâ€ŒØŽŲˆØ§Ų‡ÛŒØ¯ ڊد PIN ‏{user} ØąØ§ Ø¨Ø§Ø˛Ų†Ø´Ø§Ų†ÛŒ ÚŠŲ†ÛŒØ¯ØŸ", "copy_config_to_clipboard_description": "ÚŠŲžÛŒ ÚŠØ§Ų†ŲÛŒÚ¯ ŲØšŲ„ÛŒ ØŗÛŒØŗØĒŲ… Ø¯Øą Ų‚Ø§Ų„Ø¨ یڊ ØĸبØŦÚŠØĒ JSON Ø¯Øą ÚŠŲ„ÛŒŲž Ø¨ŲˆØąØ¯", "create_job": "ایØŦاد ØŦاب", - "cron_expression": "ØšØ¨Ø§ØąØĒ cron", - "cron_expression_description": "ŲØ§ØĩŲ„Ų‡ Ø§ØŗÚŠŲ† ØąØ§ با Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ŲØąŲ…ØĒ cron ØĒŲ†Ø¸ÛŒŲ… ÚŠŲ†ÛŒØ¯. Ø¨ØąØ§ÛŒ Ø§ØˇŲ„Ø§ØšØ§ØĒ بیشØĒØąØŒ Ų„ØˇŲØ§Ų‹ Ø¨Ų‡ ØšŲ†ŲˆØ§Ų† Ų…ØĢØ§Ų„ Ø¨Ų‡ Crontab Guru Ų…ØąØ§ØŦØšŲ‡ Ų†Ų…Ø§ÛŒÛŒØ¯", - "cron_expression_presets": "ŲžÛŒØ´â€ŒØĒŲ†Ø¸ÛŒŲ…â€ŒŲ‡Ø§ÛŒ ØšØ¨Ø§ØąØĒ cron", "disable_login": "ØēÛŒØąŲØšØ§Ų„ ÚŠØąØ¯Ų† ŲˆØąŲˆØ¯", - "download_csv": "Ø¯Ø§Ų†Ų„ŲˆØ¯ csv", "duplicate_detection_job_description": "اØŦØąØ§ÛŒ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ† Ø¨Øą ØąŲˆÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ø¨ØąØ§ÛŒ Ø´Ų†Ø§ØŗØ§ÛŒÛŒ ØĒØĩØ§ŲˆÛŒØą Ų…Ø´Ø§Ø¨Ų‡. Ø§ÛŒŲ† ŲˆØ§Ø¨ØŗØĒŲ‡ Ø¨Ų‡ ØŦØŗØĒØŦŲˆÛŒ Ų‡ŲˆØ´Ų…Ų†Ø¯ Ø§ØŗØĒ", "exclusion_pattern_description": "Ø§Ų„Ú¯ŲˆŲ‡Ø§ÛŒ Ø§ØŗØĒØĢŲ†Ø§ Ø¨Ų‡ Ø´Ų…Ø§ Ø§Ų…ÚŠØ§Ų† Ų…ÛŒâ€ŒØ¯Ų‡Ø¯ Ų‡Ų†Ú¯Ø§Ų… Ø§ØŗÚŠŲ† ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡ ØŽŲˆØ¯ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ؈ ŲžŲˆØ´Ų‡â€ŒŲ‡Ø§ ØąØ§ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ø¨Ú¯ÛŒØąÛŒØ¯ . Ø§ÛŒŲ† Ų…ŲÛŒØ¯ Ø§ØŗØĒ Ø§Ú¯Øą ŲžŲˆØ´Ų‡â€ŒŲ‡Ø§ÛŒÛŒ Ø¯Ø§ØąÛŒØ¯ ÚŠŲ‡ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒÛŒ ØąØ§ Ø´Ø§Ų…Ų„ Ų…ÛŒâ€ŒØ´ŲˆŲ†Ø¯ ÚŠŲ‡ Ų†Ų…ÛŒâ€ŒØŽŲˆØ§Ų‡ÛŒØ¯ ŲˆØ§ØąØ¯ ÚŠŲ†ÛŒØ¯ØŒ Ų…Ø§Ų†Ų†Ø¯ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ RAW.", "export_config_as_json_description": "Ø¯Ø§Ų†Ų„ŲˆØ¯ ÚŠØ§Ų†ŲÛŒÚ¯ ŲØšŲ„ÛŒ ØŗÛŒØŗØĒŲ… Ø¯Øą Ų‚Ø§Ų„Ø¨ یڊ ŲØ§ÛŒŲ„ JSON", - "external_libraries_page_description": "ØĩŲØ­Ų‡ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡ ØŽØ§ØąØŦی ب؎ش Ų…Ø¯ÛŒØąÛŒØĒ", "face_detection": "ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡", "face_detection_description": "ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ Ø¯Øą ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ با Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ†. Ø¨ØąØ§ÛŒ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ØŒ ØĒŲ†Ų‡Ø§ ØĒØĩŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ø¯Øą Ų†Ø¸Øą Ú¯ØąŲØĒŲ‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ú¯Ø˛ÛŒŲ†Ų‡ \"Ų‡Ų…Ų‡\" ØĒŲ…Ø§Ų… ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ØąØ§ (Ų…ØŦددا) ŲžØąØ¯Ø§Ø˛Ø´ Ų…ÛŒâ€ŒÚŠŲ†Ø¯. Ú¯Ø˛ÛŒŲ†Ų‡ \"Ú¯Ų…Ø´Ø¯Ų‡\" ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ØąØ§ Ø¯Øą Øĩ؁ Ų‚ØąØ§Øą Ų…ÛŒâ€ŒØ¯Ų‡Ø¯ ÚŠŲ‡ Ų‡Ų†ŲˆØ˛ ŲžØąØ¯Ø§Ø˛Ø´ Ų†Ø´Ø¯Ų‡â€ŒØ§Ų†Ø¯. Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ÛŒ ØĒØ´ØŽÛŒØĩ Ø¯Ø§Ø¯Ų‡ Ø´Ø¯Ų‡ ŲžØŗ Ø§Ø˛ اØĒŲ…Ø§Ų… ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡ØŒ Ø¨ØąØ§ÛŒ ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡ Ø¨Ų‡ ØĩŲˆØąØĒ Øĩ؁ Ø§Ų†ØĒØ¸Ø§Øą Ų‚ØąØ§Øą Ų…ÛŒâ€ŒÚ¯ÛŒØąŲ†Ø¯ØŒ ØĸŲ†â€ŒŲ‡Ø§ ØąØ§ Ø¨Ų‡ Ø§ŲØąØ§Ø¯ Ų…ŲˆØŦŲˆØ¯ یا ØŦدید Ú¯ØąŲˆŲ‡â€ŒØ¨Ų†Ø¯ÛŒ Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "facial_recognition_job_description": "Ú¯ØąŲˆŲ‡â€ŒØ¨Ų†Ø¯ÛŒ Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ÛŒ ØĒØ´ØŽÛŒØĩ Ø¯Ø§Ø¯Ų‡ Ø´Ø¯Ų‡ Ø¨Ų‡ Ø§ŲØąØ§Ø¯. Ø§ÛŒŲ† Ų…ØąØ­Ų„Ų‡ ŲžØŗ Ø§Ø˛ ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡ Ø§Ų†ØŦØ§Ų… Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ú¯Ø˛ÛŒŲ†Ų‡ \"Ų‡Ų…Ų‡\" ØĒŲ…Ø§Ų… Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ ØąØ§ (Ų…ØŦددا) Ø¯ØŗØĒŲ‡ Ø¨Ų†Ø¯ÛŒ Ų…ÛŒâ€ŒÚŠŲ†Ø¯. Ú¯Ø˛ÛŒŲ†Ų‡ \"Ú¯Ų…Ø´Ø¯Ų‡\" Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ ØąØ§ Ø¯Øą Øĩ؁ Ų‚ØąØ§Øą Ų…ÛŒâ€ŒØ¯Ų‡Ø¯ ÚŠŲ‡ Ø¨Ų‡ Ų‡ÛŒÚ† ŲØąØ¯ÛŒ ا؎ØĒØĩاØĩ Ø¯Ø§Ø¯Ų‡ Ų†Ø´Ø¯Ų‡â€ŒØ§Ų†Ø¯.", "failed_job_command": "Ø¯ØŗØĒŲˆØą {command} Ø¨ØąØ§ÛŒ ÚŠØ§Øą: {job} Ų†Ø§Ų…ŲˆŲŲ‚ Ø¨ŲˆØ¯", "force_delete_user_warning": "Ų‡Ø´Ø¯Ø§Øą: Ø§ÛŒŲ† ØšŲ…Ų„ باؚØĢ Ø­Ø°Ų ŲŲˆØąÛŒ ÚŠØ§ØąØ¨Øą ؈ ØĒŲ…Ø§Ų… ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ø§ÛŒŲ† ØšŲ…Ų„ Ų‚Ø§Ø¨Ų„ Ø¨Ø§Ø˛Ú¯Ø´ØĒ Ų†ÛŒØŗØĒ ؈ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ų‚Ø§Ø¨Ų„ Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ Ų†ÛŒØŗØĒŲ†Ø¯.", - "image_format": "ŲØąŲ…ØĒ", "image_format_description": "ŲØąŲ…ØĒ WebP ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ÚŠŲˆÚ†ÚŠØĒØąÛŒ Ų†ØŗØ¨ØĒ Ø¨Ų‡ JPEG ایØŦاد Ų…ÛŒâ€ŒÚŠŲ†Ø¯ØŒ Ø§Ų…Ø§ Ø˛Ų…Ø§Ų† ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ ØĸŲ† ÚŠŲ†Ø¯ØĒØą Ø§ØŗØĒ.", "image_fullsize_description": "ØĒØĩŲˆÛŒØą با Ø§Ų†Ø¯Ø§Ø˛Ų‡ ÚŠØ§Ų…Ų„ ؈ Ø¨Ø¯ŲˆŲ† ŲØąØ§Ø¯Ø§Ø¯Ų‡ØŒ Ų…ŲˆØąØ¯ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų‡Ų†Ú¯Ø§Ų… Ø¨Ø˛ØąÚ¯â€ŒŲ†Ų…Ø§ÛŒÛŒ", "image_fullsize_enabled": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ ØĒŲˆŲ„ÛŒØ¯ ØĒØĩŲˆÛŒØą با Ø§Ų†Ø¯Ø§Ø˛Ų‡ ÚŠØ§Ų…Ų„", @@ -97,45 +98,20 @@ "image_preview_description": "ØĒØĩŲˆÛŒØą با Ø§Ų†Ø¯Ø§Ø˛Ų‡ Ų…ØĒŲˆØŗØˇ ؈ Ø¨Ø¯ŲˆŲ† ŲØąØ§Ø¯Ø§Ø¯Ų‡ØŒ Ų…ŲˆØąØ¯ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų‡Ų†Ú¯Ø§Ų… Ų…Ø´Ø§Ų‡Ø¯Ų‡ یڊ Ø¯Ø§ØąØ§ÛŒÛŒ ؈ Ø¨ØąØ§ÛŒ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ†", "image_preview_quality_description": "ÚŠÛŒŲÛŒØĒ ŲžÛŒØ´â€ŒŲ†Ų…Ø§ÛŒØ´ Ø§Ø˛ Ûą ØĒا ÛąÛ°Û°. Ų‡ØąÚ†Ų‡ Ø¨Ø§Ų„Ø§ØĒØą باشد، ÚŠÛŒŲÛŒØĒ Ø¨Ų‡ØĒØą Ø§ØŗØĒ، Ø§Ų…Ø§ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ø¨Ø˛ØąÚ¯â€ŒØĒØąÛŒ ایØŦاد Ų…ÛŒâ€ŒÚŠŲ†Ø¯ ؈ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ŲžØ§ØŗØŽâ€ŒÚ¯ŲˆÛŒÛŒ Ø¨ØąŲ†Ø§Ų…Ų‡ ÚŠØ§Ų‡Ø´ یابد. ØĒŲ†Ø¸ÛŒŲ… Ų…Ų‚Ø¯Ø§Øą ŲžØ§ÛŒÛŒŲ† Ų…ÛŒâ€ŒØĒŲˆØ§Ų†Ø¯ Ø¨Øą ÚŠÛŒŲÛŒØĒ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ† ØĒØŖØĢÛŒØą Ø¨Ú¯Ø°Ø§ØąØ¯.", "image_preview_title": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ŲžÛŒØ´â€ŒŲ†Ų…Ø§ÛŒØ´", - "image_progressive": "ŲžÛŒØ´â€ŒØąŲˆŲ†Ø¯Ų‡", - "image_progressive_description": "ØĒØĩØ§ŲˆÛŒØą JPEG ØąØ§ Ø¨Ų‡â€ŒØĩŲˆØąØĒ ŲžÛŒØ´â€ŒØąŲˆŲ†Ø¯Ų‡ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ Ų†Ų…Ø§ÛŒÛŒØ¯ ØĒا Ø¯Øą Ø­ÛŒŲ† Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø¨Ų‡â€ŒØĒØ¯ØąÛŒØŦ Ų†Ų…Ø§ÛŒØ´ Ø¯Ø§Ø¯Ų‡ Ø´ŲˆŲ†Ø¯. Ø§ÛŒŲ† ØĒŲ†Ø¸ÛŒŲ… Ø¨Øą ØĒØĩØ§ŲˆÛŒØą WebP ØĒØŖØĢÛŒØąÛŒ Ų†Ø¯Ø§ØąØ¯.", "image_quality": "ÚŠÛŒŲÛŒØĒ", "image_resolution": "؈ØļŲˆØ­ ØĒØĩŲˆÛŒØą", "image_resolution_description": "؈ØļŲˆØ­ Ø¨Ø§Ų„Ø§ØĒØą Ų…ÛŒâ€ŒØĒŲˆØ§Ų†Ø¯ ØŦØ˛ØĻیاØĒ بیشØĒØąÛŒ ØąØ§ Ø­ŲØ¸ ÚŠŲ†Ø¯ØŒ Ø§Ų…Ø§ ØĒØ¨Ø¯ÛŒŲ„ ØĸŲ† Ø˛Ų…Ø§Ų† بیشØĒØąÛŒ Ų…ÛŒâ€ŒØ¨ØąØ¯ØŒ Ø­ØŦŲ… ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ØąØ§ Ø§ŲØ˛Ø§ÛŒØ´ Ų…ÛŒâ€ŒØ¯Ų‡Ø¯ ؈ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ŲžØ§ØŗØŽâ€ŒÚ¯ŲˆÛŒÛŒ Ø¨ØąŲ†Ø§Ų…Ų‡ ØąØ§ ÚŠØ§Ų‡Ø´ Ø¯Ų‡Ø¯.", "image_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ØšÚŠØŗ", "image_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ÚŠÛŒŲÛŒØĒ ؈ ؈ØļŲˆØ­ ØĒØĩØ§ŲˆÛŒØą ØĒŲˆŲ„ÛŒØ¯ Ø´Ø¯Ų‡", - "image_thumbnail_description": "ØĒØĩŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی ÚŠŲˆÚ†ÚŠ ؈ Ø¨Ø¯ŲˆŲ† Ø§ØˇŲ„Ø§ØšØ§ØĒ ØŦØ§Ų†Ø¨ÛŒØŒ Ø¨ØąØ§ÛŒ Ų†Ų…Ø§ÛŒØ´ Ø¯ØŗØĒŲ‡â€ŒØ§ÛŒ ØšÚŠØŗâ€ŒŲ‡Ø§ Ų…Ø§Ų†Ų†Ø¯ ØŽØˇ Ø˛Ų…Ø§Ų†ÛŒ اØĩŲ„ÛŒ", - "image_thumbnail_quality_description": "ÚŠÛŒŲÛŒØĒ Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ø§Ø˛ Ûą ØĒا ÛąÛ°Û°. Ų…Ų‚Ø§Ø¯ÛŒØą Ø¨Ø§Ų„Ø§ØĒØą ÚŠÛŒŲÛŒØĒ Ø¨Ų‡ØĒØąÛŒ Ø¯Ø§ØąŲ†Ø¯ØŒ Ø§Ų…Ø§ Ø­ØŦŲ… ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ØąØ§ Ø§ŲØ˛Ø§ÛŒØ´ Ø¯Ø§Ø¯Ų‡ ؈ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ØŗØąØšØĒ ŲžØ§ØŗØŽÚ¯ŲˆÛŒÛŒ Ø¨ØąŲ†Ø§Ų…Ų‡ ØąØ§ ÚŠØ§Ų‡Ø´ Ø¯Ų‡Ų†Ø¯.", - "image_thumbnail_title": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ØĒØĩŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی", "import_config_from_json_description": "ŲˆØ§ØąØ¯ ÚŠØąØ¯Ų† ÚŠØ§Ų†ŲÛŒÚ¯ ØŗÛŒØŗØĒŲ… با ØĸŲžŲ„ŲˆØ¯ یڊ ŲØ§ÛŒŲ„ JSON", - "integrity_checks_checksum_files": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ checksum", - "integrity_checks_checksum_files_description": "ŲžÛŒÚŠØąØ¨Ų†Ø¯ÛŒ Ø¨ØąØąØŗÛŒ checksum", - "integrity_checks_checksum_files_enable_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ checksum", - "integrity_checks_checksum_files_percentage_limit": "Ų…Ø­Ø¯ŲˆØ¯ÛŒØĒ Ø¯ØąØĩد", - "integrity_checks_checksum_files_percentage_limit_description": "حداڊØĢØą Ø¯ØąØĩد Ø¨ÛŒŲ† Û°.Û°Ûą ؈ Ûą ØąØ§ ØŦŲ‡ØĒ ØĒØšÛŒÛŒŲ† Ų…ÛŒØ˛Ø§Ų† اØŦØąØ§ÛŒ Ø¨ØąØąØŗÛŒ checksum Ø¯Øą Ų‡Øą Ø¨Ø§Ø˛Ų‡ Ø˛Ų…Ø§Ų†ÛŒ ŲžÛŒÚŠØąØ¨Ų†Ø¯ÛŒ Ų†Ų…Ø§ÛŒÛŒØ¯.", - "integrity_checks_checksum_files_time_limit": "Ų…Ø­Ø¯ŲˆØ¯ÛŒØĒ Ø˛Ų…Ø§Ų†", - "integrity_checks_checksum_files_time_limit_description": "حداڊØĢØą Ų…Ø¯ØĒ Ø˛Ų…Ø§Ų† اØŦØąØ§ÛŒ Ø¨ØąØąØŗÛŒ Checksum Ø¯Øą Ų‡Øą Ø¨Ø§Ø˛Ų‡ ØąØ§ ŲžÛŒÚŠØąØ¨Ų†Ø¯ÛŒ Ų†Ų…Ø§ÛŒÛŒØ¯. (Ų…ÛŒŲ„ÛŒâ€ŒØĢØ§Ų†ÛŒŲ‡)", - "integrity_checks_missing_files": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯", - "integrity_checks_missing_files_description": "ØĒŲ†Ø¸ÛŒŲ… ŲØ§ØĩŲ„Ų‡ Ø˛Ų…Ø§Ų†ÛŒ ؈ ŲØšØ§Ų„ یا ØēÛŒØąŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯", - "integrity_checks_missing_files_enable_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯Ø´Ø¯Ų‡", - "integrity_checks_settings": "Ø¨ØąØąØŗÛŒâ€ŒŲ‡Ø§ÛŒ ÛŒÚŠŲžØ§ØąÚ†Ú¯ÛŒ", - "integrity_checks_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ŲŲˆØ§ØĩŲ„ Ø˛Ų…Ø§Ų†ÛŒ Ø¨ØąØąØŗÛŒâ€ŒŲ‡Ø§ÛŒ ÛŒÚŠŲžØ§ØąÚ†Ú¯ÛŒ", - "integrity_checks_untracked_files": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", - "integrity_checks_untracked_files_description": "ØĒŲ†Ø¸ÛŒŲ… ŲØ§ØĩŲ„Ų‡ Ø˛Ų…Ø§Ų†ÛŒ ؈ ŲØšØ§Ų„ یا ØēÛŒØąŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", - "integrity_checks_untracked_files_enable_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", "job_concurrency": "Ų‡Ų…Ø˛Ų…Ø§Ų†ÛŒ {job}", "job_created": "ØŦاب ØŗØ§ØŽØĒŲ‡ شد", "job_not_concurrency_safe": "Ø§ÛŒŲ† ÚŠØ§Øą Ø§ÛŒŲ…Ų†ÛŒ Ų‡Ų…Ø˛Ų…Ø§Ų†ÛŒ ØąØ§ ØĒØļŲ…ÛŒŲ† Ų†Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "job_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ÚŠØ§Øą", "job_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ Ų‡Ų…Ø˛Ų…Ø§Ų†ÛŒ ÚŠØ§Øą", - "jobs_delayed": "{jobCount, plural, other {# Ø¨Ų‡ ØĒØŖØŽÛŒØą Ø§ŲØĒØ§Ø¯Ų‡}}", - "jobs_failed": "{jobCount, plural, other {# Ų†Ø§Ų…ŲˆŲŲ‚}}", - "jobs_over_time": "ŲˆØ¸Ø§ÛŒŲ Ø¯Øą ØˇŲˆŲ„ Ø˛Ų…Ø§Ų†", "library_created": "ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡ ایØŦاد Ø´Ø¯Ų‡: {library}", "library_deleted": "ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡ Ø­Ø°Ų شد", - "library_details": "ØŦØ˛ØĻیاØĒ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", "library_folder_description": "یڊ ŲžŲˆØ´Ų‡ Ø¨ØąØ§ÛŒ Ø§ÛŒŲ…ŲžŲˆØąØĒ Ų…Ø´ØŽØĩ ÚŠŲ†ÛŒØ¯. Ø§ÛŒŲ† ŲžŲˆØ´Ų‡ ؈ ŲžŲˆØ´Ų‡ Ų‡Ø§ÛŒ Ø¯Ø§ØŽŲ„ ØĸŲ†ØŒ Ø¨ØąØ§ÛŒ ØšÚŠØŗ Ų‡Ø§ ؈ ŲˆÛŒØ¯ÛŒŲˆ Ų‡Ø§ Ø§ØŗÚŠŲ† Ų…ÛŒ Ø´ŲˆŲ†Ø¯.", - "library_remove_exclusion_pattern_prompt": "Øĸیا Ø§Ø˛ Ø­Ø°Ų Ø§ÛŒŲ† Ø§Ų„Ú¯ŲˆÛŒ Ø§ØŗØĒØĢŲ†Ø§ Ø§ØˇŲ…ÛŒŲ†Ø§Ų† Ø¯Ø§ØąÛŒØ¯ØŸ", "library_remove_folder_prompt": "Øĸیا Ø§Ø˛ Ø­Ø°Ų Ø§ÛŒŲ† ŲžŲˆØ´Ų‡ Ø§ÛŒŲ…ŲžŲˆØąØĒ Ų…ØˇŲ…ØĻŲ† Ų‡ØŗØĒید؟", "library_scanning": "Ø§ØŗÚŠŲ† Ø¯ŲˆØąŲ‡ ای", "library_scanning_description": "ØĒŲ†Ø¸ÛŒŲ… Ø§ØŗÚŠŲ† Ø¯ŲˆØąŲ‡â€ŒØ§ÛŒ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", @@ -179,7 +155,6 @@ "machine_learning_min_detection_score_description": "Ø­Ø¯Ø§Ų‚Ų„ Ø§Ų…ØĒÛŒØ§Ø˛ اؚØĒŲ…Ø§Ø¯ Ø¨ØąØ§ÛŒ ØĒØ´ØŽÛŒØĩ یڊ Ú†Ų‡ØąŲ‡ØŒ Ø¯Øą Ø¨Ø§Ø˛Ų‡ 0 ØĒا 1 Ų‚ØąØ§Øą Ø¯Ø§ØąØ¯. Ų…Ų‚Ø§Ø¯ÛŒØą ÚŠŲ…ØĒØą باؚØĢ ØĒØ´ØŽÛŒØĩ بیشØĒØą Ú†Ų‡ØąŲ‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯ØŒ Ø§Ų…Ø§ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ Ų…Ų†ØŦØą Ø¨Ų‡ ØĒØ´ØŽÛŒØĩâ€ŒŲ‡Ø§ÛŒ اشØĒØ¨Ø§Ų‡ Ø´ŲˆØ¯.", "machine_learning_min_recognized_faces": "Ø­Ø¯Ø§Ų‚Ų„ Ú†Ų‡ØąŲ‡ Ų‡Ø§ÛŒ Ø´Ų†Ø§ØŽØĒŲ‡ Ø´Ø¯Ų‡", "machine_learning_min_recognized_faces_description": "Ø­Ø¯Ø§Ų‚Ų„ ØĒؚداد Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ÛŒ ØĒØ´ØŽÛŒØĩ Ø¯Ø§Ø¯Ų‡ Ø´Ø¯Ų‡ Ø¨ØąØ§ÛŒ ایØŦاد یڊ Ø´ØŽØĩ. Ø§ŲØ˛Ø§ÛŒØ´ Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą باؚØĢ Ø¯Ų‚ÛŒŲ‚â€ŒØĒØą Ø´Ø¯Ų† ØĒØ´ØŽÛŒØĩ Ú†Ų‡ØąŲ‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯ØŒ Ø§Ų…Ø§ Ų‡Ų…Ø˛Ų…Ø§Ų† باؚØĢ Ø§ŲØ˛Ø§ÛŒØ´ احØĒŲ…Ø§Ų„ Ø§ÛŒŲ† Ų…ÛŒâ€ŒØ´ŲˆØ¯ ÚŠŲ‡ یڊ Ú†Ų‡ØąŲ‡ Ø¨Ų‡ یڊ Ø´ØŽØĩ Ų†ØŗØ¨ØĒ Ø¯Ø§Ø¯Ų‡ Ų†Ø´ŲˆØ¯.", - "machine_learning_ocr": "Ø§ØŗØĒØŽØąØ§ØŦ Ų…ØĒŲ† Ø§Ø˛ ØĒØĩŲˆÛŒØą (OCR)", "machine_learning_ocr_description": "Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ† Ø¨ØąØ§ÛŒ ØĒØ´ØŽÛŒØĩ Ų…ØĒŲ† Ø¯Ø§ØŽŲ„ ØšÚŠØŗ Ų‡Ø§", "machine_learning_ocr_enabled": "ŲØšØ§Ų„ ØŗØ§Ø˛ÛŒ OCR", "machine_learning_ocr_enabled_description": "Ø§Ú¯Øą ØēÛŒØą ŲØšØ§Ų„ باشد، ØĒØ´ØŽÛŒØĩ Ų…ØĒŲ† ØąŲˆÛŒ ØšÚŠØŗ Ų‡Ø§ Ø§Ų†ØŦØ§Ų… Ų†Ų…ÛŒ Ø´ŲˆØ¯.", @@ -198,37 +173,6 @@ "machine_learning_smart_search_enabled": "ŲØšØ§Ų„ ØŗØ§Ø˛ÛŒ ØŦØŗØĒØŦŲˆÛŒ Ų‡ŲˆØ´Ų…Ų†Ø¯", "machine_learning_smart_search_enabled_description": "Ø§Ú¯Øą ØēÛŒØąŲØšØ§Ų„ باشد، ØĒØĩØ§ŲˆÛŒØą Ø¨ØąØ§ÛŒ ØŦØŗØĒØŦŲˆÛŒ Ų‡ŲˆØ´Ų…Ų†Ø¯ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ Ų†ØŽŲˆØ§Ų‡Ų†Ø¯ شد.", "machine_learning_url_description": "ØĸØ¯ØąØŗ ØŗØąŲˆØą ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ†. Ø§Ú¯Øą بیش Ø§Ø˛ یڊ ØĸØ¯ØąØŗ Ø¯Ø§Ø¯Ų‡ Ø´ŲˆØ¯ØŒ Ų‡Øą ØŗØąŲˆØą بØĩŲˆØąØĒ یڊی Ø¯Øą Ų„Ø­Ø¸Ų‡ Ø§Ų…ØĒØ­Ø§Ų† Ų…ÛŒ Ø´ŲˆŲ†Ø¯ ØĒا Ø˛Ų…Ø§Ų†ÛŒ ÚŠŲ‡ یڊی Ø§Ø˛ ØĸŲ†Ų‡Ø§ با Ų…ŲˆŲŲ‚ÛŒØĒ ŲžØ§ØŗØŽ Ø¯Ų‡Ø¯ØŒ Ø§Ø˛ Ø§ŲˆŲ„ Ø¨Ų‡ ØĸØŽØą. ØŗØąŲˆØą Ų‡Ø§ÛŒÛŒ ÚŠŲ‡ ŲžØ§ØŗØŽ Ų†Ø¯Ų‡Ų†Ø¯ بØĩŲˆØąØĒ Ų…ŲˆŲ‚ØĒ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ú¯ØąŲØĒŲ‡ Ų…ÛŒ Ø´ŲˆŲ†Ø¯ ØĒا Ø˛Ų…Ø§Ų†ÛŒ ÚŠŲ‡ Ø¨Ų‡ ؈ØļØšÛŒØĒ ØĸŲ†Ų„Ø§ÛŒŲ† Ø¨ØąÚ¯ØąØ¯Ų†Ø¯.", - "maintenance_backup_management": "Ų…Ø¯ÛŒØąÛŒØĒ ŲžØ´ØĒÛŒØ¨Ø§Ų†â€ŒÚ¯ÛŒØąÛŒ", - "maintenance_delete_backup": "Ø­Ø°Ų ŲžØ´ØĒÛŒØ¨Ø§Ų†", - "maintenance_delete_backup_description": "Ø§ÛŒŲ† ŲØ§ÛŒŲ„ Ø¨Ų‡â€ŒØˇŲˆØą ØēÛŒØąŲ‚Ø§Ø¨Ų„ Ø¨Ø§Ø˛Ú¯Ø´ØĒ Ø­Ø°Ų ØŽŲˆØ§Ų‡Ø¯ شد.", - "maintenance_delete_error": "ØŽØˇØ§ Ø¯Øą Ø­Ø°Ų ŲžØ´ØĒÛŒØ¨Ø§Ų†.", - "maintenance_integrity_check": "Ø¨ØąØąØŗÛŒ", - "maintenance_integrity_check_all": "Ø¨ØąØąØŗÛŒ Ų‡Ų…Ų‡", - "maintenance_integrity_checksum_mismatch": "ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ Checksum", - "maintenance_integrity_checksum_mismatch_description": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Checksum ØĸŲ†â€ŒŲ‡Ø§ ØąŲˆÛŒ Ø¯ÛŒØŗÚŠ با Checksum Ø°ØŽÛŒØąŲ‡â€ŒØ´Ø¯Ų‡ Ø¯Øą ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡ Immich Ų…ØˇØ§Ø¨Ų‚ØĒ Ų†Ø¯Ø§ØąØ¯.", - "maintenance_integrity_checksum_mismatch_job": "Ø¨ØąØąØŗÛŒ ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ Checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ Ú¯Ø˛Ø§ØąØ´â€ŒŲ‡Ø§ÛŒ ØšØ¯Ų… ØĒØˇØ§Ø¨Ų‚ Checksum", - "maintenance_integrity_missing_file": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯Ø´Ø¯Ų‡", - "maintenance_integrity_missing_file_description": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Immich Ø¯Øą ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡ ØŽŲˆØ¯ ØąØ¯ÛŒØ§Ø¨ÛŒ ÚŠØąØ¯Ų‡ØŒ Ø§Ų…Ø§ Ø¯Øą ØŗÛŒØŗØĒŲ… ŲØ§ÛŒŲ„ ؈ØŦŲˆØ¯ Ų†Ø¯Ø§ØąŲ†Ø¯.", - "maintenance_integrity_missing_file_job": "Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯Ø´Ø¯Ų‡", - "maintenance_integrity_missing_file_refresh_job": "Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ Ú¯Ø˛Ø§ØąØ´â€ŒŲ‡Ø§ÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲŲ‚ŲˆØ¯Ø´Ø¯Ų‡", - "maintenance_integrity_report": "Ú¯Ø˛Ø§ØąØ´ ÛŒÚŠŲžØ§ØąÚ†Ú¯ÛŒ", - "maintenance_integrity_untracked_file": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", - "maintenance_integrity_untracked_file_description": "ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ų…ŲˆØŦŲˆØ¯ Ø¯Øą ŲžŲˆØ´Ų‡â€ŒŲ‡Ø§ÛŒ Immich ÚŠŲ‡ Ų‡ÛŒÚ† ØŗŲˆØ§Ø¨Ų‚ÛŒ Ø§Ø˛ ØĸŲ†â€ŒŲ‡Ø§ Ø¯Øą Immich ØĢبØĒ Ų†Ø´Ø¯Ų‡ Ø§ØŗØĒ.", - "maintenance_integrity_untracked_file_job": "Ø¨ØąØąØŗÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", - "maintenance_integrity_untracked_file_refresh_job": "Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ Ú¯Ø˛Ø§ØąØ´â€ŒŲ‡Ø§ÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØąØ¯ÛŒØ§Ø¨ÛŒâ€ŒŲ†Ø´Ø¯Ų‡", - "maintenance_restore_backup": "Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ ŲžØ´ØĒÛŒØ¨Ø§Ų†", - "maintenance_restore_backup_description": "Immich ŲžØ§ÚŠâ€ŒØŗØ§Ø˛ÛŒ Ø´Ø¯Ų‡ ؈ Ø§Ø˛ Ų†ØŗØŽŲ‡ ŲžØ´ØĒÛŒØ¨Ø§Ų† Ø§Ų†ØĒ؎ابی Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ų‚Ø¨Ų„ Ø§Ø˛ Ø´ØąŲˆØš ŲØąØĸÛŒŲ†Ø¯ØŒ یڊ Ų†ØŗØŽŲ‡ ŲžØ´ØĒÛŒØ¨Ø§Ų† ØŦدید ØĒŲ‡ÛŒŲ‡ ØŽŲˆØ§Ų‡Ø¯ شد.", - "maintenance_restore_backup_different_version": "Ø§ÛŒŲ† Ų†ØŗØŽŲ‡ ŲžØ´ØĒÛŒØ¨Ø§Ų† با Ų†ØŗØŽŲ‡ Ų…ØĒŲØ§ŲˆØĒی Ø§Ø˛ Immich ایØŦاد Ø´Ø¯Ų‡ Ø§ØŗØĒ!", - "maintenance_restore_backup_unknown_version": "Ø§Ų…ÚŠØ§Ų† ØĒØ´ØŽÛŒØĩ Ų†ØŗØŽŲ‡ Ø§ÛŒŲ† ŲžØ´ØĒÛŒØ¨Ø§Ų† ؈ØŦŲˆØ¯ Ų†Ø¯Ø§ØąØ¯.", - "maintenance_restore_database_backup": "Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ Ų†ØŗØŽŲ‡ ŲžØ´ØĒÛŒØ¨Ø§Ų† ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡", - "maintenance_restore_database_backup_description": "Ø¨Ø§Ø˛Ú¯ØąØ¯Ø§Ų†ÛŒ ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡ Ø¨Ų‡ ؈ØļØšÛŒØĒ Ų‚Ø¨Ų„ÛŒ با Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ŲØ§ÛŒŲ„ ŲžØ´ØĒÛŒØ¨Ø§Ų†", - "maintenance_settings": "ØĒØšŲ…ÛŒØąØ§ØĒ ؈ Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ", - "maintenance_settings_description": "Immich ØąØ§ Ø¯Øą Ø­Ø§Ų„ØĒ ØĒØšŲ…ÛŒØą ؈ Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ Ų‚ØąØ§Øą Ø¯Ų‡ÛŒØ¯.", - "maintenance_start": "ØĒØēÛŒÛŒØą Ø¨Ų‡ Ø­Ø§Ų„ØĒ ØĒØšŲ…ÛŒØą ؈ Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ", - "maintenance_start_error": "ØŽØˇØ§ Ø¯Øą Ø´ØąŲˆØš Ø­Ø§Ų„ØĒ ØĒØšŲ…ÛŒØą ؈ Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ.", - "maintenance_upload_backup": "â€ĻØ¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ ŲØ§ÛŒŲ„ ŲžØ´ØĒÛŒØ¨Ø§Ų† ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡", - "maintenance_upload_backup_error": "Ø§Ų…ÚŠØ§Ų† Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ ŲžØ´ØĒÛŒØ¨Ø§Ų† ؈ØŦŲˆØ¯ Ų†Ø¯Ø§ØąØ¯Ø› Øĸیا ŲØ§ÛŒŲ„ Ø§Ø˛ Ų†ŲˆØš .sql یا .sql.gz Ø§ØŗØĒ؟", "manage_concurrency": "Ų…Ø¯ÛŒØąÛŒØĒ Ų‡Ų…Ø˛Ų…Ø§Ų†ÛŒ", "manage_concurrency_description": "ØąŲØĒŲ† Ø¨Ų‡ ØĩŲØ­Ų‡ ØŦاب Ų‡Ø§ Ø¨ØąØ§ÛŒ Ų…Ø¯ÛŒØąÛŒØĒ Ų‡Ų…Ø˛Ų…Ø§Ų†ÛŒ ØŦاب Ų‡Ø§", "manage_log_settings": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ú¯Ø˛Ø§ØąØ´", @@ -238,7 +182,7 @@ "map_gps_settings_description": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ų†Ų‚Ø´Ų‡ ؈ ØŦÛŒâ€ŒŲžÛŒâ€ŒØ§Øŗ (ژØĻŲˆÚŠØ¯ÛŒŲ†Ú¯ Ų…ØšÚŠŲˆØŗ) ØąØ§ Ų…Ø¯ÛŒØąÛŒØĒ ÚŠŲ†ÛŒØ¯", "map_implications": "Ų‚Ø§Ø¨Ų„ÛŒØĒ Ų†Ų‚Ø´Ų‡ Ø¨Ų‡ یڊ ØŗØąŲˆÛŒØŗ tile ØŽØ§ØąØŦی Ų†ÛŒØ§Ø˛ Ø¯Ø§ØąØ¯ (tiles.immich.cloud)", "map_light_style": "Ø­Ø§Ų„ØĒ ØąŲˆØ´Ų†", - "map_manage_reverse_geocoding_settings": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ų…ØšÚŠŲˆØŗâ€ŒØŗØ§Ø˛ÛŒ Ų…ŲˆŲ‚ØšÛŒØĒ ØŦØēØąØ§ŲÛŒØ§ÛŒÛŒ", + "map_manage_reverse_geocoding_settings": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ Ų…ÚŠØ§Ų†ÛŒ Ų…ØšÚŠŲˆØŗ ", "map_reverse_geocoding": "ژØĻŲˆÚŠØ¯ÛŒŲ†Ú¯ Ų…ØšÚŠŲˆØŗ", "map_reverse_geocoding_enable_description": "ŲØšØ§Ų„ ØŗØ§Ø˛ÛŒ ژØĻŲˆÚŠØ¯ÛŒŲ†Ú¯ Ų…ØšÚŠŲˆØŗ", "map_reverse_geocoding_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ژØĻŲˆÚŠØ¯ÛŒŲ†Ú¯ Ų…ØšÚŠŲˆØŗ", @@ -246,7 +190,6 @@ "map_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ų†Ų‚Ø´Ų‡", "map_style_description": "ØĸØ¯ØąØŗ Ø§ÛŒŲ†ØĒØąŲ†ØĒی (style.json) Ų†ŲˆØš Ų†Ų…Ø§ÛŒØ´ Ų†Ų‚Ø´Ų‡", "memory_cleanup_job": "ŲžØ§ÚŠ ØŗØ§Ø˛ÛŒ Ø­Ø§ŲØ¸Ų‡", - "memory_generate_job": "ØĒŲˆŲ„ÛŒØ¯ ØŽØ§ØˇØąØ§ØĒ", "metadata_extraction_job": "Ø§ØŗØĒØŽØąØ§ØŦ ŲØąØ§ Ø¯Ø§Ø¯Ų‡", "metadata_extraction_job_description": "Ø§ØŗØĒØŽØąØ§ØŦ Ø§ØˇŲ„Ø§ØšØ§ØĒ Ø§Ø¨ØąØ¯Ø§Ø¯Ų‡ØŒ Ų…Ø§Ų†Ų†Ø¯ Ų…ŲˆŲ‚ØšÛŒØĒ ØŦØēØąØ§ŲÛŒØ§ÛŒÛŒ ؈ ÚŠÛŒŲÛŒØĒ Ø§Ø˛ Ų‡Øą ŲØ§ÛŒŲ„", "metadata_faces_import_setting": "ŲØšØ§Ų„ ØŗØ§Ø˛ÛŒ Ø§ÛŒŲ…ŲžŲˆØąØĒ ØĩŲˆØąØĒ", @@ -254,19 +197,13 @@ "metadata_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Metadata", "metadata_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ metadata", "migration_job": "Ų…Ų‡Ø§ØŦØąØĒ", - "migration_job_description": "Ø§Ų†ØĒŲ‚Ø§Ų„ ØĒØĩØ§ŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ ؈ Ú†Ų‡ØąŲ‡â€ŒŲ‡Ø§ Ø¨Ų‡ ØŗØ§ØŽØĒØ§Øą ŲžŲˆØ´Ų‡ ØŦدید", "nightly_tasks_cluster_faces_setting_description": "اØŦØąØ§ÛŒ Ø´Ų†Ø§ØŗØ§ÛŒÛŒ Ú†Ų‡ØąŲ‡ ØąŲˆÛŒ Ú†Ų‡ØąŲ‡ Ų‡Ø§ÛŒ ØĒØ§Ø˛Ų‡ ØĒØ´ØŽÛŒØĩ Ø¯Ø§Ø¯Ų‡ Ø´Ø¯Ų‡", "nightly_tasks_cluster_new_faces_setting": "Ú¯ØąŲˆŲ‡ Ø¨Ų†Ø¯ÛŒ Ú†Ų‡ØąŲ‡ Ų‡Ø§ÛŒ ØŦدید", "nightly_tasks_database_cleanup_setting": "ØĒØŗÚŠ Ų‡Ø§ÛŒ ŲžØ§ÚŠ ØŗØ§Ø˛ÛŒ ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡", "nightly_tasks_database_cleanup_setting_description": "ŲžØ§ÚŠ ØŗØ§Ø˛ÛŒ Ø¯Ø§Ø¯Ų‡ Ų‡Ø§ÛŒ Ų‚Ø¯ÛŒŲ…ÛŒØŒ Ų…Ų†Ų‚Øļی Ø´Ø¯Ų‡ Ø§Ø˛ ŲžØ§ÛŒÚ¯Ø§Ų‡ Ø¯Ø§Ø¯Ų‡", "nightly_tasks_generate_memories_setting": "ایØŦاد ØŽØ§ØˇØąØ§ØĒ", "nightly_tasks_generate_memories_setting_description": "ایØŦاد ØŽØ§ØˇØąØ§ØĒ ØŦدید Ø§Ø˛ ŲØ§ÛŒŲ„ Ų‡Ø§", - "nightly_tasks_missing_thumbnails_setting": "ایØŦاد ØĒØĩØ§ŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ų…ŲŲ‚ŲˆØ¯Ø´Ø¯Ų‡", - "nightly_tasks_missing_thumbnails_setting_description": "Ø§ŲØ˛ŲˆØ¯Ų† Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ÛŒ ŲØ§Ų‚Ø¯ ØĒØĩŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ø¨Ų‡ Øĩ؁ ØŗØ§ØŽØĒ", - "nightly_tasks_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ŲˆØ¸Ø§ÛŒŲ Ø´Ø¨Ø§Ų†Ų‡", - "nightly_tasks_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ŲˆØ¸Ø§ÛŒŲ Ø´Ø¨Ø§Ų†Ų‡", "nightly_tasks_start_time_setting": "Ø˛Ų…Ø§Ų† Ø´ØąŲˆØš", - "nightly_tasks_start_time_setting_description": "Ø˛Ų…Ø§Ų†ÛŒ ÚŠŲ‡ ØŗØąŲˆØą Ø´ØąŲˆØš Ø¨Ų‡ اØŦØąØ§ÛŒ ŲˆØ¸Ø§ÛŒŲ Ø´Ø¨Ø§Ų†Ų‡ Ų…ÛŒâ€ŒÚŠŲ†Ø¯", "nightly_tasks_sync_quota_usage_setting": "Ų‡Ų…Ú¯Ø§Ų… ØŗØ§Ø˛ÛŒ Ų…ÛŒØ˛Ø§Ų† Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ØŗŲ‡Ų…ÛŒŲ‡", "nightly_tasks_sync_quota_usage_setting_description": "Ø¨ØąŲˆØ˛ØąØŗØ§Ų†ÛŒ ØŗŲ‡Ų…ÛŒŲ‡ Ø°ØŽÛŒØąŲ‡ ØŗØ§Ø˛ÛŒ ÚŠØ§ØąØ¨ØąØŒ Ø¨Øą Ø§ØŗØ§Øŗ Ø§ØŗØĒŲØ§Ø¯Ų‡ ŲØšŲ„ÛŒ", "no_paths_added": "Ų‡ÛŒÚ† Ų…ØŗÛŒØąÛŒ اØļØ§ŲŲ‡ Ų†Ø´Ø¯Ų‡", @@ -274,14 +211,12 @@ "note_apply_storage_label_previous_assets": "ØĒ؈ØŦŲ‡: Ø¨ØąØ§ÛŒ Ø§ØšŲ…Ø§Ų„ Ø¨ØąÚ†ØŗØ¨ Ø°ØŽÛŒØąŲ‡ ØŗØ§Ø˛ÛŒ Ø¨Ų‡ Ø¯Ø§ØąØ§ÛŒÛŒ Ų‡Ø§ÛŒÛŒ ÚŠŲ‡ Ų‚Ø¨Ų„Ø§Ų‹ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø´Ø¯Ų‡ Ø§Ų†Ø¯ØŒ Ø¯ØŗØĒŲˆØą Ø˛ÛŒØą ØąØ§ اØŦØąØ§ ÚŠŲ†ÛŒØ¯", "note_cannot_be_changed_later": "ØĒ؈ØŦŲ‡: Ø§ÛŒŲ† ØąØ§ Ų†Ų…ÛŒ ØĒŲˆØ§Ų† Ø¨ØšØ¯Ø§Ų‹ ØĒØēÛŒÛŒØą داد!", "notification_email_from_address": "ØĸØ¯ØąØŗ ŲØąØŗØĒŲ†Ø¯Ų‡", - "notification_email_from_address_description": "ØĸØ¯ØąØŗ Ø§ÛŒŲ…ÛŒŲ„ ŲØąØŗØĒŲ†Ø¯Ų‡ØŒ Ø¨Ų‡ ØšŲ†ŲˆØ§Ų† Ų…ØĢØ§Ų„:\"ØŗØąŲˆØą ØšÚŠØŗ Immich \". Ų…ØˇŲ…ØĻŲ† باشید Ø§Ø˛ ØĸØ¯ØąØŗÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯ ÚŠŲ‡ اØŦØ§Ø˛Ų‡ Ø§ØąØŗØ§Ų„ Ø§ÛŒŲ…ÛŒŲ„ Ø§Ø˛ ØĸŲ† ØąØ§ Ø¯Ø§ØąÛŒØ¯.", + "notification_email_from_address_description": "ØĸØ¯ØąØŗ Ø§ÛŒŲ…ÛŒŲ„ ŲØąØŗØĒŲ†Ø¯Ų‡ØŒ Ø¨Ų‡ ØšŲ†ŲˆØ§Ų† Ų…ØĢØ§Ų„:\"ØŗØąŲˆØą ØšÚŠØŗ Immich \". Ų…ØˇŲ…ØĻŲ† باشید Ø§Ø˛ ØĸØ¯ØąØŗÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯ ÚŠŲ‡ اØŦØ§Ø˛Ų‡ Ø§ØąØŗØ§Ų„ Ø§ÛŒŲ…ÛŒŲ„ Ø§Ø˛ ØĸŲ† ØąØ§ Ø¯Ø§ØąÛŒØ¯.", "notification_email_host_description": "Ų…ÛŒØ˛Ø¨Ø§Ų† ØŗØąŲˆØą Ø§ÛŒŲ…ÛŒŲ„ (Ų…ØĢŲ„Ø§Ų‹ smtp.immich.app)", "notification_email_ignore_certificate_errors": "ØŽØˇØ§Ų‡Ø§ÛŒ Ú¯ŲˆØ§Ų‡ÛŒ ØąØ§ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ø¨Ú¯ÛŒØą", "notification_email_ignore_certificate_errors_description": "ØŽØˇØ§Ų‡Ø§ÛŒ اؚØĒØ¨Ø§ØąØŗŲ†ØŦی Ú¯ŲˆØ§Ų‡ÛŒ TLS ØąØ§ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ø¨Ú¯ÛŒØą (ØĒ؈ØĩÛŒŲ‡ Ų†Ų…ÛŒâ€ŒØ´ŲˆØ¯)", "notification_email_password_description": "ØąŲ…Ø˛ ØšØ¨ŲˆØą Ø¨ØąØ§ÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø¯Øą Ų‡Ų†Ú¯Ø§Ų… Ø§Ø­ØąØ§Ø˛ Ų‡ŲˆÛŒØĒ با ØŗØąŲˆØą Ø§ÛŒŲ…ÛŒŲ„", "notification_email_port_description": "ŲžŲˆØąØĒ ØŗØąŲˆØą Ø§ÛŒŲ…ÛŒŲ„ (Ų…ØĢŲ„Ø§Ų‹ 25، 465 یا 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ SMTPS (SMTP Ø¨Øą ØąŲˆÛŒ TLS)", "notification_email_sent_test_email_button": "Ø§ÛŒŲ…ÛŒŲ„ ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ Ø¨ŲØąØŗØĒ ؈ Ø°ØŽÛŒØąŲ‡ ÚŠŲ†", "notification_email_setting_description": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø¨ØąØ§ÛŒ Ø§ØąØŗØ§Ų„ Ø§ØšŲ„Ø§Ų† Ų‡Ø§ÛŒ Ø§ÛŒŲ…ÛŒŲ„", "notification_email_test_email": "Ø§ØąØŗØ§Ų„ Ø§ÛŒŲ…ÛŒŲ„ ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ", @@ -291,22 +226,15 @@ "notification_enable_email_notifications": "ŲØšØ§Ų„ ÚŠØąØ¯Ų† Ø§ØšŲ„Ø§Ų† Ų‡Ø§ÛŒ Ø§ÛŒŲ…ÛŒŲ„", "notification_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§ØšŲ„Ø§Ų†", "notification_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§ØšŲ„Ø§Ų† Ų‡Ø§ØŒ Ø§Ø˛ ØŦŲ…Ų„Ų‡ Ø§ÛŒŲ…ÛŒŲ„", - "oauth_allow_insecure_requests": "اØŦØ§Ø˛Ų‡ Ø¯ØąØŽŲˆØ§ØŗØĒâ€ŒŲ‡Ø§ÛŒ Ų†Ø§Ø§Ų…Ų†", - "oauth_allow_insecure_requests_description": "Ų‡Ø´Ø¯Ø§Øą: Ø§ÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ اؚØĒØ¨Ø§ØąØŗŲ†ØŦی Ú¯ŲˆØ§Ų‡ÛŒ TLS ØąØ§ Ø¨ØąØ§ÛŒ Ø¯ØąØŽŲˆØ§ØŗØĒâ€ŒŲ‡Ø§ÛŒ OAuth ØēÛŒØąŲØšØ§Ų„ Ų…ÛŒâ€ŒÚŠŲ†Ø¯ ؈ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ Ø´Ų…Ø§ ØąØ§ Ø¯Øą Ų…ØšØąØļ Ø­Ų…Ų„Ø§ØĒ MITM Ų‚ØąØ§Øą Ø¯Ų‡Ø¯.", "oauth_auto_launch": "ØąØ§Ų‡ Ø§Ų†Ø¯Ø§Ø˛ÛŒ ØŽŲˆØ¯ÚŠØ§Øą", "oauth_auto_launch_description": "ØŦØąÛŒØ§Ų† ŲˆØąŲˆØ¯ Ø¨Ų‡ ØŗÛŒØŗØĒŲ… OAuth ØąØ§ Ø¨Ų‡ ØˇŲˆØą ØŽŲˆØ¯ÚŠØ§Øą Ų‡Ų†Ú¯Ø§Ų… Ų‡Ø¯Ø§ÛŒØĒ Ø¨Ų‡ ØĩŲØ­Ų‡ ŲˆØąŲˆØ¯ Ø´ØąŲˆØš ÚŠŲ†", "oauth_auto_register": "ØĢبØĒ ØŽŲˆØ¯ÚŠØ§Øą", "oauth_auto_register_description": "ÚŠØ§ØąØ¨ØąØ§Ų† ØŦدید ØąØ§ ŲžØŗ Ø§Ø˛ ŲˆØąŲˆØ¯ با OAuth Ø¨Ų‡ ØˇŲˆØą ØŽŲˆØ¯ÚŠØ§Øą ØĢبØĒ Ų†Ø§Ų… ÚŠŲ†", "oauth_button_text": "Ų…ØĒŲ† Ø¯ÚŠŲ…Ų‡", - "oauth_client_secret_description": "Ø¨ØąØ§ÛŒ ÚŠŲ„Ø§ÛŒŲ†ØĒâ€ŒŲ‡Ø§ÛŒ Ų…Ø­ØąŲ…Ø§Ų†Ų‡ Ø§Ų„Ø˛Ø§Ų…ÛŒ Ø§ØŗØĒ، یا Ø¯Øą ØĩŲˆØąØĒی ÚŠŲ‡ PKCE (ÚŠŲ„ÛŒØ¯ اØĢباØĒ Ø¨ØąØ§ÛŒ ØĒØ¨Ø§Ø¯Ų„ ڊد) ØĒŲˆØŗØˇ ÚŠŲ„Ø§ÛŒŲ†ØĒ ØšŲ…ŲˆŲ…ÛŒ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ų†Ø´ŲˆØ¯.", "oauth_enable_description": "ŲˆØąŲˆØ¯ ØĒŲˆØŗØˇ OAuth", - "oauth_end_session_url_description": "ÚŠØ§ØąØ¨Øą ØąØ§ Ų‡Ų†Ú¯Ø§Ų… ØŽØąŲˆØŦ Ø¨Ų‡ Ø§ÛŒŲ† URI Ų‡Ø¯Ø§ÛŒØĒ ÚŠŲ†ÛŒØ¯.", "oauth_mobile_redirect_uri": "ØĒØēÛŒÛŒØą Ų…ØŗÛŒØą URI Ų…ŲˆØ¨Ø§ÛŒŲ„", "oauth_mobile_redirect_uri_override": "ØĒØēÛŒÛŒØą Ų…ØŗÛŒØą URI ØĒ؄؁؆ Ų‡Ų…ØąØ§Ų‡", "oauth_mobile_redirect_uri_override_description": "Ø˛Ų…Ø§Ų†ÛŒ ÚŠŲ‡ Ø§ØąØ§ØĻŲ‡ Ø¯Ų‡Ų†Ø¯Ų‡ OAuth اØŦØ§Ø˛Ų‡ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ØĸØ¯ØąØŗ Ų…ŲˆØ¨Ø§ÛŒŲ„ØŒ Ų…Ø§Ų†Ų†Ø¯ ''{callback}'' ØąØ§ Ų†Ų…ÛŒ Ø¯Ų‡Ø¯", - "oauth_prompt_description": "ŲžØ§ØąØ§Ų…ØĒØą Prompt (Ų…Ø§Ų†Ų†Ø¯ select_account، login، consent)", - "oauth_role_claim": "Ø¯ØąØŽŲˆØ§ØŗØĒ Ų†Ų‚Ø´", - "oauth_role_claim_description": "Ø§ØšØˇØ§ÛŒ ØŽŲˆØ¯ÚŠØ§Øą Ø¯ØŗØĒØąØŗÛŒ Ų…Ø¯ÛŒØą Ø¨Øą Ø§ØŗØ§Øŗ ؈ØŦŲˆØ¯ Ø§ÛŒŲ† ادؚا. Ų…Ų‚Ø¯Ø§Øą ادؚا Ų…ÛŒâ€ŒØĒŲˆØ§Ų†Ø¯ ÂĢuserÂģ یا ÂĢadminÂģ Ø¨Ø§Ø´Ø¯.", "oauth_settings": "OAuth", "oauth_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ŲˆØąŲˆØ¯ Ø¨Ų‡ ØŗÛŒØŗØĒŲ… OAuth", "oauth_settings_more_details": "Ø¨ØąØ§ÛŒ ØŦØ˛ØĻیاØĒ بیشØĒØą Ø¯Øą Ų…ŲˆØąØ¯ Ø§ÛŒŲ† ŲˆÛŒÚ˜Ú¯ÛŒØŒ Ø¨Ų‡ Ų…ØŗØĒŲ†Ø¯Ø§ØĒ Ų…ØąØ§ØŦØšŲ‡ ÚŠŲ†ÛŒØ¯.", @@ -315,7 +243,7 @@ "oauth_storage_quota_claim": "Ø¯ØąØŽŲˆØ§ØŗØĒ ØŗŲ‡Ų…ÛŒŲ‡ ؁Øļای Ø°ØŽÛŒØąŲ‡ ØŗØ§Ø˛ÛŒ", "oauth_storage_quota_claim_description": "ØĒŲ†Ø¸ÛŒŲ… ØŽŲˆØ¯ÚŠØ§Øą ØŗŲ‡Ų…ÛŒŲ‡ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ ÚŠØ§ØąØ¨Øą Ø¨Ų‡ Ų…Ų‚Ø¯Ø§Øą Ø¯ØąØŽŲˆØ§ØŗØĒ Ø´Ø¯Ų‡.", "oauth_storage_quota_default": "Ų…Ų‚Ø¯Ø§Øą ØŗŲ‡Ų…ÛŒŲ‡ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ ŲžÛŒØ´â€ŒŲØąØļ (گیگابایØĒ)", - "oauth_storage_quota_default_description": "ØŗŲ‡Ų…ÛŒŲ‡ Ø¨Ų‡ گیگابایØĒ Ø¨ØąØ§ÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų‡Ų†Ú¯Ø§Ų…ÛŒ ÚŠŲ‡ Ų‡ÛŒÚ† Ø¯ØąØŽŲˆØ§ØŗØĒی Ø§ØąØ§ØĻŲ‡ Ų†Ø´Ø¯Ų‡ Ø§ØŗØĒ.", + "oauth_storage_quota_default_description": "ØŗŲ‡Ų…ÛŒŲ‡ Ø¨Ų‡ گیگابایØĒ Ų‡Ų†Ú¯Ø§Ų…ÛŒ ÚŠŲ‡ Ø¯ØąØŽŲˆØ§ØŗØĒی Ø§ØąØ§ØĻŲ‡ Ų†Ø´Ø¯Ų‡ باشد", "oauth_timeout": "ØĒØ§ÛŒŲ… Ø§ŲˆØĒ Ø¯ØąØŽŲˆØ§ØŗØĒ", "oauth_timeout_description": "Ø˛Ų…Ø§Ų† ØĒØ§ÛŒŲ… Ø§ŲˆØĒ Ø¨ØąØ§ÛŒ Ø¯ØąØŽŲˆØ§ØŗØĒ Ų‡Ø§ بØĩŲˆØąØĒ Ų…ÛŒŲ„ÛŒ ØĢØ§Ų†ÛŒŲ‡", "ocr_job_description": "Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ† Ø¨ØąØ§ÛŒ Ø´Ų†Ø§ØŗØ§ÛŒÛŒ Ų…ØĒŲ† Ø¯Øą ØšÚŠØŗ Ų‡Ø§", @@ -323,7 +251,6 @@ "password_settings": "Ú¯Ø°ØąŲˆØ§Ú˜Ų‡ ŲˆØąŲˆØ¯", "password_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ú¯Ø°ØąŲˆØ§Ú˜Ų‡ ŲˆØąŲˆØ¯", "paths_validated_successfully": "ØĒŲ…Ø§Ų…ÛŒ Ų…ØŗÛŒØąŲ‡Ø§ با Ų…ŲˆŲŲ‚ÛŒØĒ ØĒØŖÛŒÛŒØ¯ Ø´Ø¯Ų†Ø¯", - "person_cleanup_job": "ŲžØ§ÚŠâ€ŒØŗØ§Ø˛ÛŒ Ø´ØŽØĩ", "queue_details": "ØŦØ˛ØĻیاØĒ Øĩ؁", "queues": "Øĩ؁ Ų‡Ø§ÛŒ ØŦاب", "queues_page_description": "ØĩŲØ­Ų‡ Ø§Ø¯Ų…ÛŒŲ† Øĩ؁ Ų‡Ø§ÛŒ ØŦاب", @@ -331,22 +258,18 @@ "refreshing_all_libraries": "Ø¨ØąŲˆØ˛ ØąØŗØ§Ų†ÛŒ Ų‡Ų…Ų‡ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡ Ų‡Ø§", "registration": "ØĢبØĒ Ų†Ø§Ų… Ų…Ø¯ÛŒØą", "registration_description": "Ø§Ø˛ ØĸŲ†ØŦایی ÚŠŲ‡ Ø´Ų…Ø§ Ø§ŲˆŲ„ÛŒŲ† ÚŠØ§ØąØ¨Øą Ø¯Øą ØŗÛŒØŗØĒŲ… Ų‡ØŗØĒید، Ø¨Ų‡ ØšŲ†ŲˆØ§Ų† Ų…Ø¯ÛŒØą ØĒØšÛŒÛŒŲ† Ø´Ø¯Ų‡â€ŒØ§ÛŒØ¯ ؈ Ų…ØŗØĻŲˆŲ„ÛŒØĒ Ø§Ų†ØŦØ§Ų… ŲˆØ¸Ø§ÛŒŲ Ų…Ø¯ÛŒØąÛŒØĒی Ø¨Øą ØšŲ‡Ø¯Ų‡ Ø´Ų…Ø§ ØŽŲˆØ§Ų‡Ø¯ Ø¨ŲˆØ¯ ؈ ÚŠØ§ØąØ¨ØąØ§Ų† اØļØ§ŲÛŒ ØĒŲˆØŗØˇ Ø´Ų…Ø§ ایØŦاد ØŽŲˆØ§Ų‡Ų†Ø¯ شد.", - "release_channel_release_candidate": "Ų†Ø§Ų…Ø˛Ø¯ Ø§Ų†ØĒØ´Ø§Øą", - "release_channel_stable": "ŲžØ§ÛŒØ¯Ø§Øą", "remove_failed_jobs": "Ø­Ø°Ų ØŦاب Ų‡Ø§ÛŒ Ų†Ø§Ų…ŲˆŲŲ‚", "require_password_change_on_login": "Ø§Ų„Ø˛Ø§Ų… ÚŠØ§ØąØ¨Øą Ø¨Ų‡ ØĒØēÛŒÛŒØą Ú¯Ø°ØąŲˆØ§Ú˜Ų‡ Ø¯Øą Ø§ŲˆŲ„ÛŒŲ† ŲˆØąŲˆØ¯", "reset_settings_to_default": "Ø¨Ø§Ø˛Ų†Ø´Ø§Ų†ÛŒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø¨Ų‡ Ø­Ø§Ų„ØĒ ŲžÛŒØ´â€ŒŲØąØļ", "reset_settings_to_recent_saved": "Ø¨Ø§Ø˛Ų†Ø´Ø§Ų†ÛŒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø¨Ų‡ ØĸØŽØąÛŒŲ† ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø°ØŽÛŒØąŲ‡ Ø´Ø¯Ų‡", - "scanning_library": "Ø¯Øą Ø­Ø§Ų„ Ø§ØŗÚŠŲ† ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", "search_jobs": "ØŦاب Ų‡Ø§ÛŒ ØŦØŗØĒØŦ؈â€Ļ", "send_welcome_email": "Ø§ØąØŗØ§Ų„ Ø§ÛŒŲ…ÛŒŲ„ ØŽŲˆØ´ ØĸŲ…Ø¯ Ú¯ŲˆÛŒÛŒ", "server_external_domain_settings": "Ø¯Ø§Ų…Ų†Ų‡ ØŽØ§ØąØŦی", - "server_external_domain_settings_description": "Ø¯Ø§Ų…Ų†Ų‡ Ų…ŲˆØąØ¯ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø¨ØąØ§ÛŒ ŲžÛŒŲˆŲ†Ø¯Ų‡Ø§ÛŒ ØŽØ§ØąØŦی", + "server_external_domain_settings_description": "Ø¯Ø§Ų…Ų†Ų‡ Ø¨ØąØ§ÛŒ Ų„ÛŒŲ†ÚŠ Ų‡Ø§ÛŒ ØšŲ…ŲˆŲ…ÛŒ Ø¨Ų‡ اشØĒØąØ§ÚŠ گذاشØĒŲ‡ Ø´Ø¯Ų‡ØŒ Ø´Ø§Ų…Ų„ //:(s)http", "server_public_users": "ÚŠØ§ØąØ¨ØąØ§Ų† ØšŲ…ŲˆŲ…ÛŒ", "server_public_users_description": "ØĒŲ…Ø§Ų…ÛŒ ÚŠØ§ØąØ¨ØąØ§Ų† (Ø§ØŗŲ… ؈ Ø§ÛŒŲ…ÛŒŲ„) Ų‡Ų†Ú¯Ø§Ų… اØļØ§ŲŲ‡ ÚŠØąØ¯Ų† یڊ ÚŠØ§ØąØ¨Øą Ø¨Ų‡ یڊ ØĸŲ„Ø¨ŲˆŲ… Ų…Ø´ØĒØąÚŠ Ų„ÛŒØŗØĒ Ų…ÛŒ Ø´ŲˆŲ†Ø¯. ŲˆŲ‚ØĒی ØēÛŒØą ŲØšØ§Ų„ باشد، Ų„ÛŒØŗØĒ ÚŠØ§ØąØ¨ØąØ§Ų† ŲŲ‚Øˇ Ø¨ØąØ§ÛŒ Ø§Ø¯Ų…ÛŒŲ† Ų‚Ø§Ø¨Ų„ Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ø§ØŗØĒ.", "server_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ØŗØąŲˆØą", "server_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ØŗØąŲˆØą", - "server_stats_page_description": "ØĩŲØ­Ų‡ ØĸŲ…Ø§Øą ØŗØąŲˆØą Ų…Ø¯ÛŒØą", "server_welcome_message": "ŲžÛŒØ§Ų… ØŽŲˆØ´ ØĸŲ…Ø¯ Ú¯ŲˆÛŒÛŒ", "server_welcome_message_description": "ŲžÛŒØ§Ų…ÛŒ ÚŠŲ‡ Ø¯Øą ØĩŲØ­Ų‡ ŲˆØąŲˆØ¯ Ø¨Ų‡ ØŗÛŒØŗØĒŲ… Ų†Ų…Ø§ÛŒØ´ Ø¯Ø§Ø¯Ų‡ Ų…ÛŒ Ø´ŲˆØ¯.", "settings_page_description": "ØĩŲØ­Ų‡ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§Ø¯Ų…ÛŒŲ†", @@ -356,15 +279,13 @@ "smart_search_job_description": "اØŦØąØ§ÛŒ ÛŒØ§Ø¯Ú¯ÛŒØąÛŒ Ų…Ø§Ø´ÛŒŲ†ÛŒ Ø¨Øą ØąŲˆÛŒ Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ Ø¨ØąØ§ÛŒ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ø§Ø˛ ØŦØŗØĒØŦŲˆÛŒ Ų‡ŲˆØ´Ų…Ų†Ø¯", "storage_template_date_time_description": "Ø˛Ų…Ø§Ų†â€ŒØ¨Ų†Ø¯ÛŒ ایØŦاد Ø¯Ø§ØąØ§ÛŒÛŒ Ø¨ØąØ§ÛŒ Ø§ØˇŲ„Ø§ØšØ§ØĒ ØĒØ§ØąÛŒØŽ ؈ Ø˛Ų…Ø§Ų† Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯", "storage_template_date_time_sample": "Ų†Ų…ŲˆŲ†Ų‡ Ø˛Ų…Ø§Ų† {date}", - "storage_template_enable_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ų…ŲˆØĒŲˆØą Ø§Ų„Ú¯ŲˆÛŒ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ", "storage_template_hash_verification_enabled": "ØĒØŖÛŒÛŒØ¯ Ų‡ŲŽØ´ ŲØšØ§Ų„ شد", "storage_template_hash_verification_enabled_description": "ØĒØŖÛŒÛŒØ¯ Ų‡ŲŽØ´ ØąØ§ ŲØšØ§Ų„ Ų…ÛŒâ€ŒÚŠŲ†Ø¯Ø› Ø§ÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ ØąØ§ ØēÛŒØąŲØšØ§Ų„ Ų†ÚŠŲ†ÛŒØ¯ Ų…Ú¯Øą Ø§ÛŒŲ†ÚŠŲ‡ Ø§Ø˛ ØšŲˆØ§Ų‚Ø¨ ØĸŲ† Ų…ØˇŲ…ØĻŲ† باشید", "storage_template_migration": "Ø§Ų†ØĒŲ‚Ø§Ų„ Ø§Ų„Ú¯ŲˆÛŒ Ø°ØŽÛŒØąŲ‡ ØŗØ§Ø˛ÛŒ", "storage_template_migration_description": "Ų‚Ø§Ų„Ø¨ ŲØšŲ„ÛŒ {template} ØąØ§ Ø¨Ų‡ Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ÛŒ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø´Ø¯Ų‡ Ų‚Ø¨Ų„ÛŒ Ø§ØšŲ…Ø§Ų„ ÚŠŲ†ÛŒØ¯", - "storage_template_migration_info": "Ø§Ų„Ú¯ŲˆÛŒ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒØŒ ØĒŲ…Ø§Ų… ŲžØŗŲˆŲ†Ø¯Ų‡Ø§ ØąØ§ Ø¨Ų‡ Ø­ØąŲˆŲ ÚŠŲˆÚ†ÚŠ ØĒØ¨Ø¯ÛŒŲ„ Ų…ÛŒâ€ŒÚŠŲ†Ø¯. ØĒØēÛŒÛŒØąØ§ØĒ Ø§Ų„Ú¯Ųˆ ØĒŲ†Ų‡Ø§ Ø¨Øą ØąŲˆÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØŦدید Ø§ØšŲ…Ø§Ų„ ØŽŲˆØ§Ų‡Ø¯ شد. Ø¨ØąØ§ÛŒ Ø§ØšŲ…Ø§Ų„ Ø§Ų„Ú¯Ųˆ Ø¨Øą ØąŲˆÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØĸŲžŲ„ŲˆØ¯Ø´Ø¯Ų‡ Ų‚Ø¨Ų„ÛŒØŒ {job} ØąØ§ اØŦØąØ§ ÚŠŲ†ÛŒØ¯.", + "storage_template_migration_info": "ØĒØēÛŒÛŒØąØ§ØĒ Ų‚Ø§Ų„Ø¨ ŲŲ‚Øˇ Ø¨Ų‡ Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ÛŒ ØŦدید Ø§ØšŲ…Ø§Ų„ ØŽŲˆØ§Ų‡Ø¯ شد. Ø¨ØąØ§ÛŒ Ø§ØšŲ…Ø§Ų„ Ų‚Ø§Ų„Ø¨ Ø¨Ų‡ Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ÛŒ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø´Ø¯Ų‡ Ų‚Ø¨Ų„ÛŒØŒ باید {job} ØąØ§ اØŦØąØ§ ÚŠŲ†ÛŒØ¯.", "storage_template_migration_job": "ŲˆØ¸ÛŒŲŲ‡ Ų…Ų‡Ø§ØŦØąØĒ Ø§Ų„Ú¯ŲˆÛŒ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ", "storage_template_more_details": "Ø¨ØąØ§ÛŒ ØŦØ˛ØĻیاØĒ بیشØĒØą Ø¯ØąØ¨Ø§ØąŲ‡ Ø§ÛŒŲ† ŲˆÛŒÚ˜Ú¯ÛŒØŒ Ø¨Ų‡ Ų‚Ø§Ų„Ø¨ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ ؈ Ų…ŲØ§Ų‡ÛŒŲ… ØĸŲ† Ų…ØąØ§ØŦØšŲ‡ ÚŠŲ†ÛŒØ¯", - "storage_template_onboarding_description_v2": "با ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø§ÛŒŲ† ŲˆÛŒÚ˜Ú¯ÛŒØŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ø¨Ų‡â€ŒØĩŲˆØąØĒ ØŽŲˆØ¯ÚŠØ§Øą Ø¨Øą Ø§ØŗØ§Øŗ Ø§Ų„Ú¯ŲˆÛŒ ØĒØšØąÛŒŲâ€ŒØ´Ø¯Ų‡ ØĒŲˆØŗØˇ ÚŠØ§ØąØ¨Øą ØŗØ§Ø˛Ų…Ø§Ų†Ø¯Ų‡ÛŒ Ų…ÛŒâ€ŒØ´ŲˆŲ†Ø¯. Ø¨ØąØ§ÛŒ Ø§ØˇŲ„Ø§ØšØ§ØĒ بیشØĒØąØŒ Ų„ØˇŲØ§Ų‹ Ų…ØŗØĒŲ†Ø¯Ø§ØĒ ØąØ§ Ų…Ø´Ø§Ų‡Ø¯Ų‡ ÚŠŲ†ÛŒØ¯.", "storage_template_path_length": "حداڊØĢØą ØˇŲˆŲ„ Ų…ØŗÛŒØą ØĒŲ‚ØąÛŒØ¨ÛŒ: {length, number}/{limit, number}", "storage_template_settings": "Ų‚Ø§Ų„Ø¨ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ", "storage_template_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØŗØ§ØŽØĒØ§Øą ŲžŲˆØ´Ų‡ ؈ Ų†Ø§Ų… ŲØ§ÛŒŲ„ Ø¯Ø§ØąØ§ÛŒÛŒ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø´Ø¯Ų‡", @@ -373,7 +294,6 @@ "tag_cleanup_job": "ŲžØ§ÚŠ ØŗØ§Ø˛ÛŒ ØĒÚ¯", "template_email_available_tags": "Ø´Ų…Ø§ Ų…ÛŒØĒŲˆØ§Ų†ÛŒØ¯ Ø§Ø˛ Ų…ØĒØēÛŒØą Ų‡Ø§ÛŒ ØąŲˆØ¨ØąŲˆ Ø¯Øą Ų‚Ø§Ų„Ø¨ ØŽŲˆØ¯ Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯: {tags}", "template_email_if_empty": "Ø§Ú¯Øą Ų‚Ø§Ų„Ø¨ ØŽØ§Ų„ÛŒ باشد، Ø§ÛŒŲ…ÛŒŲ„ ŲžÛŒØ´ŲØąØļ Ø§ØŗØĒŲØ§Ø¯Ų‡ ØŽŲˆØ§Ų‡Ø¯ شد.", - "template_email_invite_album": "Ø§Ų„Ú¯ŲˆÛŒ ØĸŲ„Ø¨ŲˆŲ… Ø¯ØšŲˆØĒâ€ŒŲ†Ø§Ų…Ų‡", "template_email_preview": "ŲžÛŒØ´ Ų†Ų…Ø§ÛŒØ´", "template_email_settings": "Ų‚Ø§Ų„Ø¨ Ų‡Ø§ÛŒ Ø§ÛŒŲ…ÛŒŲ„", "template_email_update_album": "Ų‚Ø§Ų„Ø¨ Ø¨ØąŲˆØ˛ØąØŗØ§Ų†ÛŒ ØĸŲ„Ø¨ŲˆŲ…", @@ -400,20 +320,17 @@ "transcoding_accepted_video_codecs_description": "Ø§Ų†ØĒ؎اب ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ÛŒ ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ ÚŠŲ‡ Ų†ÛŒØ§Ø˛ÛŒ Ø¨Ų‡ ØĒØ¨Ø¯ÛŒŲ„ (ØĒØąŲ†ØŗÚŠŲˆØ¯) Ų†Ø¯Ø§ØąŲ†Ø¯. ŲŲ‚Øˇ Ø¨ØąØ§ÛŒ Ø¨ØąØŽÛŒ ØŗÛŒØ§ØŗØĒâ€ŒŲ‡Ø§ÛŒ ØąŲ…Ø˛Ú¯Ø´Ø§ÛŒÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯.", "transcoding_advanced_options_description": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ بیشØĒØą ÚŠØ§ØąØ¨ØąØ§Ų† Ų†ÛŒØ§Ø˛ÛŒ Ø¨Ų‡ ØĒØēÛŒÛŒØą ØĸŲ†â€ŒŲ‡Ø§ Ų†Ø¯Ø§ØąŲ†Ø¯", "transcoding_audio_codec": "ڊدڊ Øĩ؈ØĒی", - "transcoding_audio_codec_description": "Opus Ø¨Ų‡ØĒØąÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ Ø§Ø˛ Ų†Ø¸Øą ÚŠÛŒŲÛŒØĒ Ø§ØŗØĒ، Ø§Ų…Ø§ با Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ یا Ų†ØąŲ…â€ŒØ§ŲØ˛Ø§ØąŲ‡Ø§ÛŒ Ų‚Ø¯ÛŒŲ…ÛŒâ€ŒØĒØą ØŗØ§Ø˛Ú¯Ø§ØąÛŒ ÚŠŲ…ØĒØąÛŒ Ø¯Ø§ØąØ¯.", + "transcoding_audio_codec_description": "OPUS Ø¨Ų‡ØĒØąÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ Ø§Ø˛ Ų†Ø¸Øą ÚŠÛŒŲÛŒØĒ Ø§ØŗØĒ، Ø§Ų…Ø§ با Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ یا Ų†ØąŲ…â€ŒØ§ŲØ˛Ø§ØąŲ‡Ø§ÛŒ Ų‚Ø¯ÛŒŲ…ÛŒâ€ŒØĒØą ØŗØ§Ø˛Ú¯Ø§ØąÛŒ ÚŠŲ…ØĒØąÛŒ Ø¯Ø§ØąØ¯.", "transcoding_bitrate_description": "ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Ø¨Ø§Ų„Ø§ØĒØą Ø§Ø˛ حداڊØĢØą بیØĒâ€ŒØąÛŒØĒ Ų‡ØŗØĒŲ†Ø¯ یا Ø¯Øą ŲØąŲ…ØĒ ŲžØ°ÛŒØąŲØĒŲ‡â€Œ Ø´Ø¯Ų‡ Ų†ÛŒØŗØĒŲ†Ø¯", - "transcoding_codecs_learn_more": "Ø¨ØąØ§ÛŒ ØĸØ´Ų†Ø§ÛŒÛŒ بیشØĒØą با اØĩØˇŲ„Ø§Ø­Ø§ØĒ Ø¨Ų‡â€ŒÚŠØ§ØąØąŲØĒŲ‡ØŒ Ø¨Ų‡ Ų…ØŗØĒŲ†Ø¯Ø§ØĒ FFmpeg Ø¨ØąØ§ÛŒ ڊدڊ H.264، ڊدڊ HEVC ؈ ڊدڊ VP9 Ų…ØąØ§ØŦØšŲ‡ ÚŠŲ†ÛŒØ¯.", + "transcoding_codecs_learn_more": "Ø¨ØąØ§ÛŒ ØĸØ´Ų†Ø§ÛŒÛŒ بیشØĒØą با اØĩØˇŲ„Ø§Ø­Ø§ØĒ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø´Ø¯Ų‡ Ø¯Øą Ø§ÛŒŲ†ØŦا، Ø¨Ų‡ Ų…ØŗØĒŲ†Ø¯Ø§ØĒ FFmpeg Ø¨ØąØ§ÛŒ ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ÛŒ H.264، HEVC ؈ VP9 Ų…ØąØ§ØŦØšŲ‡ ÚŠŲ†ÛŒØ¯.", "transcoding_constant_quality_mode": "Ø­Ø§Ų„ØĒ ÚŠÛŒŲÛŒØĒ ØĢابØĒ", "transcoding_constant_quality_mode_description": "ICQ Ø¨Ų‡ØĒØą Ø§Ø˛ CQP Ø§ØŗØĒ، Ø§Ų…Ø§ Ø¨ØąØŽÛŒ Ø§Ø˛ Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ ØĒØŗØąÛŒØš ØŗØŽØĒâ€ŒØ§ŲØ˛Ø§ØąÛŒ Ø§Ø˛ Ø§ÛŒŲ† Ø­Ø§Ų„ØĒ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ų†Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯. ØĒŲ†Ø¸ÛŒŲ… Ø§ÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ باؚØĢ Ų…ÛŒâ€ŒØ´ŲˆØ¯ ÚŠŲ‡ Ø­Ø§Ų„ØĒ Ų…Ø´ØŽØĩ Ø´Ø¯Ų‡ Ø¯Øą Ų‡Ų†Ú¯Ø§Ų… Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ Ų…Ø¨ØĒŲ†ÛŒ Ø¨Øą ÚŠÛŒŲÛŒØĒ ØĒØąØŦیح Ø¯Ø§Ø¯Ų‡ Ø´ŲˆØ¯. Ø§ÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ ØĒŲˆØŗØˇ NVENC Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ú¯ØąŲØĒŲ‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯ Ø˛ÛŒØąØ§ Ø§Ø˛ ICQ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ų†Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "transcoding_constant_rate_factor": "ØļØąÛŒØ¨ Ų†ØąØŽ ØĢابØĒ ( crf- )", "transcoding_constant_rate_factor_description": "ØŗØˇØ­ ÚŠÛŒŲÛŒØĒ ŲˆÛŒØ¯ÛŒŲˆ. Ų‡ØąÚ†Ų‡ ؚدد ÚŠŲ…ØĒØą باشد، ÚŠÛŒŲÛŒØĒ Ø¨Ų‡ØĒØą Ø§ØŗØĒ، Ø§Ų…Ø§ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ Ø¨Ø˛ØąÚ¯â€ŒØĒØąÛŒ ØĒŲˆŲ„ÛŒØ¯ Ų…ÛŒâ€ŒÚŠŲ†Ø¯. Ų…Ų‚Ø§Ø¯ÛŒØą Ų…ØšŲ…ŲˆŲ„ ØšØ¨Ø§ØąØĒŲ†Ø¯ Ø§Ø˛: (23 <-- H.264) - (28 --> HEVC) - (31 --> VP9) - (35 --> AV1).", "transcoding_disabled_description": "Ų‡ÛŒÚ† ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ ØąØ§ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ų†ÚŠŲ†ÛŒØ¯ØŒ Ø˛ÛŒØąØ§ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ŲžØŽØ´ Ø¯Øą Ø¨ØąØŽÛŒ Ø§Ø˛ ÚŠŲ„Ø§ÛŒŲ†ØĒâ€ŒŲ‡Ø§ ØąØ§ Ų…ØŽØĒŲ„ ÚŠŲ†Ø¯", - "transcoding_encoding_options": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ", - "transcoding_encoding_options_description": "ØĒŲ†Ø¸ÛŒŲ… ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ØŒ ؈ØļŲˆØ­ ØĒØĩŲˆÛŒØąØŒ ÚŠÛŒŲÛŒØĒ ؈ ØŗØ§ÛŒØą Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ Ø¨ØąØ§ÛŒ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒâ€ŒØ´Ø¯Ų‡", "transcoding_hardware_acceleration": "Ø´ØĒاب Ø¯Ų‡Ų†Ø¯Ų‡ ØŗØŽØĒ Ø§ŲØ˛Ø§ØąÛŒ", "transcoding_hardware_acceleration_description": "ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ: Transcoding ØŗØąÛŒØš ØĒØą Ø§Ų…Ø§ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ Ø¯Øą bitrate ÛŒÚŠØŗØ§Ų† ÚŠÛŒŲÛŒØĒ ØąØ§ ÚŠØ§Ų‡Ø´ Ø¯Ų‡Ø¯", "transcoding_hardware_decoding": "ØąŲ…Ø˛Ú¯Ø´Ø§ÛŒÛŒ ØŗØŽØĒ Ø§ŲØ˛Ø§ØąÛŒ", - "transcoding_hardware_decoding_setting_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø´ØĒØ§Ø¨â€ŒØ¯Ų‡ÛŒ ØŗØąØĒØ§ØŗØąÛŒ Ø¨Ų‡ ØŦای Ø´ØĒØ§Ø¨â€ŒØ¯Ų‡ÛŒ ØĒŲ†Ų‡Ø§ Ø¯Øą Ų…ØąØ­Ų„Ų‡ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ. Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ØąŲˆÛŒ Ų‡Ų…Ų‡ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ ÚŠØ§Øą Ų†ÚŠŲ†Ø¯.", "transcoding_max_b_frames": "بیشØĒØąÛŒŲ† B-frames", "transcoding_max_b_frames_description": "Ų…Ų‚Ø§Ø¯ÛŒØą Ø¨Ø§Ų„Ø§ØĒØą ÚŠØ§ØąØ§ÛŒÛŒ ŲØ´ØąØ¯Ų‡ ØŗØ§Ø˛ÛŒ ØąØ§ Ø¨Ų‡Ø¨ŲˆØ¯ Ų…ÛŒâ€ŒØ¨ØŽØ´Ų†Ø¯ØŒ Ø§Ų…Ø§ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ ØąØ§ ÚŠŲ†Ø¯ Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯. Ų…Ų…ÚŠŲ† Ø§ØŗØĒ با Ø´ØĒاب Ø¯Ų‡ÛŒ ØŗØŽØĒâ€ŒØ§ŲØ˛Ø§ØąÛŒ Ø¯Øą Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ Ų‚Ø¯ÛŒŲ…ÛŒ ØŗØ§Ø˛Ú¯Ø§Øą Ų†Ø¨Ø§Ø´Ø¯. Ų…Ų‚Ø¯Ø§Øą( 0 ) B-frames ØąØ§ ØēÛŒØąŲØšØ§Ų„ Ų…ÛŒâ€ŒÚŠŲ†Ø¯ØŒ Ø¯Øą Ø­Ø§Ų„ÛŒ ÚŠŲ‡ Ų…Ų‚Ø¯Ø§Øą ( 1 ) Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą ØąØ§ Ø¨Ų‡ ØĩŲˆØąØĒ ØŽŲˆØ¯ÚŠØ§Øą ØĒŲ†Ø¸ÛŒŲ… Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "transcoding_max_bitrate": "بیشØĒØąÛŒŲ† بیØĒ ØąÛŒØĒ", @@ -421,20 +338,10 @@ "transcoding_max_keyframe_interval": "حداڊØĢØą ŲØ§ØĩŲ„Ų‡ ÚŠŲ„ÛŒØ¯ ŲØąÛŒŲ…", "transcoding_max_keyframe_interval_description": "حداڊØĢØą ŲØ§ØĩŲ„Ų‡ ŲØąÛŒŲ… Ø¨ÛŒŲ† ÚŠŲ„ÛŒØ¯ŲØąÛŒŲ…â€ŒŲ‡Ø§ ØąØ§ ØĒŲ†Ø¸ÛŒŲ… Ų…ÛŒâ€ŒÚŠŲ†Ø¯. Ų…Ų‚Ø§Ø¯ÛŒØą ŲžØ§ÛŒÛŒŲ†â€ŒØĒØą ÚŠØ§ØąØ§ÛŒÛŒ ŲØ´ØąØ¯Ų‡â€ŒØŗØ§Ø˛ÛŒ ØąØ§ ÚŠØ§Ų‡Ø´ Ų…ÛŒâ€ŒØ¯Ų‡Ų†Ø¯ØŒ Ø§Ų…Ø§ Ø˛Ų…Ø§Ų† ØŦØŗØĒØŦ؈ ØąØ§ Ø¨Ų‡Ø¨ŲˆØ¯ Ų…ÛŒâ€ŒØ¨ØŽØ´Ų†Ø¯ ؈ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ÚŠÛŒŲÛŒØĒ ØąØ§ Ø¯Øą ØĩØ­Ų†Ų‡â€ŒŲ‡Ø§ÛŒ با Ø­ØąÚŠØĒ ØŗØąÛŒØš Ø¨Ų‡Ø¨ŲˆØ¯ Ø¯Ų‡Ų†Ø¯. Ų…Ų‚Ø¯Ø§Øą 0 Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą ØąØ§ Ø¨Ų‡â€ŒØˇŲˆØą ØŽŲˆØ¯ÚŠØ§Øą ØĒŲ†Ø¸ÛŒŲ… Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "transcoding_optimal_description": "ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Ø§Ø˛ ØąØ˛ŲˆŲ„ŲˆØ´Ų† Ų‡Ø¯Ų Ø¨Ø§Ų„Ø§ØĒØą Ų‡ØŗØĒŲ†Ø¯ یا Ø¯Øą Ų‚Ø§Ų„Ø¨ ŲžØ°ÛŒØąŲØĒŲ‡ Ø´Ø¯Ų‡ Ų†ÛŒØŗØĒŲ†Ø¯", - "transcoding_policy": "ØŗÛŒØ§ØŗØĒ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ", - "transcoding_policy_description": "ØĒØšÛŒÛŒŲ† Ø˛Ų…Ø§Ų† ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ ŲˆÛŒØ¯ÛŒŲˆ", "transcoding_preferred_hardware_device": "Ø¯ØŗØĒÚ¯Ø§Ų‡ ØŗØŽØĒâ€ŒØ§ŲØ˛Ø§ØąÛŒ ØĒØąØŦیحی", "transcoding_preferred_hardware_device_description": "Ø§ÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡ ŲŲ‚Øˇ Ø¨Ų‡ VAAPI ؈ QSV Ø§ØšŲ…Ø§Ų„ Ų…ÛŒâ€ŒØ´ŲˆØ¯. DRI node Ų…ŲˆØąØ¯ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø¨ØąØ§ÛŒ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ ØŗØŽØĒâ€ŒØ§ŲØ˛Ø§ØąÛŒ ØąØ§ ØĒŲ†Ø¸ÛŒŲ… Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "transcoding_preset_preset": "ŲžÛŒØ´â€ŒØĒŲ†Ø¸ÛŒŲ… (preset-)", "transcoding_preset_preset_description": "ØŗØąØšØĒ ŲØ´ØąØ¯Ų‡â€ŒØŗØ§Ø˛ÛŒ. ŲžÛŒØ´â€ŒØĒŲ†Ø¸ÛŒŲ…â€ŒŲ‡Ø§ÛŒ ÚŠŲ†Ø¯ØĒØą ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ÚŠŲˆÚ†ÚŠâ€ŒØĒØąÛŒ ØĒŲˆŲ„ÛŒØ¯ Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯ ؈ ÚŠÛŒŲÛŒØĒ ØąØ§ Ų‡Ų†Ú¯Ø§Ų… Ų‡Ø¯Ųâ€ŒÚ¯Ø°Ø§ØąÛŒ Ø¨Øą ØąŲˆÛŒ یڊ بیØĒâ€ŒØąÛŒØĒ ؎اØĩ Ø§ŲØ˛Ø§ÛŒØ´ Ų…ÛŒâ€ŒØ¯Ų‡Ų†Ø¯. VP9 ØŗØąØšØĒâ€ŒŲ‡Ø§ÛŒ Ø¨Ø§Ų„Ø§ØĒØą Ø§Ø˛ 'faster' ØąØ§ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ų…ÛŒâ€ŒÚ¯ÛŒØąØ¯.", - "transcoding_realtime": "ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯ [ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ]", - "transcoding_realtime_description": "Ø§Ų…ÚŠØ§Ų† ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų‡â€ŒØĩŲˆØąØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯ Ø¯Øą Ø­ÛŒŲ† ŲžØŽØ´ ŲˆÛŒØ¯ÛŒŲˆ ØąØ§ ŲØąØ§Ų‡Ų… Ų…ÛŒâ€ŒÚŠŲ†Ø¯. Ø§ÛŒŲ† Ų‚Ø§Ø¨Ų„ÛŒØĒ ØĒØēÛŒÛŒØą ÚŠÛŒŲÛŒØĒ ØąØ§ Ų…Ų…ÚŠŲ† Ų…ÛŒâ€ŒØŗØ§Ø˛Ø¯ØŒ Ø§Ų…Ø§ Ø¨ØŗØĒŲ‡ Ø¨Ų‡ ØĒŲˆØ§Ų† ØŗØąŲˆØąØŒ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ باؚØĢ Ø§ŲØ˛Ø§ÛŒØ´ ØĒØŖØŽÛŒØą Ø¯Øą ŲžØŽØ´ ؈ Ú¯ÛŒØą ÚŠØąØ¯Ų† ØĒØĩŲˆÛŒØą Ø´ŲˆØ¯.", - "transcoding_realtime_enabled": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯", - "transcoding_realtime_enabled_description": "Ø¯Øą ØĩŲˆØąØĒ ØēÛŒØąŲØšØ§Ų„ Ø¨ŲˆØ¯Ų†ØŒ ØŗØąŲˆØą Ø§Ø˛ Ø´ØąŲˆØš Ų†Ø´ØŗØĒâ€ŒŲ‡Ø§ÛŒ ØŦدید ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯ ØŽŲˆØ¯Ø¯Ø§ØąÛŒ ØŽŲˆØ§Ų‡Ø¯ ÚŠØąØ¯.", - "transcoding_realtime_resolutions": "؈ØļŲˆØ­â€ŒŲ‡Ø§ÛŒ ØĒØĩŲˆÛŒØąÛŒ", - "transcoding_realtime_resolutions_description": "؈ØļŲˆØ­â€ŒŲ‡Ø§ÛŒ Ø§ØąØ§ØĻŲ‡â€ŒØ´Ø¯Ų‡ Ø¨ØąØ§ÛŒ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯. ؈ØļŲˆØ­â€ŒŲ‡Ø§ÛŒ Ø¨Ø§Ų„Ø§ØĒØą Ų…Ų…ÚŠŲ† Ø§ØŗØĒ Ø¯Øą ØĩŲˆØąØĒ Ų†Ø§ØĒŲˆØ§Ų†ÛŒ ØŗØąŲˆØą Ø¯Øą ØĒØ¨Ø¯ÛŒŲ„ ØŗØąÛŒØš ØĸŲ†â€ŒŲ‡Ø§ØŒ باؚØĢ Ų…Ø´ÚŠŲ„Ø§ØĒ ŲžØŽØ´ Ø´ŲˆŲ†Ø¯.", - "transcoding_realtime_video_codecs": "ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ÛŒ ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ", - "transcoding_realtime_video_codecs_description": "ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ÛŒ ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ Ø§ØąØ§ØĻŲ‡â€ŒØ´Ø¯Ų‡ Ø¨ØąØ§ÛŒ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ø¨Ų„Ø§Ø¯ØąŲ†Ú¯. ÚŠŲ„Ø§ÛŒŲ†ØĒâ€ŒŲ‡Ø§ Ø¯Øą Ø­ÛŒŲ† ŲžØŽØ´ØŒ Ø¨Ų‡ØĒØąÛŒŲ† Ú¯Ø˛ÛŒŲ†Ų‡â€ŒØ§ÛŒ ØąØ§ ÚŠŲ‡ ŲžØ´ØĒÛŒØ¨Ø§Ų†ÛŒ Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯ Ø§Ų†ØĒ؎اب ØŽŲˆØ§Ų‡Ų†Ø¯ ÚŠØąØ¯. AV1 ÚŠØ§ØąØĸŲ…Ø¯ØĒØą Ø§Ø˛ HEVC ؈ HEVC ÚŠØ§ØąØĸŲ…Ø¯ØĒØą Ø§Ø˛ H.264 Ø§ØŗØĒ. Ų‡Ų†Ú¯Ø§Ų… Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ Ø´ØĒØ§Ø¨â€ŒØ¯Ų‡Ų†Ø¯Ų‡ ØŗØŽØĒâ€ŒØ§ŲØ˛Ø§ØąÛŒØŒ ØĒŲ†Ų‡Ø§ ÚŠØ¯ÚŠâ€ŒŲ‡Ø§ÛŒÛŒ ØąØ§ Ø§Ų†ØĒ؎اب ÚŠŲ†ÛŒØ¯ ÚŠŲ‡ Ø´ØĒØ§Ø¨â€ŒØ¯Ų‡Ų†Ø¯Ų‡ ØĒŲˆØ§Ų†Ø§ÛŒÛŒ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ ØĸŲ†â€ŒŲ‡Ø§ ØąØ§ Ø¯Ø§ØąØ¯. Ø¯Øą Ø­Ø§Ų„ØĒ ØĒØ¨Ø¯ÛŒŲ„ Ų†ØąŲ…â€ŒØ§ŲØ˛Ø§ØąÛŒØŒ ØĒ؈ØŦŲ‡ داشØĒŲ‡ باشید ÚŠŲ‡ ØŗØąØšØĒ H.264 بیشØĒØą Ø§Ø˛ AV1 ؈ ØŗØąØšØĒ AV1 بیشØĒØą Ø§Ø˛ HEVC Ø§ØŗØĒ.", "transcoding_reference_frames": "ŲØąÛŒŲ…â€ŒŲ‡Ø§ÛŒ Ų…ØąØŦØš", "transcoding_reference_frames_description": "ØĒؚداد ŲØąÛŒŲ…â€ŒŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Ų‡Ų†Ú¯Ø§Ų… ŲØ´ØąØ¯Ų‡â€ŒØŗØ§Ø˛ÛŒ یڊ ŲØąÛŒŲ… Ų…Ø´ØŽØĩ Ø¨Ų‡ ØĸŲ†â€ŒŲ‡Ø§ Ø§ØąØŦاؚ Ø¯Ø§Ø¯Ų‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ų…Ų‚Ø§Ø¯ÛŒØą Ø¨Ø§Ų„Ø§ØĒØą ÚŠØ§ØąØ§ÛŒÛŒ ŲØ´ØąØ¯Ų‡â€ŒØŗØ§Ø˛ÛŒ ØąØ§ Ø¨Ų‡Ø¨ŲˆØ¯ Ų…ÛŒâ€ŒØ¨ØŽØ´Ų†Ø¯ØŒ Ø§Ų…Ø§ ÚŠØ¯Ú¯Ø°Ø§ØąÛŒ ØąØ§ ÚŠŲ†Ø¯ØĒØą Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯. Ų…Ų‚Ø¯Ø§Øą 0 Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą ØąØ§ Ø¨Ų‡â€ŒØˇŲˆØą ØŽŲˆØ¯ÚŠØ§Øą ØĒŲ†Ø¸ÛŒŲ… Ų…ÛŒâ€ŒÚŠŲ†Ø¯.", "transcoding_required_description": "ŲŲ‚Øˇ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒÛŒ ÚŠŲ‡ Ø¯Øą ŲØąŲ…ØĒ ŲžØ°ÛŒØąŲØĒŲ‡â€ŒØ´Ø¯Ų‡ Ų†ÛŒØŗØĒŲ†Ø¯", @@ -446,10 +353,9 @@ "transcoding_temporal_aq_description": "Ø§ÛŒŲ† Ų…ŲˆØąØ¯ ŲŲ‚Øˇ Ø¨ØąØ§ÛŒ NVENC Ø§ØšŲ…Ø§Ų„ Ų…ÛŒ Ø´ŲˆØ¯. Temporal Adaptive Quantization ÚŠÛŒŲÛŒØĒ ØĩØ­Ų†Ų‡ Ų‡Ø§ÛŒ با ØŦØ˛ØĻیاØĒ Ø¨Ø§Ų„Ø§ØŒ ØĒØ­ØąÚŠ ÚŠŲ… ØąØ§ Ø§ŲØ˛Ø§ÛŒØ´ Ų…ÛŒ Ø¯Ų‡Ø¯. Ų…Ų…ÚŠŲ† Ø§ØŗØĒ با Ø¯ØŗØĒÚ¯Ø§Ų‡ Ų‡Ø§ÛŒ Ų‚Ø¯ÛŒŲ…ÛŒ ØĒØą ØŗØ§Ø˛Ú¯Ø§Øą Ų†Ø¨Ø§Ø´Ø¯.", "transcoding_threads": "ØąØ´ØĒŲ‡ Ų‡Ø§ ( Ų…ŲˆØļŲˆØšØ§ØĒ )", "transcoding_threads_description": "Ų…Ų‚Ø§Ø¯ÛŒØą Ø¨Ø§Ų„Ø§ØĒØą Ų…Ų†ØŦØą Ø¨Ų‡ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ ØŗØąÛŒØš ØĒØą Ų…ÛŒ Ø´ŲˆØ¯ØŒ Ø§Ų…Ø§ ؁Øļای ÚŠŲ…ØĒØąÛŒ Ø¨ØąØ§ÛŒ ŲžØąØ¯Ø§Ø˛Ø´ ØŗØ§ÛŒØą ŲˆØ¸Ø§ÛŒŲ ØŗØąŲˆØą Ø¯Øą Ø­ÛŒŲ† ŲØšØ§Ų„ÛŒØĒ Ø¨Ø§Ų‚ÛŒ Ų…ÛŒ Ú¯Ø°Ø§ØąØ¯. Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą Ų†Ø¨Ø§ÛŒØ¯ بیشØĒØą Ø§Ø˛ ØĒؚداد Ų‡ØŗØĒŲ‡ Ų‡Ø§ÛŒ CPU باشد. Ø§Ú¯Øą ØąŲˆÛŒ 0 ØĒŲ†Ø¸ÛŒŲ… Ø´ŲˆØ¯ØŒ بیشØĒØąÛŒŲ† Ø§ØŗØĒŲØ§Ø¯Ų‡ ØąØ§ ØŽŲˆØ§Ų‡Ø¯ داشØĒ.", - "transcoding_tone_mapping": "ØĒØ¨Ø¯ÛŒŲ„ ØąŲ†Ú¯ ؈ Ų†ŲˆØą (Tone-mapping)", "transcoding_tone_mapping_description": "ØĒŲ„Ø§Ø´ Ø¨ØąØ§ÛŒ Ø­ŲØ¸ Ø¸Ø§Ų‡Øą ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ HDR Ų‡Ų†Ú¯Ø§Ų… ØĒØ¨Ø¯ÛŒŲ„ Ø¨Ų‡ SDR. Ų‡Øą Ø§Ų„Ú¯ŲˆØąÛŒØĒŲ… ØĒØšØ§Ø¯Ų„ Ų‡Ø§ÛŒ Ų…ØĒŲØ§ŲˆØĒی ØąØ§ Ø¨ØąØ§ÛŒ ØąŲ†Ú¯ØŒ ØŦØ˛ØĻیاØĒ ؈ ØąŲˆØ´Ų†Ø§ÛŒÛŒ ایØŦاد Ų…ÛŒ ÚŠŲ†Ø¯. Hable ØŦØ˛ØĻیاØĒ ØąØ§ Ø­ŲØ¸ Ų…ÛŒ ÚŠŲ†Ø¯ØŒ Mobius ØąŲ†Ú¯ ØąØ§ Ø­ŲØ¸ Ų…ÛŒ ÚŠŲ†Ø¯ ؈ Reinhard ØąŲˆØ´Ų†Ø§ÛŒÛŒ ØąØ§ Ø­ŲØ¸ Ų…ÛŒ ÚŠŲ†Ø¯.", "transcoding_transcode_policy": "ØŗÛŒØ§ØŗØĒ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ", - "transcoding_transcode_policy_description": "ØŗÛŒØ§ØŗØĒ Ø˛Ų…Ø§Ų† ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ ŲˆÛŒØ¯ÛŒŲˆ. ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ HDR ؈ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒÛŒ با ŲØąŲ…ØĒ ŲžÛŒÚŠØŗŲ„ÛŒ ØēÛŒØą Ø§Ø˛ YUV 4:2:0 Ų‡Ų…ÛŒØ´Ų‡ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ Ų…ÛŒâ€ŒØ´ŲˆŲ†Ø¯ (Ų…Ú¯Øą Ø§ÛŒŲ†ÚŠŲ‡ ØĒØ¨Ø¯ÛŒŲ„ ŲØąŲ…ØĒ ØēÛŒØąŲØšØ§Ų„ باشد).", + "transcoding_transcode_policy_description": "ØŗÛŒØ§ØŗØĒ Ø¨ØąØ§ÛŒ Ø˛Ų…Ø§Ų†ÛŒ ÚŠŲ‡ ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ باید Ų…ØŦددا ØĒØ¨Ø¯ÛŒŲ„ (ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ) Ø´ŲˆØ¯. ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ HDR Ų‡Ų…ÛŒØ´Ų‡ ØĒØ¨Ø¯ÛŒŲ„ (ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ) Ų…ØŦدد ØŽŲˆØ§Ų‡Ų†Ø¯ شد (Ų…Ú¯Øą ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ Ų…ØŦدد ØēÛŒØąŲØšØ§Ų„ باشد).", "transcoding_two_pass_encoding": "ØĒØ¨Ø¯ÛŒŲ„ (ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ) Ø¯Ųˆ Ų…ØąØ­Ų„Ų‡ ای", "transcoding_two_pass_encoding_setting_description": "ØĒØ¨Ø¯ÛŒŲ„ (ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ) ŲˆÛŒØ¯ÛŒŲˆ Ø¯Øą Ø¯Ųˆ Ų…ØąØ­Ų„Ų‡ Ø¨ØąØ§ÛŒ ØĒŲˆŲ„ÛŒØ¯ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ÛŒ ØąŲ…Ø˛Ú¯Ø°Ø§ØąÛŒ Ø´Ø¯Ų‡ Ø¨Ų‡ØĒØą. ŲˆŲ‚ØĒی حداڊØĢØą Ų†ØąØŽ بیØĒ ŲØšØ§Ų„ باشد (Ø¨ØąØ§ÛŒ ÚŠØ§Øą با H.264 ؈ HEVC Ų„Ø§Ø˛Ų… Ø§ØŗØĒ)، Ø§ÛŒŲ† Ø­Ø§Ų„ØĒ Ø§Ø˛ یڊ Ų…Ø­Ø¯ŲˆØ¯Ų‡ Ų†ØąØŽ بیØĒ Ø¨Øą Ø§ØŗØ§Øŗ حداڊØĢØą Ų†ØąØŽ بیØĒ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų…ÛŒ ÚŠŲ†Ø¯ ؈ CRF ØąØ§ Ų†Ø§Ø¯ÛŒØ¯Ų‡ Ų…ÛŒ Ú¯ÛŒØąØ¯. Ø¨ØąØ§ÛŒ VP9، Ø§Ú¯Øą حداڊØĢØą Ų†ØąØŽ بیØĒ ØēÛŒØąŲØšØ§Ų„ باشد، Ų…ÛŒ ØĒŲˆØ§Ų† Ø§Ø˛ CRF Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠØąØ¯.", "transcoding_video_codec": "Codec ŲˆÛŒØ¯ÛŒŲˆÛŒÛŒ", @@ -466,7 +372,7 @@ "user_delete_delay": "{user}'s Ø­ØŗØ§Ø¨ ÚŠØ§ØąØ¨ØąÛŒ ؈ Ø¯Ø§ØąØ§ÛŒÛŒ Ų‡Ø§(ØšÚŠØŗ ؈ ŲÛŒŲ„Ų…) Ø¨ØąØ§ÛŒ Ø­Ø°Ų داØĻŲ…ÛŒ Ø¯Øą {delay, plural, one {# ØąŲˆØ˛} other {# ØąŲˆØ˛}} Ø¨ØąŲ†Ø§Ų…Ų‡ ØąÛŒØ˛ÛŒ ØŽŲˆØ§Ų‡Ų†Ø¯ شد.", "user_delete_delay_settings": "ØĒØŖØŽÛŒØą Ø¯Øą Ø­Ø°Ų", "user_delete_delay_settings_description": "ØĒؚداد ØąŲˆØ˛Ų‡Ø§ÛŒÛŒ ÚŠŲ‡ ŲžØŗ Ø§Ø˛ Ø­Ø°ŲØŒ Ø­ØŗØ§Ø¨ ÚŠØ§ØąØ¨ØąÛŒ ؈ Ø¯Ø§ØąØ§ÛŒÛŒ Ų‡Ø§ÛŒ(ØšÚŠØŗ ؈ ŲÛŒŲ„Ų…) ÚŠØ§ØąØ¨Øą Ø¨Ų‡ ØˇŲˆØą داØĻŲ…ÛŒ Ø­Ø°Ų Ų…ÛŒ Ø´ŲˆŲ†Ø¯. ÚŠØ§Øą Ø­Ø°Ų ÚŠØ§ØąØ¨Øą Ø¯Øą Ų†ÛŒŲ…Ų‡ شب اØŦØąØ§ Ų…ÛŒ Ø´ŲˆØ¯ ØĒا ÚŠØ§ØąØ¨ØąØ§Ų†ÛŒ ÚŠŲ‡ ØĸŲ…Ø§Ø¯Ų‡ Ø­Ø°Ų Ų‡ØŗØĒŲ†Ø¯ ØąØ§ Ø¨ØąØąØŗÛŒ ÚŠŲ†Ø¯. ØĒØēÛŒÛŒØąØ§ØĒ Ø¯Øą Ø§ÛŒŲ† ØĒŲ†Ø¸ÛŒŲ… Ø¯Øą اØŦØąØ§ÛŒ بؚدی Ø§ØąØ˛ÛŒØ§Ø¨ÛŒ ØŽŲˆØ§Ų‡Ų†Ø¯ شد.", - "user_delete_immediately": "{user} Ø­ØŗØ§Ø¨ ÚŠØ§ØąØ¨ØąÛŒ ؈ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒØ´ Ø¨Ų„Ø§ŲØ§ØĩŲ„Ų‡ Ø¨ØąØ§ÛŒ Ø­Ø°Ų داØĻŲ…ÛŒ Ø¯Øą Øĩ؁ Ų‚ØąØ§Øą ØŽŲˆØ§Ų‡Ų†Ø¯ Ú¯ØąŲØĒ.", + "user_delete_immediately": "{user}'s Ø­ØŗØ§Ø¨ ÚŠØ§ØąØ¨ØąÛŒ ؈ Ø¯Ø§ØąØ§ÛŒÛŒ Ų‡Ø§ (ØšÚŠØŗ ؈ ŲÛŒŲ„Ų…) ŲŲˆØąØ§Ų‹ Ø¨ØąØ§ÛŒ Ø­Ø°Ų داØĻŲ…ÛŒ Ø¯Øą Øĩ؁ Ų‚ØąØ§Øą ØŽŲˆØ§Ų‡Ų†Ø¯ Ú¯ØąŲØĒ.", "user_delete_immediately_checkbox": "ÚŠØ§ØąØ¨Øą ؈ Ø¯Ø§ØąØ§ÛŒÛŒ Ų‡Ø§ (ØšÚŠØŗ ؈ ŲÛŒŲ„Ų…) ØąØ§ Ø¨ØąØ§ÛŒ Ø­Ø°Ų ŲŲˆØąÛŒ Ø¯Øą Øĩ؁ Ų‚ØąØ§Øą Ø¨Ø¯Ų‡", "user_details": "ØŦØ˛ØĻیاØĒ ÚŠØ§ØąØ¨Øą", "user_management": "Ų…Ø¯ÛŒØąÛŒØĒ ÚŠØ§ØąØ¨Øą", @@ -478,8 +384,6 @@ "user_settings_description": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ÚŠØ§ØąØ¨Øą", "user_successfully_removed": "ÚŠØ§ØąØ¨Øą {email} با Ų…ŲˆŲŲ‚ÛŒØĒ Ø­Ø°Ų شد.", "users_page_description": "ØĩŲØ­Ų‡ Ų…Ø¯ÛŒØąÛŒØĒ ÚŠØ§ØąØ¨ØąØ§Ų†", - "version_check_channel": "ÚŠØ§Ų†Ø§Ų„ Ø§Ų†ØĒØ´Ø§Øą", - "version_check_channel_description": "ÚŠØ§Ų†Ø§Ų„ Ø§Ų†ØĒØ´Ø§ØąÛŒ ØąØ§ Ø§Ų†ØĒ؎اب ÚŠŲ†ÛŒØ¯ ÚŠŲ‡ Ų…ÛŒâ€ŒØŽŲˆØ§Ų‡ÛŒØ¯ Ø§ØšŲ„Ø§Ų†â€ŒŲ‡Ø§ÛŒ Ų†ØŗØŽŲ‡ ØŦدید ØąØ§ Ø§Ø˛ ØĸŲ† Ø¯ØąÛŒØ§ŲØĒ Ų†Ų…Ø§ÛŒÛŒØ¯", "version_check_enabled_description": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ Ø¨ØąØąØŗÛŒ Ų†ØŗØŽŲ‡", "version_check_implications": "ŲˆÛŒÚ˜Ú¯ÛŒ Ø¨ØąØąØŗÛŒ Ų†ØŗØŽŲ‡ Ø¨Ų‡ Ø§ØąØĒØ¨Ø§Øˇ Ø¯ŲˆØąŲ‡ ای با {server} Ų…ØĒÚŠÛŒ Ø§ØŗØĒ", "version_check_settings": "Ø¨ØąØąØŗÛŒ Ų†ØŗØŽŲ‡", @@ -491,112 +395,26 @@ "admin_password": "ØąŲ…Ø˛ ØšØ¨ŲˆØą Ų…Ø¯ÛŒØą", "administration": "Ų…Ø¯ÛŒØąÛŒØĒ", "advanced": "ŲžÛŒØ´ØąŲØĒŲ‡", - "advanced_settings_clear_image_cache": "ŲžØ§ÚŠâ€ŒØŗØ§Ø˛ÛŒ Ø­Ø§ŲØ¸Ų‡ ŲžŲ†Ų‡Ø§Ų† ØĒØĩØ§ŲˆÛŒØą", - "advanced_settings_clear_image_cache_error": "ØŽØˇØ§ Ø¯Øą ŲžØ§ÚŠâ€ŒØŗØ§Ø˛ÛŒ Ø­Ø§ŲØ¸Ų‡ ŲžŲ†Ų‡Ø§Ų† ØĒØĩØ§ŲˆÛŒØą", - "advanced_settings_clear_image_cache_success": "ŲžØ§ÚŠâ€ŒØŗØ§Ø˛ÛŒ Ų…ŲˆŲŲ‚ÛŒØĒ‌ØĸŲ…ÛŒØ˛ {size}", - "advanced_settings_log_level_title": "ØŗØˇØ­ Ú¯Ø˛Ø§ØąØ´â€ŒØ¯Ų‡ÛŒ: {level}", - "advanced_settings_prefer_remote_subtitle": "Ø¨ØąØŽÛŒ Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ Ø¯Øą Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ ØĒØĩØ§ŲˆÛŒØą Ø¨Ų†Ø¯Ø§Ų†Ú¯Ø´ØĒی Ø§Ø˛ Ų…Ų†Ø§Ø¨Øš Ų…Ø­Ų„ÛŒ Ø¨ØŗÛŒØ§Øą ÚŠŲ†Ø¯ ØšŲ…Ų„ Ų…ÛŒâ€ŒÚŠŲ†Ų†Ø¯. Ø§ÛŒŲ† ØĒŲ†Ø¸ÛŒŲ… ØąØ§ ŲØšØ§Ų„ ÚŠŲ†ÛŒØ¯ ØĒا Ø¨Ų‡ ØŦای ØĸŲ†ØŒ ØĒØĩØ§ŲˆÛŒØą Ø§Ø˛ ØąØ§Ų‡ Ø¯ŲˆØą Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø´ŲˆŲ†Ø¯.", - "advanced_settings_prefer_remote_title": "ØĒØąØŦیح ØĒØĩØ§ŲˆÛŒØą ØąØ§Ų‡ Ø¯ŲˆØą", - "advanced_settings_proxy_headers_subtitle": "Ų‡Ø¯ØąŲ‡Ø§ÛŒ ŲžØąØ§ÚŠØŗÛŒ ÚŠŲ‡ Immich باید با Ų‡Øą Ø¯ØąØŽŲˆØ§ØŗØĒ Ø´Ø¨ÚŠŲ‡ Ø§ØąØŗØ§Ų„ ÚŠŲ†Ø¯ ØąØ§ ØĒØšØąÛŒŲ ÚŠŲ†ÛŒØ¯", "advanced_settings_proxy_headers_title": "Ų‡Ø¯Øą Ų‡Ø§ÛŒ ŲžØąŲˆÚŠØŗÛŒ ØŗŲØ§ØąØ´ÛŒ [ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ]", - "advanced_settings_readonly_mode_subtitle": "Ø­Ø§Ų„ØĒ ŲŲ‚Øˇ ØŽŲˆØ§Ų†Ø¯Ų†ÛŒ ØąØ§ ŲØšØ§Ų„ Ų…ÛŒâ€ŒÚŠŲ†Ø¯ ÚŠŲ‡ Ø¯Øą ØĸŲ† ØĒØĩØ§ŲˆÛŒØą ØĒŲ†Ų‡Ø§ Ų‚Ø§Ø¨Ų„ Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų‡ØŗØĒŲ†Ø¯ ؈ ØšŲ…Ų„ÛŒØ§ØĒی Ų…Ø§Ų†Ų†Ø¯ Ø§Ų†ØĒ؎اب Ú†Ų†Ø¯ØĒایی، اشØĒØąØ§ÚŠâ€ŒÚ¯Ø°Ø§ØąÛŒØŒ ŲžØŽØ´ ØąŲˆÛŒ ØĩŲØ­Ų‡ Ø¯ÛŒÚ¯Øą ؈ Ø­Ø°Ų ØēÛŒØąŲØšØ§Ų„ Ų…ÛŒâ€ŒØ´ŲˆŲ†Ø¯. ŲØšØ§Ų„/ØēÛŒØąŲØšØ§Ų„ ÚŠØąØ¯Ų† Ø§ÛŒŲ† Ø­Ø§Ų„ØĒ Ø§Ø˛ ØˇØąÛŒŲ‚ ØĸŲˆØ§ØĒØ§Øą ÚŠØ§ØąØ¨Øą Ø¯Øą ØĩŲØ­Ų‡ اØĩŲ„ÛŒ Ø§Ų†ØŦØ§Ų… Ų…ÛŒâ€ŒØ´ŲˆØ¯", - "advanced_settings_readonly_mode_title": "Ø­Ø§Ų„ØĒ ŲŲ‚Øˇ ØŽŲˆØ§Ų†Ø¯Ų†ÛŒ", - "advanced_settings_sync_remote_deletions_subtitle": "Ø¨Ų‡â€ŒØˇŲˆØą ØŽŲˆØ¯ÚŠØ§Øą یڊ ŲØ§ÛŒŲ„ ØąØ§ Ø¯Øą Ø§ÛŒŲ† Ø¯ØŗØĒÚ¯Ø§Ų‡ Ø­Ø°Ų یا Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ ÚŠŲ†ØŒ Ų‡Ų†Ú¯Ø§Ų…ÛŒ ÚŠŲ‡ Ø§ÛŒŲ† ØšŲ…Ų„ Ø¯Øą Ų†ØŗØŽŲ‡ ŲˆØ¨ Ø§Ų†ØŦØ§Ų… Ø´ŲˆØ¯", - "advanced_settings_sync_remote_deletions_title": "Ų‡Ų…Ú¯Ø§Ų…â€ŒØŗØ§Ø˛ÛŒ Ø­Ø°Ųâ€ŒŲ‡Ø§ÛŒ ØąØ§Ų‡ Ø¯ŲˆØą [ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ]", - "advanced_settings_tile_subtitle": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ŲžÛŒØ´ØąŲØĒŲ‡ ÚŠØ§ØąØ¨Øą", - "advanced_settings_troubleshooting_subtitle": "ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ ŲˆÛŒÚ˜Ú¯ÛŒâ€ŒŲ‡Ø§ÛŒ اØļØ§ŲÛŒ Ø¨ØąØ§ÛŒ ؚیب‌یابی", - "advanced_settings_troubleshooting_title": "ؚیب‌یابی", - "age_months": "ØŗŲ† {months, plural, one {# Ų…Ø§Ų‡} other {# Ų…Ø§Ų‡}}", - "age_year_months": "ØŗŲ† Ûą ØŗØ§Ų„ØŒ {months, plural, one {# Ų…Ø§Ų‡} other {# Ų…Ø§Ų‡}}", - "age_years": "{years, plural, other {ØŗŲ† #}}", - "album": "ØĸŲ„Ø¨ŲˆŲ…", "album_added": "ØĸŲ„Ø¨ŲˆŲ… اØļØ§ŲŲ‡ شد", - "album_added_notification_setting_description": "Ø¯ØąÛŒØ§ŲØĒ Ø§ØšŲ„Ø§Ų† Ø§ÛŒŲ…ÛŒŲ„ Ų‡Ų†Ú¯Ø§Ų… Ø§ŲØ˛ŲˆØ¯Ų‡ Ø´Ø¯Ų† Ø¨Ų‡ یڊ ØĸŲ„Ø¨ŲˆŲ… Ų…Ø´ØĒØąÚŠ", "album_cover_updated": "ØŦŲ„Ø¯ ØĸŲ„Ø¨ŲˆŲ… Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ شد", - "album_delete_confirmation": "Øĸیا Ø§Ø˛ Ø­Ø°Ų ØĸŲ„Ø¨ŲˆŲ… {album} Ø§ØˇŲ…ÛŒŲ†Ø§Ų† Ø¯Ø§ØąÛŒØ¯ØŸ", - "album_delete_confirmation_description": "Ø§Ú¯Øą Ø§ÛŒŲ† ØĸŲ„Ø¨ŲˆŲ… Ø¨Ų‡ اشØĒØąØ§ÚŠ گذاشØĒŲ‡ Ø´Ø¯Ų‡ باشد، ØŗØ§ÛŒØą ÚŠØ§ØąØ¨ØąØ§Ų† Ø¯ÛŒÚ¯Øą Ų†Ų…ÛŒâ€ŒØĒŲˆØ§Ų†Ų†Ø¯ Ø¨Ų‡ ØĸŲ† Ø¯ØŗØĒØąØŗÛŒ داشØĒŲ‡ Ø¨Ø§Ø´Ų†Ø¯.", - "album_deleted": "ØĸŲ„Ø¨ŲˆŲ… Ø­Ø°Ų شد", "album_info_updated": "Ø§ØˇŲ„Ø§ØšØ§ØĒ ØĸŲ„Ø¨ŲˆŲ… Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ شد", "album_name": "Ų†Ø§Ų… ØĸŲ„Ø¨ŲˆŲ…", "album_options": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ ØĸŲ„Ø¨ŲˆŲ…", - "album_remove_user": "Ø­Ø°Ų ÚŠØ§ØąØ¨ØąØŸ", - "album_remove_user_confirmation": "Øĸیا Ø§Ø˛ Ø­Ø°Ų {user} Ø§ØˇŲ…ÛŒŲ†Ø§Ų† Ø¯Ø§ØąÛŒØ¯ØŸ", - "album_search_not_found": "Ų‡ÛŒÚ† ØĸŲ„Ø¨ŲˆŲ…ÛŒ Ų…ØˇØ§Ø¨Ų‚ با ØŦØŗØĒØŦŲˆÛŒ Ø´Ų…Ø§ ÛŒØ§ŲØĒ Ų†Ø´Ø¯", - "album_share_no_users": "Ø¨Ų‡ Ų†Ø¸Øą Ų…ÛŒâ€ŒØąØŗØ¯ Ø§ÛŒŲ† ØĸŲ„Ø¨ŲˆŲ… ØąØ§ با Ų‡Ų…Ų‡ ÚŠØ§ØąØ¨ØąØ§Ų† Ø¨Ų‡ اشØĒØąØ§ÚŠ گذاشØĒŲ‡â€ŒØ§ÛŒØ¯ یا ÚŠØ§ØąØ¨ØąÛŒ Ø¨ØąØ§ÛŒ Ø¨Ų‡ اشØĒØąØ§ÚŠâ€ŒÚ¯Ø°Ø§ØąÛŒ Ų†Ø¯Ø§ØąÛŒØ¯.", - "album_summary": "ØŽŲ„Ø§ØĩŲ‡ ØĸŲ„Ø¨ŲˆŲ…", "album_updated": "ØĸŲ„Ø¨ŲˆŲ… Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ شد", - "album_updated_setting_description": "Ø¯ØąÛŒØ§ŲØĒ Ø§ØšŲ„Ø§Ų† Ø§ÛŒŲ…ÛŒŲ„ Ų‡Ų†Ú¯Ø§Ų… Ø§ŲØ˛ŲˆØ¯Ų† ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ÛŒ ØŦدید Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… Ų…Ø´ØĒØąÚŠ", - "album_upload_assets": "Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ø§Ø˛ ØąØ§ÛŒØ§Ų†Ų‡ ؈ Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ…", - "album_viewer_appbar_share_err_delete": "Ø­Ø°Ų ØĸŲ„Ø¨ŲˆŲ… Ų†Ø§Ų…ŲˆŲŲ‚ Ø¨ŲˆØ¯", - "album_viewer_page_share_add_users": "Ø§ŲØ˛ŲˆØ¯Ų† ÚŠØ§ØąØ¨ØąØ§Ų†", - "album_with_link_access": "اØŦØ§Ø˛Ų‡ Ø¯Ų‡ÛŒØ¯ Ų‡Øą ÚŠØŗÛŒ با داشØĒŲ† ŲžÛŒŲˆŲ†Ø¯ØŒ ØĒØĩØ§ŲˆÛŒØą ؈ Ø§ŲØąØ§Ø¯ Ų…ŲˆØŦŲˆØ¯ Ø¯Øą Ø§ÛŒŲ† ØĸŲ„Ø¨ŲˆŲ… ØąØ§ Ų…Ø´Ø§Ų‡Ø¯Ų‡ ÚŠŲ†Ø¯.", "albums": "ØĸŲ„Ø¨ŲˆŲ…â€ŒŲ‡Ø§", - "albums_count": "{count, plural, one {{count, number} ØĸŲ„Ø¨ŲˆŲ…} other {{count, number} ØĸŲ„Ø¨ŲˆŲ…}}", - "albums_default_sort_order": "ØĒØąØĒیب ŲžÛŒØ´â€ŒŲØąØļ Ų…ØąØĒØ¨â€ŒØŗØ§Ø˛ÛŒ ØĸŲ„Ø¨ŲˆŲ…", - "albums_default_sort_order_description": "ØĒØąØĒیب Ø§ŲˆŲ„ÛŒŲ‡ Ų…ØąØĒØ¨â€ŒØŗØ§Ø˛ÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ų‡Ų†Ú¯Ø§Ų… ایØŦاد ØĸŲ„Ø¨ŲˆŲ…â€ŒŲ‡Ø§ÛŒ ØŦدید.", - "albums_feature_description": "Ų…ØŦŲ…ŲˆØšŲ‡â€ŒŲ‡Ø§ÛŒÛŒ Ø§Ø˛ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ ÚŠŲ‡ Ų…ÛŒâ€ŒØĒŲˆØ§Ų†Ų†Ø¯ با ØŗØ§ÛŒØą ÚŠØ§ØąØ¨ØąØ§Ų† Ø¨Ų‡ اشØĒØąØ§ÚŠ گذاشØĒŲ‡ Ø´ŲˆŲ†Ø¯.", - "albums_on_device_count": "ØĸŲ„Ø¨ŲˆŲ…â€ŒŲ‡Ø§ÛŒ Ų…ŲˆØŦŲˆØ¯ Ø¯Øą Ø¯ØŗØĒÚ¯Ø§Ų‡ ({count})", "all": "Ų‡Ų…Ų‡", - "all_albums": "Ų‡Ų…Ų‡ ØĸŲ„Ø¨ŲˆŲ…â€ŒŲ‡Ø§", "all_people": "Ų‡Ų…Ų‡ Ø§ŲØąØ§Ø¯", - "all_photos": "Ų‡Ų…Ų‡ ØĒØĩØ§ŲˆÛŒØą", - "all_videos": "Ų‡Ų…Ų‡ ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§", "allow_dark_mode": "اØŦØ§Ø˛Ų‡ Ø¯Ø§Ø¯Ų† Ø¨Ų‡ Ø­Ø§Ų„ØĒ ØĒØ§ØąÛŒÚŠ", - "allow_public_user_to_download": "اØŦØ§Ø˛Ų‡ Ø¯Ø§Ų†Ų„ŲˆØ¯ Ø¨Ų‡ ÚŠØ§ØąØ¨Øą ØšŲ…ŲˆŲ…ÛŒ", - "allow_public_user_to_upload": "اØŦØ§Ø˛Ų‡ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ø¨Ų‡ ÚŠØ§ØąØ¨Øą ØšŲ…ŲˆŲ…ÛŒ", - "allowed": "Ų…ØŦØ§Ø˛", - "alt_text_qr_code": "ØĒØĩŲˆÛŒØą ڊد QR", - "always_keep": "Ų‡Ų…ÛŒØ´Ų‡ Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ Ø´ŲˆØ¯", - "always_keep_photos_hint": "ÂĢØĸØ˛Ø§Ø¯ØŗØ§Ø˛ÛŒ ؁Øļای Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒÂģ ØĒŲ…Ø§Ų… ØĒØĩØ§ŲˆÛŒØą ØąØ§ ØąŲˆÛŒ Ø§ÛŒŲ† Ø¯ØŗØĒÚ¯Ø§Ų‡ Ų†Ú¯Ų‡ Ų…ÛŒâ€ŒØ¯Ø§ØąØ¯.", - "always_keep_videos_hint": "ÂĢØĸØ˛Ø§Ø¯ØŗØ§Ø˛ÛŒ ؁Øļای Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒÂģ ØĒŲ…Ø§Ų… ŲˆÛŒØ¯ÛŒŲˆŲ‡Ø§ ØąØ§ ØąŲˆÛŒ Ø§ÛŒŲ† Ø¯ØŗØĒÚ¯Ø§Ų‡ Ų†Ú¯Ų‡ Ų…ÛŒâ€ŒØ¯Ø§ØąØ¯.", + "allow_edits": "اØŦØ§Ø˛Ų‡ ŲˆÛŒØąØ§ÛŒØ´", "api_key": "ÚŠŲ„ÛŒØ¯ API", - "api_key_description": "Ø§ÛŒŲ† Ų…Ų‚Ø¯Ø§Øą ØĒŲ†Ų‡Ø§ یڊ Ø¨Ø§Øą Ų†Ų…Ø§ÛŒØ´ Ø¯Ø§Ø¯Ų‡ Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ų„ØˇŲØ§Ų‹ ŲžÛŒØ´ Ø§Ø˛ Ø¨ØŗØĒŲ† ŲžŲ†ØŦØąŲ‡ØŒ Ø§Ø˛ ÚŠŲžÛŒ ÚŠØąØ¯Ų† ØĸŲ† Ø§ØˇŲ…ÛŒŲ†Ø§Ų† حاØĩŲ„ Ų†Ų…Ø§ÛŒÛŒØ¯.", - "api_key_empty": "Ų†Ø§Ų… ÚŠŲ„ÛŒØ¯ API Ø´Ų…Ø§ Ų†Ø¨Ø§ÛŒØ¯ ØŽØ§Ų„ÛŒ باشد", "api_keys": "ÚŠŲ„ÛŒØ¯Ų‡Ø§ÛŒ API", - "app_architecture_variant": "Ų†ØŗØŽŲ‡ (Ų…ØšŲ…Ø§ØąÛŒ)", - "app_bar_signout_dialog_content": "Øĸیا Ø§Ø˛ ØŽØąŲˆØŦ Ø§ØˇŲ…ÛŒŲ†Ø§Ų† Ø¯Ø§ØąÛŒØ¯ØŸ", - "app_bar_signout_dialog_title": "ØŽØąŲˆØŦ", - "app_download_links": "ŲžÛŒŲˆŲ†Ø¯Ų‡Ø§ÛŒ Ø¯Ø§Ų†Ų„ŲˆØ¯ Ø¨ØąŲ†Ø§Ų…Ų‡", "app_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø¨ØąŲ†Ø§Ų…Ų‡", - "app_stores": "ŲØąŲˆØ´Ú¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ Ø¨ØąŲ†Ø§Ų…Ų‡", - "app_update_available": "Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ Ø¨ØąŲ†Ø§Ų…Ų‡ Ø¯Øą Ø¯ØŗØĒØąØŗ Ø§ØŗØĒ", "appears_in": "Ø¸Ø§Ų‡Øą Ų…ÛŒâ€ŒØ´ŲˆØ¯ Ø¯Øą", - "apply_count": "Ø§ØšŲ…Ø§Ų„ ({count, number})", "archive": "Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒ", - "archive_action_prompt": "{count} Ų…ŲˆØąØ¯ Ø¨Ų‡ ØĸØąØ´ÛŒŲˆ اØļØ§ŲŲ‡ شد", - "archive_or_unarchive_photo": "Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒ ÚŠØąØ¯Ų† یا ØŽØ§ØąØŦ ÚŠØąØ¯Ų† ØĒØĩŲˆÛŒØą Ø§Ø˛ Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒ", "archive_size": "Ø§Ų†Ø¯Ø§Ø˛Ų‡ Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒ", - "archive_size_description": "ŲžÛŒÚŠØąØ¨Ų†Ø¯ÛŒ Ø­ØŦŲ… ØĸØąØ´ÛŒŲˆ Ø¨ØąØ§ÛŒ Ø¯Ø§Ų†Ų„ŲˆØ¯Ų‡Ø§ (Ø¨Øą Ø­ØŗØ¨ گیگابایØĒ)", - "archived": "Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒâ€ŒØ´Ø¯Ų‡", - "archived_count": "{count, plural, other {Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒâ€ŒØ´Ø¯Ų‡ #}}", - "are_these_the_same_person": "Øĸیا Ø§ÛŒŲ†â€ŒŲ‡Ø§ یڊ Ų†ŲØą Ų‡ØŗØĒŲ†Ø¯ØŸ", - "are_you_sure_to_do_this": "Øĸیا Ø§Ø˛ Ø§Ų†ØŦØ§Ų… Ø§ÛŒŲ† ØšŲ…Ų„ Ø§ØˇŲ…ÛŒŲ†Ø§Ų† Ø¯Ø§ØąÛŒØ¯ØŸ", - "asset_added_to_album": "Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… اØļØ§ŲŲ‡ شد", - "asset_adding_to_album": "Ø¯Øą Ø­Ø§Ų„ Ø§ŲØ˛ŲˆØ¯Ų† Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ…â€Ļ", - "asset_created": "ŲØ§ÛŒŲ„ ایØŦاد شد", - "asset_day_count": "{date}: {count, plural, one {# ŲØ§ÛŒŲ„} other {# ŲØ§ÛŒŲ„}}", - "asset_description_updated": "ØĒ؈ØļیحاØĒ ŲØ§ÛŒŲ„ Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ شد", - "asset_hashing": "Ø¯Øą Ø­Ø§Ų„ Ų‡Ø´â€ŒØŗØ§Ø˛ÛŒâ€Ļ", - "asset_list_group_by_sub_title": "Ú¯ØąŲˆŲ‡â€ŒØ¨Ų†Ø¯ÛŒ Ø¨Øą Ø§ØŗØ§Øŗ", - "asset_list_layout_settings_group_by": "Ú¯ØąŲˆŲ‡â€ŒØ¨Ų†Ø¯ÛŒ ŲØ§ÛŒŲ„â€ŒŲ‡Ø§ Ø¨Øą Ø§ØŗØ§Øŗ", - "asset_list_layout_settings_group_by_month_day": "Ų…Ø§Ų‡ + ØąŲˆØ˛", - "asset_list_layout_sub_title": "ØˇØąØ­â€ŒØ¨Ų†Ø¯ÛŒ", - "asset_list_settings_subtitle": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ ØˇØąØ­â€ŒØ¨Ų†Ø¯ÛŒ Ø´Ø¨ÚŠŲ‡ ØĒØĩØ§ŲˆÛŒØą", - "asset_list_settings_title": "Ø´Ø¨ÚŠŲ‡ ØĒØĩØ§ŲˆÛŒØą", - "asset_not_found_on_device_android": "ŲØ§ÛŒŲ„ Ø¯Øą Ø¯ØŗØĒÚ¯Ø§Ų‡ ÛŒØ§ŲØĒ Ų†Ø´Ø¯", - "asset_not_found_on_device_ios": "ŲØ§ÛŒŲ„ Ø¯Øą Ø¯ØŗØĒÚ¯Ø§Ų‡ ÛŒØ§ŲØĒ Ų†Ø´Ø¯. Ø§Ú¯Øą Ø§Ø˛ iCloud Ø§ØŗØĒŲØ§Ø¯Ų‡ Ų…ÛŒâ€ŒÚŠŲ†ÛŒØ¯ØŒ Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ŲØ§ÛŒŲ„ Ø¨Ų‡ Ø¯Ų„ÛŒŲ„ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ Ų†Ø§Ø¯ØąØŗØĒ Ø¯Øą iCloud ØēÛŒØąŲ‚Ø§Ø¨Ų„ Ø¯ØŗØĒØąØŗÛŒ باشد", - "asset_not_found_on_icloud": "ŲØ§ÛŒŲ„ Ø¯Øą iCloud ÛŒØ§ŲØĒ Ų†Ø´Ø¯. Ų…Ų…ÚŠŲ† Ø§ØŗØĒ ŲØ§ÛŒŲ„ Ø¨Ų‡ Ø¯Ų„ÛŒŲ„ Ø°ØŽÛŒØąŲ‡â€ŒØŗØ§Ø˛ÛŒ Ų†Ø§Ø¯ØąØŗØĒ Ø¯Øą iCloud ØēÛŒØąŲ‚Ø§Ø¨Ų„ Ø¯ØŗØĒØąØŗÛŒ باشد", "asset_offline": "Ų…Ø­ØĒŲˆØ§ ØĸŲŲ„Ø§ÛŒŲ†", - "asset_offline_description": "Ø§ÛŒŲ† ŲØ§ÛŒŲ„ ØŽØ§ØąØŦی Ø¯ÛŒÚ¯Øą Ø¯Øą Ø¯ÛŒØŗÚŠ ÛŒØ§ŲØĒ Ų†Ų…ÛŒâ€ŒØ´ŲˆØ¯. Ų„ØˇŲØ§Ų‹ Ø¨ØąØ§ÛŒ Ø¯ØąÛŒØ§ŲØĒ ÚŠŲ…ÚŠ با Ų…Ø¯ÛŒØą Immich ØŽŲˆØ¯ ØĒŲ…Ø§Øŗ Ø¨Ú¯ÛŒØąÛŒØ¯.", - "asset_skipped": "ØąØ¯ شد", - "asset_skipped_in_trash": "Ø¯Øą ØŗØˇŲ„ Ø˛Ø¨Ø§Ų„Ų‡", - "asset_troubleshoot": "ؚیب‌یابی ŲØ§ÛŒŲ„", - "asset_uploaded": "Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ شد", - "asset_uploading": "Ø¯Øą Ø­Ø§Ų„ Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒâ€Ļ", - "asset_viewer_settings_subtitle": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ų†Ų…Ø§ÛŒØ´Ú¯Øą Ú¯Ø§Ų„ØąÛŒ Ø´Ų…Ø§", - "asset_viewer_settings_title": "Ų†Ų…Ø§ÛŒØ´Ú¯Øą ŲØ§ÛŒŲ„", "assets": "Ų…Ø­ØĒŲˆØ§Ų‡Ø§", - "assets_added_to_album_count": "{count, plural, one {# ŲØ§ÛŒŲ„} other {# ŲØ§ÛŒŲ„}} Ø¨Ų‡ ØĸŲ„Ø¨ŲˆŲ… اØļØ§ŲŲ‡ شد", "authorized_devices": "Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ Ų…ØŦØ§Ø˛", "back": "Ø¨Ø§Ø˛Ú¯Ø´ØĒ", "backward": "ØšŲ‚Ø¨", @@ -609,9 +427,10 @@ "cannot_merge_people": "Ų†Ų…ÛŒâ€ŒØĒŲˆØ§Ų† Ø§ŲØąØ§Ø¯ ØąØ§ ادØēØ§Ų… ÚŠØąØ¯", "cannot_update_the_description": "Ų†Ų…ÛŒâ€ŒØĒŲˆØ§Ų† ØĒ؈ØļیحاØĒ ØąØ§ Ø¨Ų‡â€ŒØąŲˆØ˛ØąØŗØ§Ų†ÛŒ ÚŠØąØ¯", "change_date": "ØĒØēÛŒÛŒØą ØĒØ§ØąÛŒØŽ", + "change_expiration_time": "ØĒØēÛŒÛŒØą Ø˛Ų…Ø§Ų† Ø§Ų†Ų‚Øļا", "change_location": "ØĒØēÛŒÛŒØą Ų…ÚŠØ§Ų†", "change_name": "ØĒØēÛŒÛŒØą Ų†Ø§Ų…", - "change_name_successfully": "ØĒØēÛŒÛŒØą Ų†Ø§Ų… با Ų…ŲˆŲŲ‚ÛŒØĒ Ø§Ų†ØŦØ§Ų… شد", + "change_name_successfully": "Ų†Ø§Ų… با Ų…ŲˆŲŲ‚ÛŒØĒ ØĒØēÛŒÛŒØą ÛŒØ§ŲØĒ", "change_password": "ØĒØēÛŒÛŒØą ØąŲ…Ø˛ ØšØ¨ŲˆØą", "change_password_form_password_mismatch": "ØąŲ…Ø˛ ØšØ¨ŲˆØą Ų‡Ø§ Ų…ØˇØ§Ø¨Ų‚ØĒ Ų†Ø¯Ø§ØąŲ†Ø¯", "change_password_form_reenter_new_password": "ØĒÚŠØąØ§Øą ØąŲ…Ø˛ ØšØ¨ŲˆØą ØŦدید", @@ -624,6 +443,7 @@ "clear_value": "ŲžØ§ÚŠ ÚŠØąØ¯Ų† Ų…Ų‚Ø¯Ø§Øą", "close": "Ø¨ØŗØĒŲ†", "collapse_all": "ØŦŲ…Øš ÚŠØąØ¯Ų† Ų‡Ų…Ų‡", + "color_theme": "ØĒŲ… ØąŲ†Ú¯", "comment_options": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ Ų†Ø¸Øą", "comments_are_disabled": "Ų†Ø¸ØąØ§ØĒ ØēÛŒØąŲØšØ§Ų„ Ų‡ØŗØĒŲ†Ø¯", "confirm": "ØĒØŖÛŒÛŒØ¯", @@ -635,6 +455,7 @@ "copied_image_to_clipboard": "ØĒØĩŲˆÛŒØą Ø¨Ų‡ ÚŠŲ„ÛŒŲžâ€ŒØ¨ŲˆØąØ¯ ÚŠŲžÛŒ شد.", "copied_to_clipboard": "Ø¨Ų‡ ÚŠŲ„ÛŒŲžâ€ŒØ¨ŲˆØąØ¯ ÚŠŲžÛŒ شد!", "copy_error": "ØŽØˇØ§ Ø¯Øą ÚŠŲžÛŒ", + "copy_file_path": "ÚŠŲžÛŒ Ų…ØŗÛŒØą ŲØ§ÛŒŲ„", "copy_image": "ÚŠŲžÛŒ ØĒØĩŲˆÛŒØą", "copy_link": "ÚŠŲžÛŒ Ų„ÛŒŲ†ÚŠ", "copy_link_to_clipboard": "ÚŠŲžÛŒ Ų„ÛŒŲ†ÚŠ Ø¨Ų‡ ÚŠŲ„ÛŒŲžâ€ŒØ¨ŲˆØąØ¯", @@ -672,8 +493,10 @@ "details": "ØŦØ˛ØĻیاØĒ", "direction": "ØŦŲ‡ØĒ", "disabled": "ØēÛŒØąŲØšØ§Ų„", + "disallow_edits": "ØšØ¯Ų… اØŦØ§Ø˛Ų‡ ŲˆÛŒØąØ§ÛŒØ´", "discover": "ÚŠØ´Ų ÚŠØąØ¯Ų†", "dismiss_all_errors": "ØąØ¯ ØĒŲ…Ø§Ų… ØŽØˇØ§Ų‡Ø§", + "dismiss_error": "ØąØ¯ ØŽØˇØ§", "display_options": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ Ų†Ų…Ø§ÛŒØ´", "display_order": "ØĒØąØĒیب Ų†Ų…Ø§ÛŒØ´", "display_original_photos": "Ų†Ų…Ø§ÛŒØ´ ØšÚŠØŗâ€ŒŲ‡Ø§ÛŒ اØĩŲ„ÛŒ", @@ -756,6 +579,7 @@ "let_others_respond": "اØŦØ§Ø˛Ų‡ Ø¨Ų‡ Ø¯ÛŒÚ¯ØąØ§Ų† Ø¨ØąØ§ÛŒ ŲžØ§ØŗØŽâ€ŒÚ¯ŲˆÛŒÛŒ", "level": "ØŗØˇØ­", "library": "ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", + "library_options": "Ú¯Ø˛ÛŒŲ†Ų‡â€ŒŲ‡Ø§ÛŒ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", "light": "ØąŲˆØ´Ų†", "link_to_oauth": "اØĒØĩØ§Ų„ Ø¨Ų‡ OAuth", "linked_oauth_account": "Ø­ØŗØ§Ø¨ OAuth Ų…ØĒØĩŲ„ Ø´Ø¯Ų‡", @@ -764,12 +588,13 @@ "loading_search_results_failed": "Ø¨Ø§ØąÚ¯Ø°Ø§ØąÛŒ Ų†ØĒایØŦ ØŦØŗØĒØŦ؈ Ų†Ø§Ų…ŲˆŲŲ‚ Ø¨ŲˆØ¯", "log_out": "ØŽØąŲˆØŦ Ø§Ø˛ ØŗÛŒØŗØĒŲ…", "log_out_all_devices": "ØŽØąŲˆØŦ Ø§Ø˛ Ų‡Ų…Ų‡ Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§", - "login_has_been_disabled": "ŲˆØąŲˆØ¯ ØēÛŒØąŲØšØ§Ų„ Ø´Ø¯Ų‡ Ø§ØŗØĒ", + "login_has_been_disabled": "ŲˆØąŲˆØ¯ ØēÛŒØąŲØšØ§Ų„ Ø´Ø¯Ų‡ Ø§ØŗØĒ.", "look": "Ų†Ú¯Ø§Ų‡ ÚŠØąØ¯Ų†", "loop_videos": "ŲžØŽØ´ Ų…Ø¯Ø§ŲˆŲ… ŲˆÛŒØ¯ØĻŲˆŲ‡Ø§", "main_branch_warning": "Ø´Ų…Ø§ Ø¯Øą Ø­Ø§Ų„ Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ Ų†ØŗØŽŲ‡ ØĒŲˆØŗØšŲ‡â€ŒØ¯Ų‡Ų†Ø¯Ú¯Ø§Ų† Ų‡ØŗØĒید ÚŠŲ‡ ØĸØ˛Ų…Ø§ÛŒØ´ÛŒ ؈ Ų†Ø§ŲžØ§ÛŒØ¯Ø§Øą Ø§ØŗØĒ. Ø§ÚŠÛŒØ¯Ø§Ų‹ ØĒ؈ØĩÛŒŲ‡ Ų…ÛŒâ€ŒÚŠŲ†ÛŒŲ… Ø§Ø˛ Ų†ØŗØŽŲ‡ ØąØŗŲ…ÛŒ Ø§ØŗØĒŲØ§Ø¯Ų‡ ÚŠŲ†ÛŒØ¯!", "main_menu": "Ų…Ų†ŲˆÛŒ اØĩŲ„ÛŒ", "make": "ØŗØ§ØŽØĒŲ†", + "manage_shared_links": "Ų…Ø¯ÛŒØąÛŒØĒ Ų„ÛŒŲ†ÚŠâ€ŒŲ‡Ø§ÛŒ اشØĒØąØ§ÚŠÛŒ", "manage_sharing_with_partners": "Ų…Ø¯ÛŒØąÛŒØĒ Ų…Ø­ØĒŲˆØ§ÛŒ Ų…Ø´ØĒØąÚŠ با Ų‡Ų…ØŗØą", "manage_the_app_settings": "Ų…Ø¯ÛŒØąÛŒØĒ ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø¨ØąŲ†Ø§Ų…Ų‡", "manage_your_account": "Ų…Ø¯ÛŒØąÛŒØĒ Ø­ØŗØ§Ø¨ ÚŠØ§ØąØ¨ØąÛŒ", @@ -777,10 +602,13 @@ "manage_your_devices": "Ų…Ø¯ÛŒØąÛŒØĒ Ø¯ØŗØĒÚ¯Ø§Ų‡â€ŒŲ‡Ø§ÛŒ Ų…ØĒØĩŲ„", "manage_your_oauth_connection": "Ų…Ø¯ÛŒØąÛŒØĒ اØĒØĩØ§Ų„ OAuth", "map": "Ų†Ų‚Ø´Ų‡", + "map_assets_in_bounds": "{count} ØšÚŠØŗ Ų‡Ø§", "map_cannot_get_user_location": "Ų…ŲˆŲ‚ØšÛŒØĒ Ų…ÚŠØ§Ų†ÛŒ Ø¯Øą Ø¯ØŗØĒØąØŗ Ų†ÛŒØŗØĒ", + "map_location_dialog_yes": "Ø¨Ų„Ų‡", "map_location_picker_page_use_location": "Ø§ØŗØĒŲØ§Ø¯Ų‡ Ø§Ø˛ Ø§ÛŒŲ† Ų…ŲˆŲ‚ØšÛŒØĒ Ų…ÚŠØ§Ų†ÛŒ", "map_location_service_disabled_content": "Ø¨ØąØ§ÛŒ Ų†Ų…Ø§ÛŒØ´ Ø¯Ø§ØąØ§ÛŒÛŒâ€ŒŲ‡Ø§ Ø¨Øą Ø§ØŗØ§Øŗ Ų…ŲˆŲ‚ØšÛŒØĒ Ų…ÚŠØ§Ų†ÛŒØŒ Ų†ÛŒØ§Ø˛ Ø¨Ų‡ ŲØšØ§Ų„â€ŒØŗØ§Ø˛ÛŒ ØŗØąŲˆÛŒØŗ Ų…ÚŠØ§Ų†â€ŒÛŒØ§Ø¨ÛŒ Ø¯Ø§ØąÛŒØ¯. Ų…ÛŒâ€ŒØŽŲˆØ§Ų‡ÛŒØ¯ Ų‡Ų…ÛŒŲ† Ø­Ø§Ų„Ø§ ŲØšØ§Ų„ Ø´ŲˆØ¯ØŸ", "map_location_service_disabled_title": "ØŗØąŲˆÛŒØŗ Ų…ÚŠØ§Ų†â€ŒÛŒØ§Ø¨ÛŒ ØēÛŒØąŲØšØ§Ų„ Ø§ØŗØĒ", + "map_marker_for_images": "Ų†Ø´Ø§Ų†Ú¯Øą ØąŲˆÛŒ Ų†Ų‚Ø´Ų‡ Ø¨ØąØ§ÛŒ ØšÚŠØŗâ€ŒŲ‡Ø§ÛŒ Ú¯ØąŲØĒŲ‡â€ŒØ´Ø¯Ų‡ Ø¯Øą {city}, {country}", "map_marker_with_image": "ØšŲ„Ø§Ų…ØĒâ€ŒÚ¯Ø°Ø§ØąÛŒ Ų†Ų‚Ø´Ų‡ با ØšÚŠØŗ", "map_no_location_permission_content": "Ø¨ØąØ§ÛŒ Ų†Ų…Ø§ÛŒØ´ ØšÚŠØŗâ€ŒŲ‡Ø§ÛŒ Ø§ØˇØąØ§ŲØĒØ§Ų†ØŒ Ø¨ØąŲ†Ø§Ų…Ų‡ Ų†ÛŒØ§Ø˛ Ø¨Ų‡ Ø¯ØŗØĒØąØŗÛŒ Ø¨Ų‡ Ų…ŲˆŲ‚ØšÛŒØĒ Ų…ÚŠØ§Ų†ÛŒ Ø¯Ø§ØąØ¯. اØŦØ§Ø˛Ų‡ Ø¯ØŗØĒØąØŗÛŒ Ų…ÛŒâ€ŒØ¯Ų‡ÛŒØ¯ØŸ", "map_no_location_permission_title": "Ø¯ØŗØĒØąØŗÛŒ Ø¨Ų‡ Ų…ŲˆŲ‚ØšÛŒØĒ Ø´Ų…Ø§ ŲØšØ§Ų„ Ų†ÛŒØŗØĒ", @@ -790,6 +618,7 @@ "map_settings_date_range_option_days": "{days} ØąŲˆØ˛ گذشØĒŲ‡", "map_settings_date_range_option_year": "ŲžØ§ØąØŗØ§Ų„", "map_settings_date_range_option_years": "{years} ØŗØ§Ų„ گذشØĒŲ‡", + "map_settings_dialog_title": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ų†Ų‚Ø´Ų‡", "map_settings_include_show_archived": "Ø´Ø§Ų…Ų„ Ų…ŲˆØ§ØąØ¯ Ø¨Ø§ÛŒÚ¯Ø§Ų†ÛŒ Ø´Ø¯Ų‡", "map_settings_include_show_partners": "Ø´Ø§Ų…Ų„ Ų‡Ų…ØŗØą", "matches": "ØĒØˇØ§Ø¨Ų‚â€ŒŲ‡Ø§", @@ -820,7 +649,7 @@ "next_memory": "ØŽØ§ØˇØąŲ‡ بؚدی", "no": "ØŽÛŒØą", "no_duplicates_found": "Ų‡ÛŒÚ† ØĒÚŠØąØ§ØąÛŒ ÛŒØ§ŲØĒ Ų†Ø´Ø¯.", - "no_exif_info_available": "Ø§ØˇŲ„Ø§ØšØ§ØĒ exif Ų…ŲˆØŦŲˆØ¯ Ų†ÛŒØŗØĒ", + "no_exif_info_available": "Ø§ØˇŲ„Ø§ØšØ§ØĒ EXIF Ų…ŲˆØŦŲˆØ¯ Ų†ÛŒØŗØĒ", "no_name": "Ø¨Ø¯ŲˆŲ† Ų†Ø§Ų…", "no_places": "Ų…ÚŠØ§Ų†ÛŒ ÛŒØ§ŲØĒ Ų†Ø´Ø¯", "no_results": "Ų†ØĒیØŦŲ‡â€ŒØ§ÛŒ ÛŒØ§ŲØĒ Ų†Ø´Ø¯", @@ -829,7 +658,6 @@ "notification_toggle_setting_description": "Ø§ØšŲ„Ø§Ų†â€ŒŲ‡Ø§ÛŒ Ø§ÛŒŲ…ÛŒŲ„ÛŒ ØąØ§ ŲØšØ§Ų„ ÚŠŲ†ÛŒØ¯", "notifications": "Ø§ØšŲ„Ø§Ų†â€ŒŲ‡Ø§", "notifications_setting_description": "Ų…Ø¯ÛŒØąÛŒØĒ Ø§ØšŲ„Ø§Ų†â€ŒŲ‡Ø§", - "oauth": "Ø§Ø­ØąØ§Ø˛ Ų‡ŲˆÛŒØĒ OAuth", "offline": "ØĸŲŲ„Ø§ÛŒŲ†", "ok": "ØĒØŖÛŒÛŒØ¯", "oldest_first": "Ų‚Ø¯ÛŒŲ…ÛŒâ€ŒØĒØąÛŒŲ† ابØĒدا", @@ -878,7 +706,7 @@ "preview": "ŲžÛŒØ´â€ŒŲ†Ų…Ø§ÛŒØ´", "previous": "Ų‚Ø¨Ų„ÛŒ", "previous_memory": "ØŽØ§ØˇØąŲ‡ Ų‚Ø¨Ų„ÛŒ", - "previous_or_next_photo": "ØšÚŠØŗ بؚدی/Ų‚Ø¨Ų„ÛŒ", + "previous_or_next_photo": "ØšÚŠØŗ Ų‚Ø¨Ų„ÛŒ یا بؚدی", "primary": "اØĩŲ„ÛŒ", "profile_picture_set": "ØĒØĩŲˆÛŒØą ŲžØąŲˆŲØ§ÛŒŲ„ ØĒŲ†Ø¸ÛŒŲ… شد.", "public_share": "اشØĒØąØ§ÚŠ ØšŲ…ŲˆŲ…ÛŒ", @@ -889,9 +717,12 @@ "refresh": "ØĒØ§Ø˛Ų‡ ØŗØ§Ø˛ÛŒ", "refreshed": "ØĒØ§Ø˛Ų‡ ØŗØ§Ø˛ÛŒ شد", "remove": "Ø­Ø°Ų", + "remove_deleted_assets": "Ø­Ø°Ų Ų…Ø­ØĒŲˆØ§Ų‡Ø§ÛŒ Ø­Ø°Ųâ€ŒØ´Ø¯Ų‡", "remove_from_album": "Ø­Ø°Ų Ø§Ø˛ ØĸŲ„Ø¨ŲˆŲ…", "remove_from_favorites": "Ø­Ø°Ų Ø§Ø˛ ØšŲ„Ø§Ų‚Ų‡â€ŒŲ…Ų†Ø¯ÛŒâ€ŒŲ‡Ø§", "rename": "ØĒØēÛŒÛŒØą Ų†Ø§Ų…", + "repair": "ØĒØšŲ…ÛŒØą", + "replace_with_upload": "ØŦØ§ÛŒÚ¯Ø˛ÛŒŲ†ÛŒ با ØĸŲžŲ„ŲˆØ¯", "reset": "Ø¨Ø§Ø˛Ų†Ø´Ø§Ų†ÛŒ", "reset_password": "Ø¨Ø§Ø˛Ų†Ø´Ø§Ų†ÛŒ ØąŲ…Ø˛ ØšØ¨ŲˆØą", "restore": "Ø¨Ø§Ø˛ÛŒØ§Ø¨ÛŒ", @@ -905,6 +736,7 @@ "saved_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø°ØŽÛŒØąŲ‡ شد", "say_something": "Ú†ÛŒØ˛ÛŒ Ø¨Ú¯ŲˆÛŒÛŒØ¯", "scan_all_libraries": "Ø§ØŗÚŠŲ† Ų‡Ų…Ų‡ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡â€ŒŲ‡Ø§", + "scan_settings": "ØĒŲ†Ø¸ÛŒŲ…Ø§ØĒ Ø§ØŗÚŠŲ†", "search": "ØŦØŗØĒØŦ؈", "search_albums": "ØŦØŗØĒØŦŲˆÛŒ ØĸŲ„Ø¨ŲˆŲ…â€ŒŲ‡Ø§", "search_by_context": "ØŦØŗØĒØŦ؈ Ø¨ØąØ§ØŗØ§Øŗ Ø˛Ų…ÛŒŲ†Ų‡", @@ -925,6 +757,7 @@ "select_face": "Ø§Ų†ØĒ؎اب Ú†Ų‡ØąŲ‡", "select_featured_photo": "Ø§Ų†ØĒ؎اب ØšÚŠØŗ ŲˆÛŒÚ˜Ų‡", "select_keep_all": "Ø§Ų†ØĒ؎اب Ų†Ú¯Ų‡Ø¯Ø§ØąÛŒ Ų‡Ų…Ų‡", + "select_library_owner": "Ø§Ų†ØĒ؎اب Ų…Ø§Ų„ÚŠ ÚŠØĒØ§Ø¨ØŽØ§Ų†Ų‡", "select_new_face": "Ø§Ų†ØĒ؎اب Ú†Ų‡ØąŲ‡ ØŦدید", "select_photos": "Ø§Ų†ØĒ؎اب ØšÚŠØŗâ€ŒŲ‡Ø§", "selected": "Ø§Ų†ØĒ؎اب Ø´Ø¯Ų‡", @@ -1008,6 +841,7 @@ "view": "Ų…Ø´Ø§Ų‡Ø¯Ų‡", "view_all": "Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų‡Ų…Ų‡", "view_all_users": "Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų‡Ų…Ų‡ ÚŠØ§ØąØ¨ØąØ§Ų†", + "view_links": "Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų„ÛŒŲ†ÚŠâ€ŒŲ‡Ø§", "view_next_asset": "Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų…Ø­ØĒŲˆØ§ÛŒ بؚدی", "view_previous_asset": "Ų…Ø´Ø§Ų‡Ø¯Ų‡ Ų…Ø­ØĒŲˆØ§ÛŒ Ų‚Ø¨Ų„ÛŒ", "waiting": "Ø¯Øą Ø§Ų†ØĒØ¸Ø§Øą", @@ -1015,6 +849,5 @@ "welcome": "ØŽŲˆØ´ ØĸŲ…Ø¯ÛŒØ¯", "year": "ØŗØ§Ų„", "yes": "Ø¨Ų„Ų‡", - "zero_to_clear_rating": "Ø¨ØąØ§ÛŒ ŲžØ§ÚŠ ÚŠØąØ¯Ų† Ø§Ų…ØĒÛŒØ§Ø˛ ŲØ§ÛŒŲ„ØŒ ÚŠŲ„ÛŒØ¯ Û° ØąØ§ ŲØ´Ø§Øą Ø¯Ų‡ÛŒØ¯", "zoom_image": "Ø¨Ø˛ØąÚ¯Ų†Ų…Ø§ÛŒÛŒ ØĒØĩŲˆÛŒØą" } diff --git a/i18n/fi.json b/i18n/fi.json index 6fcd8e5e06..aaa2ee2bc1 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -10,18 +10,24 @@ "active": "Aktiivinen", "active_count": "Aktiivisia: {count}", "activity": "Tapahtumat", + "activity_changed": "Toiminto {enabled, select, true {otettu käyttÃļÃļn} other {poistettu käytÃļstä}}", "add": "Lisää", "add_a_description": "Lisää kuvaus", "add_a_location": "Lisää sijainti", "add_a_name": "Lisää nimi", "add_a_title": "Lisää otsikko", "add_action": "Lisää toiminto", + "add_action_description": "Klikkaa lisätäksesi suoritettava toiminto", "add_assets": "Lisää sisältÃļä", "add_birthday": "Lisää syntymäpäivä", "add_endpoint": "Lisää päätepiste", "add_exclusion_pattern": "Lisää poissulkemismalli", + "add_filter": "Lisää suodatin", + "add_filter_description": "Klikkaa lisätäksesi suodatinehto", "add_location": "Lisää sijainti", + "add_more_users": "Lisää käyttäjiä", "add_partner": "Lisää kumppani", + "add_path": "Lisää polku", "add_photos": "Lisää kuvia", "add_tag": "Lisää tunniste", "add_to": "Lisääâ€Ļ", @@ -29,11 +35,14 @@ "add_to_album_bottom_sheet_added": "Lisätty albumiin {album}", "add_to_album_bottom_sheet_already_exists": "Kohde on jo albumissa {album}", "add_to_album_bottom_sheet_some_local_assets": "Joitakin osia paikallisesta sisällÃļstä ei pystytty lisäämään albumiin", + "add_to_album_toggle": "Vaihda albumin {album} valintaa", "add_to_albums": "Lisää albumeihin", "add_to_albums_count": "Lisää albumeihin ({count})", "add_to_bottom_bar": "Lisää", + "add_to_shared_album": "Lisää jaettuun albumiin", "add_upload_to_stack": "Lisää kuvapinoon", "add_url": "Lisää URL", + "add_workflow_step": "Lisää tyÃļnkulun vaihe", "added_to_archive": "Lisätty arkistoon", "added_to_favorites": "Lisätty suosikkeihin", "added_to_favorites_count": "{count, number} lisätty suosikkeihin", @@ -258,8 +267,6 @@ "notification_enable_email_notifications": "Ota käyttÃļÃļn sähkÃļposti-ilmoitukset", "notification_settings": "Ilmoitusasetukset", "notification_settings_description": "Hallitse ilmoitusasetuksia, myÃļs sähkÃļpostin", - "oauth_allow_insecure_requests": "Salli turvattomat pyynnÃļt", - "oauth_allow_insecure_requests_description": "VAROITUS: Tämä poistaa TLS-varmenteen tarkistuksen OAuth-pyynnÃļiltä ja voi altistaa sinut väliintulohyÃļkkäyksille (MITM).", "oauth_auto_launch": "Automaattinen käynnistys", "oauth_auto_launch_description": "Aloita OAuth-kirjautumisvuo heti kun saavutaan kirjautumissivulle", "oauth_auto_register": "Automaattinen rekisterÃļinti", @@ -267,22 +274,20 @@ "oauth_button_text": "Painikkeen teksti", "oauth_client_secret_description": "Vaaditaan luottamukselliselle asiakasohjelmalle, tai jos julkinen asiakasohjelma ei tue PKCE:tä (Proof Key for Code Exchange).", "oauth_enable_description": "Kirjaudu käyttäen OAuthia", - "oauth_end_session_url_description": "Uudelleenohjaa käyttäjä tähän URI-osoitteeseen uloskirjautumisen jälkeen.", "oauth_mobile_redirect_uri": "Mobiilin uudelleenohjaus-URI", "oauth_mobile_redirect_uri_override": "Ohita mobiilin uudelleenohjaus-URI", "oauth_mobile_redirect_uri_override_description": "Ota käyttÃļÃļn kun OAuth-tarjoaja ei salli mobiili-URI:a, kuten ''{callback}''", - "oauth_prompt_description": "Kehoteparametri (esim. select_account, login, consent)", "oauth_role_claim": "Roolin vaatimus", "oauth_role_claim_description": "Salli pääkäyttäjän pääsyoikeus automaattisesti tämän vaatimuksen perusteella. Vaatimus voi sisältää, joko 'käyttäjän' tai 'pääkäyttäjän'.", "oauth_settings": "OAuth", "oauth_settings_description": "Hallitse OAuth-kirjautumisen asetuksia", "oauth_settings_more_details": "Saadaksesi lisätietoja tästä toiminnosta, katso dokumentaatio.", "oauth_storage_label_claim": "Tallennustilan nimikkeen valtuutusväittämä (claim)", - "oauth_storage_label_claim_description": "Määritä käyttäjän tallennustilan nimike tämän vaatimuksen arvoksi automaattisesti.", - "oauth_storage_quota_claim": "Tallennustilan kiintiÃļn vaatimus", + "oauth_storage_label_claim_description": "Määriä käyttäjän tallennustilan nimike tämän väittämän arvoksi automaattisesti.", + "oauth_storage_quota_claim": "Tallennustilan kiintiÃļn väittämä (claim)", "oauth_storage_quota_claim_description": "Aseta automaattisesti käyttäjien tallennustilan määrä tähän arvoon.", "oauth_storage_quota_default": "Tallennustilan oletuskiintiÃļ (Gt)", - "oauth_storage_quota_default_description": "Käytettävä kiintiÃļn määrä gigatavuissa, kun vaatimusta ei ole annettu.", + "oauth_storage_quota_default_description": "Käytettävä kiintiÃļn määrä gigatavuissa, kun väittämää ei ole annettu.", "oauth_timeout": "PyynnÃļn aikakatkaisu", "oauth_timeout_description": "PyyntÃļjen aikakatkaisu millisekunteina", "ocr_job_description": "Käytä koneoppimista tunnistamaan tekstiä kuvista", @@ -298,8 +303,6 @@ "refreshing_all_libraries": "Virkistetään kaikki kirjastot", "registration": "Pääkäyttäjän rekisterÃļinti", "registration_description": "Pääkäyttäjänä olet vastuussa järjestelmän hallinnallisista tehtävistä ja uusien käyttäjien luomisesta.", - "release_channel_release_candidate": "Julkaisuehdokas", - "release_channel_stable": "Vakaa", "remove_failed_jobs": "Poista epäonnistuneet tehtävät", "require_password_change_on_login": "Vaadi käyttäjää vaihtamaan salasana ensimmäisellä kirjautumiskerralla", "reset_settings_to_default": "Nollaa asetukset oletuksille", @@ -437,8 +440,6 @@ "user_settings_description": "Hallitse käyttäjäasetuksia", "user_successfully_removed": "Käyttäjä {email} on poistettu onnistuneesti.", "users_page_description": "Ylläpitäjän käyttäjien lista", - "version_check_channel": "Julkaisukanava", - "version_check_channel_description": "Valitse julkaisukanava josta haluat saada ilmoitukset", "version_check_enabled_description": "Ota käyttÃļÃļn versiotarkastus", "version_check_implications": "Versiotarkistus vaatii säännÃļllisen yhteyden {server}iin", "version_check_settings": "Versiotarkistus", @@ -453,6 +454,8 @@ "advanced_settings_clear_image_cache": "Tyhjennä kuvien välimuisti", "advanced_settings_clear_image_cache_error": "Kuvien välimuistin tyhjentäminen epäonnistui", "advanced_settings_clear_image_cache_success": "Tyhjennettiin onnistuneesti {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Käytä tätä vaihtoehtoa suodattaaksesi mediaa synkronoinnin aikana vaihtoehtoisten kriteerien perusteella. Kokeile tätä vain, jos sovelluksessa on ongelmia kaikkien albumien tunnistamisessa.", + "advanced_settings_enable_alternate_media_filter_title": "[KOKEELLINEN] Käytä vaihtoehtoisen laitteen albumin synkronointisuodatinta", "advanced_settings_log_level_title": "Kirjaustaso: {level}", "advanced_settings_prefer_remote_subtitle": "Jotkut laitteet ovat erittäin hitaita lataamaan esikatselukuvia paikallisista kohteista. Aktivoi tämä asetus käyttääksesi etäkuvia.", "advanced_settings_prefer_remote_title": "Suosi etäkuvia", @@ -460,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Mukautetut välityspalvelimen otsikot [KOKEELLINEN]", "advanced_settings_readonly_mode_subtitle": "Aktivoi vain luku -tilan, jolloin valokuvia voi ainoastaan selata. Toiminnot kuten useiden kuvien valitseminen, jakaminen, siirtäminen toistolaitteelle ja poistaminen ovat pois käytÃļstä. Laita vain luku -tila päälle tai pois päältä päävalikon käyttäjäkuvakkeesta", "advanced_settings_readonly_mode_title": "Vain luku -tila", + "advanced_settings_self_signed_ssl_subtitle": "Ohita SSL sertifikaattivarmennus palvelimen päätepisteellä. Vaaditaan self-signed -sertifikaateissa.", + "advanced_settings_self_signed_ssl_title": "Salli self-signed SSL -sertifikaatit [KOKEELLINEN]", "advanced_settings_sync_remote_deletions_subtitle": "Poista tai palauta kohde automaattisesti tällä laitteella, kun kyseinen toiminto suoritetaan verkossa", "advanced_settings_sync_remote_deletions_title": "Synkronoi etäpoistot [KOKEELLINEN]", "advanced_settings_tile_subtitle": "Edistyneen käyttäjän asetukset", @@ -475,18 +480,31 @@ "album_delete_confirmation": "Haluatko varmasti poistaa albumin {album}?", "album_delete_confirmation_description": "Jos albumi on jaettu, muut eivät pääse siihen enää.", "album_deleted": "Albumi poistettu", + "album_info_card_backup_album_excluded": "JÄTETTY POIS", + "album_info_card_backup_album_included": "SISÄLLYTETTY", "album_info_updated": "Albumin tiedot päivitetty", + "album_leave": "Poistu albumista?", + "album_leave_confirmation": "Haluatko varmasti poistua albumista {album}?", "album_name": "Albumin nimi", "album_options": "Albumin asetukset", "album_remove_user": "Poista käyttäjä?", "album_remove_user_confirmation": "Oletko varma että haluat poistaa {user}?", "album_search_not_found": "Haullasi ei lÃļytynyt yhtään albumia", + "album_selected": "Albumi valittu", "album_share_no_users": "Näyttää että olet jakanut tämän albumin kaikkien kanssa, tai sinulla ei ole käyttäjiä joille jakaa.", "album_summary": "Albumi tiivistelmä", "album_updated": "Albumi päivitetty", "album_updated_setting_description": "Saa sähkÃļpostia kun jaetussa albumissa on uutta sisältÃļä", "album_upload_assets": "Lataa kohteet tietokoneelta ja lisää ne albumiin", + "album_user_left": "Poistuttiin albumista {album}", + "album_user_removed": "{user} poistettu", + "album_viewer_appbar_delete_confirm": "Haluatko varmasti poistaa tämän albumin tililtäsi?", "album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui", + "album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui", + "album_viewer_appbar_share_err_remove": "Ongelmia kohteiden poistamisessa albumista", + "album_viewer_appbar_share_err_title": "Albumin nimen muuttaminen epäonnistui", + "album_viewer_appbar_share_leave": "Poistu albumista", + "album_viewer_appbar_share_to": "Jaa", "album_viewer_page_share_add_users": "Lisää käyttäjiä", "album_with_link_access": "Anna kenen tahansa nähdä linkin kautta tämän albumin valokuvat ja henkilÃļt.", "albums": "Albumit", @@ -495,12 +513,14 @@ "albums_default_sort_order_description": "Kohteiden ensisijainen lajittelujärjestys uusia albumeja luotaessa.", "albums_feature_description": "Kokoelma kohteita, jotka voidaan jakaa muille käyttäjille.", "albums_on_device_count": "({count}) albumia laitteella", + "albums_selected": "{count, plural, one {# albumi valittu} other {# albumia valittu}}", "all": "Kaikki", "all_albums": "Kaikki albumit", "all_people": "Kaikki henkilÃļt", "all_photos": "Kaikki kuvat", "all_videos": "Kaikki videot", "allow_dark_mode": "Salli tumma tila", + "allow_edits": "Salli muutokset", "allow_public_user_to_download": "Salli julkisten käyttäjien ladata tiedostoja", "allow_public_user_to_upload": "Salli julkisten käyttäjien lähettää tiedostoja", "allowed": "Sallittu", @@ -508,12 +528,14 @@ "always_keep": "Säilytä aina", "always_keep_photos_hint": "Tilan vapauttaminen säilyttää kaikki kuvat tällä laitteella.", "always_keep_videos_hint": "Tilan vapauttaminen säilyttää kaikki videot tällä laitteella.", + "anti_clockwise": "Vastapäivään", "api_key": "API-avain", "api_key_description": "Tämä arvo näytetään vain kerran. Varmista, että olet kopioinut sen ennen kuin suljet ikkunan.", "api_key_empty": "API-avaimesi ei pitäisi olla tyhjä", "api_keys": "API-avaimet", "app_architecture_variant": "Variantti (Arkkitehtuuri)", "app_bar_signout_dialog_content": "Haluatko varmasti kirjautua ulos?", + "app_bar_signout_dialog_ok": "Kyllä", "app_bar_signout_dialog_title": "Kirjaudu ulos", "app_download_links": "Sovelluksen latauslinkit", "app_settings": "Sovellusasetukset", @@ -524,18 +546,27 @@ "archive": "Arkisto", "archive_action_prompt": "{count} lisätty arkistoon", "archive_or_unarchive_photo": "Arkistoi kuva tai palauta arkistosta", + "archive_page_no_archived_assets": "Arkistoituja kohteita ei lÃļytynyt", + "archive_page_title": "Arkisto ({count})", "archive_size": "Arkiston koko", "archive_size_description": "Määritä arkiston koko latauksissa (Gt)", "archived": "Arkistoitu", "archived_count": "{count, plural, other {Arkistoitu #}}", "are_these_the_same_person": "Ovatko he sama henkilÃļ?", "are_you_sure_to_do_this": "Haluatko varmasti tehdä tämän?", + "array_field_not_fully_supported": "Taulukkokentät vaativat JSON:in manuaalista muokkaamista", + "asset_action_delete_err_read_only": "Vain luku -tilassa olevia kohteita ei voitu poistaa, ohitetaan", + "asset_action_share_err_offline": "Verkottomassa tilassa olevia kohteita ei voitu noutaa, ohitetaan", "asset_added_to_album": "Lisätty albumiin", "asset_adding_to_album": "Lisätään albumiinâ€Ļ", "asset_created": "Kohde luotu", "asset_description_updated": "Kohteen kuvaus on päivitetty", + "asset_filename_is_offline": "Kohde {filename} on offline-tilassa", + "asset_has_unassigned_faces": "Kohteella on määrittämättÃļmiä kasvoja", "asset_hashing": "Hajautetaanâ€Ļ", "asset_list_group_by_sub_title": "Ryhmittele", + "asset_list_layout_settings_dynamic_layout_title": "Dynaaminen asetelma", + "asset_list_layout_settings_group_automatically": "Automaattisesti", "asset_list_layout_settings_group_by": "Ryhmittele", "asset_list_layout_settings_group_by_month_day": "Kuukauden ja päivän mukaan", "asset_list_layout_sub_title": "Asettelu", @@ -546,26 +577,36 @@ "asset_not_found_on_icloud": "Kohdetta ei lÃļytynyt iCloudista. Kohde voi olla käyttämättÃļmissä iCloudiin tallennetun viallisen tiedoston vuoksi", "asset_offline": "Aineisto offline-tilassa", "asset_offline_description": "Tätä ulkoista kohdetta ei enää lÃļydy levyltä. Ole hyvä ja ota yhteyttä Immich-järjestelmänvalvojaan saadaksesi apua.", + "asset_restored_successfully": "Kohde palautettu onnistuneesti", "asset_skipped": "Ohitettu", "asset_skipped_in_trash": "Roskakorissa", + "asset_trashed": "Kohde poistettu", "asset_troubleshoot": "SisällÃļn vian paikannus", "asset_uploaded": "Lähetetty", "asset_uploading": "Ladataanâ€Ļ", "asset_viewer_settings_subtitle": "Galleriakatseluohjelman asetusten hallinta", "asset_viewer_settings_title": "Katselin", "assets": "Kohteet", + "assets_added_count": "Lisätty {count, plural, one {# kohde} other {# kohdetta}}", "assets_added_to_album_count": "Albumiin lisätty {count, plural, one {# kohde} other {# kohdetta}}", "assets_added_to_albums_count": "Lisätty {assetTotal, plural, one {# kohde} other {# kohdetta}} {albumTotal, plural, one {# albumiin} other {# albumiin}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Kohdetta} other {Kohdetta}} ei voida lisätä albumiin", "assets_cannot_be_added_to_albums": "{count, plural, one {Aineisto} other {Aineistoa}} ei voi lisätä mihinkään albumiin", "assets_count": "{count, plural, one {# media} other {# mediaa}}", + "assets_deleted_permanently": "{count} kohdetta poistettu pysyvästi", + "assets_deleted_permanently_from_server": "{count} objektia poistettu pysyvästi Immich-palvelimelta", + "assets_downloaded_failed": "{count, plural, one {Ladattu # tiedosto - {error} tiedosto epäonnistui} other {ladattu # tiedostoa - {error} tiedostot epäonnistuivat}}", + "assets_downloaded_successfully": "{count, plural, one {Ladattu # tiedosto onnistuneesti} other {Ladattu # tiedostoa onnistuneesti}}", "assets_moved_to_trash_count": "Siirretty {count, plural, one {# media} other {# mediaa}} roskakoriin", "assets_permanently_deleted_count": "{count, plural, one {# media} other {# mediaa}} poistettu pysyvästi", "assets_removed_count": "{count, plural, one {# media} other {# mediaa}} poistettu", + "assets_removed_permanently_from_device": "{count} kohdetta on poistettu pysyvästi laitteeltasi", "assets_restore_confirmation": "Haluatko varmasti palauttaa kaikki roskakoriisi siirretyt kohteet? Tätä toimintoa ei voi peruuttaa! Huomaa, että offline-kohteita ei voida palauttaa tällä tavalla.", "assets_restored_count": "{count, plural, one {# media} other {# mediaa}} palautettu", + "assets_restored_successfully": "{count} kohdetta palautettu onnistuneesti", "assets_trashed": "{count} kohdetta siirretty roskakoriin", "assets_trashed_count": "{count, plural, one {# media} other {# mediaa}} siirretty roskakoriin", + "assets_trashed_from_server": "{count} kohdetta siirretty roskakoriin Immich-palvelimelta", "assets_were_part_of_album_count": "{count, plural, one {Media oli} other {Mediat olivat}} jo albumissa", "assets_were_part_of_albums_count": "{count, plural, one {Aineisto} other {Aineistoa}} oli jo albumeissa", "authorized_devices": "Valtuutetut laitteet", @@ -574,47 +615,87 @@ "autoplay_slideshow": "Toista diaesitys automaattisesti", "back": "Takaisin", "back_close_deselect": "Palaa, sulje tai poista valinnat", + "background_backup_running_error": "Tausta varmuuskopiointi on aktiivinen, ei voida aloittaa manuaalista varmuuskopiointia", "background_location_permission": "Taustasijainnin käyttÃļoikeus", "background_location_permission_content": "Jotta sovellus voi vaihtaa verkkoa taustalla toimiessaan, Immichillä on *aina* oltava pääsy tarkkaan sijaintiin, jotta se voi lukea Wi-Fi-verkon nimen", "background_options": "Tausta valinnat", "backup": "Varmuuskopiointi", + "backup_album_selection_page_albums_device": "Laitteen albumit ({count})", "backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois", "backup_album_selection_page_assets_scatter": "Kohteet voivat olla hajaantuneina useisiin albumeihin. Albumeita voidaan sisällyttää varmuuskopiointiin tai jättää siitä pois.", "backup_album_selection_page_select_albums": "Valitse albumit", "backup_album_selection_page_selection_info": "Valintatiedot", + "backup_album_selection_page_total_assets": "Ainulaatuisia kohteita yhteensä", "backup_albums_sync": "Varmuuskopioitujen albumeiden synkronointi", + "backup_all": "Kaikki", + "backup_background_service_backup_failed_message": "Kohteiden varmuuskopiointi epäonnistui. Yritetään uudelleenâ€Ļ", "backup_background_service_complete_notification": "Kohteiden varmuuskopiointi valmis", + "backup_background_service_connection_failed_message": "Palvelimeen ei saatu yhteyttä. Yritetään uudelleenâ€Ļ", + "backup_background_service_current_upload_notification": "Lähetetään {filename}", "backup_background_service_default_notification": "Tarkistetaan uusia kohteitaâ€Ļ", + "backup_background_service_error_title": "Virhe varmuuskopioinnissa", "backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteitaâ€Ļ", + "backup_background_service_upload_failure_notification": "Lähetys epäonnistui {filename}", "backup_controller_page_albums": "Varmuuskopioi albumit", + "backup_controller_page_background_app_refresh_disabled_content": "Salli sovelluksen päivittäminen taustalla suorittaaksesi varmuuskopiointia taustalla: Asetukset > Yleiset > Appien päivitys taustalla.", + "backup_controller_page_background_app_refresh_disabled_title": "Sovelluksen päivittäminen taustalla on pois päältä", + "backup_controller_page_background_app_refresh_enable_button_text": "Siirry asetuksiin", "backup_controller_page_background_battery_info_link": "Näytä minulle miten", "backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyÃļskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Akun optimointi", + "backup_controller_page_background_charging": "Vain laitteen ollessa kytkettynä laturiin", + "backup_controller_page_background_configure_error": "Taustapalvelun asettaminen epäonnistui", "backup_controller_page_background_delay": "Viivästytä uusien kohteiden varmuuskopiointia: {duration}", + "backup_controller_page_background_description": "Kytke taustapalvelu päälle varmuuskopioidaksesi uudet kohteet automaattisesti, ilman sovelluksen avaamista", + "backup_controller_page_background_is_off": "Automaattinen varmuuskopiointi taustalla on pois päältä", + "backup_controller_page_background_is_on": "Automaattinen varmuuskopiointi taustalla on päällä", + "backup_controller_page_background_turn_off": "Kytke taustapalvelu pois päältä", + "backup_controller_page_background_turn_on": "Kytke taustapalvelu päälle", + "backup_controller_page_background_wifi": "Vain WiFi-verkossa", "backup_controller_page_backup": "Varmuuskopiointi", "backup_controller_page_backup_selected": "Valittu: ", "backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot", + "backup_controller_page_created": "Luotu: {date}", + "backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle lähettääksesi uudet kohteet palvelimelle automaattisesti.", "backup_controller_page_excluded": "Poissuljettu: ", + "backup_controller_page_failed": "Epäonnistui ({count})", + "backup_controller_page_filename": "Tiedostonimi: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Varmuuskopioinnin tiedot", "backup_controller_page_none_selected": "Ei mitään valittuna", "backup_controller_page_remainder": "Jäljellä", "backup_controller_page_remainder_sub": "Varmuuskopiointia odottavat kuvat ja videot", "backup_controller_page_server_storage": "Palvelimen tallennustila", + "backup_controller_page_start_backup": "Aloita varmuuskopiointi", + "backup_controller_page_status_off": "Varmuuskopiointi on pois päältä", + "backup_controller_page_status_on": "Varmuuskopiointi on päällä", "backup_controller_page_storage_format": "{used} / {total} käytetty", "backup_controller_page_to_backup": "Varmuuskopioitavat albumit", "backup_controller_page_total_sub": "Kaikki uniikit kuvat ja videot valituista albumeista", + "backup_controller_page_turn_off": "Varmuuskopiointi pois päältä", + "backup_controller_page_turn_on": "Varmuuskopiointi päälle", + "backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot", + "backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna", "backup_error_sync_failed": "Synkronointi epäonnistui. Varmuuskopion käsittely ei onnistu.", "backup_info_card_assets": "kohdetta", + "backup_manual_cancelled": "Peruutettu", + "backup_manual_in_progress": "Lähetys palvelimelle on jo käynnissä. Kokeile myÃļhemmin uudelleen", + "backup_manual_success": "Onnistui", + "backup_manual_title": "Lähetyksen tila", "backup_options": "Varmuuskopiointiasetukset", + "backup_options_page_title": "Varmuuskopioinnin asetukset", + "backup_setting_subtitle": "Hallinnoi aktiivisia ja taustalla olevia lähetysasetuksia", "backup_settings_subtitle": "Hallitse lähetysasetuksia", + "backup_upload_details_page_more_details": "Paina saadaksesi lisätietoja", "backward": "Taaksepäin", "biometric_auth_enabled": "Biometrinen tunnistautuminen käytÃļssä", "biometric_locked_out": "Sinulta on evätty pääsy biometriseen tunnistautumiseen", "biometric_no_options": "Ei biometrisiä vaihtoehtoja", "biometric_not_available": "Biometrinen tunnistautuminen ei ole käytettävissä tässä laitteessa", + "birthdate_saved": "Syntymäaika tallennettu", "birthdate_set_description": "Syntymäaikaa käytetään laskemaan henkilÃļn ikä kuvanottohetkellä.", "blurred_background": "Sumennettu tausta", - "browse_templates": "Selaa malleja", "bugs_and_feature_requests": "Bugit ja ominaisuuspyynnÃļt", "build": "Koontiversio", "build_image": "Koontiversion kuva", @@ -622,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Haluatko varmasti säilyttää {count, plural, one {# kaksoiskappaleen} other {# kaksoiskappaleet}}? Tämä merkitsee kaikki kaksoiskappaleet ratkaistuiksi, eikä poista mitään.", "bulk_trash_duplicates_confirmation": "Haluatko varmasti siirtää {count, plural, one {# kaksoiskappaleen} other {# kaksoiskappaleet}} roskakoriin? Tämä säilyttää kustakin mediasta kookkaimman ja siirtää loput roskakoriin.", "buy": "Osta lisenssi Immich:iin", + "cache_settings_clear_cache_button": "Tyhjennä välimuisti", + "cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.", + "cache_settings_duplicated_assets_clear_button": "Tyhjennä", + "cache_settings_duplicated_assets_subtitle": "Sovelluksen sivuutettavaksi merkitsemät valokuvat ja videot", + "cache_settings_duplicated_assets_title": "Kaksoiskappaleet ({count})", + "cache_settings_statistics_album": "Kirjaston esikatselukuvat", + "cache_settings_statistics_full": "Täysikokoiset kuvat", + "cache_settings_statistics_shared": "Jaettujen albumien esikatselukuvat", + "cache_settings_statistics_thumbnail": "Esikatselukuvat", + "cache_settings_statistics_title": "Välimuistin käyttÃļ", + "cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttÃļä", + "cache_settings_tile_subtitle": "Hallitse paikallista tallennustilaa", + "cache_settings_tile_title": "Paikallinen tallennustila", + "cache_settings_title": "Välimuistin asetukset", "camera": "Kamera", "camera_brand": "Kameran merkki", "camera_model": "Kameran malli", @@ -634,10 +729,10 @@ "cannot_update_the_description": "Kuvausta ei voi päivittää", "cast": "Suoratoisto", "cast_description": "Määritä saatavilla olevat suoratoistopalvelut", - "change": "Muutos", "change_date": "Vaihda päiväys", "change_description": "Muuta kuvausta", "change_display_order": "Muuta näyttÃļjärjestystä", + "change_expiration_time": "Muuta erääntymisaikaa", "change_location": "Vaihda sijainti", "change_name": "Vaihda nimi", "change_name_successfully": "Nimi vaihdettu", @@ -651,13 +746,17 @@ "change_password_form_password_mismatch": "Salasanat eivät täsmää", "change_password_form_reenter_new_password": "Uusi salasana uudelleen", "change_pin_code": "Vaihda PIN-koodi", + "change_trigger": "Vaihda laukaisin", + "change_trigger_prompt": "Haluatko varmasti vaihtaa laukaisimen? Tämä poistaa kaikki olemassa olevat toiminnot ja suodattimet.", "change_your_password": "Vaihda salasanasi", "changed_visibility_successfully": "Näkyvyys vaihdettu", "charging": "Ladataan laitetta", "charging_requirement_mobile_backup": "Varmuuskopiointi taustalla vaatii laitteen latautumista", + "check_corrupt_asset_backup": "Vioittuneiden varmuuskopioiden tarkistaminen", + "check_corrupt_asset_backup_button": "Suorita tarkistus", + "check_corrupt_asset_backup_description": "Suorita tämä tarkistus vain Wi-Fi-yhteyden kautta ja vasta, kun kaikki kohteet on varmuuskopioitu. Toimenpide voi kestää muutamia minuutteja.", "check_logs": "Katso lokeja", "checksum": "Tarkistussumma", - "choose": "Valitse", "choose_matching_people_to_merge": "Valitse henkilÃļt joka yhdistetään", "city": "Kaupunki", "cleanup_confirm_description": "Immich lÃļysi {count} turvallisesti palvelimelle varmuuskopioitua kohdetta (luotu ennen {date}). Poistetaanko paikalliset kopiot tästä laitteesta?", @@ -678,18 +777,22 @@ "clear_file_cache": "Tyhjennä tiedostovälimuisti", "clear_message": "Tyhjennä viesti", "clear_value": "Tyhjää arvo", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Anna salasana", "client_cert_import": "Tuo", "client_cert_import_success_msg": "Asiakasvarmenne tuotu", "client_cert_invalid_msg": "Virheellinen varmennetiedosto tai väärä salasana", "client_cert_password_message": "SyÃļtä salasana tälle sertifikaatille", "client_cert_password_title": "Sertifikaatin salasana", - "client_cert_remove_msg": "Asiakasvarmenne on poistettu", + "client_cert_remove_msg": "Asiakassertifikaatti on poistettu", "client_cert_subtitle": "Vain PKCS12 (.p12, .pfx) -muotoa tuetaan. Varmenteen tuonti/poisto on käytettävissä vain ennen sisäänkirjautumista", - "client_cert_title": "SSL-asiakasvarmenne [KOKEELLINEN]", + "client_cert_title": "SSL-asiakassertifikaatti [KOKEELLINEN]", + "clockwise": "MyÃļtäpäivään", "close": "Sulje", "collapse": "Supista", "collapse_all": "Sulje kaikki", "color": "Väri", + "color_theme": "Väriteema", "command": "Komento", "command_palette_prompt": "LÃļydä nopeasti sivuja, toimintoja tai komentoja", "command_palette_to_close": "sulkea", @@ -716,14 +819,18 @@ "contain": "Mahduta", "context": "Konteksti", "continue": "Jatka", - "control_bottom_app_bar_add_tags": "Lisää asiasanat", + "control_bottom_app_bar_create_new_album": "Luo uusi albumi", + "control_bottom_app_bar_delete_from_immich": "Poista Immichistä", "control_bottom_app_bar_delete_from_local": "Poista laitteelta", "control_bottom_app_bar_edit_location": "Muokkaa sijaintia", "control_bottom_app_bar_edit_time": "Muokkaa aikaa", + "control_bottom_app_bar_share_link": "Jaa linkki", + "control_bottom_app_bar_share_to": "Jaa", "control_bottom_app_bar_trash_from_immich": "Siirrä roskakoriin", "copied_image_to_clipboard": "Kuva kopioitu leikepÃļydälle.", "copied_to_clipboard": "Kopioitu leikepÃļydälle!", "copy_error": "Kopiointivirhe", + "copy_file_path": "Kopioi tiedostopolku", "copy_image": "Kopioi kuva", "copy_link": "Kopioi linkki", "copy_link_to_clipboard": "Kopioi linkki leikepÃļydälle", @@ -742,6 +849,7 @@ "create_link_to_share": "Luo linkki jaettavaksi", "create_link_to_share_description": "Salli kaikkien linkin saaneiden nähdä valitut kuvat", "create_new": "LUO UUSI", + "create_new_face": "Luo uudet kasvot", "create_new_person": "Luo uusi henkilÃļ", "create_new_person_hint": "Määritä valitut mediat uudelle henkilÃļlle", "create_new_user": "Luo uusi käyttäjä", @@ -758,30 +866,37 @@ "created_at": "Luotu", "creating_linked_albums": "Luodaan linkattuja albumeita...", "crop": "Rajaa", + "crop_aspect_ratio_fixed": "Kiinteä", "crop_aspect_ratio_free": "Vapaa", "crop_aspect_ratio_original": "Alkuperäinen", "crop_aspect_ratio_square": "NeliÃļ", + "curated_object_page_title": "Asiat", "current_device": "Nykyinen laite", "current_pin_code": "Nykyinen PIN-koodi", "current_server_address": "Nykyinen palvelinosoite", "custom_date": "Mukautettu päivä", "custom_locale": "Mukautettu alueasetus", "custom_locale_description": "Muotoile päivämäärät, kellonajat ja numerot valitun kielen ja alueen mukaan", + "custom_url": "Mukautettu URL", "cutoff_date_description": "Säilytä kuvat viimeisimmältäâ€Ļ", "cutoff_day": "{count, plural, one {päivä} other {päivää}}", "cutoff_year": "{count, plural, one {vuosi} other {vuotta}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Tumma", "dark_theme": "Vaihda tummaan teemaan", "date": "Päivämäärä", "date_after": "Päivämäärän jälkeen", "date_and_time": "Päivämäärä ja aika", "date_before": "Päivä ennen", - "date_of_birth": "Syntymäaika", + "date_format": "E d. LLL y â€ĸ hh:mm", "date_of_birth_saved": "Syntymäaika tallennettu", "date_range": "Päivämäärän rajaus", "day": "Päivä", "days": "Päivää", "deduplicate_all": "Poista kaikkien kaksoiskappaleet", + "default_locale": "Oletuskieli", + "default_locale_description": "Muotoile päivämäärät ja luvut selaimesi kieliasetusten mukaan", "delete": "Poista", "delete_action_confirmation_message": "Haluatko varmasti poistaa tämän aineiston? Tämä toiminto siirtää aineiston palvelimen roskakoriin ja kysyy, haluatko poistaa sen myÃļs paikallisesti", "delete_action_prompt": "{count} poistettu", @@ -790,6 +905,8 @@ "delete_dialog_alert": "Nämä kohteet poistetaan pysyvästi Immich:stä ja laitteeltasi", "delete_dialog_alert_local": "Kohteet poistetaan pysyvästi laitteelta, mutta ovat saatavilla Immich-palvelimella", "delete_dialog_alert_local_non_backed_up": "Joitain kohteista ei ole varmuuskopioitu Immichiin ja ne poistetaan laitteelta pysyvästi", + "delete_dialog_alert_remote": "Kohteet poistetaan pysyvästi Immich-palvelimelta", + "delete_dialog_ok_force": "Poista kuitenkin", "delete_dialog_title": "Poista pysyvästi", "delete_duplicates_confirmation": "Haluatko varmasti poistaa nämä kaksoiskappaleet pysyvästi?", "delete_face": "Poista kasvot", @@ -808,27 +925,34 @@ "delete_tag_confirmation_prompt": "Haluatko varmasti poistaa tunnisteen {tagName}?", "delete_user": "Poista käyttäjä", "deleted_shared_link": "Jaettu linkki poistettu", + "deletes_missing_assets": "Poistaa levyltä puuttuvat kohteet", "description": "Kuvaus", + "description_input_hint_text": "Lisää kuvaus...", + "description_input_submit_error": "Virhe kuvauksen päivittämisessä, tarkista lisätiedot lokista", "deselect_all": "Poista valinnat", "details": "Tiedot", "direction": "Suunta", "disable": "Poista käytÃļstä", "disabled": "Poistettu käytÃļstä", + "disallow_edits": "Älä salli muokkauksia", "discord": "Discord", "discover": "Tutki", "discovered_devices": "LÃļydetyt laitteet", "dismiss_all_errors": "Sivuuta kaikki virheet", + "dismiss_error": "Sivuuta virhe", "display_options": "NäyttÃļasetukset", "display_order": "NäyttÃļjärjestys", "display_original_photos": "Näytä alkuperäiset kuvat", "display_original_photos_setting_description": "Näytä mieluiten alkuperäinen kuva esikatselukuvan sijasta, kun alkuperäinen kuva on web-yhteensopiva. Tämä voi aiheuttaa kuvien näyttämisen hitautta.", - "do_not_show_again": "Älä näytä tätä uudelleen", + "do_not_show_again": "Älä näytä tätä enää", "documentation": "Dokumentaatio", "done": "Valmis", "download": "Lataa", + "download_action_prompt": "Ladataan {count} kohdetta", "download_canceled": "Lataus peruutettu", "download_complete": "Lataus valmis", "download_enqueue": "Latausjonossa", + "download_error": "Latausvirhe", "download_failed": "Lataus epäonnistui", "download_finished": "Lataus valmis", "download_include_embedded_motion_videos": "Upotetut videot", @@ -838,6 +962,9 @@ "download_paused": "Lataus keskeytetty", "download_settings": "Lataukset", "download_settings_description": "Hallitse aineiston lataukseen liittyviä asetuksia", + "download_started": "Lataus aloitettu", + "download_sucess": "Lataus onnistui", + "download_sucess_android": "Media on ladattu DCIM/Immichiin", "download_waiting_to_retry": "Odotetaan uudelleenyritystä", "downloading": "Ladataan", "downloading_asset_filename": "Ladataan mediaa {filename}", @@ -855,7 +982,9 @@ "edit_date_and_time": "Muokkaa päivämäärää ja kellonaikaa", "edit_date_and_time_action_prompt": "{count} päivämäärää ja aikaa muutettu", "edit_date_and_time_by_offset": "Muuta päivämäärää siirtymällä", + "edit_date_and_time_by_offset_interval": "Uusi päivämääräväli: {from} - {to}", "edit_description": "Muokkaa kuvausta", + "edit_description_prompt": "Valitse uusi kuvaus:", "edit_exclusion_pattern": "Muokkaa poissulkemismallia", "edit_faces": "Muokkaa kasvoja", "edit_key": "Muokkaa avainta", @@ -870,6 +999,9 @@ "edit_user": "Muokkaa käyttäjää", "edit_workflow": "Muokkaa tyÃļnkulkua", "editor": "Muokkaaja", + "editor_close_without_save_prompt": "Muutoksia ei tallenneta", + "editor_close_without_save_title": "Suljetaanko editori?", + "editor_confirm_reset_all_changes": "Haluatko varmasti nollata kaikki muutokset?", "editor_discard_edits_confirm": "Hylkää muutokset", "editor_discard_edits_prompt": "Sinulla on tallentamattomia muutoksia. Haluatko varmasti hylätä ne?", "editor_discard_edits_title": "Hylkää muutokset?", @@ -898,7 +1030,9 @@ "enter_your_pin_code": "SyÃļtä PIN-koodi", "enter_your_pin_code_subtitle": "SyÃļtä PIN-koodi päästäksesi lukittuun kansioon", "error": "Virhe", + "error_change_sort_album": "Albumin lajittelujärjestyksen muuttaminen epäonnistui", "error_delete_face": "Virhe kasvojen poistamisessa kohteesta", + "error_getting_places": "Ongelma paikkojen haussa", "error_loading_albums": "Virhe albumeita ladatessa", "error_loading_image": "Kuvan lataus ei onnistunut", "error_loading_partners": "Ongelma partnerin haussa: {error}", @@ -1036,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Lisää kuvausâ€Ļ", "exif_bottom_sheet_description_error": "Kuvauksen muuttaminen epäonnistui", + "exif_bottom_sheet_details": "TIEDOT", + "exif_bottom_sheet_location": "SIJAINTI", "exif_bottom_sheet_no_description": "Ei kuvausta", + "exif_bottom_sheet_people": "IHMISET", + "exif_bottom_sheet_person_add_person": "Lisää nimi", "exit_slideshow": "Poistu diaesityksestä", "expand": "Laajenna", "expand_all": "Laajenna kaikki", + "experimental_settings_new_asset_list_subtitle": "TyÃļn alla", + "experimental_settings_new_asset_list_title": "Ota käyttÃļÃļn kokeellinen kuvaruudukko", + "experimental_settings_subtitle": "KäyttÃļ omalla vastuulla!", + "experimental_settings_title": "Kokeellinen", "expire_after": "Umpeutuu", "expired": "Voimassaolo päättynyt", "expires_date": "Vanhenee {date}", @@ -1064,19 +1206,24 @@ "favorite_action_prompt": "{count} lisätty suosikkeihin", "favorite_or_unfavorite_photo": "Lisää tai poista kuva suosikeista", "favorites": "Suosikit", + "favorites_page_no_favorites": "Suosikkikohteita ei lÃļytynyt", "feature_photo_updated": "Kansikuva ladattu", "features": "Ominaisuudet", + "features_in_development": "Kehityksessä olevat ominaisuudet", "features_setting_description": "Hallitse sovelluksen ominaisuuksia", "file_name_or_extension": "Tiedostonimi tai tiedostopääte", "file_name_text": "Tiedoston nimi", + "file_name_with_value": "Tiedoston nimi: {file_name}", "file_size": "Tiedostokoko", "filename": "Tiedostonimi", "filetype": "Tiedostotyyppi", "filter": "Suodatin", + "filter_description": "Tavoitekohteiden suodatusehdot", "filter_people": "Suodata henkilÃļt", "filter_places": "Suodata paikkoja", "filter_tags": "Suodatintunnisteet", "filters": "Suodattimet", + "find_them_fast": "LÃļydä nopeasti hakemalla nimellä", "first": "Ensimmäinen", "fix_incorrect_match": "Korjaa virheellinen osuma", "folder": "Kansio", @@ -1109,6 +1256,7 @@ "group_owner": "Ryhmitä omistajan mukaan", "group_places_by": "Ryhmitä paikat...", "group_year": "Ryhmitä vuoden mukaan", + "haptic_feedback_switch": "Ota haptinen palaute käyttÃļÃļn", "haptic_feedback_title": "Haptinen palaute", "has_quota": "On kiintiÃļ", "hash_asset": "Tiivistetty kohde", @@ -1129,12 +1277,29 @@ "hide_schema": "Piilota skeema", "hide_text_recognition": "Piilota tekstin tunnistus", "hide_unnamed_people": "Piilota nimeämättÃļmät henkilÃļt", + "home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.", + "home_page_add_to_album_err_local": "Paikallisten kohteiden lisääminen albumeihin ei ole mahdollista, ohitetaan", + "home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.", + "home_page_album_err_partner": "Kumppanin kohteita ei voi vielä lisätä albumiin, ohitetaan", + "home_page_archive_err_local": "Paikallisten kohteiden arkistointi ei ole mahdollista, ohitetaan", + "home_page_archive_err_partner": "Kumppanin kohteita ei voi arkistoida, ohitetaan", "home_page_building_timeline": "Rakennetaan aikajanaa", + "home_page_delete_err_partner": "Kumppanin kohteita ei voi poistaa, ohitetaan", + "home_page_delete_remote_err_local": "Paikallisia kohteita etäkohdevalintojen joukossa, ohitetaan", + "home_page_favorite_err_local": "Paikallisten kohteiden lisääminen suosikkeihin ei ole mahdollista, ohitetaan", + "home_page_favorite_err_partner": "Kumppanin kohteita ei voi vielä merkitä suosikiksi, ohitetaan", + "home_page_first_time_notice": "Jos käytät sovellusta ensimmäistä kertaa, muista valita varmuuskopioitavat albumi(t), jotta aikajanalla voi olla kuvia ja videoita", + "home_page_locked_error_local": "Paikallisten kohteiden siirto lukittuun kansioon ei onnistu, ohitetaan", + "home_page_locked_error_partner": "Kumppanin kohteita ei voi siirtää lukittuun kansioon, ohitetaan", + "home_page_share_err_local": "Paikallisia kohteita ei voitu jakaa linkkien avulla, ohitetaan", + "home_page_upload_err_limit": "Voit lähettää palvelimelle enintään 30 kohdetta kerrallaan, ohitetaan", "host": "Isäntä", "hour": "Tunti", "hours": "Tunnit", "id": "ID", "idle": "Toimeton", + "ignore_icloud_photos": "Ohita iCloud-kuvat", + "ignore_icloud_photos_description": "iCloudiin tallennettuja kuvia ei ladata Immich-palvelimelle", "image": "Kuva", "image_alt_text_date": "{isVideo, select, true {Video} other {Kuva}} otettu {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Kuva}} otettu {person1} kanssa {date}", @@ -1146,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n ja {person2}n kanssa {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {person3}n kanssa {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Kuva}} otettu {city}ssä, {country}ssä {person1}n, {person2}n ja {additionalCount, number} muun kanssa {date}", + "image_saved_successfully": "Kuva tallennettu", + "image_viewer_page_state_provider_download_started": "Lataaminen aloitettu", + "image_viewer_page_state_provider_download_success": "Lataus onnistui", + "image_viewer_page_state_provider_share_error": "Jakovirhe", "immich_logo": "Immich-logo", "immich_web_interface": "Immich-verkkokäyttÃļliittymä", "import_from_json": "Tuo JSON-tiedostosta", @@ -1170,8 +1339,16 @@ "invalid_date_format": "Virheellinen päivämäärämuoto", "invite_people": "Kutsu ihmisiä", "invite_to_album": "Kutsu albumiin", + "ios_debug_info_fetch_ran_at": "Noudettu {dateTime}", + "ios_debug_info_last_sync_at": "Viimeisin synkronisointi {dateTime}", + "ios_debug_info_no_processes_queued": "Ei taustaprosesseja jonossa", + "ios_debug_info_no_sync_yet": "Taustasynkronisointia ei ole suoritettu vielä", + "ios_debug_info_processes_queued": "{count, plural, one {{count} taustaprosessi jonossa} other {{count} taustaprosessia jonossa}}", + "ios_debug_info_processing_ran_at": "Prosessi valmistui {dateTime}", "items_count": "{count, plural, one {# kpl} other {# kpl}}", "jobs": "Taustatehtävät", + "json_editor": "JSON-muokkain", + "json_error": "JSON-virhe", "keep": "Säilytä", "keep_albums": "Säilytä albumit", "keep_albums_count": "Säilytetään {count} {count, plural, one {albumi} other {albumia}}", @@ -1203,6 +1380,9 @@ "library": "Kirjasto", "library_add_folder": "Lisää kansio", "library_edit_folder": "Muokkaa kansiota", + "library_options": "Kirjastovaihtoehdot", + "library_page_device_albums": "Laitteen albumit", + "library_page_new_album": "Uusi albumi", "library_page_sort_asset_count": "Kohteiden lukumäärä", "library_page_sort_created": "Viimeisin luotu", "library_page_sort_last_modified": "Viimeksi muokattu", @@ -1212,7 +1392,6 @@ "light_theme": "Vaihda vaaleaan teemaan", "like": "Tykkää", "like_deleted": "Tykkäys poistettu", - "link": "Linkki", "link_motion_video": "Linkitä liikevideo", "link_to_docs": "Lisätietoja lÃļytyy dokumentaatiosta.", "link_to_oauth": "Linkki OAuth", @@ -1246,9 +1425,11 @@ "login": "Kirjaudu", "login_disabled": "Kirjautuminen on poistettu käytÃļstä", "login_form_api_exception": "API-virhe. Tarkista palvelimen URL-osoite ja yritä uudelleen.", + "login_form_back_button_text": "Takaisin", "login_form_email_hint": "sahkopostisi@esimerkki.fi", "login_form_endpoint_hint": "http://palvelimesi-osoite:portti", "login_form_endpoint_url": "Palvelimen URL", + "login_form_err_http": "Lisää http:// tai https://", "login_form_err_invalid_email": "Virheellinen sähkÃļpostiosoite", "login_form_err_invalid_url": "Virheellinen URL", "login_form_err_leading_whitespace": "Alussa välilyÃļnti", @@ -1256,8 +1437,9 @@ "login_form_failed_get_oauth_server_config": "Virhe kirjauduttaessa OAuth:lla, tarkista palvelimen URL", "login_form_failed_get_oauth_server_disable": "OAuth-ominaisuus ei ole käytÃļssä tällä palvelimella", "login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähkÃļpostiosoite ja salasana", - "login_form_handshake_exception": "Kättely palvelimen kanssa epäonnistui. Ota käyttÃļÃļn itse allekirjoitettujen varmenteiden tuki asetuksista, jos käytät niitä.", + "login_form_handshake_exception": "Tapahtui poikkeus kättelyssä palvelimen kanssa. Kytke päälle self-signed -sertifikaattituki asetuksista, mikäli käytät self-signed -sertifikaatteja.", "login_form_password_hint": "salasana", + "login_form_save_login": "Pysy kirjautuneena", "login_form_server_empty": "SyÃļtä palvelimen URL-osoite.", "login_form_server_error": "Palvelimeen ei saatu yhteyttä.", "login_has_been_disabled": "Kirjautuminen on otettu pois käytÃļstä.", @@ -1275,6 +1457,7 @@ "maintenance_action_restore": "Palautetaan tietokanta", "maintenance_description": "Immich on asetettu ylläpitotilaan.", "maintenance_end": "Poistu ylläpitotilasta", + "maintenance_end_error": "Poistuminen ylläpitotilasta epäonnistui.", "maintenance_logged_in_as": "Kirjautuneena käyttäjänä {user}", "maintenance_restore_from_backup": "Palauta varmuuskopiosta", "maintenance_restore_library": "Palauta kirjastosta", @@ -1300,6 +1483,7 @@ "manage_media_access_settings": "Avaa asetukset", "manage_media_access_subtitle": "Anna Immich-sovellukselle lupa hallinoida ja siirtää mediatiedostoja.", "manage_media_access_title": "Lupa median hallinnointiin", + "manage_shared_links": "Hallitse jaettuja linkkejä", "manage_sharing_with_partners": "Hallitse jakamista kumppaneille", "manage_the_app_settings": "Hallitse sovelluksen asetuksia", "manage_your_account": "Hallitse tiliäsi", @@ -1307,10 +1491,13 @@ "manage_your_devices": "Hallitse sisäänkirjautuneita laitteitasi", "manage_your_oauth_connection": "Hallitse OAuth-yhteyttäsi", "map": "Kartta", + "map_assets_in_bounds": "{count, plural, =0 {Ei kuvia tällä alueella} one {# kuva} other {# kuvaa}}", "map_cannot_get_user_location": "Käyttäjän sijaintia ei voitu määrittää", + "map_location_dialog_yes": "Kyllä", "map_location_picker_page_use_location": "Käytä tätä sijaintia", "map_location_service_disabled_content": "Paikannuspalvelun pitää olla kytkettynä päälle, jotta nykyisen sijaintisi kohteita voidaan näyttää. Haluatko kytkeä sen päälle nyt?", "map_location_service_disabled_title": "Paikannuspalvelu pois päältä", + "map_marker_for_images": "Karttamarkerointi kuville, jotka on otettu kaupungissa {city}, maassa {country}", "map_marker_with_image": "Karttamarkerointi kuvalla", "map_no_location_permission_content": "Paikannuslupa tarvitaan, jotta nykyisen sijainnin kohteita voidaan näyttää. Haluatko sallia pääsyn sijaintiin?", "map_no_location_permission_title": "Paikannuslupa estetty", @@ -1320,36 +1507,21 @@ "map_settings_date_range_option_days": "Viimeiset {days} päivää", "map_settings_date_range_option_year": "Viimeisin vuosi", "map_settings_date_range_option_years": "Viimeiset {years} vuotta", + "map_settings_dialog_title": "Kartta-asetukset", "map_settings_include_show_archived": "Sisällytä arkistoidut", "map_settings_include_show_partners": "Sisällytä kumppanit", "map_settings_only_show_favorites": "Näytä vain suosikit", "map_settings_theme_settings": "Kartan teema", + "map_zoom_to_see_photos": "Tarkenna nähdäksesi kuvat", "mark_all_as_read": "Merkitse kaikki luetuiksi", + "mark_as_read": "Merkitse luetuksi", "marked_all_as_read": "Merkitty kaikki luetuiksi", "matches": "Osumia", "matching_assets": "Vastaava sisältÃļ", - "media_chrome": { - "captions": "Tekstitykset", - "disable_captions": "Poista tekstitykset käytÃļstä", - "enable_captions": "Ota tekstitykset käyttÃļÃļn", - "enter_fullscreen_mode": "Siirry koko ruudun tilaan", - "exit_fullscreen_mode": "Poistu koko ruudun tilasta", - "loop": "Silmukka", - "mute": "Hiljennä", - "network_error": "Verkkovirhe", - "network_error_description": "Lataus keskeytyi verkkovirheen vuoksi.", - "quality": "Laatu", - "second": "sekunti", - "seconds": "sekuntia", - "time_value_remaining": "{time} jäljellä", - "unmute": "Poista hiljennys", - "video_player": "videosoitin", - "volume": "äänenvoimakkuus" - }, "media_type": "Median tyyppi", "memories": "Muistoja", "memories_all_caught_up": "Kaikki ajan tasalla", - "memories_check_back_tomorrow": "Palaa huomenna nähdäksesi lisää muistoja", + "memories_check_back_tomorrow": "Palaa huomenna nähdäskesi lisää muistoja", "memories_setting_description": "Hallitse mitä näet muistoissasi", "memories_start_over": "Aloita alusta", "memories_swipe_to_close": "Pyyhkäise ylÃļs sulkeaksesi", @@ -1365,26 +1537,31 @@ "minimize": "Pienennä", "minute": "Minuutti", "minutes": "Minuutit", + "mirror_horizontal": "Vaakasuora", + "mirror_vertical": "Pystysuora", "missing": "Puuttuvat", "mobile_app": "Mobiilisovellus", "mobile_app_download_onboarding_note": "Lataa mobiilisovellus käyttämällä seuraavia vaihtoehtoja", "model": "Malli", - "modify_date": "Muokkauspäivä", "month": "Kuukauden mukaan", + "monthly_title_text_date_format": "MMMM y", "more": "Enemmän", - "motion": "Liike", "move": "Siirrä", + "move_down": "Siirrä alas", "move_off_locked_folder": "Siirrä pois lukitusta kansiosta", "move_to": "Siirrä kohteeseen", "move_to_device_trash": "Siirrä laitteen roskakoriin", "move_to_lock_folder_action_prompt": "{count} lisätty lukittuun kansioon", "move_to_locked_folder": "Siirrä lukittuun kansioon", "move_to_locked_folder_confirmation": "Nämä kuvat ja videot poistetaan kaikista albumeista, ja ne ovat nähtävissä vain lukitussa kansiossa", + "move_up": "Siirrä ylÃļs", + "moved_to_archive": "Siirretty {count, plural, one {# kohde} other {# kohdetta}} arkistoon", + "moved_to_library": "Siirretty {count, plural, one {# kohde} other {# kohdetta}} kirjastoon", "moved_to_trash": "Siirretty roskakoriin", + "multiselect_grid_edit_date_time_err_read_only": "Vain luku -tilassa olevien kohteiden päivämäärää ei voitu muokata, ohitetaan", + "multiselect_grid_edit_gps_err_read_only": "Vain luku -tilassa olevien kohteiden sijantitietoja ei voitu muokata, ohitetaan", "mute_memories": "Mykistä muistot", "my_albums": "Omat albumit", - "my_immich_description": "Kopioi nykyinen sivu Oma Immich-linkkinä", - "my_immich_title": "Oma Immich-linkki", "name": "Nimi", "name_or_nickname": "Nimi tai lempinimi", "name_required": "Nimi on pakollinen", @@ -1399,10 +1576,12 @@ "never": "ei koskaan", "new_album": "Uusi Albumi", "new_api_key": "Uusi API-avain", + "new_date_range": "Uusi aikaväli", "new_password": "Uusi salasana", "new_person": "Uusi henkilÃļ", "new_pin_code": "Uusi PIN-koodi", "new_pin_code_subtitle": "Tämä on ensimmäinen kerta, kun käytät lukittua kansiota. Luo PIN-koodi päästäksesi tähän sisältÃļÃļn turvallisesti", + "new_timeline": "Uusi aikajana", "new_update": "Uusi päivitys", "new_user_created": "Uusi käyttäjä lisätty", "new_version_available": "UUSI VERSIO SAATAVILLA", @@ -1410,6 +1589,7 @@ "next": "Seuraava", "next_memory": "Seuraava muisto", "no": "Ei", + "no_actions_added": "Toimintoja ei ole vielä lisätty", "no_albums_found": "Albumeja ei lÃļytynyt", "no_albums_message": "Luo albumi pitääksesi kuvat ja videot järjestyksessä", "no_albums_with_name_yet": "Näyttää siltä, ettei sinulla ole yhtään tämän nimistä albumia.", @@ -1420,13 +1600,16 @@ "no_cast_devices_found": "Cast-laitteita ei lÃļytynyt", "no_checksum_local": "Ei tarkistussummaa - paikallista sisältÃļä ei voida hakea", "no_checksum_remote": "Ei tarkistussummaa - etänä olevaa sisältÃļä ei voida hakea", + "no_configuration_needed": "Konfiguraatiota ei tarvita", "no_devices": "Ei valtuutettuja laitteita", "no_duplicates_found": "Kaksoiskappaleita ei lÃļytynyt.", "no_exif_info_available": "Exif-tietoja ei ole saatavilla", "no_explore_results_message": "Lataa lisää kuvia tutkiaksesi kokoelmaasi.", "no_favorites_message": "Lisää suosikkeja lÃļytääksesi nopeasti parhaat kuvasi ja videosi", + "no_filters_added": "Suodattimia ei ole vielä lisätty", "no_libraries_message": "Luo ulkoinen kirjasto nähdäksesi valokuvasi ja videot", "no_local_assets_found": "Paikallista sisältÃļä ei lÃļytynyt tällä tarkistussummalla", + "no_location_set": "Ei sijaintia asetettuna", "no_locked_photos_message": "Kuvat ja videot lukitussa kansiossa ovat piilotettuja, eivätkä ne näy selatessasi tai etsiessäsi kirjastoasi.", "no_name": "Ei nimeä", "no_notifications": "Ei ilmoituksia", @@ -1436,6 +1619,7 @@ "no_results": "Ei tuloksia", "no_results_description": "Kokeile synonyymiä tai yleisempää avainsanaa", "no_shared_albums_message": "Luo albumi, jotta voit jakaa kuvia ja videoita toisille", + "no_uploads_in_progress": "Ei käynnissä olevia latauksia", "none": "Ei mitään", "not_allowed": "Ei sallittu", "not_available": "N/A", @@ -1480,10 +1664,10 @@ "organize_into_albums": "Järjestä albumeihin", "organize_into_albums_description": "Siirrä olemassa olevat kuvat albumeihin käyttäen nykyisiä synkronointiasetuksia", "organize_your_library": "Järjestele kirjastosi", - "orientation": "Suunta", "original": "alkuperäinen", "other": "Muut", "other_devices": "Toiset laitteet", + "other_entities": "Muut entiteetit", "other_variables": "Muut muuttujat", "owned": "Omistettu", "owner": "Omistaja", @@ -1493,9 +1677,12 @@ "partner_can_access_assets": "Kaikki valokuvasi ja videosi, lukuun ottamatta arkistoituja ja poistettuja", "partner_can_access_location": "Sijainti, jossa kuvasi on otettu", "partner_list_user_photos": "Käyttäjän {user} kuvat", + "partner_list_view_all": "Näytä kaikki", "partner_page_empty_message": "Kuviasi ei ole vielä jaettu kenenkään kumppanin kanssa.", "partner_page_no_more_users": "Ei enempää käyttäjiä lisättäväksi", + "partner_page_partner_add_failed": "Kumppanin lisääminen epäonnistui", "partner_page_select_partner": "Valitse kumppani", + "partner_page_shared_to_title": "Jaettu henkilÃļille", "partner_page_stop_sharing_content": "{partner} ei voi enää käyttää kuviasi.", "partner_sharing": "Kumppanijako", "partners": "Kumppanit", @@ -1517,6 +1704,8 @@ "people": "Ihmiset", "people_edits_count": "Muokattu {count, plural, one {# henkilÃļ} other {# henkilÃļä}}", "people_feature_description": "Selataan valokuvia ja videoita, jotka on ryhmitelty henkilÃļiden mukaan", + "people_selected": "{count, plural, one {# henkilÃļ valittu} other {# henkilÃļä valittu}}", + "people_sidebar_description": "Näytä linkki HenkilÃļihin sivupalkissa", "permanent_deletion_warning": "Pysyvän poiston varoitus", "permanent_deletion_warning_setting_description": "Näytä varoitus, kun poistat kohteita pysyvästi", "permanently_delete": "Poista pysyvästi", @@ -1526,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {# media} other {# mediaa}} poistettu pysyvästi", "permission": "KäyttÃļoikeus", "permission_empty": "KäyttÃļoikeus ei saa olla tyhjä", + "permission_onboarding_back": "Takaisin", + "permission_onboarding_continue_anyway": "Jatka silti", + "permission_onboarding_get_started": "Aloittaminen", + "permission_onboarding_go_to_settings": "Siirry asetuksiin", + "permission_onboarding_permission_denied": "Kielletty käyttÃļoikeus. Käyttääksesi Immichiä, myÃļnnä oikeus kuviin ja videoihin asetuksista.", + "permission_onboarding_permission_granted": "KäyttÃļoikeus myÃļnnetty! Kaikki valmista.", + "permission_onboarding_permission_limited": "Rajoitettu käyttÃļoikeus. Salliaksesi Immichin varmuuskopioida ja hallita koko kuvakirjastoasi, myÃļnnä oikeus kuviin ja videoihin asetuksista.", + "permission_onboarding_request": "Immich vaatii käyttÃļoikeuden kuvien ja videoiden käyttämiseen.", "person": "HenkilÃļ", "person_age_months": "{months, plural, one {# kuukauden} other {# kuukauden}} ikäinen", "person_age_year_months": "1 vuosi ja {months, plural, one {# kuukauden} other {# kuukautta}} vanha", @@ -1533,10 +1730,13 @@ "person_birthdate": "Syntynyt {date}", "person_hidden": "{name}{hidden, select, true { (piilotettu)} other {}}", "person_recognized": "HenkilÃļ tunnistettu", + "person_selected": "HenkilÃļ valittu", "photo_shared_all_users": "Näyttää että olet jakanut kuvasi kaikkien käyttäjien kanssa, tai sinulla ei ole käyttäjää kenelle jakaa.", "photos": "Kuvat", "photos_and_videos": "Kuvat ja videot", + "photos_count": "{count, plural, one {{count, number} Kuva} other {{count, number} kuvaa}}", "photos_from_previous_years": "Kuvia edellisiltä vuosilta", + "photos_only": "Vain kuvat", "pick_a_location": "Valitse sijainti", "pick_custom_range": "Mukautettu väli", "pick_date_range": "Valitse päivämäärien väli", @@ -1555,7 +1755,6 @@ "play_original_video_setting_description": "Suosi alkuperäisten videoiden toistoa transkoodattujen videoiden sijaan. Jos alkuperäinen tiedosto ei ole yhteensopiva, se ei välttämättä toistu oikein.", "play_transcoded_video": "Toista transkoodattu video", "please_auth_to_access": "Ole hyvä ja kirjaudu sisään", - "plugin_method_filter_type": "Suodatin", "port": "Portti", "preferences_settings_subtitle": "Hallitse sovelluksen asetuksia", "preferences_settings_title": "Asetukset", @@ -1572,6 +1771,7 @@ "privacy": "Tietosuoja", "profile": "Profiili", "profile_drawer_app_logs": "Lokit", + "profile_drawer_client_server_up_to_date": "Asiakasohjelma ja palvelin ovat ajan tasalla", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Muokkaus on estetty. Paina käyttäjäkuvaketta pitkään palataksesi muokkaustilaan.", "profile_image_of_user": "Käyttäjän {user} profiilikuva", @@ -1594,6 +1794,7 @@ "purchase_individual_description_2": "Tukijan tila", "purchase_individual_title": "Yksittäinen", "purchase_input_suggestion": "Onko sinulla tuoteavain? SyÃļtä avain alle", + "purchase_license_subtitle": "Osta Immich tukeaksesi palvelun jatkuvaa kehittämistä", "purchase_lifetime_description": "Elinikäinen osto", "purchase_option_title": "OSTOVAIHTOEHDOT", "purchase_panel_info_1": "Immichin rakentaminen vie paljon aikaa ja vaivannäkÃļä, ja meillä on kokopäiväisiä insinÃļÃļrejä tyÃļskentelemässä sen parissa, jotta voimme tehdä siitä mahdollisimman hyvän. Missiomme on, että avoimen lähdekoodin ohjelmistosta ja eettisistä liiketoimintakäytännÃļistä tulee kestävä tulonlähde kehittäjille, sekä luoda yksityisyyttä kunnioittava ekosysteemi, jossa on todellisia vaihtoehtoja hyväksikäyttÃļÃļn perustuville pilvipalveluille.", @@ -1626,16 +1827,19 @@ "reassigned_assets_to_new_person": "Määritetty {count, plural, one {# media} other {# mediaa}} uudelle henkilÃļlle", "reassing_hint": "Määritä valitut mediat käyttäjälle", "recent": "Viimeisin", + "recent_albums": "Viimeisimmät albumit", "recent_searches": "Edelliset haut", "recently_added": "Viimeksi lisätty", "recently_added_page_title": "Viimeksi lisätyt", "recently_taken": "Viimeksi otettu", + "recently_taken_page_title": "Viimeksi Otettu", "refresh": "Päivitä", "refresh_encoded_videos": "Päivitä enkoodatut videot", "refresh_faces": "Päivitä kasvot", "refresh_metadata": "Päivitä metadata", "refresh_thumbnails": "Päivitä pikkukuvat", "refreshed": "Päivitetty", + "refreshes_every_file": "Lukee uudelleen kaikki olemassa olevat ja uudet tiedostot", "refreshing_encoded_video": "Päivitetään enkoodattu video", "refreshing_faces": "Päivitetään kasvoja", "refreshing_metadata": "Päivitetään metadata", @@ -1648,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Haluatko varmasti poistaa {count, plural, one {# median} other {# mediaa}} tästä jakolinkistä?", "remove_assets_title": "Poistetaanko?", "remove_custom_date_range": "Poista aikaväliltä", - "remove_filter": "Poista suodatin", + "remove_deleted_assets": "Poista Offline-tiedostot", "remove_from_album": "Poista albumista", "remove_from_album_action_prompt": "{count} poistettu albumista", "remove_from_favorites": "Poista suosikeista", @@ -1659,22 +1863,29 @@ "remove_memory": "Tyhjennä muisti", "remove_photo_from_memory": "Poista kuva muistista", "remove_tag": "Poista tunniste", + "remove_url": "Poista URL", "remove_user": "Poista käyttäjä", "removed_api_key": "API-avain {name} poistettu", "removed_from_archive": "Poistettu arkistosta", "removed_from_favorites": "Poistettu suosikeista", "removed_from_favorites_count": "{count, plural, other {Poistettu #}} suosikeista", "removed_memory": "Poistettu muistista", + "removed_photo_from_memory": "Kuva poistettu muistista", "removed_tagged_assets": "Poistettu tunniste {count, plural, one {# kohteesta} other {# kohteesta}}", "rename": "Nimeä uudelleen", + "repair": "Korjaa", + "repair_no_results_message": "Seuraamattomat ja puuttuvat tiedostot näkyvät täällä", + "replace_with_upload": "Korvaa tiedostolla", "repository": "Tietovarasto", "require_password": "Vaadi salasana", + "require_user_to_change_password_on_first_login": "Vaadi käyttäjää vaihtamaan salasana seuraavalla kirjautumiskerralla", "rescan": "Skannaa uudelleen", "reset": "Nollaa", "reset_password": "Nollaa salasana", "reset_people_visibility": "Nollaa henkilÃļiden näkyvyysasetukset", "reset_pin_code": "Nollaa PIN-koodi", "reset_pin_code_description": "Jos olet unohtanut PIN-koodisi, ole yhteydessä järjestelmänvalvojaan", + "reset_pin_code_success": "PIN-koodi nollattu onnistuneesti", "reset_pin_code_with_password": "Voit aina nollata PIN-koodisi salasanan avulla", "reset_sqlite": "Nollaa SQLite Tietokanta", "reset_sqlite_clear_app_data": "Tyhjennä tiedot", @@ -1688,10 +1899,12 @@ "resolved_all_duplicates": "Kaikki kaksoiskappaleet selvitetty", "restore": "Palauta", "restore_all": "Palauta kaikki", + "restore_trash_action_prompt": "{count} palautettu roskakorista", "restore_user": "Palauta käyttäjä", "restored_asset": "Palautettu media", "resume": "Jatka", "resume_paused_jobs": "Jatka {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "Yritä latausta uudelleen", "review_duplicates": "Tarkastele kaksoiskappaleita", "review_large_files": "Tarkista suuret tiedostot", "role": "Rooli", @@ -1699,6 +1912,7 @@ "role_viewer": "Katsoja", "running": "Käynnissä", "save": "Tallenna", + "save_to_gallery": "Tallenna galleriaan", "saved": "Tallennettu", "saved_api_key": "API-avain tallennettu", "saved_profile": "Profiili tallennettu", @@ -1709,10 +1923,9 @@ "scan": "Skannaa", "scan_all_libraries": "Skannaa kaikki kirjastot", "scan_library": "Skannaa", + "scan_settings": "Skannausasetukset", "scanning": "Skannataan", "scanning_for_album": "Etsitään albumia...", - "screencast_mode_description": "Näytä näppäimistÃļn ja hiiren syÃļttÃļtapahtumat näytÃļllä", - "screencast_mode_title": "Vaihda screencast-tila", "search": "Haku", "search_albums": "Etsi albumeita", "search_by_context": "Etsi kontekstin perusteella", @@ -1720,7 +1933,6 @@ "search_by_description_example": "Vaelluspäivä Sapassa", "search_by_filename": "Hae tiedostonimen tai -päätteen mukaan", "search_by_filename_example": "esim. IMG_1234.JPG tai PNG", - "search_by_full_path": "Etsi koko polulla tai hakemistolla", "search_by_ocr": "Etsi tekstintunnistuksella (OCR)", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Etsi linssin mallia...", @@ -1740,6 +1952,7 @@ "search_filter_location_title": "Valitse sijainti", "search_filter_media_type": "Mediatyyppi", "search_filter_media_type_title": "Valitse mediatyyppi", + "search_filter_ocr": "Hae tekstintunnistuksella (OCR)", "search_filter_people_title": "Valitse ihmiset", "search_filter_star_rating": "Tähtiluokitus", "search_filter_tags_title": "Valitse tunnisteita", @@ -1750,13 +1963,25 @@ "search_no_people_named": "Ei \"{name}\" nimisiä henkilÃļitä", "search_no_result": "Ei tuloksia, kokeile toista hakusanaa tai -yhdistelmää", "search_options": "Hakuvaihtoehdot", + "search_page_categories": "Kategoriat", + "search_page_motion_photos": "Liikekuvat", + "search_page_no_objects": "Objektitietoja ei ole saatavilla", + "search_page_no_places": "Paikkatietoja ei ole saatavilla", + "search_page_screenshots": "NäyttÃļkuvat", "search_page_search_photos_videos": "Hae kuvia tai videoita", + "search_page_selfies": "Selfiet", + "search_page_things": "Asiat", "search_page_view_all_button": "Näytä kaikki", + "search_page_your_activity": "Toimintasi", + "search_page_your_map": "Sinun karttasi", "search_people": "Etsi ihmisiä", "search_places": "Etsi paikkoja", "search_rating": "Hae luokituksen mukaan...", + "search_result_page_new_search_hint": "Uusi haku", "search_settings": "Etsi asetuksia", "search_state": "Etsi maakuntaa...", + "search_suggestion_list_smart_search_hint_1": "Älykäs haku on oletuksena käytÃļssä. Käytä metatietojen etsimiseen syntaksia ", + "search_suggestion_list_smart_search_hint_2": "m:hakusana", "search_tags": "Etsi tunnisteita...", "search_timezone": "Etsi aikavyÃļhyke...", "search_type": "Etsinnän tyyppi", @@ -1770,6 +1995,7 @@ "select_albums": "Valitse albumit", "select_all": "Valitse kaikki", "select_all_duplicates": "Valitse kaikki kaksoiskappaleet", + "select_all_in": "Valitse kaikki {group}", "select_avatar_color": "Valitse avatarin väri", "select_count": "{count, plural, one {Valitse #} other {Valitse #}}", "select_cutoff_date": "Valitse katkaisuajankohta", @@ -1777,12 +2003,14 @@ "select_featured_photo": "Valitse esittelykuva", "select_from_computer": "Valitse koneeltasi", "select_keep_all": "Valitse pidä kaikki", + "select_library_owner": "Valitse kirjaston omistaja", "select_new_face": "Valitse uudet kasvot", "select_people": "Valitse henkilÃļitä", "select_person": "Valitse henkilÃļ", "select_person_to_tag": "Valitse henkilÃļ, jonka haluat merkitä", "select_photos": "Valitse kuvat", "select_trash_all": "Valitse kaikki roskakoriin", + "select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui", "selected": "Valittu", "selected_count": "{count, plural, other {# valittu}}", "selected_gps_coordinates": "Valitut GPS-koordinaatit", @@ -1815,20 +2043,33 @@ "setting_image_viewer_original_title": "Lataa alkuperäinen kuva", "setting_image_viewer_preview_subtitle": "Ota käyttÃļÃļn ladataksesi keskitarkkuuksinen kuva. Poista käytÃļstä ladataksesi alkuperäinen kuva tai käyttääksesi vain esikatselukuvaa.", "setting_image_viewer_preview_title": "Lataa esikatselukuva", + "setting_image_viewer_title": "Kuvat", "setting_languages_apply": "Käytä", "setting_languages_subtitle": "Vaihda sovelluksen kieli", + "setting_notifications_notify_failures_grace_period": "Ilmoita taustalla tapahtuvista varmuuskopiointivirheistä: {duration}", + "setting_notifications_notify_hours": "{count} tuntia", + "setting_notifications_notify_immediately": "heti", "setting_notifications_notify_minutes": "{count} minuuttia", + "setting_notifications_notify_never": "ei koskaan", "setting_notifications_notify_seconds": "{count} sekuntia", + "setting_notifications_single_progress_subtitle": "Yksityiskohtainen tieto palvelimelle lähettämisen edistymisestä kohteittain", + "setting_notifications_single_progress_title": "Näytä taustavarmuuskopioinnin edistyminen", "setting_notifications_subtitle": "Ilmoitusasetusten määrittely", + "setting_notifications_total_progress_subtitle": "Lähetyksen yleinen edistyminen (kohteita lähetetty/yhteensä)", + "setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen", "setting_video_viewer_auto_play_subtitle": "Aloita videoiden toistaminen automaattisesti kun ne avataan", "setting_video_viewer_auto_play_title": "Toista videoita automaattisesti", "setting_video_viewer_looping_title": "Silmukkatoisto", "setting_video_viewer_original_video_subtitle": "Kun toistat videota palvelimelta, toista alkuperäinen, vaikka transkoodattu versio olisi saatavilla. Tämä voi johtaa puskurointiin. Paikalliset videot toistetaan aina alkuperäislaadulla.", "setting_video_viewer_original_video_title": "Pakota alkuperäinen video", "settings": "Asetukset", + "settings_require_restart": "Käynnistä Immich uudelleen ottaaksesi tämä asetus käyttÃļÃļn", "settings_saved": "Asetukset tallennettu", "setup_pin_code": "Määritä PIN-koodi", "share": "Jaa", + "share_action_prompt": "Jaettu {count} kohdetta", + "share_add_photos": "Lisää kuvia", + "share_assets_selected": "{count} valittu", "share_dialog_preparing": "Valmistellaan...", "share_link": "Jaa linkki", "shared": "Jaettu", @@ -1836,6 +2077,8 @@ "shared_album_activity_remove_content": "Haluatko poistaa tämän aktiviteetin?", "shared_album_activity_remove_title": "Poista aktiviteetti", "shared_album_section_people_action_error": "Virhe poistuttaessa/poistaessa kohdetta albumista", + "shared_album_section_people_action_leave": "Poista käyttäjä albumista", + "shared_album_section_people_action_remove_user": "Poista käyttäjä albumista", "shared_album_section_people_title": "HENKILÖT", "shared_by": "Jakanut", "shared_by_user": "Käyttäjän {user} jakama", @@ -1844,6 +2087,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Lähetetty", "shared_link_app_bar_title": "Jaetut linkit", "shared_link_clipboard_copied_massage": "Kopioitu leikepÃļydältä", + "shared_link_clipboard_text": "Linkki: {link}\nSalasana: {password}", "shared_link_create_error": "Jaetun linkin luomisessa tapahtui virhe", "shared_link_custom_url_description": "Avaa tämä jaettu linkki mukautetulla URL-osoitteella", "shared_link_edit_description_hint": "Lisää jaon kuvaus", @@ -1879,6 +2123,12 @@ "shared_with_partner": "Jaa kumppanin {partner} kanssa", "sharing": "Jakaminen", "sharing_enter_password": "Nähdäksesi sivun sinun tulee antaa salasana.", + "sharing_page_album": "Jaetut albumit", + "sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.", + "sharing_page_empty_list": "TYHJÄ LISTA", + "sharing_sidebar_description": "Näytä jakamislinkki sivupalkissa", + "sharing_silver_appbar_create_shared_album": "Luo jaettu albumi", + "sharing_silver_appbar_share_partner": "Jaa kumppanille", "shift_to_permanent_delete": "Paina ⇧ poistaaksesi media pysyvästi", "show_album_options": "Näytä albumin asetukset", "show_albums": "Näytä albumit", @@ -1890,7 +2140,6 @@ "show_in_timeline": "Näytä aikajanalla", "show_in_timeline_setting_description": "Näytä tämän käyttäjän kuvat ja videot aikajanallasi", "show_keyboard_shortcuts": "Näytä näppäinoikotiet", - "show_less": "Näytä vähemmän", "show_metadata": "Näytä metadata", "show_or_hide_info": "Näytä tai piilota tiedot", "show_password": "Näytä salasana", @@ -1914,7 +2163,6 @@ "skip_to_folders": "Siirry kansioihin", "skip_to_tags": "Siirry tunnisteisiin", "slideshow": "Diaesitys", - "slideshow_metadata_overlay_mode_description_only": "Vain kuvaus", "slideshow_repeat": "Kertaa diaesitys", "slideshow_repeat_description": "Palaa takaisin alkuun diaesityksen päättyessä", "slideshow_settings": "Diaesityksen asetukset", @@ -1922,6 +2170,7 @@ "sort_created": "Luontipäivä", "sort_items": "Tietueiden määrä", "sort_modified": "Muokkauspäivä", + "sort_newest": "Uusin kuva", "sort_oldest": "Vanhin kuva", "sort_people_by_similarity": "Lajittele ihmiset samankaltaisuuden mukaan", "sort_recent": "Tuorein kuva", @@ -1930,6 +2179,7 @@ "stack": "Pinoa", "stack_action_prompt": "{count} pinottu", "stack_duplicates": "Pinoa kaksoiskappaleet", + "stack_select_one_photo": "Valitse yksi pääkuva pinolle", "stack_selected_photos": "Pinoa valitut kuvat", "stacked_assets_count": "Pinottu {count, plural, one {# media} other {# mediaa}}", "stacktrace": "Vianetsintätiedot", @@ -1958,18 +2208,18 @@ "swap_merge_direction": "Käännä yhdistämissuunta", "sync": "Synkronoi", "sync_albums": "Synkronoi albumit", + "sync_albums_manual_subtitle": "Synkronoi kaikki ladatut videot ja valokuvat valittuihin varmuuskopioalbumeihin", "sync_local": "Synkronoi paikallinen", "sync_remote": "Synkronoi etä", "sync_status": "Synkronoinnin status", "sync_status_subtitle": "Näytä ja hallinnoi synkronointijärjestelmää", "sync_upload_album_setting_subtitle": "Luo ja lataa valokuvasi ja videosi valittuihin albumeihin Immichissä", - "system_theme": "Järjestelmän teema", - "system_theme_command_description": "Käytä järjestelmän teemaa ({value})", "tag": "Tunniste", "tag_assets": "Lisää tunnisteita", "tag_created": "Luotu tunniste: {tag}", "tag_face": "Merkitse kasvot", "tag_feature_description": "Selaa valokuvia ja videoita, jotka on ryhmitelty loogisten tunnisteotsikoiden mukaan", + "tag_not_found_question": "EtkÃļ lÃļydä tunnistetta? Luo uusi tunniste.", "tag_people": "Merkitse henkilÃļ tunnisteella", "tag_updated": "Päivitetty tunniste: {tag}", "tagged_assets": "Tunnistettu {count, plural, one {# kohde} other {# kohdetta}}", @@ -1984,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Kohteiden määrä rivillä ({count})", "theme_setting_colorful_interface_subtitle": "Levitä pääväri taustalle.", "theme_setting_colorful_interface_title": "Värikäs käyttÃļliittymä", + "theme_setting_image_viewer_quality_subtitle": "Säädä kuvien katselun laatua", + "theme_setting_image_viewer_quality_title": "Kuvien katseluohjelman laatu", "theme_setting_primary_color_subtitle": "Valitse sovelluksen pääväri.", "theme_setting_primary_color_title": "Väriteema", "theme_setting_system_primary_color_title": "Käytä järjestelmän väriteemaa", "theme_setting_system_theme_switch": "Automaattinen (seuraa järjestelmän asetusta)", + "theme_setting_theme_subtitle": "Valitse sovelluksen teema-asetukset", + "theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataus saattaa parantaa lataustehoa, mutta aiheuttaa huomattavasti suuremman verkon kuormituksen", + "theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttÃļÃļn", "then": "Sitten", "they_will_be_merged_together": "Nämä tullaan yhdistämään", "third_party_resources": "Kolmannen osapuolen resurssit", @@ -2013,15 +2268,23 @@ "trash_all": "Vie kaikki roskakoriin", "trash_count": "Vie {count, number} roskakoriin", "trash_delete_asset": "Poista / vie roskakoriin", + "trash_emptied": "Roskakori tyhjennetty", "trash_no_results_message": "Roskakorissa olevat kuvat ja videot näytetään täällä.", "trash_page_delete_all": "Poista kaikki", + "trash_page_empty_trash_dialog_content": "Haluatko tyhjentää roskakorin? Kohteet poistetaan lopullisesti Immich:sta", "trash_page_info": "Roskakorissa olevat kohteet poistetaan pysyvästi {days} päivän kuluttua", + "trash_page_no_assets": "Ei poistettuja kohteita", + "trash_page_restore_all": "Palauta kaikki", + "trash_page_select_assets_btn": "Valitse kohteet", + "trash_page_title": "Roskakori ({count})", "trashed_items_will_be_permanently_deleted_after": "Roskakorin kohteet poistetaan pysyvästi {days, plural, one {# päivän} other {# päivän}} päästä.", "trigger": "Laukaisin", "trigger_asset_uploaded": "Kohde ladattu", "trigger_asset_uploaded_description": "Käynnistyy, kun uusi kohde ladataan", + "trigger_description": "TyÃļnkulun aloittava tapahtuma", "trigger_person_recognized": "HenkilÃļ tunnistettu", "trigger_person_recognized_description": "Laukaistaan kun henkilÃļ tunnistetaan", + "trigger_type": "Laukaisimen tyyppi", "troubleshoot": "Vianetsintä", "type": "Tyyppi", "unable_to_change_pin_code": "PIN-koodin vaihtaminen epäonnistui", @@ -2049,12 +2312,14 @@ "unsaved_change": "Tallentamaton muutos", "unselect_all": "Poista valinnat", "unselect_all_duplicates": "Poista kaikkien kaksoiskappaleiden valinta", + "unselect_all_in": "Poista kaikki valinnat {group}", "unstack": "Pura pino", "unstack_action_prompt": "{count} purettu pinosta", "unstacked_assets_count": "Poistettu pinosta {count, plural, one {# kohde} other {# kohdetta}}", "unsupported_field_type": "Ei-tuettu kentän tyyppi", "unsupported_file_type": "Tiedostoa {file} ei voi ladata, koska sen tiedostotyyppi {type} ei ole tuettu.", "untagged": "Ilman tunnistetta", + "untitled_workflow": "NimetÃļn tyÃļnkulku", "up_next": "Seuraavaksi", "update_location_action_prompt": "Päivitä {count} kohteen sijaintia:", "updated_at": "Päivitetty", @@ -2062,6 +2327,8 @@ "upload": "Siirrä palvelimelle", "upload_concurrency": "Latausten samanaikaisuus", "upload_details": "Lähetyksen tiedot", + "upload_dialog_info": "Haluatko varmuuskopioida valitut kohteet palvelimelle?", + "upload_dialog_title": "Lähetä kohde", "upload_error_with_count": "Latausvirhe {count} kohteelle", "upload_errors": "Lataus valmistui {count, plural, one {# virheen} other {# virheen}} kanssa. Päivitä sivu nähdäksesi ladatut tiedot.", "upload_finished": "Lähetys valmistui", @@ -2090,6 +2357,7 @@ "user_privacy": "Käyttäjän tietosuoja", "user_purchase_settings": "Osta", "user_purchase_settings_description": "Hallitse ostostasi", + "user_role_set": "Tee käyttäjästä {user} {role}", "user_usage_detail": "Käyttäjän käytÃļn tiedot", "user_usage_stats": "Tilin käyttÃļtilastot", "user_usage_stats_description": "Näytä tilin käyttÃļtilastot", @@ -2099,6 +2367,7 @@ "utilities": "Apuohjelmat", "validate": "Validoi", "validate_endpoint_error": "Anna kelvollinen URL-osoite", + "validation_error": "Validointivirhe", "variables": "Muuttujat", "version": "Versio", "version_announcement_closing": "Ystäväsi Alex", @@ -2110,6 +2379,7 @@ "video_hover_setting_description": "Toista videon esikatselukuva kun kursori on kuvan päällä. Vaikka toiminto on pois käytÃļstä, toiston voi aloittaa viemällä kursori toistokuvakkeen päälle.", "videos": "Videot", "videos_count": "{count, plural, one {# video} other {# videota}}", + "videos_only": "Vain videot", "view": "Katso", "view_album": "Näytä albumi", "view_all": "Näytä kaikki", @@ -2118,16 +2388,21 @@ "view_details": "Näytä tiedot", "view_in_timeline": "Näytä aikajanalla", "view_link": "Näytä linkki", + "view_links": "Näytä linkit", "view_name": "Näkymä", "view_next_asset": "Näytä seuraava", "view_previous_asset": "Näytä edellinen", "view_qr_code": "Näytä QR-koodi", "view_similar_photos": "Näytä samankaltaiset kuvat", "view_stack": "Näytä pinona", + "view_user": "Näytä käyttäjä", "viewer_remove_from_stack": "Poista pinosta", + "viewer_stack_use_as_main_asset": "Käytä pääkohteena", + "viewer_unstack": "Pura pino", "visibility": "Näkyvyys", "visibility_changed": "{count, plural, one {# henkilÃļn} other {# henkilÃļiden}} näkyvyys vaihdettu", "visual": "Visuaalinen", + "visual_builder": "Visuaalinen koostaja", "waiting": "Odottaa", "waiting_count": "Odottaa: {count}", "warning": "Varoitus", @@ -2136,7 +2411,6 @@ "welcome_to_immich": "Tervetuloa Immichiin", "width": "Leveys", "wifi_name": "Wi-Fi-verkon nimi", - "workflow": "TyÃļnkulku", "workflow_delete_prompt": "Haluatko varmasti poistaa tämän tyÃļnkulun?", "workflow_deleted": "TyÃļnkulku poistettu", "workflow_description": "TyÃļnkulun kuvaus", @@ -2147,15 +2421,16 @@ "workflow_navigation_prompt": "Haluatko varmasti poistua tallentamatta muutoksia?", "workflow_summary": "TyÃļnkulun yhteenveto", "workflow_update_success": "TyÃļnkulku päivitetty onnistuneesti", + "workflow_updated": "TyÃļnkulku päivitetty", "workflows": "TyÃļnkulut", "workflows_help_text": "TyÃļnkulut automatisoivat toimintoja kohteillesi laukaisimien ja suodattimien perusteella", "wrong_pin_code": "Väärä PIN-koodi", - "x_of_total": "{x}/{total}", "year": "Vuosi", "years_ago": "{years, plural, one {# vuosi} other {# vuotta}} sitten", "yes": "Kyllä", "you_dont_have_any_shared_links": "Sinulla ei ole jaettuja linkkejä", "your_wifi_name": "Wi-Fi-verkkosi nimi", "zero_to_clear_rating": "paina 0 poistaaksesi kohteen luokituksen", - "zoom_image": "Zoomaa kuvaa" + "zoom_image": "Zoomaa kuvaa", + "zoom_to_bounds": "Zoomaa reunoihin" } diff --git a/i18n/fil.json b/i18n/fil.json index f419bc8745..c3340f2c8f 100644 --- a/i18n/fil.json +++ b/i18n/fil.json @@ -7,39 +7,34 @@ "action_common_update": "I-update", "actions": "Mga Aksyon", "active": "Tumatakbo", - "active_count": "Aktibo: {count}", "activity": "Mga Aktibidad", + "activity_changed": "Ang aktibidad ay {enabled, select, true {naka-enable} other {hindi naka-enable}}", "add": "Mag dagdag", "add_a_description": "Dagdagan ng deskripsyon", "add_a_location": "Dagdagan ng lugar", "add_a_name": "Dagdagan ng pangalan", "add_a_title": "Dagdagan ng pamagat", - "add_action": "Magdagdag ng aksyon", - "add_assets": "Dagdagan ng asset", "add_birthday": "Maglagay ng kaarawan", "add_endpoint": "Dagdagan ng dulo", - "add_exclusion_pattern": "Magdagdag ng exlusion pattern", "add_location": "Magdagdag ng lugar", + "add_more_users": "Magdagdag ng mga user", "add_partner": "Magdagdag ng kasangga", + "add_path": "Magdagdag ng path", "add_photos": "Magdagdag ng litrato", - "add_step": "Magdagdag ng step", "add_tag": "Magdagdag ng tag", "add_to": "Idagdag saâ€Ļ", "add_to_album": "Idagdag sa album", "add_to_album_bottom_sheet_added": "Naidagdag sa {album}", "add_to_album_bottom_sheet_already_exists": "Nasa {album} na", - "add_to_album_bottom_sheet_some_local_assets": "May ilang mga local assets ang hindi maidagdag sa album", "add_to_albums": "Idagdag sa mga album", "add_to_albums_count": "Idagdag sa mga album ({count})", - "add_to_bottom_bar": "Idagdag sa", - "add_upload_to_stack": "Magdagdag ng upload para ma-stack", + "add_to_shared_album": "Idagdag sa shared album", "add_url": "Magdagdag ng URL", "added_to_archive": "Naidagdag sa archive", "added_to_favorites": "Naidagdag sa mga paborito", "added_to_favorites_count": "Naidagdag ang {count, number} sa mga paborito", "admin": { "add_exclusion_pattern_description": "Dagdagan ng pattern para maibukod. Supportado ang pag-tutugma gamit ang *, **, at ?. Para hindi maisama ang mga file sa direktoryo na may pangalang \"Raw\", gamitin ang \"**/Raw/**\". Para hindi maisama ang lahat ng mga file na nagtatapos sa \".tif\", gamitin ang \"**/*.tif\". Para hindi maisama ang isang tiyak na folder, gamitin ang \"/path/to/ignore/**\".", - "admin_user": "Admin User", "asset_offline_description": "Ang external library asset na ito ay hindi na makikita sa disk at nailipat na sa basurahan. Kung ang file ay nailipat sa loob ng library, tignan ang iyong timeline para sa kaukulang asset. Para maibalik ang asset na ito, siguraduhin na ang file ay maa-access ng Immich at muling i-scan ang library.", "authentication_settings": "Setting ng mga Pagkakakilanlan", "authentication_settings_description": "Pamahalaan ang password, OAuth, and iba pang setting ng pagkakakilanlan", @@ -49,10 +44,6 @@ "backup_database": "Gumawa ng Dump ng Database", "backup_database_enable_description": "Paganahin ang Database Dumps", "backup_keep_last_amount": "Bilang ng mga itatagong nakaraang dump", - "backup_onboarding_1_description": "offsite na kopya sa cloud o sa ibang pisikal na lokasyon.", - "backup_onboarding_footer": "Para sa karagdagang impormasyon tungkol sa pag-backup ng Immich, maaaring tignan sa dokumentasyon.", - "backup_onboarding_parts_title": "Ang 3-2-1 backup ay kasama:", - "backup_onboarding_title": "Backups", "backup_settings": "Setting ng mga Database Dump", "backup_settings_description": "Pamahalaan ang mga setting ng database dump.", "cleared_jobs": "Tinanggal na ang mga trabaho para sa {job}", @@ -70,44 +61,33 @@ "disable_login": "I-disable ang login", "duplicate_detection_job_description": "Hanapin ang mga magkakatulad na imahe gamit ang machine learning. Umaasa sa Smart Search", "exclusion_pattern_description": "Maaaring gamitin ang mga pattern na pangbukod para hindi pansinin ang ilang file o folder habang binabasa ang iyong library. Mainam itong solusyon para sa mga folder na may file na ayaw niyong ma-import, tulad ng mga RAW na file.", - "face_detection": "Face detection", "force_delete_user_warning": "BABALA: Tatanggalin itong user at lahat ng asset nila, Hindi ito mababawi at ang kanilang files ay hindi na mababalik", "image_format": "Format", "note_cannot_be_changed_later": "TANDAAN: Hindi na ito pwede baguhin sa susunod!", "server_welcome_message_description": "Mensahe na ipapakita sa login page.", "user_restore_description": "Ang account ni {user} ay maibabalik." }, + "album_user_left": "Umalis sa {album}", "all_albums": "Lahat ng albums", "all_people": "Lahat ng tao", "all_videos": "Lahat ng video", "api_key_description": "Isang beses lamang na ipapakita itong value. Siguraduhin na ikopya itong value bago iclose ang window na ito.", "are_these_the_same_person": "Itong tao na ito ay parehas?", "asset_adding_to_album": "Dinadagdag sa album...", + "asset_filename_is_offline": "Offline ang asset {filename}", "asset_uploading": "Ina-upload...", - "change_your_password": "Palitan ang iyong password", - "country": "Bansa", "create_album_page_untitled": "Walang pamagat", "documentation": "Dokumentasyion", "done": "Tapos na", "download": "I-download", "edit": "I-edit", - "edit_album": "I-edit ang album", - "edit_avatar": "I-edit ang avatar", - "edit_birthday": "I-edit ang birthday", - "edit_date": "I-edit ang petsa", - "edit_date_and_time": "I-edit ang petsa at oras", + "editor_close_without_save_title": "Isara ang editor?", "explore": "I-explore", "export": "I-export", "has_quota": "May quota", "hour": "Oras", - "info": "Informasyon", "jobs": "Mga trabaho", "language": "Wika", "leave": "Umalis", - "no_results": "Walang resulta", - "shared_link_edit_expire_after_option_minutes": "{count} minuto", - "shared_link_edit_expire_after_option_months": "{count} buwan", - "shared_link_edit_expire_after_option_year": "{count} taon", - "shared_link_edit_password_hint": "I-enter ang share password", - "year": "Taon" + "no_results": "Walang resulta" } diff --git a/i18n/fr.json b/i18n/fr.json index 4d2be831cd..f15931b0d6 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -5,36 +5,44 @@ "acknowledge": "Compris", "action": "Action", "action_common_update": "Mettre à jour", - "action_description": "Un ensemble d'actions à exÊcuter sur les mÊdias filtrÊs", + "action_description": "Un ensemble d'actions applicables sur des mÊdias filtrÊs", "actions": "Actions", "active": "En cours", - "active_count": "En cours : {count}", + "active_count": "Actif : {count}", "activity": "ActivitÊ", + "activity_changed": "ActivitÊ {enabled, select, true {activÊe} other {dÊsactivÊe}}", "add": "Ajouter", "add_a_description": "Ajouter une description", "add_a_location": "Ajouter une localisation", - "add_a_name": "Ajouter un prÊnom", + "add_a_name": "Ajouter un nom", "add_a_title": "Ajouter un titre", "add_action": "Ajouter une action", + "add_action_description": "Cliquez pour ajouter une action à rÊaliser", "add_assets": "Ajouter des mÊdias", - "add_birthday": "Ajouter la date d'anniversaire", + "add_birthday": "Ajouter un anniversaire", "add_endpoint": "Ajouter une adresse", "add_exclusion_pattern": "Ajouter un schÊma d'exclusion", + "add_filter": "Ajouter un filtre", + "add_filter_description": "Cliquez pour ajouter une condition au filtre", "add_location": "Ajouter une localisation", + "add_more_users": "Ajouter plus d'utilisateurs", "add_partner": "Ajouter un partenaire", + "add_path": "Ajouter un chemin", "add_photos": "Ajouter des photos", - "add_step": "Ajouter une Êtape", "add_tag": "Ajouter une Êtiquette", "add_to": "Ajouter àâ€Ļ", "add_to_album": "Ajouter à l'album", "add_to_album_bottom_sheet_added": "AjoutÊ à {album}", "add_to_album_bottom_sheet_already_exists": "DÊjà dans {album}", "add_to_album_bottom_sheet_some_local_assets": "Certains mÊdias n'ont pas pu ÃĒtre ajoutÊs à l'album", + "add_to_album_toggle": "Basculer la sÊlection pour {album}", "add_to_albums": "Ajouter aux albums", "add_to_albums_count": "Ajouter aux albums ({count})", "add_to_bottom_bar": "Ajouter à", - "add_upload_to_stack": "Ajouter les ÊlÊments envoyÊs à la pile", + "add_to_shared_album": "Ajouter à l'album partagÊ", + "add_upload_to_stack": "Ajouter les ÊlÊments tÊlÊversÊs à la pile", "add_url": "Ajouter l'URL", + "add_workflow_step": "Ajouter une Êtape de flux de traitement", "added_to_archive": "AjoutÊ à l'archive", "added_to_favorites": "AjoutÊ aux favoris", "added_to_favorites_count": "{count, number} ajoutÊ(s) aux favoris", @@ -53,7 +61,7 @@ "backup_onboarding_1_description": "copie hors site dans le cloud ou sur un site distant.", "backup_onboarding_2_description": "copies locales sur diffÊrents appareils. Cela inclut les fichiers principaux ainsi qu'une sauvegarde locale de ces fichiers.", "backup_onboarding_3_description": "copies total de vos donnÊes, incluant les fichiers originaux. Cela inclut 1 copie hors site ainsi que 2 copies locales.", - "backup_onboarding_description": "Une stratÊgie de sauvegarde 3-2-1 est recommandÊe pour protÊger vos donnÊes. Vous devriez conserver des copies de vos photos/vidÊos envoyÊes ainsi que de la base de donnÊes d'Immich pour une solution de sauvegarde cohÊrente.", + "backup_onboarding_description": "Une stratÊgie de sauvegarde 3-2-1 est recommandÊe pour protÊger vos donnÊes. Vous devriez conserver des copies de vos photos/vidÊos tÊlÊversÊs ainsi que de la base de donnÊes d'Immich pour une solution de sauvegarde cohÊrente.", "backup_onboarding_footer": "Pour plus d'information sur la sauvegarde d'Immich, merci de vous rÊfÊrer à la documentation.", "backup_onboarding_parts_title": "Une sauvegarde 3-2-1 inclut :", "backup_onboarding_title": "Sauvegardes", @@ -73,7 +81,6 @@ "cron_expression_description": "DÊfinir l'intervalle d'analyse à l'aide d'une expression cron. Pour plus d'informations, voir Crontab Guru", "cron_expression_presets": "PrÊrÊglages d'expression cron", "disable_login": "DÊsactiver la connexion", - "download_csv": "TÊlÊcharger en CSV", "duplicate_detection_job_description": "Lancement de l'apprentissage automatique sur les mÊdias pour dÊtecter les images similaires. Se base sur la recherche intelligente", "exclusion_pattern_description": "Les schÊmas d'exclusion vous permettent d'ignorer des fichiers et des dossiers lors de l'analyse de votre bibliothèque. Cette fonction est utile si des dossiers contiennent des fichiers que vous ne souhaitez pas importer, tels que des fichiers RAW.", "export_config_as_json_description": "TÊlÊcharger la configuration actuelle du système en tant que fichier JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "QualitÊ des miniatures : de 1 à 100. Une valeur ÊlevÊe produit de meilleurs rÊsultats, mais elle produit des fichiers plus volumineux et peut rÊduire la rÊactivitÊ de l'application.", "image_thumbnail_title": "Paramètres des miniatures", "import_config_from_json_description": "Importer la configuration système en envoyant un fichier de configuration JSON", - "integrity_checks_checksum_files": "VÊrifier l'intÊgritÊ des fichiers", - "integrity_checks_checksum_files_description": "Configurer le test d'intÊgritÊ", - "integrity_checks_checksum_files_enable_description": "Active le test d'intÊgritÊ des fichiers (checksum)", - "integrity_checks_checksum_files_percentage_limit": "Limite de pourcentage", - "integrity_checks_checksum_files_percentage_limit_description": "Configurez le pourcentage maximal du test d'intÊgritÊ qui doit ÃĒtre effectuÊ à chaque interval, entre 0.01 et 1.", - "integrity_checks_checksum_files_time_limit": "Limite de temps", - "integrity_checks_checksum_files_time_limit_description": "Configurez la durÊe maximale en millisecondes entre deux vÊrifications.", - "integrity_checks_missing_files": "Fichiers manquants", - "integrity_checks_missing_files_description": "Configurez la frÊquence et activez/dÊsactivez la vÊrification des fichiers manquants", - "integrity_checks_missing_files_enable_description": "Activez la vÊrification des fichiers manquants", - "integrity_checks_settings": "Tests d'intÊgritÊ", - "integrity_checks_settings_description": "GÊrez l'interval des tests d'intÊgritÊ", - "integrity_checks_untracked_files": "Fichiers non suivis", - "integrity_checks_untracked_files_description": "Configurez la frÊquence et activez/dÊsactivez la vÊrification des fichiers non suivis", - "integrity_checks_untracked_files_enable_description": "Activez la vÊrification des fichiers non suivis", "job_concurrency": "{job} : nombre de tÃĸches simultanÊes", "job_created": "TÃĸche crÊÊe", "job_not_concurrency_safe": "Cette tÃĸche ne peut pas ÃĒtre exÊcutÊe en multitÃĸche de façon sÃģre.", @@ -186,7 +178,7 @@ "machine_learning_ocr_max_resolution": "RÊsolution maximale", "machine_learning_ocr_max_resolution_description": "Les prÊvisualisations au-dessus de cette rÊsolution seront retaillÊes en conservant leur ratio. Des valeurs plus grandes sont plus prÊcises, mais sont plus lentes et utilisent plus de mÊmoire.", "machine_learning_ocr_min_detection_score": "Score minimum de dÊtection", - "machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la dÊtection du texte, entre 0 et 1. Des valeurs faibles permettront de reconnaÃŽtre davantage de texte mais peuvent entraÃŽner des faux positifs.", + "machine_learning_ocr_min_detection_score_description": "Score de confiance minimum pour la dÊtection du textew entre 0 et 1. Des valeurs faibles permettront de reconnaÃŽtre davantage de texte mais peuvent entraÃŽner des faux positifs.", "machine_learning_ocr_min_recognition_score": "Score de reconnaissance minimum", "machine_learning_ocr_min_score_recognition_description": "Score de confiance minimum pour la reconnaissance du texte, entre 0 et 1. Des valeurs faible permettront de reconnaÃŽtre davantage de texte, mais peuvent entraÃŽner des faux positifs.", "machine_learning_ocr_model": "Modèle de Reconnaissance Optique de Caractères OCR", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Activer la recherche intelligente", "machine_learning_smart_search_enabled_description": "Si cette option est dÊsactivÊe, les images ne seront pas encodÊes pour la recherche intelligente.", "machine_learning_url_description": "L’URL du serveur d'apprentissage automatique. Si plusieurs URL sont fournies, chaque serveur sera essayÊ un par un jusqu’à ce que l’un d’eux rÊponde avec succès, dans l’ordre de la première à la dernière. Les serveurs ne rÊpondant pas seront temporairement ignorÊs jusqu'à ce qu'ils soient de nouveau opÊrationnels.", - "maintenance_backup_management": "Gestion des sauvegardes", "maintenance_delete_backup": "Supprimer la sauvegarde", "maintenance_delete_backup_description": "Ce fichier sera dÊfinitivement supprimÊ.", "maintenance_delete_error": "Échec de la suppression de la sauvegarde.", - "maintenance_integrity_check": "VÊrifier", - "maintenance_integrity_check_all": "Tout cocher", - "maintenance_integrity_checksum_mismatch": "Non correspondance de la somme de contrôle", - "maintenance_integrity_checksum_mismatch_description": "Fichiers dont la somme de contrôle sur le disque ne correspond pas à celle stockÊe dans la base de donnÊes d'Immich.", - "maintenance_integrity_checksum_mismatch_job": "VÊrifier la correspondance de la somme de contrôle", - "maintenance_integrity_checksum_mismatch_refresh_job": "RafraÃŽchir les rapports de correspondance de la somme de contrôle", - "maintenance_integrity_missing_file": "Fichiers manquants", - "maintenance_integrity_missing_file_description": "Fichiers suivis dans la base de donnÊes d'Immich mais inexistants sur le système de fichiers.", - "maintenance_integrity_missing_file_job": "DÊtecter les fichiers manquants", - "maintenance_integrity_missing_file_refresh_job": "RafraÃŽchir les rapports sur les fichiers manquants", - "maintenance_integrity_report": "Rapport d'intÊgritÊ", - "maintenance_integrity_untracked_file": "Fichiers non suivis", - "maintenance_integrity_untracked_file_description": "Fichiers prÊsents dans les rÊpertoires d'Immich mais absents de la base de donnÊes.", - "maintenance_integrity_untracked_file_job": "VÊrifier les fichiers non suivis", - "maintenance_integrity_untracked_file_refresh_job": "RafraÃŽchir les rapports de fichiers non suivis", "maintenance_restore_backup": "Restaurer la sauvegarde", "maintenance_restore_backup_description": "Immich sera effacÊ et restaurÊ à partir de la sauvegarde choisie. Une sauvegarde sera crÊÊe avant de continuer.", "maintenance_restore_backup_different_version": "Cette sauvegarde a ÊtÊ crÊÊe avec une version diffÊrente de Immich !", @@ -229,8 +205,8 @@ "maintenance_start_error": "Échec du dÊmarrage du mode maintenance.", "maintenance_upload_backup": "TÊlÊcharger le fichier de sauvegarde de la base de donnÊes", "maintenance_upload_backup_error": "Impossible de tÊlÊcharger la sauvegarde, s'agit-il d'un fichier .sql/.sql.gz ?", - "manage_concurrency": "Gestion du multitÃĸche", - "manage_concurrency_description": "Naviguer vers la page des tÃĸches pour gÊrer le multitÃĸche", + "manage_concurrency": "GÊrer du multitÃĸche", + "manage_concurrency_description": "Naviguer vers la pages des tÃĸches pour gÊrer le multitÃĸche", "manage_log_settings": "GÊrer les paramètres de journalisation", "map_dark_style": "Thème sombre", "map_enable_description": "Activer la carte", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Activer les notifications par courriel", "notification_settings": "Paramètres de notification", "notification_settings_description": "GÊrer les paramètres de notification, y compris les courriels", - "oauth_allow_insecure_requests": "Permettre les requÃĒtes non sÊcurisÊes", - "oauth_allow_insecure_requests_description": "ATTENTION : cela dÊsactive la validation de certificat TLS pour les requÃĒtes OAuth et peut vous exposer à des attaques par l'homme du milieu (MITM).", "oauth_auto_launch": "Lancement automatique", "oauth_auto_launch_description": "DÊmarrer automatiquement le flux de connexion OAuth lors de la navigation vers la page de connexion", "oauth_auto_register": "Inscription automatique", @@ -300,11 +274,9 @@ "oauth_button_text": "Texte du bouton", "oauth_client_secret_description": "NÊcessaire pour un client confidentiel, ou si le protocole PKCE (Proof Key for Code Exchange) n'est pas supportÊ par le client public.", "oauth_enable_description": "Connexion avec OAuth", - "oauth_end_session_url_description": "Rediriger l'utilisateur vers cette URI lorsqu'il se dÊconnecte.", "oauth_mobile_redirect_uri": "URI de redirection mobile", "oauth_mobile_redirect_uri_override": "Remplacer l'URI de redirection mobile", "oauth_mobile_redirect_uri_override_description": "Activer quand le fournisseur d'OAuth ne permet pas un URI mobile, comme ''{callback}''", - "oauth_prompt_description": "Paramètre de prompt (par ex. select_account, login, consentement)", "oauth_role_claim": "Attribut de rôle", "oauth_role_claim_description": "Donne automatiquement un accès en tant qu'admin, en se basant sur la prÊsence de cet attribut. L'attribut peut avoir soit 'user' (utilisateur) soit 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Actualisation de toutes les bibliothèques", "registration": "Enregistrement de l'administrateur", "registration_description": "Puisque vous ÃĒtes le premier utilisateur sur le système, vous serez dÊsignÊ en tant qu'administrateur et responsable des tÃĸches administratives, et vous pourrez alors crÊer d'autres utilisateurs.", - "release_channel_release_candidate": "Version candidate", - "release_channel_stable": "Stable", "remove_failed_jobs": "Supprimer les tÃĸches en erreur", "require_password_change_on_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion", "reset_settings_to_default": "RÊinitialiser les paramètres par dÊfaut", @@ -381,7 +351,7 @@ "template_settings": "Modèles de notifications", "template_settings_description": "GÊrer les modèles personnalisÊs pour les notifications", "theme_custom_css_settings": "CSS personnalisÊ", - "theme_custom_css_settings_description": "Des fichiers CSS permettent de personnaliser l'apparence d'Immich.", + "theme_custom_css_settings_description": "Les feuilles de style (CSS) permettent de personnaliser l'apparence d'Immich.", "theme_settings": "Paramètres du thème", "theme_settings_description": "GÊrer la personnalisation de l'interface web d'Immich", "thumbnail_generation_job": "GÊnÊration des miniatures", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "S'applique uniquement à VAAPI et QSV. DÊfinit le nœud DRI utilisÊ pour le transcodage matÊriel.", "transcoding_preset_preset": "PrÊsÊlection (-preset)", "transcoding_preset_preset_description": "Vitesse de compression. Les prÊrÊglages les plus lents produisent des fichiers plus petits, et augmentent la qualitÊ lorsqu'un certain dÊbit est dÊfini. Le codec vidÊo VP9 ignore les vitesses supÊrieures à ÂĢ rapide (faster) Âģ.", - "transcoding_realtime": "Transcodage en temps rÊel [EXPÉRIMENTAL]", - "transcoding_realtime_description": "Permet au transcodage d'ÃĒtre rÊalisÊ en temps rÊel durant la diffusion du flux de la vidÊo. Active la bascule automatique entre rÊsolutions, mais peut entraÃŽner une latence importante de lecture et des microcoupures en fonction des capacitÊs du serveur.", - "transcoding_realtime_enabled": "Activer la transcodification en temps rÊel", - "transcoding_realtime_enabled_description": "Si dÊsactivÊ, le serveur refusera de dÊmarrer de nouvelles sessions de transcodifications en temps rÊel.", - "transcoding_realtime_resolutions": "RÊsolutions", - "transcoding_realtime_resolutions_description": "Les rÊsolutions proposÊes pour le transcodage en temps rÊel. Des rÊsolutions supÊrieures peuvent causer des soucis pendant la lecture si le serveur ne peut pas les transcoder suffisamment rapidement.", - "transcoding_realtime_video_codecs": "Codecs vidÊo", - "transcoding_realtime_video_codecs_description": "Les codecs vidÊo proposÊs pour le transcodage en temps rÊel. Les clients choisissent la meilleure option qu'ils supportent durant la lecture. AV1 est plus efficace que HEVC, qui est plus efficace que H.264. En utilisant l'accÊlÊration matÊrielle, sÊlectionnez seulement les codecs que l'accÊlÊrateur peut encoder. En utilisant le transcodage logiciel, notez que H.264 est plus rapide que AV1, qui est plus rapide que HEVC.", "transcoding_reference_frames": "Trames de rÊfÊrence", "transcoding_reference_frames_description": "Le nombre d'images à prendre en rÊfÊrence lors de la compression d'une image donnÊe. Des valeurs ÊlevÊes amÊliorent l'efficacitÊ de la compression, mais ralentissent l'encodage. 0 fixe cette valeur automatiquement.", "transcoding_required_description": "Seulement les vidÊos dans un format non acceptÊ", @@ -478,8 +440,6 @@ "user_settings_description": "GÊrer les paramètres utilisateur", "user_successfully_removed": "L'utilisateur {email} a ÊtÊ supprimÊ avec succès.", "users_page_description": "Page d'administration des utilisateurs", - "version_check_channel": "Canal de versions", - "version_check_channel_description": "Choisissez le canal de versions pour lequel vous voulez recevoir des annonces de version", "version_check_enabled_description": "Activer la vÊrification pÊriodique de nouvelle version", "version_check_implications": "Le contrôle de version repose sur une communication pÊriodique avec {server}", "version_check_settings": "VÊrification de la version", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Vider le cache des images", "advanced_settings_clear_image_cache_error": "Erreur au vidage du cache des images", "advanced_settings_clear_image_cache_success": "Vidage avec succès de {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Utilisez cette option pour filtrer les mÊdia durant la synchronisation avec des critères alternatifs. N'utilisez cela que lorsque l'application n'arrive pas à dÊtecter tous les albums.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPÉRIMENTAL] Utiliser le filtre de synchronisation d'album alternatif", "advanced_settings_log_level_title": "Niveau de journalisation : {level}", "advanced_settings_prefer_remote_subtitle": "Certains appareils sont très lents à charger des miniatures à partir de ressources locales. Activez ce paramètre pour charger des images externes à la place.", "advanced_settings_prefer_remote_title": "PrÊfÊrer les images externes", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "En-tÃĒtes de proxy personnalisÊs [EXPÉRIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Active le mode lecture seule, oÚ les photos peuvent seulement ÃĒtre visualisÊes, et les actions comme les sÊlections multiples, le partage, la diffusion, la suppression sont dÊsactivÊes. Activer/dÊsactiver la lecture seule via l'image de l'utilisateur depuis l'Êcran d'accueil", "advanced_settings_readonly_mode_title": "Mode lecture seule", + "advanced_settings_self_signed_ssl_subtitle": "Permet d'ignorer la vÊrification du certificat SSL pour le point d'accès du serveur. Requis pour les certificats auto-signÊs.", + "advanced_settings_self_signed_ssl_title": "Autoriser les certificats SSL auto-signÊs [EXPÉRIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Supprimer ou restaurer automatiquement un mÊdia sur cet appareil lorsqu'une action a ÊtÊ faite sur le web", "advanced_settings_sync_remote_deletions_title": "Synchroniser les suppressions depuis le serveur [EXPÉRIMENTAL]", "advanced_settings_tile_subtitle": "Paramètres d'utilisateur avancÊs", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Êtes-vous sÃģr de vouloir supprimer l'album {album} ?", "album_delete_confirmation_description": "Si cet album est partagÊ, les autres utilisateurs ne pourront plus y accÊder.", "album_deleted": "Album supprimÊ", + "album_info_card_backup_album_excluded": "EXCLUS", + "album_info_card_backup_album_included": "INCLUS", "album_info_updated": "DÊtails de l'album mis à jour", + "album_leave": "Quitter l'album ?", + "album_leave_confirmation": "Êtes-vous sÃģr de vouloir quitter l'album {album} ?", "album_name": "Nom de l'album", "album_options": "Options de l'album", "album_remove_user": "Supprimer l'utilisateur ?", "album_remove_user_confirmation": "Êtes-vous sÃģr de vouloir supprimer {user} ?", "album_search_not_found": "Aucun album trouvÊ ne correspond à votre recherche", + "album_selected": "Album sÊlectionnÊ", "album_share_no_users": "Il semble que vous ayez partagÊ cet album avec tous les utilisateurs ou que vous n'ayez aucun utilisateur avec lequel le partager.", "album_summary": "RÊsumÊ de l'album", "album_updated": "Album mis à jour", "album_updated_setting_description": "Recevoir une notification par courriel lorsqu'un album partagÊ a de nouveaux mÊdias", - "album_upload_assets": "Envoyez des fichiers depuis votre ordinateur et ajoutez-les à l'album", + "album_upload_assets": "TÊlÊchargez des fichiers depuis votre ordinateur et ajoutez-les à l'album", + "album_user_left": "{album} quittÊ", + "album_user_removed": "{user} supprimÊ", + "album_viewer_appbar_delete_confirm": "Êtes-vous sur de vouloir supprimer cet album de votre compte ?", "album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album", + "album_viewer_appbar_share_err_leave": "Impossible de quitter l'album", + "album_viewer_appbar_share_err_remove": "Il y a des problèmes lors de la suppression des ÊlÊments de l'album", + "album_viewer_appbar_share_err_title": "Échec de la modification du titre de l'album", + "album_viewer_appbar_share_leave": "Quitter l'album", + "album_viewer_appbar_share_to": "Partager à", "album_viewer_page_share_add_users": "Ajouter des utilisateurs", "album_with_link_access": "Permettre à n'importe qui possÊdant le lien de voir les photos et les personnes de cet album.", "albums": "Albums", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Ordre de tri des mÊdias pour les nouveaux albums crÊÊs.", "albums_feature_description": "Bibliothèques de mÊdias pouvant ÃĒtre partagÊs avec d'autres utilisateurs.", "albums_on_device_count": "Album sur l'appareil ({count})", + "albums_selected": "{count, plural, one {# album sÊlectionnÊ} other {# albums sÊlectionnÊs}}", "all": "Tout", "all_albums": "Tous les albums", "all_people": "Toutes les personnes", "all_photos": "Toutes les photos", "all_videos": "Toutes les vidÊos", "allow_dark_mode": "Autoriser le mode sombre", + "allow_edits": "Autoriser les modifications", "allow_public_user_to_download": "Permettre le tÊlÊchargement par des utilisateurs non connectÊs", "allow_public_user_to_upload": "Permettre l'envoi par des utilisateurs non connectÊs", "allowed": "AutorisÊ", @@ -549,12 +528,14 @@ "always_keep": "Toujours conserver", "always_keep_photos_hint": "LibÊrer de l'espace va conserver toutes les photos sur cet appareil.", "always_keep_videos_hint": "LibÊrer de l'espace va conserver toutes les vidÊos sur cet appareil.", + "anti_clockwise": "Sens anti-horaire", "api_key": "ClÊ API", "api_key_description": "Cette valeur ne sera affichÊe qu'une seule fois. Assurez-vous de la copier avant de fermer la fenÃĒtre.", "api_key_empty": "Le nom de votre clÊ API ne doit pas ÃĒtre vide", "api_keys": "ClÊs d'API", "app_architecture_variant": "Variante (Architecture)", "app_bar_signout_dialog_content": "Êtes-vous sÃģr(e) de vouloir vous dÊconnecter ?", + "app_bar_signout_dialog_ok": "Oui", "app_bar_signout_dialog_title": "Se dÊconnecter", "app_download_links": "Liens de tÊlÊchargement de l'appli", "app_settings": "Paramètres de l'application", @@ -565,19 +546,27 @@ "archive": "Archive", "archive_action_prompt": "{count} ajoutÊ(s) à l'archive", "archive_or_unarchive_photo": "Archiver ou dÊsarchiver une photo", + "archive_page_no_archived_assets": "Aucun ÊlÊment archivÊ n'a ÊtÊ trouvÊ", + "archive_page_title": "Archiver ({count})", "archive_size": "Taille de l'archive", "archive_size_description": "Configurer la taille de l'archive maximale pour les tÊlÊchargements (en Go)", "archived": "Archives", "archived_count": "{count, plural, one {# archivÊ} other {# archivÊs}}", "are_these_the_same_person": "Est-ce la mÃĒme personne ?", "are_you_sure_to_do_this": "Êtes-vous sÃģr de vouloir faire ceci ?", + "array_field_not_fully_supported": "Les champs du tableau nÊcessitent la modification manuelle du JSON", + "asset_action_delete_err_read_only": "Impossible de supprimer le(s) mÊdia(s) en lecture seule, ils sont ignorÊs", + "asset_action_share_err_offline": "Impossible de rÊcupÊrer le(s) mÊdia(s) hors ligne, ils sont ignorÊs", "asset_added_to_album": "AjoutÊ à l'album", "asset_adding_to_album": "Ajout à l'albumâ€Ļ", "asset_created": "MÊdia crÊÊ", - "asset_day_count": "{date} : {count, plural, one {# mÊdia} other {# mÊdias}}", "asset_description_updated": "La description du mÊdia a ÊtÊ mise à jour", + "asset_filename_is_offline": "Le mÊdia {filename} est hors ligne", + "asset_has_unassigned_faces": "Le mÊdia a des visages non attribuÊs", "asset_hashing": "Hachageâ€Ļ", "asset_list_group_by_sub_title": "Regrouper par", + "asset_list_layout_settings_dynamic_layout_title": "Affichage dynamique", + "asset_list_layout_settings_group_automatically": "Automatique", "asset_list_layout_settings_group_by": "Grouper les ÊlÊments par", "asset_list_layout_settings_group_by_month_day": "Mois + jour", "asset_list_layout_sub_title": "Disposition", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "MÊdia introuvable sur iCloud. Le mÊdia est peut-ÃĒtre inaccessible en raison d'un fichier corrompu stockÊ sur iCloud", "asset_offline": "MÊdia hors ligne", "asset_offline_description": "Ce mÊdia externe n'est plus accessible sur le disque. Veuillez contacter votre administrateur Immich pour obtenir de l'aide.", + "asset_restored_successfully": "ÉlÊment restaurÊ avec succès", "asset_skipped": "SautÊ", "asset_skipped_in_trash": "À la corbeille", + "asset_trashed": "MÊdia mis à la corbeille", "asset_troubleshoot": "DÊpannage de mÊdia", "asset_uploaded": "EnvoyÊ", "asset_uploading": "Envoiâ€Ļ", "asset_viewer_settings_subtitle": "Modifier les paramètres du visualiseur photos", "asset_viewer_settings_title": "Visualiseur d'ÊlÊments", "assets": "MÊdias", + "assets_added_count": "{count, plural, one {# mÊdia ajoutÊ} other {# mÊdias ajoutÊs}}", "assets_added_to_album_count": "{count, plural, one {# mÊdia ajoutÊ} other {# mÊdias ajoutÊs}} à l'album", - "assets_added_to_album_partial_count": "{successCount} {totalCount, plural, one {ajoutÊ} other {ajoutÊs}} parmi {totalCount} {totalCount, plural, one {mÊdia} other {mÊdias}} à l'album", "assets_added_to_albums_count": "{assetTotal, plural, one {# mÊdia ajoutÊ} other {# mÊdias ajoutÊs}} à {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Le mÊdia ne peut pas ÃĒtre ajoutÊ} other {Les mÊdias ne peuvent pas ÃĒtre ajoutÊs}} à l'album", "assets_cannot_be_added_to_albums": "{count, plural, one {Le mÊdia ne peut ÃĒtre ajoutÊ} other {Les mÊdias ne peuvent ÃĒtre ajoutÊs}} à aucun des albums", "assets_count": "{count, plural, one {# mÊdia} other {# mÊdias}}", + "assets_deleted_permanently": "{count} mÊdia(s) supprimÊ(s) dÊfinitivement", + "assets_deleted_permanently_from_server": "{count} mÊdia(s) supprimÊ(s) dÊfinitivement du serveur Immich", + "assets_downloaded_failed": "{count, plural, one {# fichier tÊlÊchargÊ - Êchec du fichier {error}} other {# fichiers tÊlÊchargÊs - Êchec des fichiers {error}}}", + "assets_downloaded_successfully": "{count, plural, one {# fichier tÊlÊchargÊ avec succès} other {# fichiers tÊlÊchargÊs avec succès}}", "assets_moved_to_trash_count": "{count, plural, one {# mÊdia dÊplacÊ} other {# mÊdias dÊplacÊs}} dans la corbeille", "assets_permanently_deleted_count": "{count, plural, one {# mÊdia supprimÊ} other {# mÊdias supprimÊs}} dÊfinitivement", "assets_removed_count": "{count, plural, one {# mÊdia supprimÊ} other {# mÊdias supprimÊs}}", + "assets_removed_permanently_from_device": "{count} mÊdia(s) supprimÊ(s) dÊfinitivement de votre appareil", "assets_restore_confirmation": "Êtes-vous sÃģr de vouloir restaurer tous vos mÊdias de la corbeille ? Vous ne pouvez pas annuler cette action ! Notez que les mÊdias hors ligne ne peuvent ÃĒtre restaurÊs de cette façon.", "assets_restored_count": "{count, plural, one {# mÊdia restaurÊ} other {# mÊdias restaurÊs}}", + "assets_restored_successfully": "{count} ÊlÊment(s) restaurÊ(s) avec succès", "assets_trashed": "{count} mÊdia(s) dÊplacÊ(s) vers la corbeille", "assets_trashed_count": "{count, plural, one {# mÊdia} other {# mÊdias}} mis à la corbeille", + "assets_trashed_from_server": "{count} mÊdia(s) dÊplacÊ(s) vers la corbeille du serveur Immich", "assets_were_part_of_album_count": "{count, plural, one {Un mÊdia est} other {Des mÊdias sont}} dÊjà dans l'album", "assets_were_part_of_albums_count": "{count, plural, one {Le mÊdia Êtait dÊjà prÊsent} other {Les mÊdias Êtaient dÊjà prÊsents}} dans les albums", "authorized_devices": "Appareils autorisÊs", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Lecture automatique d'un diaporama", "back": "Retour", "back_close_deselect": "Retournez en arrière, fermez ou dÊsÊlectionnez", + "background_backup_running_error": "La sauvegarde en tÃĸche de fond est actuellement en cours, impossible de dÊmarrer une sauvegarde manuelle", "background_location_permission": "Permission de localisation en arrière plan", "background_location_permission_content": "Afin de pouvoir changer d'adresse en arrière plan, Immich doit avoir *en permanence* accès à la localisation prÊcise, afin d'accÊder au le nom du rÊseau Wi-Fi utilisÊ", "background_options": "Options d'arrière-plan", "backup": "Sauvegarde", + "backup_album_selection_page_albums_device": "Albums sur l'appareil ({count})", "backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure", "backup_album_selection_page_assets_scatter": "Les ÊlÊments peuvent ÃĒtre rÊpartis sur plusieurs albums. De ce fait, les albums peuvent ÃĒtre inclus ou exclus pendant le processus de sauvegarde.", "backup_album_selection_page_select_albums": "SÊlectionner les albums", "backup_album_selection_page_selection_info": "Informations sur la sÊlection", + "backup_album_selection_page_total_assets": "Total des ÊlÊments uniques", "backup_albums_sync": "Sauvegarde de la Synchronisation des Albums", + "backup_all": "Tout", + "backup_background_service_backup_failed_message": "Échec de la sauvegarde des mÊdias. Nouvelle tentativeâ€Ļ", "backup_background_service_complete_notification": "Sauvegarde du mÊdia terminÊe", + "backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentativeâ€Ļ", + "backup_background_service_current_upload_notification": "Envoi de {filename}", "backup_background_service_default_notification": "Recherche de nouveaux mÊdiasâ€Ļ", + "backup_background_service_error_title": "Erreur de sauvegarde", "backup_background_service_in_progress_notification": "Sauvegarde de vos mÊdiasâ€Ļ", + "backup_background_service_upload_failure_notification": "Échec lors de l'envoi de {filename}", "backup_controller_page_albums": "Sauvegarder les albums", + "backup_controller_page_background_app_refresh_disabled_content": "Activez le rafraÃŽchissement de l'application en arrière-plan dans Paramètres > GÊnÊral > RafraÃŽchissement de l'application en arrière-plan afin d'utiliser la sauvegarde en arrière-plan.", + "backup_controller_page_background_app_refresh_disabled_title": "RafraÃŽchissement de l'application en arrière-plan dÊsactivÊ", + "backup_controller_page_background_app_refresh_enable_button_text": "Aller aux paramètres", "backup_controller_page_background_battery_info_link": "Montrez-moi comment", "backup_controller_page_background_battery_info_message": "Pour une expÊrience optimale de la sauvegarde en arrière-plan, veuillez dÊsactiver toute optimisation de la batterie limitant l'activitÊ en arrière-plan pour Immich.\n\nÉtant donnÊ que cela est spÊcifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Optimisation de la batterie", + "backup_controller_page_background_charging": "Seulement pendant la charge", + "backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan", "backup_controller_page_background_delay": "Retarder la sauvegarde des nouveaux mÊdias : {duration}", + "backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux mÊdias sans avoir à ouvrir l'application", + "backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est dÊsactivÊe", + "backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activÊe", + "backup_controller_page_background_turn_off": "DÊsactiver le service d'arrière-plan", + "backup_controller_page_background_turn_on": "Activer le service d'arrière-plan", + "backup_controller_page_background_wifi": "Uniquement en wifi", "backup_controller_page_backup": "SauvegardÊ", "backup_controller_page_backup_selected": "SÊlectionnÊ : ", "backup_controller_page_backup_sub": "Photos et vidÊos sauvegardÊes", + "backup_controller_page_created": "CrÊÊ le : {date}", + "backup_controller_page_desc_backup": "Activez la sauvegarde au premier plan pour envoyer automatiquement les nouveaux mÊdias sur le serveur lors de l'ouverture de l'application.", "backup_controller_page_excluded": "Exclus : ", + "backup_controller_page_failed": "Échec de l'opÊration ({count})", + "backup_controller_page_filename": "Nom du fichier : {filename} [{size}]", + "backup_controller_page_id": "ID : {id}", + "backup_controller_page_info": "Informations de sauvegarde", "backup_controller_page_none_selected": "Aucune sÊlection", "backup_controller_page_remainder": "Restant", "backup_controller_page_remainder_sub": "Photos et albums restants à sauvegarder à partir de la sÊlection", "backup_controller_page_server_storage": "Stockage du serveur", + "backup_controller_page_start_backup": "DÊmarrer la sauvegarde", + "backup_controller_page_status_off": "La sauvegarde est dÊsactivÊe", + "backup_controller_page_status_on": "La sauvegarde est activÊe", "backup_controller_page_storage_format": "{used} sur {total} utilisÊs", "backup_controller_page_to_backup": "Albums à sauvegarder", "backup_controller_page_total_sub": "Toutes les photos et vidÊos uniques des albums sÊlectionnÊs", + "backup_controller_page_turn_off": "DÊsactiver la sauvegarde", + "backup_controller_page_turn_on": "Activer la sauvegarde au premier plan", + "backup_controller_page_uploading_file_info": "Envoi des informations du fichier", + "backup_err_only_album": "Impossible de retirer le seul album", "backup_error_sync_failed": "Échec de synchronisation.", "backup_info_card_assets": "mÊdias", + "backup_manual_cancelled": "AnnulÊ", + "backup_manual_in_progress": "Envoi dÊjà en cours. RÊessayez plus tard", + "backup_manual_success": "Succès", + "backup_manual_title": "Statut de l'envoi", "backup_options": "Options de sauvegarde", + "backup_options_page_title": "Options de sauvegarde", + "backup_setting_subtitle": "Ajuster les paramètres d'envoi au premier et en arrière-plan", "backup_settings_subtitle": "GÊrer les paramètres de tÊlÊversement", + "backup_upload_details_page_more_details": "Tapoter pour plus de dÊtails", "backward": "Arrière", - "battery_optimization_backup_reliability": "DÊsactiver les optimisations de la batterie peut amÊliorer la fiabilitÊ de la sauvegarde en arrière-plan", "biometric_auth_enabled": "Authentification biomÊtrique activÊe", "biometric_locked_out": "L'authentification biomÊtrique est verrouillÊ", "biometric_no_options": "Aucune option biomÊtrique disponible", "biometric_not_available": "L'authentification biomÊtrique n'est pas disponible sur cet appareil", + "birthdate_saved": "Date de naissance enregistrÊe avec succès", "birthdate_set_description": "La date de naissance est utilisÊe pour calculer l'Ãĸge de cette personne au moment oÚ la photo a ÊtÊ prise.", "blurred_background": "Arrière-plan floutÊ", - "browse_templates": "Naviguer dans les modèles", "bugs_and_feature_requests": "Bugs & demandes d'Êvolutions", "build": "Version", "build_image": "Image de la version", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Êtes-vous sÃģr de vouloir conserver {count, plural, one {# doublon} other {# doublons}} ? Cela rÊsoudra tous les groupes de doublons sans rien supprimer.", "bulk_trash_duplicates_confirmation": "Êtes-vous sÃģr de vouloir mettre à la corbeille {count, plural, one {# doublon} other {# doublons}} ? Cette opÊration permet de conserver le plus grand mÊdia de chaque groupe et de mettre à la corbeille tous les autres doublons.", "buy": "Acheter Immich", + "cache_settings_clear_cache_button": "Effacer le cache", + "cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.", + "cache_settings_duplicated_assets_clear_button": "EFFACER", + "cache_settings_duplicated_assets_subtitle": "Photos et vidÊos qui sont ignorÊes par l'application", + "cache_settings_duplicated_assets_title": "MÊdias dupliquÊs ({count})", + "cache_settings_statistics_album": "Miniatures de la bibliothèque", + "cache_settings_statistics_full": "Images complètes", + "cache_settings_statistics_shared": "Miniatures de l'album partagÊ", + "cache_settings_statistics_thumbnail": "Miniatures", + "cache_settings_statistics_title": "Utilisation du cache", + "cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich", + "cache_settings_tile_subtitle": "Contrôler le comportement du stockage local", + "cache_settings_tile_title": "Stockage local", + "cache_settings_title": "Paramètres de mise en cache", "camera": "Appareil photo", "camera_brand": "Marque d'appareil", "camera_model": "Modèle d'appareil", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Impossible de mettre à jour la description", "cast": "Diffusion", "cast_description": "Configurer les destinations disponibles de diffusion", - "change": "Changer", "change_date": "Changer la date", "change_description": "Changer la description", - "change_display_order": "Changer l'ordre d'affichage", + "change_display_order": "Modifier l'ordre d'affichage", + "change_expiration_time": "Modifier le dÊlai d'expiration", "change_location": "Changer la localisation", "change_name": "Changer le nom", "change_name_successfully": "Nouveau nom enregistrÊ", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Les mots de passe ne correspondent pas", "change_password_form_reenter_new_password": "Saisissez à nouveau le nouveau mot de passe", "change_pin_code": "Changer le code PIN", + "change_trigger": "Changer le dÊclencheur", + "change_trigger_prompt": "Êtes-vous sÃģr de vouloir changer le dÊclencheur ? Cela va supprimer toutes les actions et filtres existants.", "change_your_password": "Changer votre mot de passe", "changed_visibility_successfully": "VisibilitÊ modifiÊe avec succès", "charging": "En charge", "charging_requirement_mobile_backup": "La sauvegarde en tÃĸche de fond nÊcessite que l'appareil soit en charge", + "check_corrupt_asset_backup": "VÊrifier la corruption des ÊlÊments enregistrÊs", + "check_corrupt_asset_backup_button": "VÊrifier", + "check_corrupt_asset_backup_description": "Lancer cette vÊrification uniquement lorsque connectÊ à un rÊseau Wi-Fi et que tout le contenu a ÊtÊ enregistrÊ. Cette procÊdure peut durer plusieurs minutes.", "check_logs": "VÊrifier les journaux", "checksum": "Somme de contrôle", - "choose": "Choisir", "choose_matching_people_to_merge": "Choisir les personnes à fusionner", "city": "Ville", "cleanup_confirm_description": "Immich a trouvÊ {count} ÊlÊments (crÊÊs avant {date}) sauvegardÊs en toute sÊcuritÊ sur le serveur. Supprimer les copies locales de cet appareil ?", @@ -719,10 +774,11 @@ "clear": "Effacer", "clear_all": "Effacer tout", "clear_all_recent_searches": "Supprimer les recherches rÊcentes", - "clear_failed_count": "Échec de la suppression ({count})", "clear_file_cache": "Vider le fichier de cache", "clear_message": "Effacer le message", "clear_value": "Effacer la valeur", + "client_cert_dialog_msg_confirm": "D'accord", + "client_cert_enter_password": "Entrer mot de passe", "client_cert_import": "Importer", "client_cert_import_success_msg": "Certificat importÊ", "client_cert_invalid_msg": "Fichier de certificat invalide ou mot de passe incorrect", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certificat supprimÊ", "client_cert_subtitle": "Prend en charge uniquement le format PKCS12 (.p12, .pfx). L'importation/suppression de certificats n'est possible qu'avant la connexion", "client_cert_title": "Certificat SSL [EXPÉRIMENTAL]", + "clockwise": "Sens horaire", "close": "Fermer", "collapse": "RÊduire", "collapse_all": "Tout rÊduire", "color": "Couleur", + "color_theme": "Thème de couleur", "command": "Commande", "command_palette_prompt": "Trouver rapidement des pages, actions ou commandes", "command_palette_to_close": "pour fermer", @@ -747,7 +805,6 @@ "comments_are_disabled": "Les commentaires sont dÊsactivÊs", "common_create_new_album": "CrÊer un nouvel album", "completed": "ComplÊtÊ", - "configuration": "Configuration", "confirm": "Confirmer", "confirm_admin_password": "Confirmez le mot de passe Admin", "confirm_delete_face": "Êtes-vous sÃģr de vouloir supprimer le visage de {name} du mÊdia ?", @@ -762,16 +819,19 @@ "contain": "Contenu", "context": "Contexte", "continue": "Continuer", - "control_bottom_app_bar_add_tags": "Ajouter des Êtiquettes", + "control_bottom_app_bar_create_new_album": "CrÊer un nouvel album", + "control_bottom_app_bar_delete_from_immich": "Supprimer de Immich", "control_bottom_app_bar_delete_from_local": "Supprimer de l'appareil", "control_bottom_app_bar_edit_location": "Modifier la localisation", "control_bottom_app_bar_edit_time": "Modifier la date et l'heure", + "control_bottom_app_bar_share_link": "Lien partagÊ", + "control_bottom_app_bar_share_to": "Partager à", "control_bottom_app_bar_trash_from_immich": "DÊplacer vers la corbeille", "copied_image_to_clipboard": "Image copiÊe dans le presse-papiers.", "copied_to_clipboard": "CopiÊ dans le presse-papiers !", "copy_error": "Copier l'erreur", + "copy_file_path": "Copier le chemin du fichier", "copy_image": "Copier l'image", - "copy_json": "Copier le JSON", "copy_link": "Copier le lien", "copy_link_to_clipboard": "Copier le lien dans le presse-papiers", "copy_password": "Copier le mot de passe", @@ -789,6 +849,7 @@ "create_link_to_share": "CrÊer un lien pour partager", "create_link_to_share_description": "Permettre à n'importe qui ayant le lien de voir la(es) photo(s) sÊlectionnÊe(s)", "create_new": "NOUVEAU", + "create_new_face": "CrÊer un nouveau visage", "create_new_person": "CrÊer une nouvelle personne", "create_new_person_hint": "Attribuer les mÊdias sÊlectionnÊs à une nouvelle personne", "create_new_user": "CrÊer un nouvel utilisateur", @@ -805,33 +866,37 @@ "created_at": "CrÊÊ à", "creating_linked_albums": "CrÊation des albums liÊs...", "crop": "Recadrer", + "crop_aspect_ratio_fixed": "FigÊ", "crop_aspect_ratio_free": "Libre", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "CarrÊ", + "curated_object_page_title": "Objets", "current_device": "Appareil actuel", "current_pin_code": "Code PIN actuel", "current_server_address": "Adresse actuelle du serveur", "custom_date": "Date personnalisÊe", "custom_locale": "Paramètres rÊgionaux personnalisÊs", "custom_locale_description": "Formater les dates, heures et nombres en fonction de la langue sÊlectionnÊe et de la rÊgion", + "custom_url": "URL personnalisÊe", "cutoff_date_description": "Conservez les photos depuis les derniersâ€Ļ", "cutoff_day": "{count, plural, one {jour} other {jours}}", "cutoff_year": "{count, plural, one {annÊe} other {annÊes}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Sombre", "dark_theme": "Basculer sur le thème sombre", "date": "Date", "date_after": "Date après", "date_and_time": "Date et heure", "date_before": "Date avant", - "date_of_birth": "Date de naissance", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Date de naissance enregistrÊe avec succès", "date_range": "Plage de dates", - "date_time_original": "Jour/heure originel", "day": "Jour", "days": "Jours", "deduplicate_all": "DÊdupliquer tout", - "default_quality_subtitle": "QualitÊ par dÊfaut pour le partage. Un appui long sur le bouton de partage permet de choisir la qualitÊ à chaque fois.", - "default_share_quality": "QualitÊ par dÊfaut pour le partage", + "default_locale": "Langue par dÊfaut", + "default_locale_description": "Mettre en forme les dates et nombres en fonction de la langue de votre navigateur", "delete": "Supprimer", "delete_action_confirmation_message": "Êtes-vous sÃģr de vouloir supprimer ce mÊdia ? Cela dÊplacera le mÊdia dans la poubelle du serveur et vous demandera si vous voulez le supprimer localement", "delete_action_prompt": "{count} supprimÊ(s)", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Ces mÊdias seront dÊfinitivement supprimÊs de Immich et de votre appareil", "delete_dialog_alert_local": "Ces mÊdias seront dÊfinitivement supprimÊs de votre appareil mais resteront disponibles sur le serveur d'Immich", "delete_dialog_alert_local_non_backed_up": "Certains mÊdias ne sont pas sauvegardÊs sur Immich et seront dÊfinitivement supprimÊs de votre appareil", + "delete_dialog_alert_remote": "Ces mÊdias seront dÊfinitivement supprimÊs du serveur Immich", + "delete_dialog_ok_force": "Supprimer tout de mÃĒme", "delete_dialog_title": "Supprimer dÊfinitivement", "delete_duplicates_confirmation": "Êtes-vous certain de vouloir supprimer dÊfinitivement ces doublons ?", "delete_face": "Supprimer le visage", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Êtes-vous sÃģr de vouloir supprimer l'Êtiquette {tagName} ?", "delete_user": "Supprimer l'utilisateur", "deleted_shared_link": "Lien partagÊ supprimÊ", + "deletes_missing_assets": "Supprimer les mÊdias manquants du disque", "description": "Description", + "description_input_hint_text": "Ajouter une description...", + "description_input_submit_error": "Erreur de mise à jour de la description, vÊrifier le journal pour plus de dÊtails", "deselect_all": "Tout dÊsÊlectionner", "details": "DÊtails", "direction": "Ordre", "disable": "DÊsactiver", "disabled": "DÊsactivÊ", + "disallow_edits": "Ne pas autoriser les modifications", "discord": "Discord", "discover": "DÊcouvrir", "discovered_devices": "Appareils identifiÊs", "dismiss_all_errors": "Ignorer toutes les erreurs", + "dismiss_error": "Ignorer l'erreur", "display_options": "Afficher les options", "display_order": "Ordre d'affichage", "display_original_photos": "Afficher les photos originales", @@ -876,9 +948,11 @@ "documentation": "Documentation", "done": "TerminÊ", "download": "TÊlÊcharger", + "download_action_prompt": "TÊlÊchargement de {count} ÊlÊments", "download_canceled": "TÊlÊchargement annulÊ", "download_complete": "TÊlÊchargement terminÊ", "download_enqueue": "TÊlÊchargement en attente", + "download_error": "Erreur de tÊlÊchargement", "download_failed": "TÊlÊchargement ÊchouÊ", "download_finished": "TÊlÊchargement terminÊ", "download_include_embedded_motion_videos": "VidÊos intÊgrÊes", @@ -888,15 +962,15 @@ "download_paused": "TÊlÊchargement en pause", "download_settings": "TÊlÊcharger", "download_settings_description": "GÊrer les paramètres de tÊlÊchargement des mÊdias", + "download_started": "TÊlÊchargement commencÊ", + "download_sucess": "TÊlÊchargement rÊussi", + "download_sucess_android": "Le mÊdia a ÊtÊ tÊlÊchargÊ dans DCIM/Immich", "download_waiting_to_retry": "TÊlÊchargement en attente du prochain essai", "downloading": "TÊlÊchargement", "downloading_asset_filename": "TÊlÊchargement du mÊdia {filename}", "downloading_from_icloud": "TÊlÊchargement depuis iCloud", "downloading_media": "TÊlÊchargement du mÊdia", - "drag_to_reorder": "Glisser pour rÊordonner", "drop_files_to_upload": "DÊposez les fichiers n'importe oÚ pour envoyer", - "duplicate": "Dupliquer", - "duplicate_workflow": "Dupliquer le flux de traitement", "duplicates": "Doublons", "duplicates_description": "Examiner chaque groupe et indiquer s'il y a des doublons.", "duration": "DurÊe", @@ -908,7 +982,9 @@ "edit_date_and_time": "Modifier la date et l'heure", "edit_date_and_time_action_prompt": "{count} modifiÊ(s) sur leur date et heure", "edit_date_and_time_by_offset": "Ajouter un dÊcalage à la date", + "edit_date_and_time_by_offset_interval": "Nouvelle plage de date : {from} - {to}", "edit_description": "Modifier la description", + "edit_description_prompt": "Choisir une nouvelle description :", "edit_exclusion_pattern": "Modifier le schÊma d'exclusion", "edit_faces": "Modifier les visages", "edit_key": "Modifier la clÊ", @@ -923,9 +999,12 @@ "edit_user": "Modifier l'utilisateur", "edit_workflow": "Modifier le flux de traitement", "editor": "Editeur", - "editor_discard_edits_confirm": "Annuler les modifications", + "editor_close_without_save_prompt": "Les changements ne seront pas enregistrÊs", + "editor_close_without_save_title": "Fermer l'Êditeur ?", + "editor_confirm_reset_all_changes": "Êtes-vous sÃģr de vouloir rÊinitialiser toutes les modifications ?", + "editor_discard_edits_confirm": "Annuler les Êditions", "editor_discard_edits_prompt": "Vous avez des modifications non sauvegardÊes. Etes-vous sÃģr de vouloir les perdre ?", - "editor_discard_edits_title": "Annuler les modifications ?", + "editor_discard_edits_title": "Annuler les Êditions ?", "editor_edits_applied_error": "Echec d'application des Êditions", "editor_edits_applied_success": "Editions appliquÊes avec succès", "editor_flip_horizontal": "Retourner horizontalement", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Entrez votre code PIN", "enter_your_pin_code_subtitle": "Entrez votre code PIN pour accÊder au dossier verrouillÊ", "error": "Erreur", + "error_change_sort_album": "Impossible de modifier l'ordre de tri des albums", "error_delete_face": "Erreur lors de la suppression du visage pour le mÊdia", + "error_getting_places": "Erreur à la rÊcupÊration des lieux", "error_loading_albums": "Erreur au chargement des albums", "error_loading_image": "Erreur de chargement de l'image", "error_loading_partners": "Erreur de rÊcupÊration des partenaires : {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Échec de suppression de la clÊ du produit", "failed_to_reset_pin_code": "Échec de la rÊinitialisation du code PIN", "failed_to_stack_assets": "Impossible d'empiler les mÊdias", - "failed_to_tag_assets": "Échec de l'Êtiquetage des mÊdias", "failed_to_unstack_assets": "Impossible de dÊpiler les mÊdias", "failed_to_update_notification_status": "Erreur de mise à jour du statut des notifications", "incorrect_email_or_password": "Courriel ou mot de passe incorrect", @@ -1087,13 +1167,21 @@ }, "errors_text": "Erreurs", "exclusion_pattern": "SchÊma d'exclusion", - "exif": "EXIF", + "exif": "Exif", "exif_bottom_sheet_description": "Ajouter une description...", "exif_bottom_sheet_description_error": "Erreur de mise à jour de la description", + "exif_bottom_sheet_details": "DÉTAILS", + "exif_bottom_sheet_location": "LOCALISATION", "exif_bottom_sheet_no_description": "Aucune description", + "exif_bottom_sheet_people": "PERSONNES", + "exif_bottom_sheet_person_add_person": "Ajouter un nom", "exit_slideshow": "Quitter le diaporama", "expand": "DÊvelopper", "expand_all": "Tout dÊvelopper", + "experimental_settings_new_asset_list_subtitle": "En cours de dÊveloppement", + "experimental_settings_new_asset_list_title": "Activer la grille de photos expÊrimentale", + "experimental_settings_subtitle": "Utilisez à vos dÊpends !", + "experimental_settings_title": "ExpÊrimental", "expire_after": "Expire", "expired": "ExpirÊ", "expires_date": "Expire le {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Exporter en JSON", "export_database": "Exporter la base de donnÊes", "export_database_description": "Exporter la base de donnÊes SQLite", - "exposure_time": "DurÊe d'exposition", "extension": "Extension", "external": "Externe", "external_libraries": "Bibliothèques externes", "external_network": "RÊseau externe", "external_network_sheet_info": "Quand vous n'ÃĒtes pas connectÊ(e) à votre rÊseau wifi prÊfÊrÊ, l'application va tenter de se connecter aux adresses ci-dessous, en commençant par la première", - "f_number": "Ouverture", "face_unassigned": "Non attribuÊ", "failed": "Échec", "failed_count": "En erreur : {count}", "failed_to_authenticate": "Échec de l'authentification", - "failed_to_delete_file": "Échec de suppression du fichier", "failed_to_load_assets": "Échec du chargement des ressources", "failed_to_load_folder": "Échec de chargement du dossier", "favorite": "Favori", "favorite_action_prompt": "{count} ajoutÊ(s) aux Favoris", "favorite_or_unfavorite_photo": "Ajouter ou supprimer des favoris", "favorites": "Favoris", + "favorites_page_no_favorites": "Aucun ÊlÊment favori n'a ÊtÊ trouvÊ", "feature_photo_updated": "Photo de la personne mise à jour", "features": "FonctionnalitÊs", + "features_in_development": "FonctionnalitÊs en dÊveloppement", "features_setting_description": "GÊrer les fonctionnalitÊs de l'application", "file_name_or_extension": "Nom du fichier ou extension", "file_name_text": "Nom du fichier", + "file_name_with_value": "Nom du fichier : {file_name}", "file_size": "Taille du fichier", "filename": "Nom du fichier", "filetype": "Type de fichier", "filter": "Filtrer", + "filter_description": "Conditions pour filtrer les mÊdias ciblÊs", "filter_people": "Filtrer les personnes", "filter_places": "Filtrer par lieu", "filter_tags": "Filtrer par Êtiquettes", "filters": "Filtres", + "find_them_fast": "Pour les retrouver rapidement par leur nom", "first": "Premier", "fix_incorrect_match": "Corriger une association incorrecte", - "focal_length": "Longueur de focale", "folder": "Dossier", "folder_not_found": "Dossier introuvable", "folders": "Dossiers", @@ -1147,7 +1236,6 @@ "free_up_space_description": "DÊplacer les photos et vidÊos sauvegardÊes vers la corbeille de votre appareil pour libÊrer de l'espace. Vos copies sur le serveur restent en sÊcuritÊ.", "free_up_space_settings_subtitle": "LibÊrer l'espace de votre appareil", "full_path": "Chemin complet : {path}", - "full_path_or_folder": "Chemin complet ou dossier", "gcast_enabled": "Diffusion Google Cast", "gcast_enabled_description": "Cette fonctionnalitÊ charge des ressources externes depuis Google pour fonctionner.", "general": "GÊnÊral", @@ -1168,6 +1256,7 @@ "group_owner": "Grouper par propriÊtaire", "group_places_by": "Grouper les lieux par...", "group_year": "Grouper par annÊe", + "haptic_feedback_switch": "Activer le retour haptique", "haptic_feedback_title": "Retour haptique", "has_quota": "Quota", "hash_asset": "Hasher le mÊdia", @@ -1188,12 +1277,29 @@ "hide_schema": "Masquer le schÊma", "hide_text_recognition": "Cacher la reconnaissance de texte", "hide_unnamed_people": "Cacher les personnes non nommÊes", + "home_page_add_to_album_conflicts": "{added} ÊlÊments ajoutÊs à l'album {album}. {failed} ÊlÊments sont dÊjà dans l'album.", + "home_page_add_to_album_err_local": "Impossible d'ajouter des mÊdias locaux aux albums, ils sont ignorÊs", + "home_page_add_to_album_success": "{added} ÊlÊments ajoutÊs à l'album {album}.", + "home_page_album_err_partner": "Impossible d'ajouter des mÊdias d'un partenaire à un album, ils sont ignorÊs", + "home_page_archive_err_local": "Impossible d'archiver les mÊdias locaux, ils sont ignorÊs", + "home_page_archive_err_partner": "Impossible d'archiver les mÊdias d'un partenaire, ils sont ignorÊs", "home_page_building_timeline": "Construction de la chronologie", + "home_page_delete_err_partner": "Impossible de supprimer les mÊdias d'un partenaire, ils sont ignorÊs", + "home_page_delete_remote_err_local": "Des mÊdias locaux sont dans la sÊlection de suppression à distance, ils sont ignorÊs", + "home_page_favorite_err_local": "Impossible d'ajouter des mÊdias locaux aux favoris, ils sont ignorÊs", + "home_page_favorite_err_partner": "Impossible de mettre en favori les mÊdias d'un partenaire, ils sont ignorÊs", + "home_page_first_time_notice": "Si c'est la première fois que vous utilisez l'application, veillez à choisir un ou plusieurs albums de sauvegarde afin que la chronologie puisse alimenter les photos et les vidÊos de cet ou ces albums", + "home_page_locked_error_local": "Impossible de dÊplacer l'objet vers le dossier verrouillÊ, passer", + "home_page_locked_error_partner": "Impossible de dÊplacer l'objet du collaborateur vers le dossier verrouillÊ, opÊration ignorÊe", + "home_page_share_err_local": "Impossible de partager par lien les mÊdias locaux, ils sont ignorÊs", + "home_page_upload_err_limit": "Impossible d'envoyer plus de 30 mÊdias en mÃĒme temps, demande ignorÊe", "host": "Hôte", "hour": "Heure", "hours": "Heures", "id": "ID", "idle": "Inactif", + "ignore_icloud_photos": "Ignorer les photos iCloud", + "ignore_icloud_photos_description": "Les photos stockÊes sur iCloud ne seront pas envoyÊes sur le serveur Immich", "image": "Image", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} prise le {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} prise avec {person1} le {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1} et {person2} le {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2}, et {person3} le {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} prise à {city}, {country} avec {person1}, {person2} et {additionalCount, number} autres personnes le {date}", + "image_saved_successfully": "Image enregistrÊ", + "image_viewer_page_state_provider_download_started": "TÊlÊchargement dÊmarrÊ", + "image_viewer_page_state_provider_download_success": "TÊlÊchargement rÊussi", + "image_viewer_page_state_provider_share_error": "Erreur de partage", "immich_logo": "Logo Immich", "immich_web_interface": "Interface Web Immich", "import_from_json": "Importer depuis un fichier JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Partage d'un mÊdia unique", "individual_shares": "Partages d'un mÊdia unique", "info": "Information", - "integrity_checks": "DÊtections d'intÊgritÊ", "interval": { "day_at_onepm": "Tous les jours à 13h", "hours": "Toutes les {hours, plural, one {heures} other {{hours, number} heures}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Format de date invalide", "invite_people": "Inviter une personne", "invite_to_album": "Inviter à l'album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "RÊcupÊration lancÊe {dateTime}", + "ios_debug_info_last_sync_at": "Dernière synchronisation {dateTime}", + "ios_debug_info_no_processes_queued": "Aucun processus en arrière plan en attente de traitement", + "ios_debug_info_no_sync_yet": "Le traitement de synchronisation en arrière plan n'a jamais ÊtÊ lancÊ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} processus en arrière plan en attente de traitement} other {{count} processus en arrière plan en attente de traitement}}", + "ios_debug_info_processing_ran_at": "Le traitement a ÊtÊ lancÊ {dateTime}", "items_count": "{count, plural, one {# ÊlÊment} other {# ÊlÊments}}", "jobs": "TÃĸches", + "json_editor": "Éditeur JSON", + "json_error": "Erreur JSON", "keep": "Conserver", "keep_albums": "Conserver les albums", "keep_albums_count": "Conserver {count} {count, plural, one {album} other {albums}}", @@ -1259,12 +1375,14 @@ "leave": "Quitter", "leave_album": "Quitter l'album", "lens_model": "Modèle d'objectif", - "less": "Moins", "let_others_respond": "Laisser les autres rÊagir", "level": "Niveau", "library": "Bibliothèque", "library_add_folder": "Ajouter un dossier", "library_edit_folder": "Modifier un dossier", + "library_options": "Options de bibliothèque", + "library_page_device_albums": "Albums sur l'appareil", + "library_page_new_album": "Nouvel album", "library_page_sort_asset_count": "Nombre d'ÊlÊments", "library_page_sort_created": "CrÊations les plus rÊcentes", "library_page_sort_last_modified": "Dernière modification", @@ -1274,14 +1392,11 @@ "light_theme": "Basculer sur le thème clair", "like": "J'aime", "like_deleted": "RÊaction ÂĢ J'aime Âģ supprimÊe", - "link": "Lien", "link_motion_video": "Lier la photo animÊe", "link_to_docs": "Pour plus d'informations, se rÊfÊrer à la documentation.", "link_to_oauth": "Lien au service OAuth", "linked_oauth_account": "Compte OAuth rattachÊ", "list": "Liste", - "live": "Direct", - "load_more": "Charger plus", "loading": "Chargement", "loading_search_results_failed": "Chargement des rÊsultats ÊchouÊ", "local": "Local", @@ -1310,9 +1425,11 @@ "login": "Connexion", "login_disabled": "La connexion a ÊtÊ dÊsactivÊe", "login_form_api_exception": "Erreur de l'API. Veuillez vÊrifier l'URL du serveur et rÊessayer.", + "login_form_back_button_text": "Retour", "login_form_email_hint": "votrecourriel@email.com", "login_form_endpoint_hint": "http://adresse-ip-serveur:port", "login_form_endpoint_url": "URL du point d'accès au serveur", + "login_form_err_http": "Veuillez prÊciser http:// ou https://", "login_form_err_invalid_email": "Courriel invalide", "login_form_err_invalid_url": "URL invalide", "login_form_err_leading_whitespace": "Espace en dÊbut de ligne", @@ -1322,16 +1439,17 @@ "login_form_failed_login": "Erreur de connexion, vÊrifiez l'url du serveur, le courriel et le mot de passe", "login_form_handshake_exception": "Il y a eu une exception de liaison avec le serveur. Activez la prise en charge des certificats auto-signÊs dans les paramètres si vous utilisez un certificat auto-signÊ.", "login_form_password_hint": "mot de passe", - "login_form_server_empty": "Saisissez l'URL du serveur", - "login_form_server_error": "Impossible de se connecter au serveur", - "login_has_been_disabled": "La connexion a ÊtÊ dÊsactivÊe", + "login_form_save_login": "Rester connectÊ", + "login_form_server_empty": "Saisissez l'URL du serveur.", + "login_form_server_error": "Impossible de se connecter au serveur.", + "login_has_been_disabled": "La connexion a ÊtÊ dÊsactivÊe.", "login_password_changed_error": "Une erreur s'est produite lors de la mise à jour de votre mot de passe", "login_password_changed_success": "Mot de passe mis à jour avec succès", "logout_all_device_confirmation": "Êtes-vous sÃģr de vouloir dÊconnecter tous les appareils ?", "logout_this_device_confirmation": "Êtes-vous sÃģr de vouloir dÊconnecter cet appareil ?", "logs": "Journaux", "longitude": "Longitude", - "look": "Aspect visuel", + "look": "Regarder", "loop_videos": "VidÊos en boucle", "loop_videos_description": "Activer pour voir la vidÊo en boucle dans le lecteur dÊtaillÊ.", "main_branch_warning": "Vous utilisez une version de dÊveloppement. Nous vous recommandons fortement d'utiliser une version stable !", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Restauration de la base de donnÊes", "maintenance_description": "Immich a ÊtÊ mis en mode maintenance.", "maintenance_end": "ArrÃĒter le mode maintenance", + "maintenance_end_error": "Échec de l'arrÃĒt du mode maintenance.", "maintenance_logged_in_as": "Actuellement connectÊ en tant que {user}", "maintenance_restore_from_backup": "Restaurer à partir d'une sauvegarde", "maintenance_restore_library": "Restaurer votre bibliothèque", @@ -1357,13 +1476,14 @@ "maintenance_task_migrations": "ExÊcution des migrations de base de donnÊesâ€Ļ", "maintenance_task_restore": "Restauration de la sauvegarde sÊlectionnÊeâ€Ļ", "maintenance_task_rollback": "La restauration a ÊchouÊ, retour au point de restaurationâ€Ļ", - "maintenance_title": "Temporairement indisponible", + "maintenance_title": "Temporairement non disponible", "make": "Marque", "manage_geolocation": "GÊrer la localisation", "manage_media_access_rationale": "Cette autorisation est nÊcessaire pour gÊrer correctement le dÊplacement de mÊdias vers la corbeille et la restauration depuis celle-ci.", "manage_media_access_settings": "Ouvrir les paramètres", "manage_media_access_subtitle": "Autoriser l'application Immich à gÊrer et dÊplacer des fichiers de mÊdia.", "manage_media_access_title": "Accès à la gestion de mÊdias", + "manage_shared_links": "GÊrer les liens partagÊs", "manage_sharing_with_partners": "GÊrer le partage avec les partenaires", "manage_the_app_settings": "GÊrer les paramètres de l'application", "manage_your_account": "GÊrer votre compte", @@ -1371,11 +1491,13 @@ "manage_your_devices": "GÊrer vos appareils", "manage_your_oauth_connection": "GÊrer votre connexion OAuth", "map": "Carte", + "map_assets_in_bounds": "{count, plural, =0 {Aucune photo dans cette zone} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Impossible d'obtenir la localisation de l'utilisateur", + "map_location_dialog_yes": "Oui", "map_location_picker_page_use_location": "Utiliser ma position", "map_location_service_disabled_content": "Le service de localisation doit ÃĒtre activÊ pour afficher les mÊdias de votre emplacement actuel. Souhaitez-vous l'activer maintenant ?", "map_location_service_disabled_title": "Service de localisation dÊsactivÊ", - "map_marker_for_image": "Marqueur de carte pour une image prise à {city}, {country}", + "map_marker_for_images": "Marqueur de carte pour les images prises à {city}, {country}", "map_marker_with_image": "Marqueur de carte avec image", "map_no_location_permission_content": "L'autorisation de localisation est nÊcessaire pour afficher les mÊdias de votre emplacement actuel. Souhaitez-vous l'autoriser maintenant ?", "map_no_location_permission_title": "Permission de localisation refusÊe", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "{days} derniers jours", "map_settings_date_range_option_year": "AnnÊe passÊe", "map_settings_date_range_option_years": "{years} dernières annÊes", + "map_settings_dialog_title": "Paramètres de la carte", "map_settings_include_show_archived": "Inclure les archives", "map_settings_include_show_partners": "Inclure les partenaires", "map_settings_only_show_favorites": "Afficher uniquement les favoris", "map_settings_theme_settings": "Thème de la carte", + "map_zoom_to_see_photos": "DÊzoomer pour voir les photos", "mark_all_as_read": "Tout marquer comme lu", + "mark_as_read": "Marquer comme lu", "marked_all_as_read": "Tout a ÊtÊ marquÊ comme lu", "matches": "Correspondances", "matching_assets": "MÊdias correspondants", - "media_chrome": { - "auto": "Auto", - "captions": "Sous-titres", - "captions_off": "DÊsactivÊ", - "closed_captions": "sous-titres codÊs", - "decode_error": "Erreur de dÊcodage", - "disable_captions": "DÊsactiver les sous-titres", - "enable_captions": "Activer les sous-titres", - "enter_fullscreen_mode": "Mettre en mode plein Êcran", - "exit_fullscreen_mode": "Quitter le mode plein Êcran", - "loop": "Lire en boucle", - "media_error_description": "Une erreur de mÊdia a provoquÊ l’interruption de la lecture. Le mÊdia peut ÃĒtre corrompu ou votre navigateur ne prend pas en charge ce format.", - "media_loading": "chargement des mÊdias", - "mute": "DÊsactiver le son", - "network_error": "Erreur rÊseau", - "network_error_description": "Une erreur rÊseau a causÊ l’Êchec du tÊlÊchargement du mÊdia.", - "not_supported_error": "Source non supportÊe", - "playback_rate": "Taux de lecture", - "playback_rate_current": "taux de lecture actuel", - "playback_rate_value": "Taux de lecture {playbackRate}", - "playback_time": "durÊe de lecture", - "quality": "QualitÊ", - "second": "seconde", - "seconds": "secondes", - "time_value_of_total_time": "{currentTime} sur {totalTime}", - "time_value_remaining": "{time} restant", - "unmute": "Activer le son", - "unsupported_error_description": "Une erreur non supportÊe s’est produite. Le serveur ou le rÊseau a ÊchouÊ, ou votre navigateur ne prend pas en charge ce format.", - "video_not_loaded_unknown_time": "vidÊo non chargÊe, durÊe inconnue.", - "video_player": "lecteur vidÊo", - "volume": "volume" - }, "media_type": "Type de mÊdia", "memories": "Souvenirs", "memories_all_caught_up": "Vous avez tout vu", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Voulez-vous fusionner ces personnes ? Cette action est irrÊversible.", "merge_people_successfully": "Fusion des personnes rÊussie", "merged_people_count": "{count, plural, one {# personne fusionnÊe} other {# personnes fusionnÊes}}", - "minFaces": "Nombre minimal de visages", - "minFaces_description": "Nombre minimal de visages reconnus pour afficher une personne", "minimize": "RÊduire", "minute": "Minute", "minutes": "Minutes", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Manquant", "mobile_app": "Appli mobile", "mobile_app_download_onboarding_note": "TÊlÊchargez l'application mobile compagnon via les options suivantes", "model": "Modèle", - "modify_date": "Modifier la date", "month": "Mois", + "monthly_title_text_date_format": "MMMM a", "more": "Plus", - "motion": "Mouvement", "move": "DÊplacer", + "move_down": "Descendre", "move_off_locked_folder": "DÊplacer en dehors du dossier verrouillÊ", "move_to": "DÊplacer vers", "move_to_device_trash": "DÊplacer vers la corbeille de l'appareil", "move_to_lock_folder_action_prompt": "{count} ajoutÊ(s) au dossier verrouillÊ", "move_to_locked_folder": "DÊplacer dans le dossier verrouillÊ", "move_to_locked_folder_confirmation": "Ces photos et vidÊos seront retirÊes de tous les albums et ne seront visibles que dans le dossier verrouillÊ", + "move_up": "Monter", + "moved_to_archive": "{count, plural, one {# ÊlÊment dÊplacÊ} other {# ÊlÊments dÊplacÊs}} vers les archives", + "moved_to_library": "{count, plural, one {# ÊlÊment dÊplacÊ} other {# ÊlÊments dÊplacÊs}} vers la bibliothèque", "moved_to_trash": "DÊplacÊ dans la corbeille", + "multiselect_grid_edit_date_time_err_read_only": "Impossible de modifier la date de mÊdias en lecture seule, ils sont ignorÊs", + "multiselect_grid_edit_gps_err_read_only": "Impossible de modifier l'emplacement de mÊdias en lecture seule, ils sont ignorÊs", "mute_memories": "Mettre en sourdine les souvenirs", "my_albums": "Mes albums", - "my_immich_description": "Copier la page actuelle comme Mon lien Immich", - "my_immich_title": "Mon lien Immich", "name": "Nom", "name_or_nickname": "Nom ou surnom", "name_required": "Le nom est nÊcessaire", @@ -1480,11 +1576,12 @@ "never": "Jamais", "new_album": "Nouvel Album", "new_api_key": "Nouvelle clÊ API", - "new_feature": "Nouvelle fonctionnalitÊ", + "new_date_range": "Nouvelle plage de date", "new_password": "Nouveau mot de passe", "new_person": "Nouvelle personne", "new_pin_code": "Nouveau code PIN", "new_pin_code_subtitle": "C'est votre premier accès au dossier verrouillÊ. CrÊez un code PIN pour sÊcuriser l'accès à cette page", + "new_timeline": "Nouvelle vue chronologique", "new_update": "Nouvelle mise à jour", "new_user_created": "Nouvel utilisateur crÊÊ", "new_version_available": "NOUVELLE VERSION DISPONIBLE", @@ -1492,6 +1589,7 @@ "next": "Suivant", "next_memory": "Souvenir suivant", "no": "Non", + "no_actions_added": "Aucune action ajoutÊe pour le moment", "no_albums_found": "Aucun album trouvÊ", "no_albums_message": "CrÊer un album pour organiser vos photos et vidÊos", "no_albums_with_name_yet": "Il semble que vous n'ayez pas encore d'albums avec ce nom.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Aucun appareil de diffusion trouvÊ", "no_checksum_local": "Aucune empreinte numerique disponible - impossible de rÊcupÊrer les mÊdias locaux", "no_checksum_remote": "Aucune empreinte numÊrique disponible - impossible de rÊcupÊrer les mÊdias distants", + "no_configuration_needed": "Aucune configuration nÊcessaire", "no_devices": "Aucun appareil autorisÊ", "no_duplicates_found": "Aucun doublon n'a ÊtÊ trouvÊ.", "no_exif_info_available": "Aucune information exif disponible", "no_explore_results_message": "Envoyez plus de photos pour explorer votre bibliothèque.", "no_favorites_message": "Ajouter des photos et vidÊos à vos favoris pour les retrouver plus rapidement", + "no_filters_added": "Aucun filtre ajoutÊ pour le moment", "no_libraries_message": "CrÊer une bibliothèque externe pour voir vos photos et vidÊos dans un autre espace de stockage", "no_local_assets_found": "Aucun mÊdia local trouvÊ avec cette empreinte numerique", + "no_location_set": "Aucune localisation definie", "no_locked_photos_message": "Les photos et vidÊos du dossier verrouillÊ sont masquÊs et ne s'afficheront pas dans votre galerie ou la recherche.", "no_name": "Pas de nom", "no_notifications": "Pas de notification", @@ -1518,19 +1619,14 @@ "no_results": "Aucun rÊsultat", "no_results_description": "Essayez un synonyme ou un mot-clÊ plus gÊnÊral", "no_shared_albums_message": "CrÊer un album pour partager vos photos et vidÊos avec les personnes de votre rÊseau", - "no_steps": "Aucune Êtape ajoutÊe", + "no_uploads_in_progress": "Pas d'envoi en cours", "none": "Aucun", "not_allowed": "Non autorisÊ", "not_available": "N/A", "not_in_any_album": "Dans aucun album", "not_selected": "Non sÊlectionnÊ", - "not_set": "Non dÊfini", "notes": "Notes", "nothing_here_yet": "Rien pour le moment", - "notification_backup_reliability": "Activer les notifications pour amÊliorer la fiabilitÊ de la sauvegarde en arrière-plan", - "notification_enabled_list_tile_content": "Immich utilise les notifications pour la sauvegarde en arrière-plan. GÊrez-les dans les paramètres de votre appareil.", - "notification_enabled_list_tile_open_button": "Ouvrir les paramètres", - "notification_enabled_list_tile_title": "Notifications activÊes", "notification_permission_dialog_content": "Pour activer les notifications, allez dans Paramètres et sÊlectionnez Autoriser.", "notification_permission_list_tile_content": "Accordez la permission d'activer les notifications.", "notification_permission_list_tile_enable_button": "Activer les notifications", @@ -1540,10 +1636,8 @@ "notifications_setting_description": "GÊrer les notifications", "oauth": "OAuth", "obtainium_configurator": "Configuration pour Obtainium", - "obtainium_configurator_instructions": "Utilisez Obtainium pour installer et mettre à jour l'application Android directement depuis la version d'Immich sur Github. CrÊez une clÊ d'API et sÊlectionnez une variante pour crÊer votre lien de configuration pour Obtainium", + "obtainium_configurator_instructions": "Utilisez Obtainium pour installer et mettre à jour l'application Android directement depuis la version d'Immich sur Github. CrÊer une clÊ d'API et sÊlectionner une variante pour crÊer votre lien de configuration pour Obtainium", "ocr": "Reconnaissance Optique de Caractères OCR", - "ocr_body": "Immich peut dÊsormais lire le texte dans vos photos, pour que vous puissiez les chercher dans votre galerie par ce qu'elles disent.", - "ocr_title": "Cherchez le texte dans vos photos", "official_immich_resources": "Ressources Immich officielles", "offline": "Hors ligne", "offset": "DÊcalage", @@ -1562,8 +1656,6 @@ "open": "Ouvrir", "open_calendar": "Ouvrir le calendrier", "open_in_browser": "Ouvrir dans le navigateur", - "open_in_immich_body": "DÊfinissez Immich comme vote application de gallerie par dÊfaut sur Android pour ouvrir vos photos directement depuis les autres applications.", - "open_in_immich_title": "Ouvrir les photos dans Immich", "open_in_map_view": "Montrer sur la carte", "open_in_openstreetmap": "Ouvrir dans OpenStreetMap", "open_the_search_filters": "Ouvrir les filtres de recherche", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organiser dans des albums", "organize_into_albums_description": "Mettre les photos existantes dans des albums en utilisant les paramètres de synchronisation actuels", "organize_your_library": "Organiser votre bibliothèque", - "orientation": "Orientation", "original": "original", "other": "Autre", "other_devices": "Autres appareils", + "other_entities": "Autres entitÊs", "other_variables": "Autres variables", "owned": "PossÊdÊ", "owner": "PropriÊtaire", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Toutes vos photos et vidÊos, exceptÊes celles archivÊes ou supprimÊes", "partner_can_access_location": "La localisation de vos photos et vidÊos", "partner_list_user_photos": "Photos de {user}", + "partner_list_view_all": "Voir tous", "partner_page_empty_message": "Vos photos ne sont pas encore partagÊes avec un partenaire.", "partner_page_no_more_users": "Plus d'utilisateurs à ajouter", + "partner_page_partner_add_failed": "Échec de l'ajout d'un partenaire", "partner_page_select_partner": "SÊlectionner un partenaire", + "partner_page_shared_to_title": "PartagÊ avec", "partner_page_stop_sharing_content": "{partner} ne pourra plus accÊder à vos photos.", "partner_sharing": "Partage avec les partenaires", "partners": "Partenaires", @@ -1609,6 +1704,8 @@ "people": "Personnes", "people_edits_count": "{count, plural, one {# personne ÊditÊe} other {# personnes ÊditÊes}}", "people_feature_description": "Parcourir les photos et vidÊos groupÊes par personnes", + "people_selected": "{count, plural, one {# personne sÊlectionnÊe} other {# personnes sÊlectionnÊes}}", + "people_sidebar_description": "Afficher le menu Personnes dans la barre latÊrale", "permanent_deletion_warning": "Avertissement avant suppression dÊfinitive", "permanent_deletion_warning_setting_description": "Afficher un avertissement avant la suppression dÊfinitive d'un mÊdia", "permanently_delete": "Supprimer dÊfinitivement", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# mÊdia dÊfinitivement supprimÊ} other {# mÊdias dÊfinitivement supprimÊs}}", "permission": "Autorisation", "permission_empty": "Votre autorisation ne doit pas ÃĒtre vide", + "permission_onboarding_back": "Retour", + "permission_onboarding_continue_anyway": "Continuer quand mÃĒme", + "permission_onboarding_get_started": "Commencer", + "permission_onboarding_go_to_settings": "AccÊder aux paramètres", + "permission_onboarding_permission_denied": "Permission refusÊe. Pour utiliser Immich, accordez lautorisation pour les photos et vidÊos dans les Paramètres.", + "permission_onboarding_permission_granted": "Permission accordÊe ! Vous ÃĒtes prÃĒts.", + "permission_onboarding_permission_limited": "Permission limitÊe. Pour permettre à Immich de sauvegarder et de gÊrer l'ensemble de votre bibliothèque, accordez l'autorisation pour les photos et vidÊos dans les Paramètres.", + "permission_onboarding_request": "Immich nÊcessite l'autorisation d'accÊder à vos photos et vidÊos.", "person": "Personne", "person_age_months": "{months, plural, one {# mois} other {# mois}}", "person_age_year_months": "1 an, {months, plural, one {# mois} other {# mois}}", - "person_age_years": "{years, plural, one {# an} other {# ans}}", + "person_age_years": "{years, plural, other {# ans}}", "person_birthdate": "NÊ(e) le {date}", "person_hidden": "{name}{hidden, select, true { (cachÊ)} other {}}", "person_recognized": "Personne reconnue", + "person_selected": "Personne sÊlectionnÊe", "photo_shared_all_users": "Il semble que vous ayez partagÊ vos photos avec tous les utilisateurs ou que vous n'ayez aucun utilisateur avec qui les partager.", "photos": "Photos", "photos_and_videos": "Photos et vidÊos", + "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} Photos}}", "photos_from_previous_years": "Photos des annÊes prÊcÊdentes", + "photos_only": "Photos uniquement", "pick_a_location": "Choisissez une localisation", "pick_custom_range": "PÊriode personnalisÊe", "pick_date_range": "SÊlectionner une pÊriode de dates", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "PrÊfÊrer la lecture des vidÊos originales plutôt que les vidÊos transcodÊes. Si le mÊdia original n'est pas compatible, il pourrait ne pas ÃĒtre lu correctement.", "play_transcoded_video": "Lire la vidÊo transcodÊe", "please_auth_to_access": "Merci de vous authentifier pour accÊder", - "plugin_method_filter_type": "Filtre", - "plugin_method_filter_type_description": "Cette mÊthode peut filtrer des Êvènements et Êviter via des conditions que des Êtapes suivantes se lancent", "port": "Port", "preferences_settings_subtitle": "GÊrer les prÊfÊrences de l'application", "preferences_settings_title": "PrÊfÊrences", @@ -1665,11 +1771,11 @@ "privacy": "Vie privÊe", "profile": "Profil", "profile_drawer_app_logs": "Journaux", + "profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Mode lecture seule activÊ. Faites un appui long sur l'image de l'utilisateur pour quitter.", "profile_image_of_user": "Image de profil de {user}", "profile_picture_set": "Photo de profil dÊfinie.", - "projection_type": "Type de projection", "public_album": "Album public", "public_share": "Partage public", "purchase_account_info": "Contributeur", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Statut de contributeur", "purchase_individual_title": "Utilisateur", "purchase_input_suggestion": "Si vous avez dÊjà une clÊ de produit, renseignez-la ci-dessous", + "purchase_license_subtitle": "Acheter Immich pour soutenir le dÊveloppement de ce service", "purchase_lifetime_description": "Achat à vie", "purchase_option_title": "OPTIONS D'ACHAT", "purchase_panel_info_1": "DÊvelopper Immich nÊcessite du temps et de l'Ênergie, et nous avons des ingÊnieurs qui travaillent à plein temps pour en faire le meilleur produit possible. Notre mission est de gÊnÊrer, pour les logiciels open source et les pratiques de travail Êthique, une source de revenus suffisante pour les dÊveloppeurs et de crÊer un Êcosystème respectueux de la vie privÊe grÃĸce a des alternatives crÊdibles aux services cloud peu scrupuleux.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# mÊdia rÊattribuÊ} other {# mÊdias rÊattribuÊs}} à une nouvelle personne", "reassing_hint": "Attribuer ces mÊdias à une personne existante", "recent": "RÊcent", + "recent_albums": "Albums rÊcents", "recent_searches": "Recherches rÊcentes", "recently_added": "RÊcemment ajoutÊ", - "recently_added_body": "Allez directement à ce que vous avez ajoutÊ rÊcemment dans une page dÊdiÊe.", - "recently_added_description": "Naviguez dans vos mÊdias, triÊs par date d'envoi dans Immich", "recently_added_page_title": "RÊcemment ajoutÊ", - "recently_added_title": "AjoutÊ rÊcemment", "recently_taken": "RÊcemment photographiÊ", + "recently_taken_page_title": "RÊcemment photographiÊ", "refresh": "Actualiser", "refresh_encoded_videos": "Actualiser les vidÊos encodÊes", "refresh_faces": "Actualiser les visages", "refresh_metadata": "Actualiser les mÊtadonnÊes", "refresh_thumbnails": "Actualiser les miniatures", "refreshed": "ActualisÊ", + "refreshes_every_file": "Actualise tous les fichiers (existants et nouveaux)", "refreshing_encoded_video": "Actualisation de la vidÊo encodÊe", "refreshing_faces": "Actualisation des visages", "refreshing_metadata": "Actualisation des mÊtadonnÊes", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Êtes-vous sÃģr de vouloir supprimer {count, plural, one {# mÊdia} other {# mÊdias}} de ce lien partagÊ ?", "remove_assets_title": "Supprimer les mÊdias ?", "remove_custom_date_range": "Supprimer la plage de date personnalisÊe", - "remove_filter": "Supprimer le filtre", + "remove_deleted_assets": "Supprimer les fichiers hors ligne", "remove_from_album": "Supprimer de l'album", "remove_from_album_action_prompt": "{count} supprimÊ(s) de l'album", "remove_from_favorites": "Supprimer des favoris", @@ -1756,22 +1863,29 @@ "remove_memory": "Supprimer le souvenir", "remove_photo_from_memory": "Supprimer la photo de ce souvenir", "remove_tag": "Supprimer l'Êtiquette", + "remove_url": "Supprimer l'URL", "remove_user": "Supprimer l'utilisateur", "removed_api_key": "ClÊ API supprimÊe : {name}", "removed_from_archive": "SupprimÊ de l'archive", "removed_from_favorites": "SupprimÊ des favoris", "removed_from_favorites_count": "{count, plural, one {# supprimÊ} other {# supprimÊs}} des favoris", "removed_memory": "Souvenir supprimÊ", + "removed_photo_from_memory": "Photo supprimÊe du souvenir", "removed_tagged_assets": "Étiquette supprimÊe de {count, plural, one {# mÊdia} other {# mÊdias}}", "rename": "Renommer", + "repair": "RÊparer", + "repair_no_results_message": "Les fichiers non importÊs ou absents s'afficheront ici", + "replace_with_upload": "Remplacer avec l'envoi", "repository": "DÊpôt", "require_password": "Demander le mot de passe", + "require_user_to_change_password_on_first_login": "Demander à l'utilisateur de changer son mot de passe lors de sa première connexion", "rescan": "Rescanner", "reset": "RÊinitialiser", "reset_password": "RÊinitialiser le mot de passe", "reset_people_visibility": "RÊinitialiser la visibilitÊ des personnes", "reset_pin_code": "RÊinitialiser le code PIN", "reset_pin_code_description": "Si vous avez oubliÊ votre code PIN, vous devez contacter l'administrateur du serveur pour le rÊinitialiser", + "reset_pin_code_success": "Code PIN rÊinitialisÊ avec succès", "reset_pin_code_with_password": "Vous pouvez toujours rÊinitialiser le code PIN avec votre mot de passe", "reset_sqlite": "RÊinitialiser la base de donnÊes SQLite", "reset_sqlite_clear_app_data": "Supprimer les donnÊes", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "RÊsolution de tous les doublons", "restore": "Restaurer", "restore_all": "Tout restaurer", + "restore_trash_action_prompt": "{count} restaurÊ de la corbeille", "restore_user": "Restaurer l'utilisateur", "restored_asset": "MÊdia restaurÊ", "resume": "Reprendre", "resume_paused_jobs": "Reprendre {count, plural, one {la tÃĸche en cours} other {les # tÃĸches en cours}}", + "retry_upload": "RÊessayer l'envoi", "review_duplicates": "Consulter les doublons", "review_large_files": "Consulter les fichiers volumineux", "role": "Rôle", @@ -1796,6 +1912,7 @@ "role_viewer": "Visionneur", "running": "En cours", "save": "Sauvegarder", + "save_to_gallery": "Enregistrer", "saved": "SauvegardÊ", "saved_api_key": "ClÊ API sauvegardÊe", "saved_profile": "Profil enregistrÊ", @@ -1806,19 +1923,16 @@ "scan": "Analyse", "scan_all_libraries": "Analyser toutes les bibliothèques", "scan_library": "Analyser", + "scan_settings": "Paramètres d'analyse", "scanning": "Analyse en cours", "scanning_for_album": "Recherche d'albums en cours...", - "screencast_mode_description": "Afficher à l'Êcran les indicateurs d'Êvènements de clavier et souris", - "screencast_mode_title": "Basculer le mode de diffusion d'Êcran", "search": "Recherche", "search_albums": "Rechercher des albums", - "search_by_context": "Recherche par contexte", + "search_by_context": "Rechercher par contexte", "search_by_description": "Recherche par description", "search_by_description_example": "RandonnÊe à Sapa", - "search_by_filename": "Recherche par nom du fichier ou extension", + "search_by_filename": "Rechercher par nom du fichier ou extension", "search_by_filename_example": "Exemple : IMG_1234.JPG ou PNG", - "search_by_full_path": "Recherche par chemin complet ou dossier", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - vous pouvez rechercher Projets, 3D, Impression, 2026, etc.", "search_by_ocr": "Recherche par Reconnaissance Optique de Caractères OCR", "search_by_ocr_example": "cafÊ latte", "search_camera_lens_model": "Chercher par modèle d'objectif...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "SÊlectionner une localisation", "search_filter_media_type": "Type de mÊdia", "search_filter_media_type_title": "SÊlectionner type de mÊdia", + "search_filter_ocr": "Recherche par Reconnaissance Optique de Caractères OCR", "search_filter_people_title": "SÊlectionner une personne", "search_filter_star_rating": "Note par Êtoiles", "search_filter_tags_title": "SÊlectionner des Êtiquettes", @@ -1847,14 +1962,26 @@ "search_no_people": "Aucune personne", "search_no_people_named": "Aucune personne nommÊe ÂĢ {name} Âģ", "search_no_result": "Aucun rÊsultat trouvÊ, essayez un autre terme de recherche ou une autre combinaison", - "search_options": "Options de recherche", + "search_options": "Rechercher une option", + "search_page_categories": "CatÊgories", + "search_page_motion_photos": "Photos avec mouvement", + "search_page_no_objects": "Aucune information disponible sur les objets", + "search_page_no_places": "Aucune information disponible sur la localisation", + "search_page_screenshots": "Captures d'Êcran", "search_page_search_photos_videos": "Rechercher dans vos photos et vidÊos", + "search_page_selfies": "Autoportraits (Selfies)", + "search_page_things": "Objets", "search_page_view_all_button": "Voir tout", + "search_page_your_activity": "Votre activitÊ", + "search_page_your_map": "Votre carte", "search_people": "Rechercher une personne", "search_places": "Rechercher un lieu", - "search_rating": "Recherche par Êvaluation...", - "search_settings": "Rechercher dans les paramètres", + "search_rating": "Chercher par Êvaluation...", + "search_result_page_new_search_hint": "Nouvelle recherche", + "search_settings": "Paramètres de recherche", "search_state": "Rechercher par Êtat/rÊgion...", + "search_suggestion_list_smart_search_hint_1": "La recherche intelligente est activÊe par dÊfaut. Pour rechercher des mÊtadonnÊes, utilisez la syntaxe suivante ", + "search_suggestion_list_smart_search_hint_2": "m:votre-terme-de-recherche", "search_tags": "Recherche d'Êtiquettes...", "search_timezone": "Rechercher par fuseau horaire...", "search_type": "Rechercher par type", @@ -1868,6 +1995,7 @@ "select_albums": "SÊlectionnez des albums", "select_all": "Tout sÊlectionner", "select_all_duplicates": "SÊlectionner tous les doublons", + "select_all_in": "Tout sÊlectionner dans {group}", "select_avatar_color": "SÊlectionner la couleur de l'avatar", "select_count": "{count, plural, one {SÊlectionner #} other {SÊlectionner #}}", "select_cutoff_date": "SÊlectionnez la date limite", @@ -1875,13 +2003,14 @@ "select_featured_photo": "SÊlectionner la photo de profil de cette personne", "select_from_computer": "SÊlectionner à partir de l'ordinateur", "select_keep_all": "Choisir de tout garder", + "select_library_owner": "SÊlectionner le propriÊtaire de la bibliothèque", "select_new_face": "SÊlectionner un nouveau visage", "select_people": "SÊlectionnez des personnes", "select_person": "SÊlectionnez une personne", "select_person_to_tag": "SÊlectionner une personne à identifier", "select_photos": "SÊlectionner les photos", - "select_quality": "SÊlectionnez la qualitÊ", "select_trash_all": "Choisir de tout supprimer", + "select_user_for_sharing_page_err_album": "Échec de la crÊation de l'album", "selected": "SÊlectionnÊ", "selected_count": "{count, plural, one {# sÊlectionnÊ} other {# sÊlectionnÊs}}", "selected_gps_coordinates": "CoordonnÊes GPS sÊlectionnÊes", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Charger l'image originale", "setting_image_viewer_preview_subtitle": "Activer pour charger une image de rÊsolution moyenne. DÊsactiver pour charger directement l'original ou utiliser uniquement la miniature.", "setting_image_viewer_preview_title": "Charger l'image d'aperçu", + "setting_image_viewer_title": "Images", "setting_languages_apply": "Appliquer", "setting_languages_subtitle": "Changer la langue de l'application", + "setting_notifications_notify_failures_grace_period": "Notifier les Êchecs de la sauvegarde en arrière-plan : {duration}", + "setting_notifications_notify_hours": "{count} heures", + "setting_notifications_notify_immediately": "immÊdiatement", "setting_notifications_notify_minutes": "{count} minutes", + "setting_notifications_notify_never": "jamais", "setting_notifications_notify_seconds": "{count} secondes", + "setting_notifications_single_progress_subtitle": "Informations dÊtaillÊes sur la progression de l'envoi par mÊdia", + "setting_notifications_single_progress_title": "Afficher la progression du dÊtail de la sauvegarde en arrière-plan", "setting_notifications_subtitle": "Ajustez vos prÊfÊrences de notification", + "setting_notifications_total_progress_subtitle": "Progression globale de l'envoi (effectuÊ/total des mÊdias)", + "setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan", "setting_video_viewer_auto_play_subtitle": "Lancer automatiquement la lecture des vidÊos lorsqu’elles sont ouvertes", "setting_video_viewer_auto_play_title": "Lecture automatique des vidÊos", "setting_video_viewer_looping_title": "Boucle", "setting_video_viewer_original_video_subtitle": "Lors de la diffusion d'une vidÊo depuis le serveur, lisez l'original mÃĒme si un transcodage est disponible. Cela peut entraÃŽner de la mise en mÊmoire tampon. Les vidÊos disponibles localement sont lues en qualitÊ d'origine, quel que soit ce paramètre.", "setting_video_viewer_original_video_title": "Forcer la vidÊo originale", "settings": "Paramètres", + "settings_require_restart": "Veuillez redÊmarrer Immich pour appliquer ce paramètre", "settings_saved": "Paramètres sauvegardÊs", "setup_pin_code": "DÊfinir un code PIN", "share": "Partager", + "share_action_prompt": "{count} ÊlÊments partagÊs", + "share_add_photos": "Ajouter des photos", + "share_assets_selected": "{count} sÊlectionnÊ(s)", "share_dialog_preparing": "PrÊparation...", "share_link": "Partager le lien", - "share_original": "Utiliser l'originale (grand)", - "share_preview": "Utiliser la miniature (petite)", - "share_quality_body": "Appuyez longtemps sur le bouton partager pour choisir la qualitÊ d'image avant de partager.", - "share_quality_title": "Choisissez votre qualitÊ d'envoi", "shared": "PartagÊ", "shared_album_activities_input_disable": "Les commentaires sont dÊsactivÊs", "shared_album_activity_remove_content": "Souhaitez-vous supprimer cette activitÊ ?", "shared_album_activity_remove_title": "Supprimer l'activitÊ", "shared_album_section_people_action_error": "Erreur lors de la suppression", + "shared_album_section_people_action_leave": "Supprimer l'utilisateur de l'album", + "shared_album_section_people_action_remove_user": "Supprimer l'utilisateur de l'album", "shared_album_section_people_title": "PERSONNES", "shared_by": "PartagÊ par", "shared_by_user": "PartagÊ par {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} EnvoyÊ(s)", "shared_link_app_bar_title": "Liens partagÊs", "shared_link_clipboard_copied_massage": "CopiÊ dans le presse-papier", + "shared_link_clipboard_text": "Lien : {link}\nMot de passe : {password}", "shared_link_create_error": "Erreur pendant la crÊation du lien partagÊ", - "shared_link_custom_url_description": "AccÊder à ce lien partagÊ avec un nom d'URL personnalisÊe", - "shared_link_custom_url_title": "URL personnalisÊe", - "shared_link_custom_url_warning": "Attention: un nom d'URL personnalisÊ contenant un slash (/) pourrait ne pas se comporter comme prÊvu.", + "shared_link_custom_url_description": "AccÊder à ce lien partagÊ avec une URL personnalisÊe", "shared_link_edit_description_hint": "Saisir la description du partage", "shared_link_edit_expire_after_option_day": "1 jour", "shared_link_edit_expire_after_option_days": "{count} jours", @@ -1984,6 +2123,12 @@ "shared_with_partner": "PartagÊ avec {partner}", "sharing": "Partage", "sharing_enter_password": "Veuillez saisir le mot de passe pour visualiser cette page.", + "sharing_page_album": "Albums partagÊs", + "sharing_page_description": "CrÊez des albums partagÊs pour partager des photos et des vidÊos avec les personnes de votre rÊseau.", + "sharing_page_empty_list": "LISTE VIDE", + "sharing_sidebar_description": "Afficher un lien vers Partager dans la barre latÊrale", + "sharing_silver_appbar_create_shared_album": "CrÊer un album partagÊ", + "sharing_silver_appbar_share_partner": "Partager avec un partenaire", "shift_to_permanent_delete": "appuyez sur ⇧ pour supprimer dÊfinitivement le mÊdia", "show_album_options": "Afficher les options de l'album", "show_albums": "Montrer les albums", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Afficher dans la vue chronologique", "show_in_timeline_setting_description": "Afficher les photos et vidÊos de cet utilisateur dans votre vue chronologique", "show_keyboard_shortcuts": "Afficher les raccourcis clavier", - "show_less": "Voir moins", "show_metadata": "Afficher les mÊtadonnÊes", - "show_more_fields": "{count, plural, one {Voir # champ de plus} other {Voir # champs de plus}}", "show_or_hide_info": "Afficher ou masquer les informations", "show_password": "Afficher le mot de passe", "show_person_options": "Afficher les options de personnes", @@ -2005,7 +2148,6 @@ "show_schema": "Afficher le schÊma", "show_search_options": "Afficher les options de recherche", "show_shared_links": "Afficher les liens partagÊs", - "show_slideshow_metadata_overlay": "Afficher les infos de l'image en superposition", "show_slideshow_transition": "Afficher la transition du diaporama", "show_supporter_badge": "Badge de contributeur", "show_supporter_badge_description": "Afficher le badge de contributeur", @@ -2017,25 +2159,18 @@ "sign_out": "DÊconnexion", "sign_up": "S'enregistrer", "size": "Taille", - "skip": "Passer", "skip_to_content": "Passer", "skip_to_folders": "Passer vers les dossiers", "skip_to_tags": "Passer vers les Êtiquettes", "slideshow": "Diaporama", - "slideshow_body": "Relaxez-vous et regardez vos photos dans un diaporama en plein Êcran.", - "slideshow_metadata_overlay_mode": "Contenu en superposition", - "slideshow_metadata_overlay_mode_description_only": "Seulement la description", - "slideshow_metadata_overlay_mode_full": "Tout", "slideshow_repeat": "RÊpÊter le diaporama", "slideshow_repeat_description": "Reboucler au dÊbut lorsque le diaporama se termine", "slideshow_settings": "Paramètres du diaporama", - "slideshow_title": "Diaporama", - "smart_album": "Album intelligent", - "some_assets_already_have_a_location_warning": "Certains mÊdias sÊlectionnÊs ont dÊjà une localisation", "sort_albums_by": "Trier les albums par...", "sort_created": "Date de crÊation", "sort_items": "Nombre d'ÊlÊments", "sort_modified": "Date de modification", + "sort_newest": "Photo la plus rÊcente", "sort_oldest": "Photo la plus ancienne", "sort_people_by_similarity": "Trier les personnes par similitude", "sort_recent": "Photo la plus rÊcente", @@ -2044,6 +2179,7 @@ "stack": "Empiler", "stack_action_prompt": "{count} empilÊ(s)", "stack_duplicates": "Empiler les doublons", + "stack_select_one_photo": "SÊlectionnez une photo principale pour la pile", "stack_selected_photos": "Empiler les photos sÊlectionnÊes", "stacked_assets_count": "{count, plural, one {# mÊdia empilÊ} other {# mÊdias empilÊs}}", "stacktrace": "Trace de la pile", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "La date de dÊbut doit ÃĒtre avant la date de fin", "state": "RÊgion", "status": "Statut", - "step_delete": "Supprimer l'Êtape", - "step_delete_confirm": "Êtes-vous sÃģr de vouloir supprimer cette Êtape ?", - "step_details": "DÊtails de l'Êtape", - "steps": "Étapes", "stop_casting": "ArrÃĒter la diffusion", "stop_motion_photo": "Photo Stop Motion", "stop_photo_sharing": "ArrÃĒter de partager vos photos ?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Inverser la direction de fusion", "sync": "Synchroniser", "sync_albums": "Synchroniser dans des albums", + "sync_albums_manual_subtitle": "Synchroniser toutes les vidÊos et photos envoyÊes dans les albums sÊlectionnÊs", "sync_local": "Synchronisation locale", "sync_remote": "Synchronisation à distance", "sync_status": "Statut de synchronisation", "sync_status_subtitle": "Consulter et gÊrer le système de synchronisation", "sync_upload_album_setting_subtitle": "CrÊez et envoyez vos photos et vidÊos dans les albums sÊlectionnÊs sur Immich", - "system_theme": "Thème du système", - "system_theme_command_description": "Utiliser le thème du système ({value})", "tag": "Étiquette", "tag_assets": "Étiqueter les mÊdias", "tag_created": "Étiquette crÊÊe : {tag}", "tag_face": "Étiqueter le visage", "tag_feature_description": "Parcourir les photos et vidÊos groupÊes par thèmes logiques", + "tag_not_found_question": "Vous ne trouvez pas une Êtiquette ? CrÊer une nouvelle Êtiquette.", "tag_people": "Étiqueter les personnes", "tag_updated": "Étiquette mise à jour : {tag}", "tagged_assets": "Étiquette ajoutÊe à {count, plural, one {# mÊdia} other {# mÊdias}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Nombre de mÊdias par ligne ({count})", "theme_setting_colorful_interface_subtitle": "Appliquer la couleur principale sur les surfaces d'arrière-plan.", "theme_setting_colorful_interface_title": "Interface colorÊe", + "theme_setting_image_viewer_quality_subtitle": "Ajustez la qualitÊ de la visionneuse d'images dÊtaillÊes", + "theme_setting_image_viewer_quality_title": "QualitÊ de la visualisation des images", "theme_setting_primary_color_subtitle": "Choisissez une couleur pour les actions principales et les accentuations.", "theme_setting_primary_color_title": "Couleur principale", "theme_setting_system_primary_color_title": "Utiliser la couleur du système", "theme_setting_system_theme_switch": "Automatique (suivre les paramètres du système)", + "theme_setting_theme_subtitle": "Choisissez le thème de l'application", + "theme_setting_three_stage_loading_subtitle": "Le chargement en trois Êtapes peut amÊliorer les performances de chargement, mais entraÃŽne une augmentation significative de la charge du rÊseau", + "theme_setting_three_stage_loading_title": "Activer le chargement en trois Êtapes", "then": "Ensuite", "they_will_be_merged_together": "Elles seront fusionnÊes ensemble", "third_party_resources": "Ressources tierces", @@ -2131,17 +2268,23 @@ "trash_all": "Tout supprimer", "trash_count": "Corbeille {count, number}", "trash_delete_asset": "Mettre à la corbeille/Supprimer un mÊdia", + "trash_emptied": "Corbeille vidÊe", "trash_no_results_message": "Les photos et vidÊos supprimÊes s'afficheront ici.", "trash_page_delete_all": "Tout supprimer", + "trash_page_empty_trash_dialog_content": "Voulez-vous vider les mÊdias de la corbeille ? Ces objets seront dÊfinitivement retirÊs d'Immich", "trash_page_info": "Les mÊdias mis à la corbeille seront dÊfinitivement supprimÊs au bout de {days} jours", + "trash_page_no_assets": "Aucun ÊlÊment dans la corbeille", + "trash_page_restore_all": "Tout restaurer", + "trash_page_select_assets_btn": "SÊlectionner les ÊlÊments", + "trash_page_title": "Corbeille ({count})", "trashed_items_will_be_permanently_deleted_after": "Les ÊlÊments dans la corbeille seront supprimÊs dÊfinitivement après {days, plural, one {# jour} other {# jours}}.", "trigger": "DÊclencheur", - "trigger_asset_metadata_extraction": "Extraction des mÊtadonnÊes du mÊdia", - "trigger_asset_metadata_extraction_description": "DÊclenchÊ quand les mÊtadonnÊes EXIF d'un mÊdia sont extraites", - "trigger_asset_uploaded": "TÊlÊversement du mÊdia", + "trigger_asset_uploaded": "MÊdia tÊlÊversÊ", "trigger_asset_uploaded_description": "DÊclenchÊ lorsqu'un nouveau mÊdia est tÊlÊversÊ", + "trigger_description": "Un ÊvÊnement qui active le flux de traitement", "trigger_person_recognized": "Personne reconnue", - "trigger_person_recognized_description": "DÊclenchÊ lorsqu'une personne est reconnue", + "trigger_person_recognized_description": "DÊclenchÊ lorsqu'une personne est dÊtectÊe", + "trigger_type": "Type de dÊclencheur", "troubleshoot": "DÊpannage", "type": "Type", "unable_to_change_pin_code": "Impossible de changer le code PIN", @@ -2157,7 +2300,6 @@ "unknown": "Inconnu", "unknown_country": "Pays non connu", "unknown_date": "Date inconnue", - "unknown_schema": "SchÊma inconnu", "unknown_year": "AnnÊe inconnue", "unlimited": "IllimitÊ", "unlink_motion_video": "DÊtacher la photo animÊe", @@ -2170,20 +2312,23 @@ "unsaved_change": "Modification non enregistrÊe", "unselect_all": "Annuler la sÊlection", "unselect_all_duplicates": "DÊsÊlectionner tous les doublons", + "unselect_all_in": "Tout dÊsÊlectionner dans {group}", "unstack": "DÊpiler", "unstack_action_prompt": "{count} dÊpilÊ(s)", "unstacked_assets_count": "{count, plural, one {# mÊdia dÊpilÊ} other {# mÊdias dÊpilÊs}}", "unsupported_field_type": "Type de champ non supportÊ", "unsupported_file_type": "Le fichier {file} ne peut pas ÃĒtre tÊlÊversÊ car son type {type} n'est pas supportÊ.", "untagged": "Sans Êtiquette", + "untitled_workflow": "Flux de traitement sans titre", "up_next": "Suite", "update_location_action_prompt": "Mettre à jour la localisation des {count} mÊdias sÊlectionnÊs avec :", "updated_at": "Mis à jour à", "updated_password": "Mot de passe mis à jour", "upload": "Envoyer", "upload_concurrency": "Envois simultanÊs", - "upload_day_count": "{date} : {count, plural, one {# envoi} other {# envois}}", "upload_details": "DÊtails des envois", + "upload_dialog_info": "Voulez-vous sauvegarder la sÊlection vers le serveur ?", + "upload_dialog_title": "Envoyer le mÊdia", "upload_error_with_count": "Erreur de chargement pour {count, plural, one {# mÊdia} other {# mÊdias}}", "upload_errors": "L'envoi s'est complÊtÊ avec {count, plural, one {# erreur} other {# erreurs}}. RafraÃŽchissez la page pour voir les nouveaux mÊdias envoyÊs.", "upload_finished": "Envoi fini", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Erreurs", "upload_status_uploaded": "EnvoyÊ", "upload_success": "Envoi rÊussi. RafraÃŽchissez la page pour voir les nouveaux mÊdias envoyÊs.", - "upload_to_album_body": "Pour les utilisateurs qui n'utilise pas la fonctionnalitÊ de mise en ligne manuelle, vous pouvez maintenant ajouter vos photos locales directement dans un album, plus besoin de mettre en ligne puis de dÊplacer dans un album après.", - "upload_to_album_title": "Mettre en ligne directement dans un album", "upload_to_immich": "Envoyer vers Immich ({count})", "uploading": "Envoi", "uploading_media": "Envoi du mÊdia", - "uploads": "Envois", - "uploads_count": "{count, plural, one {# envoi} other {# envois}}", "url": "URL", "usage": "Utilisation", "use_biometric": "Utiliser l'authentification biomÊtrique", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Formate les dates, heures et nombres en fonction des paramètres rÊgionaux de votre navigateur", "use_current_connection": "Utiliser le rÊseau actuel", "use_custom_date_range": "Utilisez une plage de date personnalisÊe à la place", - "use_template": "Utiliser un modèle", "user": "Utilisateur", "user_has_been_deleted": "Cet utilisateur à ÊtÊ supprimÊ.", "user_id": "ID Utilisateur", @@ -2217,6 +2357,7 @@ "user_privacy": "Vie privÊe pour l'utilisateur", "user_purchase_settings": "Achat", "user_purchase_settings_description": "GÊrer votre achat", + "user_role_set": "DÊfinir {user} comme {role}", "user_usage_detail": "DÊtail de l'utilisation des utilisateurs", "user_usage_stats": "Statistiques d'utilisation du compte", "user_usage_stats_description": "Voir les statistiques d'utilisation du compte", @@ -2226,6 +2367,7 @@ "utilities": "Utilitaires", "validate": "Valider", "validate_endpoint_error": "Merci d'entrer un lien valide", + "validation_error": "Erreur de validation", "variables": "Variables", "version": "Version", "version_announcement_closing": "Ton ami, Alex", @@ -2235,9 +2377,9 @@ "video": "VidÊo", "video_hover_setting": "Lire la miniature des vidÊos au survol", "video_hover_setting_description": "Lancer la prÊvisualisation vidÊo au survol. Si dÊsactivÊ, la lecture peut quand mÃĒme ÃĒtre dÊmarrÊe en survolant le bouton Play.", - "video_quality": "QualitÊ vidÊo", "videos": "VidÊos", "videos_count": "{count, plural, one {# VidÊo} other {# VidÊos}}", + "videos_only": "VidÊos uniquement", "view": "Voir", "view_album": "Afficher l'album", "view_all": "Voir tout", @@ -2246,29 +2388,29 @@ "view_details": "Voir les dÊtails", "view_in_timeline": "Voir dans la vue chronologique", "view_link": "Voir le lien", + "view_links": "Voir les liens", "view_name": "Vue", "view_next_asset": "Voir le mÊdia suivant", "view_previous_asset": "Voir le mÊdia prÊcÊdent", "view_qr_code": "Voir le QR code", "view_similar_photos": "Afficher les photos similaires", "view_stack": "Afficher la pile", + "view_user": "Voir l'utilisateur", "viewer_remove_from_stack": "Retirer de la pile", + "viewer_stack_use_as_main_asset": "Utiliser comme ÊlÊment principal", + "viewer_unstack": "DÊpiler", "visibility": "VisibilitÊ", "visibility_changed": "VisibilitÊ changÊe pour {count, plural, one {# personne} other {# personnes}}", "visual": "Visuel", + "visual_builder": "Constructeur visuel", "waiting": "En attente", "waiting_count": "En attente : {count}", "warning": "Attention", "week": "Semaine", "welcome": "Bienvenue", "welcome_to_immich": "Bienvenue sur Immich", - "whats_new": "Quoi de neuf", - "whats_new_settings_subtitle": "DÊcouvrez les nouveautÊs dans Immich", - "whats_new_version": "Version {version}", - "when": "Quand", "width": "Largeur", "wifi_name": "Nom du rÊseau wifi", - "workflow": "Flux de travail", "workflow_delete_prompt": "Êtes-vous sÃģr de vouloir supprimer ce flux de traitement ?", "workflow_deleted": "Flux de traitement supprimÊ", "workflow_description": "Description du flux de traitement", @@ -2278,17 +2420,17 @@ "workflow_name": "Nom du flux de traitement", "workflow_navigation_prompt": "Êtes-vous sÃģr de vouloir quitter sans enregistrer vos changements ?", "workflow_summary": "RÊsumÊ du flux de traitement", - "workflow_templates": "Modèles de flux de traitement", "workflow_update_success": "Flux de traitement mis à jour avec succès", + "workflow_updated": "Flux de traitement mis à jour", "workflows": "Flux de traitement", "workflows_help_text": "Les flux de traitement automatisent des actions sur vos mÊdias, en se basant sur des dÊclencheurs et des filtres", "wrong_pin_code": "Code PIN erronÊ", - "x_of_total": "{x}/{total}", "year": "AnnÊe", "years_ago": "Il y a {years, plural, one {# an} other {# ans}}", "yes": "Oui", "you_dont_have_any_shared_links": "Vous n'avez aucun lien partagÊ", "your_wifi_name": "Nom du rÊseau wifi", "zero_to_clear_rating": "Appuyez sur 0 pour effacer la notation du mÊdia", - "zoom_image": "Zoomer" + "zoom_image": "Zoomer", + "zoom_to_bounds": "Zoom sur la zone" } diff --git a/i18n/ga.json b/i18n/ga.json index e64ba9b16f..d493e3fe23 100644 --- a/i18n/ga.json +++ b/i18n/ga.json @@ -10,31 +10,39 @@ "active": "Gníomhach", "active_count": "Gníomhach: {count}", "activity": "Gníomhaíocht", + "activity_changed": "TÃĄ an ghníomhaíocht {enabled, select, true {enabled} other {disabled}}", "add": "Cuir leis", "add_a_description": "Cuir cur síos leis", "add_a_location": "Cuir suíomh leis", "add_a_name": "Cuir ainm leis", "add_a_title": "Cuir teideal leis", "add_action": "Cuir gníomh leis", + "add_action_description": "CliceÃĄil chun gníomh a chur leis le dÊanamh", "add_assets": "Cuir sÃŗcmhainní leis", "add_birthday": "Cuir breithlÃĄ leis", "add_endpoint": "Cuir críochphointe leis", "add_exclusion_pattern": "Cuir patrÃēn eisiaimh leis", + "add_filter": "Cuir scagaire leis", + "add_filter_description": "CliceÃĄil chun coinníoll scagaire a chur leis", "add_location": "Cuir suíomh leis", + "add_more_users": "Cuir níos mÃŗ ÃēsÃĄideoirí leis", "add_partner": "Cuir comhphÃĄirtí leis", + "add_path": "Cuir cosÃĄn leis", "add_photos": "Cuir grianghraif leis", - "add_step": "Cuir cÊim leis", "add_tag": "Cuir clib leis", "add_to": "Cuir leâ€Ļ", "add_to_album": "Cuir leis an albam", "add_to_album_bottom_sheet_added": "Curtha le {album}", "add_to_album_bottom_sheet_already_exists": "Cheana fÊin i {album}", "add_to_album_bottom_sheet_some_local_assets": "Níorbh fhÊidir roinnt sÃŗcmhainní ÃĄitiÃēla a chur leis an albam", + "add_to_album_toggle": "Athraigh rogha do {album}", "add_to_albums": "Cuir le halbaim", "add_to_albums_count": "Cuir le halbaim ({count})", "add_to_bottom_bar": "Cuir le", + "add_to_shared_album": "Cuir le halbam comhroinnte", "add_upload_to_stack": "Cuir uaslÃŗdÃĄil leis an gcruach", "add_url": "Cuir URL leis", + "add_workflow_step": "Cuir cÊim sreabha oibre leis", "added_to_archive": "Curtha leis an gcartlann", "added_to_favorites": "Curtha le rogha pearsanta", "added_to_favorites_count": "Cuireadh {count, number} le mo rogha pearsanta", @@ -73,7 +81,6 @@ "cron_expression_description": "Socraigh an t-eatramh scanadh ag baint ÃēsÃĄide as an bhformÃĄid cron. Le haghaidh tuilleadh eolais fÊach ar m.sh. Crontab Guru", "cron_expression_presets": "RÊamhshocruithe lÊirithe Cron", "disable_login": "Díchumasaigh logÃĄil isteach", - "download_csv": "ÍoslÃŗdÃĄil CSV", "duplicate_detection_job_description": "Rith foghlaim meaisín ar shÃŗcmhainní chun íomhÃĄnna comhchosÃēla a bhrath. Braitheann sÊ ar Chuardach Cliste", "exclusion_pattern_description": "Le patrÃēin eisiaimh, is fÊidir leat neamhaird a dhÊanamh de chomhaid agus fillteÃĄin agus tÃē ag scanadh do leabharlann. TÃĄ sÊ seo ÃēsÃĄideach mÃĄ tÃĄ fillteÃĄin agat ina bhfuil comhaid nach mian leat a allmhairiÃē, amhail comhaid RAW.", "export_config_as_json_description": "ÍoslÃŗdÃĄil cumraíocht reatha an chÃŗrais mar chomhad JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "CÃĄilíocht mionsamhlacha Ãŗ 1-100. Is airde an caighdeÃĄn is fearr, ach cruthaíonn sÊ comhaid níos mÃŗ agus d'fhÊadfadh sÊ freagrÃēlacht an aip a laghdÃē.", "image_thumbnail_title": "Socruithe Mionsamhail", "import_config_from_json_description": "Cumraíocht chÃŗrais a allmhairiÃē trí chomhad cumraíochta JSON a uaslÃŗdÃĄil", - "integrity_checks_checksum_files": "Comhaid seiceÃĄla", - "integrity_checks_checksum_files_description": "Cumraigh an tseiceÃĄil suime seiceÃĄla", - "integrity_checks_checksum_files_enable_description": "Cumasaigh an tseiceÃĄil suime seiceÃĄla", - "integrity_checks_checksum_files_percentage_limit": "Teorainn cÊatadÃĄin", - "integrity_checks_checksum_files_percentage_limit_description": "Cumraigh an cÊatadÃĄn uasta idir 0.01 agus 1 do cÊ mhÊad ba chÃŗir don tseiceÃĄil suime seiceÃĄla a rith gach eatramh.", - "integrity_checks_checksum_files_time_limit": "Teorainn ama", - "integrity_checks_checksum_files_time_limit_description": "Cumraigh an fad uasta ar cheart don tseiceÃĄil suime seiceÃĄla a bheith ar siÃēl le gach eatramh. (ms)", - "integrity_checks_missing_files": "Comhaid ar iarraidh", - "integrity_checks_missing_files_description": "Cumraigh an minicíocht agus cumasaigh nÃŗ díchumasaigh an seiceÃĄil comhad atÃĄ ar iarraidh", - "integrity_checks_missing_files_enable_description": "Cumasaigh an seiceÃĄil comhad atÃĄ ar iarraidh", - "integrity_checks_settings": "SeiceÃĄlacha ionracais", - "integrity_checks_settings_description": "Bainistigh eatraimh seiceÃĄlacha slÃĄine", - "integrity_checks_untracked_files": "Comhaid neamhrianaithe", - "integrity_checks_untracked_files_description": "Cumraigh an minicíocht agus cumasaigh nÃŗ díchumasaigh an seiceÃĄil comhad neamhrianaithe", - "integrity_checks_untracked_files_enable_description": "Cumasaigh an seiceÃĄil comhad neamhrianaithe", "job_concurrency": "comhthrÃĄthacht {job}", "job_created": "Post cruthaithe", "job_not_concurrency_safe": "Níl an post seo sÃĄbhÃĄilte le haghaidh comhuaineachta.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Cumasaigh cuardach cliste", "machine_learning_smart_search_enabled_description": "Mura bhfuil sÊ sin ar fÃĄil, ní dhÊanfar íomhÃĄnna a ionchÃŗdÃē le haghaidh cuardaigh chliste.", "machine_learning_url_description": "URL an fhreastalaí foghlama meaisín. MÃĄ chuirtear níos mÃŗ nÃĄ URL amhÃĄin ar fÃĄil, dÊanfar iarracht ar gach freastalaí ceann ag an am go dtí go bhfreagrÃŗidh ceann acu go rathÃēil, in ord Ãŗn gcÊad cheann go dtí an ceann deireanach. DÊanfar neamhaird shealadach ar fhreastalaithe nach bhfreagrÃŗidh go dtí go mbeidh siad ar líne arís.", - "maintenance_backup_management": "Bainistíocht chÃēltaca", "maintenance_delete_backup": "Scrios CÃēltaca", "maintenance_delete_backup_description": "Scriosfar an comhad seo go neamh-inchÃēlghairthe.", "maintenance_delete_error": "Theip ar an gcÃēltaca a scriosadh.", - "maintenance_integrity_check": "SeiceÃĄil", - "maintenance_integrity_check_all": "SeiceÃĄil Gach Rud", - "maintenance_integrity_checksum_mismatch": "Mí-chomhoiriÃēnacht suime seiceÃĄla", - "maintenance_integrity_checksum_mismatch_description": "Comhaid nach bhfuil an tsuim seiceÃĄla ar an diosca ag teacht leis an tsuim seiceÃĄla atÃĄ stÃŗrÃĄilte ag Immich ina bhunachar sonraí.", - "maintenance_integrity_checksum_mismatch_job": "SeiceÃĄil le haghaidh mí-oiriÃēnuithe suime seiceÃĄla", - "maintenance_integrity_checksum_mismatch_refresh_job": "Athnuachan tuairiscí mí-oiriÃēnachta suime seiceÃĄla", - "maintenance_integrity_missing_file": "Comhaid ar Iarraidh", - "maintenance_integrity_missing_file_description": "Comhaid atÃĄ rianaithe ag Immich ina bhunachar sonraí ach nach bhfuil ar fÃĄil ar an gcÃŗras comhad.", - "maintenance_integrity_missing_file_job": "SeiceÃĄil le haghaidh comhaid atÃĄ ar iarraidh", - "maintenance_integrity_missing_file_refresh_job": "Athnuachan tuairiscí ar chomhaid atÃĄ ar iarraidh", - "maintenance_integrity_report": "TuarascÃĄil Ionracais", - "maintenance_integrity_untracked_file": "Comhaid Gan RianÃē", - "maintenance_integrity_untracked_file_description": "Comhaid in eolairí Immich nach bhfuil aon taifead ag Immich orthu.", - "maintenance_integrity_untracked_file_job": "SeiceÃĄil le haghaidh comhaid neamhrianaithe", - "maintenance_integrity_untracked_file_refresh_job": "Athnuachan tuarascÃĄlacha comhad neamhrianaithe", "maintenance_restore_backup": "AthchÃŗirigh CÃēltaca", "maintenance_restore_backup_description": "Scriosfar agus athchÃŗireofar Immich Ãŗn gcÃēltaca roghnaithe. CruthÃŗfar cÃēltaca sula leanfar ar aghaidh.", "maintenance_restore_backup_different_version": "Cruthaíodh an cÃēltaca seo le leagan difriÃēil de Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Cumasaigh fÃŗgraí ríomhphoist", "notification_settings": "Socruithe FÃŗgra", "notification_settings_description": "Bainistigh socruithe fÃŗgraí, lena n-ÃĄirítear ríomhphost", - "oauth_allow_insecure_requests": "Ceadaigh iarratais neamhshÃĄbhÃĄilte", - "oauth_allow_insecure_requests_description": "RABHADH: Díchumasaíonn sÊ seo bailíochtÃē teastais TLS le haghaidh iarratais OAuth agus d’fhÊadfadh sÊ go mbeadh tÃē nochtaithe d’ionsaithe MITM.", "oauth_auto_launch": "Seoladh uathoibríoch", "oauth_auto_launch_description": "Tosaigh sreabhadh logÃĄla isteach OAuth go huathoibríoch nuair a dhÊantar nascleanÃēint chuig an leathanach logÃĄla isteach", "oauth_auto_register": "ClÃĄrÃē uathoibríoch", @@ -300,11 +274,9 @@ "oauth_button_text": "TÊacs cnaipe", "oauth_client_secret_description": "Riachtanach do chliant rÃēnda, nÃŗ mura dtacaítear le PKCE (Eochair ChruthÃēnais le haghaidh MalartÃē CÃŗd) do chliant poiblí.", "oauth_enable_description": "LogÃĄil isteach le OAuth", - "oauth_end_session_url_description": "Athsheol an t-ÃēsÃĄideoir chuig an URI seo nuair a logÃĄlann siad amach.", "oauth_mobile_redirect_uri": "URI atreoraithe soghluaiste", "oauth_mobile_redirect_uri_override": "SÃĄrÃē URI atreoraithe soghluaiste", "oauth_mobile_redirect_uri_override_description": "Cumasaigh nuair nach gceadaíonn solÃĄthraí OAuth URI soghluaiste, mar shampla ''{callback}''", - "oauth_prompt_description": "ParaimÊadar leid (m.sh. roghnaigh_cuntas, logÃĄil isteach, toiliÃē)", "oauth_role_claim": "Éileamh RÃŗil", "oauth_role_claim_description": "Deonaigh rochtain riarthÃŗra go huathoibríoch bunaithe ar lÃĄithreacht an Êilimh seo. FÊadfaidh 'ÃēsÃĄideoir' nÃŗ 'riarthÃŗir' a bheith san Êileamh.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Ag athnuachan na leabharlanna go lÊir", "registration": "ClÃĄrÃē RiarachÃĄin", "registration_description": "Ós rud Ê gur tusa an chÊad ÃēsÃĄideoir ar an gcÃŗras, sannfar an RiarthÃŗir duit agus beidh tÃē freagrach as tascanna riarachÃĄin, agus cruthÃŗidh tÃē fÊin ÃēsÃĄideoirí breise.", - "release_channel_release_candidate": "IarrthÃŗir scaoilte", - "release_channel_stable": "Cobhsaí", "remove_failed_jobs": "Bain poist theip orthu", "require_password_change_on_login": "Éiligh ar an ÃēsÃĄideoir an focal faire a athrÃē ar an gcÊad logÃĄil isteach", "reset_settings_to_default": "Athshocraigh socruithe go dtí na socruithe rÊamhshocraithe", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Ní bhaineann sÊ ach le VAAPI agus QSV. Socraíonn sÊ an nÃŗd dri a ÃēsÃĄidtear le haghaidh traschÃŗdÃē crua-earraí.", "transcoding_preset_preset": "RÊamhshocrÃē (-rÊamhshocrÃē)", "transcoding_preset_preset_description": "Luas comhbhrÃēite. Gineann rÊamhshocruithe níos moille comhaid níos lÃē, agus mÊadaíonn siad cÃĄilíocht nuair a bhíonn rÃĄta giotÃĄn ÃĄirithe ÃĄ spriocdhíriÃē. Ní thugann VP9 aird ar luasanna os cionn 'níos tapÃēla'.", - "transcoding_realtime": "TraschÃŗdÃē Fíor-Ama [TURGNAMHACH]", - "transcoding_realtime_description": "Ceadaíonn sÊ traschÃŗdÃē a dhÊanamh i bhfíor-am agus an físeÃĄn ÃĄ shruthÃē. Cumasaíonn sÊ athrÃē cÃĄilíochta, ach d'fhÊadfadh moill athsheinm níos airde agus stad a bheith mar thoradh air ag brath ar chumais an fhreastalaí.", - "transcoding_realtime_enabled": "Cumasaigh tras-chÃŗdÃē fíor-ama", - "transcoding_realtime_enabled_description": "Mura bhfuil sÊ cumasaithe, diÃēltÃŗidh an freastalaí seisiÃēin thraschÃŗdaithe fíor-ama nua a thosÃē.", - "transcoding_realtime_resolutions": "RÃēin", - "transcoding_realtime_resolutions_description": "Na taifigh a thairgtear le haghaidh traschÃŗdÃē fíor-ama. D’fhÊadfadh fadhbanna athsheinm a bheith mar thoradh ar taifigh níos airde mura fÊidir leis an bhfreastalaí iad a thraschÃŗdÃē go tapa go leor.", - "transcoding_realtime_video_codecs": "CÃŗideic físe", - "transcoding_realtime_video_codecs_description": "Na cÃŗideiceanna físe a thairgtear le haghaidh traschÃŗdÃē fíor-ama. RoghnÃŗidh cliaint an rogha is fearr a dtacaíonn siad leis le linn athsheinm. TÃĄ AV1 níos Êifeachtaí nÃĄ HEVC, atÃĄ níos Êifeachtaí nÃĄ H.264. Agus luasghÊarÃē crua-earraí in ÃēsÃĄid agat, roghnaigh na cÃŗideiceanna is fÊidir leis an luasaire a ionchÃŗdÃē amhÃĄin. Agus traschÃŗdÃē bogearraí in ÃēsÃĄid agat, tabhair faoi deara go bhfuil H.264 níos tapÃēla nÃĄ AV1, atÃĄ níos tapÃēla nÃĄ HEVC.", "transcoding_reference_frames": "FrÃĄmaí tagartha", "transcoding_reference_frames_description": "Líon na bhfrÃĄmaí le tagairt dÃŗibh agus frÃĄma ÃĄirithe ÃĄ chomhbhrÃē. Feabhsaíonn luachanna níos airde ÊifeachtÃēlacht an chomhbhrÃēite, ach moillíonn siad an ionchÃŗdÃē. Socraíonn 0 an luach seo go huathoibríoch.", "transcoding_required_description": "FíseÃĄin amhÃĄin nach bhfuil i bhformÃĄid inghlactha", @@ -478,8 +440,6 @@ "user_settings_description": "Bainistigh socruithe ÃēsÃĄideora", "user_successfully_removed": "Baineadh an t-ÃēsÃĄideoir {email} go rathÃēil.", "users_page_description": "Leathanach ÃēsÃĄideoirí riarthÃŗra", - "version_check_channel": "CainÊal scaoilte", - "version_check_channel_description": "Roghnaigh an cainÊal eisiÃēna ar mhaith leat fÃŗgraí leagan a fhÃĄil dÃŗ", "version_check_enabled_description": "Cumasaigh seiceÃĄil leagan", "version_check_implications": "Braitheann an ghnÊ seiceÃĄla leagan ar chumarsÃĄid thrÊimhsiÃēil le {server}", "version_check_settings": "SeiceÃĄil Leagan", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Glan an Taisce ÍomhÃĄ", "advanced_settings_clear_image_cache_error": "Theip ar an taisce íomhÃĄ a ghlanadh", "advanced_settings_clear_image_cache_success": "Glanadh {size} go rathÃēil", + "advanced_settings_enable_alternate_media_filter_subtitle": "ÚsÃĄid an rogha seo chun meÃĄin a scagadh le linn sioncrÃŗnaithe bunaithe ar chritÊir mhalartacha. NÃĄ dÊan iarracht air seo ach amhÃĄin mÃĄ bhíonn fadhbanna agat leis an aip ag braith gach albam.", + "advanced_settings_enable_alternate_media_filter_title": "[TURGNAMHACH] ÚsÃĄid scagaire sioncrÃŗnaithe albam glÊas malartach", "advanced_settings_log_level_title": "LeibhÊal loga: {level}", "advanced_settings_prefer_remote_subtitle": "Bíonn roinnt glÊasanna thar a bheith mall ag luchtÃē mionsamhlacha Ãŗ shÃŗcmhainní ÃĄitiÃēla. Gníomhachtaigh an socrÃē seo chun íomhÃĄnna iargÃēlta a luchtÃē ina ionad.", "advanced_settings_prefer_remote_title": "Is fearr leat íomhÃĄnna iargÃēlta", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "CeanntÃĄsca seachfhreastalaí saincheaptha [TURGNAÍOCH]", "advanced_settings_readonly_mode_subtitle": "Cumasaíonn sÊ seo an modh lÊite amhÃĄin ina bhfÊadfar na grianghraif a fheiceÃĄil amhÃĄin, agus bíonn rudaí cosÃēil le híomhÃĄnna iolracha a roghnÃē, a roinnt, a chraoladh, a scriosadh díchumasaithe. Cumasaigh/Díchumasaigh an modh lÊite amhÃĄin trí abhatÃĄr an ÃēsÃĄideora Ãŗn bpríomhscÃĄileÃĄn", "advanced_settings_readonly_mode_title": "MÃŗd lÊite amhÃĄin", + "advanced_settings_self_signed_ssl_subtitle": "Scipeann sÊ fíorÃē teastais SSL don chríochphointe freastalaí. Riachtanach le haghaidh teastais fÊinshínithe.", + "advanced_settings_self_signed_ssl_title": "Ceadaigh teastais SSL fÊinshínithe [TURGHAINNEACH]", "advanced_settings_sync_remote_deletions_subtitle": "Scrios nÃŗ athchÃŗirigh sÃŗcmhainn go huathoibríoch ar an nglÊas seo nuair a dhÊantar an gníomh sin ar an ngrÊasÃĄn", "advanced_settings_sync_remote_deletions_title": "SioncrÃŗnaigh scriostaí iargÃēlta [TURGNAMHACH]", "advanced_settings_tile_subtitle": "Socruithe ÃēsÃĄideora ardleibhÊil", @@ -516,18 +480,31 @@ "album_delete_confirmation": "An bhfuil tÃē cinnte gur mian leat an t-albam {album} a scriosadh?", "album_delete_confirmation_description": "Mura roinnfear an t-albam seo, ní bheidh ÃēsÃĄideoirí eile in ann rochtain a fhÃĄil air a thuilleadh.", "album_deleted": "Scriosadh an t-albam", + "album_info_card_backup_album_excluded": "EISIATA", + "album_info_card_backup_album_included": "SAN ÁIREAMH", "album_info_updated": "Eolas albam nuashonraithe", + "album_leave": "FÃĄg an t-albam?", + "album_leave_confirmation": "An bhfuil tÃē cinnte gur mian leat {album} a fhÃĄgÃĄil?", "album_name": "Ainm an Albaim", "album_options": "Roghanna albaim", "album_remove_user": "Bain an t-ÃēsÃĄideoir?", "album_remove_user_confirmation": "An bhfuil tÃē cinnte gur mian leat {user} a bhaint?", "album_search_not_found": "Ní bhfuarthas aon albaim a mheaitseÃĄlann do chuardach", + "album_selected": "Albam roghnaithe", "album_share_no_users": "Is cosÃēil gur roinn tÃē an t-albam seo le gach ÃēsÃĄideoir nÃŗ nach bhfuil aon ÃēsÃĄideoir agat le roinnt leis.", "album_summary": "Achoimre ar an albam", "album_updated": "Albam nuashonraithe", "album_updated_setting_description": "Faigh fÃŗgra ríomhphoist nuair a bhíonn sÃŗcmhainní nua i albam comhroinnte", "album_upload_assets": "UaslÃŗdÃĄil sÃŗcmhainní Ãŗ do ríomhaire agus cuir le halbam iad", + "album_user_left": "D'fhÃĄg {album}", + "album_user_removed": "Baineadh {user}", + "album_viewer_appbar_delete_confirm": "An bhfuil tÃē cinnte gur mian leat an t-albam seo a scriosadh Ãŗ do chuntas?", "album_viewer_appbar_share_err_delete": "Theip ar an albam a scriosadh", + "album_viewer_appbar_share_err_leave": "Theip ar an albam a fhÃĄgÃĄil", + "album_viewer_appbar_share_err_remove": "TÃĄ fadhbanna ann maidir le sÃŗcmhainní a bhaint as albam", + "album_viewer_appbar_share_err_title": "Theip ar theideal an albaim a athrÃē", + "album_viewer_appbar_share_leave": "FÃĄg an t-albam", + "album_viewer_appbar_share_to": "Comhroinn Le", "album_viewer_page_share_add_users": "Cuir ÃēsÃĄideoirí leis", "album_with_link_access": "Lig d’aon duine a bhfuil an nasc aige grianghraif agus daoine san albam seo a fheiceÃĄil.", "albums": "Albaim", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Ord sÃŗrtÃĄla sÃŗcmhainní tosaigh agus albaim nua ÃĄ gcruthÃē.", "albums_feature_description": "BailiÃēchÃĄin sÃŗcmhainní is fÊidir a roinnt le hÃēsÃĄideoirí eile.", "albums_on_device_count": "Albaim ar an nglÊas ({count})", + "albums_selected": "{count, plural, one {# albam roghnaithe} other {# albam roghnaithe}}", "all": "Gach", "all_albums": "Gach albam", "all_people": "Gach duine", "all_photos": "Gach grianghraf", "all_videos": "Gach físeÃĄn", "allow_dark_mode": "Ceadaigh mÃŗd dorcha", + "allow_edits": "Ceadaigh eagarthÃŗireachtaí", "allow_public_user_to_download": "Ceadaigh d'ÃēsÃĄideoirí poiblí íoslÃŗdÃĄil a dhÊanamh", "allow_public_user_to_upload": "Ceadaigh d'ÃēsÃĄideoirí poiblí uaslÃŗdÃĄil", "allowed": "Ceadaithe", @@ -549,12 +528,14 @@ "always_keep": "Coinnigh i gcÃŗnaí", "always_keep_photos_hint": "Coinneoidh Saoradh SpÃĄis na grianghraif go lÊir ar an nglÊas seo.", "always_keep_videos_hint": "Coinneoidh Saoradh SpÃĄis na físeÃĄin go lÊir ar an nglÊas seo.", + "anti_clockwise": "Tuathalach", "api_key": "Eochair API", "api_key_description": "Ní thaispeÃĄnfar an luach seo ach uair amhÃĄin. Bí cinnte Ê a chÃŗipeÃĄil sula ndÃēnann tÃē an fhuinneog.", "api_key_empty": "Níor cheart go mbeadh ainm d’Eochair API folamh", "api_keys": "Eochracha API", "app_architecture_variant": "Malartach (Ailtireacht)", "app_bar_signout_dialog_content": "An bhfuil tÃē cinnte gur mhaith leat síniÃē amach?", + "app_bar_signout_dialog_ok": "TÃĄ", "app_bar_signout_dialog_title": "Sínigh amach", "app_download_links": "Naisc ÍoslÃŗdÃĄla Aipeanna", "app_settings": "Socruithe Aipe", @@ -565,19 +546,27 @@ "archive": "Cartlann", "archive_action_prompt": "{count} curtha leis an gCartlann", "archive_or_unarchive_photo": "Cartlannaigh nÃŗ díchartlannaigh grianghraf", + "archive_page_no_archived_assets": "Ní bhfuarthas aon sÃŗcmhainní cartlannaithe", + "archive_page_title": "Cartlann ({count})", "archive_size": "MÊid na cartlainne", "archive_size_description": "Cumraigh mÊid na cartlainne le haghaidh íoslÃŗdÃĄlacha (i GiB)", "archived": "Cartlannaithe", "archived_count": "{count, plural, other {Cartlannaithe #}}", "are_these_the_same_person": "An iad seo an duine cÊanna?", "are_you_sure_to_do_this": "An bhfuil tÃē cinnte gur mian leat Ê seo a dhÊanamh?", + "array_field_not_fully_supported": "Éilíonn rÊimsí eagar eagarthÃŗireacht JSON de lÃĄimh", + "asset_action_delete_err_read_only": "Ní fÊidir sÃŗcmhainn(í) lÊite amhÃĄin a scriosadh, ag scipeÃĄil", + "asset_action_share_err_offline": "Ní fÊidir sÃŗcmhainn(í) as líne a fhÃĄil, ag scipeÃĄil", "asset_added_to_album": "Curtha leis an albam", "asset_adding_to_album": "Ag cur leis an albamâ€Ļ", "asset_created": "SÃŗcmhainn cruthaithe", - "asset_day_count": "{date}: {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "asset_description_updated": "TÃĄ cur síos na sÃŗcmhainne nuashonraithe", + "asset_filename_is_offline": "TÃĄ an tsÃŗcmhainn {filename} as líne", + "asset_has_unassigned_faces": "TÃĄ aghaidheanna neamhshannta ag an tsÃŗcmhainn", "asset_hashing": "Ag hasÃĄilâ€Ļ", "asset_list_group_by_sub_title": "GrÃēpÃĄil de rÊir", + "asset_list_layout_settings_dynamic_layout_title": "Leagan amach dinimiciÃēil", + "asset_list_layout_settings_group_automatically": "Uathoibríoch", "asset_list_layout_settings_group_by": "SÃŗcmhainní grÃēpa de rÊir", "asset_list_layout_settings_group_by_month_day": "Mí + lÃĄ", "asset_list_layout_sub_title": "Leagan Amach", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "SÃŗcmhainn gan teacht ar iCloud. B’fhÊidir nach bhfuil an tsÃŗcmhainn inrochtana mar gheall ar chomhad lochtach atÃĄ stÃŗrÃĄilte ar iCloud", "asset_offline": "SÃŗcmhainn As Líne", "asset_offline_description": "Níl an tsÃŗcmhainn sheachtrach seo le fÃĄil ar dhiosca a thuilleadh. TÊigh i dteagmhÃĄil le riarthÃŗir do Immich le haghaidh cabhrach.", + "asset_restored_successfully": "AthchÃŗiríodh an tsÃŗcmhainn go rathÃēil", "asset_skipped": "ScipeÃĄilte", "asset_skipped_in_trash": "Sa bhruscar", + "asset_trashed": "SÃŗcmhainn curtha sa bhruscar", "asset_troubleshoot": "FabhtcheartÃē SÃŗcmhainní", "asset_uploaded": "UaslÃŗdÃĄilte", "asset_uploading": "Ag uaslÃŗdÃĄilâ€Ļ", "asset_viewer_settings_subtitle": "Bainistigh do shocruithe breathnÃŗra gailearaí", "asset_viewer_settings_title": "AmharcÃŗir SÃŗcmhainní", "assets": "SÃŗcmhainní", + "assets_added_count": "Cuireadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "assets_added_to_album_count": "Cuireadh {count, plural, one {# asset} other {# assets}} leis an albam", - "assets_added_to_album_partial_count": "Cuireadh {successCount} as {totalCount} {totalCount, plural, one {asset} other {assets}} leis an albam", "assets_added_to_albums_count": "Cuireadh {assetTotal, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} go {albumTotal, plural, one {# albam} other {# albaim}}", "assets_cannot_be_added_to_album_count": "Ní fÊidir {count, plural, one {SÃŗcmhainn} other {SÃŗcmhainní}} a chur leis an albam", "assets_cannot_be_added_to_albums": "Ní fÊidir {count, plural, one {Asset} other {Assets}} a chur le haon cheann de na halbaim", "assets_count": "{count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", + "assets_deleted_permanently": "Scriosadh {count} sÃŗcmhainn(í) go buan", + "assets_deleted_permanently_from_server": "Scriosadh {count} sÃŗcmhainn(í) go buan Ãŗn bhfreastalaí Immich", + "assets_downloaded_failed": "{count, plural, one {ÍoslÃŗdÃĄil # comhad - {error} theip ar chomhad} other {ÍoslÃŗdÃĄil # comhaid - {error} theip ar chomhad}}", + "assets_downloaded_successfully": "{count, plural, one {ÍoslÃŗdÃĄileadh # comhad go rathÃēil} other {ÍoslÃŗdÃĄileadh # comhaid go rathÃēil}}", "assets_moved_to_trash_count": "Bogadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} go dtí an bruscar", "assets_permanently_deleted_count": "Scriosta go buan {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "assets_removed_count": "Baineadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", + "assets_removed_permanently_from_device": "Baineadh {count} sÃŗcmhainn(í) go buan Ãŗ do ghlÊas", "assets_restore_confirmation": "An bhfuil tÃē cinnte gur mian leat do shÃŗcmhainní uile atÃĄ curtha sa bhruscar a athchÃŗiriÃē? Ní fÊidir leat an gníomh seo a chealÃē! Tabhair faoi deara nach fÊidir aon sÃŗcmhainní as líne a athchÃŗiriÃē ar an mbealach seo.", "assets_restored_count": "AthchÃŗirithe {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", + "assets_restored_successfully": "{count} sÃŗcmhainn(í) athchÃŗirithe go rathÃēil", "assets_trashed": "{count} sÃŗcmhainn(í) curtha sa bhruscar", "assets_trashed_count": "Bruscar {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", + "assets_trashed_from_server": "{count} sÃŗcmhainn(í) curtha sa bhruscar Ãŗn bhfreastalaí Immich", "assets_were_part_of_album_count": "{count, plural, one {SÃŗcmhainn a bhí} other {SÃŗcmhainní a bhí}} mar chuid den albam cheana fÊin", "assets_were_part_of_albums_count": "Bhí {count, plural, one {an tsÃŗcmhainn} other {na tsÃŗcmhainní}} mar chuid de na halbaim cheana fÊin", "authorized_devices": "GlÊasanna Údaraithe", @@ -617,48 +615,87 @@ "autoplay_slideshow": "TaispeÃĄntas sleamhnÃĄn uathsheinm", "back": "Ar ais", "back_close_deselect": "Siar, dÃēn, nÃŗ díroghnaigh", + "background_backup_running_error": "TÃĄ cÃēltaca cÃēlra ar siÃēl faoi lÃĄthair, ní fÊidir cÃēltaca lÃĄimhe a thosÃē", "background_location_permission": "Cead suímh sa chÃēlra", "background_location_permission_content": "Chun líonraí a athrÃē agus Ê ag rith sa chÃēlra, ní mÃŗr rochtain chruinn suímh a bheith ag Immich *i gcÃŗnaí* ionas gur fÊidir leis an aip ainm an líonra Wi-Fi a lÊamh", "background_options": "Roghanna CÃēlra", "backup": "CÃēltaca", + "backup_album_selection_page_albums_device": "Albaim ar an nglÊas ({count})", "backup_album_selection_page_albums_tap": "TapÃĄil le cur san ÃĄireamh, tapÃĄil faoi dhÃŗ le heisiamh", "backup_album_selection_page_assets_scatter": "Is fÊidir sÃŗcmhainní a scaipeadh ar fud il-albaim. DÃĄ bhrí sin, is fÊidir albaim a chur san ÃĄireamh nÃŗ a eisiamh le linn an phrÃŗisis chÃēltaca.", "backup_album_selection_page_select_albums": "Roghnaigh albaim", "backup_album_selection_page_selection_info": "Eolas RoghnÃēchÃĄin", + "backup_album_selection_page_total_assets": "IomlÃĄn na sÃŗcmhainní uathÃēla", "backup_albums_sync": "SioncrÃŗnÃē Albam CÃēltaca", + "backup_all": "Gach", + "backup_background_service_backup_failed_message": "Theip ar chÃēltaca sÃŗcmhainní. Ag iarraidh arísâ€Ļ", "backup_background_service_complete_notification": "CÃēltaca sÃŗcmhainní críochnaithe", + "backup_background_service_connection_failed_message": "Theip ar cheangal leis an bhfreastalaí. Ag iarraidh arísâ€Ļ", + "backup_background_service_current_upload_notification": "Ag uaslÃŗdÃĄil {filename}", "backup_background_service_default_notification": "Ag seiceÃĄil le haghaidh sÃŗcmhainní nuaâ€Ļ", + "backup_background_service_error_title": "EarrÃĄid chÃēltaca", "backup_background_service_in_progress_notification": "Ag dÊanamh cÃēltaca de do shÃŗcmhainníâ€Ļ", + "backup_background_service_upload_failure_notification": "Theip ar {filename} a uaslÃŗdÃĄil", "backup_controller_page_albums": "Albaim ChÃēltaca", + "backup_controller_page_background_app_refresh_disabled_content": "Cumasaigh athnuachan aipeanna cÃēlra i Socruithe > GinearÃĄlta > Athnuachan Aipeanna CÃēlra chun cÃēltaca cÃēlra a ÃēsÃĄid.", + "backup_controller_page_background_app_refresh_disabled_title": "Athnuachan aip sa chÃēlra díchumasaithe", + "backup_controller_page_background_app_refresh_enable_button_text": "TÊigh go dtí na socruithe", "backup_controller_page_background_battery_info_link": "TaispeÃĄin dom conas", "backup_controller_page_background_battery_info_message": "Chun an taithí cÃēltaca cÃēlra is fearr a fhÃĄil, díchumasaigh aon uasmhÊaduithe ceallraí a chuireann srian ar ghníomhaíocht chÃēlra do Immich.\n\nÓs rud Ê go bhfuil sÊ seo sainiÃēil don ghlÊas, fÊach ar an bhfaisnÊis riachtanach le haghaidh monarÃŗir do ghlÊis.", "backup_controller_page_background_battery_info_ok": "Ceart go leor", "backup_controller_page_background_battery_info_title": "OptamÃēchÃĄin ceallraí", + "backup_controller_page_background_charging": "Agus Ê ÃĄ mhuirearÃē amhÃĄin", + "backup_controller_page_background_configure_error": "Theip ar chumrÃē na seirbhíse cÃēlra", "backup_controller_page_background_delay": "Moill ar chÃēltaca sÃŗcmhainní nua: {duration}", + "backup_controller_page_background_description": "Cas ar an tseirbhís chÃēlra chun cÃēltaca uathoibríoch d’aon sÃŗcmhainní nua a dhÊanamh gan an aip a oscailt", + "backup_controller_page_background_is_off": "TÃĄ cÃēltaca uathoibríoch sa chÃēlra mÃēchta", + "backup_controller_page_background_is_on": "TÃĄ cÃēltaca uathoibríoch sa chÃēlra ar siÃēl", + "backup_controller_page_background_turn_off": "MÃēch an tseirbhís chÃēlra", + "backup_controller_page_background_turn_on": "Cuir seirbhís chÃēlra ar siÃēl", + "backup_controller_page_background_wifi": "Ar Wi-Fi amhÃĄin", "backup_controller_page_backup": "CÃēltaca", "backup_controller_page_backup_selected": "Roghnaithe: ", "backup_controller_page_backup_sub": "Grianghraif agus físeÃĄin cÃēltaca", + "backup_controller_page_created": "Cruthaithe ar: {date}", + "backup_controller_page_desc_backup": "Cas ar chÃēltaca sa tulra chun sÃŗcmhainní nua a uaslÃŗdÃĄil go huathoibríoch chuig an bhfreastalaí nuair a osclaítear an aip.", "backup_controller_page_excluded": "Eisiata: ", + "backup_controller_page_failed": "Theip air ({count})", + "backup_controller_page_filename": "Ainm comhaid: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "FaisnÊis ChÃēltaca", "backup_controller_page_none_selected": "Níor roghnaíodh aon cheann", "backup_controller_page_remainder": "Fuílleach", "backup_controller_page_remainder_sub": "Grianghraif agus físeÃĄin atÃĄ fÃĄgtha le cÃēltaca Ãŗn rogha", "backup_controller_page_server_storage": "StÃŗrÃĄil Freastalaí", + "backup_controller_page_start_backup": "Tosaigh CÃēltaca", + "backup_controller_page_status_off": "TÃĄ cÃēltaca uathoibríoch sa tulra mÃēchta", + "backup_controller_page_status_on": "TÃĄ cÃēltaca uathoibríoch sa tulra ar siÃēl", "backup_controller_page_storage_format": "{used} de {total} ÃēsÃĄidte", "backup_controller_page_to_backup": "Albaim le cÃēltaca", "backup_controller_page_total_sub": "Gach grianghraf agus físeÃĄn uathÃēil Ãŗ albaim roghnaithe", + "backup_controller_page_turn_off": "MÃēch cÃēltaca sa tulra", + "backup_controller_page_turn_on": "Cuir cÃēltaca sa tulra ar siÃēl", + "backup_controller_page_uploading_file_info": "Eolas comhaid ÃĄ uaslÃŗdÃĄil", + "backup_err_only_album": "Ní fÊidir an t-aon albam a bhaint", "backup_error_sync_failed": "Theip ar an sioncrÃŗnÃē. Ní fÊidir an cÃēltaca a phrÃŗiseÃĄil.", "backup_info_card_assets": "sÃŗcmhainní", + "backup_manual_cancelled": "Cealaithe", + "backup_manual_in_progress": "UaslÃŗdÃĄil ar siÃēl cheana fÊin. Bain triail as ar ball", + "backup_manual_success": "Rath", + "backup_manual_title": "StÃĄdas uaslÃŗdÃĄla", "backup_options": "Roghanna CÃēltaca", + "backup_options_page_title": "Roghanna cÃēltaca", + "backup_setting_subtitle": "Bainistigh socruithe uaslÃŗdÃĄla cÃēlra agus tulra", "backup_settings_subtitle": "Bainistigh socruithe uaslÃŗdÃĄla", + "backup_upload_details_page_more_details": "TapÃĄil le haghaidh tuilleadh sonraí", "backward": "Ar gcÃēl", - "battery_optimization_backup_reliability": "Is fÊidir iontaofacht chÃēltaca cÃēlra a fheabhsÃē trí uasmhÊaduithe ceallraí a dhíchumasÃē", "biometric_auth_enabled": "Cumasaíodh fíordheimhniÃē bithmhÊadrach", "biometric_locked_out": "Albaim an chulta", "biometric_no_options": "Níl aon roghanna bithmhÊadracha ar fÃĄil", "biometric_not_available": "Níl fíordheimhniÃē bithmhÊadrach ar fÃĄil ar an nglÊas seo", + "birthdate_saved": "DÃĄta breithe sÃĄbhÃĄilte go rathÃēil", "birthdate_set_description": "ÚsÃĄidtear dÃĄta breithe chun aois an duine seo trÃĄth a ngrianghraf a ríomh.", "blurred_background": "CÃēlra doilÊir", - "browse_templates": "BrabhsÃĄil teimplÊid", "bugs_and_feature_requests": "Fabhtanna & Iarratais ar GhnÊithe", "build": "TÃŗgÃĄil", "build_image": "TÃŗg ÍomhÃĄ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "An bhfuil tÃē cinnte gur mian leat {count, plural, one {# duplicate asset} other {# duplicate assets}} a choinneÃĄil? RÊiteoidh sÊ seo gach grÃēpa dÃēblach gan aon rud a scriosadh.", "bulk_trash_duplicates_confirmation": "An bhfuil tÃē cinnte gur mian leat {count, plural, one {# duplicate asset} other {# duplicate assets}} a chur sa bhruscar? Coinneoidh sÊ seo an tsÃŗcmhainn is mÃŗ de gach grÃēpa agus cuirfidh sÊ na dÃēblaigh eile go lÊir sa bhruscar.", "buy": "CeannachÃĄn Immich", + "cache_settings_clear_cache_button": "Glan an taisce", + "cache_settings_clear_cache_button_title": "Glanann sÊ seo taisce an aip. Beidh tionchar suntasach aige seo ar fheidhmíocht an aip go dtí go mbeidh an taisce atÃŗgtha.", + "cache_settings_duplicated_assets_clear_button": "GLAN", + "cache_settings_duplicated_assets_subtitle": "Grianghraif agus físeÃĄin a ndÊantar neamhaird orthu san aip", + "cache_settings_duplicated_assets_title": "SÃŗcmhainní DÃēblaithe ({count})", + "cache_settings_statistics_album": "Mionsamhlacha leabharlainne", + "cache_settings_statistics_full": "ÍomhÃĄnna iomlÃĄna", + "cache_settings_statistics_shared": "Mionsamhlacha albam comhroinnte", + "cache_settings_statistics_thumbnail": "Mionsamhlacha", + "cache_settings_statistics_title": "ÚsÃĄid taisce", + "cache_settings_subtitle": "RialÃē iompar taisceÃĄla an fheidhmchlÃĄir shoghluaiste Immich", + "cache_settings_tile_subtitle": "RialÃē an iompair stÃŗrÃĄla ÃĄitiÃēil", + "cache_settings_tile_title": "StÃŗrÃĄil ÁitiÃēil", + "cache_settings_title": "Socruithe TaisceÃĄla", "camera": "Ceamara", "camera_brand": "Branda ceamara", "camera_model": "MÃēnla ceamara", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Ní fÊidir an cur síos a nuashonrÃē", "cast": "Caith", "cast_description": "Cumraigh na cinn scríbe teilgthe atÃĄ ar fÃĄil", - "change": "Athraigh", "change_date": "Athraigh dÃĄta", "change_description": "Athraigh cur síos", "change_display_order": "Athraigh ord taispeÃĄna", + "change_expiration_time": "Athraigh an t-am Êaga", "change_location": "Athraigh suíomh", "change_name": "Athraigh ainm", "change_name_successfully": "Athraíodh an t-ainm go rathÃēil", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Ní hionann na pasfhocail", "change_password_form_reenter_new_password": "Ath-iontrÃĄil Pasfhocal Nua", "change_pin_code": "Athraigh an cÃŗd PIN", + "change_trigger": "Athraigh an spreagadh", + "change_trigger_prompt": "An bhfuil tÃē cinnte gur mian leat an spreagthÃŗir a athrÃē? Bainfear gach gníomh agus scagaire atÃĄ ann cheana leis seo.", "change_your_password": "Athraigh do phasfhocal", "changed_visibility_successfully": "Athraíodh an infheictheacht go rathÃēil", "charging": "MuirearÃē", "charging_requirement_mobile_backup": "Éilíonn cÃēltaca cÃēlra go bhfuil an glÊas ÃĄ mhuirearÃē", + "check_corrupt_asset_backup": "SeiceÃĄil le haghaidh cÃēltacaí sÃŗcmhainní truaillithe", + "check_corrupt_asset_backup_button": "DÊan seiceÃĄil", + "check_corrupt_asset_backup_description": "NÃĄ dÊan an seiceÃĄil seo ach amhÃĄin thar Wi-Fi agus nuair a bheidh cÃēltaca dÊanta de na sÃŗcmhainní go lÊir. D’fhÊadfadh sÊ go dtÃŗgfadh an nÃŗs imeachta cÃēpla nÃŗimÊad.", "check_logs": "SeiceÃĄil Logaí", "checksum": "Suim sheiceÃĄla", - "choose": "Roghnaigh", "choose_matching_people_to_merge": "Roghnaigh daoine comhoiriÃēnacha le cumasc", "city": "Cathair", "cleanup_confirm_description": "Fuair Immich {count} sÃŗcmhainní (cruthaithe roimh {date}) cÃēltaca sÃĄbhÃĄilte chuig an bhfreastalaí. Bain na cÃŗipeanna ÃĄitiÃēla den ghlÊas seo?", @@ -719,10 +774,11 @@ "clear": "Glan", "clear_all": "Glan gach rud", "clear_all_recent_searches": "Glan gach cuardach le dÊanaí", - "clear_failed_count": "Theip ar ghlanadh ({count})", "clear_file_cache": "Glan an Taisce Comhad", "clear_message": "Teachtaireacht shoilÊir", "clear_value": "Glan luach", + "client_cert_dialog_msg_confirm": "Ceart go leor", + "client_cert_enter_password": "Cuir isteach Pasfhocal", "client_cert_import": "IompÃŗrtÃĄil", "client_cert_import_success_msg": "TÃĄ deimhniÃē cliant allmhairithe", "client_cert_invalid_msg": "Comhad teastais neamhbhailí nÃŗ pasfhocal mícheart", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Baineadh teastas an chliaint", "client_cert_subtitle": "Tacaíonn sÊ le formÃĄid PKCS12 (.p12, .pfx) amhÃĄin. Ní fÊidir teastais a allmhairiÃē/a bhaint ach amhÃĄin roimh logÃĄil isteach", "client_cert_title": "Teastas cliant SSL [TURGHAINNEACH]", + "clockwise": "Deiseal", "close": "DÃēn", "collapse": "Laghdaigh", "collapse_all": "Laghdaigh gach rud", "color": "Dath", + "color_theme": "TÊama datha", "command": "OrdÃē", "command_palette_prompt": "Aimsigh leathanaigh, gníomhartha nÃŗ orduithe go tapa", "command_palette_to_close": "a dhÃēnadh", @@ -747,7 +805,6 @@ "comments_are_disabled": "TÃĄ tuairimí díchumasaithe", "common_create_new_album": "Cruthaigh albam nua", "completed": "Críochnaithe", - "configuration": "Cumraíocht", "confirm": "Deimhnigh", "confirm_admin_password": "Deimhnigh Pasfhocal an RiarthÃŗra", "confirm_delete_face": "An bhfuil tÃē cinnte gur mian leat aghaidh {name} a scriosadh Ãŗn tsÃŗcmhainn?", @@ -762,16 +819,19 @@ "contain": "Coinnigh", "context": "ComhthÊacs", "continue": "Lean ar aghaidh", - "control_bottom_app_bar_add_tags": "Cuir Clibeanna leis", + "control_bottom_app_bar_create_new_album": "Cruthaigh albam nua", + "control_bottom_app_bar_delete_from_immich": "Scrios Ãŗ Immich", "control_bottom_app_bar_delete_from_local": "Scrios Ãŗn nglÊas", "control_bottom_app_bar_edit_location": "Cuir Suíomh in Eagar", "control_bottom_app_bar_edit_time": "Cuir DÃĄta & Am in Eagar", + "control_bottom_app_bar_share_link": "Comhroinn an Nasc", + "control_bottom_app_bar_share_to": "Comhroinn Le", "control_bottom_app_bar_trash_from_immich": "Bog go dtí an Bruscar", "copied_image_to_clipboard": "ÍomhÃĄ cÃŗipeÃĄilte chuig an ghearrthaisce.", "copied_to_clipboard": "CÃŗipeÃĄilte chuig an ghearrthaisce!", "copy_error": "EarrÃĄid chÃŗipeÃĄla", + "copy_file_path": "CÃŗipeÃĄil cosÃĄn comhaid", "copy_image": "CÃŗipeÃĄil ÍomhÃĄ", - "copy_json": "CÃŗipeÃĄil JSON", "copy_link": "CÃŗipeÃĄil nasc", "copy_link_to_clipboard": "CÃŗipeÃĄil nasc chuig an ghearrthaisce", "copy_password": "CÃŗipeÃĄil an focal faire", @@ -789,6 +849,7 @@ "create_link_to_share": "Cruthaigh nasc le roinnt", "create_link_to_share_description": "Lig do dhuine ar bith a bhfuil an nasc aige/aici an/na grianghraf/na grianghraif roghnaithe a fheiceÃĄil", "create_new": "CRUTHAIGH NUA", + "create_new_face": "Cruthaigh aghaidh nua", "create_new_person": "Cruthaigh duine nua", "create_new_person_hint": "Sannadh sÃŗcmhainní roghnaithe do dhuine nua", "create_new_user": "Cruthaigh ÃēsÃĄideoir nua", @@ -805,33 +866,37 @@ "created_at": "Cruthaithe", "creating_linked_albums": "Ag cruthÃē albaim nasctha...", "crop": "Barr", + "crop_aspect_ratio_fixed": "Seasta", "crop_aspect_ratio_free": "Saor in aisce", "crop_aspect_ratio_original": "Bunaidh", "crop_aspect_ratio_square": "CearnÃŗg", + "curated_object_page_title": "Rudaí", "current_device": "GlÊas reatha", "current_pin_code": "CÃŗd PIN reatha", "current_server_address": "Seoladh reatha an fhreastalaí", "custom_date": "DÃĄta saincheaptha", "custom_locale": "LogÃĄn saincheaptha", "custom_locale_description": "FormÃĄidigh dÃĄtaí, amanna agus uimhreacha bunaithe ar an teanga agus an rÊigiÃēn roghnaithe", + "custom_url": "URL Saincheaptha", "cutoff_date_description": "Coinnigh grianghraif Ãŗn uair dheireanachâ€Ļ", "cutoff_day": "{count, plural, one {lÃĄ} other {laethanta}}", "cutoff_year": "{count, plural, one {bliain} other {blianta}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Dorcha", "dark_theme": "Athraigh go tÊama dorcha", "date": "DÃĄta", "date_after": "DÃĄta i ndiaidh", "date_and_time": "DÃĄta agus Am", "date_before": "DÃĄta roimh", - "date_of_birth": "DÃĄta breithe", + "date_format": "E, d LLL, y â€ĸ h:mm a", "date_of_birth_saved": "DÃĄta breithe sÃĄbhÃĄilte go rathÃēil", "date_range": "Raon dÃĄta", - "date_time_original": "DÃĄta/Am Bunleagan", "day": "LÃĄ", "days": "Laethanta", "deduplicate_all": "DídhÃēblaigh Gach Rud", - "default_quality_subtitle": "CÃĄilíocht a ÃēsÃĄidtear agus tÃē ag tapÃĄil ar an gcnaipe 'roinn'. BrÃēigh an cnaipe 'roinn' ar feadh tamaill le roghnÃē gach uair.", - "default_share_quality": "CÃĄilíocht rÊamhshocraithe roinnte", + "default_locale": "LogÃĄn RÊamhshocraithe", + "default_locale_description": "FormÃĄidigh dÃĄtaí agus uimhreacha bunaithe ar shuíomh do bhrabhsÃĄlaí", "delete": "Scrios", "delete_action_confirmation_message": "An bhfuil tÃē cinnte gur mian leat an tsÃŗcmhainn seo a scriosadh? Bogfaidh an gníomh seo an tsÃŗcmhainn go dtí bruscar an fhreastalaí agus fiafrÃŗidh sÊ díot an mian leat í a scriosadh go hÃĄitiÃēil", "delete_action_prompt": "{count} scriosta", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Scriosfar na míreanna seo go buan Ãŗ Immich agus Ãŗ do ghlÊas", "delete_dialog_alert_local": "Bainfear na míreanna seo go buan de do ghlÊas ach beidh siad fÃŗs ar fÃĄil ar fhreastalaí Immich", "delete_dialog_alert_local_non_backed_up": "Níl cuid de na míreanna cÃēltaca dÊanta chuig Immich agus bainfear iad go buan de do ghlÊas", + "delete_dialog_alert_remote": "Scriosfar na míreanna seo go buan Ãŗn bhfreastalaí Immich", + "delete_dialog_ok_force": "Scrios Ar Aon Slí", "delete_dialog_title": "Scrios go Buan", "delete_duplicates_confirmation": "An bhfuil tÃē cinnte gur mian leat na dÃēblaigh seo a scriosadh go buan?", "delete_face": "Scrios aghaidh", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "An bhfuil tÃē cinnte gur mian leat an clib {tagName} a scriosadh?", "delete_user": "Scrios ÃēsÃĄideoir", "deleted_shared_link": "Nasc comhroinnte scriosta", + "deletes_missing_assets": "Scriosann sÊ sÃŗcmhainní atÃĄ ar iarraidh Ãŗn diosca", "description": "Cur síos", + "description_input_hint_text": "Cuir cur síos leis...", + "description_input_submit_error": "EarrÃĄid ag nuashonrÃē an tuairisce, fÊach ar an log le haghaidh tuilleadh sonraí", "deselect_all": "Díroghnaigh Gach Rud", "details": "Sonraí", "direction": "Treo", "disable": "Díchumasaigh", "disabled": "Míchumasaithe", + "disallow_edits": "Dícheadaigh eagarthÃŗireachtaí", "discord": "Discord", "discover": "Faigh amach", "discovered_devices": "GlÊasanna a aimsíodh", "dismiss_all_errors": "Ruaig gach earrÃĄid", + "dismiss_error": "Díbhe earrÃĄid", "display_options": "Roghanna taispeÃĄna", "display_order": "Ord taispeÃĄna", "display_original_photos": "TaispeÃĄin grianghraif bhunaidh", @@ -876,9 +948,11 @@ "documentation": "DoicimÊadÃē", "done": "DÊanta", "download": "ÍoslÃŗdÃĄil", + "download_action_prompt": "Ag íoslÃŗdÃĄil {count} sÃŗcmhainní", "download_canceled": "ÍoslÃŗdÃĄil curtha ar ceal", "download_complete": "ÍoslÃŗdÃĄil críochnaithe", "download_enqueue": "ÍoslÃŗdÃĄil curtha i scuaine", + "download_error": "EarrÃĄid ÍoslÃŗdÃĄla", "download_failed": "Theip ar an íoslÃŗdÃĄil", "download_finished": "ÍoslÃŗdÃĄil críochnaithe", "download_include_embedded_motion_videos": "FíseÃĄin leabaithe", @@ -888,15 +962,15 @@ "download_paused": "ÍoslÃŗdÃĄil curtha ar sos", "download_settings": "ÍoslÃŗdÃĄil", "download_settings_description": "Bainistigh socruithe a bhaineann le híoslÃŗdÃĄil sÃŗcmhainní", + "download_started": "ÍoslÃŗdÃĄil tosaithe", + "download_sucess": "Rath ar an íoslÃŗdÃĄil", + "download_sucess_android": "TÃĄ na meÃĄin íoslÃŗdÃĄilte chuig DCIM/Immich", "download_waiting_to_retry": "Ag fanacht le hathiarracht", "downloading": "Ag íoslÃŗdÃĄil", "downloading_asset_filename": "Ag íoslÃŗdÃĄil sÃŗcmhainn {filename}", "downloading_from_icloud": "Ag íoslÃŗdÃĄil Ãŗ iCloud", "downloading_media": "Ag íoslÃŗdÃĄil na meÃĄn", - "drag_to_reorder": "Tarraing chun athordÃē", "drop_files_to_upload": "Scaoil comhaid ÃĄit ar bith le huaslÃŗdÃĄil", - "duplicate": "DÃēblach", - "duplicate_workflow": "Sreabhadh oibre dÃēblach", "duplicates": "DÃēblaigh", "duplicates_description": "RÊitigh gach grÃēpa trína lÊiriÃē cÊ acu de na dÃēblaigh, mÃĄs ann dÃŗibh.", "duration": "Fad", @@ -908,7 +982,9 @@ "edit_date_and_time": "Cuir an dÃĄta agus an t-am in eagar", "edit_date_and_time_action_prompt": "dÃĄta agus am {count} curtha in eagar", "edit_date_and_time_by_offset": "Athraigh an dÃĄta de rÊir frithÃĄireamh", + "edit_date_and_time_by_offset_interval": "Raon dÃĄta nua: {from} - {to}", "edit_description": "Cuir cur síos in eagar", + "edit_description_prompt": "Roghnaigh cur síos nua le do thoil:", "edit_exclusion_pattern": "Cuir patrÃēn eisiaimh in eagar", "edit_faces": "Cuir aghaidheanna in eagar", "edit_key": "Eochair eagarthÃŗireachta", @@ -923,6 +999,9 @@ "edit_user": "Cuir ÃēsÃĄideoir in eagar", "edit_workflow": "Sreabhadh oibre a chur in eagar", "editor": "EagarthÃŗir", + "editor_close_without_save_prompt": "Ní shÃĄbhÃĄlfar na hathruithe", + "editor_close_without_save_title": "DÃēn an t-eagarthÃŗir?", + "editor_confirm_reset_all_changes": "An bhfuil tÃē cinnte gur mian leat na hathruithe go lÊir a athshocrÃē?", "editor_discard_edits_confirm": "Scrios na heagarthÃŗireachtaí", "editor_discard_edits_prompt": "TÃĄ eagarthÃŗireachtaí neamhshÃĄbhÃĄilte agat. An bhfuil tÃē cinnte gur mhaith leat iad a chaitheamh amach?", "editor_discard_edits_title": "Scrios na heagarthÃŗireachtaí?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Cuir isteach do chÃŗd PIN", "enter_your_pin_code_subtitle": "Cuir isteach do chÃŗd PIN chun rochtain a fhÃĄil ar an bhfillteÃĄn faoi ghlas", "error": "EarrÃĄid", + "error_change_sort_album": "Theip ar ord sÃŗrtÃĄla an albaim a athrÃē", "error_delete_face": "EarrÃĄid ag scriosadh aghaidhe Ãŗn tsÃŗcmhainn", + "error_getting_places": "EarrÃĄid ag fÃĄil ÃĄiteanna", "error_loading_albums": "EarrÃĄid ag luchtÃē albaim", "error_loading_image": "EarrÃĄid ag luchtÃē íomhÃĄ", "error_loading_partners": "EarrÃĄid ag luchtÃē comhphÃĄirtithe: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Theip ar eochair an tÃĄirge a bhaint", "failed_to_reset_pin_code": "Theip ar an gcÃŗd PIN a athshocrÃē", "failed_to_stack_assets": "Theip ar shÃŗcmhainní a chruachadh", - "failed_to_tag_assets": "Theip ar shÃŗcmhainní a chlibeÃĄil", "failed_to_unstack_assets": "Theip ar shÃŗcmhainní a dhíchruachadh", "failed_to_update_notification_status": "Theip ar stÃĄdas an fhÃŗgra a nuashonrÃē", "incorrect_email_or_password": "Ríomhphost nÃŗ pasfhocal mícheart", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Cuir Cur Síos leis...", "exif_bottom_sheet_description_error": "EarrÃĄid ag nuashonrÃē an tuairisce", + "exif_bottom_sheet_details": "SONRAÍ", + "exif_bottom_sheet_location": "SUÍOMH", "exif_bottom_sheet_no_description": "Gan cur síos", + "exif_bottom_sheet_people": "DAOINE", + "exif_bottom_sheet_person_add_person": "Cuir ainm leis", "exit_slideshow": "Scoir an TaispeÃĄntais SleamhnÃĄn", "expand": "Leathnaigh", "expand_all": "Leathnaigh gach rud", + "experimental_settings_new_asset_list_subtitle": "Obair ar siÃēl", + "experimental_settings_new_asset_list_title": "Cumasaigh eangach grianghraf turgnamhach", + "experimental_settings_subtitle": "Bain ÃēsÃĄid as ar do phriacal fÊin!", + "experimental_settings_title": "Turgnamhach", "expire_after": "Rachaidh in Êag tar Êis", "expired": "Imithe in Êag", "expires_date": "Éagaíonn {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "EaspÃŗrtÃĄil mar JSON", "export_database": "EaspÃŗrtÃĄil Bunachar Sonraí", "export_database_description": "EaspÃŗrtÃĄil an bunachar sonraí SQLite", - "exposure_time": "Aga nochta", "extension": "Síneadh", "external": "Seachtrach", "external_libraries": "Leabharlanna Seachtracha", "external_network": "Líonra seachtrach", "external_network_sheet_info": "Nuair nach bhfuil sÊ ar an líonra Wi-Fi is fearr leis, ceanglÃŗidh an aip leis an bhfreastalaí tríd an gcÊad cheann de na URLanna thíos ar fÊidir leis teacht orthu, ag tosÃē Ãŗ bharr go bun", - "f_number": "Uimhir-F", "face_unassigned": "Gan sannadh", "failed": "Theip air", "failed_count": "Theip ar: {count}", "failed_to_authenticate": "Theip ar fhíordheimhniÃē", - "failed_to_delete_file": "Theip ar an gcomhad a scriosadh", "failed_to_load_assets": "Theip ar shÃŗcmhainní a lÃŗdÃĄil", "failed_to_load_folder": "Theip ar an bhfillteÃĄn a luchtÃē", "favorite": "CeanÃĄn", "favorite_action_prompt": "{count} curtha le CeanÃĄin", "favorite_or_unfavorite_photo": "Grianghraf is fearr leat nÃŗ nach fearr leat", "favorites": "CeanÃĄin", + "favorites_page_no_favorites": "Níor aimsíodh aon sÃŗcmhainní is fearr leat", "feature_photo_updated": "Grianghraf gnÊ nuashonraithe", "features": "GnÊithe", + "features_in_development": "GnÊithe i bhForbairt", "features_setting_description": "Bainistigh gnÊithe an aip", "file_name_or_extension": "Ainm comhaid nÃŗ síneadh", "file_name_text": "Ainm comhaid", + "file_name_with_value": "Ainm comhaid: {file_name}", "file_size": "MÊid comhaid", "filename": "Ainm comhaid", "filetype": "CineÃĄl comhaid", "filter": "Scagaire", + "filter_description": "Coinníollacha chun na sÃŗcmhainní sprice a scagadh", "filter_people": "Scag daoine", "filter_places": "Scag ÃĄiteanna", "filter_tags": "Scagairí clibeanna", "filters": "Scagairí", + "find_them_fast": "Aimsigh iad go tapa de rÊir ainm le cuardach", "first": "Ar dtÃēs", "fix_incorrect_match": "Deisigh cluiche mícheart", - "focal_length": "Fad FÃŗcasach", "folder": "FillteÃĄn", "folder_not_found": "Níor aimsíodh fillteÃĄn", "folders": "FillteÃĄin", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Bog grianghraif agus físeÃĄin chÃēltaca chuig bruscar do ghlÊis chun spÃĄs a shaoradh. Fanann do chÃŗipeanna ar an bhfreastalaí slÃĄn.", "free_up_space_settings_subtitle": "Saor stÃŗrÃĄil glÊis", "full_path": "CosÃĄn iomlÃĄn: {path}", - "full_path_or_folder": "CosÃĄn nÃŗ fillteÃĄn iomlÃĄn", "gcast_enabled": "Google Cast", "gcast_enabled_description": "LÃŗdÃĄlann an ghnÊ seo acmhainní seachtracha Ãŗ Google chun go n-oibreoidh sí.", "general": "GinearÃĄlta", @@ -1168,6 +1256,7 @@ "group_owner": "GrÃēpÃĄil de rÊir ÃēinÊara", "group_places_by": "GrÃēpÃĄil ÃĄiteanna de rÊir...", "group_year": "GrÃēpa de rÊir bliana", + "haptic_feedback_switch": "Cumasaigh aiseolas haptic", "haptic_feedback_title": "Aiseolas Haptic", "has_quota": "TÃĄ cuÃŗta aige", "hash_asset": "SÃŗcmhainn hash", @@ -1188,12 +1277,29 @@ "hide_schema": "Folaigh an scÊim", "hide_text_recognition": "Folaigh aitheantas tÊacs", "hide_unnamed_people": "Folaigh daoine gan ainm", + "home_page_add_to_album_conflicts": "Cuireadh sÃŗcmhainní {added} leis an albam {album}. TÃĄ sÃŗcmhainní {failed} san albam cheana fÊin.", + "home_page_add_to_album_err_local": "Ní fÊidir sÃŗcmhainní ÃĄitiÃēla a chur le halbaim go fÃŗill, ag scipeÃĄil", + "home_page_add_to_album_success": "Cuireadh sÃŗcmhainní {added} leis an albam {album}.", + "home_page_album_err_partner": "Ní fÊidir sÃŗcmhainní comhphÃĄirtíochta a chur le halbam go fÃŗill, ag scipeÃĄil", + "home_page_archive_err_local": "Ní fÊidir sÃŗcmhainní ÃĄitiÃēla a chartlannÃē go fÃŗill, ag scipeÃĄil", + "home_page_archive_err_partner": "Ní fÊidir sÃŗcmhainní comhphÃĄirtíochta a chartlannÃē, ag scipeÃĄil", "home_page_building_timeline": "Ag tÃŗgÃĄil an amlíne", + "home_page_delete_err_partner": "Ní fÊidir sÃŗcmhainní comhphÃĄirtíochta a scriosadh, ag scipeÃĄil", + "home_page_delete_remote_err_local": "SÃŗcmhainní ÃĄitiÃēla i scriosadh roghnÃēchÃĄin iargÃēlta, ag scipeÃĄil", + "home_page_favorite_err_local": "Ní fÊidir sÃŗcmhainní ÃĄitiÃēla a chur i bhfabhar go fÃŗill, ag scipeÃĄil", + "home_page_favorite_err_partner": "Ní fÊidir sÃŗcmhainní comhphÃĄirtíochta a chur i bhfabhar go fÃŗill, ag scipeÃĄil", + "home_page_first_time_notice": "MÃĄs Ê seo an chÊad uair duit an aip a ÃēsÃĄid, dÊan cinnte albam cÃēltaca a roghnÃē ionas gur fÊidir leis an amlíne grianghraif agus físeÃĄin a líonadh ann", + "home_page_locked_error_local": "Ní fÊidir sÃŗcmhainní ÃĄitiÃēla a bhogadh chuig fillteÃĄn faoi ghlas, ag scipeÃĄil", + "home_page_locked_error_partner": "Ní fÊidir sÃŗcmhainní comhphÃĄirtíochta a bhogadh chuig fillteÃĄn faoi ghlas, ag scipeÃĄil", + "home_page_share_err_local": "Ní fÊidir sÃŗcmhainní ÃĄitiÃēla a roinnt tríd an nasc, ag scipeÃĄil", + "home_page_upload_err_limit": "Ní fÊidir ach 30 sÃŗcmhainn ar a mhÊad a uaslÃŗdÃĄil ag an am cÊanna, ag scipeÃĄil", "host": "Óstach", "hour": "Uair an chloig", "hours": "Uaireanta", "id": "ID", "idle": "Díomhaoin", + "ignore_icloud_photos": "DÊan neamhaird de ghrianghraif iCloud", + "ignore_icloud_photos_description": "Ní uaslÃŗdÃĄlfar grianghraif atÃĄ stÃŗrÃĄilte ar iCloud chuig freastalaí Immich", "image": "ÍomhÃĄ", "image_alt_text_date": "{isVideo, select, true {FíseÃĄn} other {ÍomhÃĄ}} tÃŗgtha ar {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {ÍomhÃĄ}} tÃŗgtha le {person1} ar {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {FíseÃĄn} other {ÍomhÃĄ}} tÃŗgtha i {city}, {country} le {person1} agus {person2} ar {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {ÍomhÃĄ}} tÃŗgtha i {city}, {country} le {person1}, {person2}, agus {person3} ar {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tÃŗgtha i {city}, {country} le {person1}, {person2}, agus {additionalCount, number} daoine eile ar {date}", + "image_saved_successfully": "ÍomhÃĄ sÃĄbhÃĄilte", + "image_viewer_page_state_provider_download_started": "ÍoslÃŗdÃĄil Tosaithe", + "image_viewer_page_state_provider_download_success": "ÍoslÃŗdÃĄil RathÃēil", + "image_viewer_page_state_provider_share_error": "EarrÃĄid Chomhroinnte", "immich_logo": "LÃŗgÃŗ Immich", "immich_web_interface": "ComhÊadan GrÊasÃĄin Immich", "import_from_json": "IompÃŗrtÃĄil Ãŗ JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Sciar aonair", "individual_shares": "Scaireanna aonair", "info": "Eolas", - "integrity_checks": "SeiceÃĄlacha Ionracais", "interval": { "day_at_onepm": "Gach lÃĄ ag 1pm", "hours": "Gach {hours, plural, one {uair an chloig} other {{hours, number} uair an chloig}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "FormÃĄid dÃĄta neamhbhailí", "invite_people": "Tabhair cuireadh do dhaoine", "invite_to_album": "Cuireadh chuig albam", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Rith Fetch {dateTime}", + "ios_debug_info_last_sync_at": "SioncrÃŗnÃē deireanach {dateTime}", + "ios_debug_info_no_processes_queued": "Níl aon phrÃŗisis chÃēlra i scuaine", + "ios_debug_info_no_sync_yet": "Níl aon phost sioncrÃŗnaithe cÃēlra rite fÃŗs", + "ios_debug_info_processes_queued": "{count, plural, one {{count} prÃŗiseas cÃēlra queued} other {{count} prÃŗisis chÃēlra queued}}", + "ios_debug_info_processing_ran_at": "Rith an phrÃŗiseÃĄil {dateTime}", "items_count": "{count, plural, one {# mír} other {# míreanna}}", "jobs": "Poist", + "json_editor": "EagarthÃŗir JSON", + "json_error": "EarrÃĄid JSON", "keep": "CoimeÃĄd", "keep_albums": "Coinnigh albaim", "keep_albums_count": "Ag coinneÃĄil {count} {count, plural, one {album} other {albums}}", @@ -1259,12 +1375,14 @@ "leave": "FÃĄg", "leave_album": "FÃĄg an t-albam", "lens_model": "MÃēnla lionsa", - "less": "Níos lÃē", "let_others_respond": "Lig do dhaoine eile freagairt", "level": "LeibhÊal", "library": "Leabharlann", "library_add_folder": "Cuir fillteÃĄn leis", "library_edit_folder": "Cuir fillteÃĄn in eagar", + "library_options": "Roghanna leabharlainne", + "library_page_device_albums": "Albaim ar an nGlÊas", + "library_page_new_album": "Albam nua", "library_page_sort_asset_count": "Líon na sÃŗcmhainní", "library_page_sort_created": "DÃĄta cruthaithe", "library_page_sort_last_modified": "Athraithe go deireanach", @@ -1274,14 +1392,11 @@ "light_theme": "Athraigh go tÊama Êadrom", "like": "Is maith liom", "like_deleted": "Scriosadh an rud is maith liom", - "link": "Nasc", "link_motion_video": "FíseÃĄn gluaiseachta nasctha", "link_to_docs": "Le haghaidh tuilleadh eolais, fÊach ar an doicimÊadÃē.", "link_to_oauth": "Nasc le OAuth", "linked_oauth_account": "Cuntas OAuth nasctha", "list": "Liosta", - "live": "Beo", - "load_more": "Luchtaigh Tuilleadh", "loading": "Ag luchtÃē", "loading_search_results_failed": "Theip ar lÃŗdÃĄil na dtorthaí cuardaigh", "local": "ÁitiÃēil", @@ -1310,9 +1425,11 @@ "login": "LogÃĄil Isteach", "login_disabled": "TÃĄ logÃĄil isteach díchumasaithe", "login_form_api_exception": "Eisceacht API. SeiceÃĄil URL an fhreastalaí agus dÊan iarracht arís.", + "login_form_back_button_text": "Ar ais", "login_form_email_hint": "doríomhphost@ríomhphost.com", "login_form_endpoint_hint": "http://ip-do-fhreastalaí:port", "login_form_endpoint_url": "URL Deireadhphointe an Fhreastalaí", + "login_form_err_http": "Sonraigh http:// nÃŗ https:// le do thoil", "login_form_err_invalid_email": "Ríomhphost Neamhbhailí", "login_form_err_invalid_url": "URL neamhbhailí", "login_form_err_leading_whitespace": "SpÃĄs bÃĄn tosaigh", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "EarrÃĄid ag logÃĄil isteach, seiceÃĄil URL an fhreastalaí, an ríomhphost agus an focal faire", "login_form_handshake_exception": "Bhí Eisceacht LÃĄmh-Chroith leis an bhfreastalaí. Cumasaigh tacaíocht do theastas fÊinshínithe sna socruithe mÃĄ tÃĄ teastas fÊinshínithe in ÃēsÃĄid agat.", "login_form_password_hint": "pasfhocal", - "login_form_server_empty": "Cuir isteach URL freastalaí", - "login_form_server_error": "Níorbh fhÊidir ceangal leis an bhfreastalaí", - "login_has_been_disabled": "TÃĄ logÃĄil isteach díchumasaithe", + "login_form_save_login": "Fan logÃĄilte isteach", + "login_form_server_empty": "Cuir isteach URL freastalaí.", + "login_form_server_error": "Níorbh fhÊidir ceangal leis an bhfreastalaí.", + "login_has_been_disabled": "TÃĄ logÃĄil isteach díchumasaithe.", "login_password_changed_error": "Tharla earrÃĄid agus do phasfhocal ÃĄ nuashonrÃē", "login_password_changed_success": "Nuashonraíodh an focal faire go rathÃēil", "logout_all_device_confirmation": "An bhfuil tÃē cinnte gur mian leat logÃĄil amach as gach glÊas?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Bunachar Sonraí ÃĄ AthchÃŗiriÃē", "maintenance_description": "TÃĄ Immich curtha i mÃŗd cothabhÃĄla.", "maintenance_end": "Deireadh a chur leis an modh cothabhÃĄla", + "maintenance_end_error": "Theip ar an modh cothabhÃĄla a chríochnÃē.", "maintenance_logged_in_as": "LogÃĄilte isteach faoi lÃĄthair mar {user}", "maintenance_restore_from_backup": "AthchÃŗirigh Ãŗ ChÃēltaca", "maintenance_restore_library": "AthchÃŗirigh Do Leabharlann", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Oscail socruithe", "manage_media_access_subtitle": "Lig don aip Immich comhaid meÃĄn a bhainistiÃē agus a bhogadh.", "manage_media_access_title": "Rochtain Bainistíochta MeÃĄn", + "manage_shared_links": "Bainistigh naisc chomhroinnte", "manage_sharing_with_partners": "Bainistigh comhroinnt le comhphÃĄirtithe", "manage_the_app_settings": "Bainistigh socruithe an aip", "manage_your_account": "Bainistigh do chuntas", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Bainistigh do ghlÊasanna logÃĄilte isteach", "manage_your_oauth_connection": "Bainistigh do nasc OAuth", "map": "LÊarscÃĄil", + "map_assets_in_bounds": "{count, plural, =0 {Gan aon ghrianghraif sa cheantar seo} one {# grianghraf} other {# grianghraif}}", "map_cannot_get_user_location": "Ní fÊidir suíomh an ÃēsÃĄideora a fhÃĄil", + "map_location_dialog_yes": "TÃĄ", "map_location_picker_page_use_location": "ÚsÃĄid an suíomh seo", "map_location_service_disabled_content": "Ní mÃŗr seirbhís suímh a chumasÃē chun sÃŗcmhainní Ãŗ do shuíomh reatha a thaispeÃĄint. Ar mhaith leat Ê a chumasÃē anois?", "map_location_service_disabled_title": "Seirbhís Suímh díchumasaithe", - "map_marker_for_image": "MarcÃŗir lÊarscÃĄile don íomhÃĄ a tÃŗgadh i {city}, {country}", + "map_marker_for_images": "MarcÃŗir lÊarscÃĄile le haghaidh íomhÃĄnna a tÃŗgadh i {city}, {country}", "map_marker_with_image": "MarcÃŗir lÊarscÃĄile le híomhÃĄ", "map_no_location_permission_content": "TÃĄ cead suímh ag teastÃĄil chun sÃŗcmhainní a thaispeÃĄint Ãŗ do shuíomh reatha. Ar mhaith leat Ê a cheadÃē anois?", "map_no_location_permission_title": "Cead Suímh diÃēltaithe", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Laethanta anuas ({days})", "map_settings_date_range_option_year": "An bhliain seo caite", "map_settings_date_range_option_years": "Blianta {years} anuas", + "map_settings_dialog_title": "Socruithe LÊarscÃĄile", "map_settings_include_show_archived": "Cuir Cartlannaithe san ÃĄireamh", "map_settings_include_show_partners": "Cuir ComhphÃĄirtithe san ÃĄireamh", "map_settings_only_show_favorites": "TaispeÃĄin Is Fearr Leat AmhÃĄin", "map_settings_theme_settings": "TÊama LÊarscÃĄile", + "map_zoom_to_see_photos": "ZÃēmÃĄil amach chun grianghraif a fheiceÃĄil", "mark_all_as_read": "MarcÃĄil gach rud mar lÊite", + "mark_as_read": "MarcÃĄil mar lÊite", "marked_all_as_read": "MarcÃĄladh gach rud mar lÊite", "matches": "Cluichí", "matching_assets": "SÃŗcmhainní MeaitseÃĄla", - "media_chrome": { - "auto": "Uath", - "captions": "Fotheidil", - "captions_off": "As", - "closed_captions": "fotheidil dÃēnta", - "decode_error": "EarrÃĄid díchÃŗdaithe", - "disable_captions": "Díchumasaigh fotheidil", - "enable_captions": "Cumasaigh fotheidil", - "enter_fullscreen_mode": "TÊigh isteach i mÃŗd lÃĄnscÃĄileÃĄin", - "exit_fullscreen_mode": "Scoir mÃŗd lÃĄnscÃĄileÃĄin", - "loop": "LÃēb", - "media_error_description": "EarrÃĄid sna meÃĄin ba chÃēis leis an athsheinm. B’fhÊidir go bhfuil na meÃĄin truaillithe nÃŗ nach dtacaíonn do bhrabhsÃĄlaí leis an bhformÃĄid seo.", - "media_loading": "luchtÃē meÃĄn", - "mute": "Balbhaigh", - "network_error": "EarrÃĄid líonra", - "network_error_description": "Theip ar íoslÃŗdÃĄil na meÃĄn de dheasca earrÃĄide líonra.", - "not_supported_error": "Foinse Gan Tacaíocht", - "playback_rate": "RÃĄta athsheinm", - "playback_rate_current": "rÃĄta athsheinm reatha", - "playback_rate_value": "RÃĄta athsheinm {playbackRate}", - "playback_time": "am athsheinm", - "quality": "CÃĄilíocht", - "second": "soicind", - "seconds": "soicindí", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} fÃĄgtha", - "unmute": "Díbhalbhaigh", - "unsupported_error_description": "Tharla earrÃĄid nach dtacaítear lÊi. Theip ar an bhfreastalaí nÃŗ ar an líonra, nÃŗ ní thacaíonn do bhrabhsÃĄlaí leis an bhformÃĄid seo.", - "video_not_loaded_unknown_time": "físeÃĄn gan luchtÃē, am anaithnid.", - "video_player": "seinnteoir físeÃĄin", - "volume": "toirt" - }, "media_type": "CineÃĄl meÃĄn", "memories": "Cuimhní cinn", "memories_all_caught_up": "Gach rud gafa suas", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Ar mhaith leat na daoine seo a chumasc? Ní fÊidir an gníomh seo a aisiompÃē.", "merge_people_successfully": "Daoine a chumasc go rathÃēil", "merged_people_count": "Cumaiscthe {count, plural, one {# duine} other {# daoine}}", - "minFaces": "ÍosmhÊid aghaidheanna", - "minFaces_description": "An líon íosta aghaidheanna aitheanta le go dtaispeÃĄnfar duine", "minimize": "Íoslaghdaigh", "minute": "NÃŗimÊad", "minutes": "NÃŗimÊid", + "mirror_horizontal": "CothromÃĄnach", + "mirror_vertical": "Ingearach", "missing": "Ar iarraidh", "mobile_app": "Aip Shoghluaiste", "mobile_app_download_onboarding_note": "ÍoslÃŗdÃĄil an aip shoghluaiste tionlacain ag baint ÃēsÃĄide as na roghanna seo a leanas", "model": "Samhail", - "modify_date": "DÃĄta Athraithe", "month": "Mí", + "monthly_title_text_date_format": "MMMM y", "more": "Tuilleadh", - "motion": "Gluaiseacht", "move": "Bog", + "move_down": "Bog síos", "move_off_locked_folder": "Bog amach as fillteÃĄn faoi ghlas", "move_to": "Bog go", "move_to_device_trash": "Bog go dtí bruscar an ghlÊis", "move_to_lock_folder_action_prompt": "{count} curtha leis an bhfillteÃĄn faoi ghlas", "move_to_locked_folder": "Bog go fillteÃĄn faoi ghlas", "move_to_locked_folder_confirmation": "Bainfear na grianghraif agus na físeÃĄin seo as na halbaim uile, agus ní bheidh siad le feiceÃĄil ach amhÃĄin Ãŗn bhfillteÃĄn faoi ghlas", + "move_up": "Bog suas", + "moved_to_archive": "Bogadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} chuig an gcartlann", + "moved_to_library": "Bogadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} chuig an leabharlann", "moved_to_trash": "Bogtha chuig an mbruscar", + "multiselect_grid_edit_date_time_err_read_only": "Ní fÊidir dÃĄta sÃŗcmhainn(í) inlÊite amhÃĄin a chur in eagar, ag scipeÃĄil", + "multiselect_grid_edit_gps_err_read_only": "Ní fÊidir suíomh sÃŗcmhainn(í) inlÊite amhÃĄin a chur in eagar, ag scipeÃĄil", "mute_memories": "Cuimhní Balbhaigh", "my_albums": "Mo chuid albaim", - "my_immich_description": "CÃŗipeÃĄil an leathanach reatha mar nasc My Immich", - "my_immich_title": "Mo nasc Immich", "name": "Ainm", "name_or_nickname": "Ainm nÃŗ leasainm", "name_required": "TÃĄ ainm ag teastÃĄil", @@ -1480,11 +1576,12 @@ "never": "Choíche", "new_album": "Albam Nua", "new_api_key": "Eochair API Nua", - "new_feature": "GnÊ Nua", + "new_date_range": "Raon dÃĄta nua", "new_password": "Pasfhocal nua", "new_person": "Duine nua", "new_pin_code": "CÃŗd PIN nua", "new_pin_code_subtitle": "Seo Ê an chÊad uair duit rochtain a fhÃĄil ar an bhfillteÃĄn faoi ghlas. Cruthaigh cÃŗd PIN chun rochtain shlÃĄn a fhÃĄil ar an leathanach seo", + "new_timeline": "Amlíne Nua", "new_update": "NuashonrÃē nua", "new_user_created": "ÚsÃĄideoir nua cruthaithe", "new_version_available": "LEAGAN NUA AR FÁIL", @@ -1492,6 +1589,7 @@ "next": "Ar Aghaidh", "next_memory": "An chÊad chuimhne eile", "no": "Níl", + "no_actions_added": "Níl aon ghníomhartha curtha leis fÃŗs", "no_albums_found": "Níor aimsíodh aon albaim", "no_albums_message": "Cruthaigh albam chun do ghrianghraif agus do fhíseÃĄin a eagrÃē", "no_albums_with_name_yet": "Is cosÃēil nach bhfuil aon albaim agat leis an ainm seo go fÃŗill.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Ní bhfuarthas aon ghlÊasanna teilgthe", "no_checksum_local": "Níl aon suim seiceÃĄla ar fÃĄil - ní fÊidir sÃŗcmhainní ÃĄitiÃēla a aisghabhÃĄil", "no_checksum_remote": "Níl aon suim seiceÃĄla ar fÃĄil - ní fÊidir sÃŗcmhainn iargÃēlta a aisghabhÃĄil", + "no_configuration_needed": "Níl aon chumraíocht ag teastÃĄil", "no_devices": "Gan aon fheistí Ãēdaraithe", "no_duplicates_found": "Ní bhfuarthas aon dÃēblaigh.", "no_exif_info_available": "Níl aon fhaisnÊis exif ar fÃĄil", "no_explore_results_message": "UaslÃŗdÃĄil tuilleadh grianghraf chun do bhailiÃēchÃĄn a iniÃēchadh.", "no_favorites_message": "Cuir na cinn is fearr leat leis chun do phictiÃēir agus do fhíseÃĄin is fearr a aimsiÃē go tapa", + "no_filters_added": "Níl aon scagairí curtha leis fÃŗs", "no_libraries_message": "Cruthaigh leabharlann sheachtrach chun do ghrianghraif agus físeÃĄin a fheiceÃĄil", "no_local_assets_found": "Ní bhfuarthas aon sÃŗcmhainní ÃĄitiÃēla leis an tsuim sheiceÃĄla seo", + "no_location_set": "Níl aon suíomh socraithe", "no_locked_photos_message": "TÃĄ grianghraif agus físeÃĄin sa bhfillteÃĄn faoi ghlas i bhfolach agus ní thaispeÃĄnfar iad agus tÃē ag brabhsÃĄil nÃŗ ag cuardach do leabharlann.", "no_name": "Gan Ainm", "no_notifications": "Gan aon fhÃŗgraí", @@ -1518,19 +1619,14 @@ "no_results": "Gan aon torthaí", "no_results_description": "Bain triail as comhchiallach nÃŗ eochairfhocal níos ginearÃĄlta", "no_shared_albums_message": "Cruthaigh albam chun grianghraif agus físeÃĄin a roinnt le daoine i do líonra", - "no_steps": "Níl aon chÊimeanna curtha leis fÃŗs", + "no_uploads_in_progress": "Níl aon uaslÃŗdÃĄlacha ar siÃēl", "none": "Dada", "not_allowed": "Ní cheadaítear", "not_available": "N/B", "not_in_any_album": "Ní in aon albam", "not_selected": "Níor roghnaíodh", - "not_set": "Gan socrÃē", "notes": "NÃŗtaí", "nothing_here_yet": "Níl aon rud anseo fÃŗs", - "notification_backup_reliability": "Cumasaigh fÃŗgraí chun iontaofacht cÃēltaca cÃēlra a fheabhsÃē", - "notification_enabled_list_tile_content": "ÚsÃĄideann Immich fÃŗgraí le haghaidh cÃēltaca cÃēlra. Bainistigh iad i socruithe do ghlÊis.", - "notification_enabled_list_tile_open_button": "Oscail socruithe", - "notification_enabled_list_tile_title": "FÃŗgraí cumasaithe", "notification_permission_dialog_content": "Chun fÃŗgraí a chumasÃē, tÊigh go Socruithe agus roghnaigh ceadaigh.", "notification_permission_list_tile_content": "Tabhair cead fÃŗgraí a chumasÃē.", "notification_permission_list_tile_enable_button": "Cumasaigh FÃŗgraí", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Cumraitheoir Obtainium", "obtainium_configurator_instructions": "Bain ÃēsÃĄid as Obtainium chun an aip Android a shuiteÃĄil agus a nuashonrÃē go díreach Ãŗ eisiÃēint Immich ar GitHub. Cruthaigh eochair API agus roghnaigh malairt chun do nasc cumraíochta Obtainium a chruthÃē", "ocr": "OCR", - "ocr_body": "LÊann Immich an tÊacs taobh istigh de do ghrianghraif anois, ionas gur fÊidir leat iad a chuardach de rÊir a bhfuil iontu.", - "ocr_title": "Cuardaigh tÊacs i do ghrianghraif", "official_immich_resources": "Acmhainní OifigiÃēla Immich", "offline": "As líne", "offset": "FrithÃĄireamh", @@ -1562,8 +1656,6 @@ "open": "Oscail", "open_calendar": "Oscail an fÊilire", "open_in_browser": "Oscail sa bhrabhsÃĄlaí", - "open_in_immich_body": "Socraigh Immich mar do ghailearaí ar Android chun grianghraif a oscailt go díreach Ãŗ aipeanna eile.", - "open_in_immich_title": "Oscail grianghraif in Immich", "open_in_map_view": "Oscail i radharc lÊarscÃĄile", "open_in_openstreetmap": "Oscail in OpenStreetMap", "open_the_search_filters": "Oscail na scagairí cuardaigh", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Eagraigh in albaim", "organize_into_albums_description": "Cuir grianghraif atÃĄ ann cheana fÊin in albaim ag baint ÃēsÃĄide as na socruithe sioncrÃŗnaithe reatha", "organize_your_library": "Eagraigh do leabharlann", - "orientation": "Treoshuíomh", "original": "bunaidh", "other": "Eile", "other_devices": "GlÊasanna eile", + "other_entities": "Eintitis eile", "other_variables": "AthrÃŗga eile", "owned": "Faoi ÃēinÊireacht", "owner": "ÚinÊir", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Do ghrianghraif agus do fhíseÃĄin go lÊir seachas iad siÃēd atÃĄ i gCartlann agus Scriosta", "partner_can_access_location": "An ÃĄit inar tÃŗgadh do ghrianghraif", "partner_list_user_photos": "Grianghraif {user}", + "partner_list_view_all": "FÊach ar gach rud", "partner_page_empty_message": "Níl do ghrianghraif roinnte le haon phÃĄirtí go fÃŗill.", "partner_page_no_more_users": "Níl aon ÃēsÃĄideoirí eile le cur leis", + "partner_page_partner_add_failed": "Theip ar chomhphÃĄirtí a chur leis", "partner_page_select_partner": "Roghnaigh comhphÃĄirtí", + "partner_page_shared_to_title": "Roinnte le", "partner_page_stop_sharing_content": "Ní bheidh {partner} in ann rochtain a fhÃĄil ar do ghrianghraif a thuilleadh.", "partner_sharing": "Comhroinnt ChomhphÃĄirtíochta", "partners": "ComhphÃĄirtithe", @@ -1609,6 +1704,8 @@ "people": "Daoine", "people_edits_count": "EagarthÃŗireacht dÊanta {count, plural, one {# duine} other {# daoine}}", "people_feature_description": "Ag brabhsÃĄil grianghraif agus físeÃĄin grÃēpÃĄilte de rÊir daoine", + "people_selected": "{count, plural, one {# duine roghnaithe} other {# duine roghnaithe}}", + "people_sidebar_description": "TaispeÃĄin nasc chuig Daoine sa bharra taoibh", "permanent_deletion_warning": "Rabhadh scriosadh buan", "permanent_deletion_warning_setting_description": "TaispeÃĄin rabhadh agus sÃŗcmhainní ÃĄ scriosadh go buan", "permanently_delete": "Scrios go buan", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Scriosta go buan {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "permission": "Cead", "permission_empty": "Níor cheart go mbeadh do chead folamh", + "permission_onboarding_back": "Ar ais", + "permission_onboarding_continue_anyway": "Lean ar aghaidh ar aon nÃŗs", + "permission_onboarding_get_started": "Tosaigh anois", + "permission_onboarding_go_to_settings": "TÊigh go dtí na socruithe", + "permission_onboarding_permission_denied": "Cead diÃēltaithe. Chun Immich a ÃēsÃĄid, deonaigh ceadanna grianghraf agus físeÃĄin sna Socruithe.", + "permission_onboarding_permission_granted": "Cead tugtha! TÃĄ gach rud rÊidh.", + "permission_onboarding_permission_limited": "Cead teoranta. Chun ligean d’Immich cÃēltaca a dhÊanamh de do bhailiÃēchÃĄn gailearaí iomlÃĄn agus Ê a bhainistiÃē, deonaigh ceadanna grianghraf agus físeÃĄin sna Socruithe.", + "permission_onboarding_request": "Éilíonn Immich cead chun do ghrianghraif agus do fhíseÃĄin a fheiceÃĄil.", "person": "Duine", "person_age_months": "{months, plural, one {# mí} other {# míonna}} d'aois", "person_age_year_months": "1 bhliain, {months, plural, one {# mí} other {# míonna}} d'aois", - "person_age_years": "{years, plural, one {# bliain} other {# blianta}} d'aois", + "person_age_years": "{years, plural, other {# blianta}} d'aois", "person_birthdate": "Rugadh ar {date}", "person_hidden": "{name}{hidden, select, true { (i bhfolach)} other {}}", "person_recognized": "Duine aitheanta", + "person_selected": "Duine roghnaithe", "photo_shared_all_users": "Is cosÃēil gur roinn tÃē do ghrianghraif le gach ÃēsÃĄideoir nÃŗ nach bhfuil aon ÃēsÃĄideoir agat le roinnt leis.", "photos": "Grianghraif", "photos_and_videos": "Grianghraif & FíseÃĄin", + "photos_count": "{count, plural, one {{count, number} Grianghraf} other {{count, number} Grianghraif}}", "photos_from_previous_years": "Grianghraif Ãŗ bhlianta roimhe seo", + "photos_only": "Grianghraif amhÃĄin", "pick_a_location": "Roghnaigh suíomh", "pick_custom_range": "Raon saincheaptha", "pick_date_range": "Roghnaigh raon dÃĄta", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Is fearr físeÃĄin bhunaidh a athsheinm seachas físeÃĄin thraschÃŗdaithe. Mura bhfuil an tsÃŗcmhainn bhunaidh comhoiriÃēnach, b’fhÊidir nach n-athsheinmfear i gceart í.", "play_transcoded_video": "Seinn físeÃĄn traschÃŗdaithe", "please_auth_to_access": "Fíordheimhnigh le do thoil chun rochtain a fhÃĄil", - "plugin_method_filter_type": "Scagaire", - "plugin_method_filter_type_description": "Is fÊidir leis an modh seo imeachtaí a scagadh agus cosc coinníollach a chur ar chÊimeanna ina dhiaidh sin Ãŗ rith", "port": "Port", "preferences_settings_subtitle": "Bainistigh roghanna an aip", "preferences_settings_title": "Roghanna", @@ -1665,11 +1771,11 @@ "privacy": "PríobhÃĄideacht", "profile": "PrÃŗifíl", "profile_drawer_app_logs": "Logaí", + "profile_drawer_client_server_up_to_date": "TÃĄ an cliant agus an freastalaí cothrom le dÃĄta", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "MÃŗd lÊite amhÃĄin cumasaithe. BrÃēigh ar dheilbhín an avatÃĄir ÃēsÃĄideora le himeacht.", "profile_image_of_user": "ÍomhÃĄ phrÃŗifíle de {user}", "profile_picture_set": "Sraith pictiÃēr prÃŗifíle.", - "projection_type": "CineÃĄl Teilgean", "public_album": "Albam poiblí", "public_share": "Comhroinn Phoiblí", "purchase_account_info": "Tacaíochtaí", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "StÃĄdas an tacaí", "purchase_individual_title": "Aonair", "purchase_input_suggestion": "An bhfuil eochair tÃĄirge agat? Cuir isteach an eochair thíos", + "purchase_license_subtitle": "Ceannaigh Immich chun tacÃē le forbairt leanÃēnach na seirbhíse", "purchase_lifetime_description": "Ceannach saoil", "purchase_option_title": "ROGHANNA CEANNAIGH", "purchase_panel_info_1": "TÃŗgann sÊ go leor ama agus iarrachta Immich a thÃŗgÃĄil, agus tÃĄ innealtÃŗirí lÃĄnaimseartha againn ag obair air chun Ê a dhÊanamh chomh maith agus is fÊidir linn. Is Ê ÃĄr misean go mbeidh bogearraí foinse oscailte agus cleachtais ghnÃŗ eiticiÃēla ina bhfoinse ioncaim inbhuanaithe d’fhorbrÃŗirí agus go gcruthÃŗfar ÊiceachÃŗras a urramaíonn príobhÃĄideacht le roghanna fíor seachas seirbhísí scamall saothraithe.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Athshannadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} do dhuine nua", "reassing_hint": "Sannadh sÃŗcmhainní roghnaithe do dhuine atÃĄ ann cheana fÊin", "recent": "Le dÊanaí", + "recent_albums": "Albaim le dÊanaí", "recent_searches": "Cuardaigh le dÊanaí", "recently_added": "Cuireadh leis le dÊanaí", - "recently_added_body": "LÊim díreach chuig gach rud a chuir tÃē leis le dÊanaí ar leathanach tiomnaithe.", - "recently_added_description": "BrabhsÃĄil do shÃŗcmhainní, sÃŗrtÃĄilte de rÊir cathain a uaslÃŗdÃĄladh iad chuig Immich", "recently_added_page_title": "Curtha leis le DÊanaí", - "recently_added_title": "Cuireadh leis le dÊanaí", "recently_taken": "TÃŗgtha le dÊanaí", + "recently_taken_page_title": "TÃŗgtha le DÊanaí", "refresh": "Athnuachan", "refresh_encoded_videos": "Athnuaigh físeÃĄin ionchÃŗdaithe", "refresh_faces": "Athnuachan aghaidheanna", "refresh_metadata": "Athnuaigh meiteashonraí", "refresh_thumbnails": "Athnuachan mionsamhlacha", "refreshed": "Athnuachan", + "refreshes_every_file": "AthlÊann sÊ gach comhad atÃĄ ann cheana fÊin agus comhad nua", "refreshing_encoded_video": "FíseÃĄn ionchÃŗdaithe ÃĄ athnuachan", "refreshing_faces": "Aghaidheanna athnuachana", "refreshing_metadata": "Meiteashonraí ÃĄ n-athnuachan", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "An bhfuil tÃē cinnte gur mian leat {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}} a bhaint den nasc comhroinnte seo?", "remove_assets_title": "Bain sÃŗcmhainní?", "remove_custom_date_range": "Bain raon dÃĄta saincheaptha", - "remove_filter": "Bain an scagaire", + "remove_deleted_assets": "Bain SÃŗcmhainní Scriosta", "remove_from_album": "Bain den albam", "remove_from_album_action_prompt": "Baineadh {count} den albam", "remove_from_favorites": "Bain as ceanÃĄin", @@ -1756,22 +1863,29 @@ "remove_memory": "Bain cuimhne", "remove_photo_from_memory": "Bain grianghraf as an gcuimhne seo", "remove_tag": "Bain an chlib", + "remove_url": "Bain URL", "remove_user": "Bain ÃēsÃĄideoir", "removed_api_key": "Eochair API bainte: {name}", "removed_from_archive": "Bainte as an gcartlann", "removed_from_favorites": "Bainte as na cinn is ansa leat", "removed_from_favorites_count": "{count, plural, other {Baineadh #}} Ãŗ ceanÃĄin", "removed_memory": "Cuimhne bainte", + "removed_photo_from_memory": "Baineadh grianghraf as an gcuimhne", "removed_tagged_assets": "Baineadh an clib as {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "rename": "Athainmnigh", + "repair": "DeisiÃē", + "repair_no_results_message": "Beidh comhaid neamhrianaithe agus ar iarraidh le feiceÃĄil anseo", + "replace_with_upload": "Cuir uaslÃŗdÃĄil ina ÃĄit", "repository": "StÃŗr", "require_password": "Éiligh pasfhocal", + "require_user_to_change_password_on_first_login": "Éiligh ar an ÃēsÃĄideoir an focal faire a athrÃē ar an gcÊad logÃĄil isteach", "rescan": "Athscanadh", "reset": "Athshocraigh", "reset_password": "Athshocraigh an focal faire", "reset_people_visibility": "Athshocraigh infheictheacht daoine", "reset_pin_code": "Athshocraigh an cÃŗd PIN", "reset_pin_code_description": "MÃĄ rinne tÃē dearmad ar do chÃŗd PIN, is fÊidir leat teagmhÃĄil a dhÊanamh le riarthÃŗir an fhreastalaí chun Ê a athshocrÃē", + "reset_pin_code_success": "Athshocraíodh an cÃŗd PIN go rathÃēil", "reset_pin_code_with_password": "Is fÊidir leat do chÃŗd PIN a athshocrÃē i gcÃŗnaí le do phasfhocal", "reset_sqlite": "Athshocraigh Bunachar Sonraí SQLite", "reset_sqlite_clear_app_data": "Glan Sonraí", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "RÊitíodh na dÃēblaigh go lÊir", "restore": "AthchÃŗirigh", "restore_all": "AthchÃŗirigh gach rud", + "restore_trash_action_prompt": "{count} athchÃŗirithe Ãŗn mbruscar", "restore_user": "AthchÃŗirigh ÃēsÃĄideoir", "restored_asset": "SÃŗcmhainn athchÃŗirithe", "resume": "AtosÃē", "resume_paused_jobs": "AtosÃē {count, plural, one {# post ar sos} other {# poist ar sos}}", + "retry_upload": "DÊan iarracht arís uaslÃŗdÃĄil", "review_duplicates": "Athbhreithnigh dÃēblaigh", "review_large_files": "Athbhreithnigh comhaid mhÃŗra", "role": "RÃŗl", @@ -1796,6 +1912,7 @@ "role_viewer": "AmharcÃŗir", "running": "Ag rith", "save": "SÃĄbhÃĄil", + "save_to_gallery": "SÃĄbhÃĄil chuig an ngailearaí", "saved": "SÃĄbhÃĄilte", "saved_api_key": "Eochair API SÃĄbhÃĄilte", "saved_profile": "PrÃŗifíl shÃĄbhÃĄilte", @@ -1806,10 +1923,9 @@ "scan": "Scanadh", "scan_all_libraries": "ScanÃĄil Gach Leabharlann", "scan_library": "Scanadh", + "scan_settings": "Socruithe Scanadh", "scanning": "Ag scanadh", "scanning_for_album": "Ag scanadh le haghaidh albam...", - "screencast_mode_description": "TaispeÃĄin tÃĄscairí imeachta mÊarchlÃĄir agus luiche ar an scÃĄileÃĄn", - "screencast_mode_title": "MÃŗd craolta scÃĄileÃĄin a scorÃĄnaigh", "search": "Cuardaigh", "search_albums": "Cuardaigh albaim", "search_by_context": "Cuardaigh de rÊir comhthÊacs", @@ -1817,8 +1933,6 @@ "search_by_description_example": "LÃĄ siÃēlÃŗide i Sapa", "search_by_filename": "Cuardaigh de rÊir ainm comhaid nÃŗ síneadh", "search_by_filename_example": "i.e. IMG_1234.JPG nÃŗ PNG", - "search_by_full_path": "Cuardaigh de rÊir cosÃĄin iomlÃĄin nÃŗ fillteÃĄin", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - is fÊidir leat cuardach a dhÊanamh ar Thionscadail, 3D, PriontÃĄil, 2026 srl.", "search_by_ocr": "Cuardaigh de rÊir OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Cuardaigh samhail lionsa...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Roghnaigh suíomh", "search_filter_media_type": "CineÃĄl MeÃĄn", "search_filter_media_type_title": "Roghnaigh cineÃĄl meÃĄn", + "search_filter_ocr": "Cuardaigh de rÊir OCR", "search_filter_people_title": "Roghnaigh daoine", "search_filter_star_rating": "RÃĄtÃĄil RÊalta", "search_filter_tags_title": "Roghnaigh clibeanna", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Gan aon duine darb ainm \"{name}\"", "search_no_result": "Ní bhfuarthas aon torthaí, bain triail as tÊarma cuardaigh nÃŗ teaglaim eile", "search_options": "Roghanna cuardaigh", + "search_page_categories": "CatagÃŗirí", + "search_page_motion_photos": "Grianghraif Ghluaiseachta", + "search_page_no_objects": "Níl aon eolas faoi rÊada ar fÃĄil", + "search_page_no_places": "Níl aon eolas faoi ÃĄiteanna ar fÃĄil", + "search_page_screenshots": "Seat scÃĄileÃĄin", "search_page_search_photos_videos": "Cuardaigh do ghrianghraif agus do fhíseÃĄin", + "search_page_selfies": "FÊinín", + "search_page_things": "Rudaí", "search_page_view_all_button": "FÊach ar gach rud", + "search_page_your_activity": "Do ghníomhaíocht", + "search_page_your_map": "Do LÊarscÃĄil", "search_people": "Cuardaigh daoine", "search_places": "Cuardaigh ÃĄiteanna", "search_rating": "Cuardaigh de rÊir rÃĄtÃĄla...", + "search_result_page_new_search_hint": "Cuardach Nua", "search_settings": "Socruithe cuardaigh", "search_state": "Cuardaigh stÃĄt...", + "search_suggestion_list_smart_search_hint_1": "TÃĄ cuardach cliste cumasaithe de rÊir rÊamhshocraithe, chun meiteashonraí a chuardach bain ÃēsÃĄid as an comhrÊir ", + "search_suggestion_list_smart_search_hint_2": "m:do-thÊarma-cuardaigh", "search_tags": "Cuardaigh clibeanna...", "search_timezone": "Cuardaigh crios ama...", "search_type": "CineÃĄl cuardaigh", @@ -1868,6 +1995,7 @@ "select_albums": "Roghnaigh albaim", "select_all": "Roghnaigh gach rud", "select_all_duplicates": "Roghnaigh na dÃēblaigh go lÊir", + "select_all_in": "Roghnaigh gach rud i {group}", "select_avatar_color": "Roghnaigh dath an abhatÃĄr", "select_count": "{count, plural, one {Roghnaigh #} other {Roghnaigh #}}", "select_cutoff_date": "Roghnaigh dÃĄta scoir", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Roghnaigh grianghraf le feiceÃĄil", "select_from_computer": "Roghnaigh Ãŗn ríomhaire", "select_keep_all": "Roghnaigh coinnigh gach rud", + "select_library_owner": "Roghnaigh ÃēinÊir leabharlainne", "select_new_face": "Roghnaigh aghaidh nua", "select_people": "Roghnaigh daoine", "select_person": "Roghnaigh duine", "select_person_to_tag": "Roghnaigh duine le clibeÃĄil", "select_photos": "Roghnaigh grianghraif", - "select_quality": "Roghnaigh cÃĄilíocht", "select_trash_all": "Roghnaigh gach rud sa bhruscar", + "select_user_for_sharing_page_err_album": "Theip ar albam a chruthÃē", "selected": "Roghnaithe", "selected_count": "{count, plural, other {# roghnaithe}}", "selected_gps_coordinates": "ComhordanÃĄidí GPS Roghnaithe", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Luchtaigh an íomhÃĄ bhunaidh", "setting_image_viewer_preview_subtitle": "Cumasaigh chun íomhÃĄ meÃĄntaifigh a luchtÃē. Díchumasaigh chun an bunleagan a luchtÃē go díreach nÃŗ chun an mionsamhail amhÃĄin a ÃēsÃĄid.", "setting_image_viewer_preview_title": "Luchtaigh íomhÃĄ rÊamhamhairc", + "setting_image_viewer_title": "ÍomhÃĄnna", "setting_languages_apply": "Cuir isteach", "setting_languages_subtitle": "Athraigh teanga an aip", + "setting_notifications_notify_failures_grace_period": "FÃŗgra a thabhairt faoi theipeanna cÃēltaca cÃēlra: {duration}", + "setting_notifications_notify_hours": "{count} uair an chloig", + "setting_notifications_notify_immediately": "lÃĄithreach", "setting_notifications_notify_minutes": "{count} nÃŗimÊad", + "setting_notifications_notify_never": "riamh", "setting_notifications_notify_seconds": "{count} soicind", + "setting_notifications_single_progress_subtitle": "FaisnÊis mhionsonraithe faoi dhul chun cinn uaslÃŗdÃĄla in aghaidh an tsÃŗcmhainne", + "setting_notifications_single_progress_title": "TaispeÃĄin dul chun cinn sonraí cÃēltaca sa chÃēlra", "setting_notifications_subtitle": "Coigeartaigh do chuid roghanna fÃŗgra", + "setting_notifications_total_progress_subtitle": "Dul chun cinn foriomlÃĄn uaslÃŗdÃĄla (críochnaithe/sÃŗcmhainní iomlÃĄna)", + "setting_notifications_total_progress_title": "TaispeÃĄin dul chun cinn iomlÃĄn an chÃēltaca sa chÃēlra", "setting_video_viewer_auto_play_subtitle": "Tosaíonn siad ag seinm físeÃĄin go huathoibríoch nuair a osclaítear iad", "setting_video_viewer_auto_play_title": "FíseÃĄin uath-sheinm", "setting_video_viewer_looping_title": "LÃēbÃĄil", "setting_video_viewer_original_video_subtitle": "Agus físeÃĄn ÃĄ shruthÃē Ãŗn bhfreastalaí, seinn an bunleagan fiÃē nuair a bhíonn traschÃŗd ar fÃĄil. D’fhÊadfadh sÊ seo maolÃĄnÃē a chur faoi deara. Seinntear físeÃĄin atÃĄ ar fÃĄil go hÃĄitiÃēil i gcÃĄilíocht bhunaidh beag beann ar an socrÃē seo.", "setting_video_viewer_original_video_title": "FÃŗrsaigh físeÃĄn bunaidh", "settings": "Socruithe", + "settings_require_restart": "Atosaigh Immich le do thoil chun an socrÃē seo a chur i bhfeidhm", "settings_saved": "Socruithe sÃĄbhÃĄilte", "setup_pin_code": "Socraigh cÃŗd PIN", "share": "Comhroinn", + "share_action_prompt": "SÃŗcmhainní comhroinnte {count}", + "share_add_photos": "Cuir grianghraif leis", + "share_assets_selected": "{count} roghnaithe", "share_dialog_preparing": "Ag ullmhÃē...", "share_link": "Comhroinn an Nasc", - "share_original": "ÚsÃĄid an bunleagan (mÃŗr)", - "share_preview": "ÚsÃĄid mionsamhail (beag)", - "share_quality_body": "BrÃēigh agus coinnigh an cnaipe comhroinnte chun cÃĄilíocht na híomhÃĄ a roghnÃē sula roinneann tÃē.", - "share_quality_title": "Roghnaigh do chÃĄilíocht roinnte", "shared": "Roinnte", "shared_album_activities_input_disable": "TÃĄ trÃĄcht díchumasaithe", "shared_album_activity_remove_content": "Ar mhaith leat an ghníomhaíocht seo a scriosadh?", "shared_album_activity_remove_title": "Scrios Gníomhaíocht", "shared_album_section_people_action_error": "EarrÃĄid ag fÃĄgÃĄil/ag baint den albam", + "shared_album_section_people_action_leave": "Bain ÃēsÃĄideoir den albam", + "shared_album_section_people_action_remove_user": "Bain ÃēsÃĄideoir den albam", "shared_album_section_people_title": "DAOINE", "shared_by": "Roinnte ag", "shared_by_user": "Roinnte ag {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} UaslÃŗdÃĄilte", "shared_link_app_bar_title": "Naisc Chomhroinnte", "shared_link_clipboard_copied_massage": "CÃŗipeÃĄilte chuig an ghearrthaisce", + "shared_link_clipboard_text": "Nasc: {link}\nPasfhocal: {password}", "shared_link_create_error": "EarrÃĄid agus nasc comhroinnte ÃĄ chruthÃē", - "shared_link_custom_url_description": "Rochtain a fhÃĄil ar an nasc comhroinnte seo le hainm URL saincheaptha", - "shared_link_custom_url_title": "URL Saincheaptha", - "shared_link_custom_url_warning": "Rabhadh: b’fhÊidir nach n-iomprÃŗidh ainm URL saincheaptha le slais ar aghaidh mar a bheifeÃĄ ag sÃēil leis.", + "shared_link_custom_url_description": "Rochtain a fhÃĄil ar an nasc comhroinnte seo le URL saincheaptha", "shared_link_edit_description_hint": "Cuir isteach cur síos an chomhroinnte", "shared_link_edit_expire_after_option_day": "1 lÃĄ", "shared_link_edit_expire_after_option_days": "{count} lÃĄ", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Roinnte le {partner}", "sharing": "Roinnt", "sharing_enter_password": "Cuir isteach an focal faire le do thoil chun an leathanach seo a fheiceÃĄil.", + "sharing_page_album": "Albaim chomhroinnte", + "sharing_page_description": "Cruthaigh albaim chomhroinnte chun grianghraif agus físeÃĄin a roinnt le daoine i do líonra.", + "sharing_page_empty_list": "LIOSTA FOLAMH", + "sharing_sidebar_description": "TaispeÃĄin nasc chuig Comhroinnt sa bharra taoibh", + "sharing_silver_appbar_create_shared_album": "Albam comhroinnte nua", + "sharing_silver_appbar_share_partner": "Comhroinn le comhphÃĄirtí", "shift_to_permanent_delete": "brÃēigh ⇧ chun sÃŗcmhainn a scriosadh go buan", "show_album_options": "TaispeÃĄin roghanna albaim", "show_albums": "TaispeÃĄin albaim", @@ -1995,9 +2140,7 @@ "show_in_timeline": "TaispeÃĄin san amlíne", "show_in_timeline_setting_description": "TaispeÃĄin grianghraif agus físeÃĄin Ãŗn ÃēsÃĄideoir seo i do líne ama", "show_keyboard_shortcuts": "TaispeÃĄin aicearraí mÊarchlÃĄir", - "show_less": "TaispeÃĄin níos lÃē", "show_metadata": "TaispeÃĄin meiteashonraí", - "show_more_fields": "{count, plural, one {TaispeÃĄin # rÊimse eile} other {TaispeÃĄin # rÊimse eile}}", "show_or_hide_info": "TaispeÃĄin nÃŗ folaigh faisnÊis", "show_password": "TaispeÃĄin an focal faire", "show_person_options": "TaispeÃĄin roghanna duine", @@ -2005,7 +2148,6 @@ "show_schema": "TaispeÃĄin scÊim", "show_search_options": "TaispeÃĄin roghanna cuardaigh", "show_shared_links": "TaispeÃĄin naisc chomhroinnte", - "show_slideshow_metadata_overlay": "TaispeÃĄin forleagan faisnÊise íomhÃĄ", "show_slideshow_transition": "TaispeÃĄin an t-aistriÃē sleamhnÃĄn", "show_supporter_badge": "Suaitheantas tacaíochta", "show_supporter_badge_description": "TaispeÃĄin suaitheantas tacaíochta", @@ -2017,25 +2159,18 @@ "sign_out": "Sínigh Amach", "sign_up": "ClÃĄraigh", "size": "MÊid", - "skip": "LÊim", "skip_to_content": "LÊim go dtí an t-ÃĄbhar", "skip_to_folders": "LÊim go dtí na fillteÃĄin", "skip_to_tags": "LÊim go dtí na clibeanna", "slideshow": "SleamhnÃĄn", - "slideshow_body": "Suigh siar agus fÊach ar do ghrianghraif ag seinm i sleamhnÃĄn lÃĄnscÃĄileÃĄin.", - "slideshow_metadata_overlay_mode": "Ábhar forleagan", - "slideshow_metadata_overlay_mode_description_only": "Cur síos amhÃĄin", - "slideshow_metadata_overlay_mode_full": "LÃĄn", "slideshow_repeat": "AthdhÊan an sleamhnÃĄn", "slideshow_repeat_description": "LÃēb ar ais go dtí an tÃēs nuair a chríochnaíonn an sleamhnÃĄn", "slideshow_settings": "Socruithe sleamhnÃĄn", - "slideshow_title": "SleamhnÃĄn", - "smart_album": "Albam cliste", - "some_assets_already_have_a_location_warning": "TÃĄ suíomh ag cuid de na sÃŗcmhainní roghnaithe cheana fÊin", "sort_albums_by": "SÃŗrtÃĄil albaim de rÊir...", "sort_created": "DÃĄta cruthaithe", "sort_items": "Líon na míreanna", "sort_modified": "DÃĄta athraithe", + "sort_newest": "An grianghraf is nuaí", "sort_oldest": "An grianghraf is sine", "sort_people_by_similarity": "SÃŗrtÃĄil daoine de rÊir cosÃēlachta", "sort_recent": "Grianghraf is dÊanaí", @@ -2044,6 +2179,7 @@ "stack": "Cruach", "stack_action_prompt": "{count} cruachta", "stack_duplicates": "Cruach dÃēblach", + "stack_select_one_photo": "Roghnaigh príomhghrianghraf amhÃĄin don chairn", "stack_selected_photos": "Cruach na grianghraif roghnaithe", "stacked_assets_count": "Cruachta {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "stacktrace": "Rian cruachta", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Ní mÃŗr don dÃĄta tosaigh a bheith roimh an dÃĄta deiridh", "state": "StÃĄt", "status": "StÃĄdas", - "step_delete": "Scrios cÊim", - "step_delete_confirm": "An bhfuil tÃē cinnte gur mian leat an chÊim seo a scriosadh?", - "step_details": "Sonraí cÊime", - "steps": "CÊimeanna", "stop_casting": "Stop a chur leis an gcraoladh", "stop_motion_photo": "Grianghraf Stop-Ghluaiseachta", "stop_photo_sharing": "Stop a chur le do ghrianghraif a roinnt?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Malartaigh treo an chumaisc", "sync": "SioncrÃŗnaigh", "sync_albums": "SioncrÃŗnaigh albaim", + "sync_albums_manual_subtitle": "SioncrÃŗnaigh na físeÃĄin agus na grianghraif uile a uaslÃŗdÃĄiltear leis na halbaim chÃēltaca roghnaithe", "sync_local": "SioncrÃŗnaigh ÁitiÃēil", "sync_remote": "SioncrÃŗnaigh Cianda", "sync_status": "StÃĄdas SioncrÃŗnaithe", "sync_status_subtitle": "FÊach ar an gcÃŗras sioncrÃŗnaithe agus bainistigh Ê", "sync_upload_album_setting_subtitle": "Cruthaigh agus uaslÃŗdÃĄil do ghrianghraif agus físeÃĄin chuig na halbaim roghnaithe ar Immich", - "system_theme": "TÊama an chÃŗrais", - "system_theme_command_description": "ÚsÃĄid tÊama an chÃŗrais ({value})", "tag": "Clib", "tag_assets": "SÃŗcmhainní clibe", "tag_created": "Clib cruthaithe: {tag}", "tag_face": "Aghaidh clibe", "tag_feature_description": "Ag brabhsÃĄil grianghraif agus físeÃĄin grÃēpÃĄilte de rÊir topaicí clibeanna loighciÃēla", + "tag_not_found_question": "Ní fÊidir clib a aimsiÃē? Cruthaigh clib nua.", "tag_people": "Daoine a ChlibeÃĄil", "tag_updated": "Clib nuashonraithe: {tag}", "tagged_assets": "ClibeÃĄilte {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Líon na sÃŗcmhainní in aghaidh an rÃŗ ({count})", "theme_setting_colorful_interface_subtitle": "Cuir an dath príomhÃēil i bhfeidhm ar dhromchlaí cÃēlra.", "theme_setting_colorful_interface_title": "ComhÊadan ildaite", + "theme_setting_image_viewer_quality_subtitle": "Coigeartaigh cÃĄilíocht an bhreathnÃŗra íomhÃĄ mionsonraithe", + "theme_setting_image_viewer_quality_title": "CÃĄilíocht breathnÃŗra íomhÃĄ", "theme_setting_primary_color_subtitle": "Roghnaigh dath do phríomhghníomhartha agus do bhÊimnithe.", "theme_setting_primary_color_title": "Dath príomhÃēil", "theme_setting_system_primary_color_title": "ÚsÃĄid dath an chÃŗrais", "theme_setting_system_theme_switch": "Uathoibríoch (Lean socruithe an chÃŗrais)", + "theme_setting_theme_subtitle": "Roghnaigh socrÃē tÊama an aip", + "theme_setting_three_stage_loading_subtitle": "D’fhÊadfadh luchtÃē trí chÊim feidhmíocht an luchtaithe a mhÊadÃē ach bíonn ualach líonra i bhfad níos airde mar thoradh air", + "theme_setting_three_stage_loading_title": "Cumasaigh luchtÃē trí chÊim", "then": "Ansin", "they_will_be_merged_together": "Cuirfear le chÊile iad", "third_party_resources": "Acmhainní TríÃē PÃĄirtí", @@ -2131,17 +2268,23 @@ "trash_all": "Bruscar Uile", "trash_count": "Bruscar {count, number}", "trash_delete_asset": "Bruscar/Scrios SÃŗcmhainn", + "trash_emptied": "Bruscar folamh", "trash_no_results_message": "Beidh grianghraif agus físeÃĄin atÃĄ scriosta le feiceÃĄil anseo.", "trash_page_delete_all": "Scrios Gach Rud", + "trash_page_empty_trash_dialog_content": "Ar mhaith leat do shÃŗcmhainní bruscair a fholmhÃē? Bainfear na míreanna seo go buan as Immich", "trash_page_info": "Scriosfar míreanna atÃĄ curtha sa bhruscar go buan tar Êis {days} lÃĄ", + "trash_page_no_assets": "Gan aon sÃŗcmhainní bruscar", + "trash_page_restore_all": "AthchÃŗirigh Gach Rud", + "trash_page_select_assets_btn": "Roghnaigh sÃŗcmhainní", + "trash_page_title": "Bruscar ({count})", "trashed_items_will_be_permanently_deleted_after": "Scriosfar míreanna atÃĄ curtha sa bhruscar go buan i ndiaidh {days, plural, one {# lÃĄ} other {# laethanta}}.", "trigger": "SpriocdhíriÃē", - "trigger_asset_metadata_extraction": "EastÃŗscadh Meiteashonraí SÃŗcmhainní", - "trigger_asset_metadata_extraction_description": "Spreagtha nuair a bhaintear meiteashonraí EXIF sÃŗcmhainne", - "trigger_asset_uploaded": "UaslÃŗdÃĄil SÃŗcmhainní", + "trigger_asset_uploaded": "SÃŗcmhainn UaslÃŗdÃĄilte", "trigger_asset_uploaded_description": "Spreagtha nuair a uaslÃŗdÃĄlfar sÃŗcmhainn nua", + "trigger_description": "Imeacht a chuireann tÃēs leis an sreabhadh oibre", "trigger_person_recognized": "Duine Aitheanta", - "trigger_person_recognized_description": "Spreagtar nuair a aithnítear duine", + "trigger_person_recognized_description": "Spreagtar nuair a bhraitear duine", + "trigger_type": "CineÃĄl spreagthÃŗra", "troubleshoot": "Fabhtcheartaigh", "type": "CineÃĄl", "unable_to_change_pin_code": "Ní fÊidir an cÃŗd PIN a athrÃē", @@ -2157,7 +2300,6 @@ "unknown": "Anaithnid", "unknown_country": "Tír Anaithnid", "unknown_date": "DÃĄta anaithnid", - "unknown_schema": "ScÊim anaithnid", "unknown_year": "Bliain Anaithnid", "unlimited": "Gan teorainn", "unlink_motion_video": "Dínasc físeÃĄn gluaisne", @@ -2170,20 +2312,23 @@ "unsaved_change": "AthrÃē neamhshÃĄbhÃĄilte", "unselect_all": "Díroghnaigh gach rud", "unselect_all_duplicates": "Díroghnaigh gach dÃēblach", + "unselect_all_in": "Díroghnaigh gach rud i {group}", "unstack": "Dí-chruachadh", "unstack_action_prompt": "{count} gan chruachadh", "unstacked_assets_count": "Gan chruachadh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "unsupported_field_type": "CineÃĄl rÊimse nach dtacaítear leis", "unsupported_file_type": "Ní fÊidir an comhad {file} a uaslÃŗdÃĄil mar nach dtacaítear lena chineÃĄl comhaid {type}.", "untagged": "Gan Chlib", + "untitled_workflow": "Sreabhadh oibre gan teideal", "up_next": "Ar aghaidh", "update_location_action_prompt": "Nuashonraigh suíomh na sÃŗcmhainní roghnaithe {count} le:", "updated_at": "Nuashonraithe", "updated_password": "Pasfhocal nuashonraithe", "upload": "UaslÃŗdÃĄil", "upload_concurrency": "UaslÃŗdÃĄil comhthrÃĄthacht", - "upload_day_count": "{date}: {count, plural, one {# uaslÃŗdÃĄil} other {# uaslÃŗdÃĄlacha}}", "upload_details": "Sonraí UaslÃŗdÃĄla", + "upload_dialog_info": "Ar mhaith leat cÃēltaca den ShÃŗcmhainn/na SÃŗcmhainní roghnaithe a dhÊanamh chuig an bhfreastalaí?", + "upload_dialog_title": "UaslÃŗdÃĄil SÃŗcmhainn", "upload_error_with_count": "EarrÃĄid uaslÃŗdÃĄla le haghaidh {count, plural, one {# sÃŗcmhainn} other {# sÃŗcmhainní}}", "upload_errors": "UaslÃŗdÃĄil críochnaithe le {count, plural, one {# earrÃĄid} other {# earrÃĄidí}}, athnuachan an leathanach chun sÃŗcmhainní uaslÃŗdÃĄla nua a fheiceÃĄil.", "upload_finished": "UaslÃŗdÃĄil críochnaithe", @@ -2193,13 +2338,9 @@ "upload_status_errors": "EarrÃĄidí", "upload_status_uploaded": "UaslÃŗdÃĄilte", "upload_success": "UaslÃŗdÃĄil rathÃēil, athnuachan an leathanach chun sÃŗcmhainní uaslÃŗdÃĄla nua a fheiceÃĄil.", - "upload_to_album_body": "I gcÃĄs ÃēsÃĄideoirí nach n-ÃēsÃĄideann an ghnÊ uaslÃŗdÃĄla lÃĄimhe, is fÊidir leat anois grianghraif ÃĄitiÃēla a chur go díreach le halbam agus tÃē ÃĄ n-uaslÃŗdÃĄil, níl aon ghÃĄ iad a uaslÃŗdÃĄil agus a chur le halbam níos dÊanaí a thuilleadh.", - "upload_to_album_title": "UaslÃŗdÃĄil díreach chuig albam", "upload_to_immich": "UaslÃŗdÃĄil chuig Immich ({count})", "uploading": "Ag uaslÃŗdÃĄil", "uploading_media": "Ag uaslÃŗdÃĄil meÃĄn", - "uploads": "UaslÃŗdÃĄlacha", - "uploads_count": "{count, plural, one {# uaslÃŗdÃĄil} other {# uaslÃŗdÃĄlacha}}", "url": "URL", "usage": "ÚsÃĄid", "use_biometric": "ÚsÃĄid bithmhÊadrach", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "FormÃĄidigh dÃĄtaí, amanna agus uimhreacha bunaithe ar shuíomh do bhrabhsÃĄlaí", "use_current_connection": "ÚsÃĄid an nasc reatha", "use_custom_date_range": "ÚsÃĄid raon dÃĄta saincheaptha ina ionad", - "use_template": "ÚsÃĄid teimplÊad", "user": "ÚsÃĄideoir", "user_has_been_deleted": "Scriosadh an t-ÃēsÃĄideoir seo.", "user_id": "Aitheantas ÚsÃĄideora", @@ -2217,6 +2357,7 @@ "user_privacy": "PríobhÃĄideacht ÚsÃĄideora", "user_purchase_settings": "Ceannaigh", "user_purchase_settings_description": "Bainistigh do cheannachÃĄn", + "user_role_set": "Socraigh {user} mar {role}", "user_usage_detail": "Sonraí ÃēsÃĄide ÃēsÃĄideora", "user_usage_stats": "Staitisticí ÃēsÃĄide cuntais", "user_usage_stats_description": "FÊach ar staitisticí ÃēsÃĄide cuntais", @@ -2226,6 +2367,7 @@ "utilities": "FÃŗntais", "validate": "BailíochtÃē", "validate_endpoint_error": "Cuir isteach URL bailí le do thoil", + "validation_error": "EarrÃĄid bailíochtaithe", "variables": "AthrÃŗga", "version": "Leagan", "version_announcement_closing": "Do chara, Alex", @@ -2235,9 +2377,9 @@ "video": "FíseÃĄn", "video_hover_setting": "Seinn mionsamhail físe ar an luchÃŗg", "video_hover_setting_description": "Seinn mionsamhail físe nuair a bhíonn an luch ag luascadh thar an mír. FiÃē nuair atÃĄ sÊ díchumasaithe, is fÊidir athsheinm a thosÃē tríd an luch a luascadh thar an deilbhín seinnte.", - "video_quality": "CÃĄilíocht físe", "videos": "FíseÃĄin", "videos_count": "{count, plural, one {# FíseÃĄn} other {# FíseÃĄin}}", + "videos_only": "FíseÃĄin amhÃĄin", "view": "Amharc", "view_album": "FÊach ar an Albam", "view_all": "FÊach ar Gach Rud", @@ -2246,29 +2388,29 @@ "view_details": "FÊach Sonraí", "view_in_timeline": "FÊach san amlíne", "view_link": "FÊach ar an nasc", + "view_links": "FÊach naisc", "view_name": "Amharc", "view_next_asset": "FÊach ar an gcÊad tsÃŗcmhainn eile", "view_previous_asset": "FÊach ar an tsÃŗcmhainn roimhe seo", "view_qr_code": "FÊach ar an gcÃŗd QR", "view_similar_photos": "FÊach ar ghrianghraif chomhchosÃēla", "view_stack": "FÊach ar an gCruach", + "view_user": "FÊach ar an ÚsÃĄideoir", "viewer_remove_from_stack": "Bain den Chruach", + "viewer_stack_use_as_main_asset": "ÚsÃĄid mar PhríomhshÃŗcmhainn", + "viewer_unstack": "Dí-Chruach", "visibility": "Infheictheacht", "visibility_changed": "Athraíodh infheictheacht do {count, plural, one {# duine} other {# daoine}}", "visual": "Amhairc", + "visual_builder": "TÃŗgÃĄlaí amhairc", "waiting": "Ag fanacht", "waiting_count": "Ag fanacht: {count}", "warning": "Rabhadh", "week": "Seachtain", "welcome": "FÃĄilte", "welcome_to_immich": "FÃĄilte go hImmich", - "whats_new": "Cad atÃĄ nua", - "whats_new_settings_subtitle": "FÊach cad atÃĄ nua in Immich", - "whats_new_version": "Leagan {version}", - "when": "Cathain", "width": "Leithead", "wifi_name": "Ainm Wi-Fi", - "workflow": "Sreabhadh Oibre", "workflow_delete_prompt": "An bhfuil tÃē cinnte gur mian leat an sreabhadh oibre seo a scriosadh?", "workflow_deleted": "Sreabhadh oibre scriosta", "workflow_description": "Cur síos ar an sreabhadh oibre", @@ -2278,17 +2420,17 @@ "workflow_name": "Ainm an tsreafa oibre", "workflow_navigation_prompt": "An bhfuil tÃē cinnte gur mian leat imeacht gan do chuid athruithe a shÃĄbhÃĄil?", "workflow_summary": "Achoimre ar an sreabhadh oibre", - "workflow_templates": "TeimplÊid sreabhadh oibre", "workflow_update_success": "Nuashonraíodh an sreabhadh oibre go rathÃēil", + "workflow_updated": "Sreabhadh oibre nuashonraithe", "workflows": "Sreafaí oibre", "workflows_help_text": "Uathoibríonn sreafaí oibre gníomhartha ar do shÃŗcmhainní bunaithe ar spreagthÃŗirí agus scagairí", "wrong_pin_code": "CÃŗd PIN mícheart", - "x_of_total": "{x}/{total}", "year": "Bliain", "years_ago": "{years, plural, one {# bliain} other {# blianta}} Ãŗ shin", "yes": "TÃĄ", "you_dont_have_any_shared_links": "Níl aon naisc chomhroinnte agat", "your_wifi_name": "Ainm do Wi-Fi", "zero_to_clear_rating": "brÃēigh 0 chun rÃĄtÃĄil sÃŗcmhainne a ghlanadh", - "zoom_image": "ÍomhÃĄ ZÃēmÃĄil" + "zoom_image": "ÍomhÃĄ ZÃēmÃĄil", + "zoom_to_bounds": "ZÃēmÃĄil go dtí na teorainneacha" } diff --git a/i18n/gl.json b/i18n/gl.json index 9df2baa287..201f718998 100644 --- a/i18n/gl.json +++ b/i18n/gl.json @@ -10,31 +10,39 @@ "active": "Activo", "active_count": "Activo:{count}", "activity": "Actividade", + "activity_changed": "A actividade estÃĄ {enabled, select, true {activada} other {desactivada}}", "add": "Engadir", "add_a_description": "Engadir unha descriciÃŗn", "add_a_location": "Engadir unha localizaciÃŗn", "add_a_name": "Engadir un nome", "add_a_title": "Engadir un título", "add_action": "Engadir acciÃŗn", + "add_action_description": "Faga click para engadir unha acciÃŗn a realizar", "add_assets": "Engadir activos", "add_birthday": "Engadir aniversario", "add_endpoint": "Engadir punto final", "add_exclusion_pattern": "Engadir patrÃŗn de exclusiÃŗn", + "add_filter": "Engadir filtro", + "add_filter_description": "Faga click para engadir unha condiciÃŗn de filtrado", "add_location": "Engadir localizaciÃŗn", + "add_more_users": "Engadir mÃĄis usuarios", "add_partner": "Engadir compaÃąeiro/a", + "add_path": "Engadir ruta", "add_photos": "Engadir fotos", - "add_step": "Engadir paso", "add_tag": "Engadir etiqueta", "add_to": "Engadir aâ€Ļ", "add_to_album": "Engadir ao ÃĄlbum", "add_to_album_bottom_sheet_added": "Engadido a {album}", "add_to_album_bottom_sheet_already_exists": "Xa estÃĄ en {album}", "add_to_album_bottom_sheet_some_local_assets": "Algunhas imaxes ou ficheiros locais non se puideron engadir ao ÃĄlbum", + "add_to_album_toggle": "Alternar selecciÃŗn para {album}", "add_to_albums": "Engadir a ÃĄlbums", "add_to_albums_count": "Engadir a {count} ÃĄlbums", "add_to_bottom_bar": "Engadir a", + "add_to_shared_album": "Engadir ao ÃĄlbum compartido", "add_upload_to_stack": "Engade cargar ÃĄ pila", "add_url": "Engadir URL", + "add_workflow_step": "Engadir paso de fluxo de traballo", "added_to_archive": "Engadido ao arquivo", "added_to_favorites": "Engadido a favoritos", "added_to_favorites_count": "Engadíronse {count, number} a favoritos", @@ -73,7 +81,6 @@ "cron_expression_description": "Estableza o intervalo de escaneo usando o formato cron. Para obter mÃĄis informaciÃŗn, consulte por exemplo Crontab Guru", "cron_expression_presets": "Preaxustes de expresiÃŗn Cron", "disable_login": "Desactivar inicio de sesiÃŗn", - "download_csv": "Descargar CSV", "duplicate_detection_job_description": "Executar aprendizaxe automÃĄtica nos activos para detectar imaxes similares. Depende da Busca Intelixente", "exclusion_pattern_description": "Os patrÃŗns de exclusiÃŗn permítenlle ignorar ficheiros e cartafoles ao escanear a sÃēa biblioteca. Isto Ê Ãētil se ten cartafoles que conteÃąen ficheiros que non quere importar, como ficheiros RAW.", "export_config_as_json_description": "Descarga a configuraciÃŗn actual coma un arquivo JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Calidade da miniatura de 1 a 100. Canto mÃĄis alto, mellor, pero produce ficheiros mÃĄis grandes e pode reducir a capacidade de resposta da aplicaciÃŗn.", "image_thumbnail_title": "ConfiguraciÃŗn da miniatura", "import_config_from_json_description": "Importar a configuraciÃŗn do sistema subindo un arquivo de configuraciÃŗn JSON", - "integrity_checks_checksum_files": "Ficheiros de suma de verificaciÃŗn", - "integrity_checks_checksum_files_description": "Configurar a comprobaciÃŗn da suma de comprobaciÃŗn", - "integrity_checks_checksum_files_enable_description": "Activar a comprobaciÃŗn da suma de verificaciÃŗn", - "integrity_checks_checksum_files_percentage_limit": "Límite porcentual", - "integrity_checks_checksum_files_percentage_limit_description": "Configura a porcentaxe mÃĄxima entre 0,01 e 1 para a duraciÃŗn da comprobaciÃŗn da suma de verificaciÃŗn en cada intervalo.", - "integrity_checks_checksum_files_time_limit": "Límite de tempo", - "integrity_checks_checksum_files_time_limit_description": "Configura a duraciÃŗn mÃĄxima durante a que se debe executar a comprobaciÃŗn da suma de comprobaciÃŗn en cada intervalo. (ms)", - "integrity_checks_missing_files": "Arquivos que faltan", - "integrity_checks_missing_files_description": "Configura a frecuencia e activa ou desactiva a comprobaciÃŗn de arquivos que faltan", - "integrity_checks_missing_files_enable_description": "Activar a comprobaciÃŗn de arquivos que faltan", - "integrity_checks_settings": "ComprobaciÃŗns de integridade", - "integrity_checks_settings_description": "Xestionar intervalos de comprobaciÃŗns de integridade", - "integrity_checks_untracked_files": "Arquivos sen rastrexar", - "integrity_checks_untracked_files_description": "Configura a frecuencia e activa ou desactiva a comprobaciÃŗn de arquivos sen rastrexar", - "integrity_checks_untracked_files_enable_description": "Activar a comprobaciÃŗn de arquivos sen rastrexar", "job_concurrency": "concorrencia de {job}", "job_created": "Traballo creado", "job_not_concurrency_safe": "Este traballo non Ê seguro para execuciÃŗn concorrente.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Activar busca intelixente", "machine_learning_smart_search_enabled_description": "Se estÃĄ desactivado, as imaxes non se codificarÃĄn para a busca intelixente.", "machine_learning_url_description": "A URL do servidor de aprendizaxe automÃĄtica. Se se proporciona mÃĄis dunha URL, intentarase con cada servidor un por un ata que un responda correctamente, en orde do primeiro ao Ãēltimo. Os servidores que non respondan ignoraranse temporalmente ata que volvan estar en liÃąa.", - "maintenance_backup_management": "XestiÃŗn de copias de seguridade", "maintenance_delete_backup": "Eliminar copia de seguridade", "maintenance_delete_backup_description": "Este arquivo serÃĄ borrado permanentemente.", "maintenance_delete_error": "Erro ao eliminar a copia de seguridade.", - "maintenance_integrity_check": "Verificar", - "maintenance_integrity_check_all": "Seleccionar todos", - "maintenance_integrity_checksum_mismatch": "A suma de comprobaciÃŗn non coincide", - "maintenance_integrity_checksum_mismatch_description": "Arquivos que teÃąen diferentes sumas de comprobaciÃŗn entre o disco e a base de datos de Immich.", - "maintenance_integrity_checksum_mismatch_job": "Comproba se hai discrepancias na suma de verificaciÃŗn", - "maintenance_integrity_checksum_mismatch_refresh_job": "Actualizar informes de discrepancias da suma de comprobaciÃŗn", - "maintenance_integrity_missing_file": "Arquivos faltantes", - "maintenance_integrity_missing_file_description": "Arquivos que Immich ten rexistrados na base de datos pero que non existen no sistema de arquivos.", - "maintenance_integrity_missing_file_job": "Comprobe os arquivos que faltan", - "maintenance_integrity_missing_file_refresh_job": "Actualiza os informes de arquivos que faltan", - "maintenance_integrity_report": "Informe de integridade", - "maintenance_integrity_untracked_file": "Arquivos sen rastrexar", - "maintenance_integrity_untracked_file_description": "Arquivos nos directorios de Immich dos que Immich non ten ningÃēn rexistro.", - "maintenance_integrity_untracked_file_job": "Comprobar se hai arquivos sen rastrexar", - "maintenance_integrity_untracked_file_refresh_job": "Actualizar informes de arquivos sen rastrexar", "maintenance_restore_backup": "Recuperar copia de seguridade", "maintenance_restore_backup_description": "Immich borrarase e restaurarase desde a copia de seguridade escollida. Crearase unha copia de seguridade antes de continuar.", "maintenance_restore_backup_different_version": "Esta copia de seguridade foi creada cunha versiÃŗn diferente de Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Activar notificaciÃŗns por correo electrÃŗnico", "notification_settings": "ConfiguraciÃŗn de NotificaciÃŗns", "notification_settings_description": "Xestionar a configuraciÃŗn de notificaciÃŗns, incluído o correo electrÃŗnico", - "oauth_allow_insecure_requests": "Permitir solicitudes non segureas", - "oauth_allow_insecure_requests_description": "AVISO: Isto desactiva a validaciÃŗn de certificados TLS para as solicitudes OAuth e pode expoÃąerte a ataques MITM.", "oauth_auto_launch": "Lanzamento automÃĄtico", "oauth_auto_launch_description": "Iniciar o fluxo de inicio de sesiÃŗn OAuth automaticamente ao navegar ÃĄ pÃĄxina de inicio de sesiÃŗn", "oauth_auto_register": "Rexistro automÃĄtico", @@ -300,11 +274,9 @@ "oauth_button_text": "Texto do botÃŗn", "oauth_client_secret_description": "Requirido para clientes confidenciais ou se o provedor OAuth non admite PKCE (Proof Key for Code Exchange).", "oauth_enable_description": "Iniciar sesiÃŗn con OAuth", - "oauth_end_session_url_description": "Redireccionar o usuario a este URI cando peche sesiÃŗn.", "oauth_mobile_redirect_uri": "URI de redirecciÃŗn mÃŗbil", "oauth_mobile_redirect_uri_override": "SubstituciÃŗn de URI de redirecciÃŗn mÃŗbil", "oauth_mobile_redirect_uri_override_description": "Activar cando o provedor OAuth non permite un URI mÃŗbil, como ''{callback}''", - "oauth_prompt_description": "ParÃĄmetro de solicitude (por exemplo, select_account, login, consent)", "oauth_role_claim": "DeclaraciÃŗn de rol", "oauth_role_claim_description": "Conceder acceso de administrador automaticamente segundo a presenza desta declaraciÃŗn. A declaraciÃŗn pode ter os valores 'user' ou 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Actualizando todas as bibliotecas", "registration": "Rexistro do administrador", "registration_description": "Dado que vostede Ê o primeiro usuario no sistema, asignarÃĄselle como Administrador e serÃĄ responsable das tarefas administrativas. Os usuarios adicionais serÃĄn creados por vostede.", - "release_channel_release_candidate": "Candidato de lanzamento", - "release_channel_stable": "Estable", "remove_failed_jobs": "Eliminar os traballos con erros", "require_password_change_on_login": "Requirir que o usuario cambie o contrasinal no primeiro inicio de sesiÃŗn", "reset_settings_to_default": "Restablecer a configuraciÃŗn aos valores predeterminados", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Aplícase sÃŗ a VAAPI e QSV. Establece o nodo dri usado para a transcodificaciÃŗn por hardware.", "transcoding_preset_preset": "Preaxuste (-preset)", "transcoding_preset_preset_description": "Velocidade de compresiÃŗn. Preaxustes mÃĄis lentos producen ficheiros mÃĄis pequenos e aumentan a calidade ao apuntar a un certo bitrate. VP9 ignora velocidades superiores a 'faster'.", - "transcoding_realtime": "Transcodificado en Tempo Real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permite que a transcodificaciÃŗn se realice en tempo real mentres se transmite o vídeo. Activar o cambio de calidade pode provocar unha maior latencia de reproduciÃŗn e atraso dependendo das capacidades do servidor.", - "transcoding_realtime_enabled": "Activar transcodificaciÃŗn en tempo real", - "transcoding_realtime_enabled_description": "Se estÃĄ desactivado, o servidor rexeitarÃĄ iniciar novas sesiÃŗns de transcodificaciÃŗn en tempo real.", - "transcoding_realtime_resolutions": "ResoluciÃŗns", - "transcoding_realtime_resolutions_description": "As resoluciÃŗns ofrecidas para a transcodificaciÃŗn en tempo real. As resoluciÃŗns mÃĄis altas poden causar problemas de reproduciÃŗn se o servidor non Ê capaz de transcodificalas con suficiente rapidez.", - "transcoding_realtime_video_codecs": "CÃŗdecs de vídeo", - "transcoding_realtime_video_codecs_description": "Os cÃŗdecs de vídeo ofrecidos para a transcodificaciÃŗn en tempo real. Os clientes elixirÃĄn a mellor opciÃŗn compatible durante a reproduciÃŗn. O AV1 Ê mÃĄis eficiente ca o HEVC, e este, ÃĄ sÃēa vez, mÃĄis eficiente ca o H.264. Ao utilizar aceleraciÃŗn por hardware, selecciona sÃŗ aqueles cÃŗdecs que o acelerador poida codificar. Ao utilizar transcodificaciÃŗn por software, ten en conta que o H.264 Ê mÃĄis rÃĄpido ca o AV1, e este mÃĄis rÃĄpido ca o HEVC.", "transcoding_reference_frames": "Fotogramas de referencia", "transcoding_reference_frames_description": "O nÃēmero de fotogramas aos que facer referencia ao comprimir un fotograma dado. Valores mÃĄis altos melloran a eficiencia da compresiÃŗn, pero ralentizan a codificaciÃŗn. 0 establece este valor automaticamente.", "transcoding_required_description": "SÃŗ vídeos que non estean nun formato aceptado", @@ -478,8 +440,6 @@ "user_settings_description": "Xestionar a configuraciÃŗn do usuario", "user_successfully_removed": "O usuario {email} foi eliminado satisfactoriamente.", "users_page_description": "PÃĄxina de usuarios administradores", - "version_check_channel": "Canle de lanzamento", - "version_check_channel_description": "Escolla a canle de lanzamento da que queres recibir notificaciÃŗns de versiÃŗns", "version_check_enabled_description": "Activar comprobaciÃŗn de versiÃŗn", "version_check_implications": "A funciÃŗn de comprobaciÃŗn de versiÃŗn depende da comunicaciÃŗn periÃŗdica con {server}", "version_check_settings": "ComprobaciÃŗn de VersiÃŗn", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Limpar a cachÊ da imaxe", "advanced_settings_clear_image_cache_error": "Fallo ao limpar a cachÊ da imaxe", "advanced_settings_clear_image_cache_success": "CachÊ borrada correctamente {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Use esta opciÃŗn para filtrar medios durante a sincronizaciÃŗn baseÃĄndose en criterios alternativos. SÃŗ probe isto se ten problemas coa aplicaciÃŗn para detectar todos os ÃĄlbums.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Usar filtro alternativo de sincronizaciÃŗn de ÃĄlbums do dispositivo", "advanced_settings_log_level_title": "Nivel de rexistro: {level}", "advanced_settings_prefer_remote_subtitle": "AlgÃēns dispositivos tardan moito en cargar as miniaturas de ficheiros locais. Active esta opciÃŗn para cargar imaxes remotas no seu lugar.", "advanced_settings_prefer_remote_title": "Preferir imaxes remotas", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Cabeceiras de proxy personalizadas [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Activa o modo de sÃŗ lectura, no que as fotos sÃŗ se poden visualizar; opciÃŗns como seleccionar varias imaxes, compartir, enviar a outros dispositivos ou eliminar estÃĄn deshabilitadas. Active/desactive o modo de sÃŗ lectura a travÊs do avatar do usuario na pantalla principal", "advanced_settings_readonly_mode_title": "Modo de sÃŗ lectura", + "advanced_settings_self_signed_ssl_subtitle": "Omite a verificaciÃŗn do certificado SSL para o punto final do servidor. Requirido para certificados autofirmados.", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autofirmados [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Eliminar ou restaurar automaticamente un activo neste dispositivo cando esa acciÃŗn se realiza na web", "advanced_settings_sync_remote_deletions_title": "Sincronizar eliminaciÃŗns remotas [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "ConfiguraciÃŗn de usuario avanzado", @@ -516,18 +480,31 @@ "album_delete_confirmation": "EstÃĄ seguro de que quere eliminar o ÃĄlbum {album}?", "album_delete_confirmation_description": "Se este ÃĄlbum estÃĄ compartido, outros usuarios non poderÃĄn acceder a el.", "album_deleted": "Álbum eliminado", + "album_info_card_backup_album_excluded": "EXCLUÍDO", + "album_info_card_backup_album_included": "INCLUÍDO", "album_info_updated": "InformaciÃŗn do ÃĄlbum actualizada", + "album_leave": "Saír do ÃĄlbum?", + "album_leave_confirmation": "EstÃĄ seguro de que quere saír de {album}?", "album_name": "Nome do Álbum", "album_options": "OpciÃŗns do ÃĄlbum", "album_remove_user": "Eliminar usuario?", "album_remove_user_confirmation": "EstÃĄ seguro de que quere eliminar a {user}?", "album_search_not_found": "Non se atoparon ÃĄlbums que coincidan coa sÃēa busca", + "album_selected": "Álbum seleccionado", "album_share_no_users": "Parece que compartiu este ÃĄlbum con todos os usuarios ou non ten ningÃēn usuario co que compartir.", "album_summary": "Resumo do ÃĄlbum", "album_updated": "Álbum actualizado", "album_updated_setting_description": "Recibir unha notificaciÃŗn por correo electrÃŗnico cando un ÃĄlbum compartido teÃąa novos activos", "album_upload_assets": "Cargar recursos desde o teu ordenador e engadilos ao ÃĄlbum", + "album_user_left": "Saíu de {album}", + "album_user_removed": "Eliminouse a {user}", + "album_viewer_appbar_delete_confirm": "EstÃĄ seguro de que quere eliminar este ÃĄlbum da sÃēa conta?", "album_viewer_appbar_share_err_delete": "Erro ao eliminar o ÃĄlbum", + "album_viewer_appbar_share_err_leave": "Erro ao saír do ÃĄlbum", + "album_viewer_appbar_share_err_remove": "Houbo problemas ao eliminar activos do ÃĄlbum", + "album_viewer_appbar_share_err_title": "Erro ao cambiar o título do ÃĄlbum", + "album_viewer_appbar_share_leave": "Saír do ÃĄlbum", + "album_viewer_appbar_share_to": "Compartir con", "album_viewer_page_share_add_users": "Engadir usuarios", "album_with_link_access": "Permitir que calquera persoa coa ligazÃŗn vexa fotos e persoas neste ÃĄlbum.", "albums": "Álbums", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Orde inicial dos ficheiros ao crear novos ÃĄlbums.", "albums_feature_description": "ColecciÃŗns de ficheiros que se poden compartir con outros usuarios.", "albums_on_device_count": "Álbums no dispositivo ({count})", + "albums_selected": "{count, plural, one {# ÃĄlbum selected} other {# ÃĄlbums selected}}", "all": "Todo", "all_albums": "Todos os ÃĄlbums", "all_people": "Todas as persoas", "all_photos": "Todas as fotos", "all_videos": "Todos os vídeos", "allow_dark_mode": "Permitir modo escuro", + "allow_edits": "Permitir ediciÃŗns", "allow_public_user_to_download": "Permitir que o usuario pÃēblico descargue", "allow_public_user_to_upload": "Permitir que o usuario pÃēblico cargue", "allowed": "Permitido", @@ -549,12 +528,14 @@ "always_keep": "Manter sempre", "always_keep_photos_hint": "Liberar espazo manterÃĄ todas as fotos neste dispositivo.", "always_keep_videos_hint": "Liberar espazo manterÃĄ todos os videos neste dispositivo.", + "anti_clockwise": "Sentido antihorario", "api_key": "Chave API", "api_key_description": "Este valor sÃŗ se mostrarÃĄ unha vez. AsegÃērese de copialo antes de pechar a xanela.", "api_key_empty": "O nome da sÃēa chave API non pode estar baleiro", "api_keys": "Chaves API", "app_architecture_variant": "Variante (Arquitectura)", "app_bar_signout_dialog_content": "EstÃĄ seguro de que quere pechar sesiÃŗn?", + "app_bar_signout_dialog_ok": "Si", "app_bar_signout_dialog_title": "Pechar sesiÃŗn", "app_download_links": "LigazÃŗns de Descarga da AplicaciÃŗn", "app_settings": "ConfiguraciÃŗn da AplicaciÃŗn", @@ -565,19 +546,27 @@ "archive": "Arquivo", "archive_action_prompt": "{count} engadido(s) ao Arquivo", "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", + "archive_page_no_archived_assets": "Non se atoparon activos arquivados", + "archive_page_title": "Arquivo ({count})", "archive_size": "TamaÃąo do arquivo", "archive_size_description": "Configurar o tamaÃąo do arquivo para descargas (en GiB)", "archived": "Arquivado", "archived_count": "{count, plural, other {Arquivados #}}", "are_these_the_same_person": "Son estas a mesma persoa?", "are_you_sure_to_do_this": "EstÃĄ seguro de que quere facer isto?", + "array_field_not_fully_supported": "Os campos tipo array precisan ediciÃŗn manual no JSON", + "asset_action_delete_err_read_only": "Non se poden eliminar activo(s) de sÃŗ lectura, omitindo", + "asset_action_share_err_offline": "Non se poden obter activo(s) fÃŗra de liÃąa, omitindo", "asset_added_to_album": "Engadido ao ÃĄlbum", "asset_adding_to_album": "Engadindo ao ÃĄlbumâ€Ļ", "asset_created": "Recurso creado", - "asset_day_count": "{date}: {count, plural, one {# ficheiro} other {# ficheiros}}", "asset_description_updated": "A descriciÃŗn do activo actualizouse", + "asset_filename_is_offline": "O activo {filename} estÃĄ fÃŗra de liÃąa", + "asset_has_unassigned_faces": "O activo ten caras sen asignar", "asset_hashing": "Calculando hashâ€Ļ", "asset_list_group_by_sub_title": "Agrupar por", + "asset_list_layout_settings_dynamic_layout_title": "DeseÃąo dinÃĄmico", + "asset_list_layout_settings_group_automatically": "AutomÃĄtico", "asset_list_layout_settings_group_by": "Agrupar activos por", "asset_list_layout_settings_group_by_month_day": "Mes + día", "asset_list_layout_sub_title": "DeseÃąo", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Non se atopou o recurso en iCloud. É posible que non se poida acceder ao recurso debido a un ficheiro incorrecto almacenado en iCloud", "asset_offline": "Activo FÃŗra de LiÃąa", "asset_offline_description": "Este activo externo xa non se atopa no disco. Por favor, contacte co seu administrador de Immich para obter axuda.", + "asset_restored_successfully": "Activo restaurado correctamente", "asset_skipped": "Omitido", "asset_skipped_in_trash": "No lixo", + "asset_trashed": "Ficheiro enviado ao lixo", "asset_troubleshoot": "SoluciÃŗn de problemas do ficheiro", "asset_uploaded": "Subido", "asset_uploading": "Subindoâ€Ļ", "asset_viewer_settings_subtitle": "Xestionar a sÃēa configuraciÃŗn do visor da galería", "asset_viewer_settings_title": "Visor de Activos", "assets": "Activos", + "assets_added_count": "Engadido {count, plural, one {# activo} other {# activos}}", "assets_added_to_album_count": "Engadido {count, plural, one {# activo} other {# activos}} ao ÃĄlbum", - "assets_added_to_album_partial_count": "Engadíronse {successCount} de {totalCount} {totalCount, plural, one {elemento} other {elementos}} ao ÃĄlbum", "assets_added_to_albums_count": "Engadido {assetTotal, plural, one {# ficheiro} other {# ficheiros}} a {albumTotal, plural, one {# ÃĄlbum} other {# ÃĄlbums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} ao ÃĄlbum", "assets_cannot_be_added_to_albums": "{count, plural, one {O ficheiro non se pode engadir} other {Os ficheiros non se poden engadir}} a ningÃēn dos ÃĄlbums", "assets_count": "{count, plural, one {# activo} other {# activos}}", + "assets_deleted_permanently": "{count} activo(s) eliminado(s) permanentemente", + "assets_deleted_permanently_from_server": "{count} activo(s) eliminado(s) permanentemente do servidor Immich", + "assets_downloaded_failed": "{count, plural, one {Descargouse # ficheiro - fallou 1 ficheiro} other {DescargÃĄronse # ficheiros - fallaron {error} ficheiros}}", + "assets_downloaded_successfully": "{count, plural, one {Descargouse # ficheiro correctamente} other {DescargÃĄronse # ficheiros correctamente}}", "assets_moved_to_trash_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", "assets_permanently_deleted_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", "assets_removed_count": "Eliminados {count, plural, one {# activo} other {# activos}}", + "assets_removed_permanently_from_device": "{count} activo(s) eliminado(s) permanentemente do seu dispositivo", "assets_restore_confirmation": "EstÃĄ seguro de que quere restaurar todos os activos no lixo? Non pode desfacer esta acciÃŗn! TeÃąa en conta que calquera activo fÃŗra de liÃąa non pode ser restaurado desta maneira.", "assets_restored_count": "Restaurados {count, plural, one {# activo} other {# activos}}", + "assets_restored_successfully": "{count} activo(s) restaurado(s) correctamente", "assets_trashed": "{count} activo(s) movido(s) ao lixo", "assets_trashed_count": "Movido {count, plural, one {# activo} other {# activos}} ao lixo", + "assets_trashed_from_server": "{count} activo(s) movido(s) ao lixo desde o servidor Immich", "assets_were_part_of_album_count": "{count, plural, one {O activo xa era} other {Os activos xa eran}} parte do ÃĄlbum", "assets_were_part_of_albums_count": "{count, plural, one {O ficheiro xa estaba} other {Os ficheiros xa estaban}} neses ÃĄlbums", "authorized_devices": "Dispositivos Autorizados", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ReproduciÃŗn automÃĄtica da presentaciÃŗn", "back": "AtrÃĄs", "back_close_deselect": "AtrÃĄs, pechar ou deseleccionar", + "background_backup_running_error": "A copia de seguridade en segundo plano estÃĄ en curso, non se pode iniciar unha copia manual", "background_location_permission": "Permiso de localizaciÃŗn en segundo plano", "background_location_permission_content": "Para cambiar de rede cando se executa en segundo plano, Immich debe ter *sempre* acceso ÃĄ localizaciÃŗn precisa para que a aplicaciÃŗn poida ler o nome da rede wifi", "background_options": "OpciÃŗns en segundo plano", "backup": "Copia de Seguridade", + "backup_album_selection_page_albums_device": "Álbums no dispositivo ({count})", "backup_album_selection_page_albums_tap": "Tocar para incluír, dobre toque para excluír", "backup_album_selection_page_assets_scatter": "Os activos poden estar dispersos por varios ÃĄlbums. Polo tanto, os ÃĄlbums poden incluírse ou excluírse durante o proceso de copia de seguridade.", "backup_album_selection_page_select_albums": "Seleccionar ÃĄlbums", "backup_album_selection_page_selection_info": "InformaciÃŗn da selecciÃŗn", + "backup_album_selection_page_total_assets": "Total de activos Ãēnicos", "backup_albums_sync": "SincronizaciÃŗn de ÃĄlbums de copia de seguridade", + "backup_all": "Todo", + "backup_background_service_backup_failed_message": "Erro ao facer copia de seguridade dos activos. Reintentandoâ€Ļ", "backup_background_service_complete_notification": "Copia de seguridade dos recursos completada", + "backup_background_service_connection_failed_message": "Erro ao conectar co servidor. Reintentandoâ€Ļ", + "backup_background_service_current_upload_notification": "Subindo {filename}", "backup_background_service_default_notification": "Comprobando novos activosâ€Ļ", + "backup_background_service_error_title": "Erro na copia de seguridade", "backup_background_service_in_progress_notification": "Facendo copia de seguridade dos seus activosâ€Ļ", + "backup_background_service_upload_failure_notification": "Erro ao subir {filename}", "backup_controller_page_albums": "Álbums da Copia de Seguridade", + "backup_controller_page_background_app_refresh_disabled_content": "Active a actualizaciÃŗn de aplicaciÃŗns en segundo plano en Axustes > Xeral > ActualizaciÃŗn en segundo plano para usar a copia de seguridade en segundo plano.", + "backup_controller_page_background_app_refresh_disabled_title": "ActualizaciÃŗn de aplicaciÃŗns en segundo plano desactivada", + "backup_controller_page_background_app_refresh_enable_button_text": "Ir a axustes", "backup_controller_page_background_battery_info_link": "MÃŗstreme como", "backup_controller_page_background_battery_info_message": "Para a mellor experiencia de copia de seguridade en segundo plano, desactive calquera optimizaciÃŗn de batería que restrinxa a actividade en segundo plano para Immich.\n\nDado que isto Ê específico do dispositivo, busque a informaciÃŗn requirida para o fabricante do seu dispositivo.", "backup_controller_page_background_battery_info_ok": "Aceptar", "backup_controller_page_background_battery_info_title": "OptimizaciÃŗns da batería", + "backup_controller_page_background_charging": "SÃŗ mentres se carga", + "backup_controller_page_background_configure_error": "Erro ao configurar o servizo en segundo plano", "backup_controller_page_background_delay": "Atrasar copia de seguridade de novos activos: {duration}", + "backup_controller_page_background_description": "Active o servizo en segundo plano para facer copia de seguridade automaticamente de calquera activo novo sen necesidade de abrir a aplicaciÃŗn", + "backup_controller_page_background_is_off": "A copia de seguridade automÃĄtica en segundo plano estÃĄ desactivada", + "backup_controller_page_background_is_on": "A copia de seguridade automÃĄtica en segundo plano estÃĄ activada", + "backup_controller_page_background_turn_off": "Desactivar servizo en segundo plano", + "backup_controller_page_background_turn_on": "Activar servizo en segundo plano", + "backup_controller_page_background_wifi": "SÃŗ con wifi", "backup_controller_page_backup": "Copia de Seguridade", "backup_controller_page_backup_selected": "Seleccionado: ", "backup_controller_page_backup_sub": "Fotos e vídeos con copia de seguridade", + "backup_controller_page_created": "Creado o: {date}", + "backup_controller_page_desc_backup": "Active a copia de seguridade en primeiro plano para cargar automaticamente novos activos ao servidor ao abrir a aplicaciÃŗn.", "backup_controller_page_excluded": "Excluído: ", + "backup_controller_page_failed": "Fallado ({count})", + "backup_controller_page_filename": "Nome do ficheiro: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "InformaciÃŗn da Copia de Seguridade", "backup_controller_page_none_selected": "NingÃēn seleccionado", "backup_controller_page_remainder": "Restante", "backup_controller_page_remainder_sub": "Fotos e vídeos restantes para facer copia de seguridade da selecciÃŗn", "backup_controller_page_server_storage": "Almacenamento do Servidor", + "backup_controller_page_start_backup": "Iniciar Copia de Seguridade", + "backup_controller_page_status_off": "A copia de seguridade automÃĄtica en primeiro plano estÃĄ desactivada", + "backup_controller_page_status_on": "A copia de seguridade automÃĄtica en primeiro plano estÃĄ activada", "backup_controller_page_storage_format": "{used} de {total} usado", "backup_controller_page_to_backup": "Álbums para facer copia de seguridade", "backup_controller_page_total_sub": "Todas as fotos e vídeos Ãēnicos dos ÃĄlbums seleccionados", + "backup_controller_page_turn_off": "Desactivar copia de seguridade en primeiro plano", + "backup_controller_page_turn_on": "Activar copia de seguridade en primeiro plano", + "backup_controller_page_uploading_file_info": "Subindo informaciÃŗn do ficheiro", + "backup_err_only_album": "Non se pode eliminar o Ãēnico ÃĄlbum", "backup_error_sync_failed": "A sincronizaciÃŗn fallou. Non se pode procesar a copia de seguridade.", "backup_info_card_assets": "activos", + "backup_manual_cancelled": "Cancelado", + "backup_manual_in_progress": "Subida xa en progreso. IntÊnteo despois dun tempo", + "backup_manual_success": "Éxito", + "backup_manual_title": "Estado da subida", "backup_options": "OpciÃŗns de copia de seguridade", + "backup_options_page_title": "OpciÃŗns da copia de seguridade", + "backup_setting_subtitle": "Xestionar a configuraciÃŗn de carga en segundo plano e primeiro plano", "backup_settings_subtitle": "Xestionar configuraciÃŗn de subidas", + "backup_upload_details_page_more_details": "Toca para mais detalles", "backward": "AtrÃĄs", - "battery_optimization_backup_reliability": "Desactivar as optimizaciÃŗns de batería pode mellorar a fiabilidade da copia de seguridade en segundo plano", "biometric_auth_enabled": "AutenticaciÃŗn biomÊtrica activada", "biometric_locked_out": "EstÃĄ bloqueado da autenticaciÃŗn biomÊtrica", "biometric_no_options": "Non hai opciÃŗns biomÊtricas dispoÃąibles", "biometric_not_available": "A autenticaciÃŗn biomÊtrica non estÃĄ dispoÃąible neste dispositivo", + "birthdate_saved": "Data de nacemento gardada correctamente", "birthdate_set_description": "A data de nacemento Ãēsase para calcular a idade desta persoa no momento dunha foto.", "blurred_background": "Fondo borroso", - "browse_templates": "Explorar modelos", "bugs_and_feature_requests": "Erros e Solicitudes de FunciÃŗns", "build": "CompilaciÃŗn", "build_image": "Construír Imaxe", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "EstÃĄ seguro de que quere conservar {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto resolverÃĄ todos os grupos duplicados sen eliminar nada.", "bulk_trash_duplicates_confirmation": "EstÃĄ seguro de que quere mover masivamente ao lixo {count, plural, one {# activo duplicado} other {# activos duplicados}}? Isto conservarÃĄ o activo mÃĄis grande de cada grupo e moverÃĄ ao lixo todos os demais duplicados.", "buy": "Apoiar Immich", + "cache_settings_clear_cache_button": "Borrar cachÊ", + "cache_settings_clear_cache_button_title": "Borra a cachÊ da aplicaciÃŗn. Isto afectarÃĄ significativamente o rendemento da aplicaciÃŗn ata que a cachÊ se reconstruíu.", + "cache_settings_duplicated_assets_clear_button": "BORRAR", + "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que a aplicaciÃŗn ten na lista de ignorados", + "cache_settings_duplicated_assets_title": "Activos Duplicados ({count})", + "cache_settings_statistics_album": "Miniaturas da biblioteca", + "cache_settings_statistics_full": "Imaxes completas", + "cache_settings_statistics_shared": "Miniaturas de ÃĄlbums compartidos", + "cache_settings_statistics_thumbnail": "Miniaturas", + "cache_settings_statistics_title": "Uso da cachÊ", + "cache_settings_subtitle": "Controlar o comportamento da cachÊ da aplicaciÃŗn mÃŗbil Immich", + "cache_settings_tile_subtitle": "Controlar o comportamento do almacenamento local", + "cache_settings_tile_title": "Almacenamento Local", + "cache_settings_title": "ConfiguraciÃŗn da CachÊ", "camera": "CÃĄmara", "camera_brand": "Marca da cÃĄmara", "camera_model": "Modelo da cÃĄmara", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Non se pode actualizar a descriciÃŗn", "cast": "Enviar a dispositivo", "cast_description": "Configurar destinos dispoÃąibles para enviar a dispositivo", - "change": "Cambiar", "change_date": "Cambiar data", "change_description": "Cambiar descriciÃŗn", "change_display_order": "Cambiar orde de visualizaciÃŗn", + "change_expiration_time": "Cambiar hora de caducidade", "change_location": "Cambiar localizaciÃŗn", "change_name": "Cambiar nome", "change_name_successfully": "Nome cambiado correctamente", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Os contrasinais non coinciden", "change_password_form_reenter_new_password": "Reintroducir Novo Contrasinal", "change_pin_code": "Cambiar cÃŗdigo PIN", + "change_trigger": "Cambiar o disparador", + "change_trigger_prompt": "Seguro que queres cambiar o disparador? EliminarÃĄ todas as acciÃŗns e filtros existentes.", "change_your_password": "Cambiar o seu contrasinal", "changed_visibility_successfully": "Visibilidade cambiada correctamente", "charging": "Cargando", "charging_requirement_mobile_backup": "A copia de seguridade en segundo plano require que o dispositivo estea cargando", + "check_corrupt_asset_backup": "Comprobar copias de seguridade de activos corruptos", + "check_corrupt_asset_backup_button": "Realizar comprobaciÃŗn", + "check_corrupt_asset_backup_description": "Execute esta comprobaciÃŗn sÃŗ a travÊs da wifi e unha vez que todos os activos teÃąan copia de seguridade. O procedemento pode tardar uns minutos.", "check_logs": "Comprobar Rexistros", "checksum": "Suma de comprobaciÃŗn", - "choose": "Escoller", "choose_matching_people_to_merge": "Elixir persoas coincidentes para fusionar", "city": "Cidade", "cleanup_confirm_description": "Immich atopou {count} recursos (creados antes de {date}) copiados de seguridade no servidor. Queres eliminar as copias locais deste dispositivo?", @@ -719,10 +774,11 @@ "clear": "Limpar", "clear_all": "Limpar todo", "clear_all_recent_searches": "Limpar todas as buscas recentes", - "clear_failed_count": "Limpar ({count}) fallos", "clear_file_cache": "Limpar cachÊ de ficheiros", "clear_message": "Limpar mensaxe", "clear_value": "Limpar valor", + "client_cert_dialog_msg_confirm": "Aceptar", + "client_cert_enter_password": "Introducir Contrasinal", "client_cert_import": "Importar", "client_cert_import_success_msg": "Certificado de cliente importado", "client_cert_invalid_msg": "Ficheiro de certificado invÃĄlido ou contrasinal incorrecto", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certificado de cliente eliminado", "client_cert_subtitle": "Soporta sÃŗ o formato PKCS12 (.p12, .pfx). A importaciÃŗn ou eliminaciÃŗn de certificados estÃĄ dispoÃąible sÃŗ antes de iniciar sesiÃŗn", "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", + "clockwise": "Sentido horario", "close": "Pechar", "collapse": "Contraer", "collapse_all": "Contraer todo", "color": "Cor", + "color_theme": "Tema de cor", "command": "Comando", "command_palette_prompt": "Atopa rapidamente pÃĄxinas, acciÃŗns ou comandos", "command_palette_to_close": "para pechar", @@ -747,7 +805,6 @@ "comments_are_disabled": "Os comentarios estÃĄn desactivados", "common_create_new_album": "Crear novo ÃĄlbum", "completed": "Completado", - "configuration": "ConfiguraciÃŗn", "confirm": "Confirmar", "confirm_admin_password": "Confirmar Contrasinal do Administrador", "confirm_delete_face": "EstÃĄ seguro de que quere eliminar a cara de {name} do activo?", @@ -762,16 +819,19 @@ "contain": "Conter", "context": "Contexto", "continue": "Continuar", - "control_bottom_app_bar_add_tags": "Engadir etiquetas", + "control_bottom_app_bar_create_new_album": "Crear novo ÃĄlbum", + "control_bottom_app_bar_delete_from_immich": "Eliminar de Immich", "control_bottom_app_bar_delete_from_local": "Eliminar do dispositivo", "control_bottom_app_bar_edit_location": "Editar localizaciÃŗn", "control_bottom_app_bar_edit_time": "Editar Data e Hora", + "control_bottom_app_bar_share_link": "Compartir LigazÃŗn", + "control_bottom_app_bar_share_to": "Compartir Con", "control_bottom_app_bar_trash_from_immich": "Mover ao Lixo", "copied_image_to_clipboard": "Imaxe copiada ao portapapeis.", "copied_to_clipboard": "Copiado ao portapapeis!", "copy_error": "Erro ao copiar", + "copy_file_path": "Copiar ruta do ficheiro", "copy_image": "Copiar Imaxe", - "copy_json": "Copiar JSON", "copy_link": "Copiar ligazÃŗn", "copy_link_to_clipboard": "Copiar ligazÃŗn ao portapapeis", "copy_password": "Copiar contrasinal", @@ -789,6 +849,7 @@ "create_link_to_share": "Crear ligazÃŗn para compartir", "create_link_to_share_description": "Permitir que calquera persoa coa ligazÃŗn vexa a(s) foto(s) seleccionada(s)", "create_new": "CREAR NOVO", + "create_new_face": "Crear nova cara", "create_new_person": "Crear nova persoa", "create_new_person_hint": "Asignar activos seleccionados a unha nova persoa", "create_new_user": "Crear novo usuario", @@ -805,33 +866,37 @@ "created_at": "Creado", "creating_linked_albums": "Creando ÃĄlbums vinculados...", "crop": "Recortar", + "crop_aspect_ratio_fixed": "Fixado", "crop_aspect_ratio_free": "Libre", "crop_aspect_ratio_original": "Orixinal", "crop_aspect_ratio_square": "Cadrado", + "curated_object_page_title": "Cousas", "current_device": "Dispositivo actual", "current_pin_code": "CÃŗdigo PIN actual", "current_server_address": "Enderezo do servidor actual", "custom_date": "Data personalizada", "custom_locale": "ConfiguraciÃŗn Rexional Personalizada", "custom_locale_description": "Formatar datas, tempos e nÃēmeros baseÃĄndose na lingua e a rexiÃŗn", + "custom_url": "URL personalizada", "cutoff_date_description": "Manter fotos dos Ãēltimosâ€Ļ", "cutoff_day": "{count, plural, one {day} other {days}}", "cutoff_year": "{count, plural, one {day} other {days}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Escuro", "dark_theme": "Alternar a tema escuro", "date": "Data", "date_after": "Data posterior a", "date_and_time": "Data e Hora", "date_before": "Data anterior a", - "date_of_birth": "Data de nacemento", + "date_format": "E, d LLL, y â€ĸ H:mm", "date_of_birth_saved": "Data de nacemento gardada correctamente", "date_range": "Rango de datas", - "date_time_original": "Data/Hora Orixinal", "day": "Día", "days": "Días", "deduplicate_all": "Eliminar todos os duplicados", - "default_quality_subtitle": "Calidade usada ao tocar Compartir. MantÊn premido o botÃŗn Compartir para escoller cada vez.", - "default_share_quality": "Calidade de comparticiÃŗn predeterminada", + "default_locale": "ConfiguraciÃŗn rexional predeterminada", + "default_locale_description": "Formatee as datas e os nÃēmeros segÃēn a configuraciÃŗn rexional do seu navegador", "delete": "Eliminar", "delete_action_confirmation_message": "EstÃĄ seguro de que quere eliminar este ficheiro? Esta acciÃŗn moverÃĄ o ficheiro ao lixo do servidor e preguntaralle se tamÊn quere eliminalo localmente", "delete_action_prompt": "{count} eliminado(s)", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Estes elementos eliminaranse permanentemente de Immich e do seu dispositivo", "delete_dialog_alert_local": "Estes elementos eliminaranse permanentemente do seu dispositivo pero aínda estarÃĄn dispoÃąibles no servidor Immich", "delete_dialog_alert_local_non_backed_up": "AlgÃēns dos elementos non teÃąen copia de seguridade en Immich e eliminaranse permanentemente do seu dispositivo", + "delete_dialog_alert_remote": "Estes elementos eliminaranse permanentemente do servidor Immich", + "delete_dialog_ok_force": "Eliminar Igualmente", "delete_dialog_title": "Eliminar Permanentemente", "delete_duplicates_confirmation": "EstÃĄ seguro de que quere eliminar permanentemente estes duplicados?", "delete_face": "Eliminar cara", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "EstÃĄ seguro de que quere eliminar a etiqueta {tagName}?", "delete_user": "Eliminar usuario", "deleted_shared_link": "LigazÃŗn compartida eliminada", + "deletes_missing_assets": "Elimina activos que faltan no disco", "description": "DescriciÃŗn", + "description_input_hint_text": "Engadir descriciÃŗn...", + "description_input_submit_error": "Erro ao actualizar a descriciÃŗn, comprobe o rexistro para mÃĄis detalles", "deselect_all": "Deseleccionar todo", "details": "Detalles", "direction": "DirecciÃŗn", "disable": "Desactivar", "disabled": "Desactivado", + "disallow_edits": "Non permitir ediciÃŗns", "discord": "Discord", "discover": "Descubrir", "discovered_devices": "Dispositivos descubertos", "dismiss_all_errors": "Descartar todos os erros", + "dismiss_error": "Descartar erro", "display_options": "OpciÃŗns de visualizaciÃŗn", "display_order": "Orde de visualizaciÃŗn", "display_original_photos": "Mostrar fotos orixinais", @@ -876,9 +948,11 @@ "documentation": "DocumentaciÃŗn", "done": "Feito", "download": "Descargar", + "download_action_prompt": "Descargando {count} activo(s)", "download_canceled": "Descarga cancelada", "download_complete": "Descarga completada", "download_enqueue": "Descarga en cola", + "download_error": "Erro na Descarga", "download_failed": "Descarga fallada", "download_finished": "Descarga finalizada", "download_include_embedded_motion_videos": "Vídeos incrustados", @@ -888,15 +962,15 @@ "download_paused": "Descarga pausada", "download_settings": "Descarga", "download_settings_description": "Xestionar configuraciÃŗns relacionadas coa descarga de activos", + "download_started": "Descarga iniciada", + "download_sucess": "Descarga exitosa", + "download_sucess_android": "Os medios descargÃĄronse en DCIM/Immich", "download_waiting_to_retry": "Agardando para reintentar", "downloading": "Descargando", "downloading_asset_filename": "Descargando activo {filename}", "downloading_from_icloud": "Descargando dende iCloud", "downloading_media": "Descargando medios", - "drag_to_reorder": "Arraste para reordenar", "drop_files_to_upload": "Solte ficheiros en calquera lugar para cargar", - "duplicate": "Duplicar", - "duplicate_workflow": "Duplicar fluxo de traballo", "duplicates": "Duplicados", "duplicates_description": "Resolve cada grupo indicando cales, se os houber, son duplicados.", "duration": "DuraciÃŗn", @@ -908,7 +982,9 @@ "edit_date_and_time": "Editar data e hora", "edit_date_and_time_action_prompt": "{count} data(s) e hora(s) editada(s)", "edit_date_and_time_by_offset": "Cambiar data por desfase", + "edit_date_and_time_by_offset_interval": "Nova franxa de datas: {from} - {to}", "edit_description": "Editar descriciÃŗn", + "edit_description_prompt": "Por favor, seleccione unha nova descriciÃŗn:", "edit_exclusion_pattern": "Editar patrÃŗn de exclusiÃŗn", "edit_faces": "Editar caras", "edit_key": "Editar chave", @@ -923,6 +999,9 @@ "edit_user": "Editar usuario", "edit_workflow": "Editar fluxo de traballo", "editor": "Editor", + "editor_close_without_save_prompt": "Os cambios non se gardarÃĄn", + "editor_close_without_save_title": "Pechar editor?", + "editor_confirm_reset_all_changes": "Seguro que queres restablecer todos os cambios?", "editor_discard_edits_confirm": "Descartar ediciÃŗns", "editor_discard_edits_prompt": "Tes cambios sen gardar. EstÃĄs seguro que queres descartalos?", "editor_discard_edits_title": "Descartar cambios?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Introduza o seu cÃŗdigo PIN", "enter_your_pin_code_subtitle": "Introduza o seu cÃŗdigo PIN para acceder ao cartafol bloqueado", "error": "Erro", + "error_change_sort_album": "Erro ao cambiar a orde de clasificaciÃŗn do ÃĄlbum", "error_delete_face": "Erro ao eliminar a cara do activo", + "error_getting_places": "Erro ao obter lugares", "error_loading_albums": "Produciuse un erro ao cargar os ÃĄlbums", "error_loading_image": "Erro ao cargar a imaxe", "error_loading_partners": "Erro cargando compaÃąeiros/as: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Erro ao eliminar a chave do produto", "failed_to_reset_pin_code": "Erro ao restablecer o cÃŗdigo PIN", "failed_to_stack_assets": "Erro ao apilar activos", - "failed_to_tag_assets": "Ocorreu un erro ao etiquetar recursos", "failed_to_unstack_assets": "Erro ao desapilar activos", "failed_to_update_notification_status": "Erro ao actualizar o estado das notificaciÃŗns", "incorrect_email_or_password": "Correo electrÃŗnico ou contrasinal incorrectos", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Engadir DescriciÃŗn...", "exif_bottom_sheet_description_error": "Erro ao actualizar a descriciÃŗn", + "exif_bottom_sheet_details": "DETALLES", + "exif_bottom_sheet_location": "LOCALIZACIÓN", "exif_bottom_sheet_no_description": "Sen descriciÃŗn", + "exif_bottom_sheet_people": "PERSOAS", + "exif_bottom_sheet_person_add_person": "Engadir nome", "exit_slideshow": "Saír da PresentaciÃŗn", "expand": "Expandir", "expand_all": "Expandir todo", + "experimental_settings_new_asset_list_subtitle": "Traballo en progreso", + "experimental_settings_new_asset_list_title": "Activar grella de fotos experimental", + "experimental_settings_subtitle": "Use baixo o seu propio risco!", + "experimental_settings_title": "Experimental", "expire_after": "Caduca despois de", "expired": "Caducado", "expires_date": "Caduca o {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Exportar como JSON", "export_database": "Exportar a Base de Datos", "export_database_description": "Exportar a base de datos SQLite", - "exposure_time": "Tempo de exposiciÃŗn", "extension": "ExtensiÃŗn", "external": "Externo", "external_libraries": "Bibliotecas Externas", "external_network": "Rede externa", "external_network_sheet_info": "Cando non estea na rede wifi preferida, a aplicaciÃŗn conectarase ao servidor a travÊs da primeira das seguintes URLs que poida alcanzar, comezando de arriba a abaixo", - "f_number": "Abertura", "face_unassigned": "Sen asignar", "failed": "Fallado", "failed_count": "Fallou: {count}", "failed_to_authenticate": "Fallou a autenticaciÃŗn", - "failed_to_delete_file": "Non se puido eliminar o ficheiro", "failed_to_load_assets": "Erro ao cargar activos", "failed_to_load_folder": "Erro ao cargar o cartafol", "favorite": "Favorito", "favorite_action_prompt": "{count} engadido/a a Favoritos", "favorite_or_unfavorite_photo": "Marcar ou desmarcar como favorito", "favorites": "Favoritos", + "favorites_page_no_favorites": "Non se atoparon activos favoritos", "feature_photo_updated": "Foto destacada actualizada", "features": "FunciÃŗns", + "features_in_development": "Funcionalidades en Desenvolvemento", "features_setting_description": "Xestionar as funciÃŗns da aplicaciÃŗn", "file_name_or_extension": "Nome do ficheiro ou extensiÃŗn", "file_name_text": "Nome do arquivo", + "file_name_with_value": "Nome do arquivo: {file_name}", "file_size": "TamaÃąo do arquivo", "filename": "Nome do ficheiro", "filetype": "Tipo de ficheiro", "filter": "Filtro", + "filter_description": "CondiciÃŗns para filtrar os activos obxectivo", "filter_people": "Filtrar persoas", "filter_places": "Filtrar lugares", "filter_tags": "Filtrar etiquetas", "filters": "Filtros", + "find_them_fast": "AtÃŗpeos rÃĄpido por nome coa busca", "first": "Primeiro/a", "fix_incorrect_match": "Corrixir coincidencia incorrecta", - "focal_length": "Distancia Focal", "folder": "Cartafol", "folder_not_found": "Cartafol non atopado", "folders": "Cartafoles", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Move as fotos e os vídeos dos que fixeches unha copia de seguridade ÃĄ papeleira do teu dispositivo para liberar espazo. As tÃēas copias no servidor permanecen seguras.", "free_up_space_settings_subtitle": "Liberar almacenamento do dispositivo", "full_path": "Ruta completa: {path}", - "full_path_or_folder": "Ruta completa ou cartafol", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidade carga recursos externos de Google para poder funcionar.", "general": "Xeral", @@ -1168,6 +1256,7 @@ "group_owner": "Agrupar por propietario", "group_places_by": "Agrupar lugares por...", "group_year": "Agrupar por ano", + "haptic_feedback_switch": "Activar resposta hÃĄptica", "haptic_feedback_title": "Resposta HÃĄptica", "has_quota": "Ten cota", "hash_asset": "Facer hash do recurso", @@ -1188,12 +1277,29 @@ "hide_schema": "Ocultar esquema", "hide_text_recognition": "Ocultar recoÃąecemento de texto", "hide_unnamed_people": "Ocultar persoas sen nome", + "home_page_add_to_album_conflicts": "Engadidos {added} activos ao ÃĄlbum {album}. {failed} activos xa estÃĄn no ÃĄlbum.", + "home_page_add_to_album_err_local": "Non se poden engadir activos locais a ÃĄlbums aínda, omitindo", + "home_page_add_to_album_success": "Engadidos {added} activos ao ÃĄlbum {album}.", + "home_page_album_err_partner": "Non se poden engadir activos de compaÃąeiro/a a un ÃĄlbum aínda, omitindo", + "home_page_archive_err_local": "Non se poden arquivar activos locais aínda, omitindo", + "home_page_archive_err_partner": "Non se poden arquivar activos de compaÃąeiro/a, omitindo", "home_page_building_timeline": "Construíndo a liÃąa de tempo", + "home_page_delete_err_partner": "Non se poden eliminar activos de compaÃąeiro/a, omitindo", + "home_page_delete_remote_err_local": "Activos locais na selecciÃŗn de eliminaciÃŗn remota, omitindo", + "home_page_favorite_err_local": "Non se poden marcar como favoritos activos locais aínda, omitindo", + "home_page_favorite_err_partner": "Non se poden marcar como favoritos activos de compaÃąeiro/a aínda, omitindo", + "home_page_first_time_notice": "Se esta Ê a primeira vez que usa a aplicaciÃŗn, asegÃērese de elixir un ÃĄlbum de copia de seguridade para que a liÃąa de tempo poida encherse con fotos e vídeos nel", + "home_page_locked_error_local": "Non Ê posíbel mover os recursos locais ao cartafol bloqueado, saltando", + "home_page_locked_error_partner": "Non Ê posíbel mover os recursos do/a colaborador/a ao cartafol bloqueado, saltando", + "home_page_share_err_local": "Non se poden compartir activos locais mediante ligazÃŗn, omitindo", + "home_page_upload_err_limit": "SÃŗ se pode cargar un mÃĄximo de 30 activos ÃĄ vez, omitindo", "host": "Servidor", "hour": "Hora", "hours": "Horas", "id": "ID", "idle": "Inactivo/a", + "ignore_icloud_photos": "Ignorar fotos de iCloud", + "ignore_icloud_photos_description": "As fotos que estÃĄn almacenadas en iCloud non se cargarÃĄn ao servidor Immich", "image": "Imaxe", "image_alt_text_date": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a o {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a con {person1} o {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1} e {person2} o {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1}, {person2} e {person3} o {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo} other {Imaxe}} tomado/a en {city}, {country} con {person1}, {person2} e {additionalCount, number} outros/as o {date}", + "image_saved_successfully": "Imaxe gardada", + "image_viewer_page_state_provider_download_started": "Descarga Iniciada", + "image_viewer_page_state_provider_download_success": "Descarga Exitosa", + "image_viewer_page_state_provider_share_error": "Erro ao Compartir", "immich_logo": "Logo de Immich", "immich_web_interface": "Interface Web de Immich", "import_from_json": "Importar desde JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Compartir individual", "individual_shares": "ComparticiÃŗns individuais", "info": "InformaciÃŗn", - "integrity_checks": "VerificaciÃŗns de integridade", "interval": { "day_at_onepm": "Todos os días ÃĄs 13:00", "hours": "Cada {hours, plural, one {hora} other {{hours, number} horas}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Formato de data invÃĄlido", "invite_people": "Invitar Persoas", "invite_to_album": "Invitar ao ÃĄlbum", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Obtívose ÃĄs {dateTime}", + "ios_debug_info_last_sync_at": "Última sincronizaciÃŗn: {dateTime}", + "ios_debug_info_no_processes_queued": "Sen procesos en segundo plano en cola", + "ios_debug_info_no_sync_yet": "Aínda non se executou ningunha tarefa de sincronizaciÃŗn en segundo plano", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proceso en segundo plano en cola} other {{count} procesos en segundo plano en cola}}", + "ios_debug_info_processing_ran_at": "O procesamento executouse ÃĄs {dateTime}", "items_count": "{count, plural, one {# elemento} other {# elementos}}", "jobs": "Traballos", + "json_editor": "Editor JSON", + "json_error": "Erro JSON", "keep": "Conservar", "keep_albums": "Gardar ÃĄlbums", "keep_albums_count": "Manter {count} {count, plural, one {album} other {albums}}", @@ -1259,12 +1375,14 @@ "leave": "Saír", "leave_album": "Deixar o ÃĄlbum", "lens_model": "Modelo da lente", - "less": "Menos", "let_others_respond": "Permitir que outros respondan", "level": "Nivel", "library": "Biblioteca", "library_add_folder": "Engadir carpeta", "library_edit_folder": "Editar carpeta", + "library_options": "OpciÃŗns da biblioteca", + "library_page_device_albums": "Álbums no Dispositivo", + "library_page_new_album": "Novo ÃĄlbum", "library_page_sort_asset_count": "NÃēmero de activos", "library_page_sort_created": "Data de creaciÃŗn", "library_page_sort_last_modified": "Última modificaciÃŗn", @@ -1274,14 +1392,11 @@ "light_theme": "Cambiar a tema claro", "like": "GÃēstame", "like_deleted": "GÃēstame eliminado", - "link": "LigazÃŗn", "link_motion_video": "Ligar vídeo en movemento", "link_to_docs": "Para mÃĄis informaciÃŗn, consulte a documentaciÃŗn.", "link_to_oauth": "Ligar a OAuth", "linked_oauth_account": "Conta OAuth ligada", "list": "Lista", - "live": "En directo", - "load_more": "Cargar mÃĄis", "loading": "Cargando", "loading_search_results_failed": "Erro ao cargar os resultados da busca", "local": "Local", @@ -1310,9 +1425,11 @@ "login": "Iniciar sesiÃŗn", "login_disabled": "O inicio de sesiÃŗn foi desactivado", "login_form_api_exception": "ExcepciÃŗn da API. Por favor, comprobe a URL do servidor e intÊnteo de novo.", + "login_form_back_button_text": "AtrÃĄs", "login_form_email_hint": "oseuemail@dominio.com", "login_form_endpoint_hint": "http://ip-do-servidor:porto", "login_form_endpoint_url": "URL do Punto Final do Servidor", + "login_form_err_http": "Por favor, especifique http:// ou https://", "login_form_err_invalid_email": "Correo electrÃŗnico invÃĄlido", "login_form_err_invalid_url": "URL invÃĄlida", "login_form_err_leading_whitespace": "Espazo en branco inicial", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Erro ao iniciar sesiÃŗn, comprobe a URL do servidor, correo electrÃŗnico e contrasinal", "login_form_handshake_exception": "Houbo unha ExcepciÃŗn de Handshake co servidor. Active o soporte para certificados autofirmados nas configuraciÃŗns se estÃĄ a usar un certificado autofirmado.", "login_form_password_hint": "contrasinal", - "login_form_server_empty": "Introduza unha URL do servidor", - "login_form_server_error": "Non se puido conectar co servidor", - "login_has_been_disabled": "O inicio de sesiÃŗn foi desactivado", + "login_form_save_login": "Manter sesiÃŗn iniciada", + "login_form_server_empty": "Introduza unha URL do servidor.", + "login_form_server_error": "Non se puido conectar co servidor.", + "login_has_been_disabled": "O inicio de sesiÃŗn foi desactivado.", "login_password_changed_error": "Houbo un erro ao actualizar o seu contrasinal", "login_password_changed_success": "Contrasinal actualizado correctamente", "logout_all_device_confirmation": "EstÃĄ seguro de que quere pechar sesiÃŗn en todos os dispositivos?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Restablecendo base de datos", "maintenance_description": "Immich foi posto en modo de mantemento.", "maintenance_end": "Finalizar o modo de mantemento", + "maintenance_end_error": "Erro ao finalizar o modo de mantemento.", "maintenance_logged_in_as": "SesiÃŗn iniciada actualmente como {user}", "maintenance_restore_from_backup": "Restaurar dende unha copia de seguridade", "maintenance_restore_library": "Restaura a tÃēa librería", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Abrir axustes", "manage_media_access_subtitle": "Permitir que a aplicaciÃŗn Immich xestione e mova ficheiros multimedia.", "manage_media_access_title": "Acceso ÃĄ xestiÃŗn de medios", + "manage_shared_links": "Xestionar ligazÃŗns compartidas", "manage_sharing_with_partners": "Xestionar comparticiÃŗn con compaÃąeiros/as", "manage_the_app_settings": "Xestionar a configuraciÃŗn da aplicaciÃŗn", "manage_your_account": "Xestionar a sÃēa conta", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Xestionar os seus dispositivos con sesiÃŗn iniciada", "manage_your_oauth_connection": "Xestionar a sÃēa conexiÃŗn OAuth", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {Sen fotos nesta ÃĄrea} one {# foto} other {# fotos}}", "map_cannot_get_user_location": "Non se pode obter a localizaciÃŗn do usuario", + "map_location_dialog_yes": "Si", "map_location_picker_page_use_location": "Usar esta localizaciÃŗn", "map_location_service_disabled_content": "O servizo de localizaciÃŗn debe estar activado para mostrar activos da sÃēa localizaciÃŗn actual. Quere activalo agora?", "map_location_service_disabled_title": "Servizo de localizaciÃŗn deshabilitado", - "map_marker_for_image": "Marcador do mapa para a imaxe tirada en {city}, {country}", + "map_marker_for_images": "Marcador de mapa para imaxes tomadas en {city}, {country}", "map_marker_with_image": "Marcador de mapa con imaxe", "map_no_location_permission_content": "Necesítase permiso de localizaciÃŗn para mostrar activos da sÃēa localizaciÃŗn actual. Quere permitilo agora?", "map_no_location_permission_title": "Permiso de localizaciÃŗn denegado", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Últimos {days} días", "map_settings_date_range_option_year": "Último ano", "map_settings_date_range_option_years": "Últimos {years} anos", + "map_settings_dialog_title": "ConfiguraciÃŗn do Mapa", "map_settings_include_show_archived": "Incluír Arquivados", "map_settings_include_show_partners": "Incluír CompaÃąeiros/as", "map_settings_only_show_favorites": "Mostrar SÃŗ Favoritos", "map_settings_theme_settings": "Tema do Mapa", + "map_zoom_to_see_photos": "Afaste o zoom para ver fotos", "mark_all_as_read": "Marcar todo como lido", + "mark_as_read": "Marcar como lido", "marked_all_as_read": "Marcado todo como lido", "matches": "Coincidencias", "matching_assets": "Recursos Correspondentes", - "media_chrome": { - "auto": "Auto", - "captions": "Lendas", - "captions_off": "Apagado", - "closed_captions": "Lendas ocultas", - "decode_error": "Erro de decodificaciÃŗn", - "disable_captions": "Desactivar lendas", - "enable_captions": "Activar lendas", - "enter_fullscreen_mode": "Entrar en modo pantalla completa", - "exit_fullscreen_mode": "Sair do modo pantalla completa", - "loop": "Bucle", - "media_error_description": "Un erro no arquivo multimedia provocou a cancelaciÃŗn da reproduciÃŗn. O contido podería estar corruptou ou o teu navegador non admite este formato.", - "media_loading": "cargando medios", - "mute": "Silenciar", - "network_error": "Erro de rede", - "network_error_description": "Un erro na rede provocou que a descarga dos medios fallase.", - "not_supported_error": "Fonte non soportada", - "playback_rate": "Taxa de reproduciÃŗn", - "playback_rate_current": "taxa de reproduciÃŗn actual", - "playback_rate_value": "Taxa de produciÃŗn {playbackRate}", - "playback_time": "tempo de reproduciÃŗn", - "quality": "Calidade", - "second": "segundo", - "seconds": "segundos", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} restante", - "unmute": "Activar son", - "unsupported_error_description": "Ocorreu un erro non soportado. O servidor ou a rede fallaron, ou o teu navegador non soporta este formato.", - "video_not_loaded_unknown_time": "video non cargado, tempo descoÃąecido.", - "video_player": "reprodutor de vídeo", - "volume": "volume" - }, "media_type": "Tipo de medio", "memories": "Recordos", "memories_all_caught_up": "Todo ao día", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Quere fusionar estas persoas? Esta acciÃŗn Ê irreversible.", "merge_people_successfully": "Persoas fusionadas correctamente", "merged_people_count": "Fusionadas {count, plural, one {# persoa} other {# persoas}}", - "minFaces": "Caras mínimas", - "minFaces_description": "O nÃēmero mínimo de rostros recoÃąecidos para que se mostre unha persona", "minimize": "Minimizar", "minute": "Minuto", "minutes": "Minutos", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Faltantes", "mobile_app": "AplicaciÃŗn MÃŗbil", "mobile_app_download_onboarding_note": "Descarga a aplicaciÃŗn mÃŗbil complementaria usando as seguintes opciÃŗns", "model": "Modelo", - "modify_date": "Modificar data", "month": "Mes", + "monthly_title_text_date_format": "MMMM a", "more": "MÃĄis", - "motion": "Movemento", "move": "Mover", + "move_down": "Baixar", "move_off_locked_folder": "Mover fÃŗra do cartafol bloqueado", "move_to": "Mover a", "move_to_device_trash": "Mover ÃĄ papeleira do dispositivo", "move_to_lock_folder_action_prompt": "{count} engadido/a ao cartafol bloqueado", "move_to_locked_folder": "Mover ao cartafol bloqueado", "move_to_locked_folder_confirmation": "Estas fotos e vídeo eliminaranse de todos os ÃĄlbums e sÃŗ serÃĄn visíbeis dende o cartafol bloqueado", + "move_up": "Subir", + "moved_to_archive": "Moveuse {count, plural, one {# recurso} other {# recursos}} ao arquivo", + "moved_to_library": "Moveuse {count, plural, one {# recurso} other {# recursos}} ÃĄ biblioteca", "moved_to_trash": "Movido ao lixo", + "multiselect_grid_edit_date_time_err_read_only": "Non se pode editar a data de activo(s) de sÃŗ lectura, omitindo", + "multiselect_grid_edit_gps_err_read_only": "Non se pode editar a localizaciÃŗn de activo(s) de sÃŗ lectura, omitindo", "mute_memories": "Silenciar Recordos", "my_albums": "Os meus ÃĄlbums", - "my_immich_description": "Copiar a pÃĄxina actual como LigazÃŗn do Meu Immich", - "my_immich_title": "LigazÃŗn do Meu Immich", "name": "Nome", "name_or_nickname": "Nome ou alcume", "name_required": "O nome Ê obligatorio", @@ -1480,11 +1576,12 @@ "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave API", - "new_feature": "Nova funciÃŗn", + "new_date_range": "Novo rango de datas", "new_password": "Novo contrasinal", "new_person": "Nova persoa", "new_pin_code": "Novo cÃŗdigo PIN", "new_pin_code_subtitle": "Esta Ê a tÃēa primeira vez accedendo ÃĄ carpeta segura. Crea un cÃŗdigo PIN para acceder de maneira segura a esta pÃĄxina", + "new_timeline": "Nova liÃąa de tempo", "new_update": "Nova actualizaciÃŗn", "new_user_created": "Novo usuario creado", "new_version_available": "NOVA VERSIÓN DISPOÑIBLE", @@ -1492,6 +1589,7 @@ "next": "Seguinte", "next_memory": "Seguinte recordo", "no": "Non", + "no_actions_added": "Non hai acciÃŗns engadidas polo momento", "no_albums_found": "Non se atoparon ÃĄlbums", "no_albums_message": "Cree un ÃĄlbum para organizar as sÃēas fotos e vídeos", "no_albums_with_name_yet": "Parece que aínda non ten ningÃēn ÃĄlbum con este nome.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Non se atoparon dispositivos de transmisiÃŗn", "no_checksum_local": "Non hai suma de verificaciÃŗn dispoÃąible - non se poden obter os activos locais", "no_checksum_remote": "Non hai suma de verificaciÃŗn dispoÃąible - non se pode obter o activo remoto", + "no_configuration_needed": "Non se precisa configuraciÃŗn", "no_devices": "Dispositivos non autorizados", "no_duplicates_found": "Non se atoparon duplicados.", "no_exif_info_available": "Non hai informaciÃŗn EXIF dispoÃąible", "no_explore_results_message": "Suba mÃĄis fotos para explorar a sÃēa colecciÃŗn.", "no_favorites_message": "Engada favoritos para atopar rapidamente as sÃēas mellores fotos e vídeos", + "no_filters_added": "Aínda non se engadiron filtros", "no_libraries_message": "Cree unha biblioteca externa para ver as sÃēas fotos e vídeos", "no_local_assets_found": "Non se atoparon elementos locais con esta suma de comprobaciÃŗn", + "no_location_set": "Non se estableceu a localizaciÃŗn", "no_locked_photos_message": "As fotos e vídeos no cartafol con chave estÃĄn ocultos e non aparecerÃĄn mentres navegas ou buscas na tÃēa biblioteca.", "no_name": "Sen Nome", "no_notifications": "Sen notificaciÃŗns", @@ -1518,7 +1619,7 @@ "no_results": "Sen resultados", "no_results_description": "Probe cun sinÃŗnimo ou palabra chave mÃĄis xeral", "no_shared_albums_message": "Cree un ÃĄlbum para compartir fotos e vídeos con persoas na sÃēa rede", - "no_steps": "Sen pasos engaditos aínda", + "no_uploads_in_progress": "Non hai cargas en curso", "none": "Nada", "not_allowed": "Non permitido", "not_available": "Non dispoÃąible", @@ -1526,10 +1627,6 @@ "not_selected": "Non seleccionado", "notes": "Notas", "nothing_here_yet": "Aínda nada por aquí", - "notification_backup_reliability": "Activa as notificaciÃŗns para mellorar a fiabilidade da copia de seguridade en segundo plano", - "notification_enabled_list_tile_content": "Immich emprega notificaciÃŗns para copias de seguridade en segundo plano. XestiÃŗneas nos axustes do seu dispositivo.", - "notification_enabled_list_tile_open_button": "Abrir axustes", - "notification_enabled_list_tile_title": "NotificaciÃŗns activadas", "notification_permission_dialog_content": "Para activar as notificaciÃŗns, vaia a Axustes e seleccione permitir.", "notification_permission_list_tile_content": "Conceda permiso para activar as notificaciÃŗns.", "notification_permission_list_tile_enable_button": "Activar NotificaciÃŗns", @@ -1541,8 +1638,6 @@ "obtainium_configurator": "Configurador de Obtainium", "obtainium_configurator_instructions": "Emprega Obtainium para instalar e actualizar a aplicaciÃŗn de Android directamente desde o lanzamento do GitHub de Immich. Crea unha chave API e selecciona unha variante para xerar o teu enlace de configuraciÃŗn de Obtainium", "ocr": "OCR", - "ocr_body": "Agora Immich le o texto que hai dentro das tÃēas fotos, para que poidas buscalas polo que din.", - "ocr_title": "Buscar texto nas tÃēas fotos", "official_immich_resources": "Recursos Oficiais de Immich", "offline": "FÃŗra de liÃąa", "offset": "Desprazamento", @@ -1561,8 +1656,6 @@ "open": "Abrir", "open_calendar": "Abrir calendario", "open_in_browser": "Abrir no navegador", - "open_in_immich_body": "Configura Immich como a tÃēa galería en Android para abrir fotos directamente doutras aplicaciÃŗns.", - "open_in_immich_title": "Abrir fotos en Immich", "open_in_map_view": "Abrir na vista de mapa", "open_in_openstreetmap": "Abrir en OpenStreetMap", "open_the_search_filters": "Abrir os filtros de busca", @@ -1571,10 +1664,10 @@ "organize_into_albums": "Organizar en ÃĄlbums", "organize_into_albums_description": "PoÃąer as fotos existentes en ÃĄlbums usando as opciÃŗns de sincronizaciÃŗn actuais", "organize_your_library": "Organizar a sÃēa biblioteca", - "orientation": "OrientaciÃŗn", "original": "orixinal", "other": "Outro", "other_devices": "Outros dispositivos", + "other_entities": "Outras entidades", "other_variables": "Outras variables", "owned": "Propio", "owner": "Propietario", @@ -1584,9 +1677,12 @@ "partner_can_access_assets": "Todas as sÃēas fotos e vídeos excepto os de Arquivo e Eliminados", "partner_can_access_location": "A localizaciÃŗn onde se tomaron as sÃēas fotos", "partner_list_user_photos": "Fotos de {user}", + "partner_list_view_all": "Ver todo", "partner_page_empty_message": "As sÃēas fotos aínda non estÃĄn compartidas con ningÃēn compaÃąeiro/a.", "partner_page_no_more_users": "Non hai mÃĄis usuarios para engadir", + "partner_page_partner_add_failed": "Erro ao engadir compaÃąeiro/a", "partner_page_select_partner": "Seleccionar compaÃąeiro/a", + "partner_page_shared_to_title": "Compartido con", "partner_page_stop_sharing_content": "{partner} xa non poderÃĄ acceder ÃĄs sÃēas fotos.", "partner_sharing": "ComparticiÃŗn con CompaÃąeiro/a", "partners": "CompaÃąeiros/as", @@ -1608,6 +1704,8 @@ "people": "Persoas", "people_edits_count": "Editadas {count, plural, one {# persoa} other {# persoas}}", "people_feature_description": "Navegar por fotos e vídeos agrupados por persoas", + "people_selected": "{count, plural, one {# persoa seleccionada} other {# persoas seleccionadas}}", + "people_sidebar_description": "Mostrar unha ligazÃŗn a Persoas na barra lateral", "permanent_deletion_warning": "Aviso de eliminaciÃŗn permanente", "permanent_deletion_warning_setting_description": "Mostrar un aviso ao eliminar permanentemente activos", "permanently_delete": "Eliminar permanentemente", @@ -1617,6 +1715,14 @@ "permanently_deleted_assets_count": "Eliminados permanentemente {count, plural, one {# activo} other {# activos}}", "permission": "Permiso", "permission_empty": "O teu permiso non debe estar baleiro", + "permission_onboarding_back": "AtrÃĄs", + "permission_onboarding_continue_anyway": "Continuar de todos os xeitos", + "permission_onboarding_get_started": "Comezar", + "permission_onboarding_go_to_settings": "Ir a axustes", + "permission_onboarding_permission_denied": "Permiso denegado. Para usar Immich, conceda permisos de fotos e vídeos en Axustes.", + "permission_onboarding_permission_granted": "Permiso concedido! Xa estÃĄ todo listo.", + "permission_onboarding_permission_limited": "Permiso limitado. Para permitir que Immich faga copia de seguridade e xestione toda a sÃēa colecciÃŗn da galería, conceda permisos de fotos e vídeos en ConfiguraciÃŗn.", + "permission_onboarding_request": "Immich require permiso para ver as sÃēas fotos e vídeos.", "person": "Persoa", "person_age_months": "{months, plural, one {# mes} other {# meses}} de idade", "person_age_year_months": "1 ano, {months, plural, one {# mes} other {# meses}} de idade", @@ -1624,10 +1730,13 @@ "person_birthdate": "Nacido/a o {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", "person_recognized": "Persoa recoÃąecida", + "person_selected": "Persoa seleccionada", "photo_shared_all_users": "Parece que compartiu as sÃēas fotos con todos os usuarios ou non ten ningÃēn usuario co que compartir.", "photos": "Fotos", "photos_and_videos": "Fotos e Vídeos", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de anos anteriores", + "photos_only": "SÃŗ fotos", "pick_a_location": "Elixir unha localizaciÃŗn", "pick_custom_range": "Rango personalizado", "pick_date_range": "Seleccionar un rango de datas", @@ -1646,8 +1755,6 @@ "play_original_video_setting_description": "Preferir a reproduciÃŗn dos vídeos orixinais en vez dos vídeos transcodificados. Se o recurso orixinal non Ê compatible, pode que non se reproduza correctamente.", "play_transcoded_video": "Reproducir vídeo transcodificado", "please_auth_to_access": "Por favor, autentícate para acceder", - "plugin_method_filter_type": "Filtro", - "plugin_method_filter_type_description": "Este mÊtodo pode filtrar eventos e condicionalmente impedir a execuciÃŗn de pasos posteriores", "port": "Porto", "preferences_settings_subtitle": "Xestionar as preferencias da aplicaciÃŗn", "preferences_settings_title": "Preferencias", @@ -1664,11 +1771,11 @@ "privacy": "Privacidade", "profile": "Perfil", "profile_drawer_app_logs": "Rexistros", + "profile_drawer_client_server_up_to_date": "Cliente e Servidor estÃĄn actualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo sÃŗ lectura activado. MantÊn premido o ícone do avatar do usuario para saír.", "profile_image_of_user": "Imaxe de perfil de {user}", "profile_picture_set": "Imaxe de perfil establecida.", - "projection_type": "Tipo de proxecciÃŗn", "public_album": "Álbum pÃēblico", "public_share": "Compartir PÃēblico", "purchase_account_info": "Seguidor/a", @@ -1687,6 +1794,7 @@ "purchase_individual_description_2": "Estado de seguidor/a", "purchase_individual_title": "Individual", "purchase_input_suggestion": "Ten unha chave de produto? Introduza a chave a continuaciÃŗn", + "purchase_license_subtitle": "Apoie Immich para contribuír ao desenvolvemento continuado do servizo", "purchase_lifetime_description": "ContribuciÃŗn vitalicia", "purchase_option_title": "OPCIÓNS DE APOIO", "purchase_panel_info_1": "Construír Immich leva moito tempo e esforzo, e temos enxeÃąeiros a tempo completo traballando nel para facelo o mellor posible. A nosa misiÃŗn Ê que o software de cÃŗdigo aberto e as prÃĄcticas comerciais Êticas se convertan nunha fonte de ingresos sostible para os desenvolvedores e crear un ecosistema respectuoso coa privacidade con alternativas reais aos servizos na nube explotadores.", @@ -1719,19 +1827,19 @@ "reassigned_assets_to_new_person": "Reasignados {count, plural, one {# activo} other {# activos}} a unha nova persoa", "reassing_hint": "Asignar activos seleccionados a unha persoa existente", "recent": "Recente", + "recent_albums": "Álbums recentes", "recent_searches": "Buscas recentes", "recently_added": "Engadido recentemente", - "recently_added_body": "Vaia directamente a todo o que engadiu ultimamente nunha pÃĄxina específica.", - "recently_added_description": "Explora os teus recursos ordenados por cando foron cargados en Immich", "recently_added_page_title": "Engadido Recentemente", - "recently_added_title": "Engadida recentemente", "recently_taken": "Tomado recentemente", + "recently_taken_page_title": "Tomado Recentemente", "refresh": "Actualizar", "refresh_encoded_videos": "Actualizar vídeos codificados", "refresh_faces": "Actualizar caras", "refresh_metadata": "Actualizar metadatos", "refresh_thumbnails": "Actualizar miniaturas", "refreshed": "Actualizado", + "refreshes_every_file": "Volve ler todos os ficheiros existentes e novos", "refreshing_encoded_video": "Actualizando vídeo codificado", "refreshing_faces": "Actualizando caras", "refreshing_metadata": "Actualizando metadatos", @@ -1744,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "EstÃĄ seguro de que quere eliminar {count, plural, one {# activo} other {# activos}} desta ligazÃŗn compartida?", "remove_assets_title": "Eliminar activos?", "remove_custom_date_range": "Eliminar rango de datas personalizado", - "remove_filter": "Eliminar filtro", + "remove_deleted_assets": "Eliminar Activos Eliminados", "remove_from_album": "Eliminar do ÃĄlbum", "remove_from_album_action_prompt": "{count} eliminado(s) do ÃĄlbum", "remove_from_favorites": "Eliminar de favoritos", @@ -1755,22 +1863,29 @@ "remove_memory": "Eliminar recordo", "remove_photo_from_memory": "Eliminar foto deste recordo", "remove_tag": "Eliminar a etiqueta", + "remove_url": "Eliminar URL", "remove_user": "Eliminar usuario", "removed_api_key": "Chave API eliminada: {name}", "removed_from_archive": "Eliminado do arquivo", "removed_from_favorites": "Eliminado de favoritos", "removed_from_favorites_count": "{count, plural, other {Eliminados #}} de favoritos", "removed_memory": "Recordo eliminado", + "removed_photo_from_memory": "Foto eliminada do recordo", "removed_tagged_assets": "Etiqueta eliminada de {count, plural, one {# activo} other {# activos}}", "rename": "Renomear", + "repair": "Reparar", + "repair_no_results_message": "Os ficheiros non rastrexados e faltantes aparecerÃĄn aquí", + "replace_with_upload": "Substituír con carga", "repository": "Repositorio", "require_password": "Requirir contrasinal", + "require_user_to_change_password_on_first_login": "Requirir que o usuario cambie o contrasinal no primeiro inicio de sesiÃŗn", "rescan": "Volver escanear", "reset": "Restablecer", "reset_password": "Restablecer contrasinal", "reset_people_visibility": "Restablecer visibilidade das persoas", "reset_pin_code": "Restablecer o cÃŗdigo PIN", "reset_pin_code_description": "Se esqueciches o teu cÃŗdigo PIN, podes contactar co administrador do servidor para restablecelo", + "reset_pin_code_success": "CÃŗdigo PIN restablecido correctamente", "reset_pin_code_with_password": "Sempre podes restablecer o teu cÃŗdigo PIN coa tÃēa contrasinal", "reset_sqlite": "Restablecer a Base de Datos SQLite", "reset_sqlite_clear_app_data": "Limpar Datos", @@ -1784,10 +1899,12 @@ "resolved_all_duplicates": "ResolvÊronse todos os duplicados", "restore": "Restaurar", "restore_all": "Restaurar todo", + "restore_trash_action_prompt": "{count} restaurado(s) do lixo", "restore_user": "Restaurar usuario", "restored_asset": "Activo restaurado", "resume": "Reanudar", "resume_paused_jobs": "Retomar {count, plural, one {# traballo pausado} other {# traballos pausados}}", + "retry_upload": "Reintentar carga", "review_duplicates": "Revisar duplicados", "review_large_files": "Revisar ficheiros grandes", "role": "Rol", @@ -1795,6 +1912,7 @@ "role_viewer": "Visor", "running": "ExecutÃĄndose", "save": "Gardar", + "save_to_gallery": "Gardar na galería", "saved": "Gardo", "saved_api_key": "Chave API gardada", "saved_profile": "Perfil gardado", @@ -1805,10 +1923,9 @@ "scan": "Escanear", "scan_all_libraries": "Escanear Todas as Bibliotecas", "scan_library": "Escanear", + "scan_settings": "ConfiguraciÃŗn de Escaneo", "scanning": "Escaneando", "scanning_for_album": "Escaneando ÃĄlbum...", - "screencast_mode_description": "Mostrar os indicadores de eventos do teclado e do rato na pantalla", - "screencast_mode_title": "Alternar o modo de captura da pantalla", "search": "Buscar", "search_albums": "Buscar ÃĄlbums", "search_by_context": "Buscar por contexto", @@ -1816,8 +1933,6 @@ "search_by_description_example": "Día de sendeirismo en Sapa", "search_by_filename": "Buscar por nome de ficheiro ou extensiÃŗn", "search_by_filename_example": "p. ex. IMG_1234.JPG ou PNG", - "search_by_full_path": "Buscar por ruta completa ou cartafol", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - Podes buscar Proxectos, 3D, impresiÃŗn, 2026, etc.", "search_by_ocr": "Buscar mediante OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Buscar modelo de lente...", @@ -1837,6 +1952,7 @@ "search_filter_location_title": "Seleccionar localizaciÃŗn", "search_filter_media_type": "Tipo de Medio", "search_filter_media_type_title": "Seleccionar tipo de medio", + "search_filter_ocr": "Buscar por OCR", "search_filter_people_title": "Seleccionar persoas", "search_filter_star_rating": "ClasificaciÃŗn por estrelas", "search_filter_tags_title": "Seleccionar etiquetas", @@ -1847,13 +1963,25 @@ "search_no_people_named": "Sen persoas chamadas \"{name}\"", "search_no_result": "Non se atoparon resultados, probe cun termo de busca ou combinaciÃŗn diferente", "search_options": "OpciÃŗns de busca", + "search_page_categories": "Categorías", + "search_page_motion_photos": "Fotos en Movemento", + "search_page_no_objects": "Non hai InformaciÃŗn de Obxectos DispoÃąible", + "search_page_no_places": "Non hai InformaciÃŗn de Lugares DispoÃąible", + "search_page_screenshots": "Capturas de pantalla", "search_page_search_photos_videos": "Busca as sÃēas fotos e vídeos", + "search_page_selfies": "Selfies", + "search_page_things": "Cousas", "search_page_view_all_button": "Ver todo", + "search_page_your_activity": "A sÃēa actividade", + "search_page_your_map": "O seu Mapa", "search_people": "Buscar persoas", "search_places": "Buscar lugares", "search_rating": "Buscar por clasificaciÃŗn...", + "search_result_page_new_search_hint": "Nova Busca", "search_settings": "ConfiguraciÃŗn da busca", "search_state": "Buscar estado...", + "search_suggestion_list_smart_search_hint_1": "A busca intelixente estÃĄ activada por defecto, para buscar metadatos use a sintaxe ", + "search_suggestion_list_smart_search_hint_2": "m:o-seu-termo-de-busca", "search_tags": "Buscar etiquetas...", "search_timezone": "Buscar fuso horario...", "search_type": "Tipo de busca", @@ -1867,6 +1995,7 @@ "select_albums": "Seleccionar ÃĄlbumes", "select_all": "Seleccionar todo", "select_all_duplicates": "Seleccionar todos os duplicados", + "select_all_in": "Seleccionar todo en {group}", "select_avatar_color": "Seleccionar cor do avatar", "select_count": "{count, plural, one {Seleccionar #} other {Seleccionar #}}", "select_cutoff_date": "Seleccionar data límite", @@ -1874,13 +2003,14 @@ "select_featured_photo": "Seleccionar foto destacada", "select_from_computer": "Seleccionar do ordenador", "select_keep_all": "Seleccionar conservar todo", + "select_library_owner": "Seleccionar propietario da biblioteca", "select_new_face": "Seleccionar nova cara", "select_people": "Seleccionar xente", "select_person": "Seleccionar persoa", "select_person_to_tag": "Seleccionar unha persoa para etiquetar", "select_photos": "Seleccionar fotos", - "select_quality": "Seleccionar calidade", "select_trash_all": "Seleccionar mover todo ao lixo", + "select_user_for_sharing_page_err_album": "Erro ao crear o ÃĄlbum", "selected": "Seleccionado", "selected_count": "{count, plural, other {# seleccionados}}", "selected_gps_coordinates": "Coordenadas GPS seleccionadas", @@ -1913,31 +2043,42 @@ "setting_image_viewer_original_title": "Cargar imaxe orixinal", "setting_image_viewer_preview_subtitle": "Activar para cargar unha imaxe de resoluciÃŗn media. Desactivar para cargar directamente o orixinal ou usar sÃŗ a miniatura.", "setting_image_viewer_preview_title": "Cargar imaxe de vista previa", + "setting_image_viewer_title": "Imaxes", "setting_languages_apply": "Aplicar", "setting_languages_subtitle": "Cambiar a lingua da aplicaciÃŗn", + "setting_notifications_notify_failures_grace_period": "Notificar fallos da copia de seguridade en segundo plano: {duration}", + "setting_notifications_notify_hours": "{count} horas", + "setting_notifications_notify_immediately": "inmediatamente", "setting_notifications_notify_minutes": "{count} minutos", + "setting_notifications_notify_never": "nunca", "setting_notifications_notify_seconds": "{count} segundos", + "setting_notifications_single_progress_subtitle": "InformaciÃŗn detallada do progreso da carga por activo", + "setting_notifications_single_progress_title": "Mostrar progreso detallado da copia de seguridade en segundo plano", "setting_notifications_subtitle": "Axustar as sÃēas preferencias de notificaciÃŗn", + "setting_notifications_total_progress_subtitle": "Progreso xeral da carga (feitos/total activos)", + "setting_notifications_total_progress_title": "Mostrar progreso total da copia de seguridade en segundo plano", "setting_video_viewer_auto_play_subtitle": "Reproducir vídeos automaticamente cando se abren", "setting_video_viewer_auto_play_title": "Reproducir vídeos automaticamente", - "setting_video_viewer_looping_title": "En bucle", + "setting_video_viewer_looping_title": "Bucle", "setting_video_viewer_original_video_subtitle": "Ao transmitir un vídeo desde o servidor, reproducir o orixinal aínda que haxa unha transcodificaciÃŗn dispoÃąible. Pode provocar buffering. Os vídeos dispoÃąibles localmente reprodÃēcense en calidade orixinal independentemente desta configuraciÃŗn.", "setting_video_viewer_original_video_title": "Forzar vídeo orixinal", - "settings": "Axustes", + "settings": "ConfiguraciÃŗn", + "settings_require_restart": "Por favor, reinicie Immich para aplicar esta configuraciÃŗn", "settings_saved": "ConfiguraciÃŗn gardada", "setup_pin_code": "Configurar un cÃŗdigo PIN", "share": "Compartir", + "share_action_prompt": "Compartidos {count} activos", + "share_add_photos": "Engadir fotos", + "share_assets_selected": "{count} seleccionados", "share_dialog_preparing": "Preparando...", "share_link": "LigazÃŗn para Compartir", - "share_original": "Utilizar orixinal (grande)", - "share_preview": "Utilizar miniatura (pequena)", - "share_quality_body": "MantÊn premido o botÃŗn de compartir para escoller a calidade da imaxe antes de compartila.", - "share_quality_title": "Escolle a calidade da comparticiÃŗn", "shared": "Compartido", "shared_album_activities_input_disable": "O comentario estÃĄ desactivado", "shared_album_activity_remove_content": "Quere eliminar esta actividade?", "shared_album_activity_remove_title": "Eliminar Actividade", "shared_album_section_people_action_error": "Erro ao saír/eliminar do ÃĄlbum", + "shared_album_section_people_action_leave": "Eliminar usuario do ÃĄlbum", + "shared_album_section_people_action_remove_user": "Eliminar usuario do ÃĄlbum", "shared_album_section_people_title": "PERSOAS", "shared_by": "Compartido por", "shared_by_user": "Compartido por {user}", @@ -1946,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Subidos", "shared_link_app_bar_title": "LigazÃŗns Compartidas", "shared_link_clipboard_copied_massage": "Copiado ao portapapeis", + "shared_link_clipboard_text": "LigazÃŗn: {link}\nContrasinal: {password}", "shared_link_create_error": "Erro ao crear ligazÃŗn compartida", "shared_link_custom_url_description": "Acceder a esta ligazÃŗn compartida cun URL personalizado", - "shared_link_custom_url_title": "URL Personalizado", - "shared_link_custom_url_warning": "Aviso: un nome de URL personalizado cunha barra inclinada pode causar resultados inesperados.", "shared_link_edit_description_hint": "Introduza a descriciÃŗn da comparticiÃŗn", "shared_link_edit_expire_after_option_day": "1 día", "shared_link_edit_expire_after_option_days": "{count} días", @@ -1983,6 +2123,12 @@ "shared_with_partner": "Compartido con {partner}", "sharing": "Compartir", "sharing_enter_password": "Por favor, introduza o contrasinal para ver esta pÃĄxina.", + "sharing_page_album": "Álbums compartidos", + "sharing_page_description": "Cree ÃĄlbums compartidos para compartir fotos e vídeos con persoas na sÃēa rede.", + "sharing_page_empty_list": "LISTA BALEIRA", + "sharing_sidebar_description": "Mostrar unha ligazÃŗn a Compartir na barra lateral", + "sharing_silver_appbar_create_shared_album": "Novo ÃĄlbum compartido", + "sharing_silver_appbar_share_partner": "Compartir con compaÃąeiro/a", "shift_to_permanent_delete": "prema ⇧ para eliminar permanentemente o activo", "show_album_options": "Mostrar opciÃŗns do ÃĄlbum", "show_albums": "Mostrar ÃĄlbums", @@ -1994,9 +2140,7 @@ "show_in_timeline": "Mostrar na liÃąa de tempo", "show_in_timeline_setting_description": "Mostrar fotos e vídeos deste usuario na sÃēa liÃąa de tempo", "show_keyboard_shortcuts": "Mostrar atallos de teclado", - "show_less": "Mostrar menos", "show_metadata": "Mostrar metadatos", - "show_more_fields": "{count, plural, one {Mostrar mÃĄis # campos} other {Mostrar mÃĄis # campos}}", "show_or_hide_info": "Mostrar ou ocultar informaciÃŗn", "show_password": "Mostrar contrasinal", "show_person_options": "Mostrar opciÃŗns da persoa", @@ -2004,7 +2148,6 @@ "show_schema": "Mostrar esquema", "show_search_options": "Mostrar opciÃŗns de busca", "show_shared_links": "Mostrar ligazÃŗns compartidas", - "show_slideshow_metadata_overlay": "Mostrar superposiciÃŗn de informaciÃŗn da imaxe", "show_slideshow_transition": "Mostrar transiciÃŗn da presentaciÃŗn", "show_supporter_badge": "Insignia de seguidor/a", "show_supporter_badge_description": "Mostrar unha insignia de seguidor/a", @@ -2016,25 +2159,18 @@ "sign_out": "Pechar SesiÃŗn", "sign_up": "Rexistrarse", "size": "TamaÃąo", - "skip": "Saltar", "skip_to_content": "Saltar ao contido", "skip_to_folders": "Saltar a cartafoles", "skip_to_tags": "Saltar a etiquetas", "slideshow": "PresentaciÃŗn", - "slideshow_body": "RelÃĄxate e mira as tÃēas fotos reproducíndose nunha presentaciÃŗn de diapositivas a pantalla completa.", - "slideshow_metadata_overlay_mode": "Contido superposto", - "slideshow_metadata_overlay_mode_description_only": "SÃŗ descriciÃŗn", - "slideshow_metadata_overlay_mode_full": "Cheo", "slideshow_repeat": "Repetir presentaciÃŗn de diapositivas", "slideshow_repeat_description": "Volver ao principio ao rematar a presentaciÃŗn de diapositivas", "slideshow_settings": "ConfiguraciÃŗn da presentaciÃŗn", - "slideshow_title": "PresentaciÃŗn de diapositivas", - "smart_album": "Álbume intelixente", - "some_assets_already_have_a_location_warning": "AlgÃēns dos recursos seleccionados xa teÃąen unha localizaciÃŗn", "sort_albums_by": "Ordenar ÃĄlbums por...", "sort_created": "Data de creaciÃŗn", "sort_items": "NÃēmero de elementos", "sort_modified": "Data de modificaciÃŗn", + "sort_newest": "Foto mÃĄis recente", "sort_oldest": "Foto mÃĄis antiga", "sort_people_by_similarity": "Ordenar persoas por similitude", "sort_recent": "Foto mÃĄis recente", @@ -2043,6 +2179,7 @@ "stack": "Apilar", "stack_action_prompt": "{count} apilados", "stack_duplicates": "Apilar duplicados", + "stack_select_one_photo": "Seleccionar unha foto principal para a pila", "stack_selected_photos": "Apilar fotos seleccionadas", "stacked_assets_count": "Apilados {count, plural, one {# activo} other {# activos}}", "stacktrace": "Rastro da Pila", @@ -2051,10 +2188,6 @@ "start_date_before_end_date": "A data de inicio debe ser anterior ÃĄ data de fin", "state": "Estado", "status": "Estado", - "step_delete": "Borrar paso", - "step_delete_confirm": "EstÃĄs seguro de que queres elminar este paso?", - "step_details": "Detalles do paso", - "steps": "Pasos", "stop_casting": "Deixade de emitir", "stop_motion_photo": "Deter Foto en Movemento", "stop_photo_sharing": "Deixar de compartir as sÃēas fotos?", @@ -2075,18 +2208,18 @@ "swap_merge_direction": "Intercambiar direcciÃŗn de fusiÃŗn", "sync": "Sincronizar", "sync_albums": "Sincronizar ÃĄlbums", + "sync_albums_manual_subtitle": "Sincronizar todos os vídeos e fotos cargados aos ÃĄlbums de copia de seguridade seleccionados", "sync_local": "Sincronizar Local", "sync_remote": "Sincronizar Remoto", "sync_status": "Estado de SincronizaciÃŗn", "sync_status_subtitle": "Ver e xestionar o sistema de sincronizaciÃŗn", "sync_upload_album_setting_subtitle": "Crear e subir as sÃēas fotos e vídeos aos ÃĄlbums seleccionados en Immich", - "system_theme": "Tema do sistema", - "system_theme_command_description": "Empregar o tema do sistema ({value})", "tag": "Etiqueta", "tag_assets": "Etiquetar activos", "tag_created": "Etiqueta creada: {tag}", "tag_face": "Etiquetar cara", "tag_feature_description": "Navegar por fotos e vídeos agrupados por temas de etiquetas lÃŗxicas", + "tag_not_found_question": "Non atopa unha etiqueta? Crear unha nova etiqueta.", "tag_people": "Etiquetar Persoas", "tag_updated": "Etiqueta actualizada: {tag}", "tagged_assets": "Etiquetados {count, plural, one {# activo} other {# activos}}", @@ -2101,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "NÃēmero de activos por fila ({count})", "theme_setting_colorful_interface_subtitle": "Aplicar cor primaria ÃĄs superficies de fondo.", "theme_setting_colorful_interface_title": "Interface colorida", + "theme_setting_image_viewer_quality_subtitle": "Axustar a calidade do visor de imaxes de detalle", + "theme_setting_image_viewer_quality_title": "Calidade do visor de imaxes", "theme_setting_primary_color_subtitle": "Elixa unha cor para acciÃŗns primarias e acentos.", "theme_setting_primary_color_title": "Cor primaria", "theme_setting_system_primary_color_title": "Usar cor do sistema", "theme_setting_system_theme_switch": "AutomÃĄtico (Seguir configuraciÃŗn do sistema)", + "theme_setting_theme_subtitle": "Elixir a configuraciÃŗn do tema da aplicaciÃŗn", + "theme_setting_three_stage_loading_subtitle": "A carga en tres etapas pode aumentar o rendemento da carga pero causa unha carga de rede significativamente maior", + "theme_setting_three_stage_loading_title": "Activar carga en tres etapas", "then": "EntÃŗn", "they_will_be_merged_together": "Fusionaranse xuntos", "third_party_resources": "Recursos de Terceiros", @@ -2130,17 +2268,23 @@ "trash_all": "Mover Todo ao Lixo", "trash_count": "Lixo ({count, number})", "trash_delete_asset": "Mover ao Lixo/Eliminar Activo", + "trash_emptied": "Lixo baleirado", "trash_no_results_message": "As fotos e vídeos movidos ao lixo aparecerÃĄn aquí.", "trash_page_delete_all": "Eliminar Todo", + "trash_page_empty_trash_dialog_content": "Quere baleirar os seus activos no lixo? Estes elementos eliminaranse permanentemente de Immich", "trash_page_info": "Os elementos no lixo eliminaranse permanentemente despois de {days} días", + "trash_page_no_assets": "Non hai activos no lixo", + "trash_page_restore_all": "Restaurar Todo", + "trash_page_select_assets_btn": "Seleccionar activos", + "trash_page_title": "Lixo ({count})", "trashed_items_will_be_permanently_deleted_after": "Os elementos no lixo eliminaranse permanentemente despois de {days, plural, one {# día} other {# días}}.", "trigger": "Disparador", - "trigger_asset_metadata_extraction": "ExtracciÃŗn de metadatos de activos", - "trigger_asset_metadata_extraction_description": "Activado cando se extraen os metadatos EXIF dun recurso", - "trigger_asset_uploaded": "Carga de activos", + "trigger_asset_uploaded": "Activo subido", "trigger_asset_uploaded_description": "Actívase cando se carga un activo novo", + "trigger_description": "Un evento que inicia o fluxo de traballo", "trigger_person_recognized": "Persoa recoÃąecida", - "trigger_person_recognized_description": "Actívase cando se recoÃąece a unha persoa", + "trigger_person_recognized_description": "Actívase cando se detecta a unha persoa", + "trigger_type": "TIpo de disparador", "troubleshoot": "Solucionar problemas", "type": "Tipo", "unable_to_change_pin_code": "Non Ê posible cambiar o cÃŗdigo PIN", @@ -2156,7 +2300,6 @@ "unknown": "DescoÃąecido", "unknown_country": "País DescoÃąecido", "unknown_date": "Data descoÃąecida", - "unknown_schema": "Esquema descoÃąecido", "unknown_year": "Ano DescoÃąecido", "unlimited": "Ilimitado", "unlink_motion_video": "Desvincular vídeo en movemento", @@ -2169,20 +2312,23 @@ "unsaved_change": "Cambio sen gardar", "unselect_all": "Deseleccionar todo", "unselect_all_duplicates": "Deseleccionar todos os duplicados", + "unselect_all_in": "Desmarcar todo en {group}", "unstack": "Desapilar", "unstack_action_prompt": "{count} desapilados", "unstacked_assets_count": "Desapilados {count, plural, one {# activo} other {# activos}}", "unsupported_field_type": "Tipo de campo non soportado", "unsupported_file_type": "O arquivo {file} non puido cargarse porque o seu tipo de arquivo {type} non se soporta.", "untagged": "Sen etiquetar", + "untitled_workflow": "Fluxo de traballo sen título", "up_next": "A continuaciÃŗn", "update_location_action_prompt": "Actualizar a localizaciÃŗn de {count} elementos seleccionados con:", "updated_at": "Actualizado", "updated_password": "Contrasinal actualizado", "upload": "Subir", "upload_concurrency": "Concorrencia de subida", - "upload_day_count": "{date}: {count, plural, one {# subida} other {# subidas}}", "upload_details": "Detalles da Carga", + "upload_dialog_info": "Quere facer copia de seguridade do(s) Activo(s) seleccionado(s) no servidor?", + "upload_dialog_title": "Subir Activo", "upload_error_with_count": "Erro de subida para {count, plural, one {# asset} other {# assets}}", "upload_errors": "Subida completada con {count, plural, one {# erro} other {# erros}}. Actualice a pÃĄxina para ver os novos activos subidos.", "upload_finished": "Carga finalizada", @@ -2192,13 +2338,9 @@ "upload_status_errors": "Erros", "upload_status_uploaded": "Subido", "upload_success": "Subida exitosa. Actualice a pÃĄxina para ver os novos activos subidos.", - "upload_to_album_body": "Para os usuarios que non empreguen a funciÃŗn de carga manual, agora poden optar por engadir fotos locais directamente a un ÃĄlbum mentres as cargan, sen necesidade de cargalas e engadlas a un ÃĄlbum mÃĄis tarde.", - "upload_to_album_title": "Subir directamente a un ÃĄlbum", "upload_to_immich": "Subir a Immich ({count})", "uploading": "Subindo", "uploading_media": "Cargando multimedia", - "uploads": "Subidas", - "uploads_count": "{count, plural, one {# subida} other {# subidas}}", "url": "URL", "usage": "Uso", "use_biometric": "Usar biometría", @@ -2206,7 +2348,6 @@ "use_browser_locale_description": "Formatear datas, tempos e nÃēmeros basado na configuraciÃŗn do teu navegador local", "use_current_connection": "Empregar conexiÃŗn actual", "use_custom_date_range": "Usar rango de datas personalizado no seu lugar", - "use_template": "Usar modelo", "user": "Usuario", "user_has_been_deleted": "Este usuario foi eliminado.", "user_id": "ID de Usuario", @@ -2216,6 +2357,7 @@ "user_privacy": "Privacidade do usuario", "user_purchase_settings": "ContribuciÃŗn", "user_purchase_settings_description": "Xestionar a sÃēa contribuciÃŗn", + "user_role_set": "Establecer {user} como {role}", "user_usage_detail": "Detalle de uso do usuario", "user_usage_stats": "Estatísticas de uso da conta", "user_usage_stats_description": "Ver estatísticas de uso da conta", @@ -2225,6 +2367,7 @@ "utilities": "Utilidades", "validate": "Validar", "validate_endpoint_error": "Por favor, introduza unha URL vÃĄlida", + "validation_error": "Erro de validaciÃŗn", "variables": "Variables", "version": "VersiÃŗn", "version_announcement_closing": "O seu amigo, Alex", @@ -2234,9 +2377,9 @@ "video": "Vídeo", "video_hover_setting": "Reproducir miniatura do vídeo ao pasar o rato por riba", "video_hover_setting_description": "Reproducir miniatura do vídeo cando o rato estÃĄ sobre o elemento. Mesmo cando estÃĄ desactivado, a reproduciÃŗn pode iniciarse pasando o rato sobre a icona de reproduciÃŗn.", - "video_quality": "Calidade do vídeo", "videos": "Vídeos", "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", + "videos_only": "SÃŗ vídeos", "view": "Ver", "view_album": "Ver Álbum", "view_all": "Ver Todo", @@ -2245,29 +2388,29 @@ "view_details": "Ver detalles", "view_in_timeline": "Ver na liÃąa de tempo", "view_link": "Ver ligazÃŗn", + "view_links": "Ver ligazÃŗns", "view_name": "Vista", "view_next_asset": "Ver seguinte activo", "view_previous_asset": "Ver activo anterior", "view_qr_code": "Ver cÃŗdigo QR", "view_similar_photos": "Ver fotos semellantes", "view_stack": "Ver Pila", + "view_user": "Ver Usuario", "viewer_remove_from_stack": "Eliminar da Pila", + "viewer_stack_use_as_main_asset": "Usar como Activo Principal", + "viewer_unstack": "Desapilar", "visibility": "Visibilidade", "visibility_changed": "Visibilidade cambiada para {count, plural, one {# persoa} other {# persoas}}", "visual": "Visual", + "visual_builder": "Construtor visual", "waiting": "Agardando", "waiting_count": "Esperando: {count}", "warning": "Aviso", "week": "Semana", "welcome": "Benvido/a", "welcome_to_immich": "Benvido/a a Immich", - "whats_new": "O que hai de novo", - "whats_new_settings_subtitle": "Vexa as novidades de Immich", - "whats_new_version": "VersiÃŗn {version}", - "when": "Cando", "width": "Ancho", "wifi_name": "Nome da wifi", - "workflow": "Fluxo de traballo", "workflow_delete_prompt": "EstÃĄs seguro que queres eliminar este fluxo de traballo?", "workflow_deleted": "Fluxo de traballo eliminado", "workflow_description": "DescriciÃŗn do fluxo de traballo", @@ -2277,17 +2420,17 @@ "workflow_name": "Nome do fluxo de traballo", "workflow_navigation_prompt": "EstÃĄs seguro que desexar saír sen gardar os cambios?", "workflow_summary": "Resumo do fluxo de traballo", - "workflow_templates": "Modelos de fluxo de traballo", "workflow_update_success": "Fluxo de traballo actualizado con Êxito", + "workflow_updated": "Fluxo de traballo actualizado", "workflows": "Fluxos de traballo", "workflows_help_text": "Os fluxos de traballo automatizan acciÃŗns nos teus recursos en funciÃŗn de disparadores e filtros", "wrong_pin_code": "CÃŗdigo PIN incorrecto", - "x_of_total": "{x}/{total}", "year": "Ano", "years_ago": "Hai {years, plural, one {# ano} other {# anos}}", "yes": "Si", "you_dont_have_any_shared_links": "Non ten ningunha ligazÃŗn compartida", "your_wifi_name": "O nome da sÃēa wifi", "zero_to_clear_rating": "preme 0 para borrar a cualificaciÃŗn do activo", - "zoom_image": "Ampliar Imaxe" + "zoom_image": "Ampliar Imaxe", + "zoom_to_bounds": "Axustar ao perímetro" } diff --git a/i18n/gsw.json b/i18n/gsw.json index a5e9713869..7893ea1482 100644 --- a/i18n/gsw.json +++ b/i18n/gsw.json @@ -5,41 +5,43 @@ "acknowledge": "Bestätige", "action": "Aktion", "action_common_update": "Aktualisiere", - "action_description": "A Gruppe fo Aktione wo sÃļll usgfÃŧert werde", "actions": "Aktione", "active": "Aktiv", "active_count": "Aktive:{count}", "activity": "Aktivität", + "activity_changed": "Aktivität isch {enabled, select, true {aktiviert} other {deaktiviert}}", "add": "AafÃŧege", "add_a_description": "Beschriebig hinzuäzfÃŧägä", "add_a_location": "Standort hiifÃŧege", "add_a_name": "Name hiifÃŧege", "add_a_title": "Titel hiifÃŧege", "add_action": "Aktion hiifÃŧege", - "add_assets": "Objäkt HinzuefÃŧege", "add_birthday": "Geburtsdatum hiifÃŧege", "add_endpoint": "Endpunkt hiifÃŧege", "add_exclusion_pattern": "Ausschlussmuster hiifÃŧege", "add_location": "Standort hiifÃŧege", + "add_more_users": "Wiiteri Nutzer hiifÃŧege", "add_partner": "Partner hiifÃŧege", + "add_path": "Pfad hiifÃŧege", "add_photos": "Fotos hiifÃŧege", - "add_step": "Schritt hinzuefÃŧege", "add_tag": "Tag hiifÃŧege", "add_to": "HiifÃŧege zu â€Ļ", "add_to_album": "Zum Album hiifÃŧege", "add_to_album_bottom_sheet_added": "Zu {album} hiigfÃŧegt", "add_to_album_bottom_sheet_already_exists": "Bereits in {album}", "add_to_album_bottom_sheet_some_local_assets": "E paar lokali Dateie händ nÃļd chÃļnne zum Album hiigfÃŧegt werde", + "add_to_album_toggle": "Uswahl umschalte fÃŧr {album}", "add_to_albums": "Zu Albe hiifÃŧege", "add_to_albums_count": "Zu Albe hiifÃŧege ({count})", "add_to_bottom_bar": "HiifÃŧege zu", + "add_to_shared_album": "Zum gteilte Album hiifÃŧege", "add_upload_to_stack": "Upload zum Stapel hiifÃŧege", "add_url": "URL hiifÃŧege", "added_to_archive": "Zum Archiv hiigfÃŧegt", "added_to_favorites": "Zu de Favoritä hiigfÃŧegt", "added_to_favorites_count": "{count, number} zu de Favoritä hiigfÃŧegt", "admin": { - "add_exclusion_pattern_description": "Ausschlussmuster hiifÃŧege. Platzhalter wie *, ** und ? werde unterstÃŧtzt. Um alli Datei in eme Verzeichnis mit em Name „Raw“ z ignoriere, „/Raw/“ verwände. Um alli Datei z ignoriere, wo uf „.tif“ änded, „/*.tif“ verwände. Um en absolute Pfad z ignoriere, „/pfad/zum/ignorieren/“ verwände.", + "add_exclusion_pattern_description": "Ausschlussmuster hiifÃŧege. Platzhalter wie *, ** und ? werde unterstÃŧtzt. Um alli Datei in eme Verzeichnis mit em Name „Raw“ z ignoriere, „/Raw/“ verwände.\nUm alli Datei z ignoriere, wo uf „.tif“ änded, „/*.tif“ verwände. Um en absolute Pfad z ignoriere, „/pfad/zum/ignorieren/“ verwände.", "admin_user": "Administrator", "asset_offline_description": "Die Datei vo ere externe Bibliothek isch nÃŧmme uf de Festplatte und isch i de Papirkorb verschobe worde. Falls d’Datei i dä Bibliothek verschobe worde isch, lueg i diner Ziitliiste noch de neue entsprechende Datei. Um die Datei wiederherzstelle, sorg bitte derfÃŧr, dass Immich uf de unten aagegehene Dateipfad cha zgriffe und lass d’Bibliothek neu scanne.", "authentication_settings": "Authentifizierigsiistellige", @@ -70,10 +72,9 @@ "copy_config_to_clipboard_description": "Kopier d’aktuel Systemkonfiguration als JSON-Objekt i d’ZwÃŧscheablage", "create_job": "Ufgob erstelle", "cron_expression": "Cron-Ziitahgob", - "cron_expression_description": "Setz s’Scan-Intervall im Cron-Format. Hilf zum Format findes du zum Bispiel bi de Crontab Guru", + "cron_expression_description": "Setz s’Scan-Intervall im Cron-Format. Hilf zum Format findes du zum Bispiel bi de Setz s’Scan-Intervall im Cron-Format. Hilf zum Format findes du zum Bispiel bi de Crontab Guru.Crontab Guru.", "cron_expression_presets": "Vorlag fÃŧr Cron-Ziitangab", "disable_login": "Login deaktiviiere", - "download_csv": "Als CSV abäladä", "duplicate_detection_job_description": "Die Ufgab fÃŧhrt s’maschinelli Lärene fÃŧr jede Datei us, um Duplikat z finde. Die Ufgab basiert uf de intelligente Suechi", "exclusion_pattern_description": "Mit Usschlussmuster chÃļnnd Datei und Ordner bim Scanne vo diner Bibliothek ignoriert werde. Das isch nÃļÃŧtzlich, wenn du Ordner hesch, wo Datei enthalte, wo du nÃļd importiere wotsch – zum Bispiel RAW-Dateie.", "export_config_as_json_description": "Lad d’aktuel Systemkonfiguration als JSON-Datei ab", @@ -97,8 +98,6 @@ "image_preview_description": "Mittelgrosses Bild mit entfernte Metadate, wo bim Aaluegä vo ere einzelne Datei und fÃŧrs maschinelli Lärne bruucht wird", "image_preview_quality_description": "Vorschauqualität vo 1–100. En hÃļcherer Wert isch besser, erzeugt aber grÃļsseri Dateie und cha d’Reaktionsfähigkait vo dr App beeinträchtige. En tieferer Wert cha hingegen d’Qualität vom maschinelle Lärne beeinträchtige.", "image_preview_title": "Vorschauiistellige", - "image_progressive": "Progressiv", - "image_progressive_description": "JPEG progressiv enkodiärä fÃŧr graduells Display-Ladeverhaltä. Hät kei Effekt fÃŧr WebP Bilder.", "image_quality": "Qualität", "image_resolution": "UuflÃļsig", "image_resolution_description": "HÃļcheri UflÃļsige chÃļi meh Detail erhalte, bruuched aber meh Ziit fÃŧr d’Kodierig, hei grÃļsseri DateigrÃļssi und chÃļi d’Reaktionsfähigkait vo Applikatione beeinträchtige.", @@ -108,10 +107,6 @@ "image_thumbnail_quality_description": "Qualität vo dr Miniaturahsicht vo 1–100. HÃļcher isch besser, erzeugt aber grÃļsseri Dateie und cha d’Reaktionsfähigkait vo dr App beeinträchtige.", "image_thumbnail_title": "Miniaturahsicht-Iistellige", "import_config_from_json_description": "Importier d’Systemkonfiguration, indem du e JSON-Konfigurationsdatei ufladsch", - "integrity_checks_checksum_files": "Checksum-Dateiä", - "integrity_checks_checksum_files_description": "D Cheksum ÜberprÃŧäfig konfigurierä", - "integrity_checks_checksum_files_enable_description": "D Checksum ÜberprÃŧäfig iischaltä", - "integrity_checks_checksum_files_percentage_limit": "Prozentuali Obergränzä", "job_concurrency": "{job} (Aazahl gliichziitig laufendi Prozäss)", "job_created": "Ufgab erstellt", "job_not_concurrency_safe": "Die Ufgab isch nÃļd parallelisierigssicher.", @@ -187,15 +182,10 @@ "machine_learning_smart_search_enabled": "Intelligenti Suechi aktiviere", "machine_learning_smart_search_enabled_description": "Wenn die Option deaktiviert isch, werde d’Bilder nÃļd fÃŧr d’intelligenti Suechi bruucht.", "machine_learning_url_description": "D’URL vom Server fÃŧrs maschinelli Lärne. Wenn meh als eini URL aagee wird, wird jeder Server einzeln usprobiert, bis einer erfolgrich antwortet – und zwar i dr Räihäfolg vom erschte bis zum letschte. Server, wo nÃļd antworted, werde vorÃŧbergehend ignoriert, bis si wieder verfÃŧgbar sind.", - "maintenance_delete_backup": "Backup lÃļsche", - "maintenance_restore_backup": "Backup wiederherstelle", - "maintenance_restore_backup_description": "Immich wird glÃļscht und vom usgwählte Backup wiederhergstellt. Es Backup wird vorher erstellt.", - "maintenance_restore_backup_different_version": "Das Backup esch mitere andere Version vo Immich erstellt worde!", "maintenance_settings": "Wartig", "maintenance_settings_description": "Immich i de Wartigsmodus versetze.", "maintenance_start": "Wartigsmodus startä", "maintenance_start_error": "Wartigsmodus het nÃļd chÃļnne gstartet werde.", - "maintenance_upload_backup_error": "Backup hed ned chÃļnne ufeglade werde, esch es en .sql/.sql.gz Datei?", "manage_concurrency": "Gliichziitigi UusfÃŧerig verwalte", "manage_concurrency_description": "Gang zur Job-Site, um d’Job-Parallelität z verwalte", "manage_log_settings": "Log-Iistellige verwalte", @@ -258,8 +248,6 @@ "notification_enable_email_notifications": "E-Mail-Benochrichtigige aktiviere", "notification_settings": "Benochrichtigigs-Iistellige", "notification_settings_description": "Benochrichtigigs-Iistellige (inkl. E-Mail) verwalte", - "oauth_allow_insecure_requests": "Unsicheri Aafroge erlaube", - "oauth_allow_insecure_requests_description": "ACHTUNG: Das deaktiviert TLS Zertifikats-Validierig fÃŧr OAuth aafroge und setzt dech mÃļglicherwiis MITM Attacke us.", "oauth_auto_launch": "Auto-Start", "oauth_auto_launch_description": "Automatischer Start vom OAuth-Aahmäldvorgang bim Ufruäfä vo dr Aahmäldigs-Site", "oauth_auto_register": "Automatischi Registrierig", @@ -267,7 +255,6 @@ "oauth_button_text": "Button-Text", "oauth_client_secret_description": "Erforderlich, wenn PKCE (Proof Key for Code Exchange) nÃļd vom OAuth-Anbieter unterstÃŧtzt wird", "oauth_enable_description": "Ahmeldig mit OAuth", - "oauth_end_session_url_description": "Benutzer zu dere URI witerleite wenn sie sech uslogged.", "oauth_mobile_redirect_uri": "Mobile Umleitigs-URI", "oauth_mobile_redirect_uri_override": "Mobile Umleitigs-URI Ãŧberschriebe", "oauth_mobile_redirect_uri_override_description": "Iischalte, wenn dr OAuth-Anbieter kei mobile URI wie „{callback}“ erlaubt", @@ -445,6 +432,8 @@ "admin_password": "Administrator Passwort", "administration": "Verwaltig", "advanced": "Erwiitert", + "advanced_settings_enable_alternate_media_filter_subtitle": "Bruuch die Option, um Medie während dr Synchronisierung nach andere Kriterie z filtere. Versuech das nume, wenn Problem mit dr Erkenig vo allne Albe dur d’App auftrete.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELL] Benutz alternative Filter fÃŧr d’Synchronisierig vo de Geräte-Albe", "advanced_settings_log_level_title": "Log-Level: {level}", "advanced_settings_prefer_remote_subtitle": "Einigi Gerät sind sehr langsam bim Lade vo lokale Vorschaubilder. Aktivier die Iistellige, um stattdesse d’Server-Bilder z lade.", "advanced_settings_prefer_remote_title": "Server-Bilder bevorzuge", @@ -452,6 +441,8 @@ "advanced_settings_proxy_headers_title": "Benutzerdefinierti Proxy-Header [Experimentell]", "advanced_settings_readonly_mode_subtitle": "Aktiviert dr schriibgschÃŧtzte Modus, i dem d’Fotos nume aazeigt werde chÃļi. Funktione wie mehri Bilder uswähle, teile, Ãŧbertrage und lÃļsche sind deaktiviert. Aktivier/Deaktivier dr schriibgschÃŧtzte Modus Ãŧber dr Benutzer-Avatar uf em Hauptbildschirm", "advanced_settings_readonly_mode_title": "SchriibgschÃŧtzte Modus", + "advanced_settings_self_signed_ssl_subtitle": "Verifizierig vo SSL-Zertifikate vom Server Ãŧberspringe. NÃļtig bi selbstsignierte Zertifikate.", + "advanced_settings_self_signed_ssl_title": "Selbstsignierti SSL-Zertifikate erlaubä [Experimentell]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisches LÃļsche oder Wiederherstellig vo ener Datei uf däm Gerät, wenn die Aktion im Web durgfÃŧert wird", "advanced_settings_sync_remote_deletions_title": "Mit Server-LÃļschige synchronisiere [Experimentell]", "advanced_settings_tile_subtitle": "Erwiiterti Benutzer-Iistellige", @@ -467,7 +458,11 @@ "album_delete_confirmation": "Bisch du sicher, dass du s’Album {album} wotsch lÃļsche?", "album_delete_confirmation_description": "Wenn das Album gteiltt worde isch, chÃļnnt anderi Benutzer nÃŧmme druf zuegriife.", "album_deleted": "Album glÃļscht", + "album_info_card_backup_album_excluded": "USGSCHLOSSÄ", + "album_info_card_backup_album_included": "IHGSCHLOSSÄ", "album_info_updated": "Album-Infos aktualisiert", + "album_leave": "Album verloh?", + "album_leave_confirmation": "Bisch du sicher, dass du s’Album {album} verla lasse wotsch?", "album_name": "Albumname", "album_options": "Albumoptione", "album_remove_user": "Nutzer entfernä?", @@ -477,8 +472,15 @@ "album_summary": "Album Zämmefassig", "album_updated": "Album aktualisiert", "album_updated_setting_description": "Erhalt e E-Mail-Benachrichtigig, wenn e freigähs Album neui Dateie enthält", - "album_upload_assets": "Dateie vo dim Computer ufelade und zu Album hinzuefÃŧege", + "album_user_left": "{album} verlasse", + "album_user_removed": "{user} entfernt", + "album_viewer_appbar_delete_confirm": "Bisch du sicher, dass du das Album us dim Konto wotsch lÃļsche?", "album_viewer_appbar_share_err_delete": "Album het nÃļd chenne glÃļscht werde", + "album_viewer_appbar_share_err_leave": "Album het nÃļd chenne verloh werde", + "album_viewer_appbar_share_err_remove": "Bi dr LÃļschung vo Element us em Album isch es Problem uufgetrete", + "album_viewer_appbar_share_err_title": "Dr Titel het nÃļd chÃļnne gänderet werde", + "album_viewer_appbar_share_leave": "Album verloh", + "album_viewer_appbar_share_to": "Teile Ãŧber", "album_viewer_page_share_add_users": "Nutzer hinzufÃŧegä", "album_with_link_access": "Lah jede mit em Link d’Fotos und d’Persone i däm Album aaluege.", "albums": "Albene", @@ -490,20 +492,21 @@ "all": "Alli", "all_albums": "Alli Albene", "all_people": "Alli Persone", - "all_photos": "Alli Fotis", "all_videos": "Alli Videos", "allow_dark_mode": "Dunkle-Modus erlaube", + "allow_edits": "Bearbeitig erlaube", "allow_public_user_to_download": "Erlaub Ãļffentliche Benutzer z’downloade", "allow_public_user_to_upload": "Erlaub Ãļffentliche Benutzer ufezlade", "allowed": "Erlaubt", "alt_text_qr_code": "QR-Code Bild", - "always_keep": "Immer bhalte", + "anti_clockwise": "Gäge de Uhrzeigersinn", "api_key": "API-SchlÃŧssel", "api_key_description": "Dä Wert wird nume einisch aazeigt. Bitte kopier ihn, bevor du s’Fänschter schliessisch.", "api_key_empty": "Din API-SchlÃŧssel-Name dÃļrf nÃļd leer si", "api_keys": "API-SchlÃŧssel", "app_architecture_variant": "Variante (Architektur)", "app_bar_signout_dialog_content": "Bisch du sicher, dass du dich abmälde wotsch?", + "app_bar_signout_dialog_ok": "Jo", "app_bar_signout_dialog_title": "Abmälde", "app_download_links": "App Download Links", "app_settings": "App-Ihstellige", @@ -514,46 +517,62 @@ "archive": "Archiv", "archive_action_prompt": "{count} zum Archiv hinzuegfÃŧegt", "archive_or_unarchive_photo": "Foti archivierä bzw. Archvierig ufhebä", + "archive_page_no_archived_assets": "Kei archivierti Inhält gfundä", + "archive_page_title": "Archiv ({count})", "archive_size": "ArchivgrÃļssi", "archive_size_description": "ArchivgrÃļssi fÃŧr Downloads konfiguriere (in GiB)", "archived": "Archiviert", "archived_count": "{count, plural, other {# archiviert}}", "are_these_the_same_person": "Isch das di gliichi Person?", "are_you_sure_to_do_this": "Bisch du sicher, dass du das wotsch mache?", + "asset_action_delete_err_read_only": "SchriibgschÃŧtzti Inhält chÃļnd nÃļd glÃļscht werde, Ãŧberspringe", + "asset_action_share_err_offline": "Die Offline-Date chÃļnnd nÃļd glesä werdä, Ãŧberspringä", "asset_added_to_album": "Zum Album hinzugfÃŧegt", "asset_adding_to_album": "HinzufÃŧegä zum Albumâ€Ļ", - "asset_created": "Datei erstellt", "asset_description_updated": "Die Beschribiig vo de Datei isch aktualisiert worde", + "asset_filename_is_offline": "Datei {filename} isch offline", + "asset_has_unassigned_faces": "Datei het nÃļd zuegwiseni Gsichter", "asset_hashing": "Berechni PrÃŧfsummiâ€Ļ", "asset_list_group_by_sub_title": "Gruppiere noch", + "asset_list_layout_settings_dynamic_layout_title": "Dynamischs Layout", + "asset_list_layout_settings_group_automatically": "Automatisch", "asset_list_layout_settings_group_by": "Gruppierä Elemente noch", "asset_list_layout_settings_group_by_month_day": "Monet + Tag", "asset_list_layout_sub_title": "Layout", "asset_list_settings_subtitle": "Iistellige fÃŧrs Foto-Gitter-Layout", "asset_list_settings_title": "Fotigitter", - "asset_not_found_on_device_android": "Datei ned ufem Grät gfunde", "asset_offline": "Datei offline", "asset_offline_description": "Die externi Datei isch nÃŧmme uf em Datenträger vorhande. Bitte wend dich ah din Immich-Administrator, um Hilf z becho.", + "asset_restored_successfully": "Datei erfolgriich wiederhergstellt", "asset_skipped": "Übersprungä", "asset_skipped_in_trash": "Im Papierkorb", + "asset_trashed": "Datei glÃļscht", "asset_troubleshoot": "Datei Fehlerbehebig", "asset_uploaded": "Ufeglade", "asset_uploading": "Ufeladeâ€Ļ", "asset_viewer_settings_subtitle": "Verwaltig vo de Iistellige fÃŧr d’Foto-Aazeig", "asset_viewer_settings_title": "Foto-Aazeig", "assets": "Dateie", + "assets_added_count": "{count, plural, one {# Datei} other {# Dateie}} hinzugefÃŧgt", "assets_added_to_album_count": "{count, plural, one {# Datei} other {# Dateie}} zum Album hinzugefÃŧgt", "assets_added_to_albums_count": "{assetTotal, plural, one {# Datei} other {# Dateie}} zu {albumTotal, plural, one {# Album} other {# Albene}} hinzugefÃŧgt", "assets_cannot_be_added_to_album_count": "{count, plural, one {Datei chan}other {Dateie chÃļnnd}} nÃļd zum Album hinzuegfÃŧegt werde", "assets_cannot_be_added_to_albums": "{count, plural, one {Datei chan} other {Dateie chÃļnnd}} nÃļd zu dene Albe hinzuegfÃŧegt werde", "assets_count": "{count, plural, one {# Datei} other {# Dateie}}", + "assets_deleted_permanently": "{count} Elemänt(e) permanent glÃļscht", + "assets_deleted_permanently_from_server": "{count} Elemänt(e) permanent vom Immich-Server glÃļscht", + "assets_downloaded_failed": "{count, plural, one {# Datei abeglade – {error} gschiiteret} other {# Dateie abeglade – {error} gschiiteret}}", + "assets_downloaded_successfully": "{count, plural, one {# Datei erfolgreich abeglade} other {# Dateie erfolgreich abeglade}}", "assets_moved_to_trash_count": "{count, plural, one {# Datei} other {# Dateie}} i de Papirkorb verschobe", "assets_permanently_deleted_count": "{count, plural, one {# Datei} other {# Dateie}} endgÃŧltig glÃļscht", "assets_removed_count": "{count, plural, one {# Datei} other {# Dateie}} entfernt", + "assets_removed_permanently_from_device": "{count} Element(er) permanent vo dim Gerät glÃļscht", "assets_restore_confirmation": "Bisch du sicher, dass du alli Date us em Papirkorb wiederherställe wotsch? Die Aktion cha nÃļd rÃŧckgängig gmacht werde! Beachte, dass Offline-Dateie uf die Wiis nÃļd wiederherställt werde chÃļnnd.", "assets_restored_count": "{count, plural, one {# Datei} other {# Dateie}} wiederhergstellt", + "assets_restored_successfully": "{count} Elemänt(e) erfolgriich wiederhergstellt", "assets_trashed": "{count} Element(e) glÃļscht", "assets_trashed_count": "{count, plural, one {# Datei} other {# Dateie}} ih de Papierkorb verschobe", + "assets_trashed_from_server": "{count} Elemänt(e) vom Immich-Server glÃļscht", "assets_were_part_of_album_count": "{count, plural, one {# Datei isch} other {# Dateie sind}} bereits im Album vorhanden", "assets_were_part_of_albums_count": "{count, plural, one {Datei isch} other {Dateie sind}} bereits i de Albene", "authorized_devices": "Verwendeti Grät", @@ -562,44 +581,84 @@ "autoplay_slideshow": "Automatische Diashow", "back": "Zrugg", "back_close_deselect": "Zrugg, Schliesse oder Abwählä", + "background_backup_running_error": "Sicherig läuft im Hintergrund. Manuelli Sicherig cha nÃļd gstartet werde", "background_location_permission": "Hintergrund Standortfreigob", "background_location_permission_content": "Um im Hintergrund zwÃŧsche de Netzwerke wächsle z’chÃļnne, mues Immich *immer* Zuegriff uf dr genaue Standort ha, damit d’App dr Name vom WLAN-Netzwerk ermittle cha", "background_options": "Hintergrund Optione", "backup": "Sicherig", + "backup_album_selection_page_albums_device": "Albene uf dem Grät ({count})", "backup_album_selection_page_albums_tap": "Aatippe zum sichere, nomol aatippe zum usschliesse", "backup_album_selection_page_assets_scatter": "Elemente (Fotos / Videos) chÃļnnd sich Ãŧber mehreri Albe verteile. Drum chÃļnnd die vor dr Sicherig iigschlosse oder usgschlosse werde.", "backup_album_selection_page_select_albums": "Albene uuswählä", "backup_album_selection_page_selection_info": "Uuswahlinformatione", + "backup_album_selection_page_total_assets": "Element gsamt", "backup_albums_sync": "Synchronisation vo de Sicherigsalbene", + "backup_all": "Alli", + "backup_background_service_backup_failed_message": "Es isch e Fähler bi dr Sicherig uufträtä. Versueche erneutâ€Ļ", "backup_background_service_complete_notification": "Datei Backup abgschlossä", + "backup_background_service_connection_failed_message": "Es het kei Verbindung zum Server chÃļnne hergstellt werde. Nomol versuecheâ€Ļ", + "backup_background_service_current_upload_notification": "Lädt {filename} hoch", "backup_background_service_default_notification": "Suech nach neue Elementâ€Ļ", + "backup_background_service_error_title": "Fähler bi de Sicherig", "backup_background_service_in_progress_notification": "Elemänt werdet gsicheretâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} het nÃļd chÃļnne ufeglade werde", "backup_controller_page_albums": "Gsicherti Albene", + "backup_controller_page_background_app_refresh_disabled_content": "Aktiviere Hintergrundaktualisierige i de Iihstellige -> Allgemein -> Hintergrundaktualisierige, um Sicherige im Hintergrund z ermÃļglichä.", + "backup_controller_page_background_app_refresh_disabled_title": "Hintergrundaktualisierige sind deaktiviert", + "backup_controller_page_background_app_refresh_enable_button_text": "Gang zu de Iistellige", "backup_controller_page_background_battery_info_link": "Zeig mir wie", "backup_controller_page_background_battery_info_message": "FÃŧr di beschte Resultat bi Hintergrund‑Sicherige: deaktiviere alli Batterieoptimierig und all Beschränkige fÃŧr Hintergrund‑Aktivitäte vo Immich.\n\nWäge dem das gerätespezifisch isch, lueg bitte i de Infoe vom Hersteller vo dim Gerät nach.", "backup_controller_page_background_battery_info_ok": "Ok", "backup_controller_page_background_battery_info_title": "Batterieoptimierige", + "backup_controller_page_background_charging": "Nume während des Ladens", + "backup_controller_page_background_configure_error": "Hand nÃļd chÃļnne de Hintergrundservice konfiguriere", "backup_controller_page_background_delay": "Sicherig vo neue Elemente verzÃļgeret um: {duration}", + "backup_controller_page_background_description": "Schalt de Hintergrundservice iih, um neui Elemente automatisch im Hintergrund z sichere, ohni d’App z Ãļffne", + "backup_controller_page_background_is_off": "Automatischi Sicherig im Hintergrund isch deaktiviert", + "backup_controller_page_background_is_on": "Automatischi Sicherig im Hintergrund isch aktiviert", + "backup_controller_page_background_turn_off": "Hintergrundservice ussschaltä", + "backup_controller_page_background_turn_on": "Hintergrundservice iihschaltä", + "backup_controller_page_background_wifi": "Nume im WLAN", "backup_controller_page_backup": "Sicherig", "backup_controller_page_backup_selected": "Usgwählt: ", "backup_controller_page_backup_sub": "Gsicherti Fotis und Videos", + "backup_controller_page_created": "Erstellt am: {date}", + "backup_controller_page_desc_backup": "Aktivier d’Sicherig, um Elemänt immer automatisch uf de Server ufezlade, während du d’App bruuchsch.", "backup_controller_page_excluded": "Usgschlossä: ", + "backup_controller_page_failed": "Fählgschagä ({count})", + "backup_controller_page_filename": "Dateiname: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informatione zur Sicherig", "backup_controller_page_none_selected": "Keini usgwählt", "backup_controller_page_remainder": "Verbliibend", "backup_controller_page_remainder_sub": "No z sichere Fotis und Videos", "backup_controller_page_server_storage": "Server-Speicher", + "backup_controller_page_start_backup": "Sicherig starte", + "backup_controller_page_status_off": "Sicherig im Vordergrund isch inaktiv", + "backup_controller_page_status_on": "Sicherig im Vordergrund isch aktiv", "backup_controller_page_storage_format": "{used} von {total} gnutzt", "backup_controller_page_to_backup": "No z sichere Albene", "backup_controller_page_total_sub": "Alli Fotis und Videos", + "backup_controller_page_turn_off": "Sicherig im Vordergrund ussschalte", + "backup_controller_page_turn_on": "Sicherig im Vordergrund iihschalte", + "backup_controller_page_uploading_file_info": "Informatione", + "backup_err_only_album": "Das einzigi Album cha nÃļd entfernet werde", "backup_error_sync_failed": "Synchronisierung gschiiteret. Sicherig cha nÃļd verarbeitet werde.", "backup_info_card_assets": "Elemänt", + "backup_manual_cancelled": "Abbroche", + "backup_manual_in_progress": "Sicherig lauft scho. Bitte versuech es spÃļter nomol", + "backup_manual_success": "Erfolgriich", + "backup_manual_title": "Sicherigsstatus", "backup_options": "Backup Optione", + "backup_options_page_title": "Sicherigsoptione", + "backup_setting_subtitle": "Verwaltig vo de Upload-iihstellige im Hintergrund und Vordergrund", "backup_settings_subtitle": "Upload-Iihstellige verwalte", "backward": "RÃŧckwärts", "biometric_auth_enabled": "Biometrische Authentifizierig aktiviert", "biometric_locked_out": "Du bisch vo de biometrische Authenfizierig usgschlossä", "biometric_no_options": "Kei biometrische Optione verfÃŧegbar", "biometric_not_available": "D’biometrischi Authentifizierig isch uf däm Gerät nÃļd verfÃŧgbar", + "birthdate_saved": "Geburtsdatum erfolgrich gspeichert", "birthdate_set_description": "Das Geburtsdatum wird bruucht, um s’Alter vo däre Person zum Ziitpunkt vo enem Foto z berechne.", "blurred_background": "Unscharfe Hintergrund", "bugs_and_feature_requests": "Fähler & Verbesserigsvorschläg", @@ -609,6 +668,20 @@ "bulk_keep_duplicates_confirmation": "Bisch du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateie}} behalte wotsch? Das wird alli Duplikat-Gruppä uuflÃļse, ohni Ãļppis z lÃļsche.", "bulk_trash_duplicates_confirmation": "Bisch du sicher, dass du {count, plural, one {# duplizierte Datei} other {# duplizierte Dateie gemeinsam}} i de Papirkorb verschiebe wotsch? Das wird d’grÃļssti Datei vo jeder Gruppe behalte und alli andere Duplikate i de Papirkorb verschiebe.", "buy": "Immich erwerbä", + "cache_settings_clear_cache_button": "ZwÃŧschespeicher lÃļsche", + "cache_settings_clear_cache_button_title": "LÃļscht dr ZwÃŧschecache vo dr App. Das wird d’Leistungsfähigkeit vo dr App deutlich einschränke, bis dr ZwÃŧschecache wieder ufbaut worde isch.", + "cache_settings_duplicated_assets_clear_button": "LÄÄRE", + "cache_settings_duplicated_assets_subtitle": "Fotos und Videos, wo vo dr App blockiert werdend", + "cache_settings_duplicated_assets_title": "Duplikate ({count})", + "cache_settings_statistics_album": "Vorschaubilder i dä Bibliothek", + "cache_settings_statistics_full": "Originalbilder", + "cache_settings_statistics_shared": "Vorschaubilder vo geteilte Albene", + "cache_settings_statistics_thumbnail": "Vorschaubilder", + "cache_settings_statistics_title": "ZwÃŧschespiicher-Nutzig", + "cache_settings_subtitle": "Kontrolliere, wie Immich dr ZwÃŧschecache bruucht", + "cache_settings_tile_subtitle": "Lokale Spiicher verwalte", + "cache_settings_tile_title": "Lokale Spiicher", + "cache_settings_title": "ZwÃŧschespiicher Ihstellige", "camera": "Kamera", "camera_brand": "Kamera-Marke", "camera_model": "Kamera-Modell", @@ -621,10 +694,10 @@ "cannot_update_the_description": "Beschriibrig cha nÃļd aktualisiert werde", "cast": "Überträge", "cast_description": "Konfiguration verfÃŧgbarer Ziel", - "change": "Ändere", "change_date": "Datum ändere", "change_description": "Beschriibig aahpasse", "change_display_order": "Aahzeigereihefolg ändere", + "change_expiration_time": "Verfallsziitpunkt ändere", "change_location": "Standort ändere", "change_name": "Name ändere", "change_name_successfully": "Name isch erfolgrich gänderet worde", @@ -642,16 +715,16 @@ "changed_visibility_successfully": "D’Sichtbarkeit isch erfolgrich gänderet worde", "charging": "Uufladä", "charging_requirement_mobile_backup": "Sicherig im Hintergrund fordert, dass s’Gerät ufglade wird", + "check_corrupt_asset_backup": "Uf bschädigti Asset-Sicherige prÃŧefe", + "check_corrupt_asset_backup_button": "ÜberprÃŧefig durrefÃŧehrä", + "check_corrupt_asset_backup_description": "FÃŧhr die PrÃŧefig nume mit aktiviertem WLAN dur, nachdem alli Dateie gsiichert worde sind. Dä Vorgang cha e paar Minute duurä.", "check_logs": "Logs prÃŧafä", "checksum": "PrÃŧefsumme", - "choose": "Uswähle", "choose_matching_people_to_merge": "Wähl passendi Persone zum ZämmezfÃŧehre", "city": "Stadt", "cleanup_confirm_description": "Immich hed {count} Dateie (vorem {date} erstellt) sicher ufem Server gfunde. SÃļlled die lokale Kopie vo dem Grät glÃļscht werde?", "cleanup_confirm_prompt_title": "Vo dem Grät entferne?", "cleanup_deleted_assets": "{count} Dateie i de lokali Papierchorb verschobe", - "cleanup_found_assets": "{count} ufegladni Dateie gfunde", - "cleanup_found_assets_with_size": "{count} gsicherti Dateie gfunde ({size})", "cleanup_icloud_shared_albums_excluded": "Teilti iCloud Albe sind vom Scan usgschlosse", "clear": "Lääre", "clear_all": "Alles lääre", @@ -659,16 +732,20 @@ "clear_file_cache": "Dateie-Cache lääre", "clear_message": "Nochrichte lääre", "clear_value": "Wert lääre", + "client_cert_dialog_msg_confirm": "Ok", + "client_cert_enter_password": "Passwort iigäh", "client_cert_import": "Importiere", "client_cert_import_success_msg": "Client Zertifikat isch importiert wordä", "client_cert_invalid_msg": "UngÃŧltigi Zertifikatsdatei oder falsches Passwort", "client_cert_remove_msg": "Client Zertifikat isch entfernt wordä", "client_cert_subtitle": "UnterstÃŧtzt nume s’PKCS12 (.p12, .pfx) Format. Zertifikat-Import oder -Entferne sind nume vor em Login mÃļglech", "client_cert_title": "SSL-Client-Zertifikat [Experimentell]", + "clockwise": "Im Uhrzeigersinn", "close": "SchlÃŧssä", "collapse": "Zämmeklappe", "collapse_all": "Alles zämmeklappe", "color": "Farb", + "color_theme": "Farb-Theme", "command": "Befähl", "comment_deleted": "Kommentar glÃļscht", "comment_options": "Kommentaroptione", @@ -690,13 +767,18 @@ "contain": "Vollständig", "context": "Kontext", "continue": "Fortsetze", + "control_bottom_app_bar_create_new_album": "Neus Album erstellä", + "control_bottom_app_bar_delete_from_immich": "Vo Immich lÃļschä", "control_bottom_app_bar_delete_from_local": "Vom Grät lÃļsche", "control_bottom_app_bar_edit_location": "Standort bearbeite", "control_bottom_app_bar_edit_time": "Datum und Uhrziit bearbeite", + "control_bottom_app_bar_share_link": "Link teile", + "control_bottom_app_bar_share_to": "Teile mit", "control_bottom_app_bar_trash_from_immich": "I de Papierkorb verschiebä", "copied_image_to_clipboard": "S’Bild isch i d’ZwÃŧscheablag kopiert worde.", "copied_to_clipboard": "I d’ZwÃŧscheablage kopiert!", "copy_error": "Kopier-Fähler", + "copy_file_path": "Dateipfad kopiere", "copy_image": "Bild kopiere", "copy_link": "Link kopiere", "copy_link_to_clipboard": "Link I d’ZwÃŧscheablage kopiere", @@ -729,19 +811,25 @@ "created_at": "Erstellt", "creating_linked_albums": "Erstelle verknÃŧpfti Albene...", "crop": "Zueschniidä", + "crop_aspect_ratio_fixed": "Fixiert", "crop_aspect_ratio_free": "Frei", "crop_aspect_ratio_original": "Original", + "curated_object_page_title": "Sachä", "current_device": "Aktuells Grät", "current_pin_code": "Aktuelle PIN Code", "current_server_address": "Aktuelli Serveradresse", "custom_locale": "Benutzerdefinierti Sproch", "custom_locale_description": "Datumsahgoob und Zahle je nach Sproch und Land formatiere", + "custom_url": "Benutzerdefinierti URL", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Dunkäl", "dark_theme": "Dunkli Aahsicht umschalte", "date": "Datum", "date_after": "Datum noch", "date_and_time": "Datum und Ziit", "date_before": "Datum vor", + "date_format": "E d. LLL y â€ĸ hh:mm", "date_of_birth_saved": "S’Geburtsdatum isch erfolgrich gspiicheret worde", "date_range": "Datumsberiich", "day": "Tag", @@ -755,6 +843,8 @@ "delete_dialog_alert": "Die Elemente werde unwiderruflich vo Immich und vom Grät entfernt", "delete_dialog_alert_local": "Die Inhält werdend vom Grät glÃļscht, bliibend aber uf dem Immich-Server", "delete_dialog_alert_local_non_backed_up": "Einigi Inhalt sind nÃļd i Immich gsiichert und werde dauerhaft vom Gerät gelÃļscht", + "delete_dialog_alert_remote": "Die Inhält werdend dauerhaft vom Immich-Server glÃļscht", + "delete_dialog_ok_force": "Trotzdem lÃļsche", "delete_dialog_title": "EndgÃŧltig lÃļsche", "delete_duplicates_confirmation": "Bisch du sicher, dass du diä Duplikat endgÃŧltig lÃļsche wotsch?", "delete_face": "Gsicht lÃļschä", @@ -773,16 +863,21 @@ "delete_tag_confirmation_prompt": "Bisch du sicher, dass de {tagName} glÃļscht werde sÃļtt?", "delete_user": "Nutzer lÃļsche", "deleted_shared_link": "teilte link lÃļsche", + "deletes_missing_assets": "LÃļscht Dateie, welli uf de Feschtplatte fählend", "description": "Beschriibig", + "description_input_hint_text": "Beschriibig hinzufÃŧegä...", + "description_input_submit_error": "D’Beschribig het nÃļd chenne gänderet werde, bitte lueg im Log fÃŧr meh Details", "deselect_all": "Alli abwähle", "details": "Details", "direction": "Richtig", "disable": "Deaktiviere", "disabled": "Deaktiviert", + "disallow_edits": "Bearbeitig verbÃŧÃŧtä", "discord": "Discord", "discover": "Entdeckä", "discovered_devices": "Gfundeni Grät", "dismiss_all_errors": "Alli Fähler ignorierä", + "dismiss_error": "Fähler ignorierä", "display_options": "Aahzeigeoptione", "display_order": "Aahzeigereihefolg", "display_original_photos": "Originali Fotis Aahzeige", @@ -791,9 +886,11 @@ "documentation": "Dokumentation", "done": "Fertig", "download": "Abelade", + "download_action_prompt": "Abelade vo {count} Dateie", "download_canceled": "Download abbroche", "download_complete": "Download vollständig", "download_enqueue": "Download i d’Warteschlange gstellt", + "download_error": "Download fählerhaft", "download_failed": "Download fählgschlage", "download_finished": "Download abgschlosse", "download_include_embedded_motion_videos": "Iigbetteti Videos", @@ -803,6 +900,9 @@ "download_paused": "Download pausiert", "download_settings": "Download", "download_settings_description": "Iihstelligä fÃŧrs Abeladä vo Dateie verwalte", + "download_started": "Download gstartet", + "download_sucess": "Download erfolgriich", + "download_sucess_android": "Die Datei wurde nach DCIM/Immich abeglade", "download_waiting_to_retry": "Warte uf neue Versuech", "downloading": "Abelade", "downloading_asset_filename": "Datei {filename} wird abeglade", @@ -819,7 +919,9 @@ "edit_date_and_time": "Datum und Uhrziit bearbeite", "edit_date_and_time_action_prompt": "{count} Date und Ziite gänderet", "edit_date_and_time_by_offset": "Datum ändere um Versatz", + "edit_date_and_time_by_offset_interval": "Neue Datumsbereich: {from} - {to}", "edit_description": "Beschriibig bearbeite", + "edit_description_prompt": "Bitte wähl e neui Beschriibig:", "edit_exclusion_pattern": "Usschlussmuster bearbeite", "edit_faces": "Gsichter bearbeite", "edit_key": "SchlÃŧssel bearbeite", @@ -834,6 +936,8 @@ "edit_user": "Nutzer bearbeite", "edit_workflow": "Workflow bearbeite", "editor": "Bearbeiter", + "editor_close_without_save_prompt": "D’Änderige werden nÃļd gspeichert", + "editor_close_without_save_title": "Editor schlÃŧssä?", "email": "E-Mail", "email_notifications": "E-Mail Benochrichtigunge", "empty_folder": "Dä Ordner isch leer", @@ -849,7 +953,9 @@ "enter_your_pin_code": "PIN-Code Iigäh", "enter_your_pin_code_subtitle": "Gib din PIN-Code ii, um uf dr gschperrte Ordner zuezgriffä", "error": "Fähler", + "error_change_sort_album": "Änderig vo dr Aazeigeräihefolg isch gscheitert", "error_delete_face": "Fähler bim LÃļschä vom Gsicht", + "error_getting_places": "Fähler bim Abruefä vo dä Ört", "error_loading_image": "Fähler bim Lade vom Bild", "error_loading_partners": "Fähler bim Lade vo de Partner: {error}", "error_saving_image": "Fähler: {error}", @@ -978,9 +1084,17 @@ "exif": "EXIF", "exif_bottom_sheet_description": "Beschriibig dezuefÃŧege...", "exif_bottom_sheet_description_error": "Fähler bim d Aktualisierig vo de Beschriibig", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "STANDORT", "exif_bottom_sheet_no_description": "Kei Beschriibig", + "exif_bottom_sheet_people": "PERSONE", + "exif_bottom_sheet_person_add_person": "Name dezuefÃŧege", "exit_slideshow": "Diashow beende", "expand_all": "Alli ufklappe", + "experimental_settings_new_asset_list_subtitle": "In Arbet", + "experimental_settings_new_asset_list_title": "Experimentells FÃļteli-Gitter aktiviere", + "experimental_settings_subtitle": "Benutzig uf eigni Gfohr!", + "experimental_settings_title": "Experimentell", "expire_after": "Verfällt noch", "expired": "Verfalle", "expires_date": "Lauft ab: {date}", @@ -1005,8 +1119,10 @@ "favorite_action_prompt": "{count} zu de Favorite hinzuegfÃŧegt", "favorite_or_unfavorite_photo": "Favorisierts oder nÃļd favorisierts Foti", "favorites": "Favorite", + "favorites_page_no_favorites": "Kei favorisierte Inhält gfundä", "feature_photo_updated": "Profilbild aktualisiert", "features": "Funktione", + "features_in_development": "Feature isch in Entwicklig", "features_setting_description": "Funkione i de App verwalte", "file_name_or_extension": "Dateiname oder -erwiiterig", "file_size": "DateigrÃļssi", @@ -1015,6 +1131,7 @@ "filter": "Filter", "filter_people": "Persone filterä", "filter_places": "Ort filterä", + "find_them_fast": "Finded sie schneller mit de Suechi noch Näme", "first": "Erste", "fix_incorrect_match": "Fählerhafti Überiistimmig behebe", "folder": "Ordner", @@ -1043,6 +1160,7 @@ "group_owner": "Gruppiere noch Bsitzer", "group_places_by": "Ort gruppiere noch...", "group_year": "Gruppierig noch Johr", + "haptic_feedback_switch": "Haptisches Feedback aktiviere", "haptic_feedback_title": "Haptisches Feedback", "has_quota": "Kontingent", "hash_asset": "Dateihash", @@ -1061,12 +1179,29 @@ "hide_person": "Person verbergä", "hide_text_recognition": "Texterkennung verbergä", "hide_unnamed_people": "Unbenannti Person verbergä", + "home_page_add_to_album_conflicts": "{added} Elemente zu {album} hinzugfÃŧegt. {failed} Elemente sind bereits vorhande.", + "home_page_add_to_album_err_local": "Es chÃļnd no nÃļd lokali Element zu Albe dezuegfuegt werde, Ãŧberspringe", + "home_page_add_to_album_success": "{added} Element zu {album} dezuegfuegt.", + "home_page_album_err_partner": "Inhält vo Partner chÃļnd im Momänt nÃļd zu Albe dezuegfuegt werde", + "home_page_archive_err_local": "Cha lokali Element nÃļd archiviere, Ãŧberspringe", + "home_page_archive_err_partner": "Inhält vo Partner chÃļnd nÃļd archiviert werde", "home_page_building_timeline": "Ziitachse wird erstellt", + "home_page_delete_err_partner": "Inhält vo Partner chÃļnd nÃļd glÃļscht werde, Ãŧberspringe", + "home_page_delete_remote_err_local": "Lokali Element in de Uswahl zum Entferne vo Remote-Element, Überspringe", + "home_page_favorite_err_local": "Cha lokali Element no nÃļd favorisiere, Ãŧberspringe", + "home_page_favorite_err_partner": "Inhält vo Partner chÃļnd nÃļd favorisiert werde, Ãŧberspringe", + "home_page_first_time_notice": "Wenn Du d'App zum erschte Mol bruuchsch, wähl bitte es Album zum Siichere uus, demit d'Ziitachs mit FÃļteli und Videos cha gfÃŧllt werde", + "home_page_locked_error_local": "Lokali Dateie chÃļnd nÃļd i de gsperrti Ordner verschobe werde, Ãŧberspringe", + "home_page_locked_error_partner": "Dateie vo Partner chÃļnd nÃļd i de gsperrti Ordner verschobe werde, Ãŧberspringe", + "home_page_share_err_local": "Lokali Inhält chÃļnd nÃļd per Link teilt werde, Ãŧberspringe", + "home_page_upload_err_limit": "Es chÃļnd max. 30 Element gliichzitig ufelglade werde, Ãŧberspringe", "host": "Host", "hour": "Stund", "hours": "Stunde", "id": "ID", "idle": "Untätig", + "ignore_icloud_photos": "iCloud Fotos ignorierä", + "ignore_icloud_photos_description": "FÃļteli, wo i de iCloud gspiicheret sind, wärded nÃļd uf de immich Server ufeglade", "image": "FÃļteli", "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} ufgnoo am {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} ufgnoo mit {person1} am {date}", @@ -1078,6 +1213,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1} und {person2} am {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1}, {person2} und {person3} am {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} ufgnoo in {city}, {country} mit {person1}, {person2} und {additionalCount, number} andere am {date}", + "image_saved_successfully": "Bild gspiicheret", + "image_viewer_page_state_provider_download_started": "Download gstartet", + "image_viewer_page_state_provider_download_success": "Erfolgriich abeglade", + "image_viewer_page_state_provider_share_error": "Fähler bim Teile", "immich_logo": "Immich-Logo", "immich_web_interface": "Immich-Web-Oberflächi", "import_from_json": "Us JSON importierä", @@ -1102,6 +1241,12 @@ "invalid_date_format": "UngÃŧltigs Datumsformat", "invite_people": "Persone ilade", "invite_to_album": "Zum Album ilade", + "ios_debug_info_fetch_ran_at": "Abruäf lauft {dateTime}", + "ios_debug_info_last_sync_at": "Zletscht aktualisiert {dateTime}", + "ios_debug_info_no_processes_queued": "Kei HintergrÃŧndsprozäss i de Waarteschlange", + "ios_debug_info_no_sync_yet": "No kei Hindergrundsynchronisierigs-Uftrag usgfÃŧehrt", + "ios_debug_info_processes_queued": "{count, plural, one {{count} Hindergrundsprozäss i de Waarteschlange} other {{count} Hindergrundsprozäss i de Waarteschlange}}", + "ios_debug_info_processing_ran_at": "Prozess lauft {dateTime}", "items_count": "{count, plural, one {# Iitrag} other {# Iiträg}}", "jobs": "Ufgobe", "keep": "Bhalte", @@ -1128,6 +1273,9 @@ "library": "Bibliothek", "library_add_folder": "Ordner dezuefÃŧege", "library_edit_folder": "Ordner bearbeite", + "library_options": "Bibliotheksoptione", + "library_page_device_albums": "Albene uf dem Grät", + "library_page_new_album": "Neus Album", "library_page_sort_asset_count": "Aazahl vo de Inhält", "library_page_sort_created": "Zletscht erstellt", "library_page_sort_last_modified": "Zletscht bearbeitet", @@ -1167,9 +1315,11 @@ "login": "Aamälde", "login_disabled": "Login isch deaktiviert", "login_form_api_exception": "API Fähler. Bitte d'Serveradressa ÃŧberprÃŧäfe und noochemaa probiere.", + "login_form_back_button_text": "Zrugg", "login_form_email_hint": "dini@email.ch", "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Server-URL", + "login_form_err_http": "Bitte gib http:// oder https:// aa", "login_form_err_invalid_email": "UungÃŧltigi E-Mail", "login_form_err_invalid_url": "UungÃŧltigi URL", "login_form_err_leading_whitespace": "Läärzeiche am Aafang", @@ -1179,6 +1329,7 @@ "login_form_failed_login": "Fähler bim Login, bitte ÃŧberprÃŧäf d'Server-URL, dini E-Mail oder's Passwoort", "login_form_handshake_exception": "Fähler bim Verbindigsufbau mit em Server. Falls du en sälbscht-underschriibigs Zärtifikaat verwändisch, aktivier d'UunderstÃŧtzig defÃŧr i de Iistellige.", "login_form_password_hint": "Passwort", + "login_form_save_login": "Aagmäldet bliibe", "login_form_server_empty": "Serveradressa iigä.", "login_form_server_error": "Es het nÃļd chÃļnne Verbindig zum Server ufgnoh werde.", "login_has_been_disabled": "D'Aamäldig isch deaktiviert worde.", @@ -1195,6 +1346,7 @@ "main_menu": "HauptmenÃŧ", "maintenance_description": "Immich isch i de Wartigmodus versetzt worde.", "maintenance_end": "Wartigsmodus beendä", + "maintenance_end_error": "Wartigmodus het nÃļd chenne beendet werde.", "maintenance_logged_in_as": "Aktuell aagmäldet als {user}", "maintenance_title": "VorÃŧbergehend nÃļd verfÃŧgbar", "make": "Markä", @@ -1203,6 +1355,7 @@ "manage_media_access_settings": "Iistellige Ãļffne", "manage_media_access_subtitle": "Erlaub Immich, Mediedateie z verwalte und z verschiebe.", "manage_media_access_title": "Verwaltig vo Mediedateie", + "manage_shared_links": "Freigäbeni Links verwalte", "manage_sharing_with_partners": "Gmeinsami Nutzig mit Partner verwalte", "manage_the_app_settings": "App-Iistellige verwalte", "manage_your_account": "Dis Chonto verwalte", @@ -1210,10 +1363,13 @@ "manage_your_devices": "Dini iigloggte Gerät verwalte", "manage_your_oauth_connection": "Dini OAuth-VerchnÃŧpfig verwalte", "map": "Charte", + "map_assets_in_bounds": "{count, plural, =0 {Kei Fotos i däm Gebiet} one {# Foto} other {# Fotos}}", "map_cannot_get_user_location": "Standort het nÃļd chenne ermittelt werde", + "map_location_dialog_yes": "Jo", "map_location_picker_page_use_location": "Ufnahmeort verwände", "map_location_service_disabled_content": "D’Ortigsdienscht mÃŧend aktiviert si, um Inhält am aktuelle Standort aazeige z chÃļnne. Wotsch d’Ortigsdienscht jetzt aktiviere?", "map_location_service_disabled_title": "Ortigsdienscht deaktiviert", + "map_marker_for_images": "Charte-Markierige fÃŧr Bilder, wo i {city}, {country} ufgnoh worde sind", "map_marker_with_image": "Charte-Markierig mit Bild", "map_no_location_permission_content": "D’Ortigsdienscht mÃŧend aktiviert si, um Inhält am aktuelle Standort aazeige z chÃļnne. Wotsch d’Ortigsdienscht jetzt aktiviere?", "map_no_location_permission_title": "Kei Zuegriff uf dä Standort", @@ -1223,11 +1379,14 @@ "map_settings_date_range_option_days": "Letschte {days} Täg", "map_settings_date_range_option_year": "Letschtes Johr", "map_settings_date_range_option_years": "Letschte {years} Johre", + "map_settings_dialog_title": "Charte-Iistellige", "map_settings_include_show_archived": "Archivierti azeige", "map_settings_include_show_partners": "Partner iibeziehä", "map_settings_only_show_favorites": "Nur Favoritä azeige", "map_settings_theme_settings": "Chartedesign", + "map_zoom_to_see_photos": "Ansicht chliiner mache, um Fotos z gseh", "mark_all_as_read": "Alli als gläse markiere", + "mark_as_read": "Als gläse markiere", "marked_all_as_read": "Alli als gläse markiert", "matches": "Treffer", "matching_assets": "Passendi Dateie", @@ -1255,6 +1414,7 @@ "mobile_app_download_onboarding_note": "Herunterlade vo dr mobile App Ãŧber eini vo de folgende MÃļglichkeitä", "model": "Modäll", "month": "Monet", + "monthly_title_text_date_format": "MMMM y", "more": "Meh", "move": "Verschiebä", "move_off_locked_folder": "Us em gschperrte Ordner verschiebe", @@ -1262,7 +1422,11 @@ "move_to_lock_folder_action_prompt": "{count} zum gschperrte Ordner hinzuegfÃŧegt", "move_to_locked_folder": "I dä gschperrte Ordner verschiebe", "move_to_locked_folder_confirmation": "Die Fotos und Videos werde us allne Albe entfernet und chÃļnnd nume no im gschperrte Ordner azeigt werde", + "moved_to_archive": "{count, plural, one {# Datei} other {# Dateie}} archiviert", + "moved_to_library": "{count, plural, one {# Datei} other {# Dateie}} i d'Bibliotheek verschobe", "moved_to_trash": "I de Papiirschorb verschobe", + "multiselect_grid_edit_date_time_err_read_only": "S Daatum und d'Uhrziit vo schriibgschÃŧtztem Inehalt chan nÃļd veränderet werde, Ãŧberspringe", + "multiselect_grid_edit_gps_err_read_only": "De Uufnaameort vo schriibgschÃŧtztem Inhält chan nÃļd veränderet werde, Ãŧberspringe", "mute_memories": "Erinnerige stumm schalte", "my_albums": "Mini Albä", "name": "Name", @@ -1278,10 +1442,12 @@ "never": "Niemols", "new_album": "Neus Album", "new_api_key": "Neue API-SchlÃŧssel", + "new_date_range": "Neue Datumsberiich", "new_password": "Neus Passwort", "new_person": "Neui Person", "new_pin_code": "Neue PIN-Code", "new_pin_code_subtitle": "Das isch din erschte Zuegriff uf de gspärrti Ordner. Erstell en PIN-Code, um sicher uf die Siite z'cho", + "new_timeline": "Neui Zytschine", "new_update": "Neus Update", "new_user_created": "Neue Benutzer isch erstellt worde", "new_version_available": "NEUI VERSION VERFÜEGBAR", @@ -1305,6 +1471,7 @@ "no_favorites_message": "FÃŧeg Favoriite dezue, zum dini beschte Bild und Videos schnäll finde", "no_libraries_message": "Erstell e externi Bibliotheek, zum dini FÃļteli und Videos aluäge", "no_local_assets_found": "Kei lokali Datei mit dä PrÃŧäfsumme gfunde", + "no_location_set": "Kei Standoort feschtgleit", "no_locked_photos_message": "FÃļteli und Videos im gspärrte Ordner sind versteckt und wärded nÃļd aazeigt, wänn du dini Bibliotheek dursuächsch.", "no_name": "Kei Name", "no_notifications": "Kei Benochrichtigunge", @@ -1314,6 +1481,7 @@ "no_results": "Kei Ergebniss", "no_results_description": "Versuech's mit eme SynonÃŧm oder eme allgemäinere Stichwort", "no_shared_albums_message": "Erstell es Album, zum FÃļteli und Videos mit Persone i dim Näzwerk teile", + "no_uploads_in_progress": "Kei Upload am Laufä", "not_allowed": "NÃļd erlaubt", "not_available": "N/A", "not_in_any_album": "I keinem Album", @@ -1339,8 +1507,5 @@ "on_this_device": "Uf däm Grät", "onboarding": "Iistig", "onboarding_locale_description": "Wähl dini bevorzugti Sprooch. Du chasch die au spÃļter i dine Iistellige ändere.", - "onboarding_privacy_description": "Diä folgende (optionali) Funktione hänged vo externä Diänscht ab und chÃļnd jederziit i de Iistellige deaktiviärt wärde.", - "upload_finished": "Ufelade beändet", - "users": "Benutzer", - "waiting": "Usstehend" + "onboarding_privacy_description": "Diä folgende (optionali) Funktione hänged vo externä Diänscht ab und chÃļnd jederziit i de Iistellige deaktiviärt wärde." } diff --git a/i18n/gu.json b/i18n/gu.json index 31a5a92199..d0dde2eec9 100644 --- a/i18n/gu.json +++ b/i18n/gu.json @@ -2,7 +2,7 @@ "about": "āĒĩāĒŋāĒļā̇", "account": "āĒ–āĒžāǤā́āĒ‚", "account_settings": "āĒ–āĒžāǤāĒžāǍāĒžāĒ‚ āǏā̇āǟāĒŋāĒ‚āĒ—āĢāǏ", - "acknowledge": "āǜāĒžāĒŖāĒ•āĒžāǰāĢ€", + "acknowledge": "āǜāĒžāĒŖāĒ•āĒžāǰāĢ€ āĒŽāĒŗāĢ€", "action": "āĒ•āĢāǰāĒŋāǝāĒž", "action_common_update": "āǏā́āǧāĒžāǰāĢ‹", "action_description": "āĒ›āĒžāĒ‚āǟā̇āǞāĢ€ āĒĩāǏāĢāǤā́āĒ“ āĒĒāǰ āĒ•āǰāĢ€ āĒļāĒ•āĒžāǝ āǤā̇āĒĩāĢ€ āĒ•āĢāǰāĒŋāǝāĒžāĒ“", @@ -10,31 +10,39 @@ "active": "āǏāĒ•āĢāǰāĒŋāǝ", "active_count": "āǏāĒ•āĢāǰāĒŋāǝ: {count}", "activity": "āĒĒāĢāǰāĒĩā̃āǤāĢāǤāĒŋ", + "activity_changed": "āĒĒāĢāǰāĒĩā̃āǤāĢāǤāĒŋ {enabled, select, true {āǏāĒ•āĢāǰāĒŋāǝ} other {āǍāĒŋāǎāĢāĒ•āĢāǰāĒŋāǝ}} āĒ›ā̇", "add": "āljāĒŽā̇āǰāĢ‹", "add_a_description": "āĒāĒ• āĒĩāǰāĢāĒŖāǍ āljāĒŽā̇āǰāĢ‹", "add_a_location": "āĒāĒ• āǏāĢāĒĨāĒžāǍ āljāĒŽā̇āǰāĢ‹", "add_a_name": "āĒāĒ• āǍāĒžāĒŽ āljāĒŽā̇āǰāĢ‹", "add_a_title": "āĒāĒ• āĒļāĢ€āǰāĢāǎāĒ• āljāĒŽā̇āǰāĢ‹", "add_action": "āĒ•āĢāǰāĒŋāǝāĒž āljāĒŽā̇āǰāĢ‹", + "add_action_description": "āĒ•āĢāǰāĒŋāǝāĒž āljāĒŽā̇āǰāĒĩāĒž āĒŽāĒžāǟā̇ āĒ•āĢāǞāĒŋāĒ• āĒ•āǰāĢ‹", "add_assets": "āǏāĒ‚āǏāĒžāǧāǍāĢ‹ āljāĒŽā̇āǰāĢ‹", "add_birthday": "āǜāǍāĢāĒŽāĒĻāĒŋāĒĩāǏ āljāĒŽā̇āǰāĢ‹", "add_endpoint": "āĒāǍāĢāĒĄāĒĒāĢ‹āLJāǍāĢāǟ āljāĒŽā̇āǰāĢ‹", "add_exclusion_pattern": "āĒŦāĒžāĒ•āĒžāǤ āǰāĒžāĒ–āĒĩāĒžāǍāĢ€ āĒĒāĒĻāĢāǧāǤāĒŋ āljāĒŽā̇āǰāĢ‹", + "add_filter": "āĒĢāĒŋāǞāĢāǟāǰ āljāĒŽā̇āǰāĢ‹", + "add_filter_description": "āĒĢāĒŋāǞāĢāǟāǰ āĒļāǰāǤ āljāĒŽā̇āǰāĒĩāĒž āĒŽāĒžāǟā̇ āĒ•āĢāǞāĒŋāĒ• āĒ•āǰāĢ‹", "add_location": "āǏāĢāĒĨāĒžāǍ āljāĒŽā̇āǰāĢ‹", + "add_more_users": "āĒĩāǧāĒžāǰā̇ āǝā́āĒāǰ āljāĒŽā̇āǰāĢ‹", "add_partner": "āǏāĒžāĒĨāĢ€ āljāĒŽā̇āǰāĢ‹", + "add_path": "āĒĒāĒžāĒĨ āljāĒŽā̇āǰāĢ‹", "add_photos": "āĒĢāĢ‹āǟāĢ‹ āljāĒŽā̇āǰāĢ‹", - "add_step": "āĒĒāĒ—āǞā́āĒ‚ āljāĒŽā̇āǰāĢ‹", "add_tag": "āǟā̇āĒ— āljāĒŽā̇āǰāĢ‹", "add_to": "āĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", "add_to_album": "ādžāǞāĢāĒŦāĒŽāĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", "add_to_album_bottom_sheet_added": "{album} āĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", "add_to_album_bottom_sheet_already_exists": "āĒĒāĒšā̇āǞā̇āĒĨāĢ€ āǜ {album} āĒŽāĒžāĒ‚ āĒ›ā̇", "add_to_album_bottom_sheet_some_local_assets": "āĒ•ā̇āǟāǞāĒžāĒ• āǞāĢ‹āĒ•āǞ āĒāǏā̇āǟāĢāǏāǍā̇ āĒāǞāĢāĒŦāĒŽāĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩāĢ€ āĒļāĒ•āĢāǝāĒž āǍāĒĨāĢ€", + "add_to_album_toggle": "{album} āĒŽāĒžāǟā̇ āĒĒāǏāĒ‚āĒĻāĒ—āĢ€ āǟā̉āĒ—āǞ āĒ•āǰāĢ‹", "add_to_albums": "ādžāǞāĢāĒŦāĒŽāĢ‹ āljāĒŽā̇āǰāĢ‹", "add_to_albums_count": "ādžāǞāĢāĒŦāĒŽāĢ‹ āĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹ {count}", "add_to_bottom_bar": "āĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", + "add_to_shared_album": "āĒļā̇āǰ āĒ•āǰā̇āǞ ādžāǞāĢāĒŦāĒŽāĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", "add_upload_to_stack": "āĒ…āĒĒāǞāĢ‹āĒĄāǍā̇ āǏāĢāǟā̇āĒ•āĒŽāĒžāĒ‚ āljāĒŽā̇āǰāĢ‹", "add_url": "URL āljāĒŽā̇āǰāĢ‹", + "add_workflow_step": "āĒĩāǰāĢāĒ•āĒĢāĢāǞāĢ‹ āĒĒāĒ—āǞā́ āljāĒŽā̇āǰāĢ‹", "added_to_archive": "ādžāǰāĢāĒ•āĒžāLJāĒĩāĒŽāĒžāĒ‚ āǏāĒ‚āĒ—āĢāǰāĒšāĒŋāǤ āĒ•āǰāĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩāĢ€ āĒ›ā̇", "added_to_favorites": "āĒŽāǍāĒĒāǏāĒ‚āĒĻ āǤāǰāĢ€āĒ•ā̇ āǚāĒŋāǍāĢāĒšāĢ€āǤ āĒ•āǰā̇āǞ āĒ›ā̇", "added_to_favorites_count": "āĒŽāǍāĒĒāǏāĒ‚āĒĻ āǤāǰāĢ€āĒ•ā̇ {count, number} āǍā̇ āǚāĒŋāĒšāĢāǍāĒŋāǤ āĒ•āǰāĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩā̇āǞ āĒ›ā̇", @@ -73,7 +81,6 @@ "cron_expression_description": "āĒ•āĢāǰāĢ‹āǍ āĒĢāĢ‹āǰāĢāĒŽā̇āǟāǍāĢ‹ āljāĒĒāǝāĢ‹āĒ— āĒ•āǰāĢ€āǍā̇ āǏāĢāĒ•ā̇āǍāĒŋāĒ‚āĒ— āLJāǍāĢāǟāǰāĒĩāǞ āǏā̇āǟ āĒ•āǰāĢ‹. āĒĩāǧā́ āĒŽāĒžāĒšāĒŋāǤāĢ€ āĒŽāĒžāǟā̇ āĒ•ā̃āĒĒāĒž āĒ•āǰāĢ€āǍā̇ Crontab Guru āǜā́āĒ“.", "cron_expression_presets": "āĒ•āĢāǰāĢ‹āǍ āĒāĒ•āĢāǏāĒĒāĢāǰā̇āĒļāǍ āĒĒāĢāǰāĢ€āǏā̇āǟāĢāǏ", "disable_login": "āǞāĢ‹āĒ—āĒŋāǍ āĒĄāĒŋāǏā̇āĒŦāǞ āĒ•āǰāĢ‹", - "download_csv": "CSV āĒĄāĒžāljāǍāǞāĢ‹āĒĄ āĒ•āǰāĢ‹", "duplicate_detection_job_description": "āǏāǰāĒ–āĢ€ āLjāĒŽā̇āǜāĢ‹ āĒļāĢ‹āǧāĒĩāĒž āĒŽāĒžāǟā̇ āǤāĒŽāĒžāǰāĒž āǏāĒ‚āǏāĒžāǧāǍāĢ‹ āĒĒāǰ āĒŽāĒļāĢ€āǍ āǞāǰāĢāǍāĒŋāĒ‚āĒ—āǍāĢ‹ āljāĒĒāǝāĢ‹āĒ— āĒ•āǰāĢ‹. ādž āǏā́āĒĩāĒŋāǧāĒž āǏāĢāĒŽāĒžāǰāĢāǟ āǏāǰāĢāǚ āĒĒāǰ ādžāǧāĒžāǰāĒŋāǤ āĒ›ā̇", "exclusion_pattern_description": "āǤāĒŽāĒžāǰāĒž āǏāĒ‚āĒ—āĢāǰāĒšāǍā̇ āǏāĢāĒ•ā̇āǍ āĒ•āǰāǤāĢ€ āĒĩāĒ–āǤā̇ āĒāĒ•āĢāǏāĒ•āĢāǞā́āĒāǍ āĒĒā̇āǟāǰāĢāǍ āǤāĒŽāǍā̇ āĒĢāĒžāLJāǞāĢ‹ āĒ…āǍā̇ āĒĢāĢ‹āǞāĢāĒĄāǰāĢāǏāǍā̇ āĒ…āĒĩāĒ—āĒŖāĒĩāĒžāĒŽāĒžāĒ‚ āĒŽāĒĻāĒĻ āĒ•āǰā̇ āĒ›ā̇. āǜāĢ‹ āǤāĒŽāĒžāǰāĢ€ āĒĒāĒžāǏā̇ āĒāĒĩāĢ€ āĒĢāĒžāLJāǞāĢ‹ āǧāǰāĒžāĒĩāǤāĒž āĒĢāĢ‹āǞāĢāĒĄāǰāĢāǏ āĒšāĢ‹āǝ āǜā̇āǍā̇ āǤāĒŽā̇ āLjāĒŽāĢāĒĒāĢ‹āǰāĢāǟ āĒ•āǰāĒĩāĒž āǍāĒĨāĢ€ āĒŽāĒžāĒ‚āĒ—āǤāĒž, āǜā̇āĒŽ āĒ•ā̇ RAW āĒĢāĒžāLJāǞāĢ‹, āǤāĢ‹ ādž āǏā́āĒĩāĒŋāǧāĒž āljāĒĒāǝāĢ‹āĒ—āĢ€ āĒ›ā̇.", "export_config_as_json_description": "āĒĩāǰāĢāǤāĒŽāĒžāǍ āǏāĒŋāǏāĢāǟāĒŽ āĒ•āĢ‹āǍāĢāĒĢāĒŋāĒ—āǍā̇ JSON āĒĢāĒžāLJāǞ āǤāǰāĢ€āĒ•ā̇ āĒĄāĒžāljāǍāǞāĢ‹āĒĄ āĒ•āǰāĢ‹", @@ -82,13 +89,6 @@ "face_detection_description": "āĒŽāĒļāĢ€āǍ āǞāǰāĢāǍāĒŋāĒ‚āĒ—āǍāĢ‹ āljāĒĒāǝāĢ‹āĒ— āĒ•āǰāĢ€āǍā̇ āǏāĒ‚āǏāĒžāǧāǍāĢ‹āĒŽāĒžāĒ‚ āǚāĒšā̇āǰāĒžāǍāĢ€ āĒĒāǰāĒ– āĒ•āǰāĢ‹. āĒĩāĢ€āĒĄāĒŋāǝāĢ‹ āĒŽāĒžāǟā̇, āĒĢāĒ•āĢāǤ āĒĨāĒ‚āĒŦāǍā̇āǞ āǜ āǧāĢāǝāĒžāǍāĒŽāĒžāĒ‚ āǞā̇āĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩā̇ āĒ›ā̇. \"āǰāĒŋāĒĢāĢāǰā̇āĒļ\" āĒŦāǧāĒž āǏāĒ‚āǏāĒžāǧāǍāĢ‹ āĒĒāǰ āĒĢāǰāĢ€āĒĨāĢ€ āĒĒāĢāǰāĒ•āĢāǰāĒŋāǝāĒž āĒ•āǰā̇ āĒ›ā̇. \"āǰāĢ€āǏā̇āǟ\" āĒĩāǧāĒžāǰāĒžāĒŽāĒžāĒ‚ āǚāĒšā̇āǰāĒžāǍāĒž āǤāĒŽāĒžāĒŽ āĒĩāǰāĢāǤāĒŽāĒžāǍ āĒĄā̇āǟāĒžāǍā̇ āǏāĒžāĒĢ āĒ•āǰā̇ āĒ›ā̇. \"āĒ–āĢ‚āǟāǤāĒž\" āǤā̇āĒĩāĒž āǏāĒ‚āǏāĒžāǧāǍāĢ‹āǍā̇ āĒ•āǤāĒžāǰāĒŽāĒžāĒ‚ āĒŽāĢ‚āĒ•ā̇ āĒ›ā̇ āǜā̇āǍāĒž āĒĒāǰ āĒšāǜā́ āǏā́āǧāĢ€ āĒĒāĢāǰāĒ•āĢāǰāĒŋāǝāĒž āĒ•āǰāĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩāĢ€ āǍāĒĨāĢ€. āǚāĒšā̇āǰāĒžāǍāĢ€ āĒĒāǰāĒ– āĒĒāĢ‚āǰāĢāĒŖ āĒĨāǝāĒž āĒĒāĒ›āĢ€, āĒļāĢ‹āǧāĒžāǝā̇āǞāĒž āǚāĒšā̇āǰāĒžāĒ“āǍā̇ 'āǚāĒšā̇āǰāĒžāǍāĢ€ āĒ“āĒŗāĒ–' āĒŽāĒžāǟā̇ āĒ•āǤāĒžāǰāĒŽāĒžāĒ‚ āĒŽāĢ‚āĒ•āĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩāĒļā̇, āǜā̇ āǤā̇āĒŽāǍā̇ āĒšāĒžāǞāǍāĢ€ āĒ…āĒĨāĒĩāĒž āǍāĒĩāĢ€ āĒĩāĢāǝāĒ•āĢāǤāĒŋāĒ“āǍāĒž āǜāĢ‚āĒĨāĒŽāĒžāĒ‚ āĒĩāĒŋāĒ­āĒžāǜāĒŋāǤ āĒ•āǰāĒļā̇.", "facial_recognition_job_description": "āĒļāĢ‹āǧāĒžāǝā̇āǞāĒž āǚāĒšā̇āǰāĒžāĒ“āǍā̇ āĒĩāĢāǝāĒ•āĢāǤāĒŋāĒ“āǍāĒž āǜāĢ‚āĒĨāĒŽāĒžāĒ‚ āĒĩāĒŋāĒ­āĒžāǜāĒŋāǤ āĒ•āǰāĢ‹. ādž āĒĒāĒ—āǞā́āĒ‚ 'āǚāĒšā̇āǰāĒžāǍāĢ€ āĒĒāǰāĒ–' āĒĒāĢ‚āǰāĢāĒŖ āĒĨāǝāĒž āĒĒāĒ›āĢ€ āĒļāǰāĢ‚ āĒĨāĒžāǝ āĒ›ā̇. \"āǰāĢ€āǏā̇āǟ\" āĒŦāǧāĒž āǚāĒšā̇āǰāĒžāĒ“āǍā́āĒ‚ āĒĢāǰāĢ€āĒĨāĢ€ āǜāĢ‚āĒĨāĢ€āĒ•āǰāĒŖ āĒ•āǰā̇ āĒ›ā̇. \"āĒ–āĢ‚āǟāǤāĒž\" āǤā̇āĒĩāĒž āǚāĒšā̇āǰāĒžāĒ“āǍā̇ āĒ•āǤāĒžāǰāĒŽāĒžāĒ‚ āĒŽāĢ‚āĒ•ā̇ āĒ›ā̇ āǜā̇āĒŽāǍā̇ āĒšāǜā́ āǏā́āǧāĢ€ āĒ•āĢ‹āLj āĒĩāĢāǝāĒ•āĢāǤāĒŋ āĒĢāĒžāĒŗāĒĩāĒĩāĒžāĒŽāĒžāĒ‚ ādžāĒĩāĢ€ āǍāĒĨāĢ€.", "failed_job_command": "ādž āĒ•āĒžāǰāĢāǝ āĒŽāĒžāǟā̇ ādžāĒĻā̇āĒļ {command} āǍāĒŋāǎāĢāĒĢāĒŗ āǰāĒšāĢāǝāĢ‹: {job}", - "force_delete_user_warning": "āǚā̇āǤāĒĩāĒŖāĢ€: ādž āĒĒāĢāǰāĒ•āĢāǰāĒŋāǝāĒž āǤāǰāǤ āǜ āĒĩāĒĒāǰāĒžāĒļāĒ•āǰāĢāǤāĒž āĒ…āǍā̇ āǤāĒŽāĒžāĒŽ āǏāĒ‚āǏāĒžāǧāǍāĢ‹āǍā̇ āĒĻāĢ‚āǰ āĒ•āǰāĢ€ āĒĻā̇āĒļā̇. ādž āǍāĒŋāǰāĢāĒŖāǝ āĒŦāĒĻāǞāĢ€ āĒļāĒ•āĒžāĒļā̇ āǍāĒšāĢ€āĒ‚ āĒ…āǍā̇ āĒĢāĒžāLJāǞāĢ‹āǍā̇ āĒĢāǰāĢ€āĒĨāĢ€ āĒŽā̇āĒŗāĒĩāĢ€ āĒļāĒ•āĒžāĒļā̇ āǍāĒšāĢ€āĒ‚.", - "image_format": "āĒ—āĢ‹āĒ āĒĩāĒŖ", - "image_format_description": "WebP JPEG āĒ•āǰāǤāĒž āǍāĒžāǍāĢ€ āĒĢāĒžāLJāǞāĢ‹ āĒŦāǍāĒžāĒĩā̇ āĒ›ā̇, āĒĒāǰāĒ‚āǤā́ āĒāǍāĢāĒ•āĢ‹āĒĄ āĒ•āǰāĒĩāĒžāĒŽāĒžāĒ‚ āǧāĢ€āĒŽāĢ€ āĒ›ā̇.", - "image_prefer_embedded_preview": "āĒāĒŽāĢāĒŦā̇āĒĄā̇āĒĄ āĒĒāĢ‚āǰāĢāĒĩāĒžāĒĩāǞāĢ‹āĒ•āǍ āĒĒāǏāĒ‚āĒĻ āĒ•āǰāĢ‹", - "image_prefer_wide_gamut": "āĒĒāĒšāĢ‹āĒŗāĒž āĒ…āĒ‚āǤāǰāǍā̇ āĒĒāĢāǰāĒžāǧāĒžāǍāĢāǝ ādžāĒĒāĢ‹", - "image_preview_quality_description": "āĒĒāĢāǰāĢ€āĒĩāĢāǝāĢ‚ āĒ—ā́āĒŖāĒĩāǤāĢāǤāĒž ā̧-ā̧āĢĻāĢĻ āǏā́āǧāĢ€. āljāǚāĢāǚ āĒĩāǧā́ āǏāĒžāǰā́āĒ‚ āĒ›ā̇, āĒĒāǰāĒ‚āǤā́ āǤā̇ āĒŽāĢ‹āǟāĢ€ āĒĢāĒžāLJāǞāĢ‹ āljāǤāĢāĒĒāǍāĢāǍ āĒ•āǰā̇ āĒ›ā̇ āĒ…āǍā̇ āĒāĒĒāĢāǞāĒŋāĒ•ā̇āĒļāǍ āĒĒāĢāǰāǤāĒŋāĒ­āĒžāĒĩ āǘāǟāĒžāĒĄāĢ€ āĒļāĒ•ā̇ āĒ›ā̇. āĒ“āĒ›ā́āĒ‚ āĒŽāĢ‚āǞāĢāǝ āǏā̇āǟ āĒ•āǰāĒĩāĒžāĒĨāĢ€ āĒŽāĒļāĢ€āǍ āǞāǰāĢāǍāĒŋāĒ‚āĒ— āĒ—ā́āĒŖāĒĩāǤāĢāǤāĒž āĒĒāǰ āĒ…āǏāǰ āĒĒāĒĄāĢ€ āĒļāĒ•ā̇ āĒ›ā̇.", - "image_preview_title": "āĒĒāĢ‚āǰāĢāĒĩāĒžāĒĩāǞāĢ‹āĒ•āǍ āǏā̇āǟāĒŋāĒ‚āĒ—āĢāǏ", - "image_progressive": "āĒĒāĢāǰāĒ—āǤāĒŋāĒļāĢ€āǞ" + "force_delete_user_warning": "āǚā̇āǤāĒĩāĒŖāĢ€: ādž āĒĒāĢāǰāĒ•āĢāǰāĒŋāǝāĒž āǤāǰāǤ āǜ āĒĩāĒĒāǰāĒžāĒļāĒ•āǰāĢāǤāĒž āĒ…āǍā̇ āǤāĒŽāĒžāĒŽ āǏāĒ‚āǏāĒžāǧāǍāĢ‹āǍā̇ āĒĻāĢ‚āǰ āĒ•āǰāĢ€ āĒĻā̇āĒļā̇. ādž āǍāĒŋāǰāĢāĒŖāǝ āĒŦāĒĻāǞāĢ€ āĒļāĒ•āĒžāĒļā̇ āǍāĒšāĢ€āĒ‚ āĒ…āǍā̇ āĒĢāĒžāLJāǞāĢ‹āǍā̇ āĒĢāǰāĢ€āĒĨāĢ€ āĒŽā̇āĒŗāĒĩāĢ€ āĒļāĒ•āĒžāĒļā̇ āǍāĒšāĢ€āĒ‚." } } diff --git a/i18n/he.json b/i18n/he.json index 58bb78a9d7..3169c356ec 100644 --- a/i18n/he.json +++ b/i18n/he.json @@ -5,36 +5,44 @@ "acknowledge": "הבנ×Ēי", "action": "פ×ĸולה", "action_common_update": "×ĸדכון", - "action_description": "ץט פ×ĸולו×Ē ×œ×‘×™×Ļו×ĸ ×ĸל קב×Ļים מסוננים", + "action_description": "ץט פ×ĸולו×Ē ×œ×‘×™×Ļו×ĸ ×ĸל נכסים מסוננים", "actions": "פ×ĸולו×Ē", "active": "פ×ĸיל", "active_count": "פ×ĸיל: {count}", "activity": "פ×ĸילו×Ē", + "activity_changed": "הפ×ĸילו×Ē {enabled, select, true {מופ×ĸל×Ē} other {מושב×Ē×Ē}}", "add": "הוספה", "add_a_description": "הוספ×Ē ×Ēיאור", "add_a_location": "הוספ×Ē ×ž×™×§×•×", "add_a_name": "הוספ×Ē ×Š×", "add_a_title": "הוספ×Ē ×›×•×Ēר×Ē", - "add_action": "הוספ×Ē ×¤×ĸולה", - "add_assets": "הוספ×Ē ×Ēמונו×Ē", + "add_action": "×”×•×Ą×Ŗ פ×ĸולה", + "add_action_description": "לח×Ĩ כדי ×œ×”×•×Ą×™×Ŗ פ×ĸולה לבי×Ļו×ĸ", + "add_assets": "×”×•×Ą×Ŗ ×Ēמונו×Ē", "add_birthday": "הוספ×Ē ×™×•× הולד×Ē", "add_endpoint": "הוספ×Ē ×›×Ēוב×Ē ×§×Ļה", "add_exclusion_pattern": "הוספ×Ē ×“×¤×•×Ą החרגה", + "add_filter": "×”×•×Ą×Ŗ סינון", + "add_filter_description": "לח×Ĩ כדי ×œ×”×•×Ą×™×Ŗ ×Ēנאי לסינון", "add_location": "הוספ×Ē ×ž×™×§×•×", + "add_more_users": "הוספ×Ē ×ĸוד מ׊×Ēמשים", "add_partner": "הוספ×Ē ×Š×•×Ē×Ŗ", + "add_path": "הוספ×Ē × ×Ēיב", "add_photos": "הוספ×Ē ×Ēמונו×Ē", - "add_step": "הוספ×Ē ×Š×œ×‘", "add_tag": "הוספ×Ē ×Ēג", "add_to": "הוספה לâ€Ļ", "add_to_album": "הוספה לאלבום", "add_to_album_bottom_sheet_added": "× ×•×Ą×Ŗ ל {album}", "add_to_album_bottom_sheet_already_exists": "כבר ב {album}", "add_to_album_bottom_sheet_some_local_assets": "לא ני×Ēן היה ×œ×”×•×Ą×™×Ŗ חלק מהקב×Ļים המקומיים לאלבום", + "add_to_album_toggle": "החלפ×Ē ×ž×Ļב בחירה ×ĸבור {album}", "add_to_albums": "הוספה לאלבומים", "add_to_albums_count": "×”×•×Ą×Ŗ ({count}) לאלבום", "add_to_bottom_bar": "הוספה אל", + "add_to_shared_album": "הוספה לאלבום משו×Ē×Ŗ", "add_upload_to_stack": "×”×•×Ą×Ŗ א×Ē ×”×”×ĸלאה ל×ĸרימה", "add_url": "הוספ×Ē ×§×™×Š×•×¨", + "add_workflow_step": "הוספ×Ē ×Š×œ×‘ זרימ×Ē ×ĸבודה", "added_to_archive": "× ×•×Ą×Ŗ לארכיון", "added_to_favorites": "× ×•×Ą×Ŗ למו×ĸדפים", "added_to_favorites_count": "{count, number} נוספו למו×ĸדפים", @@ -73,7 +81,6 @@ "cron_expression_description": "הגדר א×Ē ×ž×¨×•×•×— הסריקה באמ×Ļ×ĸו×Ē ×Ēבני×Ē ×”- cron. למיד×ĸ × ×•×Ą×Ŗ נא לפנו×Ē ×œ×ž×Š×œ אל Crontab Guru", "cron_expression_presets": "הגדרו×Ē ×§×‘×•×ĸו×Ē ×ž×¨××Š של ביטוי cron", "disable_login": "השב×Ē ×›× ×™×Ą×”", - "download_csv": "הורד×Ē ×§×•×‘×Ĩ CSV (אקסל)", "duplicate_detection_job_description": "הפ×ĸל למיד×Ē ×ž×›×•× ×” ×ĸל ×Ēמונו×Ē ×›×“×™ לזהו×Ē ×Ēמונו×Ē ×“×•×ž×•×Ē. נ׊×ĸן ×ĸל חיפוש חכם", "exclusion_pattern_description": "דפוסי החרגה מאפשרים לך לה×Ē×ĸלם מקב×Ļים ומ×Ēיקיו×Ē ×‘×ĸ×Ē ×Ą×¨×™×§×Ē ×”×Ą×¤×¨×™×™×” שלך. זה שימושי אם יש לך ×Ēיקיו×Ē ×”×ž×›×™×œ×•×Ē ×§×‘×Ļים שאינך רו×Ļה לייבא, כגון קוב×Ļי RAW.", "export_config_as_json_description": "הורד×Ē ×”×’×“×¨×•×Ē ×”×ž×ĸרכ×Ē ×”× ×•×›×—×™×•×Ē ×›×§×•×‘×Ĩ JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "איכו×Ē ×Ēמונה ממוז×ĸר×Ē ×ž-1 ×ĸד 100. איכו×Ē ×’×‘×•×”×” יו×Ēר היא טובה יו×Ēר, אבל מיי×Ļר×Ē ×§×‘×Ļים גדולים יו×Ēר ויכולה להפחי×Ē ××Ē ×Ēגוב×Ēיו×Ē ×”×™×™×Š×•×.", "image_thumbnail_title": "הגדרו×Ē ×Ēמונה ממוז×ĸר×Ē", "import_config_from_json_description": "ייבוא ×Ē×Ļור×Ē ×ž×ĸרכ×Ē ×‘××ž×Ļ×ĸו×Ē ×§×•×‘×Ĩ ×Ē×Ļורה JSON", - "integrity_checks_checksum_files": "קב×Ļי סיכום ביקור×Ē", - "integrity_checks_checksum_files_description": "הגדר×Ē ×‘×“×™×§×•×Ē ×Ą×™×›×•× ביקור×Ē (checksum)", - "integrity_checks_checksum_files_enable_description": "הפ×ĸל×Ē ×‘×“×™×§×Ē ×Ą×™×›×•× ביקור×Ē", - "integrity_checks_checksum_files_percentage_limit": "מגבלה באחוזים", - "integrity_checks_checksum_files_percentage_limit_description": "הגדר×Ē ××—×•×– מקסימלי בין 0.01 ו־1 לשי×ĸור בדיק×Ē ×Ą×™×›×•× ביקור×Ē ×‘×›×œ פרק זמן.", - "integrity_checks_checksum_files_time_limit": "מגבל×Ē ×–×ž×Ÿ", - "integrity_checks_checksum_files_time_limit_description": "הגדר א×Ē ×ž×Š×š הזמן המרבי שבו בדיק×Ē ×Ą×›×•×ž×™ הביקור×Ē ×Ē×Ēב×Ļ×ĸ בכל מרווח זמן. (אלפיו×Ē ×Š× ×™×™×”)", - "integrity_checks_missing_files": "קב×Ļים חסרים", - "integrity_checks_missing_files_description": "הגדר א×Ē ×”×Ēדירו×Ē ×•×”×¤×ĸל או השב×Ē ××Ē ×‘×“×™×§×Ē ×”×§×‘×Ļים החסרים", - "integrity_checks_missing_files_enable_description": "הפ×ĸל א×Ē ×‘×“×™×§×Ē ×”×§×‘×Ļים החסרים", - "integrity_checks_settings": "בדיק×Ē ×Š×œ×ž×•×Ē", - "integrity_checks_settings_description": "ניהול מרווחי הזמן של בדיקו×Ē ×”×Š×œ×ž×•×Ē", - "integrity_checks_untracked_files": "קב×Ļים לא במ×ĸקב", - "integrity_checks_untracked_files_description": "הגדר×Ē ×”×Ēדירו×Ē ×•×”×¤×ĸל×Ē ××• השב×Ē×Ē ×‘×“×™×§×Ē ×”×§×‘×Ļים שלא במ×ĸקב", - "integrity_checks_untracked_files_enable_description": "הפ×ĸל×Ē ×‘×“×™×§×Ē ×§×‘×Ļים בל×Ēי × ×ĸקבים", "job_concurrency": "בו-זמניו×Ē ×Š×œ {job}", "job_created": "×ĸבודה נו×Ļרה", "job_not_concurrency_safe": "×ĸבודה זו אינה בטוחה להר×Ļה במקביל.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "אפ׊ר חיפוש חכם", "machine_learning_smart_search_enabled_description": "אם מושב×Ē, ×Ēמונו×Ē ×œ× יקודדו לחיפוש חכם.", "machine_learning_url_description": "כ×Ēוב×Ē ×”-URL של ׊ר×Ē ×œ×ž×™×“×Ē ×”×ž×›×•× ×”. אם ני×Ē× ×Ē ×™×•×Ēר מכ×Ēוב×Ē URL אח×Ē, כל ׊ר×Ē ×™× ×•×Ą×” ניסיון אחד בכל פ×ĸם ×ĸד שאחד מהם יגיב בה×Ļלחה, לפי הסדר מהראשון ×ĸד האחרון. ׊ר×Ēים שלא מגיבים יוזנחו זמני×Ē ×ĸד שיחזרו להיו×Ē ×ž×§×•×•× ×™×.", - "maintenance_backup_management": "ניהול גיבוי", "maintenance_delete_backup": "מחק גיבוי", "maintenance_delete_backup_description": "קוב×Ĩ זה ימחק ל×Ļמי×Ēו×Ē.", "maintenance_delete_error": "מחיק×Ē ×”×’×™×‘×•×™ נכשלה.", - "maintenance_integrity_check": "בדיקה", - "maintenance_integrity_check_all": "בדוק הכל", - "maintenance_integrity_checksum_mismatch": "סיכום ביקור×Ē ×œ× ×Ēואם", - "maintenance_integrity_checksum_mismatch_description": "קב×Ļים שסכום הביקור×Ē ×Š×œ×”× בדיסק אינו ×Ēואם לסכום הביקור×Ē ×ŠÖžImmich ׊מר במסד הנ×Ēונים שלו.", - "maintenance_integrity_checksum_mismatch_job": "בדוק אי־ה×Ēאמו×Ē ×‘×Ą×›×•×ž×™ ביקור×Ē", - "maintenance_integrity_checksum_mismatch_refresh_job": "ר×ĸנן דוחו×Ē ××™Öž×”×Ēאמו×Ē ×‘×Ą×›×•×ž×™ ביקור×Ē", - "maintenance_integrity_missing_file": "קב×Ļים חסרים", - "maintenance_integrity_missing_file_description": "קב×Ļים ש‐Immich ×ĸקב אחריהם במסד הנ×Ēונים אך אינם קיימים במ×ĸרכ×Ē ×”×§×‘×Ļים.", - "maintenance_integrity_missing_file_job": "בדוק אם יש קב×Ļים חסרים", - "maintenance_integrity_missing_file_refresh_job": "ר×ĸנון דוחו×Ē ×§×‘×Ļים חסרים", - "maintenance_integrity_report": "דוח ×Ēקינו×Ē", - "maintenance_integrity_untracked_file": "קב×Ļים לא במ×ĸקב", - "maintenance_integrity_untracked_file_description": "קב×Ļים ב×Ēיקיו×Ē ×Š×œ Immich של-Immich אין ×Ēי×ĸוד שלהם.", - "maintenance_integrity_untracked_file_job": "בדיק×Ē ×§×‘×Ļים שאינם במ×ĸקב", - "maintenance_integrity_untracked_file_refresh_job": "ר×ĸנן דוחו×Ē ×§×‘×Ļים לא במ×ĸקב", "maintenance_restore_backup": "שחזור מגיבוי", "maintenance_restore_backup_description": "Immich יימחק וישוחזר מהגיבוי שנבחר. גיבוי ייוו×Ļר לפני ׊×Ēמשיך.", "maintenance_restore_backup_different_version": "גיבוי זה נו×Ļר ×ĸם גרסה שונה של Immich!", @@ -291,20 +267,16 @@ "notification_enable_email_notifications": "אפ׊ר ה×Ēראו×Ē ×“×•×\"ל", "notification_settings": "הגדרו×Ē ×”×Ēראו×Ē", "notification_settings_description": "ניהול הגדרו×Ē ×”×Ēראו×Ē, כולל דוא\"ל", - "oauth_allow_insecure_requests": "אפ׊ר בקשו×Ē ×œ× מאובטחו×Ē", - "oauth_allow_insecure_requests_description": "אזהרה: פ×ĸולה זו מבטל×Ē ××Ē ××™×ž×•×Ē ×Ē×ĸוד×Ē ×”××‘×˜×—×” (TLS) ×ĸבור בקשו×Ē OAuth, ו×ĸלולה ×œ×—×Š×•×Ŗ או×Ēך למ×Ēקפו×Ē â€œ××“× ב×Ēווך” (MITM).", "oauth_auto_launch": "הפ×ĸלה אוטומטי×Ē", - "oauth_auto_launch_description": "ה×Ēחברו×Ē ×‘××ž×Ļ×ĸו×Ē OAuth ×Ē×Ēחיל אוטומטי×Ē ×›×Š× ×›× ×Ą×™× ל×ĸמוד הה×Ēחברו×Ē", - "oauth_auto_register": "הרשמה אוטומטי×Ē", + "oauth_auto_launch_description": "ה×Ēחל א×Ē ×–×¨×™×ž×Ē ×”×”×Ēחברו×Ē ×Š×œ OAuth באופן אוטומטי ×ĸם הניווט ×œ×“×Ŗ הה×Ēחברו×Ē", + "oauth_auto_register": "רישום אוטומטי", "oauth_auto_register_description": "רשום אוטומטי×Ē ×ž×Š×Ēמשים חדשים לאחר כניסה ×ĸם OAuth", "oauth_button_text": "טקץט לח×Ļן", "oauth_client_secret_description": "נדרש ×ĸבור לקוח סודי, או אם PKCE (מפ×Ēח הוכחה להחלפ×Ē ×§×•×“) אינו × ×Ēמך ×ĸבור לקוח ×Ļיבורי.", "oauth_enable_description": "ה×Ēחבר ×ĸם OAuth", - "oauth_end_session_url_description": "הפנה א×Ē ×”×ž×Š×Ēמ׊ ל-URI זה כאשר הוא מ×Ē× ×Ē×§.", "oauth_mobile_redirect_uri": "URI להפניה מחדש בנייד", "oauth_mobile_redirect_uri_override": "×ĸקיפ×Ē URI להפניה מחדש בנייד", "oauth_mobile_redirect_uri_override_description": "אפ׊ר כאשר ץפק OAuth לא מאפ׊ר כ×Ēוב×Ē URI לנייד, כמו ''{callback}''", - "oauth_prompt_description": "פרמטר בקשה (למשל select_account, login, consent)", "oauth_role_claim": "דריש×Ē ×Ēפקיד", "oauth_role_claim_description": "ה×ĸ× ×§ גיש×Ē ×ž× ×”×œ באופן אוטומטי אם ×Ēבי×ĸה זו קיימ×Ē. ×ĸרך ה×Ēבי×ĸה יכול להיו×Ē 'user' או 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "מר×ĸנן א×Ē ×›×œ הספריו×Ē", "registration": "רישום מנהל מ×ĸרכ×Ē", "registration_description": "מכיוון ׊א×Ēה המש×Ēמ׊ הראשון במ×ĸרכ×Ē, א×Ēה ×Ēוק×Ļה כמנהל וא×Ēה אחראי ×ĸל משימו×Ē × ×™×”×•×œ, ומש×Ēמשים נוספים ייוו×Ļרו ×ĸל ידך.", - "release_channel_release_candidate": "מו×ĸמד לשחרור", - "release_channel_stable": "י×Ļיב", "remove_failed_jobs": "הסר×Ē ×ž×Š×™×ž×•×Ē ×Š× ×›×Š×œ×•", "require_password_change_on_login": "דרוש מהמש×Ēמ׊ לשנו×Ē ×Ą×™×Ą×ž×” בכניסה הראשונה", "reset_settings_to_default": "אפץ הגדרו×Ē ×œ×‘×¨×™×¨×Ē ×”×ž×—×“×œ", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "חל רק ×ĸל VAAPI ו-QSV. מגדיר א×Ē ×Ļומ×Ē ×”-dri המשמש להמר×Ē ×§×™×“×•×“ של חומרה.", "transcoding_preset_preset": "הגדרו×Ē ×§×‘×•×ĸו×Ē ×ž×¨××Š (-preset)", "transcoding_preset_preset_description": "מהירו×Ē ×“×—×™×Ą×”. הגדרו×Ē ×§×‘×•×ĸו×Ē ×ž×¨××Š איטיו×Ē ×™×•×Ēר מיי×Ļרו×Ē ×§×‘×Ļים קטנים יו×Ēר, ומגבירו×Ē ××Ē ×”××™×›×•×Ē ×›××Š×¨ מכוונים לק×Ļב סיביו×Ē ×ž×Ą×•×™×. VP9 מ×Ē×ĸלם ממהירויו×Ē ×ž×ĸל 'מהיר יו×Ēר'.", - "transcoding_realtime": "קידוד בזמן אמ×Ē [ניסיוני]", - "transcoding_realtime_description": "אפ׊ר קידוד בזמן אמ×Ē ×‘×–×ž×Ÿ שהסרטון משודר‮. מאפ׊ר החלפ×Ē ××™×›×•×Ē, אך ×ĸלול לגרום להשהיו×Ē ×•×’×ž×’×•×ž×™× בה×Ēאם ליכולו×Ē ×”×Š×¨×Ē.", - "transcoding_realtime_enabled": "הפ×ĸל קידוד בזמן אמ×Ē", - "transcoding_realtime_enabled_description": "אם אפשרו×Ē ×–×• מושב×Ē×Ē, השר×Ē ×™×Ą×¨×‘ לה×Ēחיל הפ×ĸלו×Ē ×—×“×Š×•×Ē ×Š×œ המרה בזמן אמ×Ē.", - "transcoding_realtime_resolutions": "רזולו×Ļיו×Ē", - "transcoding_realtime_resolutions_description": "הרזולו×Ļיו×Ē ×”×–×ž×™× ×•×Ē ×œ×”×ž×¨×” בזמן אמ×Ē. רזולו×Ļיו×Ē ×’×‘×•×”×•×Ē ×™×•×Ēר ×ĸלולו×Ē ×œ×’×¨×•× לב×ĸיו×Ē ×‘× ×™×’×•×Ÿ אם השר×Ē ××™× ×• מסוגל לב×Ļ×ĸ א×Ē ×”×”×ž×¨×” במהירו×Ē ×ž×Ą×¤×§×Ē.", - "transcoding_realtime_video_codecs": "מקודדי וידאו", - "transcoding_realtime_video_codecs_description": "מקודדי הווידאו הזמינים לטרנסקודינג בזמן אמ×Ē. יישומי הק×Ļה יבחרו במהלך ההפ×ĸלה א×Ē ×”××¤×Š×¨×•×Ē ×”×˜×•×‘×” ביו×Ēר שבה הם ×Ēומכים. AV1 י×ĸיל יו×Ēר מ־HEVC, א׊ר י×ĸיל יו×Ēר מ־H.264. ב×ĸ×Ē ×Š×™×ž×•×Š בהא×Ļ×Ē ×—×•×ž×¨×”, יש לבחור רק א×Ē ×”×ž×§×•×“×“×™× שבהם מאי×Ĩ החומרה מסוגל לב×Ļ×ĸ קידוד. ב×ĸ×Ē ×Š×™×ž×•×Š בטרנסקודינג באמ×Ļ×ĸו×Ē ×Ēוכנה, יש לשים לב ׊֞H.264 מהיר יו×Ēר מ־AV1, א׊ר מהיר יו×Ēר מ־HEVC.", "transcoding_reference_frames": "פריימים לייחוס", "transcoding_reference_frames_description": "מספר הפריימים לייחוס ב×ĸ×Ē ×“×—×™×Ą×” של פריים × ×Ēון. ×ĸרכים גבוהים יו×Ēר משפרים א×Ē ×™×ĸילו×Ē ×”×“×—×™×Ą×”, אך מאטים א×Ē ×”×§×™×“×•×“. 0 מגדיר א×Ē ×”×ĸרך זה באופן אוטומטי.", "transcoding_required_description": "רק סרטונים שאינם בפורמט מקובל", @@ -478,8 +440,6 @@ "user_settings_description": "ניהול הגדרו×Ē ×ž×Š×Ēמ׊", "user_successfully_removed": "המש×Ēמ׊ {email} הוסר בה×Ļלחה.", "users_page_description": "×ĸמוד ניהול מ׊×Ēמשים", - "version_check_channel": "×ĸרו×Ĩ הפ×Ļה", - "version_check_channel_description": "בחר א×Ē ×ĸרו×Ĩ ההפ×Ļה ׊×ĸבורו בר×Ļונך לקבל הוד×ĸו×Ē ×ĸל גרסאו×Ē ×—×“×Š×•×Ē", "version_check_enabled_description": "אפ׊ר בדיק×Ē ×’×¨×Ą×”", "version_check_implications": "×Ēכונ×Ē ×‘×“×™×§×Ē ×”×’×¨×Ą×” מס×Ēמכ×Ē ×ĸל ×Ēקשור×Ē ×Ēקופ×Ēי×Ē ×ĸם {server}", "version_check_settings": "בדיק×Ē ×’×¨×Ą×”", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "נקה קב×Ļי מטמון", "advanced_settings_clear_image_cache_error": "לא ני×Ēן היה לנקו×Ē ××Ē ×§×‘×Ļי המטמון", "advanced_settings_clear_image_cache_success": "נוקה בה×Ļלחה {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "הש×Ēמ׊ באפשרו×Ē ×–×• כדי לסנן מדיה במהלך הסנכרון לפי קריטריונים חלופיים. מומל×Ĩ להש×Ēמ׊ בזה רק אם יש ב×ĸיה בזיהוי כל האלבומים באפליק×Ļיה.", + "advanced_settings_enable_alternate_media_filter_title": "[ניסיוני] הש×Ēמ׊ במסנן סנכרון אלבום חלופי שמבכשיר", "advanced_settings_log_level_title": "רמ×Ē ×¨×™×Š×•× ביומן: {level}", "advanced_settings_prefer_remote_subtitle": "במכשירים מסוימים ט×ĸינ×Ē ×Ēמונו×Ē ×ž×ž×•×–×ĸרו×Ē ×ž×§×‘×Ļים מקומיים ×ĸלולה להיו×Ē ××™×˜×™×Ē ×‘×ž×™×•×—×“. הפ×ĸל הגדרה זו כדי לט×ĸון ×Ēמונו×Ē ×ž×¨×•×—×§×•×Ē ×‘×ž×§×•× זא×Ē.", "advanced_settings_prefer_remote_title": "ה×ĸדת ×Ēמונו×Ē ×ž×¨×•×—×§×•×Ē", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "כו×Ēרו×Ē ×¤×¨×•×§×Ą×™ [ניסיוני]", "advanced_settings_readonly_mode_subtitle": "מאפ׊ר א×Ē ×ž×Ļב לקריאה בלבד בו ה×Ēמונו×Ē × ×™×Ēנו×Ē ×œ×Ļפייה בלבד, דברים כמו בחיר×Ē ×Ēמונו×Ē ×ž×¨×•×‘×•×Ē, שי×Ēות, שידור, מחיקה הם כולם מושב×Ēים. אפ׊ר/השב×Ē ×ž×Ļב לקריאה בלבד באמ×Ļ×ĸו×Ē ×™×Ļגן המש×Ēמ׊ מהמסך הראשי", "advanced_settings_readonly_mode_title": "מ×Ļב קריאה בלבד", + "advanced_settings_self_signed_ssl_subtitle": "מדלג ×ĸל אימו×Ē ×Ē×ĸוד×Ē SSL ×ĸבור כ×Ēוב×Ē URL של השר×Ē. דרוש ×ĸבור ×Ē×ĸודו×Ē ×‘×—×Ēימה ×ĸ×Ļמי×Ē.", + "advanced_settings_self_signed_ssl_title": "ה×Ēר ×Ē×ĸודו×Ē SSL בח×Ēימה ×ĸ×Ļמי×Ē [ניסיוני]", "advanced_settings_sync_remote_deletions_subtitle": "מחק או שחזר ×Ēמונה במכשיר זה באופן אוטומטי כאשר פ×ĸולה זו × ×ĸשי×Ē ×‘×“×¤×“×¤×Ÿ", "advanced_settings_sync_remote_deletions_title": "סנכרן מחיקו×Ē ×Š×‘×•×Ļ×ĸו במכשירים אחרים [נסיוני]", "advanced_settings_tile_subtitle": "הגדרו×Ē ×ž×Š×Ēמ׊ מ×Ēקדם", @@ -516,18 +480,31 @@ "album_delete_confirmation": "האם באמ×Ē ×‘×¨×Ļונך למחוק א×Ē ×”××œ×‘×•× {album}?", "album_delete_confirmation_description": "אם האלבום הזה משו×Ē×Ŗ, מ׊×Ēמשים אחרים לא יוכלו לגש×Ē ××œ×™×• יו×Ēר.", "album_deleted": "אלבום נמחק", + "album_info_card_backup_album_excluded": "הוחרגו", + "album_info_card_backup_album_included": "נכללו", "album_info_updated": "מיד×ĸ האלבום ×ĸודכן", + "album_leave": "ל×ĸזוב אלבום?", + "album_leave_confirmation": "האם באמ×Ē ×‘×¨×Ļונך ל×ĸזוב א×Ē {album}?", "album_name": "׊ם האלבום", "album_options": "אפשרויו×Ē ×”××œ×‘×•×", "album_remove_user": "להסיר מ׊×Ēמ׊?", "album_remove_user_confirmation": "האם באמ×Ē ×‘×¨×Ļונך להסיר א×Ē {user}?", "album_search_not_found": "לא נמ×Ļאו אלבומים ה×Ēואמים לחיפוש שלך", + "album_selected": "אלבום נבחר", "album_share_no_users": "נראה ששי×Ēפ×Ē ××Ē ×”××œ×‘×•× הזה ×ĸם כל המש×Ēמשים או שאין לך את מ׊×Ēמ׊ לש×Ē×Ŗ אי×Ēו.", "album_summary": "×Ē×§×Ļיר אלבום", "album_updated": "אלבום ×ĸודכן", "album_updated_setting_description": "קבל הוד×ĸ×Ē ×“×•×\"ל כאשר לאלבום משו×Ē×Ŗ יש ×Ēמונו×Ē ×—×“×Š×•×Ē", "album_upload_assets": "ה×ĸלה ×Ēמונו×Ē ×ž×”×ž×—×Š×‘ שלך ×•×”×•×Ą×Ŗ לאלבום", + "album_user_left": "×ĸזב א×Ē {album}", + "album_user_removed": "{user} הוסר", + "album_viewer_appbar_delete_confirm": "האם א×Ē/ה בטוח/ה שבר×Ļונך למחוק א×Ē ×”××œ×‘×•× הזה מהחשבון שלך?", "album_viewer_appbar_share_err_delete": "מחיק×Ē ××œ×‘×•× נכשלה", + "album_viewer_appbar_share_err_leave": "×ĸזיב×Ē ×”××œ×‘×•× נכשלה", + "album_viewer_appbar_share_err_remove": "יש ב×ĸיו×Ē ×‘×”×Ą×¨×Ē ×”×Ēמונו×Ē ×ž×”××œ×‘×•×", + "album_viewer_appbar_share_err_title": "נכשל בשינוי כו×Ēר×Ē ×”××œ×‘×•×", + "album_viewer_appbar_share_leave": "×ĸזוב אלבום", + "album_viewer_appbar_share_to": "׊×Ē×Ŗ ×ĸם", "album_viewer_page_share_add_users": "×”×•×Ą×Ŗ מ׊×Ēמשים", "album_with_link_access": "אפ׊ר לכל אחד ×ĸם הקישור לראו×Ē ×Ēמונו×Ē ×•×× ×Š×™× באלבום הזה.", "albums": "אלבומים", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "סדר מיון ×Ēמונו×Ē ×¨××Š×•× ×™ ב×ĸ×Ē ×™×Ļיר×Ē ××œ×‘×•×ž×™× חדשים.", "albums_feature_description": "אוספים של ×Ēמונו×Ē ××Š×¨ ני×Ēנים לשי×Ēות ×ĸם מ׊×Ēמשים אחרים.", "albums_on_device_count": "אלבומים במכשיר ({count})", + "albums_selected": "{count, plural, one {אלבום אחד נבחר} other {# אלבומים נבחרו}}", "all": "הכל", "all_albums": "כל האלבומים", "all_people": "כל האנשים", "all_photos": "כל ה×Ēמונו×Ē", "all_videos": "כל הסרטונים", "allow_dark_mode": "אפ׊ר מ×Ļב כהה", + "allow_edits": "אפ׊ר ×ĸריכו×Ē", "allow_public_user_to_download": "אפ׊ר למש×Ēמ׊ ×Ļיבורי להוריד", "allow_public_user_to_upload": "אפ׊ר למש×Ēמ׊ ×Ļיבורי לה×ĸלו×Ē", "allowed": "מורשה", @@ -549,12 +528,14 @@ "always_keep": "שמור ×Ēמיד", "always_keep_photos_hint": "פינוי מקום ישמור א×Ē ×›×œ ה×Ēמונו×Ē ×‘×ž×›×Š×™×¨ זה.", "always_keep_videos_hint": "פינוי מקום ישמור א×Ē ×›×œ הסרטונים במכשיר זה.", + "anti_clockwise": "נגד כיוון הש×ĸון", "api_key": "מפ×Ēח API", "api_key_description": "ה×ĸרך הזה יו×Ļג רק פ×ĸם אח×Ē. נא לוודא שה×ĸ×Ē×§×Ē ××•×Ēו לפני סגיר×Ē ×”×—×œ×•×Ÿ.", "api_key_empty": "מפ×Ēח ה-API שלך לא אמור להיו×Ē ×¨×™×§", "api_keys": "מפ×Ēחו×Ē API", "app_architecture_variant": "וריאנט (ארכיטקטורה)", "app_bar_signout_dialog_content": "האם א×Ē/ה בטוח/ה שבר×Ļונך לה×Ē× ×Ē×§?", + "app_bar_signout_dialog_ok": "כן", "app_bar_signout_dialog_title": "ה×Ē× ×Ē×§", "app_download_links": "קישורים להורד×Ē ×”××¤×œ×™×§×Ļיה", "app_settings": "הגדרו×Ē ×™×™×Š×•×", @@ -565,18 +546,27 @@ "archive": "ארכיון", "archive_action_prompt": "{count} נוספו לארכיון", "archive_or_unarchive_photo": "ה×ĸבר ×Ēמונה לארכיון או הו×Ļא או×Ēה מ׊ם", + "archive_page_no_archived_assets": "לא נמ×Ļאו ×Ēמונו×Ē ×‘××¨×›×™×•×Ÿ", + "archive_page_title": "בארכיון ({count})", "archive_size": "גודל הארכיון", "archive_size_description": "הגדר א×Ē ×’×•×“×œ הארכיון להורדו×Ē (ב-GiB)", "archived": "בארכיון", "archived_count": "{count, plural, other {# הו×ĸברו לארכיון}}", "are_these_the_same_person": "האם אלה או×Ēו האדם?", "are_you_sure_to_do_this": "האם באמ×Ē ×‘×¨×Ļונך ל×ĸשו×Ē ××Ē ×–×”?", + "array_field_not_fully_supported": "שדו×Ē ×”×ž×ĸרך דורשים ×ĸריכה ידני×Ē ×Š×œ ה-JSON", + "asset_action_delete_err_read_only": "לא ני×Ēן למחוק ×Ēמונו×Ē ×œ×§×¨×™××” בלבד, מדלג", + "asset_action_share_err_offline": "לא ני×Ēן להשיג ×Ēמונו×Ē ×œ× מקוונו×Ē, מדלג", "asset_added_to_album": "× ×•×Ą×Ŗ לאלבום", "asset_adding_to_album": "×ž×•×Ą×™×Ŗ לאלבוםâ€Ļ", "asset_created": "×Ēמונה נו×Ļרה", "asset_description_updated": "×Ēיאור ה×Ēמונה ×ĸודכן", + "asset_filename_is_offline": "ה×Ēמונה {filename} אינה מקוונ×Ē", + "asset_has_unassigned_faces": "ל×Ēמונה יש פנים שלא הוק×Ļו", "asset_hashing": "מגבבâ€Ļ", "asset_list_group_by_sub_title": "קב×Ĩ לפי", + "asset_list_layout_settings_dynamic_layout_title": "פריסה דינמי×Ē", + "asset_list_layout_settings_group_automatically": "אוטומטי", "asset_list_layout_settings_group_by": "קב×Ĩ ×Ēמונו×Ē ×œ×¤×™", "asset_list_layout_settings_group_by_month_day": "חודש + יום", "asset_list_layout_sub_title": "פריסה", @@ -587,27 +577,36 @@ "asset_not_found_on_icloud": "המדיה לא נמ×Ļאה ב-iCloud. יי×Ēכן שהמדיה אינה נגישה ×ĸקב קוב×Ĩ פגום המאוחסן ב-iCloud", "asset_offline": "×Ēמונה לא מקוונ×Ē", "asset_offline_description": "ה×Ēמונה החי×Ļוני×Ē ×”×–××Ē ×›×‘×¨ לא נמ×Ļא×Ē ×‘×“×™×Ą×§. נא לי×Ļור ק׊ר ×ĸם מנהל Immich שלך לקבל×Ē ×ĸזרה.", + "asset_restored_successfully": "×Ēמונה שוחזרה בה×Ļלחה", "asset_skipped": "דילג", "asset_skipped_in_trash": "באשפה", + "asset_trashed": "ה×Ēמונה הו×ĸברה לאשפה", "asset_troubleshoot": "פ×Ēרון ב×ĸיו×Ē ×‘×Ēמונו×Ē", "asset_uploaded": "הו×ĸלה", "asset_uploading": "מ×ĸלהâ€Ļ", "asset_viewer_settings_subtitle": "ניהול הגדרו×Ē ×ž×Ļיג הגלריה שלך", "asset_viewer_settings_title": "מ×Ļיג ה×Ēמונו×Ē", "assets": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™×", + "assets_added_count": "{count, plural, one {נוספה ×Ēומנה #} other {נוספו # ×Ēמונו×Ē}}", "assets_added_to_album_count": "{count, plural, one {נוספה ×Ēמונה #} other {נוספו # ×Ēמונו×Ē}} לאלבום", - "assets_added_to_album_partial_count": "נוספו {successCount} מ×Ēוך {totalCount} {totalCount, plural, one {×Ēמונה} other {×Ēמונו×Ē}} לאלבום", "assets_added_to_albums_count": "{assetTotal, plural, one {× ×•×Ą×Ŗ פריט #} other {נוספו # פריטים}} אל {albumTotal, plural, one {אלבום #} other {# אלבומים}}", "assets_cannot_be_added_to_album_count": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ א×Ē ×”{count, plural, one {×Ēמונה} other {×Ēמונו×Ē}} לאלבום", "assets_cannot_be_added_to_albums": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ {count, plural, one {פריט} other {פריטים}} לאת אחד מהאלבומים", "assets_count": "{count, plural, one {×Ēמונה #} other {# ×Ēמונו×Ē}}", + "assets_deleted_permanently": "{count} ×Ēמונו×Ē × ×ž×—×§×• ל×Ļמי×Ēו×Ē", + "assets_deleted_permanently_from_server": "{count} ×Ēמונו×Ē × ×ž×—×§×• ל×Ļמי×Ēו×Ē ×ž×Š×¨×Ē ×”-Immich", + "assets_downloaded_failed": "{count, plural, one {הורד קוב×Ĩ # - קוב×Ĩ {error} נכשל} other {ירדו # קב×Ļים - {error} קב×Ļים נכשלו}}", + "assets_downloaded_successfully": "{count, plural, one {קוב×Ĩ # ירד בה×Ļלחה} other {# קב×Ļים ירדו בה×Ļלחה}}", "assets_moved_to_trash_count": "{count, plural, one {×Ēמונה # הו×ĸברה} other {# ×Ēמונו×Ē ×”×•×ĸברו}} לאשפה", "assets_permanently_deleted_count": "{count, plural, one {×Ēמונה # נמחקה} other {# ×Ēמונו×Ē × ×ž×—×§×•}} ל×Ļמי×Ēו×Ē", "assets_removed_count": "{count, plural, one {×Ēמונה # הוסרה} other {# ×Ēמונו×Ē ×”×•×Ą×¨×•}}", + "assets_removed_permanently_from_device": "{count} ×Ēמונו×Ē × ×ž×—×§×• ל×Ļמי×Ēו×Ē ×ž×”×ž×›×Š×™×¨ שלך", "assets_restore_confirmation": "האם באמ×Ē ×‘×¨×Ļונך לשחזר א×Ē ×›×œ ה×Ēמונו×Ē ×Š×‘××Š×¤×”? אין באפשרו×Ēך לבטל א×Ē ×”×¤×ĸולה הזו! יש לשים לב שלא ני×Ēן לשחזר ×Ēמונו×Ē ×œ× מקוונו×Ē ×‘×“×¨×š זו.", "assets_restored_count": "{count, plural, one {×Ēמונה # שוחזרה} other {# ×Ēמונו×Ē ×Š×•×—×–×¨×•}}", + "assets_restored_successfully": "{count} ×Ēמונו×Ē ×Š×•×—×–×¨×• בה×Ļלחה", "assets_trashed": "{count} ×Ēמונו×Ē ×”×•×ĸברו לאשפה", "assets_trashed_count": "{count, plural, one {×Ēמונה # הושלכה} other {# ×Ēמונו×Ē ×”×•×Š×œ×›×•}} לאשפה", + "assets_trashed_from_server": "{count} ×Ēמונו×Ē ×”×•×ĸברו לאשפה מהשר×Ē", "assets_were_part_of_album_count": "{count, plural, one {×Ēמונה היי×Ēה} other {×Ēמונו×Ē ×”×™×•}} כבר חלק מהאלבום", "assets_were_part_of_albums_count": "{count, plural, one {הפריט כבר היה} other {הפריטים כבר היו}} חלק מהאלבומים", "authorized_devices": "מכשירים מורשים", @@ -616,48 +615,87 @@ "autoplay_slideshow": "מ×Ļג×Ē ×Ēמונו×Ē ××•×˜×•×ž×˜×™×Ē", "back": "חזרה", "back_close_deselect": "חזור, סגור, או בטל בחירה", + "background_backup_running_error": "גיבוי ברק×ĸ פו×ĸל כ×ĸ×Ē, לא ני×Ēן להפ×ĸיל גיבוי ידני", "background_location_permission": "הרשא×Ē ×ž×™×§×•× ברק×ĸ", "background_location_permission_content": "כדי ×œ×”×—×œ×™×Ŗ ר׊×Ēו×Ē ×‘×ĸ×Ē ×¨×™×Ļה ברק×ĸ, היישום ×Ļריך *×Ēמיד* גישה למיקום מדויק ×ĸל מנ×Ē ×œ×§×¨×•× א×Ē ×”×Š× של ר׊×Ē ×”××™× ×˜×¨× ×˜ האלחוטי", "background_options": "אפשרויו×Ē ×¨×§×ĸ", "backup": "גיבוי", + "backup_album_selection_page_albums_device": "({count}) אלבומים במכשיר", "backup_album_selection_page_albums_tap": "הקש כדי לכלול, הקש פ×ĸמיים כדי להחריג", "backup_album_selection_page_assets_scatter": "×Ēמונו×Ē ×™×›×•×œ×•×Ē ×œ×”×Ēפזר ×ĸל פני אלבומים מרובים. לפיכך, ני×Ēן לכלול או להחריג אלבומים במהלך ×Ēהליך הגיבוי.", "backup_album_selection_page_select_albums": "בחיר×Ē ××œ×‘×•×ž×™×", "backup_album_selection_page_selection_info": "פרטי בחירה", + "backup_album_selection_page_total_assets": "סה״כ ×Ēמונו×Ē ×™×™×—×•×“×™×•×Ē", "backup_albums_sync": "סנכרון אלבומי גיבוי", + "backup_all": "הכל", + "backup_background_service_backup_failed_message": "נכשל בגיבוי ×Ēמונו×Ē. מנסה שובâ€Ļ", "backup_background_service_complete_notification": "גיבוי הנכסים הושלם", + "backup_background_service_connection_failed_message": "נכשל בה×Ēחברו×Ē ×œ×Š×¨×Ē. מנסה שובâ€Ļ", + "backup_background_service_current_upload_notification": "מ×ĸלה {filename}", "backup_background_service_default_notification": "מחפש ×Ēמונו×Ē ×—×“×Š×•×Ēâ€Ļ", + "backup_background_service_error_title": "שגיא×Ē ×’×™×‘×•×™", "backup_background_service_in_progress_notification": "מגבה א×Ē ×”×Ēמונו×Ē ×Š×œ×šâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} נכשל בה×ĸלאה", "backup_controller_page_albums": "אלבומים לגיבוי", + "backup_controller_page_background_app_refresh_disabled_content": "אפ׊ר ר×ĸנון אפליק×Ļיה ברק×ĸ בהגדרו×Ē > כללי > ר×ĸנון אפליק×Ļיה ברק×ĸ כדי להש×Ēמ׊ בגיבוי ברק×ĸ.", + "backup_controller_page_background_app_refresh_disabled_title": "ר×ĸנון אפליק×Ļיה ברק×ĸ מושב×Ē", + "backup_controller_page_background_app_refresh_enable_button_text": "לך להגדרו×Ē", "backup_controller_page_background_battery_info_link": "הראה לי איך", "backup_controller_page_background_battery_info_message": "×ĸבור חווי×Ē ×’×™×‘×•×™ ברק×ĸ הטובה ביו×Ēר, נא להשבי×Ē ××Ē ×›×œ מיטובי הסוללה המגבילים פ×ĸילו×Ē ×‘×¨×§×ĸ ×ĸבור היישום.\n\nמכיוון שזה ×Ēלוי מכשיר, בבקשה חפש/י א×Ē ×”×ž×™×“×ĸ הנדרש ×ĸבור י×Ļרן המכשיר שלך.", "backup_controller_page_background_battery_info_ok": "בסדר", "backup_controller_page_background_battery_info_title": "מיטובי סוללה", + "backup_controller_page_background_charging": "רק בט×ĸינה", + "backup_controller_page_background_configure_error": "נכשל בהגדר×Ē ×Ē×Ļור×Ē ×Š×™×¨×•×Ē ×”×¨×§×ĸ", "backup_controller_page_background_delay": "השהה גיבוי של ×Ēמונו×Ē ×—×“×Š×•×Ē: {duration}", + "backup_controller_page_background_description": "הפ×ĸל א×Ē ×”×Š×™×¨×•×Ē ×¨×§×ĸ כדי לגבו×Ē ×‘××•×¤×Ÿ אוטומטי כל ×Ēמונה חדשה מבלי לה×Ļטרך לפ×Ēוח א×Ē ×”×™×™×Š×•×", + "backup_controller_page_background_is_off": "גיבוי אוטומטי ברק×ĸ כבוי", + "backup_controller_page_background_is_on": "גיבוי אוטומטי ברק×ĸ מופ×ĸל", + "backup_controller_page_background_turn_off": "כבה שירו×Ē ×’×™×‘×•×™ ברק×ĸ", + "backup_controller_page_background_turn_on": "הפ×ĸל שירו×Ē ×’×™×‘×•×™ ברק×ĸ", + "backup_controller_page_background_wifi": "רק ברש×Ē ××œ×—×•×˜×™×Ē", "backup_controller_page_backup": "גיבוי", "backup_controller_page_backup_selected": "נבחרו: ", "backup_controller_page_backup_sub": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× מגובים", + "backup_controller_page_created": "נו×Ļר ב: {date}", + "backup_controller_page_desc_backup": "הפ×ĸל גיבוי חזי×Ē ×›×“×™ לה×ĸלו×Ē ×‘××•×¤×Ÿ אוטומטי ×Ēמונו×Ē ×—×“×Š×•×Ē ×œ×Š×¨×Ē ×›×Š×¤×•×Ēחים א×Ē ×”×™×™×Š×•×.", "backup_controller_page_excluded": "הוחרגו: ", + "backup_controller_page_failed": "({count}) נכשלו", + "backup_controller_page_filename": "׊ם הקוב×Ĩ: {size} [{filename}]", + "backup_controller_page_id": "מזהה: {id}", + "backup_controller_page_info": "פרטי גיבוי", "backup_controller_page_none_selected": "אין בחירה", "backup_controller_page_remainder": "בהמ×Ēנה לגיבוי", "backup_controller_page_remainder_sub": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× הנו×Ēרים לגיבוי מ×Ēוך בחירה", "backup_controller_page_server_storage": "אחסון בשר×Ē", + "backup_controller_page_start_backup": "ה×Ēחל גיבוי", + "backup_controller_page_status_off": "גיבוי חזי×Ē ××•×˜×•×ž×˜×™ כבוי", + "backup_controller_page_status_on": "גיבוי חזי×Ē ××•×˜×•×ž×˜×™ מופ×ĸל", "backup_controller_page_storage_format": "{used} מ×Ēוך {total} בשימוש", "backup_controller_page_to_backup": "אלבומים לגבו×Ē", "backup_controller_page_total_sub": "כל ה×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× הייחודיים מאלבומים שנבחרו", + "backup_controller_page_turn_off": "כיבוי גיבוי חזי×Ē", + "backup_controller_page_turn_on": "הפ×ĸל גיבוי חזי×Ē", + "backup_controller_page_uploading_file_info": "מ×ĸלה מיד×ĸ ×ĸל הקוב×Ĩ", + "backup_err_only_album": "לא ני×Ēן להסיר א×Ē ×”××œ×‘×•× היחיד", "backup_error_sync_failed": "הסינכרון נכשל. לא ני×Ēן להשלים א×Ē ×”×’×™×‘×•×™.", "backup_info_card_assets": "×Ēמונו×Ē", + "backup_manual_cancelled": "בוטל", + "backup_manual_in_progress": "ה×ĸלאה כבר ב×Ēהליך. נסה אחרי זמן מה", + "backup_manual_success": "ה×Ļלחה", + "backup_manual_title": "מ×Ļב ה×ĸלאה", "backup_options": "אפשרויו×Ē ×’×™×‘×•×™", + "backup_options_page_title": "אפשרויו×Ē ×’×™×‘×•×™", + "backup_setting_subtitle": "ניהול הגדרו×Ē ×”×ĸלא×Ē ×¨×§×ĸ וחזי×Ē", "backup_settings_subtitle": "נהל הגדרו×Ē ×”×ĸלאה", + "backup_upload_details_page_more_details": "הקש לפרטים נוספים", "backward": "אחורה", - "battery_optimization_backup_reliability": "השב×Ē×Ē ×ž×™×˜×•×‘ ×Ļריכ×Ē ×”×Ą×•×œ×œ×” ×ĸשויה לשפר א×Ē ×”××ž×™× ×•×Ē ×Š×œ גיבוי ברק×ĸ", "biometric_auth_enabled": "אימו×Ē ×‘×™×•×ž×˜×¨×™ הופ×ĸל", "biometric_locked_out": "גישה לאימו×Ē ×”×‘×™×•×ž×˜×¨×™ נחסמה", "biometric_no_options": "אין אפשרויו×Ē ×–×ž×™× ×•×Ē ×ĸבור אימו×Ē ×‘×™×•×ž×˜×¨×™", "biometric_not_available": "אימו×Ē ×‘×™×•×ž×˜×¨×™ אינו זמין במכשיר זה", + "birthdate_saved": "×Ēאריך לידה נ׊מר בה×Ļלחה", "birthdate_set_description": "×Ēאריך לידה מ׊מ׊ לחישוב הגיל של האדם הזה בזמן ×Ē×Ļלום.", "blurred_background": "רק×ĸ מטושטש", - "browse_templates": "×ĸיון ב×Ēבניו×Ē", "bugs_and_feature_requests": "באגים & בקשו×Ē ×œ×Ēכונו×Ē", "build": "גרס×Ē ×‘× ×™×™×”", "build_image": "גרס×Ē ×Ēוכנה", @@ -665,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "האם באמ×Ē ×‘×¨×Ļונך להשאיר {count, plural, one {×Ēמונה # כפולה} other {# ×Ēמונו×Ē ×›×¤×•×œ×•×Ē}}? זה יסגור א×Ē ×›×œ הקבו×Ļו×Ē ×”×›×¤×•×œ×•×Ē ×ž×‘×œ×™ למחוק דבר.", "bulk_trash_duplicates_confirmation": "האם באמ×Ē ×‘×¨×Ļונך לה×ĸביר לאשפה בכמו×Ē ×’×“×•×œ×” {count, plural, one {×Ēמונה # כפולה} other {# ×Ēמונו×Ē ×›×¤×•×œ×•×Ē}}? זה ישמור ×ĸל ה×Ēמונה הגדולה ביו×Ēר של כל קבו×Ļה וי×ĸביר לאשפה א×Ē ×›×œ ׊אר הכפילויו×Ē.", "buy": "רכוש א×Ē Immich", + "cache_settings_clear_cache_button": "ניקוי מטמון", + "cache_settings_clear_cache_button_title": "מנקה א×Ē ×”×ž×˜×ž×•×Ÿ של היישום. זה ישפי×ĸ באופן מ׊מ×ĸו×Ēי ×ĸל הבי×Ļו×ĸים של היישום ×ĸד שהמטמון מ×Ēמלא מחדש.", + "cache_settings_duplicated_assets_clear_button": "נקה", + "cache_settings_duplicated_assets_subtitle": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ׊נמ×Ļאים ברשימ×Ē ×”×”×Ē×ĸלמו×Ē ×Š×œ האפליק×Ļיה", + "cache_settings_duplicated_assets_title": "({count}) ×Ēמונו×Ē ×ž×Š×•×›×¤×œ×•×Ē", + "cache_settings_statistics_album": "×Ēמונו×Ē ×ž×ž×•×–×ĸרו×Ē ×Š×œ ספרייה", + "cache_settings_statistics_full": "×Ēמונו×Ē ×ž×œ××•×Ē", + "cache_settings_statistics_shared": "×Ēמונו×Ē ×ž×ž×•×–×ĸרו×Ē ×Š×œ אלבום משו×Ē×Ŗ", + "cache_settings_statistics_thumbnail": "×Ēמונו×Ē ×ž×ž×•×–×ĸרו×Ē", + "cache_settings_statistics_title": "שימוש במטמון", + "cache_settings_subtitle": "הגדר כי×Ļד אפליק×Ļיי×Ē Immich שומר×Ē × ×Ēונים באופן זמני", + "cache_settings_tile_subtitle": "שלוט בה×Ēנהגו×Ē ×”××—×Ą×•×Ÿ המקומי", + "cache_settings_tile_title": "אחסון מקומי", + "cache_settings_title": "הגדרו×Ē ×Š×ž×™×¨×Ē ×ž×˜×ž×•×Ÿ", "camera": "מ×Ļלמה", "camera_brand": "מו×Ēג המ×Ļלמה", "camera_model": "דגם המ×Ļלמה", @@ -677,10 +729,10 @@ "cannot_update_the_description": "לא ני×Ēן ל×ĸדכן א×Ē ×”×Ēיאור", "cast": "שידור למסך", "cast_description": "הגדר×Ē ×”×Ēקנים זמינים לשידור", - "change": "שנה", "change_date": "שנה ×Ēאריך", "change_description": "שנה ×Ēיאור", "change_display_order": "שנה סדר ×Ē×Ļוגה", + "change_expiration_time": "שנה א×Ē ×–×ž×Ÿ ה×Ēפוגה", "change_location": "שנה מיקום", "change_name": "שנה ׊ם", "change_name_successfully": "השם שונה בה×Ļלחה", @@ -694,13 +746,17 @@ "change_password_form_password_mismatch": "סיסמאו×Ē ×œ× ×Ēואמו×Ē", "change_password_form_reenter_new_password": "הכנס שוב סיסמה חדשה", "change_pin_code": "שנה קוד PIN", + "change_trigger": "שנה טריגר", + "change_trigger_prompt": "האם א×Ēה בטוח שבר×Ļונך לשנו×Ē ××Ē ×”×˜×¨×™×’×¨? פ×ĸולה זו ×Ēסיר א×Ē ×›×œ הפ×ĸולו×Ē ×•×”×ž×Ą× × ×™× הקיימים.", "change_your_password": "×”×—×œ×Ŗ א×Ē ×”×Ą×™×Ą×ž×” שלך", "changed_visibility_successfully": "הנראו×Ē ×Š×•× ×Ēה בה×Ļלחה", "charging": "טו×ĸן", "charging_requirement_mobile_backup": "גיבוי ברק×ĸ דורש ט×ĸינה של המכשיר", + "check_corrupt_asset_backup": "בדוק גיבויים פגומים של ×Ēמונו×Ē", + "check_corrupt_asset_backup_button": "ב×Ļ×ĸ בדיקה", + "check_corrupt_asset_backup_description": "הר×Ĩ בדיקה זו רק ×ĸל Wi-Fi ולאחר שכל ה×Ēמונו×Ē ×’×•×‘×•. ההליך ×ĸשוי לקח×Ē ×›×ž×” דקו×Ē.", "check_logs": "בדוק יומני רישום", "checksum": "סיכום ביקור×Ē", - "choose": "בחר", "choose_matching_people_to_merge": "בחר אנשים ×Ēואמים למיזוג", "city": "×ĸיר", "cleanup_confirm_description": "Immich מ×Ļא {count} ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× (שנו×Ļרו לפני {date}) מגובים בבטחה לשר×Ē. להסיר א×Ē ×”×ĸו×Ēקים המקומיים ממכשיר זה?", @@ -718,10 +774,11 @@ "clear": "נקה", "clear_all": "נקה הכל", "clear_all_recent_searches": "נקה א×Ē ×›×œ החיפושים האחרונים", - "clear_failed_count": "ניקוי נכשל ({count})", "clear_file_cache": "נקה קב×Ļי מטמון", "clear_message": "נקה הוד×ĸה", "clear_value": "נקה ×ĸרך", + "client_cert_dialog_msg_confirm": "בסדר", + "client_cert_enter_password": "הזן סיסמה", "client_cert_import": "ייבוא", "client_cert_import_success_msg": "×Ē×ĸוד×Ē ×œ×§×•×— מיובא×Ē", "client_cert_invalid_msg": "קוב×Ĩ ×Ē×ĸודה לא ×Ēקין או סיסמה שגויה", @@ -730,10 +787,12 @@ "client_cert_remove_msg": "×Ē×ĸוד×Ē ×œ×§×•×— הוסרה", "client_cert_subtitle": "×Ēומך בפורמט PKCS12 (.p12, .pfx) בלבד. ייבוא/הסרה של ×Ē×ĸודה זמינה רק לפני ה×Ēחברו×Ē", "client_cert_title": "×Ē×ĸוד×Ē ×œ×§×•×— SSL [ניסיוני]", + "clockwise": "×ĸם כיוון הש×ĸון", "close": "סגור", "collapse": "כוו×Ĩ", "collapse_all": "כוו×Ĩ הכל", "color": "×Ļב×ĸ", + "color_theme": "×Ļב×ĸ ×ĸרכ×Ē × ×•×Š×", "command": "פקודה", "command_palette_prompt": "מ×Ļא במהירו×Ē ×“×¤×™×, פ×ĸולו×Ē ××• פקודו×Ē", "command_palette_to_close": "לסגור", @@ -746,7 +805,6 @@ "comments_are_disabled": "×Ēגובו×Ē ×ž×•×Š×‘×Ēו×Ē", "common_create_new_album": "×Ļור אלבום חדש", "completed": "הושלמו", - "configuration": "×Ē×Ļורה", "confirm": "אישור", "confirm_admin_password": "אישור סיסמ×Ē ×ž× ×”×œ", "confirm_delete_face": "האם באמ×Ē ×‘×¨×Ļונך למחוק א×Ē ×”×¤× ×™× של {name} מה×Ēמונה?", @@ -761,16 +819,19 @@ "contain": "מכיל", "context": "הקשר", "continue": "המשך", - "control_bottom_app_bar_add_tags": "×”×•×Ą×Ŗ ×Ēגיו×Ē", + "control_bottom_app_bar_create_new_album": "×Ļור אלבום חדש", + "control_bottom_app_bar_delete_from_immich": "מחק מהשר×Ē", "control_bottom_app_bar_delete_from_local": "מחק מהמכשיר", "control_bottom_app_bar_edit_location": "×ĸרוך מיקום", "control_bottom_app_bar_edit_time": "×ĸרוך ×Ēאריך & זמן", + "control_bottom_app_bar_share_link": "׊×Ē×Ŗ קישור", + "control_bottom_app_bar_share_to": "׊×Ē×Ŗ ×ĸם", "control_bottom_app_bar_trash_from_immich": "ה×ĸבר לאשפה", "copied_image_to_clipboard": "×Ēמונה הו×ĸ×Ēקה ללוח.", "copied_to_clipboard": "הו×ĸ×Ē×§ ללוח!", "copy_error": "שגיא×Ē ×”×ĸ×Ēקה", + "copy_file_path": "ה×ĸ×Ē×§ א×Ē × ×Ēיב הקוב×Ĩ", "copy_image": "ה×ĸ×Ē×§ ×Ēמונה", - "copy_json": "ה×ĸ×Ē×§ JSON", "copy_link": "ה×ĸ×Ē×§ קישור", "copy_link_to_clipboard": "ה×ĸ×Ē×§ קישור ללוח", "copy_password": "ה×ĸ×Ē×§ סיסמה", @@ -791,8 +852,6 @@ "create_new_person": "×Ļור אדם חדש", "create_new_person_hint": "הק×Ļה א×Ē ×”×Ēמונו×Ē ×Š× ×‘×—×¨×• לאדם חדש", "create_new_user": "×Ļור מ׊×Ēמ׊ חדש", - "create_person": "×Ļור אדם", - "create_person_subtitle": "×”×•×Ą×Ŗ ׊ם לפנים שנבחרו כדי לי×Ļור ול×Ēייג א×Ē ×”××“× החדש", "create_shared_album_page_share_add_assets": "×”×•×Ą×Ŗ ×Ēמונו×Ē", "create_shared_album_page_share_select_photos": "בחיר×Ē ×Ēמונו×Ē", "create_shared_link": "×Ļור קישור משו×Ē×Ŗ", @@ -804,33 +863,37 @@ "created_at": "נו×Ļר", "creating_linked_albums": "יו×Ļר אלבומים מקושרים...", "crop": "ח×Ēוך", + "crop_aspect_ratio_fixed": "×Ēוקן", "crop_aspect_ratio_free": "חינם", "crop_aspect_ratio_original": "מקורי", "crop_aspect_ratio_square": "ריבו×ĸ", + "curated_object_page_title": "דברים", "current_device": "מכשיר נוכחי", "current_pin_code": "קוד PIN הנוכחי", "current_server_address": "כ×Ēוב×Ē ×Š×¨×Ē × ×•×›×—×™×Ē", "custom_date": "×Ēאריך מו×Ēאם אישי×Ē", "custom_locale": "אזור שפה מו×Ēאם אישי×Ē", "custom_locale_description": "×ĸ×Ļב ×Ēאריכים, זמנים, ומספרים ×ĸל סמך השפה והאזור שנבחרו", + "custom_url": "קישור מו×Ēאם אישי×Ē", "cutoff_date_description": "שמור ×Ēמונו×Ē ×žâ€Ļ", "cutoff_day": "{count, plural, one {יום} other {ימים}}", "cutoff_year": "{count, plural, one {שנה} other {שנים}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "כהה", "dark_theme": "מ×ĸבר ל×ĸרכ×Ē × ×•×Š× כהה", "date": "×Ēאריך", "date_after": "×Ēאריך אחרי", "date_and_time": "×Ēאריך וש×ĸה", "date_before": "×Ēאריך לפני", - "date_of_birth": "×Ēאריך לידה", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "×Ēאריך לידה נ׊מר בה×Ļלחה", "date_range": "טווח ×Ēאריכים", - "date_time_original": "×Ēאריך/׊×ĸה מקוריים", "day": "יום", "days": "ימים", "deduplicate_all": "ביטול כל הכפילויו×Ē", - "default_quality_subtitle": "איכו×Ē ××™×›×•×Ēי×Ē ×‘×Š×™×ž×•×Š ב×ĸ×Ē ×œ×—×™×Ļה ×ĸל שי×Ēות. לח×Ĩ לחי×Ļה ארוכה ×ĸל כפ×Ēור השי×Ēות כדי לבחור בכל פ×ĸם.", - "default_share_quality": "איכו×Ē ×Š×™×Ēות בריר×Ē ×ž×—×“×œ", + "default_locale": "אזור שפה בריר×Ē ×ž×—×“×œ", + "default_locale_description": "×ĸי×Ļוב ×Ēאריכים ומספרים בה×Ēבסס ×ĸל אזור השפה של הדפדפן שלך", "delete": "מחק", "delete_action_confirmation_message": "האם א×Ēה בטוח שבר×Ļונך למחוק א×Ē ×”×Ēמונה הזא×Ē? פ×ĸולה זו ×Ē×ĸביר או×Ēו לאשפה של השר×Ē, ו×Ēשאל אם בר×Ļונך למחוק או×Ēו גם מהמכשיר המקומי", "delete_action_prompt": "{count} נמחקו", @@ -839,6 +902,8 @@ "delete_dialog_alert": "הפריטים האלה ימחקו ל×Ļמי×Ēו×Ē ×ž×”×Š×¨×Ē ×•×ž×”×ž×›×Š×™×¨ שלך", "delete_dialog_alert_local": "הפריטים האלה יוסרו ל×Ļמי×Ēו×Ē ×ž×”×ž×›×Š×™×¨ שלך אבל ×ĸדיין יהיו זמינים בשר×Ē", "delete_dialog_alert_local_non_backed_up": "חלק מהפריטים לא מגובים לשר×Ē ×•×™×•×Ą×¨×• ל×Ļמי×Ēו×Ē ×ž×”×ž×›×Š×™×¨ שלך", + "delete_dialog_alert_remote": "הפריטים האלה ימחקו ל×Ļמי×Ēו×Ē ×ž×”×Š×¨×Ē", + "delete_dialog_ok_force": "מחק בכל זא×Ē", "delete_dialog_title": "מחק ל×Ļמי×Ēו×Ē", "delete_duplicates_confirmation": "האם באמ×Ē ×‘×¨×Ļונך למחוק ל×Ļמי×Ēו×Ē ××Ē ×”×›×¤×™×œ×•×™×•×Ē ×”××œ×”?", "delete_face": "מחק פנים", @@ -857,16 +922,21 @@ "delete_tag_confirmation_prompt": "האם באמ×Ē ×‘×¨×Ļונך למחוק ×Ēג {tagName}?", "delete_user": "מחק מ׊×Ēמ׊", "deleted_shared_link": "קישור משו×Ē×Ŗ נמחק", + "deletes_missing_assets": "מוחק ×Ēמונו×Ē ×Š×—×Ą×¨×•×Ē ×‘×“×™×Ą×§", "description": "×Ēיאור", + "description_input_hint_text": "×”×•×Ą×Ŗ ×Ēיאור...", + "description_input_submit_error": "שגיאה ב×ĸדכון ×Ēיאור, בדוק א×Ē ×”×™×•×ž×Ÿ לפרטים נוספים", "deselect_all": "בטל הכל", "details": "פרטים", "direction": "כיוון", "disable": "השב×Ē", "disabled": "מושב×Ē", + "disallow_edits": "אל ×Ēאפ׊ר ×ĸריכו×Ē", "discord": "דיסקורד", "discover": "גילוי", "discovered_devices": "מכשירים שה×Ēגלו", "dismiss_all_errors": "ה×Ē×ĸלמו×Ē ×ž×›×œ השגיאו×Ē", + "dismiss_error": "ה×Ē×ĸלמו×Ē ×ž×”×Š×’×™××”", "display_options": "ה×Ļג×Ē ××¤×Š×¨×•×™×•×Ē", "display_order": "סדר ×Ē×Ļוגה", "display_original_photos": "ה×Ļג×Ē ×Ēמונו×Ē ×ž×§×•×¨×™×•×Ē", @@ -875,9 +945,11 @@ "documentation": "×Ēי×ĸוד", "done": "סיום", "download": "הורדה", + "download_action_prompt": "מוריד {count} ×Ēמונו×Ē", "download_canceled": "הורדה בוטלה", "download_complete": "הורדה הושלמה", "download_enqueue": "הורדה נוספה ל×Ēור", + "download_error": "שגיא×Ē ×”×•×¨×“×”", "download_failed": "הורדה נכשלה", "download_finished": "הורדה הס×Ēיימה", "download_include_embedded_motion_videos": "סרטונים מוטמ×ĸים", @@ -887,15 +959,15 @@ "download_paused": "הורדה הופסקה", "download_settings": "הורדה", "download_settings_description": "ניהול הגדרו×Ē ×”×§×Š×•×¨×•×Ē ×œ×”×•×¨×“×Ē ×Ēמונו×Ē", + "download_started": "הורדה החלה", + "download_sucess": "ה×Ļלח×Ē ×”×•×¨×“×”", + "download_sucess_android": "המדיה הורדה אל DCIM/Immich", "download_waiting_to_retry": "מחכה כדי לנסו×Ē ×Š×•×‘", "downloading": "מוריד", "downloading_asset_filename": "מוריד ×Ēמונה {filename}", "downloading_from_icloud": "מוריד מ-iCloud", "downloading_media": "מוריד מדיה", - "drag_to_reorder": "גרור כדי לשנו×Ē ×Ą×“×¨", "drop_files_to_upload": "שחרר קב×Ļים בכל מקום כדי לה×ĸלו×Ē", - "duplicate": "שכפול", - "duplicate_workflow": "שכפול זרימ×Ē ×ĸבודה", "duplicates": "כפילויו×Ē", "duplicates_description": "הפרד כל קבו×Ļה ×ĸל ידי ×Ļיון אילו, אם בכלל, הן כפילויו×Ē.", "duration": "משך זמן", @@ -907,7 +979,9 @@ "edit_date_and_time": "×ĸרוך ×Ēאריך וש×ĸה", "edit_date_and_time_action_prompt": "{count} ×Ēאריך וש×ĸה × ×ĸרכו", "edit_date_and_time_by_offset": "שינוי ×Ēאריך לפי קיזוז", + "edit_date_and_time_by_offset_interval": "טווח ×Ēאריכים חדש: {from} ×ĸד {to}", "edit_description": "×ĸרוך ×Ēיאור", + "edit_description_prompt": "אנא בחר ×Ēיאור חדש:", "edit_exclusion_pattern": "×ĸרוך דפוס החרגה", "edit_faces": "×ĸרוך פנים", "edit_key": "×ĸרוך מפ×Ēח", @@ -922,6 +996,9 @@ "edit_user": "×ĸרוך מ׊×Ēמ׊", "edit_workflow": "×ĸרוך זרימ×Ē ×ĸבודה", "editor": "×ĸורך", + "editor_close_without_save_prompt": "השינויים לא יישמרו", + "editor_close_without_save_title": "לסגור א×Ē ×”×ĸורך?", + "editor_confirm_reset_all_changes": "האם א×Ēה בטוח שבר×Ļונך לאפס א×Ē ×›×œ השינויים?", "editor_discard_edits_confirm": "בטל ×ĸריכו×Ē", "editor_discard_edits_prompt": "יש לך ×ĸריכו×Ē ×Š×œ× נשמרו. האם א×Ēה בטוח שבר×Ļונך למחוק או×Ēן?", "editor_discard_edits_title": "למחוק א×Ē ×”×ĸריכו×Ē?", @@ -950,7 +1027,9 @@ "enter_your_pin_code": "הזן א×Ē ×§×•×“ ה־PIN שלך", "enter_your_pin_code_subtitle": "הזן א×Ē ×§×•×“ ה־PIN שלך כדי לגש×Ē ×œ×Ą×¤×¨×™×™×” הנ×ĸולה", "error": "שגיאה", + "error_change_sort_album": "שינוי סדר מיון אלבום נכשל", "error_delete_face": "שגיאה במחיק×Ē ×¤× ×™× מ×Ēמונה", + "error_getting_places": "שגיאה בקבל×Ē ×ž×§×•×ž×•×Ē", "error_loading_albums": "שגיאה בט×ĸינ×Ē ××œ×‘×•×ž×™×", "error_loading_image": "שגיאה בט×ĸינ×Ē ×”×Ēמונה", "error_loading_partners": "שגיאה בט×ĸינ×Ē ×Š×•×Ēפים: {error}", @@ -990,7 +1069,6 @@ "failed_to_remove_product_key": "הסר×Ē ×ž×¤×Ēח מו×Ļר נכשלה", "failed_to_reset_pin_code": "איפוס קוד PIN נכשל", "failed_to_stack_assets": "י×Ļיר×Ē ×ĸרימ×Ē ×Ēמונו×Ē × ×›×Š×œ×”", - "failed_to_tag_assets": "×Ēיוג ה×Ēמונו×Ē × ×›×Š×œ", "failed_to_unstack_assets": "ביטול ×ĸרימ×Ē ×Ēמונו×Ē × ×›×Š×œ×”", "failed_to_update_notification_status": "שגיאה ב×ĸדכון הה×Ēראה", "incorrect_email_or_password": "דוא\"ל או סיסמה שגויים", @@ -1089,10 +1167,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "×”×•×Ą×Ŗ ×Ēיאור...", "exif_bottom_sheet_description_error": "שגיאה ב×ĸדכון ה×Ēיאור", + "exif_bottom_sheet_details": "פרטים", + "exif_bottom_sheet_location": "מיקום", "exif_bottom_sheet_no_description": "ללא ×Ēיאור", + "exif_bottom_sheet_people": "אנשים", + "exif_bottom_sheet_person_add_person": "×”×•×Ą×Ŗ ׊ם", "exit_slideshow": "×Ļא ממ×Ļג×Ē ×Š×§×•×¤×™×•×Ē", "expand": "הרחב", "expand_all": "הרחב הכל", + "experimental_settings_new_asset_list_subtitle": "×ĸבודה ב×Ēהליך", + "experimental_settings_new_asset_list_title": "אפ׊ר ר׊×Ē ×Ēמונו×Ē × ×™×Ą×™×•× ×™×Ē", + "experimental_settings_subtitle": "השימוש הוא ×ĸל אחריו×Ēך בלבד!", + "experimental_settings_title": "נסיוני", "expire_after": "פג לאחר", "expired": "פג", "expires_date": "יפוג {date}", @@ -1102,40 +1188,41 @@ "export_as_json": "יי×Ļוא כ-JSON", "export_database": "יי×Ļא מסד × ×Ēונים", "export_database_description": "יי×Ļא מסד × ×Ēונים SQL", - "exposure_time": "זמן חשיפה", "extension": "סיומ×Ē", "external": "חי×Ļוני", "external_libraries": "ספריו×Ē ×—×™×Ļוניו×Ē", "external_network": "ר׊×Ē ×—×™×Ļוני×Ē", "external_network_sheet_info": "אם אינך מחובר ל־Wi‑Fi מו×ĸדת, האפליק×Ļיה ×Ēנסה לה×Ēחבר לשר×Ē ×œ×¤×™ הסדר מהרשימה שלמטה (מלמ×ĸלה למטה)", - "f_number": "מספר F (מפ×Ēח ×Ļמ×Ļם)", "face_unassigned": "לא מוק×Ļה", "failed": "נכשלו", "failed_count": "נכשלו: {count}", "failed_to_authenticate": "ההזדהו×Ē × ×›×Š×œ×”", - "failed_to_delete_file": "מחיק×Ē ×”×§×•×‘×Ĩ נכשלה", "failed_to_load_assets": "ט×ĸינ×Ē ×Ēמונו×Ē × ×›×Š×œ×”", "failed_to_load_folder": "ט×ĸינ×Ē ×Ēיקיה נכשלה", "favorite": "מו×ĸדת", "favorite_action_prompt": "{count} נוספו למו×ĸדפים", "favorite_or_unfavorite_photo": "×”×•×Ą×Ŗ או הסר ×Ēמונה מהמו×ĸדפים", "favorites": "מו×ĸדפים", + "favorites_page_no_favorites": "לא נמ×Ļאו ×Ēמונו×Ē ×ž×•×ĸדפים", "feature_photo_updated": "×Ēמונה מיי×Ļג×Ē ×ĸודכנה", "features": "×Ēכונו×Ē", + "features_in_development": "×Ēכונו×Ē ×‘×¤×™×Ēוח", "features_setting_description": "ניהול ×Ēכונו×Ē ×”×™×™×Š×•×", "file_name_or_extension": "׊ם קוב×Ĩ או סיומ×Ē", "file_name_text": "׊ם הקוב×Ĩ", + "file_name_with_value": "׊ם הקוב×Ĩ: {file_name}", "file_size": "גודל קוב×Ĩ", "filename": "׊ם קוב×Ĩ", "filetype": "סוג קוב×Ĩ", "filter": "סנן", + "filter_description": "×Ēנאים לסינון ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ר×Ļויים", "filter_people": "סנן אנשים", "filter_places": "סינון מקומו×Ē", "filter_tags": "×Ēגי סינון", "filters": "מסננים", + "find_them_fast": "מ×Ļא או×Ēם מהר לפי ׊ם ×ĸם חיפוש", "first": "ראשון", "fix_incorrect_match": "×Ēקן ה×Ēאמה שגויה", - "focal_length": "אורך מוקד", "folder": "×Ēיקיה", "folder_not_found": "×Ēיקיה לא נמ×Ļאה", "folders": "×Ēיקיו×Ē", @@ -1146,7 +1233,6 @@ "free_up_space_description": "ה×ĸבר ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× מגובים לאשפה של המכשיר שלך כדי לפנו×Ē ×ž×§×•×. ה×ĸו×Ēקים בשר×Ē × ×Š××¨×™× בטוחים.", "free_up_space_settings_subtitle": "פינוי אחסון במכשיר", "full_path": "× ×Ēיב מלא: {path}", - "full_path_or_folder": "מיקום מלא או ×Ēיקייה", "gcast_enabled": "Google Cast", "gcast_enabled_description": "×Ēכונה זא×Ē ×˜×•×ĸ× ×Ē ×ž×Š××‘×™× חי×Ļוניים מגוגל בכדי לפ×ĸול.", "general": "כללי", @@ -1167,6 +1253,7 @@ "group_owner": "קב×Ĩ לפי ב×ĸלים", "group_places_by": "קב×Ĩ מקומו×Ē ×œ×¤×™...", "group_year": "קב×Ĩ לפי שנה", + "haptic_feedback_switch": "אפ׊ר משוב ברטט", "haptic_feedback_title": "משוב ברטט", "has_quota": "יש מכסה", "hash_asset": "גבב פריט", @@ -1187,12 +1274,29 @@ "hide_schema": "הס×Ēר א×Ē ×”×Ą×›×™×ž×”", "hide_text_recognition": "הס×Ēר×Ē ×–×™×”×•×™ טקץט", "hide_unnamed_people": "הס×Ēר אנשים ללא ׊ם", + "home_page_add_to_album_conflicts": "{added} ×Ēמונו×Ē × ×•×Ą×¤×• לאלבום {album}. {failed} ×Ēמונו×Ē ×›×‘×¨ נמ×Ļאו×Ē ×‘××œ×‘×•×.", + "home_page_add_to_album_err_local": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×œ××œ×‘×•× ×ĸדיין, מדלג", + "home_page_add_to_album_success": "{added} ×Ēמונו×Ē × ×•×Ą×¤×• לאלבום {album}.", + "home_page_album_err_partner": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ ×Ēמונ×Ē ×Š×•×Ē×Ŗ לאלבום ×ĸדיין, מדלג", + "home_page_archive_err_local": "לא ני×Ēן לה×ĸביר לארכיון ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×ĸדיין, מדלג", + "home_page_archive_err_partner": "לא ני×Ēן לה×ĸביר לארכיון ×Ēמונו×Ē ×Š×œ השו×Ē×Ŗ, מדלג", "home_page_building_timeline": "בונה א×Ē ×Ļיר הזמן", + "home_page_delete_err_partner": "לא ני×Ēן למחוק ×Ēמונו×Ē ×Š×œ השו×Ē×Ŗ, מדלג", + "home_page_delete_remote_err_local": "×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē × ×‘×—×¨×• מרחוק למחיקה, מדלג", + "home_page_favorite_err_local": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ למו×ĸדפים ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×ĸדיין, מדלג", + "home_page_favorite_err_partner": "לא ני×Ēן ×œ×”×•×Ą×™×Ŗ למו×ĸדפים ×Ēמונו×Ē ×Š×œ השו×Ē×Ŗ ×ĸדיין, מדלג", + "home_page_first_time_notice": "אם זא×Ē ×”×¤×ĸם הראשונה ׊א×Ē/ה מ׊×Ēמ׊/×Ē ×‘×™×™×Š×•×, נא להקפיד לבחור אלבומ(ים) לגיבוי כך ׊×Ļיר הזמן יוכל לאכלס ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× באלבומ(ים)", + "home_page_locked_error_local": "לא ני×Ēן לה×ĸביר ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×œ×Ēיקייה × ×ĸולה, מדלג", + "home_page_locked_error_partner": "לא ני×Ēן לה×ĸביר ×Ēמונו×Ē ×Š×œ שו×Ē×Ŗ ל×Ēיקיה × ×ĸולה, מדלג", + "home_page_share_err_local": "לא ני×Ēן לש×Ē×Ŗ ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×ĸל ידי קישור, מדלג", + "home_page_upload_err_limit": "ני×Ēן לה×ĸלו×Ē ×¨×§ מקסימום של 30 ×Ēמונו×Ē ×‘×›×œ פ×ĸם, מדלג", "host": "מארח", "hour": "׊×ĸה", "hours": "׊×ĸו×Ē", "id": "מזהה", "idle": "במ×Ļב ץרק", + "ignore_icloud_photos": "ה×Ē×ĸלם מ×Ēמונו×Ē iCloud", + "ignore_icloud_photos_description": "×Ēמונו×Ē ×Š×ž××•×—×Ą× ×•×Ē ×‘-iCloud לא יו×ĸלו לשר×Ē", "image": "×Ēמונה", "image_alt_text_date": "{isVideo, select, true {סרטון ׊×Ļולם} other {×Ēמונה ׊×Ļולמה}} ב-{date}", "image_alt_text_date_1_person": "{isVideo, select, true {סרטון ׊×Ļולם} other {×Ēמונה ׊×Ļולמה}} ×ĸם {person1} ב-{date}", @@ -1204,6 +1308,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {סרטון ׊×Ļולם} other {×Ēמונה ׊×Ļולמה}} ב-{city}, {country} ×ĸם {person1} ו-{person2} ב-{date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {סרטון ׊×Ļולם} other {×Ēמונה ׊×Ļולמה}} ב-{city}, {country} ×ĸם {person1}, {person2}, ו-{person3} ב-{date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {סרטון ׊×Ļולם} other {×Ēמונה ׊×Ļולמה}} ב-{city}, {country} ×ĸם {person1}, {person2}, ו-{additionalCount, number} אחרים ב-{date}", + "image_saved_successfully": "×Ēמונה נשמרה", + "image_viewer_page_state_provider_download_started": "ההורדה החלה", + "image_viewer_page_state_provider_download_success": "ה×Ļלח×Ē ×”×•×¨×“×”", + "image_viewer_page_state_provider_share_error": "שי×Ēות שגיאה", "immich_logo": "הלוגו של Immich", "immich_web_interface": "ממ׊ק האינטרנט של Immich", "import_from_json": "ייבוא מ-JSON", @@ -1218,7 +1326,6 @@ "individual_share": "שי×Ēות יחיד", "individual_shares": "שי×Ēופים בודדים", "info": "מיד×ĸ", - "integrity_checks": "בדיקו×Ē ×Ēקינו×Ē", "interval": { "day_at_onepm": "כל יום בש×ĸה 13:00", "hours": "כל {hours, plural, one {׊×ĸה} other {{hours, number} ׊×ĸו×Ē}}", @@ -1229,9 +1336,16 @@ "invalid_date_format": "פורמט ×Ēאריך לא ×Ēקין", "invite_people": "הזמן אנשים", "invite_to_album": "הזמן לאלבום", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "השליפה בו×Ļ×ĸה ב־{dateTime}", + "ios_debug_info_last_sync_at": "סנכרון אחרון ב- {dateTime}", + "ios_debug_info_no_processes_queued": "אין ×Ēהליכי רק×ĸ הממ×Ēינים ב×Ēור", + "ios_debug_info_no_sync_yet": "טרם ה×Ēב×Ļ×ĸ ×Ēהליך סנכרון ברק×ĸ", + "ios_debug_info_processes_queued": "{count, plural, one {×Ēהליך רק×ĸ {count} ב×Ēור} other {{count} ×Ēהליכי רק×ĸ ב×Ēור}}", + "ios_debug_info_processing_ran_at": "ה×ĸיבוד ה×Ēב×Ļ×ĸ ב־{dateTime}", "items_count": "{count, plural, one {פריט #} other {# פריטים}}", "jobs": "משימו×Ē", + "json_editor": "×ĸורך JSON", + "json_error": "שגיא×Ē JSON", "keep": "שמור", "keep_albums": "שמור אלבומים", "keep_albums_count": "השאר×Ē {count, plural, one {אלבום אחד} other {{count} אלבומים}}", @@ -1258,12 +1372,14 @@ "leave": "ל×ĸזוב", "leave_album": "×ĸזיב×Ē ××œ×‘×•×", "lens_model": "דגם ×ĸדשה", - "less": "פחו×Ē", "let_others_respond": "אפ׊ר לאחרים להגיב", "level": "רמה", "library": "ספרייה", "library_add_folder": "הוספ×Ē ×Ą×¤×¨×™×”", "library_edit_folder": "×ĸריכ×Ē ×Ą×¤×¨×™×”", + "library_options": "אפשרויו×Ē ×Ą×¤×¨×™×™×”", + "library_page_device_albums": "אלבומים במכשיר", + "library_page_new_album": "אלבום חדש", "library_page_sort_asset_count": "מספר ×Ēמונו×Ē", "library_page_sort_created": "×Ēאריך י×Ļירה", "library_page_sort_last_modified": "שונה לאחרונה", @@ -1273,14 +1389,11 @@ "light_theme": "החלפה ל×ĸרכ×Ē × ×•×Š× בהירה", "like": "אהב×Ēי", "like_deleted": "לייק נמחק", - "link": "קישור", "link_motion_video": "ק׊ר סרטון ×Ēנו×ĸה", "link_to_docs": "למיד×ĸ × ×•×Ą×Ŗ, יש ל×ĸיין ב×Ēי×ĸוד.", "link_to_oauth": "קישור ל-OAuth", "linked_oauth_account": "חשבון OAuth מקושר", "list": "רשימה", - "live": "חי", - "load_more": "ט×ĸן ×ĸוד", "loading": "טו×ĸן", "loading_search_results_failed": "ט×ĸינ×Ē ×Ēו×Ļאו×Ē ×”×—×™×¤×•×Š נכשלה", "local": "מקומי", @@ -1309,9 +1422,11 @@ "login": "כניסה", "login_disabled": "כניסה למ×ĸרכ×Ē ×”×•×Š×‘×Ēה", "login_form_api_exception": "חריג×Ē API. נא לבדוק א×Ē ×›×Ēוב×Ē ×”×Š×¨×Ē ×•×œ× ×Ą×•×Ē ×Š×•×‘.", + "login_form_back_button_text": "חזרה", "login_form_email_hint": "yourmail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "כ×Ēוב×Ē URL של השר×Ē", + "login_form_err_http": "נא ל×Ļיין //:http או //:https", "login_form_err_invalid_email": "דוא\"ל שגוי", "login_form_err_invalid_url": "כ×Ēוב×Ē ×œ× חוקי×Ē", "login_form_err_leading_whitespace": "רווח לבן מוביל", @@ -1321,9 +1436,10 @@ "login_form_failed_login": "שגיאה בכניסה למ×ĸרכ×Ē, בדוק א×Ē ×›×Ēוב×Ē ×”×Š×¨×Ē, דוא\"ל וסיסמה", "login_form_handshake_exception": "איר×ĸה חריגה ב×ĸ×Ē ×‘×™×Ļו×ĸ Handshake ×ĸם השר×Ē. אפ׊ר ×Ēמיכה ב×Ē×ĸודה בח×Ēימה ×ĸ×Ļמי×Ē ×‘×”×’×“×¨×•×Ē ×× א×Ē/ה מ׊×Ēמ׊/×Ē ×‘×Ē×ĸודה בח×Ēימה ×ĸ×Ļמי×Ē.", "login_form_password_hint": "סיסמה", - "login_form_server_empty": "כ×Ēוב×Ē ×Š×¨×Ē", - "login_form_server_error": "לא ני×Ēן לה×Ēחבר לשר×Ē", - "login_has_been_disabled": "הה×Ēחברו×Ē ×œ×—×Š×‘×•×Ÿ הושב×Ēה", + "login_form_save_login": "הישאר/י מחובר/×Ē", + "login_form_server_empty": "הכנס כ×Ēוב×Ē ×Š×¨×Ē.", + "login_form_server_error": "לא היה ני×Ēן לה×Ēחבר לשר×Ē.", + "login_has_been_disabled": "הכניסה הושב×Ēה.", "login_password_changed_error": "היי×Ēה שגיאה ב×ĸדכון הסיסמה שלך", "login_password_changed_success": "סיסמה ×ĸודכנה בה×Ļלחה", "logout_all_device_confirmation": "האם באמ×Ē ×‘×¨×Ļונך לה×Ē× ×Ē×§ מכל המכשירים?", @@ -1338,6 +1454,7 @@ "maintenance_action_restore": "שחזור מסד × ×Ēונים", "maintenance_description": "Immich הו×ĸבר למ×Ļב ×Ēחזוקה.", "maintenance_end": "סיום מ×Ļב ×Ēחזוקה", + "maintenance_end_error": "כשל בסיום מ×Ļב ×Ēחזוקה.", "maintenance_logged_in_as": "מחובר כרג×ĸ ב×Ēור {user}", "maintenance_restore_from_backup": "שחזור מגיבוי", "maintenance_restore_library": "שחזר א×Ē ×”×Ą×¤×¨×™×™×” שלך", @@ -1363,6 +1480,7 @@ "manage_media_access_settings": "פ×Ēח הגדרו×Ē", "manage_media_access_subtitle": "אפ׊ר לאפליק×Ļיי×Ē Immich לנהל ולהזיז קב×Ļי מדיה.", "manage_media_access_title": "גישה לניהול מדיה", + "manage_shared_links": "ניהול קישורים משו×Ēפים", "manage_sharing_with_partners": "ניהול שי×Ēות ×ĸם שו×Ēפים", "manage_the_app_settings": "ניהול הגדרו×Ē ×”××¤×œ×™×§×Ļיה", "manage_your_account": "ניהול החשבון שלך", @@ -1370,12 +1488,14 @@ "manage_your_devices": "ניהול המכשירים המחוברים שלך", "manage_your_oauth_connection": "ניהול חיבור ה-OAuth שלך", "map": "מפה", + "map_assets_in_bounds": "{count, plural, =0 {אין ×Ēמונו×Ē ×‘××–×•×¨ זה} one {×Ēמונה #} other {# ×Ēמונו×Ē}}", "map_cannot_get_user_location": "לא ני×Ēן לקבו×ĸ א×Ē ×ž×™×§×•× המש×Ēמ׊", + "map_location_dialog_yes": "כן", "map_location_picker_page_use_location": "הש×Ēמ׊ במיקום הזה", "map_location_service_disabled_content": "שירו×Ē ×”×ž×™×§×•× ×Ļריך להיו×Ē ×ž×•×¤×ĸל כדי לה×Ļיג ×Ēמונו×Ē ×ž×”×ž×™×§×•× הנוכחי שלך. האם בר×Ļונך להפ×ĸיל או×Ēו ×ĸכשיו?", "map_location_service_disabled_title": "שירו×Ē ×ž×™×§×•× מבוטל", - "map_marker_for_image": "מרקר מפה ל×Ēמונה ממיקום {city}, {country}", - "map_marker_with_image": "סמן מפה מ×Ēמונה", + "map_marker_for_images": "סמן מפה ל×Ēמונו×Ē ×Š×Ļולמו ב{city}, {country}", + "map_marker_with_image": "סמן מפה ×ĸם ×Ēמונה", "map_no_location_permission_content": "יש ×Ļורך בהרשאה למיקום כדי לה×Ļיג ×Ēמונו×Ē ×ž×”×ž×™×§×•× הנוכחי שלך. האם בר×Ļונך לאפשר זא×Ē ×ĸכשיו?", "map_no_location_permission_title": "הרשאה למיקום נדח×Ēה", "map_settings": "הגדרו×Ē ×ž×¤×”", @@ -1384,46 +1504,17 @@ "map_settings_date_range_option_days": "ב-{days} ימים אחרונים", "map_settings_date_range_option_year": "שנה אחרונה", "map_settings_date_range_option_years": "ב-{years} שנים אחרונו×Ē", - "map_settings_include_show_archived": "הכלל×Ē ××¨×›×™×•×Ÿ", - "map_settings_include_show_partners": "הכלל×Ē ×Š×•×Ēפים", - "map_settings_only_show_favorites": "ה×Ļג×Ē ×ž×•×ĸדפים בלבד", + "map_settings_dialog_title": "הגדרו×Ē ×ž×¤×”", + "map_settings_include_show_archived": "כלול ארכיון", + "map_settings_include_show_partners": "כלול שו×Ēפים", + "map_settings_only_show_favorites": "ה×Ļג מו×ĸדפים בלבד", "map_settings_theme_settings": "×ĸרכ×Ē × ×•×Š× למפה", + "map_zoom_to_see_photos": "הקטן א×Ē ×”×Ē×Ļוגה כדי לראו×Ē ×Ēמונו×Ē", "mark_all_as_read": "סמן הכל כנקרא", + "mark_as_read": "סמן כנקרא", "marked_all_as_read": "כל הה×Ēראו×Ē ×Ą×•×ž× ×• כנקראו", "matches": "ה×Ēאמו×Ē", "matching_assets": "×Ēמונו×Ē ×Ēואמו×Ē", - "media_chrome": { - "auto": "אוטומטי", - "captions": "כ×Ēוביו×Ē", - "captions_off": "כבוי", - "closed_captions": "כ×Ēוביו×Ē ×ž×•×‘× ×•×Ē", - "decode_error": "שגיא×Ē ×§×™×“×•×“", - "disable_captions": "ביטול כ×Ēוביו×Ē", - "enable_captions": "אפ׊ר כ×Ēוביו×Ē", - "enter_fullscreen_mode": "הפ×ĸל×Ē ×ž×Ļב מסך מלא", - "exit_fullscreen_mode": "י×Ļיאה ממסך מלא", - "loop": "לולאה", - "media_error_description": "שגיאה במדיה ×ĸ×Ļדה א×Ē ×”× ×™×’×•×Ÿ. המדיה אינה ×Ēקינה או שהדפדפן אינו ×Ēומך בפורמט.", - "media_loading": "ט×ĸינ×Ē ×ž×“×™×”", - "mute": "הש×Ēקה", - "network_error": "שגיא×Ē ×¨×Š×Ē", - "network_error_description": "ההורדה נכשלה בגלל שגיא×Ē ×¨×Š×Ē.", - "not_supported_error": "מקור לא × ×Ēמך", - "playback_rate": "דירוג ניגון", - "playback_rate_current": "דירוג נוכחי", - "playback_rate_value": "דירוג {playbackRate}", - "playback_time": "זמן ניגון", - "quality": "איכו×Ē", - "second": "שניה", - "seconds": "שניו×Ē", - "time_value_of_total_time": "{currentTime} מ×Ēוך {totalTime}", - "time_value_remaining": "נ׊אר {time}", - "unmute": "ביטול הש×Ēקה", - "unsupported_error_description": "איר×ĸה שגיאה שאינה × ×Ēמכ×Ē. השר×Ē ××• הרש×Ē × ×›×Š×œ×•, או שהדפדפן שלך אינו ×Ēומך בפורמט זה.", - "video_not_loaded_unknown_time": "הווידאו לא נט×ĸן, זמן לא ידו×ĸ.", - "video_player": "נגן וידיאו", - "volume": "×ĸו×Ļמ×Ē ×§×•×œ" - }, "media_type": "סוג מדיה", "memories": "זכרונו×Ē", "memories_all_caught_up": "ראי×Ē ×”×›×œ", @@ -1440,31 +1531,34 @@ "merge_people_prompt": "האם בר×Ļונך למזג א×Ē ×”×× ×Š×™× האלה? פ×ĸולה זו היא בל×Ēי הפיכה.", "merge_people_successfully": "מיזוג אנשים בו×Ļ×ĸ בה×Ļלחה", "merged_people_count": "{count, plural, one {אדם # מוזג} other {# אנשים מוזגו}}", - "minFaces": "מינימום פנים", - "minFaces_description": "המספר המינימלי של פנים שזוהו ×ĸבור אדם כדי שיו×Ļג", "minimize": "מז×ĸר", "minute": "דקה", "minutes": "דקו×Ē", + "mirror_horizontal": "אופקי", + "mirror_vertical": "אנכי", "missing": "חסרים", "mobile_app": "אפליק×Ļיה לטלפון", "mobile_app_download_onboarding_note": "הורד א×Ē ×”××¤×œ×™×§×Ļיה המלווה באח×Ē ×ž×”××¤×Š×¨×•×™×•×Ē ×”×‘××•×Ē", "model": "דגם", - "modify_date": "×Ēאריך שינוי", "month": "חודש", + "monthly_title_text_date_format": "MMMM y", "more": "×ĸוד", - "motion": "×Ēנו×ĸה", "move": "ה×ĸבר", + "move_down": "הזזה למטה", "move_off_locked_folder": "הו×Ļאה מה×Ēיקייה הנ×ĸולה", "move_to": "ה×ĸבר ל", "move_to_device_trash": "ה×ĸברה לפח האשפה של המכשיר", "move_to_lock_folder_action_prompt": "{count} נוספו ל×Ēיקייה הנ×ĸולה", "move_to_locked_folder": "ה×ĸבר ל×Ēיקיה הנ×ĸולה", "move_to_locked_folder_confirmation": "ה×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× האלו יוסרו מכל האלבומים, ויהיו מו×Ļגים רק ב×Ēיקיה הנ×ĸולה", + "move_up": "הזזה למ×ĸלה", + "moved_to_archive": "{count, plural, one {הו×ĸברה ×Ēמונה # } other {# ×Ēמונו×Ē ×”×•×ĸברו}} לארכיון", + "moved_to_library": "{count, plural, one {הו×ĸברה ×Ēמונה # } other {# ×Ēמונו×Ē ×”×•×ĸברו}} לספריה", "moved_to_trash": "הו×ĸבר לאשפה", + "multiselect_grid_edit_date_time_err_read_only": "לא ני×Ēן ל×ĸרוך ×Ēאריך של ×Ēמונו×Ē ×œ×§×¨×™××” בלבד, מדלג", + "multiselect_grid_edit_gps_err_read_only": "לא ני×Ēן ל×ĸרוך מיקום של ×Ēמונו×Ē ×œ×§×¨×™××” בלבד, מדלג", "mute_memories": "הש×Ē×§×Ē ×–×™×›×¨×•× ×•×Ē", "my_albums": "האלבומים שלי", - "my_immich_description": "ה×ĸ×Ē×§ א×Ē ×”×“×Ŗ הנוכחי כקישור ל-My Immich", - "my_immich_title": "קישור ה Immich link שלי", "name": "׊ם", "name_or_nickname": "׊ם או כינוי", "name_required": "נדרש ׊ם", @@ -1479,11 +1573,12 @@ "never": "את פ×ĸם", "new_album": "אלבום חדש", "new_api_key": "מפ×Ēח API חדש", - "new_feature": "×Ēכונה חדשה", + "new_date_range": "טווח ×Ēאריך חדש", "new_password": "סיסמה חדשה", "new_person": "אדם חדש", "new_pin_code": "קוד PIN חדש", "new_pin_code_subtitle": "זא×Ē ×”×¤×ĸם הראשונה שנכנס×Ē ×œ×Ēיקיה הנ×ĸולה. ×Ļור קוד PIN כדי לאבטח א×Ē ×”×’×™×Š×” ×œ×“×Ŗ זה", + "new_timeline": "×Ļיר הזמן החדש", "new_update": "×ĸדכון חדש", "new_user_created": "מ׊×Ēמ׊ חדש נו×Ļר", "new_version_available": "גרסה חדשה זמינה", @@ -1491,6 +1586,7 @@ "next": "הבא", "next_memory": "הזיכרון הבא", "no": "לא", + "no_actions_added": "×ĸדיין לא נוספו פ×ĸולו×Ē", "no_albums_found": "לא נמ×Ļאו אלבומים", "no_albums_message": "×Ļור אלבום כדי לארגן א×Ē ×”×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× שלך", "no_albums_with_name_yet": "זה נראה שאין לך ×ĸדיין את אלבום ×ĸם השם הזה.", @@ -1501,13 +1597,16 @@ "no_cast_devices_found": "לא נמ×Ļאו מכשירי שידור", "no_checksum_local": "אין Checksum זמין - לא ני×Ēן לאחזר ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē", "no_checksum_remote": "אין Checksum זמין - לא ני×Ēן לאחזר ×Ēמונו×Ē ×ž×”×Š×¨×Ē", + "no_configuration_needed": "לא נדרש×Ē ×Ē×Ļורה", "no_devices": "אין מכשירים מורשים", "no_duplicates_found": "לא נמ×Ļאו כפילויו×Ē.", "no_exif_info_available": "אין מיד×ĸ זמין ×ĸל מטא-× ×Ēונים (exif)", "no_explore_results_message": "ה×ĸלה ×Ēמונו×Ē × ×•×Ą×¤×•×Ē ×›×“×™ לחקור א×Ē ×”××•×Ą×Ŗ שלך.", "no_favorites_message": "×”×•×Ą×Ŗ מו×ĸדפים כדי למ×Ļוא במהירו×Ē ××Ē ×”×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× הכי טובים שלך", + "no_filters_added": "×ĸדיין לא נוספו מסננים", "no_libraries_message": "×Ļור ספרייה חי×Ļוני×Ē ×›×“×™ לראו×Ē ××Ē ×”×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× שלך", "no_local_assets_found": "לא נמ×Ļאו ×Ēמונו×Ē ×ĸם Checksum זהה", + "no_location_set": "לא הוגדר מיקום", "no_locked_photos_message": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ב×Ēיקייה הנ×ĸולה מוס×Ēרים ולא יופי×ĸו בזמן הגלישה או החיפוש בספרייה שלך.", "no_name": "אין ׊ם", "no_notifications": "אין ה×Ēראו×Ē", @@ -1517,19 +1616,14 @@ "no_results": "אין ×Ēו×Ļאו×Ē", "no_results_description": "נסה להש×Ēמ׊ במילה נרדפ×Ē ××• במיל×Ē ×ž×¤×Ēח יו×Ēר כללי×Ē", "no_shared_albums_message": "×Ļור אלבום כדי לש×Ē×Ŗ ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ×ĸם אנשים ברש×Ē ×Š×œ×š", - "no_steps": "טרם נוספו שלבים", + "no_uploads_in_progress": "אין ה×ĸלאו×Ē ×‘×Ēהליך", "none": "את אחד", "not_allowed": "לא מורשה", "not_available": "לא רלוונטי", "not_in_any_album": "לא בשום אלבום", "not_selected": "לא נבחרו", - "not_set": "לא מוגדר", "notes": "ה×ĸרו×Ē", "nothing_here_yet": "אין כאן כלום ×ĸדיין", - "notification_backup_reliability": "הפ×ĸל×Ē ×”×Ēראו×Ē ×œ×Š×™×¤×•×¨ אמינו×Ē ×”×’×™×‘×•×™ ברק×ĸ", - "notification_enabled_list_tile_content": "Immich מ׊×Ēמ׊ בה×Ēראו×Ē ×œ×Ļורך גיבוי ברק×ĸ. ני×Ēן לנהל או×Ēן בהגדרו×Ē ×”×ž×›×Š×™×¨.", - "notification_enabled_list_tile_open_button": "פ×Ēח הגדרו×Ē", - "notification_enabled_list_tile_title": "ה×Ēראו×Ē ×ž×•×¤×ĸלו×Ē", "notification_permission_dialog_content": "כדי לאפשר ה×Ēראו×Ē, לך להגדרו×Ē ×”×ž×›×Š×™×¨ ובחר אפ׊ר.", "notification_permission_list_tile_content": "ה×ĸ× ×§ הרשאה כדי לאפשר ה×Ēראו×Ē.", "notification_permission_list_tile_enable_button": "אפ׊ר ה×Ēראו×Ē", @@ -1541,8 +1635,6 @@ "obtainium_configurator": "הגדרו×Ē Obtainium", "obtainium_configurator_instructions": "הש×Ēמ׊ ב-Obtainium כגדי לה×Ēקין ול×ĸדכן א×Ē ××¤×œ×™×§×Ļיי×Ē ×”×× ×“×¨×•××™×“ ישירו×Ē ×œ×’×¨×Ą××•×Ē ×”×’×™×˜×”××‘ של Immich. ×Ļור מפ×Ēח API ובחר וריאנט כדי לי×Ļור קישור קונפיגור×Ļיה ×ĸבור Obtainium", "ocr": "OCR", - "ocr_body": "Immich קורא כ×ĸ×Ē ××Ē ×”×˜×§×Ą×˜ שב×Ēוך ה×Ēמונו×Ē ×Š×œ×š, כך ׊×Ēוכל לחפש או×Ēן לפי ה×Ēוכן הכ×Ēוב בהן.", - "ocr_title": "חפש טקץט ב×Ēוך ה×Ēמונו×Ē ×Š×œ×š", "official_immich_resources": "מקורו×Ē ×¨×Š×ž×™×™× של Immich", "offline": "לא מקוון", "offset": "קיזוז", @@ -1561,8 +1653,6 @@ "open": "פ×Ēח", "open_calendar": "פ×Ēיח×Ē ×œ×•×— שנה", "open_in_browser": "פ×Ēיחה בדפדפן", - "open_in_immich_body": "הגדר א×Ē Immich כגלריה שלך ב־Android כדי לפ×Ēוח ×Ēמונו×Ē ×™×Š×™×¨×•×Ē ×ž××¤×œ×™×§×Ļיו×Ē ××—×¨×•×Ē.", - "open_in_immich_title": "פ×Ēח ×Ēמונו×Ē ×‘ Immich", "open_in_map_view": "פ×Ēח ב×Ē×Ļוג×Ē ×ž×¤×”", "open_in_openstreetmap": "פ×Ēח ב-OpenStreetMap", "open_the_search_filters": "פ×Ēח א×Ē ×ž×Ą× × ×™ החיפוש", @@ -1571,10 +1661,10 @@ "organize_into_albums": "ארגן ב×Ēוך אלבומים", "organize_into_albums_description": "שים ×Ēמונו×Ē ×§×™×™×ž×•×Ē ×‘×Ēוך אלבומים באמ×Ļ×ĸו×Ē ×”×’×“×¨×•×Ē ×”×Ą× ×›×¨×•×Ÿ הנוכחיו×Ē", "organize_your_library": "ארגן א×Ē ×”×Ą×¤×¨×™×™×” שלך", - "orientation": "כיוון", "original": "מקורי", "other": "אחר", "other_devices": "מכשירים אחרים", + "other_entities": "ישויו×Ē ××—×¨×•×Ē", "other_variables": "מ׊×Ēנים אחרים", "owned": "בב×ĸלו×Ē", "owner": "ב×ĸלים", @@ -1584,9 +1674,12 @@ "partner_can_access_assets": "כל ה×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× שלך פרט לאלו שבארכיון ושנמחקו", "partner_can_access_location": "המיקום שבו ×Ļולמו ה×Ēמונו×Ē ×Š×œ×š", "partner_list_user_photos": "×Ēמונו×Ē ×Š×œ {user}", + "partner_list_view_all": "ה×Ļג הכל", "partner_page_empty_message": "ה×Ēמונו×Ē ×Š×œ×š ×ĸדיין לא משו×Ēפו×Ē ×ĸם את שו×Ē×Ŗ.", "partner_page_no_more_users": "אין ×ĸוד מ׊×Ēמשים ×œ×”×•×Ą×™×Ŗ", + "partner_page_partner_add_failed": "הוספ×Ē ×Š×•×Ē×Ŗ נכשלה", "partner_page_select_partner": "בחיר×Ē ×Š×•×Ē×Ŗ", + "partner_page_shared_to_title": "משו×Ē×Ŗ ×ĸם", "partner_page_stop_sharing_content": "{partner} לא יוכל יו×Ēר לגש×Ē ×œ×Ēמונו×Ē ×Š×œ×š.", "partner_sharing": "שי×Ēות שו×Ēפים", "partners": "שו×Ēפים", @@ -1608,6 +1701,8 @@ "people": "אנשים", "people_edits_count": "{count, plural, one {אדם # × ×ĸרך} other {# אנשים × ×ĸרכו}}", "people_feature_description": "×ĸיון ב×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× שקוב×Ļו ×ĸל ידי אנשים", + "people_selected": "{count, plural, one {אדם אחד נבחר} other {# אנשים נבחרו}}", + "people_sidebar_description": "ה×Ļג קישור אל אנשים בסרגל ה×Ļד", "permanent_deletion_warning": "אזהר×Ē ×ž×—×™×§×” ל×Ļמי×Ēו×Ē", "permanent_deletion_warning_setting_description": "ה×Ļג אזהרה ב×ĸ×Ē ×ž×—×™×§×Ē ×Ēמונו×Ē ×œ×Ļמי×Ēו×Ē", "permanently_delete": "מחק ל×Ļמי×Ēו×Ē", @@ -1617,17 +1712,28 @@ "permanently_deleted_assets_count": "{count, plural, one {×Ēמונה # נמחקה} other {# ×Ēמונו×Ē × ×ž×—×§×•}} ל×Ļמי×Ēו×Ē", "permission": "הרשאו×Ē", "permission_empty": "ההרשאו×Ē ×Š×œ×š לא יכולו×Ē ×œ×”×™×•×Ē ×¨×™×§×•×Ē", + "permission_onboarding_back": "חזרה", + "permission_onboarding_continue_anyway": "המשך בכל זא×Ē", + "permission_onboarding_get_started": "לה×Ēחיל", + "permission_onboarding_go_to_settings": "לך להגדרו×Ē", + "permission_onboarding_permission_denied": "הרשאה נדח×Ēה. כדי להש×Ēמ׊ ביישום, ה×ĸ× ×§ הרשאה ל×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× בהגדרו×Ē.", + "permission_onboarding_permission_granted": "ההרשאה ני×Ēנה! הכל מוכן.", + "permission_onboarding_permission_limited": "הרשאה מוגבל×Ē. כדי ל×Ē×Ē ×œ×™×™×Š×•× לגבו×Ē ×•×œ× ×”×œ א×Ē ×›×œ ××•×Ą×Ŗ הגלריה שלך, ה×ĸ× ×§ הרשאה ל×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× בהגדרו×Ē.", + "permission_onboarding_request": "היישום דורש הרשאה כדי לראו×Ē ××Ē ×”×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× שלך.", "person": "אדם", "person_age_months": "{months, plural, one {חודש #} other {# חודשים}}", "person_age_year_months": "בגיל שנה ו{months, plural, one {חודש #} other {# חודשים}}", - "person_age_years": "בגיל {years, plural,one{#שנה}, other {# שנים}}", + "person_age_years": "בגיל {years, plural, other {# שנים}}", "person_birthdate": "נולד ב×Ēאריך {date}", "person_hidden": "{name}{hidden, select, true { (מוס×Ēר)} other {}}", "person_recognized": "זוהה אדם", + "person_selected": "נבחר אדם", "photo_shared_all_users": "נראה ששי×Ēפ×Ē ××Ē ×”×Ēמונו×Ē ×Š×œ×š ×ĸם כל המש×Ēמשים או שאין לך את מ׊×Ēמ׊ לש×Ē×Ŗ אי×Ēו.", "photos": "×Ēמונו×Ē", "photos_and_videos": "×Ēמונו×Ē & סרטונים", + "photos_count": "{count, plural, one {×Ēמונה {count, number}} other {{count, number} ×Ēמונו×Ē}}", "photos_from_previous_years": "×Ēמונו×Ē ×ž×Š× ×™× קודמו×Ē", + "photos_only": "רק ×Ēמונו×Ē", "pick_a_location": "בחר מיקום", "pick_custom_range": "טווח מו×Ēאם אישי×Ē", "pick_date_range": "בחר טווח ×Ēאריכים", @@ -1646,8 +1752,6 @@ "play_original_video_setting_description": "ה×ĸדת לנגן סרטונים המקוריים ×ĸל פני סרטונים משו×ĸ×Ēקים. אם הסרטון המקורי אינו ×Ēואם הוא ×ĸלול לה×Ēנגן לא נכון.", "play_transcoded_video": "נגן סרטון משו×ĸ×Ē×§", "please_auth_to_access": "אנא אמ×Ē ××Ē ×–×”×•×Ēך כדי לגש×Ē", - "plugin_method_filter_type": "מסנן", - "plugin_method_filter_type_description": "שיטה זו יכולה לסנן אירו×ĸים ולמנו×ĸ באופן מו×Ēנה א×Ē ×”×¤×ĸל×Ē ×”×Š×œ×‘×™× הבאים", "port": "י×Ļיאה", "preferences_settings_subtitle": "ניהול ה×ĸדפו×Ē ×™×™×Š×•×", "preferences_settings_title": "ה×ĸדפו×Ē", @@ -1664,11 +1768,11 @@ "privacy": "פרטיו×Ē", "profile": "פרופיל", "profile_drawer_app_logs": "יומן", + "profile_drawer_client_server_up_to_date": "היישום והשר×Ē ×ž×ĸודכנים", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "מ×Ļב לקריאה בלבד מופ×ĸל. לח×Ĩ לחי×Ļה ארוכה ×ĸל סמל הי×Ļגן של המש×Ēמ׊ כדי ל×Ļא×Ē.", "profile_image_of_user": "×Ēמונ×Ē ×¤×¨×•×¤×™×œ של {user}", "profile_picture_set": "×Ēמונ×Ē ×¤×¨×•×¤×™×œ נבחרה.", - "projection_type": "סוג הקרנה", "public_album": "אלבום ×Ļיבורי", "public_share": "שי×Ēות ×Ļיבורי", "purchase_account_info": "×Ēומך", @@ -1687,6 +1791,7 @@ "purchase_individual_description_2": "מ×ĸמד ×Ēומך", "purchase_individual_title": "יחיד", "purchase_input_suggestion": "יש לך מפ×Ēח מו×Ļר? הכנס א×Ē ×”×ž×¤×Ēח למטה", + "purchase_license_subtitle": "קנה א×Ē Immich כדי ל×Ēמוך בפי×Ēוח המ×Ēמשך של השירו×Ē", "purchase_lifetime_description": "רכישה לכל החיים", "purchase_option_title": "אפשרויו×Ē ×¨×›×™×Š×”", "purchase_panel_info_1": "בניי×Ē Immich לוקח×Ē ×”×¨×‘×” זמן ומאמ×Ĩ, ויש לנו מהנדסים במשרה מלאה ׊×ĸובדים ×ĸל זה כדי ל×ĸשו×Ē ××Ē ×–×” הכי טוב שאנחנו יכולים. המשימה שלנו היא ׊×Ēוכנו×Ē ×§×•×“-פ×Ēוח ושיטו×Ē ×ĸסקיו×Ē ××Ēיו×Ē ×™×”×™×• מקור הכנסה בר-קיימא למפ×Ēחים ולי×Ļור אקוסיסטם המכבד×Ē ×¤×¨×˜×™×•×Ē ×ĸם חלופו×Ē ××ž×™×Ēיו×Ē ×œ×Š×™×¨×•×Ēי ×ĸנן × ×Ļלנים.", @@ -1719,19 +1824,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {×Ēמונה # הוק×Ļ×Ēה} other {# ×Ēמונו×Ē ×”×•×§×Ļו}} מחדש לאדם חדש", "reassing_hint": "הק×Ļא×Ē ×Ēמונו×Ē ×Š× ×‘×—×¨×• לאדם קיים", "recent": "חדש", + "recent_albums": "אלבומים אחרונים", "recent_searches": "חיפושים אחרונים", "recently_added": "× ×•×Ą×Ŗ לאחרונה", - "recently_added_body": "×ĸבור ישירו×Ē ×œ×›×œ מה שהוספ×Ē ×œ××—×¨×•× ×” ב×ĸמוד יי×ĸודי.", - "recently_added_description": "×ĸיין ב×Ēמונו×Ē ×Š×œ×š כשהם ממוינו×Ē ×œ×¤×™ מו×ĸד ה×ĸלא×Ēם ל־Immich", "recently_added_page_title": "× ×•×Ą×Ŗ לאחרונה", - "recently_added_title": "נוספו לאחרונה", "recently_taken": "×Ļולם לאחרונה", + "recently_taken_page_title": "×Ļולם לאחרונה", "refresh": "ר×ĸנון", "refresh_encoded_videos": "ר×ĸנון סרטונים מקודדים", "refresh_faces": "ר×ĸנון פנים", "refresh_metadata": "ר×ĸנון מטא-× ×Ēונים", "refresh_thumbnails": "ר×ĸנון ×Ēמונו×Ē ×ž×ž×•×–×ĸרו×Ē", "refreshed": "רו×ĸנן", + "refreshes_every_file": "קורא מחדש א×Ē ×›×œ הקב×Ļים הקיימים והחדשים", "refreshing_encoded_video": "מר×ĸנן סרטון מקודד", "refreshing_faces": "מר×ĸנן פר×Ļופים", "refreshing_metadata": "מר×ĸנן מטא-× ×Ēונים", @@ -1744,7 +1849,7 @@ "remove_assets_shared_link_confirmation": "האם בר×Ļונך להסיר {count, plural, one {×Ēמונה #} other {# ×Ēמונו×Ē}} מהקישור המשו×Ē×Ŗ הזה?", "remove_assets_title": "להסיר ×Ēמונו×Ē?", "remove_custom_date_range": "הסר×Ē ×˜×•×•×— ×Ēאריכים מו×Ēאם", - "remove_filter": "הסר מסנן", + "remove_deleted_assets": "הסר×Ē ×§×‘×Ļים לא מקוונים", "remove_from_album": "הסרה מאלבום", "remove_from_album_action_prompt": "{count} הוסרו מהאלבום", "remove_from_favorites": "הסרה מהמו×ĸדפים", @@ -1755,22 +1860,29 @@ "remove_memory": "הסר זיכרון", "remove_photo_from_memory": "הסר ×Ēמונה מזיכרון זה", "remove_tag": "הסר×Ē ×Ēג", + "remove_url": "הסר URL", "remove_user": "הסר מ׊×Ēמ׊", "removed_api_key": "מפ×Ēח API הוסר: {name}", "removed_from_archive": "הוסר מארכיון", "removed_from_favorites": "הוסר ממו×ĸדפים", "removed_from_favorites_count": "{count, plural, other {הוסרו #}} מהמו×ĸדפים", "removed_memory": "זיכרון הוסר", + "removed_photo_from_memory": "ה×Ēמונה הוסרה מהזיכרון", "removed_tagged_assets": "×Ēג הוסר מ{count, plural, one {×Ēמונה #} other {# ×Ēמונו×Ē}}", "rename": "שנה ׊ם", + "repair": "×Ēיקון", + "repair_no_results_message": "קב×Ļים חסרי מ×ĸקב וחסרים יופי×ĸו כאן", + "replace_with_upload": "×”×—×œ×Ŗ בה×ĸלאה", "repository": "מאגר", "require_password": "דרוש סיסמה", + "require_user_to_change_password_on_first_login": "דרוש מהמש×Ēמ׊ לשנו×Ē ×Ą×™×Ą×ž×” בכניסה הראשונה", "rescan": "סרוק מחדש", "reset": "איפוס", "reset_password": "איפוס סיסמה", "reset_people_visibility": "אפץ א×Ē × ×¨××•×Ē ×”×× ×Š×™×", "reset_pin_code": "אפץ קוד PIN", "reset_pin_code_description": "אם שחכ×Ē ××Ē ×§×•×“ ה-PIN שלך, באפשרו×Ēך לי×Ļור ק׊ר ×ĸם מנהל השר×Ē ×›×“×™ לאפס או×Ēו", + "reset_pin_code_success": "קוד ה-PIN אופס בה×Ļלחה", "reset_pin_code_with_password": "באפשרו×Ēך ×Ēמיד לאפס א×Ē ×§×•×“ ה-PIN שלך ×ĸם הסיסמה שלך", "reset_sqlite": "אפץ א×Ē ×ž×Ą×“ הנ×Ēונים SQLite", "reset_sqlite_clear_app_data": "ניקוי × ×Ēונים", @@ -1784,10 +1896,12 @@ "resolved_all_duplicates": "כל הכפילויו×Ē × ×¤×Ēרו", "restore": "שחזר", "restore_all": "שחזר הכל", + "restore_trash_action_prompt": "{count} שוחזרו מהשאפה", "restore_user": "שחזר מ׊×Ēמ׊", "restored_asset": "ה×Ēמונה שוחזרה", "resume": "המשך", "resume_paused_jobs": "המשך {count, plural, one {×ĸבודה # שהופסקה} other {# ×ĸבודו×Ē ×Š×”×•×¤×Ą×§×•}}", + "retry_upload": "נסה שוב לה×ĸלו×Ē", "review_duplicates": "בדוק כפילויו×Ē", "review_large_files": "×Ļפייה בקב×Ļים גדולים", "role": "×Ēפקיד", @@ -1795,6 +1909,7 @@ "role_viewer": "×Ļופה", "running": "פו×ĸל", "save": "שמור", + "save_to_gallery": "שמור לגלריה", "saved": "נ׊מר", "saved_api_key": "מפ×Ēח API שמור", "saved_profile": "פרופיל שמור", @@ -1805,10 +1920,9 @@ "scan": "סריקה", "scan_all_libraries": "סרוק א×Ē ×›×œ הספריו×Ē", "scan_library": "סרוק", + "scan_settings": "הגדרו×Ē ×Ą×¨×™×§×”", "scanning": "ב×Ēהליך סריקה", "scanning_for_album": "סורק אחר אלבום...", - "screencast_mode_description": "ה×Ļג מחווני אירו×ĸי מקלד×Ē ×•×ĸכבר ×ĸל המסך", - "screencast_mode_title": "כבה/הפ×ĸל מ×Ļב שידור מסך", "search": "חיפוש", "search_albums": "חיפוש אלבומים", "search_by_context": "חיפוש לפי הקשר", @@ -1816,8 +1930,6 @@ "search_by_description_example": "יום טיול בסאפה", "search_by_filename": "חיפוש לפי ׊ם קוב×Ĩ או סיומ×Ē", "search_by_filename_example": "לדוגמא IMG_1234.JPG או PNG", - "search_by_full_path": "חפש לפי × ×Ēיב מלא או ×Ēיקייה", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 — ני×Ēן לחפש לפי Projects, 3D, Printing, 2026 וכו׺.", "search_by_ocr": "חיפוש לפי OCR", "search_by_ocr_example": "לאטה", "search_camera_lens_model": "חיפוש סוג ×ĸדשה...", @@ -1837,6 +1949,7 @@ "search_filter_location_title": "בחר מיקום", "search_filter_media_type": "סוג מדיה", "search_filter_media_type_title": "בחר סוג מדיה", + "search_filter_ocr": "חיפוש לפי OCR", "search_filter_people_title": "בחר אנשים", "search_filter_star_rating": "דירוג כוכבים", "search_filter_tags_title": "בחיר×Ē ×Ēגים", @@ -1847,13 +1960,25 @@ "search_no_people_named": "אין אנשים בשם \"{name}\"", "search_no_result": "לא נמ×Ļאו ×Ēו×Ļאו×Ē, יש לנסו×Ē ×‘×™×˜×•×™ או ×Ļי׍ות חיפוש שונה", "search_options": "אפשרויו×Ē ×—×™×¤×•×Š", + "search_page_categories": "קטגוריו×Ē", + "search_page_motion_photos": "×Ēמונו×Ē ×ĸם ×Ēנו×ĸה", + "search_page_no_objects": "אין מיד×ĸ זמין ×ĸל אובייקטים", + "search_page_no_places": "אין מיד×ĸ זמין ×ĸל מקומו×Ē", + "search_page_screenshots": "×Ļילומי מסך", "search_page_search_photos_videos": "חפש א×Ē ×”×Ēמונו×Ē ×•×”×Ą×¨×˜×•× ×™× שלך", + "search_page_selfies": "×Ļילומי סלפי", + "search_page_things": "דברים", "search_page_view_all_button": "ה×Ļג הכל", + "search_page_your_activity": "הפ×ĸילו×Ē ×Š×œ×š", + "search_page_your_map": "המפה שלך", "search_people": "חיפוש אנשים", "search_places": "חיפוש מקומו×Ē", "search_rating": "חיפוש לפי דירוג...", + "search_result_page_new_search_hint": "חיפוש חדש", "search_settings": "הגדרו×Ē ×—×™×¤×•×Š", "search_state": "חיפוש מדינה...", + "search_suggestion_list_smart_search_hint_1": "חיפוש חכם מופ×ĸל כבריר×Ē ×ž×—×“×œ, כדי לחפש מטא-× ×Ēונים הש×Ēמ׊ ב×Ēחביר ", + "search_suggestion_list_smart_search_hint_2": "×Ēנאי-החיפוש-שלך:m", "search_tags": "חיפוש ×Ēגים...", "search_timezone": "חיפוש אזור זמן...", "search_type": "סוג חיפוש", @@ -1867,6 +1992,7 @@ "select_albums": "בחיר×Ē ××œ×‘×•×ž×™×", "select_all": "בחר הכל", "select_all_duplicates": "בחר א×Ē ×›×œ הכפילויו×Ē", + "select_all_in": "בחר הכול ב×Ēוך {group}", "select_avatar_color": "בחר ×Ļב×ĸ י×Ļגן", "select_count": "{count, plural, one {בחיר×Ē #} other {בחיר×Ē #}}", "select_cutoff_date": "בחיר×Ē ×Ēאריך חי×Ēוך", @@ -1874,13 +2000,14 @@ "select_featured_photo": "בחר ×Ēמונה מיי×Ļג×Ē", "select_from_computer": "בחר מהמחשב", "select_keep_all": "בחר שמור הכל", + "select_library_owner": "בחר א×Ē ×”×‘×ĸלים של הספרייה", "select_new_face": "בחר פנים חדשו×Ē", "select_people": "בחיר×Ē ×× ×Š×™×", "select_person": "בחיר×Ē ××“×", "select_person_to_tag": "בחר אדם ל×Ēיוג", "select_photos": "בחר ×Ēמונו×Ē", - "select_quality": "בחר איכו×Ē", "select_trash_all": "בחר ה×ĸבר הכל לאשפה", + "select_user_for_sharing_page_err_album": "י×Ļיר×Ē ××œ×‘×•× נכשלה", "selected": "נבחרו", "selected_count": "{count, plural, other {# נבחרו}}", "selected_gps_coordinates": "קואורדינטו×Ē GPS שנבחרו", @@ -1913,31 +2040,42 @@ "setting_image_viewer_original_title": "ט×ĸן ×Ēמונה מקורי×Ē", "setting_image_viewer_preview_subtitle": "אפ׊ר לט×ĸון ×Ēמונה ברזלו×Ļיה בינוני×Ē. השב×Ē ×›×“×™ או לט×ĸון א×Ē ×”×ž×§×•×¨×™×Ē ××• רק להש×Ēמ׊ ב×Ēמונה הממוז×ĸר×Ē.", "setting_image_viewer_preview_title": "ט×ĸן ×Ēמונ×Ē ×Ē×Ļוגה מקדימה", + "setting_image_viewer_title": "×Ēמונו×Ē", "setting_languages_apply": "החל", "setting_languages_subtitle": "שינוי ׊פ×Ē ×”×™×™×Š×•×", + "setting_notifications_notify_failures_grace_period": "הוד×ĸ ×ĸל כשלים בגיבוי ברק×ĸ: {duration}", + "setting_notifications_notify_hours": "{count} ׊×ĸו×Ē", + "setting_notifications_notify_immediately": "באופן מיידי", "setting_notifications_notify_minutes": "{count} דקו×Ē", + "setting_notifications_notify_never": "את פ×ĸם", "setting_notifications_notify_seconds": "{count} שניו×Ē", + "setting_notifications_single_progress_subtitle": "מיד×ĸ מפורט ×ĸל ה×Ēקדמו×Ē ×”×ĸלאה לכל ×Ēמונה", + "setting_notifications_single_progress_title": "הראה פרטי ה×Ēקדמו×Ē ×’×™×‘×•×™ ברק×ĸ", "setting_notifications_subtitle": "ה×Ēאם א×Ē ×”×ĸדפו×Ē ×”×”×Ēראה שלך", + "setting_notifications_total_progress_subtitle": "ה×Ēקדמו×Ē ×”×ĸלאה כללי×Ē (בו×Ļ×ĸ/סה״כ ×Ēמונו×Ē)", + "setting_notifications_total_progress_title": "הראה סה״כ ה×Ēקדמו×Ē ×’×™×‘×•×™ ברק×ĸ", "setting_video_viewer_auto_play_subtitle": "נגן סרטונים אוטומטי×Ē ×›×Š×”× נפ×Ēחים", "setting_video_viewer_auto_play_title": "נגן סרטונים אוטומטי×Ē", "setting_video_viewer_looping_title": "הפ×ĸלה חוזר×Ē", "setting_video_viewer_original_video_subtitle": "כאשר מזרימים סרטון מהשר×Ē, נגן א×Ē ×”×ž×§×•×¨×™ אפילו כשהמר×Ē ×§×™×“×•×“ זמינה. ×ĸלול להוביל ל×Ēקי×ĸו×Ē. סרטונים זמינים מקומי×Ē ×ž× ×•×’× ×™× באיכו×Ē ×ž×§×•×¨×™×Ē ×œ×œ× ק׊ר להגדרה זו.", "setting_video_viewer_original_video_title": "כפה סרטון מקורי", "settings": "הגדרו×Ē", + "settings_require_restart": "אנא הפ×ĸל מחדש א×Ē ×”×™×™×Š×•× כדי להחיל הגדרה זו", "settings_saved": "ההגדרו×Ē × ×Š×ž×¨×•", "setup_pin_code": "הגדר קוד PIN", "share": "׊×Ē×Ŗ", + "share_action_prompt": "שו×Ēפו {count} ×Ēמונו×Ē", + "share_add_photos": "×”×•×Ą×Ŗ ×Ēמונו×Ē", + "share_assets_selected": "{count} נבחרו", "share_dialog_preparing": "מכין...", "share_link": "׊×Ē×Ŗ קישור", - "share_original": "הש×Ēמ׊ במקור (גדול)", - "share_preview": "הש×Ēמ׊ ב×Ēמונה ממוז×ĸר×Ē (קטנה)", - "share_quality_body": "לח×Ĩ לחי×Ļה ארוכה ×ĸל כפ×Ēור השי×Ēות כדי לבחור א×Ē ××™×›×•×Ē ×”×Ēמונה לפני השי×Ēות.", - "share_quality_title": "בחר א×Ē ××™×›×•×Ē ×”×Š×™×Ēות שלך", "shared": "משו×Ē×Ŗ", "shared_album_activities_input_disable": "ה×Ēגובה מושב×Ē×Ē", "shared_album_activity_remove_content": "האם בר×Ļונך למחוק א×Ē ×”×¤×ĸילו×Ē ×”×–××Ē?", "shared_album_activity_remove_title": "מחיק×Ē ×¤×ĸילו×Ē", "shared_album_section_people_action_error": "שגיאה ב×ĸזיבה/הסרה מאלבום", + "shared_album_section_people_action_leave": "הסר מ׊×Ēמ׊ מאלבום", + "shared_album_section_people_action_remove_user": "הסר מ׊×Ēמ׊ מאלבום", "shared_album_section_people_title": "אנשים", "shared_by": "משו×Ē×Ŗ ×ĸל ידי", "shared_by_user": "משו×Ē×Ŗ ×ĸל ידי {user}", @@ -1946,10 +2084,9 @@ "shared_intent_upload_button_progress_text": "{total} / {current} הו×ĸלו", "shared_link_app_bar_title": "קישורים משו×Ēפים", "shared_link_clipboard_copied_massage": "הו×ĸ×Ē×§ ללוח", + "shared_link_clipboard_text": "קישור: {password}\nסיסמה: {link}", "shared_link_create_error": "שגיאה בי×Ļיר×Ē ×§×™×Š×•×¨ משו×Ē×Ŗ", - "shared_link_custom_url_description": "גש לקישור ששו×Ē×Ŗ באמ×Ļ×ĸו×Ē ×Š× כ×Ēוב×Ē URL מו×Ēאמ×Ē ××™×Š×™×Ē", - "shared_link_custom_url_title": "URL מו×Ēאם אישי×Ē", - "shared_link_custom_url_warning": "אזהרה: URL מו×Ēאם אישי×Ē ×ĸם לוכסן קדמי ×ĸלול לה×Ēנהג שלא כמ×Ļופה.", + "shared_link_custom_url_description": "גש לקישור ששו×Ē×Ŗ באמ×Ļ×ĸו×Ē ×›×Ēוב×Ē URL מו×Ēאמ×Ē ××™×Š×™×Ē", "shared_link_edit_description_hint": "הכנס א×Ē ×Ēיאור השי×Ēות", "shared_link_edit_expire_after_option_day": "1 יום", "shared_link_edit_expire_after_option_days": "{count} ימים", @@ -1983,6 +2120,12 @@ "shared_with_partner": "משו×Ē×Ŗ ×ĸם {partner}", "sharing": "שי×Ēות", "sharing_enter_password": "נא להזין א×Ē ×”×Ą×™×Ą×ž×” כדי ל×Ļפו×Ē ×‘×“×Ŗ זה.", + "sharing_page_album": "אלבומים משו×Ēפים", + "sharing_page_description": "×Ļור אלבומים משו×Ēפים כדי לש×Ē×Ŗ ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ×ĸם אנשים ברש×Ē ×Š×œ×š.", + "sharing_page_empty_list": "רשימה ריקה", + "sharing_sidebar_description": "ה×Ļג קישור אל שי×Ēות בסרגל ה×Ļד", + "sharing_silver_appbar_create_shared_album": "אלבום משו×Ē×Ŗ חדש", + "sharing_silver_appbar_share_partner": "שי×Ēות ×ĸם שו×Ē×Ŗ", "shift_to_permanent_delete": "לח×Ĩ ⇧ כדי למחוק ×Ēמונה ל×Ļמי×Ēו×Ē", "show_album_options": "ה×Ļג אפשרויו×Ē ××œ×‘×•×", "show_albums": "ה×Ļג אלבומים", @@ -1994,9 +2137,7 @@ "show_in_timeline": "ה×Ļג ב×Ļיר הזמן", "show_in_timeline_setting_description": "ה×Ļג ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× ממ׊×Ēמ׊ זה ב×Ļיר הזמן שלך", "show_keyboard_shortcuts": "ה×Ļג קי×Ļורי מקלד×Ē", - "show_less": "ה×Ļג פחו×Ē", "show_metadata": "ה×Ļג מטא-× ×Ēונים", - "show_more_fields": "{count, plural, one {ה×Ļג ×ĸוד שדה אחד} other {ה×Ļג ×ĸוד # שדו×Ē}}", "show_or_hide_info": "ה×Ļג או הס×Ēר מיד×ĸ", "show_password": "הראה סיסמה", "show_person_options": "ה×Ļג אפשרויו×Ē ××“×", @@ -2004,7 +2145,6 @@ "show_schema": "ה×Ļג×Ē ×Ēרשים", "show_search_options": "ה×Ļג אפשרויו×Ē ×—×™×¤×•×Š", "show_shared_links": "ה×Ļג קישורים משו×Ēפים", - "show_slideshow_metadata_overlay": "ה×Ļג שכב×Ē ×ž×™×“×ĸ ×ĸל ה×Ēמונה", "show_slideshow_transition": "ה×Ļג מ×ĸבר מ×Ļג×Ē", "show_supporter_badge": "×Ēג ×Ēומך", "show_supporter_badge_description": "ה×Ļג ×Ēג ×Ēומך", @@ -2016,25 +2156,18 @@ "sign_out": "י×Ļיאה מהמ×ĸרכ×Ē", "sign_up": "הרשמה", "size": "גודל", - "skip": "דלג", "skip_to_content": "דלג ל×Ēוכן", "skip_to_folders": "דלג ל×Ēיקיו×Ē", "skip_to_tags": "דלג ל×Ēגים", "slideshow": "מ×Ļג×Ē ×Š×§×•×¤×™×•×Ē", - "slideshow_body": "שב בנוחו×Ē ×•×Ļפה ב×Ēמונו×Ē ×Š×œ×š מו×Ļגו×Ē ×‘×ž×Ļג×Ē ×Š×§×•×¤×™×•×Ē ×‘×ž×Ą×š מלא.", - "slideshow_metadata_overlay_mode": "שכב×Ē ×Ēוכן", - "slideshow_metadata_overlay_mode_description_only": "×Ēיאור בלבד", - "slideshow_metadata_overlay_mode_full": "מלא", "slideshow_repeat": "חזרה ×ĸל מ×Ļג×Ē", "slideshow_repeat_description": "חזרה לה×Ēחלה כאשר המ×Ļג×Ē × ×’×ž×¨×Ē", "slideshow_settings": "הגדרו×Ē ×ž×Ļג×Ē ×Š×§×•×¤×™×•×Ē", - "slideshow_title": "מ×Ļג×Ē ×Š×§×•×¤×™×•×Ē", - "smart_album": "אלבום חכם", - "some_assets_already_have_a_location_warning": "לחלק מה×Ēמונו×Ē ×Š× ×‘×—×¨×• כבר קיים מיקום", "sort_albums_by": "מיין אלבומים לפי...", "sort_created": "×Ēאריך י×Ļירה", "sort_items": "מספר פריטים", "sort_modified": "×Ēאריך שינוי", + "sort_newest": "×Ēמונה הכי חדשה", "sort_oldest": "×Ēמונה הכי ישנה", "sort_people_by_similarity": "מיין אנשים לפי דמיון", "sort_recent": "×Ēמונה אחרונה ביו×Ēר", @@ -2043,6 +2176,7 @@ "stack": "×ĸרימה", "stack_action_prompt": "{count} קוב×Ļו", "stack_duplicates": "×Ļור ×ĸרימ×Ē ×›×¤×™×œ×•×™×•×Ē", + "stack_select_one_photo": "בחר ×Ēמונה ראשי×Ē ××—×Ē ×ĸבור ה×ĸרימה", "stack_selected_photos": "×Ļור ×ĸרימ×Ē ×Ēמונו×Ē × ×‘×—×¨×•×Ē", "stacked_assets_count": "{count, plural, one {×Ēמונה # × ×ĸרמה} other {# ×Ēמונו×Ē × ×ĸרמו}}", "stacktrace": "Stack trace", @@ -2051,10 +2185,6 @@ "start_date_before_end_date": "×Ēאריך הה×Ēחלה חייב להיו×Ē ×œ×¤× ×™ ×Ēאריך הסיום", "state": "מדינה", "status": "מ×Ļב", - "step_delete": "מחיק×Ē ×Š×œ×‘", - "step_delete_confirm": "האם א×Ēה בטוח שבר×Ļונך למחוק שלב זה?", - "step_details": "פרטי השלב", - "steps": "שלבים", "stop_casting": "הפסק×Ē ×Š×™×“×•×¨", "stop_motion_photo": "×ĸ×Ļור ×Ēמונה ×ĸם ×Ēנו×ĸה", "stop_photo_sharing": "להפסיק לש×Ē×Ŗ א×Ē ×”×Ēמונו×Ē ×Š×œ×š?", @@ -2075,18 +2205,17 @@ "swap_merge_direction": "×”×—×œ×Ŗ כיוון מיזוג", "sync": "סנכרן", "sync_albums": "סנכרן אלבומים", + "sync_albums_manual_subtitle": "סנכרן א×Ē ×›×œ הסרטונים וה×Ēמונו×Ē ×Š×”×•×ĸלו לאלבומי הגיבוי שנבחרו", "sync_local": "סנכרן מקומי", "sync_remote": "סנכרן מהשר×Ē", "sync_status": "סטטוס סנכרון", "sync_status_subtitle": "ה×Ļג ונהל א×Ē ×ž×ĸרכ×Ē ×”×Ą× ×›×¨×•×Ÿ", "sync_upload_album_setting_subtitle": "×Ļור וה×ĸלה ×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× שלך לאלבומים שנבחרו ביישום", - "system_theme": "×ĸרכ×Ē × ×•×Š× של המ×ĸרכ×Ē", - "system_theme_command_description": "הש×Ēמ׊ ב×ĸרכ×Ē ×”×ĸי×Ļוב של המ×ĸרכ×Ē ({value})", "tag": "×Ēג", "tag_assets": "×Ēיוג ×Ēמונו×Ē", "tag_created": "נו×Ļר ×Ēג: {tag}", - "tag_face": "×Ēייג פנים", "tag_feature_description": "×ĸיון ב×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× שקוב×Ļו ×ĸל ידי נושאי ×Ēג לוגיים", + "tag_not_found_question": "לא ני×Ēן למ×Ļוא ×Ēג? י×Ļיר×Ē ×Ēג חדש.", "tag_people": "×Ēייג אנשים", "tag_updated": "×Ēג מ×ĸודכן: {tag}", "tagged_assets": "×Ēויגו {count, plural, one {×Ēמונה #} other {# ×Ēמונו×Ē}}", @@ -2101,10 +2230,15 @@ "theme_setting_asset_list_tiles_per_row_title": "מספר ×Ēמונו×Ē ×‘×›×œ שורה ({count})", "theme_setting_colorful_interface_subtitle": "החל א×Ē ×”×Ļב×ĸ ה×ĸיקרי למשטחי רק×ĸ.", "theme_setting_colorful_interface_title": "ממ׊ק ×Ļב×ĸוני", + "theme_setting_image_viewer_quality_subtitle": "ה×Ēאם א×Ē ×”××™×›×•×Ē ×Š×œ מ×Ļיג פרטי ה×Ēמונו×Ē", + "theme_setting_image_viewer_quality_title": "איכו×Ē ×ž×Ļיג ×Ēמונו×Ē", "theme_setting_primary_color_subtitle": "בחר ×Ļב×ĸ לפ×ĸולו×Ē ×ĸיקריו×Ē ×•×”×“×’×Š×•×Ē.", "theme_setting_primary_color_title": "×Ļב×ĸ ×ĸיקרי", "theme_setting_system_primary_color_title": "הש×Ēמ׊ ב×Ļב×ĸ המ×ĸרכ×Ē", "theme_setting_system_theme_switch": "אוטומטי (×ĸקוב אחרי הגדר×Ē ×ž×ĸרכ×Ē)", + "theme_setting_theme_subtitle": "בחר א×Ē ×”×’×“×¨×Ē ×ĸרכ×Ē ×”× ×•×Š× של היישום", + "theme_setting_three_stage_loading_subtitle": "ט×ĸינה בשלושה שלבים ×ĸשויה לשפר א×Ē ×‘×™×Ļו×ĸי הט×ĸינה אבל גורמ×Ē ×‘××•×¤×Ÿ מ׊מ×ĸו×Ēי ל×ĸומס ר׊×Ē ×’×‘×•×” יו×Ēר", + "theme_setting_three_stage_loading_title": "אפ׊ר ט×ĸינה בשלושה שלבים", "then": "אז", "they_will_be_merged_together": "הם י×Ēמזגו יחד", "third_party_resources": "משאבי ×Ļד שלישי", @@ -2130,17 +2264,23 @@ "trash_all": "ה×ĸבר הכל לאשפה", "trash_count": "ה×ĸבר לאשפה {count, number}", "trash_delete_asset": "ה×ĸבר לאשפה/מחק ×Ēמונה", + "trash_emptied": "האשפה רוקנה", "trash_no_results_message": "×Ēמונו×Ē ×•×Ą×¨×˜×•× ×™× שהו×ĸברו לאשפה יופי×ĸו כאן.", "trash_page_delete_all": "מחק הכל", + "trash_page_empty_trash_dialog_content": "האם בר×Ļונך לרוקן א×Ē ×”×Ēמונו×Ē ×Š×‘××Š×¤×”? הפריטים האלה ימחקו ל×Ļמי×Ēו×Ē ×ž×”×Š×¨×Ē", "trash_page_info": "פריטים באשפה ימחקו ל×Ļמי×Ēו×Ē ×œ××—×¨ {days} ימים", + "trash_page_no_assets": "אין ×Ēמונו×Ē ×‘××Š×¤×”", + "trash_page_restore_all": "שחזר הכל", + "trash_page_select_assets_btn": "בחר ×Ēמונו×Ē", + "trash_page_title": "אשפה ({count})", "trashed_items_will_be_permanently_deleted_after": "פריטים באשפה ימחקו ל×Ļמי×Ēו×Ē ×œ××—×¨ {days, plural, one {יום #} other {# ימים}}.", "trigger": "טריגר", - "trigger_asset_metadata_extraction": "חילו×Ĩ מטא־נ×Ēונים של ×Ēמונו×Ē", - "trigger_asset_metadata_extraction_description": "מופ×ĸל כאשר מטא־נ×Ēוני EXIF של ×Ēמונו×Ē ×ž×—×•×œ×Ļים", - "trigger_asset_uploaded": "×Ēוכן ×ĸולה", + "trigger_asset_uploaded": "נכס הו×ĸלה", "trigger_asset_uploaded_description": "מ×Ē×ĸורר כאשר נכס חדש הו×ĸלה", + "trigger_description": "אירו×ĸ ׊מ×Ēחיל א×Ē ×–×¨×™×ž×Ē ×”×ĸבודה", "trigger_person_recognized": "זוהה אדם", - "trigger_person_recognized_description": "מופ×ĸל כאשר אדם זוהה", + "trigger_person_recognized_description": "מ×Ē×ĸורר כאשר זוהה אדם", + "trigger_type": "סוג הטריגר", "troubleshoot": "פ×Ēור ב×ĸיו×Ē", "type": "סוג", "unable_to_change_pin_code": "לא ני×Ēן לשנו×Ē ××Ē ×§×•×“ ה PIN", @@ -2156,7 +2296,6 @@ "unknown": "לא ידו×ĸ", "unknown_country": "מדינה לא ידו×ĸה", "unknown_date": "×Ēאריך לא ידו×ĸ", - "unknown_schema": "סכימה לא ידו×ĸה", "unknown_year": "שנה לא ידו×ĸה", "unlimited": "בל×Ēי מוגבל", "unlink_motion_video": "בטל קישור סרטון ×Ēנו×ĸה", @@ -2169,20 +2308,23 @@ "unsaved_change": "שינוי לא נ׊מר", "unselect_all": "בטל בחירה בהכל", "unselect_all_duplicates": "בטל בחיר×Ē ×›×œ הכפילויו×Ē", + "unselect_all_in": "בטל א×Ē ×”×‘×—×™×¨×” של הכל ב {group}", "unstack": "בטל ×ĸרימה", "unstack_action_prompt": "{count} הופרדו", "unstacked_assets_count": "{count, plural, one {×Ēמונה # הוסרה} other {# ×Ēמונו×Ē ×”×•×Ą×¨×•}} מה×ĸרימה", "unsupported_field_type": "סוג שדה לא ידו×ĸ", "unsupported_file_type": "הקוב×Ĩ {file} אינו ני×Ēן לה×ĸלאה בגלל שסוג הקוב×Ĩ שלו {type} אינו × ×Ēמך.", "untagged": "לא מ×Ēיוגים", + "untitled_workflow": "זרימ×Ē ×ĸבודה ללא כו×Ēר×Ē", "up_next": "הבא ב×Ēור", "update_location_action_prompt": "×ĸדכן א×Ē ×”×ž×™×§×•× של {count} פריטים שנבחרו ×ĸם:", "updated_at": "×ĸודכן", "updated_password": "סיסמה ×ĸודכנה", "upload": "ה×ĸלאה", "upload_concurrency": "מספר ה×ĸלאו×Ē ×‘×ž×§×‘×™×œ", - "upload_day_count": "{date}: {count, plural, one {# ה×ĸלאה} other {# ה×ĸלאו×Ē}}", "upload_details": "פרטי ה×ĸלאה", + "upload_dialog_info": "האם בר×Ļונך לגבו×Ē ××Ē ×”×Ēמונו×Ē ×Š× ×‘×—×¨×• לשר×Ē?", + "upload_dialog_title": "ה×ĸלא×Ē ×Ēמונה", "upload_error_with_count": "שגיא×Ē ×”×ĸלאה ×ĸבור {count, plural, one {נכס אחד} other {# נכסים}}", "upload_errors": "ה×ĸלאה הושלמה ×ĸם {count, plural, one {שגיאה #} other {# שגיאו×Ē}}, ר×ĸנן א×Ē ×”×“×Ŗ כדי לראו×Ē ×Ēמונו×Ē ×Š×”×•×ĸלו.", "upload_finished": "ה×ĸלאה הס×Ēיימה", @@ -2192,13 +2334,9 @@ "upload_status_errors": "שגיאו×Ē", "upload_status_uploaded": "הו×ĸלה", "upload_success": "הה×ĸלאה בו×Ļ×ĸה בה×Ļלחה. ר×ĸנן א×Ē ×”×“×Ŗ כדי ל×Ļפו×Ē ×‘×Ēמונו×Ē ×Š×”×•×ĸלו.", - "upload_to_album_body": "×ĸבור מ׊×Ēמשים שאינם מ׊×Ēמשים ב×Ēכונ×Ē ×”×”×ĸלאה הידני×Ē, ני×Ēן כ×ĸ×Ē ×œ×‘×—×•×¨ ×œ×”×•×Ą×™×Ŗ ×Ēמונו×Ē ×ž×§×•×ž×™×•×Ē ×™×Š×™×¨×•×Ē ×œ××œ×‘×•× במהלך הה×ĸלאה, ואין ×ĸוד ×Ļורך לה×ĸלו×Ē ××•×Ēן ואז ×œ×”×•×Ą×™×Ŗ לאלבום מאוחר יו×Ēר.", - "upload_to_album_title": "ה×ĸלה ישירו×Ē ×œ××œ×‘×•×", "upload_to_immich": "ה×ĸלה לשר×Ē ({count})", "uploading": "מ×ĸלה", "uploading_media": "מ×ĸלה מדיה", - "uploads": "ה×ĸלאו×Ē", - "uploads_count": "{count, plural, one {# ה×ĸלאה} other {# ה×ĸלאו×Ē}}", "url": "URL", "usage": "שימוש", "use_biometric": "הש×Ēמ׊ באימו×Ē ×‘×™×•×ž×˜×¨×™", @@ -2206,7 +2344,6 @@ "use_browser_locale_description": "×ĸי×Ļוב ×Ēאריכים, זמנים, ומספרים בה×Ēבסס ×ĸל אזור השפה של הדפדפן שלך", "use_current_connection": "שימוש בחיבור הנוכחי", "use_custom_date_range": "הש×Ēמ׊ בטווח ×Ēאריכים מו×Ēאם במקום", - "use_template": "הש×Ēמ׊ ב×Ēבני×Ē", "user": "מ׊×Ēמ׊", "user_has_been_deleted": "מ׊×Ēמ׊ זה נמחק.", "user_id": "מזהה מ׊×Ēמ׊", @@ -2216,6 +2353,7 @@ "user_privacy": "פרטיו×Ē ×”×ž×Š×Ēמ׊", "user_purchase_settings": "רכישה", "user_purchase_settings_description": "ניהול הרכישה שלך", + "user_role_set": "הגדר א×Ē {user} ב×Ēור {role}", "user_usage_detail": "פרטי השימוש של המש×Ēמ׊", "user_usage_stats": "סטטיסטיקו×Ē ×Š×™×ž×•×Š בחשבון", "user_usage_stats_description": "ה×Ļג סטטיסטיקו×Ē ×Š×™×ž×•×Š בחשבון", @@ -2225,6 +2363,7 @@ "utilities": "כלים", "validate": "לאמ×Ē", "validate_endpoint_error": "נא להזין כ×Ēוב×Ē URL ×Ēקני×Ē", + "validation_error": "שגיא×Ē ×Ēיקות", "variables": "מ׊×Ēנים", "version": "גרסה", "version_announcement_closing": "החבר שלך, אלכס", @@ -2234,9 +2373,9 @@ "video": "סרטון", "video_hover_setting": "הפ×ĸל ×Ē×Ļוג×Ē ×Ą×¨×˜×•×Ÿ מקדימה ב×ĸ×Ē ×¨×™×—×•×Ŗ", "video_hover_setting_description": "הפ×ĸל ×Ē×Ļוג×Ē ×Ą×¨×˜×•×Ÿ מקדימה כאשר ה×ĸכבר ×ž×¨×—×Ŗ מ×ĸל פריט. אפילו כשהגדרה זו מושב×Ē×Ē, ני×Ēן לה×Ēחיל א×Ē ×”× ×™×’×•×Ÿ ×ĸל ידי ׍יחות מ×ĸל סמל ההפ×ĸלה.", - "video_quality": "איכו×Ē ×•×™×“××•", "videos": "סרטונים", "videos_count": "{count, plural, one {סרטון #} other {# סרטונים}}", + "videos_only": "רק סרטונים", "view": "ה×Ļג", "view_album": "ה×Ļג אלבום", "view_all": "ה×Ļג הכל", @@ -2245,29 +2384,29 @@ "view_details": "ה×Ļג פרטים", "view_in_timeline": "ראה ב×Ļיר הזמן", "view_link": "ה×Ļג קישור", + "view_links": "ה×Ļג קישורים", "view_name": "ה×Ļג", "view_next_asset": "ה×Ļג א×Ē ×”×Ēמונה הבאה", "view_previous_asset": "ה×Ļג א×Ē ×”×Ēמונה הקודמ×Ē", "view_qr_code": "ה×Ļג ברקוד", "view_similar_photos": "ה×Ļג ×Ēמונו×Ē ×“×•×ž×•×Ē", "view_stack": "ה×Ļג ×ĸרימה", + "view_user": "ה×Ļג מ׊×Ēמ׊", "viewer_remove_from_stack": "הסר מ×ĸרימה", + "viewer_stack_use_as_main_asset": "הש×Ēמ׊ כ×Ēמונה ראשי×Ē", + "viewer_unstack": "ביטול ×ĸרימה", "visibility": "נראו×Ē", "visibility_changed": "הנראו×Ē ×”×Š×Ē× ×Ēה ×ĸבור {count, plural, one {אדם #} other {# אנשים}}", "visual": "חזו×Ēי", + "visual_builder": "בונה חזו×Ēי", "waiting": "ממ×Ēין", "waiting_count": "ממ×Ēין: {count}", "warning": "אזהרה", "week": "שבו×ĸ", "welcome": "ברוכים הבאים", "welcome_to_immich": "ברוכים הבאים אל immich", - "whats_new": "מה חדש", - "whats_new_settings_subtitle": "ראה מה חדש ב־Immich", - "whats_new_version": "גרסה {version}", - "when": "מ×Ēי", "width": "רוחב", "wifi_name": "׊ם הרש×Ē ×”××œ×—×•×˜×™×Ē", - "workflow": "זרימ×Ē ×ĸבודה", "workflow_delete_prompt": "האם בר×Ļונך למחוק א×Ē ×–×¨×™×ž×Ē ×”×ĸבודה הזו?", "workflow_deleted": "זרימ×Ē ×”×ĸבודה נמחקה", "workflow_description": "×Ēיאור זרימ×Ē ×”×ĸבודה", @@ -2277,17 +2416,17 @@ "workflow_name": "׊ם זרימ×Ē ×”×ĸבודה", "workflow_navigation_prompt": "האם בר×Ļונך ל×ĸזוב מבלי לשמור א×Ē ×”×Š×™× ×•×™×™× שלך?", "workflow_summary": "סיכום זרימ×Ē ×”×ĸבודה", - "workflow_templates": "×Ēבניו×Ē ×Š×œ זרימ×Ē ×ĸבודה", "workflow_update_success": "זרימ×Ē ×”×ĸבודה ×ĸודכנה בה×Ļלחה", + "workflow_updated": "זרימ×Ē ×”×ĸבודה ×ĸודכנה", "workflows": "זרימו×Ē ×ĸבודה", "workflows_help_text": "זרימו×Ē ×ĸבודה מאטמטו×Ē ×¤×ĸולו×Ē ×ĸל הנכסים שלך בה×Ēבסס ×ĸל טריגרים ומסננים", "wrong_pin_code": "קוד PIN שגוי", - "x_of_total": "{x}/{total}", "year": "שנה", "years_ago": "לפני {years, plural, one {שנה #} other {# שנים}}", "yes": "כן", "you_dont_have_any_shared_links": "אין לך קישורים משו×Ēפים", "your_wifi_name": "׊ם אינטרנט אלחוטי שלך", "zero_to_clear_rating": "יש ללחו×Ĩ ×ĸל 0 כדי לנקו×Ē ××Ē ×“×™×¨×•×’ ה×Ēמונה", - "zoom_image": "זום ל×Ēמונה" + "zoom_image": "זום ל×Ēמונה", + "zoom_to_bounds": "ה×Ēמקד באזור" } diff --git a/i18n/hi.json b/i18n/hi.json index 5fc09c0c00..668952775a 100644 --- a/i18n/hi.json +++ b/i18n/hi.json @@ -10,18 +10,24 @@ "active": "⤏⤕āĨā¤°ā¤ŋ⤝", "active_count": "⤏⤕āĨā¤°ā¤ŋ⤝: {count}", "activity": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ", + "activity_changed": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ {enabled, select, true {enabled} other {disabled}}", "add": "ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_a_description": "ā¤ā¤• ā¤ĩā¤ŋā¤ĩ⤰⤪ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_a_location": "ā¤ā¤• ⤏āĨā¤Ĩā¤žā¤¨ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_a_name": "ā¤¨ā¤žā¤Ž ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_a_title": "ā¤ā¤• ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_action": "ā¤•ā¤žā¤°āĨā¤°ā¤ĩā¤žā¤ˆ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_action_description": "⤕āĨ‹ā¤ˆ ā¤ā¤•āĨā¤ļ⤍ ⤜āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨā¤˛ā¤ŋ⤕ ⤕⤰āĨ‡ā¤‚", "add_assets": "ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", "add_birthday": "⤅ā¤Ē⤍āĨ‡ ⤜⤍āĨā¤Žā¤Ļā¤ŋ⤍ ā¤•ā¤ž ⤉⤞āĨā¤˛āĨ‡ā¤– ⤕⤰āĨ‡ā¤‚", "add_endpoint": "endpoint ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_exclusion_pattern": "⤅ā¤Ēā¤ĩā¤žā¤Ļ ⤉ā¤Ļā¤žā¤šā¤°ā¤Ŗ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_filter": "āĨžā¤ŋ⤞āĨā¤Ÿā¤° ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_filter_description": "ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ā¤•ā¤‚ā¤ĄāĨ€ā¤ļ⤍ ⤜āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨā¤˛ā¤ŋ⤕ ⤕⤰āĨ‡ā¤‚", "add_location": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_more_users": "⤅⤧ā¤ŋ⤕ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_partner": "⤜āĨ‹ā¤Ąā¤ŧāĨ€ā¤Ļā¤žā¤° ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_path": "ā¤Ēā¤Ĩ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_photos": "ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_tag": "⤚ā¤ŋā¤šāĨā¤¨ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "add_to": "ā¤‡ā¤¸ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ‡ā¤‚â€Ļ", @@ -29,11 +35,14 @@ "add_to_album_bottom_sheet_added": "{album} ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_to_album_bottom_sheet_already_exists": "{album} ā¤ŽāĨ‡ā¤‚ ā¤Ēā¤šā¤˛āĨ‡ ⤏āĨ‡ ā¤šāĨˆ", "add_to_album_bottom_sheet_some_local_assets": "⤕āĨā¤› ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ā¤ā¤¸āĨ‡ā¤Ÿ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤¨ā¤šāĨ€ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ ā¤œā¤ž ⤏⤕āĨ‡", + "add_to_album_toggle": "{album} ⤕āĨ‡ ⤞ā¤ŋā¤ ⤚⤝⤍ ⤟āĨ‰ā¤—⤞ ⤕⤰āĨ‡ā¤‚", "add_to_albums": "ā¤ā¤•ā¤žā¤§ā¤ŋ⤕ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ‡", "add_to_albums_count": "ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ‡ā¤‚ ({count})", "add_to_bottom_bar": "ā¤‡ā¤¸ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", + "add_to_shared_album": "ā¤ļāĨ‡ā¤¯ā¤° ⤕ā¤ŋā¤ ā¤—ā¤ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "add_upload_to_stack": "⤏āĨā¤ŸāĨˆā¤• ā¤ŽāĨ‡ā¤‚ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "add_url": "URL ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "add_workflow_step": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ⤏āĨā¤ŸāĨ‡ā¤Ē ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", "added_to_archive": "⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "added_to_favorites": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ā¤ž ā¤—ā¤¯ā¤ž", "added_to_favorites_count": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤ŽāĨ‡ā¤‚ {count, number} ā¤Ąā¤žā¤˛ā¤ž ā¤—ā¤¯ā¤ž", @@ -72,7 +81,6 @@ "cron_expression_description": "⤕āĨā¤°āĨ‰ā¤¨ ā¤ĒāĨā¤°ā¤žā¤°āĨ‚ā¤Ē ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤕āĨ‡ ⤏āĨā¤•āĨˆā¤¨ā¤ŋ⤂⤗ ā¤…ā¤‚ā¤¤ā¤°ā¤žā¤˛ ⤏āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚āĨ¤ ⤅⤧ā¤ŋ⤕ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨƒā¤Ēā¤¯ā¤ž ⤕āĨā¤°āĨ‹ā¤¨ā¤ŸāĨˆā¤Ŧ ⤗āĨā¤°āĨ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "cron_expression_presets": "⤕āĨā¤°āĨ‰ā¤¨ ⤅⤭ā¤ŋā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤ĒāĨā¤°āĨ€ā¤¸āĨ‡ā¤Ÿ", "disable_login": "⤞āĨ‰ā¤—ā¤ŋ⤍ ⤅⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚", - "download_csv": "⤏āĨ€ā¤ā¤¸ā¤ĩāĨ€ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "duplicate_detection_job_description": "ā¤¸ā¤Žā¤žā¤¨ ⤛ā¤ĩā¤ŋ⤝āĨ‹ā¤‚ ā¤•ā¤ž ā¤Ēā¤¤ā¤ž ā¤˛ā¤—ā¤žā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤Ē⤰ ā¤Žā¤ļāĨ€ā¤¨ ⤞⤰āĨā¤¨ā¤ŋ⤂⤗ ā¤šā¤˛ā¤žā¤ā¤‚āĨ¤ ā¤¯ā¤š ā¤•ā¤žā¤°āĨā¤¯ā¤•āĨā¤ˇā¤Žā¤¤ā¤ž ⤏āĨā¤Žā¤žā¤°āĨā¤Ÿ ⤖āĨ‹ā¤œ ā¤Ē⤰ ⤍ā¤ŋ⤰āĨā¤­ā¤° ⤕⤰⤤āĨ€ ā¤šāĨˆ", "exclusion_pattern_description": "Exclusion ā¤ĒāĨˆā¤Ÿā¤°āĨā¤¨ ⤆ā¤Ē⤕āĨ‹ ⤅ā¤Ē⤍āĨ€ ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ⤕āĨ‹ ⤏āĨā¤•āĨˆā¤¨ ⤕⤰⤤āĨ‡ ā¤¸ā¤Žā¤¯ ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‹ā¤‚ ⤔⤰ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤°āĨ‹ā¤‚ ⤕āĨ‹ ⤅⤍ā¤ĻāĨ‡ā¤–ā¤ž ⤕⤰⤍āĨ‡ ā¤ĻāĨ‡ā¤¤ā¤ž ā¤šāĨˆāĨ¤ ā¤¯ā¤š ⤉ā¤Ē⤝āĨ‹ā¤—āĨ€ ā¤šāĨˆ ⤝ā¤Ļā¤ŋ ⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ⤐⤏āĨ‡ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤šāĨˆā¤‚ ⤜ā¤ŋā¤¨ā¤ŽāĨ‡ā¤‚ ⤐⤏āĨ€ ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤šāĨˆā¤‚ ⤜ā¤ŋ⤍āĨā¤šāĨ‡ā¤‚ ⤆ā¤Ē ā¤†ā¤¯ā¤žā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚, ⤜āĨˆā¤¸āĨ‡ RAW ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚āĨ¤", "export_config_as_json_description": "ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ ⤕āĨ‹ JSON ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤕āĨ‡ ⤰āĨ‚ā¤Ē ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", @@ -107,7 +115,6 @@ "image_thumbnail_quality_description": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤕āĨ€ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž 1-100 ⤤⤕āĨ¤ ā¤‰ā¤šāĨā¤šā¤¤ā¤° ā¤ŦāĨ‡ā¤šā¤¤ā¤° ā¤šāĨˆ, ⤞āĨ‡ā¤•ā¤ŋ⤍ ā¤Ŧā¤Ąā¤ŧāĨ€ ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤Ŧā¤¨ā¤žā¤¤ā¤ž ā¤šāĨˆ ⤔⤰ ⤐ā¤Ē ⤕āĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤕āĨā¤°ā¤ŋā¤¯ā¤žā¤ļāĨ€ā¤˛ā¤¤ā¤ž ⤕āĨ‹ ā¤•ā¤Ž ⤕⤰ ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆāĨ¤", "image_thumbnail_title": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸", "import_config_from_json_description": "JSON ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤕āĨ‡ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ ⤇⤂ā¤ĒāĨ‹ā¤°āĨā¤Ÿ ⤕⤰āĨ‡ā¤‚", - "integrity_checks_checksum_files_time_limit": "ā¤¸ā¤Žā¤¯ ⤏āĨ€ā¤Žā¤ž", "job_concurrency": "{job} ā¤¸ā¤Žā¤°āĨ‚ā¤Ēā¤¤ā¤ž", "job_created": "⤍āĨŒā¤•⤰āĨ€ ā¤Ŧā¤¨ā¤žā¤ˆ ā¤—ā¤ˆ", "job_not_concurrency_safe": "ā¤¯ā¤š ā¤•ā¤žā¤°āĨā¤¯ (⤜āĨ‰ā¤Ŧ) ā¤¸ā¤Žā¤ĩ⤰āĨā¤¤āĨ€-⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆāĨ¤", @@ -447,6 +454,8 @@ "advanced_settings_clear_image_cache": "ā¤‡ā¤ŽāĨ‡ā¤œ ⤕āĨˆā¤ļ (cache) ā¤¸ā¤žā¤Ģā¤ŧ ⤕⤰āĨ‡ā¤‚", "advanced_settings_clear_image_cache_error": "ā¤‡ā¤ŽāĨ‡ā¤œ ⤕āĨˆā¤ļ (cache) ā¤¸ā¤žā¤Ģā¤ŧ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤ž", "advanced_settings_clear_image_cache_success": "{size} ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤¸ā¤žā¤Ģā¤ŧ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "advanced_settings_enable_alternate_media_filter_subtitle": "⤏ā¤ŋ⤂⤕ ⤕āĨ‡ ā¤ĻāĨŒā¤°ā¤žā¤¨ ā¤ĩāĨˆā¤•⤞āĨā¤Ēā¤ŋ⤕ ā¤Žā¤žā¤¨ā¤Ļā¤‚ā¤ĄāĨ‹ā¤‚ ⤕āĨ‡ ā¤†ā¤§ā¤žā¤° ā¤Ē⤰ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ⤕āĨ‹ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤇⤏ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚āĨ¤ ⤇⤏āĨ‡ ⤕āĨ‡ā¤ĩ⤞ ⤤⤭āĨ€ ā¤†ā¤œā¤ŧā¤Žā¤žā¤ā¤ ⤜ā¤Ŧ ⤆ā¤Ē⤕āĨ‹ ⤐ā¤Ē ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ⤏⤭āĨ€ ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ā¤•ā¤ž ā¤Ēā¤¤ā¤ž ā¤˛ā¤—ā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž ā¤šāĨ‹āĨ¤", + "advanced_settings_enable_alternate_media_filter_title": "[ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤•] ā¤ĩāĨˆā¤•⤞āĨā¤Ēā¤ŋ⤕ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏ā¤ŋ⤂⤕ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚", "advanced_settings_log_level_title": "⤞āĨ‰ā¤— ⤏āĨā¤¤ā¤°:{level}", "advanced_settings_prefer_remote_subtitle": "⤕āĨā¤› ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ā¤ā¤¸āĨ‡ā¤Ÿ ⤏āĨ‡ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤Ŧā¤šāĨā¤¤ ⤧āĨ€ā¤ŽāĨ‡ ā¤šāĨ‹ā¤¤āĨ‡ ā¤šāĨˆā¤‚āĨ¤ ⤇⤏⤕āĨ‡ ā¤Ŧā¤œā¤žā¤¯, ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ā¤‡ā¤ŽāĨ‡ā¤œ ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤇⤏ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤕āĨ‹ ⤏⤕āĨā¤°ā¤ŋ⤝ ⤕⤰āĨ‡ā¤‚āĨ¤", "advanced_settings_prefer_remote_title": "ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ⤛ā¤ĩā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋā¤•ā¤¤ā¤ž ā¤ĻāĨ‡ā¤‚", @@ -454,6 +463,8 @@ "advanced_settings_proxy_headers_title": "⤕⤏āĨā¤Ÿā¤Ž ā¤ĒāĨā¤°āĨ‰ā¤•āĨā¤¸āĨ€ ā¤šāĨ‡ā¤Ąā¤° [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", "advanced_settings_readonly_mode_subtitle": "⤰āĨ€ā¤Ą-⤓⤍⤞āĨ€ ā¤ĒāĨā¤°ā¤Ŗā¤žā¤˛āĨ€ ⤕āĨ‹ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤¤ā¤ž ā¤šāĨˆ ā¤œā¤šā¤žā¤‚ ⤚ā¤ŋ⤤āĨā¤° ⤕āĨ‹ ⤕āĨ‡ā¤ĩ⤞ ā¤ĻāĨ‡ā¤–ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆ, ā¤ā¤•ā¤žā¤§ā¤ŋ⤕ ⤚ā¤ŋ⤤āĨā¤°āĨ‹ā¤‚ ā¤•ā¤ž ⤚⤝⤍ ā¤•ā¤°ā¤¨ā¤ž, ā¤¸ā¤žā¤ā¤ž ā¤•ā¤°ā¤¨ā¤ž, ā¤•ā¤žā¤¸āĨā¤Ÿā¤ŋ⤂⤗ ā¤•ā¤°ā¤¨ā¤ž, ā¤šā¤Ÿā¤žā¤¨ā¤ž ⤜āĨˆā¤¸āĨ€ ⤏⤭āĨ€ ⤚āĨ€ā¤œā¤ŧāĨ‡ā¤‚ ⤅⤕āĨā¤ˇā¤Ž ā¤šāĨˆā¤‚āĨ¤ ā¤ŽāĨā¤–āĨā¤¯ ⤏āĨā¤•āĨā¤°āĨ€ā¤¨ ā¤ŽāĨ‡ā¤‚ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž- ⤅ā¤ĩā¤¤ā¤žā¤° ⤕āĨ‡ ā¤Žā¤žā¤§āĨā¤¯ā¤Ž ⤏āĨ‡ ⤰āĨ€ā¤Ą-⤓⤍⤞āĨ€ ā¤ĒāĨā¤°ā¤Ŗā¤žā¤˛āĨ€ ⤕āĨ‹ ⤏⤕āĨā¤ˇā¤Ž/⤅⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚", "advanced_settings_readonly_mode_title": "⤰āĨ€ā¤Ą-⤓⤍⤞āĨ€ ā¤ĒāĨā¤°ā¤Ŗā¤žā¤˛āĨ€", + "advanced_settings_self_signed_ssl_subtitle": "⤏⤰āĨā¤ĩ⤰ ā¤ā¤‚ā¤Ąā¤ĒāĨ‰ā¤‡ā¤‚ā¤Ÿ ⤕āĨ‡ ⤞ā¤ŋā¤ SSL ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ⤏⤤āĨā¤¯ā¤žā¤Ē⤍ ⤕āĨ‹ ⤛āĨ‹ā¤Ąā¤ŧ ā¤ĻāĨ‡ā¤¤ā¤ž ā¤šāĨˆāĨ¤ ⤏āĨā¤ĩ-ā¤šā¤¸āĨā¤¤ā¤žā¤•āĨā¤ˇā¤°ā¤ŋ⤤ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤°āĨ‹ā¤‚ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤šāĨˆāĨ¤", + "advanced_settings_self_signed_ssl_title": "⤏āĨā¤ĩ-ā¤šā¤¸āĨā¤¤ā¤žā¤•āĨā¤ˇā¤°ā¤ŋ⤤ SSL ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤°āĨ‹ā¤‚ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚ [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", "advanced_settings_sync_remote_deletions_subtitle": "ā¤ĩāĨ‡ā¤Ŧ ā¤Ē⤰ ā¤•ā¤žā¤°āĨā¤°ā¤ĩā¤žā¤ˆ ⤕ā¤ŋā¤ ā¤œā¤žā¤¨āĨ‡ ā¤Ē⤰ ⤇⤏ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤Ē⤰ ⤕ā¤ŋ⤏āĨ€ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤕āĨ‹ ⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤ ā¤¯ā¤ž ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "advanced_settings_sync_remote_deletions_title": "ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ā¤ĩā¤ŋ⤞āĨ‹ā¤Ē⤍ ⤏ā¤ŋ⤂⤕ ⤕⤰āĨ‡ā¤‚ [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", "advanced_settings_tile_subtitle": "⤉⤍āĨā¤¨ā¤¤ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸", @@ -469,18 +480,31 @@ "album_delete_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ā¤ā¤˛āĨā¤Ŧā¤Ž {album} ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "album_delete_confirmation_description": "⤝ā¤Ļā¤ŋ ā¤¯ā¤š ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ, ⤤āĨ‹ ⤅⤍āĨā¤¯ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤇⤏āĨ‡ ā¤ā¤•āĨā¤¸āĨ‡ā¤¸ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ā¤Ēā¤žā¤ā¤‚ā¤—āĨ‡āĨ¤", "album_deleted": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "album_info_card_backup_album_excluded": "⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", + "album_info_card_backup_album_included": "ā¤ļā¤žā¤Žā¤ŋ⤞", "album_info_updated": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤕āĨ€ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕āĨ€ ā¤—ā¤ˆ", + "album_leave": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤛āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚?", + "album_leave_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ {album} ⤛āĨ‹ā¤Ąā¤ŧā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "album_name": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤•ā¤ž ā¤¨ā¤žā¤Ž", "album_options": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", "album_remove_user": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤šā¤Ÿā¤žā¤ā¤‚?", "album_remove_user_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ {user} ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "album_search_not_found": "⤆ā¤Ē⤕āĨ€ ⤖āĨ‹ā¤œ ⤏āĨ‡ ā¤ŽāĨ‡ā¤˛ ā¤–ā¤žā¤¤ā¤ž ⤕āĨ‹ā¤ˆ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤ž", + "album_selected": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤚āĨā¤¨ā¤ž ā¤—ā¤¯ā¤ž", "album_share_no_users": "ā¤ā¤¸ā¤ž ā¤˛ā¤—ā¤¤ā¤ž ā¤šāĨˆ ⤕ā¤ŋ ⤆ā¤Ē⤍āĨ‡ ā¤¯ā¤š ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏⤭āĨ€ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤žā¤“⤂ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¸ā¤žā¤ā¤ž ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤šāĨˆ ā¤¯ā¤ž ⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ā¤¸ā¤žā¤ā¤ž ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨ‹ā¤ˆ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆāĨ¤", "album_summary": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¸ā¤žā¤°ā¤žā¤‚ā¤ļ", "album_updated": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "album_updated_setting_description": "⤜ā¤Ŧ ⤕ā¤ŋ⤏āĨ€ ā¤¸ā¤žā¤ā¤ž ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ⤍⤈ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤ ā¤šāĨ‹ā¤‚ ⤤āĨ‹ ā¤ā¤• ā¤ˆā¤ŽāĨ‡ā¤˛ ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ⤕⤰āĨ‡ā¤‚", "album_upload_assets": "⤅ā¤Ē⤍āĨ‡ ⤕⤂ā¤ĒāĨā¤¯āĨ‚ā¤Ÿā¤° ⤏āĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚ ⤔⤰ ⤉⤍āĨā¤šāĨ‡ā¤‚ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", + "album_user_left": "ā¤Ŧā¤žā¤¯ā¤žā¤ {album}", + "album_user_removed": "{user} ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "album_viewer_appbar_delete_confirm": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤇⤏ ā¤ā¤˛āĨā¤Ŧā¤Ž ⤕āĨ‹ ⤅ā¤Ē⤍āĨ‡ ā¤–ā¤žā¤¤āĨ‡ ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "album_viewer_appbar_share_err_delete": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤šā¤Ÿā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", + "album_viewer_appbar_share_err_leave": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤛āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", + "album_viewer_appbar_share_err_remove": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤šā¤Ÿā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤žā¤ā¤ ā¤šāĨˆā¤‚", + "album_viewer_appbar_share_err_title": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤•ā¤ž ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤Ŧā¤Ļ⤞⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", + "album_viewer_appbar_share_leave": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤛āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", + "album_viewer_appbar_share_to": "ā¤¸ā¤žā¤ā¤ž ⤕⤰āĨ‡ā¤‚", "album_viewer_page_share_add_users": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", "album_with_link_access": "⤞ā¤ŋ⤂⤕ ā¤ĩā¤žā¤˛āĨ‡ ⤕ā¤ŋ⤏āĨ€ ⤭āĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤕āĨ‹ ⤇⤏ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ā¤ĻāĨ‡ā¤‚āĨ¤", "albums": "ā¤ā¤˛ā¤Ŧā¤Ž", @@ -489,12 +513,14 @@ "albums_default_sort_order_description": "⤍⤝āĨ‡ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤¤āĨ‡ ā¤¸ā¤Žā¤¯ ⤆⤰⤂⤭ā¤ŋ⤕ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤏āĨ‰ā¤°āĨā¤Ÿ ⤕āĨā¤°ā¤ŽāĨ¤", "albums_feature_description": "ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤•ā¤ž ⤏⤂⤗āĨā¤°ā¤š ⤜ā¤ŋ⤏āĨ‡ ⤅⤍āĨā¤¯ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤žā¤“⤂ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆāĨ¤", "albums_on_device_count": "ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤Ē⤰ ā¤ā¤˛āĨā¤Ŧā¤Ž ({count})", + "albums_selected": "{count, plural, one {# ā¤ā¤˛āĨā¤Ŧā¤Ž ⤚āĨā¤¨ā¤ž ā¤—ā¤¯ā¤ž} other {# ā¤ā¤˛āĨā¤Ŧā¤Ž ⤚āĨā¤¨āĨ‡ ā¤—ā¤}}", "all": "⤏⤭āĨ€", "all_albums": "⤏⤭āĨ€ ā¤ā¤˛ā¤Ŧā¤Ž", "all_people": "⤏⤭āĨ€ ⤞āĨ‹ā¤—", "all_photos": "⤏⤭āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹", "all_videos": "⤏⤭āĨ€ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "allow_dark_mode": "ā¤Ąā¤žā¤°āĨā¤• ā¤ŽāĨ‹ā¤Ą ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚", + "allow_edits": "⤏⤂ā¤Ēā¤žā¤Ļ⤍ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚", "allow_public_user_to_download": "ā¤¸ā¤žā¤°āĨā¤ĩ⤜⤍ā¤ŋ⤕ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚", "allow_public_user_to_upload": "ā¤¸ā¤žā¤°āĨā¤ĩ⤜⤍ā¤ŋ⤕ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚", "allowed": "⤅⤍āĨā¤Žā¤¤", @@ -502,12 +528,14 @@ "always_keep": "ā¤šā¤ŽāĨ‡ā¤ļā¤ž ⤰⤖āĨ‡ā¤‚", "always_keep_photos_hint": "“ā¤ĢāĨā¤°āĨ€ ⤅ā¤Ē ⤏āĨā¤ĒāĨ‡ā¤¸â€ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤍āĨ‡ ā¤Ē⤰ ⤇⤏ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤕āĨ€ ⤏⤭āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤Ŧ⤍āĨ€ ā¤°ā¤šāĨ‡ā¤‚⤗āĨ€āĨ¤", "always_keep_videos_hint": "“ā¤ĢāĨā¤°āĨ€ ⤅ā¤Ē ⤏āĨā¤ĒāĨ‡ā¤¸â€ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤍āĨ‡ ā¤Ē⤰ ⤇⤏ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤕āĨ€ ⤏⤭āĨ€ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤Ŧ⤍āĨ€ ā¤°ā¤šāĨ‡ā¤‚⤗āĨ€āĨ¤", + "anti_clockwise": "ā¤ĩā¤žā¤Žā¤žā¤ĩ⤰āĨā¤¤", "api_key": "ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨ€", "api_key_description": "ā¤¯ā¤š ⤕āĨ€ ⤕āĨ‡ā¤ĩ⤞ ā¤ā¤• ā¤Ŧā¤žā¤° ā¤Ļā¤ŋā¤–ā¤žā¤ˆ ā¤œā¤žā¤ā¤—āĨ€āĨ¤ ā¤ĩā¤ŋā¤‚ā¤ĄāĨ‹ ā¤Ŧ⤂ā¤Ļ ⤕⤰⤍āĨ‡ ⤏āĨ‡ ā¤Ēā¤šā¤˛āĨ‡ ⤕āĨƒā¤Ēā¤¯ā¤ž ⤇⤏āĨ‡ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤¨ā¤ž ⤏āĨā¤¨ā¤ŋā¤ļāĨā¤šā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚āĨ¤āĨ¤", "api_key_empty": "⤆ā¤Ēā¤•ā¤ž ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨā¤‚ā¤œāĨ€ ā¤¨ā¤žā¤Ž ā¤–ā¤žā¤˛āĨ€ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨ‹ā¤¨ā¤ž ā¤šā¤žā¤šā¤ŋā¤", "api_keys": "ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨ€ā¤œ", "app_architecture_variant": "⤰āĨ‚ā¤Ēā¤žā¤¨āĨā¤¤ā¤° (⤏āĨā¤Ĩā¤žā¤Ē⤤āĨā¤¯/⤆⤰āĨā¤•ā¤ŋ⤟āĨ‡ā¤•āĨā¤šā¤°)", "app_bar_signout_dialog_content": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤏āĨā¤¨ā¤ŋā¤ļāĨā¤šā¤ŋ⤤ ā¤šāĨˆā¤‚ ⤕ā¤ŋ ⤆ā¤Ē ⤞āĨ‰ā¤— ā¤†ā¤‰ā¤Ÿ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", + "app_bar_signout_dialog_ok": "ā¤šā¤žā¤", "app_bar_signout_dialog_title": "⤞āĨ‰ā¤— ā¤†ā¤‰ā¤Ÿ", "app_download_links": "⤐ā¤Ē ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤞ā¤ŋ⤂⤕", "app_settings": "ā¤ā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ļ⤍ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗", @@ -518,18 +546,27 @@ "archive": "⤏⤂⤗āĨā¤°ā¤šā¤žā¤˛ā¤¯", "archive_action_prompt": "{count} ⤕āĨ‹ ⤏⤂⤗āĨā¤°ā¤š ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", "archive_or_unarchive_photo": "ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤕āĨ‹ ⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ā¤¯ā¤ž ⤅⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ⤕⤰āĨ‡ā¤‚", + "archive_page_no_archived_assets": "⤕āĨ‹ā¤ˆ ⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋ⤞āĨ€", + "archive_page_title": "ā¤ĒāĨā¤°ā¤žā¤˛āĨ‡ā¤– ({count})", "archive_size": "ā¤ĒāĨā¤°ā¤žā¤˛āĨ‡ā¤– ā¤†ā¤•ā¤žā¤°", "archive_size_description": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏⤂⤗āĨā¤°ā¤š ā¤†ā¤•ā¤žā¤° ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰ ⤕⤰āĨ‡ā¤‚ (GiB ā¤ŽāĨ‡ā¤‚)", "archived": "⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤", "archived_count": "{count, plural, other {# ⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ⤕ā¤ŋā¤ ā¤—ā¤}}", "are_these_the_same_person": "⤕āĨā¤¯ā¤ž ⤝āĨ‡ ā¤ĩā¤šāĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤šāĨˆā¤‚?", "are_you_sure_to_do_this": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤¸āĨā¤¤ā¤ĩ ā¤ŽāĨ‡ā¤‚ ⤇⤏āĨ‡ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", + "array_field_not_fully_supported": "Array ā¤Ģā¤ŧāĨ€ā¤˛āĨā¤Ą ⤕āĨ‡ ⤞ā¤ŋā¤ JSON ⤕āĨ‹ ā¤ŽāĨˆā¤¨āĨā¤¯āĨā¤…⤞ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤¨ā¤ž ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤šāĨˆ", + "asset_action_delete_err_read_only": "⤕āĨ‡ā¤ĩ⤞ ā¤Ēā¤ĸā¤ŧ⤍āĨ‡ ⤝āĨ‹ā¤—āĨā¤¯ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(⤓⤂) ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¯ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆ", + "asset_action_share_err_offline": "⤑ā¤Ģā¤ŧā¤˛ā¤žā¤‡ā¤¨ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤ā¤) ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤œā¤ž ⤏⤕⤤āĨ€, ⤛āĨ‹ā¤Ąā¤ŧāĨ€ ā¤œā¤ž ā¤°ā¤šāĨ€ ā¤šāĨˆ", "asset_added_to_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ā¤ž ā¤—ā¤¯ā¤ž", "asset_adding_to_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆâ€Ļ", "asset_created": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "asset_description_updated": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", + "asset_filename_is_offline": "ā¤ā¤¸āĨ‡ā¤Ÿ {filename} ⤑ā¤Ģā¤ŧā¤˛ā¤žā¤‡ā¤¨ ā¤šāĨˆ", + "asset_has_unassigned_faces": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤ŽāĨ‡ā¤‚ ⤅⤍ā¤ŋ⤰āĨā¤§ā¤žā¤°ā¤ŋ⤤ ⤚āĨ‡ā¤šā¤°āĨ‡ ā¤šāĨˆā¤‚", "asset_hashing": "ā¤šāĨˆā¤ļā¤ŋ⤂⤗â€Ļ", "asset_list_group_by_sub_title": "ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ā¤¸ā¤ŽāĨ‚ā¤š ā¤Ŧā¤¨ā¤žā¤ā¤‚", + "asset_list_layout_settings_dynamic_layout_title": "⤗⤤ā¤ŋā¤ļāĨ€ā¤˛ ⤞āĨ‡ā¤†ā¤‰ā¤Ÿ", + "asset_list_layout_settings_group_automatically": "⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤", "asset_list_layout_settings_group_by": "ā¤¸ā¤ŽāĨ‚ā¤š ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž", "asset_list_layout_settings_group_by_month_day": "ā¤Žā¤šāĨ€ā¤¨ā¤ž + ā¤Ļā¤ŋ⤍", "asset_list_layout_sub_title": "⤞āĨ‡ā¤†ā¤‰ā¤Ÿ", @@ -540,26 +577,36 @@ "asset_not_found_on_icloud": "iCloud ā¤Ē⤰ ā¤ā¤¸āĨ‡ā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤žāĨ¤ iCloud ā¤ŽāĨ‡ā¤‚ ā¤–ā¤°ā¤žā¤Ŧ ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤šāĨ‹ā¤¨āĨ‡ ⤕āĨ‡ ā¤•ā¤žā¤°ā¤Ŗ ā¤ā¤¸āĨ‡ā¤Ÿ ⤤⤕ ā¤Ēā¤šāĨā¤ā¤šā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž", "asset_offline": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤑ā¤Ģā¤ŧā¤˛ā¤žā¤‡ā¤¨", "asset_offline_description": "ā¤¯ā¤š ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤑ā¤Ģā¤ŧā¤˛ā¤žā¤‡ā¤¨ ā¤šāĨˆāĨ¤", + "asset_restored_successfully": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕āĨ€ ā¤—ā¤ˆā¤‚", "asset_skipped": "⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", "asset_skipped_in_trash": "ā¤•ā¤šā¤°āĨ‡ ā¤ŽāĨ‡ā¤‚", + "asset_trashed": "ā¤ā¤¸āĨ‡ā¤Ÿ ⤍⤎āĨā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "asset_troubleshoot": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž ⤍ā¤ŋā¤ĩā¤žā¤°ā¤Ŗ", "asset_uploaded": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕ā¤ŋā¤ ā¤—ā¤", "asset_uploading": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤šāĨ‹ ā¤°ā¤šā¤ž ā¤šāĨˆâ€Ļ", "asset_viewer_settings_subtitle": "⤅ā¤Ē⤍āĨ€ ⤗āĨˆā¤˛ā¤°āĨ€ ā¤ĩāĨā¤¯āĨ‚⤅⤰ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "asset_viewer_settings_title": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤ĩāĨā¤¯āĨ‚⤅⤰", "assets": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚", + "assets_added_count": "{count, plural, one {# asset} other {# assets}} ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", "assets_added_to_album_count": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ {count, plural, one {# asset} other {# assets}} ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", "assets_added_to_albums_count": "{assetTotal, plural, one {# asset} other {# assets}} ⤕āĨ‹ {albumTotal, plural, one {# album} other {# albums}} ⤏āĨ‡ ⤜āĨ‹āĨœā¤ž ā¤—ā¤¯ā¤ž", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} ⤕āĨ‹ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤¨ā¤šāĨ€ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} ⤕ā¤ŋ⤏āĨ€ ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ā¤¨ā¤šāĨ€ā¤‚ ⤜āĨ‹āĨœāĨ‡ ā¤œā¤ž ⤏⤕⤤āĨ‡", "assets_count": "{count, plural, one {# ā¤†ā¤‡ā¤Ÿā¤Ž} other {# ā¤†ā¤‡ā¤Ÿā¤ŽāĨā¤¸}}", + "assets_deleted_permanently": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤ĻāĨ€ ā¤—ā¤ˆā¤‚", + "assets_deleted_permanently_from_server": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ā¤‡ā¤Žā¤ŋ⤚ ⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤ĻāĨ€ ā¤—ā¤ˆā¤‚", + "assets_downloaded_failed": "{count, plural, one {# ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤šāĨā¤ˆ - {error} ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨ‹ ⤏⤕āĨ€} other {# ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤šāĨā¤ˆā¤‚ - {error} ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨ‹ ⤏⤕āĨ€ā¤‚}}", + "assets_downloaded_successfully": "{count, plural, one {# ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕āĨ€ ā¤—ā¤ˆ} other {# ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕āĨ€ ā¤—ā¤ˆā¤‚}}", "assets_moved_to_trash_count": "{count, plural, one {# asset} other {# assets}} ⤕āĨ‹ ⤟āĨā¤°āĨˆā¤ļ ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "assets_permanently_deleted_count": "⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž {count, plural, one {# asset} other {# assets}}", "assets_removed_count": "{count, plural, one {# asset} other {# assets}} ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "assets_removed_permanently_from_device": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ⤆ā¤Ē⤕āĨ‡ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤ĻāĨ€ ā¤—ā¤ˆā¤‚", "assets_restore_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤅ā¤Ē⤍āĨ€ ⤏⤭āĨ€ ⤍⤎āĨā¤Ÿ ⤕āĨ€ ā¤—ā¤ˆ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ⤆ā¤Ē ⤇⤏ ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤕āĨ‹ ā¤ĒāĨ‚⤰āĨā¤ĩā¤ĩ⤤ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ⤏⤕⤤āĨ‡āĨ¤", "assets_restored_count": "ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ {count, plural, one {# asset} other {# assets}}", + "assets_restored_successfully": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕āĨ€ ā¤—ā¤ˆā¤‚", "assets_trashed": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ā¤•ā¤šā¤°āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ€ ā¤—ā¤ˆā¤‚", "assets_trashed_count": "⤟āĨā¤°āĨˆā¤ļ ⤕āĨ€ ā¤—ā¤ˆ {count, plural, one {# asset} other {# assets}}", + "assets_trashed_from_server": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(ā¤¯ā¤žā¤) ā¤‡ā¤Žā¤ŋ⤚ ⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ā¤•ā¤šā¤°āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛āĨ€ ā¤—ā¤ˆā¤‚", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}}ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤•ā¤ž ā¤Ēā¤šā¤˛āĨ‡ ⤏āĨ‡ ā¤šāĨ€ ā¤šā¤ŋ⤏āĨā¤¸ā¤ž ā¤ĨāĨ‡", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} ā¤Ēā¤šā¤˛āĨ‡ ā¤šāĨ€ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ⤏⤂⤝āĨ‹ā¤œā¤ŋ⤤ ā¤šāĨˆā¤‚", "authorized_devices": "⤅⤧ā¤ŋ⤕āĨƒā¤¤ ⤉ā¤Ē⤕⤰⤪", @@ -568,44 +615,85 @@ "autoplay_slideshow": "ā¤‘ā¤ŸāĨ‹ā¤ĒāĨā¤˛āĨ‡ ⤏āĨā¤˛ā¤žā¤‡ā¤Ą ā¤ļāĨ‹", "back": "ā¤ĩā¤žā¤Ē⤏", "back_close_deselect": "ā¤ĩā¤žā¤Ē⤏ ā¤œā¤žā¤ā¤, ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚, ā¤¯ā¤ž ā¤…ā¤šā¤¯ā¤¨ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "background_backup_running_error": "ā¤Ē⤰ā¤ŋā¤ĒāĨā¤°āĨ‡ā¤•āĨā¤ˇāĨā¤¯ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤅⤭āĨ€ ā¤œā¤žā¤°āĨ€ ā¤šāĨˆ, ⤍ā¤ŋā¤¯ā¤Žā¤žā¤ĩ⤞āĨ€ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĒāĨā¤°ā¤žā¤°ā¤‚⤭ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž", "background_location_permission": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤏āĨā¤Ĩā¤žā¤¨ ⤅⤍āĨā¤Žā¤¤ā¤ŋ", "background_location_permission_content": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŽāĨ‡ā¤‚ ⤚⤞⤤āĨ‡ ā¤¸ā¤Žā¤¯ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ā¤Ŧā¤Ļ⤞⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤, Immich ⤕āĨ‡ ā¤Ēā¤žā¤¸ *ā¤šā¤ŽāĨ‡ā¤ļā¤ž* ⤏⤟āĨ€ā¤• ⤏āĨā¤Ĩā¤žā¤¨ ⤤⤕ ā¤Ēā¤šāĨā¤‚ā¤š ā¤šāĨ‹ā¤¨āĨ€ ā¤šā¤žā¤šā¤ŋā¤ ā¤¤ā¤žā¤•ā¤ŋ ⤐ā¤Ē ā¤ĩā¤žā¤ˆ-ā¤Ģā¤žā¤ˆ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ā¤•ā¤ž ā¤¨ā¤žā¤Ž ā¤Ēā¤ĸā¤ŧ ⤏⤕āĨ‡", "background_options": "ā¤Ē⤰ā¤ŋā¤ĒāĨā¤°āĨ‡ā¤•āĨā¤ˇāĨā¤¯ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", "backup": "ā¤ŦāĨˆā¤•⤅ā¤Ē", + "backup_album_selection_page_albums_device": "ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤Ē⤰ ā¤ā¤˛āĨā¤Ŧā¤Ž ({count})", "backup_album_selection_page_albums_tap": "ā¤ļā¤žā¤Žā¤ŋ⤞ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤟āĨˆā¤Ē ⤕⤰āĨ‡ā¤‚, ā¤Ŧā¤žā¤šā¤° ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ąā¤Ŧ⤞ ⤟āĨˆā¤Ē ⤕⤰āĨ‡ā¤‚", "backup_album_selection_page_assets_scatter": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤ˆ ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ā¤ŽāĨ‡ā¤‚ ā¤Ŧā¤ŋ⤖⤰āĨ‡ ā¤šāĨ‹ ⤏⤕⤤āĨ‡ ā¤šāĨˆā¤‚āĨ¤ ⤇⤏⤞ā¤ŋā¤, ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ⤕āĨ‡ ā¤ĻāĨŒā¤°ā¤žā¤¨ ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ⤕āĨ‹ ā¤ļā¤žā¤Žā¤ŋ⤞ ā¤¯ā¤ž ā¤Ŧā¤žā¤šā¤° ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆāĨ¤", "backup_album_selection_page_select_albums": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤚āĨā¤¨āĨ‡ā¤‚", "backup_album_selection_page_selection_info": "⤚⤝⤍ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€", + "backup_album_selection_page_total_assets": "⤕āĨā¤˛ ⤅ā¤ĻāĨā¤ĩā¤ŋ⤤āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤", "backup_albums_sync": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤•ā¤ž ⤏ā¤ŋ⤂⤕āĨā¤°āĨ‹ā¤¨ā¤žā¤‡ā¤œā¤ŧāĨ‡ā¤ļ⤍", + "backup_all": "⤏⤭āĨ€", + "backup_background_service_backup_failed_message": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞. ā¤ĒāĨā¤¨ā¤ƒ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆâ€Ļ", "backup_background_service_complete_notification": "ā¤ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĒāĨ‚ā¤°ā¤ž ā¤šāĨā¤†", + "backup_background_service_connection_failed_message": "⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ⤕⤍āĨ‡ā¤•āĨā¤Ÿ ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞. ā¤ĒāĨā¤¨ā¤ƒ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆâ€Ļ", + "backup_background_service_current_upload_notification": "{filename} ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤šāĨ‹ ā¤°ā¤šā¤ž ā¤šāĨˆ", "backup_background_service_default_notification": "⤍⤈ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ€ ā¤œā¤žā¤‚ā¤š ⤕āĨ€ ā¤œā¤ž ā¤°ā¤šāĨ€ ā¤šāĨˆâ€Ļ", + "backup_background_service_error_title": "ā¤ŦāĨˆā¤•⤅ā¤Ē ⤤āĨā¤°āĨā¤Ÿā¤ŋ", "backup_background_service_in_progress_notification": "⤅ā¤Ē⤍āĨ€ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨ā¤žâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", "backup_controller_page_albums": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ā¤˛āĨā¤Ŧā¤Ž", + "backup_controller_page_background_app_refresh_disabled_content": "ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ > ā¤¸ā¤žā¤Žā¤žā¤¨āĨā¤¯ > ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤐ā¤Ē ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤ŽāĨ‡ā¤‚ ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤐ā¤Ē ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ ⤏⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚āĨ¤", + "backup_controller_page_background_app_refresh_disabled_title": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤐ā¤Ē ⤰āĨ€ā¤Ģā¤ŧāĨā¤°āĨ‡ā¤ļ ⤅⤕āĨā¤ˇā¤Ž", + "backup_controller_page_background_app_refresh_enable_button_text": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ā¤Ē⤰ ā¤œā¤žā¤ā¤", "backup_controller_page_background_battery_info_link": "⤕āĨˆā¤¸āĨ‡ ā¤ŽāĨā¤āĨ‡ ā¤Ļā¤ŋā¤–ā¤žā¤“", "backup_controller_page_background_battery_info_message": "⤏⤰āĨā¤ĩāĨ‹ā¤¤āĨā¤¤ā¤Ž ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨˆā¤•⤅ā¤Ē ⤅⤍āĨā¤­ā¤ĩ ⤕āĨ‡ ⤞ā¤ŋā¤, ⤕āĨƒā¤Ēā¤¯ā¤ž Immich ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ ⤕āĨ‹ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰⤍āĨ‡ ā¤ĩā¤žā¤˛āĨ‡ ⤕ā¤ŋ⤏āĨ€ ⤭āĨ€ ā¤ŦāĨˆā¤Ÿā¤°āĨ€ ⤑ā¤ĒāĨā¤Ÿā¤ŋā¤Žā¤žā¤‡ā¤œā¤ŧāĨ‡ā¤ļ⤍ ⤕āĨ‹ ⤅⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚āĨ¤\n\n⤚āĨ‚⤁⤕ā¤ŋ ā¤¯ā¤š ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸-ā¤ĩā¤ŋā¤ļā¤ŋ⤎āĨā¤Ÿ ā¤šāĨˆ, ⤇⤏⤞ā¤ŋā¤ ⤕āĨƒā¤Ēā¤¯ā¤ž ⤅ā¤Ē⤍āĨ‡ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤍ā¤ŋ⤰āĨā¤Žā¤žā¤¤ā¤ž ⤏āĨ‡ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚āĨ¤", "backup_controller_page_background_battery_info_ok": "⤠āĨ€ā¤•", "backup_controller_page_background_battery_info_title": "ā¤ŦāĨˆā¤Ÿā¤°āĨ€ ⤅⤍āĨā¤•āĨ‚⤞⤍", + "backup_controller_page_background_charging": "⤕āĨ‡ā¤ĩ⤞ ā¤šā¤žā¤°āĨā¤œ ⤕⤰⤤āĨ‡ ā¤¸ā¤Žā¤¯", + "backup_controller_page_background_configure_error": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤏āĨ‡ā¤ĩā¤ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰ ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", "backup_controller_page_background_delay": "⤍⤈ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĩā¤ŋ⤞⤂ā¤Ŧā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚: {duration}", + "backup_controller_page_background_description": "⤐ā¤Ē ⤖āĨ‹ā¤˛āĨ‡ ā¤Ŧā¤ŋā¤¨ā¤ž ⤕ā¤ŋ⤏āĨ€ ⤭āĨ€ ⤍⤈ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤•ā¤ž ⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤏āĨ‡ā¤ĩā¤ž ā¤šā¤žā¤˛āĨ‚ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_background_is_off": "⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ā¤šāĨˆ", + "backup_controller_page_background_is_on": "⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤šāĨˆ", + "backup_controller_page_background_turn_off": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤏āĨ‡ā¤ĩā¤ž ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_background_turn_on": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤏āĨ‡ā¤ĩā¤ž ā¤šā¤žā¤˛āĨ‚ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_background_wifi": "⤕āĨ‡ā¤ĩ⤞ ā¤ĩā¤žā¤ˆ-ā¤Ģā¤ŧā¤žā¤ˆ ā¤Ē⤰", "backup_controller_page_backup": "ā¤ŦāĨˆā¤•⤅ā¤Ē", "backup_controller_page_backup_selected": "⤚⤝⤍ā¤ŋ⤤: ", "backup_controller_page_backup_sub": "ā¤ŦāĨˆā¤•⤅ā¤Ē ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", + "backup_controller_page_created": "⤍ā¤ŋ⤰āĨā¤Žā¤ŋ⤤ ⤤ā¤ŋā¤Ĩā¤ŋ: {date}", + "backup_controller_page_desc_backup": "⤐ā¤Ē ⤖āĨ‹ā¤˛ā¤¤āĨ‡ ā¤¸ā¤Žā¤¯ ⤏⤰āĨā¤ĩ⤰ ā¤Ē⤰ ⤍⤈ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ⤕⤰āĨ‡ā¤‚āĨ¤", "backup_controller_page_excluded": "⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž: ", + "backup_controller_page_failed": "ā¤ĩā¤ŋā¤Ģ⤞ ({count})", + "backup_controller_page_filename": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤¨ā¤žā¤Ž: {filename} [{size}]", + "backup_controller_page_id": "ā¤†ā¤ˆā¤ĄāĨ€: {id}", + "backup_controller_page_info": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€", "backup_controller_page_none_selected": "⤕āĨ‹ā¤ˆ ⤭āĨ€ ⤚⤝⤍ā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚", "backup_controller_page_remainder": "ā¤ļāĨ‡ā¤ˇ", "backup_controller_page_remainder_sub": "⤚⤝⤍ ⤏āĨ‡ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ļāĨ‡ā¤ˇ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "backup_controller_page_server_storage": "⤏⤰āĨā¤ĩ⤰ ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ", + "backup_controller_page_start_backup": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĒāĨā¤°ā¤žā¤°ā¤‚⤭ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_status_off": "⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ā¤šāĨˆ", + "backup_controller_page_status_on": "⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤šāĨˆ", "backup_controller_page_storage_format": "{total} ā¤ŽāĨ‡ā¤‚ ⤏āĨ‡ {used} ⤉ā¤Ē⤝āĨ‹ā¤— ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "backup_controller_page_to_backup": "ā¤ŦāĨˆā¤•⤅ā¤Ē ⤕ā¤ŋā¤ ā¤œā¤žā¤¨āĨ‡ ā¤ĩā¤žā¤˛āĨ‡ ā¤ā¤˛āĨā¤Ŧā¤Ž", "backup_controller_page_total_sub": "⤚⤝⤍ā¤ŋ⤤ ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ⤏āĨ‡ ⤏⤭āĨ€ ⤅ā¤ĻāĨā¤ĩā¤ŋ⤤āĨ€ā¤¯ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", + "backup_controller_page_turn_off": "⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_turn_on": "⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ⤕⤰āĨ‡ā¤‚", + "backup_controller_page_uploading_file_info": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤¨ā¤ž", + "backup_err_only_album": "ā¤ā¤•ā¤Žā¤žā¤¤āĨā¤° ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šā¤Ÿā¤žā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž", "backup_error_sync_failed": "⤤āĨā¤˛āĨā¤¯ā¤•ā¤žā¤˛ā¤¨ ā¤ĩā¤ŋā¤Ģ⤞. ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤¸ā¤‚ā¤¸ā¤žā¤§ā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤žāĨ¤", "backup_info_card_assets": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ", + "backup_manual_cancelled": "⤰ā¤ĻāĨā¤Ļ", + "backup_manual_in_progress": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤Ēā¤šā¤˛āĨ‡ ⤏āĨ‡ ā¤šāĨ€ ā¤ĒāĨā¤°ā¤—⤤ā¤ŋ ā¤Ē⤰ ā¤šāĨˆāĨ¤ ⤕āĨā¤› ā¤ĻāĨ‡ā¤° ā¤Ŧā¤žā¤Ļ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕⤰āĨ‡ā¤‚", + "backup_manual_success": "⤏ā¤Ģā¤˛ā¤¤ā¤ž", + "backup_manual_title": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨā¤Ĩā¤ŋ⤤ā¤ŋ", "backup_options": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", + "backup_options_page_title": "ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", + "backup_setting_subtitle": "ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ⤔⤰ ⤅⤗āĨā¤°ā¤­āĨ‚ā¤Žā¤ŋ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "backup_settings_subtitle": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ā¤¸ā¤‚ā¤­ā¤žā¤˛āĨ‡ā¤‚", + "backup_upload_details_page_more_details": "⤅⤧ā¤ŋ⤕ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤟āĨˆā¤Ē ⤕⤰āĨ‡ā¤‚", "backward": "ā¤Ēā¤ŋā¤›ā¤˛ā¤ž", "biometric_auth_enabled": "ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•⤰⤪ ⤏⤕āĨā¤ˇā¤Ž", "biometric_locked_out": "⤆ā¤Ē ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•⤰⤪ ⤏āĨ‡ ā¤Ŧā¤žā¤šā¤° ā¤šāĨˆā¤‚", "biometric_no_options": "⤕āĨ‹ā¤ˆ ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", "biometric_not_available": "⤇⤏ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤Ē⤰ ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•⤰⤪ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", + "birthdate_saved": "⤜⤍āĨā¤Žā¤¤ā¤ŋā¤Ĩā¤ŋ ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤¸ā¤šāĨ‡ā¤œāĨ€ ā¤—ā¤ˆ", "birthdate_set_description": "⤜⤍āĨā¤Žā¤¤ā¤ŋā¤Ĩā¤ŋ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤕āĨ‡ ā¤¸ā¤Žā¤¯ ⤇⤏ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤕āĨ€ ⤆⤝āĨ ⤕āĨ€ ā¤—ā¤Ŗā¤¨ā¤ž ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤žā¤¤ā¤ž ā¤šāĨˆāĨ¤", "blurred_background": "⤧āĨā¤‚⤧⤞āĨ€ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ", "bugs_and_feature_requests": "ā¤Ŧ⤗ ⤔⤰ ⤏āĨā¤ĩā¤ŋā¤§ā¤ž ⤅⤍āĨā¤°āĨ‹ā¤§", @@ -615,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ {count, plural, one {# duplicate asset} other {# duplicate assets}} ā¤°ā¤–ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ⤇⤏⤏āĨ‡ ā¤Ŧā¤ŋā¤¨ā¤ž ⤕āĨā¤› ā¤šā¤Ÿā¤žā¤ ⤏⤭āĨ€ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤗āĨā¤°āĨā¤Ē ā¤šā¤˛ ā¤šāĨ‹ ā¤œā¤žā¤ā¤ā¤—āĨ‡āĨ¤", "bulk_trash_duplicates_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ {count, plural, one {# duplicate asset} other {# duplicate assets}} ⤕āĨ‹ ā¤Ŧ⤞āĨā¤• ⤟āĨā¤°āĨˆā¤ļ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ⤇⤏⤏āĨ‡ ā¤šā¤° ⤗āĨā¤°āĨā¤Ē ⤕āĨ€ ⤏ā¤Ŧ⤏āĨ‡ ā¤Ŧā¤Ąā¤ŧāĨ€ ā¤ā¤¸āĨ‡ā¤Ÿ ā¤°ā¤šāĨ‡ā¤—āĨ€ ⤔⤰ ā¤Ŧā¤žā¤•āĨ€ ⤏⤭āĨ€ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤟āĨā¤°āĨˆā¤ļ ā¤šāĨ‹ ā¤œā¤žā¤ā¤ā¤—āĨ‡āĨ¤", "buy": "ā¤‡ā¤ŽāĨā¤ŽāĨ€ā¤š ⤖⤰āĨ€ā¤ĻāĨ‹", + "cache_settings_clear_cache_button": "⤕āĨˆā¤ļ ⤕āĨ‹ ā¤¸ā¤žā¤Ģā¤ŧ ⤕⤰āĨ‡ā¤‚", + "cache_settings_clear_cache_button_title": "⤐ā¤Ē ā¤•ā¤ž ⤕āĨˆā¤ļ ā¤¸ā¤žā¤Ģā¤ŧ ā¤•ā¤°ā¤¤ā¤ž ā¤šāĨˆāĨ¤ ⤕āĨˆā¤ļ ⤕āĨ‡ ā¤ĻāĨ‹ā¤Ŧā¤žā¤°ā¤ž ā¤Ŧ⤍⤍āĨ‡ ⤤⤕, ā¤¯ā¤š ⤐ā¤Ē ⤕āĨ‡ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ā¤Ē⤰ ā¤•ā¤žā¤Ģā¤ŧāĨ€ ⤅⤏⤰ ā¤Ąā¤žā¤˛āĨ‡ā¤—ā¤žāĨ¤", + "cache_settings_duplicated_assets_clear_button": "⤏āĨā¤Ē⤎āĨā¤Ÿ", + "cache_settings_duplicated_assets_subtitle": "⤐ā¤Ē ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ⤅⤍ā¤ĻāĨ‡ā¤–ā¤ž ⤕āĨ€ ā¤—ā¤ˆ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", + "cache_settings_duplicated_assets_title": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ({count})", + "cache_settings_statistics_album": "ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_full": "ā¤ĒāĨ‚⤰āĨā¤Ŗ ⤚ā¤ŋ⤤āĨā¤°", + "cache_settings_statistics_shared": "ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤ ā¤—ā¤ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_thumbnail": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_title": "⤕āĨˆā¤ļ ⤉ā¤Ē⤝āĨ‹ā¤—", + "cache_settings_subtitle": "Immich ā¤ŽāĨ‹ā¤Ŧā¤žā¤‡ā¤˛ ā¤ā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ļ⤍ ⤕āĨ‡ ⤕āĨˆā¤ļā¤ŋ⤂⤗ ā¤ĩāĨā¤¯ā¤ĩā¤šā¤žā¤° ⤕āĨ‹ ⤍ā¤ŋ⤝⤂⤤āĨā¤°ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "cache_settings_tile_subtitle": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ ⤕āĨ‡ ā¤ĩāĨā¤¯ā¤ĩā¤šā¤žā¤° ⤕āĨ‹ ⤍ā¤ŋ⤝⤂⤤āĨā¤°ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "cache_settings_tile_title": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ", + "cache_settings_title": "⤕āĨˆā¤ļā¤ŋ⤂⤗ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸", "camera": "⤕āĨˆā¤Žā¤°ā¤ž", "camera_brand": "⤕āĨˆā¤Žā¤°ā¤ž ā¤ŦāĨā¤°ā¤žā¤‚ā¤Ą", "camera_model": "⤕āĨˆā¤Žā¤°ā¤ž ā¤ŽāĨ‰ā¤Ąā¤˛", @@ -630,6 +732,7 @@ "change_date": "ā¤Ŧā¤Ļā¤˛ā¤žā¤ĩ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤•", "change_description": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", "change_display_order": "ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ⤕āĨā¤°ā¤Ž ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "change_expiration_time": "ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤ā¤ŋ ā¤¸ā¤Žā¤¯ ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", "change_location": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", "change_name": "ā¤¨ā¤žā¤Ž ā¤Ē⤰ā¤ŋā¤ĩ⤰āĨā¤¤ā¤¨ ⤕⤰āĨ‡ā¤‚", "change_name_successfully": "ā¤¨ā¤žā¤Ž ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤Ŧā¤Ļā¤˛ā¤ž ā¤—ā¤¯ā¤ž", @@ -643,10 +746,15 @@ "change_password_form_password_mismatch": "ā¤¸ā¤žā¤‚ā¤•āĨ‡ā¤¤ā¤ŋ⤕ ā¤ļā¤ŦāĨā¤Ļ ā¤ŽāĨ‡ā¤˛ ā¤¨ā¤šāĨ€ā¤‚ ā¤–ā¤žā¤¤āĨ‡", "change_password_form_reenter_new_password": "ā¤¨ā¤¯ā¤ž ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤ĒāĨā¤¨ā¤ƒ ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚", "change_pin_code": "ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "change_trigger": "⤟āĨā¤°ā¤ŋ⤗⤰ ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "change_trigger_prompt": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤟āĨā¤°ā¤ŋ⤗⤰ ā¤Ŧā¤Ļā¤˛ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ⤇⤏⤏āĨ‡ ⤏⤭āĨ€ ā¤ŽāĨŒā¤œāĨ‚ā¤Ļā¤ž ā¤ā¤•āĨā¤ļ⤍ ⤔⤰ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤ā¤—āĨ‡āĨ¤", "change_your_password": "⤅ā¤Ēā¤¨ā¤ž ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", "changed_visibility_successfully": "ā¤ĻāĨƒā¤ļāĨā¤¯ā¤¤ā¤ž ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤Ē⤰ā¤ŋā¤ĩ⤰āĨā¤¤ā¤ŋ⤤", "charging": "ā¤šā¤žā¤°āĨā¤œā¤ŋ⤂⤗", "charging_requirement_mobile_backup": "ā¤Ē⤰ā¤ŋā¤ĒāĨā¤°āĨ‡ā¤•āĨā¤ˇāĨā¤¯ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤•ā¤ž ā¤šā¤žā¤°āĨā¤œā¤ŋ⤂⤗ ā¤ĒāĨ‡ ⤞⤗āĨ‡ ā¤šāĨ‹ā¤¨ā¤ž ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤šāĨˆ", + "check_corrupt_asset_backup": "ā¤ĻāĨ‚⤎ā¤ŋ⤤ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤕āĨ€ ā¤œā¤žā¤ā¤š ⤕⤰āĨ‡ā¤‚", + "check_corrupt_asset_backup_button": "ā¤œā¤žā¤ā¤š ⤕⤰āĨ‡ā¤‚", + "check_corrupt_asset_backup_description": "ā¤¯ā¤š ā¤œā¤žā¤ā¤š ⤕āĨ‡ā¤ĩ⤞ ā¤ĩā¤žā¤ˆ-ā¤Ģā¤ŧā¤žā¤ˆ ā¤Ē⤰ ā¤šāĨ€ ⤕⤰āĨ‡ā¤‚ ⤔⤰ ⤏⤭āĨ€ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨āĨ‡ ⤕āĨ‡ ā¤Ŧā¤žā¤Ļ ā¤šāĨ€ ⤕⤰āĨ‡ā¤‚āĨ¤ ⤇⤏ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤ŽāĨ‡ā¤‚ ⤕āĨā¤› ā¤Žā¤ŋ⤍⤟ ⤞⤗ ⤏⤕⤤āĨ‡ ā¤šāĨˆā¤‚āĨ¤", "check_logs": "⤞āĨ‰ā¤— ā¤œā¤žā¤‚ā¤šāĨ‡ā¤‚", "checksum": "⤚āĨ‡ā¤•ā¤¸ā¤Ž (checksum)", "choose_matching_people_to_merge": "ā¤Žā¤°āĨā¤œ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Žā¤ŋ⤞⤤āĨ‡-⤜āĨā¤˛ā¤¤āĨ‡ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ⤚āĨā¤¨āĨ‡ā¤‚", @@ -669,6 +777,8 @@ "clear_file_cache": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤕āĨˆā¤ļ ā¤¸ā¤žā¤Ģā¤ŧ ⤕⤰āĨ‡ā¤‚", "clear_message": "⤏āĨā¤Ē⤎āĨā¤Ÿ ⤏⤂ā¤ĻāĨ‡ā¤ļ", "clear_value": "⤏āĨā¤Ē⤎āĨā¤Ÿ ā¤ŽāĨ‚⤞āĨā¤¯", + "client_cert_dialog_msg_confirm": "⤠āĨ€ā¤•", + "client_cert_enter_password": "ā¤Ēā¤žā¤¸ ā¤ĩ⤰āĨā¤Ą ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚", "client_cert_import": "ā¤†ā¤¯ā¤žā¤¤", "client_cert_import_success_msg": "⤕āĨā¤˛ā¤žā¤‡ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤†ā¤¯ā¤žā¤¤ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "client_cert_invalid_msg": "ā¤…ā¤Žā¤žā¤¨āĨā¤¯ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤¯ā¤ž ⤗⤞⤤ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", @@ -677,10 +787,12 @@ "client_cert_remove_msg": "⤕āĨā¤˛ā¤žā¤‡ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "client_cert_subtitle": "⤕āĨ‡ā¤ĩ⤞ PKCS12 (.p12, .pfx) ā¤ĒāĨā¤°ā¤žā¤°āĨ‚ā¤Ē ā¤•ā¤ž ā¤¸ā¤Žā¤°āĨā¤Ĩ⤍ ā¤•ā¤°ā¤¤ā¤ž ā¤šāĨˆāĨ¤ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤†ā¤¯ā¤žā¤¤/⤍ā¤ŋā¤•ā¤žā¤˛ā¤¨ā¤ž ⤕āĨ‡ā¤ĩ⤞ ⤞āĨ‰ā¤—ā¤ŋ⤍ ⤏āĨ‡ ā¤Ēā¤šā¤˛āĨ‡ ā¤šāĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤šāĨˆ", "client_cert_title": "SSL ⤕āĨā¤˛ā¤žā¤‡ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", + "clockwise": "ā¤Ļ⤕āĨā¤ˇā¤ŋā¤Ŗā¤žā¤ĩ⤰āĨā¤¤", "close": "ā¤Ŧ⤂ā¤Ļ", "collapse": "⤗ā¤ŋ⤰ ā¤œā¤žā¤¨ā¤ž", "collapse_all": "⤏⤭āĨ€ ⤕āĨ‹ ⤏⤂⤕āĨā¤šā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "color": "⤰⤂⤗", + "color_theme": "⤰⤂⤗ ā¤ĨāĨ€ā¤Ž", "command": "⤆ā¤ĻāĨ‡ā¤ļ", "command_palette_prompt": "⤜⤞āĨā¤ĻāĨ€ ⤏āĨ‡ ā¤ĒāĨ‡ā¤œ, ā¤•ā¤žā¤°āĨā¤¯ ā¤¯ā¤ž ā¤•ā¤Žā¤žā¤‚ā¤Ą ā¤ĸāĨ‚⤂ā¤ĸāĨ‡ā¤‚", "command_palette_to_close": "ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚", @@ -707,13 +819,18 @@ "contain": "ā¤¸ā¤Žā¤žā¤šā¤ŋ⤤", "context": "⤏⤂ā¤Ļ⤰āĨā¤­", "continue": "ā¤œā¤žā¤°āĨ€", + "control_bottom_app_bar_create_new_album": "ā¤¨ā¤¯ā¤ž ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ā¤", + "control_bottom_app_bar_delete_from_immich": "ā¤‡ā¤ŽāĨā¤Žā¤ŋ⤚ ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤‚", "control_bottom_app_bar_delete_from_local": "ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤‚", "control_bottom_app_bar_edit_location": "⤏āĨā¤Ĩā¤žā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "control_bottom_app_bar_edit_time": "ā¤¤ā¤žā¤°āĨ€ā¤– ⤔⤰ ā¤¸ā¤Žā¤¯ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "control_bottom_app_bar_share_link": "⤞ā¤ŋ⤂⤕ ā¤ļāĨ‡ā¤¯ā¤° ⤕⤰āĨ‡ā¤‚", + "control_bottom_app_bar_share_to": "ā¤¸ā¤žā¤ā¤ž ⤕⤰āĨ‡ā¤‚", "control_bottom_app_bar_trash_from_immich": "⤟āĨā¤°āĨˆā¤ļ ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤ā¤‚", "copied_image_to_clipboard": "⤛ā¤ĩā¤ŋ ⤕āĨ‹ ⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ą ā¤Ē⤰ ⤕āĨ‰ā¤ĒāĨ€ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤žāĨ¤", "copied_to_clipboard": "⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ą ā¤Ē⤰ ⤍⤕⤞!", "copy_error": "ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤞ā¤ŋā¤Ēā¤ŋ ⤤āĨā¤°āĨā¤Ÿā¤ŋ", + "copy_file_path": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤Ēā¤Ĩ ⤕āĨ‰ā¤ĒāĨ€ ⤕⤰āĨ‡ā¤‚", "copy_image": "⤍⤕⤞ ⤛ā¤ĩā¤ŋ", "copy_link": "⤞ā¤ŋ⤂⤕ ⤕āĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤞ā¤ŋā¤Ēā¤ŋ ⤕⤰āĨ‡ā¤‚", "copy_link_to_clipboard": "⤞ā¤ŋ⤂⤕ ⤕āĨ‹ ⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ą ā¤Ē⤰ ⤕āĨ‰ā¤ĒāĨ€ ⤕⤰āĨ‡ā¤‚", @@ -746,23 +863,29 @@ "created_at": "ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤Ĩā¤ž", "creating_linked_albums": "⤜āĨāĨœāĨ‡ ā¤šāĨā¤ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ ā¤œā¤ž ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚..āĨ¤", "crop": "ā¤›ā¤žā¤ā¤ŸāĨ‡ā¤‚", + "crop_aspect_ratio_fixed": "⤍ā¤ŋā¤ļāĨā¤šā¤ŋ⤤", "crop_aspect_ratio_free": "⤏āĨā¤ĩ⤤⤂⤤āĨā¤°", "crop_aspect_ratio_original": "ā¤ŽāĨ‚⤞ ⤅⤍āĨā¤Ēā¤žā¤¤", + "curated_object_page_title": "⤚āĨ€ā¤œā¤ŧāĨ‡ā¤‚", "current_device": "ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤉ā¤Ē⤕⤰⤪", "current_pin_code": "ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą", "current_server_address": "ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤏⤰āĨā¤ĩ⤰ ā¤Ēā¤¤ā¤ž", "custom_date": "ā¤Žā¤¨ā¤šā¤žā¤šāĨ€ ⤤ā¤ŋā¤Ĩā¤ŋ", "custom_locale": "⤕⤏āĨā¤Ÿā¤Ž ⤞āĨ‹ā¤•āĨ‡ā¤˛", "custom_locale_description": "ā¤­ā¤žā¤ˇā¤ž ⤔⤰ ⤕āĨā¤ˇāĨ‡ā¤¤āĨā¤° ⤕āĨ‡ ā¤†ā¤§ā¤žā¤° ā¤Ē⤰ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ⤔⤰ ⤏⤂⤖āĨā¤¯ā¤žā¤ā¤ ā¤ĒāĨā¤°ā¤žā¤°āĨ‚ā¤Ēā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "custom_url": "⤕⤏āĨā¤Ÿā¤Ž URL", "cutoff_date_description": "ā¤Ēā¤ŋ⤛⤞āĨ‡ ⤕ā¤ŋ⤤⤍āĨ‡ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤰⤖āĨ‡ā¤‚â€Ļ", "cutoff_day": "{count, plural, one {ā¤Ļā¤ŋ⤍} other {ā¤Ļā¤ŋ⤍}}", "cutoff_year": "{count, plural, one {ā¤ĩ⤰āĨā¤ˇ} other {ā¤ĩ⤰āĨā¤ˇ}}", + "daily_title_text_date": "⤈, ā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤ĄāĨ€ā¤ĄāĨ€", + "daily_title_text_date_year": "⤈, ā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤Ļā¤ŋ⤍, ā¤ĩ⤰āĨā¤ˇ", "dark": "ā¤Ąā¤žā¤°āĨā¤•", "dark_theme": "ā¤Ąā¤žā¤°āĨā¤• ā¤ĨāĨ€ā¤Ž ⤟āĨ‰ā¤—⤞ ⤕⤰āĨ‡ā¤‚", "date": "ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤•", "date_after": "⤇⤏⤕āĨ‡ ā¤Ŧā¤žā¤Ļ ⤕āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", "date_and_time": "⤤ā¤ŋā¤Ĩā¤ŋ ⤔⤰ ā¤¸ā¤Žā¤¯", "date_before": "ā¤Ēā¤šā¤˛āĨ‡ ⤕āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", + "date_format": "⤈, ā¤ā¤˛ā¤ā¤˛ā¤ā¤˛ ā¤ĄāĨ€, ā¤ĩā¤žā¤ˆ â€ĸ ā¤ā¤š:ā¤ā¤Žā¤ā¤Ž ā¤", "date_of_birth_saved": "⤜⤍āĨā¤Žā¤¤ā¤ŋā¤Ĩā¤ŋ ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ā¤¸ā¤šāĨ‡ā¤œāĨ€ ā¤—ā¤ˆ", "date_range": "⤤ā¤ŋā¤Ĩā¤ŋ ⤏āĨ€ā¤Žā¤ž", "day": "ā¤Ļā¤ŋ⤍", @@ -776,6 +899,8 @@ "delete_dialog_alert": "⤝āĨ‡ ā¤†ā¤‡ā¤Ÿā¤Ž Immich ⤔⤰ ⤆ā¤Ē⤕āĨ‡ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤‚ā¤—āĨ‡", "delete_dialog_alert_local": "⤝āĨ‡ ā¤†ā¤‡ā¤Ÿā¤Ž ⤆ā¤Ē⤕āĨ‡ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤‚ā¤—āĨ‡, ⤞āĨ‡ā¤•ā¤ŋ⤍ ā¤Ģā¤ŋ⤰ ⤭āĨ€ Immich ⤏⤰āĨā¤ĩ⤰ ā¤Ē⤰ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤°ā¤šāĨ‡ā¤‚⤗āĨ‡", "delete_dialog_alert_local_non_backed_up": "⤕āĨā¤› ā¤†ā¤‡ā¤Ÿā¤Ž ā¤•ā¤ž Immich ā¤ŽāĨ‡ā¤‚ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤¨ā¤šāĨ€ā¤‚ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ ⤔⤰ ⤉⤍āĨā¤šāĨ‡ā¤‚ ⤆ā¤Ē⤕āĨ‡ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤œā¤žā¤ā¤—ā¤ž", + "delete_dialog_alert_remote": "⤝āĨ‡ ā¤†ā¤‡ā¤Ÿā¤Ž Immich ⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤‚ā¤—āĨ‡", + "delete_dialog_ok_force": "ā¤Ģā¤ŋ⤰ ⤭āĨ€ ā¤šā¤Ÿā¤žā¤ā¤‚", "delete_dialog_title": "⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤", "delete_duplicates_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤇⤍ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤕āĨ‹ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "delete_face": "⤚āĨ‡ā¤šā¤°ā¤ž ā¤šā¤Ÿā¤žā¤ā¤‚", @@ -794,16 +919,21 @@ "delete_tag_confirmation_prompt": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ {tagName} ⤟āĨˆā¤— ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "delete_user": "⤉ā¤Ē⤭āĨ‹ā¤•āĨā¤¤ā¤ž ā¤Žā¤ŋā¤Ÿā¤žā¤¯āĨ‡ā¤‚", "deleted_shared_link": "ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ⤞ā¤ŋ⤂⤕ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "deletes_missing_assets": "ā¤Ąā¤ŋ⤏āĨā¤• ⤏āĨ‡ ā¤—ā¤žā¤¯ā¤Ŧ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¤ā¤ž ā¤šāĨˆ", "description": "ā¤ĩ⤰āĨā¤Ŗā¤¨", + "description_input_hint_text": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚..āĨ¤", + "description_input_submit_error": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕⤰⤤āĨ‡ ā¤¸ā¤Žā¤¯ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ, ⤅⤧ā¤ŋ⤕ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤞āĨ‰ā¤— ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "deselect_all": "⤏ā¤Ŧ⤕āĨ‹ ā¤…ā¤šā¤¯ā¤¨ā¤ŋ⤤ ⤕⤰āĨ‹", "details": "ā¤ĩā¤ŋā¤ĩ⤰⤪", "direction": "ā¤Ļā¤ŋā¤ļā¤ž", "disable": "⤍ā¤ŋ⤎āĨā¤•āĨā¤°ā¤ŋ⤝ ⤕⤰āĨ‡ā¤‚", "disabled": "⤅⤕āĨā¤ˇā¤Ž", + "disallow_edits": "⤏⤂ā¤Ēā¤žā¤Ļ⤍āĨ‹ā¤‚ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤍ ā¤ĻāĨ‡ā¤‚", "discord": "ā¤Ąā¤ŋ⤏⤕āĨ‰ā¤°āĨā¤Ą", "discover": "⤖āĨ‹ā¤œāĨ‡ā¤‚", "discovered_devices": "⤖āĨ‹ā¤œāĨ‡ ā¤—ā¤ ⤉ā¤Ē⤕⤰⤪", "dismiss_all_errors": "⤏⤭āĨ€ ⤤āĨā¤°āĨā¤Ÿā¤ŋā¤¯ā¤žā¤ ⤖ā¤ŧā¤žā¤°ā¤ŋ⤜ ⤕⤰āĨ‡ā¤‚", + "dismiss_error": "⤤āĨā¤°āĨā¤Ÿā¤ŋ ⤖ā¤ŧā¤žā¤°ā¤ŋ⤜ ⤕⤰āĨ‡ā¤‚", "display_options": "ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ⤚āĨā¤¨ā¤žā¤ĩ", "display_order": "⤆ā¤ĻāĨ‡ā¤ļ ⤕āĨ‹ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "display_original_photos": "ā¤ŽāĨ‚⤞ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", @@ -812,9 +942,11 @@ "documentation": "ā¤ĒāĨā¤°ā¤˛āĨ‡ā¤–⤍", "done": "⤠āĨ€ā¤• ā¤šāĨˆ", "download": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", + "download_action_prompt": "{count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤šāĨ‹ ā¤°ā¤šāĨ€ ā¤šāĨˆā¤‚", "download_canceled": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤰ā¤ĻāĨā¤Ļ ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "download_complete": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ĒāĨ‚ā¤°ā¤ž", "download_enqueue": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤¤ā¤žā¤° ā¤ŽāĨ‡ā¤‚ ā¤šāĨˆ", + "download_error": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤤āĨā¤°āĨā¤Ÿā¤ŋ", "download_failed": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ĩā¤ŋā¤Ģ⤞", "download_finished": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤", "download_include_embedded_motion_videos": "ā¤ā¤ŽāĨā¤ŦāĨ‡ā¤ĄāĨ‡ā¤Ą ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", @@ -824,6 +956,9 @@ "download_paused": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏āĨā¤Ĩ⤗ā¤ŋ⤤", "download_settings": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤¨ā¤ž", "download_settings_description": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏āĨ‡ ⤏⤂ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "download_started": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ĒāĨā¤°ā¤žā¤°ā¤‚⤭ ā¤šāĨā¤†", + "download_sucess": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏ā¤Ģ⤞", + "download_sucess_android": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž DCIM/Immich ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤šāĨ‹ ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "download_waiting_to_retry": "ā¤ĒāĨā¤¨ā¤ƒ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕⤰⤍āĨ‡ ā¤•ā¤ž ā¤‡ā¤‚ā¤¤ā¤œā¤žā¤° ⤕⤰ ā¤°ā¤šā¤ž ā¤šāĨˆ", "downloading": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą", "downloading_asset_filename": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ {filename} ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤šāĨ‹ ā¤°ā¤šāĨ€ ā¤šāĨˆ", @@ -841,7 +976,9 @@ "edit_date_and_time": "ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ⤔⤰ ā¤¸ā¤Žā¤¯ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "edit_date_and_time_action_prompt": "{count} ā¤¤ā¤žā¤°āĨ€ā¤– ⤔⤰ ā¤¸ā¤Žā¤¯ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕ā¤ŋā¤ ā¤—ā¤", "edit_date_and_time_by_offset": "⤅⤂⤕āĨā¤° ⤏āĨ‡ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "edit_date_and_time_by_offset_interval": "⤍⤝āĨ€ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ⤏āĨ€ā¤Žā¤ž: {from} - {to}", "edit_description": "⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚ ā¤ĩ⤰āĨā¤Ŗā¤¨", + "edit_description_prompt": "⤕āĨƒā¤Ēā¤¯ā¤ž ā¤ā¤• ā¤¨ā¤¯ā¤ž ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤚āĨā¤¨āĨ‡ā¤‚:", "edit_exclusion_pattern": "ā¤Ŧā¤šā¤ŋ⤎āĨā¤•⤰⤪ ā¤ĒāĨˆā¤Ÿā¤°āĨā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "edit_faces": "⤚āĨ‡ā¤šā¤°āĨ‡ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "edit_key": "⤕āĨā¤‚ā¤œāĨ€ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", @@ -856,6 +993,9 @@ "edit_user": "⤝āĨ‚ā¤œā¤° ⤕āĨ‹ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‹", "edit_workflow": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "editor": "⤏⤂ā¤Ēā¤žā¤Ļ⤕", + "editor_close_without_save_prompt": "ā¤Ē⤰ā¤ŋā¤ĩ⤰āĨā¤¤ā¤¨ ā¤¸ā¤šāĨ‡ā¤œāĨ‡ ā¤¨ā¤šāĨ€ā¤‚ ā¤œā¤žā¤ā¤ā¤—āĨ‡", + "editor_close_without_save_title": "⤏⤂ā¤Ēā¤žā¤Ļ⤕ ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚?", + "editor_confirm_reset_all_changes": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤏⤭āĨ€ ā¤Ŧā¤Ļā¤˛ā¤žā¤ĩ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "editor_discard_edits_confirm": "⤏⤂ā¤Ēā¤žā¤Ļ⤍ ⤤āĨā¤¯ā¤žā¤—āĨ‡ā¤‚", "editor_discard_edits_prompt": "⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ā¤¸ā¤šāĨ‡ā¤œāĨ‡ ⤍ ā¤—ā¤ ⤏⤂ā¤Ēā¤žā¤Ļ⤍ ā¤šāĨˆā¤‚āĨ¤ ⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤉⤍āĨā¤šāĨ‡ā¤‚ ⤤āĨā¤¯ā¤žā¤—ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "editor_discard_edits_title": "⤏⤂ā¤Ēā¤žā¤Ļ⤍ ⤤āĨā¤¯ā¤žā¤—āĨ‡ā¤‚?", @@ -882,7 +1022,9 @@ "enter_your_pin_code": "⤅ā¤Ēā¤¨ā¤ž ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "enter_your_pin_code_subtitle": "⤞āĨ‰ā¤• ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤖āĨ‹ā¤˛ā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "error": "ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž", + "error_change_sort_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤•ā¤ž ⤕āĨā¤°ā¤Ž ā¤Ŧā¤Ļ⤞⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤅⤏ā¤Ģ⤞ ā¤°ā¤šā¤ž", "error_delete_face": "ā¤ā¤¸āĨ‡ā¤Ÿ ⤏āĨ‡ ⤚āĨ‡ā¤šā¤°āĨ‡ ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ", + "error_getting_places": "⤏āĨā¤Ĩā¤žā¤¨āĨ‹ā¤‚ ⤕āĨ‹ ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ", "error_loading_albums": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž", "error_loading_image": "⤛ā¤ĩā¤ŋ ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ", "error_loading_partners": "⤜āĨ‹āĨœāĨ€ā¤Ļā¤žā¤° ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ: {error}", @@ -1019,10 +1161,18 @@ "exif": "ā¤ā¤•āĨā¤¸ā¤ŋā¤Ģ", "exif_bottom_sheet_description": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚..āĨ¤", "exif_bottom_sheet_description_error": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤕āĨ‡ ⤆⤧āĨā¤¨āĨ€ā¤•⤰⤪ ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ", + "exif_bottom_sheet_details": "ā¤ĩā¤ŋā¤ĩ⤰⤪", + "exif_bottom_sheet_location": "⤏āĨā¤Ĩā¤žā¤¨", "exif_bottom_sheet_no_description": "⤕āĨ‹ā¤ˆ ā¤ĩā¤ŋā¤ĩ⤰⤪ ā¤¨ā¤šāĨ€ā¤‚", + "exif_bottom_sheet_people": "⤞āĨ‹ā¤—", + "exif_bottom_sheet_person_add_person": "ā¤¨ā¤žā¤Ž ā¤Ąā¤žā¤˛āĨ‡ā¤‚", "exit_slideshow": "⤏āĨā¤˛ā¤žā¤‡ā¤Ą ā¤ļāĨ‹ ⤏āĨ‡ ā¤Ŧā¤žā¤šā¤° ⤍ā¤ŋ⤕⤞āĨ‡ā¤‚", "expand": "ā¤ĩā¤ŋ⤏āĨā¤¤ā¤žā¤° ⤕⤰āĨ‡ā¤‚", "expand_all": "⤏⤭āĨ€ ā¤•ā¤ž ā¤ĩā¤ŋ⤏āĨā¤¤ā¤žā¤°", + "experimental_settings_new_asset_list_subtitle": "ā¤•ā¤žā¤°āĨā¤¯ ā¤ĒāĨā¤°ā¤—⤤ā¤ŋ ā¤Ē⤰ ā¤šāĨˆ", + "experimental_settings_new_asset_list_title": "ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤• ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤗āĨā¤°ā¤ŋā¤Ą ⤏⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚", + "experimental_settings_subtitle": "⤅ā¤Ē⤍āĨ‡ ⤜āĨ‹ā¤–ā¤ŋā¤Ž ā¤Ē⤰ ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚!", + "experimental_settings_title": "ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤•", "expire_after": "ā¤ā¤•āĨā¤¸ā¤Ēā¤žā¤¯ā¤° ⤆ā¤ĢāĨā¤Ÿā¤°", "expired": "⤖⤤āĨā¤Ž ā¤šāĨ‹ ⤚āĨā¤•ā¤ž", "expires_date": "{date} ⤕āĨ‹ ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤ ā¤šāĨ‹ ā¤°ā¤šā¤ž ā¤šāĨˆ", @@ -1047,19 +1197,24 @@ "favorite_action_prompt": "{count} ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ⤏⤂⤕⤞⤍ ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹āĨœāĨ‡ ā¤—ā¤", "favorite_or_unfavorite_photo": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤¯ā¤ž ā¤¨ā¤žā¤Ē⤏⤂ā¤Ļ ā¤ĢāĨ‹ā¤ŸāĨ‹", "favorites": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž", + "favorites_page_no_favorites": "⤕āĨ‹ā¤ˆ ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤ā¤¸āĨ‡ā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋ⤞āĨ‡", "feature_photo_updated": "ā¤Ģā¤ŧāĨ€ā¤šā¤° ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "features": "ā¤ĩā¤ŋā¤ļāĨ‡ā¤ˇā¤¤ā¤žā¤ā¤", + "features_in_development": "ā¤ĩā¤ŋā¤•ā¤žā¤¸ ā¤ŽāĨ‡ā¤‚ ⤏āĨā¤ĩā¤ŋā¤§ā¤žā¤ā¤", "features_setting_description": "⤐ā¤Ē ⤏āĨā¤ĩā¤ŋā¤§ā¤žā¤“ā¤‚ ā¤•ā¤ž ā¤ĒāĨā¤°ā¤Ŧ⤂⤧⤍ ⤕⤰āĨ‡ā¤‚", "file_name_or_extension": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤•ā¤ž ā¤¨ā¤žā¤Ž ā¤¯ā¤ž ā¤ā¤•āĨā¤¸ā¤ŸāĨ‡ā¤‚ā¤ļ⤍", "file_name_text": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤¨ā¤žā¤Ž", + "file_name_with_value": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤¨ā¤žā¤Ž: {file_name}", "file_size": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤•ā¤ž ā¤¸ā¤žā¤‡ā¤œā¤ŧ", "filename": "ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤•ā¤ž ā¤¨ā¤žā¤Ž", "filetype": "ā¤Ģā¤žā¤‡ā¤˛ ā¤•ā¤ž ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "filter": "ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤°", + "filter_description": "⤚āĨā¤¨āĨ‡ ā¤—ā¤ ā¤ā¤¸āĨ‡ā¤Ÿ ⤕āĨ‹ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰⤍āĨ‡ ⤕āĨ€ ā¤ļ⤰āĨā¤¤āĨ‡ā¤‚", "filter_people": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰āĨ‡ā¤‚", "filter_places": "⤏āĨā¤Ĩā¤žā¤¨āĨ‹ā¤‚ ⤕āĨ‹ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰āĨ‡ā¤‚", "filter_tags": "⤟āĨˆā¤— ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰āĨ‡ā¤‚", "filters": "āĨžā¤ŋ⤞⤟⤰āĨā¤¸", + "find_them_fast": "⤖āĨ‹ā¤œ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¨ā¤žā¤Ž ⤏āĨ‡ ⤉⤍āĨā¤šāĨ‡ā¤‚ ⤤āĨ‡ā¤œāĨ€ ⤏āĨ‡ ā¤ĸāĨ‚⤂ā¤ĸāĨ‡ā¤‚", "first": "ā¤Ēā¤šā¤˛ā¤ž", "fix_incorrect_match": "⤗ā¤ŧ⤞⤤ ā¤Žā¤ŋā¤˛ā¤žā¤¨ ⤠āĨ€ā¤• ⤕⤰āĨ‡ā¤‚", "folder": "ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤°", @@ -1092,6 +1247,7 @@ "group_owner": "⤏āĨā¤ĩā¤žā¤ŽāĨ€ ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ā¤¸ā¤ŽāĨ‚ā¤š", "group_places_by": "⤏āĨā¤Ĩā¤žā¤¨āĨ‹ā¤‚ ⤕āĨ‹ ā¤¸ā¤ŽāĨ‚ā¤šā¤Ŧā¤ĻāĨā¤§ ⤕⤰āĨ‡ā¤‚..āĨ¤", "group_year": "ā¤ĩ⤰āĨā¤ˇ ⤕āĨ‡ ⤅⤍āĨā¤¸ā¤žā¤° ā¤¸ā¤ŽāĨ‚ā¤š", + "haptic_feedback_switch": "⤏āĨā¤Ē⤰āĨā¤ļ ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤏⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚", "haptic_feedback_title": "ā¤šāĨˆā¤ĒāĨā¤Ÿā¤ŋ⤕ ā¤°ā¤žā¤¯", "has_quota": "⤕āĨ‹ā¤Ÿā¤ž ā¤šāĨˆ", "hash_asset": "ā¤šāĨˆā¤ļ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ", @@ -1112,12 +1268,29 @@ "hide_schema": "⤏āĨā¤•āĨ€ā¤Žā¤ž ⤛ā¤ŋā¤Ēā¤žā¤ā¤", "hide_text_recognition": "⤟āĨ‡ā¤•āĨā¤¸āĨā¤Ÿ ā¤Ēā¤šā¤šā¤žā¤¨ ⤛ā¤ŋā¤Ēā¤žā¤ā¤", "hide_unnamed_people": "ā¤…ā¤¨ā¤žā¤Ž ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ⤛āĨā¤Ēā¤žā¤ā¤‚", + "home_page_add_to_album_conflicts": "{added} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤ā¤˛āĨā¤Ŧā¤Ž {album} ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ€ ā¤—ā¤ˆā¤‚. {failed} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤Ēā¤šā¤˛āĨ‡ ⤏āĨ‡ ā¤šāĨ€ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤šāĨˆā¤‚āĨ¤", + "home_page_add_to_album_err_local": "⤅⤭āĨ€ ⤤⤕ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤¨ā¤šāĨ€ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ€ ā¤œā¤ž ⤏⤕āĨ€ā¤‚, ⤛āĨ‹ā¤Ąā¤ŧ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "home_page_add_to_album_success": "ā¤ā¤˛āĨā¤Ŧā¤Ž {album} ā¤ŽāĨ‡ā¤‚ {added} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ€ ā¤—ā¤ˆā¤‚āĨ¤", + "home_page_album_err_partner": "⤅⤭āĨ€ ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤕āĨ‹ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ ā¤¨ā¤šāĨ€ā¤‚ ⤏⤕⤤āĨ‡, ⤏āĨā¤•ā¤ŋā¤Ē ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚", + "home_page_archive_err_local": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤅⤭āĨ€ ⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆ", + "home_page_archive_err_partner": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤕āĨ‹ ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ⤏⤕⤤āĨ‡, ⤏āĨā¤•ā¤ŋā¤Ē ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚", "home_page_building_timeline": "ā¤¸ā¤Žā¤¯ā¤°āĨ‡ā¤–ā¤ž ā¤•ā¤ž ⤍ā¤ŋ⤰āĨā¤Žā¤žā¤Ŗ", + "home_page_delete_err_partner": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤕āĨ‹ ā¤Ąā¤ŋ⤞āĨ€ā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ⤏⤕⤤āĨ‡, ⤏āĨā¤•ā¤ŋā¤Ē ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚", + "home_page_delete_remote_err_local": "ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ⤚⤝⤍ ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¨āĨ‡, ⤛āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤", + "home_page_favorite_err_local": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤅⤭āĨ€ ⤤⤕ ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆ", + "home_page_favorite_err_partner": "⤅ā¤Ŧ ⤤⤕ ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤕āĨ‹ ā¤ĢāĨ‡ā¤ĩ⤰āĨ‡ā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ⤏⤕⤤āĨ‡, ⤏āĨā¤•ā¤ŋā¤Ē ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚", + "home_page_first_time_notice": "⤝ā¤Ļā¤ŋ ⤆ā¤Ē ā¤Ēā¤šā¤˛āĨ€ ā¤Ŧā¤žā¤° ⤇⤏ ⤐ā¤Ē ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚, ⤤āĨ‹ ⤕āĨƒā¤Ēā¤¯ā¤ž ā¤ā¤• ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ā¤˛āĨā¤Ŧā¤Ž ⤚āĨā¤¨āĨ‡ā¤‚, ā¤¤ā¤žā¤•ā¤ŋ ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨ ā¤ŽāĨ‡ā¤‚ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤Ļā¤ŋā¤–ā¤žā¤ˆ ā¤ĻāĨ‡ ⤏⤕āĨ‡ā¤‚", + "home_page_locked_error_local": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ŽāĨ‡ā¤‚ ā¤¨ā¤šāĨ€ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž ā¤šāĨˆ", + "home_page_locked_error_partner": "ā¤¸ā¤žā¤āĨ‡ā¤Ļā¤žā¤° ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ŽāĨ‡ā¤‚ ā¤¨ā¤šāĨ€ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", + "home_page_share_err_local": "⤞āĨ‹ā¤•⤞ ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸ ⤕āĨ‹ ⤞ā¤ŋ⤂⤕ ⤕āĨ‡ ⤜⤰ā¤ŋā¤ ā¤ļāĨ‡ā¤¯ā¤° ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ⤏⤕⤤āĨ‡, ⤏āĨā¤•ā¤ŋā¤Ē ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚", + "home_page_upload_err_limit": "ā¤ā¤• ā¤¸ā¤Žā¤¯ ā¤ŽāĨ‡ā¤‚ ⤅⤧ā¤ŋā¤•ā¤¤ā¤Ž 30 ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤šāĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕āĨ€ ā¤œā¤ž ⤏⤕⤤āĨ€ ā¤šāĨˆā¤‚, ⤛āĨ‹ā¤Ąā¤ŧ⤕⤰", "host": "ā¤ŽāĨ‡ā¤œā¤ŧā¤Ŧā¤žā¤¨", "hour": "ā¤˜ā¤‚ā¤Ÿā¤ž", "hours": "ā¤˜ā¤‚ā¤ŸāĨ‡", "id": "ā¤Ēā¤šā¤šā¤žā¤¨", "idle": "⤍ā¤ŋ⤎āĨā¤•āĨā¤°ā¤ŋ⤝", + "ignore_icloud_photos": "⤆⤇⤕āĨā¤˛ā¤žā¤‰ā¤Ą ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤕āĨ‹ ⤅⤍ā¤ĻāĨ‡ā¤–ā¤ž ⤕⤰āĨ‡ā¤‚", + "ignore_icloud_photos_description": "⤆⤇⤕āĨā¤˛ā¤žā¤‰ā¤Ą ā¤Ē⤰ ⤏āĨā¤ŸāĨ‹ā¤° ⤕āĨ€ ā¤—ā¤ˆ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ā¤œā¤ŧ ā¤‡ā¤Žā¤ŋ⤚ ⤏⤰āĨā¤ĩ⤰ ā¤Ē⤰ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤œā¤žā¤ā¤‚ā¤—āĨ€", "image": "⤛ā¤ĩā¤ŋ", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} {date} ⤕āĨ‹ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} {person1} ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ {date} ⤕āĨ‹ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", @@ -1129,6 +1302,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} ā¤ŽāĨ‡ā¤‚ {person1} ⤔⤰ {person2} ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ {date} ⤕āĨ‹ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} ā¤ŽāĨ‡ā¤‚ {person1}, {person2}, ⤔⤰ {person3} ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ {date} ⤕āĨ‹ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} {city}, {country} ā¤ŽāĨ‡ā¤‚ {person1}, {person2}, ⤔⤰ {additionalCount, number} ⤅⤍āĨā¤¯ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ {date} ⤕āĨ‹ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "image_saved_successfully": "ā¤‡ā¤ŽāĨ‡ā¤œ ā¤¸ā¤šāĨ‡ā¤œ ā¤ĻāĨ€ ā¤—ā¤ˆ", + "image_viewer_page_state_provider_download_started": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ļāĨā¤°āĨ‚", + "image_viewer_page_state_provider_download_success": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏ā¤Ģ⤞", + "image_viewer_page_state_provider_share_error": "ā¤¸ā¤žā¤ā¤ž ⤕⤰⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ", "immich_logo": "Immich ⤞āĨ‹ā¤—āĨ‹", "immich_web_interface": "ā¤‡ā¤Žā¤ŋ⤚ ā¤ĩāĨ‡ā¤Ŧ ā¤‡ā¤‚ā¤Ÿā¤°ā¤Ģā¤ŧāĨ‡ā¤¸", "import_from_json": "JSON ⤏āĨ‡ ā¤†ā¤¯ā¤žā¤¤ ⤕⤰āĨ‡ā¤‚", @@ -1153,8 +1330,16 @@ "invalid_date_format": "ā¤…ā¤Žā¤žā¤¨āĨā¤¯ ā¤¤ā¤žā¤°āĨ€ā¤–ā¤ŧ ā¤ĒāĨā¤°ā¤žā¤°āĨ‚ā¤Ē", "invite_people": "⤞āĨ‹ā¤—āĨ‹ ⤕āĨ‹ ⤍ā¤ŋā¤Žā¤‚ā¤¤āĨā¤°ā¤Ŗ ⤭āĨ‡ā¤œāĨ‹", "invite_to_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤†ā¤Žā¤‚ā¤¤āĨā¤°ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "ios_debug_info_fetch_ran_at": "ā¤Ģā¤ŧāĨ‡ā¤š ⤰⤍ {dateTime}", + "ios_debug_info_last_sync_at": "⤅⤂⤤ā¤ŋā¤Ž ⤏ā¤ŋ⤂⤕ {dateTime}", + "ios_debug_info_no_processes_queued": "⤕āĨ‹ā¤ˆ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤•ā¤¤ā¤žā¤°ā¤Ŧā¤ĻāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", + "ios_debug_info_no_sync_yet": "⤅⤭āĨ€ ⤤⤕ ⤕āĨ‹ā¤ˆ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤¸ā¤Žā¤¨āĨā¤ĩ⤝⤍ ā¤•ā¤žā¤°āĨā¤¯ ā¤¨ā¤šāĨ€ā¤‚ ā¤šā¤˛ā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ĒāĨā¤°āĨ‹ā¤¸āĨ‡ā¤¸ ā¤•ā¤¤ā¤žā¤° ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž} other {{count} ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ĒāĨā¤°āĨ‹ā¤¸āĨ‡ā¤¸ ā¤•ā¤¤ā¤žā¤° ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ ā¤—ā¤}}", + "ios_debug_info_processing_ran_at": "ā¤ĒāĨā¤°ā¤¸ā¤‚⤏āĨā¤•⤰⤪ {dateTime} ā¤Ē⤰ ā¤šā¤˛ā¤ž", "items_count": "{count, plural, one {# ā¤†ā¤‡ā¤Ÿā¤Ž} other {# ā¤†ā¤‡ā¤Ÿā¤Ž}}", "jobs": "⤍āĨŒā¤•⤰ā¤ŋā¤¯ā¤žā¤‚", + "json_editor": "JSON ⤏⤂ā¤Ēā¤žā¤Ļ⤕", + "json_error": "JSON ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž", "keep": "ā¤°ā¤–ā¤¨ā¤ž", "keep_albums": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ ⤰⤖āĨ‡ā¤‚", "keep_albums_count": "⤰⤖āĨ‡ ā¤œā¤ž ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚ {count} {count, plural, one {ā¤ā¤˛āĨā¤Ŧā¤Ž} other {ā¤ā¤˛āĨā¤Ŧā¤Ž}}", @@ -1186,6 +1371,9 @@ "library": "ā¤ĒāĨā¤¸āĨā¤¤ā¤•ā¤žā¤˛ā¤¯", "library_add_folder": "ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", "library_edit_folder": "ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "library_options": "ā¤ĒāĨā¤¸āĨā¤¤ā¤•ā¤žā¤˛ā¤¯ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", + "library_page_device_albums": "ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤Ē⤰ ā¤ā¤˛āĨā¤Ŧā¤Ž", + "library_page_new_album": "ā¤¨ā¤¯ā¤ž ā¤ā¤˛āĨā¤Ŧā¤Ž", "library_page_sort_asset_count": "ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ€ ⤏⤂⤖āĨā¤¯ā¤ž", "library_page_sort_created": "⤏āĨƒā¤œā¤ŋ⤤ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤•", "library_page_sort_last_modified": "⤅⤂⤤ā¤ŋā¤Ž ⤏⤂ā¤ļāĨ‹ā¤§ā¤¨", @@ -1226,9 +1414,11 @@ "login": "⤞āĨ‰ā¤— ⤇⤍ ⤕⤰āĨ‡ā¤‚", "login_disabled": "⤞āĨ‰ā¤—ā¤ŋ⤍ ⤅⤕āĨā¤ˇā¤Ž ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "login_form_api_exception": "API ⤅ā¤Ēā¤ĩā¤žā¤Ļ. ⤕āĨƒā¤Ēā¤¯ā¤ž ⤏⤰āĨā¤ĩ⤰ URL ā¤œā¤žā¤ā¤šāĨ‡ā¤‚ ⤔⤰ ā¤ĒāĨā¤¨ā¤ƒ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕⤰āĨ‡ā¤‚āĨ¤", + "login_form_back_button_text": "ā¤ĒāĨ€ā¤›āĨ‡", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://⤆ā¤Ēā¤•ā¤ž-⤏⤰āĨā¤ĩ⤰-ā¤†ā¤ˆā¤ĒāĨ€:ā¤ĒāĨ‹ā¤°āĨā¤Ÿ", "login_form_endpoint_url": "⤏⤰āĨā¤ĩ⤰ ā¤ā¤‚ā¤Ąā¤ĒāĨ‰ā¤‡ā¤‚ā¤Ÿ URL", + "login_form_err_http": "⤕āĨƒā¤Ēā¤¯ā¤ž http:// ā¤¯ā¤ž https:// ⤍ā¤ŋ⤰āĨā¤Ļā¤ŋ⤎āĨā¤Ÿ ⤕⤰āĨ‡ā¤‚", "login_form_err_invalid_email": "ā¤…ā¤Žā¤žā¤¨āĨā¤¯ ā¤ˆā¤ŽāĨ‡ā¤˛", "login_form_err_invalid_url": "ā¤…ā¤¸ā¤žā¤Žā¤žā¤¨āĨā¤¯ ⤝āĨ‚ā¤†ā¤°ā¤ā¤˛", "login_form_err_leading_whitespace": "⤅⤗āĨā¤°ā¤ŖāĨ€ ⤰ā¤ŋ⤕āĨā¤¤ ⤏āĨā¤Ĩā¤žā¤¨", @@ -1238,6 +1428,7 @@ "login_form_failed_login": "⤞āĨ‰ā¤— ⤇⤍ ⤕⤰⤤āĨ‡ ā¤¸ā¤Žā¤¯ ⤤āĨā¤°āĨā¤Ÿā¤ŋ ā¤šāĨā¤ˆ, ⤏⤰āĨā¤ĩ⤰ URL, ā¤ˆā¤ŽāĨ‡ā¤˛ ⤔⤰ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤œā¤žā¤‚ā¤šāĨ‡ā¤‚", "login_form_handshake_exception": "⤏⤰āĨā¤ĩ⤰ ā¤ŽāĨ‡ā¤‚ ā¤ā¤• ā¤šāĨˆā¤‚ā¤Ąā¤ļāĨ‡ā¤• ⤅ā¤Ēā¤ĩā¤žā¤Ļ ā¤Ĩā¤žāĨ¤ ⤝ā¤Ļā¤ŋ ⤆ā¤Ē ⤏āĨā¤ĩ-ā¤šā¤¸āĨā¤¤ā¤žā¤•āĨā¤ˇā¤°ā¤ŋ⤤ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚, ⤤āĨ‹ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ā¤ŽāĨ‡ā¤‚ ⤏āĨā¤ĩ-ā¤šā¤¸āĨā¤¤ā¤žā¤•āĨā¤ˇā¤°ā¤ŋ⤤ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤¸ā¤Žā¤°āĨā¤Ĩ⤍ ⤏⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚āĨ¤", "login_form_password_hint": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", + "login_form_save_login": "⤞āĨ‰ā¤— ⤇⤍ ā¤°ā¤šāĨ‡ā¤‚", "login_form_server_empty": "⤏⤰āĨā¤ĩ⤰ URL ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚āĨ¤", "login_form_server_error": "⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ⤕⤍āĨ‡ā¤•āĨā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ⤕⤰ ā¤¸ā¤•ā¤žāĨ¤", "login_has_been_disabled": "⤞āĨ‰ā¤—ā¤ŋ⤍ ⤅⤕āĨā¤ˇā¤Ž ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆāĨ¤", @@ -1255,6 +1446,7 @@ "maintenance_action_restore": "ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆ", "maintenance_description": "Immich ⤕āĨ‹ ā¤ŽāĨ‡ā¤‚ā¤ŸāĨ‡ā¤¨āĨ‡ā¤‚⤏ ā¤ŽāĨ‹ā¤Ą ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤šāĨˆāĨ¤", "maintenance_end": "ā¤°ā¤–ā¤°ā¤–ā¤žā¤ĩ ā¤ŽāĨ‹ā¤Ą ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤ ⤕⤰āĨ‡ā¤‚", + "maintenance_end_error": "ā¤ŽāĨ‡ā¤‚ā¤ŸāĨ‡ā¤¨āĨ‡ā¤‚⤏ ā¤ŽāĨ‹ā¤Ą ⤖⤤āĨā¤Ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨ‹ ā¤¸ā¤•ā¤žāĨ¤", "maintenance_logged_in_as": "⤅⤭āĨ€ {user} ⤕āĨ‡ ⤤āĨŒā¤° ā¤Ē⤰ ⤞āĨ‰ā¤— ⤇⤍ ā¤šāĨˆā¤‚", "maintenance_restore_from_backup": "ā¤ŦāĨˆā¤•⤅ā¤Ē ⤏āĨ‡ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "maintenance_restore_library": "⤅ā¤Ē⤍āĨ€ ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", @@ -1280,6 +1472,7 @@ "manage_media_access_settings": "⤖āĨā¤˛āĨ€ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗", "manage_media_access_subtitle": "Immich ⤐ā¤Ē ⤕āĨ‹ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‹ā¤‚ ⤕āĨ‹ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤔⤰ ⤏āĨā¤Ĩā¤žā¤¨ā¤žā¤‚ā¤¤ā¤°ā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚āĨ¤", "manage_media_access_title": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤Ŧ⤂⤧⤍ ā¤Ēā¤šāĨā¤ā¤š", + "manage_shared_links": "ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤ ā¤—ā¤ ⤞ā¤ŋ⤂⤕ ā¤•ā¤ž ā¤ĒāĨā¤°ā¤Ŧ⤂⤧⤍ ⤕⤰āĨ‡ā¤‚", "manage_sharing_with_partners": "ā¤¸ā¤žā¤āĨ‡ā¤Ļā¤žā¤°āĨ‹ā¤‚ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¸ā¤žā¤ā¤žā¤•ā¤°ā¤Ŗ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "manage_the_app_settings": "⤐ā¤Ē ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "manage_your_account": "⤅ā¤Ēā¤¨ā¤ž ā¤–ā¤žā¤¤ā¤ž ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", @@ -1287,10 +1480,13 @@ "manage_your_devices": "⤅ā¤Ē⤍āĨ‡ ⤞āĨ‰ā¤—-⤇⤍ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "manage_your_oauth_connection": "⤅ā¤Ēā¤¨ā¤ž OAuth ⤕⤍āĨ‡ā¤•āĨā¤ļ⤍ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "map": "⤍⤕āĨā¤ļā¤ž", + "map_assets_in_bounds": "{count, plural, =0 {ā¤¯ā¤šā¤žā¤ ⤕āĨ‹ā¤ˆ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ} one {# ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹} other {# ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹}}", "map_cannot_get_user_location": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤•ā¤ž ⤏āĨā¤Ĩā¤žā¤¨ ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤ž", + "map_location_dialog_yes": "ā¤šā¤žā¤", "map_location_picker_page_use_location": "⤇⤏ ⤏āĨā¤Ĩā¤žā¤¨ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚", "map_location_service_disabled_content": "⤆ā¤Ē⤕āĨ‡ ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤏āĨā¤Ĩā¤žā¤¨ ⤕āĨ€ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏āĨā¤Ĩā¤žā¤¨ ⤏āĨ‡ā¤ĩā¤ž ⤏⤕āĨā¤ˇā¤Ž ā¤šāĨ‹ā¤¨āĨ€ ā¤šā¤žā¤šā¤ŋā¤āĨ¤ ⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤇⤏āĨ‡ ⤅⤭āĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "map_location_service_disabled_title": "⤏āĨā¤Ĩā¤žā¤¨ ⤏āĨ‡ā¤ĩā¤ž ⤅⤕āĨā¤ˇā¤Ž", + "map_marker_for_images": "{city}, {country} ā¤ŽāĨ‡ā¤‚ ⤞āĨ€ ā¤—ā¤ˆ ⤛ā¤ĩā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Žā¤žā¤¨ā¤šā¤ŋ⤤āĨā¤° ā¤Žā¤žā¤°āĨā¤•⤰", "map_marker_with_image": "⤛ā¤ĩā¤ŋ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤Žā¤žā¤¨ā¤šā¤ŋ⤤āĨā¤° ā¤Žā¤žā¤°āĨā¤•⤰", "map_no_location_permission_content": "⤆ā¤Ē⤕āĨ‡ ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤏āĨā¤Ĩā¤žā¤¨ ⤏āĨ‡ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏āĨā¤Ĩā¤žā¤¨ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤šāĨˆāĨ¤ ⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤇⤏āĨ‡ ⤅⤭āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "map_no_location_permission_title": "⤏āĨā¤Ĩā¤žā¤¨ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤅⤏āĨā¤ĩāĨ€ā¤•āĨƒā¤¤", @@ -1300,11 +1496,14 @@ "map_settings_date_range_option_days": "ā¤Ēā¤ŋ⤛⤞āĨ‡ {days} ā¤Ļā¤ŋ⤍", "map_settings_date_range_option_year": "ā¤Ēā¤ŋ⤛⤞āĨ‡ ā¤ā¤• ā¤¸ā¤žā¤˛", "map_settings_date_range_option_years": "ā¤Ēā¤ŋ⤛⤞āĨ‡ {years} ā¤ĩ⤰āĨā¤ˇ", + "map_settings_dialog_title": "ā¤Žā¤žā¤¨ā¤šā¤ŋ⤤āĨā¤° ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸", "map_settings_include_show_archived": "⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ā¤ļā¤žā¤Žā¤ŋ⤞ ⤕⤰āĨ‡ā¤‚", "map_settings_include_show_partners": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°āĨ‹ā¤‚ ⤕āĨ‹ ā¤ļā¤žā¤Žā¤ŋ⤞ ⤕⤰āĨ‡ā¤‚", "map_settings_only_show_favorites": "⤕āĨ‡ā¤ĩ⤞ ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ā¤Ļā¤ŋā¤–ā¤žā¤ā¤", "map_settings_theme_settings": "ā¤Žā¤žā¤¨ā¤šā¤ŋ⤤āĨā¤° ā¤ĨāĨ€ā¤Ž", + "map_zoom_to_see_photos": "ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤜ā¤ŧāĨ‚ā¤Ž ā¤†ā¤‰ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "mark_all_as_read": "⤏⤭āĨ€ ⤕āĨ‹ ā¤Ēā¤ĸā¤ŧā¤ž ā¤šāĨā¤† ā¤Žā¤žā¤°āĨā¤• ⤕⤰āĨ‡ā¤‚", + "mark_as_read": "ā¤Ēā¤ĸā¤ŧāĨ‡ ā¤šāĨā¤ ā¤•ā¤ž ⤚ā¤ŋā¤šāĨā¤¨", "marked_all_as_read": "⤏⤭āĨ€ ⤕āĨ‹ ā¤Ēā¤ĸā¤ŧā¤ž ā¤šāĨā¤† ⤚ā¤ŋā¤šāĨā¤¨ā¤ŋ⤤ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "matches": "ā¤Žā¤žā¤šā¤ŋ⤏", "matching_assets": "ā¤Žā¤ŋā¤˛ā¤žā¤¨ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚", @@ -1327,20 +1526,29 @@ "minimize": "⤛āĨ‹ā¤Ÿā¤ž ā¤•ā¤°ā¤¨ā¤ž", "minute": "ā¤Žā¤ŋ⤍⤟", "minutes": "ā¤Žā¤ŋ⤍⤟", + "mirror_horizontal": "⤕āĨā¤ˇāĨˆā¤¤ā¤ŋ⤜", + "mirror_vertical": "⤊⤰āĨā¤§āĨā¤ĩā¤žā¤§ā¤°", "missing": "⤗āĨā¤Ž", "mobile_app": "ā¤ŽāĨ‹ā¤Ŧā¤žā¤‡ā¤˛ ā¤ā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ļ⤍", "mobile_app_download_onboarding_note": "⤍ā¤ŋā¤ŽāĨā¤¨ā¤˛ā¤ŋ⤖ā¤ŋ⤤ ā¤ĩā¤ŋ⤕⤞āĨā¤ĒāĨ‹ā¤‚ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤕āĨ‡ ā¤¸ā¤žā¤ĨāĨ€ ā¤ŽāĨ‹ā¤Ŧā¤žā¤‡ā¤˛ ⤐ā¤Ē ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "model": "ā¤ŽāĨ‰ā¤Ąā¤˛", "month": "ā¤Žā¤šāĨ€ā¤¨ā¤ž", + "monthly_title_text_date_format": "ā¤ā¤Žā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤ĩā¤žā¤ˆ", "more": "⤅⤧ā¤ŋ⤕", "move": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ē⤰ā¤ŋā¤ĩ⤰āĨā¤¤ā¤¨", + "move_down": "⤍āĨ€ā¤šāĨ‡ ⤞āĨ‡ ā¤œā¤žā¤ā¤", "move_off_locked_folder": "⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤏āĨ‡ ā¤Ŧā¤žā¤šā¤° ⤞āĨ‡ ā¤œā¤žā¤ā¤‚", "move_to": "⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕ā¤Ļā¤Ž", "move_to_device_trash": "ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ⤕āĨ‡ ⤟āĨā¤°āĨˆā¤ļ ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤ā¤", "move_to_lock_folder_action_prompt": "{count} ⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ŽāĨ‡ā¤‚ ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤—ā¤¯ā¤ž", "move_to_locked_folder": "⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤ā¤‚", "move_to_locked_folder_confirmation": "⤝āĨ‡ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤏⤭āĨ€ ā¤ā¤˛āĨā¤Ŧā¤ŽāĨ‹ā¤‚ ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤ā¤—āĨ‡ ⤔⤰ ⤕āĨ‡ā¤ĩ⤞ ⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤏āĨ‡ ā¤šāĨ€ ā¤ĻāĨ‡ā¤–āĨ‡ ā¤œā¤ž ⤏⤕āĨ‡ā¤‚⤗āĨ‡", + "move_up": "⤊ā¤Ē⤰ ⤞āĨ‡ ā¤œā¤žā¤ā¤", + "moved_to_archive": "{count, plural, one {# asset} other {# assets}} ⤕āĨ‹ ⤏⤂⤗āĨā¤°ā¤š ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "moved_to_library": "{count, plural, one {# asset} other {# assets}} ⤕āĨ‹ ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "moved_to_trash": "⤕āĨ‚ā¤Ąā¤ŧāĨ‡ā¤Ļā¤žā¤¨ ā¤ŽāĨ‡ā¤‚ ⤞āĨ‡ ā¤œā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "multiselect_grid_edit_date_time_err_read_only": "⤕āĨ‡ā¤ĩ⤞ ā¤Ēā¤ĸā¤ŧ⤍āĨ‡ ⤝āĨ‹ā¤—āĨā¤¯ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(⤝āĨ‹ā¤‚) ⤕āĨ€ ⤤ā¤ŋā¤Ĩā¤ŋ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤œā¤ž ⤏⤕⤤āĨ€, ⤛āĨ‹ā¤Ąā¤ŧāĨ€ ā¤œā¤ž ā¤°ā¤šāĨ€ ā¤šāĨˆ", + "multiselect_grid_edit_gps_err_read_only": "⤕āĨ‡ā¤ĩ⤞ ā¤Ēā¤ĸā¤ŧ⤍āĨ‡ ⤝āĨ‹ā¤—āĨā¤¯ ā¤Ē⤰ā¤ŋ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ(⤝āĨ‹ā¤‚) ā¤•ā¤ž ⤏āĨā¤Ĩā¤žā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤¸ā¤•ā¤¤ā¤ž, ⤛āĨ‹ā¤Ąā¤ŧā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆ", "mute_memories": "ā¤ŽāĨ‚⤕ ā¤¯ā¤žā¤ĻāĨ‡ā¤‚", "my_albums": "ā¤ŽāĨ‡ā¤°āĨ‡ ā¤ā¤˛āĨā¤Ŧā¤Ž", "name": "ā¤¨ā¤žā¤Ž", @@ -1357,10 +1565,12 @@ "never": "⤕⤭āĨ€ ā¤¨ā¤šāĨ€ā¤‚", "new_album": "⤍⤝āĨ€ ā¤ā¤˛āĨā¤Ŧā¤Ž", "new_api_key": "⤍⤈ ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨā¤‚ā¤œāĨ€", + "new_date_range": "⤍⤈ ⤤ā¤ŋā¤Ĩā¤ŋ ⤏āĨ€ā¤Žā¤ž", "new_password": "ā¤¨ā¤¯ā¤ž ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", "new_person": "ā¤¨ā¤¯ā¤ž ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ", "new_pin_code": "ā¤¨ā¤¯ā¤ž ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą", "new_pin_code_subtitle": "⤆ā¤Ē ā¤Ēā¤šā¤˛āĨ€ ā¤Ŧā¤žā¤° ⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ⤤⤕ ā¤Ēā¤šāĨā¤ā¤š ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚āĨ¤ ⤇⤏ ā¤ĒāĨƒā¤ˇāĨā¤  ⤤⤕ ⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤Ēā¤šāĨā¤ā¤šā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ā¤Ŧā¤¨ā¤žā¤ā¤", + "new_timeline": "⤍⤈ ā¤¸ā¤Žā¤¯ā¤°āĨ‡ā¤–ā¤ž", "new_update": "⤍⤈ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ", "new_user_created": "ā¤¨ā¤¯ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "new_version_available": "ā¤¨ā¤¯ā¤ž ⤏⤂⤏āĨā¤•⤰⤪ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤šāĨˆ", @@ -1368,6 +1578,7 @@ "next": "ā¤…ā¤—ā¤˛ā¤ž", "next_memory": "⤅⤗⤞āĨ€ ⤏āĨā¤ŽāĨƒā¤¤ā¤ŋ", "no": "ā¤¨ā¤šāĨ€ā¤‚", + "no_actions_added": "⤅⤭āĨ€ ⤤⤕ ⤕āĨ‹ā¤ˆ ā¤•ā¤žā¤°āĨā¤°ā¤ĩā¤žā¤ˆ ⤜āĨ‹ā¤Ąā¤ŧāĨ€ ā¤¨ā¤šāĨ€ā¤‚ ā¤—ā¤ˆ ā¤šāĨˆ", "no_albums_found": "⤕āĨ‹ā¤ˆ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤ž", "no_albums_message": "⤅ā¤Ē⤍āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤕āĨ‹ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ā¤‚", "no_albums_with_name_yet": "ā¤ā¤¸ā¤ž ā¤˛ā¤—ā¤¤ā¤ž ā¤šāĨˆ ⤕ā¤ŋ ⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ⤅⤭āĨ€ ⤤⤕ ⤇⤏ ā¤¨ā¤žā¤Ž ā¤•ā¤ž ⤕āĨ‹ā¤ˆ ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆāĨ¤", @@ -1378,13 +1589,16 @@ "no_cast_devices_found": "⤕āĨ‹ā¤ˆ ā¤•ā¤žā¤¸āĨā¤Ÿ ā¤Ąā¤ŋā¤ĩā¤žā¤‡ā¤¸ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤ž", "no_checksum_local": "⤕āĨ‹ā¤ˆ ⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ - ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤‚ ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤œā¤ž ⤏⤕⤤āĨ€ā¤‚", "no_checksum_remote": "⤕āĨ‹ā¤ˆ ⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ - ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤œā¤ž ⤏⤕⤤āĨ€", + "no_configuration_needed": "⤕ā¤ŋ⤏āĨ€ ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŧā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ ⤕āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", "no_devices": "⤕āĨ‹ā¤ˆ ⤅⤧ā¤ŋ⤕āĨƒā¤¤ ⤉ā¤Ē⤕⤰⤪ ā¤¨ā¤šāĨ€ā¤‚", "no_duplicates_found": "⤕āĨ‹ā¤ˆ ā¤¨ā¤•ā¤˛ā¤šāĨ€ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤žāĨ¤", "no_exif_info_available": "⤕āĨ‹ā¤ˆ ā¤ā¤•āĨā¤¸ā¤ŋā¤Ģā¤ŧ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", "no_explore_results_message": "⤅ā¤Ē⤍āĨ‡ ⤏⤂⤗āĨā¤°ā¤š ā¤•ā¤ž ā¤Ēā¤¤ā¤ž ā¤˛ā¤—ā¤žā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤔⤰ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚āĨ¤", "no_favorites_message": "⤅ā¤Ē⤍āĨ€ ⤏⤰āĨā¤ĩā¤ļāĨā¤°āĨ‡ā¤ˇāĨā¤  ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤤āĨā¤°ā¤‚⤤ ā¤ĸāĨ‚⤂ā¤ĸ⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ⤜āĨ‹ā¤Ąā¤ŧāĨ‡ā¤‚", + "no_filters_added": "⤅⤭āĨ€ ⤤⤕ ⤕āĨ‹ā¤ˆ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤° ⤜āĨ‹ā¤Ąā¤ŧā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤—ā¤¯ā¤ž ā¤šāĨˆ", "no_libraries_message": "⤅ā¤Ē⤍āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ā¤Ŧā¤žā¤šā¤°āĨ€ ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ā¤Ŧā¤¨ā¤žā¤ā¤‚", "no_local_assets_found": "⤇⤏ ⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ⤕āĨ‹ā¤ˆ ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋ⤞āĨ€", + "no_location_set": "⤕āĨ‹ā¤ˆ ⤏āĨā¤Ĩā¤žā¤¨ ⤍ā¤ŋ⤰āĨā¤§ā¤žā¤°ā¤ŋ⤤ ā¤¨ā¤šāĨ€ā¤‚", "no_locked_photos_message": "⤞āĨ‰ā¤• ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ŽāĨ‡ā¤‚ ā¤ŽāĨŒā¤œāĨ‚ā¤Ļ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤛ā¤ŋā¤ĒāĨ‡ ā¤šāĨā¤ ā¤šāĨˆā¤‚ ⤔⤰ ⤜ā¤Ŧ ⤆ā¤Ē ⤅ā¤Ē⤍āĨ€ ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ā¤ŦāĨā¤°ā¤žā¤‰ā¤œā¤ŧ ā¤¯ā¤ž ⤖āĨ‹ā¤œāĨ‡ā¤‚⤗āĨ‡ ⤤āĨ‹ ā¤ĩāĨ‡ ā¤Ļā¤ŋā¤–ā¤žā¤ˆ ā¤¨ā¤šāĨ€ā¤‚ ā¤ĻāĨ‡ā¤‚⤗āĨ‡āĨ¤", "no_name": "⤕āĨ‹ā¤ˆ ā¤¨ā¤žā¤Ž ā¤¨ā¤šāĨ€ā¤‚", "no_notifications": "⤕āĨ‹ā¤ˆ ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤¨ā¤šāĨ€ā¤‚", @@ -1394,6 +1608,7 @@ "no_results": "⤕āĨ‹ā¤ˆ ā¤Ē⤰ā¤ŋā¤Ŗā¤žā¤Ž ā¤¨ā¤šāĨ€ā¤‚", "no_results_description": "⤕āĨ‹ā¤ˆ ā¤Ē⤰āĨā¤¯ā¤žā¤¯ā¤ĩā¤žā¤šāĨ€ ā¤¯ā¤ž ⤅⤧ā¤ŋ⤕ ā¤¸ā¤žā¤Žā¤žā¤¨āĨā¤¯ ⤕āĨ€ā¤ĩ⤰āĨā¤Ą ā¤†ā¤œā¤ŧā¤Žā¤žā¤ā¤", "no_shared_albums_message": "⤅ā¤Ē⤍āĨ‡ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ā¤ŽāĨ‡ā¤‚ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤¸ā¤žā¤ā¤ž ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ā¤‚", + "no_uploads_in_progress": "⤕āĨ‹ā¤ˆ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤°ā¤—⤤ā¤ŋ ā¤Ē⤰ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", "none": "⤕āĨ‹ā¤ˆ ā¤¨ā¤šāĨ€ā¤‚", "not_allowed": "⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤¨ā¤šāĨ€ā¤‚", "not_available": "ā¤˛ā¤žā¤—āĨ‚ ā¤¨ā¤šāĨ€ā¤‚", @@ -1440,6 +1655,7 @@ "original": "ā¤ŽāĨ‚⤞", "other": "⤅⤍āĨā¤¯", "other_devices": "⤅⤍āĨā¤¯ ⤉ā¤Ē⤕⤰⤪", + "other_entities": "⤅⤍āĨā¤¯ ⤏⤂⤏āĨā¤Ĩā¤žā¤ā¤", "other_variables": "⤅⤍āĨā¤¯ ⤚⤰", "owned": "⤏āĨā¤ĩā¤žā¤Žā¤ŋ⤤āĨā¤ĩ", "owner": "ā¤Žā¤žā¤˛ā¤ŋ⤕", @@ -1449,9 +1665,12 @@ "partner_can_access_assets": "⤏⤂⤗āĨā¤°ā¤šāĨ€ā¤¤ ⤔⤰ ā¤šā¤Ÿā¤žā¤ ā¤—ā¤ ⤕āĨ‹ ⤛āĨ‹ā¤Ąā¤ŧ⤕⤰ ⤆ā¤Ē⤕āĨ‡ ⤏⤭āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "partner_can_access_location": "ā¤ĩā¤š ⤏āĨā¤Ĩā¤žā¤¨ ā¤œā¤šā¤žā¤‚ ⤆ā¤Ē⤕āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤞āĨ€ ā¤—ā¤ˆā¤‚ ā¤ĨāĨ€ā¤‚", "partner_list_user_photos": "{user} ⤕āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚", + "partner_list_view_all": "⤏⤭āĨ€ ⤕āĨ‹ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "partner_page_empty_message": "⤆ā¤Ē⤕āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤅⤭āĨ€ ⤤⤕ ⤕ā¤ŋ⤏āĨ€ ⤭āĨ€ ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¸ā¤žā¤ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ⤕āĨ€ ā¤—ā¤ˆ ā¤šāĨˆā¤‚āĨ¤", "partner_page_no_more_users": "⤅ā¤Ŧ ⤔⤰ ⤕āĨ‹ā¤ˆ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤜āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ⤕āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", + "partner_page_partner_add_failed": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤜āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", "partner_page_select_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤚āĨā¤¨āĨ‡ā¤‚", + "partner_page_shared_to_title": "ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "partner_page_stop_sharing_content": "{partner} will no longer be able to access your photosāĨ¤", "partner_sharing": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ā¤ļāĨ‡ā¤¯ā¤°ā¤ŋ⤂⤗", "partners": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°āĨ‹ā¤‚", @@ -1473,6 +1692,8 @@ "people": "⤞āĨ‹ā¤—", "people_edits_count": "⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ {count, plural, one {# person} other {# people}}", "people_feature_description": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ā¤¸ā¤ŽāĨ‚ā¤šāĨ€ā¤•āĨƒā¤¤ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤ŦāĨā¤°ā¤žā¤‰ā¤œā¤ŧ ā¤•ā¤°ā¤¨ā¤ž", + "people_selected": "{count, plural, one {# ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤚āĨā¤¨ā¤ž ā¤—ā¤¯ā¤ž} other {# ⤞āĨ‹ā¤— ⤚āĨā¤¨āĨ‡ ā¤—ā¤}}", + "people_sidebar_description": "ā¤¸ā¤žā¤‡ā¤Ąā¤Ŧā¤žā¤° ā¤ŽāĨ‡ā¤‚ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ⤞ā¤ŋ⤂⤕ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "permanent_deletion_warning": "⤏āĨā¤Ĩā¤žā¤¯āĨ€ ā¤ĩā¤ŋ⤞āĨ‹ā¤Ē⤍ ⤚āĨ‡ā¤¤ā¤žā¤ĩ⤍āĨ€", "permanent_deletion_warning_setting_description": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¤āĨ‡ ā¤¸ā¤Žā¤¯ ā¤ā¤• ⤚āĨ‡ā¤¤ā¤žā¤ĩ⤍āĨ€ ā¤Ļā¤ŋā¤–ā¤žā¤ā¤‚", "permanently_delete": "⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¨ā¤ž", @@ -1482,6 +1703,14 @@ "permanently_deleted_assets_count": "⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž {count, plural, one {# asset} other {# assets}}", "permission": "⤅⤍āĨā¤Žā¤¤ā¤ŋ", "permission_empty": "⤆ā¤Ē⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤–ā¤žā¤˛āĨ€ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨ‹ā¤¨āĨ€ ā¤šā¤žā¤šā¤ŋā¤", + "permission_onboarding_back": "ā¤ĩā¤žā¤Ē⤏", + "permission_onboarding_continue_anyway": "ā¤Ģā¤ŋ⤰ ⤭āĨ€ ā¤œā¤žā¤°āĨ€ ⤰⤖āĨ‡ā¤‚", + "permission_onboarding_get_started": "ā¤ļāĨā¤°āĨ‚ ā¤šāĨ‹ ā¤œā¤žā¤“", + "permission_onboarding_go_to_settings": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ā¤Ē⤰ ā¤œā¤žā¤ā¤", + "permission_onboarding_permission_denied": "⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤅⤏āĨā¤ĩāĨ€ā¤•āĨƒā¤¤āĨ¤ Immich ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤, ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤ŽāĨ‡ā¤‚ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚āĨ¤", + "permission_onboarding_permission_granted": "⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤Žā¤ŋ⤞ ā¤—ā¤ˆ! ⤆ā¤Ē ⤤āĨˆā¤¯ā¤žā¤° ā¤šāĨˆā¤‚āĨ¤", + "permission_onboarding_permission_limited": "⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤏āĨ€ā¤Žā¤ŋ⤤ ā¤šāĨˆāĨ¤ Immich ⤕āĨ‹ ⤆ā¤Ē⤕āĨ‡ ⤏⤂ā¤ĒāĨ‚⤰āĨā¤Ŗ ⤗āĨˆā¤˛ā¤°āĨ€ ⤏⤂⤗āĨā¤°ā¤š ā¤•ā¤ž ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨āĨ‡ ⤔⤰ ⤉⤏āĨ‡ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰⤍āĨ‡ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤, ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤ŽāĨ‡ā¤‚ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤕āĨ€ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤‚āĨ¤", + "permission_onboarding_request": "Immich ⤕āĨ‹ ⤆ā¤Ē⤕āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤅⤍āĨā¤Žā¤¤ā¤ŋ ⤕āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ā¤šāĨˆāĨ¤", "person": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ", "person_age_months": "{months, plural, one {# month} other {# months}} ā¤ĒāĨā¤°ā¤žā¤¨ā¤ž", "person_age_year_months": "1 ā¤ĩ⤰āĨā¤ˇ, {months, plural, one {# month} other {# months}} ā¤ĒāĨā¤°ā¤žā¤¨ā¤ž", @@ -1489,10 +1718,13 @@ "person_birthdate": "{date} ⤕āĨ‹ ⤜⤍āĨā¤ŽāĨ‡", "person_hidden": "{name}{hidden, select, true { (⤛ā¤ŋā¤Ēā¤ž ā¤šāĨā¤†)} other {}}", "person_recognized": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤Ēā¤šā¤šā¤žā¤¨ā¤ž ā¤—ā¤¯ā¤ž", + "person_selected": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤚āĨā¤¨ā¤ž ā¤—ā¤¯ā¤ž", "photo_shared_all_users": "ā¤ā¤¸ā¤ž ā¤˛ā¤—ā¤¤ā¤ž ā¤šāĨˆ ⤕ā¤ŋ ⤆ā¤Ē⤍āĨ‡ ⤅ā¤Ē⤍āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤏⤭āĨ€ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤žā¤“⤂ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤¸ā¤žā¤ā¤ž ⤕āĨ€ā¤‚ ā¤¯ā¤ž ⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ā¤¸ā¤žā¤ā¤ž ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨ‹ā¤ˆ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆāĨ¤", "photos": "⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚", "photos_and_videos": "⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", + "photos_count": "{count, plural, one {{count, number} ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹} other {{count, number} ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹}}", "photos_from_previous_years": "ā¤Ēā¤ŋ⤛⤞āĨ‡ ā¤ĩ⤰āĨā¤ˇāĨ‹ā¤‚ ⤕āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°āĨ‡ā¤‚", + "photos_only": "⤕āĨ‡ā¤ĩ⤞ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹", "pick_a_location": "ā¤ā¤• ⤏āĨā¤Ĩā¤žā¤¨ ⤚āĨā¤¨āĨ‡ā¤‚", "pick_custom_range": "⤕⤏āĨā¤Ÿā¤Ž ⤰āĨ‡ā¤‚ā¤œ", "pick_date_range": "ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ⤏āĨ€ā¤Žā¤ž ⤚āĨā¤¨āĨ‡ā¤‚", @@ -1527,6 +1759,7 @@ "privacy": "⤗āĨ‹ā¤Ē⤍āĨ€ā¤¯ā¤¤ā¤ž", "profile": "ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤ŧā¤žā¤‡ā¤˛", "profile_drawer_app_logs": "⤞āĨ‰ā¤—āĨā¤¸", + "profile_drawer_client_server_up_to_date": "⤕āĨā¤˛ā¤žā¤‡ā¤‚ā¤Ÿ ⤔⤰ ⤏⤰āĨā¤ĩ⤰ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤ŋ⤤ ā¤šāĨˆā¤‚", "profile_drawer_github": "⤗ā¤ŋā¤Ÿā¤šā¤Ŧ", "profile_drawer_readonly_mode": "⤕āĨ‡ā¤ĩ⤞-ā¤Ē⤠⤍ ā¤ŽāĨ‹ā¤Ą ⤏⤕āĨā¤ˇā¤Ž ā¤šāĨˆāĨ¤ ā¤Ŧā¤žā¤šā¤° ⤍ā¤ŋ⤕⤞⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤅ā¤ĩā¤¤ā¤žā¤° ⤆⤇⤕⤍ ⤕āĨ‹ ā¤ĻāĨ‡ā¤° ⤤⤕ ā¤Ļā¤Ŧā¤žā¤ā¤āĨ¤", "profile_image_of_user": "{user} ⤕āĨ€ ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤛ā¤ĩā¤ŋ", @@ -1549,6 +1782,7 @@ "purchase_individual_description_2": "ā¤¸ā¤Žā¤°āĨā¤Ĩ⤕ ⤏āĨā¤Ĩā¤ŋ⤤ā¤ŋ", "purchase_individual_title": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ", "purchase_input_suggestion": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ⤉⤤āĨā¤Ēā¤žā¤Ļ ⤕āĨā¤‚ā¤œāĨ€ ā¤šāĨˆ? ⤍āĨ€ā¤šāĨ‡ ⤕āĨā¤‚ā¤œāĨ€ ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚", + "purchase_license_subtitle": "⤏āĨ‡ā¤ĩā¤ž ⤕āĨ‡ ⤍ā¤ŋ⤰⤂⤤⤰ ā¤ĩā¤ŋā¤•ā¤žā¤¸ ā¤•ā¤ž ā¤¸ā¤Žā¤°āĨā¤Ĩ⤍ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤‡ā¤Žā¤ŋ⤚ ⤖⤰āĨ€ā¤ĻāĨ‡ā¤‚", "purchase_lifetime_description": "⤜āĨ€ā¤ĩ⤍ ⤭⤰ ⤕āĨ€ ⤖⤰āĨ€ā¤Ļā¤žā¤°āĨ€", "purchase_option_title": "⤖⤰āĨ€ā¤Ļ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", "purchase_panel_info_1": "ā¤‡ā¤Žā¤ŋ⤚ ⤕āĨ‹ ā¤Ŧā¤¨ā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤Ŧā¤šāĨā¤¤ ā¤¸ā¤Žā¤¯ ⤔⤰ ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ā¤˛ā¤—ā¤¤ā¤ž ā¤šāĨˆ, ⤔⤰ ā¤šā¤Žā¤žā¤°āĨ‡ ā¤Ēā¤žā¤¸ ⤇⤏āĨ‡ ⤜ā¤ŋā¤¤ā¤¨ā¤ž ⤏⤂⤭ā¤ĩ ā¤šāĨ‹ ⤏⤕āĨ‡ ā¤‰ā¤¤ā¤¨ā¤ž ā¤…ā¤šāĨā¤›ā¤ž ā¤Ŧā¤¨ā¤žā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ĒāĨ‚⤰āĨā¤Ŗā¤•ā¤žā¤˛ā¤ŋ⤕ ā¤‡ā¤‚ā¤œāĨ€ā¤¨ā¤ŋ⤝⤰ ⤇⤏ ā¤Ē⤰ ā¤•ā¤žā¤Ž ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚āĨ¤", @@ -1581,16 +1815,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# asset} other {# assets}} ⤕āĨ‹ ā¤ā¤• ā¤¨ā¤ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤕āĨ‹ ā¤Ģā¤ŋ⤰ ⤏āĨ‡ ā¤…ā¤¸ā¤žā¤‡ā¤¨ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "reassing_hint": "⤚⤝⤍ā¤ŋ⤤ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤕ā¤ŋ⤏āĨ€ ā¤ŽāĨŒā¤œāĨ‚ā¤Ļā¤ž ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤕āĨ‹ ⤏āĨŒā¤‚ā¤ĒāĨ‡ā¤‚", "recent": "ā¤šā¤žā¤˛ ā¤šāĨ€ ā¤•ā¤ž", + "recent_albums": "ā¤šā¤žā¤˛ ⤕āĨ‡ ā¤ā¤˛āĨā¤Ŧā¤Ž", "recent_searches": "ā¤šā¤žā¤˛ ⤕āĨ€ ⤖āĨ‹ā¤œāĨ‡ā¤‚", "recently_added": "ā¤šā¤žā¤˛ ā¤šāĨ€ ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ā¤ž ā¤—ā¤¯ā¤ž", "recently_added_page_title": "ā¤šā¤žā¤˛ ā¤šāĨ€ ā¤ŽāĨ‡ā¤‚ ā¤Ąā¤žā¤˛ā¤ž ā¤—ā¤¯ā¤ž", "recently_taken": "ā¤šā¤žā¤˛ ā¤šāĨ€ ā¤ŽāĨ‡ā¤‚ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "recently_taken_page_title": "ā¤šā¤žā¤˛ ā¤šāĨ€ ā¤ŽāĨ‡ā¤‚ ⤞ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "refresh": "ā¤¤ā¤žā¤œā¤ŧā¤ž ā¤•ā¤°ā¤¨ā¤ž", "refresh_encoded_videos": "ā¤ā¤¨āĨā¤•āĨ‹ā¤ĄāĨ‡ā¤Ą ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕⤰āĨ‡ā¤‚", "refresh_faces": "⤚āĨ‡ā¤šā¤°āĨ‡ ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕⤰āĨ‡ā¤‚", "refresh_metadata": "ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕⤰āĨ‡ā¤‚", "refresh_thumbnails": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕⤰āĨ‡ā¤‚", "refreshed": "ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕ā¤ŋā¤¯ā¤ž", + "refreshes_every_file": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ⤕āĨ‹ ā¤¤ā¤žā¤œā¤ŧā¤ž ā¤•ā¤°ā¤¤ā¤ž ā¤šāĨˆ", "refreshing_encoded_video": "ā¤¤ā¤žā¤œā¤ŧā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤ā¤¨āĨā¤•āĨ‹ā¤ĄāĨ‡ā¤Ą ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "refreshing_faces": "ā¤¤ā¤žā¤œā¤ŧā¤ž ⤚āĨ‡ā¤šā¤°āĨ‡", "refreshing_metadata": "ā¤¤ā¤žā¤œā¤ŧā¤ž ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž", @@ -1603,6 +1840,7 @@ "remove_assets_shared_link_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ ⤇⤏ ā¤ļāĨ‡ā¤¯ā¤°āĨā¤Ą ⤞ā¤ŋ⤂⤕ ⤏āĨ‡ {count, plural, one {# asset} other {# assets}} ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "remove_assets_title": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤ ā¤šā¤Ÿā¤žā¤ā¤?", "remove_custom_date_range": "⤕⤏āĨā¤Ÿā¤Ž ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ⤏āĨ€ā¤Žā¤ž ā¤šā¤Ÿā¤žā¤ā¤", + "remove_deleted_assets": "⤑ā¤Ģā¤ŧā¤˛ā¤žā¤‡ā¤¨ ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤šā¤Ÿā¤žā¤ā¤", "remove_from_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤", "remove_from_album_action_prompt": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ {count} ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "remove_from_favorites": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ⤏āĨ‡ ⤍ā¤ŋā¤•ā¤žā¤˛āĨ‡ā¤‚", @@ -1613,22 +1851,29 @@ "remove_memory": "ā¤ŽāĨ‡ā¤ŽāĨ‹ā¤°āĨ€ ā¤šā¤Ÿā¤žā¤ā¤", "remove_photo_from_memory": "⤇⤏ ā¤ŽāĨ‡ā¤ŽāĨ‹ā¤°āĨ€ ⤏āĨ‡ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤šā¤Ÿā¤žā¤ā¤", "remove_tag": "⤟āĨˆā¤— ā¤šā¤Ÿā¤žā¤ā¤", + "remove_url": "URL ā¤šā¤Ÿā¤žā¤ā¤", "remove_user": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤ā¤", "removed_api_key": "ā¤šā¤Ÿā¤žā¤ˆ ā¤—ā¤ˆ API ⤕āĨā¤‚ā¤œāĨ€: {name}", "removed_from_archive": "⤏⤂⤗āĨā¤°ā¤š ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "removed_from_favorites": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "removed_from_favorites_count": "ā¤Ē⤏⤂ā¤ĻāĨ€ā¤Ļā¤ž ⤏āĨ‡ {count, plural, other {Removed #}}", "removed_memory": "ā¤šā¤Ÿā¤žā¤ˆ ā¤—ā¤ˆ ā¤ŽāĨ‡ā¤ŽāĨ‹ā¤°āĨ€", + "removed_photo_from_memory": "ā¤ŽāĨ‡ā¤ŽāĨ‹ā¤°āĨ€ ⤏āĨ‡ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤šā¤Ÿā¤ž ā¤ĻāĨ€ ā¤—ā¤ˆ", "removed_tagged_assets": "{count, plural, one {# asset} other {# assets}} ⤏āĨ‡ ⤟āĨˆā¤— ā¤šā¤Ÿā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "rename": "ā¤¨ā¤žā¤Ž ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "repair": "ā¤Žā¤°ā¤ŽāĨā¤Žā¤¤", + "repair_no_results_message": "⤟āĨā¤°āĨˆā¤• ⤍ ⤕āĨ€ ā¤—ā¤ˆ ⤔⤰ ⤗āĨā¤Ž ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‡ā¤‚ ā¤¯ā¤šā¤žā¤‚ ā¤Ļā¤ŋā¤–ā¤žā¤ˆ ā¤ĻāĨ‡ā¤‚⤗āĨ€", + "replace_with_upload": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", "repository": "⤕āĨ‹ā¤ˇ", "require_password": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤕āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ā¤šāĨˆ", + "require_user_to_change_password_on_first_login": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤Ēā¤šā¤˛āĨ‡ ⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤Ē⤰ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤Ŧā¤Ļ⤞⤍āĨ‡ ⤕āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ā¤šāĨˆ", "rescan": "ā¤ĒāĨā¤¨: ⤏āĨā¤•āĨˆā¤¨", "reset": "⤰āĨ€ā¤¸āĨ‡ā¤Ÿ", "reset_password": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ", "reset_people_visibility": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ€ ā¤ĻāĨƒā¤ļāĨā¤¯ā¤¤ā¤ž ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "reset_pin_code": "ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "reset_pin_code_description": "⤅⤗⤰ ⤆ā¤Ē ⤅ā¤Ēā¤¨ā¤ž PIN ⤕āĨ‹ā¤Ą ⤭āĨ‚⤞ ā¤—ā¤ ā¤šāĨˆā¤‚, ⤤āĨ‹ ⤆ā¤Ē ⤇⤏āĨ‡ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏⤰āĨā¤ĩ⤰ ā¤ā¤Ąā¤Žā¤ŋ⤍ā¤ŋ⤏āĨā¤ŸāĨā¤°āĨ‡ā¤Ÿā¤° ⤏āĨ‡ ⤏⤂ā¤Ē⤰āĨā¤• ⤕⤰ ⤏⤕⤤āĨ‡ ā¤šāĨˆā¤‚", + "reset_pin_code_success": "ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "reset_pin_code_with_password": "⤆ā¤Ē ā¤šā¤ŽāĨ‡ā¤ļā¤ž ⤅ā¤Ē⤍āĨ‡ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤏āĨ‡ ⤅ā¤Ēā¤¨ā¤ž ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰ ⤏⤕⤤āĨ‡ ā¤šāĨˆā¤‚", "reset_sqlite": "SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "reset_sqlite_confirmation": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤ĩā¤žā¤•ā¤ˆ SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤕āĨ‹ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ā¤ĄāĨ‡ā¤Ÿā¤ž ⤕āĨ‹ ā¤Ģā¤ŋ⤰ ⤏āĨ‡ ⤏ā¤ŋ⤂⤕ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤆ā¤Ē⤕āĨ‹ ⤞āĨ‰ā¤— ā¤†ā¤‰ā¤Ÿ ⤕⤰⤕āĨ‡ ā¤Ģā¤ŋ⤰ ⤏āĨ‡ ⤞āĨ‰ā¤— ⤇⤍ ā¤•ā¤°ā¤¨ā¤ž ā¤šāĨ‹ā¤—ā¤ž", @@ -1639,10 +1884,12 @@ "resolved_all_duplicates": "⤏⤭āĨ€ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤•ā¤ž ā¤¸ā¤Žā¤žā¤§ā¤žā¤¨ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "restore": "ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤¨ā¤ž", "restore_all": "⤏⤭āĨ€ ā¤Ŧā¤šā¤žā¤˛ ⤕⤰āĨ‹", + "restore_trash_action_prompt": "{count} ⤟āĨā¤°āĨˆā¤ļ ⤏āĨ‡ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "restore_user": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "restored_asset": "ā¤ĒāĨā¤¨ā¤°āĨā¤¸āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ", "resume": "ā¤Ģā¤ŋ⤰ ā¤ļāĨā¤°āĨ‚ ā¤•ā¤°ā¤¨ā¤ž", "resume_paused_jobs": "⤰ā¤ŋ⤜āĨā¤¯āĨ‚ā¤ŽāĨ‡ {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "ā¤ĒāĨā¤¨ā¤ƒ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤•ā¤ž ā¤ĒāĨā¤°ā¤¯ā¤žā¤¸ ⤕⤰āĨ‡ā¤‚", "review_duplicates": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤕āĨ€ ā¤¸ā¤ŽāĨ€ā¤•āĨā¤ˇā¤ž ⤕⤰āĨ‡ā¤‚", "review_large_files": "ā¤Ŧā¤Ąā¤ŧāĨ€ ā¤Ģā¤ŧā¤žā¤‡ā¤˛āĨ‹ā¤‚ ⤕āĨ€ ā¤¸ā¤ŽāĨ€ā¤•āĨā¤ˇā¤ž ⤕⤰āĨ‡ā¤‚", "role": "⤭āĨ‚ā¤Žā¤ŋā¤•ā¤ž", @@ -1650,6 +1897,7 @@ "role_viewer": "ā¤Ļ⤰āĨā¤ļ⤕", "running": "⤚⤞ ā¤°ā¤šā¤ž ā¤šāĨˆ", "save": "ā¤Ŧā¤šā¤žā¤¨ā¤ž", + "save_to_gallery": "⤗āĨˆā¤˛ā¤°āĨ€ ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤šāĨ‡ā¤œāĨ‡ā¤‚", "saved": "ā¤¸ā¤šāĨ‡ā¤œā¤ž ā¤—ā¤¯ā¤ž", "saved_api_key": "ā¤¸ā¤šāĨ‡ā¤œāĨ€ ā¤—ā¤ˆ ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨā¤‚ā¤œāĨ€", "saved_profile": "ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤ŧā¤žā¤‡ā¤˛ ā¤¸ā¤šāĨ‡ā¤œāĨ€ ā¤—ā¤ˆ", @@ -1659,6 +1907,7 @@ "scan": "⤏āĨā¤•āĨˆā¤¨", "scan_all_libraries": "⤏⤭āĨ€ ā¤ĒāĨā¤¸āĨā¤¤ā¤•ā¤žā¤˛ā¤¯āĨ‹ā¤‚ ⤕āĨ‹ ⤏āĨā¤•āĨˆā¤¨ ⤕⤰āĨ‡ā¤‚", "scan_library": "⤏āĨā¤•āĨˆā¤¨", + "scan_settings": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ⤏āĨā¤•āĨˆā¤¨ ⤕⤰āĨ‡ā¤‚", "scanning": "⤏āĨā¤•āĨˆā¤¨ ā¤šāĨ‹ ā¤°ā¤šā¤ž ā¤šāĨˆ", "scanning_for_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏āĨā¤•āĨˆā¤¨ ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤ž ā¤°ā¤šā¤ž ā¤šāĨˆ..āĨ¤", "search": "⤖āĨ‹ā¤œ", @@ -1687,6 +1936,7 @@ "search_filter_location_title": "⤏āĨā¤Ĩā¤žā¤¨ ⤚āĨā¤¨āĨ‡ā¤‚", "search_filter_media_type": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "search_filter_media_type_title": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤•ā¤žā¤° ⤚āĨā¤¨āĨ‡ā¤‚", + "search_filter_ocr": "OCR ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ⤖āĨ‹ā¤œāĨ‡ā¤‚", "search_filter_people_title": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "search_filter_star_rating": "⤏āĨā¤Ÿā¤žā¤° ⤰āĨ‡ā¤Ÿā¤ŋ⤂⤗", "search_filter_tags_title": "⤟āĨˆā¤— ⤚āĨā¤¨āĨ‡ā¤‚", @@ -1697,13 +1947,25 @@ "search_no_people_named": "\"{name}\" ā¤¨ā¤žā¤Ž ⤕āĨ‡ ⤕āĨ‹ā¤ˆ ⤞āĨ‹ā¤— ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆā¤‚", "search_no_result": "⤕āĨ‹ā¤ˆ ⤰ā¤ŋ⤜ā¤ŧ⤞āĨā¤Ÿ ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋā¤˛ā¤ž, ⤕āĨ‹ā¤ˆ ā¤ĻāĨ‚ā¤¸ā¤°ā¤ž ⤏⤰āĨā¤š ⤟⤰āĨā¤Ž ā¤¯ā¤ž ⤕āĨ‰ā¤ŽāĨā¤Ŧā¤ŋ⤍āĨ‡ā¤ļ⤍ ⤟āĨā¤°ā¤žā¤ˆ ⤕⤰āĨ‡ā¤‚", "search_options": "⤖āĨ‹ā¤œ ā¤ĩā¤ŋ⤕⤞āĨā¤Ē", + "search_page_categories": "ā¤ļāĨā¤°āĨ‡ā¤Ŗā¤ŋā¤¯ā¤žā¤", + "search_page_motion_photos": "ā¤ŽāĨ‹ā¤ļ⤍ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹", + "search_page_no_objects": "⤕āĨ‹ā¤ˆ ⤑ā¤ŦāĨā¤œāĨ‡ā¤•āĨā¤Ÿ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", + "search_page_no_places": "⤕āĨ‹ā¤ˆ ā¤œā¤—ā¤š ⤕āĨ€ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", + "search_page_screenshots": "⤏āĨā¤•āĨā¤°āĨ€ā¤¨ā¤ļāĨ‰ā¤Ÿ", "search_page_search_photos_videos": "⤅ā¤Ē⤍āĨ€ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤖āĨ‹ā¤œāĨ‡ā¤‚", + "search_page_selfies": "⤏āĨ‡ā¤˛āĨā¤Ģā¤ŧāĨ€ā¤œā¤ŧ", + "search_page_things": "⤚āĨ€ā¤œā¤ŧāĨ‡ā¤‚", "search_page_view_all_button": "⤏⤭āĨ€ ⤕āĨ‹ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", + "search_page_your_activity": "⤆ā¤Ē⤕āĨ€ ⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ", + "search_page_your_map": "⤆ā¤Ēā¤•ā¤ž ⤍⤕āĨā¤ļā¤ž", "search_people": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ⤖āĨ‹ā¤œāĨ‡ā¤‚", "search_places": "⤏āĨā¤Ĩā¤žā¤¨ ⤖āĨ‹ā¤œāĨ‡ā¤‚", "search_rating": "⤰āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤕āĨ‡ ā¤šā¤ŋā¤¸ā¤žā¤Ŧ ⤏āĨ‡ ⤖āĨ‹ā¤œāĨ‡ā¤‚..āĨ¤", + "search_result_page_new_search_hint": "⤍⤈ ⤖āĨ‹ā¤œ", "search_settings": "⤖āĨ‹ā¤œ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸", "search_state": "⤏āĨā¤Ĩā¤ŋ⤤ā¤ŋ ⤖āĨ‹ā¤œāĨ‡ā¤‚..āĨ¤", + "search_suggestion_list_smart_search_hint_1": "⤏āĨā¤Žā¤žā¤°āĨā¤Ÿ ⤏⤰āĨā¤š ā¤Ąā¤ŋā¤Ģā¤ŧāĨ‰ā¤˛āĨā¤Ÿ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤žā¤˛āĨ‚ ā¤°ā¤šā¤¤ā¤ž ā¤šāĨˆ, ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ⤖āĨ‹ā¤œā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤏ā¤ŋā¤‚ā¤ŸāĨˆā¤•āĨā¤¸ ā¤•ā¤ž ⤇⤏āĨā¤¤āĨ‡ā¤Žā¤žā¤˛ ⤕⤰āĨ‡ā¤‚ ", + "search_suggestion_list_smart_search_hint_2": "m:⤆ā¤Ēā¤•ā¤ž-⤖āĨ‹ā¤œ-ā¤ļā¤ŦāĨā¤Ļ", "search_tags": "⤟āĨˆā¤— ⤖āĨ‹ā¤œāĨ‡ā¤‚..āĨ¤", "search_timezone": "ā¤¸ā¤Žā¤¯ā¤•āĨā¤ˇāĨ‡ā¤¤āĨā¤° ⤖āĨ‹ā¤œāĨ‡ā¤‚..āĨ¤", "search_type": "ā¤¤ā¤˛ā¤žā¤ļ ⤕āĨ€ ā¤ĩā¤ŋ⤧ā¤ŋ", @@ -1717,6 +1979,7 @@ "select_albums": "⤅⤞ā¤ŦāĨā¤Žā¤¸ ⤚āĨā¤¨āĨ‡ā¤‚", "select_all": "⤏ā¤Ŧā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "select_all_duplicates": "⤏⤭āĨ€ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", + "select_all_in": "{group} ā¤ŽāĨ‡ā¤‚ ⤏⤭āĨ€ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "select_avatar_color": "⤅ā¤ĩā¤¤ā¤žā¤° ⤰⤂⤗ ⤚āĨā¤¨āĨ‡ā¤‚", "select_count": "{count, plural, one {# ⤚āĨā¤¨āĨ‡ā¤‚} other {# ⤚āĨā¤¨āĨ‡ā¤‚}}", "select_cutoff_date": "ā¤•ā¤Ÿā¤‘ā¤Ģā¤ŧ ⤤ā¤ŋā¤Ĩā¤ŋ ⤚āĨā¤¨āĨ‡ā¤‚", @@ -1724,12 +1987,14 @@ "select_featured_photo": "⤚āĨā¤¨ā¤ŋ⤂ā¤Ļā¤ž ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤚āĨā¤¨āĨ‡ā¤‚", "select_from_computer": "⤕⤂ā¤ĒāĨā¤¯āĨ‚ā¤Ÿā¤° ⤏āĨ‡ ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "select_keep_all": "⤏⤭āĨ€ ⤰⤖āĨ‡ā¤‚ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", + "select_library_owner": "ā¤˛ā¤žā¤‡ā¤ŦāĨā¤°āĨ‡ā¤°āĨ€ ⤏āĨā¤ĩā¤žā¤ŽāĨ€ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "select_new_face": "ā¤¨ā¤¯ā¤ž ⤚āĨ‡ā¤šā¤°ā¤ž ⤚āĨā¤¨āĨ‡ā¤‚", "select_people": "⤞āĨ‹ā¤— ⤚āĨā¤¨āĨ‡ā¤‚", "select_person": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ⤚āĨā¤¨āĨ‡ā¤‚", "select_person_to_tag": "⤟āĨˆā¤— ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕ā¤ŋ⤏āĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", "select_photos": "ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤚āĨā¤¨āĨ‡ā¤‚", "select_trash_all": "⤟āĨā¤°āĨˆā¤ļ ⤑⤞ ā¤•ā¤ž ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", + "select_user_for_sharing_page_err_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤¨āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤ĩā¤ŋā¤Ģ⤞", "selected": "⤚⤝⤍ā¤ŋ⤤", "selected_count": "{count, plural, other {# ⤚⤝⤍ā¤ŋ⤤}}", "selected_gps_coordinates": "⤚⤝⤍ā¤ŋ⤤ GPS ⤍ā¤ŋ⤰āĨā¤ĻāĨ‡ā¤ļā¤žā¤‚ā¤•", @@ -1759,20 +2024,33 @@ "setting_image_viewer_original_title": "ā¤ŽāĨ‚⤞ ⤛ā¤ĩā¤ŋ ⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "setting_image_viewer_preview_subtitle": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤Ž-⤰ā¤ŋ⤜ā¤ŧāĨ‰ā¤˛āĨā¤¯āĨ‚ā¤ļ⤍ ā¤‡ā¤ŽāĨ‡ā¤œ ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤šā¤žā¤˛āĨ‚ ⤕⤰āĨ‡ā¤‚āĨ¤ ⤓⤰ā¤ŋ⤜ā¤ŋ⤍⤞ ā¤‡ā¤ŽāĨ‡ā¤œ ⤕āĨ‹ ⤏āĨ€ā¤§āĨ‡ ⤞āĨ‹ā¤Ą ⤕⤰⤍āĨ‡ ā¤¯ā¤ž ⤏ā¤ŋ⤰āĨā¤Ģā¤ŧ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤇⤏āĨā¤¤āĨ‡ā¤Žā¤žā¤˛ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ŧ⤂ā¤Ļ ⤕⤰āĨ‡ā¤‚āĨ¤", "setting_image_viewer_preview_title": "ā¤ĒāĨ‚⤰āĨā¤ĩā¤žā¤ĩ⤞āĨ‹ā¤•⤍ ⤛ā¤ĩā¤ŋ ⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", + "setting_image_viewer_title": "ā¤‡ā¤ŽāĨ‡ā¤œā¤ŋ⤏", "setting_languages_apply": "⤆ā¤ĩāĨ‡ā¤Ļ⤍ ā¤•ā¤°ā¤¨ā¤ž", "setting_languages_subtitle": "⤐ā¤Ē ⤕āĨ€ ā¤­ā¤žā¤ˇā¤ž ā¤Ŧā¤Ļ⤞āĨ‡ā¤‚", + "setting_notifications_notify_failures_grace_period": "ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ĢāĨ‡ā¤˛ā¤ŋ⤝⤰ ⤕āĨ€ ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤ĻāĨ‡ā¤‚: {duration}", + "setting_notifications_notify_hours": "{count} ā¤˜ā¤‚ā¤ŸāĨ‡", + "setting_notifications_notify_immediately": "⤤āĨā¤°ā¤‚⤤", "setting_notifications_notify_minutes": "{count} ā¤Žā¤ŋ⤍⤟", + "setting_notifications_notify_never": "⤕⤭āĨ€ ā¤¨ā¤šāĨ€ā¤‚", "setting_notifications_notify_seconds": "{count} ⤏āĨ‡ā¤•ā¤‚ā¤Ą", + "setting_notifications_single_progress_subtitle": "ā¤šā¤° ā¤ā¤¸āĨ‡ā¤Ÿ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤°āĨ‹ā¤—āĨā¤°āĨ‡ā¤¸ ⤕āĨ€ ā¤ĒāĨ‚⤰āĨ€ ā¤œā¤žā¤¨ā¤•ā¤žā¤°āĨ€", + "setting_notifications_single_progress_title": "ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤Ąā¤ŋ⤟āĨ‡ā¤˛ ā¤ĒāĨā¤°āĨ‹ā¤—āĨā¤°āĨ‡ā¤¸ ā¤Ļā¤ŋā¤–ā¤žā¤ā¤", "setting_notifications_subtitle": "⤅ā¤Ē⤍āĨ€ ⤍āĨ‹ā¤Ÿā¤ŋā¤Ģā¤ŧā¤ŋ⤕āĨ‡ā¤ļ⤍ ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋā¤•ā¤¤ā¤žā¤ā¤ ā¤¸ā¤Žā¤žā¤¯āĨ‹ā¤œā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "setting_notifications_total_progress_subtitle": "⤓ā¤ĩ⤰⤑⤞ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤°āĨ‹ā¤—āĨā¤°āĨ‡ā¤¸ (ā¤šāĨ‹ ā¤—ā¤¯ā¤ž/⤟āĨ‹ā¤Ÿā¤˛ ā¤ā¤¸āĨ‡ā¤ŸāĨā¤¸)", + "setting_notifications_total_progress_title": "ā¤ŦāĨˆā¤•⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨˆā¤•⤅ā¤Ē ⤕āĨ€ ⤕āĨā¤˛ ā¤ĒāĨā¤°āĨ‹ā¤—āĨā¤°āĨ‡ā¤¸ ā¤Ļā¤ŋā¤–ā¤žā¤ā¤", "setting_video_viewer_auto_play_subtitle": "ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤖āĨā¤˛ā¤¨āĨ‡ ā¤Ē⤰ ⤅ā¤Ē⤍āĨ‡ ⤆ā¤Ē ā¤šā¤˛ā¤¨ā¤ž ā¤ļāĨā¤°āĨ‚ ā¤šāĨ‹ ā¤œā¤žā¤¤ā¤ž ā¤šāĨˆ", "setting_video_viewer_auto_play_title": "ā¤‘ā¤ŸāĨ‹ ā¤ĒāĨā¤˛āĨ‡ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "setting_video_viewer_looping_title": "ā¤Ēā¤žā¤ļ⤍", "setting_video_viewer_original_video_subtitle": "⤏⤰āĨā¤ĩ⤰ ⤏āĨ‡ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤏āĨā¤ŸāĨā¤°āĨ€ā¤Ž ⤕⤰⤤āĨ‡ ā¤¸ā¤Žā¤¯, ⤟āĨā¤°ā¤žā¤‚⤏⤕āĨ‹ā¤Ą ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤šāĨ‹ā¤¨āĨ‡ ā¤Ē⤰ ⤭āĨ€ ⤓⤰ā¤ŋ⤜ā¤ŋ⤍⤞ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤šā¤˛ā¤žā¤ā¤‚āĨ¤ ⤇⤏⤏āĨ‡ ā¤Ŧā¤Ģ⤰ā¤ŋ⤂⤗ ā¤šāĨ‹ ⤏⤕⤤āĨ€ ā¤šāĨˆāĨ¤ ⤇⤏ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤕āĨ‡ ā¤Ŧā¤žā¤ĩ⤜āĨ‚ā¤Ļ, ⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤓⤰ā¤ŋ⤜ā¤ŋ⤍⤞ ⤕āĨā¤ĩā¤žā¤˛ā¤ŋ⤟āĨ€ ā¤ŽāĨ‡ā¤‚ ā¤šā¤˛ā¤žā¤ ā¤œā¤žā¤¤āĨ‡ ā¤šāĨˆā¤‚āĨ¤", "setting_video_viewer_original_video_title": "ā¤ŽāĨ‚⤞ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤕āĨ‹ ā¤Ŧ⤞ā¤ĒāĨ‚⤰āĨā¤ĩ⤕", "settings": "ā¤¸ā¤Žā¤žā¤¯āĨ‹ā¤œā¤¨", + "settings_require_restart": "⤇⤏ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤕āĨ‹ ā¤˛ā¤žā¤—āĨ‚ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⤕āĨƒā¤Ēā¤¯ā¤ž Immich ⤕āĨ‹ ⤰āĨ€ā¤¸āĨā¤Ÿā¤žā¤°āĨā¤Ÿ ⤕⤰āĨ‡ā¤‚", "settings_saved": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤¸ ⤕āĨ‹ ā¤¸ā¤šāĨ‡ā¤œā¤ž ā¤—ā¤¯ā¤ž", "setup_pin_code": "ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "share": "ā¤ļāĨ‡ā¤¯ā¤° ā¤•ā¤°ā¤¨ā¤ž", + "share_action_prompt": "ā¤¸ā¤žā¤ā¤ž {count} ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤", + "share_add_photos": "ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤Ąā¤žā¤˛āĨ‡ā¤‚", + "share_assets_selected": "{count} ⤚⤝⤍ā¤ŋ⤤", "share_dialog_preparing": "⤤āĨˆā¤¯ā¤žā¤°āĨ€..āĨ¤", "share_link": "⤞ā¤ŋ⤂⤕ ā¤ļāĨ‡ā¤¯ā¤° ⤕⤰āĨ‡ā¤‚", "shared": "ā¤¸ā¤žā¤ā¤ž", @@ -1780,6 +2058,8 @@ "shared_album_activity_remove_content": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤇⤏ ⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ ⤕āĨ‹ ā¤šā¤Ÿā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "shared_album_activity_remove_title": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ ā¤šā¤Ÿā¤žā¤ā¤‚", "shared_album_section_people_action_error": "ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¨āĨ‡/⤛āĨ‹ā¤Ąā¤ŧ⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ⤤āĨā¤°āĨā¤Ÿā¤ŋ", + "shared_album_section_people_action_leave": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤", + "shared_album_section_people_action_remove_user": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤕āĨ‹ ā¤ā¤˛āĨā¤Ŧā¤Ž ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤", "shared_album_section_people_title": "⤞āĨ‹ā¤—", "shared_by": "ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ā¤¸ā¤žā¤ā¤ž", "shared_by_user": "{user} ā¤ĻāĨā¤ĩā¤žā¤°ā¤ž ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", @@ -1788,6 +2068,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "shared_link_app_bar_title": "ā¤¸ā¤žā¤ā¤ž ⤕ā¤ŋā¤ ā¤—ā¤ ⤞ā¤ŋ⤂⤕", "shared_link_clipboard_copied_massage": "⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ą ā¤Ē⤰ ⤕āĨ‰ā¤ĒāĨ€ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "shared_link_clipboard_text": "⤞ā¤ŋ⤂⤕: {link}\nā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą: {password}", "shared_link_create_error": "ā¤ļāĨ‡ā¤¯ā¤°āĨā¤Ą ⤞ā¤ŋ⤂⤕ ā¤Ŧā¤¨ā¤žā¤¤āĨ‡ ā¤¸ā¤Žā¤¯ ā¤ā¤°ā¤° ā¤†ā¤¯ā¤ž", "shared_link_custom_url_description": "⤕⤏āĨā¤Ÿā¤Ž URL ⤏āĨ‡ ⤇⤏ ā¤ļāĨ‡ā¤¯ā¤°āĨā¤Ą ⤞ā¤ŋ⤂⤕ ⤕āĨ‹ ā¤ā¤•āĨā¤¸āĨ‡ā¤¸ ⤕⤰āĨ‡ā¤‚", "shared_link_edit_description_hint": "ā¤ļāĨ‡ā¤¯ā¤° ā¤ĩā¤ŋā¤ĩ⤰⤪ ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚", @@ -1823,6 +2104,12 @@ "shared_with_partner": "{partner} ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤ļāĨ‡ā¤¯ā¤° ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "sharing": "ā¤ļāĨ‡ā¤¯ā¤°ā¤ŋ⤂⤗", "sharing_enter_password": "⤕āĨƒā¤Ēā¤¯ā¤ž ⤇⤏ ā¤ĒāĨƒā¤ˇāĨā¤  ⤕āĨ‹ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚āĨ¤", + "sharing_page_album": "ā¤¸ā¤žā¤ā¤ž ā¤ā¤˛āĨā¤Ŧā¤Ž", + "sharing_page_description": "⤅ā¤Ē⤍āĨ‡ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ā¤ŽāĨ‡ā¤‚ ⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤ļāĨ‡ā¤¯ā¤° ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ļāĨ‡ā¤¯ā¤°āĨā¤Ą ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ŧā¤¨ā¤žā¤ā¤‚āĨ¤", + "sharing_page_empty_list": "ā¤–ā¤žā¤˛āĨ€ ⤏āĨ‚ā¤šāĨ€", + "sharing_sidebar_description": "ā¤¸ā¤žā¤‡ā¤Ąā¤Ŧā¤žā¤° ā¤ŽāĨ‡ā¤‚ ā¤ļāĨ‡ā¤¯ā¤°ā¤ŋ⤂⤗ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ⤞ā¤ŋ⤂⤕ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "sharing_silver_appbar_create_shared_album": "ā¤¨ā¤¯ā¤ž ā¤¸ā¤žā¤ā¤ž ā¤ā¤˛āĨā¤Ŧā¤Ž", + "sharing_silver_appbar_share_partner": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤° ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤ļāĨ‡ā¤¯ā¤° ⤕⤰āĨ‡ā¤‚", "shift_to_permanent_delete": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤕āĨ‹ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤¨āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ⇧ ā¤Ļā¤Ŧā¤žā¤ā¤", "show_album_options": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ĩā¤ŋ⤕⤞āĨā¤Ē ā¤Ļā¤ŋā¤–ā¤žā¤ā¤", "show_albums": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤Ļā¤ŋā¤–ā¤žā¤ā¤", @@ -1864,6 +2151,7 @@ "sort_created": "ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤•", "sort_items": "ā¤Žā¤ĻāĨ‹ā¤‚ ⤕āĨ€ ⤏⤂⤖āĨā¤¯ā¤ž", "sort_modified": "ā¤ĄāĨ‡ā¤Ÿā¤ž ⤏⤂ā¤ļāĨ‹ā¤§ā¤ŋ⤤", + "sort_newest": "⤍ā¤ĩāĨ€ā¤¨ā¤¤ā¤Ž ā¤ĢāĨ‹ā¤ŸāĨ‹", "sort_oldest": "⤏ā¤Ŧ⤏āĨ‡ ā¤ĒāĨā¤°ā¤žā¤¨āĨ€ ⤤⤏āĨā¤ĩāĨ€ā¤°", "sort_people_by_similarity": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ā¤¸ā¤Žā¤žā¤¨ā¤¤ā¤ž ⤕āĨ‡ ā¤†ā¤§ā¤žā¤° ā¤Ē⤰ ā¤›ā¤žā¤ā¤ŸāĨ‡ā¤‚", "sort_recent": "⤏ā¤Ŧ⤏āĨ‡ ā¤¤ā¤žā¤œā¤ŧā¤ž ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹", @@ -1872,6 +2160,7 @@ "stack": "ā¤ĸāĨ‡ā¤°", "stack_action_prompt": "{count} ⤏āĨā¤ŸāĨˆā¤•āĨā¤Ą", "stack_duplicates": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤏āĨā¤ŸāĨˆā¤• ⤕⤰āĨ‡ā¤‚", + "stack_select_one_photo": "⤏āĨā¤ŸāĨˆā¤• ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ā¤ŽāĨā¤–āĨā¤¯ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤚āĨā¤¨āĨ‡ā¤‚", "stack_selected_photos": "⤚⤝⤍ā¤ŋ⤤ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤕āĨ‹ ā¤ĸāĨ‡ā¤° ⤕⤰āĨ‡ā¤‚", "stacked_assets_count": "⤏āĨā¤ŸāĨˆā¤•āĨā¤Ą {count, plural, one {# asset} other {# assets}}", "stacktrace": "⤏āĨā¤ŸāĨˆā¤• ⤟āĨā¤°āĨ‡ā¤¸", @@ -1900,6 +2189,7 @@ "swap_merge_direction": "ā¤Žā¤°āĨā¤œ ā¤Ļā¤ŋā¤ļā¤ž ⤏āĨā¤ĩāĨˆā¤Ē ⤕⤰āĨ‡ā¤‚", "sync": "ā¤¸ā¤žā¤Ĩ-ā¤¸ā¤žā¤Ĩ ā¤•ā¤°ā¤¨ā¤ž", "sync_albums": "ā¤ā¤˛āĨā¤Ŧā¤ŽāĨā¤¸ ⤏ā¤ŋ⤂⤕ ⤕⤰āĨ‡ā¤‚", + "sync_albums_manual_subtitle": "⤚āĨā¤¨āĨ‡ ā¤šāĨā¤ ā¤ŦāĨˆā¤•⤅ā¤Ē ā¤ā¤˛āĨā¤Ŧā¤ŽāĨā¤¸ ā¤ŽāĨ‡ā¤‚ ⤏⤭āĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕āĨ€ ā¤—ā¤ˆ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ⤔⤰ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤏ā¤ŋ⤂⤕ ⤕⤰āĨ‡ā¤‚", "sync_local": "⤏āĨā¤Ĩā¤žā¤¨āĨ€ā¤¯ ⤏ā¤ŋ⤂⤕ ⤕⤰āĨ‡ā¤‚", "sync_remote": "⤏ā¤ŋ⤂⤕ ⤰ā¤ŋā¤ŽāĨ‹ā¤Ÿ", "sync_status": "⤏ā¤ŋ⤂⤕ ⤏āĨā¤Ĩā¤ŋ⤤ā¤ŋ", @@ -1909,6 +2199,7 @@ "tag_assets": "⤟āĨˆā¤— ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋā¤¯ā¤žā¤", "tag_created": "ā¤Ŧā¤¨ā¤žā¤¯ā¤ž ā¤—ā¤¯ā¤ž ⤟āĨˆā¤—: {tag}", "tag_feature_description": "⤞āĨ‰ā¤œā¤ŋ⤕⤞ ⤟āĨˆā¤— ⤟āĨ‰ā¤Ēā¤ŋ⤕ ⤕āĨ‡ ā¤šā¤ŋā¤¸ā¤žā¤Ŧ ⤏āĨ‡ ⤗āĨā¤°āĨā¤Ē ⤕ā¤ŋā¤ ā¤—ā¤ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤ŦāĨā¤°ā¤žā¤‰ā¤œā¤ŧ ā¤•ā¤°ā¤¨ā¤ž", + "tag_not_found_question": "⤟āĨˆā¤— ā¤¨ā¤šāĨ€ā¤‚ ā¤Žā¤ŋ⤞ ā¤°ā¤šā¤ž ā¤šāĨˆ? ā¤¨ā¤¯ā¤ž ⤟āĨˆā¤— ā¤Ŧā¤¨ā¤žā¤ā¤‚.", "tag_people": "⤞āĨ‹ā¤—āĨ‹ā¤‚ ⤕āĨ‹ ⤟āĨˆā¤— ⤕⤰āĨ‡ā¤‚", "tag_updated": "⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž ⤟āĨˆā¤—: {tag}", "tagged_assets": "⤟āĨˆā¤— ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž {count, plural, one {# asset} other {# assets}}", @@ -1923,10 +2214,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ā¤ĒāĨā¤°ā¤¤ā¤ŋ ā¤Ē⤂⤕āĨā¤¤ā¤ŋ ā¤ā¤¸āĨ‡ā¤Ÿ ⤕āĨ€ ⤏⤂⤖āĨā¤¯ā¤ž ({count})", "theme_setting_colorful_interface_subtitle": "ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤰⤂⤗ ⤕āĨ‹ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­āĨ‚ā¤Žā¤ŋ ā¤¸ā¤¤ā¤šāĨ‹ā¤‚ ā¤Ē⤰ ā¤˛ā¤žā¤—āĨ‚ ⤕⤰āĨ‡ā¤‚āĨ¤", "theme_setting_colorful_interface_title": "⤰⤂⤗āĨ€ā¤¨ ā¤‡ā¤‚ā¤Ÿā¤°ā¤Ģā¤ŧāĨ‡ā¤¸", + "theme_setting_image_viewer_quality_subtitle": "ā¤Ąā¤ŋ⤟āĨ‡ā¤˛ ā¤‡ā¤ŽāĨ‡ā¤œ ā¤ĩāĨā¤¯āĨ‚⤅⤰ ⤕āĨ€ ⤕āĨā¤ĩā¤žā¤˛ā¤ŋ⤟āĨ€ ā¤ā¤Ąā¤œā¤¸āĨā¤Ÿ ⤕⤰āĨ‡ā¤‚", + "theme_setting_image_viewer_quality_title": "⤛ā¤ĩā¤ŋ ā¤Ļ⤰āĨā¤ļ⤕ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž", "theme_setting_primary_color_subtitle": "ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤕āĨā¤°ā¤ŋā¤¯ā¤žā¤“ā¤‚ ⤔⤰ ā¤‰ā¤šāĨā¤šā¤žā¤°ā¤ŖāĨ‹ā¤‚ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ā¤• ⤰⤂⤗ ⤚āĨā¤¨āĨ‡ā¤‚āĨ¤", "theme_setting_primary_color_title": "ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤰⤂⤗", "theme_setting_system_primary_color_title": "⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤰⤂⤗ ā¤•ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚", "theme_setting_system_theme_switch": "ā¤‘ā¤ŸāĨ‹ā¤ŽāĨˆā¤Ÿā¤ŋ⤕ (⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤Ģā¤ŧāĨ‰ā¤˛āĨ‹ ⤕⤰āĨ‡ā¤‚)", + "theme_setting_theme_subtitle": "⤐ā¤Ē ⤕āĨ€ ā¤ĨāĨ€ā¤Ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤚āĨā¤¨āĨ‡ā¤‚", + "theme_setting_three_stage_loading_subtitle": "ā¤ĨāĨā¤°āĨ€-⤏āĨā¤ŸāĨ‡ā¤œ ⤞āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤏āĨ‡ ⤞āĨ‹ā¤Ąā¤ŋ⤂⤗ ā¤Ē⤰ā¤ĢāĨ‰ā¤°āĨā¤ŽāĨ‡ā¤‚⤏ ā¤Ŧā¤ĸā¤ŧ ⤏⤕⤤āĨ€ ā¤šāĨˆ ⤞āĨ‡ā¤•ā¤ŋ⤍ ⤇⤏⤏āĨ‡ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ⤞āĨ‹ā¤Ą ā¤•ā¤žā¤ĢāĨ€ ā¤Ŧā¤ĸā¤ŧ ā¤œā¤žā¤¤ā¤ž ā¤šāĨˆ", + "theme_setting_three_stage_loading_title": "⤤āĨ€ā¤¨-⤚⤰⤪ ⤞āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤏⤕āĨā¤ˇā¤Ž ⤕⤰āĨ‡ā¤‚", "then": "ā¤Ģā¤ŋ⤰", "they_will_be_merged_together": "⤇⤍āĨā¤šāĨ‡ā¤‚ ā¤ā¤• ā¤¸ā¤žā¤Ĩ ā¤Žā¤ŋā¤˛ā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤œā¤žā¤ā¤—ā¤ž", "third_party_resources": "⤤āĨƒā¤¤āĨ€ā¤¯-ā¤Ē⤕āĨā¤ˇ ā¤¸ā¤‚ā¤¸ā¤žā¤§ā¤¨", @@ -1952,15 +2248,23 @@ "trash_all": "⤏ā¤Ŧ ā¤•ā¤šā¤°ā¤ž", "trash_count": "ā¤•ā¤šā¤°ā¤ž {count, number}", "trash_delete_asset": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤕āĨ‹ ⤟āĨā¤°āĨˆā¤ļ/ā¤Ąā¤ŋ⤞āĨ€ā¤Ÿ ⤕⤰āĨ‡ā¤‚", + "trash_emptied": "ā¤•ā¤šā¤°ā¤ž ā¤–ā¤žā¤˛āĨ€ ⤕⤰ ā¤Ļā¤ŋā¤¯ā¤ž", "trash_no_results_message": "⤟āĨā¤°āĨˆā¤ļ ⤕āĨ€ ā¤—ā¤ˆ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ⤔⤰ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤¯ā¤šā¤žā¤‚ ā¤Ļā¤ŋā¤–ā¤žā¤ˆ ā¤ĻāĨ‡ā¤‚⤗āĨ‡āĨ¤", "trash_page_delete_all": "⤏⤭āĨ€ ā¤šā¤Ÿā¤ž ā¤ĻāĨ‹", + "trash_page_empty_trash_dialog_content": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤅ā¤Ē⤍āĨ€ ⤕āĨ‚ā¤Ąā¤ŧāĨ‡ā¤Ļā¤žā¤¨ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ā¤–ā¤žā¤˛āĨ€ ā¤•ā¤°ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚? ⤇⤍ ā¤†ā¤‡ā¤Ÿā¤ŽāĨ‹ā¤‚ ⤕āĨ‹ Immich ⤏āĨ‡ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤¯ā¤ž ā¤œā¤žā¤ā¤—ā¤ž", "trash_page_info": "⤟āĨā¤°āĨˆā¤ļ ⤕ā¤ŋā¤ ā¤—ā¤ ā¤†ā¤‡ā¤Ÿā¤Ž {days} ā¤Ļā¤ŋ⤍āĨ‹ā¤‚ ⤕āĨ‡ ā¤Ŧā¤žā¤Ļ ā¤šā¤ŽāĨ‡ā¤ļā¤ž ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤Ąā¤ŋ⤞āĨ€ā¤Ÿ ⤕⤰ ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤‚ā¤—āĨ‡", + "trash_page_no_assets": "⤕āĨ‹ā¤ˆ ⤟āĨā¤°āĨˆā¤ļ ⤕āĨ€ ā¤—ā¤ˆ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤¨ā¤šāĨ€ā¤‚", + "trash_page_restore_all": "⤏⤭āĨ€ ⤕āĨ‹ ā¤ĒāĨā¤¨ā¤ƒ ⤏āĨā¤Ĩā¤žā¤¨ā¤žā¤‚ā¤¤ā¤°ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "trash_page_select_assets_btn": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ⤝āĨ‹ā¤‚ ⤕āĨ‹ ⤚⤝⤍ ⤕⤰āĨ‡ā¤‚", + "trash_page_title": "ā¤•ā¤šā¤°ā¤ž ({count})", "trashed_items_will_be_permanently_deleted_after": "⤟āĨā¤°āĨˆā¤ļ ⤕ā¤ŋā¤ ā¤—ā¤ ā¤†ā¤‡ā¤Ÿā¤Ž {days, plural, one {# day} other {# days}} ⤕āĨ‡ ā¤Ŧā¤žā¤Ļ ⤏āĨā¤Ĩā¤žā¤¯āĨ€ ⤰āĨ‚ā¤Ē ⤏āĨ‡ ā¤šā¤Ÿā¤ž ā¤Ļā¤ŋā¤ ā¤œā¤žā¤ā¤‚ā¤—āĨ‡āĨ¤", "trigger": "⤟āĨā¤°ā¤ŋ⤗⤰", "trigger_asset_uploaded": "ā¤ā¤¸āĨ‡ā¤Ÿ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "trigger_asset_uploaded_description": "⤜ā¤Ŧ ā¤¨ā¤¯ā¤ž ā¤ā¤¸āĨ‡ā¤Ÿ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕ā¤ŋā¤¯ā¤ž ā¤œā¤žā¤¤ā¤ž ā¤šāĨˆ, ⤤ā¤Ŧ ⤟āĨā¤°ā¤ŋ⤗⤰ ā¤šāĨ‹ā¤¤ā¤ž ā¤šāĨˆ", + "trigger_description": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ā¤ļāĨā¤°āĨ‚ ⤕⤰⤍āĨ‡ ā¤ĩā¤žā¤˛āĨ€ ⤕āĨā¤°ā¤ŋā¤¯ā¤ž", "trigger_person_recognized": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤Ēā¤šā¤šā¤žā¤¨ā¤ž ā¤—ā¤¯ā¤ž", "trigger_person_recognized_description": "⤜ā¤Ŧ ⤕ā¤ŋ⤏āĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤ā¤ŋ ā¤•ā¤ž ā¤Ēā¤¤ā¤ž ā¤šā¤˛ā¤¤ā¤ž ā¤šāĨˆ, ⤤ā¤Ŧ ⤟āĨā¤°ā¤ŋ⤗⤰ ā¤šāĨ‹ā¤¤ā¤ž ā¤šāĨˆ", + "trigger_type": "⤟āĨā¤°ā¤ŋ⤗⤰ ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "troubleshoot": "ā¤¸ā¤Žā¤¸āĨā¤¯ā¤žā¤“⤂ ā¤•ā¤ž ⤍ā¤ŋā¤ĩā¤žā¤°ā¤Ŗ", "type": "ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "unable_to_change_pin_code": "ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ā¤Ŧā¤Ļ⤞⤍āĨ‡ ā¤ŽāĨ‡ā¤‚ ā¤…ā¤¸ā¤Žā¤°āĨā¤Ĩ", @@ -1988,11 +2292,13 @@ "unsaved_change": "ā¤¸ā¤šāĨ‡ā¤œā¤ž ⤍ ā¤—ā¤¯ā¤ž ā¤Ē⤰ā¤ŋā¤ĩ⤰āĨā¤¤ā¤¨", "unselect_all": "⤏⤭āĨ€ ⤕āĨ‹ ā¤…ā¤šā¤¯ā¤¨ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", "unselect_all_duplicates": "⤏⤭āĨ€ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤕āĨ‹ ā¤…ā¤šā¤¯ā¤¨ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "unselect_all_in": "{group} ā¤ŽāĨ‡ā¤‚ ⤏⤭āĨ€ ā¤•ā¤ž ⤚⤝⤍ ⤰ā¤ĻāĨā¤Ļ ⤕⤰āĨ‡ā¤‚", "unstack": "⤏āĨā¤ŸāĨˆā¤• ⤰ā¤ĻāĨā¤Ļ ⤕⤰āĨ‡ā¤‚", "unstack_action_prompt": "{count} ⤅⤍⤏āĨā¤ŸāĨˆā¤•āĨā¤Ą", "unstacked_assets_count": "⤅⤍-⤏āĨā¤ŸāĨˆā¤•āĨā¤Ą {count, plural, one {# asset} other {# assets}}", "unsupported_field_type": "ā¤…ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ā¤Ģā¤ŧāĨ€ā¤˛āĨā¤Ą ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "untagged": "⤟āĨˆā¤— ā¤¨ā¤šāĨ€ā¤‚ ⤕ā¤ŋā¤ ā¤—ā¤", + "untitled_workflow": "ā¤Ŧā¤ŋā¤¨ā¤ž ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤•ā¤ž ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹", "up_next": "⤅ā¤Ŧ ā¤…ā¤—ā¤˛ā¤ž", "update_location_action_prompt": "{count} ⤚āĨā¤¨āĨ‡ ā¤—ā¤ ā¤ā¤¸āĨ‡ā¤Ÿ ⤕āĨ€ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚:", "updated_at": "⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", @@ -2000,6 +2306,8 @@ "upload": "ā¤Ąā¤žā¤˛ā¤¨ā¤ž", "upload_concurrency": "ā¤¸ā¤Žā¤ĩ⤰āĨā¤¤āĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "upload_details": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", + "upload_dialog_info": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ⤚āĨā¤¨āĨ‡ ā¤šāĨā¤ ā¤ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤ž ⤏⤰āĨā¤ĩ⤰ ā¤Ē⤰ ā¤ŦāĨˆā¤•⤅ā¤Ē ⤞āĨ‡ā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", + "upload_dialog_title": "⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‡ā¤‚", "upload_error_with_count": "{count, plural, one {# ā¤ā¤¸āĨ‡ā¤Ÿ} other {# ā¤ā¤¸āĨ‡ā¤Ÿ}} ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž", "upload_errors": "⤅ā¤Ē⤞āĨ‹ā¤Ą {count, plural, one {# error} other {# errors}} ⤕āĨ‡ ā¤¸ā¤žā¤Ĩ ā¤ĒāĨ‚ā¤°ā¤ž ā¤šāĨā¤†, ā¤¨ā¤ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ā¤¸āĨ‡ā¤Ÿ ā¤ĻāĨ‡ā¤–⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ĒāĨ‡ā¤œ ⤕āĨ‹ ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ ⤕⤰āĨ‡ā¤‚āĨ¤", "upload_finished": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤", @@ -2026,6 +2334,7 @@ "user_privacy": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤗āĨ‹ā¤Ē⤍āĨ€ā¤¯ā¤¤ā¤ž", "user_purchase_settings": "⤖⤰āĨ€ā¤Ļā¤¨ā¤ž", "user_purchase_settings_description": "⤅ā¤Ē⤍āĨ€ ⤖⤰āĨ€ā¤Ļā¤žā¤°āĨ€ ā¤ĒāĨā¤°ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤕⤰āĨ‡ā¤‚", + "user_role_set": "{user} ⤕āĨ‹ {role} ⤕āĨ‡ ⤤āĨŒā¤° ā¤Ē⤰ ⤏āĨ‡ā¤Ÿ ⤕⤰āĨ‡ā¤‚", "user_usage_detail": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ā¤ĩā¤ŋā¤ĩ⤰⤪", "user_usage_stats": "ā¤–ā¤žā¤¤ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ⤕āĨ‡ ā¤†ā¤ā¤•ā¤Ąā¤ŧāĨ‡", "user_usage_stats_description": "ā¤–ā¤žā¤¤ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤— ā¤¸ā¤žā¤‚ā¤–āĨā¤¯ā¤ŋ⤕āĨ€ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", @@ -2035,6 +2344,7 @@ "utilities": "⤉ā¤Ē⤝āĨ‹ā¤—ā¤ŋā¤¤ā¤žā¤“ā¤‚", "validate": "ā¤Žā¤žā¤¨āĨā¤¯", "validate_endpoint_error": "⤕āĨā¤°āĨā¤Ēā¤¯ā¤ž ā¤Žā¤žā¤¨āĨā¤¯ ⤝āĨ‚ā¤†ā¤°ā¤ā¤˛ ā¤Ļ⤰āĨā¤œ ⤕⤰āĨ‡ā¤‚", + "validation_error": "ā¤Žā¤žā¤¨āĨā¤¯ā¤•⤰⤪ ā¤ŽāĨ‡ā¤‚ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž", "variables": "⤚⤰", "version": "⤏⤂⤏āĨā¤•⤰⤪", "version_announcement_closing": "⤆ā¤Ēā¤•ā¤ž ā¤Žā¤ŋ⤤āĨā¤°, ā¤ā¤˛āĨ‡ā¤•āĨā¤¸", @@ -2046,6 +2356,7 @@ "video_hover_setting_description": "⤜ā¤Ŧ ā¤Žā¤žā¤‰ā¤¸ ā¤†ā¤‡ā¤Ÿā¤Ž ā¤Ē⤰ ⤘āĨ‚ā¤Ž ā¤°ā¤šā¤ž ā¤šāĨ‹ ⤤āĨ‹ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ā¤šā¤˛ā¤žā¤ā¤‚āĨ¤", "videos": "ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "videos_count": "{count, plural, one {# ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹} other {# ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹}}", + "videos_only": "⤕āĨ‡ā¤ĩ⤞ ā¤ĩāĨ€ā¤Ąā¤ŋ⤝āĨ‹", "view": "ā¤ĻāĨ‡ā¤–ā¤¨ā¤ž", "view_album": "ā¤ā¤˛āĨā¤Ŧā¤Ž ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_all": "⤏⤭āĨ€ ⤕āĨ‹ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", @@ -2054,15 +2365,20 @@ "view_details": "ā¤ĩā¤ŋā¤ĩ⤰⤪ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_in_timeline": "ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨ ā¤ŽāĨ‡ā¤‚ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_link": "⤞ā¤ŋ⤂⤕ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", + "view_links": "⤞ā¤ŋ⤂⤕ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_name": "ā¤ĻāĨ‡ā¤–ā¤¨ā¤ž", "view_next_asset": "⤅⤗⤞āĨ€ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_previous_asset": "ā¤Ēā¤ŋ⤛⤞āĨ€ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_qr_code": "QR ⤕āĨ‹ā¤Ą ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_similar_photos": "ā¤¸ā¤Žā¤žā¤¨ ā¤Ģā¤ŧāĨ‹ā¤ŸāĨ‹ ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "view_stack": "ā¤ĸāĨ‡ā¤° ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", + "view_user": "⤉ā¤Ē⤝āĨ‹ā¤—⤕⤰āĨā¤¤ā¤ž ā¤ĻāĨ‡ā¤–āĨ‡ā¤‚", "viewer_remove_from_stack": "⤏āĨā¤ŸāĨˆā¤• ⤏āĨ‡ ā¤šā¤Ÿā¤žā¤ā¤‚", + "viewer_stack_use_as_main_asset": "ā¤ŽāĨā¤–āĨā¤¯ ⤏⤂ā¤Ē⤤āĨā¤¤ā¤ŋ ⤕āĨ‡ ⤰āĨ‚ā¤Ē ā¤ŽāĨ‡ā¤‚ ⤉ā¤Ē⤝āĨ‹ā¤— ⤕⤰āĨ‡ā¤‚", + "viewer_unstack": "⤏āĨā¤ŸāĨˆā¤• ⤰ā¤ĻāĨā¤Ļ ⤕⤰āĨ‡ā¤‚", "visibility_changed": "{count, plural, one {# person} other {# people}} ⤕āĨ‡ ⤞ā¤ŋā¤ ā¤ĩā¤ŋ⤜ā¤ŧā¤ŋā¤Ŧā¤ŋ⤞ā¤ŋ⤟āĨ€ ā¤Ŧā¤Ļ⤞āĨ€ ā¤—ā¤ˆ", "visual": "ā¤ĩā¤ŋāĨ›āĨ‚⤅⤞", + "visual_builder": "ā¤ĩā¤ŋāĨ›āĨ‚⤅⤞ ā¤Ŧā¤ŋ⤞āĨā¤Ąā¤°", "waiting": "ā¤‡ā¤‚ā¤¤ā¤œā¤ŧā¤žā¤° ā¤ŽāĨ‡ā¤‚", "waiting_count": "ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇā¤ž ⤕⤰ ā¤°ā¤šāĨ‡ ā¤šāĨˆā¤‚: {count}", "warning": "⤚āĨ‡ā¤¤ā¤žā¤ĩ⤍āĨ€", @@ -2081,6 +2397,7 @@ "workflow_navigation_prompt": "⤕āĨā¤¯ā¤ž ⤆ā¤Ē ā¤Ŧā¤ŋā¤¨ā¤ž ⤅ā¤Ē⤍āĨ‡ ā¤Ŧā¤Ļā¤˛ā¤žā¤ĩ ā¤¸ā¤šāĨ‡ā¤œāĨ‡ ā¤¯ā¤šā¤žā¤ ⤏āĨ‡ ā¤Ŧā¤žā¤šā¤° ā¤œā¤žā¤¨ā¤ž ā¤šā¤žā¤šā¤¤āĨ‡ ā¤šāĨˆā¤‚?", "workflow_summary": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ā¤¸ā¤žā¤°ā¤žā¤‚ā¤ļ", "workflow_update_success": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ⤏ā¤Ģā¤˛ā¤¤ā¤žā¤ĒāĨ‚⤰āĨā¤ĩ⤕ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", + "workflow_updated": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ⤕ā¤ŋā¤¯ā¤ž ā¤—ā¤¯ā¤ž", "workflows": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ā¤‚", "workflows_help_text": "ā¤ĩ⤰āĨā¤•ā¤Ģā¤ŧāĨā¤˛āĨ‹ ⤆ā¤Ē⤕āĨ‡ ā¤ā¤¸āĨ‡ā¤Ÿ ⤕āĨ‡ ⤟āĨā¤°ā¤ŋ⤗⤰āĨ‹ā¤‚ ⤔⤰ ā¤Ģā¤ŧā¤ŋ⤞āĨā¤Ÿā¤°āĨ‹ā¤‚ ⤕āĨ‡ ā¤†ā¤§ā¤žā¤° ā¤Ē⤰ ⤕āĨā¤°ā¤ŋā¤¯ā¤žā¤“ā¤‚ ⤕āĨ‹ ⤏āĨā¤ĩā¤šā¤žā¤˛ā¤ŋ⤤ ⤕⤰⤤āĨ‡ ā¤šāĨˆā¤‚", "wrong_pin_code": "⤗⤞⤤ ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą", @@ -2090,5 +2407,6 @@ "you_dont_have_any_shared_links": "⤆ā¤Ē⤕āĨ‡ ā¤Ēā¤žā¤¸ ⤕āĨ‹ā¤ˆ ā¤¸ā¤žā¤ā¤ž ⤞ā¤ŋ⤂⤕ ā¤¨ā¤šāĨ€ā¤‚ ā¤šāĨˆ", "your_wifi_name": "⤆ā¤Ē⤕āĨ‡ ā¤ĩā¤žā¤ˆā¤Ģā¤žā¤ˆ ā¤•ā¤ž ā¤¨ā¤žā¤Ž", "zero_to_clear_rating": "ā¤ā¤¸āĨ‡ā¤Ÿ ⤰āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰⤍āĨ‡ ⤕āĨ‡ ⤞ā¤ŋā¤ 0 ā¤Ļā¤Ŧā¤žā¤ā¤", - "zoom_image": "⤛ā¤ĩā¤ŋ ⤜ā¤ŧāĨ‚ā¤Ž ⤕⤰āĨ‡ā¤‚" + "zoom_image": "⤛ā¤ĩā¤ŋ ⤜ā¤ŧāĨ‚ā¤Ž ⤕⤰āĨ‡ā¤‚", + "zoom_to_bounds": "⤏āĨ€ā¤Žā¤ž ⤤⤕ āĨ›āĨ‚ā¤Ž ⤕⤰āĨ‡ā¤‚" } diff --git a/i18n/hr.json b/i18n/hr.json index 5af7b6790d..5b8f19d174 100644 --- a/i18n/hr.json +++ b/i18n/hr.json @@ -10,31 +10,39 @@ "active": "Aktivno", "active_count": "Aktivno:{count}", "activity": "Aktivnost", + "activity_changed": "Aktivnost je {enabled, select, true {omogućena} other {onemogućena}}", "add": "Dodaj", "add_a_description": "Dodaj opis", "add_a_location": "Dodaj lokaciju", "add_a_name": "Dodaj ime", "add_a_title": "Dodaj naslov", "add_action": "Dodaj akciju", + "add_action_description": "Kliknite za dodavanje radnje koju treba izvrÅĄiti", "add_assets": "Dodaj stavke", "add_birthday": "Dodaj rođendan", "add_endpoint": "Dodaj krajnju točku", "add_exclusion_pattern": "Dodaj uzorak izuzimanja", + "add_filter": "Dodaj filter", + "add_filter_description": "Klikni za dodavanje uvjetnog filtriranja", "add_location": "Dodaj lokaciju", + "add_more_users": "Dodaj joÅĄ korisnika", "add_partner": "Dodaj partnera", + "add_path": "Dodaj putanju", "add_photos": "Dodaj slike", - "add_step": "Dodaj korak", "add_tag": "Dodaj oznaku", "add_to": "Dodaj uâ€Ļ", "add_to_album": "Dodaj u album", "add_to_album_bottom_sheet_added": "Dodano u {album}", "add_to_album_bottom_sheet_already_exists": "Već u {album}", "add_to_album_bottom_sheet_some_local_assets": "Neke lokalne stavke nije moguće dodati u album", + "add_to_album_toggle": "Uključi/isključi odabir za {album}", "add_to_albums": "Dodaj u albume", "add_to_albums_count": "Dodaj u albume ({count})", "add_to_bottom_bar": "Dodaj u", + "add_to_shared_album": "Dodaj u dijeljeni album", "add_upload_to_stack": "Dodaj preneseno u skup", "add_url": "Dodaj URL", + "add_workflow_step": "Dodaj korak radnog procesa", "added_to_archive": "Dodano u arhivu", "added_to_favorites": "Dodano u omiljeno", "added_to_favorites_count": "Dodano {count, number} u omiljeno", @@ -73,7 +81,6 @@ "cron_expression_description": "Postavite interval skeniranja koristeći cron format. Za viÅĄe informacija pogledajte npr. Crontab Guru", "cron_expression_presets": "Unaprijed postavljene postavke cron izraza", "disable_login": "Onemogući prijavu", - "download_csv": "Preuzmi CSV", "duplicate_detection_job_description": "Pokrenite strojno učenje na stavkama kako biste otkrili slične slike. Oslanja se na Pametno pretraÅživanje", "exclusion_pattern_description": "Uzorci izuzimanja omogućuju vam da ignorirate datoteke i mape prilikom skeniranja svoje biblioteke. Ovo je korisno ako imate mape koje sadrÅže datoteke koje ne Åželite uvesti, kao ÅĄto su RAW datoteke.", "export_config_as_json_description": "Preuzmi trenutnu konfiguraciju sustava kao JSON datoteku", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Kvaliteta sličica od 1-100. ViÅĄe je bolje, ali proizvodi veće datoteke i moÅže smanjiti odziv aplikacije.", "image_thumbnail_title": "Postavke sličica", "import_config_from_json_description": "Učitaj konfiguraciju sustava učitavanjem JSON datoteke konfiguracije", - "integrity_checks_checksum_files": "Provjeri kontrolni broj datoteka", - "integrity_checks_checksum_files_description": "Kofiguriraj provjeru kontrolnog broja", - "integrity_checks_checksum_files_enable_description": "Omogući provjeru kontrolnog broja", - "integrity_checks_checksum_files_percentage_limit": "Postotno ograničenje", - "integrity_checks_checksum_files_percentage_limit_description": "Konfiguriraj maksimalni postotak, između 0.01 i 1, za koliko se provjera kontrolnog zbroja treba izvrÅĄavati u svakom intervalu.", - "integrity_checks_checksum_files_time_limit": "Ograničenje vremena", - "integrity_checks_checksum_files_time_limit_description": "Konfiguriraj maksimalno trajanje provjere kontrolnog broja u svakom intervala. (ms)", - "integrity_checks_missing_files": "Nedostajuće datoteke", - "integrity_checks_missing_files_description": "Konfiguriraj frekvenciju i upali/izgasi provjeru nedostajućih datoteka", - "integrity_checks_missing_files_enable_description": "Upali provjeru nedostajućih datoteka", - "integrity_checks_settings": "Provjere integriteta", - "integrity_checks_settings_description": "Upravljaj intervalima za provjeru integriteta", - "integrity_checks_untracked_files": "Nepraćene datoteke", - "integrity_checks_untracked_files_description": "Konfiguriraj frekvenciju i upali/izgasi provjeru nepraćenih datoteka", - "integrity_checks_untracked_files_enable_description": "Upali provjeru nepraćenih datoteka", "job_concurrency": "{job} istovremenost", "job_created": "Zadatak je kreiran", "job_not_concurrency_safe": "Ovaj posao nije siguran za istovremenost.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Omogući pametno pretraÅživanje", "machine_learning_smart_search_enabled_description": "Ako je onemogućeno, slike neće biti kodirane za pametno pretraÅživanje.", "machine_learning_url_description": "URL posluÅžitelja strojnog učenja. Ako ste dodali viÅĄe od jednog URLa, svaki server će biti kontaktiraj jedanput dok jedan ne odgovori uspjeÅĄno, u redu od prvog do zadnjeg. Serveri koji ne odgovore će privremeno biti ignorirani dok ponovo ne postanu dostupni.", - "maintenance_backup_management": "Upravljanje sigurnosnim kopijama", "maintenance_delete_backup": "IzbriÅĄi sigurnosnu kopiju", "maintenance_delete_backup_description": "Ova datoteka će biti trajno obrisana.", "maintenance_delete_error": "Brisanje sigurnosne kopije nije uspjelo.", - "maintenance_integrity_check": "Provjera", - "maintenance_integrity_check_all": "Provjeri sve", - "maintenance_integrity_checksum_mismatch": "Nepodudaranje kontrolnog zbroja (checksum)", - "maintenance_integrity_checksum_mismatch_description": "Datoteke čiji kontrolni zbroj na disku ne podudara se za kontrolnim zbrojom Immicha spremljenog u bazi podataka.", - "maintenance_integrity_checksum_mismatch_job": "Provjera za nepodudaranje kontrolnih zbrojeva", - "maintenance_integrity_checksum_mismatch_refresh_job": "OsvjeÅži izvjeÅĄtaje nepodudaranja kontrolnih zbrojeva", - "maintenance_integrity_missing_file": "Nedostaju datoteke", - "maintenance_integrity_missing_file_description": "Datoteke koje je Immich zabiljeÅžio u svojoj bazi podataka ali koje ne postoje na datotečnom sustavu.", - "maintenance_integrity_missing_file_job": "Provjeri nedostajuće datoteke", - "maintenance_integrity_missing_file_refresh_job": "OsvjeÅži izvjeÅĄtaje o nedostajućim datotekama", - "maintenance_integrity_report": "IzvjeÅĄtaj Integriteta", - "maintenance_integrity_untracked_file": "Nepraćene Datoteke", - "maintenance_integrity_untracked_file_description": "Datoteke u Immich direktorijima koje Immich nije zabiljeÅžio.", - "maintenance_integrity_untracked_file_job": "Provjeri nepraćene datoteke", - "maintenance_integrity_untracked_file_refresh_job": "OsvjeÅži izvjeÅĄtaje o nepraćenim datotekama", "maintenance_restore_backup": "Vrati sigurnosnu kopiju", "maintenance_restore_backup_description": "Immich će biti obrisan i vraćen iz odabrane sigurnosne kopije. Prije nastavka izradit će se nova sigurnosna kopija.", "maintenance_restore_backup_different_version": "Ova sigurnosna kopija izrađena je s drugom verzijom Immicha!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Omogući obavijesti putem e-poÅĄte", "notification_settings": "Postavke obavijesti", "notification_settings_description": "Upravljanje postavkama obavijesti, uključujući e-poÅĄtu", - "oauth_allow_insecure_requests": "Dozvoli nesigurne zahtjeve", - "oauth_allow_insecure_requests_description": "UPOZORENJE: Ovo onemogućava validaciju TLS certifikata za OAuth zahtjeve i moÅže vas izloÅžiti MITM napadima.", "oauth_auto_launch": "Automatsko pokretanje", "oauth_auto_launch_description": "Automatski pokrenite OAuth prijavu nakon navigacije na stranicu za prijavu", "oauth_auto_register": "Automatska registracija", @@ -300,11 +274,9 @@ "oauth_button_text": "Tekst gumba", "oauth_client_secret_description": "Obaveznoya privatnog klijenta ili ukoliko PKCE (Proof Key for Code Exchange) nije podrÅžan od javnog klijenta.", "oauth_enable_description": "Prijavite se putem OAutha", - "oauth_end_session_url_description": "Preusmjeri korisnika na ovaj URI nakon odjave.", "oauth_mobile_redirect_uri": "Mobilnog Preusmjeravanja URI", "oauth_mobile_redirect_uri_override": "Nadjačavanje URI-preusmjeravanja za mobilne uređaje", "oauth_mobile_redirect_uri_override_description": "Omogući kada pruÅžatelj OAuth ne dopuÅĄta mobilni URI, poput ''{callback}''", - "oauth_prompt_description": "Parametri upita (npr. select_account, login, consent)", "oauth_role_claim": "Dodjela uloge", "oauth_role_claim_description": "Automatski dodijeli administratorski pristup na temelju prisutnosti ove tvrdnje. Tvrdnja moÅže sadrÅžavati ili 'user' ili 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "OsvjeÅžavanje svih biblioteka", "registration": "Registracija administratora", "registration_description": "Budući da ste prvi korisnik na sustavu, bit ćete dodijeljeni administratorsku ulogu i odgovorni ste za administrativne poslove, a dodatne korisnike kreirat ćete sami.", - "release_channel_release_candidate": "Kandidat za izdanje", - "release_channel_stable": "Stabilno", "remove_failed_jobs": "Makni neuspjeÅĄne poslove", "require_password_change_on_login": "Zahtijevajte od korisnika promjenu lozinke pri prvoj prijavi", "reset_settings_to_default": "Vrati postavke na zadane", @@ -381,7 +351,7 @@ "template_settings": "PredloÅžak Obavijesti", "template_settings_description": "Upravljaj prilagođenim predloÅĄcima za obavijesti", "theme_custom_css_settings": "Prilagođeni CSS", - "theme_custom_css_settings_description": "Kaskadni Stilovi (CSS) omogućavaju prilagođavanje dizajna Immich-a.", + "theme_custom_css_settings_description": "Kaskadni listovi stilova (CSS) omogućuju prilagođavanje dizajna Immicha.", "theme_settings": "Postavke tema", "theme_settings_description": "Upravljajte prilagodbom Immich web sučelja", "thumbnail_generation_job": "Generirajte sličice", @@ -413,7 +383,7 @@ "transcoding_hardware_acceleration": "Hardversko ubrzanje", "transcoding_hardware_acceleration_description": "Eksperimentalno: brÅže transkodiranje, ali moÅže smanjiti kvalitetu pri istoj brzini prijenosa", "transcoding_hardware_decoding": "Hardversko dekodiranje", - "transcoding_hardware_decoding_setting_description": "Omogućava ubrzanje od početka do kraja (end-to-end) umjesto samo ubrzanja kodiranja. MoÅžda neće raditi na svim video snimcima.", + "transcoding_hardware_decoding_setting_description": "Odnosi se samo na NVENC, QSV i RKMPP. Omogućuje ubrzanje s kraja na kraj umjesto samo ubrzavanja kodiranja. MoÅžda neće raditi na svim videozapisima.", "transcoding_max_b_frames": "Maksimalni B-frameovi", "transcoding_max_b_frames_description": "ViÅĄe vrijednosti poboljÅĄavaju učinkovitost kompresije, ali usporavaju kodiranje. MoÅžda nije kompatibilan s hardverskim ubrzanjem na starijim uređajima. 0 onemogućuje B-frameove, dok -1 automatski postavlja ovu vrijednost.", "transcoding_max_bitrate": "Maksimalne brzina prijenosa (bitrate)", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Odnosi se samo na VAAPI i QSV. Postavlja dri node koji se koristi za hardversko transkodiranje.", "transcoding_preset_preset": "Preset (-preset)", "transcoding_preset_preset_description": "Brzina kompresije. Sporije postavke proizvode manje datoteke i povećavaju kvalitetu pri ciljanju određene postavke bitratea. VP9 zanemaruje brzine iznad 'brÅže'.", - "transcoding_realtime": "Transkodiranje u stvarnom vremenu [EKSPERIMENTALNO]", - "transcoding_realtime_description": "Omogućuje transkodiranje u stvarnom vremenu tijekom slanja videozapisa. Omogućuje promjenu kvalitete, no moÅže uzrokovati veće kaÅĄnjenje reprodukcije i ÅĄtopanje, ovisno o mogućnostima posluÅžitelja.", - "transcoding_realtime_enabled": "Upali transkodiranje u stvarnom vremenu", - "transcoding_realtime_enabled_description": "Ako je izgaÅĄeno, posluÅžitelj će početi da odbija stvaranje novih sesija transkodiranja u stvarnom vremenu.", - "transcoding_realtime_resolutions": "Rezolucije", - "transcoding_realtime_resolutions_description": "Rezolucije ponuđene za transkodiranje u stvarnom vremenu. Veće rezolucije mogu uzrokovati probleme s reprodukcijom ako ih posluÅžitelj ne moÅže dovoljno brzo transkodirati.", - "transcoding_realtime_video_codecs": "Video kodeci", - "transcoding_realtime_video_codecs_description": "Video kodeci dostupni za transkodiranje u stvarnom vremenu. Klijenti će tijekom reprodukcije odabrati najbolju opciju koju podrÅžavaju. AV1 je učinkovitiji od HEVC-a, koji je učinkovitiji od H.264. Pri koriÅĄtenju hardverskog ubrzanja, odaberite samo one kodeke koje akcelerator moÅže kodirati. Kod softverskog transkodiranja imajte na umu da je H.264 brÅži od AV1, koji je brÅži od HEVC-a.", "transcoding_reference_frames": "Referentne slike", "transcoding_reference_frames_description": "Broj slika za referencu prilikom komprimiranja određene slike. ViÅĄe vrijednosti poboljÅĄavaju učinkovitost kompresije, ali usporavaju kodiranje. 0 automatski postavlja ovu vrijednost.", "transcoding_required_description": "Samo videozapisi koji nisu u prihvaćenom formatu", @@ -478,8 +440,6 @@ "user_settings_description": "Upravljanje korisničkim postavkama", "user_successfully_removed": "Korisnik {email} je uspjeÅĄno uklonjen.", "users_page_description": "Administracija korisnika stranica", - "version_check_channel": "Kanal za izdavanje", - "version_check_channel_description": "Odaberite kanal izdavanja za koji Åželite primati obavijesti o verzijama", "version_check_enabled_description": "Omogući provjeru verzije", "version_check_implications": "Značajka provjere verzije oslanja se na periodičnu komunikaciju s {server}", "version_check_settings": "Provjera verzije", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "ObriÅĄi međuspremnik slika", "advanced_settings_clear_image_cache_error": "NeuspjeÅĄno čiÅĄÄ‡enje međuspremnika slika", "advanced_settings_clear_image_cache_success": "UspjeÅĄno očiÅĄÄ‡eno {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Koristite ovu opciju za filtriranje medija tijekom sinkronizacije na temelju alternativnih kriterija. PokuÅĄajte ovo samo ako imate problema s aplikacijom koja ne prepoznaje sve albume.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Koristite alternativni filter za sinkronizaciju albuma na uređaju", "advanced_settings_log_level_title": "Razina zapisivanja: {level}", "advanced_settings_prefer_remote_subtitle": "Neki uređaji sporo učitavaju sličice s lokalnih stavki. Aktivirajte ovu postavku kako biste umjesto toga učitali slike s udaljenih izvora.", "advanced_settings_prefer_remote_title": "Preferiraj udaljene slike", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Prilagođeni proxy zaglavlja [EKSPERIMENTALNO]", "advanced_settings_readonly_mode_subtitle": "Omogućuje način rada za čitanje u kojem se fotografije mogu samo pregledavati, a stvari poput odabira viÅĄe slika, dijeljenja, emitiranja i brisanja su onemogućene. Omogući/onemogući način rada za čitanje putem korisničkog avatara s glavnog zaslona", "advanced_settings_readonly_mode_title": "Read-only mod", + "advanced_settings_self_signed_ssl_subtitle": "Preskoči provjeru SSL certifikata za krajnju točku posluÅžitelja. Potrebno za samo-potpisane certifikate.", + "advanced_settings_self_signed_ssl_title": "Dopusti samopotpisane SSL certifikate [EKSPERIMENTALNO]", "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbriÅĄi ili obnovi stavku na ovom uređaju kada se ta radnja izvrÅĄi na webu", "advanced_settings_sync_remote_deletions_title": "Sinkroniziraj udaljena brisanja [EKSPERIMENTALNO]", "advanced_settings_tile_subtitle": "Postavke za napredne korisnike", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Jeste li sigurni da Åželite izbrisati album {album}?", "album_delete_confirmation_description": "Ako se ovaj album dijeli, drugi korisnici mu viÅĄe neće moći pristupiti.", "album_deleted": "Album izbrisan", + "album_info_card_backup_album_excluded": "IZUZETO", + "album_info_card_backup_album_included": "UKLJUČENO", "album_info_updated": "Podaci o albumu aÅžurirani", + "album_leave": "Napustiti album?", + "album_leave_confirmation": "Jeste li sigurni da Åželite napustiti {album}?", "album_name": "Naziv albuma", "album_options": "Opcije albuma", "album_remove_user": "Ukloni korisnika?", "album_remove_user_confirmation": "Jeste li sigurni da Åželite ukloniti {user}?", "album_search_not_found": "Nema albuma koji odgovaraju vaÅĄem pretraÅživanju", + "album_selected": "Album odabran", "album_share_no_users": "Čini se da ste podijelili ovaj album sa svim korisnicima ili nemate nijednog korisnika s kojim biste ga dijelili.", "album_summary": "SaÅžetak albuma", "album_updated": "Album aÅžuriran", "album_updated_setting_description": "Primite obavijest e-poÅĄtom kada dijeljeni album ima nove stavke", "album_upload_assets": "Učitaj stavku s vlastitog računala i dodaj u album", + "album_user_left": "NapuÅĄten {album}", + "album_user_removed": "Uklonjen {user}", + "album_viewer_appbar_delete_confirm": "Jeste li sigurni da Åželite izbrisati ovaj album s vaÅĄeg računa?", "album_viewer_appbar_share_err_delete": "NeuspjeÅĄno brisanje albuma", + "album_viewer_appbar_share_err_leave": "NeuspjeÅĄno napuÅĄtanje albuma", + "album_viewer_appbar_share_err_remove": "Postoje problemi s uklanjanjem stavki iz albuma", + "album_viewer_appbar_share_err_title": "NeuspjeÅĄno mijenjanje naslova albuma", + "album_viewer_appbar_share_leave": "Napusti album", + "album_viewer_appbar_share_to": "Podijeli s", "album_viewer_page_share_add_users": "Dodaj korisnike", "album_with_link_access": "Dopusti svima s poveznicom pristup fotografijama i osobama u ovom albumu.", "albums": "Albumi", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Početni redoslijed sortiranja stavki prilikom izrade novih albuma.", "albums_feature_description": "Zbirke stavki koje se mogu dijeliti s drugim korisnicima.", "albums_on_device_count": "Albumi na uređaju ({count})", + "albums_selected": "{count, plural, one {# odabrani album} other {# odabrani albumi}}", "all": "Sve", "all_albums": "Svi albumi", "all_people": "Sve osobe", "all_photos": "Sve slike", "all_videos": "Svi videi", "allow_dark_mode": "Dozvoli tamni način", + "allow_edits": "Dozvoli izmjene", "allow_public_user_to_download": "Dopusti javnom korisniku preuzimanje", "allow_public_user_to_upload": "Dopusti javnom korisniku učitavanje", "allowed": "DopuÅĄteno", @@ -549,35 +528,44 @@ "always_keep": "Uvijek zadrÅži", "always_keep_photos_hint": "Oslobodi prostora će zadrÅžati sve slike na ovom uređaju.", "always_keep_videos_hint": "Oslobodi prostora će zadrÅžati sve videe na ovom uređaju.", + "anti_clockwise": "Suprotno smjeru kazaljke na satu", "api_key": "API Ključ", "api_key_description": "Ova će vrijednost biti prikazana samo jednom. Obavezno ju kopirajte prije zatvaranja prozora.", "api_key_empty": "Naziv vaÅĄeg API ključa ne smije biti prazan", "api_keys": "API ključevi", "app_architecture_variant": "Varijanta(Arhitektura)", "app_bar_signout_dialog_content": "Jeste li sigurni da se Åželite odjaviti?", + "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Odjavi se", "app_download_links": "Poveznica za preuzimanje aplikacije", "app_settings": "Postavke aplikacije", - "app_stores": "Trgovine Aplikacija", "app_update_available": "AÅžuriranje aplikacije je dostupno", "appears_in": "Pojavljuje se u", "apply_count": "Primijeni ({count, number})", "archive": "Arhiva", "archive_action_prompt": "{count} dodano u arhivu", "archive_or_unarchive_photo": "Arhivirajte ili dearhivirajte fotografiju", + "archive_page_no_archived_assets": "Nema arhiviranih stavki", + "archive_page_title": "Arhiviraj ({count})", "archive_size": "Veličina arhive", "archive_size_description": "Konfigurirajte veličinu arhive za preuzimanja (u GiB)", "archived": "Arhivirano", "archived_count": "{count, plural, one {Arhivirana #} few {Arhivirane #} other {Arhivirano #}}", "are_these_the_same_person": "Je li ovo ista osoba?", "are_you_sure_to_do_this": "Jeste li sigurni da to Åželite učiniti?", + "array_field_not_fully_supported": "Polja niza zahtijevaju ručno JSON editiranje", + "asset_action_delete_err_read_only": "Nije moguće izbrisati stavke samo za čitanje, preskakanje", + "asset_action_share_err_offline": "Nije moguće dohvatiti izvanmreÅžne stavke, preskakanje", "asset_added_to_album": "Dodano u album", "asset_adding_to_album": "Dodavanje u albumâ€Ļ", "asset_created": "Stavka stvorena", - "asset_day_count": "{date}: {count, plural, one {# stavka} few {# stavke} other {# stavki}}", "asset_description_updated": "Opis stavke je aÅžuriran", + "asset_filename_is_offline": "Stavka {filename} je izvan mreÅže", + "asset_has_unassigned_faces": "Stavka ima nedodijeljena lica", "asset_hashing": "Hashiranjeâ€Ļ", "asset_list_group_by_sub_title": "Grupiraj po", + "asset_list_layout_settings_dynamic_layout_title": "Dinamički raspored", + "asset_list_layout_settings_group_automatically": "Automatski", "asset_list_layout_settings_group_by": "Grupiraj stavke po", "asset_list_layout_settings_group_by_month_day": "Mjesec + dan", "asset_list_layout_sub_title": "Raspored", @@ -588,27 +576,36 @@ "asset_not_found_on_icloud": "Stavka nije pronađena na iCloud. Stavka je moÅžda nedostupna zbog loÅĄe datoteke spremljena na iCloud", "asset_offline": "Stavka izvan mreÅže", "asset_offline_description": "Ova vanjska stavka nije pronađena na disku. Za pomoć se obratite Immich administratoru.", + "asset_restored_successfully": "Stavka uspjeÅĄno obnovljena", "asset_skipped": "Preskočeno", "asset_skipped_in_trash": "U smeću", + "asset_trashed": "Stavka premjeÅĄtena u smeće", "asset_troubleshoot": "RjeÅĄavanje problema sa stavkom", "asset_uploaded": "Preneseno", "asset_uploading": "PrenoÅĄenjeâ€Ļ", "asset_viewer_settings_subtitle": "Upravljajte postavkama vaÅĄeg preglednika galerije", "asset_viewer_settings_title": "Preglednik stavki", "assets": "Stavke", + "assets_added_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}}", "assets_added_to_album_count": "{count, plural, one {Dodana # stavka} few {Dodane # stavke} other {Dodano # stavki}} u album", - "assets_added_to_album_partial_count": "Dodano je {successCount} od {totalCount} {totalCount, plural, one {stavke} few {stavke} other {stavki}} u album", "assets_added_to_albums_count": "{assetTotal, plural, one {Dodana # stavka} other {Dodano # stavki}} u {albumTotal, plural, one {# album} other {# albuma}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Stavka se ne moÅže} other {Stavke se ne mogu}} dodati u album", "assets_cannot_be_added_to_albums": "{count, plural, one {Stavka se ne moÅže} few {Stavke se ne mogu} other {Stavki se ne moÅže}} dodati ni u jedan album", "assets_count": "{count, plural, one {# stavka} few {# stavke} other {# stavki}}", + "assets_deleted_permanently": "{count, plural, one {# stavka trajno izbrisana} few {# stavke trajno izbrisane} other {# stavki trajno izbrisano}}", + "assets_deleted_permanently_from_server": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}} s Immich servera", + "assets_downloaded_failed": "{count, plural, one {Preuzeta # datoteka – {error} datoteka nije uspjela} few {Preuzete # datoteke - {error} datoteke nisu uspjele} other {Preuzeto # datoteka – {error} datoteke nisu uspjele}}", + "assets_downloaded_successfully": "{count, plural, one {UspjeÅĄno preuzeta # datoteka} few {UspjeÅĄno preuzete # datoteke} other {UspjeÅĄno preueto # datoteka}}", "assets_moved_to_trash_count": "{count, plural, one {# stavka premjeÅĄtena} few {# stavke premjeÅĄtene} other {# stavk premjeÅĄtenoi}} u smeće", "assets_permanently_deleted_count": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbrisano # stavki}}", "assets_removed_count": "{count, plural, one {Uklonjena # stavka} few {Uklonjene # stavke} other {Uklonjeno # stavki}}", + "assets_removed_permanently_from_device": "{count, plural, one {# stavka trajno uklonjena} few {# stavke trajno uklonjene} other {# stavki trajno uklonjeno}} s vaÅĄeg uređaja", "assets_restore_confirmation": "Jeste li sigurni da Åželite vratiti sve svoje izbrisane stavke? Ovu radnju ne moÅžete poniÅĄtiti! Imajte na umu da se na ovaj način ne mogu vratiti izvanmreÅžne stavke.", "assets_restored_count": "{count, plural, one {Vraćena # stavka} few {Vraćene # stavke} other {Vraćeno # stavki}}", + "assets_restored_successfully": "{count, plural, one {# stavka uspjeÅĄno obnovljena} few {# stavke uspjeÅĄno obnovljene} other {# stavki uspjeÅĄno obnovljeno}}", "assets_trashed": "{count, plural, one {# stavka premjeÅĄtena} few {# stavke premjeÅĄtene} other {# stavki premjeÅĄteno}} u smeće", "assets_trashed_count": "{count, plural, one {# stavka premjeÅĄtena} few {# stavke premjeÅĄtene} other {# stavki premjeÅĄteno}} u smeće", + "assets_trashed_from_server": "{count, plural, one {# stavka premjeÅĄtena} few {# stavke premjeÅĄtene} other {# stavki premjeÅĄteno}} u smeće s Immich posluÅžitelja", "assets_were_part_of_album_count": "{count, plural, one {Stavka je već bila} other {Stavke su već bile}} dio albuma", "assets_were_part_of_albums_count": "{count, plural, one {Stavka je već bila} other {Stavke su već bile}} dio albuma", "authorized_devices": "OvlaÅĄteni uređaji", @@ -617,48 +614,87 @@ "autoplay_slideshow": "Automatsko prikazivanje slajdova", "back": "Nazad", "back_close_deselect": "Natrag, zatvorite ili poniÅĄtite odabir", + "background_backup_running_error": "Sigurnosno kopiranje u pozadini je trenutno aktivno, ručno sigurnosno kopiranje nije moguće pokrenuti", "background_location_permission": "Dozvola za lokaciju u pozadini", "background_location_permission_content": "Kako bi prebacivao mreÅže dok radi u pozadini, Immich mora *uvijek* imati pristup preciznoj lokaciji kako bi aplikacija mogla pročitati naziv Wi-Fi mreÅže", "background_options": "Pozadinske opcije", "backup": "Sigurnosna kopija", + "backup_album_selection_page_albums_device": "Albumi na uređaju ({count})", "backup_album_selection_page_albums_tap": "Dodirnite za uključivanje, dvostruki dodir za isključivanje", "backup_album_selection_page_assets_scatter": "Stavke se mogu rasprÅĄiti po viÅĄe albuma. Stoga se albumi mogu uključiti ili isključiti tijekom postupka sigurnosnog kopiranja.", "backup_album_selection_page_select_albums": "Odabrani albumi", "backup_album_selection_page_selection_info": "Informacije o odabiru", - "backup_albums_sync": "Sinkronizacija Sigurnosnih Kopija Albuma", + "backup_album_selection_page_total_assets": "Ukupan broj jedinstvenih stavki", + "backup_albums_sync": "Sinkronizacija sigurnosnih kopija albuma", + "backup_all": "Sve", + "backup_background_service_backup_failed_message": "NeuspjeÅĄno sigurnosno kopiranje stavki. Ponovno pokuÅĄavanjeâ€Ļ", "backup_background_service_complete_notification": "Sigurnosno kopiranje stavki dovrÅĄeno", + "backup_background_service_connection_failed_message": "NeuspjeÅĄno povezivanje s posluÅžiteljem. PokuÅĄavam ponovoâ€Ļ", + "backup_background_service_current_upload_notification": "Å aljem {filename}", "backup_background_service_default_notification": "Provjera novih stavkiâ€Ļ", + "backup_background_service_error_title": "PogreÅĄka pri sigurnosnom kopiranju", "backup_background_service_in_progress_notification": "Sigurnosno kopiranje vaÅĄih stavkiâ€Ļ", + "backup_background_service_upload_failure_notification": "NeuspjeÅĄno slanje {filename}", "backup_controller_page_albums": "Sigurnosno kopiranje albuma", + "backup_controller_page_background_app_refresh_disabled_content": "Omogućite osvjeÅžavanje aplikacije u pozadini u Postavke > Opće Postavke > OsvjeÅžavanje Aplikacija u Pozadini kako biste koristili sigurnosno kopiranje u pozadini.", + "backup_controller_page_background_app_refresh_disabled_title": "OsvjeÅžavanje aplikacija u pozadini je onemogućeno", + "backup_controller_page_background_app_refresh_enable_button_text": "Idite u postavke", "backup_controller_page_background_battery_info_link": "PokaÅži mi kako", "backup_controller_page_background_battery_info_message": "Za najbolje iskustvo sigurnosnog kopiranja u pozadini, molimo onemogućite sve optimizacije baterije koje ograničavaju pozadinsku aktivnost Immicha.\n\nBudući da je ovo specifično za uređaj, molimo potraÅžite potrebne informacije za proizvođača vaÅĄeg uređaja.", "backup_controller_page_background_battery_info_ok": "U redu", "backup_controller_page_background_battery_info_title": "Optimizacije baterije", + "backup_controller_page_background_charging": "Samo tijekom punjenja", + "backup_controller_page_background_configure_error": "NeuspjeÅĄno konfiguriranje pozadinske usluge", "backup_controller_page_background_delay": "Odgoda sigurnosnog kopiranja novih stavki: {duration}", + "backup_controller_page_background_description": "Uključite pozadinsku uslugu za automatsko sigurnosno kopiranje svih novih stavki bez potrebe za otvaranjem aplikacije", + "backup_controller_page_background_is_off": "Automatsko sigurnosno kopiranje u pozadini je isključeno", + "backup_controller_page_background_is_on": "Automatsko sigurnosno kopiranje u pozadini je uključeno", + "backup_controller_page_background_turn_off": "Isključite pozadinsku uslugu", + "backup_controller_page_background_turn_on": "Uključite pozadinsku uslugu", + "backup_controller_page_background_wifi": "Samo na Wi-Fi mreÅži", "backup_controller_page_backup": "Sigurnosna kopija", "backup_controller_page_backup_selected": "Odabrani: ", "backup_controller_page_backup_sub": "Sigurnosno kopirane fotografije i videozapisi", + "backup_controller_page_created": "Kreirano: {date}", + "backup_controller_page_desc_backup": "Uključite sigurnosno kopiranje u prvom planu kako biste automatski prenijeli nove stavke na posluÅžitelj prilikom otvaranja aplikacije.", "backup_controller_page_excluded": "Izuzeto: ", + "backup_controller_page_failed": "NeuspjeÅĄno ({count})", + "backup_controller_page_filename": "Naziv datoteke: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informacije o sigurnosnom kopiranju", "backup_controller_page_none_selected": "Nema odabranih", "backup_controller_page_remainder": "Preostalo", "backup_controller_page_remainder_sub": "Preostale fotografije i videozapisi za sigurnosno kopiranje iz odabira", "backup_controller_page_server_storage": "Pohrana na posluÅžitelju", + "backup_controller_page_start_backup": "Pokreni Sigurnosno Kopiranje", + "backup_controller_page_status_off": "Automatsko sigurnosno kopiranje u prvom planu je isključeno", + "backup_controller_page_status_on": "Automatsko sigurnosno kopiranje u prvom planu je uključeno", "backup_controller_page_storage_format": "{used} od {total} iskoriÅĄteno", "backup_controller_page_to_backup": "Albumi za sigurnosno kopiranje", "backup_controller_page_total_sub": "Sve jedinstvene fotografije i videozapisi iz odabranih albuma", + "backup_controller_page_turn_off": "Isključite sigurnosno kopiranje u prvom planu", + "backup_controller_page_turn_on": "Uključite sigurnosno kopiranje u prvom planu", + "backup_controller_page_uploading_file_info": "Slanje informacija o datoteci", + "backup_err_only_album": "Nije moguće ukloniti jedini album", "backup_error_sync_failed": "Sinkronizacija nije uspjela. Sigurnosna kopija se ne moÅže obraditi.", "backup_info_card_assets": "stavke", + "backup_manual_cancelled": "Otkazano", + "backup_manual_in_progress": "Slanje već u tijeku. PokÅĄuajte nakon nekog vremena", + "backup_manual_success": "Uspijeh", + "backup_manual_title": "Status slanja", "backup_options": "Opcije sigurnosne kopije", + "backup_options_page_title": "Opcije sigurnosnog kopiranja", + "backup_setting_subtitle": "Upravljajte postavkama učitavanja u pozadini i prvom planu", "backup_settings_subtitle": "Upravljaj postavkama slanja", + "backup_upload_details_page_more_details": "Pritisnite za vise informacija", "backward": "Unazad", - "battery_optimization_backup_reliability": "Onemogućivanje optimizacije baterije moÅže poboljÅĄati pouzdanost sigurnosnog kopiranja u pozadini", "biometric_auth_enabled": "Biometrijska autentikacija omogućena", "biometric_locked_out": "Zaključani ste iz biometrijske autentikacije", "biometric_no_options": "Nema dostupnih biometrijskih opcija", "biometric_not_available": "Biometrijska autentikacija nije dostupna na ovom uređaju", + "birthdate_saved": "Datum rođenja uspjeÅĄno spremljen", "birthdate_set_description": "Datum rođenja se koristi za izračunavanje godina ove osobe u trenutku fotografije.", "blurred_background": "Zamućena pozadina", - "browse_templates": "Pregledaj predloÅĄke", "bugs_and_feature_requests": "Bugovi i zahtjevi za značajke", "build": "Sagradi (Build)", "build_image": "Sagradi (Build) Image", @@ -666,6 +702,20 @@ "bulk_keep_duplicates_confirmation": "Jeste li sigurni da Åželite zadrÅžati {count, plural, one {# duplicate asset} other {# duplicate asset}}? Ovo će rijeÅĄiti sve duplicirane grupe bez brisanja ičega.", "bulk_trash_duplicates_confirmation": "Jeste li sigurni da Åželite skupno u smeće premjestiti {count, plural, one {# dupliciranu stavku} few {# duplicirane stavke} other {# dupliciranih stavki}}? Ovo će zadrÅžati najveću stavku svake grupe i premjestiti sve ostale duplikate u smeće.", "buy": "Kupi Immich", + "cache_settings_clear_cache_button": "Očisti predmemoriju", + "cache_settings_clear_cache_button_title": "BriÅĄe predmemoriju aplikacije. Ovo će značajno utjecati na performanse aplikacije dok se predmemorija ponovno ne izgradi.", + "cache_settings_duplicated_assets_clear_button": "OČISTI", + "cache_settings_duplicated_assets_subtitle": "Fotografije i videozapisi koje aplikacija ignorira", + "cache_settings_duplicated_assets_title": "Duplicirane stavke ({count})", + "cache_settings_statistics_album": "Sličice biblioteke", + "cache_settings_statistics_full": "Pune slike", + "cache_settings_statistics_shared": "Sličice dijeljenih albuma", + "cache_settings_statistics_thumbnail": "Sličice", + "cache_settings_statistics_title": "KoriÅĄtenje predmemorije", + "cache_settings_subtitle": "Upravljajte ponaÅĄanjem predmemorije mobilne aplikacije Immich", + "cache_settings_tile_subtitle": "Upravljajte ponaÅĄanjem lokalne pohrane", + "cache_settings_tile_title": "Lokalna pohrana", + "cache_settings_title": "Postavke predmemorije", "camera": "Kamera", "camera_brand": "Marka kamere", "camera_model": "Model kamere", @@ -678,10 +728,10 @@ "cannot_update_the_description": "Nije moguće aÅžurirati opis", "cast": "Prijenos na uređaj (Cast)", "cast_description": "Konfigurirajte dostupna odrediÅĄta za prijenos (cast)", - "change": "Izmijeni", "change_date": "Promjena datuma", "change_description": "Promijeni opis", "change_display_order": "Promijeni redoslijed prikaza", + "change_expiration_time": "Promjena vremena isteka", "change_location": "Promjena lokacije", "change_name": "Promjena imena", "change_name_successfully": "Promijena imena uspjeÅĄna", @@ -699,9 +749,11 @@ "changed_visibility_successfully": "Vidljivost je uspjeÅĄno promijenjena", "charging": "Punjenje", "charging_requirement_mobile_backup": "Za izradu sigurnosne kopije u pozadini potrebno je punjenje uređaja", + "check_corrupt_asset_backup": "Provjeri oÅĄtećene sigurnosne kopije stavki", + "check_corrupt_asset_backup_button": "IzvrÅĄi provjeru", + "check_corrupt_asset_backup_description": "Pokrenite ovu provjeru samo putem Wi-Fi mreÅže i nakon ÅĄto su sve stavke sigurnosno kopirane. Postupak moÅže potrajati nekoliko minuta.", "check_logs": "Provjera Zapisa", "checksum": "Kontrolni zbroj", - "choose": "Izaberi", "choose_matching_people_to_merge": "Odaberite odgovarajuće osobe za spajanje", "city": "Grad", "cleanup_confirm_description": "Immich je pronaÅĄao {count} stavki (stvorene prije {date}) sigurno spremljene na serveru. Ukloni lokalne kopije s ovog uređaja?", @@ -719,10 +771,11 @@ "clear": "Očisti", "clear_all": "Očisti sve", "clear_all_recent_searches": "IzbriÅĄi sva nedavna pretraÅživanja", - "clear_failed_count": "ČiÅĄÄ‡enje nije uspjelo ({count})", "clear_file_cache": "Očisti predmemoriju datoteka", "clear_message": "Jasna poruka", "clear_value": "Očisti vrijednost", + "client_cert_dialog_msg_confirm": "U redu", + "client_cert_enter_password": "Unesite lozinku", "client_cert_import": "Uvezi", "client_cert_import_success_msg": "Klijentski certifikat je uvezen", "client_cert_invalid_msg": "Neispravna datoteka certifikata ili pogreÅĄna lozinka", @@ -731,10 +784,12 @@ "client_cert_remove_msg": "Klijentski certifikat je uklonjen", "client_cert_subtitle": "PodrÅžava samo PKCS12 (.p12, .pfx) format. Uvoz/uklanjanje certifikata dostupno je samo prije prijave", "client_cert_title": "SSL klijentski certifikat [EKSPERIMENTALNO]", + "clockwise": "U smjeru kazaljke na satu", "close": "Zatvori", "collapse": "SaÅžmi", "collapse_all": "SaÅžmi sve", "color": "Boja", + "color_theme": "Tema boja", "command": "Naredba", "command_palette_prompt": "Brzo nađi stranice, akcije ili naredbe", "command_palette_to_close": "za zatvoriti", @@ -747,7 +802,6 @@ "comments_are_disabled": "Komentari onemogućeni", "common_create_new_album": "Kreiraj novi album", "completed": "DovrÅĄeno", - "configuration": "Konfiguracija", "confirm": "Potvrdi", "confirm_admin_password": "Potvrdite lozinku administratora", "confirm_delete_face": "Jeste li sigurni da Åželite izbrisati lice {name} iz stavke?", @@ -762,16 +816,19 @@ "contain": "SadrÅži", "context": "Kontekst", "continue": "Nastavi", - "control_bottom_app_bar_add_tags": "Dodaj Oznake", + "control_bottom_app_bar_create_new_album": "Kreiraj novi album", + "control_bottom_app_bar_delete_from_immich": "IzbriÅĄi iz Immicha", "control_bottom_app_bar_delete_from_local": "IzbriÅĄi s uređaja", "control_bottom_app_bar_edit_location": "Uredi lokaciju", "control_bottom_app_bar_edit_time": "Uredi datum i vrijeme", + "control_bottom_app_bar_share_link": "Podijeli poveznicu", + "control_bottom_app_bar_share_to": "Dijeli s", "control_bottom_app_bar_trash_from_immich": "Premjesti u smeće", "copied_image_to_clipboard": "Slika je kopirana u međuspremnik.", "copied_to_clipboard": "Kopirano u međuspremnik!", "copy_error": "GreÅĄka kopiranja", + "copy_file_path": "Kopiraj put datoteke", "copy_image": "Kopiraj Sliku", - "copy_json": "Kopiraj JSON", "copy_link": "Kopiraj poveznicu", "copy_link_to_clipboard": "Kopiraj poveznicu u međuspremnik", "copy_password": "Kopiraj lozinku", @@ -783,7 +840,6 @@ "create_album": "Kreiraj album", "create_album_page_untitled": "Bez naslova", "create_api_key": "Izradi API ključ", - "create_first_workflow": "Napravite prvi radni proces", "create_library": "Kreiraj Biblioteku", "create_link": "Kreiraj poveznicu", "create_link_to_share": "Izradite vezu za dijeljenje", @@ -792,46 +848,45 @@ "create_new_person": "Stvorite novu osobu", "create_new_person_hint": "Dodijelite odabrane stavke novoj osobi", "create_new_user": "Kreiraj novog korisnika", - "create_person": "Stvori novu osobu", - "create_person_subtitle": "Dodaj ime odabranom licu kako bi stvorio i tagirao novu osobu", "create_shared_album_page_share_add_assets": "DODAJ STAVKE", "create_shared_album_page_share_select_photos": "Odaberi fotografije", "create_shared_link": "Kreiraj dijeljeni link", "create_tag": "Stvori oznaku", "create_tag_description": "Napravite novu oznaku. Za ugnijeŞđene oznake unesite punu putanju oznake uključujući kose crte.", "create_user": "Stvori korisnika", - "create_workflow": "Napravite radni proces", "created": "Stvoreno", "created_at": "Kreirano", "creating_linked_albums": "Izradi povezane albume...", "crop": "ObreÅži", + "crop_aspect_ratio_fixed": "Popravljeno", "crop_aspect_ratio_free": "Slobodno", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Kvadrat", + "curated_object_page_title": "Stvari", "current_device": "Trenutačni uređaj", "current_pin_code": "Trenutni PIN kod", "current_server_address": "Trenutna adresa posluÅžitelja", "custom_date": "Specifičan datum", "custom_locale": "Prilagođena lokalizacija", "custom_locale_description": "Formatiranje datuma, vremena i brojeva na temelju selektiranog jezika i regije", + "custom_url": "Prilagođena URL adresa", "cutoff_date_description": "ZadrÅži slike od zadnjihâ€Ļ", "cutoff_day": "{count, plural, one {dan} other {dana}}", "cutoff_year": "{count, plural, one {godina} other {godine}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Tamno", "dark_theme": "Prebaci u tamnu temu", "date": "Datum", "date_after": "Datum nakon", "date_and_time": "Datum i Vrijeme", "date_before": "Datum prije", - "date_of_birth": "Datum rođenja", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Datum rođenja uspjeÅĄno spremljen", "date_range": "Razdoblje", - "date_time_original": "Datum/Vrijeme Originala", "day": "Dan", "days": "Dani", "deduplicate_all": "Dedupliciraj Sve", - "default_quality_subtitle": "Kvaliteta koja se koristi pri dijeljenju. Pritisnite i drÅžite pritisnutim gumb za dijeljenje kako biste svaki put mogli odabrati.", - "default_share_quality": "Zadana kvaliteta dijeljenja", "delete": "IzbriÅĄi", "delete_action_confirmation_message": "Jeste li sigurni da Åželite izbrisati ovu stavku? Ova radnja će premjestiti stavku u smeće posluÅžitelja i pitati vas Åželite li ju izbrisati lokalno", "delete_action_prompt": "{count} izbrisano", @@ -840,6 +895,8 @@ "delete_dialog_alert": "Ove stavke bit će trajno izbrisane iz Immicha i s vaÅĄeg uređaja", "delete_dialog_alert_local": "Ove stavke bit će trajno uklonjene s vaÅĄeg uređaja, ali će i dalje biti dostupne na Immich posluÅžitelju", "delete_dialog_alert_local_non_backed_up": "Neke od stavki nisu sigurnosno kopirane na Immich i bit će trajno uklonjene s vaÅĄeg uređaja", + "delete_dialog_alert_remote": "Ove stavke bit će trajno izbrisane s Immich posluÅžitelja", + "delete_dialog_ok_force": "IzbriÅĄi svejedno", "delete_dialog_title": "Trajno izbriÅĄi", "delete_duplicates_confirmation": "Jeste li sigurni da Åželite trajno izbrisati ove duplikate?", "delete_face": "IzbriÅĄi lice", @@ -858,16 +915,20 @@ "delete_tag_confirmation_prompt": "Jeste li sigurni da Åželite izbrisati oznaku {tagName}?", "delete_user": "IzbriÅĄi korisnika", "deleted_shared_link": "Izbrisana dijeljena poveznica", + "deletes_missing_assets": "BriÅĄe stavke koje nedostaju na disku", "description": "Opis", + "description_input_hint_text": "Dodaj opis...", + "description_input_submit_error": "PogreÅĄka pri aÅžuriranju opisa, provjerite zapisnik za viÅĄe detalja", "deselect_all": "PoniÅĄti odabir svih", "details": "Detalji", "direction": "Smjer", - "disable": "Onesposobi", "disabled": "Onemogućeno", + "disallow_edits": "Zabrani izmjene", "discord": "Discord", "discover": "Otkrij", "discovered_devices": "Otkriveni uređaji", "dismiss_all_errors": "Odbaci sve pogreÅĄke", + "dismiss_error": "Odbaci pogreÅĄku", "display_options": "Mogućnosti prikaza", "display_order": "Redoslijed prikaza", "display_original_photos": "Prikaz originalnih fotografija", @@ -876,29 +937,29 @@ "documentation": "Dokumentacija", "done": "Gotovo", "download": "Preuzmi", + "download_action_prompt": "Preuzimanje {count, plural, one {# stavke} few {# stavke} other {# stavki}}", "download_canceled": "Preuzimanje otkazano", "download_complete": "Preuzimanje zavrÅĄeno", "download_enqueue": "Preuzimanje dodano u red", + "download_error": "PogreÅĄka pri preuzimanju", "download_failed": "Preuzimanje nije uspjelo", "download_finished": "Preuzimanje zavrÅĄeno", "download_include_embedded_motion_videos": "Ugrađeni videozapisi", "download_include_embedded_motion_videos_description": "Uključite videozapise ugrađene u fotografije s pokretom kao zasebnu datoteku", "download_notfound": "Preuzimanje nije pronađeno", - "download_original": "Preuzmi original", "download_paused": "Preuzimanje pauzirano", "download_settings": "Preuzmi", "download_settings_description": "Upravljajte postavkama vezanim uz preuzimanje stavki", + "download_started": "Preuzimanje započeto", + "download_sucess": "Preuzimanje uspjeÅĄno", + "download_sucess_android": "Medij je preuzet u DCIM/Immich", "download_waiting_to_retry": "Čeka se ponovni pokuÅĄaj", "downloading": "Preuzimanje", "downloading_asset_filename": "Preuzimanje stavke {filename}", - "downloading_from_icloud": "Preuzmi s iCloud", "downloading_media": "Preuzimanje medija", - "drag_to_reorder": "Povucite za promjenu redoslijeda", "drop_files_to_upload": "Ispustite datoteke bilo gdje za prijenos", - "duplicate": "Dupliciraj", - "duplicate_workflow": "Dupliciraj radni proces", "duplicates": "Duplikati", - "duplicates_description": "RazrijeÅĄite svaku grupu tako da naznačite koji su duplikati, ako ih ima.", + "duplicates_description": "RazrijeÅĄite svaku grupu tako da naznačite koji su duplikati, ako ih ima", "duration": "Trajanje", "edit": "Izmjena", "edit_album": "Uredi album", @@ -908,7 +969,9 @@ "edit_date_and_time": "Uredite datum i vrijeme", "edit_date_and_time_action_prompt": "{count} datuma i vremena uređeno", "edit_date_and_time_by_offset": "Promijeni datum prema pomaku", + "edit_date_and_time_by_offset_interval": "Novi raspon datuma: {from} - {to}", "edit_description": "Uredi opis", + "edit_description_prompt": "Molimo odaberite novi opis:", "edit_exclusion_pattern": "Uredi uzorak izuzimanja", "edit_faces": "Uređivanje lica", "edit_key": "Ključ za uređivanje", @@ -921,21 +984,9 @@ "edit_tag": "Uredi oznaku", "edit_title": "Uredi Naslov", "edit_user": "Uredi korisnika", - "edit_workflow": "Uredi radni proces", "editor": "Urednik", - "editor_discard_edits_confirm": "Odbaci izmjene", - "editor_discard_edits_prompt": "Imate nesačuvane izmjene. Jeste li sigurni da ih Åželite odbaciti?", - "editor_discard_edits_title": "Odbaci izmjene?", - "editor_edits_applied_error": "GreÅĄka pri spremanju izmjena", - "editor_edits_applied_success": "Izmjene uspjeÅĄno spremljene", - "editor_flip_horizontal": "Vodoravno zrcaljenje", - "editor_flip_vertical": "Vertikalno zrcaljenje", - "editor_handle_corner": "{corner, select, top_left {Gornja lijeva} top_right {Gornja desna} bottom_left {Donja lijeva} bottom_right {Donja desna} other {A}} kutna ručka", - "editor_handle_edge": "{edge, select, top {Gornja} bottom {Donja} left {Lijeva} right {Desna} other {Bilo koja}} rubna ručka", - "editor_orientation": "Orijentacija", - "editor_reset_all_changes": "PoniÅĄti promjene", - "editor_rotate_left": "Rotiraj 90° u suprotnom smjeru kazaljke na satu", - "editor_rotate_right": "Rotiraj 90° u smjeru kazaljke na satu", + "editor_close_without_save_prompt": "Promjene neće biti spremljene", + "editor_close_without_save_title": "Zatvoriti uređivač?", "email": "E-poÅĄta", "email_notifications": "Obavijesti putem e-maila", "empty_folder": "Ova mapa je prazna", @@ -951,11 +1002,11 @@ "enter_your_pin_code": "Unesite svoj PIN kod", "enter_your_pin_code_subtitle": "Unesite svoj PIN kod za pristup zaključanoj mapi", "error": "GreÅĄka", + "error_change_sort_album": "Nije moguće promijeniti redoslijed albuma", "error_delete_face": "PogreÅĄka pri brisanju lica sa stavke", - "error_loading_albums": "GreÅĄka prilikom učitavanja albuma", + "error_getting_places": "GreÅĄka prilikom dohvaćanja lokacija", "error_loading_image": "PogreÅĄka pri učitavanju slike", "error_loading_partners": "GreÅĄka prilikom učitavanja partnera:{error}", - "error_retrieving_asset_information": "GreÅĄka prilikom dobavljanja informacija o stavkama", "error_saving_image": "PogreÅĄka: {error}", "error_tag_face_bounding_box": "PogreÅĄka pri označavanju lica – nije moguće dohvatiti koordinate granica (bounding box)", "error_title": "GreÅĄka - NeÅĄto je poÅĄlo krivo", @@ -991,7 +1042,6 @@ "failed_to_remove_product_key": "Uklanjanje ključa proizvoda nije uspjelo", "failed_to_reset_pin_code": "NeuspjeÅĄno resetiranje PIN koda", "failed_to_stack_assets": "Slaganje stavki nije uspjelo", - "failed_to_tag_assets": "Označavanje stavki nije uspjelo", "failed_to_unstack_assets": "Razdvajanje stavki nije uspjelo", "failed_to_update_notification_status": "NeuspjeÅĄno aÅžuriranje statusa obavijesti", "incorrect_email_or_password": "Netočna adresa e-poÅĄte ili lozinka", @@ -1019,7 +1069,6 @@ "unable_to_complete_oauth_login": "Nije moguće dovrÅĄiti OAuth prijavu", "unable_to_connect": "Povezivanje nije moguće", "unable_to_copy_to_clipboard": "Nije moguće kopirati u međuspremnik, provjerite pristupate li stranici putem https-a", - "unable_to_create": "Stvaranje radnog procesa nije uspjelo", "unable_to_create_admin_account": "Nije moguće stvoriti administratorski račun", "unable_to_create_api_key": "Nije moguće izraditi novi API ključ", "unable_to_create_library": "Nije moguće stvoriti biblioteku", @@ -1030,7 +1079,6 @@ "unable_to_delete_exclusion_pattern": "Nije moguće izbrisati uzorak izuzimanja", "unable_to_delete_shared_link": "Nije moguće izbrisati dijeljenu poveznicu", "unable_to_delete_user": "Nije moguće izbrisati korisnika", - "unable_to_delete_workflow": "Brisanje radnog procesa nije uspjelo", "unable_to_download_files": "Nije moguće preuzeti datoteke", "unable_to_edit_exclusion_pattern": "Nije moguće urediti uzorak izuzimanja", "unable_to_empty_trash": "Nije moguće isprazniti otpad", @@ -1070,7 +1118,6 @@ "unable_to_scan_library": "Nije moguće skenirati knjiÅžnicu", "unable_to_set_feature_photo": "Nije moguće postaviti istaknutu fotografiju", "unable_to_set_profile_picture": "Nije moguće postaviti profilnu sliku", - "unable_to_set_rating": "Nije moguće postaviti ocjenu", "unable_to_submit_job": "Nije moguće poslati posao", "unable_to_trash_asset": "Nije moguće premjestiti stavku u smeće", "unable_to_unlink_account": "Nije moguće prekinuti vezu računa", @@ -1082,7 +1129,6 @@ "unable_to_update_settings": "Nije moguće aÅžurirati postavke", "unable_to_update_timeline_display_status": "Nije moguće aÅžurirati status prikaza vremenske trake", "unable_to_update_user": "Nije moguće aÅžurirati korisnika", - "unable_to_update_workflow": "Nije moguće aÅžurirati radni proces", "unable_to_upload_file": "Nije moguće učitati datoteku" }, "errors_text": "GreÅĄke", @@ -1090,10 +1136,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Dodaj opis...", "exif_bottom_sheet_description_error": "PogreÅĄka pri aÅžuriranju opisa", + "exif_bottom_sheet_details": "DETALJI", + "exif_bottom_sheet_location": "LOKACIJA", "exif_bottom_sheet_no_description": "Bez opisa", + "exif_bottom_sheet_people": "OSOBE", + "exif_bottom_sheet_person_add_person": "Dodaj ime", "exit_slideshow": "Izađi iz projekcije slideova", "expand": "ProÅĄiri", "expand_all": "ProÅĄiri sve", + "experimental_settings_new_asset_list_subtitle": "Rad u tijeku", + "experimental_settings_new_asset_list_title": "Omogući eksperimentalnu mreÅžu fotografija", + "experimental_settings_subtitle": "Koristite na vlastitu odgovornost!", + "experimental_settings_title": "Eksperimentalno", "expire_after": "Istječe nakon", "expired": "Isteklo", "expires_date": "Ističe {date}", @@ -1103,57 +1157,50 @@ "export_as_json": "Izvezi kao JSON", "export_database": "Izvezi bazu podataka", "export_database_description": "Izvezi SQLite bazu podataka", - "exposure_time": "Vrijeme Ekspozicije", "extension": "ProÅĄirenje (Extension)", "external": "Vanjski", "external_libraries": "Vanjske biblioteke", "external_network": "Vanjska mreÅža", "external_network_sheet_info": "Kada niste na Åželjenoj Wi-Fi mreÅži, aplikacija će se povezati s posluÅžiteljem putem prve dostupne URL adrese s popisa ispod, redom od vrha prema dnu", - "f_number": "F-Broj", "face_unassigned": "Nedodijeljeno", "failed": "NeuspjeÅĄno", "failed_count": "Neuspjelo:{count}", "failed_to_authenticate": "NeuspjeÅĄna autentikacija", - "failed_to_delete_file": "Brisanje datoteke nije uspjelo", "failed_to_load_assets": "Učitavanje stavki nije uspjelo", "failed_to_load_folder": "Neuspjelo učitavanje mape", "favorite": "Omiljeno", "favorite_action_prompt": "{count} dodano u Omiljeno", "favorite_or_unfavorite_photo": "Omiljena ili neomiljena fotografija", "favorites": "Omiljene", + "favorites_page_no_favorites": "Nema pronađenih favorita", "feature_photo_updated": "Istaknuta fotografija aÅžurirana", "features": "Značajke", + "features_in_development": "Značajke u razvoju", "features_setting_description": "Upravljajte značajkama aplikacije", "file_name_or_extension": "Naziv ili ekstenzija datoteke", "file_name_text": "Ime datoteke", + "file_name_with_value": "Ime datoteke: {file_name}", "file_size": "Veličina datoteke", "filename": "Naziv datoteke", "filetype": "Vrsta datoteke", "filter": "Filtar", "filter_people": "Filtrirajte ljude", "filter_places": "Filtriraj mjesta", - "filter_tags": "Filtriraj oznake", - "filters": "Filteri", + "find_them_fast": "Pronađite ih brzo po imenu pomoću pretraÅživanja", "first": "Prvi", "fix_incorrect_match": "Ispravite netočno podudaranje", - "focal_length": "Fokalna Duljina", "folder": "Mapa", "folder_not_found": "Mapa nije pronađena", "folders": "Mape", "folders_feature_description": "Pregledavanje prikaza mape za fotografije i videozapise u sustavu datoteka", "forgot_pin_code_question": "Zaboravili ste svoj PIN?", "forward": "Naprijed", - "free_up_space": "Oslobodi Prostor", - "free_up_space_description": "Premjestite preneÅĄene fotografije i videozapise u smeće na uređaju kako biste oslobodili prostor. VaÅĄe kopije na posluÅžitelju ostaju sigurne.", - "free_up_space_settings_subtitle": "Oslobodite prostor na uređaju", "full_path": "Puna putanja:{path}", - "full_path_or_folder": "Puna putanja ili mapa", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Ova značajka učitava vanjske stavke s Googlea kako bi radila.", "general": "Općenito", "geolocation_instruction_location": "Kliknite na stavku s GPS koordinatama da biste koristili njezinu lokaciju ili odaberite lokaciju izravno s karte", "get_help": "PotraÅžite pomoć", - "get_people_error": "GreÅĄka prilikom dobavljanja ljudi", "get_wifiname_error": "Nije moguće dohvatiti naziv Wi-Fi mreÅže. Provjerite imate li potrebna dopuÅĄtenja i jeste li povezani na Wi-Fi mreÅžu", "getting_started": "Početak Rada", "go_back": "Idi natrag", @@ -1168,6 +1215,7 @@ "group_owner": "Grupiraj po vlasniku", "group_places_by": "Grupiraj mjesta po...", "group_year": "Grupiraj po godini", + "haptic_feedback_switch": "Omogući haptičku povratnu informaciju", "haptic_feedback_title": "Haptička povratna informacija", "has_quota": "Ima kvotu", "hash_asset": "Hashiraj stavku", @@ -1178,22 +1226,37 @@ "header_settings_header_name_input": "Naziv zaglavlja", "header_settings_header_value_input": "Vrijednost zaglavlja", "headers_settings_tile_title": "Prilagođena proxy zaglavlja", - "height": "Visina", "hi_user": "Bok {name} ({email})", "hide_all_people": "Sakrij sve ljude", "hide_gallery": "Sakrij galeriju", "hide_named_person": "Sakrij osobu {name}", "hide_password": "Sakrij lozinku", "hide_person": "Sakrij osobu", - "hide_schema": "Sakrij shemu", "hide_text_recognition": "Sakrij prepoznavanje teksta", "hide_unnamed_people": "Sakrij neimenovane osobe", + "home_page_add_to_album_conflicts": "Dodano {added} stavki u album {album}. {failed} stavki je već u albumu.", + "home_page_add_to_album_err_local": "Lokalne stavke joÅĄ nije moguće dodati u albume, preskakanje", + "home_page_add_to_album_success": "Dodano {added} stavki u album {album}.", + "home_page_album_err_partner": "JoÅĄ nije moguće dodati partnerove stavke u album, preskakanje", + "home_page_archive_err_local": "Lokalne stavke joÅĄ nije moguće arhivirati, preskakanje", + "home_page_archive_err_partner": "Partnerove stavke nije moguće arhivirati, preskakanje", "home_page_building_timeline": "Izrada vremenske crte", + "home_page_delete_err_partner": "Nije moguće izbrisati partnerove stavke, preskakanje", + "home_page_delete_remote_err_local": "Lokalne stavke su u odabiru za udaljeno brisanje, preskakanje", + "home_page_favorite_err_local": "Lokalne stavke joÅĄ nije moguće označiti kao favorite, preskakanje", + "home_page_favorite_err_partner": "Partnerove stavke joÅĄ nije moguće označiti kao favorite, preskakanje", + "home_page_first_time_notice": "Ako prvi put koristite aplikaciju, svakako odaberite album za sigurnosnu kopiju kako bi vremenska crta mogla prikazati fotografije i videozapise", + "home_page_locked_error_local": "Nije moguće premjestiti lokalne stavke u zaključanu mapu, preskakanje", + "home_page_locked_error_partner": "Nije moguće premjestiti partnerove stavke u zaključanu mapu, preskakanje", + "home_page_share_err_local": "Lokalne stavke nije moguće dijeliti putem poveznice, preskakanje", + "home_page_upload_err_limit": "Moguće je prenijeti najviÅĄe 30 stavki odjednom, preskakanje", "host": "Domaćin", "hour": "Sat", "hours": "Sati", "id": "ID", "idle": "Neaktivan", + "ignore_icloud_photos": "Ignoriraj iCloud fotografije", + "ignore_icloud_photos_description": "Fotografije pohranjene na iCloudu neće biti učitane na Immich posluÅžitelj", "image": "Slika", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} snimljeno {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno s {person1} {date}", @@ -1205,21 +1268,22 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1} i {person2} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1}, {person2} i {person3} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} s {person1}, {person2} i {additionalCount, number} drugih {date}", + "image_saved_successfully": "Slika je spremljena", + "image_viewer_page_state_provider_download_started": "Preuzimanje započelo", + "image_viewer_page_state_provider_download_success": "UspjeÅĄno Preuzimanje", + "image_viewer_page_state_provider_share_error": "GreÅĄka pri dijeljenju", "immich_logo": "Immich Logo", "immich_web_interface": "Immich Web Sučelje", "import_from_json": "Uvoz iz JSON-a", "import_path": "Putanja uvoza", "in_albums": "U {count, plural, one {# album} other {# albuma}}", "in_archive": "U arhivi", - "in_year": "Godine {year}", - "in_year_selector": "Godine", "include_archived": "Uključi arhivirano", "include_shared_albums": "Uključi dijeljene albume", "include_shared_partner_assets": "Uključi zajedničke stavke partnera", "individual_share": "Pojedinačni udio", "individual_shares": "Pojedinačna dijeljenja", "info": "Informacije", - "integrity_checks": "Provjere Integriteta", "interval": { "day_at_onepm": "Svaki dan u 13 sati", "hours": "{hours, plural, one {Svaki sat} few {Svakih {hours, number} sata} other {Svakih {hours, number} sati}}", @@ -1230,19 +1294,17 @@ "invalid_date_format": "Neispravan format datuma", "invite_people": "Pozovite ljude", "invite_to_album": "Pozovi u album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Dohvaćanje pokrenuto {dateTime}", + "ios_debug_info_last_sync_at": "Zadnja sinkronizacija {dateTime}", + "ios_debug_info_no_processes_queued": "Nema pozadinskih procesa u redu čekanja", + "ios_debug_info_no_sync_yet": "JoÅĄ nije pokrenut nijedan zadatak pozadinske sinkronizacije", + "ios_debug_info_processes_queued": "{count, plural, one {{count} pozadinski proces u redu čekanja} other {{count} pozadinskih procesa u redu čekanja}}", + "ios_debug_info_processing_ran_at": "Obrada pokrenuta {dateTime}", "items_count": "{count, plural, one {# datoteka} other {# datoteke}}", "jobs": "Poslovi", "keep": "ZadrÅži", - "keep_albums": "ZadrÅži albume", - "keep_albums_count": "Čuvam {count} {count, plural, one {album} other {albuma}}", "keep_all": "ZadrÅži Sve", - "keep_description": "Odaberite ÅĄto ostaje na vaÅĄem uređaju prilikom oslobađanja prostora.", - "keep_favorites": "ZadrÅži favorite", - "keep_on_device": "ZadrÅži za uređaju", - "keep_on_device_hint": "Izaberite stavke koje Åželite zadrÅžati na ovom uređaju", "keep_this_delete_others": "ZadrÅži ovo, izbriÅĄi ostale", - "keeping": "Čuvam: {items}", "kept_this_deleted_others": "ZadrÅžana je ova stavka i {count, plural, one {izbrisana # datoteka} few {izbrisane # datoteke} other {izbrisano # datoteka}}", "keyboard_shortcuts": "Prečaci tipkovnice", "language": "Jezik", @@ -1252,46 +1314,37 @@ "language_setting_description": "Odaberite Åželjeni jezik", "large_files": "Velike datoteke", "last": "Zadnji", - "last_months": "{count, plural, one {ProÅĄli mjesec} few {ProÅĄla # mjeseca} other {ProÅĄlih # mjeseci}}", "last_seen": "Zadnji put viđen", "latest_version": "Najnovija verzija", "latitude": "Zemljopisna ÅĄirina", "leave": "Izađi", "leave_album": "Napusti album", "lens_model": "Model objektiva", - "less": "Manje", "let_others_respond": "Dozvoli da drugi odgovore", "level": "Razina", "library": "Biblioteka", - "library_add_folder": "Dodaj mapu", - "library_edit_folder": "Uredi mapu", + "library_options": "Mogućnosti biblioteke", + "library_page_device_albums": "Albumi na uređaju", + "library_page_new_album": "Novi album", "library_page_sort_asset_count": "Broj stavki", "library_page_sort_created": "Datum kreiranja", "library_page_sort_last_modified": "Zadnja izmjena", "library_page_sort_title": "Naslov albuma", "licenses": "Licence", "light": "Svjetlo", - "light_theme": "Prebaci na svijetlu temu", "like": "Sviđa mi se", "like_deleted": "Like izbrisan", - "link": "Poveznica", "link_motion_video": "PoveÅžite videozapis pokreta", - "link_to_docs": "Za viÅĄe informacija, pogledajte dokumentaciju.", "link_to_oauth": "Veza na OAuth", "linked_oauth_account": "Povezani OAuth račun", "list": "Popis", - "live": "UÅživo", - "load_more": "Učitaj JoÅĄ", "loading": "Učitavanje", "loading_search_results_failed": "Učitavanje rezultata pretraÅživanja nije uspjelo", "local": "Lokalno", "local_asset_cast_failed": "Nije moguće emitirati stavku koja nije prenesena na posluÅžitelj", "local_assets": "Lokalne stavke", - "local_id": "Lokalni ID", - "local_media_summary": "SaÅžetak Lokalnih Medija", "local_network": "Lokalna mreÅža", "local_network_sheet_info": "Aplikacija će se povezati s posluÅžiteljem putem ovog URL-a kada koristi određenu Wi-Fi mreÅžu", - "location": "Lokacija", "location_permission": "Dozvola za lokaciju", "location_permission_content": "Kako bi koristio značajku automatskog prebacivanja, Immich treba dozvolu za preciznu lokaciju kako bi mogao očitati naziv trenutne Wi-Fi mreÅže", "location_picker_choose_on_map": "Odaberi na karti", @@ -1301,7 +1354,6 @@ "location_picker_longitude_hint": "Unesite ovdje svoju geografsku duÅžinu", "lock": "Zaključaj", "locked_folder": "Zaključana mapa", - "log_detail_title": "Detalji Zapisnika", "log_out": "Odjavi se", "log_out_all_devices": "Odjava sa svih uređaja", "logged_in_as": "Prijavljeni kao {user}", @@ -1310,9 +1362,11 @@ "login": "Prijava", "login_disabled": "Prijava je onemogućena", "login_form_api_exception": "API iznimka. Provjerite URL posluÅžitelja i pokuÅĄajte ponovno.", + "login_form_back_button_text": "Nazad", "login_form_email_hint": "vasaemailadresa@email.com", "login_form_endpoint_hint": "http://vaÅĄ-server-ip:port", "login_form_endpoint_url": "URL krajnje točke posluÅžitelja", + "login_form_err_http": "Molimo navedite http:// ili https://", "login_form_err_invalid_email": "NevaÅžeća e-mail adresa", "login_form_err_invalid_url": "NevaÅžeći URL", "login_form_err_leading_whitespace": "Početni razmak", @@ -1322,48 +1376,22 @@ "login_form_failed_login": "PogreÅĄka pri prijavi, provjerite URL posluÅžitelja, e-mail i lozinku", "login_form_handshake_exception": "DoÅĄlo je do greÅĄke u uspostavi veze s posluÅžiteljem. Omogućite podrÅĄku za samopotpisane certifikate u postavkama ako koristite takav certifikat.", "login_form_password_hint": "lozinka", - "login_form_server_empty": "Unesite URL posluÅžitelja", - "login_form_server_error": "Nije moguće povezivanje s posluÅžiteljem", - "login_has_been_disabled": "Prijava je onemogućena", + "login_form_save_login": "Ostani prijavljen", + "login_form_server_empty": "Unesite URL posluÅžitelja.", + "login_form_server_error": "Nije moguće povezivanje s posluÅžiteljem.", + "login_has_been_disabled": "Prijava je onemogućena.", "login_password_changed_error": "DoÅĄlo je do pogreÅĄke pri aÅžuriranju lozinke", "login_password_changed_success": "Lozinka je uspjeÅĄno aÅžurirana", "logout_all_device_confirmation": "Jeste li sigurni da Åželite odjaviti sve uređaje?", "logout_this_device_confirmation": "Jeste li sigurni da se Åželite odjaviti s ovog uređaja?", - "logs": "Zapisnici", "longitude": "Zemljopisna duÅžina", "look": "Izgled", "loop_videos": "Ponavljajte videozapise", "loop_videos_description": "Omogućite automatsko ponavljanje videozapisa u pregledniku detalja.", "main_branch_warning": "Koristite razvojnu verziju; strogo preporučamo koriÅĄtenje izdane verzije!", "main_menu": "Glavni izbornik", - "maintenance_action_restore": "Vraćanje Baze Podataka", - "maintenance_description": "Immich je prebačen u način rada za odrÅžavanje.", - "maintenance_end": "ZavrÅĄi način odrÅžavanja", - "maintenance_logged_in_as": "Trenutno ste prijavljeni kao {user}", - "maintenance_restore_from_backup": "Vrati iz Sigurnosne Kopije", - "maintenance_restore_library": "Vratite Svoju KnjiÅžnicu", - "maintenance_restore_library_confirm": "Ako ovo izgleda ispravno, nastavite s vraćanjem sigurnosne kopije!", - "maintenance_restore_library_description": "Vraćanje Baze Podataka", - "maintenance_restore_library_folder_has_files": "{folder} ima {count, plural, one {# mapu} few {# mape} other {# mapa}}", - "maintenance_restore_library_folder_no_files": "U mapi {folder} nedostaju datoteke!", - "maintenance_restore_library_folder_pass": "omogućeno čitanje i pisanje", - "maintenance_restore_library_folder_read_fail": "nije omogućeno čitanje", - "maintenance_restore_library_folder_write_fail": "nije omogućeno pisanje", - "maintenance_restore_library_hint_missing_files": "MoÅžda vam nedostaju vaÅžne datoteke", - "maintenance_restore_library_hint_regenerate_later": "MoÅžete ovo kasnije ponovno generirati u postavkama", - "maintenance_restore_library_hint_storage_template_missing_files": "Koristite predloÅžak za pohranu? MoÅžda vam nedostaju datoteke", - "maintenance_restore_library_loading": "Učitavanje provjera cjelovitosti i heuristikeâ€Ļ", - "maintenance_task_backup": "Izrada sigurnosne kopije postojeće baze podatakaâ€Ļ", - "maintenance_task_migrations": "Pokretanje migracija baze podatakaâ€Ļ", - "maintenance_task_restore": "Vraćanje odabrane sigurnosne kopijeâ€Ļ", - "maintenance_task_rollback": "Vraćanje nije uspjelo, povratak na prijaÅĄnju točku vraćanjaâ€Ļ", - "maintenance_title": "Privremeno Nedostupno", "make": "Proizvođač", - "manage_geolocation": "Upravljaj Lokacijom", - "manage_media_access_rationale": "Ovo je dopuÅĄtenje potrebno za ispravno premjeÅĄtanje stavki u smeće i njihovo vraćanje iz njega.", - "manage_media_access_settings": "Otvori postavke", - "manage_media_access_subtitle": "Dopustite aplikaciji Immich da premjeÅĄta i upravlja multimedijskim datotekama.", - "manage_media_access_title": "Pristup Upravljanju Medijima", + "manage_shared_links": "Upravljanje dijeljenim vezama", "manage_sharing_with_partners": "Upravljajte dijeljenjem s partnerima", "manage_the_app_settings": "Upravljajte postavkama aplikacije", "manage_your_account": "Upravljajte svojim računom", @@ -1371,11 +1399,13 @@ "manage_your_devices": "Upravljajte uređajima na kojima ste prijavljeni", "manage_your_oauth_connection": "Upravljajte svojom OAuth vezom", "map": "Karta", + "map_assets_in_bounds": "{count, plural, =0 {Nema fotografija na ovom području} one {# fotografija} few {# fotografije} other {# fotografija}}", "map_cannot_get_user_location": "Nije moguće dohvatiti lokaciju korisnika", + "map_location_dialog_yes": "Da", "map_location_picker_page_use_location": "Koristi ovu lokaciju", "map_location_service_disabled_content": "Usluga lokacije mora biti omogućena za prikaz stavki s vaÅĄe trenutne lokacije. ÅŊelite li je sada omogućiti?", "map_location_service_disabled_title": "Usluga lokacije onemogućena", - "map_marker_for_image": "Oznaka na karti za sliku uslikanu u gradu {city}, {country}", + "map_marker_for_images": "Oznaka karte za slike snimljene u {city}, {country}", "map_marker_with_image": "Oznaka karte sa slikom", "map_no_location_permission_content": "Potrebno je dopuÅĄtenje za lokaciju kako bi se prikazale stavke s vaÅĄe trenutne lokacije. ÅŊelite li ga sada omogućiti?", "map_no_location_permission_title": "DopuÅĄtenje za lokaciju odbijeno", @@ -1385,46 +1415,17 @@ "map_settings_date_range_option_days": "Posljednjih {days} dana", "map_settings_date_range_option_year": "ProÅĄla godina", "map_settings_date_range_option_years": "Posljednjih {years} godina", + "map_settings_dialog_title": "Postavke karte", "map_settings_include_show_archived": "Uključi arhivirane", "map_settings_include_show_partners": "Uključi partnere", "map_settings_only_show_favorites": "PrikaÅži samo omiljene", "map_settings_theme_settings": "Tema karte", + "map_zoom_to_see_photos": "Umanjite prikaz za pregled fotografija", "mark_all_as_read": "Označi sve kao pročitano", + "mark_as_read": "Označi kao pročitano", "marked_all_as_read": "Označeno sve kao pročitano", "matches": "Podudaranja", "matching_assets": "Odgovarajuće stavke", - "media_chrome": { - "auto": "Automatski", - "captions": "Titlovi", - "captions_off": "IzgaÅĄeni", - "closed_captions": "titlovi", - "decode_error": "PogreÅĄka pri dekodiranju", - "disable_captions": "Onemogući titlove", - "enable_captions": "Omogući titlove", - "enter_fullscreen_mode": "Prijeđi na prikaz preko cijelog zaslona", - "exit_fullscreen_mode": "Izađi iz prikaza preko cijelog zaslona", - "loop": "Petlja", - "media_error_description": "PogreÅĄka medija uzrokovala je prekid reprodukcije. Medij je moÅžda oÅĄtećen ili vaÅĄ preglednik ne podrÅžava ovaj format.", - "media_loading": "učitavanje medija", - "mute": "Isključi zvuk", - "network_error": "PogreÅĄka mreÅže", - "network_error_description": "PogreÅĄka u mreÅži uzrokovala je neuspjeh preuzimanja medijskog sadrÅžaja.", - "not_supported_error": "Izvor Nije PodrÅžan", - "playback_rate": "Brzina reprodukcije", - "playback_rate_current": "trenutna brzina reprodukcije", - "playback_rate_value": "Brzina reprodukcije {playbackRate}", - "playback_time": "vrijeme reprodukcije", - "quality": "Kvaliteta", - "second": "sekunda", - "seconds": "sekundi", - "time_value_of_total_time": "{currentTime} od {totalTime}", - "time_value_remaining": "{time} preostalo", - "unmute": "Uključi zvuk", - "unsupported_error_description": "DoÅĄlo je do nepoznate pogreÅĄke. GreÅĄka je na posluÅžitelju ili mreÅži, ili vaÅĄ preglednik ne podrÅžava ovaj format.", - "video_not_loaded_unknown_time": "videozapis nije učitan, nepoznato vrijeme.", - "video_player": "videoplayer", - "volume": "jačina zvuka" - }, "media_type": "Vrsta medija", "memories": "Sjećanja", "memories_all_caught_up": "Sve ste pregledali", @@ -1441,36 +1442,28 @@ "merge_people_prompt": "ÅŊelite li spojiti ove ljude? Ova radnja je nepovratna.", "merge_people_successfully": "UspjeÅĄno spajanje osoba", "merged_people_count": "{count, plural, one {# Spojena osoba} other {# Spojene osobe}}", - "minFaces": "Minimalni broj lica", - "minFaces_description": "Minimalni broj prepoznatih lica koji je potreban da bi se osoba prikazala", "minimize": "Minimiziraj", "minute": "Minuta", "minutes": "Minute", "missing": "Nedostaje", - "mobile_app": "Mobilna Aplikacija", - "mobile_app_download_onboarding_note": "Preuzmi prateću mobilnu aplikaciju koristeći sljedeće opcije", "model": "Model", - "modify_date": "Datum Izmjene", "month": "Mjesec", + "monthly_title_text_date_format": "MMMM y", "more": "ViÅĄe", - "motion": "Pokret", "move": "Pomakni", "move_off_locked_folder": "Premjesti iz zaključane mape", - "move_to": "Premjesti u", - "move_to_device_trash": "Premjesti u smeće uređaja", "move_to_lock_folder_action_prompt": "{count} dodano u zaključanu mapu", "move_to_locked_folder": "Premjesti u zaključanu mapu", "move_to_locked_folder_confirmation": "Ove fotografije i videozapis bit će uklonjeni iz svih albuma i bit će vidljivi samo iz zaključane mape", + "moved_to_archive": "{count, plural, one {PremjeÅĄtena # stavka} few {PremjeÅĄtene # stavke} other {PremjeÅĄteno # stavki}} u arhivu", + "moved_to_library": "{count, plural, one {PremjeÅĄtena # stavka} few {PremjeÅĄtene # stavke} other {PremjeÅĄteno # stavki}} u biblioteku", "moved_to_trash": "PremjeÅĄteno u smeće", + "multiselect_grid_edit_date_time_err_read_only": "Nije moguće urediti datum stavki označenih kao samo za čitanje, preskakanje", + "multiselect_grid_edit_gps_err_read_only": "Nije moguće urediti lokaciju stavki označenih kao samo za čitanje, preskakanje", "mute_memories": "Isključi uspomene", "my_albums": "Moji albumi", - "my_immich_description": "Kopiraj trenutnu stranicu kao My Immich poveznicu", - "my_immich_title": "My Immich poveznica", "name": "Ime", "name_or_nickname": "Ime ili nadimak", - "name_required": "Ime je obavezno", - "navigate": "Navigacija", - "navigate_to_time": "Idi na Vrijeme", "network_requirement_photos_upload": "Koristi mobilne podatke za sigurnosno kopiranje fotografija", "network_requirement_videos_upload": "Koristi mobilne podatke za sigurnosno kopiranje videozapisa", "network_requirements": "MreÅžni zahtjevi", @@ -1480,11 +1473,12 @@ "never": "Nikada", "new_album": "Novi Album", "new_api_key": "Novi API ključ", - "new_feature": "Nova Značajka", + "new_date_range": "Novi raspon datuma", "new_password": "Nova lozinka", "new_person": "Nova osoba", "new_pin_code": "Novi PIN kod", "new_pin_code_subtitle": "Ovo je vaÅĄ prvi pristup zaključanoj mapi. Kreirajte PIN kod za siguran pristup ovoj stranici", + "new_timeline": "Nova vremenska lenta", "new_update": "Novo aÅžuriranje", "new_user_created": "Stvoren novi korisnik", "new_version_available": "DOSTUPNA NOVA VERZIJA", @@ -1492,12 +1486,11 @@ "next": "Sljedeće", "next_memory": "Sljedeće sjećanje", "no": "Ne", - "no_albums_found": "Albumi nisu pronađeni", "no_albums_message": "Izradite album za organiziranje svojih fotografija i videozapisa", "no_albums_with_name_yet": "Čini se da joÅĄ nemate nijedan album s ovim imenom.", "no_albums_yet": "Čini se da joÅĄ nemate nijedan album.", "no_archived_assets_message": "Arhivirajte fotografije i videozapise kako biste ih sakrili iz prikaza fotografija", - "no_assets_message": "Kliknite za prijenos svoje prve fotografije", + "no_assets_message": "KLIKNITE DA PRENESETE SVOJU PRVU FOTOGRAFIJU", "no_assets_to_show": "Nema stavki za prikaz", "no_cast_devices_found": "Nisu pronađeni uređaji za reprodukciju", "no_checksum_local": "Nema dostupnog kontrolnog zbroja - nije moguće dohvatiti lokalne stavke", @@ -1509,6 +1502,7 @@ "no_favorites_message": "Dodajte favorite kako biste brzo pronaÅĄli svoje najbolje slike i videozapise", "no_libraries_message": "Stvorite vanjsku biblioteku za pregled svojih fotografija i videozapisa", "no_local_assets_found": "Nisu pronađene lokalne stavke s ovim kontrolnim zbrojem", + "no_location_set": "Lokacija nije postavljena", "no_locked_photos_message": "Fotografije i videozapisi u zaključanoj mapi su skriveni i neće se prikazivati dok pregledavate ili pretraÅžujete svoju biblioteku.", "no_name": "Bez imena", "no_notifications": "Nema notifikacija", @@ -1518,18 +1512,13 @@ "no_results": "Nema rezultata", "no_results_description": "PokuÅĄajte sa sinonimom ili općenitijom ključnom riječi", "no_shared_albums_message": "Stvorite album za dijeljenje fotografija i videozapisa s osobama u svojoj mreÅži", - "no_steps": "Nisu joÅĄ dodani koraci", - "none": "NiÅĄta", + "no_uploads_in_progress": "Nema aktivnih prijenosa", "not_allowed": "Zabranjeno", "not_available": "N/A", "not_in_any_album": "Ni u jednom albumu", "not_selected": "Nije odabrano", "notes": "BiljeÅĄke", "nothing_here_yet": "Ovdje joÅĄ nema ničega", - "notification_backup_reliability": "Omogućite obavijesti za poboljÅĄanje pouzdanosti sigurnosne kopije u pozadini", - "notification_enabled_list_tile_content": "Immich koristi obavijesti za sigurnosno kopiranje u pozadini. Upravljajte njima u postavkama uređaja.", - "notification_enabled_list_tile_open_button": "Otvori postavke", - "notification_enabled_list_tile_title": "Obavijesti uključene", "notification_permission_dialog_content": "Da biste omogućili obavijesti, idite u Postavke i odaberite dopusti.", "notification_permission_list_tile_content": "Dodijelite dopuÅĄtenje za omogućavanje obavijesti.", "notification_permission_list_tile_enable_button": "Omogući obavijesti", @@ -1539,10 +1528,6 @@ "notifications_setting_description": "Upravljanje obavijestima", "oauth": "OAuth", "obtainium_configurator": "Obtainium konfigurator", - "obtainium_configurator_instructions": "Koristite Obtainium za instalaciju i aÅžuriranje Android aplikacije izravno sa Immich GitHub izdanja. Napravite API ključ i odaberite varijantu kako biste kreirali poveznicu za Obtainium konfiguraciju", - "ocr": "OCR", - "ocr_body": "Immich sada čita tekst unutar vaÅĄih fotografija, pa ih moÅžete pretraÅživati prema onome ÅĄto na njima piÅĄe.", - "ocr_title": "PretraÅžite tekst u svojim fotografijama", "official_immich_resources": "SluÅžbeni Immich resursi", "offline": "Izvan mreÅže", "offset": "Pomak", @@ -1559,34 +1544,30 @@ "online": "Dostupan (Online)", "only_favorites": "Samo omiljeno", "open": "Otvori", - "open_calendar": "Otvori kalendar", - "open_in_browser": "Otvori u pregledniku", - "open_in_immich_body": "Postavite Immich kao svoju Android galeriju kako biste mogli otvarali fotografije izravno iz drugih aplikacija.", - "open_in_immich_title": "Otvori fotografije u Immich", "open_in_map_view": "Otvori u prikazu karte", "open_in_openstreetmap": "Otvori u OpenStreetMap", "open_the_search_filters": "Otvorite filtre pretraÅživanja", "options": "Opcije", "or": "ili", - "organize_into_albums": "Organiziraj u albume", - "organize_into_albums_description": "Organiziraj postojeće fotografije u albume koristeći trenutne postavke sinkronizacije", "organize_your_library": "Organizirajte svoju knjiÅžnicu", - "orientation": "Orijentacija", "original": "originalno", "other": "Ostalo", "other_devices": "Ostali uređaji", + "other_entities": "Ostali entiteti", "other_variables": "Ostale varijable", "owned": "VlasniÅĄtvo", "owner": "Vlasnik", - "page": "Stranica", "partner": "Partner", "partner_can_access": "{partner} moÅže pristupiti", "partner_can_access_assets": "Sve vaÅĄe fotografije i videozapisi osim onih u arhivi i smeću", "partner_can_access_location": "Mjesto otkuda je slika otkinuta", "partner_list_user_photos": "{user} fotografije", + "partner_list_view_all": "PrikaÅži sve", "partner_page_empty_message": "VaÅĄe fotografije joÅĄ nisu podijeljene ni s jednim partnerom.", "partner_page_no_more_users": "Nema viÅĄe korisnika za dodavanje", + "partner_page_partner_add_failed": "Nije uspjelo dodavanje partnera", "partner_page_select_partner": "Odaberi partnera", + "partner_page_shared_to_title": "Podijeljeno s", "partner_page_stop_sharing_content": "{partner} viÅĄe neće moći pristupiti vaÅĄim fotografijama.", "partner_sharing": "Dijeljenje s partnerom", "partners": "Partneri", @@ -1608,6 +1589,7 @@ "people": "Osobe", "people_edits_count": "Izmjenjeno {count, plural, one {# osoba} other {# osobe}}", "people_feature_description": "Pregledavanje fotografija i videozapisa grupiranih po osobama", + "people_sidebar_description": "PrikaÅžite poveznicu na Osobe na bočnoj traci", "permanent_deletion_warning": "Upozorenje za nepovratno brisanje", "permanent_deletion_warning_setting_description": "PrikaÅži upozorenje prilikom trajnog brisanja stavki", "permanently_delete": "Nepovratno obriÅĄi", @@ -1617,20 +1599,26 @@ "permanently_deleted_assets_count": "Trajno {count, plural, one {izbrisana # stavka} few {izbrisane # stavke} other {izbisano # stavki}}", "permission": "Dozvola", "permission_empty": "VaÅĄa dozvola ne smije biti prazna", + "permission_onboarding_back": "Natrag", + "permission_onboarding_continue_anyway": "Nastavi svejedno", + "permission_onboarding_get_started": "Započni", + "permission_onboarding_go_to_settings": "Idi u postavke", + "permission_onboarding_permission_denied": "DopuÅĄtenje odbijeno. Za koriÅĄtenje Immicha, dodijelite dopuÅĄtenja za fotografije i videozapise u Postavkama.", + "permission_onboarding_permission_granted": "DopuÅĄtenje dodijeljeno! Sve je spremno.", + "permission_onboarding_permission_limited": "DopuÅĄtenje ograničeno. Da biste Immichu dopustili sigurnosno kopiranje i upravljanje cijelom galerijom, dodijelite dopuÅĄtenja za fotografije i videozapise u Postavkama.", + "permission_onboarding_request": "Immich zahtijeva dopuÅĄtenje za pregled vaÅĄih fotografija i videozapisa.", "person": "Osoba", "person_age_months": "{months, plural, one {# mjesec} few {# mjeseca} other {# mjeseci}} staro", "person_age_year_months": "1 godina, {months, plural, one {# mjesec} few {# mjeseca} other {# mjeseci}} staro", "person_age_years": "{years, plural, few {# godine} other {# godina}} staro", "person_birthdate": "Rođen/a {date}", "person_hidden": "{name}{hidden, select, true { (skriveno)} other {}}", - "person_recognized": "Osoba prepoznata", "photo_shared_all_users": "Čini se da ste svoje fotografije podijelili sa svim korisnicima ili nemate nijednog korisnika s kojim biste ih podijelili.", "photos": "Fotografije", "photos_and_videos": "Fotografije i videozapisi", + "photos_count": "{count, plural, one {{count, number} fotografija} few {{count, number} fotografije} other {{count, number} fotografija}}", "photos_from_previous_years": "Fotografije iz prethodnih godina", "pick_a_location": "Odaberite lokaciju", - "pick_custom_range": "Prilagođeni raspon", - "pick_date_range": "Odaberite raspon datuma", "pin_code_changed_successfully": "PIN kod je uspjeÅĄno promijenjen", "pin_code_reset_successfully": "PIN kod je uspjeÅĄno poniÅĄten", "pin_code_setup_successfully": "PIN kod je uspjeÅĄno postavljen", @@ -1642,12 +1630,8 @@ "play_memories": "Pokreni sjećanja", "play_motion_photo": "Reproduciraj Pokretnu fotografiju", "play_or_pause_video": "Reproducirajte ili pauzirajte video", - "play_original_video": "Reproduciraj izvorni videozapis", "play_original_video_setting_description": "Preferirajte reprodukciju originalnih videozapisa umjesto transkodiranih videozapisa. Ako originalna stavka nije kompatibilna, moÅžda se neće ispravno reproducirati.", - "play_transcoded_video": "Reproduciraj transkodirani videozapis", "please_auth_to_access": "Molimo autentificirajte se za pristup", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Ova metoda moÅže filtrirati događaje i uvjetno spriječiti izvrÅĄavanje sljedećih koraka", "port": "Port", "preferences_settings_subtitle": "Upravljajte postavkama aplikacije", "preferences_settings_title": "Postavke", @@ -1664,11 +1648,11 @@ "privacy": "Privatnost", "profile": "Profil", "profile_drawer_app_logs": "Zapisnici", + "profile_drawer_client_server_up_to_date": "Klijent i posluÅžitelj su aÅžurirani", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Omogućen je način rada samo za čitanje. Dugo pritisnite ikonu korisničkog avatara za izlaz.", "profile_image_of_user": "Profilna slika korisnika {user}", "profile_picture_set": "Profilna slika postavljena.", - "projection_type": "Vrsta Projekcije", "public_album": "Javni album", "public_share": "Javno dijeljenje", "purchase_account_info": "PodrÅžava softver", @@ -1687,6 +1671,7 @@ "purchase_individual_description_2": "Status podrÅžavanja", "purchase_individual_title": "Pojedinačna licenca", "purchase_input_suggestion": "Imate ključ proizvoda? Unesite ključ ispod", + "purchase_license_subtitle": "Kupite Immich kako biste podrÅžali kontinuirani razvoj usluge", "purchase_lifetime_description": "DoÅživotna kupnja", "purchase_option_title": "MOGUĆNOSTI KUPNJE", "purchase_panel_info_1": "Za izgradnju Immicha potrebno je puno vremena i truda, a mi imamo inÅženjere koji rade na tome s punim radnim vremenom kako bismo ga učinili ÅĄto boljim. NaÅĄa je misija da softver otvorenog koda i etička poslovna praksa postanu odrÅživi izvor prihoda za programere i da se stvori ekosustav koji poÅĄtuje privatnost sa stvarnim alternativama eksploatacijskim uslugama u oblaku.", @@ -1704,10 +1689,9 @@ "purchase_settings_server_activated": "Ključem proizvoda posluÅžitelja upravlja administrator", "query_asset_id": "Ispitaj ID stavke", "queue_status": "Stavljanje u red {count}/{total}", - "rate_asset": "Ocjeni Stavku", "rating": "Broj zvjezdica", "rating_clear": "ObriÅĄi ocjenu", - "rating_count": "{count, plural, =0 {Bez ocjene} one {# zvijezda} few {# zvijezde} other {# zvijezda}}", + "rating_count": "{count, plural, one {# zvijezda} other {# zvijezde}}", "rating_description": "PrikaÅži EXIF ocjenu na info ploči", "reaction_options": "Mogućnosti reakcije", "read_changelog": "Pročitajte Dnevnik promjena", @@ -1719,32 +1703,31 @@ "reassigned_assets_to_new_person": "{count, plural, one {# stavka ponovno dodijeljena} few {# stavke ponovno dodijeljene} other {# stavki ponovno dodijeljeno}} novoj osobi", "reassing_hint": "Dodijelite odabrane stavke postojećoj osobi", "recent": "Nedavno", + "recent_albums": "Nedavni albumi", "recent_searches": "Nedavne pretrage", "recently_added": "Nedavno dodano", - "recently_added_body": "Izravno pristupite svemu ÅĄto ste nedavno dodali preko namjenske stranice.", - "recently_added_description": "Pregledajte svoje stavke poredane po vremenu prijenosa na Immich", - "recently_added_page_title": "Nedavno Dodano", - "recently_added_title": "Nedavno dodano", + "recently_added_page_title": "Nedavno dodano", "recently_taken": "Nedavno snimljeno", + "recently_taken_page_title": "Nedavno snimljeno", "refresh": "OsvjeÅži", "refresh_encoded_videos": "OsvjeÅžite kodirane videozapise", "refresh_faces": "OsvjeÅžite lica", "refresh_metadata": "OsvjeÅži metapodatke", "refresh_thumbnails": "OsvjeÅži sličice", "refreshed": "OsvjeÅženo", + "refreshes_every_file": "OsvjeÅžava svaku datoteku", "refreshing_encoded_video": "OsvjeÅžavanje kodiranog videa", "refreshing_faces": "OsvjeÅžavanje lica", "refreshing_metadata": "OsvjeÅžavanje metapodataka", "regenerating_thumbnails": "Obnavljanje sličica", "remote": "Udaljeno", "remote_assets": "Udaljene stavke", - "remote_media_summary": "SaÅžetak Daljinskih Medija", "remove": "Ukloni", "remove_assets_album_confirmation": "Jeste li sigurni da Åželite ukloniti {count, plural, one {# stavku} few {# stavke} other {# stavki}} iz albuma?", "remove_assets_shared_link_confirmation": "Jeste li sigurni da Åželite ukloniti {count, plural, one {# stavku} few {# stavke} other {# stavki}} iz ove dijeljene veze?", "remove_assets_title": "Ukloniti stavke?", "remove_custom_date_range": "Ukloni prilagođeni datumski raspon", - "remove_filter": "Ukloni filter", + "remove_deleted_assets": "Ukloni izbrisane stavke", "remove_from_album": "Ukloni iz albuma", "remove_from_album_action_prompt": "{count} uklonjeno iz albuma", "remove_from_favorites": "Ukloni iz favorita", @@ -1755,39 +1738,43 @@ "remove_memory": "Ukloni uspomenu", "remove_photo_from_memory": "Ukloni fotografiju iz ove uspomene", "remove_tag": "Ukloni oznaku", + "remove_url": "Ukloni URL", "remove_user": "Ukloni korisnika", "removed_api_key": "Uklonjen API ključ: {name}", "removed_from_archive": "Uklonjeno iz arhive", "removed_from_favorites": "Uklonjeno iz favorita", "removed_from_favorites_count": "{count, plural, other {Uklonjeno #}} iz omiljenih", "removed_memory": "Uklonjena uspomena", + "removed_photo_from_memory": "Uklonjena fotografija iz uspomene", "removed_tagged_assets": "Uklonjena oznaka iz {count, plural, one {# stavke} few {# stavke} other {# stavki}}", "rename": "Preimenuj", + "repair": "Popravi", + "repair_no_results_message": "Nepraćene datoteke i datoteke koje nedostaju pojavit će se ovdje", + "replace_with_upload": "Zamijeni s prijenosom", "repository": "SpremiÅĄte (Repository)", "require_password": "Zahtijevaj lozinku", + "require_user_to_change_password_on_first_login": "Zahtijevajte od korisnika promjenu lozinke pri prvoj prijavi", "rescan": "Ponovno skeniraj", "reset": "Resetiraj", "reset_password": "Resetiraj lozinku", "reset_people_visibility": "PoniÅĄti vidljivost osoba", "reset_pin_code": "Resetiraj PIN kod", "reset_pin_code_description": "Ako ste zaboravili svoj PIN kod, moÅžete kontaktirati administratora posluÅžitelja da ga resetira", + "reset_pin_code_success": "PIN kod je uspjeÅĄno resetiran", "reset_pin_code_with_password": "Uvijek moÅžete resetirati svoj PIN kod pomoću svoje lozinke", "reset_sqlite": "Resetiraj SQLite bazu podataka", - "reset_sqlite_clear_app_data": "IzbriÅĄi Podatke", - "reset_sqlite_confirmation": "Jeste li sigurni da Åželite izbrisati podatke aplikacije? Time ćete ukloniti sve postavke i bit ćete odjavljeni.", - "reset_sqlite_confirmation_note": "Napomena: Nakon brisanja morat ćete ponovno pokrenuti aplikaciju.", - "reset_sqlite_done": "Podaci aplikacije su izbrisani. Molimo vas da ponovno pokrenete Immich i ponovno se prijavite.", + "reset_sqlite_confirmation": "Jeste li sigurni da Åželite resetirati SQLite bazu podataka? Morat ćete se odjaviti i ponovno prijaviti kako biste ponovno sinkronizirali podatke", "reset_sqlite_success": "SQLite baza podataka je uspjeÅĄno resetirana", "reset_to_default": "Vrati na zadano", - "resolution": "Rezolucija", "resolve_duplicates": "RijeÅĄite duplikate", "resolved_all_duplicates": "RazrijeÅĄi sve duplikate", "restore": "Oporavi", "restore_all": "Oporavi sve", + "restore_trash_action_prompt": "{count} vraćeno iz smeća", "restore_user": "Vrati korisnika", "restored_asset": "Obnovljena stavka", "resume": "Nastavi", - "resume_paused_jobs": "Nastavi {count, plural, one {# pauzirani posao} few {# pauzirana posla} other {# pauziranih poslova}}", + "retry_upload": "Ponovi prijenos", "review_duplicates": "Pregledajte duplikate", "review_large_files": "Pregledaj velike datoteke", "role": "Uloga", @@ -1795,20 +1782,16 @@ "role_viewer": "Gledatelj", "running": "U tijeku", "save": "Spremi", - "saved": "Spremljeno", + "save_to_gallery": "Spremi u galeriju", "saved_api_key": "Spremljen API ključ", "saved_profile": "Spremljen profil", "saved_settings": "Spremljene postavke", "say_something": "Reci neÅĄto", "scaffold_body_error_occurred": "DoÅĄlo je do pogreÅĄke", - "scaffold_body_error_unrecoverable": "DoÅĄlo je do nepopravljive pogreÅĄke. Podijelite pogreÅĄku i trag stoga na Discord ili GitHub kako bismo vam mogli pomoći. Ako vam se tako savjetuje, moÅžete izbrisati podatke aplikacije u nastavku.", - "scan": "Skeniraj", "scan_all_libraries": "Skeniraj sve KnjiÅžnice", "scan_library": "Skeniraj", - "scanning": "Skeniranje", + "scan_settings": "Postavke skeniranja", "scanning_for_album": "Skeniranje albuma...", - "screencast_mode_description": "PrikaÅži indikatore tipkovnice i miÅĄa na zaslonu", - "screencast_mode_title": "Uključi/isključi način prikazivanja zaslona", "search": "PretraÅživanje", "search_albums": "TraÅži albume", "search_by_context": "PretraÅživanje po kontekstu", @@ -1816,10 +1799,6 @@ "search_by_description_example": "Planinarenje u Sapi", "search_by_filename": "PretraÅžujte prema nazivu datoteke ili ekstenziji", "search_by_filename_example": "npr. IMG_1234.JPG ili PNG", - "search_by_full_path": "PretraÅži prema punoj putanji ili mapi", - "search_by_full_path_example": "/Ivan/Projekti/3D_Printanje/2026-07-01 - moÅžete pretraÅžiti Projekti, 3D, Printanje, 2026 itd.", - "search_by_ocr": "PretraÅživanje pomoću OCR-a", - "search_by_ocr_example": "Kava", "search_camera_lens_model": "PretraÅži model objektiva...", "search_camera_make": "PretraÅžite marku kamere...", "search_camera_model": "PretraÅžite model kamere...", @@ -1837,9 +1816,8 @@ "search_filter_location_title": "Odaberi lokaciju", "search_filter_media_type": "Vrsta medija", "search_filter_media_type_title": "Odaberi vrstu medija", + "search_filter_ocr": "PretraÅži OCRom", "search_filter_people_title": "Odaberi osobe", - "search_filter_star_rating": "Ocjena u Zvjezdicama", - "search_filter_tags_title": "Odaberi oznake", "search_for": "TraÅži", "search_for_existing_person": "PotraÅžite postojeću osobu", "search_no_more_result": "Nema viÅĄe rezultata", @@ -1847,13 +1825,25 @@ "search_no_people_named": "Nema osoba s imenom \"{name}\"", "search_no_result": "Nema rezultata, pokuÅĄajte s drugim pojmom za pretraÅživanje ili kombinacijom", "search_options": "Opcije pretraÅživanja", + "search_page_categories": "Kategorije", + "search_page_motion_photos": "Pokretne fotografije", + "search_page_no_objects": "Nema dostupnih informacija o objektima", + "search_page_no_places": "Nema dostupnih informacija o mjestima", + "search_page_screenshots": "Snimke zaslona", "search_page_search_photos_videos": "PretraÅžite svoje fotografije i videozapise", + "search_page_selfies": "Selfiji", + "search_page_things": "Stvari", "search_page_view_all_button": "PrikaÅži sve", + "search_page_your_activity": "VaÅĄa aktivnost", + "search_page_your_map": "VaÅĄa karta", "search_people": "TraÅži ljude", "search_places": "TraÅži mjesta", "search_rating": "PretraÅži po ocjeni...", + "search_result_page_new_search_hint": "Nova pretraga", "search_settings": "Postavke pretraÅživanja", "search_state": "DrÅžava pretraÅživanja...", + "search_suggestion_list_smart_search_hint_1": "Pametna pretraga je omogućena prema zadanim postavkama, za pretraÅživanje metapodataka koristite sintaksu ", + "search_suggestion_list_smart_search_hint_2": "m:vaÅĄ-pojam-pretrage", "search_tags": "TraÅži oznake...", "search_timezone": "PretraÅživanje vremenske zone...", "search_type": "Vrsta pretraÅživanja", @@ -1862,25 +1852,21 @@ "second": "Drugi", "see_all_people": "Vidi sve ljude", "select": "Odaberi", - "select_album": "Odaberi album", "select_album_cover": "Odaberite omot albuma", - "select_albums": "Odaberi albume", "select_all": "Odaberi sve", "select_all_duplicates": "Odaberi sve duplikate", + "select_all_in": "Odaberi sve u {group}", "select_avatar_color": "Odaberi boju avatara", - "select_count": "{count, plural, one {Odaberi #} other {Odaberi #}}", - "select_cutoff_date": "Odaberi granični datum", "select_face": "Odaberi lice", "select_featured_photo": "Odaberi istaknutu fotografiju", "select_from_computer": "Odaberi s računala", "select_keep_all": "Odaberi zadrÅži sve", + "select_library_owner": "Odaberi vlasnika knjiÅžnice", "select_new_face": "Odaberi novo lice", - "select_people": "Odaberi ljude", - "select_person": "Odaberi osobu", "select_person_to_tag": "Odaberite osobu za označavanje", "select_photos": "Odaberi fotografije", - "select_quality": "Odaberi kvalitetu", "select_trash_all": "Odaberi izbriÅĄi sve", + "select_user_for_sharing_page_err_album": "Nije uspjelo kreiranje albuma", "selected": "Odabrano", "selected_count": "{count, plural, =1 {# odabran} few {# odabrana} other {# odabranih}}", "selected_gps_coordinates": "Odaberi GPS koordinate", @@ -1905,39 +1891,47 @@ "set_profile_picture": "Postavi profilnu sliku", "set_slideshow_to_fullscreen": "Postavi prezentaciju na cijeli zaslon", "set_stack_primary_asset": "Postavi kao glavnu stavku", - "setting_image_navigation_enable_subtitle": "Ako je omogućeno, moÅžete prijeći na prethodnu ili sljedeću sliku dodirivanjem krajnje lijeve ili krajnje desne četvrtine zaslona.", - "setting_image_navigation_enable_title": "Dodirnite za Navigiranje", - "setting_image_navigation_title": "Navigacija Slikama", "setting_image_viewer_help": "Preglednik detalja prvo učitava malu sličicu, zatim učitava pregled srednje veličine (ako je omogućen), te na kraju učitava original (ako je omogućen).", "setting_image_viewer_original_subtitle": "Omogućite za učitavanje originalne slike pune rezolucije (velika!). Onemogućite za smanjenje potroÅĄnje podataka (i mreÅžne i na predmemoriji uređaja).", "setting_image_viewer_original_title": "Učitaj originalnu sliku", "setting_image_viewer_preview_subtitle": "Omogućite za učitavanje slike srednje rezolucije. Onemogućite za izravno učitavanje originala ili koriÅĄtenje samo sličice.", "setting_image_viewer_preview_title": "Učitaj sliku za pregled", + "setting_image_viewer_title": "Slike", "setting_languages_apply": "Primijeni", "setting_languages_subtitle": "Promijeni jezik aplikacije", + "setting_notifications_notify_failures_grace_period": "Obavijesti o neuspjehu sigurnosnog kopiranja u pozadini: {duration}", + "setting_notifications_notify_hours": "{count} sati", + "setting_notifications_notify_immediately": "odmah", "setting_notifications_notify_minutes": "{count} minuta", + "setting_notifications_notify_never": "nikad", "setting_notifications_notify_seconds": "{count} sekundi", + "setting_notifications_single_progress_subtitle": "Detaljne informacije o napretku prijenosa po stavki", + "setting_notifications_single_progress_title": "PrikaÅži detaljni napredak sigurnosnog kopiranja u pozadini", "setting_notifications_subtitle": "Prilagodite postavke obavijesti", + "setting_notifications_total_progress_subtitle": "Ukupni napredak prijenosa (zavrÅĄeno/ukupan broj stavki)", + "setting_notifications_total_progress_title": "PrikaÅži ukupni napredak sigurnosnog kopiranja u pozadini", "setting_video_viewer_auto_play_subtitle": "Automatski započni reprodukciju videa kad se otvori", "setting_video_viewer_auto_play_title": "Automatski pokreni videe", "setting_video_viewer_looping_title": "Ponavljanje", "setting_video_viewer_original_video_subtitle": "Prilikom strujanja videozapisa s posluÅžitelja, reproducirajte original čak i kada je dostupna transkodirana verzija. MoÅže doći do međuspremanja. Videozapisi dostupni lokalno reproduciraju se u originalnoj kvaliteti bez obzira na ovu postavku.", "setting_video_viewer_original_video_title": "Forsiraj originalni videozapis", "settings": "Postavke", + "settings_require_restart": "Ponovno pokrenite Immich da biste primijenili ovu postavku", "settings_saved": "Postavke su spremljene", "setup_pin_code": "Postavi PIN kod", "share": "Podijeli", + "share_action_prompt": "{count, plural, one {Podijeljena # stavka} few {Podijeljene # stavke} other {Podijeljeno # stavki}}", + "share_add_photos": "Dodaj fotografije", + "share_assets_selected": "{count, plural, one {# odabran} few {# odabrana} other {# odabrano}}", "share_dialog_preparing": "Priprema...", "share_link": "Podijeli Link", - "share_original": "Koristi izvornu (velika)", - "share_preview": "Koristi sličicu (malu)", - "share_quality_body": "Pritisni i drÅži tipku za dijeljenje kako biste odabrali kvalitetu slike prije dijeljenja.", - "share_quality_title": "Odaberi kvalitetu dijeljenja", "shared": "Podijeljeno", "shared_album_activities_input_disable": "Komentiranje je onemogućeno", "shared_album_activity_remove_content": "ÅŊelite li izbrisati ovu aktivnost?", "shared_album_activity_remove_title": "IzbriÅĄi aktivnost", "shared_album_section_people_action_error": "PogreÅĄka pri napuÅĄtanju/uklanjanju iz albuma", + "shared_album_section_people_action_leave": "Ukloni korisnika iz albuma", + "shared_album_section_people_action_remove_user": "Ukloni korisnika iz albuma", "shared_album_section_people_title": "OSOBE", "shared_by": "Podijelio", "shared_by_user": "Podijelio {user}", @@ -1946,6 +1940,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Preneseno", "shared_link_app_bar_title": "Dijeljene poveznice", "shared_link_clipboard_copied_massage": "Kopirano u međuspremnik", + "shared_link_clipboard_text": "Poveznica: {link}\nLozinka: {password}", "shared_link_create_error": "PogreÅĄka pri kreiranju dijeljene poveznice", "shared_link_custom_url_description": "Pristupite ovom dijeljenom linku pomoću prilagođene URL adrese", "shared_link_edit_description_hint": "Unesite opis dijeljenja", @@ -1981,6 +1976,12 @@ "shared_with_partner": "Podijeljeno s {partner}", "sharing": "Dijeljenje", "sharing_enter_password": "Molimo unesite lozinku za pregled ove stranice.", + "sharing_page_album": "Dijeljeni albumi", + "sharing_page_description": "Kreirajte dijeljene albume za dijeljenje fotografija i videozapisa s ljudima u vaÅĄoj mreÅži.", + "sharing_page_empty_list": "PRAZAN POPIS", + "sharing_sidebar_description": "PrikaÅži poveznicu na Dijeljenje u bočnoj traci", + "sharing_silver_appbar_create_shared_album": "Novi dijeljeni album", + "sharing_silver_appbar_share_partner": "Podijeli s partnerom", "shift_to_permanent_delete": "pritisnite ⇧ za trajno brisanje stavke", "show_album_options": "PrikaÅži opcije albuma", "show_albums": "PrikaÅži albume", @@ -1992,17 +1993,13 @@ "show_in_timeline": "PrikaÅži na vremenskoj crti", "show_in_timeline_setting_description": "PrikaÅži fotografije i videozapise ovog korisnika na vaÅĄoj vremenskoj crti", "show_keyboard_shortcuts": "PrikaÅži tipkovničke prečace", - "show_less": "PrikaÅži manje", "show_metadata": "PrikaÅži metapodatke", - "show_more_fields": "{count, plural, one {PokaÅži joÅĄ # polje} other {PokaÅži joÅĄ # polja}}", "show_or_hide_info": "PrikaÅži ili sakrij informacije", "show_password": "PrikaÅži lozinku", "show_person_options": "PrikaÅži opcije osobe", "show_progress_bar": "PrikaÅži traku napretka", - "show_schema": "PrikaÅži shemu", "show_search_options": "PrikaÅži opcije pretraÅživanja", "show_shared_links": "PrikaÅži dijeljene poveznice", - "show_slideshow_metadata_overlay": "PrikaÅži sloj s informacijama o slici", "show_slideshow_transition": "PrikaÅži prijelaz prezentacije", "show_supporter_badge": "Značka podrÅžavatelja", "show_supporter_badge_description": "PrikaÅži značku podrÅžavatelja", @@ -2014,25 +2011,16 @@ "sign_out": "Odjava", "sign_up": "Registriraj se", "size": "Veličina", - "skip": "Preskoči", "skip_to_content": "Preskoči na sadrÅžaj", "skip_to_folders": "Preskoči na mape", "skip_to_tags": "Preskoči na oznake", "slideshow": "Prezentacija", - "slideshow_body": "Opustite se i gledajte svoje fotografije u dijaprojekciji preko cijelog zaslona.", - "slideshow_metadata_overlay_mode": "SadrÅžaj prekrivanja", - "slideshow_metadata_overlay_mode_description_only": "Samo opis", - "slideshow_metadata_overlay_mode_full": "Puni", - "slideshow_repeat": "Ponavljaj dijaprojekciju", - "slideshow_repeat_description": "Vrati se na početak nakon zavrÅĄetka dijaprojekcije", "slideshow_settings": "Postavke prezentacije", - "slideshow_title": "Dijaprojekcija", - "smart_album": "Pametni album", - "some_assets_already_have_a_location_warning": "Neke od odabranih stavki već imaju lokaciju", "sort_albums_by": "Sortiraj albume po...", "sort_created": "Datum kreiranja", "sort_items": "Broj stavki", "sort_modified": "Datum izmjene", + "sort_newest": "Najnovija fotografija", "sort_oldest": "Najstarija fotografija", "sort_people_by_similarity": "Sortiraj osobe po sličnosti", "sort_recent": "Najnovija fotografija", @@ -2041,6 +2029,7 @@ "stack": "SloÅži", "stack_action_prompt": "{count} sloÅženo", "stack_duplicates": "SloÅži duplikate", + "stack_select_one_photo": "Odaberi jednu glavnu fotografiju za slaganje", "stack_selected_photos": "SloÅži odabrane fotografije", "stacked_assets_count": "{count, plural, one {SloÅžena # stavka} few {SloÅžene # stavke} other {SloÅženo # stavki}}", "stacktrace": "Pracenje stoga", @@ -2049,10 +2038,6 @@ "start_date_before_end_date": "Početni datum mora biti prije krajnjeg datuma", "state": "Stanje", "status": "Status", - "step_delete": "IzbriÅĄi korak", - "step_delete_confirm": "Jeste li sigurni da Åželite izbrisati ovaj korak?", - "step_details": "Detalji koraka", - "steps": "Koraci", "stop_casting": "Zaustavi reprodukciju", "stop_motion_photo": "Zaustavi pokretnu fotografiju", "stop_photo_sharing": "Prestati dijeliti svoje fotografije?", @@ -2069,22 +2054,20 @@ "support": "PodrÅĄka", "support_and_feedback": "PodrÅĄka i povratne informacije", "support_third_party_description": "VaÅĄa Immich instalacija je pakirana od strane treće strane. Problemi koje doÅživljavate mogu biti uzrokovani tim paketom, stoga vas molimo da probleme prvo prijavite njima putem poveznica u nastavku.", - "supporter": "PodrÅžavatelj", "swap_merge_direction": "Zamijeni smjer spajanja", "sync": "Sink.", "sync_albums": "Sinkroniziraj albume", + "sync_albums_manual_subtitle": "Sinkroniziraj sve prenesene videozapise i fotografije u odabrane albume za sigurnosnu kopiju", "sync_local": "Sinkroniziraj lokalno", "sync_remote": "Sinkroniziraj udaljeno", "sync_status": "Status sinkronizacije", "sync_status_subtitle": "Pregledajte i upravljajte sistemom sinkronizacije", "sync_upload_album_setting_subtitle": "Kreiraj i prenesi svoje fotografije i videozapise u odabrane albume na Immichu", - "system_theme": "Tema sustava", - "system_theme_command_description": "Koristi sistemsku temu ({value})", "tag": "Oznaka", "tag_assets": "Označi stavke", "tag_created": "Kreirana oznaka: {tag}", - "tag_face": "Označi lice", "tag_feature_description": "Pregledavanje fotografija i videozapisa grupiranih po logičkim temama oznaka", + "tag_not_found_question": "Nije moguće pronaći oznaku? Napravite novu oznaku.", "tag_people": "Označi osobe", "tag_updated": "AÅžurirana oznaka: {tag}", "tagged_assets": "{count, plural, =1 {Označena # stavka} few {Označene # stavke} other {Označeno # stavki}}", @@ -2099,11 +2082,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Broj stavki po retku ({count})", "theme_setting_colorful_interface_subtitle": "Primijeni primarnu boju na pozadinske povrÅĄine.", "theme_setting_colorful_interface_title": "Å areno sučelje", + "theme_setting_image_viewer_quality_subtitle": "Prilagodite kvalitetu preglednika detalja slike", + "theme_setting_image_viewer_quality_title": "Kvaliteta preglednika slika", "theme_setting_primary_color_subtitle": "Odaberite boju za primarne radnje i naglaske.", "theme_setting_primary_color_title": "Primarna boja", "theme_setting_system_primary_color_title": "Koristi boju sustava", "theme_setting_system_theme_switch": "Automatski (Prati postavke sustava)", - "then": "Onda", + "theme_setting_theme_subtitle": "Odaberite postavku teme aplikacije", + "theme_setting_three_stage_loading_subtitle": "Trostupanjsko učitavanje moÅže poboljÅĄati performanse učitavanja, ali uzrokuje znatno veće opterećenje mreÅže", + "theme_setting_three_stage_loading_title": "Omogući trostupanjsko učitavanje", "they_will_be_merged_together": "Oni ću biti spojeni zajedno", "third_party_resources": "Resursi trećih strana", "time": "Vrijeme", @@ -2128,17 +2115,16 @@ "trash_all": "Stavi sve u smeće", "trash_count": "Smeće {count, number}", "trash_delete_asset": "Premjesti u smeće / IzbriÅĄi stavku", + "trash_emptied": "IspraÅžnjeno smeće", "trash_no_results_message": "Ovdje će se prikazati bačene fotografije i videozapisi.", "trash_page_delete_all": "IzbriÅĄi sve", + "trash_page_empty_trash_dialog_content": "ÅŊelite li isprazniti svoje stavke u smeću? Ove stavke će biti trajno uklonjene iz Immicha", "trash_page_info": "Stavke u smeću bit će trajno izbrisane nakon {days} dana", + "trash_page_no_assets": "Nema stavki u smeću", + "trash_page_restore_all": "Vrati sve", + "trash_page_select_assets_btn": "Odaberi stavke", + "trash_page_title": "Smeće ({count})", "trashed_items_will_be_permanently_deleted_after": "Stavke bačene u smeće trajno će se izbrisati nakon {days, plural, one {# day} other {# days}}.", - "trigger": "Okidač", - "trigger_asset_metadata_extraction": "Ekstrakcija Metapodataka iz Stavki", - "trigger_asset_metadata_extraction_description": "Pokrene se kada se izdvoje EXIF metapodatci stavke", - "trigger_asset_uploaded": "Prijenos Stavke", - "trigger_asset_uploaded_description": "Pokrene se kada se prenese nova stavka", - "trigger_person_recognized": "Osoba Prepoznata", - "trigger_person_recognized_description": "Pokrene se kada se osoba prepozna", "troubleshoot": "RjeÅĄavanje problema", "type": "Vrsta", "unable_to_change_pin_code": "Nije moguće promijeniti PIN kod", @@ -2153,8 +2139,6 @@ "unhide_person": "PrikaÅži osobu", "unknown": "Nepoznato", "unknown_country": "Nepoznata drÅžava", - "unknown_date": "Nepoznat datum", - "unknown_schema": "Nepoznata shema", "unknown_year": "Nepoznata godina", "unlimited": "Neograničeno", "unlink_motion_video": "OdpoveÅži pokretni videozapis", @@ -2167,11 +2151,10 @@ "unsaved_change": "Nespremljena promjena", "unselect_all": "PoniÅĄti odabir svih", "unselect_all_duplicates": "PoniÅĄti odabir svih duplikata", + "unselect_all_in": "PoniÅĄti odabir svih u {group}", "unstack": "Razdvoji", "unstack_action_prompt": "{count} razloÅženo", "unstacked_assets_count": "{count, plural, one {Razdvojena # stavka} few {Razvdvojene # stavke} other {Razdvojeno # stavki}}", - "unsupported_field_type": "NepodrÅžana vrsta polja", - "unsupported_file_type": "Datoteka {file} se ne moÅže učitati jer njezina vrsta {type} nije podrÅžana.", "untagged": "Bez oznaka", "up_next": "Sljedeće", "update_location_action_prompt": "AÅžuriraj lokaciju ({count, plural, one {# odabrane stavke} few {# odabrane stavke} other {# odabranih stavki}}) s:", @@ -2179,9 +2162,9 @@ "updated_password": "Lozinka aÅžurirana", "upload": "Prijenos", "upload_concurrency": "Istovremeni prijenosi", - "upload_day_count": "{date}: {count, plural, one {# prijenos} other {# prijenosa}}", "upload_details": "Detalji prijenosa", - "upload_error_with_count": "PogreÅĄka pri prijenosu za {count, plural, one {# stavku} few {# stavke} other {# stavki}}", + "upload_dialog_info": "ÅŊelite li sigurnosno kopirati odabrane stavke na posluÅžitelj?", + "upload_dialog_title": "Prenesi stavku", "upload_errors": "Prijenos zavrÅĄen s {count, plural, one {# greÅĄkom} few {# greÅĄke} other {# greÅĄaka}}, osvjeÅžite stranicu da biste vidjeli nove prenesene stavke.", "upload_finished": "Prijenos zavrÅĄen", "upload_progress": "Preostalo {remaining, number} - Obrađeno {processed, number}/{total, number}", @@ -2190,21 +2173,14 @@ "upload_status_errors": "GreÅĄke", "upload_status_uploaded": "Preneseno", "upload_success": "Prijenos uspjeÅĄan, osvjeÅžite stranicu da biste vidjeli nove prenesene stavke.", - "upload_to_album_body": "Za korisnike koji ne koriste ručni prijenos: sada moÅžete prenjeti lokalne fotografije izravno u album tijekom samog prijenosa - viÅĄe nema potrebe za naknadnim organiziranjem fotografija u album nakon prijenosa.", - "upload_to_album_title": "Prenesi izravno u album", "upload_to_immich": "Prenesi na Immich ({count})", "uploading": "Prijenos u tijeku", "uploading_media": "Prijenos medija", - "uploads": "Prijenosi", - "uploads_count": "{count, plural, one {# prijenos} other {# prijenosa}}", "url": "URL", "usage": "KoriÅĄtenje", "use_biometric": "Koristi biometriju", - "use_browser_locale": "Koristi lokalne postavke preglednika", - "use_browser_locale_description": "Oblikujte datume, vremena i brojeve prema postavkama lokalizacije vaÅĄeg preglednika", - "use_current_connection": "Koristi trenutnu vezu", + "use_current_connection": "koristi trenutnu vezu", "use_custom_date_range": "Koristi prilagođeni raspon datuma", - "use_template": "Koristi predloÅžak", "user": "Korisnik", "user_has_been_deleted": "Ovaj korisnik je izbrisan.", "user_id": "ID korisnika", @@ -2214,6 +2190,7 @@ "user_privacy": "Privatnost korisnika", "user_purchase_settings": "Kupnja", "user_purchase_settings_description": "Upravljajte svojom kupnjom", + "user_role_set": "Postavi {user} kao {role}", "user_usage_detail": "Detalji koriÅĄtenja korisnika", "user_usage_stats": "Statistika koriÅĄtenja računa", "user_usage_stats_description": "Pregledajte statistiku koriÅĄtenja računa", @@ -2232,7 +2209,6 @@ "video": "Videozapis", "video_hover_setting": "Reproduciraj sličicu videozapisa pri prelasku miÅĄem", "video_hover_setting_description": "Reproduciraj sličicu videozapisa kada miÅĄ prelazi preko stavke. Čak i kada je onemogućeno, reprodukcija se moÅže pokrenuti prelaskom miÅĄa preko ikone za reprodukciju.", - "video_quality": "Kvaliteta videozapisa", "videos": "Videozapisi", "videos_count": "{count, plural, =1 {# Videozapis} few {# Videozapisa} other {# Videozapisa}}", "view": "Prikaz", @@ -2243,49 +2219,31 @@ "view_details": "PrikaÅži pojedinosti", "view_in_timeline": "PrikaÅži na vremenskoj crti", "view_link": "PrikaÅži poveznicu", + "view_links": "PrikaÅži poveznice", "view_name": "Prikaz", "view_next_asset": "PrikaÅži sljedeću stavku", "view_previous_asset": "PrikaÅži prethodnu stavku", "view_qr_code": "PrikaÅži QR kod", "view_similar_photos": "Pregledaj slične slike", "view_stack": "PrikaÅži sloÅžene", + "view_user": "PrikaÅži korisnika", "viewer_remove_from_stack": "Ukloni iz sloÅženih", - "visibility": "Vidljivost", + "viewer_stack_use_as_main_asset": "Koristi kao glavnu stavku", + "viewer_unstack": "Razdvoji", "visibility_changed": "Vidljivost promijenjena za {count, plural, =1 {# osobu} few {# osobe} other {# osoba}}", - "visual": "Vizualno", "waiting": "Čekanje", "waiting_count": "Čekanje:{count}", "warning": "Upozorenje", "week": "Tjedan", "welcome": "DobrodoÅĄli", "welcome_to_immich": "DobrodoÅĄli u Immich", - "whats_new": "Å to je novo", - "whats_new_settings_subtitle": "Pogledaj ÅĄto ima novo u Immichu", - "whats_new_version": "Verzija {version}", - "when": "Kada", - "width": "Å irina", "wifi_name": "Naziv Wi-Fi mreÅže", - "workflow": "Radni proces", - "workflow_delete_prompt": "Jeste li sigurni da Åželite izbrisati ovaj radni proces?", - "workflow_deleted": "Radni proces izbrisan", - "workflow_description": "Opis radnog procesa", - "workflow_info": "Informacije o radnog procesu", - "workflow_json": "JSON radnog procesa", - "workflow_json_help": "Uredite konfiguraciju radnog procesa u JSON formatu. Promjene će se sinkronizirati s vizualnim alatom za izradu.", - "workflow_name": "Naziv radnog procesa", - "workflow_navigation_prompt": "Jeste li sigurni da Åželite izaći bez spremanja promjena?", - "workflow_summary": "SaÅžetak radnog procesa", - "workflow_templates": "PredloÅĄci za radne procese", - "workflow_update_success": "Radni proces uspjeÅĄno aÅžuriran", - "workflows": "Radni procesi", - "workflows_help_text": "Radni procesi automatiziraju radnje nad vaÅĄim stavkama na temelju okidača i filtara", "wrong_pin_code": "Krivi PIN kod", - "x_of_total": "{x}/{total}", "year": "Godina", "years_ago": "prije {years, plural, =1 {# godinu} few {# godine} other {# godina}}", "yes": "Da", "you_dont_have_any_shared_links": "Nemate nijednu dijeljenu poveznicu", "your_wifi_name": "Naziv vaÅĄe Wi-Fi mreÅže", - "zero_to_clear_rating": "pritisni 0 za brisanje ocjene stavke", - "zoom_image": "Povećaj sliku" + "zoom_image": "Povećaj sliku", + "zoom_to_bounds": "Zumiraj do granica" } diff --git a/i18n/hu.json b/i18n/hu.json index 9ab0181f3c..c4788b9915 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -10,31 +10,39 @@ "active": "Aktív", "active_count": "Aktív: {count}", "activity": "TevÊkenysÊg", + "activity_changed": "A tevÊkenysÊg {enabled, select, true {bekapcsolva} other {kikapcsolva}}", "add": "HozzÃĄadÃĄs", "add_a_description": "LeírÃĄs megadÃĄsa", "add_a_location": "Helyszín hozzÃĄadÃĄsa", "add_a_name": "NÊv megadÃĄsa", "add_a_title": "Cím megadÃĄsa", "add_action": "MÅąvelet hozzÃĄadÃĄsa", + "add_action_description": "Kattints ide egy vÊgrehajtandÃŗ mÅąvelet hozzÃĄadÃĄsÃĄhoz", "add_assets": "Elemek hozzÃĄadÃĄsa", "add_birthday": "SzÃŧletÊsnap megadÃĄsa", "add_endpoint": "VÊgpont hozzÃĄadÃĄsa", "add_exclusion_pattern": "KihagyÃĄsi minta (pattern) hozzÃĄadÃĄsa", + "add_filter": "SzÅąrő hozzÃĄadÃĄsa", + "add_filter_description": "Kattints ide egy szÅąrÊsi feltÊtel hozzÃĄadÃĄsÃĄhoz", "add_location": "Helyszín megadÃĄsa", + "add_more_users": "TovÃĄbbi felhasznÃĄlÃŗk hozzÃĄadÃĄsa", "add_partner": "Partner hozzÃĄadÃĄsa", + "add_path": "ElÊrÊsi Ãētvonal megadÃĄsa", "add_photos": "FotÃŗk hozzÃĄadÃĄsa", - "add_step": "LÊpÊs hozzÃĄadÃĄsa", "add_tag": "Címke hozzÃĄadÃĄsa", "add_to": "HozzÃĄadÃĄs ideâ€Ļ", "add_to_album": "FelvÊtel albumba", "add_to_album_bottom_sheet_added": "HozzÃĄadva a(z) \"{album}\" albumhoz", "add_to_album_bottom_sheet_already_exists": "MÃĄr benne van a(z) \"{album}\" albumban", "add_to_album_bottom_sheet_some_local_assets": "NÊhÃĄny helyi elem nem adhatÃŗ hozzÃĄ az albumhoz", + "add_to_album_toggle": "{album} kijelÃļlÊsÊnek vÃĄltÃĄsa", "add_to_albums": "HozzÃĄadÃĄs albumokhoz", "add_to_albums_count": "HozzÃĄadÃĄs albumokhoz ({count})", "add_to_bottom_bar": "HozzÃĄadÃĄs ehhez", + "add_to_shared_album": "FelvÊtel megosztott albumba", "add_upload_to_stack": "FeltÃļltÊs hozzÃĄadÃĄsa csoporthoz", "add_url": "URL hozzÃĄadÃĄsa", + "add_workflow_step": "Folyamat lÊpÊs hozzÃĄadÃĄsa", "added_to_archive": "HozzÃĄadva az archívumhoz", "added_to_favorites": "HozzÃĄadva a kedvencekhez", "added_to_favorites_count": "{count, number} hozzÃĄadva a kedvencekhez", @@ -73,7 +81,6 @@ "cron_expression_description": "A beolvasÃĄsi időkÃļz beÃĄllítÃĄsa a cron formÃĄtummal. TovÃĄbbi informÃĄciÃŗÃŠrt lÃĄsd pl. Crontab Guru", "cron_expression_presets": "Cron kifejezÊs előbeÃĄllítÃĄs", "disable_login": "BejelentkezÊs letiltÃĄsa", - "download_csv": "CSV letÃļltÊse", "duplicate_detection_job_description": "GÊpi tanulÃĄs futtatÃĄsa a hasonlÃŗ elemek megtalÃĄlÃĄsa cÊljÃĄbÃŗl. Ez az Okos keresÊs funkciÃŗt hasznÃĄlja", "exclusion_pattern_description": "A kihagyÃĄsi mintÃĄk (pattern) hasznÃĄlatakor a mintÃĄnak megfelelő fÃĄjlok vagy mappÃĄk ÃĄt lesznek ugorva a kÊptÃĄr ÃĄtfÊsÃŧlÊsekor. Akkor hasznos, ha a mappÃĄkban vannak olyan fÃĄjlok is, amelyeket nem szeretnÊl importÃĄlni, pl. nyers (RAW) fÃĄjlok.", "export_config_as_json_description": "Jelenlegi rendszer konfigurÃĄciÃŗ letÃļltÊse JSON fÃĄjlkÊnt", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "BÊlyegkÊp minősÊge 1-100 kÃļzÃļtt. A magasabb szÃĄm jobb minősÊget, de nagyobb fÃĄjlmÊretet eredmÊnyez Ês belassíthatja az alkalmazÃĄst.", "image_thumbnail_title": "BÊlyegkÊp beÃĄllítÃĄsok", "import_config_from_json_description": "Rendszer konfigurÃĄciÃŗ importÃĄlÃĄsa JSON fÃĄjlbÃŗl", - "integrity_checks_checksum_files": "EllenőrzőÃļsszeg fÃĄjlok", - "integrity_checks_checksum_files_description": "Az ellenőrzőÃļsszeg-ellenőrzÊs beÃĄllítÃĄsa", - "integrity_checks_checksum_files_enable_description": "EllenőrzőÃļsszeg-ellenőrzÊs bekapcsolÃĄsa", - "integrity_checks_checksum_files_percentage_limit": "SzÃĄzalÊkos limit", - "integrity_checks_checksum_files_percentage_limit_description": "Állítsd be 0,01 Ês 1 kÃļzÃļtti ÊrtÊkre azt a maximÃĄlis szÃĄzalÊkot, amely meghatÃĄrozza, hogy az ellenőrzőÃļsszeg-ellenőrzÊs az egyes időkÃļzÃļkben milyen gyakorisÃĄggal fusson.", - "integrity_checks_checksum_files_time_limit": "Idő limit", - "integrity_checks_checksum_files_time_limit_description": "Állítsd be, hogy az egyes időkÃļzÃļkben az ellenőrzőÃļsszeg-ellenőrzÊs legfeljebb mennyi ideig futhasson. (ms)", - "integrity_checks_missing_files": "HiÃĄnyzÃŗ fÃĄjlok", - "integrity_checks_missing_files_description": "GyakorisÃĄg beÃĄllítÃĄsa, valamint a hiÃĄnyzÃŗ fÃĄjlok ellenőrzÊsÊnek engedÊlyezÊse vagy letiltÃĄsa", - "integrity_checks_missing_files_enable_description": "A hiÃĄnyzÃŗ fÃĄjlok ellenőrzÊsÊnek engedÊlyezÊse", - "integrity_checks_settings": "IntegritÃĄs ellenőrzÊsek", - "integrity_checks_settings_description": "IntegritÃĄs-ellenőrzÊsek gyakorisÃĄgÃĄnak kezelÊse", - "integrity_checks_untracked_files": "Nem kÃļvetett fÃĄjlok", - "integrity_checks_untracked_files_description": "GyakorisÃĄg beÃĄllítÃĄsa, valamint nem kÃļvetett fÃĄjlok ellenőrzÊsÊnek engedÊlyezÊse vagy letiltÃĄsa", - "integrity_checks_untracked_files_enable_description": "Nem kÃļvetett fÃĄjlok ellenőrzÊsÊnek engedÊlyezÊse", "job_concurrency": "{job} pÃĄrhuzamosan", "job_created": "Feladat lÊtrehozva", "job_not_concurrency_safe": "Ez a feladat nem pÃĄrhuzamossÃĄg-biztos.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Okos keresÊs engedÊlyezÊse", "machine_learning_smart_search_enabled_description": "Ha ki van kapcsolva, a kÊpek nem lesznek ÃĄtalakítva Okos keresÊshez.", "machine_learning_url_description": "GÊpi tanulÃĄs szerver URL címe. Ha tÃļbbi, mint egy URL van megadva, mindegyik szervert egyenkÊnt prÃŗbÃĄlja meg, amíg az egyik sikeresen nem vÃĄlaszol, sorrendben az elsőtől az utÃŗlsÃŗig. A nem elÊrhető szervereket ÃĄtmenetileg figyelmen kívÃŧl lesznek hagyva, amíg Ãējra online nem lesznek.", - "maintenance_backup_management": "backup kezelÊs", "maintenance_delete_backup": "BiztonsÃĄgi mentÊs tÃļrlÊse", "maintenance_delete_backup_description": "A fÃĄjl tÃļrlÊse nem visszafordíthatÃŗ.", "maintenance_delete_error": "A biztonsÃĄgi mentÊs tÃļrlÊse sikertelen volt.", - "maintenance_integrity_check": "Ellenőrizze", - "maintenance_integrity_check_all": "Mind ellenőrzÊse", - "maintenance_integrity_checksum_mismatch": "EllenőrzőÃļsszeg-eltÊrÊs", - "maintenance_integrity_checksum_mismatch_description": "Azon fÃĄjlok, amelyek lemezen tÃĄrolt ellenőrzőÃļsszege eltÊr az Immich adatbÃĄzisÃĄban szereplőtől.", - "maintenance_integrity_checksum_mismatch_job": "Az ellenőrzőÃļsszegek eltÊrÊseinek ellenőrzÊse", - "maintenance_integrity_checksum_mismatch_refresh_job": "Az ellenőrzőÃļsszeg-eltÊrÊsről szÃŗlÃŗ jelentÊsek frissítÊse", - "maintenance_integrity_missing_file": "HiÃĄnyzÃŗ fÃĄjlok", - "maintenance_integrity_missing_file_description": "Az Immich adatbÃĄzisÃĄban szereplő, de a fÃĄjlrendszerben nem lÊtező fÃĄjlok.", - "maintenance_integrity_missing_file_job": "HiÃĄnyzÃŗ fÃĄjlok ellenőrzÊse", - "maintenance_integrity_missing_file_refresh_job": "HiÃĄnyzÃŗ fÃĄjl riport frissítÊse", - "maintenance_integrity_report": "IntegritÃĄsi jelentÊs", - "maintenance_integrity_untracked_file": "Nem figyelt/kÃļvetett fÃĄjlok", - "maintenance_integrity_untracked_file_description": "Az Immich kÃļnyvtÃĄraiban talÃĄlhatÃŗ, de a nyilvÃĄntartÃĄsÃĄban nem szereplő fÃĄjlok.", - "maintenance_integrity_untracked_file_job": "Nem kÃļvetett fÃĄjlok ellenőrzÊse", - "maintenance_integrity_untracked_file_refresh_job": "Nem nyomon kÃļvetett fÃĄjlok riportjÃĄnak frissítÊse", "maintenance_restore_backup": "BiztonsÃĄgi mentÊs visszaÃĄllítÃĄsa", "maintenance_restore_backup_description": "Az Immich adatai tÃļrÃļlve lesznek Ês a kivÃĄlasztott biztonsÃĄgi mentÊs kerÃŧl visszaÃĄllítÃĄsra. Egy biztonsÃĄgi mentÊs kÊszÃŧl, mielőtt folytatnÃĄd.", "maintenance_restore_backup_different_version": "Ez a biztonsÃĄgi mentÊs az Immich egy mÃĄsik verziÃŗjÃĄval kÊszÃŧlt!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Email ÊrtesítÊsek engedÊlyezÊse", "notification_settings": "ÉrtesítÊs beÃĄllítÃĄsok", "notification_settings_description": "ÉrtesítÊsi Ês email beÃĄllítÃĄsok kezelÊse", - "oauth_allow_insecure_requests": "Nem biztonsÃĄgos kÊrÊsek engedÊlyezÊse", - "oauth_allow_insecure_requests_description": "FIGYELEM: Ez letiltja a TLS-tanÃēsítvÃĄnyok ellenőrzÊsÊt az [OAuth] kÊrÊsek esetÊben, Ês ezÃĄltal MITM tÃĄmadÃĄsoknak tehet ki tÊged.", "oauth_auto_launch": "Automatikus indítÃĄs", "oauth_auto_launch_description": "Az OAuth bejelentkezÊsi folyamat automatikus indítÃĄsa a bejelentkezÊsi oldal megnyitÃĄsakor", "oauth_auto_register": "Automatikus regisztrÃĄciÃŗ", @@ -300,11 +274,9 @@ "oauth_button_text": "Gomb szÃļvege", "oauth_client_secret_description": "Bizalmas kliens esetÊn kÃļtelező, vagy ha az OAuth szolgÃĄltatÃŗ nem tÃĄmogatja a PKCE-t (Proof Key for Code Exchange) nyilvÃĄnos kliensnÊl.", "oauth_enable_description": "BejelentkezÊs OAuth hasznÃĄlatÃĄval", - "oauth_end_session_url_description": "A felhasznÃĄlÃŗ kijelentkezÊsekor irÃĄnyítsd ÃĄt erre az URI-ra.", "oauth_mobile_redirect_uri": "Mobil ÃĄtirÃĄnyítÃĄsi URI", "oauth_mobile_redirect_uri_override": "Mobil ÃĄtirÃĄnyítÃĄsi URI felÃŧlírÃĄs", "oauth_mobile_redirect_uri_override_description": "EngedÊlyezd, ha az OAuth szolgÃĄltatÃŗ tiltja a mobil URI-t, mint pÊldÃĄul ''{callback}''", - "oauth_prompt_description": "Parancssori paramÊter (pl. select_account, login, consent)", "oauth_role_claim": "SzerepkÃļr kÊrelem", "oauth_role_claim_description": "Automatikusan adjon rendszergazdai hozzÃĄfÊrÊst ennek az igÊnylÊsnek a jelenlÊte alapjÃĄn. A kÊrelem lehet „felhasznÃĄlÃŗâ€ vagy „rendszergazda”.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Összes kÊptÃĄr frissítÊse", "registration": "Admin regisztrÃĄciÃŗ", "registration_description": "Mivel ez az első felhasznÃĄlÃŗ a rendszerben, ezÊrt te leszel az Admin, aki az adminisztratív teendőkÊrt felelős Ês tovÃĄbbi felhasznÃĄlÃŗkat tud lÊtrehozni.", - "release_channel_release_candidate": "KiadÃĄsra jelÃļlt verziÃŗ", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Sikertelen feladatok eltÃĄvolítÃĄsa", "require_password_change_on_login": "KÃļtelező jelszÃŗmÃŗdosítÃĄs az első bejelentkezÊskor", "reset_settings_to_default": "BeÃĄllítÃĄsok visszaÃĄllítÃĄsa az alapÊrtelmezettre", @@ -365,7 +335,7 @@ "storage_template_migration_job": "TÃĄrhely sablon migrÃĄlÃĄsa", "storage_template_more_details": "TovÃĄbbi rÊszletekÊrt erről a funkciÃŗrÃŗl lÃĄsd a tÃĄrhely sablon Ês annak kÃļvetkezmÊnyeit a dokumentÃĄciÃŗban", "storage_template_onboarding_description_v2": "A funkciÃŗ engedÊlyezÊsÊvel automatikusan, a felhasznÃĄlÃŗ ÃĄltal definiÃĄlt sablon alapjÃĄn lesznek rendezve a fÃĄjlok. TÃļbb informÃĄciÃŗÃŠrt lÃĄsd a dokumentÃĄciÃŗt.", - "storage_template_path_length": "Útvonal hozzÃĄvetőleges maximÃĄlis hossza: {length, number}/{limit, number}", + "storage_template_path_length": "Útvonal hozzÃĄvetőleges maximÃĄlis hossza: {length, number}{limit, number}", "storage_template_settings": "TÃĄrhely sablon", "storage_template_settings_description": "A feltÃļltÃļtt elemek mappaszerkezetÊnek Ês fÃĄjl elnevezÊsÊnek kezelÊse", "storage_template_user_label": "A felhasznÃĄlÃŗ tÃĄrhely címkÊje {label}", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Csak VAAPI vagy QSV esetÊn. BeÃĄllítja a hardveres ÃĄtkÃŗdolÃĄshoz hasznÃĄlt DRI node-ot.", "transcoding_preset_preset": "Előre beÃĄllított (-preset)", "transcoding_preset_preset_description": "TÃļmÃļrítÊsi sebessÊg. A lassabb beÃĄllítÃĄsok kisebb fÃĄjlokat hoznak lÊtre Ês nÃļvelik a minősÊget az adott bitrÃĄta mellett. A VP9 kÃŗdolÃĄs figyelmen kívÃŧl hagyja a 'gyorsabb (faster)'-nÊl nagyobb sebessÊgeket.", - "transcoding_realtime": "ValÃŗs idejÅą transzkÃŗdolÃĄs [KÍSÉRLETI]", - "transcoding_realtime_description": "EngedÊlyezi a videÃŗ lejÃĄtszÃĄsa kÃļzben a valÃŗs idejÅą transzkÃŗdolÃĄst. LehetővÊ teszi a minősÊg kivÃĄlasztÃĄsÃĄt, de akadozÃĄsokat Ês nagyobb visszajÃĄtszÃĄsi kÊsleltetÊst eredmÊnyezhet a szerver teljesítmÊnyÊtől fÃŧggően.", - "transcoding_realtime_enabled": "ValÃŗs idejÅą transzkÃŗdolÃĄs bekapcsolÃĄsa", - "transcoding_realtime_enabled_description": "Ha ki van kapcsolva, a szerver nem fogja elfogadni az Ãēj, valÃŗs idejÅą transzkÃŗdolÃĄsÃē kÊrÊseket.", - "transcoding_realtime_resolutions": "FelbontÃĄsok", - "transcoding_realtime_resolutions_description": "A valÃŗs idejÅą ÃĄtkÃŗdolÃĄshoz kínÃĄlt felbontÃĄsok. A nagyobb felbontÃĄsok lejÃĄtszÃĄsi problÊmÃĄkhoz vezethetnek, ha a szerver nem tudja elÊg gyorsan feldolgozni őket.", - "transcoding_realtime_video_codecs": "VideÃŗkodekek", - "transcoding_realtime_video_codecs_description": "A valÃŗs idejÅą transzkÃŗdolÃĄshoz elÊrhető videÃŗkodekek. LejÃĄtszÃĄs sorÃĄn a kliensek a legmegfelelőbb ÃĄltaluk tÃĄmogatott opciÃŗt vÃĄlasztjÃĄk ki. Az AV1 hatÊkonyabb a HEVC-nÊl, a HEVC pedig hatÊkonyabb a H.264-nÊl. Hardveres gyorsítÃĄs hasznÃĄlatakor csak azokat a kodekeket vÃĄlaszd ki, amelyeket a gyorsítÃŗ kÊpes kÃŗdolni. Szoftveres transzkÃŗdolÃĄs esetÊn vedd figyelembe, hogy a H.264 gyorsabb az AV1-nÊl, az AV1 pedig gyorsabb a HEVC-nÊl.", "transcoding_reference_frames": "Referencia kÊpkockÃĄk", "transcoding_reference_frames_description": "A hivatkozott kÊpkockÃĄk szÃĄma egy kÊpkocka tÃļmÃļrítÊsÊhez. Magasabb ÊrtÊkek nÃļvelik a tÃļmÃļrítÊsi hatÊkonysÃĄgot, de lelassítjÃĄk a kÃŗdolÃĄsi folyamatot. 0 esetÊn a szoftver magÃĄnak ÃĄllítja be az ÊrtÊket.", "transcoding_required_description": "Csak az el nem fogadott formÃĄtumÃē videÃŗkat", @@ -478,8 +440,6 @@ "user_settings_description": "FelhasznÃĄlÃŗ beÃĄllítÃĄsok kezelÊse", "user_successfully_removed": "{email} felhasznÃĄlÃŗ sikeresen eltÃĄvolítva.", "users_page_description": "Admin felhasznÃĄlÃŗk oldala", - "version_check_channel": "FrissítÊs csatorna", - "version_check_channel_description": "VÃĄlaszd ki az frissítÊs csatornÃĄt, hogy ÊrtesÃŧlhess a legÃējabb frissítÊsekről", "version_check_enabled_description": "Új verziÃŗk elÊrhetősÊgÊnek ellenőrzÊse", "version_check_implications": "Az Ãēj verziÃŗk ellenőrzÊse időszakos kommunikÃĄciÃŗt igÊnyel a {server} oldallal", "version_check_settings": "VerziÃŗ ellenőrzÊs", @@ -494,14 +454,18 @@ "advanced_settings_clear_image_cache": "FÊnykÊpek gyorsítÃŗtÃĄrÃĄnak kiÃŧrítÊse", "advanced_settings_clear_image_cache_error": "FÊnykÊpek gyorsítÃŗtÃĄrÃĄnak kiÃŧrítÊse sikertelen", "advanced_settings_clear_image_cache_success": "{size} sikeresen felszabadítva", + "advanced_settings_enable_alternate_media_filter_subtitle": "Ezzel a beÃĄllítÃĄssal a szinkronizÃĄlÃĄs sorÃĄn alternatív kritÊriumok alapjÃĄn szÅąrheted a fÃĄjlokat. Csak akkor prÃŗbÃĄld ki, ha problÊmÃĄid vannak azzal, hogy az alkalmazÃĄs nem ismeri fel az Ãļsszes albumot.", + "advanced_settings_enable_alternate_media_filter_title": "[KÍSÉRLETI] Alternatív eszkÃļz album szinkronizÃĄlÃĄsi szÅąrő hasznÃĄlata", "advanced_settings_log_level_title": "NaplÃŗzÃĄs szintje: {level}", - "advanced_settings_prefer_remote_subtitle": "NÊhÃĄny eszkÃļz fÃĄjdalmasan lassan tÃļlti be az eszkÃļzÃļn lÊvő indexkÊpeket. AktivÃĄld ezt a beÃĄllítÃĄst, hogy inkÃĄbb a tÃĄvoli (a szerveren lÊvő) indexkÊpek kerÃŧljenek betÃļltÊsre helyettÃŧk.", + "advanced_settings_prefer_remote_subtitle": "NÊhÃĄny eszkÃļz fÃĄjdalmasan lassan tÃļlti be az eszkÃļzÃļn lÊvő indexkÊpeket. Ez a beÃĄllítÃĄs inkÃĄbb a tÃĄvoli kÊpeket (a szerverről) tÃļlti be helyettÃŧk.", "advanced_settings_prefer_remote_title": "TÃĄvoli kÊpek előnyben rÊszesítÊse", "advanced_settings_proxy_headers_subtitle": "Add meg azokat a proxy fejlÊceket, amiket az app elkÃŧldjÃļn minden hÃĄlÃŗzati kÊrÊsnÊl", "advanced_settings_proxy_headers_title": "Egyedi proxy fejlÊcek [KÍSÉRLETI]", - "advanced_settings_readonly_mode_subtitle": "Bekapcsol egy írÃĄsvÊdett mÃŗdot, ahol csak fotÃŗkat nÊzni lehetsÊges. Egyebek, mint pÊldÃĄul tÃļbb kÊp kivÃĄlasztÃĄsa, megosztÃĄs, kivetítÊs Ês tÃļrlÊs ilyenkor ki vannak kapcsolva. Be-/kikapcsolhatÃŗ a fő kÊpernyőn a felhasznÃĄlÃŗ profil fotÃŗjÃĄn tÃļrtÊnő hosszÃē ÊrintÊssel", + "advanced_settings_readonly_mode_subtitle": "Bekapcsol egy írÃĄsvÊdett mÃŗdot ahol csak fotÃŗkat nÊzni lehetsÊges, egyebek, mint tÃļbb kÊp kivÃĄlasztÃĄsa, megosztÃĄs, kivetítÊs Ês tÃļrlÊs ki vannak kapcsolva. Ki/bekapcsolhatÃŗ a felhasznÃĄlÃŗ ikonjÃĄrÃŗl a fő kÊpernyőn", "advanced_settings_readonly_mode_title": "ÍrÃĄsvÊdett mÃŗd", - "advanced_settings_sync_remote_deletions_subtitle": "Automatikusan tÃļrÃļl vagy visszaÃĄllít egy elemet ezen az eszkÃļzÃļn, ha az adott mÅąveletet a weben hajtottÃĄk vÊgre", + "advanced_settings_self_signed_ssl_subtitle": "Nem ellenőrzi a szerver SSL tanÃēsítvÃĄnyÃĄt. ÖnalÃĄÃ­rt tanÃēsítvÃĄny esetÊn szÃŧksÊges beÃĄllítÃĄs.", + "advanced_settings_self_signed_ssl_title": "ÖnalÃĄÃ­rt SSL tanÃēsítvÃĄnyok engedÊlyezÊse [KÍSÉRLETI]", + "advanced_settings_sync_remote_deletions_subtitle": "Automatikusan tÃļrÃļlni vagy visszaÃĄllítani egy elemet ezen az eszkÃļzÃļn, ha az adott mÅąveletet a weben hajtottÃĄk vÊgre", "advanced_settings_sync_remote_deletions_title": "TÃĄvoli tÃļrlÊsek szinkronizÃĄlÃĄsa [KÍSÉRLETI]", "advanced_settings_tile_subtitle": "HaladÃŗ felhasznÃĄlÃŗi beÃĄllítÃĄsok", "advanced_settings_troubleshooting_subtitle": "TovÃĄbbi funkciÃŗk engedÊlyezÊse hibaelhÃĄrítÃĄs cÊljÃĄbÃŗl", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Biztos, hogy ki szeretnÊd tÃļrÃļlni a(z) {album} albumot?", "album_delete_confirmation_description": "Amennyiben ez egy megosztott album, a tÃļbbi felhasznÃĄlÃŗ sem fog tudni tÃļbbÊ hozzÃĄfÊrni.", "album_deleted": "Album tÃļrÃļlve", + "album_info_card_backup_album_excluded": "KIHAGYVA", + "album_info_card_backup_album_included": "BELEÉRTVE", "album_info_updated": "Album infÃŗ frissítve", + "album_leave": "KilÊpsz az albumbÃŗl?", + "album_leave_confirmation": "Biztos, hogy ki szeretnÊl lÊpni a(z) {album} albumbÃŗl?", "album_name": "Album nÊv", "album_options": "Album beÃĄllítÃĄsok", "album_remove_user": "FelhasznÃĄlÃŗ tÃļrlÊse?", "album_remove_user_confirmation": "Biztos, hogy el szeretnÊd tÃĄvolítani {user} felhasznÃĄlÃŗt?", "album_search_not_found": "Nem talÃĄlhatÃŗ a keresÊsnek megfelelő album", + "album_selected": "Album kivÃĄlasztva", "album_share_no_users": "Úgy tÅąnik, hogy mÃĄr minden felhasznÃĄlÃŗval megosztottad ezt az albumot, vagy nincs senki, akivel meg tudnÃĄd osztani.", "album_summary": "Album ÃļsszefogalalÃŗ", "album_updated": "Album frissÃŧlt", "album_updated_setting_description": "KÃŧldjÃļn email Êrtesítőt, amikor egy megosztott albumhoz Ãēj elemeket adnak hozzÃĄ", "album_upload_assets": "Elemek feltÃļltÊse Ês albumhoz adÃĄsa", + "album_user_left": "KilÊptÊl a(z) {album} albumbÃŗl", + "album_user_removed": "{user} eltÃĄvolítva", + "album_viewer_appbar_delete_confirm": "Biztos, hogy tÃļrÃļlni szeretnÊd ezt az albumot?", "album_viewer_appbar_share_err_delete": "Az album tÃļrlÊse sikertelen", + "album_viewer_appbar_share_err_leave": "Nem sikerÃŧlt kilÊpni az albumbÃŗl", + "album_viewer_appbar_share_err_remove": "NÊhÃĄny elemet nem sikerÃŧlt tÃļrÃļlni az albumbÃŗl", + "album_viewer_appbar_share_err_title": "Az album ÃĄtnevezÊse sikertelen", + "album_viewer_appbar_share_leave": "KilÊpÊs az albumbÃŗl", + "album_viewer_appbar_share_to": "MegosztÃĄs ide", "album_viewer_page_share_add_users": "FelhasznÃĄlÃŗk hozzÃĄadÃĄsa", "album_with_link_access": "A link birtokÃĄban bÃĄrki lÃĄthatja a fotÃŗkat Ês a szemÊlyeket ebben az albumban.", "albums": "Albumok", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "AlapÊrtelmezett sorrendezÊs Ãēj albumok lÊtrehozÃĄsÃĄnÃĄl.", "albums_feature_description": "MÃĄsokkal megoszthatÃŗ elemek gyÅąjtemÊnye.", "albums_on_device_count": "Albumok az eszkÃļzÃļn ({count})", + "albums_selected": "{count, plural, one {# album kivÃĄlasztva} other {# album kivÃĄlasztva}}", "all": "Mind", "all_albums": "Minden album", "all_people": "Minden szemÊly", "all_photos": "Minden fÊnykÊp", "all_videos": "Minden videÃŗ", "allow_dark_mode": "SÃļtÊt tÊma engedÊlyezÊse", + "allow_edits": "MÃŗdosítÃĄsok engedÊlyezÊse", "allow_public_user_to_download": "EngedÊlyezi a letÃļltÊst publikus felhasznÃĄlÃŗ szÃĄmÃĄra", "allow_public_user_to_upload": "EngedÊlyezi a feltÃļltÊst publikus felhasznÃĄlÃŗ szÃĄmÃĄra", "allowed": "EngedÊlyezett", @@ -549,12 +528,14 @@ "always_keep": "Tartsa meg mindig", "always_keep_photos_hint": "A tÃĄrhely-felszabadítÃĄs nem tÃļrli az eszkÃļzÃļn talÃĄlhatÃŗ fÊnykÊpeket.", "always_keep_videos_hint": "A tÃĄrhely-felszabadítÃĄs nem tÃļrli az eszkÃļzÃļn talÃĄlhatÃŗ videÃŗkat.", + "anti_clockwise": "ÓramutatÃŗ jÃĄrÃĄsÃĄval ellentÊtes irÃĄny", "api_key": "API kulcs", "api_key_description": "Ez csak most az egyszer jelenik meg. Az ablak bezÃĄrÃĄsa előtt feltÊtlenÃŧl mÃĄsold.", "api_key_empty": "Az API kulcs nÊv nem lehet Ãŧres", "api_keys": "API kulcsok", "app_architecture_variant": "Variant (ArchitektÃēra)", "app_bar_signout_dialog_content": "Biztos, hogy ki szeretnÊl jelentkezni?", + "app_bar_signout_dialog_ok": "Igen", "app_bar_signout_dialog_title": "KijelentkezÊs", "app_download_links": "App letÃļltÊsi linkek", "app_settings": "AlkalmazÃĄs beÃĄllítÃĄsok", @@ -565,19 +546,27 @@ "archive": "Archívum", "archive_action_prompt": "{count} elem hozzÃĄadva az Archívumhoz", "archive_or_unarchive_photo": "FotÃŗ archivÃĄlÃĄsa vagy archivÃĄlÃĄsÃĄnak visszavonÃĄsa", + "archive_page_no_archived_assets": "Nem talÃĄlhatÃŗ archivÃĄlt elem", + "archive_page_title": "Archívum ({count})", "archive_size": "Archívum mÊrete", "archive_size_description": "BeÃĄllítja letÃļltÊsnÊl az archívum mÊretÊt (GiB)", "archived": "ArchivÃĄlt", "archived_count": "{count, plural, other {ArchivÃĄlva #}}", "are_these_the_same_person": "Ugyanaz a szemÊly?", "are_you_sure_to_do_this": "Biztosan ezt szeretnÊd csinÃĄlni?", + "array_field_not_fully_supported": "Lista mezőkhÃļz a JSON manuÃĄlis szerkesztÊse szÃŧksÊges", + "asset_action_delete_err_read_only": "Csak-olvashatÃŗ elem(ek)et nem lehet tÃļrÃļlni, így ezeket ÃĄtugorjuk", + "asset_action_share_err_offline": "Nem lehet betÃļlteni a kapcsolat nÊlkÃŧli elem(ek)et, így ezeket kihagyjuk", "asset_added_to_album": "HozzÃĄadva az albumhoz", "asset_adding_to_album": "HozzÃĄadÃĄs az albumhozâ€Ļ", "asset_created": "Elem lÊtrehozva", - "asset_day_count": "{date}: {count} elem", "asset_description_updated": "Az elem leírÃĄsa frissÃŧlt", + "asset_filename_is_offline": "A(z) {filename} elem nem elÊrhető, mert offline", + "asset_has_unassigned_faces": "Az elemnek hozzÃĄ nem rendelt arcai vannak", "asset_hashing": "Hash szÃĄmítÃĄsaâ€Ļ", "asset_list_group_by_sub_title": "CsoportosítÃĄs", + "asset_list_layout_settings_dynamic_layout_title": "Dinamikus elrendezÊs", + "asset_list_layout_settings_group_automatically": "Automatikusan", "asset_list_layout_settings_group_by": "Elemek csoportosítÃĄsa", "asset_list_layout_settings_group_by_month_day": "HÃŗnapok Ês napok szerint", "asset_list_layout_sub_title": "ElrendezÊs", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Az elem nem talÃĄlhatÃŗ az iCloud-on. Lehet, hogy azÊrt nem elÊrhető, mert rossz fÃĄjl van az iCloud-on tÃĄrolva", "asset_offline": "Elem offline", "asset_offline_description": "Ez a kÃŧlső elem mÃĄr nem elÊrhető a lemezen. KÊrlek, lÊpj kapcsolatba az Immich adminisztrÃĄtorÃĄval.", + "asset_restored_successfully": "Elem sikeresen helyreÃĄllítva", "asset_skipped": "Kihagyva", "asset_skipped_in_trash": "LomtÃĄrban", + "asset_trashed": "Elem lomtÃĄrba helyezve", "asset_troubleshoot": "HibajavítÃĄs", "asset_uploaded": "FeltÃļltve", "asset_uploading": "FeltÃļltÊsâ€Ļ", "asset_viewer_settings_subtitle": "A kÊpnÊzegető beÃĄllítÃĄsainak kezelÊse", "asset_viewer_settings_title": "Elem megjelenítő", "assets": "Elemek", + "assets_added_count": "{count, plural, other {# elem}} hozzÃĄadva", "assets_added_to_album_count": "{count, plural, other {# elem}} hozzÃĄadva az albumhoz", - "assets_added_to_album_partial_count": "{totalCount} {totalCount, plural, one {elemből} other {elemből}} {successCount} hozzÃĄadva az albumhoz", "assets_added_to_albums_count": "{assetTotal, plural, one {# elem} other {# elemek}} hozzÃĄadva {albumTotal, plural, one {# albumhoz} other {# albumokhoz}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Az elem} other {Az elemek}} nem adhatÃŗak hozzÃĄ az albumhoz", "assets_cannot_be_added_to_albums": "Az {count, plural, one {elemet} other {elemeket}} nem lehet hozzÃĄadni egy albumhoz sem", "assets_count": "{count, plural, other {# elem}}", + "assets_deleted_permanently": "{count} elem vÊglegesen tÃļrÃļlve", + "assets_deleted_permanently_from_server": "{count} elem vÊglegesen tÃļrÃļlve az Immich szerverről", + "assets_downloaded_failed": "{count, plural, one {# fÃĄjl letÃļltve - {error} fÃĄjl sikertelen} other {# fÃĄjl letÃļltve - {error} fÃĄjl sikertelen}}", + "assets_downloaded_successfully": "{count, plural, one {# fÃĄjl sikeresen letÃļltve} other {# fÃĄjl sikeresen letÃļltve}}", "assets_moved_to_trash_count": "{count, plural, other {# elem}} ÃĄthelyezve a lomtÃĄrba", "assets_permanently_deleted_count": "{count, plural, other {# elem}} vÊglegesen tÃļrÃļlve", "assets_removed_count": "{count, plural, other {# elem}} eltÃĄvolítva", + "assets_removed_permanently_from_device": "{count} elem vÊglegesen tÃļrÃļlve az eszkÃļzÃļdről", "assets_restore_confirmation": "Biztos, hogy visszaÃĄllítod a lomtÃĄrban lÊvő Ãļsszes elemet? Ez a mÅąvelet nem visszavonhatÃŗ! MegjegyzÊs: az offline elemeket nem lehet így visszaÃĄllítani.", "assets_restored_count": "{count, plural, other {# elem}} visszaÃĄllítva", + "assets_restored_successfully": "{count} elem sikeresen helyreÃĄllítva", "assets_trashed": "{count} elem lomtÃĄrba helyezve", "assets_trashed_count": "{count, plural, other {# elem}} a lomtÃĄrba helyezve", + "assets_trashed_from_server": "{count} elem lomtÃĄrba helyezve az Immich szerveren", "assets_were_part_of_album_count": "{count, plural, other {# elem}} mÃĄr eleve szerepelt az albumban", "assets_were_part_of_albums_count": "Az {count, plural, one {elem} other {elemek}} mÃĄr az hozzÃĄ lettek adva az albumhoz", "authorized_devices": "EngedÊlyezett eszkÃļzÃļk", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Automatikus diavetítÊs", "back": "Vissza", "back_close_deselect": "Vissza, bezÃĄrÃĄs, vagy kijelÃļlÊs tÃļrlÊse", + "background_backup_running_error": "HÃĄttÊrben futÃŗ mentÊs folyamatban, a kÊzi mentÊs nem indíthatÃŗ", "background_location_permission": "HÃĄttÊrben tÃļrtÊnő helymeghatÃĄrozÃĄsi engedÊly", "background_location_permission_content": "HÃĄlÃŗzatok automatikus vÃĄltÃĄsÃĄhoz az Immich-nek *mindenkÊppen* hozzÃĄ kell fÊrnie a pontos helyzethez, hogy az alkalmazÃĄs le tudja kÊrni a Wi-Fi hÃĄlÃŗzat nevÊt", "background_options": "HÃĄttÊrbeli futÃĄs beÃĄllítÃĄsai", "backup": "BiztonsÃĄgi MentÊs", + "backup_album_selection_page_albums_device": "Ezen az eszkÃļzÃļn lÊvő albumok ({count})", "backup_album_selection_page_albums_tap": "Koppints a hozzÃĄadÃĄshoz, duplÃĄn koppints az eltÃĄvolítÃĄshoz", "backup_album_selection_page_assets_scatter": "Egy elem tÃļbb albumban is lehet. EzÊrt a mentÊshez albumokat lehet hozzÃĄadni vagy azokat a mentÊsből kihagyni.", "backup_album_selection_page_select_albums": "VÃĄlassz albumokat", "backup_album_selection_page_selection_info": "ÖsszegzÊs", + "backup_album_selection_page_total_assets": "Összes egyedi elem", "backup_albums_sync": "BiztonsÃĄgi mentÊs albumok szinkronizÃĄlÃĄsa", + "backup_all": "Összes", + "backup_background_service_backup_failed_message": "Az elemek mentÊse sikertelen. ÚjraprÃŗbÃĄlkozÃĄsâ€Ļ", "backup_background_service_complete_notification": "Az adatok mentÊse befejeződÃļtt", + "backup_background_service_connection_failed_message": "A szerverhez csatlakozÃĄs sikertelen. ÚjraprÃŗbÃĄlkozÃĄsâ€Ļ", + "backup_background_service_current_upload_notification": "FeltÃļltÊs {filename}", "backup_background_service_default_notification": "Új elemek ellenőrzÊseâ€Ļ", + "backup_background_service_error_title": "Hiba a mentÊs kÃļzben", "backup_background_service_in_progress_notification": "Elemek mentÊse folyamatbanâ€Ļ", + "backup_background_service_upload_failure_notification": "A feltÃļltÊs sikertelen {filename}", "backup_controller_page_albums": "Albumok biztonsÃĄgi mentÊse", + "backup_controller_page_background_app_refresh_disabled_content": "EngedÊlyezd a hÃĄttÊrben tÃļrtÊnő frissítÊst a BeÃĄllítÃĄsok > ÁltalÃĄnos > HÃĄttÊrben FrissítÊs menÃŧpontban.", + "backup_controller_page_background_app_refresh_disabled_title": "HÃĄttÊrben frissítÊs kikapcsolva", + "backup_controller_page_background_app_refresh_enable_button_text": "UgrÃĄs a beÃĄllítÃĄsokhoz", "backup_controller_page_background_battery_info_link": "Mutasd meg hogyan", "backup_controller_page_background_battery_info_message": "A sikeres hÃĄttÊrben tÃļrtÊnő mentÊshez kÊrjÃŧk, tiltsd le az Immich akkumulÃĄtor optimalizÃĄlÃĄsÃĄt.\n\nMivel ezt a kÃŧlÃļnfÊle eszkÃļzÃļkÃļn mÃĄshogy kell, ezÊrt kÊrjÃŧk, az eszkÃļzÃļd gyÃĄrtÃŗjÃĄtÃŗl tudd meg, hogyan kell.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "AkkumulÃĄtor optimalizÃĄlÃĄs", + "backup_controller_page_background_charging": "Csak tÃļltÊs kÃļzben", + "backup_controller_page_background_configure_error": "A hÃĄttÊrszolgÃĄltatÃĄs beÃĄllítÃĄsa sikertelen", "backup_controller_page_background_delay": "Új elemek mentÊsÊnek kÊsleltetÊse: {duration}", + "backup_controller_page_background_description": "Kapcsold be a hÃĄttÊrfolyamatot, hogy automatikusan mentsen elemeket az applikÃĄciÃŗ megnyitÃĄsa nÊlkÃŧl", + "backup_controller_page_background_is_off": "Automatikus mentÊs a hÃĄttÊrben ki van kapcsolva", + "backup_controller_page_background_is_on": "Automatikus mentÊs a hÃĄttÊrben be van kapcsolva", + "backup_controller_page_background_turn_off": "HÃĄttÊrszolgÃĄltatÃĄs kikapcsolÃĄsa", + "backup_controller_page_background_turn_on": "HÃĄttÊrszolgÃĄltatÃĄs bekapcsolÃĄsa", + "backup_controller_page_background_wifi": "Csak Wi-Fi-n", "backup_controller_page_backup": "MentÊs", "backup_controller_page_backup_selected": "KivÃĄlasztva: ", "backup_controller_page_backup_sub": "Mentett fotÃŗk Ês videÃŗk", + "backup_controller_page_created": "LÊtrehozva: {date}", + "backup_controller_page_desc_backup": "Ha bekapcsolod az előtÊrben mentÊst, akkor az Ãēj elemek automatikusan feltÃļltődnek a szerverre, amikor megyitod az alkalmazÃĄst.", "backup_controller_page_excluded": "KivÊve: ", + "backup_controller_page_failed": "Sikertelen ({count})", + "backup_controller_page_filename": "FÃĄjlnÊv: {filename}[{size}]", + "backup_controller_page_id": "AzonosítÃŗ: {id}", + "backup_controller_page_info": "MentÊsi informÃĄciÃŗk", "backup_controller_page_none_selected": "Egy sincs kivÃĄlasztva", "backup_controller_page_remainder": "HÃĄtralÊvő", "backup_controller_page_remainder_sub": "HÃĄtralÊvő fotÃŗk Ês videÃŗk a kijelÃļltek kÃļzÃŧl", "backup_controller_page_server_storage": "Szerver tÃĄrhely", + "backup_controller_page_start_backup": "MentÊs indítÃĄsa", + "backup_controller_page_status_off": "Automatikus mentÊs az előtÊrben ki van kapcsolva", + "backup_controller_page_status_on": "Automatikus mentÊs az előtÊrben be van kapcsolva", "backup_controller_page_storage_format": "{used} / {total} felhasznÃĄlva", "backup_controller_page_to_backup": "MentÊsre kijelÃļlt albumok", "backup_controller_page_total_sub": "Minden egyedi fotÃŗ Ês videÃŗ a kijelÃļlt albumokbÃŗl", + "backup_controller_page_turn_off": "ElőtÊrben mentÊs kikapcsolÃĄsa", + "backup_controller_page_turn_on": "ElőtÊrben mentÊs bekapcsolÃĄsa", + "backup_controller_page_uploading_file_info": "FÃĄjl informÃĄciÃŗk feltÃļltÊse", + "backup_err_only_album": "Az utolsÃŗ albumot nem tudod tÃļrÃļlni", "backup_error_sync_failed": "A szinkronizÃĄlÃĄs nem sikerÃŧlt. A biztonsÃĄgi mentÊs nem elkÊszíthető.", "backup_info_card_assets": "elemek", + "backup_manual_cancelled": "Megszakítva", + "backup_manual_in_progress": "FeltÃļltÊs mÃĄr folyamatban. PrÃŗbÃĄld meg kÊsőbb", + "backup_manual_success": "Sikeres", + "backup_manual_title": "FeltÃļltÊs ÃĄllapota", "backup_options": "BiztonsÃĄgi mentÊs beÃĄllítÃĄsai", + "backup_options_page_title": "BiztonÃĄgi mentÊs beÃĄllítÃĄsai", + "backup_setting_subtitle": "A hÃĄttÊrben Ês előtÊrben mentÊs beÃĄllítÃĄsainak kezelÊse", "backup_settings_subtitle": "FeltÃļltÊs beÃĄllítÃĄsai", + "backup_upload_details_page_more_details": "Érintse meg tovÃĄbbi rÊszletekhez", "backward": "Visszafele", - "battery_optimization_backup_reliability": "Az akkumulÃĄtor-optimalizÃĄlÃĄs kikapcsolÃĄsa javíthatja a hÃĄttÊrben futÃŗ biztonsÃĄgi mentÊs megbízhatÃŗsÃĄgÃĄt", "biometric_auth_enabled": "Biometrikus azonosítÃĄs engedÊlyezve", "biometric_locked_out": "Ki vagy zÃĄrva a biometrikus azonosítÃĄsbÃŗl", "biometric_no_options": "Nincsen elÊrhető biometrikus azonosítÃĄs", "biometric_not_available": "Biometrikus azonosítÃĄs ezen az eszkÃļzÃļn nem elÊrhető", + "birthdate_saved": "SzÃŧletÊsnap elmentve", "birthdate_set_description": "A szÃŧletÊs napjÃĄt a rendszer arra hasznÃĄlja, hogy kiírja, hogy a fÊnykÊp kÊszítÊsekor a szemÊly hÃĄny Êves volt.", "blurred_background": "HomÃĄlyos hÃĄttÊr", - "browse_templates": "Sablonok bÃļngÊszÊse", "bugs_and_feature_requests": "HibabejelentÊs Ês Ãēj funkciÃŗ kÊrÊse", "build": "FelÊpítÊs", "build_image": "KÊp elkÊszítÊse", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Biztosan meg szeretnÊl tartani {count, plural, other {# egyező elemet}}? Ez a mÅąvelet az elemek tÃļrlÊse nÊlkÃŧl megszÃŧnteti az Ãļsszes duplikÃĄlt csoportosítÃĄst.", "bulk_trash_duplicates_confirmation": "Biztosan kitÃļrÃļlsz {count, plural, one {# duplikÃĄlt fÃĄjlt} other {# duplikÃĄlt fÃĄjlt}}? Ez a mÅąvelet megtartja minden csoportbÃŗl a legnagyobb mÊretÅą elemet, Ês kitÃļrÃļl minden mÃĄsik duplikÃĄltat.", "buy": "Immich megvÃĄsÃĄrlÃĄsa", + "cache_settings_clear_cache_button": "GyorsítÃŗtÃĄr kiÃŧrítÊse", + "cache_settings_clear_cache_button_title": "KiÃŧríti az alkalmazÃĄs gyorsítÃŗtÃĄrÃĄt. Ez jelentősen kihat az alkalmazÃĄs teljesítmÊnyÊre, amíg a gyorsítÃŗtÃĄr Ãējra nem ÊpÃŧl.", + "cache_settings_duplicated_assets_clear_button": "KIÜRÍT", + "cache_settings_duplicated_assets_subtitle": "FotÃŗk Ês videÃŗk, amiket az alkalmazÃĄs figyelmen kívÃŧl hagyott", + "cache_settings_duplicated_assets_title": "DuplikÃĄlt elemek ({count})", + "cache_settings_statistics_album": "KÊptÃĄr bÊlyegkÊpei", + "cache_settings_statistics_full": "Teljes mÊretÅą kÊpek", + "cache_settings_statistics_shared": "Megosztott album bÊlyegkÊpei", + "cache_settings_statistics_thumbnail": "BÊlyegkÊpek", + "cache_settings_statistics_title": "GyorsítÃŗtÃĄr hasznÃĄlata", + "cache_settings_subtitle": "Az Immich mobilalkalmazÃĄs gyorsítÃŗtÃĄr viselkedÊsÊnek beÃĄllítÃĄsa", + "cache_settings_tile_subtitle": "Helyi tÃĄrhely viselkedÊsÊnek beÃĄllítÃĄsa", + "cache_settings_tile_title": "Helyi tÃĄrhely", + "cache_settings_title": "GyorsítÃŗtÃĄr beÃĄllítÃĄsok", "camera": "FÊnykÊpezőgÊp", "camera_brand": "FÊnykÊpezőgÊp mÃĄrka", "camera_model": "FÊnykÊpezőgÊp modell", @@ -678,10 +729,10 @@ "cannot_update_the_description": "A leírÃĄs megvÃĄltoztatÃĄsa nem sikerÃŧlt", "cast": "KÃļzvetítÊs", "cast_description": "KÃļzvetítÊsi cÊlok beÃĄllítÃĄsa", - "change": "MegvÃĄltoztat", "change_date": "DÃĄtum vÃĄltoztatÃĄsa", "change_description": "LeírÃĄs megvÃĄltoztatÃĄsa", "change_display_order": "MegjelenítÊsi sorrend megvÃĄltoztatÃĄsa", + "change_expiration_time": "LejÃĄrati idő megvÃĄltoztatÃĄsa", "change_location": "Helyszín vÃĄltoztatÃĄsa", "change_name": "NÊv vÃĄltoztatÃĄsa", "change_name_successfully": "A nÊv megvÃĄltoztatÃĄsa sikeres", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "A beírt jelszavak nem egyeznek", "change_password_form_reenter_new_password": "JelszÃŗ (mÊg egyszer)", "change_pin_code": "PIN kÃŗd megvÃĄltoztatÃĄsa", + "change_trigger": "FeltÊtel mÃŗdosítÃĄsa", + "change_trigger_prompt": "Biztosan mÃŗdosítani szeretnÊd az indítÃĄsi feltÊtelt? Ezzel tÃļrlÃļd az Ãļsszes mÅąveletet Ês szÅąrőt.", "change_your_password": "Jelszavad megvÃĄltoztatÃĄsa", "changed_visibility_successfully": "LÃĄthatÃŗsÃĄg sikeresen megvÃĄltoztatva", "charging": "TÃļltÊs", "charging_requirement_mobile_backup": "HÃĄttÊrben mentÊshez szÃŧksÊges, hogy az eszkÃļz tÃļltőn legyen", + "check_corrupt_asset_backup": "SÊrÃŧlt elemek keresÊse a mentÊsben", + "check_corrupt_asset_backup_button": "EllenőrzÊs", + "check_corrupt_asset_backup_description": "Ezt az ellenőtzÊst csak Wi-Fi hÃĄlÃŗzaton futtasd Ês csak akkot, ha mÃĄr az Ãļsszes elem feltÃļltÊsre kerÃŧlt. A folyamat nÊhÃĄny percig is eltarthat.", "check_logs": "HibanaplÃŗ megnyitÃĄsa", "checksum": "Ellenőrző Ãļsszeg", - "choose": "VÃĄlaszt", "choose_matching_people_to_merge": "VÃĄlaszd ki a megegyező szemÊlyeket ÃļsszevonÃĄsra", "city": "VÃĄros", "cleanup_confirm_description": "Az Immich {count} elemet talÃĄlt ({date}-ig), amelyek biztonsÃĄgosan mentÊsre kerÃŧltek a szerveren. TÃļrlÊsre kerÃŧljenek a lokÃĄlis pÊldÃĄnyok erről az eszkÃļzről?", @@ -719,10 +774,11 @@ "clear": "TÃļrlÊs", "clear_all": "Alaphelyzet", "clear_all_recent_searches": "LegutÃŗbbi keresÊsek tÃļrlÊse", - "clear_failed_count": "A tÃļrlÊs nem sikerÃŧlt ({count})", "clear_file_cache": "GyorsítÃŗtÃĄr tÃļrlÊse", "clear_message": "Üzenet tÃļrlÊse", "clear_value": "ÉrtÊk tÃļrlÊse", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "JelszÃŗ megadÃĄsa", "client_cert_import": "ImportÃĄlÃĄs", "client_cert_import_success_msg": "Kliens tanÃēsítvÃĄny importÃĄlva", "client_cert_invalid_msg": "ÉrvÊnytelen tanÃēsítvÃĄny fÃĄjl vagy hibÃĄs jelszÃŗ", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Kliens tanÃēsítvÃĄny eltÃĄvolítva", "client_cert_subtitle": "Csak a PKCS12 (.p12, .pfx) formÃĄtum tÃĄmogatott. TanÃēsítvÃĄny importÃĄlÃĄsa/eltÃĄvolítÃĄsa csak a bejelentkezÊs előtt lehetsÊges", "client_cert_title": "SSL kliens tanÃēsítvÃĄny [KÍSÉRLETI]", + "clockwise": "ÓramutatÃŗ jÃĄrÃĄsÃĄval megegyező irÃĄny", "close": "BezÃĄrÃĄs", "collapse": "Összecsuk", "collapse_all": "Mindet Ãļsszecsuk", "color": "Szín", + "color_theme": "SzíntÊma", "command": "Parancs", "command_palette_prompt": "Gyors keresÊs oldalak, mÅąveletek Ês parancsok kÃļzÃļtt", "command_palette_to_close": "bezÃĄrÃĄs", @@ -747,7 +805,6 @@ "comments_are_disabled": "A megjegyzÊsek le vannak tiltva", "common_create_new_album": "Új album lÊtrehozÃĄsa", "completed": "KÊsz", - "configuration": "BeÃĄllítÃĄsok", "confirm": "JÃŗvÃĄhagyÃĄs", "confirm_admin_password": "Admin jelszÃŗ megerősítÊse", "confirm_delete_face": "Biztos, hogy tÃļrÃļlni szeretnÊd a(z) {name} arcÃĄt az elemről?", @@ -762,16 +819,19 @@ "contain": "BelÃŧl", "context": "Kontextus", "continue": "FolytatÃĄs", - "control_bottom_app_bar_add_tags": "CímkÊk hozzÃĄadÃĄsa", + "control_bottom_app_bar_create_new_album": "Új album lÊtrehozÃĄsa", + "control_bottom_app_bar_delete_from_immich": "TÃļrlÊs az Immich-ből", "control_bottom_app_bar_delete_from_local": "TÃļrlÊs az eszkÃļzről", "control_bottom_app_bar_edit_location": "Hely mÃŗdosítÃĄsa", "control_bottom_app_bar_edit_time": "DÃĄtum Ês idő mÃŗdosítÃĄsa", + "control_bottom_app_bar_share_link": "Link megosztÃĄsa", + "control_bottom_app_bar_share_to": "MegosztÃĄs ide", "control_bottom_app_bar_trash_from_immich": "LomtÃĄrba helyezÊs", "copied_image_to_clipboard": "KÊp a vÃĄgÃŗlapra mÃĄsolva.", "copied_to_clipboard": "VÃĄgÃŗlapra mÃĄsolva!", "copy_error": "MÃĄsolÃĄsi hiba", + "copy_file_path": "FÃĄjlÃētvonal mÃĄsolÃĄsa", "copy_image": "KÊp mÃĄsolÃĄsa", - "copy_json": "JSON mÃĄsolÃĄsa", "copy_link": "Link mÃĄsolÃĄsa", "copy_link_to_clipboard": "Link mÃĄsolÃĄsa a vÃĄgÃŗlapra", "copy_password": "JelszÃŗ mÃĄsolÃĄsa", @@ -792,8 +852,6 @@ "create_new_person": "Új szemÊly lÊtrehozÃĄsa", "create_new_person_hint": "KivÃĄlasztott elemek Ãēj szemÊlyhez rendelÊse", "create_new_user": "Új felhasznÃĄlÃŗ lÊtrehozÃĄsa", - "create_person": "Új ember lÊtrehozÃĄsa", - "create_person_subtitle": "Adj nevet a kijelÃļlt arcnak az Ãēj szemÊly lÊtrehozÃĄsÃĄhoz Ês megjelÃļlÊsÊhez", "create_shared_album_page_share_add_assets": "ELEMEK HOZZÁADÁSA", "create_shared_album_page_share_select_photos": "FotÃŗk vÃĄlasztÃĄsa", "create_shared_link": "Megosztott link lÊtrehozÃĄsa", @@ -805,33 +863,37 @@ "created_at": "LÊtrehozva", "creating_linked_albums": "Kapcsolt albumok lÊtrehozÃĄsa...", "crop": "KivÃĄgÃĄs", + "crop_aspect_ratio_fixed": "RÃļgzített", "crop_aspect_ratio_free": "Tetszőleges", "crop_aspect_ratio_original": "Eredeti", "crop_aspect_ratio_square": "NÊgyzet", + "curated_object_page_title": "Dolgok", "current_device": "Ez az eszkÃļz", "current_pin_code": "AktuÃĄlis PIN kÃŗd", "current_server_address": "Jelenlegi szerver cím", "custom_date": "EgyÊni dÃĄtum", "custom_locale": "EgyÊni terÃŧleti beÃĄllítÃĄs", "custom_locale_description": "DÃĄtumok, idők Ês szÃĄmok formÃĄzÃĄsa a kivÃĄlasztott nyelv Ês a terÃŧlet szerint", + "custom_url": "EgyÊni URL", "cutoff_date_description": "FotÃŗk megtartÃĄsa az elmÃēltâ€Ļ", "cutoff_day": "{count, plural, one {nap} other {nap}}", "cutoff_year": "{count, plural, one {Êv} other {Êv}}", + "daily_title_text_date": "MMM dd (E)", + "daily_title_text_date_year": "yyyy MMM dd (E)", "dark": "SÃļtÊt", "dark_theme": "SÃļtÊt tÊmÃĄra vÃĄltÃĄs", "date": "DÃĄtum", "date_after": "DÃĄtumtÃŗl", "date_and_time": "DÃĄtum Ês idő", "date_before": "DÃĄtumig", - "date_of_birth": "SzÃŧletÊsi dÃĄtum", + "date_format": "y LLL d (E) â€ĸ HH:mm", "date_of_birth_saved": "SzÃŧletÊsnap sikeresen elmentve", "date_range": "DÃĄtum intervallum", - "date_time_original": "Eredeti dÃĄtum/idő", "day": "Nap", "days": "Napok", "deduplicate_all": "Összes deduplikÃĄlÃĄsa", - "default_quality_subtitle": "MegosztÃĄskor hasznÃĄlt minősÊg. A kivÃĄlasztÃĄshoz minden alkalommal hosszan nyomd meg a megosztÃĄs gombot.", - "default_share_quality": "AlapÊrtelmezett megosztÃĄsi minősÊg", + "default_locale": "AlapÊrtelmezett nyelvi beÃĄllítÃĄs", + "default_locale_description": "A dÃĄtumok Ês szÃĄmok formÃĄzÃĄsa a bÃļngÊsző nyelvi beÃĄllítÃĄsai alapjÃĄn", "delete": "TÃļrlÊs", "delete_action_confirmation_message": "Biztosan tÃļrÃļlni szeretnÊd ezt az elemet? Így az elem a szerver lomtÃĄrÃĄba kerÃŧl, Ês megkÊrdezi, hogy tÃļrÃļlni szeretnÊd-e a az eszkÃļzÃļn is", "delete_action_prompt": "{count} tÃļrÃļlve", @@ -840,6 +902,8 @@ "delete_dialog_alert": "Ezek az elemek vÊglegesen tÃļrÃļlve lesznek Immich-ről Ês az eszkÃļzÃļdről is", "delete_dialog_alert_local": "Ezek az elemek vÊglegesen tÃļrÃļlve lesznek az eszkÃļzÃļdről, de tovÃĄbbra is elÊrhetőek maradnak az Immich szerveren", "delete_dialog_alert_local_non_backed_up": "NÊhÃĄny elem nem lett elmentve az Immich szerverre Ês most vÊglegesen tÃļrÃļlve lesznek az eszkÃļzÃļdről is", + "delete_dialog_alert_remote": "Ezek az elemek vÊglegesen tÃļrlÊsre kerÃŧlnek az Immich szerverről", + "delete_dialog_ok_force": "TÃļrlÊs mindenkÊpp", "delete_dialog_title": "VÊgleges tÃļrlÊs", "delete_duplicates_confirmation": "Biztosan vÊglegesen tÃļrÃļlni szeretnÊd ezeket a duplikÃĄtumokat?", "delete_face": "Arc tÃļrlÊse", @@ -858,16 +922,21 @@ "delete_tag_confirmation_prompt": "Biztosan tÃļrÃļlni szeretnÊd a(z) {tagName} címkÊt?", "delete_user": "FelhasznÃĄlÃŗ tÃļrlÊse", "deleted_shared_link": "TÃļrÃļlt megosztott link", + "deletes_missing_assets": "TÃļrli a fizikailag hiÃĄnyzÃŗ elemeket", "description": "LeírÃĄs", + "description_input_hint_text": "LeírÃĄs hozzÃĄadÃĄsa...", + "description_input_submit_error": "Nem sikerÃŧlt frissíteni a leírÃĄst. TovÃĄbbi informÃĄciÃŗÃŠrt kÊrjÃŧk, nÊzd meg az esemÊnynaplÃŗt", "deselect_all": "KijelÃļlÊs megszÃŧntetÊs", "details": "RÊszletek", "direction": "IrÃĄny", "disable": "LetiltÃĄs", "disabled": "Letiltott", + "disallow_edits": "MÃŗdosítÃĄsok letiltÃĄsa", "discord": "Discord", "discover": "Felfedez", "discovered_devices": "Felfedezett eszkÃļzÃļk", "dismiss_all_errors": "Minden hiba elvetÊse", + "dismiss_error": "Hiba elvetÊse", "display_options": "MegjelenítÊsi beÃĄllítÃĄsok", "display_order": "MegjelenítÊsi sorrend", "display_original_photos": "Eredeti fotÃŗk megjelenítÊse", @@ -876,9 +945,11 @@ "documentation": "DokumentÃĄciÃŗ", "done": "KÊsz", "download": "LetÃļltÊs", + "download_action_prompt": "{count} elem letÃļltÊse", "download_canceled": "LetÃļltÊs megszakítva", "download_complete": "LetÃļltÊs kÊsz", "download_enqueue": "LetÃļltÊs sorba ÃĄllítva", + "download_error": "LetÃļltÊsi hiba", "download_failed": "Sikertelen letÃļltÊs", "download_finished": "LetÃļltÊs kÊsz", "download_include_embedded_motion_videos": "BeÃĄgyazott videÃŗk", @@ -888,15 +959,15 @@ "download_paused": "LetÃļltÊs szÃŧneteltetve", "download_settings": "LetÃļltÊs", "download_settings_description": "Elemek letÃļltÊsÊvel kapcsolatos beÃĄllítÃĄsok kezelÊse", + "download_started": "LetÃļltÊs megkezdve", + "download_sucess": "Sikeres letÃļltÊs", + "download_sucess_android": "MÊdia letÃļltve a DCIM/Immich mappÃĄba", "download_waiting_to_retry": "VÃĄrÃĄs az ÃējraprÃŗbÃĄlkozÃĄsra", "downloading": "LetÃļltÊs", "downloading_asset_filename": "{filename} elem letÃļltÊse", "downloading_from_icloud": "LetÃļltÊs az iCloudrÃŗl", "downloading_media": "MÊdia letÃļltÊse", - "drag_to_reorder": "HÃēzd az ÃējrarendezÊshez", "drop_files_to_upload": "A feltÃļltÊshez hÃēzd bÃĄrhova a fÃĄjlokat", - "duplicate": "MÃĄsolat", - "duplicate_workflow": "DuplikÃĄlt munkafolyamat", "duplicates": "DuplikÃĄtumok", "duplicates_description": "JelÃļld meg a duplikÃĄtumokat (ha lÊteznek) a csoportokban.", "duration": "Időtartam", @@ -908,7 +979,9 @@ "edit_date_and_time": "DÃĄtum Ês idő mÃŗdosítÃĄsa", "edit_date_and_time_action_prompt": "{count} dÃĄtum Ês idő mÃŗdosítva", "edit_date_and_time_by_offset": "DÃĄtum mÃŗdosítÃĄsa időeltolÃĄssal", + "edit_date_and_time_by_offset_interval": "Új dÃĄtumtartomÃĄny: {from} - {to}", "edit_description": "LeírÃĄs szerkesztÊse", + "edit_description_prompt": "KÊrlek vÃĄlassz egy Ãēj leírÃĄst:", "edit_exclusion_pattern": "KizÃĄrÃĄsi minta (pattern) mÃŗdosítÃĄsa", "edit_faces": "Arcok mÃŗdosítÃĄsa", "edit_key": "Kulcs mÃŗdosítÃĄsa", @@ -923,6 +996,9 @@ "edit_user": "FelhasznÃĄlÃŗ mÃŗdosítÃĄsa", "edit_workflow": "Folyamat mÃŗdosítÃĄsa", "editor": "Szerkesztő", + "editor_close_without_save_prompt": "A vÃĄltoztatÃĄsok nem lesznek elmentve", + "editor_close_without_save_title": "Szerkesztő bezÃĄrÃĄsa?", + "editor_confirm_reset_all_changes": "Biztosan vissza szeretnÊd ÃĄllítani az Ãļsszes mÃŗdosítÃĄst?", "editor_discard_edits_confirm": "MÃŗdosítÃĄsok elvetÊse", "editor_discard_edits_prompt": "A mÃŗdosítÃĄsokat nem mentetted. Biztos vagy benne, hogy mindet elveted?", "editor_discard_edits_title": "Elveted a mÃŗdosítÃĄsokat?", @@ -951,7 +1027,9 @@ "enter_your_pin_code": "Add meg a jelszavad", "enter_your_pin_code_subtitle": "Add meg a PIN kÃŗdodat a zÃĄrolt mappa megnyitÃĄsÃĄhoz", "error": "Hiba", + "error_change_sort_album": "Album sorbarendezÊsÊnek megvÃĄltoztatÃĄsa sikertelen", "error_delete_face": "Hiba az arc tÃļrlÊse sorÃĄn", + "error_getting_places": "Hiba a helyek betÃļltÊsekor", "error_loading_albums": "Hiba az albumok betÃļltÊsekor", "error_loading_image": "Hiba a kÊp betÃļltÊse kÃļzben", "error_loading_partners": "Hiba a partnerek betÃļltÊsÊnÊl: {error}", @@ -991,7 +1069,6 @@ "failed_to_remove_product_key": "TermÊkkulcs eltÃĄvolítÃĄsa sikertelen", "failed_to_reset_pin_code": "PIN kÃŗd visszaÃĄllítÃĄsa sikertelen", "failed_to_stack_assets": "Elemek csoportosítÃĄsa sikertelen", - "failed_to_tag_assets": "Nem sikerÃŧlt címkÊzni", "failed_to_unstack_assets": "Csoportosított elemek szÊtszedÊse sikertelen", "failed_to_update_notification_status": "ÉrtesítÊs stÃĄtusz frissítÊse sikertelen", "incorrect_email_or_password": "Helytelen email vagy jelszÃŗ", @@ -1090,10 +1167,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "LeírÃĄs HozzÃĄadÃĄsa...", "exif_bottom_sheet_description_error": "Hiba a leírÃĄs frissítÊsekor", + "exif_bottom_sheet_details": "RÉSZLETEK", + "exif_bottom_sheet_location": "HELY", "exif_bottom_sheet_no_description": "Nincs leírÃĄs", + "exif_bottom_sheet_people": "EMBEREK", + "exif_bottom_sheet_person_add_person": "Elnevez", "exit_slideshow": "KilÊpÊs a diavetítÊsből", "expand": "Kinyit", "expand_all": "Összes kinyitÃĄsa", + "experimental_settings_new_asset_list_subtitle": "FejlesztÊs alatt", + "experimental_settings_new_asset_list_title": "KisÊrleti kÊprÃĄcs engedÊlyezÊse", + "experimental_settings_subtitle": "Csak sajÃĄt felelőssÊgre hasznÃĄld!", + "experimental_settings_title": "KísÊrleti", "expire_after": "LejÃĄrati idő", "expired": "LejÃĄrt", "expires_date": "LejÃĄr: {date}", @@ -1103,40 +1188,41 @@ "export_as_json": "ExportÃĄlÃĄs JSON-kÊnt", "export_database": "AdatbÃĄzis exportÃĄlÃĄsa", "export_database_description": "Az SQLite adatbÃĄzis exportÃĄlÃĄsa", - "exposure_time": "ZÃĄridő", "extension": "KiterjesztÊs", "external": "KÃŧlső kÊptÃĄr", "external_libraries": "KÃŧlső kÊptÃĄrak", "external_network": "KÃŧlső hÃĄlÃŗzat", "external_network_sheet_info": "Ha nem vagy a megadott Wi-Fi hÃĄlÃŗzathoz csatlakozva, akkor az alkalmazÃĄs az alÃĄbbi URL címeken fogja elÊrni a szervert, fentről lefelÊ haladva", - "f_number": "Blende", "face_unassigned": "Nincs hozzÃĄrendelve", "failed": "Sikertelen", "failed_count": "Sikertelen: {count}", "failed_to_authenticate": "AutentikÃĄciÃŗ sikertelen", - "failed_to_delete_file": "Sikertelen fÃĄjl tÃļrlÊs", "failed_to_load_assets": "Nem sikerÃŧlt betÃļlteni az elemeket", "failed_to_load_folder": "Mappa betÃļltÊse sikertelen", "favorite": "Kedvenc", "favorite_action_prompt": "{count} hozzÃĄadva a Kedvencekhez", "favorite_or_unfavorite_photo": "FotÃŗ kedvencnek jelÃļlÊse vagy annak visszavonÃĄsa", "favorites": "Kedvencek", + "favorites_page_no_favorites": "Nem talÃĄlhatÃŗ kedvencnek jelÃļlt elem", "feature_photo_updated": "CímlapkÊp frissítve", "features": "BeÃĄllítÃĄsok", + "features_in_development": "Folyamatban lÊvő fejlesztÊsek", "features_setting_description": "Az alkalmazÃĄs jellemzőinek kezelÊse", "file_name_or_extension": "FÃĄjlnÊv vagy kiterjesztÊs", "file_name_text": "FÃĄjl neve", + "file_name_with_value": "FÃĄjlnÊv:{file_name}", "file_size": "FÃĄjlmÊret", "filename": "FÃĄjlnÊv", "filetype": "FÃĄjltípus", "filter": "SzÅąrő", + "filter_description": "Az elemek szÅąrÊsi feltÊtelei", "filter_people": "SzemÊlyek szÅąrÊse", "filter_places": "Helyszínek szÅąrÊse", "filter_tags": "címkÊk szÅąrÊse", "filters": "SzÅąrők", + "find_them_fast": "NÊv alapjÃĄn keresÊssel gyorsan megtalÃĄlhatÃŗak", "first": "Első", "fix_incorrect_match": "HibÃĄs talÃĄlat javítÃĄsa", - "focal_length": "GyÃējtÃŗtÃĄvolsÃĄg", "folder": "Mappa", "folder_not_found": "Mappa nem talÃĄlhatÃŗ", "folders": "MappÃĄk", @@ -1147,7 +1233,6 @@ "free_up_space_description": "Hely felszabadítÃĄsa ÊrdekÊben helyezze ÃĄt a mentett fotÃŗkat Ês videÃŗkat az eszkÃļz kukÃĄjÃĄba. A szerveren lÊvő mÃĄsolatok biztonsÃĄgban maradnak.", "free_up_space_settings_subtitle": "EszkÃļz tÃĄrhely felszabadítÃĄsa", "full_path": "Teljes elÊrÊi Ãētvonal: {path}", - "full_path_or_folder": "Teljes elÊrÊsi Ãēt vagy mappa", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Ez a funkciÃŗ a Google-től tÃļlti be a mÅąkÃļdÊsÊhez szÃŧksÊges kÃŧlső adatokat.", "general": "ÁltalÃĄnos", @@ -1168,6 +1253,7 @@ "group_owner": "CsoportosítÃĄs tulajdonos szerint", "group_places_by": "Helyszínek csoportosítÃĄsa...", "group_year": "CsoportosítÃĄs Êv szerint", + "haptic_feedback_switch": "RezgÊses visszajelzÊs engedÊlyezÊse", "haptic_feedback_title": "RezgÊses visszajelzÊs", "has_quota": "KvÃŗta", "hash_asset": "Elem hash-elÊse", @@ -1188,12 +1274,29 @@ "hide_schema": "SÊma elrejtÊse", "hide_text_recognition": "SzÃļvegfelismerÊs elrejtÊse", "hide_unnamed_people": "NÊv nÊlkÃŧli szemÊlyek elrejtÊse", + "home_page_add_to_album_conflicts": "{added} elem hozzÃĄadva a(z) \"{album}\" albumhoz. {failed} elem mÃĄr eleve az albumban volt.", + "home_page_add_to_album_err_local": "Helyi elemeket mÊg nem lehet albumba tenni, ki lesznek hagyva", + "home_page_add_to_album_success": "{added} elem hozzÃĄadva a(z) \"{album}\" albumhoz.", + "home_page_album_err_partner": "MÊg nem lehet a partner elemeit albumokhoz adni, ki lesznek hagyva", + "home_page_archive_err_local": "Helyi elemek archivÃĄlÃĄsa mÊg nem tÃĄmogatott, Ãēgyhogy kihagyjuk", + "home_page_archive_err_partner": "Partner elemeit nem lehet archivÃĄlni, Ãēgyhogy kihagyjuk", "home_page_building_timeline": "Idővonal ÃļsszeÃĄllítÃĄsa", + "home_page_delete_err_partner": "Partner elemeit nem lehet tÃļrÃļlni, Ãēgyhogy kihagyjuk", + "home_page_delete_remote_err_local": "Helyi elemek vannak tÃĄvoli tÃļrlÊsre kivÃĄlasztva, Ãēgyhogy ezeket kihagyjuk", + "home_page_favorite_err_local": "Helyi elemeket mÊg nem lehet a kedvencek kÃļzÊ tenni, Ãēgyhogy ezeket kihagyjuk", + "home_page_favorite_err_partner": "Partner elemeit mÊg nem lehet a kedvencek kÃļzÊ tenni, Ãēgyhogy ezeket kihagyjuk", + "home_page_first_time_notice": "Ha most hasznÃĄlod előszÃļr az alkalmazÃĄst, a fotÃŗk Ês videÃŗk megjelenítÊsÊhez az idővonaladon, ÃĄllítsd be, hogy melyik albumaidrÃŗl kÊszÃŧljÃļn biztonsÃĄgi mentÊs", + "home_page_locked_error_local": "A helyi elemek nem mozgathatÃŗak a zÃĄrolt mappÃĄba, ki lesznek hagyva", + "home_page_locked_error_partner": "Partner elemek nem mozgathatÃŗak a zÃĄrolt mappÃĄba, ÃĄtugorva", + "home_page_share_err_local": "Helyi elemekről nem lehet megosztott linket kÊszíteni, Ãēgyhogy kihagyjuk", + "home_page_upload_err_limit": "Csak 30 elemet tudsz egyszerre feltÃļlteni, Ãēgyhogy kihagyjuk", "host": "KiszolgÃĄlÃŗ", "hour": "Óra", "hours": "ÓrÃĄk", "id": "AzonosítÃŗ", "idle": "ÜresjÃĄrat", + "ignore_icloud_photos": "iCloud fotÃŗk figyelmen kívÃŧl hagyÃĄsa", + "ignore_icloud_photos_description": "Az iCloud-ban tÃĄrolt fotÃŗk nem lesznek feltÃļltve az Immich szerverre", "image": "KÊp", "image_alt_text_date": "{isVideo, select, true {VideÃŗ} other {KÊp}} kÊszítÊsi dÃĄtuma: {date}", "image_alt_text_date_1_person": "{isVideo, select, true {VideÃŗ} other {KÊp}} vele: {person1} (kÊszÃŧlt {date})", @@ -1205,6 +1308,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {VideÃŗ} other {KÊp}} itt: {country}, {city}, velÃŧk: {person1} Ês {person2} (kÊszÃŧlt: {date})", "image_alt_text_date_place_3_people": "{isVideo, select, true {VideÃŗ} other {KÊp}} itt: {country}, {city}, velÃŧk: {person1}, {person2} Ês {person3} (kÊszÃŧlt: {date})", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {VideÃŗ} other {KÊp}} itt: {country}, {city}, velÃŧk: {person1}, {person2} Ês tovÃĄbbi {additionalCount, number} szemÊly (kÊszÃŧlt: {date})", + "image_saved_successfully": "KÊp elmentve", + "image_viewer_page_state_provider_download_started": "A letÃļltÊs elkezdődÃļtt", + "image_viewer_page_state_provider_download_success": "LetÃļltÊs sikeres", + "image_viewer_page_state_provider_share_error": "MegosztÃĄsi hiba", "immich_logo": "Immich logÃŗ", "immich_web_interface": "Immich webes felÃŧlet", "import_from_json": "ImportÃĄlÃĄs JSON-ből", @@ -1219,7 +1326,6 @@ "individual_share": "EgymagÃĄban megosztott elem", "individual_shares": "EgyÊni megosztÃĄsok", "info": "InfÃŗ", - "integrity_checks": "IntegritÃĄs-ellenőrzÊsek", "interval": { "day_at_onepm": "Minden nap 13 Ãŗrakor", "hours": "{hours, plural, one {ÃŗrÃĄnkÊnt} other {{hours, number} ÃŗrÃĄnkÊnt}}", @@ -1230,9 +1336,16 @@ "invalid_date_format": "ÉrvÊnytelen dÃĄtumformÃĄtum", "invite_people": "SzemÊlyek meghívÃĄsa", "invite_to_album": "MeghívÃĄs az albumba", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "LetÃļltÊs futtatva {dateTime}", + "ios_debug_info_last_sync_at": "UtoljÃĄra szinkronizÃĄlva {dateTime}", + "ios_debug_info_no_processes_queued": "Nincs a sorban hÃĄttÊrfolyamat jelenleg", + "ios_debug_info_no_sync_yet": "MÊg nem futott szinkronizÃĄlÃŗ hÃĄttÊrfolyamat", + "ios_debug_info_processes_queued": "{count, plural, one {{count} hÃĄttÊrfolyamat előkÊszítve} other {{count} hÃĄttÊrfolyamat előkÊszítve}}", + "ios_debug_info_processing_ran_at": "A feldolgozÃĄs ekkor futott: {dateTime}", "items_count": "{count, plural, other {# elem}}", "jobs": "Feladatok", + "json_editor": "JSON szerkesztő", + "json_error": "JSON hiba", "keep": "Megtart", "keep_albums": "Albumok megtartÃĄsa", "keep_albums_count": "{count} album megtartÃĄsa", @@ -1259,12 +1372,14 @@ "leave": "ElhagyÃĄs", "leave_album": "Album elhagyÃĄsa", "lens_model": "Objektív modell", - "less": "Kevesebb", "let_others_respond": "MÃĄsok is reagÃĄlhatnak", "level": "Szint", "library": "KÊptÃĄr", "library_add_folder": "KÃļnyvtÃĄr hozzÃĄadÃĄsa", "library_edit_folder": "KÃļnyvtÃĄr szerkesztÊse", + "library_options": "KÊptÃĄr beÃĄllítÃĄsok", + "library_page_device_albums": "Albumok az eszkÃļzÃļn", + "library_page_new_album": "Új album", "library_page_sort_asset_count": "Elemek szÃĄma", "library_page_sort_created": "LÊtrehozÃĄs ideje", "library_page_sort_last_modified": "UtolsÃŗ mÃŗdosítÃĄs ideje", @@ -1274,14 +1389,11 @@ "light_theme": "VilÃĄgos tÊmÃĄra vÃĄltÃĄs", "like": "Tetszik", "like_deleted": "ReakciÃŗ tÃļrÃļlve", - "link": "Link", "link_motion_video": "Motion videÃŗ hozzÃĄrendelÊse", "link_to_docs": "TovÃĄbbi informÃĄciÃŗÃŠrt nÊzd meg a dokumentÃĄciÃŗt.", "link_to_oauth": "CsatlakoztatÃĄs OAuth-hoz", "linked_oauth_account": "Csatlakoztatott OAuth fiÃŗk", "list": "Lista", - "live": "Élő", - "load_more": "TÃļbb betÃļltÊse", "loading": "BetÃļltÊs", "loading_search_results_failed": "KeresÊsi eredmÊnyek betÃļltÊse sikertelen", "local": "Helyi", @@ -1310,9 +1422,11 @@ "login": "BejelentkezÊs", "login_disabled": "A bejelentkezÊs letiltva", "login_form_api_exception": "API hiba. KÊrljÃŧk, ellenőrid a szerver címÊt, majd prÃŗbÃĄld Ãējra.", + "login_form_back_button_text": "Vissza", "login_form_email_hint": "email@cimed.hu", "login_form_endpoint_hint": "http://szerver-címe:port", "login_form_endpoint_url": "Szerver címe", + "login_form_err_http": "KÊrjÃŧk, hogy egy http:// vagy https:// címet adj meg", "login_form_err_invalid_email": "ÉrvÊnytelen email cím", "login_form_err_invalid_url": "ÉrvÊnytelen cím", "login_form_err_leading_whitespace": "Az első karakter szÃŗkÃļz", @@ -1322,7 +1436,8 @@ "login_form_failed_login": "Hiba a bejelentkezÊs kÃļzben, ellenőrizd a szerver címÊt, az emailt Ês a jelszÃŗt", "login_form_handshake_exception": "Handshake hiba tÃļrtÊnt a szerverrel. EngedÊlyezd a sajÃĄt alÃĄÃ­rÃĄsÃē tanÃēsítvÃĄnyok hasznÃĄlatÃĄt a beÃĄllítÃĄsokban, ha ilyen tanÃēsítvÃĄnyt hasznÃĄlsz.", "login_form_password_hint": "jelszÃŗ", - "login_form_server_empty": "Add meg a szerver címÊt", + "login_form_save_login": "Maradjon bejelentkezve", + "login_form_server_empty": "Add meg a szerver címÊt.", "login_form_server_error": "Nem sikerÃŧlt kapcsolÃŗdni a szerverhez.", "login_has_been_disabled": "BejelentkezÊs le van tiltva.", "login_password_changed_error": "Nem sikerÃŧlt mÃŗdosítani a jelszÃŗt", @@ -1339,6 +1454,7 @@ "maintenance_action_restore": "AdatbÃĄzis helyreÃĄllítÃĄsa", "maintenance_description": "Az Immich maintenance mode-ba lett ÃĄllítva.", "maintenance_end": "KarbantartÃĄsi mÃŗd kikapcsolÃĄsa", + "maintenance_end_error": "KarbantartÃĄsi mÃŗd kikapcsolÃĄsa sikertelen.", "maintenance_logged_in_as": "Bejelentkezve mint: {user}", "maintenance_restore_from_backup": "HelyreÃĄllítÃĄs biztonsÃĄgi mentÊsből", "maintenance_restore_library": "KÃļnyvtÃĄr helyreÃĄllítÃĄsa", @@ -1360,10 +1476,11 @@ "maintenance_title": "Átmenetileg nem elÊrhető", "make": "GyÃĄrtÃŗ", "manage_geolocation": "Helyadatok kezelÊse", - "manage_media_access_rationale": "Ez az engedÊly szÃŧksÊges a mozgathatÃŗ elemek kukÃĄba valÃŗ ÃĄthelyezÊshez Ês az onnan valÃŗ visszaÃĄllítÃĄshoz.", + "manage_media_access_rationale": "Ez az engedÊly szÃŧksÊges a mozgathatÃŗ eszkÃļzÃļk kukÃĄba valÃŗ ÃĄthelyezÊshez Ês onnan valÃŗ visszaÃĄllítÃĄshoz.", "manage_media_access_settings": "BeÃĄllítÃĄsok megnyitÃĄsa", "manage_media_access_subtitle": "EngedÊlyezze az Immichnek a mÊdiafÃĄjlok kezelÊsÊt Ês ÃĄthelyezÊsÊt.", "manage_media_access_title": "MÊdiakezelÊs hozzÃĄfÊrÊs", + "manage_shared_links": "MegosztÃĄsi linkek kezelÊse", "manage_sharing_with_partners": "Partnerekkel valÃŗ megosztÃĄs kezelÊse", "manage_the_app_settings": "AlkalmazÃĄs beÃĄllítÃĄsainak kezelÊse", "manage_your_account": "SajÃĄt fiÃŗkod kezelÊse", @@ -1371,11 +1488,13 @@ "manage_your_devices": "Bejelentkezett eszkÃļzÃļk kezelÊse", "manage_your_oauth_connection": "OAuth kapcsolÃŗdÃĄs kezelÊse", "map": "TÊrkÊp", + "map_assets_in_bounds": "{count, plural, =0 {Nincs fotÃŗ ezen a terÃŧleten} one {# fotÃŗ} other {# fotÃŗ}}", "map_cannot_get_user_location": "A helymeghatÃĄrozÃĄs nem sikerÃŧlt", + "map_location_dialog_yes": "Igen", "map_location_picker_page_use_location": "KivÃĄlasztott hely hasznÃĄlata", "map_location_service_disabled_content": "A helymeghatÃĄrozÃĄs szolgÃĄltatÃĄst engedÊlyezni kell a jelenlegi helyednÊl lÊvő elemek megjelenítÊsÊhez. SzeretnÊd most engedÊlyezni?", "map_location_service_disabled_title": "HelymeghatÃĄrozÃĄs szolgÃĄltatÃĄs letiltva", - "map_marker_for_image": "TÊrkÊpjelÃļlő a kÃļvetkező helyen kÊszÃŧlt kÊphez: {city}, {country}", + "map_marker_for_images": "{country}, {city} helyen kÊszÃŧlt kÊpek tÊrkÊpjelÃļlője", "map_marker_with_image": "TÊrkÊpjelÃļlő kÊppel", "map_no_location_permission_content": "A helymeghatÃĄrozÃĄst engedÊlyezni kell a jelenlegi helyednÊl lÊvő elemek megjelenítÊsÊhez. SzeretnÊd most engedÊlyezni?", "map_no_location_permission_title": "HelymeghatÃĄrozÃĄs letiltva", @@ -1385,53 +1504,24 @@ "map_settings_date_range_option_days": "ElmÃēlt {days} nap", "map_settings_date_range_option_year": "ElmÃēlt Êv", "map_settings_date_range_option_years": "ElmÃēlt {years} Êv", + "map_settings_dialog_title": "TÊrkÊp beÃĄllítÃĄsok", "map_settings_include_show_archived": "ArchivÃĄltakkal egyÃŧtt", "map_settings_include_show_partners": "Partnerekkel egyÃŧtt", "map_settings_only_show_favorites": "Csak kedvencek megjelenítÊse", "map_settings_theme_settings": "TÊrkÊp tÊma", + "map_zoom_to_see_photos": "Kicsinyítsd, hogy lÃĄss fÊnykÊpeket", "mark_all_as_read": "Összes megjelÃļlÊse olvasottkÊnt", + "mark_as_read": "MegjelÃļlÊs olvasottkÊnt", "marked_all_as_read": "Összes megjelÃļlve olvasottkÊnt", "matches": "Azonosak", "matching_assets": "KapcsolÃŗdÃŗ elemek", - "media_chrome": { - "auto": "Automatikus", - "captions": "Feliratok", - "captions_off": "Ki", - "closed_captions": "feliratok", - "decode_error": "DekÃŗdolÃĄsi hiba", - "disable_captions": "Feliratok kikapcsolÃĄsa", - "enable_captions": "Feliratok bekapcsolÃĄsa", - "enter_fullscreen_mode": "Teljes kÊpernyős mÃŗd bekapcsolÃĄsa", - "exit_fullscreen_mode": "Teljes kÊpernyős mÃŗd kikapcsolÃĄsa", - "loop": "IsmÊtel", - "media_error_description": "MÊdiahiba miatt a lejÃĄtszÃĄs megszakadt. Lehet, hogy a fÃĄjl sÊrÃŧlt, vagy a bÃļngÊsző nem tÃĄmogatja ezt a formÃĄtumot.", - "media_loading": "mÊdia betÃļltÊse", - "mute": "NÊmít", - "network_error": "HÃĄlÃŗzati hiba", - "network_error_description": "HÃĄlÃŗzati hiba miatt a mÊdia letÃļltÊse nem sikerÃŧlt.", - "not_supported_error": "Nem tÃĄmogatott forrÃĄs", - "playback_rate": "LejÃĄtszÃĄsi sebessÊg", - "playback_rate_current": "aktuÃĄlis lejÃĄtszÃĄsi sebessÊg", - "playback_rate_value": "LejÃĄtszÃĄsi sebessÊg {playbackRate}", - "playback_time": "lejÃĄtszÃĄsi idő", - "quality": "MinősÊg", - "second": "mÃĄsodperc", - "seconds": "mÃĄsodperc", - "time_value_of_total_time": "{currentTime} a {totalTime}-ből", - "time_value_remaining": "{time} van hÃĄtra", - "unmute": "NÊmítÃĄs kikapcsolÃĄsa", - "unsupported_error_description": "Nem tÃĄmogatott hiba tÃļrtÊnt. A szerver vagy a hÃĄlÃŗzat meghibÃĄsodott, vagy a bÃļngÊsző nem tÃĄmogatja ezt a formÃĄtumot.", - "video_not_loaded_unknown_time": "A videÃŗ nem tÃļltődÃļtt be, ismeretlen hossz.", - "video_player": "videÃŗ lejÃĄtszÃŗ", - "volume": "hangerő" - }, "media_type": "MÊdiatípus", "memories": "EmlÊkek", "memories_all_caught_up": "NaprakÊsz vagy", "memories_check_back_tomorrow": "NÊzz vissza holnap Ãējabb emlÊkekÊrt", "memories_setting_description": "Állítsd be, hogy mik jelenjenek meg az emlÊkeid kÃļzt", "memories_start_over": "ÚjrakezdÊs", - "memories_swipe_to_close": "A bezÃĄrÃĄshoz hÃēzd felfelÊ a kÊpernyőt", + "memories_swipe_to_close": "BezÃĄrÃĄshoz sÃļpÃļrd ki felfelÊ", "memory": "EmlÊk", "memory_lane_title": "EmlÊkek {title}", "menu": "MenÃŧ", @@ -1441,31 +1531,34 @@ "merge_people_prompt": "Biztosan Ãļsszevonod ezeket a szemÊlyeket? Ez a mÅąvelet nem visszavonhatÃŗ.", "merge_people_successfully": "SzemÊlyek sikeresen Ãļsszevonva", "merged_people_count": "Összevonva {count, plural, other {# szemÊly}}", - "minFaces": "MinimÃĄlis arcszÃĄm", - "minFaces_description": "A minimum ÊrtÊke az egy szemÊlyhez kÃļthető felismert arcoknak, mielőtt mentett szemÊlykÊnt lenne kezelve", "minimize": "KicsinyítÊs", "minute": "Perc", "minutes": "Percek", + "mirror_horizontal": "Vízszintesen", + "mirror_vertical": "FÃŧggőlegesen", "missing": "HiÃĄnyzÃŗk", "mobile_app": "MobilapplikÃĄciÃŗ", "mobile_app_download_onboarding_note": "TÃļltse le a kiegÊszítő mobilalkalmazÃĄst az alÃĄbbi opciÃŗk segítsÊgÊvel", "model": "Modell", - "modify_date": "DÃĄtum mÃŗdosítÃĄsa", "month": "HÃŗnap", + "monthly_title_text_date_format": "y MMMM", "more": "TovÃĄbbiak", - "motion": "MozgÃĄs", "move": "ÁthelyezÊs", + "move_down": "Lejjebb", "move_off_locked_folder": "ÁtmozgatÃĄs a zÃĄrolt mappÃĄbÃŗl", "move_to": "MozgatÃĄs", "move_to_device_trash": "ÁthelyezÊs az eszkÃļz szemetesÊbe", "move_to_lock_folder_action_prompt": "{count} hozzÃĄadva a zÃĄrolt mappÃĄhoz", "move_to_locked_folder": "ÁthelyezÊs a zÃĄrolt mappÃĄba", "move_to_locked_folder_confirmation": "Ezek a kÊpek Ês videÃŗk az Ãļsszes albumbÃŗl kikerÃŧlnek, Ês csak a zÃĄrolt mappÃĄban lesznek elÊrhetőek", + "move_up": "Feljebb", + "moved_to_archive": "{count, plural, one {# elem} other {# elem}} archivÃĄlva", + "moved_to_library": "{count, plural, one {# elem} other {# elem}} mÃĄsik kÃļnyvtÃĄrba helyezve", "moved_to_trash": "Áthelyezve a lomtÃĄrba", + "multiselect_grid_edit_date_time_err_read_only": "Csak-olvashatÃŗ elem(ek) dÃĄtuma nem mÃŗdosíthatÃŗ, ezÊrt kihagyjuk", + "multiselect_grid_edit_gps_err_read_only": "Csak-olvashatÃŗ elem(ek) helye nem mÃŗdosíthatÃŗ, ezÊrt kihagyjuk", "mute_memories": "EmlÊkek elnÊmítÃĄsa", "my_albums": "SajÃĄt albumaim", - "my_immich_description": "Az aktuÃĄlis oldal mÃĄsolÃĄsa Én Immich-em linkkÊnt", - "my_immich_title": "Én Immich-em link", "name": "NÊv", "name_or_nickname": "NÊv vagy becenÊv", "name_required": "KÃļtelező megadni egy nevet", @@ -1480,11 +1573,12 @@ "never": "Soha", "new_album": "Új album", "new_api_key": "Új API kulcs", - "new_feature": "Új funkciÃŗ", + "new_date_range": "Új dÃĄtumtartomÃĄny", "new_password": "Új jelszÃŗ", "new_person": "Új szemÊly", "new_pin_code": "Új PIN kÃŗd", "new_pin_code_subtitle": "Ez az első alkalom hogy megnyitod a zÃĄrolt mappÃĄt. Hozz lÊtre egy jelszÃŗt a mappa biztonsÃĄgos elÊrÊsÊhez", + "new_timeline": "Új idővonal", "new_update": "Új frissítÊs", "new_user_created": "Új felhasznÃĄlÃŗ lÊtrehozva", "new_version_available": "ÚJ VERZIÓ ÉRHETŐ EL", @@ -1492,6 +1586,7 @@ "next": "KÃļvetkező", "next_memory": "KÃļvetkező emlÊk", "no": "Nem", + "no_actions_added": "MÊg nincsenek mÅąveletek", "no_albums_found": "Nem talÃĄlhatÃŗk albumok", "no_albums_message": "FotÃŗid Ês videÃŗid rendszerezÊsÊhez hozz lÊtre egy Ãēj albumot", "no_albums_with_name_yet": "Úgy tÅąnik, hogy ilyen nÊvvel mÊg nincs albumod.", @@ -1502,13 +1597,16 @@ "no_cast_devices_found": "Nem talÃĄlhatÃŗ eszkÃļz vetítÊshez", "no_checksum_local": "Nincs elÊrhető ellenőrző Ãļsszeg - a helyi elemek nem kÊrhetők le", "no_checksum_remote": "Nincs elÊrhető ellenőrző Ãļsszeg - a tÃĄvoli elem nem kÊrhető le", + "no_configuration_needed": "Nincs szÃŧksÊg konfigurÃĄciÃŗra", "no_devices": "Nincs engedÊlyezett eszkÃļz", "no_duplicates_found": "Nem talÃĄlhatÃŗk duplikÃĄtumok.", "no_exif_info_available": "Nincs elÊrhető Exif informÃĄciÃŗ", "no_explore_results_message": "TÃļlts fel tÃļbb kÊpet, hogy bÃļngÊszhesd a gyÅąjtemÊnyed.", "no_favorites_message": "Add hozzÃĄ a kedvencekhez, hogy gyorsan megtalÃĄld a legjobb kÊpeidet Ês videÃŗidat", + "no_filters_added": "MÊg nincsenek szÅąrők", "no_libraries_message": "Hozz lÊtre kÃŧlső kÊptÃĄrat a fÊnykÊpeid Ês videÃŗid megtekintÊsÊhez", "no_local_assets_found": "Nem talÃĄlhatÃŗk helyi eszkÃļzÃļk ezzel az ellenőrzőÃļsszeggel", + "no_location_set": "Nincs hely megadva", "no_locked_photos_message": "A zÃĄrolt mappÃĄban elhelyezett fotÃŗk Ês videÃŗk rejtettek, Ês nem jelennek meg a kÃļnyvtÃĄrad bÃļngÊszÊse vagy keresÊse kÃļzben sem.", "no_name": "Nincs nÊv", "no_notifications": "Nincsenek ÊrtesítÊsek", @@ -1518,19 +1616,14 @@ "no_results": "Nincs talÃĄlat", "no_results_description": "PrÃŗbÃĄlkozz szinonimÃĄkkal vagy ÃĄltalÃĄnosabb kulcsszavakkal", "no_shared_albums_message": "Hozz lÊtre egy Ãēj albumot, hogy megoszthasd fÊnykÊpeid Ês videÃŗid mÃĄsokkal", - "no_steps": "MÊg nem adtak hozzÃĄ lÊpÊseket", + "no_uploads_in_progress": "Nincs folyamatban lÊvő feltÃļltÊs", "none": "Semelyik", "not_allowed": "Nem engedÊlyezett", "not_available": "N/A", "not_in_any_album": "Nincs albumban", "not_selected": "Nincs kivÃĄlasztva", - "not_set": "Nincs beÃĄllítva", "notes": "MegjegyzÊsek", "nothing_here_yet": "MÊg semmi sincs itt", - "notification_backup_reliability": "EngedÊlyezd az ÊrtesítÊseket a hÃĄttÊrben futÃŗ biztonsÃĄgi mentÊs megbízhatÃŗsÃĄgÃĄnak javítÃĄsa ÊrdekÊben", - "notification_enabled_list_tile_content": "Az Immich ÊrtesítÊseket hasznÃĄl a hÃĄttÊrben tÃļrtÊnő biztonsÃĄgi mentÊshez. Ezeket a kÊszÃŧlÊked beÃĄllítÃĄsaiban kezelheted.", - "notification_enabled_list_tile_open_button": "BeÃĄllítÃĄsok megnyitÃĄsa", - "notification_enabled_list_tile_title": "ÉrtesítÊsek engedÊlyezve", "notification_permission_dialog_content": "Az ÊrtesítÊsek bekapcsolÃĄsÃĄhoz a BeÃĄllítÃĄsok menÃŧben vÃĄlaszd ki az EngedÊlyezÊs-t.", "notification_permission_list_tile_content": "ÉrtesítÊsek engedÊlyezÊse.", "notification_permission_list_tile_enable_button": "ÉrtesítÊsek engedÊlyezÊse", @@ -1542,8 +1635,6 @@ "obtainium_configurator": "Obtainium konfigurÃĄtor", "obtainium_configurator_instructions": "Az Obtainium segítsÊgÊvel kÃļzvetlenÃŧl az Immich GitHub-os kiadÃĄsÃĄbÃŗl telepítheted Ês frissítheted az Android-alkalmazÃĄst. Hozz lÊtre egy API-kulcsot Ês vÃĄlassz egy vÃĄltozatot az Obtainium konfigurÃĄciÃŗs hivatkozÃĄs elkÊszítÊsÊhez", "ocr": "OCR", - "ocr_body": "Az Immich mostantÃŗl elolvassa a fotÃŗkon szereplő szÃļveget, így a szÃļveg alapjÃĄn is kereshetsz kÃļzÃļttÃŧk.", - "ocr_title": "SzÃļvegkeresÊs a fÊnykÊpeidben", "official_immich_resources": "Hivatalos Immich forrÃĄsok", "offline": "Nem elÊrhető (offline)", "offset": "EltolÃĄs", @@ -1562,8 +1653,6 @@ "open": "Nyitva", "open_calendar": "NaptÃĄr megnyitÃĄsa", "open_in_browser": "MegnyitÃĄs bÃļngÊszőben", - "open_in_immich_body": "Állítsd be az Immich-et galÊriÃĄdkÊnt az Androidon, hogy a fÊnykÊpeket kÃļzvetlenÃŧl mÃĄs alkalmazÃĄsokbÃŗl nyithasd meg.", - "open_in_immich_title": "FotÃŗk megnyitÃĄsa az Immich-ben", "open_in_map_view": "MegnyitÃĄs tÊrkÊp nÊzetben", "open_in_openstreetmap": "MegnyitÃĄs OpenStreetMap-ben", "open_the_search_filters": "KeresÊsi szÅąrők megnyitÃĄsa", @@ -1572,10 +1661,10 @@ "organize_into_albums": "Albumokba rendezÊs", "organize_into_albums_description": "MeglÊvő fotÃŗk albumokba helyezÊse, a jelenlegi szinkronizÃĄciÃŗs beÃĄllítÃĄsok alapjÃĄn", "organize_your_library": "Rendszerezd a kÊptÃĄradat", - "orientation": "OrientÃĄciÃŗ", "original": "eredeti", "other": "EgyÊb", "other_devices": "EgyÊb eszkÃļzÃļk", + "other_entities": "EgyÊb entitÃĄsok", "other_variables": "EgyÊb vÃĄltozÃŗk", "owned": "Tulajdonos", "owner": "Tulajdonos", @@ -1585,9 +1674,12 @@ "partner_can_access_assets": "Minden fÊnykÊped Ês videÃŗd, kivÊve az archivÃĄltak Ês a tÃļrÃļltek", "partner_can_access_location": "A helyszín, ahol a fotÃŗkat kÊszítettÊk", "partner_list_user_photos": "{user} fÊnykÊpei", + "partner_list_view_all": "Összes megjelenítÊse", "partner_page_empty_message": "MÊg senkivel nem osztottad meg a fÊnykÊpeidet.", "partner_page_no_more_users": "Nincs tÃļbb hozzÃĄadhatÃŗ felhasznÃĄlÃŗ", + "partner_page_partner_add_failed": "Partner hozzÃĄadÃĄsa sikertelen", "partner_page_select_partner": "Partner kivÃĄlasztÃĄsa", + "partner_page_shared_to_title": "Megosztva", "partner_page_stop_sharing_content": "{partner} nem fog tÃļbbÊ hozzÃĄfÊrni a fotÃŗidhoz.", "partner_sharing": "Partnerrel megosztÃĄs", "partners": "Partnerek", @@ -1609,6 +1701,8 @@ "people": "SzemÊlyek", "people_edits_count": "{count, plural, other {# szemÊly}} mÃŗdosítva", "people_feature_description": "SzemÊlyek szerint csoportosított fÊnykÊpek Ês videÃŗk bÃļngÊszÊse", + "people_selected": "{count, plural, other {# szemÊly}} kivÃĄlasztva", + "people_sidebar_description": "SzemÊlyek link megjelenítÊse az oldalsÃĄvban", "permanent_deletion_warning": "FigyelmeztetÊs vÊgleges tÃļrlÊsről", "permanent_deletion_warning_setting_description": "Figyelmeztessen elemek vÊgleges tÃļrlÊse előtt", "permanently_delete": "VÊgleges tÃļrlÊs", @@ -1618,17 +1712,28 @@ "permanently_deleted_assets_count": "{count, plural, other {# elem}} vÊglegesen tÃļrÃļlve", "permission": "JogosultsÃĄg", "permission_empty": "A jogosultsÃĄg nem hagyhatÃŗ Ãŧresen", + "permission_onboarding_back": "Vissza", + "permission_onboarding_continue_anyway": "FolytatÃĄs mindenkÊpp", + "permission_onboarding_get_started": "VÃĄgjunk bele", + "permission_onboarding_go_to_settings": "UgrÃĄs a beÃĄllítÃĄsokhoz", + "permission_onboarding_permission_denied": "HozzÃĄfÊrÊs megtagadva. Az Immich hasznÃĄlatÃĄhoz engedÊlyezni kell a fotÃŗ Ês videÃŗ hozzÃĄfÊrÊst a BeÃĄllítÃĄsokban.", + "permission_onboarding_permission_granted": "HozzÃĄfÊrÊs engedÊlyezve! Minden kÊszen ÃĄll.", + "permission_onboarding_permission_limited": "KorlÃĄtozott hozzÃĄfÊrÊs. Ha szeretnÊd, hogy az Immich a teljes galÊria gyÅąjtemÊnyedet mentse Ês kezelje, akkor a BeÃĄllítÃĄsokban engedÊlyezd a fotÃŗ Ês videÃŗ jogosultsÃĄgokat.", + "permission_onboarding_request": "EngedÊlyezni kell, hogy az Immich hozzÃĄfÊrjen a kÊpeidhez Ês videÃŗidhoz.", "person": "SzemÊly", "person_age_months": "{months, plural, one {# hÃŗnapja} other {# hÃŗnapja}}", "person_age_year_months": "Egy Êv Ês {months, plural, one {# hÃŗnapja} other {# hÃŗnapja}}", - "person_age_years": "{years, plural, one {# Êves} other {# Êves}}", + "person_age_years": "{years, plural, other {# Êve}}", "person_birthdate": "SzÃŧletett: {date}", "person_hidden": "{name}{hidden, select, true { (rejtett)} other {}}", "person_recognized": "SzemÊly felismerve", + "person_selected": "SzemÊly kivÃĄlasztva", "photo_shared_all_users": "Úgy tÅąnik, hogy mÃĄr mindenkivel megosztottad a fÊnykÊpeidet, vagy nincs senki, akivel meg tudnÃĄd osztani.", "photos": "FÊnykÊpek", "photos_and_videos": "FÊnykÊpek Ês videÃŗk", + "photos_count": "{count, plural, one {{count, number} fotÃŗ} other {{count, number} fotÃŗ}}", "photos_from_previous_years": "FÊnykÊpek az előző Êvekből", + "photos_only": "Csak kÊpek", "pick_a_location": "Hely vÃĄlasztÃĄsa", "pick_custom_range": "Egyedi tartomÃĄny", "pick_date_range": "VÃĄlasszon egy dÃĄtumtartomÃĄnyt", @@ -1647,8 +1752,6 @@ "play_original_video_setting_description": "A rendszer az eredeti videÃŗk lejÃĄtszÃĄsÃĄt rÊszesíti előnyben a transzkÃŗdolt verziÃŗkkal szemben. Ha az eredeti fÃĄjl nem kompatibilis, előfordulhat, hogy nem jÃĄtszhatÃŗ le megfelelően.", "play_transcoded_video": "TranszkÃŗdolt videÃŗ lejÃĄtszÃĄsa", "please_auth_to_access": "KÊrlek jelentkezz be a hozzÃĄfÊrÊshez", - "plugin_method_filter_type": "SzÅąrő", - "plugin_method_filter_type_description": "Ez a metÃŗdus kÊpes kiszÅąrni az esemÊnyeket, Ês feltÊtelekhez kÃļtve megakadÃĄlyozhatja a kÃļvetkező lÊpÊsek vÊgrehajtÃĄsÃĄt", "port": "Port", "preferences_settings_subtitle": "AlkalmazÃĄsbeÃĄllítÃĄsok kezelÊse", "preferences_settings_title": "BeÃĄllítÃĄsok", @@ -1665,11 +1768,11 @@ "privacy": "MagÃĄnszfÊra", "profile": "Profil", "profile_drawer_app_logs": "NaplÃŗk", + "profile_drawer_client_server_up_to_date": "A kliens Ês a szerver is naprakÊsz", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Csak olvashatÃŗ mÃŗd engedÊlyezve. A kilÊpÊshez hosszan nyomja meg a felhasznÃĄlÃŗi avatar ikont.", "profile_image_of_user": "{user} profilkÊpe", "profile_picture_set": "ProfilkÊp beÃĄllítva.", - "projection_type": "VetítÊsi típus", "public_album": "NyilvÃĄnos album", "public_share": "NyilvÃĄnos megosztÃĄs", "purchase_account_info": "TÃĄmogatÃŗ", @@ -1688,6 +1791,7 @@ "purchase_individual_description_2": "TÃĄmogatÃŗ ÃĄllapot", "purchase_individual_title": "MagÃĄnszemÊly", "purchase_input_suggestion": "Van mÃĄr termÊkkulcsod? Add meg itt", + "purchase_license_subtitle": "Az Immich megvÃĄsÃĄrlÃĄsÃĄval tÃĄmogasd a szolgÃĄltatÃĄs folyamatos fejlesztÊsÊt", "purchase_lifetime_description": "Élettartamra szÃŗlÃŗ vÃĄsÃĄrlÃĄs", "purchase_option_title": "VÁSÁRLÁSI LEHETŐSÉGEK", "purchase_panel_info_1": "Az Immich kÊszítÊse sok időt Ês erőfeszítÊst igÊnyel, ezÊrt fÅ‘ÃĄllÃĄsban foglalkoztatunk szoftvermÊrnÃļkÃļket, hogy annyira jÃŗ programmÃĄ tegyÃŧk, amennyire csak lehet. KÃŧldetÊsÃŧnk, hogy a nyílt forrÃĄskÃŗdÃē szoftver Ês etikus Ãŧzleti gyakorlat fenntarthatÃŗ bevÊteli forrÃĄs legyen a fejlesztőknek, Ês hogy lÊtrehozzunk egy magÃĄnszfÊrÃĄt tiszteletben tartÃŗ ÃļkoszisztÊmÃĄt, ami valÃŗdi alternatívÃĄjÃĄt jelenti a felhasznÃĄlÃŗkat kizsÃĄkmÃĄnyolÃŗ felhőalapÃē szolgÃĄltatÃĄsoknak.", @@ -1720,19 +1824,19 @@ "reassigned_assets_to_new_person": "{count, plural, other {# elem}} hozzÃĄrendelve egy Ãēj szemÊlyhez", "reassing_hint": "KijelÃļlt elemek lÊtező szemÊlyhez rendelÊse", "recent": "Friss", + "recent_albums": "LegutÃŗbbi albumok", "recent_searches": "LegutÃŗbbi keresÊsek", "recently_added": "NemrÊg hozzÃĄadott", - "recently_added_body": "Egy kÃŧlÃļn oldalon azonnal megtekintheted az Ãļsszes legutÃŗbb hozzÃĄadott elemet.", - "recently_added_description": "Az Immich-re valÃŗ feltÃļltÊs időpontja szerint rendezve bÃļngÊszheted a fÃĄjljaid", "recently_added_page_title": "NemrÊg hozzÃĄadott", - "recently_added_title": "LegutÃŗbb hozzÃĄadott", "recently_taken": "NemrÊg kÊszített", + "recently_taken_page_title": "NemrÊg kÊszített", "refresh": "FrissítÊs", "refresh_encoded_videos": "ÁtkÃŗdolt videÃŗk frissítÊse", "refresh_faces": "Arcok frissítÊse", "refresh_metadata": "Metaadatok frissítÊse", "refresh_thumbnails": "BÊlyegkÊpek frissítÊse", "refreshed": "Frissítve", + "refreshes_every_file": "Minden lÊtező Ês Ãēj fÃĄjl ÃējraolvasÃĄsa", "refreshing_encoded_video": "ÁtkÃŗdolt videÃŗk frissítÊse folyamatban", "refreshing_faces": "Arcok frissítÊse folyamatban", "refreshing_metadata": "Metaadatok frissítÊse folyamatban", @@ -1745,7 +1849,7 @@ "remove_assets_shared_link_confirmation": "Biztosan el szeretnÊl tÃĄvolítani {count, plural, one {# elemet} other {# elemet}} ebből a megosztott linkből?", "remove_assets_title": "Elemek eltÃĄvolítÃĄsa?", "remove_custom_date_range": "EgyÊni időintervallum eltÃĄvolítÃĄsa", - "remove_filter": "SzÅąrő eltÃĄvolítÃĄsa", + "remove_deleted_assets": "TÃļrÃļlt elemek eltÃĄvolítÃĄsa", "remove_from_album": "EltÃĄvolítÃĄs az albumbÃŗl", "remove_from_album_action_prompt": "{count} eltÃĄvolítva az albumbÃŗl", "remove_from_favorites": "EltÃĄvolítÃĄs a kedvencekből", @@ -1756,22 +1860,29 @@ "remove_memory": "EmlÊk eltÃĄvolítÃĄsa", "remove_photo_from_memory": "KÊp eltÃĄvolítÃĄsa az emlÊkből", "remove_tag": "Címke eltÃĄvolítÃĄsa", + "remove_url": "URL eltÃĄvolítÃĄsa", "remove_user": "FelhasznÃĄlÃŗ eltÃĄvolítÃĄsa", "removed_api_key": "API kulcs eltÃĄvolítva: {name}", "removed_from_archive": "ArchívumbÃŗl eltÃĄvolítva", "removed_from_favorites": "Kedvencekből eltÃĄvolítva", "removed_from_favorites_count": "A kedvencekből {count, plural, other {# elem}} eltÃĄvolítva", "removed_memory": "EltÃĄvolított emlÊk", + "removed_photo_from_memory": "FotÃŗ tÃļrÃļlve az emlÊkből", "removed_tagged_assets": "Címke eltÃĄvolítva {count, plural, one {# elemről} other {# elemről}}", "rename": "ÁtnevezÊs", + "repair": "JavítÃĄs", + "repair_no_results_message": "A nem nyilvÃĄntartott Ês a hiÃĄnyzÃŗ fÃĄjlok itt jelennek meg", + "replace_with_upload": "Csere feltÃļltÊssel", "repository": "TÃĄrolÃŗ", "require_password": "JelszÃŗ megadÃĄsa szÃŧksÊges", + "require_user_to_change_password_on_first_login": "A felhasznÃĄlÃŗnak kÃļtelező megvÃĄltoztatnia a jelszavÃĄt az első bejelentkezÊskor", "rescan": "ÚjraszkennelÊs", "reset": "VisszaÃĄllítÃĄs", "reset_password": "JelszÃŗ visszaÃĄllítÃĄsa", "reset_people_visibility": "SzemÊlyek lÃĄthatÃŗsÃĄgÃĄnak visszaÃĄllítÃĄsa", "reset_pin_code": "PIN kÃŗd visszaÃĄllítÃĄsa", "reset_pin_code_description": "Ha elfelejtetted a PIN-kÃŗdod, vedd fel a kapcsolatot a szerver rendszergazdÃĄjÃĄval, hogy visszaÃĄllíthassa azt", + "reset_pin_code_success": "PIN kÃŗd sikeresen visszaÃĄllítva", "reset_pin_code_with_password": "A PIN kÃŗdod mindig visszaÃĄllíthatod a jelszavaddal", "reset_sqlite": "SQLite adatbÃĄzis visszaÃĄllítÃĄsa", "reset_sqlite_clear_app_data": "Adat kiÃŧrítÊs", @@ -1785,10 +1896,12 @@ "resolved_all_duplicates": "Minden duplikÃĄtum feloldÃĄsa", "restore": "VisszaÃĄllít", "restore_all": "Minden visszaÃĄllítÃĄsa", + "restore_trash_action_prompt": "{count} visszaÃĄllítva a lomtÃĄrbÃŗl", "restore_user": "FelhasznÃĄlÃŗ visszaÃĄllítÃĄsa", "restored_asset": "VisszaÃĄllított elem", "resume": "FolytatÃĄs", - "resume_paused_jobs": "{count, plural, one {# szÃŧneteltetett feladat} other {# szÃŧneteltetett feladat}} folytatÃĄsa", + "resume_paused_jobs": "FolytatÃĄs {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "FeltÃļltÊs ÃējraprÃŗbÃĄlÃĄsa", "review_duplicates": "DuplikÃĄtumok ÃĄttekintÊse", "review_large_files": "Nagy fÃĄjlok ÃĄttekintÊse", "role": "JogkÃļr", @@ -1796,6 +1909,7 @@ "role_viewer": "Megjelenítő", "running": "FutÃŗ", "save": "MentÊs", + "save_to_gallery": "MentÊs a galÊriÃĄba", "saved": "Mentve", "saved_api_key": "API kulcs elmentve", "saved_profile": "Profil elmentve", @@ -1806,21 +1920,18 @@ "scan": "ÁtfÊsÃŧl", "scan_all_libraries": "Minden kÊptÃĄr ÃĄtfÊsÃŧlÊse", "scan_library": "BeolvasÃĄs", + "scan_settings": "ÁtfÊsÃŧlÊsi beÃĄllítÃĄsok", "scanning": "ÁtfÊsÃŧlÊs folyamatban", "scanning_for_album": "Albumok ÃĄtfÊsÃŧlÊse...", - "screencast_mode_description": "BillentyÅązet Ês egÊr esemÊny megjelenítÊse a kÊpernyőn", - "screencast_mode_title": "Screencast mÃŗd kapcsolÃĄsa", "search": "KeresÊs", "search_albums": "Albumok keresÊse", "search_by_context": "KeresÊs tartalom alapjÃĄn", "search_by_description": "KeresÊs leírÃĄs alapjÃĄn", - "search_by_description_example": "PÊldÃĄul: tÃērÃĄzÃŗs nap a hegyekben", + "search_by_description_example": "TÃērÃĄzÃŗs nap SzapÃĄban", "search_by_filename": "KeresÊs fÃĄjlnÊv vagy kiterjesztÊs alapjÃĄn", "search_by_filename_example": "pÊldÃĄul IMG_1234.JPG vagy PNG", - "search_by_full_path": "KeresÊs teljes elÊrÊsi Ãēt vagy mappa alapjÃĄn", - "search_by_full_path_example": "/Jancsi/Projektek/3D_nyomtatÃĄs/2026-07-01 – kereshetsz a Projektek, 3D, NyomtatÃĄs, 2026 stb. kifejezÊsekre.", "search_by_ocr": "KeresÊs szÃļvegfelismerÊssel (OCR)", - "search_by_ocr_example": "PÊldÃĄul: szÃĄmla", + "search_by_ocr_example": "Latte", "search_camera_lens_model": "KeresÊs objektívmodell alapjÃĄn...", "search_camera_make": "KameragyÃĄrtÃŗ keresÊse...", "search_camera_model": "Kameramodell keresÊse...", @@ -1838,6 +1949,7 @@ "search_filter_location_title": "VÃĄlassz helyet", "search_filter_media_type": "MÊdia típus", "search_filter_media_type_title": "VÃĄlassz mÊdia típust", + "search_filter_ocr": "KeresÊs szÃļvegfelismerÊssel (OCR)", "search_filter_people_title": "VÃĄlassz embereket", "search_filter_star_rating": "ÉrtÊkelÊs", "search_filter_tags_title": "VÃĄlasszon címkÊket", @@ -1848,13 +1960,25 @@ "search_no_people_named": "Nincs \"{name}\" nevÅą szemÊly", "search_no_result": "Nincs talÃĄlat, prÃŗbÃĄlj mÃĄs kulcsszavakkal keresni", "search_options": "KeresÊsi lehetősÊgek", + "search_page_categories": "KategÃŗriÃĄk", + "search_page_motion_photos": "MozgÃŗkÊpek", + "search_page_no_objects": "Nincs informÃĄciÃŗ a tÃĄrgyakrÃŗl", + "search_page_no_places": "Nincs informÃĄciÃŗ a helyekről", + "search_page_screenshots": "KÊpernyőkÊpek", "search_page_search_photos_videos": "KeresÊs a fotÃŗid Ês videÃŗid kÃļzt", + "search_page_selfies": "Szelfik", + "search_page_things": "Dolgok", "search_page_view_all_button": "Összes megjelenítÊse", + "search_page_your_activity": "TevÊkenysÊgeid", + "search_page_your_map": "TÊrkÊped", "search_people": "SzemÊlyek keresÊse", "search_places": "Helyek keresÊse", "search_rating": "KeresÊs ÊrtÊkelÊs szerint...", + "search_result_page_new_search_hint": "Új keresÊs", "search_settings": "BeÃĄllítÃĄsok keresÊse", "search_state": "Megye/Állam keresÊse...", + "search_suggestion_list_smart_search_hint_1": "Az intelligens keresÊs alapÊrtelmezetten be van kapcsolva, metaadatokat így kereshetsz ", + "search_suggestion_list_smart_search_hint_2": "m:keresÊsi-kifejezÊs", "search_tags": "CímkÊk keresÊse...", "search_timezone": "IdőzÃŗna keresÊse...", "search_type": "Típus keresÊse", @@ -1868,6 +1992,7 @@ "select_albums": "Albumok kivÃĄlasztÃĄsa", "select_all": "Összes kijelÃļlÊse", "select_all_duplicates": "Minden duplikÃĄtum kijelÃļlÊse", + "select_all_in": "Összes kijelÃļlÊse itt: {group}", "select_avatar_color": "AvatÃĄr színÊnek vÃĄlasztÃĄsa", "select_count": "{count, plural, one {# kivÃĄlasztÃĄsa} other {# kivÃĄlasztÃĄsa}}", "select_cutoff_date": "HatÃĄrdÃĄtum vÃĄlasztÃĄsa", @@ -1875,13 +2000,14 @@ "select_featured_photo": "AlapÊrtelmezett fÊnykÊp kivÃĄlasztÃĄsa", "select_from_computer": "KivÃĄlasztÃĄs a szÃĄmítÃŗgÊpről", "select_keep_all": "'Megtart' kijelÃļlÊse", + "select_library_owner": "VÃĄlaszd ki a kÊptÃĄr tulajdonosÃĄt", "select_new_face": "Új arc vÃĄlasztÃĄsa", "select_people": "SzemÊlyek kivÃĄlasztÃĄsa", "select_person": "SzemÊly kivÃĄlasztÃĄsa", "select_person_to_tag": "VÃĄlassz ki egy szemÊlyt a megjelÃļlÊshez", "select_photos": "FotÃŗk vÃĄlasztÃĄsa", - "select_quality": "MinősÊg kivÃĄlasztÃĄsa", "select_trash_all": "'LomtÃĄr' kijelÃļlÊse", + "select_user_for_sharing_page_err_album": "Az album lÊtrehozÃĄsa sikertelen", "selected": "KivÃĄlasztott", "selected_count": "{count, plural, other {# kivÃĄlasztva}}", "selected_gps_coordinates": "KivÃĄlasztott GPS kordinÃĄtÃĄk", @@ -1895,7 +2021,7 @@ "server_privacy": "Szerver biztonsÃĄg", "server_restarting_description": "Az oldal pillanatokon belÃŧl frissÃŧl.", "server_restarting_title": "A szerver Ãējraindul", - "server_stats": "Szerver statisztika", + "server_stats": "Szerver statisztikÃĄk", "server_update_available": "SzerverfrissítÊs Êrhető el", "server_version": "Szerver verziÃŗ", "set": "BeÃĄllít", @@ -1914,31 +2040,42 @@ "setting_image_viewer_original_title": "Eredeti kÊp betÃļltÊse", "setting_image_viewer_preview_subtitle": "EngedÊlyezi a kÃļzepes felbontÃĄsÃē kÊp betÃļltÊsÊt. Kikapcsolva vagy az eredeti kÊp tÃļltődik be, vagy csak a bÊlyegkÊp.", "setting_image_viewer_preview_title": "ElőnÊzet betÃļltÊse", + "setting_image_viewer_title": "KÊpek", "setting_languages_apply": "Alkalmaz", "setting_languages_subtitle": "Az alkalmazÃĄs nyelvÊnek megvÃĄltoztatÃĄsa", + "setting_notifications_notify_failures_grace_period": "ÉrtesítÊs a hÃĄttÊrben tÃļrtÊnő mentÊs hibÃĄirÃŗl: {duration}", + "setting_notifications_notify_hours": "{count} Ãŗra", + "setting_notifications_notify_immediately": "azonnal", "setting_notifications_notify_minutes": "{count} perc", + "setting_notifications_notify_never": "soha", "setting_notifications_notify_seconds": "{count} mÃĄsodperc", + "setting_notifications_single_progress_subtitle": "RÊszletes feltÃļltÊsi folyamat informÃĄciÃŗ minden elemről", + "setting_notifications_single_progress_title": "Mutassa a hÃĄttÊrben tÃļrtÊnő mentÊs rÊszletes folyamatÃĄt", "setting_notifications_subtitle": "ÉrtesítÊsi beÃĄllítÃĄsok mÃŗdosítÃĄsa", + "setting_notifications_total_progress_subtitle": "ÁtfogÃŗ feltÃļltÊsi folyamat (kÊsz/Ãļsszes elem)", + "setting_notifications_total_progress_title": "Mutassa a hÃĄttÊrben tÃļrtÊnő mentÊs teljes folyamatÃĄt", "setting_video_viewer_auto_play_subtitle": "A videÃŗk automatikus lejÃĄtszÃĄsa megnyitÃĄskor", "setting_video_viewer_auto_play_title": "VideÃŗk automatikus lejÃĄtszÃĄsa", "setting_video_viewer_looping_title": "IsmÊtlÊs", "setting_video_viewer_original_video_subtitle": "A szerverről tÃļrtÊnő videÃŗlejÃĄtszÃĄs sorÃĄn az eredeti videÃŗ lejÃĄtszÃĄsa mÊg akkor is, ha van optimalizÃĄlt, ÃĄtkÃŗdolt verziÃŗ. AkadozÃŗ lejÃĄtszÃĄst eredmÊnyezhet. A helyi eszkÃļzÃļn eleve elÊrhető videÃŗkat mindenkÊppen eredeti minősÊgben jÃĄtszuk le.", "setting_video_viewer_original_video_title": "Mindig az eredeti videÃŗ lejÃĄtszÃĄsa", "settings": "BeÃĄllítÃĄsok", + "settings_require_restart": "Ennek a beÃĄllítÃĄsnak az ÊrvÊnybe lÊpÊsÊhez indítsd Ãējra az Immich-et", "settings_saved": "BeÃĄllítÃĄsok elmentve", "setup_pin_code": "PIN kÃŗd beÃĄllítÃĄsa", "share": "MegosztÃĄs", + "share_action_prompt": "{count} elem megosztva", + "share_add_photos": "FotÃŗk hozzÃĄadÃĄsa", + "share_assets_selected": "{count} kivÃĄlasztva", "share_dialog_preparing": "ElőkÊszítÊs...", "share_link": "Link megosztÃĄsa", - "share_original": "HasznÃĄld az eredetit (nagy)", - "share_preview": "HasznÃĄld a miniatÅąrt (kicsi)", - "share_quality_body": "A megosztÃĄs előtt tartsd lenyomva a megosztÃĄs gombot, hogy kivÃĄlaszd a kÊpminősÊget.", - "share_quality_title": "VÃĄlaszd ki a megosztÃĄsi minősÊget", "shared": "Megosztva", "shared_album_activities_input_disable": "HozzÃĄszÃŗlÃĄsok kikapcsolva", "shared_album_activity_remove_content": "TÃļrÃļlni szeretnÊd ezt a tevÊkenysÊget?", "shared_album_activity_remove_title": "TevÊkenysÊg tÃļrlÊse", "shared_album_section_people_action_error": "Hiba az albummal kapcsolatos kilÊpÊs/eltÃĄvolítÃĄs kÃļzben", + "shared_album_section_people_action_leave": "FelhasznÃĄlÃŗ eltÃĄvolítÃĄsa az albumbÃŗl", + "shared_album_section_people_action_remove_user": "FelhasznÃĄlÃŗ eltÃĄvolítÃĄsa az albumbÃŗl", "shared_album_section_people_title": "EMBEREK", "shared_by": "Megosztotta", "shared_by_user": "{user} osztotta meg", @@ -1947,10 +2084,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} feltÃļltve", "shared_link_app_bar_title": "Megosztott linkek", "shared_link_clipboard_copied_massage": "VÃĄgÃŗlapra mÃĄsolva", + "shared_link_clipboard_text": "Link: {link}\nJelszÃŗ: {password}", "shared_link_create_error": "Hiba a megosztott link lÊtrehozÃĄsakor", "shared_link_custom_url_description": "HozzÃĄfÊrÊs ehhez a megosztott linkhez egyedi URL címen keresztÃŧl", - "shared_link_custom_url_title": "Egyedi URL", - "shared_link_custom_url_warning": "Figyelem: a perjelet tartalmazÃŗ egyÊni URL-nevek nem biztos, hogy a vÃĄrt mÃŗdon mÅąkÃļdnek.", "shared_link_edit_description_hint": "Add meg a megosztÃĄs leírÃĄsÃĄt", "shared_link_edit_expire_after_option_day": "1 nap", "shared_link_edit_expire_after_option_days": "{count} nap", @@ -1984,6 +2120,12 @@ "shared_with_partner": "Megosztva {partner} partnereddel", "sharing": "MegosztÃĄs", "sharing_enter_password": "Add meg a jelszÃŗt az oldal megtekintÊsÊhez.", + "sharing_page_album": "Megosztott albumok", + "sharing_page_description": "Megosztott albumok lÊtrehozÃĄsÃĄval fÊnykÊpeket Ês videÃŗkat oszthatsz meg az ismerőseiddel.", + "sharing_page_empty_list": "ÜRES LISTA", + "sharing_sidebar_description": "MegosztÃĄs link megjelenítÊse az oldalsÃĄvban", + "sharing_silver_appbar_create_shared_album": "Új megosztott album", + "sharing_silver_appbar_share_partner": "MegosztÃĄs partnerrel", "shift_to_permanent_delete": "nyomd meg a ⇧ nyilat az elem vÊgleges tÃļrlÊsÊhez", "show_album_options": "Album beÃĄllítÃĄsok megjelenítÊse", "show_albums": "Albumok megjelenítÊse", @@ -1995,9 +2137,7 @@ "show_in_timeline": "MutatÃĄs az idővonalon", "show_in_timeline_setting_description": "Ennek a felhasznÃĄlÃŗnak a kÊpei Ês videÃŗi jelenjenek meg az idővonaladon", "show_keyboard_shortcuts": "BillentyÅąparancsok megjelenítÊse", - "show_less": "Kevesebb megjelenítÊse", "show_metadata": "Metaadatok megjelenítÊse", - "show_more_fields": "TovÃĄbbi {count} mező megjelenítÊse", "show_or_hide_info": "InformÃĄciÃŗk megjelenítÊse vagy elrejtÊse", "show_password": "JelszÃŗ megjelenítÊse", "show_person_options": "SzemÊly beÃĄllítÃĄsok megjelenítÊse", @@ -2005,7 +2145,6 @@ "show_schema": "SÊma megjelenítÊse", "show_search_options": "KeresÊsi beÃĄllítÃĄsok megjelenítÊse", "show_shared_links": "Megosztott linkek megjelenítÊse", - "show_slideshow_metadata_overlay": "KÊpadatok megjelenítÊse", "show_slideshow_transition": "VetítÊs ÃĄttÅąnÊsi effektus megjelenítÊse", "show_supporter_badge": "TÃĄmogatÃŗ jelvÊny", "show_supporter_badge_description": "TÃĄmogatÃŗ jelvÊny megjelenítÊse", @@ -2017,25 +2156,18 @@ "sign_out": "KijelentkezÊs", "sign_up": "RegisztrÃĄciÃŗ", "size": "MÊret", - "skip": "KihagyÃĄs", "skip_to_content": "UgrÃĄs a tartalomhoz", "skip_to_folders": "UgrÃĄs a mappÃĄkhoz", "skip_to_tags": "UgrÃĄs a címkÊkhez", "slideshow": "DiavetítÊs", - "slideshow_body": "Dőlj hÃĄtra, Ês nÊzd meg fotÃŗidat teljes kÊpernyős diavetítÊsben.", - "slideshow_metadata_overlay_mode": "KÊpadatok tartalma", - "slideshow_metadata_overlay_mode_description_only": "Csak leírÃĄs", - "slideshow_metadata_overlay_mode_full": "Teljes", "slideshow_repeat": "DiavetítÊs ismÊtlÊse", "slideshow_repeat_description": "Ha a diavetítÊs vÊget Êr, Ãējraindul az elejÊtől", "slideshow_settings": "DiavetítÊs beÃĄllítÃĄsai", - "slideshow_title": "DiavetítÊs", - "smart_album": "Okos album", - "some_assets_already_have_a_location_warning": "A kivÃĄlasztott elemek kÃļzÃŧl nÊhÃĄnynak mÃĄr van helyszíne", "sort_albums_by": "Albumok rendezÊse...", "sort_created": "LÊtrehozÃĄs dÃĄtuma", "sort_items": "Elemek szÃĄma", "sort_modified": "MÃŗdosítÃĄs dÃĄtuma", + "sort_newest": "LegÃējabb fotÃŗ", "sort_oldest": "LegrÊgebbi fÊnykÊp", "sort_people_by_similarity": "Emberek hasonlÃŗsÃĄg szerinti rendezÊse", "sort_recent": "LegÃējabb fÊnykÊp", @@ -2044,6 +2176,7 @@ "stack": "KollÃĄzs", "stack_action_prompt": "{count} egymÃĄsra helyezve", "stack_duplicates": "DuplikÃĄtumok csoportosítÃĄsa", + "stack_select_one_photo": "VÃĄlassz egy fő kÊpet a csoportbÃŗl", "stack_selected_photos": "KivÃĄlasztott fÊnykÊpek csoportosítÃĄsa", "stacked_assets_count": "{count, plural, other {# elem}} csoportosítva", "stacktrace": "Hiba leírÃĄsa", @@ -2052,10 +2185,6 @@ "start_date_before_end_date": "A kezdeti dÃĄtumnak a befejezÊsi dÃĄtum előtt kell lennie", "state": "Megye/Állam", "status": "Állapot", - "step_delete": "LÊpÊs tÃļrlÊse", - "step_delete_confirm": "Biztosan tÃļrÃļlni szeretnÊ ezt a lÊpÊst?", - "step_details": "LÊpÊs rÊszletei", - "steps": "LÊpÊsek", "stop_casting": "VetítÊs megszÃŧntetÊse", "stop_motion_photo": "Stop motion kÊp", "stop_photo_sharing": "MegszÃŧnteted fotÃŗid megosztÃĄsÃĄt?", @@ -2068,7 +2197,7 @@ "submit": "BekÃŧldÊs", "success": "Siker", "suggestions": "Javaslatok", - "sunrise_on_the_beach": "PÊldÃĄul: napfelkelte a tengerparton", + "sunrise_on_the_beach": "Napkelte a tengerparton", "support": "TÃĄmogatÃĄs", "support_and_feedback": "TÃĄmogatÃĄs Ês visszajelzÊs", "support_third_party_description": "Az Immich telepítÊsedet egy harmadik fÊl csomagolta. Mivel elkÊpzelhető, hogy az esetlegesen felmerÃŧlő problÊmÃĄkat ez a csomag okozza, ezÊrt kÊrjÃŧk, előszÃļr velÃŧk kÃļzÃļld a problÊmÃĄkat az alÃĄbbi linkek segítsÊgÊvel.", @@ -2076,18 +2205,17 @@ "swap_merge_direction": "EgyesítÊs irÃĄnyÃĄnak megfordítÃĄsa", "sync": "SzinkronizÃĄlÃĄs", "sync_albums": "Albumok szinkronizÃĄlÃĄsa", + "sync_albums_manual_subtitle": "Összes feltÃļltÃļtt fotÃŗ Ês videÃŗ szinkronizÃĄlÃĄsa a kivÃĄlasztott albumokba", "sync_local": "Helyi szinkronizÃĄlÃĄsa", "sync_remote": "TÃĄvoli szinkronizÃĄlÃĄsa", "sync_status": "SzinkronizÃĄlÃĄs ÃĄllapota", "sync_status_subtitle": "SzinkronizÃĄlÃĄs megtekintÊse Ês kezelÊse", "sync_upload_album_setting_subtitle": "FotÃŗk Ês videÃŗk lÊtrehozÃĄsa Ês szinkronizÃĄlÃĄsa a kivÃĄlasztott Immich albumokba", - "system_theme": "Rendszer tÊma", - "system_theme_command_description": "Rendszer tÊma hasznÃĄlata ({value})", "tag": "Címke", "tag_assets": "Elemek címkÊzÊse", "tag_created": "LÊtrehozott címke: {tag}", - "tag_face": "Arc címkÊzÊse", "tag_feature_description": "FÊnykÊpek Ês videÃŗk bÃļngÊszÊse a címkÊk tÊmÃĄja szerint csoportosítva", + "tag_not_found_question": "Nem talÃĄlod a címkÊt? Hozz lÊtre egy Ãējat.", "tag_people": "Emberek címkÊzÊse", "tag_updated": "Frissített címke: {tag}", "tagged_assets": "{count, plural, one {# elem} other {# elem}} felcímkÊzve", @@ -2102,10 +2230,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Elemek szÃĄma soronkÊnt ({count})", "theme_setting_colorful_interface_subtitle": "AlapÊrtelmezett szín hasznÃĄlata a hÃĄttÊrben lÊvő felÃŧletekhez.", "theme_setting_colorful_interface_title": "Színes felhasznÃĄlÃŗi felÃŧlet", + "theme_setting_image_viewer_quality_subtitle": "RÊszletes kÊpmegjelenítő minősÊgÊnek beÃĄllítÃĄsa", + "theme_setting_image_viewer_quality_title": "KÊpmegjelenítő minősÊge", "theme_setting_primary_color_subtitle": "VÃĄlassz egy színt az alapÊrtelmezett mÅąveletekhez Ês kiemelÊsekhez.", "theme_setting_primary_color_title": "AlapÊrtelmezett szín", "theme_setting_system_primary_color_title": "Rendszerszínek hasznÃĄlata", "theme_setting_system_theme_switch": "Automatikus (kÃļveti a rendszer tÊmÃĄjÃĄt)", + "theme_setting_theme_subtitle": "AlkalmazÃĄs tÊmÃĄjÃĄnak vÃĄlasztÃĄsa", + "theme_setting_three_stage_loading_subtitle": "A hÃĄromlÊpcsős betÃļltÊs javíthatja a betÃļltÊsi teljesítmÊnyt, de jelentősen nÃļveli a hÃĄlÃŗzati forgalmat", + "theme_setting_three_stage_loading_title": "HÃĄromlÊpcsős betÃļltÊs engedÊlyezÊse", "then": "Akkor", "they_will_be_merged_together": "Egyesítve lesznek", "third_party_resources": "Harmadik fÊltől szÃĄrmazÃŗ forrÃĄsok", @@ -2131,17 +2264,23 @@ "trash_all": "Mindet lomtÃĄrba", "trash_count": "{count, number} elem lomtÃĄrba helyezÊse", "trash_delete_asset": "Elem tÃļrlÊse / lomtÃĄrba helyezÊse", + "trash_emptied": "LomtÃĄr kiÃŧrítve", "trash_no_results_message": "Itt lesznek lÃĄthatÃŗak a lomtÃĄrba tett kÊpek Ês videÃŗk.", "trash_page_delete_all": "Összes tÃļrlÊse", + "trash_page_empty_trash_dialog_content": "Ki szeretnÊd Ãŧríteni a lomtÃĄrban lÊvő elemeket? Ezeket vÊglegesen eltÃĄvolítjuk az Immich-ből", "trash_page_info": "A LomÃĄtrba helyezett elemek {days} nap utÃĄn vÊglegesen tÃļrlődnek", + "trash_page_no_assets": "A LomtÃĄr Ãŧres", + "trash_page_restore_all": "Összes visszaÃĄllítÃĄsa", + "trash_page_select_assets_btn": "Elemek kivÃĄlasztÃĄsa", + "trash_page_title": "LomtÃĄr ({count})", "trashed_items_will_be_permanently_deleted_after": "A lomtÃĄrban lÊvő elemek vÊglegesen tÃļrlÊsre kerÃŧlnek {days, plural, other {# nap}} mÃēlva.", "trigger": "FeltÊtel", - "trigger_asset_metadata_extraction": "Elemek metaadatainak kinyerÊse", - "trigger_asset_metadata_extraction_description": "A mÅąvelet akkor indult el, amikor az elem EXIF-adatai kinyerÊsre kerÃŧltek", - "trigger_asset_uploaded": "Elem feltÃļltÊs", + "trigger_asset_uploaded": "Elem feltÃļltve", "trigger_asset_uploaded_description": "Új elem feltÃļltÊsekor indul el", + "trigger_description": "Egy esemÊny, ami elindítja a folyamatot", "trigger_person_recognized": "SzemÊly felismerve", "trigger_person_recognized_description": "SzemÊly felismerÊsekor indul el", + "trigger_type": "FeltÊtel típusa", "troubleshoot": "HibaelhÃĄrítÃĄs", "type": "Típus", "unable_to_change_pin_code": "Sikertelen PIN kÃŗd vÃĄltoztatÃĄs", @@ -2157,7 +2296,6 @@ "unknown": "Ismeretlen", "unknown_country": "Ismeretlen orszÃĄg", "unknown_date": "Ismeretlen dÃĄtum", - "unknown_schema": "Ismeretlen sÊma", "unknown_year": "Ismeretlen Év", "unlimited": "KorlÃĄtlan", "unlink_motion_video": "MozgÃŗkÊp levÃĄlasztÃĄsa", @@ -2170,20 +2308,23 @@ "unsaved_change": "Nem mentett vÃĄltoztatÃĄs", "unselect_all": "KijelÃļlÊsek megszÃŧntetÊse", "unselect_all_duplicates": "DuplikÃĄtumok kijelÃļlÊsÊnek megszÃŧntetÊse", + "unselect_all_in": "KijelÃļlÊs megszÃŧntetÊse itt: {group}", "unstack": "Csoport szÊtbontÃĄsa", "unstack_action_prompt": "{count} egymÃĄsra helyezÊs megszÃŧntetÊse", "unstacked_assets_count": "{count, plural, other {# elemből}} ÃĄllÃŗ csoport szÊtszedve", "unsupported_field_type": "Nem tÃĄmogatott mezőtípus", "unsupported_file_type": "A fÃĄjl {file} nem feltÃļlthető mert a típusa {type} nem tÃĄmogatott.", "untagged": "Címke eltÃĄvolítva", + "untitled_workflow": "NÊvtelen folyamat", "up_next": "KÃļvetkezik", "update_location_action_prompt": "{count} elem pozíciÃŗjÃĄnak frissítÊse a kÃļvetkezővel:", "updated_at": "Frissítve", "updated_password": "JelszÃŗ megvÃĄltoztatva", "upload": "FeltÃļltÊs", "upload_concurrency": "PÃĄrhuzamos feltÃļltÊs", - "upload_day_count": "{date}: {count} feltÃļltÊs", "upload_details": "FeltÃļltÊs ÃĄllapota", + "upload_dialog_info": "SzeretnÊl mentÊst kÊszíteni a kivÃĄlasztott elem(ek)ről a szerverre?", + "upload_dialog_title": "Elem feltÃļltÊse", "upload_error_with_count": "FeltÃļltÊsi hiba {count} elemnÊl", "upload_errors": "FeltÃļltÊs befejezve {count, plural, other {# hibÃĄval}}, frissítsd az oldalt az Ãējonnan feltÃļltÃļtt elemek megtekintÊsÊhez.", "upload_finished": "FeltÃļltÊs befejezve", @@ -2193,13 +2334,9 @@ "upload_status_errors": "HibÃĄk", "upload_status_uploaded": "FeltÃļltve", "upload_success": "FeltÃļltÊs sikeres, frissítsd az oldalt az Ãējonnan feltÃļltÃļtt elemek megtekintÊsÊhez.", - "upload_to_album_body": "Azok a felhasznÃĄlÃŗk, akik nem hasznÃĄljÃĄk a kÊzi feltÃļltÊsi funkciÃŗt, mostantÃŗl a helyi fÊnykÊpeket feltÃļltÊs kÃļzben kÃļzvetlenÃŧl hozzÃĄadhatjÃĄk egy albumhoz; így mÃĄr nem kell előszÃļr feltÃļlteniÃŧk a kÊpeket, majd kÊsőbb hozzÃĄadniuk őket az albumhoz.", - "upload_to_album_title": "FeltÃļltÊs kÃļzvetlenÃŧl egy albumba", "upload_to_immich": "FeltÃļltÊs Immich-be ({count})", "uploading": "FeltÃļltÊs folyamatban", "uploading_media": "MÊdia feltÃļltÊs folyamatban", - "uploads": "FeltÃļltÊsek", - "uploads_count": "{count} feltÃļltÊs", "url": "URL", "usage": "HasznÃĄlat", "use_biometric": "Biometrikus azonosítÃĄs hasznÃĄlata", @@ -2207,7 +2344,6 @@ "use_browser_locale_description": "DÃĄtumok, idők Ês szÃĄmok meghatÃĄrozÃĄsa a bÃļngÊsző helye alapjÃĄn", "use_current_connection": "Jelenlegi kapcsolat hasznÃĄlata", "use_custom_date_range": "Szabadon megadott időintervallum hasznÃĄlata", - "use_template": "Sablon hasznÃĄlata", "user": "FelhasznÃĄlÃŗ", "user_has_been_deleted": "Ez a felhasznÃĄlÃŗ tÃļrlÊsre kerÃŧlt.", "user_id": "FelhasznÃĄlÃŗ azonosítÃŗja", @@ -2217,6 +2353,7 @@ "user_privacy": "FelhasznÃĄlÃŗi adatvÊdelem", "user_purchase_settings": "MegvÃĄsÃĄrlÃĄs", "user_purchase_settings_description": "VÃĄsÃĄrlÃĄs kezelÊse", + "user_role_set": "{user} felhasznÃĄlÃŗnak {role} jogkÃļr biztosítÃĄsa", "user_usage_detail": "FelhasznÃĄlÃŗ hasznÃĄlati adatai", "user_usage_stats": "FiÃŗk hasznÃĄlati statisztikÃĄi", "user_usage_stats_description": "FiÃŗk hasznÃĄlati statisztikÃĄinak megtekintÊse", @@ -2226,6 +2363,7 @@ "utilities": "SegÊdeszkÃļzÃļk", "validate": "EllenőrzÊs", "validate_endpoint_error": "KÊrlek, ÊrvÊnyes URL-t adj meg", + "validation_error": "ValidÃĄciÃŗs hiba", "variables": "VÃĄltozÃŗk", "version": "VerziÃŗ", "version_announcement_closing": "BarÃĄtsÃĄggal, Alex", @@ -2235,9 +2373,9 @@ "video": "VideÃŗ", "video_hover_setting": "KismÊretÅą videÃŗ elindítÃĄsa, ha az egÊr az elem felÊ megy", "video_hover_setting_description": "Ha az egÊr az elem felÊ megy, akkor induljon el a kismÊretÅą videÃŗ lejÃĄtszÃĄsa. MÊg ha ez az opciÃŗ ki is van kapcsolva, a lejÃĄtszÃĄs akkor is elindíthatÃŗ a lejÃĄtszÃĄs gombbal.", - "video_quality": "VideÃŗminősÊg", "videos": "VideÃŗk", "videos_count": "{count, plural, one {# videÃŗ} other {# videÃŗ}}", + "videos_only": "Csak videÃŗk", "view": "MegtekintÊs", "view_album": "Album megtekintÊse", "view_all": "Összes megtekintÊse", @@ -2246,29 +2384,29 @@ "view_details": "RÊszletek megtekintÊse", "view_in_timeline": "MegtekintÊs az idővonalon", "view_link": "Link megtekintÊse", + "view_links": "Linkek megtekintÊse", "view_name": "MegtekintÊs", "view_next_asset": "KÃļvetkező elem megtekintÊse", "view_previous_asset": "Előző elem megtekintÊse", "view_qr_code": "QR kÃŗd megtekintÊse", "view_similar_photos": "HasonlÃŗ kÊpek keresÊse", "view_stack": "Csoport megtekintÊse", + "view_user": "FelhasznÃĄlÃŗ megtekintÊse", "viewer_remove_from_stack": "EltÃĄvolítÃĄs a csoportbÃŗl", + "viewer_stack_use_as_main_asset": "Fő elemnek beÃĄllítÃĄs", + "viewer_unstack": "Csoport megszÃŧntetÊse", "visibility": "LÃĄthatÃŗsÃĄg", "visibility_changed": "{count, plural, other {# szemÊly}} lÃĄthatÃŗsÃĄga megvÃĄltozott", "visual": "VizuÃĄlis", + "visual_builder": "VizuÃĄlis ÃļsszerakÃŗ", "waiting": "VÃĄrakozik", "waiting_count": "VÃĄrakozik: {count}", "warning": "FigyelmeztetÊs", "week": "HÊt", "welcome": "ÜdvÃļzlÃŧnk", "welcome_to_immich": "ÜdvÃļzÃļl az Immich", - "whats_new": "ÚjdonsÃĄgok", - "whats_new_settings_subtitle": "NÊzd meg az Immich ÃējdonsÃĄgait", - "whats_new_version": "{version} verziÃŗ", - "when": "Mikor", "width": "SzÊlessÊg", "wifi_name": "Wi-Fi neve", - "workflow": "Munkafolyamat", "workflow_delete_prompt": "Biztosan tÃļrÃļlni szeretnÊd ezt a folyamatot?", "workflow_deleted": "Folyamat tÃļrÃļlve", "workflow_description": "Folyamat leírÃĄsa", @@ -2278,17 +2416,17 @@ "workflow_name": "Folyamat neve", "workflow_navigation_prompt": "Biztosan tovÃĄbb szeretnÊl lÊpni a vÃĄltozÃĄsok mentÊse nÊlkÃŧl?", "workflow_summary": "Folyamat ÃļsszefoglalÃŗ", - "workflow_templates": "Munkafolyamat sablonok", "workflow_update_success": "Folyamat sikeresen frissítve", + "workflow_updated": "Folyamat frissítve", "workflows": "Folyamatok", "workflows_help_text": "A folyamatok automatizÃĄlt mÅąveleteket hajtanak vÊgre elemeken, indítÃĄsi feltÊtelek Ês szÅąrők alapjÃĄn", "wrong_pin_code": "HibÃĄs PIN kÃŗd", - "x_of_total": "{x}/{total}", "year": "Év", "years_ago": "{years, plural, one {# Êvvel} other {# Êvvel}} ezelőtt", "yes": "Igen", "you_dont_have_any_shared_links": "Nincsenek megosztott linkjeid", "your_wifi_name": "A Wi-Fi hÃĄlÃŗzatod neve", "zero_to_clear_rating": "0: ÊrtÊkelÊs eltÃĄvolítÃĄsa", - "zoom_image": "KÊp nagyítÃĄsa" + "zoom_image": "KÊp nagyítÃĄsa", + "zoom_to_bounds": "NagyítÃĄs a hatÃĄrokhoz" } diff --git a/i18n/hy.json b/i18n/hy.json index fe83c07fa7..1b12eb1cf0 100644 --- a/i18n/hy.json +++ b/i18n/hy.json @@ -15,6 +15,7 @@ "add_birthday": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕŽÕļÕļÕ¤ÕĩÕĄÕļ ÕĄÕ´ÕŊÕĄÕŠÕĢÕž", "add_endpoint": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕžÕĨրÕģÕļÕĄÕ¯ÕĨÕŋ", "add_location": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕŋÕĨÕ˛", + "add_more_users": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ Õ°ÕĄÕžÕĨÕŦÕĩÕĄÕŦ Ö…ÕŖÕŋÕĄÕŋÕĨրÕĨր", "add_partner": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕŖÕ¸Ö€ÕŽÕ¨ÕļÕ¯ÕĨր", "add_photos": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕļÕ¯ÕĄÖ€ÕļÕĨր", "added_to_archive": "ÔąÕžÕĨÕŦÕĄÖÕžÕĨÕŦ Õ§ ÕĄÖ€Õ­ÕĢÕžÕ¸Ö‚Õ´", @@ -24,8 +25,12 @@ "authentication_settings_disable_all": "ՎÕŊÕŋÕĄÕžÕ° ÕĨք, որ ÖÕĄÕļÕ¯ÕĄÕļում ÕĨք ÕĄÕļÕģÕĄÕŋÕĨÕŦ մուÕŋÖ„ÕĄÕĩÕĢÕļ Õ´ÕĨÕŠÕ¸Õ¤ÕļÕĨրը։ ՄուÕŋքÕļ ÕĄÕ´ÕĸÕ¸Õ˛ÕģÕ¸Ö‚ÕŠÕĩÕĄÕ´Õĸ Õ¯ÕĄÕļÕģÕĄÕŋÕžÕĢ։" }, "back": "ՀÕĨÕŋ", + "backup_all": "Ô˛Õ¸ÕŦոր", "backup_controller_page_background_battery_info_link": "ՑուÕĩց Õŋուր ÕĢÕļÕšÕēÕĨÕŊ", "backup_controller_page_background_battery_info_ok": "ÔŧÕĄÕž", + "backup_controller_page_background_wifi": "ՄÕĢÕĄÕĩÕļ Wi-Fi Õ´ÕĢÕĄÖÕ¸Ö‚Õ´Õ¸Ö‚Õ´", + "backup_controller_page_created": "ՍÕŋÕĨÕ˛ÕŽÕžÕĨÕŦ Õ§ {date}֊ÕĢÕļ", + "backup_controller_page_uploading_file_info": "ՎÕĨրÕĸÕĨÕŧÕļÕžÕ¸Ö‚Õ´ Õ§ Ö†ÕĄÕĩÕŦÕĢ ÕŋÕĨÕ˛ÕĨÕ¯Õ¸Ö‚ÕŠÕĩուÕļÕļÕĨրը", "biometric_auth_enabled": "ÔŋÕĨÕļÕŊÕĄÕšÕĄÖƒÕĄÕ¯ÕĄÕļ ÕļուÕĩÕļÕĄÕ¯ÕĄÕļցումը Õ´ÕĢÕĄÖÕžÕĄÕŽ Õ§", "change_display_order": "ՓոփոխÕĨÕŦ ÖÕ¸Ö‚ÖÕĄÕ¤Ö€Õ´ÕĄÕļ Õ°ÕĄÕģÕ¸Ö€Õ¤ÕĄÕ¯ÕĄÕļÕ¸Ö‚ÕŠÕĩուÕļÕ¨", "change_location": "ՓոխÕĨÕŦ ÕŋÕĨÕ˛Õ¨", @@ -33,9 +38,12 @@ "change_password_form_reenter_new_password": "ÔŋրկÕĢÕļ մուÕŋÖ„ÕĄÕŖÖ€ÕĨÕŦ Õļոր ÕŖÕĄÕ˛ÕŋÕļÕĄÕĸÕĄÕŧÕ¨", "change_pin_code": "ՓոփոխÕĨÕŦ ՊÔģՆ Õ¯Õ¸Õ¤Õ¨", "city": "Õ”ÕĄÕ˛ÕĄÖ„", + "client_cert_dialog_msg_confirm": "ÔŧÕĄÕž", "client_cert_title": "SSL Õ°ÕĄÕŗÕĄÕ­Õ¸Ö€Õ¤ÕĢ Õ°ÕĄÕžÕĄÕŊÕŋÕĄÕŖÕĢր", "color": "ÔŗÕ¸Ö‚ÕĩÕļ", "common_create_new_album": "ՍÕŋÕĨÕ˛ÕŽÕĨÕŦ Õļոր ÕĄÕŦÕĸÕ¸Õ´", + "control_bottom_app_bar_create_new_album": "ՍÕŋÕĨÕ˛ÕŽÕĨÕŦ Õļոր ÕĄÕŦÕĸÕ¸Õ´", + "control_bottom_app_bar_delete_from_immich": "ՋÕļÕģÕĨÕŦ Immich֊ÕĢց", "control_bottom_app_bar_delete_from_local": "ՋÕļÕģÕĨÕŦ ÕŊÕĄÖ€Ö„ÕĄÕžÕ¸Ö€Õ¸Ö‚Õ´ÕĢց", "control_bottom_app_bar_edit_location": "ՓոխÕĨÕŦ ՏÕĨÕ˛Õ¨", "control_bottom_app_bar_trash_from_immich": "ՏÕĨÕ˛ÕĄÖƒÕ¸Õ­ÕĨÕŦ ÕĄÕ˛ÕĸÕĄÕ´ÕĄÕļ", @@ -43,8 +51,10 @@ "create_new": "ՍՏÔĩÕ‚ÔžÔĩÔŧ ՆՈՐ", "create_new_person": "ՍÕŋÕĨÕ˛ÕŽÕĨÕŦ Õļոր ÕĄÕļÕą", "create_shared_album_page_share_select_photos": "Ô¸ÕļÕŋրÕĨ Õ†Õ¯ÕĄÖ€ÕļÕĨր", + "curated_object_page_title": "Ô˛ÕĄÕļÕĨր", "current_pin_code": "Տ՞ÕĩÕĄÕŦ ՊÔģՆ Õ¯Õ¸Õ¤Õ¨", "current_server_address": "Ô¸ÕļÕŠÕĄÖÕĢÕ¯ ÕŊÕĨÖ€ÕžÕĨրÕĢ Õ°ÕĄÕŊցÕĨ", + "daily_title_text_date": "E, MMM dd", "dark": "Õ„Õ¸Ö‚ÕŠ", "dark_theme": "ՄÕĢÕĄÖÕļÕĨÕŦ/ÕĄÕļÕģÕĄÕŋÕĨÕŦ Õ´Õ¸Ö‚ÕŖ ÕŋÕĨÕŊքը", "day": "Օր", @@ -53,27 +63,37 @@ "download_notfound": "ՆÕĨրÕĸÕĨÕŧÕļÕžÕĄÕŽÕ¨ ÕšÕĢ Õ°ÕĄÕĩÕŋÕļÕĄÕĸÕĨÖ€ÕžÕĨÕŦ", "edit_location": "ՓոխÕĨÕŦ ÕŋÕĨÕ˛Õ¨", "enter_wifi_name": "ՄուÕŋÖ„ÕĄÕŖÖ€ÕĨÕŦ Wi-Fi ÕĄÕļուÕļÕ¨", + "exif_bottom_sheet_person_add_person": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕĄÕļուÕļ", + "experimental_settings_new_asset_list_subtitle": "Ô¸ÕļÕŠÕĄÖÖ„Õ¸Ö‚Õ´ Õ§", "failed_to_authenticate": "ՆուÕĩÕļÕĄÕ¯ÕĄÕļÕĄÖÕ¸Ö‚Õ´Õ¨ ÕąÕĄÕ­Õ¸Õ˛ÕžÕĨÕŦ Õ§", "folder_not_found": "ÔšÕ˛ÕŠÕĄÕēÕĄÕļÕĄÕ¯Õ¨ ÕšÕĢ Õ°ÕĄÕĩÕŋÕļÕĄÕĸÕĨÖ€ÕžÕĨÕŦ", "hi_user": "Ô˛ÕĄÖ€ÕĨւ {name} ({email})", + "ignore_icloud_photos": "ÔąÕļÕŋÕĨÕŊÕĨÕŦ iCloud֊ÕĢ ÕļÕ¯ÕĄÖ€ÕļÕĨրը", "invalid_date_format": "ÔąÕļÕžÕĄÕžÕĨր ÕĄÕ´ÕŊÕĄÕŠÕžÕĢ ÕąÖ‡ÕĄÕšÕĄÖƒ", + "ios_debug_info_last_sync_at": "ՎÕĨրÕģÕĢÕļ ÕŠÕĄÖ€Õ´ÕĄÖÕ¸Ö‚Õ´Õ¨ {dateTime}֊ÕĢÕļ", "language_no_results_title": "ÔŧÕĨÕĻուÕļÕĨր ÕšÕĨÕļ ÕŖÕŋÕļÕžÕĨÕŦ", + "library_page_device_albums": "ÕÕĄÖ€Ö„ÕĄÕžÕ¸Ö€Õ´ÕĄÕļ ÕĄÕŦÕĸÕ¸Õ´ÕļÕĨրը", "location_picker_choose_on_map": "Ô¸ÕļÕŋրÕĨÕŦ Ö„ÕĄÖ€ÕŋÕĨÕĻÕĢ ÕžÖ€ÕĄ", "login_form_endpoint_url": "ՍÕĨÖ€ÕžÕĨրÕĢ ÕžÕĨրÕģÕļÕĄÕ¯ÕĨÕŋÕĢ URL", + "login_form_save_login": "ՄÕļÕĄÕŦ մուÕŋÖ„ÕĄÕŖÖ€ÕžÕĄÕŽ", "login_password_changed_success": "ÔŗÕĄÕ˛ÕŋÕļÕĄÕĸÕĄÕŧÕ¨ Õ°ÕĄÕģÕ¸Õ˛Õ¸Ö‚ÕŠÕĩÕĄÕ´Õĸ ÖƒÕ¸ÖƒÕ¸Õ­ÕžÕĨÕŦ Õ§", + "map_assets_in_bounds": "{count} ÕļÕ¯ÕĄÖ€ÕļÕĨր", "map_location_picker_page_use_location": "Õ•ÕŖÕŋÕĄÕŖÕ¸Ö€ÕŽÕĨÕŦ ÕĄÕĩÕŊ ÕŋÕĨÕ˛Õ¨", "map_location_service_disabled_title": "ՏÕĨÕ˛Õ¸Ö€Õ¸ÕˇÕ´ÕĄÕļ ÕŽÕĄÕŧÕĄÕĩÕ¸Ö‚ÕŠÕĩուÕļÕļÕĨրÕļ ÕĄÕļÕģÕĄÕŋÕžÕĄÕŽ ÕĨÕļ", "map_no_location_permission_title": "ՏÕĨÕ˛Õ¸Ö€Õ¸ÕˇÕ´ÕĄÕļ ÕŠÕ¸Ö‚ÕĩÕŦÕŋÕžÕ¸Ö‚ÕŠÕĩուÕļÕ¨ Õ´ÕĨրÕĒÕžÕĄÕŽ Õ§", "map_settings_date_range_option_day": "ՎÕĨրÕģÕĢÕļ 24 ÕĒÕĄÕ´Õ¸Ö‚Õ´", "map_settings_date_range_option_days": "ՎÕĨրÕģÕĢÕļ {days} օրում", "map_settings_date_range_option_years": "ՎÕĨրÕģÕĢÕļ {years} ÕŋÕĄÖ€Õ¸Ö‚Õ´", + "mark_as_read": "Õ†ÕˇÕĨÕŦ Õ¯ÕĄÖ€Õ¤ÕĄÖÕĄÕŽ", "new_pin_code": "Նոր ՊÔģՆ Õ¯Õ¸Õ¤", "on_this_device": "ÔąÕĩÕŊ ÕŊÕĄÖ€Ö„ÕĄÕžÕ¸Ö€Õ¸Ö‚Õ´Õ¸Ö‚Õ´", "partner_list_user_photos": "{}-ÕĢÕļ ÕļÕ¯ÕĄÖ€ÕļÕĨրը", + "permission_onboarding_go_to_settings": "ÔŗÕļÕĄÕŦ Õ¯ÕĄÖ€ÕŖÕĄÕžÕ¸Ö€Õ¸Ö‚Õ´ÕļÕĨր", "photos": "Õ†Õ¯ÕĄÖ€ÕļÕĨր", "pin_verification": "ՊÔģՆ Õ¯Õ¸Õ¤ÕĢ ÕŊÕŋÕ¸Ö‚ÕŖÕ¸Ö‚Õ´", "reset_pin_code": "ՎÕĨÖ€ÕĄÕ¯ÕĄÕļÕŖÕļÕĨÕŦ ՊÔģՆ Õ¯Õ¸Õ¤Õ¨", "save": "ÕŠÕĄÕ°ÕĨ", + "save_to_gallery": "ÕŠÕĄÕ°ÕēÕĄÕļÕĨÕŦ ÕēÕĄÕŋÕ¯ÕĨÖ€ÕĄÕŊÖ€ÕĄÕ°Õ¸Ö‚Õ´", "scan_library": "Õ†ÕĄÕĩÕĨ", "search": "ՓÕļÕŋրÕĨ", "search_city": "ՈրոÕļÕĨ Ö„ÕĄÕ˛ÕĄÖ„â€Ļ", @@ -86,11 +106,14 @@ "search_filter_media_type_title": "Ô¸ÕļÕŋրÕĨÕŦ Õ´ÕĨÕ¤ÕĢÕĄÕĩÕĢ ÕŋÕĨÕŊÕĄÕ¯Õ¨", "search_no_more_result": "ÔąÕĩÕŦևÕŊ ÕĄÖ€Õ¤ÕĩուÕļքÕļÕĨր ÕšÕ¯ÕĄÕļ", "search_no_people": "ÕˆÕš Õ´ÕĢ ÕĄÕļÕą", + "search_page_motion_photos": "Õ‡ÕĄÖ€ÕĒÕžÕ¸Õ˛ Õ†Õ¯ÕĄÖ€ÕļÕĨր", "select_photos": "Ô¸ÕļÕŋրÕĨ ÕļÕ¯ÕĄÖ€ÕļÕĨր", "setting_image_viewer_original_title": "Ô˛ÕĨÕŧÕļÕĨÕŦ ÕĸÕļÕĄÕŖÕĢր ÕļÕ¯ÕĄÖ€Õ¨", "setting_image_viewer_preview_title": "Ô˛ÕĨÕŧÕļÕĨÕŦ ÕļÕĄÕ­ÕĄÕ¤ÕĢÕŋÕ´ÕĄÕļ ÕļÕ¯ÕĄÖ€Õ¨", + "setting_notifications_notify_never": "ÕĨրÕĸÕĨք", "setting_notifications_notify_seconds": "{count} ÕžÕĄÕĩրկÕĩÕĄÕļ", "setting_video_viewer_original_video_title": "ՍÕŋÕĢÕēÕĨÕŦ ÕĸÕļÕĄÕŖÕĢր ÕŋÕĨÕŊÕĄÕ°Õ¸ÕŦÕ¸ÕžÕĄÕ¯Õ¨", + "share_add_photos": "ÔąÕžÕĨÕŦÕĄÖÕļÕĨÕŦ ÕļÕ¯ÕĄÖ€ÕļÕĨր", "shared_album_activities_input_disable": "ՄÕĨÕ¯ÕļÕĄÕĸÕĄÕļÕ¸Ö‚ÕŠÕĩուÕļÕļÕĨրÕļ ÕĄÕļÕģÕĄÕŋÕžÕĄÕŽ ÕĨÕļ", "shared_link_clipboard_copied_massage": "ÕŠÕĄÕŋÕŗÕĨÕļÕĄÕ°ÕĄÕļÕžÕĄÕŽ Õ§", "shared_link_edit_expire_after_option_day": "1 օր", @@ -103,12 +126,16 @@ "shared_link_edit_expire_after_option_year": "{count} ÕŋÕĄÖ€ÕĢ", "shared_link_manage_links": "ÔŋÕĄÕŧÕĄÕžÕĄÖ€ÕĨÕŦ Õ¯ÕĢÕŊÕžÕĄÕŽ Õ°Õ˛Õ¸Ö‚Õ´ÕļÕĨրը", "shared_with_me": "ÔģÕļÕą Õ°ÕĨÕŋ Õ¯ÕĢÕŊÕžÕĄÕŽ", + "sharing_silver_appbar_create_shared_album": "Նոր Õ¯ÕĢÕŊÕžÕĄÕŽ ÕĄÕŦÕĸÕ¸Õ´", + "sharing_silver_appbar_share_partner": "ÔŋÕĢÕŊÕĨÕŦ ÕŖÕ¸Ö€ÕŽÕ¨ÕļÕ¯ÕĨրոÕģ Õ°ÕĨÕŋ", "sort_oldest": "ÔąÕ´ÕĨÕļÕĄÕ°ÕĢÕļ ÕļÕ¯ÕĄÖ€Õ¨", "sort_recent": "ÔąÕ´ÕĨÕļÕĄÕļոր ÕļÕ¯ÕĄÖ€Õ¨", + "theme_setting_image_viewer_quality_title": "Õ†Õ¯ÕĄÖ€ÕĢ Õ¤ÕĢÕŋÕ´ÕĄÕļ Õ¸Ö€ÕĄÕ¯", "theme_setting_system_primary_color_title": "Õ•ÕŖÕŋÕĄÕŖÕ¸Ö€ÕŽÕĨÕŦ Õ°ÕĄÕ´ÕĄÕ¯ÕĄÖ€ÕŖÕĢ ÕŖÕ¸Ö‚ÕĩÕļÕ¨", "timezone": "ÔēÕĄÕ´ÕĄÕĩÕĢÕļ ÕŖÕ¸ÕŋÕĢ", "to_trash": "ÔąÕ˛Õĸ", "trash": "ÔąÕ˛Õĸ", + "trash_page_title": "ÔąÕ˛Õĸ ({count})", "type": "ՏÕĨÕŊÕĄÕ¯", "unknown": "ÔąÕļÕ°ÕĄÕĩÕŋ", "unknown_country": "ÔąÕļÕ°ÕĄÕĩÕŋ ÔĩրկÕĢր", diff --git a/i18n/id.json b/i18n/id.json index bf5281fcb9..a6f0b649b9 100644 --- a/i18n/id.json +++ b/i18n/id.json @@ -10,31 +10,39 @@ "active": "Aktif", "active_count": "Aktif: {count}", "activity": "Aktivitas", + "activity_changed": "Fitur Aktivitas sedang {enabled, select, true {aktif} other {nonaktif}}", "add": "Tambahkan", "add_a_description": "Tambahkan keterangan", "add_a_location": "Tambahkan lokasi", "add_a_name": "Tambahkan nama", "add_a_title": "Tambahkan judul", "add_action": "Tambah tindakan", + "add_action_description": "Klik untuk menambahkan tindakan untuk dijalankan", "add_assets": "Tambahkan aset", "add_birthday": "Tambahkan tanggal lahir", "add_endpoint": "Tambahkan titik akhir", "add_exclusion_pattern": "Tambahkan pola pengecualian", + "add_filter": "Tambahkan filter", + "add_filter_description": "Klik untuk menambahkan kondisi filter", "add_location": "Tambahkan lokasi", + "add_more_users": "Tambahkan lebih banyak pengguna", "add_partner": "Tambahkan partner", + "add_path": "Tambahkan jalur", "add_photos": "Tambahkan foto", - "add_step": "Tambah langkah", "add_tag": "Tambahkan tag", "add_to": "Tambahkan keâ€Ļ", "add_to_album": "Tambahkan ke album", "add_to_album_bottom_sheet_added": "Ditambahkan ke {album}", "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", "add_to_album_bottom_sheet_some_local_assets": "Beberapa aset lokal tidak dapat ditambahkan ke album", + "add_to_album_toggle": "Masukkan ke {album}/Batalkan dari {album}", "add_to_albums": "Tambahkan ke album", "add_to_albums_count": "Tambahkan ke album ({count})", "add_to_bottom_bar": "Tambahkan ke", + "add_to_shared_album": "Tambahkan ke album bersama", "add_upload_to_stack": "Tambahkan unggahan ke tumpukan", "add_url": "Tambahkan URL", + "add_workflow_step": "Tambahkan langkah alur kerja", "added_to_archive": "Ditambahkan ke arsip", "added_to_favorites": "Ditambahkan ke favorit", "added_to_favorites_count": "{count, number} ditambahkan ke favorit", @@ -73,7 +81,6 @@ "cron_expression_description": "Tetapkan interval pemindaian menggunakan format cron. Untuk informasi lebih lanjut, silakan merujuk misalnya ke Crontab Guru", "cron_expression_presets": "Prasetel ekspresi cron", "disable_login": "Nonaktifkan log masuk", - "download_csv": "Unduh CSV", "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mendeteksi gambar yang serupa. Bergantung pada Pencarian Cerdas", "exclusion_pattern_description": "Pola pengecualian dapat digunakan untuk mengabaikan file dan folder ketika memindai pustaka Anda. Ini berguna jika Anda memiliki folder yang berisi file yang tidak ingin diimpor, seperti file RAW.", "export_config_as_json_description": "Unduh konfigurasi sistem saat ini sebagai file JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Nilai kualitas gambar kecil dari 1-100. Lebih tinggi lebih baik, tetapi menghasilkan file lebih besar dan dapat mengurangi respons aplikasi.", "image_thumbnail_title": "Pengaturan Gambar Kecil", "import_config_from_json_description": "Impor konfigurasi sistem dengan mengunggah file konfigurasi JSON", - "integrity_checks_checksum_files": "File checksum", - "integrity_checks_checksum_files_description": "Konfigurasikan pemeriksaan checksum", - "integrity_checks_checksum_files_enable_description": "Aktifkan pemeriksaan checksum", - "integrity_checks_checksum_files_percentage_limit": "Batas persentase", - "integrity_checks_checksum_files_percentage_limit_description": "Konfigurasikan persentase maksimum antara 0,01 dan 1 untuk menentukan seberapa banyak pemeriksaan checksum dijalankan pada setiap interval.", - "integrity_checks_checksum_files_time_limit": "Batas waktu", - "integrity_checks_checksum_files_time_limit_description": "Konfigurasikan durasi maksimum pemeriksaan checksum pada setiap interval. (ms)", - "integrity_checks_missing_files": "File hilang", - "integrity_checks_missing_files_description": "Konfigurasikan frekuensi serta aktifkan atau nonaktifkan pemeriksaan file hilang", - "integrity_checks_missing_files_enable_description": "Aktifkan pemeriksaan file hilang", - "integrity_checks_settings": "Pemeriksaan integritas", - "integrity_checks_settings_description": "Kelola interval pemeriksaan integritas", - "integrity_checks_untracked_files": "File tidak terlacak", - "integrity_checks_untracked_files_description": "Konfigurasikan frekuensi serta aktifkan atau nonaktifkan pemeriksaan file tidak terlacak", - "integrity_checks_untracked_files_enable_description": "Aktifkan pemeriksaan file tidak terlacak", "job_concurrency": "Konkurensi {job}", "job_created": "Tugas telah dibuat", "job_not_concurrency_safe": "Tugas ini tidak aman untuk konkurensi.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Aktifkan pencarian cerdas", "machine_learning_smart_search_enabled_description": "Jika dinonaktifkan, gambar tidak akan dienkode untuk pencarian cerdas.", "machine_learning_url_description": "URL server pembelajaran mesin. Jika lebih dari satu URL disediakan, setiap server akan dicoba satu per satu hingga salah satu berhasil merespons, dari urutan pertama sampai terakhir. Server yang tidak merespons akan diabaikan sementara hingga kembali online.", - "maintenance_backup_management": "Pengelolaan cadangan", "maintenance_delete_backup": "Hapus Cadangan", "maintenance_delete_backup_description": "File ini akan dihapus secara permanen.", "maintenance_delete_error": "Gagal menghapus cadangan.", - "maintenance_integrity_check": "Periksa", - "maintenance_integrity_check_all": "Periksa Semua", - "maintenance_integrity_checksum_mismatch": "Checksum Tidak Cocok", - "maintenance_integrity_checksum_mismatch_description": "File yang checksum di penyimpanannya tidak cocok dengan checksum yang disimpan Immich di basis data.", - "maintenance_integrity_checksum_mismatch_job": "Periksa ketidakcocokan checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Segarkan laporan ketidakcocokan checksum", - "maintenance_integrity_missing_file": "File Hilang", - "maintenance_integrity_missing_file_description": "File yang dilacak Immich di basis datanya tetapi tidak ada di sistem file.", - "maintenance_integrity_missing_file_job": "Periksa file yang hilang", - "maintenance_integrity_missing_file_refresh_job": "Segarkan laporan file yang hilang", - "maintenance_integrity_report": "Laporan Integritas", - "maintenance_integrity_untracked_file": "File Tidak Terlacak", - "maintenance_integrity_untracked_file_description": "File di direktori Immich yang tidak memiliki catatan apa pun di Immich.", - "maintenance_integrity_untracked_file_job": "Periksa file yang tidak terlacak", - "maintenance_integrity_untracked_file_refresh_job": "Segarkan laporan file yang tidak terlacak", "maintenance_restore_backup": "Memulihkan Cadangan", "maintenance_restore_backup_description": "Immich akan dihapus dan dipulihkan dari cadangan yang dipilih. Sebuah cadangan akan dibuat sebelum dilanjutkan.", "maintenance_restore_backup_different_version": "Cadangan ini dibuat dengan versi Immich yang berbeda!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Aktifkan notifikasi email", "notification_settings": "Pengaturan Notifikasi", "notification_settings_description": "Kelola pengaturan notifikasi, termasuk email", - "oauth_allow_insecure_requests": "Izinkan permintaan tidak aman", - "oauth_allow_insecure_requests_description": "PERINGATAN: Ini menonaktifkan validasi sertifikat TLS untuk permintaan OAuth dan dapat membuat Anda rentan terhadap serangan MITM.", "oauth_auto_launch": "Peluncuran otomatis", "oauth_auto_launch_description": "Mulai alur log masuk OAuth secara otomatis setelah menuju ke halaman log masuk", "oauth_auto_register": "Pendaftaran otomatis", @@ -300,11 +274,9 @@ "oauth_button_text": "Teks tombol", "oauth_client_secret_description": "Diperlukan untuk klien yang konfidensial, atau jika PKCE (Proof Key for Code Exchange) tidak didukung untuk klien umum.", "oauth_enable_description": "Log masuk dengan OAuth", - "oauth_end_session_url_description": "Arahkan pengguna ke URI ini saat mereka keluar.", "oauth_mobile_redirect_uri": "URI pengalihan ponsel", "oauth_mobile_redirect_uri_override": "Penimpaan URI penerusan ponsel", "oauth_mobile_redirect_uri_override_description": "Aktifkan ketika penyedia OAuth tidak mengizinkan tautan ponsel, seperti ''{callback}''", - "oauth_prompt_description": "Parameter prompt (mis. select_account, login, consent)", "oauth_role_claim": "Klaim Peran", "oauth_role_claim_description": "Secara otomatis memberikan akses admin berdasarkan keberadaan klaim ini. Klaim dapat berupa \"user\" atau \"admin\".", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Menyegarkan semua pustaka", "registration": "Pendaftaran Admin", "registration_description": "Karena Anda merupakan pengguna pertama dalam sistem, Anda akan ditetapkan sebagai Admin, yang bertanggung jawab atas tugas administratif dan membuat pengguna baru lainnya.", - "release_channel_release_candidate": "Kandidat rilis", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Hapus tugas gagal", "require_password_change_on_login": "Wajibkan pengguna untuk mengubah sandi pada saat log masuk pertama", "reset_settings_to_default": "Atur ulang pengaturan ke bawaan", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Hanya diterapkan pada VAAPI dan QSV. Menetapkan node dri yang digunakan untuk transkode perangkat keras.", "transcoding_preset_preset": "Prasetel (-preset)", "transcoding_preset_preset_description": "Kecepatan kompresi. Prasetel yang lebih lambat membuat file lebih kecil dan meningkatkan kualitas ketika menargetkan kecepatan bit tertentu. VP9 mengabaikan kecepatan di atas `faster`.", - "transcoding_realtime": "Transcoding Waktu Nyata [EKSPERIMENTAL]", - "transcoding_realtime_description": "Memungkinkan transcoding dilakukan secara waktu nyata saat video sedang di-stream. Mengaktifkan pergantian kualitas, tetapi dapat menyebabkan latensi pemutaran yang lebih tinggi dan tersendat-sendat tergantung pada kemampuan server.", - "transcoding_realtime_enabled": "Aktifkan transcoding waktu nyata", - "transcoding_realtime_enabled_description": "Jika dinonaktifkan, server akan menolak memulai sesi transcoding waktu nyata yang baru.", - "transcoding_realtime_resolutions": "Resolusi", - "transcoding_realtime_resolutions_description": "Resolusi yang ditawarkan untuk transcoding waktu nyata. Resolusi yang lebih tinggi dapat menyebabkan masalah pemutaran jika server tidak dapat melakukan transcoding dengan cukup cepat.", - "transcoding_realtime_video_codecs": "Codec video", - "transcoding_realtime_video_codecs_description": "Codec video yang ditawarkan untuk transcoding waktu nyata. Klien akan memilih opsi terbaik yang didukung saat pemutaran. AV1 lebih efisien daripada HEVC, dan HEVC lebih efisien daripada H.264. Saat menggunakan akselerasi perangkat keras, pilih hanya codec yang dapat dienkode oleh akselerator. Saat menggunakan transcoding perangkat lunak, perlu diketahui bahwa H.264 lebih cepat daripada AV1, dan AV1 lebih cepat daripada HEVC.", "transcoding_reference_frames": "Bingkai referensi", "transcoding_reference_frames_description": "Jumlah bingkai untuk direferensikan ketika mengompres bingkai tertentu. Nilai lebih tinggi meningkatkan efisiensi kompresi, tetapi membuat pengodean lambat. 0 menetapkan nilai ini secara otomatis.", "transcoding_required_description": "Hanya video dalam format yang tidak diterima", @@ -478,8 +440,6 @@ "user_settings_description": "Kelola pengaturan pengguna", "user_successfully_removed": "Pengguna {email} berhasil dihapus.", "users_page_description": "Halaman pengguna admin", - "version_check_channel": "Saluran rilis", - "version_check_channel_description": "Pilih saluran rilis yang ingin Anda gunakan untuk menerima pengumuman versi", "version_check_enabled_description": "Aktifkan pemeriksaan versi", "version_check_implications": "Fitur pemeriksaan versi tergantung pada komunikasi berkala dengan {server}", "version_check_settings": "Pemeriksaan Versi", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Bersihkan Cache Gambar", "advanced_settings_clear_image_cache_error": "Gagal untuk membersihkan cache gambar", "advanced_settings_clear_image_cache_success": "Sukses membersihkan {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Gunakan opsi ini untuk memfilter media saat sinkronisasi berdasarkan kriteria alternatif. Hanya coba ini dengan aplikasi mendeteksi semua album.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAL] Gunakan filter sinkronisasi album perangkat alternatif", "advanced_settings_log_level_title": "Tingkat log: {level}", "advanced_settings_prefer_remote_subtitle": "Beberapa perangkat akan lambat memuat gambar kecil dari aset lokal. Mengaktifkan opsi ini akan memuat gambar kecil dari server.", "advanced_settings_prefer_remote_title": "Prioritaskan gambar dari server", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Header proxy khusus [EKSPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Mengaktifkan mode hanya-baca, agar foto hanya bisa dilihat. Fitur seperti memilih banyak foto, berbagi, cast, dan penghapusan akan dinonaktifkan. Mode hanya-baca bisa diaktifkan/dinonaktifkan lewat avatar pengguna di layar utama", "advanced_settings_readonly_mode_title": "Mode hanya-baca", + "advanced_settings_self_signed_ssl_subtitle": "Lewati verifikasi sertifikat SSL untuk titik akhir server. Diperlukan untuk sertifikat yang ditandatangani sendiri.", + "advanced_settings_self_signed_ssl_title": "Izinkan sertifikat SSL yang ditandatangani sendiri [EKSPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Hapus atau pulihkan aset pada perangkat ini secara otomatis ketika tindakan dilakukan di web", "advanced_settings_sync_remote_deletions_title": "Sinkronisasi penghapusan jarak jauh [EKSPERIMENTAL]", "advanced_settings_tile_subtitle": "Pengaturan pengguna tingkat lanjut", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Yakin ingin menghapus album {album}?", "album_delete_confirmation_description": "Jika album ini dibagikan, pengguna lain tidak akan dapat mengaksesnya lagi.", "album_deleted": "Album dihapus", + "album_info_card_backup_album_excluded": "Dikecualikan", + "album_info_card_backup_album_included": "Disertakan", "album_info_updated": "Info album diperbarui", + "album_leave": "Tinggalkan album?", + "album_leave_confirmation": "Yakin ingin keluar dari {album}?", "album_name": "Nama Album", "album_options": "Opsi album", "album_remove_user": "Keluarkan pengguna?", "album_remove_user_confirmation": "Yakin ingin mengeluarkan {user}?", "album_search_not_found": "Tidak ada album yang ditemukan sesuai pencarian Anda", + "album_selected": "Album yang dipilih", "album_share_no_users": "Sepertinya Anda telah membagikan album ini dengan semua pengguna atau tidak ada pengguna yang bisa menjadi tujuan berbagi.", "album_summary": "Ringkasan album", "album_updated": "Album diperbarui", "album_updated_setting_description": "Terima notifikasi email ketika album bersama memiliki aset baru", "album_upload_assets": "Unggah aset dari komputer Anda dan tambahkan ke album", + "album_user_left": "Keluar dari {album}", + "album_user_removed": "{user} dikeluarkan", + "album_viewer_appbar_delete_confirm": "Yakin ingin menghapus album ini dari akun Anda?", "album_viewer_appbar_share_err_delete": "Gagal menghapus album", + "album_viewer_appbar_share_err_leave": "Gagal keluar dari album", + "album_viewer_appbar_share_err_remove": "Ada masalah saat menghapus aset dari album", + "album_viewer_appbar_share_err_title": "Gagal mengubah judul album", + "album_viewer_appbar_share_leave": "Keluar dari album", + "album_viewer_appbar_share_to": "Bagikan Ke", "album_viewer_page_share_add_users": "Tambah pengguna", "album_with_link_access": "Izinkan siapa pun dengan tautan untuk melihat foto dan orang dalam album ini.", "albums": "Album", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Urutan awal aset saat membuat album baru.", "albums_feature_description": "Koleksi foto atau video yang dapat dibagikan kepada pengguna lain.", "albums_on_device_count": "Album di perangkat ({count})", + "albums_selected": "{count, plural, one {# album yang dipilih} other {# album yang dipilih}}", "all": "Semua", "all_albums": "Semua album", "all_people": "Semua orang", "all_photos": "Semua foto", "all_videos": "Semua video", "allow_dark_mode": "Izinkan mode gelap", + "allow_edits": "Izinkan pengeditan", "allow_public_user_to_download": "Izinkan pengguna publik untuk mengunduh", "allow_public_user_to_upload": "Izinkan pengguna publik untuk mengunggah", "allowed": "Diizinkan", @@ -549,12 +528,14 @@ "always_keep": "Selalu simpan", "always_keep_photos_hint": "Fitur Bebaskan Ruang akan menyimpan semua foto di perangkat ini.", "always_keep_videos_hint": "Fitur Bebaskan Ruang akan menyimpan semua video di perangkat ini.", + "anti_clockwise": "Berlawanan arah jarum jam", "api_key": "Kunci API", "api_key_description": "Nilai ini hanya akan ditampilkan sekali. Pastikan untuk menyalin sebelum menutup jendela ini.", "api_key_empty": "Nama Kunci API Anda tidak boleh kosong", "api_keys": "Kunci API", "app_architecture_variant": "Varian (Arsitektur)", "app_bar_signout_dialog_content": "Yakin ingin keluar?", + "app_bar_signout_dialog_ok": "Ya", "app_bar_signout_dialog_title": "Keluar", "app_download_links": "Tautan Unduh Aplikasi", "app_settings": "Pengaturan Aplikasi", @@ -565,19 +546,27 @@ "archive": "Arsip", "archive_action_prompt": "{count} telah ditambahkan ke Arsip", "archive_or_unarchive_photo": "Arsipkan atau batalkan pengarsipan foto", + "archive_page_no_archived_assets": "Tidak ada aset diarsipkan", + "archive_page_title": "Arsip ({count})", "archive_size": "Ukuran arsip", "archive_size_description": "Atur ukuran arsip untuk unduhan (dalam GiB)", "archived": "Diarsipkan", "archived_count": "{count, plural, other {# diarsipkan}}", "are_these_the_same_person": "Apakah ini adalah orang yang sama?", "are_you_sure_to_do_this": "Yakin ingin melakukan ini?", + "array_field_not_fully_supported": "Bidang array membutuhkan pengeditan JSON secara manual", + "asset_action_delete_err_read_only": "Tidak dapat menghapus aset yang bersifat hanya-baca, proses dilewati", + "asset_action_share_err_offline": "Tidak dapat mengambil aset offline, proses dilewati", "asset_added_to_album": "Ditambahkan ke album", "asset_adding_to_album": "Menambahkan ke albumâ€Ļ", "asset_created": "Aset dibuat", - "asset_day_count": "{date}: {count, plural, one {# aset} other {# aset}}", "asset_description_updated": "Deskripsi aset telah diperbarui", + "asset_filename_is_offline": "Aset {filename} sedang offline", + "asset_has_unassigned_faces": "Aset memiliki wajah yang belum ditetapkan", "asset_hashing": "Membuat hashâ€Ļ", "asset_list_group_by_sub_title": "Kelompokkan berdasarkan", + "asset_list_layout_settings_dynamic_layout_title": "Tata letak dinamis", + "asset_list_layout_settings_group_automatically": "Otomatis", "asset_list_layout_settings_group_by": "Kelompokkan aset berdasarkan", "asset_list_layout_settings_group_by_month_day": "Bulan + tanggal", "asset_list_layout_sub_title": "Penataan", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Aset tidak ditemukan di iCloud. Aset mungkin tidak dapat diakses karena file rusak di iCloud", "asset_offline": "Aset Offline", "asset_offline_description": "Aset eksternal ini tidak ada lagi di disk. Hubungi administrator Immich Anda untuk mendapatkan bantuan.", + "asset_restored_successfully": "Aset berhasil dipulihkan", "asset_skipped": "Dilewati", "asset_skipped_in_trash": "Dalam sampah", + "asset_trashed": "Aset dibuang", "asset_troubleshoot": "Mengatasi Masalah Aset", "asset_uploaded": "Diunggah", "asset_uploading": "Mengunggahâ€Ļ", "asset_viewer_settings_subtitle": "Kelola pengaturan penampil galeri Anda", "asset_viewer_settings_title": "Penampil Aset", "assets": "Aset", + "assets_added_count": "{count, plural, one {# aset} other {# aset}} ditambahkan", "assets_added_to_album_count": "{count, plural, one {# aset} other {# aset}} ditambahkan ke album", - "assets_added_to_album_partial_count": "Berhasil menambahkan {successCount} dari {totalCount} {totalCount, plural, one {aset} other {aset}} ke album", "assets_added_to_albums_count": "{assetTotal, plural, one {# aset} other {# aset}} ditambahkan ke {albumTotal, plural, one {# album} other {# album}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Aset} other {Aset}} tidak dapat ditambahkan ke album", "assets_cannot_be_added_to_albums": "{count, plural, one {Aset} other {Aset}} tidak dapat ditambahkan ke album mana pun", "assets_count": "{count, plural, one {# aset} other {# aset}}", + "assets_deleted_permanently": "{count} aset dihapus secara permanen", + "assets_deleted_permanently_from_server": "{count} aset dihapus secara permanen dari server Immich", + "assets_downloaded_failed": "{count, plural, one {# file diunduh - {error} file gagal} other {# file diunduh - {error} file gagal}}", + "assets_downloaded_successfully": "{count, plural, one {# file berhasil diunduh} other {# file berhasil diunduh}}", "assets_moved_to_trash_count": "{count, plural, one {# aset} other {# aset}} dipindahkan ke sampah", "assets_permanently_deleted_count": "{count, plural, one {# aset} other {# aset}} dihapus secara permanen", "assets_removed_count": "{count, plural, one {# aset} other {# aset}} dihapus", + "assets_removed_permanently_from_device": "{count} aset dihapus secara permanen dari perangkat Anda", "assets_restore_confirmation": "Yakin ingin memulihkan semua aset yang dibuang? Anda tidak dapat mengurungkan tindakan ini! Perlu diingat bahwa aset offline tidak dapat dipulihkan.", "assets_restored_count": "{count, plural, one {# aset} other {# aset}} dipulihkan", + "assets_restored_successfully": "{count} aset berhasil dipulihkan", "assets_trashed": "{count} aset dipindahkan ke sampah", "assets_trashed_count": "{count, plural, one {# aset} other {# aset}} dibuang ke sampah", + "assets_trashed_from_server": "{count} aset dipindahkan ke sampah dari server Immich", "assets_were_part_of_album_count": "{count, plural, one {Aset} other {Aset}} telah menjadi bagian dari album sebelumnya", "assets_were_part_of_albums_count": "{count, plural, one {Aset} other {Aset}} sudah ada di album", "authorized_devices": "Perangkat Terotorisasi", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Putar otomatis tayangan slide", "back": "Kembali", "back_close_deselect": "Kembali, tutup, atau batalkan pemilihan", + "background_backup_running_error": "Pencadangan latar belakang sedang berjalan, tidak dapat memulai pencadangan manual", "background_location_permission": "Izin lokasi latar belakang", "background_location_permission_content": "Untuk beralih jaringan saat berjalan di latar belakang, Immich harus selalu memiliki akses lokasi akurat agar aplikasi dapat membaca nama jaringan Wi-Fi", "background_options": "Opsi Latar Belakang", "backup": "Cadangan", + "backup_album_selection_page_albums_device": "Album di perangkat ({count})", "backup_album_selection_page_albums_tap": "Ketuk untuk menyertakan, ketuk ganda untuk mengecualikan", "backup_album_selection_page_assets_scatter": "Aset dapat tersebar dalam banyak album, sehingga album dapat dipilih atau dikecualikan saat proses pencadangan.", "backup_album_selection_page_select_albums": "Pilih album", "backup_album_selection_page_selection_info": "Info dari Pilihan", + "backup_album_selection_page_total_assets": "Total aset unik", "backup_albums_sync": "Sinkronisasi Cadangan Album", + "backup_all": "Semua", + "backup_background_service_backup_failed_message": "Gagal mencadangkan aset. Mencoba lagiâ€Ļ", "backup_background_service_complete_notification": "Pencadangan aset selesai", + "backup_background_service_connection_failed_message": "Koneksi ke server gagal. Mencoba ulangâ€Ļ", + "backup_background_service_current_upload_notification": "Mengunggah {filename}", "backup_background_service_default_notification": "Memeriksa aset baruâ€Ļ", + "backup_background_service_error_title": "Kesalahan pencadangan", "backup_background_service_in_progress_notification": "Mencadangkan aset Andaâ€Ļ", + "backup_background_service_upload_failure_notification": "Gagal mengunggah {filename}", "backup_controller_page_albums": "Cadangkan Album", + "backup_controller_page_background_app_refresh_disabled_content": "Aktifkan penyegaran aplikasi di latar belakang di Pengaturan > Umum > Penyegaran Aplikasi Latar Belakang untuk menggunakan pencadangan latar belakang.", + "backup_controller_page_background_app_refresh_disabled_title": "Penyegaran aplikasi latar belakang dinonaktifkan", + "backup_controller_page_background_app_refresh_enable_button_text": "Buka pengaturan", "backup_controller_page_background_battery_info_link": "Tunjukkan caranya", "backup_controller_page_background_battery_info_message": "Untuk mendapatkan pengalaman pencadangan latar belakang yang optimal, nonaktifkan setiap pengaturan pengoptimalan baterai yang membatasi aktivitas latar belakang pada aplikasi Immich.\n\nKarena pengaturan ini bergantung pada jenis perangkat, silakan cari informasi yang relevan sesuai dengan produsen perangkat Anda.", "backup_controller_page_background_battery_info_ok": "Oke", "backup_controller_page_background_battery_info_title": "Optimisasi baterai", + "backup_controller_page_background_charging": "Hanya saat mengisi daya", + "backup_controller_page_background_configure_error": "Gagal mengatur layanan latar belakang", "backup_controller_page_background_delay": "Tunda pencadangan aset baru: {duration}", + "backup_controller_page_background_description": "Aktifkan layanan latar belakang untuk mencadangkan aset baru secara otomatis tanpa perlu membuka app", + "backup_controller_page_background_is_off": "Pencadangan otomatis di latar belakang sedang nonaktif", + "backup_controller_page_background_is_on": "Pencadangan otomatis di latar belakang sedang aktif", + "backup_controller_page_background_turn_off": "Matikan layanan latar belakang", + "backup_controller_page_background_turn_on": "Nyalakan layanan latar belakang", + "backup_controller_page_background_wifi": "Hanya melalui Wi-Fi", "backup_controller_page_backup": "Cadangkan", "backup_controller_page_backup_selected": "Terpilih: ", "backup_controller_page_backup_sub": "Foto dan video yang dicadangkan", + "backup_controller_page_created": "Dibuat pada: {date}", + "backup_controller_page_desc_backup": "Aktifkan pencadangan di latar depan untuk mengunggah aset baru ke server secara otomatis saat aplikasi terbuka.", "backup_controller_page_excluded": "Dikecualikan: ", + "backup_controller_page_failed": "Gagal ({count})", + "backup_controller_page_filename": "Nama file: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informasi Cadangan", "backup_controller_page_none_selected": "Tidak ada yang dipilih", "backup_controller_page_remainder": "Sisa", "backup_controller_page_remainder_sub": "Sisa foto dan video untuk dicadangkan dari yang terpilih", "backup_controller_page_server_storage": "Penyimpanan Server", + "backup_controller_page_start_backup": "Mulai Cadangkan", + "backup_controller_page_status_off": "Pencadangan otomatis di latar depan sedang nonaktif", + "backup_controller_page_status_on": "Pencadangan otomatis di latar depan sedang aktif", "backup_controller_page_storage_format": "{used} dari {total} digunakan", "backup_controller_page_to_backup": "Album untuk dicadangkan", "backup_controller_page_total_sub": "Semua foto dan video unik dari album terpilih", + "backup_controller_page_turn_off": "Nonaktifkan pencadangan latar depan", + "backup_controller_page_turn_on": "Aktifkan pencadangan latar depan", + "backup_controller_page_uploading_file_info": "Mengunggah info file", + "backup_err_only_album": "Tidak dapat menghapus satu-satunya album", "backup_error_sync_failed": "Sinkronisasi gagal. Tidak dapat memproses cadangan.", "backup_info_card_assets": "aset", + "backup_manual_cancelled": "Dibatalkan", + "backup_manual_in_progress": "Dalam proses unggah. Coba lagi nanti", + "backup_manual_success": "Sukses", + "backup_manual_title": "Status unggah", "backup_options": "Opsi Cadangan", + "backup_options_page_title": "Opsi cadangan", + "backup_setting_subtitle": "Kelola pengaturan unggahan latar belakang dan latar depan", "backup_settings_subtitle": "Kelola pengaturan unggahan", + "backup_upload_details_page_more_details": "Ketuk untuk detail lebih lanjut", "backward": "Mundur", - "battery_optimization_backup_reliability": "Menonaktifkan pengoptimalan baterai dapat meningkatkan keandalan pencadangan latar belakang", "biometric_auth_enabled": "Autentikasi biometrik diaktifkan", "biometric_locked_out": "Anda terkunci oleh autentikasi biometrik", "biometric_no_options": "Opsi biometrik tidak tersedia", "biometric_not_available": "Autentikasi biometrik tidak tersedia di perangkat ini", + "birthdate_saved": "Tanggal lahir berhasil disimpan", "birthdate_set_description": "Tanggal lahir digunakan untuk menghitung umur orang ini pada saat foto diambil.", "blurred_background": "Latar belakang buram", - "browse_templates": "Telusuri templat", "bugs_and_feature_requests": "Bug dan Permintaan Fitur", "build": "Build", "build_image": "Versi Build", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Yakin ingin menyimpan {count, plural, one {# aset duplikat} other {# aset duplikat}}? Ini akan menyelesaikan semua kelompok duplikat tanpa menghapus apa pun.", "bulk_trash_duplicates_confirmation": "Yakin ingin membuang massal {count, plural, one {# aset duplikat} other {# aset duplikat}}? Ini akan menyimpan aset terbesar dari setiap kelompok dan membuang semua duplikat lainnya.", "buy": "Beli Immich", + "cache_settings_clear_cache_button": "Hapus cache", + "cache_settings_clear_cache_button_title": "Menghapus cache aplikasi. Performa aplikasi akan terpengaruh hingga cache dibuat kembali.", + "cache_settings_duplicated_assets_clear_button": "BERSIHKAN", + "cache_settings_duplicated_assets_subtitle": "Foto dan video yang masuk dalam daftar abaikan oleh aplikasi", + "cache_settings_duplicated_assets_title": "Aset Duplikat ({count})", + "cache_settings_statistics_album": "Pustaka gambar kecil", + "cache_settings_statistics_full": "Gambar penuh", + "cache_settings_statistics_shared": "Gambar kecil dari album bersama", + "cache_settings_statistics_thumbnail": "Gambar kecil", + "cache_settings_statistics_title": "Penggunaan cache", + "cache_settings_subtitle": "Mengontrol perilaku cache aplikasi ponsel Immich", + "cache_settings_tile_subtitle": "Mengatur perilaku penyimpanan lokal", + "cache_settings_tile_title": "Penyimpanan Lokal", + "cache_settings_title": "Pengaturan Cache", "camera": "Kamera", "camera_brand": "Merek kamera", "camera_model": "Model kamera", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Tidak dapat memperbarui deskripsi", "cast": "Cast", "cast_description": "Mengonfigurasi tujuan cast yang tersedia", - "change": "Ubah", "change_date": "Ubah tanggal", "change_description": "Ubah deskripsi", "change_display_order": "Ubah urutan tampilan", + "change_expiration_time": "Ubah waktu kedaluwarsa", "change_location": "Ubah lokasi", "change_name": "Ubah nama", "change_name_successfully": "Nama berhasil diubah", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Sandi tidak cocok", "change_password_form_reenter_new_password": "Masukkan Ulang Sandi Baru", "change_pin_code": "Ubah kode PIN", + "change_trigger": "Ubah pemicu", + "change_trigger_prompt": "Yakin ingin mengubah pemicunya? Tindakan ini akan menghapus seluruh aksi dan filter yang sudah ada.", "change_your_password": "Ubah sandi Anda", "changed_visibility_successfully": "Keterlihatan berhasil diubah", "charging": "Mengisi daya", "charging_requirement_mobile_backup": "Cadangan latar belakang memerlukan perangkat dalam keadaan mengisi daya", + "check_corrupt_asset_backup": "Periksa cadangan aset yang rusak", + "check_corrupt_asset_backup_button": "Lakukan pemeriksaan", + "check_corrupt_asset_backup_description": "Jalankan pemeriksaan ini hanya melalui Wi-Fi dan setelah semua aset dicadangkan. Prosedur ini mungkin memerlukan waktu beberapa menit.", "check_logs": "Periksa Log", "checksum": "Checksum", - "choose": "Pilih", "choose_matching_people_to_merge": "Pilih orang yang cocok untuk digabungkan", "city": "Kota", "cleanup_confirm_description": "Immich menemukan {count} aset (dibuat sebelum {date}) telah aman dicadangkan di server. Hapus salinan lokal dari perangkat ini?", @@ -719,10 +774,11 @@ "clear": "Hapus", "clear_all": "Hapus semua", "clear_all_recent_searches": "Hapus semua pencarian terakhir", - "clear_failed_count": "Hapus yang gagal ({count})", "clear_file_cache": "Hapus Cache File", "clear_message": "Hapus pesan", "clear_value": "Hapus nilai", + "client_cert_dialog_msg_confirm": "Oke", + "client_cert_enter_password": "Masukkan Sandi", "client_cert_import": "Impor", "client_cert_import_success_msg": "Sertifikat klien telah diimpor", "client_cert_invalid_msg": "File sertifikat tidak valid atau sandinya salah", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Sertifikat klien dihapus", "client_cert_subtitle": "Hanya mendukung format PKCS12 (.p12, .pfx). Impor/hapus sertifikat hanya tersedia sebelum masuk", "client_cert_title": "Sertifikat SSL klien [EKSPERIMENTAL]", + "clockwise": "Searah jarum jam", "close": "Tutup", "collapse": "Ciutkan", "collapse_all": "Ciutkan aemua", "color": "Warna", + "color_theme": "Tema warna", "command": "Perintah", "command_palette_prompt": "Temukan halaman, tindakan, atau perintah dengan cepat", "command_palette_to_close": "untuk menutup", @@ -747,7 +805,6 @@ "comments_are_disabled": "Komentar dinonaktifkan", "common_create_new_album": "Buat album baru", "completed": "Selesai", - "configuration": "Konfigurasi", "confirm": "Konfirmasi", "confirm_admin_password": "Konfirmasi Sandi Admin", "confirm_delete_face": "Yakin ingin menghapus wajah {name} dari aset?", @@ -762,16 +819,19 @@ "contain": "Pas Di Dalam", "context": "Konteks", "continue": "Lanjutkan", - "control_bottom_app_bar_add_tags": "Tambah Tag", + "control_bottom_app_bar_create_new_album": "Buat album baru", + "control_bottom_app_bar_delete_from_immich": "Hapus dari Immich", "control_bottom_app_bar_delete_from_local": "Hapus dari perangkat", "control_bottom_app_bar_edit_location": "Edit Lokasi", "control_bottom_app_bar_edit_time": "Edit Tanggal & Waktu", + "control_bottom_app_bar_share_link": "Bagikan Tautan", + "control_bottom_app_bar_share_to": "Bagikan Ke", "control_bottom_app_bar_trash_from_immich": "Pindahkan ke Sampah", "copied_image_to_clipboard": "Gambar disalin ke papan klip.", "copied_to_clipboard": "Disalin ke papan klip!", "copy_error": "Salin kesalahan", + "copy_file_path": "Salin jalur file", "copy_image": "Salin Gambar", - "copy_json": "Salin JSON", "copy_link": "Salin tautan", "copy_link_to_clipboard": "Salin tautan ke papan klip", "copy_password": "Salin sandi", @@ -792,8 +852,6 @@ "create_new_person": "Buat orang baru", "create_new_person_hint": "Tetapkan aset yang dipilih ke orang yang baru", "create_new_user": "Buat pengguna baru", - "create_person": "Buat orang", - "create_person_subtitle": "Tambahkan nama pada wajah yang dipilih untuk membuat dan menandai orang baru", "create_shared_album_page_share_add_assets": "TAMBAHKAN ASET", "create_shared_album_page_share_select_photos": "Pilih Foto", "create_shared_link": "Buat tautan bersama", @@ -805,33 +863,37 @@ "created_at": "Dibuat", "creating_linked_albums": "Membuat album tertaut...", "crop": "Pangkas", + "crop_aspect_ratio_fixed": "Diperbaiki", "crop_aspect_ratio_free": "Bebas", "crop_aspect_ratio_original": "Asli", "crop_aspect_ratio_square": "Persegi", + "curated_object_page_title": "Benda", "current_device": "Perangkat saat ini", "current_pin_code": "Kode PIN saat ini", "current_server_address": "Alamat server saat ini", "custom_date": "Tanggal khusus", "custom_locale": "Pelokalan khusus", "custom_locale_description": "Format tanggal, waktu, dan angka berdasarkan bahasa dan wilayah yang dipilih", + "custom_url": "URL Khusus", "cutoff_date_description": "Simpan foto dari â€Ļ terakhir", "cutoff_day": "{count, plural, one {hari} other {hari}}", "cutoff_year": "{count, plural, one {tahun} other {tahun}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM yyyy", "dark": "Gelap", "dark_theme": "Beralih ke tema gelap", "date": "Tanggal", "date_after": "Tanggal setelah", "date_and_time": "Tanggal dan Waktu", "date_before": "Tanggal sebelum", - "date_of_birth": "Tanggal lahir", + "date_format": "E, d LLL y â€ĸ HH:mm", "date_of_birth_saved": "Tanggal lahir berhasil disimpan", "date_range": "Rentang tanggal", - "date_time_original": "Tanggal/Waktu Asli", "day": "Hari", "days": "Hari", "deduplicate_all": "Hapus semua duplikat", - "default_quality_subtitle": "Kualitas yang digunakan saat mengetuk bagikan. Tekan lama tombol bagikan untuk memilih setiap kali.", - "default_share_quality": "Kualitas bagikan default", + "default_locale": "Bahasa Default", + "default_locale_description": "Sesuaikan format tanggal dan angka sesuai dengan pengaturan wilayah browser Anda", "delete": "Hapus", "delete_action_confirmation_message": "Yakin ingin menghapus aset ini? Tindakan ini akan memindahkan aset ke tempat sampah pada server dan akan mengkonfirmasi apakah Anda ingin menghapusnya juga secara lokal", "delete_action_prompt": "{count} item telah dihapus", @@ -840,6 +902,8 @@ "delete_dialog_alert": "Item ini akan dihapus permanen dari Immich dan perangkat", "delete_dialog_alert_local": "Item ini akan dihapus secara permanen dari perangkat Anda, tapi masih akan tetap tersedia di server Immich", "delete_dialog_alert_local_non_backed_up": "Beberapa item belum dicadangkan ke Immich dan akan dihapus secara permanen dari perangkat Anda", + "delete_dialog_alert_remote": "Item ini akan dihapus secara permanen dari server Immich", + "delete_dialog_ok_force": "Lanjutkan Hapus", "delete_dialog_title": "Hapus Permanen", "delete_duplicates_confirmation": "Yakin ingin menghapus duplikat ini secara permanen?", "delete_face": "Hapus wajah", @@ -858,16 +922,21 @@ "delete_tag_confirmation_prompt": "Yakin ingin menghapus tag {tagName}?", "delete_user": "Hapus pengguna", "deleted_shared_link": "Tautan bersama dihapus", + "deletes_missing_assets": "Menghapus aset yang hilang dari disk", "description": "Deskripsi", + "description_input_hint_text": "Tambah deskripsi...", + "description_input_submit_error": "Terjadi kesalahan saat memperbarui deskripsi, periksa log untuk detail selengkapnya", "deselect_all": "Batalkan semua pilihan", "details": "Detail", "direction": "Arah", "disable": "Nonaktifkan", "disabled": "Dinonaktifkan", + "disallow_edits": "Jangan izinkan pengeditan", "discord": "Discord", "discover": "Jelajahi", "discovered_devices": "Perangkat yang ditemukan", "dismiss_all_errors": "Abaikan semua kesalahan", + "dismiss_error": "Abaikan kesalahan", "display_options": "Opsi penampilan", "display_order": "Urutan penampilan", "display_original_photos": "Tampilkan foto asli", @@ -876,9 +945,11 @@ "documentation": "Dokumentasi", "done": "Selesai", "download": "Unduh", + "download_action_prompt": "Mengunduh {count} aset", "download_canceled": "Unduhan dibatalkan", "download_complete": "Unduhan selesai", "download_enqueue": "Unduhan diantrekan", + "download_error": "Kesalahan Unduh", "download_failed": "Unduhan gagal", "download_finished": "Unduhan selesai", "download_include_embedded_motion_videos": "Video tersemat", @@ -888,15 +959,15 @@ "download_paused": "Unduhan dijeda", "download_settings": "Unduhan", "download_settings_description": "Kelola pengaturan terkait dengan pengunduhan aset", + "download_started": "Unduhan dimulai", + "download_sucess": "Unduhan sukses", + "download_sucess_android": "Media ini telah diunduh ke DCIM/Immich", "download_waiting_to_retry": "Menunggu untuk mencoba lagi", "downloading": "Mengunduh", "downloading_asset_filename": "Mengunduh aset {filename}", "downloading_from_icloud": "Mengunduh dari iCloud", "downloading_media": "Mengunduh media", - "drag_to_reorder": "Seret untuk mengurutkan ulang", "drop_files_to_upload": "Lepaskan file di mana saja untuk mengunggah", - "duplicate": "Duplikat", - "duplicate_workflow": "Duplikat alur kerja", "duplicates": "Duplikat", "duplicates_description": "Selesaikan setiap kelompok dengan menunjukkan mana saja yang merupakan duplikat, jika ada.", "duration": "Durasi", @@ -908,7 +979,9 @@ "edit_date_and_time": "Edit tanggal dan waktu", "edit_date_and_time_action_prompt": "{count} tanggal dan waktu diedit", "edit_date_and_time_by_offset": "Ubah tanggal berdasarkan selisih", + "edit_date_and_time_by_offset_interval": "Rentang tanggal baru: {from} - {to}", "edit_description": "Edit deskripsi", + "edit_description_prompt": "Silakan pilih deskripsi baru:", "edit_exclusion_pattern": "Edit pola pengecualian", "edit_faces": "Edit wajah", "edit_key": "Edit kunci", @@ -923,6 +996,9 @@ "edit_user": "Edit pengguna", "edit_workflow": "Edit alur kerja", "editor": "Editor", + "editor_close_without_save_prompt": "Perubahan tidak akan disimpan", + "editor_close_without_save_title": "Tutup editor?", + "editor_confirm_reset_all_changes": "Yakin ingin mengatur ulang semua perubahan?", "editor_discard_edits_confirm": "Buang hasil edit", "editor_discard_edits_prompt": "Anda memiliki hasil edit yang belum disimpan. Yakin ingin membuangnya?", "editor_discard_edits_title": "Buang hasil edit?", @@ -951,7 +1027,9 @@ "enter_your_pin_code": "Masukkan kode PIN Anda", "enter_your_pin_code_subtitle": "Masukkan kode PIN Anda untuk mengakses folder terkunci", "error": "Kesalahan", + "error_change_sort_album": "Gagal mengubah urutan album", "error_delete_face": "Terjadi kesalahan saat menghapus wajah dari aset", + "error_getting_places": "Terjadi kesalahan saat mengambil lokasi", "error_loading_albums": "Terjadi kesalahan saat memuat album", "error_loading_image": "Terjadi kesalahan saat memuat gambar", "error_loading_partners": "Terjadi kesalahan saat memuat partner: {error}", @@ -991,7 +1069,6 @@ "failed_to_remove_product_key": "Gagal menghapus kunci produk", "failed_to_reset_pin_code": "Gagal mengatur ulang kode PIN", "failed_to_stack_assets": "Gagal menumpuk aset", - "failed_to_tag_assets": "Gagal menandai aset", "failed_to_unstack_assets": "Gagal membatalkan penumpukan aset", "failed_to_update_notification_status": "Gagal memperbarui status notifikasi", "incorrect_email_or_password": "Email atau sandi salah", @@ -1090,10 +1167,18 @@ "exif": "EXIF", "exif_bottom_sheet_description": "Tambahkan Deskripsi...", "exif_bottom_sheet_description_error": "Terjadi kesalahan saat memperbarui deskripsi", + "exif_bottom_sheet_details": "DETAIL", + "exif_bottom_sheet_location": "LOKASI", "exif_bottom_sheet_no_description": "Tidak ada deskripsi", + "exif_bottom_sheet_people": "ORANG", + "exif_bottom_sheet_person_add_person": "Tambah nama", "exit_slideshow": "Keluar dari Salindia", "expand": "Bentangkan", "expand_all": "Bentangkan semua", + "experimental_settings_new_asset_list_subtitle": "Dalam proses", + "experimental_settings_new_asset_list_title": "Aktifkan grid foto eksperimental", + "experimental_settings_subtitle": "Risiko ditanggung pengguna!", + "experimental_settings_title": "Eksperimental", "expire_after": "Kedaluwarsa setelah", "expired": "Kedaluwarsa", "expires_date": "Kedaluwarsa pada {date}", @@ -1103,40 +1188,41 @@ "export_as_json": "Ekspor sebagai JSON", "export_database": "Ekspor Basis Data", "export_database_description": "Ekspor Basis Data SQLite", - "exposure_time": "Waktu Eksposur", "extension": "Ekstensi", "external": "Eksternal", "external_libraries": "Pustaka Eksternal", "external_network": "Jaringan Eksternal", "external_network_sheet_info": "Ketika tidak berada di jaringan Wi-Fi yang dipilih, aplikasi akan terhubung ke server melalui URL pertama di bawah ini yang dapat dijangkaunya, mulai dari atas ke bawah", - "f_number": "F-Number", "face_unassigned": "Tidak ditetapkan", "failed": "Gagal", "failed_count": "Gagal: {count}", "failed_to_authenticate": "Autentikasi gagal", - "failed_to_delete_file": "Gagal menghapus file", "failed_to_load_assets": "Gagal memuat aset", "failed_to_load_folder": "Gagal memuat folder", "favorite": "Favorit", "favorite_action_prompt": "{count} telah ditambahkan ke Favorit", "favorite_or_unfavorite_photo": "Favorit atau batalkan pemfavoritan foto", "favorites": "Favorit", + "favorites_page_no_favorites": "Tidak ada aset favorit", "feature_photo_updated": "Foto unggulan diperbarui", "features": "Fitur", + "features_in_development": "Fitur dalam Pengembangan", "features_setting_description": "Kelola fitur aplikasi", "file_name_or_extension": "Nama file atau ekstensi", "file_name_text": "Nama file", + "file_name_with_value": "Nama file: {file_name}", "file_size": "Ukuran file", "filename": "Nama file", "filetype": "Jenis file", "filter": "Filter", + "filter_description": "Kondisi untuk memfilter aset target", "filter_people": "Filter orang", "filter_places": "Filter tempat", "filter_tags": "Filter tag", "filters": "Filter", + "find_them_fast": "Temukan dengan cepat berdasarkan nama dengan pencarian", "first": "Pertama", "fix_incorrect_match": "Perbaiki kecocokan yang salah", - "focal_length": "Panjang Fokal", "folder": "Folder", "folder_not_found": "Folder tidak ditemukan", "folders": "Folder", @@ -1147,7 +1233,6 @@ "free_up_space_description": "Pindahkan foto dan video yang dicadangkan ke tempat sampah perangkat Anda untuk mengosongkan ruang. Salinan Anda di server tetap aman.", "free_up_space_settings_subtitle": "Kosongkan penyimpanan perangkat", "full_path": "Jalur lengkap: {path}", - "full_path_or_folder": "Jalur lengkap atau folder", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Fitur ini memuat sumber daya eksternal dari Google agar dapat berfungsi.", "general": "Umum", @@ -1168,6 +1253,7 @@ "group_owner": "Kelompokkan berdasarkan pemilik", "group_places_by": "Kelompokkan tempat berdasarkanâ€Ļ", "group_year": "Kelompokkan berdasarkan tahun", + "haptic_feedback_switch": "Nyalakan getar", "haptic_feedback_title": "Getar", "has_quota": "Memiliki kuota", "hash_asset": "Aset hash", @@ -1188,12 +1274,29 @@ "hide_schema": "Sembunyikan skema", "hide_text_recognition": "Sembunyikan pengenalan teks", "hide_unnamed_people": "Sembunyikan orang tanpa nama", + "home_page_add_to_album_conflicts": "{added} aset telah ditambahkan ke album {album}. {failed} aset sudah ada dalam album.", + "home_page_add_to_album_err_local": "Tidak dapat menambahkan aset lokal ke album, dilewati", + "home_page_add_to_album_success": "Aset {added} telah ditambahkan ke {album}.", + "home_page_album_err_partner": "Tidak dapat menambahkan aset pasangan ke album, dilewati", + "home_page_archive_err_local": "Tidak dapat mengarsipkan aset lokal, dilewati", + "home_page_archive_err_partner": "Tidak dapat mengarsipkan aset pasangan, dilewati", "home_page_building_timeline": "Memuat linimasa", + "home_page_delete_err_partner": "Tidak dapat menghapus aset pasangan, dilewati", + "home_page_delete_remote_err_local": "Aset lokal ada dalam pilihan hapus jarak jauh, dilewati", + "home_page_favorite_err_local": "Belum dapat menandai aset lokal sebagai favorit, dilewati", + "home_page_favorite_err_partner": "Belum dapat menandai aset pasangan sebagai favorit, dilewati", + "home_page_first_time_notice": "Jika ini pertama kali Anda menggunakan aplikasi, pastikan untuk memiliki album untuk dicadangkan agar lini masa Anda terisi oleh foto dan video dalam album", + "home_page_locked_error_local": "Tidak dapat memindahkan aset lokal ke folder terkunci, lewati", + "home_page_locked_error_partner": "Tidak dapat memindahkan aset partner ke folder terkunci, lewati", + "home_page_share_err_local": "Tidak dapat membagikan aset lokal melalui tautan, dilewati", + "home_page_upload_err_limit": "Hanya dapat mengunggah maksimal 30 aset dalam satu waktu, dilewati", "host": "Host", "hour": "Jam", "hours": "Jam", "id": "ID", "idle": "Siaga", + "ignore_icloud_photos": "Abaikan foto iCloud", + "ignore_icloud_photos_description": "Foto yang disimpan di iCloud tidak akan diunggah ke server Immich", "image": "Gambar", "image_alt_text_date": "{isVideo, select, true {Video} other {Gambar}} diambil pada {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Gambar}} diambil dengan {person1} pada {date}", @@ -1205,6 +1308,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Gambar}} diambil di {city}, {country} dengan {person1} dan {person2} pada {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Gambar}} diambil di {city}, {country} dengan {person1}, {person2}, dan {person3} pada {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Gambar}} diambil di {city}, {country} berisi {person1}, {person2}, dan {additionalCount, number} lainnya pada {date}", + "image_saved_successfully": "Gambar disimpan", + "image_viewer_page_state_provider_download_started": "Unduhan Dimulai", + "image_viewer_page_state_provider_download_success": "Unduhan Sukses", + "image_viewer_page_state_provider_share_error": "Kesalahan Saat Berbagi", "immich_logo": "Logo Immich", "immich_web_interface": "Antarmuka Web Immich", "import_from_json": "Impor dari JSON", @@ -1219,7 +1326,6 @@ "individual_share": "Bagikan individu", "individual_shares": "Pembagian individu", "info": "Info", - "integrity_checks": "Pemeriksaan Integritas", "interval": { "day_at_onepm": "Setiap hari pada 13.00", "hours": "Setiap {hours, plural, one {jam} other {{hours, number} jam}}", @@ -1230,9 +1336,16 @@ "invalid_date_format": "Format tanggal tidak valid", "invite_people": "Undang Orang", "invite_to_album": "Undang ke album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Pengambilan dijalankan pada {dateTime}", + "ios_debug_info_last_sync_at": "Sinkronisasi terakhir pada {dateTime}", + "ios_debug_info_no_processes_queued": "Tidak ada proses latar belakang dalam antrean", + "ios_debug_info_no_sync_yet": "Belum ada pekerjaan sinkronisasi latar belakang yang dijalankan", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proses latar belakang dalam antrean} other {{count} proses latar belakang dalam antrean}}", + "ios_debug_info_processing_ran_at": "Pemrosesan dijalankan pada {dateTime}", "items_count": "{count, plural, one {# item} other {# item}}", "jobs": "Tugas", + "json_editor": "Editor JSON", + "json_error": "Kesalahan JSON", "keep": "Simpan", "keep_albums": "Simpan album", "keep_albums_count": "Menyimpan {count} {count, plural, one {album} other {album}}", @@ -1259,12 +1372,14 @@ "leave": "Tinggalkan", "leave_album": "Keluar dari album", "lens_model": "Model lensa", - "less": "Lebih sedikit", "let_others_respond": "Biarkan orang lain merespons", "level": "Tingkat", "library": "Pustaka", "library_add_folder": "Tambahkan folder", "library_edit_folder": "Edit folder", + "library_options": "Opsi pustaka", + "library_page_device_albums": "Album pada Perangkat", + "library_page_new_album": "Album baru", "library_page_sort_asset_count": "Jumlah aset", "library_page_sort_created": "Tanggal dibuat", "library_page_sort_last_modified": "Terakhir diubah", @@ -1274,14 +1389,11 @@ "light_theme": "Ganti ke mode terang", "like": "Suka", "like_deleted": "Suka dihapus", - "link": "Tautan", "link_motion_video": "Tautan video gerak", "link_to_docs": "Untuk informasi lebih lanjut, silakan lihat dokumentasi.", "link_to_oauth": "Tautkan ke OAuth", "linked_oauth_account": "Akun OAuth tertaut", "list": "Daftar", - "live": "Langsung", - "load_more": "Muat Lebih Banyak", "loading": "Memuat", "loading_search_results_failed": "Pemuatan hasil pencarian gagal", "local": "Lokal", @@ -1310,9 +1422,11 @@ "login": "Log masuk", "login_disabled": "Log masuk telah dinonaktifkan", "login_form_api_exception": "Kesalahan API. Periksa URL server dan coba lagi.", + "login_form_back_button_text": "Kembali", "login_form_email_hint": "emailmu@example.com", "login_form_endpoint_hint": "http://ip-server-anda:port", "login_form_endpoint_url": "URL Endpoint Server", + "login_form_err_http": "Harap tentukan http:// atau https://", "login_form_err_invalid_email": "Email Tidak Valid", "login_form_err_invalid_url": "URL Tidak Valid", "login_form_err_leading_whitespace": "Spasi awal", @@ -1322,9 +1436,10 @@ "login_form_failed_login": "Log masuk gagal, periksa URL server, email, dan sandi", "login_form_handshake_exception": "Terjadi Handshake Exception dengan server. Aktifkan dukungan sertifikat swatanda di pengaturan jika Anda menggunakan sertifikat swatanda.", "login_form_password_hint": "sandi", - "login_form_server_empty": "Masukkan URL server", - "login_form_server_error": "Tidak dapat terhubung ke server", - "login_has_been_disabled": "Login telah dinonaktifkan", + "login_form_save_login": "Tetap masuk", + "login_form_server_empty": "Masukkan URL server.", + "login_form_server_error": "Tidak dapat menghubungi server.", + "login_has_been_disabled": "Log masuk telah dinonaktifkan.", "login_password_changed_error": "Terjadi kesalahan saat memperbarui sandi", "login_password_changed_success": "Sandi berhasil diperbarui", "logout_all_device_confirmation": "Yakin ingin keluar dari semua perangkat?", @@ -1339,6 +1454,7 @@ "maintenance_action_restore": "Memulihkan Basis Data", "maintenance_description": "Immich telah ditempatkan dalam mode pemeliharaan.", "maintenance_end": "Akhiri mode pemeliharaan", + "maintenance_end_error": "Gagal mengakhiri mode pemeliharaan.", "maintenance_logged_in_as": "Saat ini masuk sebagai {user}", "maintenance_restore_from_backup": "Pulihkan Dari Cadangan", "maintenance_restore_library": "Pulihkan Pustaka Anda", @@ -1364,6 +1480,7 @@ "manage_media_access_settings": "Buka pengaturan", "manage_media_access_subtitle": "Izinkan aplikasi Immich untuk mengelola dan memindahkan file media.", "manage_media_access_title": "Akses Manajemen Media", + "manage_shared_links": "Kelola tautan bersama", "manage_sharing_with_partners": "Kelola pembagian dengan partner", "manage_the_app_settings": "Kelola pengaturan aplikasi", "manage_your_account": "Kelola akun Anda", @@ -1371,11 +1488,13 @@ "manage_your_devices": "Kelola perangkat Anda yang masuk", "manage_your_oauth_connection": "Kelola koneksi OAuth Anda", "map": "Peta", + "map_assets_in_bounds": "{count, plural, =0 {Tidak ada foto di area ini} one {# foto} other {# foto}}", "map_cannot_get_user_location": "Tidak dapat memperoleh lokasi pengguna", + "map_location_dialog_yes": "Ya", "map_location_picker_page_use_location": "Gunakan lokasi ini", "map_location_service_disabled_content": "Layanan lokasi perlu diaktifkan untuk menampilkan aset yang terletak di lokasi Anda saat ini. Ingin mengaktifkan layanan tersebut sekarang?", "map_location_service_disabled_title": "Layanan Lokasi nonaktif", - "map_marker_for_image": "Penanda peta untuk gambar yang diambil di {city}, {country}", + "map_marker_for_images": "Penanda peta untuk gambar yang diambil di {city}, {country}", "map_marker_with_image": "Penanda peta dengan gambar", "map_no_location_permission_content": "Izin lokasi diperlukan untuk menampilkan aset yang terletak di lokasi Anda. Ingin mengizinkannya sekarang?", "map_no_location_permission_title": "Izin Lokasi ditolak", @@ -1385,46 +1504,17 @@ "map_settings_date_range_option_days": "{days} hari terakhir", "map_settings_date_range_option_year": "1 tahun terakhir", "map_settings_date_range_option_years": "{years} tahun terakhir", + "map_settings_dialog_title": "Pengaturan Peta", "map_settings_include_show_archived": "Sertakan yang diarsipkan", "map_settings_include_show_partners": "Sertakan Partner", "map_settings_only_show_favorites": "Tampilkan Hanya Favorit", "map_settings_theme_settings": "Tema Peta", + "map_zoom_to_see_photos": "Perkecil untuk lihat foto", "mark_all_as_read": "Tandai semua sebagai telah dibaca", + "mark_as_read": "Tandai sebagai telah dibaca", "marked_all_as_read": "Semua telah ditandai sebagai telah dibaca", "matches": "Yang Cocok", "matching_assets": "Aset yang Cocok", - "media_chrome": { - "auto": "Otomatis", - "captions": "Teks terjemahan", - "captions_off": "Mati", - "closed_captions": "teks terjemahan tertutup", - "decode_error": "Kesalahan dekode", - "disable_captions": "Nonaktifkan teks terjemahan", - "enable_captions": "Aktifkan teks terjemahan", - "enter_fullscreen_mode": "Masuk mode layar penuh", - "exit_fullscreen_mode": "Keluar dari mode layar penuh", - "loop": "Ulangi", - "media_error_description": "Kesalahan media menyebabkan pemutaran dihentikan. Media mungkin rusak atau browser Anda tidak mendukung format ini.", - "media_loading": "memuat media", - "mute": "Bisukan", - "network_error": "Kesalahan jaringan", - "network_error_description": "Kesalahan jaringan menyebabkan pengunduhan media gagal.", - "not_supported_error": "Sumber Tidak Didukung", - "playback_rate": "Kecepatan putar", - "playback_rate_current": "kecepatan putar saat ini", - "playback_rate_value": "Kecepatan putar {playbackRate}", - "playback_time": "waktu pemutaran", - "quality": "Kualitas", - "second": "detik", - "seconds": "detik", - "time_value_of_total_time": "{currentTime} dari {totalTime}", - "time_value_remaining": "{time} tersisa", - "unmute": "Bunyikan", - "unsupported_error_description": "Terjadi kesalahan yang tidak didukung. Server atau jaringan gagal, atau browser Anda tidak mendukung format ini.", - "video_not_loaded_unknown_time": "video belum dimuat, waktu tidak diketahui.", - "video_player": "pemutar video", - "volume": "volume" - }, "media_type": "Jenis media", "memories": "Kenangan", "memories_all_caught_up": "Semua telah dilihat", @@ -1441,31 +1531,34 @@ "merge_people_prompt": "Ingin menggabungkan orang ini? Tindakan ini tidak dapat diurungkan.", "merge_people_successfully": "Orang berhasil digabungkan", "merged_people_count": "{count, plural, one {# orang} other {# orang}} digabung", - "minFaces": "Wajah minimum", - "minFaces_description": "Jumlah minimum wajah yang dikenali agar seseorang ditampilkan", "minimize": "Kecilkan", "minute": "Menit", "minutes": "Menit", + "mirror_horizontal": "Horisontal", + "mirror_vertical": "Vertikal", "missing": "Hilang", "mobile_app": "Aplikasi Seluler", "mobile_app_download_onboarding_note": "Unduh aplikasi seluler pendamping dengan menggunakan opsi berikut", "model": "Model", - "modify_date": "Tanggal Modifikasi", "month": "Bulan", + "monthly_title_text_date_format": "BBBB t", "more": "Lainnya", - "motion": "Gerak", "move": "Pindahkan", + "move_down": "Pindahkan ke bawah", "move_off_locked_folder": "Pindahkan dari folder terkunci", "move_to": "Pindahkan ke", "move_to_device_trash": "Pindahkan ke tempat sampah perangkat", "move_to_lock_folder_action_prompt": "{count} ditambahkan ke folder terkunci", "move_to_locked_folder": "Pindahkan ke folder terkunci", "move_to_locked_folder_confirmation": "Foto dan video ini akan dihapus dari semua album, dan hanya dapat dilihat dari folder terkunci", + "move_up": "Pindahkan ke atas", + "moved_to_archive": "{count, plural, one {# aset} other {# aset}} dipindahkan ke arsip", + "moved_to_library": "{count, plural, one {# aset} other {# aset}} dipindahkan ke pustaka", "moved_to_trash": "Dipindahkan ke sampah", + "multiselect_grid_edit_date_time_err_read_only": "Tidak dapat mengedit tanggal aset hanya-baca, dilewati", + "multiselect_grid_edit_gps_err_read_only": "Tidak dapat mengedit lokasi aset hanya-baca, dilewati", "mute_memories": "Nonaktifkan Kenangan", "my_albums": "Album saya", - "my_immich_description": "Salin halaman saat ini sebagai tautan My Immich", - "my_immich_title": "Tautan My Immich", "name": "Nama", "name_or_nickname": "Nama atau nama panggilan", "name_required": "Nama diperlukan", @@ -1480,11 +1573,12 @@ "never": "Tidak pernah", "new_album": "Album baru", "new_api_key": "Kunci API Baru", - "new_feature": "Fitur Baru", + "new_date_range": "Rentang tanggal baru", "new_password": "Sandi baru", "new_person": "Orang baru", "new_pin_code": "Kode PIN baru", "new_pin_code_subtitle": "Ini adalah akses pertama Anda ke folder terkunci. Buat kode PIN untuk mengamankan akses ke halaman ini", + "new_timeline": "Linimasa Baru", "new_update": "Pembaruan baru", "new_user_created": "Pengguna baru dibuat", "new_version_available": "VERSI BARU TERSEDIA", @@ -1492,6 +1586,7 @@ "next": "Berikutnya", "next_memory": "Kenangan berikutnya", "no": "Tidak", + "no_actions_added": "Belum ada aksi yang ditambahkan", "no_albums_found": "Tidak ada album yang ditemukan", "no_albums_message": "Buat album untuk mengelola foto dan video Anda", "no_albums_with_name_yet": "Sepertinya Anda belum memiliki album apa pun dengan nama ini.", @@ -1502,13 +1597,16 @@ "no_cast_devices_found": "Tidak ada perangkat cast yang ditemukan", "no_checksum_local": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset lokal", "no_checksum_remote": "Tidak ada checksum yang tersedia - tidak dapat mengambil aset jarak jauh", + "no_configuration_needed": "Tidak ada konfigurasi yang diperlukan", "no_devices": "Tidak ada perangkat terotorisasi", "no_duplicates_found": "Tidak ada duplikat yang ditemukan.", "no_exif_info_available": "Tidak ada info EXIF yang tersedia", "no_explore_results_message": "Unggah lebih banyak foto untuk menjelajahi koleksi Anda.", "no_favorites_message": "Tambahkan favorit untuk mencari foto dan video terbaik Anda dengan cepat", + "no_filters_added": "Belum ada filter yang ditambahkan", "no_libraries_message": "Buat pustaka eksternal untuk menampilkan foto dan video Anda", "no_local_assets_found": "Tidak ada aset lokal yang ditemukan dengan checksum ini", + "no_location_set": "Tidak ada lokasi yang ditetapkan", "no_locked_photos_message": "Foto dan video di folder terkunci disembunyikan dan tidak akan muncul saat Anda menelusuri atau mencari di pustaka.", "no_name": "Tidak Ada Nama", "no_notifications": "Tidak ada notifikasi", @@ -1518,7 +1616,7 @@ "no_results": "Tidak ada hasil", "no_results_description": "Coba sinonim atau kata kunci yang lebih umum", "no_shared_albums_message": "Buat sebuah album untuk membagikan foto dan video dengan orang lain dalam jaringan Anda", - "no_steps": "Belum ada langkah yang ditambahkan", + "no_uploads_in_progress": "Tidak ada unggahan yang sedang berlangsung", "none": "Tidak ada", "not_allowed": "Tidak diizinkan", "not_available": "T/T", @@ -1526,10 +1624,6 @@ "not_selected": "Belum dipilih", "notes": "Catatan", "nothing_here_yet": "Masih kosong", - "notification_backup_reliability": "Aktifkan notifikasi untuk meningkatkan keandalan pencadangan latar belakang", - "notification_enabled_list_tile_content": "Immich menggunakan notifikasi untuk pencadangan di latar belakang. Kelola notifikasi tersebut di pengaturan perangkat Anda.", - "notification_enabled_list_tile_open_button": "Buka pengaturan", - "notification_enabled_list_tile_title": "Notifikasi diaktifkan", "notification_permission_dialog_content": "Untuk mengaktifkan notifikasi, buka Pengaturan lalu berikan izin.", "notification_permission_list_tile_content": "Berikan izin untuk mengaktifkan notifikasi.", "notification_permission_list_tile_enable_button": "Aktifkan Notifikasi", @@ -1541,8 +1635,6 @@ "obtainium_configurator": "Konfigurator Obtainium", "obtainium_configurator_instructions": "Gunakan Obtainium untuk menginstal dan memperbarui aplikasi Android secara langsung dari rilis GitHub Immich. Buat kunci API dan pilih varian untuk membuat tautan konfigurasi Obtainium Anda", "ocr": "OCR", - "ocr_body": "Immich kini membaca teks dalam foto Anda sehingga Anda dapat mencarinya berdasarkan teks yang tertulis di dalamnya.", - "ocr_title": "Cari teks dalam foto Anda", "official_immich_resources": "Sumber Daya Immich Resmi", "offline": "Offline", "offset": "Ofset", @@ -1561,8 +1653,6 @@ "open": "Buka", "open_calendar": "Buka kalender", "open_in_browser": "buka di browser", - "open_in_immich_body": "Atur Immich sebagai galeri Anda di Android untuk membuka foto langsung dari aplikasi lain.", - "open_in_immich_title": "Buka foto di Immich", "open_in_map_view": "Buka dalam tampilan peta", "open_in_openstreetmap": "Buka di OpenStreetMap", "open_the_search_filters": "Buka filter pencarian", @@ -1571,10 +1661,10 @@ "organize_into_albums": "Kelola ke dalam album", "organize_into_albums_description": "Masukkan foto yang ada ke album sesuai pengaturan sinkronisasi", "organize_your_library": "Kelola pustaka Anda", - "orientation": "Orientasi", "original": "asli", "other": "Lainnya", "other_devices": "Perangkat lain", + "other_entities": "Entitas lain", "other_variables": "Variabel lain", "owned": "Dimiliki", "owner": "Pemilik", @@ -1584,9 +1674,12 @@ "partner_can_access_assets": "Semua foto dan video Anda kecuali yang ada di Arsip dan Terhapus", "partner_can_access_location": "Lokasi tempat foto diambil", "partner_list_user_photos": "Foto {user}", + "partner_list_view_all": "Lihat semua", "partner_page_empty_message": "Foto Anda tidak dibagikan dengan partner mana pun.", "partner_page_no_more_users": "Tidak ada pengguna lain yang bisa ditambahkan", + "partner_page_partner_add_failed": "Gagal menambahkan partner", "partner_page_select_partner": "Pilih partner", + "partner_page_shared_to_title": "Dibagikan dengan", "partner_page_stop_sharing_content": "{partner} tidak akan bisa mengakses foto Anda lagi.", "partner_sharing": "Berbagi dengan Partner", "partners": "Partner", @@ -1608,6 +1701,8 @@ "people": "Orang", "people_edits_count": "{count, plural, one {# orang} other {# orang}} telah diedit", "people_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan orang", + "people_selected": "{count, plural, one {# orang dipilih} other {# orang dipilih}}", + "people_sidebar_description": "Tampilkan tautan ke Orang di bilah samping", "permanent_deletion_warning": "Peringatan penghapusan permanen", "permanent_deletion_warning_setting_description": "Tampilkan peringatan ketika menghapus aset secara permanen", "permanently_delete": "Hapus secara permanen", @@ -1617,6 +1712,14 @@ "permanently_deleted_assets_count": "{count, plural, one {# aset} other {# aset}} dihapus secara permanen", "permission": "Perizinan", "permission_empty": "Bagian izin tidak boleh dibiarkan kosong", + "permission_onboarding_back": "Kembali", + "permission_onboarding_continue_anyway": "Lanjutkan saja", + "permission_onboarding_get_started": "Memulai", + "permission_onboarding_go_to_settings": "Buka pengaturan", + "permission_onboarding_permission_denied": "Izin ditolak. Untuk menggunakan Immich, berikan izin akses foto dan video di Pengaturan.", + "permission_onboarding_permission_granted": "Izin diberikan! Semua sudah siap.", + "permission_onboarding_permission_limited": "Izin dibatasi. Agar Immich dapat mencadangkan dan mengelola seluruh koleksi galeri, izinkan akses foto dan video pada Pengaturan.", + "permission_onboarding_request": "Immich memerlukan izin untuk melihat foto dan video Anda.", "person": "Orang", "person_age_months": "{months, plural, one {# bulan} other {# bulan}}", "person_age_year_months": "1 tahun {months, plural, one {# bulan} other {# bulan}}", @@ -1624,10 +1727,13 @@ "person_birthdate": "Lahir pada {date}", "person_hidden": "{name}{hidden, select, true { (disembunyikan)} other {}}", "person_recognized": "Orang yang dikenali", + "person_selected": "Orang yang dipilih", "photo_shared_all_users": "Sepertinya Anda telah membagikan foto Anda dengan semua pengguna atau tidak ada pengguna yang bisa menjadi tujuan berbagi.", "photos": "Foto", "photos_and_videos": "Foto & Video", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foto}}", "photos_from_previous_years": "Foto dari tahun lalu", + "photos_only": "Hanya foto", "pick_a_location": "Pilih lokasi", "pick_custom_range": "Rentang khusus", "pick_date_range": "Pilih rentang tanggal", @@ -1646,8 +1752,6 @@ "play_original_video_setting_description": "Lebih menyukai memutar video asli daripada video yang telah dikonversi. Jika aset asli tidak kompatibel, video mungkin tidak dapat diputar dengan benar.", "play_transcoded_video": "Putar video yang telah dikonversi", "please_auth_to_access": "Silakan autentikasi untuk mengakses", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Metode ini dapat memfilter acara dan secara kondisional mencegah langkah berikutnya berjalan", "port": "Port", "preferences_settings_subtitle": "Kelola preferensi aplikasi", "preferences_settings_title": "Preferensi", @@ -1664,11 +1768,11 @@ "privacy": "Privasi", "profile": "Profil", "profile_drawer_app_logs": "Log", + "profile_drawer_client_server_up_to_date": "Klien dan server menjalankan versi terbaru", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Mode hanya-baca aktif. Tekan lama ikon avatar pengguna untuk keluar.", "profile_image_of_user": "Foto profil dari {user}", "profile_picture_set": "Foto profil ditetapkan.", - "projection_type": "Tipe Proyeksi", "public_album": "Album publik", "public_share": "Berbagi Publik", "purchase_account_info": "Pendukung", @@ -1687,6 +1791,7 @@ "purchase_individual_description_2": "Status pendukung", "purchase_individual_title": "Perorangan", "purchase_input_suggestion": "Punya kunci produk? Masukkan kunci di bawah ini", + "purchase_license_subtitle": "Beli Immich untuk keberlangsungan pengembangan layanan", "purchase_lifetime_description": "Pembayaran seumur hidup", "purchase_option_title": "PILIHAN PEMBAYARAN", "purchase_panel_info_1": "Membangun Immich membutuhkan banyak waktu dan upaya, dan kami memiliki insinyur purnawaktu yang bekerja untuk membuatnya sebaik mungkin. Misi kami adalah agar perangkat lunak sumber terbuka dan praktik bisnis yang beretika menjadi sumber pendapatan yang berkelanjutan bagi para pengembang dan menciptakan ekosistem yang menghargai privasi dengan alternatif nyata untuk layanan cloud yang eksploitatif.", @@ -1719,19 +1824,19 @@ "reassigned_assets_to_new_person": "Menetapkan ulang {count, plural, one {# aset} other {# aset}} kepada orang baru", "reassing_hint": "Tetapkan aset yang dipilih ke orang yang sudah ada", "recent": "Terkini", + "recent_albums": "Album terkini", "recent_searches": "Pencarian terkini", "recently_added": "Baru saja ditambahkan", - "recently_added_body": "Langsung buka semua yang baru-baru ini Anda tambahkan pada halaman khusus.", - "recently_added_description": "Telusuri aset Anda yang diurutkan berdasarkan waktu pengunggahannya ke Immich", "recently_added_page_title": "Baru Ditambahkan", - "recently_added_title": "Baru ditambahkan", "recently_taken": "Baru diambil", + "recently_taken_page_title": "Baru Diambil", "refresh": "Segarkan", "refresh_encoded_videos": "Segarkan video terenkode", "refresh_faces": "Segarkan wajah", "refresh_metadata": "Segarkan metadata", "refresh_thumbnails": "Segarkan gambar kecil", "refreshed": "Disegarkan", + "refreshes_every_file": "Membaca ulang semua file yang sudah ada dan yang baru", "refreshing_encoded_video": "Menyegarkan video terenkode", "refreshing_faces": "Menyegarkan wajah", "refreshing_metadata": "Menyegarkan metadata", @@ -1744,7 +1849,7 @@ "remove_assets_shared_link_confirmation": "Yakin ingin menghapus {count, plural, one {# aset} other {# aset}} dari tautan bersama ini?", "remove_assets_title": "Hapus aset?", "remove_custom_date_range": "Hapus rentang tanggal khusus", - "remove_filter": "Hapus filter", + "remove_deleted_assets": "Bersihkan Aset yang Dihapus", "remove_from_album": "Hapus dari album", "remove_from_album_action_prompt": "{count} telah dihapus dari album", "remove_from_favorites": "Hapus dari favorit", @@ -1755,22 +1860,29 @@ "remove_memory": "Hapus kenangan", "remove_photo_from_memory": "Hapus foto dari kenangan ini", "remove_tag": "Hapus tag", + "remove_url": "Hapus URL", "remove_user": "Hapus pengguna", "removed_api_key": "Kunci API Dihapus: {name}", "removed_from_archive": "Dihapus dari arsip", "removed_from_favorites": "Dihapus dari favorit", "removed_from_favorites_count": "{count, plural, other {# dihapus}} dari favorit", "removed_memory": "Kenangan dihapus", + "removed_photo_from_memory": "Foto dihapus dari kenangan", "removed_tagged_assets": "Hapus tag dari {count, plural, one {# aset} other {# aset}}", "rename": "Ubah nama", + "repair": "Perbaiki", + "repair_no_results_message": "File yang tidak dilacak dan hilang akan muncul di sini", + "replace_with_upload": "Ganti dengan unggahan", "repository": "Repositori", "require_password": "Memerlukan sandi", + "require_user_to_change_password_on_first_login": "Wajibkan pengguna untuk mengubah sandi pada log masuk pertama", "rescan": "Pindai ulang", "reset": "Atur ulang", "reset_password": "Atur ulang sandi", "reset_people_visibility": "Atur ulang keterlihatan orang", "reset_pin_code": "Atur ulang kode PIN", "reset_pin_code_description": "Jika Anda lupa kode PIN, Anda bisa menghubungi admin untuk mengatur ulang", + "reset_pin_code_success": "Berhasil mengatur ulang kode PIN", "reset_pin_code_with_password": "Anda selalu dapat mengatur ulang kode PIN dengan sandi Anda", "reset_sqlite": "Atur ulang basis data SQLite", "reset_sqlite_clear_app_data": "Hapus Data", @@ -1784,10 +1896,12 @@ "resolved_all_duplicates": "Semua duplikat terselesaikan", "restore": "Pulihkan", "restore_all": "Pulihkan semua", + "restore_trash_action_prompt": "{count} item telah dipulihkan dari tempat sampah", "restore_user": "Pulihkan pengguna", "restored_asset": "Aset dipulihkan", "resume": "Lanjutkan", "resume_paused_jobs": "Lanjutkan {count, plural, one {# pekerjaan yang dijeda} other {# pekerjaan yang dijeda}}", + "retry_upload": "Ulangi pengunggahan", "review_duplicates": "Tinjau duplikat", "review_large_files": "Tinjau file berukuran besar", "role": "Peran", @@ -1795,6 +1909,7 @@ "role_viewer": "Penampil", "running": "Berjalan", "save": "Simpan", + "save_to_gallery": "Simpan ke galeri", "saved": "Disimpan", "saved_api_key": "Kunci API Tersimpan", "saved_profile": "Profil disimpan", @@ -1805,10 +1920,9 @@ "scan": "Pindai", "scan_all_libraries": "Pindai Semua Pustaka", "scan_library": "Pindai", + "scan_settings": "Pengaturan Pemindaian", "scanning": "Memindai", "scanning_for_album": "Memindai album...", - "screencast_mode_description": "Tampilkan indikator peristiwa keyboard dan mouse di layar", - "screencast_mode_title": "Aktifkan/nonaktifkan mode siaran layar", "search": "Cari", "search_albums": "Cari album", "search_by_context": "Cari berdasarkan konteks", @@ -1816,8 +1930,6 @@ "search_by_description_example": "Mendaki Bromo", "search_by_filename": "Cari berdasarkan nama file atau ekstensi", "search_by_filename_example": "mis. IMG_1234.JPG atau PNG", - "search_by_full_path": "Cari berdasarkan jalur lengkap atau folder", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - Anda dapat mencari Projects, 3D, Printing, 2026, dll.", "search_by_ocr": "Cari dengan OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Cari model lensa...", @@ -1837,6 +1949,7 @@ "search_filter_location_title": "Pilih Lokasi", "search_filter_media_type": "Tipe Media", "search_filter_media_type_title": "Pilih jenis media", + "search_filter_ocr": "Cari dengan OCR", "search_filter_people_title": "Pilih orang", "search_filter_star_rating": "Peringkat Bintang", "search_filter_tags_title": "Pilih tag", @@ -1847,13 +1960,25 @@ "search_no_people_named": "Tidak ada orang bernama \"{name}\"", "search_no_result": "Tidak ada hasil yang ditemukan, coba kata kunci atau kombinasi lain", "search_options": "Opsi pencarian", + "search_page_categories": "Kategori", + "search_page_motion_photos": "Foto Bergerak", + "search_page_no_objects": "Tidak Ada Info Objek", + "search_page_no_places": "Tidak Ada Info Lokasi", + "search_page_screenshots": "Tangkapan Layar", "search_page_search_photos_videos": "Cari foto dan video Anda", + "search_page_selfies": "Selfi", + "search_page_things": "Objek", "search_page_view_all_button": "Lihat semua", + "search_page_your_activity": "Aktivitas Anda", + "search_page_your_map": "Peta Anda", "search_people": "Cari orang", "search_places": "Cari tempat", "search_rating": "Cari berdasarkan penilaian...", + "search_result_page_new_search_hint": "Pencarian Baru", "search_settings": "Pengaturan pencarian", "search_state": "Cari negara bagian...", + "search_suggestion_list_smart_search_hint_1": "Penelusuran cerdas diaktifkan secara bawaan. Untuk menelusuri metadata, gunakan sintaks ", + "search_suggestion_list_smart_search_hint_2": "m:kata-yang-ingin-dicari", "search_tags": "Cari tag...", "search_timezone": "Cari zona waktu...", "search_type": "Jenis pencarian", @@ -1867,6 +1992,7 @@ "select_albums": "Pilih album", "select_all": "Pilih semua", "select_all_duplicates": "Pilih semua duplikat", + "select_all_in": "Pilih semua di {group}", "select_avatar_color": "Pilih warna avatar", "select_count": "{count, plural, one {Pilih #} other {Pilih #}}", "select_cutoff_date": "Pilih tanggal batas", @@ -1874,13 +2000,14 @@ "select_featured_photo": "Pilih foto unggulan", "select_from_computer": "Pilih dari komputer", "select_keep_all": "Pilih simpan semua", + "select_library_owner": "Pilih pemilik pustaka", "select_new_face": "Pilih wajah baru", "select_people": "Pilih orang", "select_person": "Pilih orang", "select_person_to_tag": "Pilih orang untuk diberi tag", "select_photos": "Pilih foto", - "select_quality": "Pilih kualitas", "select_trash_all": "Pilih buang semua", + "select_user_for_sharing_page_err_album": "Gagal membuat album", "selected": "Dipilih", "selected_count": "{count, plural, other {# dipilih}}", "selected_gps_coordinates": "Koordinat GPS yang dipilih", @@ -1913,31 +2040,42 @@ "setting_image_viewer_original_title": "Muat gambar kualitas asli", "setting_image_viewer_preview_subtitle": "Aktifkan untuk memuat gambar dengan resolusi sedang. Nonaktifkan jika ingin langsung memuat gambar asli atau hanya ingin memuat gambar kecil.", "setting_image_viewer_preview_title": "Muat gambar pratinjau", + "setting_image_viewer_title": "Gambar", "setting_languages_apply": "Terapkan", "setting_languages_subtitle": "Mengubah bahasa pada aplikasi", + "setting_notifications_notify_failures_grace_period": "Beritahu kegagalan cadangan latar belakang: {duration}", + "setting_notifications_notify_hours": "{count} jam", + "setting_notifications_notify_immediately": "segera", "setting_notifications_notify_minutes": "{count} menit", + "setting_notifications_notify_never": "jangan pernah", "setting_notifications_notify_seconds": "{count} detik", + "setting_notifications_single_progress_subtitle": "Detail info proses unggah tiap asset", + "setting_notifications_single_progress_title": "Tampilkan detail proses pencadangan latar belakang", "setting_notifications_subtitle": "Atur pengaturan notifikasi", + "setting_notifications_total_progress_subtitle": "Progres keseluruhan unggahan (selesai/total aset)", + "setting_notifications_total_progress_title": "Tampilkan progres total pencadangan latar belakang", "setting_video_viewer_auto_play_subtitle": "Otomatis memutar video saat dibuka", "setting_video_viewer_auto_play_title": "Putar video secara otomatis", "setting_video_viewer_looping_title": "Ulangi", "setting_video_viewer_original_video_subtitle": "Ketika melakukan streaming video dari server, sistem akan memutar versi asli meskipun tersedia hasil transkode. Pengaturan ini dapat menyebabkan terjadinya buffering. Video yang tersedia secara lokal akan selalu diputar dalam kualitas asli tanpa terpengaruh oleh pengaturan ini.", "setting_video_viewer_original_video_title": "Paksa video asli", "settings": "Pengaturan", + "settings_require_restart": "Mulai ulang Immich untuk menerapkan pengaturan ini", "settings_saved": "Pengaturan disimpan", "setup_pin_code": "Siapkan kode PIN", "share": "Bagikan", + "share_action_prompt": "{count} aset bersama", + "share_add_photos": "Tambah foto", + "share_assets_selected": "{count} dipilih", "share_dialog_preparing": "Menyiapkan...", "share_link": "Bagikan Tautan", - "share_original": "Gunakan versi asli (besar)", - "share_preview": "Gunakan thumbnail (kecil)", - "share_quality_body": "Tekan dan tahan tombol bagikan untuk memilih kualitas gambar sebelum membagikannya.", - "share_quality_title": "Pilih kualitas untuk dibagikan", "shared": "Dibagikan", "shared_album_activities_input_disable": "Komentar dinonaktifkan", "shared_album_activity_remove_content": "Ingin menghapus aktivitas ini?", "shared_album_activity_remove_title": "Hapus Aktivitas", "shared_album_section_people_action_error": "Gagal menghapus dari album", + "shared_album_section_people_action_leave": "Hapus pengguna dari album", + "shared_album_section_people_action_remove_user": "Hapus pengguna dari album", "shared_album_section_people_title": "ORANG", "shared_by": "Dibagikan oleh", "shared_by_user": "Dibagikan oleh {user}", @@ -1946,6 +2084,7 @@ "shared_intent_upload_button_progress_text": "{current}/{total} Diunggah", "shared_link_app_bar_title": "Tautan Bersama", "shared_link_clipboard_copied_massage": "Disalin ke papan klip", + "shared_link_clipboard_text": "Tautan: {link}\nSandi: {password}", "shared_link_create_error": "Terjadi kesalahan saat membuat tautan bersama", "shared_link_custom_url_description": "Akses tautan bersama ini dengan URL khusus", "shared_link_edit_description_hint": "Masukkan deskripsi berbagi", @@ -1981,6 +2120,12 @@ "shared_with_partner": "Dibagikan dengan {partner}", "sharing": "Berbagi", "sharing_enter_password": "Masukkan sandi untuk membuka tautan halaman ini.", + "sharing_page_album": "Album bersama", + "sharing_page_description": "Buat album bersama untuk berbagi foto dan video dengan pengguna di dalam jaringan.", + "sharing_page_empty_list": "DAFTAR KOSONG", + "sharing_sidebar_description": "Tampilkan tautan ke Berbagi di bilah samping", + "sharing_silver_appbar_create_shared_album": "Buat album bersama", + "sharing_silver_appbar_share_partner": "Berbagi dengan partner", "shift_to_permanent_delete": "tekan ⇧ untuk menghapus aset secara permanen", "show_album_options": "Tampilkan opsi album", "show_albums": "Tampilkan album", @@ -1992,9 +2137,7 @@ "show_in_timeline": "Tampilkan dalam lini masa", "show_in_timeline_setting_description": "Tampilkan foto dan video dari pengguna ini di lini masa Anda", "show_keyboard_shortcuts": "Tampilkan pintasan papan ketik", - "show_less": "Tampilkan lebih sedikit", "show_metadata": "Tampilkan metadata", - "show_more_fields": "{count, plural, one {Tampilkan # kolom lagi} other {Tampilkan # kolom lagi}}", "show_or_hide_info": "Tampilkan atau sembunyikan info", "show_password": "Tampilkan sandi", "show_person_options": "Tampilkan opsi orang", @@ -2002,7 +2145,6 @@ "show_schema": "Tampilkan skema", "show_search_options": "Tampilkan opsi pencarian", "show_shared_links": "Tampilkan tautan bersama", - "show_slideshow_metadata_overlay": "Tampilkan overlay info gambar", "show_slideshow_transition": "Tampilkan transisi salindia", "show_supporter_badge": "Lencana pendukung", "show_supporter_badge_description": "Tampilkan lencana pendukung", @@ -2014,25 +2156,18 @@ "sign_out": "Keluar", "sign_up": "Daftar", "size": "Ukuran", - "skip": "Lewati", "skip_to_content": "Lewati ke konten", "skip_to_folders": "Lewati ke folder", "skip_to_tags": "Lewati ke tag", "slideshow": "Salindia", - "slideshow_body": "Bersantai dan saksikan foto Anda diputar dalam slideshow layar penuh.", - "slideshow_metadata_overlay_mode": "Konten overlay", - "slideshow_metadata_overlay_mode_description_only": "Deskripsi saja", - "slideshow_metadata_overlay_mode_full": "Penuh", "slideshow_repeat": "Ulangi salindia", "slideshow_repeat_description": "Ulangi dari awal saat salindia berakhir", "slideshow_settings": "Pengaturan salindia", - "slideshow_title": "Slideshow", - "smart_album": "Album cerdas", - "some_assets_already_have_a_location_warning": "Sebagian aset yang dipilih sudah memiliki lokasi", "sort_albums_by": "Urutkan album berdasarkan...", "sort_created": "Tanggal dibuat", "sort_items": "Jumlah item", "sort_modified": "Tanggal diubah", + "sort_newest": "Foto terbaru", "sort_oldest": "Foto terlawas", "sort_people_by_similarity": "Urutkan orang berdasarkan kemiripan", "sort_recent": "Foto paling terkini", @@ -2041,6 +2176,7 @@ "stack": "Tumpukan", "stack_action_prompt": "{count} tumpukan", "stack_duplicates": "Tumpukan duplikat", + "stack_select_one_photo": "Pilih satu foto utama untuk tumpukan", "stack_selected_photos": "Tumpuk foto terpilih", "stacked_assets_count": "{count, plural, one {# aset} other {# aset}} ditumpuk", "stacktrace": "Jejak tumpukan", @@ -2049,10 +2185,6 @@ "start_date_before_end_date": "Tanggal mulai harus sebelum tanggal akhir", "state": "Keadaan", "status": "Status", - "step_delete": "Hapus langkah", - "step_delete_confirm": "Apakah Anda yakin ingin menghapus langkah ini?", - "step_details": "Detail langkah", - "steps": "Langkah-langkah", "stop_casting": "Hentikan cast", "stop_motion_photo": "Hentikan Foto Gerak", "stop_photo_sharing": "Berhenti membagikan foto Anda?", @@ -2073,18 +2205,17 @@ "swap_merge_direction": "Ganti arah penggabungan", "sync": "Sinkronkan", "sync_albums": "Sinkronkan album", + "sync_albums_manual_subtitle": "Sinkronkan semua video dan foto yang telah diunggah ke album cadangan yang dipilih", "sync_local": "Sinkronkan Lokal", "sync_remote": "Sinkronkan Jarak Jauh", "sync_status": "Status Sinkronisasi", "sync_status_subtitle": "Lihat dan kelola sistem sinkronisasi", "sync_upload_album_setting_subtitle": "Buat dan unggah foto serta video Anda ke album yang telah dipilih pada Immich", - "system_theme": "Tema sistem", - "system_theme_command_description": "Gunakan tema sistem ({value})", "tag": "Tag", "tag_assets": "Tag aset", "tag_created": "Tag yang dibuat: {tag}", - "tag_face": "Tandai wajah", "tag_feature_description": "Menjelajahi foto dan video yang dikelompokkan berdasarkan topik tag yang logis", + "tag_not_found_question": "Tidak dapat menemukan tag? Buat tag baru.", "tag_people": "Beri Tag Orang", "tag_updated": "Tag yang diperbarui: {tag}", "tagged_assets": "{count, plural, one {# aset} other {# aset}} diberi tag", @@ -2099,10 +2230,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Jumlah aset per baris ({count})", "theme_setting_colorful_interface_subtitle": "Menerapkan warna utama ke permukaan latar belakang.", "theme_setting_colorful_interface_title": "Antarmuka berwarna-warni", + "theme_setting_image_viewer_quality_subtitle": "Sesuaikan kualitas dari penampil gambar", + "theme_setting_image_viewer_quality_title": "Kualitas penampil gambar", "theme_setting_primary_color_subtitle": "Pilih warna yang akan digunakan untuk tindakan utama dan elemen aksen.", "theme_setting_primary_color_title": "Warna utama", "theme_setting_system_primary_color_title": "Gunakan warna sistem", "theme_setting_system_theme_switch": "Otomatis (Ikuti pengaturan sistem)", + "theme_setting_theme_subtitle": "Pilih pengaturan tema aplikasi", + "theme_setting_three_stage_loading_subtitle": "Pemuatan tiga tahap dapat meningkatkan kinerja pemuatan, namun akan menyebabkan beban jaringan meningkat secara signifikan", + "theme_setting_three_stage_loading_title": "Aktifkan pemuatan tiga tahap", "then": "Lalu", "they_will_be_merged_together": "Mereka akan digabungkan bersama", "third_party_resources": "Sumber Daya Pihak Ketiga", @@ -2128,17 +2264,23 @@ "trash_all": "Buang Semua", "trash_count": "Sampah {count, number}", "trash_delete_asset": "Hapus Aset", + "trash_emptied": "Tempat sampah telah dikosongkan", "trash_no_results_message": "Foto dan video di sampah akan muncul di sini.", "trash_page_delete_all": "Hapus Semua", + "trash_page_empty_trash_dialog_content": "Ingin menghapus semua aset di sampah? Item tersebut akan dihapus secara permanen dari Immich", "trash_page_info": "Item yang dipindahkan ke sampah akan terhapus secara permanen setelah {days} hari", + "trash_page_no_assets": "Tidak ada aset di sampah", + "trash_page_restore_all": "Pulihkan Semua", + "trash_page_select_assets_btn": "Pilih aset", + "trash_page_title": "Sampah ({count})", "trashed_items_will_be_permanently_deleted_after": "Item yang dibuang akan dihapus secara permanen setelah {days, plural, one {# hari} other {# hari}}.", "trigger": "Pemicu", - "trigger_asset_metadata_extraction": "Ekstraksi Metadata Aset", - "trigger_asset_metadata_extraction_description": "Dipicu saat metadata EXIF dari sebuah aset diekstrak", - "trigger_asset_uploaded": "Unggah Aset", + "trigger_asset_uploaded": "Aset Diunggah", "trigger_asset_uploaded_description": "Terpicu saat aset baru telah diunggah", + "trigger_description": "Sebuah peristiwa yang memicu alur kerja", "trigger_person_recognized": "Orang Dikenali", - "trigger_person_recognized_description": "Dipicu saat seseorang dikenali", + "trigger_person_recognized_description": "Terpicu saat seseorang terdeteksi", + "trigger_type": "Tipe pemicu", "troubleshoot": "Pemecahan Masalah", "type": "Jenis", "unable_to_change_pin_code": "Tidak dapat mengubah kode PIN", @@ -2154,7 +2296,6 @@ "unknown": "Tidak diketahui", "unknown_country": "Negara Tidak Diketahui", "unknown_date": "Tanggal tidak diketahui", - "unknown_schema": "Skema tidak dikenal", "unknown_year": "Tahun Tidak Diketahui", "unlimited": "Tidak terbatas", "unlink_motion_video": "Hapus tautan video gerak", @@ -2167,20 +2308,23 @@ "unsaved_change": "Perubahan belum disimpan", "unselect_all": "Batalkan semua pilihan", "unselect_all_duplicates": "Batalkan pilihan semua duplikat", + "unselect_all_in": "Batalkan semua pilihan dalam {group}", "unstack": "Batalkan penumpukan", "unstack_action_prompt": "{count} tidak dalam tumpukan", "unstacked_assets_count": "Penumpukan {count, plural, one {# aset} other {# aset}} dibatalkan", "unsupported_field_type": "Tipe bidang tidak didukung", "unsupported_file_type": "File {file} tidak dapat diunggah karena jenis file {type} tidak didukung.", "untagged": "Tag Dihapus", + "untitled_workflow": "Alur kerja tak berjudul", "up_next": "Berikutnya", "update_location_action_prompt": "Perbarui lokasi {count} aset yang dipilih dengan:", "updated_at": "Diperbarui", "updated_password": "Sandi diperbarui", "upload": "Unggah", "upload_concurrency": "Konkurensi pengunggahan", - "upload_day_count": "{date}: {count, plural, one {# unggahan} other {# unggahan}}", "upload_details": "Detail Unggahan", + "upload_dialog_info": "Ingin mencadangkan aset terpilih ke server?", + "upload_dialog_title": "Unggah Aset", "upload_error_with_count": "Kesalahan unggah untuk {count, plural, one {# aset} other {# aset}}", "upload_errors": "Unggahan selesai dengan {count, plural, one {# kesalahan} other {# kesalahan}}, muat ulang halaman untuk melihat aset terunggah yang baru.", "upload_finished": "Unggahan selesai", @@ -2190,13 +2334,9 @@ "upload_status_errors": "Kesalahan", "upload_status_uploaded": "Diunggah", "upload_success": "Pengunggahan berhasil, muat ulang halaman untuk melihat aset terunggah yang baru.", - "upload_to_album_body": "Bagi pengguna yang tidak menggunakan fitur unggah manual, kini Anda dapat memilih untuk menambahkan foto lokal langsung ke album saat mengunggahnya. Tidak perlu lagi mengunggah terlebih dahulu, lalu menambahkannya ke album.", - "upload_to_album_title": "Unggah langsung ke album", "upload_to_immich": "Unggah ke Immich ({count})", "uploading": "Mengunggah", "uploading_media": "Mengunggah media", - "uploads": "Unggahan", - "uploads_count": "{count, plural, one {# unggahan} other {# unggahan}}", "url": "URL", "usage": "Penggunaan", "use_biometric": "Gunakan biometrik", @@ -2204,7 +2344,6 @@ "use_browser_locale_description": "Format tanggal, waktu, dan angka berdasarkan pelokalan browser Anda", "use_current_connection": "Gunakan koneksi saat ini", "use_custom_date_range": "Gunakan rentang tanggal khusus saja", - "use_template": "Gunakan templat", "user": "Pengguna", "user_has_been_deleted": "Pengguna ini telah dihapus.", "user_id": "ID Pengguna", @@ -2214,6 +2353,7 @@ "user_privacy": "Privasi Pengguna", "user_purchase_settings": "Pembelian", "user_purchase_settings_description": "Kelola pembelian Anda", + "user_role_set": "Tetapkan {user} sebagai {role}", "user_usage_detail": "Detail penggunaan pengguna", "user_usage_stats": "Statistik penggunaan akun", "user_usage_stats_description": "Tampilkan statistik penggunaan akun", @@ -2223,6 +2363,7 @@ "utilities": "Peralatan", "validate": "Validasi", "validate_endpoint_error": "Masukkan URL yang valid", + "validation_error": "Kesalahan validasi", "variables": "Variabel", "version": "Versi", "version_announcement_closing": "Temanmu, Alex", @@ -2232,9 +2373,9 @@ "video": "Video", "video_hover_setting": "Putar gambar kecil video saat kursor di atas", "video_hover_setting_description": "Putar gambar kecil video ketika kursor berada di atas item. Bahkan saat dinonaktifkan, pemutaran dapat dimulai dengan mengarahkan kursor di atas ikon putar.", - "video_quality": "Kualitas video", "videos": "Video", "videos_count": "{count, plural, one {# Video} other {# Video}}", + "videos_only": "Hanya video", "view": "Tampilkan", "view_album": "Tampilkan Album", "view_all": "Tampilkan Semua", @@ -2243,29 +2384,29 @@ "view_details": "Tampilkan Detail", "view_in_timeline": "Lihat di timeline", "view_link": "Tampilkan tautan", + "view_links": "Tampilkan tautan", "view_name": "Tampilkan", "view_next_asset": "Tampilkan aset berikutnya", "view_previous_asset": "Tampilkan aset sebelumnya", "view_qr_code": "Tampilkan kode QR", "view_similar_photos": "Lihat foto yang mirip", "view_stack": "Tampilkan Tumpukan", + "view_user": "Lihat Pengguna", "viewer_remove_from_stack": "Keluarkan dari Tumpukan", + "viewer_stack_use_as_main_asset": "Gunakan sebagai aset utama", + "viewer_unstack": "Lepas tumpukan", "visibility": "Visibilitas", "visibility_changed": "Keterlihatan diubah untuk {count, plural, one {# orang} other {# orang}}", "visual": "Visual", + "visual_builder": "Pembangun visual", "waiting": "Menunggu", "waiting_count": "Menunggu: {count}", "warning": "Peringatan", "week": "Pekan", "welcome": "Selamat datang", "welcome_to_immich": "Selamat datang di Immich", - "whats_new": "Yang baru", - "whats_new_settings_subtitle": "Lihat yang baru di Immich", - "whats_new_version": "Versi {version}", - "when": "Saat", "width": "Lebar", "wifi_name": "Nama Wi-Fi", - "workflow": "Alur Kerja", "workflow_delete_prompt": "Yakin ingin menghapus alur kerja ini?", "workflow_deleted": "Alur kerja telah dihapus", "workflow_description": "Deskripsi alur kerja", @@ -2275,17 +2416,17 @@ "workflow_name": "Nama alur kerja", "workflow_navigation_prompt": "Yakin ingin keluar tanpa menyimpan perubahan Anda?", "workflow_summary": "Ringkasan alur kerja", - "workflow_templates": "Templat alur kerja", "workflow_update_success": "Alur kerja berhasil diubah", + "workflow_updated": "Alur kerja diubah", "workflows": "Alur kerja", "workflows_help_text": "Alur kerja untuk otomasi kegiatan pada aset Anda sesuai dengan pemicu dan filter", "wrong_pin_code": "Kode PIN salah", - "x_of_total": "{x}/{total}", "year": "Tahun", "years_ago": "{years, plural, one {# tahun} other {# tahun}} lalu", "yes": "Ya", "you_dont_have_any_shared_links": "Anda tidak memiliki tautan bersama", "your_wifi_name": "Nama Wi-Fi Anda", "zero_to_clear_rating": "tekan 0 untuk menghapus penilaian pada aset", - "zoom_image": "Perbesar Gambar" + "zoom_image": "Perbesar Gambar", + "zoom_to_bounds": "Perbesar ke batas" } diff --git a/i18n/is.json b/i18n/is.json index 2fb6c95d26..7d15b0fd69 100644 --- a/i18n/is.json +++ b/i18n/is.json @@ -9,6 +9,7 @@ "active": "Virkt", "active_count": "Virk: {count}", "activity": "Virkni", + "activity_changed": "Virkni er {enabled, select, true {enabled} other {disabled}}", "add": "BÃĻta við", "add_a_description": "BÃĻta við lÃŊsingu", "add_a_location": "Setja inn staðsetningu", @@ -18,7 +19,9 @@ "add_endpoint": "BÃĻta við endapunkti", "add_exclusion_pattern": "BÃĻta við Ãētilokunarmynstri", "add_location": "BÃĻta við staðsetningu", + "add_more_users": "BÃĻta við fleiri notendum", "add_partner": "BÃĻta við maka", + "add_path": "BÃĻta við slÃŗÃ°", "add_photos": "BÃĻta við myndum", "add_tag": "BÃĻta við merki", "add_to": "BÃĻta íâ€Ļ", @@ -26,9 +29,11 @@ "add_to_album_bottom_sheet_added": "BÃĻtt við {album}", "add_to_album_bottom_sheet_already_exists": "Þegar í {album}", "add_to_album_bottom_sheet_some_local_assets": "Ekki gekk að bÃĻta sumum staðvÃĻrum eignum í myndasafn", + "add_to_album_toggle": "Breyta vali fyrir {album}", "add_to_albums": "BÃĻta í albÃēm", "add_to_albums_count": "BÃĻtt í albÃēm ({count})", "add_to_bottom_bar": "BÃĻta í", + "add_to_shared_album": "BÃĻta í deilt albÃēm", "add_upload_to_stack": "BÃĻta upphleðslu í hrÃēgu", "add_url": "Setja inn URL", "added_to_archive": "BÃĻtt í geymslu", @@ -426,6 +431,8 @@ "admin_password": "Lykilorð stjÃŗrnanda", "administration": "StjÃŗrnun", "advanced": "Fyrir lengra komna", + "advanced_settings_enable_alternate_media_filter_subtitle": "Notið Þessa stillingu til að sía efni Þegar Því er hlaðið inn byggt ÃĄ Ãļðrum eiginleikum. Reynið Þetta bara ef Immich ÃĄ í erfiðleikum með að greina Ãļll myndasÃļfn.", + "advanced_settings_enable_alternate_media_filter_title": "[Á TILRAUNASTIGI] Nota aðrar síur fyrir samstillingu myndasafna ÃĄ tÃĻki", "advanced_settings_log_level_title": "Stig atburða: {level}", "advanced_settings_prefer_remote_subtitle": "Sum tÃĻki eru agalega lengi að hlaða gaummyndum Ãēr staðvÃĻrum eignum. Virkið Þetta til að hlaða inn gaummyndum Ãēr fjarska í staðinn.", "advanced_settings_prefer_remote_title": "KjÃŗsa myndir Ãēr fjarska", @@ -433,6 +440,8 @@ "advanced_settings_proxy_headers_title": "SÊrsniðnir vefselshausar [Á TILRAUNASTIGI]", "advanced_settings_readonly_mode_subtitle": "Virkja skoðunarham Þar sem aðeins mÃĄ skoða myndir, aðgerðir ÃĄ borð við að velja margar myndir, deila, streyma og eyða eru allar bannaðar. Af-/virkið skoðunarham í gegnum notandateiknið ÃĄ aðalsíðunni", "advanced_settings_readonly_mode_title": "Skoðunarhamur", + "advanced_settings_self_signed_ssl_subtitle": "Sleppir Því að sannreyna TLS skírteini frÃĄ vefÞjÃŗninum. Nauðsyn ef notast er við sjÃĄlfskrÃĄÃ° skírteini.", + "advanced_settings_self_signed_ssl_title": "Leyfa sjÃĄlfskrÃĄÃ° TLS skírteini [Á TILRAUNASTIGI]", "advanced_settings_sync_remote_deletions_subtitle": "Eyða eða endurheimta eign sjÃĄlfkrafa ÃĄ Þessu tÃĻki Þegar Þetta er framkvÃĻmt ÃĄ vefnum", "advanced_settings_sync_remote_deletions_title": "SamrÃĻma eyðingu Ãēr fjarska [Á TILRAUNASTIGI]", "advanced_settings_tile_subtitle": "Notandastillingar fyrir lengra komna", @@ -448,7 +457,11 @@ "album_delete_confirmation": "Ertu viss um að ÞÃē viljir eyða myndasafni {album}?", "album_delete_confirmation_description": "Ef Þessu myndasafni er deilt með Ãļðrum missa Þeir notendur aðgang að Því.", "album_deleted": "Myndasafni eytt", + "album_info_card_backup_album_excluded": "SLEPPT", + "album_info_card_backup_album_included": "MEÐ TALIÐ", "album_info_updated": "UpplÃŊsingar um myndasafn uppfÃĻrðar", + "album_leave": "Fara Ãēr myndasafni?", + "album_leave_confirmation": "Ertu viss um að ÞÃē viljir segja Þig Ãēr {album}?", "album_name": "Nafn Myndasafns", "album_options": "ValmÃļguleikar myndasafns", "album_remove_user": "FjarlÃĻgja notanda?", @@ -458,7 +471,15 @@ "album_summary": "Samantekt myndasafns", "album_updated": "Myndasafn uppfÃĻrt", "album_updated_setting_description": "FÃĄ meldingu í tÃļlvupÃŗsti Þegar eignum er bÃĻtt í deilt myndasafn", + "album_user_left": "FÃŗr Ãēr {album}", + "album_user_removed": "FjarlÃĻgði {user}", + "album_viewer_appbar_delete_confirm": "Ertu viss um að ÞÃē viljir eyða Þessu myndasafni Ãēr aðgangnum Þínum?", "album_viewer_appbar_share_err_delete": "Ekki gekk að eyða myndasafni", + "album_viewer_appbar_share_err_leave": "Ekki gekk að fara Ãēr myndasafni", + "album_viewer_appbar_share_err_remove": "Það eru vandamÃĄl við að fjarlÃĻgja eignir Ãēr Þessu myndasafni", + "album_viewer_appbar_share_err_title": "Ekki gekk að breyta titli ÃĄ myndasafni", + "album_viewer_appbar_share_leave": "Fara Ãēr myndasafni", + "album_viewer_appbar_share_to": "Deila til", "album_viewer_page_share_add_users": "BÃĻta við notendum", "album_with_link_access": "Leyfa hverjum sem hefur hlekkinn að sjÃĄ myndir og fÃŗlk í myndasafninu.", "albums": "MyndasÃļfn", @@ -472,16 +493,19 @@ "all_people": "Allt fÃŗlk", "all_videos": "Öll myndbÃļnd", "allow_dark_mode": "Leyfa skuggaham", + "allow_edits": "Leyfa breytingar", "allow_public_user_to_download": "Leyfa almennum notendum að hala niður", "allow_public_user_to_upload": "Leyfa almennum notendum að hlaða upp", "allowed": "Leyft", "alt_text_qr_code": "Mynd af snarkÃŗÃ°a", + "anti_clockwise": "Öfugur sÃŗlarhringur", "api_key": "API lykill", "api_key_description": "Þetta gildi er aðeins sÃŊnt einu sinni. Afritaðu Það ÃĄÃ°ur en glugganum er lokað.", "api_key_empty": "Heiti API lyklisins ÃĻtti ekki að vera tÃŗmt", "api_keys": "API lyklar", "app_architecture_variant": "Margbreytni (HÃļgun)", "app_bar_signout_dialog_content": "Ertu viss um að ÞÃē viljir skrÃĄ Þig Ãēt?", + "app_bar_signout_dialog_ok": "JÃĄ", "app_bar_signout_dialog_title": "SkrÃĄ Ãēt", "app_download_links": "Hlekkir til að hala niður forriti", "app_settings": "Stillingar forrits", @@ -492,17 +516,25 @@ "archive": "Geymsla", "archive_action_prompt": "{count} bÃĻtt í Geymslu", "archive_or_unarchive_photo": "Setja í eða taka Ãēr geymslu", + "archive_page_no_archived_assets": "Engar eignir í geymslu fundust", + "archive_page_title": "Geymsla ({count})", "archive_size": "StÃĻrð geymslu", "archive_size_description": "Stillið stÃĻrð geymslu fyrir niðurhÃļl (í GiB)", "archived": "Geymt", "archived_count": "{count, plural, other {Geymt #}}", "are_these_the_same_person": "Eru Þessi sama manneskjan?", "are_you_sure_to_do_this": "Ertu viss um að ÞÃē viljir gera Þetta?", + "asset_action_delete_err_read_only": "Get ekki eytt eignum í skoðunarham, sleppi", + "asset_action_share_err_offline": "Get ekki sÃŗtt Ãŗtengdar eignir, sleppi", "asset_added_to_album": "BÃĻtt í myndasafn", "asset_adding_to_album": "BÃĻti í myndasafnâ€Ļ", "asset_description_updated": "LÃŊsing eignarinnar hefur verið uppfÃĻrð", + "asset_filename_is_offline": "Eign {filename} er Ãŗtengd", + "asset_has_unassigned_faces": "Eign er með ÃŗskrÃĄÃ° andlit", "asset_hashing": "Hakkaâ€Ļ", "asset_list_group_by_sub_title": "HÃŗpa eftir", + "asset_list_layout_settings_dynamic_layout_title": "Kviklegt viðmÃŗt", + "asset_list_layout_settings_group_automatically": "SjÃĄlfvirkt", "asset_list_layout_settings_group_by": "HÃŗpa eignir eftir", "asset_list_layout_settings_group_by_month_day": "MÃĄnuður + dagur", "asset_list_layout_sub_title": "ViðmÃŗt", @@ -510,26 +542,36 @@ "asset_list_settings_title": "Myndanet", "asset_offline": "Eign Ótengd", "asset_offline_description": "Þessi utanaðkomandi eign finnst ekki lengur í skrÃĄakerfinu. Kallið ÃĄ hjÃĄlp frÃĄ ykkar Immich stjÃŗrnanda.", + "asset_restored_successfully": "Eign endurheimtuð", "asset_skipped": "Sleppt", "asset_skipped_in_trash": "Í rusli", + "asset_trashed": "Eign hent í ruslið", "asset_troubleshoot": "Bilanagreina eign", "asset_uploaded": "Hlaðið upp", "asset_uploading": "Hleð uppâ€Ļ", "asset_viewer_settings_subtitle": "SÃŊsla með skoðun myndasafns", "asset_viewer_settings_title": "Skoðun Eigna", "assets": "Eignir", + "assets_added_count": "BÃĻtti við {count, plural, one {# eign} other {# eignum}}", "assets_added_to_album_count": "BÃĻtti við {count, plural, one {# eign} other {# eigum}} í myndasafnið", "assets_added_to_albums_count": "BÃĻtti við {assetTotal, plural, one {# eign} other {# eignum}} í {albumTotal, plural, one {# myndasafn} other {# myndasÃļfn}}", "assets_cannot_be_added_to_album_count": "Ekki hÃĻgt að bÃĻta {count, plural, one {eign} other {eignum}} í myndasafnið", "assets_cannot_be_added_to_albums": "Ekki hÃĻgt að bÃĻta {count, plural, one {eign} other {eignum}} í neitt af Þessum myndasÃļfnum", "assets_count": "{count, plural, one {# eign} other {# eignir}}", + "assets_deleted_permanently": "{count} eign/-um eytt fyrir fullt og allt", + "assets_deleted_permanently_from_server": "{count} eign/-um eytt fyrir fullt og allt af Immich ÞjÃŗninum", + "assets_downloaded_failed": "{count, plural, one {SÃŗtt # skrÃĄ - {error} skrÃĄ brast} other {SÃŗttar # skrÃĄr - {error} skrÃĄr brugðust}}", + "assets_downloaded_successfully": "{count, plural, one {SÃŗtti # skrÃĄ} other {SÃŗtti # skrÃĄr}}", "assets_moved_to_trash_count": "FÃĻrði {count, plural, one {# eign} other {# eignir}} í ruslið", "assets_permanently_deleted_count": "Eyddi {count, plural, one {# eign} other {# eignum}} fyrir fullt og allt", "assets_removed_count": "FjarlÃĻgði {count, plural, one {# eign} other {# eignir}}", + "assets_removed_permanently_from_device": "{count} eign/-ir fjarlÃĻgðar af tÃĻkinu Þínu fyrir fullt og allt", "assets_restore_confirmation": "Ertu viss um að ÞÃē viljir endurheimta allar eignir Ãēr ruslinu? ÞÃē getur ekki tekið Þetta til baka! Athugið að Ãŗtengdar eignir geta ekki verið endurheimtar ÃĄ Þennan hÃĄtt.", "assets_restored_count": "Endurheimti {count, plural, one {# eign} other {# eignir}}", + "assets_restored_successfully": "{count} eign/-ir endurheimtar", "assets_trashed": "{count} eign/-um hent í ruslið", "assets_trashed_count": "Henti {count, plural, one {# eign} other {# eignum}} í ruslið", + "assets_trashed_from_server": "{count} eign/-um hent í ruslið ÃĄ Immich ÞjÃŗninum", "assets_were_part_of_album_count": "{count, plural, one {Eign var} other {Eignir voru}} nÃē Þegar í myndasafninu", "assets_were_part_of_albums_count": "{count, plural, one {Eign var} other {Eignir voru}} nÃē Þegar í myndasÃļfnunum", "authorized_devices": "Leyfð TÃĻki", @@ -538,44 +580,85 @@ "autoplay_slideshow": "Spila glÃĻrusÃŊningu sjÃĄlfkrafa", "back": "Til baka", "back_close_deselect": "Til baka, loka eða hÃĻtta við val", + "background_backup_running_error": "Afritun er nÃē Þegar í gangi í bakgrunni, get ekki hafið handvirka afritun", "background_location_permission": "Leyfi ÃĄ staðsetningu afritunar", "background_location_permission_content": "Til að skipta um net í bakgrunni Þarf Immich *alltaf* að hafa nÃĄkvÃĻman aðgang að nÃĄkvÃĻmum staðsetningargÃļgnum svo að forritið geti lesið heitið ÃĄ Wi-Fi netinu", "background_options": "BakgrunnsvalmÃļguleikar", "backup": "Afrit", + "backup_album_selection_page_albums_device": "MyndasÃļfn ÃĄ tÃĻki ({count})", "backup_album_selection_page_albums_tap": "Smelltu til að taka með, tvísmelltu til að sleppa", "backup_album_selection_page_assets_scatter": "Eignum getur verið dreift ÃĄ milli margra myndasafna. Þannig er hÃĻgt að velja hvaða myndasÃļfn eru afrituð.", "backup_album_selection_page_select_albums": "Velja myndasÃļfn", "backup_album_selection_page_selection_info": "UpplÃŊsingar um val", + "backup_album_selection_page_total_assets": "HeildarfjÃļldi einkvÃĻmra eigna", "backup_albums_sync": "Samstilling afritaðra myndasafna", + "backup_all": "Allt", + "backup_background_service_backup_failed_message": "Ekki gekk að afrita eignir. Reyni afturâ€Ļ", "backup_background_service_complete_notification": "Afritun eigna lokið", + "backup_background_service_connection_failed_message": "Ekki gekk að tengjast ÞjÃŗni. Reyni afturâ€Ļ", + "backup_background_service_current_upload_notification": "Hleð upp {filename}", "backup_background_service_default_notification": "Athuga með nÃŊjar eignirâ€Ļ", + "backup_background_service_error_title": "Afritunarvilla", "backup_background_service_in_progress_notification": "Afrita eignirâ€Ļ", + "backup_background_service_upload_failure_notification": "Ekki gekk að hlaða upp {filename}", "backup_controller_page_albums": "Afrituð MyndasÃļfn", + "backup_controller_page_background_app_refresh_disabled_content": "Virkið bakgrunnsvinnslu í Settings > General > Background App Refresh til að afrita gÃļgn í bakgrunni.", + "backup_controller_page_background_app_refresh_disabled_title": "Bakgrunnsvinnsla Ãŗvirk", + "backup_controller_page_background_app_refresh_enable_button_text": "Fara í stillingar", "backup_controller_page_background_battery_info_link": "SÃŊndu mÊr hvernig", "backup_controller_page_background_battery_info_message": "SlÃļkktu ÃĄ allri bestun ÃĄ rafhlÃļðunotkun fyrir Immich til að fÃĄ sem ÃĄreiðanlegasta bakgrunnsafritun.\n\nÞetta er mismunandi eftir framleiðanda, flettu upp leiðbeiningum frÃĄ Þínum framleiðanda.", "backup_controller_page_background_battery_info_ok": "Í lagi", "backup_controller_page_background_battery_info_title": "Bestun rafhlÃļðu", + "backup_controller_page_background_charging": "Aðeins Þegar í hleðslu", + "backup_controller_page_background_configure_error": "Ekki gekk að stilla bakgrunnsÞjÃŗnustuna", "backup_controller_page_background_delay": "Seinka afritun nÃŊrrar eignar: {duration}", + "backup_controller_page_background_description": "Kveiktu ÃĄ bakgrunnsÞjÃŗnustum til að afrita nÃŊjar eignir sjÃĄlfkrafa ÃĄn Þess að Þurfa að opna forritið", + "backup_controller_page_background_is_off": "SjÃĄlfvirk afritun í bakgrunni er Ãŗvirk", + "backup_controller_page_background_is_on": "SjÃĄlfvirk afritun í bakgrunni er virk", + "backup_controller_page_background_turn_off": "SlÃļkkva ÃĄ bakgrunnsÞjÃŗnustu", + "backup_controller_page_background_turn_on": "Kveikja ÃĄ bakgrunnsÞjÃŗnustu", + "backup_controller_page_background_wifi": "Aðeins ÃĄ Wi-Fi", "backup_controller_page_backup": "Afrit", "backup_controller_page_backup_selected": "Valið: ", "backup_controller_page_backup_sub": "Afritaðar myndir og myndbÃļnd", + "backup_controller_page_created": "BÃēið til ÃĄ: {date}", + "backup_controller_page_desc_backup": "Kveikið ÃĄ afritun í forgrunni til að afrita nÃŊjar eignir sjÃĄlfkrafa Þegar forritið er opnað.", "backup_controller_page_excluded": "Sleppt: ", + "backup_controller_page_failed": "Brostið ({count})", + "backup_controller_page_filename": "SkrÃĄarheiti: {filename} [{size}]", + "backup_controller_page_id": "Auðkenni: {id}", + "backup_controller_page_info": "UpplÃŊsingar um Afrit", "backup_controller_page_none_selected": "Ekkert valið", "backup_controller_page_remainder": "Afgangur", "backup_controller_page_remainder_sub": "Myndir og myndbÃļnd Ãēr vali sem ÃĄ eftir að afrita", "backup_controller_page_server_storage": "PlÃĄss ÃĄ ÞjÃŗni", + "backup_controller_page_start_backup": "Hefja Afritun", + "backup_controller_page_status_off": "SjÃĄlfvirk afritun í forgrunni er Ãŗvirk", + "backup_controller_page_status_on": "SjÃĄlfvirk afritun í forgrunni er virk", "backup_controller_page_storage_format": "{used} af {total} notað", "backup_controller_page_to_backup": "MyndasÃļfn sem ÃĄ að afrita", "backup_controller_page_total_sub": "Allar einkvÃĻmar myndir og myndbÃļnd Ãēr vÃļldum myndasÃļfnum", + "backup_controller_page_turn_off": "SlÃļkkva ÃĄ afritun í forgrunni", + "backup_controller_page_turn_on": "Kveikja ÃĄ afritun í forgrunni", + "backup_controller_page_uploading_file_info": "Hleð upp skrÃĄaupplÃŊsingum", + "backup_err_only_album": "Get ekki fjarlÃĻgt eina myndasafnið", "backup_error_sync_failed": "SamrÃĻming gekk ekki. Get ekki unnið afrit.", "backup_info_card_assets": "eignir", + "backup_manual_cancelled": "HÃĻtt við", + "backup_manual_in_progress": "Upphleðsla nÃē Þegar í gangi. Reyndu aftur seinna", + "backup_manual_success": "GlÃĻstur ÃĄrangur", + "backup_manual_title": "Staða upphleðslu", "backup_options": "ValmÃļguleikar Afritunar", + "backup_options_page_title": "ValmÃļguleikar afritunar", + "backup_setting_subtitle": "SÃŊsla með upphleðslu í for- og bakgrunni", "backup_settings_subtitle": "SÃŊsla með upphleðslu", + "backup_upload_details_page_more_details": "Smelltu fyrir frekari upplÃŊsingar", "backward": "AfturÃĄbak", "biometric_auth_enabled": "Auðkenning með lífkennum virk", "biometric_locked_out": "ÞÃē ert lÃĻst Ãēt Ãēr auðkenningu með lífkennum", "biometric_no_options": "Ekkert val ÃĄ lífkennum í boði", "biometric_not_available": "Auðkenning með lífkennum er ekki í boði ÃĄ Þessu tÃĻki", + "birthdate_saved": "FÃĻðingardagur vistaður", "birthdate_set_description": "FÃĻðingardagur er notaður til að reikna Ãēt aldur manneskju ÃĄ myndinni.", "blurred_background": "SkÃŊjaður bakgrunnur", "bugs_and_feature_requests": "PÃļddur og Beiðnir um NÃŊja Eiginleika", @@ -585,6 +668,18 @@ "bulk_keep_duplicates_confirmation": "Ertu viss um að ÞÃē viljir halda {count, plural, one {# margfaldri eign} other {# margfÃļldum eignum}}? Þetta leysir alla margfeldnihÃŗpa ÃĄn Þess að eyða neinu.", "bulk_trash_duplicates_confirmation": "Ertu viss um að ÞÃē viljir eyða {count, plural, one {# margfaldri eign} other {# margfÃļldum eignum}}? StÃĻrsta skrÃĄin í hverjum hÃŗp verður geymd og Ãļðrum hent í ruslið.", "buy": "Kaupa Immich", + "cache_settings_clear_cache_button": "Hreinsa skyndiminni", + "cache_settings_duplicated_assets_clear_button": "HREINSA", + "cache_settings_duplicated_assets_title": "Margfaldar Eignir ({count})", + "cache_settings_statistics_album": "Gaummyndir fyrir myndasÃļfn", + "cache_settings_statistics_full": "Myndir í fullum gÃĻðum", + "cache_settings_statistics_shared": "Gaummyndir fyrir sameiginleg myndasÃļfn", + "cache_settings_statistics_thumbnail": "Gaummynd", + "cache_settings_statistics_title": "Skyndiminni í notkun", + "cache_settings_subtitle": "SÃŊsla með hÃļgun skyndiminnis í smÃĄforriti Immich", + "cache_settings_tile_subtitle": "SÃŊsla með hÃļgun staðbundinnar geymslu", + "cache_settings_tile_title": "Staðbundin geymsla", + "cache_settings_title": "Stillingar Skyndiminnis", "camera": "MyndavÊl", "camera_brand": "Framleiðandi myndvÊlar", "camera_model": "Gerð myndavÊlar", @@ -600,6 +695,7 @@ "change_date": "Breyta dagsetningu", "change_description": "Breyta lÃŊsingu", "change_display_order": "Breyta rÃļðun", + "change_expiration_time": "Breyta fyrningardagsetningu", "change_location": "Breyta staðsetningu", "change_name": "Breyta nafni", "change_name_successfully": "Breytti heiti", @@ -617,6 +713,9 @@ "changed_visibility_successfully": "Breytti sÃŊn", "charging": "Hleður", "charging_requirement_mobile_backup": "Aftirun í bakgrunni krefst Þess að tÃĻkið sÊ í hleðslu", + "check_corrupt_asset_backup": "Athuga með spillt afritunargÃļgn", + "check_corrupt_asset_backup_button": "FramkvÃĻma athugun", + "check_corrupt_asset_backup_description": "Vertu ÃĄ Wi-Fi tengingu og með Ãļll afrit klÃĄr Þegar Þetta er keyrt. Þetta gÃĻti tekið nokkrar mínÃētur.", "check_logs": "Athuga AtburðaskrÃĄ", "checksum": "PrÃŗfsumma", "choose_matching_people_to_merge": "Veldur samskonar fÃŗlk til að sameina", @@ -627,16 +726,20 @@ "clear_file_cache": "Hreinsa FlÃŊtiminni SkrÃĄa", "clear_message": "Hreinsa skilboð", "clear_value": "Hreinsa gildi", + "client_cert_dialog_msg_confirm": "Í lagi", + "client_cert_enter_password": "SlÃĄÃ°u inn lykilorð", "client_cert_import": "Flytja inn", "client_cert_import_success_msg": "Skírteini biðlara flutt inn", "client_cert_invalid_msg": "Ógilt skírteini eða rangt lykilorð", "client_cert_remove_msg": "Skírteini biðlara fjarlÃĻgt", "client_cert_subtitle": "Styður einungis PKC12 (.p12, .ptx) snið. Einungis hÃĻgt að flytja inn/fjarlÃĻgja skírteini fyrir innskrÃĄningu", "client_cert_title": "TLS skírteini biðlara [Á TILRAUNASTIGI]", + "clockwise": "SÃŗlarhringur", "close": "Loka", "collapse": "Fella upp", "collapse_all": "Fella allt upp", "color": "Litur", + "color_theme": "LitaÞema", "command": "Skipun", "comment_deleted": "Athugasemd eytt", "comment_options": "ValmÃļguleikar athugasemda", @@ -658,13 +761,18 @@ "contain": "Inniheldur", "context": "Samhengi", "continue": "Halda ÃĄfram", + "control_bottom_app_bar_create_new_album": "BÃēa til nÃŊtt myndasafn", + "control_bottom_app_bar_delete_from_immich": "Eyða Ãēr Immich", "control_bottom_app_bar_delete_from_local": "Eyða Ãēr tÃĻki", "control_bottom_app_bar_edit_location": "Breyta Staðsetningu", "control_bottom_app_bar_edit_time": "Breyta Tíma og Dagsetningu", + "control_bottom_app_bar_share_link": "Deila Hlekk", + "control_bottom_app_bar_share_to": "Deila Til", "control_bottom_app_bar_trash_from_immich": "FÃĻra í Ruslið", "copied_image_to_clipboard": "Afritaði mynd ÃĄ klemmuspjald.", "copied_to_clipboard": "Afritað ÃĄ klemmuspjald!", "copy_error": "Villa við að afrita", + "copy_file_path": "Afrita slÃŗÃ°", "copy_image": "Afrita Mynd", "copy_link": "Afrita hlekk", "copy_link_to_clipboard": "Afrita hlekk ÃĄ klemmuspjald", @@ -691,6 +799,9 @@ "download_paused": "Niðurhal í pÃĄsu", "download_settings": "Niðurhal", "download_settings_description": "SÃŊsla með stillingar er varða niðurhal ÃĄ eignum", + "download_started": "Niðurhal byrjað", + "download_sucess": "Niðurhal tÃŗkst", + "download_sucess_android": "Efninu hefur verið halað niður í DCIM/Immich", "download_waiting_to_retry": "Bíð eftir annarri tilraun", "downloading": "Hleð niður", "downloading_asset_filename": "Hleð niður {filename}", @@ -707,7 +818,9 @@ "edit_date_and_time": "Breyta tíma og dagsetningu", "edit_date_and_time_action_prompt": "{count} tímasetningum breytt", "edit_date_and_time_by_offset": "Breyta dagsetningu með hliðrun", + "edit_date_and_time_by_offset_interval": "NÃŊtt tímabil: {from} - {to}", "edit_description": "Breyta lÃŊsingu", + "edit_description_prompt": "Vinsamlega veldu nÃŊja lÃŊsingu:", "edit_exclusion_pattern": "Breyta Ãētilokunarmynstri", "edit_faces": "Haga andlitum", "edit_key": "Breyta lykli", @@ -721,6 +834,8 @@ "edit_title": "Breyta Titli", "edit_user": "Breyta notanda", "editor": "Myndvinnsla", + "editor_close_without_save_prompt": "Breytingarnar verða ekki vistaðar", + "editor_close_without_save_title": "Loka myndvinnslu?", "email": "Netfang", "email_notifications": "Meldingar í tÃļlvupÃŗsti", "empty_folder": "Þessi mappa er tÃŗm", @@ -736,7 +851,9 @@ "enter_your_pin_code": "SlÃĄÃ°u inn PIN nÃēmerið Þitt", "enter_your_pin_code_subtitle": "SlÃĄÃ°u inn PIN nÃēmer til að fÃĄ aðgang að lÃĻstu mÃļppunni", "error": "Villa", + "error_change_sort_album": "Ekki gekk að breyta flokkunarrÃļð myndasafna", "error_delete_face": "Villa við að eyða andliti Ãēr eign", + "error_getting_places": "Villa við að sÃĻkja staði", "error_loading_image": "Villa við að hlaða mynd", "error_loading_partners": "Villa við að hlaða fÊlÃļgum: {error}", "error_saving_image": "Villa: {error}", @@ -865,9 +982,17 @@ "exif": "Exif", "exif_bottom_sheet_description": "BÃĻta við lÃŊsingu...", "exif_bottom_sheet_description_error": "Villa við að uppfÃĻra lÃŊsingu", + "exif_bottom_sheet_details": "SMÁATRIÐI", + "exif_bottom_sheet_location": "STAÐSETNING", "exif_bottom_sheet_no_description": "Engin lÃŊsing", + "exif_bottom_sheet_people": "FÓLK", + "exif_bottom_sheet_person_add_person": "BÃĻta við nafni", "exit_slideshow": "HÃĻtta glÃĻrusÃŊningu", "expand_all": "Fella allt niður", + "experimental_settings_new_asset_list_subtitle": "Í vinnslu", + "experimental_settings_new_asset_list_title": "Virkja myndanet ÃĄ tilraunastigi", + "experimental_settings_subtitle": "Notkun ÃĄ eigin ÃĄhÃĻttu!", + "experimental_settings_title": "Á tilraunastigi", "expire_after": "Fyrnist eftir", "expired": "Útrunnið", "expires_date": "Fyrnist {date}", @@ -892,8 +1017,10 @@ "favorite_action_prompt": "{count} bÃĻtt í UppÃĄhalds", "favorite_or_unfavorite_photo": "Taka Ãēr eða setja í uppÃĄhalds", "favorites": "UppÃĄhalds", + "favorites_page_no_favorites": "Engar uppÃĄhalds eignir fundust", "feature_photo_updated": "Birtingarmynd uppfÃĻrð", "features": "Eiginleikar", + "features_in_development": "Eiginleikar í ÞrÃŗun", "features_setting_description": "SÃŊsla með eiginleika smÃĄforrits", "file_name_or_extension": "SkrÃĄarheiti eða nafnauki", "file_size": "SkrÃĄarstÃĻrð", @@ -902,6 +1029,7 @@ "filter": "Sía", "filter_people": "Sía fÃŗlk", "filter_places": "Sía staði", + "find_them_fast": "Finndu Þau í snatri með leit að nafni", "first": "Fyrst", "fix_incorrect_match": "Laga ranga pÃļrun", "folder": "Mappa", @@ -930,6 +1058,7 @@ "group_owner": "HÃŗpa eftir eigendum", "group_places_by": "HÃŗpa staði eftir...", "group_year": "HÃŗpa eftir ÃĄri", + "haptic_feedback_switch": "Virkja snertisvÃļrun", "haptic_feedback_title": "SnertisvÃļrun", "has_quota": "Er með Ãēthutaða notkun", "hash_asset": "Hakka eign", @@ -949,12 +1078,29 @@ "hide_person": "Fela manneskju", "hide_text_recognition": "Fela textagreiningu", "hide_unnamed_people": "Fela Ãŗnefnt fÃŗlk", + "home_page_add_to_album_conflicts": "BÃĻtti {added} eignum við safnið {album}. {failed} eignir eru nÃē Þegar í safninu.", + "home_page_add_to_album_err_local": "Get ekki bÃĻtt staðvÃĻrum eignum í safn, sleppi", + "home_page_add_to_album_success": "BÃĻtti {added} eignum í safnið {album}.", + "home_page_album_err_partner": "Get ekki bÃĻtt eignum fÊlaga í safn, sleppi", + "home_page_archive_err_local": "Get ekki sett staðvÃĻrar eignir í geymslu, sleppi", + "home_page_archive_err_partner": "Get ekki sett eignir fÊlaga í geymslu, sleppi", "home_page_building_timeline": "Smíða tímalínu", + "home_page_delete_err_partner": "Get ekki eytt eignum fÊlaga, sleppi", + "home_page_delete_remote_err_local": "StaðvÃĻrar eignir í vali eigna í fjarska, sleppi", + "home_page_favorite_err_local": "Get ekki sett staðvÃĻrar eignir í uppÃĄhalds, sleppi", + "home_page_favorite_err_partner": "Get ekki sett eignir fÊlaga í uppÃĄhalds, sleppi", + "home_page_first_time_notice": "Ef ÞÃē ert að nota Þetta smÃĄforrit í fyrsta sinn, gakktu Ãēr skugga um að ÞÃē veljir myndasafn til að afrita svo að tímalínan fyllist af myndum og myndbÃļndum", + "home_page_locked_error_local": "Get ekki fÃĻrt staðvÃĻrar eignir í lÃĻsta mÃļppu, sleppi", + "home_page_locked_error_partner": "Get ekki fÃĻrt eignir fÊlaga í lÃĻsta mÃļppu, sleppi", + "home_page_share_err_local": "Get ekki deilt staðvÃĻrum eignum með hlekk, sleppi", + "home_page_upload_err_limit": "Get bara hlaðið upp 30 myndum að hÃĄmarki í einu, sleppi", "host": "HÃŊsill", "hour": "Klukkustund", "hours": "Klukkustundir", "id": "Auðkenni", "idle": "Hvílandi", + "ignore_icloud_photos": "Hundsa myndir í iCloud", + "ignore_icloud_photos_description": "Myndum sem eru geymdar í iCloud verður ekki hlaðið upp ÃĄ Immich ÞjÃŗninn", "image": "Mynd", "image_alt_text_date": "{isVideo, select, true {Myndband} other {Mynd}} tekin ÃĄ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Myndband} other {Mynd}} með {person1} ÃĄ {date}", @@ -966,6 +1112,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} með {person1} og {person2} ÃĄ {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Myndband} other {Mynd}} í {city}, {country} með {person1}, {person2}, og {person3} ÃĄ {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} í {city}, {country} með {person1}, {person2}, og {additionalCount, number} Ãļðrum ÃĄ {date}", + "image_saved_successfully": "Mynd vistuð", + "image_viewer_page_state_provider_download_started": "Niðurhal hafið", + "image_viewer_page_state_provider_download_success": "Niðurhal tÃŗkst", + "image_viewer_page_state_provider_share_error": "Villa við deilingu", "immich_logo": "Immich merkið", "immich_web_interface": "VefviðmÃŗt Immich", "import_from_json": "Flytja inn Ãēr JSON", diff --git a/i18n/it.json b/i18n/it.json index 102d54b26d..d51e45b37b 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -1,7 +1,7 @@ { "about": "Informazioni", "account": "Profilo", - "account_settings": "Impostazioni profilo", + "account_settings": "Impostazioni Profilo", "acknowledge": "Ho capito", "action": "Azione", "action_common_update": "Aggiorna", @@ -10,44 +10,52 @@ "active": "Attivo", "active_count": "Attivi: {count}", "activity": "Attività", + "activity_changed": "L'attività è {enabled, select, true {abilitata} other {disabilitata}}", "add": "Aggiungi", "add_a_description": "Aggiungi una descrizione", "add_a_location": "Aggiungi una posizione", "add_a_name": "Aggiungi un nome", "add_a_title": "Aggiungi un titolo", "add_action": "Aggiungi azione", + "add_action_description": "Fare clic per aggiungere un'azione da eseguire", "add_assets": "Aggiungi risorse", - "add_birthday": "Aggiungi un compleanno", - "add_endpoint": "Aggiungi endpoint", - "add_exclusion_pattern": "Aggiungi pattern di esclusione", + "add_birthday": "Aggiungi compleanno", + "add_endpoint": "Aggiungi un endpoint", + "add_exclusion_pattern": "Aggiungi un pattern di esclusione", + "add_filter": "Aggiungi filtro", + "add_filter_description": "Fare clic per aggiungere una condizione di filtro", "add_location": "Aggiungi posizione", + "add_more_users": "Aggiungi altri utenti", "add_partner": "Aggiungi partner", + "add_path": "Aggiungi un percorso", "add_photos": "Aggiungi foto", - "add_step": "Aggiungi passo", "add_tag": "Aggiungi tag", "add_to": "Aggiungi aâ€Ļ", "add_to_album": "Aggiungi all'album", "add_to_album_bottom_sheet_added": "Aggiunto in {album}", "add_to_album_bottom_sheet_already_exists": "Già presente in {album}", "add_to_album_bottom_sheet_some_local_assets": "Alcune risorse locali non possono essere aggiunte all'album", + "add_to_album_toggle": "Attiva/disattiva selezione per {album}", "add_to_albums": "Aggiungi ad album", "add_to_albums_count": "Aggiungi ad album ({count})", "add_to_bottom_bar": "Aggiungi a", + "add_to_shared_album": "Aggiungi ad album condiviso", "add_upload_to_stack": "Aggiungi caricamento allo stack", "add_url": "Aggiungi URL", + "add_workflow_step": "Aggiungi passaggio del flusso di lavoro", "added_to_archive": "Aggiunto all'archivio", "added_to_favorites": "Aggiunto ai preferiti", - "added_to_favorites_count": "Aggiunti {count, number} ai preferiti", + "added_to_favorites_count": "Aggiunto {count, number} ai preferiti", "admin": { "add_exclusion_pattern_description": "Aggiungi modelli di esclusione. È supportato il globbing utilizzando *, ** e ?. Per ignorare tutti i file in qualsiasi directory denominata \"Raw\", usa \"**/Raw/**\". Per ignorare tutti i file con estensione \".tif\", usa \"**/*.tif\". Per ignorare un percorso assoluto, usa \"/percorso/da/ignorare/**\".", "admin_user": "Utente amministratore", "asset_offline_description": "Questa risorsa della libreria esterna non si trova piÚ sul disco ed è stata spostata nel cestino. Se il file è stato spostato all'interno della libreria, controlla la timeline per la nuova risorsa corrispondente. Per ripristinare questa risorsa, assicurati che Immich possa accedere al percorso del file ed esegui la scansione della libreria.", - "authentication_settings": "Impostazioni di autenticazione", + "authentication_settings": "Impostazioni di Autenticazione", "authentication_settings_description": "Gestisci password, OAuth e altre impostazioni di autenticazione", "authentication_settings_disable_all": "Sei sicuro di voler disabilitare tutte le modalità di accesso? Il login verrà disabilitato completamente.", "authentication_settings_reenable": "Per ri-abilitare, utilizza un Comando Server.", - "background_task_job": "Attività in background", - "backup_database": "Crea dump del database", + "background_task_job": "Attività in Background", + "backup_database": "Crea Dump Database", "backup_database_enable_description": "Abilita i backup del database", "backup_keep_last_amount": "Numero di backup da mantenere", "backup_onboarding_1_description": "copia offsite nel cloud o in un'altra sede fisica.", @@ -57,31 +65,30 @@ "backup_onboarding_footer": "Per ulteriori informazioni sul backup di Immich, consulta la documentazione.", "backup_onboarding_parts_title": "Un backup 3-2-1 include:", "backup_onboarding_title": "Backup", - "backup_settings": "Impostazioni dump del database", + "backup_settings": "Impostazioni Dump database", "backup_settings_description": "Gestisci le impostazioni dei backup.", "cleared_jobs": "Cancellati i processi per: {job}", "config_set_by_file": "La configurazione è attualmente impostata da un file di configurazione", - "confirm_delete_library": "Sei sicuro di voler eliminare la libreria {library}?", - "confirm_delete_library_assets": "Sei sicuro di voler eliminare questa libreria? Questo eliminerà {count, plural, one {# risorsa contenuta} other {tutti le # risorse contenute}} da Immich e l'azione non potrà essere annullata. I file rimarranno sul disco.", + "confirm_delete_library": "Sei sicuro di voler cancellare la libreria {library}?", + "confirm_delete_library_assets": "Sei sicuro di voler cancellare questa libreria? CiÃ˛ rimuoverà {count, plural, one {# risorsa} other {tutte le # risorse}} da Immich senza possibilità di tornare indietro. I file rimarranno comunque sul disco.", "confirm_email_below": "Per confermare, scrivi \"{email}\" qui sotto", "confirm_reprocess_all_faces": "Sei sicuro di voler riprocessare tutti i volti? Questo cancellerà anche tutte le persone associate.", "confirm_user_password_reset": "Sei sicuro di voler resettare la password di {user}?", "confirm_user_pin_code_reset": "Sicuro di voler resettare il codice PIN di {user}?", "copy_config_to_clipboard_description": "Copia la configurazione attuale del sistema come oggetto JSON negli appunti", - "create_job": "Crea processo", - "cron_expression": "Espressione cron", + "create_job": "Crea Processo", + "cron_expression": "Espressione Cron", "cron_expression_description": "Imposta il tempo di scansione utilizzando il formato Cron. Per ulteriori informazioni fare riferimento a Crontab Guru", - "cron_expression_presets": "Preimpostazioni espressioni cron", + "cron_expression_presets": "Espressione Cron preimpostata", "disable_login": "Disabilita login", - "download_csv": "Scarica CSV", "duplicate_detection_job_description": "Esegui il machine learning sulle risorse per rilevare immagini simili. Basato su Ricerca Intelligente", "exclusion_pattern_description": "I modelli di esclusione ti permettono di ignorare file e cartelle durante la scansione della tua libreria. Questo è utile se hai cartelle che contengono file che non vuoi importare, come ad esempio, i file RAW.", "export_config_as_json_description": "Scarica la configurazione attuale del sistema come file JSON", "external_libraries_page_description": "Pagina librerie esterne (admin)", - "face_detection": "Rilevamento volti", - "face_detection_description": "Rileva i volti presenti nelle risorse utilizzando il machine-learning. Per i video, viene presa in considerazione solo la miniatura. Utilizza \"Ripristina\" per cancellare tutti i volti presenti, \"Ricarica\" per processare di nuovo tutti le risorse, \"Mancanti\" processa solo le risorse che non sono ancora stati processati. I volti rilevati verranno selezionati per il riconoscimento facciale dopo che il rilevamento dei volti sarà stato completato, raggruppandoli in persone esistenti e/o nuove.", + "face_detection": "Rilevamento Volti", + "face_detection_description": "Rileva i volti presenti nelle risorse utilizzando il machine-learning. Per i video, viene presa in considerazione solo la miniatura. Utilizzare \"Ripristina\" per cancellare tutti i volti presenti, \"Ricarica\" per processare di nuovo tutti le risorse, \"Mancanti\" processa solo le risorse che non sono ancora stati processati. I volti rilevati verranno selezionati per il riconoscimento facciale dopo che il rilevamento dei volti sarà stato completato, raggruppandoli in persone esistenti e/o nuove.", "facial_recognition_job_description": "Raggruppa i volti rilevati in persone. Questo processo viene eseguito dopo che il rilevamento volti è stato completato. \"Reset\" (ri-)unisce tutti i volti. \"Mancanti\" processa i volti che non hanno una persona assegnata.", - "failed_job_command": "Esecuzione del comando {command} non riuscita per il processo: {job}", + "failed_job_command": "Il comando {command} è fallito per il processo: {job}", "force_delete_user_warning": "ATTENZIONE: Questo rimuoverà immediatamente l'utente e tutti le sue risorse. Non è possibile tornare indietro e i file non potranno essere recuperati.", "image_format": "Formato", "image_format_description": "WebP produce file piÚ piccoli rispetto a JPEG, ma è piÚ lento da codificare.", @@ -93,63 +100,48 @@ "image_prefer_embedded_preview": "Preferisci l'anteprima integrata", "image_prefer_embedded_preview_setting_description": "Usa l'anteprima integrata nelle foto RAW come input per l'elaborazione delle immagini, se disponibile. Questo permette un miglioramento dei colori per alcune immagini, ma la qualità delle anteprime dipende dalla macchina fotografica. Inoltre le immagini potrebbero presentare artefatti di compressione.", "image_prefer_wide_gamut": "Preferisci gamut piÚ ampio", - "image_prefer_wide_gamut_setting_description": "Usa lo spazio colore Display P3 per le miniature. Questo aiuta a mantenere la vivacità delle immagini con spazi colore piÚ ampi, tuttavia potrebbe non mostrare correttamente le immagini con dispositivi e browser obsoleti. Le immagini sRGB vengono preservate per evitare alterazioni del colore.", + "image_prefer_wide_gamut_setting_description": "Usa lo spazio colore Display P3 per le anteprime. Questo aiuta a mantenere la vivacità delle immagini con spazi colore piÚ ampi, tuttavia potrebbe non mostrare correttamente le immagini con dispositivi e browser obsoleti. Le immagini sRGB vengono preservate per evitare alterazioni del colore.", "image_preview_description": "Immagine a media dimensione senza metadati, utilizzata durante la visualizzazione di una singola risorsa e per il machine learning", "image_preview_quality_description": "Qualità dell'anteprima da 1 a 100. PiÚ alto è meglio ma produce file piÚ pesanti e puÃ˛ ridurre la reattività dell'app. Impostare un valore basso puÃ˛ influenzare negativamente la qualità del machine learning.", - "image_preview_title": "Impostazioni anteprime", - "image_progressive": "Progressiva", + "image_preview_title": "Impostazioni dell'anteprima", + "image_progressive": "Progressivo", "image_progressive_description": "Codifica progressivamente le immagini JPEG per mostrarle con un caricamento graduale. Questo non ha effetto sulle immagini WebP.", "image_quality": "Qualità", "image_resolution": "Risoluzione", "image_resolution_description": "Risoluzioni piÚ elevate possono preservare piÚ dettagli ma richiedere piÚ tempo per la codifica, avere dimensioni di file piÚ grandi e ridurre la reattività dell'app.", - "image_settings": "Impostazioni immagini", + "image_settings": "Impostazioni delle immagini", "image_settings_description": "Gestisci qualità e risoluzione delle immagini generate", - "image_thumbnail_description": "Miniatura piccola senza metadati, utilizzata per la visualizzazione di gruppi di foto come la timeline principale", + "image_thumbnail_description": "Miniatura piccola senza metadati, utilizzata durante la visualizzazione di gruppi di foto come nella galleria principale", "image_thumbnail_quality_description": "Qualità delle miniature da 1 a 100. Un valore piÚ alto è migliore ma produce file piÚ grandi e puÃ˛ ridurre la reattività dell'app.", - "image_thumbnail_title": "Impostazioni miniature", + "image_thumbnail_title": "Impostazioni delle miniature", "import_config_from_json_description": "Importa la configurazione del sistema caricando un file JSON", - "integrity_checks_checksum_files": "File di verifica", - "integrity_checks_checksum_files_description": "Configura il controllo del checksum", - "integrity_checks_checksum_files_enable_description": "Attiva il controllo del checksum", - "integrity_checks_checksum_files_percentage_limit": "Limite percentuale", - "integrity_checks_checksum_files_percentage_limit_description": "Imposta la percentuale massima, compresa tra 0,01 e 1, che indica la frequenza con cui deve essere eseguito il controllo del checksum in ciascun intervallo.", - "integrity_checks_checksum_files_time_limit": "Limite di tempo", - "integrity_checks_checksum_files_time_limit_description": "Configura la durata massima della verifica del checksum per ciascun intervallo. (ms)", - "integrity_checks_missing_files": "File mancanti", - "integrity_checks_missing_files_description": "Configurare la frequenza e attivare o disattivare il controllo dei file mancanti", - "integrity_checks_missing_files_enable_description": "Attiva il controllo dei file mancanti", - "integrity_checks_settings": "Controlli integrità", - "integrity_checks_settings_description": "Gestisci l'intervallo dei controlli di integrità", - "integrity_checks_untracked_files": "File non tracciati", - "integrity_checks_untracked_files_description": "Configurare la frequenza e attivare/disattivare il controllo dei file non tracciati", - "integrity_checks_untracked_files_enable_description": "Abilita il controllo dei file non tracciati", "job_concurrency": "Concorrenza {job}", "job_created": "Processo creato", - "job_not_concurrency_safe": "Questa operazione non è sicura dal punto di vista della concorrenza.", - "job_settings": "Impostazioni processi", + "job_not_concurrency_safe": "Questo processo non è eseguibile in maniera concorrente.", + "job_settings": "Impostazioni dei processi", "job_settings_description": "Gestisci la concorrenza dei processi", "jobs_delayed": "{jobCount, plural, one {# posticipato} other {# posticipati}}", - "jobs_failed": "{jobCount, plural, one {# non riuscito} other {# non riusciti}}", + "jobs_failed": "{jobCount, plural, one {# fallito} other {# falliti}}", "jobs_over_time": "Processi nel tempo", - "library_created": "Libreria creata: {library}", + "library_created": "Creata libreria: {library}", "library_deleted": "Libreria eliminata", "library_details": "Dettagli libreria", "library_folder_description": "Specifica una cartella da importare. Questa cartella, incluse le sottocartelle, verrà analizzata alla ricerca di immagini e video.", - "library_remove_exclusion_pattern_prompt": "Sei sicuro di voler rimuovere questo pattern di esclusione?", - "library_remove_folder_prompt": "Sei sicuro di voler rimuovere questa cartella di importazione?", + "library_remove_exclusion_pattern_prompt": "Vuoi davvero rimuovere questo pattern di esclusione?", + "library_remove_folder_prompt": "Vuoi davvero rimuovere questa cartella di importazione?", "library_scanning": "Scansione periodica", "library_scanning_description": "Configura la scansione periodica della libreria", "library_scanning_enable_description": "Attiva la scansione periodica della libreria", - "library_settings": "Libreria esterna", + "library_settings": "Libreria Esterna", "library_settings_description": "Gestisci le impostazioni della libreria esterna", "library_tasks_description": "Scansiona le librerie esterne per risorse nuove o modificate", "library_updated": "Libreria aggiornata", "library_watching_enable_description": "Osserva le librerie esterne per cambiamenti", "library_watching_settings": "Osserva librerie [SPERIMENTALE]", "library_watching_settings_description": "Osserva automaticamente i cambiamenti dei file", - "logging_enable_description": "Attiva registrazione", + "logging_enable_description": "Attiva il logging", "logging_level_description": "Quando attivato, che livello di log utilizzare.", - "logging_settings": "Registrazione", + "logging_settings": "Registro dei Log", "machine_learning_availability_checks": "Verifiche di disponibilità", "machine_learning_availability_checks_description": "Rileva automaticamente e usa i server di machine learning disponibili", "machine_learning_availability_checks_enabled": "Attiva verifiche di disponibilità", @@ -158,14 +150,14 @@ "machine_learning_availability_checks_timeout": "Timeout richiesta", "machine_learning_availability_checks_timeout_description": "Timeout (ms) per le verifiche di disponibilità", "machine_learning_clip_model": "Modello CLIP", - "machine_learning_clip_model_description": "Il nome di un modello CLIP elencato qui. Nota: è necessario eseguire nuovamente il processo 'Ricerca intelligente' per tutte le immagini dopo aver modificato un modello.", - "machine_learning_duplicate_detection": "Rilevamento duplicati", + "machine_learning_clip_model_description": "Il nome del modello CLIP mostrato qui. Nota che devi rieseguire il processo 'Ricerca Intelligente' per tutte le immagini al cambio del modello.", + "machine_learning_duplicate_detection": "Rilevamento Duplicati", "machine_learning_duplicate_detection_enabled": "Attiva rilevazione duplicati", "machine_learning_duplicate_detection_enabled_description": "Se disattivo, risorse perfettamente identiche saranno comunque deduplicate.", "machine_learning_duplicate_detection_setting_description": "Utilizza i CLIP embeddings per trovare possibili duplicati", "machine_learning_enabled": "Attiva machine learning", "machine_learning_enabled_description": "Se disabilitato, tutte le funzioni di ML saranno disabilitate ignorando le impostazioni sottostanti.", - "machine_learning_facial_recognition": "Riconoscimento facciale", + "machine_learning_facial_recognition": "Riconoscimento Facciale", "machine_learning_facial_recognition_description": "Rileva, riconosci e raggruppa volti nelle immagini", "machine_learning_facial_recognition_model": "Modello di riconoscimento facciale", "machine_learning_facial_recognition_model_description": "I modelli sono mostrati in ordine decrescente in base alla dimensione. I modelli piÚ grandi sono piÚ lenti e utilizzano piÚ memoria, perÃ˛ producono risultati migliori. Nota che devi ri-eseguire il processo di rilevamento facciale per tutte le immagini quando cambi il modello.", @@ -177,47 +169,31 @@ "machine_learning_max_recognition_distance_description": "La distanza massima tra due volti per essere considerati la stessa persona, che varia da 0 a 2. Abbassare questo valore puÃ˛ prevenire l'etichettatura di due persone come se fossero la stessa persona, mentre aumentarlo puÃ˛ prevenire l'etichettatura della stessa persona come se fossero due persone diverse. Nota che è piÚ facile unire due persone che separare una persona in due, quindi è preferibile mantenere una soglia piÚ bassa quando possibile.", "machine_learning_min_detection_score": "Punteggio minimo di rilevazione", "machine_learning_min_detection_score_description": "Punteggio di confidenza minimo per rilevare un volto, da 0 a 1. Valori piÚ bassi rileveranno piÚ volti, ma potrebbero generare risultati fasulli.", - "machine_learning_min_recognized_faces": "Numero minimo di volti riconosciuti", + "machine_learning_min_recognized_faces": "Minimo numero di volti rilevati", "machine_learning_min_recognized_faces_description": "Il numero minimo di volti riconosciuti per creare una persona. Aumentando questo valore si rende il riconoscimento facciale piÚ preciso, ma aumenta la possibilità che un volto non venga assegnato a una persona.", "machine_learning_ocr": "OCR", "machine_learning_ocr_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", "machine_learning_ocr_enabled": "Attiva OCR", "machine_learning_ocr_enabled_description": "Se disattivato, le immagini non saranno sottoposte al riconoscimento del testo.", - "machine_learning_ocr_max_resolution": "Risoluzione massima", - "machine_learning_ocr_max_resolution_description": "Le anteprime che superano questa risoluzione verranno ridimensionate mantenendo le proporzioni. Valori piÚ alti offrono una maggiore precisione, ma richiedono tempi di elaborazione piÚ lunghi e un maggiore utilizzo di memoria.", + "machine_learning_ocr_max_resolution": "Massima risoluzione", + "machine_learning_ocr_max_resolution_description": "L'anteprima maggiore di questa risoluzione verrà ridimensionata preservando le proporzioni. Valori maggiori sono piÚ accurati, ma impiegano piÚ tempo per essere processati e usano piÚ memoria.", "machine_learning_ocr_min_detection_score": "Punteggio minimo di rilevamento", "machine_learning_ocr_min_detection_score_description": "Punteggio minimo di affidabilità per il rilevamento del testo da 0 a 1. Valori piÚ bassi rileveranno piÚ testo, ma potrebbero generare falsi positivi.", "machine_learning_ocr_min_recognition_score": "Punteggio minimo di riconoscimento", "machine_learning_ocr_min_score_recognition_description": "Punteggio minimo di affidabilità per il riconoscimento del testo da 0 a 1. Valori piÚ bassi rileveranno piÚ testo, ma potrebbero generare falsi positivi.", "machine_learning_ocr_model": "Modello OCR", "machine_learning_ocr_model_description": "I modelli server sono piÚ accurati dei modelli mobile, ma impiegano piÚ tempo nel processo e utilizzano piÚ memoria.", - "machine_learning_settings": "Impostazioni machine learning", + "machine_learning_settings": "Impostazioni Machine Learning", "machine_learning_settings_description": "Gestisci le impostazioni e le funzionalità del machine learning", - "machine_learning_smart_search": "Ricerca intelligente", + "machine_learning_smart_search": "Ricerca Intelligente", "machine_learning_smart_search_description": "Cerca immagini semanticamente utilizzato gli embedding CLIP", "machine_learning_smart_search_enabled": "Attiva ricerca intelligente", "machine_learning_smart_search_enabled_description": "Se disabilitato le immagini non saranno codificate per la ricerca intelligente.", "machine_learning_url_description": "URL del server machine learning. Se sono stati forniti piÚ di un URL, verrà testato un server alla volta finchÊ uno non risponderà, in ordine dal primo all'ultimo. I server che non rispondono saranno temporaneamente ignorati finchÊ non torneranno online.", - "maintenance_backup_management": "Gestione backup", - "maintenance_delete_backup": "Elimina backup", - "maintenance_delete_backup_description": "Questo file sarà eliminato in modo irreversibile.", - "maintenance_delete_error": "Impossibile eliminare il backup.", - "maintenance_integrity_check": "Verifica", - "maintenance_integrity_check_all": "Verifica Tutto", - "maintenance_integrity_checksum_mismatch": "I checksum non corrispondono", - "maintenance_integrity_checksum_mismatch_description": "File il cui checksum sul disco non corrisponde al checksum memorizzato da Immich nel database.", - "maintenance_integrity_checksum_mismatch_job": "Verifica corrispondenza dei checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Aggiorna report corrispondenze dei checksum", - "maintenance_integrity_missing_file": "File mancanti", - "maintenance_integrity_missing_file_description": "File che Immich ha registrato nel suo database ma che non esistono sul file system.", - "maintenance_integrity_missing_file_job": "Verifica file mancanti", - "maintenance_integrity_missing_file_refresh_job": "Aggiorna report sui file mancanti", - "maintenance_integrity_report": "Report di integrità", - "maintenance_integrity_untracked_file": "File non tracciati", - "maintenance_integrity_untracked_file_description": "File nelle directory di Immich di cui Immich non ha alcuna traccia.", - "maintenance_integrity_untracked_file_job": "Verifica presenza file non tracciati", - "maintenance_integrity_untracked_file_refresh_job": "Aggiorna il report dei file non tracciati", - "maintenance_restore_backup": "Ripristina backup", + "maintenance_delete_backup": "Elimina Backup", + "maintenance_delete_backup_description": "Questo file verrà eliminato irreversibilmente.", + "maintenance_delete_error": "Eliminazione del backup fallita.", + "maintenance_restore_backup": "Ripristina Backup", "maintenance_restore_backup_description": "Immich verrà cancellato e ripristinato dal backup scelto. Prima di procedere, verrà creato un backup.", "maintenance_restore_backup_different_version": "Questo backup è stato creato con un'altra versione di Immich!", "maintenance_restore_backup_unknown_version": "Impossibile determinare la versione del backup.", @@ -226,48 +202,48 @@ "maintenance_settings": "Manutenzione", "maintenance_settings_description": "Metti Immich in modalità manutenzione.", "maintenance_start": "Passa a modalità manutenzione", - "maintenance_start_error": "Impossibile avviare la modalità manutenzione.", + "maintenance_start_error": "Errore nell'avvio della modalità manutenzione.", "maintenance_upload_backup": "Carica file di backup del database", "maintenance_upload_backup_error": "Impossibile caricare il backup, è un file .sql/.sql.gz?", - "manage_concurrency": "Gestisci concorrenza", - "manage_concurrency_description": "Vai alla pagina dei processi per gestire la concorrenza dei processi", + "manage_concurrency": "Gestisci Concorrenza", + "manage_concurrency_description": "Vai alla pagina dei processi per gestire la concorrenza dei job", "manage_log_settings": "Gestisci le impostazioni dei log", "map_dark_style": "Tema scuro", "map_enable_description": "Abilita funzionalità della mappa", - "map_gps_settings": "Impostazioni mappa e GPS", - "map_gps_settings_description": "Gestisci impostazioni mappa e GPS (geocodifica inversa)", + "map_gps_settings": "Impostazioni Mappe & GPS", + "map_gps_settings_description": "Gestisci le impostazioni di Mappe & GPS (Geocoding Inverso)", "map_implications": "La funzionalità mappa si basa su un servizio tile esterno (tiles.immich.cloud)", "map_light_style": "Tema chiaro", "map_manage_reverse_geocoding_settings": "Gestisci impostazioni Geocodifica inversa", "map_reverse_geocoding": "Geocodifica inversa", "map_reverse_geocoding_enable_description": "Abilita geocodifica inversa", - "map_reverse_geocoding_settings": "Impostazioni geocodifica inversa", + "map_reverse_geocoding_settings": "Impostazioni Geocodifica Inversa", "map_settings": "Mappa", "map_settings_description": "Gestisci impostazioni mappa", "map_style_description": "URL per un tema della mappa style.json", "memory_cleanup_job": "Pulizia dei vecchi Ricordi", "memory_generate_job": "Generazione dei Ricordi", - "metadata_extraction_job": "Estrazione metadati", + "metadata_extraction_job": "Estrazione Metadati", "metadata_extraction_job_description": "Estrai informazioni dai metadati di ciascuna risorsa, come coordinate GPS, volti e risoluzione", "metadata_faces_import_setting": "Abilita l'importazione dei volti", "metadata_faces_import_setting_description": "Importa i volti dai dati EXIF dell'immagine e dai file sidecar", - "metadata_settings": "Impostazioni metadati", + "metadata_settings": "Impostazioni Metadati", "metadata_settings_description": "Gestisci le impostazioni dei metadati", "migration_job": "Migrazione", - "migration_job_description": "Migra le miniature per le risorse e i volti alla struttura di cartelle piÚ recente", + "migration_job_description": "Migra le anteprime per le risorse e i volti alla struttura di cartelle piÚ recente", "nightly_tasks_cluster_faces_setting_description": "Avvia riconoscimento facciale sui volti appena rilevati", "nightly_tasks_cluster_new_faces_setting": "Raggruppa nuovi volti", "nightly_tasks_database_cleanup_setting": "Processi di pulizia del database", "nightly_tasks_database_cleanup_setting_description": "Ripulisci il database da file vecchi e scaduti", - "nightly_tasks_generate_memories_setting": "Genera ricordi", + "nightly_tasks_generate_memories_setting": "Genera Ricordi", "nightly_tasks_generate_memories_setting_description": "Genera nuovi Ricordi a partire dalle risorse", - "nightly_tasks_missing_thumbnails_setting": "Genera miniature mancanti", - "nightly_tasks_missing_thumbnails_setting_description": "Metti in coda le risorse senza miniatura per la generazione delle miniature", - "nightly_tasks_settings": "Impostazioni attività notturne", + "nightly_tasks_missing_thumbnails_setting": "Genera anteprime mancanti", + "nightly_tasks_missing_thumbnails_setting_description": "Metti in coda le risorse senza miniatura per la generazione delle anteprime", + "nightly_tasks_settings": "Impostazioni delle attività notturne", "nightly_tasks_settings_description": "Gestisci attività notturne", "nightly_tasks_start_time_setting": "Orario di avvio", "nightly_tasks_start_time_setting_description": "L'orario in cui il server fa partire le attività notturne", - "nightly_tasks_sync_quota_usage_setting": "Sincronizza quota di utilizzo", + "nightly_tasks_sync_quota_usage_setting": "Sincronizza la quota di utilizzo", "nightly_tasks_sync_quota_usage_setting_description": "Aggiorna la quota di spazio dell'utente in base all'utilizzo corrente", "no_paths_added": "Nessun percorso aggiunto", "no_pattern_added": "Nessun pattern aggiunto", @@ -286,75 +262,69 @@ "notification_email_setting_description": "Impostazioni per le notifiche via email", "notification_email_test_email": "Invia email di prova", "notification_email_test_email_failed": "Impossibile inviare email di prova, controlla i valori inseriti", - "notification_email_test_email_sent": "Un'email di prova è stata inviata a {email}. Controlla la tua casella di posta.", + "notification_email_test_email_sent": "Un'email di prova è stata inviata a {email}. Per favore controlla la tua casella di posta.", "notification_email_username_description": "Nome utente utilizzato per l'autenticazione con il server email", "notification_enable_email_notifications": "Attiva notifiche via email", - "notification_settings": "Impostazioni notifiche", + "notification_settings": "Impostazioni Notifiche", "notification_settings_description": "Gestisci le impostazioni di notifica, incluse le email", - "oauth_allow_insecure_requests": "Consenti richieste non sicure", - "oauth_allow_insecure_requests_description": "ATTENZIONE: Questo disabilita la validazione del certificato TLS per richieste OAuth e potrebbe esporti ad attacchi MITM.", "oauth_auto_launch": "Avvio automatico", "oauth_auto_launch_description": "Esegui il flusso di autenticazione OAuth automaticamente navigando sulla pagina di login", "oauth_auto_register": "Registrazione automatica", - "oauth_auto_register_description": "Registra automaticamente i nuovi utenti dopo l'accesso con OAuth", + "oauth_auto_register_description": "Automaticamente registra nuovi utenti dopo il login OAuth", "oauth_button_text": "Testo pulsante", "oauth_client_secret_description": "Richiesto per client confidenziali o se PKCE (Proof Key for Code Exchange) non è supportato dal client pubblico.", "oauth_enable_description": "Login con OAuth", - "oauth_end_session_url_description": "Reindirizza l'utente a questo URI quando si disconnettono.", - "oauth_mobile_redirect_uri": "URI di reindirizzamento mobile", - "oauth_mobile_redirect_uri_override": "Sostituzione URI di reindirizzamento mobile", - "oauth_mobile_redirect_uri_override_description": "Abilita quando il provider OAuth non consente un URI mobile, come ''{callback}''", - "oauth_prompt_description": "Parametri del prompt (es. select_account, login, consent)", + "oauth_mobile_redirect_uri": "URI di reindirizzamento per app mobile", + "oauth_mobile_redirect_uri_override": "Sovrascrivi URI di reindirizzamento per app mobile", + "oauth_mobile_redirect_uri_override_description": "Abilita quando il gestore OAuth non consente un URL come ''{callback}''", "oauth_role_claim": "Claim del ruolo", "oauth_role_claim_description": "Concedi automaticamente l'accesso come amministratore in base alla presenza di questo claim. Il claim puÃ˛ essere 'user' o 'admin'.", "oauth_settings": "OAuth", "oauth_settings_description": "Gestisci impostazioni di login OAuth", - "oauth_settings_more_details": "Per maggiori informazioni su questa funzionalità, consulta la documentazione.", - "oauth_storage_label_claim": "Claim dell'etichetta di archiviazione", - "oauth_storage_label_claim_description": "Imposta automaticamente l'etichetta di archiviazione dell'utente sul valore di questo claim.", - "oauth_storage_quota_claim": "Claim della quota di archiviazione", - "oauth_storage_quota_claim_description": "Imposta automaticamente la quota di archiviazione dell'utente sul valore di questo claim.", + "oauth_settings_more_details": "Per maggiori informazioni su questa funzionalità, consulta la documentazione.", + "oauth_storage_label_claim": "Dichiarazione di ambito(claim) etichetta archiviazione", + "oauth_storage_label_claim_description": "Imposta automaticamente l'etichetta dell'archiviazione dell'utente al valore di questa dichiarazione di ambito(claim).", + "oauth_storage_quota_claim": "Dichiarazione di ambito(claim) limite archiviazione", + "oauth_storage_quota_claim_description": "Imposta automaticamente il limite di archiviazione dell'utente in base al valore di questa dichiarazione di ambito(claim).", "oauth_storage_quota_default": "Limite predefinito di archiviazione (GiB)", - "oauth_storage_quota_default_description": "Quota in GiB da utilizzare quando non viene fornito alcun claim.", - "oauth_timeout": "Timeout della richiesta", - "oauth_timeout_description": "Timeout delle richieste in millisecondi", + "oauth_storage_quota_default_description": "Limite in GiB da usare quanto nessuna dichiarazione di ambito(claim) è stata fornita.", + "oauth_timeout": "Timeout Richiesta", + "oauth_timeout_description": "Timeout per le richieste, espresso in millisecondi", "ocr_job_description": "Utilizza il machine learning per riconoscere il testo nelle immagini", "password_enable_description": "Login con email e password", "password_settings": "Login con password", "password_settings_description": "Gestisci impostazioni del login con password", "paths_validated_successfully": "Percorsi validati con successo", - "person_cleanup_job": "Pulizia persone", - "queue_details": "Dettagli della coda", + "person_cleanup_job": "Pulizia Persona", + "queue_details": "Dettagli della Coda", "queues": "Code dei processi", - "queues_page_description": "Pagina delle code dei processi amministrativi", - "quota_size_gib": "Dimensione della quota (GiB)", + "queues_page_description": "Pagina delle code dei job (admin)", + "quota_size_gib": "Dimensione Archiviazione (GiB)", "refreshing_all_libraries": "Aggiornando tutte le librerie", "registration": "Registrazione amministratore", - "registration_description": "PoichÊ sei il primo utente del sistema, sarai assegnato come Amministratore e sarai responsabile delle attività amministrative, e gli utenti aggiuntivi saranno creati da te.", - "release_channel_release_candidate": "Anteprima finale", - "release_channel_stable": "Stabile", - "remove_failed_jobs": "Rimuovi processi non riusciti", + "registration_description": "PoichÊ sei il primo utente del sistema, sarai assegnato come Amministratore e sarai responsabile dei task amministrativi, e utenti aggiuntivi saranno creati da te.", + "remove_failed_jobs": "Rimuovi i job falliti", "require_password_change_on_login": "Richiedi all'utente di cambiare password al primo accesso", "reset_settings_to_default": "Ripristina impostazioni predefinite", "reset_settings_to_recent_saved": "Ripristina impostazioni alle impostazioni salvate di recente", "scanning_library": "Scansione della libreria", - "search_jobs": "Cerca attivitàâ€Ļ", + "search_jobs": "Cerca Attivitàâ€Ļ", "send_welcome_email": "Invia email di benvenuto", "server_external_domain_settings": "Dominio esterno", "server_external_domain_settings_description": "Dominio utilizzato per i link esterni", - "server_public_users": "Utenti pubblici", + "server_public_users": "Utenti Pubblici", "server_public_users_description": "Tutti gli utenti (nome ed e-mail) sono elencati quando si aggiunge un utente agli album condivisi. Quando disabilitato, l'elenco degli utenti sarà disponibile solo per gli utenti amministratori.", - "server_settings": "Impostazioni server", + "server_settings": "Impostazioni Server", "server_settings_description": "Gestisci le impostazioni del server", - "server_stats_page_description": "Pagina delle statistiche del server amministratore", + "server_stats_page_description": "Pagina delle statistiche del server (admin)", "server_welcome_message": "Messaggio di benvenuto", "server_welcome_message_description": "Un messaggio mostrato sulla pagina di login.", - "settings_page_description": "Pagina delle impostazioni amministratore", + "settings_page_description": "Pagina delle impostazioni (admin)", "sidecar_job": "Metadati sidecar", "sidecar_job_description": "Scopri o sincronizza metadati sidecar dal filesystem", "slideshow_duration_description": "Numero di secondi per cui mostrare ciascuna immagine", "smart_search_job_description": "Esegui il machine learning sulle risorse per permettere la ricerca intelligente", - "storage_template_date_time_description": "La data e l'ora di creazione della risorsa vengono utilizzate per le informazioni cronologiche", + "storage_template_date_time_description": "Data e ora di creazione del media vengono usate come data e ora dello stesso", "storage_template_date_time_sample": "Esempio di data {date}", "storage_template_enable_description": "Attiva il motore del modello di archiviazione", "storage_template_hash_verification_enabled": "Verifica hash abilitata", @@ -362,44 +332,44 @@ "storage_template_migration": "Migrazione modello archiviazione", "storage_template_migration_description": "Applica il {template} attuale alle risorse caricate in precedenza", "storage_template_migration_info": "Le modifiche al modello di archiviazione verranno applicate solo alle nuove risorse. Per applicare le modifiche retroattivamente esegui {job}.", - "storage_template_migration_job": "Processo di migrazione del modello di archiviazione", - "storage_template_more_details": "Per maggiori dettagli su questa funzionalità, consulta il Modello di archiviazione e le sue implicazioni", + "storage_template_migration_job": "Processo di migrazione del Modello di Archiviazione", + "storage_template_more_details": "Per maggiori informazioni riguardo a questa funzionalità, consulta il Modello di Archiviazione e le sue conseguenze", "storage_template_onboarding_description_v2": "Se attiva, questa funzionalità organizzerà automaticamente i file utilizzando un modello definito dall'utente. Per maggiori informazioni, consultare la documentazione.", "storage_template_path_length": "Limite approssimativo lunghezza percorso: {length, number}/{limit, number}", - "storage_template_settings": "Modello di archiviazione", + "storage_template_settings": "Modello di Archiviazione", "storage_template_settings_description": "Gestisci la struttura delle cartelle e il nome degli asset caricati", "storage_template_user_label": "{label} è l'etichetta di archiviazione dell'utente", "system_settings": "Impostazioni di sistema", - "tag_cleanup_job": "Pulizia tag", + "tag_cleanup_job": "Pulisci Tag", "template_email_available_tags": "Puoi usare le seguenti variabili nel tuo modello: {tags}", "template_email_if_empty": "Se il modello è vuoto, verrà usata l'email di default.", "template_email_invite_album": "Modello di invito all'album", "template_email_preview": "Anteprima", - "template_email_settings": "Modelli email", - "template_email_update_album": "Aggiorna modello album", + "template_email_settings": "Template Email", + "template_email_update_album": "Aggiorna template dell'album", "template_email_welcome": "Modello di email di benvenuto", - "template_settings": "Modelli di notifica", + "template_settings": "Templates Notifiche", "template_settings_description": "Gestisci i modelli personalizzati per le notifiche", - "theme_custom_css_settings": "CSS personalizzato", - "theme_custom_css_settings_description": "I fogli di stile CSS consentono di personalizzare la grafica di Immich.", - "theme_settings": "Impostazioni tema", + "theme_custom_css_settings": "CSS Personalizzato", + "theme_custom_css_settings_description": "I fogli di stile CSS consentono di personalizzare il design di Immich.", + "theme_settings": "Impostazioni Tema", "theme_settings_description": "Gestisci la personalizzazione dell'interfaccia web di Immich", - "thumbnail_generation_job": "Genera miniature", - "thumbnail_generation_job_description": "Genera miniature grandi, piccole e sfocate per ogni risorsa, oltre a miniature per ogni persona", + "thumbnail_generation_job": "Genera Anteprime", + "thumbnail_generation_job_description": "Genera anteprime grandi, piccole e sfocate per ogni asset, oltre a miniature per ogni persona", "transcoding_acceleration_api": "API di accelerazione", - "transcoding_acceleration_api_description": "L'API che interagirà con il dispositivo per accelerare la transcodifica. Questa impostazione è di tipo 'best effort': in caso di errore, passerà alla transcodifica software. Il supporto a VP9 potrebbe non funzionare a seconda dell'hardware in uso.", + "transcoding_acceleration_api_description": "L'API che interagirà con il tuo dispositivo per accelerare la transcodifica. Questa impostazione è \"best effort\": ripiegherà sulla transcodifica software in caso di fallimento. VP9 potrebbe funzionare o meno a seconda del tuo hardware.", "transcoding_acceleration_nvenc": "NVENC (richiede GPU NVIDIA)", "transcoding_acceleration_qsv": "Quick Sync (richiede CPU Intel di 7a gen o piÚ recente)", "transcoding_acceleration_rkmpp": "RKMPP (Solo per SOC Rockchip)", "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "Codec audio accettati", - "transcoding_accepted_audio_codecs_description": "Seleziona quali codec audio non devono essere transcodificati. Utilizzato solo per determinate politiche di transcodifica.", + "transcoding_accepted_audio_codecs": "Codifiche audio accettate", + "transcoding_accepted_audio_codecs_description": "Seleziona quali codifiche audio non devono essere transcodificate. Solo usato per alcune politiche di transcodifica.", "transcoding_accepted_containers": "Contenitori accettati", "transcoding_accepted_containers_description": "Seleziona quali formati non hanno bisogno di essere remuxati in MP4. Usato solo per certe politiche di transcodifica.", - "transcoding_accepted_video_codecs": "Codec video accettati", - "transcoding_accepted_video_codecs_description": "Seleziona quali codec video non devono essere transcodificati. Utilizzato solo per determinate politiche di transcodifica.", + "transcoding_accepted_video_codecs": "Codifiche video accettate", + "transcoding_accepted_video_codecs_description": "Seleziona quali codifiche video non devono essere transcodificate. Usato solo per alcune politiche di transcodifica.", "transcoding_advanced_options_description": "Impostazioni che la maggior parte degli utenti non dovrebbero cambiare", - "transcoding_audio_codec": "Codec audio", + "transcoding_audio_codec": "Codifica Audio", "transcoding_audio_codec_description": "Opus è l'opzione con la qualità piÚ alta, ma è meno compatibile con dispositivi o software vecchi.", "transcoding_bitrate_description": "Video con bitrate superiore al massimo o in formato non accettato", "transcoding_codecs_learn_more": "Per maggiori informazioni sulla terminologia qui utilizzata, fare riferimento alla documentazione di FFmpeg per codec H.264, codec HEVC e codec VP9.", @@ -409,13 +379,13 @@ "transcoding_constant_rate_factor_description": "Livello di qualità video. I valori tipici sono 23 per H.264, 28 per HEVC, 31 per VP9 e 35 per AV1. Un valore inferiore indica una qualità migliore, ma produce file di dimensioni maggiori.", "transcoding_disabled_description": "Non transcodificare alcun video, potrebbe rompere la riproduzione su alcuni client", "transcoding_encoding_options": "Opzioni di codifica", - "transcoding_encoding_options_description": "Imposta codec, risoluzione, qualità ed altre opzioni per i video codificati", - "transcoding_hardware_acceleration": "Accelerazione hardware", - "transcoding_hardware_acceleration_description": "Sperimentale: transcodifica piÚ rapida, ma potrebbe ridurre la qualità a parità di bitrate", + "transcoding_encoding_options_description": "Imposta codecs, risoluzione, qualità ed altre opzioni per i video codificati", + "transcoding_hardware_acceleration": "Accelerazione Hardware", + "transcoding_hardware_acceleration_description": "Sperimentale: codifica piÚ veloce ma con qualità inferiore a parità bitrate", "transcoding_hardware_decoding": "Decodifica hardware", "transcoding_hardware_decoding_setting_description": "Abilita l'accelerazione end-to-end anzichÊ accelerare solo la codifica. Potrebbe non funzionare su tutti i video.", - "transcoding_max_b_frames": "B-frames massimi", - "transcoding_max_b_frames_description": "Valori piÚ alti migliorano l'efficienza di compressione, ma rallentano la codifica. Potrebbe non essere compatibile con l'accelerazione hardware sui dispositivi piÚ vecchi. 0 disabilita i B-frame, mentre -1 imposta questo valore automaticamente.", + "transcoding_max_b_frames": "B-frames Massimi", + "transcoding_max_b_frames_description": "Valori piÚ alti migliorano l'efficienza di compressione, ma rallentano l'encoding. Potrebbero non essere compatibili con l'accelerazione hardware su dispositivi piÚ vecchi. 0 disabilita i B-frames, mentre -1 imposta questo valore automaticamente.", "transcoding_max_bitrate": "Bitrate massimo", "transcoding_max_bitrate_description": "Impostare un bitrate massimo puÃ˛ rendere le dimensioni dei file piÚ prevedibili a un costo minore per la qualità. A 720p, i valori tipici sono 2600 kbit/s per VP9 o HEVC, o 4500 kbit/s per H.264. Disabilitato se impostato su 0. Quando non viene specificata alcuna unità, si presume k (per kbit/s); pertanto 5000, 5000k e 5M (per Mbit/s) sono equivalenti.", "transcoding_max_keyframe_interval": "Intervallo massimo dei keyframe", @@ -426,19 +396,11 @@ "transcoding_preferred_hardware_device": "Dispositivo hardware preferito", "transcoding_preferred_hardware_device_description": "Si applica solo a VAAPI e QSV. Imposta il nodo DRI utilizzato per la transcodifica hardware.", "transcoding_preset_preset": "Preimpostazione (-preset)", - "transcoding_preset_preset_description": "Velocità di compressione. Preset piÚ lenti producono file piÚ piccoli e aumentano la qualità quando viene impostato un certo bitrate. VP9 ignora velocità superiori a 'faster'.", - "transcoding_realtime": "Transcodifica in tempo reale [SPERIMENTALE]", - "transcoding_realtime_description": "Consenti l'esecuzione della transcodifica in tempo reale, durante la riproduzione del video. Abilita la possibilità di selezionare la qualità del video, ma potrebbe causare una latenza di riproduzione piÚ alta e scatti a seconda della potenza di calcolo del server.", - "transcoding_realtime_enabled": "Abilita la transcodifica in tempo reale", - "transcoding_realtime_enabled_description": "Se disabilitato, il server non avvierà nuove sessioni di transcodifica in tempo reale.", - "transcoding_realtime_resolutions": "Risoluzioni", - "transcoding_realtime_resolutions_description": "Le risoluzioni offerte per la transcodifica in tempo reale. Risoluzioni piÚ elevate possono causare problemi di riproduzione se il server non riesce a transcodificarle abbastanza rapidamente.", - "transcoding_realtime_video_codecs": "Codec video", - "transcoding_realtime_video_codecs_description": "I codec video disponibili per la transcodifica in tempo reale. I client sceglieranno l'opzione migliore tra quelle supportate durante la riproduzione. L'AV1 è piÚ efficiente dell'HEVC, che a sua volta è piÚ efficiente dell'H.264. Quando si utilizza l'accelerazione hardware, selezionare solo i codec che l'acceleratore è in grado di codificare. Quando si utilizza la transcodifica software, tenere presente che l'H.264 è piÚ veloce dell'AV1, che a sua volta è piÚ veloce dell'HEVC.", + "transcoding_preset_preset_description": "Velocità di compressione. Preset piÚ lenti producono file piÚ piccoli e aumentano la qualità quando viene impostato un certo bitrate. VP9 ignora velocità superiori a `faster`.", "transcoding_reference_frames": "Frame di riferimento", "transcoding_reference_frames_description": "Il numero di frame da prendere in considerazione nel comprimere un determinato frame. Valori piÚ alti migliorano l'efficienza di compressione, ma rallentano la codifica. 0 imposta questo valore automaticamente.", "transcoding_required_description": "Solo video che non sono in un formato accettato", - "transcoding_settings": "Impostazioni transcodifica video", + "transcoding_settings": "Impostazioni Transcodifica Video", "transcoding_settings_description": "Gestisci quali video transcodificare e come processarli", "transcoding_target_resolution": "Risoluzione desiderata", "transcoding_target_resolution_description": "Risoluzioni piÚ elevate possono preservare piÚ dettagli ma richiedono piÚ tempo per la codifica, producono file di dimensioni maggiori e possono ridurre la reattività dell'applicazione.", @@ -454,7 +416,7 @@ "transcoding_two_pass_encoding_setting_description": "Transcodifica in due passaggi per produrre video codificati migliori. Quando il bitrate massimo è abilitato (necessario affinchÊ funzioni con H.264 e HEVC), questa modalità utilizza un intervallo di bitrate basato sul bitrate massimo e ignora CRF. Per VP9, CRF puÃ˛ essere utilizzato se il bitrate massimo è disabilitato.", "transcoding_video_codec": "Codec video", "transcoding_video_codec_description": "VP9 ha alta efficienza e compatibilità web, ma richiede piÚ tempo per la transcodifica. HEVC ha prestazioni simili, ma una minore compatibilità web. H.264 è ampiamente compatibile e veloce da transcodificare, ma produce file molto piÚ grandi. AV1 è il codec piÚ efficiente, ma non è supportato sui dispositivi piÚ vecchi.", - "trash_enabled_description": "Abilita funzionalità cestino", + "trash_enabled_description": "Abilita Funzionalità Cestino", "trash_number_of_days": "Numero di giorni", "trash_number_of_days_description": "Numero di giorni per cui mantenere gli asset nel cestino prima di rimuoverli definitivamente", "trash_settings": "Impostazioni cestino", @@ -462,38 +424,38 @@ "unlink_all_oauth_accounts": "Disconnetti tutti gli account OAuth", "unlink_all_oauth_accounts_description": "Ricorda di scollegare tutti gli account OAuth prima di passare a un nuovo provider.", "unlink_all_oauth_accounts_prompt": "Sei sicuro di voler scollegare tutti gli account OAuth? Questa operazione reimposterà l’ID OAuth per ogni utente e non potrà essere annullata.", - "user_cleanup_job": "Pulizia utente", - "user_delete_delay": "L'account e le risorse dell'utente {user} verranno programmati per l'eliminazione definitiva tra {delay, plural, one {# giorno} other {# giorni}}.", + "user_cleanup_job": "Pulizia Utente", + "user_delete_delay": "L'account e gli asset dell'utente {user} verranno programmati per la cancellazione definitiva tra {delay, plural, one {# giorno} other {# giorni}}.", "user_delete_delay_settings": "Ritardo eliminazione", - "user_delete_delay_settings_description": "Numero di giorni dopo la rimozione per eliminare definitivamente l'account e le risorse di un utente. Il processo di eliminazione degli utenti viene eseguito a mezzanotte per verificare quali utenti sono pronti per essere rimossi. Le modifiche a questa impostazione verranno applicate alla prossima esecuzione.", - "user_delete_immediately": "L'account e le risorse di {user} saranno messi in coda per l'eliminazione definitiva immediatamente.", - "user_delete_immediately_checkbox": "Metti in coda l'utente e le sue risorse per l'eliminazione immediata", - "user_details": "Dettagli utente", - "user_management": "Gestione utente", + "user_delete_delay_settings_description": "Numero di giorni dopo l'eliminazione per cancellare in modo definitivo l'account e gli asset di un utente. Il processo di cancellazione dell'utente viene eseguito a mezzanotte per verificare se esistono utenti pronti a essere eliminati. Le modifiche a questa impostazioni saranno prese in considerazione dalla prossima esecuzione.", + "user_delete_immediately": "L'account e tutti gli asset dell'utente {user} verranno messi in coda per la cancellazione permanente immediata.", + "user_delete_immediately_checkbox": "utente", + "user_details": "Dettagli Utente", + "user_management": "Gestione Utenti", "user_password_has_been_reset": "La password dell'utente è stata reimpostata:", "user_password_reset_description": "Per favore inserisci una password temporanea per l'utente e informalo che dovrà cambiare la password al prossimo login.", "user_restore_description": "L'account di {user} verrà ripristinato.", "user_restore_scheduled_removal": "Ripristina utente - rimozione programmata per il {date, date, long}", - "user_settings": "Impostazioni utente", + "user_settings": "Impostazione Utente", "user_settings_description": "Gestisci impostazioni utente", "user_successfully_removed": "L'utente {email} è stato rimosso con successo.", - "users_page_description": "Pagina degli utenti amministratore", - "version_check_channel": "Canale di rilascio", - "version_check_channel_description": "Seleziona il canale da cui desideri ricevere le notifiche sulle nuove versioni", + "users_page_description": "Pagina utenti (admin)", "version_check_enabled_description": "Abilita controllo della versione", "version_check_implications": "La funzione di controllo della versione fa uso di una comunicazione periodica con {server}", - "version_check_settings": "Controllo versione", + "version_check_settings": "Controllo Versione", "version_check_settings_description": "Abilita/disabilita la notifica per nuove versioni", "video_conversion_job": "Transcodifica video", "video_conversion_job_description": "Transcodifica video per maggiore compatibilità con browser e dispositivi" }, - "admin_email": "Email amministratore", - "admin_password": "Password amministratore", + "admin_email": "Email Amministratore", + "admin_password": "Password Amministratore", "administration": "Amministrazione", "advanced": "Avanzate", - "advanced_settings_clear_image_cache": "Cancella cache dell'immagine", + "advanced_settings_clear_image_cache": "Cancella la cache dell' immagine", "advanced_settings_clear_image_cache_error": "Impossibile cancellare la cache dell'immagine", "advanced_settings_clear_image_cache_success": "Cancellato/i con successo {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Usa questa opzione per filtrare i contenuti multimediali durante la sincronizzazione in base a criteri alternativi. Prova questa opzione solo se riscontri problemi con il rilevamento di tutti gli album da parte dell'app.", + "advanced_settings_enable_alternate_media_filter_title": "[SPERIMENTALE] Usa un filtro alternativo per la sincronizzazione degli album del dispositivo", "advanced_settings_log_level_title": "Livello log: {level}", "advanced_settings_prefer_remote_subtitle": "Alcuni dispositivi sono estremamente lenti a caricare le miniature da risorse locali. Attiva questa impostazione per caricare invece le immagini remote.", "advanced_settings_prefer_remote_title": "Preferisci immagini remote", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Header Proxy Personalizzato [SPERIMENTALE]", "advanced_settings_readonly_mode_subtitle": "Abilita la modalità di sola lettura in cui le foto possono essere solo visualizzate, mentre funzioni come la selezione di piÚ immagini, la condivisione, la trasmissione e l'eliminazione sono tutte disabilitate. Abilita/Disabilita la sola lettura tramite l'avatar dell'utente dalla schermata principale", "advanced_settings_readonly_mode_title": "Modalità di sola lettura", + "advanced_settings_self_signed_ssl_subtitle": "Salta la verifica dei certificati SSL del server. Richiesto con l'uso di certificati self-signed.", + "advanced_settings_self_signed_ssl_title": "Consenti certificati SSL self-signed [SPERIMENTALE]", "advanced_settings_sync_remote_deletions_subtitle": "Rimuovi o ripristina automaticamente un elemento su questo dispositivo quando l'azione è stata fatta via web", "advanced_settings_sync_remote_deletions_title": "Sincronizza le cancellazioni remote [SPERIMENTALE]", "advanced_settings_tile_subtitle": "Impostazioni avanzate dell'utente", @@ -513,21 +477,34 @@ "album_added": "Album aggiunto", "album_added_notification_setting_description": "Ricevi una notifica email quando sei aggiunto ad un album condiviso", "album_cover_updated": "Copertina dell'album aggiornata", - "album_delete_confirmation": "Sei sicuro di voler eliminare l'album {album}?", + "album_delete_confirmation": "Sei sicuro di voler cancellare l'album {album}?", "album_delete_confirmation_description": "Se l'album è condiviso gli altri utenti perderanno l'accesso.", "album_deleted": "Album eliminato", + "album_info_card_backup_album_excluded": "ESCLUSI", + "album_info_card_backup_album_included": "INCLUSI", "album_info_updated": "Informazioni dell'album aggiornate", - "album_name": "Nome album", - "album_options": "Opzioni album", - "album_remove_user": "Rimuovere l'utente?", - "album_remove_user_confirmation": "Sei sicuro di voler rimuovere {user}?", + "album_leave": "Abbandona l'album?", + "album_leave_confirmation": "Sei sicuro di voler abbandonare {album}?", + "album_name": "Nome Album", + "album_options": "Impostazioni Album", + "album_remove_user": "Rimuovi l'utente?", + "album_remove_user_confirmation": "Sicuro di voler rimuovere l'utente {user}?", "album_search_not_found": "Nessun album trovato corrispondente alla tua ricerca", + "album_selected": "Album selezionato", "album_share_no_users": "Sembra che tu abbia condiviso questo album con tutti gli utenti oppure non hai nessun utente con cui condividere.", - "album_summary": "Riepilogo album", + "album_summary": "Sommario Album", "album_updated": "Album aggiornato", "album_updated_setting_description": "Ricevi una notifica email quando un album condiviso ha nuovi media", "album_upload_assets": "Carica risorse dal tuo computer e aggiungile all'album", - "album_viewer_appbar_share_err_delete": "Impossibile eliminare l'album", + "album_user_left": "{album} abbandonato", + "album_user_removed": "Utente {user} rimosso", + "album_viewer_appbar_delete_confirm": "Sei sicuro di voler rimuovere questo album dal tuo account?", + "album_viewer_appbar_share_err_delete": "Non è stato possibile eliminare l'album", + "album_viewer_appbar_share_err_leave": "Non è stato possibile lasciare l'album", + "album_viewer_appbar_share_err_remove": "Ci sono problemi nella rimozione di risorse dall'album", + "album_viewer_appbar_share_err_title": "Non è stato possibile cambiare il titolo dell'album", + "album_viewer_appbar_share_leave": "Lascia album", + "album_viewer_appbar_share_to": "Condividi a", "album_viewer_page_share_add_users": "Aggiungi utenti", "album_with_link_access": "Permetti a chiunque possieda il link di visualizzare le foto e le persone dell'album.", "albums": "Album", @@ -536,48 +513,60 @@ "albums_default_sort_order_description": "Ordine iniziale delle risorse nei nuovi album.", "albums_feature_description": "Raggruppamento delle risorse che possono essere condivise con altri utenti.", "albums_on_device_count": "Album sul dispositivo ({count})", + "albums_selected": "{count,plural, one{# album selezionato} other {# album selezionati}}", "all": "Tutti", "all_albums": "Tutti gli album", "all_people": "Tutte le persone", "all_photos": "Tutte le foto", "all_videos": "Tutti i video", - "allow_dark_mode": "Consenti modalità scura", - "allow_public_user_to_download": "Consenti agli utenti pubblici di scaricare", - "allow_public_user_to_upload": "Consenti agli utenti pubblici di caricare", + "allow_dark_mode": "Permetti Tema Scuro", + "allow_edits": "Permetti modifiche", + "allow_public_user_to_download": "Permetti agli utenti pubblici di scaricare", + "allow_public_user_to_upload": "Permetti agli utenti pubblici di caricare", "allowed": "Consentito", "alt_text_qr_code": "Immagine QR", "always_keep": "Mantieni sempre", - "always_keep_photos_hint": "Libera spazio manterrà tutte le foto su questo dispositivo.", - "always_keep_videos_hint": "Libera spazio manterrà tutti i video su questo dispositivo.", + "always_keep_photos_hint": "Liberare spazio manterrà tutte le foto su questo dispositivo.", + "always_keep_videos_hint": "Libera Spazio mantiene tutti i video su questo dispositivo.", + "anti_clockwise": "Senso anti-orario", "api_key": "Chiave API", "api_key_description": "Questo valore verrà mostrato una sola volta. Assicurati di copiarlo prima di chiudere la finestra.", "api_key_empty": "Il nome della chiave API non dovrebbe essere vuoto", "api_keys": "Chiavi API", "app_architecture_variant": "Variante (Architettura)", "app_bar_signout_dialog_content": "Sei sicuro di volerti disconnettere?", + "app_bar_signout_dialog_ok": "Si", "app_bar_signout_dialog_title": "Disconnetti", "app_download_links": "Link per il download dell'app", - "app_settings": "Impostazioni applicazione", - "app_stores": "Store applicazione", - "app_update_available": "Aggiornamento app disponibile", + "app_settings": "Impostazioni Applicazione", + "app_stores": "App Store", + "app_update_available": "Aggiornamento App disponibile", "appears_in": "Compare in", "apply_count": "Applica ({count, number})", "archive": "Archivio", - "archive_action_prompt": "Aggiunti {count} all'archivio", + "archive_action_prompt": "Aggiunti {count} elementi all'Archivio", "archive_or_unarchive_photo": "Archivia o ripristina foto", - "archive_size": "Dimensione archivio", - "archive_size_description": "Imposta la dimensione dell'archivio per i download (in GiB)", + "archive_page_no_archived_assets": "Non è stato trovato nessuna risorsa archiviata", + "archive_page_title": "Archivio ({count})", + "archive_size": "Dimensioni Archivio", + "archive_size_description": "Imposta le dimensioni dell'archivio per i download (in GiB)", "archived": "Archiviati", - "archived_count": "{count, plural, one {# archiviato} other {# archiviati}}", + "archived_count": "{count, plural, other {Archiviati #}}", "are_these_the_same_person": "Sono la stessa persona?", "are_you_sure_to_do_this": "Sei sicuro di voler procedere?", + "array_field_not_fully_supported": "Insieme di campi richiedono una modifica manuale del JSON", + "asset_action_delete_err_read_only": "Non puoi eliminare risorse in sola lettura, azione ignorata", + "asset_action_share_err_offline": "Non è possibile recuperare le risorse offline, azione ignorata", "asset_added_to_album": "Aggiunto all'album", "asset_adding_to_album": "Inserimento nell'albumâ€Ļ", "asset_created": "Risorsa creata", - "asset_day_count": "{date}: {count, plural, one {# risorsa} other {# risorse}}", "asset_description_updated": "La descrizione della risorsa è stata aggiornata", - "asset_hashing": "Hashingâ€Ļ", + "asset_filename_is_offline": "La risorsa {filename} è offline", + "asset_has_unassigned_faces": "La risoesa ha dei volti non categorizzati", + "asset_hashing": "Hashing in corso â€Ļ", "asset_list_group_by_sub_title": "Raggruppa per", + "asset_list_layout_settings_dynamic_layout_title": "Layout dinamico", + "asset_list_layout_settings_group_automatically": "Automatico", "asset_list_layout_settings_group_by": "Raggruppa le risorse per", "asset_list_layout_settings_group_by_month_day": "Mese + giorno", "asset_list_layout_sub_title": "Layout", @@ -586,29 +575,38 @@ "asset_not_found_on_device_android": "Risorsa non trovata sul dispositivo", "asset_not_found_on_device_ios": "Risorsa non trovata sul dispositivo. Se stai usando iCloud, la risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud", "asset_not_found_on_icloud": "Risorsa non trovata su iCloud. La risorsa potrebbe essere inaccessibile a causa di un file errato salvato su iCloud", - "asset_offline": "Elemento offline", + "asset_offline": "Elemento Offline", "asset_offline_description": "Questa risorsa esterna non esiste piÚ sul disco. Contatta il tuo amministratore di Immich per assistenza.", + "asset_restored_successfully": "Risorsa ripristinata con successo", "asset_skipped": "Saltato", "asset_skipped_in_trash": "Nel cestino", + "asset_trashed": "Risorsa cestinata", "asset_troubleshoot": "Risoluzione dei problemi della risorsa", "asset_uploaded": "Caricata", "asset_uploading": "Caricamentoâ€Ļ", "asset_viewer_settings_subtitle": "Gestisci le impostazioni del visualizzatore della galleria", - "asset_viewer_settings_title": "Visualizzatore risorse", + "asset_viewer_settings_title": "Visualizzazione Risorse", "assets": "Risorse", + "assets_added_count": "{count, plural, one {# risorsa aggiunta} other {# risorse aggiunte}}", "assets_added_to_album_count": "{count, plural, one {# risorsa aggiunta} other {# risorse aggiunte}} all'album", - "assets_added_to_album_partial_count": "{successCount} su {totalCount} {totalCount, plural, one {risorsa aggiunta} other {risorse aggiunte}} all'album", - "assets_added_to_albums_count": "{assetTotal, plural, one {# risorsa aggiunta} other {# risorse aggiunte}} a {albumTotal, plural, one {# album} other {# album}}", + "assets_added_to_albums_count": "Aggiunto {assetTotal, plural, one {# risorsa} other {# risorse}} a {albumTotal, plural, one {# album} other {# album}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {La risorsa} other {Le risorse}} non possono essere aggiunte all'album", "assets_cannot_be_added_to_albums": "Non Ê stato possibile aggiungere {count, plural, one {la risorsa} other {le risorse}} a nessun album", "assets_count": "{count, plural, one {# risorsa} other {# risorse}}", + "assets_deleted_permanently": "{count} risorsa/e cancellate definitivamente", + "assets_deleted_permanently_from_server": "{count} risorsa/e cancellate definitivamente sul server Immich", + "assets_downloaded_failed": "{count, plural, one {Scaricato # file - {error} file non riuscito} other {Scaricati # file - {error} file non riusciti}}", + "assets_downloaded_successfully": "{count, plural, one {Scaricato # file con successo} other {Scaricati # file con successo}}", "assets_moved_to_trash_count": "{count, plural, one {# risorsa spostata} other {# risorse spostate}} nel cestino", - "assets_permanently_deleted_count": "{count, plural, one {# risorsa eliminata} other {# risorse eliminate}} definitivamente", + "assets_permanently_deleted_count": "{count, plural, one {# risorsa cancellata} other {# risorse cancellate}} definitivamente", "assets_removed_count": "{count, plural, one {# risorsa rimossa} other {# risorse rimosse}}", - "assets_restore_confirmation": "Sei sicuro di voler ripristinare tutti le risorse cestinate? Non puoi annullare questa azione! Tieni presente che eventuali risorse offline non potranno essere ripristinate in questo modo.", + "assets_removed_permanently_from_device": "{count} risorsa/e cancellate definitivamente sul tuo dispositivo", + "assets_restore_confirmation": "Sei sicuro di voler ripristinare tutti le risorse cancellate? Non puoi annullare questa azione! Tieni presente che eventuali risorse offline non potranno essere ripristinate in questo modo.", "assets_restored_count": "{count, plural, one {# risorsa ripristinata} other {# risorse ripristinate}}", + "assets_restored_successfully": "{count} risorsa/e ripristinati", "assets_trashed": "{count} risorsa/e cestinati", "assets_trashed_count": "{count, plural, one {Spostato # risorsa} other {Spostate # risorse}} nel cestino", + "assets_trashed_from_server": "{count} risorsa/e cestinate sul server Immich", "assets_were_part_of_album_count": "{count, plural, one {La risorsa fa} other {Le risorse facevano}} già parte dell'album", "assets_were_part_of_albums_count": "{count, plural, one {La risorsa fa} other {Le risorse facevano}} già parte degli album", "authorized_devices": "Dispositivi autorizzati", @@ -617,55 +615,108 @@ "autoplay_slideshow": "Avvio automatico presentazione", "back": "Indietro", "back_close_deselect": "Indietro, chiudi o deseleziona", - "background_location_permission": "Autorizzazione di localizzazione in background", + "background_backup_running_error": "Il backup in background è attualmente in esecuzione, impossibile avviare il backup manuale", + "background_location_permission": "Permesso di localizzazione in background", "background_location_permission_content": "Per fare in modo che sia possibile cambiare rete quando è in esecuzione in background, Immich deve *sempre* avere accesso alla tua posizione precisa in modo da poter leggere il nome della rete Wi-Fi", - "background_options": "Opzioni in background", + "background_options": "Opzioni sfondo", "backup": "Backup", - "backup_album_selection_page_albums_tap": "Tocca per includere, doppio tocco per escludere", + "backup_album_selection_page_albums_device": "Album sul dispositivo ({count})", + "backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere", "backup_album_selection_page_assets_scatter": "Visto che le risorse possono trovarsi in piÚ album, questi possono essere inclusi o esclusi dal backup.", - "backup_album_selection_page_select_albums": "Seleziona album", + "backup_album_selection_page_select_albums": "Seleziona gli album", "backup_album_selection_page_selection_info": "Informazioni sulla selezione", - "backup_albums_sync": "Sincronizzazione album di backup", + "backup_album_selection_page_total_assets": "Numero totale delle risorse", + "backup_albums_sync": "Sincronizzazione Album di Backup", + "backup_all": "Tutti", + "backup_background_service_backup_failed_message": "Impossibile effettuare il backup delle risorse. Riprovoâ€Ļ", "backup_background_service_complete_notification": "Backup completato", + "backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovoâ€Ļ", + "backup_background_service_current_upload_notification": "Caricamento di {filename} in corso", "backup_background_service_default_notification": "Ricerca di nuove risorseâ€Ļ", + "backup_background_service_error_title": "Errore di backup", "backup_background_service_in_progress_notification": "Backup delle tue risorseâ€Ļ", + "backup_background_service_upload_failure_notification": "Impossibile caricare {filename}", "backup_controller_page_albums": "Backup Album", + "backup_controller_page_background_app_refresh_disabled_content": "Attiva l'aggiornamento dell'app in background in Impostazioni > Generale > Aggiorna app in background per utilizzare backup in background.", + "backup_controller_page_background_app_refresh_disabled_title": "Aggiornamento dell'app in background disattivo", + "backup_controller_page_background_app_refresh_enable_button_text": "Vai alle impostazioni", "backup_controller_page_background_battery_info_link": "Mostrami come", "backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimizzazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Ottimizzazioni batteria", + "backup_controller_page_background_charging": "Solo durante la ricarica", + "backup_controller_page_background_configure_error": "Impossibile configurare i servizi in background", "backup_controller_page_background_delay": "Ritarda il backup delle nuove risorse: {duration}", + "backup_controller_page_background_description": "Abilita il servizio in background per effettuare il backup delle nuove risorse senza la necessità di aprire l'app", + "backup_controller_page_background_is_off": "Backup automatico in background disattivato", + "backup_controller_page_background_is_on": "Backup automatico in background attivo", + "backup_controller_page_background_turn_off": "Disabilita servizi in background", + "backup_controller_page_background_turn_on": "Abilita servizi in background", + "backup_controller_page_background_wifi": "Solo con Wi-Fi", "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selezionati: ", "backup_controller_page_backup_sub": "Foto e video caricati", + "backup_controller_page_created": "Creato il: {date}", + "backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server all'apertura dell'applicazione.", "backup_controller_page_excluded": "Esclusi: ", + "backup_controller_page_failed": "Falliti: ({count})", + "backup_controller_page_filename": "Nome file: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informazioni sul backup", "backup_controller_page_none_selected": "Nessuna selezione", "backup_controller_page_remainder": "Rimanenti", "backup_controller_page_remainder_sub": "Foto e video che devono essere ancora caricati", "backup_controller_page_server_storage": "Spazio sul server", + "backup_controller_page_start_backup": "Avvia backup", + "backup_controller_page_status_off": "Backup è disattivato", + "backup_controller_page_status_on": "Backup è attivato", "backup_controller_page_storage_format": "{used} di {total} usati", - "backup_controller_page_to_backup": "Album da includere nel backup", + "backup_controller_page_to_backup": "Album da caricare", "backup_controller_page_total_sub": "Tutte le foto e i video unici caricati dagli album selezionati", + "backup_controller_page_turn_off": "Disattiva backup", + "backup_controller_page_turn_on": "Attiva backup", + "backup_controller_page_uploading_file_info": "Caricamento informazioni file", + "backup_err_only_album": "Non è possibile rimuovere l'unico album", "backup_error_sync_failed": "Sincronizzazione non riuscita. Impossibile elaborare il backup.", "backup_info_card_assets": "risorse", - "backup_options": "Opzioni di backup", + "backup_manual_cancelled": "Annullato", + "backup_manual_in_progress": "Caricamento già in corso. Riprova piÚ tardi", + "backup_manual_success": "Successo", + "backup_manual_title": "Stato del caricamento", + "backup_options": "Opzioni di Backup", + "backup_options_page_title": "Opzioni di Backup", + "backup_setting_subtitle": "Gestisci le impostazioni di upload in primo piano e in background", "backup_settings_subtitle": "Gestisci le impostazioni di caricamento", + "backup_upload_details_page_more_details": "Tocca per maggiori informazioni", "backward": "Indietro", - "battery_optimization_backup_reliability": "Disabilitare le ottimizzazioni della batteria puÃ˛ migliorare l'affidabilità del backup in background", "biometric_auth_enabled": "Autenticazione biometrica attivata", "biometric_locked_out": "Sei stato bloccato dall'autenticazione biometrica", "biometric_no_options": "Nessuna opzione biometrica disponibile", "biometric_not_available": "L'autenticazione biometrica non è disponibile su questo dispositivo", + "birthdate_saved": "Data di nascita salvata con successo", "birthdate_set_description": "La data di nascita è usata per calcolare l'età di questa persona al momento dello scatto della foto.", "blurred_background": "Sfondo sfocato", - "browse_templates": "Sfoglia i modelli", - "bugs_and_feature_requests": "Bug e richieste di funzionalità", - "build": "Versione", - "build_image": "Build dell'immagine", - "bulk_delete_duplicates_confirmation": "Sei sicuro di voler eliminare in massa {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Verrà conservata la risorsa piÚ grande di ogni gruppo e tutti gli altri duplicati saranno eliminati definitivamente. Non potrai annullare questa azione!", - "bulk_keep_duplicates_confirmation": "Sei sicuro di voler conservare {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questo risolverà tutti i gruppi di duplicati senza eliminare nulla.", - "bulk_trash_duplicates_confirmation": "Sei sicuro di voler spostare nel cestino {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Verrà conservato la risorsa piÚ grande di ogni gruppo e tutti gli altri duplicati saranno spostati nel cestino.", + "bugs_and_feature_requests": "Bug & Richieste di nuove funzionalità", + "build": "Compilazione", + "build_image": "Immagine Compilata", + "bulk_delete_duplicates_confirmation": "Sei sicuro di voler cancellare {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione manterrà la risorsa piÚ grande di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati. Non puoi annullare questa operazione!", + "bulk_keep_duplicates_confirmation": "Sei sicuro di voler tenere {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione risolverà tutti i gruppi duplicati senza cancellare nulla.", + "bulk_trash_duplicates_confirmation": "Sei davvero sicuro di voler cancellare {count, plural, one {# risorsa duplicata} other {# risorse duplicate}}? Questa operazione manterrà la risorsa piÚ grande di ogni gruppo e cancellerà permanentemente tutti gli altri duplicati.", "buy": "Acquista Immich", + "cache_settings_clear_cache_button": "Pulisci cache", + "cache_settings_clear_cache_button_title": "Pulisce la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.", + "cache_settings_duplicated_assets_clear_button": "PULISCI", + "cache_settings_duplicated_assets_subtitle": "Foto e video che sono nella black list dell'applicazione", + "cache_settings_duplicated_assets_title": "Risorse duplicate ({count})", + "cache_settings_statistics_album": "Anteprime librerie", + "cache_settings_statistics_full": "Immagini complete", + "cache_settings_statistics_shared": "Anteprime album condivisi", + "cache_settings_statistics_thumbnail": "Anteprime", + "cache_settings_statistics_title": "Uso della cache", + "cache_settings_subtitle": "Controlla il comportamento della cache dell'applicazione mobile immich", + "cache_settings_tile_subtitle": "Controlla il comportamento dello storage locale", + "cache_settings_tile_title": "Archiviazione locale", + "cache_settings_title": "Impostazioni della Cache", "camera": "Fotocamera", "camera_brand": "Marca fotocamera", "camera_model": "Modello fotocamera", @@ -678,30 +729,34 @@ "cannot_update_the_description": "Impossibile aggiornare la descrizione", "cast": "Trasmetti", "cast_description": "Configura le destinazioni di trasmissione disponibili", - "change": "Cambia", "change_date": "Modifica data", "change_description": "Modifica descrizione", - "change_display_order": "Modifica l'ordine di visualizzazione", + "change_display_order": "Cambia l'ordine di visualizzazione", + "change_expiration_time": "Modifica tempo di scadenza", "change_location": "Modifica posizione", "change_name": "Modifica nome", "change_name_successfully": "Nome modificato con successo", - "change_password": "Cambia password", + "change_password": "Modifica Password", "change_password_description": "È stato richiesto di cambiare la password (oppure è la prima volta che accedi). Inserisci la tua nuova password qui sotto.", - "change_password_form_confirm_password": "Conferma password", + "change_password_form_confirm_password": "Conferma Password", "change_password_form_description": "Ciao {name},\n\nQuesto è la prima volta che accedi al sistema oppure è stato fatto una richiesta di cambiare la password. Per favore inserisca la nuova password qui sotto.", - "change_password_form_log_out": "Disconnettiti da tutti gli altri dispositivi", - "change_password_form_log_out_description": "È consigliato disconnettersi da tutti gli altri dispositivi", - "change_password_form_new_password": "Nuova password", + "change_password_form_log_out": "Log out da tutti gli altri dispositivi", + "change_password_form_log_out_description": "È consigliato il log out da tutti gli altri dispositivi", + "change_password_form_new_password": "Nuova Password", "change_password_form_password_mismatch": "Le password non coincidono", - "change_password_form_reenter_new_password": "Conferma la nuova password", + "change_password_form_reenter_new_password": "Inserisci ancora la nuova password", "change_pin_code": "Cambia il codice PIN", + "change_trigger": "Cambia il trigger", + "change_trigger_prompt": "Sei sicuro di voler cambiare il trigger? Questo rimuoverà tutte le esistenti azioni e filtri.", "change_your_password": "Modifica la tua password", "changed_visibility_successfully": "Visibilità modificata con successo", "charging": "In carica", "charging_requirement_mobile_backup": "Il backup in background richiede che il dispositivo sia in carica", + "check_corrupt_asset_backup": "Verifica la presenza di backup di risorse corrotte", + "check_corrupt_asset_backup_button": "Effettua controllo", + "check_corrupt_asset_backup_description": "Effettua questo controllo solo su rete Wi-Fi e solo quando tutte le risorse saranno state sottoposte a backup. La procedura potrebbe impiegare qualche minuto.", "check_logs": "Controlla i log", "checksum": "Checksum", - "choose": "Scegli", "choose_matching_people_to_merge": "Scegli persone combacianti da unire", "city": "Città", "cleanup_confirm_description": "Immich ha trovato {count} risorse (create prima del {date}) e già salvate sul server. Rimuovo le copie locali da questo dispositivo?", @@ -710,8 +765,8 @@ "cleanup_deleting": "Spostamento nel cestino...", "cleanup_found_assets": "Trovate {count} risorse già salvate", "cleanup_found_assets_with_size": "Trovate {count} risorse salvate ({size})", - "cleanup_icloud_shared_albums_excluded": "Gli album condivisi di iCloud sono esclusi dalla ricerca", - "cleanup_no_assets_found": "Nessuna risorsa trovata con i criteri specificati. Libera spazio puÃ˛ solo rimuovere le risorse che sono state salvate sul server", + "cleanup_icloud_shared_albums_excluded": "Gli Album Condivisi di iCloud sono esclusi dalla ricerca", + "cleanup_no_assets_found": "Nessuna risorsa trovata con i criteri specificati. Libera Spazio puÃ˛ solo rimuovere le risorse che sono state salvate sul server", "cleanup_preview_title": "Risorse da rimuovere ({count})", "cleanup_step3_description": "Ricerca risorse già salvate sul server corrispondenti alle opzioni di ricerca.", "cleanup_step4_summary": "{count} risorse (create prima del {date}) da rimuovere sul tuo dispositivo. Rimarrano comunque accessibili dall'app Immich.", @@ -719,10 +774,11 @@ "clear": "Pulisci", "clear_all": "Pulisci tutto", "clear_all_recent_searches": "Rimuovi tutte le ricerche recenti", - "clear_failed_count": "Cancella non riusciti ({count})", "clear_file_cache": "Cancella la cache dei file", "clear_message": "Pulisci messaggio", "clear_value": "Pulisci valore", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Inserire la password", "client_cert_import": "Importa", "client_cert_import_success_msg": "Certificato client importato", "client_cert_invalid_msg": "File certificato invalido o password errata", @@ -730,11 +786,13 @@ "client_cert_password_title": "Password del certificato", "client_cert_remove_msg": "Certificato client rimosso", "client_cert_subtitle": "Supporta solo il formato PKCS12 (.p12, .pfx). L'importazione/rimozione del certificato è disponibile solo prima del login", - "client_cert_title": "Certificato client SSL [SPERIMENTALE]", + "client_cert_title": "Certificato Client SSL [SPERIMENTALE]", + "clockwise": "Senso orario", "close": "Chiudi", - "collapse": "Comprimi", + "collapse": "Restringi", "collapse_all": "Comprimi tutto", "color": "Colore", + "color_theme": "Colore Tema", "command": "Comando", "command_palette_prompt": "Trova rapidamente pagine, azioni o comandi", "command_palette_to_close": "per chiudere", @@ -742,15 +800,14 @@ "command_palette_to_select": "per selezionare", "command_palette_to_show_all": "per mostrare tutto", "comment_deleted": "Commento eliminato", - "comment_options": "Opzioni commenti", - "comments_and_likes": "Commenti e mi piace", + "comment_options": "Opzioni per i commenti", + "comments_and_likes": "Commenti & mi piace", "comments_are_disabled": "I commenti sono disabilitati", - "common_create_new_album": "Crea nuovo album", + "common_create_new_album": "Crea nuovo Album", "completed": "Completato", - "configuration": "Configurazione", "confirm": "Conferma", "confirm_admin_password": "Conferma password dell'amministratore", - "confirm_delete_face": "Sei sicuro di voler eliminare il volto di {name} dalla risorsa?", + "confirm_delete_face": "Sei sicuro di voler cancellare il volto di {name} dalla risorsa?", "confirm_delete_shared_link": "Sei sicuro di voler eliminare questo link condiviso?", "confirm_keep_this_delete_others": "Tutti le altre risorse nello stack saranno eliminate, eccetto questa. Sei sicuro di voler continuare?", "confirm_new_pin_code": "Conferma il nuovo codice PIN", @@ -758,20 +815,23 @@ "confirm_tag_face": "Vuoi taggare questo volto come {name}?", "confirm_tag_face_unnamed": "Vuoi taggare questo volto?", "connected_device": "Dispositivo connesso", - "connected_to": "Connesso a", + "connected_to": "Connesso", "contain": "Adatta alla finestra", "context": "Contesto", "continue": "Continua", - "control_bottom_app_bar_add_tags": "Aggiungi tag", + "control_bottom_app_bar_create_new_album": "Crea nuovo album", + "control_bottom_app_bar_delete_from_immich": "Elimina da Immich", "control_bottom_app_bar_delete_from_local": "Elimina dal dispositivo", "control_bottom_app_bar_edit_location": "Modifica posizione", "control_bottom_app_bar_edit_time": "Modifica data e ora", + "control_bottom_app_bar_share_link": "Condividi Link", + "control_bottom_app_bar_share_to": "Condividi a", "control_bottom_app_bar_trash_from_immich": "Sposta nel cestino", "copied_image_to_clipboard": "Immagine copiata negli appunti.", "copied_to_clipboard": "Copiato negli appunti!", "copy_error": "Errore nella copia", + "copy_file_path": "Copia percorso file", "copy_image": "Copia immagine", - "copy_json": "Copia JSON", "copy_link": "Copia link", "copy_link_to_clipboard": "Copia link negli appunti", "copy_password": "Copia password", @@ -783,70 +843,77 @@ "create_album": "Crea album", "create_album_page_untitled": "Senza titolo", "create_api_key": "Crea chiave API", - "create_first_workflow": "Crea la prima sequenza", + "create_first_workflow": "Crea il primo workflow", "create_library": "Crea libreria", "create_link": "Crea link", "create_link_to_share": "Crea link da condividere", "create_link_to_share_description": "Permetti a chiunque con il link di vedere le foto selezionate", "create_new": "CREA NUOVO", + "create_new_face": "Crea nuova faccia", "create_new_person": "Crea nuova persona", "create_new_person_hint": "Assegna le risorse selezionate a una nuova persona", "create_new_user": "Crea nuovo utente", "create_person": "Crea persona", - "create_person_subtitle": "Aggiungi un nome al volto selezionato per creare e taggare la nuova persona", + "create_person_subtitle": "Aggiungi un nome alla faccia selezionata per creare e taggare la nuova persona", "create_shared_album_page_share_add_assets": "AGGIUNGI RISORSE", "create_shared_album_page_share_select_photos": "Seleziona foto", "create_shared_link": "Crea link condiviso", "create_tag": "Crea tag", "create_tag_description": "Crea un nuovo tag. Per i tag nidificati, inserisci il percorso completo del tag includendo le barre oblique (/).", "create_user": "Crea utente", - "create_workflow": "Crea sequenza", + "create_workflow": "Crea il workflow", "created": "Creato", "created_at": "Creato il", "creating_linked_albums": "Creazione di album collegati...", "crop": "Ritaglia", + "crop_aspect_ratio_fixed": "Fisso", "crop_aspect_ratio_free": "Libero", "crop_aspect_ratio_original": "Originale", "crop_aspect_ratio_square": "Quadrato", + "curated_object_page_title": "Oggetti", "current_device": "Dispositivo attuale", - "current_pin_code": "Codice PIN attuale", + "current_pin_code": "Attuale codice PIN", "current_server_address": "Indirizzo del server in uso", - "custom_date": "Data personalizzata", + "custom_date": "Data specifica", "custom_locale": "Localizzazione personalizzata", - "custom_locale_description": "Formatta date, orari e numeri in base alla lingua e al paese selezionati", - "cutoff_date_description": "Conserva le foto fino alâ€Ļ", + "custom_locale_description": "Formatta date e numeri in base alla lingua e al paese selezionati", + "custom_url": "URL personalizzato", + "cutoff_date_description": "Mantieni le foto fino alâ€Ļ", "cutoff_day": "{count, plural, one {giorno} other {giorni}}", "cutoff_year": "{count, plural, one {anno} other {anni}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Scuro", "dark_theme": "Imposta tema scuro", "date": "Data", "date_after": "Dopo la data", "date_and_time": "Data e ora", "date_before": "Prima della data", - "date_of_birth": "Data di nascita", + "date_format": "E, d LLL, y â€ĸ hh:mm", "date_of_birth_saved": "Data di nascita salvata con successo", "date_range": "Intervallo di date", - "date_time_original": "Data originale", "day": "Giorno", "days": "Giorni", - "deduplicate_all": "Elimina tutti i duplicati", - "default_quality_subtitle": "Qualità utilizzata toccando il pulsante di condivisione. Tenere premuto il pulsante di condivisione per scegliere la qualità ogni volta.", - "default_share_quality": "Qualità di condivisione predefinita", + "deduplicate_all": "Elimina tutti i doppioni", + "default_locale": "Predefinito Locale", + "default_locale_description": "Formatta le date e i numeri sulla base del tuo browser locale", "delete": "Elimina", "delete_action_confirmation_message": "Vuoi davvero eliminare questa risorsa? Questa azione sposterà la risorsa nel cestino del server e ti chiederà se desideri eliminarla dal dispositivo", - "delete_action_prompt": "{count} eliminati", + "delete_action_prompt": "{count} elementi eliminati", "delete_album": "Elimina album", "delete_api_key_prompt": "Sei sicuro di voler eliminare questa chiave API?", - "delete_dialog_alert": "Questi oggetti saranno eliminati definitivamente da Immich e dal tuo dispositivo", + "delete_dialog_alert": "Questi oggetti saranno eliminati definitivamente da Immich e dal tuo device", "delete_dialog_alert_local": "Questi elementi verranno eliminati definitivamente dal dispositivo, ma saranno ancora disponibili sul server Immich", "delete_dialog_alert_local_non_backed_up": "Alcuni degli elementi non sono stati caricati su Immich e saranno rimossi definitivamente dal tuo dispositivo", + "delete_dialog_alert_remote": "Questi elementi verranno eliminati permanentemente dal server Immich", + "delete_dialog_ok_force": "Elimina comunque", "delete_dialog_title": "Elimina definitivamente", - "delete_duplicates_confirmation": "Sei sicuro di voler eliminare definitivamente questi duplicati?", - "delete_face": "Elimina volto", + "delete_duplicates_confirmation": "Sei sicuro di voler eliminare questi duplicati per sempre?", + "delete_face": "cancella faccia", "delete_key": "Elimina chiave", "delete_library": "Elimina libreria", "delete_link": "Elimina link", - "delete_local_action_prompt": "{count} eliminati in locale", + "delete_local_action_prompt": "{count} elementi rimossi in locale", "delete_local_dialog_ok_backed_up_only": "Elimina solo con backup", "delete_local_dialog_ok_force": "Elimina comunque", "delete_others": "Elimina gli altri", @@ -855,20 +922,25 @@ "delete_shared_link": "Elimina link condiviso", "delete_shared_link_dialog_title": "Elimina link condiviso", "delete_tag": "Elimina tag", - "delete_tag_confirmation_prompt": "Sei sicuro di voler eliminare il tag {tagName}?", + "delete_tag_confirmation_prompt": "Sei sicuro di voler cancellare il tag {tagName}?", "delete_user": "Elimina utente", - "deleted_shared_link": "Link condiviso eliminato", + "deleted_shared_link": "Elimina link condiviso", + "deletes_missing_assets": "Cancella le risorse mancanti dal disco", "description": "Descrizione", - "deselect_all": "Deseleziona tutto", + "description_input_hint_text": "Aggiungi descrizione...", + "description_input_submit_error": "Errore modificare descrizione, controlli I log per maggiori dettagli", + "deselect_all": "Deseleziona Tutto", "details": "Dettagli", "direction": "Direzione", "disable": "Disabilita", "disabled": "Disabilitato", + "disallow_edits": "Blocca modifiche", "discord": "Discord", "discover": "Scopri", "discovered_devices": "Dispositivi trovati", "dismiss_all_errors": "Ignora tutti gli errori", - "display_options": "Opzioni di visualizzazione", + "dismiss_error": "Ignora errore", + "display_options": "Impostazioni visualizzazione", "display_order": "Ordine di visualizzazione", "display_original_photos": "Visualizza foto originali", "display_original_photos_setting_description": "Visualizza la foto originale anzichÊ le miniature quando la risorsa originale è compatibile con il web. Questo potrebbe causare un ritardo nella visualizzazione delle foto.", @@ -876,27 +948,29 @@ "documentation": "Documentazione", "done": "Fatto", "download": "Scarica", + "download_action_prompt": "Sto scaricando {count} risorse", "download_canceled": "Download annullato", "download_complete": "Download completato", "download_enqueue": "Download in coda", - "download_failed": "Download non riuscito", + "download_error": "Errore durante il download", + "download_failed": "Download fallito", "download_finished": "Download terminato", "download_include_embedded_motion_videos": "Video incorporati", - "download_include_embedded_motion_videos_description": "Includi i video incorporati nelle foto in movimento come file separato", + "download_include_embedded_motion_videos_description": "Includere i video incorporati nelle foto in movimento come file separato", "download_notfound": "Download non trovato", - "download_original": "Scarica originale", + "download_original": "Scarica l'originale", "download_paused": "Download in pausa", "download_settings": "Scarica", "download_settings_description": "Gestisci le impostazioni relative al download delle risorse", + "download_started": "Download avviato", + "download_sucess": "Download completato", + "download_sucess_android": "I contenuti multimediali sono stati scaricati in DCIM/Immich", "download_waiting_to_retry": "In attesa di riprovare", "downloading": "Scaricamento", "downloading_asset_filename": "Sto scaricando la risorsa {filename}", "downloading_from_icloud": "Scaricamento da iCloud", "downloading_media": "Scaricamento file multimediali", - "drag_to_reorder": "Trascina per riordinare", "drop_files_to_upload": "Rilascia i file ovunque per caricarli", - "duplicate": "Duplica", - "duplicate_workflow": "Duplica sequenza", "duplicates": "Duplicati", "duplicates_description": "Risolvi ciascun gruppo indicando quali sono, se esistono, i duplicati.", "duration": "Durata", @@ -908,7 +982,9 @@ "edit_date_and_time": "Modifica data e ora", "edit_date_and_time_action_prompt": "{count} data e ora modificata", "edit_date_and_time_by_offset": "Modifica data per offset", - "edit_description": "Modifica descrizione", + "edit_date_and_time_by_offset_interval": "Nuovo intervallo di date: {from} - {to}", + "edit_description": "Modifica la descrizione", + "edit_description_prompt": "Selezionare una nuova descrizione:", "edit_exclusion_pattern": "Modifica pattern di esclusione", "edit_faces": "Modifica volti", "edit_key": "Modifica chiave", @@ -919,12 +995,15 @@ "edit_name": "Modifica nome", "edit_people": "Modifica persone", "edit_tag": "Modifica tag", - "edit_title": "Modifica titolo", + "edit_title": "Modifica Titolo", "edit_user": "Modifica utente", - "edit_workflow": "Modifica sequenza", + "edit_workflow": "Edita il workflow", "editor": "Editor", + "editor_close_without_save_prompt": "Le modifiche non verranno salvate", + "editor_close_without_save_title": "Vuoi chiudere l'editor?", + "editor_confirm_reset_all_changes": "Sicuro di voler resettare tutte le modifiche?", "editor_discard_edits_confirm": "Ignora modifiche", - "editor_discard_edits_prompt": "Hai delle modifiche non salvate. Sei sicuro di volerle ignorare?", + "editor_discard_edits_prompt": "Hai delle modifiche non salvate. Vuoi davvero eliminarle?", "editor_discard_edits_title": "Ignorare le modifiche?", "editor_edits_applied_error": "Impossibile applicare le modifiche", "editor_edits_applied_success": "Modifiche applicate con successo", @@ -938,28 +1017,30 @@ "editor_rotate_right": "Ruota di 90° orario", "email": "Email", "email_notifications": "Notifiche email", - "empty_folder": "Questa cartella è vuota", + "empty_folder": "La cartella è vuota", "empty_trash": "Svuota cestino", "empty_trash_confirmation": "Sei sicuro di volere svuotare il cestino? Questo rimuoverà tutte le risorse nel cestino in modo permanente da Immich.\nNon puoi annullare questa azione!", "enable": "Abilita", - "enable_backup": "Abilita backup", + "enable_backup": "Abilita Backup", "enable_biometric_auth_description": "Inserire il codice PIN per abilitare l'autenticazione biometrica", "enabled": "Abilitato", - "end_date": "Data fine", + "end_date": "Data Fine", "enqueued": "Accodato", "enter_wifi_name": "Inserisci il nome della rete Wi-Fi", "enter_your_pin_code": "Inserisci il tuo codice PIN", "enter_your_pin_code_subtitle": "Inserire il codice PIN per accedere alla cartella protetta", "error": "Errore", + "error_change_sort_album": "Errore nel cambiare l'ordine di degli album", "error_delete_face": "Errore nella rimozione del volto dalla risorsa", + "error_getting_places": "Errore durante il recupero dei luoghi", "error_loading_albums": "Errore nel caricamento degli album", "error_loading_image": "Errore nel caricamento dell'immagine", "error_loading_partners": "Errore durante il caricamento dei partner: {error}", - "error_retrieving_asset_information": "Errore nel recuperare informazioni sulla risorsa", + "error_retrieving_asset_information": "Errore nel recuperare informazioni sull'elemento", "error_saving_image": "Errore: {error}", "error_tag_face_bounding_box": "Errore durante il tag del volto - impossibile ricavare le coordinate del riquadro", "error_title": "Errore - Qualcosa è andato storto", - "error_while_navigating": "Errore durante la navigazione verso la risorsa", + "error_while_navigating": "Errore durante la navigazione verso l'elemento", "errors": { "cannot_navigate_next_asset": "Impossibile passare alla risorsa successiva", "cannot_navigate_previous_asset": "Impossibile passare alla risorsa precedente", @@ -972,34 +1053,33 @@ "cant_search_people": "Impossibile cercare persone", "cant_search_places": "Impossibile cercare luoghi", "error_adding_assets_to_album": "Errore nell'aggiunta di risorse all'album", - "error_adding_users_to_album": "Errore nell'aggiunta di utenti all'album", - "error_deleting_shared_user": "Errore durante l'eliminazione dell'utente condiviso", + "error_adding_users_to_album": "Errore aggiungendo gli utenti all'album", + "error_deleting_shared_user": "Errore durante la cancellazione dell'utente condiviso", "error_downloading": "Errore scaricando {filename}", "error_hiding_buy_button": "Errore nel nascondere il pulsante di acquisto", "error_removing_assets_from_album": "Errore nella rimozione di risorse dall'album, controlla la console per ulteriori dettagli", "error_selecting_all_assets": "Errore nella selezione di tutte le risorse", "exclusion_pattern_already_exists": "Questo pattern di esclusione è già presente.", - "failed_to_create_album": "Impossibile creare l'album", - "failed_to_create_shared_link": "Impossibile creare il link condivisibile", - "failed_to_edit_shared_link": "Impossibile modificare il link condivisibile", + "failed_to_create_album": "Creazione dell'album non riuscita", + "failed_to_create_shared_link": "Creazione del link condivisibile non riuscita", + "failed_to_edit_shared_link": "Errore durante la modifica del link condivisibile", "failed_to_get_people": "Impossibile ottenere le persone", "failed_to_keep_this_delete_others": "Impossibile conservare questa risorsa ed eliminare le altre", - "failed_to_load_asset": "Impossibile caricare la risorsa", - "failed_to_load_assets": "Impossibile caricare le risorse", - "failed_to_load_notifications": "Impossibile caricare le notifiche", - "failed_to_load_people": "Impossibile caricare le persone", - "failed_to_remove_product_key": "Impossibile rimuovere il product key", + "failed_to_load_asset": "Errore durante il caricamento della risorsa", + "failed_to_load_assets": "Errore durante il caricamento delle risorse", + "failed_to_load_notifications": "Errore nel caricamento delle notifiche", + "failed_to_load_people": "Caricamento delle persone non riuscito", + "failed_to_remove_product_key": "Rimozione del codice del prodotto fallita", "failed_to_reset_pin_code": "Impossibile reimpostare il codice PIN", - "failed_to_stack_assets": "Impossibile raggruppare le risorse", - "failed_to_tag_assets": "Impossibile assegnare tag alle risorse", - "failed_to_unstack_assets": "Impossibile separare le risorse", - "failed_to_update_notification_status": "Impossibile aggiornare lo stato delle notifiche", + "failed_to_stack_assets": "Errore durante il raggruppamento delle risorse", + "failed_to_unstack_assets": "Errore durante la separazione delle risorse", + "failed_to_update_notification_status": "Aggiornamento stato notifiche fallito", "incorrect_email_or_password": "Email o password non corretta", "library_folder_already_exists": "Questo path di importazione esiste già.", - "page_not_found": "Pagina non trovata", - "paths_validation_failed": "{paths, plural, one {La convalida di # percorso non è riuscita} other {La convalida di # percorsi non è riuscita}}", + "page_not_found": "Impossibile trovare la pagina", + "paths_validation_failed": "{paths, plural, one {# percorso} other {# percorsi}} hanno fallito la validazione", "profile_picture_transparent_pixels": "Le foto profilo non possono avere pixel trasparenti. Riprova ingrandendo e/o muovendo l'immagine.", - "quota_higher_than_disk_size": "Hai impostato una quota piÚ alta della dimensione del disco", + "quota_higher_than_disk_size": "Hai impostato un limite piÚ alto della dimensione del disco", "something_went_wrong": "Qualcosa è andato storto", "unable_to_add_album_users": "Impossibile aggiungere utenti all'album", "unable_to_add_assets_to_shared_link": "Impossibile aggiungere le risorse al link condiviso", @@ -1012,55 +1092,55 @@ "unable_to_change_album_user_role": "Impossibile modificare il ruolo dell'utente nell'album", "unable_to_change_date": "Impossibile modificare la data", "unable_to_change_description": "Impossibile modificare la descrizione", - "unable_to_change_favorite": "Impossibile modificare lo stato preferito della risorsa", + "unable_to_change_favorite": "Errore durante il cambio di stato preferito della risorsa", "unable_to_change_location": "Impossibile modificare posizione", "unable_to_change_password": "Impossibile modificare password", - "unable_to_change_visibility": "Impossibile modificare la visibilità per {count, plural, one {# persona} other {# persone}}", - "unable_to_complete_oauth_login": "Impossibile accedere tramite OAuth", + "unable_to_change_visibility": "Errore durante la modifica della visibilità per {count, plural, one {# persona} other {# persone}}", + "unable_to_complete_oauth_login": "Errore durante l'accesso tramite OAuth", "unable_to_connect": "Impossibile connettersi", "unable_to_copy_to_clipboard": "Impossibile copiare negli appunti, assicurati di aver aperto la pagina in https", - "unable_to_create": "Impossibile creare la sequenza", - "unable_to_create_admin_account": "Impossibile creare un account amministratore", + "unable_to_create": "Impossibile create il workflow", + "unable_to_create_admin_account": "Impossibile creare un account admin", "unable_to_create_api_key": "Impossibile creare una nuova chiave API", "unable_to_create_library": "Impossibile creare la libreria", - "unable_to_create_user": "Impossibile creare l'utente", - "unable_to_delete_album": "Impossibile eliminare l'album", - "unable_to_delete_asset": "Impossibile eliminare la risorsa", + "unable_to_create_user": "Impossibile creare utente", + "unable_to_delete_album": "Impossibile cancellare album", + "unable_to_delete_asset": "Impossibile cancellare la risorsa", "unable_to_delete_assets": "Errore durante l'eliminazione delle risorse", - "unable_to_delete_exclusion_pattern": "Impossibile eliminare il pattern di esclusione", - "unable_to_delete_shared_link": "Impossibile eliminare il link condiviso", - "unable_to_delete_user": "Impossibile eliminare l'utente", - "unable_to_delete_workflow": "Impossibile eliminare la sequenza", + "unable_to_delete_exclusion_pattern": "Impossibile cancellare pattern di esclusione", + "unable_to_delete_shared_link": "Impossibile cancellare link condiviso", + "unable_to_delete_user": "Impossibile cancellare utente", + "unable_to_delete_workflow": "Impossibile eleminare il workflow", "unable_to_download_files": "Impossibile scaricare i file", - "unable_to_edit_exclusion_pattern": "Impossibile modificare il pattern di esclusione", + "unable_to_edit_exclusion_pattern": "Impossibile modificare pattern di esclusione", "unable_to_empty_trash": "Impossibile svuotare il cestino", "unable_to_enter_fullscreen": "Impossibile aprire l'applicazione a schermo intero", "unable_to_exit_fullscreen": "Impossibile uscire dallo schermo intero", "unable_to_get_comments_number": "Impossibile ottenere il numero di commenti", - "unable_to_get_shared_link": "Impossibile ottenere il link condivisibile", - "unable_to_hide_person": "Impossibile nascondere la persona", + "unable_to_get_shared_link": "Impossibile ottenere il link condiviso", + "unable_to_hide_person": "Impossibile nascondere persona", "unable_to_link_motion_video": "Impossibile collegare video in movimento", "unable_to_link_oauth_account": "Impossibile collegare l'account OAuth", "unable_to_log_out_all_devices": "Impossibile eseguire il logout da tutti i dispositivi", "unable_to_log_out_device": "Impossibile eseguire il logout dal dispositivo", "unable_to_login_with_oauth": "Impossibile effettuare l'accesso tramite OAuth", "unable_to_play_video": "Impossibile riprodurre il video", - "unable_to_reassign_assets_existing_person": "Impossibile riassegnare le risorse a {name, select, null {una persona esistente} other {{name}}}", - "unable_to_reassign_assets_new_person": "Impossibile riassegnare le risorse ad una nuova persona", + "unable_to_reassign_assets_existing_person": "Errore durante la riassegnazione delle risorse a {name, select, null {una persona esistente} other {{name}}}", + "unable_to_reassign_assets_new_person": "Errore durante la riassegnazione delle risorse ad una nuova persona", "unable_to_refresh_user": "Impossibile aggiornare l'utente", "unable_to_remove_album_users": "Impossibile rimuovere gli utenti dall'album", "unable_to_remove_api_key": "Impossibile rimuovere la chiave API", - "unable_to_remove_assets_from_shared_link": "Impossibile rimuovere le risorse dal link condiviso", - "unable_to_remove_library": "Impossibile rimuovere la libreria", + "unable_to_remove_assets_from_shared_link": "Errore durante la rimozione delle risorse dal link condiviso", + "unable_to_remove_library": "Impossibile rimuovere libreria", "unable_to_remove_partner": "Impossibile rimuovere compagno", - "unable_to_remove_reaction": "Impossibile rimuovere la reazione", + "unable_to_remove_reaction": "Impossibile rimuovere reazione", "unable_to_reset_password": "Impossibile reimpostare la password", - "unable_to_reset_pin_code": "Impossibile reimpostare il codice PIN", + "unable_to_reset_pin_code": "Impossibile resettare il codice PIN", "unable_to_resolve_duplicate": "Impossibile risolvere duplicato", "unable_to_restore_assets": "Impossibile ripristinare le risorse", - "unable_to_restore_trash": "Impossibile ripristinare il cestino", - "unable_to_restore_user": "Impossibile ripristinare l'utente", - "unable_to_save_album": "Impossibile salvare l'album", + "unable_to_restore_trash": "Impossibile ripristinare cestino", + "unable_to_restore_user": "Impossibile ripristinare utente", + "unable_to_save_album": "Impossibile salvare album", "unable_to_save_api_key": "Impossibile salvare chiave API", "unable_to_save_date_of_birth": "Impossible salvare la data di nascita", "unable_to_save_name": "Impossibile salvare il nome", @@ -1075,25 +1155,33 @@ "unable_to_trash_asset": "Impossibile cestinare la risorsa", "unable_to_unlink_account": "Impossibile scollegare l'account", "unable_to_unlink_motion_video": "Impossibile scollegare video in movimento", - "unable_to_update_album_cover": "Impossibile aggiornare la copertina dell'album", + "unable_to_update_album_cover": "Errore durante l'aggiornamento della copertina dell'album", "unable_to_update_album_info": "Impossibile aggiornare le informazioni sull'album", "unable_to_update_library": "Impossibile aggiornare la libreria", "unable_to_update_location": "Impossibile aggiornare la posizione", "unable_to_update_settings": "Impossibile aggiornare le impostazioni", - "unable_to_update_timeline_display_status": "Impossibile aggiornare lo stato di visualizzazione della timeline", + "unable_to_update_timeline_display_status": "Impossibile aggiornare lo stato di visualizzazione della sequenza temporale", "unable_to_update_user": "Impossibile aggiornare l'utente", - "unable_to_update_workflow": "Impossibile aggiornare la sequenza", + "unable_to_update_workflow": "Impossibile aggiornare il workflow", "unable_to_upload_file": "Impossibile caricare il file" }, "errors_text": "Errori", "exclusion_pattern": "Pattern di esclusione", "exif": "Exif", - "exif_bottom_sheet_description": "Aggiungi descrizione...", + "exif_bottom_sheet_description": "Aggiungi una descrizione...", "exif_bottom_sheet_description_error": "Errore durante l'aggiornamento della descrizione", + "exif_bottom_sheet_details": "DETTAGLI", + "exif_bottom_sheet_location": "POSIZIONE", "exif_bottom_sheet_no_description": "Nessuna descrizione", + "exif_bottom_sheet_people": "PERSONE", + "exif_bottom_sheet_person_add_person": "Aggiungi nome", "exit_slideshow": "Esci dalla presentazione", "expand": "Espandi", "expand_all": "Espandi tutto", + "experimental_settings_new_asset_list_subtitle": "Lavori in corso", + "experimental_settings_new_asset_list_title": "Attiva griglia foto sperimentale", + "experimental_settings_subtitle": "Usalo a tuo rischio!", + "experimental_settings_title": "Sperimentale", "expire_after": "Scade dopo", "expired": "Scaduto", "expires_date": "Scade il {date}", @@ -1103,56 +1191,56 @@ "export_as_json": "Esporta come JSON", "export_database": "Esporta database", "export_database_description": "Esporta il database SQLite", - "exposure_time": "Tempo di esposizione", "extension": "Estensione", "external": "Esterno", "external_libraries": "Librerie esterne", "external_network": "Rete esterna", "external_network_sheet_info": "Quando non si è connessi alla rete Wi-Fi preferita, l'app si collegherà al server tramite il primo degli indirizzi della lista che riuscirà a raggiungere, dall'alto verso il basso", - "f_number": "Rapporto focale", "face_unassigned": "Non assegnata", - "failed": "Non riuscito", - "failed_count": "Non riusciti: {count}", - "failed_to_authenticate": "Impossibile autenticarsi", - "failed_to_delete_file": "Impossibile eliminare il file", + "failed": "Fallito", + "failed_count": "Falliti: {count}", + "failed_to_authenticate": "Autenticazione non riuscita", "failed_to_load_assets": "Impossibile caricare le risorse", "failed_to_load_folder": "Impossibile caricare la cartella", "favorite": "Preferito", "favorite_action_prompt": "{count} elementi aggiunti ai preferiti", "favorite_or_unfavorite_photo": "Aggiungi o rimuovi foto da preferiti", "favorites": "Preferiti", + "favorites_page_no_favorites": "Nessun preferito", "feature_photo_updated": "Foto in evidenza aggiornata", "features": "Funzionalità", + "features_in_development": "Funzionalità in fase di sviluppo", "features_setting_description": "Gestisci le funzionalità dell'app", "file_name_or_extension": "Nome file o estensione", "file_name_text": "Nome del file", + "file_name_with_value": "Nome del file: {file_name}", "file_size": "Dimensione del file", "filename": "Nome file", "filetype": "Tipo file", "filter": "Filtro", + "filter_description": "Condizioni per filtrare le risorse obiettivo", "filter_people": "Filtra persone", "filter_places": "Filtra luoghi", "filter_tags": "Filtra tag", "filters": "Filtri", + "find_them_fast": "Trovale velocemente con la ricerca", "first": "Primo", "fix_incorrect_match": "Correggi corrispondenza errata", - "focal_length": "Lunghezza focale", "folder": "Cartella", "folder_not_found": "Cartella non trovata", "folders": "Cartelle", "folders_feature_description": "Navigare la visualizzazione a cartelle per le foto e i video sul file system", "forgot_pin_code_question": "Hai dimenticato il tuo PIN?", "forward": "Avanti", - "free_up_space": "Libera spazio", + "free_up_space": "Libera Spazio", "free_up_space_description": "Sposta le foto e i video del tuo dispositivo nel cestino per liberare spazio. Le copie sul server rimarranno al sicuro.", "free_up_space_settings_subtitle": "Libera spazio sul dispositivo", "full_path": "Percorso completo: {path}", - "full_path_or_folder": "Percorso assoluto o cartella", - "gcast_enabled": "Google Cast", + "gcast_enabled": "Google Cast Abilitato", "gcast_enabled_description": "Questa funzione carica risorse esterne da Google per poter funzionare.", "general": "Generale", "geolocation_instruction_location": "Fai clic su una risorsa con coordinate GPS per utilizzare la sua posizione oppure seleziona una posizione direttamente dalla mappa", - "get_help": "Chiedi aiuto", + "get_help": "Chiedi Aiuto", "get_people_error": "Errore nel ritrovare le persone", "get_wifiname_error": "Non sono riuscito a recuperare il nome della rete Wi-Fi. Accertati di aver concesso i permessi necessari e di essere connesso ad una rete Wi-Fi", "getting_started": "Iniziamo", @@ -1165,9 +1253,10 @@ "group_albums_by": "Raggruppa album in base a...", "group_country": "Raggruppa per paese", "group_no": "Nessun raggruppamento", - "group_owner": "Raggruppa per proprietario", + "group_owner": "Raggruppa in base al proprietario", "group_places_by": "Raggruppa posti per...", "group_year": "Raggruppa per anno", + "haptic_feedback_switch": "Abilita feedback aptico", "haptic_feedback_title": "Feedback aptico", "has_quota": "Ha limite", "hash_asset": "Hash risorsa", @@ -1188,12 +1277,29 @@ "hide_schema": "Nascondi schema", "hide_text_recognition": "Nascondi riconoscimento del testo", "hide_unnamed_people": "Nascondi persone senza nome", - "home_page_building_timeline": "Generazione della timeline", + "home_page_add_to_album_conflicts": "Aggiunte {added} risorse all'album {album}. {failed} risorse erano già presenti nell'album.", + "home_page_add_to_album_err_local": "Non puoi aggiungere all'album risorse non ancora caricate, azione ignorata", + "home_page_add_to_album_success": "Aggiunte {added} risorse all'album {album}.", + "home_page_album_err_partner": "Non puoi ancora aggiungere risorse del partner a un album, azione ignorata", + "home_page_archive_err_local": "Non puoi archiviare risorse non ancora caricate, azione ignorata", + "home_page_archive_err_partner": "Non puoi archiviare risorse del partner, azione ignorata", + "home_page_building_timeline": "Caricamento della timeline", + "home_page_delete_err_partner": "Non puoi eliminare risorse del partner, azione ignorata", + "home_page_delete_remote_err_local": "Risorse locali presenti nella selezione della eliminazione remota, azione ignorata", + "home_page_favorite_err_local": "Non puoi aggiungere ai preferiti le risorse non ancora caricate, azione ignorata", + "home_page_favorite_err_partner": "Non puoi aggiungere le risorse del partner ai preferiti, azione ignorata", + "home_page_first_time_notice": "Se è la prima volta che utilizzi l'app, assicurati di scegliere uno o piÚ album di backup, in modo che la timeline possa popolare le foto e i video presenti negli album", + "home_page_locked_error_local": "Non puoi spostare le risorse locali nella cartella privata, azione ignorata", + "home_page_locked_error_partner": "Non puoi spostare le risorse del partner nella cartella privata, azione ignorata", + "home_page_share_err_local": "Non puoi condividere una risorsa locale tramite link, azione ignorata", + "home_page_upload_err_limit": "Puoi caricare al massimo 30 risorse per volta, azione ignorata", "host": "Host", "hour": "Ora", "hours": "Ore", "id": "ID", "idle": "Inattivo", + "ignore_icloud_photos": "Ignora foto iCloud", + "ignore_icloud_photos_description": "Le foto che sono memorizzate su iCloud non verranno caricate sul server Immich", "image": "Immagine", "image_alt_text_date": "{isVideo, select, true {Video girato} other {Foto scattata}} il {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video girato} other {Foto scattata}} con {person1} il giorno {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1} e {person2} il giorno {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1}, {person2}, e {person3} il giorno {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video girato} other {Foto scattata}} a {city}, {country} con {person1}, {person2} e {additionalCount, number} altre persone il {date}", + "image_saved_successfully": "Immagine salvata", + "image_viewer_page_state_provider_download_started": "Download iniziato", + "image_viewer_page_state_provider_download_success": "Download con successo", + "image_viewer_page_state_provider_share_error": "Errore di condivisione", "immich_logo": "Logo Immich", "immich_web_interface": "Interfaccia Web Immich", "import_from_json": "Importa da JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Condivisione individuale", "individual_shares": "Condivisioni individuali", "info": "Info", - "integrity_checks": "Verifiche di integrità", "interval": { "day_at_onepm": "Ogni giorno alle 13", "hours": "Ogni {hours, plural, one {ora} other {{hours, number} ore}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Formato data invalido", "invite_people": "Invita Persone", "invite_to_album": "Invita nell'album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Recupero dati eseguito {dateTime}", + "ios_debug_info_last_sync_at": "Ultima sincronizzazione {dateTime}", + "ios_debug_info_no_processes_queued": "Nessun processo in background in coda", + "ios_debug_info_no_sync_yet": "Nessun processo di sincronizzazione in background è stato ancora avviato", + "ios_debug_info_processes_queued": "{count, plural, one {{count} processo in background messo in coda} other {{count} processi in background messi in coda}}", + "ios_debug_info_processing_ran_at": "Processo eseguito {dateTime}", "items_count": "{count, plural, one {# elemento} other {# elementi}}", "jobs": "Processi", + "json_editor": "Modificatore JSON", + "json_error": "JSON errore", "keep": "Mantieni", "keep_albums": "Mantieni gli album", "keep_albums_count": "{count} {count, plural, one {Album} other {Album}} mantenuti", @@ -1259,12 +1375,14 @@ "leave": "Esci", "leave_album": "Esci dall’album", "lens_model": "Modello lenti", - "less": "Meno", "let_others_respond": "Permetti agli altri di rispondere", "level": "Livello", "library": "Libreria", "library_add_folder": "Aggiungi cartella", "library_edit_folder": "Modifica cartella", + "library_options": "Impostazioni Libreria", + "library_page_device_albums": "Album sul dispositivo", + "library_page_new_album": "Nuovo Album", "library_page_sort_asset_count": "Numero di risorse", "library_page_sort_created": "Data di creazione", "library_page_sort_last_modified": "Ultima modifica", @@ -1274,14 +1392,11 @@ "light_theme": "Cambia a tema chiaro", "like": "Mi piace", "like_deleted": "Mi piace rimosso", - "link": "Link", "link_motion_video": "Collega video in movimento", "link_to_docs": "Per maggiori informazioni, riferirsi al documentazione.", "link_to_oauth": "Collegamento a OAuth", "linked_oauth_account": "Account OAuth collegato", "list": "Lista", - "live": "Live", - "load_more": "Carica di piÚ", "loading": "Caricamento", "loading_search_results_failed": "Impossibile caricare i risultati della ricerca", "local": "Locale", @@ -1310,21 +1425,24 @@ "login": "Accesso", "login_disabled": "L'accesso è stato disattivato", "login_form_api_exception": "API error, per favore ricontrolli URL del server e riprovi.", + "login_form_back_button_text": "Indietro", "login_form_email_hint": "tuaemail@email.com", "login_form_endpoint_hint": "http://ip-del-tuo-server:port", "login_form_endpoint_url": "URL dell'Endpoint del Server", + "login_form_err_http": "Per favore specificare http:// o https://", "login_form_err_invalid_email": "Email non valida", "login_form_err_invalid_url": "URL non valido", "login_form_err_leading_whitespace": "Whitespace all'inizio", "login_form_err_trailing_whitespace": "Whitespace alla fine", "login_form_failed_get_oauth_server_config": "Errore di login usando OAuth, controlla l'URL del server", "login_form_failed_get_oauth_server_disable": "OAuth non è disponibile su questo server", - "login_form_failed_login": "Errore nel login, controlla l'URL del server e le credenziali (email e password)", + "login_form_failed_login": "Errore nel login, controlla URL del server e le credenziali (email e password)", "login_form_handshake_exception": "Si è verificata un'eccezione di handshake con il server. Abilita il supporto del certificato self-signed nelle impostazioni se si utilizza questo tipo di certificato.", "login_form_password_hint": "password", - "login_form_server_empty": "Inserisci URL del server", - "login_form_server_error": "Impossibile connettersi al server", - "login_has_been_disabled": "Il login è stato disabilitato", + "login_form_save_login": "Rimani connesso", + "login_form_server_empty": "Inserisci URL del server.", + "login_form_server_error": "Non è possibile connettersi al server.", + "login_has_been_disabled": "Il login è stato disabilitato.", "login_password_changed_error": "C'è stato un errore durante l'aggiornamento della password", "login_password_changed_success": "Password aggiornata con successo", "logout_all_device_confirmation": "Sei sicuro di volerti disconnettere da tutti i dispositivi?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Ripristinando Database", "maintenance_description": "Immich è stato posto in modalità manutenzione.", "maintenance_end": "Termina modalità manutenzione", + "maintenance_end_error": "Errore nel terminare la modalità manutenzione.", "maintenance_logged_in_as": "Accesso effettuato come {user}", "maintenance_restore_from_backup": "Ripristina da Backup", "maintenance_restore_library": "Ripristina la tua Libreria", @@ -1356,7 +1475,7 @@ "maintenance_task_backup": "Creando un backup del database esistenteâ€Ļ", "maintenance_task_migrations": "Esecuzione delle migrazioni del databaseâ€Ļ", "maintenance_task_restore": "Ripristinando il backup sceltoâ€Ļ", - "maintenance_task_rollback": "Ripristino non riuscito, ritorno al punto di ripristinoâ€Ļ", + "maintenance_task_rollback": "Ripristino fallito, tornando al punto di ripristinoâ€Ļ", "maintenance_title": "Temporaneamente non disponibile", "make": "Produttore", "manage_geolocation": "Gestisci posizione", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Apri impostazioni", "manage_media_access_subtitle": "Permetti all'app di Immich di gestire e spostare file multimediali.", "manage_media_access_title": "Accesso alla gestione di contenuti multimediali", + "manage_shared_links": "Gestisci link condivisi", "manage_sharing_with_partners": "Gestisci la condivisione con i compagni", "manage_the_app_settings": "Gestisci le impostazioni dell'applicazione", "manage_your_account": "Gestisci il tuo account", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Gestisci i tuoi dispositivi collegati", "manage_your_oauth_connection": "Gestisci la tua connessione OAuth", "map": "Mappa", + "map_assets_in_bounds": "{count, plural, =0 {Nessuna risorsa in quest’area} one {# risorsa} other {# risorse}}", "map_cannot_get_user_location": "Non è possibile ottenere la posizione dell'utente", + "map_location_dialog_yes": "Si", "map_location_picker_page_use_location": "Usa questa posizione", "map_location_service_disabled_content": "I servizi di geolocalizzazione devono essere attivati per poter visualizzare le risorse dalla tua posizione attuale. Vuoi attivarli adesso?", "map_location_service_disabled_title": "Servizio Localizzazione disattivato", - "map_marker_for_image": "Segnaposto nella mappa per immagini scattate in {city}, {country}", + "map_marker_for_images": "Indicatore mappa per le immagini scattate in {city}, {country}", "map_marker_with_image": "Segnaposto con immagine", "map_no_location_permission_content": "L'accesso alla posizione è necessario per visualizzare le risorse dalla tua posizione attuale. Vuoi consentirlo adesso?", "map_no_location_permission_title": "Autorizzazione Posizione negata", @@ -1385,49 +1507,20 @@ "map_settings_date_range_option_days": "Ultimi {days} giorni", "map_settings_date_range_option_year": "Ultimo anno", "map_settings_date_range_option_years": "Ultimi {years} anni", + "map_settings_dialog_title": "Impostazioni Mappa", "map_settings_include_show_archived": "Includi Archiviati", "map_settings_include_show_partners": "Includi Partner", "map_settings_only_show_favorites": "Mostra solo preferiti", "map_settings_theme_settings": "Tema della mappa", + "map_zoom_to_see_photos": "Riduci lo zoom per vedere le foto", "mark_all_as_read": "Segna tutto come letto", + "mark_as_read": "Segna come letto", "marked_all_as_read": "Segnato tutto come letto", "matches": "Corrispondenze", "matching_assets": "Risorse Corrispondenti", - "media_chrome": { - "auto": "Auto", - "captions": "Didascalie", - "captions_off": "Spento", - "closed_captions": "sottotitoli", - "decode_error": "Errore di decodifica", - "disable_captions": "Disattiva didascalie", - "enable_captions": "Attiva didascalie", - "enter_fullscreen_mode": "Passa alla modalità a schermo intero", - "exit_fullscreen_mode": "Esci dalla modalità a schermo intero", - "loop": "Ciclo", - "media_error_description": "Si è verificato un errore nel file multimediale che ha interrotto la riproduzione. Il file potrebbe essere danneggiato oppure il tuo browser non supporta questo formato.", - "media_loading": "Caricamento dei contenuti multimediali", - "mute": "Silenzioso", - "network_error": "Errore di rete", - "network_error_description": "A causa di un errore di rete, il download del file non è andato a buon fine.", - "not_supported_error": "Sorgente non supportata", - "playback_rate": "Velocità di riproduzione", - "playback_rate_current": "Velocità di riproduzione corrente", - "playback_rate_value": "Velocità di riproduzione {playbackRate}", - "playback_time": "durata della riproduzione", - "quality": "Qualità", - "second": "secondo", - "seconds": "Secondi", - "time_value_of_total_time": "{currentTime} di {totalTime}", - "time_value_remaining": "{time} rimanente", - "unmute": "Riattiva l'audio", - "unsupported_error_description": "Si è verificato un errore non supportato. Si è verificato un errore a livello di server o di rete, oppure il browser in uso non supporta questo formato.", - "video_not_loaded_unknown_time": "video non caricato, tempo sconosciuto.", - "video_player": "lettore video", - "volume": "volume" - }, "media_type": "Tipo Media", "memories": "Ricordi", - "memories_all_caught_up": "Niente di nuovo", + "memories_all_caught_up": "Tutto a posto", "memories_check_back_tomorrow": "Torna domani per altri ricordi", "memories_setting_description": "Gestisci cosa vedi nei tuoi ricordi", "memories_start_over": "Ricomincia", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Vuoi unire queste persone? Questa azione è irreversibile.", "merge_people_successfully": "Unione persone completata con successo", "merged_people_count": "{count, plural, one {Unita # persona} other {Unite # persone}}", - "minFaces": "Minimo numero di volti", - "minFaces_description": "Numero minimo di volti rilevati per identificare una persona", "minimize": "Minimizza", "minute": "Minuto", "minutes": "Minuti", + "mirror_horizontal": "Orizzontale", + "mirror_vertical": "Verticale", "missing": "Mancanti", "mobile_app": "App Cellulare", "mobile_app_download_onboarding_note": "Scarica l’app mobile dedicata utilizzando una delle seguenti opzioni", "model": "Modello", - "modify_date": "Data di modifica", "month": "Mese", + "monthly_title_text_date_format": "MMMM y", "more": "Di piÚ", - "motion": "Movimento", "move": "Sposta", + "move_down": "Muovi in basso", "move_off_locked_folder": "Sposta al di fuori della cartella privata", "move_to": "Sposta in", "move_to_device_trash": "Sposta nel cestino del dispositivo", "move_to_lock_folder_action_prompt": "{count} elementi aggiunti alla cartella sicura", "move_to_locked_folder": "Sposta nella cartella privata", "move_to_locked_folder_confirmation": "Queste foto e video verranno rimossi da tutti gli album, e saranno visibili solo dalla cartella privata", + "move_up": "Muovi in alto", + "moved_to_archive": "{count, plural, one {Spostata # risorsa} other {Spostate # risorse}} nell'archivio", + "moved_to_library": "{count, plural, one {Spostata # risorsa} other {Spostate # risorse}} nella libreria", "moved_to_trash": "Spostato nel cestino", + "multiselect_grid_edit_date_time_err_read_only": "Non puoi modificare la data di risorse in sola lettura, azione ignorata", + "multiselect_grid_edit_gps_err_read_only": "Non puoi modificare la posizione di risorse in sola lettura, azione ignorata", "mute_memories": "Silenzia ricordi", "my_albums": "I miei album", - "my_immich_description": "Copia la pagina corrente come un link My Immich", - "my_immich_title": "Link My Immich", "name": "Nome", "name_or_nickname": "Nome o soprannome", "name_required": "Nome è richiesto", @@ -1480,11 +1576,12 @@ "never": "Mai", "new_album": "Nuovo Album", "new_api_key": "Nuova Chiave di API", - "new_feature": "Nuova funzionalità", + "new_date_range": "Nuovo intervallo di date", "new_password": "Nuova password", "new_person": "Nuova persona", "new_pin_code": "Nuovo codice PIN", "new_pin_code_subtitle": "Questa è la prima volta che accedi alla cartella privata. Crea un codice PIN per accedere in modo sicuro a questa pagina", + "new_timeline": "Nuova Timeline", "new_update": "Nuovo aggiornamento", "new_user_created": "Nuovo utente creato", "new_version_available": "NUOVA VERSIONE DISPONIBILE", @@ -1492,6 +1589,7 @@ "next": "Prossimo", "next_memory": "Prossima memoria", "no": "No", + "no_actions_added": "Nessuna azione è stata ancora aggiunta", "no_albums_found": "Nessun album trovato", "no_albums_message": "Crea un album per organizzare le tue foto ed i tuoi video", "no_albums_with_name_yet": "Sembra che tu non abbia ancora nessun album con questo nome.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Nessun dispositivo di trasmissione trovato", "no_checksum_local": "Nessun checksum disponibile: impossibile recuperare le risorse locali", "no_checksum_remote": "Nessun checksum disponibile: impossibile recuperare la risorsa remota", + "no_configuration_needed": "Nessuna configurazione è necessaria", "no_devices": "Nessun device autorizzato", "no_duplicates_found": "Nessun duplicato trovato.", "no_exif_info_available": "Nessuna informazione exif disponibile", "no_explore_results_message": "Carica piÚ foto per esplorare la tua collezione.", "no_favorites_message": "Aggiungi preferiti per trovare facilmente le tue migliori foto e video", + "no_filters_added": "Nessun filtro ancora aggiunto", "no_libraries_message": "Crea una libreria esterna per vedere le tue foto e i tuoi video", "no_local_assets_found": "Nessuna risorsa locale trovata con questo checksum", + "no_location_set": "Nessuna posizione impostata", "no_locked_photos_message": "Le foto e i video nella cartella privata sono nascosti e non vengono visualizzati mentre navighi o cerchi nella tua libreria.", "no_name": "Nessun nome", "no_notifications": "Nessuna notifica", @@ -1518,19 +1619,14 @@ "no_results": "Nessun risultato", "no_results_description": "Prova ad usare un sinonimo oppure una parola chiave piÚ generica", "no_shared_albums_message": "Crea un album per condividere foto e video con le persone nella tua rete", - "no_steps": "Nessun passo aggiunto", + "no_uploads_in_progress": "Nessun upload in corso", "none": "Nulla", "not_allowed": "Non permesso", "not_available": "N/A", "not_in_any_album": "In nessun album", "not_selected": "Non selezionato", - "not_set": "Non impostato", "notes": "Note", "nothing_here_yet": "Ancora nulla qui", - "notification_backup_reliability": "Attiva le notifiche per migliorare l'affidabilità del backup in background", - "notification_enabled_list_tile_content": "Immich utilizza le notifiche per il backup in background. Gestisci le notifiche tramite le impostazioni del tuo dispositivo.", - "notification_enabled_list_tile_open_button": "Apri impostazioni", - "notification_enabled_list_tile_title": "Notifiche attivate", "notification_permission_dialog_content": "Per attivare le notifiche, vai alle Impostazioni e seleziona concedi.", "notification_permission_list_tile_content": "Concedi i permessi per attivare le notifiche.", "notification_permission_list_tile_enable_button": "Attiva notifiche", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Configuratore Obtainium", "obtainium_configurator_instructions": "Utilizza Obtainium per installare e aggiornare l'app Android direttamente dalla versione rilasciata su GitHub da Immich. Crea una chiave API e seleziona una variante per creare il tuo link di configurazione Obtainium", "ocr": "OCR", - "ocr_body": "Immich ora riconosce il testo contenuto nelle tue foto, cosÃŦ puoi cercarle in base a ciÃ˛ che dicono.", - "ocr_title": "Cerca tra il testo nelle tue foto", "official_immich_resources": "Risorse Ufficiali Immich", "offline": "Offline", "offset": "Offset", @@ -1562,8 +1656,6 @@ "open": "Apri", "open_calendar": "Apri il calendario", "open_in_browser": "Apri con il browser", - "open_in_immich_body": "Imposta Immich come galleria su Android per aprire le foto direttamente da altre app.", - "open_in_immich_title": "Apri foto in Immich", "open_in_map_view": "Apri nella visualizzazione mappa", "open_in_openstreetmap": "Apri su OpenStreetMap", "open_the_search_filters": "Apri filtri di ricerca", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organizza all'interno degli albums", "organize_into_albums_description": "Inserisci le foto esistenti all'interno degli albums utilizzando le attuale impostazioni di sincronizzazione", "organize_your_library": "Organizza la tua libreria", - "orientation": "Orientamento", "original": "originale", "other": "Altro", "other_devices": "Altri dispositivi", + "other_entities": "Altre entità", "other_variables": "Altre variabili", "owned": "Posseduti", "owner": "Proprietario", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Tutte le tue foto e i tuoi video eccetto quelli Archiviati e Cancellati", "partner_can_access_location": "La posizione dove è stata scattata la foto", "partner_list_user_photos": "Foto di {user}", + "partner_list_view_all": "Mostra tutto", "partner_page_empty_message": "Le tue foto non sono ancora condivise con alcun partner.", "partner_page_no_more_users": "Nessun altro utente da aggiungere", + "partner_page_partner_add_failed": "Aggiunta del partner non riuscita", "partner_page_select_partner": "Seleziona partner", + "partner_page_shared_to_title": "Condividi con", "partner_page_stop_sharing_content": "{partner} non sarà piÚ in grado di accedere alle tue foto.", "partner_sharing": "Condivisione Compagno", "partners": "Compagni", @@ -1609,6 +1704,8 @@ "people": "Persone", "people_edits_count": "{count, plural, one {Modificata # persona} other {Modificate # persone}}", "people_feature_description": "Navigare foto e video raggruppati da persone", + "people_selected": "{count, plural, one {# persona selezionata} other {# persone selezionate}}", + "people_sidebar_description": "Mostra un link alle persone nella barra laterale", "permanent_deletion_warning": "Avviso eliminazione permanente", "permanent_deletion_warning_setting_description": "Mostra un avviso all'eliminazione definitiva di una risorsa", "permanently_delete": "Elimina definitivamente", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {Cancellata # risorsa} other {Cancellate # risorse}} definitivamente", "permission": "Autorizzazione", "permission_empty": "La tua autorizzazione non puÃ˛ essere vuota", + "permission_onboarding_back": "Indietro", + "permission_onboarding_continue_anyway": "Continua lo stesso", + "permission_onboarding_get_started": "Inizia", + "permission_onboarding_go_to_settings": "Vai a Impostazioni", + "permission_onboarding_permission_denied": "Permessi negati. Per usare Immich concedi i permessi ai video e foto dalle impostazioni.", + "permission_onboarding_permission_granted": "Concessi i permessi! Ora sei tutto apposto.", + "permission_onboarding_permission_limited": "Permessi limitati. Per consentire a Immich di gestire e fare i backup di tutta la galleria, concedi i permessi Foto e Video dalle Impostazioni.", + "permission_onboarding_request": "Immich richiede i permessi per vedere le tue foto e video.", "person": "Persona", "person_age_months": "{months, plural, one {# mese} other {# mesi}}", "person_age_year_months": "1 anno e {months, plural, one {# mese} other {# mesi}}", @@ -1625,10 +1730,13 @@ "person_birthdate": "Nato il {date}", "person_hidden": "{name}{hidden, select, true { (nascosto)} other {}}", "person_recognized": "Persona riconosciuta", + "person_selected": "Persona selezionata", "photo_shared_all_users": "Sembra che tu abbia condiviso le tue foto con tutti gli utenti, oppure che tu non abbia alcun utente con cui condividerle.", "photos": "Foto", "photos_and_videos": "Foto & Video", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foto}}", "photos_from_previous_years": "Foto dagli anni scorsi", + "photos_only": "Solo foto", "pick_a_location": "Scegli una posizione", "pick_custom_range": "Intervallo personalizzato", "pick_date_range": "Seleziona un periodo temporale", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Preferisci la riproduzione dei video originali anzichè ricodificarli. Se l'originale non è compatibile non sarà riprodotto correttamente.", "play_transcoded_video": "Riproduci video ricodificato", "please_auth_to_access": "Autenticati per accedere", - "plugin_method_filter_type": "Filtro", - "plugin_method_filter_type_description": "Questo metodo puÃ˛ filtrare eventi e impedire condizionalmente l'esecuzione dei passi successivi", "port": "Porta", "preferences_settings_subtitle": "Gestisci le preferenze dell'app", "preferences_settings_title": "Preferenze", @@ -1665,11 +1771,11 @@ "privacy": "Privacy", "profile": "Profilo", "profile_drawer_app_logs": "Registri", + "profile_drawer_client_server_up_to_date": "Client e server sono aggiornati", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modalità di sola lettura abilitata. Tieni premuto sull'avatar dell'utente per disabilitarla.", "profile_image_of_user": "Immagine profilo di {user}", "profile_picture_set": "Foto profilo impostata.", - "projection_type": "Tipo di proiezione", "public_album": "Album pubblico", "public_share": "Condivisione Pubblica", "purchase_account_info": "Contributore", @@ -1683,11 +1789,12 @@ "purchase_button_reminder": "Ricordamelo tra 30 giorni", "purchase_button_remove_key": "Rimuovi chiave", "purchase_button_select": "Seleziona", - "purchase_failed_activation": "Attivazione non riuscita! Controlla la tua email per verificare la chiave prodotto corretta!", + "purchase_failed_activation": "Attivazione fallita! Controlla la tua email per la chiave prodotto corretta!", "purchase_individual_description_1": "Per un individuo", "purchase_individual_description_2": "Stato di Sostenitore", "purchase_individual_title": "Individuale", "purchase_input_suggestion": "Hai una chiave prodotto? Inseriscila qui sotto", + "purchase_license_subtitle": "Acquista Immich per supportare lo sviluppo continuo del servizio", "purchase_lifetime_description": "Acquisto a vita", "purchase_option_title": "OPZIONI DI ACQUISTO", "purchase_panel_info_1": "Sviluppare Immich richiede molto tempo e impegno, e abbiamo ingegneri a tempo pieno che lavorano per renderlo il migliore possibile. La nostra missione è fare in modo che il software open source e pratiche commerciali etiche diventino una fonte di reddito sostenibile per gli sviluppatori, creando al contempo un ecosistema che rispetti la privacy, offrendo vere alternative ai servizi cloud sfruttatori.", @@ -1720,23 +1827,23 @@ "reassigned_assets_to_new_person": "{count, plural, one {Riassegnata # risorsa} other {Riassegnate # risorse}} ad una nuova persona", "reassing_hint": "Assegna le risorse selezionate ad una persona esistente", "recent": "Recenti", + "recent_albums": "Album recenti", "recent_searches": "Ricerche recenti", "recently_added": "Aggiunti recentemente", - "recently_added_body": "Vai direttamente a tutto ciÃ˛ che hai aggiunto di recente su una pagina dedicata.", - "recently_added_description": "Sfoglia le tue risorse ordinate in base alla data di caricamento su Immich", "recently_added_page_title": "Aggiunti di recente", - "recently_added_title": "Aggiunto di recente", "recently_taken": "Scattate di recente", + "recently_taken_page_title": "Scattate di Recente", "refresh": "Ricarica", "refresh_encoded_videos": "Ricarica video codificati", "refresh_faces": "Aggiorna volti", "refresh_metadata": "Ricarica metadati", - "refresh_thumbnails": "Ricarica miniature", + "refresh_thumbnails": "Ricarica anteprime", "refreshed": "Aggiornato", + "refreshes_every_file": "Rilegge tutti i file esistenti e nuovi", "refreshing_encoded_video": "Ricaricando il video codificato", "refreshing_faces": "Aggiornando volti", "refreshing_metadata": "Ricaricando i metadati", - "regenerating_thumbnails": "Rigenerando le miniature", + "regenerating_thumbnails": "Rigenerando le anteprime", "remote": "Remoto", "remote_assets": "Risorse remote", "remote_media_summary": "Riepilogo dei Media Remoti", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Sei sicuro di voler rimuovere {count, plural, one {# risorsa} other {# risorse}} da questo link condiviso?", "remove_assets_title": "Rimuovo le risorse?", "remove_custom_date_range": "Rimuovi intervallo data personalizzato", - "remove_filter": "Rimuovi filtro", + "remove_deleted_assets": "Rimuovi le Risorse cancellate", "remove_from_album": "Rimuovere dall'album", "remove_from_album_action_prompt": "{count} elementi rimossi dall'album", "remove_from_favorites": "Rimuovi dai preferiti", @@ -1756,22 +1863,29 @@ "remove_memory": "Rimuovi ricordo", "remove_photo_from_memory": "Rimuovi foto da questo ricordo", "remove_tag": "Rimuovi tag", + "remove_url": "Rimuovi URL", "remove_user": "Rimuovi utente", "removed_api_key": "Rimossa chiave API: {name}", "removed_from_archive": "Rimosso dall'archivio", "removed_from_favorites": "Rimosso dai preferiti", "removed_from_favorites_count": "{count, plural, one {Rimosso } other {Rimossi #}} dai preferiti", "removed_memory": "Memoria rimossa", + "removed_photo_from_memory": "Foto rimossa dalla memoria", "removed_tagged_assets": "Rimossa etichetta {count, plural, one {# dalla risorsa} other {# dalle risorse}}", "rename": "Rinomina", + "repair": "Ripara", + "repair_no_results_message": "I file mancanti e non tracciati saranno mostrati qui", + "replace_with_upload": "Rimpiazza con upload", "repository": "Repository", "require_password": "Richiedi password", + "require_user_to_change_password_on_first_login": "Richiedi all'utente di cambiare password al primo accesso", "rescan": "Scansiona nuovamente", "reset": "Ripristina", "reset_password": "Ripristina password", "reset_people_visibility": "Ripristina visibilità persone", "reset_pin_code": "Resetta il codice PIN", "reset_pin_code_description": "Se hai dimenticato il codice PIN, puoi contattare l’amministratore del server per reimpostarlo", + "reset_pin_code_success": "Codice PIN reimpostato con successo", "reset_pin_code_with_password": "Puoi sempre reimpostare il codice PIN usando la tua password", "reset_sqlite": "Resetta Database SQLite", "reset_sqlite_clear_app_data": "Cancella i dati", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Tutti i duplicati sono stati risolti", "restore": "Ripristina", "restore_all": "Ripristina tutto", + "restore_trash_action_prompt": "{count} ripristinati dal cestino", "restore_user": "Ripristina utente", "restored_asset": "Risorsa ripristinata", "resume": "Riprendi", "resume_paused_jobs": "Riprendi {count, plural, one {# processo in pausa} other {# i processi in pausa}}", + "retry_upload": "Riprova caricamento", "review_duplicates": "Esamina duplicati", "review_large_files": "Revisiona file pesanti", "role": "Ruolo", @@ -1796,6 +1912,7 @@ "role_viewer": "Visualizzatore", "running": "In esecuzione", "save": "Salva", + "save_to_gallery": "Salva in galleria", "saved": "Salvato", "saved_api_key": "Chiave API salvata", "saved_profile": "Profilo salvato", @@ -1806,10 +1923,9 @@ "scan": "Scansione", "scan_all_libraries": "Analizza tutte le librerie", "scan_library": "Scansione", + "scan_settings": "Impostazioni Analisi", "scanning": "Scansione in corso", "scanning_for_album": "Sto cercando l'album...", - "screencast_mode_description": "Mostra indicatori di eventi mouse e tastiera sullo schermo", - "screencast_mode_title": "Attiva/disattiva modalità screencast", "search": "Cerca", "search_albums": "Cerca album", "search_by_context": "Cerca con contesto", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Giornata di escursioni a Sapa", "search_by_filename": "Cerca per nome del file o estensione", "search_by_filename_example": "es. IMG_1234.JPG o PNG", - "search_by_full_path": "Cerca per percorso completo o cartella", - "search_by_full_path_example": "/Giovanni/Progetti/Stampa_3D/2026-07-01 - puoi cercare per Progetti, 3D, Stampa, 2026 ecc.", "search_by_ocr": "Ricerca tramite OCR", "search_by_ocr_example": "Caffè Latte", "search_camera_lens_model": "Cerca il modello del'obiettivo...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Seleziona posizione", "search_filter_media_type": "Tipo di media", "search_filter_media_type_title": "Seleziona il tipo di media", + "search_filter_ocr": "Cerca tramite OCR", "search_filter_people_title": "Seleziona persone", "search_filter_star_rating": "Voto in Stelle", "search_filter_tags_title": "Seleziona tag", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Nessuna persona chiamate \"{name}\"", "search_no_result": "Nessun risultato trovato, prova con un termine o combinazione diversi", "search_options": "Opzioni di ricerca", + "search_page_categories": "Categoria", + "search_page_motion_photos": "Foto in movimento", + "search_page_no_objects": "Nessuna informazione sugli oggetti disponibile", + "search_page_no_places": "Nessuna informazione sui luoghi disponibile", + "search_page_screenshots": "Screenshot", "search_page_search_photos_videos": "Ricerca le tue foto e i tuoi video", + "search_page_selfies": "Selfie", + "search_page_things": "Oggetti", "search_page_view_all_button": "Guarda tutto", + "search_page_your_activity": "Le tua attività", + "search_page_your_map": "La tua mappa", "search_people": "Cerca persone", "search_places": "Cerca luoghi", "search_rating": "Cerca per valutazione...", + "search_result_page_new_search_hint": "Nuova ricerca", "search_settings": "Cerca Impostazioni", "search_state": "Cerca stato...", + "search_suggestion_list_smart_search_hint_1": "Ricerca Smart è attiva di default, per usare la ricerca con i metadata usare la seguente sintassi ", + "search_suggestion_list_smart_search_hint_2": "m:termine-di-ricerca", "search_tags": "Cerca tag...", "search_timezone": "Cerca fuso orario...", "search_type": "Cerca tipo", @@ -1868,6 +1995,7 @@ "select_albums": "Seleziona gli album", "select_all": "Seleziona tutto", "select_all_duplicates": "Seleziona tutti i duplicati", + "select_all_in": "Seleziona tutto in {group}", "select_avatar_color": "Seleziona colore avatar", "select_count": "{count, plural, one {Seleziona #} other {Seleziona #}}", "select_cutoff_date": "Seleziona la data limite", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Seleziona foto in evidenza", "select_from_computer": "Seleziona dal computer", "select_keep_all": "Seleziona mantieni tutto", + "select_library_owner": "Seleziona proprietario libreria", "select_new_face": "Seleziona nuovo volto", "select_people": "Seleziona persone", "select_person": "Seleziona una persona", "select_person_to_tag": "Seleziona una persona da taggare", "select_photos": "Seleziona foto", - "select_quality": "Seleziona qualità", "select_trash_all": "Seleziona cestina tutto", + "select_user_for_sharing_page_err_album": "Impossibile nel creare l'album", "selected": "Selezionato", "selected_count": "{count, plural, one {# selezionato} other {# selezionati}}", "selected_gps_coordinates": "Coordinate GPS selezionate", @@ -1909,36 +2038,47 @@ "setting_image_navigation_enable_subtitle": "Se abilitata, puoi passare all'immagine precedente/successiva toccando il quarto di schermo piÚ a sinistra/piÚ a destra.", "setting_image_navigation_enable_title": "Clicca per navigare", "setting_image_navigation_title": "Navigazione delle immagini", - "setting_image_viewer_help": "Il visualizzatore dei dettagli carica prima la miniatura piccola, poi quella di medie dimensioni (se abilitata) e infine l'originale (se abilitato).", + "setting_image_viewer_help": "Il visualizzatore dettagliato prima carica una piccola thumbnail , per poi caricare un immagine di media grandezza (se abilitato). Ed infine carica l'originale (se abilitato).", "setting_image_viewer_original_subtitle": "Abilita il caricamento dell’immagine originale in alta risoluzione (dimensioni elevate). Disattiva per ridurre il consumo di dati, sia di rete che in cache locale.", "setting_image_viewer_original_title": "Carica l'immagine originale", - "setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media. Disabilita per caricare direttamente l'immagine originale o usare la miniatura.", + "setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media. Disabilita per caricare direttamente l'immagine originale o usare la thumbnail.", "setting_image_viewer_preview_title": "Carica immagine di anteprima", + "setting_image_viewer_title": "Immagini", "setting_languages_apply": "Applica", "setting_languages_subtitle": "Cambia la lingua dell'app", + "setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {duration}", + "setting_notifications_notify_hours": "{count} ore", + "setting_notifications_notify_immediately": "immediatamente", "setting_notifications_notify_minutes": "{count} minuti", + "setting_notifications_notify_never": "mai", "setting_notifications_notify_seconds": "{count} secondi", + "setting_notifications_single_progress_subtitle": "Informazioni dettagliate sul caricamento della risorsa", + "setting_notifications_single_progress_title": "Mostra avanzamento dettagliato del backup in background", "setting_notifications_subtitle": "Cambia le impostazioni di notifica", + "setting_notifications_total_progress_subtitle": "Avanzamento complessivo del caricamento (completati/risorse totali)", + "setting_notifications_total_progress_title": "Mostra avanzamento del backup in background", "setting_video_viewer_auto_play_subtitle": "Avvia automaticamente la riproduzione dei video quando vengono aperti", "setting_video_viewer_auto_play_title": "Riproduci video automaticamente", "setting_video_viewer_looping_title": "Iterazione", "setting_video_viewer_original_video_subtitle": "Quando riproduci un video dal server, riproduci l'originale anche se è disponibile una versione transcodificata. Questo potrebbe portare a buffering. I video disponibili localmente sono sempre riprodotti a qualità originale indipendentemente da questa impostazione.", "setting_video_viewer_original_video_title": "Forza video originale", "settings": "Impostazioni", + "settings_require_restart": "Si prega di riavviare Immich perchÊ vengano applicate le impostazioni", "settings_saved": "Impostazioni salvate", "setup_pin_code": "Configura un codice PIN", "share": "Condividi", + "share_action_prompt": "Condivisi {count} risorse", + "share_add_photos": "Aggiungi foto", + "share_assets_selected": "{count} selezionati", "share_dialog_preparing": "Preparoâ€Ļ", "share_link": "Link di condivisione", - "share_original": "Usa originale (grande)", - "share_preview": "Usa miniatura (piccola)", - "share_quality_body": "Tieni premuto il pulsante di condivisione per scegliere la qualità dell'immagine prima di condividerla.", - "share_quality_title": "Seleziona la qualità di condivisione", "shared": "Condivisi", "shared_album_activities_input_disable": "I commenti sono disabilitati", "shared_album_activity_remove_content": "Vuoi eliminare questa attività?", "shared_album_activity_remove_title": "Elimina attività", "shared_album_section_people_action_error": "Errore durante la rimozione/uscita dell'album", + "shared_album_section_people_action_leave": "Rimuovi utente dall'album", + "shared_album_section_people_action_remove_user": "Rimuovi utente dall'album", "shared_album_section_people_title": "PERSONE", "shared_by": "Condiviso da", "shared_by_user": "Condiviso da {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Caricati", "shared_link_app_bar_title": "Link condivisi", "shared_link_clipboard_copied_massage": "Copiato negli appunti", + "shared_link_clipboard_text": "Link: {link}\nPassword: {password}", "shared_link_create_error": "Si è verificato un errore durante la creazione del link condiviso", - "shared_link_custom_url_description": "Accedi a questo link condiviso con un nome URL personalizzato", - "shared_link_custom_url_title": "URL personalizzato", - "shared_link_custom_url_warning": "Attenzione: un URL personalizzato con una barra obliqua potrebbe comportarsi in modo inaspettato.", + "shared_link_custom_url_description": "Accedi a questo link condiviso con un URL personalizzato", "shared_link_edit_description_hint": "Inserisci la descrizione della condivisione", "shared_link_edit_expire_after_option_day": "1 giorno", "shared_link_edit_expire_after_option_days": "{count} giorni", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Condiviso con {partner}", "sharing": "Condivisione", "sharing_enter_password": "Inserisci la password per accedere a questa pagina.", + "sharing_page_album": "Album condivisi", + "sharing_page_description": "Crea un album condiviso per condividere foto e video con gli utenti della tua rete Immich.", + "sharing_page_empty_list": "LISTA VUOTA", + "sharing_sidebar_description": "Mostra un link a Condivisione nella barra laterale", + "sharing_silver_appbar_create_shared_album": "Crea album condiviso", + "sharing_silver_appbar_share_partner": "Condividi con partner", "shift_to_permanent_delete": "premi ⇧ per cancellare definitivamente la risorsa", "show_album_options": "Mostra opzioni album", "show_albums": "Mostra gli album", @@ -1992,12 +2137,10 @@ "show_file_location": "Mostra percorso file", "show_gallery": "Mostra galleria", "show_hidden_people": "Mostra persone nascoste", - "show_in_timeline": "Mostra nella timeline", - "show_in_timeline_setting_description": "Mostra foto e video di questo utente nella tua timeline", + "show_in_timeline": "Mostra nella linea temporale", + "show_in_timeline_setting_description": "Mostra foto e video dalla linea temporale di questo utente", "show_keyboard_shortcuts": "Mostra comandi rapidi", - "show_less": "Mostra meno", "show_metadata": "Visualizza metadati", - "show_more_fields": "{count, plural, one {Visualizza # campo in piÚ} other {Visualizza # campi in piÚ}}", "show_or_hide_info": "Mostra o nascondi informazioni", "show_password": "Mostra password", "show_person_options": "Mostra opzioni persona", @@ -2005,7 +2148,6 @@ "show_schema": "Mostra lo schema", "show_search_options": "Mostra impostazioni di ricerca", "show_shared_links": "Mostra link condivisi", - "show_slideshow_metadata_overlay": "Mostra metadati in sovrimpressione", "show_slideshow_transition": "Mostra la transizione della presentazione", "show_supporter_badge": "Medaglia di Contributore", "show_supporter_badge_description": "Mostra la medaglia di contributore", @@ -2017,25 +2159,18 @@ "sign_out": "Esci", "sign_up": "Registrati", "size": "Dimensione", - "skip": "Salta", "skip_to_content": "Salta al contenuto", "skip_to_folders": "Salta alle cartelle", "skip_to_tags": "Salta ai tag", "slideshow": "Presentazione", - "slideshow_body": "Rilassati e guarda le tue foto scorrere in una presentazione a schermo intero.", - "slideshow_metadata_overlay_mode": "Contenuto dell'overlay", - "slideshow_metadata_overlay_mode_description_only": "Solo descrizione", - "slideshow_metadata_overlay_mode_full": "Completo", "slideshow_repeat": "Ripeti presentazione", "slideshow_repeat_description": "Ricomincia da capo quando la presentazione termina", "slideshow_settings": "Impostazioni presentazione", - "slideshow_title": "Presentazione", - "smart_album": "Raccolta intelligente", - "some_assets_already_have_a_location_warning": "Alcune delle risorse selezionate hanno già una posizione", "sort_albums_by": "Ordina album per...", "sort_created": "Data creazione", "sort_items": "Numero di elementi", "sort_modified": "Data modifica", + "sort_newest": "Foto piÚ recente", "sort_oldest": "Foto piÚ vecchia", "sort_people_by_similarity": "Ordina persone per somiglianza", "sort_recent": "Foto piÚ recente", @@ -2044,6 +2179,7 @@ "stack": "Raggruppa", "stack_action_prompt": "{count} elementi raggruppati", "stack_duplicates": "Raggruppa i duplicati", + "stack_select_one_photo": "Seleziona una foto principale per il gruppo", "stack_selected_photos": "Raggruppa foto selezionate", "stacked_assets_count": "{count, plural, one {Raggruppata # risorsa} other {Raggruppate # risorse}}", "stacktrace": "Traccia dell'errore", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "La data di inizio deve essere precedente alla data di fine", "state": "Provincia", "status": "Stato", - "step_delete": "Rimuovi passo", - "step_delete_confirm": "Sei sicuro di voler rimuovere questo passo?", - "step_details": "Dettagli del passo", - "steps": "Passi", "stop_casting": "Interrompi trasmissione", "stop_motion_photo": "Ferma Foto in Movimento", "stop_photo_sharing": "Interrompere la condivisione delle tue foto?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Scambia direzione di unione", "sync": "Sincronizza", "sync_albums": "Sincronizza album", + "sync_albums_manual_subtitle": "Sincronizza tutti i video e le foto caricati con gli album di backup selezionati", "sync_local": "Sincronizza gli elementi locali", "sync_remote": "Sincronizza gli elementi remoti", "sync_status": "Stato di Sincronizzazione", "sync_status_subtitle": "Visualizza e gestisci il sistema di sincronizzazione", "sync_upload_album_setting_subtitle": "Crea e carica le tue foto e video sull'album selezionato in Immich", - "system_theme": "Tema di sistema", - "system_theme_command_description": "Utilizza il tema di sistema ({value})", "tag": "Tag", "tag_assets": "Tagga risorse", "tag_created": "Tag creato: {tag}", "tag_face": "Tagga la faccia", "tag_feature_description": "Navigazione foto e video raggruppati per argomenti tag logici", + "tag_not_found_question": "Non riesci a trovare un tag? Creane uno nuovo.", "tag_people": "Tagga persone", "tag_updated": "Tag {tag} aggiornata", "tagged_assets": "{count, plural, one {# risorsa etichettata} other {# risorse etichettate}}", @@ -2102,17 +2234,22 @@ "theme_setting_asset_list_tiles_per_row_title": "Numero di risorse per riga ({count})", "theme_setting_colorful_interface_subtitle": "Applica il colore primario alle superfici di sfondo.", "theme_setting_colorful_interface_title": "Interfaccia colorata", + "theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine", + "theme_setting_image_viewer_quality_title": "Qualità immagine", "theme_setting_primary_color_subtitle": "Scegli un colore per le azioni primarie e accentate.", "theme_setting_primary_color_title": "Colore primario", "theme_setting_system_primary_color_title": "Usa colori di sistema", "theme_setting_system_theme_switch": "Automatico (Segue le impostazioni di sistema)", + "theme_setting_theme_subtitle": "Scegli un'impostazione per il tema dell'app", + "theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda", + "theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage", "then": "Allora", "they_will_be_merged_together": "Verranno uniti insieme", "third_party_resources": "Risorse di Terze Parti", "time": "Orario", "time_based_memories": "Ricordi basati sul tempo", "time_based_memories_duration": "Numero di secondi per visualizzare ciascuna immagine.", - "timeline": "Timeline", + "timeline": "Linea temporale", "timezone": "Fuso orario", "to_archive": "Archivio", "to_change_password": "Modifica password", @@ -2131,17 +2268,23 @@ "trash_all": "Cestina Tutto", "trash_count": "Cancella {count, number}", "trash_delete_asset": "Cestina/Cancella Risorsa", + "trash_emptied": "Cestino svuotato", "trash_no_results_message": "Le foto cestinate saranno mostrate qui.", "trash_page_delete_all": "Elimina tutti", + "trash_page_empty_trash_dialog_content": "Vuoi eliminare le risorse dal cestino? Saranno eliminate definitivamente da Immich", "trash_page_info": "Gli elementi cestinati saranno eliminati definitivamente dopo {days} giorni", + "trash_page_no_assets": "Nessuna risorsa cestinata", + "trash_page_restore_all": "Ripristina tutto", + "trash_page_select_assets_btn": "Seleziona risorse", + "trash_page_title": "Cestino ({count})", "trashed_items_will_be_permanently_deleted_after": "Gli elementi cestinati saranno eliminati definitivamente dopo {days, plural, one {# giorno} other {# giorni}}.", "trigger": "Evento di attivazione", - "trigger_asset_metadata_extraction": "Estrazione dei metadati delle risorse", - "trigger_asset_metadata_extraction_description": "Attivato quando i metadati EXIF di una risorsa vengono estratti", - "trigger_asset_uploaded": "Caricamento risorsa", + "trigger_asset_uploaded": "Risorsa Caricata", "trigger_asset_uploaded_description": "Attivato quando una nuova risorsa viene caricata", + "trigger_description": "Un evento che attiva il flusso di lavoro", "trigger_person_recognized": "Persona Riconosciuta", - "trigger_person_recognized_description": "Attivato quando è riconosciuta una persona", + "trigger_person_recognized_description": "Attivato quando è rilevata una persona", + "trigger_type": "Tipo di trigger", "troubleshoot": "Risoluzione dei problemi", "type": "Tipo", "unable_to_change_pin_code": "Impossibile cambiare il codice PIN", @@ -2157,7 +2300,6 @@ "unknown": "Sconosciuto", "unknown_country": "Paese sconosciuto", "unknown_date": "Data sconosciuta", - "unknown_schema": "Schema sconosciuto", "unknown_year": "Anno sconosciuto", "unlimited": "Illimitato", "unlink_motion_video": "Scollega video in movimento", @@ -2170,20 +2312,23 @@ "unsaved_change": "Modifica non salvata", "unselect_all": "Deseleziona tutto", "unselect_all_duplicates": "Deseleziona tutti i duplicati", + "unselect_all_in": "Deseleziona tutto in {group}", "unstack": "Separa dal gruppo", "unstack_action_prompt": "{count} separati", "unstacked_assets_count": "{count, plural, one {Separata # risorsa} other {Separate # risorse}}", "unsupported_field_type": "Tipo di campo non supportato", "unsupported_file_type": "Il file {file} non puÃ˛ essere caricato perchÊ il tipo di file {type} non è supportato.", "untagged": "Senza tag", + "untitled_workflow": "Flusso di lavoro senza titolo", "up_next": "Prossimo", "update_location_action_prompt": "Aggiorna la posizione di {count} risorse selezionate con:", "updated_at": "Aggiornato il", "updated_password": "Password aggiornata", "upload": "Carica", "upload_concurrency": "Caricamenti contemporanei", - "upload_day_count": "{date}: {count, plural, one {# caricamento} other {# caricamenti}}", "upload_details": "Dettagli di caricamento", + "upload_dialog_info": "Vuoi fare il backup sul server delle risorse selezionate?", + "upload_dialog_title": "Carica Risorsa", "upload_error_with_count": "Invio in errore per {count, plural, one {# risorsa} other {# risorse}}", "upload_errors": "Caricamento completato con {count, plural, one {# errore} other {# errori}}, ricarica la pagina per vedere le risorse caricate.", "upload_finished": "Upload terminato", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Errori", "upload_status_uploaded": "Caricato", "upload_success": "Caricamento completato, aggiorna la pagina per vedere le nuove risorse caricate.", - "upload_to_album_body": "Per gli utenti che non utilizzano la funzione di caricamento manuale, ora è possibile scegliere di aggiungere le foto locali direttamente a un album mentre le si carica, senza piÚ bisogno di caricarle prima per poi aggiungerle a un album in un secondo momento.", - "upload_to_album_title": "Carica direttamente in un album", "upload_to_immich": "Carica su Immich ({count})", "uploading": "Caricamento", "uploading_media": "Caricando i media", - "uploads": "Caricamenti", - "uploads_count": "{count, plural, one {# caricamento} other {# caricamenti}}", "url": "URL", "usage": "Utilizzo", "use_biometric": "Usa biometrica", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Formatta date e numeri in base alla lingua e al paese del browser", "use_current_connection": "Usa la connessione attuale", "use_custom_date_range": "Altrimenti utilizza un intervallo date personalizzato", - "use_template": "Usa modello predefinito", "user": "Utente", "user_has_been_deleted": "L'utente è stato rimosso.", "user_id": "ID utente", @@ -2217,6 +2357,7 @@ "user_privacy": "Privacy dell'utente", "user_purchase_settings": "Acquisto", "user_purchase_settings_description": "Gestisci il tuo acquisto", + "user_role_set": "Imposta {user} come {role}", "user_usage_detail": "Dettagli utilizzo utente", "user_usage_stats": "Statistiche d'uso", "user_usage_stats_description": "Consulta le statistiche d'uso dell'account", @@ -2226,6 +2367,7 @@ "utilities": "Utilità", "validate": "Validazione", "validate_endpoint_error": "Inserisci un URL valido", + "validation_error": "Erroe di validazione", "variables": "Variabili", "version": "Versione", "version_announcement_closing": "Il tuo amico, Alex", @@ -2233,62 +2375,62 @@ "version_history": "Storico delle Versioni", "version_history_item": "Versione installata {version} il {date}", "video": "Video", - "video_hover_setting": "Riproduci la miniatura del video al passaggio del mouse", - "video_hover_setting_description": "Riproduci la miniatura del video quando il mouse passa sopra l'elemento. Anche se disabilitato, la riproduzione puÃ˛ essere avviata passando con il mouse sopra l'icona di riproduzione.", - "video_quality": "Qualità video", + "video_hover_setting": "Riproduci l'anteprima del video al passaggio del mouse", + "video_hover_setting_description": "Riproduci miniatura video quando il mouse passa sopra l'elemento. Anche se disabilitato, la riproduzione puÃ˛ essere avviata passando con il mouse sopra l'icona riproduci.", "videos": "Video", "videos_count": "{count, plural, one {# Video} other {# Video}}", + "videos_only": "Solo video", "view": "Visualizza", "view_album": "Visualizza Album", "view_all": "Vedi tutto", "view_all_users": "Visualizza tutti gli utenti", "view_asset_owners": "Visualizza proprietari della risorsa", "view_details": "Visualizza Dettagli", - "view_in_timeline": "Visualizza nella timeline", + "view_in_timeline": "Visualizza in timeline", "view_link": "Visualizza link", + "view_links": "Visualizza i link", "view_name": "Vista", "view_next_asset": "Visualizza risorsa successiva", "view_previous_asset": "Visualizza risorsa precedente", "view_qr_code": "Visualizza Codice QR", "view_similar_photos": "Visualizza foto simili", "view_stack": "Visualizza Raggruppamento", + "view_user": "Visualizza Utente", "viewer_remove_from_stack": "Rimuovi dal gruppo", + "viewer_stack_use_as_main_asset": "Usa come risorsa principale", + "viewer_unstack": "Separa dal gruppo", "visibility": "Visibilità", "visibility_changed": "Visibilità modificata per {count, plural, one {# persona} other {# persone}}", "visual": "Visuale", + "visual_builder": "Costruttore di visuale", "waiting": "In Attesa", "waiting_count": "In attesa: {count}", "warning": "Attenzione", "week": "Settimana", "welcome": "Benvenuto", "welcome_to_immich": "Benvenuto in Immich", - "whats_new": "Novità", - "whats_new_settings_subtitle": "Scopri le novità di Immich", - "whats_new_version": "Versione {version}", - "when": "Quando", "width": "Larghezza", "wifi_name": "Nome rete Wi-Fi", - "workflow": "Sequenza", - "workflow_delete_prompt": "Sei sicuro di voler cancellare questa sequenza?", - "workflow_deleted": "Sequenza cancellata", - "workflow_description": "Descrizione della sequenza", - "workflow_info": "Informazioni sulla sequenza", - "workflow_json": "JSON della sequenza", - "workflow_json_help": "Edita la configurazione della sequenza in formato JSON. I cambiamenti verranno sincronizzati con il costruttore visuale.", - "workflow_name": "Nome della sequenza", + "workflow_delete_prompt": "Sei sicuro di voler cancellare questo flusso di lavoro?", + "workflow_deleted": "Flusso di lavoro cancellato", + "workflow_description": "Descrizione del flusso di lavoro", + "workflow_info": "Informazioni sul flusso di lavoro", + "workflow_json": "Flusso di lavoro JSON", + "workflow_json_help": "Edita la configurazione del flusso di lavoro in formato JSON. I cambiamenti verranno sincronizzati con il costruttore visuale.", + "workflow_name": "Nome del flusso di lavoro", "workflow_navigation_prompt": "Sei sicuro di voler uscire senza salvare i cambiamenti?", - "workflow_summary": "Sommario della sequenza", - "workflow_templates": "Modelli di sequenze", - "workflow_update_success": "Sequenza aggiornata con successo", - "workflows": "Sequenze", - "workflows_help_text": "Le sequenze automatizzano azioni sulle tue risorse a seconda di eventi e filtri", + "workflow_summary": "Sommario del flusso di lavoro", + "workflow_update_success": "Flusso di lavoro aggiornato con successo", + "workflow_updated": "Flusso di lavoro aggiornato", + "workflows": "Flussi di lavoro", + "workflows_help_text": "I flussi di lavoro automatizzano azioni sulle tue risorse a seconda di eventi e filtri", "wrong_pin_code": "Codice PIN errato", - "x_of_total": "{x}/{total}", "year": "Anno", "years_ago": "{years, plural, one {# anno} other {# anni}} fa", "yes": "SÃŦ", "you_dont_have_any_shared_links": "Non hai nessun link condiviso", "your_wifi_name": "Nome della tua rete Wi-Fi", "zero_to_clear_rating": "Premi 0 per eliminare la valutazione", - "zoom_image": "Ingrandisci immagine" + "zoom_image": "Ingrandisci immagine", + "zoom_to_bounds": "Ingrandisci fino ai bordi" } diff --git a/i18n/ja.json b/i18n/ja.json index 15e86f4852..144c52ba83 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -2,39 +2,47 @@ "about": "Immich ãĢついãĻ", "account": "ã‚ĸã‚Ģã‚Ļãƒŗãƒˆ", "account_settings": "ã‚ĸã‚Ģã‚Ļãƒŗãƒˆč¨­åŽš", - "acknowledge": "わかりぞした", + "acknowledge": "äē†č§Ŗ", "action": "ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗ", - "action_common_update": "更新する", + "action_common_update": "更新", "action_description": "æŠŊå‡ēã•ã‚ŒãŸå†™įœŸ/動į”ģãĢ寞しãĻčĄŒã†æ‰‹é †", "actions": "ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗ", "active": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ–", - "active_count": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ–æ•°: {count}", + "active_count": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ–: {count}", "activity": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ“ãƒ†ã‚Ŗ", + "activity_changed": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ“ãƒ†ã‚Ŗã¯{enabled, select, true {有劚} other {į„ĄåŠš}}ãĢãĒりぞした", "add": "čŋŊ加", "add_a_description": "čĒŦ明をčŋŊ加", "add_a_location": "場所をčŋŊ加", "add_a_name": "名前をčŋŊ加", "add_a_title": "ã‚ŋイトãƒĢをčŋŊ加", "add_action": "ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗã‚’čŋŊ加", + "add_action_description": "クãƒĒックしã‚ĸã‚¯ã‚ˇãƒ§ãƒŗã‚’čŋŊ加", "add_assets": "é …į›Žã‚’čŋŊ加", "add_birthday": "čĒ•į”Ÿæ—Ĩã‚’č¨­åŽš", - "add_endpoint": "įĩ‚äē†åœ°į‚šã‚’čŋŊ加", + "add_endpoint": "ã‚¨ãƒŗãƒ‰ãƒã‚¤ãƒŗãƒˆã‚’čŋŊ加", "add_exclusion_pattern": "除外パã‚ŋãƒŧãƒŗã‚’čŋŊ加", + "add_filter": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧをčŋŊ加", + "add_filter_description": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧã™ã‚‹æĄäģļをčŋŊ加", "add_location": "場所をčŋŊ加", + "add_more_users": "ãƒĻãƒŧã‚ļãƒŧをčŋŊ加", "add_partner": "パãƒŧトナãƒŧをčŋŊ加", + "add_path": "パ゚をčŋŊ加", "add_photos": "å†™įœŸã‚’čŋŊ加", - "add_step": "゚テップをčŋŊ加", "add_tag": "ã‚ŋグをčŋŊ加", "add_to": "čŋŊ加先â€Ļ", "add_to_album": "ã‚ĸãƒĢバムãĢčŋŊ加", "add_to_album_bottom_sheet_added": "{album}ãĢčŋŊ加", "add_to_album_bottom_sheet_already_exists": "{album}ãĢčŋŊ加済ãŋ", "add_to_album_bottom_sheet_some_local_assets": "ã„ãã¤ã‹ãŽé …į›Žã¯ãžã ã‚ĩãƒŧバãƒŧへã‚ĸップロãƒŧドされãĻいãĒいためã‚ĸãƒĢバムãĢčŋŊ加できぞせんでした", + "add_to_album_toggle": "{album}ぎ選択を切りæ›ŋえ", "add_to_albums": "ã‚ĸãƒĢバムãĢčŋŊ加", "add_to_albums_count": "{count}äģļをã‚ĸãƒĢバムãĢčŋŊ加", "add_to_bottom_bar": "čŋŊ加する", + "add_to_shared_album": "å…ąæœ‰ã‚ĸãƒĢバムãĢčŋŊ加", "add_upload_to_stack": "゚ã‚ŋックãĢã‚ĸップロãƒŧドをčŋŊ加", "add_url": "URLをčŋŊ加", + "add_workflow_step": "ワãƒŧクフロãƒŧぎ゚テップをčŋŊ加", "added_to_archive": "ã‚ĸãƒŧã‚Ģイブしぞした", "added_to_favorites": "お気ãĢå…ĨりãĢčŋŊ加済", "added_to_favorites_count": "{count, number} 枚ぎį”ģ像をお気ãĢå…ĨりãĢčŋŊ加しぞした", @@ -73,7 +81,6 @@ "cron_expression_description": "cronåŊĸåŧã§åŽŸčĄŒã‚ŋã‚¤ãƒŸãƒŗã‚°ã‚’č¨­åŽšã—ãžã™ã€‚čŠŗã—ãã¯ Crontab Guru ã‚’å‚į…§ã—ãĻください", "cron_expression_presets": "ã‚šã‚ąã‚¸ãƒĨãƒŧãƒĢīŧˆãƒ—ãƒĒã‚ģットīŧ‰", "disable_login": "ãƒ­ã‚°ã‚¤ãƒŗã‚’į„ĄåŠšãĢする", - "download_csv": "CSVãƒ•ã‚Ąã‚¤ãƒĢをダã‚Ļãƒŗãƒ­ãƒŧド", "duplicate_detection_job_description": "抟æĸ°å­Ļįŋ’ã‚’į”¨ã„ãĻ類äŧŧį”ģ像ぎ検å‡ēã‚’čĄŒã„ãžã™ã€‚īŧˆã‚šãƒžãƒŧトã‚ĩãƒŧチãĢ䞝存īŧ‰", "exclusion_pattern_description": "除外パã‚ŋãƒŧãƒŗã‚’äŊŋį”¨ã™ã‚‹ã¨ã€ãƒŠã‚¤ãƒ–ãƒŠãƒĒã‚’ã‚šã‚­ãƒŖãƒŗã™ã‚‹éš›ãĢãƒ•ã‚Ąã‚¤ãƒĢやフりãƒĢãƒ€ã‚’į„ĄčĻ–ã™ã‚‹ã“ã¨ãŒã§ããžã™ã€‚RAWãƒ•ã‚Ąã‚¤ãƒĢãĒãŠã€ã‚¤ãƒŗãƒãƒŧトしたくãĒã„ãƒ•ã‚Ąã‚¤ãƒĢをåĢむフりãƒĢダがある場合ãĢäžŋ刊です。", "export_config_as_json_description": "įžåœ¨ãŽã‚ˇã‚šãƒ†ãƒ ã‚ŗãƒŗãƒ•ã‚Ŗã‚°ã‚’JSONãƒ•ã‚Ąã‚¤ãƒĢとしãĻダã‚Ļãƒŗãƒ­ãƒŧド", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ã‚ĩムネイãƒĢぎį”ģčŗĒを1〜100ãŽé–“ã§č¨­åŽšã§ããžã™ã€‚å€¤ãŒå¤§ãã„ãģãŠč‰¯ã„å“čŗĒã§ã™ãŒãƒ•ã‚Ąã‚¤ãƒĢã‚ĩイã‚ēが大きくãĒりã‚ĸプãƒĒぎåŋœį­”性がäŊŽä¸‹ã—ぞす。", "image_thumbnail_title": "ã‚ĩムネイãƒĢč¨­åŽš", "import_config_from_json_description": "ã‚ˇã‚šãƒ†ãƒ ã‚ŗãƒŗãƒ•ã‚Ŗã‚°ãŽJSONãƒ•ã‚Ąã‚¤ãƒĢをã‚ĸップロãƒŧãƒ‰ã—ã‚¤ãƒŗãƒãƒŧト", - "integrity_checks_checksum_files": "検æŸģåˆč¨ˆãƒ•ã‚Ąã‚¤ãƒĢ", - "integrity_checks_checksum_files_description": "検æŸģåˆč¨ˆãƒ•ã‚Ąã‚¤ãƒĢã‚’č¨­åŽšã™ã‚‹", - "integrity_checks_checksum_files_enable_description": "検æŸģåˆč¨ˆãƒã‚§ãƒƒã‚¯ã‚’æœ‰åŠšãĢする", - "integrity_checks_checksum_files_percentage_limit": "å‰˛åˆīŧˆ%īŧ‰ãŽåˆļ限", - "integrity_checks_checksum_files_percentage_limit_description": "間隔ごとãĢ検æŸģåˆč¨ˆãŽãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†æœ€å¤§ãŽå‰˛åˆã‚’0.01から0.1ãŽį¯„å›˛ã§č¨­åŽšã—ãĻください。", - "integrity_checks_checksum_files_time_limit": "時間åˆļ限", - "integrity_checks_checksum_files_time_limit_description": "間隔ごとãĢ検æŸģåˆč¨ˆãŽãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†æœ€å¤§ãŽæ™‚é–“ã‚’ãƒŸãƒĒį§’ã§č¨­åŽšã—ãĻください。", - "integrity_checks_missing_files": "不čļŗã—ãĻã„ã‚‹ãƒ•ã‚Ąã‚¤ãƒĢ", - "integrity_checks_missing_files_description": "不čļŗãƒ•ã‚Ąã‚¤ãƒĢãŽãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†æœ‰į„Ąã‚„ã€é ģåēĻã‚’č¨­åŽšã—ãĻください。", - "integrity_checks_missing_files_enable_description": "不čļŗãƒ•ã‚Ąã‚¤ãƒĢぎチェックをã‚ĒãƒŗãĢする", - "integrity_checks_settings": "デãƒŧã‚ŋぎ整合性チェック", - "integrity_checks_settings_description": "デãƒŧã‚ŋぎ整合性チェックé ģåēĻã‚’įŽĄį†ã™ã‚‹ã€‚", - "integrity_checks_untracked_files": "čŋŊčˇĄã•ã‚ŒãĻいãĒã„ãƒ•ã‚Ąã‚¤ãƒĢ", - "integrity_checks_untracked_files_description": "čŋŊčˇĄã•ã‚ŒãĻいãĒã„ãƒ•ã‚Ąã‚¤ãƒĢãŽãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†æœ‰į„Ąã‚„ã€é ģåēĻã‚’č¨­åŽšã—ãĻください。", - "integrity_checks_untracked_files_enable_description": "čŋŊčˇĄã•ã‚ŒãĻいãĒã„ãƒ•ã‚Ąã‚¤ãƒĢãŽãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†ã€‚", "job_concurrency": "{job} ãŽåŒæ™‚åŽŸčĄŒæ•°", "job_created": "ジョブをäŊœæˆã—ぞした", "job_not_concurrency_safe": "こぎジョブは厉全ãĢåŒæ™‚åŽŸčĄŒã§ããžã›ã‚“ã€‚", @@ -130,7 +122,7 @@ "job_settings_description": "ã‚¸ãƒ§ãƒ–ãŽåŒæ™‚åŽŸčĄŒã‚’įŽĄį†ã—ãžã™", "jobs_delayed": "{jobCount, plural, other {#äģļ}}ぎ遅åģļ", "jobs_failed": "{jobCount, plural, other {#äģļ}}ãŽå¤ąæ•—", - "jobs_over_time": "ジョブぎåąĨæ­´", + "jobs_over_time": "įĩ‚わらãĒã‹ãŖãŸã‚¸ãƒ§ãƒ–", "library_created": "äŊœæˆã•れたナイブナãƒĒīŧš{library}", "library_deleted": "ナイブナãƒĒは削除されぞした", "library_details": "ナイブナãƒĒãŽčŠŗį´°", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "゚マãƒŧトã‚ĩãƒŧチを有劚ãĢしぞす", "machine_learning_smart_search_enabled_description": "į„ĄåŠšãĢすると、į”ģ像ぱマãƒŧトã‚ĩãƒŧãƒį”¨ãĢã‚¨ãƒŗã‚ŗãƒŧドされぞせん。", "machine_learning_url_description": "抟æĸ°å­Ļįŋ’ã‚ĩãƒŧバãƒŧぎURLã€‚č¤‡æ•°ãŽURLãŒč¨­åŽšã•ã‚ŒãŸå ´åˆã¯1つずつã‚ĩãƒŧバãƒŧãŒæ­Ŗå¸¸ãĢåŋœį­”するぞでæŽĨįļšã‚’čŠĻãŋぞす。åŋœį­”ぎãĒいã‚ĩãƒŧバãƒŧはã‚ĒãƒŗãƒŠã‚¤ãƒŗãĢãĒã‚‹ãžã§ä¸€æ™‚įš„ãĢį„ĄčĻ–ã•ã‚Œãžã™ã€‚", - "maintenance_backup_management": "バックã‚ĸãƒƒãƒ—ãŽįŽĄį†", "maintenance_delete_backup": "バックã‚ĸップを削除", "maintenance_delete_backup_description": "ã“ãŽãƒ•ã‚Ąã‚¤ãƒĢã¯ä¸å¯é€†įš„ãĢ削除されぞす。", "maintenance_delete_error": "バックã‚ĸップぎ削除ãĢå¤ąæ•—ã—ãžã—ãŸã€‚", - "maintenance_integrity_check": "チェックする", - "maintenance_integrity_check_all": "すずãĻチェック", - "maintenance_integrity_checksum_mismatch": "チェックã‚ĩãƒ ä¸ä¸€č‡´", - "maintenance_integrity_checksum_mismatch_description": "ãƒ‡ã‚Ŗã‚šã‚¯ä¸ŠãŽãƒã‚§ãƒƒã‚¯ã‚ĩムが、Immichぎデãƒŧã‚ŋベãƒŧ゚ãĢäŋå­˜ã•れãĻいるチェックã‚ĩãƒ ã¨ä¸€č‡´ã—ãĒã„ãƒ•ã‚Ąã‚¤ãƒĢ。", - "maintenance_integrity_checksum_mismatch_job": "チェックã‚ĩãƒ ä¸ä¸€č‡´ã‚’æ¤œå‡ēする", - "maintenance_integrity_checksum_mismatch_refresh_job": "チェックã‚ĩãƒ ä¸ä¸€č‡´ãƒŦポãƒŧãƒˆã‚’å†į”Ÿæˆ", - "maintenance_integrity_missing_file": "æŦ čŊãƒ•ã‚Ąã‚¤ãƒĢ", - "maintenance_integrity_missing_file_description": "Immichがデãƒŧã‚ŋベãƒŧ゚でčŋŊčˇĄã—ãĻã„ã‚‹ãŒã€ãƒ•ã‚Ąã‚¤ãƒĢã‚ˇã‚šãƒ†ãƒ ä¸ŠãĢ存在しãĒã„ãƒ•ã‚Ąã‚¤ãƒĢ。", - "maintenance_integrity_missing_file_job": "æŦ čŊãƒ•ã‚Ąã‚¤ãƒĢぎチェック", - "maintenance_integrity_missing_file_refresh_job": "æŦ čŊãƒ•ã‚Ąã‚¤ãƒĢãƒŦポãƒŧトぎ更新", - "maintenance_integrity_report": "整合性ãƒŦポãƒŧト", - "maintenance_integrity_untracked_file": "æœĒčŋŊčˇĄãƒ•ã‚Ąã‚¤ãƒĢ", - "maintenance_integrity_untracked_file_description": "ImmichãŽãƒ‡ã‚ŖãƒŦクトãƒĒ内ãĢ存在するが、ImmichãĢč¨˜éŒ˛ãŒãĒã„ãƒ•ã‚Ąã‚¤ãƒĢ。", - "maintenance_integrity_untracked_file_job": "æœĒčŋŊčˇĄãƒ•ã‚Ąã‚¤ãƒĢぎチェック", - "maintenance_integrity_untracked_file_refresh_job": "čŋŊčˇĄã•ã‚ŒãĻいãĒã„ãƒ•ã‚Ąã‚¤ãƒĢãƒŦポãƒŧトを更新", "maintenance_restore_backup": "バックã‚ĸップを垊元", "maintenance_restore_backup_description": "įžåœ¨ãŽImmichは削除され、選択したバックã‚ĸップから垊元されぞす。įļščĄŒå‰ãĢバックã‚ĸップがäŊœæˆã•れぞす。", "maintenance_restore_backup_different_version": "こぎバックã‚ĸãƒƒãƒ—ã¯į•°ãĒるバãƒŧã‚¸ãƒ§ãƒŗãŽImmichãĢよりäŊœæˆã•れたもぎですīŧ", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ãƒĄãƒŧãƒĢ通įŸĨを有劚ãĢしぞす", "notification_settings": "通įŸĨč¨­åŽš", "notification_settings_description": "ãƒĄãƒŧãƒĢをåĢむ通įŸĨč¨­åŽšã‚’įŽĄį†ã—ãžã™", - "oauth_allow_insecure_requests": "厉全ではãĒいãƒĒã‚¯ã‚¨ã‚šãƒˆã‚’č¨ąå¯ã™ã‚‹", - "oauth_allow_insecure_requests_description": "č­Ļ告: OAuthãƒĒクエ゚トãĢ寞するTLSč¨ŧ明書ぎ検č¨ŧãŒį„ĄåŠšåŒ–ã•ã‚Œã€ä¸­é–“č€…æ”ģ撃īŧˆMITMīŧ‰ãŽãƒĒ゚クãĢさらされる可čƒŊ性がありぞす。", "oauth_auto_launch": "č‡Ē動čĩˇå‹•", "oauth_auto_launch_description": "ãƒ­ã‚°ã‚¤ãƒŗãƒšãƒŧジãĢį§ģ動すると、OAuthãƒ­ã‚°ã‚¤ãƒŗãƒ•ãƒ­ãƒŧがč‡Ēå‹•įš„ãĢ開始されぞす", "oauth_auto_register": "č‡Ē動į™ģ錞", @@ -300,11 +274,9 @@ "oauth_button_text": "ボã‚ŋãƒŗãƒ†ã‚­ã‚šãƒˆ", "oauth_client_secret_description": "抟密クナイã‚ĸãƒŗãƒˆã€ãžãŸã¯å…Ŧ開クナイã‚ĸãƒŗãƒˆã§PKCEがã‚ĩポãƒŧトされãĻいãĒい場合ãĢåŋ…須です。", "oauth_enable_description": "OAuthã§ãƒ­ã‚°ã‚¤ãƒŗ", - "oauth_end_session_url_description": "ログã‚ĸã‚Ļト時、ãƒĻãƒŧã‚ļをこぎURIãĢãƒĒダイãƒŦクトする。", "oauth_mobile_redirect_uri": "ãƒĸバイãƒĢᔍãƒĒダイãƒŦクトURI", "oauth_mobile_redirect_uri_override": "ãƒĸバイãƒĢᔍãƒĒダイãƒŦクトURIīŧˆä¸Šæ›¸ãīŧ‰", "oauth_mobile_redirect_uri_override_description": "''{callback}''ãĒお、ãƒĸバイãƒĢURIがOAuthプロバイダãƒŧãĢã‚ˆãŖãĻč¨ąå¯ã•ã‚ŒãĻいãĒい場合ãĢ有劚ãĢしãĻください", - "oauth_prompt_description": "ãƒ—ãƒ­ãƒŗãƒ—ãƒˆãƒ‘ãƒŠãƒĄãƒŧã‚ŋīŧˆäž‹īŧšselect_account、login、consentīŧ‰", "oauth_role_claim": "åŊščˇã‚’ä¸ģåŧĩする", "oauth_role_claim_description": "įŽĄį†č€…ãĢãĒã‚‹ã¨į”ŗã—įĢ‹ãĻãŒčĄŒã‚ã‚ŒãŸå ´åˆã€č‡Ēå‹•įš„ãĢįŽĄį†č€…ã‚ĸクã‚ģ゚がそぎäēēãĢäģ˜ä¸Žã•れるようãĢしぞす。", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "すずãĻぎナイブナãƒĒを更新", "registration": "įŽĄį†č€…į™ģ錞", "registration_description": "あãĒãŸã¯ã‚ˇã‚šãƒ†ãƒ ãŽæœ€åˆãŽãƒĻãƒŧã‚ļãƒŧã§ã‚ã‚‹ãŸã‚ã€įŽĄį†č€…ã¨ã—ãĻå‰˛ã‚ŠåŊ“ãĻã‚‰ã‚Œã€įŽĄį†ã‚ŋ゚クを担åŊ“し、čŋŊ加ぎãƒĻãƒŧã‚ļãƒŧはあãĒたãĢã‚ˆãŖãĻäŊœæˆã•れぞす。", - "release_channel_release_candidate": "ãƒĒãƒĒãƒŧã‚šå€™čŖœ", - "release_channel_stable": "åŽ‰åŽšį‰ˆ", "remove_failed_jobs": "å¤ąæ•—ã—ãŸã‚¸ãƒ§ãƒ–ã‚’å‰Šé™¤", "require_password_change_on_login": "åˆå›žãƒ­ã‚°ã‚¤ãƒŗæ™‚ãĢパ゚ワãƒŧド変更をčĻæą‚ã™ã‚‹", "reset_settings_to_default": "č¨­åŽšã‚’ãƒ‡ãƒ•ã‚ŠãƒĢトãĢãƒĒã‚ģットしぞす", @@ -407,7 +377,7 @@ "transcoding_constant_quality_mode_description": "ICQはCQPよりå„ĒれãĻいぞすが、一部ぎハãƒŧドã‚Ļェã‚ĸã‚ĸクã‚ģナãƒŦãƒŧã‚ˇãƒ§ãƒŗãƒ‡ãƒã‚¤ã‚šã¯ã“ãŽãƒĸãƒŧドをã‚ĩポãƒŧトしãĻいぞせん。こぎã‚Ēãƒ—ã‚ˇãƒ§ãƒŗã‚’č¨­åŽšã™ã‚‹ã¨ã€å“čŗĒベãƒŧã‚šãŽã‚¨ãƒŗã‚ŗãƒŧドをäŊŋį”¨ã™ã‚‹éš›ãĢ指厚されたãƒĸãƒŧドがå„Ē先されぞす。ただし、NVEncではICQをã‚ĩポãƒŧトしãĻいãĒã„ãŸã‚ã€ã“ãŽč¨­åŽšã¯į„ĄčĻ–ã•ã‚Œãžã™ã€‚", "transcoding_constant_rate_factor": "CRF値 (-crf)", "transcoding_constant_rate_factor_description": "å‡ē力動į”ģぎ品čŗĒãƒŦベãƒĢ。H.264ぎ場合は23、HEVCぎ場合は28、VP9ぎ場合は31、AV1ぎ場合は35が一čˆŦįš„ãĒ値です。値がäŊŽã„ãģお品čŗĒãŒč‰¯ããĒã‚Šãžã™ãŒã€ãƒ•ã‚Ąã‚¤ãƒĢã‚ĩイã‚ēが大きくãĒりぞす。", - "transcoding_disabled_description": "動į”ģã‚’ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドしãĒいīŧˆä¸€éƒ¨ãŽã‚¯ãƒŠã‚¤ã‚ĸãƒŗãƒˆã§å†į”Ÿã§ããĒくãĒる可čƒŊ性ありīŧ‰", + "transcoding_disabled_description": "動į”ģã‚’ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドしãĒã„č¨­åŽšãĢしぞすが、これãĢより一部ぎクナイã‚ĸãƒŗãƒˆã§å†į”ŸãŒã§ããĒくãĒる可čƒŊ性がありぞす", "transcoding_encoding_options": "ã‚¨ãƒŗã‚ŗãƒŧドã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", "transcoding_encoding_options_description": "ã‚¨ãƒŗã‚ŗãƒŧドされた動į”ģãŽã‚ŗãƒŧãƒ‡ãƒƒã‚¯ã€č§ŖåƒåēĻ、į”ģčŗĒ、そぎäģ–ã‚Ēãƒ—ã‚ˇãƒ§ãƒŗãŽč¨­åŽšã—ãžã™", "transcoding_hardware_acceleration": "ハãƒŧドã‚Ļェã‚ĸã‚ĸクã‚ģナãƒŦãƒŧã‚ˇãƒ§ãƒŗ", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "VAAPI と QSV ぎãŋãĢéŠį”¨ã•ã‚Œãžã™ã€‚ ハãƒŧドã‚Ļェã‚ĸãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドãĢäŊŋį”¨ã•ã‚Œã‚‹driノãƒŧãƒ‰ã‚’č¨­åŽšã—ãžã™ã€‚", "transcoding_preset_preset": "プãƒĒã‚ģット (-preset)", "transcoding_preset_preset_description": "åœ§į¸Žé€ŸåēĻ。遅いプãƒĒã‚ģãƒƒãƒˆã¯ãƒ•ã‚Ąã‚¤ãƒĢã‚ĩイã‚ēã‚’å°ã•ãã—ã€į‰šåŽšãŽãƒ“ãƒƒãƒˆãƒŦãƒŧãƒˆã‚’į›Žæ¨™ã¨ã™ã‚‹å ´åˆãĢ品čŗĒを向上させぞす。VP9は 'faster'äģĨ上ぎ速åēĻã‚’į„ĄčĻ–ã—ãžã™ã€‚", - "transcoding_realtime": "ãƒĒã‚ĸãƒĢã‚ŋã‚¤ãƒ ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧド [åŽŸé¨“įš„æŠŸčƒŊ]", - "transcoding_realtime_description": "動į”ģぎ゚トãƒĒãƒŧãƒŸãƒŗã‚°å†į”Ÿä¸­ãŽãƒĒã‚ĸãƒĢã‚ŋã‚¤ãƒ ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧãƒ‰ã‚’č¨ąå¯ã€‚ å†į”Ÿå“čŗĒぎč‡Ē動切æ›ŋが有劚化されぞすが、ã‚ĩãƒŧバãƒŧぎ性čƒŊãĢã‚ˆãŖãĻは遅åģļや途切れがåĸ—加する可čƒŊ性がありぞす。", - "transcoding_realtime_enabled": "ãƒĒã‚ĸãƒĢã‚ŋã‚¤ãƒ ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドを有劚化", - "transcoding_realtime_enabled_description": "į„ĄåŠšãĢした場合、ã‚ĩãƒŧバãƒŧは新たãĒãƒĒã‚ĸãƒĢã‚ŋã‚¤ãƒ ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドã‚ģãƒƒã‚ˇãƒ§ãƒŗãŽé–‹å§‹ã‚’æ‹’åĻしぞす。", - "transcoding_realtime_resolutions": "č§ŖåƒåēĻ", - "transcoding_realtime_resolutions_description": "ãƒĒã‚ĸãƒĢã‚ŋã‚¤ãƒ ã§å¤‰æ›ã™ã‚‹ãŸã‚ãŽč§ŖåƒåēĻ。éĢ˜č§ŖåƒåēĻはã‚ĩãƒŧバãƒŧぎ変換速åēĻãĢã‚ˆãŖãĻは、動į”ģãŽå†į”ŸãĢåŊąéŸŋを及ãŧす可čƒŊ性がありぞす。", - "transcoding_realtime_video_codecs": "動į”ģãŽã‚ŗãƒŧデック", - "transcoding_realtime_video_codecs_description": "これらぎ動į”ģãŽã‚ŗãƒŧデックはãƒĒã‚ĸãƒĢã‚ŋイムでぎ変換が可čƒŊです。ã‚ģットã‚ĸãƒƒãƒ—į’°åĸƒãĢã‚ˆãŖãĻã¯ã€å†į”ŸãŽéš›ãĢ遊切ãĒã‚ŗãƒŧデックを選ãļことができぞす。AV1は、HEVCや、H.264よりもさぞざぞãĒį’°åĸƒãĢ遊しãĻいぞす。ハãƒŧドã‚Ļェã‚ĸã‚ĸクã‚ģナãƒŦãƒŧã‚ˇãƒ§ãƒŗã‚’ãŠäŊŋいぎ場合は、ã‚ĸクã‚ģナãƒŦãƒŧã‚ˇãƒ§ãƒŗã‚ĩポãƒŧトされãĻã„ã‚‹ã‚ŗãƒŧデックをおäŊŋいください。ã‚Ŋフトã‚Ļェã‚ĸで変換される場合は、変換速åēĻãĢおいãĻH.264がAV1や、HEVCより速いです。", "transcoding_reference_frames": "å‚į…§ãƒ•ãƒŦãƒŧム", "transcoding_reference_frames_description": "į‰šåŽšãŽãƒ•ãƒŦãƒŧãƒ ã‚’åœ§į¸Žã™ã‚‹ã¨ããĢå‚į…§ã™ã‚‹ãƒ•ãƒŦãƒŧムぎ数。よりéĢ˜ã„å€¤ã¯åœ§į¸ŽåŠšįŽ‡ã‚’æ”šå–„ã—ãžã™ãŒã€ã‚¨ãƒŗã‚ŗãƒŧドが遅くãĒりぞす。\"0\" ãĢč¨­åŽšã™ã‚‹ã¨ã€ã“ãŽå€¤ãŒč‡Ēå‹•įš„ãĢč¨­åŽšã•ã‚Œãžã™ã€‚", "transcoding_required_description": "č¨ąåŽšã•ã‚ŒãĻいãĒい動į”ģåŊĸåŧãŽãŋ", @@ -478,8 +440,6 @@ "user_settings_description": "ãƒĻãƒŧã‚ļãƒŧč¨­åŽšã‚’įŽĄį†ã—ãžã™", "user_successfully_removed": "ãƒĻãƒŧã‚ļãƒŧ {email} ã¯æ­Ŗå¸¸ãĢ削除されぞした。", "users_page_description": "įŽĄį†č€…į”¨ ãƒĻãƒŧã‚ļãƒŧ ペãƒŧジ", - "version_check_channel": "ãƒĒãƒĒãƒŧã‚šãƒãƒŖãƒŗãƒãƒĢ", - "version_check_channel_description": "バãƒŧã‚¸ãƒ§ãƒŗé€šįŸĨを受け取りたいãƒĒãƒĒãƒŧã‚šãƒãƒŖãƒŗãƒãƒĢを選ãļ", "version_check_enabled_description": "バãƒŧã‚¸ãƒ§ãƒŗãŽįĸēčĒã‚’æœ‰åŠšãĢする", "version_check_implications": "こぎバãƒŧã‚¸ãƒ§ãƒŗįĸēčĒæŠŸčƒŊã¯åŽšæœŸįš„ãĒ{server}とぎ通äŋĄãĢよりぞす", "version_check_settings": "バãƒŧã‚¸ãƒ§ãƒŗãƒã‚§ãƒƒã‚¯", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "į”ģåƒãŽã‚­ãƒŖãƒƒã‚ˇãƒĨを削除", "advanced_settings_clear_image_cache_error": "į”ģåƒãŽã‚­ãƒŖãƒƒã‚ˇãƒĨぎ削除ãĢå¤ąæ•—ã—ãžã—ãŸ", "advanced_settings_clear_image_cache_success": "{size}ぎ削除ãĢ成功しぞした", + "advanced_settings_enable_alternate_media_filter_subtitle": "åˆĨぎåŸēæē–ãĢåž“ãŖãĻãƒĄãƒ‡ã‚Ŗã‚ĸãƒ•ã‚Ąã‚¤ãƒĢãĢãƒ•ã‚ŖãƒĢã‚ŋãƒŧをかけãĻã€åŒæœŸã‚’čĄŒã„ãžã™ã€‚ã‚ĸプãƒĒがすずãĻぎã‚ĸãƒĢバムをčĒ­ãŋčžŧんでくれãĒい場合ãĢぎãŋ、こぎ抟čƒŊをčŠĻしãĻください。", + "advanced_settings_enable_alternate_media_filter_title": "[čŠĻ鍓運ᔍ] åˆĨぎデバイ゚ぎã‚ĸãƒĢãƒãƒ åŒæœŸãƒ•ã‚ŖãƒĢã‚ŋãƒŧをäŊŋį”¨ã™ã‚‹", "advanced_settings_log_level_title": "ログãƒŦベãƒĢ: {level}", "advanced_settings_prefer_remote_subtitle": "デバイ゚ãĢã‚ˆãŖãĻは、デバイ゚上ãĢあるã‚ĩムネイãƒĢぎロãƒŧドãĢ非常ãĢ時間がかかることがありぞす。こぎã‚Ēãƒ—ã‚ˇãƒ§ãƒŗã‚’æœ‰åŠšãĢするäē‹ãĢより、ã‚ĩãƒŧバãƒŧã‹ã‚‰į›´æŽĨį”ģ像をロãƒŧドすることが可čƒŊです。", "advanced_settings_prefer_remote_title": "ãƒĒãƒĸãƒŧトをå„Ē先する", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "ã‚Ģ゚ã‚ŋãƒ ãƒ—ãƒ­ã‚­ã‚ˇãƒ˜ãƒƒãƒ€ [åŽŸé¨“įš„]", "advanced_settings_readonly_mode_subtitle": "čĒ­ãŋå–ã‚Šå°‚į”¨ãƒĸãƒŧドを有劚ãĢã™ã‚‹ã¨ã€å†™įœŸãŽč¤‡æ•°é¸æŠžã‚„ã€å…ąæœ‰ã€å‰Šé™¤ã€ã‚­ãƒŖã‚šãƒˆæŠŸčƒŊãŒį„ĄåŠšãĢãĒã‚Šãžã™ã€‚ãƒĄã‚¤ãƒŗã‚šã‚¯ãƒĒãƒŧãƒŗãŽãƒĻãƒŧã‚ļãƒŧã‚ĸバã‚ŋãƒŧから有劚īŧį„ĄåŠšã‚’åˆ‡ã‚Šæ›ŋえられぞす", "advanced_settings_readonly_mode_title": "čĒ­ãŋå–ã‚Šå°‚į”¨ãƒĸãƒŧド", + "advanced_settings_self_signed_ssl_subtitle": "SSLぎチェックを゚キップする。č‡ĒåˇąįŊ˛åč¨ŧ明書がåŋ…čĻã§ã™ã€‚", + "advanced_settings_self_signed_ssl_title": "č‡ĒåˇąįŊ˛åč¨ŧæ˜Žæ›¸ã‚’č¨ąå¯ã™ã‚‹ [åŽŸé¨“įš„]", "advanced_settings_sync_remote_deletions_subtitle": "Webでこぎ操äŊœã‚’čĄŒãŖãŸéš›ãĢ、č‡Ēå‹•įš„ãĢã“ãŽãƒ‡ãƒã‚¤ã‚šä¸Šã‹ã‚‰é …į›Žã‚’å‰Šé™¤ãžãŸã¯åžŠå…ƒã™ã‚‹", "advanced_settings_sync_remote_deletions_title": "ãƒĒãƒĸãƒŧト削除ぎ同期 [čŠĻ鍓運ᔍ]", "advanced_settings_tile_subtitle": "čŋŊ加ãƒĻãƒŧã‚ļãƒŧč¨­åŽš", @@ -516,18 +480,31 @@ "album_delete_confirmation": "ã‚ĸãƒĢバム{album}をæœŦåŊ“ãĢ削除しぞすか?", "album_delete_confirmation_description": "こぎã‚ĸãƒĢãƒãƒ ãŒå…ąæœ‰ã•ã‚ŒãĻいるãĒら、äģ–ぎãƒĻãƒŧã‚ļãƒŧもã‚ĸãƒĢバムãĢã‚ĸクã‚ģ゚できãĒくãĒりぞす。", "album_deleted": "ã‚ĸãƒĢバムが削除されぞした", + "album_info_card_backup_album_excluded": "除外中", + "album_info_card_backup_album_included": "選択中", "album_info_updated": "ã‚ĸãƒĢãƒãƒ æƒ…å ąæ›´æ–°", + "album_leave": "ã‚ĸãƒĢバムからåŽģりぞすか?", + "album_leave_confirmation": "æœŦåŊ“ãĢ {album} からåŽģりぞすか?", "album_name": "ã‚ĸãƒĢバム名", "album_options": "ã‚ĸãƒĢãƒãƒ č¨­åŽš", "album_remove_user": "ãƒĻãƒŧã‚ļãƒŧを削除しぞすか?", "album_remove_user_confirmation": "æœŦåŊ“ãĢ{user}を削除しぞすか?", "album_search_not_found": "検į´ĸãĢä¸€č‡´ã™ã‚‹ã‚ĸãƒĢバムがありぞせん", + "album_selected": "ã‚ĸãƒĢバム選択中", "album_share_no_users": "こぎã‚ĸãƒĢバムを全ãĻぎãƒĻãƒŧã‚ļãƒŧã¨å…ąæœ‰ã—ãŸã‹ã€å…ąæœ‰ã™ã‚‹ãƒĻãƒŧã‚ļãƒŧがいãĒいようです。", "album_summary": "ã‚ĸãƒĢバムぎぞとめ", "album_updated": "ã‚ĸãƒĢバム更新", "album_updated_setting_description": "å…ąæœ‰ã‚ĸãƒĢバムãĢæ–°ã—ã„é …į›ŽãŒčŋŊ加されたとき通įŸĨを受け取る", "album_upload_assets": "ã‚ŗãƒŗãƒ”ãƒĨãƒŧã‚ŋã‹ã‚‰é …į›Žã‚’ã‚ĸップロãƒŧドし、ã‚ĸãƒĢバムãĢčŋŊ加する", + "album_user_left": "{album} をåŽģりぞした", + "album_user_removed": "{user} を削除しぞした", + "album_viewer_appbar_delete_confirm": "æœŦåŊ“ãĢこぎã‚ĸãƒĢバムを削除しぞすかīŧŸ", "album_viewer_appbar_share_err_delete": "ã‚ĸãƒĢバムぎ削除ãĢå¤ąæ•—ã—ãžã—ãŸ", + "album_viewer_appbar_share_err_leave": "退å‡ēãĢå¤ąæ•—ã—ãžã—ãŸ", + "album_viewer_appbar_share_err_remove": "ã‚ĸãƒĢãƒãƒ ã‹ã‚‰å†™įœŸã‚’å‰Šé™¤ã™ã‚‹éš›ãĢエナãƒŧį™ēį”Ÿ", + "album_viewer_appbar_share_err_title": "ã‚ŋイトãƒĢå¤‰æ›´ãŽå¤ąæ•—", + "album_viewer_appbar_share_leave": "ã‚ĸãƒĢバムから退å‡ē", + "album_viewer_appbar_share_to": "æŦĄãŽæ–šã€…ã¨å…ąæœ‰ã—ãžã™", "album_viewer_page_share_add_users": "ãƒĻãƒŧã‚ļãƒŧをčŋŊ加", "album_with_link_access": "ãƒĒãƒŗã‚¯ã‚’įŸĨãŖãĻいるäēēはčĒ°ã§ã‚‚ã€ã“ãŽã‚ĸãƒĢãƒãƒ ä¸­ãŽå†™įœŸã¨äēēį‰Šã‚’é–˛čĻ§ã§ãã‚‹ã‚ˆã†ãĢãĒりぞす。", "albums": "ã‚ĸãƒĢバム", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "新čĻã‚ĸãƒĢバムäŊœæˆæ™‚ãŽåˆæœŸčĄ¨į¤ē順.", "albums_feature_description": "äģ–ぎãƒĻãƒŧã‚ļãƒŧã¨å…ąæœ‰ã§ãã‚‹ã‚ĸã‚ģãƒƒãƒˆãŽã‚ŗãƒŦã‚¯ã‚ˇãƒ§ãƒŗ.", "albums_on_device_count": "デバイ゚上ぎã‚ĸãƒĢバム ({count})", + "albums_selected": "{count, plural, one {# ã‚ĸãƒĢバム選択中} other {# ã‚ĸãƒĢバム選択中}}", "all": "すずãĻ", "all_albums": "全ãĻぎã‚ĸãƒĢバム", "all_people": "全ãĻぎäēēį‰Š", "all_photos": "全ãĻãŽå†™įœŸ", "all_videos": "全ãĻぎ動į”ģ", "allow_dark_mode": "ダãƒŧクãƒĸãƒŧãƒ‰ã‚’č¨ąå¯", + "allow_edits": "įˇ¨é›†ã‚’č¨ąå¯", "allow_public_user_to_download": "一čˆŦãƒĻãƒŧã‚ļãƒŧãĢよるダã‚Ļãƒŗãƒ­ãƒŧãƒ‰ã‚’č¨ąå¯", "allow_public_user_to_upload": "一čˆŦãƒĻãƒŧã‚ļãƒŧãĢよるã‚ĸップロãƒŧãƒ‰ã‚’č¨ąå¯", "allowed": "č¨ąå¯ã•ã‚ŒãĻいる", @@ -549,12 +528,14 @@ "always_keep": "常ãĢäŋæŒ", "always_keep_photos_hint": "「゚トãƒŦãƒŧã‚¸ã‚’č§Ŗæ”žã€ã§ã€å…¨ãĻãŽå†™įœŸãŒã“ãŽãƒ‡ãƒã‚¤ã‚šãĢäŋæŒã•れぞす。", "always_keep_videos_hint": "「゚トãƒŦãƒŧã‚¸ã‚’č§Ŗæ”žã€ã§ã€å…¨ãĻぎ動į”ģがこぎデバイ゚ãĢäŋæŒã•れぞす。", + "anti_clockwise": "åæ™‚č¨ˆå›žã‚Š", "api_key": "APIキãƒŧ", "api_key_description": "こぎ値は一回ぎãŋ襨į¤ēされぞす。 ã‚Ļã‚Ŗãƒŗãƒ‰ã‚Ļを閉じる前ãĢåŋ…ãšã‚ŗãƒ”ãƒŧしãĻください。", "api_key_empty": "APIキãƒŧ名はįŠēį™ŊãĢできぞせん", "api_keys": "APIキãƒŧ", "app_architecture_variant": "CPUã‚ĸãƒŧã‚­ãƒ†ã‚¯ãƒãƒŖ (ABI)", "app_bar_signout_dialog_content": "ã‚ĩã‚¤ãƒŗã‚ĸã‚ĻトしぞすかīŧŸ", + "app_bar_signout_dialog_ok": "はい", "app_bar_signout_dialog_title": "ã‚ĩã‚¤ãƒŗã‚ĸã‚Ļト", "app_download_links": "ã‚ĸプãƒĒぎダã‚Ļãƒŗãƒ­ãƒŧドãƒĒãƒŗã‚¯", "app_settings": "ã‚ĸプãƒĒč¨­åŽš", @@ -565,19 +546,27 @@ "archive": "ã‚ĸãƒŧã‚Ģイブ", "archive_action_prompt": "ã‚ĸãƒŧã‚ĢイブãĢ{count}é …į›ŽčŋŊ加しぞした", "archive_or_unarchive_photo": "å†™įœŸã‚’ã‚ĸãƒŧã‚Ģイブぞたはã‚ĸãƒŧã‚Ģã‚¤ãƒ–č§Ŗé™¤", + "archive_page_no_archived_assets": "ã‚ĸãƒŧã‚Ģã‚¤ãƒ–ã—ãŸå†™įœŸãžãŸã¯ãƒ“ãƒ‡ã‚Ēがありぞせん", + "archive_page_title": "ã‚ĸãƒŧã‚Ģイブ ({count})", "archive_size": "ã‚ĸãƒŧã‚Ģイブã‚ĩイã‚ē", "archive_size_description": "ダã‚Ļãƒŗãƒ­ãƒŧドぎã‚ĸãƒŧã‚Ģイブ ã‚ĩイã‚ēã‚’č¨­åŽš(GiB 単äŊ)", "archived": "ã‚ĸãƒŧã‚Ģイブ", "archived_count": "ã‚ĸãƒŧã‚Ģイブされた{count, plural, other {#å€‹ãŽé …į›Ž}}", "are_these_the_same_person": "これらは同じäēēį‰Šã§ã™ã‹?", "are_you_sure_to_do_this": "æœŦåŊ“ãĢã“ã‚Œã‚’čĄŒã„ãžã™ã‹?", + "array_field_not_fully_supported": "é…åˆ—ãƒ•ã‚ŖãƒŧãƒĢドは手動でJSONįˇ¨é›†ã™ã‚‹åŋ…čĻãŒã‚ã‚Šãžã™", + "asset_action_delete_err_read_only": "čĒ­ãŋå–ã‚Šå°‚į”¨ãŽé …į›Žã¯å‰Šé™¤ã§ããžã›ã‚“ã€‚ã‚šã‚­ãƒƒãƒ—ã—ãžã™", + "asset_action_share_err_offline": "ã‚Ēãƒ•ãƒŠã‚¤ãƒŗãŽé …į›Žã‚’ã‚˛ãƒƒãƒˆã§ããžã›ã‚“ã€‚ã‚šã‚­ãƒƒãƒ—ã—ãžã™", "asset_added_to_album": "ã‚ĸãƒĢバムãĢčŋŊ加", "asset_adding_to_album": "ã‚ĸãƒĢバムãĢčŋŊ加しãĻいぞすâ€Ļ", "asset_created": "é …į›ŽãŒäŊœæˆã•れぞした", - "asset_day_count": "{date}: {count, plural, one {# ã‚ĸã‚ģット} other {# ã‚ĸã‚ģット}}", "asset_description_updated": "é …į›ŽãŽčĒŦ明文が更新されぞした", + "asset_filename_is_offline": "é …į›Ž {filename} がã‚Ēãƒ•ãƒŠã‚¤ãƒŗã§ã™", + "asset_has_unassigned_faces": "é …į›ŽãĢ名前ぎついãĻいãĒいäēēį‰ŠãŽéĄ”ãŒã‚ã‚Šãžã™", "asset_hashing": "ãƒãƒƒã‚ˇãƒĨč¨ˆįŽ—ä¸­â€Ļ", "asset_list_group_by_sub_title": "グãƒĢãƒŧプ分け", + "asset_list_layout_settings_dynamic_layout_title": "ダイナミックãƒŦイã‚ĸã‚Ļト", + "asset_list_layout_settings_group_automatically": "č‡Ē動", "asset_list_layout_settings_group_by": "å†™įœŸãŽã‚°ãƒĢãƒŧプ分け", "asset_list_layout_settings_group_by_month_day": "月 + æ—Ĩ", "asset_list_layout_sub_title": "ãƒŦイã‚ĸã‚Ļト", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "iCloudä¸ŠãŽå†™įœŸ/動į”ģがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“ã§ã—ãŸã€‚", "asset_offline": "é …į›ŽãŒã‚Ēãƒ•ãƒŠã‚¤ãƒŗã§ã™", "asset_offline_description": "ã“ãŽå¤–éƒ¨é …į›Žã¯ãƒ‡ã‚Ŗã‚šã‚¯ä¸ŠãĢもうありぞせん。Immichã‚ĩãƒŧバãƒŧãŽįŽĄį†č€…ãĢ逪įĩĄã‚’しãĻください。", + "asset_restored_successfully": "垊元できぞした", "asset_skipped": "゚キップ済", "asset_skipped_in_trash": "ã‚´ãƒŸįŽąãŽä¸­", + "asset_trashed": "é …į›ŽãŒå‰Šé™¤ã•ã‚Œãžã—ãŸ", "asset_troubleshoot": "é …į›Žã‚’ãƒˆãƒŠãƒ–ãƒĢã‚ˇãƒĨãƒŧã‡ŗ", "asset_uploaded": "ã‚ĸップロãƒŧド済", "asset_uploading": "ã‚ĸップロãƒŧド中â€Ļ", "asset_viewer_settings_subtitle": "ã‚ŽãƒŖãƒŠãƒĒãƒŧビãƒĨãƒŧã‚ĸãƒŧãĢé–ĸã™ã‚‹č¨­åŽš", "asset_viewer_settings_title": "ã‚ĸã‚ģットビãƒĨãƒŧã‚ĸãƒŧ", "assets": "ã‚ĸã‚ģット", + "assets_added_count": "{count, plural, one {#個} other {#個}}ぎã‚ĸã‚ģットをčŋŊ加しぞした", "assets_added_to_album_count": "{count, plural, one {#個} other {#個}}ぎã‚ĸã‚ģットをã‚ĸãƒĢバムãĢčŋŊ加しぞした", - "assets_added_to_album_partial_count": "{totalCount}{totalCount, plural, one {é …į›Ž} other {é …į›Ž}}中{successCount}ぎã‚ĸã‚ģットがã‚ĸãƒĢバムãĢčŋŊ加されぞした", "assets_added_to_albums_count": "{assetTotal, plural, one {# é …į›Ž} other {# é …į›Ž}}を{albumTotal, plural, one {# つぎã‚ĸãƒĢバム} other {# つぎã‚ĸãƒĢバム}}ãĢčŋŊ加しぞした", "assets_cannot_be_added_to_album_count": "{count, plural, one {ã‚ĸã‚ģット} other {ã‚ĸã‚ģット}} はã‚ĸãƒĢバムãĢčŋŊ加できぞせん", "assets_cannot_be_added_to_albums": "{count, plural, one {é …į›Ž} other {é …į›Ž}} をおぎã‚ĸãƒĢバムãĢもčŋŊ加できぞせんでした", "assets_count": "{count, plural, one {#個} other {#個}}ぎã‚ĸã‚ģット", + "assets_deleted_permanently": "{count}é …į›Žã‚’åŽŒå…¨ãĢ削除しぞした", + "assets_deleted_permanently_from_server": "ã‚ĩãƒŧバãƒŧ上ぎ{count}é …į›Žã‚’åŽŒå…¨ãĢ削除しぞした", + "assets_downloaded_failed": "{count, plural, other {# ãƒ•ã‚Ąã‚¤ãƒĢ中 {error} ãƒ•ã‚Ąã‚¤ãƒĢぎダã‚Ļãƒŗãƒ­ãƒŧドãĢå¤ąæ•—ã—ãžã—ãŸ}}", + "assets_downloaded_successfully": "{count, plural, other {# ãƒ•ã‚Ąã‚¤ãƒĢぎダã‚Ļãƒŗãƒ­ãƒŧドãĢ成功しぞした}}", "assets_moved_to_trash_count": "{count, plural, one {#é …į›Ž} other {#é …į›Ž}}ã‚’ã‚´ãƒŸįŽąãĢį§ģ動しぞした", "assets_permanently_deleted_count": "{count, plural, one {#é …į›Ž} other {#é …į›Ž}}を厌全ãĢ削除しぞした", "assets_removed_count": "{count, plural, one {#é …į›Ž} other {#é …į›Ž}}を削除しぞした", + "assets_removed_permanently_from_device": "デバイ゚から{count}é …į›Žã‚’åŽŒå…¨ãĢ削除しぞした", "assets_restore_confirmation": "ごãŋįŽąãŽã‚ĸã‚ģットをすずãĻ垊元しãĻもよろしいですか? こぎ操äŊœã‚’å…ƒãĢæˆģすことはできぞせん! ã‚Ēãƒ•ãƒŠã‚¤ãƒŗãŽã‚ĸã‚ģãƒƒãƒˆã¯ã“ãŽæ–šæŗ•ã§ã¯åžŠå…ƒã§ããžã›ã‚“ã€‚", "assets_restored_count": "{count, plural, one {#} other {#}}é …į›Žã‚’åžŠå…ƒã—ãžã—ãŸ", + "assets_restored_successfully": "{count}é …į›Žã‚’åžŠå…ƒã—ãžã—ãŸ", "assets_trashed": "{count}é …į›Žã‚’ã‚´ãƒŸįŽąãĢį§ģ動しぞした", "assets_trashed_count": "{count, plural, one {#個} other {#個}}ぎã‚ĸã‚ģットをごãŋįŽąãĢį§ģ動しぞした", + "assets_trashed_from_server": "ã‚ĩãƒŧバãƒŧ上ぎ{count}é …į›Žã‚’ã‚´ãƒŸįŽąãĢį§ģ動しぞした", "assets_were_part_of_album_count": "{count, plural, one {個} other {個}}ぎã‚ĸã‚ģットはæ—ĸãĢã‚ĸãƒĢバムぎ一部です", "assets_were_part_of_albums_count": "{count, plural, one {é …į›Žã¯} other {é …į›Žã¯}}すでãĢã‚ĸãƒĢバムãĢčŋŊ加されãĻいるもぎでした", "authorized_devices": "čĒå¯æ¸ˆãŋデバイ゚", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧをč‡Ēå‹•å†į”Ÿ", "back": "æˆģる", "back_close_deselect": "æˆģã‚‹ã€é–‰ã˜ã‚‹ã€é¸æŠžč§Ŗé™¤", + "background_backup_running_error": "バックグナã‚Ļãƒŗãƒ‰ãŽãƒãƒƒã‚¯ã‚ĸップがすでãĢčĄŒã‚ã‚ŒãĻいる最中です。そぎため、マニãƒĨã‚ĸãƒĢでぎバックã‚ĸップを開始することはできぞせん", "background_location_permission": "バックグナã‚Ļãƒŗãƒ‰äŊįŊŽæƒ…å ąã‚ĸクã‚ģ゚", "background_location_permission_content": "æ­Ŗå¸¸ãĢWi-Fiぎ名前(SSID)ã‚’į˛åž—ã™ã‚‹ãĢはã‚ĸプãƒĒが常ãĢčŠŗį´°ãĒäŊįŊŽæƒ…å ąãĢã‚ĸクã‚ģ゚できるåŋ…čĻãŒã‚ã‚Šãžã™", "background_options": "バックグナã‚Ļãƒŗãƒ‰ãŽå‹•äŊœã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", "backup": "バックã‚ĸップ", + "backup_album_selection_page_albums_device": "デバイ゚上ぎã‚ĸãƒĢバム({count})", "backup_album_selection_page_albums_tap": "ã‚ŋップで選択、ダブãƒĢã‚ŋップで除外", "backup_album_selection_page_assets_scatter": "ã‚ĸãƒĢバムを選択ãƒģ除外しãĻバックã‚ĸãƒƒãƒ—ã™ã‚‹å†™įœŸã‚’é¸ãļ。 (åŒã˜å†™įœŸãŒč¤‡æ•°ãŽã‚ĸãƒĢバムãĢį™ģéŒ˛ã•ã‚ŒãĻいることがあるため)", "backup_album_selection_page_select_albums": "ã‚ĸãƒĢバムを選択", "backup_album_selection_page_selection_info": "選択ãƒģ除外中ぎã‚ĸãƒĢバム", + "backup_album_selection_page_total_assets": "選択されたã‚ĸãƒĢãƒãƒ ãŽå†™įœŸã¨å‹•į”ģぎ数", "backup_albums_sync": "ã‚ĸãƒĢバム同期įŠļ態をバックã‚ĸップ", + "backup_all": "すずãĻ", + "backup_background_service_backup_failed_message": "ã‚ĸップロãƒŧドãĢå¤ąæ•—ã—ãžã—ãŸã€‚ãƒĒトナイ中â€Ļ", "backup_background_service_complete_notification": "å†™įœŸ/動į”ģぎバックã‚ĸップが厌äē†ã—ぞした", + "backup_background_service_connection_failed_message": "ã‚ĩãƒŧバãƒŧãĢæŽĨįļšã§ããžã›ã‚“。ãƒĒトナイ中â€Ļ", + "backup_background_service_current_upload_notification": "{filename}をã‚ĸップロãƒŧド中", "backup_background_service_default_notification": "æ–°ã—ã„å†™įœŸã‚’įĸēčĒä¸­â€Ļ", + "backup_background_service_error_title": "バックã‚ĸップエナãƒŧ", "backup_background_service_in_progress_notification": "バックã‚ĸップ中â€Ļ", + "backup_background_service_upload_failure_notification": "{filename}ぎã‚ĸップロãƒŧドãĢå¤ąæ•—", "backup_controller_page_albums": "ã‚ĸãƒĢバム", + "backup_controller_page_background_app_refresh_disabled_content": "バックグナã‚Ļãƒŗãƒ‰ã§å†™įœŸãŽãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—ã‚’čĄŒã„ãŸã„å ´åˆã¯ã€ãƒãƒƒã‚¯ã‚°ãƒŠã‚Ļãƒŗãƒ‰æ›´æ–°ã‚’\nč¨­åŽš > 一čˆŦ > Appぎバックグナã‚Ļãƒŗãƒ‰æ›´æ–°\nからã‚ĒãƒŗãĢしãĻください。", + "backup_controller_page_background_app_refresh_disabled_title": "バックグナã‚Ļãƒŗãƒ‰æ›´æ–°ã¯ã‚ĒフãĢãĒãŖãĻいぞす", + "backup_controller_page_background_app_refresh_enable_button_text": "č¨­åŽšã‚’é–‹ã", "backup_controller_page_background_battery_info_link": "čŠŗį´°", "backup_controller_page_background_battery_info_message": "バックグナã‚Ļãƒŗãƒ‰å‡Ļį†ã‚’æ­Ŗå¸¸ãĢ動äŊœã•せるためãĢは、Immichã‚ĸプãƒĒãĢéŠį”¨ã•ã‚ŒãĻいるバッテãƒĒãƒŧぎ最遊化をã‚ĒフãĢしãĻください。\n\nデバイ゚ãĢã‚ˆãŖãĻč¨­åŽšæ–šæŗ•ãŒį•°ãĒりぞすぎで各々čĒŋずãĻください。", "backup_controller_page_background_battery_info_ok": "äē†č§Ŗ", "backup_controller_page_background_battery_info_title": "バッテãƒĒãƒŧぎ最遊化", + "backup_controller_page_background_charging": "充é›ģ中ぎãŋ", + "backup_controller_page_background_configure_error": "バックグナã‚Ļãƒŗãƒ‰ã‚ĩãƒŧãƒ“ã‚šãŽč¨­åŽšãĢå¤ąæ•—", "backup_controller_page_background_delay": "æ–°ã—ã„é …į›ŽãŽãƒãƒƒã‚¯ã‚ĸップ開始ぞで垅つ時間: {duration}", + "backup_controller_page_background_description": "ã‚ĸプãƒĒを開いãĻいãĒいときもバックã‚ĸãƒƒãƒ—ã‚’čĄŒã„ãžã™", + "backup_controller_page_background_is_off": "ãƒãƒƒã‚¯ã‚°ãƒŠãƒŗãƒ‰ã‚ĩãƒŧビ゚がã‚ĒフãĢãĒãŖãĻいぞす", + "backup_controller_page_background_is_on": "ãƒãƒƒã‚¯ã‚°ãƒŠãƒŗãƒ‰ã‚ĩãƒŧビ゚がã‚ĒãƒŗãĢãĒãŖãĻいぞす", + "backup_controller_page_background_turn_off": "バックグナã‚Ļãƒŗãƒ‰ã‚ĩãƒŧビ゚をã‚ĒフãĢする", + "backup_controller_page_background_turn_on": "バックグナã‚Ļãƒŗãƒ‰ã‚ĩãƒŧビ゚をã‚ĒãƒŗãĢする", + "backup_controller_page_background_wifi": "WiFiæŽĨįļšä¸­ãŽãŋ", "backup_controller_page_backup": "バックã‚ĸップ", "backup_controller_page_backup_selected": "選択中: ", "backup_controller_page_backup_sub": "バックã‚ĸãƒƒãƒ—ã•ã‚ŒãŸå†™įœŸã¨å‹•į”ģぎ数", + "backup_controller_page_created": "äŊœæˆæ—Ĩ: {date}", + "backup_controller_page_desc_backup": "ã‚ĸプãƒĒを開いãĻいるときãĢå†™įœŸã¨å‹•į”ģをバックã‚ĸップしぞす。", "backup_controller_page_excluded": "除外中ぎã‚ĸãƒĢバム: ", + "backup_controller_page_failed": "å¤ąæ•—: ({count})", + "backup_controller_page_filename": "ãƒ•ã‚Ąã‚¤ãƒĢ名: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "バックã‚ĸãƒƒãƒ—æƒ…å ą", "backup_controller_page_none_selected": "ãĒし", "backup_controller_page_remainder": "掋り", "backup_controller_page_remainder_sub": "æŽ‹ã‚ŠãŽå†™įœŸã¨å‹•į”ģぎ数", "backup_controller_page_server_storage": "ã‚ĩãƒŧバãƒŧ厚量", + "backup_controller_page_start_backup": "バックã‚ĸップ開始", + "backup_controller_page_status_off": "バックã‚ĸップがã‚ĒフãĢãĒãŖãĻいぞす", + "backup_controller_page_status_on": "バックã‚ĸップがã‚ĒãƒŗãĢãĒãŖãĻいぞす", "backup_controller_page_storage_format": "äŊŋᔍ䏭: {used} / {total}", "backup_controller_page_to_backup": "バックã‚ĸップされるã‚ĸãƒĢバム", "backup_controller_page_total_sub": "選択されたã‚ĸãƒĢãƒãƒ ãŽå†™įœŸã¨å‹•į”ģぎ数", + "backup_controller_page_turn_off": "バックã‚ĸップをã‚ĒフãĢする", + "backup_controller_page_turn_on": "バックã‚ĸップをã‚ĒãƒŗãĢする", + "backup_controller_page_uploading_file_info": "ã‚ĸップロãƒŧãƒ‰ä¸­ãŽãƒ•ã‚Ąã‚¤ãƒĢ", + "backup_err_only_album": "最äŊŽ1つぎã‚ĸãƒĢバムを選択しãĻください", "backup_error_sync_failed": "同期ãĢå¤ąæ•—ã—ãžã—ãŸã€‚ãƒãƒƒã‚¯ã‚ĸップができぞせん。", "backup_info_card_assets": "å†™įœŸã¨å‹•į”ģ", + "backup_manual_cancelled": "ã‚­ãƒŖãƒŗã‚ģãƒĢされぞした", + "backup_manual_in_progress": "ã‚ĸップロãƒŧãƒ‰ãŒé€˛čĄŒä¸­ã§ã™ã€‚åžŒã§ã‚‚ã†ä¸€åēĻčŠĻしãĻください", + "backup_manual_success": "成功", + "backup_manual_title": "ã‚ĸップロãƒŧドįŠļæŗ", "backup_options": "バックã‚ĸップã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", + "backup_options_page_title": "バックã‚ĸップã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", + "backup_setting_subtitle": "ã‚ĸップロãƒŧドãĢé–ĸã™ã‚‹č¨­åŽš", "backup_settings_subtitle": "ã‚ĸップロãƒŧãƒ‰č¨­åŽšã‚’įŽĄį†", + "backup_upload_details_page_more_details": "ã‚ŋãƒƒãƒ—ã§čŠŗį´°é–˛čϧ", "backward": "新しい斚へ", - "battery_optimization_backup_reliability": "バッテãƒĒãƒŧãŽæœ€éŠåŒ–ã‚’į„ĄåŠšãĢすると、バックグナã‚Ļãƒŗãƒ‰ãƒãƒƒã‚¯ã‚ĸップぎäŋĄé ŧ性が向上しぞす", "biometric_auth_enabled": "į”ŸäŊ“čĒč¨ŧを有劚化しぞした", "biometric_locked_out": "į”ŸäŊ“čĒč¨ŧãĢより、ã‚ĸクã‚ģ゚できぞせん", "biometric_no_options": "į”ŸäŊ“čĒč¨ŧã‚’åˆŠį”¨ã§ããžã›ã‚“", "biometric_not_available": "ã“ãŽãƒ‡ãƒã‚¤ã‚šã§ã¯į”ŸäŊ“čĒč¨ŧã‚’ã”åˆŠį”¨ã„ãŸã ã‘ãžã›ã‚“", + "birthdate_saved": "į”Ÿåš´æœˆæ—ĨãŒæ­Ŗå¸¸ãĢäŋå­˜ã•れぞした", "birthdate_set_description": "į”Ÿåš´æœˆæ—Ĩã¯ã€å†™įœŸæ’ŽåŊąæ™‚ぎこぎäēēį‰ŠãŽåš´éŊĸã‚’č¨ˆįŽ—ã™ã‚‹ãŸã‚ãĢäŊŋį”¨ã•ã‚Œãžã™ã€‚", "blurred_background": "ãŧã‚„ã‘ãŸčƒŒæ™¯", - "browse_templates": "ãƒ†ãƒŗãƒ—ãƒŦãƒŧãƒˆã‚’čĄ¨į¤ē", "bugs_and_feature_requests": "バグと抟čƒŊぎãƒĒクエ゚ト", "build": "ビãƒĢド", "build_image": "ビãƒĢãƒ‰ã‚¤ãƒĄãƒŧジ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "æœŦåŊ“ãĢ{count, plural, one {#個} other {#個}}ãŽé‡č¤‡ã—ãŸé …į›Žã‚’äŋæŒã—ぞすか?これãĢよりäŊ•も削除されずãĢé‡č¤‡ã‚°ãƒĢãƒŧãƒ—ãŒč§Ŗæąēされぞす。", "bulk_trash_duplicates_confirmation": "æœŦåŊ“ãĢ{count, plural, one {#個} other {#個}}ãŽé‡č¤‡ã—ãŸé …į›Žã‚’ä¸€æ‹ŦでごãŋįŽąãĢį§ģ動しぞすか?これãĢã‚ˆã‚Šå„é‡č¤‡ä¸­ãŽæœ€å¤§ã‚ĩイã‚ēãŽé …į›ŽãŒäŋæŒã•れ、äģ–ぎ全ãĻãŽé‡č¤‡ã¯ã”ãŋįŽąãĢį§ģ動されぞす。", "buy": "Immichをčŗŧå…Ĩ", + "cache_settings_clear_cache_button": "ã‚­ãƒŖãƒƒã‚ˇãƒĨをクãƒĒã‚ĸ", + "cache_settings_clear_cache_button_title": "ã‚­ãƒŖãƒƒã‚ˇãƒĨを削除 (ã‚­ãƒŖãƒƒã‚ˇãƒĨãŒå†į”Ÿæˆã•ã‚Œã‚‹ãžã§ã€ã‚ĸプãƒĒぎパフりãƒŧãƒžãƒŗã‚šãŒč‘—ã—ãäŊŽä¸‹ã—ぞす)", + "cache_settings_duplicated_assets_clear_button": "クãƒĒã‚ĸ", + "cache_settings_duplicated_assets_subtitle": "ã‚ĩãƒŧバãƒŧãĢã‚ĸップロãƒŧド済ãŋとčĒč­˜ã•ã‚ŒãŸå†™įœŸã‚„å‹•į”ģぎ数", + "cache_settings_duplicated_assets_title": "é‡č¤‡ã—ãŸé …į›Žæ•°: ({count})", + "cache_settings_statistics_album": "ナイブナãƒĒぎã‚ĩムネイãƒĢ", + "cache_settings_statistics_full": "フãƒĢį”ģ像", + "cache_settings_statistics_shared": "å…ąæœ‰ã‚ĸãƒĢバムぎã‚ĩムネイãƒĢ", + "cache_settings_statistics_thumbnail": "ã‚ĩムネイãƒĢ", + "cache_settings_statistics_title": "ã‚­ãƒŖãƒƒã‚ˇãƒĨ", + "cache_settings_subtitle": "ã‚­ãƒŖãƒƒã‚ˇãƒĨぎ動äŊœã‚’変更する", + "cache_settings_tile_subtitle": "ロãƒŧã‚ĢãƒĢ゚トãƒŦãƒŧジぎ挙動をįĸēčĒã™ã‚‹", + "cache_settings_tile_title": "ロãƒŧã‚ĢãƒĢ゚トãƒŦãƒŧジ", + "cache_settings_title": "ã‚­ãƒŖãƒƒã‚ˇãƒĨãŽč¨­åŽš", "camera": "ã‚ĢãƒĄãƒŠãƒ–ãƒŠãƒŗãƒ‰", "camera_brand": "ã‚ĢãƒĄãƒŠãƒ–ãƒŠãƒŗãƒ‰", "camera_model": "ã‚ĢãƒĄãƒŠãƒĸデãƒĢ", @@ -678,10 +729,10 @@ "cannot_update_the_description": "čĒŦ明を更新できぞせん", "cast": "ã‚­ãƒŖã‚šãƒˆ", "cast_description": "ã‚­ãƒŖã‚šãƒˆå…ˆãŽå€™čŖœã‚’č¨­åŽš", - "change": "変更", "change_date": "æ—Ĩ時を変更", "change_description": "čĒŦ明文を変更", "change_display_order": "襨į¤ē順を変更", + "change_expiration_time": "有劚期限を変更", "change_location": "場所を変更", "change_name": "名前を変更", "change_name_successfully": "åå‰ã‚’æ­Ŗå¸¸ãĢ変更しぞした", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "パ゚ワãƒŧãƒ‰ãŒä¸€č‡´ã—ãžã›ã‚“", "change_password_form_reenter_new_password": "再åēĻパ゚ワãƒŧドをå…Ĩ力しãĻください", "change_pin_code": "PINã‚ŗãƒŧドを変更", + "change_trigger": "トãƒĒã‚Ŧãƒŧを変更", + "change_trigger_prompt": "トãƒĒã‚Ŧãƒŧを変えãĻもよいですかīŧŸã‚ĸã‚¯ã‚ˇãƒ§ãƒŗãƒģãƒ•ã‚ŖãƒĢã‚ŋãƒŧが全ãĻ削除されぞす", "change_your_password": "パ゚ワãƒŧドを変更しぞす", "changed_visibility_successfully": "非表į¤ēč¨­åŽšã‚’æ­Ŗå¸¸ãĢ変更しぞした", "charging": "充é›ģ中", "charging_requirement_mobile_backup": "バックグナã‚Ļãƒŗãƒ‰ã§ãŽãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—ã‚’čĄŒã†ãŸã‚ãĢは、デバイ゚が充é›ģ中であるåŋ…čĻãŒã‚ã‚Šãžã™", + "check_corrupt_asset_backup": "į ´æã•ã‚ŒãĻã„ã‚‹é …į›Žã‚’æŽĸす", + "check_corrupt_asset_backup_button": "ãƒã‚§ãƒƒã‚¯ã‚’čĄŒã†", + "check_corrupt_asset_backup_description": "å†™įœŸã‚„å‹•į”ģãĒおが全ãĻã‚ĸップロãƒŧドしįĩ‚えãĻからWi-FiãĢæŽĨį™‚ぎãŋãƒã‚§ãƒƒã‚¯ã‚’čĄŒãĒãŖãĻください。äŊœæĨ­ãŒåތäē†ã™ã‚‹ãĢは数分かかる場合がありぞす。", "check_logs": "ログをįĸēčĒ", "checksum": "チェックã‚ĩム", - "choose": "選択", "choose_matching_people_to_merge": "įĩąåˆå…ˆãŽäēēį‰Šã‚’é¸ã‚“ã§ãã ã•ã„", "city": "市į”ē村", "cleanup_confirm_description": "ã‚ĩãƒŧバãƒŧãĢバックã‚ĸップ済ãŋãŽå†™įœŸ/動į”ģīŧˆ{date}äģĨ前ãĢäŊœæˆīŧ‰ã‚’{count}äģļį™ēčĻ‹ã—ãžã—ãŸã€‚ã“ãŽãƒ‡ãƒã‚¤ã‚šã‹ã‚‰ãƒ­ãƒŧã‚ĢãƒĢã‚ŗãƒ”ãƒŧを削除しぞすかīŧŸ", @@ -719,10 +774,11 @@ "clear": "クãƒĒã‚ĸ", "clear_all": "全ãĻクãƒĒã‚ĸ", "clear_all_recent_searches": "全ãĻぎ最čŋ‘ぎ検į´ĸをクãƒĒã‚ĸ", - "clear_failed_count": "クãƒĒã‚ĸå¤ąæ•— ({count})", "clear_file_cache": "ãƒ•ã‚Ąã‚¤ãƒĢã‚­ãƒŖãƒƒã‚ˇãƒĨを削除", "clear_message": "ãƒĄãƒƒã‚ģãƒŧジをクãƒĒã‚ĸ", "clear_value": "値をクãƒĒã‚ĸ", + "client_cert_dialog_msg_confirm": "äē†č§Ŗ", + "client_cert_enter_password": "パ゚ワãƒŧドをå…Ĩ力", "client_cert_import": "ã‚¤ãƒŗãƒãƒŧト", "client_cert_import_success_msg": "クナイã‚ĸãƒŗãƒˆč¨ŧ明書が導å…Ĩされぞした", "client_cert_invalid_msg": "パ゚ワãƒŧãƒ‰ãŒé–“é•ãŖãĻいるかč¨ŧæ˜Žæ›¸ãŒį„ĄåŠšã§ã™", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "クナイã‚ĸãƒŗãƒˆč¨ŧ明書が削除されぞした", "client_cert_subtitle": "PKCS12 (.p12 .pfx) フりãƒŧマットぎãŋ寞åŋœã—ãĻいぞす。č¨ŧ明書ぎ導å…Ĩã‚„å‰Šé™¤ã¯ãƒ­ã‚°ã‚¤ãƒŗå‰ãŽãŋčĄŒãˆãžã™", "client_cert_title": "SSLクナイã‚ĸãƒŗãƒˆč¨ŧ明書 [åŽŸé¨“įš„]", + "clockwise": "æ™‚č¨ˆå›žã‚Š", "close": "閉じる", "collapse": "åą•é–‹", "collapse_all": "全ãĻåą•é–‹", "color": "ã‚Ģナãƒŧ", + "color_theme": "ã‚Ģナãƒŧテãƒŧマ", "command": "ã‚ŗãƒžãƒŗãƒ‰", "command_palette_prompt": "ペãƒŧジ、ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗã€ã‚ŗãƒžãƒŗãƒ‰ã‚’į´ æ—Šãæ¤œį´ĸ", "command_palette_to_close": "閉じる", @@ -747,7 +805,6 @@ "comments_are_disabled": "ã‚ŗãƒĄãƒŗãƒˆã¯į„ĄåŠšåŒ–ã•ã‚ŒãĻいぞす", "common_create_new_album": "ã‚ĸãƒĢバムをäŊœæˆ", "completed": "厌äē†", - "configuration": "č¨­åŽš", "confirm": "įĸēčĒ", "confirm_admin_password": "įŽĄį†č€…ãƒ‘ã‚šãƒ¯ãƒŧドをįĸēčĒ", "confirm_delete_face": "æœŦåŊ“ãĢ『{name}ã€ãŽéĄ”ã‚’é …į›Žã‹ã‚‰å‰Šé™¤ã—ãžã™ã‹?", @@ -762,16 +819,19 @@ "contain": "収める", "context": "å†™įœŸãŽå†…åŽš", "continue": "įļšã‘ã‚‹", - "control_bottom_app_bar_add_tags": "ã‚ŋグをčŋŊ加", + "control_bottom_app_bar_create_new_album": "ã‚ĸãƒĢバムをäŊœæˆ", + "control_bottom_app_bar_delete_from_immich": "ã‚ĩãƒŧバãƒŧから削除", "control_bottom_app_bar_delete_from_local": "デバイ゚上から削除", "control_bottom_app_bar_edit_location": "äŊįŊŽæƒ…å ąã‚’įˇ¨é›†", "control_bottom_app_bar_edit_time": "æ’ŽåŊąæ—Ĩæ™‚ã‚’įˇ¨é›†", + "control_bottom_app_bar_share_link": "å…ąæœ‰ãƒĒãƒŗã‚¯", + "control_bottom_app_bar_share_to": "æŦĄãŽãƒĻãƒŧã‚ļãƒŧãĢå…ąæœ‰:", "control_bottom_app_bar_trash_from_immich": "ã‚´ãƒŸįŽąãĢå…Ĩれる", "copied_image_to_clipboard": "į”ģ像をクãƒĒップボãƒŧドãĢã‚ŗãƒ”ãƒŧしぞした。", "copied_to_clipboard": "クãƒĒップボãƒŧドãĢã‚ŗãƒ”ãƒŧしぞした!", "copy_error": "ã‚ŗãƒ”ãƒŧぎエナãƒŧ", + "copy_file_path": "ãƒ•ã‚Ąã‚¤ãƒĢãƒ‘ã‚šã‚’ã‚ŗãƒ”ãƒŧ", "copy_image": "į”ģåƒã‚’ã‚ŗãƒ”ãƒŧ", - "copy_json": "JSONã‚’ã‚ŗãƒ”ãƒŧ", "copy_link": "ãƒĒãƒŗã‚¯ã‚’ã‚ŗãƒ”ãƒŧ", "copy_link_to_clipboard": "ãƒĒãƒŗã‚¯ã‚’ã‚¯ãƒĒップボãƒŧドãĢã‚ŗãƒ”ãƒŧ", "copy_password": "パ゚ワãƒŧãƒ‰ã‚’ã‚ŗãƒ”ãƒŧ", @@ -789,6 +849,7 @@ "create_link_to_share": "å…ąæœ‰ãƒĒãƒŗã‚¯ã‚’äŊœã‚‹", "create_link_to_share_description": "ãƒĒãƒŗã‚¯ã‚’įŸĨãŖãĻいるäēēå…¨å“ĄãŒé¸æŠžã—ãŸå†™įœŸã‚’é–˛čĻ§ã§ãã‚‹ã‚ˆã†ãĢãĒりぞす", "create_new": "新čĻäŊœæˆ", + "create_new_face": "æ–°ã—ã„éĄ”ã‚’äŊœæˆ", "create_new_person": "新しいäēēį‰Šã‚’äŊœæˆ", "create_new_person_hint": "é¸æŠžã—ãŸå†™įœŸ/動į”ģを新しいäēēį‰Šã¨ã—ãĻå‰˛ã‚ŠåŊ“ãĻ", "create_new_user": "新čĻãƒĻãƒŧã‚ļãƒŧぎäŊœæˆ", @@ -803,35 +864,39 @@ "create_workflow": "ワãƒŧクフロãƒŧをäŊœæˆ", "created": "äŊœæˆ", "created_at": "äŊœæˆ:", - "creating_linked_albums": "ãƒĒãƒŗã‚¯ã•ã‚ŒãŸã‚ĸãƒĢバムをäŊœæˆä¸­â€Ļ", + "creating_linked_albums": "ãƒĒãƒŗã‚¯ã•ã‚ŒãŸã‚ĸãƒĢバムをäŊœæˆä¸­ãƒģãƒģãƒģ", "crop": "クロップ", + "crop_aspect_ratio_fixed": "å›ē厚", "crop_aspect_ratio_free": "č‡Ēį”ą", "crop_aspect_ratio_original": "ã‚ĒãƒĒジナãƒĢ", "crop_aspect_ratio_square": "゚クエã‚ĸ", + "curated_object_page_title": "čĸĢ写äŊ“", "current_device": "įžåœ¨ãŽãƒ‡ãƒã‚¤ã‚š", "current_pin_code": "įžåœ¨ãŽPINã‚ŗãƒŧド", "current_server_address": "įžåœ¨ãŽã‚ĩãƒŧバãƒŧURL", "custom_date": "ã‚Ģ゚ã‚ŋムæ—Ĩäģ˜", "custom_locale": "言čĒžã¨åœ°åŸŸãŽæ‰‹å‹•č¨­åŽš", "custom_locale_description": "é¸æŠžã—ãŸč¨€čĒžã¨åœ°åŸŸãŽč¨­åŽšãĢåž“ãŖãĻ、æ—Ĩäģ˜ãƒģ時åˆģãƒģ数値を書åŧč¨­åŽšã—ãžã™", + "custom_url": "ã‚Ģ゚ã‚ŋムURL", "cutoff_date_description": "å†™įœŸã‚’äŋæŒã™ã‚‹æœŸé–“:", "cutoff_day": "{count, plural, one {(æ—Ĩ)} other {(æ—Ĩ)}}", "cutoff_year": "{count, plural, one {åš´} other {åš´}}", + "daily_title_text_date": "MM DD, EE", + "daily_title_text_date_year": "yyyy MM DD, EE", "dark": "ダãƒŧクãƒĸãƒŧド", "dark_theme": "ダãƒŧクãƒĸãƒŧドãĢ切りæ›ŋえ", "date": "æ—Ĩäģ˜", "date_after": "こぎæ—ĨäģĨ降", "date_and_time": "æ—Ĩäģ˜ã¨æ™‚é–“", "date_before": "こぎæ—ĨäģĨ前", - "date_of_birth": "į”Ÿåš´æœˆæ—Ĩ", + "date_format": "MM DD, EE â€ĸ hh:mm", "date_of_birth_saved": "į”Ÿåš´æœˆæ—Ĩã¯æ­Ŗå¸¸ãĢäŋå­˜ã•れぞした", "date_range": "æ—Ĩäģ˜", - "date_time_original": "元ぎæ—Ĩäģ˜/時åˆģ", - "day": "æ—Ĩ", + "day": "ナイトãƒĸãƒŧド", "days": "æ—Ĩ", "deduplicate_all": "全ãĻé‡č¤‡æŽ’é™¤", - "default_quality_subtitle": "å…ąæœ‰ã™ã‚‹éš›ãĢäŊŋわれる品čŗĒã€‚å…ąæœ‰ã‚’é•ˇæŠŧしすることで毎回選択することができぞす。", - "default_share_quality": "デフりãƒĢãƒˆãŽå…ąæœ‰ã•ã‚Œã‚‹å“čŗĒ", + "default_locale": "デフりãƒĢãƒˆãŽč¨€čĒžã¨åœ°åŸŸ", + "default_locale_description": "ブナã‚Ļã‚ļãŽč¨€čĒžã¨åœ°åŸŸãŽč¨­åŽšãĢåŸēãĨいãĻ、æ—Ĩäģ˜ã¨æ•°å€¤ã‚’フりãƒŧマットしぞす", "delete": "削除", "delete_action_confirmation_message": "ã“ãŽé …į›Žã‚’å‰Šé™¤ã—ãžã™ã‹īŧŸãžãšã€ã“ãŽé …į›Žã¯ã‚ĩãƒŧバãƒŧä¸ŠãŽã‚´ãƒŸįŽąã¸į§ģ動されぞす。そぎ垌、あãĒたぎデバイ゚上から削除するかをæąēめãĻいただきぞす", "delete_action_prompt": "{count}é …į›Žã‚’å‰Šé™¤ã—ãžã—ãŸ", @@ -840,6 +905,8 @@ "delete_dialog_alert": "é¸æŠžã•ã‚ŒãŸé …į›Žã¯ã‚ĩãƒŧバãƒŧã¨ãƒ‡ãƒã‚¤ã‚šãŽä¸Ąæ–šã‹ã‚‰åŽŒå…¨ãĢ削除されぞす", "delete_dialog_alert_local": "é¸æŠžã•ã‚ŒãŸé …į›Žã¯ãƒ‡ãƒã‚¤ã‚šã‹ã‚‰åŽŒå…¨ãĢ削除されぞすが、ã‚ĩãƒŧバãƒŧãĢは掋りぞす", "delete_dialog_alert_local_non_backed_up": "é¸æŠžã•ã‚ŒãŸé …į›ŽãŽä¸€éƒ¨ã¯ã‚ĩãƒŧバãƒŧãĢバックã‚ĸップされãĻおらず、デバイ゚から厌全ãĢ削除されぞす", + "delete_dialog_alert_remote": "é¸æŠžã•ã‚ŒãŸé …į›Žã¯ã‚ĩãƒŧバãƒŧから厌全ãĢ削除されぞす", + "delete_dialog_ok_force": "削除しぞす", "delete_dialog_title": "厌全ãĢ削除", "delete_duplicates_confirmation": "æœŦåŊ“ãĢã“ã‚Œã‚‰ãŽé‡č¤‡ã‚’åŽŒå…¨ãĢ削除しぞすか?", "delete_face": "éĄ”ãŽå‰Šé™¤", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "æœŦåŊ“ãĢ{tagName}ã‚ŋグを削除しぞすか?", "delete_user": "ãƒĻãƒŧã‚ļãƒŧを削除", "deleted_shared_link": "å…ąæœ‰ãƒĒãƒŗã‚¯ã‚’å‰Šé™¤", + "deletes_missing_assets": "ãƒ‡ã‚Ŗã‚šã‚¯ã‹ã‚‰ãĒくãĒãŖãŸã‚ĸã‚ģットを削除する", "description": "čĒŦ明", + "description_input_hint_text": "čĒŦ明をčŋŊ加", + "description_input_submit_error": "čĒŦæ˜ŽãŽįˇ¨é›†ãĢå¤ąæ•—ã—ãžã—ãŸã€‚čŠŗį´°ã¯ãƒ­ã‚°ã‚’įĸēčĒã—ãĻください。", "deselect_all": "すずãĻãŽé¸æŠžã‚’č§Ŗé™¤", "details": "čŠŗį´°", "direction": "斚向", "disable": "į„ĄåŠšåŒ–", "disabled": "į„ĄåŠš", + "disallow_edits": "įˇ¨é›†ã‚’č¨ąå¯ã—ãĒい", "discord": "Discord", "discover": "æŽĸį´ĸ", "discovered_devices": "į™ēčĻ‹ã•ã‚ŒãŸãƒ‡ãƒã‚¤ã‚š", "dismiss_all_errors": "全ãĻぎエナãƒŧã‚’į„ĄčĻ–", + "dismiss_error": "エナãƒŧã‚’į„ĄčĻ–", "display_options": "襨į¤ēã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", "display_order": "襨į¤ē順", "display_original_photos": "ã‚ĒãƒĒジナãƒĢãŽå†™įœŸã‚’čĄ¨į¤ē", @@ -876,9 +948,11 @@ "documentation": "ドキãƒĨãƒĄãƒŗãƒˆ", "done": "厌äē†", "download": "ダã‚Ļãƒŗãƒ­ãƒŧド", + "download_action_prompt": "{count}é …į›Žã‚’ãƒ€ã‚Ļãƒŗãƒ­ãƒŧド中", "download_canceled": "ダã‚Ļãƒŗãƒ­ãƒŧãƒ‰ãŒã‚­ãƒŖãƒŗã‚ģãƒĢされぞした", "download_complete": "ダã‚Ļãƒŗãƒ­ãƒŧド厌äē†", "download_enqueue": "ダã‚Ļãƒŗãƒ­ãƒŧド垅抟中", + "download_error": "ダã‚Ļãƒŗãƒ­ãƒŧドエナãƒŧ", "download_failed": "ダã‚Ļãƒŗãƒ­ãƒŧãƒ‰å¤ąæ•—", "download_finished": "ダã‚Ļãƒŗãƒ­ãƒŧドįĩ‚äē†", "download_include_embedded_motion_videos": "埋めčžŧぞれた動į”ģ", @@ -888,15 +962,15 @@ "download_paused": "ダã‚Ļãƒŗãƒ­ãƒŧド一時停æ­ĸ中", "download_settings": "ダã‚Ļãƒŗãƒ­ãƒŧド", "download_settings_description": "å†™įœŸ/動į”ģぎダã‚Ļãƒŗãƒ­ãƒŧドãĢé–ĸé€Ŗã™ã‚‹č¨­åŽšã‚’įŽĄį†ã—ãžã™", + "download_started": "ダã‚Ļãƒŗãƒ­ãƒŧド開始", + "download_sucess": "ダã‚Ļãƒŗãƒ­ãƒŧド成功", + "download_sucess_android": "DCIM/ImmichãĢäŋå­˜ã•れぞした", "download_waiting_to_retry": "ãƒĒトナイ中", "downloading": "ダã‚Ļãƒŗãƒ­ãƒŧド中", "downloading_asset_filename": "å†™įœŸ/動į”ģ {filename} をダã‚Ļãƒŗãƒ­ãƒŧド中", "downloading_from_icloud": "iCloudからダã‚Ļãƒŗãƒ­ãƒŧド", "downloading_media": "ダã‚Ļãƒŗãƒ­ãƒŧド中", - "drag_to_reorder": "ドナッグしãĻä¸Ļãŗæ›ŋえ", "drop_files_to_upload": "ãƒ•ã‚Ąã‚¤ãƒĢをドロップしãĻã‚ĸップロãƒŧド", - "duplicate": "複čŖŊ", - "duplicate_workflow": "ワãƒŧクフロãƒŧã‚’č¤‡čŖŊ", "duplicates": "重複", "duplicates_description": "各グãƒĢãƒŧプをįĸēčĒã—ã€é‡č¤‡ã—ãĻã„ã‚‹é …į›Žã‚’æ•´į†ã—ãĻください。", "duration": "間隔", @@ -908,7 +982,9 @@ "edit_date_and_time": "æ’ŽåŊąæ—Ĩæ™‚ã‚’įˇ¨é›†", "edit_date_and_time_action_prompt": "{count} 個ぎã‚ĸイテムぎæ—Ĩæ™‚ãŒįˇ¨é›†ã•ã‚Œãžã—ãŸ", "edit_date_and_time_by_offset": "ã‚Ēフã‚ģãƒƒãƒˆã‚’į”¨ã„ãĻæ—Ĩäģ˜ã‚’変更", + "edit_date_and_time_by_offset_interval": "新しいæ—Ĩäģ˜į¯„回: {from} - {to}", "edit_description": "čĒŦæ˜Žæ–‡ã‚’įˇ¨é›†", + "edit_description_prompt": "新しいčĒŦ明文を選んでください:", "edit_exclusion_pattern": "除外パã‚ŋãƒŧãƒŗã‚’įˇ¨é›†", "edit_faces": "éĄ”ã‚’įˇ¨é›†", "edit_key": "キãƒŧã‚’įˇ¨é›†", @@ -923,6 +999,9 @@ "edit_user": "ãƒĻãƒŧã‚ļãƒŧã‚’įˇ¨é›†", "edit_workflow": "ワãƒŧクフロãƒŧã‚’įˇ¨é›†", "editor": "ᎍ集į”ģéĸ", + "editor_close_without_save_prompt": "å¤‰æ›´ã¯į ´æŖ„ã•ã‚Œãžã™", + "editor_close_without_save_title": "ᎍ集į”ģéĸを閉じぞすか?", + "editor_confirm_reset_all_changes": "æœŦåŊ“ãĢ全ãĻぎ変更をãƒĒã‚ģットしぞすかīŧŸ", "editor_discard_edits_confirm": "įˇ¨é›†ã‚’į ´æŖ„ã™ã‚‹", "editor_discard_edits_prompt": "įˇ¨é›†å†…åŽšãŒäŋå­˜ã•れãĻã„ãžã›ã‚“ã€‚į ´æŖ„ã—ãžã™ã‹īŧŸ", "editor_discard_edits_title": "įˇ¨é›†ã‚’į ´æŖ„ã—ãžã™ã‹īŧŸ", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "PINã‚ŗãƒŧドをå…Ĩ力しãĻください", "enter_your_pin_code_subtitle": "éĩäģ˜ããƒ•りãƒĢダãƒŧį”¨ãŽPINã‚ŗãƒŧドをå…Ĩ力しãĻください", "error": "エナãƒŧ", + "error_change_sort_album": "ã‚ĸãƒĢãƒãƒ ãŽčĄ¨į¤ē順ぎ変更ãĢå¤ąæ•—ã—ãžã—ãŸ", "error_delete_face": "å†™įœŸ/動į”ģã‹ã‚‰éĄ”ãŽå‰Šé™¤ãŒã§ããžã›ã‚“ã§ã—ãŸ", + "error_getting_places": "場所ぎ取垗ãĢå¤ąæ•—ã—ãžã—ãŸ", "error_loading_albums": "ã‚ĸãƒĢバムぎčĒ­ãŋčžŧãŋエナãƒŧ", "error_loading_image": "į”ģ像ぎčĒ­ãŋčžŧãŋエナãƒŧ", "error_loading_partners": "パãƒŧトナãƒŧぎčĒ­ãŋčžŧãŋãĢå¤ąæ•—ã—ãžã—ãŸ: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "プロダクトキãƒŧを削除できぞせんでした", "failed_to_reset_pin_code": "PINã‚ŗãƒŧドぎãƒĒã‚ģットãĢå¤ąæ•—ã—ãžã—ãŸ", "failed_to_stack_assets": "å†™įœŸ/動į”ģを゚ã‚ŋックできぞせんでした", - "failed_to_tag_assets": "å†™įœŸ/動į”ģぎã‚ŋグäģ˜ã‘ãĢå¤ąæ•—", "failed_to_unstack_assets": "å†™įœŸ/動į”ģを゚ã‚ŋãƒƒã‚¯č§Ŗé™¤ãŒã§ããžã›ã‚“ã§ã—ãŸ", "failed_to_update_notification_status": "通įŸĨ゚テãƒŧã‚ŋ゚ぎ更新ãĢå¤ąæ•—ã—ãžã—ãŸ", "incorrect_email_or_password": "ãƒĄãƒŧãƒĢã‚ĸドãƒŦ゚ぞたはパ゚ワãƒŧãƒ‰ãŒé–“é•ãŖãĻいぞす", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "čĒŦ明をčŋŊ加", "exif_bottom_sheet_description_error": "čĒŦ明文をã‚ĸップデãƒŧトできぞせんでした", + "exif_bottom_sheet_details": "čŠŗį´°", + "exif_bottom_sheet_location": "æ’ŽåŊąå ´æ‰€", "exif_bottom_sheet_no_description": "čĒŦ明ãĒし", + "exif_bottom_sheet_people": "äēēį‰Š", + "exif_bottom_sheet_person_add_person": "名前をčŋŊ加", "exit_slideshow": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧをįĩ‚わる", "expand": "åą•é–‹", "expand_all": "全ãĻåą•é–‹", + "experimental_settings_new_asset_list_subtitle": "čŖŊäŊœé€”中 (WIP)", + "experimental_settings_new_asset_list_title": "čŠĻé¨“įš„ãĒグãƒĒッドを有劚化", + "experimental_settings_subtitle": "čŠĻé¨“įš„æŠŸčƒŊãĢつきč‡Ēåˇąč˛Ŧäģģでīŧ", + "experimental_settings_title": "čŠĻé¨“įš„æŠŸčƒŊ", "expire_after": "有劚期限", "expired": "有劚期限が切れぞした", "expires_date": "{date} ãĢå¤ąåŠš", @@ -1103,40 +1191,41 @@ "export_as_json": "JSONとしãĻエク゚ポãƒŧト", "export_database": "デãƒŧã‚ŋベãƒŧ゚をå‡ē力", "export_database_description": "SQLiteデãƒŧã‚ŋベãƒŧ゚をå‡ē力", - "exposure_time": "éœ˛å…‰æ™‚é–“", "extension": "æ‹Ąåŧĩ子", "external": "外部", "external_libraries": "外部ナイブナãƒĒ", "external_network": "外部ぎネットワãƒŧク", "external_network_sheet_info": "指厚されたWi-FiãĢįš‹ãŒãŖãĻいãĒい時ã‚ĸプãƒĒはã‚ĩãƒŧバãƒŧへぎæŽĨįļšã‚’指厚されたURLã§čĄŒã„ãžã™ã€‚å„Ē先順äŊã¯ä¸Šã‹ã‚‰ä¸‹ã§ã™", - "f_number": "F値", "face_unassigned": "æœĒå‰˛ã‚ŠåŊ“ãĻ", "failed": "å¤ąæ•—", "failed_count": "å¤ąæ•—: {count}", "failed_to_authenticate": "čĒč¨ŧãĢå¤ąæ•—ã—ãžã—ãŸ", - "failed_to_delete_file": "ãƒ•ã‚Ąã‚¤ãƒĢぎ削除ãĢå¤ąæ•—ã—ãžã—ãŸã€‚", "failed_to_load_assets": "å†™įœŸ/動į”ģぎロãƒŧドãĢå¤ąæ•—ã—ãžã—ãŸ", "failed_to_load_folder": "フりãƒĢダãƒŧぎčĒ­ãŋčžŧãŋãĢå¤ąæ•—", "favorite": "お気ãĢå…Ĩり", "favorite_action_prompt": "{count}é …į›Žã‚’ãŠæ°—ãĢå…ĨりãĢčŋŊ加しぞした", "favorite_or_unfavorite_photo": "å†™įœŸã‚’ãŠæ°—ãĢいりãĢį™ģéŒ˛ãžãŸã¯č§Ŗé™¤", "favorites": "お気ãĢå…Ĩり", + "favorites_page_no_favorites": "お気ãĢå…Ĩりį™ģéŒ˛ã•ã‚ŒãŸé …į›ŽãŒã‚ã‚Šãžã›ã‚“", "feature_photo_updated": "äēēį‰Šį”ģ像が更新されぞした", "features": "抟čƒŊ", + "features_in_development": "開į™ē中ぎ抟čƒŊ", "features_setting_description": "ã‚ĸプãƒĒぎ抟čƒŊã‚’įŽĄį†ã™ã‚‹", "file_name_or_extension": "ãƒ•ã‚Ąã‚¤ãƒĢåãžãŸã¯æ‹Ąåŧĩ子", "file_name_text": "ãƒ•ã‚Ąã‚¤ãƒĢ名", + "file_name_with_value": "ãƒ•ã‚Ąã‚¤ãƒĢ名: {file_name}", "file_size": "ãƒ•ã‚Ąã‚¤ãƒĢã‚ĩイã‚ē", "filename": "ãƒ•ã‚Ąã‚¤ãƒĢ名", "filetype": "ãƒ•ã‚Ąã‚¤ãƒĢã‚ŋイプ", "filter": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧ", + "filter_description": "å¯žčąĄã¨ã™ã‚‹ã‚ĸã‚ģットぎæŠŊå‡ēæĄäģļ", "filter_people": "äēēį‰Šã‚’įĩžã‚Ščžŧãŋ", "filter_places": "å ´æ‰€ã‚’ãƒ•ã‚ŖãƒĢã‚ŋãƒŧ", "filter_tags": "ã‚ŋグでįĩžã‚Ščžŧむ", "filters": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧ", + "find_them_fast": "名前で検į´ĸしãĻį´ æ—Šãį™ēčĻ‹", "first": "はじめ", "fix_incorrect_match": "é–“é•ãŖãŸä¸€č‡´ã‚’äŋŽæ­Ŗ", - "focal_length": "į„Ļį‚ščˇé›ĸ", "folder": "フりãƒĢダãƒŧ", "folder_not_found": "フりãƒĢダãƒŧがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“ã§ã—ãŸ", "folders": "フりãƒĢダ", @@ -1147,7 +1236,6 @@ "free_up_space_description": "バックã‚ĸãƒƒãƒ—ã•ã‚ŒãŸå†™įœŸã¨å‹•į”ģをあãĒãŸãŽãƒ‡ãƒã‚¤ã‚šãŽã‚´ãƒŸįŽąã¸į§ģ動し、゚トãƒŦãƒŧã‚¸ã‚’č§Ŗæ”žã—ãžã™ã€‚ã‚ŗãƒ”ãƒŧはã‚ĩãƒŧバ上ãĢ厉全ãĢäŋįŽĄã•ã‚ŒãĻいぞす。", "free_up_space_settings_subtitle": "デバイ゚ぎ゚トãƒŦãƒŧã‚¸ã‚’č§Ŗæ”žã™ã‚‹", "full_path": "フãƒĢパ゚: {path}", - "full_path_or_folder": "フãƒĢパ゚ぞたはフりãƒĢダ", "gcast_enabled": "Google Cast", "gcast_enabled_description": "こぎ抟čƒŊは動äŊœãŽãŸã‚ãĢGoogleぎãƒĒã‚Ŋãƒŧ゚をčĒ­ãŋčžŧãŋぞす。", "general": "一čˆŦ", @@ -1168,6 +1256,7 @@ "group_owner": "æ‰€æœ‰č€…ã§ã‚°ãƒĢãƒŧプ化", "group_places_by": "グãƒĢãƒŧプ分け...", "group_year": "嚴でグãƒĢãƒŧプ化", + "haptic_feedback_switch": "ãƒãƒ—ãƒ†ã‚Ŗãƒƒã‚¯ãƒ•ã‚Ŗãƒŧドバック", "haptic_feedback_title": "ãƒãƒ—ãƒ†ã‚Ŗãƒƒã‚¯ãƒ•ã‚Ŗãƒŧドバックを有劚ãĢする", "has_quota": "クりãƒŧã‚ŋ有り", "hash_asset": "é …į›Žã‚’ãƒãƒƒã‚ˇãƒĨ化する", @@ -1188,12 +1277,29 @@ "hide_schema": "゚キãƒŧãƒžã‚’éžčĄ¨į¤ē", "hide_text_recognition": "文字čĒč­˜ã‚’éžčĄ¨į¤ē", "hide_unnamed_people": "名前がãĒいäēēį‰Šã‚’éžčĄ¨į¤ē", + "home_page_add_to_album_conflicts": "{album}ãĢ{added}å€‹ãŽå†™įœŸ/動į”ģをčŋŊ加しぞした。čŋŊ加済ãŋぎ{failed}個ぱキップしぞした。", + "home_page_add_to_album_err_local": "ぞだã‚ĸップロãƒŧドされãĻãĒã„å†™įœŸ/動į”ģは、ã‚ĸãƒĢバムãĢčŋŊ加できぞせん", + "home_page_add_to_album_success": "{album}ãĢ{added}å€‹ãŽå†™įœŸ/動į”ģをčŋŊ加しぞした", + "home_page_album_err_partner": "ぞだパãƒŧトナãƒŧãŽå†™įœŸã¯ã‚ĸãƒĢバムãĢčŋŊ加できぞせん。゚キップしぞす (ã‚ĸップデãƒŧãƒˆã‚’ãŠåž…ãĄãã ã•ã„)", + "home_page_archive_err_local": "ぞだã‚ĸップロãƒŧドされãĻãĒã„é …į›Žã¯ã‚ĸãƒŧã‚Ģイブできぞせん", + "home_page_archive_err_partner": "パãƒŧトナãƒŧãŽå†™įœŸã¯ã‚ĸãƒŧã‚Ģイブできぞせん。゚キップしぞす", "home_page_building_timeline": "ã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗæ§‹į¯‰ä¸­", + "home_page_delete_err_partner": "パãƒŧトナãƒŧãŽå†™įœŸã¯å‰Šé™¤ã§ããžã›ã‚“ã€‚ã‚šã‚­ãƒƒãƒ—ã—ãžã™", + "home_page_delete_remote_err_local": "ã‚ĩãƒŧバãƒŧ上ぎã‚ĸイテムぎ削除ぎ選択ãĢデバイ゚上ぎã‚ĸイテムがåĢぞれãĻいるぎで゚キップしぞす", + "home_page_favorite_err_local": "ぞだã‚ĸップロãƒŧドされãĻãĒã„é …į›Žã¯ãŠæ°—ãĢå…Ĩりį™ģéŒ˛ã§ããžã›ã‚“", + "home_page_favorite_err_partner": "パãƒŧトナãƒŧãŽå†™įœŸã¯ãŠæ°—ãĢå…Ĩりį™ģéŒ˛ã§ããžã›ã‚“ã€‚ã‚šã‚­ãƒƒãƒ—ã—ãžã™", + "home_page_first_time_notice": "はじめãĻã‚ĸプãƒĒをäŊŋう場合、ã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗãĢå†™įœŸã‚’čĄ¨į¤ēするためãĢã‚ĸãƒĢバムを選択しãĻください", + "home_page_locked_error_local": "デバイ゚上ãĢしかãĒã„é …į›Žã‚’éĩäģ˜ããƒ•りãƒĢダãƒŧãĢį§ģ動することはできぞせん。゚キップしぞす", + "home_page_locked_error_partner": "パãƒŧトナãƒŧãŽé …į›Žã‚’éĩäģ˜ããƒ•りãƒĢダãƒŧãĢį§ģ動することはできぞせん。゚キップしぞす", + "home_page_share_err_local": "ロãƒŧã‚ĢãƒĢぎãŋãŽé …į›Žã‚’ãƒĒãƒŗã‚¯ã§å…ąæœ‰ã¯ã§ããžã›ã‚“ã€‚ã‚šã‚­ãƒƒãƒ—ã—ãžã™", + "home_page_upload_err_limit": "1回でã‚ĸップロãƒŧãƒ‰ã§ãã‚‹å†™įœŸãŽæ•°ã¯30枚です。゚キップしぞす", "host": "ポト", "hour": "時間", "hours": "時間", "id": "ID", "idle": "ã‚ĸイドãƒĒãƒŗã‚°", + "ignore_icloud_photos": "iCloudä¸ŠãŽå†™įœŸã‚’ã‚šã‚­ãƒƒãƒ—", + "ignore_icloud_photos_description": "iCloudãĢäŋå­˜ã•れãĻã„ã‚‹å†™įœŸ/動į”ģはImmichã‚ĩãƒŧバãƒŧãĢã‚ĸップロãƒŧドされぞせん", "image": "å†™įœŸ", "image_alt_text_date": "{isVideo, select, true {動į”ģ} other {å†™įœŸ}}は{date} ãĢæ’ŽåŊą", "image_alt_text_date_1_person": "{date}ぎ、{person1}とぎ{isVideo, select, true {動į”ģ} other {į”ģ像}}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{date}ぎ、{country}、{city}でぎ{person1}と{person2}ぎ{isVideo, select, true {動į”ģ} other {į”ģ像}}", "image_alt_text_date_place_3_people": "{date}ぎ、{country}、{city}でぎ{person1}と{person2}、そしãĻ{person3}ぎ{isVideo, select, true {動į”ģ} other {į”ģ像}}", "image_alt_text_date_place_4_or_more_people": "{date}ぎ、{country}、{city}でぎ{person1}と{person2}、そしãĻそぎäģ–{additionalCount, number}äēēぎ{isVideo, select, true {動į”ģ} other {į”ģ像}}", + "image_saved_successfully": "į”ģ像がäŋå­˜ã•れぞした", + "image_viewer_page_state_provider_download_started": "ダã‚Ļãƒŗãƒ­ãƒŧドが始ぞりぞす", + "image_viewer_page_state_provider_download_success": "ダã‚Ļãƒŗãƒ­ãƒŧド成功", + "image_viewer_page_state_provider_share_error": "å…ąæœ‰ã‚¨ãƒŠãƒŧ", "immich_logo": "Immichぎロゴ", "immich_web_interface": "ImmichぎWebã‚¤ãƒŗã‚ŋãƒŧフェãƒŧ゚", "import_from_json": "JSONã‹ã‚‰ã‚¤ãƒŗãƒãƒŧト", @@ -1219,7 +1329,6 @@ "individual_share": "1æžšãŽå…ąæœ‰", "individual_shares": "1æžšãšã¤ãŽå…ąæœ‰", "info": "æƒ…å ą", - "integrity_checks": "デãƒŧã‚ŋぎ整合性チェック", "interval": { "day_at_onepm": "毎æ—Ĩ 午垌1時", "hours": "{hours, plural, one {1時間} other {{hours, number}時間}}ごと", @@ -1230,9 +1339,16 @@ "invalid_date_format": "æ—Ĩäģ˜ãŽãƒ•りãƒŧãƒžãƒƒãƒˆãŒį„ĄåŠšã§ã™", "invite_people": "äēē々を招垅", "invite_to_album": "ã‚ĸãƒĢバムãĢ招垅", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "{dateTime}ãĢ取垗をčŠĻãŋぞした", + "ios_debug_info_last_sync_at": "前回ぎ同期時åˆģ: {dateTime}", + "ios_debug_info_no_processes_queued": "バックグナã‚Ļãƒŗãƒ‰ã§å‡Ļį†åž…ãĄãŽé …į›Žã¯ã‚ã‚Šãžã›ã‚“", + "ios_debug_info_no_sync_yet": "バックグナã‚Ļãƒŗãƒ‰åŒæœŸã¯åŽŸčĄŒã•ã‚ŒãĻいぞせん", + "ios_debug_info_processes_queued": "バックグナã‚Ļãƒŗãƒ‰å‡Ļį†åž…ãĄ: {count, plural, one {{count}} other {{count}}}", + "ios_debug_info_processing_ran_at": "å‡Ļį†åŽŸčĄŒæ¸ˆãŋ: {dateTime}", "items_count": "{count, plural, one {#個} other {#個}}ãŽé …į›Ž", "jobs": "ジョブ", + "json_editor": "JSONã‚¨ãƒ‡ã‚Ŗã‚ŋãƒŧ", + "json_error": "JSONエナãƒŧ", "keep": "äŋæŒ", "keep_albums": "ã‚ĸãƒĢバムをäŋæŒ", "keep_albums_count": "{count}個ぎã‚ĸãƒĢバムを掋す", @@ -1259,12 +1375,14 @@ "leave": "退å‡ē", "leave_album": "ã‚ĸãƒĢバムから抜ける", "lens_model": "ãƒŦãƒŗã‚ēãƒĸデãƒĢ", - "less": "少ãĒい", "let_others_respond": "äģ–ぎãƒĻãƒŧã‚ļãƒŧぎčŋ”äŋĄã‚’č¨ąå¯ã™ã‚‹", "level": "ãƒŦベãƒĢ", "library": "ナイブナãƒĒ", "library_add_folder": "フりãƒĢダãƒŧをčŋŊ加", "library_edit_folder": "フりãƒĢダãƒŧã‚’įˇ¨é›†", + "library_options": "ナイブナãƒĒč¨­åŽš", + "library_page_device_albums": "デバイ゚上ぎã‚ĸãƒĢバム", + "library_page_new_album": "新しいã‚ĸãƒĢバム", "library_page_sort_asset_count": "é …į›ŽãŽæ•°", "library_page_sort_created": "äŊœæˆæ—Ĩ時", "library_page_sort_last_modified": "最įĩ‚変更", @@ -1274,14 +1392,11 @@ "light_theme": "ナイトテãƒŧマãĢ切りæ›ŋえ", "like": "いいね", "like_deleted": "いいねが削除されぞした", - "link": "ãƒĒãƒŗã‚¯", "link_motion_video": "ãƒĸãƒŧã‚ˇãƒ§ãƒŗãƒ“ãƒ‡ã‚ĒぎãƒĒãƒŗã‚¯", "link_to_docs": "čŠŗį´°ã¯ãƒ‰ã‚­ãƒĨãƒĄãƒŗãƒˆã‚’å‚į…§ã—ãĻください。", "link_to_oauth": "OAuthへãƒĒãƒŗã‚¯ã™ã‚‹", "linked_oauth_account": "ãƒĒãƒŗã‚¯ã•ã‚ŒãŸOAuthã‚ĸã‚Ģã‚Ļãƒŗãƒˆ", "list": "ãƒĒ゚ト", - "live": "ナイブ", - "load_more": "さらãĢ襨į¤ē", "loading": "čĒ­ãŋčžŧãŋ中", "loading_search_results_failed": "検į´ĸįĩæžœã‚’čĒ­ãŋčžŧめぞせんでした", "local": "ロãƒŧã‚ĢãƒĢ", @@ -1310,9 +1425,11 @@ "login": "ãƒ­ã‚°ã‚¤ãƒŗ", "login_disabled": "ãƒ­ã‚°ã‚¤ãƒŗã¯į„ĄåŠšåŒ–ã•ã‚Œãžã—ãŸ", "login_form_api_exception": "APIエナãƒŧがį™ēį”Ÿã—ãžã—ãŸã€‚URLをチェックしãĻもう一åēĻおčŠĻしください。", + "login_form_back_button_text": "æˆģる", "login_form_email_hint": "hoge@email.com", "login_form_endpoint_hint": "http://ã‚ĩãƒŧバãƒŧぎã‚ĸドãƒŦ゚:ポãƒŧトį•Ēåˇ", "login_form_endpoint_url": "ã‚ĩãƒŧバãƒŧãŽã‚¨ãƒŗãƒ‰ãƒã‚¤ãƒŗãƒˆURL", + "login_form_err_http": "http://かhttps://かを指厚しãĻください", "login_form_err_invalid_email": "ãƒĄãƒŧãƒĢã‚ĸドãƒŦã‚šãŒį„ĄåŠšã§ã™", "login_form_err_invalid_url": "į„ĄåŠšãĒURL", "login_form_err_leading_whitespace": "最初ãĢ゚ペãƒŧ゚がåĢぞれãĻいぞす", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "ãƒ­ã‚°ã‚¤ãƒŗã‚¨ãƒŠãƒŧがį™ēį”Ÿã—ãžã—ãŸã€‚ã‚ĩãƒŧバãƒŧぎURLãƒģãƒĄãƒŧãƒĢã‚ĸドãƒŦ゚ãƒģパ゚ワãƒŧドを再įĸēčĒã—ãĻください。", "login_form_handshake_exception": "Handshake Exceptionエナãƒŧがį™ēį”Ÿã—ãžã—ãŸã€‚č‡ĒåˇąįŊ˛åč¨ŧæ˜Žæ›¸ã‚’č¨­åŽšã‹ã‚‰æœ‰åŠšãĢしãĻください。", "login_form_password_hint": "パ゚ワãƒŧド", - "login_form_server_empty": "URLをå…Ĩ力しãĻください", + "login_form_save_login": "ãƒ­ã‚°ã‚¤ãƒŗã‚’äŋæŒ", + "login_form_server_empty": "URLをå…Ĩ力", "login_form_server_error": "ã‚ĩãƒŧバãƒŧãĢæŽĨįļšã§ããžã›ã‚“", - "login_has_been_disabled": "ãƒ­ã‚°ã‚¤ãƒŗã¯į„ĄåŠšåŒ–ã•ã‚ŒãĻいぞす", + "login_has_been_disabled": "ãƒ­ã‚°ã‚¤ãƒŗã¯į„ĄåŠšåŒ–ã•ã‚ŒãĻいぞす。", "login_password_changed_error": "パ゚ワãƒŧドぎ変更でエナãƒŧがį™ēį”Ÿã—ãžã—ãŸ", "login_password_changed_success": "パ゚ワãƒŧドぎ変更ãĢ成功", "logout_all_device_confirmation": "æœŦåŊ“ãĢ全ãĻぎデバイ゚からログã‚ĸã‚Ļトしぞすか?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "デãƒŧã‚ŋベãƒŧ゚を垊元", "maintenance_description": "Immich は ãƒĄãƒŗãƒ†ãƒŠãƒŗã‚šãƒĸãƒŧド中です。", "maintenance_end": "ãƒĄãƒŗãƒ†ãƒŠãƒŗã‚šãƒĸãƒŧドをįĩ‚äē†ã™ã‚‹", + "maintenance_end_error": "ãƒĄãƒŗãƒ†ãƒŠãƒŗã‚šãƒĸãƒŧドぎįĩ‚äē†ãĢå¤ąæ•—ã—ãžã—ãŸã€‚", "maintenance_logged_in_as": "įžåœ¨ {user}としãĻãƒ­ã‚°ã‚¤ãƒŗã—ãĻいぞす", "maintenance_restore_from_backup": "バックã‚ĸップから垊元", "maintenance_restore_library": "あãĒたぎナイブナãƒĒを垊元", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "č¨­åŽšã‚’é–‹ã", "manage_media_access_subtitle": "Immichã‚ĸプãƒĒãĢãƒĄãƒ‡ã‚Ŗã‚ĸãƒ•ã‚Ąã‚¤ãƒĢãŽįŽĄį†ã¨į§ģå‹•ã‚’č¨ąå¯ã™ã‚‹ã€‚", "manage_media_access_title": "ãƒĄãƒ‡ã‚Ŗã‚ĸįŽĄį†ã‚ĸクã‚ģ゚", + "manage_shared_links": "å…ąæœ‰æ¸ˆãŋぎãƒĒãƒŗã‚¯ã‚’įŽĄį†", "manage_sharing_with_partners": "パãƒŧトナãƒŧã¨ãŽå…ąæœ‰ã‚’įŽĄį†ã—ãžã™", "manage_the_app_settings": "ã‚ĸプãƒĒãŽč¨­åŽšã‚’įŽĄį†ã—ãžã™", "manage_your_account": "あãĒたぎã‚ĸã‚Ģã‚Ļãƒŗãƒˆã‚’įŽĄį†ã—ãžã™", @@ -1371,11 +1491,13 @@ "manage_your_devices": "ãƒ­ã‚°ã‚¤ãƒŗãƒ‡ãƒã‚¤ã‚šã‚’įŽĄį†ã—ãžã™", "manage_your_oauth_connection": "OAuthæŽĨįļšã‚’įŽĄį†ã—ãžã™", "map": "åœ°å›ŗ", + "map_assets_in_bounds": "{count, plural, =0 {ここãĢå†™įœŸã¯ã‚ã‚Šãžã›ã‚“} one {# 枚} other {# 枚}}", "map_cannot_get_user_location": "äŊįŊŽæƒ…å ąãŒã‚˛ãƒƒãƒˆã§ããžã›ã‚“", + "map_location_dialog_yes": "はい", "map_location_picker_page_use_location": "こぎäŊįŊŽæƒ…å ąã‚’äŊŋう", "map_location_service_disabled_content": "įžåœ¨åœ°ãŽé …į›Žã‚’čĄ¨į¤ēするãĢはäŊįŊŽæƒ…å ąãŒã‚Ēãƒŗã§ã‚ã‚‹åŋ…čĻãŒã‚ã‚Šãžã™ã€‚æœ‰åŠšåŒ–ã—ãžã™ã‹īŧŸ", "map_location_service_disabled_title": "äŊįŊŽæƒ…å ąãŒã‚Ēフです", - "map_marker_for_image": "{city}īŊ¤{country}で撎åŊąã•れたį”ģåƒãŽåœ°å›ŗãƒžãƒŧã‚Ģãƒŧ", + "map_marker_for_images": "{country} {city}で撎åŊąã•ã‚ŒãŸå†™įœŸãŽåœ°å›ŗãƒžãƒŧã‚Ģãƒŧ", "map_marker_with_image": "į”ģåƒãŽåœ°å›ŗãƒžãƒŧã‚Ģãƒŧ", "map_no_location_permission_content": "įžåœ¨åœ°ãŽé …į›Žã‚’čĄ¨į¤ēするãĢはäŊįŊŽæƒ…å ąã¸ãŽã‚ĸクã‚ģ゚がåŋ…čĻã§ã™ã€‚č¨ąå¯ã—ãžã™ã‹īŧŸ", "map_no_location_permission_title": "äŊįŊŽæƒ…å ąã¸ãŽã‚ĸクã‚ģ゚が拒åĻされぞした", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "過åŽģ{days}æ—Ĩ間", "map_settings_date_range_option_year": "過åŽģ1åš´é–“", "map_settings_date_range_option_years": "過åŽģ{years}åš´é–“", + "map_settings_dialog_title": "ãƒžãƒƒãƒ—ãŽč¨­åŽš", "map_settings_include_show_archived": "ã‚ĸãƒŧã‚Ģイブ済ãŋをåĢめる", "map_settings_include_show_partners": "パãƒŧトナãƒŧをåĢめる", "map_settings_only_show_favorites": "お気ãĢå…Ĩりぎãŋã‚’čĄ¨į¤ē", "map_settings_theme_settings": "åœ°å›ŗãŽčĻ‹ãŸį›Ž", + "map_zoom_to_see_photos": "å†™įœŸã‚’čĻ‹ã‚‹ãĢはã‚ēãƒŧムã‚ĸã‚Ļト", "mark_all_as_read": "すずãĻæ—ĸčĒ­ãĢする", + "mark_as_read": "æ—ĸčĒ­ãĢする", "marked_all_as_read": "すずãĻæ—ĸčĒ­ãĢしぞした", "matches": "マッチ", "matching_assets": "ä¸€č‡´ã™ã‚‹é …į›Ž", - "media_chrome": { - "auto": "č‡Ē動", - "captions": "ã‚­ãƒŖãƒ—ã‚ˇãƒ§ãƒŗ", - "captions_off": "ã‚Ēフ", - "closed_captions": "クロãƒŧã‚ēãƒ‰ã‚­ãƒŖãƒ—ã‚ˇãƒ§ãƒŗ", - "decode_error": "ãƒ‡ã‚ŗãƒŧドエナãƒŧ", - "disable_captions": "ã‚­ãƒŖãƒ—ã‚ˇãƒ§ãƒŗį„ĄåŠšåŒ–", - "enable_captions": "ã‚­ãƒŖãƒ—ã‚ˇãƒ§ãƒŗæœ‰åŠšåŒ–", - "enter_fullscreen_mode": "フãƒĢ゚クãƒĒãƒŧãƒŗãƒĸãƒŧド開始", - "exit_fullscreen_mode": "フãƒĢ゚クãƒĒãƒŧãƒŗãƒĸãƒŧドįĩ‚äē†", - "loop": "ãƒĢãƒŧプ", - "media_error_description": "ãƒĄãƒ‡ã‚Ŗã‚ĸぎエナãƒŧãĢã‚ˆã‚Šå†į”ŸãŒä¸­æ­ĸされぞした。 ãƒĄãƒ‡ã‚Ŗã‚ĸãŒį ´æã—ãĻいたり、ブナã‚Ļã‚ļがこぎフりãƒŧマットをã‚ĩポãƒŧトしãĻいãĒい可čƒŊ性がありぞす。", - "media_loading": "ãƒĄãƒ‡ã‚Ŗã‚ĸčĒ­ãŋčžŧãŋ", - "mute": "ミãƒĨãƒŧト", - "network_error": "ネットワãƒŧクエナãƒŧ", - "network_error_description": "ネットワãƒŧクエナãƒŧãĢã‚ˆã‚ŠãƒĄãƒ‡ã‚Ŗã‚ĸぎダã‚Ļãƒŗãƒ­ãƒŧãƒ‰ãŒå¤ąæ•—ã—ãžã—ãŸã€‚", - "not_supported_error": "ã‚ĩポãƒŧトされãĻいãĒいã‚Ŋãƒŧ゚", - "playback_rate": "å†į”Ÿé€ŸåēĻ", - "playback_rate_current": "įžåœ¨ãŽå†į”Ÿé€ŸåēĻ", - "playback_rate_value": "å†į”Ÿé€ŸåēĻ {playbackRate}", - "playback_time": "å†į”Ÿæ™‚é–“", - "quality": "品čŗĒ", - "second": "į§’", - "seconds": "į§’", - "time_value_of_total_time": "{currentTime}中{totalTime}", - "time_value_remaining": "掋り {time}", - "unmute": "ミãƒĨãƒŧãƒˆč§Ŗé™¤", - "unsupported_error_description": "ã‚ĩポãƒŧトされãĻいãĒいエナãƒŧがį™ēį”Ÿã—ãžã—ãŸã€‚ ã‚ĩãƒŧバやネットワãƒŧクãĢį•°å¸¸ãŒã‚ã‚‹ã‹ã€ãƒ–ãƒŠã‚Ļã‚ļがこぎフりãƒŧマットをã‚ĩポãƒŧトしãĻいぞせん.", - "video_not_loaded_unknown_time": "動į”ģがčĒ­ãŋčžŧぞれぞせんでした、時間不明。", - "video_player": "動į”ģプãƒŦãƒŧヤãƒŧ", - "volume": "éŸŗé‡" - }, "media_type": "ãƒĄãƒ‡ã‚Ŗã‚ĸã‚ŋイプ", "memories": "ãƒĄãƒĸãƒĒãƒŧ", "memories_all_caught_up": "これで全部です", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "これらぎäēēį‰Šã‚’įĩąåˆã—ぞすか? こぎ操äŊœã¯å…ƒãĢæˆģせぞせん。", "merge_people_successfully": "äēēį‰ŠãŽįĩąåˆãĢ成功しぞした", "merged_people_count": "{count, plural, one {#äēē} other {#äēē}}ぎäēēį‰Šã‚’įĩąåˆã—ぞした", - "minFaces": "最小éĸ数", - "minFaces_description": "そぎäēēį‰ŠãŒčĄ¨į¤ēされるためãĢåŋ…čρãĒ顔čĒč­˜æ•°ãŽæœ€å°å€¤", "minimize": "最小化", "minute": "分", "minutes": "分", + "mirror_horizontal": "æ°´åšŗ", + "mirror_vertical": "åž‚į›´", "missing": "æŦ čŊ", "mobile_app": "ãƒĸバイãƒĢã‚ĸプãƒĒ", "mobile_app_download_onboarding_note": "äģĨ下ぎã‚Ēãƒ—ã‚ˇãƒ§ãƒŗã‚’äŊŋį”¨ã—ãĻã‚ŗãƒŗãƒ‘ãƒ‹ã‚ĒãƒŗãƒĸバイãƒĢã‚ĸプãƒĒをダã‚Ļãƒŗãƒ­ãƒŧドしãĻください", "model": "ãƒĸデãƒĢ", - "modify_date": "æ—Ĩäģ˜ãŽäŋŽæ­Ŗ", "month": "月", + "monthly_title_text_date_format": "yyyy MM", "more": "ã‚‚ãŖã¨čĄ¨į¤ē", - "motion": "動き", "move": "į§ģ動", + "move_down": "下へ", "move_off_locked_folder": "éĩäģ˜ããƒ•りãƒĢダãƒŧからå‡ēす", "move_to": "į§ģ動する", "move_to_device_trash": "ãƒ‡ãƒã‚¤ã‚šãŽã‚´ãƒŸįŽąã¸į§ģ動", "move_to_lock_folder_action_prompt": "{count}é …į›Žã‚’éĩäģ˜ããƒ•りãƒĢダãƒŧãĢčŋŊ加しぞした", "move_to_locked_folder": "éĩäģ˜ããƒ•りãƒĢダãƒŧへį§ģ動", "move_to_locked_folder_confirmation": "ã“ã‚Œã‚‰ãŽå†™įœŸã‚„å‹•į”ģはすずãĻぎã‚ĸãƒĢバムから外され、éĩäģ˜ããƒ•りãƒĢダãƒŧ内でぎãŋ閲čĻ§å¯čƒŊãĢãĒりぞす", + "move_up": "上へ", + "moved_to_archive": "{count, plural, one {#} other {#}}é …į›Žã‚’ã‚ĸãƒŧã‚Ģイブしぞした", + "moved_to_library": "{count, plural, one {#} other {#}}é …į›Žã‚’ãƒŠã‚¤ãƒ–ãƒŠãƒĒãĢį§ģ動しぞした", "moved_to_trash": "ã‚´ãƒŸįŽąãĢį§ģ動しぞした", + "multiselect_grid_edit_date_time_err_read_only": "čĒ­ãŋå–ã‚Šå°‚į”¨ãŽé …į›ŽãŽæ—Ĩäģ˜ã‚’変更できぞせん", + "multiselect_grid_edit_gps_err_read_only": "čĒ­ãŋå–ã‚Šå°‚į”¨ãŽé …į›ŽãŽäŊįŊŽæƒ…å ąã‚’å¤‰æ›´ã§ããžã›ã‚“", "mute_memories": "ãƒĄãƒĸãƒĒãƒŧぎミãƒĨãƒŧト", "my_albums": "į§ãŽã‚ĸãƒĢバム", - "my_immich_description": "įžåœ¨ãŽãƒšãƒŧã‚¸ã‚’į§ãŽImmichãƒĒãƒŗã‚¯ã¨ã—ãĻã‚ŗãƒ”ãƒŧする", - "my_immich_title": "į§ãŽImmichãƒĒãƒŗã‚¯", "name": "名前", "name_or_nickname": "名前ぞたはニックネãƒŧム", "name_required": "名前はåŋ…é ˆé …į›Žã§ã™", @@ -1480,11 +1576,12 @@ "never": "čĄŒã‚ãĒい", "new_album": "新たãĒã‚ĸãƒĢバム", "new_api_key": "新しいAPI キãƒŧ", - "new_feature": "新抟čƒŊ", + "new_date_range": "新しいæ—Ĩäģ˜į¯„回", "new_password": "新しいパ゚ワãƒŧド", "new_person": "新しいäēēį‰Š", "new_pin_code": "新しいPINã‚ŗãƒŧド", "new_pin_code_subtitle": "éĩäģ˜ããƒ•りãƒĢダãƒŧã‚’åˆŠį”¨ã™ã‚‹ãŽãŒåˆã‚ãĻぎようです。PINã‚ŗãƒŧドをäŊœæˆã—ãĻください", + "new_timeline": "新たãĒã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗ", "new_update": "新たãĒ更新", "new_user_created": "新しいãƒĻãƒŧã‚ļãƒŧがäŊœæˆã•れぞした", "new_version_available": "新しいバãƒŧã‚¸ãƒ§ãƒŗãŒåˆŠį”¨å¯čƒŊ", @@ -1492,6 +1589,7 @@ "next": "æŦĄ", "next_memory": "æŦĄãŽãƒĄãƒĸãƒĒãƒŧ", "no": "いいえ", + "no_actions_added": "ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗãŒã‚ã‚Šãžã›ã‚“", "no_albums_found": "ã‚ĸãƒĢバムがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“", "no_albums_message": "ã‚ĸãƒĢバムをäŊœæˆã—ãĻå†™įœŸã‚„å‹•į”ģã‚’æ•´į†ã—ãžã—ã‚‡ã†", "no_albums_with_name_yet": "こぎ名前ぎã‚ĸãƒĢバムはぞだãĒいようです。", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "ã‚­ãƒŖã‚šãƒˆå…ˆãŽãƒ‡ãƒã‚¤ã‚šãŒčĻ‹ã¤ã‹ã‚Šãžã›ã‚“", "no_checksum_local": "チェックã‚ĩムがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“ - ãƒ‡ãƒã‚¤ã‚šä¸ŠãŽé …į›Žã‚’å–åž—ã§ããĒいようです", "no_checksum_remote": "チェックã‚ĩムがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“ - ã‚ĩãƒŧバãƒŧä¸ŠãŽé …į›Žã‚’å–åž—ã§ããĒいようです", + "no_configuration_needed": "č¨­åŽšã¯ä¸čĻã§ã™", "no_devices": "č¨ąå¯ã•ã‚ŒãŸãƒ‡ãƒã‚¤ã‚šãŒã‚ã‚Šãžã›ã‚“", "no_duplicates_found": "é‡č¤‡ã¯čĻ‹ã¤ã‹ã‚Šãžã›ã‚“ã§ã—ãŸã€‚", "no_exif_info_available": "exifæƒ…å ąãŒåˆŠį”¨ã§ããžã›ã‚“", "no_explore_results_message": "ã‚ŗãƒŦã‚¯ã‚ˇãƒ§ãƒŗã‚’æŽĸį´ĸするãĢはさらãĢå†™įœŸã‚’ã‚ĸップロãƒŧドしãĻください。", "no_favorites_message": "お気ãĢå…Ĩりį™ģéŒ˛ã™ã‚‹ã¨åĨŊきãĒå†™įœŸã‚„å‹•į”ģをすぐãĢčĻ‹ã¤ã‘ã‚‰ã‚Œãžã™", + "no_filters_added": "ãžã ãƒ•ã‚ŖãƒĢã‚ŋãƒŧがčŋŊ加されãĻいぞせん", "no_libraries_message": "あãĒãŸãŽå†™įœŸã‚„å‹•į”ģã‚’čĄ¨į¤ēするためぎ外部ナイブナãƒĒをäŊœæˆã—ぞしょう", "no_local_assets_found": "こぎチェックã‚ĩãƒ ãŽé …į›Žã¯ãƒ‡ãƒã‚¤ã‚šä¸ŠãĢ存在しぞせん", + "no_location_set": "äŊįŊŽæƒ…å ąãŒæŒ‡åŽšã•ã‚ŒãĻいぞせん", "no_locked_photos_message": "éĩäģ˜ããƒ•りãƒĢダãƒŧå†…ãŽå†™įœŸã‚„å‹•į”ģは通常ぎナイブナãƒĒãĢ襨į¤ēされãĒくãĒりぞす。", "no_name": "名前ãĒし", "no_notifications": "通įŸĨãĒし", @@ -1518,19 +1619,14 @@ "no_results": "įĩæžœãŒã‚りぞせん", "no_results_description": "åŒįžŠčĒžã‚„ã‚ˆã‚Šä¸€čˆŦįš„ãĒキãƒŧワãƒŧドをčŠĻしãĻください", "no_shared_albums_message": "ã‚ĸãƒĢバムをäŊœæˆã—ãĻå†™įœŸã‚„å‹•į”ģã‚’å…ąæœ‰ã—ãžã—ã‚‡ã†", - "no_steps": "ぞだ゚テップがčŋŊ加されãĻいぞせん", + "no_uploads_in_progress": "ã‚ĸップロãƒŧãƒ‰ã¯čĄŒã‚ã‚ŒãĻいぞせん", "none": "ãĒし", "not_allowed": "č¨ąå¯ã•ã‚ŒãĻいぞせん", "not_available": "éŠį”¨ãĒし", "not_in_any_album": "おぎã‚ĸãƒĢバムãĢもå…ĨãŖãĻいãĒい", "not_selected": "選択ãĒし", - "not_set": "æœĒč¨­åŽš", "notes": "æŗ¨æ„", "nothing_here_yet": "ぞだäŊ•ã‚‚į„Ąã„ã‚ˆã†ã§ã™", - "notification_backup_reliability": "バックグナã‚Ļãƒŗãƒ‰ã§ãŽãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—ãŒæ­Ŗå¸¸ãĢčĄŒã‚ã‚Œã‚‹ãŸã‚ã€é€šįŸĨã‚’č¨ąå¯ã—ãĻください。", - "notification_enabled_list_tile_content": "Immichは、バックグナã‚Ļãƒŗãƒ‰ã§ãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—ã‚’čĄŒã†ãŸã‚ã€é€šįŸĨぎ樊限をäŊŋį”¨ã—ãĻã„ãžã™ã€‚ã“ã‚Œã‚‰ã¯ãƒ‡ãƒã‚¤ã‚šãŽč¨­åŽšã§įŽĄį†ã™ã‚‹ã“ã¨ãŒã§ããžã™ã€‚", - "notification_enabled_list_tile_open_button": "č¨­åŽšã‚’é–‹ã", - "notification_enabled_list_tile_title": "通įŸĨãŒč¨ąå¯ã•ã‚Œãžã—ãŸ", "notification_permission_dialog_content": "通įŸĨã‚’č¨ąå¯ã™ã‚‹ãĢã¯č¨­åŽšã‚’é–‹ã„ãĻã‚ĒãƒŗãĢしãĻください", "notification_permission_list_tile_content": "通įŸĨãŽč¨ąå¯ をã‚ĒãƒŗãĢしãĻください", "notification_permission_list_tile_enable_button": "通įŸĨをã‚ĒãƒŗãĢする", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "ObtainiumãŽč¨­åŽš", "obtainium_configurator_instructions": "ObtainiumをäŊŋį”¨ã™ã‚‹ã¨ã€Immich GitHubぎãƒĒãƒĒãƒŧã‚šã‹ã‚‰į›´æŽĨAndroidã‚ĸプãƒĒã‚’ã‚¤ãƒŗã‚šãƒˆãƒŧãƒĢãŠã‚ˆãŗã‚ĸップデãƒŧトできぞす。APIキãƒŧをäŊœæˆã—、バãƒĒã‚ĸãƒŗãƒˆã‚’é¸æŠžã—ãĻObtainiumãŽč¨­åŽšãƒĒãƒŗã‚¯ã‚’äŊœæˆã—ãĻください", "ocr": "OCR", - "ocr_body": "Immichã¯å†™įœŸãĢå†™ãŖãĻいる文字をčĒč­˜ã§ãã‚‹ã‚ˆã†ãĢãĒãŖãŸãŸã‚ã€į”ģ像検į´ĸがしやすくãĒりぞした。", - "ocr_title": "å†™įœŸãŽä¸­ã‹ã‚‰ãƒ†ã‚­ã‚šãƒˆã‚’æŽĸす", "official_immich_resources": "å…ŦåŧImmichãƒĒã‚Ŋãƒŧ゚", "offline": "ã‚Ēãƒ•ãƒŠã‚¤ãƒŗ", "offset": "ã‚Ēフã‚ģット", @@ -1562,8 +1656,6 @@ "open": "開く", "open_calendar": "ã‚ĢãƒŦãƒŗãƒ€ãƒŧを開く", "open_in_browser": "ブナã‚Ļã‚ļで開く", - "open_in_immich_body": "Androidデバイ゚で、ImmichをデフりãƒĢãƒˆãŽå†™įœŸã‚ŽãƒŖãƒŠãƒĒãƒŧとしãĻč¨­åŽšã™ã‚‹ã“ã¨ã§ã€ãģかぎã‚ĸプãƒĒã‹ã‚‰å†™įœŸãŒã‚ĸクã‚ģ゚しやすくãĒりぞす。", - "open_in_immich_title": "Immichã§å†™įœŸã‚’é–‹ã", "open_in_map_view": "åœ°å›ŗčĄ¨į¤ēでčĻ‹ã‚‹", "open_in_openstreetmap": "OpenStreetMapで開く", "open_the_search_filters": "検į´ĸãƒ•ã‚ŖãƒĢã‚ŋを開く", @@ -1572,10 +1664,10 @@ "organize_into_albums": "ã‚ĸãƒĢバムãĢčŋŊ加しãĻæ•´į†ã™ã‚‹", "organize_into_albums_description": "æ—ĸå­˜ãŽå†™įœŸã‚’ã€įžåœ¨ãŽåŒæœŸč¨­åŽšãĢåŸēãĨきã‚ĸãƒĢバムãĢčŋŊ加する", "organize_your_library": "ナイブナãƒĒã‚’æ•´į†", - "orientation": "į”ģ像ぎ向き", "original": "ã‚ĒãƒĒジナãƒĢ", "other": "そぎäģ–", "other_devices": "そぎäģ–ぎデバイ゚", + "other_entities": "äģ–ぎもぎ", "other_variables": "そぎäģ–ぎ変数", "owned": "所有中", "owner": "ã‚Ēãƒŧナãƒŧ", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "ã‚ĸãƒŧã‚Ģイブ済ãŋぎもぎと削除済ãŋぎもぎを除いた全ãĻãŽå†™įœŸã¨å‹•į”ģ", "partner_can_access_location": "å†™įœŸãŒæ’ŽåŊąã•れた場所", "partner_list_user_photos": "{user}ã•ã‚“ãŽå†™įœŸ", + "partner_list_view_all": "すずãĻčĻ‹ã‚‹", "partner_page_empty_message": "ぞだおぎパãƒŧトナãƒŧã¨ã‚‚å†™įœŸã‚’å…ąæœ‰ã—ãĻぞせん", "partner_page_no_more_users": "čŋŊ加できるãƒĻãƒŧã‚ļãƒŧがもういぞせん", + "partner_page_partner_add_failed": "パãƒŧトナãƒŧぎčŋŊ加ãĢå¤ąæ•—", "partner_page_select_partner": "パãƒŧトナãƒŧを選択", + "partner_page_shared_to_title": "æŦĄãŽãƒĻãƒŧã‚ļãƒŧã¨å…ąæœ‰ã—ãžã™:", "partner_page_stop_sharing_content": "{partner}はäģŠåžŒã‚ãĒãŸãŽå†™įœŸã¸ã‚ĸクã‚ģ゚できãĒくãĒりぞす", "partner_sharing": "パãƒŧãƒˆãƒŠã¨ãŽå…ąæœ‰", "partners": "パãƒŧトナãƒŧ", @@ -1609,6 +1704,8 @@ "people": "äēēį‰Š", "people_edits_count": "{count, plural, one {#äēē} other {#äēē}}ãŒįˇ¨é›†æ¸ˆ", "people_feature_description": "äēēį‰Šã§ã‚°ãƒĢãƒŧãƒ—åŒ–ã•ã‚ŒãŸå†™įœŸã¨å‹•į”ģã‚’é–˛čĻ§ã™ã‚‹", + "people_selected": "{count, plural, one {# äēēį‰Šã‚’é¸æŠžä¸­} other {# äēēį‰Šã‚’é¸æŠžä¸­}}", + "people_sidebar_description": "äēēį‰Šã¸ãŽãƒĒãƒŗã‚¯ã‚’ã‚ĩイドバãƒŧãĢ襨į¤ē", "permanent_deletion_warning": "永䚅削除ぎč­Ļ告", "permanent_deletion_warning_setting_description": "ã‚ĸã‚ģットを厌全ãĢ削除するときãĢč­Ļå‘Šã‚’čĄ¨į¤ēする", "permanently_delete": "厌全ãĢ削除", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {#個} other {#個}}ãŽé …į›Žã‚’åŽŒå…¨ãĢ削除しぞした", "permission": "樊限", "permission_empty": "樊限をįŠēį™ŊãĢはå‡ēæĨぞせん", + "permission_onboarding_back": "æˆģる", + "permission_onboarding_continue_anyway": "į„ĄčĻ–ã—ãĻįļščĄŒ", + "permission_onboarding_get_started": "はじめる", + "permission_onboarding_go_to_settings": "ã‚ˇã‚šãƒ†ãƒ č¨­åŽš", + "permission_onboarding_permission_denied": "å†™įœŸã¸ãŽã‚ĸクã‚ģã‚šãŒč¨ąå¯ã•ã‚ŒãĻいぞせん。こぎã‚ĸプãƒĒをäŊŋうãĢã¯č¨­åŽšã‹ã‚‰å†™įœŸã¨å‹•į”ģへぎã‚ĸクã‚ģã‚šã‚’č¨ąå¯ã—ãĻください", + "permission_onboarding_permission_granted": "å†™įœŸã¸ãŽã‚ĸクã‚ģã‚šãŒč¨ąå¯ã•ã‚Œãžã—ãŸ", + "permission_onboarding_permission_limited": "å†™įœŸã¸ãŽã‚ĸクã‚ģ゚がåˆļ限されãĻいぞす。ImmichãŒå†™įœŸãŽãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—ã¨įŽĄį†ã‚’čĄŒã†ãĢã¯ã€ã‚ˇã‚šãƒ†ãƒ č¨­åŽšã‹ã‚‰å†™įœŸã¨å‹•į”ģぎã‚ĸクã‚ģ゚樊限を変更しãĻください。", + "permission_onboarding_request": "Immichã¯å†™įœŸã¸ãŽã‚ĸクã‚ģã‚šč¨ąå¯ãŒåŋ…čĻã§ã™", "person": "äēēį‰Š", "person_age_months": "į”ŸåžŒ {months, plural, one {# ãƒļ月} other {# ãƒļ月}}", "person_age_year_months": "1 æ­ŗã¨, {months, plural, one {# ãƒļ月} other {# ãƒļ月}}", @@ -1625,10 +1730,13 @@ "person_birthdate": "{date}į”Ÿãžã‚Œ", "person_hidden": "{name}{hidden, select, true { (非表į¤ē)} other {}}", "person_recognized": "äēēį‰ŠãŒčĒč­˜ã•ã‚ŒãĻいぞす", + "person_selected": "äēēį‰ŠãŒé¸æŠžã•ã‚ŒãĻいぞす", "photo_shared_all_users": "å†™įœŸã‚’ã™ãšãĻぎãƒĻãƒŧã‚ļãƒŧã¨å…ąæœ‰ã—ãŸã‹ã€å…ąæœ‰ã™ã‚‹ãƒĻãƒŧã‚ļãƒŧがいãĒいようです。", "photos": "å†™įœŸ", "photos_and_videos": "å†™įœŸã¨å‹•į”ģ", + "photos_count": "{count, plural, one {{count, number}æžšãŽå†™įœŸ} other {{count, number}æžšãŽå†™įœŸ}}", "photos_from_previous_years": "äģĨå‰ãŽåš´ãŽå†™įœŸ", + "photos_only": "å†™įœŸãŽãŋ", "pick_a_location": "場所を選択", "pick_custom_range": "期間を指厚", "pick_date_range": "æ—Ĩäģ˜į¯„å›˛ãŽé¸æŠž", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドされた動į”ģよりも、ã‚ĒãƒĒジナãƒĢぎ動į”ģをå„Ē先しãĻå†į”Ÿã—ãžã™ã€‚ã‚ĒãƒĒジナãƒĢぎã‚ĸã‚ģットがäē’æ›æ€§ãŒãĒã„å ´åˆã€æ­Ŗã—ãå†į”Ÿã•ã‚ŒãĒい可čƒŊ性がありぞす。", "play_transcoded_video": "ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧド済ãŋぎ動į”ģã‚’å†į”Ÿã™ã‚‹", "please_auth_to_access": "ã‚ĸクã‚ģ゚するãĢはčĒč¨ŧがåŋ…čĻã§ã™", - "plugin_method_filter_type": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧ", - "plugin_method_filter_type_description": "ã“ãŽæ–šæŗ•ã§ã¯ã€ã‚¤ãƒ™ãƒŗãƒˆã‚’ãƒ•ã‚ŖãƒĢã‚ŋãƒĒãƒŗã‚°ã—ã€æĄäģļãĢåŋœã˜ãĻæŦĄãŽã‚šãƒ†ãƒƒãƒ—ãŽåŽŸčĄŒã‚’æ­ĸめることができぞす。", "port": "ポãƒŧトãƒŦãƒŧト", "preferences_settings_subtitle": "ã‚ĸプãƒĒãĢé–ĸã™ã‚‹č¨­åŽš", "preferences_settings_title": "č¨­åŽš", @@ -1665,11 +1771,11 @@ "privacy": "ãƒ—ãƒŠã‚¤ãƒã‚ˇãƒŧ", "profile": "ãƒ—ãƒ­ãƒ•ã‚ŖãƒŧãƒĢ", "profile_drawer_app_logs": "ログ", + "profile_drawer_client_server_up_to_date": "すずãĻæœ€æ–°į‰ˆã§ã™", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "čĒ­ãŋå–ã‚Šå°‚į”¨ãƒĸãƒŧドが有劚です。ãƒĻãƒŧã‚ļãƒŧぎã‚ĸã‚¤ã‚ŗãƒŗã‚’é•ˇæŠŧししãĻčĒ­ãŋå–ã‚Šå°‚į”¨ãƒĸãƒŧãƒ‰ã‚’č§Ŗé™¤ã—ãĻください。", "profile_image_of_user": "{user} ãŽãƒ—ãƒ­ãƒ•ã‚ŖãƒŧãƒĢį”ģ像", "profile_picture_set": "ãƒ—ãƒ­ãƒ•ã‚ŖãƒŧãƒĢį”ģåƒãŒč¨­åŽšã•ã‚Œãžã—ãŸã€‚", - "projection_type": "投写斚åŧ", "public_album": "å…Ŧ開ã‚ĸãƒĢバム", "public_share": "å…Ŧé–‹å…ąæœ‰", "purchase_account_info": "ã‚ĩポãƒŧã‚ŋãƒŧ", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "ã‚ĩポãƒŧã‚ŋãƒŧぎįŠļ態", "purchase_individual_title": "個äēē", "purchase_input_suggestion": "プロダクトキãƒŧã‚’ãŠæŒãĄã§ã™ã‹? 下ãĢå…Ĩ力しãĻください", + "purchase_license_subtitle": "Immich をčŗŧå…ĨしãĻã‚ĩãƒŧビ゚ぎįļ™įļšįš„ãĒ開į™ēを支援しãĻください", "purchase_lifetime_description": "į”Ÿæļ¯ãŽčŗŧå…Ĩ", "purchase_option_title": "čŗŧå…Ĩã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", "purchase_panel_info_1": "ImmichぎčŖŊäŊœãĢは多くぎ時間と労力をčĻã—ãĻおり、ぞた、可čƒŊãĒ限りImmichã‚’č‰¯ã„ã‚‚ãŽãĢするためãĢ取りįĩ„んでいる専äģģãŽæŠ€čĄ“č€…ãŒã„ãžã™ã€‚į§ãŸãĄãŽäŊŋå‘Ŋは、ã‚Ēãƒŧãƒ—ãƒŗã‚Ŋãƒŧ゚ã‚Ŋフトã‚Ļェã‚ĸでありå€ĢᐆčĻŗãĢ則したビジネ゚ぎ原čˇĩぎためãĢ、開į™ēč€…ãŽæŒįļšå¯čƒŊãĒ収å…Ĩæēã¨ãĒること、そしãĻæžå–įš„ãĒクナã‚Ļドã‚ĩãƒŧビ゚ぎæœŦåŊ“ぎäģŖæ›ŋã‚ĩãƒŧãƒ“ã‚šã§ã€ãƒ—ãƒŠã‚¤ãƒã‚ˇãƒŧã‚’å°Šé‡ã—ãŸã‚¨ã‚ŗã‚ˇã‚šãƒ†ãƒ ã‚’ã¤ãã‚‹ã“ã¨ã§ã™ã€‚", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {#個} other {#個}}ãŽå†™įœŸ/動į”ģを新しいäēēį‰ŠãĢå‰˛ã‚ŠåŊ“ãĻぞした", "reassing_hint": "é¸æŠžã•ã‚ŒãŸå†™įœŸ/動į”ģをæ—ĸ存ぎäēēį‰ŠãĢå‰˛ã‚ŠåŊ“ãĻ", "recent": "最čŋ‘", + "recent_albums": "最čŋ‘ぎã‚ĸãƒĢバム", "recent_searches": "最čŋ‘ぎ検į´ĸ", "recently_added": "最čŋ‘čŋŊåŠ ã•ã‚ŒãŸé …į›Ž", - "recently_added_body": "あãĒたが最čŋ‘čŋŊåŠ ã—ãŸé …į›ŽãĢすぐã‚ĸクã‚ģ゚することができるペãƒŧジがありぞす。", - "recently_added_description": "ImmichãĢã‚ĸップロãƒŧドされたæ—Ĩ時順ãĢä¸Ļãšã‚‰ã‚ŒãŸé …į›Žã‚’é–˛čĻ§ã™ã‚‹", "recently_added_page_title": "最čŋ‘", - "recently_added_title": "最čŋ‘čŋŊåŠ ã•ã‚ŒãŸé …į›Ž", "recently_taken": "最čŋ‘撎られたもぎ", + "recently_taken_page_title": "最čŋ‘ぎ撎åŊą", "refresh": "更新", "refresh_encoded_videos": "ã‚¨ãƒŗã‚ŗãƒŧドされた動į”ģを更新", "refresh_faces": "顔čĒč­˜ã‚’æ›´æ–°", "refresh_metadata": "ãƒĄã‚ŋデãƒŧã‚ŋを更新", "refresh_thumbnails": "ã‚ĩムネイãƒĢを更新", "refreshed": "更新済", + "refreshes_every_file": "すずãĻãŽãƒ•ã‚Ąã‚¤ãƒĢを更新", "refreshing_encoded_video": "ã‚¨ãƒŗã‚ŗãƒŧドされた動į”ģを更新中", "refreshing_faces": "顔čĒč­˜ã‚’æ›´æ–°ä¸­", "refreshing_metadata": "ãƒĄã‚ŋデãƒŧã‚ŋを更新中", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "æœŦåŊ“ãĢã“ãŽå…ąæœ‰ãƒĒãƒŗã‚¯ã‹ã‚‰{count, plural, one {#個} other {#個}}ãŽå†™įœŸ/動į”ģを削除しぞすか?", "remove_assets_title": "å†™įœŸ/動į”ģを削除しぞすか?", "remove_custom_date_range": "ã‚Ģ゚ã‚ŋムæ—Ĩäģ˜į¯„å›˛ã‚’å‰Šé™¤", - "remove_filter": "ãƒ•ã‚ŖãƒĢã‚ŋãƒŧを除く", + "remove_deleted_assets": "ã‚Ēãƒ•ãƒŠã‚¤ãƒŗãŽå†™įœŸ/動į”ģを削除", "remove_from_album": "ã‚ĸãƒĢバムから削除", "remove_from_album_action_prompt": "{count}é …į›ŽãŒã‚ĸãƒĢバムから除かれぞした", "remove_from_favorites": "お気ãĢå…Ĩã‚Šč§Ŗé™¤", @@ -1756,22 +1863,29 @@ "remove_memory": "ãƒĄãƒĸãƒĒãƒŧぎ削除", "remove_photo_from_memory": "ãƒĄãƒĸãƒĒãƒŧã‹ã‚‰å†™įœŸã‚’å‰Šé™¤", "remove_tag": "ã‚ŋグを削除", + "remove_url": "URLぎ削除", "remove_user": "ãƒĻãƒŧã‚ļãƒŧを削除", "removed_api_key": "削除されたAPI キãƒŧ: {name}", "removed_from_archive": "ã‚ĸãƒŧã‚Ģイブから外しぞした", "removed_from_favorites": "お気ãĢå…Ĩã‚Šã‚’č§Ŗé™¤ã—ãžã—ãŸ", "removed_from_favorites_count": "{count, plural, other {#é …į›Ž}}をお気ãĢå…Ĩりから外しぞした", "removed_memory": "å‰Šé™¤ã•ã‚ŒãŸãƒĄãƒĸãƒĒãƒŧ", + "removed_photo_from_memory": "ãƒĄãƒĸãƒĒãƒŧã‹ã‚‰å‰Šé™¤ã•ã‚ŒãŸå†™įœŸ", "removed_tagged_assets": "{count, plural, one {#é …į›Ž} other {#é …į›Ž}}からã‚ŋグを外しぞした", "rename": "ãƒĒネãƒŧム", + "repair": "äŋŽåžŠ", + "repair_no_results_message": "čŋŊčˇĄã•ã‚ŒãĻいãĒã„ãƒ•ã‚Ąã‚¤ãƒĢや存在しãĒã„ãƒ•ã‚Ąã‚¤ãƒĢがここãĢ襨į¤ēされぞす", + "replace_with_upload": "ã‚ĸップロãƒŧドしãĻįŊŽãæ›ãˆ", "repository": "ãƒĒポジトãƒĒ", "require_password": "パ゚ワãƒŧドをčĻæą‚", + "require_user_to_change_password_on_first_login": "ãƒĻãƒŧã‚ļãƒŧãĢåˆå›žãƒ­ã‚°ã‚¤ãƒŗæ™‚ãĢパ゚ワãƒŧドぎ変更をčĻæą‚ã™ã‚‹", "rescan": "å†ã‚šã‚­ãƒŖãƒŗ", "reset": "ãƒĒã‚ģット", "reset_password": "パ゚ワãƒŧドをãƒĒã‚ģット", "reset_people_visibility": "äēēį‰ŠãŽéžčĄ¨į¤ēč¨­åŽšã‚’ãƒĒã‚ģット", "reset_pin_code": "PINã‚ŗãƒŧドをãƒĒã‚ģット", "reset_pin_code_description": "PINã‚ŗãƒŧドをåŋ˜ã‚ŒãŸå ´åˆã¯ã€ã‚ĩãƒŧバãƒŧįŽĄį†č€…ãĢ逪įĩĄã—ãĻãƒĒã‚ģットできぞす", + "reset_pin_code_success": "æ­Ŗå¸¸ãĢPINã‚ŗãƒŧドをãƒĒã‚ģットしぞした", "reset_pin_code_with_password": "PINã‚ŗãƒŧドはいつでもパ゚ワãƒŧドをäŊŋãŖãĻãƒĒã‚ģットできぞす", "reset_sqlite": "SQLiteデãƒŧã‚ŋベãƒŧ゚をãƒĒã‚ģット", "reset_sqlite_clear_app_data": "デãƒŧã‚ŋをæļˆåŽģ", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "全ãĻãŽé‡č¤‡ã‚’č§Ŗæąēしぞした", "restore": "垊元", "restore_all": "全ãĻ垊元", + "restore_trash_action_prompt": "{count}é …į›ŽãŒã‚´ãƒŸįŽąã‹ã‚‰åžŠå…ƒã•ã‚Œãžã—ãŸ", "restore_user": "ãƒĻãƒŧã‚ļãƒŧを垊元", "restored_asset": "é …į›Žã‚’åžŠå…ƒã—ãžã—ãŸ", "resume": "再開", "resume_paused_jobs": "再開: {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "ã‚ĸップロãƒŧドを再čŠĻ行", "review_duplicates": "é‡č¤‡ã‚’čĒŋæŸģ", "review_large_files": "ã‚ĩイã‚ēぎ大きãĒãƒ•ã‚Ąã‚¤ãƒĢをčĻ‹ã‚‹", "role": "ロãƒŧãƒĢ", @@ -1796,6 +1912,7 @@ "role_viewer": "閲čϧ者", "running": "åŽŸčĄŒä¸­", "save": "äŋå­˜", + "save_to_gallery": "ã‚ŽãƒŖãƒŠãƒĒãƒŧãĢäŋå­˜", "saved": "äŋå­˜ã—ぞした", "saved_api_key": "APIキãƒŧをäŋå­˜ã—ぞした", "saved_profile": "ãƒ—ãƒ­ãƒ•ã‚ŖãƒŧãƒĢをäŋå­˜ã—ぞした", @@ -1806,10 +1923,9 @@ "scan": "ã‚šã‚­ãƒŖãƒŗ", "scan_all_libraries": "全ãĻぎナイブナãƒĒã‚’ã‚šã‚­ãƒŖãƒŗ", "scan_library": "ã‚šã‚­ãƒŖãƒŗ", + "scan_settings": "ã‚šã‚­ãƒŖãƒŗč¨­åŽš", "scanning": "ã‚šã‚­ãƒŖãƒŗä¸­", "scanning_for_album": "ã‚ĸãƒĢãƒãƒ ã‚’ã‚šã‚­ãƒŖãƒŗä¸­â€Ļ", - "screencast_mode_description": "į”ģéĸ上ãĢキãƒŧボãƒŧドとマã‚Ļã‚šãŽã‚¤ãƒŗã‚¸ã‚ąãƒŧã‚ŋãƒŧã‚’čĄ¨į¤ēする", - "screencast_mode_title": "゚クãƒĒãƒŧãƒŗã‚­ãƒŖã‚šãƒˆãŽåˆ‡ã‚Šæ›ŋえ", "search": "検į´ĸ", "search_albums": "ã‚ĸãƒĢバムを検į´ĸ", "search_by_context": "å†™įœŸãŽå†…åŽšã§æ¤œį´ĸ", @@ -1817,8 +1933,6 @@ "search_by_description_example": "äēŦéƒŊãŽæĄœã‚ãã‚Š", "search_by_filename": "ãƒ•ã‚Ąã‚¤ãƒĢåã‚‚ã—ãã¯æ‹Ąåŧĩ子で検į´ĸ", "search_by_filename_example": "例: IMG_1234.JPG もしくは PNG", - "search_by_full_path": "フãƒĢパ゚ぞたはフりãƒĢダãƒŧで検į´ĸ", - "search_by_full_path_example": "/ãƒĻãƒŧã‚ļãƒŧ/プロジェクト/3DプãƒĒãƒŗãƒˆ/2026åš´7月1æ—Ĩ - プロジェクト、3D、プãƒĒãƒŗãƒˆã€2026ãĒおぎčĒžåĨで検į´ĸできぞす。", "search_by_ocr": "OCR検į´ĸ", "search_by_ocr_example": "バ゚ 時åˆģ襨", "search_camera_lens_model": "ãƒŦãƒŗã‚ēãƒĸデãƒĢで検į´ĸâ€Ļ", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "場所を選択", "search_filter_media_type": "ãƒĄãƒ‡ã‚Ŗã‚ĸãŽį¨ŽéĄž", "search_filter_media_type_title": "ãƒĄãƒ‡ã‚Ŗã‚ĸãŽį¨ŽéĄžã‚’é¸æŠž", + "search_filter_ocr": "OCRで検į´ĸ", "search_filter_people_title": "äēēį‰Šã‚’é¸æŠž", "search_filter_star_rating": "æ˜ŸčŠ•äžĄ", "search_filter_tags_title": "ã‚ŋグを選択", @@ -1848,13 +1963,25 @@ "search_no_people_named": "「{name}」という名前ぎäēēį‰ŠãŒã„ãžã›ã‚“", "search_no_result": "検į´ĸįĩæžœãĒし", "search_options": "検į´ĸã‚Ēãƒ—ã‚ˇãƒ§ãƒŗ", + "search_page_categories": "ã‚ĢテゴãƒĒ", + "search_page_motion_photos": "ãƒĸãƒŧã‚ˇãƒ§ãƒŗãƒ•ã‚Šãƒˆ", + "search_page_no_objects": "čĸĢ写äŊ“ãĢé–ĸするデãƒŧã‚ŋがãĒし", + "search_page_no_places": "場所ãĢé–ĸするデãƒŧã‚ŋãĒし", + "search_page_screenshots": "゚クãƒĒãƒŧãƒŗã‚ˇãƒ§ãƒƒãƒˆ", "search_page_search_photos_videos": "å†™įœŸã‚„å‹•į”ģを検į´ĸ", + "search_page_selfies": "č‡Ē撎り", + "search_page_things": "čĸĢ写äŊ“", "search_page_view_all_button": "すずãĻ襨į¤ē", + "search_page_your_activity": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ“ãƒ†ã‚Ŗ", + "search_page_your_map": "あãĒたぎマップ", "search_people": "äēēį‰Šã‚’æ¤œį´ĸ", "search_places": "場所を検į´ĸ", "search_rating": "ãƒŦãƒŧトで検į´ĸ...", + "search_result_page_new_search_hint": "検į´ĸ", "search_settings": "検į´ĸč¨­åŽš", "search_state": "éƒŊ道åēœįœŒã‚’検į´ĸâ€Ļ", + "search_suggestion_list_smart_search_hint_1": "゚マãƒŧト検į´ĸはデフりãƒĢトでã‚ĒãƒŗãĢãĒãŖãĻã„ãžã™ã€‚ãƒĄã‚ŋデãƒŧã‚ŋで検į´ĸã‚’čĄŒã†å ´åˆ:", + "search_suggestion_list_smart_search_hint_2": "m:単čĒž", "search_tags": "ã‚ŋグを検į´ĸ...", "search_timezone": "ã‚ŋイムゞãƒŧãƒŗã‚’æ¤œį´ĸâ€Ļ", "search_type": "検į´ĸã‚ŋイプ", @@ -1868,6 +1995,7 @@ "select_albums": "ã‚ĸãƒĢバム選択", "select_all": "全ãĻ選択", "select_all_duplicates": "全ãĻãŽé‡č¤‡ã‚’é¸æŠž", + "select_all_in": "{group}ぎすずãĻを選択", "select_avatar_color": "ã‚ĸバã‚ŋãƒŧãŽč‰˛ã‚’é¸æŠž", "select_count": "{count, plural, one {# 選択中} other {# 選択中}}", "select_cutoff_date": "æ‰“ãĄåˆ‡ã‚ŠæœŸé–“ã‚’é¸æŠž", @@ -1875,13 +2003,14 @@ "select_featured_photo": "äēēį‰Šå†™įœŸã‚’é¸æŠž", "select_from_computer": "PCから選択", "select_keep_all": "全ãĻäŋæŒ", + "select_library_owner": "ナイブナãƒĒæ‰€æœ‰č€…ã‚’é¸æŠž", "select_new_face": "æ–°ã—ã„éĄ”ã‚’é¸æŠž", "select_people": "äēēį‰Šã‚’é¸æŠž", "select_person": "äēēį‰Šã‚’é¸æŠž", "select_person_to_tag": "ã‚ŋグをäģ˜ã‘ã‚‹äēēį‰Šã‚’é¸ã‚“ã§ãã ã•ã„", "select_photos": "å†™įœŸã‚’é¸æŠž", - "select_quality": "品čŗĒを選択する", "select_trash_all": "全ãĻ削除", + "select_user_for_sharing_page_err_album": "ã‚ĸãƒĢバムäŊœæˆãĢå¤ąæ•—", "selected": "選択済ãŋ", "selected_count": "{count, plural, other {#個選択済ãŋ}}", "selected_gps_coordinates": "選択されたäŊįŊŽæƒ…å ą", @@ -1891,7 +2020,7 @@ "server_info_box_app_version": "ã‚ĸプãƒĒぎバãƒŧã‚¸ãƒ§ãƒŗ", "server_info_box_server_url": "ã‚ĩãƒŧバãƒŧぎURL", "server_offline": "ã‚ĩãƒŧバãƒŧがã‚Ēãƒ•ãƒŠã‚¤ãƒŗã§ã™", - "server_online": "ã‚ĩãƒŧバãƒŧはã‚ĒãƒŗãƒŠã‚¤ãƒŗã§ã™", + "server_online": "ã‚ĩãƒŧバãƒŧがã‚ĒãƒŗãƒŠã‚¤ãƒŗã§ã™", "server_privacy": "ã‚ĩãƒŧバãƒŧãƒ—ãƒŠã‚¤ãƒã‚ˇãƒŧ", "server_restarting_description": "こぎペãƒŧジはすぐãĢ更新されぞす。", "server_restarting_title": "ã‚ĩãƒŧバãƒŧは再čĩˇå‹•しãĻいぞす", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "ã‚ĒãƒĒジナãƒĢをčĒ­ãŋčžŧむ", "setting_image_viewer_preview_subtitle": "中į”ģčŗĒãŽå†™įœŸã‚’ãƒ­ãƒŧドしたいときãĢã‚ĒãƒŗãĢしãĻください。こぎ゚テップを゚キップしãĻį›´æŽĨ最大į”ģčŗĒãŽå†™įœŸã‚’čĄ¨į¤ēしたい場合はã‚ĒフãĢしãĻください。(ロãƒŧド中はã‚ĩムネイãƒĢがäģŖã‚ã‚ŠãĢ襨į¤ēされぞす)", "setting_image_viewer_preview_title": "プãƒŦビãƒĨãƒŧをčĒ­ãŋčžŧむ", + "setting_image_viewer_title": "į”ģ像", "setting_languages_apply": "éŠį”¨ã™ã‚‹", "setting_languages_subtitle": "ã‚ĸプãƒĒãŽč¨€čĒžã‚’å¤‰æ›´ã™ã‚‹", + "setting_notifications_notify_failures_grace_period": "バックグナã‚Ļãƒŗãƒ‰ãƒãƒƒã‚¯ã‚ĸãƒƒãƒ—å¤ąæ•—ãŽé€šįŸĨ: {duration}", + "setting_notifications_notify_hours": "{count}時間垌", + "setting_notifications_notify_immediately": "すぐãĢčĄŒã†", "setting_notifications_notify_minutes": "{count}分垌", + "setting_notifications_notify_never": "čĄŒã‚ãĒい", "setting_notifications_notify_seconds": "{count}į§’åžŒ", + "setting_notifications_single_progress_subtitle": "ã‚ĸップロãƒŧãƒ‰ä¸­ãŽå†™įœŸãŽčŠŗį´°", + "setting_notifications_single_progress_title": "バックã‚ĸãƒƒãƒ—ãŽčŠŗį´°ãĒ進行įŠļæŗã‚’čĄ¨į¤ē", "setting_notifications_subtitle": "通įŸĨč¨­åŽšã‚’å¤‰æ›´ã™ã‚‹", + "setting_notifications_total_progress_subtitle": "ã‚ĸップロãƒŧãƒ‰ãŽé€˛čĄŒįŠļæŗ (厌ä熿¸ˆãŋ/全äŊ“æžšæ•°)", + "setting_notifications_total_progress_title": "全äŊ“ぎバックã‚ĸãƒƒãƒ—ãŽé€˛čĄŒįŠļæŗã‚’čĄ¨į¤ē", "setting_video_viewer_auto_play_subtitle": "動į”ģを開くとč‡Ēå‹•ã§å†į”Ÿã•ã‚Œãžã™", "setting_video_viewer_auto_play_title": "動į”ģぎč‡Ēå‹•å†į”Ÿ", "setting_video_viewer_looping_title": "動į”ģをãƒĢãƒŧプする", "setting_video_viewer_original_video_subtitle": "動į”ģを゚トãƒĒãƒŧãƒŸãƒŗã‚°ã™ã‚‹éš›ãĢã€ãƒˆãƒŠãƒŗã‚šã‚ŗãƒŧドされた動į”ģが存在しãĻいãĻも、あえãĻã‚ĒãƒĒジナãƒĢį”ģčŗĒぎ動į”ģã‚’å†į”Ÿã—ãžã™ã€‚ã‚šãƒˆãƒĒãƒŧãƒŸãƒŗã‚°ãĢåž…ãĄæ™‚é–“ãŒį”Ÿã˜ã‚‹ã‹ã‚‚ã—ã‚Œãžã›ã‚“ã€‚ãĒお、デバイ゚上ãĢäŋå­˜ã•れãĻいる動į”ģã¯ã“ãŽč¨­åŽšãŽæœ‰į„ĄãĢé–ĸわらず、ã‚ĒãƒĒジナãƒĢį”ģčŗĒぎ動į”ģã‚’å†į”Ÿã—ãžã™ã€‚", "setting_video_viewer_original_video_title": "常ãĢã‚ĒãƒĒジナãƒĢį”ģčŗĒぎ動į”ģã‚’å†į”Ÿã™ã‚‹", "settings": "č¨­åŽš", + "settings_require_restart": "Immichを再čĩˇå‹•しãĻč¨­åŽšã‚’éŠį”¨ã—ãĻください", "settings_saved": "č¨­åŽšãŒäŋå­˜ã•れぞした", "setup_pin_code": "PINã‚ŗãƒŧドをã‚ģットã‚ĸップ", "share": "å…ąæœ‰", + "share_action_prompt": "{count}é …į›Žã‚’å…ąæœ‰ã—ãžã—ãŸ", + "share_add_photos": "å†™įœŸã‚’čŋŊ加", + "share_assets_selected": "{count}選択中", "share_dialog_preparing": "æē–備中", "share_link": "å…ąæœ‰ãƒĒãƒŗã‚¯", - "share_original": "å…ƒãŽč§ŖåƒåēĻīŧˆå¤§īŧ‰ã‚’äŊŋį”¨ã™ã‚‹", - "share_preview": "ã‚ĩムネイãƒĢīŧˆå°īŧ‰ã‚’äŊŋį”¨ã™ã‚‹", - "share_quality_body": "å…ąæœ‰ã™ã‚‹å‰ãĢã€å…ąæœ‰ã‚’é•ˇæŠŧししãĻį”ģčŗĒを選択しãĻください。", - "share_quality_title": "å…ąæœ‰ã™ã‚‹éš›ãŽį”ģčŗĒを選択しãĻください", "shared": "å…ąæœ‰æ¸ˆãŋ", "shared_album_activities_input_disable": "ã‚ŗãƒĄãƒŗãƒˆã¯ã‚ĒフãĢãĒãŖãĻぞす", "shared_album_activity_remove_content": "こぎã‚ĸã‚¯ãƒ†ã‚Ŗãƒ“ãƒ†ã‚Ŗã‚’å‰Šé™¤ã—ãžã™ã‹īŧŸ", "shared_album_activity_remove_title": "ã‚ĸã‚¯ãƒ†ã‚Ŗãƒ“ãƒ†ã‚Ŗã‚’å‰Šé™¤ã—ãžã™", "shared_album_section_people_action_error": "退å‡ēãĢå¤ąæ•—", + "shared_album_section_people_action_leave": "ãƒĻãƒŧã‚ļãƒŧをã‚ĸãƒĢバムから退å‡ēさせる", + "shared_album_section_people_action_remove_user": "ãƒĻãƒŧã‚ļãƒŧをã‚ĸãƒĢバムから退å‡ēさせる", "shared_album_section_people_title": "äēēį‰Š", "shared_by": "ãĢã‚ˆã‚Šå…ąæœ‰", "shared_by_user": "{user} ãĢã‚ˆã‚Šå…ąæœ‰", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ã‚ĸップロãƒŧド厌äē†", "shared_link_app_bar_title": "å…ąæœ‰ãƒĒãƒŗã‚¯", "shared_link_clipboard_copied_massage": "クãƒĒップボãƒŧドãĢã‚ŗãƒ”ãƒŧしぞした", + "shared_link_clipboard_text": "ãƒĒãƒŗã‚¯: {link}\nパ゚ワãƒŧド: {password}", "shared_link_create_error": "å…ąæœ‰į”¨ãŽãƒĒãƒŗã‚¯äŊœæˆæ™‚ãĢエナãƒŧがį™ēį”Ÿã—ãžã—ãŸ", "shared_link_custom_url_description": "ã“ãŽå…ąæœ‰ãƒĒãƒŗã‚¯ãĢã‚Ģ゚ã‚ŋムURLでã‚ĸクã‚ģ゚", - "shared_link_custom_url_title": "ã‚Ģ゚ã‚ŋムぎURL", - "shared_link_custom_url_warning": "č­Ļ告:「/」がå…ĨãŖãŸã‚Ģ゚ã‚ŋムぎURL名は、期垅おおりãĢ動äŊœã—ãĒい可čƒŊ性がありぞす。", "shared_link_edit_description_hint": "čĒŦ明をčŋŊ加", "shared_link_edit_expire_after_option_day": "1æ—Ĩ", "shared_link_edit_expire_after_option_days": "{count}æ—Ĩ", @@ -1984,6 +2123,12 @@ "shared_with_partner": "{partner} ã¨å…ąæœ‰ã—ãžã—ãŸ", "sharing": "å…ąæœ‰", "sharing_enter_password": "こぎペãƒŧã‚¸ã‚’é–˛čĻ§ã™ã‚‹ãĢはパ゚ワãƒŧドをå…Ĩ力しãĻください。", + "sharing_page_album": "å…ąæœ‰ã‚ĸãƒĢバム", + "sharing_page_description": "å…ąæœ‰ã‚ĸãƒĢバムをäŊœæˆã—ãĻ同じネットワãƒŧクãĢいるäēēãŸãĄãĢå†™įœŸã‚’å…ąæœ‰", + "sharing_page_empty_list": "å…ąæœ‰ã‚ĸãƒĢバムãĒし", + "sharing_sidebar_description": "å…ąæœ‰ã¸ãŽãƒĒãƒŗã‚¯ã‚’ã‚ĩイドバãƒŧãĢ襨į¤ē", + "sharing_silver_appbar_create_shared_album": "å…ąæœ‰ã‚ĸãƒĢバムをäŊœæˆ", + "sharing_silver_appbar_share_partner": "パãƒŧトナãƒŧã¨å…ąæœ‰", "shift_to_permanent_delete": "⇧をæŠŧしãĻã‚ĸã‚ģットを厌全ãĢ削除", "show_album_options": "ã‚ĸãƒĢãƒãƒ č¨­åŽšã‚’čĄ¨į¤ē", "show_albums": "ã‚ĸãƒĢãƒãƒ ã‚’čĄ¨į¤ē", @@ -1995,7 +2140,6 @@ "show_in_timeline": "ã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗãĢ襨į¤ē", "show_in_timeline_setting_description": "こぎãƒĻãƒŧã‚ļãƒŧãŽå†™įœŸã¨å‹•į”ģをã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗãĢ襨į¤ē", "show_keyboard_shortcuts": "キãƒŧボãƒŧãƒ‰ã‚ˇãƒ§ãƒŧトã‚Ģãƒƒãƒˆã‚’čĄ¨į¤ē", - "show_less": "襨į¤ē数をへらす", "show_metadata": "ãƒĄã‚ŋデãƒŧã‚ŋをčĻ‹ã‚‹", "show_or_hide_info": "æƒ…å ąã‚’čĄ¨į¤ē/非表į¤ē", "show_password": "パ゚ワãƒŧãƒ‰ã‚’čĄ¨į¤ē", @@ -2004,7 +2148,6 @@ "show_schema": "゚キãƒŧãƒžã‚’čĄ¨į¤ē", "show_search_options": "検į´ĸã‚Ēãƒ—ã‚ˇãƒ§ãƒŗã‚’čĄ¨į¤ē", "show_shared_links": "å…ąæœ‰ãƒĒãƒŗã‚¯ã‚’čĄ¨į¤ē", - "show_slideshow_metadata_overlay": "į”ģåƒæƒ…å ąã‚’čĄ¨į¤ē", "show_slideshow_transition": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧãŽãƒˆãƒŠãƒŗã‚¸ã‚ˇãƒ§ãƒŗã‚’čĄ¨į¤ē", "show_supporter_badge": "ã‚ĩポãƒŧã‚ŋãƒŧバッジ", "show_supporter_badge_description": "ã‚ĩポãƒŧã‚ŋãƒŧãƒãƒƒã‚¸ã‚’čĄ¨į¤ē", @@ -2016,25 +2159,18 @@ "sign_out": "ã‚ĩã‚¤ãƒŗã‚ĸã‚Ļト", "sign_up": "į™ģ錞", "size": "ã‚ĩイã‚ē", - "skip": "゚キップする", "skip_to_content": "ã‚ŗãƒŗãƒ†ãƒŗãƒ„ã¸ã‚šã‚­ãƒƒãƒ—", "skip_to_folders": "フりãƒĢダぺキップ", "skip_to_tags": "ã‚ŋグぺキップ", "slideshow": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧ", - "slideshow_body": "ã‚†ãŖãŸã‚Šã¨ãã¤ã‚ãŽãĒがら、フãƒĢ゚クãƒĒãƒŧãƒŗãŽã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧã§å†™įœŸã‚’ãŠæĨŊしãŋください。", - "slideshow_metadata_overlay_mode": "ã‚ĒãƒŧバãƒŧãƒŦã‚¤ã‚ŗãƒŗãƒ†ãƒŗãƒ„", - "slideshow_metadata_overlay_mode_description_only": "čĒŦ明ぎãŋ", - "slideshow_metadata_overlay_mode_full": "フãƒĢ", "slideshow_repeat": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧã‚’įš°ã‚Ščŋ”す", "slideshow_repeat_description": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧがįĩ‚ã‚ãŖãŸã‚‰å§‹ã‚ãĢæˆģりぞす", "slideshow_settings": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧč¨­åŽš", - "slideshow_title": "ã‚šãƒŠã‚¤ãƒ‰ã‚ˇãƒ§ãƒŧ", - "smart_album": "゚マãƒŧトã‚ĸãƒĢバム", - "some_assets_already_have_a_location_warning": "é¸æŠžã•ã‚ŒãŸé …į›ŽãŽä¸€éƒ¨ãĢは、すでãĢå ´æ‰€ãŒč¨­åŽšã•ã‚ŒãĻいぞす。", "sort_albums_by": "こぎ順åēã§ã‚ĸãƒĢバムをã‚Ŋãƒŧトâ€Ļ", "sort_created": "äŊœæˆæ—Ĩ", "sort_items": "é …į›ŽãŽæ•°", "sort_modified": "変更æ—Ĩ", + "sort_newest": "æœ€æ–°ãŽå†™įœŸ", "sort_oldest": "å¤ã„å†™įœŸ", "sort_people_by_similarity": "äŧŧãĻいる順ãĢäēēį‰Šã‚’ä¸Ļãŗæ›ŋえる", "sort_recent": "æœ€æ–°ãŽå†™įœŸ", @@ -2043,6 +2179,7 @@ "stack": "゚ã‚ŋック", "stack_action_prompt": "{count}が重ねられぞした", "stack_duplicates": "゚ã‚ŋãƒƒã‚¯ãŽé‡č¤‡", + "stack_select_one_photo": "゚ã‚ŋãƒƒã‚¯ãŽãƒĄã‚¤ãƒŗãŽå†™įœŸã‚’é¸æŠž", "stack_selected_photos": "é¸æŠžã—ãŸå†™įœŸã‚’ã‚šã‚ŋックする", "stacked_assets_count": "{count, plural, one {#枚} other {#枚}}゚ã‚ŋックしぞした", "stacktrace": "゚ã‚ŋックトãƒŦãƒŧ゚", @@ -2051,10 +2188,6 @@ "start_date_before_end_date": "開始æ—Ĩはįĩ‚ä熿—Ĩより前でãĒければãĒりぞせん", "state": "éƒŊ道åēœįœŒ", "status": "゚テãƒŧã‚ŋ゚", - "step_delete": "゚テップを削除する", - "step_delete_confirm": "こぎ゚テップを削除しãĻもよろしいですか?", - "step_details": "ã‚šãƒ†ãƒƒãƒ—ãŽčŠŗį´°", - "steps": "゚テップ", "stop_casting": "ã‚­ãƒŖã‚šãƒˆã‚’åœæ­ĸ", "stop_motion_photo": "゚トップãƒĸãƒŧã‚ˇãƒ§ãƒŗå†™įœŸ", "stop_photo_sharing": "å†™įœŸãŽå…ąæœ‰ã‚’į„ĄåŠšåŒ–ã—ãžã™ã‹īŧŸ", @@ -2075,18 +2208,18 @@ "swap_merge_direction": "įĩąåˆã™ã‚‹æ–šå‘ã‚’å…Ĩれæ›ŋえ", "sync": "同期", "sync_albums": "ã‚ĸãƒĢバムを同期", + "sync_albums_manual_subtitle": "ã‚ĸップロãƒŧド済ãŋぎ全ãĻãŽå†™įœŸã‚„å‹•į”ģを選択されたバックã‚ĸップã‚ĸãƒĢバムãĢ同期する", "sync_local": "ロãƒŧã‚ĢãƒĢを同期", "sync_remote": "ãƒĒãƒĸãƒŧトを同期", "sync_status": "同期ぎįŠļ態", "sync_status_subtitle": "åŒæœŸã‚ˇã‚šãƒ†ãƒ ã‚’įĸēčĒãƒģįŽĄį†", "sync_upload_album_setting_subtitle": "ã‚ĩãƒŧバãƒŧ上ぎã‚ĸãƒĢバムぎ内厚をį̝æœĢ上ぎã‚ĸãƒĢバムと同期しぞす (ã‚ĩãƒŧバãƒŧãĢã‚ĸãƒĢãƒãƒ ãŒį„Ąã„å ´åˆč‡Ē動でäŊœæˆã•れぞす。ぞた、ã‚ĸップロãƒŧドされãĻいãĒã„å†™įœŸã‚„å‹•į”ģは同期されぞせん)", - "system_theme": "ã‚ˇã‚šãƒ†ãƒ ãŽãƒ†ãƒŧマ", - "system_theme_command_description": "ã‚ˇã‚šãƒ†ãƒ ãŽãƒ†ãƒŧマをäŊŋį”¨ã™ã‚‹{value}", "tag": "ã‚ŋグäģ˜ã‘する", "tag_assets": "å†™įœŸ/動į”ģãĢã‚ŋグäģ˜ã‘する", "tag_created": "ã‚ŋグ: {tag} をäŊœæˆã—ぞした", "tag_face": "éĄ”ã‚’ã‚ŋグäģ˜ã‘", "tag_feature_description": "æ„å‘ŗã‚’æŒãŸã›ãŸã‚ŋグトでグãƒĢãƒŧプ化しãĻå†™įœŸã¨å‹•į”ģã‚’é–˛čĻ§ã™ã‚‹", + "tag_not_found_question": "ã‚ŋグがčĻ‹ã¤ã‹ã‚Šãžã›ã‚“ã‹? ã“ãĄã‚‰ã‹ã‚‰ã‚ŋグをäŊœæˆã§ããžã™", "tag_people": "äēēį‰Šã‚ŋグ", "tag_updated": "ã‚ŋグ: {tag} を更新しぞした", "tagged_assets": "{count, plural, one {#個} other {#個}}ãŽå†™įœŸ/動į”ģãĢã‚ŋグäģ˜ã‘しぞした", @@ -2101,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ä¸€čĄŒã”ã¨ãŽčĄ¨į¤ē枚数: {count}", "theme_setting_colorful_interface_subtitle": "ã‚ĸクã‚ģãƒŗãƒˆã‚Ģナãƒŧã‚’čƒŒæ™¯ãĢもäŊŋį”¨ã™ã‚‹", "theme_setting_colorful_interface_title": "ã‚ĢナフãƒĢãĒUI", + "theme_setting_image_viewer_quality_subtitle": "į”ģ像ビãƒĨãƒŧぎį”ģčŗĒãŽč¨­åŽš", + "theme_setting_image_viewer_quality_title": "į”ģ像ビãƒĨãƒŧ", "theme_setting_primary_color_subtitle": "ã‚ĸクã‚ģãƒŗãƒˆį”¨ãŽč‰˛ã‚’é¸æŠž", "theme_setting_primary_color_title": "ã‚ĸクã‚ģãƒŗãƒˆã‚Ģナãƒŧ", "theme_setting_system_primary_color_title": "į̝æœĢã§č¨­åŽšã•ã‚ŒãĻã„ã‚‹č‰˛ã‚’äŊŋう", "theme_setting_system_theme_switch": "č‡Ē動 (ãƒ‡ãƒã‚¤ã‚šãŽč¨­åŽšã‚’åæ˜ )", + "theme_setting_theme_subtitle": "テãƒŧãƒžč¨­åŽš", + "theme_setting_three_stage_loading_subtitle": "三æŽĩ階čĒ­ãŋčžŧãŋを有劚ãĢすると、パフりãƒŧãƒžãƒŗã‚šãŒæ”šå–„ã™ã‚‹å¯čƒŊ性がありぞすが、ネットワãƒŧã‚¯č˛ čˇãŒč‘—ã—ãåĸ—加しぞす。", + "theme_setting_three_stage_loading_title": "三æŽĩ階čĒ­ãŋčžŧãŋをã‚ĒãƒŗãĢする", "then": "そぎとき", "they_will_be_merged_together": "ã“ã‚Œã‚‰ã¯ä¸€įˇ’ãĢįĩąåˆã•れぞす", "third_party_resources": "ã‚ĩãƒŧドパãƒŧãƒ†ã‚ŖãƒŧãƒĒã‚Ŋãƒŧ゚", @@ -2130,17 +2268,23 @@ "trash_all": "全ãĻ削除", "trash_count": "{count, number}æžšã‚´ãƒŸįŽąã¸į§ģ動", "trash_delete_asset": "å†™įœŸ/動į”ģã‚’ã‚´ãƒŸįŽąã¸į§ģ動/削除", + "trash_emptied": "ã‚´ãƒŸįŽąã‚’įŠēãĢしぞした", "trash_no_results_message": "ã‚´ãƒŸįŽąãĢį§ģå‹•ã—ãŸå†™įœŸã‚„å‹•į”ģがここãĢ襨į¤ēされぞす。", "trash_page_delete_all": "すずãĻ削除", + "trash_page_empty_trash_dialog_content": "ã‚´ãƒŸįŽąã‚’įŠēãĢしぞすかīŧŸé¸æŠžã•ã‚ŒãŸé …į›Žã¯åŽŒå…¨ãĢ削除されぞす。こぎ操äŊœã¯å–りæļˆã›ãžã›ã‚“。", "trash_page_info": "ã‚´ãƒŸįŽąãĢį§ģ動したã‚ĸイテムは{days}æ—Ĩ垌ãĢ削除されぞす", + "trash_page_no_assets": "ã‚´ãƒŸįŽąã¯įŠēです", + "trash_page_restore_all": "すずãĻ垊元", + "trash_page_select_assets_btn": "é …į›Žã‚’é¸æŠž", + "trash_page_title": "ã‚´ãƒŸįŽą ({count})", "trashed_items_will_be_permanently_deleted_after": "ã‚´ãƒŸįŽąãĢå…Ĩれられたã‚ĸイテムは{days, plural, one {#æ—Ĩ} other {#æ—Ĩ}}垌ãĢ厌全ãĢ削除されぞす。", "trigger": "トãƒĒã‚Ŧãƒŧ", - "trigger_asset_metadata_extraction": "é …į›ŽãŽãƒĄã‚ŋデãƒŧã‚ŋぎæŠŊå‡ē", - "trigger_asset_metadata_extraction_description": "é …į›Žã‹ã‚‰EXIFãƒĄã‚ŋデãƒŧã‚ŋがæŠŊå‡ēされた際ãĢトãƒĒã‚Ŧãƒŧされる", - "trigger_asset_uploaded": "ã‚ĸã‚ģットをã‚ĸップロãƒŧド", + "trigger_asset_uploaded": "ã‚ĸã‚ģットがã‚ĸップロãƒŧド", "trigger_asset_uploaded_description": "æ–°ã—ã„é …į›ŽãŒã‚ĸップロãƒŧドされたときãĢトãƒĒã‚Ŧãƒŧされぞす", + "trigger_description": "ワãƒŧクフロãƒŧã‚’é–‹å§‹ã™ã‚‹ã‚¤ãƒ™ãƒŗãƒˆ", "trigger_person_recognized": "čĒč­˜ã•ã‚ŒãŸäēēį‰Š", - "trigger_person_recognized_description": "äēēį‰ŠãŒæ¤œįŸĨされた際ãĢåŽŸčĄŒã•ã‚Œãžã™", + "trigger_person_recognized_description": "äēēį‰ŠãŒæ¤œįŸĨされた際ぎトãƒĒã‚Ŧãƒŧ", + "trigger_type": "トãƒĒã‚Ŧãƒŧã‚ŋイプ", "troubleshoot": "トナブãƒĢã‚ˇãƒĨãƒŧãƒ†ã‚Ŗãƒŗã‚°", "type": "ã‚ŋイプ", "unable_to_change_pin_code": "PINã‚ŗãƒŧドを変更できぞせんでした", @@ -2156,7 +2300,6 @@ "unknown": "不明", "unknown_country": "不明ãĒå›Ŋ", "unknown_date": "不明ãĒæ—Ĩäģ˜", - "unknown_schema": "不明ãĒ゚キãƒŧマ", "unknown_year": "不明ãĒåš´", "unlimited": "į„Ąåˆļ限", "unlink_motion_video": "ãƒĸãƒŧã‚ˇãƒ§ãƒŗãƒ“ãƒ‡ã‚ĒぎãƒĒãƒŗã‚¯ã‚’č§Ŗé™¤", @@ -2169,12 +2312,14 @@ "unsaved_change": "æœĒäŋå­˜ãŽå¤‰æ›´", "unselect_all": "全ãĻé¸æŠžč§Ŗé™¤", "unselect_all_duplicates": "全ãĻãŽé‡č¤‡ãŽé¸æŠžã‚’č§Ŗé™¤", + "unselect_all_in": "{group}ぎすずãĻãŽé¸æŠžã‚’č§Ŗé™¤", "unstack": "゚ã‚ŋãƒƒã‚¯ã‚’č§Ŗé™¤", "unstack_action_prompt": "{count}é …į›ŽãŽé‡ã­åˆã‚ã›ã‚’č§Ŗé™¤", "unstacked_assets_count": "{count, plural, one {#個} other {#個}}ãŽå†™įœŸ/動į”ģを゚ã‚ŋãƒƒã‚¯ã‹ã‚‰č§Ŗé™¤ã—ãžã—ãŸ", "unsupported_field_type": "ã‚ĩポãƒŧトされãĻいãĒã„ãƒ•ã‚ŖãƒŧãƒĢドã‚ŋイプ", "unsupported_file_type": "ãƒ•ã‚Ąã‚¤ãƒĢåŊĸåŧã€Œ{type}」はã‚ĩポãƒŧトされãĻいãĒã„ãŸã‚ã€ãƒ•ã‚Ąã‚¤ãƒĢ「{file}」をã‚ĸップロãƒŧドできぞせん。", "untagged": "ã‚ŋã‚°ã‚’č§Ŗé™¤", + "untitled_workflow": "į„ĄéĄŒãŽãƒ¯ãƒŧクフロãƒŧ", "up_next": "æŦĄã¸", "update_location_action_prompt": "{count}é …į›Žã‚’åŗč¨˜ãŽäŊįŊŽæƒ…å ąãĢã‚ĸップデãƒŧトしぞす:", "updated_at": "更新", @@ -2182,6 +2327,8 @@ "upload": "ã‚ĸップロãƒŧド", "upload_concurrency": "ã‚ĸップロãƒŧãƒ‰ãŽåŒæ™‚åŽŸčĄŒæ•°", "upload_details": "ã‚ĸップロãƒŧãƒ‰ãŽčŠŗį´°", + "upload_dialog_info": "é¸æŠžã—ãŸé …į›ŽãŽãƒãƒƒã‚¯ã‚ĸップをしぞすかīŧŸ", + "upload_dialog_title": "ã‚ĸップロãƒŧド", "upload_error_with_count": "{count, plural, one {#å€‹ãŽå†™įœŸ/動į”ģ} other {#å€‹ãŽå†™įœŸ/動į”ģ}}ãĢついãĻã‚ĸップロãƒŧドエナãƒŧがį™ēį”Ÿã—ãžã—ãŸ", "upload_errors": "ã‚ĸップロãƒŧドは{count, plural, one {#個} other {#個}}ぎエナãƒŧで厌äē†ã—ぞした、新しくã‚ĸップロãƒŧドされたã‚ĸã‚ģットをčĻ‹ã‚‹ãĢはペãƒŧジを更新しãĻください。", "upload_finished": "ã‚ĸップロãƒŧド厌äē†", @@ -2191,12 +2338,9 @@ "upload_status_errors": "エナãƒŧ", "upload_status_uploaded": "ã‚ĸップロãƒŧド済", "upload_success": "ã‚ĸップロãƒŧド成功、新しくã‚ĸップロãƒŧãƒ‰ã•ã‚ŒãŸå†™įœŸ/動į”ģをčĻ‹ã‚‹ãĢはペãƒŧジを更新しãĻください。", - "upload_to_album_body": "手動ã‚ĸップロãƒŧド抟čƒŊをäŊŋį”¨ã—ãĒい場合、ロãƒŧã‚ĢãƒĢãŽå†™įœŸã‚’ã‚ĸップロãƒŧドする際ãĢį›´æŽĨã‚ĸãƒĢバムへčŋŊ加できるようãĢãĒりぞした。ã‚ĸップロãƒŧドした垌ãĢ攚めãĻã‚ĸãƒĢバムへčŋŊ加する手間はもうåŋ…čĻã‚ã‚Šãžã›ã‚“ã€‚", - "upload_to_album_title": "ã‚ĸãƒĢバムãĢį›´æŽĨã‚ĸップロãƒŧドする", "upload_to_immich": "ImmichãĢã‚ĸップロãƒŧド ({count})", "uploading": "ã‚ĸップロãƒŧド中", "uploading_media": "ãƒĄãƒ‡ã‚Ŗã‚ĸをã‚ĸップロãƒŧド中", - "uploads": "ã‚ĸップロãƒŧド", "url": "URL", "usage": "äŊŋį”¨åŽšé‡", "use_biometric": "į”ŸäŊ“čĒč¨ŧã‚’ã”åˆŠį”¨ãã ã•ã„", @@ -2204,7 +2348,6 @@ "use_browser_locale_description": "ブナã‚Ļã‚ļãŽč¨€čĒžã¨åœ°åŸŸãŽč¨­åŽšãĢåž“ãŖãĻ、æ—Ĩäģ˜ãƒģ時åˆģãƒģ数値を書åŧč¨­åŽšã—ãžã™", "use_current_connection": "įžåœ¨ãŽæŽĨįƒ…å ąã‚’äŊŋᔍ", "use_custom_date_range": "äģŖã‚ã‚ŠãĢã‚Ģ゚ã‚ŋムæ—Ĩäģ˜į¯„å›˛ã‚’äŊŋᔍ", - "use_template": "ãƒ†ãƒŗãƒ—ãƒŦãƒŧトをäŊŋᔍ", "user": "ãƒĻãƒŧã‚ļãƒŧ", "user_has_been_deleted": "こぎãƒĻãƒŧã‚ļãƒŧは削除されぞした", "user_id": "ãƒĻãƒŧã‚ļãƒŧID", @@ -2214,6 +2357,7 @@ "user_privacy": "ãƒĻãƒŧã‚ļãƒŧãƒ—ãƒŠã‚¤ãƒã‚ˇãƒŧ", "user_purchase_settings": "čŗŧå…Ĩ", "user_purchase_settings_description": "čŗŧå…Ĩã‚’įŽĄį†", + "user_role_set": "{user} を{role}ãĢč¨­åŽšã—ãžã—ãŸ", "user_usage_detail": "ãƒĻãƒŧã‚ļãƒŧäŊŋᔍįŠļæŗãŽčŠŗį´°", "user_usage_stats": "ã‚ĸã‚Ģã‚ĻãƒŗãƒˆåˆŠį”¨įŠļæŗįĩąč¨ˆ", "user_usage_stats_description": "ã‚ĸã‚Ģã‚ĻãƒŗãƒˆåˆŠį”¨įŠļæŗįĩąč¨ˆã‚’襨į¤ē", @@ -2223,6 +2367,7 @@ "utilities": "ãƒĻãƒŧãƒ†ã‚ŖãƒĒãƒ†ã‚Ŗ", "validate": "čĒč¨ŧ", "validate_endpoint_error": "有劚ãĒURLをå…Ĩ力しãĻください", + "validation_error": "バãƒĒデãƒŧã‚ˇãƒ§ãƒŗã‚¨ãƒŠãƒŧ", "variables": "変数", "version": "バãƒŧã‚¸ãƒ§ãƒŗ", "version_announcement_closing": "あãĒたぎ友äēē、Alex", @@ -2232,9 +2377,9 @@ "video": "動į”ģ", "video_hover_setting": "ホバãƒŧ時ãĢã‚ĩムネイãƒĢで動į”ģã‚’å†į”Ÿ", "video_hover_setting_description": "マã‚Ļã‚šãŒé …į›ŽãŽä¸ŠãĢあるときãĢ動į”ģぎã‚ĩムネイãƒĢã‚’å†į”Ÿã—ãžã™ã€‚į„ĄåŠšæ™‚ã§ã‚‚å†į”Ÿã‚ĸã‚¤ã‚ŗãƒŗãĢã‚Ģãƒŧã‚ŊãƒĢã‚’åˆã‚ã›ã‚‹ã¨å†į”Ÿã‚’é–‹å§‹ã§ããžã™ã€‚", - "video_quality": "į”ģčŗĒ", "videos": "ビデã‚Ē", "videos_count": "{count, plural, one {#個} other {#個}}ぎ動į”ģ", + "videos_only": "動į”ģぎãŋ", "view": "čĻ‹ã‚‹", "view_album": "ã‚ĸãƒĢバムをčĻ‹ã‚‹", "view_all": "すずãĻčĻ‹ã‚‹", @@ -2243,29 +2388,29 @@ "view_details": "čŠŗį´°ã‚’čĄ¨į¤ē", "view_in_timeline": "ã‚ŋã‚¤ãƒ ãƒŠã‚¤ãƒŗã§čĻ‹ã‚‹", "view_link": "ãƒĒãƒŗã‚¯ã‚’čĻ‹ã‚‹", + "view_links": "ãƒĒãƒŗã‚¯ã‚’įĸēčĒã™ã‚‹", "view_name": "åˆ†éĄž", "view_next_asset": "æŦĄãŽå†™įœŸ/動į”ģをčĻ‹ã‚‹", "view_previous_asset": "å‰ãŽå†™įœŸ/動į”ģをčĻ‹ã‚‹", "view_qr_code": "QRã‚ŗãƒŧドをčĻ‹ã‚‹", "view_similar_photos": "類äŧŧã™ã‚‹å†™įœŸã‚’čĻ‹ã‚‹", "view_stack": "ビãƒĨãƒŧ゚ã‚ŋック", + "view_user": "ãƒĻãƒŧã‚ļãƒŧをčĻ‹ã‚‹", "viewer_remove_from_stack": "゚ã‚ŋックから外す", + "viewer_stack_use_as_main_asset": "ãƒĄã‚¤ãƒŗãŽį”ģ像としãĻäŊŋį”¨ã™ã‚‹", + "viewer_unstack": "゚ã‚ŋãƒƒã‚¯ã‚’č§Ŗé™¤", "visibility": "襨į¤ēč¨­åŽš", "visibility_changed": "{count, plural, one {#äēē} other {#äēē}}ぎäēēį‰ŠãŽéžčĄ¨į¤ēč¨­åŽšãŒå¤‰æ›´ã•ã‚Œãžã—ãŸ", "visual": "ビジãƒĨã‚ĸãƒĢ", + "visual_builder": "ビジãƒĨã‚ĸãƒĢビãƒĢダãƒŧ", "waiting": "垅抟中", "waiting_count": "垅抟中: {count}", "warning": "č­Ļ告", "week": "週", "welcome": "ようこそ", "welcome_to_immich": "ImmichãĢようこそ", - "whats_new": "æ–°į€æƒ…å ą", - "whats_new_settings_subtitle": "ImmichãŽæ–°į€æƒ…å ąã‚’ãƒã‚§ãƒƒã‚¯", - "whats_new_version": "バãƒŧã‚¸ãƒ§ãƒŗ{version}", - "when": "いつ", "width": "åš…", "wifi_name": "Wi-Fiぎ名前(SSID)", - "workflow": "ワãƒŧクフロãƒŧ", "workflow_delete_prompt": "こぎワãƒŧクフロãƒŧをãģんとうãĢ削除しぞすかīŧŸ", "workflow_deleted": "ワãƒŧクフロãƒŧ削除厌äē†", "workflow_description": "ワãƒŧクフロãƒŧぎčĒŦ明文", @@ -2275,17 +2420,17 @@ "workflow_name": "ワãƒŧクフロãƒŧåį§°", "workflow_navigation_prompt": "変更内厚をäŋå­˜ã›ãšãĢįĩ‚äē†ã—ぞすかīŧŸ", "workflow_summary": "ワãƒŧクフロãƒŧぎã‚ĩマãƒĒ", - "workflow_templates": "ワãƒŧクフロãƒŧãŽãƒ†ãƒŗãƒ—ãƒŦãƒŧト", "workflow_update_success": "ワãƒŧクフロãƒŧぎ更新ãĢ成功しぞした", + "workflow_updated": "ワãƒŧクフロãƒŧが更新されぞした", "workflows": "ワãƒŧクフロãƒŧ", "workflows_help_text": "ワãƒŧクフロãƒŧはあãĒたぎã‚ĸã‚ģットãĢ寞し、トãƒĒã‚Ŧãƒŧã‚„ãƒ•ã‚ŖãƒĢã‚ŋãƒŧã‚’č¨­åŽšã™ã‚‹ã“ã¨ã§ã‚ĸã‚¯ã‚ˇãƒ§ãƒŗã‚’č‡Ē動化しぞす", "wrong_pin_code": "PINã‚ŗãƒŧãƒ‰ãŒé–“é•ãŖãĻいぞす", - "x_of_total": "{x}Ãˇ{total}", "year": "åš´", "years_ago": "{years, plural, one {#åš´} other {#åš´}}前", "yes": "はい", "you_dont_have_any_shared_links": "å…ąæœ‰ãƒĒãƒŗã‚¯ã¯ã‚ã‚Šãžã›ã‚“", "your_wifi_name": "Wi-Fiぎ名前(SSID)", "zero_to_clear_rating": "0をæŠŧã™ã¨é …į›ŽãŽčŠ•äžĄã‚’å‰Šé™¤ã§ããžã™", - "zoom_image": "į”ģåƒã‚’æ‹Ąå¤§" + "zoom_image": "į”ģåƒã‚’æ‹Ąå¤§", + "zoom_to_bounds": "į”ģéĸįĢ¯ãžã§ã‚ēãƒŧム" } diff --git a/i18n/ka.json b/i18n/ka.json index 0a0d4c095e..6ed5cdd6ce 100644 --- a/i18n/ka.json +++ b/i18n/ka.json @@ -10,18 +10,24 @@ "active": "აáƒĨáƒĸáƒ˜áƒŖáƒ áƒ˜", "active_count": "აáƒĨáƒĸáƒ˜áƒŖáƒ áƒ˜: {count}", "activity": "აáƒĨáƒĸივობა", + "activity_changed": "აáƒĨáƒĸივობა {enabled, select, true {áƒŠáƒáƒ áƒ—áƒŖáƒšáƒ˜} other {áƒ’áƒáƒ›áƒáƒ áƒ—áƒŖáƒšáƒ˜}}", "add": "დაამაáƒĸე", "add_a_description": "დაამაáƒĸე აáƒĻáƒŦერა", "add_a_location": "დაამაáƒĸე ადგილი", "add_a_name": "დაამაáƒĸე სახელი", "add_a_title": "áƒ“áƒáƒáƒĄáƒáƒ—áƒáƒŖáƒ áƒ”", "add_action": "დაამაáƒĸე მოáƒĨმედება", + "add_action_description": "დააჭირე რომ დაამაáƒĸო მოáƒĨმედება", "add_assets": "áƒ áƒ”áƒĄáƒŖáƒ áƒĄáƒ˜áƒĄ აáƒĸვირთვა", "add_birthday": "დაბადების დáƒĻიქ დამაáƒĸება", "add_endpoint": "ბოლოáƒŦერáƒĸილის დამაáƒĸება", "add_exclusion_pattern": "დაამაáƒĸე გამონაკლისი áƒœáƒ˜áƒ›áƒŖáƒ¨áƒ˜", + "add_filter": "დაამაáƒĸე ფილáƒĸრი", + "add_filter_description": "დააჭირე ფილáƒĸრიქ დასამაáƒĸებლად", "add_location": "დაამაáƒĸე ადგილი", + "add_more_users": "დაამაáƒĸე მომხმარებლები", "add_partner": "დაამაáƒĸე პარáƒĸნიორი", + "add_path": "დაამაáƒĸე მისამართი", "add_photos": "დაამაáƒĸე ფოáƒĸოები", "add_tag": "დაამაáƒĸე თეგი", "add_to": "დაამაáƒĸე ...ში", @@ -29,9 +35,11 @@ "add_to_album_bottom_sheet_added": "დამაáƒĸáƒ”áƒ‘áƒŖáƒšáƒ˜áƒ {album}-ში", "add_to_album_bottom_sheet_already_exists": "{album}-ში áƒŖáƒ™áƒ•áƒ” არსებობს", "add_to_album_bottom_sheet_some_local_assets": "ზოგიერთი áƒšáƒáƒ™áƒáƒšáƒŖáƒ áƒ˜ áƒ áƒ”áƒĄáƒŖáƒ áƒĄáƒ˜ ვერ დაემაáƒĸა ალბომში", + "add_to_album_toggle": "გადართე მონიშვნა {album}_სთვის", "add_to_albums": "დაამაáƒĸე ალბომებში", "add_to_albums_count": "დაამაáƒĸე ალბომში ({count})", "add_to_bottom_bar": "დაამაáƒĸე ...ში", + "add_to_shared_album": "დაამაáƒĸე საზიარო ალბომში", "add_upload_to_stack": "დაამაáƒĸე აáƒĸáƒ•áƒ˜áƒ áƒ—áƒŖáƒšáƒ˜ ქáƒĸეკში", "add_url": "დაამაáƒĸე URL", "added_to_archive": "დაარáƒĨივდა", @@ -80,6 +88,7 @@ "image_format": "ფორმაáƒĸი", "image_format_description": "WebP ფორმაáƒĸი JPEG-ზე პაáƒĸარა ფაილებს აáƒŦარმოებს, მაგრამ მის დამზადებას áƒŖáƒ¤áƒ áƒ მეáƒĸი დრო სჭირდება.", "image_fullsize_enabled": "ჩართე áƒĄáƒ áƒŖáƒšáƒ˜ ზომის ფოáƒĸოების გენერაáƒĒია", + "image_fullsize_enabled_description": "დააგენერირე მთლიანი ზომის ფოáƒĸოები არა ვებ áƒ›áƒ”áƒ’áƒáƒ‘áƒ áƒŖáƒšáƒ˜ ფორმაáƒĸებისთვის. როáƒĒა", "image_fullsize_quality_description": "მთლიანი ზომის áƒĄáƒŖáƒ áƒáƒ—áƒ˜áƒĄ ჎არიქ჎ი 1-100მდეა. მეáƒĸი არიქ áƒŖáƒ™áƒ”áƒĸეთეში, მაგრამ áƒŦარმოáƒĨმნის áƒŖáƒ¤áƒ áƒ დიდ ფაილებს.", "image_fullsize_title": "áƒĄáƒ áƒŖáƒšáƒ˜ ზომის áƒ’áƒáƒ›áƒáƒĄáƒáƒŽáƒŖáƒšáƒ”áƒ‘áƒ˜áƒĄ პარამეáƒĸრები", "image_prefer_embedded_preview": "áƒŠáƒáƒ¨áƒ”áƒœáƒ”áƒ‘áƒŖáƒšáƒ˜ გადახედვის áƒŖáƒžáƒ˜áƒ áƒáƒĸესობა", @@ -134,19 +143,23 @@ "transcoding_acceleration_vaapi": "VAAPI", "transcoding_hardware_acceleration": "áƒ°áƒáƒ áƒ“áƒ•áƒ”áƒáƒ áƒŖáƒšáƒ˜ ამაჩáƒĨარებელი", "transcoding_policy": "áƒĸრანსკოდირების პოლიáƒĸიკა", - "transcoding_realtime_resolutions": "გარჩევადობები", "transcoding_threads": "ნაკადები", "transcoding_tone_mapping": "áƒĸონების ასახვა" }, "administration": "ადმინისáƒĸრაáƒĒია", "advanced": "დამაáƒĸებით", "advanced_settings_troubleshooting_title": "პრობლემების გადაáƒŦყვეáƒĸა", + "album_info_card_backup_album_excluded": "ამოáƒĻáƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", + "album_info_card_backup_album_included": "áƒŠáƒáƒĄáƒ›áƒŖáƒšáƒ˜áƒ", "albums": "ალბომები", "all": "ყველა", "allowed": "áƒ“áƒáƒ¨áƒ•áƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", + "anti_clockwise": "საათის იქრიქ ქაáƒŦინააáƒĻმდეგო", + "app_bar_signout_dialog_ok": "დიახ", "archive": "არáƒĨივი", "archived": "დაარáƒĨáƒ˜áƒ•áƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", "asset_hashing": "დაჰეშვა.â€Ļ", + "asset_list_layout_settings_group_automatically": "ავáƒĸომაáƒĸáƒŖáƒ áƒ˜", "asset_list_layout_sub_title": "განლაგება", "asset_skipped": "გამოáƒĸáƒáƒ•áƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", "asset_uploaded": "აáƒĸáƒ•áƒ˜áƒ áƒ—áƒŖáƒšáƒ˜áƒ", @@ -154,25 +167,30 @@ "assets": "ობიეáƒĨáƒĸები", "back": "áƒŖáƒ™áƒáƒœ", "backup": "მარáƒĨაფი", + "backup_all": "ყველა", "backup_controller_page_background_battery_info_ok": "დიახ", "backup_controller_page_backup": "მარáƒĨაფი", "backup_controller_page_backup_selected": "áƒáƒ áƒŠáƒ”áƒŖáƒšáƒ˜áƒ: ", "backup_controller_page_excluded": "ამოáƒĻáƒ”áƒ‘áƒŖáƒšáƒ˜áƒ: ", "backup_controller_page_remainder": "დარჩენილია", "backup_info_card_assets": "აáƒĨáƒĸივები", + "backup_manual_cancelled": "áƒ’áƒáƒŖáƒĨáƒ›áƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", + "backup_manual_success": "áƒŦარმაáƒĸება", "backward": "áƒŖáƒ™áƒáƒœ გადასვლა", "build": "აგება", + "cache_settings_duplicated_assets_clear_button": "áƒ’áƒáƒĄáƒŖáƒ¤áƒ—áƒáƒ•áƒ”áƒ‘áƒ", + "cache_settings_statistics_thumbnail": "მინიაáƒĸáƒŖáƒ áƒ”áƒ‘áƒ˜", "camera": "კამერა", "cancel": "áƒ’áƒáƒŖáƒĨმება", "canceled": "áƒ’áƒáƒŖáƒĨáƒ›áƒ”áƒ‘áƒŖáƒšáƒ˜áƒ", "canceling": "áƒŖáƒĨმდება", "cast": "áƒĸრანსლაáƒĒია", "charging": "იáƒĸენება", - "checksum": "საკონáƒĸროლო ჯამი", - "choose": "არჩევა", "city": "áƒĨალაáƒĨი", "clear": "áƒ’áƒáƒĄáƒŖáƒ¤áƒ—áƒáƒ•áƒ”áƒ‘áƒ", + "client_cert_dialog_msg_confirm": "დიახ", "client_cert_import": "შემოáƒĸანა", + "clockwise": "საათის იქრიქ áƒ›áƒ˜áƒ›áƒáƒ áƒ—áƒŖáƒšáƒ”áƒ‘áƒ˜áƒ—", "close": "áƒ“áƒáƒŽáƒŖáƒ áƒ•áƒ", "collapse": "აკეáƒĒვა", "color": "ფერი", @@ -189,7 +207,7 @@ "created": "შეáƒĨმნილია", "created_at": "შეიáƒĨმნა", "crop": "ამოჭრა", - "crop_aspect_ratio_original": "ორიგინალი", + "curated_object_page_title": "ნივთები", "dark": "áƒ›áƒŖáƒĨი", "date": "თარიáƒĻი", "day": "დáƒĻე", @@ -206,7 +224,6 @@ "download": "გადმოáƒŦერა", "download_settings": "გადმოáƒŦერა", "downloading": "მიმდინარეობს გადმოáƒŦერა", - "duplicate": "áƒ“áƒŖáƒ‘áƒšáƒ˜áƒ áƒ”áƒ‘áƒ", "duplicates": "áƒ“áƒŖáƒ‘áƒšáƒ˜áƒ™áƒáƒĸები", "duration": "ხანგრáƒĢლივობა", "edit": "჊აქáƒŦორება", @@ -218,7 +235,10 @@ "enqueued": "რიგში áƒŠáƒáƒĄáƒ›áƒŖáƒšáƒ˜áƒ", "error": "შეáƒĒდომა", "exif": "Exif", - "expand": "გამოშლა", + "exif_bottom_sheet_details": "დეáƒĸალები", + "exif_bottom_sheet_location": "მდებარეობა", + "exif_bottom_sheet_people": "ხალხი", + "experimental_settings_title": "ქაáƒĒდელი", "expired": "ვადაამოáƒŦáƒŖáƒ áƒŖáƒšáƒ˜áƒ", "explore": "დათვალიერება", "explorer": "გამáƒĒილებელი", @@ -233,7 +253,6 @@ "filename": "ფაილის სახელი", "filetype": "ფაილის áƒĸიპი", "filter": "ფილáƒĸრი", - "filters": "ფილáƒĸრები", "first": "პირველი", "folder": "ქაáƒĨაáƒĻალდე", "folders": "ქაáƒĨაáƒĻალდეები", @@ -259,13 +278,13 @@ "licenses": "ლიáƒĒენზიები", "light": "áƒĻია", "like": "მოáƒŦონება", - "link": "áƒ‘áƒ›áƒŖáƒšáƒ˜", "list": "ქია", "loading": "჊აáƒĸვირთვა", "local": "áƒšáƒáƒ™áƒáƒšáƒŖáƒ áƒ˜", "location": "მდებარეობა", "lock": "დაბლოკვა", "login": "შესვლა", + "login_form_back_button_text": "áƒŖáƒ™áƒáƒœ", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:პორáƒĸი", "login_form_password_hint": "პაროლი", @@ -274,15 +293,8 @@ "look": "შეხედვა", "make": "მáƒŦარმოებელი", "map": "áƒ áƒŖáƒ™áƒ", + "map_location_dialog_yes": "დიახ", "matches": "დამთხვევები", - "media_chrome": { - "auto": "ავáƒĸო", - "captions_off": "გამორთ", - "loop": "áƒ›áƒáƒ áƒ§áƒŖáƒŸáƒ˜", - "mute": "áƒ“áƒáƒ“áƒŖáƒ›áƒ”áƒ‘áƒ", - "second": "áƒŦამი", - "unmute": "áƒ“áƒáƒ“áƒŖáƒ›áƒ”áƒ‘áƒ˜áƒĄ áƒ’áƒáƒŖáƒĨმება" - }, "memories": "მოგონებები", "memory": "მეხსიერება", "menu": "áƒ›áƒ”áƒœáƒ˜áƒŖ", @@ -294,7 +306,6 @@ "model": "მოდელი", "month": "თვე", "more": "მეáƒĸი", - "motion": "მოáƒĢრავი", "move": "გადაáƒĸანა", "name": "სახელი", "navigate": "ნავიგაáƒĒია", @@ -329,6 +340,7 @@ "pending": "რიგშია", "people": "ხალხი", "permission": "áƒŦვდომა", + "permission_onboarding_back": "áƒŖáƒ™áƒáƒœ", "person": "პიროვნება", "photos": "ფოáƒĸოები", "place": "ადგილი", @@ -358,6 +370,7 @@ "remote": "áƒ“áƒáƒ¨áƒáƒ áƒ”áƒ‘áƒŖáƒšáƒ˜", "remove": "áƒŦაშლა", "rename": "სახელის გადარáƒĨმევა", + "repair": "შეკეთება", "repository": "რეპოზიáƒĸორია", "rescan": "თავიდან სკანირება", "reset": "ჩამოყრა", @@ -371,16 +384,23 @@ "save": "შენახვა", "saved": "áƒ¨áƒ”áƒœáƒáƒŽáƒŖáƒšáƒ˜áƒ", "scan_library": "სკანირება", - "scanning": "სკანირება", "search": "áƒĢებნა", "search_by_ocr_example": "ლაáƒĸე", "search_filter_date": "თარიáƒĻი", "search_filter_location": "მდებარეობა", + "search_page_categories": "კაáƒĸეგორიები", + "search_page_screenshots": "ეკრანის ანაბეჭდები", + "search_page_selfies": "სელფიები", + "search_page_things": "ნივთები", + "search_suggestion_list_smart_search_hint_2": "m:თáƒĨვენი-ქაáƒĢებნი-ქáƒĸრიáƒĨონი", "second": "áƒŦამი", "select": "აირჩიეთ", "selected": "áƒáƒ áƒŠáƒ”áƒŖáƒšáƒ˜áƒ", "set": "დაყენება", + "setting_image_viewer_title": "áƒ’áƒáƒ›áƒáƒĄáƒáƒŽáƒŖáƒšáƒ”áƒ‘áƒ”áƒ‘áƒ˜", "setting_languages_apply": "გადაáƒĸარება", + "setting_notifications_notify_immediately": "áƒ“áƒáƒŖáƒ§áƒáƒ•áƒœáƒ”áƒ‘áƒšáƒ˜áƒ•", + "setting_notifications_notify_never": "არასდროს", "setting_video_viewer_looping_title": "áƒŦáƒ áƒ˜áƒŖáƒšáƒáƒ“", "settings": "მორგება", "share": "გაზიარება", @@ -404,7 +424,6 @@ "success": "áƒŦარმაáƒĸება", "suggestions": "რჩევები", "support": "მხარდაჭერა", - "supporter": "მხარდამჭერი", "sync": "სინáƒĨრონიზაáƒĒია", "tag": "ჭდე", "tags": "ჭდეები", @@ -448,12 +467,11 @@ "videos": "ვიდეოები", "view": "დათვალიერება", "view_name": "ხედი", - "visibility": "ხოლვადობა", + "viewer_unstack": "áƒ’áƒáƒœáƒ¯áƒ’áƒŖáƒ¤áƒ”áƒ‘áƒ", "waiting": "მოლოდინი", "warning": "გაფრთხილება", "week": "კვირა", "welcome": "მოგესალმებით", - "width": "სიგანე", "year": "áƒŦელი", "yes": "დიახ" } diff --git a/i18n/kab.json b/i18n/kab.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/kab.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/kk.json b/i18n/kk.json index d3d8921fc2..7025ae0d22 100644 --- a/i18n/kk.json +++ b/i18n/kk.json @@ -7,6 +7,7 @@ "add_a_name": "ĐŅ‚Ņ‹ĐŊ Ō›ĐžŅŅƒ", "add_birthday": "ĐĸŅƒŌ“Đ°ĐŊ ĐēŌ¯ĐŊŅ–ĐŊ Ō›ĐžŅŅƒ", "add_location": "ĐļĐĩŅ€Đ´Ņ– Ō›ĐžŅŅƒ", + "add_more_users": "Ō›ĐžŅŅ‹ĐŧŅˆĐ° адаĐŧĐ´Đ°Ņ€Đ´Ņ‹ ҂ҖҀĐēĐĩ҃", "add_partner": "ĐļĐžĐģĐ´Đ°ŅŅ‚Ņ‹ Ō›ĐžŅŅƒ", "add_photos": "ŅŅƒŅ€Đĩ҂҂ĐĩŅ€Đ´Ņ– Ō›ĐžŅŅƒ", "add_tag": "Ņ‚ĐĩĐŗŅ‚Ņ– Ō›ĐžŅŅƒ", @@ -15,6 +16,7 @@ "add_to_album_bottom_sheet_added": "{album}'Ō“Đ° Ō›ĐžŅŅ‹ĐģŌ“Đ°ĐŊ", "add_to_album_bottom_sheet_already_exists": "ОĐŊŅŅ‹Đˇ да {album} йОĐģŌ“Đ°ĐŊ", "add_to_albums": "аĐģŅŒĐąĐžĐŧĐ´Đ°Ņ€Ō“Đ° Ō›ĐžŅŅƒ", + "add_to_shared_album": "ĐąĶŠĐģҖҁĐēĐĩĐŊ аĐģŅŒĐąĐžĐŧŌ“Đ° Ō›ĐžŅŅƒ", "add_url": "URL Ņ‚Đ°ŌŖĐ´Đ°Ņƒ", "added_to_archive": "ĐŅ€Ņ…Đ¸Đ˛ĐēĐĩ ĐļŅ–ĐąĐĩҀҖĐģĐŗĐĩĐŊ", "added_to_favorites": "Ņ‚Đ°ŌŖĐ´Đ°ŅƒĐģŅ‹ĐģĐ°Ņ€Ō“Đ° Ō›ĐžŅŅ‹ĐģŌ“Đ°ĐŊ", diff --git a/i18n/km.json b/i18n/km.json index ed7afbfdd1..f14c34095d 100644 --- a/i18n/km.json +++ b/i18n/km.json @@ -10,18 +10,24 @@ "active": "កំពážģងសកម្ម", "active_count": "ដំណើរកážļរ :{count}", "activity": "សកម្មភážļពផ្សេងៗ", + "activity_changed": "សកម្មភážļពគážē {enabled, select, true {បážļនបើក} other {បážļនបិទ}}", "add": "បន្ថែម", "add_a_description": "បន្ថែមកážļរពិពណ៌នážļ", "add_a_location": "បន្ថែមទីតážļំង", "add_a_name": "បន្ងែមឈ្មោះ", "add_a_title": "បន្ងែមចំណងជើង", "add_action": "បន្ថែមសកម្មភážļព", + "add_action_description": "ចážģចដើម្បីបន្ថែមសកម្មភážļឲ្យážĸនážģវត្ត", "add_assets": "បន្ថែមទ្រព្យ", "add_birthday": "បន្ថែមថ្ងៃខែឆ្មážļំកំណើត", "add_endpoint": "បន្ថែម Endpoint", "add_exclusion_pattern": "បន្ថែមលំនážļំលើកលែង", + "add_filter": "បន្ថែមតម្រង", + "add_filter_description": "ចážģចដើម្បីបន្ថែមតម្រងផ្សេងទៀត", "add_location": "បន្ថែមទីតážļំង", + "add_more_users": "បន្ថែមážĸ្នកប្រើប្រážļស់", "add_partner": "បន្ថែមដៃគážŧ", + "add_path": "បន្លែម Path", "add_photos": "បន្ថែមរážŧបថត", "add_tag": "បន្ថែមស្លážļក", "add_to": "បន្ថែមទៅក្នážģងâ€Ļ", @@ -29,11 +35,14 @@ "add_to_album_bottom_sheet_added": "បážļនបន្ថែមទៅក្នážģង {album}", "add_to_album_bottom_sheet_already_exists": "បន្ថែមក្នážģង {album} រážŊចទៅហើយ", "add_to_album_bottom_sheet_some_local_assets": "ទ្រព្យក្នážģងម៉ážļស៊ីនមážŊយចំនážŊនមិនážĸážļចបន្ថែមចážŧលážĸážļល់ប៊ážģមបážļនទេ", + "add_to_album_toggle": "បិទបើកកážļរជ្រើសរើសសម្រážļប់ {album}", "add_to_albums": "បន្ថែមទៅក្នážģងážĸážļល់ប៊ážģមច្រើន", "add_to_albums_count": "បន្ថែមទៅក្នážģងážĸážļល់ប៊ážģមចំនážŊន ({count})", "add_to_bottom_bar": "បន្ថែមទៅក្នážģង", + "add_to_shared_album": "បន្ថែមទៅក្នážģងážĸážļល់ប៊ážģមដែលបážļនចែករំលែក", "add_upload_to_stack": "បន្ថែមកážļរបង្ហោះទៅជង់", "add_url": "បន្ថែម URL", + "add_workflow_step": "បន្ថែមលំនážļំកážļរងážļរ", "added_to_archive": "បážļនបន្ថែមទៅក្នážģងបណ្ណសážļរ", "added_to_favorites": "បážļនបន្ថែមទៅក្នážģងចំណង់ចំណážŧលចិត្ត", "added_to_favorites_count": "បážļនបន្ថែម {count, number} ទៅក្នážģងចំណង់ចំណážŧលចិត្ត", diff --git a/i18n/kn.json b/i18n/kn.json index 97d5aab946..6fe20c6794 100644 --- a/i18n/kn.json +++ b/i18n/kn.json @@ -10,31 +10,39 @@ "active": "ā˛¸ā˛•āŗā˛°ā˛ŋ➝", "active_count": "ā˛¸ā˛•āŗā˛°ā˛ŋ➝: {count}", "activity": "➚➟⺁ā˛ĩ➟ā˛ŋ➕⺆", + "activity_changed": "➚➟⺁ā˛ĩ➟ā˛ŋ➕⺆ {enabled, select, true{ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†} other {➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†}}", "add": "➏⺇➰ā˛ŋ➏ā˛ŋ", "add_a_description": "ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_a_location": "ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_a_name": "ā˛šāŗ†ā˛¸ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_a_title": "ā˛ļāŗ€ā˛°āŗā˛ˇā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_action": "ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_action_description": "➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏➞⺁ ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏➞⺁ ā˛•āŗā˛˛ā˛ŋā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ", "add_assets": "ā˛†ā˛¸āŗā˛¤ā˛ŋ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_birthday": "ā˛œā˛¨āŗā˛Žā˛Ļā˛ŋ➍ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_endpoint": "ā˛Žā˛‚ā˛Ąāŗâ€Œā˛Ēā˛žā˛¯ā˛ŋā˛‚ā˛Ÿāŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_exclusion_pattern": "ā˛šāŗŠā˛°ā˛—ā˛ŋā˛Ąāŗā˛ĩā˛ŋ➕⺆ ā˛Žā˛žā˛Ļ➰ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_filter": "ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_filter_description": "ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ā˛¸āŗā˛Ĩā˛ŋ➤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏➞⺁ ā˛•āŗā˛˛ā˛ŋā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ", "add_location": "ā˛¸āŗā˛Ĩ➺ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_more_users": "ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_partner": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_path": "ā˛šā˛žā˛Ļā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_photos": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", - "add_step": "ā˛šā˛‚ā˛¤ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_tag": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_to": "➏⺇➰ā˛ŋ➏ā˛ŋâ€Ļ", "add_to_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_to_album_bottom_sheet_added": "{album} ➗⺆ ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "add_to_album_bottom_sheet_already_exists": "ā˛ˆā˛—ā˛žā˛—ā˛˛āŗ‡ {album} ā˛¨ā˛˛āŗā˛˛ā˛ŋā˛Ļāŗ†", "add_to_album_bottom_sheet_some_local_assets": "➕⺆➞ā˛ĩ⺁ ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—ā˛˛ā˛ŋā˛˛āŗā˛˛", + "add_to_album_toggle": "{album}ā˛—ā˛žā˛—ā˛ŋ ā˛†ā˛¯āŗā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ÿā˛žā˛—ā˛˛āŗ ā˛Žā˛žā˛Ąā˛ŋ", "add_to_albums": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_to_albums_count": "({count}) ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_to_bottom_bar": "➗⺆ ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_to_shared_album": "ā˛šā˛‚ā˛šā˛ŋā˛Ļ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_upload_to_stack": "ā˛¸āŗā˛Ÿāŗā˛¯ā˛žā˛•āŗâ€Œā˛—āŗ† ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "add_url": "URL ➏⺇➰ā˛ŋ➏ā˛ŋ", + "add_workflow_step": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩā˛ŋ➍ ā˛šā˛‚ā˛¤ā˛ĩā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "added_to_archive": "ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "added_to_favorites": "ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛ĩ⺁➗➺ā˛ŋ➗⺆ ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "added_to_favorites_count": "{count, number} ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛ĩ⺁➗➺ā˛ŋ➗⺆ ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -72,7 +80,6 @@ "cron_expression_description": "ā˛•āŗā˛°ā˛žā˛¨āŗ ā˛¸āŗā˛ĩ➰⺂ā˛Ēā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋā˛•āŗŠā˛‚ā˛Ąāŗ ā˛¸āŗā˛•āŗā˛¯ā˛žā˛¨ā˛ŋā˛‚ā˛—āŗ ā˛Žā˛§āŗā˛¯ā˛‚ā˛¤ā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ. ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➉ā˛Ļā˛ž. Crontab Guru ā˛…ā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛ŋ", "cron_expression_presets": "ā˛•āŗā˛°ā˛žā˛¨āŗ ➅➭ā˛ŋā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ ā˛Ēāŗ‚ā˛°āŗā˛ĩ➍ā˛ŋ➗ā˛Ļā˛ŋ➗➺⺁", "disable_login": "ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "download_csv": "➏ā˛ŋā˛Žā˛¸āŗā˛ĩā˛ŋ ā˛ĄāŗŒā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋ", "duplicate_detection_job_description": "➒➂ā˛Ļāŗ‡ ➰⺀➤ā˛ŋ➝ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēā˛¤āŗā˛¤āŗ†ā˛šā˛šāŗā˛šā˛˛āŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗ ā˛Žāŗ‡ā˛˛āŗ† ā˛¯ā˛‚ā˛¤āŗā˛° ➕➞ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛°ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ. ā˛¸āŗā˛Žā˛žā˛°āŗā˛Ÿāŗ ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿā˛ĩā˛¨āŗā˛¨āŗ ➅ā˛ĩ➞➂ā˛Ŧā˛ŋ➏ā˛ŋā˛Ļāŗ†", "exclusion_pattern_description": "➍ā˛ŋā˛Žāŗā˛Ž ➞⺈ā˛Ŧāŗā˛°ā˛°ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛•āŗā˛¯ā˛žā˛¨āŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛˛ā˛•āŗā˛ˇā˛ŋ➏➞⺁ ā˛Žā˛•āŗā˛¸āŗâ€Œā˛•āŗā˛˛āŗ‚ā˛ˇā˛¨āŗ ā˛Ēāŗā˛¯ā˛žā˛Ÿā˛°āŗā˛¨āŗâ€Œā˛—ā˛ŗāŗ ➍ā˛ŋā˛Žā˛—āŗ† ➅ā˛ĩā˛•ā˛žā˛ļ ā˛¨āŗ€ā˛Ąāŗā˛¤āŗā˛¤ā˛ĩāŗ†. ➍⺀ā˛ĩ⺁ ā˛†ā˛Žā˛Ļ⺁ ā˛Žā˛žā˛Ąā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛Ŧ➝➏ā˛Ļ ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰⺁ā˛ĩ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛Ļāŗā˛Ļ➰⺆, ➉ā˛Ļā˛žā˛šā˛°ā˛Ŗāŗ†ā˛—āŗ† RAW ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗāŗ.", "export_config_as_json_description": "ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ➏ā˛ŋā˛¸āŗā˛Ÿā˛Žāŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋ➗➰⺇ā˛ļā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ JSON ā˛Ģāŗˆā˛˛āŗ ➆➗ā˛ŋ ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋ", @@ -107,20 +114,6 @@ "image_thumbnail_quality_description": "ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗ ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿ 1 ➰ā˛ŋ➂ā˛Ļ 100 ➰ā˛ĩ➰⺆➗⺆. ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛Ļ⺁ ā˛‰ā˛¤āŗā˛¤ā˛Ž, ➆ā˛Ļ➰⺆ ā˛ĻāŗŠā˛Ąāŗā˛Ą ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‰ā˛¤āŗā˛Ēā˛žā˛Ļā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛Ēāŗā˛°ā˛¤ā˛ŋā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛•ā˛Ąā˛ŋā˛Žāŗ† ā˛Žā˛žā˛Ąāŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "image_thumbnail_title": "ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗ", "import_config_from_json_description": "JSON ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋ➗➰⺇ā˛ļā˛¨āŗ ā˛Ģāŗˆā˛˛āŗ ā˛…ā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąāŗā˛ĩ ā˛Žāŗ‚ā˛˛ā˛• ➏ā˛ŋā˛¸āŗā˛Ÿā˛Žāŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋ➗➰⺇ā˛ļā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛†ā˛Žā˛Ļ⺁ ā˛Žā˛žā˛Ąā˛ŋ", - "integrity_checks_checksum_files": "ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛Ģāŗˆā˛˛āŗ ➗➺⺁", - "integrity_checks_checksum_files_description": "ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛šāŗ†ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ", - "integrity_checks_checksum_files_enable_description": "ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛šāŗ†ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "integrity_checks_checksum_files_percentage_limit": "ā˛ļāŗ‡ā˛•ā˛Ąā˛žā˛ĩā˛žā˛°āŗ ā˛Žā˛ŋ➤ā˛ŋ", - "integrity_checks_checksum_files_percentage_limit_description": "ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛Žā˛§āŗā˛¯ā˛‚ā˛¤ā˛°ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛šāŗ†ā˛•āŗ ā˛Žā˛ˇāŗā˛Ÿāŗ ā˛šā˛˛ā˛žā˛¯ā˛ŋ➏ā˛Ŧ⺇➕⺁ ā˛Žā˛‚ā˛Ŧ⺁ā˛Ļā˛•āŗā˛•āŗ† 0.01 ā˛Žā˛¤āŗā˛¤āŗ 1 ➰ ā˛¨ā˛Ąāŗā˛ĩā˛ŋ➍ ➗➰ā˛ŋā˛ˇāŗā˛  ā˛ļāŗ‡ā˛•ā˛Ąā˛žā˛ĩā˛žā˛°āŗ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛ĩā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ.", - "integrity_checks_checksum_files_time_limit": "ā˛¸ā˛Žā˛¯ ā˛Žā˛ŋ➤ā˛ŋ", - "integrity_checks_checksum_files_time_limit_description": "ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛šāŗ†ā˛•āŗ ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛Žā˛§āŗā˛¯ā˛‚ā˛¤ā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛šā˛˛ā˛žā˛¯ā˛ŋ➏ā˛Ŧāŗ‡ā˛•ā˛žā˛Ļ ➗➰ā˛ŋā˛ˇāŗā˛  ➅ā˛ĩ➧ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ. (ā˛Žāŗ€)", - "integrity_checks_missing_files_description": "➆ā˛ĩā˛°āŗā˛¤ā˛¨ā˛ĩā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛Ģāŗˆā˛˛āŗ ➗➺ ā˛šāŗ†ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "integrity_checks_missing_files_enable_description": "ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛Ģāŗˆā˛˛āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "integrity_checks_settings": "ā˛¸ā˛Žā˛—āŗā˛°ā˛¤āŗ† ā˛Ē➰ā˛ŋā˛ļ⺀➞➍⺆➗➺⺁", - "integrity_checks_settings_description": "ā˛¸ā˛Žā˛—āŗā˛°ā˛¤āŗ†ā˛¯ ā˛šāŗ†ā˛•āŗ ā˛Žā˛§āŗā˛¯ā˛‚ā˛¤ā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", - "integrity_checks_untracked_files": "ā˛…ā˛¨āŗā˛°āŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛Ļ ā˛Ģāŗˆā˛˛āŗ ➗➺⺁", - "integrity_checks_untracked_files_description": "➆ā˛ĩā˛°āŗā˛¤ā˛¨ā˛ĩā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛Ļ ā˛Ģāŗˆā˛˛āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗ†ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "integrity_checks_untracked_files_enable_description": "ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛Ļ ā˛Ģāŗˆā˛˛āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "job_concurrency": "{job} ā˛¸ā˛šā˛ĩā˛°āŗā˛¤ā˛ŋā˛¤āŗā˛ĩ", "job_created": "➕⺆➞➏ā˛ĩā˛¨āŗā˛¨āŗ ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "job_not_concurrency_safe": "➈ ➕⺆➞➏ā˛ĩ⺁ ā˛¸ā˛šā˛ĩā˛°āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛•āŗā˛•āŗ† ā˛¸āŗā˛°ā˛•āŗā˛ˇā˛ŋ➤ā˛ĩā˛˛āŗā˛˛.", @@ -195,21 +188,9 @@ "machine_learning_smart_search_enabled": "ā˛¸āŗā˛Žā˛žā˛°āŗā˛Ÿāŗ ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿā˛ĩā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "machine_learning_smart_search_enabled_description": "➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋā˛Ļ➰⺆, ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Žā˛žā˛°āŗā˛Ÿāŗ ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿā˛•āŗā˛•ā˛žā˛—ā˛ŋ ā˛Žā˛¨āŗâ€Œā˛•āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", "machine_learning_url_description": "ā˛¯ā˛‚ā˛¤āŗā˛° ➕➞ā˛ŋ➕⺆ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ URL. ➒➂ā˛Ļā˛•āŗā˛•ā˛ŋ➂➤ ā˛šāŗ†ā˛šāŗā˛šāŗ URL ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➒ā˛Ļ➗ā˛ŋ➏ā˛ŋā˛Ļ➰⺆, ā˛ŽāŗŠā˛Ļ➞ā˛ŋ➍ā˛ŋ➂ā˛Ļ ā˛•āŗŠā˛¨āŗ†ā˛¯ā˛ĩ➰⺆➗⺆, ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛¸ā˛°āŗā˛ĩā˛°āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Ēāŗā˛°ā˛¤ā˛ŋā˛•āŗā˛°ā˛ŋ➝ā˛ŋ➏⺁ā˛ĩā˛ĩ➰⺆➗⺆ ➒➂ā˛ĻāŗŠā˛‚ā˛Ļā˛žā˛—ā˛ŋ ā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛Ēāŗā˛°ā˛¤ā˛ŋā˛•āŗā˛°ā˛ŋ➝ā˛ŋ➏ā˛Ļ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅ā˛ĩ➰⺁ ā˛†ā˛¨āŗâ€Œā˛˛āŗˆā˛¨āŗâ€Œā˛—āŗ† ā˛šā˛ŋ➂➤ā˛ŋ➰⺁➗⺁ā˛ĩā˛ĩ➰⺆➗⺆ ā˛¤ā˛žā˛¤āŗā˛•ā˛žā˛˛ā˛ŋ➕ā˛ĩā˛žā˛—ā˛ŋ ➍ā˛ŋā˛°āŗā˛˛ā˛•āŗā˛ˇā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", - "maintenance_backup_management": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛Ŗāŗ†", "maintenance_delete_backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➅➺ā˛ŋ➏ā˛ŋ", "maintenance_delete_backup_description": "➈ ā˛Ģāŗˆā˛˛āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "maintenance_delete_error": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➅➺ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†.", - "maintenance_integrity_check": "ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", - "maintenance_integrity_check_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ‚ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", - "maintenance_integrity_checksum_mismatch": "ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛Žā˛ŋā˛¸āŗā˛Žāŗā˛¯ā˛žā˛šāŗ", - "maintenance_integrity_checksum_mismatch_description": "ā˛†ā˛¨āŗ-ā˛Ąā˛ŋā˛¸āŗā˛•āŗ ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ➤ā˛Ēāŗā˛Ēā˛žā˛—ā˛ŋ ā˛šāŗ‡ā˛ŗāŗā˛ĩ ā˛Ģāŗˆā˛˛āŗ ➗➺⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➅ā˛Ļ➰ ā˛Ąāŗ‡ā˛Ÿā˛žā˛Ŧāŗ‡ā˛¸āŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ŋ➏ā˛ŋā˛Ļāŗ†.", - "maintenance_integrity_checksum_mismatch_job": "ā˛šāŗ†ā˛•ā˛Žāŗ ā˛Žā˛ŋā˛¸āŗā˛Žāŗā˛¯ā˛žā˛šāŗ ➗➺ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", - "maintenance_integrity_checksum_mismatch_refresh_job": "➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛šāŗ†ā˛•āŗā˛¸ā˛Žāŗ ā˛Žā˛ŋā˛¸āŗā˛Žāŗā˛¯ā˛žā˛šāŗ ā˛ĩ➰ā˛Ļā˛ŋ➗➺⺁", - "maintenance_integrity_missing_file_description": "➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛Ģāŗˆā˛˛āŗ ā˛Žā˛žā˛Ąāŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➅ā˛Ļ➰ ā˛Ąāŗ‡ā˛Ÿā˛žā˛Ŧāŗ‡ā˛¸āŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ† ➆ā˛Ļ➰⺆ ā˛Ģāŗˆā˛˛āŗ ➏ā˛ŋā˛¸āŗā˛Ÿā˛Žāŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋā˛˛āŗā˛˛.", - "maintenance_integrity_missing_file_job": "ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛Ģāŗˆā˛˛āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", - "maintenance_integrity_missing_file_refresh_job": "ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛Ģāŗˆā˛˛āŗ ā˛ĩ➰ā˛Ļā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛Žā˛žā˛Ąā˛ŋ", - "maintenance_integrity_report": "ā˛¸ā˛Žā˛—āŗā˛°ā˛¤āŗ† ā˛ĩ➰ā˛Ļā˛ŋ", - "maintenance_integrity_untracked_file_description": "ā˛Ģāŗˆā˛˛āŗ ➗➺⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗā˛…ā˛Ļ➰ ā˛Ąāŗˆā˛°āŗ†ā˛•āŗā˛Ÿā˛°ā˛ŋ➗➺⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ļā˛žā˛–ā˛˛āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛˛āŗā˛˛.", "maintenance_restore_backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏ā˛ŋ", "maintenance_restore_backup_description": "ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛…ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏ā˛ŋā˛šā˛žā˛•ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛Žāŗā˛‚ā˛Ļ⺁ā˛ĩ➰ā˛ŋ➝⺁ā˛ĩ ā˛ŽāŗŠā˛Ļ➞⺁ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "maintenance_restore_backup_different_version": "➈ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛¨ ā˛ĩā˛ŋ➭ā˛ŋā˛¨āŗā˛¨ ➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋā˛¯āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†!", @@ -284,8 +265,6 @@ "notification_enable_email_notifications": "ā˛‡ā˛Žāŗ‡ā˛˛āŗ ➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "notification_settings": "➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ† ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗ", "notification_settings_description": "ā˛‡ā˛Žāŗ‡ā˛˛āŗ ➏⺇➰ā˛ŋā˛Ļ➂➤⺆ ➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ† ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", - "oauth_allow_insecure_requests": "ā˛…ā˛¸āŗā˛°ā˛•āŗā˛ˇā˛ŋ➤ ā˛ĩā˛ŋ➍➂➤ā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ", - "oauth_allow_insecure_requests_description": "ā˛Žā˛šāŗā˛šā˛°ā˛ŋ➕⺆: ➇ā˛Ļ⺁ ➟ā˛ŋā˛Žā˛˛āŗā˛Žā˛¸āŗ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛° ā˛ŽāŗŒā˛˛āŗā˛¯ā˛Žā˛žā˛Ē➍ā˛ĩā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ† OAuth ā˛ĩā˛ŋ➍➂➤ā˛ŋ➗➺⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛Žā˛‚ā˛ā˛Ÿā˛ŋā˛Žā˛‚ ā˛Ļā˛žā˛ŗā˛ŋ➗⺆ ➍ā˛ŋā˛Žāŗā˛Žā˛¨āŗā˛¨āŗ ā˛Ŧā˛šā˛ŋ➰➂➗ā˛Ēā˛Ąā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁.", "oauth_auto_launch": "ā˛¸āŗā˛ĩ➝➂ ā˛‰ā˛Ąā˛žā˛ĩ➪⺆", "oauth_auto_launch_description": "ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ā˛Ēāŗā˛Ÿā˛•āŗā˛•āŗ† ā˛¨āŗā˛¯ā˛žā˛ĩā˛ŋā˛—āŗ‡ā˛Ÿāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ➍➂➤➰ OAuth ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ā˛šā˛°ā˛ŋā˛ĩā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏ā˛ŋ", "oauth_auto_register": "ā˛¸āŗā˛ĩ➝➂ ➍⺋➂ā˛Ļ➪ā˛ŋ", @@ -296,7 +275,6 @@ "oauth_mobile_redirect_uri": "ā˛ŽāŗŠā˛Ŧāŗˆā˛˛āŗ ā˛Žā˛°āŗā˛¨ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļ➍ URI", "oauth_mobile_redirect_uri_override": "ā˛ŽāŗŠā˛Ŧāŗˆā˛˛āŗ ā˛Žā˛°āŗā˛¨ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļ➍ URI ➅➤ā˛ŋā˛•āŗā˛°ā˛Žā˛Ŗ", "oauth_mobile_redirect_uri_override_description": "''{callback}'' ā˛¨ā˛‚ā˛¤ā˛š ā˛ŽāŗŠā˛Ŧāŗˆā˛˛āŗ URI ā˛…ā˛¨āŗā˛¨āŗ OAuth ā˛Ēāŗ‚ā˛°āŗˆā˛•āŗ†ā˛Ļā˛žā˛°ā˛°āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛Ļā˛ŋā˛Ļāŗā˛Ļā˛žā˛— ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "oauth_prompt_description": "ā˛Ēāŗā˛°ā˛žā˛‚ā˛Ēāŗā˛Ÿāŗ ā˛Ēāŗā˛¯ā˛žā˛°ā˛žā˛Žāŗ€ā˛Ÿā˛°āŗ (➉ā˛Ļā˛ž. ā˛¸āŗ†ā˛˛āŗ†ā˛•āŗā˛Ÿāŗ_ā˛•āŗŒā˛‚ā˛Ÿāŗ, ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ, ➒ā˛Ēāŗā˛Ēā˛ŋ➗⺆)", "oauth_role_claim": "ā˛Ēā˛žā˛¤āŗā˛°ā˛Ļ ā˛šā˛•āŗā˛•āŗ", "oauth_role_claim_description": "➈ ā˛šā˛•āŗā˛•ā˛ŋ➍ ➉ā˛Ēā˛¸āŗā˛Ĩā˛ŋ➤ā˛ŋ➝ ā˛†ā˛§ā˛žā˛°ā˛Ļ ā˛Žāŗ‡ā˛˛āŗ† ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛• ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ĩā˛¨āŗā˛¨āŗ ā˛¨āŗ€ā˛Ąā˛ŋ. ā˛šā˛•āŗā˛•ā˛ŋā˛¨ā˛˛āŗā˛˛ā˛ŋ 'ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°' ➅ā˛Ĩā˛ĩā˛ž '➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛•' ➇➰ā˛Ŧā˛šāŗā˛Ļ⺁.", "oauth_settings": "OAuth", @@ -323,8 +301,6 @@ "refreshing_all_libraries": "ā˛Žā˛˛āŗā˛˛ā˛ž ā˛—āŗā˛°ā˛‚ā˛Ĩā˛žā˛˛ā˛¯ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "registration": "➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛• ➍⺋➂ā˛Ļ➪ā˛ŋ", "registration_description": "➍⺀ā˛ĩ⺁ ➏ā˛ŋā˛¸āŗā˛Ÿā˛‚ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛ŽāŗŠā˛Ļ➞ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛žā˛—ā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ, ➍ā˛ŋā˛Žāŗā˛Žā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛•ā˛°ā˛žā˛—ā˛ŋ ➍ā˛ŋā˛¯āŗ‹ā˛œā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛†ā˛Ąā˛ŗā˛ŋā˛¤ā˛žā˛¤āŗā˛Žā˛• ā˛•ā˛žā˛°āŗā˛¯ā˛—ā˛ŗā˛ŋ➗⺆ ➜ā˛ĩā˛žā˛Ŧāŗā˛Ļā˛žā˛°ā˛°ā˛žā˛—ā˛ŋā˛°āŗā˛¤āŗā˛¤āŗ€ā˛°ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛šāŗ†ā˛šāŗā˛šāŗā˛ĩ➰ā˛ŋ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➍⺀ā˛ĩ⺁ ➰➚ā˛ŋā˛¸āŗā˛¤āŗā˛¤āŗ€ā˛°ā˛ŋ.", - "release_channel_release_candidate": "ā˛Ŧā˛ŋā˛Ąāŗā˛—ā˛Ąāŗ† ā˛…ā˛­āŗā˛¯ā˛°āŗā˛Ĩā˛ŋ", - "release_channel_stable": "ā˛¸āŗā˛Ĩā˛ŋ➰", "remove_failed_jobs": "ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛Ļ ā˛•āŗ†ā˛˛ā˛¸ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", "require_password_change_on_login": "ā˛ŽāŗŠā˛Ļ➞ ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛Ŧāŗ‡ā˛•ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "reset_settings_to_default": "ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ąāŗ€ā˛Ģā˛žā˛˛āŗā˛Ÿāŗâ€Œā˛—āŗ† ā˛Žā˛°āŗā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", @@ -419,12 +395,6 @@ "transcoding_preferred_hardware_device_description": "VAAPI ā˛Žā˛¤āŗā˛¤āŗ QSV ➗⺆ ā˛Žā˛žā˛¤āŗā˛° ā˛…ā˛¨āŗā˛ĩ➝ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛šā˛žā˛°āŗā˛Ąāŗâ€Œā˛ĩāŗ‡ā˛°āŗ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗâ€Œā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛žā˛—ā˛ŋ ā˛Ŧ➺➏⺁ā˛ĩ dri ā˛¨āŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "transcoding_preset_preset": "ā˛Ēāŗ‚ā˛°āŗā˛ĩ➍ā˛ŋ➗ā˛Ļā˛ŋ (-ā˛Ēāŗ‚ā˛°āŗā˛ĩ➍ā˛ŋ➗ā˛Ļā˛ŋ)", "transcoding_preset_preset_description": "ā˛¸ā˛‚ā˛•āŗ‹ā˛šā˛¨ ā˛ĩ⺇➗. ➍ā˛ŋā˛§ā˛žā˛¨ā˛ĩā˛žā˛Ļ ā˛Ēāŗ‚ā˛°āŗā˛ĩ➍ā˛ŋ➗ā˛Ļā˛ŋ➗➺⺁ ➚ā˛ŋā˛•āŗā˛• ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‰ā˛¤āŗā˛Ēā˛žā˛Ļā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛ĩāŗ† ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛°āŗā˛Ļā˛ŋā˛ˇāŗā˛Ÿ ā˛Ŧā˛ŋā˛Ÿāŗā˛°āŗ‡ā˛Ÿāŗ ā˛…ā˛¨āŗā˛¨āŗ ➗⺁➰ā˛ŋā˛¯ā˛žā˛—ā˛ŋ➏ā˛ŋā˛•āŗŠā˛‚ā˛Ąā˛žā˛— ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗ†ā˛šāŗā˛šā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛ĩāŗ†. VP9 'ā˛ĩ⺇➗' ā˛•āŗā˛•ā˛ŋ➂➤ ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛ĩ⺇➗ā˛ĩā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛˛ā˛•āŗā˛ˇā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", - "transcoding_realtime_description": "ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ÿāŗā˛°āŗ€ā˛Žāŗ ā˛†ā˛—āŗā˛¤āŗā˛¤ā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ➍⺈➜ ā˛¸ā˛Žā˛¯ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏➞⺁ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿā˛Ļ ā˛¸āŗā˛ĩā˛ŋ➚ā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†, ➆ā˛Ļ➰⺆ ā˛¸ā˛°āŗā˛ĩā˛°āŗ ā˛¸ā˛žā˛Žā˛°āŗā˛Ĩāŗā˛¯ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅ā˛ĩ➞➂ā˛Ŧā˛ŋ➏ā˛ŋ ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ➏⺁ā˛Ēāŗā˛¤ā˛¤āŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛¸āŗā˛Ÿā˛Ÿā˛°ā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛‰ā˛‚ā˛Ÿāŗā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁.", - "transcoding_realtime_enabled": "➍⺈➜-ā˛¸ā˛Žā˛¯ā˛Ļ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", - "transcoding_realtime_resolutions": "➍ā˛ŋā˛°āŗā˛Ŗā˛¯ā˛—ā˛ŗāŗ", - "transcoding_realtime_resolutions_description": "➍⺈➜-ā˛¸ā˛Žā˛¯ā˛Ļ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛—ā˛žā˛—ā˛ŋ ā˛¨āŗ€ā˛Ąā˛˛ā˛žā˛Ļ ➍ā˛ŋā˛°āŗā˛Ŗā˛¯ā˛—ā˛ŗāŗ. ā˛¸ā˛°āŗā˛ĩā˛°āŗ ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¤āŗā˛ĩ➰ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—ā˛Ļā˛ŋā˛Ļāŗā˛Ļ➰⺆ ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ➍ā˛ŋā˛°āŗā˛Ŗā˛¯ā˛—ā˛ŗāŗ ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ā˛¸ā˛Žā˛¸āŗā˛¯āŗ†ā˛—ā˛ŗā˛ŋ➗⺆ ā˛•ā˛žā˛°ā˛Ŗā˛ĩā˛žā˛—ā˛Ŧā˛šāŗā˛Ļ⺁.", - "transcoding_realtime_video_codecs": "ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛•āŗ‹ā˛Ąāŗ†ā˛•āŗ ➗➺⺁", - "transcoding_realtime_video_codecs_description": "➍⺈➜-ā˛¸ā˛Žā˛¯ā˛Ļ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛—ā˛žā˛—ā˛ŋ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛•āŗ‹ā˛Ąāŗ†ā˛•āŗ ➗➺⺁ ā˛¨āŗ€ā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ā˛¸ā˛Žā˛¯ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛—āŗā˛°ā˛žā˛šā˛•ā˛°āŗ ā˛¤ā˛žā˛ĩ⺁ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏⺁ā˛ĩ ā˛…ā˛¤āŗā˛¯āŗā˛¤āŗā˛¤ā˛Ž ā˛†ā˛¯āŗā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąāŗā˛¤āŗā˛¤ā˛žā˛°āŗ†. AV1 HEVC ➗ā˛ŋ➂➤ ā˛šāŗ†ā˛šāŗā˛šāŗ ā˛Ē➰ā˛ŋā˛Ŗā˛žā˛Žā˛•ā˛žā˛°ā˛ŋā˛¯ā˛žā˛—ā˛ŋā˛Ļāŗ†, ➇ā˛Ļ⺁ H.264 ➗ā˛ŋ➂➤ ā˛šāŗ†ā˛šāŗā˛šāŗ ā˛Ē➰ā˛ŋā˛Ŗā˛žā˛Žā˛•ā˛žā˛°ā˛ŋā˛¯ā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛šā˛žā˛°āŗā˛Ąāŗ ā˛ĩāŗ‡ā˛°āŗ ā˛ĩ⺇➗ā˛ĩā˛°āŗā˛§ā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏⺁ā˛ĩā˛žā˛—, ā˛ĩ⺇➗ā˛ĩā˛°āŗā˛§ā˛•ā˛ĩ⺁ ā˛Žā˛¨āŗā˛•āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļā˛žā˛Ļ ā˛•āŗ‹ā˛Ąāŗ†ā˛•āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ➆➰ā˛ŋ➏ā˛ŋ. ā˛¸ā˛žā˛Ģāŗā˛Ÿāŗ ā˛ĩāŗ‡ā˛°āŗ ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗ ā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛Ŧ➺➏⺁ā˛ĩā˛žā˛—, H.264 AV1 ➗ā˛ŋ➂➤ ā˛ĩ⺇➗ā˛ĩā˛žā˛—ā˛ŋā˛°āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛‚ā˛Ŧ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛—ā˛Žā˛¨ā˛ŋ➏ā˛ŋ, ➅ā˛Ļ⺁ HVC ➗ā˛ŋ➂➤ ā˛ĩ⺇➗ā˛ĩā˛žā˛—ā˛ŋā˛°āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "transcoding_reference_frames": "ā˛‰ā˛˛āŗā˛˛āŗ‡ā˛– ā˛šāŗŒā˛•ā˛Ÿāŗā˛Ÿāŗā˛—ā˛ŗāŗ", "transcoding_reference_frames_description": "➍ā˛ŋā˛°āŗā˛Ļā˛ŋā˛ˇāŗā˛Ÿ ā˛Ģāŗā˛°āŗ‡ā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛•āŗā˛—āŗā˛—ā˛ŋ➏⺁ā˛ĩā˛žā˛— ā˛‰ā˛˛āŗā˛˛āŗ‡ā˛–ā˛ŋ➏ā˛Ŧāŗ‡ā˛•ā˛žā˛Ļ ā˛Ģāŗā˛°āŗ‡ā˛Žāŗâ€Œā˛—ā˛ŗ ā˛¸ā˛‚ā˛–āŗā˛¯āŗ†. ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛ŽāŗŒā˛˛āŗā˛¯ā˛—ā˛ŗāŗ ā˛¸ā˛‚ā˛•āŗā˛šā˛ŋ➤ ā˛Ļā˛•āŗā˛ˇā˛¤āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛§ā˛žā˛°ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†, ➆ā˛Ļ➰⺆ ā˛Žā˛¨āŗâ€Œā˛•āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛§ā˛žā˛¨ā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. 0 ➈ ā˛ŽāŗŒā˛˛āŗā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "transcoding_required_description": "ā˛¸āŗā˛ĩ⺀➕➰ā˛ŋ➏ā˛ŋā˛Ļ ā˛¸āŗā˛ĩ➰⺂ā˛Ēā˛Ļā˛˛āŗā˛˛ā˛ŋā˛˛āŗā˛˛ā˛Ļ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ā˛Žā˛žā˛¤āŗā˛°", @@ -468,8 +438,6 @@ "user_settings_description": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛° ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", "user_successfully_removed": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛° {email} ➅ā˛ĩā˛°ā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†.", "users_page_description": "➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛• ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛° ā˛Ē⺁➟", - "version_check_channel": "ā˛šā˛žā˛¨ā˛˛āŗ ā˛Ŧā˛ŋā˛Ąāŗā˛—ā˛Ąāŗ†", - "version_check_channel_description": "➍⺀ā˛ĩ⺁ ➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋ ā˛Ēāŗā˛°ā˛•ā˛Ÿā˛Ŗāŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛Ŧ➝➏⺁ā˛ĩ ā˛Ŧā˛ŋā˛Ąāŗā˛—ā˛Ąāŗ† ā˛šā˛žā˛¨ā˛˛āŗ ā˛…ā˛¨āŗā˛¨āŗ ➆➰ā˛ŋ➏ā˛ŋ", "version_check_enabled_description": "➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋ ā˛Ē➰ā˛ŋā˛ļāŗ€ā˛˛ā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "version_check_implications": "➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋ ā˛Ē➰ā˛ŋā˛ļ⺀➞➍⺆ ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛ĩ⺁ {server} ā˛¨āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➆ā˛ĩā˛°āŗā˛¤ā˛• ➏➂ā˛ĩā˛šā˛¨ā˛ĩā˛¨āŗā˛¨āŗ ➅ā˛ĩ➞➂ā˛Ŧā˛ŋ➏ā˛ŋā˛Ļāŗ†", "version_check_settings": "➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋ ā˛Ē➰ā˛ŋā˛ļ⺀➞➍⺆", @@ -484,6 +452,8 @@ "advanced_settings_clear_image_cache": "ā˛‡ā˛Žāŗ‡ā˛œāŗ ā˛•āŗā˛¯ā˛žā˛ļāŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "advanced_settings_clear_image_cache_error": "➚ā˛ŋā˛¤āŗā˛°ā˛Ļ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", "advanced_settings_clear_image_cache_success": "{size} ā˛…ā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "advanced_settings_enable_alternate_media_filter_subtitle": "ā˛Ēā˛°āŗā˛¯ā˛žā˛¯ ā˛Žā˛žā˛¨ā˛Ļā˛‚ā˛Ąā˛—ā˛ŗ ā˛†ā˛§ā˛žā˛°ā˛Ļ ā˛Žāŗ‡ā˛˛āŗ† ➏ā˛ŋā˛‚ā˛•āŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛Žā˛žā˛§āŗā˛¯ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➈ ā˛†ā˛¯āŗā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋ. ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēā˛¤āŗā˛¤āŗ†ā˛šā˛šāŗā˛šāŗā˛ĩā˛˛āŗā˛˛ā˛ŋ ➍ā˛ŋā˛Žā˛—āŗ† ā˛¸ā˛Žā˛¸āŗā˛¯āŗ†ā˛—ā˛ŗā˛ŋā˛Ļāŗā˛Ļ➰⺆ ā˛Žā˛žā˛¤āŗā˛° ➇ā˛Ļā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋ➏ā˛ŋ.", + "advanced_settings_enable_alternate_media_filter_title": "[ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕] ā˛Ēā˛°āŗā˛¯ā˛žā˛¯ ā˛¸ā˛žā˛§ā˛¨ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➏ā˛ŋā˛‚ā˛•āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ā˛Ŧ➺➏ā˛ŋ", "advanced_settings_log_level_title": "ā˛˛ā˛žā˛—āŗ ā˛Žā˛Ÿāŗā˛Ÿ: {level}", "advanced_settings_prefer_remote_subtitle": "➕⺆➞ā˛ĩ⺁ ā˛¸ā˛žā˛§ā˛¨ā˛—ā˛ŗāŗ ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛ŋ➂ā˛Ļ ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➤⺁➂ā˛Ŧā˛ž ➍ā˛ŋā˛§ā˛žā˛¨ā˛ĩā˛žā˛—ā˛ŋā˛ĩāŗ†. ā˛Ŧā˛Ļ➞ā˛ŋ➗⺆ ➰ā˛ŋā˛Žāŗ‹ā˛Ÿāŗ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➈ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ.", "advanced_settings_prefer_remote_title": "ā˛Ļāŗ‚ā˛°ā˛¸āŗā˛Ĩ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛ŋ➗⺆ ➆ā˛Ļāŗā˛¯ā˛¤āŗ† ā˛¨āŗ€ā˛Ąā˛ŋ", @@ -491,6 +461,8 @@ "advanced_settings_proxy_headers_title": "ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ ā˛Ēāŗā˛°ā˛žā˛•āŗā˛¸ā˛ŋ ā˛šāŗ†ā˛Ąā˛°āŗâ€Œā˛—ā˛ŗāŗ [ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕]", "advanced_settings_readonly_mode_subtitle": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļā˛žā˛Ļ ➓ā˛Ļ➞⺁-ā˛Žā˛žā˛¤āŗā˛° ā˛Žāŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†, ā˛Ŧā˛šāŗ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąāŗā˛ĩ⺁ā˛Ļ⺁, ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛ĩ⺁ā˛Ļ⺁, ā˛Ŧā˛ŋā˛¤āŗā˛¤ā˛°ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ⺁, ➅➺ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ⺁ ā˛Žāŗā˛‚ā˛¤ā˛žā˛Ļ ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ‚ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛Žāŗā˛–āŗā˛¯ ā˛Ē➰ā˛Ļ⺆➝ā˛ŋ➂ā˛Ļ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛° ➅ā˛ĩā˛¤ā˛žā˛°āŗ ā˛Žāŗ‚ā˛˛ā˛• ➓ā˛Ļ➞⺁-ā˛Žā˛žā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ/➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "advanced_settings_readonly_mode_title": "➓ā˛Ļ➞⺁-ā˛Žā˛žā˛¤āŗā˛° ā˛Žāŗ‹ā˛Ąāŗ", + "advanced_settings_self_signed_ssl_subtitle": "ā˛¸ā˛°āŗā˛ĩā˛°āŗ ā˛Žā˛‚ā˛Ąāŗâ€Œā˛Ēā˛žā˛¯ā˛ŋā˛‚ā˛Ÿāŗâ€Œā˛—ā˛žā˛—ā˛ŋ SSL ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛° ā˛Ē➰ā˛ŋā˛ļāŗ€ā˛˛ā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąāŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛¸āŗā˛ĩ➝➂ ā˛¸ā˛šā˛ŋ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛°ā˛—ā˛ŗā˛ŋ➗⺆ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†.", + "advanced_settings_self_signed_ssl_title": "ā˛¸āŗā˛ĩ➝➂ ā˛¸ā˛šā˛ŋ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ SSL ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ [ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕]", "advanced_settings_sync_remote_deletions_subtitle": "ā˛ĩāŗ†ā˛Ŧāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ➆ ā˛•āŗā˛°ā˛Ž ā˛•āŗˆā˛—āŗŠā˛‚ā˛Ąā˛žā˛— ➈ ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ➍ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏ā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏ā˛ŋ", "advanced_settings_sync_remote_deletions_title": "➰ā˛ŋā˛Žāŗ‹ā˛Ÿāŗ ➅➺ā˛ŋ➏⺁ā˛ĩā˛ŋā˛•āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏ā˛ŋā˛‚ā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ [ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕]", "advanced_settings_tile_subtitle": "ā˛¸āŗā˛§ā˛žā˛°ā˛ŋ➤ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛° ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗ", @@ -506,18 +478,31 @@ "album_delete_confirmation": "➍⺀ā˛ĩ⺁ {album} ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "album_delete_confirmation_description": "➈ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ąā˛°āŗ†, ➇➤➰ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗ ā˛‡ā˛¨āŗā˛¨āŗ ā˛Žāŗā˛‚ā˛Ļāŗ† ➇ā˛Ļā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", "album_deleted": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_info_card_backup_album_excluded": "ā˛šāŗŠā˛°ā˛—ā˛ŋā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_info_card_backup_album_included": "➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "album_info_updated": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_leave": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛¤āŗŠā˛°āŗ†ā˛¯āŗā˛ĩ⺁ā˛Ļāŗ‡?", + "album_leave_confirmation": "➍⺀ā˛ĩ⺁ {album} ā˛¤āŗŠā˛°āŗ†ā˛¯ā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "album_name": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛šāŗ†ā˛¸ā˛°āŗ", "album_options": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ", "album_remove_user": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛ĩ⺁ā˛Ļāŗ‡?", "album_remove_user_confirmation": "➍⺀ā˛ĩ⺁ {user} ➅ā˛ĩā˛°ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "album_search_not_found": "➍ā˛ŋā˛Žāŗā˛Ž ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿā˛•āŗā˛•āŗ† ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛•āŗ†ā˛¯ā˛žā˛—āŗā˛ĩ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗāŗ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛˛āŗā˛˛", + "album_selected": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "album_share_no_users": "➍⺀ā˛ĩ⺁ ➈ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ąā˛ŋ➰⺁ā˛ĩ➂➤⺆ ā˛•ā˛žā˛Ŗāŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ† ➅ā˛Ĩā˛ĩā˛ž ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧ➺ā˛ŋ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗ ā˛‡ā˛˛āŗā˛˛.", "album_summary": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛¸ā˛žā˛°ā˛žā˛‚ā˛ļ", "album_updated": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "album_updated_setting_description": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰⺁ā˛ĩā˛žā˛— ā˛‡ā˛Žāŗ‡ā˛˛āŗ ➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩ⺀➕➰ā˛ŋ➏ā˛ŋ", "album_upload_assets": "➍ā˛ŋā˛Žāŗā˛Ž ➕➂ā˛Ēāŗā˛¯āŗ‚ā˛Ÿā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋ➏ā˛ŋ", + "album_user_left": "ā˛Žā˛Ą {album}", + "album_user_removed": "{user} ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_viewer_appbar_delete_confirm": "➍ā˛ŋā˛Žāŗā˛Ž ā˛–ā˛žā˛¤āŗ†ā˛¯ā˛ŋ➂ā˛Ļ ➈ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ➍⺀ā˛ĩ⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "album_viewer_appbar_share_err_delete": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➅➺ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_viewer_appbar_share_err_leave": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛¤āŗŠā˛°āŗ†ā˛¯ā˛˛āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_viewer_appbar_share_err_remove": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛ĩā˛˛āŗā˛˛ā˛ŋ ā˛¸ā˛Žā˛¸āŗā˛¯āŗ†ā˛—ā˛ŗā˛ŋā˛ĩāŗ†", + "album_viewer_appbar_share_err_title": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛ļāŗ€ā˛°āŗā˛ˇā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", + "album_viewer_appbar_share_leave": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛¤āŗŠā˛°āŗ†ā˛¯ā˛ŋ➰ā˛ŋ", + "album_viewer_appbar_share_to": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ", "album_viewer_page_share_add_users": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "album_with_link_access": "➞ā˛ŋā˛‚ā˛•āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰⺁ā˛ĩ ā˛¯ā˛žā˛°ā˛žā˛Ļ➰⺂ ➈ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛œā˛¨ā˛°ā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛˛ā˛ŋ.", "albums": "ā˛†ā˛˛āŗā˛Ŧ➂➗➺⺁", @@ -531,6 +516,7 @@ "all_photos": "ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ", "all_videos": "ā˛Žā˛˛āŗā˛˛ā˛ž ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", "allow_dark_mode": "ā˛Ąā˛žā˛°āŗā˛•āŗ ā˛Žāŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ", + "allow_edits": "➏➂ā˛Ēā˛žā˛Ļā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ", "allow_public_user_to_download": "ā˛¸ā˛žā˛°āŗā˛ĩ➜➍ā˛ŋ➕ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗ ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ", "allow_public_user_to_upload": "ā˛¸ā˛žā˛°āŗā˛ĩ➜➍ā˛ŋ➕ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛ŋ➗⺆ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏ā˛ŋ", "allowed": "ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -538,6 +524,7 @@ "always_keep": "ā˛¯ā˛žā˛ĩā˛žā˛—ā˛˛āŗ‚ ā˛‡ā˛Ÿāŗā˛Ÿāŗā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ", "always_keep_photos_hint": "ā˛¸āŗā˛Ĩā˛ŗā˛žā˛ĩā˛•ā˛žā˛ļ ā˛Žāŗā˛•āŗā˛¤ā˛—āŗŠā˛ŗā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ ➈ ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➇➰ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "always_keep_videos_hint": "ā˛¸āŗā˛Ĩā˛ŗā˛žā˛ĩā˛•ā˛žā˛ļ ā˛Žāŗā˛•āŗā˛¤ā˛—āŗŠā˛ŗā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ➈ ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➉➺ā˛ŋā˛¯āŗā˛¤āŗā˛¤ā˛ĩāŗ†.", + "anti_clockwise": "➅ā˛Ēāŗā˛°ā˛Ļā˛•āŗā˛ˇā˛ŋā˛Ŗā˛žā˛•ā˛žā˛°ā˛ĩā˛žā˛—ā˛ŋ", "api_key_description": "➈ ā˛ŽāŗŒā˛˛āŗā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ā˛’ā˛Žāŗā˛Žāŗ† ā˛Žā˛žā˛¤āŗā˛° ➤⺋➰ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛ĩā˛ŋā˛‚ā˛Ąāŗ‹ā˛ĩā˛¨āŗā˛¨āŗ ā˛Žāŗā˛šāŗā˛šāŗā˛ĩ ā˛ŽāŗŠā˛Ļ➞⺁ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➅ā˛Ļā˛¨āŗā˛¨āŗ ➍➕➞ā˛ŋ➏➞⺁ ā˛Žā˛°āŗ†ā˛¯ā˛Ļā˛ŋ➰ā˛ŋ.", "api_key_empty": "➍ā˛ŋā˛Žāŗā˛Ž API ➕⺀ ā˛šāŗ†ā˛¸ā˛°āŗ ā˛–ā˛žā˛˛ā˛ŋā˛¯ā˛žā˛—ā˛ŋ➰ā˛Ŧā˛žā˛°ā˛Ļ⺁", "app_bar_signout_dialog_content": "➍⺀ā˛ĩ⺁ ā˛¸āŗˆā˛¨āŗ ā˛”ā˛Ÿāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", @@ -549,17 +536,23 @@ "appears_in": "ā˛•ā˛žā˛Ŗā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛¤āŗā˛¤ā˛Ļāŗ†", "archive": "ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ", "archive_or_unarchive_photo": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ā˛…ā˛¨āŗâ€Œā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛ŋ", - "archive_size": "ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛—ā˛žā˛¤āŗā˛°", + "archive_page_no_archived_assets": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛˛āŗā˛˛", "archive_size_description": "ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗâ€Œā˛—ā˛ŗā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛—ā˛žā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛ŋ (GiB ā˛¨ā˛˛āŗā˛˛ā˛ŋ)", - "archived": "ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "are_these_the_same_person": "➇ā˛ĩ➰⺁ ➒➂ā˛Ļāŗ‡ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ➝⺇?", "are_you_sure_to_do_this": "➍⺀ā˛ĩ⺁ ➇ā˛Ļā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", + "array_field_not_fully_supported": "➅➰⺇ ā˛•āŗā˛ˇāŗ‡ā˛¤āŗā˛°ā˛—ā˛ŗā˛ŋ➗⺆ ā˛šā˛¸āŗā˛¤ā˛šā˛žā˛˛ā˛ŋ➤ JSON ➏➂ā˛Ēā˛žā˛Ļ➍⺆➝ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†", + "asset_action_delete_err_read_only": "➓ā˛Ļ➞⺁ ā˛Žā˛žā˛¤āŗā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(ā˛—ā˛ŗā˛¨āŗā˛¨āŗ) ➅➺ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "asset_action_share_err_offline": "➆ā˛Ģāŗâ€Œā˛˛āŗˆā˛¨āŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(ā˛—ā˛ŗā˛¨āŗā˛¨āŗ) ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "asset_added_to_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_adding_to_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", "asset_created": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_description_updated": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛ŋ➍ ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "asset_filename_is_offline": "{filename} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ➆ā˛Ģāŗâ€Œā˛˛āŗˆā˛¨āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋā˛Ļāŗ†", + "asset_has_unassigned_faces": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛¯āŗ‹ā˛œā˛ŋ➏ā˛Ļ ā˛Žāŗā˛–ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛Ļāŗ†", "asset_hashing": "ā˛šāŗā˛¯ā˛žā˛ļā˛ŋā˛‚ā˛—āŗâ€Ļ", "asset_list_group_by_sub_title": "➗⺁➂ā˛Ē⺁ ā˛Žā˛žā˛Ąā˛ŋ", + "asset_list_layout_settings_dynamic_layout_title": "ā˛Ąāŗˆā˛¨ā˛žā˛Žā˛ŋā˛•āŗ ā˛˛āŗ‡ā˛”ā˛Ÿāŗ", + "asset_list_layout_settings_group_automatically": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤", "asset_list_layout_settings_group_by": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➈ ā˛Ēāŗā˛°ā˛•ā˛žā˛° ➗⺁➂ā˛Ē⺁ ā˛Žā˛žā˛Ąā˛ŋ", "asset_list_layout_settings_group_by_month_day": "➤ā˛ŋ➂➗➺⺁ + ā˛Ļā˛ŋ➍", "asset_list_layout_sub_title": "ā˛ĩā˛ŋā˛¨āŗā˛¯ā˛žā˛¸", @@ -570,56 +563,100 @@ "asset_not_found_on_icloud": "ā˛ā˛•āŗā˛˛āŗŒā˛Ąāŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛˛āŗā˛˛. ā˛ā˛•āŗā˛˛āŗŒā˛Ąāŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛•āŗ†ā˛Ÿāŗā˛Ÿ ā˛Ģāŗˆā˛˛āŗ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛žā˛—ā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—ā˛Ļā˛ŋ➰ā˛Ŧā˛šāŗā˛Ļ⺁", "asset_offline": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ➆ā˛Ģāŗâ€Œā˛˛āŗˆā˛¨āŗ", "asset_offline_description": "➈ ā˛Ŧā˛žā˛šāŗā˛¯ ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛‡ā˛¨āŗā˛¨āŗ ā˛Žāŗā˛‚ā˛Ļāŗ† ā˛Ąā˛ŋā˛¸āŗā˛•āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➰⺁ā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛. ā˛¸ā˛šā˛žā˛¯ā˛•āŗā˛•ā˛žā˛—ā˛ŋ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛•ā˛°ā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛°āŗā˛•ā˛ŋ➏ā˛ŋ.", + "asset_restored_successfully": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_skipped": "ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_skipped_in_trash": "➕➏ā˛Ļā˛˛āŗā˛˛ā˛ŋā˛Ļāŗ†", + "asset_trashed": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ā˛•ā˛ŗāŗā˛šā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_troubleshoot": "ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛¸ā˛Žā˛¸āŗā˛¯āŗ† ➍ā˛ŋā˛ĩā˛žā˛°ā˛Ŗāŗ†", "asset_uploaded": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "asset_uploading": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", "asset_viewer_settings_subtitle": "➍ā˛ŋā˛Žāŗā˛Ž ā˛—āŗā˛¯ā˛žā˛˛ā˛°ā˛ŋ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛• ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", "asset_viewer_settings_title": "ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛•", "assets": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", + "assets_deleted_permanently": "{count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺⺁) ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "assets_deleted_permanently_from_server": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ {count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺⺁) ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "assets_removed_permanently_from_device": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛ŋ➂ā˛Ļ {count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(ā˛—ā˛ŗā˛¨āŗā˛¨āŗ) ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "assets_restore_confirmation": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Žā˛˛āŗā˛˛ā˛ž ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏➞⺁ ➍⺀ā˛ĩ⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➍⺀ā˛ĩ⺁ ➈ ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➰ā˛Ļāŗā˛Ļāŗā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛! ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ➆ā˛Ģāŗâ€Œā˛˛āŗˆā˛¨āŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➈ ➰⺀➤ā˛ŋā˛¯ā˛˛āŗā˛˛ā˛ŋ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛ ā˛Žā˛‚ā˛Ŧ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛—ā˛Žā˛¨ā˛ŋ➏ā˛ŋ.", + "assets_restored_successfully": "{count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺⺁) ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "assets_trashed": "{count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺⺁) ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ā˛•ā˛ŗāŗā˛šā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "assets_trashed_from_server": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ {count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺⺁) ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "authorized_devices": "➅➧ā˛ŋā˛•āŗƒā˛¤ ā˛¸ā˛žā˛§ā˛¨ā˛—ā˛ŗāŗ", "automatic_endpoint_switching_subtitle": "ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋ➰⺁ā˛ĩā˛žā˛— ā˛—āŗŠā˛¤āŗā˛¤āŗā˛Ēā˛Ąā˛ŋ➏ā˛ŋā˛Ļ ā˛ĩ⺈-ā˛Ģ⺈ ā˛Žāŗ‚ā˛˛ā˛• ā˛¸āŗā˛Ĩ➺⺀➝ā˛ĩā˛žā˛—ā˛ŋ ➏➂ā˛Ēā˛°āŗā˛•ā˛ŋ➏ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ŧāŗ‡ā˛°āŗ†ā˛Ąāŗ† ā˛Ēā˛°āŗā˛¯ā˛žā˛¯ ➏➂ā˛Ēā˛°āŗā˛•ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋ", "automatic_endpoint_switching_title": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ URL ā˛Ŧā˛Ļā˛˛ā˛žā˛ĩ➪⺆", "autoplay_slideshow": "ā˛¸āŗā˛˛āŗˆā˛Ąāŗâ€Œā˛ļāŗ‹ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩ➝➂ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ", "back": "ā˛šā˛ŋ➂ā˛Ļāŗ†", "back_close_deselect": "ā˛šā˛ŋ➂ā˛Ļā˛•āŗā˛•āŗ†, ā˛Žāŗā˛šāŗā˛šā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ā˛†ā˛¯āŗā˛•āŗ† ➰ā˛Ļāŗā˛Ļāŗā˛Žā˛žā˛Ąā˛ŋ", + "background_backup_running_error": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛šā˛žā˛˛ā˛¨āŗ†ā˛¯ā˛˛āŗā˛˛ā˛ŋā˛Ļāŗ†, ā˛šā˛¸āŗā˛¤ā˛šā˛žā˛˛ā˛ŋ➤ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "background_location_permission": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛¸āŗā˛Ĩ➺ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ", "background_location_permission_content": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ†ā˛¯ā˛˛āŗā˛˛ā˛ŋ ā˛šā˛žā˛˛ā˛¨āŗ†ā˛¯ā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩā˛žā˛— ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁, ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ *ā˛¯ā˛žā˛ĩā˛žā˛—ā˛˛āŗ‚* ➍ā˛ŋ➖➰ā˛ĩā˛žā˛Ļ ā˛¸āŗā˛Ĩ➺ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰ā˛Ŧ⺇➕⺁ ➇ā˛Ļ➰ā˛ŋ➂ā˛Ļ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛ĩ⺈-ā˛Ģ⺈ ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗâ€Œā˛¨ ā˛šāŗ†ā˛¸ā˛°ā˛¨āŗā˛¨āŗ ➓ā˛Ļā˛Ŧā˛šāŗā˛Ļ⺁", "background_options": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ", "backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ", + "backup_album_selection_page_albums_device": "ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗāŗ ({count})", "backup_album_selection_page_albums_tap": "➏⺇➰ā˛ŋ➏➞⺁ ā˛Ÿāŗā˛¯ā˛žā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ, ā˛šāŗŠā˛°ā˛—ā˛ŋā˛Ąā˛˛āŗ ā˛Ąā˛Ŧā˛˛āŗ ā˛Ÿāŗā˛¯ā˛žā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ", "backup_album_selection_page_assets_scatter": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ā˛Ŧā˛šāŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ā˛šā˛°ā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁. ā˛šāŗ€ā˛—ā˛žā˛—ā˛ŋ, ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Ēāŗā˛°ā˛•āŗā˛°ā˛ŋ➝⺆➝ ā˛¸ā˛Žā˛¯ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁ ➅ā˛Ĩā˛ĩā˛ž ā˛šāŗŠā˛°ā˛—ā˛ŋā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁.", "backup_album_selection_page_select_albums": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "backup_album_selection_page_selection_info": "ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋ", + "backup_album_selection_page_total_assets": "ā˛’ā˛Ÿāŗā˛Ÿāŗ ā˛…ā˛¨ā˛¨āŗā˛¯ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", "backup_albums_sync": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗ ➏ā˛ŋā˛‚ā˛•āŗā˛°āŗŠā˛¨āŗˆā˛¸āŗ‡ā˛ļā˛¨āŗ", + "backup_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩāŗ‚", + "backup_background_service_backup_failed_message": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛Žā˛°āŗā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", + "backup_background_service_connection_failed_message": "ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—āŗ† ➏➂ā˛Ēā˛°āŗā˛•ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛Žā˛°āŗā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", "backup_background_service_default_notification": "ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", "backup_background_service_in_progress_notification": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†â€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", + "backup_controller_page_background_app_refresh_disabled_content": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Ŧ➺➏➞⺁ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗ > ā˛¸ā˛žā˛Žā˛žā˛¨āŗā˛¯ > ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ.", + "backup_controller_page_background_app_refresh_disabled_title": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "backup_controller_page_background_battery_info_link": "ā˛šāŗ‡ā˛—āŗ†ā˛‚ā˛Ļ⺁ ➍➍➗⺆ ➤⺋➰ā˛ŋ➏ā˛ŋ", "backup_controller_page_background_battery_info_message": "ā˛…ā˛¤āŗā˛¯āŗā˛¤āŗā˛¤ā˛Ž ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➅➍⺁➭ā˛ĩā˛•āŗā˛•ā˛žā˛—ā˛ŋ, ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛—ā˛žā˛—ā˛ŋ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➚➟⺁ā˛ĩ➟ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛Ŧ➂➧ā˛ŋ➏⺁ā˛ĩ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ŧāŗā˛¯ā˛žā˛Ÿā˛°ā˛ŋ ➆ā˛Ēāŗā˛Ÿā˛ŋā˛Žāŗˆā˛¸āŗ‡ā˛ļā˛¨āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ.\n\n➇ā˛Ļ⺁ ā˛¸ā˛žā˛§ā˛¨-➍ā˛ŋā˛°āŗā˛Ļā˛ŋā˛ˇāŗā˛Ÿā˛ĩā˛žā˛—ā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ, ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ ā˛¤ā˛¯ā˛žā˛°ā˛•ā˛°ā˛ŋ➗⺆ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋ➰⺁ā˛ĩ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛ŋ.", "backup_controller_page_background_battery_info_ok": "➏➰ā˛ŋ", "backup_controller_page_background_battery_info_title": "ā˛Ŧāŗā˛¯ā˛žā˛Ÿā˛°ā˛ŋ ➆ā˛Ēāŗā˛Ÿā˛ŋā˛Žāŗˆā˛¸āŗ‡ā˛ļā˛¨āŗâ€Œā˛—ā˛ŗāŗ", + "backup_controller_page_background_charging": "ā˛šā˛žā˛°āŗā˛œāŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛Žā˛žā˛¤āŗā˛°", + "backup_controller_page_background_configure_error": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➏⺇ā˛ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", "backup_controller_page_background_delay": "ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛ĩā˛ŋ➺➂ā˛Ŧ: {duration}", + "backup_controller_page_background_description": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➤⺆➰⺆➝ā˛Ļ⺆➝⺇ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➏⺇ā˛ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ", + "backup_controller_page_background_is_off": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➆ā˛Ģāŗ ➆➗ā˛ŋā˛Ļāŗ†", + "backup_controller_page_background_is_on": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛¨āŗ ➆➗ā˛ŋā˛Ļāŗ†", + "backup_controller_page_background_turn_off": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➏⺇ā˛ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➆ā˛Ģāŗ ā˛Žā˛žā˛Ąā˛ŋ", + "backup_controller_page_background_turn_on": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➏⺇ā˛ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ", + "backup_controller_page_background_wifi": "ā˛ĩ⺈-ā˛Ģ⺈ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Žā˛žā˛¤āŗā˛°", "backup_controller_page_backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ", "backup_controller_page_backup_sub": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", + "backup_controller_page_created": "➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛Ļ ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•: {date}", + "backup_controller_page_desc_backup": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➤⺆➰⺆➝⺁ā˛ĩā˛žā˛— ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—āŗ† ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛†ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ.", + "backup_controller_page_failed": "ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ† ({count})", + "backup_controller_page_filename": "ā˛Ģāŗˆā˛˛āŗ ā˛šāŗ†ā˛¸ā˛°āŗ: {filename} [{size}]", + "backup_controller_page_id": "ā˛ā˛Ąā˛ŋ: {id}", + "backup_controller_page_info": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋ", "backup_controller_page_none_selected": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ‚ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛ŋā˛˛āŗā˛˛", "backup_controller_page_remainder": "ā˛ļ⺇➎", "backup_controller_page_remainder_sub": "ā˛†ā˛¯āŗā˛•āŗ†ā˛¯ā˛ŋ➂ā˛Ļ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➉➺ā˛ŋā˛Ļā˛ŋ➰⺁ā˛ĩ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", "backup_controller_page_server_storage": "ā˛¸ā˛°āŗā˛ĩā˛°āŗ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛Ŗāŗ†", + "backup_controller_page_start_backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏ā˛ŋ", + "backup_controller_page_status_off": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➆ā˛Ģāŗ ➆➗ā˛ŋā˛Ļāŗ†", + "backup_controller_page_status_on": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛¨āŗ ➆➗ā˛ŋā˛Ļāŗ†", "backup_controller_page_to_backup": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛Ŧāŗ‡ā˛•ā˛žā˛Ļ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗāŗ", "backup_controller_page_total_sub": "ā˛†ā˛¯āŗā˛Ļ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➂ā˛Ļ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛…ā˛¨ā˛¨āŗā˛¯ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", + "backup_controller_page_turn_off": "ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➆ā˛Ģāŗ ā˛Žā˛žā˛Ąā˛ŋ", + "backup_controller_page_turn_on": "ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ", + "backup_controller_page_uploading_file_info": "ā˛Ģāŗˆā˛˛āŗ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "backup_err_only_album": "➒➂ā˛Ļāŗ‡ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "backup_error_sync_failed": "➏ā˛ŋā˛‚ā˛•āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Ēāŗā˛°ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛.", "backup_info_card_assets": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", + "backup_manual_cancelled": "➰ā˛Ļāŗā˛Ļāŗā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "backup_manual_in_progress": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛ˆā˛—ā˛žā˛—ā˛˛āŗ‡ ā˛Ēāŗā˛°ā˛—ā˛¤ā˛ŋā˛¯ā˛˛āŗā˛˛ā˛ŋā˛Ļāŗ†. ā˛¸āŗā˛ĩā˛˛āŗā˛Ē ā˛¸ā˛Žā˛¯ā˛Ļ ➍➂➤➰ ā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋ➏ā˛ŋ", + "backup_manual_success": "➝ā˛ļā˛¸āŗā˛¸āŗ", + "backup_manual_title": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛¸āŗā˛Ĩā˛ŋ➤ā˛ŋ", "backup_options": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ", + "backup_options_page_title": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ", + "backup_setting_subtitle": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛Žāŗā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", "backup_settings_subtitle": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", + "backup_upload_details_page_more_details": "ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛ĩā˛ŋā˛ĩ➰➗➺ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ÿāŗā˛¯ā˛žā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ", "backward": "ā˛šā˛ŋ➂ā˛Ļā˛•āŗā˛•āŗ†", - "battery_optimization_backup_reliability": "ā˛Ŧāŗā˛¯ā˛žā˛Ÿā˛°ā˛ŋ ➆ā˛Ēāŗā˛Ÿā˛ŋā˛Žāŗˆā˛¸āŗ‡ā˛ļā˛¨āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ➍ ā˛ĩā˛ŋā˛ļāŗā˛ĩā˛žā˛¸ā˛žā˛°āŗā˛šā˛¤āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛§ā˛žā˛°ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "biometric_auth_enabled": "ā˛Ŧā˛¯āŗ‹ā˛Žāŗ†ā˛Ÿāŗā˛°ā˛ŋā˛•āŗ ā˛Ļ⺃ā˛ĸ⺀➕➰➪ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "biometric_locked_out": "➍⺀ā˛ĩ⺁ ā˛Ŧā˛¯āŗ‹ā˛Žāŗ†ā˛Ÿāŗā˛°ā˛ŋā˛•āŗ ā˛Ļ⺃ā˛ĸ⺀➕➰➪ā˛Ļā˛ŋ➂ā˛Ļ ā˛šāŗŠā˛°ā˛—āŗā˛ŗā˛ŋā˛Ļā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ", "biometric_no_options": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ŧā˛¯āŗ‹ā˛Žāŗ†ā˛Ÿāŗā˛°ā˛ŋā˛•āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "biometric_not_available": "➈ ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛Ŧā˛¯āŗ‹ā˛Žāŗ†ā˛Ÿāŗā˛°ā˛ŋā˛•āŗ ā˛Ļ⺃ā˛ĸ⺀➕➰➪ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", + "birthdate_saved": "ā˛œā˛¨āŗā˛Ž ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛ĩā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ➉➺ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "birthdate_set_description": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ ➤⺆➗⺆➝⺁ā˛ĩ ā˛¸ā˛Žā˛¯ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➆ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ➝ ā˛ĩā˛¯ā˛¸āŗā˛¸ā˛¨āŗā˛¨āŗ ā˛˛āŗ†ā˛•āŗā˛•ā˛šā˛žā˛•ā˛˛āŗ ā˛œā˛¨āŗā˛Ž ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧā˛ŗā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "blurred_background": "ā˛Žā˛¸āŗā˛•ā˛žā˛Ļ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ†", "bugs_and_feature_requests": "ā˛Ļ⺋➎➗➺⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ ā˛ĩā˛ŋ➍➂➤ā˛ŋ➗➺⺁", @@ -629,6 +666,20 @@ "bulk_keep_duplicates_confirmation": "➍⺀ā˛ĩ⺁ {count, plural, one {# duplicate asset} other {# duplicate assets}} ā˛…ā˛¨āŗā˛¨āŗ ➇➰ā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➇ā˛Ļ⺁ ā˛¯ā˛žā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ‚ ➅➺ā˛ŋ➏ā˛Ļāŗ† ā˛Žā˛˛āŗā˛˛ā˛ž ➍➕➞ā˛ŋ ➗⺁➂ā˛Ēāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛šā˛°ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "bulk_trash_duplicates_confirmation": "➍⺀ā˛ĩ⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ➝⺂ ā˛Ŧā˛˛āŗā˛•āŗ ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛ļāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛Ŧā˛¯ā˛¸āŗā˛¤āŗā˛¤āŗ€ā˛°ā˛ž {count, plural, one {# duplicate asset} other {# duplicate assets}}? ➇ā˛Ļ⺁ ā˛Ēāŗā˛°ā˛¤ā˛ŋ ➗⺁➂ā˛Ēā˛ŋ➍ ➅➤ā˛ŋā˛ĻāŗŠā˛Ąāŗā˛Ą ā˛†ā˛¸āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➉➺ā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ➇➤➰ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛¨ā˛•ā˛˛āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛ļāŗ ā˛Žā˛žā˛Ąāŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "buy": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➖➰⺀ā˛Ļā˛ŋ➏ā˛ŋ", + "cache_settings_clear_cache_button": "ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", + "cache_settings_clear_cache_button_title": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗâ€Œā˛¨ ā˛•āŗā˛¯ā˛žā˛ļāŗ ā˛…ā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛•āŗā˛¯ā˛žā˛ļāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛¨ā˛ŋā˛°āŗā˛Žā˛ŋ➏⺁ā˛ĩā˛ĩ➰⺆➗⺆ ➇ā˛Ļ⺁ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗâ€Œā˛¨ ā˛•ā˛žā˛°āŗā˛¯ā˛•āŗā˛ˇā˛Žā˛¤āŗ†ā˛¯ ā˛Žāŗ‡ā˛˛āŗ† ā˛—ā˛Žā˛¨ā˛žā˛°āŗā˛šā˛ĩā˛žā˛—ā˛ŋ ā˛Ē➰ā˛ŋā˛Ŗā˛žā˛Ž ā˛Ŧāŗ€ā˛°āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", + "cache_settings_duplicated_assets_clear_button": "➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", + "cache_settings_duplicated_assets_subtitle": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛Ēā˛Ÿāŗā˛Ÿā˛ŋ ā˛Žā˛žā˛Ąā˛ŋ➰⺁ā˛ĩ ➍ā˛ŋā˛°āŗā˛˛ā˛•āŗā˛ˇā˛ŋā˛¸ā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", + "cache_settings_duplicated_assets_title": "➍➕➞ā˛ŋ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ({count})", + "cache_settings_statistics_album": "➞⺈ā˛Ŧāŗā˛°ā˛°ā˛ŋ ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗâ€Œā˛—ā˛ŗāŗ", + "cache_settings_statistics_full": "ā˛Ēāŗ‚ā˛°āŗā˛Ŗ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗāŗ", + "cache_settings_statistics_shared": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗâ€Œā˛—ā˛ŗāŗ", + "cache_settings_statistics_thumbnail": "ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗâ€Œā˛—ā˛ŗāŗ", + "cache_settings_statistics_title": "ā˛•āŗā˛¯ā˛žā˛ļāŗ ā˛Ŧ➺➕⺆", + "cache_settings_subtitle": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛ŽāŗŠā˛Ŧāŗˆā˛˛āŗ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗâ€Œā˛¨ ā˛•āŗā˛¯ā˛žā˛ļā˛ŋā˛‚ā˛—āŗ ā˛¨ā˛Ąā˛ĩ➺ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛¯ā˛‚ā˛¤āŗā˛°ā˛ŋ➏ā˛ŋ", + "cache_settings_tile_subtitle": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛Ŗāŗ†ā˛¯ ā˛¨ā˛Ąā˛ĩ➺ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛¯ā˛‚ā˛¤āŗā˛°ā˛ŋ➏ā˛ŋ", + "cache_settings_tile_title": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛Ŗāŗ†", + "cache_settings_title": "ā˛•āŗā˛¯ā˛žā˛ļā˛ŋā˛‚ā˛—āŗ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗ", "camera": "ā˛•āŗā˛¯ā˛žā˛Žāŗ†ā˛°ā˛ž", "camera_brand": "ā˛•āŗā˛¯ā˛žā˛Žāŗ†ā˛°ā˛ž ā˛Ŧāŗā˛°āŗā˛¯ā˛žā˛‚ā˛Ąāŗ", "camera_model": "ā˛•āŗā˛¯ā˛žā˛Žāŗ†ā˛°ā˛ž ā˛Žā˛žā˛Ļ➰ā˛ŋ", @@ -644,6 +695,7 @@ "change_date": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", "change_description": "ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", "change_display_order": "ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļ➍ ā˛•āŗā˛°ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", + "change_expiration_time": "ā˛Žāŗā˛•āŗā˛¤ā˛žā˛¯ ā˛¸ā˛Žā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", "change_location": "ā˛¸āŗā˛Ĩ➺ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", "change_name": "ā˛šāŗ†ā˛¸ā˛°āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", "change_name_successfully": "ā˛šāŗ†ā˛¸ā˛°ā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -657,7 +709,11 @@ "change_password_form_password_mismatch": "ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗâ€Œā˛—ā˛ŗāŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛•āŗ†ā˛¯ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛", "change_password_form_reenter_new_password": "ā˛šāŗŠā˛¸ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛¤āŗā˛¤āŗ† ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", "change_pin_code": "ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", + "change_trigger": "ā˛Ÿāŗā˛°ā˛ŋā˛—āŗā˛—ā˛°āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", + "change_trigger_prompt": "➍⺀ā˛ĩ⺁ ā˛Ÿāŗā˛°ā˛ŋā˛—āŗā˛—ā˛°āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➇ā˛Ļ⺁ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛•āŗā˛°ā˛ŋ➝⺆➗➺⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "charging_requirement_mobile_backup": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗâ€Œā˛—āŗ† ā˛¸ā˛žā˛§ā˛¨ā˛ĩ⺁ ā˛šā˛žā˛°āŗā˛œāŗ ā˛†ā˛—āŗā˛¤āŗā˛¤ā˛ŋ➰ā˛Ŧ⺇➕⺁", + "check_corrupt_asset_backup": "ā˛­āŗā˛°ā˛ˇāŗā˛Ÿ ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗâ€Œā˛—ā˛ŗā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", + "check_corrupt_asset_backup_description": "➈ ā˛Ē➰ā˛ŋā˛ļāŗ€ā˛˛ā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛ĩ⺈-ā˛Ģ⺈ ā˛Žāŗ‚ā˛˛ā˛• ā˛Žā˛žā˛¤āŗā˛° ā˛°ā˛¨āŗ ā˛Žā˛žā˛Ąā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ➍➂➤➰. ➈ ā˛Ēāŗā˛°ā˛•āŗā˛°ā˛ŋ➝⺆➝⺁ ➕⺆➞ā˛ĩ⺁ ➍ā˛ŋā˛Žā˛ŋā˛ˇā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛•āŗŠā˛ŗāŗā˛ŗā˛Ŧā˛šāŗā˛Ļ⺁.", "choose_matching_people_to_merge": "ā˛ĩā˛ŋā˛˛āŗ€ā˛¨ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛šāŗŠā˛‚ā˛Ļā˛žā˛Ŗā˛ŋā˛•āŗ†ā˛¯ā˛žā˛—āŗā˛ĩ ā˛œā˛¨ā˛°ā˛¨āŗā˛¨āŗ ➆➰ā˛ŋ➏ā˛ŋ", "city": "➍➗➰", "cleanup_confirm_description": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ {date} ā˛•āŗā˛•ā˛ŋ➂➤ ā˛ŽāŗŠā˛Ļ➞⺁ ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛Ļ {count} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—āŗ† ā˛¸āŗā˛°ā˛•āŗā˛ˇā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛•ā˛‚ā˛Ąāŗā˛•āŗŠā˛‚ā˛Ąā˛°āŗ. ➈ ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛ŋ➂ā˛Ļ ā˛¸āŗā˛Ĩ➺⺀➝ ā˛Ēāŗā˛°ā˛¤ā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛ĩ⺁ā˛Ļāŗ‡?", @@ -675,6 +731,8 @@ "clear_file_cache": "ā˛Ģāŗˆā˛˛āŗ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "clear_message": "➏➂ā˛Ļāŗ‡ā˛ļā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "clear_value": "ā˛ŽāŗŒā˛˛āŗā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", + "client_cert_dialog_msg_confirm": "➏➰ā˛ŋ", + "client_cert_enter_password": "ā˛Ēā˛žā˛¸āŗā˛ĩā˛°āŗā˛Ąāŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", "client_cert_import": "ā˛†ā˛Žā˛Ļ⺁ ā˛Žā˛žā˛Ąā˛ŋ", "client_cert_import_success_msg": "ā˛•āŗā˛˛āŗˆā˛‚ā˛Ÿāŗ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛Žā˛Ļ⺁ ā˛Žā˛žā˛Ąā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "client_cert_invalid_msg": "ā˛…ā˛Žā˛žā˛¨āŗā˛¯ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛° ā˛Ģāŗˆā˛˛āŗ ➅ā˛Ĩā˛ĩā˛ž ➤ā˛Ēāŗā˛Ē⺁ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ", @@ -683,10 +741,12 @@ "client_cert_remove_msg": "ā˛•āŗā˛˛āŗˆā˛‚ā˛Ÿāŗ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "client_cert_subtitle": "PKCS12 (.p12, .pfx) ā˛¸āŗā˛ĩ➰⺂ā˛Ēā˛ĩā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ➆➗⺁ā˛ĩ ā˛ŽāŗŠā˛Ļ➞⺁ ā˛Žā˛žā˛¤āŗā˛° ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛° ā˛†ā˛Žā˛Ļ⺁/➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛ĩā˛ŋ➕⺆ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†", "client_cert_title": "SSL ā˛•āŗā˛˛āŗˆā˛‚ā˛Ÿāŗ ā˛Ēāŗā˛°ā˛Žā˛žā˛Ŗā˛Ēā˛¤āŗā˛° [ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕]", + "clockwise": "ā˛•āŗā˛˛ā˛žā˛•āŗâ€Œā˛ĩāŗˆā˛¸āŗ", "close": "ā˛Žāŗā˛šāŗā˛šā˛ŋ", "collapse": "ā˛•āŗā˛—āŗā˛—ā˛ŋ➏⺁", "collapse_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ ā˛•āŗā˛—āŗā˛—ā˛ŋ➏ā˛ŋ", "color": "ā˛Ŧā˛Ŗāŗā˛Ŗ", + "color_theme": "ā˛Ŧā˛Ŗāŗā˛Ŗ ā˛Ĩāŗ€ā˛Žāŗ", "command": "ā˛†ā˛œāŗā˛žāŗ†", "command_palette_prompt": "ā˛Ēāŗā˛Ÿā˛—ā˛ŗāŗ, ā˛•āŗā˛°ā˛ŋ➝⺆➗➺⺁ ➅ā˛Ĩā˛ĩā˛ž ā˛†ā˛œāŗā˛žāŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¤āŗā˛ĩ➰ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "command_palette_to_close": "ā˛Žāŗā˛šāŗā˛šā˛˛āŗ", @@ -701,10 +761,12 @@ "context": "➏➂ā˛Ļā˛°āŗā˛­", "continue": "ā˛Žāŗā˛‚ā˛Ļ⺁ā˛ĩ➰ā˛ŋ➏ā˛ŋ", "control_bottom_app_bar_edit_time": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛Žā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛žā˛Ļā˛ŋ➏ā˛ŋ", + "control_bottom_app_bar_share_to": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ", "control_bottom_app_bar_trash_from_immich": "➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ➏➰ā˛ŋ➏ā˛ŋ", "copied_image_to_clipboard": "➚ā˛ŋā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛•āŗā˛˛ā˛ŋā˛Ēāŗâ€Œā˛Ŧāŗ‹ā˛°āŗā˛Ąāŗâ€Œā˛—āŗ† ➍➕➞ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†.", "copied_to_clipboard": "ā˛•āŗā˛˛ā˛ŋā˛Ēāŗ ā˛Ŧāŗ‹ā˛°āŗā˛Ąāŗ ➗⺆ ➍➕➞ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†!", "copy_error": "ā˛Ļ⺋➎ā˛ĩā˛¨āŗā˛¨āŗ ➍➕➞ā˛ŋ➏ā˛ŋ", + "copy_file_path": "ā˛Ģāŗˆā˛˛āŗ ā˛Žā˛žā˛°āŗā˛—ā˛ĩā˛¨āŗā˛¨āŗ ➍➕➞ā˛ŋ➏ā˛ŋ", "copy_image": "➚ā˛ŋā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ➍➕➞ā˛ŋ➏ā˛ŋ", "copy_link": "➞ā˛ŋā˛‚ā˛•āŗ ➍➕➞ā˛ŋ➏ā˛ŋ", "copy_link_to_clipboard": "➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛•āŗā˛˛ā˛ŋā˛Ēāŗâ€Œā˛Ŧāŗ‹ā˛°āŗā˛Ąāŗâ€Œā˛—āŗ† ➍➕➞ā˛ŋ➏ā˛ŋ", @@ -726,7 +788,6 @@ "create_new_person": "ā˛šāŗŠā˛¸ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏ā˛ŋ", "create_new_person_hint": "ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛¸ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ➗⺆ ➍ā˛ŋā˛¯āŗ‹ā˛œā˛ŋ➏ā˛ŋ", "create_new_user": "ā˛šāŗŠā˛¸ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏ā˛ŋ", - "create_person_subtitle": "ā˛šāŗŠā˛¸ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏➞⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛†ā˛¯āŗā˛Ļ ā˛Žāŗā˛–ā˛•āŗā˛•āŗ† ā˛šāŗ†ā˛¸ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", "create_shared_album_page_share_add_assets": "ā˛Žā˛Ąā˛ŋā˛Ąā˛ŋ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", "create_shared_album_page_share_select_photos": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "create_shared_link": "ā˛šā˛‚ā˛šā˛ŋā˛Ļ ➞ā˛ŋā˛‚ā˛•āŗ ➰➚ā˛ŋ➏ā˛ŋ", @@ -738,15 +799,18 @@ "created_at": "➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "creating_linked_albums": "➞ā˛ŋā˛‚ā˛•āŗā˛Ąāŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ⺁ ...", "crop": "ā˛Ŧ⺆➺⺆", + "crop_aspect_ratio_fixed": "ā˛¸āŗā˛Ĩā˛ŋ➰", "crop_aspect_ratio_free": "ā˛‰ā˛šā˛ŋ➤", "crop_aspect_ratio_original": "ā˛Žāŗ‚ā˛˛", "crop_aspect_ratio_square": "ā˛šāŗŒā˛•", + "curated_object_page_title": "ā˛ĩā˛ŋ➎➝➗➺⺁", "current_device": "ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛¸ā˛žā˛§ā˛¨", "current_pin_code": "ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ", "current_server_address": "ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛¸ā˛°āŗā˛ĩā˛°āŗ ā˛ĩā˛ŋā˛ŗā˛žā˛¸", "custom_date": "ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•", "custom_locale": "ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ ā˛˛āŗŠā˛•āŗ‡ā˛˛āŗ", "custom_locale_description": "ā˛†ā˛¯āŗā˛Ļ ā˛­ā˛žā˛ˇāŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛Ēāŗā˛°ā˛Ļāŗ‡ā˛ļā˛ĩā˛¨āŗā˛¨āŗ ➆➧➰ā˛ŋ➏ā˛ŋā˛Ļ ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛—ā˛ŗāŗ, ā˛¸ā˛Žā˛¯ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛‚ā˛–āŗā˛¯āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ģā˛žā˛°āŗā˛Žāŗā˛¯ā˛žā˛Ÿāŗ ā˛Žā˛žā˛Ąā˛ŋ", + "custom_url": "ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ URL", "cutoff_date_description": "ā˛šā˛ŋ➂ā˛Ļā˛ŋ➍ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➇➰ā˛ŋ➏ā˛ŋâ€Ļ", "dark": "ā˛•ā˛¤āŗā˛¤ā˛˛āŗ", "dark_theme": "ā˛Ąā˛žā˛°āŗā˛•āŗ ā˛Ĩāŗ€ā˛Žāŗ ➗⺆ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", @@ -758,7 +822,8 @@ "day": "ā˛Ļā˛ŋ➍", "days": "ā˛Ļā˛ŋ➍➗➺⺁", "deduplicate_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ‚ ā˛¸ā˛Žā˛°āŗā˛Ēā˛ŋ➏ā˛ŋ", - "default_quality_subtitle": "ā˛Ēā˛žā˛˛ā˛¨āŗā˛¨āŗ ā˛Ÿāŗā˛¯ā˛žā˛Ēāŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛Ŧ➺➏⺁ā˛ĩ ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿ. ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛Ŧā˛žā˛°ā˛ŋ➝⺂ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛˛āŗ ā˛Ēā˛žā˛˛āŗ ā˛Ŧā˛Ÿā˛¨āŗ ā˛’ā˛¤āŗā˛¤ā˛ŋ➰ā˛ŋ.", + "default_locale": "ā˛Ąāŗ€ā˛Ģā˛žā˛˛āŗā˛Ÿāŗ ā˛˛āŗŠā˛•āŗ‡ā˛˛āŗ", + "default_locale_description": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧāŗā˛°āŗŒā˛¸ā˛°āŗ ā˛˛āŗŠā˛•āŗ‡ā˛˛āŗ ā˛†ā˛§ā˛žā˛°ā˛ŋ➤ ā˛¸āŗā˛ĩ➰⺂ā˛Ē ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛‚ā˛–āŗā˛¯āŗ†ā˛—ā˛ŗāŗ", "delete": "➅➺ā˛ŋ➏ā˛ŋ", "delete_action_confirmation_message": "➍⺀ā˛ĩ⺁ ➈ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➈ ā˛•āŗā˛°ā˛ŋ➝⺆➝⺁ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ➏➰ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ➍⺀ā˛ĩ⺁ ➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ĩ➺⺀➝ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏➞⺁ ā˛Ŧ➝➏ā˛ŋā˛Ļ➰⺆ ā˛•āŗ‡ā˛ŗāŗā˛¤āŗā˛¤ā˛Ļāŗ†", "delete_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➅➺ā˛ŋ➏ā˛ŋ", @@ -766,10 +831,13 @@ "delete_dialog_alert": "➈ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛ŋ➂ā˛Ļ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "delete_dialog_alert_local": "➈ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛ŋ➂ā˛Ļ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ† ➆ā˛Ļ➰⺆ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛°āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "delete_dialog_alert_local_non_backed_up": "➕⺆➞ā˛ĩ⺁ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛—āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛˛āŗā˛˛ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛ŋ➂ā˛Ļ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", + "delete_dialog_alert_remote": "➈ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "delete_duplicates_confirmation": "➍⺀ā˛ĩ⺁ ➈ ā˛¨ā˛•ā˛˛āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "delete_local_dialog_ok_backed_up_only": "ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ➅➺ā˛ŋ➏ā˛ŋ", "delete_tag_confirmation_prompt": "➍⺀ā˛ĩ⺁ {tagName} ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", + "deletes_missing_assets": "ā˛Ąā˛ŋā˛¸āŗā˛•āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅➺ā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "description": "ā˛ĩā˛ŋā˛ĩ➰➪⺆", + "description_input_submit_error": "ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ĩ⺀➕➰ā˛ŋ➏⺁ā˛ĩā˛˛āŗā˛˛ā˛ŋ ā˛Ļ⺋➎, ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛ĩā˛ŋā˛ĩ➰➗➺ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛˛ā˛žā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", "details": "ā˛ĩā˛ŋā˛ĩ➰➗➺⺁", "direction": "➍ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļ➍", "disabled": "➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -783,6 +851,7 @@ "download_include_embedded_motion_videos_description": "ā˛šā˛˛ā˛¨āŗ†ā˛¯ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ā˛Žā˛‚ā˛Ŧāŗ†ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛Ļ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛¤āŗā˛¯āŗ‡ā˛• ā˛Ģāŗˆā˛˛āŗ ➆➗ā˛ŋ ➏⺇➰ā˛ŋ➏ā˛ŋ", "download_settings": "ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗ", "download_settings_description": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗâ€Œā˛—āŗ† ➏➂ā˛Ŧ➂➧ā˛ŋ➏ā˛ŋā˛Ļ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", + "download_sucess_android": "ā˛Žā˛žā˛§āŗā˛¯ā˛Žā˛ĩā˛¨āŗā˛¨āŗ DCIM/Immich ➗⺆ ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "downloading": "ā˛ĄāŗŒā˛¨āŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "drop_files_to_upload": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛˛āŗā˛˛ā˛ŋā˛¯ā˛žā˛Ļ➰⺂ ā˛Ŧā˛ŋā˛Ąā˛ŋ", "duplicates": "➍➕➞⺁➗➺⺁", @@ -791,8 +860,11 @@ "edit": "➤ā˛ŋā˛Ļāŗā˛Ļ⺁", "edit_date_and_time": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛Žā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛žā˛Ļā˛ŋ➏ā˛ŋ", "edit_date_and_time_action_prompt": "{count} ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛Žā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛žā˛Ļā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "edit_date_and_time_by_offset_interval": "ā˛šāŗŠā˛¸ ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛ļāŗā˛°āŗ‡ā˛Ŗā˛ŋ: {from} - {to}", + "edit_description_prompt": "ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛šāŗŠā˛¸ ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ:", "editor": "➏➂ā˛Ēā˛žā˛Ļ➕", - "editor_discard_edits_prompt": "➍⺀ā˛ĩ⺁ ā˛¸āŗā˛¸ā˛œāŗā˛œā˛ŋ➤ ➏➂ā˛Ēā˛žā˛Ļā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ. ➍⺀ā˛ĩ⺁ ➅ā˛ĩā˛°ā˛¨āŗā˛¨āŗ ā˛¤āŗā˛¯ā˛œā˛ŋ➏➞⺁ ā˛Ŧā˛¯ā˛¸āŗā˛¤āŗā˛¤āŗ€ā˛°ā˛ŋ ā˛Žā˛‚ā˛Ļ⺁ ➍ā˛ŋā˛Žā˛—āŗ† ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋā˛Ļ⺆➝⺇?", + "editor_close_without_save_prompt": "ā˛Ŧā˛Ļā˛˛ā˛žā˛ĩā˛Ŗāŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➉➺ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛", + "editor_confirm_reset_all_changes": "➍⺀ā˛ĩ⺁ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ŧā˛Ļā˛˛ā˛žā˛ĩā˛Ŗāŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏➞⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "email": "ā˛‡ā˛Žāŗ†āŗ•ā˛˛āŗ", "empty_folder": "➈ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗ ā˛–ā˛žā˛˛ā˛ŋā˛¯ā˛žā˛—ā˛ŋā˛Ļāŗ†", "empty_trash_confirmation": "➍⺀ā˛ĩ⺁ ➕➏ā˛ĩā˛¨āŗā˛¨āŗ ā˛–ā˛žā˛˛ā˛ŋ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➇ā˛Ļ⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ➕➏ā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛¤āŗā˛¤ā˛Ļāŗ†.\n➍⺀ā˛ĩ⺁ ➈ ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➰ā˛Ļāŗā˛Ļāŗā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛!", @@ -801,6 +873,7 @@ "enabled": "ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "enter_your_pin_code_subtitle": "ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏➞⺁ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", "error": "ā˛Ļ⺋➎", + "error_change_sort_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛ĩā˛ŋā˛‚ā˛—ā˛Ąā˛Ŗā˛ž ā˛•āŗā˛°ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", "error_delete_face": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛ŋ➍ā˛ŋ➂ā˛Ļ ā˛Žāŗā˛–ā˛ĩā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏⺁ā˛ĩā˛žā˛— ā˛Ļ⺋➎ ā˛Žā˛Ļāŗā˛°ā˛žā˛—ā˛ŋā˛Ļāŗ†", "error_tag_face_bounding_box": "ā˛Žāŗā˛–ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛˛āŗā˛˛ā˛ŋ ā˛Ļ⺋➎ - ā˛ŦāŗŒā˛‚ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛Ŧā˛žā˛•āŗā˛¸āŗ ➍ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļā˛žā˛‚ā˛•ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛", "error_title": "ā˛Ļ⺋➎ - ā˛ā˛¨āŗ‹ ➤ā˛Ēāŗā˛Ēā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -907,6 +980,8 @@ "unable_to_upload_file": "ā˛Ģāŗˆā˛˛āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛" }, "exif": "ā˛Žā˛•āŗā˛¸ā˛ŋā˛Ģāŗ", + "experimental_settings_new_asset_list_title": "ā˛Ēāŗā˛°ā˛žā˛¯āŗ‹ā˛—ā˛ŋ➕ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ā˛—āŗā˛°ā˛ŋā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", + "experimental_settings_subtitle": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗā˛ĩ➂➤ ➅ā˛Ēā˛žā˛¯ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛Ŧ➺➏ā˛ŋ!", "expired": "➅ā˛ĩ➧ā˛ŋ ā˛Žāŗ€ā˛°ā˛ŋā˛Ļāŗ†", "explore": "ā˛Ē➰ā˛ŋā˛ļ⺋➧ā˛ŋ➏⺁", "explorer": "ā˛Žā˛•āŗā˛¸āŗâ€Œā˛Ēāŗā˛˛āŗ‹ā˛°ā˛°āŗ", @@ -920,11 +995,14 @@ "favorite": "ā˛¨āŗ†ā˛šāŗā˛šā˛ŋ➍", "favorite_or_unfavorite_photo": "ā˛¨āŗ†ā˛šāŗā˛šā˛ŋ➍ ➅ā˛Ĩā˛ĩā˛ž ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛Ļ➰ā˛ŋ➂ā˛Ļ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹", "favorites": "ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛ĩ⺁➗➺⺁", + "favorites_page_no_favorites": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛¨āŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛˛āŗā˛˛", "features": "ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛—ā˛ŗāŗ", "features_setting_description": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", "file_name_or_extension": "ā˛Ģāŗˆā˛˛āŗ ā˛šāŗ†ā˛¸ā˛°āŗ ➅ā˛Ĩā˛ĩā˛ž ā˛ĩā˛ŋā˛¸āŗā˛¤ā˛°ā˛Ŗāŗ†", "filename": "ā˛Ģāŗˆā˛˛āŗ ā˛šāŗ†ā˛¸ā˛°āŗ", "filetype": "ā˛Ģāŗˆā˛˛āŗ ā˛Ēāŗā˛°ā˛•ā˛žā˛°", + "filter_description": "➗⺁➰ā˛ŋ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛ˇā˛°ā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", + "find_them_fast": "ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿā˛Ļ ā˛Žāŗ‚ā˛˛ā˛• ā˛šāŗ†ā˛¸ā˛°ā˛ŋ➍ā˛ŋ➂ā˛Ļ ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩ⺇➗ā˛ĩā˛žā˛—ā˛ŋ ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "folders": "ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—ā˛ŗāŗ", "folders_feature_description": "ā˛Ģāŗˆā˛˛āŗ ➏ā˛ŋā˛¸āŗā˛Ÿā˛Žāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛°āŗŒā˛¸āŗ ā˛Žā˛žā˛Ąāŗā˛ĩ⺁ā˛Ļ⺁", "forward": "ā˛Žāŗā˛‚ā˛Ļāŗ†", @@ -934,8 +1012,24 @@ "geolocation_instruction_location": "GPS ➍ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļā˛žā˛‚ā˛•ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰⺁ā˛ĩ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛ŋ➍ ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏➞⺁ ➅ā˛Ļ➰ ā˛Žāŗ‡ā˛˛āŗ† ā˛•āŗā˛˛ā˛ŋā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ, ➅ā˛Ĩā˛ĩā˛ž ā˛¨ā˛•āŗā˛ˇāŗ†ā˛¯ā˛ŋ➂ā˛Ļ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "get_wifiname_error": "ā˛ĩ⺈-ā˛Ģ⺈ ā˛šāŗ†ā˛¸ā˛°ā˛¨āŗā˛¨āŗ ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—ā˛˛ā˛ŋā˛˛āŗā˛˛. ➍⺀ā˛ĩ⺁ ā˛…ā˛—ā˛¤āŗā˛¯ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ€ā˛Ąā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩ⺈-ā˛Ģ⺈ ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗâ€Œā˛—āŗ† ➏➂ā˛Ēā˛°āŗā˛•ā˛—āŗŠā˛‚ā˛Ąā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ ā˛Žā˛‚ā˛Ļ⺁ ā˛–ā˛šā˛ŋ➤ā˛Ēā˛Ąā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ", "header_settings_field_validator_msg": "ā˛ŽāŗŒā˛˛āŗā˛¯ ā˛–ā˛žā˛˛ā˛ŋā˛¯ā˛žā˛—ā˛ŋ➰ā˛Ŧā˛žā˛°ā˛Ļ⺁", + "home_page_add_to_album_conflicts": "{album} ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† {added} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†. {failed} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ā˛ˆā˛—ā˛žā˛—ā˛˛āŗ‡ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋā˛ĩāŗ†.", + "home_page_add_to_album_err_local": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ➏⺇➰ā˛ŋ➏➞⺁ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_add_to_album_success": "{album} ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† {added} ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†.", + "home_page_album_err_partner": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—āŗ† ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏➞⺁ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_archive_err_local": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_archive_err_partner": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛°āŗā˛•āŗˆā˛ĩāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_delete_err_partner": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_delete_remote_err_local": "➅➺ā˛ŋ➏⺁ ➰ā˛ŋā˛Žāŗ‹ā˛Ÿāŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛¯ā˛˛āŗā˛˛ā˛ŋ ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_favorite_err_local": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛ĩ⺁ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_favorite_err_partner": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛Žāŗ†ā˛šāŗā˛šā˛ŋ➍ā˛ĩ⺁ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_first_time_notice": "➍⺀ā˛ĩ⺁ ➈ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛ŽāŗŠā˛Ļ➞ ā˛Ŧā˛žā˛°ā˛ŋ➗⺆ ā˛Ŧā˛ŗā˛¸āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗā˛Ļ➰⺆, ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ ➇ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛Ÿāŗˆā˛Žāŗâ€Œā˛˛āŗˆā˛¨āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺁➂ā˛Ŧā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁", + "home_page_locked_error_local": "ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—āŗ† ➏➰ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_locked_error_partner": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—āŗ† ➏➰ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_share_err_local": "➞ā˛ŋā˛‚ā˛•āŗ ā˛Žāŗ‚ā˛˛ā˛• ā˛¸āŗā˛Ĩ➺⺀➝ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "home_page_upload_err_limit": "ā˛’ā˛Žāŗā˛Žāŗ†ā˛—āŗ† ➗➰ā˛ŋā˛ˇāŗā˛  30 ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁", "host": "ā˛šāŗ‹ā˛¸āŗā˛Ÿāŗ", "hour": "ā˛—ā˛‚ā˛Ÿāŗ†", + "ignore_icloud_photos_description": "ā˛ā˛•āŗā˛˛āŗŒā˛Ąāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛žā˛—ā˛ŋ➰⺁ā˛ĩ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—āŗ† ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛", "image": "➚ā˛ŋā˛¤āŗā˛°", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} ā˛…ā˛¨āŗā˛¨āŗ {city}, {country} ā˛¨ā˛˛āŗā˛˛ā˛ŋ {person1}, {person2}, ā˛Žā˛¤āŗā˛¤āŗ {additionalCount, number} others ā˛œāŗŠā˛¤āŗ†ā˛—āŗ† {date} ➰➂ā˛Ļ⺁ ➤⺆➗⺆ā˛Ļāŗā˛•āŗŠā˛ŗāŗā˛ŗā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "include_shared_partner_assets": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛ŋ", @@ -945,6 +1039,7 @@ "night_at_midnight": "ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛°ā˛žā˛¤āŗā˛°ā˛ŋ ā˛Žā˛§āŗā˛¯ā˛°ā˛žā˛¤āŗā˛°ā˛ŋā˛¯ā˛˛āŗā˛˛ā˛ŋ", "night_at_twoam": "ā˛Ēāŗā˛°ā˛¤ā˛ŋā˛Ļā˛ŋ➍ ā˛°ā˛žā˛¤āŗā˛°ā˛ŋ 2 ā˛—ā˛‚ā˛Ÿāŗ†ā˛—āŗ†" }, + "ios_debug_info_no_sync_yet": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ➏ā˛ŋā˛‚ā˛•āŗ ➕⺆➞➏ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛šā˛žā˛˛ā˛¨āŗ†ā˛¯ā˛žā˛—ā˛ŋā˛˛āŗā˛˛", "jobs": "➉ā˛Ļāŗā˛¯āŗ‹ā˛—ā˛—ā˛ŗāŗ", "keep": "➇➰ā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ", "keep_description": "ā˛¸āŗā˛Ĩā˛ŗā˛žā˛ĩā˛•ā˛žā˛ļ ā˛Žāŗā˛•āŗā˛¤ā˛—āŗŠā˛ŗā˛ŋ➏⺁ā˛ĩā˛žā˛— ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛ā˛¨āŗ ➉➺ā˛ŋā˛¯āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛‚ā˛Ŧ⺁ā˛Ļā˛¨āŗā˛¨āŗ ➆➰ā˛ŋ➏ā˛ŋ.", @@ -973,6 +1068,7 @@ "login": "ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ", "login_disabled": "ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "login_form_api_exception": "API ā˛ĩā˛ŋā˛¨ā˛žā˛¯ā˛ŋ➤ā˛ŋ. ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗ URL ā˛…ā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Žā˛¤āŗā˛¤āŗ† ā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋ➏ā˛ŋ.", + "login_form_err_http": "ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ http:// ➅ā˛Ĩā˛ĩā˛ž https:// ā˛…ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛Ļā˛ŋā˛ˇāŗā˛Ÿā˛Ēā˛Ąā˛ŋ➏ā˛ŋ", "login_form_failed_get_oauth_server_config": "OAuth ā˛Ŧ➺➏ā˛ŋā˛•āŗŠā˛‚ā˛Ąāŗ ā˛˛ā˛žā˛—ā˛ŋā˛‚ā˛—āŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛Ļ⺋➎, ā˛¸ā˛°āŗā˛ĩā˛°āŗ URL ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", "login_form_failed_get_oauth_server_disable": "➈ ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ OAuth ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "login_form_failed_login": "➍ā˛ŋā˛Žāŗā˛Žā˛¨āŗā˛¨āŗ ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛˛āŗā˛˛ā˛ŋ ā˛Ļ⺋➎, ā˛¸ā˛°āŗā˛ĩā˛°āŗ URL, ā˛‡ā˛Žāŗ‡ā˛˛āŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", @@ -988,6 +1084,7 @@ "loop_videos_description": "ā˛ĩā˛ŋā˛ĩ➰ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛•ā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ➞⺂ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ.", "main_branch_warning": "➍⺀ā˛ĩ⺁ ➅➭ā˛ŋā˛ĩ⺃ā˛Ļāŗā˛§ā˛ŋ ➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛ŗā˛¸āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ; ā˛Ŧā˛ŋā˛Ąāŗā˛—ā˛Ąāŗ† ➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏➞⺁ ā˛¨ā˛žā˛ĩ⺁ ā˛Ŧ➞ā˛ĩā˛žā˛—ā˛ŋ ā˛ļā˛ŋā˛Ģā˛žā˛°ā˛¸āŗ ā˛Žā˛žā˛Ąāŗā˛¤āŗā˛¤āŗ‡ā˛ĩāŗ†!", "maintenance_description": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛…ā˛¨āŗā˛¨āŗ maintenance mode ā˛•āŗā˛•āŗ† ➇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†.", + "maintenance_end_error": "➍ā˛ŋā˛°āŗā˛ĩā˛šā˛Ŗā˛ž ā˛•āŗā˛°ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ā˛•āŗŠā˛¨āŗ†ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†.", "maintenance_logged_in_as": "ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ {user} ➆➗ā˛ŋ ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗ ➆➗ā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ", "maintenance_restore_library_confirm": "➇ā˛Ļ⺁ ➏➰ā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļ➰⺆, ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛Žāŗā˛‚ā˛Ļ⺁ā˛ĩ➰ā˛ŋ➏ā˛ŋ!", "maintenance_restore_library_hint_missing_files": "➍⺀ā˛ĩ⺁ ā˛Ēāŗā˛°ā˛Žāŗā˛– ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➕➺⺆ā˛Ļāŗā˛•āŗŠā˛‚ā˛Ąā˛ŋ➰ā˛Ŧā˛šāŗā˛Ļ⺁", @@ -1007,14 +1104,11 @@ "map": "ā˛¨ā˛•āŗā˛ˇāŗ†", "map_cannot_get_user_location": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛° ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "map_location_service_disabled_content": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛¸āŗā˛Ĩ➺ā˛Ļā˛ŋ➂ā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏➞⺁ ā˛¸āŗā˛Ĩ➺ ➏⺇ā˛ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏⺁ā˛ĩ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†. ➍⺀ā˛ĩ⺁ ā˛ˆā˛— ➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", + "map_marker_for_images": "{city}, {country} ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➤⺆➗⺆ā˛Ļ ➚ā˛ŋā˛¤āŗā˛°ā˛—ā˛ŗā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛¨ā˛•āŗā˛ˇāŗ† ā˛Žā˛žā˛°āŗā˛•ā˛°āŗ", "map_marker_with_image": "➚ā˛ŋā˛¤āŗā˛°ā˛ĻāŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛¨ā˛•āŗā˛ˇāŗ† ā˛Žā˛žā˛°āŗā˛•ā˛°āŗ", "map_no_location_permission_content": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēāŗā˛°ā˛¸āŗā˛¤āŗā˛¤ ā˛¸āŗā˛Ĩ➺ā˛Ļā˛ŋ➂ā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏➞⺁ ā˛¸āŗā˛Ĩ➺ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†. ➍⺀ā˛ĩ⺁ ā˛ˆā˛— ➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏➞⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", + "map_zoom_to_see_photos": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛˛āŗ ā˛āŗ‚ā˛Žāŗ ā˛”ā˛Ÿāŗ ā˛Žā˛žā˛Ąā˛ŋ", "matches": "ā˛Ē➂ā˛Ļāŗā˛¯ā˛—ā˛ŗāŗ", - "media_chrome": { - "media_error_description": "ā˛Žā˛žā˛§āŗā˛¯ā˛Ž ā˛Ļ⺋➎ā˛ĩ⺁ ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ĩ➗ā˛ŋā˛¤ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛•ā˛žā˛°ā˛Ŗā˛ĩā˛žā˛¯ā˛ŋ➤⺁. ā˛Žā˛žā˛§āŗā˛¯ā˛Žā˛—ā˛ŗāŗ ā˛­āŗā˛°ā˛ˇāŗā˛Ÿā˛ĩā˛žā˛—ā˛Ŧā˛šāŗā˛Ļ⺁ ➅ā˛Ĩā˛ĩā˛ž ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧāŗā˛°āŗŒā˛¸ā˛°āŗ ➈ ā˛¸āŗā˛ĩ➰⺂ā˛Ēā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", - "network_error_description": "ā˛¨āŗ†ā˛Ÿāŗ ā˛ĩā˛°āŗā˛•āŗ ā˛Ļ⺋➎ā˛ĩ⺁ ā˛Žā˛žā˛§āŗā˛¯ā˛Ž ā˛ĄāŗŒā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛ĩā˛ŋā˛Ģā˛˛ā˛—āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛•ā˛žā˛°ā˛Ŗā˛ĩā˛žā˛¯ā˛ŋ➤⺁.", - "unsupported_error_description": "ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏ā˛Ļ ā˛Ļ⺋➎ ➏➂➭ā˛ĩā˛ŋ➏ā˛ŋā˛Ļāŗ†. ā˛¸ā˛°āŗā˛ĩā˛°āŗ ➅ā˛Ĩā˛ĩā˛ž ā˛¨āŗ†ā˛Ÿāŗ ā˛ĩā˛°āŗā˛•āŗ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†, ➅ā˛Ĩā˛ĩā˛ž ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧāŗā˛°āŗŒā˛¸ā˛°āŗ ➈ ā˛¸āŗā˛ĩ➰⺂ā˛Ēā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛." - }, "memories": "➍⺆➍ā˛Ē⺁➗➺⺁", "memories_check_back_tomorrow": "ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ➍⺆➍ā˛Ē⺁➗➺ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛¨ā˛žā˛ŗāŗ† ā˛Žā˛¤āŗā˛¤āŗ† ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ", "memories_setting_description": "➍ā˛ŋā˛Žāŗā˛Ž ➍⺆➍ā˛Ēāŗā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ➍⺀ā˛ĩ⺁ ā˛¨āŗ‹ā˛Ąāŗā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", @@ -1024,7 +1118,6 @@ "merge": "ā˛ĩā˛ŋ➞⺀➍", "merge_people_limit": "➍⺀ā˛ĩ⺁ ā˛’ā˛Žāŗā˛Žāŗ†ā˛—āŗ† 5 ā˛Žāŗā˛–ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ā˛ĩā˛ŋā˛˛āŗ€ā˛¨ā˛—āŗŠā˛ŗā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁", "merge_people_prompt": "➍⺀ā˛ĩ⺁ ➈ ā˛œā˛¨ā˛°ā˛¨āŗā˛¨āŗ ā˛ĩā˛ŋā˛˛āŗ€ā˛¨ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➈ ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", - "minFaces_description": "ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏➞⺁ ➕➍ā˛ŋā˛ˇāŗā˛  ā˛¸ā˛‚ā˛–āŗā˛¯āŗ†ā˛¯ ā˛Žā˛žā˛¨āŗā˛¯ā˛¤āŗ† ā˛Ēā˛Ąāŗ†ā˛Ļ ā˛Žāŗā˛–ā˛—ā˛ŗāŗ", "minimize": "➕➍ā˛ŋā˛ˇāŗā˛ āŗ€ā˛•ā˛°ā˛ŋ➏ā˛ŋ", "minute": "➍ā˛ŋā˛Žā˛ŋ➎", "missing": "ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -1035,6 +1128,8 @@ "move_off_locked_folder": "ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛šāŗŠā˛°ā˛—āŗ† ➏➰ā˛ŋ➏ā˛ŋ", "move_to_lock_folder_action_prompt": "ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛—āŗ† {count} ➏⺇➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "move_to_locked_folder_confirmation": "➈ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➂ā˛Ļ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ† ā˛Žā˛¤āŗā˛¤āŗ ā˛˛ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛Žā˛žā˛¤āŗā˛° ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļā˛žā˛—ā˛ŋā˛Ļāŗ†", + "multiselect_grid_edit_date_time_err_read_only": "➓ā˛Ļ➞⺁ ā˛Žā˛žā˛¤āŗā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺) ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛ĩā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛žā˛Ļā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "multiselect_grid_edit_gps_err_read_only": "➓ā˛Ļ➞⺁ ā˛Žā˛žā˛¤āŗā˛° ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(➗➺) ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛žā˛Ļā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛, ā˛Ŧā˛ŋā˛Ÿāŗā˛Ÿāŗā˛Ŧā˛ŋā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "name": "ā˛šāŗ†ā˛¸ā˛°āŗ", "network_requirement_photos_upload": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸āŗ†ā˛˛āŗā˛¯āŗā˛˛ā˛žā˛°āŗ ā˛Ąāŗ‡ā˛Ÿā˛žā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋ", "network_requirement_videos_upload": "ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸āŗ†ā˛˛āŗā˛¯āŗā˛˛ā˛žā˛°āŗ ā˛Ąāŗ‡ā˛Ÿā˛žā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋ", @@ -1062,12 +1157,10 @@ "no_shared_albums_message": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛œā˛¨ā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➰➚ā˛ŋ➏ā˛ŋ", "not_in_any_album": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋā˛˛āŗā˛˛", "notes": "➟ā˛ŋā˛Ēāŗā˛Ē➪ā˛ŋ➗➺⺁", - "notification_enabled_list_tile_content": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛—ā˛žā˛—ā˛ŋ ➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧā˛ŗā˛¸āŗā˛¤āŗā˛¤ā˛Ļāŗ†. ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸ā˛žā˛§ā˛¨ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ.", "notification_permission_dialog_content": "➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁, ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ā˛šāŗ‹ā˛—ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ➏⺁ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ.", "notification_permission_list_tile_content": "➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛¨āŗ€ā˛Ąā˛ŋ.", "notifications": "➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ†ā˛—ā˛ŗāŗ", "obtainium_configurator_instructions": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➗ā˛ŋā˛Ÿāŗâ€Œā˛šā˛Ŧāŗâ€Œā˛¨ ā˛Ŧā˛ŋā˛Ąāŗā˛—ā˛Ąāŗ†ā˛¯ā˛ŋ➂ā˛Ļ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛†ā˛‚ā˛Ąāŗā˛°ā˛žā˛¯āŗā˛Ąāŗ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏➞⺁ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ĩ⺀➕➰ā˛ŋ➏➞⺁ ā˛…ā˛Ÿāŗ‡ā˛Ÿā˛ŋ➍ā˛ŋā˛¯ā˛Žāŗ ā˛Ŧ➺➏ā˛ŋ. API ➕⺀➞ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛…ā˛Ÿāŗ‡ā˛Ÿā˛ŋ➍ā˛ŋā˛¯ā˛Žāŗ ā˛•ā˛žā˛¨āŗā˛Ģā˛ŋ➗➰⺇ā˛ļā˛¨āŗ ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏➞⺁ ➰⺂ā˛Ēā˛žā˛‚ā˛¤ā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", - "ocr_body": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛ˆā˛— ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗŠā˛ŗā˛—ā˛ŋ➍ ā˛Ēā˛ āŗā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ➓ā˛Ļāŗā˛¤āŗā˛¤ā˛Ļāŗ†, ➆ā˛Ļāŗā˛Ļ➰ā˛ŋ➂ā˛Ļ ➅ā˛ĩ➰⺁ ā˛šāŗ‡ā˛ŗāŗā˛ĩ ā˛Žāŗ‚ā˛˛ā˛• ➍⺀ā˛ĩ⺁ ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗā˛Ąāŗā˛•ā˛Ŧā˛šāŗā˛Ļ⺁.", "offline": "➆ā˛Ģāŗ ā˛˛āŗˆā˛¨āŗ", "ok": "➏➰ā˛ŋ", "onboarding": "ā˛†ā˛¨āŗ ā˛Ŧāŗ‹ā˛°āŗā˛Ąā˛ŋā˛‚ā˛—āŗ", @@ -1076,7 +1169,6 @@ "onboarding_server_welcome_description": "➍ā˛ŋā˛Žāŗā˛Ž ➍ā˛ŋā˛Ļā˛°āŗā˛ļ➍ā˛ĩā˛¨āŗā˛¨āŗ ➕⺆➞ā˛ĩ⺁ ā˛¸ā˛žā˛Žā˛žā˛¨āŗā˛¯ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗāŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏⺋➪.", "onboarding_theme_description": "➍ā˛ŋā˛Žāŗā˛Ž ➍ā˛ŋā˛Ļā˛°āŗā˛ļā˛¨ā˛•āŗā˛•āŗ† ā˛Ŧā˛Ŗāŗā˛Ŗā˛Ļ ā˛Ĩāŗ€ā˛Žāŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ. ➍⺀ā˛ĩ⺁ ➇ā˛Ļā˛¨āŗā˛¨āŗ ➍➂➤➰ ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁.", "online": "ā˛†ā˛¨āŗ ā˛˛āŗˆā˛¨āŗ", - "open_in_immich_body": "ā˛¸āŗ†ā˛Ÿāŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➇➤➰ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ➗➺ā˛ŋ➂ā˛Ļ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➰⺆➝➞⺁ ā˛†ā˛‚ā˛Ąāŗā˛°ā˛žā˛¯āŗā˛Ąāŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ➍ā˛ŋā˛Žāŗā˛Ž ā˛—āŗā˛¯ā˛žā˛˛ā˛°ā˛ŋ➝➂➤⺆.", "open_in_map_view": "ā˛¨ā˛•āŗā˛ˇāŗ† ā˛ĩāŗ€ā˛•āŗā˛ˇā˛Ŗāŗ†ā˛¯ā˛˛āŗā˛˛ā˛ŋ ➤⺆➰⺆➝ā˛ŋ➰ā˛ŋ", "open_the_search_filters": "ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➰⺆➝ā˛ŋ➰ā˛ŋ", "options": "ā˛†ā˛¯āŗā˛•āŗ†ā˛—ā˛ŗāŗ", @@ -1091,6 +1183,7 @@ "partner_can_access_location": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļ ā˛¸āŗā˛Ĩ➺", "partner_page_empty_message": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛¨āŗā˛¨āŗ‚ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛°ā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ąā˛ŋā˛˛āŗā˛˛.", "partner_page_no_more_users": "➏⺇➰ā˛ŋ➏➞⺁ ā˛‡ā˛¨āŗā˛¨āŗ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛ŋā˛˛āŗā˛˛", + "partner_page_partner_add_failed": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", "partner_page_stop_sharing_content": "{partner} ā˛‡ā˛¨āŗā˛¨āŗ ā˛Žāŗā˛‚ā˛Ļāŗ† ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", "partners": "ā˛Ēā˛žā˛˛āŗā˛Ļā˛žā˛°ā˛°āŗ", "password": "ā˛Ēā˛žā˛¸āŗā˛ĩā˛°āŗā˛Ąāŗ", @@ -1101,9 +1194,14 @@ "pending": "ā˛Ŧā˛žā˛•ā˛ŋ ➉➺ā˛ŋā˛Ļā˛ŋā˛Ļāŗ†", "people": "➜➍➰⺁", "people_feature_description": "➜➍➰ā˛ŋ➂ā˛Ļ ➗⺁➂ā˛Ē⺁ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛°āŗŒā˛¸āŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", + "people_sidebar_description": "ā˛¸āŗˆā˛Ąāŗâ€Œā˛Ŧā˛žā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ➜➍➰⺁ ā˛Žā˛‚ā˛Ŧ ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏ā˛ŋ", "permanent_deletion_warning_setting_description": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏⺁ā˛ĩā˛žā˛— ā˛Žā˛šāŗā˛šā˛°ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ", "permanently_delete_assets_prompt": "➍⺀ā˛ĩ⺁ {count, plural, one {this asset?} other {these # assets?}} ā˛…ā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏➞⺁ ā˛Ŧā˛¯ā˛¸āŗā˛¤āŗā˛¤āŗ€ā˛°ā˛ž? ➇ā˛Ļ⺁ {count, plural, one {it from its} other {them from their}} ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ(➗➺⺁) ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛š ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "permission_empty": "➍ā˛ŋā˛Žāŗā˛Ž ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛–ā˛žā˛˛ā˛ŋā˛¯ā˛žā˛—ā˛ŋ➰ā˛Ŧā˛žā˛°ā˛Ļ⺁", + "permission_onboarding_permission_denied": "ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ➍ā˛ŋā˛°ā˛žā˛•ā˛°ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛Ŧ➺➏➞⺁, ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ€ā˛Ąā˛ŋ.", + "permission_onboarding_permission_granted": "ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛¨āŗ€ā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†! ➍⺀ā˛ĩ⺁ ➏ā˛ŋā˛Ļāŗā˛§ā˛°ā˛žā˛—ā˛ŋā˛Ļāŗā˛Ļ⺀➰ā˛ŋ.", + "permission_onboarding_permission_limited": "ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛¸āŗ€ā˛Žā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†. ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➍ā˛ŋā˛Žāŗā˛Ž ➏➂ā˛Ēāŗ‚ā˛°āŗā˛Ŗ ā˛—āŗā˛¯ā˛žā˛˛ā˛°ā˛ŋ ā˛¸ā˛‚ā˛—āŗā˛°ā˛šā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏➞⺁, ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗâ€Œā˛—ā˛ŗā˛˛āŗā˛˛ā˛ŋ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ€ā˛Ąā˛ŋ.", + "permission_onboarding_request": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏➞⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛—āŗ† ā˛…ā˛¨āŗā˛Žā˛¤ā˛ŋ ā˛Ŧ⺇➕⺁.", "person": "ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ", "photo_shared_all_users": "➍⺀ā˛ĩ⺁ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ąā˛ŋ➰⺁ā˛ĩ➂➤⺆ ā˛•ā˛žā˛Ŗāŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ† ➅ā˛Ĩā˛ĩā˛ž ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧ➺ā˛ŋ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛ŋā˛˛āŗā˛˛.", "photos": "ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ", @@ -1114,7 +1212,6 @@ "play": "ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ", "play_or_pause_video": "ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ā˛ĩā˛ŋā˛°ā˛žā˛Žā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "play_original_video_setting_description": "ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗâ€Œā˛•āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛ŋ➗ā˛ŋ➂➤ ā˛Žāŗ‚ā˛˛ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗ ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗâ€Œā˛—āŗ† ➆ā˛Ļāŗā˛¯ā˛¤āŗ† ā˛¨āŗ€ā˛Ąā˛ŋ. ā˛Žāŗ‚ā˛˛ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ ā˛šāŗŠā˛‚ā˛Ļā˛žā˛Ŗā˛ŋā˛•āŗ†ā˛¯ā˛žā˛—ā˛Ļā˛ŋā˛Ļāŗā˛Ļ➰⺆ ➅ā˛Ļ⺁ ➏➰ā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ➆➗ā˛Ļā˛ŋ➰ā˛Ŧā˛šāŗā˛Ļ⺁.", - "plugin_method_filter_type_description": "➈ ā˛ĩā˛ŋā˛§ā˛žā˛¨ā˛ĩ⺁ ā˛˜ā˛Ÿā˛¨āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗ ā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁ ā˛Žā˛¤āŗā˛¤āŗ ➍➂➤➰ā˛Ļ ā˛šā˛‚ā˛¤ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛“ā˛Ąā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛ˇā˛°ā˛¤āŗā˛¤āŗā˛Ŧā˛Ļāŗā˛§ā˛ĩā˛žā˛—ā˛ŋ ā˛¤ā˛Ąāŗ†ā˛¯ā˛Ŧā˛šāŗā˛Ļ⺁", "port": "ā˛Ēāŗ‹ā˛°āŗā˛Ÿāŗ", "preferences_settings_subtitle": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗâ€Œā˛¨ ➆ā˛Ļāŗā˛¯ā˛¤āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛šā˛ŋ➏ā˛ŋ", "preset": "ā˛ŽāŗŠā˛Ļ➞⺇", @@ -1122,6 +1219,7 @@ "previous": "ā˛šā˛ŋ➂ā˛Ļā˛ŋ➍", "primary": "ā˛Ēāŗā˛°ā˛žā˛Ĩā˛Žā˛ŋ➕", "privacy": "ā˛—āŗŒā˛Ēāŗā˛¯ā˛¤āŗ†", + "profile_drawer_client_server_up_to_date": "ā˛•āŗā˛˛āŗˆā˛‚ā˛Ÿāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛°āŗā˛ĩā˛°āŗ ➍ā˛ĩāŗ€ā˛•āŗƒā˛¤ā˛ĩā˛žā˛—ā˛ŋā˛ĩāŗ†", "profile_drawer_readonly_mode": "➓ā˛Ļ➞⺁-ā˛Žā˛žā˛¤āŗā˛° ā˛Žāŗ‹ā˛Ąāŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†. ➍ā˛ŋā˛°āŗā˛—ā˛Žā˛ŋ➏➞⺁ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛° ➅ā˛ĩā˛¤ā˛žā˛°āŗ ā˛ā˛•ā˛žā˛¨āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ļāŗ€ā˛°āŗā˛˜ā˛•ā˛žā˛˛ ā˛’ā˛¤āŗā˛¤ā˛ŋ➰ā˛ŋ.", "profile_image_of_user": "{user} ➰ ā˛Ēāŗā˛°āŗŠā˛Ģāŗˆā˛˛āŗ ➚ā˛ŋā˛¤āŗā˛°", "purchase_account_info": "ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➗", @@ -1133,6 +1231,7 @@ "purchase_failed_activation": "ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†! ➏➰ā˛ŋā˛¯ā˛žā˛Ļ ā˛‰ā˛¤āŗā˛Ēā˛¨āŗā˛¨ ➕⺀➞ā˛ŋā˛—ā˛žā˛—ā˛ŋ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛‡ā˛Žāŗ‡ā˛˛āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ē➰ā˛ŋā˛ļ⺀➞ā˛ŋ➏ā˛ŋ!", "purchase_individual_title": "ā˛ĩāŗˆā˛¯ā˛•āŗā˛¤ā˛ŋ➕", "purchase_input_suggestion": "ā˛‰ā˛¤āŗā˛Ēā˛¨āŗā˛¨ ➕⺀➞ā˛ŋ ➇ā˛Ļ⺆➝⺇? ➕⺆➺➗⺆ ➕⺀➞ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", + "purchase_license_subtitle": "➏⺇ā˛ĩ⺆➝ ➍ā˛ŋ➰➂➤➰ ➅➭ā˛ŋā˛ĩ⺃ā˛Ļāŗā˛§ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏➞⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛…ā˛¨āŗā˛¨āŗ ➖➰⺀ā˛Ļā˛ŋ➏ā˛ŋ", "purchase_panel_info_1": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ➍ā˛ŋā˛°āŗā˛Žā˛žā˛Ŗā˛ĩ⺁ ā˛¸ā˛žā˛•ā˛ˇāŗā˛Ÿāŗ ā˛¸ā˛Žā˛¯ ā˛Žā˛¤āŗā˛¤āŗ ā˛ļāŗā˛°ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛¤āŗā˛¤ā˛Ļāŗ†, ā˛Žā˛¤āŗā˛¤āŗ ➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛Ļā˛ˇāŗā˛Ÿāŗ ā˛‰ā˛¤āŗā˛¤ā˛Žā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ ā˛¨ā˛žā˛ĩ⺁ ā˛Ēāŗ‚ā˛°āŗā˛Ŗ ā˛¸ā˛Žā˛¯ā˛Ļ ā˛Žā˛‚ā˛œā˛ŋ➍ā˛ŋā˛¯ā˛°āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛Ļāŗā˛Ļāŗ‡ā˛ĩāŗ†. ➓ā˛Ēā˛¨āŗ-ā˛¸āŗ‹ā˛°āŗā˛¸āŗ ā˛¸ā˛žā˛Ģāŗā˛Ÿāŗâ€Œā˛ĩāŗ‡ā˛°āŗ ā˛Žā˛¤āŗā˛¤āŗ ➍⺈➤ā˛ŋ➕ ā˛ĩāŗā˛¯ā˛ĩā˛šā˛žā˛° ā˛…ā˛­āŗā˛¯ā˛žā˛¸ā˛—ā˛ŗāŗ ā˛Ąāŗ†ā˛ĩ➞ā˛Ēā˛°āŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ā˛¸āŗā˛¸āŗā˛Ĩā˛ŋ➰ ➆ā˛Ļā˛žā˛¯ā˛Ļ ā˛Žāŗ‚ā˛˛ā˛ĩā˛žā˛—āŗā˛ĩ⺁ā˛Ļ⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛ļ⺋➎➪⺆➝ ā˛•āŗā˛˛āŗŒā˛Ąāŗ ➏⺇ā˛ĩ⺆➗➺ā˛ŋ➗⺆ ➍ā˛ŋ➜ā˛ĩā˛žā˛Ļ ā˛Ēā˛°āŗā˛¯ā˛žā˛¯ā˛—ā˛ŗāŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛—āŗŒā˛Ēāŗā˛¯ā˛¤āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛—āŗŒā˛°ā˛ĩā˛ŋ➏⺁ā˛ĩ ā˛Ē➰ā˛ŋ➏➰ ā˛ĩāŗā˛¯ā˛ĩā˛¸āŗā˛Ĩāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļ⺁ ā˛¨ā˛Žāŗā˛Ž ā˛§āŗā˛¯āŗ‡ā˛¯ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†.", "purchase_panel_info_2": "ā˛¨ā˛žā˛ĩ⺁ ā˛Ēāŗ‡ā˛ĩā˛žā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏⺇➰ā˛ŋ➏ā˛Ļā˛ŋ➰➞⺁ ā˛Ŧā˛Ļāŗā˛§ā˛°ā˛žā˛—ā˛ŋ➰⺁ā˛ĩ⺁ā˛Ļ➰ā˛ŋ➂ā˛Ļ, ➈ ➖➰⺀ā˛Ļā˛ŋ➝⺁ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ➍ā˛ŋā˛Žā˛—āŗ† ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛šāŗ†ā˛šāŗā˛šāŗā˛ĩ➰ā˛ŋ ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ€ā˛Ąāŗā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛. ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛¨ ā˛¨ā˛Ąāŗ†ā˛¯āŗā˛¤āŗā˛¤ā˛ŋ➰⺁ā˛ĩ ➅➭ā˛ŋā˛ĩ⺃ā˛Ļāŗā˛§ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏➞⺁ ā˛¨ā˛žā˛ĩ⺁ ➍ā˛ŋā˛Žāŗā˛Žā˛‚ā˛¤ā˛š ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➅ā˛ĩ➞➂ā˛Ŧā˛ŋ➏ā˛ŋā˛Ļāŗā˛Ļāŗ‡ā˛ĩāŗ†.", "purchase_remove_product_key_prompt": "➍⺀ā˛ĩ⺁ ā˛‰ā˛¤āŗā˛Ēā˛¨āŗā˛¨ ➕⺀➞ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", @@ -1145,10 +1244,9 @@ "reassign": "ā˛Žā˛°āŗā˛šā˛‚ā˛šāŗā˛ĩā˛ŋ➕⺆", "reassigned_assets_to_existing_person": "{count, plural, one {# ā˛†ā˛¸āŗā˛¤ā˛ŋ} other {# ā˛†ā˛¸āŗā˛¤ā˛ŋ➗➺⺁}} ā˛…ā˛¨āŗā˛¨āŗ {name, select, null {➒➂ā˛Ļ⺁ ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ} other {{name}}} ➗⺆ ā˛Žā˛°āŗ ➍ā˛ŋā˛¯āŗ‹ā˛œā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "reassing_hint": "ā˛†ā˛¯āŗā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ➗⺆ ➍ā˛ŋā˛¯āŗ‹ā˛œā˛ŋ➏ā˛ŋ", - "recently_added_body": "ā˛Žāŗ€ā˛¸ā˛˛ā˛žā˛Ļ ā˛Ē⺁➟ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➍⺀ā˛ĩ⺁ ā˛‡ā˛¤āŗā˛¤āŗ€ā˛šāŗ†ā˛—āŗ† ➏⺇➰ā˛ŋ➏ā˛ŋā˛Ļ ā˛Žā˛˛āŗā˛˛ā˛Ļā˛•āŗā˛•āŗ‚ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛šāŗ‹ā˛—āŗ.", - "recently_added_description": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļā˛žā˛— ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩā˛ŋā˛‚ā˛—ā˛Ąā˛ŋ➏ā˛ŋā˛Ļā˛žā˛— ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛°āŗŒā˛¸āŗ ā˛Žā˛žā˛Ąā˛ŋ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ", "refresh": "➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ", "refreshed": "➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "refreshes_every_file": "ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛Žā˛¤āŗā˛¤āŗ ā˛šāŗŠā˛¸ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ē⺁➍➃ ➓ā˛Ļāŗā˛¤āŗā˛¤ā˛Ļāŗ†", "remove": "➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", "remove_assets_album_confirmation": "➍⺀ā˛ĩ⺁ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ {count, plural, one {# asset} other {# assets}} ā˛…ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "remove_assets_shared_link_confirmation": "➈ ā˛šā˛‚ā˛šā˛ŋ➕⺆➝ ➞ā˛ŋā˛‚ā˛•āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ {count, plural, one {# asset} other {# assets}} ā˛…ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛āŗ ➍⺀ā˛ĩ⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", @@ -1159,8 +1257,12 @@ "remove_from_shared_link": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ➞ā˛ŋā˛‚ā˛•āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", "remove_photo_from_memory": "➈ ➍⺆➍ā˛Ēā˛ŋ➍ā˛ŋ➂ā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", "removed_api_key": "➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛Ļ API ➕⺀: {name}", + "removed_photo_from_memory": "➍⺆➍ā˛Ēā˛ŋ➍ā˛ŋ➂ā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "rename": "ā˛Žā˛°āŗā˛šāŗ†ā˛¸ā˛°ā˛ŋ➏ā˛ŋ", + "repair": "ā˛Ļāŗā˛°ā˛¸āŗā˛¤ā˛ŋ", + "repair_no_results_message": "ā˛Ÿāŗā˛°āŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛Ļ ā˛Žā˛¤āŗā˛¤āŗ ā˛•ā˛žā˛Ŗāŗ†ā˛¯ā˛žā˛Ļ ā˛Ģāŗˆā˛˛āŗâ€Œā˛—ā˛ŗāŗ ā˛‡ā˛˛āŗā˛˛ā˛ŋ ā˛•ā˛žā˛Ŗā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛¤āŗā˛¤ā˛ĩāŗ†", "repository": "➰⺆ā˛Ē⺊➏ā˛ŋ➟➰ā˛ŋ", + "require_user_to_change_password_on_first_login": "ā˛ŽāŗŠā˛Ļ➞ ā˛˛ā˛žā˛—ā˛ŋā˛¨āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°āŗ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛Ŧāŗ‡ā˛•ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "reset": "ā˛Žā˛°āŗā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", "reset_pin_code_description": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ➍⺀ā˛ĩ⺁ ā˛Žā˛°āŗ†ā˛¤ā˛ŋā˛Ļāŗā˛Ļ➰⺆, ➅ā˛Ļā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏➞⺁ ➍⺀ā˛ĩ⺁ ā˛¸ā˛°āŗā˛ĩā˛°āŗ ➍ā˛ŋā˛°āŗā˛ĩā˛žā˛šā˛•ā˛°ā˛¨āŗā˛¨āŗ ➏➂ā˛Ēā˛°āŗā˛•ā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁", "reset_pin_code_with_password": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗâ€Œā˛¨āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➍⺀ā˛ĩ⺁ ā˛¯ā˛žā˛ĩā˛žā˛—ā˛˛āŗ‚ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁", @@ -1172,18 +1274,19 @@ "resume": "ā˛Ēāŗā˛¨ā˛°ā˛žā˛°ā˛‚ā˛­", "role": "ā˛Ēā˛žā˛¤āŗā˛°", "scaffold_body_error_unrecoverable": "➏➰ā˛ŋā˛Ēā˛Ąā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛Ļ ā˛Ļ⺋➎ ➏➂➭ā˛ĩā˛ŋ➏ā˛ŋā˛Ļāŗ†. ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛Ļ⺋➎ā˛ĩā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ąā˛ŋā˛¸āŗā˛•ā˛žā˛°āŗā˛Ąāŗ ➅ā˛Ĩā˛ĩā˛ž ➗ā˛ŋā˛Ÿāŗâ€Œā˛šā˛Ŧāŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛Ÿāŗā˛°āŗ‡ā˛¸āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ÿāŗā˛¯ā˛žā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ ➇ā˛Ļ➰ā˛ŋ➂ā˛Ļ ā˛¨ā˛žā˛ĩ⺁ ā˛¸ā˛šā˛žā˛¯ ā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁. ā˛¸ā˛˛ā˛šāŗ† ā˛¨āŗ€ā˛Ąā˛ŋā˛Ļ➰⺆, ➍⺀ā˛ĩ⺁ ➕⺆➺➗ā˛ŋ➍ ➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗ ā˛Ąāŗ‡ā˛Ÿā˛žā˛ĩā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁.", - "screencast_mode_description": "ā˛Ē➰ā˛Ļ⺆➝ ā˛Žāŗ‡ā˛˛āŗ† ➕⺀ā˛Ŧāŗ‹ā˛°āŗā˛Ąāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ŽāŗŒā˛¸āŗ ➈ā˛ĩāŗ†ā˛‚ā˛Ÿāŗ ā˛¸āŗ‚ā˛šā˛•ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ", "search_by_description_example": "ā˛¸ā˛žā˛Ēā˛žā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛Ēā˛žā˛Ļā˛¯ā˛žā˛¤āŗā˛°āŗ†ā˛¯ ā˛Ļā˛ŋ➍", "search_by_filename": "ā˛Ģāŗˆā˛˛āŗ ā˛šāŗ†ā˛¸ā˛°āŗ ➅ā˛Ĩā˛ĩā˛ž ā˛ĩā˛ŋā˛¸āŗā˛¤ā˛°ā˛Ŗāŗ†ā˛¯ ā˛Žāŗ‚ā˛˛ā˛• ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "search_by_filename_example": "➅➂ā˛Ļ➰⺆ IMG_1234.JPG ➅ā˛Ĩā˛ĩā˛ž PNG", - "search_by_full_path": "ā˛Ēāŗ‚ā˛°āŗā˛Ŗ ā˛Žā˛žā˛°āŗā˛— ➅ā˛Ĩā˛ĩā˛ž ā˛Ģāŗ‹ā˛˛āŗā˛Ąā˛°āŗ ā˛Žāŗ‚ā˛˛ā˛• ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "search_filter_date_title": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛ļāŗā˛°āŗ‡ā˛Ŗā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "search_filter_filename": "ā˛Ģāŗˆā˛˛āŗ ā˛šāŗ†ā˛¸ā˛°ā˛ŋ➍ ā˛Žāŗ‚ā˛˛ā˛• ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "search_for_existing_person": "ā˛…ā˛¸āŗā˛¤ā˛ŋā˛¤āŗā˛ĩā˛Ļā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "search_no_people_named": "\"{name}\" ā˛šāŗ†ā˛¸ā˛°ā˛ŋ➍ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ➜➍➰ā˛ŋā˛˛āŗā˛˛", "search_no_result": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛Ģ➞ā˛ŋā˛¤ā˛žā˛‚ā˛ļ➗➺⺁ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛˛āŗā˛˛, ā˛Ŧ⺇➰⺆ ā˛šāŗā˛Ąāŗā˛•ā˛žā˛Ÿ ā˛Ēā˛Ļ ➅ā˛Ĩā˛ĩā˛ž ā˛¸ā˛‚ā˛¯āŗ‹ā˛œā˛¨āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛¯ā˛¤āŗā˛¨ā˛ŋ➏ā˛ŋ", + "search_page_no_objects": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛ĩā˛¸āŗā˛¤āŗā˛—ā˛ŗ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", + "search_page_no_places": "ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛¸āŗā˛Ĩ➺➗➺ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛", "search_page_search_photos_videos": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗā˛Ąāŗā˛•ā˛ŋ", "select_person_to_tag": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", + "select_user_for_sharing_page_err_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➰➚ā˛ŋ➏➞⺁ ā˛ĩā˛ŋā˛Ģ➞ā˛ĩā˛žā˛—ā˛ŋā˛Ļāŗ†", "server_restarting_description": "➈ ā˛Ē⺁➟ā˛ĩ⺁ ā˛•āŗā˛ˇā˛Ŗā˛Žā˛žā˛¤āŗā˛°ā˛Ļā˛˛āŗā˛˛ā˛ŋ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛†ā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", "set_as_album_cover": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➕ā˛ĩā˛°āŗ ➆➗ā˛ŋ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", "set_as_featured_photo": "ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ ā˛Žā˛‚ā˛Ļ⺁ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", @@ -1195,16 +1298,22 @@ "setting_image_viewer_original_subtitle": "ā˛Žāŗ‚ā˛˛ ā˛Ēāŗ‚ā˛°āŗā˛Ŗ-ā˛°āŗ†ā˛¸ā˛˛āŗā˛¯āŗ‚ā˛ļā˛¨āŗ ➚ā˛ŋā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ (ā˛ĻāŗŠā˛Ąāŗā˛Ąā˛Ļ⺁!). ā˛Ąāŗ‡ā˛Ÿā˛ž ā˛Ŧā˛ŗā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛•ā˛Ąā˛ŋā˛Žāŗ† ā˛Žā˛žā˛Ąā˛˛āŗ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ (ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛¸ā˛žā˛§ā˛¨ā˛Ļ ā˛¸ā˛‚ā˛—āŗā˛°ā˛š ā˛Žā˛°ā˛Ąā˛°ā˛˛āŗā˛˛āŗ‚).", "setting_image_viewer_preview_subtitle": "ā˛Žā˛§āŗā˛¯ā˛Ž ā˛°āŗ†ā˛¸ā˛˛āŗā˛¯āŗ‚ā˛ļā˛¨āŗ ➚ā˛ŋā˛¤āŗā˛°ā˛ĩā˛¨āŗā˛¨āŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ. ā˛Žāŗ‚ā˛˛ā˛ĩā˛¨āŗā˛¨āŗ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➅ā˛Ĩā˛ĩā˛ž ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛žā˛¤āŗā˛° ā˛Ŧ➺➏➞⺁ ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ.", "setting_languages_subtitle": "➅ā˛Ēāŗā˛˛ā˛ŋ➕⺇ā˛ļā˛¨āŗâ€Œā˛¨ ā˛­ā˛žā˛ˇāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏ā˛ŋ", + "setting_notifications_notify_failures_grace_period": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛ĩ⺈ā˛Ģā˛˛āŗā˛¯ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¸āŗ‚ā˛šā˛ŋ➏ā˛ŋ: {duration}", + "setting_notifications_single_progress_subtitle": "ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛ŋ➍ ā˛ĩā˛ŋā˛ĩ➰ā˛ĩā˛žā˛Ļ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Ēāŗā˛°ā˛—ā˛¤ā˛ŋ ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋ", + "setting_notifications_single_progress_title": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛ĩā˛ŋā˛ĩ➰ ā˛Ēāŗā˛°ā˛—ā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ", "setting_notifications_subtitle": "➍ā˛ŋā˛Žāŗā˛Ž ➅➧ā˛ŋā˛¸āŗ‚ā˛šā˛¨āŗ† ➆ā˛Ļāŗā˛¯ā˛¤āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", + "setting_notifications_total_progress_subtitle": "ā˛’ā˛Ÿāŗā˛Ÿā˛žā˛°āŗ† ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Ēāŗā˛°ā˛—ā˛¤ā˛ŋ (ā˛Žāŗā˛—ā˛ŋā˛Ļā˛ŋā˛Ļāŗ†/ā˛’ā˛Ÿāŗā˛Ÿāŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ)", + "setting_notifications_total_progress_title": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛’ā˛Ÿāŗā˛Ÿāŗ ā˛Ēāŗā˛°ā˛—ā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ", "setting_video_viewer_auto_play_subtitle": "ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ➤⺆➰⺆ā˛Ļā˛žā˛— ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ēāŗā˛˛āŗ‡ ➆➗➞⺁ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏ā˛ŋ", "setting_video_viewer_original_video_subtitle": "ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ā˛ĩā˛¨āŗā˛¨āŗ ā˛¸āŗā˛Ÿāŗā˛°āŗ€ā˛Žāŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛—, ā˛Ÿāŗā˛°ā˛žā˛¨āŗā˛¸āŗâ€Œā˛•āŗ‹ā˛Ąāŗ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗā˛Ļ➰⺂ ā˛¸ā˛š ā˛Žāŗ‚ā˛˛ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ. ā˛Ŧā˛Ģ➰ā˛ŋā˛‚ā˛—āŗâ€Œā˛—āŗ† ā˛•ā˛žā˛°ā˛Ŗā˛ĩā˛žā˛—ā˛Ŧā˛šāŗā˛Ļ⺁. ➈ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛˛āŗ†ā˛•āŗā˛•ā˛ŋ➏ā˛Ļāŗ† ā˛¸āŗā˛Ĩ➺⺀➝ā˛ĩā˛žā˛—ā˛ŋ ā˛˛ā˛­āŗā˛¯ā˛ĩā˛ŋ➰⺁ā˛ĩ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Žāŗ‚ā˛˛ ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿā˛Ļā˛˛āŗā˛˛ā˛ŋ ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†.", - "share_quality_body": "➍⺀ā˛ĩ⺁ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛ĩ ā˛ŽāŗŠā˛Ļ➞⺁ ➚ā˛ŋā˛¤āŗā˛°ā˛Ļ ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛˛āŗ ➎⺇➰⺁ ā˛—āŗā˛‚ā˛Ąā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛’ā˛¤āŗā˛¤ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛šā˛ŋā˛Ąā˛ŋā˛Ļāŗā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ.", + "settings_require_restart": "➈ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛ĩ➝ā˛ŋ➏➞⺁ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏ā˛ŋ", "shared_album_activity_remove_content": "➍⺀ā˛ĩ⺁ ➈ ➚➟⺁ā˛ĩ➟ā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "shared_album_section_people_action_error": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛¤āŗŠā˛°āŗ†ā˛¯āŗā˛ĩā˛žā˛—/➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•āŗā˛ĩā˛žā˛— ā˛Ļ⺋➎ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛Ļāŗ†", + "shared_album_section_people_action_leave": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", + "shared_album_section_people_action_remove_user": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", "shared_intent_upload_button_progress_text": "{current} / {total} ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "shared_link_create_error": "ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ➞ā˛ŋā˛‚ā˛•āŗ ➰➚ā˛ŋ➏⺁ā˛ĩā˛žā˛— ā˛Ļ⺋➎ ā˛•ā˛‚ā˛Ąāŗā˛Ŧ➂ā˛Ļā˛ŋā˛Ļāŗ†", "shared_link_custom_url_description": "ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ URL ā˛¨āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➈ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏ā˛ŋ", - "shared_link_custom_url_warning": "ā˛Žā˛šāŗā˛šā˛°ā˛ŋ➕⺆: ā˛Ģā˛žā˛°āŗā˛ĩā˛°āŗā˛Ąāŗ ā˛¸āŗā˛˛āŗā˛¯ā˛žā˛ˇāŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➰⺁ā˛ĩ ā˛•ā˛¸āŗā˛Ÿā˛Žāŗ URL ā˛šāŗ†ā˛¸ā˛°āŗ ➍⺀ā˛ĩ⺁ ➍ā˛ŋā˛°āŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋā˛Ļ➂➤⺆ ā˛ĩā˛°āŗā˛¤ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļā˛ŋā˛˛āŗā˛˛.", "shared_link_edit_description_hint": "ā˛šā˛‚ā˛šā˛ŋ➕⺆ ā˛ĩā˛ŋā˛ĩā˛°ā˛Ŗāŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", "shared_link_edit_password_hint": "ā˛šā˛‚ā˛šā˛ŋ➕⺆ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", "shared_link_error_server_url_fetch": "ā˛¸ā˛°āŗā˛ĩā˛°āŗ url ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēā˛Ąāŗ†ā˛¯ā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛", @@ -1219,24 +1328,26 @@ "shared_link_password_description": "➈ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛ĩāŗ‡ā˛ļā˛ŋ➏➞⺁ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛Ļāŗ†", "shared_links_description": "➞ā˛ŋā˛‚ā˛•āŗ ā˛Žāŗ‚ā˛˛ā˛• ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ", "sharing_enter_password": "➈ ā˛Ē⺁➟ā˛ĩā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏➞⺁ ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛Ēā˛žā˛¸āŗâ€Œā˛ĩā˛°āŗā˛Ąāŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ.", + "sharing_page_description": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛œā˛¨ā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗā˛˛āŗ ā˛šā˛‚ā˛šā˛ŋā˛Ļ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➰➚ā˛ŋ➏ā˛ŋ.", + "sharing_sidebar_description": "ā˛¸āŗˆā˛Ąāŗâ€Œā˛Ŧā˛žā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛šā˛‚ā˛šā˛ŋ➕⺆➗⺆ ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏ā˛ŋ", "shift_to_permanent_delete": "ā˛†ā˛¸āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋ➏➞⺁ ⇧ ā˛’ā˛¤āŗā˛¤ā˛ŋ➰ā˛ŋ", "show_and_hide_people": "ā˛œā˛¨ā˛°ā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ā˛Žā˛°āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "show_in_timeline_setting_description": "➈ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛° ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ÿāŗˆā˛Žāŗâ€Œā˛˛āŗˆā˛¨āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ➤⺋➰ā˛ŋ➏ā˛ŋ", "show_or_hide_info": "ā˛Žā˛žā˛šā˛ŋ➤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ➤⺋➰ā˛ŋ➏ā˛ŋ ➅ā˛Ĩā˛ĩā˛ž ā˛Žā˛°āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "show_supporter_badge_description": "ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➗➰ ā˛Ŧāŗā˛¯ā˛žā˛Ąāŗā˛œāŗ ➤⺋➰ā˛ŋ➏ā˛ŋ", "sidebar_display_description": "ā˛¸āŗˆā˛Ąāŗâ€Œā˛Ŧā˛žā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛Ŗāŗ†ā˛—āŗ† ➞ā˛ŋā˛‚ā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛Ļā˛°āŗā˛ļā˛ŋ➏ā˛ŋ", - "slideshow_body": "➕⺁➺ā˛ŋā˛¤āŗā˛•āŗŠā˛ŗāŗā˛ŗā˛ŋ ā˛Žā˛¤āŗā˛¤āŗ ➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Ēāŗ‚ā˛°āŗā˛Ŗ-ā˛Ē➰ā˛Ļ⺆➝ ā˛¸āŗā˛˛āŗˆā˛Ąāŗ ā˛ļāŗ‹ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛†ā˛Ąāŗā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛ŋ.", "slideshow_repeat_description": "ā˛¸āŗā˛˛āŗˆā˛Ąāŗâ€Œā˛ļāŗ‹ ā˛•āŗŠā˛¨āŗ†ā˛—āŗŠā˛‚ā˛Ąā˛žā˛— ā˛†ā˛°ā˛‚ā˛­ā˛•āŗā˛•āŗ† ā˛šā˛ŋ➂➤ā˛ŋ➰⺁➗ā˛ŋ", - "some_assets_already_have_a_location_warning": "ā˛†ā˛¯āŗā˛Ļ ➕⺆➞ā˛ĩ⺁ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ ā˛ˆā˛—ā˛žā˛—ā˛˛āŗ‡ ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛ĩāŗ†", "sort_created": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ➰➚ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "sort_items": "ā˛ĩā˛¸āŗā˛¤āŗā˛—ā˛ŗ ā˛¸ā˛‚ā˛–āŗā˛¯āŗ†", "sort_modified": "ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛• ā˛Žā˛žā˛°āŗā˛Ēā˛Ąā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "sort_newest": "ā˛šāŗŠā˛¸ ā˛Ģāŗ‹ā˛Ÿāŗ‹", "sort_oldest": "ā˛šā˛ŗāŗ†ā˛¯ ā˛Ģāŗ‹ā˛Ÿāŗ‹", "sort_people_by_similarity": "ā˛šāŗ‹ā˛˛ā˛ŋ➕⺆➝ ā˛†ā˛§ā˛žā˛°ā˛Ļ ā˛Žāŗ‡ā˛˛āŗ† ā˛œā˛¨ā˛°ā˛¨āŗā˛¨āŗ ā˛ĩā˛ŋā˛‚ā˛—ā˛Ąā˛ŋ➏ā˛ŋ", "sort_recent": "ā˛¤āŗ€ā˛°ā˛ž ā˛‡ā˛¤āŗā˛¤āŗ€ā˛šā˛ŋ➍ ā˛Ģāŗ‹ā˛Ÿāŗ‹", "sort_title": "ā˛ļāŗ€ā˛°āŗā˛ˇā˛ŋ➕⺆", "stack": "ā˛¸āŗā˛Ÿā˛žā˛•āŗ", "stack_duplicates": "ā˛¸āŗā˛Ÿā˛žā˛•āŗ ➍➕➞⺁➗➺⺁", + "stack_select_one_photo": "ā˛¸āŗā˛Ÿāŗā˛¯ā˛žā˛•āŗâ€Œā˛—ā˛žā˛—ā˛ŋ ➒➂ā˛Ļ⺁ ā˛Žāŗā˛–āŗā˛¯ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛ĩā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "stack_selected_photos": "ā˛†ā˛¯āŗā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛œāŗ‹ā˛Ąā˛ŋ➏ā˛ŋ", "stacktrace": "ā˛¸āŗā˛Ÿā˛žā˛•āŗā˛Ÿāŗā˛°āŗ‡ā˛¸āŗ", "start": "ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­", @@ -1244,7 +1355,6 @@ "start_date_before_end_date": "➆➰➂➭ā˛Ļ ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛ĩ⺁ ➅➂➤ā˛ŋā˛Ž ā˛Ļā˛ŋā˛¨ā˛žā˛‚ā˛•ā˛•āŗā˛•ā˛ŋ➂➤ ā˛ŽāŗŠā˛Ļ➞⺁ ➇➰ā˛Ŧ⺇➕⺁", "state": "ā˛°ā˛žā˛œāŗā˛¯", "status": "ā˛¸āŗā˛Ĩā˛ŋ➤ā˛ŋ", - "step_delete_confirm": "➈ ā˛šā˛‚ā˛¤ā˛ĩā˛¨āŗā˛¨āŗ ➅➺ā˛ŋ➏➞⺁ ➍⺀ā˛ĩ⺁ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "stop_casting": "ā˛Ŧā˛ŋā˛¤āŗā˛¤ā˛°ā˛ŋ➏⺁ā˛ĩā˛ŋā˛•āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛˛āŗā˛˛ā˛ŋ➏ā˛ŋ", "stop_motion_photo": "ā˛šā˛˛ā˛¨āŗ†ā˛¯ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛ĩā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛˛āŗā˛˛ā˛ŋ➏ā˛ŋ", "stop_photo_sharing": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛ĩ⺁ā˛Ļā˛¨āŗā˛¨āŗ ➍ā˛ŋā˛˛āŗā˛˛ā˛ŋ➏⺁ā˛ĩ⺁ā˛Ļāŗ‡?", @@ -1264,6 +1374,7 @@ "swap_merge_direction": "ā˛¸āŗā˛ĩā˛žā˛Ēāŗ ā˛ĩā˛ŋ➞⺀➍ ➍ā˛ŋā˛°āŗā˛Ļāŗ‡ā˛ļ➍", "sync": "➏ā˛ŋā˛‚ā˛•āŗ", "sync_albums": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➏ā˛ŋā˛‚ā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ", + "sync_albums_manual_subtitle": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Žā˛˛āŗā˛˛ā˛ž ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗâ€Œā˛—ā˛ŗā˛ŋ➗⺆ ➏ā˛ŋā˛‚ā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ", "sync_local": "ā˛¸āŗā˛Ĩ➺⺀➝ ➏ā˛ŋā˛‚ā˛•āŗ ā˛Žā˛žā˛Ąā˛ŋ", "sync_remote": "➏ā˛ŋā˛‚ā˛•āŗ ➰ā˛ŋā˛Žāŗ‹ā˛Ÿāŗ", "sync_status": "➏ā˛ŋā˛‚ā˛•āŗ ā˛¸āŗā˛Ĩā˛ŋ➤ā˛ŋ", @@ -1272,6 +1383,7 @@ "tag": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛Žā˛žā˛Ąā˛ŋ", "tag_assets": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗāŗ", "tag_feature_description": "ā˛¤ā˛žā˛°āŗā˛•ā˛ŋ➕ ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ā˛ĩā˛ŋ➎➝➗➺ ā˛Žāŗ‚ā˛˛ā˛• ➗⺁➂ā˛Ē⺁ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛Ŧāŗā˛°āŗŒā˛¸āŗ ā˛Žā˛žā˛Ąāŗā˛ĩ⺁ā˛Ļ⺁", + "tag_not_found_question": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ➏ā˛ŋā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛ā˛ĩāŗ‡? Create a new tag.", "tag_people": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗ ➜➍➰⺁", "tags": "ā˛Ÿāŗā˛¯ā˛žā˛—āŗā˛—ā˛ŗāŗ", "tap_to_run_job": "➕⺆➞➏ā˛ĩā˛¨āŗā˛¨āŗ ā˛šā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁ ā˛Ÿāŗā˛¯ā˛žā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ", @@ -1284,10 +1396,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ā˛Ēāŗā˛°ā˛¤ā˛ŋ ā˛¸ā˛žā˛˛ā˛ŋā˛¨ā˛˛āŗā˛˛ā˛ŋ➰⺁ā˛ĩ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗ ā˛¸ā˛‚ā˛–āŗā˛¯āŗ† ({count})", "theme_setting_colorful_interface_subtitle": "ā˛šā˛ŋā˛¨āŗā˛¨āŗ†ā˛˛āŗ† ā˛Žāŗ‡ā˛˛āŗā˛Žāŗˆā˛—ā˛ŗā˛ŋ➗⺆ ā˛Ēāŗā˛°ā˛žā˛Ĩā˛Žā˛ŋ➕ ā˛Ŧā˛Ŗāŗā˛Ŗā˛ĩā˛¨āŗā˛¨āŗ ā˛…ā˛¨āŗā˛ĩ➝ā˛ŋ➏ā˛ŋ.", "theme_setting_colorful_interface_title": "ā˛ĩā˛°āŗā˛Ŗā˛°ā˛‚ā˛œā˛ŋ➤ ā˛‡ā˛‚ā˛Ÿā˛°āŗā˛Ģāŗ‡ā˛¸āŗ", + "theme_setting_image_viewer_quality_subtitle": "ā˛ĩā˛ŋā˛ĩ➰ ➚ā˛ŋā˛¤āŗā˛° ā˛ĩāŗ€ā˛•āŗā˛ˇā˛•ā˛° ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋ➏ā˛ŋ", + "theme_setting_image_viewer_quality_title": "➚ā˛ŋā˛¤āŗā˛° ā˛ĩāŗ€ā˛•āŗā˛ˇā˛•ā˛° ā˛—āŗā˛Ŗā˛Žā˛Ÿāŗā˛Ÿ", "theme_setting_primary_color_subtitle": "ā˛Ēāŗā˛°ā˛žā˛Ĩā˛Žā˛ŋ➕ ā˛•āŗā˛°ā˛ŋ➝⺆➗➺⺁ ā˛Žā˛¤āŗā˛¤āŗ ā˛‰ā˛šāŗā˛šā˛žā˛°ā˛Ŗāŗ†ā˛—ā˛ŗā˛ŋ➗⺆ ā˛Ŧā˛Ŗāŗā˛Ŗā˛ĩā˛¨āŗā˛¨āŗ ➆➰ā˛ŋ➏ā˛ŋ.", "theme_setting_primary_color_title": "ā˛Ēāŗā˛°ā˛žā˛Ĩā˛Žā˛ŋ➕ ā˛Ŧā˛Ŗāŗā˛Ŗ", "theme_setting_system_primary_color_title": "➏ā˛ŋā˛¸āŗā˛Ÿā˛Žāŗ ā˛Ŧā˛Ŗāŗā˛Ŗā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛ŋ", "theme_setting_system_theme_switch": "ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋ➤ (➏ā˛ŋā˛¸āŗā˛Ÿā˛‚ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ➅➍⺁➏➰ā˛ŋ➏ā˛ŋ)", + "theme_setting_theme_subtitle": "ā˛†āŗā˛¯ā˛Ēāŗâ€Œā˛¨ ā˛Ĩāŗ€ā˛Žāŗ ā˛¸āŗ†ā˛Ÿāŗā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ➆➰ā˛ŋ➏ā˛ŋ", + "theme_setting_three_stage_loading_subtitle": "ā˛Žāŗ‚ā˛°āŗ-ā˛šā˛‚ā˛¤ā˛Ļ ā˛˛āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛•ā˛žā˛°āŗā˛¯ā˛•āŗā˛ˇā˛Žā˛¤āŗ†ā˛¯ā˛¨āŗā˛¨āŗ ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁ ➆ā˛Ļ➰⺆ ā˛—ā˛Žā˛¨ā˛žā˛°āŗā˛šā˛ĩā˛žā˛—ā˛ŋ ā˛šāŗ†ā˛šāŗā˛šā˛ŋ➍ ā˛¨āŗ†ā˛Ÿāŗâ€Œā˛ĩā˛°āŗā˛•āŗ ā˛˛āŗ‹ā˛Ąāŗâ€Œā˛—āŗ† ā˛•ā˛žā˛°ā˛Ŗā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", + "theme_setting_three_stage_loading_title": "ā˛Žāŗ‚ā˛°āŗ ā˛šā˛‚ā˛¤ā˛Ļ ā˛˛āŗ‹ā˛Ąā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛¸ā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋ", "then": "➍➂➤➰", "they_will_be_merged_together": "➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛’ā˛Ÿāŗā˛Ÿā˛ŋ➗⺆ ā˛ĩā˛ŋā˛˛āŗ€ā˛¨ā˛—āŗŠā˛ŗā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "third_party_resources": "ā˛Žāŗ‚ā˛°ā˛¨āŗ‡ ā˛­ā˛žā˛—ā˛Ļ ➏➂ā˛Ēā˛¨āŗā˛Žāŗ‚ā˛˛ā˛—ā˛ŗāŗ", @@ -1311,15 +1428,21 @@ "trash": "➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤", "trash_all": "ā˛Žā˛˛āŗā˛˛ā˛ž ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤", "trash_delete_asset": "➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ / ➅➺ā˛ŋ➏⺁ ā˛†ā˛¸āŗā˛¤ā˛ŋ", + "trash_emptied": "ā˛–ā˛žā˛˛ā˛ŋ ➕➏", "trash_no_results_message": "➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ā˛ĩā˛°āŗā˛—ā˛žā˛¯ā˛ŋā˛¸ā˛˛ā˛žā˛Ļ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ā˛‡ā˛˛āŗā˛˛ā˛ŋ ā˛•ā˛žā˛Ŗā˛ŋ➏ā˛ŋā˛•āŗŠā˛ŗāŗā˛ŗāŗā˛¤āŗā˛¤ā˛ĩāŗ†.", "trash_page_delete_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ‚ ➅➺ā˛ŋ➏ā˛ŋ", + "trash_page_empty_trash_dialog_content": "➍ā˛ŋā˛Žāŗā˛Ž ➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛–ā˛žā˛˛ā˛ŋ ā˛Žā˛žā˛Ąā˛˛āŗ ➍⺀ā˛ĩ⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž? ➈ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗâ€Œā˛¨ā˛ŋ➂ā˛Ļ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "trash_page_info": "➅➍⺁ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛•āŗā˛•āŗ† ➏⺇➰ā˛ŋ➏ā˛ŋā˛Ļ ā˛ā˛Ÿā˛‚ā˛—ā˛ŗā˛¨āŗā˛¨āŗ {days} ā˛Ļā˛ŋ➍➗➺ ➍➂➤➰ ā˛ļā˛žā˛ļāŗā˛ĩ➤ā˛ĩā˛žā˛—ā˛ŋ ➅➺ā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", + "trash_page_no_assets": "➕➏ā˛Ļ ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛‡ā˛˛āŗā˛˛", + "trash_page_restore_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ ā˛Žā˛°āŗā˛¸āŗā˛Ĩā˛žā˛Ēā˛ŋ➏ā˛ŋ", + "trash_page_select_assets_btn": "ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋ", "trigger": "ā˛Ÿāŗā˛°ā˛ŋā˛—āŗā˛—ā˛°āŗ", - "trigger_asset_metadata_extraction_description": "ā˛¯ā˛žā˛ĩā˛žā˛— ā˛Žā˛‚ā˛Ļ⺁ ā˛Ēāŗā˛°ā˛šāŗ‹ā˛Ļā˛ŋā˛¸ā˛˛ā˛žā˛¯ā˛ŋ➤⺁ ā˛Žā˛•āŗā˛¸ā˛ŋā˛Ģāŗ ā˛†ā˛¸āŗā˛¤ā˛ŋ➝ ā˛Žāŗ†ā˛Ÿā˛žā˛Ąāŗ‡ā˛Ÿā˛žā˛ĩā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛°ā˛¤āŗ†ā˛—āŗ†ā˛¯ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", "trigger_asset_uploaded": "ā˛†ā˛¸āŗā˛¤ā˛ŋ ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "trigger_asset_uploaded_description": "ā˛šāŗŠā˛¸ ā˛¸āŗā˛ĩā˛¤āŗā˛¤ā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋā˛Ļā˛žā˛— ā˛Ÿāŗā˛°ā˛ŋā˛—ā˛°āŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", + "trigger_description": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏⺁ā˛ĩ ➒➂ā˛Ļ⺁ ā˛˜ā˛Ÿā˛¨āŗ†", "trigger_person_recognized": "ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋ ➗⺁➰⺁➤ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "trigger_person_recognized_description": "➒ā˛Ŧāŗā˛Ŧ ā˛ĩāŗā˛¯ā˛•āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛Ēā˛¤āŗā˛¤āŗ†ā˛šā˛šāŗā˛šā˛ŋā˛Ļā˛žā˛— ā˛Ēāŗā˛°ā˛šāŗ‹ā˛Ļā˛ŋā˛¸ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛Ļāŗ†", + "trigger_type": "ā˛Ÿāŗā˛°ā˛ŋā˛—āŗā˛—ā˛°āŗ ā˛Ēāŗā˛°ā˛•ā˛žā˛°", "troubleshoot": "ā˛¤āŗŠā˛‚ā˛Ļ➰⺆", "type": "➟⺈ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛ŋ", "unable_to_change_pin_code": "ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛Ŧā˛Ļā˛˛ā˛žā˛¯ā˛ŋ➏➞⺁ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛˛āŗā˛˛", @@ -1345,6 +1468,7 @@ "unsupported_field_type": "ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➏ā˛Ļ ā˛•āŗā˛ˇāŗ‡ā˛¤āŗā˛° ā˛Ēāŗā˛°ā˛•ā˛žā˛°", "unsupported_file_type": "{file} ā˛Ģāŗˆā˛˛āŗ ā˛Ēāŗā˛°ā˛•ā˛žā˛°ā˛ĩ⺁ ā˛Ŧ⺆➂ā˛Ŧ➞ā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋā˛˛āŗā˛˛ā˛Ļ {type} ā˛•ā˛žā˛°ā˛Ŗ ➅ā˛Ļā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ā˛¸ā˛žā˛§āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛.", "untagged": "ā˛…ā˛¨āŗā˛Ÿā˛žā˛—āŗā˛Ąāŗ", + "untitled_workflow": "ā˛ļāŗ€ā˛°āŗā˛ˇā˛ŋā˛•āŗ†ā˛°ā˛šā˛ŋ➤ ➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩ⺁", "up_next": "ā˛Žāŗā˛‚ā˛Ļā˛ŋ➍ ➅ā˛Ēāŗ", "update_location_action_prompt": "{count} ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗ ā˛¸āŗā˛Ĩ➺ā˛ĩā˛¨āŗā˛¨āŗ ➇ā˛Ļā˛°āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➍ā˛ĩ⺀➕➰ā˛ŋ➏ā˛ŋ:", "updated_at": "➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", @@ -1352,13 +1476,14 @@ "upload": "➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋ", "upload_concurrency": "➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛•ā˛¨āŗā˛•āŗā˛¯āŗā˛°āŗ†ā˛¨āŗā˛¸ā˛ŋ", "upload_details": "➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛ĩā˛ŋā˛ĩ➰➗➺⺁", + "upload_dialog_info": "ā˛†ā˛¯āŗā˛•āŗ†ā˛Žā˛žā˛Ąā˛ŋā˛Ļ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗ(ā˛—ā˛ŗā˛¨āŗā˛¨āŗ) ā˛¸ā˛°āŗā˛ĩā˛°āŗâ€Œā˛—āŗ† ā˛Ŧāŗā˛¯ā˛žā˛•ā˛Ēāŗ ā˛Žā˛žā˛Ąā˛˛āŗ ➍⺀ā˛ĩ⺁ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", + "upload_dialog_title": "➅ā˛Ēāŗā˛˛āŗ‹ā˛Ąāŗ ā˛†ā˛¸āŗā˛¤ā˛ŋ", "upload_errors": "{count, plural, one {# ā˛Ļ⺋➎} other {# ā˛Ļ⺋➎➗➺⺁}} ā˛¨āŗŠā˛‚ā˛Ļā˛ŋ➗⺆ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Ēāŗ‚ā˛°āŗā˛Ŗā˛—āŗŠā˛‚ā˛Ąā˛ŋā˛Ļāŗ†, ā˛šāŗŠā˛¸ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛˛āŗ ā˛Ē⺁➟ā˛ĩā˛¨āŗā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛Žā˛žā˛Ąā˛ŋ.", "upload_finished": "➅ā˛Ēāŗā˛˛āŗ‹ā˛Ąāŗ ā˛Žāŗā˛—ā˛ŋā˛Ļā˛ŋā˛Ļāŗ†", "upload_status_duplicates": "➍➕➞⺁", "upload_status_errors": "ā˛Ļ⺋➎➗➺⺁", "upload_status_uploaded": "➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "upload_success": "➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋā˛Ļāŗ†, ā˛šāŗŠā˛¸ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛¨āŗ‹ā˛Ąā˛˛āŗ ā˛Ē⺁➟ā˛ĩā˛¨āŗā˛¨āŗ ➰ā˛ŋā˛Ģāŗā˛°āŗ†ā˛ļāŗ ā˛Žā˛žā˛Ąā˛ŋ.", - "upload_to_album_body": "ā˛šā˛¸āŗā˛¤ā˛šā˛žā˛˛ā˛ŋ➤ ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛ĩ⺈ā˛ļā˛ŋā˛ˇāŗā˛Ÿāŗā˛¯ā˛ĩā˛¨āŗā˛¨āŗ ā˛Ŧ➺➏ā˛Ļ ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛ŋ➗⺆, ➍⺀ā˛ĩ⺁ ➅ā˛ĩāŗā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąāŗā˛ĩā˛žā˛— ā˛¸āŗā˛Ĩ➺⺀➝ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➍⺇➰ā˛ĩā˛žā˛—ā˛ŋ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➗⺆ ➏⺇➰ā˛ŋ➏➞⺁ ➍⺀ā˛ĩ⺁ ā˛ˆā˛— ā˛†ā˛¯āŗā˛•āŗ† ā˛Žā˛žā˛Ąā˛Ŧā˛šāŗā˛Ļ⺁, ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąāŗā˛ĩ ā˛…ā˛—ā˛¤āŗā˛¯ā˛ĩā˛ŋā˛˛āŗā˛˛ ā˛Žā˛¤āŗā˛¤āŗ ➍➂➤➰ ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ➗⺆ ➏⺇➰ā˛ŋ➏ā˛ŋ.", "upload_to_immich": "ā˛‡ā˛Žāŗā˛Žā˛ŋā˛šāŗ ({count}) ➗⺆ ➅ā˛Ēāŗâ€Œā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛ŋ", "uploading": "➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛†ā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "uploading_media": "ā˛Žā˛žā˛§āŗā˛¯ā˛Žā˛ĩā˛¨āŗā˛¨āŗ ➅ā˛Ēāŗ ā˛˛āŗ‹ā˛Ąāŗ ā˛Žā˛žā˛Ąā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", @@ -1384,6 +1509,7 @@ "utilities": "➉ā˛Ēā˛¯āŗā˛•āŗā˛¤ā˛¤āŗ†ā˛—ā˛ŗāŗ", "validate": "ā˛ŽāŗŒā˛˛āŗā˛¯āŗ€ā˛•ā˛°ā˛ŋ➏ā˛ŋ", "validate_endpoint_error": "ā˛Ļ➝ā˛ĩā˛ŋā˛Ÿāŗā˛Ÿāŗ ā˛Žā˛žā˛¨āŗā˛¯ā˛ĩā˛žā˛Ļ URL ā˛…ā˛¨āŗā˛¨āŗ ā˛¨ā˛Žāŗ‚ā˛Ļā˛ŋ➏ā˛ŋ", + "validation_error": "ā˛•āŗā˛°ā˛Žā˛Ŧā˛Ļāŗā˛§ ā˛Ļ⺋➎", "variables": "ā˛…ā˛¸āŗā˛Ĩā˛ŋ➰➗➺⺁", "version": "➆ā˛ĩāŗƒā˛¤āŗā˛¤ā˛ŋ", "version_announcement_closing": "➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗā˛¨āŗ‡ā˛šā˛ŋ➤, ā˛…ā˛˛āŗ†ā˛•āŗā˛¸āŗ", @@ -1394,6 +1520,7 @@ "video_hover_setting": "ā˛šāŗ‹ā˛ĩā˛°āŗâ€Œā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗ ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ", "video_hover_setting_description": "ā˛ŽāŗŒā˛¸āŗ ā˛ā˛Ÿā˛‚ ā˛Žāŗ‡ā˛˛āŗ† ➏⺁➺ā˛ŋā˛Ļā˛žā˛Ąāŗā˛¤āŗā˛¤ā˛ŋ➰⺁ā˛ĩā˛žā˛— ā˛ĩāŗ€ā˛Ąā˛ŋ➝⺊ ā˛Ĩ➂ā˛Ŧāŗâ€Œā˛¨āŗ‡ā˛˛āŗ ā˛Ēāŗā˛˛āŗ‡ ā˛Žā˛žā˛Ąā˛ŋ. ➍ā˛ŋā˛ˇāŗā˛•āŗā˛°ā˛ŋā˛¯ā˛—āŗŠā˛ŗā˛ŋ➏ā˛ŋā˛Ļāŗā˛Ļ➰⺂ ā˛¸ā˛š, ā˛Ēāŗā˛˛āŗ‡ ā˛ā˛•ā˛žā˛¨āŗ ā˛Žāŗ‡ā˛˛āŗ† ➏⺁➺ā˛ŋā˛Ļā˛žā˛Ąāŗā˛ĩ ā˛Žāŗ‚ā˛˛ā˛• ā˛Ēāŗā˛˛āŗ‡ā˛Ŧāŗā˛¯ā˛žā˛•āŗ ā˛…ā˛¨āŗā˛¨āŗ ā˛Ēāŗā˛°ā˛žā˛°ā˛‚ā˛­ā˛ŋ➏ā˛Ŧā˛šāŗā˛Ļ⺁.", "videos": "ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ", + "videos_only": "ā˛ĩāŗ€ā˛Ąā˛ŋā˛¯āŗŠā˛—ā˛ŗāŗ ā˛Žā˛žā˛¤āŗā˛°", "view": "ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_album": "ā˛†ā˛˛āŗā˛Ŧā˛Žāŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_all": "ā˛Žā˛˛āŗā˛˛ā˛ĩā˛¨āŗā˛¨āŗ‚ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", @@ -1402,15 +1529,20 @@ "view_details": "ā˛ĩā˛ŋā˛ĩā˛°ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_in_timeline": "ā˛Ÿāŗˆā˛Žāŗ ā˛˛āŗˆā˛¨āŗ ā˛¨ā˛˛āŗā˛˛ā˛ŋ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_link": "➞ā˛ŋā˛‚ā˛•āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", + "view_links": "➞ā˛ŋā˛‚ā˛•āŗ ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_name": "ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_next_asset": "ā˛Žāŗā˛‚ā˛Ļā˛ŋ➍ ā˛†ā˛¸āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_previous_asset": "ā˛šā˛ŋ➂ā˛Ļā˛ŋ➍ ā˛†ā˛¸āŗā˛¤ā˛ŋā˛¯ā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_qr_code": "ā˛•āŗā˛¯āŗ‚ā˛†ā˛°āŗ ā˛•āŗ‹ā˛Ąāŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_similar_photos": "➇ā˛Ļāŗ‡ ➰⺀➤ā˛ŋ➝ ā˛Ģāŗ‹ā˛Ÿāŗ‹ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "view_stack": "ā˛¸āŗā˛Ÿā˛žā˛•āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", + "view_user": "ā˛Ŧ➺➕⺆ā˛Ļā˛žā˛°ā˛°ā˛¨āŗā˛¨āŗ ā˛ĩāŗ€ā˛•āŗā˛ˇā˛ŋ➏ā˛ŋ", "viewer_remove_from_stack": "ā˛¸āŗā˛Ÿā˛žā˛•āŗā˛¨ā˛ŋ➂ā˛Ļ ➤⺆➗⺆ā˛Ļāŗā˛šā˛žā˛•ā˛ŋ", + "viewer_stack_use_as_main_asset": "ā˛Žāŗā˛–āŗā˛¯ ā˛†ā˛¸āŗā˛¤ā˛ŋā˛¯ā˛žā˛—ā˛ŋ ā˛Ŧ➺➏ā˛ŋ", + "viewer_unstack": "ā˛…ā˛¨āŗ-ā˛¸āŗā˛Ÿā˛žā˛•āŗ", "visibility": "ā˛—āŗ‹ā˛šā˛°ā˛¤āŗ†", "visual": "ā˛ĩā˛ŋā˛ˇāŗā˛¯ā˛˛āŗ", + "visual_builder": "ā˛ĩā˛ŋā˛ˇāŗā˛¯ā˛˛āŗ ā˛Ŧā˛ŋā˛˛āŗā˛Ąā˛°āŗ", "waiting": "ā˛•ā˛žā˛¯ā˛˛ā˛žā˛—āŗā˛¤āŗā˛¤ā˛ŋā˛Ļāŗ†", "warning": "ā˛Žā˛šāŗā˛šā˛°ā˛ŋ➕⺆", "week": "ā˛ĩā˛žā˛°", @@ -1428,6 +1560,7 @@ "workflow_navigation_prompt": "➍ā˛ŋā˛Žāŗā˛Ž ā˛Ŧā˛Ļā˛˛ā˛žā˛ĩā˛Ŗāŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➉➺ā˛ŋ➏ā˛Ļ⺆➝⺇ ➍⺀ā˛ĩ⺁ ā˛šāŗŠā˛°ā˛Ąā˛˛āŗ ā˛–ā˛šā˛ŋ➤ā˛ĩā˛žā˛—ā˛ŋ ā˛Ŧ➝➏⺁ā˛ĩā˛ŋā˛°ā˛ž?", "workflow_summary": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩā˛ŋ➍ ā˛¸ā˛žā˛°ā˛žā˛‚ā˛ļ", "workflow_update_success": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩā˛¨āŗā˛¨āŗ ➝ā˛ļā˛¸āŗā˛ĩā˛ŋā˛¯ā˛žā˛—ā˛ŋ ➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", + "workflow_updated": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩā˛¨āŗā˛¨āŗ ➍ā˛ĩ⺀➕➰ā˛ŋā˛¸ā˛˛ā˛žā˛—ā˛ŋā˛Ļāŗ†", "workflows": "➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩ⺁➗➺⺁", "workflows_help_text": "ā˛Ÿāŗā˛°ā˛ŋā˛—āŗā˛—ā˛°āŗâ€Œā˛—ā˛ŗāŗ ā˛Žā˛¤āŗā˛¤āŗ ā˛Ģā˛ŋā˛˛āŗā˛Ÿā˛°āŗâ€Œā˛—ā˛ŗ ā˛†ā˛§ā˛žā˛°ā˛Ļ ā˛Žāŗ‡ā˛˛āŗ† ➍ā˛ŋā˛Žāŗā˛Ž ā˛¸āŗā˛ĩā˛¤āŗā˛¤āŗā˛—ā˛ŗ ā˛Žāŗ‡ā˛˛ā˛ŋ➍ ā˛•āŗā˛°ā˛ŋā˛¯āŗ†ā˛—ā˛ŗā˛¨āŗā˛¨āŗ ➕⺆➞➏ā˛Ļ ā˛šā˛°ā˛ŋā˛ĩ⺁➗➺⺁ ā˛¸āŗā˛ĩā˛¯ā˛‚ā˛šā˛žā˛˛ā˛ŋā˛¤ā˛—āŗŠā˛ŗā˛ŋā˛¸āŗā˛¤āŗā˛¤ā˛ĩāŗ†", "wrong_pin_code": "➤ā˛Ēāŗā˛Ēā˛žā˛Ļ ā˛Ēā˛ŋā˛¨āŗ ā˛•āŗ‹ā˛Ąāŗ", @@ -1436,5 +1569,6 @@ "you_dont_have_any_shared_links": "➍⺀ā˛ĩ⺁ ā˛¯ā˛žā˛ĩ⺁ā˛Ļāŗ‡ ā˛šā˛‚ā˛šā˛ŋā˛•āŗŠā˛‚ā˛Ą ➞ā˛ŋā˛‚ā˛•āŗâ€Œā˛—ā˛ŗā˛¨āŗā˛¨āŗ ā˛šāŗŠā˛‚ā˛Ļā˛ŋā˛˛āŗā˛˛", "your_wifi_name": "➍ā˛ŋā˛Žāŗā˛Ž ā˛ĩ⺈-ā˛Ģ⺈ ā˛šāŗ†ā˛¸ā˛°āŗ", "zero_to_clear_rating": "ā˛†ā˛¸āŗā˛¤ā˛ŋ ā˛°āŗ‡ā˛Ÿā˛ŋā˛‚ā˛—āŗ ā˛…ā˛¨āŗā˛¨āŗ ➤⺆➰ā˛ĩāŗā˛—āŗŠā˛ŗā˛ŋ➏➞⺁ 0 ā˛’ā˛¤āŗā˛¤ā˛ŋ➰ā˛ŋ", - "zoom_image": "ā˛œāŗ‚ā˛Žāŗ ā˛‡ā˛Žāŗ‡ā˛œāŗ" + "zoom_image": "ā˛œāŗ‚ā˛Žāŗ ā˛‡ā˛Žāŗ‡ā˛œāŗ", + "zoom_to_bounds": "ā˛Žā˛Ąā˛ŋ➕➞⺁" } diff --git a/i18n/ko.json b/i18n/ko.json index 5042134948..7114316134 100644 --- a/i18n/ko.json +++ b/i18n/ko.json @@ -10,31 +10,39 @@ "active": "í™œė„ą", "active_count": "í™œė„ą: {count}", "activity": "활동", + "activity_changed": "í™œë™ė´ {enabled, select, true {í™œė„ąí™”} other {ëš„í™œė„ąí™”}}ë˜ė—ˆėŠĩ니다.", "add": "ėļ”ę°€", "add_a_description": "네ëĒ… ėļ”ę°€", "add_a_location": "ėœ„ėš˜ ėļ”ę°€", "add_a_name": "ė´ëĻ„ ėļ”ę°€", "add_a_title": "렜ëĒŠ ėļ”ę°€", "add_action": "ėž‘ė—… ėļ”ę°€", + "add_action_description": "클ëĻ­í•˜ė—Ŧ ėˆ˜í–‰í•  ėž‘ė—… ėļ”ę°€", "add_assets": "항ëĒŠ ėļ”ę°€", "add_birthday": "ėƒėŧ ėļ”ę°€", "add_endpoint": "ė—”ë“œíŦė¸íŠ¸ ėļ”ę°€", "add_exclusion_pattern": "ė œė™¸ ęˇœėš™ ėļ”ę°€", + "add_filter": "필터 ėļ”ę°€", + "add_filter_description": "클ëĻ­í•˜ė—Ŧ 필터 ėĄ°ęą´ ėļ”ę°€", "add_location": "ėœ„ėš˜ ėļ”ę°€", + "add_more_users": "다ëĨ¸ ė‚ŦėšŠėž ėļ”ę°€", "add_partner": "파트너 ėļ”ę°€", + "add_path": "ę˛Ŋ로 ėļ”ę°€", "add_photos": "ė‚Ŧė§„ ėļ”ę°€", - "add_step": "ë‹¨ęŗ„ ėļ”ę°€", "add_tag": "태그 ėļ”ę°€", "add_to": "ė´ęŗŗė— ėļ”ę°€â€Ļ", "add_to_album": "ė•¨ë˛”ė— ėļ”ę°€", "add_to_album_bottom_sheet_added": "{album}뗐 ėļ”가됨", "add_to_album_bottom_sheet_already_exists": "ė´ë¯¸ {album}뗐 ėžˆėŒ", "add_to_album_bottom_sheet_some_local_assets": "ėŧëļ€ ëĄœėģŦ 항ëĒŠė„ ė•¨ë˛”ė— ėļ”가할 눘 ė—†ėŠĩ니다.", + "add_to_album_toggle": "{album} ė„ íƒ/í•´ė œ", "add_to_albums": "ė—ŦëŸŦ ė•¨ë˛”ė— ėļ”ę°€", "add_to_albums_count": "ė—ŦëŸŦ ė•¨ë˛”ė— ėļ”ę°€ ({count})", "add_to_bottom_bar": "ë‹¤ėŒė— ėļ”ę°€", + "add_to_shared_album": "ęŗĩ뜠 ė•¨ë˛”ė— ėļ”ę°€", "add_upload_to_stack": "ėŠ¤íƒė— 항ëĒŠ ė—…ëĄœë“œ", "add_url": "URL ėļ”ę°€", + "add_workflow_step": "ė›ŒíŦ플로 ë‹¨ęŗ„ ėļ”ę°€", "added_to_archive": "ëŗ´ę´€í•¨ėœŧ로 ė´ë™ë˜ė—ˆėŠĩ니다.", "added_to_favorites": "ėĻę˛¨ė°žę¸°ė— ėļ”ę°€ë˜ė—ˆėŠĩ니다.", "added_to_favorites_count": "ėĻę˛¨ė°žę¸°ė— 항ëĒŠ {count, number}氜 ėļ”가됨", @@ -50,9 +58,9 @@ "backup_database": "ë°ė´í„°ë˛ ė´ėŠ¤ 덤프 ėƒė„ą", "backup_database_enable_description": "ë°ė´í„°ë˛ ė´ėŠ¤ 덤프 í™œė„ąí™”", "backup_keep_last_amount": "ëŗ´ę´€í•  ė´ė „ 덤프 눘", - "backup_onboarding_1_description": "클ëŧėš°ë“œ 또는 다ëĨ¸ ëŦŧëĻŦ렁 ėœ„ėš˜ė— ė˜¤í”„ė‚Ŧė´íŠ¸ ė‚Ŧëŗ¸ė„ ëŗ´ę´€í•Šë‹ˆë‹¤.", - "backup_onboarding_2_description": "ė—ŦëŸŦ ėžĨėš˜ė— 로ėģŦ ëŗĩė‚Ŧëŗ¸ė´ ėžˆėŠĩ니다. ė—Ŧę¸°ė—ëŠ” ėŖŧėš” 파ėŧęŗŧ 해당 파ėŧė˜ 로ėģŦ ë°ąė—…ė´ íŦ함됩니다.", - "backup_onboarding_3_description": "ė›ëŗ¸ 파ėŧė„ íŦ함한 ë°ė´í„°ė˜ ëĒ¨ë“  ė‚Ŧëŗ¸ ėˆ˜ėž…ë‹ˆë‹¤. ė—Ŧę¸°ė—ëŠ” ė˜¤í”„ė‚Ŧė´íŠ¸ ė‚Ŧëŗ¸ 1ę°œė™€ 로ėģŦ ė‚Ŧëŗ¸ 2개가 íŦ함됩니다.", + "backup_onboarding_1_description": "개는 클ëŧėš°ë“œë‚˜ 다ëĨ¸ ëŦŧëĻŦ렁 ėœ„ėš˜ė— ëŗ´ę´€í•Šë‹ˆë‹¤.", + "backup_onboarding_2_description": "개는 ė„œëĄœ 다ëĨ¸ 로ėģŦ ėžĨėš˜ė— ëŗ´ę´€í•˜ęŗ ,", + "backup_onboarding_3_description": "ę°œė˜ ë°ė´í„° ė‚Ŧëŗ¸ė„ 만듭니다.", "backup_onboarding_description": "ë°ė´í„° ëŗ´í˜¸ëĨŧ ėœ„í•´ 3-2-1 ë°ąė—… ė „ëžĩ ė‚ŦėšŠė„ ęļŒėžĨ합니다. ë°ąė—…ė—ëŠ” ė—…ëĄœë“œí•œ ė‚Ŧė§„ 및 ë™ė˜ėƒëŋ ė•„ë‹ˆëŧ Immich ë°ė´í„°ë˛ ė´ėŠ¤ë„ íŦí•¨ë˜ė–´ė•ŧ 합니다.", "backup_onboarding_footer": "Immich ë°ąė—…ė— 대한 ėžė„¸í•œ ë‚´ėšŠė€ ęŗĩė‹ ëŦ¸ė„œëĨŧ ė°¸ėĄ°í•˜ė„¸ėš”.", "backup_onboarding_parts_title": "3-2-1 ë°ąė—…ė´ëž€:", @@ -73,7 +81,6 @@ "cron_expression_description": "Cron í‘œí˜„ė‹ėœŧ로 늤ėē” ėŖŧ기ëĨŧ ė„¤ė •í•Šë‹ˆë‹¤. ėžė„¸í•œ ë‚´ėšŠė€ Crontab GuruëĨŧ í™•ė¸í•˜ė„¸ėš”.", "cron_expression_presets": "Cron í‘œí˜„ė‹ 프ëĻŦė…‹", "disable_login": "ëĄœęˇ¸ė¸ ëš„í™œė„ąí™”", - "download_csv": "CSV ë‹¤ėš´ëĄœë“œ", "duplicate_detection_job_description": "揰溄 학ėŠĩėœŧ로 뜠ė‚Ŧ한 ė´ë¯¸ė§€ëĨŧ ę°ė§€í•Šë‹ˆë‹¤. ėŠ¤ë§ˆíŠ¸ ę˛€ėƒ‰ė´ í™œė„ąí™”ë˜ė–´ ėžˆė–´ė•ŧ 합니다.", "exclusion_pattern_description": "ëŧė´ë¸ŒëŸŦëĻŦ 늤ėē”ė—ė„œ ė œė™¸í•  파ėŧė´ë‚˜ 폴더 ęˇœėš™ė„ ė„¤ė •í•Šë‹ˆë‹¤. ę°€ė ¸ė˜¤ė§€ ė•Šė„ 파ėŧ(RAW 파ėŧ 등)ė´ íŦ함된 폴더가 ėžˆė„ 때 ė‚ŦėšŠí•Šë‹ˆë‹¤.", "export_config_as_json_description": "현ėžŦ ė‹œėŠ¤í…œ ęĩŦė„ąė„ JSON 파ėŧ로 ë‹¤ėš´ëĄœë“œí•Šë‹ˆë‹¤.", @@ -108,17 +115,6 @@ "image_thumbnail_quality_description": "ė„Ŧ네ėŧ í’ˆė§ˆė„ 1~100 ë˛”ėœ„ė—ė„œ ė„¤ė •í•Šë‹ˆë‹¤. ę°’ė´ í´ėˆ˜ëĄ ėĸ‹ė§€ë§Œ 파ėŧ íŦ기가 ėģ¤ė§€ęŗ  ė•ą ë°˜ė‘ ė†ë„ę°€ ëŠë ¤ė§ˆ 눘 ėžˆėŠĩ니다.", "image_thumbnail_title": "ė„Ŧ네ėŧ 네렕", "import_config_from_json_description": "JSON 파ėŧė„ ė—…ëĄœë“œí•˜ė—Ŧ ė‹œėŠ¤í…œ ęĩŦė„ąė„ 氀렏ė˜ĩ니다.", - "integrity_checks_checksum_files": "랴íŦė„Ŧ 파ėŧ", - "integrity_checks_checksum_files_description": "랴íŦė„Ŧ 검ė‚Ŧ 네렕", - "integrity_checks_checksum_files_enable_description": "랴íŦė„Ŧ í™•ė¸ í™œė„ąí™”", - "integrity_checks_checksum_files_percentage_limit": "íŧė„ŧ트 한도", - "integrity_checks_checksum_files_percentage_limit_description": "랴íŦė„Ŧ 검ė‚Ŧ가 각 간격마다 ė–ŧ마나 ėžėŖŧ ėˆ˜í–‰ë ė§€ëĨŧ ę˛°ė •í•˜ëŠ” ėĩœëŒ€ ëš„ėœ¨ė„ 0.01ė—ė„œ 1 ė‚Ŧė´ëĄœ ė„¤ė •í•˜ė„¸ėš”.", - "integrity_checks_checksum_files_time_limit": "ė‹œę°„ ė œí•œ", - "integrity_checks_checksum_files_time_limit_description": "각 간격마다 랴íŦė„Ŧ 검ė‚Ŧ가 ėˆ˜í–‰ë˜ëŠ” ėĩœëŒ€ ė‹œę°„ė„ ė„¤ė •í•˜ė„¸ėš”. (밀ëĻŦ봈)", - "integrity_checks_missing_files": "누ëŊ된 파ėŧ", - "integrity_checks_missing_files_description": "ėŖŧ기ëĨŧ ė„¤ė •í•˜ęŗ , 누ëŊ된 파ėŧė„ í™•ė¸í•˜ëŠ” 기ëŠĨė„ ėŧœęą°ë‚˜ ë„ė„¸ėš”", - "integrity_checks_missing_files_enable_description": "누ëŊ된 파ėŧ í™•ė¸ í™œė„ąí™”", - "integrity_checks_settings": "ëŦ´ę˛°ė„ą 검ė‚Ŧ", "job_concurrency": "{job} ë™ė‹œė„ą", "job_created": "ėž‘ė—…ė´ ėƒė„ąë˜ė—ˆėŠĩ니다.", "job_not_concurrency_safe": "ė´ ėž‘ė—…ė€ ë™ė‹œ ė‹¤í–‰ė— ė•ˆė „í•˜ė§€ ė•ŠėŠĩ니다.", @@ -186,7 +182,7 @@ "machine_learning_ocr_min_recognition_score": "ėĩœė†Œ ė¸ė‹ 렐눘", "machine_learning_ocr_min_score_recognition_description": "ė¸ė‹í•  í…ėŠ¤íŠ¸ė˜ ėĩœė†Œ ė‹ ëĸ°ë„ 렐눘ëĨŧ 0~1 ë˛”ėœ„ė—ė„œ ė„¤ė •í•Šë‹ˆë‹¤. ę°’ė´ ėž‘ė„ėˆ˜ëĄ 더 ë§Žė€ í…ėŠ¤íŠ¸ëĨŧ ė¸ė‹í•˜ė§€ë§Œ ėž˜ëĒģ ė¸ė‹ë  가ëŠĨė„ąë„ ë†’ė•„ė§‘ë‹ˆë‹¤.", "machine_learning_ocr_model": "OCR ëĒ¨ë¸", - "machine_learning_ocr_model_description": "ė„œë˛„ ëĒ¨ë¸ė€ ëĒ¨ë°”ėŧ ëĒ¨ë¸ëŗ´ë‹¤ ė •í™•ë„ę°€ ë†’ė§€ë§Œ 래ëĻŦ ė‹œę°„ė´ 더 ė˜¤ëž˜ 깸ëĻŦęŗ  메ëǍëĻŦ ė‚ŦėšŠëŸ‰ë„ 더 많ėŠĩ니다.", + "machine_learning_ocr_model_description": "ė„œë˛„ ëĒ¨ë¸ė€ ëĒ¨ë°”ėŧ ëĒ¨ë¸ëŗ´ë‹¤ ė •í™•í•˜ė§€ë§Œ, 래ëĻŦ ė‹œę°„ė´ 描떴맀溠 메ëǍëĻŦ ė‚ŦėšŠëŸ‰ë„ ëŠ˜ė–´ë‚Šë‹ˆë‹¤.", "machine_learning_settings": "揰溄 학ėŠĩ 네렕", "machine_learning_settings_description": "揰溄 학ėŠĩ ė‹œ ė‚ŦėšŠí•  ëĒ¨ë¸ęŗŧ 넏ëļ€ ė„¤ė •ė„ 관ëĻŦ합니다.", "machine_learning_smart_search": "ėŠ¤ë§ˆíŠ¸ ę˛€ėƒ‰", @@ -194,25 +190,9 @@ "machine_learning_smart_search_enabled": "ėŠ¤ë§ˆíŠ¸ ę˛€ėƒ‰ í™œė„ąí™”", "machine_learning_smart_search_enabled_description": "ëš„í™œė„ąí™”í•˜ëŠ´ ėŠ¤ë§ˆíŠ¸ ę˛€ėƒ‰ė„ ėœ„í•œ ė´ë¯¸ė§€ 래ëĻŦëĨŧ ė§„í–‰í•˜ė§€ ė•ŠėŠĩ니다.", "machine_learning_url_description": "揰溄 학ėŠĩ ė„œë˛„ė˜ URLė„ ė„¤ė •í•Šë‹ˆë‹¤. ė—ŦëŸŦ 개가 ėž…ë Ĩ되늴 ė˛Ģ ë˛ˆė§¸ëļ€í„° 한 ë˛ˆė— í•˜ë‚˜ė”Š ėˆœė„œëŒ€ëĄœ ė‘ë‹ĩ하는 ė„œë˛„ëĨŧ ė°žė„ ë•ŒęšŒė§€ ėš”ė˛­ė„ ė‹œë„í•Šë‹ˆë‹¤. ė‘ë‹ĩí•˜ė§€ ė•ŠëŠ” ė„œë˛„ëŠ” ë‹¤ė‹œ ė‚ŦėšŠ 가ëŠĨ할 ë•ŒęšŒė§€ ėŧė‹œė ėœŧ로 ė œė™¸ëŠë‹ˆë‹¤.", - "maintenance_backup_management": "ë°ąė—… 관ëĻŦ", "maintenance_delete_backup": "ë°ąė—… ė‚­ė œ", "maintenance_delete_backup_description": "ė´ 파ėŧė´ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", "maintenance_delete_error": "ë°ąė—… ė‚­ė œė— ė‹¤íŒ¨í–ˆėŠĩ니다.", - "maintenance_integrity_check": "랴íŦ", - "maintenance_integrity_check_all": "ė „ė˛´ė„ íƒ", - "maintenance_integrity_checksum_mismatch": "랴íŦė„Ŧ ëļˆėŧėš˜", - "maintenance_integrity_checksum_mismatch_description": "ë””ėŠ¤íŦė˜ 랴íŦė„Ŧė´ Immich ë°ė´í„°ë˛ ė´ėŠ¤ė— ė €ėžĨ된 랴íŦė„Ŧęŗŧ ėŧėš˜í•˜ė§€ ė•ŠëŠ” 파ėŧėž…ë‹ˆë‹¤.", - "maintenance_integrity_checksum_mismatch_job": "파ėŧ ëŦ´ę˛°ė„ą 검ė‚Ŧ", - "maintenance_integrity_checksum_mismatch_refresh_job": "ëŦ´ę˛°ė„ą 똤ëĨ˜ ëŗ´ęŗ ė„œ ėƒˆëĄœęŗ ėš¨", - "maintenance_integrity_missing_file": "누ëŊ된 파ėŧ", - "maintenance_integrity_missing_file_description": "Immich가 ë°ė´í„°ë˛ ė´ėŠ¤ė—ė„œ ėļ”ė í–ˆė§€ë§Œ 파ėŧ ė‹œėŠ¤í…œė—ëŠ” ėĄ´ėžŦí•˜ė§€ ė•ŠëŠ” 파ėŧėž…ë‹ˆë‹¤.", - "maintenance_integrity_missing_file_job": "누ëŊ된 파ėŧ í™•ė¸", - "maintenance_integrity_missing_file_refresh_job": "누ëŊ된 파ėŧ ëŗ´ęŗ ė„œ ėƒˆëĄœęŗ ėš¨", - "maintenance_integrity_report": "ëŦ´ę˛°ė„ą ëŗ´ęŗ ė„œ", - "maintenance_integrity_untracked_file": "ėļ”ė ë˜ė§€ ė•Šė€ 파ėŧ", - "maintenance_integrity_untracked_file_description": "Immichė˜ 디렉터ëĻŦ뗐 ėžˆė§€ë§Œ Immich가 ę¸°ëĄė„ 氀맀溠 ėžˆė§€ ė•Šė€ 파ėŧ들.", - "maintenance_integrity_untracked_file_job": "ėļ”ė ë˜ė§€ ė•Šė€ 파ėŧ í™•ė¸", - "maintenance_integrity_untracked_file_refresh_job": "ėļ”ė ë˜ė§€ ė•Šė€ 파ėŧ ëŗ´ęŗ ė„œ ėƒˆëĄœęŗ ėš¨", "maintenance_restore_backup": "ë°ąė—… ëŗĩ뛐", "maintenance_restore_backup_description": "Immich가 ė´ˆę¸°í™”ëœ 후 ė„ íƒí•œ ë°ąė—…ėœŧ로 ëŗĩė›ëŠë‹ˆë‹¤. ęŗ„ė†í•˜ę¸° 렄뗐 ë°ąė—…ė´ ėžë™ėœŧ로 ėƒė„ąëŠë‹ˆë‹¤.", "maintenance_restore_backup_different_version": "ė´ ë°ąė—…ė€ 다ëĨ¸ ë˛„ė „ė˜ Immichė—ė„œ ėƒė„ąë˜ė—ˆėŠĩ니다!", @@ -287,8 +267,6 @@ "notification_enable_email_notifications": "ė´ëŠ”ėŧ ė•ŒëĻŧ í™œė„ąí™”", "notification_settings": "ė•ŒëĻŧ 네렕", "notification_settings_description": "ė´ëŠ”ėŧė„ íŦ함한 ė•ŒëĻŧ ė„¤ė •ė„ 관ëĻŦ합니다.", - "oauth_allow_insecure_requests": "ëš„ëŗ´ė•ˆ ėš”ė˛­ í—ˆėšŠ", - "oauth_allow_insecure_requests_description": "ę˛Ŋęŗ : OAuth ėš”ė˛­ė— 대한 TLS ė¸ėĻė„œ 검ėĻė„ ëš„í™œė„ąí™”í•˜ė—Ŧ ė¤‘ę°„ėž ęŗĩę˛Šė— 노ėļœë  눘 ėžˆėŠĩ니다.", "oauth_auto_launch": "ėžë™ ė‹¤í–‰", "oauth_auto_launch_description": "ëĄœęˇ¸ė¸ íŽ˜ė´ė§€ė— ė ‘ęˇŧ ė‹œ OAuth ëĄœęˇ¸ė¸ ęŗŧė •ė„ ėžë™ėœŧ로 ė§„í–‰", "oauth_auto_register": "ėžë™ 등록", @@ -296,11 +274,9 @@ "oauth_button_text": "버íŠŧ í…ėŠ¤íŠ¸", "oauth_client_secret_description": "비ęŗĩ氜 클ëŧė´ė–¸íŠ¸ 또는 ęŗĩ氜 클ëŧė´ė–¸íŠ¸ę°€ PKCE(Proof Key for Code Exchange, ėŊ”드 ęĩí™˜ėšŠ 검ėĻ 키)ëĨŧ ė§€ė›í•˜ė§€ ė•ŠëŠ” ę˛Ŋ뚰 í•„ėš”í•Šë‹ˆë‹¤.", "oauth_enable_description": "OAuth ëĄœęˇ¸ė¸", - "oauth_end_session_url_description": "ëĄœęˇ¸ė•„ė›ƒ ëĻŦë‹¤ė´ë ‰íŠ¸ URI", "oauth_mobile_redirect_uri": "ëĒ¨ë°”ėŧ ëĻŦë‹¤ė´ë ‰íŠ¸ URI", "oauth_mobile_redirect_uri_override": "ëĒ¨ë°”ėŧ ëĻŦë‹¤ė´ë ‰íŠ¸ URI ė˜¤ë˛„ëŧė´ë“œ", "oauth_mobile_redirect_uri_override_description": "OAuth ęŗĩę¸‰ėžę°€ ''{callback}'' ë“ąė˜ ëĒ¨ë°”ėŧ URIëĨŧ í—ˆėšŠí•˜ė§€ ė•ŠëŠ” ę˛Ŋ뚰 í™œė„ąí™”í•˜ė„¸ėš”.", - "oauth_prompt_description": "프ëĄŦ프트 파ëŧ미터 (똈: select_account, login, consent)", "oauth_role_claim": "ė—­í•  í´ë ˆėž„", "oauth_role_claim_description": "ėš”ė˛­í•œ í´ë ˆėž„ė„ ė‚ŦėšŠėžė˜ ė—­í• ëĄœ ėžë™ ė„¤ė •í•Šë‹ˆë‹¤. 'user' 또는 'admin'ė„ ė„ íƒí•  눘 ėžˆėŠĩ니다.", "oauth_settings": "OAuth", @@ -327,8 +303,6 @@ "refreshing_all_libraries": "ëĒ¨ë“  ëŧė´ë¸ŒëŸŦëĻŦëĨŧ ėƒˆëĄœęŗ ėš¨í•Šë‹ˆë‹¤.", "registration": "관ëĻŦėž 등록", "registration_description": "ė˛Ģ ë˛ˆė§¸ ė‚ŦėšŠėžė´ë¯€ëĄœ 관ëĻŦėž ęļŒí•œė´ ëļ€ė—Ŧ됩니다. 관ëĻŦ ėž‘ė—… 및 ė‚ŦėšŠėž ėƒė„ąė´ 가ëŠĨ합니다.", - "release_channel_release_candidate": "ëĻ´ëĻŦ늤 í›„ëŗ´ ë˛„ė „", - "release_channel_stable": "ė•ˆė •í™” ë˛„ė „", "remove_failed_jobs": "ė‹¤íŒ¨í•œ ėž‘ė—… ė‚­ė œ", "require_password_change_on_login": "ėĩœė´ˆ ëĄœęˇ¸ė¸ ė‹œ 비밀번호 ëŗ€ę˛Ŋ ėš”ęĩŦ", "reset_settings_to_default": "ė„¤ė •ė„ ę¸°ëŗ¸ę°’ėœŧ로 ëŗĩ뛐", @@ -423,10 +397,6 @@ "transcoding_preferred_hardware_device_description": "(VAAPI 및 QSVė¸ ę˛Ŋ뚰) í•˜ë“œė›¨ė–´ íŠ¸ëžœėŠ¤ėŊ”ë”Šė— ė‚ŦėšŠí•  dri 노드ëĨŧ ė§€ė •í•Šë‹ˆë‹¤.", "transcoding_preset_preset": "프ëĻŦė…‹ (-preset)", "transcoding_preset_preset_description": "ė••ėļ• ė†ë„ëĨŧ ė„¤ė •í•Šë‹ˆë‹¤. 동ėŧ ëš„íŠ¸ë ˆė´íŠ¸ė—ė„œ 느ëϰ ė†ë„ëĨŧ ė„ íƒí•˜ëŠ´ ėƒëŒ€ė ėœŧ로 파ėŧ íŦ기가 ę°ė†Œí•˜ëŠ° í’ˆė§ˆė´ í–ĨėƒëŠë‹ˆë‹¤. VP9는 'faster' ė´ėƒė˜ ė†ë„ëĨŧ ëŦ´ė‹œí•Šë‹ˆë‹¤.", - "transcoding_realtime": "ė‹¤ė‹œę°„ íŠ¸ëžœėŠ¤ėŊ”딊 [ė‹¤í—˜ 기ëŠĨ]", - "transcoding_realtime_description": "ë™ė˜ėƒ ėŠ¤íŠ¸ëĻŦ밍 뤑 ė‹¤ė‹œę°„ íŠ¸ëžœėŠ¤ėŊ”ë”Šė„ ėˆ˜í–‰í•Šë‹ˆë‹¤. ė´ëĨŧ í†ĩ해 ėžŦėƒ 뤑 í™”ė§ˆė„ ëŗ€ę˛Ŋ할 눘 ėžˆė§€ë§Œ, ė„œë˛„ ė„ąëŠĨ뗐 따ëŧ ėžŦėƒ ė§€ė—°ė´ë‚˜ 끊김 í˜„ėƒė´ ë°œėƒí•  눘 ėžˆėŠĩ니다.", - "transcoding_realtime_enabled": "ė‹¤ė‹œę°„ íŠ¸ëžœėŠ¤ėŊ”딊 í™œė„ąí™”", - "transcoding_realtime_enabled_description": "ė´ 기ëŠĨė„ ëš„í™œė„ąí™”í•˜ëŠ´ ėƒˆëĄœėš´ ė‹¤ė‹œę°„ íŠ¸ëžœėŠ¤ėŊ”딊 ėž‘ė—…ė„ ė‹œėž‘í•  눘 ė—†ėŠĩ니다.", "transcoding_reference_frames": "ė°¸ėĄ° í”„ë ˆėž„ 눘", "transcoding_reference_frames_description": "íŠšė • í”„ë ˆėž„ė„ ė••ėļ•í•  때 ė°¸ėĄ°í•  í”„ë ˆėž„ 눘ëĨŧ ė„¤ė •í•Šë‹ˆë‹¤. ę°’ė„ ë†’ė´ëŠ´ ė••ėļ• íš¨ėœ¨ė´ í–Ĩėƒë˜ė§€ë§Œ ė¸ėŊ”딊 ė†ë„ę°€ ëŠë ¤ė§‘ë‹ˆë‹¤. 0ė„ ėž…ë Ĩ하면 ėžë™ėœŧ로 ė„¤ė •í•Šë‹ˆë‹¤.", "transcoding_required_description": "í—ˆėšŠ íŦë§ˇė´ ė•„ë‹Œ ë™ė˜ėƒë§Œ íŠ¸ëžœėŠ¤ėŊ”딊", @@ -470,8 +440,6 @@ "user_settings_description": "ė‚ŦėšŠėž ė„¤ė •ė„ 관ëĻŦ합니다.", "user_successfully_removed": "ė‚ŦėšŠėž {email}ë‹˜ė´ ė„ąęŗĩ렁ėœŧ로 ė‚­ė œë˜ė—ˆėŠĩ니다.", "users_page_description": "관ëĻŦėž ė‚ŦėšŠėž íŽ˜ė´ė§€", - "version_check_channel": "ë°°íŦ ėą„ë„", - "version_check_channel_description": "ë˛„ė „ ė—…ë°ė´íŠ¸ ė•ŒëĻŧė„ ë°›ė„ ë°°íŦ ėą„ë„ė„ ė„ íƒí•˜ė„¸ėš”", "version_check_enabled_description": "ë˛„ė „ í™•ė¸ í™œė„ąí™”", "version_check_implications": "ėŖŧ揰렁ėœŧ로 {server}뗐 ėš”ė˛­ė„ ëŗ´ë‚´ 냈 ë˛„ė „ė„ í™•ė¸í•Šë‹ˆë‹¤.", "version_check_settings": "ë˛„ė „ í™•ė¸", @@ -486,6 +454,8 @@ "advanced_settings_clear_image_cache": "ė´ë¯¸ė§€ ėēė‹œ ė§€ėš°ę¸°", "advanced_settings_clear_image_cache_error": "ė´ë¯¸ė§€ ėēė‹œ ė‚­ė œ ė‹¤íŒ¨", "advanced_settings_clear_image_cache_success": "{size}가 ė„ąęŗĩ렁ėœŧ로 ė •ëĻŦ됨", + "advanced_settings_enable_alternate_media_filter_subtitle": "ė´ ė˜ĩė…˜ė„ ė‚ŦėšŠí•˜ëŠ´ 동기화 뤑 ë¯¸ë””ė–´ëĨŧ ëŒ€ė˛´ 揰뤀ėœŧ로 필터링할 눘 ėžˆėŠĩ니다. ė•ąė´ ëĒ¨ë“  ė•¨ë˛”ė„ ė œëŒ€ëĄœ ę°ė§€í•˜ė§€ ëĒģ할 때만 ė‚ŦėšŠí•˜ė„¸ėš”.", + "advanced_settings_enable_alternate_media_filter_title": "ëŒ€ė˛´ 기기 ė•¨ë˛” 동기화 필터 ė‚ŦėšŠ (ė‹¤í—˜ė )", "advanced_settings_log_level_title": "로그 레벨: {level}", "advanced_settings_prefer_remote_subtitle": "ėŧëļ€ ę¸°ę¸°ė˜ ę˛Ŋ뚰 로ėģŦ 항ëĒŠė—ė„œ ė„Ŧ네ėŧė„ 로드하는 ė†ë„ę°€ ë§¤ėš° 느ëĻŊ니다. ė„œë˛„ ė´ë¯¸ė§€ëĨŧ ëŒ€ė‹  로드하려면 ė´ ė„¤ė •ė„ í™œė„ąí™”í•˜ė„¸ėš”.", "advanced_settings_prefer_remote_title": "ė„œë˛„ ė´ë¯¸ė§€ ė„ í˜¸", @@ -493,6 +463,8 @@ "advanced_settings_proxy_headers_title": "ėģ¤ėŠ¤í…€ í”„ëĄė‹œ 헤더 (ė‹¤í—˜ė )", "advanced_settings_readonly_mode_subtitle": "ėŊ기 ė „ėšŠ ëĒ¨ë“œëĨŧ í™œė„ąí™”í•˜ëŠ´ ė´ë¯¸ė§€ ė„ íƒ, ęŗĩ뜠, ėēėŠ¤íŠ¸, ė‚­ė œ ë“ąė˜ ë™ėž‘ė´ ëš„í™œė„ąí™”ëŠë‹ˆë‹¤. ëŠ”ė¸ í™”ëŠ´ė—ė„œ ė‚ŦėšŠėž ė•„ė´ėŊ˜ė„ 描枌 눌ëŸŦ ėŊ기 ė „ėšŠ ëĒ¨ë“œëĨŧ í™œė„ąí™”/ëš„í™œė„ąí™”í•˜ė„¸ėš”.", "advanced_settings_readonly_mode_title": "ėŊ기 ė „ėšŠ ëĒ¨ë“œ", + "advanced_settings_self_signed_ssl_subtitle": "ė„œë˛„ ė—”ë“œíŦė¸íŠ¸ė˜ SSL ė¸ėĻė„œ 검ėĻė„ 건너뜁니다. ėžė˛´ ė„œëĒ… ė¸ėĻė„œëĨŧ ė‚ŦėšŠí•˜ëŠ” ę˛Ŋ뚰 í™œė„ąí™”í•˜ė„¸ėš”.", + "advanced_settings_self_signed_ssl_title": "ėžė˛´ ė„œëĒ…ëœ SSL ė¸ėĻė„œ í—ˆėšŠ (ė‹¤í—˜ė )", "advanced_settings_sync_remote_deletions_subtitle": "ė›šė—ė„œ ė‚­ė œí•˜ęą°ë‚˜ ëŗĩė›í•œ 항ëĒŠė„ ė´ ę¸°ę¸°ė—ė„œë„ ėžë™ėœŧ로 래ëĻŦ하도록 네렕", "advanced_settings_sync_remote_deletions_title": "ė›ę˛Š ė‚­ė œ 동기화 (ė‹¤í—˜ė )", "advanced_settings_tile_subtitle": "溠揉 ė‚ŦėšŠėž 네렕", @@ -508,18 +480,31 @@ "album_delete_confirmation": "{album} ė•¨ë˛”ė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "album_delete_confirmation_description": "ė´ ė•¨ë˛”ė´ ęŗĩėœ ëœ ę˛Ŋ뚰, 다ëĨ¸ ė‚ŦėšŠėžę°€ ë”ė´ėƒ ė•¨ë˛”ė— ė ‘ęˇŧ할 눘 ė—†ėŠĩ니다.", "album_deleted": "ė•¨ë˛” ė‚­ė œë¨", + "album_info_card_backup_album_excluded": "ė œė™¸ë¨", + "album_info_card_backup_album_included": "ė„ íƒë¨", "album_info_updated": "ė•¨ë˛” ė •ëŗ´ ė—…ë°ė´íŠ¸ë¨", + "album_leave": "ė•¨ë˛”ė—ė„œ ë‚˜ę°€ė‹œę˛ ėŠĩ니까?", + "album_leave_confirmation": "{album} ė•¨ë˛”ė—ė„œ ë‚˜ę°€ė‹œę˛ ėŠĩ니까?", "album_name": "ė•¨ë˛” ė´ëĻ„", "album_options": "ė•¨ë˛” ė˜ĩė…˜", "album_remove_user": "ė‚ŦėšŠėžëĨŧ ė œęą°í•˜ė‹œę˛ ėŠĩ니까?", "album_remove_user_confirmation": "{user}ë‹˜ė„ ė•¨ë˛”ė—ė„œ ė œęą°í•˜ė‹œę˛ ėŠĩ니까?", "album_search_not_found": "ę˛€ėƒ‰ 결ęŗŧ뗐 해당하는 ė•¨ë˛”ė´ ė—†ėŠĩ니다.", + "album_selected": "ė„ íƒëœ ė•¨ë˛”", "album_share_no_users": "ė´ë¯¸ ëĒ¨ë“  ė‚ŦėšŠėžė™€ ė•¨ë˛”ė„ ęŗĩėœ í–ˆęą°ë‚˜ ęŗĩėœ í•  ė‚ŦėšŠėžę°€ ė—†ėŠĩ니다.", "album_summary": "ė•¨ë˛” ėš”ė•Ŋ", "album_updated": "항ëĒŠ ėļ”ę°€ ė•ŒëĻŧ", "album_updated_setting_description": "ęŗĩ뜠 ė•¨ë˛”ė— 항ëĒŠė´ ėļ”ę°€ëœ ę˛Ŋ뚰 ė´ëŠ”ėŧ ė•ŒëĻŧ 받기", "album_upload_assets": "ėģ´í“¨í„°ė—ė„œ 항ëĒŠė„ ė—…ëĄœë“œí•˜ęŗ  ė•¨ë˛”ė— ėļ”ę°€", + "album_user_left": "{album} ė•¨ë˛”ė—ė„œ ë‚˜ė˜´", + "album_user_removed": "{user}ë‹˜ė„ ė•¨ë˛”ė—ė„œ ė œęą°í•¨", + "album_viewer_appbar_delete_confirm": "ė´ ė•¨ë˛”ė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "album_viewer_appbar_share_err_delete": "ė•¨ë˛” ė‚­ė œ ė‹¤íŒ¨", + "album_viewer_appbar_share_err_leave": "ė•¨ë˛” 나가기 ė‹¤íŒ¨", + "album_viewer_appbar_share_err_remove": "ė•¨ë˛”ė—ė„œ 항ëĒŠ ė œęą° 뤑 ëŦ¸ė œ ë°œėƒ", + "album_viewer_appbar_share_err_title": "ė•¨ë˛”ëĒ… ëŗ€ę˛Ŋ ė‹¤íŒ¨", + "album_viewer_appbar_share_leave": "ė•¨ë˛” 나가기", + "album_viewer_appbar_share_to": "ęŗĩ뜠 ëŒ€ėƒ", "album_viewer_page_share_add_users": "ė‚ŦėšŠėž ėļ”ę°€", "album_with_link_access": "링íŦ가 ėžˆëŠ” ę˛Ŋ뚰 누ęĩŦ나 ė´ ė•¨ë˛”ė˜ ė‚Ŧė§„ęŗŧ ė¸ëŦŧė„ ëŗŧ 눘 ėžˆėŠĩ니다.", "albums": "ė•¨ë˛”", @@ -528,12 +513,14 @@ "albums_default_sort_order_description": "냈 ė•¨ë˛” ėƒė„ą ė‹œ ė ėšŠë˜ëŠ” ę¸°ëŗ¸ ė •ë Ŧė„ ė„¤ė •í•Šë‹ˆë‹¤.", "albums_feature_description": "ė—ŦëŸŦ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ í•œęŗŗė— ëǍ땄 둘 눘 ėžˆėŠĩ니다.", "albums_on_device_count": "ę¸°ę¸°ė˜ ė•¨ë˛” ({count}氜)", + "albums_selected": "{count, plural, one {#氜} other {#氜}} ė•¨ë˛” ė„ íƒë¨", "all": "ëĒ¨ë‘", "all_albums": "ëĒ¨ë“  ė•¨ë˛”", "all_people": "ëĒ¨ë“  ė¸ëŦŧ", "all_photos": "ëĒ¨ë“  ė‚Ŧė§„", "all_videos": "ëĒ¨ë“  ë™ė˜ėƒ", "allow_dark_mode": "다íŦ ëĒ¨ë“œ ė‚ŦėšŠ", + "allow_edits": "íŽ¸ė§‘ėžëĄœ 네렕", "allow_public_user_to_download": "ëĒ¨ë“  ė‚ŦėšŠėžė˜ ë‹¤ėš´ëĄœë“œ í—ˆėšŠ", "allow_public_user_to_upload": "ëĒ¨ë“  ė‚ŦėšŠėžė˜ ė—…ëĄœë“œ í—ˆėšŠ", "allowed": "í—ˆėšŠë¨", @@ -541,12 +528,14 @@ "always_keep": "í•­ėƒ ėœ ė§€", "always_keep_photos_hint": "ė´ 揰揰뗐 ëĒ¨ë“  ė‚Ŧė§„ė´ ëŗ´ę´€ëŠë‹ˆë‹¤.", "always_keep_videos_hint": "ė´ 揰揰뗐 ëĒ¨ë“  ë™ė˜ėƒė´ ëŗ´ę´€ëŠë‹ˆë‹¤.", + "anti_clockwise": "ë°˜ė‹œęŗ„ ë°Ší–Ĩ", "api_key": "API 키", "api_key_description": "ė´ ę°’ė€ 한 번만 í‘œė‹œëŠë‹ˆë‹¤. ė°Ŋė„ ë‹Ģ기 ė „ ë°˜ë“œė‹œ ëŗĩė‚Ŧ해ėŖŧė„¸ėš”.", "api_key_empty": "API 키 ė´ëĻ„ė€ ëš„ė›Œë‘˜ 눘 ė—†ėŠĩ니다.", "api_keys": "API 키", "app_architecture_variant": "ëŗ€í˜• (ė•„í‚¤í…ė˛˜)", "app_bar_signout_dialog_content": "ė •ë§ ëĄœęˇ¸ė•„ė›ƒí•˜ė‹œę˛ ėŠĩ니까?", + "app_bar_signout_dialog_ok": "네", "app_bar_signout_dialog_title": "ëĄœęˇ¸ė•„ė›ƒ", "app_download_links": "ė•ą ë‹¤ėš´ëĄœë“œ 링íŦ", "app_settings": "ė•ą 네렕", @@ -557,19 +546,27 @@ "archive": "ëŗ´ę´€í•¨", "archive_action_prompt": "ëŗ´ę´€í•¨ėœŧ로 항ëĒŠ {count}氜 ė´ë™ë¨", "archive_or_unarchive_photo": "ëŗ´ę´€ 래ëĻŦ 또는 í•´ė œ", + "archive_page_no_archived_assets": "ëŗ´ę´€ëœ 항ëĒŠ ė—†ėŒ", + "archive_page_title": "ëŗ´ę´€í•¨ ({count})", "archive_size": "ė••ėļ• íŒŒėŧ íŦ기", "archive_size_description": "ė§€ė •í•œ íŦ기ëĨŧ 봈ęŗŧ하면 ė—ŦëŸŦ ę°œė˜ 파ėŧ로 ëļ„할됊니다. (GiB ë‹¨ėœ„)", "archived": "ëŗ´ę´€í•¨", "archived_count": "ëŗ´ę´€í•¨ėœŧ로 항ëĒŠ {count, plural, other {#氜}} ė´ë™ë¨", "are_these_the_same_person": "동ėŧ한 ė¸ëŦŧė¸ę°€ėš”?", "are_you_sure_to_do_this": "ęŗ„ė† ė§„í–‰í•˜ė‹œę˛ ėŠĩ니까?", + "array_field_not_fully_supported": "ë°°ė—´ 필드는 JSONė„ ėˆ˜ë™ėœŧ로 íŽ¸ė§‘í•´ė•ŧ 합니다", + "asset_action_delete_err_read_only": "ėŊ기 ė „ėšŠ 항ëĒŠė€ ė‚­ė œí•  눘 뗆떴 건너뜁니다.", + "asset_action_share_err_offline": "ė˜¤í”„ëŧė¸ 항ëĒŠė€ ëļˆëŸŦė˜Ŧ 눘 뗆떴 건너뜁니다.", "asset_added_to_album": "ė•¨ë˛”ė— ėļ”ę°€ë˜ė—ˆėŠĩ니다.", "asset_adding_to_album": "ė•¨ë˛”ė— ėļ”ę°€ 뤑â€Ļ", "asset_created": "ėžė‚° ėƒė„ąë¨", - "asset_day_count": "{date}: ėžė‚° {count}氜", "asset_description_updated": "항ëĒŠ 네ëĒ…ė´ ė—…ë°ė´íŠ¸ë˜ė—ˆėŠĩ니다.", + "asset_filename_is_offline": "{filename} 항ëĒŠ 누ëŊ됨", + "asset_has_unassigned_faces": "항ëĒŠė— í• ë‹šë˜ė§€ ė•Šė€ ė–ŧęĩ´ė´ ėžˆėŒ", "asset_hashing": "í•´ė‹ą 뤑â€Ļ", "asset_list_group_by_sub_title": "ë‹¤ėŒėœŧ로 ęˇ¸ëŖší™”", + "asset_list_layout_settings_dynamic_layout_title": "ë™ė  ë ˆė´ė•„ė›ƒ", + "asset_list_layout_settings_group_automatically": "ėžë™", "asset_list_layout_settings_group_by": "ë‹¤ėŒėœŧ로 ęˇ¸ëŖší™”", "asset_list_layout_settings_group_by_month_day": "ė›” + ėŧ", "asset_list_layout_sub_title": "ë ˆė´ė•„ė›ƒ", @@ -580,26 +577,36 @@ "asset_not_found_on_icloud": "iCloudė—ė„œ 항ëĒŠė„ ė°žė„ 눘 ė—†ėŠĩ니다. iCloud뗐 ė €ėžĨ된 파ėŧė´ ė†ėƒë˜ė—ˆė„ 눘 ėžˆėŠĩ니다.", "asset_offline": "누ëŊ된 항ëĒŠ", "asset_offline_description": "ë””ėŠ¤íŦė—ė„œ 항ëĒŠė„ ë”ė´ėƒ ė°žė„ 눘 ė—†ėŠĩ니다. ė„œë˛„ 관ëĻŦėžė—ę˛Œ ė—°ëŊí•˜ė„¸ėš”.", + "asset_restored_successfully": "항ëĒŠė´ ëŗĩė›ë˜ė—ˆėŠĩ니다.", "asset_skipped": "건너뜀", "asset_skipped_in_trash": "íœ´ė§€í†ĩė˜ 항ëĒŠ", + "asset_trashed": "항ëĒŠ ė‚­ė œë¨", "asset_troubleshoot": "항ëĒŠ 트ëŸŦë¸”ėŠˆíŒ…", "asset_uploaded": "ė—…ëĄœë“œ ė™„ëŖŒ", "asset_uploading": "ė—…ëĄœë“œ 뤑â€Ļ", "asset_viewer_settings_subtitle": "ę°¤ëŸŦëĻŦ ëŗ´ę¸° ė„¤ė •ė„ 관ëĻŦ합니다.", "asset_viewer_settings_title": "ëŗ´ę¸° ė˜ĩė…˜", "assets": "항ëĒŠ", + "assets_added_count": "항ëĒŠ {count, plural, one {#氜} other {#氜}} ėļ”가됨", "assets_added_to_album_count": "ė•¨ë˛”ė— 항ëĒŠ {count, plural, one {#氜} other {#氜}} ėļ”가됨", "assets_added_to_albums_count": "{albumTotal, plural, one {ė•¨ë˛”ė—} other {ė•¨ë˛” #ę°œė—}} {assetTotal, plural, one {항ëĒŠė´} other {항ëĒŠ #개가}} ėļ”가됨", "assets_cannot_be_added_to_album_count": "{count, plural, one {ė•¨ë˛”ė— 항ëĒŠė„} other {ėŧëļ€ í•­ëĒŠė„ ė•¨ë˛”ė—}} ėļ”가할 눘 ė—†ėŠĩ니다.", "assets_cannot_be_added_to_albums": "{count, plural, one {항ëĒŠė„} other {항ëĒŠë“¤ė„}} ė–´ëŠ ė•¨ë˛”ė—ë„ ėļ”가할 눘 ė—†ėŠĩ니다.", "assets_count": "{count, plural, one {#氜} other {#氜}} 항ëĒŠ", + "assets_deleted_permanently": "{count}氜 항ëĒŠė´ 똁ęĩŦ렁ėœŧ로 ė‚­ė œë¨", + "assets_deleted_permanently_from_server": "{count}氜 항ëĒŠė´ ė„œë˛„ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œë¨", + "assets_downloaded_failed": "{count, plural, one {파ėŧ #氜 ë‹¤ėš´ëĄœë“œ ė™„ëŖŒ - {error}氜 ė‹¤íŒ¨} other {파ėŧ #氜 ë‹¤ėš´ëĄœë“œ ė™„ëŖŒ - {error}氜 ė‹¤íŒ¨}}", + "assets_downloaded_successfully": "{count, plural, one {파ėŧ #氜 ë‹¤ėš´ëĄœë“œ ė™„ëŖŒ} other {파ėŧ #氜 ë‹¤ėš´ëĄœë“œ ė™„ëŖŒ}}", "assets_moved_to_trash_count": "íœ´ė§€í†ĩėœŧ로 항ëĒŠ {count, plural, one {#氜} other {#氜}} ė´ë™ë¨", "assets_permanently_deleted_count": "{count, plural, one {#氜 항ëĒŠ} other {#氜 항ëĒŠ}}ė´ 똁ęĩŦ렁ėœŧ로 ė‚­ė œë¨", "assets_removed_count": "{count, plural, one {항ëĒŠ #氜} other {항ëĒŠ #氜}} ė œęą°ë¨", + "assets_removed_permanently_from_device": "{count}氜 항ëĒŠė´ ę¸°ę¸°ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œë¨", "assets_restore_confirmation": "íœ´ė§€í†ĩė˜ ëĒ¨ë“  항ëĒŠė„ ëŗĩė›í•˜ė‹œę˛ ėŠĩ니까? ė´ ėž‘ė—…ė€ 되돌ëĻ´ 눘 ė—†ėŠĩ니다! 누ëŊ된 항ëĒŠė€ ëŗĩė›ë˜ė§€ ė•ŠėŠĩ니다.", "assets_restored_count": "{count, plural, one {항ëĒŠ #氜} other {항ëĒŠ #氜}} ëŗĩė›ë¨", + "assets_restored_successfully": "항ëĒŠ {count}氜 ëŗĩė›ë¨", "assets_trashed": "íœ´ė§€í†ĩėœŧ로 항ëĒŠ {count}氜 ė´ë™ë¨", "assets_trashed_count": "íœ´ė§€í†ĩėœŧ로 항ëĒŠ {count, plural, one {#氜} other {#氜}} ė´ë™ë¨", + "assets_trashed_from_server": "ė„œë˛„ 항ëĒŠ {count}氜 íœ´ė§€í†ĩėœŧ로 ė´ë™ë¨", "assets_were_part_of_album_count": "ė•¨ë˛”ė— ė´ë¯¸ íŦ함된 {count, plural, one {항ëĒŠ} other {항ëĒŠ}}ėž…ë‹ˆë‹¤.", "assets_were_part_of_albums_count": "ė´ë¯¸ ė•¨ë˛”ė— íŦ함된 {count, plural, one {항ëĒŠ} other {항ëĒŠ}}ėž…ë‹ˆë‹¤.", "authorized_devices": "내 기기", @@ -608,48 +615,87 @@ "autoplay_slideshow": "ėŠŦëŧė´ë“œ ė‡ŧ ėžë™ ėžŦėƒ", "back": "뒤로", "back_close_deselect": "뒤로, ë‹Ģ기 또는 ė„ íƒ í•´ė œ", + "background_backup_running_error": "밹꡸ëŧėš´ë“œ ë°ąė—…ė´ ė§„í–‰ ė¤‘ėž…ë‹ˆë‹¤. ėˆ˜ë™ ë°ąė—…ė„ ė‹œėž‘í•  눘 ė—†ėŠĩ니다.", "background_location_permission": "밹꡸ëŧėš´ë“œ ėœ„ėš˜ ęļŒí•œ", "background_location_permission_content": "Immich가 밹꡸ëŧėš´ë“œė—ė„œ ė‹¤í–‰ 뤑ėŧ 때 ë„¤íŠ¸ė›ŒíŦëĨŧ ė „í™˜í•˜ë ¤ëŠ´ Wi-Fi ë„¤íŠ¸ė›ŒíŦ ė´ëĻ„ė„ í™•ė¸í•´ė•ŧ 하며, ė´ëĨŧ ėœ„í•´ 'ė •í™•í•œ ėœ„ėš˜' ęļŒí•œė„ í•­ėƒ í—ˆėšŠí•´ė•ŧ 합니다.", "background_options": "밹꡸ëŧėš´ë“œ ė˜ĩė…˜", "backup": "ë°ąė—…", + "backup_album_selection_page_albums_device": "ę¸°ę¸°ė˜ ė•¨ë˛” ({count})", "backup_album_selection_page_albums_tap": "탭하ė—Ŧ íŦ함, 두 번 탭하ė—Ŧ ė œė™¸", "backup_album_selection_page_assets_scatter": "각 항ëĒŠė€ ė—ŦëŸŦ ė•¨ë˛”ė— íŦ함될 눘 ėžˆėœŧ늰, ë°ąė—… ė§„í–‰ ė¤‘ė—ë„ ëŒ€ėƒ ė•¨ë˛”ė„ íŦ함하거나 ė œė™¸í•  눘 ėžˆėŠĩ니다.", "backup_album_selection_page_select_albums": "ė•¨ë˛” ė„ íƒ", "backup_album_selection_page_selection_info": "ė„ íƒí•œ ė•¨ë˛”", + "backup_album_selection_page_total_assets": "렄랴 항ëĒŠ", "backup_albums_sync": "ė•¨ë˛” 동기화 ë°ąė—…", + "backup_all": "ëĒ¨ë‘", + "backup_background_service_backup_failed_message": "항ëĒŠ ë°ąė—…ė— ė‹¤íŒ¨í–ˆėŠĩ니다. ë‹¤ė‹œ ė‹œë„í•˜ëŠ” 뤑â€Ļ", "backup_background_service_complete_notification": "항ëĒŠ ë°ąė—… ė™„ëŖŒ", + "backup_background_service_connection_failed_message": "ė„œë˛„ 뗰枰뗐 ė‹¤íŒ¨í–ˆėŠĩ니다. ë‹¤ė‹œ ė‹œë„í•˜ëŠ” 뤑â€Ļ", + "backup_background_service_current_upload_notification": "{filename} ė—…ëĄœë“œ 뤑", "backup_background_service_default_notification": "ėƒˆëĄœėš´ 항ëĒŠė„ í™•ė¸í•˜ëŠ” 뤑â€Ļ", + "backup_background_service_error_title": "ë°ąė—… 똤ëĨ˜", "backup_background_service_in_progress_notification": "항ëĒŠė„ ë°ąė—…í•˜ëŠ” 뤑â€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ė—…ëĄœë“œ ė‹¤íŒ¨", "backup_controller_page_albums": "ë°ąė—…í•  ė•¨ë˛”", + "backup_controller_page_background_app_refresh_disabled_content": "밹꡸ëŧėš´ë“œ ë°ąė—…ė„ ė‚ŦėšŠí•˜ë ¤ëŠ´ 네렕 > ėŧ반 > 밹꡸ëŧėš´ë“œ ė•ą ėƒˆëĄœ ęŗ ėš¨ė—ė„œ 밹꡸ëŧėš´ë“œ ė•ą ėƒˆëĄœ ęŗ ėš¨ė„ í™œė„ąí™”í•˜ė„¸ėš”.", + "backup_controller_page_background_app_refresh_disabled_title": "밹꡸ëŧėš´ë“œ ėƒˆëĄœ ęŗ ėš¨ ëš„í™œė„ąí™”ë¨", + "backup_controller_page_background_app_refresh_enable_button_text": "네렕ėœŧ로 ė´ë™", "backup_controller_page_background_battery_info_link": "네렕 방법", "backup_controller_page_background_battery_info_message": "ėĩœėƒė˜ 밹꡸ëŧėš´ë“œ ë°ąė—… 환ę˛Ŋė„ ėœ„í•´ Immichė˜ 밹꡸ëŧėš´ë“œ í™œë™ė„ ė œí•œí•˜ëŠ” 배터ëĻŦ ėĩœė í™” 기ëŠĨė„ ëš„í™œė„ąí™”í•˜ė„¸ėš”.\n\n기기마다 네렕 ë°Šë˛•ė— ė°¨ė´ę°€ ėžˆėœŧë¯€ëĄœ ė œėĄ°ė‚Ŧė—ė„œ 관련 ė •ëŗ´ëĨŧ ė°žė•„ëŗ´ė„¸ėš”.", "backup_controller_page_background_battery_info_ok": "í™•ė¸", "backup_controller_page_background_battery_info_title": "배터ëĻŦ ėĩœė í™”", + "backup_controller_page_background_charging": "ėļŠė „ ė¤‘ė—ë§Œ", + "backup_controller_page_background_configure_error": "밹꡸ëŧėš´ë“œ ė„œëš„ėŠ¤ ęĩŦė„ą ė‹¤íŒ¨", "backup_controller_page_background_delay": "냈 항ëĒŠ ë°ąė—… 맀뗰: {duration}", + "backup_controller_page_background_description": "ė•ąė„ ė—´ė§€ ė•Šęŗ ë„ 냈 항ëĒŠė„ ėžë™ėœŧ로 ë°ąė—…í•˜ë ¤ëŠ´ 밹꡸ëŧėš´ë“œ ė„œëš„ėŠ¤ëĨŧ í™œė„ąí™”í•˜ė„¸ėš”.", + "backup_controller_page_background_is_off": "ėžë™ 밹꡸ëŧėš´ë“œ ë°ąė—…ė„ ė‚ŦėšŠí•˜ė§€ ė•ŠėŠĩ니다.", + "backup_controller_page_background_is_on": "ėžë™ 밹꡸ëŧėš´ë“œ ë°ąė—…ė„ ė‚ŦėšŠ ė¤‘ėž…ë‹ˆë‹¤.", + "backup_controller_page_background_turn_off": "밹꡸ëŧėš´ë“œ ė„œëš„ėŠ¤ ëš„í™œė„ąí™”", + "backup_controller_page_background_turn_on": "밹꡸ëŧėš´ë“œ ė„œëš„ėŠ¤ í™œė„ąí™”", + "backup_controller_page_background_wifi": "Wi-Fiė—ė„œë§Œ", "backup_controller_page_backup": "ë°ąė—…", "backup_controller_page_backup_selected": "ė„ íƒë¨: ", "backup_controller_page_backup_sub": "ë°ąė—…ëœ ė‚Ŧė§„ 및 ë™ė˜ėƒ", + "backup_controller_page_created": "ėƒė„ąėŧ: {date}", + "backup_controller_page_desc_backup": "íŦ꡸ëŧėš´ë“œ ë°ąė—…ė„ í™œė„ąí™”í•˜ė—Ŧ ė•ąė„ ė‹œėž‘í•  때 냈 항ëĒŠė„ ė„œë˛„ė— ėžë™ėœŧ로 ė—…ëĄœë“œí•˜ė„¸ėš”.", "backup_controller_page_excluded": "ė œė™¸ë¨: ", + "backup_controller_page_failed": "ė‹¤íŒ¨ ({count})", + "backup_controller_page_filename": "파ėŧëĒ…: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ë°ąė—… ė •ëŗ´", "backup_controller_page_none_selected": "ė„ íƒëœ 항ëĒŠ ė—†ėŒ", "backup_controller_page_remainder": "ë‚¨ė€ 항ëĒŠ", "backup_controller_page_remainder_sub": "ë°ąė—… 대기 ė¤‘ė¸ ė‚Ŧė§„ 및 ë™ė˜ėƒ", "backup_controller_page_server_storage": "ė €ėžĨ ęŗĩ간", + "backup_controller_page_start_backup": "ë°ąė—… ė‹œėž‘", + "backup_controller_page_status_off": "ėžë™ íŦ꡸ëŧėš´ë“œ ë°ąė—…ė„ ė‚ŦėšŠí•˜ė§€ ė•ŠėŠĩ니다.", + "backup_controller_page_status_on": "ėžë™ íŦ꡸ëŧėš´ë“œ ë°ąė—…ė„ ė‚ŦėšŠ ė¤‘ėž…ë‹ˆë‹¤.", "backup_controller_page_storage_format": "{total} 뤑 {used} ė‚ŦėšŠ", "backup_controller_page_to_backup": "ë°ąė—…í•  ė•¨ë˛” ëĒŠëĄ", "backup_controller_page_total_sub": "ė„ íƒí•œ ė•¨ë˛”ė˜ ęŗ ėœ í•œ ė‚Ŧė§„ 및 ë™ė˜ėƒ", + "backup_controller_page_turn_off": "ëš„í™œė„ąí™”", + "backup_controller_page_turn_on": "í™œė„ąí™”", + "backup_controller_page_uploading_file_info": "파ėŧ ė •ëŗ´ ė—…ëĄœë“œ 뤑", + "backup_err_only_album": "뜠ėŧ한 ė•¨ë˛”ė€ ė‚­ė œí•  눘 ė—†ėŠĩ니다.", "backup_error_sync_failed": "ë™ę¸°í™”ė— ė‹¤íŒ¨í–ˆėŠĩ니다. ë°ąė—…ė„ ė§„í–‰í•  눘 ė—†ėŠĩ니다.", "backup_info_card_assets": "항ëĒŠ", + "backup_manual_cancelled": "ėˇ¨ė†Œë¨", + "backup_manual_in_progress": "ė—…ëĄœë“œę°€ ė´ë¯¸ ė§„í–‰ ė¤‘ėž…ë‹ˆë‹¤. ėž ė‹œ 후 ë‹¤ė‹œ ė‹œë„í•˜ė„¸ėš”", + "backup_manual_success": "ė„ąęŗĩ", + "backup_manual_title": "ė—…ëĄœë“œ ėƒíƒœ", "backup_options": "ë°ąė—… ė˜ĩė…˜", + "backup_options_page_title": "ë°ąė—… ė˜ĩė…˜", + "backup_setting_subtitle": "밹꡸ëŧėš´ë“œ 및 íŦ꡸ëŧėš´ë“œ ë°ąė—… ė„¤ė •ė„ 관ëĻŦ합니다.", "backup_settings_subtitle": "ė—…ëĄœë“œ ė„¤ė •ė„ 관ëĻŦ합니다.", + "backup_upload_details_page_more_details": "탭하ė—Ŧ ėƒė„¸ëŗ´ę¸°", "backward": "뒤로", - "battery_optimization_backup_reliability": "배터ëĻŦ ėĩœė í™”ëĨŧ í•´ė œí•˜ëŠ´ 밹꡸ëŧėš´ë“œ ë°ąė—…ė´ ë”ėšą ė•ˆė •ė ėœŧ로 ë™ėž‘í•  눘 ėžˆėŠĩ니다", "biometric_auth_enabled": "ėƒė˛´ ė¸ėĻė´ í™œė„ąí™”ë˜ė—ˆėŠĩ니다.", "biometric_locked_out": "ėƒė˛´ ė¸ėĻė´ ėŧė‹œė ėœŧ로 ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다.", "biometric_no_options": "ė‚ŦėšŠ 가ëŠĨ한 ėƒė˛´ ė¸ėĻ ė˜ĩė…˜ ė—†ėŒ", "biometric_not_available": "ė´ ę¸°ę¸°ė—ė„œëŠ” ėƒė˛´ ė¸ėĻė„ ė‚ŦėšŠí•  눘 ė—†ėŠĩ니다.", + "birthdate_saved": "ėƒë…„ė›”ėŧė´ ė €ėžĨë˜ė—ˆėŠĩ니다.", "birthdate_set_description": "ėƒë…„ė›”ėŧė€ ė‚Ŧė§„ ė´Ŧ똁 ë‹šė‹œ ė¸ëŦŧė˜ ë‚˜ė´ëĨŧ ęŗ„ė‚°í•˜ëŠ” 데 ė‚ŦėšŠëŠë‹ˆë‹¤.", "blurred_background": "흐ëϰ ë°°ę˛Ŋ", - "browse_templates": "템플ëĻŋ 둘ëŸŦëŗ´ę¸°", "bugs_and_feature_requests": "버그 ė œëŗ´ & 기ëŠĨ ėš”ė˛­", "build": "빌드", "build_image": "빌드 ė´ë¯¸ė§€", @@ -657,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "ëš„ėŠˇí•œ 항ëĒŠ {count, plural, one {#氜ëĨŧ} other {#氜ëĨŧ}} ėœ ė§€í•˜ė‹œę˛ ėŠĩ니까? ė´ ėž‘ė—…ė€ ëĒ¨ë“  ëš„ėŠˇí•œ 항ëĒŠė˜ ęˇ¸ëŖšė„ ė‚­ė œ ė—†ė´ ė •ëĻŦ합니다.", "bulk_trash_duplicates_confirmation": "ëš„ėŠˇí•œ 항ëĒŠ {count, plural, one {#氜ëĨŧ} other {#氜ëĨŧ}} íœ´ė§€í†ĩėœŧ로 ė´ë™í•˜ė‹œę˛ ėŠĩ니까? 각 ęˇ¸ëŖšė—ė„œ íŦ기가 가ėžĨ 큰 항ëĒŠė„ ė œė™¸í•œ ë‚˜ë¨¸ė§€ëĨŧ íœ´ė§€í†ĩėœŧ로 ė´ë™í•Šë‹ˆë‹¤.", "buy": "Immich ęĩŦ매", + "cache_settings_clear_cache_button": "ėēė‹œ ė§€ėš°ę¸°", + "cache_settings_clear_cache_button_title": "ė•ą ėēė‹œëĨŧ ė§€ė›ë‹ˆë‹¤. ė´ ėž‘ė—…ė€ ėēė‹œę°€ ë‹¤ė‹œ ėƒė„ąë  ë•ŒęšŒė§€ ė•ą ė„ąëŠĨ뗐 ėƒë‹ší•œ 똁í–Ĩė„ ë¯¸ėš  눘 ėžˆėŠĩ니다.", + "cache_settings_duplicated_assets_clear_button": "ė§€ėš°ę¸°", + "cache_settings_duplicated_assets_subtitle": "ė œė™¸ëœ ė‚Ŧė§„ 및 ë™ė˜ėƒ", + "cache_settings_duplicated_assets_title": "ëš„ėŠˇí•œ 항ëĒŠ ({count})", + "cache_settings_statistics_album": "ëŧė´ë¸ŒëŸŦëĻŦ ė„Ŧ네ėŧ", + "cache_settings_statistics_full": "렄랴 ė´ë¯¸ė§€", + "cache_settings_statistics_shared": "ęŗĩ뜠 ė•¨ë˛” ė„Ŧ네ėŧ", + "cache_settings_statistics_thumbnail": "ė„Ŧ네ėŧ", + "cache_settings_statistics_title": "ėēė‹œ ė‚ŦėšŠëĨ ", + "cache_settings_subtitle": "Immich ëĒ¨ë°”ėŧ ė•ąė˜ ėēė‹ą ë™ėž‘ ė œė–´", + "cache_settings_tile_subtitle": "로ėģŦ ėŠ¤í† ëĻŦė§€ ë™ėž‘ ė œė–´", + "cache_settings_tile_title": "로ėģŦ ėŠ¤í† ëĻŦė§€", + "cache_settings_title": "ėēė‹œ 네렕", "camera": "ėš´ëŠ”ëŧ", "camera_brand": "ėš´ëŠ”ëŧ ė œėĄ°ė‚Ŧ", "camera_model": "ėš´ëŠ”ëŧ ëĒ¨ë¸", @@ -669,10 +729,10 @@ "cannot_update_the_description": "네ëĒ…ė„ ëŗ€ę˛Ŋ할 눘 ė—†ėŠĩ니다.", "cast": "ėēėŠ¤íŠ¸", "cast_description": "ė‚ŦėšŠ 가ëŠĨ한 ėēėŠ¤íŠ¸ 기기 ęĩŦė„ą", - "change": "ëŗ€ę˛Ŋ", "change_date": "ë‚ ė§œ ëŗ€ę˛Ŋ", "change_description": "네ëĒ… ëŗ€ę˛Ŋ", "change_display_order": "í‘œė‹œ ėˆœė„œ ëŗ€ę˛Ŋ", + "change_expiration_time": "ë§ŒëŖŒėŧ ëŗ€ę˛Ŋ", "change_location": "ėœ„ėš˜ ëŗ€ę˛Ŋ", "change_name": "ė´ëĻ„ ëŗ€ę˛Ŋ", "change_name_successfully": "ė´ëĻ„ė´ ëŗ€ę˛Ŋë˜ė—ˆėŠĩ니다.", @@ -686,13 +746,17 @@ "change_password_form_password_mismatch": "비밀번호가 ėŧėš˜í•˜ė§€ ė•ŠėŠĩ니다.", "change_password_form_reenter_new_password": "냈 비밀번호 í™•ė¸", "change_pin_code": "PIN ėŊ”드 ëŗ€ę˛Ŋ", + "change_trigger": "트ëĻŦęą° ëŗ€ę˛Ŋ", + "change_trigger_prompt": "트ëĻŦęą°ëĨŧ ëŗ€ę˛Ŋí•˜ė‹œę˛ ėŠĩ니까? ė´ë ‡ę˛Œ 하면 ę¸°ėĄ´ė˜ ëĒ¨ë“  ė•Ąė…˜ęŗŧ 필터가 ė œęą°ëŠë‹ˆë‹¤.", "change_your_password": "ė‚ŦėšŠėž ęŗ„ė •ė˜ 비밀번호ëĨŧ ëŗ€ę˛Ŋ합니다.", "changed_visibility_successfully": "ėˆ¨ęš€ ė—Ŧëļ€ę°€ ëŗ€ę˛Ŋë˜ė—ˆėŠĩ니다.", "charging": "ėļŠė „ 뤑", "charging_requirement_mobile_backup": "밹꡸ëŧėš´ë“œ ë°ąė—…ė€ 기기가 ėļŠė „ 뤑ėŧ 때 ė§„í–‰ëŠë‹ˆë‹¤.", + "check_corrupt_asset_backup": "ë°ąė—…ëœ 항ëĒŠė˜ ė†ėƒ ė—Ŧëļ€ í™•ė¸", + "check_corrupt_asset_backup_button": "í™•ė¸ ėˆ˜í–‰", + "check_corrupt_asset_backup_description": "ė´ 검ė‚Ŧ는 ëĒ¨ë“  항ëĒŠė´ ë°ąė—…ëœ 후 Wi-Fi가 ė—°ę˛°ëœ ėƒíƒœė—ė„œë§Œ ė‹¤í–‰í•˜ė„¸ėš”. ė´ ėž‘ė—…ė€ ëLJ ëļ„ ė •ë„ ė†Œėš”ë  눘 ėžˆėŠĩ니다.", "check_logs": "로그 í™•ė¸", "checksum": "랴íŦė„Ŧ", - "choose": "ė„ íƒ", "choose_matching_people_to_merge": "ëŗ‘í•Ší•  ė¸ëŦŧ ė„ íƒ", "city": "ë„ė‹œ", "cleanup_confirm_description": "Immich ė„œë˛„ė—ė„œ ė•ˆė „í•˜ę˛Œ ë°ąė—…ëœ 항ëĒŠ {count}氜({date} ė´ė „ė— ėƒė„ąë¨)ëĨŧ ė°žė•˜ėŠĩ니다. ė´ ę¸°ę¸°ė—ė„œ 로ėģŦ ëŗĩė‚Ŧëŗĩė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", @@ -710,10 +774,11 @@ "clear": "ė§€ėš°ę¸°", "clear_all": "ëĒ¨ë‘ ė§€ėš°ę¸°", "clear_all_recent_searches": "ę˛€ėƒ‰ 기록 렄랴 ė‚­ė œ", - "clear_failed_count": "ė‹¤íŒ¨ 항ëĒŠ ė‚­ė œ ({count})", "clear_file_cache": "파ėŧ ėēė‹œ ė§€ėš°ę¸°", "clear_message": "ëŠ”ė‹œė§€ ė§€ėš°ę¸°", "clear_value": "값 ė§€ėš°ę¸°", + "client_cert_dialog_msg_confirm": "í™•ė¸", + "client_cert_enter_password": "비밀번호 ėž…ë Ĩ", "client_cert_import": "ę°€ė ¸ė˜¤ę¸°", "client_cert_import_success_msg": "클ëŧė´ė–¸íŠ¸ ė¸ėĻė„œ ę°€ė ¸ė˜¤ę¸° ė™„ëŖŒ", "client_cert_invalid_msg": "ė¸ėĻė„œę°€ ėœ íš¨í•˜ė§€ ė•Šęą°ë‚˜ 비밀번호가 ė˜Ŧ바ëĨ´ė§€ ė•ŠėŒ", @@ -722,10 +787,12 @@ "client_cert_remove_msg": "클ëŧė´ė–¸íŠ¸ ė¸ėĻė„œ ė œęą°ë¨", "client_cert_subtitle": "ė¸ėĻė„œ ę°€ė ¸ė˜¤ę¸°/ė œęą°ëŠ” ëĄœęˇ¸ė¸ ė „ė—ë§Œ 가ëŠĨ하며, PKCS12 (.p12, .pfx) í˜•ė‹ë§Œ ė§€ė›í•Šë‹ˆë‹¤.", "client_cert_title": "SSL 클ëŧė´ė–¸íŠ¸ ė¸ėĻė„œ (ė‹¤í—˜ė )", + "clockwise": "ė‹œęŗ„ ë°Ší–Ĩ", "close": "ë‹Ģ기", "collapse": "렑揰", "collapse_all": "ëĒ¨ë‘ 렑揰", "color": "ėƒ‰ėƒ", + "color_theme": "테마 ėƒ‰ėƒ", "command": "ëĒ…ë š", "command_palette_prompt": "íŽ˜ė´ė§€, ėž‘ė—…, ëĒ…ë šė„ ëš ëĨ´ę˛Œ ė°žę¸°", "command_palette_to_close": "ë‹Ģ기", @@ -738,7 +805,6 @@ "comments_are_disabled": "ëŒ“ę¸€ė´ ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다.", "common_create_new_album": "ė•¨ë˛” ėƒė„ą", "completed": "ė™„ëŖŒë¨", - "configuration": "네렕", "confirm": "í™•ė¸", "confirm_admin_password": "관ëĻŦėž 비밀번호 í™•ė¸", "confirm_delete_face": "항ëĒŠė—ė„œ {name}ė˜ ė–ŧęĩ´ė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", @@ -753,16 +819,19 @@ "contain": "맞ėļ¤", "context": "ëŦ¸ë§Ĩ", "continue": "ęŗ„ė†", - "control_bottom_app_bar_add_tags": "태그 ėļ”ę°€", + "control_bottom_app_bar_create_new_album": "ė•¨ë˛” ėƒė„ą", + "control_bottom_app_bar_delete_from_immich": "Immichė—ė„œ ė‚­ė œ", "control_bottom_app_bar_delete_from_local": "ę¸°ę¸°ė—ė„œ ė‚­ė œ", "control_bottom_app_bar_edit_location": "ėœ„ėš˜ íŽ¸ė§‘", "control_bottom_app_bar_edit_time": "ë‚ ė§œ ëŗ€ę˛Ŋ", + "control_bottom_app_bar_share_link": "ęŗĩ뜠 링íŦ", + "control_bottom_app_bar_share_to": "ęŗĩ뜠 ëŒ€ėƒ", "control_bottom_app_bar_trash_from_immich": "íœ´ė§€í†ĩ", "copied_image_to_clipboard": "ė´ë¯¸ė§€ę°€ 클ëĻŊëŗ´ë“œė— ëŗĩė‚Ŧë˜ė—ˆėŠĩ니다.", "copied_to_clipboard": "클ëĻŊëŗ´ë“œė— ëŗĩė‚Ŧë˜ė—ˆėŠĩ니다!", "copy_error": "똤ëĨ˜ ëŗĩė‚Ŧ", + "copy_file_path": "파ėŧ ę˛Ŋ로 ëŗĩė‚Ŧ", "copy_image": "ė´ë¯¸ė§€ ëŗĩė‚Ŧ", - "copy_json": "JSON ëŗĩė‚Ŧ", "copy_link": "링íŦ ëŗĩė‚Ŧ", "copy_link_to_clipboard": "링íŦëĨŧ 클ëĻŊëŗ´ë“œė— ëŗĩė‚Ŧ", "copy_password": "비밀번호 ëŗĩė‚Ŧ", @@ -780,6 +849,7 @@ "create_link_to_share": "ęŗĩ뜠 링íŦ ėƒė„ą", "create_link_to_share_description": "링íŦ가 ėžˆëŠ” ę˛Ŋ뚰 누ęĩŦ나 ė„ íƒí•œ ė‚Ŧė§„ė„ ëŗŧ 눘 ėžˆėŠĩ니다.", "create_new": "ėƒˆëĄœ 만들기", + "create_new_face": "냈 ė–ŧęĩ´ ėƒė„ą", "create_new_person": "ė¸ëŦŧ ėƒė„ą", "create_new_person_hint": "ė„ íƒí•œ 항ëĒŠė˜ ė¸ëŦŧė„ 냈 ė¸ëŦŧ로 ëŗ€ę˛Ŋ", "create_new_user": "냈 ė‚ŦėšŠėž ėƒė„ą", @@ -796,33 +866,37 @@ "created_at": "ėƒė„ąë¨", "creating_linked_albums": "ė—°ę˛°ëœ ė•¨ë˛” ėƒė„ą 뤑...", "crop": "ėžëĨ´ę¸°", + "crop_aspect_ratio_fixed": "ęŗ ė •", "crop_aspect_ratio_free": "링렑 ėĄ°ė ˆ", "crop_aspect_ratio_original": "ė›ëŗ¸", "crop_aspect_ratio_square": "ė •ė‚Ŧ각형", + "curated_object_page_title": "ė‚ŦëŦŧ", "current_device": "현ėžŦ 기기", "current_pin_code": "현ėžŦ PIN ėŊ”드", "current_server_address": "현ėžŦ ė„œë˛„ ėŖŧė†Œ", "custom_date": "ë‚ ė§œ ė„ íƒ", "custom_locale": "ė‚ŦėšŠėž 맀렕 로ėŧ€ėŧ", "custom_locale_description": "떏떴 및 맀뗭 ė„ íƒė— 따ëĨ¸ ë‚ ė§œ, ė‹œę°„ 및 ėˆĢėž í˜•ė‹ 맀렕", + "custom_url": "ė‚ŦėšŠėž 맀렕 URL", "cutoff_date_description": "ė„ íƒí•œ ę¸°ę°„ė˜ ė‚Ŧė§„ė„ ėœ ė§€í•Šë‹ˆë‹¤â€Ļ", "cutoff_day": "{count, plural, one {ėŧ} other {ėŧ}}", "cutoff_year": "{count, plural, one {년} other {년}}", + "daily_title_text_date": "Mė›” dėŧ EEEE", + "daily_title_text_date_year": "yyyy년 Mė›” dėŧ EEEE", "dark": "다íŦ", "dark_theme": "다íŦ 테마 ė „í™˜", "date": "ë‚ ė§œ", "date_after": "ë‹¤ėŒ ë‚ ė§œ ė´í›„", "date_and_time": "ë‚ ė§œ 및 ė‹œę°„", "date_before": "ë‹¤ėŒ ë‚ ė§œ ė „", - "date_of_birth": "ėƒë…„ė›”ėŧ", + "date_format": "yyyy년 Mė›” dėŧ EEEE â€ĸ a h:mm", "date_of_birth_saved": "ėƒë…„ė›”ėŧė´ ė €ėžĨë˜ė—ˆėŠĩ니다.", "date_range": "ë‚ ė§œ ë˛”ėœ„", - "date_time_original": "ė›ëŗ¸ ė´Ŧ똁 ėŧė‹œ", "day": "ėŧ", "days": "ėŧ", "deduplicate_all": "ëĒ¨ë‘ ė‚­ė œ", - "default_quality_subtitle": "ęŗĩ뜠 ė‹œ ė‚ŦėšŠí•  ę¸°ëŗ¸ í™”ė§ˆėž…ë‹ˆë‹¤. ęŗĩ뜠 버íŠŧė„ 描枌 누ëĨ´ëŠ´ 매번 í™”ė§ˆė„ ė„ íƒí•  눘 ėžˆėŠĩ니다.", - "default_share_quality": "ę¸°ëŗ¸ ęŗĩ뜠 í™”ė§ˆ", + "default_locale": "ę¸°ëŗ¸ 로ėŧ€ėŧ", + "default_locale_description": "브ëŧėš°ė € 로ėŧ€ėŧ 네렕뗐 따ëŧ ë‚ ė§œ 및 ėˆĢėž í˜•ė‹ė„ ė§€ė •í•Šë‹ˆë‹¤", "delete": "ė‚­ė œ", "delete_action_confirmation_message": "ė´ 항ëĒŠė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까? ė„œë˛„ė—ė„œëŠ” 항ëĒŠė„ íœ´ė§€í†ĩėœŧ로 ė´ë™ė‹œí‚¤ëŠ°, 로ėģŦė—ė„œë„ ė‚­ė œí•  ę˛ƒė¸ė§€ í™•ė¸ ëŠ”ė‹œė§€ę°€ í‘œė‹œëŠë‹ˆë‹¤.", "delete_action_prompt": "{count}氜 항ëĒŠ ė‚­ė œë¨", @@ -831,6 +905,8 @@ "delete_dialog_alert": "ė´ 항ëĒŠë“¤ė´ Immich뙀 ę¸°ę¸°ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", "delete_dialog_alert_local": "ė´ 항ëĒŠë“¤ė´ ę¸°ę¸°ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤. ė„œë˛„ė—ė„œëŠ” ė‚­ė œë˜ė§€ ė•ŠėŠĩ니다.", "delete_dialog_alert_local_non_backed_up": "ėŧëļ€ í•­ëĒŠė´ ė„œë˛„ė— ë°ąė—…ë˜ė§€ ė•Šė•˜ėœŧ늰, ę¸°ę¸°ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", + "delete_dialog_alert_remote": "ė´ 항ëĒŠë“¤ė´ ė„œë˛„ė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", + "delete_dialog_ok_force": "ëŦ´ė‹œí•˜ęŗ  ė‚­ė œ", "delete_dialog_title": "똁ęĩŦ렁ėœŧ로 ė‚­ė œ", "delete_duplicates_confirmation": "ëš„ėŠˇí•œ 항ëĒŠë“¤ė„ 똁ęĩŦ렁ėœŧ로 ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "delete_face": "ė–ŧęĩ´ ė‚­ė œ", @@ -849,16 +925,21 @@ "delete_tag_confirmation_prompt": "{tagName} 태그ëĨŧ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "delete_user": "ė‚ŦėšŠėž ė‚­ė œ", "deleted_shared_link": "ęŗĩ뜠 링íŦ가 ė‚­ė œë˜ė—ˆėŠĩ니다.", + "deletes_missing_assets": "ë””ėŠ¤íŦ뗐 ėĄ´ėžŦí•˜ė§€ ė•ŠëŠ” 항ëĒŠ ė œęą°", "description": "네ëĒ…", + "description_input_hint_text": "네ëĒ… ėļ”ę°€...", + "description_input_submit_error": "네ëĒ… ëŗ€ę˛Ŋ 뤑 똤ëĨ˜ę°€ ë°œėƒí–ˆėŠĩ니다. ëĄœęˇ¸ė—ė„œ ėžė„¸í•œ ë‚´ėšŠė„ í™•ė¸í•˜ė„¸ėš”.", "deselect_all": "ëĒ¨ë‘ ė„ íƒ í•´ė œ", "details": "ėƒė„¸ ė •ëŗ´", "direction": "ë°Ší–Ĩ", "disable": "ëš„í™œė„ąí™”", "disabled": "ëš„í™œė„ąí™”", + "disallow_edits": "ëˇ°ė–´ëĄœ 네렕", "discord": "Discord", "discover": "íƒėƒ‰", "discovered_devices": "ėŖŧëŗ€ 기기", "dismiss_all_errors": "ëĒ¨ë“  똤ëĨ˜ ëŦ´ė‹œ", + "dismiss_error": "똤ëĨ˜ ëŦ´ė‹œ", "display_options": "í‘œė‹œ ė˜ĩė…˜", "display_order": "í‘œė‹œ ėˆœė„œ", "display_original_photos": "ė›ëŗ¸ ė‚Ŧė§„ í‘œė‹œ", @@ -867,9 +948,11 @@ "documentation": "ëŦ¸ė„œ", "done": "ė™„ëŖŒ", "download": "ë‹¤ėš´ëĄœë“œ", + "download_action_prompt": "항ëĒŠ {count}氜 ë‹¤ėš´ëĄœë“œ 뤑...", "download_canceled": "ë‹¤ėš´ëĄœë“œę°€ ėˇ¨ė†Œë˜ė—ˆėŠĩ니다.", "download_complete": "ë‹¤ė€ëĄœë“œę°€ ė™„ëŖŒë˜ė—ˆėŠĩ니다.", "download_enqueue": "ëŒ€ę¸°ė—´ė— ë‹¤ėš´ëĄœë“œ", + "download_error": "ë‹¤ėš´ëĄœë“œ 똤ëĨ˜", "download_failed": "ë‹¤ėš´ëĄœë“œ ė‹¤íŒ¨", "download_finished": "ë‹¤ėš´ëĄœë“œę°€ ė™„ëŖŒë˜ė—ˆėŠĩ니다.", "download_include_embedded_motion_videos": "ëĒ¨ė…˜ íŦ토 똁냁", @@ -879,15 +962,15 @@ "download_paused": "ë‹¤ėš´ëĄœë“œ ėŧė‹œ ė¤‘ė§€ë¨", "download_settings": "ë‹¤ėš´ëĄœë“œ", "download_settings_description": "파ėŧ ë‹¤ėš´ëĄœë“œ ė„¤ė •ė„ 관ëĻŦ합니다.", + "download_started": "ë‹¤ėš´ëĄœë“œę°€ ė‹œėž‘ë˜ė—ˆėŠĩ니다.", + "download_sucess": "ë‹¤ėš´ëĄœë“œę°€ ė™„ëŖŒë˜ė—ˆėŠĩ니다.", + "download_sucess_android": "항ëĒŠė´ DCIM/Immich í´ë”ė— ë‹¤ėš´ëĄœë“œë˜ė—ˆėŠĩ니다.", "download_waiting_to_retry": "ėžŦė‹œë„ 대기 뤑", "downloading": "ë‹¤ėš´ëĄœë“œ", "downloading_asset_filename": "{filename} ë‹¤ėš´ëĄœë“œ 뤑...", "downloading_from_icloud": "iCloudė—ė„œ ë‹¤ėš´ëĄœë“œ 뤑", "downloading_media": "ë¯¸ë””ė–´ ë‹¤ėš´ëĄœë“œ 뤑", - "drag_to_reorder": "드래그하ė—Ŧ ėˆœė„œ ëŗ€ę˛Ŋ", "drop_files_to_upload": "ė•„ëŦ´ ęŗŗė—ë‚˜ 파ėŧė„ 드롭하ė—Ŧ ė—…ëĄœë“œ", - "duplicate": "ëŗĩ렜", - "duplicate_workflow": "ė›ŒíŦí”ŒëĄœėš° ëŗĩ렜", "duplicates": "ëš„ėŠˇí•œ 항ëĒŠ", "duplicates_description": "각 ęˇ¸ëŖšė„ í™•ė¸í•˜ęŗ , ëš„ėŠˇí•œ 항ëĒŠė„ ė„ íƒí•´ ė‚­ė œ ė—Ŧëļ€ëĨŧ ę˛°ė •í•˜ė„¸ėš”.", "duration": "기간", @@ -899,7 +982,9 @@ "edit_date_and_time": "ë‚ ė§œ 및 ė‹œę°„ ëŗ€ę˛Ŋ", "edit_date_and_time_action_prompt": "항ëĒŠ {count}氜 ë‚ ė§œę°€ ëŗ€ę˛Ŋ됨", "edit_date_and_time_by_offset": "ë‚ ė§œëĨŧ ė˜¤í”„ė…‹ėœŧ로 ëŗ€ę˛Ŋ", + "edit_date_and_time_by_offset_interval": "냈 ë‚ ė§œ ë˛”ėœ„: {from} - {to}", "edit_description": "네ëĒ… íŽ¸ė§‘", + "edit_description_prompt": "냈 네ëĒ…ė„ ėž…ë Ĩí•˜ė„¸ėš”:", "edit_exclusion_pattern": "ė œė™¸ ęˇœėš™ ėˆ˜ė •", "edit_faces": "ė–ŧęĩ´ ėˆ˜ė •", "edit_key": "키 ėˆ˜ė •", @@ -914,6 +999,9 @@ "edit_user": "ė‚ŦėšŠėž ėˆ˜ė •", "edit_workflow": "ė›ŒíŦ플로 íŽ¸ė§‘", "editor": "íŽ¸ė§‘ę¸°", + "editor_close_without_save_prompt": "ëŗ€ę˛Ŋ ė‚Ŧí•­ė´ ė €ėžĨë˜ė§€ ė•ŠėŠĩ니다.", + "editor_close_without_save_title": "íŽ¸ė§‘ė„ ėĸ…ëŖŒí•˜ė‹œę˛ ėŠĩ니까?", + "editor_confirm_reset_all_changes": "ëĒ¨ë“  ėˆ˜ė •ė‚Ŧí•­ė„ ė´ˆę¸°í™”í•˜ė‹œę˛ ėŠĩ니까?", "editor_discard_edits_confirm": "ëŗ€ę˛Ŋ ė‚Ŧ항 ėˇ¨ė†Œ", "editor_discard_edits_prompt": "ė €ėžĨë˜ė§€ ė•Šė€ ëŗ€ę˛Ŋė‚Ŧí•­ė´ ėžˆėŠĩ니다. ëŗ€ę˛Ŋė‚Ŧí•­ė„ ėˇ¨ė†Œí•˜ę˛ ėŠĩ니까?", "editor_discard_edits_title": "íŽ¸ė§‘ė„ ėˇ¨ė†Œí• ęšŒėš”?", @@ -942,7 +1030,9 @@ "enter_your_pin_code": "PIN ėŊ”드 ėž…ë Ĩ", "enter_your_pin_code_subtitle": "ėž ę¸ˆ í´ë”ė— ė ‘ęˇŧ하려면 PIN ėŊ”드ëĨŧ ėž…ë Ĩí•˜ė„¸ėš”.", "error": "똤ëĨ˜", + "error_change_sort_album": "ė•¨ë˛” í‘œė‹œ ėˆœė„œ ëŗ€ę˛Ŋ ė‹¤íŒ¨", "error_delete_face": "항ëĒŠė—ė„œ ė–ŧęĩ´ ė‚­ė œ 뤑 똤ëĨ˜ ë°œėƒ", + "error_getting_places": "ėžĨė†Œ 로딩 똤ëĨ˜", "error_loading_albums": "ė•¨ë˛” 로딩 똤ëĨ˜", "error_loading_image": "ė´ë¯¸ė§€ 로딩 똤ëĨ˜", "error_loading_partners": "파트너 로딩 똤ëĨ˜: {error}", @@ -982,7 +1072,6 @@ "failed_to_remove_product_key": "ė œí’ˆ 키 ė œęą°ė— ė‹¤íŒ¨í–ˆėŠĩ니다.", "failed_to_reset_pin_code": "PIN ėŊ”드 ė´ˆę¸°í™” ė‹¤íŒ¨", "failed_to_stack_assets": "항ëĒŠ ėŠ¤íƒė— ė‹¤íŒ¨í–ˆėŠĩ니다.", - "failed_to_tag_assets": "항ëĒŠė— 태그ëĨŧ ėļ”ę°€í•˜ė§€ ëĒģ했ėŠĩ니다", "failed_to_unstack_assets": "항ëĒŠ ėŠ¤íƒ í’€ę¸°ė— ė‹¤íŒ¨í–ˆėŠĩ니다.", "failed_to_update_notification_status": "ė•ŒëĻŧ ėƒíƒœ ė—…ë°ė´íŠ¸ ė‹¤íŒ¨", "incorrect_email_or_password": "ėž˜ëĒģ된 ė´ëŠ”ėŧ 또는 비밀번호", @@ -1081,10 +1170,18 @@ "exif": "EXIF", "exif_bottom_sheet_description": "네ëĒ… ėļ”ę°€...", "exif_bottom_sheet_description_error": "네ëĒ… ëŗ€ę˛Ŋ 뤑 똤ëĨ˜ ë°œėƒ", + "exif_bottom_sheet_details": "ėƒė„¸ ė •ëŗ´", + "exif_bottom_sheet_location": "ėœ„ėš˜", "exif_bottom_sheet_no_description": "네ëĒ… ė—†ėŒ", + "exif_bottom_sheet_people": "ė¸ëŦŧ", + "exif_bottom_sheet_person_add_person": "ė´ëĻ„ ėļ”ę°€", "exit_slideshow": "ėŠŦëŧė´ë“œ ė‡ŧ ėĸ…ëŖŒ", "expand": "확ėžĨ", "expand_all": "ëĒ¨ë‘ 확ėžĨ", + "experimental_settings_new_asset_list_subtitle": "ė§„í–‰ 뤑", + "experimental_settings_new_asset_list_title": "냈 ė‚Ŧė§„ ë°°ė—´ ė‚ŦėšŠ (ė‹¤í—˜ė )", + "experimental_settings_subtitle": "ëŗ¸ė¸ ėą…ėž„ í•˜ė— ė‚ŦėšŠí•˜ė„¸ėš”!", + "experimental_settings_title": "ė‹¤í—˜ė ", "expire_after": "ë§ŒëŖŒėŧ 네렕", "expired": "ë§ŒëŖŒë¨", "expires_date": "{date} ë§ŒëŖŒ", @@ -1094,40 +1191,41 @@ "export_as_json": "JSONėœŧ로 ë‚´ëŗ´ë‚´ę¸°", "export_database": "ë°ė´í„°ë˛ ė´ėŠ¤ ë‚´ëŗ´ë‚´ę¸°", "export_database_description": "SQLite ë°ė´í„°ë˛ ė´ėŠ¤ ë‚´ëŗ´ë‚´ę¸°", - "exposure_time": "노ėļœ ė‹œę°„", "extension": "확ėžĨėž", "external": "뙏ëļ€", "external_libraries": "뙏ëļ€ ëŧė´ë¸ŒëŸŦëĻŦ", "external_network": "뙏ëļ€ ë„¤íŠ¸ė›ŒíŦ", "external_network_sheet_info": "ė„ í˜¸í•˜ëŠ” Wi-Fi ë„¤íŠ¸ė›ŒíŦ뗐 ė—°ę˛°ë˜ė–´ ėžˆė§€ ė•Šė€ ę˛Ŋ뚰, ė•ąė€ ė•„ëž˜ ë‚˜ė—´ëœ URL 뤑 ėœ„ė—ė„œëļ€í„° ėˆœė„œëŒ€ëĄœ ė‚ŦėšŠ 가ëŠĨ한 ė˛Ģ ë˛ˆė§¸ URLė„ ė‚ŦėšŠí•˜ė—Ŧ ė—°ę˛°í•Šë‹ˆë‹¤.", - "f_number": "ėĄ°ëĻŦ氜 값(F값)", "face_unassigned": "ė•Œ 눘 ė—†ėŒ", "failed": "ė‹¤íŒ¨í•¨", "failed_count": "ė‹¤íŒ¨: {count}", "failed_to_authenticate": "ė¸ėĻė— ė‹¤íŒ¨í–ˆėŠĩ니다.", - "failed_to_delete_file": "파ėŧė„ ė‚­ė œí•˜ė§€ ëĒģ했ėŠĩ니다", "failed_to_load_assets": "항ëĒŠ 로드 ė‹¤íŒ¨", "failed_to_load_folder": "폴더 로드 ė‹¤íŒ¨", "favorite": "ėĻę˛¨ė°žę¸°", "favorite_action_prompt": "ėĻę˛¨ė°žę¸°ė— {count}氜 항ëĒŠ ėļ”가됨", "favorite_or_unfavorite_photo": "ėĻę˛¨ė°žę¸° ėļ”ę°€/ė œęą°", "favorites": "ėĻę˛¨ė°žę¸°", + "favorites_page_no_favorites": "ėĻę˛¨ė°žę¸°ëœ 항ëĒŠ ė—†ėŒ", "feature_photo_updated": "대표 ė‚Ŧė§„ ė—…ë°ė´íŠ¸ë¨", "features": "기ëŠĨ", + "features_in_development": "개발 ė¤‘ė¸ 기ëŠĨ", "features_setting_description": "ė‚Ŧė§„ 및 ë™ė˜ėƒ 관ëĻŦ 기ëŠĨė„ ė„¤ė •í•Šë‹ˆë‹¤.", "file_name_or_extension": "파ėŧëĒ… 또는 확ėžĨėž", "file_name_text": "파ėŧëĒ…", + "file_name_with_value": "파ėŧëĒ…: {file_name}", "file_size": "파ėŧ íŦ기", "filename": "파ėŧëĒ…", "filetype": "파ėŧ í˜•ė‹", "filter": "필터", + "filter_description": "ëŒ€ėƒ ėžė‚°ė„ 필터링하기 ėœ„í•œ ėĄ°ęą´", "filter_people": "ė¸ëŦŧ 필터", "filter_places": "ėžĨė†Œ 필터", "filter_tags": "태그 필터", "filters": "필터", + "find_them_fast": "ė´ëĻ„ėœŧ로 ę˛€ėƒ‰í•˜ė—Ŧ ëš ëĨ´ę˛Œ ė°žę¸°", "first": "ė˛Ģ ë˛ˆė§¸", "fix_incorrect_match": "ėž˜ëĒģ된 ëļ„ëĨ˜ ėˆ˜ė •", - "focal_length": "봈렐 ęą°ëĻŦ", "folder": "폴더", "folder_not_found": "폴더ëĨŧ ė°žė„ 눘 ė—†ėŒ", "folders": "폴더", @@ -1138,7 +1236,6 @@ "free_up_space_description": "ë°ąė—…ëœ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ ę¸°ę¸°ė˜ íœ´ė§€í†ĩėœŧ로 ė´ë™í•˜ė—Ŧ ė €ėžĨ ęŗĩę°„ė„ í™•ëŗ´í•˜ė„¸ėš”. ė›ëŗ¸ 파ėŧė€ ė„œë˛„ė— ė•ˆė „í•˜ę˛Œ ëŗ´ę´€ëŠë‹ˆë‹¤.", "free_up_space_settings_subtitle": "ę¸°ę¸°ė˜ ė €ėžĨ ęŗĩę°„ė„ í™•ëŗ´í•Šë‹ˆë‹¤.", "full_path": "렄랴 ę˛Ŋ로: {path}", - "full_path_or_folder": "렄랴 ę˛Ŋ로 또는 폴더", "gcast_enabled": "ęĩŦ글 ėēėŠ¤íŠ¸", "gcast_enabled_description": "ė´ 기ëŠĨė€ Googleė˜ 뙏ëļ€ ëĻŦė†ŒėŠ¤ëĨŧ ė‚ŦėšŠí•Šë‹ˆë‹¤.", "general": "ėŧ반", @@ -1159,6 +1256,7 @@ "group_owner": "ė†Œėœ ėžëĄœ ęˇ¸ëŖší™”", "group_places_by": "ë‹¤ėŒėœŧ로 ėžĨė†Œ ęˇ¸ëŖší™”â€Ļ", "group_year": "ė—°ë„ëĄœ ęˇ¸ëŖší™”", + "haptic_feedback_switch": "햅틱 í”ŧ드백 í™œė„ąí™”", "haptic_feedback_title": "햅틱 í”ŧ드백", "has_quota": "할당량", "hash_asset": "항ëĒŠ í•´ė‹ą", @@ -1179,12 +1277,29 @@ "hide_schema": "ėŠ¤í‚¤ë§ˆ 눍揰揰", "hide_text_recognition": "í…ėŠ¤íŠ¸ ė¸ė‹ 눍揰揰", "hide_unnamed_people": "ė´ëĻ„ ė—†ëŠ” ė¸ëŦŧ 눍揰揰", + "home_page_add_to_album_conflicts": "{album} ė•¨ë˛”ė— 항ëĒŠ {added}개가 ėļ”ę°€ë˜ė—ˆėŠĩ니다. 항ëĒŠ {failed}개는 ė•¨ë˛”ė— ė´ë¯¸ ėĄ´ėžŦ합니다.", + "home_page_add_to_album_err_local": "로ėģŦ 항ëĒŠė€ ė•¨ë˛”ė— ėļ”가할 눘 뗆떴 건너뜁니다.", + "home_page_add_to_album_success": "{album} ė•¨ë˛”ė— 항ëĒŠ {added}개가 ėļ”ę°€ë˜ė—ˆėŠĩ니다.", + "home_page_album_err_partner": "파트너 항ëĒŠė€ ė•¨ë˛”ė— ėļ”가할 눘 뗆떴 건너뜁니다.", + "home_page_archive_err_local": "로ėģŦ 항ëĒŠė€ ëŗ´ę´€í•  눘 뗆떴 건너뜁니다.", + "home_page_archive_err_partner": "파트너 항ëĒŠė€ ëŗ´ę´€í•  눘 뗆떴 건너뜁니다.", "home_page_building_timeline": "íƒ€ėž„ëŧė¸ ęĩŦė„ą 뤑", + "home_page_delete_err_partner": "파트너 항ëĒŠė€ ė‚­ė œí•  눘 뗆떴 건너뜁니다.", + "home_page_delete_remote_err_local": "ė„œë˛„ė—ė„œ ė‚­ė œ ëŒ€ėƒė— íŦ함된 로ėģŦ 항ëĒŠė€ 건너뜁니다.", + "home_page_favorite_err_local": "로ėģŦ 항ëĒŠė€ ėĻę˛¨ė°žę¸°í•  눘 뗆떴 건너뜁니다.", + "home_page_favorite_err_partner": "파트너 항ëĒŠė€ ėĻę˛¨ė°žę¸°í•  눘 뗆떴 건너뜁니다.", + "home_page_first_time_notice": "ė•ąė„ ė˛˜ėŒ ė‚ŦėšŠí•˜ëŠ” ę˛Ŋ뚰, 揰揰뗐 ėžˆëŠ” ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ íƒ€ėž„ëŧė¸ė— í‘œė‹œí•˜ęŗ  ë°ąė—…í•˜ë ¤ëŠ´ ë°ąė—…í•  ė•¨ë˛”ė„ ė„ íƒí•˜ė„¸ėš”.", + "home_page_locked_error_local": "로ėģŦ 항ëĒŠė€ ėž ę¸ˆ 폴더로 ė´ë™í•  눘 ė—†ėŠĩ니다. ėƒëžĩ됨", + "home_page_locked_error_partner": "파트너 항ëĒŠė€ ėž ę¸ˆ 폴더로 ė´ë™í•  눘 ė—†ėŠĩ니다. ėƒëžĩ됨", + "home_page_share_err_local": "로ėģŦ 항ëĒŠė€ 링íŦ로 ęŗĩėœ í•  눘 뗆떴 건너뜁니다.", + "home_page_upload_err_limit": "한 ë˛ˆė— ėĩœëŒ€ 30ę°œė˜ 항ëĒŠęšŒė§€ ė—…ëĄœë“œí•  눘 ėžˆėŠĩ니다.", "host": "í˜¸ėŠ¤íŠ¸", "hour": "ė‹œę°„", "hours": "ė‹œę°„", "id": "ID", "idle": "ėœ íœ´", + "ignore_icloud_photos": "iCloud ė‚Ŧė§„ ė œė™¸", + "ignore_icloud_photos_description": "iCloud뗐 ė €ėžĨ된 ė‚Ŧė§„ė´ Immich뗐 ė—…ëĄœë“œë˜ė§€ ė•ŠėŠĩ니다.", "image": "ė´ë¯¸ė§€", "image_alt_text_date": "{date} ė´Ŧė˜í•œ {isVideo, select, true {ë™ė˜ėƒ} other {ė‚Ŧė§„}}", "image_alt_text_date_1_person": "{date} {person1}님ęŗŧ 함ęģ˜í•œ {isVideo, select, true {ë™ė˜ėƒ} other {ė‚Ŧė§„}}", @@ -1196,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{date} {country}, {city}ė—ė„œ {person1}, {person2}님ęŗŧ 함ęģ˜í•œ {isVideo, select, true {ë™ė˜ėƒ} other {ė‚Ŧė§„}}", "image_alt_text_date_place_3_people": "{date} {country}, {city}ė—ė„œ {person1}, {person2}님 및 {person3}님ęŗŧ 함ęģ˜í•œ {isVideo, select, true {ë™ė˜ėƒ} other {ė‚Ŧė§„}}", "image_alt_text_date_place_4_or_more_people": "{date} {country}, {city}ė—ė„œ {person1}, {person2}님 및 {additionalCount, number}ëĒ…ęŗŧ 함ęģ˜í•œ {isVideo, select, true {ë™ė˜ėƒ} other {ė‚Ŧė§„}}", + "image_saved_successfully": "ė´ë¯¸ė§€ę°€ ė €ėžĨë˜ė—ˆėŠĩ니다.", + "image_viewer_page_state_provider_download_started": "ë‹¤ėš´ëĄœë“œę°€ ė‹œėž‘ë˜ė—ˆėŠĩ니다.", + "image_viewer_page_state_provider_download_success": "ë‹¤ėš´ëĄœë“œ ė™„ëŖŒ", + "image_viewer_page_state_provider_share_error": "ęŗĩ뜠 똤ëĨ˜", "immich_logo": "Immich ëĄœęŗ ", "immich_web_interface": "Immich ė›š ė¸í„°íŽ˜ė´ėŠ¤", "import_from_json": "JSONė—ė„œ ę°€ė ¸ė˜¤ę¸°", @@ -1210,7 +1329,6 @@ "individual_share": "ę°œė¸ ęŗĩ뜠", "individual_shares": "ę°œëŗ„ ęŗĩ뜠", "info": "ė •ëŗ´", - "integrity_checks": "ëŦ´ę˛°ė„ą 검ė‚Ŧ", "interval": { "day_at_onepm": "매ėŧ ė˜¤í›„ 1ė‹œ", "hours": "매 {hours, plural, one {ė‹œę°„} other {{hours, number}ė‹œę°„}}", @@ -1221,9 +1339,16 @@ "invalid_date_format": "ėœ íš¨í•˜ė§€ ė•Šė€ ë‚ ė§œ í˜•ė‹", "invite_people": "ė‚ŦėšŠėž ė´ˆëŒ€", "invite_to_album": "ė•¨ë˛”ėœŧ로 ė´ˆëŒ€", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "ë°ė´í„° ëļˆëŸŦ똤揰 ėž‘ė—…ė´ {dateTime}뗐 ė‹¤í–‰ë¨", + "ios_debug_info_last_sync_at": "ë§ˆė§€ë§‰ 동기화: {dateTime}", + "ios_debug_info_no_processes_queued": "대기 ė¤‘ė¸ 밹꡸ëŧėš´ë“œ ėž‘ė—… ė—†ėŒ", + "ios_debug_info_no_sync_yet": "밹꡸ëŧėš´ë“œ 동기화 ėž‘ė—…ė´ 땄링 ė‹¤í–‰ë˜ė§€ ė•ŠėŒ", + "ios_debug_info_processes_queued": "{count, plural, one {밹꡸ëŧėš´ë“œ ėž‘ė—… {count}氜 대기 뤑} other {밹꡸ëŧėš´ë“œ ėž‘ė—… {count}氜 대기 뤑}}", + "ios_debug_info_processing_ran_at": "{dateTime}뗐 래ëĻŦ됨", "items_count": "{count, plural, one {#氜} other {#氜}} 항ëĒŠ", "jobs": "ėž‘ė—…", + "json_editor": "JSON íŽ¸ė§‘ę¸°", + "json_error": "JSON 똤ëĨ˜", "keep": "ėœ ė§€", "keep_albums": "ė•¨ë˛” ėœ ė§€", "keep_albums_count": "{count}ę°œė˜ {count, plural, one {ė•¨ë˛”} other {ė•¨ë˛”}} ėœ ė§€", @@ -1250,12 +1375,14 @@ "leave": "나가기", "leave_album": "ė•¨ë˛”ė—ė„œ 나가기", "lens_model": "ėš´ëŠ”ëŧ 렌ėψ ëĒ¨ë¸", - "less": "간ëžĩ히 ëŗ´ę¸°", "let_others_respond": "다ëĨ¸ ė‚ŦėšŠėžė˜ ë°˜ė‘ í—ˆėšŠ", "level": "레벨", "library": "ëŧė´ë¸ŒëŸŦëĻŦ", "library_add_folder": "폴더 ėļ”ę°€", "library_edit_folder": "폴더 ėˆ˜ė •", + "library_options": "ëŧė´ë¸ŒëŸŦëĻŦ ė˜ĩė…˜", + "library_page_device_albums": "ę¸°ę¸°ė˜ ė•¨ë˛”", + "library_page_new_album": "ė•¨ë˛” ėƒė„ą", "library_page_sort_asset_count": "항ëĒŠ 눘", "library_page_sort_created": "만든 ë‚ ė§œ", "library_page_sort_last_modified": "ë§ˆė§€ë§‰ ėˆ˜ė •", @@ -1265,14 +1392,11 @@ "light_theme": "ëŧė´íŠ¸ 테마로 ė „í™˜", "like": "ėĸ‹ė•„ėš”", "like_deleted": "ėĸ‹ė•„ėš”ę°€ ė‚­ė œë˜ė—ˆėŠĩ니다.", - "link": "링íŦ", "link_motion_video": "ëĒ¨ė…˜ ëš„ë””ė˜¤ 링íŦ", "link_to_docs": "ėžė„¸í•œ ë‚´ėšŠė€ ëŦ¸ė„œëĨŧ ė°¸ėĄ°í•˜ė‹­ė‹œė˜¤.", "link_to_oauth": "OAuth뗐 뗰枰", "linked_oauth_account": "OAuth ęŗ„ė •ė´ ė—°ę˛°ë˜ė—ˆėŠĩ니다.", "list": "ëĒŠëĄ", - "live": "ëŧė´ë¸Œ", - "load_more": "더 ëļˆëŸŦ똤揰", "loading": "로드 뤑", "loading_search_results_failed": "ę˛€ėƒ‰ 결ęŗŧ 로드 ė‹¤íŒ¨", "local": "로ėģŦ", @@ -1301,9 +1425,11 @@ "login": "ëĄœęˇ¸ė¸", "login_disabled": "ëĄœęˇ¸ė¸ė´ ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다.", "login_form_api_exception": "API ė˜ˆė™¸ę°€ ë°œėƒí–ˆėŠĩ니다. ė„œë˛„ URLė„ í™•ė¸í•œ 후 ë‹¤ė‹œ ė‹œë„í•˜ė„¸ėš”.", + "login_form_back_button_text": "뒤로", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "ė„œë˛„ ė—”ë“œíŦė¸íŠ¸ URL", + "login_form_err_http": "http:// 또는 https://로 ė‹œėž‘í•´ė•ŧ 합니다.", "login_form_err_invalid_email": "ėœ íš¨í•˜ė§€ ė•Šė€ ė´ëŠ”ėŧ", "login_form_err_invalid_url": "ėž˜ëĒģ된 URLėž…ë‹ˆë‹¤.", "login_form_err_leading_whitespace": "ėž…ë Ĩ값 ė•žė— ęŗĩë°ąė´ ėžˆėŠĩ니다.", @@ -1313,9 +1439,10 @@ "login_form_failed_login": "ëĄœęˇ¸ė¸ 뤑 똤ëĨ˜ę°€ ë°œėƒí–ˆėŠĩ니다. ė„œë˛„ URL, ė´ëŠ”ėŧ 및 비밀번호ëĨŧ í™•ė¸í•˜ė„¸ėš”.", "login_form_handshake_exception": "ė„œë˛„ė™€ í†ĩė‹  뤑 ė¸ėĻė„œ ė˜ˆė™¸ę°€ ë°œėƒí–ˆėŠĩ니다. ėžė˛´ ė„œëĒ…ëœ ė¸ėĻė„œëĨŧ ė‚ŦėšŠ ė¤‘ė´ëŧ늴, ė„¤ė •ė—ė„œ ėžė˛´ ė„œëĒ…ëœ ė¸ėĻė„œ í—ˆėšŠė„ í™œė„ąí™”í•˜ė„¸ėš”.", "login_form_password_hint": "비밀번호", - "login_form_server_empty": "ė„œë˛„ URL ėž…ë Ĩ", - "login_form_server_error": "ė„œë˛„ė— ė—°ę˛°í•  눘 ė—†ėŒ", - "login_has_been_disabled": "ëĄœęˇ¸ė¸ė´ ëš„í™œė„ąí™”ë¨", + "login_form_save_login": "ëĄœęˇ¸ė¸ ėœ ė§€", + "login_form_server_empty": "ė„œë˛„ URLė„ ėž…ë Ĩí•˜ė„¸ėš”.", + "login_form_server_error": "ė„œë˛„ė— ė—°ę˛°í•  눘 ė—†ėŠĩ니다.", + "login_has_been_disabled": "ëĄœęˇ¸ė¸ė´ ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다.", "login_password_changed_error": "비밀번호 ëŗ€ę˛Ŋ 뤑 똤ëĨ˜ę°€ ë°œėƒí–ˆėŠĩ니다.", "login_password_changed_success": "비밀번호가 ëŗ€ę˛Ŋë˜ė—ˆėŠĩ니다.", "logout_all_device_confirmation": "ëĒ¨ë“  ę¸°ę¸°ė—ė„œ ëĄœęˇ¸ė•„ė›ƒí•˜ė‹œę˛ ėŠĩ니까?", @@ -1330,6 +1457,7 @@ "maintenance_action_restore": "ë°ė´í„°ë˛ ė´ėŠ¤ëĨŧ ëŗĩė›í•˜ëŠ” 뤑", "maintenance_description": "Immich가 ėœ ė§€ę´€ëĻŦ ëĒ¨ë“œëĄœ ė „í™˜ë˜ė—ˆėŠĩ니다.", "maintenance_end": "ėœ ė§€ 관ëĻŦ ëĒ¨ë“œ ėĸ…ëŖŒ", + "maintenance_end_error": "ėœ ė§€ę´€ëĻŦ ëĒ¨ë“œëĨŧ ėĸ…ëŖŒí•˜ëŠ” 데 ė‹¤íŒ¨í–ˆėŠĩ니다.", "maintenance_logged_in_as": "현ėžŦ {user}님ėœŧ로 ëĄœęˇ¸ė¸ë˜ė–´ ėžˆėŠĩ니다.", "maintenance_restore_from_backup": "ë°ąė—…ė—ė„œ ëŗĩ뛐", "maintenance_restore_library": "ëŧė´ë¸ŒëŸŦëĻŦ ëŗĩ뛐", @@ -1355,6 +1483,7 @@ "manage_media_access_settings": "네렕 뗴揰", "manage_media_access_subtitle": "Immich ė•ąė´ ë¯¸ë””ė–´ 파ėŧė„ 관ëĻŦí•˜ęŗ  ė´ë™í•  눘 ėžˆë„ëĄ í—ˆėšŠí•˜ė‹­ė‹œė˜¤.", "manage_media_access_title": "ë¯¸ë””ė–´ 관ëĻŦ ė•Ąė„¸ėŠ¤", + "manage_shared_links": "ęŗĩ뜠 링íŦ 관ëĻŦ", "manage_sharing_with_partners": "ęŗĩėœ í•  파트너ëĨŧ ė´ˆëŒ€í•˜ęą°ë‚˜ ė œęą°í•Šë‹ˆë‹¤.", "manage_the_app_settings": "ė•ą ë™ėž‘ 및 í‘œė‹œ 환ę˛Ŋė„ ė‚ŦėšŠėž ė •ė˜í•Šë‹ˆë‹¤.", "manage_your_account": "ė‚ŦėšŠėžė˜ ęŗ„ė • ė •ëŗ´ëĨŧ í™•ė¸í•˜ęŗ  ëŗ€ę˛Ŋ합니다.", @@ -1362,11 +1491,13 @@ "manage_your_devices": "현ėžŦ ëĄœęˇ¸ė¸í•œ 기기ëĨŧ í™•ė¸í•˜ęŗ  ëĄœęˇ¸ė•„ė›ƒí•  눘 ėžˆėŠĩ니다.", "manage_your_oauth_connection": "OAuth ė—°ę˛°ė„ 관ëĻŦ합니다.", "map": "ė§€ë„", + "map_assets_in_bounds": "{count, plural, =0 {ė‚Ŧė§„ ė—†ėŒ} one {ė‚Ŧė§„ #氜} other {ė‚Ŧė§„ #氜}}", "map_cannot_get_user_location": "ė‚ŦėšŠėžė˜ ėœ„ėš˜ëĨŧ 氀렏ė˜Ŧ 눘 ė—†ėŠĩ니다.", + "map_location_dialog_yes": "똈", "map_location_picker_page_use_location": "ė´ ėœ„ėš˜ ė‚ŦėšŠ", "map_location_service_disabled_content": "현ėžŦ ėœ„ėš˜ė˜ 항ëĒŠė„ í‘œė‹œí•˜ë ¤ëŠ´ ėœ„ėš˜ ė„œëš„ėŠ¤ëĨŧ í™œė„ąí™”í•´ė•ŧ 합니다. ė§€ę¸ˆ í™œė„ąí™”í•˜ė‹œę˛ ėŠĩ니까?", "map_location_service_disabled_title": "ėœ„ėš˜ ė„œëš„ėŠ¤ ëš„í™œė„ąí™”ë¨", - "map_marker_for_image": "{city}, {country}ė—ė„œ ė´Ŧė˜í•œ ė´ë¯¸ė§€ė˜ ė§€ë„ 마ėģ¤ėž…니다.", + "map_marker_for_images": "{country}, {city}ė—ė„œ ė´Ŧė˜ëœ ė´ë¯¸ė§€ė˜ ė§€ë„ 마ėģ¤", "map_marker_with_image": "ė´ë¯¸ė§€ę°€ ėžˆëŠ” ė§€ë„ 마ėģ¤", "map_no_location_permission_content": "현ėžŦ ėœ„ėš˜ė˜ 항ëĒŠė„ í‘œė‹œí•˜ë ¤ëŠ´ ėœ„ėš˜ ęļŒí•œė´ í•„ėš”í•Šë‹ˆë‹¤. ė§€ę¸ˆ í—ˆėšŠí•˜ė‹œę˛ ėŠĩ니까?", "map_no_location_permission_title": "ėœ„ėš˜ ęļŒí•œ ęą°ëļ€ë¨", @@ -1376,46 +1507,17 @@ "map_settings_date_range_option_days": "ė§€ë‚œ {days}ėŧ", "map_settings_date_range_option_year": "ė§€ë‚œ 1년", "map_settings_date_range_option_years": "ė§€ë‚œ {years}년", + "map_settings_dialog_title": "ė§€ë„ 네렕", "map_settings_include_show_archived": "ëŗ´ę´€ëœ 항ëĒŠ íŦ함", "map_settings_include_show_partners": "파트너가 ęŗĩėœ í•œ 항ëĒŠ íŦ함", "map_settings_only_show_favorites": "ėĻę˛¨ė°žę¸°ë§Œ í‘œė‹œ", "map_settings_theme_settings": "ė§€ë„ 테마", + "map_zoom_to_see_photos": "ėļ•ė†Œí•˜ė—Ŧ ė‚Ŧė§„ ëŗ´ę¸°", "mark_all_as_read": "ëĒ¨ë‘ ėŊėŒėœŧ로 í‘œė‹œ", + "mark_as_read": "ėŊėŒėœŧ로 í‘œė‹œ", "marked_all_as_read": "ëĒ¨ë‘ ėŊėŒėœŧ로 í‘œė‹œí–ˆėŠĩ니다.", "matches": "ėŧėš˜", "matching_assets": "ėŧėš˜í•˜ëŠ” 항ëĒŠ", - "media_chrome": { - "auto": "ėžë™", - "captions": "ėžë§‰", - "captions_off": "í•´ė œ", - "closed_captions": "ėžë§‰(Cc)", - "decode_error": "디ėŊ”딊 똤ëĨ˜", - "disable_captions": "ėžë§‰ 끄기", - "enable_captions": "ėžë§‰ ėŧœę¸°", - "enter_fullscreen_mode": "렄랴 화면ėœŧ로 ëŗ´ę¸°", - "exit_fullscreen_mode": "렄랴 화면 ėĸ…ëŖŒ", - "loop": "반ëŗĩ", - "media_error_description": "ë¯¸ë””ė–´ 똤ëĨ˜ę°€ ë°œėƒí•˜ė—Ŧ ėžŦėƒė„ ęŗ„ė†í•  눘 ė—†ėŠĩ니다. 파ėŧė´ ė†ėƒë˜ė—ˆęą°ë‚˜ 브ëŧėš°ė €ė—ė„œ ė§€ė›í•˜ė§€ ė•ŠëŠ” í˜•ė‹ėŧ 눘 ėžˆėŠĩ니다.", - "media_loading": "ë¯¸ë””ė–´ 로딩 뤑", - "mute": "ėŒė†Œęą°", - "network_error": "Network 똤ëĨ˜", - "network_error_description": "ë„¤íŠ¸ė›ŒíŦ 똤ëĨ˜ëĄœ ė¸í•´ ë¯¸ë””ė–´ ë‹¤ėš´ëĄœë“œė— ė‹¤íŒ¨í–ˆėŠĩ니다.", - "not_supported_error": "ė§€ė›ë˜ė§€ ė•ŠëŠ” ė†ŒėŠ¤", - "playback_rate": "ėžŦėƒ ė†ë„", - "playback_rate_current": "현ėžŦ ėžŦėƒ ė†ë„", - "playback_rate_value": "ėžŦėƒ ė†ë„ {playbackRate}", - "playback_time": "ėžŦėƒ ė‹œę°„", - "quality": "í™”ė§ˆ", - "second": "봈", - "seconds": "봈", - "time_value_of_total_time": "{currentTime} (ė´ {totalTime})", - "time_value_remaining": "{time} ë‚¨ėŒ", - "unmute": "ėŒė†Œęą° í•´ė œ", - "unsupported_error_description": "ė•Œ 눘 ė—†ëŠ” 똤ëĨ˜ę°€ ë°œėƒí–ˆėŠĩ니다. ė„œë˛„ 또는 ë„¤íŠ¸ė›ŒíŦ뗐 ëŦ¸ė œę°€ ėžˆęą°ë‚˜ 브ëŧėš°ė €ė—ė„œ 해당 í˜•ė‹ė„ ė§€ė›í•˜ė§€ ė•Šė„ 눘 ėžˆėŠĩ니다.", - "video_not_loaded_unknown_time": "ë™ė˜ėƒė„ ëļˆëŸŦė˜¤ė§€ ëĒģ했ėŠĩ니다. ėžŦėƒ ė‹œę°„ė„ í™•ė¸í•  눘 ė—†ėŠĩ니다.", - "video_player": "ëš„ë””ė˜¤ í”Œë ˆė´ė–´", - "volume": "ëŗŧëĨ¨" - }, "media_type": "ë¯¸ë””ė–´ ėĸ…ëĨ˜", "memories": "ėļ”ė–ĩ", "memories_all_caught_up": "ëĒ¨ë‘ í™•ė¸í•¨", @@ -1432,31 +1534,34 @@ "merge_people_prompt": "ė¸ëŦŧë“¤ė„ ëŗ‘í•Ší•˜ė‹œę˛ ėŠĩ니까? ė´ ėž‘ė—…ė€ 되돌ëĻ´ 눘 ė—†ėŠĩ니다.", "merge_people_successfully": "ė„ íƒí•œ ė¸ëŦŧė„ í•Šėŗ¤ėŠĩ니다.", "merged_people_count": "ė¸ëŦŧ {count, plural, one {#ëĒ…} other {#ëĒ…}}ė„ í•Šėŗ¤ėŠĩ니다.", - "minFaces": "ėĩœė†Œ ė¸ė‹ ė–ŧęĩ´ 눘", - "minFaces_description": "ė¸ëŦŧ ëĒŠëĄė— í‘œė‹œë˜ę¸° ėœ„í•´ í•„ėš”í•œ ėĩœė†Œ ė–ŧęĩ´ ė¸ė‹ íšŸėˆ˜ëĨŧ ė„¤ė •í•Šë‹ˆë‹¤", "minimize": "ėĩœė†Œí™”", "minute": "ëļ„", "minutes": "ëļ„", + "mirror_horizontal": "ėˆ˜í‰", + "mirror_vertical": "눘링", "missing": "누ëŊ", "mobile_app": "ëĒ¨ë°”ėŧ ė•ą", "mobile_app_download_onboarding_note": "ë‹¤ėŒ ė˜ĩė…˜ 뤑 하나ëĨŧ ė‚ŦėšŠí•´ ëĒ¨ë°”ėŧ ė•ąė„ ë‹¤ėš´ëĄœë“œí•˜ė„¸ėš”.", "model": "ëĒ¨ë¸", - "modify_date": "ėˆ˜ė •ėŧ", "month": "ė›”", + "monthly_title_text_date_format": "yyyy년 Mė›”", "more": "ë”ëŗ´ę¸°", - "motion": "ëĒ¨ė…˜ ė‚Ŧė§„", "move": "ė´ë™", + "move_down": "ė•„ëž˜ëĄœ ė´ë™", "move_off_locked_folder": "ėž ę¸ˆ í´ë”ė—ė„œ í•´ė œ", "move_to": "ë‹¤ėŒėœŧ로 ė´ë™", "move_to_device_trash": "ę¸°ę¸°ė˜ íœ´ė§€í†ĩėœŧ로 ė´ë™", "move_to_lock_folder_action_prompt": "ėž ę¸ˆ 폴더로 항ëĒŠ {count}氜 ė´ë™ë¨", "move_to_locked_folder": "ėž ę¸ˆ 폴더로 ė´ë™", "move_to_locked_folder_confirmation": "ė„ íƒí•œ ė‚Ŧė§„ 또는 ë™ė˜ėƒė´ ëĒ¨ë“  ė•¨ë˛”ė—ė„œ ė œęą°ë˜ëŠ°, ėž ę¸ˆ í´ë”ė—ė„œë§Œ ëŗŧ 눘 ėžˆėŠĩ니다.", + "move_up": "ėœ„ëĄœ ė´ë™", + "moved_to_archive": "ëŗ´ę´€í•¨ėœŧ로 항ëĒŠ {count, plural, one {#氜} other {#氜}} ė´ë™ë¨", + "moved_to_library": "ëŧė´ë¸ŒëŸŦëĻŦ로 항ëĒŠ {count, plural, one {#氜} other {#氜}} ė´ë™ë¨", "moved_to_trash": "íœ´ė§€í†ĩėœŧ로 ė´ë™ë˜ė—ˆėŠĩ니다.", + "multiselect_grid_edit_date_time_err_read_only": "ėŊ기 ė „ėšŠė¸ 항ëĒŠė˜ ë‚ ė§œëŠ” ëŗ€ę˛Ŋ할 눘 뗆떴 건너뜁니다.", + "multiselect_grid_edit_gps_err_read_only": "ėŊ기 ė „ėšŠė¸ 항ëĒŠė˜ ėœ„ėš˜ëŠ” ëŗ€ę˛Ŋ할 눘 뗆떴 건너뜁니다.", "mute_memories": "ėļ”ė–ĩ ėŒė†Œęą°", "my_albums": "내 ė•¨ë˛”", - "my_immich_description": "현ėžŦ íŽ˜ė´ė§€ëĨŧ Immich 링íŦ로 ëŗĩė‚Ŧ", - "my_immich_title": "내 Immich 링íŦ", "name": "ė´ëĻ„", "name_or_nickname": "ė´ëĻ„ 또는 ë‹‰ë„¤ėž„", "name_required": "ė´ëĻ„ė€ í•„ėˆ˜ ėž…ë Ĩ ė‚Ŧí•­ėž…ë‹ˆë‹¤", @@ -1471,10 +1576,12 @@ "never": "ė—†ėŒ", "new_album": "냈 ė•¨ë˛”", "new_api_key": "냈 API 키", + "new_date_range": "냈 ë‚ ė§œ ë˛”ėœ„", "new_password": "냈 비밀번호", "new_person": "냈 ė¸ëŦŧ ėƒė„ą", "new_pin_code": "냈 PIN ėŊ”드", "new_pin_code_subtitle": "ėž ę¸ˆ í´ë”ė— ė˛˜ėŒ ė ‘ęˇŧí•˜ė…¨ėŠĩ니다. ė´ęŗŗė— ė•ˆė „í•˜ę˛Œ ė ‘ęˇŧ하기 ėœ„í•œ PIN ėŊ”드ëĨŧ ė„¤ė •í•˜ė„¸ėš”.", + "new_timeline": "냈 íƒ€ėž„ëŧė¸", "new_update": "ėƒˆëĄœėš´ ė—…ë°ė´íŠ¸", "new_user_created": "ė‚ŦėšŠėž ęŗ„ė •ė´ ėƒė„ąë˜ė—ˆėŠĩ니다.", "new_version_available": "냈 ë˛„ė „ ė‚ŦėšŠ 가ëŠĨ", @@ -1482,6 +1589,7 @@ "next": "ë‹¤ėŒ", "next_memory": "ë‹¤ėŒ ėļ”ė–ĩ", "no": "ė•„ë‹ˆėš”", + "no_actions_added": "땄링 ėļ”ę°€ëœ ėž‘ė—…ė´ ė—†ėŠĩ니다", "no_albums_found": "ė•¨ë˛”ė´ ė—†ėŠĩ니다.", "no_albums_message": "ė•¨ë˛”ė„ ėƒė„ąí•˜ė—Ŧ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ ė •ëĻŦ하기", "no_albums_with_name_yet": "땄링 해당하는 ė´ëĻ„ė˜ ė•¨ë˛”ė´ ė—†ëŠ” 것 같ėŠĩ니다.", @@ -1492,13 +1600,16 @@ "no_cast_devices_found": "ėēėŠ¤íŠ¸ 기기 ė—†ėŒ", "no_checksum_local": "랴íŦė„Ŧė´ ė—†ėŠĩ니다. 로ėģŦ 항ëĒŠė„ ëļˆëŸŦė˜Ŧ 눘 ė—†ėŠĩ니다.", "no_checksum_remote": "랴íŦė„Ŧė´ ė—†ėŠĩ니다. ė›ę˛Š 항ëĒŠė„ ëļˆëŸŦė˜Ŧ 눘 ė—†ėŠĩ니다.", + "no_configuration_needed": "ëŗ„ë„ė˜ ė„¤ė •ė´ í•„ėš”í•˜ė§€ ė•ŠėŠĩ니다", "no_devices": "ėŠšė¸ë˜ė§€ ė•Šė€ 기기", "no_duplicates_found": "ëš„ėŠˇí•œ 항ëĒŠė´ ė—†ėŠĩ니다.", "no_exif_info_available": "EXIF ė •ëŗ´ ė—†ėŒ", "no_explore_results_message": "더 ë§Žė€ ė‚Ŧė§„ė„ ė—…ëĄœë“œí•˜ė—Ŧ íƒėƒ‰ 기ëŠĨė„ ė‚ŦėšŠí•˜ė„¸ėš”.", "no_favorites_message": "ėĻę˛¨ė°žę¸°ė—ė„œ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ ëš ëĨ´ę˛Œ ė°žę¸°", + "no_filters_added": "땄링 ėļ”ę°€ëœ 필터 ė—†ėŒ", "no_libraries_message": "뙏ëļ€ ëŧė´ë¸ŒëŸŦëĻŦ로 다ëĨ¸ ę˛ŊëĄœė˜ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ í™•ė¸í•˜ė„¸ėš”.", "no_local_assets_found": "랴íŦė„Ŧęŗŧ ėŧėš˜í•˜ëŠ” 로ėģŦ 항ëĒŠė„ ė°žė„ 눘 ė—†ėŠĩ니다.", + "no_location_set": "ėœ„ėš˜ę°€ ė„¤ė •ë˜ė§€ ė•Šė•˜ėŠĩ니다", "no_locked_photos_message": "ėž ę¸ˆ í´ë”ė˜ ė‚Ŧė§„ 및 ë™ė˜ėƒė€ ėˆ¨ę˛¨ė§€ëŠ° ëŧė´ë¸ŒëŸŦëĻŦëĨŧ íƒėƒ‰í•  때 í‘œė‹œë˜ė§€ ė•ŠėŠĩ니다.", "no_name": "ė´ëĻ„ ė—†ėŒ", "no_notifications": "ė•ŒëĻŧ ė—†ėŒ", @@ -1508,7 +1619,7 @@ "no_results": "결ęŗŧ ė—†ėŒ", "no_results_description": "ë™ė˜ė–´ 또는 더 ėŧë°˜ė ė¸ ë‹¨ė–´ëĨŧ ė‚ŦėšŠí•´ ëŗ´ė„¸ėš”.", "no_shared_albums_message": "ė•¨ë˛”ė„ ë§Œë“¤ė–´ ėŖŧëŗ€ ė‚Ŧ람들ęŗŧ ė‚Ŧė§„ 및 ë™ė˜ėƒė„ ęŗĩėœ í•˜ė„¸ėš”.", - "no_steps": "땄링 ėļ”ę°€ëœ ë‹¨ęŗ„ę°€ ė—†ėŠĩ니다", + "no_uploads_in_progress": "ė§„í–‰ ė¤‘ė¸ ė—…ëĄœë“œ ė—†ėŒ", "none": "None", "not_allowed": "í—ˆėšŠë˜ė§€ ė•ŠėŒ", "not_available": "ė—†ėŒ", @@ -1516,7 +1627,6 @@ "not_selected": "ė„ íƒë˜ė§€ ė•ŠėŒ", "notes": "및溠", "nothing_here_yet": "땄링 ė•„ëŦ´ę˛ƒë„ ė—†ėŒ", - "notification_backup_reliability": "밹꡸ëŧėš´ë“œ ë°ąė—…ė˜ ė•ˆė •ė„ąė„ ë†’ė´ë ¤ëŠ´ ė•ŒëĻŧė„ í™œė„ąí™”í•˜ė„¸ėš”", "notification_permission_dialog_content": "ė•ŒëĻŧė„ í™œė„ąí™”í•˜ë ¤ëŠ´ ė„¤ė •ė—ė„œ ė•ŒëĻŧ ęļŒí•œė„ í—ˆėšŠí•˜ė„¸ėš”.", "notification_permission_list_tile_content": "ė•ŒëĻŧė„ í™œė„ąí™”í•˜ë ¤ëŠ´ ęļŒí•œė„ ëļ€ė—Ŧí•˜ė„¸ėš”.", "notification_permission_list_tile_enable_button": "ė•ŒëĻŧ í™œė„ąí™”", @@ -1554,10 +1664,10 @@ "organize_into_albums": "ė•¨ë˛”ėœŧ로 ė •ëĻŦ하기", "organize_into_albums_description": "현ėžŦ 동기화 ė„¤ė •ė„ ė‚ŦėšŠí•˜ė—Ŧ ę¸°ėĄ´ ė‚Ŧė§„ė„ ė•¨ë˛”ėœŧ로 ė •ëĻŦ합니다", "organize_your_library": "ëŧė´ë¸ŒëŸŦëĻŦ ė •ëĻŦ", - "orientation": "똤ëĻŦė—”í…Œė´ė…˜", "original": "ė›ëŗ¸", "other": "기타", "other_devices": "다ëĨ¸ 기기", + "other_entities": "기타 ė—”í‹°í‹°", "other_variables": "기타 ëŗ€ėˆ˜", "owned": "ė†Œėœ í•¨", "owner": "ė†Œėœ ėž", @@ -1567,9 +1677,12 @@ "partner_can_access_assets": "ëŗ´ę´€ë˜ęą°ë‚˜ ė‚­ė œëœ 항ëĒŠė„ ė œė™¸í•œ ëĒ¨ë“  ė‚Ŧė§„ 및 ë™ė˜ėƒ", "partner_can_access_location": "ė‚Ŧė§„ė„ ė´Ŧė˜í•œ ėœ„ėš˜", "partner_list_user_photos": "{user}ë‹˜ė˜ ė‚Ŧė§„", + "partner_list_view_all": "ëĒ¨ë‘ ëŗ´ę¸°", "partner_page_empty_message": "ė‚Ŧė§„ė´ 땄링 ė–´ë–¤ íŒŒíŠ¸ë„ˆė™€ë„ ęŗĩėœ ë˜ė§€ ė•Šė•˜ėŠĩ니다.", "partner_page_no_more_users": "ėļ”가할 ė‚ŦėšŠėžę°€ ė—†ėŠĩ니다.", + "partner_page_partner_add_failed": "파트너 ėļ”ę°€ ė‹¤íŒ¨", "partner_page_select_partner": "파트너 ė„ íƒ", + "partner_page_shared_to_title": "ęŗĩ뜠 ëŒ€ėƒ", "partner_page_stop_sharing_content": "더 ė´ėƒ {partner}ë‹˜ė´ ė‚Ŧ맄뗐 ė ‘ęˇŧ할 눘 ė—†ėŠĩ니다.", "partner_sharing": "íŒŒíŠ¸ë„ˆė™€ ęŗĩ뜠", "partners": "파트너", @@ -1591,6 +1704,8 @@ "people": "ė¸ëŦŧ", "people_edits_count": "ė¸ëŦŧ {count, plural, one {#ëĒ…} other {#ëĒ…}}ė´ ėˆ˜ė •ë˜ė—ˆėŠĩ니다.", "people_feature_description": "ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ ė¸ëŦŧ ęˇ¸ëŖšëŗ„ëĄœ íƒėƒ‰", + "people_selected": "ė¸ëŦŧ {count, plural, one {#ëĒ…} other {#ëĒ…}} ė„ íƒë¨", + "people_sidebar_description": "ė‚Ŧė´ë“œë°”ė— ė¸ëŦŧ 링íŦ í‘œė‹œ", "permanent_deletion_warning": "똁ęĩŦ ė‚­ė œ ę˛Ŋęŗ ", "permanent_deletion_warning_setting_description": "항ëĒŠė„ ė™„ė „ížˆ ė‚­ė œí•˜ę¸° ė „ ę˛Ŋęŗ  ëŠ”ė‹œė§€ëĨŧ í‘œė‹œí•Šë‹ˆë‹¤.", "permanently_delete": "똁ęĩŦ ė‚­ė œ", @@ -1600,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {#氜} other {#氜}} 항ëĒŠė´ 똁ęĩŦ렁ėœŧ로 ė‚­ė œë¨", "permission": "ęļŒí•œ", "permission_empty": "하나 ė´ėƒė˜ ęļŒí•œė„ ė„ íƒí•´ė•ŧ 합니다.", + "permission_onboarding_back": "뒤로", + "permission_onboarding_continue_anyway": "ëŦ´ė‹œí•˜ęŗ  ė§„í–‰", + "permission_onboarding_get_started": "ė‹œėž‘í•˜ę¸°", + "permission_onboarding_go_to_settings": "네렕ėœŧ로 ė´ë™", + "permission_onboarding_permission_denied": "ęļŒí•œė´ ė—†ėŠĩ니다. ImmichëĨŧ ė‚ŦėšŠí•˜ë ¤ëŠ´ ė„¤ė •ė—ė„œ ė‚Ŧė§„ 및 ë™ė˜ėƒ ęļŒí•œė„ ëļ€ė—Ŧí•˜ė„¸ėš”.", + "permission_onboarding_permission_granted": "ęļŒí•œė´ ëļ€ė—Ŧë˜ė—ˆėŠĩ니다! ė¤€ëš„ę°€ ė™„ëŖŒë˜ė—ˆėŠĩ니다.", + "permission_onboarding_permission_limited": "ęļŒí•œė´ ė—†ėŠĩ니다. Immich가 렄랴 ę°¤ëŸŦëĻŦ ėģŦë ‰ė…˜ė„ ë°ąė—…í•˜ęŗ  관ëĻŦ할 눘 ėžˆë„ëĄ 하려면 ė„¤ė •ė—ė„œ ė‚Ŧė§„ 및 ë™ė˜ėƒ ęļŒí•œė„ ëļ€ė—Ŧí•˜ė„¸ėš”.", + "permission_onboarding_request": "Immich가 ė‚Ŧė§„ 및 ë™ė˜ėƒė— ė ‘ęˇŧ하기 ėœ„í•œ ęļŒí•œė´ í•„ėš”í•Šë‹ˆë‹¤.", "person": "ė¸ëŦŧ", "person_age_months": "{months, plural, one {#ę°œė›”} other {#ę°œė›”}}", "person_age_year_months": "1넏, {months, plural, one {#ę°œė›”} other {#ę°œė›”}}", @@ -1607,10 +1730,13 @@ "person_birthdate": "{date} ėļœėƒ", "person_hidden": "{name}{hidden, select, true { (ėˆ¨ęš€)} other {}}", "person_recognized": "ė‹ ė›ė´ í™•ė¸ëœ ė‚Ŧ람", + "person_selected": "ė„ íƒëœ ė‚Ŧ람", "photo_shared_all_users": "ė´ë¯¸ ëĒ¨ë“  ė‚ŦėšŠėžė™€ ė‚Ŧė§„ė„ ęŗĩ뜠 ė¤‘ė´ęą°ë‚˜ 다ëĨ¸ ė‚ŦėšŠėžę°€ ė—†ëŠ” 것 같ėŠĩ니다.", "photos": "ė‚Ŧė§„", "photos_and_videos": "ė‚Ŧė§„ 및 ë™ė˜ėƒ", + "photos_count": "ė‚Ŧė§„ {count, plural, one {{count, number}氜} other {{count, number}氜}}", "photos_from_previous_years": "ė§€ë‚œ ëLJ ë…„ę°„ė˜ ė‚Ŧė§„", + "photos_only": "ė‚Ŧė§„ë§Œ", "pick_a_location": "ėœ„ėš˜ ė„ íƒ", "pick_custom_range": "ë˛”ėœ„ 맀렕", "pick_date_range": "ë‚ ė§œ ë˛”ėœ„ ė„ íƒ", @@ -1626,11 +1752,9 @@ "play_motion_photo": "ëĒ¨ė…˜ íŦ토 ėžŦėƒ", "play_or_pause_video": "ë™ė˜ėƒ ėžŦėƒ/ėŧė‹œ ė •ė§€", "play_original_video": "ė›ëŗ¸ ë™ė˜ėƒ ėžŦėƒ", - "play_original_video_setting_description": "ëŗ€í™˜ëœ ė˜ėƒëŗ´ë‹¤ëŠ” ė›ëŗ¸ ė˜ėƒė„ ėžŦėƒí•˜ëŠ” ę˛ƒė„ ęļŒėžĨ합니다. ė›ëŗ¸ ė˜ėƒė´ í˜¸í™˜ë˜ė§€ ė•Šėœŧ늴 ė œëŒ€ëĄœ ėžŦėƒë˜ė§€ ė•Šė„ 눘 ėžˆėŠĩ니다.", + "play_original_video_setting_description": "íŠ¸ëžœėŠ¤ėŊ”딊된 ė˜ėƒëŗ´ë‹¤ ė›ëŗ¸ ė˜ėƒė„ ėš°ė„  ėžŦėƒí•Šë‹ˆë‹¤. ė›ëŗ¸ė´ í˜¸í™˜ë˜ė§€ ė•ŠëŠ” í˜•ė‹ė¸ ę˛Ŋ뚰 ė •ėƒė ėœŧ로 ėžŦėƒë˜ė§€ ė•Šė„ 눘 ėžˆėŠĩ니다.", "play_transcoded_video": "íŠ¸ëžœėŠ¤ėŊ”딊 ë™ė˜ėƒ ėžŦėƒ", "please_auth_to_access": "ęŗ„ė† ė§„í–‰í•˜ë ¤ëŠ´ ė¸ėĻí•˜ė„¸ėš”.", - "plugin_method_filter_type": "필터", - "plugin_method_filter_type_description": "ė´ ëŠ”ė„œë“œëŠ” ė´ë˛¤íŠ¸ëĨŧ 필터링하며, íŠšė • ėĄ°ęą´ė—ė„œ ë‹¤ėŒ ë‹¨ęŗ„ę°€ ė‹¤í–‰ë˜ė§€ ė•Šë„ëĄ 할 눘 ėžˆėŠĩ니다", "port": "íŦ트", "preferences_settings_subtitle": "ė•ą ę°œė¸ ė„¤ė •ė„ 관ëĻŦ합니다.", "preferences_settings_title": "ę°œė¸ 네렕", @@ -1647,11 +1771,11 @@ "privacy": "ę°œė¸ė •ëŗ´", "profile": "프로필", "profile_drawer_app_logs": "로그", + "profile_drawer_client_server_up_to_date": "클ëŧė´ė–¸íŠ¸ė™€ ė„œë˛„ę°€ ėĩœė‹  ėƒíƒœėž…ë‹ˆë‹¤.", "profile_drawer_github": "Github", "profile_drawer_readonly_mode": "ėŊ기 ė „ėšŠ ëĒ¨ë“œ í™œė„ąí™”. ė‚ŦėšŠėž ė•„ė´ėŊ˜ė„ 描枌 눌ëŸŦ í•´ė œí•  눘 ėžˆėŠĩ니다.", "profile_image_of_user": "{user}ë‹˜ė˜ 프로필 ė´ë¯¸ė§€", "profile_picture_set": "프로필 ė‚Ŧė§„ė´ ė„¤ė •ë˜ė—ˆėŠĩ니다.", - "projection_type": "í‘œė‹œ ë°Šė‹", "public_album": "ęŗĩ氜 ė•¨ë˛”", "public_share": "ëĒ¨ë“  ė‚ŦėšŠėžė™€ ęŗĩ뜠", "purchase_account_info": "ė„œíŦ터", @@ -1670,6 +1794,7 @@ "purchase_individual_description_2": "ė„œíŦ터 ë°°ė§€", "purchase_individual_title": "ę°œė¸", "purchase_input_suggestion": "ė œí’ˆ 키ëĨŧ ëŗ´ėœ  ė¤‘ė¸ę°€ėš”? ė•„ëž˜ė— 키ëĨŧ ėž…ë Ĩí•˜ė„¸ėš”.", + "purchase_license_subtitle": "ImmichëĨŧ ęĩŦ매하ė—Ŧ ė§€ė†ė ė¸ ę°œë°œė„ ė§€ė›í•˜ė„¸ėš”.", "purchase_lifetime_description": "ėŧíšŒė„ą ęĩŦ매", "purchase_option_title": "ęĩŦ매 ė˜ĩė…˜", "purchase_panel_info_1": "ImmichëĨŧ 개발하는 데는 ë§Žė€ ė‹œę°„ęŗŧ 노ë Ĩė´ í•„ėš”í•Šë‹ˆë‹¤. 뚰ëĻŦ는 ėĸ‹ė€ ė•ąė„ 만들기 ėœ„í•´ 풀 íƒ€ėž„ ę°œë°œėžė™€ 함ęģ˜í•˜ęŗ  ėžˆėœŧ늰, ėĩœėĸ…렁ėœŧ로 ė˜¤í”ˆ ė†ŒėŠ¤ ė†Œí”„íŠ¸ė›¨ė–´ė™€ 비ėĻˆë‹ˆėŠ¤ 행동 뜤ëĻŦ가 ę°œë°œėžė—ę˛Œ ė§€ė† 가ëŠĨ한 ėˆ˜ėž…ė›ė„ 렜ęŗĩí•˜ęŗ  ė°Šėˇ¨ė ė¸ 클ëŧėš°ë“œ ė„œëš„ėŠ¤ëĨŧ ëŒ€ė˛´í•  눘 ėžˆëŠ” ę°œė¸ ė •ëŗ´ ëŗ´í˜¸ ėƒíƒœęŗ„ëĨŧ ęĩŦėļ•하는 ę˛ƒė„ ëĒŠí‘œëĄœ 합니다.", @@ -1702,16 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {항ëĒŠ #氜} other {항ëĒŠ #氜}}ëĨŧ 냈 ė¸ëŦŧė—ę˛Œ ėžŦė§€ė •í–ˆėŠĩ니다.", "reassing_hint": "ę¸°ėĄ´ ė¸ëŦŧ뗐 ė„ íƒí•œ 항ëĒŠ 할당", "recent": "ėĩœęˇŧ", + "recent_albums": "ėĩœęˇŧ ė•¨ë˛”", "recent_searches": "ėĩœęˇŧ ę˛€ėƒ‰", "recently_added": "ėĩœęˇŧ ėļ”ę°€", "recently_added_page_title": "ėĩœęˇŧ ėļ”ę°€", "recently_taken": "ėĩœęˇŧ 항ëĒŠ", + "recently_taken_page_title": "ėĩœęˇŧ ė´Ŧė˜ë¨", "refresh": "ėƒˆëĄœęŗ ėš¨", "refresh_encoded_videos": "ë™ė˜ėƒ ėžŦė¸ėŊ”딊", "refresh_faces": "ė–ŧęĩ´ ėƒˆëĄœęŗ ėš¨", "refresh_metadata": "ëŠ”íƒ€ë°ė´í„° ę°ąė‹ ", "refresh_thumbnails": "ė„Ŧ네ėŧ ë‹¤ė‹œ ėƒė„ą", "refreshed": "ėƒˆëĄœęŗ ėš¨ė´ ė™„ëŖŒë˜ė—ˆėŠĩ니다.", + "refreshes_every_file": "ę¸°ėĄ´ 파ėŧ 및 냈 파ėŧ 늤ėē”", "refreshing_encoded_video": "ë™ė˜ėƒė„ ë‹¤ė‹œ ė¸ėŊ”딊핊니다.", "refreshing_faces": "ė–ŧęĩ´ė„ ėƒˆëĄœęŗ ėš¨í•Šë‹ˆë‹¤.", "refreshing_metadata": "ëŠ”íƒ€ë°ė´í„°ëĨŧ ėƒˆëĄœęŗ ėš¨í•Šë‹ˆë‹¤.", @@ -1724,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "ęŗĩ뜠 링íŦė—ė„œ 항ëĒŠ {count, plural, one {#氜} other {#氜}}ëĨŧ ė œęą°í•˜ė‹œę˛ ėŠĩ니까?", "remove_assets_title": "항ëĒŠė„ ė œęą°í•˜ė‹œę˛ ėŠĩ니까?", "remove_custom_date_range": "맞ėļ¤ ę¸°ę°„ ė œęą°", - "remove_filter": "필터 ė œęą°", + "remove_deleted_assets": "누ëŊ된 파ėŧ ė œęą°", "remove_from_album": "ė•¨ë˛”ė—ė„œ ė œęą°", "remove_from_album_action_prompt": "ė•¨ë˛”ė—ė„œ 항ëĒŠ {count}氜 ė œęą°ë¨", "remove_from_favorites": "ėĻę˛¨ė°žę¸°ė—ė„œ ė œęą°", @@ -1735,22 +1863,29 @@ "remove_memory": "ėļ”ė–ĩ ė œęą°", "remove_photo_from_memory": "ėļ”ė–ĩė—ė„œ ė‚Ŧė§„ ė œęą°", "remove_tag": "태그 ė œęą°", + "remove_url": "URL ė œęą°", "remove_user": "ė‚ŦėšŠėž ė‚­ė œ", "removed_api_key": "API 키 ė‚­ė œ: {name}", "removed_from_archive": "ëŗ´ę´€í•¨ė—ė„œ ė œęą°ë˜ė—ˆėŠĩ니다.", "removed_from_favorites": "ėĻę˛¨ė°žę¸°ė—ė„œ ė œęą°ë˜ė—ˆėŠĩ니다.", "removed_from_favorites_count": "ėĻę˛¨ė°žę¸°ė—ė„œ 항ëĒŠ {count, plural, other {#氜}} ė œęą°ë¨", "removed_memory": "ėļ”ė–ĩė´ ė œęą°ë˜ė—ˆėŠĩ니다.", + "removed_photo_from_memory": "ėļ”ė–ĩė—ė„œ ė‚Ŧė§„ė´ ė œęą°ë˜ė—ˆėŠĩ니다.", "removed_tagged_assets": "항ëĒŠ {count, plural, one {#氜} other {#氜}}ė—ė„œ 태그ëĨŧ ė œęą°í•¨", "rename": "ė´ëĻ„ 바꾸기", + "repair": "눘ëĻŦ", + "repair_no_results_message": "ėļ”ė ë˜ė§€ ė•Šęą°ë‚˜ 누ëŊ된 파ėŧė´ ė—Ŧ揰뗐 í‘œė‹œëŠë‹ˆë‹¤.", + "replace_with_upload": "파ėŧ 바꾸기", "repository": "ëĻŦíŦė§€í„°ëĻŦ", "require_password": "비밀번호 í•„ėš”", + "require_user_to_change_password_on_first_login": "ė‚ŦėšŠėžę°€ ė˛˜ėŒ ëĄœęˇ¸ė¸í•  때 비밀번호ëĨŧ ëŗ€ę˛Ŋ하도록 ėš”ęĩŦ", "rescan": "ėžŦę˛€ėƒ‰", "reset": "ė´ˆę¸°í™”", "reset_password": "비밀번호 ėžŦ네렕", "reset_people_visibility": "ė¸ëŦŧ í‘œė‹œ ė—Ŧëļ€ ė´ˆę¸°í™”", "reset_pin_code": "PIN ėŊ”드 ė´ˆę¸°í™”", "reset_pin_code_description": "PIN ėŊ”드ëĨŧ ėžŠė–´ë˛„ëϰ ę˛Ŋ뚰 ė´ˆę¸°í™”í•˜ë ¤ëŠ´ ė„œë˛„ 관ëĻŦėžė—ę˛Œ ė—°ëŊí•˜ė„¸ėš”.", + "reset_pin_code_success": "PIN ėŊ”드ëĨŧ ė´ˆę¸°í™”í–ˆėŠĩ니다.", "reset_pin_code_with_password": "íŒ¨ėŠ¤ė›Œë“œëĄœ PIN ėŊ”드ëĨŧ ėžŦė„¤ė •í•  눘 ėžˆėŠĩ니다.", "reset_sqlite": "SQLite ë°ė´í„°ë˛ ė´ėŠ¤ ė´ˆę¸°í™”", "reset_sqlite_clear_app_data": "ë°ė´í„° ė‚­ė œ", @@ -1764,10 +1899,12 @@ "resolved_all_duplicates": "ëš„ėŠˇí•œ 항ëĒŠė„ ëĒ¨ë‘ 래ëĻŦ했ėŠĩ니다.", "restore": "ëŗĩ뛐", "restore_all": "ëĒ¨ë‘ ëŗĩ뛐", + "restore_trash_action_prompt": "íœ´ė§€í†ĩė—ė„œ {count}氜 항ëĒŠ ëŗĩė›ë¨", "restore_user": "ė‚ŦėšŠėž ëŗĩ뛐", "restored_asset": "항ëĒŠė´ ëŗĩė›ë˜ė—ˆėŠĩ니다.", "resume": "ėžŦ氜", "resume_paused_jobs": "ėŧė‹œ ė¤‘ė§€ëœ ėž‘ė—… {count, plural, one {#氜} other {#氜}} ėžŦ氜", + "retry_upload": "ë‹¤ė‹œ ė‹œë„", "review_duplicates": "ëš„ėŠˇí•œ 항ëĒŠ í™•ė¸", "review_large_files": "ėšŠëŸ‰ė´ 큰 파ėŧ í™•ė¸", "role": "ė—­í• ", @@ -1775,6 +1912,7 @@ "role_viewer": "ëˇ°ė–´", "running": "ėž‘ë™ 뤑", "save": "ė €ėžĨ", + "save_to_gallery": "ę°¤ëŸŦëĻŦ뗐 ė €ėžĨ", "saved": "ė €ėžĨ됨", "saved_api_key": "API 키가 ėˆ˜ė •ë˜ė—ˆėŠĩ니다.", "saved_profile": "í”„ëĄœí•„ė´ ė €ėžĨë˜ė—ˆėŠĩ니다.", @@ -1785,10 +1923,9 @@ "scan": "늤ėē”", "scan_all_libraries": "ëĒ¨ë“  ëŧė´ë¸ŒëŸŦëĻŦ 늤ėē”", "scan_library": "늤ėē”", + "scan_settings": "늤ėē” ė„¤ė •", "scanning": "늤ėē” ė¤‘", "scanning_for_album": "ė•¨ë˛”ė„ 늤ėē”하는 뤑...", - "screencast_mode_description": "í™”ëŠ´ė— í‚¤ëŗ´ë“œ 및 ë§ˆėš°ėŠ¤ ė´ë˛¤íŠ¸ í‘œė‹œę¸°ëĨŧ í‘œė‹œ", - "screencast_mode_title": "늤íŦëϰėēėŠ¤íŠ¸ ëĒ¨ë“œ 토글", "search": "ę˛€ėƒ‰", "search_albums": "ė•¨ë˛” ę˛€ėƒ‰", "search_by_context": "ëŦ¸ë§Ĩ 기반 ę˛€ėƒ‰", @@ -1796,10 +1933,7 @@ "search_by_description_example": "ë™í•´ė•ˆė—ė„œ ë§žė´í•œ ėƒˆí•´ ėŧėļœ", "search_by_filename": "파ėŧëĒ… 또는 확ėžĨėžëĄœ ę˛€ėƒ‰", "search_by_filename_example": "똈: IMG_1234.JPG 또는 PNG", - "search_by_full_path": "렄랴 ę˛Ŋ로 또는 폴더로 ę˛€ėƒ‰", - "search_by_full_path_example": "똈: /John/Projects/3D_Printing/2026-07-01 Projects, 3D, Printing, 2026 등 ę˛ŊëĄœė— íŦ함된 ë‹¨ė–´ëĄœ ę˛€ėƒ‰í•  눘 ėžˆėŠĩ니다.", "search_by_ocr": "OCR로 ę˛€ėƒ‰", - "search_by_ocr_example": "ëŧë–ŧ", "search_camera_lens_model": "렌ėψ ëĒ¨ë¸ ę˛€ėƒ‰...", "search_camera_make": "ėš´ëŠ”ëŧ ė œėĄ°ė‚Ŧ ę˛€ėƒ‰...", "search_camera_model": "ėš´ëŠ”ëŧ ëĒ¨ë¸ëĒ… ę˛€ėƒ‰...", @@ -1817,6 +1951,7 @@ "search_filter_location_title": "ėœ„ėš˜ ė„ íƒ", "search_filter_media_type": "ë¯¸ë””ė–´ ėĸ…ëĨ˜", "search_filter_media_type_title": "ë¯¸ë””ė–´ ėĸ…ëĨ˜ ė„ íƒ", + "search_filter_ocr": "OCR ę˛€ėƒ‰", "search_filter_people_title": "ė¸ëŦŧ ė„ íƒ", "search_filter_star_rating": "ëŗ„ė ", "search_filter_tags_title": "태그 ė„ íƒ", @@ -1827,13 +1962,25 @@ "search_no_people_named": "\"{name}\"ė„(ëĨŧ) ė°žė„ 눘 ė—†ėŒ", "search_no_result": "ę˛€ėƒ‰ 결ęŗŧ가 ė—†ėŠĩ니다. 다ëĨ¸ ę˛€ėƒ‰ė–´ë‚˜ ėĄ°í•Šėœŧ로 ë‹¤ė‹œ ė‹œë„í•´ ëŗ´ė„¸ėš”.", "search_options": "ę˛€ėƒ‰ ė˜ĩė…˜", + "search_page_categories": "ëļ„ëĨ˜", + "search_page_motion_photos": "ëĒ¨ė…˜ íŦ토", + "search_page_no_objects": "í‘œė‹œí•  ė‚ŦëŦŧ ė •ëŗ´ ė—†ėŒ", + "search_page_no_places": "í‘œė‹œí•  ėžĨė†Œ ė •ëŗ´ ė—†ėŒ", + "search_page_screenshots": "늤íŦëϰ냎", "search_page_search_photos_videos": "ė‚Ŧė§„ 및 ë™ė˜ėƒ ę˛€ėƒ‰", + "search_page_selfies": "ė…€í”ŧ", + "search_page_things": "ė‚ŦëŦŧ", "search_page_view_all_button": "ëĒ¨ë‘ ëŗ´ę¸°", + "search_page_your_activity": "활동", + "search_page_your_map": "ë‚˜ė˜ ė§€ë„", "search_people": "ė¸ëŦŧ ę˛€ėƒ‰", "search_places": "ėžĨė†Œ ę˛€ėƒ‰", "search_rating": "ëŗ„ė ėœŧ로 ę˛€ėƒ‰...", + "search_result_page_new_search_hint": "냈 ę˛€ėƒ‰", "search_settings": "네렕 ę˛€ėƒ‰", "search_state": "맀뗭 ę˛€ėƒ‰...", + "search_suggestion_list_smart_search_hint_1": "ėŠ¤ë§ˆíŠ¸ ę˛€ėƒ‰ė´ ę¸°ëŗ¸ í™œė„ąí™”ë˜ė–´ ėžˆėŠĩ니다. ëŠ”íƒ€ë°ė´í„° ę˛€ėƒ‰ė€ ë‹¤ėŒęŗŧ ę°™ė´ ėž…ë Ĩí•˜ė„¸ėš”: ", + "search_suggestion_list_smart_search_hint_2": "m:ę˛€ėƒ‰ė–´", "search_tags": "태그 ę˛€ėƒ‰...", "search_timezone": "ė‹œę°„ëŒ€ ę˛€ėƒ‰...", "search_type": "ę˛€ėƒ‰ ėœ í˜•", @@ -1847,20 +1994,21 @@ "select_albums": "ė•¨ë˛” ė„ íƒ", "select_all": "ëĒ¨ë‘ ė„ íƒ", "select_all_duplicates": "ëš„ėŠˇí•œ 항ëĒŠ ëĒ¨ë‘ ė„ íƒ", + "select_all_in": "{group}ė˜ ëĒ¨ë“  항ëĒŠ ė„ íƒ", "select_avatar_color": "ė•„ë°”íƒ€ ėƒ‰ėƒ ė„ íƒ", "select_count": "{count, plural, one {# ė„ íƒė¤‘} other {# ė„ íƒė¤‘}}", - "select_cutoff_date": "ėœ ė§€ 기간 네렕", "select_face": "ė–ŧęĩ´ ė„ íƒ", "select_featured_photo": "대표 ė‚Ŧė§„ ė„ íƒ", "select_from_computer": "ėģ´í“¨í„°ė—ė„œ ė„ íƒ", "select_keep_all": "ëĒ¨ë‘ ėœ ė§€", + "select_library_owner": "ëŧė´ë¸ŒëŸŦëĻŦ ė†Œėœ ėž ė„ íƒ", "select_new_face": "냈 ė–ŧęĩ´ ė„ íƒ", "select_people": "ė‚Ŧ람 ė„ íƒ", "select_person": "ė‚Ŧ람 ė„ íƒ", "select_person_to_tag": "태그할 ė¸ëŦŧė„ ė„ íƒí•˜ė„¸ėš”.", "select_photos": "ė‚Ŧė§„ ė„ íƒ", - "select_quality": "í™”ė§ˆ ė„ íƒ", "select_trash_all": "ëĒ¨ë‘ ė‚­ė œ", + "select_user_for_sharing_page_err_album": "ė•¨ë˛”ė„ ėƒė„ąí•˜ė§€ ëĒģ했ėŠĩ니다.", "selected": "ė„ íƒë¨", "selected_count": "{count, plural, other {#氜 ė„ íƒë¨}}", "selected_gps_coordinates": "ė„ íƒí•œ GPS ėĸŒí‘œ", @@ -1893,29 +2041,42 @@ "setting_image_viewer_original_title": "ė›ëŗ¸ ė´ë¯¸ė§€ 로드", "setting_image_viewer_preview_subtitle": "뤑氄 í•´ėƒë„ ė´ë¯¸ė§€ëĨŧ ëļˆëŸŦė˜ĩ니다. ëš„í™œė„ąí™”í•˜ëŠ” ę˛Ŋ뚰 ė›ëŗ¸ 또는 ė„Ŧ네ėŧ만 ëļˆëŸŦė˜ĩ니다.", "setting_image_viewer_preview_title": "미ëĻŦëŗ´ę¸° ė´ë¯¸ė§€ 로드", + "setting_image_viewer_title": "ė´ë¯¸ė§€", "setting_languages_apply": "ė ėšŠ", "setting_languages_subtitle": "ė•ą 떏떴 ëŗ€ę˛Ŋ", + "setting_notifications_notify_failures_grace_period": "밹꡸ëŧėš´ë“œ ë°ąė—… ė‹¤íŒ¨ ė•ŒëĻŧ: {duration} 후", + "setting_notifications_notify_hours": "{count}ė‹œę°„", + "setting_notifications_notify_immediately": "ėĻ‰ė‹œ", "setting_notifications_notify_minutes": "{count}ëļ„", + "setting_notifications_notify_never": "ė•ŒëĻŦė§€ ė•ŠėŒ", "setting_notifications_notify_seconds": "{count}봈", + "setting_notifications_single_progress_subtitle": "ę°œëŗ„ 항ëĒŠė˜ ėƒė„¸ ė—…ëĄœë“œ ė •ëŗ´ í‘œė‹œ", + "setting_notifications_single_progress_title": "밹꡸ëŧėš´ë“œ ë°ąė—… ėƒė„¸ ė§„í–‰ëĨ  í‘œė‹œ", "setting_notifications_subtitle": "ė•ŒëĻŧ ę¸°ëŗ¸ 네렕 ėĄ°ė •", + "setting_notifications_total_progress_subtitle": "렄랴 ė—…ëĄœë“œ ė§„í–‰ëĨ  (ė™„ëŖŒ/ė´ 항ëĒŠ)", + "setting_notifications_total_progress_title": "밹꡸ëŧėš´ë“œ ë°ąė—… 렄랴 ė§„í–‰ëĨ  í‘œė‹œ", "setting_video_viewer_auto_play_subtitle": "ë™ė˜ėƒė„ ė—´ëŠ´ ėžë™ėœŧ로 ėžŦėƒ", "setting_video_viewer_auto_play_title": "ë™ė˜ėƒ ėžë™ ėžŦėƒ", "setting_video_viewer_looping_title": "반ëŗĩ", "setting_video_viewer_original_video_subtitle": "ë™ė˜ėƒ ėŠ¤íŠ¸ëĻŦ밍 ė‹œ íŠ¸ëžœėŠ¤ėŊ”딊된 파ėŧ ëŒ€ė‹  ė›ëŗ¸ė„ ėžŦėƒí•Šë‹ˆë‹¤. ėžŦėƒ ė‹œ 버íŧë§ė´ ë°œėƒí•  눘 ėžˆėŠĩ니다. 로ėģŦ뗐 ėžˆëŠ” ė˜ėƒė€ í•­ėƒ ė›ëŗ¸ í™”ė§ˆëĄœ ėžŦėƒëŠë‹ˆë‹¤.", "setting_video_viewer_original_video_title": "ė›ëŗ¸ ë™ė˜ėƒ ę°•ė œ ė‚ŦėšŠ", "settings": "네렕", + "settings_require_restart": "ė„¤ė •ė„ ė ėšŠí•˜ë ¤ëŠ´ ImmichëĨŧ ë‹¤ė‹œ ė‹œėž‘í•´ė•ŧ 합니다.", "settings_saved": "ė„¤ė •ė´ ė €ėžĨë˜ė—ˆėŠĩ니다.", "setup_pin_code": "PIN ėŊ”드 네렕", "share": "ęŗĩ뜠", + "share_action_prompt": "항ëĒŠ {count}氜 ęŗĩėœ ë¨", + "share_add_photos": "ė‚Ŧė§„ ėļ”ę°€", + "share_assets_selected": "{count}氜 ė„ íƒë¨", "share_dialog_preparing": "ė¤€ëš„ 뤑...", "share_link": "ęŗĩ뜠 링íŦ", - "share_original": "ė›ëŗ¸ ė‚ŦėšŠ (ęŗ í•´ėƒë„)", - "share_preview": "ė¸ë„¤ėŧ ė‚ŦėšŠ (ė €í•´ėƒë„)", "shared": "ęŗĩėœ ë¨", "shared_album_activities_input_disable": "ëŒ“ę¸€ė´ ëš„í™œė„ąí™”ë˜ė—ˆėŠĩ니다", "shared_album_activity_remove_content": "ė´ í™œë™ė„ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "shared_album_activity_remove_title": "활동 ė‚­ė œ", "shared_album_section_people_action_error": "ė•¨ë˛”ė—ė„œ 나가기/ė œęą° 뤑 ëŦ¸ė œę°€ ë°œėƒí–ˆėŠĩ니다.", + "shared_album_section_people_action_leave": "ė•¨ë˛”ė—ė„œ ė‚ŦėšŠėž ė œęą°", + "shared_album_section_people_action_remove_user": "ė•¨ë˛”ė—ė„œ ė‚ŦėšŠėž ė œęą°", "shared_album_section_people_title": "ė‚ŦėšŠėž", "shared_by": "ë‹¤ėŒ ė‚ŦėšŠėžę°€ ęŗĩ뜠", "shared_by_user": "{user}ë‹˜ė´ ęŗĩėœ í•¨", @@ -1924,6 +2085,7 @@ "shared_intent_upload_button_progress_text": "{total}氜 뤑 {current}氜 ė—…ëĄœë“œë¨", "shared_link_app_bar_title": "ęŗĩ뜠 링íŦ", "shared_link_clipboard_copied_massage": "클ëĻŊëŗ´ë“œė— ëŗĩė‚Ŧë˜ė—ˆėŠĩ니다.", + "shared_link_clipboard_text": "링íŦ: {link}\n비밀번호: {password}", "shared_link_create_error": "ęŗĩ뜠 링íŦ ėƒė„ą 뤑 똤ëĨ˜ ë°œėƒ", "shared_link_custom_url_description": "ė‚ŦėšŠėž 맀렕 URLė„ ė‚ŦėšŠí•˜ė—Ŧ ęŗĩ뜠 링íŦ ė ‘ęˇŧ", "shared_link_edit_description_hint": "ęŗĩ뜠 링íŦ 네ëĒ… ėž…ë Ĩ", @@ -1959,6 +2121,12 @@ "shared_with_partner": "{partner}님ęŗŧ ęŗĩėœ í•¨", "sharing": "ęŗĩ뜠", "sharing_enter_password": "ė´ íŽ˜ė´ė§€ëĨŧ ëŗ´ë ¤ëŠ´ 비밀번호ëĨŧ ėž…ë Ĩí•˜ė„¸ėš”.", + "sharing_page_album": "ęŗĩ뜠 ė•¨ë˛”", + "sharing_page_description": "ęŗĩ뜠 ė•¨ë˛”ė„ ë§Œë“¤ė–´ ėŖŧëŗ€ ė‚Ŧ람들ęŗŧ ė‚Ŧė§„ 및 ë™ė˜ėƒė„ ęŗĩėœ í•˜ė„¸ėš”.", + "sharing_page_empty_list": "ęŗĩ뜠 ė•¨ë˛” ė—†ėŒ", + "sharing_sidebar_description": "ė‚Ŧė´ë“œë°”ė— ęŗĩ뜠 링íŦ í‘œė‹œ", + "sharing_silver_appbar_create_shared_album": "냈 ęŗĩ뜠 ė•¨ë˛”", + "sharing_silver_appbar_share_partner": "íŒŒíŠ¸ë„ˆė™€ ęŗĩ뜠", "shift_to_permanent_delete": "⇧ëĨŧ 눌ëŸŦ 항ëĒŠ 똁ęĩŦ ė‚­ė œ", "show_album_options": "ė•¨ë˛” ė˜ĩė…˜ í‘œė‹œ", "show_albums": "ė•¨ë˛” í‘œė‹œ", @@ -1970,9 +2138,7 @@ "show_in_timeline": "íƒ€ėž„ëŧė¸ė— í‘œė‹œ", "show_in_timeline_setting_description": "íƒ€ėž„ëŧė¸ė— ė´ ė‚ŦėšŠėžė˜ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė„ í‘œė‹œ", "show_keyboard_shortcuts": "í‚¤ëŗ´ë“œ 단ėļ•키 í‘œė‹œ", - "show_less": "렑揰", "show_metadata": "ëŠ”íƒ€ë°ė´í„° í‘œė‹œ", - "show_more_fields": "필드 {count}氜 더 ëŗ´ę¸°", "show_or_hide_info": "ė •ëŗ´ í‘œė‹œ 또는 눍揰揰", "show_password": "비밀번호 í‘œė‹œ", "show_person_options": "ė¸ëŦŧ ė˜ĩė…˜ í‘œė‹œ", @@ -1980,7 +2146,6 @@ "show_schema": "ėŠ¤í‚¤ë§ˆ í‘œė‹œ", "show_search_options": "ę˛€ėƒ‰ ė˜ĩė…˜ í‘œė‹œ", "show_shared_links": "ęŗĩ뜠 링íŦ í‘œė‹œ", - "show_slideshow_metadata_overlay": "ė´ë¯¸ė§€ ė •ëŗ´ í‘œė‹œ", "show_slideshow_transition": "ėŠŦëŧė´ë“œ ė „í™˜ í‘œė‹œ", "show_supporter_badge": "ė„œíŦ터 ë°°ė§€", "show_supporter_badge_description": "ė„œíŦ터 ë°°ė§€ í‘œė‹œ", @@ -1996,18 +2161,14 @@ "skip_to_folders": "폴더로 건너뛰기", "skip_to_tags": "태그로 건너뛰기", "slideshow": "ėŠŦëŧė´ë“œ ė‡ŧ", - "slideshow_metadata_overlay_mode": "í‘œė‹œí•  ė •ëŗ´", - "slideshow_metadata_overlay_mode_description_only": "네ëĒ…ë§Œ ëŗ´ę¸°", - "slideshow_metadata_overlay_mode_full": "렄랴 í‘œė‹œ", "slideshow_repeat": "ėŠŦëŧė´ë“œ ė‡ŧ 반ëŗĩ", "slideshow_repeat_description": "ėŠŦëŧė´ë“œ ė‡ŧ가 끝나면 ė˛˜ėŒėœŧ로 ë˜ëŒė•„ę°‘ë‹ˆë‹¤", "slideshow_settings": "ėŠŦëŧė´ë“œ ė‡ŧ 네렕", - "smart_album": "ėŠ¤ë§ˆíŠ¸ ė•¨ë˛”", - "some_assets_already_have_a_location_warning": "ė„ íƒí•œ 항ëĒŠ 뤑 ėŧëļ€ė—ëŠ” ė´ë¯¸ ėœ„ėš˜ ė •ëŗ´ę°€ ėžˆėŠĩ니다", "sort_albums_by": "ë‹¤ėŒėœŧ로 ė•¨ë˛” ė •ë Ŧ...", "sort_created": "ėƒė„ąëœ ë‚ ė§œ", "sort_items": "항ëĒŠ 눘", "sort_modified": "ėˆ˜ė •ëœ ë‚ ė§œ", + "sort_newest": "ėĩœęˇŧ ė‚Ŧė§„", "sort_oldest": "ė˜¤ëž˜ëœ ė‚Ŧė§„", "sort_people_by_similarity": "뜠ė‚Ŧė„ąė„ 揰뤀ėœŧ로 ė¸ëŦŧ ė •ë Ŧ", "sort_recent": "ėĩœęˇŧ ė‚Ŧė§„", @@ -2016,6 +2177,7 @@ "stack": "ėŠ¤íƒ", "stack_action_prompt": "항ëĒŠ {count}氜 ėŠ¤íƒë¨", "stack_duplicates": "ëš„ėŠˇí•œ 항ëĒŠ ėŠ¤íƒ", + "stack_select_one_photo": "ėŠ¤íƒė˜ 대표 ė‚Ŧė§„ ė„ íƒ", "stack_selected_photos": "ė„ íƒí•œ ė´ë¯¸ė§€ ėŠ¤íƒ", "stacked_assets_count": "항ëĒŠ {count, plural, one {#氜} other {#氜}} ėŠ¤íƒë¨", "stacktrace": "ėŠ¤íƒ ėļ”렁", @@ -2024,10 +2186,6 @@ "start_date_before_end_date": "ė‹œėž‘ėŧė€ ėĸ…ëŖŒėŧëŗ´ë‹¤ ė´ė „ė´ė–´ė•ŧ 합니다", "state": "맀뗭", "status": "ėƒíƒœ", - "step_delete": "ë‹¨ęŗ„ ė‚­ė œ", - "step_delete_confirm": "ė´ ë‹¨ęŗ„ëĨŧ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", - "step_details": "ë‹¨ęŗ„ ėƒė„¸ ė •ëŗ´", - "steps": "ë‹¨ęŗ„", "stop_casting": "ėēėŠ¤íŒ… ė¤‘ë‹¨", "stop_motion_photo": "ëĒ¨ė…˜ íŦ토 ė •ė§€", "stop_photo_sharing": "ęŗĩ뜠ëĨŧ ė¤‘ë‹¨í•˜ė‹œę˛ ėŠĩ니까?", @@ -2048,18 +2206,17 @@ "swap_merge_direction": "ëŗ‘í•Š ë°Ší–Ĩ ëŗ€ę˛Ŋ", "sync": "동기화", "sync_albums": "ė•¨ë˛” 동기화", + "sync_albums_manual_subtitle": "ė—…ëĄœë“œí•œ ëĒ¨ë“  ë™ė˜ėƒęŗŧ ė‚Ŧė§„ė„ ė„ íƒí•œ ë°ąė—… ė•¨ë˛”ė— 동기화", "sync_local": "로ėģŦ 동기화", "sync_remote": "ė›ę˛Š 동기화", "sync_status": "동기화 ėƒíƒœ", "sync_status_subtitle": "동기화 ė‹œėŠ¤í…œ í™•ė¸ 및 관ëĻŦ", "sync_upload_album_setting_subtitle": "ė„ íƒí•œ ė•¨ë˛”ė„ Immich뗐 ėƒė„ąí•˜ęŗ  ė‚Ŧė§„ 및 ë™ė˜ėƒ ė—…ëĄœë“œ", - "system_theme": "ė‹œėŠ¤í…œ 테마", - "system_theme_command_description": "ė‹œėŠ¤í…œ 테마 ė‚ŦėšŠ({value})", "tag": "태그", "tag_assets": "항ëĒŠ 태그", "tag_created": "태그 ėƒė„ąë¨: {tag}", - "tag_face": "ė–ŧęĩ´ 태그 ëļ€ė—Ŧ", "tag_feature_description": "태그 ėŖŧė œëŗ„ëĄœ ęˇ¸ëŖší™”ëœ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒ íƒėƒ‰", + "tag_not_found_question": "태그ëĨŧ ė°žė„ 눘 ė—†ë‚˜ėš”? 냈 태그ëĨŧ ėƒė„ąí•˜ė„¸ėš”.", "tag_people": "ė¸ëŦŧ 태그", "tag_updated": "태그 ė—…ë°ė´íŠ¸ë¨: {tag}", "tagged_assets": "항ëĒŠ {count, plural, one {#氜} other {#氜}}뗐 태그ëĨŧ ė ėšŠí•¨", @@ -2074,11 +2231,15 @@ "theme_setting_asset_list_tiles_per_row_title": "한 뤄뗐 í‘œė‹œí•  항ëĒŠ 눘 ({count})", "theme_setting_colorful_interface_subtitle": "ę¸°ëŗ¸ ėƒ‰ėƒė„ ë°°ę˛Ŋ뗐 ė ėšŠ", "theme_setting_colorful_interface_title": "미려한 ė¸í„°íŽ˜ė´ėŠ¤", + "theme_setting_image_viewer_quality_subtitle": "ėƒė„¸ ëŗ´ę¸° ė´ë¯¸ė§€ í’ˆė§ˆ ėĄ°ė •", + "theme_setting_image_viewer_quality_title": "ė´ë¯¸ė§€ ëŗ´ę¸° í’ˆė§ˆ", "theme_setting_primary_color_subtitle": "ėŖŧėš” 기ëŠĨ 및 ę°•ėĄ°ė— ė‚ŦėšŠí•  ėƒ‰ėƒ ė„ íƒ", "theme_setting_primary_color_title": "ę¸°ëŗ¸ ėƒ‰ėƒ", "theme_setting_system_primary_color_title": "ė‹œėŠ¤í…œ ėƒ‰ėƒ ė‚ŦėšŠ", "theme_setting_system_theme_switch": "ėžë™ (ė‹œėŠ¤í…œ 네렕)", - "then": "ęˇ¸ë‹¤ėŒ", + "theme_setting_theme_subtitle": "ė•ą 테마 ė„ íƒ", + "theme_setting_three_stage_loading_subtitle": "3ë‹¨ęŗ„ ëĄœë”Šė„ ė‚ŦėšŠí•˜ëŠ´ 로드 ė„ąëŠĨė„ í–Ĩėƒė‹œí‚Ŧ 눘 ėžˆėœŧ나, ë„¤íŠ¸ė›ŒíŦ ëļ€í•˜ę°€ íŦ枌 ėĻę°€í•Šë‹ˆë‹¤.", + "theme_setting_three_stage_loading_title": "3ë‹¨ęŗ„ 로드 í™œė„ąí™”", "they_will_be_merged_together": "ė„ íƒí•œ ė¸ëŦŧë“¤ė„ 한 ė¸ëŦŧ로 í•ŠėšŠë‹ˆë‹¤.", "third_party_resources": "ė„œë“œ 파티 ëĻŦė†ŒėŠ¤", "time": "ė‹œę°„", @@ -2103,17 +2264,23 @@ "trash_all": "ëĒ¨ë‘ ė‚­ė œ", "trash_count": "{count, number}氜 ė‚­ė œ", "trash_delete_asset": "íœ´ė§€í†ĩ ė´ë™/ė‚­ė œ", + "trash_emptied": "íœ´ė§€í†ĩė„ ëš„ė› ėŠĩ니다.", "trash_no_results_message": "ė‚­ė œëœ ė‚Ŧė§„ęŗŧ ë™ė˜ėƒė´ ė—Ŧ揰뗐 í‘œė‹œëŠë‹ˆë‹¤.", "trash_page_delete_all": "ëĒ¨ë‘ ė‚­ė œ", + "trash_page_empty_trash_dialog_content": "íœ´ė§€í†ĩė„ ëš„ėš°ė‹œę˛ ėŠĩ니까? íœ´ė§€í†ĩė˜ ëĒ¨ë“  항ëĒŠė´ Immichė—ė„œ 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", "trash_page_info": "íœ´ė§€í†ĩėœŧ로 ė´ë™ëœ 항ëĒŠė€ {days}ėŧ 후 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", + "trash_page_no_assets": "íœ´ė§€í†ĩė´ ëš„ė–´ ėžˆėŒ", + "trash_page_restore_all": "ëĒ¨ë‘ ëŗĩ뛐", + "trash_page_select_assets_btn": "항ëĒŠ ė„ íƒ", + "trash_page_title": "íœ´ė§€í†ĩ ({count})", "trashed_items_will_be_permanently_deleted_after": "íœ´ė§€í†ĩėœŧ로 ė´ë™ëœ 항ëĒŠė€ {days, plural, one {#ėŧ} other {#ėŧ}} 후 똁ęĩŦ렁ėœŧ로 ė‚­ė œëŠë‹ˆë‹¤.", "trigger": "트ëĻŦęą°", - "trigger_asset_metadata_extraction": "ë¯¸ë””ė–´ ëŠ”íƒ€ë°ė´í„° ėļ”ėļœ", - "trigger_asset_metadata_extraction_description": "ė‚Ŧė§„ 또는 ë™ė˜ėƒė˜ EXIF ëŠ”íƒ€ë°ė´í„°ę°€ ėļ”ėļœë  때 ë°œėƒí•Šë‹ˆë‹¤", - "trigger_asset_uploaded": "ë¯¸ë””ė–´ ė—…ëĄœë“œ ė™„ëŖŒ", + "trigger_asset_uploaded": "ėžė‚° ė—…ëĄœë“œë¨", "trigger_asset_uploaded_description": "ėƒˆëĄœėš´ ė—ė…‹ė´ ė—…ëĄœë“œë  때 트ëĻŦ거됩니다", + "trigger_description": "ė›ŒíŦí”ŒëĄœėš°ëĨŧ ė‹œėž‘í•˜ëŠ” ė´ë˛¤íŠ¸", "trigger_person_recognized": "닠뛐 í™•ė¸ë¨", "trigger_person_recognized_description": "ė‚ŦëžŒė´ ę°ė§€ë˜ëŠ´ ėž‘ë™í•Šë‹ˆë‹¤", + "trigger_type": "트ëĻŦęą° ėœ í˜•", "troubleshoot": "ëŦ¸ė œ 해결", "type": "í˜•ė‹", "unable_to_change_pin_code": "PIN ėŊ”드ëĨŧ ëŗ€ę˛Ŋ할 눘 ė—†ėŒ", @@ -2141,20 +2308,23 @@ "unsaved_change": "ė €ėžĨë˜ė§€ ė•Šė€ ëŗ€ę˛Ŋ ė‚Ŧ항", "unselect_all": "ëĒ¨ë‘ ė„ íƒ í•´ė œ", "unselect_all_duplicates": "ëĒ¨ë‘ ė„ íƒ í•´ė œ", + "unselect_all_in": "ëĒ¨ë“  {group} ė„ íƒ í•´ė œ", "unstack": "ėŠ¤íƒ 풀기", "unstack_action_prompt": "항ëĒŠ {count}氜 ėŠ¤íƒ 풀ëĻŧ", "unstacked_assets_count": "항ëĒŠ {count, plural, one {#氜} other {#氜}}ė˜ ėŠ¤íƒė„ í’€ė—ˆėŠĩ니다.", "unsupported_field_type": "ė§€ė›ë˜ė§€ ė•ŠëŠ” 필드 ėœ í˜•", "unsupported_file_type": "{file} 파ėŧė€ {type} ėœ í˜•ė´ ė§€ė›ë˜ė§€ ė•Šė•„ ė—…ëĄœë“œí•  눘 ė—†ė—ˆėŠĩ니다.", "untagged": "태그 í•´ė œë¨", + "untitled_workflow": "렜ëĒŠ ė—†ëŠ” ė›ŒíŦ플로", "up_next": "ë‹¤ėŒ", "update_location_action_prompt": "ė„ íƒí•œ {count}氜 항ëĒŠ ėœ„ėš˜ ė—…ë°ė´íŠ¸:", "updated_at": "ė—…ë°ė´íŠ¸ë¨", "updated_password": "비밀번호가 ëŗ€ę˛Ŋë˜ė—ˆėŠĩ니다.", "upload": "ė—…ëĄœë“œ", "upload_concurrency": "ė—…ëĄœë“œ ë™ė‹œė„ą", - "upload_day_count": "{date}: {count}ęą´ ė—…ëĄœë“œ", "upload_details": "ė—…ëĄœë“œ ėƒė„¸", + "upload_dialog_info": "ė„ íƒí•œ 항ëĒŠė„ ė„œë˛„ė— ë°ąė—…í•˜ė‹œę˛ ėŠĩ니까?", + "upload_dialog_title": "항ëĒŠ ė—…ëĄœë“œ", "upload_error_with_count": "{count, plural, one {#氜} other {#氜}} 항ëĒŠ ė—…ëĄœë“œ ė‹¤íŒ¨", "upload_errors": "ė—…ëĄœë“œę°€ ė™„ëŖŒë˜ė—ˆėŠĩ니다. 항ëĒŠ {count, plural, one {#氜} other {#氜}}ëĨŧ ė—…ëĄœë“œí•˜ė§€ ëĒģ했ėŠĩ니다. ė—…ëĄœë“œëœ 항ëĒŠė„ ëŗ´ë ¤ëŠ´ íŽ˜ė´ė§€ëĨŧ ėƒˆëĄœęŗ ėš¨í•˜ė„¸ėš”.", "upload_finished": "ė—…ëĄœë“œ ė™„ëŖŒ", @@ -2167,8 +2337,6 @@ "upload_to_immich": "Immich뗐 ė—…ëĄœë“œ ({count})", "uploading": "ė—…ëĄœë“œ 뤑", "uploading_media": "ë¯¸ë””ė–´ ė—…ëĄœë“œ 뤑...", - "uploads": "ė—…ëĄœë“œ", - "uploads_count": "{count}ęą´ ė—…ëĄœë“œ", "url": "URL", "usage": "ė‚ŦėšŠëŸ‰", "use_biometric": "ėƒė˛´ ė¸ėĻ ė‚ŦėšŠ", @@ -2176,7 +2344,6 @@ "use_browser_locale_description": "브ëŧėš°ė € 로ėŧ€ėŧ 기반 ë‚ ė§œ, ė‹œę°„, ėˆĢėž í˜•ė‹", "use_current_connection": "현ėžŦ ë„¤íŠ¸ė›ŒíŦ ė‚ŦėšŠ", "use_custom_date_range": "ëŒ€ė‹  맞ėļ¤ ę¸°ę°„ ė‚ŦėšŠ", - "use_template": "템플ëĻŋ ė‚ŦėšŠ", "user": "ė‚ŦėšŠėž", "user_has_been_deleted": "ė´ ė‚ŦėšŠėžëŠ” ė‚­ė œë˜ė—ˆėŠĩ니다.", "user_id": "ė‚ŦėšŠėž ID", @@ -2186,6 +2353,7 @@ "user_privacy": "ę°œė¸ė •ëŗ´", "user_purchase_settings": "ęĩŦ매", "user_purchase_settings_description": "ęĩŦ매 ė„¤ė •ė„ 관ëĻŦí•˜ęŗ  키ëĨŧ 등록 및 ė œęą°í•Šë‹ˆë‹¤.", + "user_role_set": "{user}ė—ę˛Œ {role} ė—­í•  맀렕", "user_usage_detail": "ė‚ŦėšŠėž ė‚ŦėšŠëŸ‰ ėƒė„¸", "user_usage_stats": "ė‚ŦėšŠëŸ‰ í†ĩęŗ„", "user_usage_stats_description": "ęŗ„ė •ė˜ 렄랴 및 범ėŖŧëŗ„ ė‚ŦėšŠëŸ‰ė„ í™•ė¸í•Šë‹ˆë‹¤.", @@ -2195,6 +2363,7 @@ "utilities": "도ęĩŦ", "validate": "검ėĻ", "validate_endpoint_error": "ėœ íš¨í•œ URLė„ ėž…ë Ĩí•˜ė„¸ėš”.", + "validation_error": "ėœ íš¨ė„ą 검ė‚Ŧ 똤ëĨ˜", "variables": "ëŗ€ėˆ˜", "version": "ë˛„ė „", "version_announcement_closing": "ë‹šė‹ ė˜ ėšœęĩŦ, Alex가", @@ -2204,9 +2373,9 @@ "video": "ë™ė˜ėƒ", "video_hover_setting": "ė„Ŧ네ėŧ 똁냁 미ëĻŦëŗ´ę¸°", "video_hover_setting_description": "ė„Ŧ네ėŧ ėœ„ė— ë§ˆėš°ėŠ¤ëĨŧ ė˜ŦëĻŦ늴 미ëĻŦëŗ´ę¸°ëĨŧ ėžŦėƒí•Šë‹ˆë‹¤. ëš„í™œė„ąí™”í•´ë„ ėžŦėƒ ė•„ė´ėŊ˜ė— ë§ˆėš°ėŠ¤ëĨŧ ė˜Ŧë ¤ 미ëĻŦëŗŧ 눘 ėžˆėŠĩ니다.", - "video_quality": "ë™ė˜ėƒ í™”ė§ˆ", "videos": "ë™ė˜ėƒ", "videos_count": "ë™ė˜ėƒ {count, plural, one {#氜} other {#氜}}", + "videos_only": "ë™ė˜ėƒë§Œ", "view": "ëŗ´ę¸°", "view_album": "ė•¨ë˛” ëŗ´ę¸°", "view_all": "ëĒ¨ë‘ ëŗ´ę¸°", @@ -2215,26 +2384,29 @@ "view_details": "ėƒė„¸ ëŗ´ę¸°", "view_in_timeline": "íƒ€ėž„ëŧė¸ė—ė„œ ëŗ´ę¸°", "view_link": "링íŦ ëŗ´ę¸°", + "view_links": "링íŦ ëŗ´ę¸°", "view_name": "ëŗ´ę¸°", "view_next_asset": "ë‹¤ėŒ 항ëĒŠ ëŗ´ę¸°", "view_previous_asset": "ė´ė „ 항ëĒŠ ëŗ´ę¸°", "view_qr_code": "QR ėŊ”드 ëŗ´ę¸°", "view_similar_photos": "ëš„ėŠˇí•œ ė‚Ŧė§„ ëŗ´ę¸°", "view_stack": "ėŠ¤íƒ ëŗ´ę¸°", + "view_user": "ė‚ŦėšŠėž ëŗ´ę¸°", "viewer_remove_from_stack": "ėŠ¤íƒė—ė„œ ė œęą°", + "viewer_stack_use_as_main_asset": "대표 항ëĒŠėœŧ로 네렕", + "viewer_unstack": "ėŠ¤íƒ 풀기", "visibility": "í‘œė‹œ 네렕", "visibility_changed": "ė¸ëŦŧ {count, plural, one {#ëĒ…} other {#ëĒ…}}ė˜ í‘œė‹œ ė—Ŧëļ€ę°€ ëŗ€ę˛Ŋ됨", "visual": "비ėŖŧė–ŧ", + "visual_builder": "비ėŖŧė–ŧ 빌더", "waiting": "대기 뤑", "waiting_count": "대기: {count}", "warning": "ę˛Ŋęŗ ", "week": "ėŖŧ", "welcome": "í™˜ė˜í•Šë‹ˆë‹¤", "welcome_to_immich": "í™˜ė˜í•Šë‹ˆë‹¤", - "when": "ė‹¤í–‰ ėĄ°ęą´", "width": "너비", "wifi_name": "W-Fi ė´ëĻ„", - "workflow": "ėž‘ė—… í”„ëĄœė„¸ėŠ¤", "workflow_delete_prompt": "ė´ ė›ŒíŦ플로ëĨŧ ė •ë§ëĄœ ė‚­ė œí•˜ė‹œę˛ ėŠĩ니까?", "workflow_deleted": "ė›ŒíŦ플로가 ė‚­ė œë˜ė—ˆėŠĩ니다", "workflow_description": "ė›ŒíŦ플로 네ëĒ…", @@ -2244,17 +2416,17 @@ "workflow_name": "ė›ŒíŦ플로 ė´ëĻ„", "workflow_navigation_prompt": "ëŗ€ę˛Ŋ ė‚Ŧí•­ė„ ė €ėžĨí•˜ė§€ ė•Šęŗ  ė´ë™í•˜ė‹œę˛ ėŠĩ니까?", "workflow_summary": "ė›ŒíŦí”ŒëĄœėš° ėš”ė•Ŋ", - "workflow_templates": "ėžë™í™” 템플ëĻŋ", "workflow_update_success": "ė›ŒíŦ플로가 ė„ąęŗĩ렁ėœŧ로 ė—…ë°ė´íŠ¸ë˜ė—ˆėŠĩ니다", + "workflow_updated": "ė›ŒíŦ플로가 ė—…ë°ė´íŠ¸ë˜ė—ˆėŠĩ니다", "workflows": "ė›ŒíŦ플로", "workflows_help_text": "ė›ŒíŦ플로는 트ëĻŦęą°ė™€ 필터ëĨŧ 기반ėœŧ로 ėžė‚°ė— 대한 ėž‘ė—…ė„ ėžë™í™”í•Šë‹ˆë‹¤", "wrong_pin_code": "ėž˜ëĒģ된 PIN ėŊ”드", - "x_of_total": "{x}/{total}", "year": "년", "years_ago": "{years, plural, one {#년} other {#년}} ė „", "yes": "네", "you_dont_have_any_shared_links": "ęŗĩ뜠 링íŦ가 ė—†ėŠĩ니다.", "your_wifi_name": "Wi-Fi ë„¤íŠ¸ė›ŒíŦ ė´ëĻ„", "zero_to_clear_rating": "0ė„ 눌ëŸŦ 항ëĒŠ ëŗ„ė  ė´ˆę¸°í™”", - "zoom_image": "ė´ë¯¸ė§€ 확대" + "zoom_image": "ė´ë¯¸ė§€ 확대", + "zoom_to_bounds": "í™”ëŠ´ė— 맞ėļ° í™•ëŒ€" } diff --git a/i18n/kxm.json b/i18n/kxm.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/kxm.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/lmo.json b/i18n/lmo.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/lmo.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/lt.json b/i18n/lt.json index b41d751c7b..b686c37b83 100644 --- a/i18n/lt.json +++ b/i18n/lt.json @@ -10,31 +10,39 @@ "active": "Vykdoma", "active_count": "Vykdoma: {count}", "activity": "Veikla", + "activity_changed": "Veikla yra {enabled, select, true {įjungta} other {iÅĄjungta}}", "add": "Pridėti", "add_a_description": "Pridėti apraÅĄymą", "add_a_location": "Pridėti vietovę", "add_a_name": "Pridėti vardą", "add_a_title": "Pridėti pavadinimą", "add_action": "Pridėti veiksmą", + "add_action_description": "Spustelėkite, kad pridėtumėte veiksmą atlikimui", "add_assets": "Pridėti", "add_birthday": "Pridėti gimimo diena", "add_endpoint": "Pridėti galutinį taÅĄką", "add_exclusion_pattern": "Pridėti iÅĄimčiÅŗ ÅĄabloną", + "add_filter": "Pritaikyti filtrą", + "add_filter_description": "Spustelėkite, kad pridėtumėte filtro sąlygą", "add_location": "Pridėti vietovę", + "add_more_users": "Pridėti daugiau naudotojÅŗ", "add_partner": "Pridėti partnerį", + "add_path": "Pridėti kelią", "add_photos": "Pridėti nuotraukÅŗ", - "add_step": "Pridėti Åžingsnį", "add_tag": "Pridėti Åžymą", "add_to": "Pridėti įâ€Ļ", "add_to_album": "Pridėti į albumą", "add_to_album_bottom_sheet_added": "Pridėta į {album}", "add_to_album_bottom_sheet_already_exists": "Jau yra {album}", "add_to_album_bottom_sheet_some_local_assets": "Dalis vietiniÅŗ elementÅŗ negalėjo bÅĢti pridėti į albumą", + "add_to_album_toggle": "Perjungti pasirinkimus ÅĄiam {album}", "add_to_albums": "Pridėti į albumus", "add_to_albums_count": "Pridėti į albumus ({count})", "add_to_bottom_bar": "Pridėti prie", + "add_to_shared_album": "Pridėti į bendrinamą albumą", "add_upload_to_stack": "Pridėti įkėlimą į krÅĢvą", "add_url": "Pridėti URL", + "add_workflow_step": "Pridėti darbÅŗ eigos Åžingsnį", "added_to_archive": "Pridėta į archyvą", "added_to_favorites": "Pridėta prie mėgstamiausiÅŗ", "added_to_favorites_count": "{count, plural, one {# pridėtas} few {# pridėti} other {# pridėta}} prie mėgstamiausiÅŗ", @@ -73,7 +81,6 @@ "cron_expression_description": "Nustatyti skenavimo intervalą naudojant cron formatą. Norėdami gauti daugiau informacijos ÅžiÅĢrėkite Crontab Guru", "cron_expression_presets": "IÅĄankstiniai Cron nustatymai", "disable_login": "IÅĄjungti prisijungimą", - "download_csv": "ParsisiÅŗsti CSV", "duplicate_detection_job_description": "Vykdyti maÅĄininį mokymąsi panaÅĄiÅŗ vaizdÅŗ aptikimui. Priklauso nuo iÅĄmaniosios paieÅĄkos", "exclusion_pattern_description": "IÅĄimčiÅŗ ÅĄablonai leidÅžia nepaisyti failÅŗ ir aplankÅŗ skenuojant jÅĢsÅŗ biblioteką. Tai yra naudinga, jei turite aplankÅŗ su failais, kuriÅŗ nenorite importuoti, pavyzdÅžiui, RAW failai.", "export_config_as_json_description": "AtsisiÅŗskite dabartinę sistemos konfigÅĢraciją kaip JSON failą", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "MiniatiÅĢros kokybė nuo 1-100. AukÅĄtesnės reikÅĄmės yra geriau, bet pagaminami didesni failai ir gali bÅĢti sulėtintas programos reagavimo greitis.", "image_thumbnail_title": "MiniatiÅĢros nustatymai", "import_config_from_json_description": "Importuokite sistemos konfigÅĢraciją, įkeliant JSON konfigÅĢracijos failą", - "integrity_checks_checksum_files": "Kontrolinės sumos failai", - "integrity_checks_checksum_files_description": "KonfigÅĢruoti kontrolinės sumos patikrą", - "integrity_checks_checksum_files_enable_description": "ÄŽjungti kontrolinės sumos patikrą", - "integrity_checks_checksum_files_percentage_limit": "Procentinis limitas", - "integrity_checks_checksum_files_percentage_limit_description": "Nustatykite maksimalią procentinę dalį (nuo 0,01 iki 1), nurodančią, kiek kontrolinės sumos tikrinimas turėtÅŗ vykti kiekvieno intervalo metu.", - "integrity_checks_checksum_files_time_limit": "Laiko limitas", - "integrity_checks_checksum_files_time_limit_description": "KonfigÅĢruoti maksimalią trukmę, per kurią kontrolinės sumos patikra turėtÅŗ bÅĢti atlikta kiekvieną intervalą. (ms)", - "integrity_checks_missing_files": "TrÅĢkstami failai", - "integrity_checks_missing_files_description": "KonfigÅĢruoti daÅžnumą ir įjungti arba iÅĄjungti trÅĢkstamÅŗ failÅŗ patikrą", - "integrity_checks_missing_files_enable_description": "ÄŽjungti trÅĢkstamÅŗ failÅŗ patikrą", - "integrity_checks_settings": "Vientisumo patikra", - "integrity_checks_settings_description": "Tvarkyti vientisumo patikrÅŗ intervalus", - "integrity_checks_untracked_files": "Nesekami failai", - "integrity_checks_untracked_files_description": "KonfigÅĢruoti daÅžnumą ir įjungti arba iÅĄjungti nesekamÅŗ failÅŗ patikrą", - "integrity_checks_untracked_files_enable_description": "ÄŽjungti nesekamÅŗ failÅŗ patikrą", "job_concurrency": "{job} lygiagretumas", "job_created": "UÅžduotis sukurta", "job_not_concurrency_safe": "Å i uÅžduotis nėra saugi apdoroti lygiagrečiai.", @@ -198,32 +190,16 @@ "machine_learning_smart_search_enabled": "ÄŽjungti iÅĄmaniąją paieÅĄką", "machine_learning_smart_search_enabled_description": "Jei iÅĄjungta, vaizdai nebus uÅžkoduoti iÅĄmaniajai paieÅĄkai.", "machine_learning_url_description": "MaÅĄininio mokymosi serverio URL. Jei pateikta daugiau nei vienas URL, serveriai bus bandomi eilės tvarka nuo pirmo iki paskutinio tol, kol bus rastas vienas veikiantis serveris.", - "maintenance_backup_management": "AtsarginiÅŗ kopijÅŗ tvarkymas", "maintenance_delete_backup": "IÅĄtrinti atsarginę kopiją", "maintenance_delete_backup_description": "Å is failas bus negrįŞtamai iÅĄtrintas.", "maintenance_delete_error": "Nepavyko iÅĄtrinti atsarginės kopijos.", - "maintenance_integrity_check": "Tikrinti", - "maintenance_integrity_check_all": "Tikrinti Visus", - "maintenance_integrity_checksum_mismatch": "Checksum neatitikimas", - "maintenance_integrity_checksum_mismatch_description": "Failai, kuriÅŗ kontrolinė suma diske nesutampa su Immich duomenÅŗ bazėje įraÅĄyta kontroline suma.", - "maintenance_integrity_checksum_mismatch_job": "Tikrinti checksum neatitikimÅŗ", - "maintenance_integrity_checksum_mismatch_refresh_job": "Atnaujinti checksum neatitikimo ataskaitas", - "maintenance_integrity_missing_file": "TrÅĢkstami failai", - "maintenance_integrity_missing_file_description": "Failai, įtraukti į Immich duomenÅŗ bazę, tačiau neegzistuojantys failÅŗ sistemoje.", - "maintenance_integrity_missing_file_job": "Tikrinti, ar nėra trÅĢkstamÅŗ failÅŗ", - "maintenance_integrity_missing_file_refresh_job": "Atnaujinti trÅĢkstamÅŗ failÅŗ ataskaitas", - "maintenance_integrity_report": "Vientisumo Ataskaita", - "maintenance_integrity_untracked_file": "Nesekami Failai", - "maintenance_integrity_untracked_file_description": "Failai Immich kataloguose, apie kuriuos Immich neturi jokiÅŗ įraÅĄÅŗ.", - "maintenance_integrity_untracked_file_job": "Patikrinti, ar nėra nesekamÅŗ failÅŗ", - "maintenance_integrity_untracked_file_refresh_job": "Atnaujinti nesekamÅŗ failÅŗ ataskaitas", "maintenance_restore_backup": "Atstatyti atsarginę kopiją", "maintenance_restore_backup_description": "Immich bus iÅĄtrintas ir atkurtas iÅĄ pasirinktos atsarginės kopijos. PrieÅĄ tęsiant bus sukurta atsarginė kopija.", "maintenance_restore_backup_different_version": "Å i atsarginė kopija buvo sukurta su skirtinga Immich versija!", "maintenance_restore_backup_unknown_version": "Nepavyko nustatyti atsarginės kopijos versijos.", "maintenance_restore_database_backup": "Atstatyti duomenÅŗ bazę", "maintenance_restore_database_backup_description": "GrÄ…Åžinti į ankstesnę duomenÅŗ bazės bÅĢseną naudojant atsarginę kopiją", - "maintenance_settings": "PrieÅžiÅĢra", + "maintenance_settings": "Aptarnavimas", "maintenance_settings_description": "Perjungti „Immich“ į aptarnavimo reÅžimą.", "maintenance_start": "Perjungti į aptarnavimo reÅžimą", "maintenance_start_error": "Nepavyko paleisti aptarnavimo reÅžimo.", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ÄŽgalinti el. paÅĄto praneÅĄimus", "notification_settings": "PraneÅĄimÅŗ nustatymai", "notification_settings_description": "Tvarkyti praneÅĄimÅŗ nustatymus, įskaitant el. paÅĄto", - "oauth_allow_insecure_requests": "Leisti nesaugias uÅžklausas", - "oauth_allow_insecure_requests_description": "ÄŽSPĖJIMAS: Tai iÅĄjungia TLS sertifikato patvirtinimą OAuth uÅžklausoms ir gali suteikti MITM atakÅŗ galimybę.", "oauth_auto_launch": "Paleisti automatiÅĄkai", "oauth_auto_launch_description": "Prisijungimo puslapyje automatiÅĄkai pradėti OAuth prisijungimo procesą", "oauth_auto_register": "Automatinis registravimas", @@ -300,11 +274,9 @@ "oauth_button_text": "Mygtuko tekstas", "oauth_client_secret_description": "Privalomas konfidencialaus kliento, arba jei vieÅĄasis klientas nepalaiko PKCE (Proof Key for Code Exchange).", "oauth_enable_description": "Prisijungti su OAuth", - "oauth_end_session_url_description": "Nukreipkite vartotoją į ÅĄÄ¯ URI, kai jie atsijungia.", "oauth_mobile_redirect_uri": "Mobiliojo peradresavimo URI", "oauth_mobile_redirect_uri_override": "Mobiliojo peradresavimo URI pakeitimas", "oauth_mobile_redirect_uri_override_description": "ÄŽjunkite, kai OAuth teikėjas nepalaiko mobiliojo URI, tokio kaip ''{callback}''", - "oauth_prompt_description": "UÅžklausos parametras (pvz., select_account, prisijungti, sutikti)", "oauth_role_claim": "Rolės Tvirtinimas", "oauth_role_claim_description": "Suteikti admin teises automatiÅĄkai pagal ÅĄios rolės tvirtinimo buvimą. Tvirtinimas gali turėti priskirtus arba 'vartotoją' arba 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Perkraunamos visos bibliotekos", "registration": "Administratoriaus registracija", "registration_description": "Kadangi esate pirmasis ÅĄio sistemos naudotojas, jums bus priskirta administratoriaus rolė, ir bÅĢsite atsakingas uÅž administracines uÅžduotis ir papildomÅŗ naudotojÅŗ kÅĢrimą.", - "release_channel_release_candidate": "IÅĄleidimo kandidatas", - "release_channel_stable": "Stabili", "remove_failed_jobs": "PaÅĄalinti nepavykusius darbus", "require_password_change_on_login": "Reikalauti, kad naudotojas pasikeistÅŗ slaptaÅžodį po pirmojo prisijungimo", "reset_settings_to_default": "Atstatyti nustatymus į numatytuosius", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Galioja tik VAAPI ir QSV. Nustato dri mazgą aparatiniam perkodavimui.", "transcoding_preset_preset": "IÅĄ anksto nustatytas (-preset)", "transcoding_preset_preset_description": "Kompresijos greitis. Siekiant tam tikro bitrate lėtesnis apdorojimas lems maÅžesnius failÅŗ dydÅžius ir padidins kokybę. VP9 ignoruos greičius virÅĄ \"gretesnis\" lygio.", - "transcoding_realtime": "Realaus laiko transkodavimas [EKSPERIMENTINIS]", - "transcoding_realtime_description": "LeidÅžia transkoduoti realiuoju laiku, kai vaizdo įraÅĄas transliuojamas. ÄŽgalina kokybės perjungimą, tačiau, priklausomai nuo serverio galimybiÅŗ, atkÅĢrimas gali strigti ar bÅĢti uÅždelstas.", - "transcoding_realtime_enabled": "ÄŽgalinti transkodavimą realiuoju laiku", - "transcoding_realtime_enabled_description": "Jei iÅĄjungta, serveris atsisakys pradėti naujas transkodavimo sesijas realiuoju laiku.", - "transcoding_realtime_resolutions": "Rezoliucijos", - "transcoding_realtime_resolutions_description": "RaiÅĄkos, siÅĢlomos transliavimui realiuoju laiku (transkodavimui). Pasirinkus didesnę raiÅĄką, gali kilti atkÅĢrimo problemÅŗ, jei serveris nespės vaizdo įraÅĄo transkoduoti pakankamai greitai.", - "transcoding_realtime_video_codecs": "Vaizdo įraÅĄÅŗ kodekai", - "transcoding_realtime_video_codecs_description": "Vaizdo kodekai, siÅĢlomi transkodavimui realiuoju laiku. AtkÅĢrimo metu klientai (įrenginiai) pasirinks geriausią jÅŗ palaikomą variantą. AV1 yra efektyvesnis uÅž HEVC, o ÅĄis – efektyvesnis uÅž H.264. Naudodami aparatinį spartinimą, pasirinkite tik tuos kodekus, kuriuos spartintuvas sugeba koduoti. Naudodami programinį transkodavimą, atkreipkite dėmesį, kad H.264 yra spartesnis uÅž AV1, o ÅĄis – spartesnis uÅž HEVC.", "transcoding_reference_frames": "Nuorodiniai kadrai", "transcoding_reference_frames_description": "KadrÅŗ, į kuriuos reikia remtis suspaudÅžiant duotą kadrą, skaičius. AukÅĄtesnė reikÅĄmė pagerina suspaudimo efektyvumą, bet sulėtina uÅžkodavimą. 0 - nustato reikÅĄmę automatiÅĄkai.", "transcoding_required_description": "Tik nepalaikomo formato vaizdo įraÅĄai", @@ -478,8 +440,6 @@ "user_settings_description": "Valdyti naudotojo nustatymus", "user_successfully_removed": "Naudotojas {email} sėkmingai paÅĄalintas.", "users_page_description": "AdministratoriÅŗ vartotojÅŗ puslapis", - "version_check_channel": "IÅĄleidimo kanalas", - "version_check_channel_description": "Pasirinkite leidimo kanalą, kurio versijÅŗ praneÅĄimus norite gauti", "version_check_enabled_description": "ÄŽgalinti versijÅŗ tikrinimą", "version_check_implications": "VersijÅŗ tikrinimas reikalauja periodiÅĄkos komunikacijos su {server}", "version_check_settings": "Versijos tikrinimas", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "IÅĄvalyti vaizdo talpyklą", "advanced_settings_clear_image_cache_error": "Nepavyko iÅĄvalyti vaizdo taupyklos", "advanced_settings_clear_image_cache_success": "Sėkmingai iÅĄvalyta {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Naudokite ÅĄÄ¯ nustatymą medijos filtravimui sinchronizuojant remiantis alternatyviais kriterijais. Naudokite tik jei programa turi problemÅŗ su visÅŗ albumÅŗ aptikimu.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTINIS] Naudokite alternatyvÅŗ įrenginio albumÅŗ sinchronizavimo filtrą", "advanced_settings_log_level_title": "ÅŊurnalo įraÅĄÅŗ lygis: {level}", "advanced_settings_prefer_remote_subtitle": "Kai kurie įrenginiai labai lėtai įkelia miniatiÅĢras iÅĄ vietiniÅŗ elementÅŗ. Aktyvuokite ÅĄÄ¯ nustatymą, kad vietoje to uÅžkrautumėte nuotolines nuotraukas.", "advanced_settings_prefer_remote_title": "Teikti pirmenybę nuotolinėms nuotraukoms", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Custom proxy headeriai [Experimentinis]", "advanced_settings_readonly_mode_subtitle": "ÄŽgalina tik skaitymo reÅžimą kai nuotraukas galima tik ÅžiÅĢrėti, draudÅžiama paÅžymėti kelias, dalintis, transliuoti ar iÅĄtrinti. ÄŽgalinkit/uÅždrauskit tik skaitymą per naudotojo avatar'ą iÅĄ pagrindinio lango", "advanced_settings_readonly_mode_title": "Tik skaitymo rÄ—Åžimas", + "advanced_settings_self_signed_ssl_subtitle": "PraleidÅžia SSL sertifikato tikrinimą serverio galutiniam taÅĄkui. Privaloma pačiÅŗ pasiraÅĄytiems sertifikatams.", + "advanced_settings_self_signed_ssl_title": "Leisti self-signed SSL sertifikatus [Experimentinis]", "advanced_settings_sync_remote_deletions_subtitle": "AutomatiÅĄkai iÅĄtrinti ar atkurti elementus įrenginyje, kai tie veiksmai atliekami narÅĄyklėje", "advanced_settings_sync_remote_deletions_title": "Sinchronizuoti nuotolinius iÅĄtrynimus [EKSPERIMENTINIS]", "advanced_settings_tile_subtitle": "PaÅžangesni naudotojÅŗ nustatymai", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ar tikrai norite iÅĄtrinti albumą {album}?", "album_delete_confirmation_description": "Jei ÅĄiuo albumu dalijamasi, tai kiti naudotojai jo nebegalės pasiekti.", "album_deleted": "Albumas iÅĄtrintas", + "album_info_card_backup_album_excluded": "neįtrauktas", + "album_info_card_backup_album_included": "įtrauktas", "album_info_updated": "Albumo informacija atnaujinta", + "album_leave": "Palikti albumą?", + "album_leave_confirmation": "Ar tikrai norite palikti albumą {album}?", "album_name": "Albumo pavadinimas", "album_options": "Albumo parinktys", "album_remove_user": "PaÅĄalinti naudotoją?", "album_remove_user_confirmation": "Ar tikrai norite paÅĄalinti naudotoją {user}?", "album_search_not_found": "Pagal jÅĢsÅŗ paieÅĄką albumÅŗ nerasta", + "album_selected": "Albumas pasirinktas", "album_share_no_users": "Atrodo, kad bendrinate ÅĄÄ¯ albumą su visais naudotojais, arba neturite naudotojÅŗ, su kuriais galėtumėte bendrinti.", "album_summary": "Albumo santrauka", "album_updated": "Albumas atnaujintas", "album_updated_setting_description": "Gauti praneÅĄimą el. paÅĄtu, kai bendrinamas albumas turi naujÅŗ elementÅŗ", "album_upload_assets": "ÄŽkelkite elementus iÅĄ savo kompiuterio ir pridėkite prie albumo", + "album_user_left": "Paliko {album}", + "album_user_removed": "PaÅĄalintas {user}", + "album_viewer_appbar_delete_confirm": "Ar tikrai norite iÅĄtrinti ÅĄÄ¯ albumą iÅĄ savo paskyros?", "album_viewer_appbar_share_err_delete": "Nepavyko iÅĄtrinti albumo", + "album_viewer_appbar_share_err_leave": "Nepavyko iÅĄeiti iÅĄ albumo", + "album_viewer_appbar_share_err_remove": "Kilo problemÅŗ paÅĄalinant elementus iÅĄ albumo", + "album_viewer_appbar_share_err_title": "Nepavyko pakeisti albumo pavadinimą", + "album_viewer_appbar_share_leave": "Palikti albumą", + "album_viewer_appbar_share_to": "Dalintis su", "album_viewer_page_share_add_users": "Pridėti naudotojÅŗ", "album_with_link_access": "Tegul visi, turintys nuorodą, mato ÅĄio albumo nuotraukas ir Åžmones.", "albums": "Albumai", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Pradinė elementÅŗ rÅĢÅĄiavimo tvarka kai kuriamas naujas albumas.", "albums_feature_description": "ElementÅŗ rinkinys kuriuo galima dalintis su kitais naudotojais.", "albums_on_device_count": "AlbumÅŗ įrenginyje ({count})", + "albums_selected": "{count, plural, one {# pasirinktas albumas} other {# pasirinkti albumai}}", "all": "Visi", "all_albums": "Visi albumai", "all_people": "Visi Åžmonės", "all_photos": "Visos nuotraukos", "all_videos": "Visi video", "allow_dark_mode": "Leisti tamsÅŗjį reÅžimą", + "allow_edits": "Leisti redagavimus", "allow_public_user_to_download": "Leisti vieÅĄam naudotojui atsisiÅŗsti", "allow_public_user_to_upload": "Leisti vieÅĄam naudotojui įkelti", "allowed": "LeidÅžiama", @@ -549,12 +528,14 @@ "always_keep": "Visada laikyti", "always_keep_photos_hint": "Atlaisvinti Vietos iÅĄsaugos visas foto ÅĄiame įrenginyje.", "always_keep_videos_hint": "Atlaisvinti Vietos iÅĄsaugos visas video ÅĄiame įrenginyje.", + "anti_clockwise": "PrieÅĄ laikrodÅžio rodyklę", "api_key": "API raktas", "api_key_description": "Å i reikÅĄmė bus parodyta tik vieną kartą. PraÅĄome nusikopijuoti prieÅĄ uÅždarant ÅĄÄ¯ langą.", "api_key_empty": "JÅĢsÅŗ API rakto pavadinimas netÅĢrėtÅŗ bÅĢti tuÅĄÄias", "api_keys": "API raktai", "app_architecture_variant": "Variantas (architektÅĢra)", "app_bar_signout_dialog_content": "Ar tikrai norite atsijungti?", + "app_bar_signout_dialog_ok": "Taip", "app_bar_signout_dialog_title": "Atsijungti", "app_download_links": "Programėlės atsisiuntimo nuorodos", "app_settings": "Programos nustatymai", @@ -565,19 +546,27 @@ "archive": "Archyvas", "archive_action_prompt": "{count} pridėta į archyvą", "archive_or_unarchive_photo": "Archyvuoti arba iÅĄarchyvuoti nuotrauką", + "archive_page_no_archived_assets": "Nerasta jokiÅŗ archyvuotÅŗ elementÅŗ", + "archive_page_title": "Archyve ({count})", "archive_size": "Archyvo dydis", "archive_size_description": "KonfigÅĢruoti archyvo dydį atsisiuntimams (GiB)", "archived": "Archyvuota", "archived_count": "{count, plural, other {# suarchyvuota}}", "are_these_the_same_person": "Ar tai tas pats asmuo?", "are_you_sure_to_do_this": "Ar tikrai norite tai daryti?", + "array_field_not_fully_supported": "MasyvÅŗ laukams reikia rankinio JSON redagavimo", + "asset_action_delete_err_read_only": "Negalima iÅĄtrinti tik skaitom(o, Åŗ) element(o, Åŗ), praleidÅžiama", + "asset_action_share_err_offline": "Negalima uÅžkrauti neprisijungusiÅŗ elementÅŗ, praleidÅžiama", "asset_added_to_album": "Pridėta į albumą", "asset_adding_to_album": "Pridedama į albumąâ€Ļ", "asset_created": "Elementas sukurtas", - "asset_day_count": "{date}: {count, plural, one {# elementas} few {# elementai} other {# elementÅŗ}}", "asset_description_updated": "Elemento apraÅĄymas buvo atnaujintas", + "asset_filename_is_offline": "Elementas {filename} nepasiekiamas", + "asset_has_unassigned_faces": "Elementas turi nepriskirtÅŗ veidÅŗ", "asset_hashing": "Kuriami bylÅŗ paraÅĄaiâ€Ļ", "asset_list_group_by_sub_title": "Grupuoti pagal", + "asset_list_layout_settings_dynamic_layout_title": "Dinaminis iÅĄdėstymas", + "asset_list_layout_settings_group_automatically": "AutomatiÅĄkai", "asset_list_layout_settings_group_by": "Grupuoti elementus pagal", "asset_list_layout_settings_group_by_month_day": "Mėnesis + diena", "asset_list_layout_sub_title": "IÅĄdėstymas", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Elementas nerastas iCloud. Elementas gali bÅĢti nepasiekiamas dėl blogo failo, saugomo iCloud", "asset_offline": "Elementas nepasiekiamas", "asset_offline_description": "Å is iÅĄorinis elementas neberandamas diske. Dėl pagalbos susisiekite su savo Immich administratoriumi.", + "asset_restored_successfully": "Elementas atkurtas sėkmingai", "asset_skipped": "Praleista", "asset_skipped_in_trash": "Å iukÅĄliadėŞėje", + "asset_trashed": "Elementai iÅĄtrinti", "asset_troubleshoot": "ElementÅŗ trikčiÅŗ ÅĄalinimas", "asset_uploaded": "ÄŽkelta", "asset_uploading": "ÄŽkeliamaâ€Ļ", "asset_viewer_settings_subtitle": "Tvarkykite savo galerijos perÅžiÅĢros nustatymus", "asset_viewer_settings_title": "ElementÅŗ perÅžiÅĢra", "assets": "Elementai", + "assets_added_count": "{count, plural, one {Pridėtas # elementas} few {Pridėti # elementai} other {Pridėta # elementÅŗ}}", "assets_added_to_album_count": "ÄŽ albumą {count, plural, one {įtrauktas # elementas} few {įtraukti # elementai} other {įtraukta # elementÅŗ}}", - "assets_added_to_album_partial_count": "ÄŽ albumą pridėta {successCount} iÅĄ {totalCount} {totalCount, plural, one {elemento} few {elementÅŗ} other {elementÅŗ}}", "assets_added_to_albums_count": "Pridėta {assetTotal, plural, one {# elementas} few {# elementai} other {# elementÅŗ}} į {albumTotal, plural, one {# albumą} few {# albumus} other {# albumÅŗ}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Elementas negali bÅĢti pridėtas} few {Elementai negali bÅĢti pridėti} other {ElementÅŗ negali bÅĢti pridėta}} į albumą", "assets_cannot_be_added_to_albums": "{count, plural, one {Elementas negali bÅĢti pridėtas} few {Elementai negali bÅĢti pridėti} other {ElementÅŗ negali bÅĢti pridėta}} į nei vieną albumą", "assets_count": "{count, plural, one {# elementas} few {# elementai} other {# elementÅŗ}}", + "assets_deleted_permanently": "{count} {count, plural, one {elementas iÅĄtrintas} few {elementai iÅĄtrinti} other {elementÅŗ iÅĄtrinta}} galutinai", + "assets_deleted_permanently_from_server": "{count} {count, plural, one {elementas iÅĄtrintas} few {elementai iÅĄtrinti} other {elementÅŗ iÅĄtrinta}} galutinai iÅĄ Immich serverio", + "assets_downloaded_failed": "{count, plural, one {AtsisiÅŗstas # failas - {error} failas nepavyko} few {AtsisiÅŗsti # failai - {error} failai nepavyko} other {AtsisiÅŗsta # failÅŗ - {error} failÅŗ nepavyko}}", + "assets_downloaded_successfully": "{count, plural, one {AtsisiÅŗstas # failas sėkmingai} few {AtsisiÅŗsti # failai sėkmingai} other {AtsisiÅŗsta # failÅŗ sėkmingai}}", "assets_moved_to_trash_count": "{count, plural, one {# elementas perkeltas} few {# elementai perkelti} other {# elementÅŗ perkelta}} į ÅĄiukÅĄliadėŞę", "assets_permanently_deleted_count": "{count, plural, one {# elementas iÅĄtrintas} few {# elementai iÅĄtrinti} other {# elementÅŗ iÅĄtrinta}} visam laikui", "assets_removed_count": "{count, plural, one {PaÅĄalintas # elementas} few {PaÅĄalinti # elementai} other {PaÅĄalinta # elementÅŗ}}", + "assets_removed_permanently_from_device": "{count} {count, plural, one {elementas paÅĄalintas} few {elementai paÅĄalinti} other {elementÅŗ paÅĄalinta}} galutinai iÅĄ jÅĢsÅŗ įrenginio", "assets_restore_confirmation": "Ar tikrai norite atkurti visus ÅĄiukÅĄliadėŞėje esančius perkeltus elementus? Å io veiksmo atÅĄaukti negalėsite! Pastaba: nepasiekiami elementai tokiu bÅĢdu atkurti nebus.", "assets_restored_count": "{count, plural, one {Atkurtas # elementas} few {Atkurti # elementai} other {Atkurta # elementÅŗ}}", + "assets_restored_successfully": "{count} {count, plural, one {elementas atkurtas} few {elementai atkurti} other {elementÅŗ atkurta}} sėkmingai", "assets_trashed": "{count} {count, plural, one {elementas perkeltas} few {elementai perkelti} other {elementÅŗ perkelta}} į ÅĄiukÅĄliadėŞę", "assets_trashed_count": "Perkelta į ÅĄiukÅĄliadėŞę {count, plural, one {# elementas} few {# elementai} other {# elementÅŗ}}", + "assets_trashed_from_server": "{count} {count, plural, one {elementas perkeltas} few {elementai perkelti} other {elementÅŗ perkelta}} į ÅĄiukÅĄliadėŞę iÅĄ Immich serverio", "assets_were_part_of_album_count": "{count, plural, one {# elementas} few {# elementai} other {# elementÅŗ}} jau prieÅĄ tai buvo albume", "assets_were_part_of_albums_count": "{count, plural, one {Elementas } few {Elementai} other {ElementÅŗ}} jau buvo albumuose", "authorized_devices": "Autorizuoti įrenginiai", @@ -617,48 +615,87 @@ "autoplay_slideshow": "AutomatiÅĄkai rodyti skaidriÅŗ demonstraciją", "back": "Atgal", "back_close_deselect": "Atgal, uÅždaryti arba atÅžymėti", + "background_backup_running_error": "Vyksta foninis atsarginis kopijavimas, negalima pradėti rankinio kopijavimo", "background_location_permission": "Foninis vietovės leidimas", "background_location_permission_content": "Veikiant fone tinklo perjungimui Immich privalo *visada* turėti prieigą prie tikslios vietovės, kad programa galėtÅŗ perskaityti Wi-Fi tinklo pavadinimą", "background_options": "Fono nuostatos", "backup": "Atsarginė kopija", + "backup_album_selection_page_albums_device": "AlbumÅŗ įrenginyje ({count})", "backup_album_selection_page_albums_tap": "Palieskite įtraukti, du kart palieskite neįtraukti", "backup_album_selection_page_assets_scatter": "Elementai gali iÅĄsibarstyti per kelis albumus. Todėl albumai gali bÅĢti įtraukti arba neįtraukti per atsarginio kopijavimo procesą.", "backup_album_selection_page_select_albums": "PaÅžymėti albumai", "backup_album_selection_page_selection_info": "PaÅžymėjimo informacija", + "backup_album_selection_page_total_assets": "Viso unikaliÅŗ elementÅŗ", "backup_albums_sync": "AtsarginiÅŗ kopijÅŗ AlbumÅŗ Sinchronizacija", + "backup_all": "Visi", + "backup_background_service_backup_failed_message": "Nepavyko sukurti atsarginiÅŗ kopijÅŗ. Bandoma dar kartąâ€Ļ", "backup_background_service_complete_notification": "ElementÅŗ atsarginės kopijos kÅĢrimas baigtas", + "backup_background_service_connection_failed_message": "Nepavyko prisijungti prie serverio. Bandoma dar kartąâ€Ļ", + "backup_background_service_current_upload_notification": "ÄŽkeliamas {filename}", "backup_background_service_default_notification": "IeÅĄkoma naujÅŗ elementÅŗâ€Ļ", + "backup_background_service_error_title": "Atsarginio kopijavimo klaida", "backup_background_service_in_progress_notification": "Kuriama elementÅŗ atsarginė kopijaâ€Ļ", + "backup_background_service_upload_failure_notification": "Nepavyko įkelti {filename}", "backup_controller_page_albums": "Atsarginės kopijos albumai", + "backup_controller_page_background_app_refresh_disabled_content": "Norėdami naudoti foninį atsarginį kopijavimą, įjunkite foninį programÅŗ atnaujinimą meniu „Nustatymai“ > „Bendrieji“ > „Foninis programÅŗ atnaujinimas“.", + "backup_controller_page_background_app_refresh_disabled_title": "Foninis programos atnaujinimas iÅĄjungtas", + "backup_controller_page_background_app_refresh_enable_button_text": "Eiti į nustatymus", "backup_controller_page_background_battery_info_link": "Parodyk man kaip", "backup_controller_page_background_battery_info_message": "Norint geriausiÅŗ foninio atsarginio kopijavimo rezultatÅŗ, praÅĄome iÅĄjungti akumuliatoriaus optimizavimą ribojantį foninį Immich veikimą.\n\nKadangi tai priklauso nuo įrenginio, praÅĄome susirasti reikiamą informaciją pas įrenginio gamintoją.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Akumuliatoriaus optimizavimai", + "backup_controller_page_background_charging": "Tik kol kraunasi", + "backup_controller_page_background_configure_error": "Nepavyko sukonfigÅĢruoti foniniÅŗ paslaugÅŗ", "backup_controller_page_background_delay": "Atidėti naujÅŗ elementÅŗ atsarginį kopijavimą: {duration}", + "backup_controller_page_background_description": "ÄŽjunkite fonines paslaugas, kad galėtumėte automatiÅĄkai kurti atsargines kopijas neatidarant programos", + "backup_controller_page_background_is_off": "Automatinis atsarginis kopijavimas yra iÅĄjungtas", + "backup_controller_page_background_is_on": "Automatinis atsarginis kopijavimas yra įjungtas", + "backup_controller_page_background_turn_off": "IÅĄjungti fonines paslaugas", + "backup_controller_page_background_turn_on": "ÄŽjungti fonines paslaugas", + "backup_controller_page_background_wifi": "Tik su Wi-Fi", "backup_controller_page_backup": "Atsarginis kopijavimas", "backup_controller_page_backup_selected": "Pasirinkta: ", "backup_controller_page_backup_sub": "Perkeltos nuotraukos ir vaizdo įraÅĄai", + "backup_controller_page_created": "Sukurta: {date}", + "backup_controller_page_desc_backup": "ÄŽjunkite foninį atsarginį kopijavimą, kad bÅĢtÅŗ automatiÅĄkai perkeliami nauji elementai į serverį kai atidaroma programa.", "backup_controller_page_excluded": "Neįtraukta: ", + "backup_controller_page_failed": "Nepavyko ({count})", + "backup_controller_page_filename": "Failo pavadinimas: {filename}[{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Atsarginio kopijavimo informacija", "backup_controller_page_none_selected": "Niekas nepasirinkta", "backup_controller_page_remainder": "Dar liko", "backup_controller_page_remainder_sub": "Likusios pasirinktos atsarginio kopijavimo nuotraukos ir vaizdo įraÅĄai", "backup_controller_page_server_storage": "Serverio saugykla", + "backup_controller_page_start_backup": "Pradėti atsarginį kopijavimą", + "backup_controller_page_status_off": "Automatinis foninis atsarginis kopijavimas yra iÅĄjungtas", + "backup_controller_page_status_on": "Automatinis foninis atsarginis kopijavimas yra įjungtas", "backup_controller_page_storage_format": "{used} iÅĄ {total} panaudota", "backup_controller_page_to_backup": "Albumai kuriÅŗ atsarginis kopijavimas bus atliktas", "backup_controller_page_total_sub": "Visos unikalios nuotraukos ir video įraÅĄai iÅĄ paÅžymėtÅŗ albumÅŗ", + "backup_controller_page_turn_off": "IÅĄjungti foninį atsarginį kopijavimą", + "backup_controller_page_turn_on": "ÄŽjungti foninį atsarginį kopijavimą", + "backup_controller_page_uploading_file_info": "ÄŽkeliama failo info", + "backup_err_only_album": "Negalima paÅĄalinti vienintelio albumo", "backup_error_sync_failed": "Sinchronizavimas nepavyko. Atsarginė kopija negali bÅĢti apdorota.", "backup_info_card_assets": "elementai", + "backup_manual_cancelled": "AtÅĄaukta", + "backup_manual_in_progress": "Jau įkeliama, bandykite dar kartą vėliau", + "backup_manual_success": "Pavyko", + "backup_manual_title": "ÄŽkėlimo bÅĢklė", "backup_options": "Atsarginio kopijavimo nustatymai", + "backup_options_page_title": "Atsarginio kopijavimo nustatymai", + "backup_setting_subtitle": "Tvarkyti foninio ir priekinio plano įkėlimo nustatymus", "backup_settings_subtitle": "Tvarkyti įkėlimo nustatymus", + "backup_upload_details_page_more_details": "Bakstelėkite detalesnei informacijai", "backward": "Atgalinis", - "battery_optimization_backup_reliability": "IÅĄjungus akumuliatoriaus optimizavimą, galima pagerinti foninio atsarginio kopijavimo patikimumą", "biometric_auth_enabled": "Biometrinis autentifikavimas įgalintas", "biometric_locked_out": "JÅĢs esate uÅžblokuotas biometrinio autentifikavimo funkcijai", "biometric_no_options": "Nėra galimÅŗ biometriniÅŗ nustatymÅŗ", "biometric_not_available": "Biometrinis autentifikavimas ÅĄiame įrenginyje negalimas", + "birthdate_saved": "Sėkmingai iÅĄsaugota gimimo data", "birthdate_set_description": "Gimimo data naudojama apskaičiuoti asmens amÅžiÅŗ nuotraukos darymo metu.", "blurred_background": "NeryÅĄkus fonas", - "browse_templates": "NarÅĄyti ÅĄablonus", "bugs_and_feature_requests": "KlaidÅŗ ir funkcijÅŗ uÅžklausos", "build": "Versija", "build_image": "Programos Paketas", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Ar tikrai norite palikti visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančiÅŗ elementÅŗ}}? Tokiu bÅĢdu nieko netrinant bus sutvarkytos visos dublikatÅŗ grupės.", "bulk_trash_duplicates_confirmation": "Ar tikrai norite perkelti į ÅĄiukÅĄliadėŞę visus {count, plural, one {# besidubliuojantį elementą} few {# besidubliuojančius elementus} other {# besidubliuojančiÅŗ elementÅŗ}}? Bus paliktas didÅžiausias kiekvienos grupės elementas ir į ÅĄiukÅĄliadėŞę perkelti kiti besidubliuojantys elementai.", "buy": "ÄŽsigyti Immich", + "cache_settings_clear_cache_button": "IÅĄvalyti laikiną talpyklą", + "cache_settings_clear_cache_button_title": "IÅĄvalo programos laikiną talpyklą. Tai gali smarkiai paveikti programos greitį, kol bus sukurta nauja laikinoji talpykla.", + "cache_settings_duplicated_assets_clear_button": "IÅ VALYTI", + "cache_settings_duplicated_assets_subtitle": "Nuotraukos ir video įraÅĄai kurie yra programos ignoruojamÅŗ sąraÅĄe", + "cache_settings_duplicated_assets_title": "Sudubliuoti elementai ({count})", + "cache_settings_statistics_album": "Bibliotekos miniatiÅĢros", + "cache_settings_statistics_full": "Pilno dydÅžio nuotraukos", + "cache_settings_statistics_shared": "BendrinamÅŗ albumÅŗ miniatiÅĢros", + "cache_settings_statistics_thumbnail": "MiniatiÅĢros", + "cache_settings_statistics_title": "Laikinos talpyklos naudojimas", + "cache_settings_subtitle": "Valdykite Immich mobiliosios programos laikinosios talpyklos elgesį", + "cache_settings_tile_subtitle": "Valdykite vietinės talpyklos elgesį", + "cache_settings_tile_title": "Vietinė talpykla", + "cache_settings_title": "Laikinosios talpyklos nustatymai", "camera": "Fotoaparatas", "camera_brand": "Fotoaparato prekės Åženklas", "camera_model": "Fotoaparato modelis", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Negalima atnaujinti apraÅĄymo", "cast": "Transliuoti", "cast_description": "Valdyti galimas transliavimo kryptis", - "change": "Keisti", "change_date": "Pakeisti datą", "change_description": "Pakeisti apraÅĄymus", "change_display_order": "Pakeisti atvaizdavimo tvarką", + "change_expiration_time": "Pakeisti galiojimo trukmę", "change_location": "Pakeisti vietovę", "change_name": "Pakeisti vardą", "change_name_successfully": "Vardas pakeistas sėkmingai", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "SlaptaÅžodÅžiai nesutampa", "change_password_form_reenter_new_password": "Pakartotinai įveskite naują slaptaÅžodį", "change_pin_code": "Pakeisti PIN kodą", + "change_trigger": "Pakeisti vykdymo sąlygą", + "change_trigger_prompt": "Ar tikrai norite vykdymo sąlygą? Tai paÅĄalins visas esamas veiksmÅŗ sekas ir filtrus.", "change_your_password": "Pakeisti slaptaÅžodį", "changed_visibility_successfully": "Matomumas pakeistas sėkmingai", "charging": "Kraunasi", "charging_requirement_mobile_backup": "Foninis kopijavimas reikalauja, kad įrenginys bÅĢtÅŗ prijungtas pakrovimui", + "check_corrupt_asset_backup": "Patikrinti sugadintÅŗ elementÅŗ atsarginę kopiją", + "check_corrupt_asset_backup_button": "Atlikti patikrinimą", + "check_corrupt_asset_backup_description": "Paleiskite ÅĄÄ¯ patikrinimą tik per Wi-Fi ir tik kai visi elementai buvo perkopijuoti. Å i procedÅĢra uÅžtruks kelias minutes.", "check_logs": "Tikrinti Åžurnalus", "checksum": "„Checksum“", - "choose": "IÅĄsirinkti", "choose_matching_people_to_merge": "Pasirinkite atitinkančius Åžmones sujungimui", "city": "Miestas", "cleanup_confirm_description": "Immich rado {count} {count, plural, one {elementą (sukurtą iki {date}), kurio atsarginė kopija jau iÅĄsaugota serveryje. PaÅĄalinti vietinę kopiją iÅĄ ÅĄio įrenginio} few {elementai (sukurti iki {date}), kuriÅŗ atsarginės kopijos jau iÅĄsaugotos serveryje. PaÅĄalinti vietines kopijas iÅĄ ÅĄio įrenginio} other {elementÅŗ (sukurtÅŗ iki {date}), kuriÅŗ atsarginės kopijos jau iÅĄsaugotos serveryje. PaÅĄalinti vietines kopijas iÅĄ ÅĄio įrenginio}}?", @@ -719,10 +774,11 @@ "clear": "IÅĄvalyti", "clear_all": "IÅĄvalyti viską", "clear_all_recent_searches": "IÅĄvalyti visas naujausias paieÅĄkas", - "clear_failed_count": "IÅĄvalyti nepavykusius ({count})", "clear_file_cache": "IÅĄvalyti failÅŗ laikiną talpyklą", "clear_message": "IÅĄvalyti praneÅĄimą", "clear_value": "IÅĄvalyti reikÅĄmę", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "ÄŽveskite slaptaÅžodį", "client_cert_import": "Importuoti", "client_cert_import_success_msg": "Kliento sertifikatas yra importuotas", "client_cert_invalid_msg": "Netinkamas sertifikato failas arba neteisingas slaptaÅžodis", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Kliento sertifikatas yra paÅĄalintas", "client_cert_subtitle": "Palaikomi tik PKCS12 (.p12, .pfx) formatai. Sertifikato importavimas/ paÅĄalinimas galimas tik prieÅĄ prisijungimą", "client_cert_title": "SSL kliento sertifikatas [Experimentinis]", + "clockwise": "Pagal laikrodÅžio rodykles", "close": "UÅždaryti", "collapse": "Suskleisti", "collapse_all": "Suskleisti viską", "color": "Spalva", + "color_theme": "Temos spalva", "command": "Komanda", "command_palette_prompt": "Greitai raskite puslapius, veiksmus ar komandas", "command_palette_to_close": "uÅždaryti", @@ -747,7 +805,6 @@ "comments_are_disabled": "Komentarai yra iÅĄjungti", "common_create_new_album": "Sukurti naują albumą", "completed": "UÅžbaigta", - "configuration": "KonfigÅĢracija", "confirm": "Patvirtinti", "confirm_admin_password": "Patvirtinti administratoriaus slaptaÅžodį", "confirm_delete_face": "Ar tikrai norite iÅĄtrinti {name} veidą iÅĄ elementÅŗ?", @@ -762,16 +819,19 @@ "contain": "Tilpti", "context": "Kontekstas", "continue": "Tęsti", - "control_bottom_app_bar_add_tags": "Pridėti ÅŊymas", + "control_bottom_app_bar_create_new_album": "Sukurti naują albumą", + "control_bottom_app_bar_delete_from_immich": "IÅĄtrinti iÅĄ Immich", "control_bottom_app_bar_delete_from_local": "IÅĄtrinti iÅĄ įrenginio", "control_bottom_app_bar_edit_location": "Redaguoti vietovę", "control_bottom_app_bar_edit_time": "Redaguoti datą ir laiką", + "control_bottom_app_bar_share_link": "Dalintis nuoroda", + "control_bottom_app_bar_share_to": "Dalintis su", "control_bottom_app_bar_trash_from_immich": "Perkelti į ÅĄiukÅĄliadėŞę", "copied_image_to_clipboard": "Nuotrauka nukopijuota į iÅĄkarpinę.", "copied_to_clipboard": "Nukopijuota į iÅĄkapinę!", "copy_error": "Kopijavimo klaida", + "copy_file_path": "Kopijuoti failo kelią", "copy_image": "Kopijuoti vaizdą", - "copy_json": "Kopijuoti JSON", "copy_link": "Kopijuoti nuorodą", "copy_link_to_clipboard": "Kopijuoti nuorodą į iÅĄkarpinę", "copy_password": "Kopijuoti slaptaÅžodį", @@ -789,6 +849,7 @@ "create_link_to_share": "Sukurti bendrinimo nuorodą", "create_link_to_share_description": "Leisti bet kam su nuoroda matyti paÅžymėtą(-as) nuotrauką(-as)", "create_new": "SUKURTI NAUJĄ", + "create_new_face": "Sukurti naują veidą", "create_new_person": "Sukurti naują ÅžmogÅŗ", "create_new_person_hint": "Priskirti pasirinktus elementus naujam Åžmogui", "create_new_user": "Sukurti naują varotoją", @@ -805,33 +866,37 @@ "created_at": "Sukurta", "creating_linked_albums": "Kuriami susieti albumai...", "crop": "Apkirpti", + "crop_aspect_ratio_fixed": "UÅžfiksuota", "crop_aspect_ratio_free": "Nefiksuota", "crop_aspect_ratio_original": "Originalus", "crop_aspect_ratio_square": "Kvadratas", + "curated_object_page_title": "Daiktai", "current_device": "Dabartinis įrenginys", "current_pin_code": "Dabartinis PIN kodas", "current_server_address": "Dabartinis serverio adresas", "custom_date": "Pasirinktinė data", "custom_locale": "Pasirinktinė vietovė", "custom_locale_description": "Formatuoti datas, laiką ir skaičius pagal pasirinktą kalbą ir regioną", + "custom_url": "Pasirinktinis URL", "cutoff_date_description": "IÅĄsaugoti nuotraukas iÅĄ paskutinėsâ€Ļ", "cutoff_day": "{count, plural, one {diena} other {dienos}}", "cutoff_year": "{count, plural, one {metai} other {metai}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Tamsi", "dark_theme": "Perjungti į tamsią temą", "date": "Data", "date_after": "Data po", "date_and_time": "Data ir laikas", "date_before": "Data prieÅĄ", - "date_of_birth": "Gimimo data", + "date_format": "E, LLL d, y â€ĸ h:mm", "date_of_birth_saved": "Gimimo data sėkmingai iÅĄsaugota", "date_range": "DatÅŗ intervalas", - "date_time_original": "Pradiniai Data/Laikas", "day": "Diena", "days": "DienÅŗ", "deduplicate_all": "Å alinti visus dublikatus", - "default_quality_subtitle": "Kokybė, naudojama paliečiant bendrinimą. Kiekvieną kartą pasirinksite paspaudę ir ilgai spausdami bendrinimo mygtuką.", - "default_share_quality": "Numatytoji bendrinimo kokybė", + "default_locale": "Numatytoji Vietovė", + "default_locale_description": "Formatuoti datas ir skaičius pagal savo narÅĄyklės lokalę", "delete": "IÅĄtrinti", "delete_action_confirmation_message": "Ar tikrai norite iÅĄtrinti ÅĄÄ¯ elementą? Å is veiksmas perkels elementą į serverio ÅĄiukÅĄliadėŞę ir paklaus ar norite iÅĄtrinti vietiniame įrenginyje", "delete_action_prompt": "{count} iÅĄtrinta", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Å ie elementai bus galutinai iÅĄtrinti iÅĄ Immich ir iÅĄ jÅĢsÅŗ įrenginio", "delete_dialog_alert_local": "Å ie elementai bus galutinai paÅĄalinti iÅĄ jÅĢsÅŗ įrenginio, bet bus prieinami Immich serveryje", "delete_dialog_alert_local_non_backed_up": "Kai kurie elementai be Immich atsarginės kopijos ir bus galutinai paÅĄalinti iÅĄ jÅĢsÅŗ įrenginio", + "delete_dialog_alert_remote": "Å ie elementai bus galutinai iÅĄtrinti iÅĄ Immich serverio", + "delete_dialog_ok_force": "Vis tiek iÅĄtrinti", "delete_dialog_title": "IÅĄtrinti galutinai", "delete_duplicates_confirmation": "Ar tikrai norite visam laikui iÅĄtrinti ÅĄiuos dublikatus?", "delete_face": "IÅĄtrinti veidą", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Ar tikrai norite iÅĄtrinti Åžymą {tagName}?", "delete_user": "IÅĄtrinti naudotoją", "deleted_shared_link": "Bendrinimo nuoroda iÅĄtrinta", + "deletes_missing_assets": "IÅĄtrinti diske trÅĢkstamus elementus", "description": "ApraÅĄymas", + "description_input_hint_text": "Pridėti apraÅĄymą...", + "description_input_submit_error": "Klaida atnaujinant apraÅĄymą, pasitikrinkite Åžurnalą norint detalesnės informacijos", "deselect_all": "AtÅžymėti visus", "details": "Detalės", "direction": "Kryptis", "disable": "IÅĄjungti", "disabled": "IÅĄjungta", + "disallow_edits": "Neleisti redaguoti", "discord": "Discord", "discover": "Atrasti", "discovered_devices": "Aptikti įrenginiai", "dismiss_all_errors": "Nepaisyti visÅŗ klaidÅŗ", + "dismiss_error": "Nepaisyti klaidos", "display_options": "Atvaizdavimo parinktys", "display_order": "Atvaizdavimo tvarka", "display_original_photos": "Rodyti originalias nuotraukas", @@ -876,9 +948,11 @@ "documentation": "Dokumentacija", "done": "Atlikta", "download": "AtsisiÅŗsti", + "download_action_prompt": "Atsisiunčiama {count} {count, plural, one {elementas} few {elementai} other {elementÅŗ}}", "download_canceled": "Atsisiuntimas atÅĄauktas", "download_complete": "Atsisiuntimas pabaigtas", "download_enqueue": "Atsisiuntimai įtraukti į eilę", + "download_error": "Atsisiuntimo klaida", "download_failed": "Nepavyko parsisiÅŗsti", "download_finished": "Atsisiuntimas pabaigtas", "download_include_embedded_motion_videos": "ÄŽterpti vaizdo įraÅĄai", @@ -888,15 +962,15 @@ "download_paused": "Atsisiuntimas pristabdytas", "download_settings": "AtsisiÅŗsti", "download_settings_description": "Tvarkyti elementÅŗ atsisiuntimo nustatymus", + "download_started": "Atsisiuntimas pradėtas", + "download_sucess": "Atsisiuntimas pavyko", + "download_sucess_android": "Medija buvo atsiÅŗsta į DCIM/Immich", "download_waiting_to_retry": "Laukiama bandymo iÅĄ naujo", "downloading": "Siunčiama", "downloading_asset_filename": "Parsisiunčiamas resursas {filename}", "downloading_from_icloud": "AtsisiÅŗsti iÅĄ iCloud", "downloading_media": "Atsisiunčiama medija", - "drag_to_reorder": "Vilkite, kad pertvarkyti", "drop_files_to_upload": "UÅžkelkite failus bet kurioje vietoje kad įkeltumėte", - "duplicate": "Dubliuoti", - "duplicate_workflow": "Dubliuoti darbo eigą", "duplicates": "Dublikatai", "duplicates_description": "Tvarkyti kiekvieną elementÅŗ grupę nurodant elementus, kurie yra dublikatai (jei tokiÅŗ yra).", "duration": "Trukmė", @@ -908,7 +982,9 @@ "edit_date_and_time": "Redaguoti datą ir laiką", "edit_date_and_time_action_prompt": "{count} {count, plural, one {data ir laikas redaguotas} few {datos ir laikai redaguoti} other {datÅŗ ir laikÅŗ redaguota}}", "edit_date_and_time_by_offset": "Keisti datą pagal poslinkį", + "edit_date_and_time_by_offset_interval": "Naujas datos intervalas: {from} - {to}", "edit_description": "Redaguoti apraÅĄymą", + "edit_description_prompt": "PraÅĄome pasirinkti naują apraÅĄymą:", "edit_exclusion_pattern": "Redaguoti iÅĄimčiÅŗ ÅĄabloną", "edit_faces": "Redaguoti veidus", "edit_key": "Redaguoti raktą", @@ -923,6 +999,9 @@ "edit_user": "Redaguoti naudotoją", "edit_workflow": "Redaguoti darbÅŗ eigą", "editor": "Redaktorius", + "editor_close_without_save_prompt": "Pakeitimai nebus iÅĄsaugoti", + "editor_close_without_save_title": "UÅždaryti redaktoriÅŗ?", + "editor_confirm_reset_all_changes": "Ar tikrai norite atstatyti visus pakeitimus?", "editor_discard_edits_confirm": "Atmesti pakeitimus", "editor_discard_edits_prompt": "Turite neiÅĄsaugotÅŗ pakeitimÅŗ. Ar tikrai norite juos atmesti?", "editor_discard_edits_title": "Atmesti pakeitimus?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "ÄŽveskite savo PIN kodą", "enter_your_pin_code_subtitle": "ÄŽveskite savo PIN kodą, kad pasiektumėte uÅžrakintą aplanką", "error": "Klaida", + "error_change_sort_album": "Nepavyko pakeisti albumo rÅĢÅĄiavimo tvarkos", "error_delete_face": "Klaida trinant veidą iÅĄ elementÅŗ", + "error_getting_places": "Klaida gaunant vietoves", "error_loading_albums": "Klaida kraunant albumus", "error_loading_image": "Klaida įkeliant vaizdą", "error_loading_partners": "Klaida uÅžkraunant partnerius: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Nepavyko paÅĄalinti produkto rakto", "failed_to_reset_pin_code": "Nepavyko atkurti PIN kodo", "failed_to_stack_assets": "Nepavyko sugrupuoti elementÅŗ", - "failed_to_tag_assets": "Nepavyko paÅžymėti elementÅŗ", "failed_to_unstack_assets": "Nepavyko iÅĄgrupuoti elementÅŗ", "failed_to_update_notification_status": "Nepavyko atnaujinti praneÅĄimo statuso", "incorrect_email_or_password": "Neteisingas el. paÅĄto adresas arba slaptaÅžodis", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Pridėti apraÅĄymą...", "exif_bottom_sheet_description_error": "Klaida atnaujinant apraÅĄymą", + "exif_bottom_sheet_details": "DETALĖS", + "exif_bottom_sheet_location": "VIETOVĖ", "exif_bottom_sheet_no_description": "Nėra apraÅĄymo", + "exif_bottom_sheet_people": "ÅŊMONĖS", + "exif_bottom_sheet_person_add_person": "Pridėti vardą", "exit_slideshow": "IÅĄeiti iÅĄ skaidriÅŗ perÅžiÅĢros", "expand": "IÅĄplėsti", "expand_all": "IÅĄskleisti viską", + "experimental_settings_new_asset_list_subtitle": "Dirbama", + "experimental_settings_new_asset_list_title": "ÄŽgalinti eksperimentinį nuotraukÅŗ tinklelį", + "experimental_settings_subtitle": "Naudokite savo pačiÅŗ rizika!", + "experimental_settings_title": "Eksperimentinis", "expire_after": "Galiojimas baigiasi", "expired": "Nebegalioja", "expires_date": "Nebegalios uÅž {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Eksportuoti kaip JSON", "export_database": "Eksportuoti duomenÅŗ bazę", "export_database_description": "Eksportuoti SQLite duomenÅŗ bazę", - "exposure_time": "Ekspozicijos Laikas", "extension": "Plėtinys", "external": "IÅĄorinis", "external_libraries": "IÅĄorinės bibliotekos", "external_network": "IÅĄorinis tinklas", "external_network_sheet_info": "Kai neprisijungta prie pageidaujamo Wi-Fi tinklo, programa jungsis prie serverio per pirmą URL nuorodą, kurią galės pasiekti, pradedant nuo virÅĄaus į apačią", - "f_number": "F-Skaičius", "face_unassigned": "Nepriskirta", "failed": "ÄŽvyko klaida", "failed_count": "Nepavykę: {count}", "failed_to_authenticate": "Nepavyko autentifikuoti", - "failed_to_delete_file": "Nepavyko iÅĄtrinti failo", "failed_to_load_assets": "Nepavyko įkelti elementÅŗ", "failed_to_load_folder": "Nepavyko įkelti katalogą", "favorite": "Mėgstamiausias", "favorite_action_prompt": "{count} {count, plural, one {pridėtas} few {pridėti} other {pridėta}} prie mėgstamiausiÅŗ", "favorite_or_unfavorite_photo": "ÄŽtraukti prie arba paÅĄalinti iÅĄ mėgstamiausiÅŗ", "favorites": "Mėgstamiausi", + "favorites_page_no_favorites": "Nerasta mėgstamiausiÅŗ elementÅŗ", "feature_photo_updated": "Pageidaujama nuotrauka atnaujinta", "features": "Funkcijos", + "features_in_development": "KÅĢrimo funkcijos", "features_setting_description": "Valdyti aplikacijos funkcijas", "file_name_or_extension": "Failo pavadinimas arba plėtinys", "file_name_text": "Failo pavadinimas", + "file_name_with_value": "Failo pavadinimas: {file_name}", "file_size": "Failo dydis", - "filename": "Failo pavadinimas", + "filename": "Failopavadinimas", "filetype": "Failo tipas", "filter": "Filtras", + "filter_description": "TiksliniÅŗ elementÅŗ filtravimo sąlygos", "filter_people": "Filtruoti Åžmones", "filter_places": "Filtruoti vietoves", "filter_tags": "Filtruoti Åžymas", "filters": "Filtrai", + "find_them_fast": "Raskite greitai paieÅĄkoje pagal vardą", "first": "Pirmas", "fix_incorrect_match": "Pataisyti neteisingą porą", - "focal_length": "ÅŊidinio Nuotolis", "folder": "Katalogas", "folder_not_found": "Katalogas nerastas", "folders": "Aplankai", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Perkelkite atsargines nuotraukÅŗ ir vaizdo įraÅĄÅŗ kopijas į įrenginio ÅĄiukÅĄliadėŞę, kad atlaisvintumėte vietos. JÅĢsÅŗ kopijos serveryje lieka saugios.", "free_up_space_settings_subtitle": "Atlaisvinkite vietos įrenginyje", "full_path": "Pilnas kelias: {path}", - "full_path_or_folder": "Pilnas kelias ar aplankas", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Kad veiktÅŗ, ÅĄi funkcija įkelia iÅĄorinius „Google“ iÅĄteklius.", "general": "Bendri", @@ -1168,6 +1256,7 @@ "group_owner": "Grupuoti pagal savininką", "group_places_by": "Grupuoti vietoves pagal...", "group_year": "Grupuoti pagal metus", + "haptic_feedback_switch": "ÄŽjungti haptinį grįŞtamąjį ryÅĄÄ¯", "haptic_feedback_title": "Haptinis grįŞtamasis ryÅĄys", "has_quota": "Turi kvotą", "hash_asset": "Kurti bylos paraÅĄÄ… elementui", @@ -1188,12 +1277,29 @@ "hide_schema": "Slėpti schemą", "hide_text_recognition": "Slėpti teksto atpaÅžinimą", "hide_unnamed_people": "Slėpti neįvardintus asmenis", + "home_page_add_to_album_conflicts": "Pridėta {added} elementÅŗ į albumą {album}. {failed} elementai jau yra albume.", + "home_page_add_to_album_err_local": "Kol kas negalima pridėti vietiniÅŗ elementÅŗ į albumus, praleidÅžiama", + "home_page_add_to_album_success": "Pridėta {added} elementÅŗ į albumą {album}.", + "home_page_album_err_partner": "Kol kas negalima pridėti partnerio elementÅŗ į albumą, praleidÅžiama", + "home_page_archive_err_local": "Kol kas negalima archyvuoti vietiniÅŗ elementÅŗ, praleidÅžiama", + "home_page_archive_err_partner": "Negalima archyvuoti partnerio elementÅŗ, praleidÅžiama", "home_page_building_timeline": "Kuriama laiko juosta", + "home_page_delete_err_partner": "Negalima iÅĄtrinti partnerio elementÅŗ, praleidÅžiama", + "home_page_delete_remote_err_local": "Vietiniai elementai iÅĄtrinant nuotolinį pasirinkimą, praleidÅžiami", + "home_page_favorite_err_local": "Kol kas negalima priskirti mėgstamiausiÅŗ vietiniÅŗ elementÅŗ, praleidÅžiama", + "home_page_favorite_err_partner": "Kol kas negalima priskirti mėgstamiausiÅŗ partnerio elementÅŗ, praleidÅžiama", + "home_page_first_time_notice": "Jei jÅĢs naudojate programą pirmą kartą, tai praÅĄome pasirinkti atsarginės kopijos albumą, kad laiko juosta galėtÅŗ tvarkyti albumo nuotraukas ir vaizdo įraÅĄus", + "home_page_locked_error_local": "Nepavyko perkelti lokaliÅŗ failÅŗ į uÅžrakintą aplanką, praleidÅžiama", + "home_page_locked_error_partner": "Nepavyko perkelti partnerio failÅŗ į uÅžrakintą aplanką, praleidÅžiama", + "home_page_share_err_local": "Negalima dalinti vietiniÅŗ elementÅŗ per nuorodą, praleidÅžiama", + "home_page_upload_err_limit": "Galima įkelti tik iki 30 elementÅŗ vienu metu, praleidÅžiama", "host": "Å eimininkas", "hour": "Valanda", "hours": "Valandos", "id": "ID", "idle": "Laisva", + "ignore_icloud_photos": "Ignoruoti iCloud nuotraukas", + "ignore_icloud_photos_description": "Nuotraukos laikomos iCloud nebus įkeltos į Immich serverį", "image": "Nuotrauka", "image_alt_text_date": "{isVideo, select, true {Filmuota} other {Fotografuota}} {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1} ir {person2} - {city}, {country} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1}, {person2}, ir {person3} - {city}, {country} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Filmuota} other {Fotografuota}} su {person1}, {person2}, ir {additionalCount, number} kitais - {city}, {country} {date}", + "image_saved_successfully": "Nuotrauka iÅĄsaugota", + "image_viewer_page_state_provider_download_started": "Atsisiuntimas pradėtas", + "image_viewer_page_state_provider_download_success": "Atsisiuntimas pavyko", + "image_viewer_page_state_provider_share_error": "Dalinimosi klaida", "immich_logo": "Immich logotipas", "immich_web_interface": "Immich Web sąsaja", "import_from_json": "Importuoti iÅĄ JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Pavienis pasidalinimas", "individual_shares": "Pavieniai pasidalinimai", "info": "Informacija", - "integrity_checks": "Vientisumo Patikrinimai", "interval": { "day_at_onepm": "Kiekvieną dieną 13:00", "hours": "Kas{hours, plural, one {valandą} few {#valandas} other {{hours, number} valandÅŗ}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Netinkamas datos formatas", "invite_people": "Kviesti Åžmones", "invite_to_album": "Pakviesti į albumą", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "UÅžkrovimas vyko {dateTime}", + "ios_debug_info_last_sync_at": "Paskutinė sinchronizacija {dateTime}", + "ios_debug_info_no_processes_queued": "Eilėje nėra foniniÅŗ procesÅŗ", + "ios_debug_info_no_sync_yet": "Jokia background sync uÅžduotis dar nebuvo paleista", + "ios_debug_info_processes_queued": "{count, plural, one {Eilėje {count} foninis procesas} few {Eilėje {count} foniniai procesai} other {Eilėje {count} foniniÅŗ procesÅŗ}}", + "ios_debug_info_processing_ran_at": "Apdorojimas vyko {dateTime}", "items_count": "{count, plural, one {# elementas} few {# elementai} other {# elementÅŗ}}", "jobs": "UÅžduotys", + "json_editor": "JSON redagavimas", + "json_error": "JSON klaida", "keep": "Palikti", "keep_albums": "Palikti albumus", "keep_albums_count": "{count, plural, one {Paliekamas {count} albumas} few {Paliekami {count} albumai} other {Paliekama {count} albumÅŗ}}", @@ -1259,12 +1375,14 @@ "leave": "IÅĄeiti", "leave_album": "Palikti albumą", "lens_model": "LÄ™ÅĄiÅŗ modelis", - "less": "MaÅžiau", "let_others_respond": "Leisti kitiems reaguoti", "level": "Lygis", "library": "Biblioteka", "library_add_folder": "Pridėti aplanką", "library_edit_folder": "Redaguoti aplanką", + "library_options": "Bibliotekos pasirinktys", + "library_page_device_albums": "Albumai įrenginyje", + "library_page_new_album": "Naujas albumas", "library_page_sort_asset_count": "ElementÅŗ skaičius", "library_page_sort_created": "KÅĢrimo data", "library_page_sort_last_modified": "Paskutinį kartą modifikuota", @@ -1272,16 +1390,13 @@ "licenses": "Licencijos", "light": "Å viesi", "light_theme": "Perjungti į ÅĄviesią temą", - "like": "Patinka", - "like_deleted": "Patinka panaikintas", - "link": "Nuoroda", + "like": "Kaip", + "like_deleted": "Kaip iÅĄtrintas", "link_motion_video": "Susieti judesio vaizdo įraÅĄÄ…", "link_to_docs": "Daugiau informacijos rasite dokumentacijoje.", "link_to_oauth": "Susieti su OAuth", "linked_oauth_account": "Susieta OAuth paskyra", "list": "SąraÅĄas", - "live": "Tiesiogiai", - "load_more": "ÄŽkelti Daugiau", "loading": "Kraunama", "loading_search_results_failed": "Nepavyko uÅžkrauti paieÅĄkos rezultatÅŗ", "local": "Vietinis", @@ -1310,9 +1425,11 @@ "login": "Prisijungti", "login_disabled": "Prisijungimas neįgalintas", "login_form_api_exception": "API iÅĄimtis. Patikrinkite serverio URL ir bandykite dar kartą.", + "login_form_back_button_text": "Atgal", "login_form_email_hint": "jusupastas@email.com", "login_form_endpoint_hint": "http://jusu-serverio-ip:port", "login_form_endpoint_url": "Serverio galutinio taÅĄko URL", + "login_form_err_http": "PraÅĄome nurodyti http:// arba https://", "login_form_err_invalid_email": "Neteisingas el. paÅĄtas", "login_form_err_invalid_url": "Neteisingas URL", "login_form_err_leading_whitespace": "Pradinis tarpas", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Klaida prisijungiant, patikrinkite serverio URL, el. paÅĄtą ir slaptaÅžodį", "login_form_handshake_exception": "ÄŽvyko serverio patvirtinimo iÅĄimtis. Jei naudojate savarankiÅĄkai pasiraÅĄytą sertifikatą, nustatymuose įjunkite savarankiÅĄkai pasiraÅĄyto sertifikato palaikymą.", "login_form_password_hint": "slaptaÅžodis", - "login_form_server_empty": "ÄŽveskite serverio URL", - "login_form_server_error": "Nepavyko prisijungti prie serverio", - "login_has_been_disabled": "Prisijungimas iÅĄjungtas", + "login_form_save_login": "Likti prisijungus", + "login_form_server_empty": "ÄŽveskite serverio URL.", + "login_form_server_error": "Nepavyko prisijungti prie serverio.", + "login_has_been_disabled": "Prisijungimas iÅĄjungtas.", "login_password_changed_error": "ÄŽvyko klaida atnaujinant jÅĢsÅŗ slaptaÅžodį", "login_password_changed_success": "SlaptaÅžodis sėkmingai atnaujintas", "logout_all_device_confirmation": "Ar tikrai norite atsijungti iÅĄ visÅŗ įrenginiÅŗ?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "DuomenÅŗ bazė atstatoma", "maintenance_description": "ÄŽjungtas Immich techninės prieÅžiÅĢros reÅžimas.", "maintenance_end": "Baigti techninę prieÅžiÅĢrą", + "maintenance_end_error": "Nepavyko iÅĄjungti techninės prieÅžiÅĢros reÅžimo.", "maintenance_logged_in_as": "Å iuo metu prisijungę kaip {user}", "maintenance_restore_from_backup": "Atstatyti iÅĄ atsarginės kopijos", "maintenance_restore_library": "Atstatyti Savo Biblioteką", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Atidaryti nustatymus", "manage_media_access_subtitle": "Leisti Immich tvarkyti ir perkelti medijos failus.", "manage_media_access_title": "Medijos Valdymo Prieiga", + "manage_shared_links": "Bendrinimo nuorodÅŗ tvarkymas", "manage_sharing_with_partners": "Valdyti dalijimąsi su partneriais", "manage_the_app_settings": "Valdyti programos nustatymus", "manage_your_account": "Valdyti savo paskyrą", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Valdyti prijungtus įrenginius", "manage_your_oauth_connection": "Tvarkyti OAuth prisijungimą", "map": "ÅŊemėlapis", + "map_assets_in_bounds": "{count, plural, =0 {NuotraukÅŗ nėra} one {# nuotrauka} other {# nuotraukos}}", "map_cannot_get_user_location": "Negalime gauti naudotojo vietovės", + "map_location_dialog_yes": "Taip", "map_location_picker_page_use_location": "Naudoti ÅĄią vietovę", "map_location_service_disabled_content": "Vietovės servisas turi bÅĢti įjungtas, kad rodytÅŗ elementus iÅĄ dabartinės vietovės. ÄŽjungti vietovės servisą?", "map_location_service_disabled_title": "Vietovės servisas iÅĄjungtas", - "map_marker_for_image": "ÅŊemėlapio Åžymeklis nuotraukai, padarytai {city}, {country}", + "map_marker_for_images": "ÅŊemėlapio Åžymeklis nuotraukoms yra {city}, {country}", "map_marker_with_image": "ÅŊemėlapio Åžymeklis su nuotrauka", "map_no_location_permission_content": "Reikalingas vietovės leidimas, kad rodytÅŗ elementus iÅĄ dabartinės vietovės. Ar norite suteikti leidimą?", "map_no_location_permission_title": "Vietovės leidimas atmestas", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Pastarąsias {days} dienas", "map_settings_date_range_option_year": "Pastarieji metai", "map_settings_date_range_option_years": "Pastaruosius {years} metus", + "map_settings_dialog_title": "ÅŊemėlapio nustatymai", "map_settings_include_show_archived": "ÄŽtraukti archyvuotus", "map_settings_include_show_partners": "Pridėti partneriai", "map_settings_only_show_favorites": "Rodyti tik mėgstamiausius", "map_settings_theme_settings": "ÅŊemėlapio tema", + "map_zoom_to_see_photos": "Atitolinkite, kad matytumėte nuotraukas", "mark_all_as_read": "PaÅžymėti viską kaip perskaitytą", + "mark_as_read": "PaÅžymėti kaip perskaitytą", "marked_all_as_read": "Viskas paÅžymėta kaip perskaityta", "matches": "Atitikmenys", "matching_assets": "Atitinkantys elementai", - "media_chrome": { - "auto": "Auto", - "captions": "Subtitrai", - "captions_off": "IÅĄjungta", - "closed_captions": "uÅždarieji subtitrai", - "decode_error": "Dekodavimo klaida", - "disable_captions": "IÅĄjungti subtitrus", - "enable_captions": "ÄŽjungti subtitrus", - "enter_fullscreen_mode": "Pilno ekrano reÅžimas", - "exit_fullscreen_mode": "IÅĄeiti iÅĄ pilno ekrano reÅžimo", - "loop": "Kartoti", - "media_error_description": "Dėl medijos klaidos atkÅĢrimas buvo nutrauktas. Medija gali bÅĢti sugadinta arba jÅĢsÅŗ narÅĄyklė nepalaiko ÅĄio formato.", - "media_loading": "medijos įkėlimas", - "mute": "Nutildyti", - "network_error": "Tinklo klaida", - "network_error_description": "Dėl tinklo klaidos nepavyko atsisiÅŗsti medijos.", - "not_supported_error": "Å altinis Nepalaikomas", - "playback_rate": "Grojimo daÅžnis", - "playback_rate_current": "dabartinis grojimo daÅžnis", - "playback_rate_value": "Grojimo daÅžnis {playbackRate}", - "playback_time": "grojimo laikas", - "quality": "Kokybė", - "second": "sekundė", - "seconds": "sekundės", - "time_value_of_total_time": "{currentTime} iÅĄ {totalTime}", - "time_value_remaining": "{time} liko", - "unmute": "ÄŽjungti garsą", - "unsupported_error_description": "ÄŽvyko klaida. Serveris arba tinklas sugedo arba jÅĢsÅŗ narÅĄyklė nepalaiko ÅĄio formato.", - "video_not_loaded_unknown_time": "vaizdo įraÅĄas neįkeltas, laikas neÅžinomas.", - "video_player": "vaizdo grotuvas", - "volume": "garso stiprumas" - }, "media_type": "Laikmenos tipas", "memories": "Atsiminimai", "memories_all_caught_up": "Jau viskas perÅžiÅĢrėta", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Ar norite sujungti ÅĄiuos asmenis? Å is veiksmas yra negrįŞtamas.", "merge_people_successfully": "Asmenys sėkmingai sujungti", "merged_people_count": "{count, plural, one {Sujungtas # asmuo} few {Sujungti # asmenys} other {Sujungta # asmenÅŗ}}", - "minFaces": "Minimalus veidÅŗ skaičius", - "minFaces_description": "Minimalus atpaÅžintÅŗ veidÅŗ skaičius, kad bÅĢtÅŗ rodomas asmuo", "minimize": "SumaÅžinti", "minute": "Minutė", "minutes": "Minutės", + "mirror_horizontal": "Horizontaliai", + "mirror_vertical": "Vertikaliai", "missing": "TrÅĢkstami", "mobile_app": "Mobili programa", "mobile_app_download_onboarding_note": "AtsisiÅŗskite mobiliąją programėlę naudodami ÅĄias parinktis", "model": "Modelis", - "modify_date": "Redaguoti Datą", "month": "Mėnesis", + "monthly_title_text_date_format": "MMMM y", "more": "Daugiau", - "motion": "Judesys", "move": "Perkelti", + "move_down": "ÅŊemyn", "move_off_locked_folder": "IÅĄtraukti iÅĄ uÅžrakinto aplanko", "move_to": "Perkelti į", "move_to_device_trash": "Perkelti į įrenginio ÅĄiukÅĄliadėŞę", "move_to_lock_folder_action_prompt": "{count} įkelta į uÅžrakintą aplanką", "move_to_locked_folder": "ÄŽtraukti į uÅžrakintą aplanką", "move_to_locked_folder_confirmation": "Å ios nuotraukos ir vaizdo įraÅĄai bus paÅĄalinti iÅĄ visÅŗ albumÅŗ ir bus matomi tik uÅžrakintame aplanke", + "move_up": "AukÅĄtyn", + "moved_to_archive": "{count, plural, one {# Elementas perkeltas} few {# Elementai perkelti} other {# ElementÅŗ perkelta}} į archyvą", + "moved_to_library": "{count, plural, one {# Elementas perkeltas} few {# Elementai perkelti} other {# ElementÅŗ perkelta}} į biblioteką", "moved_to_trash": "Perkelta į ÅĄiukÅĄliadėŞę", + "multiselect_grid_edit_date_time_err_read_only": "Negalima redaguoti tik skaitomo elemento datos, praleidÅžiama", + "multiselect_grid_edit_gps_err_read_only": "Negalima redaguoti tik skaitomo elemento vietovės, praleidÅžiama", "mute_memories": "UÅžtildyti prisiminimus", "my_albums": "Mano albumai", - "my_immich_description": "Kopijuoti dabartinį puslapį kaip Mano Immich nuorodą", - "my_immich_title": "Mano Immich nuoroda", "name": "Vardas", "name_or_nickname": "Vardas arba slapyvardis", "name_required": "Vardas yra privalomas", @@ -1480,11 +1576,12 @@ "never": "Niekada", "new_album": "Naujas albumas", "new_api_key": "Naujas API raktas", - "new_feature": "Nauja Funkcija", + "new_date_range": "Naujas datos intervalas", "new_password": "Naujas slaptaÅžodis", "new_person": "Naujas asmuo", "new_pin_code": "Naujas PIN kodas", "new_pin_code_subtitle": "Tai pirmas kartas, kai naudojate uÅžrakinto aplanko funkciją. Nustatykite PIN kodą savo uÅžrakintam aplankui", + "new_timeline": "Nauja laiko juosta", "new_update": "Nauja versija", "new_user_created": "Naujas naudotojas sukurtas", "new_version_available": "IÅ LEISTA NAUJA VERSIJA", @@ -1492,6 +1589,7 @@ "next": "Sekantis", "next_memory": "Sekantis atsiminimas", "no": "Ne", + "no_actions_added": "JokiÅŗ veiksmÅŗ dar nepridėta", "no_albums_found": "AlbumÅŗ nerasta", "no_albums_message": "Sukurkite albumą nuotraukoms ir vaizdo įraÅĄams tvarkyti", "no_albums_with_name_yet": "Atrodo, kad dar neturite albumÅŗ su ÅĄiuo pavadinimu.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Nerasta transliavimo įrenginiÅŗ", "no_checksum_local": "Kontrolinė suma nepasiekiama – negalima gauti vietiniÅŗ elementÅŗ", "no_checksum_remote": "Kontrolinė suma nepasiekiama – negalima gauti nuotoliniÅŗ elementÅŗ", + "no_configuration_needed": "KonfigÅĢracija nereikalinga", "no_devices": "Nėra autorizuotÅŗ įrenginiÅŗ", "no_duplicates_found": "DublikatÅŗ nerasta.", "no_exif_info_available": "Nėra Exif informacijos", "no_explore_results_message": "ÄŽkelkite daugiau nuotraukÅŗ ir tyrinėkite savo kolekciją.", "no_favorites_message": "Pridėti į mėgstamiausius, kad greitai rastum geriausias nuotraukas ir vaizdo įraÅĄus", + "no_filters_added": "FiltrÅŗ dar nepridėta", "no_libraries_message": "Sukurkite iÅĄorinę biblioteką nuotraukoms ir vaizdo įraÅĄams perÅžiÅĢrėti", "no_local_assets_found": "Nerasta jokiÅŗ vietiniÅŗ elementÅŗ su ÅĄia kontroline suma", + "no_location_set": "Nenustatyta vietovė", "no_locked_photos_message": "UÅžrakintame aplanke esančios nuotraukos ir vaizdo įraÅĄai yra paslėpti ir nematomi narÅĄant ir ieÅĄkant.", "no_name": "Be vardo", "no_notifications": "PraneÅĄimÅŗ nėra", @@ -1518,19 +1619,14 @@ "no_results": "RezultatÅŗ nerasta", "no_results_description": "Pabandykite sinonimą arba bendresnį raktaÅžodį", "no_shared_albums_message": "Sukurkite nuotraukÅŗ ar vaizdo įraÅĄÅŗ albumą dalinimuisi su Åžmonėmis jÅĢsÅŗ tinkle", - "no_steps": "ÅŊingsniai visdar nepridėti", + "no_uploads_in_progress": "Nėra vykstančiÅŗ įkėlimÅŗ", "none": "Niekas", "not_allowed": "NeleidÅžiama", "not_available": "Nepasiekiamas", "not_in_any_album": "Nė viename albume", "not_selected": "Nepasirinkta", - "not_set": "Nenustatyta", "notes": "Pastabos", "nothing_here_yet": "Kol kas tuÅĄÄia", - "notification_backup_reliability": "ÄŽjunkite praneÅĄimus, kad pagerintumėte foniniÅŗ atsarginiÅŗ kopijÅŗ patikimumą", - "notification_enabled_list_tile_content": "Immich naudoja praneÅĄimus atsarginiam kopijavimui fone. Juos galite valdyti savo įrenginio nustatymuose.", - "notification_enabled_list_tile_open_button": "Atidaryti nustatymus", - "notification_enabled_list_tile_title": "PraneÅĄimai įjungti", "notification_permission_dialog_content": "PraneÅĄimÅŗ įgalinimui eikite į Nustatymus ir pasirinkite Leisti.", "notification_permission_list_tile_content": "Suteikti leidimą praneÅĄimÅŗ įgalinimui.", "notification_permission_list_tile_enable_button": "ÄŽjungti praneÅĄimus", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Obtainium KonfigÅĢratorius", "obtainium_configurator_instructions": "Naudokite Obtainium, jei norite įdiegti ir atnaujinti Android programėlę tiesiai iÅĄ Immich GitHub. Sukurkite API raktą ir pasirinkite variantą, Obtainium konfigÅĢracijos nuorodos sukÅĢrimui", "ocr": "OCR", - "ocr_body": "Immich dabar atpaŞįsta tekstą jÅĢsÅŗ nuotraukose, todėl galite jÅŗ ieÅĄkoti pagal tai, kas jose paraÅĄyta.", - "ocr_title": "IeÅĄkokite teksto savo nuotraukose", "official_immich_resources": "OficialÅĢs Immich iÅĄtekliai", "offline": "Neprisijungęs", "offset": "Ofsetas", @@ -1562,8 +1656,6 @@ "open": "Atverti", "open_calendar": "Atidaryti kalendoriÅŗ", "open_in_browser": "Atverti narÅĄyklėje", - "open_in_immich_body": "Nustatykite Immich kaip numatytąją galeriją „Android“ įrenginyje, kad nuotraukas galėtumėte atidaryti tiesiai iÅĄ kitÅŗ programėliÅŗ.", - "open_in_immich_title": "Atidaryti nuotraukas per Immich", "open_in_map_view": "Atverti Åžemėlapyje", "open_in_openstreetmap": "Atverti per OpenStreetMap", "open_the_search_filters": "Atidaryti paieÅĄkos filtrus", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Sutvarkyti į albumus", "organize_into_albums_description": "Sukelti egzistuojančias nuotraukas į albumus naudojant dabartinius sinchronizavimo nustatymus", "organize_your_library": "Tvarkykite savo biblioteką", - "orientation": "Orientacija", "original": "Originalas", "other": "Kiti", "other_devices": "Kiti įrenginiai", + "other_entities": "Kiti subjektai", "other_variables": "Kiti kintamieji", "owned": "Nuosavi", "owner": "Savininkas", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Visos jÅĢsÅŗ nuotraukos ir vaizdo įraÅĄai, iÅĄskyrus archyvuotus ir iÅĄtrintus", "partner_can_access_location": "Vieta, kurioje darytos nuotraukos", "partner_list_user_photos": "{user} nuotraukos", + "partner_list_view_all": "ÅŊiÅĢrėti viską", "partner_page_empty_message": "JÅĢsÅŗ nuotraukomis dar nesidalinama su jokiu partneriu.", "partner_page_no_more_users": "Nėra daugiau naudotojÅŗ pridėjimui", + "partner_page_partner_add_failed": "Nepavyko pridėti partnerio", "partner_page_select_partner": "Pasirinkite partnerį", + "partner_page_shared_to_title": "Dalinamasi su", "partner_page_stop_sharing_content": "{partner} daugiau nebegalės pasiekti jÅĢsÅŗ nuotraukÅŗ.", "partner_sharing": "Dalinimasis su partneriu", "partners": "Partneriai", @@ -1609,6 +1704,8 @@ "people": "Asmenys", "people_edits_count": "{count, plural, one {Redaguotas # asmuo} few {Redaguoti # asmenys} other {Redaguota # asmenÅŗ}}", "people_feature_description": "PerÅžiÅĢrėkite nuotraukas ir vaizdo įraÅĄus sugrupuotus pagal asmenis", + "people_selected": "{count, plural, one {# asmuo pasirinktas} other {# asmenÅŗ pasirinkta}}", + "people_sidebar_description": "Rodyti asmenÅŗ rodinio nuorodą ÅĄoninėje juostoje", "permanent_deletion_warning": "IÅĄtrynimo visam laikui perspėjimas", "permanent_deletion_warning_setting_description": "Rodyti perspėjimą kai elementas iÅĄtrinamas visam laikui", "permanently_delete": "IÅĄtrinti visam laikui", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "Visam laikui {count, plural, one {iÅĄtrintas # elementas} few {iÅĄtrinti # elementai} other {iÅĄtrinta # elementÅŗ}}", "permission": "Leidimas", "permission_empty": "JÅĢsÅŗ leidimas neturėtÅŗ bÅĢti tuÅĄÄias", + "permission_onboarding_back": "Atgal", + "permission_onboarding_continue_anyway": "Vis tiek tęsti", + "permission_onboarding_get_started": "Pradėkite", + "permission_onboarding_go_to_settings": "Eiti į nustatymus", + "permission_onboarding_permission_denied": "Leidimas nesuteiktas. Norėdami naudoti Immich, suteikite nuotraukÅŗ ir vaizdo įraÅĄÅŗ leidimus nustatymuose.", + "permission_onboarding_permission_granted": "Leidimas suteiktas! jÅĢs pasiruoÅĄÄ™.", + "permission_onboarding_permission_limited": "Leidimai apriboti. Norėdami leisti Immich kurti atsargines kopijas ir tvarkyti visą jÅĢsÅŗ galerijos kolekciją, suteikite nuotraukÅŗ ir vaizdo įraÅĄÅŗ leidimus nustatymuose.", + "permission_onboarding_request": "Immich reikalingas leidimas perÅžiÅĢrėti jÅĢsÅŗ nuotraukas ir vaizdo įraÅĄus.", "person": "Asmuo", "person_age_months": "{months, plural, one {# mėnesio} other {# mėnesiÅŗ}} amÅžiaus", "person_age_year_months": "1 metÅŗ ir {months, plural, one {# mėnesio} other {# mėnesiÅŗ}} amÅžiaus", @@ -1625,10 +1730,13 @@ "person_birthdate": "Gimė {date}", "person_hidden": "{name}{hidden, select, true { (paslėptas)} other {}}", "person_recognized": "Asmuo atpaÅžintas", + "person_selected": "Asmuo pasirinktas", "photo_shared_all_users": "PanaÅĄu, kad savo nuotraukomis pasidalijote su visais naudotojais arba neturite naudotojÅŗ, su kuriais galėtumėte jomis pasidalyti.", "photos": "Nuotraukos", "photos_and_videos": "Nuotraukos ir vaizdo įraÅĄai", + "photos_count": "{count, plural, one {{count, number} nuotrauka} few {{count, number} nuotraukos} other {{count, number} nuotraukÅŗ}}", "photos_from_previous_years": "AnkstesniÅŗ metÅŗ nuotraukos", + "photos_only": "Tik nuotraukos", "pick_a_location": "Pasirinkite vietovę", "pick_custom_range": "Pasirinktinis diapazonas", "pick_date_range": "Pasirinkti datos diapazoną", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Rodyti originalius vaizdo įraÅĄus vietoje konvertuotÅŗ vaizdo įraÅĄÅŗ. Jei originalus įraÅĄas yra nesuderinamas, jis gali groti neteisingai.", "play_transcoded_video": "Rodyti konvertuotą vaizdo įraÅĄÄ…", "please_auth_to_access": "PraÅĄome patvirtinti prisijungimą", - "plugin_method_filter_type": "Filtras", - "plugin_method_filter_type_description": "Å is metodas gali filtruoti įvykius ir sąlyginai uÅžkirsti kelią tolesniems veiksmams", "port": "Portas", "preferences_settings_subtitle": "Tvarkyti programos nuostatas", "preferences_settings_title": "Nuostatos", @@ -1665,11 +1771,11 @@ "privacy": "Privatumas", "profile": "Profilis", "profile_drawer_app_logs": "Logai", + "profile_drawer_client_server_up_to_date": "Klientas ir Serveris yra atnaujinti", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Tik skaitymo rÄ—Åžimas įgalintas. Ilgai paspauskite vartotojo ikoną iÅĄÄ—jimui.", "profile_image_of_user": "{user} profilio nuotrauka", "profile_picture_set": "Profilio nuotrauka nustatyta.", - "projection_type": "Projekcijos Tipas", "public_album": "VieÅĄas albumas", "public_share": "VieÅĄas dalinimasis", "purchase_account_info": "Rėmėjas", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Rėmėjo statusas", "purchase_individual_title": "Asmeninis", "purchase_input_suggestion": "Turite produkto raktą? ÄŽveskite jį Åžemiau", + "purchase_license_subtitle": "ÄŽsigykite Immich, kad palaikytumėte tolesnį paslaugos vystymą", "purchase_lifetime_description": "Pirkimas visam gyvenimui", "purchase_option_title": "PIRKIMO PASIRINKIMAS", "purchase_panel_info_1": "Immich kÅĢrimas uÅžima daug laiko ir pastangÅŗ, inÅžinieriai dirba visą darbo dieną, kad Immich bÅĢtÅŗ kuo geresnis. MÅĢsÅŗ misija yra, kad atvirojo kodo programinė įranga ir etiÅĄka verslo praktika taptÅŗ tvariu kÅĢrėjÅŗ pajamÅŗ ÅĄaltiniu ir sukurtÅŗ privatumą gerbiančią ekosistemą su realia alternatyva tarp kitÅŗ pelno siekiančiÅŗ Cloud paslaugÅŗ.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {Perskirstytas # elementas} few {Perskirstyti # elementai} other {Perskirstyta # elementÅŗ}} į naują asmenį", "reassing_hint": "Priskirti pasirinktus elementus esamam asmeniui", "recent": "Naujausi", + "recent_albums": "Naujausi albumai", "recent_searches": "Naujausios paieÅĄkos", "recently_added": "Neseniai pridėta", - "recently_added_body": "Pasiekite visus naujausius įkėlimus viename tam skirtame puslapyje.", - "recently_added_description": "PerÅžiÅĢrėkite failus, surÅĢÅĄiuotus pagal įkėlimo į Immich datą", "recently_added_page_title": "Neseniai pridėta", - "recently_added_title": "Neseniai pridėta", "recently_taken": "Neseniai sukurti", + "recently_taken_page_title": "Neseniai sukurti", "refresh": "Atnaujinti", "refresh_encoded_videos": "Perkrauti apdorotus vaizdo įraÅĄus", "refresh_faces": "Perkrauti veidus", "refresh_metadata": "Perkrauti metaduomenis", "refresh_thumbnails": "Perkrauti miniatiÅĢras", "refreshed": "Atnaujinta", + "refreshes_every_file": "IÅĄ naujo perskaito visus esamus ir naujai pridėtus failus", "refreshing_encoded_video": "Perkraunamas apdorotas vaizdo įraÅĄas", "refreshing_faces": "Perkraunami veidai", "refreshing_metadata": "Perkraunami metaduomenys", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Ar tikrai norite paÅĄalinti {count, plural, one {# elementą} few {# elementus} other {# elementÅŗ}} iÅĄ ÅĄios bendrinimo nuorodos?", "remove_assets_title": "PaÅĄalinti elementus?", "remove_custom_date_range": "PaÅĄalinti pasirinktinę dienÅŗ seką", - "remove_filter": "PaÅĄalinti filtrą", + "remove_deleted_assets": "PaÅĄalinti IÅĄtrintus Elemenuts", "remove_from_album": "PaÅĄalinti iÅĄ albumo", "remove_from_album_action_prompt": "{count} paÅĄalinta iÅĄ albumo", "remove_from_favorites": "PaÅĄalinti iÅĄ mėgstamiausiÅŗ", @@ -1756,22 +1863,29 @@ "remove_memory": "PaÅĄalinti atsiminimus", "remove_photo_from_memory": "PaÅĄalinti nuotrauką iÅĄ atsiminimÅŗ", "remove_tag": "PaÅĄalinti Åžymę", + "remove_url": "PaÅĄalinti URL", "remove_user": "PaÅĄalinti naudotoją", "removed_api_key": "PaÅĄalintas API Raktas: {name}", "removed_from_archive": "PaÅĄalinta iÅĄ archyvo", "removed_from_favorites": "PaÅĄalinta iÅĄ mėgstamiausiÅŗ", "removed_from_favorites_count": "{count, plural, one {# paÅĄalintas} few {# paÅĄalinti} other {# paÅĄalinta}} iÅĄ mėgstamiausiÅŗ", "removed_memory": "Atsiminimas paÅĄalintas", + "removed_photo_from_memory": "Nuotrauka iÅĄtrinta iÅĄ atsiminimÅŗ", "removed_tagged_assets": "ÅŊyma paÅĄalinta iÅĄ {count, plural, one {# elemento} other {# elementÅŗ}}", "rename": "Pervadinti", + "repair": "Pataisyti", + "repair_no_results_message": "Nesekami ir trÅĢkstami failai bus rodomi čia", + "replace_with_upload": "Pakeisti naujai įkeltu failu", "repository": "Repozitoriumas", "require_password": "Reikalauti slaptaÅžodÅžio", + "require_user_to_change_password_on_first_login": "Reikalauti, kad vartotojas pakeistÅŗ slaptaÅžodį pirmą kartą prisijungdamas", "rescan": "Perskenuoti", "reset": "Atstatyti", "reset_password": "Atstatyti slaptaÅžodį", "reset_people_visibility": "Atstatyti ÅžmoniÅŗ matomumą", "reset_pin_code": "Atsatyti PIN kodą", "reset_pin_code_description": "Jei pamirÅĄote PIN kodą, galite susisiekti su serverio administratoriumi, kad jis jį atstatytÅŗ", + "reset_pin_code_success": "Sėkmingai atstatytas PIN kodas", "reset_pin_code_with_password": "PIN kodą visada galite atkurti naudodami savo slaptaÅžodį", "reset_sqlite": "Atstatyti SQLite duomenÅŗ bazę", "reset_sqlite_clear_app_data": "IÅĄtrinti Duomenis", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Sutvarkyti visi dublikatai", "restore": "Atkurti", "restore_all": "Atkurti visus", + "restore_trash_action_prompt": "{count} atstatyta iÅĄ ÅĄiukÅĄliadėŞės", "restore_user": "Atkurti naudotoją", "restored_asset": "Atkurti elementą", "resume": "Tęsti", "resume_paused_jobs": "Tęsti {count, plural, one {# pristabdytą darbą} other {# pristabdytus darbus}}", + "retry_upload": "Bandyti iÅĄsiÅŗsti dar kartą", "review_duplicates": "PerÅžiÅĢrėti dublikatus", "review_large_files": "PerÅžiÅĢrėti didelius failus", "role": "Rolė", @@ -1796,6 +1912,7 @@ "role_viewer": "Stebėtojas", "running": "Vykdoma", "save": "IÅĄsaugoti", + "save_to_gallery": "IÅĄsaugoti galerijoje", "saved": "IÅĄsaugota", "saved_api_key": "IÅĄsaugotas API raktas", "saved_profile": "IÅĄsaugotas profilis", @@ -1806,10 +1923,9 @@ "scan": "Skenuoti", "scan_all_libraries": "Skenuoti visas bibliotekas", "scan_library": "Skenuoti", + "scan_settings": "Skenavimo nustatymai", "scanning": "Skenuojama", "scanning_for_album": "Skenuojama albumÅŗ...", - "screencast_mode_description": "Rodyti klaviatÅĢros ir pelės veiklos indikatorius ekrane", - "screencast_mode_title": "ÄŽjungti screencast reÅžimą", "search": "IeÅĄkoti", "search_albums": "IeÅĄkoti albumÅŗ", "search_by_context": "IeÅĄkoti pagal kontekstą", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Ilga kelionė per kopas", "search_by_filename": "IeÅĄkoti pagal failo pavadinimą arba plėtinį", "search_by_filename_example": "pvz. IMG_1234.JPG arba PNG", - "search_by_full_path": "IeÅĄkoti pagal pilną kelią ar aplanką", - "search_by_full_path_example": "/Jonas/Projektai/3D_Printavimas/2026-07-01 - paieÅĄka pagal Projektus, 3D, Printavimas, 2026 ir t.t.", "search_by_ocr": "IeÅĄkoti pagal OCR", "search_by_ocr_example": "Kakava", "search_camera_lens_model": "IeÅĄkoti objektyvo modelio...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Pasirinkti vietovę", "search_filter_media_type": "Medijos tipas", "search_filter_media_type_title": "Pasirinkti medijos tipą", + "search_filter_ocr": "IeÅĄkoti pagal OCR", "search_filter_people_title": "Pasirinkti asmenis", "search_filter_star_rating": "ÄŽvertinimas", "search_filter_tags_title": "Pasirinkti Åžymas", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Nėra ÅžmoniÅŗ vardu „{name}“", "search_no_result": "RezultatÅŗ nerasta, pabandykite kitą paieÅĄkos terminą ar derinį", "search_options": "PaieÅĄkos parinktys", + "search_page_categories": "Kategorijos", + "search_page_motion_photos": "Judanti Foto", + "search_page_no_objects": "Objekto info nepasiekiama", + "search_page_no_places": "Vietovės info nepasiekiama", + "search_page_screenshots": "Ekrano nuotraukos", "search_page_search_photos_videos": "IeÅĄkokite nuotraukÅŗ ir vaizdo įraÅĄÅŗ", + "search_page_selfies": "Asmenukės", + "search_page_things": "Dalykai", "search_page_view_all_button": "PerÅžiÅĢrėti visus", + "search_page_your_activity": "JÅĢsÅŗ veikla", + "search_page_your_map": "JÅĢsÅŗ Åžemėlapis", "search_people": "IeÅĄkoti ÅžmoniÅŗ", "search_places": "IeÅĄkoti vietÅŗ", "search_rating": "IeÅĄkoti pagal įvertinimą...", + "search_result_page_new_search_hint": "Nauja PaieÅĄka", "search_settings": "IeÅĄkoti nustatymÅŗ", "search_state": "IeÅĄkoti valstijos/apskrities...", + "search_suggestion_list_smart_search_hint_1": "IÅĄmanioji paieÅĄka įjungta pagal numatytuosius nustatymus, metaduomenÅŗ paieÅĄkai naudokite sintaksę ", + "search_suggestion_list_smart_search_hint_2": "PaieÅĄka", "search_tags": "IeÅĄkoti ÅžymÅŗ...", "search_timezone": "IeÅĄkoti laiko zonos...", "search_type": "PaieÅĄkos tipas", @@ -1868,6 +1995,7 @@ "select_albums": "Rinktis albumus", "select_all": "Pasirinkti visus", "select_all_duplicates": "Pasirinkti visus dublikatus", + "select_all_in": "PaÅžymėti visus esančius {group}", "select_avatar_color": "Pasirinkti avataro spalvą", "select_count": "{count, plural, one {Pasirinkti #} other {Pasirinkti #}}", "select_cutoff_date": "Pasirinkite galutinę datą", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Pasirinkti rodomą nuotrauką", "select_from_computer": "Pasirinkti iÅĄ kompiuterio", "select_keep_all": "Visus paÅžymėti \"Palikti\"", + "select_library_owner": "Pasirinkti bibliotekos savininką", "select_new_face": "Pasirinkti naują veidą", "select_people": "Pasirinkti asmenis", "select_person": "Pasirinkti asmenį", "select_person_to_tag": "Pasirinkti asmenį Åžymai", "select_photos": "Pasirinkti nuotraukas", - "select_quality": "Pasirinkite kokybę", "select_trash_all": "Visus paÅžymėti \"IÅĄmesti\"", + "select_user_for_sharing_page_err_album": "Nepavyko sukurti albumo", "selected": "Pasirinkta", "selected_count": "{count, plural, one {# pasirinktas} few {# pasirinkti} other {# pasirinktÅŗ}}", "selected_gps_coordinates": "Pasirinkti GPS Koordinates", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "UÅžkrauti originalią nuotrauką", "setting_image_viewer_preview_subtitle": "ÄŽjunkite, jei norite įkelti vidutinės raiÅĄkos vaizdą. IÅĄjunkite, jei norite tiesiogiai įkelti originalą ar naudoti tik miniatiÅĢrą.", "setting_image_viewer_preview_title": "UÅžkrauti perÅžiÅĢros nuotrauką", + "setting_image_viewer_title": "Nuotraukos", "setting_languages_apply": "Pritaikyti", "setting_languages_subtitle": "Pakeisti programos kalbą", + "setting_notifications_notify_failures_grace_period": "Informuoti apie foninio atsarginio kopijavimo nesėkmes: {duration}", + "setting_notifications_notify_hours": "{count} valandÅŗ", + "setting_notifications_notify_immediately": "nedelsiant", "setting_notifications_notify_minutes": "{count} minučiÅŗ", + "setting_notifications_notify_never": "niekada", "setting_notifications_notify_seconds": "{count} sekundÅžiÅŗ", + "setting_notifications_single_progress_subtitle": "Detali įkėlimo progreso informacija kiekvienam elementui", + "setting_notifications_single_progress_title": "Rodyti foninio atsarginio kopijavimo eigą", "setting_notifications_subtitle": "Koreguoti praneÅĄimÅŗ nuostatas", + "setting_notifications_total_progress_subtitle": "Bendra įkėlimo eiga (atlikta/viso elementÅŗ)", + "setting_notifications_total_progress_title": "Rodyti visą foninio atsarginio kopijavimo eigą", "setting_video_viewer_auto_play_subtitle": "AutomatiÅĄkai pradėti leisti vaizdo įraÅĄus juos atidarius", "setting_video_viewer_auto_play_title": "Groti video automatiÅĄkai", "setting_video_viewer_looping_title": "Kartotinai", "setting_video_viewer_original_video_subtitle": "Transliuojant vaizdo įraÅĄÄ… iÅĄ serverio, leisti originalą, net jei įraÅĄas yra konvertuotas. ÄŽraÅĄas gali strigti. Vietoje pasiekiami vaizdo įraÅĄai leidÅžiami originalia kokybe, nepaisant ÅĄio nustatymo.", "setting_video_viewer_original_video_title": "Priversti originalÅŗ vaizdo įraÅĄÄ…", "settings": "Nustatymai", + "settings_require_restart": "PraÅĄome perkrauti Immich, siekiant pritaikyti ÅĄÄ¯ nustatymą", "settings_saved": "Nustatymai iÅĄsaugoti", "setup_pin_code": "Nustatyti PIN kodą", "share": "Dalintis", + "share_action_prompt": "{count} dalinamasi", + "share_add_photos": "ÄŽtraukti nuotraukÅŗ", + "share_assets_selected": "{count} paÅžymėta", "share_dialog_preparing": "RuoÅĄiama...", "share_link": "Bendrinti nuorodą", - "share_original": "Naudoti originalą (didelį)", - "share_preview": "Naudoti miniatiÅĢrą (maŞą)", - "share_quality_body": "Palaikykite nuspaudę bendrinimo mygtuką, kad pasirinktumėte vaizdo kokybę prieÅĄ siÅŗsdami.", - "share_quality_title": "Pasirinkite bendrinimo kokybę", "shared": "Bendrinami", "shared_album_activities_input_disable": "Komentarai iÅĄjungti", "shared_album_activity_remove_content": "Ar norite iÅĄtrinti ÅĄią veiklą?", "shared_album_activity_remove_title": "IÅĄtrinti veiklą", "shared_album_section_people_action_error": "Klaida iÅĄeinant/ÅĄalinant iÅĄ albumo", + "shared_album_section_people_action_leave": "PaÅĄalinti naudotoją iÅĄ albumo", + "shared_album_section_people_action_remove_user": "PaÅĄalinti naudotoją iÅĄ albumo", "shared_album_section_people_title": "ASMENYS", "shared_by": "Bendrina", "shared_by_user": "Bendrina {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ÄŽkelta", "shared_link_app_bar_title": "Dalinimosi Nuorodos", "shared_link_clipboard_copied_massage": "Nukopijuota į iÅĄkarpinę", + "shared_link_clipboard_text": "Nuoroda: {link}\nSlaptaÅžodis: {password}", "shared_link_create_error": "Klaida kuriant bendrinimo nuorodą", "shared_link_custom_url_description": "Pasiekite ÅĄią bendrinimo nuorodą naudodami tinkintą URL", - "shared_link_custom_url_title": "Pasirinktinis URL", - "shared_link_custom_url_warning": "ÄŽspėjimas: pasirinktinis URL adresas su pasviruoju brÅĢkÅĄniu gali veikti ne taip, kaip tikitės.", "shared_link_edit_description_hint": "ÄŽveskite bendrinimo apraÅĄymą", "shared_link_edit_expire_after_option_day": "1 diena", "shared_link_edit_expire_after_option_days": "{count} dienÅŗ", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Pasidalinta su {partner}", "sharing": "Dalijimasis", "sharing_enter_password": "Norėdami perÅžiÅĢrėti ÅĄÄ¯ puslapį, įveskite slaptaÅžodį.", + "sharing_page_album": "Bendrinami albumai", + "sharing_page_description": "Kurkite bendrinamus albumus, kad galėtumėte dalintis foto ir video su Åžmonėmis savo tinkle.", + "sharing_page_empty_list": "TUŠČIAS SĄRAÅ AS", + "sharing_sidebar_description": "Rodyti bendrinimo rodinio nuorodą ÅĄoninėje juostoje", + "sharing_silver_appbar_create_shared_album": "Naujas bendrinamas albumas", + "sharing_silver_appbar_share_partner": "Bendrinti su partneriu", "shift_to_permanent_delete": "spauskite ⇧, kad visam laikui iÅĄtrintumėte elementą", "show_album_options": "Rodyti albumo parinktis", "show_albums": "Rodyti albumus", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Rodyti laiko skalėje", "show_in_timeline_setting_description": "Rodyti ÅĄio naudotojo nuotraukas ir vaizdo įraÅĄus mano laiko skalėje", "show_keyboard_shortcuts": "Rodyti klaviatÅĢros trumpinius", - "show_less": "Rodyti maÅžiau", "show_metadata": "Rodyti metaduomenis", - "show_more_fields": "{count, plural, one {Rodyti # lauku daugiau} other {Rodyti # laukÅŗ daugiau}}", "show_or_hide_info": "Rodyti arba slėpti informaciją", "show_password": "Rodyti slaptaÅžodį", "show_person_options": "Rodyti asmens parinktis", @@ -2005,7 +2148,6 @@ "show_schema": "Rodyti schemą", "show_search_options": "Rodyti paieÅĄkos parinktis", "show_shared_links": "Rodyti bendrinamas nuorodas", - "show_slideshow_metadata_overlay": "Rodyti paveikslėlio informacijos juostą", "show_slideshow_transition": "Rodyti perėjimą tarp skaidriÅŗ", "show_supporter_badge": "Rėmėjo Åženklelis", "show_supporter_badge_description": "Rodyti rėmėjo Åženklelį", @@ -2017,25 +2159,18 @@ "sign_out": "Atsijungti", "sign_up": "UÅžsiregistruoti", "size": "Dydis", - "skip": "Praleisti", "skip_to_content": "Pereiti prie turinio", "skip_to_folders": "Praleisti iki aplankÅŗ", "skip_to_tags": "Praleisti iki ÅžymiÅŗ", "slideshow": "SkaidriÅŗ perÅžiÅĢra", - "slideshow_body": "AtsiloÅĄkite ir stebėkite savo nuotraukas per visą ekraną rodomoje skaidriÅŗ perÅžiÅĢroje.", - "slideshow_metadata_overlay_mode": "Info juostos turinys", - "slideshow_metadata_overlay_mode_description_only": "Tik apraÅĄymas", - "slideshow_metadata_overlay_mode_full": "Pilnas", "slideshow_repeat": "Kartoti skaidres", "slideshow_repeat_description": "Pradėti iÅĄ pradÅžiÅŗ, kai skaidrės baigiasi", "slideshow_settings": "SkaidriÅŗ perÅžiÅĢros nustatymai", - "slideshow_title": "SkaidriÅŗ perÅžiÅĢra", - "smart_album": "IÅĄmanus albumas", - "some_assets_already_have_a_location_warning": "Kai kurie pasirinkti elementai jau turi vietovę", "sort_albums_by": "Rikiuoti albumus pagal...", "sort_created": "SukÅĢrimo data", "sort_items": "ElementÅŗ skaičiÅŗ", "sort_modified": "Keitimo data", + "sort_newest": "Naujausia nuotrauka", "sort_oldest": "Seniausia nuotrauka", "sort_people_by_similarity": "Rikiuoti Åžmonės pagal panaÅĄumą", "sort_recent": "Naujausia nuotrauka", @@ -2044,6 +2179,7 @@ "stack": "Grupuoti", "stack_action_prompt": "{count} sugrupuota", "stack_duplicates": "Grupuoti dublikatus", + "stack_select_one_photo": "Pasirinkti pagrindinę grupės nuotrauką", "stack_selected_photos": "Grupuoti pasirinktas nuotraukas", "stacked_assets_count": "{count, plural, one {Sugrupuotas # elementas} few {Sugrupuoti # elementai} other {Sugrupuota # elementÅŗ}}", "stacktrace": "Stacktrace", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "PradÅžios data turi bÅĢti ankstesnė uÅž pabaigos datą", "state": "Valstija/Apskritis", "status": "Statusas", - "step_delete": "IÅĄtrinti Åžingsnį", - "step_delete_confirm": "Ar tikrai norite iÅĄtrinti ÅĄÄ¯ Åžingsnį?", - "step_details": "ÅŊingsnio detalės", - "steps": "ÅŊingsniai", "stop_casting": "Nutraukti transliavimą", "stop_motion_photo": "Sustabdyti Judančią Foto", "stop_photo_sharing": "Nustoti dalytis savo nuotraukomis?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Keisti sujungimo kryptį", "sync": "Sinchronizuoti", "sync_albums": "Sinchronizuoti albumus", + "sync_albums_manual_subtitle": "Sinchronizuoti visus įkeltus vaizdo įraÅĄus ir nuotraukas su pasirinktomis atsarginėmis kopijomis", "sync_local": "Sinchronizuoti vietinį", "sync_remote": "Sinchronizuoti nuotolinį", "sync_status": "Sinchronizacijos bÅĢklė", "sync_status_subtitle": "ÅŊiÅĢrėti ir valdyti sinchronizacijos systemą", "sync_upload_album_setting_subtitle": "Sukurti ir įkelti jÅĢsÅŗ nuotraukas ir vaizdo įraÅĄus į pasirinktus Immich albumus", - "system_theme": "Sistemos tema", - "system_theme_command_description": "Naudoti sistemos temą ({value})", "tag": "ÅŊyma", "tag_assets": "PaÅžymėti", "tag_created": "Sukurta Åžyma: {tag}", "tag_face": "PaÅžymėti veidą", "tag_feature_description": "PerÅžiÅĢrėkite nuotraukas ir vaizdo įraÅĄus sugrupuotus pagal suÅžymėtas temas", + "tag_not_found_question": "Nerandate Åžymos? Sukurti naują Åžymą.", "tag_people": "PaÅžymėti ÅŊmones", "tag_updated": "Atnaujinta Åžyma: {tag}", "tagged_assets": "ÅŊyma pridėta prie {count, plural, one {# elemento} other {# elementÅŗ}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ElementÅŗ per eilutę ({count})", "theme_setting_colorful_interface_subtitle": "Fono pavirÅĄiams uÅžtepkite pagrindinę spalvą.", "theme_setting_colorful_interface_title": "Spalvinga sąsaja", + "theme_setting_image_viewer_quality_subtitle": "Koreguoti detaliÅŗ vaizdÅŗ perÅžiÅĢros kokybę", + "theme_setting_image_viewer_quality_title": "Vaizdo perÅžiÅĢros priemonės kokybė", "theme_setting_primary_color_subtitle": "Pasirinkite spalvą pagrindiniams veiksmams ir akcentams.", "theme_setting_primary_color_title": "Pagrindinė spalva", "theme_setting_system_primary_color_title": "Naudoti sistemos spalvą", "theme_setting_system_theme_switch": "Automatinė (Naudoti sistemos nustatymus)", + "theme_setting_theme_subtitle": "Pasirinkite programos temos nustatymą", + "theme_setting_three_stage_loading_subtitle": "TrijÅŗ etapÅŗ įkėlimas gali padidinti įkėlimo naÅĄumą, tačiau sukelia Åžymiai didesnę tinklo apkrovą", + "theme_setting_three_stage_loading_title": "ÄŽjungti trijÅŗ etapÅŗ įkėlimą", "then": "Tada", "they_will_be_merged_together": "Jie bus sujungti kartu", "third_party_resources": "Trečios Å alies IÅĄtekliai", @@ -2131,17 +2268,23 @@ "trash_all": "Perkelti visus į ÅĄiukÅĄliadėŞę", "trash_count": "Perkelti {count, number} į ÅĄiukÅĄliadėŞę", "trash_delete_asset": "IÅĄmesti/IÅĄtrinti elementą", + "trash_emptied": "IÅĄvalytos ÅĄiukÅĄlės", "trash_no_results_message": "ÄŽ ÅĄiukÅĄliadėŞę perkeltos nuotraukos ir vaizdo įraÅĄai bus rodomi čia.", "trash_page_delete_all": "IÅĄtrinti Visus", + "trash_page_empty_trash_dialog_content": "Ar norite iÅĄtrinti iÅĄmestus elementus? Å ie elementai bus visam laikui paÅĄalinti iÅĄ Immich", "trash_page_info": "Å iukÅĄliadėŞės elementai bus galutinai iÅĄtrinti uÅž {days} dienÅŗ", + "trash_page_no_assets": "Nėra iÅĄmestÅŗ elementÅŗ", + "trash_page_restore_all": "Atkurti Visus", + "trash_page_select_assets_btn": "Pasirinkti elementus", + "trash_page_title": "Å iukÅĄliÅŗ ({count})", "trashed_items_will_be_permanently_deleted_after": "ÄŽ ÅĄiukÅĄliadėŞę perkelti elementai bus visam laikui iÅĄtrinti po {days, plural, one {# dienos} other {# dienÅŗ}}.", "trigger": "Trigeris", - "trigger_asset_metadata_extraction": "Elemento MetaduomenÅŗ IÅĄtraukimas", - "trigger_asset_metadata_extraction_description": "Suaktyvinama, kai iÅĄgaunami elemento EXIF metaduomenys", - "trigger_asset_uploaded": "Elemento IÅĄsiuntimas", + "trigger_asset_uploaded": "Elementas IÅĄsiÅŗstas", "trigger_asset_uploaded_description": "Pradės, kai naujas elementas bus iÅĄsiÅŗstas", + "trigger_description": "ÄŽvykis, kuris pradeda darbo eigą", "trigger_person_recognized": "Asmuo AtpaÅžintas", - "trigger_person_recognized_description": "Suaktyvinama, kai asmuo yra atpaÅžintas", + "trigger_person_recognized_description": "Pradės, kai asmuo yra aptiktas", + "trigger_type": "Trigerio tipas", "troubleshoot": "Å alinti triktis", "type": "Tipas", "unable_to_change_pin_code": "Negalima pakeisti PIN kodo", @@ -2157,7 +2300,6 @@ "unknown": "NeÅžinoma", "unknown_country": "NeÅžinoma Å alis", "unknown_date": "NeÅžinoma data", - "unknown_schema": "NeÅžinoma schema", "unknown_year": "NeÅžinomi metai", "unlimited": "Neribota", "unlink_motion_video": "Atsieti video", @@ -2170,20 +2312,23 @@ "unsaved_change": "NeiÅĄsaugoti pakeitimai", "unselect_all": "AtÅĄaukti visÅŗ pasirinkimą", "unselect_all_duplicates": "AtÅžymėti visus dublikatus", + "unselect_all_in": "AtÅžymėti viską {group}", "unstack": "IÅĄgrupuoti", "unstack_action_prompt": "{count} iÅĄgrupuota", "unstacked_assets_count": "{count, plural, one {IÅĄgrupuotas # elementas} few {IÅĄgrupuoti # elementai} other {IÅĄgrupuota # elementÅŗ}}", "unsupported_field_type": "Nepalaikomas laukelio tipas", "unsupported_file_type": "Failas {file} negali bÅĢti iÅĄsiÅŗstas, nes failo tipas {type} yra nepalaikomas.", "untagged": "NepaÅžymėta", + "untitled_workflow": "Neįvardinta veikla", "up_next": "Seknatis", "update_location_action_prompt": "Atnaujinti {count} {count, plural, one {pasirinkto elemento} few {pasirinktÅŗ elementÅŗ} other {pasirinktÅŗ elementÅŗ}} vietovę naudojant:", "updated_at": "Atnaujintas", "updated_password": "SlaptaÅžodis atnaujintas", "upload": "ÄŽkelti", "upload_concurrency": "ÄŽkėlimo lygiagretumas", - "upload_day_count": "{date}: {count, plural, one {# iÅĄsiuntimas} few {# iÅĄsiuntimai} other {# iÅĄsiuntimÅŗ}}", "upload_details": "ÄŽkėlimo Detalės", + "upload_dialog_info": "Ar norite sukurti pasirinkto(-Åŗ) turinio(-Åŗ) atsarginę kopiją serveryje?", + "upload_dialog_title": "ÄŽkelti turinį", "upload_error_with_count": "IÅĄsiuntimo klaida {count, plural, one {# elementui} few {# elementams} other {# elementÅŗ}}", "upload_errors": "ÄŽkėlimas įvyko su {count, plural, one {# klaida} few {# klaidomis} other {# klaidÅŗ}}, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", "upload_finished": "IÅĄsiuntimas neuÅžbaigtas", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Klaidos", "upload_status_uploaded": "ÄŽkelta", "upload_success": "ÄŽkėlimas pavyko, norėdami pamatyti naujai įkeltus elementus perkraukite puslapį.", - "upload_to_album_body": "Nenaudojantiems rankinio įkėlimo: dabar galite pridėti vietines nuotraukas tiesiai į albumą jas įkeldami. Nebereikės įkelti nuotraukÅŗ ir tik vėliau jas priskirti albumui.", - "upload_to_album_title": "ÄŽkelti tiesiai į albumą", "upload_to_immich": "ÄŽkelti į Immich ({count})", "uploading": "ÄŽkeliama", "uploading_media": "ÄŽkeliama medija", - "uploads": "IÅĄsiuntimai", - "uploads_count": "{count, plural, one {# iÅĄsiuntimas} few {# iÅĄsiuntimai} other {# iÅĄsiuntimÅŗ}}", "url": "URL", "usage": "Naudojimas", "use_biometric": "Naudoti biometriją", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Formatuoti datas, laiką ir skaičius pagal jÅĢsÅŗ narÅĄyklės vietovę", "use_current_connection": "Naudoti dabartinį ryÅĄÄ¯", "use_custom_date_range": "Naudokite pasirinktinę dienÅŗ seką", - "use_template": "Naudoti ÅĄabloną", "user": "Naudotojas", "user_has_been_deleted": "Å is naudotojas buvo iÅĄtrintas.", "user_id": "Naudotojo ID", @@ -2217,6 +2357,7 @@ "user_privacy": "Vartotojo Privatumas", "user_purchase_settings": "ÄŽsigyti", "user_purchase_settings_description": "Pirkinio pasirinkimas", + "user_role_set": "Nustatyti {user}, kaip {role}", "user_usage_detail": "Vartotojo naudojimo detalės", "user_usage_stats": "Paskyros naudojimo statistika", "user_usage_stats_description": "ÅŊiÅĢrėti paskyros naudojimo statistiką", @@ -2226,6 +2367,7 @@ "utilities": "ÄŽrankiai", "validate": "Validuoti", "validate_endpoint_error": "PraÅĄome įvesti galiojantį URL", + "validation_error": "Patvirtinimo klaida", "variables": "Kintamieji", "version": "Versija", "version_announcement_closing": "Tavo draugas, Alex", @@ -2235,9 +2377,9 @@ "video": "Vaizdo įraÅĄas", "video_hover_setting": "Paleisti vaizdo įraÅĄo miniatiÅĢrą uÅžvedus pele", "video_hover_setting_description": "Atkurti vaizdo įraÅĄo miniatiÅĢrą, kai pelė uÅžvedama ant elemento. Net ir iÅĄjungus, atkÅĢrimą galima pradėti uÅžvedus pelės Åžymeklį ant atkÅĢrimo piktogramos.", - "video_quality": "Vaizdo įraÅĄo kokybė", "videos": "Video", "videos_count": "{count, plural, one {# vaizdo įraÅĄas} few {# vaizdo įraÅĄai} other {# vaizdo įraÅĄÅŗ}}", + "videos_only": "Tik Video", "view": "ÅŊiÅĢrėti", "view_album": "ÅŊiÅĢrėti albumą", "view_all": "PerÅžiÅĢrėti viską", @@ -2246,29 +2388,29 @@ "view_details": "ÅŊiÅĢrėti Detales", "view_in_timeline": "ÅŊiÅĢrėti laiko skalėje", "view_link": "ÅŊiÅĢrėti nuorodą", + "view_links": "ÅŊiÅĢrėti nuorodas", "view_name": "ÅŊiÅĢrėti", "view_next_asset": "ÅŊiÅĢrėti sekantį elementą", "view_previous_asset": "ÅŊiÅĢrėti ankstesnį elementą", "view_qr_code": "ÅŊiÅĢrėti QR kodą", "view_similar_photos": "ÅŊiÅĢrėti panaÅĄias foto", "view_stack": "PerÅžiÅĢrėti grupę", + "view_user": "ÅŊiÅĢrėti Vartotoją", "viewer_remove_from_stack": "PaÅĄalinti iÅĄ Grupės", + "viewer_stack_use_as_main_asset": "Naudoti, kaip pagrindinį elementą", + "viewer_unstack": "IÅĄgrupuoti", "visibility": "Matomumas", "visibility_changed": "Matomumas pasikeitė {count, plural, one {# asmeniui} few {# asmenims} other {# asmenÅŗ}}", "visual": "IÅĄdėstymas", + "visual_builder": "IÅĄdėstymo koreguotojas", "waiting": "Laukiama", "waiting_count": "Laukiama: {count}", "warning": "ÄŽspėjimas", "week": "Savaitė", "welcome": "Sveiki atvykę", "welcome_to_immich": "Sveiki atvykę į Immich", - "whats_new": "Kas naujo", - "whats_new_settings_subtitle": "SuÅžinokite, kas naujo Immich", - "whats_new_version": "Versija {version}", - "when": "Kada", "width": "Plotis", "wifi_name": "Wi-Fi Pavadinimas", - "workflow": "Darbo eiga", "workflow_delete_prompt": "Ar tikrai norite iÅĄtrinti ÅĄią darbÅŗ eigą?", "workflow_deleted": "DarbÅŗ eiga iÅĄtrinta", "workflow_description": "DarbÅŗ eigos apraÅĄymas", @@ -2278,17 +2420,17 @@ "workflow_name": "DarbÅŗ eigos pavadinimas", "workflow_navigation_prompt": "Ar norite iÅĄeiti neiÅĄsaugoję pakeitimÅŗ?", "workflow_summary": "DarbÅŗ eigos santrauka", - "workflow_templates": "DarbÅŗ eigos ÅĄablonai", "workflow_update_success": "DarbÅŗ eiga sėkmingai atnaujinta", + "workflow_updated": "DarbÅŗ eiga atnaujinta", "workflows": "DarbÅŗ eigos", "workflows_help_text": "Darbo eigos automatizuoja veiksmus su jÅĢsÅŗ elementais pagal trigerius ir filtrus", "wrong_pin_code": "Neteisingas PIN kodas", - "x_of_total": "{x}/{total}", "year": "Metai", "years_ago": "PrieÅĄ {years, plural, one {# metus} other {# metÅŗ}}", "yes": "Taip", "you_dont_have_any_shared_links": "Bendrinimo nuorodÅŗ neturite", "your_wifi_name": "JÅĢsÅŗ Wi-Fi pavadinimas", "zero_to_clear_rating": "spauskite 0, kad paÅĄalinti elemento įvertinimą", - "zoom_image": "Priartinti vaizdą" + "zoom_image": "Priartinti vaizdą", + "zoom_to_bounds": "Priartinti iki kraÅĄtÅŗ" } diff --git a/i18n/lv.json b/i18n/lv.json index f1620c9b1b..8ad03da2b8 100644 --- a/i18n/lv.json +++ b/i18n/lv.json @@ -10,36 +10,44 @@ "active": "AktÄĢvs", "active_count": "AktÄĢvi: {count}", "activity": "Aktivitāte", + "activity_changed": "Aktivitāte ir {enabled, select, true {iespējota} other {atspējota}}", "add": "Pievienot", "add_a_description": "Pievienot aprakstu", "add_a_location": "Pievienot atraÅĄanās vietu", "add_a_name": "Pievienot vārdu", "add_a_title": "Pievienot virsrakstu", "add_action": "Pievienot darbÄĢbu", + "add_action_description": "KlikÅĄÄˇini, lai pievienotu veicamo darbÄĢbu", "add_assets": "Pievienot failus", "add_birthday": "Pievienot dzimÅĄanas dienu", "add_endpoint": "Pievienot galapunktu", "add_exclusion_pattern": "Pievienot izslēgÅĄanas ÅĄablonu", + "add_filter": "Pievienot filtru", + "add_filter_description": "KlikÅĄÄˇini, lai pievienotu filtra nosacÄĢjumu", "add_location": "Pievienot lokāciju", + "add_more_users": "Pievienot vēl lietotājus", "add_partner": "Pievienot partneri", + "add_path": "Pievienot ceÄŧu", "add_photos": "Pievienot fotoattēlus", - "add_step": "Pievienot soli", "add_tag": "Pievienot atzÄĢmi", "add_to": "Pievienotâ€Ļ", "add_to_album": "Pievienot albumam", "add_to_album_bottom_sheet_added": "Pievienots {album}", "add_to_album_bottom_sheet_already_exists": "Jau pievienots {album}", "add_to_album_bottom_sheet_some_local_assets": "DaÅžus lokālos failus albumam nevarēja pievienot", + "add_to_album_toggle": "Pārslēgt izvēli {album}", "add_to_albums": "Pievienot albumiem", "add_to_albums_count": "Pievienot albumiem ({count})", "add_to_bottom_bar": "Pievienot", + "add_to_shared_album": "Pievienot koplietotam albumam", "add_upload_to_stack": "Pievienot augÅĄupielādi kaudzei", "add_url": "Pievienot URL", + "add_workflow_step": "Pievienot darba plÅĢsmas soli", "added_to_archive": "Pievienots arhÄĢvam", "added_to_favorites": "Pievienots izlasei", "added_to_favorites_count": "{count, number} pievienoti izlasei", "admin": { - "add_exclusion_pattern_description": "Pievieno izslēgÅĄanas ÅĄablonus. Tiek atbalstÄĢta aizstājējzÄĢmju *, **, un ? izmantoÅĄana. Lai ignorētu visus failus jebkurā direktorijā ar nosaukumu “Raw”, izmanto “**/Raw/**”. Lai ignorētu visus failus, kas beidzas ar “. tif”, izmanto “**/*. tif”. Lai ignorētu absolÅĢto ceÄŧu, izmanto “/kāds/ignorējamais/ceÄŧÅĄ/**”.", + "add_exclusion_pattern_description": "Pievieno izslēgÅĄanas ÅĄablonus. Tiek atbalstÄĢta aizstājējzÄĢmju *, **, un ? izmantoÅĄana. Lai ignorētu visus failus jebkurā direktorijā ar nosaukumu “RAW”, izmanto “**/RAW/**”. Lai ignorētu visus failus, kas beidzas ar “. tif”, izmanto “**/*. tif”. Lai ignorētu absolÅĢto ceÄŧu, izmanto “/kāds/ignorējamais/ceÄŧÅĄ/**”.", "admin_user": "Administrators", "asset_offline_description": "Å is ārējās bibliotēkas resurss vairs nav atrodams diskā un ir pārvietots uz atkritni. Ja fails tika pārvietots bibliotēkas ietvaros, pārbaudiet, vai jÅĢsu laika skalā ir jauns atbilstoÅĄais resurss. Lai atjaunotu ÅĄo resursu, pārliecinieties, vai Immich var piekÄŧÅĢt tālāk norādÄĢtajam faila ceÄŧam un uzsāc bibliotēkas skenÄ“ÅĄanu.", "authentication_settings": "Autentifikācijas iestatÄĢjumi", @@ -58,7 +66,7 @@ "backup_onboarding_parts_title": "3-2-1 rezerves kopija ietver:", "backup_onboarding_title": "Rezerves kopijas", "backup_settings": "Datubāzes izrakstu iestatÄĢjumi", - "backup_settings_description": "Datubāzes izrakstu iestatÄĢjumu pārvaldÄĢba.", + "backup_settings_description": "Datubāzes izrakstu iestatÄĢjumu pārvaldÄĢba", "cleared_jobs": "NotÄĢrÄĢti uzdevumi priekÅĄ: {job}", "config_set_by_file": "Konfigurāciju paÅĄlaik iestata konfigurācijas fails", "confirm_delete_library": "Vai tieÅĄÄm vēlaties dzēst {library} bibliotēku?", @@ -73,7 +81,6 @@ "cron_expression_description": "Iestatiet skenÄ“ÅĄanas intervālu, izmantojot cron formātu. Papildu informācijai skatiet, piemēram, Crontab Guru", "cron_expression_presets": "Cron izteiksmju sagataves", "disable_login": "Atspējot pieteikÅĄanos", - "download_csv": "Lejupielādēt CSV", "duplicate_detection_job_description": "Analizēt failus ar maÅĄÄĢnmācÄĢÅĄanos, lai noteiktu lÄĢdzÄĢgus attēlus. Å ÄĢ funkcija izmanto viedo meklÄ“ÅĄanu", "exclusion_pattern_description": "IzslēgÅĄanas ÅĄabloni Äŧauj ignorēt failus un mapes, skenējot bibliotēku. Tas ir noderÄĢgi, ja jums ir mapes, kas satur failus, kurus nevēlaties importēt, piemēram, RAW failus.", "export_config_as_json_description": "Lejupielādēt paÅĄreizējo sistēmas konfigurāciju kā JSON failu", @@ -113,15 +120,14 @@ "job_not_concurrency_safe": "Å is uzdevums nav droÅĄs vienlaicÄĢgai izpildei.", "job_settings": "Uzdevumu iestatÄĢjumi", "job_settings_description": "Uzdevumu izpildes vienlaicÄĢguma pārvaldÄĢba", - "jobs_delayed": "{jobCount, plural, other {# aizkavēti}}", - "jobs_failed": "{jobCount, plural, other {# neizdevās}}", + "jobs_delayed": "{jobCount, plural, other {# delayed}}", "jobs_over_time": "Uzdevumi laika gaitā", "library_created": "Izveidoja bibliotēku: {library}", "library_deleted": "Bibliotēka dzēsta", "library_details": "Bibliotēkas dati", "library_folder_description": "Norādi importējamo mapi. Lai meklētu attēlus un videoklipus, tiks pārbaudÄĢta ÅĄÄĢ mape un tās apakÅĄmapes.", "library_remove_exclusion_pattern_prompt": "Vai tieÅĄÄm vēlaties noņemt ÅĄo izslēgÅĄanas modeli?", - "library_remove_folder_prompt": "Vai tieÅĄÄm vēlies noņemt ÅĄo importÄ“ÅĄanas mapi?", + "library_remove_folder_prompt": "Vai tieÅĄÄm vēlies dzēst ÅĄo importa failu?", "library_scanning": "Periodiska skenÄ“ÅĄana", "library_scanning_description": "Konfigurē periodisku bibliotēku skenÄ“ÅĄanu", "library_scanning_enable_description": "Iespējot periodisku bibliotēku skenÄ“ÅĄanu", @@ -186,7 +192,6 @@ "maintenance_delete_backup": "Dzēst rezerves kopiju", "maintenance_delete_backup_description": "Å is fails tiks neatgriezeniski dzēsts.", "maintenance_delete_error": "Neizdevās dzēst rezerves kopiju.", - "maintenance_integrity_report": "Integritātes pārbaude", "maintenance_restore_backup": "Atjaunot no rezerves kopijas", "maintenance_restore_backup_description": "Immich dati tiks dzēsti un atjaunoti no izvēlētā dublējuma. Tiks izveidots dublējums pirms turpinājuma.", "maintenance_restore_backup_different_version": "Å ÄĢ rezerves kopija tika izveidota ar citu Immich versiju!", @@ -261,8 +266,6 @@ "notification_enable_email_notifications": "Iespējot e-pasta paziņojumus", "notification_settings": "Paziņojumu iestatÄĢjumi", "notification_settings_description": "Paziņojumu iestatÄĢjumu, tostarp e-pasta, pārvaldÄĢba", - "oauth_allow_insecure_requests": "AtÄŧaut nedroÅĄus pieprasÄĢjumus", - "oauth_allow_insecure_requests_description": "UZMANÄĒBU: Å is atspējo TLS sertifikāta validāciju priekÅĄ OAuth pieprasÄĢjumiem un var jÅĢs pakÄŧaut MITM uzbrukumiem.", "oauth_auto_launch": "Palaist automātiski", "oauth_auto_launch_description": "Pie navigācijas uz pieslēgÅĄanās lapu automātiski uzsākt OAuth pieslēgÅĄanās plÅĢsmu", "oauth_auto_register": "Automātiska reÄŖistrācija", @@ -270,11 +273,9 @@ "oauth_button_text": "Pogas teksts", "oauth_client_secret_description": "NepiecieÅĄams konfidenciālam klientam vai ja PKCE (Proof Key for Code Exchange) netiek atbalstÄĢta publiskajam klientam.", "oauth_enable_description": "Pieslēgties ar OAuth", - "oauth_end_session_url_description": "NovirzÄĢt lietotāju uz ÅĄo URI, kad viņi izrakstās.", "oauth_mobile_redirect_uri": "Mobilās pāradresÄ“ÅĄanas URI", "oauth_mobile_redirect_uri_override": "Mobilās pāradresÄ“ÅĄanas URI pārrakstÄĢÅĄana", "oauth_mobile_redirect_uri_override_description": "Jāiespējo, ja OAuth pakalpojuma sniedzējs nepieÄŧauj mobilo URI, piemēram, \"{callback}\"", - "oauth_prompt_description": "Uzvednes parametrs (piem., select_account, login, consent)", "oauth_role_claim": "Lomas pieteikums", "oauth_role_claim_description": "Automātiski pieÅĄÄˇirt administratora piekÄŧuvi, pamatojoties uz ÅĄÄĢs prasÄĢbas klātbÅĢtni. PrasÄĢba var bÅĢt vai nu \"user\", vai \"admin\".", "oauth_settings": "OAuth", @@ -332,7 +333,7 @@ "storage_template_migration_info": "Krātuves veidne pārveidos visus failu paplaÅĄinājumus uz mazajiem burtiem. Veidnes izmaiņas attieksies tikai uz jauniem failiem. Lai veidni piemērotu ar atpakaÄŧejoÅĄu efektu iepriekÅĄ augÅĄupielādētiem failiem, palaidiet {job}.", "storage_template_migration_job": "Krātuves veidņu migrācijas uzdevumu", "storage_template_more_details": "PlaÅĄÄku informāciju par ÅĄo funkcionalitāti skatÄĢt sadaÄŧā Krātuves veidne un tās sekas", - "storage_template_onboarding_description_v2": "Kad iespējots, ÅĄÄĢ funkcija automātiski organizēs failus ņemot vērā lietotāja doto veidni. Lai saņemtu vairāk informāciju, lÅĢdzu apskatiet dokumentāciju.", + "storage_template_onboarding_description_v2": "Kad iespējots, ÅĄÄĢ funkcija automātiski organizēs failus ņemot vērā lietotāja doto veidni. Lai saņemtu vairāk informāciju, lÅĢdzu apskatiet dokumentāciju.", "storage_template_path_length": "Aptuvenais ceÄŧa garuma ierobeÅžojums: {length, number}/{limit, number}", "storage_template_settings": "Krātuves veidne", "storage_template_settings_description": "PārvaldÄĢt augÅĄupielādēto failu mapju struktÅĢru un faila nosaukumu", @@ -380,53 +381,17 @@ "transcoding_encoding_options_description": "UzstādÄĢt kodeksus, izÅĄÄˇirtspēju, kvalitāti un citas opcijas kodētiem videoklipiem", "transcoding_hardware_acceleration": "AparatÅĢras paātrinājums", "transcoding_hardware_acceleration_description": "Eksperimentāli: ātrāka transkodÄ“ÅĄana, bet var samazināt kvalitāti tādā paÅĄÄ bitu pārraides ātrumā", - "transcoding_hardware_decoding": "AparatÅĢras dekodÄ“ÅĄana", - "transcoding_hardware_decoding_setting_description": "Iespējo pilnÄĢgu paātrinājumu, nevis tikai paātrina iekodÄ“ÅĄanu. Var nedarboties visiem videoklipiem.", - "transcoding_max_b_frames": "Maksimālais B kadru skaits", - "transcoding_max_b_frames_description": "Augstākas vērtÄĢbas uzlabo kompresijas efektivitāti, bet palēlina iekodÄ“ÅĄanu. Var nebÅĢt saderÄĢgs ar aparatÅĢras paātrinājumu uz vecākām ierÄĢcēm. 0 atspējo B kadrējumus, bet -1 pieÅĄÄˇir ÅĄo vērtÄĢbu automātiski.", - "transcoding_max_bitrate": "Maksimālais bitu pārraides ātrums", - "transcoding_max_bitrate_description": "Maksimālā bitu pārraides ātruma iestatÄĢÅĄana var padarÄĢt failu lielumus paredzamākus ar nelielām kvalitātes izmaiņām. Pie 720p tipiskās vērtÄĢbas ir 2600 kbit/s VP9 vai HEVC vai 4500 kbit/s H.264. Atspējots, ja iestatÄĢts kā 0. Ja nav norādÄĢta vienÄĢba, tiek pieņemta k (kbit/s) vērtÄĢba; tāpēc 5000, 5000k un 5M (Mbit/s) ir ekvivalenti.", - "transcoding_max_keyframe_interval": "Maksimālais atslēgkadru intervāls", - "transcoding_max_keyframe_interval_description": "Iestata maksimālo kadra attālumu starp atslēgkadriem. Zemākas vērtÄĢbas pasliktina saspieÅĄanas efektivitāti, bet uzlabo meklÄ“ÅĄanas laiku un var uzlabot kvalitāti ainās ar ātru kustÄĢbu. 0 iestata ÅĄo vērtÄĢbu automātiski.", - "transcoding_optimal_description": "Videoklipi, kas pārsniedz mērġa izÅĄÄˇirtspēju vai nav akceptētā formātā", - "transcoding_policy": "TranskodÄ“ÅĄanas Politika", - "transcoding_policy_description": "IestatÄĢt, kad video tiks transkodēts", - "transcoding_preferred_hardware_device": "Vēlamā aparatÅĢras ierÄĢce", - "transcoding_preferred_hardware_device_description": "Attiecas tikai uz VAAPI un QSV. Iestata aparatÅĢras pārkodÄ“ÅĄanai izmantoto dri mezglu.", - "transcoding_preset_preset": "IepriekÅĄ iestatÄĢts (-preset)", - "transcoding_preset_preset_description": "SaspieÅĄanas ātrums. Lēnāki sākotnējie iestatÄĢjumi rada mazākus failus un palielina kvalitāti, izmantojot noteiktu bitu pārraides ātrumu. VP9 ignorē ātrumu virs \"ātrāks\".", - "transcoding_reference_frames": "References kadri", - "transcoding_reference_frames_description": "Kadru skaits, uz kuru jāatsaucas, saspieÅžot konkrētu kadru. Augstākas vērtÄĢbas uzlabo saspieÅĄanas efektivitāti, bet palēnina iekodÄ“ÅĄanu. 0 iestata ÅĄo vērtÄĢbu automātiski.", + "transcoding_hardware_decoding": "AparatÅĢras (HW) kodÄ“ÅĄana", "transcoding_required_description": "Tikai video, kas nav atbalstÄĢtā formātā", "transcoding_settings": "Video transkodÄ“ÅĄanas iestatÄĢjumi", - "transcoding_settings_description": "PārvaldÄĢt, kurus videoklipus transkodēt un kā tos apstrādāt", - "transcoding_target_resolution": "Mērġa izÅĄÄˇirtspēja", - "transcoding_target_resolution_description": "Augstāka izÅĄÄˇirtspēja var saglabāt vairāk detaÄŧu, bet iekodÄ“ÅĄana aizņem ilgāku laiku, ir lielāki failu izmēri un var samazināt programmas reaÄŖÄ“tspēju.", - "transcoding_temporal_aq": "Laika AQ", - "transcoding_temporal_aq_description": "Attiecas tikai uz NVENC. Laika adaptÄĢvā kvantÄ“ÅĄana uzlabo augstas detalizācijas un vājas kustÄĢbas ainu kvalitāti. Var nebÅĢt saderÄĢga ar vecākām ierÄĢcēm.", "transcoding_threads": "Pavedieni", "transcoding_threads_description": "Augstākas vērtÄĢbas nodroÅĄina ātrāku kodÄ“ÅĄanu, bet atstāj mazāk jaudas serverim, lai apstrādātu citus aktÄĢvos uzdevumus. Å ai vērtÄĢbai nevajadzētu pārsniegt CPU kodolu skaitu. Ja iestatÄĢta uz 0, maksimizē izmantoÅĄanu.", - "transcoding_tone_mapping": "Toņu kartÄ“ÅĄana", - "transcoding_tone_mapping_description": "MēĪina saglabāt HDR videoklipa izskatu, konvertējot uz SDR. Katrs algoritms veic atÅĄÄˇirÄĢgus kompromisus attiecÄĢbā uz krāsu, detaÄŧām un spilgtumu. Hable saglabā detaÄŧas, Mobius saglabā krāsu, bet Reinhard saglabā spilgtumu.", - "transcoding_transcode_policy": "TranskodÄ“ÅĄanas politika", - "transcoding_transcode_policy_description": "Politika, kad videoklips ir jāpārkodē. HDR videoklipi un videoklipi, kuru pikseÄŧu formāts nav YUV 4:2:0, vienmēr tiks pārkodēti (izņemot gadÄĢjumus, kad pārkodÄ“ÅĄana ir atspējota).", - "transcoding_two_pass_encoding": "Divu kārtu kodÄ“ÅĄana", - "transcoding_two_pass_encoding_setting_description": "Pārkodējiet divās kārtās, lai iegÅĢtu labāk kodētus videoklipus. Ja ir iespējots maksimālais bitu pārraides ātrums (nepiecieÅĄams, lai tas darbotos ar H.264 un HEVC), ÅĄis reÅžÄĢms izmanto bitu pārraides diapazonu, pamatojoties uz maksimālo bitu pārraides ātrumu, un ignorē CRF. PriekÅĄ VP9, CRF var tikts izmantots, ja maksimālais bitu pārraides ātrums ir atspējots.", "transcoding_video_codec": "Video kodeks", - "transcoding_video_codec_description": "VP9 ir augsta efektivitāte un tÄĢmekÄŧa saderÄĢba, bet transkodÄ“ÅĄana prasa ilgāku laiku. HEVC darbojas lÄĢdzÄĢgi, bet tam ir zemāka tÄĢmekÄŧa saderÄĢba. H.264 ir plaÅĄi saderÄĢgs un ātri pārkodējams, bet rada daudz lielākus failus. AV1 ir visefektÄĢvākais kodeks, bet tam trÅĢkst atbalsta vecākās ierÄĢcēs.", - "trash_enabled_description": "Iespējot Atkritumu funkcijas", "trash_number_of_days": "Dienu skaits", - "trash_number_of_days_description": "Dienu skaits, kas jāglabā atkritnē pirms to neatgriezeniskas izņemÅĄanas", "trash_settings": "Atkritnes iestatÄĢjumi", "trash_settings_description": "Atkritnes iestatÄĢjumu pārvaldÄĢba", - "unlink_all_oauth_accounts": "Visu OAuth kontu atsaistÄĢÅĄana", - "unlink_all_oauth_accounts_description": "Neaizmirstiet atsaistÄĢt visus OAuth kontus pirms migrācijas uz jaunu pakalpojumu sniedzēju.", - "unlink_all_oauth_accounts_prompt": "Vai tieÅĄÄm vēlaties atsaistÄĢt visus OAuth kontus? Tādējādi tiks atiestatÄĢts OAuth ID katram lietotājam, un to nevar atsaukt.", - "user_cleanup_job": "Lietotāju tÄĢrÄĢÅĄana", - "user_delete_delay": "{user}'s konta un lÄĢdzekÄŧu neatgriezeniska dzÄ“ÅĄana tiks ieplānota pēc {delay, plural, one {# day} other {# days}}.", "user_delete_delay_settings": "DzÄ“ÅĄanas aizture", "user_delete_delay_settings_description": "Dienu skaits pēc izdzÄ“ÅĄanas, kad neatgriezeniski tiks dzēsti lietotāja konti un faili. Lietotāju dzÄ“ÅĄanas uzdevums tiek izpildÄĢts pusnaktÄĢ un pārbauda, kuri lietotāji ir gatavi dzÄ“ÅĄanai. Izmaiņas ÅĄajā iestatÄĢjumā tiks ņemtas vērā nākamajā izpildes reizē.", - "user_delete_immediately": "{user}'s konts un lÄĢdzekÄŧi tiks nekavējoties ievietoti rindā neatgriezeniskai dzÄ“ÅĄanai.", "user_delete_immediately_checkbox": "Ierindot lietotāju un failus tÅĢlÄĢtējai dzÄ“ÅĄanai", "user_details": "Lietotāja informācija", "user_management": "Lietotāju pārvaldÄĢba", @@ -436,14 +401,10 @@ "user_restore_scheduled_removal": "Atjaunot lietotāju - plānotā dzÄ“ÅĄana {date, date, long}", "user_settings": "Lietotāja iestatÄĢjumi", "user_settings_description": "Lietotāju iestatÄĢjumu pārvaldÄĢba", - "user_successfully_removed": "Lietotājs {email} ir veiksmÄĢgi noņemts.", - "users_page_description": "Administratora lietotāju lapa", "version_check_enabled_description": "Ieslēgt versijas pārbaudi", "version_check_implications": "Versiju pārbaudes funkcija ir atkarÄĢga no periodiskas saziņas ar {server}", "version_check_settings": "Versijas pārbaude", - "version_check_settings_description": "Ieslēgt/izslēgt paziņojumus par jaunu versiju", - "video_conversion_job": "Videoklipu pārkodÄ“ÅĄana", - "video_conversion_job_description": "Pārkodējiet videoklipus plaÅĄÄkai saderÄĢbai ar pārlÅĢkprogrammām un ierÄĢcēm" + "version_check_settings_description": "Ieslēgt/izslēgt paziņojumus par jaunu versiju" }, "admin_email": "Administratora e-pasts", "admin_password": "Administratora parole", @@ -455,12 +416,9 @@ "advanced_settings_log_level_title": "ÅŊurnalÄ“ÅĄanas lÄĢmenis: {level}", "advanced_settings_prefer_remote_subtitle": "DaŞās ierÄĢcēs sÄĢktēli no ierÄĢces atmiņas ielādējas Äŧoti lēni. Aktivizējiet ÅĄo iestatÄĢjumu, lai tā vietā ielādētu attālus attēlus.", "advanced_settings_prefer_remote_title": "Dot priekÅĄroku attāliem attēliem", - "advanced_settings_proxy_headers_subtitle": "Starpniekservera galvenes definÄ“ÅĄana, kas Immich jānosÅĢta kopā ar katru tÄĢkla pieprasÄĢjumu", "advanced_settings_proxy_headers_title": "Pielāgotas starpniekservera galvenes [EKSPERIMENTĀLAS]", - "advanced_settings_readonly_mode_subtitle": "Iespējo tikai lasÄĢÅĄanas reÅžÄĢmu, kurā fotoattēlus var tikai apskatÄĢt, tādas lietas kā vairāku attēlu atlase, koplietoÅĄana, apraide, dzÄ“ÅĄana ir atspējotas. Iespējot / atspējot tikai lasāmo, izmantojot lietotāja bildi no galvenā ekrāna", - "advanced_settings_readonly_mode_title": "Tikai lasÄĢÅĄanas reÅžÄĢms", - "advanced_settings_sync_remote_deletions_subtitle": "Automātiska lÄĢdzekÄŧa dzÄ“ÅĄana vai atjaunoÅĄana ÅĄajā ierÄĢcē, kad ÅĄÄĢ darbÄĢba tiek veikta tÄĢmeklÄĢ", - "advanced_settings_sync_remote_deletions_title": "Attālās dzÄ“ÅĄanas sinhronizÄ“ÅĄana [EKSPERIMENTĀLS]", + "advanced_settings_self_signed_ssl_subtitle": "IzlaiÅž servera galapunkta SSL sertifikātu verifikāciju. NepiecieÅĄams paÅĄparakstÄĢtajiem sertifikātiem.", + "advanced_settings_self_signed_ssl_title": "AtÄŧaut paÅĄparakstÄĢtus SSL sertifikātus [EKSPERIMENTĀLI]", "advanced_settings_tile_subtitle": "Lietotāja papildu iestatÄĢjumi", "advanced_settings_troubleshooting_subtitle": "Iespējot papildu aktÄĢvus problēmu novērÅĄanai", "advanced_settings_troubleshooting_title": "Problēmas novērÅĄana", @@ -471,25 +429,29 @@ "album_added": "Albums pievienots", "album_added_notification_setting_description": "Saņemt e-pasta paziņojumu, kad tevi pievieno kopÄĢgam albumam", "album_cover_updated": "Albuma vāciÅ†ÅĄ atjaunināts", - "album_delete_confirmation": "Vai tieÅĄÄm vēlaties izdzēst albumu {album}?", "album_delete_confirmation_description": "Ja ÅĄis albums tiek kopÄĢgots, citi lietotāji vairs nevarēs tam piekÄŧÅĢt.", "album_deleted": "Albums dzēsts", + "album_info_card_backup_album_excluded": "NEIEKÄģAUTS", + "album_info_card_backup_album_included": "IEKÄģAUTS", "album_info_updated": "Albuma informācija atjaunināta", + "album_leave": "Pamest albumu?", "album_name": "Albuma nosaukums", - "album_options": "Albuma opcijas", "album_remove_user": "Noņemt lietotāju?", - "album_remove_user_confirmation": "Vai tieÅĄÄm vēlaties noņemt {user}?", - "album_search_not_found": "Nav atrasts neviens albums, kas atbilstu jÅĢsu meklÄ“ÅĄanas kritērijiem", - "album_share_no_users": "Izskatās, ka esat kopÄĢgojis ÅĄo albumu ar visiem lietotājiem vai jums nav neviena lietotāja, ar kuru kopÄĢgot.", + "album_selected": "Albums izvēlēts", "album_summary": "Albuma kopsavilkums", "album_updated": "Albums atjaunināts", - "album_updated_setting_description": "E-pasta paziņojuma saņemÅĄana, ja koplietotam albumam ir jauni lÄĢdzekÄŧi", "album_upload_assets": "AugÅĄupielādē failus no sava datora un pievieno tos albumam", + "album_user_left": "Pameta {album}", + "album_user_removed": "Noņēma {user}", + "album_viewer_appbar_delete_confirm": "Vai tieÅĄÄm vēlaties dzēst ÅĄo albumu no sava konta?", "album_viewer_appbar_share_err_delete": "Neizdevās izdzēst albumu", + "album_viewer_appbar_share_err_leave": "Neizdevās pamest albumu", + "album_viewer_appbar_share_err_remove": "Ir problēmas ar aktÄĢvu noņemÅĄanu no albuma", + "album_viewer_appbar_share_err_title": "Neizdevās mainÄĢt albuma nosaukumu", + "album_viewer_appbar_share_leave": "Pamest albumu", + "album_viewer_appbar_share_to": "KopÄĢgot Uz", "album_viewer_page_share_add_users": "Pievienot lietotājus", - "album_with_link_access": "Äģaujiet ikvienam, kam ir saite, skatÄĢt fotoattēlus un personas ÅĄajā albumā.", "albums": "Albumi", - "albums_count": "{count, plural, one {{count, number} Albums} other {{count, number} Albumi}}", "albums_default_sort_order": "Albuma noklusējuma kārtoÅĄanas secÄĢba", "albums_default_sort_order_description": "Sākotnējā failu kārtoÅĄanas secÄĢba, veidojot jaunus albumus.", "albums_feature_description": "Failu kolekcijas, kuras var koplietot ar citiem lietotājiem.", @@ -500,19 +462,20 @@ "all_photos": "Visas fotogrāfijas", "all_videos": "Visi video", "allow_dark_mode": "AtÄŧaut tumÅĄo reÅžÄĢmu", + "allow_edits": "AtÄŧaut laboÅĄanu", "allow_public_user_to_download": "AtÄŧaut lejupielādēt publiskiem lietotājiem", "allow_public_user_to_upload": "AtÄŧaut augÅĄupielādēt publiskiem lietotājiem", - "allowed": "AtÄŧauts", "alt_text_qr_code": "QR koda attēls", "always_keep": "Vienmēr paturēt", "always_keep_photos_hint": "Vietas atbrÄĢvoÅĄanas funkcija paturēs visas fotogrāfijas ÅĄajā ierÄĢcē.", "always_keep_videos_hint": "Vietas atbrÄĢvoÅĄanas funkcija paturēs visus video ÅĄajā ierÄĢcē.", + "anti_clockwise": "Pretēji pulksteņrādÄĢtāja virzienam", "api_key": "API atslēga", "api_key_description": "Å ÄĢ vērtÄĢba tiks parādÄĢta tikai vienu reizi. Nokopējiet to pirms loga aizvērÅĄanas.", - "api_key_empty": "API atslēgas nosaukums nedrÄĢkst bÅĢt tukÅĄs", "api_keys": "API atslēgas", "app_architecture_variant": "Variants (arhitektÅĢra)", "app_bar_signout_dialog_content": "Vai tieÅĄÄm vēlaties izrakstÄĢties?", + "app_bar_signout_dialog_ok": "Jā", "app_bar_signout_dialog_title": "IzrakstÄĢties", "app_download_links": "Lietotņu lejupielādes saites", "app_settings": "Lietotnes iestatÄĢjumi", @@ -521,105 +484,137 @@ "appears_in": "Parādās iekÅĄ", "apply_count": "Pielietot ({count, number})", "archive": "ArhÄĢvs", - "archive_action_prompt": "{count} pievienots arhÄĢvam", - "archive_or_unarchive_photo": "Fotoattēlu arhivÄ“ÅĄana vai atarhivÄ“ÅĄana", + "archive_page_no_archived_assets": "Nav atrasts neviens arhivēts aktÄĢvs", + "archive_page_title": "ArhÄĢvs ({count})", "archive_size": "ArhÄĢva izmērs", - "archive_size_description": "Lejupielādes arhÄĢva lieluma konfigurÄ“ÅĄana (GiB)", "archived": "Arhivēts", - "archived_count": "{count, plural, other {Arhivēti #}}", "are_these_the_same_person": "Vai ÅĄÄĢ ir tā pati persona?", - "are_you_sure_to_do_this": "Vai tieÅĄÄm vēlaties to darÄĢt?", + "array_field_not_fully_supported": "MasÄĢva lauki prasa manuālu JSON rediÄŖÄ“ÅĄanu", + "asset_action_delete_err_read_only": "Nevar dzēst read only aktÄĢvu(-s), notiek izlaiÅĄana", + "asset_action_share_err_offline": "Nevar iegÅĢt bezsaistes aktÄĢvu(-s), notiek izlaiÅĄana", "asset_added_to_album": "Pievienots albumam", "asset_adding_to_album": "Pievieno albumamâ€Ļ", - "asset_created": "Izveidots lÄĢdzeklis", "asset_description_updated": "Faila apraksts ir atjaunināts", "asset_hashing": "Veido jaucējvērtÄĢbasâ€Ļ", "asset_list_group_by_sub_title": "Grupēt pēc", + "asset_list_layout_settings_dynamic_layout_title": "Dinamiskais izkārtojums", + "asset_list_layout_settings_group_automatically": "Automātiski", "asset_list_layout_settings_group_by": "Grupēt failus pēc", "asset_list_layout_settings_group_by_month_day": "Mēnesis + diena", "asset_list_layout_sub_title": "Izvietojums", "asset_list_settings_subtitle": "FotoreÅžÄŖa izkārtojuma iestatÄĢjumi", "asset_list_settings_title": "FotoreÅžÄŖis", - "asset_not_found_on_device_android": "LÄĢdzeklis ierÄĢcē nav atrasts", - "asset_not_found_on_device_ios": "LÄĢdzeklis ierÄĢcē nav atrasts. Ja izmantojat iCloud, lÄĢdzeklis var nebÅĢt pieejams, jo pakalpojumā iCloud glabāts saplÄĢsis fails", - "asset_not_found_on_icloud": "LÄĢdzeklis nav atrasts pakalpojumā iCloud. LÄĢdzeklis var nebÅĢt pieejams, jo pakalpojumā iCloud glabāts saplÄĢsis fails", - "asset_offline": "LÄĢdzeklis bezsaistē", - "asset_offline_description": "Å is ārējais lÄĢdzeklis vairs nav atrodams diskā. LÅĢdzu, sazinieties ar Immich administratoru, lai saņemtu palÄĢdzÄĢbu.", "asset_skipped": "Izlaists", "asset_skipped_in_trash": "Atkritnē", - "asset_troubleshoot": "LÄĢdzekÄŧu problēmu novērÅĄana", "asset_uploaded": "AugÅĄupielādēts", "asset_uploading": "AugÅĄupielādēâ€Ļ", - "asset_viewer_settings_subtitle": "Pārvaldiet jÅĢsu galerijas skatÄĢtāja iestatÄĢjumus", "asset_viewer_settings_title": "Failu skatÄĢtājs", "assets": "Faili", + "assets_added_count": "Pievienoja {count, plural, one {# failu} other {# failus}}", "assets_added_to_album_count": "Pievienoja albumam {count, plural, one {# failu} other {# failus}}", - "assets_added_to_albums_count": "Pievienots {assetTotal, plural, one {# asset} other {# assets}} albumam {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Failu} other {Failus}} nevar pievienot albumam", - "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} nevar tikt pievienots nevienā no albumiem", - "assets_count": "{count, plural, one {# resurss} other {# resursi}}", + "assets_deleted_permanently_from_server": "{count} faili dzēsti no Immich servera", "assets_moved_to_trash_count": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz atkritni", - "assets_permanently_deleted_count": "Neatgriezeniski dzēsti {count, plural, one {# asset} other {# assets}}", "assets_removed_count": "Noņēma {count, plural, one {# failu} other {# failus}}", - "assets_restore_confirmation": "Vai tieÅĄÄm vēlaties atjaunot visus izmestos lÄĢdzekÄŧus? Å o darbÄĢbu nevar atsaukt! Ņemiet vērā, ka ÅĄÄdā veidā nevar atjaunot bezsaistes lÄĢdzekÄŧus.", - "assets_restored_count": "Atjaunots {count, plural, one {# asset} other {# assets}}", "assets_trashed": "{count} faili pārvietoti uz atkritni", - "assets_trashed_count": "Atkritumos iemesti {count, plural, one {# asset} other {# assets}}", - "assets_were_part_of_album_count": "{count, plural, one {LÄĢdzeklis} other {LÄĢdzekÄŧi}} jau bija daÄŧa no albuma", - "assets_were_part_of_albums_count": "{count, plural, one {LÄĢdzeklis} other {LÄĢdzekÄŧi}} jau bija daÄŧa no albumiem", + "assets_trashed_from_server": "{count} faili pārvietoti uz Immich servera atkritni", "authorized_devices": "Autorizētās ierÄĢces", - "automatic_endpoint_switching_subtitle": "Izveidojiet lokālu savienojumu, izmantojot norādÄĢto Wi-Fi, ja tas ir pieejams, un izmantojiet alternatÄĢvus savienojumus citur", "automatic_endpoint_switching_title": "Automātiska URL pārslēgÅĄana", "autoplay_slideshow": "Automātiska slaidrādes atskaņoÅĄana", "back": "AtpakaÄŧ", - "back_close_deselect": "AtpakaÄŧ, aizvērÅĄana vai atlases atcelÅĄana", - "background_location_permission": "AtraÅĄanās vietas fona atÄŧauja", - "background_location_permission_content": "Lai pārslēgtu tÄĢklus, darbojoties fonā, Immich * vienmēr * jābÅĢt precÄĢzai atraÅĄanās vietas piekÄŧuvei, lai lietotne varētu nolasÄĢt Wi-Fi tÄĢkla nosaukumu", + "background_backup_running_error": "PaÅĄlaik darbojas dublÄ“ÅĄana fonā, nevar uzsākt manuālu dublÄ“ÅĄanu", "background_options": "Fona opcijas", "backup": "DublÄ“ÅĄana", + "backup_album_selection_page_albums_device": "Albumi ierÄĢcē ({count})", "backup_album_selection_page_albums_tap": "Pieskarieties, lai iekÄŧautu, veiciet dubultskārienu, lai izslēgtu", "backup_album_selection_page_assets_scatter": "Faili var bÅĢt izmētāti pa vairākiem albumiem. Tādējādi dublÄ“ÅĄanas procesā albumus var iekÄŧaut vai neiekÄŧaut.", "backup_album_selection_page_select_albums": "AtlasÄĢt albumus", "backup_album_selection_page_selection_info": "Atlases informācija", - "backup_albums_sync": "Dublējuma albumu sinhronizācija", - "backup_background_service_complete_notification": "LÄĢdzekÄŧu dublÄ“ÅĄana pabeigta", + "backup_album_selection_page_total_assets": "Unikālo failu kopsumma", + "backup_albums_sync": "DublÄ“ÅĄanas albumu sinhronizācija", + "backup_all": "Viss", + "backup_background_service_backup_failed_message": "Neizdevās dublēt lÄĢdzekÄŧus. Notiek atkārtota mēĪinÄÅĄanaâ€Ļ", + "backup_background_service_connection_failed_message": "Neizdevās izveidot savienojumu ar serveri. Notiek atkārtota mēĪinÄÅĄanaâ€Ļ", + "backup_background_service_current_upload_notification": "Notiek {filename} augÅĄupielāde", "backup_background_service_default_notification": "Notiek jaunu aktÄĢvu meklÄ“ÅĄanaâ€Ļ", + "backup_background_service_error_title": "DublÄ“ÅĄanas kÄŧÅĢda", "backup_background_service_in_progress_notification": "Notiek aktÄĢvu dublÄ“ÅĄanaâ€Ļ", + "backup_background_service_upload_failure_notification": "Neizdevās augÅĄupielādēt {filename}", "backup_controller_page_albums": "Dublējuma Albumi", + "backup_controller_page_background_app_refresh_disabled_content": "Iespējojiet fona aplikācijas atsvaidzinÄÅĄanu sadaÄŧā IestatÄĢjumi > VispārÄĢgi > Fona Aplikācijas AtsvaidzinÄÅĄana, lai izmantotu fona dublÄ“ÅĄanu.", + "backup_controller_page_background_app_refresh_disabled_title": "Fona aplikācijas atsvaidzinÄÅĄana atspējota", + "backup_controller_page_background_app_refresh_enable_button_text": "Doties uz iestatÄĢjumiem", "backup_controller_page_background_battery_info_link": "ParādÄĢt, kā", "backup_controller_page_background_battery_info_message": "Lai iegÅĢtu vislabāko fona dublÄ“ÅĄanas pieredzi, lÅĢdzu, atspējojiet visas akumulatora optimizācijas, kas ierobeÅžo Immich fona aktivitāti.\n\nTā kā katrai ierÄĢcei iestatÄĢjumi ir citādāki, lÅĢdzu, meklējiet nepiecieÅĄamo informāciju pie ierÄĢces raÅžotāja.", "backup_controller_page_background_battery_info_ok": "Labi", "backup_controller_page_background_battery_info_title": "Akumulatora optimizācija", + "backup_controller_page_background_charging": "Tikai uzlādes laikā", + "backup_controller_page_background_configure_error": "Neizdevās konfigurēt fona pakalpojumu", "backup_controller_page_background_delay": "Aizkavēt jaunu failu dublÄ“ÅĄanu: {duration}", + "backup_controller_page_background_description": "Ieslēdziet fona pakalpojumu, lai automātiski dublētu visus jaunos aktÄĢvus, neatverot programmu", + "backup_controller_page_background_is_off": "Automātiskā fona dublÄ“ÅĄana ir izslēgta", + "backup_controller_page_background_is_on": "Automātiskā fona dublÄ“ÅĄana ir ieslēgta", + "backup_controller_page_background_turn_off": "Izslēgt fona pakalpojumu", + "backup_controller_page_background_turn_on": "Ieslēgt fona pakalpojumu", + "backup_controller_page_background_wifi": "Tikai Wi-Fi tÄĢklā", "backup_controller_page_backup": "DublÄ“ÅĄana", "backup_controller_page_backup_selected": "AtlasÄĢts: ", "backup_controller_page_backup_sub": "Dublētie Fotoattēli un videoklipi", + "backup_controller_page_created": "Izveidots: {date}", + "backup_controller_page_desc_backup": "Ieslēdziet priekÅĄplāna dublÄ“ÅĄanu, lai, atverot programmu, serverÄĢ automātiski augÅĄupielādētu jaunus aktÄĢvus.", "backup_controller_page_excluded": "Izņemot: ", + "backup_controller_page_failed": "Neizdevās ({count})", + "backup_controller_page_filename": "Faila nosaukums: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Dublējuma Informācija", "backup_controller_page_none_selected": "Neviens nav atlasÄĢts", "backup_controller_page_remainder": "Atlikums", "backup_controller_page_remainder_sub": "AtlikuÅĄie fotoattēli un videoklipi, kurus dublēt no atlases", "backup_controller_page_server_storage": "Servera krātuve", + "backup_controller_page_start_backup": "Sākt dublÄ“ÅĄanu", + "backup_controller_page_status_off": "Automātiskā priekÅĄplāna dublÄ“ÅĄana ir izslēgta", + "backup_controller_page_status_on": "Automātiskā priekÅĄplāna dublÄ“ÅĄana ir ieslēgta", "backup_controller_page_storage_format": "{used} no {total} tiek izmantots", "backup_controller_page_to_backup": "Dublējamie albumi", "backup_controller_page_total_sub": "Visi unikālie fotoattēli un videoklipi no izvēlētajiem albumiem", + "backup_controller_page_turn_off": "Izslēgt priekÅĄplāna dublÄ“ÅĄanu", + "backup_controller_page_turn_on": "Ieslēgt priekÅĄplāna dublÄ“ÅĄanu", + "backup_controller_page_uploading_file_info": "Faila informācijas augÅĄupielāde", + "backup_err_only_album": "Nevar noņemt vienÄĢgo albumu", "backup_error_sync_failed": "Sinhronizācija neizdevās. Nevar apstrādāt rezerves kopiju.", "backup_info_card_assets": "faili", - "backup_options": "DublÄ“ÅĄanas opcijas", + "backup_manual_cancelled": "Atcelts", + "backup_manual_in_progress": "AugÅĄupielāde jau notiek. MēĪiniet pēc kāda laika atkārtoti", + "backup_manual_success": "VeiksmÄĢgi", + "backup_manual_title": "AugÅĄupielādes statuss", + "backup_options_page_title": "DublÄ“ÅĄanas iestatÄĢjumi", "backup_settings_subtitle": "PārvaldÄĢt augÅĄupielādes iestatÄĢjumus", "backward": "AtpakaÄŧejoÅĄa", "biometric_auth_enabled": "Ieslēgta biometriskā autentifikācija", "biometric_locked_out": "Biometriskā autentifikācija tev ir bloķēta", "biometric_no_options": "Nav pieejamas biometriskās autentifikācijas iespējas", "biometric_not_available": "Biometriskā autentifikācija ÅĄajā ierÄĢcē nav pieejama", + "birthdate_saved": "DzimÅĄanas datums veiksmÄĢgi saglabāts", "birthdate_set_description": "DzimÅĄanas datums tiek izmantots, lai aprēķinātu ÅĄÄĢs personas vecumu fotogrāfijas uzņemÅĄanas brÄĢdÄĢ.", "blurred_background": "Izpludināts fons", "bugs_and_feature_requests": "KÄŧÅĢdas un funkciju pieprasÄĢjumi", "build": "BÅĢvējums", "build_image": "BÅĢvējuma attēls", - "bulk_delete_duplicates_confirmation": "Vai tieÅĄÄm vēlaties masveidā dzēst {count, plural, one {# ÄŧÄĢdzekÄŧā duplikātu} other {# lÄĢdzekÄŧu duplikātus}}? Tādējādi tiks saglabāts katras grupas lielākais resurs un neatgriezeniski dzēsti visi pārējie dublikāti. Å o darbÄĢbu nevar atsaukt!", - "bulk_keep_duplicates_confirmation": "Vai tieÅĄÄm vēlaties paturēt {count, plural, one {# lÄĢdzekÄŧa duplikātu} other {# lÄĢdzekÄŧu duplikātus}}? Tas atrisinās visas dublikātu grupas, neko nedzÄ“ÅĄot.", - "bulk_trash_duplicates_confirmation": "Vai tieÅĄÄm vēlaties masveidā izmest {count, plural, one {# lÄĢdzekÄŧa duplikātu} other {# lÄĢdzekÄŧu duplikātus}}? Tādējādi tiks saglabāts katras grupas lielākais resurs un izmesti visi pārējie dublikāti.", "buy": "Iegādāties Immich", + "cache_settings_clear_cache_button": "IztÄĢrÄĢt keÅĄatmiņu", + "cache_settings_clear_cache_button_title": "IztÄĢra aplikācijas keÅĄatmiņu. Tas bÅĢtiski ietekmēs lietotnes veiktspēju, lÄĢdz keÅĄatmiņa bÅĢs pārbÅĢvēta.", + "cache_settings_duplicated_assets_clear_button": "NOTÄĒRÄĒT", + "cache_settings_duplicated_assets_subtitle": "Fotoattēli un videoklipi, kurus lietotne ir iekÄŧāvusi ignorējamo sarakstā", + "cache_settings_duplicated_assets_title": "Dublicētie faili ({count})", + "cache_settings_statistics_album": "Bibliotēkas sÄĢktēli", + "cache_settings_statistics_full": "Pilni attēli", + "cache_settings_statistics_shared": "Koplietojamo albumu sÄĢktēli", + "cache_settings_statistics_thumbnail": "SÄĢktēli", + "cache_settings_statistics_title": "KeÅĄatmiņas lietojums", + "cache_settings_subtitle": "Kontrolēt Immich mobilās lietotnes keÅĄdarbi", + "cache_settings_tile_subtitle": "Kontrolēt lokālās krātuves uzvedÄĢbu", + "cache_settings_tile_title": "Lokālā krātuve", + "cache_settings_title": "KeÅĄdarbes iestatÄĢjumi", "camera": "Fotokamera", "camera_brand": "Fotokameras zÄĢmols", "camera_model": "Fotokameras modelis", @@ -629,12 +624,12 @@ "canceling": "AtceÄŧ", "cannot_merge_people": "Nevar apvienot personas", "cannot_undo_this_action": "Å o darbÄĢbu nevar atcelt!", - "cannot_update_the_description": "Nevar atjaunināt aprakstu", "cast": "PārraidÄĢt", "cast_description": "Konfigurēt pieejamos pārraides galamērġus", "change_date": "MainÄĢt datumu", "change_description": "MainÄĢt aprakstu", "change_display_order": "MainÄĢt attēloÅĄanas secÄĢbu", + "change_expiration_time": "IzmainÄĢt derÄĢguma termiņu", "change_location": "MainÄĢt atraÅĄanās vietu", "change_name": "MainÄĢt nosaukumu", "change_name_successfully": "Vārds veiksmÄĢgi nomainÄĢts", @@ -642,31 +637,22 @@ "change_password_description": "Vai nu ÅĄÄĢ ir pirmā reize, kad pieslēdzaties sistēmai, vai arÄĢ ir iesniegts pieprasÄĢjums mainÄĢt paroli. LÅĢdzu, ievadiet jauno paroli zemāk.", "change_password_form_confirm_password": "Apstiprināt Paroli", "change_password_form_description": "Sveiki {name},\n\nÅ ÄĢ ir pirmā reize, kad pierakstāties sistēmā, vai arÄĢ ir iesniegts pieprasÄĢjums mainÄĢt paroli. LÅĢdzu, zemāk ievadiet jauno paroli.", - "change_password_form_log_out": "IzrakstÄĢties no visām pārējām ierÄĢcēm", - "change_password_form_log_out_description": "Ieteicams izrakstÄĢties no visām pārējām ierÄĢcēm", "change_password_form_new_password": "Jauna Parole", "change_password_form_password_mismatch": "Paroles nesakrÄĢt", "change_password_form_reenter_new_password": "Atkārtoti ievadÄĢt jaunu paroli", "change_pin_code": "NomainÄĢt PIN kodu", - "change_your_password": "Mainiet savu paroli", - "changed_visibility_successfully": "RedzamÄĢba veiksmÄĢgi mainÄĢta", "charging": "Lādē", "charging_requirement_mobile_backup": "Fona dublÄ“ÅĄanai nepiecieÅĄams, lai ierÄĢce tiktu lādēta", - "check_logs": "PārbaudÄĢt Åžurnālus", + "check_corrupt_asset_backup_button": "Veikt pārbaudi", "checksum": "Kontrolsumma", "choose_matching_people_to_merge": "Izvēlies atbilstoÅĄas personas apvienoÅĄanai", "city": "Pilsēta", - "cleanup_confirm_description": "Immich atrada {count} resursus (izveidotus pirms {date}), kas droÅĄi dublēti serverÄĢ. Vai noņemt lokālās kopijas no ÅĄÄĢs ierÄĢces?", "cleanup_confirm_prompt_title": "Dzēst no ÅĄÄĢs ierÄĢces?", "cleanup_deleted_assets": "Pārvietoja {count} failus uz ierÄĢces atkritni", "cleanup_deleting": "Pārvieto uz atkritni...", - "cleanup_found_assets": "Atrasti {count} dublētie resursi", "cleanup_found_assets_with_size": "Atrada {count} dublētus failus ({size})", "cleanup_icloud_shared_albums_excluded": "SkenÄ“ÅĄanā netiek iekÄŧauti iCloud kopÄĢgotie albumi", - "cleanup_no_assets_found": "Nav atrasti resursi, kas atbilstu iepriekÅĄ minētajiem kritērijiem. AtbrÄĢvojot vietu, var noņemt tikai resursus, kas ir dublēti serverÄĢ", "cleanup_preview_title": "DzÄ“ÅĄamie faili ({count})", - "cleanup_step3_description": "Meklējiet dublētos resursus, kas atbilst jÅĢsu datumam, un saglabājiet iestatÄĢjumus.", - "cleanup_step4_summary": "No lokālās ierÄĢces jānoņem {count} resursi (izveidoti pirms {date}). Fotoattēli joprojām bÅĢs pieejami no lietotnes Immich.", "cleanup_trash_hint": "Lai pilnÄĢbā atbrÄĢvotu uzglabÄÅĄanas vietu, atveriet sistēmas galerijas lietotni un iztukÅĄojiet atkritni", "clear": "NotÄĢrÄĢt", "clear_all": "NotÄĢrÄĢt visu", @@ -674,6 +660,8 @@ "clear_file_cache": "NotÄĢrÄĢt failu keÅĄatmiņu", "clear_message": "NotÄĢrÄĢt paziņojumu", "clear_value": "NotÄĢrÄĢt vērtÄĢbu", + "client_cert_dialog_msg_confirm": "Labi", + "client_cert_enter_password": "Ievadi paroli", "client_cert_import": "Importēt", "client_cert_import_success_msg": "Klienta sertifikāts ir importēts", "client_cert_invalid_msg": "NederÄĢgs sertifikāta fails vai nepareiza parole", @@ -682,16 +670,14 @@ "client_cert_remove_msg": "Klienta sertifikāts ir noņemts", "client_cert_subtitle": "Atbalsta tikai PKCS12 (.p12, .pfx) formātu. Sertifikātu importÄ“ÅĄana/noņemÅĄana ir pieejama tikai pirms pieslēgÅĄanās", "client_cert_title": "SSL klienta sertifikāts [EKSPERIMENTĀLS]", + "clockwise": "PulksteņrādÄĢtāja virzienā", "close": "Aizvērt", "collapse": "SakÄŧaut", "collapse_all": "SakÄŧaut visu", "color": "Krāsa", + "color_theme": "Krāsu tēma", "command": "Komanda", "command_palette_prompt": "Ātri atrodi lapas, darbÄĢbas vai komandas", - "command_palette_to_close": "aizvērt", - "command_palette_to_navigate": "ieiet", - "command_palette_to_select": "izvēlēties", - "command_palette_to_show_all": "parādÄĢt visu", "comment_deleted": "Komentārs dzēsts", "comment_options": "Komentāru iespējas", "comments_and_likes": "Komentāri un tÄĢkÅĄÄˇi", @@ -700,29 +686,20 @@ "completed": "Pabeigts", "confirm": "Apstiprināt", "confirm_admin_password": "Administratora paroles apstiprinājums", - "confirm_delete_face": "Vai tieÅĄÄm vēlaties dzēst {name} seju no resursa?", - "confirm_delete_shared_link": "Vai tieÅĄÄm vēlaties dzēst ÅĄo koplietoto saiti?", - "confirm_keep_this_delete_others": "Visi pārējie kaudzē esoÅĄie resursi tiks dzēsti, izņemot ÅĄo resursu. Vai tieÅĄÄm vēlaties turpināt?", "confirm_new_pin_code": "Apstiprināt jauno PIN kodu", "confirm_password": "Apstiprināt paroli", "confirm_tag_face": "Vai vēlies atzÄĢmēt ÅĄo seju kā {name}?", "confirm_tag_face_unnamed": "Vai vēlies atzÄĢmēt ÅĄo seju?", - "connected_device": "Pievienotā ierÄĢce", - "connected_to": "Savienots ar", - "contain": "Ietvert", "context": "Konteksts", "continue": "Turpināt", + "control_bottom_app_bar_create_new_album": "Izveidot jaunu albumu", + "control_bottom_app_bar_delete_from_immich": "Dzēst no Immich", "control_bottom_app_bar_delete_from_local": "Dzēst no ierÄĢces", "control_bottom_app_bar_edit_location": "RediÄŖÄ“t atraÅĄanās vietu", "control_bottom_app_bar_edit_time": "RediÄŖÄ“t datumu un laiku", + "control_bottom_app_bar_share_to": "KopÄĢgot uz", "control_bottom_app_bar_trash_from_immich": "Pārvietot uz Atkritni", - "copied_image_to_clipboard": "Attēls ir nokopēts starpliktuvē.", - "copied_to_clipboard": "Kopēts starpliktuvē!", "copy_error": "KopÄ“ÅĄanas kÄŧÅĢda", - "copy_image": "Kopēt attēlu", - "copy_link": "Kopēt saiti", - "copy_link_to_clipboard": "Kopēt saiti uz starpliktuvi", - "copy_password": "Kopēt paroli", "copy_to_clipboard": "Kopēt starpliktuvē", "country": "Valsts", "cover": "AizpildÄĢts ekrāns", @@ -735,57 +712,48 @@ "create_library": "Izveidot bibliotēku", "create_link": "Izveidot saiti", "create_link_to_share": "Izveidot kopÄĢgoÅĄanas saiti", - "create_link_to_share_description": "Äģaut ikvienam, kam ir saite, redzēt atlasÄĢto(-os) fotoattēlu(-us)", "create_new": "IZVEIDOT JAUNU", "create_new_person": "Izveidot jaunu personu", "create_new_person_hint": "PiesaistÄĢt izvēlētos failus jaunai personai", "create_new_user": "Izveidot jaunu lietotāju", - "create_person": "Izveidot personu", - "create_person_subtitle": "Pievienojiet vārdu atlasÄĢtajai sejai, lai izveidotu un atzÄĢmētu jauno personu", "create_shared_album_page_share_add_assets": "PIEVIENOT AKTÄĒVUS", "create_shared_album_page_share_select_photos": "Fotoattēlu Izvēle", - "create_shared_link": "Izveidot koplietojamu saiti", - "create_tag": "Izveidot birku", - "create_tag_description": "Izveidojiet jaunu atzÄĢmi. Ligzdotām atzÄĢmēm, lÅĢdzu, ievadiet pilnu atzÄĢmes ceÄŧu, ieskaitot slÄĢpsvÄĢtras.", "create_user": "Izveidot lietotāju", "create_workflow": "Izveidot darba plÅĢsmu", - "created": "Izveidots", "created_at": "Izveidots", - "creating_linked_albums": "Notiek saistÄĢtu albumu izveide...", "crop": "Apcirpt", + "crop_aspect_ratio_fixed": "Fiksēts", "crop_aspect_ratio_free": "BrÄĢvs", "crop_aspect_ratio_original": "OriÄŖināls", - "crop_aspect_ratio_square": "Kvadrāts", - "current_device": "PaÅĄreizējā ierÄĢce", + "curated_object_page_title": "Lietas", "current_pin_code": "EsoÅĄais PIN kods", "current_server_address": "PaÅĄreizējā servera adrese", "custom_date": "Pielāgots datums", "custom_locale": "Pielāgota lokalizācija", - "custom_locale_description": "Formatēt datumus, laikus un skaitÄŧus atbilstoÅĄi valodai un reÄŖionam", + "custom_locale_description": "Formatēt datumus un skaitÄŧus atbilstoÅĄi valodai un reÄŖionam", + "custom_url": "Pielāgots URL", "cutoff_date_description": "Paturēt fotoattēlus no pēdējāâ€Ļ", "cutoff_day": "{count, plural, one {dienas} other {dienām}}", "cutoff_year": "{count, plural, one {gada} other {gadiem}}", - "dark": "TumÅĄs", - "dark_theme": "Pārslēgt uz tumÅĄo tēmu", - "date": "Datums", + "daily_title_text_date_year": "E, MMM dd, gggg", + "dark_theme": "Pārslēgt tumÅĄo tēmu", "date_after": "Datums pēc", "date_and_time": "Datums un Laiks", "date_before": "Datums pirms", + "date_format": "E, LLL d, g â€ĸ h:mm a", "date_of_birth_saved": "DzimÅĄanas datums veiksmÄĢgi saglabāts", "date_range": "Datumu diapazons", "day": "Diena", "days": "Dienas", "deduplicate_all": "Dedublicēt visus", "delete": "Dzēst", - "delete_action_confirmation_message": "Vai tieÅĄÄm vēlaties dzēst ÅĄo resursu? Å ÄĢ darbÄĢba pārvietos resursu uz servera miskasti un parādÄĢs vaicājumu, vai vēlaties to dzēst lokāli", - "delete_action_prompt": "{count} dzēsts (-i)", "delete_album": "Dzēst albumu", - "delete_api_key_prompt": "Vai tieÅĄÄm vēlaties dzēst ÅĄo API atslēgu?", "delete_dialog_alert": "Å ie vienumi tiks neatgriezeniski dzēsti no Immich un jÅĢsu ierÄĢces", - "delete_dialog_alert_local": "Å ie vienumi tiks neatgriezeniski dzēsti no jÅĢsu ierÄĢces, bet joprojām bÅĢs pieejami Immich serverÄĢ", - "delete_dialog_alert_local_non_backed_up": "DaÅži no ÅĄiem elementiem netiek dublēti Immich un tiks neatgriezeniski dzēsti no jÅĢsu ierÄĢces", + "delete_dialog_alert_local": "Å ie vienumi tiks neatgriezeniski dzēsti no jÅĢsu ierÄĢces, bet joprojām bÅĢs pieejami Immich serverÄĢ.", + "delete_dialog_alert_local_non_backed_up": "DaÅži no ÅĄiem elementiem netiek dublēti Immich un tiks neatgriezeniski dzēsti no jÅĢsu ierÄĢces.", + "delete_dialog_alert_remote": "Å ie vienumi tiks neatgriezeniski dzēsti no Immich servera.", + "delete_dialog_ok_force": "Tā pat dzēst", "delete_dialog_title": "Neatgriezeniski Dzēst", - "delete_duplicates_confirmation": "Vai tieÅĄÄm vēlaties neatgriezeniski dzēst ÅĄos dublikātus?", "delete_face": "Dzēst seju", "delete_key": "Dzēst atslēgu", "delete_library": "Dzēst bibliotēku", @@ -794,35 +762,29 @@ "delete_local_dialog_ok_backed_up_only": "Dzēst tikai dublētos", "delete_local_dialog_ok_force": "Tā pat dzēst", "delete_others": "Dzēst citus", - "delete_permanently": "Dzēst neatgriezeniski", - "delete_permanently_action_prompt": "{count} neatgriezeniski izdzēsts(-i)", "delete_shared_link": "Dzēst KopÄĢgoÅĄanas saiti", "delete_shared_link_dialog_title": "Dzēst KopÄĢgoÅĄanas saiti", - "delete_tag": "Dzēst birku", - "delete_tag_confirmation_prompt": "Vai tieÅĄÄm vēlaties dzēst atzÄĢmi {tagName}?", "delete_user": "Dzēst lietotāju", "deleted_shared_link": "Dzēst kopÄĢgoto saiti", "description": "Apraksts", - "deselect_all": "Noņemt atlasi visam", - "details": "SÄĢkāka informācija", + "description_input_hint_text": "Pievienot aprakstu...", + "description_input_submit_error": "Atjauninot aprakstu, radās kÄŧÅĢda; papildinformāciju skatiet Åžurnālā", + "details": "INFORMĀCIJA", "direction": "SecÄĢba", - "disable": "Atspējot", - "disabled": "Atspējots", "discord": "Discord", "discover": "Atklāt", "discovered_devices": "Atrastās ierÄĢces", - "dismiss_all_errors": "NoraidÄĢt visas kÄŧÅĢdas", - "display_options": "AttēloÅĄanas opcijas", "display_order": "AttēloÅĄanas secÄĢba", "display_original_photos": "RādÄĢt oriÄŖinālās fotogrāfijas", - "display_original_photos_setting_description": "Ja sākotnējais resurs ir saderÄĢgs ar tÄĢmekli, skatot resursu, ieteicams rādÄĢt oriÄŖinālo fotoattēlu, nevis sÄĢktēlus. Tas var izraisÄĢt lēnāku fotoattēlu attēloÅĄanas ātrumu.", "do_not_show_again": "Vairs nerādÄĢt ÅĄo ziņojumu", "documentation": "Dokumentācija", "done": "Gatavs", "download": "Lejupielādēt", + "download_action_prompt": "Lejupielādē {count} failus", "download_canceled": "Lejupielāde atcelta", "download_complete": "Lejupielāde pabeigta", "download_enqueue": "Lejupielāde ierindota", + "download_error": "Lejupielādes kÄŧÅĢda", "download_failed": "Lejupielāde neizdevās", "download_finished": "Lejupielāde pabeigta", "download_include_embedded_motion_videos": "Iegultie videoklipi", @@ -832,14 +794,16 @@ "download_paused": "Lejupielāde nopauzēta", "download_settings": "Lejupielāde", "download_settings_description": "Ar failu lejupielādi saistÄĢto iestatÄĢjumu pārvaldÄĢba", + "download_started": "Lejupielāde sākta", + "download_sucess": "Lejupielāde izdevās", + "download_sucess_android": "Multivides fails ir lejupielādēts uz DCIM/Immich", "download_waiting_to_retry": "Gaida, lai mēĪinātu atkārtoti", "downloading": "Lejupielādē", "downloading_asset_filename": "Lejupielādē failu {filename}", "downloading_from_icloud": "Lejupielādē no iCloud", "downloading_media": "Lejupielādē failu", - "drop_files_to_upload": "Nometiet failus jebkurā vietā, lai augÅĄupielādētu", "duplicates": "Dublikāti", - "duplicates_description": "Atrisini katru grupu, norādot, kuri no tiem ir dublikāti.", + "duplicates_description": "Atrisini katru grupu, norādot, kuri no tiem ir dublikāti", "duration": "Ilgums", "edit": "Labot", "edit_album": "Labot albumu", @@ -848,31 +812,26 @@ "edit_date": "Labot datumu", "edit_date_and_time": "Labot datumu un laiku", "edit_date_and_time_action_prompt": "{count} datums un laiks labots", - "edit_date_and_time_by_offset": "MainÄĢt datumu pēc nobÄĢdes", "edit_description": "Labot aprakstu", + "edit_description_prompt": "LÅĢdzu, izvēlies jaunu aprakstu:", "edit_exclusion_pattern": "Labot izslēgÅĄanas ÅĄablonu", "edit_faces": "Labot sejas", "edit_key": "Labot atslēgu", "edit_link": "RediÄŖÄ“t saiti", "edit_location": "RediÄŖÄ“t AtraÅĄanās Vietu", - "edit_location_action_prompt": "{count} atraÅĄanās vieta rediÄŖÄ“ta", "edit_location_dialog_title": "AtraÅĄanās vieta", "edit_name": "RediÄŖÄ“t vārdu", "edit_people": "Labot profilu", - "edit_tag": "Labot birku", "edit_title": "Labot nosaukumu", "edit_user": "Labot lietotāju", "edit_workflow": "Labot darba plÅĢsmu", "editor": "Redaktors", + "editor_close_without_save_prompt": "Izmaiņas netiks saglabātas", + "editor_close_without_save_title": "Aizvērt redaktoru?", "editor_discard_edits_confirm": "Atmest labojumus", - "editor_discard_edits_prompt": "Jums ir nesaglabātas izmaiņas. Vai tieÅĄÄm vēlaties tās atmest?", "editor_discard_edits_title": "Atmest labojumus?", - "editor_edits_applied_error": "Neizdevās pievienot labojumus", - "editor_edits_applied_success": "RediÄŖÄ“jumi veiksmÄĢgi pievienoti", "editor_flip_horizontal": "Apvērst horizontāli", "editor_flip_vertical": "Apvērst vertikāli", - "editor_handle_corner": "{corner, select, top_left {AugÅĄÄ“jais kreisais} top_right {AugÅĄÄ“jais labais} bottom_left {ApakÅĄÄ“jais kreisais} bottom_right {ApakÅĄÄ“jais labais} other {A}} stÅĢra turis", - "editor_handle_edge": "{edge, select, top {AugÅĄÄ“jās} bottom {ApakÅĄÄ“jās} left {Kreisās} right {Labās} other {An}} malas turis", "editor_orientation": "Orientācija", "editor_reset_all_changes": "Atcelt izmaiņas", "editor_rotate_left": "Pagriezt par 90° pretēji pulksteņrādÄĢtāja virzienam", @@ -881,44 +840,28 @@ "email_notifications": "E-pasta paziņojumi", "empty_folder": "Å ÄĢ mape ir tukÅĄa", "empty_trash": "IztukÅĄot atkritni", - "empty_trash_confirmation": "Vai tieÅĄÄm vēlaties iztukÅĄot atkritni? Tas neatgriezeniski noņems visus atkritnē esoÅĄos resursus no Immich. \nÅ o darbÄĢbu nevar atsaukt!", - "enable": "Iespējot", "enable_backup": "Ieslēgt dublÄ“ÅĄanu", "enable_biometric_auth_description": "Lai iespējotu biometrisko autentifikāciju, Ievadiet savu PIN kodu", - "enabled": "Iespējots", "end_date": "Beigu datums", "enqueued": "Ierindots", "enter_wifi_name": "Ievadi Wi-Fi nosaukumu", "enter_your_pin_code": "Ievadi savu PIN kodu", "enter_your_pin_code_subtitle": "Ievadi savu PIN kodu, lai piekÄŧÅĢtu slēgtajai mapei", "error": "KÄŧÅĢda", - "error_delete_face": "KÄŧÅĢda, dzÄ“ÅĄot seju no resursa", + "error_change_sort_album": "Neizdevās nomainÄĢt albuma kārtoÅĄanas secÄĢbu", "error_loading_albums": "KÄŧÅĢda, ielādējot albumus", "error_loading_image": "KÄŧÅĢda, ielādējot attēlu", "error_loading_partners": "KÄŧÅĢda, ielādējot partnerus: {error}", "error_retrieving_asset_information": "KÄŧÅĢda, iegÅĢstot informāciju par resursu", "error_saving_image": "KÄŧÅĢda: {error}", - "error_tag_face_bounding_box": "KÄŧÅĢda, atzÄĢmējot seju — nevar iegÅĢt ierobeÅžojoÅĄÄ lodziņa koordinātas", "error_title": "KÄŧÅĢda - kaut kas nogāja greizi", "error_while_navigating": "KÄŧÅĢda, navigējot uz resursu", "errors": { "cannot_navigate_next_asset": "Nevar pāriet uz nākamo resursu", "cannot_navigate_previous_asset": "Nevar pāriet uz iepriekÅĄÄ“jo resursu", "cant_apply_changes": "Nevar piemērot izmaiņas", - "cant_change_activity": "Nevar {enabled, select, true {atspējot} other {iespējot}} aktivitāti", - "cant_change_asset_favorite": "Nevar mainÄĢt favorÄĢtu priekÅĄ resursa", - "cant_change_metadata_assets_count": "Nevar mainÄĢt metadatus {count, plural, one {# resursam} other {# resursiem}}", "cant_get_faces": "Nevar iegÅĢt sejas", - "cant_get_number_of_comments": "Nevar izgÅĢt komentāru skaitu", "cant_search_people": "Neizdevās veikt peronu meklÄ“ÅĄanu", - "cant_search_places": "Nevar meklēt vietas", - "error_adding_assets_to_album": "KÄŧÅĢda, pievienojot resursus albumam", - "error_adding_users_to_album": "KÄŧÅĢda, pievienojot lietotājus albumam", - "error_deleting_shared_user": "KÄŧÅĢda, dzÄ“ÅĄot koplietotu lietotāju", - "error_downloading": "KÄŧÅĢda, lejupielādējot {filename}", - "error_hiding_buy_button": "KÄŧÅĢda, slēpjot pirkt pogu", - "error_removing_assets_from_album": "KÄŧÅĢda, noņemot resursus no albuma, skatiet konsoli, lai iegÅĢtu plaÅĄÄku informāciju", - "error_selecting_all_assets": "KÄŧÅĢda, atlasot visus resursus", "exclusion_pattern_already_exists": "Šāds izslēgÅĄanas ÅĄablons jau pastāv.", "failed_to_create_album": "Neizdevās izveidot albumu", "failed_to_create_shared_link": "Neizdevās izvedot kopÄĢgoÅĄanas saiti", @@ -936,109 +879,46 @@ "failed_to_update_notification_status": "Neizdevās mainÄĢt paziņojuma statusu", "incorrect_email_or_password": "Nepareizs e-pasts vai parole", "library_folder_already_exists": "Å is importa ceÄŧÅĄ jau pastāv.", - "page_not_found": "Lapa nav atrasta", - "paths_validation_failed": "{paths, plural, one {# ceÄŧa} other {# ceÄŧu}} validācija neizdevās", "profile_picture_transparent_pixels": "Profila attēlos nevar bÅĢt caurspÄĢdÄĢgi pikseÄŧi. LÅĢdzu, palielini un/vai pārvieto attēlu.", "quota_higher_than_disk_size": "Tu esi iestatÄĢjis kvotu, kas pārsniedz diska izmēru", "something_went_wrong": "Kaut kas nogāja greizi", - "unable_to_add_album_users": "Nevar pievienot lietotājus albumam", - "unable_to_add_assets_to_shared_link": "Nevar pievienot resursus koplietojamai saitei", - "unable_to_add_comment": "Nevar pievienot komentāru", "unable_to_add_exclusion_pattern": "Neizdevās pievienot izslēgÅĄanas ÅĄablonu", - "unable_to_add_partners": "Nevar pievienot partnerus", - "unable_to_add_remove_archive": "Nevar {archived, select, true {noņemt resursu no} other {pievienot resursu}} arhÄĢva(-m)", - "unable_to_add_remove_favorites": "Nevar {favorite, select, true {pievienot resursu} other {noņemt resursu no}} favorÄĢtiem", - "unable_to_archive_unarchive": "Nevar {archived, select, true {arhivēt} other {atvienot no arhÄĢva}}", - "unable_to_change_album_user_role": "Nevar mainÄĢt albuma lietotāja lomu", - "unable_to_change_date": "Nevar mainÄĢt datumu", "unable_to_change_description": "Neizdevās nomainÄĢt aprakstu", - "unable_to_change_favorite": "Nevar mainÄĢt favorÄĢtu resursam", - "unable_to_change_location": "Nevar mainÄĢt atraÅĄanās vietu", - "unable_to_change_password": "Nevar nomainÄĢt paroli", - "unable_to_change_visibility": "Neizdevās mainÄĢt redzamÄĢbu {count, plural, one {# personai} other {# personām}}", - "unable_to_complete_oauth_login": "Neizdevās pabeigt OAuth pieteikÅĄanos", - "unable_to_connect": "Neizdevās izveidot savienojumu", - "unable_to_copy_to_clipboard": "Neizdevās kopēt starpliktuvē, pārliecinieties, vai piekÄŧÅĢstat lapai, izmantojot https", - "unable_to_create": "Neizdevās izveidot darbplÅĢsmu", "unable_to_create_admin_account": "Nevar izveidot administratora kontu", "unable_to_create_api_key": "Nevar izveidot jaunu API atslēgu", "unable_to_create_library": "Nevar izveidot bibliotēku", "unable_to_create_user": "Neizdevās izveidot lietotāju", "unable_to_delete_album": "Nevar izdzēst albumu", "unable_to_delete_asset": "Nevar izdzēst failu", - "unable_to_delete_assets": "KÄŧÅĢda, dzÄ“ÅĄot resursus", "unable_to_delete_exclusion_pattern": "Neizdevās dzēst izslēgÅĄanas ÅĄablonu", - "unable_to_delete_shared_link": "Neizdevās izdzēst koplietojamo saiti", "unable_to_delete_user": "Neizdevās dzēst lietotāju", - "unable_to_delete_workflow": "Neizdevās izdzēst darbplÅĢsmu", - "unable_to_download_files": "Neizdevās lejupielādēt failus", "unable_to_edit_exclusion_pattern": "Neizdevās labot izslēgÅĄanas ÅĄablonu", "unable_to_empty_trash": "Neizdevās iztukÅĄot atkritni", - "unable_to_enter_fullscreen": "Neizdevās ieslēgt pilnekrāna reÅžÄĢmu", - "unable_to_exit_fullscreen": "Neizdevās iziet no pilnekrāna reÅžÄĢma", - "unable_to_get_comments_number": "Neizdevās iegÅĢt komentāru skaitu", - "unable_to_get_shared_link": "Neizdevās iegÅĢt koplietojamu saiti", "unable_to_hide_person": "Neizdevās paslēpt personu", - "unable_to_link_motion_video": "Nevar saistÄĢt kustÄĢbas video", - "unable_to_link_oauth_account": "Nevar saistÄĢt OAuth kontu", - "unable_to_log_out_all_devices": "Nevar izrakstÄĢties no visām ierÄĢcēm", - "unable_to_log_out_device": "Nevar izrakstÄĢties no ierÄĢces", - "unable_to_login_with_oauth": "Nevar pieteikties ar OAuth", - "unable_to_play_video": "Nevar atskaņot video", - "unable_to_reassign_assets_existing_person": "Nevar atkārtoti pieÅĄÄˇirt resursus {name, select, null {esoÅĄai personai} other{{name}}}", - "unable_to_reassign_assets_new_person": "Nevar atkārtoti pieÅĄÄˇirt resursus jaunai personai", - "unable_to_refresh_user": "Nevar atsvaidzināt lietotāju", - "unable_to_remove_album_users": "Nevar noņemt lietotājus no albuma", - "unable_to_remove_api_key": "Nevar noņemt API atslēgu", - "unable_to_remove_assets_from_shared_link": "Nevar noņemt resursus no koplietojamās saites", - "unable_to_remove_library": "Nevar noņemt bibliotēku", - "unable_to_remove_partner": "Nevar noņemt partneri", - "unable_to_remove_reaction": "Nevar noņemt reakciju", - "unable_to_reset_password": "Nevar atiestatÄĢt paroli", - "unable_to_reset_pin_code": "Nevar atiestatÄĢt PIN kodu", - "unable_to_resolve_duplicate": "Nevar atrisināt dublikātu", - "unable_to_restore_assets": "Nevar atjaunot resursus", "unable_to_restore_trash": "Neizdevās atjaunot failus no atkritnes", - "unable_to_restore_user": "Nevar atjaunot lietotāju", - "unable_to_save_album": "Nevar saglabāt albumu", - "unable_to_save_api_key": "Nevar saglabāt API atslēgu", "unable_to_save_date_of_birth": "Neizdevās saglabāt dzimÅĄanas datumu", - "unable_to_save_name": "Nevar saglabāt vārdu", - "unable_to_save_profile": "Nevar saglabāt profilu", - "unable_to_save_settings": "Nevar saglabāt iestatÄĢjumus", "unable_to_scan_libraries": "Bibliotēku skenÄ“ÅĄana neizdevās", "unable_to_scan_library": "Bibliotēkas skenÄ“ÅĄana neizdevās", - "unable_to_set_feature_photo": "Nevar iestatÄĢt resursa fotoattēlu", - "unable_to_set_profile_picture": "Nevar iestatÄĢt profila attēlu", - "unable_to_set_rating": "Nevar iestatÄĢt vērtējumu", - "unable_to_submit_job": "Nevar iesniegt darbu", "unable_to_trash_asset": "Neizdevās pārvietot failu uz atkritni", - "unable_to_unlink_account": "Nevar atsaistÄĢt kontu", - "unable_to_unlink_motion_video": "Nevar atsaistÄĢt kustÄĢbas video", - "unable_to_update_album_cover": "Nevar atjaunināt albuma vāciņu", - "unable_to_update_album_info": "Nevar atjaunināt albuma informāciju", - "unable_to_update_library": "Nevar atjaunināt bibliotēku", - "unable_to_update_location": "Nevar atjaunināt atraÅĄanās vietu", - "unable_to_update_settings": "Nevar atjaunināt iestatÄĢjumus", - "unable_to_update_timeline_display_status": "Nevar atjaunināt laika grafika parādÄĢÅĄanas statusu", - "unable_to_update_user": "Nevar atjaunināt lietotāju", - "unable_to_update_workflow": "Nevar atjaunināt darbplÅĢsmu", - "unable_to_upload_file": "Nevar augÅĄupielādēt failu" + "unable_to_update_album_cover": "Nevar atjaunināt albuma vāciņu" }, "errors_text": "KÄŧÅĢdas", - "exclusion_pattern": "IzslēgÅĄanas ÅĄablons", "exif": "Exif", "exif_bottom_sheet_description": "Pievienot Aprakstu...", - "exif_bottom_sheet_description_error": "KÄŧÅĢda, atjauninot aprakstu", + "exif_bottom_sheet_details": "INFORMĀCIJA", + "exif_bottom_sheet_location": "ATRAÅ ANĀS VIETA", "exif_bottom_sheet_no_description": "Nav apraksta", + "exif_bottom_sheet_people": "PERSONAS", + "exif_bottom_sheet_person_add_person": "Pievienot vārdu", "exit_slideshow": "Iziet no slÄĢdrādes", "expand": "Izvērst", - "expand_all": "Izvērst visus", + "experimental_settings_new_asset_list_subtitle": "Izstrādes posmā", + "experimental_settings_new_asset_list_title": "Iespējot eksperimentālo fotoreÅžÄŖi", + "experimental_settings_subtitle": "Izmanto uzņemoties risku!", + "experimental_settings_title": "Eksperimentāls", "expire_after": "DerÄĢguma termiÅ†ÅĄ beidzas pēc", "expired": "DerÄĢguma termiÅ†ÅĄ beidzās", - "expires_date": "DerÄĢguma termiÅ†ÅĄ beidzas {date}", "explore": "IzpētÄĢt", - "explorer": "Meklētājs", "export": "Eksportēt", "export_as_json": "Eksportēt kā JSON", "export_database": "Eksportēt datubāzi", @@ -1055,28 +935,23 @@ "failed_to_load_assets": "Neizdevās ielādēt failus", "failed_to_load_folder": "Neizdevās ielādēt mapi", "favorite": "Izlase", - "favorite_action_prompt": "{count} pievienots favorÄĢtiem", - "favorite_or_unfavorite_photo": "Pievienot foto favorÄĢtiem vai noņemt foto no favorÄĢtiem", "favorites": "Izlase", - "feature_photo_updated": "Galvenā fotogrāfija atjaunināta", - "features": "Funkcijas", + "favorites_page_no_favorites": "Nav atrasti iecienÄĢtākie faili", + "features_in_development": "Izstrādes stadijā esoÅĄas funkcijas", "features_setting_description": "Lietotnes funkciju pārvaldÄĢba", "file_name_or_extension": "Faila nosaukums vai paplaÅĄinājums", "file_name_text": "Faila nosaukums", - "file_size": "Faila izmērs", + "file_name_with_value": "Faila nosaukums: {file_name}", "filename": "Faila nosaukums", "filetype": "Faila tips", "filter": "Filtrēt", "filter_people": "Filtrēt personas", "filter_places": "Filtrēt vietas", - "filter_tags": "Filtrēt birkas", "filters": "Filtri", "first": "Pirmais", - "fix_incorrect_match": "Labot nepareizu atbilstÄĢbu", "folder": "Mape", "folder_not_found": "Mape nav atrasta", "folders": "Mapes", - "folders_feature_description": "Fotoattēlu un videoklipu mapju skata pārlÅĢkoÅĄana failu sistēmā", "forgot_pin_code_question": "Aizmirsi savu PIN?", "forward": "Uz priekÅĄu", "free_up_space": "AtbrÄĢvot vietu", @@ -1085,10 +960,7 @@ "full_path": "Pilnais ceÄŧÅĄ: {path}", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Å ÄĢ funkcija darbojas, lejupielādējot ārējos resursus no Google.", - "general": "VispārÄĢgi", - "geolocation_instruction_location": "NoklikÅĄÄˇiniet uz resursa ar GPS koordinātām, lai izmantotu tā atraÅĄanās vietu, vai atlasiet atraÅĄanās vietu tieÅĄi no kartes", "get_help": "Saņemt palÄĢdzÄĢbu", - "get_people_error": "KÄŧÅĢda, iegÅĢstot cilvēkus", "get_wifiname_error": "Nevarēja iegÅĢt Wi-Fi nosaukumu. Pārliecinies, ka esi pieÅĄÄˇÄĢris nepiecieÅĄamās atÄŧaujas un esi savienots ar Wi-Fi tÄĢklu", "getting_started": "Pirmie soÄŧi", "go_back": "Doties atpakaÄŧ", @@ -1103,18 +975,17 @@ "group_owner": "Grupēt pēc ÄĢpaÅĄnieka", "group_places_by": "Grupēt vietas pēc...", "group_year": "Grupēt pēc gada", + "haptic_feedback_switch": "Iespējot haptisku reakciju", "haptic_feedback_title": "Haptiska Reakcija", "has_quota": "Kvota", "hash_asset": "Veidot faila jaucējvērtÄĢbu", "hashed_assets": "Faili ar jaucējvērtÄĢbām", "hashing": "Veido jaucējvērtÄĢbas", - "header_settings_add_header_tip": "Pievienot galveni", "header_settings_field_validator_msg": "VērtÄĢba nevar bÅĢt tukÅĄa", "header_settings_header_name_input": "Galvenes lauks", "header_settings_header_value_input": "Galvenes vērtÄĢba", "headers_settings_tile_title": "Pielāgotas starpniekservera galvenes", "height": "Augstums", - "hi_user": "Sveiki, {name} ({email})", "hide_all_people": "Paslēpt visas personas", "hide_gallery": "Paslēpt galeriju", "hide_named_person": "Paslēpt personu {name}", @@ -1123,40 +994,45 @@ "hide_schema": "Paslēpt shēmu", "hide_text_recognition": "Slēpt teksta atpazÄĢÅĄanu", "hide_unnamed_people": "Paslēpt nenosauktas personas", + "home_page_add_to_album_conflicts": "Pievienoja {added} failus albumam {album}. {failed} faili jau ir albumā.", + "home_page_add_to_album_err_local": "Albumiem vēl nevar pievienot lokālos failus, izlaiÅž", + "home_page_add_to_album_success": "Pievienoja {added} aktÄĢvus albumam {album}.", + "home_page_album_err_partner": "Pagaidām nevar pievienot partnera aktÄĢvus albumam, notiek izlaiÅĄana", + "home_page_archive_err_local": "Vēl nevar arhivēt lokālos aktÄĢvus, izlaiÅž", + "home_page_archive_err_partner": "Nevarēja arhivēt partnera aktÄĢvus, notiek izlaiÅĄana", "home_page_building_timeline": "Tiek izveidota laika skala", - "host": "Saimniekdators", + "home_page_delete_err_partner": "Nevarēja dzēst partnera aktÄĢvus, notiek izlaiÅĄana", + "home_page_delete_remote_err_local": "Lokālie faili dzÄ“ÅĄanai attālinātajā izvēlē, izlaiÅž", + "home_page_favorite_err_local": "Vēl nevar pievienot izlasei lokālos failus, izlaiÅž", + "home_page_favorite_err_partner": "Pagaidām nevar ievietot izlasē partnera failus, izlaiÅž", + "home_page_first_time_notice": "Ja ÅĄÄĢ ir pirmā reize, kad izmanto lietotni, lÅĢdzu, izvēlies dublējamo albumu, lai laika skalā varētu aizpildÄĢt fotoattēlus un videoklipus", + "home_page_locked_error_local": "Nevar pārvietot lokālos failus uz slēgto mapi, izlaiÅž", + "home_page_locked_error_partner": "Nevar pārvietot partneru failus uz slēgto mapi, izlaiÅž", + "home_page_share_err_local": "Caur saiti nevarēja kopÄĢgot lokālos aktÄĢvus, izlaiÅž", + "home_page_upload_err_limit": "Vienlaikus var augÅĄupielādēt ne vairāk kā 30 aktÄĢvus, notiek izlaiÅĄana", "hour": "Stunda", "hours": "Stundas", "id": "ID", "idle": "DÄĢkstāvē", + "ignore_icloud_photos": "Ignorēt iCloud fotogrāfijas", + "ignore_icloud_photos_description": "iCloud uzglabātās fotogrāfijas netiks augÅĄupielādētas Immich serverÄĢ", "image": "Attēls", - "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} uzņemts {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} uzņemts ar {person1} {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} uzņemts ar {person1} un {person2} {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} uzņemts kopā ar {person1}, {person2} un {person3} {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} uzņemts kopā ar {person1}, {person2} un {additionalCount, number} citiem lietotājiem {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} uzņemts {city}, {country}, {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} uzņemts {city}, {country} ar {person1} {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} uzņemts {city}, {country} ar {person1} un {person2} {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} uzņemts {city}, {country} ar {person1}, {person2} un {person3} {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} uzņemts {city}, {country} ar {person1}, {person2} un {additionalCount, number} citiem lietotājiem {date}", + "image_saved_successfully": "Attēls saglabāts", + "image_viewer_page_state_provider_download_started": "Lejupielāde uzsākta", + "image_viewer_page_state_provider_download_success": "Lejupielāde izdevās", + "image_viewer_page_state_provider_share_error": "KopÄĢgoÅĄanas KÄŧÅĢda", "immich_logo": "Immich logo", "immich_web_interface": "Immich tÄĢmekÄŧa saskarne", "import_from_json": "Importēt no JSON", "import_path": "Importa ceÄŧÅĄ", "in_albums": "{count, plural, one {# albumā} other {# albumos}}", "in_archive": "ArhÄĢvā", - "in_year": "{year} gadā", - "in_year_selector": "IekÅĄÄ", "include_archived": "IekÄŧaut arhivētos", "include_shared_albums": "IekÄŧaut koplietotos albumus", "include_shared_partner_assets": "IekÄŧaut partneru koplietotos failus", - "individual_share": "Individuāla daÄŧa", - "individual_shares": "Individuālās akcijas", "info": "Informācija", "interval": { "day_at_onepm": "Katru dienu 13.00", - "hours": "Katru(-as) {hours, plural, one {stundu} other {{hours, number} stundas}}", "night_at_midnight": "Katru dienu pusnaktÄĢ", "night_at_twoam": "Katru dienu 2.00 naktÄĢ" }, @@ -1164,19 +1040,23 @@ "invalid_date_format": "NederÄĢgs datuma formāts", "invite_people": "IelÅĢgt cilvēkus", "invite_to_album": "Uzaicināt albumā", + "ios_debug_info_fetch_ran_at": "IelasÄĢÅĄana notika {dateTime}", + "ios_debug_info_last_sync_at": "Pēdējā sinhronizācija {dateTime}", + "ios_debug_info_no_processes_queued": "Nav ierindotu fona procesu", + "ios_debug_info_processing_ran_at": "Apstrāde notika {dateTime}", "items_count": "{count, plural, one {# vienums} other {# vienumi}}", "jobs": "Uzdevumi", + "json_editor": "JSON redaktors", + "json_error": "JSON kÄŧÅĢda", "keep": "Paturēt", "keep_albums": "Paturēt albumus", "keep_albums_count": "Patur {count} {count, plural, one {albumu} other {albumus}}", "keep_all": "Paturēt visus", "keep_description": "Izvēlies, kas paliks tavā ierÄĢcē, atbrÄĢvojot vietu.", - "keep_favorites": "Saglabāt favorÄĢtus", "keep_on_device": "Paturēt ierÄĢcē", "keep_on_device_hint": "Izvēlies failus, kurus paturēt ÅĄajā ierÄĢcē", "keep_this_delete_others": "Paturēt ÅĄo, dzēst citus", "keeping": "Patur: {items}", - "kept_this_deleted_others": "Paturēju ÅĄo resursu un izdzēsu {count, plural, one {# asset} other {# assets}}", "keyboard_shortcuts": "TastatÅĢras saÄĢsnes", "language": "Valoda", "language_no_results_subtitle": "MēĪini pielāgot meklÄ“ÅĄanas terminu", @@ -1185,7 +1065,6 @@ "language_setting_description": "Izvēlies vēlamo valodu", "large_files": "Lielie faili", "last": "Pēdējais", - "last_months": "{count, plural, one {PagājuÅĄo mēnes} other {PāgājuÅĄajos # mēneÅĄos}}", "last_seen": "Pēdējo reizi redzēts", "latest_version": "Jaunākā versija", "latitude": "Äĸeogrāfiskais platums", @@ -1197,31 +1076,26 @@ "library": "Bibliotēka", "library_add_folder": "Pievienot mapi", "library_edit_folder": "Labot mapi", + "library_options": "Bibliotēkas opcijas", + "library_page_device_albums": "Albumi ierÄĢcē", + "library_page_new_album": "Jauns albums", "library_page_sort_asset_count": "Failu skaits", "library_page_sort_created": "Jaunākais izveidotais", "library_page_sort_last_modified": "Pēdējās izmaiņas", "library_page_sort_title": "Albuma virsraksts", "licenses": "Licences", - "light": "Gaisma", - "light_theme": "Pārslēgties uz gaiÅĄo tēmu", "like": "PatÄĢk", "like_deleted": "TÄĢkÅĄÄˇis dzēsts", - "link": "Saite", - "link_motion_video": "Saites kustÄĢbas video", - "link_to_docs": "PlaÅĄÄku informāciju skatiet dokumentācijā.", "link_to_oauth": "PiesaistÄĢt OAuth", "linked_oauth_account": "PiesaistÄĢtais OAuth konts", "list": "Saraksts", "loading": "Ielādē", - "loading_search_results_failed": "MeklÄ“ÅĄanas rezultātu ielāde neizdevās", "local": "Lokāli", "local_asset_cast_failed": "Nav iespējams pārraidÄĢt resursu, kas nav augÅĄupielādēts serverÄĢ", "local_assets": "Lokālie faili", - "local_id": "Vietējais ID", "local_media_summary": "Lokālo mediju kopsavilkums", "local_network": "Lokālais tÄĢkls", "local_network_sheet_info": "Izmantojot norādÄĢto Wi-Fi tÄĢklu, lietotne veidos savienojumu ar serveri, izmantojot ÅĄo URL", - "location": "AtraÅĄanās vieta", "location_permission": "AtraÅĄanās vietas atÄŧauja", "location_permission_content": "Lai izmantotu automātiskās pārslēgÅĄanās funkciju, Immich ir nepiecieÅĄama precÄĢzas atraÅĄanās vietas atÄŧauja, lai varētu nolasÄĢt paÅĄreizējā Wi-Fi tÄĢkla nosaukumu", "location_picker_choose_on_map": "Izvēlēties uz kartes", @@ -1229,20 +1103,17 @@ "location_picker_latitude_hint": "Ievadiet savu ÄŖeogrāfisko platumu ÅĄeit", "location_picker_longitude_error": "Ievadiet korektu ÄŖeogrāfisko garumu", "location_picker_longitude_hint": "Ievadiet savu ÄŖeogrāfisko garumu ÅĄeit", - "lock": "Slēdzene", "locked_folder": "Slēgtā mape", - "log_detail_title": "ÅŊurnāla detaÄŧas", "log_out": "IzrakstÄĢties", - "log_out_all_devices": "IzrakstÄĢties no visām ierÄĢcēm", "logged_in_as": "Pieslēdzies kā {user}", - "logged_out_all_devices": "Visas ierÄĢces ir atslēgtas", - "logged_out_device": "IerÄĢce, kurā neesat pieteicies", "login": "Pieslēgties", "login_disabled": "PieslēgÅĄanās ir atslēgta", "login_form_api_exception": "API izņēmums. LÅĢdzu, pārbaudiet servera URL un mēĪiniet vēlreiz.", + "login_form_back_button_text": "AtpakaÄŧ", "login_form_email_hint": "jÅĢsuepasts@email.com", "login_form_endpoint_hint": "http://jÅĢsu-servera-ip:ports", "login_form_endpoint_url": "Servera Galapunkta URL", + "login_form_err_http": "LÅĢdzu norādiet http:// vai https://", "login_form_err_invalid_email": "NederÄĢgs e-pasts", "login_form_err_invalid_url": "NederÄĢgs URL", "login_form_err_leading_whitespace": "PriekÅĄÄ“jā baltstarpa", @@ -1252,24 +1123,16 @@ "login_form_failed_login": "Radās kÄŧÅĢda, piesakoties, pārbaudiet servera URL, e-pastu un paroli", "login_form_handshake_exception": "Ar serveri tika konstatēta Handshake Exception kÄŧÅĢda. Ja izmantojat paÅĄparakstÄĢtu sertifikātu, tad iestatÄĢjumos iespējojiet paÅĄparakstÄĢtu sertifikātu atbalstu.", "login_form_password_hint": "parole", + "login_form_save_login": "Palikt pieteiktam", "login_form_server_empty": "Ieraksties servera URL.", "login_form_server_error": "Nevarēja izveidot savienojumu ar serveri.", - "login_has_been_disabled": "PieteikÅĄanās ir atspējota.", "login_password_changed_error": "Atjaunojot paroli radās kÄŧÅĢda", "login_password_changed_success": "Parole veiksmÄĢgi atjaunota", - "logout_all_device_confirmation": "Vai tieÅĄÄm vēlaties atteikties no visām ierÄĢcēm?", - "logout_this_device_confirmation": "Vai tieÅĄÄm vēlaties atteikties no ÅĄÄĢs ierÄĢces?", - "logs": "ÅŊurnāli", "longitude": "Äĸeogrāfiskais garums", "look": "Izskats", - "loop_videos": "Cilpas video", "loop_videos_description": "Iespējot, lai automātiski videoklips tiktu cikliski palaists detaÄŧu skatÄĢtājā.", - "main_branch_warning": "JÅĢs izmantojat izstrādes versiju; mēs stingri iesakām izmantot laidiena versiju!", - "main_menu": "Galvenā izvēlne", "maintenance_action_restore": "Atjauno datubāzi", "maintenance_description": "Immich ir pārslēgts apkopes reÅžÄĢmā.", - "maintenance_end": "Beigt apkopes reÅžÄĢmu", - "maintenance_logged_in_as": "PaÅĄlaik esat ierakstÄĢjuÅĄies kā {user}", "maintenance_restore_from_backup": "Atjaunot no rezerves kopijas", "maintenance_restore_library": "Atjaunot tavu bibliotēku", "maintenance_restore_library_confirm": "Ja tas izskatās pareizi, turpini rezerves kopijas atjaunoÅĄanu!", @@ -1279,9 +1142,6 @@ "maintenance_restore_library_folder_pass": "lasāms un rakstāms", "maintenance_restore_library_folder_read_fail": "nav nolasāms", "maintenance_restore_library_folder_write_fail": "nav rakstāms", - "maintenance_restore_library_hint_missing_files": "Iespējams, jums trÅĢkst svarÄĢgu failu", - "maintenance_restore_library_hint_regenerate_later": "Varat tos vēlāk atjaunot iestatÄĢjumos", - "maintenance_restore_library_hint_storage_template_missing_files": "Vai izmantojat krātuves veidni? Iespējams, jums trÅĢkst failu", "maintenance_restore_library_loading": "Ielādē integritātes pārbaudes un heiristikuâ€Ļ", "maintenance_task_backup": "Veido esoÅĄÄs datubāzes rezerves kopijuâ€Ļ", "maintenance_task_migrations": "Veic datubāzes migrācijuâ€Ļ", @@ -1290,10 +1150,7 @@ "maintenance_title": "ÄĒslaicÄĢgi nav pieejams", "make": "RaÅžotājs", "manage_geolocation": "PārvaldÄĢt atraÅĄanās vietu", - "manage_media_access_rationale": "Å ÄĢ atÄŧauja ir nepiecieÅĄama, lai pareizi pārvietotu resursus uz atkritni un atjaunotu tos no tās.", - "manage_media_access_settings": "Atvērt iestatÄĢjumus", - "manage_media_access_subtitle": "AtÄŧaut lietotnei Immich pārvaldÄĢt un pārvietot multivides failus.", - "manage_media_access_title": "PiekÄŧuve multivides pārvaldÄĢbai", + "manage_shared_links": "KopÄĢgoto saiÅĄu pārvaldÄĢba", "manage_sharing_with_partners": "KoplietoÅĄanas ar partneriem pārvaldÄĢba", "manage_the_app_settings": "Lietotnes iestatÄĢjumu pārvaldÄĢba", "manage_your_account": "Sava konta pārvaldÄĢba", @@ -1301,10 +1158,13 @@ "manage_your_devices": "Pieslēgto ierÄĢču pārvaldÄĢba", "manage_your_oauth_connection": "OAuth savienojumu pārvaldÄĢba", "map": "Karte", + "map_assets_in_bounds": "{count} fotoattēli", "map_cannot_get_user_location": "Nevar iegÅĢt lietotāja atraÅĄanās vietu", + "map_location_dialog_yes": "Jā", "map_location_picker_page_use_location": "Izvēlēties ÅĄo atraÅĄanās vietu", "map_location_service_disabled_content": "Lai tiktu rādÄĢti jÅĢsu paÅĄreizējās atraÅĄanās vietas faili, ir jāaktivizē atraÅĄanās vietas pakalpojums. Vai vēlaties to iespējot tagad?", "map_location_service_disabled_title": "AtraÅĄanās vietas Pakalpojums atslēgts", + "map_marker_for_images": "Kartes marġieris attēliem, kas uzņemti {city}, {country}", "map_marker_with_image": "Kartes marġieris ar attēlu", "map_no_location_permission_content": "AtraÅĄanās vietas atÄŧauja ir nepiecieÅĄama, lai parādÄĢtu jÅĢsu paÅĄreizējās atraÅĄanās vietas aktÄĢvus. Vai vēlaties to atÄŧaut tagad?", "map_no_location_permission_title": "AtraÅĄanās vietas AtÄŧaujas liegtas", @@ -1314,55 +1174,58 @@ "map_settings_date_range_option_days": "Pēdējās {days} dienas", "map_settings_date_range_option_year": "Pēdējais gads", "map_settings_date_range_option_years": "Pēdējie {years} gadi", + "map_settings_dialog_title": "Kartes IestatÄĢjumi", "map_settings_include_show_archived": "IekÄŧaut Arhivētos", "map_settings_include_show_partners": "IekÄŧaut Partnerus", "map_settings_only_show_favorites": "RādÄĢt tikai izlasi", "map_settings_theme_settings": "Kartes Dizains", + "map_zoom_to_see_photos": "Attāliniet, lai redzētu fotoattēlus", "mark_all_as_read": "AtzÄĢmēt visus kā lasÄĢtus", "marked_all_as_read": "Visi atzÄĢmēti kā lasÄĢti", "matches": "AtbilstÄĢbas", - "matching_assets": "AtbilstoÅĄie aktÄĢvi", "media_type": "Faila veids", "memories": "Atmiņas", "memories_all_caught_up": "Å obrÄĢd, tas arÄĢ viss", "memories_check_back_tomorrow": "Atgriezies rÄĢt, lai skatÄĢtu vairāk atmiņu", - "memories_setting_description": "Pārvaldiet to, ko redzat savās atmiņās", "memories_start_over": "Sākt no jauna", "memories_swipe_to_close": "Pavelciet uz augÅĄu, lai aizvērtu", "memory": "Atmiņa", - "memory_lane_title": "Atmiņu josla {title}", "menu": "Izvēlne", "merge": "Apvienot", "merge_people": "Personu apvienoÅĄana", "merge_people_limit": "Vienlaikus var apvienot ne vairāk kā 5 sejas", "merge_people_prompt": "Vai vēlies apvienot ÅĄÄĢs personas? Å ÄĢ darbÄĢba ir neatceÄŧama.", "merge_people_successfully": "Personas veiksmÄĢgi apvienotas", - "merged_people_count": "Apvienoti {count, plural, one {# person} other {# people}}", "minimize": "Minimizēt", "minute": "MinÅĢte", "minutes": "MinÅĢtes", + "mirror_horizontal": "Horizontāli", + "mirror_vertical": "Vertikāli", "missing": "TrÅĢkstoÅĄie", "mobile_app": "Mobilā lietotne", "mobile_app_download_onboarding_note": "Lejupielādē papildinoÅĄo mobilo lietotni, izmantojot ÅĄÄdas izvēles iespējas", "model": "Modelis", "month": "Mēnesis", + "monthly_title_text_date_format": "MMMM g", "more": "Vairāk", "move": "Pārvietot", + "move_down": "Pārvietot lejup", "move_off_locked_folder": "Izņemt no slēgtās mapes", "move_to": "Pārvietot uz", "move_to_device_trash": "Pārvietot uz ierÄĢces atkritni", - "move_to_lock_folder_action_prompt": "{count} pievienots bloķētajai mapei", "move_to_locked_folder": "Pārvietot uz slēgto mapi", "move_to_locked_folder_confirmation": "Å ÄĢs fotogrāfijas un video tiks izņemti no visiem albumiem un bÅĢs apskatāmi tikai no slēgtās mapes", + "move_up": "Pārvietot augÅĄup", + "moved_to_archive": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz arhÄĢvu", + "moved_to_library": "Pārvietoja {count, plural, one {# failu} other {# failus}} uz bibliotēku", "moved_to_trash": "Pārvietots uz atkritni", + "multiselect_grid_edit_date_time_err_read_only": "Nevar rediÄŖÄ“t read only aktÄĢva(-u) datumu, notiek izlaiÅĄana", + "multiselect_grid_edit_gps_err_read_only": "Nevar rediÄŖÄ“t atraÅĄanās vietu read only aktÄĢva(-u) datumu, notiek izlaiÅĄana", "mute_memories": "Apklusināt atmiņas", "my_albums": "Mani albumi", - "my_immich_description": "Kopēt paÅĄreizējo lapu kā saiti uz My Immich", - "my_immich_title": "Mana Immiča saite", "name": "Vārds", "name_or_nickname": "Vārds vai iesauka", "name_required": "Nosaukums ir obligāts", - "navigate": "Navigācija", "navigate_to_time": "Pāriet uz laiku", "network_requirement_photos_upload": "Izmantot mobilo datu pārraidi, lai dublētu fotoattēlus", "network_requirement_videos_upload": "Izmantot mobilo datu pārraidi, lai dublētu video", @@ -1376,11 +1239,10 @@ "new_password": "Jaunā parole", "new_person": "Jauna persona", "new_pin_code": "Jaunais PIN kods", - "new_pin_code_subtitle": "Å ÄĢ ir pirmā reize, kad piekÄŧÅĢstat bloķētajai mapei. Izveidojiet PIN kodu, lai droÅĄi piekÄŧÅĢtu ÅĄai lapai", + "new_timeline": "Jaunā laikjosla", "new_update": "Pieejams atjauninājums", "new_user_created": "Izveidots jauns lietotājs", "new_version_available": "PIEEJAMA JAUNA VERSIJA", - "newest_first": "Vispirms jaunākie", "next": "Nākamais", "next_memory": "Nākamā atmiņa", "no": "Nē", @@ -1389,28 +1251,21 @@ "no_albums_with_name_yet": "Izskatās, ka tev vēl nav albumu ar ÅĄÄdu nosaukumu.", "no_albums_yet": "Izskatās, ka tev vēl nav neviena albuma.", "no_archived_assets_message": "Arhivē fotoattēlus un videoklipus, lai paslēptu tos no Fotoattēli skata", - "no_assets_message": "NoklikÅĄÄˇiniet, lai augÅĄupielādētu savu pirmo fotoattēlu", + "no_assets_message": "NOKLIKÅ ÄļINIET, LAI AUGÅ UPIELĀDĒTU SAVU PIRMO FOTOATTĒLU", "no_assets_to_show": "Nav uzrādāmo aktÄĢvu", "no_cast_devices_found": "Nav atrasta neviena pārraides ierÄĢce", "no_checksum_local": "Nav pieejama kontrolsumma - nevar iegÅĢt lokālos failus", "no_checksum_remote": "Nav pieejama kontrolsumma - nevar iegÅĢt attālo failu", - "no_devices": "Nav autorizētu ierÄĢču", + "no_configuration_needed": "Konfigurācija nav nepiecieÅĄama", "no_duplicates_found": "Dublikāti netika atrasti.", "no_exif_info_available": "Nav pieejama exif informācija", "no_explore_results_message": "AugÅĄupielādē vairāk fotogrāfiju, lai iepazÄĢtu savu kolekciju.", - "no_favorites_message": "Pievienojiet izlasei, lai ātri atrastu labākos attēlus un videoklipus", - "no_libraries_message": "Izveidojiet ārēju bibliotēku, lai skatÄĢtu savus fotoattēlus un videoklipus", "no_local_assets_found": "Ar ÅĄo kontrolsummu nav atrasts neviens lokālais fails", - "no_locked_photos_message": "Bloķētajā mapē esoÅĄie fotoattēli un videoklipi ir paslēpti un netiks rādÄĢti, pārlÅĢkojot vai meklējot bibliotēkā.", "no_name": "Nav nosaukuma", "no_notifications": "Nav paziņojumu", - "no_people_found": "Nav atrastas atbilstoÅĄas personas", "no_places": "Nav atraÅĄanās vietu", - "no_remote_assets_found": "Ar ÅĄo kontrolsummu nav atrasti attālināti lÄĢdzekÄŧi", "no_results": "Nav rezultātu", "no_results_description": "IzmēĪiniet sinonÄĢmu vai vispārÄĢgāku atslēgvārdu", - "no_shared_albums_message": "Izveidojiet albumu, lai kopÄĢgotu fotoattēlus un videoklipus ar cilvēkiem savā tÄĢklā", - "none": "Neviens", "not_allowed": "Nav atÄŧauts", "not_available": "Nav pieejams", "not_in_any_album": "Nav nevienā albumā", @@ -1426,26 +1281,20 @@ "notifications_setting_description": "Paziņojumu pārvaldÄĢba", "oauth": "OAuth", "obtainium_configurator": "Obtainium konfigurētājs", - "obtainium_configurator_instructions": "Izmanto Obtainium, lai instalētu un atjauninātu Android lietotni pa tieÅĄo no Immich GitHub laidiena. Izveido API atslēgu un izvēlies variantu, lai izveidotu savu Obtainium konfigurācijas saiti", - "ocr": "OCR", + "obtainium_configurator_instructions": "Izmanto Obtainium, lai instalētu un atjauninātu Android lietotni pa tieÅĄo no Immich GitHub laidiena. Izveido API atslēgu un izvēlies variantu, lai izveidotu savu Obtainium konfigurācijas saiti.", "official_immich_resources": "Oficiālie Immich resursi", "offline": "Bezsaistē", "offset": "NobÄĢde", "ok": "Labi", - "oldest_first": "Vispirms vecākie", - "on_this_device": "Å ajā ierÄĢcē", "onboarding": "UzņemÅĄana", "onboarding_locale_description": "Izvēlies vēlamo valodu. To vēlāk var mainÄĢt iestatÄĢjumos.", - "onboarding_privacy_description": "Tālāk norādÄĢtās (pēc izvēles) funkcijas ir atkarÄĢgas no ārējiem pakalpojumiem, un tās var jebkurā laikā atspējot iestatÄĢjumos.", "onboarding_server_welcome_description": "IestatÄĢsim ÅĄo instanci ar daÅžiem vispārÄĢgiem iestatÄĢjumiem.", "onboarding_theme_description": "Izvēlies savas instances krāsu motÄĢvu. To vēlāk var mainÄĢt iestatÄĢjumos.", "onboarding_user_welcome_description": "Sāksim darbu!", - "onboarding_welcome_user": "Laipni lÅĢdzam, {user}", "online": "TieÅĄsaistē", "only_favorites": "Tikai izlase", "open": "Atvērt", "open_calendar": "Atvērt kalendāru", - "open_in_browser": "Atvērt pārlÅĢkprogrammā", "open_in_map_view": "Atvērt kartes skatā", "open_in_openstreetmap": "Atvērt OpenStreetMap", "open_the_search_filters": "Atvērt meklÄ“ÅĄanas filtrus", @@ -1463,93 +1312,69 @@ "page": "Lapa", "partner": "Partneris", "partner_can_access": "{partner} var piekÄŧÅĢt", - "partner_can_access_assets": "Visi jÅĢsu fotoattēli un videoklipi, izņemot tos, kas ir arhivēti un dzēsti", "partner_can_access_location": "Fotogrāfiju uzņemÅĄanas vieta", "partner_list_user_photos": "{user} fotoattēli", + "partner_list_view_all": "ApskatÄĢt visu", "partner_page_empty_message": "JÅĢsu fotogrāfijas pagaidām nav kopÄĢgotas ar nevienu partneri.", "partner_page_no_more_users": "Nav vairs lietotāju, kurus var pievienot", + "partner_page_partner_add_failed": "Neizdevās pievienot partneri", "partner_page_select_partner": "Izvēlēties partneri", + "partner_page_shared_to_title": "KopÄĢgots uz", "partner_page_stop_sharing_content": "{partner} vairs nevarēs piekÄŧÅĢt jÅĢsu fotoattēliem.", "partner_sharing": "KoplietoÅĄana ar partneriem", "partners": "Partneri", "password": "Parole", "password_does_not_match": "Parole nesakrÄĢt", - "password_required": "NepiecieÅĄama parole", - "password_reset_success": "Paroles atiestatÄĢÅĄana veiksmÄĢga", - "past_durations": { - "days": "Pagātnes {days, plural, one {diena} other {# dienas}}", - "hours": "Pagātnes {hours, plural, one {stunda} other {# stundas}}", - "years": "IepriekÅĄÄ“jais(-ie) {years, plural, one {gads} other {# gadi}}" - }, "path": "CeÄŧÅĄ", "pattern": "Å ablons", "pause": "Pauzēt", "pause_memories": "Pauzēt atmiņas", "paused": "Nopauzēts", - "pending": "Gaida izskatÄĢÅĄanu", "people": "Personas", - "people_edits_count": "RediÄŖÄ“ts {count, plural, one {# person} other {# people}}", - "people_feature_description": "PārlÅĢkot fotoattēlus un videoklipus, kas grupēti pēc personām", - "permanent_deletion_warning": "BrÄĢdinājums par pastāvÄĢgu dzÄ“ÅĄanu", - "permanent_deletion_warning_setting_description": "RādÄĢt brÄĢdinājumu, neatgriezeniski dzÄ“ÅĄot lÄĢdzekÄŧus", - "permanently_delete": "Neatgriezeniski dzēst", - "permanently_delete_assets_count": "Neatgriezeniski dzēst {count, plural, one {asset} other {assets}}", - "permanently_delete_assets_prompt": "Vai tieÅĄÄm vēlaties neatgriezeniski dzēst {count, plural, one {this asset?} other {these # assets?}} Tas noņems arÄĢ {count, plural, one {it from its} other {them from their}} album(-iem).", - "permanently_deleted_asset": "Neatgriezeniski dzēsts aktÄĢvs", - "permanently_deleted_assets_count": "Neatgriezeniski dzēsts {count, plural, one {# asset} other {# assets}}", + "people_sidebar_description": "ParādÄĢt saiti uz personām sānu joslā", "permission": "AtÄŧauja", "permission_empty": "Tava atÄŧauja nedrÄĢkst bÅĢt tukÅĄa", + "permission_onboarding_back": "AtpakaÄŧ", + "permission_onboarding_continue_anyway": "Tomēr turpināt", + "permission_onboarding_get_started": "Darba sākÅĄana", + "permission_onboarding_go_to_settings": "Doties uz iestatÄĢjumiem", + "permission_onboarding_permission_denied": "AtÄŧauja liegta. Lai izmantotu Immich, sadaÄŧā IestatÄĢjumi pieÅĄÄˇiriet fotoattēlu un video atÄŧaujas.", + "permission_onboarding_permission_granted": "AtÄŧauja pieÅĄÄˇirta! JÅĢs esat gatavi darbam.", + "permission_onboarding_permission_limited": "AtÄŧauja ierobeÅžota. Lai atÄŧautu Immich dublÄ“ÅĄanu un varētu pārvaldÄĢt visu galeriju kolekciju, sadaÄŧā IestatÄĢjumi pieÅĄÄˇiriet fotoattēlu un video atÄŧaujas.", + "permission_onboarding_request": "Immich nepiecieÅĄama atÄŧauja skatÄĢt jÅĢsu fotoattēlus un videoklipus.", "person": "Persona", - "person_age_months": "{months, plural, one {# mēneÅĄa} other {# mēneÅĄu}} vecs", - "person_age_year_months": "1 gada, {months, plural, one {# mēneÅĄa} other {# mēneÅĄu}} vecs", - "person_age_years": "{years, plural, other {# gadu}} vecs", - "person_birthdate": "Dzimis {date}", - "person_hidden": "{name}{hidden, select, true { (slēpts)} other {}}", "person_recognized": "Persona atpazÄĢta", - "photo_shared_all_users": "Izskatās, ka esat kopÄĢgojis savus fotoattēlus ar visiem lietotājiem vai arÄĢ jums nav neviena lietotāja, ar ko kopÄĢgot.", + "person_selected": "Persona izvēlēta", "photos": "Fotoattēli", "photos_and_videos": "Fotogrāfijas un video", "photos_from_previous_years": "Fotogrāfijas no iepriekÅĄÄ“jiem gadiem", + "photos_only": "Tikai fotogrāfijas", "pick_a_location": "Izvēlies atraÅĄanās vietu", "pick_custom_range": "Pielāgots intervāls", "pick_date_range": "Izvēlies datumu intervālu", - "pin_code_changed_successfully": "PIN kods veiksmÄĢgi nomainÄĢts", - "pin_code_reset_successfully": "PIN kods veiksmÄĢgi atiestatÄĢts", - "pin_code_setup_successfully": "PIN kods veiksmÄĢgi iestatÄĢts", "pin_verification": "PIN koda pārbaude", "place": "AtraÅĄanās vieta", "places": "Vietas", - "places_count": "{count, plural, one {{count, number} Vieta} other {{count, number} Vietas}}", "play": "Atskaņot", "play_memories": "Atskaņot atmiņas", - "play_motion_photo": "Atskaņot kustÄĢgo fotoattēlu", - "play_or_pause_video": "Atskaņot vai apturēt video", - "play_original_video": "Atskaņot oriÄŖinālo video", - "play_original_video_setting_description": "PriekÅĄroka tiek dota oriÄŖinālo, nevis transkodēto video atskaņoÅĄanai. Ja oriÄŖinālais resursa saturs nav saderÄĢgs, tas, iespējams, netiks atskaņots pareizi.", - "play_transcoded_video": "Atskaņot transkodētu video", "please_auth_to_access": "Lai piekÄŧÅĢtu, lÅĢdzu, autentificējieties", "port": "Ports", - "preferences_settings_subtitle": "PārvaldÄĢt lietotnes preferences", "preferences_settings_title": "IestatÄĢjumi", "preparing": "Sagatavo", - "preset": "IepriekÅĄ iestatÄĢts", "preview": "PriekÅĄskatÄĢjums", "previous": "IepriekÅĄÄ“jais", "previous_memory": "IepriekÅĄÄ“jā atmiņa", "previous_or_next_day": "Dienu uz priekÅĄu/atpakaÄŧ", "previous_or_next_month": "Mēnesi uz priekÅĄu/atpakaÄŧ", - "previous_or_next_photo": "Foto uz priekÅĄu/atpakaÄŧ", "previous_or_next_year": "Gadu uz priekÅĄu/atpakaÄŧ", - "primary": "Primārā", "privacy": "Privātums", "profile": "Profils", "profile_drawer_app_logs": "ÅŊurnāli", + "profile_drawer_client_server_up_to_date": "Klients un serveris ir atjaunināti", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "Tikai lasÄĢÅĄanas reÅžÄĢms ir iespējots. Lai izietu, nospiediet un turiet lietotāja avatāra ikonu.", "profile_image_of_user": "{user} profila attēls", "profile_picture_set": "Profila attēls iestatÄĢts.", "public_album": "Publisks albums", - "public_share": "Publiska koplietoÅĄana", "purchase_account_info": "AtbalstÄĢtājs", "purchase_activated_subtitle": "Paldies, ka atbalstāt Immich un atvērtā koda programmatÅĢru", "purchase_activated_time": "Aktivizēts {date}", @@ -1566,6 +1391,7 @@ "purchase_individual_description_2": "AtbalstÄĢtāja statuss", "purchase_individual_title": "Individuāla", "purchase_input_suggestion": "Vai tev ir produkta atslēga? Ievadi atslēgu zemāk", + "purchase_license_subtitle": "Nopērc Immich licenci, lai atbalstÄĢtu turpmāku pakalpojuma attÄĢstÄĢbu", "purchase_lifetime_description": "Pirkums uz mÅĢÅžu", "purchase_option_title": "IEGĀDES IESPĒJAS", "purchase_panel_info_1": "Immich veidoÅĄanai ir nepiecieÅĄams daudz laika un pÅĢÄŧu, un pie tā strādā pilna laika inÅženieri, lai padarÄĢtu to pēc iespējas labāku. MÅĢsu misija ir panākt, lai atvērtā koda programmatÅĢra un ētiska uzņēmējdarbÄĢbas prakse kÄŧÅĢtu par ilgtspējÄĢgu ienākumu avotu izstrādātājiem un izveidotu privātumu respektējoÅĄu ekosistēmu ar reālām alternatÄĢvām ekspluatējoÅĄiem mākoņpakalpojumiem.", @@ -1581,109 +1407,82 @@ "purchase_server_description_2": "AtbalstÄĢtāja statuss", "purchase_server_title": "Serveris", "purchase_settings_server_activated": "Servera produkta atslēgu pārvalda administrators", - "query_asset_id": "Vaicājuma lÄĢdzekÄŧa ID", "queue_status": "Ierindo {count}/{total}", "rate_asset": "Novērtēt failu", - "rating": "ZvaigŞņu vērtējums", "rating_clear": "Noņemt vērtējumu", - "rating_count": "{count, plural, =0 {Bez vērtējuma} one {# zvaigzne} other {# zvaigznes}}", "rating_description": "RādÄĢt EXIF vērtējumu informācijas panelÄĢ", "reaction_options": "Reakcijas iespējas", "read_changelog": "LasÄĢt izmaiņu sarakstu", - "readonly_mode_disabled": "Tikai lasÄĢÅĄanas reÅžÄĢms ir atspējots", - "readonly_mode_enabled": "Tikai lasÄĢÅĄanas reÅžÄĢms ir iespējots", "ready_for_upload": "Gatavs augÅĄupielādei", - "reassign": "PārdalÄĢt", - "reassigned_assets_to_existing_person": "PārdalÄĢts {count, plural, one {# asset} other {# assets}} lietotājam {name, select, null {an existing person} other {{name}}}", - "reassigned_assets_to_new_person": "PārdalÄĢts {count, plural, one {# asset} other {# assets}} jaunai personai", - "reassing_hint": "PieÅĄÄˇirt atlasÄĢtos aktÄĢvus esoÅĄai personai", - "recent": "Nesen", - "recent_searches": "Nesen veiktās meklÄ“ÅĄanas", - "recently_added": "Nesen pievienots", + "recent_albums": "Nesenie albumi", "recently_added_page_title": "Nesen Pievienotais", - "recently_taken": "Nesen uzņemts", "refresh": "Atsvaidzināt", - "refresh_encoded_videos": "Atsvaidzināt kodētos videoklipus", "refresh_faces": "Atsvaidzināt sejas", "refresh_metadata": "Atsvaidzināt metadatus", "refresh_thumbnails": "Atsvaidzināt sÄĢktēlus", "refreshed": "Atsvaidzināts", - "refreshing_encoded_video": "Notiek kodētā video atsvaidzinÄÅĄana", + "refreshes_every_file": "Vēlreiz nolasa esoÅĄos un jaunos failus", "refreshing_faces": "Atsvaidzina sejas", "refreshing_metadata": "Atsvaidzina metadatus", - "regenerating_thumbnails": "SÄĢktēlu atkārtota ÄŖenerÄ“ÅĄana", "remote": "Attāli", - "remote_assets": "Attālinātie aktÄĢvi", - "remote_media_summary": "Attālās multivides kopsavilkums", "remove": "Noņemt", - "remove_assets_album_confirmation": "Vai tieÅĄÄm vēlaties noņemt {count, plural, one {# asset} other {# assets}} no albuma?", - "remove_assets_shared_link_confirmation": "Vai tieÅĄÄm vēlaties noņemt {count, plural, one {# asset} other {# assets}} no ÅĄÄĢs koplietotās saites?", "remove_assets_title": "Izņemt failus?", "remove_custom_date_range": "Novākt pielāgoto datuma intervālu", + "remove_deleted_assets": "Izņemt dzēstos failus", "remove_from_album": "Noņemt no albuma", "remove_from_album_action_prompt": "No albuma izņemti {count} faili", "remove_from_favorites": "Noņemt no izlases", "remove_from_lock_folder_action_prompt": "No slēgtās mapes izņemti {count} faili", "remove_from_locked_folder": "Izņemt no slēgtās mapes", - "remove_from_locked_folder_confirmation": "Vai tieÅĄÄm vēlaties pārvietot ÅĄos fotoattēlus un videoklipus no bloķētās mapes? Tie bÅĢs redzami jÅĢsu bibliotēkā.", - "remove_from_shared_link": "Noņemt no koplietotās saites", "remove_memory": "Noņemt atmiņu", "remove_photo_from_memory": "Noņemt fotogrāfiju no ÅĄÄĢs atmiņas", - "remove_tag": "Noņemt birku", + "remove_url": "Noņemt URL", "remove_user": "Noņemt lietotāju", "removed_api_key": "Noņēma API atslēgu: {name}", "removed_from_archive": "Noņēma no arhÄĢva", "removed_from_favorites": "Noņēma no izlases", "removed_from_favorites_count": "{count, plural, other {Izņēma #}} no izlases", "removed_memory": "Noņēma atmiņu", - "removed_tagged_assets": "Noņemta atzÄĢme no {count, plural, one {# asset} other {# assets}}", + "removed_photo_from_memory": "Noņēma fotogrāfiju no atmiņas", "rename": "Pārsaukt", + "repair": "Remonts", + "replace_with_upload": "Aizstāt ar augÅĄupielādi", "repository": "Repozitorijs", - "require_password": "NepiecieÅĄama parole", + "require_user_to_change_password_on_first_login": "PieprasÄĢt lietotājam mainÄĢt paroli pēc pirmās pieteikÅĄanās", "rescan": "Pārskenēt atkārtoti", "reset": "AtiestatÄĢt", "reset_password": "AtiestatÄĢt paroli", "reset_people_visibility": "AtiestatÄĢt personu redzamÄĢbu", "reset_pin_code": "AtiestatÄĢt PIN kodu", - "reset_pin_code_description": "Ja esat aizmirsis PIN kodu, varat sazināties ar servera administratoru, lai to atiestatÄĢtu", - "reset_pin_code_with_password": "JÅĢs vienmēr varat atiestatÄĢt savu PIN kodu ar paroli", "reset_sqlite": "AtiestatÄĢt SQLite datubāzi", - "reset_sqlite_clear_app_data": "NotÄĢrÄĢt datus", - "reset_sqlite_confirmation": "Vai tieÅĄÄm vēlaties notÄĢrÄĢt lietotnes datus? Tas noņems visus iestatÄĢjumus un jÅĢs izrakstÄĢsieties.", - "reset_sqlite_confirmation_note": "PiezÄĢme: pēc notÄĢrÄĢÅĄanas jums bÅĢs jārestartē lietotne.", - "reset_sqlite_done": "Lietotnes dati ir notÄĢrÄĢti. LÅĢdzu, restartējiet Immich un piesakieties vēlreiz.", - "reset_sqlite_success": "SQLite datubāze veiksmÄĢgi atiestatÄĢta", "reset_to_default": "AtiestatÄĢt noklusējuma iestatÄĢjumus", - "resolution": "RezolÅĢcija", "resolve_duplicates": "Atrisināt dublÄ“ÅĄanās gadÄĢjumus", "resolved_all_duplicates": "Visi dublikāti ir atrisināti", "restore": "Atjaunot", "restore_all": "Atjaunot visu", + "restore_trash_action_prompt": "{count} atjaunoti no atkritnes", "restore_user": "Atjaunot lietotāju", - "restored_asset": "Atjaunots aktÄĢvs", "resume": "Turpināt", - "resume_paused_jobs": "Atsākt {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "Atkārtot augÅĄupielādi", "review_duplicates": "PārskatÄĢt dublikātus", "review_large_files": "PārskatÄĢt lielos failus", "role": "Loma", "role_editor": "Redaktors", "role_viewer": "SkatÄĢtājs", - "running": "SkrieÅĄana", "save": "Saglabāt", + "save_to_gallery": "Saglabāt galerijā", "saved": "Saglabāts", "saved_api_key": "API atslēga saglabāta", "saved_profile": "Profils saglabāts", "saved_settings": "IestatÄĢjumi saglabāti", "say_something": "Teikt kaut ko", "scaffold_body_error_occurred": "Radās kÄŧÅĢda", - "scaffold_body_error_unrecoverable": "Radusies neatkopjama kÄŧÅĢda. LÅĢdzu, kopÄĢgojiet kÄŧÅĢdu un steka izsekoÅĄanas informāciju pakalpojumā Discord vai GitHub, lai mēs varētu palÄĢdzēt. Ja jums tiek ieteikts, varat notÄĢrÄĢt lietotnes datus, kas norādÄĢti tālāk.", "scan": "Skenēt", "scan_all_libraries": "Skenēt visas bibliotēkas", "scan_library": "Skenēt", + "scan_settings": "SkenÄ“ÅĄanas iestatÄĢjumi", "scanning": "Skenē", "scanning_for_album": "Skenē albumu...", - "screencast_mode_description": "RādÄĢt tastatÅĢras un peles notikumu indikatorus ekrānā", - "screencast_mode_title": "Pārslēgt ekrāna apraides reÅžÄĢmu", "search": "Meklēt", "search_albums": "Meklēt albumus", "search_by_context": "Meklēt pēc konteksta", @@ -1691,9 +1490,6 @@ "search_by_description_example": "Pārgājiens LÄĢgatnē", "search_by_filename": "Meklēt pēc faila nosaukuma vai paplaÅĄinājuma", "search_by_filename_example": "piemēram, IMG_1234.JPG vai PNG", - "search_by_ocr": "Meklēt pēc OCR", - "search_by_ocr_example": "Latte", - "search_camera_lens_model": "Meklēt objektÄĢva modeli...", "search_camera_make": "Meklēt pēc fotokameras raÅžotāja...", "search_camera_model": "Meklēt pēc fotokameras modeÄŧa...", "search_city": "Meklēt pēc pilsētas...", @@ -1701,67 +1497,58 @@ "search_filter_apply": "Lietot filtru", "search_filter_camera_title": "Izvēlies fotokameras veidu", "search_filter_date": "Datums", - "search_filter_date_interval": "{start} lÄĢdz {end}", - "search_filter_date_title": "Atlasiet datumu diapazonu", "search_filter_display_option_not_in_album": "Nav albumā", - "search_filter_display_options": "AttēloÅĄanas opcijas", "search_filter_filename": "Meklēt pēc faila nosaukuma", "search_filter_location": "AtraÅĄanās vieta", "search_filter_location_title": "Izvēlies atraÅĄanās vietu", "search_filter_media_type": "Multivides veids", "search_filter_media_type_title": "Izvēlies multivides veidu", - "search_filter_people_title": "Izvēlēties cilvēkus", "search_filter_star_rating": "ZvaigznÄĢÅĄu vērtējums", - "search_filter_tags_title": "Izvēlēties birkas", - "search_for": "Meklēt", "search_for_existing_person": "Meklēt esoÅĄu personu", - "search_no_more_result": "Vairs nav rezultātu", "search_no_people": "Nav personu", "search_no_people_named": "Nav personas ar vārdu \"{name}\"", - "search_no_result": "Nav atrasts neviens rezultāts, mēĪiniet citu meklÄ“ÅĄanas terminu vai kombināciju", "search_options": "MeklÄ“ÅĄanas iespējas", - "search_page_search_photos_videos": "Meklēt savus fotoattēlus un videoklipus", + "search_page_categories": "Kategorijas", + "search_page_motion_photos": "KustÄĢbu Fotoattēli", + "search_page_no_objects": "Informācija par Objektiem nav pieejama", + "search_page_no_places": "Nav pieejama Informācija par Vietām", + "search_page_screenshots": "Ekrānuzņēmumi", + "search_page_selfies": "Selfiji", + "search_page_things": "Lietas", "search_page_view_all_button": "ApskatÄĢt visu", + "search_page_your_activity": "JÅĢsu aktivitāte", + "search_page_your_map": "JÅĢsu Karte", "search_people": "Meklēt personas", - "search_places": "Meklēt vietas", "search_rating": "Meklēt pēc vērtējuma...", + "search_result_page_new_search_hint": "Jauns Meklējums", "search_settings": "Meklēt iestatÄĢjumos", "search_state": "Meklēt pēc ÅĄtata...", - "search_tags": "Meklēt birkas...", - "search_timezone": "Meklēt laika joslu...", + "search_suggestion_list_smart_search_hint_1": "Viedā meklÄ“ÅĄana pēc noklusējuma ir iespējota, lai meklētu metadatos, izmanto sintaksi ", + "search_suggestion_list_smart_search_hint_2": "m:jÅĢsu-meklÄ“ÅĄanas-frāze", "search_type": "MeklÄ“ÅĄanas veids", "search_your_photos": "Meklēt fotoattēlos", "searching_locales": "Meklē lokalizācijas...", "second": "Sekunde", "see_all_people": "SkatÄĢt visas personas", - "select": "AtlasÄĢt", "select_album": "Izvēlies albumu", "select_album_cover": "Izvēlieties albuma vāciņu", "select_albums": "Izvēlies albumus", - "select_all": "AtlasÄĢt visu", "select_all_duplicates": "AtlasÄĢt visus paturÄ“ÅĄanai", "select_avatar_color": "Izvēlies avatāra krāsu", - "select_count": "{count, plural, one {Izvēlēties #} other {Izvēlēties #}}", - "select_cutoff_date": "AtlasÄĢt beigu datumu", "select_face": "Izvēlies seju", - "select_featured_photo": "AtlasÄĢt piedāvāto fotoattēlu", "select_from_computer": "Izvēlēties no datora", "select_keep_all": "AtzÄĢmēt visus paturÄ“ÅĄanai", + "select_library_owner": "Izvēlies bibliotēkas ÄĢpaÅĄnieku", "select_new_face": "Izvēlies jaunu seju", "select_people": "Izvēlies personas", "select_person": "Izvēlies personu", - "select_person_to_tag": "Izvēlies atzÄĢmējamo personu", "select_photos": "Fotoattēlu Izvēle", "select_trash_all": "AtzÄĢmēt visus dzÄ“ÅĄanai", + "select_user_for_sharing_page_err_album": "Neizdevās izveidot albumu", "selected": "Izvēlētie", - "selected_count": "{count, plural, other {# atlasÄĢts}}", "selected_gps_coordinates": "Izvēlētās ÄŖeogrāfiskās koordinātas", - "send_message": "SÅĢtÄĢt ziņojumu", - "send_welcome_email": "NosÅĢtÄĢt apsveikuma e-pastu", - "server_endpoint": "Servera galapunkts", "server_info_box_app_version": "Aplikācijas Versija", "server_info_box_server_url": "Servera URL", - "server_offline": "Serveris bezsaistē", "server_online": "Serveris tieÅĄsaistē", "server_privacy": "Servera privātums", "server_restarting_description": "Å ÄĢ lapa pēc brÄĢÅža tiks atjaunināta.", @@ -1769,53 +1556,52 @@ "server_stats": "Servera statistika", "server_update_available": "Pieejams servera atjauninājums", "server_version": "Servera versija", - "set": "Komplekts", "set_as_album_cover": "IestatÄĢt kā albuma vāciņu", - "set_as_featured_photo": "IestatÄĢt kā izceltu fotoattēlu", "set_as_profile_picture": "IestatÄĢt kā profila attēlu", "set_date_of_birth": "IestatÄĢt dzimÅĄanas datumu", - "set_profile_picture": "IestatÄĢt profila attēlu", - "set_slideshow_to_fullscreen": "IestatÄĢt slaidrādi pilnekrāna reÅžÄĢmā", - "set_stack_primary_asset": "IestatÄĢt kā primāro lÄĢdzekli", - "setting_image_navigation_enable_subtitle": "Ja ÅĄÄĢ opcija ir iespējota, varat pāriet uz iepriekÅĄÄ“jo/nākamo attēlu, pieskaroties ekrāna kreisajai/labajai ceturtdaÄŧai.", - "setting_image_navigation_enable_title": "Pieskarieties, lai pārvietotos", - "setting_image_navigation_title": "Attēlu navigācija", "setting_image_viewer_help": "DetaÄŧu skatÄĢtājs vispirms ielādē mazo sÄĢktēlu, pēc tam ielādē vidēja lieluma priekÅĄskatÄĢjumu (ja iespējots), visbeidzot ielādē oriÄŖinālu (ja iespējots).", "setting_image_viewer_original_subtitle": "Iespējot sākotnējā pilnas izÅĄÄˇirtspējas attēla (liels!) ielādi. Atspējot, lai samazinātu datu lietojumu (gan tÄĢklā, gan ierÄĢces keÅĄatmiņā).", "setting_image_viewer_original_title": "Ielādēt oriÄŖinālo attēlu", "setting_image_viewer_preview_subtitle": "Iespējojiet vidējas izÅĄÄˇirtspējas attēla ielādÄ“ÅĄanu. Atspējojiet vai nu tieÅĄu oriÄŖināla ielādi, vai izmantojiet tikai sÄĢktēlu.", "setting_image_viewer_preview_title": "Ielādēt priekÅĄskatÄĢjuma attēlu", + "setting_image_viewer_title": "Attēli", "setting_languages_apply": "Lietot", - "setting_languages_subtitle": "MainÄĢt lietotnes valodu", + "setting_notifications_notify_failures_grace_period": "Paziņot par fona dublÄ“ÅĄanas kÄŧÅĢmēm: {duration}", + "setting_notifications_notify_hours": "{count} stundas", + "setting_notifications_notify_immediately": "nekavējoties", "setting_notifications_notify_minutes": "{count} minÅĢtes", + "setting_notifications_notify_never": "nekad", "setting_notifications_notify_seconds": "{count} sekundes", + "setting_notifications_single_progress_subtitle": "Detalizēta augÅĄupielādes progresa informācija par katru failu", + "setting_notifications_single_progress_title": "RādÄĢt fona dublējuma detalizēto progresu", "setting_notifications_subtitle": "Paziņojumu preferenču pielāgoÅĄana", + "setting_notifications_total_progress_subtitle": "Kopējais augÅĄupielādes progress (pabeigti/kopējie faili)", + "setting_notifications_total_progress_title": "RādÄĢt fona dublējuma kopējo progresu", "setting_video_viewer_auto_play_subtitle": "Automātiski sākt videoklipu atskaņoÅĄanu, kad tie tiek atvērti", "setting_video_viewer_auto_play_title": "Automātiska video atskaņoÅĄana", "setting_video_viewer_looping_title": "Cikliski", "setting_video_viewer_original_video_subtitle": "Straumējot video no servera, izmantot oriÄŖinālu, pat ja ir pieejama pārkodÄ“ÅĄana. Tas var izraisÄĢt buferÄ“ÅĄanu. Lokāli pieejamie video tiek atskaņoti oriÄŖinālajā kvalitātē, neatkarÄĢgi no ÅĄÄĢs iestatÄĢjuma.", "setting_video_viewer_original_video_title": "Vienmēr izmantot oriÄŖinālo video", "settings": "IestatÄĢjumi", - "settings_saved": "IestatÄĢjumi saglabāti", + "settings_require_restart": "LÅĢdzu, restartējiet Immich, lai lietotu ÅĄo iestatÄĢjumu", "setup_pin_code": "UzstādÄĢt PIN kodu", "share": "KopÄĢgot", + "share_add_photos": "Pievienot fotoattēlus", + "share_assets_selected": "{count} atlasÄĢti", "share_dialog_preparing": "Notiek sagatavoÅĄana...", - "share_link": "KopÄĢgot saiti", "shared": "KopÄĢgots", "shared_album_activities_input_disable": "Komentāri atslēgti", "shared_album_activity_remove_content": "Vai vēlaties ÅĄo aktivitāti dzēst?", "shared_album_activity_remove_title": "Dzēst Aktivitāti", "shared_album_section_people_action_error": "KÄŧÅĢme pametot/noņemot no albuma", + "shared_album_section_people_action_leave": "Noņemt lietotāju no albuma", + "shared_album_section_people_action_remove_user": "Noņemt lietotāju no albuma", "shared_album_section_people_title": "PERSONAS", - "shared_by": "KopÄĢgots ar", - "shared_by_user": "KopÄĢgojis {user}", - "shared_by_you": "JÅĢsu kopÄĢgotais", - "shared_from_partner": "Fotoattēli no {partner}", "shared_intent_upload_button_progress_text": "AugÅĄupielādēti {current} / {total}", "shared_link_app_bar_title": "KopÄĢgotas Saites", "shared_link_clipboard_copied_massage": "Ievietots starpliktuvē", + "shared_link_clipboard_text": "Saite: {link}\nParole: {password}", "shared_link_create_error": "KÄŧÅĢda izveidojot kopÄĢgoÅĄanas saiti", - "shared_link_custom_url_description": "PiekÄŧÅĢstiet ÅĄai koplietotajai saitei, izmantojot pielāgotu URL", "shared_link_edit_description_hint": "Ievadiet kopÄĢgojuma aprakstu", "shared_link_edit_expire_after_option_day": "1 diena", "shared_link_edit_expire_after_option_days": "{count} dienas", @@ -1827,7 +1613,7 @@ "shared_link_edit_expire_after_option_year": "{count} gads", "shared_link_edit_password_hint": "IerakstÄĢt kopÄĢgojuma paroli", "shared_link_edit_submit_button": "Atjaunināt saiti", - "shared_link_error_server_url_fetch": "Nevar iegÅĢt servera url", + "shared_link_error_server_url_fetch": "Nevarēja ienest servera URL", "shared_link_expires_day": "DerÄĢguma termiÅ†ÅĄ beigsies pēc {count} dienas", "shared_link_expires_days": "DerÄĢguma termiÅ†ÅĄ beigsies pēc {count} dienām", "shared_link_expires_hour": "DerÄĢguma termiÅ†ÅĄ beigsies pēc {count} stundas", @@ -1840,16 +1626,16 @@ "shared_link_individual_shared": "Individuāli kopÄĢgots", "shared_link_info_chip_metadata": "EXIF", "shared_link_manage_links": "PārvaldÄĢt KopÄĢgotās saites", - "shared_link_options": "KopÄĢgotās saites opcijas", - "shared_link_password_description": "Lai piekÄŧÅĢtu ÅĄai koplietotajai saitei, nepiecieÅĄama parole", "shared_links": "KopÄĢgotās saites", - "shared_links_description": "KopÄĢgot fotoattēlus un videoklipus, izmantojot saiti", - "shared_photos_and_videos_count": "{assetCount, plural, other {# koplietotie fotoattēli un videoklipi.}}", - "shared_with_me": "KopÄĢgots ar mani", "shared_with_partner": "KopÄĢgots ar {partner}", "sharing": "KopÄĢgoÅĄana", "sharing_enter_password": "LÅĢdzu, ievadi paroli, lai apskatÄĢtu ÅĄo lapu.", - "shift_to_permanent_delete": "nospiediet ⇧, lai neatgriezeniski dzēstu resursu", + "sharing_page_album": "KopÄĢgotie albumi", + "sharing_page_description": "Izveidojiet koplietojamus albumus, lai kopÄĢgotu fotoattēlus un videoklipus ar JÅĢsu tÄĢkla lietotājiem.", + "sharing_page_empty_list": "TUKÅ S SARAKSTS", + "sharing_sidebar_description": "ParādÄĢt saiti uz kopÄĢgoÅĄanu sānu joslā", + "sharing_silver_appbar_create_shared_album": "Izveidot kopÄĢgotu albumu", + "sharing_silver_appbar_share_partner": "DalÄĢties ar partneri", "show_album_options": "RādÄĢt albuma iespējas", "show_albums": "RādÄĢt albumus", "show_all_people": "RādÄĢt visas personas", @@ -1881,7 +1667,6 @@ "size": "Izmērs", "skip_to_content": "Pāriet uz saturu", "skip_to_folders": "Pāriet uz mapēm", - "skip_to_tags": "Pāriet uz tagiem", "slideshow": "SlÄĢdrāde", "slideshow_repeat": "Atkārtot slÄĢdrādi", "slideshow_repeat_description": "Beidzoties slÄĢdrādei, atgriezties pie tās sākuma", @@ -1890,87 +1675,59 @@ "sort_created": "Izveides datums", "sort_items": "Vienumu skaits", "sort_modified": "Izmaiņu datums", + "sort_newest": "Jaunākā fotogrāfija", "sort_oldest": "Vecākā fotogrāfija", "sort_people_by_similarity": "Sakārtot personas pēc lÄĢdzÄĢbas", "sort_recent": "Nesenākā fotogrāfija", "sort_title": "Nosaukums", "source": "Pirmkods", "stack": "Apvienot kaudzē", - "stack_action_prompt": "{count} sakrauts", "stack_duplicates": "Apvienot dublikātus kaudzē", - "stack_selected_photos": "Sakraut atlasÄĢtos fotoattēlus kaudzē", - "stacked_assets_count": "Sakrauts {count, plural, one {# asset} other {# assets}}", - "stacktrace": "Stacktrack", "start": "Sākt", "start_date": "Sākuma datums", "start_date_before_end_date": "Sākuma datumam jābÅĢt pirms beigu datuma", "state": "Å tats", "status": "Statuss", - "stop_casting": "Pārtraukt apraidi", - "stop_motion_photo": "Apturētas kustÄĢbas foto", "stop_photo_sharing": "Beigt kopÄĢgot jÅĢsu fotogrāfijas?", "stop_photo_sharing_description": "{partner} vairs nevarēs piekÄŧÅĢt tavām fotogrāfijām.", "stop_sharing_photos_with_user": "Pārtraukt dalÄĢties ar fotogrāfijām ar ÅĄo lietotāju", "storage": "Vieta krātuvē", "storage_label": "Glabātuves nosaukums", - "storage_quota": "UzglabÄÅĄanas kvota", "storage_usage": "{used} no {available} izmantoti", "submit": "Iesniegt", - "success": "Panākumi", "suggestions": "Ieteikumi", "sunrise_on_the_beach": "Saullēkts pludmalē", "support": "Atbalsts", "support_and_feedback": "Atbalsts un atsauksmes", "support_third_party_description": "Tavu Immich instalāciju ir sagatavojusi treÅĄÄ puse. Problēmas, ar kurām sastopies, var bÅĢt saistÄĢtas ar ÅĄo pakotni, tāpēc lÅĢdzu vispirms ziņo par tām, izmantojot zemāk norādÄĢtās saites.", "supporter": "AtbalstÄĢtājs", - "swap_merge_direction": "ApmainÄĢt apvienoÅĄanas virzienu", "sync": "Sinhronizēt", - "sync_albums": "Sinhronizēt albumus", "sync_local": "Sinhronizēt lokāli", - "sync_remote": "Sinhronizēt tālvadÄĢbu", "sync_status": "Sinhronizācijas statuss", "sync_status_subtitle": "SkatÄĢt un pārvaldÄĢt sinhronizācijas sistēmu", - "sync_upload_album_setting_subtitle": "Izveidojiet un augÅĄupielādējiet savus fotoattēlus un videoklipus atlasÄĢtajos albumos pakalpojumā Immich", - "system_theme": "Sistēmas tēma", - "system_theme_command_description": "Izmantot sistēmas tēmu ({value})", - "tag": "Birka", - "tag_assets": "AtzÄĢmēt aktÄĢvus", - "tag_created": "Izveidoja birku: {tag}", - "tag_face": "AtzÄĢmēt seju", - "tag_feature_description": "Fotoattēlu un videoklipu pārlÅĢkoÅĄana, kas grupēti pēc loÄŖiskām tagu tēmām", - "tag_people": "AtzÄĢmēt cilvēkus", - "tag_updated": "Atjaunināta atzÄĢme: {tag}", - "tagged_assets": "AtzÄĢmēts ar {count, plural, one {# asset} other {# assets}}", - "tags": "Birkas", - "tap_to_run_job": "Pieskarieties, lai palaistu uzdevumu", - "template": "Veidne", "text_recognition": "Teksta atpazÄĢÅĄana", "theme": "Dizains", - "theme_selection": "Tēmas izvēle", - "theme_selection_description": "Automātiski iestatÄĢt tēmu uz gaiÅĄu vai tumÅĄu, pamatojoties uz jÅĢsu pārlÅĢkprogrammas sistēmas preferencēm", "theme_setting_asset_list_storage_indicator_title": "RādÄĢt krātuves indikatoru uz attēliem reÅžga skatā", "theme_setting_asset_list_tiles_per_row_title": "Failu skaits rindā ({count})", "theme_setting_colorful_interface_subtitle": "Piemērot pamatkrāsu fona virsmām.", "theme_setting_colorful_interface_title": "Krāsaina saskarne", + "theme_setting_image_viewer_quality_subtitle": "Attēlu skatÄĢtāja detaÄŧu kvalitātes pielāgoÅĄana", + "theme_setting_image_viewer_quality_title": "Attēlu skatÄĢtāja kvalitāte", "theme_setting_primary_color_subtitle": "Izvēlies krāsu galvenajām darbÄĢbām un akcentiem.", "theme_setting_primary_color_title": "Pamatkrāsa", "theme_setting_system_primary_color_title": "Izmantot sistēmas krāsu", "theme_setting_system_theme_switch": "Automātisks (sekot sistēmas iestatÄĢjumiem)", + "theme_setting_theme_subtitle": "Izvēlieties programmas dizaina iestatÄĢjumu", + "theme_setting_three_stage_loading_subtitle": "TrÄĢspakāpju ielāde var palielināt ielādÄ“ÅĄanas veiktspēju, bet izraisa ievērojami lielāku tÄĢkla noslodzi", + "theme_setting_three_stage_loading_title": "Iespējot trÄĢspakāpju ielādi", "then": "Tad", "they_will_be_merged_together": "Tās tiks apvienotas", "third_party_resources": "TreÅĄo puÅĄu resursi", - "time": "Laiks", - "time_based_memories": "Laikam atbilstoÅĄas atmiņas", - "time_based_memories_duration": "Katra attēla rādÄĢÅĄanas laiks sekundēs.", "timeline": "Laika skala", "timezone": "Laika zona", "to_archive": "Arhivēt", "to_change_password": "MainÄĢt paroli", "to_favorite": "Pievienot izlasei", - "to_login": "PieteikÅĄanās", - "to_multi_select": "atlasÄĢt vairākus vienumus", - "to_parent": "Dodieties pie vecākiem", - "to_select": "izvēlēties", "to_trash": "Pārvietot uz atkritni", "toggle_settings": "Pārslēgt iestatÄĢjumus", "toggle_theme_description": "Pārslēgt motÄĢvu", @@ -1981,80 +1738,60 @@ "trash_all": "Dzēst visu", "trash_count": "Pārvietot uz atkritni {count, number}", "trash_delete_asset": "Pārvietot uz atkritni/dzēst failu", + "trash_emptied": "Atkritne iztukÅĄota", "trash_no_results_message": "Å eit parādÄĢsies uz atkritni pārvietotās fotogrāfijas un video.", "trash_page_delete_all": "Dzēst Visu", + "trash_page_empty_trash_dialog_content": "Vai vēlaties iztukÅĄot savus izmestos failus? Tie tiks neatgriezeniski izņemti no Immich", "trash_page_info": "Atkritnes vienumi tiks neatgriezeniski dzēsti pēc {days} dienām", + "trash_page_no_assets": "Atkritnē nav aktÄĢvu", + "trash_page_restore_all": "Atjaunot Visu", + "trash_page_select_assets_btn": "AtlasÄĢt aktÄĢvus", + "trash_page_title": "Atkritne ({count})", "trashed_items_will_be_permanently_deleted_after": "Faili no atkritnes tiks neatgriezeniski dzēsti pēc {days, plural, one {# dienas} other {# dienām}}.", - "trigger": "Trigeris", "trigger_asset_uploaded": "Fails augÅĄupielādēts", - "trigger_asset_uploaded_description": "Aktivizējas, kad tiek augÅĄupielādēts jauns resurs", + "trigger_description": "Notikums, kas uzsāk darba plÅĢsmu", "trigger_person_recognized": "Persona atpazÄĢta", - "trigger_person_recognized_description": "Aktivizējas, kad tiek atklāta persona", "troubleshoot": "Problēmu novērÅĄana", "type": "Veids", "unable_to_change_pin_code": "Neizdevās nomainÄĢt PIN kodu", - "unable_to_check_version": "Nevar pārbaudÄĢt lietotnes vai servera versiju", "unable_to_setup_pin_code": "Neizdevās uzstādÄĢt PIN kodu", "unarchive": "Atarhivēt", - "unarchive_action_prompt": "{count} noņemts no arhÄĢva", - "unarchived_count": "{count, plural, other {Unarhived #}}", - "undo": "Atsaukt", "unfavorite": "Noņemt no izlases", - "unfavorite_action_prompt": "{count} noņemts no izlases", "unhide_person": "Atcelt personas slēpÅĄanu", "unknown": "Nezināms", "unknown_country": "Nezināma Valsts", "unknown_date": "Nezināms datums", "unknown_year": "Nezināms gads", "unlimited": "NeierobeÅžots", - "unlink_motion_video": "AtsaistÄĢt kustÄĢbas video", - "unlink_oauth": "AtsaistÄĢt OAuth", - "unlinked_oauth_account": "AtsaistÄĢts OAuth konts", - "unmute_memories": "Ieslēgt atmiņu skaņu", "unnamed_album": "Albums bez nosaukuma", - "unnamed_album_delete_confirmation": "Vai tieÅĄÄm vēlaties dzēst ÅĄo albumu?", - "unnamed_share": "Nenosaukta koplietoÅĄanas daÄŧa", "unsaved_change": "Nesaglabāta izmaiņa", "unselect_all": "Atcelt visu atlasi", "unselect_all_duplicates": "AtlasÄĢt visus dzÄ“ÅĄanai", "unstack": "At-Stekot", - "unstack_action_prompt": "{count} nesakrauts", - "unstacked_assets_count": "Nesakrauts {count, plural, one {# asset} other {# assets}}", "unsupported_field_type": "NesatbalstÄĢts lauka tips", - "unsupported_file_type": "Failu {file} nevar augÅĄupielādēt, jo tā faila tips {type} netiek atbalstÄĢts.", - "untagged": "NeatzÄĢmēts", - "up_next": "Nākamais", + "untitled_workflow": "Nenosaukta darba plÅĢsma", "update_location_action_prompt": "NorādÄĢt {count} izvēlēto failu atraÅĄanās vietu kā:", "updated_at": "Atjaunināts", "updated_password": "Parole ir atjaunināta", "upload": "AugÅĄupielādēt", - "upload_concurrency": "AugÅĄupielādes vienlaicÄĢgums", - "upload_details": "AugÅĄupielādes informācija", - "upload_error_with_count": "AugÅĄupielādes kÄŧÅĢda {count, plural, one {# asset} other {# assets}}", - "upload_errors": "AugÅĄupielāde pabeigta ar {count, plural, one {# error} other {# errors}}, atsvaidziniet lapu, lai skatÄĢtu jaunos augÅĄupielādes resursus.", + "upload_dialog_info": "Vai vēlaties veikt izvēlētā(-o) aktÄĢva(-u) dublējumu uz servera?", + "upload_dialog_title": "AugÅĄupielādēt AktÄĢvu", "upload_finished": "AugÅĄupielāde pabeigta", - "upload_progress": "AtlikuÅĄais {remaining, number} - Apstrādātais {processed, number}/{total, number}", - "upload_skipped_duplicates": "Izlaists {count, plural, one {# duplikats aktÄĢvs} other {# duplikats aktÄĢvs}}", "upload_status_duplicates": "Dublikāti", "upload_status_errors": "KÄŧÅĢdas", "upload_status_uploaded": "AugÅĄupielādēts", - "upload_success": "AugÅĄupielāde veiksmÄĢga, atsvaidziniet lapu, lai redzētu jaunos augÅĄupielādes resursus.", "upload_to_immich": "AugÅĄupielādēt Immich ({count})", "uploading": "AugÅĄupielādē", "uploading_media": "AugÅĄupielādē failus", "url": "URL", "usage": "Lietojums", "use_biometric": "Izmantot biometrisko autentifikāciju", - "use_browser_locale": "Izmantot pārlÅĢkprogrammas lokalizāciju", - "use_browser_locale_description": "Formatējiet datumus, laikus un skaitÄŧus, pamatojoties uz jÅĢsu pārlÅĢkprogrammas lokalizāciju", "use_current_connection": "izmantot paÅĄreizējo savienojumu", "use_custom_date_range": "Izmantot pielāgotu datuma intervālu", "user": "Lietotājs", "user_has_been_deleted": "Å is lietotājs ir dzēsts.", "user_id": "Lietotāja ID", - "user_liked": "{user} atzÄĢmēja ar \"patÄĢk\" {type, select, photo {this photo} video {this video} asset {this asset} other {it}}", "user_pin_code_settings": "PIN kods", - "user_pin_code_settings_description": "Pārvaldiet savu PIN kodu", "user_privacy": "Lietotāju privātums", "user_purchase_settings": "Iegādāties", "user_purchase_settings_description": "Pirkuma pārvaldÄĢba", @@ -2063,10 +1800,10 @@ "user_usage_stats_description": "SkatÄĢt konta lietojuma statistiku", "username": "Lietotājvārds", "users": "Lietotāji", - "users_added_to_album_count": "Albumam pievienots {count, plural, one {# user} other {# users}}", "utilities": "RÄĢki", "validate": "PārbaudÄĢt", "validate_endpoint_error": "LÅĢdzu, ievadi derÄĢgu URL", + "validation_error": "Pārbaudes kÄŧÅĢda", "variables": "MainÄĢgie", "version": "Versija", "version_announcement_closing": "Tavs draugs, Alekss", @@ -2074,10 +1811,9 @@ "version_history": "Versiju vēsture", "version_history_item": "{version} uzstādÄĢta {date}", "video": "Videoklips", - "video_hover_setting": "Atskaņot video sÄĢktēlu, virzot kursoru virs tā", "video_hover_setting_description": "Atskaņot video sÄĢktēlu, kad peles kursors atrodas virs objekta. Pat ja funkcija ir atspējota, atskaņoÅĄanu var sākt, uzvirzot kursoru uz atskaņoÅĄanas ikonas.", "videos": "Videoklipi", - "videos_count": "{count, plural, one {# Video} other {# Video}}", + "videos_only": "Tikai video", "view": "ApskatÄĢt", "view_album": "SkatÄĢt Albumu", "view_all": "ApskatÄĢt visu", @@ -2086,16 +1822,19 @@ "view_details": "ApskatÄĢt informāciju", "view_in_timeline": "SkatÄĢt laika skalā", "view_link": "SkatÄĢt saiti", + "view_links": "SkatÄĢt saites", "view_name": "ApskatÄĢt", "view_next_asset": "SkatÄĢt nākamo failu", "view_previous_asset": "SkatÄĢt iepriekÅĄÄ“jo failu", "view_qr_code": "SkatÄĢt QR kodu", "view_similar_photos": "SkatÄĢt lÄĢdzÄĢgas fotogrāfijas", "view_stack": "ApskatÄĢt kaudzi", + "view_user": "ApskatÄĢt lietotāju", "viewer_remove_from_stack": "Noņemt no Steka", - "visibility": "RedzamÄĢba", - "visibility_changed": "RedzamÄĢba mainÄĢta {count, plural, one {# person} other {# people}}", + "viewer_stack_use_as_main_asset": "Izmantot kā Galveno AktÄĢvu", + "viewer_unstack": "At-Stekot", "visual": "Vizuāli", + "visual_builder": "Vizuālais veidotājs", "waiting": "Gaida", "waiting_count": "Gaida: {count}", "warning": "BrÄĢdinājums", @@ -2104,23 +1843,20 @@ "welcome_to_immich": "Laipni lÅĢgti Immich", "width": "Platums", "wifi_name": "Wi-Fi nosaukums", - "workflow_delete_prompt": "Vai tieÅĄÄm vēlaties dzēst ÅĄo darbplÅĢsmu?", "workflow_deleted": "Darba plÅĢsma dzēsta", "workflow_description": "Darba plÅĢsmas apraksts", "workflow_info": "Darba plÅĢsmas informācija", "workflow_json": "Darba plÅĢsmas JSON", "workflow_json_help": "Labot darba plÅĢsmas konfigurāciju JSON formātā. Izmaiņas tiks sinhronizētas ar vizuālo veidotāju.", "workflow_name": "Darba plÅĢsmas nosaukums", - "workflow_navigation_prompt": "Vai tieÅĄÄm vēlaties iziet, nesaglabājot izmaiņas?", "workflow_summary": "Darba plÅĢsmas kopsavilkums", "workflow_update_success": "Darba plÅĢsma veiksmÄĢgi izmainÄĢta", + "workflow_updated": "Darba plÅĢsma izmainÄĢta", "workflows": "Darba plÅĢsmas", - "workflows_help_text": "DarbplÅĢsmas automatizē darbÄĢbas ar jÅĢsu resursiem, pamatojoties uz aktivizētājiem un filtriem", "wrong_pin_code": "Nepareizs PIN kods", "year": "Gads", "years_ago": "Pirms {years, plural, one {# gada} other {# gadiem}}", "yes": "Jā", - "you_dont_have_any_shared_links": "Jums nav nevienas koplietotas saites", "your_wifi_name": "Tava Wi-Fi nosaukums", "zero_to_clear_rating": "nospied 0, lai notÄĢrÄĢtu faila vērtējumu", "zoom_image": "Pietuvināt attēlu" diff --git a/i18n/mi.json b/i18n/mi.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/mi.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/mk.json b/i18n/mk.json index 7c2bd18cf9..b12fe7ca15 100644 --- a/i18n/mk.json +++ b/i18n/mk.json @@ -8,6 +8,7 @@ "actions": "АĐēŅ†Đ¸Đ¸", "active": "АĐēŅ‚Đ¸Đ˛ĐŊи", "activity": "АĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚", + "activity_changed": "АĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚Đ° Đĩ {enabled, select, true {ОвОСĐŧĐžĐļĐĩĐŊа} other {ĐŊĐĩОвОСĐŧĐžĐļĐĩĐŊа}}", "add": "Додади", "add_a_description": "Додади ĐžĐŋĐ¸Ņ", "add_a_location": "Додади ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đ°", @@ -17,7 +18,9 @@ "add_endpoint": "Додади ĐēŅ€Đ°Ņ˜ĐŊа Ņ‚ĐžŅ‡Đēа", "add_exclusion_pattern": "Додади ŅˆĐ°ĐąĐģĐžĐŊ Са Đ¸ŅĐēĐģŅƒŅ‡ŅƒĐ˛Đ°ŅšĐĩ", "add_location": "Додади ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đ°", + "add_more_users": "Додади ŅƒŅˆŅ‚Đĩ ĐēĐžŅ€Đ¸ŅĐŊĐ¸Ņ†Đ¸", "add_partner": "Додади ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€", + "add_path": "Додади ĐŋĐ°Ņ‚ĐĩĐēа", "add_photos": "Додади ҁĐģиĐēи", "add_tag": "Додади ОСĐŊаĐēа", "add_to": "Додади вОâ€Ļ", @@ -25,8 +28,10 @@ "add_to_album_bottom_sheet_added": "ДодадĐĩĐŊĐž вО {album}", "add_to_album_bottom_sheet_already_exists": "ВĐĩҜĐĩ вО {album}", "add_to_album_bottom_sheet_some_local_assets": "НĐĩĐēОи ĐģĐžĐēаĐģĐŊи Ņ€ĐĩŅŅƒŅ€ŅĐ¸ ĐŊĐĩ ĐŧĐžĐļĐĩа да ҁĐĩ Đ´ĐžĐ´Đ°Đ´Đ°Ņ‚ вО аĐģĐąŅƒĐŧĐžŅ‚", + "add_to_album_toggle": "ĐŸŅ€ĐžĐŧĐĩĐŊи Ņ˜Đ° ҁĐĩĐģĐĩĐēŅ†Đ¸Ņ˜Đ°Ņ‚Đ° Са {album}", "add_to_albums": "Додади вО аĐģĐąŅƒĐŧи", "add_to_albums_count": "Додади вО аĐģĐąŅƒĐŧи ({count})", + "add_to_shared_album": "Додади вО ҁĐŋОдĐĩĐģĐĩĐŊ аĐģĐąŅƒĐŧ", "add_upload_to_stack": "Đ”ĐžĐ´Đ°Ņ˜ ĐŋŅ€Đ¸ĐēĐ°Ņ‡ĐĩĐŊĐžŅ‚Đž вО Đē҃Đŋ", "add_url": "Додади URL", "added_to_archive": "ДодадĐĩĐŊĐž вО Đ°Ņ€Ņ…Đ¸Đ˛Đ°", @@ -118,6 +123,7 @@ "albums": "АĐģĐąŅƒĐŧи", "all": "ĐĄĐ¸Ņ‚Đĩ", "all_people": "ĐĄĐ¸Ņ‚Đĩ ĐģŅƒŅ“Đĩ", + "anti_clockwise": "ĐĄĐŋŅ€ĐžŅ‚Đ¸Đ˛ĐŊĐž Од ҁ҂ҀĐĩĐģĐēĐ¸Ņ‚Đĩ ĐŊа Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžŅ‚", "appears_in": "ĐĄĐĩ ĐŋĐžŅ˜Đ°Đ˛ŅƒĐ˛Đ° вО", "archive": "ĐŅ€Ņ…Đ¸Đ˛Đ°", "archive_size": "ГоĐģĐĩĐŧиĐŊа ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ°", @@ -142,6 +148,7 @@ "city": "Đ“Ņ€Đ°Đ´", "clear": "Đ˜ŅŅ‡Đ¸ŅŅ‚Đ¸", "clear_all": "Đ˜ŅŅ‡Đ¸ŅŅ‚Đ¸ ҁҐ", + "clockwise": "Во ĐŊĐ°ŅĐžĐēа ĐŊа ҁ҂ҀĐĩĐģĐēĐ¸Ņ‚Đĩ ĐŊа Ņ‡Đ°ŅĐžĐ˛ĐŊиĐēĐžŅ‚", "close": "Đ—Đ°Ņ‚Đ˛ĐžŅ€Đ¸", "collapse": "КоĐģаĐŋҁ", "collapse_all": "КоĐģаĐŋŅĐ¸Ņ€Đ°Ņ˜ ҁҐ", @@ -286,6 +293,7 @@ "refresh": "ĐžŅĐ˛ĐĩĐļи", "refreshed": "ĐžŅĐ˛ĐĩĐļĐĩĐŊĐž", "remove": "ĐžŅ‚ŅŅ‚Ņ€Đ°ĐŊи", + "repair": "ПоĐŋŅ€Đ°Đ˛Đ¸", "require_password": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐŊĐž ĐģОСиĐŊĐēа", "reset": "Đ ĐĩҁĐĩŅ‚Đ¸Ņ€Đ°Ņ˜", "restore": "ĐŸĐžĐ˛Ņ€Đ°Ņ‚Đ¸", diff --git a/i18n/ml.json b/i18n/ml.json index b264cda3ec..5b8a4ab7c4 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -10,31 +10,39 @@ "active": "ⴏⴜāĩ€ā´ĩā´‚", "active_count": "ⴏⴜāĩ€ā´ĩā´Žā´žā´¯ā´¤āĩ: {count}", "activity": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚", + "activity_changed": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ {enabled, select, true {ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋ} other {ā´¨ā´ŋāĩŧⴜāĩā´œāĩ€ā´ĩā´Žā´žā´•āĩā´•ā´ŋ}}", "add": "ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_a_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_a_location": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_a_name": "ā´Ēāĩ‡ā´°āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_a_title": "ā´ļāĩ€āĩŧⴎⴕⴂ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_action": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_action_description": "ⴍⴟā´Ēāĩā´Ēā´ŋā´˛ā´žā´•āĩā´•āĩ‡ā´Ŗāĩā´Ÿ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´‡ā´ĩā´ŋⴟāĩ† ā´•āĩā´˛ā´ŋā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "add_assets": "ā´ĩā´¸āĩā´¤āĩā´•āĩā´•āĩž ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_birthday": "ⴜⴍāĩā´Žā´Ļā´ŋⴍⴂ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_endpoint": "ā´Žāĩģā´Ąāĩâ€Œā´Ēāĩ‹ā´¯ā´ŋā´¨āĩā´ąāĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_exclusion_pattern": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩŊ ā´Ēā´žā´ąāĩā´ąāĩ‡āĩē ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_filter": "ā´Ģā´ŋāĩŊā´ąāĩā´ąāĩŧ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_filter_description": "ā´’ā´°āĩ ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•āĩā´˛ā´ŋā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "add_location": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_more_users": "ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ† ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩ† ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_path": "ā´Ēā´žā´¤āĩā´¤āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", - "add_step": "ⴘⴟāĩā´Ÿā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_tag": "ā´Ÿā´žā´—āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", - "add_to": "ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•â€Ļ", + "add_to": "...ā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_to_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_to_album_bottom_sheet_added": "{album} ā´Žā´¨āĩā´¨ā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "add_to_album_bottom_sheet_already_exists": "{album}-āĩŊ ⴇⴤā´ŋⴍⴕⴂ ⴤⴍāĩā´¨āĩ†ā´¯āĩā´Ŗāĩā´Ÿāĩ", "add_to_album_bottom_sheet_some_local_assets": "ⴚā´ŋā´˛ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´žāĩā´žā´ŋā´˛āĩā´˛", + "add_to_album_toggle": "{album}-ā´¨āĩā´ŗāĩā´ŗ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´Ēāĩā´Ēāĩ ⴟāĩ‹ā´—ā´ŋāĩž ⴚāĩ†ā´¯āĩā´¯āĩā´•", "add_to_albums": "ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_to_albums_count": "ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´• ({count})", "add_to_bottom_bar": "ⴇⴤā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_to_shared_album": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_upload_to_stack": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "add_url": "URL ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "add_workflow_step": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴘⴟāĩā´Ÿā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "added_to_archive": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "added_to_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "added_to_favorites_count": "{count, number} ā´Žā´Ŗāĩā´Ŗā´‚ ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", @@ -73,7 +81,6 @@ "cron_expression_description": "ā´•āĩā´°āĩ‹āĩē ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¸āĩā´•ā´žā´¨ā´ŋā´‚ā´—āĩ ā´‡ā´Ÿā´ĩāĩ‡ā´ŗ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•. ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´•āĩā´°āĩ‹āĩēā´Ÿā´žā´Ŧāĩ ā´—āĩā´°āĩ ā´Ēāĩ‹ā´˛āĩā´ŗāĩā´ŗā´ĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", "cron_expression_presets": "ā´•āĩā´°āĩ‹āĩē ā´Žā´•āĩā´¸āĩā´Ēāĩā´°ā´ˇāĩģ ā´Ēāĩā´°āĩ€ā´¸āĩ†ā´ąāĩā´ąāĩā´•āĩž", "disable_login": "ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•", - "download_csv": "ā´•āĩ‹ā´Ž ā´•āĩŠā´Ŗāĩā´Ÿāĩ ā´ĩāĩ‡āĩŧā´¤ā´ŋā´°ā´ŋⴚāĩā´š (CSV) ā´Ģā´¯ā´˛ā´žā´¯ā´ŋ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "duplicate_detection_job_description": "ā´¸ā´Žā´žā´¨ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´žāĩģ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗā´ŋāĩŊ ā´Žāĩ†ā´ˇāĩ€āĩģ ā´˛āĩ‡ā´Ŗā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•. ⴇⴤāĩ ā´¸āĩā´Žā´žāĩŧⴟāĩā´Ÿāĩ ā´¸āĩ†āĩŧⴚāĩā´šā´ŋā´¨āĩ† ā´†ā´ļāĩā´°ā´¯ā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "exclusion_pattern_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ģⴝⴞāĩā´•ā´ŗāĩā´‚ ā´Ģāĩ‹āĩžā´Ąā´ąāĩā´•ā´ŗāĩā´‚ ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´žāĩģ ā´Žā´•āĩā´¸āĩā´•āĩā´˛āĩ‚ā´ˇāĩģ ā´Ēā´žā´ąāĩā´ąāĩ‡ā´Ŗāĩā´•āĩž ā´¨ā´ŋā´™āĩā´™ā´ŗāĩ† ā´¸ā´šā´žā´¯ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´¨ā´ŋā´™āĩā´™āĩž ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ⴆⴗāĩā´°ā´šā´ŋā´•āĩā´•ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•āĩž ā´…ā´Ÿā´™āĩā´™ā´ŋā´¯ ā´Ģāĩ‹āĩžā´Ąā´ąāĩā´•āĩž (ā´‰ā´Ļā´žā´šā´°ā´Ŗā´¤āĩā´¤ā´ŋā´¨āĩ RAW ā´Ģⴝⴞāĩā´•āĩž) ⴉ⴪āĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ ⴇⴤāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´Ēāĩā´°ā´Ļā´Žā´žā´Ŗāĩ.", "export_config_as_json_description": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ JSON ā´Ģā´¯ā´˛ā´žā´¯ā´ŋ ā´ĄāĩŒāĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛ā´ŋā´¨āĩā´ąāĩ† ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Ž 1-100 ā´ĩā´°āĩ†. ⴉⴝāĩŧā´¨āĩā´¨ ā´Žāĩ‚ā´˛āĩā´¯ā´‚ ā´Žā´ŋā´•ā´šāĩā´šā´¤ā´žā´Ŗāĩ, ā´Ēā´•āĩā´ˇāĩ‡ ā´ĩā´˛ā´ŋā´¯ ā´Ģⴝⴞāĩā´•āĩž ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•ā´¯āĩā´‚ ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩā´°ā´¤ā´ŋā´•ā´°ā´Ŗā´ļāĩ‡ā´ˇā´ŋ ā´•āĩā´ąā´¯āĩā´•āĩā´•āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´‚.", "image_thumbnail_title": "ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋāĩŊ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "import_config_from_json_description": "ā´’ā´°āĩ JSON ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ ā´Ģā´¯āĩŊ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩā´•āĩŠā´Ŗāĩā´Ÿāĩ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ ā´‡ā´ąā´•āĩā´•āĩā´Žā´¤ā´ŋ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "integrity_checks_checksum_files": "ⴚāĩ†ā´•āĩâ€Œā´¸ā´‚ ā´Ģā´¯āĩŊā´¸āĩ", - "integrity_checks_checksum_files_description": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", - "integrity_checks_checksum_files_enable_description": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "integrity_checks_checksum_files_percentage_limit": "ā´Ēā´°ā´ŋā´§ā´ŋ ā´ļā´¤ā´Žā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ", - "integrity_checks_checksum_files_percentage_limit_description": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ⴚāĩ†ā´•āĩā´•āĩ ā´“ā´°āĩ‹ ā´‡ā´Ÿā´ĩāĩ‡ā´ŗā´¯ā´ŋā´˛āĩā´‚ ā´Žā´¤āĩā´°ā´¤āĩā´¤āĩ‹ā´ŗā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•ā´Ŗā´‚ ā´Žā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ 0.01 ā´¨āĩā´‚ 1 ā´¨āĩā´‚ ā´‡ā´Ÿā´¯ā´ŋā´˛āĩā´ŗāĩā´ŗ ā´Ēā´°ā´Žā´žā´ĩā´§ā´ŋ ā´ļā´¤ā´Žā´žā´¨ā´‚ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•.", - "integrity_checks_checksum_files_time_limit": "ā´¸ā´Žā´¯ ā´Ēā´°ā´ŋā´§ā´ŋ", - "integrity_checks_checksum_files_time_limit_description": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ⴚāĩ†ā´•āĩā´•āĩ ā´“ā´°āĩ‹ ā´‡ā´Ÿā´ĩāĩ‡ā´ŗā´¯ā´ŋā´˛āĩā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩ‡ā´Ŗāĩā´Ÿ ā´Ēā´°ā´Žā´žā´ĩā´§ā´ŋ ā´Ļāĩˆāĩŧⴘāĩā´¯ā´‚ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•. (ā´Žā´ŋā´˛āĩā´˛ā´ŋā´¸āĩ†ā´•āĩā´•āĩģā´Ąāĩ)", - "integrity_checks_missing_files": "ā´Ģⴝⴞāĩā´•āĩž ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", - "integrity_checks_missing_files_description": "ā´Ģāĩā´°āĩ€ā´•āĩā´ĩāĩģā´¸ā´ŋ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ ⴍⴎāĩâ€Œā´Ÿā´Žā´žā´¯ ā´Ģⴝⴞāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´• ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•", - "integrity_checks_missing_files_enable_description": "ā´ĩā´ŋⴟāĩā´Ÿāĩā´Ēāĩ‹ā´¯ ā´Ģⴝⴞāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "integrity_checks_settings": "ā´¸ā´Žā´—āĩā´°ā´¤ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "integrity_checks_settings_description": "ā´¸ā´Žā´—āĩā´°ā´¤ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´‡ā´Ÿā´ĩāĩ‡ā´ŗā´•āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "integrity_checks_untracked_files": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•āĩž", - "integrity_checks_untracked_files_description": "ā´Ģāĩā´°āĩ€ā´•āĩā´ĩāĩģā´¸ā´ŋ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•ā´¯āĩ‹ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•ā´¯āĩ‹ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "integrity_checks_untracked_files_enable_description": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "job_concurrency": "{job} ā´•āĩ‹āĩēā´•ā´ąāĩģā´¸ā´ŋ", "job_created": "ⴜāĩ‹ā´˛ā´ŋ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šāĩ", "job_not_concurrency_safe": "ⴈ ⴜāĩ‹ā´˛ā´ŋ ā´•āĩ‹āĩēā´•ā´ąāĩģā´¸ā´ŋ-ā´¸āĩ‡ā´Ģāĩ ā´…ā´˛āĩā´˛.", @@ -130,7 +122,6 @@ "job_settings_description": "ⴜāĩ‹ā´˛ā´ŋ ā´•āĩ‹āĩēā´•ā´ąāĩģā´¸ā´ŋ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "jobs_delayed": "{jobCount, plural, one {# ⴜāĩ‹ā´˛ā´ŋ ā´ĩāĩˆā´•ā´ŋ} other {# ⴜāĩ‹ā´˛ā´ŋā´•āĩž ā´ĩāĩˆā´•ā´ŋ}}", "jobs_failed": "{jobCount, plural, one {# ⴜāĩ‹ā´˛ā´ŋ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ} other {# ⴜāĩ‹ā´˛ā´ŋā´•āĩž ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ}}", - "jobs_over_time": "ā´•ā´žā´˛ā´•āĩā´°ā´Žāĩ‡ā´Ŗ ⴜāĩ‹ā´˛ā´ŋā´•āĩž", "library_created": "{library} ā´Žā´¨āĩā´¨ ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šāĩ", "library_deleted": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", "library_details": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž", @@ -198,39 +189,11 @@ "machine_learning_smart_search_enabled": "ā´¸āĩā´Žā´žāĩŧⴟāĩā´Ÿāĩ ā´¸āĩ†āĩŧⴚāĩā´šāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "machine_learning_smart_search_enabled_description": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋā´¯ā´žāĩŊ, ā´¸āĩā´Žā´žāĩŧⴟāĩā´Ÿāĩ ā´¸āĩ†āĩŧⴚāĩā´šā´ŋā´¨ā´žā´¯ā´ŋ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž ā´Žāĩģā´•āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿā´ŋā´˛āĩā´˛.", "machine_learning_url_description": "ā´Žāĩ†ā´ˇāĩ€āĩģ ā´˛āĩ‡ā´Ŗā´ŋā´‚ā´—āĩ ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´¨āĩā´ąāĩ† URL. ā´’ā´¨āĩā´¨ā´ŋāĩŊ ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ URL-ā´•āĩž ā´¨āĩŊā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´“ā´°āĩ‹ ā´¸āĩ†āĩŧā´ĩā´ąāĩā´‚ ā´’ā´¨āĩā´¨ā´ŋā´¨āĩ ā´Ēāĩā´ąā´•āĩ† ā´’ā´¨āĩā´¨ā´žā´¯ā´ŋ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ēāĩā´°ā´¤ā´ŋā´•ā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩā´ĩā´°āĩ† ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´‚. ā´Ēāĩā´°ā´¤ā´ŋā´•ā´°ā´ŋā´•āĩā´•ā´žā´¤āĩā´¤ ā´¸āĩ†āĩŧā´ĩā´ąāĩā´•āĩž ā´“āĩēā´˛āĩˆā´¨ā´ŋāĩŊ ā´¤ā´ŋā´°ā´ŋā´•āĩ† ā´ĩā´°āĩā´¨āĩā´¨ā´¤āĩā´ĩā´°āĩ† ā´¤ā´žāĩŊā´•āĩā´•ā´žā´˛ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´…ā´ĩā´—ā´Ŗā´ŋā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚.", - "maintenance_backup_management": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Žā´žā´¨āĩ‡ā´œāĩā´Žāĩ†ā´¨āĩā´ąāĩ", - "maintenance_delete_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "maintenance_delete_backup_description": "ⴈ ā´Ģā´¯āĩŊ ā´¤ā´ŋā´°ā´ŋⴚāĩā´šāĩ†ā´Ÿāĩā´•āĩā´•ā´žāĩģ ā´Ēā´ąāĩā´ąā´žā´¤āĩā´¤ ā´°āĩ€ā´¤ā´ŋā´¯ā´ŋāĩŊ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚.", - "maintenance_delete_error": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ.", - "maintenance_integrity_check": "ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "maintenance_integrity_check_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "maintenance_integrity_checksum_mismatch": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", - "maintenance_integrity_checksum_mismatch_description": "ā´“āĩē-ā´Ąā´ŋā´¸āĩâ€Œā´•āĩ ⴚāĩ†ā´•āĩā´•āĩâ€Œā´¸ā´‚ ā´…ā´¤ā´ŋā´¨āĩā´ąāĩ† Immich ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸ā´ŋāĩŊ ⴏⴂⴭⴰā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ⴚāĩ†ā´•āĩā´•āĩâ€Œā´¸ā´ĩāĩā´Žā´žā´¯ā´ŋ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´Ēāĩā´Ēāĩ†ā´Ÿā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•āĩž.", - "maintenance_integrity_checksum_mismatch_job": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´•āĩā´•āĩ‡ā´Ÿāĩā´•āĩž ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "maintenance_integrity_checksum_mismatch_refresh_job": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´•āĩā´•āĩ‡ā´Ÿāĩ ā´ąā´ŋā´Ēāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩā´•āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", - "maintenance_integrity_missing_file": "ā´Ģⴝⴞāĩā´•āĩž ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", - "maintenance_integrity_missing_file_description": "Immich ā´…ā´¤ā´ŋā´¨āĩā´ąāĩ† ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸ā´ŋāĩŊ ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ā´¤āĩā´‚ ā´Žā´¨āĩā´¨ā´žāĩŊ ā´Ģā´¯āĩŊ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛ā´ŋā´˛āĩā´˛ā´žā´¤āĩā´¤ā´¤āĩā´Žā´žā´¯ ā´Ģⴝⴞāĩā´•āĩž.", - "maintenance_integrity_missing_file_job": "ⴍⴎāĩā´Ÿā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ā´Ģⴝⴞāĩā´•āĩž ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "maintenance_integrity_missing_file_refresh_job": "ⴍⴎāĩā´Ÿā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ā´Ģā´¯āĩŊ ā´ąā´ŋā´Ēāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩā´•āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", - "maintenance_integrity_report": "ā´¸ā´Žā´—āĩā´°ā´¤ ā´ąā´ŋā´Ēāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ", - "maintenance_integrity_untracked_file": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•āĩž", - "maintenance_integrity_untracked_file_description": "Immich-ā´¨āĩā´ąāĩ† ā´Ąā´¯ā´ąā´•āĩâ€Œā´Ÿā´ąā´ŋā´•ā´ŗā´ŋā´˛āĩ† ā´Ģⴝⴞāĩā´•ā´ŗā´ŋāĩŊ Immich -ā´¨āĩ ā´’ā´°āĩ ā´ąāĩ†ā´•āĩā´•āĩ‹āĩŧā´Ąāĩā´‚ ⴇⴞāĩā´˛ā´žā´¤āĩā´¤ā´ĩ.", - "maintenance_integrity_untracked_file_job": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģⴝⴞāĩā´•āĩž ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "maintenance_integrity_untracked_file_refresh_job": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Ģā´¯āĩŊ ā´ąā´ŋā´Ēāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩā´•āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", - "maintenance_restore_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", - "maintenance_restore_backup_description": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ Immich ā´Žā´žā´¯āĩâ€Œā´•āĩā´•āĩā´•ā´¯āĩā´‚ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´‚. ā´¤āĩā´Ÿā´°āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ā´’ā´°āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚.", - "maintenance_restore_backup_different_version": "Immich -ā´¨āĩā´ąāĩ† ā´Žā´ąāĩā´ąāĩŠā´°āĩ ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šā´žā´Ŗāĩ ⴈ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋⴚāĩā´šā´¤āĩ!", - "maintenance_restore_backup_unknown_version": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´¨ā´ŋāĩŧā´Ŗāĩā´Ŗā´¯ā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´žāĩā´žā´ŋā´˛āĩā´˛.", - "maintenance_restore_database_backup": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", - "maintenance_restore_database_backup_description": "ā´’ā´°āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ģā´¯āĩŊ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´Žāĩā´Žāĩā´Ēā´¤āĩā´¤āĩ† ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´…ā´ĩā´¸āĩā´Ĩā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´ąāĩ‹āĩžā´Ŧā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "maintenance_settings": "ā´…ā´ąāĩā´ąā´•āĩā´ąāĩā´ąā´Ēāĩā´Ēā´Ŗā´ŋ (Maintenance)", "maintenance_settings_description": "ā´‡ā´Žāĩā´Žā´ŋā´•āĩā´•āĩ ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´•.", - "maintenance_start": "ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´•", + "maintenance_start": "ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąāĩ ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´•", "maintenance_start_error": "ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąāĩ ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ.", - "maintenance_upload_backup": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ģā´¯āĩŊ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "maintenance_upload_backup_error": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´žāĩā´žā´ŋā´˛āĩā´˛, ⴇⴤāĩŠā´°āĩ .sql/.sql.gz ā´Ģā´¯ā´˛ā´žā´Ŗāĩ‹?", "manage_concurrency": "ā´•āĩ‹āĩēā´•ā´ąāĩģā´¸ā´ŋ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "manage_concurrency_description": "ⴜāĩ‹ā´Ŧāĩ ā´•āĩēā´•ā´ąāĩģā´¸ā´ŋ ā´Žā´žā´¨āĩ‡ā´œāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ⴜāĩ‹ā´Ŧāĩ ā´Ēāĩ‡ā´œā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "manage_log_settings": "ā´˛āĩ‹ā´—āĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "map_dark_style": "ā´Ąā´žāĩŧā´•āĩā´•āĩ ā´¸āĩā´ąāĩā´ąāĩˆāĩŊ", "map_enable_description": "ā´Žā´žā´Ēāĩā´Ēāĩ ā´Ģāĩ€ā´šāĩā´šā´ąāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", @@ -291,20 +254,16 @@ "notification_enable_email_notifications": "ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "notification_settings": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "notification_settings_description": "ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩ†ā´¯āĩā´ŗāĩā´ŗ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "oauth_allow_insecure_requests": "ā´¸āĩā´°ā´•āĩā´ˇā´ŋā´¤ā´Žā´˛āĩā´˛ā´žā´¤āĩā´¤ ā´…ā´­āĩā´¯āĩŧā´¤āĩā´Ĩⴍⴕāĩž ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", - "oauth_allow_insecure_requests_description": "ā´Žāĩā´¨āĩā´¨ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ: ⴇⴤāĩ OAuth ā´…ā´­āĩā´¯āĩŧā´¤āĩā´Ĩⴍⴕāĩžā´•āĩā´•ā´žā´¯āĩā´ŗāĩā´ŗ TLS ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´Žāĩ‚ā´˛āĩā´¯ā´¨ā´ŋāĩŧā´Ŗāĩā´Ŗā´¯ā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•ā´¯āĩā´‚ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩ† MITM ⴆⴕāĩā´°ā´Žā´Ŗā´™āĩā´™āĩžā´•āĩā´•āĩ ā´ĩā´ŋā´§āĩ‡ā´¯ā´Žā´žā´•āĩā´•āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ‡ā´•āĩā´•ā´žā´‚.", "oauth_auto_launch": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¤āĩā´Ÿā´™āĩā´™āĩā´•", "oauth_auto_launch_description": "ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩ‡ā´œā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´Žāĩā´Ēāĩ‹āĩž OAuth ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ģāĩā´˛āĩ‹ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´•", "oauth_auto_register": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴰⴜā´ŋā´¸āĩā´ąāĩā´ąāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "oauth_auto_register_description": "OAuth ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¸āĩˆāĩģ ā´‡āĩģ ⴚāĩ†ā´¯āĩā´¤ ā´ļāĩ‡ā´ˇā´‚ ā´Ēāĩā´¤ā´ŋā´¯ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ† ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴰⴜā´ŋā´¸āĩā´ąāĩā´ąāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "oauth_button_text": "ā´Ŧⴟāĩā´Ÿā´Ŗā´ŋā´˛āĩ† ā´ĩā´žā´šā´•ā´‚", - "oauth_client_secret_description": "ā´¸āĩā´°ā´•āĩā´ˇā´ŋā´¤ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ‹, OAuth ā´Ļā´žā´¤ā´žā´ĩāĩ PKCE (ā´Ēāĩā´°āĩ‚ā´Ģāĩ ā´•āĩ€ ā´Ģāĩ‹āĩŧ ā´•āĩ‹ā´Ąāĩ ā´Žā´•āĩā´¸āĩā´šāĩ‡ā´žāĩā´šāĩ) ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋā´˛āĩ‹ ⴇⴤāĩ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ", + "oauth_client_secret_description": "OAuth ā´Ļā´žā´¤ā´žā´ĩāĩ PKCE (ā´Ēāĩā´°āĩ‚ā´Ģāĩ ā´•āĩ€ ā´Ģāĩ‹āĩŧ ā´•āĩ‹ā´Ąāĩ ā´Žā´•āĩā´¸āĩā´šāĩ‡ā´žāĩā´šāĩ) ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ", "oauth_enable_description": "OAuth ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "oauth_end_session_url_description": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩāĩ ā´˛āĩ‹ā´—āĩ ā´”ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ⴈ ā´¯āĩā´†āĩŧⴐⴝā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´ąāĩ€ā´Ąā´¯ā´ąā´•āĩâ€Œā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•.", "oauth_mobile_redirect_uri": "ā´ŽāĩŠā´ŦāĩˆāĩŊ ā´ąāĩ€ā´Ąā´¯ā´ąā´•āĩā´Ÿāĩ URI", "oauth_mobile_redirect_uri_override": "ā´ŽāĩŠā´ŦāĩˆāĩŊ ā´ąāĩ€ā´Ąā´¯ā´ąā´•āĩā´Ÿāĩ URI ā´“ā´ĩāĩŧā´ąāĩˆā´Ąāĩ", "oauth_mobile_redirect_uri_override_description": "OAuth ā´Ļā´žā´¤ā´žā´ĩāĩ \"{callback}\" ā´Ēāĩ‹ā´˛āĩā´ŗāĩā´ŗ ā´’ā´°āĩ ā´ŽāĩŠā´ŦāĩˆāĩŊ URI ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•ā´žā´¤āĩā´¤ā´Ēāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "oauth_prompt_description": "ā´Ēāĩā´°āĩ‹ā´‚ā´Ēāĩā´ąāĩā´ąāĩ ā´Ēā´žā´°ā´žā´Žāĩ€ā´ąāĩā´ąāĩŧ (ā´‰ā´Ļā´ž. select_account, login, consent)", "oauth_role_claim": "ā´ąāĩ‹āĩž ā´•āĩā´˛āĩ†ā´¯ā´ŋā´‚", "oauth_role_claim_description": "ⴈ ā´•āĩā´˛āĩ†ā´¯ā´ŋā´Žā´ŋā´¨āĩā´ąāĩ† ā´¸ā´žā´¨āĩā´¨ā´ŋā´§āĩā´¯ā´¤āĩā´¤āĩ† ā´…ā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´Žā´žā´•āĩā´•ā´ŋ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´…ā´Ąāĩā´Žā´ŋāĩģ ⴆⴕāĩā´¸ā´¸āĩ ā´¨āĩŊā´•āĩā´•. ā´•āĩā´˛āĩ†ā´¯ā´ŋā´Žā´ŋā´¨āĩ 'user' ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ 'admin' ā´Žā´¨āĩā´¨āĩ ⴉ⴪āĩā´Ÿā´žā´•ā´žā´‚.", "oauth_settings": "OAuth", @@ -324,16 +283,10 @@ "password_settings_description": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´˛āĩ‹ā´—ā´ŋāĩģ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "paths_validated_successfully": "ā´Žā´˛āĩā´˛ā´ž ā´Ēā´žā´¤āĩā´¤āĩā´•ā´ŗāĩā´‚ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´¸ā´žā´§āĩ‚ā´•ā´°ā´ŋⴚāĩā´šāĩ", "person_cleanup_job": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋ ā´•āĩā´˛āĩ€ā´¨ā´Ēāĩā´Ēāĩ", - "queue_details": "ā´•āĩā´¯āĩ‚ ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž", - "queues": "ⴜāĩ‹ā´˛ā´ŋ ā´•āĩā´¯āĩ‚ā´•āĩž", - "queues_page_description": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ⴜāĩ‹ā´˛ā´ŋ ā´•āĩā´¯āĩ‚ ā´Ēāĩ‡ā´œāĩ", "quota_size_gib": "ā´•āĩā´ĩā´žā´Ÿāĩā´Ÿ ā´ĩā´˛āĩā´Ēāĩā´Ēā´‚ (GiB)", "refreshing_all_libraries": "ā´Žā´˛āĩā´˛ā´ž ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋā´•ā´ŗāĩā´‚ ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ", "registration": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ⴰⴜā´ŋā´¸āĩā´Ÿāĩā´°āĩ‡ā´ˇāĩģ", "registration_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗā´žā´Ŗāĩ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´¤āĩā´¤ā´ŋā´˛āĩ† ā´†ā´Ļāĩā´¯ā´¤āĩā´¤āĩ† ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩāĩ ā´Žā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žāĩŊ, ā´¨ā´ŋā´™āĩā´™ā´ŗāĩ† ā´…ā´Ąāĩā´Žā´ŋāĩģ ⴆⴝā´ŋ ā´¨ā´ŋā´¯ā´Žā´ŋā´•āĩā´•āĩā´‚. ā´­ā´°ā´Ŗā´Ēā´°ā´Žā´žā´¯ ⴜāĩ‹ā´˛ā´ŋā´•āĩžā´•āĩā´•āĩ ā´¨ā´ŋā´™āĩā´™āĩž ⴉⴤāĩā´¤ā´°ā´ĩā´žā´Ļā´ŋā´¯ā´žā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´‚, ā´•āĩ‚ā´Ÿā´žā´¤āĩ† ā´…ā´§ā´ŋā´• ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ† ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ ā´¨ā´ŋā´™āĩā´™ā´ŗā´žā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´‚.", - "release_channel_release_candidate": "ā´Ēāĩā´ąā´¤āĩā´¤ā´ŋā´ąā´•āĩā´•ā´žāĩģ ā´¯āĩ‹ā´œā´ŋⴚāĩā´šā´¤āĩ", - "release_channel_stable": "ā´¸āĩā´Ĩā´ŋⴰⴤⴝāĩā´ŗāĩā´ŗ", - "remove_failed_jobs": "ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ⴜāĩ‹ā´˛ā´ŋā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "require_password_change_on_login": "ā´†ā´Ļāĩā´¯ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´Žā´žā´ąāĩā´ąā´žāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ‹ā´Ÿāĩ ā´†ā´ĩā´ļāĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´•", "reset_settings_to_default": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´Ąā´ŋā´Ģāĩ‹āĩžā´Ÿāĩā´Ÿā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•āĩā´•", "reset_settings_to_recent_saved": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•āĩā´•", @@ -341,15 +294,13 @@ "search_jobs": "ⴜāĩ‹ā´˛ā´ŋā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•â€Ļ", "send_welcome_email": "ā´¸āĩā´ĩā´žā´—ā´¤ ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ⴅⴝⴕāĩā´•āĩā´•", "server_external_domain_settings": "ā´Ŧā´žā´šāĩā´¯ ā´ĄāĩŠā´Žāĩ†ā´¯āĩāĩģ", - "server_external_domain_settings_description": "ā´Ēāĩā´ąā´¤āĩā´¤āĩ‡ā´•āĩā´•āĩā´ŗāĩā´ŗ ā´˛ā´ŋā´™āĩā´•āĩā´•ā´ŗā´ŋāĩŊ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´ĄāĩŠā´Žāĩˆāĩģ", + "server_external_domain_settings_description": "ā´ĒāĩŠā´¤āĩā´ĩā´žā´¯ā´ŋ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩžā´•āĩā´•āĩā´ŗāĩā´ŗ ā´ĄāĩŠā´Žāĩ†ā´¯āĩāĩģ, http(s):// ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩ†", "server_public_users": "ā´ĒāĩŠā´¤āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•āĩž", "server_public_users_description": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´’ā´°āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴚāĩ‡āĩŧā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´Žā´˛āĩā´˛ā´ž ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ†ā´¯āĩā´‚ (ā´Ēāĩ‡ā´°āĩā´‚ ā´‡ā´Žāĩ†ā´¯ā´ŋā´˛āĩā´‚) ā´˛ā´ŋā´¸āĩā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´‚. ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž, ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´˛ā´ŋā´¸āĩā´ąāĩā´ąāĩ ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•āĩžā´•āĩā´•āĩ ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ⴞⴭāĩā´¯ā´Žā´žā´•āĩ‚.", "server_settings": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "server_settings_description": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "server_stats_page_description": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´¸āĩ†āĩŧā´ĩāĩŧ ā´¸āĩā´ąāĩā´ąā´žā´ąāĩā´ąā´ŋā´¸āĩā´ąāĩā´ąā´ŋā´•āĩā´¸āĩ ā´Ēāĩ‡ā´œāĩ", "server_welcome_message": "ā´¸āĩā´ĩā´žā´—ā´¤ ⴏⴍāĩā´Ļāĩ‡ā´ļā´‚", "server_welcome_message_description": "ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩ‡ā´œā´ŋāĩŊ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´’ā´°āĩ ⴏⴍāĩā´Ļāĩ‡ā´ļā´‚.", - "settings_page_description": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗ ā´Ēāĩ‡ā´œāĩ", "sidecar_job": "ā´¸āĩˆā´Ąāĩâ€Œā´•ā´žāĩŧ ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą", "sidecar_job_description": "ā´Ģā´¯āĩŊā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¸āĩˆā´Ąāĩâ€Œā´•ā´žāĩŧ ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´•ā´¯āĩ‹ ā´¸ā´ŋāĩģā´•āĩā´°āĩŠā´Ŗāĩˆā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•ā´¯āĩ‹ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "slideshow_duration_description": "ā´“ā´°āĩ‹ ⴚā´ŋā´¤āĩā´°ā´ĩāĩā´‚ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´žā´¨āĩā´ŗāĩā´ŗ ā´¸āĩ†ā´•āĩā´•āĩģā´Ąāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Žā´Ŗāĩā´Ŗā´‚", @@ -427,10 +378,6 @@ "transcoding_preferred_hardware_device_description": "VAAPI, QSV ā´Žā´¨āĩā´¨ā´ŋā´ĩā´¯āĩā´•āĩā´•āĩ ā´Žā´žā´¤āĩā´°ā´‚ ā´Ŧā´žā´§ā´•ā´‚. ā´šā´žāĩŧā´Ąāĩâ€Œā´ĩāĩ†ā´¯āĩŧ ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąā´ŋā´‚ā´—ā´ŋā´¨ā´žā´¯ā´ŋ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ dri ā´¨āĩ‹ā´Ąāĩ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ.", "transcoding_preset_preset": "ā´Ēāĩā´°āĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ (-preset)", "transcoding_preset_preset_description": "ā´•ā´‚ā´Ēāĩā´°ā´ˇāĩģ ā´ĩāĩ‡ā´—ā´¤. ā´ĩāĩ‡ā´—ā´¤ ā´•āĩā´ąā´žāĩā´ž ā´Ēāĩā´°āĩ€ā´¸āĩ†ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ†ā´ąā´ŋā´¯ ā´Ģⴝⴞāĩā´•āĩž ā´¨ā´ŋāĩŧā´Žāĩā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´’ā´°āĩ ā´¨ā´ŋā´ļāĩā´šā´ŋā´¤ ā´Ŧā´ŋā´ąāĩā´ąāĩā´ąāĩ‡ā´ąāĩā´ąāĩ ⴞⴕāĩā´ˇāĩā´¯ā´Žā´ŋⴟāĩā´Žāĩā´Ēāĩ‹āĩž ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Ž ā´ĩāĩŧā´Ļāĩā´§ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. 'faster'-ā´¨āĩ‡ā´•āĩā´•ā´žāĩž ⴉⴝāĩŧā´¨āĩā´¨ ā´ĩāĩ‡ā´—ā´¤ VP9 ā´…ā´ĩā´—ā´Ŗā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ.", - "transcoding_realtime": "ⴤⴤāĩā´¸ā´Žā´¯ ⴟāĩā´°ā´žāĩģā´¸āĩā´•āĩ‹ā´Ąā´ŋā´‚ā´—āĩ [ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´¤āĩā´Žā´•ā´‚]", - "transcoding_realtime_description": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´¸āĩā´Ÿāĩā´°āĩ€ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žāĩŊ ⴤⴤāĩā´¸ā´Žā´¯ā´‚ ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ⴍⴟⴤāĩā´¤ā´žāĩģ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Žā´¯āĩā´ŗāĩā´ŗ ā´¸āĩā´ĩā´ŋⴚāĩā´šā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´Žā´¨āĩā´¨ā´žāĩŊ ā´¸āĩ†āĩŧā´ĩāĩŧ ā´•ā´´ā´ŋā´ĩāĩā´•ā´ŗāĩ† ā´†ā´ļāĩā´°ā´¯ā´ŋⴚāĩā´šāĩ ⴉⴝāĩŧā´¨āĩā´¨ ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´˛āĩ‡ā´ąāĩā´ąāĩģā´¸ā´ŋā´•āĩā´•āĩā´‚ ā´‡ā´Ÿāĩŧⴚāĩā´šā´¯āĩā´•āĩā´•āĩā´‚ ā´•ā´žā´°ā´Ŗā´Žā´žā´¯āĩ‡ā´•āĩā´•ā´žā´‚.", - "transcoding_realtime_enabled": "ⴤⴤāĩā´¸ā´Žā´¯ ⴟāĩā´°ā´žāĩģā´¸āĩā´•āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "transcoding_realtime_enabled_description": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋā´¯ā´žāĩŊ, ā´Ēāĩā´¤ā´ŋā´¯ ⴤⴤāĩā´¸ā´Žā´¯ ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ā´¸āĩ†ā´ˇā´¨āĩā´•āĩž ⴆⴰⴂⴭā´ŋā´•āĩā´•ā´žāĩģ ā´¸āĩ†āĩŧā´ĩāĩŧ ā´ĩā´ŋā´¸ā´Žāĩā´Žā´¤ā´ŋā´•āĩā´•āĩā´‚.", "transcoding_reference_frames": "ā´ąā´Ģā´ąāĩģā´¸āĩ ā´Ģāĩā´°āĩ†ā´¯ā´ŋā´Žāĩā´•āĩž", "transcoding_reference_frames_description": "ā´¨āĩŊā´•ā´ŋā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´’ā´°āĩ ā´Ģāĩā´°āĩ†ā´¯ā´ŋā´‚ ā´•ā´‚ā´Ēāĩā´°ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´ąā´Ģā´ąāĩģā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩ‡ā´Ŗāĩā´Ÿ ā´Ģāĩā´°āĩ†ā´¯ā´ŋā´Žāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Žā´Ŗāĩā´Ŗā´‚. ⴉⴝāĩŧā´¨āĩā´¨ ā´Žāĩ‚ā´˛āĩā´¯ā´™āĩā´™āĩž ā´•ā´‚ā´Ēāĩā´°ā´ˇāĩģ ā´•ā´žā´°āĩā´¯ā´•āĩā´ˇā´Žā´¤ ā´Žāĩ†ā´šāĩā´šā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´¨āĩā´¨āĩ, ā´Ēā´•āĩā´ˇāĩ‡ ā´Žāĩģā´•āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ā´ĩāĩ‡ā´—ā´¤ ā´•āĩā´ąā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ. 0 ⴈ ā´Žāĩ‚ā´˛āĩā´¯ā´‚ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ.", "transcoding_required_description": "ā´…ā´‚ā´—āĩ€ā´•āĩƒā´¤ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąā´ŋāĩŊ ā´…ā´˛āĩā´˛ā´žā´¤āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´Žā´žā´¤āĩā´°ā´‚", @@ -472,10 +419,6 @@ "user_restore_scheduled_removal": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´• - {date, date, long}-ā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´ˇāĩ†ā´Ąāĩā´¯āĩ‚āĩž ⴚāĩ†ā´¯āĩâ€Œā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "user_settings": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩā´ąāĩ† ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "user_settings_description": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "user_successfully_removed": "{email} ā´Žā´¨āĩā´¨ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ.", - "users_page_description": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ēāĩ‡ā´œāĩ", - "version_check_channel": "ā´ąā´ŋā´˛āĩ€ā´¸āĩ ā´šā´žā´¨āĩŊ", - "version_check_channel_description": "ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ⴞⴭā´ŋā´•āĩā´•ā´žāĩģ ⴆⴗāĩā´°ā´šā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´ąā´ŋā´˛āĩ€ā´¸āĩ ā´šā´žā´¨āĩŊ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "version_check_enabled_description": "ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "version_check_implications": "ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´Ģāĩ€ā´šāĩā´šāĩŧ {server}-ā´Žā´žā´¯ā´ŋ ⴆⴍāĩā´•ā´žā´˛ā´ŋā´• ā´†ā´ļā´¯ā´ĩā´ŋā´¨ā´ŋā´Žā´¯ā´¤āĩā´¤āĩ† ā´†ā´ļāĩā´°ā´¯ā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "version_check_settings": "ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨", @@ -487,9 +430,8 @@ "admin_password": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", "administration": "ā´…ā´Ąāĩā´Žā´ŋā´¨ā´ŋā´¸āĩā´Ÿāĩā´°āĩ‡ā´ˇāĩģ", "advanced": "ā´ĩā´ŋā´Ēāĩā´˛ā´Žā´žā´¯ā´¤āĩ", - "advanced_settings_clear_image_cache": "ā´‡ā´Žāĩ‡ā´œāĩ ā´•ā´žā´ˇāĩ† ā´Žā´žā´¯āĩâ€Œā´•āĩā´•āĩā´•", - "advanced_settings_clear_image_cache_error": "ā´‡ā´Žāĩ‡ā´œāĩ ā´•ā´žā´ˇāĩ† ā´Žā´žā´¯āĩâ€Œā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", - "advanced_settings_clear_image_cache_success": "{size} ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Žā´žā´¯āĩā´šāĩā´šāĩ", + "advanced_settings_enable_alternate_media_filter_subtitle": "ⴇⴤⴰ ā´Žā´žā´¨ā´Ļā´Ŗāĩā´Ąā´™āĩā´™ā´ŗāĩ† ā´…ā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´Žā´žā´•āĩā´•ā´ŋ ā´¸ā´ŋā´™āĩā´•āĩ ā´¸ā´Žā´¯ā´¤āĩā´¤āĩ ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ⴈ ā´“ā´Ēāĩā´ˇāĩģ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•. ā´Žā´˛āĩā´˛ā´ž ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩ ā´Ēāĩā´°ā´ļāĩā´¨ā´™āĩā´™ā´ŗāĩā´Ŗāĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´žā´¤āĩā´°ā´‚ ⴇⴤāĩ ā´Ēā´°āĩ€ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•.", + "advanced_settings_enable_alternate_media_filter_title": "[ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ] ⴇⴤⴰ ā´‰ā´Ēā´•ā´°ā´Ŗ ā´†āĩŊā´Ŧā´‚ ā´¸ā´ŋā´™āĩā´•āĩ ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "advanced_settings_log_level_title": "ā´˛āĩ‹ā´—āĩ ā´˛āĩ†ā´ĩāĩŊ: {level}", "advanced_settings_prefer_remote_subtitle": "ⴚā´ŋā´˛ ā´‰ā´Ēā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛āĩā´•āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´ĩā´ŗā´°āĩ† ā´ĩāĩ‡ā´—ā´¤ ā´•āĩā´ąā´ĩā´žā´Ŗāĩ. ā´Ēā´•ā´°ā´‚ ā´ąā´ŋā´Žāĩ‹ā´Ÿāĩā´Ÿāĩ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ⴈ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´‚ ⴏⴜāĩ€ā´ĩā´Žā´žā´•āĩā´•āĩā´•.", "advanced_settings_prefer_remote_title": "ā´ąā´ŋā´Žāĩ‹ā´Ÿāĩā´Ÿāĩ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Žāĩāĩģā´—ā´Ŗā´¨ ā´¨āĩŊā´•āĩā´•", @@ -497,6 +439,8 @@ "advanced_settings_proxy_headers_title": "ā´•ā´¸āĩā´ąāĩā´ąā´‚ ā´Ēāĩā´°āĩ‹ā´•āĩā´¸ā´ŋ ā´šāĩ†ā´Ąā´ąāĩā´•āĩž [ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ]", "advanced_settings_readonly_mode_subtitle": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Žā´žā´¤āĩā´°ā´‚ ā´•ā´žā´Ŗā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´¨āĩā´¨ ā´ąāĩ€ā´Ąāĩ-ā´“āĩēā´˛ā´ŋ ā´Žāĩ‹ā´Ąāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´’ā´¨āĩā´¨ā´ŋⴞⴧā´ŋā´•ā´‚ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩŊ, ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ, ā´•ā´žā´¸āĩā´ąāĩā´ąā´ŋā´‚ā´—āĩ, ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩŊ ā´Žā´¨āĩā´¨ā´ŋā´ĩā´¯āĩ†ā´˛āĩā´˛ā´žā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´‚. ā´Ēāĩā´°ā´§ā´žā´¨ ā´¸āĩā´•āĩā´°āĩ€ā´¨ā´ŋā´˛āĩ† ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´…ā´ĩā´¤ā´žāĩŧ ā´ĩā´´ā´ŋ ā´ąāĩ€ā´Ąāĩ-ā´“āĩēā´˛ā´ŋ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•/ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•.", "advanced_settings_readonly_mode_title": "ā´ąāĩ€ā´Ąāĩ-ā´“āĩēā´˛ā´ŋ ā´Žāĩ‹ā´Ąāĩ", + "advanced_settings_self_signed_ssl_subtitle": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´Žāĩģā´Ąāĩâ€Œā´Ēāĩ‹ā´¯ā´ŋā´¨āĩā´ąā´ŋā´¨ā´žā´¯āĩā´ŗāĩā´ŗ SSL ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´¸āĩā´ĩⴝⴂ ā´’ā´Ēāĩā´Ēā´ŋⴟāĩā´Ÿ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩā´•āĩžā´•āĩā´•āĩ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ.", + "advanced_settings_self_signed_ssl_title": "ā´¸āĩā´ĩⴝⴂ ā´’ā´Ēāĩā´Ēā´ŋⴟāĩā´Ÿ SSL ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩā´•āĩž ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´• [ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ]", "advanced_settings_sync_remote_deletions_subtitle": "ā´ĩāĩ†ā´Ŧā´ŋāĩŊ ā´† ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ⴍⴟⴤāĩā´¤āĩā´Žāĩā´Ēāĩ‹āĩž ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋā´˛āĩ† ā´’ā´°āĩ ā´…ā´¸ā´ąāĩā´ąāĩ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•ā´¯āĩ‹ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•ā´¯āĩ‹ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "advanced_settings_sync_remote_deletions_title": "ā´ąā´ŋā´Žāĩ‹ā´Ÿāĩā´Ÿāĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´˛āĩā´•āĩž ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´• [ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ]", "advanced_settings_tile_subtitle": "ā´ĩā´ŋā´Ēāĩā´˛ā´Žā´žā´¯ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", @@ -512,7 +456,11 @@ "album_delete_confirmation": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ {album} ā´†āĩŊā´Ŧā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "album_delete_confirmation_description": "ⴈ ā´†āĩŊā´Ŧā´‚ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿā´¤ā´žā´Ŗāĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´Žā´ąāĩā´ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•āĩžā´•āĩā´•āĩ ⴇⴍā´ŋ ⴇⴤāĩ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛.", "album_deleted": "ā´†āĩŊā´Ŧā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", + "album_info_card_backup_album_excluded": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´ŋ", + "album_info_card_backup_album_included": "ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ŋ", "album_info_updated": "ā´†āĩŊā´Ŧā´‚ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", + "album_leave": "ā´†āĩŊā´Ŧā´‚ ā´ĩā´ŋⴟāĩā´•ā´¯ā´žā´Ŗāĩ‹?", + "album_leave_confirmation": "{album} ā´ĩā´ŋⴟāĩā´Ÿāĩā´Ēāĩ‹ā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "album_name": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ", "album_options": "ā´†āĩŊā´Ŧā´‚ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", "album_remove_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ŗāĩ‹?", @@ -522,8 +470,15 @@ "album_summary": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ⴏⴂⴗāĩā´°ā´šā´‚", "album_updated": "ā´†āĩŊā´Ŧā´‚ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "album_updated_setting_description": "ā´’ā´°āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴉ⴪āĩā´Ÿā´žā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´’ā´°āĩ ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ ā´¸āĩā´ĩāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", - "album_upload_assets": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´•ā´Žāĩā´Ēāĩā´¯āĩ‚ā´Ÿāĩā´Ÿā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "album_user_left": "{album} ā´ĩā´ŋⴟāĩā´Ÿāĩā´Ēāĩ‹ā´¯ā´ŋ", + "album_user_removed": "{user}-ā´¨āĩ† ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", + "album_viewer_appbar_delete_confirm": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴈ ā´†āĩŊā´Ŧā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "album_viewer_appbar_share_err_delete": "ā´†āĩŊā´Ŧā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", + "album_viewer_appbar_share_err_leave": "ā´†āĩŊā´Ŧā´‚ ā´ĩā´ŋⴟāĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", + "album_viewer_appbar_share_err_remove": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēāĩā´°ā´ļāĩā´¨ā´™āĩā´™ā´ŗāĩā´Ŗāĩā´Ÿāĩ", + "album_viewer_appbar_share_err_title": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´ļāĩ€āĩŧⴎⴕⴂ ā´Žā´žā´ąāĩā´ąāĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", + "album_viewer_appbar_share_leave": "ā´†āĩŊā´Ŧā´‚ ā´ĩā´ŋⴟāĩā´•", + "album_viewer_appbar_share_to": "ⴇⴤā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", "album_viewer_page_share_add_users": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ† ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "album_with_link_access": "ā´˛ā´ŋā´™āĩā´•āĩā´ŗāĩā´ŗ ā´†āĩŧā´•āĩā´•āĩā´‚ ⴈ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ⴆⴺāĩā´•ā´ŗāĩ†ā´¯āĩā´‚ ā´•ā´žā´Ŗā´žāĩģ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•.", "albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž", @@ -535,22 +490,21 @@ "all": "ā´Žā´˛āĩā´˛ā´žā´‚", "all_albums": "ā´Žā´˛āĩā´˛ā´ž ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩā´‚", "all_people": "ā´Žā´˛āĩā´˛ā´ž ⴆⴺāĩā´•ā´ŗāĩā´‚", - "all_photos": "ā´Žā´˛āĩā´˛ā´ž ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚", "all_videos": "ā´Žā´˛āĩā´˛ā´ž ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", "allow_dark_mode": "ā´Ąā´žāĩŧā´•āĩā´•āĩ ā´Žāĩ‹ā´Ąāĩ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", + "allow_edits": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", "allow_public_user_to_download": "ā´ĒāĩŠā´¤āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", "allow_public_user_to_upload": "ā´ĒāĩŠā´¤āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", "allowed": "ā´…ā´¨āĩā´ĩā´Ļā´ŋⴚāĩā´šāĩ", "alt_text_qr_code": "QR ā´•āĩ‹ā´Ąāĩ ⴚā´ŋā´¤āĩā´°ā´‚", - "always_keep": "ā´Žā´Ēāĩā´Ēāĩ‹ā´´āĩā´‚ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "always_keep_photos_hint": "ā´‡ā´Ÿā´‚ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•āĩā´•, ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´Žā´˛āĩā´˛ā´ž ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´‚.", - "always_keep_videos_hint": "ā´‡ā´Ÿā´‚ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•āĩā´•, ā´Žā´˛āĩā´˛ā´ž ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´‚.", + "anti_clockwise": "ā´…ā´Ēāĩā´°ā´Ļā´•āĩā´ˇā´ŋā´Ŗā´Žā´žā´¯ā´ŋ", "api_key": "API ā´•āĩ€", "api_key_description": "ⴈ ā´Žāĩ‚ā´˛āĩā´¯ā´‚ ā´’ā´°āĩ ā´¤ā´ĩā´Ŗ ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩ‚. ā´ĩā´ŋāĩģā´Ąāĩ‹ ā´…ā´Ÿā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ⴇⴤāĩ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´¨āĩā´¨ā´¤āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´•āĩā´•āĩā´•.", "api_key_empty": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† API ā´•āĩ€ā´¯āĩā´Ÿāĩ† ā´Ēāĩ‡ā´°āĩ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•ā´°āĩā´¤āĩ", "api_keys": "API ā´•āĩ€ā´•āĩž", "app_architecture_variant": "ā´ĩāĩ‡ā´°ā´ŋⴝⴍāĩā´ąāĩ (ā´†āĩŧā´•āĩā´•ā´ŋⴟāĩ†ā´•āĩā´šāĩŧ)", "app_bar_signout_dialog_content": "ā´¸āĩˆāĩģ ā´”ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", + "app_bar_signout_dialog_ok": "ā´…ā´¤āĩ†", "app_bar_signout_dialog_title": "ā´¸āĩˆāĩģ ā´”ā´Ÿāĩā´Ÿāĩ", "app_download_links": "ā´†ā´Ēāĩā´Ēāĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž", "app_settings": "ā´†ā´Ēāĩā´Ēāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", @@ -561,49 +515,62 @@ "archive": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ", "archive_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´†āĩŧā´•āĩā´•āĩˆā´ĩā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "archive_or_unarchive_photo": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´• ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´…āĩēā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "archive_page_no_archived_assets": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩā´šāĩ†ā´¯āĩâ€Œā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", + "archive_page_title": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ({count})", "archive_size": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ā´ĩā´˛āĩā´Ēāĩā´Ēā´‚", "archive_size_description": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ā´ĩā´˛āĩā´Ēāĩā´Ēā´‚ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´• (GiB-ā´¯ā´ŋāĩŊ)", "archived": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩā´šāĩ†ā´¯āĩâ€Œā´¤āĩ", "archived_count": "{count, plural, other {ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩā´šāĩ†ā´¯āĩâ€Œā´¤ā´ĩ #}}", "are_these_the_same_person": "ā´‡ā´ĩāĩŧ ā´’ā´°āĩ‡ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯ā´žā´Ŗāĩ‹?", "are_you_sure_to_do_this": "ⴇⴤāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", + "asset_action_delete_err_read_only": "ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žāĩģ ā´Žā´žā´¤āĩā´°ā´Žāĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "asset_action_share_err_offline": "ā´“ā´Ģāĩâ€Œā´˛āĩˆāĩģ ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ⴞⴭāĩā´¯ā´Žā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", "asset_added_to_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "asset_adding_to_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩā´•āĩŠā´Ŗāĩā´Ÿā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", - "asset_created": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šāĩ", - "asset_day_count": "{date}: {count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}}", "asset_description_updated": "ā´…ā´¸ā´ąāĩā´ąā´ŋā´¨āĩā´ąāĩ† ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", + "asset_filename_is_offline": "{filename} ā´Žā´¨āĩā´¨ ā´…ā´¸ā´ąāĩā´ąāĩ ā´“ā´Ģāĩâ€Œā´˛āĩˆā´¨ā´žā´Ŗāĩ", + "asset_has_unassigned_faces": "ā´…ā´¸ā´ąāĩā´ąā´ŋāĩŊ ā´…ā´¸āĩˆāĩģ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Žāĩā´–ā´™āĩā´™ā´ŗāĩā´Ŗāĩā´Ÿāĩ", "asset_hashing": "ā´šā´žā´ˇā´ŋā´‚ā´—āĩâ€Ļ", "asset_list_group_by_sub_title": "ⴇⴤⴍāĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "asset_list_layout_settings_dynamic_layout_title": "ā´Ąāĩˆā´¨ā´žā´Žā´ŋā´•āĩ ā´˛āĩ‡ā´”ā´Ÿāĩā´Ÿāĩ", + "asset_list_layout_settings_group_automatically": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´‚", "asset_list_layout_settings_group_by": "ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩ† ⴇⴤⴍāĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "asset_list_layout_settings_group_by_month_day": "ā´Žā´žā´¸ā´‚ + ā´Ļā´ŋā´ĩⴏⴂ", "asset_list_layout_sub_title": "ā´˛āĩ‡ā´”ā´Ÿāĩā´Ÿāĩ", "asset_list_settings_subtitle": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´—āĩā´°ā´ŋā´Ąāĩ ā´˛āĩ‡ā´”ā´Ÿāĩā´Ÿāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "asset_list_settings_title": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´—āĩā´°ā´ŋā´Ąāĩ", - "asset_not_found_on_device_android": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "asset_not_found_on_device_ios": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛. ā´¨ā´ŋā´™āĩā´™āĩž iCloud ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•ā´¯ā´žā´Ŗāĩ†ā´™āĩā´•ā´ŋāĩŊ, iCloud-āĩŊ ⴏⴂⴭⴰā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Žāĩ‹ā´ļā´‚ ā´Ģā´¯āĩŊ ā´•ā´žā´°ā´Ŗā´‚ ā´…ā´¸ā´ąāĩā´ąāĩ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "asset_not_found_on_icloud": "ⴐⴕāĩā´˛āĩ—ā´Ąā´ŋāĩŊ ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛. iCloud-āĩŊ ⴏⴂⴭⴰā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Žāĩ‹ā´ļā´‚ ā´Ģā´¯āĩŊ ā´•ā´žā´°ā´Ŗā´‚ ā´…ā´¸ā´ąāĩā´ąāĩ ⴆⴕāĩâ€Œā´¸ā´¸āĩā´¸āĩā´šāĩ†ā´¯āĩā´¯ā´žā´¨ā´žā´•ā´žā´¤āĩ† ā´ĩā´¨āĩā´¨āĩ‡ā´•āĩā´•ā´žā´‚", "asset_offline": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´“ā´Ģāĩâ€Œā´˛āĩˆāĩģ", "asset_offline_description": "ⴈ ā´Žā´•āĩā´¸āĩā´ąāĩā´ąāĩ‡ā´ŖāĩŊ ā´…ā´¸ā´ąāĩā´ąāĩ ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´Ąā´ŋā´¸āĩā´•ā´ŋāĩŊ ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛. ā´¸ā´šā´žā´¯ā´¤āĩā´¤ā´ŋā´¨ā´žā´¯ā´ŋ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† Immich ā´…ā´Ąāĩā´Žā´ŋā´¨ā´ŋā´¸āĩā´Ÿāĩā´°āĩ‡ā´ąāĩā´ąā´ąāĩā´Žā´žā´¯ā´ŋ ā´Ŧā´¨āĩā´§ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´•.", + "asset_restored_successfully": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ", "asset_skipped": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´ŋ", "asset_skipped_in_trash": "ⴟāĩā´°ā´žā´ˇā´ŋāĩŊ", + "asset_trashed": "ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "asset_troubleshoot": "ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´Ŧā´ŋāĩžā´ˇāĩ‚ā´Ÿāĩā´Ÿāĩ", "asset_uploaded": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "asset_uploading": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩâ€Ļ", "asset_viewer_settings_subtitle": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´—ā´žā´˛ā´ąā´ŋ ā´ĩāĩā´¯āĩ‚ā´ĩāĩŧ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "asset_viewer_settings_title": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ĩāĩā´¯āĩ‚ā´ĩāĩŧ", "assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž", + "assets_added_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ}}", "assets_added_to_album_count": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ {count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ}}", "assets_added_to_albums_count": "{albumTotal, plural, one {# ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ} other {# ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ}} {assetTotal, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ā´…ā´¸ā´ąāĩā´ąāĩ} other {ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "assets_cannot_be_added_to_albums": "{count, plural, one {ā´…ā´¸ā´ąāĩā´ąāĩ} other {ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´’ā´°āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩā´‚ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "assets_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}}", + "assets_deleted_permanently": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", + "assets_deleted_permanently_from_server": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) Immich ā´¸āĩ†āĩŧā´ĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", + "assets_downloaded_failed": "{count, plural, one {# ā´Ģā´¯āĩŊ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ - {error} ā´Ģā´¯āĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ} other {# ā´Ģⴝⴞāĩā´•āĩž ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ - {error} ā´Ģⴝⴞāĩā´•āĩž ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ}}", + "assets_downloaded_successfully": "{count, plural, one {# ā´Ģā´¯āĩŊ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ} other {# ā´Ģⴝⴞāĩā´•āĩž ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ}}", "assets_moved_to_trash_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋ}}", "assets_permanently_deleted_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ}}", "assets_removed_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ}}", + "assets_removed_permanently_from_device": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "assets_restore_confirmation": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Žā´˛āĩā´˛ā´ž ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹? ⴈ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ā´Ēā´´ā´¯ā´Ēⴟā´ŋā´¯ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛! ā´“ā´Ģāĩâ€Œā´˛āĩˆāĩģ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ⴈ ā´°āĩ€ā´¤ā´ŋā´¯ā´ŋāĩŊ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛āĩ†ā´¨āĩā´¨ ā´•ā´žā´°āĩā´¯ā´‚ ā´ļāĩā´°ā´Ļāĩā´§ā´ŋā´•āĩā´•āĩā´•.", "assets_restored_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ}}", + "assets_restored_successfully": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ", "assets_trashed": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "assets_trashed_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤āĩ}}", + "assets_trashed_from_server": "{count} ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) Immich ā´¸āĩ†āĩŧā´ĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "assets_were_part_of_album_count": "{count, plural, one {ā´…ā´¸ā´ąāĩā´ąāĩ ⴇⴤā´ŋⴍⴕⴂ} other {ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴇⴤā´ŋⴍⴕⴂ}} ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´­ā´žā´—ā´Žā´žā´¯ā´ŋā´°āĩā´¨āĩā´¨āĩ", "assets_were_part_of_albums_count": "{count, plural, one {ā´…ā´¸ā´ąāĩā´ąāĩ ⴇⴤā´ŋⴍⴕⴂ} other {ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴇⴤā´ŋⴍⴕⴂ}} ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´­ā´žā´—ā´Žā´žā´¯ā´ŋā´°āĩā´¨āĩā´¨āĩ", "authorized_devices": "ā´…ā´‚ā´—āĩ€ā´•āĩƒā´¤ ā´‰ā´Ēā´•ā´°ā´Ŗā´™āĩā´™āĩž", @@ -612,48 +579,86 @@ "autoplay_slideshow": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "back": "ā´¤ā´ŋā´°ā´ŋā´•āĩ†", "back_close_deselect": "ā´Ēāĩā´ąā´•āĩ‹ā´Ÿāĩā´Ÿāĩ, ā´…ā´Ÿā´¯āĩā´•āĩā´•āĩā´•, ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", + "background_backup_running_error": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´Žā´žā´¨āĩā´ĩāĩŊ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴆⴰⴂⴭā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "background_location_permission": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´…ā´¨āĩā´Žā´¤ā´ŋ", "background_location_permission_content": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ā´¤āĩā´¤ā´ŋāĩŊ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩā´•āĩž ā´Žā´žā´ąāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ, Immich-ā´¨āĩ ā´Žā´Ēāĩā´Ēāĩ‹ā´´āĩā´‚ ā´•āĩƒā´¤āĩā´¯ā´Žā´žā´¯ ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴉ⴪āĩā´Ÿā´žā´¯ā´ŋā´°ā´ŋā´•āĩā´•ā´Ŗā´‚, ā´…ā´¤āĩā´ĩā´´ā´ŋ ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩ ā´ĩāĩˆ-ā´Ģāĩˆ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•ā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´‚", "background_options": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", "backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ", + "backup_album_selection_page_albums_device": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋā´˛āĩ† ā´†āĩŊā´Ŧā´™āĩā´™āĩž ({count})", "backup_album_selection_page_albums_tap": "ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤ā´žāĩģ ā´Ÿā´žā´Ēāĩā´Ēāĩā´šāĩ†ā´¯āĩā´¯āĩā´•, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´žāĩģ ā´Ąā´Ŧā´ŋāĩž ā´Ÿā´žā´Ēāĩā´Ēāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "backup_album_selection_page_assets_scatter": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´’ā´¨āĩā´¨ā´ŋⴞⴧā´ŋā´•ā´‚ ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛ā´žā´¯ā´ŋ ā´ĩāĩā´¯ā´žā´Ēā´ŋⴚāĩā´šāĩā´•ā´ŋā´Ÿā´•āĩā´•ā´žā´‚. ā´…ā´¤ā´ŋā´¨ā´žāĩŊ, ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´°ā´•āĩā´°ā´ŋⴝⴝā´ŋāĩŊ ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•ā´¯āĩ‹ ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´•ā´¯āĩ‹ ⴚāĩ†ā´¯āĩā´¯ā´žā´‚.", "backup_album_selection_page_select_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "backup_album_selection_page_selection_info": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩŊ ā´ĩā´ŋā´ĩā´°ā´‚", + "backup_album_selection_page_total_assets": "ⴆⴕāĩ† ā´¸ā´ĩā´ŋā´ļāĩ‡ā´ˇā´Žā´žā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž", "backup_albums_sync": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´†āĩŊā´Ŧā´‚ ā´¸ā´ŋāĩģā´•āĩā´°āĩŠā´Ŗāĩˆā´¸āĩ‡ā´ˇāĩģ", + "backup_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "backup_background_service_backup_failed_message": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ. ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", "backup_background_service_complete_notification": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´¯ā´ŋ", + "backup_background_service_connection_failed_message": "ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´•ā´Ŗā´•āĩā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ. ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", + "backup_background_service_current_upload_notification": "{filename} ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "backup_background_service_default_notification": "ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", + "backup_background_service_error_title": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēā´ŋā´ļā´•āĩ", "backup_background_service_in_progress_notification": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "backup_controller_page_albums": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´†āĩŊā´Ŧā´™āĩā´™āĩž", + "backup_controller_page_background_app_refresh_disabled_content": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ, ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž > ā´ĒāĩŠā´¤āĩā´ĩā´žā´¯ā´¤āĩ > ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´†ā´Ēāĩā´Ēāĩ ā´ąā´ŋā´Ģāĩā´°ā´ˇāĩ ā´Žā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´†ā´Ēāĩā´Ēāĩ ā´ąā´ŋā´Ģāĩā´°ā´ˇāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•.", + "backup_controller_page_background_app_refresh_disabled_title": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´†ā´Ēāĩā´Ēāĩ ā´ąā´ŋā´Ģāĩā´°ā´ˇāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ", + "backup_controller_page_background_app_refresh_enable_button_text": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´•", "backup_controller_page_background_battery_info_link": "ā´Žā´™āĩā´™ā´¨āĩ†ā´¯āĩ†ā´¨āĩā´¨āĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "backup_controller_page_background_battery_info_message": "ā´Žā´ŋā´•ā´šāĩā´š ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´…ā´¨āĩā´­ā´ĩā´¤āĩā´¤ā´ŋā´¨ā´žā´¯ā´ŋ, Immich-ā´¨āĩā´ąāĩ† ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ā´¨ā´ŋⴝⴍāĩā´¤āĩā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´ā´¤āĩ†ā´™āĩā´•ā´ŋā´˛āĩā´‚ ā´Ŧā´žā´ąāĩā´ąā´ąā´ŋ ā´’ā´Ēāĩā´ąāĩā´ąā´ŋā´Žāĩˆā´¸āĩ‡ā´ˇā´¨āĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•.\n\nⴇⴤāĩ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤āĩ† ā´†ā´ļāĩā´°ā´¯ā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žāĩŊ, ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗ ā´¨ā´ŋāĩŧā´Žāĩā´Žā´žā´¤ā´žā´ĩā´ŋā´¨āĩā´ŗāĩā´ŗ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´¯ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´•.", "backup_controller_page_background_battery_info_ok": "ā´ļā´°ā´ŋ", "backup_controller_page_background_battery_info_title": "ā´Ŧā´žā´ąāĩā´ąā´ąā´ŋ ā´’ā´Ēāĩā´ąāĩā´ąā´ŋā´Žāĩˆā´¸āĩ‡ā´ˇā´¨āĩā´•āĩž", + "backup_controller_page_background_charging": "ā´šā´žāĩŧⴜāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Žā´žā´¤āĩā´°ā´‚", + "backup_controller_page_background_configure_error": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´¸āĩ‡ā´ĩⴍⴂ ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "backup_controller_page_background_delay": "ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´ĩāĩˆā´•ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•: {duration}", + "backup_controller_page_background_description": "ā´†ā´Ēāĩā´Ēāĩ ā´¤āĩā´ąā´•āĩā´•ā´žā´¤āĩ† ⴤⴍāĩā´¨āĩ† ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´¸āĩ‡ā´ĩⴍⴂ ā´“ā´Ŗā´žā´•āĩā´•āĩā´•", + "backup_controller_page_background_is_off": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´• ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ģā´žā´Ŗāĩ", + "backup_controller_page_background_is_on": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´• ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ŗā´žā´Ŗāĩ", + "backup_controller_page_background_turn_off": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´¸āĩ‡ā´ĩⴍⴂ ā´“ā´Ģā´žā´•āĩā´•āĩā´•", + "backup_controller_page_background_turn_on": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´¸āĩ‡ā´ĩⴍⴂ ā´“ā´Ŗā´žā´•āĩā´•āĩā´•", + "backup_controller_page_background_wifi": "ā´ĩāĩˆ-ā´Ģāĩˆā´¯ā´ŋāĩŊ ā´Žā´žā´¤āĩā´°ā´‚", "backup_controller_page_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ", "backup_controller_page_backup_selected": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ĩ: ", "backup_controller_page_backup_sub": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", + "backup_controller_page_created": "ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šā´¤āĩ: {date}", + "backup_controller_page_desc_backup": "ā´†ā´Ēāĩā´Ēāĩ ā´¤āĩā´ąā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ģāĩ‹āĩŧā´—āĩā´°āĩ—ā´Ŗāĩā´Ÿāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ŗā´žā´•āĩā´•āĩā´•.", "backup_controller_page_excluded": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´ŋā´¯ā´ĩ: ", + "backup_controller_page_failed": "ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ ({count})", + "backup_controller_page_filename": "ā´Ģⴝⴞā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ: {filename}[{size}]", + "backup_controller_page_id": "ā´ā´Ąā´ŋ: {id}", + "backup_controller_page_info": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž", "backup_controller_page_none_selected": "ā´’ā´¨āĩā´¨āĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", "backup_controller_page_remainder": "ā´ļāĩ‡ā´ˇā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ", "backup_controller_page_remainder_sub": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ĩā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´ŗāĩā´ŗ ā´ļāĩ‡ā´ˇā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", "backup_controller_page_server_storage": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´¸āĩā´ąāĩā´ąāĩ‹ā´ąāĩ‡ā´œāĩ", + "backup_controller_page_start_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´•", + "backup_controller_page_status_off": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´• ā´Ģāĩ‹āĩŧā´—āĩā´°āĩ—ā´Ŗāĩā´Ÿāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ģā´žā´Ŗāĩ", + "backup_controller_page_status_on": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´• ā´Ģāĩ‹āĩŧā´—āĩā´°āĩ—ā´Ŗāĩā´Ÿāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ŗā´žā´Ŗāĩ", "backup_controller_page_storage_format": "{total}-āĩŊ {used} ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ", "backup_controller_page_to_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩ‡ā´Ŗāĩā´Ÿ ā´†āĩŊā´Ŧā´™āĩā´™āĩž", "backup_controller_page_total_sub": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩā´ŗāĩā´ŗ ā´Žā´˛āĩā´˛ā´ž ā´¸ā´ĩā´ŋā´ļāĩ‡ā´ˇā´Žā´žā´¯ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", + "backup_controller_page_turn_off": "ā´Ģāĩ‹āĩŧā´—āĩā´°āĩ—ā´Ŗāĩā´Ÿāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ģā´žā´•āĩā´•āĩā´•", + "backup_controller_page_turn_on": "ā´Ģāĩ‹āĩŧā´—āĩā´°āĩ—ā´Ŗāĩā´Ÿāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ŗā´žā´•āĩā´•āĩā´•", + "backup_controller_page_uploading_file_info": "ā´Ģā´¯āĩŊ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", + "backup_err_only_album": "ā´’ā´°āĩ‡ā´¯āĩŠā´°āĩ ā´†āĩŊā´Ŧā´‚ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "backup_error_sync_failed": "ā´¸ā´ŋā´™āĩā´•āĩ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ. ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛.", "backup_info_card_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž", + "backup_manual_cancelled": "ā´ąā´Ļāĩā´Ļā´žā´•āĩā´•ā´ŋ", + "backup_manual_in_progress": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴇⴤā´ŋⴍⴕⴂ ā´Ēāĩā´°āĩ‹ā´—ā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´•āĩā´ąā´šāĩā´šāĩ ā´¸ā´Žā´¯ā´¤āĩā´¤ā´ŋā´¨āĩ ā´ļāĩ‡ā´ˇā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´•", + "backup_manual_success": "ā´ĩā´ŋā´œā´¯ā´‚", + "backup_manual_title": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´¨ā´ŋā´˛", "backup_options": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", + "backup_options_page_title": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", + "backup_setting_subtitle": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛, ā´Žāĩāĩģā´¨ā´ŋā´° ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "backup_settings_subtitle": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "backward": "ā´Ēā´ŋā´¨āĩā´¨āĩ‹ā´Ÿāĩā´Ÿāĩ", - "battery_optimization_backup_reliability": "ā´Ŧā´žā´ąāĩā´ąā´ąā´ŋ ā´’ā´Ēāĩā´ąāĩā´ąā´ŋā´Žāĩˆā´¸āĩ‡ā´ˇā´¨āĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋāĩģāĩā´ąāĩ† ā´ĩā´ŋā´ļāĩā´ĩā´žā´¸āĩā´¯ā´¤ ā´Žāĩ†ā´šāĩā´šā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´‚", "biometric_auth_enabled": "ā´Ŧā´¯āĩ‹ā´Žāĩ†ā´Ÿāĩā´°ā´ŋā´•āĩ ā´Ēāĩā´°ā´žā´Žā´žā´Ŗāĩ€ā´•ā´°ā´Ŗā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋ", "biometric_locked_out": "ā´Ŧā´¯āĩ‹ā´Žāĩ†ā´Ÿāĩā´°ā´ŋā´•āĩ ā´Ēāĩā´°ā´žā´Žā´žā´Ŗāĩ€ā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩ† ā´˛āĩ‹ā´•āĩā´•āĩ ā´”ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "biometric_no_options": "ā´Ŧā´¯āĩ‹ā´Žāĩ†ā´Ÿāĩā´°ā´ŋā´•āĩ ā´“ā´Ēāĩā´ˇā´¨āĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", "biometric_not_available": "ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´Ŧā´¯āĩ‹ā´Žāĩ†ā´Ÿāĩā´°ā´ŋā´•āĩ ā´Ēāĩā´°ā´žā´Žā´žā´Ŗāĩ€ā´•ā´°ā´Ŗā´‚ ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", + "birthdate_saved": "ⴜⴍⴍⴤāĩā´¤āĩ€ā´¯ā´¤ā´ŋ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "birthdate_set_description": "ā´’ā´°āĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´Žā´Ÿāĩā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹ā´´āĩā´ŗāĩā´ŗ ⴈ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩā´Ÿāĩ† ā´Ēāĩā´°ā´žā´¯ā´‚ ā´•ā´Ŗā´•āĩā´•ā´žā´•āĩā´•ā´žāĩģ ⴜⴍⴍⴤāĩā´¤āĩ€ā´¯ā´¤ā´ŋ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ.", "blurred_background": "ā´Žā´™āĩā´™ā´ŋā´¯ ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ā´‚", - "browse_templates": "ⴟāĩ†ā´‚ā´Ēāĩā´˛āĩ‡ā´ąāĩā´ąāĩā´•āĩž ā´Ŧāĩā´°āĩ—ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "bugs_and_feature_requests": "ā´Ŧā´—āĩā´•ā´ŗāĩā´‚ ā´Ģāĩ€ā´šāĩā´šāĩŧ ā´…ā´­āĩā´¯āĩŧā´¤āĩā´Ĩⴍⴕⴺāĩā´‚", "build": "ā´Ŧā´ŋāĩŊā´Ąāĩ", "build_image": "ā´‡ā´Žāĩ‡ā´œāĩ ā´¨ā´ŋāĩŧā´Žāĩā´Žā´ŋā´•āĩā´•āĩā´•", @@ -661,6 +666,20 @@ "bulk_keep_duplicates_confirmation": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ {count, plural, one {# ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´¨ā´ŋⴞⴍā´ŋāĩŧā´¤āĩā´¤ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹? ⴇⴤāĩ ā´’ā´¨āĩā´¨āĩā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žā´¤āĩ† ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩā´•ā´ŗāĩ†ā´¯āĩā´‚ ā´Ēā´°ā´ŋā´šā´°ā´ŋā´•āĩā´•āĩā´‚.", "bulk_trash_duplicates_confirmation": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ {count, plural, one {# ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´Ŧāĩžā´•āĩā´•ā´žā´¯ā´ŋ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹? ⴇⴤāĩ ā´“ā´°āĩ‹ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēā´ŋā´˛āĩ†ā´¯āĩā´‚ ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´ĩā´˛ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩ ā´¨ā´ŋⴞⴍā´ŋāĩŧā´¤āĩā´¤āĩā´•ā´¯āĩā´‚ ā´Žā´ąāĩā´ąāĩ ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´‚.", "buy": "Immich ā´ĩā´žā´™āĩā´™āĩā´•", + "cache_settings_clear_cache_button": "ā´•ā´žā´ˇāĩ† ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "cache_settings_clear_cache_button_title": "ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´•ā´žā´ˇāĩ† ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ. ā´•ā´žā´ˇāĩ† ā´Ēāĩā´¨āĩŧā´¨ā´ŋāĩŧā´Žāĩā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩā´ĩā´°āĩ† ⴇⴤāĩ ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩā´°ā´•ā´Ÿā´¨ā´¤āĩā´¤āĩ† ā´¸ā´žā´°ā´Žā´žā´¯ā´ŋ ā´Ŧā´žā´§ā´ŋā´•āĩā´•āĩā´‚.", + "cache_settings_duplicated_assets_clear_button": "ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "cache_settings_duplicated_assets_subtitle": "ā´†ā´Ēāĩā´Ēāĩ ā´…ā´ĩā´—ā´Ŗā´ŋⴚāĩā´š ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", + "cache_settings_duplicated_assets_title": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ({count})", + "cache_settings_statistics_album": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛āĩā´•āĩž", + "cache_settings_statistics_full": "ā´Ēāĩ‚āĩŧā´Ŗāĩā´Ŗ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž", + "cache_settings_statistics_shared": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´‚ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛āĩā´•āĩž", + "cache_settings_statistics_thumbnail": "ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛āĩā´•āĩž", + "cache_settings_statistics_title": "ā´•ā´žā´ˇāĩ† ā´‰ā´Ēā´¯āĩ‹ā´—ā´‚", + "cache_settings_subtitle": "Immich ā´ŽāĩŠā´ŦāĩˆāĩŊ ā´†ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ˇā´¨āĩā´ąāĩ† ā´•ā´žā´ˇā´ŋā´‚ā´—āĩ ā´¸āĩā´ĩā´­ā´žā´ĩā´‚ ā´¨ā´ŋⴝⴍāĩā´¤āĩā´°ā´ŋā´•āĩā´•āĩā´•", + "cache_settings_tile_subtitle": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´¸āĩā´ąāĩā´ąāĩ‹ā´ąāĩ‡ā´œāĩ ā´¸āĩā´ĩā´­ā´žā´ĩā´‚ ā´¨ā´ŋⴝⴍāĩā´¤āĩā´°ā´ŋā´•āĩā´•āĩā´•", + "cache_settings_tile_title": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´¸āĩā´ąāĩā´ąāĩ‹ā´ąāĩ‡ā´œāĩ", + "cache_settings_title": "ā´•ā´žā´ˇā´ŋā´‚ā´—āĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "camera": "ā´•āĩā´¯ā´žā´Žā´ą", "camera_brand": "ā´•āĩā´¯ā´žā´Žā´ą ā´Ŧāĩā´°ā´žāĩģā´Ąāĩ", "camera_model": "ā´•āĩā´¯ā´žā´Žā´ą ā´Žāĩ‹ā´ĄāĩŊ", @@ -673,10 +692,10 @@ "cannot_update_the_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "cast": "ā´•ā´žā´¸āĩā´ąāĩā´ąāĩ", "cast_description": "ⴞⴭāĩā´¯ā´Žā´žā´¯ ā´•ā´žā´¸āĩā´ąāĩā´ąāĩ ā´Ąāĩ†ā´¸āĩā´ąāĩā´ąā´ŋā´¨āĩ‡ā´ˇā´¨āĩā´•āĩž ā´•āĩ‹āĩēā´Ģā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "change": "ā´Žā´žā´ąāĩā´ąāĩā´•", "change_date": "ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Žā´žā´ąāĩā´ąāĩā´•", "change_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´Žā´žā´ąāĩā´ąāĩā´•", "change_display_order": "ā´Ēāĩā´°ā´Ļāĩŧā´ļā´¨ ā´•āĩā´°ā´Žā´‚ ā´Žā´žā´ąāĩā´ąāĩā´•", + "change_expiration_time": "ā´•ā´žā´˛ā´šā´°ā´Ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¨āĩā´¨ ā´¸ā´Žā´¯ā´‚ ā´Žā´žā´ąāĩā´ąāĩā´•", "change_location": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ā´Žā´žā´ąāĩā´ąāĩā´•", "change_name": "ā´Ēāĩ‡ā´°āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "change_name_successfully": "ā´Ēāĩ‡ā´°āĩ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Žā´žā´ąāĩā´ąā´ŋ", @@ -694,55 +713,38 @@ "changed_visibility_successfully": "ā´Ļāĩƒā´ļāĩā´¯ā´¤ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Žā´žā´ąāĩā´ąā´ŋ", "charging": "ā´šā´žāĩŧⴜāĩā´œāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "charging_requirement_mobile_backup": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋā´¨āĩ ā´‰ā´Ēā´•ā´°ā´Ŗā´‚ ā´šā´žāĩŧⴜāĩ ⴚāĩ†ā´¯āĩā´¯āĩ‡ā´Ŗāĩā´Ÿā´¤āĩā´Ŗāĩā´Ÿāĩ", + "check_corrupt_asset_backup": "ā´•āĩ‡ā´Ÿā´žā´¯ ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩā´•āĩž ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", + "check_corrupt_asset_backup_button": "ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ⴍⴟⴤāĩā´¤āĩā´•", + "check_corrupt_asset_backup_description": "ā´Žā´˛āĩā´˛ā´ž ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´ļāĩ‡ā´ˇā´‚, ā´ĩāĩˆ-ā´Ģāĩˆā´¯ā´ŋāĩŊ ā´Žā´žā´¤āĩā´°ā´‚ ⴈ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ ⴍⴟⴤāĩā´¤āĩā´•. ⴍⴟā´Ēⴟā´ŋā´•āĩā´°ā´Žā´¤āĩā´¤ā´ŋā´¨āĩ ā´•āĩā´ąā´šāĩā´šāĩ ā´Žā´ŋā´¨ā´ŋā´ąāĩā´ąāĩā´•āĩž ā´Žā´Ÿāĩā´¤āĩā´¤āĩ‡ā´•āĩā´•ā´žā´‚.", "check_logs": "ā´˛āĩ‹ā´—āĩā´•āĩž ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", - "checksum": "ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚", - "choose": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "choose_matching_people_to_merge": "ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´žāĩģ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¨āĩā´¨ ⴆⴺāĩā´•ā´ŗāĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "city": "ⴍⴗⴰⴂ", - "cleanup_confirm_description": "ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¸āĩā´°ā´•āĩā´ˇā´ŋā´¤ā´Žā´žā´¯ā´ŋ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ {count} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ({date}-ā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋⴚāĩā´šā´¤āĩ) ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋ. ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´Ēā´•āĩŧā´Ēāĩā´Ēāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ŗāĩ‹?", - "cleanup_confirm_prompt_title": "ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ŗāĩ‹?", - "cleanup_deleted_assets": "ā´‰ā´Ēā´•ā´°ā´Ŗ ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ {count} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´ŋ", - "cleanup_deleting": "ⴚā´ĩā´ąāĩā´ąāĩā´•āĩā´Ÿāĩā´Ÿā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨āĩ€ā´™āĩā´™āĩā´¨āĩā´¨āĩ...", - "cleanup_found_assets": "{count} ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋ", - "cleanup_found_assets_with_size": "{count} ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋ ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´¸āĩā´•ā´žā´¨ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•ā´ŋā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "cleanup_no_assets_found": "ā´Žāĩā´•ā´ŗā´ŋā´˛āĩā´ŗāĩā´ŗ ā´Žā´žā´¨ā´Ļā´Ŗāĩā´Ąā´™āĩā´™ā´ŗāĩā´Žā´žā´¯ā´ŋ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¨āĩā´¨ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛. ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ā´Ģāĩā´°āĩ€ ā´…ā´Ēāĩā´Ēāĩ ā´¸āĩâ€Œā´Ēāĩ†ā´¯āĩâ€Œā´¸ā´ŋā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¯ā´žā´¨ā´žā´•āĩ‚", - "cleanup_preview_title": "ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ({count})", - "cleanup_step3_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¤āĩ€ā´¯ā´¤ā´ŋā´¯āĩā´Žā´žā´¯ā´ŋ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¨āĩā´¨ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´¸āĩâ€Œā´•ā´žāĩģ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•.", - "cleanup_step4_summary": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ {count} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ({date}-ā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋⴚāĩā´šā´¤āĩ). ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´†ā´Ēāĩā´Ēā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¤āĩā´Ÿāĩŧā´¨āĩā´¨āĩā´‚ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨ā´žā´•āĩā´‚.", - "cleanup_trash_hint": "ⴏⴂⴭⴰ⴪ ā´¸āĩā´Ĩⴞⴂ ā´Ēāĩ‚āĩŧā´Ŗāĩā´Ŗā´Žā´žā´¯ā´ŋ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩ†ā´Ÿāĩā´•āĩā´•ā´žāĩģ, ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´—ā´žā´˛ā´ąā´ŋ ā´†ā´Ēāĩā´Ēāĩ ā´¤āĩā´ąā´¨āĩā´¨āĩ ⴟāĩā´°ā´žā´ˇāĩ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•āĩā´•", "clear": "ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "clear_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "clear_all_recent_searches": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ⴍⴟⴤāĩā´¤ā´ŋā´¯ ā´Žā´˛āĩā´˛ā´ž ā´¤ā´ŋⴰⴝⴞāĩā´•ā´ŗāĩā´‚ ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "clear_failed_count": "ā´Žā´žā´¯āĩâ€Œā´•āĩā´•ā´žā´¨ā´žā´¯ā´ŋā´˛āĩā´˛ ({count})", "clear_file_cache": "ā´Ģā´¯āĩŊ ā´•ā´žā´ˇāĩ† ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "clear_message": "ⴏⴍāĩā´Ļāĩ‡ā´ļā´‚ ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "clear_value": "ā´Žāĩ‚ā´˛āĩā´¯ā´‚ ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "client_cert_dialog_msg_confirm": "ā´ļā´°ā´ŋ", + "client_cert_enter_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´¨āĩŊā´•āĩā´•", "client_cert_import": "ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "client_cert_import_success_msg": "ā´•āĩā´˛ā´¯ā´ŋā´¨āĩā´ąāĩ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩā´šāĩ†ā´¯āĩâ€Œā´¤āĩ", "client_cert_invalid_msg": "ā´…ā´¸ā´žā´§āĩā´ĩā´žā´¯ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´Ģā´¯āĩŊ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´¤āĩ†ā´ąāĩā´ąā´žā´¯ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", - "client_cert_password_message": "ⴈ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąā´ŋāĩģāĩā´ąāĩ† ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´¨āĩŊā´•āĩā´•", - "client_cert_password_title": "ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", "client_cert_remove_msg": "ā´•āĩā´˛ā´¯ā´ŋā´¨āĩā´ąāĩ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩâ€Œā´¤āĩ", "client_cert_subtitle": "PKCS12 (.p12, .pfx) ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąāĩ ā´Žā´žā´¤āĩā´°ā´‚ ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ/ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¯āĩŊ ⴞⴭāĩā´¯ā´Žā´žā´•āĩ‚", "client_cert_title": "SSL ā´•āĩā´˛ā´¯ā´ŋā´¨āĩā´ąāĩ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ [ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋāĩŊ]", + "clockwise": "ā´Ēāĩā´°ā´Ļā´•āĩā´ˇā´ŋā´Ŗā´Žā´žā´¯ā´ŋ", "close": "ā´…ā´Ÿā´¯āĩā´•āĩā´•āĩā´•", "collapse": "ⴚāĩā´°āĩā´•āĩā´•āĩā´•", "collapse_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ⴚāĩā´°āĩā´•āĩā´•āĩā´•", "color": "ā´¨ā´ŋā´ąā´‚", - "command": "ā´•ā´Žā´žāĩģā´Ąāĩ", - "command_palette_prompt": "ā´Ēāĩ‡ā´œāĩā´•ā´ŗāĩ‹ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´™āĩā´™ā´ŗāĩ‹ ā´•ā´Žā´žāĩģā´Ąāĩā´•ā´ŗāĩ‹ ā´ĩāĩ‡ā´—ā´¤āĩā´¤ā´ŋāĩŊ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´•", - "command_palette_to_close": "ā´…ā´Ÿā´¯āĩā´•āĩā´•ā´žāĩģ", - "command_palette_to_navigate": "ā´Ēāĩā´°ā´ĩāĩ‡ā´ļā´ŋā´•āĩā´•ā´žāĩģ", - "command_palette_to_select": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•ā´žāĩģ", - "command_palette_to_show_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•ā´žā´Ŗā´ŋā´•āĩā´•ā´žāĩģ", + "color_theme": "ā´•ā´ŗāĩŧ ā´¤āĩ€ā´‚", "comment_deleted": "ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", "comment_options": "ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´¤āĩā´¤ā´ŋā´¨āĩā´ŗāĩā´ŗ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", "comments_and_likes": "ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´™āĩā´™ā´ŗāĩā´‚ ā´˛āĩˆā´•āĩā´•āĩā´•ā´ŗāĩā´‚", "comments_are_disabled": "ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´™āĩā´™āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ", "common_create_new_album": "ā´Ēāĩā´¤ā´ŋā´¯ ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "completed": "ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´¯ā´ŋ", - "configuration": "ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ", "confirm": "ā´¸āĩā´Ĩā´ŋā´°āĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "confirm_admin_password": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´¸āĩā´Ĩā´ŋā´°āĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "confirm_delete_face": "{name} ā´Žā´¨āĩā´¨ ā´Žāĩā´–ā´‚ ā´…ā´¸ā´ąāĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", @@ -757,16 +759,19 @@ "contain": "ā´‰āĩžā´•āĩā´•āĩŠā´ŗāĩā´ŗāĩā´•", "context": "ⴏⴍāĩā´Ļāĩŧā´­ā´‚", "continue": "ā´¤āĩā´Ÿā´°āĩā´•", - "control_bottom_app_bar_add_tags": "ā´Ÿā´žā´—āĩā´•āĩž ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "control_bottom_app_bar_create_new_album": "ā´Ēāĩā´¤ā´ŋā´¯ ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", + "control_bottom_app_bar_delete_from_immich": "Immich-āĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "control_bottom_app_bar_delete_from_local": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "control_bottom_app_bar_edit_location": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "control_bottom_app_bar_edit_time": "ā´¤āĩ€ā´¯ā´¤ā´ŋā´¯āĩā´‚ ā´¸ā´Žā´¯ā´ĩāĩā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", + "control_bottom_app_bar_share_link": "ā´˛ā´ŋā´™āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", + "control_bottom_app_bar_share_to": "ⴇⴤā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", "control_bottom_app_bar_trash_from_immich": "ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "copied_image_to_clipboard": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´•āĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´Ŧāĩ‹āĩŧā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤ā´ŋ.", "copied_to_clipboard": "ā´•āĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´Ŧāĩ‹āĩŧā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤ā´ŋ!", "copy_error": "ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", + "copy_file_path": "ā´Ģā´¯āĩŊ ā´Ēā´žā´¤āĩā´¤āĩ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", "copy_image": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", - "copy_json": "JSON ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", "copy_link": "ā´˛ā´ŋā´™āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", "copy_link_to_clipboard": "ā´˛ā´ŋā´™āĩā´•āĩ ā´•āĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´Ŧāĩ‹āĩŧā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", "copy_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", @@ -778,7 +783,6 @@ "create_album": "ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_album_page_untitled": "ā´Ēāĩ‡ā´°ā´ŋā´˛āĩā´˛ā´žā´¤āĩā´¤ā´¤āĩ", "create_api_key": "ā´Žā´Ēā´ŋⴐ ā´•āĩ€ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´•", - "create_first_workflow": "ā´†ā´Ļāĩā´¯ ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´•", "create_library": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_link": "ā´˛ā´ŋā´™āĩā´•āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_link_to_share": "ā´Ēā´™āĩā´•ā´ŋā´Ÿā´žāĩģ ā´˛ā´ŋā´™āĩā´•āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", @@ -787,46 +791,37 @@ "create_new_person": "ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_new_person_hint": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´•āĩā´•āĩ ā´¨āĩŊā´•āĩā´•", "create_new_user": "ā´Ēāĩā´¤ā´ŋā´¯ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", - "create_person": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´•", - "create_person_subtitle": "ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•ā´žā´¨āĩā´‚ ā´Ÿā´žā´—āĩā´šāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´Žāĩā´–ā´¤āĩā´¤āĩ‡ā´•āĩā´•āĩ ā´’ā´°āĩ ā´Ēāĩ‡ā´°āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "create_shared_album_page_share_add_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "create_shared_album_page_share_select_photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "create_shared_link": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_tag": "ā´Ÿā´žā´—āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "create_tag_description": "ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´Ÿā´žā´—āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•. ā´¨āĩ†ā´¸āĩā´ąāĩā´ąā´Ąāĩ ā´Ÿā´žā´—āĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ, ā´Ģāĩ‹āĩŧā´ĩāĩ‡ā´Ąāĩ ā´¸āĩā´˛ā´žā´ˇāĩā´•āĩž ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩ† ā´Ÿā´žā´—ā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‚āĩŧā´Ŗāĩā´Ŗ ā´Ēā´žā´¤ ā´¨āĩŊā´•āĩā´•.", "create_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", - "create_workflow": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´•", "created": "ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šā´¤āĩ", "created_at": "ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šā´¤āĩ", "creating_linked_albums": "ā´Ŧā´¨āĩā´§ā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´š ā´†āĩŊā´Ŧā´™āĩā´™āĩž ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ...", "crop": "ā´•āĩā´°āĩ‹ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "crop_aspect_ratio_free": "ā´¸āĩ—ā´œā´¨āĩā´¯ā´‚", - "crop_aspect_ratio_original": "ā´’ā´ąā´ŋⴜā´ŋā´¨āĩŊ", - "crop_aspect_ratio_square": "ā´¸ā´Žā´šā´¤āĩā´°ā´‚", + "curated_object_page_title": "ā´ĩā´¸āĩā´¤āĩā´•āĩā´•āĩž", "current_device": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´‚", "current_pin_code": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ", "current_server_address": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´¸āĩ†āĩŧā´ĩāĩŧ ā´ĩā´ŋā´˛ā´žā´¸ā´‚", - "custom_date": "ⴇⴎāĩā´Ÿā´žā´¨āĩā´¸āĩƒā´¤ ā´¤āĩ€ā´¯ā´¤ā´ŋ", "custom_locale": "ā´•ā´¸āĩā´ąāĩā´ąā´‚ ā´˛āĩŠā´•āĩā´•āĩ‡āĩŊ", "custom_locale_description": "ā´­ā´žā´ˇā´¯āĩ†ā´¯āĩā´‚ ā´Ēāĩā´°ā´Ļāĩ‡ā´ļā´¤āĩā´¤āĩ†ā´¯āĩā´‚ ā´…ā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´Žā´žā´•āĩā´•ā´ŋ ā´¤āĩ€ā´¯ā´¤ā´ŋā´•ā´ŗāĩā´‚ ā´…ā´•āĩā´•ā´™āĩā´™ā´ŗāĩā´‚ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "cutoff_date_description": "ā´…ā´ĩā´¸ā´žā´¨ā´¤āĩā´¤āĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•â€Ļ", - "cutoff_day": "{count, plural, one {ā´Ļā´ŋā´ĩⴏⴂ} other {ā´Ļā´ŋā´ĩⴏⴙāĩā´™āĩž}}", - "cutoff_year": "{count, plural, one {ā´ĩāĩŧⴎⴂ} other {ā´ĩāĩŧⴎⴙāĩā´™āĩž}}", + "custom_url": "ā´•ā´¸āĩā´ąāĩā´ąā´‚ URL", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "ⴇⴰāĩā´Ŗāĩā´Ÿā´¤āĩ", "dark_theme": "ā´Ąā´žāĩŧā´•āĩā´•āĩ ā´¤āĩ€ā´‚ ⴟāĩ‹ā´—ā´ŋāĩž ⴚāĩ†ā´¯āĩā´¯āĩā´•", "date": "ā´¤āĩ€ā´¯ā´¤ā´ŋ", "date_after": "ⴇⴤā´ŋā´¨āĩ ā´ļāĩ‡ā´ˇā´Žāĩā´ŗāĩā´ŗ ā´¤āĩ€ā´¯ā´¤ā´ŋ", "date_and_time": "ā´¤āĩ€ā´¯ā´¤ā´ŋā´¯āĩā´‚ ā´¸ā´Žā´¯ā´ĩāĩā´‚", "date_before": "ⴇⴤā´ŋā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩā´ŗāĩā´ŗ ā´¤āĩ€ā´¯ā´¤ā´ŋ", - "date_of_birth": "ⴜⴍⴍⴤāĩā´¤āĩ€ā´¯ā´¤ā´ŋ", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "ⴜⴍⴍⴤāĩā´¤āĩ€ā´¯ā´¤ā´ŋ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "date_range": "ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´§ā´ŋ", - "date_time_original": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´¤āĩ€ā´¯ā´¤ā´ŋ/ā´¸ā´Žā´¯ā´‚", "day": "ā´Ļā´ŋā´ĩⴏⴂ", "days": "ā´Ļā´ŋā´ĩⴏⴙāĩā´™āĩž", "deduplicate_all": "ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´•", - "default_quality_subtitle": "ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ ā´Ÿā´žā´Ēāĩā´Ēāĩā´šāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´—āĩā´Ŗā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚. ā´“ā´°āĩ‹ ā´¤ā´ĩā´Ŗā´¯āĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•ā´žāĩģ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ ā´Ŧⴟāĩā´Ÿāĩē ā´Ļāĩ€āĩŧⴘⴍāĩ‡ā´°ā´‚ ā´…ā´Žāĩŧā´¤āĩā´¤āĩā´•.", - "default_share_quality": "ā´Ąā´ŋā´Ģāĩ‹āĩžā´Ÿāĩā´Ÿāĩ ā´ˇāĩ†ā´¯āĩŧ ā´•āĩā´ĩā´žā´ŗā´ŋā´ąāĩā´ąā´ŋ", "delete": "ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "delete_action_confirmation_message": "ⴈ ā´…ā´¸ā´ąāĩā´ąāĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹? ⴈ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ā´…ā´¸ā´ąāĩā´ąā´ŋā´¨āĩ† ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´¨āĩā´ąāĩ† ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´‚, ā´•āĩ‚ā´Ÿā´žā´¤āĩ† ⴇⴤāĩ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗāĩ‹ ā´Žā´¨āĩā´¨āĩ ⴚāĩ‹ā´Ļā´ŋā´•āĩā´•āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´‚", "delete_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", @@ -835,6 +830,8 @@ "delete_dialog_alert": "ⴈ ⴇⴍⴙāĩā´™āĩž Immich-āĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩā´‚ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩā´‚ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", "delete_dialog_alert_local": "ⴈ ⴇⴍⴙāĩā´™āĩž ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚, ā´Ēā´•āĩā´ˇāĩ‡ Immich ā´¸āĩ†āĩŧā´ĩā´ąā´ŋāĩŊ ā´¤āĩā´Ÿāĩŧā´¨āĩā´¨āĩā´‚ ⴞⴭāĩā´¯ā´Žā´žā´•āĩā´‚", "delete_dialog_alert_local_non_backed_up": "ⴚā´ŋā´˛ ⴇⴍⴙāĩā´™āĩž Immich-ā´˛āĩ‡ā´•āĩā´•āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛, ā´…ā´ĩ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", + "delete_dialog_alert_remote": "ⴈ ⴇⴍⴙāĩā´™āĩž Immich ā´¸āĩ†āĩŧā´ĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", + "delete_dialog_ok_force": "ā´Žā´¨āĩā´¤ā´žā´¯ā´žā´˛āĩā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "delete_dialog_title": "ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "delete_duplicates_confirmation": "ⴈ ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•āĩž ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "delete_face": "ā´Žāĩā´–ā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", @@ -853,16 +850,20 @@ "delete_tag_confirmation_prompt": "{tagName} ā´Žā´¨āĩā´¨ ā´Ÿā´žā´—āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "delete_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "deleted_shared_link": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", + "deletes_missing_assets": "ā´Ąā´ŋā´¸āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´•ā´žā´Ŗā´žā´¤ā´žā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", "description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚", + "description_input_hint_text": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•...", + "description_input_submit_error": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ, ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩžā´•āĩā´•āĩ ā´˛āĩ‹ā´—āĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", "deselect_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "details": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž", "direction": "ā´Ļā´ŋā´ļ", - "disable": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•", "disabled": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋⴤⴂ", + "disallow_edits": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•ā´°āĩā´¤āĩ", "discord": "ā´Ąā´ŋā´¸āĩā´•āĩ‹āĩŧā´Ąāĩ", "discover": "ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´•", "discovered_devices": "ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ ā´‰ā´Ēā´•ā´°ā´Ŗā´™āĩā´™āĩž", "dismiss_all_errors": "ā´Žā´˛āĩā´˛ā´ž ā´Ēā´ŋā´ļā´•āĩā´•ā´ŗāĩā´‚ ⴤⴺāĩā´ŗā´ŋā´•āĩā´•ā´ŗā´¯āĩā´•", + "dismiss_error": "ā´Ēā´ŋā´ļā´•āĩ ⴤⴺāĩā´ŗā´ŋā´•āĩā´•ā´ŗā´¯āĩā´•", "display_options": "ā´Ēāĩā´°ā´Ļāĩŧā´ļā´¨ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", "display_order": "ā´Ēāĩā´°ā´Ļāĩŧā´ļā´¨ ā´•āĩā´°ā´Žā´‚", "display_original_photos": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", @@ -871,27 +872,27 @@ "documentation": "ā´Ąāĩ‹ā´•āĩā´¯āĩā´Žāĩ†ā´¨āĩā´ąāĩ‡ā´ˇāĩģ", "done": "ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´¯ā´ŋ", "download": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ", + "download_action_prompt": "{count} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "download_canceled": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´ąā´Ļāĩā´Ļā´žā´•āĩā´•ā´ŋ", "download_complete": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´¯ā´ŋ", "download_enqueue": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´•āĩā´¯āĩ‚ā´ĩā´ŋāĩŊ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", + "download_error": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´Ēā´ŋā´ļā´•āĩ", "download_failed": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "download_finished": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´•ā´´ā´ŋā´žāĩā´žāĩ", "download_include_embedded_motion_videos": "ā´‰āĩžā´šāĩā´šāĩ‡āĩŧā´¤āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž", "download_include_embedded_motion_videos_description": "ⴚⴞā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗā´ŋāĩŊ ā´‰āĩžā´šāĩā´šāĩ‡āĩŧā´¤āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´’ā´°āĩ ā´Ēāĩā´°ā´¤āĩā´¯āĩ‡ā´• ā´Ģā´¯ā´˛ā´žā´¯ā´ŋ ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•", "download_notfound": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "download_original": "ā´’ā´ąā´ŋⴜā´ŋā´¨āĩŊ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "download_paused": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´¤ā´žāĩŊā´•āĩā´•ā´žā´˛ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¨ā´ŋāĩŧā´¤āĩā´¤ā´ŋ", "download_settings": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "download_settings_description": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩā´Žā´žā´¯ā´ŋ ā´Ŧā´¨āĩā´§ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "download_started": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴆⴰⴂⴭā´ŋⴚāĩā´šāĩ", + "download_sucess": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´ĩā´ŋⴜⴝā´ŋⴚāĩā´šāĩ", + "download_sucess_android": "ā´Žāĩ€ā´Ąā´ŋā´¯ DCIM/Immich ā´Žā´¨āĩā´¨ā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "download_waiting_to_retry": "ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´žā´¤āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "downloading": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "downloading_asset_filename": "{filename} ā´Žā´¨āĩā´¨ ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", - "downloading_from_icloud": "iCloud-āĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "downloading_media": "ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", - "drag_to_reorder": "ā´Ēāĩā´¨ā´ƒā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•ā´žāĩģ ā´ĩā´˛ā´ŋⴚāĩā´šā´ŋⴟāĩā´•", "drop_files_to_upload": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ģⴝⴞāĩā´•āĩž ā´Žā´ĩā´ŋⴟāĩ†ā´¯āĩā´‚ ā´Ąāĩā´°āĩ‹ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "duplicate": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ", - "duplicate_workflow": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹", "duplicates": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•āĩž", "duplicates_description": "ā´ā´¤ā´žā´Ŗāĩ ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩ ā´Žā´¨āĩā´¨āĩ ā´¸āĩ‚ā´šā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩ ā´“ā´°āĩ‹ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩā´‚ ā´Ēā´°ā´ŋā´šā´°ā´ŋā´•āĩā´•āĩā´•", "duration": "ā´Ļāĩˆāĩŧⴘāĩā´¯ā´‚", @@ -903,7 +904,9 @@ "edit_date_and_time": "ā´¤āĩ€ā´¯ā´¤ā´ŋā´¯āĩā´‚ ā´¸ā´Žā´¯ā´ĩāĩā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "edit_date_and_time_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´¤āĩ€ā´¯ā´¤ā´ŋā´¯āĩā´‚ ā´¸ā´Žā´¯ā´ĩāĩā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¤āĩ", "edit_date_and_time_by_offset": "ā´“ā´Ģāĩā´¸āĩ†ā´ąāĩā´ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Žā´žā´ąāĩā´ąāĩā´•", + "edit_date_and_time_by_offset_interval": "ā´Ēāĩā´¤ā´ŋā´¯ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´§ā´ŋ: {from}-{to}", "edit_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", + "edit_description_prompt": "ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•:", "edit_exclusion_pattern": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩŊ ā´Ēā´žā´ąāĩā´ąāĩ‡āĩē ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "edit_faces": "ā´Žāĩā´–ā´™āĩā´™āĩž ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "edit_key": "ā´•āĩ€ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", @@ -916,21 +919,9 @@ "edit_tag": "ā´Ÿā´žā´—āĩ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "edit_title": "ā´ļāĩ€āĩŧⴎⴕⴂ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "edit_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", - "edit_workflow": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "editor": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩŧ", - "editor_discard_edits_confirm": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´¨ā´ŋā´°ā´¸ā´ŋā´•āĩā´•āĩā´•", - "editor_discard_edits_prompt": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ⴏⴂⴰⴕāĩā´ˇā´ŋā´•āĩā´•ā´žā´¤āĩā´¤ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ⴉ⴪āĩā´Ÿāĩ. ā´…ā´ĩ ā´‰ā´Ēāĩ‡ā´•āĩā´ˇā´ŋā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¤āĩ€āĩŧⴚāĩā´šā´¯ā´žā´Ŗāĩ‹?", - "editor_discard_edits_title": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´¨ā´ŋā´°ā´¸ā´ŋā´•āĩā´•ā´Ŗāĩ‹?", - "editor_edits_applied_error": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´Ēāĩā´°ā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", - "editor_edits_applied_success": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´•āĩž ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´Ēāĩā´°ā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ", - "editor_flip_horizontal": "ā´¤ā´ŋā´°ā´ļāĩā´šāĩ€ā´¨ā´Žā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´•", - "editor_flip_vertical": "ⴞⴂā´Ŧā´Žā´žā´¯ā´ŋ ā´Ģāĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", - "editor_handle_corner": "{corner, select, top_left {ā´Žāĩā´•ā´ŗā´ŋāĩŊ-ā´‡ā´Ÿā´¤āĩ} top_right {ā´Žāĩā´•ā´ŗā´ŋāĩŊ-ā´ĩⴞⴤāĩ} bottom_left {ā´¤ā´žā´´āĩ†-ā´‡ā´Ÿā´¤āĩ} bottom_right {ā´¤ā´žā´´āĩ†-ā´ĩⴞⴤāĩ} other {ā´’ā´°āĩ}} ā´•āĩ‹āĩŧā´Ŗāĩŧ ā´šā´žāĩģā´Ąā´ŋāĩŊ", - "editor_handle_edge": "{edge, select, top {ā´Žāĩā´•ā´ŗā´ŋāĩŊ} bottom {ā´¤ā´žā´´āĩ†} left {ā´‡ā´Ÿā´¤āĩ} right {ā´ĩⴞⴤāĩ} other {ā´’ā´°āĩ}} ā´Žā´Ąāĩā´œāĩ ā´šā´žāĩģā´Ąā´ŋāĩŊ", - "editor_orientation": "ā´“ā´ąā´ŋā´¯āĩģāĩā´ąāĩ‡ā´ˇāĩģ", - "editor_reset_all_changes": "ā´Žā´žā´ąāĩā´ąā´™āĩā´™āĩž ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•āĩā´•", - "editor_rotate_left": "ā´Žā´¤ā´ŋāĩŧ ⴘⴟā´ŋā´•ā´žā´°ā´Ļā´ŋā´ļā´¯ā´ŋāĩŊ 90° ā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´•", - "editor_rotate_right": "ⴘⴟā´ŋā´•ā´žā´°ā´Ļā´ŋā´ļā´¯ā´ŋāĩŊ 90° ā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´•", + "editor_close_without_save_prompt": "ā´Žā´žā´ąāĩā´ąā´™āĩā´™āĩž ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯ā´ŋā´˛āĩā´˛", + "editor_close_without_save_title": "ā´Žā´Ąā´ŋā´ąāĩā´ąāĩŧ ā´…ā´Ÿā´¯āĩā´•āĩā´•ā´Ŗāĩ‹?", "email": "ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ", "email_notifications": "ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž", "empty_folder": "ⴈ ā´Ģāĩ‹āĩžā´Ąāĩŧ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´Ŗāĩ", @@ -946,15 +937,14 @@ "enter_your_pin_code": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´¨āĩŊā´•āĩā´•", "enter_your_pin_code_subtitle": "ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąāĩŧ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´¨āĩŊā´•āĩā´•", "error": "ā´Ēā´ŋā´ļā´•āĩ", + "error_change_sort_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´¸āĩ‹āĩŧⴟāĩā´Ÿāĩ ā´“āĩŧā´Ąāĩŧ ā´Žā´žā´ąāĩā´ąāĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "error_delete_face": "ā´…ā´¸ā´ąāĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Žāĩā´–ā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", - "error_loading_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", + "error_getting_places": "ā´¸āĩā´Ĩⴞⴙāĩā´™āĩž ⴞⴭā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "error_loading_image": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "error_loading_partners": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´•ā´ŗāĩ† ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ: {error}", - "error_retrieving_asset_information": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩ†ā´Ÿāĩā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "error_saving_image": "ā´Ēā´ŋā´ļā´•āĩ: {error}", "error_tag_face_bounding_box": "ā´Žāĩā´–ā´‚ ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ - ā´Ŧāĩ—ā´Ŗāĩā´Ÿā´ŋā´‚ā´—āĩ ā´Ŧāĩ‹ā´•āĩā´¸āĩ ā´•āĩ‹āĩŧā´Ąā´ŋā´¨āĩ‡ā´ąāĩā´ąāĩā´•āĩž ⴞⴭā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", "error_title": "ā´Ēā´ŋā´ļā´•āĩ - ā´Žā´¨āĩā´¤āĩ‹ ā´•āĩā´´ā´Ēāĩā´Ēā´‚ ⴏⴂⴭā´ĩā´ŋⴚāĩā´šāĩ", - "error_while_navigating": "ā´…ā´¸ā´ąāĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ēā´ŋā´ļā´•āĩ", "errors": { "cannot_navigate_next_asset": "ā´…ā´Ÿāĩā´¤āĩā´¤ ā´…ā´¸ā´ąāĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "cannot_navigate_previous_asset": "ā´Žāĩā´Žāĩā´Ēā´¤āĩā´¤āĩ† ā´…ā´¸ā´ąāĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", @@ -986,12 +976,10 @@ "failed_to_remove_product_key": "ā´Ēāĩā´°āĩŠā´Ąā´•āĩā´ąāĩā´ąāĩ ā´•āĩ€ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_reset_pin_code": "ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_stack_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", - "failed_to_tag_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_unstack_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´…āĩē-ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_update_notification_status": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´¨ā´ŋā´˛ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "incorrect_email_or_password": "ā´¤āĩ†ā´ąāĩā´ąā´žā´¯ ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", "library_folder_already_exists": "ⴈ ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ ā´Ēā´žā´¤āĩā´¤āĩ ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´Ŗāĩā´Ÿāĩ.", - "page_not_found": "ā´Ēāĩ‡ā´œāĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", "paths_validation_failed": "{paths, plural, one {# ā´Ēā´žā´¤āĩā´¤āĩ} other {# ā´Ēā´žā´¤āĩā´¤āĩā´•āĩž}} ā´¸ā´žā´§āĩ‚ā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "profile_picture_transparent_pixels": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩžā´•āĩā´•āĩ ā´¸āĩā´¤ā´žā´°āĩā´¯ā´Žā´žā´¯ ā´Ēā´ŋā´•āĩā´¸ā´˛āĩā´•āĩž ⴉ⴪āĩā´Ÿā´žā´•ā´°āĩā´¤āĩ. ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´¸āĩ‚ā´‚ ā´‡āĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´• ā´•āĩ‚ā´Ÿā´žā´¤āĩ†/ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ⴚā´ŋā´¤āĩā´°ā´‚ ā´¨āĩ€ā´•āĩā´•āĩā´•.", "quota_higher_than_disk_size": "ā´Ąā´ŋā´¸āĩā´•āĩ ā´ĩā´˛āĩā´Ēāĩā´Ēā´¤āĩā´¤āĩ‡ā´•āĩā´•ā´žāĩž ⴉⴝāĩŧā´¨āĩā´¨ ā´•āĩā´ĩā´žā´Ÿāĩā´Ÿ ā´¨ā´ŋā´™āĩā´™āĩž ⴏⴜāĩā´œā´Žā´žā´•āĩā´•ā´ŋā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", @@ -1014,7 +1002,6 @@ "unable_to_complete_oauth_login": "OAuth ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_connect": "ā´Ŧā´¨āĩā´§ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_copy_to_clipboard": "ā´•āĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´Ŧāĩ‹āĩŧā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´¨ā´ŋā´™āĩā´™āĩž https ā´ĩā´´ā´ŋā´¯ā´žā´Ŗāĩ ā´Ēāĩ‡ā´œāĩ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤āĩ†ā´¨āĩā´¨āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´•āĩā´•āĩā´•", - "unable_to_create": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•ā´žā´¨ā´žā´¯ā´ŋā´˛āĩā´˛", "unable_to_create_admin_account": "ā´…ā´Ąāĩā´Žā´ŋāĩģ ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_create_api_key": "ā´Ēāĩā´¤ā´ŋā´¯ API ā´•āĩ€ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_create_library": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", @@ -1025,7 +1012,6 @@ "unable_to_delete_exclusion_pattern": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩŊ ā´Ēā´žā´ąāĩā´ąāĩ‡āĩē ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_delete_shared_link": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_delete_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "unable_to_delete_workflow": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", "unable_to_download_files": "ā´Ģⴝⴞāĩā´•āĩž ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_edit_exclusion_pattern": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩŊ ā´Ēā´žā´ąāĩā´ąāĩ‡āĩē ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_empty_trash": "ⴟāĩā´°ā´žā´ˇāĩ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", @@ -1065,7 +1051,6 @@ "unable_to_scan_library": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_set_feature_photo": "ā´Ģāĩ€ā´šāĩā´šāĩŧ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_set_profile_picture": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ⴚā´ŋā´¤āĩā´°ā´‚ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "unable_to_set_rating": "ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋā´•āĩā´•ā´žā´¨ā´žā´¯ā´ŋā´˛āĩā´˛", "unable_to_submit_job": "ⴜāĩ‹ā´˛ā´ŋ ā´¸ā´Žāĩŧā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_trash_asset": "ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_unlink_account": "ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ ā´…āĩēā´˛ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", @@ -1077,18 +1062,23 @@ "unable_to_update_settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_update_timeline_display_status": "ⴟāĩˆā´‚ā´˛āĩˆāĩģ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´¨ ā´¨ā´ŋā´˛ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "unable_to_update_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", - "unable_to_update_workflow": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨ā´žā´ĩāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", "unable_to_upload_file": "ā´Ģā´¯āĩŊ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛" }, - "errors_text": "ā´Ēā´ŋā´ļā´•āĩā´•āĩž", "exclusion_pattern": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩŊ ā´Ēā´žā´ąāĩā´ąāĩ‡āĩē", "exif": "Exif", "exif_bottom_sheet_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•...", "exif_bottom_sheet_description_error": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", + "exif_bottom_sheet_details": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž", + "exif_bottom_sheet_location": "ā´¸āĩā´Ĩā´žā´¨ā´‚", "exif_bottom_sheet_no_description": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´Žā´ŋā´˛āĩā´˛", + "exif_bottom_sheet_people": "ⴆⴺāĩā´•āĩž", + "exif_bottom_sheet_person_add_person": "ā´Ēāĩ‡ā´°āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "exit_slideshow": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ēāĩā´ąā´¤āĩā´¤āĩā´•ā´Ÿā´•āĩā´•āĩā´•", - "expand": "ā´ĩā´ŋā´•ā´¸ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", "expand_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´ĩā´ŋā´•ā´¸ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", + "experimental_settings_new_asset_list_subtitle": "ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋā´¯ā´ŋā´˛ā´žā´Ŗāĩ", + "experimental_settings_new_asset_list_title": "ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋā´˛āĩā´ŗāĩā´ŗ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´—āĩā´°ā´ŋā´Ąāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", + "experimental_settings_subtitle": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¸āĩā´ĩā´¨āĩā´¤ā´‚ ⴉⴤāĩā´¤ā´°ā´ĩā´žā´Ļā´ŋā´¤āĩā´ĩā´¤āĩā´¤ā´ŋāĩŊ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•!", + "experimental_settings_title": "ā´Ēā´°āĩ€ā´•āĩā´ˇā´Ŗā´žā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤ā´ŋā´˛āĩā´ŗāĩā´ŗā´¤āĩ", "expire_after": "ⴇⴤā´ŋā´¨āĩ ā´ļāĩ‡ā´ˇā´‚ ā´•ā´žā´˛ā´šā´°ā´Ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", "expired": "ā´•ā´žā´˛ā´šā´°ā´Ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "expires_date": "{date}-ā´¨āĩ ā´•ā´žā´˛ā´šā´°ā´Ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", @@ -1098,57 +1088,47 @@ "export_as_json": "JSON ⴆⴝā´ŋ ā´•ā´¯ā´ąāĩā´ąāĩā´Žā´¤ā´ŋ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "export_database": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´•ā´¯ā´ąāĩā´ąāĩā´Žā´¤ā´ŋ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "export_database_description": "SQLite ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´•ā´¯ā´ąāĩā´ąāĩā´Žā´¤ā´ŋ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "exposure_time": "ā´¸ā´Žāĩā´Ēāĩŧā´•āĩā´• ā´¸ā´Žā´¯ā´‚", "extension": "ā´Žā´•āĩā´¸āĩā´ąāĩā´ąāĩģā´ˇāĩģ", "external": "ā´Ŧā´žā´šāĩā´¯ā´‚", "external_libraries": "ā´Ŧā´žā´šāĩā´¯ ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋā´•āĩž", "external_network": "ā´Ŧā´žā´šāĩā´¯ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ", "external_network_sheet_info": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´ĩāĩˆ-ā´Ģāĩˆ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•ā´ŋāĩŊ ā´…ā´˛āĩā´˛ā´žā´¤āĩā´¤ā´Ēāĩā´Ēāĩ‹āĩž, ā´¤ā´žā´´āĩ† ā´¨āĩŊā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´ŗāĩā´ŗ URL-ā´•ā´ŗā´ŋāĩŊ ā´†ā´Ļāĩā´¯ā´‚ ⴞⴭāĩā´¯ā´Žā´žā´•āĩā´¨āĩā´¨ ā´’ā´¨āĩā´¨ā´ŋā´˛āĩ‚ā´Ÿāĩ†, ā´Žāĩā´•ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¤ā´žā´´āĩ‡ā´•āĩā´•āĩā´ŗāĩā´ŗ ā´•āĩā´°ā´Žā´¤āĩā´¤ā´ŋāĩŊ, ā´†ā´Ēāĩā´Ēāĩ ā´¸āĩ†āĩŧā´ĩā´ąāĩā´Žā´žā´¯ā´ŋ ā´Ŧā´¨āĩā´§ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´‚", - "f_number": "ā´Žā´Ģāĩ-ā´¨ā´Žāĩā´Ēāĩŧ", "face_unassigned": "ā´…ā´¸āĩˆāĩģ ⴚāĩ†ā´¯āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", "failed": "ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", - "failed_count": "ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ: {count}", "failed_to_authenticate": "ā´Ēāĩā´°ā´žā´Žā´žā´Ŗāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", - "failed_to_delete_file": "ā´Ģā´¯āĩŊ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_load_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "failed_to_load_folder": "ā´Ģāĩ‹āĩžā´Ąāĩŧ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "favorite": "ā´Ēāĩā´°ā´ŋⴝⴂ", "favorite_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "favorite_or_unfavorite_photo": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´¤ā´žā´•āĩā´•āĩā´• ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´žā´ąāĩā´ąāĩā´•", "favorites": "ā´Ēāĩā´°ā´ŋⴝⴙāĩā´™āĩž", + "favorites_page_no_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", "feature_photo_updated": "ā´Ģāĩ€ā´šāĩā´šāĩŧ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "features": "ā´Ģāĩ€ā´šāĩā´šā´ąāĩā´•āĩž", + "features_in_development": "ā´ĩā´ŋā´•ā´¸ā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩā´•āĩŠā´Ŗāĩā´Ÿā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ€ā´šāĩā´šā´ąāĩā´•āĩž", "features_setting_description": "ā´†ā´Ēāĩā´Ēāĩ ā´Ģāĩ€ā´šāĩā´šā´ąāĩā´•āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "file_name_or_extension": "ā´Ģⴝⴞā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´•āĩā´¸āĩā´ąāĩā´ąāĩģā´ˇāĩģ", - "file_name_text": "ā´Ģⴝⴞā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ", "file_size": "ā´Ģā´¯āĩŊ ā´ĩā´˛ā´ŋā´Ēāĩā´Ēā´‚", "filename": "ā´Ģā´¯āĩŊā´¨ā´žā´Žā´‚", "filetype": "ā´Ģā´¯āĩŊ ⴤⴰⴂ", "filter": "ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "filter_people": "ⴆⴺāĩā´•ā´ŗāĩ† ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "filter_places": "ā´¸āĩā´Ĩⴞⴙāĩā´™āĩž ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "filter_tags": "ā´Ÿā´žā´—āĩā´•āĩž ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "filters": "ā´Ģā´ŋāĩŊⴟāĩā´Ÿā´ąāĩā´•āĩž", + "find_them_fast": "ā´¤ā´ŋⴰⴝⴞā´ŋā´˛āĩ‚ā´Ÿāĩ† ā´Ēāĩ‡ā´°āĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´…ā´ĩā´°āĩ† ā´ĩāĩ‡ā´—ā´¤āĩā´¤ā´ŋāĩŊ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤āĩā´•", "first": "ā´†ā´Ļāĩā´¯ā´‚", "fix_incorrect_match": "ā´¤āĩ†ā´ąāĩā´ąā´žā´¯ ā´ĒāĩŠā´°āĩā´¤āĩā´¤ā´‚ ā´ļā´°ā´ŋā´¯ā´žā´•āĩā´•āĩā´•", - "focal_length": "ā´Ģāĩ‹ā´•āĩā´•āĩŊ ā´˛āĩ†ā´™āĩā´¤āĩ", "folder": "ā´Ģāĩ‹āĩžā´Ąāĩŧ", "folder_not_found": "ā´Ģāĩ‹āĩžā´Ąāĩŧ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", "folders": "ā´Ģāĩ‹āĩžā´Ąā´ąāĩā´•āĩž", "folders_feature_description": "ā´Ģā´¯āĩŊ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´¤āĩā´¤ā´ŋā´˛āĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩžā´•āĩā´•āĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩžā´•āĩā´•āĩā´Žā´žā´¯ā´ŋ ā´Ģāĩ‹āĩžā´Ąāĩŧ ā´•ā´žā´´āĩâ€Œā´š ā´Ŧāĩā´°āĩ—ā´¸āĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "forgot_pin_code_question": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēā´ŋāĩģ ā´Žā´ąā´¨āĩā´¨āĩ‹?", "forward": "ā´Žāĩā´¨āĩā´¨āĩ‹ā´Ÿāĩā´Ÿāĩ", - "free_up_space": "ā´‡ā´Ÿā´‚ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•āĩā´•", - "free_up_space_description": "ā´‡ā´Ÿā´‚ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•ā´žāĩģ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩģāĩā´ąāĩ† ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨āĩ€ā´•āĩā´•āĩā´•. ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ† ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēā´•āĩŧā´Ēāĩā´Ēāĩā´•āĩž ā´¸āĩā´°ā´•āĩā´ˇā´ŋā´¤ā´Žā´žā´¯ā´ŋ ā´¤āĩā´Ÿā´°āĩā´¨āĩā´¨āĩ.", - "free_up_space_settings_subtitle": "ā´‰ā´Ēā´•ā´°ā´Ŗ ⴏⴂⴭⴰ⴪ⴂ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•āĩā´•", "full_path": "ā´Žāĩā´´āĩā´ĩāĩģ ā´Ēā´žā´¤āĩā´¤āĩ: {path}", - "full_path_or_folder": "ā´Žāĩā´´āĩā´ĩāĩģ ā´Ēā´žā´¤ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Ģāĩ‹āĩžā´Ąāĩŧ", "gcast_enabled": "ā´—āĩ‚ā´—ā´ŋāĩž ā´•ā´žā´¸āĩā´ąāĩā´ąāĩ", "gcast_enabled_description": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žā´¯ā´ŋ ⴈ ā´Ģāĩ€ā´šāĩā´šāĩŧ ā´—āĩ‚ā´—ā´ŋā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ŧā´žā´šāĩā´¯ ā´‰ā´ąā´ĩā´ŋā´Ÿā´™āĩā´™āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ.", "general": "ā´ĒāĩŠā´¤āĩā´ĩā´žā´¯ā´¤āĩ", "geolocation_instruction_location": "ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ GPS ā´•āĩ‹āĩŧā´Ąā´ŋā´¨āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´ŗāĩā´ŗ ā´’ā´°āĩ ā´…ā´¸ā´ąāĩā´ąā´ŋāĩŊ ā´•āĩā´˛ā´ŋā´•āĩā´•āĩā´šāĩ†ā´¯āĩā´¯āĩā´•, ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´žā´Ēāĩā´Ēā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´’ā´°āĩ ā´¸āĩā´Ĩⴞⴂ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "get_help": "ā´¸ā´šā´žā´¯ā´‚ ā´¨āĩ‡ā´Ÿāĩā´•", - "get_people_error": "ⴆⴺāĩā´•ā´ŗāĩ† ā´•ā´ŋⴟāĩā´Ÿāĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "get_wifiname_error": "ā´ĩāĩˆ-ā´Ģāĩˆā´¯āĩā´Ÿāĩ† ā´Ēāĩ‡ā´°āĩ ⴞⴭā´ŋⴚāĩā´šā´ŋā´˛āĩā´˛. ā´¨ā´ŋā´™āĩā´™āĩž ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´¯ ā´…ā´¨āĩā´Žā´¤ā´ŋā´•āĩž ā´¨āĩŊā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´¨āĩā´¨āĩā´‚ ā´’ā´°āĩ ā´ĩāĩˆ-ā´Ģāĩˆ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´•ā´Ŗā´•āĩā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩâ€Œā´¤ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´¨āĩā´¨āĩā´‚ ā´‰ā´ąā´Ēāĩā´Ēā´žā´•āĩā´•āĩā´•", "getting_started": "ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "go_back": "ā´Ēā´ŋā´¨āĩā´¨āĩ‹ā´Ÿāĩā´Ÿāĩ ā´Ēāĩ‹ā´•āĩā´•", @@ -1163,6 +1143,7 @@ "group_owner": "ā´‰ā´Ÿā´Ž ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "group_places_by": "ā´¸āĩā´Ĩⴞⴙāĩā´™ā´ŗāĩ† ⴇⴤⴍāĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•...", "group_year": "ā´ĩāĩŧⴎⴂ ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "haptic_feedback_switch": "ā´šā´žā´Ēāĩā´ąāĩā´ąā´ŋā´•āĩ ā´Ģāĩ€ā´Ąāĩâ€Œā´Ŧā´žā´•āĩā´•āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "haptic_feedback_title": "ā´šā´žā´Ēāĩā´ąāĩā´ąā´ŋā´•āĩ ā´Ģāĩ€ā´Ąāĩâ€Œā´Ŧā´žā´•āĩā´•āĩ", "has_quota": "ā´•āĩā´ĩā´žā´Ÿāĩā´Ÿā´¯āĩā´Ŗāĩā´Ÿāĩ", "hash_asset": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´šā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -1173,22 +1154,37 @@ "header_settings_header_name_input": "ā´šāĩ†ā´Ąā´ąā´ŋā´¨āĩā´ąāĩ† ā´Ēāĩ‡ā´°āĩ", "header_settings_header_value_input": "ā´šāĩ†ā´Ąā´ąā´ŋā´¨āĩā´ąāĩ† ā´Žāĩ‚ā´˛āĩā´¯ā´‚", "headers_settings_tile_title": "ā´•ā´¸āĩā´ąāĩā´ąā´‚ ā´Ēāĩā´°āĩ‹ā´•āĩā´¸ā´ŋ ā´šāĩ†ā´Ąā´ąāĩā´•āĩž", - "height": "ⴉⴝⴰⴂ", "hi_user": "ā´¨ā´Žā´¸āĩā´•ā´žā´°ā´‚ {name} ({email})", "hide_all_people": "ā´Žā´˛āĩā´˛ā´ž ⴆⴺāĩā´•ā´ŗāĩ†ā´¯āĩā´‚ ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_gallery": "ā´—ā´žā´˛ā´ąā´ŋ ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_named_person": "{name} ā´Žā´¨āĩā´¨ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_person": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", - "hide_schema": "ā´¸āĩā´•āĩ€ā´Ž ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_text_recognition": "ⴟāĩ†ā´•āĩā´¸āĩā´ąāĩā´ąāĩ ā´¤ā´ŋā´°ā´ŋⴚāĩā´šā´ąā´ŋā´¯āĩŊ ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "hide_unnamed_people": "ā´Ēāĩ‡ā´°ā´ŋā´˛āĩā´˛ā´žā´¤āĩā´¤ ⴆⴺāĩā´•ā´ŗāĩ† ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", + "home_page_add_to_album_conflicts": "{album} ā´Žā´¨āĩā´¨ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ {added} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ. {failed} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴇⴤā´ŋⴍⴕⴂ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩā´Ŗāĩā´Ÿāĩ.", + "home_page_add_to_album_err_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_add_to_album_success": "{album} ā´Žā´¨āĩā´¨ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ {added} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ.", + "home_page_album_err_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´’ā´°āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_archive_err_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_archive_err_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", "home_page_building_timeline": "ⴟāĩˆā´‚ā´˛āĩˆāĩģ ā´¨ā´ŋāĩŧā´Žāĩā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_delete_err_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_delete_remote_err_local": "ā´ąā´ŋā´Žāĩ‹ā´Ÿāĩā´Ÿāĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩŊ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´Ēāĩā´Ēā´ŋāĩŊ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ⴉ⴪āĩā´Ÿāĩ, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_favorite_err_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_favorite_err_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´¤ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_first_time_notice": "ā´¨ā´ŋā´™āĩā´™āĩž ā´†ā´Ļāĩā´¯ā´Žā´žā´¯ā´ŋⴟāĩā´Ÿā´žā´Ŗāĩ ⴈ ā´†ā´Ēāĩā´Ēāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´’ā´°āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´†āĩŊā´Ŧā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´•āĩā´•āĩā´•, ā´…ā´¤āĩā´ĩā´´ā´ŋ ⴟāĩˆā´‚ā´˛āĩˆā´¨ā´ŋāĩŊ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´‚", + "home_page_locked_error_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_locked_error_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_share_err_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´˛ā´ŋā´™āĩā´•āĩ ā´ĩā´´ā´ŋ ā´Ēā´™āĩā´•ā´ŋā´Ÿā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "home_page_upload_err_limit": "ā´’ā´°āĩ ā´¸ā´Žā´¯ā´‚ ā´Ēā´°ā´Žā´žā´ĩā´§ā´ŋ 30 ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩ‚, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", "host": "ā´šāĩ‹ā´¸āĩā´ąāĩā´ąāĩ", "hour": "ā´Žā´Ŗā´ŋā´•āĩā´•āĩ‚āĩŧ", "hours": "ā´Žā´Ŗā´ŋā´•āĩā´•āĩ‚ā´ąāĩā´•āĩž", "id": "ā´ā´Ąā´ŋ", "idle": "ā´¨ā´ŋā´ˇāĩâ€Œā´•āĩā´°ā´ŋⴝⴂ", + "ignore_icloud_photos": "iCloud ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´…ā´ĩā´—ā´Ŗā´ŋā´•āĩā´•āĩā´•", + "ignore_icloud_photos_description": "iCloud-āĩŊ ⴏⴂⴭⴰā´ŋⴚāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž Immich ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´ŋā´˛āĩā´˛", "image": "ⴚā´ŋā´¤āĩā´°ā´‚", "image_alt_text_date": "{date}-ā´¨āĩ ā´Žā´Ÿāĩā´¤āĩā´¤ {isVideo, select, true {ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {ⴚā´ŋā´¤āĩā´°ā´‚}}", "image_alt_text_date_1_person": "{date}-ā´¨āĩ {person1}-ā´¨āĩŠā´Ēāĩā´Ēā´‚ ā´Žā´Ÿāĩā´¤āĩā´¤ {isVideo, select, true {ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {ⴚā´ŋā´¤āĩā´°ā´‚}}", @@ -1200,6 +1196,10 @@ "image_alt_text_date_place_2_people": "{date}-ā´¨āĩ {city}, {country} ā´Žā´¨āĩā´¨ā´ŋā´ĩā´ŋā´Ÿā´™āĩā´™ā´ŗā´ŋāĩŊ ā´ĩāĩ†ā´šāĩā´šāĩ {person1}, {person2} ā´Žā´¨āĩā´¨ā´ŋā´ĩāĩŧā´•āĩā´•āĩŠā´Ēāĩā´Ēā´‚ ā´Žā´Ÿāĩā´¤āĩā´¤ {isVideo, select, true {ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {ⴚā´ŋā´¤āĩā´°ā´‚}}", "image_alt_text_date_place_3_people": "{date}-ā´¨āĩ {city}, {country} ā´Žā´¨āĩā´¨ā´ŋā´ĩā´ŋā´Ÿā´™āĩā´™ā´ŗā´ŋāĩŊ ā´ĩāĩ†ā´šāĩā´šāĩ {person1}, {person2}, {person3} ā´Žā´¨āĩā´¨ā´ŋā´ĩāĩŧā´•āĩā´•āĩŠā´Ēāĩā´Ēā´‚ ā´Žā´Ÿāĩā´¤āĩā´¤ {isVideo, select, true {ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {ⴚā´ŋā´¤āĩā´°ā´‚}}", "image_alt_text_date_place_4_or_more_people": "{date}-ā´¨āĩ {city}, {country} ā´Žā´¨āĩā´¨ā´ŋā´ĩā´ŋā´Ÿā´™āĩā´™ā´ŗā´ŋāĩŊ ā´ĩāĩ†ā´šāĩā´šāĩ {person1}, {person2}, ā´•āĩ‚ā´Ÿā´žā´¤āĩ† ā´Žā´ąāĩā´ąāĩ {additionalCount, number} ā´Ēāĩ‡āĩŧā´•āĩā´•āĩā´ŽāĩŠā´Ēāĩā´Ēā´‚ ā´Žā´Ÿāĩā´¤āĩā´¤ {isVideo, select, true {ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {ⴚā´ŋā´¤āĩā´°ā´‚}}", + "image_saved_successfully": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", + "image_viewer_page_state_provider_download_started": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴆⴰⴂⴭā´ŋⴚāĩā´šāĩ", + "image_viewer_page_state_provider_download_success": "ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´ĩā´ŋⴜⴝā´ŋⴚāĩā´šāĩ", + "image_viewer_page_state_provider_share_error": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "immich_logo": "Immich ā´˛āĩ‹ā´—āĩ‹", "immich_web_interface": "Immich ā´ĩāĩ†ā´Ŧāĩ ⴇⴍāĩā´ąāĩŧā´Ģāĩ‡ā´¸āĩ", "import_from_json": "JSON-āĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´‡ā´Žāĩā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", @@ -1214,7 +1214,6 @@ "individual_share": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´—ā´¤ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ", "individual_shares": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´—ā´¤ ā´Ēā´™āĩā´•ā´ŋⴟⴞāĩā´•āĩž", "info": "ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž", - "integrity_checks": "ā´¸ā´Žā´—āĩā´°ā´¤ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ā´•āĩž", "interval": { "day_at_onepm": "ā´Žā´˛āĩā´˛ā´ž ā´Ļā´ŋā´ĩā´¸ā´ĩāĩā´‚ ā´‰ā´šāĩā´šā´¯āĩā´•āĩā´•āĩ 1 ā´Žā´Ŗā´ŋā´•āĩā´•āĩ", "hours": "{hours, plural, one {ā´“ā´°āĩ‹ ā´Žā´Ŗā´ŋā´•āĩā´•āĩ‚ā´ąā´ŋā´˛āĩā´‚} other {ā´“ā´°āĩ‹ {hours, number} ā´Žā´Ŗā´ŋā´•āĩā´•āĩ‚ā´ąā´ŋā´˛āĩā´‚}}", @@ -1225,19 +1224,17 @@ "invalid_date_format": "ā´…ā´¸ā´žā´§āĩā´ĩā´žā´¯ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąāĩ", "invite_people": "ⴆⴺāĩā´•ā´ŗāĩ† ā´•āĩā´ˇā´Ŗā´ŋā´•āĩā´•āĩā´•", "invite_to_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´•āĩā´ˇā´Ŗā´ŋā´•āĩā´•āĩā´•", - "iso": "ā´ā´Žā´¸āĩā´’", + "ios_debug_info_fetch_ran_at": "ā´Ģāĩ†ā´šāĩā´šāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋⴚāĩā´šā´¤āĩ {dateTime}", + "ios_debug_info_last_sync_at": "ā´…ā´ĩā´¸ā´žā´¨ ā´¸ā´ŋā´™āĩā´•āĩ {dateTime}", + "ios_debug_info_no_processes_queued": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸āĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•āĩā´¯āĩ‚ā´ĩā´ŋāĩŊ ⴇⴞāĩā´˛", + "ios_debug_info_no_sync_yet": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´¸ā´ŋā´™āĩā´•āĩ ⴜāĩ‹ā´˛ā´ŋ ⴇⴤāĩā´ĩā´°āĩ† ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋⴚāĩā´šā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", + "ios_debug_info_processes_queued": "{count, plural, one {{count} ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸āĩ ā´•āĩā´¯āĩ‚ā´ĩā´ŋā´˛āĩā´Ŗāĩā´Ÿāĩ} other {{count} ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸āĩā´•āĩž ā´•āĩā´¯āĩ‚ā´ĩā´ŋā´˛āĩā´Ŗāĩā´Ÿāĩ}}", + "ios_debug_info_processing_ran_at": "ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸ā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋⴚāĩā´šā´¤āĩ {dateTime}", "items_count": "{count, plural, one {# ⴇⴍⴂ} other {# ⴇⴍⴙāĩā´™āĩž}}", "jobs": "ⴜāĩ‹ā´˛ā´ŋā´•āĩž", "keep": "ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "keep_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "keep_albums_count": "{count} {count, plural, one {ā´†āĩŊā´Ŧā´‚} other {ā´†āĩŊā´Ŧā´™āĩā´™āĩž}} ā´¨ā´ŋⴞⴍā´ŋāĩŧā´¤āĩā´¤āĩā´¨āĩā´¨āĩ", "keep_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "keep_description": "ā´‡ā´Ÿā´‚ ā´¸āĩƒā´ˇāĩâ€Œā´Ÿā´ŋā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´Žā´¨āĩā´¤ā´žā´Ŗāĩ ā´¨ā´ŋⴞⴍā´ŋāĩŊā´•āĩā´•āĩ‡ā´Ŗāĩā´Ÿā´¤āĩ†ā´¨āĩā´¨āĩ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•.", - "keep_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "keep_on_device": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•", - "keep_on_device_hint": "ⴈ ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋāĩŊ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•ā´žāĩģ ⴇⴍⴙāĩā´™āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "keep_this_delete_others": "ⴇⴤāĩ ā´¸āĩ‚ā´•āĩā´ˇā´ŋⴚāĩā´šāĩ ā´Žā´ąāĩā´ąāĩā´ŗāĩā´ŗā´ĩ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", - "keeping": "ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ: {items}", "kept_this_deleted_others": "ⴈ ā´…ā´¸ā´ąāĩā´ąāĩ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•āĩā´•ā´¯āĩā´‚ {count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•ā´¯āĩā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "keyboard_shortcuts": "ā´•āĩ€ā´Ŧāĩ‹āĩŧā´Ąāĩ ā´•āĩā´ąāĩā´•āĩā´•āĩā´ĩā´´ā´ŋā´•āĩž", "language": "ā´­ā´žā´ˇ", @@ -1254,35 +1251,31 @@ "leave": "ā´ĩā´ŋⴟāĩā´•", "leave_album": "ā´†āĩŊā´Ŧā´‚ ā´ĩā´ŋⴟāĩā´•", "lens_model": "ā´˛āĩ†āĩģā´¸āĩ ā´Žāĩ‹ā´ĄāĩŊ", - "less": "ā´•āĩā´ąā´ĩāĩ", "let_others_respond": "ā´Žā´ąāĩā´ąāĩā´ŗāĩā´ŗā´ĩā´°āĩ† ā´Ēāĩā´°ā´¤ā´ŋā´•ā´°ā´ŋā´•āĩā´•ā´žāĩģ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•", "level": "ⴤⴞⴂ", "library": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ", "library_add_folder": "ā´Ģāĩ‹āĩžā´Ąāĩŧ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "library_edit_folder": "ā´Ģāĩ‹āĩžā´Ąāĩŧ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "library_options": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", + "library_page_device_albums": "ā´‰ā´Ēā´•ā´°ā´Ŗā´¤āĩā´¤ā´ŋā´˛āĩ† ā´†āĩŊā´Ŧā´™āĩā´™āĩž", + "library_page_new_album": "ā´Ēāĩā´¤ā´ŋā´¯ ā´†āĩŊā´Ŧā´‚", "library_page_sort_asset_count": "ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Žā´Ŗāĩā´Ŗā´‚", "library_page_sort_created": "ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´š ā´¤āĩ€ā´¯ā´¤ā´ŋ", "library_page_sort_last_modified": "ā´…ā´ĩā´¸ā´žā´¨ā´‚ ā´Žā´žā´ąāĩā´ąā´‚ ā´ĩā´°āĩā´¤āĩā´¤ā´ŋⴝⴤāĩ", "library_page_sort_title": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´ļāĩ€āĩŧⴎⴕⴂ", "licenses": "ā´˛āĩˆā´¸āĩģā´¸āĩā´•āĩž", "light": "ā´˛āĩˆā´ąāĩā´ąāĩ", - "light_theme": "ā´˛āĩˆā´ąāĩā´ąāĩ ā´¤āĩ€ā´Žā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´•", "like": "ⴇⴎāĩā´Ÿā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "like_deleted": "ⴇⴎāĩā´Ÿā´‚ ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¤āĩ", - "link": "ā´˛ā´ŋā´™āĩā´•āĩ", "link_motion_video": "ⴚⴞā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´˛ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "link_to_docs": "ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩžā´•āĩā´•āĩ, ā´Ąāĩ‹ā´•āĩā´¯āĩā´Žāĩ†āĩģāĩā´ąāĩ‡ā´ˇāĩģ ā´•ā´žā´Ŗāĩā´•.", "link_to_oauth": "OAuth-ā´˛āĩ‡ā´•āĩā´•āĩ ā´˛ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "linked_oauth_account": "ā´˛ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ OAuth ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ", "list": "ā´˛ā´ŋā´¸āĩā´ąāĩā´ąāĩ", - "live": "ⴤⴤāĩā´¸ā´Žā´¯ā´‚", - "load_more": "ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "loading": "ā´˛āĩ‹ā´Ąā´ŋā´‚ā´—āĩ", "loading_search_results_failed": "ā´¤ā´ŋā´°ā´¯āĩŊ ā´Ģⴞⴙāĩā´™āĩž ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´•ā´‚", "local_asset_cast_failed": "ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´’ā´°āĩ ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´žā´¸āĩā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", "local_assets": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž", - "local_id": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´ā´Ąā´ŋ", "local_media_summary": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´Žāĩ€ā´Ąā´ŋā´¯ ⴏⴂⴗāĩā´°ā´šā´‚", "local_network": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ", "local_network_sheet_info": "ā´¨ā´ŋāĩŧā´Ļāĩā´Ļā´ŋā´ˇāĩâ€Œā´Ÿ ā´ĩāĩˆ-ā´Ģāĩˆ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ⴈ URL ā´ĩā´´ā´ŋ ā´†ā´Ēāĩā´Ēāĩ ā´¸āĩ†āĩŧā´ĩā´ąāĩā´Žā´žā´¯ā´ŋ ā´Ŧā´¨āĩā´§ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´‚", @@ -1305,9 +1298,11 @@ "login": "ā´˛āĩ‹ā´—ā´ŋāĩģ", "login_disabled": "ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ", "login_form_api_exception": "API ā´Žā´•āĩā´¸āĩ†ā´Ēāĩā´ˇāĩģ. ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´¸āĩ†āĩŧā´ĩāĩŧ URL ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋⴚāĩā´šāĩ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´•.", + "login_form_back_button_text": "ā´¤ā´ŋā´°ā´ŋā´•āĩ†", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´Žāĩģā´Ąāĩâ€Œā´Ēāĩ‹ā´¯ā´ŋā´¨āĩā´ąāĩ URL", + "login_form_err_http": "ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ http:// ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ https:// ā´Žā´¨āĩā´¨āĩ ā´ĩāĩā´¯ā´•āĩā´¤ā´Žā´žā´•āĩā´•āĩā´•", "login_form_err_invalid_email": "ā´…ā´¸ā´žā´§āĩā´ĩā´žā´¯ ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ", "login_form_err_invalid_url": "ā´…ā´¸ā´žā´§āĩā´ĩā´žā´¯ URL", "login_form_err_leading_whitespace": "ā´¤āĩā´Ÿā´•āĩā´•ā´¤āĩā´¤ā´ŋā´˛āĩ† ā´ļāĩ‚ā´¨āĩā´¯ā´¸āĩā´Ĩⴞⴂ", @@ -1317,6 +1312,7 @@ "login_form_failed_login": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩ† ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ, ā´¸āĩ†āĩŧā´ĩāĩŧ URL, ā´‡ā´Žāĩ†ā´¯ā´ŋāĩŊ, ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´Žā´¨āĩā´¨ā´ŋā´ĩ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´ŋā´•āĩā´•āĩā´•", "login_form_handshake_exception": "ā´¸āĩ†āĩŧā´ĩā´ąāĩā´Žā´žā´¯ā´ŋ ā´’ā´°āĩ ā´šā´žāĩģā´Ąāĩâ€Œā´ˇāĩ‡ā´•āĩā´•āĩ ā´Žā´•āĩā´¸āĩ†ā´Ēāĩā´ˇāĩģ ⴉ⴪āĩā´Ÿā´žā´¯ā´ŋ. ā´¨ā´ŋā´™āĩā´™āĩž ā´’ā´°āĩ ā´¸āĩā´ĩⴝⴂ ā´’ā´Ēāĩā´Ēā´ŋⴟāĩā´Ÿ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąā´žā´Ŗāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´¸āĩā´ĩⴝⴂ ā´’ā´Ēāĩā´Ēā´ŋⴟāĩā´Ÿ ā´¸āĩŧⴟāĩā´Ÿā´ŋā´Ģā´ŋā´•āĩā´•ā´ąāĩā´ąāĩ ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•.", "login_form_password_hint": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", + "login_form_save_login": "ā´˛āĩ‹ā´—ā´ŋāĩģ ⴆⴝā´ŋ ā´¤āĩā´Ÿā´°āĩā´•", "login_form_server_empty": "ā´’ā´°āĩ ā´¸āĩ†āĩŧā´ĩāĩŧ URL ā´¨āĩŊā´•āĩā´•.", "login_form_server_error": "ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´•ā´Ŗā´•āĩā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´žāĩā´žā´ŋā´˛āĩā´˛.", "login_has_been_disabled": "ā´˛āĩ‹ā´—ā´ŋāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ.", @@ -1331,27 +1327,10 @@ "loop_videos_description": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļ ā´ĩāĩā´¯āĩ‚ā´ĩā´ąā´ŋāĩŊ ā´’ā´°āĩ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´˛āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•.", "main_branch_warning": "ā´¨ā´ŋā´™āĩā´™āĩž ā´’ā´°āĩ ā´Ąāĩ†ā´ĩā´˛ā´Ēāĩâ€Œā´Žāĩ†ā´¨āĩā´ąāĩ ā´Ēā´¤ā´ŋā´Ēāĩā´Ēā´žā´Ŗāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ; ā´’ā´°āĩ ā´ąā´ŋā´˛āĩ€ā´¸āĩ ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•ā´žāĩģ ā´žā´™āĩā´™āĩž ā´ļā´•āĩā´¤ā´Žā´žā´¯ā´ŋ ā´ļāĩā´Ēā´žāĩŧā´ļ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ!", "main_menu": "ā´Ēāĩā´°ā´§ā´žā´¨ ā´Žāĩ†ā´¨āĩ", - "maintenance_action_restore": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "maintenance_description": "ā´‡ā´Žāĩā´Žā´ŋā´•āĩā´•āĩ ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ.", "maintenance_end": "ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąāĩ ā´…ā´ĩā´¸ā´žā´¨ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", + "maintenance_end_error": "ā´Žāĩ†ā´¯ā´ŋā´¨āĩā´ąā´¨āĩģā´¸āĩ ā´Žāĩ‹ā´Ąāĩ ā´…ā´ĩā´¸ā´žā´¨ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ.", "maintenance_logged_in_as": "ā´¨ā´ŋā´˛ā´ĩā´ŋāĩŊ {user} ⴆⴝā´ŋ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "maintenance_restore_from_backup": "ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", - "maintenance_restore_library": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", - "maintenance_restore_library_confirm": "ⴇⴤāĩ ā´ļā´°ā´ŋā´¯ā´žā´Ŗāĩ†ā´¨āĩā´¨āĩ ā´¤āĩ‹ā´¨āĩā´¨āĩā´¨āĩā´¨āĩā´ĩāĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´’ā´°āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ ā´¤āĩā´Ÿā´°āĩā´•!", - "maintenance_restore_library_description": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "maintenance_restore_library_folder_has_files": "{folder} ā´Žā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ {count} ā´Ģāĩ‹āĩžā´Ąāĩŧ(ā´•āĩž) ⴉ⴪āĩā´Ÿāĩ", - "maintenance_restore_library_folder_no_files": "{folder} ā´Ģⴝⴞāĩā´•āĩž ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛!", - "maintenance_restore_library_folder_pass": "ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žā´ĩāĩā´¨āĩā´¨ā´¤āĩā´‚ ā´Žā´´āĩā´¤ā´žā´ĩāĩā´¨āĩā´¨ā´¤āĩā´Žā´žā´Ŗāĩ", - "maintenance_restore_library_folder_read_fail": "ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žā´¨ā´žā´ĩāĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", - "maintenance_restore_library_folder_write_fail": "ā´Žā´´āĩā´¤ā´žāĩģ ā´Ēā´ąāĩā´ąā´ŋā´˛āĩā´˛", - "maintenance_restore_library_hint_missing_files": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Ēāĩā´°ā´§ā´žā´¨ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿ ā´Ģⴝⴞāĩā´•āĩž ⴍⴎāĩâ€Œā´Ÿā´Žā´žā´¯āĩ‡ā´•āĩā´•ā´žā´‚", - "maintenance_restore_library_hint_regenerate_later": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‡ā´ĩ ā´Ēā´ŋā´¨āĩā´¨āĩ€ā´Ÿāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Ēāĩā´¨ā´ƒā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•ā´žā´‚", - "maintenance_restore_library_hint_storage_template_missing_files": "ā´¸āĩā´ąāĩā´ąāĩ‹ā´ąāĩ‡ā´œāĩ ⴟāĩ†ā´‚ā´Ēāĩā´˛āĩ‡ā´ąāĩā´ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩā´Ŗāĩā´Ÿāĩ‹? ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Ģⴝⴞāĩā´•āĩž ⴍⴎāĩâ€Œā´Ÿā´Žā´žā´¯āĩ‡ā´•āĩā´•ā´žā´‚", - "maintenance_restore_library_loading": "ā´¸ā´Žā´—āĩā´°ā´¤ ā´Ēā´°ā´ŋā´ļāĩ‹ā´§ā´¨ā´•ā´ŗāĩā´‚ ā´šāĩā´¯āĩ‚ā´ąā´ŋā´¸āĩā´ąāĩā´ąā´ŋā´•āĩā´¸āĩā´‚ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩâ€Ļ", - "maintenance_task_backup": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗ ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸ā´ŋāĩģāĩā´ąāĩ† ā´’ā´°āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", - "maintenance_task_migrations": "ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´Žāĩˆā´—āĩā´°āĩ‡ā´ˇā´¨āĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", - "maintenance_task_restore": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩâ€Ļ", - "maintenance_task_rollback": "ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ, ā´Ēāĩ‹ā´¯ā´ŋāĩģāĩā´ąāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´ŋā´•āĩ† ā´Ēāĩ‹ā´•āĩā´¨āĩā´¨āĩâ€Ļ", "maintenance_title": "ā´¤ā´žāĩŊā´•āĩā´•ā´žā´˛ā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", "make": "ā´¨ā´ŋāĩŧā´Žāĩā´Žā´žā´¤ā´žā´ĩāĩ", "manage_geolocation": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -1359,6 +1338,7 @@ "manage_media_access_settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´¤āĩā´ąā´•āĩā´•āĩā´•", "manage_media_access_subtitle": "ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Ģⴝⴞāĩā´•āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩā´‚ ā´Žā´žā´ąāĩā´ąāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩā´‚ ā´‡ā´Žāĩā´Žā´ŋā´•āĩā´•āĩ ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩ† ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•.", "manage_media_access_title": "ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Žā´žā´¨āĩ‡ā´œāĩā´Žāĩ†ā´¨āĩā´ąāĩ ⴆⴕāĩā´¸ā´¸āĩ", + "manage_shared_links": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "manage_sharing_with_partners": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´•ā´ŗāĩā´Žā´žā´¯āĩā´ŗāĩā´ŗ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "manage_the_app_settings": "ā´†ā´Ēāĩā´Ēāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "manage_your_account": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -1366,11 +1346,13 @@ "manage_your_devices": "ā´¨ā´ŋā´™āĩā´™āĩž ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¤ ā´‰ā´Ēā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "manage_your_oauth_connection": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† OAuth ā´•ā´Ŗā´•āĩā´ˇāĩģ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "map": "ā´Žā´žā´Ēāĩā´Ēāĩ", + "map_assets_in_bounds": "{count, plural, =0 {ⴈ ā´Ēāĩā´°ā´Ļāĩ‡ā´ļā´¤āĩā´¤āĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛} one {# ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹} other {# ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž}}", "map_cannot_get_user_location": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩā´ąāĩ† ā´¸āĩā´Ĩā´žā´¨ā´‚ ⴞⴭā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", + "map_location_dialog_yes": "ā´…ā´¤āĩ†", "map_location_picker_page_use_location": "ⴈ ā´¸āĩā´Ĩⴞⴂ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "map_location_service_disabled_content": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤āĩ ā´¨ā´ŋā´¨āĩā´¨āĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´¸āĩ‡ā´ĩⴍⴂ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩ‡ā´Ŗāĩā´Ÿā´¤āĩā´Ŗāĩā´Ÿāĩ. ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´Ŗāĩ‹?", "map_location_service_disabled_title": "ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´¸āĩ‡ā´ĩⴍⴂ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ", - "map_marker_for_image": "{city}, {country} ā´Žā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Žā´Ÿāĩā´¤āĩā´¤ ⴚā´ŋā´¤āĩā´°ā´¤āĩā´¤ā´ŋā´¨ā´žā´¯āĩā´ŗāĩā´ŗ ā´Žā´žā´Ēāĩā´Ēāĩ ā´Žā´žāĩŧā´•āĩā´•āĩŧ", + "map_marker_for_images": "{city}, {country} ā´Žā´¨āĩā´¨ā´ŋā´ĩā´ŋā´Ÿā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Žā´Ÿāĩā´¤āĩā´¤ ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩžā´•āĩā´•āĩā´ŗāĩā´ŗ ā´Žā´žā´Ēāĩā´Ēāĩ ā´Žā´žāĩŧā´•āĩā´•āĩŧ", "map_marker_with_image": "ⴚā´ŋā´¤āĩā´°ā´¤āĩā´¤āĩ‹ā´Ÿāĩā´•āĩ‚ā´Ÿā´ŋā´¯ ā´Žā´žā´Ēāĩā´Ēāĩ ā´Žā´žāĩŧā´•āĩā´•āĩŧ", "map_no_location_permission_content": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´¸āĩā´Ĩā´žā´¨ā´¤āĩā´¤āĩ ā´¨ā´ŋā´¨āĩā´¨āĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ. ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•ā´Ŗāĩ‹?", "map_no_location_permission_title": "ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´¨ā´ŋā´ˇāĩ‡ā´§ā´ŋⴚāĩā´šāĩ", @@ -1380,46 +1362,17 @@ "map_settings_date_range_option_days": "ā´•ā´´ā´ŋā´žāĩā´ž {days} ā´Ļā´ŋā´ĩⴏⴙāĩā´™āĩž", "map_settings_date_range_option_year": "ā´•ā´´ā´ŋā´žāĩā´ž ā´ĩāĩŧⴎⴂ", "map_settings_date_range_option_years": "ā´•ā´´ā´ŋā´žāĩā´ž {years} ā´ĩāĩŧⴎⴙāĩā´™āĩž", + "map_settings_dialog_title": "ā´Žā´žā´Ēāĩā´Ēāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "map_settings_include_show_archived": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩā´šāĩ†ā´¯āĩâ€Œā´¤ā´ĩ ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•", "map_settings_include_show_partners": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´•ā´ŗāĩ† ā´‰āĩžā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•", "map_settings_only_show_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩ ā´Žā´žā´¤āĩā´°ā´‚ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "map_settings_theme_settings": "ā´Žā´žā´Ēāĩā´Ēāĩ ā´¤āĩ€ā´‚", + "map_zoom_to_see_photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´•ā´žā´Ŗā´žāĩģ ā´¸āĩ‚ā´‚ ā´”ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "mark_all_as_read": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´ĩā´žā´¯ā´ŋⴚāĩā´šā´¤ā´žā´¯ā´ŋ ā´…ā´Ÿā´¯ā´žā´ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•", + "mark_as_read": "ā´ĩā´žā´¯ā´ŋⴚāĩā´šā´¤ā´žā´¯ā´ŋ ā´…ā´Ÿā´¯ā´žā´ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤āĩā´•", "marked_all_as_read": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´ĩā´žā´¯ā´ŋⴚāĩā´šā´¤ā´žā´¯ā´ŋ ā´…ā´Ÿā´¯ā´žā´ŗā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ŋ", "matches": "ⴚāĩ‡āĩŧⴚāĩā´šā´•āĩž", "matching_assets": "ⴚāĩ‡āĩŧⴚāĩā´šā´¯āĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž", - "media_chrome": { - "auto": "ā´“ā´Ÿāĩā´Ÿāĩ‹", - "captions": "ā´…ā´Ÿā´ŋā´•āĩā´•āĩā´ąā´ŋā´Ēāĩā´Ēāĩā´•āĩž", - "captions_off": "ā´“ā´Ģāĩ", - "closed_captions": "ā´…ā´Ÿā´žāĩā´ž ā´…ā´Ÿā´ŋā´•āĩā´•āĩā´ąā´ŋā´Ēāĩā´Ēāĩā´•āĩž", - "decode_error": "ā´Ąāĩ€ā´•āĩ‹ā´Ąāĩ ā´Ēā´ŋā´ļā´•āĩ", - "disable_captions": "ā´…ā´Ÿā´ŋā´•āĩā´•āĩā´ąā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•", - "enable_captions": "ā´…ā´Ÿā´ŋā´•āĩā´•āĩā´ąā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "enter_fullscreen_mode": "ā´Ģāĩāĩžā´¸āĩā´•āĩā´°āĩ€āĩģ ā´Žāĩ‹ā´Ąāĩ ā´¨āĩŊā´•āĩā´•", - "exit_fullscreen_mode": "ā´Ēāĩ‚āĩŧā´Ŗāĩā´Ŗā´¸āĩâ€Œā´•āĩā´°āĩ€āĩģ ā´Žāĩ‹ā´Ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ēāĩā´ąā´¤āĩā´¤āĩā´•ā´Ÿā´•āĩā´•āĩā´•", - "loop": "ā´˛āĩ‚ā´Ēāĩā´Ēāĩ", - "media_error_description": "ā´’ā´°āĩ ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Ēā´ŋā´ļā´•āĩ ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´¨ā´ŋāĩŧā´¤āĩā´¤ā´˛ā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´•ā´žā´°ā´Ŗā´Žā´žā´¯ā´ŋ. ā´Žāĩ€ā´Ąā´ŋā´¯ ā´•āĩ‡ā´Ÿā´žā´¯āĩ‡ā´•āĩā´•ā´žā´‚ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ŧāĩā´°āĩ—ā´¸āĩŧ ⴈ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąā´ŋā´¨āĩ† ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛.", - "media_loading": "ā´Žāĩ€ā´Ąā´ŋā´¯ ā´˛āĩ‹ā´Ąā´ŋā´‚ā´—āĩ", - "mute": "ā´¨ā´ŋā´ļā´Ŧāĩā´Ļā´Žā´žā´•āĩā´•āĩā´•", - "network_error": "ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ ā´Ēā´ŋā´ļā´•āĩ", - "network_error_description": "ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ ā´Ēā´ŋā´ļā´•āĩ ā´Žāĩ€ā´Ąā´ŋā´¯ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿā´žāĩģ ā´•ā´žā´°ā´Ŗā´Žā´žā´¯ā´ŋ.", - "not_supported_error": "ā´‰ā´ąā´ĩā´ŋā´Ÿā´‚ ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", - "playback_rate": "ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´¨ā´ŋā´°ā´•āĩā´•āĩ", - "playback_rate_current": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´¨ā´ŋā´°ā´•āĩā´•āĩ", - "playback_rate_value": "ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´¨ā´ŋā´°ā´•āĩā´•āĩ {playbackRate}", - "playback_time": "ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ā´¸ā´Žā´¯ā´‚", - "quality": "ā´—āĩā´Ŗā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚", - "second": "ā´°ā´Ŗāĩā´Ÿā´žā´Žā´¤āĩā´¤āĩ‡ā´¤āĩ", - "seconds": "ā´¸āĩ†ā´•āĩā´•āĩģāĩā´ąāĩā´•āĩž", - "time_value_of_total_time": "{currentTime} / {totalTime}", - "time_value_remaining": "{time} ā´ļāĩ‡ā´ˇā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "unmute": "ā´…āĩēā´Žāĩā´¯āĩ‚ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "unsupported_error_description": "ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩâ€Œā´•āĩā´•ā´žā´¤āĩā´¤ ā´’ā´°āĩ ā´Ēā´ŋā´ļā´•āĩ ⴏⴂⴭā´ĩā´ŋⴚāĩā´šāĩ. ā´¸āĩ†āĩŧā´ĩā´ąāĩ‹ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ‹ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ, ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ŧāĩā´°āĩ—ā´¸āĩŧ ⴈ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąā´ŋā´¨āĩ† ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛.", - "video_not_loaded_unknown_time": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛, ā´…ā´ąā´ŋā´¯ā´žā´¤āĩā´¤ ā´¸ā´Žā´¯ā´‚.", - "video_player": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´Ēāĩā´˛āĩ†ā´¯āĩŧ", - "volume": "ā´ĩāĩ‹ā´ŗā´ŋⴝⴂ" - }, "media_type": "ā´Žāĩ€ā´Ąā´ŋā´¯ ⴤⴰⴂ", "memories": "ā´“āĩŧā´Žāĩā´Žā´•āĩž", "memories_all_caught_up": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•ā´Ŗāĩā´Ÿāĩā´¤āĩ€āĩŧā´¤āĩā´¤āĩ", @@ -1436,8 +1389,6 @@ "merge_people_prompt": "ⴈ ⴆⴺāĩā´•ā´ŗāĩ† ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´Ŗāĩ‹? ⴈ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ā´Ēā´´ā´¯ā´Ēⴟā´ŋā´¯ā´žā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛.", "merge_people_successfully": "ⴆⴺāĩā´•ā´ŗāĩ† ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩ", "merged_people_count": "{count, plural, one {# ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩ} other {# ⴆⴺāĩā´•ā´ŗāĩ† ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩ}}", - "minFaces": "ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´•āĩā´ąā´žāĩā´ž ā´Žāĩā´–ā´™āĩā´™āĩž", - "minFaces_description": "ā´’ā´°āĩ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´•āĩā´•āĩ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•ā´žā´¨āĩā´ŗāĩā´ŗ ā´…ā´‚ā´—āĩ€ā´•āĩƒā´¤ ā´Žāĩā´–ā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´•āĩā´ąā´žāĩā´ž ā´Žā´Ŗāĩā´Ŗā´‚", "minimize": "ⴚāĩ†ā´ąāĩā´¤ā´žā´•āĩā´•āĩā´•", "minute": "ā´Žā´ŋā´¨ā´ŋā´ąāĩā´ąāĩ", "minutes": "ā´Žā´ŋā´¨ā´ŋā´ąāĩā´ąāĩā´•āĩž", @@ -1445,25 +1396,24 @@ "mobile_app": "ā´ŽāĩŠā´ŦāĩˆāĩŊ ā´†ā´Ēāĩā´Ēāĩ", "mobile_app_download_onboarding_note": "ā´¤ā´žā´´āĩ† ā´Ēā´ąā´¯āĩā´¨āĩā´¨ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´ŽāĩŠā´ŦāĩˆāĩŊ ā´†ā´Ēāĩā´Ēāĩ ā´Ąāĩ—āĩēā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "model": "ā´Žāĩ‹ā´ĄāĩŊ", - "modify_date": "ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´ˇāĩā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "month": "ā´Žā´žā´¸ā´‚", + "monthly_title_text_date_format": "MMMM y", "more": "ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ", - "motion": "ā´šā´˛ā´¨ā´‚", "move": "ā´¨āĩ€ā´•āĩā´•āĩā´•", "move_off_locked_folder": "ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "move_to": "ⴇⴤā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", - "move_to_device_trash": "ā´‰ā´Ēā´•ā´°ā´Ŗ ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨āĩ€ā´•āĩā´•āĩā´•", "move_to_lock_folder_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´¤āĩā´¤āĩ", "move_to_locked_folder": "ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "move_to_locked_folder_confirmation": "ⴈ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´¯āĩā´‚ ā´Žā´˛āĩā´˛ā´ž ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩā´‚ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚, ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Žā´žā´¤āĩā´°ā´Žāĩ‡ ā´•ā´žā´Ŗā´žāĩģ ā´•ā´´ā´ŋā´¯āĩ‚", + "moved_to_archive": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´†āĩŧā´•āĩā´•āĩˆā´ĩā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋ", + "moved_to_library": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋ", "moved_to_trash": "ⴟāĩā´°ā´žā´ˇā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąā´ŋ", + "multiselect_grid_edit_date_time_err_read_only": "ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žāĩģ ā´Žā´žā´¤āĩā´°ā´Žāĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąā´ŋ(ā´•ā´ŗāĩ)ⴟāĩ† ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "multiselect_grid_edit_gps_err_read_only": "ā´ĩā´žā´¯ā´ŋā´•āĩā´•ā´žāĩģ ā´Žā´žā´¤āĩā´°ā´Žāĩā´ŗāĩā´ŗ ā´…ā´¸ā´ąāĩā´ąā´ŋ(ā´•ā´ŗāĩ)ⴟāĩ† ā´¸āĩā´Ĩā´žā´¨ā´‚ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ", "mute_memories": "ā´“āĩŧā´Žāĩā´Žā´•āĩž ā´¨ā´ŋā´ļāĩā´ļā´Ŧāĩā´Ļā´Žā´žā´•āĩā´•āĩā´•", "my_albums": "ā´Žā´¨āĩā´ąāĩ† ā´†āĩŊā´Ŧā´™āĩā´™āĩž", - "my_immich_description": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´Ēāĩ‡ā´œāĩ My Immich ā´˛ā´ŋā´™āĩā´•ā´žā´¯ā´ŋ ā´Ēā´•āĩŧā´¤āĩā´¤āĩā´•", - "my_immich_title": "ā´Žāĩģāĩā´ąāĩ† ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´˛ā´ŋā´™āĩā´•āĩ", "name": "ā´Ēāĩ‡ā´°āĩ", "name_or_nickname": "ā´Ēāĩ‡ā´°āĩ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´ĩā´ŋā´ŗā´ŋā´Ēāĩā´Ēāĩ‡ā´°āĩ", - "name_required": "ā´Ēāĩ‡ā´°āĩ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ", "navigate": "ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "navigate_to_time": "ā´¸ā´Žā´¯ā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "network_requirement_photos_upload": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´¸āĩ†ā´˛āĩā´˛āĩā´˛ā´žāĩŧ ā´Ąā´žā´ąāĩā´ą ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", @@ -1475,11 +1425,12 @@ "never": "ā´’ā´°ā´ŋā´•āĩā´•ā´˛āĩā´‚ ⴇⴞāĩā´˛", "new_album": "ā´Ēāĩā´¤ā´ŋā´¯ ā´†āĩŊā´Ŧā´‚", "new_api_key": "ā´Ēāĩā´¤ā´ŋā´¯ API ā´•āĩ€", - "new_feature": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Ģāĩ€ā´šāĩā´šāĩŧ", + "new_date_range": "ā´Ēāĩā´¤ā´ŋā´¯ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´§ā´ŋ", "new_password": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ", "new_person": "ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋ", "new_pin_code": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ", "new_pin_code_subtitle": "ā´¨ā´ŋā´™āĩā´™āĩž ā´†ā´Ļāĩā´¯ā´Žā´žā´¯ā´ŋⴟāĩā´Ÿā´žā´Ŗāĩ ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹āĩžā´Ąāĩŧ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤āĩ. ⴈ ā´Ēāĩ‡ā´œāĩ ā´¸āĩā´°ā´•āĩā´ˇā´ŋā´¤ā´Žā´žā´¯ā´ŋ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´’ā´°āĩ ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", + "new_timeline": "ā´Ēāĩā´¤ā´ŋā´¯ ⴟāĩˆā´‚ā´˛āĩˆāĩģ", "new_update": "ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´Ēāĩâ€Œā´Ąāĩ‡ā´ąāĩā´ąāĩ", "new_user_created": "ā´Ēāĩā´¤ā´ŋā´¯ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´ŋ", "new_version_available": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ ⴞⴭāĩā´¯ā´Žā´žā´Ŗāĩ", @@ -1487,7 +1438,6 @@ "next": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ", "next_memory": "ā´…ā´Ÿāĩā´¤āĩā´¤ ā´“āĩŧā´Žāĩā´Ž", "no": "ⴇⴞāĩā´˛", - "no_albums_found": "ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", "no_albums_message": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•ā´žāĩģ ā´’ā´°āĩ ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "no_albums_with_name_yet": "ⴈ ā´Ēāĩ‡ā´°ā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ⴇⴤāĩā´ĩā´°āĩ† ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ⴇⴞāĩā´˛āĩ†ā´¨āĩā´¨āĩ ā´¤āĩ‹ā´¨āĩā´¨āĩā´¨āĩā´¨āĩ.", "no_albums_yet": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ⴇⴤāĩā´ĩā´°āĩ† ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ⴇⴞāĩā´˛āĩ†ā´¨āĩā´¨āĩ ā´¤āĩ‹ā´¨āĩā´¨āĩā´¨āĩā´¨āĩ.", @@ -1504,6 +1454,7 @@ "no_favorites_message": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žā´ŋā´•ā´šāĩā´š ⴚā´ŋā´¤āĩā´°ā´™āĩā´™ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ‡ā´—ā´¤āĩā´¤ā´ŋāĩŊ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´žāĩģ ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", "no_libraries_message": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´’ā´°āĩ ā´Ŧā´žā´šāĩā´¯ ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", "no_local_assets_found": "ⴈ ⴚāĩ†ā´•āĩā´•āĩā´¸ā´‚ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛", + "no_location_set": "ā´¸āĩā´Ĩⴞⴂ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋⴚāĩā´šā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", "no_locked_photos_message": "ā´˛āĩ‹ā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ ā´Ģāĩ‹āĩžā´Ąā´ąā´ŋā´˛āĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Žā´ąā´šāĩā´šā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´¨ā´ŋā´™āĩā´™āĩž ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´Ŧāĩā´°āĩ—ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹ā´´āĩ‹ ā´¤ā´ŋā´°ā´¯āĩā´Žāĩā´Ēāĩ‹ā´´āĩ‹ ā´…ā´ĩ ā´Ļāĩƒā´ļāĩā´¯ā´Žā´žā´•ā´ŋā´˛āĩā´˛.", "no_name": "ā´Ēāĩ‡ā´°ā´ŋā´˛āĩā´˛", "no_notifications": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", @@ -1513,18 +1464,13 @@ "no_results": "ā´Ģⴞⴙāĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", "no_results_description": "ā´’ā´°āĩ ā´Ēā´°āĩā´¯ā´žā´¯ā´Ēā´Ļā´Žāĩ‹ ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´ĒāĩŠā´¤āĩā´ĩā´žā´¯ ā´•āĩ€ā´ĩāĩ‡ā´Ąāĩ‹ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´•", "no_shared_albums_message": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•ā´ŋā´˛āĩ† ⴆⴺāĩā´•ā´ŗāĩā´Žā´žā´¯ā´ŋ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Ēā´™āĩā´•ā´ŋā´Ÿā´žāĩģ ā´’ā´°āĩ ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•", - "no_steps": "ⴇⴤāĩā´ĩā´°āĩ† ⴘⴟāĩā´Ÿā´™āĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ⴚāĩ‡āĩŧā´¤āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", - "none": "ā´’ā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", + "no_uploads_in_progress": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´Ēāĩā´°āĩ‹ā´—ā´Žā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛", "not_allowed": "ā´…ā´¨āĩā´ĩā´Ļā´¨āĩ€ā´¯ā´Žā´˛āĩā´˛", "not_available": "ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", "not_in_any_album": "ā´’ā´°āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩā´Žā´ŋā´˛āĩā´˛", "not_selected": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", "notes": "ā´•āĩā´ąā´ŋā´Ēāĩā´Ēāĩā´•āĩž", "nothing_here_yet": "ā´‡ā´ĩā´ŋⴟāĩ† ⴇⴤāĩā´ĩā´°āĩ† ā´’ā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", - "notification_backup_reliability": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´ĩā´ŋā´ļāĩā´ĩā´žā´¸āĩā´¯ā´¤ ā´Žāĩ†ā´šāĩā´šā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´¤āĩā´¤ā´žāĩģ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", - "notification_enabled_list_tile_content": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋā´¨ā´žā´¯ā´ŋ ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´‰ā´Ēā´•ā´°ā´Ŗ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´…ā´ĩ ā´¨ā´ŋⴝⴍāĩā´¤āĩā´°ā´ŋā´•āĩā´•āĩā´•.", - "notification_enabled_list_tile_open_button": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´¤āĩā´ąā´•āĩā´•āĩā´•", - "notification_enabled_list_tile_title": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋ", "notification_permission_dialog_content": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´žāĩģ, ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´¯ā´ŋ 'ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´•' ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•.", "notification_permission_list_tile_content": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´žāĩģ ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´¨āĩŊā´•āĩā´•.", "notification_permission_list_tile_enable_button": "ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩā´•āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", @@ -1536,8 +1482,6 @@ "obtainium_configurator": "ā´’ā´Ŧāĩâ€Œā´ąāĩā´ąāĩˆā´¨ā´ŋⴝⴂ ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ąāĩā´ąāĩŧ", "obtainium_configurator_instructions": "ā´‡ā´Žāĩā´Žā´ŋā´•āĩā´•āĩ ā´—ā´ŋā´ąāĩā´ąāĩā´šā´Ŧāĩ ā´ąā´ŋā´˛āĩ€ā´¸ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´†āĩģā´Ąāĩā´°āĩ‹ā´¯ā´ŋā´Ąāĩ ā´†ā´Ēāĩā´Ēāĩ ā´‡āĩģā´¸āĩā´ąāĩā´ąā´žāĩž ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´…ā´Ēāĩâ€Œā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´’ā´Ŧāĩâ€Œā´ąāĩā´ąāĩˆā´¨ā´ŋⴝⴂ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•. ā´’ā´°āĩ ā´Žā´Ēā´ŋⴐ ā´•āĩ€ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´šāĩ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´’ā´Ŧāĩâ€Œā´ąāĩā´ąāĩˆā´¨ā´ŋⴝⴂ ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ ā´˛ā´ŋā´™āĩā´•āĩ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•ā´žāĩģ ā´’ā´°āĩ ā´ĩāĩ‡ā´°ā´ŋⴝⴍāĩā´ąāĩ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "ocr": "ā´’.ā´¸ā´ŋ.ā´†āĩŧ (OCR)", - "ocr_body": "ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩžā´•āĩā´•āĩā´ŗāĩā´ŗā´ŋā´˛āĩ† ā´ĩā´žā´šā´•ā´‚ ā´ĩā´žā´¯ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´…ā´¤ā´ŋā´¨ā´žāĩŊ ā´…ā´ĩāĩŧ ā´Ēā´ąā´¯āĩā´¨āĩā´¨ā´¤ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´…ā´ĩ ā´¤ā´ŋā´°ā´¯ā´žā´¨ā´žā´•āĩā´‚.", - "ocr_title": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗā´ŋāĩŊ ⴟāĩ†ā´•āĩā´¸āĩā´ąāĩā´ąāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", "official_immich_resources": "Immich-ā´¨āĩā´ąāĩ† ā´”ā´Ļāĩā´¯āĩ‹ā´—ā´ŋā´• ā´‰ā´ąā´ĩā´ŋā´Ÿā´™āĩā´™āĩž", "offline": "ā´“ā´Ģāĩâ€Œā´˛āĩˆāĩģ", "offset": "ā´“ā´Ģāĩā´¸āĩ†ā´ąāĩā´ąāĩ", @@ -1554,10 +1498,6 @@ "online": "ā´“āĩēā´˛āĩˆāĩģ", "only_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩ ā´Žā´žā´¤āĩā´°ā´‚", "open": "ā´¤āĩā´ąā´•āĩā´•āĩā´•", - "open_calendar": "ⴕⴞ⴪āĩā´Ÿāĩŧ ā´¤āĩā´ąā´•āĩā´•āĩā´•", - "open_in_browser": "ā´Ŧāĩā´°āĩ—ā´¸ā´ąā´ŋāĩŊ ā´¤āĩā´ąā´•āĩā´•āĩā´•", - "open_in_immich_body": "ā´Žā´ąāĩā´ąāĩ ā´†ā´Ēāĩā´Ēāĩā´•ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¤āĩā´ąā´•āĩā´•ā´žāĩģ ā´†āĩģā´Ąāĩā´°āĩ‹ā´¯ā´ŋā´Ąā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´—ā´žā´˛ā´ąā´ŋā´¯ā´žā´¯ā´ŋ Immich ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•.", - "open_in_immich_title": "ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šā´ŋāĩŊ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¤āĩā´ąā´•āĩā´•āĩā´•", "open_in_map_view": "ā´Žā´žā´Ēāĩā´Ēāĩ ā´•ā´žā´´āĩâ€Œā´šā´¯ā´ŋāĩŊ ā´¤āĩā´ąā´•āĩā´•āĩā´•", "open_in_openstreetmap": "OpenStreetMap-āĩŊ ā´¤āĩā´ąā´•āĩā´•āĩā´•", "open_the_search_filters": "ā´¤ā´ŋā´°ā´¯āĩŊ ā´Ģā´ŋāĩŊⴟāĩā´Ÿā´ąāĩā´•āĩž ā´¤āĩā´ąā´•āĩā´•āĩā´•", @@ -1566,22 +1506,24 @@ "organize_into_albums": "ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´žā´¯ā´ŋ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "organize_into_albums_description": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´¸ā´ŋā´™āĩā´•āĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "organize_your_library": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", - "orientation": "ā´“ā´ąā´ŋā´¯āĩģāĩā´ąāĩ‡ā´ˇāĩģ", "original": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩā´‚", "other": "ā´Žā´ąāĩā´ąāĩā´ŗāĩā´ŗā´ĩ", "other_devices": "ā´Žā´ąāĩā´ąāĩ ā´‰ā´Ēā´•ā´°ā´Ŗā´™āĩā´™āĩž", + "other_entities": "ā´Žā´ąāĩā´ąāĩ ā´Žā´¨āĩā´ąā´ŋā´ąāĩā´ąā´ŋā´•āĩž", "other_variables": "ā´Žā´ąāĩā´ąāĩ ā´ĩāĩ‡ā´°ā´ŋā´¯ā´Ŧā´ŋā´ŗāĩā´•āĩž", "owned": "ā´¸āĩā´ĩā´¨āĩā´¤ā´Žā´žā´¯āĩā´ŗāĩā´ŗā´¤āĩ", "owner": "ā´‰ā´Ÿā´Ž", - "page": "ā´Ēāĩ‡ā´œāĩ", "partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋ", "partner_can_access": "{partner}-ā´•āĩā´•āĩ ⴆⴕāĩā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´‚", "partner_can_access_assets": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩāĩā´šāĩ†ā´¯āĩâ€Œā´¤ā´¤āĩā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋⴝⴤāĩā´Žā´žā´¯ā´ĩ ā´’ā´´ā´ŋā´•āĩ† ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žā´˛āĩā´˛ā´ž ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", "partner_can_access_location": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Žā´Ÿāĩā´¤āĩā´¤ ā´¸āĩā´Ĩⴞⴂ", "partner_list_user_photos": "{user}-ā´¨āĩā´ąāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž", + "partner_list_view_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•ā´žā´Ŗāĩā´•", "partner_page_empty_message": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ⴇⴤāĩā´ĩā´°āĩ† ā´’ā´°āĩ ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Žā´žā´¯āĩā´‚ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛.", "partner_page_no_more_users": "ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗā´ŋā´˛āĩā´˛", + "partner_page_partner_add_failed": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩ† ⴚāĩ‡āĩŧā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "partner_page_select_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "partner_page_shared_to_title": "ⴇⴤā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿāĩ", "partner_page_stop_sharing_content": "{partner}-ā´•āĩā´•āĩ ⴇⴍā´ŋ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛.", "partner_sharing": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Žā´žā´¯ā´ŋ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ", "partners": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´•āĩž", @@ -1603,6 +1545,7 @@ "people": "ⴆⴺāĩā´•āĩž", "people_edits_count": "{count, plural, one {# ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¤āĩ} other {# ⴆⴺāĩā´•ā´ŗāĩ† ā´Žā´Ąā´ŋā´ąāĩā´ąāĩā´šāĩ†ā´¯āĩā´¤āĩ}}", "people_feature_description": "ⴆⴺāĩā´•ā´ŗā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Ŧāĩā´°āĩ—ā´¸āĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", + "people_sidebar_description": "ā´¸āĩˆā´Ąāĩâ€Œā´Ŧā´žā´ąā´ŋāĩŊ 'ⴆⴺāĩā´•āĩž' ā´Žā´¨āĩā´¨ā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´’ā´°āĩ ā´˛ā´ŋā´™āĩā´•āĩ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", "permanent_deletion_warning": "ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩŊ ā´Žāĩā´¨āĩā´¨ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ", "permanent_deletion_warning_setting_description": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´’ā´°āĩ ā´Žāĩā´¨āĩā´¨ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "permanently_delete": "ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", @@ -1612,16 +1555,24 @@ "permanently_deleted_assets_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", "permission": "ā´…ā´¨āĩā´Žā´¤ā´ŋ", "permission_empty": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•ā´°āĩā´¤āĩ", + "permission_onboarding_back": "ā´¤ā´ŋā´°ā´ŋā´•āĩ†", + "permission_onboarding_continue_anyway": "ā´Žā´¨āĩā´¤ā´žā´¯ā´žā´˛āĩā´‚ ā´¤āĩā´Ÿā´°āĩā´•", + "permission_onboarding_get_started": "ā´¤āĩā´Ÿā´™āĩā´™ā´žā´‚", + "permission_onboarding_go_to_settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´•", + "permission_onboarding_permission_denied": "ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´¨ā´ŋā´ˇāĩ‡ā´§ā´ŋⴚāĩā´šāĩ. Immich ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ, ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹, ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´…ā´¨āĩā´Žā´¤ā´ŋā´•āĩž ā´¨āĩŊā´•āĩā´•.", + "permission_onboarding_permission_granted": "ā´…ā´¨āĩā´Žā´¤ā´ŋ ⴞⴭā´ŋⴚāĩā´šāĩ! ā´¨ā´ŋā´™āĩā´™āĩž ⴤⴝāĩā´¯ā´žā´ąā´žā´Ŗāĩ.", + "permission_onboarding_permission_limited": "ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´Ēā´°ā´ŋā´Žā´ŋā´¤ā´Žā´žā´Ŗāĩ. Immich-ā´¨āĩ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žāĩā´´āĩā´ĩāĩģ ā´—ā´žā´˛ā´ąā´ŋ ā´ļāĩ‡ā´–ā´°ā´ĩāĩā´‚ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´…ā´¨āĩā´ĩā´Ļā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ, ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹, ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´…ā´¨āĩā´Žā´¤ā´ŋā´•āĩž ā´¨āĩŊā´•āĩā´•.", + "permission_onboarding_request": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ Immich-ā´¨āĩ ā´…ā´¨āĩā´Žā´¤ā´ŋ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ.", "person": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋ", "person_age_months": "{months, plural, one {# ā´Žā´žā´¸ā´‚} other {# ā´Žā´žā´¸ā´‚}} ā´Ēāĩā´°ā´žā´¯ā´‚", "person_age_year_months": "1 ā´ĩāĩŧā´ˇā´ĩāĩā´‚ {months, plural, one {# ā´Žā´žā´¸ā´ĩāĩā´‚} other {# ā´Žā´žā´¸ā´ĩāĩā´‚}} ā´Ēāĩā´°ā´žā´¯ā´‚", "person_age_years": "{years, plural, other {# ā´ĩⴝⴏāĩā´¸āĩ}}", "person_birthdate": "{date}-ā´¨āĩ ⴜⴍā´ŋⴚāĩā´šāĩ", "person_hidden": "{name}{hidden, select, true { (ā´Žā´ąā´šāĩā´šā´¤āĩ)} other {}}", - "person_recognized": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋ ā´¤ā´ŋā´°ā´ŋⴚāĩā´šā´ąā´ŋā´žāĩā´žāĩ", "photo_shared_all_users": "ā´¨ā´ŋā´™āĩā´™āĩž ā´Žā´˛āĩā´˛ā´ž ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩā´Žā´žā´¯āĩā´‚ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿāĩ, ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋā´Ÿā´žāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗā´žā´°āĩā´‚ ⴇⴞāĩā´˛āĩ†ā´¨āĩā´¨āĩ ā´¤āĩ‹ā´¨āĩā´¨āĩā´¨āĩā´¨āĩ.", "photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž", "photos_and_videos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚", + "photos_count": "{count, plural, one {{count, number} ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹} other {{count, number} ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž}}", "photos_from_previous_years": "ā´Žāĩāĩģ ā´ĩāĩŧⴎⴙāĩā´™ā´ŗā´ŋā´˛āĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž", "pick_a_location": "ā´’ā´°āĩ ā´¸āĩā´Ĩⴞⴂ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "pick_custom_range": "ⴇⴎāĩā´Ÿā´žā´¨āĩā´¸āĩƒā´¤ ā´Ēā´°ā´ŋā´§ā´ŋ", @@ -1641,8 +1592,6 @@ "play_original_video_setting_description": "ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩ‡ā´•āĩā´•ā´žāĩž ā´’ā´ąā´ŋⴜā´ŋā´¨āĩŊ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Žāĩāĩģā´—ā´Ŗā´¨ ā´¨āĩŊā´•āĩā´•. ā´’ā´ąā´ŋⴜā´ŋā´¨āĩŊ ā´Ģā´¯āĩŊ ā´…ā´¨āĩā´¯āĩ‹ā´œāĩā´¯ā´Žā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´…ā´¤āĩ ā´ļā´°ā´ŋā´¯ā´žā´¯ā´ŋ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¤āĩ‡ā´•āĩā´•ā´ŋā´˛āĩā´˛.", "play_transcoded_video": "ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "please_auth_to_access": "ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´Ēāĩā´°ā´žā´Žā´žā´Ŗāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", - "plugin_method_filter_type": "ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "plugin_method_filter_type_description": "ⴈ ā´°āĩ€ā´¤ā´ŋā´•āĩā´•āĩ ā´‡ā´ĩāĩģāĩā´ąāĩā´•āĩž ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ⴚāĩ†ā´¯āĩā´¯ā´žā´¨āĩā´‚ ā´¤āĩā´Ÿāĩŧā´¨āĩā´¨āĩā´ŗāĩā´ŗ ⴘⴟāĩā´Ÿā´™āĩā´™āĩž ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¸āĩ‹ā´Ēā´žā´§ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¤ā´Ÿā´¯ā´žā´¨āĩā´‚ ā´•ā´´ā´ŋā´¯āĩā´‚", "port": "ā´Ēāĩ‹āĩŧⴟāĩā´Ÿāĩ", "preferences_settings_subtitle": "ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´Žāĩāĩģⴗ⴪ⴍⴕāĩž ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "preferences_settings_title": "ā´Žāĩāĩģⴗ⴪ⴍⴕāĩž", @@ -1659,11 +1608,11 @@ "privacy": "ā´¸āĩā´ĩā´•ā´žā´°āĩā´¯ā´¤", "profile": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ", "profile_drawer_app_logs": "ā´˛āĩ‹ā´—āĩā´•āĩž", + "profile_drawer_client_server_up_to_date": "ā´•āĩā´˛ā´¯ā´ŋā´¨āĩā´ąāĩā´‚ ā´¸āĩ†āĩŧā´ĩā´ąāĩā´‚ ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´Ēāĩā´¤ā´ŋā´¯ā´¤ā´žā´Ŗāĩ", "profile_drawer_github": "ā´—ā´ŋā´ąāĩā´ąāĩā´šā´Ŧāĩ", "profile_drawer_readonly_mode": "ā´ąāĩ€ā´Ąāĩ-ā´“āĩēā´˛ā´ŋ ā´Žāĩ‹ā´Ąāĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋ. ā´Ēāĩā´ąā´¤āĩā´¤āĩā´•ā´Ÿā´•āĩā´•ā´žāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´…ā´ĩā´¤ā´žāĩŧ ⴐⴕāĩā´•ā´Ŗā´ŋāĩŊ ā´Ļāĩ€āĩŧⴘⴍāĩ‡ā´°ā´‚ ā´…ā´Žāĩŧā´¤āĩā´¤āĩā´•.", "profile_image_of_user": "{user}-ā´¨āĩā´ąāĩ† ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ⴚā´ŋā´¤āĩā´°ā´‚", "profile_picture_set": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ⴚā´ŋā´¤āĩā´°ā´‚ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋⴚāĩā´šāĩ.", - "projection_type": "ā´Ēāĩā´°āĩŠā´œā´•āĩā´ˇāĩģ ⴤⴰⴂ", "public_album": "ā´ĒāĩŠā´¤āĩ ā´†āĩŊā´Ŧā´‚", "public_share": "ā´ĒāĩŠā´¤āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ", "purchase_account_info": "ā´¸ā´šā´žā´¯ā´ŋ", @@ -1682,6 +1631,7 @@ "purchase_individual_description_2": "ā´¸ā´šā´žā´¯ā´ŋā´¯āĩā´Ÿāĩ† ā´¨ā´ŋā´˛", "purchase_individual_title": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋⴗⴤⴂ", "purchase_input_suggestion": "ā´Ēāĩā´°āĩŠā´Ąā´•āĩā´ąāĩā´ąāĩ ā´•āĩ€ ⴉ⴪āĩā´Ÿāĩ‹? ā´¤ā´žā´´āĩ† ā´•āĩ€ ā´¨āĩŊā´•āĩā´•", + "purchase_license_subtitle": "ā´¸āĩ‡ā´ĩⴍⴤāĩā´¤ā´ŋā´¨āĩā´ąāĩ† ā´¤āĩā´Ÿāĩŧ ā´ĩā´ŋⴕⴏⴍⴤāĩā´¤āĩ† ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•ā´žāĩģ Immich ā´ĩā´žā´™āĩā´™āĩā´•", "purchase_lifetime_description": "ā´†ā´œāĩ€ā´ĩā´¨ā´žā´¨āĩā´¤ ā´ĩā´žā´™āĩā´™āĩŊ", "purchase_option_title": "ā´ĩā´žā´™āĩā´™ā´žā´¨āĩā´ŗāĩā´ŗ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", "purchase_panel_info_1": "Immich ā´¨ā´ŋāĩŧā´Žāĩā´Žā´ŋā´•āĩā´•ā´žāĩģ ā´§ā´žā´°ā´žā´ŗā´‚ ā´¸ā´Žā´¯ā´ĩāĩā´‚ ā´Ēāĩā´°ā´¯ā´¤āĩā´¨ā´ĩāĩā´‚ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ, ā´…ā´¤āĩ ā´•ā´´ā´ŋā´¯āĩā´¨āĩā´¨ā´¤āĩā´° ā´Žā´ŋā´•ā´šāĩā´šā´¤ā´žā´•āĩā´•ā´žāĩģ ā´Žāĩā´´āĩā´ĩāĩģ ā´¸ā´Žā´¯ ā´Žā´žāĩā´šā´ŋā´¨āĩ€ā´¯āĩŧā´Žā´žāĩŧ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ. ā´“ā´Ēāĩā´Ēāĩē ā´¸āĩ‹ā´´āĩâ€Œā´¸āĩ ā´¸āĩ‹ā´Ģāĩā´ąāĩā´ąāĩâ€Œā´ĩāĩ†ā´¯ā´ąāĩā´‚ ā´§ā´žāĩŧā´Žāĩā´Žā´ŋā´•ā´Žā´žā´¯ ā´Ŧā´ŋā´¸ā´ŋⴍⴏāĩā´¸āĩ ā´°āĩ€ā´¤ā´ŋā´•ā´ŗāĩā´‚ ā´Ąāĩ†ā´ĩā´˛ā´Ēāĩā´Ēāĩŧā´Žā´žāĩŧā´•āĩā´•āĩ ā´¸āĩā´¸āĩā´Ĩā´ŋā´°ā´Žā´žā´¯ ā´ĩā´°āĩā´Žā´žā´¨ ā´Žā´žāĩŧā´—āĩā´—ā´Žā´žā´•āĩā´•āĩā´•, ⴚāĩ‚ⴎ⴪ā´Ēā´°ā´Žā´žā´¯ ā´•āĩā´˛āĩ—ā´Ąāĩ ā´¸āĩ‡ā´ĩⴍⴙāĩā´™āĩžā´•āĩā´•āĩ ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´Ŧā´Ļā´˛āĩā´•ā´ŗāĩā´ŗāĩā´ŗ ā´¸āĩā´ĩā´•ā´žā´°āĩā´¯ā´¤ā´¯āĩ† ā´Žā´žā´¨ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´’ā´°āĩ ā´†ā´ĩā´žā´¸ā´ĩāĩā´¯ā´ĩā´¸āĩā´Ĩ ā´¸āĩƒā´ˇāĩā´Ÿā´ŋā´•āĩā´•āĩā´• ā´Žā´¨āĩā´¨ā´¤ā´žā´Ŗāĩ ā´žā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ļāĩ—ā´¤āĩā´¯ā´‚.", @@ -1699,7 +1649,6 @@ "purchase_settings_server_activated": "ā´¸āĩ†āĩŧā´ĩāĩŧ ā´Ēāĩā´°āĩŠā´Ąā´•āĩā´ąāĩā´ąāĩ ā´•āĩ€ ā´…ā´Ąāĩā´Žā´ŋāĩģ ⴆ⴪āĩ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤āĩ", "query_asset_id": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ā´Ąā´ŋ ā´…ā´¨āĩā´ĩāĩ‡ā´ˇā´ŋā´•āĩā´•āĩā´•", "queue_status": "ā´•āĩā´¯āĩ‚ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ {count}/{total}", - "rate_asset": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "rating": "ā´¸āĩā´ąāĩā´ąā´žāĩŧ ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ", "rating_clear": "ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ ā´•āĩā´˛ā´ŋā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "rating_count": "{count, plural, one {# ā´¸āĩā´ąāĩā´ąā´žāĩŧ} other {# ā´¸āĩā´ąāĩā´ąā´žā´ąāĩā´•āĩž}}", @@ -1714,19 +1663,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´•āĩā´•āĩ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´¨āĩŊā´•ā´ŋ", "reassing_hint": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗ ā´’ā´°āĩ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´•āĩā´•āĩ ā´¨āĩŊā´•āĩā´•", "recent": "ā´¸ā´Žāĩ€ā´Ēā´•ā´žā´˛ā´‚", + "recent_albums": "ā´¸ā´Žāĩ€ā´Ēā´•ā´žā´˛ ā´†āĩŊā´Ŧā´™āĩā´™āĩž", "recent_searches": "ā´¸ā´Žāĩ€ā´Ēā´•ā´žā´˛ ā´¤ā´ŋⴰⴝⴞāĩā´•āĩž", "recently_added": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ⴚāĩ‡āĩŧā´¤āĩā´¤ā´¤āĩ", - "recently_added_body": "ā´’ā´°āĩ ā´¸ā´Žāĩŧā´Ēāĩā´Ēā´ŋā´¤ ā´Ēāĩ‡ā´œā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™āĩž ⴈⴝā´ŋⴟāĩ† ⴚāĩ‡āĩŧā´¤āĩā´¤ ā´Žā´˛āĩā´˛ā´žā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩā´‚ ā´¨āĩ‡ā´°āĩ† ā´Ēāĩ‹ā´•āĩā´•.", - "recently_added_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤ā´Ēāĩā´Ēāĩ‹āĩž ⴤⴰⴂⴤā´ŋā´°ā´ŋⴚāĩā´š ā´Ŧāĩā´°āĩ—ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "recently_added_page_title": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ⴚāĩ‡āĩŧā´¤āĩā´¤ā´¤āĩ", - "recently_added_title": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ⴚāĩ‡āĩŧā´¤āĩā´¤ā´¤āĩ", "recently_taken": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ā´Žā´Ÿāĩā´¤āĩā´¤ā´¤āĩ", + "recently_taken_page_title": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´ŋⴟāĩ† ā´Žā´Ÿāĩā´¤āĩā´¤ā´¤āĩ", "refresh": "ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", "refresh_encoded_videos": "ā´Žāĩģā´•āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", "refresh_faces": "ā´Žāĩā´–ā´™āĩā´™āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", "refresh_metadata": "ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", "refresh_thumbnails": "ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋā´˛āĩā´•āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•", "refreshed": "ā´Ēāĩā´¤āĩā´•āĩā´•ā´ŋ", + "refreshes_every_file": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗā´¤āĩā´‚ ā´Ēāĩā´¤ā´ŋⴝⴤāĩā´Žā´žā´¯ ā´Žā´˛āĩā´˛ā´ž ā´Ģⴝⴞāĩā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ĩā´žā´¯ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "refreshing_encoded_video": "ā´Žāĩģā´•āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ", "refreshing_faces": "ā´Žāĩā´–ā´™āĩā´™āĩž ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ", "refreshing_metadata": "ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´¨āĩā´¨āĩ", @@ -1739,7 +1688,7 @@ "remove_assets_shared_link_confirmation": "ⴈ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ {count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹?", "remove_assets_title": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ŗāĩ‹?", "remove_custom_date_range": "ā´•ā´¸āĩā´ąāĩā´ąā´‚ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´§ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "remove_filter": "ā´Ģā´ŋāĩŊⴟāĩā´Ÿāĩŧ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "remove_deleted_assets": "ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "remove_from_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "remove_from_album_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "remove_from_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -1750,28 +1699,32 @@ "remove_memory": "ā´“āĩŧā´Žāĩā´Ž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "remove_photo_from_memory": "ⴈ ā´“āĩŧā´Žāĩā´Žā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "remove_tag": "ā´Ÿā´žā´—āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "remove_url": "URL ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "remove_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "removed_api_key": "API ā´•āĩ€ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ: {name}", "removed_from_archive": "ā´†āĩŧā´•āĩā´•āĩˆā´ĩā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "removed_from_favorites": "ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "removed_from_favorites_count": "{count, plural, other {# ā´Žā´Ŗāĩā´Ŗā´‚}} ā´Ēāĩā´°ā´ŋā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿā´ĩā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "removed_memory": "ā´“āĩŧā´Žāĩā´Ž ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", + "removed_photo_from_memory": "ā´“āĩŧā´Žāĩā´Žā´¯ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "removed_tagged_assets": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ}} ā´Ÿā´žā´—āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¤āĩ", "rename": "ā´Ēāĩā´¨āĩŧā´¨ā´žā´Žā´•ā´°ā´Ŗā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "repair": "ā´ąā´ŋā´Ēāĩā´Ēā´¯āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "repair_no_results_message": "ⴟāĩā´°ā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ā´¤āĩā´‚ ā´•ā´žā´Ŗā´žā´¤ā´žā´¯ā´¤āĩā´Žā´žā´¯ ā´Ģⴝⴞāĩā´•āĩž ā´‡ā´ĩā´ŋⴟāĩ† ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´‚", + "replace_with_upload": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´Žā´žā´ąāĩā´ąā´ŋā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", "repository": "ā´ąāĩ†ā´Ēāĩā´Ēāĩ‹ā´¸ā´ŋā´ąāĩā´ąā´ąā´ŋ", "require_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´†ā´ĩā´ļāĩā´¯ā´Žā´žā´Ŗāĩ", + "require_user_to_change_password_on_first_login": "ā´†ā´Ļāĩā´¯ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´Žā´žā´ąāĩā´ąā´žāĩģ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ‹ā´Ÿāĩ ā´†ā´ĩā´ļāĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´•", "rescan": "ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "reset": "ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "reset_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "reset_people_visibility": "ⴆⴺāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Ļāĩƒā´ļāĩā´¯ā´¤ ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•āĩā´•", "reset_pin_code": "ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "reset_pin_code_description": "ā´¨ā´ŋā´™āĩā´™āĩž ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´Žā´ąā´¨āĩā´¨āĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´…ā´¤āĩ ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•ā´žāĩģ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´¸āĩ†āĩŧā´ĩāĩŧ ā´…ā´Ąāĩā´Žā´ŋā´¨ā´ŋā´¸āĩā´Ÿāĩā´°āĩ‡ā´ąāĩā´ąā´ąāĩā´Žā´žā´¯ā´ŋ ā´Ŧā´¨āĩā´§ā´Ēāĩā´Ēāĩ†ā´Ÿā´žā´‚", + "reset_pin_code_success": "ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "reset_pin_code_with_password": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Žā´Ēāĩā´Ēāĩ‹ā´´āĩā´‚ ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´‚", "reset_sqlite": "SQLite ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "reset_sqlite_clear_app_data": "ā´Ąā´žā´ąāĩā´ą ā´Žā´žā´¯āĩā´•āĩā´•āĩā´•", "reset_sqlite_confirmation": "SQLite ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‰ā´ąā´Ēāĩā´Ēā´žā´Ŗāĩ‹? ā´Ąā´žā´ąāĩā´ą ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´¨ā´ŋā´™āĩā´™āĩž ā´˛āĩ‹ā´—āĩ ā´”ā´Ÿāĩā´Ÿāĩ ⴚāĩ†ā´¯āĩā´¤āĩ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩ‡ā´Ŗāĩā´Ÿā´ŋā´ĩā´°āĩā´‚", - "reset_sqlite_confirmation_note": "ā´ļāĩā´°ā´Ļāĩā´§ā´ŋā´•āĩā´•āĩā´•: ā´Žā´žā´¯āĩâ€Œā´šāĩā´šā´¤ā´ŋā´¨āĩ ā´ļāĩ‡ā´ˇā´‚ ā´¨ā´ŋā´™āĩā´™āĩž ā´†ā´Ēāĩā´Ēāĩ ā´Ēāĩā´¨ā´°ā´žā´°ā´‚ā´­ā´ŋā´•āĩā´•āĩ‡ā´Ŗāĩā´Ÿā´¤āĩā´Ŗāĩā´Ÿāĩ.", - "reset_sqlite_done": "ā´†ā´Ēāĩā´Ēāĩ ā´Ąā´žā´ąāĩā´ą ā´Žā´žā´¯āĩâ€Œā´šāĩā´šāĩ. ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šāĩ ā´Ēāĩā´¨ā´°ā´žā´°ā´‚ā´­ā´ŋⴚāĩā´šāĩ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´˛āĩ‹ā´—ā´ŋāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•.", "reset_sqlite_success": "SQLite ā´Ąā´žā´ąāĩā´ąā´žā´Ŧāĩ‡ā´¸āĩ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´ąāĩ€ā´¸āĩ†ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "reset_to_default": "ā´Ąā´ŋā´Ģāĩ‹āĩžā´Ÿāĩā´Ÿā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩā´¨ā´ƒā´¸ā´œāĩā´œā´Žā´žā´•āĩā´•āĩā´•", "resolution": "ā´ąāĩ†ā´¸ā´˛āĩā´¯āĩ‚ā´ˇāĩģ", @@ -1779,10 +1732,12 @@ "resolved_all_duplicates": "ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´Ēā´°ā´ŋā´šā´°ā´ŋⴚāĩā´šāĩ", "restore": "ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", "restore_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", + "restore_trash_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ⴟāĩā´°ā´žā´ˇā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ", "restore_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", "restored_asset": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋⴚāĩā´šāĩ", "resume": "ā´Ēāĩā´¨ā´°ā´žā´°ā´‚ā´­ā´ŋā´•āĩā´•āĩā´•", "resume_paused_jobs": "{count, plural, one {ā´¤ā´žāĩŊā´•āĩā´•ā´žā´˛ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¨ā´ŋāĩŧā´¤āĩā´¤ā´ŋā´¯ # ⴜāĩ‹ā´˛ā´ŋ} other {ā´¤ā´žāĩŊā´•āĩā´•ā´žā´˛ā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¨ā´ŋāĩŧā´¤āĩā´¤ā´ŋā´¯ # ⴜāĩ‹ā´˛ā´ŋā´•āĩž}} ā´Ēāĩā´¨ā´°ā´žā´°ā´‚ā´­ā´ŋā´•āĩā´•āĩā´•", + "retry_upload": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩā´‚ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´•", "review_duplicates": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•āĩž ā´…ā´ĩā´˛āĩ‹ā´•ⴍⴂ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "review_large_files": "ā´ĩā´˛ā´ŋā´¯ ā´Ģⴝⴞāĩā´•āĩž ā´…ā´ĩā´˛āĩ‹ā´•ⴍⴂ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "role": "ā´ąāĩ‹āĩž", @@ -1790,20 +1745,17 @@ "role_viewer": "ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¯ā´žāĩž", "running": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", "save": "ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "save_to_gallery": "ā´—ā´žā´˛ā´ąā´ŋā´¯ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "saved": "ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "saved_api_key": "API ā´•āĩ€ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "saved_profile": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "saved_settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "say_something": "ā´Žā´¨āĩā´¤āĩ†ā´™āĩā´•ā´ŋā´˛āĩā´‚ ā´Ēā´ąā´¯āĩ‚", "scaffold_body_error_occurred": "ā´Ēā´ŋā´ļā´•āĩ ⴏⴂⴭā´ĩā´ŋⴚāĩā´šāĩ", - "scaffold_body_error_unrecoverable": "ā´ĩāĩ€ā´Ŗāĩā´Ÿāĩ†ā´Ÿāĩā´•āĩā´•ā´žā´¨ā´žā´•ā´žā´¤āĩā´¤ ā´’ā´°āĩ ā´Ēā´ŋā´ļā´•āĩ ⴏⴂⴭā´ĩā´ŋⴚāĩā´šāĩ. ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´Ēā´ŋā´ļā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•ā´¯āĩā´‚ ā´Ąā´ŋā´¸āĩâ€Œā´•āĩ‹āĩŧā´Ąā´ŋā´˛āĩ‹ GitHub-ā´˛āĩ‹ ⴟāĩā´°āĩ‡ā´¸āĩ ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•, ā´…ā´™āĩā´™ā´¨āĩ† ā´žā´™āĩā´™āĩžā´•āĩā´•āĩ ā´¸ā´šā´žā´¯ā´ŋā´•āĩā´•ā´žā´¨ā´žā´•āĩā´‚. ā´‰ā´Ēā´Ļāĩ‡ā´ļā´ŋā´•āĩā´•āĩā´•ā´¯ā´žā´Ŗāĩ†ā´™āĩā´•ā´ŋāĩŊ, ⴚāĩā´ĩⴟāĩ†ā´¯āĩā´ŗāĩā´ŗ ā´†ā´Ēāĩā´Ēāĩ ā´Ąā´žā´ąāĩā´ą ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Žā´žā´¯āĩā´•āĩā´•ā´žā´ĩāĩā´¨āĩā´¨ā´¤ā´žā´Ŗāĩ.", - "scan": "ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "scan_all_libraries": "ā´Žā´˛āĩā´˛ā´ž ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋā´•ā´ŗāĩā´‚ ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "scan_library": "ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "scanning": "ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", + "scan_settings": "ā´¸āĩā´•ā´žāĩģ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "scanning_for_album": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´¨ā´žā´¯ā´ŋ ā´¸āĩā´•ā´žāĩģ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ...", - "screencast_mode_description": "ā´¸āĩā´•āĩā´°āĩ€ā´¨ā´ŋāĩŊ ā´•āĩ€ā´Ŧāĩ‹āĩŧā´Ąāĩ, ā´Žāĩ—ā´¸āĩ ā´‡ā´ĩāĩģāĩā´ąāĩ ā´¸āĩ‚ā´šā´•ā´™āĩā´™āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", - "screencast_mode_title": "ā´¸āĩâ€Œā´•āĩā´°āĩ€āĩģā´•ā´žā´¸āĩā´ąāĩā´ąāĩ ā´Žāĩ‹ā´Ąāĩ ⴟāĩ‹ā´—ā´ŋāĩž ⴚāĩ†ā´¯āĩā´¯āĩā´•", "search": "ā´¤ā´ŋā´°ā´¯āĩā´•", "search_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_by_context": "ⴏⴍāĩā´Ļāĩŧā´­ā´‚ ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", @@ -1811,8 +1763,6 @@ "search_by_description_example": "ā´¸ā´Ēāĩā´Ēā´¯ā´ŋā´˛āĩ† ā´šāĩˆā´•āĩā´•ā´ŋā´‚ā´—āĩ ā´Ļā´ŋⴍⴂ", "search_by_filename": "ā´Ģā´¯āĩŊ ā´¨ā´žā´Žā´‚ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´•āĩā´¸āĩā´ąāĩā´ąāĩģā´ˇāĩģ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_by_filename_example": "ā´‰ā´Ļā´ž. IMG_1234.JPG ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ PNG", - "search_by_full_path": "ā´Žāĩā´´āĩā´ĩāĩģ ā´Ēā´žā´¤ā´¯āĩ‹ ā´Ģāĩ‹āĩžā´Ąā´ąāĩ‹ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Ēāĩā´°āĩ‹ā´œā´•āĩâ€Œā´ąāĩā´ąāĩā´•āĩž, 3D, ā´Ēāĩā´°ā´ŋāĩģāĩā´ąā´ŋā´‚ā´—āĩ, 2026 ā´Žāĩā´¤ā´˛ā´žā´¯ā´ĩ ā´¤ā´ŋā´°ā´¯ā´žā´¨ā´žā´•āĩā´‚.", "search_by_ocr": "ā´’.ā´¸ā´ŋ.ā´†āĩŧ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_by_ocr_example": "ā´˛ā´žā´ąāĩā´ąāĩ† (Latte)", "search_camera_lens_model": "ā´˛āĩ†āĩģā´¸āĩ ā´Žāĩ‹ā´ĄāĩŊ ā´¤ā´ŋā´°ā´¯āĩā´•...", @@ -1832,9 +1782,8 @@ "search_filter_location_title": "ā´¸āĩā´Ĩā´žā´¨ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "search_filter_media_type": "ā´Žāĩ€ā´Ąā´ŋā´¯ ⴤⴰⴂ", "search_filter_media_type_title": "ā´Žāĩ€ā´Ąā´ŋā´¯ ⴤⴰⴂ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "search_filter_ocr": "ā´’.ā´¸ā´ŋ.ā´†āĩŧ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_filter_people_title": "ⴆⴺāĩā´•ā´ŗāĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "search_filter_star_rating": "ā´¸āĩā´ąāĩā´ąā´žāĩŧ ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ", - "search_filter_tags_title": "ā´Ÿā´žā´—āĩā´•āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "search_for": "ⴇⴤā´ŋā´¨ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_for_existing_person": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩā´ŗāĩā´ŗ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•", "search_no_more_result": "ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´Ģⴞⴙāĩā´™ā´ŗā´ŋā´˛āĩā´˛", @@ -1842,13 +1791,25 @@ "search_no_people_named": "\"{name}\" ā´Žā´¨āĩā´¨āĩ ā´Ēāĩ‡ā´°āĩā´ŗāĩā´ŗ ⴆⴺāĩā´•ā´ŗā´ŋā´˛āĩā´˛", "search_no_result": "ā´Ģⴞⴙāĩā´™ā´ŗāĩŠā´¨āĩā´¨āĩā´‚ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´ŋā´¯ā´ŋā´˛āĩā´˛, ā´Žā´ąāĩā´ąāĩŠā´°āĩ ā´¤ā´ŋā´°ā´¯āĩŊ ā´Ēā´Ļā´Žāĩ‹ ⴏⴂⴝāĩ‹ā´œā´¨ā´Žāĩ‹ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´ļāĩā´°ā´Žā´ŋā´•āĩā´•āĩā´•", "search_options": "ā´¤ā´ŋā´°ā´¯āĩŊ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž", + "search_page_categories": "ā´ĩā´ŋā´­ā´žā´—ā´™āĩā´™āĩž", + "search_page_motion_photos": "ⴚⴞā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž", + "search_page_no_objects": "ā´ĩā´¸āĩā´¤āĩā´•āĩā´•ā´ŗāĩā´Ÿāĩ† ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", + "search_page_no_places": "ā´¸āĩā´Ĩⴞⴙāĩā´™ā´ŗāĩā´Ÿāĩ† ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ⴞⴭāĩā´¯ā´Žā´˛āĩā´˛", + "search_page_screenshots": "ā´¸āĩā´•āĩā´°āĩ€āĩģā´ˇāĩ‹ā´Ÿāĩā´Ÿāĩā´•āĩž", "search_page_search_photos_videos": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩžā´•āĩā´•āĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩžā´•āĩā´•āĩā´Žā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•", + "search_page_selfies": "ā´¸āĩ†āĩŊā´Ģā´ŋā´•āĩž", + "search_page_things": "ā´ĩā´¸āĩā´¤āĩā´•āĩā´•āĩž", "search_page_view_all_button": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•ā´žā´Ŗāĩā´•", + "search_page_your_activity": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚", + "search_page_your_map": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žā´žā´Ēāĩā´Ēāĩ", "search_people": "ⴆⴺāĩā´•ā´ŗāĩ† ā´¤ā´ŋā´°ā´¯āĩā´•", "search_places": "ā´¸āĩā´Ĩⴞⴙāĩā´™āĩž ā´¤ā´ŋā´°ā´¯āĩā´•", "search_rating": "ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´¤ā´ŋā´°ā´¯āĩā´•...", + "search_result_page_new_search_hint": "ā´Ēāĩā´¤ā´ŋā´¯ ā´¤ā´ŋā´°ā´¯āĩŊ", "search_settings": "ā´¤ā´ŋā´°ā´¯āĩŊ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", "search_state": "ⴏⴂⴏāĩā´Ĩā´žā´¨ā´‚ ā´¤ā´ŋā´°ā´¯āĩā´•...", + "search_suggestion_list_smart_search_hint_1": "ā´¸āĩā´Žā´žāĩŧⴟāĩā´Ÿāĩ ā´¸āĩ†āĩŧⴚāĩā´šāĩ ā´Ąā´ŋā´Ģāĩ‹āĩžā´Ÿāĩā´Ÿā´žā´¯ā´ŋ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋā´¯ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ, ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ąā´¯āĩā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯ā´žāĩģ ⴈ ā´ĩā´žā´•āĩā´¯ā´˜ā´Ÿā´¨ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´• ", + "search_suggestion_list_smart_search_hint_2": "m:ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ†-ā´¤ā´ŋā´°ā´¯āĩŊ-ā´Ēā´Ļā´‚", "search_tags": "ā´Ÿā´žā´—āĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•...", "search_timezone": "ā´¸ā´Žā´¯ā´Žāĩ‡ā´–ⴞⴝāĩā´•āĩā´•ā´žā´¯ā´ŋ ā´¤ā´ŋā´°ā´¯āĩā´•...", "search_type": "ā´¤ā´ŋā´°ā´¯āĩŊ ⴤⴰⴂ", @@ -1857,25 +1818,21 @@ "second": "ā´¸āĩ†ā´•āĩā´•ā´¨āĩā´ąāĩ", "see_all_people": "ā´Žā´˛āĩā´˛ā´ž ⴆⴺāĩā´•ā´ŗāĩ†ā´¯āĩā´‚ ā´•ā´žā´Ŗāĩā´•", "select": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_album": "ā´†āĩŊā´Ŧā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_album_cover": "ā´†āĩŊā´Ŧā´‚ ā´•ā´ĩāĩŧ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_all_duplicates": "ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "select_all_in": "{group}-ā´˛āĩ† ā´Žā´˛āĩā´˛ā´žā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_avatar_color": "ā´…ā´ĩā´¤ā´žāĩŧ ā´¨ā´ŋā´ąā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_count": "{count, plural, one {# ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•} other {# ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•}}", - "select_cutoff_date": "ā´•ā´Ÿāĩā´Ÿāĩā´“ā´Ģāĩ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_face": "ā´Žāĩā´–ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_featured_photo": "ā´Ģāĩ€ā´šāĩā´šāĩŧ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_from_computer": "ā´•ā´Žāĩā´Ēāĩā´¯āĩ‚ā´Ÿāĩā´Ÿā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_keep_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´¸āĩ‚ā´•āĩā´ˇā´ŋā´•āĩā´•ā´žāĩģ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "select_library_owner": "ā´˛āĩˆā´Ŧāĩā´°ā´ąā´ŋ ā´‰ā´Ÿā´Žā´¯āĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_new_face": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Žāĩā´–ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_people": "ⴆⴺāĩā´•ā´ŗāĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_person": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_person_to_tag": "ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´’ā´°āĩ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", - "select_quality": "ā´—āĩā´Ŗā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "select_trash_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "select_user_for_sharing_page_err_album": "ā´†āĩŊā´Ŧā´‚ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´°ā´žā´œā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´Ÿāĩ", "selected": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤āĩ", "selected_count": "{count, plural, other {# ā´Žā´Ŗāĩā´Ŗā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤āĩ}}", "selected_gps_coordinates": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ GPS ā´•āĩ‹āĩŧā´Ąā´ŋā´¨āĩ‡ā´ąāĩā´ąāĩā´•āĩž", @@ -1900,39 +1857,47 @@ "set_profile_picture": "ā´Ēāĩā´°āĩŠā´ĢāĩˆāĩŊ ⴚā´ŋā´¤āĩā´°ā´‚ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•āĩā´•", "set_slideshow_to_fullscreen": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ ā´Ģāĩāĩžā´¸āĩā´•āĩā´°āĩ€ā´¨ā´žā´¯ā´ŋ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•āĩā´•", "set_stack_primary_asset": "ā´Ēāĩā´°ā´§ā´žā´¨ ā´…ā´¸ā´ąāĩā´ąā´žā´¯ā´ŋ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•āĩā´•", - "setting_image_navigation_enable_subtitle": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ, ā´¸āĩâ€Œā´•āĩā´°āĩ€ā´¨ā´ŋāĩģāĩā´ąāĩ† ā´‡ā´Ÿā´¤āĩā´­ā´žā´—ā´¤āĩā´¤āĩ/ā´ĩⴞⴤāĩ ā´­ā´žā´—ā´¤āĩā´¤āĩ ā´Ÿā´žā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ ā´Žāĩā´Žāĩā´Ēā´¤āĩā´¤āĩ†/ā´…ā´Ÿāĩā´¤āĩā´¤ ⴚā´ŋā´¤āĩā´°ā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´‚.", - "setting_image_navigation_enable_title": "ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ÿā´žā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "setting_image_navigation_title": "ā´‡ā´Žāĩ‡ā´œāĩ ā´¨ā´žā´ĩā´ŋā´—āĩ‡ā´ˇāĩģ", "setting_image_viewer_help": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļ ā´ĩāĩā´¯āĩ‚ā´ĩāĩŧ ā´†ā´Ļāĩā´¯ā´‚ ⴚāĩ†ā´ąā´ŋā´¯ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋāĩŊ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ, ā´¤āĩā´Ÿāĩŧā´¨āĩā´¨āĩ ā´‡ā´Ÿā´¤āĩā´¤ā´°ā´‚ ā´Ēāĩā´°ā´ŋā´ĩāĩā´¯āĩ‚ (ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ), ā´’ā´Ÿāĩā´ĩā´ŋāĩŊ ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ⴚā´ŋā´¤āĩā´°ā´‚ (ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•ā´ŋā´¯ā´ŋⴟāĩā´Ÿāĩā´Ŗāĩā´Ÿāĩ†ā´™āĩā´•ā´ŋāĩŊ) ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ.", "setting_image_viewer_original_subtitle": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´Ēāĩ‚āĩŧā´Ŗāĩā´Ŗ-ā´ąāĩ†ā´¸ā´˛āĩā´¯āĩ‚ā´ˇāĩģ ⴚā´ŋā´¤āĩā´°ā´‚ (ā´ĩā´˛āĩā´¤āĩ!) ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•. ā´Ąā´žā´ąāĩā´ą ā´‰ā´Ēā´¯āĩ‹ā´—ā´‚ (ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩā´‚ ā´‰ā´Ēā´•ā´°ā´Ŗ ā´•ā´žā´ˇāĩ†ā´¯āĩā´‚) ā´•āĩā´ąā´¯āĩā´•āĩā´•ā´žāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•.", "setting_image_viewer_original_title": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ⴚā´ŋā´¤āĩā´°ā´‚ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", "setting_image_viewer_preview_subtitle": "ā´‡ā´Ÿā´¤āĩā´¤ā´°ā´‚ ā´ąāĩ†ā´¸ā´˛āĩā´¯āĩ‚ⴎⴍāĩā´ŗāĩā´ŗ ⴚā´ŋā´¤āĩā´°ā´‚ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯ā´žāĩģ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•. ā´’ā´¨āĩā´¨āĩā´•ā´ŋāĩŊ ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ⴚā´ŋā´¤āĩā´°ā´‚ ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ‹ ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋāĩŊ ā´Žā´žā´¤āĩā´°ā´‚ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ‹ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•āĩā´•.", "setting_image_viewer_preview_title": "ā´Ēāĩā´°ā´ŋā´ĩāĩā´¯āĩ‚ ⴚā´ŋā´¤āĩā´°ā´‚ ā´˛āĩ‹ā´Ąāĩā´šāĩ†ā´¯āĩā´¯āĩā´•", + "setting_image_viewer_title": "ⴚā´ŋā´¤āĩā´°ā´™āĩā´™āĩž", "setting_languages_apply": "ā´Ēāĩā´°ā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "setting_languages_subtitle": "ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´­ā´žā´ˇ ā´Žā´žā´ąāĩā´ąāĩā´•", + "setting_notifications_notify_failures_grace_period": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´Ēā´°ā´žā´œā´¯ā´™āĩā´™āĩž ā´…ā´ąā´ŋā´¯ā´ŋā´•āĩā´•āĩā´•: {duration}", + "setting_notifications_notify_hours": "{count} ā´Žā´Ŗā´ŋā´•āĩā´•āĩ‚āĩŧ", + "setting_notifications_notify_immediately": "ā´‰ā´Ÿā´¨ā´Ÿā´ŋ", "setting_notifications_notify_minutes": "{count} ā´Žā´ŋā´¨ā´ŋā´ąāĩā´ąāĩ", + "setting_notifications_notify_never": "ā´’ā´°ā´ŋā´•āĩā´•ā´˛āĩā´‚", "setting_notifications_notify_seconds": "{count} ā´¸āĩ†ā´•āĩā´•āĩģā´Ąāĩ", + "setting_notifications_single_progress_subtitle": "ā´“ā´°āĩ‹ ā´…ā´¸ā´ąāĩā´ąā´ŋā´¨āĩā´ąāĩ†ā´¯āĩā´‚ ā´ĩā´ŋā´ļā´Ļā´Žā´žā´¯ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋ ā´ĩā´ŋā´ĩā´°ā´‚", + "setting_notifications_single_progress_title": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´ĩā´ŋā´ļā´Ļā´Žā´žā´¯ ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "setting_notifications_subtitle": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ ā´Žāĩāĩģⴗ⴪ⴍⴕāĩž ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", + "setting_notifications_total_progress_subtitle": "ā´ŽāĩŠā´¤āĩā´¤ā´¤āĩā´¤ā´ŋā´˛āĩā´ŗāĩā´ŗ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋ (ⴚāĩ†ā´¯āĩā´¤ā´¤āĩ/ⴆⴕāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž)", + "setting_notifications_total_progress_title": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´ŽāĩŠā´¤āĩā´¤ā´‚ ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "setting_video_viewer_auto_play_subtitle": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´¤āĩā´ąā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ⴤⴍāĩā´¨āĩ† ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ⴆⴰⴂⴭā´ŋā´•āĩā´•āĩā´•", "setting_video_viewer_auto_play_title": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ā´“ā´Ÿāĩā´Ÿāĩ‹ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "setting_video_viewer_looping_title": "ā´˛āĩ‚ā´Ēāĩā´Ēā´ŋā´‚ā´—āĩ", "setting_video_viewer_original_video_subtitle": "ā´¸āĩ†āĩŧā´ĩā´ąā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´’ā´°āĩ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´¸āĩā´Ÿāĩā´°āĩ€ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž, ā´’ā´°āĩ ⴟāĩā´°ā´žāĩģā´¸āĩâ€Œā´•āĩ‹ā´Ąāĩ ⴞⴭāĩā´¯ā´Žā´žā´Ŗāĩ†ā´™āĩā´•ā´ŋā´˛āĩā´‚ ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•. ⴇⴤāĩ ā´Ŧā´Ģā´ąā´ŋā´‚ā´—ā´ŋā´¨āĩ ā´•ā´žā´°ā´Ŗā´Žā´žā´¯āĩ‡ā´•āĩā´•ā´žā´‚. ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´•ā´Žā´žā´¯ā´ŋ ⴞⴭāĩā´¯ā´Žā´žā´¯ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž ⴈ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´‚ ā´Ēā´°ā´ŋā´—ā´Ŗā´ŋā´•āĩā´•ā´žā´¤āĩ† ⴤⴍāĩā´¨āĩ† ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Žā´¯ā´ŋāĩŊ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´‚.", "setting_video_viewer_original_video_title": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´¨ā´ŋāĩŧā´Ŧā´¨āĩā´§ā´Žā´žā´•āĩā´•āĩā´•", "settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", + "settings_require_restart": "ⴈ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´‚ ā´Ēāĩā´°ā´¯āĩ‹ā´—ā´ŋā´•āĩā´•ā´žāĩģ ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ Immich ā´Ēāĩā´¨ā´°ā´žā´°ā´‚ā´­ā´ŋā´•āĩā´•āĩā´•", "settings_saved": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "setup_pin_code": "ā´’ā´°āĩ ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ⴏⴜāĩā´œāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "share": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", + "share_action_prompt": "{count} ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿāĩ", + "share_add_photos": "ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•", + "share_assets_selected": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤āĩ", "share_dialog_preparing": "ⴤⴝāĩā´¯ā´žā´ąā´žā´•āĩā´•āĩā´¨āĩā´¨āĩ...", "share_link": "ā´˛ā´ŋā´™āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", - "share_original": "ā´¯ā´Ĩā´žāĩŧā´¤āĩā´Ĩ (ā´ĩā´˛āĩā´¤āĩ) ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", - "share_preview": "ⴞⴘāĩā´šā´ŋā´¤āĩā´°ā´‚ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´• (ⴚāĩ†ā´ąāĩā´¤āĩ)", - "share_quality_body": "ā´¨ā´ŋā´™āĩā´™āĩž ā´Ēā´™āĩā´•ā´ŋⴟāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Žāĩā´Žāĩā´Ēāĩ ⴚā´ŋā´¤āĩā´°ā´¤āĩā´¤ā´ŋāĩģāĩā´ąāĩ† ā´—āĩā´Ŗā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ ā´Ŧⴟāĩā´Ÿāĩē ā´…ā´Žāĩŧā´¤āĩā´¤ā´ŋā´Ēāĩā´Ēā´ŋⴟā´ŋā´•āĩā´•āĩā´•.", - "share_quality_title": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´ˇāĩ†ā´¯āĩŧ ā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "shared": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿā´¤āĩ", "shared_album_activities_input_disable": "ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´‚ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋ", "shared_album_activity_remove_content": "ⴈ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗāĩ‹?", "shared_album_activity_remove_title": "ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", "shared_album_section_people_action_error": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ĩā´ŋⴟāĩā´Ÿāĩā´Ēāĩ‹ā´•āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ‹/ā´¨āĩ€ā´•āĩā´•ā´‚ā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ‹ ā´Ēā´ŋā´ļā´•āĩ", + "shared_album_section_people_action_leave": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "shared_album_section_people_action_remove_user": "ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "shared_album_section_people_title": "ⴆⴺāĩā´•āĩž", "shared_by": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿā´¤āĩ", "shared_by_user": "{user} ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿāĩ", @@ -1941,6 +1906,7 @@ "shared_intent_upload_button_progress_text": "{current}/{total} ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "shared_link_app_bar_title": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž", "shared_link_clipboard_copied_massage": "ā´•āĩā´˛ā´ŋā´Ēāĩā´Ēāĩā´Ŧāĩ‹āĩŧā´Ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēā´•āĩŧā´¤āĩā´¤ā´ŋ", + "shared_link_clipboard_text": "ā´˛ā´ŋā´™āĩā´•āĩ: {link}\nā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ: {password}", "shared_link_create_error": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´¨āĩā´¨ā´¤ā´ŋāĩŊ ā´Ēā´ŋā´ļā´•āĩ", "shared_link_custom_url_description": "ā´’ā´°āĩ ā´•ā´¸āĩā´ąāĩā´ąā´‚ URL ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ⴈ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩ ⴆⴕāĩâ€Œā´¸ā´¸āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "shared_link_edit_description_hint": "ā´Ēā´™āĩā´•ā´ŋⴟⴞā´ŋā´¨āĩā´ąāĩ† ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´¨āĩŊā´•āĩā´•", @@ -1976,6 +1942,12 @@ "shared_with_partner": "{partner}-ā´Žā´žā´¯ā´ŋ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿāĩ", "sharing": "ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ", "sharing_enter_password": "ⴈ ā´Ēāĩ‡ā´œāĩ ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Ļā´¯ā´ĩā´žā´¯ā´ŋ ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´¨āĩŊā´•āĩā´•.", + "sharing_page_album": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´™āĩā´™āĩž", + "sharing_page_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•ā´ŋā´˛āĩ† ⴆⴺāĩā´•ā´ŗāĩā´Žā´žā´¯ā´ŋ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Ēā´™āĩā´•ā´ŋā´Ÿā´žāĩģ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´™āĩā´™āĩž ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•.", + "sharing_page_empty_list": "ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´¯ ā´˛ā´ŋā´¸āĩā´ąāĩā´ąāĩ", + "sharing_sidebar_description": "ā´¸āĩˆā´Ąāĩâ€Œā´Ŧā´žā´ąā´ŋāĩŊ 'ā´Ēā´™āĩā´•ā´ŋⴟāĩŊ' ā´Žā´¨āĩā´¨ā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´’ā´°āĩ ā´˛ā´ŋā´™āĩā´•āĩ ā´Ēāĩā´°ā´Ļāĩŧā´ļā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´•", + "sharing_silver_appbar_create_shared_album": "ā´Ēāĩā´¤ā´ŋā´¯ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´†āĩŊā´Ŧā´‚", + "sharing_silver_appbar_share_partner": "ā´Ēā´™āĩā´•ā´žā´ŗā´ŋā´¯āĩā´Žā´žā´¯ā´ŋ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´•", "shift_to_permanent_delete": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´žāĩģ ⇧ ā´…ā´Žāĩŧā´¤āĩā´¤āĩā´•", "show_album_options": "ā´†āĩŊā´Ŧā´‚ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", @@ -1987,17 +1959,13 @@ "show_in_timeline": "ⴟāĩˆā´‚ā´˛āĩˆā´¨ā´ŋāĩŊ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_in_timeline_setting_description": "ⴈ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩā´ŗāĩā´ŗ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ⴟāĩˆā´‚ā´˛āĩˆā´¨ā´ŋāĩŊ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_keyboard_shortcuts": "ā´•āĩ€ā´Ŧāĩ‹āĩŧā´Ąāĩ ā´•āĩā´ąāĩā´•āĩā´•āĩā´ĩā´´ā´ŋā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", - "show_less": "ā´•āĩā´ąā´ĩāĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_metadata": "ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", - "show_more_fields": "{count, plural, one {# ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´Ģāĩ€āĩŊā´Ąāĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•} other {# ā´•āĩ‚ā´Ÿāĩā´¤āĩŊ ā´Ģāĩ€āĩŊā´Ąāĩā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•}}", "show_or_hide_info": "ā´ĩā´ŋā´ĩā´°ā´™āĩā´™āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´• ā´…ā´˛āĩā´˛āĩ†ā´™āĩā´•ā´ŋāĩŊ ā´Žā´ąā´¯āĩā´•āĩā´•āĩā´•", "show_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_person_options": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩā´Ÿāĩ† ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_progress_bar": "ā´Ēāĩā´°āĩ‹ā´—ā´¤ā´ŋ ā´Ŧā´žāĩŧ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", - "show_schema": "ā´¸āĩā´•āĩ€ā´Ž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_search_options": "ā´¤ā´ŋā´°ā´¯āĩŊ ā´“ā´Ēāĩā´ˇā´¨āĩā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_shared_links": "ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", - "show_slideshow_metadata_overlay": "ⴚā´ŋā´¤āĩā´° ā´ĩā´ŋā´ĩā´° ā´“ā´ĩāĩŧā´˛āĩ‡ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_slideshow_transition": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ ⴏⴂⴕāĩā´°ā´Žā´Ŗā´‚ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", "show_supporter_badge": "ā´¸ā´šā´žā´¯ā´ŋā´¯āĩā´Ÿāĩ† ā´Ŧā´žā´Ąāĩā´œāĩ", "show_supporter_badge_description": "ā´’ā´°āĩ ā´¸ā´šā´žā´¯ā´ŋā´¯āĩā´Ÿāĩ† ā´Ŧā´žā´Ąāĩā´œāĩ ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´•", @@ -2009,25 +1977,16 @@ "sign_out": "ā´¸āĩˆāĩģ ā´”ā´Ÿāĩā´Ÿāĩ", "sign_up": "ā´¸āĩˆāĩģ ā´…ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "size": "ā´ĩā´˛ā´ŋā´Ēāĩā´Ēā´‚", - "skip": "ā´’ā´´ā´ŋā´ĩā´žā´•āĩā´•āĩā´•", "skip_to_content": "ⴉⴺāĩā´ŗā´Ÿā´•āĩā´•ā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´•", "skip_to_folders": "ā´Ģāĩ‹āĩžā´Ąā´ąāĩā´•ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´•", "skip_to_tags": "ā´Ÿā´žā´—āĩā´•ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ēāĩ‹ā´•āĩā´•", "slideshow": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹", - "slideshow_body": "ā´’ā´°āĩ ā´Ģāĩāĩž ā´¸āĩâ€Œā´•āĩā´°āĩ€āĩģ ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ā´¯ā´ŋāĩŊ ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨ā´¤āĩ ā´•ā´žā´Ŗāĩ‚.", - "slideshow_metadata_overlay_mode": "ā´“ā´ĩāĩŧā´˛āĩ‡ ⴉⴺāĩā´ŗā´Ÿā´•āĩā´•ā´‚", - "slideshow_metadata_overlay_mode_description_only": "ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚ ā´Žā´žā´¤āĩā´°ā´‚", - "slideshow_metadata_overlay_mode_full": "ā´¨ā´ŋā´ąā´žāĩā´žāĩ", - "slideshow_repeat": "ā´¸āĩā´˛āĩˆā´Ąāĩā´ˇāĩ‹ ā´†ā´ĩāĩŧā´¤āĩā´¤ā´ŋā´•āĩā´•āĩā´•", - "slideshow_repeat_description": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ ā´…ā´ĩā´¸ā´žā´¨ā´ŋā´•āĩā´•āĩā´Žāĩā´Ēāĩ‹āĩž ā´¤āĩā´Ÿā´•āĩā´•ā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¤ā´ŋā´°ā´ŋā´•āĩ† ā´˛āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "slideshow_settings": "ā´¸āĩā´˛āĩˆā´Ąāĩâ€Œā´ˇāĩ‹ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž", - "slideshow_title": "ā´¸āĩā´˛āĩˆā´Ąāĩā´ˇāĩ‹", - "smart_album": "ā´¸āĩā´Žā´žāĩŧⴟāĩā´Ÿāĩ ā´†āĩŊā´Ŧā´‚", - "some_assets_already_have_a_location_warning": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ⴚā´ŋā´˛ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩžā´•āĩā´•āĩ ⴇⴤā´ŋⴍⴕⴂ ā´’ā´°āĩ ā´˛āĩŠā´•āĩā´•āĩ‡ā´ˇāĩģ ⴉ⴪āĩā´Ÿāĩ", "sort_albums_by": "ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗāĩ† ⴇⴤⴍāĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•...", "sort_created": "ā´¸āĩƒā´ˇāĩā´Ÿā´ŋⴚāĩā´š ā´¤āĩ€ā´¯ā´¤ā´ŋ", "sort_items": "ⴇⴍⴙāĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žā´Ŗāĩā´Ŗā´‚", "sort_modified": "ā´Žā´žā´ąāĩā´ąā´‚ ā´ĩā´°āĩā´¤āĩā´¤ā´ŋā´¯ ā´¤āĩ€ā´¯ā´¤ā´ŋ", + "sort_newest": "ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´Ēāĩā´¤ā´ŋā´¯ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹", "sort_oldest": "ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´Ēā´´ā´¯ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹", "sort_people_by_similarity": "ā´¸ā´žā´Žāĩā´¯ā´‚ ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ⴆⴺāĩā´•ā´ŗāĩ† ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", "sort_recent": "ā´ā´ąāĩā´ąā´ĩāĩā´‚ ā´Ēāĩā´¤ā´ŋā´¯ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹", @@ -2036,6 +1995,7 @@ "stack": "ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ", "stack_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "stack_duplicates": "ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•āĩž ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "stack_select_one_photo": "ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•ā´ŋā´¨ā´žā´¯ā´ŋ ā´’ā´°āĩ ā´Ēāĩā´°ā´§ā´žā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", "stack_selected_photos": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "stacked_assets_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "stacktrace": "ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩā´Ÿāĩā´°āĩ‡ā´¸āĩ", @@ -2044,10 +2004,6 @@ "start_date_before_end_date": "ⴆⴰⴂⴭ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´…ā´ĩā´¸ā´žā´¨ ā´¤āĩ€ā´¯ā´¤ā´ŋā´•āĩā´•āĩ ā´Žāĩā´Žāĩā´Ēā´žā´¯ā´ŋā´°ā´ŋā´•āĩā´•ā´Ŗā´‚", "state": "ⴏⴂⴏāĩā´Ĩā´žā´¨ā´‚", "status": "ā´¨ā´ŋā´˛", - "step_delete": "ⴘⴟāĩā´Ÿā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", - "step_delete_confirm": "ⴈ ⴘⴟāĩā´Ÿā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¤āĩ€āĩŧⴚāĩā´šā´¯ā´žā´Ŗāĩ‹?", - "step_details": "ⴘⴟāĩā´Ÿā´¤āĩā´¤ā´ŋāĩģāĩā´ąāĩ† ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž", - "steps": "ā´Ēⴟā´ŋā´•āĩž", "stop_casting": "ā´•ā´žā´¸āĩā´ąāĩā´ąā´ŋā´‚ā´—āĩ ā´¨ā´ŋāĩŧā´¤āĩā´¤āĩā´•", "stop_motion_photo": "ⴚⴞā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ ā´¨ā´ŋāĩŧā´¤āĩā´¤āĩā´•", "stop_photo_sharing": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´Ēā´™āĩā´•ā´ŋⴟāĩā´¨āĩā´¨ā´¤āĩ ā´¨ā´ŋāĩŧā´¤āĩā´¤ā´Ŗāĩ‹?", @@ -2064,22 +2020,20 @@ "support": "ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗ", "support_and_feedback": "ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´‚ ā´…ā´­ā´ŋā´Ēāĩā´°ā´žā´¯ā´ĩāĩā´‚", "support_third_party_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† Immich ā´‡āĩģā´¸āĩā´ąāĩā´ąā´žā´ŗāĩ‡ā´ˇāĩģ ā´’ā´°āĩ ā´Žāĩ‚ā´¨āĩā´¨ā´žā´‚ ā´•ā´•āĩā´ˇā´ŋ ā´Ēā´žā´•āĩā´•āĩ‡ā´œāĩ ⴚāĩ†ā´¯āĩā´¤ā´¤ā´žā´Ŗāĩ. ā´¨ā´ŋā´™āĩā´™āĩž ā´…ā´¨āĩā´­ā´ĩā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´Ēāĩā´°ā´ļāĩā´¨ā´™āĩā´™āĩž ā´† ā´Ēā´žā´•āĩā´•āĩ‡ā´œāĩ ā´•ā´žā´°ā´Ŗā´Žā´žā´•ā´žā´‚, ā´…ā´¤ā´ŋā´¨ā´žāĩŊ ā´¤ā´žā´´āĩ†ā´¯āĩā´ŗāĩā´ŗ ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´†ā´Ļāĩā´¯ā´‚ ā´…ā´ĩā´°āĩā´Žā´žā´¯ā´ŋ ā´Ēāĩā´°ā´ļāĩā´¨ā´™āĩā´™āĩž ⴉⴍāĩā´¨ā´¯ā´ŋā´•āĩā´•āĩā´•.", - "supporter": "ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´•āĩā´•ā´žā´°āĩģ", "swap_merge_direction": "ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩŊ ā´Ļā´ŋā´ļ ā´Žā´žā´ąāĩā´ąāĩā´•", "sync": "ā´¸ā´ŋā´™āĩā´•āĩ", "sync_albums": "ā´†āĩŊā´Ŧā´™āĩā´™āĩž ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "sync_albums_manual_subtitle": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Žā´˛āĩā´˛ā´ž ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "sync_local": "ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´•ā´Žā´žā´¯ā´ŋ ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "sync_remote": "ā´ąā´ŋā´Žāĩ‹ā´Ÿāĩā´Ÿā´žā´¯ā´ŋ ā´¸ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "sync_status": "ā´¸ā´ŋā´™āĩā´•āĩ ā´¨ā´ŋā´˛", "sync_status_subtitle": "ā´¸ā´ŋā´™āĩā´•āĩ ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´•ā´žā´Ŗāĩā´•, ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "sync_upload_album_setting_subtitle": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ Immich-ā´˛āĩ† ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´†āĩŊā´Ŧā´™āĩā´™ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´ŋ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "system_theme": "ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´¤āĩ€ā´‚", - "system_theme_command_description": "ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´¤āĩ€ā´‚ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´• ({value})", "tag": "ā´Ÿā´žā´—āĩ", "tag_assets": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "tag_created": "{tag} ā´Žā´¨āĩā´¨ ā´Ÿā´žā´—āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•ā´ŋ", - "tag_face": "ā´Žāĩā´–ā´‚ ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "tag_feature_description": "ā´¯āĩā´•āĩā´¤ā´ŋā´¸ā´šā´Žā´žā´¯ ā´Ÿā´žā´—āĩ ā´ĩā´ŋⴎⴝⴙāĩā´™āĩž ā´…ā´¨āĩā´¸ā´°ā´ŋⴚāĩā´šāĩ ā´—āĩā´°āĩ‚ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´Ŧāĩā´°āĩ—ā´¸āĩā´šāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", + "tag_not_found_question": "ā´’ā´°āĩ ā´Ÿā´žā´—āĩ ā´•ā´Ŗāĩā´Ÿāĩ†ā´¤āĩā´¤ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛āĩ‡? ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´Ÿā´žā´—āĩ ⴉ⴪āĩā´Ÿā´žā´•āĩā´•āĩā´•.", "tag_people": "ⴆⴺāĩā´•ā´ŗāĩ† ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "tag_updated": "{tag} ā´Žā´¨āĩā´¨ ā´Ÿā´žā´—āĩ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "tagged_assets": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¤āĩ", @@ -2094,11 +2048,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ā´“ā´°āĩ‹ ā´ĩā´°ā´ŋā´¯ā´ŋā´˛āĩā´‚ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´Ÿāĩ† ā´Žā´Ŗāĩā´Ŗā´‚ ({count})", "theme_setting_colorful_interface_subtitle": "ā´Ēā´ļāĩā´šā´žā´¤āĩā´¤ā´˛ ā´Ēāĩā´°ā´¤ā´˛ā´™āĩā´™ā´ŗā´ŋāĩŊ ā´Ēāĩā´°ā´§ā´žā´¨ ā´¨ā´ŋā´ąā´‚ ā´Ēāĩā´°ā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•.", "theme_setting_colorful_interface_title": "ā´ĩāĩŧā´Ŗāĩā´Ŗā´žā´­ā´Žā´žā´¯ ⴇⴍāĩā´ąāĩŧā´Ģāĩ‡ā´¸āĩ", + "theme_setting_image_viewer_quality_subtitle": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļ ā´‡ā´Žāĩ‡ā´œāĩ ā´ĩāĩā´¯āĩ‚ā´ĩā´ąā´ŋā´¨āĩā´ąāĩ† ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Ž ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´ŋā´•āĩā´•āĩā´•", + "theme_setting_image_viewer_quality_title": "ā´‡ā´Žāĩ‡ā´œāĩ ā´ĩāĩā´¯āĩ‚ā´ĩāĩŧ ā´—āĩā´Ŗā´Žāĩ‡ā´¨āĩā´Ž", "theme_setting_primary_color_subtitle": "ā´Ēāĩā´°ā´§ā´žā´¨ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´™āĩā´™āĩžā´•āĩā´•āĩā´‚ ⴆⴕāĩâ€Œā´¸ā´¨āĩā´ąāĩā´•āĩžā´•āĩā´•āĩā´Žā´žā´¯ā´ŋ ā´’ā´°āĩ ā´¨ā´ŋā´ąā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•.", "theme_setting_primary_color_title": "ā´Ēāĩā´°ā´§ā´žā´¨ ā´¨ā´ŋā´ąā´‚", "theme_setting_system_primary_color_title": "ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´¨ā´ŋā´ąā´‚ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "theme_setting_system_theme_switch": "ā´¯ā´žā´¨āĩā´¤āĩā´°ā´ŋā´•ā´‚ (ā´¸ā´ŋā´¸āĩā´ąāĩā´ąā´‚ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´‚ ā´Ēā´ŋā´¨āĩā´¤āĩā´Ÿā´°āĩā´•)", - "then": "ā´Ēā´ŋā´¨āĩā´¨āĩ†", + "theme_setting_theme_subtitle": "ā´†ā´Ēāĩā´Ēā´ŋā´¨āĩā´ąāĩ† ā´¤āĩ€ā´‚ ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "theme_setting_three_stage_loading_subtitle": "ā´Žāĩ‚ā´¨āĩā´¨āĩ-ⴘⴟāĩā´Ÿ ā´˛āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´•ā´Ÿā´¨ā´‚ ā´ĩāĩŧā´Ļāĩā´§ā´ŋā´Ēāĩā´Ēā´ŋⴚāĩā´šāĩ‡ā´•āĩā´•ā´žā´‚, ā´Ēā´•āĩā´ˇāĩ‡ ā´¨āĩ†ā´ąāĩā´ąāĩâ€Œā´ĩāĩŧā´•āĩā´•āĩ ā´˛āĩ‹ā´Ąāĩ ā´—ā´Ŗāĩā´¯ā´Žā´žā´¯ā´ŋ ā´ĩāĩŧā´Ļāĩā´§ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", + "theme_setting_three_stage_loading_title": "ā´Žāĩ‚ā´¨āĩā´¨āĩ-ⴘⴟāĩā´Ÿ ā´˛āĩ‹ā´Ąā´ŋā´‚ā´—āĩ ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´•āĩā´ˇā´Žā´Žā´žā´•āĩā´•āĩā´•", "they_will_be_merged_together": "ā´…ā´ĩā´¯āĩ† ā´’ā´°āĩā´Žā´ŋⴚāĩā´šāĩ ⴞⴝā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´‚", "third_party_resources": "ā´Žāĩ‚ā´¨āĩā´¨ā´žā´‚ ā´•ā´•āĩā´ˇā´ŋ ā´‰ā´ąā´ĩā´ŋā´Ÿā´™āĩā´™āĩž", "time": "ā´¸ā´Žā´¯ā´‚", @@ -2115,7 +2073,6 @@ "to_select": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•ā´žāĩģ", "to_trash": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "toggle_settings": "ā´•āĩā´°ā´Žāĩ€ā´•ā´°ā´Ŗā´™āĩā´™āĩž ⴟāĩ‹ā´—ā´ŋāĩž ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "toggle_theme_description": "ā´¤āĩ€ā´‚ ⴟāĩ‹ā´—ā´ŋāĩž ⴚāĩ†ā´¯āĩā´¯āĩā´•", "total": "ⴆⴕāĩ†", "total_usage": "ⴆⴕāĩ† ā´‰ā´Ēā´¯āĩ‹ā´—ā´‚", "trash": "ⴟāĩā´°ā´žā´ˇāĩ", @@ -2123,17 +2080,16 @@ "trash_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "trash_count": "ⴟāĩā´°ā´žā´ˇāĩ ({count, number})", "trash_delete_asset": "ā´…ā´¸ā´ąāĩā´ąāĩ ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•/ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", + "trash_emptied": "ⴟāĩā´°ā´žā´ˇāĩ ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•ā´ŋ", "trash_no_results_message": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•ā´ŗāĩā´‚ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•ā´ŗāĩā´‚ ā´‡ā´ĩā´ŋⴟāĩ† ā´•ā´žā´Ŗā´ŋā´•āĩā´•āĩā´‚.", "trash_page_delete_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•āĩā´•", + "trash_page_empty_trash_dialog_content": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´ļāĩ‚ā´¨āĩā´¯ā´Žā´žā´•āĩā´•ā´Ŗāĩ‹? ⴈ ⴇⴍⴙāĩā´™āĩž Immich-āĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", "trash_page_info": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ⴇⴍⴙāĩā´™āĩž {days} ā´Ļā´ŋā´ĩⴏⴙāĩā´™āĩžā´•āĩā´•āĩ ā´ļāĩ‡ā´ˇā´‚ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚", + "trash_page_no_assets": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", + "trash_page_restore_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´Ēāĩā´¨ā´ƒā´¸āĩā´Ĩā´žā´Ēā´ŋā´•āĩā´•āĩā´•", + "trash_page_select_assets_btn": "ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•āĩā´•", + "trash_page_title": "ⴟāĩā´°ā´žā´ˇāĩ ({count})", "trashed_items_will_be_permanently_deleted_after": "ⴟāĩā´°ā´žā´ˇāĩ ⴚāĩ†ā´¯āĩā´¤ ⴇⴍⴙāĩā´™āĩž {days, plural, one {# ā´Ļā´ŋā´ĩⴏⴤāĩā´¤ā´ŋā´¨āĩ} other {# ā´Ļā´ŋā´ĩⴏⴙāĩā´™āĩžā´•āĩā´•āĩ}} ā´ļāĩ‡ā´ˇā´‚ ā´ļā´žā´ļāĩā´ĩā´¤ā´Žā´žā´¯ā´ŋ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ†ā´Ÿāĩā´‚.", - "trigger": "ⴟāĩā´°ā´ŋā´—āĩŧ", - "trigger_asset_metadata_extraction": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´Žā´•āĩâ€Œā´¸āĩâ€Œā´Ÿāĩā´°ā´žā´•āĩā´ˇāĩģ", - "trigger_asset_metadata_extraction_description": "ā´’ā´°āĩ ā´…ā´¸ā´ąāĩā´ąā´ŋāĩģāĩā´ąāĩ† EXIF ā´Žāĩ†ā´ąāĩā´ąā´žā´Ąā´žā´ąāĩā´ą ā´Žā´•āĩâ€Œā´¸āĩâ€Œā´Ÿāĩā´°ā´žā´•āĩâ€Œā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ⴟāĩā´°ā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩâ€Œā´¤āĩ", - "trigger_asset_uploaded": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ", - "trigger_asset_uploaded_description": "ā´’ā´°āĩ ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´¸ā´ąāĩā´ąāĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ⴟāĩā´°ā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¤āĩ", - "trigger_person_recognized": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋ ā´¤ā´ŋā´°ā´ŋⴚāĩā´šā´ąā´ŋā´žāĩā´žāĩ", - "trigger_person_recognized_description": "ā´’ā´°āĩ ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´¤ā´ŋā´°ā´ŋⴚāĩā´šā´ąā´ŋā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ⴟāĩā´°ā´ŋā´—āĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "troubleshoot": "ⴟāĩā´°ā´Ŧā´ŋāĩžā´ˇāĩ‚ā´Ÿāĩā´Ÿāĩ", "type": "ⴤⴰⴂ", "unable_to_change_pin_code": "ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ ā´Žā´žā´ąāĩā´ąā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛", @@ -2148,7 +2104,6 @@ "unhide_person": "ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩ† ā´Žā´ąā´ĩā´ŋāĩŊā´¨ā´ŋā´¨āĩā´¨āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "unknown": "ā´…ā´œāĩā´žā´žā´¤ā´‚", "unknown_country": "ā´…ā´œāĩā´žā´žā´¤ ā´°ā´žā´œāĩā´¯ā´‚", - "unknown_date": "ā´…ā´œāĩā´žā´žā´¤ ā´¤āĩ€ā´¯ā´¤ā´ŋ", "unknown_year": "ā´…ā´œāĩā´žā´žā´¤ ā´ĩāĩŧⴎⴂ", "unlimited": "ā´Ēā´°ā´ŋā´§ā´ŋā´¯ā´ŋā´˛āĩā´˛ā´žā´¤āĩā´¤ā´¤āĩ", "unlink_motion_video": "ⴚⴞā´ŋā´•āĩā´•āĩā´¨āĩā´¨ ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´…āĩēā´˛ā´ŋā´™āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", @@ -2161,11 +2116,10 @@ "unsaved_change": "ā´¸āĩ‡ā´ĩāĩ ⴚāĩ†ā´¯āĩā´¯ā´žā´¤āĩā´¤ ā´Žā´žā´ąāĩā´ąā´‚", "unselect_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "unselect_all_duplicates": "ā´Žā´˛āĩā´˛ā´ž ā´Ąāĩā´¯āĩ‚ā´Ēāĩā´˛ā´ŋā´•āĩā´•āĩ‡ā´ąāĩā´ąāĩā´•ā´ŗāĩā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", + "unselect_all_in": "{group}-ā´˛āĩ† ā´Žā´˛āĩā´˛ā´žā´‚ ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ ā´Žā´žā´ąāĩā´ąāĩā´•", "unstack": "ā´…āĩē-ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "unstack_action_prompt": "{count} ā´Žā´Ŗāĩā´Ŗā´‚ ā´…āĩē-ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "unstacked_assets_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´…āĩē-ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¤āĩ", - "unsupported_field_type": "ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•ā´žā´¤āĩā´¤ ā´Ģāĩ€āĩŊā´Ąāĩ ⴤⴰⴂ", - "unsupported_file_type": "{file} ā´Ģā´¯āĩŊ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯ā´žāĩģ ā´•ā´´ā´ŋā´¯ā´ŋā´˛āĩā´˛ ā´•ā´žā´°ā´Ŗā´‚ ā´…ā´¤ā´ŋāĩģāĩā´ąāĩ† ā´Ģā´¯āĩŊ ⴤⴰⴂ {type} ā´Ēā´ŋā´¨āĩā´¤āĩā´Ŗā´¯āĩā´•āĩā´•āĩā´¨āĩā´¨ā´ŋā´˛āĩā´˛.", "untagged": "ā´Ÿā´žā´—āĩ ⴚāĩ†ā´¯āĩā´¤ā´ŋⴟāĩā´Ÿā´ŋā´˛āĩā´˛", "up_next": "ā´…ā´Ÿāĩā´¤āĩā´¤ā´¤āĩ", "update_location_action_prompt": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ {count} ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗāĩā´Ÿāĩ† ā´¸āĩā´Ĩā´žā´¨ā´‚ ⴇⴤāĩā´Ēā´¯āĩ‹ā´—ā´ŋⴚāĩā´šāĩ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•:", @@ -2173,9 +2127,9 @@ "updated_password": "ā´Ēā´žā´¸āĩâ€Œā´ĩāĩ‡ā´Ąāĩ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "upload": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ", "upload_concurrency": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´•āĩ‹āĩēā´•ā´ąāĩģā´¸ā´ŋ", - "upload_day_count": "{date}: {count, plural, one {# ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ} other {# ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩā´•āĩž}}", "upload_details": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž", - "upload_error_with_count": "{count, plural, one {# ā´…ā´¸ā´ąāĩā´ąāĩ} other {# ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž}} ā´Žā´¨āĩā´¨ā´¤ā´ŋā´¨ā´žā´¯āĩā´ŗāĩā´ŗ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´Ēā´ŋā´ļā´•āĩ", + "upload_dialog_info": "ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´¤āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩ(ā´•āĩž) ā´¸āĩ†āĩŧā´ĩā´ąā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´Ŧā´žā´•āĩā´•ā´Ēāĩā´Ēāĩ ⴚāĩ†ā´¯āĩā´¯ā´Ŗāĩ‹?", + "upload_dialog_title": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "upload_errors": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ {count, plural, one {# ā´Ēā´ŋā´ļā´•āĩ‹ā´Ÿāĩ†} other {# ā´Ēā´ŋā´ļā´•āĩā´•ā´ŗāĩ‹ā´Ÿāĩ†}} ā´Ēāĩ‚āĩŧā´¤āĩā´¤ā´ŋā´¯ā´žā´¯ā´ŋ, ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Ēāĩ‡ā´œāĩ ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•.", "upload_finished": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´•ā´´ā´ŋā´žāĩā´žāĩ", "upload_progress": "ā´ļāĩ‡ā´ˇā´ŋā´•āĩā´•āĩā´¨āĩā´¨ā´¤āĩ {remaining, number} - ā´Ēāĩā´°āĩ‹ā´¸ā´¸āĩā´¸āĩ ⴚāĩ†ā´¯āĩā´¤ā´¤āĩ {processed, number}/{total, number}", @@ -2184,21 +2138,14 @@ "upload_status_errors": "ā´Ēā´ŋā´ļā´•āĩā´•āĩž", "upload_status_uploaded": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", "upload_success": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´ĩā´ŋⴜⴝā´ŋⴚāĩā´šāĩ, ā´Ēāĩā´¤ā´ŋā´¯ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´…ā´¸ā´ąāĩā´ąāĩā´•āĩž ā´•ā´žā´Ŗāĩā´¨āĩā´¨ā´¤ā´ŋā´¨āĩ ā´Ēāĩ‡ā´œāĩ ā´Ēāĩā´¤āĩā´•āĩā´•āĩā´•.", - "upload_to_album_body": "ā´Žā´žā´¨āĩā´ĩāĩŊ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ā´Ģāĩ€ā´šāĩā´šāĩŧ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•ā´žā´¤āĩā´¤ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•āĩžā´•āĩā´•ā´žā´¯ā´ŋ, ā´¨ā´ŋā´™āĩā´™āĩž ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´Ēāĩā´°ā´žā´Ļāĩ‡ā´ļā´ŋā´• ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•ā´žāĩģ ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´‡ā´Ēāĩā´Ēāĩ‹āĩž ā´¤ā´ŋā´°ā´žāĩā´žāĩ†ā´Ÿāĩā´•āĩā´•ā´žā´‚, ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩ‡ā´Ŗāĩā´Ÿā´¤ā´ŋā´˛āĩā´˛, ā´Ēā´ŋā´¨āĩā´¨āĩ€ā´Ÿāĩ ā´’ā´°āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ⴚāĩ‡āĩŧā´•āĩā´•āĩā´•.", - "upload_to_album_title": "ā´’ā´°āĩ ā´†āĩŊā´Ŧā´¤āĩā´¤ā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¨āĩ‡ā´°ā´ŋⴟāĩā´Ÿāĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "upload_to_immich": "Immich-ā´˛āĩ‡ā´•āĩā´•āĩ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´• ({count})", "uploading": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "uploading_media": "ā´Žāĩ€ā´Ąā´ŋā´¯ ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", - "uploads": "ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩā´•āĩž", - "uploads_count": "{count, plural, one {# ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩ} other {# ā´…ā´Ēāĩâ€Œā´˛āĩ‹ā´Ąāĩā´•āĩž}}", "url": "ā´¯āĩā´†āĩŧā´ŽāĩŊ", "usage": "ā´‰ā´Ēā´¯āĩ‹ā´—ā´‚", "use_biometric": "ā´Ŧā´¯āĩ‹ā´Žāĩ†ā´Ÿāĩā´°ā´ŋā´•āĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", - "use_browser_locale": "ā´Ŧāĩā´°āĩ—ā´¸āĩŧ ā´˛āĩŠā´•āĩā´•āĩ‡āĩŊ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", - "use_browser_locale_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Ŧāĩā´°āĩ—ā´¸āĩŧ ā´˛āĩŠā´•āĩā´•āĩ‡ā´˛ā´ŋā´¨āĩ† ā´…ā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´Žā´žā´•āĩā´•ā´ŋ ā´¤āĩ€ā´¯ā´¤ā´ŋā´•ā´ŗāĩā´‚ ā´¸ā´Žā´¯ā´™āĩā´™ā´ŗāĩā´‚ ā´¨ā´Žāĩā´Ēā´ąāĩā´•ā´ŗāĩā´‚ ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "use_current_connection": "ā´¨ā´ŋā´˛ā´ĩā´ŋā´˛āĩ† ā´•ā´Ŗā´•āĩā´ˇāĩģ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "use_custom_date_range": "ā´Ēā´•ā´°ā´‚ ā´•ā´¸āĩā´ąāĩā´ąā´‚ ā´¤āĩ€ā´¯ā´¤ā´ŋ ā´Ēā´°ā´ŋā´§ā´ŋ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", - "use_template": "ⴟāĩ†ā´‚ā´Ēāĩā´˛āĩ‡ā´ąāĩā´ąāĩ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", "user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩāĩ", "user_has_been_deleted": "ⴈ ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ.", "user_id": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´ā´Ąā´ŋ", @@ -2208,6 +2155,7 @@ "user_privacy": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´¸āĩā´ĩā´•ā´žā´°āĩā´¯ā´¤", "user_purchase_settings": "ā´ĩā´žā´™āĩā´™āĩŊ", "user_purchase_settings_description": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´ĩā´žā´™āĩā´™āĩŊ ā´•āĩˆā´•ā´žā´°āĩā´¯ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", + "user_role_set": "{user}-ā´¨āĩ† {role} ⴆⴝā´ŋ ⴏⴜāĩā´œā´Žā´žā´•āĩā´•ā´ŋ", "user_usage_detail": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤āĩƒ ā´‰ā´Ēā´¯āĩ‹ā´— ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´‚", "user_usage_stats": "ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ ā´‰ā´Ēā´¯āĩ‹ā´— ā´¸āĩā´Ĩā´ŋā´¤ā´ŋā´ĩā´ŋā´ĩā´°ā´•āĩā´•ā´Ŗā´•āĩā´•āĩā´•āĩž", "user_usage_stats_description": "ā´…ā´•āĩā´•āĩ—ā´Ŗāĩā´Ÿāĩ ā´‰ā´Ēā´¯āĩ‹ā´— ā´¸āĩā´Ĩā´ŋā´¤ā´ŋā´ĩā´ŋā´ĩā´°ā´•āĩā´•ā´Ŗā´•āĩā´•āĩā´•āĩž ā´•ā´žā´Ŗāĩā´•", @@ -2226,60 +2174,39 @@ "video": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹", "video_hover_setting": "ā´šāĩ‹ā´ĩāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋāĩŊ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "video_hover_setting_description": "ā´Žāĩ—ā´¸āĩ ā´’ā´°āĩ ⴇⴍⴤāĩā´¤ā´ŋā´¨āĩ ā´Žāĩā´•ā´ŗā´ŋā´˛āĩ‚ā´Ÿāĩ† ā´šāĩ‹ā´ĩāĩŧ ⴚāĩ†ā´¯āĩā´¯āĩā´Žāĩā´Ēāĩ‹āĩž ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ⴤⴂā´Ŧāĩā´¨āĩ†ā´¯ā´ŋāĩŊ ā´Ēāĩā´˛āĩ‡ ⴚāĩ†ā´¯āĩā´¯āĩā´•. ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´°ā´šā´ŋā´¤ā´Žā´žā´•āĩā´•ā´ŋā´¯ā´žā´˛āĩā´‚, ā´Ēāĩā´˛āĩ‡ ⴐⴕāĩā´•ā´Ŗā´ŋā´¨āĩ ā´Žāĩā´•ā´ŗā´ŋā´˛āĩ‚ā´Ÿāĩ† ā´šāĩ‹ā´ĩāĩŧ ⴚāĩ†ā´¯āĩā´¤āĩ ā´Ēāĩā´˛āĩ‡ā´Ŧā´žā´•āĩā´•āĩ ⴆⴰⴂⴭā´ŋā´•āĩā´•ā´žāĩģ ā´•ā´´ā´ŋā´¯āĩā´‚.", - "video_quality": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ ā´¨ā´ŋā´˛ā´ĩā´žā´°ā´‚", "videos": "ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž", "videos_count": "{count, plural, one {# ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹} other {# ā´ĩāĩ€ā´Ąā´ŋā´¯āĩ‹ā´•āĩž}}", "view": "ā´•ā´žā´Ŗāĩā´•", "view_album": "ā´†āĩŊā´Ŧā´‚ ā´•ā´žā´Ŗāĩā´•", "view_all": "ā´Žā´˛āĩā´˛ā´žā´‚ ā´•ā´žā´Ŗāĩā´•", "view_all_users": "ā´Žā´˛āĩā´˛ā´ž ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´•āĩā´•ā´ŗāĩ†ā´¯āĩā´‚ ā´•ā´žā´Ŗāĩā´•", - "view_asset_owners": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´‰ā´Ÿā´Žā´•ā´ŗāĩ† ā´•ā´žā´Ŗāĩā´•", "view_details": "ā´ĩā´ŋā´ļā´Ļā´žā´‚ā´ļā´™āĩā´™āĩž ā´•ā´žā´Ŗāĩā´•", "view_in_timeline": "ⴟāĩˆā´‚ā´˛āĩˆā´¨ā´ŋāĩŊ ā´•ā´žā´Ŗāĩā´•", "view_link": "ā´˛ā´ŋā´™āĩā´•āĩ ā´•ā´žā´Ŗāĩā´•", + "view_links": "ā´˛ā´ŋā´™āĩā´•āĩā´•āĩž ā´•ā´žā´Ŗāĩā´•", "view_name": "ā´•ā´žā´Ŗāĩā´•", "view_next_asset": "ā´…ā´Ÿāĩā´¤āĩā´¤ ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´žā´Ŗāĩā´•", "view_previous_asset": "ā´Žāĩā´Žāĩā´Ēā´¤āĩā´¤āĩ† ā´…ā´¸ā´ąāĩā´ąāĩ ā´•ā´žā´Ŗāĩā´•", "view_qr_code": "QR ā´•āĩ‹ā´Ąāĩ ā´•ā´žā´Ŗāĩā´•", "view_similar_photos": "ā´¸ā´Žā´žā´¨ā´Žā´žā´¯ ā´Ģāĩ‹ā´Ÿāĩā´Ÿāĩ‹ā´•āĩž ā´•ā´žā´Ŗāĩā´•", "view_stack": "ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ā´•ā´žā´Ŗāĩā´•", + "view_user": "ā´‰ā´Ēā´¯āĩ‹ā´•āĩā´¤ā´žā´ĩā´ŋā´¨āĩ† ā´•ā´žā´Ŗāĩā´•", "viewer_remove_from_stack": "ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•ā´ŋāĩŊ ā´¨ā´ŋā´¨āĩā´¨āĩ ā´¨āĩ€ā´•āĩā´•ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•", - "visibility": "ā´Ļāĩƒā´ļāĩā´¯ā´Ēā´°ā´¤", + "viewer_stack_use_as_main_asset": "ā´Ēāĩā´°ā´§ā´žā´¨ ā´…ā´¸ā´ąāĩā´ąā´žā´¯ā´ŋ ā´‰ā´Ēā´¯āĩ‹ā´—ā´ŋā´•āĩā´•āĩā´•", + "viewer_unstack": "ā´…āĩē-ā´¸āĩā´ąāĩā´ąā´žā´•āĩā´•āĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•", "visibility_changed": "{count, plural, one {# ā´ĩāĩā´¯ā´•āĩā´¤ā´ŋā´¯āĩā´Ÿāĩ†} other {# ⴆⴺāĩā´•ā´ŗāĩā´Ÿāĩ†}} ā´Ļāĩƒā´ļāĩā´¯ā´¤ ā´Žā´žā´ąāĩā´ąā´ŋ", - "visual": "ā´ĩā´ŋā´ˇāĩā´ĩāĩŊ", "waiting": "ā´•ā´žā´¤āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ", - "waiting_count": "ā´•ā´žā´¤āĩā´¤ā´ŋā´°ā´ŋā´•āĩā´•āĩā´¨āĩā´¨āĩ: {count}", "warning": "ā´Žāĩā´¨āĩā´¨ā´ąā´ŋā´¯ā´ŋā´Ēāĩā´Ēāĩ", "week": "ⴆⴴāĩā´š", "welcome": "ā´¸āĩā´ĩā´žā´—ā´¤ā´‚", "welcome_to_immich": "Immich-ā´˛āĩ‡ā´•āĩā´•āĩ ā´¸āĩā´ĩā´žā´—ā´¤ā´‚", - "whats_new": "ā´Ēāĩā´¤ā´ŋⴝⴤāĩ†ā´¨āĩā´¤ā´žā´Ŗāĩ", - "whats_new_settings_subtitle": "ā´‡ā´Žāĩā´Žā´ŋⴚāĩā´šā´ŋāĩŊ ā´Žā´¨āĩā´¤ā´žā´Ŗāĩ ā´Ēāĩā´¤ā´ŋⴝⴤāĩ†ā´¨āĩā´¨āĩ ā´•ā´žā´Ŗāĩā´•", - "whats_new_version": "ā´Ēā´¤ā´ŋā´Ēāĩā´Ēāĩ {version}", - "when": "ā´Žā´Ēāĩā´Ēāĩ‡ā´žāĩž", - "width": "ā´ĩāĩ€ā´¤ā´ŋ", "wifi_name": "ā´ĩāĩˆ-ā´Ģāĩˆā´¯āĩā´Ÿāĩ† ā´Ēāĩ‡ā´°āĩ", - "workflow": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹", - "workflow_delete_prompt": "ⴈ ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¤āĩ€āĩŧⴚāĩā´šā´¯ā´žā´Ŗāĩ‹?", - "workflow_deleted": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴇⴞāĩā´˛ā´žā´¤ā´žā´•āĩā´•ā´ŋ", - "workflow_description": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´ĩā´ŋā´ĩā´°ā´Ŗā´‚", - "workflow_info": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´ĩā´ŋā´ĩā´°ā´‚", - "workflow_json": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ JSON", - "workflow_json_help": "JSON ā´Ģāĩ‹āĩŧā´Žā´žā´ąāĩā´ąā´ŋāĩŊ ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´•āĩ‹āĩēā´Ģā´ŋā´—ā´ąāĩ‡ā´ˇāĩģ ā´Žā´Ąā´ŋā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´•. ā´Žā´žā´ąāĩā´ąā´™āĩā´™āĩž ā´ĩā´ŋā´ˇāĩā´ĩāĩŊ ā´Ŧā´ŋāĩŊā´Ąā´ąāĩā´Žā´žā´¯ā´ŋ ā´¸ā´Žā´¨āĩā´ĩā´¯ā´ŋā´Ēāĩā´Ēā´ŋā´•āĩā´•āĩā´‚.", - "workflow_name": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´Ēāĩ‡ā´°āĩ", - "workflow_navigation_prompt": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´Žā´žā´ąāĩā´ąā´™āĩā´™āĩž ⴏⴂⴰⴕāĩā´ˇā´ŋā´•āĩā´•ā´žā´¤āĩ† ā´Ēāĩ‹ā´•ā´Ŗā´Žāĩ†ā´¨āĩā´¨āĩ ā´¤āĩ€āĩŧⴚāĩā´šā´¯ā´žā´Ŗāĩ‹?", - "workflow_summary": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴏⴂⴗāĩā´°ā´šā´‚", - "workflow_templates": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ⴟāĩ†ā´‚ā´Ēāĩā´˛āĩ‡ā´ąāĩā´ąāĩā´•āĩž", - "workflow_update_success": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ ā´ĩā´ŋā´œā´¯ā´•ā´°ā´Žā´žā´¯ā´ŋ ā´…ā´Ēāĩā´Ąāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¤āĩ", - "workflows": "ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ā´•āĩž", - "workflows_help_text": "ⴟāĩā´°ā´ŋā´—ā´ąāĩā´•ā´ŗāĩā´‚ ā´Ģā´ŋāĩŊⴟāĩā´Ÿā´ąāĩā´•ā´ŗāĩā´‚ ā´…ā´Ÿā´ŋā´¸āĩā´Ĩā´žā´¨ā´Žā´žā´•āĩā´•ā´ŋ ā´ĩāĩŧā´•āĩā´•āĩā´Ģāĩā´˛āĩ‹ā´•āĩž ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´…ā´¸ā´ąāĩā´ąāĩā´•ā´ŗā´ŋā´˛āĩ† ā´Ēāĩā´°ā´ĩāĩŧā´¤āĩā´¤ā´¨ā´™āĩā´™āĩž ā´“ā´Ÿāĩā´Ÿāĩ‹ā´Žāĩ‡ā´ąāĩā´ąāĩ ⴚāĩ†ā´¯āĩā´¯āĩā´¨āĩā´¨āĩ", "wrong_pin_code": "ā´¤āĩ†ā´ąāĩā´ąā´žā´¯ ā´Ēā´ŋāĩģ ā´•āĩ‹ā´Ąāĩ", - "x_of_total": "{x}/{total}", "year": "ā´ĩāĩŧⴎⴂ", "years_ago": "{years, plural, one {# ā´ĩāĩŧⴎⴂ} other {# ā´ĩāĩŧⴎⴙāĩā´™āĩž}} ā´Žāĩā´Žāĩā´Ēāĩ", "yes": "ā´…ā´¤āĩ†", "you_dont_have_any_shared_links": "ā´¨ā´ŋā´™āĩā´™āĩžā´•āĩā´•āĩ ā´Ēā´™āĩā´•ā´ŋⴟāĩā´Ÿ ā´˛ā´ŋā´™āĩā´•āĩā´•ā´ŗāĩŠā´¨āĩā´¨āĩā´Žā´ŋā´˛āĩā´˛", "your_wifi_name": "ā´¨ā´ŋā´™āĩā´™ā´ŗāĩā´Ÿāĩ† ā´ĩāĩˆ-ā´Ģāĩˆā´¯āĩā´Ÿāĩ† ā´Ēāĩ‡ā´°āĩ", - "zero_to_clear_rating": "ā´…ā´¸ā´ąāĩā´ąāĩ ā´ąāĩ‡ā´ąāĩā´ąā´ŋā´‚ā´—āĩ ā´Žā´žā´¯āĩâ€Œā´•āĩā´•ā´žāĩģ 0 ā´…ā´Žāĩŧā´¤āĩā´¤āĩā´•", - "zoom_image": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´ĩā´˛āĩā´¤ā´žā´•āĩā´•āĩā´•" + "zoom_image": "ⴚā´ŋā´¤āĩā´°ā´‚ ā´ĩā´˛āĩā´¤ā´žā´•āĩā´•āĩā´•", + "zoom_to_bounds": "ā´Ēā´°ā´ŋā´§ā´ŋā´•ā´ŗā´ŋā´˛āĩ‡ā´•āĩā´•āĩ ā´¸āĩ‚ā´‚ ⴚāĩ†ā´¯āĩā´¯āĩā´•" } diff --git a/i18n/mn.json b/i18n/mn.json index f8cbe282f5..fb87e18f48 100644 --- a/i18n/mn.json +++ b/i18n/mn.json @@ -5,131 +5,74 @@ "acknowledge": "ОйĐģĐŗĐžĐģОО", "action": "ŌŽĐšĐģĐ´ŅĐģ", "action_common_update": "ШиĐŊŅŅ‡ĐģŅŅ…", - "action_description": "Đ¨Ō¯Ō¯ĐŗĐ´ŅŅĐŊ ĐˇŌ¯ĐšĐģҁ Đ´ŅŅŅ€ Ņ…Đ¸ĐšŅ… Ō¯ĐšĐģĐ´ŅĐģŌ¯Ō¯Đ´", - "actions": "ŌŽĐšĐģ аĐļиĐģĐģĐ°ĐŗĐ°Đ°", + "actions": "ŌŽĐšĐģĐ´ĐģŌ¯Ō¯Đ´", "active": "Đ˜Đ´ŅĐ˛Ņ…Ņ‚ŅĐš", - "active_count": "Đ˜Đ´ŅĐ˛Ņ…Ņ‚ŅĐš: {count}", - "activity": "ŌŽĐšĐģĐ´ĐģŌ¯Ō¯Đ´", + "activity": "ŌŽĐšĐģĐ´ĐģиКĐŊ ĐąŌ¯Ņ€Ņ‚ĐŗŅĐģ", + "activity_changed": "ŌŽĐšĐģĐ´ĐģиКĐŊ ĐąŌ¯Ņ€Ņ‚ĐŗŅĐģ {enabled, select, true {Đ¸Đ´ŅĐ˛Ņ…Ņ‚ŅĐš} other {Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš}}", "add": "ĐŅĐŧŅŅ…", "add_a_description": "ĐĸаКĐģĐąĐ°Ņ€ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", "add_a_location": "Đ‘Đ°ĐšŅ€ŅˆĐ¸Đģ ĐŊŅĐŧŅŅ…", - "add_a_name": "ĐŅŅ€ ĐŊŅĐŧŅŅ…", - "add_a_title": "Đ“Đ°Ņ€Ņ‡Đ¸Đŗ ĐŊŅĐŧŅŅ…", - "add_action": "ŌŽĐšĐģĐ´ŅĐģ ĐŊŅĐŧŅŅ…", - "add_assets": "Đ—Ō¯ĐšĐģҁ ĐŊŅĐŧŅŅ…", - "add_birthday": "ĐĸĶŠŅ€ŅĶŠĐŊ ĐžĐŗĐŊОО ĐŊŅĐŧŅŅ…", - "add_endpoint": "ĐĨĐ°ŅĐŗ ĐŊŅĐŧŅŅ…", - "add_exclusion_pattern": "ĐĨĐ°ŅĐ°ĐŗĐ´Đ°Ņ… ĐˇĐ°ĐŗĐ˛Đ°Ņ€ ĐŊŅĐŧŅŅ…", + "add_a_name": "ĐŅŅ€ ĶŠĐŗĶŠŅ…", + "add_a_title": "Đ“Đ°Ņ€Ņ‡Đ¸Đŗ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", + "add_endpoint": "Endpoint ĐŊŅĐŧŅŅ…", "add_location": "Đ‘Đ°ĐšŅ€ŅˆĐ¸Đģ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", + "add_more_users": "Ķ¨ĶŠŅ€ Ņ…ŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸Đ´ ĐŊŅĐŧŅŅ…", "add_partner": "ĐĨаĐŧŅ‚Ņ€Đ°ĐŗŅ‡ ĐŊŅĐŧŅŅ…", + "add_path": "ЗаĐŧ ĐŊŅĐŧŅŅ…", "add_photos": "Đ—ŅƒŅ€Đ°Đŗ ĐŊŅĐŧŅŅ…", - "add_step": "АĐģŅ…Đ°Đŧ ĐŊŅĐŧŅŅ…", "add_tag": "Đ¨ĐžŅˆĐŗĐž ĐŊŅĐŧŅŅ…", - "add_to": "ĐŅĐŧŅŅ…â€Ļ", "add_to_album": "ĐĻĐžĐŧĐžĐŗŅ‚ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", "add_to_album_bottom_sheet_added": "{album}-Đ´ ĐŊŅĐŧĐģŅŅ", "add_to_album_bottom_sheet_already_exists": "{album}-Đ´ аĐģҌ Ņ…ŅĐ´Đ¸ĐšĐŊ ĐžŅ€ŅĐžĐŊ йаКĐŊа", - "add_to_album_bottom_sheet_some_local_assets": "Đ—Đ°Ņ€Đ¸Đŧ ŅŅƒŅƒŅ€Đ¸ĐŊ ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ Ņ†ĐžĐŧĐžĐŗŅ‚ ĐŊŅĐŧĐļ Ņ‡Đ°Đ´ŅĐ°ĐŊĐŗŌ¯Đš", - "add_to_albums": "ĐĻĐžĐŧĐŗŅƒŅƒĐ´Đ°Đ´ ĐŊŅĐŧŅŅ…", - "add_to_albums_count": "ĐĻĐžĐŧĐŗŅƒŅƒĐ´Đ°Đ´ ĐŊŅĐŧŅŅ… {count}", - "add_to_bottom_bar": "ĐŅĐŧŅŅ…", - "add_upload_to_stack": "ĐžŅ€ŅƒŅƒĐģаад ĐąŌ¯ĐģŅĐŗŅ‚ ĐŊŅĐŧŅŅ…", + "add_to_shared_album": "ĐŅŅĐģŅ‚Ņ‚ŅĐš аĐģĐąŅƒĐŧĐ´ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", "add_url": "URL ĐŊŅĐŧŅŅ…", "added_to_archive": "ĐŅ€Ņ…Đ¸Đ˛Đ´ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", - "added_to_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģҁ-Đ´ ĐŊŅĐŧŅŅ…", - "added_to_favorites_count": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģҁ-Đ´ {count, number} ĐŊŅĐŧŅĐŗĐ´ĐģŅŅ", + "added_to_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€ĐŗĐ°ĐŊĐ´ ĐŊŅĐŧŅŅ…", + "added_to_favorites_count": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Đ°Đ´ {count, number} ĐŊŅĐŧŅĐŗĐ´ĐģŅŅ", "admin": { - "add_exclusion_pattern_description": "ŌŽĐģ Ņ‚ĐžĐžĐŧŅĐžŅ€ĐģĐžŅ… ĐˇĐ°ĐŗĐ˛Đ°Ņ€Ņ‹Đŗ ĐŊŅĐŧŅŅ…. Đ”Đ°Ņ€Đ°Đ°Ņ… Ņ‚ŅĐŧĐ´ŅĐŗ ĐžŅ€Đģ҃҃ĐģаĐģŅ‚Ņ‹Đŗ Đ´ŅĐŧĐļиĐŊŅ *,** йа ?. \"Raw\" ĐŊŅŅ€Ņ‚ŅĐš Đ´ŅƒŅ€Ņ‹ĐŊ ŅĐ°ĐŊ Đ´Đ°Ņ…ŅŒ ĐąŌ¯Ņ… Ņ„Đ°ĐšĐģŅ‹Đŗ Ō¯Đģ Ņ‚ĐžĐžĐŧŅĐžŅ€ĐģĐžŅ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ \"**/Raw/**\" ĐŗŅĐļ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ°Ņ€Đ°Đš. \".tif\"-ŅŅŅ€ Ņ‚ĶŠĐŗŅŅĶŠĐŊ ĐąŌ¯Ņ… Ņ„Đ°ĐšĐģŅ‹Đŗ Ō¯Đģ Ņ‚ĐžĐžĐŧŅĐžŅ€ĐģĐžŅ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ \"**/*.tif\" ĐŗŅĐļ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ°Ņ€Đ°Đš. ĐĐąŅĐžĐģŅŽŅ‚ СаĐŧŅ‹Đŗ Ō¯Đģ Ņ‚ĐžĐžĐŧŅĐžŅ€ĐģĐžŅ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ \"/path/to/ignore/**\" ĐŗŅĐļ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ°Ņ€Đ°Đš.", "admin_user": "АдĐŧиĐŊ Ņ…ŅŅ€ŅĐŗĐģŅĐŗŅ‡", - "asset_offline_description": "Đ­ĐŊŅŅ…Ō¯Ō¯ ĐŗĐ°Đ´ĐŊĐ°Ņ… ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ ĐˇŌ¯ĐšĐģҁ Đ´Đ¸ŅĐēĐŊŅŅŅ ĐžĐģĐ´ĐžŅ…ĐžĐž йОĐģŅŒŅĐžĐŊ ĐąĶŠĐŗĶŠĶŠĐ´ Ņ…ĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ Ņ€ŅƒŅƒ ĐˇĶŠĶŠĐŗĐ´ŅĶŠĐŊ йаКĐŊа. ĐĨŅŅ€ŅĐ˛ Ņ„Đ°ĐšĐģŅ‹Đŗ ŅĐ°ĐŊ Đ´ĐžŅ‚Ņ€ĐžĐž ĐˇĶŠĶŠŅĶŠĐŊ йОĐģ Ņ…Đ°Ņ€ĐŗĐ°ĐģĐˇĐ°Ņ… ŅˆĐ¸ĐŊŅ ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ ĐžĐŗĐŊООĐŊ Ņ…ŅƒĐ˛Đ°Đ°Ņ€Đ°Đ°Ņ ŅˆĐ°ĐģĐŗĐ°ĐŊа ҃҃. Đ­Đ´ĐŗŅŅŅ€ ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ ŅŅŅ€ĐŗŅŅŅ…Đ¸ĐšĐŊ Ņ‚ŅƒĐģĐ´, Immich ĐŊҌ Đ´ĐžĐžŅ€Ņ… Ņ„Đ°ĐšĐģŅ‹ĐŊ СаĐŧĐ´ Ņ…Đ°ĐŊдаĐļ Ņ‡Đ°Đ´Đ°Đļ ĐąĐ°ĐšĐŗĐ°Đ° ŅŅŅŅ…Đ¸ĐšĐŗ ŅˆĐ°ĐģĐŗĐ°Đ°Đ´ ŅĐ°ĐŊĐŗ Đ´Đ°Ņ…Đ¸ĐŊ ҁĐēаĐŊĐŊĐĩŅ€Đ´Đ°ĐŊа ҃҃.", - "authentication_settings": "Đ‘Đ°Ņ‚Đ°ĐģĐŗĐ°Đ°Đļ҃҃ĐģаĐģ҂ҋĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "authentication_settings_description": "ĐŅƒŅƒŅ† Ō¯ĐŗĐ¸ĐšĐŊ ŅƒĐ´Đ¸Ņ€Đ´ĐģĐ°ĐŗĐ°, OAuth, йОĐģĐžĐŊ ĐąŅƒŅĐ°Đ´ ĐąĐ°Ņ‚Đ°ĐģĐŗĐ°Đ°Đļ҃҃ĐģаĐģ҂ҋĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "authentication_settings_disable_all": "Đ‘Ō¯Ņ… ĐŊŅĐ˛Ņ‚Ņ€ŅŅ… Đ°Ņ€ĐŗŅƒŅƒĐ´Ņ‹Đŗ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐŗŌ¯Đš йОĐģĐŗĐžŅ…Đ´ĐžĐž Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃? ĐŅĐ˛Ņ‚Ņ€ŅĐģŅ‚ ĐąŌ¯Ņ€ŅĐŊ Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŊĐž.", - "authentication_settings_reenable": "Đ”Đ°Ņ…Đ¸ĐŊ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…ŅĐ´ Đ´Đ°Ņ€Đ°Đ°Ņ… ĐĄĐĩŅ€Đ˛ĐĩŅ€Đ¸ĐšĐŊ ĐēĐžĐŧĐŧаĐŊĐ´Ņ‹Đŗ Đ°ŅˆĐ¸ĐŗĐģаĐŊа.", - "background_task_job": "ĐŅ€Ņ‹ĐŊ ĐŋŅ€ĐžŅ†Đĩҁҁ", + "authentication_settings": "ĐĸаĐŊиĐŊ ĐŊŅĐ˛Ņ‚Ņ€ŅĐģŅ‚ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", + "authentication_settings_description": "ĐŅƒŅƒŅ† Ō¯ĐŗĐ¸ĐšĐŊ ŅƒĐ´Đ¸Ņ€Đ´ĐģĐ°ĐŗĐ°, OAuth йОĐģĐžĐŊ ĐąŅƒŅĐ°Đ´ Ņ‚Đ°ĐŊиĐŊ ĐŊŅĐ˛Ņ‚Ņ€ŅĐģŅ‚Đ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", + "authentication_settings_disable_all": "Đ‘Ō¯Ņ… ĐŊŅĐ˛Ņ‚Ņ€ŅŅ… Đ°Ņ€ĐŗŅƒŅƒĐ´Ņ‹Đŗ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐŗŌ¯Đš йОĐģĐŗĐžŅ…Đ´ĐžĐž Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃? ĐŅĐ˛Ņ‚Ņ€ŅŅ… Ō¯ĐšĐģĐ´ŅĐģ ĐąŌ¯Ņ€ŅĐŊ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐŗŌ¯Đš йОĐģĐŊĐž.", + "background_task_job": "ĐŅ€Ņ‹ĐŊ аĐļиĐģ", "backup_database": "Ķ¨ĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ даĐŧĐŋ Ō¯Ō¯ŅĐŗŅŅ…", - "backup_database_enable_description": "Ķ¨ĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ даĐŧĐŋ-Đŗ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐļŌ¯Ō¯ĐģŅŅ…", - "backup_keep_last_amount": "͍ĐŧĐŊĶŠŅ… даĐŧĐŋŅƒŅƒĐ´Ņ‹Đŗ Ņ…Đ°Đ´ĐŗĐ°ĐģĐ°Ņ… Ņ‚ĐžĐž", - "backup_onboarding_1_description": "ĐŊŅĐŧŅĐģŅ‚ŅŅŅ€ ĐŊĶŠĶŠŅ† Ņ…ŅƒŅƒĐģĐąĐ°Ņ€Ņ‹Đŗ Ō¯Ō¯ĐģŅĐŊĐ´ ĐąŅƒŅŽŅƒ ĶŠĶŠŅ€ Ņ„Đ¸ĐˇĐ¸Đē ĐąĐ°ĐšŅ€ŅˆĐ¸ĐģĐ´ Ņ…Đ°Đ´ĐŗĐ°ĐģĐ°Ņ….", - "backup_onboarding_2_description": "Ō¯ĐŊĐ´ŅŅĐŊ Ņ„Đ°ĐšĐģŅƒŅƒĐ´ йОĐģĐžĐŊ Ņ‚ŅĐ´ĐŗŅŅŅ€Đ¸ĐšĐŊ ŅŅƒŅƒŅ€Đ¸ĐŊ Ņ…ŅƒŅƒĐģĐąĐ°Ņ€Ņ‹Đŗ Ņ…ĐžŅ‘Ņ€ ĶŠĶŠŅ€ Ņ‚ĶŠŅ€ĐģиКĐŊ Ņ…Đ°Đ´ĐŗĐ°ĐģĐ°ĐŗŅ‡ Đ´ŅŅŅ€ Ņ…Đ°Đ´ĐŗĐ°ĐģĐ°Ņ….", - "backup_onboarding_3_description": "ĶŠĐŗĶŠĐŗĐ´ĐģŌ¯Ō¯Đ´Đ¸ĐšĐŊ ĐŗŅƒŅ€Đ˛Đ°ĐŊ Ņ…ŅƒŅƒĐģĐąĐ°Ņ€Ņ‹Đŗ Ō¯Ō¯ŅĐŗŅŅ…, ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ Ņ„Đ°ĐšĐģŅƒŅƒĐ´Ņ‹Đŗ ĐžŅ€ŅƒŅƒĐģаад: 1 ĐŗĐ°Đ´ĐŊа йОĐģĐžĐŊ 2 ŅŅƒŅƒŅ€Đ¸ĐŊ Ņ…ŅƒŅƒĐģĐąĐ°Ņ€.", - "backup_onboarding_description": "ĐĐ°ĐšĐ´Đ˛Đ°Ņ€Ņ‚Đ°Đš Ņ…Đ°ĐŧĐŗĐ°Đ°ĐģĐ°Ņ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ 3-2-1 ĐŗŅĐŗĐ´ŅŅ… ĶŠĐŗĶŠĐŗĐ´ĐģиКĐŊ ĐŊĶŠĶŠŅ†ĐģĶŠĐģиКĐŊ ŅŅ‚Ņ€Đ°Ņ‚ĐĩĐŗĐ¸ĐšĐŗ Đ°ŅˆĐ¸ĐŗĐģĐ°Ņ…Ņ‹Đŗ ĐˇĶŠĐ˛ĐģĶŠĐļ йаКĐŊа. ĐžŅ€ŅƒŅƒĐģŅĐ°ĐŊ ĐˇŅƒŅ€Đ°Đŗ йОĐģĐžĐŊ ĐąĐ¸Ņ‡ĐģŅĐŗŌ¯Ō¯Đ´ ĐŧĶŠĐŊ Immich иКĐŊ ĶŠĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ Ņ…ŅƒŅƒĐģĐąĐ°Ņ€Ņ‹Đŗ Ō¯Ō¯ŅĐŗŅĐļ ĐąĐ°ĐšĐŗĐ°Đ°Ņ€Đ°Đš.", - "backup_onboarding_footer": "Immich-Đ¸ĐšĐŗ ĐŊĶŠĶŠŅ†ĐģĶŠŅ… Ņ‚Đ°ĐģĐ°Đ°Ņ€ Đ´ŅĐģĐŗŅŅ€ŅĐŊĐŗŌ¯Đš ĐŧŅĐ´ŅŅĐģĐģĐ¸ĐšĐŗ ĐąĐ°Ņ€Đ¸ĐŧŅ‚ ĐąĐ¸Ņ‡Đ¸Đŗ-ҁ Ō¯ĐˇĐŊŅ Ō¯Ō¯.", - "backup_onboarding_parts_title": "A 3-2-1 ĐŊĶŠĶŠŅ†ĐģĶŠĐģ ĐŗŅĐ´ŅĐŗŅ‚:", - "backup_onboarding_title": "ĐĶŠĶŠŅ†ĐģĶŠĐģŅ‚Ō¯Ō¯Đ´", - "backup_settings": "Ķ¨ĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ даĐŧĐŋ-Ņ‹ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "backup_settings_description": "Ķ¨ĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ даĐŧĐŋŅ‹ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅƒŅƒĐ´.", - "cleared_jobs": "Đ”Đ°Ņ€Đ°Đ°Ņ… аĐļиĐģĐąĐ°Ņ€ŅƒŅƒĐ´Ņ‹Đŗ Ņ†ŅĐ˛ŅŅ€ĐģŅĐ˛: {job}", - "config_set_by_file": "ĐĸĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅ‹ Ņ„Đ°ĐšĐģŅ‹ĐŊ Ņ‚ŅƒŅĐģаĐŧĐļŅ‚Đ°ĐšĐŗĐ°Đ°Ņ€ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģŅĐ°ĐŊ", - "confirm_delete_library": "Đĸа {library} ŅĐ°ĐŊĐŗ ŅƒŅŅ‚ĐŗĐ°Ņ…Đ´Đ°Đ° Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃?", - "confirm_delete_library_assets": "Đĸа ŅĐŊŅ ŅĐ°ĐŊĐŗ ŅƒŅŅ‚ĐŗĐ°Ņ…Đ´Đ°Đ° Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃? Đ­ĐŊŅ Ō¯ĐšĐģĐ´ĐģŅŅŅ€ Ņ‚Đ°ĐŊŅ‹ {count, plural, one {# contained asset} other {all # contained assets}} ҁĐĩŅ€Đ˛ĐĩŅ€ŅŅŅ ĐąŅƒŅ†Đ°ĐģŅ‚ĐŗŌ¯Đš ŅƒŅŅ‚Đ°Ņ… йа ĐąŅƒŅ†Đ°Đ°Ņ… йОĐģĐžĐŧĐļĐŗŌ¯Đš. Đ“ŅŅ…Đ´ŅŅ Ņ„Đ°ĐšĐģŅƒŅƒĐ´ Đ´Đ¸ŅĐē Đ´ŅŅŅ€ŅŅ Ō¯ĐģĐ´ŅĐŊŅ.", - "confirm_email_below": "Đ‘Đ°Ņ‚Đ°ĐģĐŗĐ°Đ°Đļ҃҃ĐģĐ°Ņ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ \"{email}\" ĐąĐ¸Ņ‡ĐŊŅ Ō¯Ō¯", - "confirm_reprocess_all_faces": "Đ‘Ō¯Ņ… ĐŊԝԝҀ Ņ†Đ°Ņ€Đ°ĐšĐŊĐ´ Đ´Đ°Ņ…Đ¸ĐŊ йОĐģĐžĐ˛ŅŅ€ŅƒŅƒĐģаĐģŅ‚ Ņ…Đ¸ĐšŅ… Ō¯Ō¯? Đ­ĐŊŅ ĐŊҌ Ņ…Ō¯ĐŧŌ¯Ō¯ŅĐ¸ĐšĐŊ ĶŠĐŧĐŊĶŠŅ… ĐąŌ¯Ņ… ĐŊŅŅ€ŅĐ¸ĐšĐŗ ŅƒŅŅ‚ĐŗĐ°ĐŊа.", + "backup_database_enable_description": "Ķ¨ĐŗĶŠĐŗĐ´ĐģиКĐŊ ŅĐ°ĐŊĐŗĐ¸ĐšĐŊ даĐŧĐŋ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐļŌ¯Ō¯ĐģŅŅ…", + "backup_keep_last_amount": "͍ĐŧĐŊĶŠŅ… Ņ…ŅĐ´ŅĐŊ даĐŧĐŋŅ‹Đŗ Ņ…Đ°Đ´ĐŗĐ°ĐģĐ°Ņ… Đ˛Ņ", + "backup_settings": "Đ”Đ°Ņ‚Đ°ĐąĐ°Đˇ даĐŧĐŋ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", + "backup_settings_description": "Đ”Đ°Ņ‚Đ°ĐąĐ°ĐˇĐ°Đ°Ņ даĐŧĐŋ Ņ…Đ¸ĐšŅ… Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅƒŅƒĐ´.", + "config_set_by_file": "ĐĸĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ ĐžĐ´ĐžĐžĐŗĐžĐžŅ€ Ņ„Đ°ĐšĐģĐ°Đ°Ņ Đ°Đ˛Ņ‡ йаКĐŊа", + "confirm_delete_library": "Đĸа {library} ĐŗŅŅŅĐŊ ŅĐ°ĐŊĐŗ ŅƒŅŅ‚ĐŗĐ°Ņ…Đ´Đ°Đ° Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃?", + "confirm_delete_library_assets": "Đĸа ŅĐŊŅ ŅĐ°ĐŊĐŗ ŅƒŅŅ‚ĐŗĐ°Ņ…Đ´Đ°Đ° Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃? Đ­ĐŊŅ Ō¯ĐšĐģĐ´ĐģŅŅŅ€ Ņ‚Đ°ĐŊŅ‹ {count, plural, one {# contained asset} other {all # contained assets}} ҁĐĩŅ€Đ˛ĐĩŅ€ŅŅŅ ŅƒŅŅ‚Đ°Ņ… ĐąĶŠĐŗĶŠĶŠĐ´ ĐąŅƒŅ†Đ°Đ°Ņ… йОĐģĐžĐŧĐļĐŗŌ¯Đš. Đ“ŅŅ…Đ´ŅŅ Ņ„Đ°ĐšĐģŅƒŅƒĐ´ Đ´Đ¸ŅĐē Đ´ŅŅŅ€ŅŅ Ō¯ĐģĐ´ŅĐŊŅ.", + "confirm_email_below": "Đ‘Đ°Ņ‚Đ°ĐģĐŗĐ°Đ°Đļ҃҃ĐģĐ°Ņ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ Ņ‚Đ° \"{email}\" ĐŗŅĐļ ĐąĐ¸Ņ‡ĐŊŅ Ō¯Ō¯", + "confirm_reprocess_all_faces": "Đ‘Ō¯Ņ… Ņ†Đ°Ņ€Đ°ĐšĐŗ Đ´Đ°Ņ…Đ¸ĐŊ ĐŋŅ€ĐžŅ†Đĩҁҁ Ņ…Đ¸ĐšŅ… Ō¯Ō¯? ĐĸŅĐŗĐ˛ŅĐģ ĐąŌ¯Ņ… ĐŊŅŅ€Ņ Đ°Ņ€Đ¸ĐģĐ°Ņ… йОĐģĐŊĐž.", "confirm_user_password_reset": "{user}-иКĐŊ ĐŊŅƒŅƒŅ† Ō¯ĐŗĐ¸ĐšĐŗ Đ´Đ°Ņ…Đ¸ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°Ņ… ҃҃?", "confirm_user_pin_code_reset": "{user} Ņ…ŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŊ PIN code Đ´Đ°Ņ…Đ¸ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°Ņ… ҃҃?", - "copy_config_to_clipboard_description": "ĐžĐ´ĐžĐžĐŗĐ¸ĐšĐŊ ŅĐ¸ŅŅ‚ĐĩĐŧиКĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ JSON object Ņ…ŅĐģĐąŅŅ€ŅŅŅ€ clipboard Ņ€ŅƒŅƒ Ņ…ŅƒŅƒĐģĐ°Ņ…", - "create_job": "аĐļиĐģĐąĐ°Ņ€ Ō¯Ō¯ŅĐŗŅŅ…", - "cron_expression": "ĐĨŅƒĐ˛Đ¸Đ°Ņ€ŅŒ (cron)", - "cron_expression_description": "cron Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ‹Đŗ Đ°ŅˆĐ¸ĐŗĐģаĐŊ ҁĐēаĐŊ Ņ…Đ¸ĐšŅ… иĐŊŅ‚ĐĩŅ€Đ˛Đ°ĐģŅ‹Đŗ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐŊа ҃҃. Đ”ŅĐģĐŗŅŅ€ŅĐŊĐŗŌ¯Đš ĐŧŅĐ´ŅŅĐģĐģĐ¸ĐšĐŗ ĐļĐ¸ŅˆŅŅ ĐŊҌ Crontab Guru -ҁ авĐŊа ҃҃.", - "cron_expression_presets": "ĐĨŅƒĐ˛Đ¸Đ°Ņ€ŅŒ (ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģаĐŊ ҁ҃҃ĐģĐŗĐ°ŅĐ°ĐŊ Ņ…ŅƒĐ˛Đ¸ĐģĐąĐ°Ņ€ŅƒŅƒĐ´)", - "disable_login": "ĐŅĐ˛Ņ‚Ņ€ŅŅ…Đ¸ĐšĐŗ Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŗĐžŅ…", - "duplicate_detection_job_description": "ИĐļиĐģ Ņ‚ĶŠŅŅ‚ŅĐš ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŗ иĐģŅ€Ō¯Ō¯ĐģŅŅ…Đ¸ĐšĐŊ Ņ‚ŅƒĐģĐ´ ĐŧĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°ĐģŅ‚Ņ‹Đŗ ĐˇŌ¯ĐšĐģҁ (ĐˇŅƒŅ€Đ°Đŗ, видĐĩĐž) Đ´ŅŅŅ€ аĐļиĐģĐģ҃҃ĐģĐŊа ҃҃. Smart Search Đ´ŅŅŅ€ Ņ‚ŅƒĐģĐŗŅƒŅƒŅ€ĐģаĐŊа.", - "exclusion_pattern_description": "ĐĨĐ°ŅĐ°Ņ… ĐˇĐ°ĐŗĐ˛Đ°Ņ€ŅƒŅƒĐ´ ĐŊҌ Ņ‚Đ°ĐŊŅ‹ ŅĐ°ĐŊĐŗ ҁĐēаĐŊĐŊĐĩŅ€Đ´Đ°Ņ… Ō¯ĐĩĐ´ Ņ„Đ°ĐšĐģ йОĐģĐžĐŊ Ņ…Đ°Đ˛Ņ‚Đ°ŅĐŊŅƒŅƒĐ´Ņ‹Đŗ Ō¯Đģ Ņ‚ĐžĐžĐŧŅĐžŅ€ĐģĐžŅ… йОĐģĐžĐŧĐļĐ¸ĐšĐŗ ĐžĐģĐŗĐžĐ´ĐžĐŗ. ĐĨŅŅ€ŅĐ˛ Ņ‚Đ°ĐŊĐ´ RAW Ņ„Đ°ĐšĐģ ĐŗŅŅ… ĐŧŅŅ‚ иĐŧĐŋĐžŅ€Ņ‚ĐģĐžŅ…Ņ‹Đŗ Ņ…Ō¯ŅŅŅ…ĐŗŌ¯Đš Ņ„Đ°ĐšĐģ Đ°ĐŗŅƒŅƒĐģŅĐ°ĐŊ Ņ…Đ°Đ˛Ņ‚Đ°Ņ ĐąĐ°ĐšĐŗĐ°Đ° йОĐģ ŅĐŊŅ ĐŊҌ Ņ…ŅŅ€ŅĐŗŅ‚ŅĐš.", - "export_config_as_json_description": "ĐžĐ´ĐžĐžĐŗĐ¸ĐšĐŊ ŅĐ¸ŅŅ‚ĐĩĐŧиКĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ JSON Ņ„Đ°ĐšĐģ Ņ…ŅĐģĐąŅŅ€ŅŅŅ€ Ņ‚Đ°Ņ‚Đ°Đļ Đ°Đ˛Đ°Ņ…", - "external_libraries_page_description": "ГадĐŊĐ°Ņ… ŅĐ°ĐŊĐŗ ŅƒĐ´Đ¸Ņ€Đ´Đ°Ņ… Ņ…ŅƒŅƒĐ´Đ°Ņ", - "face_detection": "ĐŌ¯Ō¯Ņ€ Ņ†Đ°Ņ€Đ°Đš иĐģŅ€Ō¯Ō¯ĐģŅŅ…", - "face_detection_description": "ĐœĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°ĐģŅ‚Ņ‹Đŗ Đ°ŅˆĐ¸ĐŗĐģаĐŊ ĐˇŌ¯ĐšĐģҁ Đ´ŅŅ… Ņ†Đ°Ņ€Đ°ĐšĐŗ иĐģŅ€Ō¯Ō¯ĐģĐŊŅ. ВидĐĩĐž ĐąĐ¸Ņ‡ĐģŅĐŗĐ¸ĐšĐŊ Ņ…ŅƒĐ˛ŅŒĐ´ ĐˇĶŠĐ˛Ņ…ĶŠĐŊ ŅŅ…ĐŊиК ĐąĐžĐŗĐ¸ĐŊĐž ĐąĐ¸Ņ‡ĐģŅĐŗŅŅŅ Ņ…Đ°ĐšĐļ Ō¯ĐˇĐŊŅ. \"Refresh\" ĐąŌ¯Ņ… ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ Đ´Đ°Ņ…Đ¸ĐŊ йОĐģĐžĐ˛ŅŅ€ŅƒŅƒĐģĐŊа. \"Reset\" ĐŊҌ ĐžĐ´ĐžĐžĐŗĐ¸ĐšĐŊ ĐąŌ¯Ņ… Ņ†Đ°Ņ€Đ°ĐšĐŊŅ‹ ĶŠĐŗĶŠĐŗĐ´ĐģĐ¸ĐšĐŗ ĐŧĶŠĐŊ Đ°Ņ€Đ¸ĐģĐŗĐ°ĐŊа. \"Missing\" ĐŊҌ Ņ…Đ°Ņ€Đ°Đ°Ņ…Đ°ĐŊ йОĐģĐžĐ˛ŅŅ€ŅƒŅƒĐģĐ°ĐŗĐ´Đ°Đ°ĐŗŌ¯Đš ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ Đ´Đ°Ņ€Đ°Đ°ĐģаĐģĐ´ ĐžŅ€ŅƒŅƒĐģĐŊа. ИĐģŅŅ€ŅŅĐŊ Ņ†Đ°Ņ€Đ°ĐšĐŗ Ņ†Đ°Ņ€Đ°Đš Ņ‚Đ°ĐŊĐ¸Ņ… аĐļиĐģĐģĐ°ĐŗĐ°Đ° Đ´ŅƒŅƒŅŅĐ°ĐŊŅ‹ Đ´Đ°Ņ€Đ°Đ° Ņ†Đ°Ņ€Đ°Đš Ņ‚Đ°ĐŊĐ¸Ņ… Đ´Đ°Ņ€Đ°Đ°ĐģаĐģĐ´ ĐžŅ€ŅƒŅƒĐģĐļ, ОдОО ĐąĐ°ĐšĐŗĐ°Đ° ŅŅĐ˛ŅĐģ ŅˆĐ¸ĐŊŅ Ņ…Ō¯Đŧԝԝҁ҂ Ņ…ŅƒĐ˛Đ°Đ°ĐŊа.", - "facial_recognition_job_description": "ИĐģŅŅ€ŅŅĐŊ Ņ†Đ°Ņ€Đ°ĐšĐŊŅƒŅƒĐ´Ņ‹Đŗ Ņ…Ō¯Đŧԝԝҁ҂ Ņ…ŅƒĐ˛Đ¸Đ°Ņ€ĐģаĐŊ ĐąŌ¯ĐģŅĐŗĐģŅĐŊŅ. Đ­ĐŊŅ аĐģŅ…Đ°Đŧ ĐŊҌ Ņ†Đ°Ņ€Đ°Đš Ņ‚Đ°ĐŊĐ¸Ņ… аĐļиĐģĐģĐ°ĐŗĐ°Đ° Đ´ŅƒŅƒŅŅĐ°ĐŊŅ‹ Đ´Đ°Ņ€Đ°Đ° аĐļиĐģĐģаĐŊа. \"Reset\" Ņ‚ĐžĐ˛Ņ‡ĐģŅƒŅƒŅ€ ĐŊҌ ĐąŌ¯Ņ… Ņ†Đ°Ņ€Đ°ĐšĐŗ Đ´Đ°Ņ…Đ¸ĐŊ Ņ…ŅƒĐ˛Đ¸Đ°Ņ€ĐģаĐŊа. ĐĨŌ¯ĐŊ ĐžĐŊĐžĐžĐŗŌ¯Đš Ņ†Đ°Ņ€Đ°ĐšĐŊŅƒŅƒĐ´Ņ‹Đŗ \"ОĐģĐ´ĐžĐžĐŗŌ¯Đš\" Ņ…Ō¯ĐŧŌ¯Ō¯ŅĐ¸ĐšĐŊ Đ´Đ°Ņ€Đ°Đ°ĐģаĐģĐ´ ĐžŅ€ŅƒŅƒĐģĐŊа.", - "failed_job_command": "КоĐŧĐŧаĐŊĐ´ {command} Đ´Đ°Ņ€Đ°Đ°Ņ… аĐļĐģŅƒŅƒĐ´Đ°Đ´ йиĐĩĐģŅŅĐŗŌ¯Đš:{job}", - "force_delete_user_warning": "АНĐĨĐĐĐ ĐŖĐŖĐ›Đ“Đ: Đ­ĐŊŅ ĐŊҌ Ņ…ŅŅ€ŅĐŗĐģŅĐŗŅ‡ йОĐģĐžĐŊ ĐąŌ¯Ņ… ĐˇŌ¯ĐšĐģŅĐ¸ĐšĐŗ ĐŊŅĐŊ Đ´Đ°Ņ€ŅƒĐš ŅƒŅŅ‚ĐŗĐ°Ņ… йОĐģĐŊĐž. ŌŽŌ¯ĐŊĐ¸ĐšĐŗ ĐąŅƒŅ†Đ°Đ°Ņ… йОĐģĐžĐŧĐļĐŗŌ¯Đš ĐąĶŠĐŗĶŠĶŠĐ´ Ņ„Đ°ĐšĐģŅƒŅƒĐ´Ņ‹Đŗ ŅŅŅ€ĐŗŅŅŅ… йОĐģĐžĐŧĐļĐŗŌ¯Đš.", - "image_format": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚", - "image_format_description": "WebP ĐŊҌ JPEG-ŅŅŅ ĐļиĐļĐ¸Đŗ Ņ„Đ°ĐšĐģŅƒŅƒĐ´Ņ‹Đŗ Ō¯Ō¯ŅĐŗŅĐ´ŅĐŗ йОĐģĐžĐ˛Ņ‡ ĐēОдĐģĐžŅ…ĐžĐ´ ŅƒĐ´Đ°Đ°ĐŊ.", - "image_fullsize_description": "Đ—ŅƒŅ€ĐŗĐ¸ĐšĐŗ Ņ‚ĐžĐŧŅ€ŅƒŅƒĐģĐ°Ņ…Đ°Đ´ ĐŧĐĩŅ‚Đ° ĶŠĐŗĶŠĐŗĐ´ĶŠĐģĐŗŌ¯Đš ĐąŌ¯Ņ€ŅĐŊ Ņ…ŅĐŧĐļŅŅŅŅ‚ ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŗ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ´Đ°Đŗ", - "image_fullsize_enabled": "Đ‘Ō¯Ņ€ŅĐŊ Ņ…ŅĐŧĐļŅŅŅ‚ŅĐš ĐˇŅƒŅ€Đ°Đŗ Ō¯Ō¯ŅĐŗŅĐģŅ‚Đ¸ĐšĐŗ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…", - "image_fullsize_enabled_description": "Đ’ŅĐą-Đ´ ĐˇĐžŅ€Đ¸ŅƒĐģĐ°ĐŗĐ´Đ°Đ°ĐŗŌ¯Đš Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ‹ĐŊ ĐąŌ¯Ņ€ŅĐŊ Ņ…ŅĐŧĐļŅŅŅ‚ŅĐš ĐˇŅƒŅ€Đ°Đŗ Ō¯Ō¯ŅĐŗŅŅ…. \"ĐĄŅƒŅƒĐģĐŗĐ°ĐŗĐ´ŅĐ°ĐŊ ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģŅĐ°ĐŊ Ņ…Đ°Ņ€Đ°ĐŗĐ´Đ°Ņ† \" Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅĐŊ Ō¯ĐĩĐ´ ҁ҃҃ĐģĐŗĐ°ĐŗĐ´ŅĐ°ĐŊ ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģŅĐ°ĐŊ Ņ…Đ°Ņ€Đ°ĐŗĐ´Đ°Ņ†Ņ‹Đŗ Ņ…ĶŠŅ€Đ˛Ō¯Ō¯ĐģŅĐģŅ‚ĐŗŌ¯ĐšĐŗŅŅŅ€ ŅˆŅƒŅƒĐ´ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ´Đ°Đŗ. Đ­ĐŊŅ ĐŊҌ JPEG ĐˇŅŅ€ŅĐŗ Đ˛ŅĐąŅĐ´ ĐŊĐ¸ĐšŅ†Ņ‚ŅĐš Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đ°Đ´ ĐŊĶŠĐģĶŠĶŠĐģĶŠŅ…ĐŗŌ¯Đš.", - "image_fullsize_quality_description": "Đ‘Ō¯Ņ€ŅĐŊ Ņ…ŅĐŧĐļŅŅĐŊиК ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ Ņ‡Đ°ĐŊĐ°Ņ€ 1-100 Ņ…ĐžĐžŅ€ĐžĐŊĐ´ . ͍ĐŊĐ´ĶŠŅ€ ĐąĐ°ĐšŅ… ĐŊҌ ŅĐ°ĐšĐŊ йОĐģĐžĐ˛Ņ‡ Ņ„Đ°ĐšĐģŅ‹ĐŊ Ņ…ŅĐŧĐļŅŅ иĐģŌ¯Ō¯ Ņ‚ĐžĐŧ йаКĐŊа.", - "image_fullsize_title": "Đ‘Ō¯Ņ‚ŅĐŊ Ņ…ŅĐŧĐļŅŅŅ‚ŅĐš ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "image_prefer_embedded_preview": "ĐĄŅƒŅƒĐģĐŗĐ°ĐŗĐ´ŅĐ°ĐŊ ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģаĐŊ Ņ…Đ°Ņ€Đ°ĐŗŅ‡Đ¸ĐšĐŗ иĐģŌ¯Ō¯Đ´ Ō¯ĐˇŅŅ…", - "image_prefer_embedded_preview_setting_description": "БоĐģĐžĐŧĐļŅ‚ĐžĐš йОĐģ RAW Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐ¸ĐšĐŗ ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģŅĐ°ĐŊ Ņ…Đ°Ņ€Đ°ĐŗŅ‡ Đ°ŅˆĐ¸ĐŗĐģаĐŊ Ō¯ĐˇŅŅŅ€ŅĐš. Đ­ĐŊŅ ĐŊҌ ĐˇĐ°Ņ€Đ¸Đŧ ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ ĶŠĐŊĐŗĶŠĐŊиК Ņ…ŅƒŅƒĐģĐąĐ°Ņ€Ņ‹Đŗ иĐģŌ¯Ō¯ ĐŊĐ°Ņ€Đ¸ĐšĐ˛Ņ‡ĐģаĐģŅ‚Đ°Đš йОĐģĐŗĐžĐļ йОĐģĐžŅ… йОĐģĐžĐ˛Ņ‡ ŅƒŅ€ŅŒĐ´Ņ‡Đ¸ĐģŅĐ°ĐŊ Ņ…Đ°Ņ€Đ°ĐŗŅ‡Đ¸ĐšĐŊ ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ Ņ‡Đ°ĐŊĐ°Ņ€ ĐŊҌ ĐēаĐŧĐĩŅ€Đ°Đ°Ņ Ņ…Đ°ĐŧĐ°Đ°Ņ€Đ´Đ°Đŗ ĐąĶŠĐŗĶŠĶŠĐ´ ĐˇŅƒŅ€Đ°Đŗ ĐŊҌ иĐģŌ¯Ō¯ Đ¸Ņ… ŅˆĐ°Ņ…Đ°Đģ҂ҋĐŊ (compression artifacts) ҃Đģ ĐŧĶŠŅ€ Đ°ĐŗŅƒŅƒĐģĐļ йОĐģĐˇĐžŅˆĐŗŌ¯Đš.", - "image_prefer_wide_gamut": "Ķ¨Ņ€ĐŗĶŠĐŊ Ņ…Ō¯Ņ€ŅŅĐŗ (gamut) иĐģŌ¯Ō¯Đ´ Ō¯ĐˇĐŊŅ", - "image_preview_description": "МĐĩŅ‚Đ° ĶŠĐŗĶŠĐŗĐ´ĶŠĐģĐŗŌ¯Đš Đ´ŅƒĐŊдаĐļ Ņ…ŅĐŧĐļŅŅŅ‚ŅĐš ĐˇŅƒŅ€Đ°Đŗ, даĐŊ ĐˇŅƒŅ€Đ°Đŗ Ņ…Đ°Ņ€Đ°Ņ… ĐŧĶŠĐŊ ĐĨĐžĐŖ-ĐŊŅ‹ ŅŅƒŅ€ĐŗĐ°ĐģŅ‚Đ°Đ´ Đ°ŅˆĐ¸ĐŗĐģĐ°Đ´Đ°Đŗ", - "image_preview_quality_description": "ĐŖŅ€ŅŒĐ´Ņ‡Đ¸ĐģаĐŊ Ņ…Đ°Ņ€Đ°Ņ… Ņ‡Đ°ĐŊĐ°Ņ€ 1-100. ͍ĐŊĐ´ĶŠŅ€ Ņ‚ŅƒŅĐŧаа ŅĐ°ĐšĐŊ йОĐģĐžĐ˛Ņ‡ Ņ‚ĐžĐŧ Ņ…ŅĐŧĐļŅŅŅ‚ŅĐš Ņ„Đ°ĐšĐģ Ō¯Ō¯ŅĐŗŅĐŊ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧŅ‹ĐŊ Đ°Ņ‡Đ°Đ°ĐģĐģŅ‹Đŗ Đ¸Ņ…ŅŅĐŗŅĐŊŅ. Đ‘Đ°ĐŗĐ° ĐąĐ°ĐšŅ… ĐŊҌ ĐĨĐžĐŖ-ĐŊŅ‹ Ņ‡Đ°ĐŊĐ°Ņ€Ņ‹Đŗ ĐąŅƒŅƒŅ€ŅƒŅƒĐģĐŊа.", - "image_preview_title": "ĐŖŅ€ŅŒĐ´Ņ‡Đ¸ĐģаĐŊ Ņ…Đ°Ņ€Đ°ĐŗŅ‡Đ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "image_progressive": "ĐŸŅ€ĐžĐŗŅ€ĐĩŅŅĐ¸Đ˛ JPEG", - "image_progressive_description": "ĐŸŅ€ĐžĐŗŅ€ĐĩŅŅĐ¸Đ˛ ĐēĐžĐ´Ņ‡Đ¸ĐģĐžĐģŅ‚ĐžĐš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´ Đ°Ņ€Đ°Đš Ņ…ŅƒŅ€Đ´Đ°ĐŊ Đ°Ņ‡Đ°Đ°ĐģĐģаĐļ ааĐļĐŧĐ°Đ°Ņ€ ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ Ņ‡Đ°ĐŊĐ°Ņ€Ņ‹Đŗ ŅĐ°ĐšĐļŅ€ŅƒŅƒĐģĐŊа. WebP ĐˇŅƒŅ€ĐŗĐ°ĐŊĐ´ ĐŊĶŠĐģĶŠĶŠĐģĶŠŅ…ĐŗŌ¯Đš.", + "face_detection": "ĐŌ¯Ō¯Ņ€ иĐģŅ€Ō¯Ō¯ĐģŅŅ…", "image_quality": "ЧаĐŊĐ°Ņ€", - "image_resolution": "ĐĐ°Ņ€Đ¸ĐšĐ˛Ņ‡ĐģаĐģ", - "image_resolution_description": "ĐĐ°Ņ€Đ¸ĐšĐ˛Ņ‡ĐģаĐģ ĶŠĐŊĐ´ĶŠŅ€ŅĶŠŅ… ĐŊҌ ĐˇŅƒŅ€ĐŗĐ¸ĐšĐŊ ĐŊĐ°Ņ€Đ¸ĐšĐ˛Ņ‡ĐģаĐģŅ‹Đŗ ŅĐ°ĐšĐļŅ€ŅƒŅƒĐģĐ°Ņ… ҇ ĐēĐžĐ´Ņ‡Đ¸ĐģĐžĐģŅ‹ĐŊ Ņ…ŅƒĐŗĐ°Ņ†Đ°Đ°Đŗ ŅƒŅ€Ņ‚Đ°ŅĐŗĐ°Đļ, Ņ„Đ°ĐšĐģŅ‹ĐŊ Ņ…ŅĐŧĐļŅŅĐŗ Đ¸Ņ…ŅŅĐŗŅĐŊ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧŅ‹ĐŊ Đ°Ņ‡Đ°Đ°ĐģĐģŅ‹Đŗ ĐŊŅĐŧŅĐŗĐ´Ō¯Ō¯ĐģĐŊŅ.", - "image_settings": "Đ—ŅƒŅ€ĐŗĐ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "image_settings_description": "ŌŽŌ¯ŅĐŗŅŅŅĐŊ ĐˇŅƒŅ€Đ°ĐŗĐŊŅ‹ Ņ‡Đ°ĐŊĐ°Ņ€ йОĐģĐžĐŊ ĐŊĐ°Ņ€Đ¸ĐšĐ˛Ņ‡ĐģаĐģŅ‹Đŗ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°Ņ…", - "image_thumbnail_description": "ЖиĐļĐ¸ĐŗŅ€Ō¯Ō¯ĐģŅŅĐŊ ĐˇŅƒŅ€Đ°Đŗ ĐŊҌ ĐŧĐĩŅ‚Đ°Đ´Đ°Ņ‚Đ°ĐŗŌ¯Đš ĐąĶŠĐŗĶŠĶŠĐ´ ĐąŌ¯ĐģŅĐŗĐģŅŅŅĐŊ ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Ņ‹Đŗ Ņ…Đ°Ņ€Đ°Ņ…Đ°Đ´ Đ°ŅˆĐ¸ĐŗĐģĐ°ĐŗĐ´Đ´Đ°Đŗ. Đ–Đ¸ŅˆŅŅ ĐŊҌ: Ņ†Đ°Đŗ Ņ…ŅƒĐŗĐ°Ņ†Đ°Đ°ĐŊŅ‹ Ņ…Ō¯Ņ€Đ´ĐŊиК ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´", - "image_thumbnail_quality_description": "ЖиĐļĐ¸ĐŗŅ€Ō¯Ō¯ĐģŅŅĐŊ ĐˇŅƒŅ€Đ°ĐŗĐŊŅ‹ Ņ‡Đ°ĐŊĐ°Ņ€ 1-100. ͍ĐŊĐ´ĶŠŅ€ Ņ‚ŅƒŅĐŧаа ŅĐ°ĐšĐŊ, ĐŗŅĐ˛Ņ‡ Ņ„Đ°ĐšĐģŅ‹ĐŊ Ņ…ŅĐŧĐļŅŅĐŗ Đ¸Ņ…ŅŅĐŗŅĐŊ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧŅ‹ĐŊ Đ°Ņ‡Đ°Đ°ĐģĐģŅ‹Đŗ ĐŊŅĐŧŅĐŗĐ´Ō¯Ō¯ĐģĐŊŅ.", - "image_thumbnail_title": "ЖиĐļĐ¸ĐŗŅ€Ō¯Ō¯ĐģŅŅĐŊ ĐˇŅƒŅ€Đ°ĐŗĐŊŅ‹ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "import_config_from_json_description": "ĐĄĐ¸ŅŅ‚ĐĩĐŧиКĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ JSON Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅ‹ Ņ„Đ°ĐšĐģŅ‹ĐŊ Ņ‚ŅƒŅĐģаĐŧĐļŅ‚Đ°ĐšĐŗĐ°Đ°Ņ€ иĐŧĐŋĐžŅ€Ņ‚ĐžĐģĐļ ŅŅŅ€ĐŗŅŅŅ…", - "job_concurrency": "Đ—ŅŅ€ŅĐŗŅ†ŅŅ аĐļиĐģĐģĐ°Ņ… аĐļиĐģĐąĐ°Ņ€Ņ‹ĐŊ Ņ‚ĐžĐž {job}", - "job_created": "АĐļиĐģĐąĐ°Ņ€ Ō¯Ō¯ŅĐŗŅĐŗĐ´ŅŅĐŊ", "job_settings": "АĐļĐģŅ‹ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", - "machine_learning_enabled": "ĐœĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°ĐģŅ‚ ĐąŅƒŅŽŅƒ Ņ…Đ¸ĐšĐŧŅĐģ ŅƒŅ…Đ°Đ°ĐŊ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…", - "machine_learning_enabled_description": "ĐĨŅŅ€ŅĐ˛ Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŗĐžŅĐžĐŊ йОĐģ, Ņ†Đ°Đ°ŅˆĐ¸Ņ… Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅƒŅƒĐ´Đ°Đ°Ņ Ō¯Đģ Ņ…Đ°ĐŧĐ°Đ°Ņ€Đ°ĐŊ ĐŧĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°Đģ҂ҋĐŊ ĐąŌ¯Ņ… йОĐģĐžĐŧĐļŅƒŅƒĐ´ Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŊĐž.", + "machine_learning_enabled": "ĐœĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°ĐģŅ‚ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…", + "machine_learning_enabled_description": "Đ˜Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŗĐžŅĐžĐŊ Ō¯ĐĩĐ´ Đ´ĐžĐžŅ€Ņ… Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊĐžĐžŅ Ņ…Đ°ĐŧĐ°Đ°Ņ€Đ°Ņ…ĐŗŌ¯ĐšĐŗŅŅŅ€ ĐąŌ¯Ņ… ĐŧĐ°ŅˆĐ¸ĐŊ ŅŅƒŅ€ĐŗĐ°Đģ҂ҋĐŊ йОĐģĐžĐŧĐļ Đ¸Đ´ŅĐ˛Ņ…ĐŗŌ¯Đš йОĐģĐŊĐž.", "machine_learning_facial_recognition": "ĐŌ¯Ō¯Ņ€ Ņ‚Đ°ĐŊиĐģŅ‚", "machine_learning_facial_recognition_description": "Đ—ŅƒŅ€Đ°Đŗ Đ´ŅŅŅ€Ņ… Ņ…Ō¯ĐŧŌ¯Ō¯ŅĐ¸ĐšĐŊ ĐŊŌ¯Ō¯Ņ€Đ¸ĐšĐŗ иĐģŅ€Ō¯Ō¯ĐģĐļ, Ņ‚Đ°ĐŊҌĐļ, ĐąŌ¯ĐģŅĐŗĐģŅĐŊŅ", "machine_learning_facial_recognition_model": "ĐŌ¯Ō¯Ņ€ Ņ‚Đ°ĐŊиĐģ҂ҋĐŊ ĐˇĐ°ĐŗĐ˛Đ°Ņ€", "machine_learning_facial_recognition_model_description": "Đ—Đ°ĐŗĐ˛Đ°Ņ€ŅƒŅƒĐ´ Ņ…ŅĐŧĐļŅŅ ĐŊҌ ĐąŅƒŅƒŅ€Đ°Ņ… ŅŅ€ŅĐŧĐąŅŅŅ€ ĐļĐ°ĐŗŅŅĐ°ĐŊ. ĐĸĐžĐŧ ĐˇĐ°ĐŗĐ˛Đ°Ņ€ŅƒŅƒĐ´ ŅƒĐ´Đ°Đ°ĐŊ, иĐģŌ¯Ō¯ Đ¸Ņ… ŅĐ°ĐŊĐ°Ņ… ОК Ņ…ŅŅ€ŅĐŗĐģŅŅ… йОĐģĐžĐ˛Ņ‡ Ņ…Đ°Ņ€ŅŒŅ†Đ°ĐŊĐŗŅƒĐš Ņ‡Đ°ĐŊĐ°Ņ€Ņ‚Đ°Đš ԝҀ Đ´Ō¯ĐŊ Ō¯ĐˇŌ¯Ō¯ĐģĐŊŅ. Đ—Đ°ĐŗĐ˛Đ°Ņ€ ĶŠĶŠŅ€Ņ‡Đ¸ĐģŅĶŠĐŊ Ņ‚ĐžŅ…Đ¸ĐžĐģĐ´ĐžĐģĐ´ ĐŊԝԝҀ иĐģŅ€Ō¯Ō¯ĐģŅĐģŅ‚Đ¸ĐšĐŊ аĐļĐģŅ‹Đŗ Đ´Đ°Ņ…Đ¸ĐŊ ŅŅ…ĐģŌ¯Ō¯ĐģŅŅ… ŅˆĐ°Đ°Ņ€Đ´ĐģĐ°ĐŗĐ°Ņ‚Đ°ĐšĐŗ ŅĐ°ĐŊĐ°Đ°Ņ€Đ°Đš.", "machine_learning_facial_recognition_setting": "ĐŌ¯Ō¯Ņ€ Ņ‚Đ°ĐŊиĐģŅ‚ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…", "map_settings": "Đ“Đ°ĐˇŅ€Ņ‹ĐŊ ĐˇŅƒŅ€Đ°Đŗ", - "trash_enabled_description": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ /Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŊŅ‹ йОĐģĐžĐŧĐļŅƒŅƒĐ´/", + "trash_enabled_description": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ Đ¸Đ´ŅĐ˛Ņ…ĐļŌ¯Ō¯ĐģŅŅ…", "trash_number_of_days": "ĐĨĐžĐŊĐžĐŗĐ¸ĐšĐŊ Ņ‚ĐžĐž", - "trash_number_of_days_description": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛Đ°ĐŊĐ´ Ņ…ŅĐ´ Ņ…ĐžĐŊĐžĐŗ Ņ…Đ°Đ´ĐŗĐ°Đģаад Đ´Đ°Ņ€Đ°Đ° ĐŊҌ ĐąŌ¯Ņ€ ĐŧĶŠŅĶŠĐŊ ŅƒŅŅ‚ĐŗĐ°Ņ… Đ˛Ņ", + "trash_number_of_days_description": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛Đ°ĐŊĐ´ Ņ…ŅĐ´ Ņ…ĐžĐŊĐžĐŗ Ņ…Đ°Đ´ĐŗĐ°Đģаад ĐąŌ¯Ņ€ ĐŧĶŠŅĶŠĐŊ ŅƒŅŅ‚ĐŗĐ°Ņ… Đ˛Ņ", "trash_settings": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ĐŊŅ‹ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", "trash_settings_description": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ĐŊŅ‹ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐžĐŗ ĶŠĶŠŅ€Ņ‡ĐģĶŠŅ…", "user_management": "ĐĨŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŊ ŅƒĐ´Đ¸Ņ€Đ´ĐģĐ°ĐŗĐ°", - "user_password_has_been_reset": "ĐĨŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŊ ĐŊŅƒŅƒŅ† Ō¯Đŗ ŅˆĐ¸ĐŊŅŅŅ€ reset Ņ…Đ¸ĐšĐŗĐ´ĐģŅŅ:", - "user_restore_description": "{user}-ĐŊ ĐąŌ¯Ņ€Ņ‚ĐŗŅĐģ ŅŅŅ€ĐŗŅŅĐŗĐ´ŅŅ… йОĐģĐŊĐž.", + "user_password_has_been_reset": "ĐĨŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŊ ĐŊŅƒŅƒŅ† Ō¯Đŗ ŅˆĐ¸ĐŊŅŅŅ€ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°ĐŗĐ´Đģаа:", + "user_restore_description": "{user}-ĐŊ ĐąŌ¯Ņ€Ņ‚ĐŗŅĐģ ŅŅŅ€ĐŗŅĐŊŅ.", "user_settings": "ĐĨŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž" }, - "administration": "ĐĄĐĩŅ€Đ˛ĐĩŅ€Đ¸ĐšĐŊ ŅƒĐ´Đ¸Ņ€Đ´ĐģĐ°ĐŗĐ°", + "administration": "АдĐŧиĐŊ", "album_added": "ĐĻĐžĐŧĐžĐŗ ĐŊŅĐŧŅĐŗĐ´ĐģŅŅ", - "album_info_updated": "ĐĻĐžĐŧĐŗĐ¸ĐšĐŊ ĐŧŅĐ´ŅŅĐģŅĐģ ŅˆĐ¸ĐŊŅŅ‡ĐģŅĐŗĐ´ĐģŅŅ", + "album_info_updated": "ĐĻĐžĐŧĐŗĐ¸ĐšĐŊ ĐŧŅĐģŅŅĐģŅĐģ ŅˆĐ¸ĐŊŅŅ‡ĐģŅĐŗĐ´ĐģŅŅ", + "album_leave": "ĐĻĐžĐŧĐŗĐžĐžŅ ĐŗĐ°Ņ€Đ°Ņ… ҃҃?", + "album_leave_confirmation": "Đĸа {album} Ņ†ĐžĐŧĐŗĐžĐžŅ ĐŗĐ°Ņ€Đ°Ņ…Đ´Đ°Đ° Đ¸Ņ‚ĐŗŅĐģŅ‚ŅĐš йаКĐŊа ҃҃?", "album_name": "ĐĻĐžĐŧĐŗĐ¸ĐšĐŊ ĐŊŅŅ€", "album_options": "ĐĻĐžĐŧĐŗĐ¸ĐšĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", "album_remove_user": "ĐĨŅŅ€ŅĐŗĐģŅĐŗŅ‡ Ņ…Đ°ŅĐ°Ņ… ҃҃?", @@ -139,21 +82,25 @@ "all_albums": "Đ‘Ō¯Ņ… Ņ†ĐžĐŧĐžĐŗ", "all_people": "Đ‘Ō¯Ņ… Ņ…Ō¯ĐŊ", "all_videos": "Đ‘Ō¯Ņ… ĐąĐ¸Ņ‡ĐģŅĐŗ", - "allow_dark_mode": "Đ‘Đ°Ņ€Đ°Đ°ĐŊ Ņ…Đ°Ņ€Đ°ĐŗĐ´Đ°Ņ†Ņ‹ĐŊ ĐŗĐžŅ€Đ¸ĐŧŅ‹Đŗ ĐˇĶŠĐ˛ŅˆĶŠĶŠŅ€ĶŠŅ…", + "allow_dark_mode": "ĐĨĐ°Ņ€Đ°ĐŊŅ…ŅƒĐš ĐŗĐžŅ€Đ¸Đŧ ĐˇĶŠĐ˛ŅˆĶŠĶŠŅ€ĶŠŅ…", + "allow_edits": "Đ—Đ°ŅĐ˛Đ°Ņ€ĐģаĐģŅ‚ ĐˇĶŠĐ˛ŅˆĶŠĶŠŅ€ĶŠŅ…", "api_key": "API ҂ԝĐģ҅ԝԝҀ", "api_key_description": "Đ­ĐŊŅ ŅƒŅ‚ĐŗĐ° ĐˇĶŠĐ˛Ņ…ĶŠĐŊ ĐŗĐ°ĐŊ҆ Đģ ŅƒĐ´Đ°Đ° Ņ…Đ°Ņ€Đ°ĐŗĐ´Đ°ĐŊа. ĐĻĐžĐŊŅ…ĐžĐž Ņ…Đ°Đ°Ņ…Đ°Đ°Ņ ĶŠĐŧĐŊĶŠ Ņ…ŅƒŅƒĐģĐļ Đ°Đ˛Đ°Đ°Ņ€Đ°Đš.", "api_key_empty": "ĐĸаĐŊŅ‹ API ҂ԝĐģŅ…Ō¯Ō¯Ņ€Đ¸ĐšĐŊ ĐŊŅŅ€ Ņ…ĐžĐžŅĐžĐŊ йаКĐļ йОĐģĐžŅ…ĐŗŌ¯Đš", "api_keys": "API ҂ԝĐģŅ…Ō¯Ō¯Ņ€Ō¯Ō¯Đ´", - "app_settings": "ĐŸŅ€ĐžĐŗŅ€Đ°ĐŧŅ‹ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", + "app_settings": "АĐŋĐŋ-ĐŊ Ņ‚ĐžŅ…Đ¸Ņ€ĐŗĐžĐž", "archive": "ĐŅ€Ņ…Đ¸Đ˛", - "archive_or_unarchive_photo": "Đ—ŅƒŅ€ĐŗĐ¸ĐšĐŗ Đ°Ņ€Ņ…Đ¸Đ˛Ņ‚ ĐžŅ€ŅƒŅƒĐģĐ°Ņ… ŅŅĐ˛ŅĐģ ĐŗĐ°Ņ€ĐŗĐ°Ņ…", + "archive_or_unarchive_photo": "Đ—ŅƒŅ€ĐŗĐ¸ĐšĐŗ Đ°Ņ€Ņ…Đ¸Đ˛Ņ‚ Ņ…Đ¸ĐšŅ… ŅŅĐ˛ŅĐģ ĐŗĐ°Ņ€ĐŗĐ°Ņ…", "archive_size": "ĐŅ€Ņ…Đ¸Đ˛Ņ‹ĐŊ Ņ…ŅĐŧĐļŅŅ", - "archive_size_description": "ĐĸĐ°Ņ‚Đ°Ņ… Ō¯ĐĩиКĐŊ Đ°Ņ€Ņ…Đ¸Đ˛Ņ‹ĐŊ Ņ…ŅĐŧĐļŅŅĐŗ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°Ņ… (GiB-Đ°Đ°Ņ€)", + "archive_size_description": "ĐĸĐ°Ņ‚Đ°Ņ… Ō¯ĐĩиКĐŊ Đ°Ņ€Ņ…Đ¸Đ˛Ņ‹ĐŊ Ņ…ŅĐŧĐļŅŅĐŗ Ņ‚ĐžŅ…Đ¸Ņ€ŅƒŅƒĐģĐ°Ņ… (GiB-Ņ€)", "asset_added_to_album": "ĐĻĐžĐŧĐžĐŗŅ‚ ĐŊŅĐŧŅŅĐŊ", - "asset_adding_to_album": "ĐĻĐžĐŧĐžĐŗŅ‚ ĐŊŅĐŧĐļ йаКĐŊаâ€Ļ", + "asset_adding_to_album": "ĐĻĐžĐŧĐžĐŗŅ‚ ĐŊŅĐŧĐļ йаКĐŊа...", + "backup_controller_page_background_app_refresh_disabled_content": "АĐŋĐŋ ĐŊŅŅĐŗŅŅĐŗŌ¯Đš ĐąĐ°ĐšŅ… Ō¯ĐĩĐ´ ĐŊĶŠĶŠŅ†ĐģĶŠĐģŅ‚ Ņ…Đ¸ĐšŅ… йОĐģ Settings > General > Background App Refresh Ņ…Đ°ĐŊдаĐļ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐļŌ¯Ō¯ĐģĐŊŅ Ō¯Ō¯.", + "backup_controller_page_background_app_refresh_disabled_title": "АĐŋĐŋ ĐŊŅŅĐŗŅŅĐŗŌ¯Đš ĐąĐ°ĐšŅ… Ō¯ĐĩĐ´ ĐŊĶŠĶŠŅ†ĐģĶŠĐģŅ‚ Đ¸Đ´ŅĐ˛Ņ…Đ¸ĐŗŌ¯Đš.", + "backup_controller_page_background_app_refresh_enable_button_text": "ĐĸĐžŅ…Đ¸Ņ€ĐŗĐžĐž Ņ…ŅŅŅĐŗŅ‚ ĐžŅ‡Đ¸Ņ…", "buy": "Immich Ņ…ŅƒĐ´Đ°ĐģдаĐļ Đ°Đ˛Đ°Ņ…", "camera": "КаĐŧĐĩŅ€", - "camera_brand": "КаĐŧĐĩҀҋĐŊ ĐąŅ€ĐĩĐŊĐ´", + "camera_brand": "КаĐŧĐĩҀҋĐŊ Ō¯ĐšĐģĐ´Đ˛ŅŅ€", "camera_model": "КаĐŧĐĩҀҋĐŊ ĐˇĐ°ĐŗĐ˛Đ°Ņ€", "cancel": "ĐĻŅƒŅ†ĐģĐ°Ņ…", "cancel_search": "ĐĨаКĐģŅ‚ Ņ†ŅƒŅ†ĐģĐ°Ņ…", @@ -168,35 +115,33 @@ "empty_trash": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ Ņ…ĐžĐžŅĐģĐžŅ…", "errors": { "unable_to_empty_trash": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛Ņ‹Đŗ Ņ…ĐžĐžŅĐģĐžĐļ Ņ‡Đ°Đ´ŅĐ°ĐŊĐŗŌ¯Đš", - "unable_to_restore_trash": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ĐŊĐ°Đ°Ņ ŅŅŅ€ĐŗŅĐļ Ņ‡Đ°Đ´ŅĐ°ĐŊĐŗŌ¯Đš" + "unable_to_restore_trash": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛ĐŊĐ°Đ°Ņ ĐŗĐ°Ņ€ĐŗĐ°Đļ Ņ‡Đ°Đ´ŅĐ°ĐŊĐŗŌ¯Đš" }, - "explore": "ĐĨаКĐģŅ‚", - "failed_to_load_assets": "Đ—Ō¯ĐšĐģŅĐ¸ĐšĐŗ ҃ĐŊŅˆĐ¸Đļ Ņ‡Đ°Đ´ŅĐ°ĐŊĐŗŌ¯Đš", - "favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģҁ", - "folders": "ĐĨĐ°Đ˛Ņ‚Đ°ŅĐŊŅƒŅƒĐ´", + "explore": "Đ­Ņ€Đļ ĐžĐģĐžŅ…", + "favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš", "invite_people": "ĐĨŌ¯Đŧԝԝҁ ŅƒŅ€Đ¸Ņ…", - "library": "ХаĐŊ", - "no_assets_message": "Đ­ĐŊĐ´ Đ´Đ°Ņ€Đļ аĐŊŅ…ĐŊŅ‹ ĐˇŅƒŅ€ĐŗĐ°Đ° ĐžŅ€ŅƒŅƒĐģĐŊа ҃҃", - "no_explore_results_message": "ИĐģŌ¯Ō¯ ĐžĐģĐžĐŊ ĐˇŅƒŅ€Đ°Đŗ ĐžŅ€ŅƒŅƒĐģŅĐ°ĐŊŅ‹ Đ´Đ°Ņ€Đ°Đ° Ņ…Đ°ĐšĐģŅ‚ Đ°ŅˆĐ¸ĐŗĐģĐ°Ņ… йОĐģĐžĐŧĐļŅ‚ĐžĐš йОĐģĐŊĐž.", - "notification_permission_list_tile_content": "ĐœŅĐ´ŅĐŗĐ´ŅĐģ Ņ…Đ°Ņ€ŅƒŅƒĐģĐ°Ņ…Ņ‹ĐŊ Ņ‚ŅƒĐģĐ´ ŅŅ€Ņ… ĶŠĐŗĐŊĶŠ Ō¯Ō¯.", + "library": "Đ—ŅƒŅ€ĐŗĐ¸ĐšĐŊ ŅĐ°ĐŊ", + "no_assets_message": "Đ­ĐŊĐ´ Đ´Đ°Ņ€Đļ Ņ‚Đ° ŅŅ…ĐŊиК ĐˇŅƒŅ€ĐŗĐ°Đ° Ņ…ŅƒŅƒĐģĐļ Ō¯ĐˇŅŅ… Ō¯Ō¯", + "no_explore_results_message": "Đ—ŅƒŅ€Đ°Đŗ Ņ…ŅƒŅƒĐģĐļ ĐžŅ€ŅƒŅƒĐģŅĐ°ĐŊŅ‹ Đ´Đ°Ņ€Đ°Đ° Đ°ŅˆĐ¸ĐŗĐģĐ°Ņ… йОĐģĐžĐŧĐļŅ‚ĐžĐš йОĐģĐŊĐž.", + "notification_permission_list_tile_content": "ĐœŅĐ´ŅĐŗĐ´ŅĐģ ĐŊŅŅŅ… ŅŅ€Ņ… ĶŠĐŗĐŊĶŠ Ō¯Ō¯.\n", "notification_permission_list_tile_enable_button": "ĐœŅĐ´ŅĐŗĐ´ŅĐģ ĐŊŅŅŅ…", "notification_permission_list_tile_title": "ĐœŅĐ´ŅĐŗĐ´ĐģиКĐŊ ŅŅ€Ņ…", - "only_favorites": "Đ—ĶŠĐ˛Ņ…ĶŠĐŊ Đ´ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģҁ", + "only_favorites": "Đ—ĶŠĐ˛Ņ…ĶŠĐŊ Đ´ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´", "people": "ĐĨŌ¯Đŧԝԝҁ", "places": "Đ‘Đ°ĐšŅ€ŅˆĐ¸ĐģŅƒŅƒĐ´", - "remove_from_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģŅŅŅŅ Ņ…Đ°ŅĐ°Ņ…", - "removed_from_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģŅŅŅŅ Ņ…Đ°ŅĐ°ĐŗĐ´ŅĐ°ĐŊ", - "removed_from_favorites_count": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŌ¯ĐšĐģŅŅŅŅ {count, plural, other {Removed #}} Ņ…Đ°ŅĐ°ĐŗĐ´Đģаа", - "search_places": "Đ‘Đ°ĐšŅ€ŅˆĐ¸ĐģŅƒŅƒĐ´ Ņ…Đ°ĐšŅ…", - "search_your_photos": "Đ—ŅƒŅ€Đ°ĐŗĐŊĐ°Đ°Ņ Ņ…Đ°ĐšĐģŅ‚ Ņ…Đ¸ĐšŅ…", - "server_online": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ ĐŅĐ°Đ°ĐģŅ‚Ņ‚Đ°Đš", + "remove_from_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Đ°Đ°Ņ Ņ…Đ°ŅĐ°Ņ…", + "removed_from_favorites": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Đ°Đ°Ņ Ņ…Đ°ŅĐ°ĐŗĐ´ŅĐ°ĐŊ", + "removed_from_favorites_count": "Đ”ŅƒŅ€Ņ‚Đ°Đš ĐˇŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Đ°Đ°Ņ {count, plural, other {Removed #}} Ņ…Đ°ŅĐ°ĐŗĐ´Đģаа", + "search_places": "Đ‘Đ°ĐšŅ€ŅˆĐ¸Đģ Ņ…Đ°ĐšŅ…", + "search_your_photos": "Đ—ŅƒŅ€Đ°ĐŗĐŊŅƒŅƒĐ´Đ°Đ°ŅĐ°Đ° Ņ…Đ°ĐšĐģŅ‚ Ņ…Đ¸ĐšŅ…", + "server_online": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ ОĐŊĐģаКĐŊ", "settings": "ĐĸĐžŅ…Đ¸Ņ€ĐŗĐžĐž", "sharing": "ĐĨŅƒĐ˛Đ°Đ°ĐģŅ†Đ°Ņ…", "sign_out": "Đ“Đ°Ņ€Đ°Ņ…", "storage": "Đ”Đ¸ŅĐēĐŊиК ĐąĐ°ĐŗŅ‚Đ°Đ°ĐŧĐļ", "storage_usage": "ĐĐ¸ĐšŅ‚ {available} йОĐģĐžĐŧĐļŅ‚ĐžĐšĐŗĐžĐžŅ {used} Ņ…ŅŅ€ŅĐŗĐģŅŅŅĐŊ", "trash": "ĐĨĐžĐŗĐ¸ĐšĐŊ ŅĐ°Đ˛", - "upload": "Đ—ŅƒŅ€Đ°Đŗ ĐžŅ€ŅƒŅƒĐģĐ°Ņ…", + "upload": "Đ—ŅƒŅ€Đ°Đŗ Ņ…ŅƒŅƒĐģĐ°Ņ…", "utilities": "Đ‘Đ°ĐŗĐ°Đļ Ņ…ŅŅ€ŅĐŗŅŅĐģ", "view_all": "Đ‘Ō¯ĐŗĐ´Đ¸ĐšĐŗ Ņ…Đ°Ņ€Đ°Ņ…", "view_all_users": "Đ‘Ō¯Ņ… Ņ…ŅŅ€ŅĐŗĐģŅĐŗŅ‡Đ¸ĐšĐŗ Ņ…Đ°Ņ€Đ°Ņ…", @@ -204,8 +149,8 @@ "warning": "АĐŊŅ…Đ°Đ°Ņ€ŅƒŅƒĐģĐŗĐ°", "week": "ДоĐģОО Ņ…ĐžĐŊĐžĐŗ", "welcome": "ĐĸĐ°Đ˛Ņ‚Đ°Đš ĐŧĐžŅ€Đ¸Đģ", - "welcome_to_immich": "Immich-Đ´ ĐĸĐ°Đ˛Ņ‚Đ°Đš ĐŧĐžŅ€Đ¸ĐģĐŊĐž ҃҃", - "wifi_name": "Wi-Fi ĐŅŅ€", + "welcome_to_immich": "ĐĸĐ°Đ˛Ņ‚Đ°Đš ĐŧĐžŅ€Đ¸ĐģĐŊĐž ҃҃", + "wifi_name": "WiFi Name", "year": "ОĐŊ", "years_ago": "{years, plural, one {# year} other {# years}} ĶŠĐŧĐŊĶŠ", "yes": "ĐĸиКĐŧ", diff --git a/i18n/mr.json b/i18n/mr.json index 29b1da18e6..8b6244b94e 100644 --- a/i18n/mr.json +++ b/i18n/mr.json @@ -4,40 +4,42 @@ "account_settings": "ā¤–ā¤žā¤¤āĨ‡ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤ž", "acknowledge": "ā¤Žā¤žā¤¨āĨā¤¯ā¤¤ā¤ž", "action": "⤕āĨƒā¤¤āĨ€", - "action_common_update": "⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨", - "action_description": "ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Žā¤žā¤§āĨā¤¯ā¤Žā¤žā¤‚ā¤ĩ⤰ ā¤•ā¤°ā¤žā¤¯ā¤šāĨā¤¯ā¤ž ⤕āĨā¤°ā¤ŋā¤¯ā¤žā¤‚ā¤šā¤ž ā¤¸ā¤‚ā¤š", + "action_common_update": "⤅ā¤ĻāĨā¤¯ā¤¯ā¤žā¤ĩ⤤", "actions": "⤕āĨƒā¤¤āĨā¤¯āĨ‡", "active": "⤏⤕āĨā¤°ā¤ŋ⤝", - "active_count": "⤕āĨƒā¤¤āĨ€: {⤏⤂⤖āĨā¤¯ā¤ž}", + "active_count": "⤕āĨƒā¤¤āĨ€: {count}", "activity": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ", + "activity_changed": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ {enabled, select, true {enabled} other {disabled}}", "add": "⤜āĨ‹ā¤Ąā¤ž", "add_a_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ā¤•ā¤°ā¤ž", - "add_a_location": "ā¤ā¤• ⤏āĨā¤Ĩ⤺ ⤜āĨ‹ā¤Ąā¤ž", - "add_a_name": "ā¤¨ā¤žā¤ĩ ⤜āĨ‹ā¤Ąā¤ž", - "add_a_title": "ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ⤜āĨ‹ā¤Ąā¤ž", + "add_a_location": "ā¤ā¤• ⤏āĨā¤Ĩ⤺ ā¤Ÿā¤žā¤•ā¤ž", + "add_a_name": "ā¤¨ā¤žā¤ĩ ā¤Ÿā¤žā¤•ā¤ž", + "add_a_title": "ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤Ÿā¤žā¤•ā¤ž", "add_action": "⤕āĨƒā¤¤āĨ€ ⤜āĨ‹ā¤Ąā¤ž", - "add_assets": "ā¤Žā¤žā¤§āĨā¤¯ā¤ŽāĨ‡ ⤜āĨ‹ā¤Ąā¤ž", "add_birthday": "⤜⤍āĨā¤Žā¤Ļā¤ŋā¤ĩ⤏ ⤍āĨ‹ā¤‚ā¤Ļā¤ĩā¤ž", "add_endpoint": "ā¤ā¤‚ā¤Ąā¤ĒāĨ‰ā¤‡ā¤‚ā¤Ÿ ⤜āĨ‹ā¤Ąā¤ž", "add_exclusion_pattern": "⤅ā¤Ēā¤ĩā¤žā¤Ļ ā¤¨ā¤ŽāĨā¤¨ā¤ž ⤜āĨ‹ā¤Ąā¤ž", - "add_location": "⤏āĨā¤Ĩ⤺ ⤜āĨ‹ā¤Ąā¤ž", + "add_location": "⤏āĨā¤Ĩ⤺ ā¤Ÿā¤žā¤•ā¤ž", + "add_more_users": "⤅⤧ā¤ŋ⤕ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", "add_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤜āĨ‹ā¤Ąā¤ž", + "add_path": "ā¤Žā¤žā¤°āĨā¤— ā¤Ÿā¤žā¤•ā¤ž", "add_photos": "ā¤›ā¤žā¤¯ā¤žā¤šā¤ŋ⤤āĨā¤°āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", - "add_step": "⤟ā¤ĒāĨā¤Ēā¤ž ⤜āĨ‹ā¤Ąā¤ž", "add_tag": "⤟āĨ…⤗ ⤜āĨ‹ā¤Ąā¤ž", "add_to": "⤤āĨā¤¯ā¤ž ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤žâ€Ļ", - "add_to_album": "⤏⤂⤗āĨā¤°ā¤šā¤žā¤¤ ⤜āĨ‹ā¤Ąā¤ž", + "add_to_album": "⤏⤂⤗āĨā¤°ā¤šā¤žā¤¤ ā¤Ÿā¤žā¤•ā¤ž", "add_to_album_bottom_sheet_added": "{album} ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡ ⤗āĨ‡ā¤˛āĨ‡", "add_to_album_bottom_sheet_already_exists": "⤆⤧āĨ€ā¤š {album} ā¤Žā¤§āĨā¤¯āĨ‡ ā¤†ā¤šāĨ‡", "add_to_album_bottom_sheet_some_local_assets": "ā¤•ā¤žā¤šāĨ€ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤Žā¤žā¤§āĨā¤¯ā¤ŽāĨ‡ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤ŖāĨ‡ ā¤ļ⤕āĨā¤¯ ā¤ā¤žā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€", + "add_to_album_toggle": "{album} ā¤¸ā¤žā¤ āĨ€ ⤍ā¤ŋā¤ĩā¤Ą ā¤Ŧā¤Ļā¤˛ā¤ž", "add_to_albums": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", "add_to_albums_count": "⤅⤞āĨā¤Ŧā¤Žā¤žā¤‚ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤ž ({count})", "add_to_bottom_bar": "ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", + "add_to_shared_album": "ā¤¸ā¤žā¤Žā¤žā¤¯ā¤ŋ⤕ ⤏⤂⤗āĨā¤°ā¤šā¤žā¤¤ ā¤Ÿā¤žā¤•ā¤ž", "add_upload_to_stack": "⤏āĨā¤ŸāĨ…ā¤•ā¤Žā¤§āĨā¤¯āĨ‡ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤜āĨ‹ā¤Ąā¤ž", "add_url": "URL ā¤ĒāĨā¤°ā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", "added_to_archive": "⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡", "added_to_favorites": "⤆ā¤ĩā¤Ąā¤¤āĨā¤¯ā¤ž ⤏⤂⤗āĨā¤°ā¤šā¤žā¤¤ ⤜āĨ‹ā¤Ąā¤˛āĨ‡", - "added_to_favorites_count": "⤆ā¤ĩā¤Ąā¤¤āĨā¤¯ā¤žā¤¤ {count, number} ⤜āĨ‹ā¤Ąā¤˛āĨ‡", + "added_to_favorites_count": "⤆ā¤ĩā¤Ąā¤¤āĨā¤¯ā¤žā¤¤ {count, number} ā¤Ÿā¤žā¤•ā¤˛āĨ‡", "admin": { "add_exclusion_pattern_description": "⤅ā¤Ēā¤ĩā¤žā¤Ļ ⤅⤍āĨā¤•āĨ‚⤞⤍ ⤜āĨ‹ā¤Ąā¤ž. ** ⤆⤪ā¤ŋ ? ā¤¯ā¤ž ⤉ā¤Ē⤝āĨ‹ā¤—ā¤žā¤¤ ⤗āĨā¤˛āĨ‹ā¤Ŧā¤ŋ⤂⤗ ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ā¤†ā¤šāĨ‡. ⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ \"Raw\" ā¤¨ā¤žā¤ĩā¤žā¤šāĨā¤¯ā¤ž ⤍ā¤ŋ⤰āĨā¤ĻāĨ‡ā¤ļā¤ŋ⤕āĨ‡ā¤Žā¤§āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ā¤–ā¤¤ā¤žā¤ĩ⤪āĨā¤¯ā¤ž ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇāĨ€ā¤¤ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ \"/Raw/\" ā¤ĩā¤žā¤Ēā¤°ā¤ž. \".tif\" ā¤¯ā¤ž ā¤¸ā¤žā¤Žā¤žā¤¨āĨā¤¯ ā¤Ēā¤Ĩā¤žā¤ĩ⤰ ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤ ⤅⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ⤏⤰āĨā¤ĩ ā¤–ā¤¤ā¤žā¤ĩ⤪āĨā¤¯ā¤ž ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇāĨ€ā¤¤ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ \"**/.tif\" ā¤ĩā¤žā¤Ēā¤°ā¤ž. ā¤ĩā¤ŋā¤ļā¤ŋ⤎āĨā¤Ÿ ā¤Ēā¤Ĩ ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ \"/path/to/ignore/**\" ā¤ĩā¤žā¤Ēā¤°ā¤ž.", "admin_user": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤¨ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž", @@ -67,17 +69,13 @@ "confirm_reprocess_all_faces": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ā¤–ā¤žā¤¤āĨā¤°āĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž ⤕āĨ€ ⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤏⤰āĨā¤ĩ ⤚āĨ‡ā¤šā¤ąāĨā¤¯ā¤žā¤‚ā¤ĩ⤰ ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤•ā¤°ā¤žā¤¯ā¤šāĨ€ ā¤†ā¤šāĨ‡? ā¤¯ā¤žā¤ŽāĨā¤ŗāĨ‡ ā¤¨ā¤žā¤ĩ ā¤Ļā¤ŋ⤞āĨ‡ā¤˛āĨ‡ ⤞āĨ‹ā¤•ā¤šāĨ€ ā¤¸ā¤žā¤Ģ ā¤šāĨ‹ā¤¤āĨ€ā¤˛.", "confirm_user_password_reset": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ {user} ā¤šā¤ž ā¤Ē⤰ā¤ĩ⤞āĨ€ā¤šā¤ž ā¤ļā¤ŦāĨā¤Ļ ā¤Ŧā¤Ļā¤˛ā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "confirm_user_pin_code_reset": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ {user} ā¤šā¤ž ā¤Ēā¤ŋ⤍ ⤕āĨ‹ā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", - "copy_config_to_clipboard_description": "ā¤šā¤žā¤˛āĨ‚ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ JSON ⤑ā¤ŦāĨā¤œāĨ‡ā¤•āĨā¤Ÿ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ąā¤ĩ⤰ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", "create_job": "ā¤•ā¤žā¤°āĨā¤¯ ā¤Ŧ⤍ā¤ĩā¤ž", "cron_expression": "ā¤ĩāĨ‡ā¤ŗā¤žā¤Ē⤤āĨā¤°ā¤• ⤏āĨ‚⤤āĨā¤°", "cron_expression_description": "ā¤šā¤žā¤ŗā¤¨āĨā¤¯ā¤žā¤šāĨ‡ ā¤ĩāĨ‡ā¤ŗā¤žā¤Ē⤤āĨā¤°ā¤• ⤕āĨā¤°āĨ‰ā¤¨ ā¤Ēā¤ĻāĨā¤§ā¤¤āĨ€ ⤍āĨ‡ ā¤•ā¤°ā¤ž. ⤅⤧ā¤ŋ⤕ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ā¤¸ā¤žā¤ āĨ€ ā¤Ēā¤šā¤ž: ⤕āĨā¤°āĨ‰ā¤¨ ⤗āĨā¤°āĨ", "cron_expression_presets": "ā¤ĒāĨ‚⤰āĨā¤ĩ⤍ā¤ŋ⤰āĨā¤§ā¤žā¤°ā¤ŋ⤤ ā¤ĩāĨ‡ā¤ŗā¤žā¤Ē⤤āĨā¤°ā¤• ⤏āĨ‚⤤āĨā¤°āĨ‡", "disable_login": "ā¤ĒāĨā¤°ā¤ĩāĨ‡ā¤ļā¤žā¤§ā¤ŋ⤕⤰ ā¤ĩ⤰āĨā¤œāĨā¤¯ ā¤•ā¤°ā¤ž", - "download_csv": "CSV ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", "duplicate_detection_job_description": "ā¤¸ā¤žā¤°ā¤–āĨā¤¯ā¤ž ā¤›ā¤žā¤¯ā¤žā¤šā¤ŋ⤤āĨā¤°ā¤žā¤‚ā¤šā¤ž ā¤ļāĨ‹ā¤§ ⤘āĨ‡ā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤¯ā¤žā¤‚ā¤¤āĨā¤°ā¤ŋ⤕āĨ€ ā¤ĒāĨā¤°ā¤ļā¤ŋ⤕āĨā¤ˇā¤Ŗ ā¤ĻāĨā¤¯ā¤ž. ā¤šāĨ€ ā¤•ā¤žā¤°āĨā¤¯ā¤•āĨā¤ˇā¤Žā¤¤ā¤ž ⤚⤤āĨā¤° ā¤ļāĨ‹ā¤§ā¤ĒāĨā¤°ā¤Ŗā¤žā¤˛āĨ€ā¤ĩ⤰ ⤅ā¤ĩ⤞⤂ā¤ŦāĨ‚⤍ ā¤†ā¤šāĨ‡", "exclusion_pattern_description": "⤆ā¤Ē⤞āĨ‡ ⤏⤂⤗āĨā¤°ā¤šā¤žā¤˛ā¤¯ ā¤šā¤žā¤ŗā¤¤ā¤žā¤¨ā¤ž ⤅ā¤Ēā¤ĩā¤žā¤Ļ ā¤¨ā¤ŽāĨā¤¨āĨ‡ ⤆ā¤Ē⤞āĨā¤¯ā¤žā¤˛ā¤ž ā¤–ā¤¤ā¤žā¤ĩ⤪āĨā¤¯ā¤ž ⤆⤪ā¤ŋ ⤰āĨā¤¨ā¤ŋ⤰āĨā¤ĻāĨ‡ā¤ļā¤ŋ⤕āĨ‡ā¤˛ā¤ž ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇāĨ€ā¤¤ ⤕⤰āĨ‚ ā¤ĻāĨ‡ā¤¤ā¤žā¤¤. ⤆ā¤Ē⤞āĨā¤¯ā¤žā¤•ā¤ĄāĨ‡ ā¤•ā¤šāĨā¤šāĨā¤¯ā¤ž ā¤–ā¤¤ā¤žā¤ĩ⤪āĨā¤¯ā¤ž ā¤¸ā¤žā¤°ā¤–āĨā¤¯ā¤ž ā¤†ā¤¯ā¤žā¤¤ ⤕⤰āĨ‚ ā¤‡ā¤šāĨā¤›ā¤ŋ⤤ ⤍⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ⤅⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ (RAW) ā¤–ā¤¤ā¤žā¤ĩ⤪āĨā¤¯ā¤ž ⤅⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ⤍ā¤ŋ⤰āĨā¤ĻāĨ‡ā¤ļā¤ŋā¤•ā¤ž ⤅⤏⤞āĨā¤¯ā¤žā¤¸ ā¤šāĨ‡ ⤉ā¤Ē⤝āĨā¤•āĨā¤¤ ā¤†ā¤šāĨ‡.", - "export_config_as_json_description": "ā¤šā¤žā¤˛āĨ‚ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰āĨ‡ā¤ļ⤍ JSON ā¤Ģā¤žā¤ˆā¤˛ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", - "external_libraries_page_description": "ā¤Ŧā¤žā¤šāĨā¤¯ ⤗āĨā¤°ā¤‚ā¤Ĩā¤žā¤˛ā¤¯ā¤žā¤šāĨā¤¯ā¤ž ā¤ĒāĨƒā¤ˇāĨā¤ ā¤žā¤šāĨ‡ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ē⤍", "face_detection": "ā¤ŽāĨā¤– ⤏⤂ā¤ļāĨ‹ā¤§ā¤¨", "face_detection_description": "ā¤Žā¤ļāĨ€ā¤¨ ⤞⤰āĨā¤¨ā¤ŋ⤂⤗ ā¤ĩā¤žā¤Ē⤰āĨ‚⤍ ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤žā¤‚ā¤Žā¤§āĨ€ā¤˛ ⤚āĨ‡ā¤šā¤°āĨ‡ ā¤ļāĨ‹ā¤§ā¤ž. ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋā¤“ā¤‚ā¤¸ā¤žā¤ āĨ€, ā¤Ģ⤕āĨā¤¤ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ā¤šā¤ž ā¤ĩā¤ŋā¤šā¤žā¤° ⤕āĨ‡ā¤˛ā¤ž ā¤œā¤žā¤¤āĨ‹. \"⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ\" (ā¤ĒāĨā¤¨āĨā¤šā¤ž) ⤏⤰āĨā¤ĩ ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤žā¤‚ā¤ĩ⤰ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ⤕⤰⤤āĨ‡. \"⤰āĨ€ā¤¸āĨ‡ā¤Ÿ\" ā¤¯ā¤žā¤ĩāĨā¤¯ā¤¤ā¤ŋ⤰ā¤ŋ⤕āĨā¤¤ ⤏⤰āĨā¤ĩ ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤚āĨ‡ā¤šā¤°ā¤ž ā¤ĄāĨ‡ā¤Ÿā¤ž ā¤¸ā¤žā¤Ģ ⤕⤰⤤āĨ‡. \"ā¤—ā¤šā¤žā¤ŗ\" ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤žā¤‚ā¤ĩ⤰ ⤅ā¤ĻāĨā¤¯ā¤žā¤Ē ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤°ā¤žā¤‚ā¤—āĨ‡ā¤¤ ⤠āĨ‡ā¤ĩ⤤āĨ‡. ā¤ļāĨ‹ā¤§ā¤˛āĨ‡ā¤˛āĨ‡ ⤚āĨ‡ā¤šā¤°āĨ‡ ā¤ĢāĨ‡ā¤¸ ā¤Ąā¤ŋ⤟āĨ‡ā¤•āĨā¤ļ⤍ ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨā¤¯ā¤žā¤¨ā¤‚⤤⤰ ā¤ĢāĨ‡ā¤ļā¤ŋ⤝⤞ ⤰āĨ‡ā¤•⤗āĨā¤¨ā¤ŋā¤ļā¤¨ā¤¸ā¤žā¤ āĨ€ ā¤°ā¤žā¤‚ā¤—āĨ‡ā¤¤ ⤠āĨ‡ā¤ĩ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛, ⤤āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ā¤ĩā¤ŋā¤ĻāĨā¤¯ā¤Žā¤žā¤¨ ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤍ā¤ĩāĨ€ā¤¨ ⤞āĨ‹ā¤•ā¤žā¤‚ā¤Žā¤§āĨā¤¯āĨ‡ ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤ˆā¤˛.", "facial_recognition_job_description": "ā¤ļāĨ‹ā¤§ā¤˛āĨ‡ā¤˛āĨ‡ ⤚āĨ‡ā¤šā¤°āĨ‡ ⤞āĨ‹ā¤•ā¤žā¤‚ā¤Žā¤§āĨā¤¯āĨ‡ ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤•ā¤°ā¤ž. ā¤šāĨ‡ ⤚⤰⤪ ⤚āĨ‡ā¤šā¤°ā¤ž ā¤ļāĨ‹ā¤§ā¤ŖāĨ‡ ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨā¤¯ā¤žā¤¨ā¤‚⤤⤰ ā¤šā¤žā¤˛ā¤¤āĨ‡. \"⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž\" (ā¤ĒāĨā¤¨āĨā¤šā¤ž) ⤏⤰āĨā¤ĩ ⤚āĨ‡ā¤šā¤°āĨ‡ ⤕āĨā¤˛ā¤¸āĨā¤Ÿā¤° ⤕⤰. \"ā¤—ā¤šā¤žā¤ŗ\" ⤚āĨ‡ā¤šā¤°āĨ‡ ā¤°ā¤žā¤‚ā¤—āĨ‡ā¤¤ ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ⤕⤰⤤āĨ‡ ⤜āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ⤍ā¤ŋ⤝āĨā¤•āĨā¤¤ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤¨ā¤žā¤šāĨ€.", @@ -97,8 +95,6 @@ "image_preview_description": "ā¤ā¤•ā¤š ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ā¤Ēā¤žā¤šā¤¤ā¤žā¤¨ā¤ž ⤆⤪ā¤ŋ ā¤Žā¤ļāĨ€ā¤¨ ⤞⤰āĨā¤¨ā¤ŋā¤‚ā¤—ā¤¸ā¤žā¤ āĨ€ ā¤ĩā¤žā¤Ē⤰⤞āĨ€ ā¤œā¤žā¤Ŗā¤žā¤°āĨ€, ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ļā¤ŋ⤞āĨ‡ā¤˛āĨ€ ā¤Žā¤§āĨā¤¯ā¤Ž ā¤†ā¤•ā¤žā¤°ā¤žā¤šāĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž", "image_preview_quality_description": "ā¤ĒāĨ‚⤰āĨā¤ĩā¤žā¤ĩ⤞āĨ‹ā¤•⤍ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž 1–100: ⤜ā¤ŋ⤤⤕āĨ€ ā¤‰ā¤šāĨā¤š, ⤤ā¤ŋ⤤⤕āĨ€ ā¤šā¤žā¤‚ā¤—ā¤˛āĨ€; ā¤Ģā¤žā¤‡ā¤˛ ā¤†ā¤•ā¤žā¤° ā¤ĩā¤žā¤ĸ⤤āĨ‹ ⤆⤪ā¤ŋ āĨ˛ā¤Ē⤚āĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤¸ā¤žā¤Ļ⤕āĨā¤ˇā¤Žā¤¤ā¤ž ā¤•ā¤ŽāĨ€ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡. ā¤•ā¤ŽāĨ€ ā¤ŽāĨ‚⤞āĨā¤¯ ⤏āĨ‡ā¤Ÿ ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¸ ā¤Žā¤ļāĨ€ā¤¨ ⤞⤰āĨā¤¨ā¤ŋ⤂⤗ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž ā¤ĒāĨā¤°ā¤­ā¤žā¤ĩā¤ŋ⤤ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡.", "image_preview_title": "ā¤ĒāĨ‚⤰āĨā¤ĩā¤žā¤ĩ⤞āĨ‹ā¤•⤍ ā¤ĩā¤ŋ⤍āĨā¤¯ā¤žā¤¸", - "image_progressive": "ā¤ĒāĨā¤°ā¤—⤤āĨ€ā¤ļāĨ€ā¤˛", - "image_progressive_description": "JPEG ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ā¤šā¤ŗāĨ‚ā¤šā¤ŗāĨ‚ ⤞āĨ‹ā¤Ą ā¤šāĨ‹ā¤Šā¤¨ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ā¤šāĨ‹ā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤕āĨā¤°ā¤Žā¤žā¤•āĨā¤°ā¤Žā¤žā¤¨āĨā¤¸ā¤žā¤° ā¤ā¤¨āĨā¤•āĨ‹ā¤Ą ā¤•ā¤°ā¤ž. ā¤¯ā¤žā¤šā¤ž WebP ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤žā¤‚ā¤ĩ⤰ ⤕āĨ‹ā¤Ŗā¤¤ā¤žā¤šāĨ€ ā¤Ē⤰ā¤ŋā¤Ŗā¤žā¤Ž ā¤šāĨ‹ā¤¤ ā¤¨ā¤žā¤šāĨ€.", "image_quality": "⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž", "image_resolution": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤ŽāĨ‡ā¤šāĨ€ ⤏āĨā¤Ē⤎āĨā¤Ÿā¤¤ā¤ž", "image_resolution_description": "ā¤‰ā¤šāĨā¤š ⤰ā¤ŋā¤āĨ‹ā¤˛āĨā¤¯āĨ‚ā¤ļ⤍ ⤅⤧ā¤ŋ⤕ ⤤ā¤Ēā¤ļāĨ€ā¤˛ ⤜⤤⤍ ā¤•ā¤°ā¤¤ā¤žā¤¤, ā¤Ē⤰⤂⤤āĨ ⤤āĨā¤¯ā¤žā¤‚ā¤šāĨ‡ ā¤ā¤¨āĨā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ā¤œā¤žā¤¸āĨā¤¤ ā¤ĩāĨ‡ā¤ŗ ⤘āĨ‡ā¤¤ā¤‚, ā¤Ģā¤žā¤‡ā¤˛ ā¤¸ā¤žā¤ˆā¤œ ā¤ŽāĨ‹ā¤ āĨ€ ā¤šāĨ‹ā¤¤āĨ‡ ⤆⤪ā¤ŋ ā¤…â€āĨ…ā¤Ē⤚āĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤¸ā¤žā¤Ļ⤕āĨā¤ˇā¤Žā¤¤ā¤ž ā¤•ā¤ŽāĨ€ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡.", @@ -107,22 +103,6 @@ "image_thumbnail_description": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤¸ā¤ŽāĨ‚ā¤š ā¤Ēā¤žā¤šā¤¤ā¤žā¤¨ā¤ž ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ļā¤žā¤–ā¤ĩ⤞āĨ‡ā¤˛āĨ‡ ā¤˛ā¤šā¤žā¤¨ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", "image_thumbnail_quality_description": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž (1–100): ⤜ā¤ŋ⤤⤕āĨ€ ā¤œā¤žā¤¸āĨā¤¤, ⤤ā¤ŋ⤤⤕āĨ€ ā¤šā¤žā¤‚ā¤—ā¤˛āĨ€; ā¤Ē⤰⤂⤤āĨ ā¤Ģā¤žā¤‡ā¤˛ ā¤†ā¤•ā¤žā¤° ā¤ĩā¤žā¤ĸ⤤āĨ‹ ⤆⤪ā¤ŋ āĨ˛ā¤Ē⤚āĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤¸ā¤žā¤Ļ⤕āĨā¤ˇā¤Žā¤¤ā¤ž ā¤•ā¤ŽāĨ€ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡.", "image_thumbnail_title": "⤞⤘āĨā¤°āĨ‚ā¤Ē ā¤ĩā¤ŋ⤍āĨā¤¯ā¤žā¤¸", - "import_config_from_json_description": "JSON ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗ ā¤Ģā¤žā¤‡ā¤˛ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰āĨ‚⤍ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗ ā¤†ā¤¯ā¤žā¤¤ ā¤•ā¤°ā¤ž", - "integrity_checks_checksum_files": "⤚āĨ‡ā¤•ā¤¸ā¤Ž ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸", - "integrity_checks_checksum_files_description": "⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ā¤•ā¤°ā¤ž", - "integrity_checks_checksum_files_enable_description": "⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", - "integrity_checks_checksum_files_percentage_limit": "ā¤Ÿā¤•āĨā¤•āĨ‡ā¤ĩā¤žā¤°āĨ€ ā¤Žā¤°āĨā¤¯ā¤žā¤Ļā¤ž", - "integrity_checks_checksum_files_percentage_limit_description": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤…ā¤‚ā¤¤ā¤°ā¤žā¤˛ā¤Žā¤§āĨā¤¯āĨ‡ ⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤕ā¤ŋ⤤āĨ€ ā¤Ÿā¤•āĨā¤•āĨ‡ā¤Ē⤰āĨā¤¯ā¤‚⤤ ā¤šā¤žā¤˛ā¤ĩā¤žā¤¯ā¤šāĨ€ ⤤āĨ‡ 0.01 ⤤āĨ‡ 1 ā¤¯ā¤ž ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€ā¤¤ ⤏āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž.", - "integrity_checks_checksum_files_time_limit": "ā¤ĩāĨ‡ā¤ŗāĨ‡ā¤šāĨ€ ā¤Žā¤°āĨā¤¯ā¤žā¤Ļā¤ž", - "integrity_checks_checksum_files_time_limit_description": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤…ā¤‚ā¤¤ā¤°ā¤žā¤˛ā¤žā¤¨ā¤‚ā¤¤ā¤° ⤚āĨ‡ā¤•ā¤¸ā¤Ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤕ā¤ŋ⤤āĨ€ ā¤•ā¤žā¤ŗ ā¤šā¤žā¤˛ā¤ĩā¤žā¤¯ā¤šāĨ€ ⤤āĨ‡ ā¤•ā¤Žā¤žā¤˛ ā¤•ā¤žā¤˛ā¤žā¤ĩ⤧āĨ€ ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ā¤•ā¤°ā¤ž. (ā¤Žā¤ŋ⤞āĨ€ā¤¸āĨ‡ā¤•⤂ā¤Ļ)", - "integrity_checks_missing_files": "ā¤—ā¤šā¤žā¤ŗ ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸", - "integrity_checks_missing_files_description": "ā¤ĩā¤žā¤°ā¤‚ā¤ĩā¤žā¤°ā¤¤ā¤ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ā¤•ā¤°ā¤ž ⤆⤪ā¤ŋ ā¤—ā¤šā¤žā¤ŗ ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤅⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", - "integrity_checks_missing_files_enable_description": "ā¤—ā¤šā¤žā¤ŗ ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", - "integrity_checks_settings": "ā¤…ā¤–ā¤‚ā¤Ąā¤¤ā¤ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨā¤¯ā¤ž", - "integrity_checks_settings_description": "ā¤…ā¤–ā¤‚ā¤Ąā¤¤ā¤ž ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ā¤…ā¤‚ā¤¤ā¤°ā¤žā¤‚ā¤šāĨ‡ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ē⤍ ā¤•ā¤°ā¤ž", - "integrity_checks_untracked_files": "ā¤…ā¤¨ā¤ŸāĨā¤°āĨ…⤕āĨā¤Ą ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸", - "integrity_checks_untracked_files_description": "ā¤ĩā¤žā¤°ā¤‚ā¤ĩā¤žā¤°ā¤¤ā¤ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ā¤•ā¤°ā¤ž ⤆⤪ā¤ŋ ⤟āĨā¤°āĨ…⤕ ⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ā¤šāĨ€ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤅⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", - "integrity_checks_untracked_files_enable_description": "ā¤…ā¤¨ā¤ŸāĨā¤°āĨ…⤕āĨā¤Ą ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ā¤šāĨ€ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "job_concurrency": "{job} ā¤ā¤•ā¤°āĨ‚ā¤Ēā¤¤ā¤ž", "job_created": "ā¤•ā¤žā¤°āĨā¤¯ ā¤¤ā¤¯ā¤žā¤° ā¤ā¤žā¤˛āĨ‡", "job_not_concurrency_safe": "ā¤šāĨ‡ ā¤•ā¤žā¤°āĨā¤¯ ā¤¸ā¤Žā¤žā¤‚ā¤¤ā¤°ā¤Ē⤪āĨ‡ ā¤šā¤žā¤˛ā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ ā¤¨ā¤žā¤šāĨ€.", @@ -197,15 +177,10 @@ "machine_learning_smart_search_enabled": "⤏āĨā¤Žā¤žā¤°āĨā¤Ÿ ⤏⤰āĨā¤š ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "machine_learning_smart_search_enabled_description": "⤅⤕āĨā¤ˇā¤Ž ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¸, ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤏āĨā¤Žā¤žā¤°āĨā¤Ÿ ⤏⤰āĨā¤šā¤¸ā¤žā¤ āĨ€ ā¤ā¤¨āĨā¤•āĨ‹ā¤Ą ⤕āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤œā¤žā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€ā¤¤.", "machine_learning_url_description": "ā¤Žā¤ļāĨ€ā¤¨ ⤞⤰āĨā¤¨ā¤ŋ⤂⤗ ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤šāĨ€ URL. ā¤ā¤•ā¤žā¤šāĨ‚⤍ ⤅⤧ā¤ŋ⤕ URL ā¤Ļā¤ŋ⤞āĨā¤¯ā¤žā¤¸, ā¤ĒāĨā¤°ā¤Ĩā¤Ž ⤤āĨ‡ ā¤Ļā¤ŋ⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ⤕āĨā¤°ā¤Žā¤žā¤¨āĨ‡ ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ā¤ā¤•-ā¤ā¤• ⤕⤰āĨ‚⤍ ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛, ⤜āĨ‹ā¤Ē⤰āĨā¤¯ā¤‚⤤ ⤕āĨ‹ā¤ŖāĨ€ā¤¤ā¤°āĨ€ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤¸ā¤žā¤Ļ ā¤ĻāĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ ⤤āĨ‹ā¤Ē⤰āĨā¤¯ā¤‚⤤. ⤜āĨ‡ ⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤¸ā¤žā¤Ļ ā¤ĻāĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ⤤āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ⤤āĨ‡ ā¤Ē⤰⤤ ā¤‘ā¤¨ā¤˛ā¤žā¤‡ā¤¨ ⤝āĨ‡ā¤ˆā¤Ē⤰āĨā¤¯ā¤‚⤤ ā¤¤ā¤žā¤¤āĨā¤ĒāĨā¤°ā¤¤āĨ‡ ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤ˆā¤˛.", - "maintenance_restore_backup_unknown_version": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤍ā¤ŋā¤ļāĨā¤šā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤆⤞āĨ€ ā¤¨ā¤žā¤šāĨ€.", - "maintenance_restore_database_backup": "ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž", - "maintenance_restore_database_backup_description": "ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ģā¤žā¤‡ā¤˛ ā¤ĩā¤žā¤Ē⤰āĨ‚⤍ ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ā¤šāĨā¤¯ā¤ž ā¤ĒāĨ‚⤰āĨā¤ĩāĨ€ā¤šāĨā¤¯ā¤ž ⤏āĨā¤Ĩā¤ŋ⤤āĨ€ā¤¤ ā¤Ē⤰⤤ ā¤•ā¤°ā¤ž", "maintenance_settings": "ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛", "maintenance_settings_description": "Immich ā¤˛ā¤ž ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ąā¤Žā¤§āĨā¤¯āĨ‡ ⤠āĨ‡ā¤ĩā¤ž.", "maintenance_start": "ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ą ⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", "maintenance_start_error": "ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ą ⤏āĨā¤°āĨ‚ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€.", - "maintenance_upload_backup": "ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ģā¤žā¤‡ā¤˛ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", - "maintenance_upload_backup_error": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤¤ā¤ž ā¤†ā¤˛ā¤ž ā¤¨ā¤žā¤šāĨ€, ā¤šāĨ‡ .sql/.sql.gz ā¤Ģā¤žā¤‡ā¤˛ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "manage_concurrency": "ā¤¸ā¤Žā¤žā¤‚ā¤¤ā¤°ā¤¤ā¤ž ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "manage_log_settings": "⤞āĨ‰ā¤— ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ⤍ā¤ŋ⤝⤂⤤āĨā¤°ā¤Ŗ ā¤•ā¤°ā¤ž", "map_dark_style": "ā¤—ā¤Ąā¤Ļ ā¤ļāĨˆā¤˛āĨ€", @@ -272,7 +247,7 @@ "oauth_auto_register": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ⤍āĨ‹ā¤‚ā¤Ļ⤪āĨ€ ā¤•ā¤°ā¤ž", "oauth_auto_register_description": "OAuth ā¤¸ā¤š ā¤¸ā¤žā¤‡ā¤¨ ⤇⤍ ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤ĩ⤰ ⤍ā¤ĩāĨ€ā¤¨ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚ā¤šāĨ€ ⤆ā¤ĒāĨ‹ā¤†ā¤Ē ⤍āĨ‹ā¤‚ā¤Ļ⤪āĨ€ ā¤•ā¤°ā¤ž", "oauth_button_text": "ā¤Ŧ⤟⤪ ā¤Žā¤œā¤•āĨ‚⤰", - "oauth_client_secret_description": "PKCE (ā¤ĒāĨā¤°āĨ‚ā¤Ģ ⤕āĨ€ ā¤ĢāĨ‰ā¤° ⤕āĨ‹ā¤Ą ā¤ā¤•āĨā¤¸ā¤šāĨ‡ā¤‚ā¤œ) OAuth ā¤ĒāĨā¤°ā¤Ļā¤žā¤¤āĨā¤¯ā¤žā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ⤍⤏⤞āĨā¤¯ā¤žā¤¸ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•.", + "oauth_client_secret_description": "PKCE (ā¤ĒāĨā¤°āĨ‚ā¤Ģ ⤕āĨ€ ā¤ĢāĨ‰ā¤° ⤕āĨ‹ā¤Ą ā¤ā¤•āĨā¤¸ā¤šāĨ‡ā¤‚ā¤œ) OAuth ā¤ĒāĨā¤°ā¤Ļā¤žā¤¤āĨā¤¯ā¤žā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤¸ā¤Žā¤°āĨā¤Ĩā¤ŋ⤤ ⤍⤏⤞āĨā¤¯ā¤žā¤¸ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•", "oauth_enable_description": "OAuth ā¤¸ā¤š ⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤•ā¤°ā¤ž", "oauth_mobile_redirect_uri": "ā¤ŽāĨ‹ā¤Ŧā¤žā¤‡ā¤˛ ⤰āĨ€ā¤Ąā¤žā¤¯ā¤°āĨ‡ā¤•āĨā¤Ÿ URI", "oauth_mobile_redirect_uri_override": "ā¤ŽāĨ‹ā¤Ŧā¤žā¤ˆā¤˛ ⤰āĨ€ā¤Ąā¤žā¤¯ā¤°āĨ‡ā¤•āĨā¤Ÿ URI ⤅⤧ā¤ŋ⤞āĨ‡ā¤–⤍", @@ -300,9 +275,6 @@ "refreshing_all_libraries": "⤏⤰āĨā¤ĩ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ⤕⤰āĨ€ā¤¤ ā¤†ā¤šāĨ‡", "registration": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤• ⤍āĨ‹ā¤‚ā¤Ļ⤪āĨ€", "registration_description": "⤆ā¤Ē⤪ ā¤ĒāĨā¤°ā¤Ŗā¤žā¤˛āĨ€ā¤ĩ⤰āĨ€ā¤˛ ā¤Ēā¤šā¤ŋ⤞āĨ‡ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤†ā¤šā¤žā¤¤, ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤆ā¤Ē⤞āĨā¤¯ā¤žā¤˛ā¤ž ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤• ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤍ā¤ŋ⤝āĨā¤•āĨā¤¤ ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤ˆā¤˛ ⤆⤪ā¤ŋ ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•āĨ€ā¤¯ ā¤•ā¤žā¤°āĨā¤¯āĨ‡ ⤆ā¤Ē⤞āĨā¤¯ā¤žā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤šā¤žā¤¤ā¤žā¤ŗā¤˛āĨ€ ā¤œā¤žā¤¤āĨ€ā¤˛; ⤤⤏āĨ‡ā¤š ⤇⤤⤰ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨ‡ ⤆ā¤Ē⤪ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤žā¤˛.", - "release_channel_release_candidate": "⤰ā¤ŋ⤞āĨ€ā¤œ ⤕āĨ…ā¤‚ā¤Ąā¤ŋā¤ĄāĨ‡ā¤Ÿ", - "release_channel_stable": "⤏āĨā¤Ĩā¤ŋ⤰", - "remove_failed_jobs": "⤅ā¤Ē⤝ā¤ļāĨ€ ā¤ā¤žā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤•ā¤žā¤Žā¤žā¤‚ā¤¨ā¤ž ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤ž", "require_password_change_on_login": "ā¤Ēā¤šā¤ŋ⤞āĨā¤¯ā¤ž ⤞āĨ‰ā¤—ā¤ŋ⤍ā¤ĩ⤰ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤¸ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤Ŧā¤Ļ⤞⤪āĨā¤¯ā¤žā¤šāĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•ā¤¤ā¤ž ⤅⤏āĨ‡ā¤˛", "reset_settings_to_default": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĄāĨ€ā¤ĢāĨ‰ā¤˛āĨā¤Ÿā¤ĩ⤰ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "reset_settings_to_recent_saved": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ⤅⤞āĨ€ā¤•ā¤ĄāĨ€ā¤˛ ⤜⤤⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤ĩ⤰ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", @@ -315,10 +287,8 @@ "server_public_users_description": "ā¤¸ā¤žā¤°āĨā¤ĩ⤜⤍ā¤ŋ⤕ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤜āĨ‹ā¤Ąā¤¤ā¤žā¤¨ā¤ž ⤏⤰āĨā¤ĩ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚ā¤šāĨ€ (ā¤¨ā¤žā¤ĩ ā¤ĩ ā¤ˆā¤ŽāĨ‡ā¤˛) ā¤¯ā¤žā¤ĻāĨ€ ā¤Ļ⤰āĨā¤ļā¤ĩ⤞āĨ€ ā¤œā¤žā¤¤āĨ‡. ⤅⤕āĨā¤ˇā¤Ž ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¸, ā¤šāĨ€ ā¤¯ā¤žā¤ĻāĨ€ ā¤Ģ⤕āĨā¤¤ ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•ā¤žā¤‚ā¤¸ā¤žā¤ āĨ€ā¤š ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ⤅⤏āĨ‡ā¤˛.", "server_settings": "⤏⤰āĨā¤ĩāĨā¤šā¤° ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "server_settings_description": "⤏⤰āĨā¤ĩāĨā¤šā¤° ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", - "server_stats_page_description": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•āĨ€ā¤¯ ⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤¸ā¤žā¤‚ā¤–āĨā¤¯ā¤ŋ⤕āĨ€ ā¤ĒāĨƒā¤ˇāĨā¤ ", "server_welcome_message": "⤏āĨā¤ĩā¤žā¤—ā¤¤ ⤏⤂ā¤ĻāĨ‡ā¤ļ", "server_welcome_message_description": "⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤ĒāĨƒā¤ˇāĨā¤ ā¤žā¤ĩ⤰ ā¤Ļ⤰āĨā¤ļā¤ĩā¤ŋ⤪āĨā¤¯ā¤žā¤¤ ⤝āĨ‡ā¤Ŗā¤žā¤°ā¤ž ⤏⤂ā¤ĻāĨ‡ā¤ļāĨ¤", - "settings_page_description": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•āĨ€ā¤¯ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĒāĨƒā¤ˇāĨā¤ ", "sidecar_job": "ā¤¸ā¤žā¤‡ā¤Ąā¤•ā¤žā¤° ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž", "sidecar_job_description": "ā¤Ģā¤žā¤ˆā¤˛ ⤏ā¤ŋ⤏āĨā¤Ÿā¤Žā¤Žā¤§āĨ‚⤍ ā¤¸ā¤žā¤‡ā¤Ąā¤•ā¤žā¤° ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ā¤ļāĨ‹ā¤§ā¤ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤ŋ⤤ ā¤•ā¤°ā¤ž", "slideshow_duration_description": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤕ā¤ŋ⤤āĨ€ ⤏āĨ‡ā¤•⤂ā¤Ļ ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļā¤ŋ⤤ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ€", @@ -396,10 +366,6 @@ "transcoding_preferred_hardware_device_description": "⤕āĨ‡ā¤ĩ⤺ VAAPI ⤆⤪ā¤ŋ QSV ā¤¸ā¤žā¤ āĨ€ ā¤˛ā¤žā¤—āĨ‚. ā¤šā¤žā¤°āĨā¤Ąā¤ĩāĨ‡ā¤…⤰ ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ā¤¸ā¤žā¤ āĨ€ ā¤ĩā¤žā¤Ēā¤°ā¤˛ā¤ž ā¤œā¤žā¤Ŗā¤žā¤°ā¤ž DRI ⤍āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž.", "transcoding_preset_preset": "ā¤ĒāĨā¤°āĨ€ā¤¸āĨ‡ā¤Ÿ (–preset)", "transcoding_preset_preset_description": "⤏⤂⤕āĨā¤šā¤¨ ⤗⤤āĨ€. ⤕ā¤ŋā¤‚ā¤šā¤ŋ⤤ ā¤Žā¤‚ā¤Ļ ā¤ĒāĨā¤°āĨ€ā¤¸āĨ‡ā¤ŸāĨā¤¸ ā¤˛ā¤šā¤žā¤¨ ā¤Ģā¤žā¤‡ā¤˛ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤¤ā¤žā¤¤ ⤆⤪ā¤ŋ ā¤ ā¤°ā¤žā¤ĩā¤ŋ⤕ ā¤Ŧā¤ŋ⤟⤰āĨ‡ā¤Ÿā¤¸ā¤žā¤ āĨ€ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž ā¤ĩā¤žā¤ĸā¤ĩā¤¤ā¤žā¤¤. VP9 ‘faster’ ā¤ĒāĨ‡ā¤•āĨā¤ˇā¤ž ā¤œā¤žā¤¸āĨā¤¤ ⤗⤤āĨ€ ⤞⤕āĨā¤ˇā¤žā¤¤ ⤘āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€.", - "transcoding_realtime": "⤰ā¤ŋ⤅⤞-ā¤Ÿā¤žā¤‡ā¤Ž ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ [ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤•]", - "transcoding_realtime_description": "ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ĒāĨā¤°ā¤•āĨā¤ˇāĨ‡ā¤Ēā¤ŋ⤤ ā¤šāĨ‹ā¤¤ ā¤…ā¤¸ā¤¤ā¤žā¤¨ā¤ž ⤰ā¤ŋ⤅⤞-ā¤Ÿā¤žā¤‡ā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤕⤰⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨ‡ā¤¤āĨ‡. ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž ⤏āĨā¤ĩā¤ŋ⤚ā¤ŋ⤂⤗ ⤏⤕āĨā¤ˇā¤Ž ⤕⤰⤤āĨ‡, ā¤Ē⤰⤂⤤āĨ ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤šāĨā¤¯ā¤ž ⤕āĨā¤ˇā¤Žā¤¤āĨ‡ā¤ĩ⤰ ⤅ā¤ĩ⤞⤂ā¤ŦāĨ‚⤍ ā¤ĒāĨā¤˛āĨ‡ā¤ŦāĨ…ā¤•ā¤Žā¤§āĨā¤¯āĨ‡ ⤅⤧ā¤ŋ⤕ ā¤ĩā¤ŋ⤞⤂ā¤Ŧ ⤆⤪ā¤ŋ ā¤…ā¤Ąā¤Ĩ⤺āĨ‡ ⤝āĨ‡ā¤Š ā¤ļā¤•ā¤¤ā¤žā¤¤.", - "transcoding_realtime_enabled": "⤰ā¤ŋ⤅⤞-ā¤Ÿā¤žā¤‡ā¤Ž ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", - "transcoding_realtime_enabled_description": "⤅⤕āĨā¤ˇā¤Ž ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¸, ⤏⤰āĨā¤ĩāĨā¤šā¤° ⤍ā¤ĩāĨ€ā¤¨ ⤰ā¤ŋ⤅⤞-ā¤Ÿā¤žā¤‡ā¤Ž ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤏⤤āĨā¤°āĨ‡ ⤏āĨā¤°āĨ‚ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ ā¤¨ā¤•ā¤žā¤° ā¤ĻāĨ‡ā¤ˆā¤˛.", "transcoding_reference_frames": "⤏⤂ā¤Ļ⤰āĨā¤­ ā¤ĢāĨā¤°āĨ‡ā¤ŽāĨā¤¸", "transcoding_reference_frames_description": "ā¤Ļā¤ŋ⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĢāĨā¤°āĨ‡ā¤Žā¤šāĨ‡ ⤏⤂⤕āĨā¤šā¤¨ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤕ā¤ŋ⤤āĨ€ ā¤ĢāĨā¤°āĨ‡ā¤ŽāĨā¤¸ ⤏⤂ā¤Ļ⤰āĨā¤­ā¤ŋ⤤ ā¤•ā¤°ā¤žā¤ĩāĨā¤¯ā¤žā¤¤ ā¤šāĨ‡ ⤠⤰ā¤ĩ⤤āĨ‡. ā¤œā¤žā¤¸āĨā¤¤ ā¤ŽāĨ‚⤞āĨā¤¯āĨ‡ ⤏⤂⤕āĨā¤šā¤¨ ā¤•ā¤žā¤°āĨā¤¯ā¤•āĨā¤ˇā¤Žā¤¤āĨ‡ā¤¤ ⤏āĨā¤§ā¤žā¤°ā¤Ŗā¤ž ā¤•ā¤°ā¤¤ā¤žā¤¤, ā¤Ē⤰⤂⤤āĨ ā¤ā¤¨āĨā¤•āĨ‹ā¤Ąā¤ŋ⤂⤗ ā¤Žā¤‚ā¤Ļ ā¤•ā¤°ā¤¤ā¤žā¤¤. 0 ⤠āĨ‡ā¤ĩ⤞āĨā¤¯ā¤žā¤¸ ā¤šāĨ‡ ⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ā¤Ē⤪āĨ‡ ⤏āĨ‡ā¤Ÿ ā¤šāĨ‹ā¤¤āĨ‡.", "transcoding_required_description": "ā¤Ģ⤕āĨā¤¤ ā¤Žā¤žā¤¨āĨā¤¯ ā¤ĒāĨā¤°ā¤žā¤°āĨ‚ā¤Ēā¤žā¤¤ ⤍⤏⤞āĨ‡ā¤˛āĨ‡ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", @@ -441,10 +407,6 @@ "user_restore_scheduled_removal": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž – ⤍ā¤ŋ⤝āĨ‹ā¤œā¤ŋ⤤ ā¤šā¤Ÿā¤ĩā¤ŋ⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤¤ā¤žā¤°āĨ€ā¤– {date, date, long}", "user_settings": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "user_settings_description": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", - "user_successfully_removed": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž {email} ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤ŖāĨā¤¯ā¤žā¤¤ ā¤†ā¤˛ā¤ž ā¤†ā¤šāĨ‡.", - "users_page_description": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤• ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚ā¤šāĨ‡ ā¤ĒāĨƒā¤ˇāĨā¤ ", - "version_check_channel": "ā¤ĩā¤ŋā¤ŽāĨ‹ā¤šā¤¨ ā¤Žā¤žā¤°āĨā¤—", - "version_check_channel_description": "⤆ā¤Ē⤞āĨā¤¯ā¤žā¤˛ā¤ž ⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤘āĨ‹ā¤ˇā¤Ŗā¤ž ā¤Žā¤ŋ⤺ā¤ĩā¤žā¤¯ā¤šāĨā¤¯ā¤ž ⤅⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĒāĨā¤°ā¤•ā¤žā¤ļ⤍ ⤚āĨ…⤍āĨ‡ā¤˛ā¤šāĨ€ ⤍ā¤ŋā¤ĩā¤Ą ā¤•ā¤°ā¤ž", "version_check_enabled_description": "⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "version_check_implications": "⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ā¤ĩāĨˆā¤ļā¤ŋ⤎āĨā¤ŸāĨā¤¯ {server} ⤏āĨ‹ā¤Ŧ⤤ ⤆ā¤ĩ⤰āĨā¤¤āĨ€ ⤏⤂ā¤ĩā¤žā¤Ļā¤žā¤ĩ⤰ ⤅ā¤ĩ⤞⤂ā¤ŦāĨ‚⤍ ā¤†ā¤šāĨ‡", "version_check_settings": "⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€", @@ -456,9 +418,8 @@ "admin_password": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤• ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", "administration": "ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤¨", "advanced": "ā¤ĒāĨā¤°ā¤—⤤", - "advanced_settings_clear_image_cache": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤕āĨ…ā¤ļ ā¤šā¤Ÿā¤ĩā¤ž", - "advanced_settings_clear_image_cache_error": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤕āĨ…ā¤ļ ā¤¸ā¤žā¤Ģ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", - "advanced_settings_clear_image_cache_success": "⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž {size} ā¤Ēā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡", + "advanced_settings_enable_alternate_media_filter_subtitle": "⤏ā¤ŋ⤂⤕ ā¤Ļā¤°ā¤ŽāĨā¤¯ā¤žā¤¨ ā¤ĩāĨˆā¤•⤞āĨā¤Ēā¤ŋ⤕ ⤍ā¤ŋā¤•ā¤ˇā¤žā¤‚ā¤ĩ⤰ ā¤†ā¤§ā¤žā¤°ā¤ŋ⤤ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤° ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤šā¤ž ā¤Ē⤰āĨā¤¯ā¤žā¤¯ ā¤ĩā¤žā¤Ēā¤°ā¤ž. āĨ˛ā¤Ē ⤏⤰āĨā¤ĩ ⤅⤞āĨā¤Ŧā¤Ž ⤓⤺⤖⤪āĨā¤¯ā¤žā¤¤ ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž ⤝āĨ‡ā¤¤ ⤅⤏⤞āĨā¤¯ā¤žā¤¸ā¤š ā¤ĩā¤žā¤Ēā¤°ā¤ž.", + "advanced_settings_enable_alternate_media_filter_title": "[ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤•] ⤉ā¤Ē⤕⤰⤪-ā¤†ā¤§ā¤žā¤°ā¤ŋ⤤ ⤅⤞āĨā¤Ŧā¤Ž ⤏ā¤ŋ⤂⤕ ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤° ā¤ĩā¤žā¤Ēā¤°ā¤ž", "advanced_settings_log_level_title": "⤞āĨ‰ā¤— ā¤Ēā¤žā¤¤ā¤ŗāĨ€: {level}", "advanced_settings_prefer_remote_subtitle": "ā¤•ā¤žā¤šāĨ€ ⤉ā¤Ē⤕⤰⤪āĨ‡ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ⤅āĨ…⤏āĨ‡ā¤Ÿā¤Žā¤§āĨ‚⤍ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤖āĨ‚ā¤Ē ā¤Žā¤‚ā¤Ļ ā¤†ā¤šāĨ‡ā¤¤. ⤤āĨā¤¯ā¤žā¤ā¤ĩ⤜āĨ€ ⤰ā¤ŋā¤ŽāĨ‹ā¤Ÿ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤šā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž.", "advanced_settings_prefer_remote_title": "⤰ā¤ŋā¤ŽāĨ‹ā¤Ÿ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ā¤Ē⤏⤂⤤ ā¤•ā¤°ā¤ž", @@ -466,6 +427,8 @@ "advanced_settings_proxy_headers_title": "⤕⤏āĨā¤Ÿā¤Ž ā¤ĒāĨā¤°āĨ‰ā¤•āĨā¤¸āĨ€ ā¤šāĨ‡ā¤Ąā¤°āĨā¤¸ [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", "advanced_settings_readonly_mode_subtitle": "ā¤¯ā¤ž ā¤ŽāĨ‹ā¤Ąā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤Ģ⤕āĨā¤¤ ā¤Ēā¤žā¤šā¤¤ā¤ž ⤝āĨ‡ā¤¤ā¤žā¤¤ - ⤅⤍āĨ‡ā¤• ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤍ā¤ŋā¤ĩā¤Ąā¤ŖāĨ‡, ā¤ļāĨ‡ā¤…⤰ ⤕⤰⤪āĨ‡, ā¤•ā¤žā¤¸āĨā¤Ÿ ⤕⤰⤪āĨ‡ ⤆⤪ā¤ŋ ā¤šā¤Ÿā¤ĩ⤪āĨ‡ ⤅ā¤ļā¤ž ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤍ā¤ŋ⤎āĨā¤•āĨā¤°ā¤ŋ⤝ ā¤°ā¤žā¤šā¤¤ā¤žā¤¤. ā¤ŽāĨā¤–āĨā¤¯ ⤏āĨā¤•āĨā¤°āĨ€ā¤¨ā¤ĩ⤰āĨ€ā¤˛ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤅ā¤ĩā¤¤ā¤žā¤°ā¤žā¤¤āĨ‚⤍ ā¤šā¤ž ā¤ŽāĨ‹ā¤Ą ā¤šā¤žā¤˛āĨ‚ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž", "advanced_settings_readonly_mode_title": "ā¤Ģ⤕āĨā¤¤ ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤šā¤ž ā¤ŽāĨ‹ā¤Ą", + "advanced_settings_self_signed_ssl_subtitle": "⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤ā¤‚ā¤Ąā¤ĒāĨ‰ā¤‡ā¤‚ā¤Ÿā¤¸ā¤žā¤ āĨ€ SSL ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ⤏⤤āĨā¤¯ā¤žā¤Ē⤍ ā¤ĩ⤗⤺⤤āĨ‡. ⤏āĨā¤ĩā¤žā¤•āĨā¤ˇā¤°āĨ€ā¤¤ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤°ā¤žā¤‚ā¤¸ā¤žā¤ āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•.", + "advanced_settings_self_signed_ssl_title": "⤏āĨā¤ĩ⤤⤃ ⤏āĨā¤ĩā¤žā¤•āĨā¤ˇā¤°āĨ€ā¤¤ SSL ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤°ā¤žā¤‚ā¤¨ā¤ž ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", "advanced_settings_sync_remote_deletions_subtitle": "ā¤ĩāĨ‡ā¤Ŧā¤ĩ⤰ ā¤šāĨ€ ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤕āĨ‡ā¤˛āĨ€ ⤗āĨ‡ā¤˛āĨā¤¯ā¤žā¤ĩ⤰ ā¤¯ā¤ž ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰ ⤅⤏⤞āĨ‡ā¤˛āĨ‡ ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤆ā¤ĒāĨ‹ā¤†ā¤Ē ā¤šā¤Ÿā¤ĩā¤ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž", "advanced_settings_sync_remote_deletions_title": "⤰ā¤ŋā¤ŽāĨ‹ā¤Ÿ ā¤šā¤Ÿā¤ĩ⤪āĨā¤¯ā¤ž ⤏ā¤ŋ⤂⤕ ā¤•ā¤°ā¤ž [ā¤ĒāĨā¤°ā¤¯āĨ‹ā¤—ā¤žā¤¤āĨā¤Žā¤•]", "advanced_settings_tile_subtitle": "ā¤ĒāĨā¤°ā¤—⤤ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", @@ -481,7 +444,11 @@ "album_delete_confirmation": "⤆ā¤Ē⤪ ⤍ā¤ŋā¤ļāĨā¤šā¤ŋ⤤⤚ ⤅⤞āĨā¤Ŧā¤Ž {album} ā¤šā¤Ÿā¤ĩā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤•ā¤ž?", "album_delete_confirmation_description": "ā¤šā¤ž ⤅⤞āĨā¤Ŧā¤Ž ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛ā¤ž ⤅⤏āĨ‡ā¤˛ ⤤⤰ ⤇⤤⤰ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨ‡ ā¤†ā¤¤ā¤ž ⤤āĨ‹ ā¤Ēā¤žā¤šāĨ‚ ā¤ļā¤•ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€ā¤¤.", "album_deleted": "⤅⤞āĨā¤Ŧā¤Ž ā¤šā¤Ÿā¤ĩ⤞āĨ‡", + "album_info_card_backup_album_excluded": "ā¤ĩ⤗⤺⤞āĨ‡ ⤗āĨ‡ā¤˛āĨ‡", + "album_info_card_backup_album_included": "ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ", "album_info_updated": "⤅⤞āĨā¤Ŧā¤Ž ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤", + "album_leave": "⤅⤞āĨā¤Ŧā¤Ž ⤏āĨ‹ā¤Ąā¤Ŗā¤žā¤° ā¤•ā¤ž?", + "album_leave_confirmation": "⤆ā¤Ē⤪ ⤍ā¤ŋā¤ļāĨā¤šā¤ŋ⤤⤚ ⤅⤞āĨā¤Ŧā¤Ž {album} ⤏āĨ‹ā¤Ąā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤•ā¤ž?", "album_name": "⤅⤞āĨā¤Ŧā¤Žā¤šāĨ‡ ā¤¨ā¤žā¤ĩ", "album_options": "⤅⤞āĨā¤Ŧā¤Ž ā¤Ē⤰āĨā¤¯ā¤žā¤¯", "album_remove_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤žā¤¯ā¤šā¤ž ā¤•ā¤ž?", @@ -491,8 +458,15 @@ "album_summary": "⤅⤞āĨā¤Ŧā¤Ž ā¤¸ā¤žā¤°ā¤žā¤‚ā¤ļ", "album_updated": "⤅⤞āĨā¤Ŧā¤Ž ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤", "album_updated_setting_description": "ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤍ā¤ĩāĨ€ā¤¨ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤆⤞āĨā¤¯ā¤žā¤¸ ā¤ˆā¤ŽāĨ‡ā¤˛ ⤏āĨ‚ā¤šā¤¨ā¤žā¤°āĨā¤Ĩ ā¤ĒāĨā¤°ā¤žā¤ĒāĨā¤¤ ā¤•ā¤°ā¤ž", - "album_upload_assets": "⤆ā¤Ē⤞āĨā¤¯ā¤ž ā¤¸ā¤‚ā¤—ā¤Ŗā¤•ā¤žā¤¤āĨ‚⤍ ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤ž ⤆⤪ā¤ŋ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", + "album_user_left": "⤏āĨ‹ā¤Ąā¤˛āĨ‡: {album}", + "album_user_removed": "ā¤•ā¤žā¤ĸ⤞āĨ‡: {user}", + "album_viewer_appbar_delete_confirm": "⤆ā¤Ē⤪ ⤍ā¤ŋā¤ļāĨā¤šā¤ŋ⤤⤚ ā¤šā¤ž ⤅⤞āĨā¤Ŧā¤Ž ⤆ā¤Ē⤞āĨā¤¯ā¤ž ā¤–ā¤žā¤¤āĨā¤¯ā¤žā¤¤āĨ‚⤍ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "album_viewer_appbar_share_err_delete": "⤅⤞āĨā¤Ŧā¤Ž ā¤šā¤Ÿā¤ĩ⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "album_viewer_appbar_share_err_leave": "⤅⤞āĨā¤Ŧā¤Ž ⤏āĨ‹ā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "album_viewer_appbar_share_err_remove": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤•ā¤žā¤ĸ⤪āĨā¤¯ā¤žā¤¤ ā¤…ā¤Ąā¤šā¤ŖāĨ€", + "album_viewer_appbar_share_err_title": "⤅⤞āĨā¤Ŧā¤Žā¤šā¤‚ ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤Ŧā¤Ļ⤞⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "album_viewer_appbar_share_leave": "⤅⤞āĨā¤Ŧā¤Ž ⤏āĨ‹ā¤Ąā¤ž", + "album_viewer_appbar_share_to": "ā¤¯ā¤žā¤‚ā¤¨ā¤ž ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤ž", "album_viewer_page_share_add_users": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨ‡ ⤜āĨ‹ā¤Ąā¤ž", "album_with_link_access": "⤞ā¤ŋ⤂⤕ ⤅⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤¸ ā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ€ā¤˛ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ⤞āĨ‹ā¤• ā¤Ēā¤žā¤šā¤¤ā¤ž ⤝āĨ‡ā¤¤āĨ€ā¤˛.", "albums": "⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸", @@ -504,22 +478,21 @@ "all": "⤏⤰āĨā¤ĩ", "all_albums": "⤏⤰āĨā¤ĩ ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸", "all_people": "⤏⤰āĨā¤ĩ ⤞āĨ‹ā¤•", - "all_photos": "⤏⤰āĨā¤ĩ ā¤ĢāĨ‹ā¤ŸāĨ‹", "all_videos": "⤏⤰āĨā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", "allow_dark_mode": "ā¤Ąā¤žā¤°āĨā¤• ā¤ŽāĨ‹ā¤Ąā¤˛ā¤ž ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž", + "allow_edits": "⤏⤂ā¤Ēā¤žā¤Ļā¤¨ā¤žā¤‚ā¤¨ā¤ž ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž", "allow_public_user_to_download": "ā¤¸ā¤žā¤°āĨā¤ĩ⤜⤍ā¤ŋ⤕ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž", "allow_public_user_to_upload": "ā¤¸ā¤žā¤°āĨā¤ĩ⤜⤍ā¤ŋ⤕ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž", "allowed": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤†ā¤šāĨ‡", "alt_text_qr_code": "QR ⤕āĨ‹ā¤Ą ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž", - "always_keep": "⤍āĨ‡ā¤šā¤ŽāĨ€ ⤠āĨ‡ā¤ĩā¤ž", - "always_keep_photos_hint": "Free Up Space ā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤜ā¤ĒāĨ‚⤍ ⤠āĨ‡ā¤ĩāĨ‡ā¤˛.", - "always_keep_videos_hint": "Free Up Space ā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤜ā¤ĒāĨ‚⤍ ⤠āĨ‡ā¤ĩāĨ‡ā¤˛.", + "anti_clockwise": "ā¤˜ā¤ĄāĨ€ā¤šāĨā¤¯ā¤ž ā¤‰ā¤˛ā¤Ÿ ā¤Ļā¤ŋā¤ļāĨ‡ā¤¨āĨ‡", "api_key": "ā¤ā¤ĒāĨ€ā¤†ā¤ˆ ⤕āĨ€", "api_key_description": "ā¤šā¤ž ā¤ŽāĨ‚⤞āĨā¤¯ ā¤ā¤•ā¤Ļā¤žā¤š ā¤Ļā¤žā¤–ā¤ĩā¤ŋā¤˛ā¤ž ā¤œā¤žā¤ˆā¤˛. ⤕āĨƒā¤Ēā¤¯ā¤ž ā¤ĩā¤ŋā¤‚ā¤ĄāĨ‹ ā¤Ŧ⤂ā¤Ļ ⤕⤰⤪āĨā¤¯ā¤žā¤ĒāĨ‚⤰āĨā¤ĩāĨ€ ⤤āĨ‡ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤žā¤¯ā¤˛ā¤ž ā¤ĩā¤ŋ⤏⤰āĨ‚ ā¤¨ā¤•ā¤ž.", "api_key_empty": "⤆ā¤Ē⤞āĨ‡ API ⤕āĨ€ ā¤¨ā¤žā¤ĩ ⤰ā¤ŋ⤕āĨā¤¤ ⤅⤏āĨ‚ ⤍⤝āĨ‡", "api_keys": "API ⤕āĨ€ā¤œ", "app_architecture_variant": "ā¤ĒāĨā¤°ā¤•ā¤žā¤° (⤆⤰āĨā¤•ā¤ŋ⤟āĨ‡ā¤•āĨā¤šā¤°)", "app_bar_signout_dialog_content": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ ā¤¸ā¤žā¤‡ā¤¨ ā¤†ā¤‰ā¤Ÿ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", + "app_bar_signout_dialog_ok": "ā¤šāĨ‹", "app_bar_signout_dialog_title": "ā¤¸ā¤žā¤‡ā¤¨ ā¤†ā¤‰ā¤Ÿ", "app_download_links": "⤅āĨ…ā¤Ē ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤞ā¤ŋ⤂⤕⤏āĨ", "app_settings": "ā¤…â€āĨ…ā¤Ē ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", @@ -530,49 +503,62 @@ "archive": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š", "archive_action_prompt": "{count} ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤šā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡", "archive_or_unarchive_photo": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ā¤•ā¤°ā¤ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤅⤍⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ā¤•ā¤°ā¤ž", + "archive_page_no_archived_assets": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨā¤¯ā¤ž ā¤¨ā¤žā¤šāĨ€ā¤¤", + "archive_page_title": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ({count})", "archive_size": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ā¤†ā¤•ā¤žā¤°", "archive_size_description": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ąā¤¸ā¤žā¤ āĨ€ ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ā¤†ā¤•ā¤žā¤° (GiB ā¤Žā¤§āĨā¤¯āĨ‡) ⤏āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "archived": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡", "archived_count": "{count, plural, other {⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ #}}", "are_these_the_same_person": "ā¤šāĨ‡ ā¤ĻāĨ‹ā¤¨āĨā¤šāĨ€ ā¤ā¤•ā¤žā¤š ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤†ā¤šāĨ‡ā¤¤ ā¤•ā¤ž?", "are_you_sure_to_do_this": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ā¤šāĨ‡ ā¤–ā¤°ā¤‚ā¤š ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", + "asset_action_delete_err_read_only": "ā¤ĩā¤žā¤šā¤¨-⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ ā¤¸ā¤žā¤§ā¤¨(āĨ‡) ā¤šā¤Ÿā¤ĩā¤ŋā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤞āĨ‡ ā¤œā¤žā¤¤ ā¤†ā¤šāĨ‡", + "asset_action_share_err_offline": "⤑ā¤Ģā¤˛ā¤žā¤‡ā¤¨ ā¤¸ā¤žā¤§ā¤¨(āĨ‡) ā¤Žā¤ŋ⤺ā¤ĩā¤ŋā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤞āĨ‡ ā¤œā¤žā¤¤ ā¤†ā¤šāĨ‡", "asset_added_to_album": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡ ⤗āĨ‡ā¤˛āĨ‡", "asset_adding_to_album": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ ā¤†ā¤šāĨ‡â€Ļ", - "asset_created": "ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ā¤†ā¤˛ā¤ž", - "asset_day_count": "{date}: {count, plural, ā¤ā¤• {# asset} ⤇⤤⤰ {# assets}}", "asset_description_updated": "ā¤¸ā¤žā¤§ā¤¨ā¤žā¤šāĨ‡ ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤅ā¤ĻāĨā¤¯ā¤žā¤ĩ⤤ ⤕āĨ‡ā¤˛āĨ‡ ⤗āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡", + "asset_filename_is_offline": "{filename} ā¤¨ā¤žā¤ĩā¤žā¤šāĨ‡ ā¤¸ā¤žā¤§ā¤¨ ⤑ā¤Ģā¤˛ā¤žā¤‡ā¤¨ ā¤†ā¤šāĨ‡", + "asset_has_unassigned_faces": "ā¤¸ā¤žā¤§ā¤¨ā¤žā¤¤ ā¤…ā¤¸ā¤žā¤§āĨā¤¯ ⤚āĨ‡ā¤šā¤ąāĨā¤¯ā¤žā¤‚ā¤šāĨ€ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ā¤†ā¤šāĨ‡", "asset_hashing": "ā¤šāĨ…ā¤ļā¤ŋ⤂⤗â€Ļ", "asset_list_group_by_sub_title": "ā¤—ā¤Ÿā¤žā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤•ā¤°ā¤ž", + "asset_list_layout_settings_dynamic_layout_title": "ā¤Ąā¤žā¤¯ā¤¨āĨ…ā¤Žā¤ŋ⤕ ⤞āĨ‡ā¤†ā¤‰ā¤Ÿ", + "asset_list_layout_settings_group_automatically": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤", "asset_list_layout_settings_group_by": "ā¤¸ā¤žā¤§ā¤¨āĨ‡ ā¤—ā¤Ÿā¤žā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤•ā¤°ā¤ž", "asset_list_layout_settings_group_by_month_day": "ā¤Žā¤šā¤ŋā¤¨ā¤ž + ā¤Ļā¤ŋā¤ĩ⤏", "asset_list_layout_sub_title": "⤞āĨ‡ā¤†ā¤‰ā¤Ÿ", "asset_list_settings_subtitle": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤗āĨā¤°ā¤ŋā¤Ą ⤞āĨ‡ā¤†ā¤‰ā¤Ÿ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "asset_list_settings_title": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤗āĨā¤°ā¤ŋā¤Ą", - "asset_not_found_on_device_android": "⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰ ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨ€ ā¤¨ā¤žā¤šāĨ€", - "asset_not_found_on_device_ios": "ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰ ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ā¤¸ā¤žā¤Ēā¤Ąā¤˛ā¤ž ā¤¨ā¤žā¤šāĨ€. ⤜⤰ ⤆ā¤Ē⤪ iCloud ā¤ĩā¤žā¤Ē⤰⤤ ā¤…ā¤¸ā¤žā¤˛, ⤤⤰ iCloud ā¤Žā¤§āĨā¤¯āĨ‡ ⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ā¤–ā¤°ā¤žā¤Ŧ ā¤Ģā¤žā¤‡ā¤˛ā¤ŽāĨā¤ŗāĨ‡ ⤤āĨ‹ ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ⤍⤏āĨ‡ā¤˛", - "asset_not_found_on_icloud": "iCloud ā¤ĩ⤰ ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ā¤¸ā¤žā¤Ēā¤Ąā¤˛ā¤ž ā¤¨ā¤žā¤šāĨ€. iCloud ā¤Žā¤§āĨā¤¯āĨ‡ ⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ā¤–ā¤°ā¤žā¤Ŧ ā¤Ģā¤žā¤‡ā¤˛ā¤ŽāĨā¤ŗāĨ‡ ā¤šā¤ž ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ⤍⤏āĨ‚ ā¤ļ⤕⤤āĨ‹", "asset_offline": "ā¤¸ā¤žā¤§ā¤¨ ⤑ā¤Ģā¤˛ā¤žā¤‡ā¤¨ ā¤†ā¤šāĨ‡", "asset_offline_description": "ā¤šāĨ‡ ā¤Ŧā¤žā¤šāĨā¤¯ ā¤¸ā¤žā¤§ā¤¨ ā¤†ā¤¤ā¤ž ā¤Ąā¤ŋ⤏āĨā¤•ā¤ĩ⤰ ā¤¸ā¤žā¤Ēā¤Ąā¤¤ ā¤¨ā¤žā¤šāĨ€. ā¤Žā¤Ļ⤤āĨ€ā¤¸ā¤žā¤ āĨ€ ⤆ā¤Ē⤞āĨā¤¯ā¤ž Immich ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•ā¤žā¤ļāĨ€ ⤏⤂ā¤Ē⤰āĨā¤• ā¤•ā¤°ā¤ž.", + "asset_restored_successfully": "ā¤¸ā¤žā¤§ā¤¨ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤Ē⤪āĨ‡ ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡ ⤗āĨ‡ā¤˛āĨ‡", "asset_skipped": "ā¤ĩ⤗⤺⤞āĨ‡", "asset_skipped_in_trash": "⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡", + "asset_trashed": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩā¤˛ā¤ž", "asset_troubleshoot": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤¸ā¤Žā¤¸āĨā¤¯ā¤žā¤¨ā¤ŋā¤ĩā¤žā¤°ā¤Ŗ", "asset_uploaded": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ā¤žā¤˛āĨ‡", "asset_uploading": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡â€Ļ", "asset_viewer_settings_subtitle": "⤆ā¤Ē⤞āĨā¤¯ā¤ž ⤗āĨ…⤞⤰āĨ€ ā¤ĩāĨā¤šāĨā¤¯āĨ‚ā¤…ā¤°ā¤šāĨā¤¯ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "asset_viewer_settings_title": "ā¤¸ā¤žā¤§ā¤¨ ā¤Ļ⤰āĨā¤ļ⤕", "assets": "ā¤¸ā¤žā¤§ā¤¨āĨ‡", + "assets_added_count": "{count, plural, one {# ā¤¸ā¤žā¤§ā¤¨ ⤜āĨ‹ā¤Ąā¤˛āĨ‡} other {# ā¤¸ā¤žā¤§ā¤¨āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡}}", "assets_added_to_album_count": "{count, plural, one {# ā¤¸ā¤žā¤§ā¤¨ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡} other {# ā¤¸ā¤žā¤§ā¤¨āĨ‡ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡}}", "assets_added_to_albums_count": "{albumTotal, plural, one {# ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡} other {# ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡}} {assetTotal, plural, one {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤•} other {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤•}} ⤜āĨ‹ā¤Ąā¤˛āĨ‡", "assets_cannot_be_added_to_album_count": "{count, plural, one {# ā¤¸ā¤žā¤§ā¤¨ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€} other {# ā¤¸ā¤žā¤§ā¤¨āĨ‡ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€ā¤¤}}", "assets_cannot_be_added_to_albums": "{count, plural, one {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€} other {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤}}", "assets_count": "{count, plural, one {# ā¤¸ā¤žā¤§ā¤¨} other {# ā¤¸ā¤žā¤§ā¤¨āĨ‡}}", + "assets_deleted_permanently": "{count} ā¤¸ā¤žā¤§ā¤¨(āĨ‡) ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩā¤ŋ⤞āĨ‡", + "assets_deleted_permanently_from_server": "Immich ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰āĨ‚⤍ {count} ā¤¸ā¤žā¤§ā¤¨(āĨ‡) ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩā¤ŋ⤞āĨ‡", + "assets_downloaded_failed": "{count, plural, one {ā¤ā¤• ā¤Ģā¤žā¤ˆā¤˛ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€: {error}} other {# ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€: {error}}}", + "assets_downloaded_successfully": "{count, plural, one {ā¤ā¤• ā¤Ģā¤žā¤ˆā¤˛ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ā¤žā¤˛āĨ€} other {# ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ā¤žā¤˛āĨā¤¯ā¤ž}}", "assets_moved_to_trash_count": "{count, plural, one {ā¤ā¤• ā¤Ģā¤žā¤ˆā¤˛ ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩ⤞āĨ€} other {# ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩ⤞āĨā¤¯ā¤ž}}", "assets_permanently_deleted_count": "{count, plural, one {ā¤ā¤• ā¤Ģā¤žā¤ˆā¤˛ ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩ⤞āĨ€} other {# ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩ⤞āĨā¤¯ā¤ž}}", "assets_removed_count": "{count, plural, one {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤˛ā¤ž} other {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤˛āĨ‡}}", + "assets_removed_permanently_from_device": "{count} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤•ā¤žā¤ĸ⤞āĨ‡ ⤗āĨ‡ā¤˛āĨ‡", "assets_restore_confirmation": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ā¤˛āĨ‡ ⤏⤰āĨā¤ĩ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ā¤¤ ā¤•ā¤ž? ā¤šāĨ€ ⤕āĨƒā¤¤āĨ€ ā¤ĒāĨ‚⤰āĨā¤ĩā¤ĩ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€. ⤞⤕āĨā¤ˇā¤žā¤¤ ⤠āĨ‡ā¤ĩā¤ž - ⤑ā¤Ģā¤˛ā¤žā¤‡ā¤¨ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤅ā¤ļā¤ž ā¤ĒāĨā¤°ā¤•ā¤žā¤°āĨ‡ ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤.", "assets_restored_count": "{count, plural, one {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ⤕āĨ‡ā¤˛ā¤ž} other {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡}}", + "assets_restored_successfully": "{count} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤ā¤žā¤˛āĨ‡", "assets_trashed": "{count} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨ‡", "assets_trashed_count": "{count, plural, one {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩā¤˛ā¤ž} other {# ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨ‡}}", + "assets_trashed_from_server": "{count} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• Immich ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰āĨ‚⤍ ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨ‡", "assets_were_part_of_album_count": "{count, plural, one {ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤆⤧āĨ€ā¤š ⤤āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤šā¤ž ā¤­ā¤žā¤— ā¤šāĨ‹ā¤¤ā¤ž} other {ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤆⤧āĨ€ā¤š ⤤āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤šā¤ž ā¤­ā¤žā¤— ā¤šāĨ‹ā¤¤āĨ‡}}", "assets_were_part_of_albums_count": "{count, plural, one {ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤆⤧āĨ€ā¤š ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸ā¤šā¤ž ā¤­ā¤žā¤— ā¤šāĨ‹ā¤¤ā¤ž} other {ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤆⤧āĨ€ā¤š ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸ā¤šā¤ž ā¤­ā¤žā¤— ā¤šāĨ‹ā¤¤āĨ‡}}", "authorized_devices": "⤅⤧ā¤ŋ⤕āĨƒā¤¤ ⤉ā¤Ē⤕⤰⤪āĨ‡", @@ -581,48 +567,86 @@ "autoplay_slideshow": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ⤏āĨā¤˛ā¤žā¤‡ā¤Ąā¤ļāĨ‹", "back": "ā¤Žā¤žā¤—āĨ‡", "back_close_deselect": "ā¤Žā¤žā¤—āĨ‡ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž / ⤍ā¤ŋā¤ĩā¤Ą ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", + "background_backup_running_error": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ŦāĨ…⤕⤅ā¤Ē ⤏⤧āĨā¤¯ā¤ž ā¤šā¤žā¤˛āĨ‚ ā¤†ā¤šāĨ‡, ā¤ŽāĨ…⤍āĨā¤¯āĨā¤…⤞ ā¤ŦāĨ…⤕⤅ā¤Ē ⤏āĨā¤°āĨ‚ ⤕⤰āĨ‚ ā¤ļ⤕⤤ ā¤¨ā¤žā¤šāĨ€", "background_location_permission": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ąā¤Žā¤§āĨā¤¯āĨ‡ ⤏āĨā¤Ĩā¤žā¤¨ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž", "background_location_permission_content": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ąā¤Žā¤§āĨā¤¯āĨ‡ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ⤏āĨā¤ĩā¤ŋ⤚ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ Immich ā¤˛ā¤ž ⤍āĨ‡ā¤šā¤ŽāĨ€ ā¤…ā¤šāĨ‚⤕ ⤏āĨā¤Ĩā¤žā¤¨ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ (Wi-Fi ā¤¨ā¤žā¤ĩ) ā¤Ēā¤žā¤šā¤ŋ⤜āĨ‡", "background_options": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", "backup": "ā¤ŦāĨ…⤕⤅ā¤Ē", + "backup_album_selection_page_albums_device": "⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰āĨ€ā¤˛ ⤅⤞āĨā¤Ŧā¤Ž ({count})", "backup_album_selection_page_albums_tap": "ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ā¤•ā¤Ļā¤žā¤š ⤟āĨ…ā¤Ē ā¤•ā¤°ā¤ž; ā¤ĩ⤗⤺⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤Ąā¤Ŧ⤞ ⤟āĨ…ā¤Ē ā¤•ā¤°ā¤ž", "backup_album_selection_page_assets_scatter": "ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤅⤍āĨ‡ā¤• ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĩā¤ŋā¤­ā¤žā¤—ā¤˛āĨā¤¯ā¤ž ā¤œā¤žā¤Š ā¤ļā¤•ā¤¤ā¤žā¤¤; ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ļā¤°ā¤ŽāĨā¤¯ā¤žā¤¨ ⤅⤞āĨā¤Ŧā¤Ž ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤ĩā¤—ā¤ŗā¤žāĨ¤", "backup_album_selection_page_select_albums": "⤅⤞āĨā¤Ŧā¤Ž ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "backup_album_selection_page_selection_info": "⤍ā¤ŋā¤ĩā¤Ą ā¤Žā¤žā¤šā¤ŋ⤤āĨ€", + "backup_album_selection_page_total_assets": "ā¤ā¤•āĨ‚⤪ ⤏āĨā¤ĩ⤤⤂⤤āĨā¤° ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸", "backup_albums_sync": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤅⤞āĨā¤Ŧā¤Ž ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ", + "backup_all": "⤏⤰āĨā¤ĩ", + "backup_background_service_backup_failed_message": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€. ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ⤕⤰⤤ ā¤†ā¤šāĨ‡â€Ļ", "backup_background_service_complete_notification": "⤅āĨ…⤏āĨ‡ā¤Ÿā¤šā¤ž ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛ā¤ž", + "backup_background_service_connection_failed_message": "⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ļāĨ€ ⤕⤍āĨ‡ā¤•āĨā¤Ÿ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€. ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ⤕⤰⤤ ā¤†ā¤šāĨ‡â€Ļ", + "backup_background_service_current_upload_notification": "{filename} ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", "backup_background_service_default_notification": "⤍ā¤ĩāĨ€ā¤¨ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤ļāĨ‹ā¤§ā¤¤ ā¤†ā¤šāĨ‡â€Ļ", + "backup_background_service_error_title": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤤āĨā¤°āĨā¤ŸāĨ€", "backup_background_service_in_progress_notification": "ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤ŦāĨ…⤕⤅ā¤Ē ⤕⤰⤤ ā¤†ā¤šāĨ‡â€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "backup_controller_page_albums": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤅⤞āĨā¤Ŧā¤Ž", + "backup_controller_page_background_app_refresh_disabled_content": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ēā¤¸ā¤žā¤ āĨ€ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ > ⤜⤍⤰⤞ > ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤅āĨ…ā¤Ē ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤Žā¤§āĨā¤¯āĨ‡ ‘ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤅āĨ…ā¤Ē ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ’ ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤ž.", + "backup_controller_page_background_app_refresh_disabled_title": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤅āĨ…ā¤Ē ⤰ā¤ŋā¤ĢāĨā¤°āĨ‡ā¤ļ ⤅⤕āĨā¤ˇā¤Ž", + "backup_controller_page_background_app_refresh_enable_button_text": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤Žā¤§āĨā¤¯āĨ‡ ā¤œā¤ž", "backup_controller_page_background_battery_info_link": "⤕⤏āĨ‡ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ⤤āĨ‡ ā¤Ļā¤žā¤–ā¤ĩā¤ž", "backup_controller_page_background_battery_info_message": "⤉⤤āĨā¤¤ā¤Ž ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ēā¤¸ā¤žā¤ āĨ€ Immich ā¤¸ā¤žā¤ āĨ€ ⤏⤰āĨā¤ĩ ā¤ŦāĨ…ā¤Ÿā¤°āĨ€ ⤑ā¤ĒāĨā¤Ÿā¤ŋā¤Žā¤žā¤¯ā¤āĨ‡ā¤ļ⤍ ⤅⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž. \n\nā¤šāĨ‡ ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤¨āĨā¤¸ā¤žā¤° ā¤ĩāĨ‡ā¤—⤺āĨ‡ ⤅⤏āĨ‚ ā¤ļ⤕⤤āĨ‡, ⤤āĨā¤¯ā¤žā¤ŽāĨā¤ŗāĨ‡ ⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ ⤍ā¤ŋ⤰āĨā¤¯ā¤žā¤¤ā¤•⤰āĨā¤¤āĨā¤¯ā¤žā¤Ēā¤žā¤¸āĨ‚⤍ ā¤Žā¤žā¤°āĨā¤—ā¤Ļ⤰āĨā¤ļ⤍ ⤤ā¤Ēā¤žā¤¸ā¤ž.", "backup_controller_page_background_battery_info_ok": "⤠āĨ€ā¤• ā¤†ā¤šāĨ‡", "backup_controller_page_background_battery_info_title": "ā¤ŦāĨ…ā¤Ÿā¤°āĨ€ ⤑ā¤ĒāĨā¤Ÿā¤ŋā¤Žā¤žā¤¯ā¤āĨ‡ā¤ļ⤍āĨā¤¸", + "backup_controller_page_background_charging": "ā¤šā¤žā¤°āĨā¤œ ā¤šāĨ‹ā¤¤ā¤žā¤¨ā¤žā¤š", + "backup_controller_page_background_configure_error": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤏āĨ‡ā¤ĩā¤ž ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "backup_controller_page_background_delay": "⤍ā¤ĩāĨ€ā¤¨ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤ŦāĨ…⤕⤅ā¤Ē ⤉ā¤ļā¤ŋā¤°ā¤ž: {duration}", + "backup_controller_page_background_description": "⤅āĨ…ā¤Ē ā¤‰ā¤˜ā¤Ąā¤˛āĨā¤¯ā¤žā¤ļā¤ŋā¤ĩā¤žā¤¯ ⤍ā¤ĩāĨ€ā¤¨ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤆ā¤ĒāĨ‹ā¤†ā¤Ē ā¤ŦāĨ…⤕⤅ā¤Ē ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤏āĨ‡ā¤ĩā¤ž ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤ž", + "backup_controller_page_background_is_off": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ā¤†ā¤šāĨ‡", + "backup_controller_page_background_is_on": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤†ā¤šāĨ‡", + "backup_controller_page_background_turn_off": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤏āĨ‡ā¤ĩā¤ž ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž", + "backup_controller_page_background_turn_on": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤏āĨ‡ā¤ĩā¤ž ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤ž", + "backup_controller_page_background_wifi": "ā¤Ģ⤕āĨā¤¤ Wi-Fi", "backup_controller_page_backup": "ā¤ŦāĨ…⤕⤅ā¤Ē", "backup_controller_page_backup_selected": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡: ", "backup_controller_page_backup_sub": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ā¤žā¤˛āĨ‡", + "backup_controller_page_created": "⤍ā¤ŋ⤰āĨā¤Žā¤ŋ⤤: {date}", + "backup_controller_page_desc_backup": "⤅āĨ…ā¤Ē ā¤‰ā¤˜ā¤Ąā¤˛āĨā¤¯ā¤žā¤ĩ⤰ ⤍ā¤ĩāĨ€ā¤¨ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ⤆ā¤ĒāĨ‹ā¤†ā¤Ē ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤žāĨ¤", "backup_controller_page_excluded": "ā¤ĩ⤗⤺⤞āĨ‡: ", + "backup_controller_page_failed": "{count} ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "backup_controller_page_filename": "ā¤Ģā¤žā¤ˆā¤˛ ā¤¨ā¤žā¤ĩ: {filename} ({size})", + "backup_controller_page_id": "ā¤†ā¤¯ā¤ĄāĨ€: {id}", + "backup_controller_page_info": "ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Žā¤žā¤šā¤ŋ⤤āĨ€", "backup_controller_page_none_selected": "ā¤•ā¤žā¤šāĨ€ā¤šāĨ€ ⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€", "backup_controller_page_remainder": "ā¤ļā¤ŋ⤞āĨā¤˛ā¤•", "backup_controller_page_remainder_sub": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ā¤Žā¤§āĨ€ā¤˛ ⤉⤰āĨā¤ĩ⤰ā¤ŋ⤤ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡", "backup_controller_page_server_storage": "⤏⤰āĨā¤ĩāĨā¤šā¤° ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ", + "backup_controller_page_start_backup": "ā¤ŦāĨ…⤕⤅ā¤Ē ⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", + "backup_controller_page_status_off": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ā¤†ā¤šāĨ‡", + "backup_controller_page_status_on": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤†ā¤šāĨ‡", "backup_controller_page_storage_format": "{used} ā¤ĒāĨˆā¤•āĨ€ {total} ā¤ĩā¤žā¤Ē⤰⤞āĨ‡", "backup_controller_page_to_backup": "ā¤ŦāĨ…⤕⤅ā¤Ēā¤¸ā¤žā¤ āĨ€ ⤅⤞āĨā¤Ŧā¤Ž", "backup_controller_page_total_sub": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ⤏āĨā¤ĩ⤤⤂⤤āĨā¤° ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", + "backup_controller_page_turn_off": "ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž", + "backup_controller_page_turn_on": "ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ē ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤ž", + "backup_controller_page_uploading_file_info": "ā¤Ģā¤žā¤ˆā¤˛ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", + "backup_err_only_album": "⤅⤂⤤ā¤ŋā¤Ž ⤅⤞āĨā¤Ŧā¤Ž ā¤•ā¤žā¤ĸā¤¤ā¤ž ⤝āĨ‡ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€", "backup_error_sync_failed": "ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€. ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€.", "backup_info_card_assets": "ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸", + "backup_manual_cancelled": "⤰ā¤ĻāĨā¤Ļ ⤕āĨ‡ā¤˛āĨ‡", + "backup_manual_in_progress": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤆⤧āĨ€ā¤š ā¤šā¤žā¤˛āĨ‚ ā¤†ā¤šāĨ‡. ā¤ĨāĨ‹ā¤ĄāĨā¤¯ā¤žā¤ĩāĨ‡ā¤ŗāĨ‡ā¤¨ā¤‚⤤⤰ ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ā¤•ā¤°ā¤ž", + "backup_manual_success": "⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "backup_manual_title": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨā¤Ĩā¤ŋ⤤āĨ€", "backup_options": "ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ē⤰āĨā¤¯ā¤žā¤¯", + "backup_options_page_title": "ā¤ŦāĨ…⤕⤅ā¤Ē ā¤Ē⤰āĨā¤¯ā¤žā¤¯", + "backup_setting_subtitle": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤆⤪ā¤ŋ ā¤ĢāĨ‹ā¤°ā¤—āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "backup_settings_subtitle": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "backward": "ā¤Žā¤žā¤—ā¤žā¤¸ā¤˛āĨ‡ā¤˛āĨ‡", - "battery_optimization_backup_reliability": "ā¤ŦāĨ…ā¤Ÿā¤°āĨ€ ⤑ā¤ĒāĨā¤Ÿā¤ŋā¤Žā¤žā¤¯ā¤āĨ‡ā¤ļ⤍ ⤅⤕āĨā¤ˇā¤Ž ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¨āĨ‡ ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ŦāĨ…⤕⤅ā¤Ē⤚āĨ€ ā¤ĩā¤ŋā¤ļāĨā¤ĩ⤏⤍āĨ€ā¤¯ā¤¤ā¤ž ⤏āĨā¤§ā¤žā¤°āĨ‚ ā¤ļ⤕⤤āĨ‡", "biometric_auth_enabled": "ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•⤰⤪ ā¤šā¤žā¤˛āĨ‚ ā¤†ā¤šāĨ‡", "biometric_locked_out": "⤆ā¤Ē⤪ ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•ā¤°ā¤Ŗā¤žā¤Ēā¤žā¤¸āĨ‚⤍ ⤞āĨ‰ā¤• ā¤†ā¤šā¤žā¤¤", "biometric_no_options": "⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤Ē⤰āĨā¤¯ā¤žā¤¯ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€ā¤¤", "biometric_not_available": "ā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰ ā¤Ŧā¤žā¤¯āĨ‹ā¤ŽāĨ‡ā¤ŸāĨā¤°ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤Žā¤žā¤ŖāĨ€ā¤•⤰⤪ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€", + "birthdate_saved": "⤜⤍āĨā¤Žā¤¤ā¤žā¤°āĨ€ā¤– ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ⤜⤤⤍ ā¤ā¤žā¤˛āĨ€", "birthdate_set_description": "ā¤ĢāĨ‹ā¤ŸāĨ‹ā¤šāĨā¤¯ā¤ž ā¤ĩāĨ‡ā¤ŗāĨ€ ā¤¯ā¤ž ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤šāĨ‡ ā¤ĩ⤝ ā¤ŽāĨ‹ā¤œā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤜⤍āĨā¤Žā¤¤ā¤žā¤°āĨ€ā¤– ā¤ĩā¤žā¤Ē⤰⤞āĨ€ ā¤œā¤žā¤¤āĨ‡.", "blurred_background": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ⤧āĨā¤¸ā¤ŗā¤˛āĨ‡ā¤˛āĨ€", - "browse_templates": "⤟āĨ…ā¤ŽāĨā¤Ē⤞āĨ‡ā¤ŸāĨā¤¸ ā¤ŦāĨā¤°ā¤žā¤‰ā¤ ā¤•ā¤°ā¤ž", "bugs_and_feature_requests": "ā¤Ŧ⤗āĨā¤œ & ā¤Ģā¤ŋ⤚⤰ ā¤ĩā¤ŋ⤍⤂⤤āĨā¤¯ā¤ž", "build": "ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "build_image": "ā¤‡ā¤ŽāĨ‡ā¤œ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", @@ -630,6 +654,20 @@ "bulk_keep_duplicates_confirmation": "⤆ā¤Ē⤪ {count, plural, one {# ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤠āĨ‡ā¤ĩā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤¯ā¤žā¤šāĨ€ ā¤–ā¤žā¤¤āĨā¤°āĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž} other {# ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ⤠āĨ‡ā¤ĩā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤¯ā¤žā¤šāĨ€ ā¤–ā¤žā¤¤āĨā¤°āĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž}}? ā¤¯ā¤žā¤ŽāĨā¤ŗāĨ‡ ⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤍ ā¤šā¤Ÿā¤ĩā¤¤ā¤ž ⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤—ā¤Ÿ ⤏āĨ‹ā¤Ąā¤ĩ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛.", "bulk_trash_duplicates_confirmation": "⤆ā¤Ē⤪ {count, plural, one {# ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤Ÿā¤žā¤•ā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤¯ā¤žā¤šāĨ€ ā¤–ā¤žā¤¤āĨā¤°āĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž} other {# ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤Ÿā¤žā¤•ā¤Ŗā¤žā¤° ā¤†ā¤šā¤žā¤¤ ā¤¯ā¤žā¤šāĨ€ ā¤–ā¤žā¤¤āĨā¤°āĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž}}? ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤—ā¤Ÿā¤žā¤¤āĨ€ā¤˛ ⤏⤰āĨā¤ĩā¤žā¤¤ ā¤ŽāĨ‹ā¤ āĨ‡ ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤠āĨ‡ā¤ĩ⤞āĨ‡ ā¤œā¤žā¤ˆā¤˛ ⤆⤪ā¤ŋ ā¤Ŧā¤žā¤•āĨ€ ⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤Ÿā¤žā¤•ā¤˛āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛.", "buy": "Immich ⤖⤰āĨ‡ā¤ĻāĨ€ ā¤•ā¤°ā¤ž", + "cache_settings_clear_cache_button": "⤕āĨ…ā¤ļ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", + "cache_settings_clear_cache_button_title": "⤅āĨ…ā¤Ē⤚āĨ‡ ⤕āĨ…ā¤ļ ā¤Žā¤ŋ⤟ā¤ĩ⤤āĨ‡. ⤕āĨ…ā¤ļ ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤¤ā¤¯ā¤žā¤° ā¤šāĨ‹ā¤ˆā¤Ē⤰āĨā¤¯ā¤‚⤤ ⤅āĨ…ā¤Ē⤚āĨ€ ā¤•ā¤žā¤Žā¤—ā¤ŋ⤰āĨ€ ā¤ĒāĨā¤°ā¤­ā¤žā¤ĩā¤ŋ⤤ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡.", + "cache_settings_duplicated_assets_clear_button": "ā¤Žā¤ŋ⤟ā¤ĩā¤ž", + "cache_settings_duplicated_assets_subtitle": "⤅āĨ…ā¤Ē⤍āĨ‡ ā¤ĩ⤗⤺⤞āĨ‡ā¤˛āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", + "cache_settings_duplicated_assets_title": "{count} ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸", + "cache_settings_statistics_album": "ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_full": "ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž", + "cache_settings_statistics_shared": "ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤šāĨ‡ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_thumbnail": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛", + "cache_settings_statistics_title": "⤕āĨ…ā¤ļ ā¤ĩā¤žā¤Ē⤰", + "cache_settings_subtitle": "Immich ⤅āĨ…ā¤Ē⤚āĨ‡ ⤕āĨ…ā¤ļā¤ŋ⤂⤗ ⤍ā¤ŋ⤝⤂⤤āĨā¤°ā¤Ŗ ā¤•ā¤°ā¤ž", + "cache_settings_tile_subtitle": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ ⤍ā¤ŋ⤝⤂⤤āĨā¤°ā¤Ŗ ā¤•ā¤°ā¤ž", + "cache_settings_tile_title": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ⤏⤂⤗āĨā¤°ā¤šā¤Ŗ", + "cache_settings_title": "⤕āĨ…ā¤ļ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "camera": "⤕āĨ…ā¤ŽāĨ‡ā¤°ā¤ž", "camera_brand": "⤕āĨ…ā¤ŽāĨ‡ā¤°ā¤ž ā¤ŦāĨā¤°ā¤ā¤Ą", "camera_model": "⤕āĨ…ā¤ŽāĨ‡ā¤°ā¤ž ā¤ŽāĨ‰ā¤ĄāĨ‡ā¤˛", @@ -642,10 +680,10 @@ "cannot_update_the_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€", "cast": "ā¤•ā¤žā¤¸āĨā¤Ÿ", "cast_description": "⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤•ā¤žā¤¸āĨā¤Ÿ ⤗⤂⤤ā¤ĩāĨā¤¯āĨ‡ ⤕āĨ‰ā¤¨āĨā¤Ģā¤ŋ⤗⤰ ā¤•ā¤°ā¤ž", - "change": "ā¤Ŧā¤Ļ⤞", "change_date": "ā¤¤ā¤žā¤°āĨ€ā¤– ā¤Ŧā¤Ļā¤˛ā¤ž", "change_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ā¤Ŧā¤Ļā¤˛ā¤ž", "change_display_order": "ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ⤕āĨā¤°ā¤Ž ā¤Ŧā¤Ļā¤˛ā¤ž", + "change_expiration_time": "ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤āĨ€ ā¤ĩāĨ‡ā¤ŗ ā¤Ŧā¤Ļā¤˛ā¤ž", "change_location": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ŧā¤Ļā¤˛ā¤ž", "change_name": "ā¤¨ā¤žā¤ĩ ā¤Ŧā¤Ļā¤˛ā¤ž", "change_name_successfully": "ā¤¨ā¤žā¤ĩ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤Ŧā¤Ļ⤞⤞āĨ‡", @@ -663,55 +701,38 @@ "changed_visibility_successfully": "ā¤ĻāĨƒā¤ļāĨā¤¯ā¤Žā¤žā¤¨ā¤¤ā¤ž ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°ā¤ŋ⤤āĨā¤¯ā¤ž ā¤Ŧā¤Ļ⤞⤞āĨ€", "charging": "ā¤šā¤žā¤°āĨā¤œā¤ŋ⤂⤗", "charging_requirement_mobile_backup": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤‰ā¤‚ā¤Ą ā¤ŦāĨ…⤕⤅ā¤Ēā¤¸ā¤žā¤ āĨ€ ⤉ā¤Ē⤕⤰⤪ ā¤šā¤žā¤°āĨā¤œ ā¤šāĨ‹ā¤¤ ⤅⤏⤪āĨ‡ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤†ā¤šāĨ‡", + "check_corrupt_asset_backup": "⤭āĨā¤°ā¤ˇāĨā¤Ÿ ā¤Ģā¤žā¤ˆā¤˛ ā¤ŦāĨ…⤕⤅ā¤Ē ⤤ā¤Ēā¤žā¤¸ā¤ž", + "check_corrupt_asset_backup_button": "⤤ā¤Ēā¤žā¤¸ā¤ŖāĨ€ ā¤•ā¤°ā¤ž", + "check_corrupt_asset_backup_description": "ā¤Ģ⤕āĨā¤¤ Wi-Fi ā¤ĩ⤰ ā¤šā¤ž ⤤ā¤Ēā¤žā¤¸ ā¤šā¤žā¤˛ā¤ĩā¤ž ⤆⤪ā¤ŋ ⤏⤰āĨā¤ĩ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ā¤žā¤˛āĨā¤¯ā¤žā¤ĩ⤰⤚. ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤•ā¤žā¤šāĨ€ ā¤Žā¤ŋ⤍ā¤ŋ⤟āĨ‡ ā¤˛ā¤žā¤—āĨ‚ ā¤ļ⤕⤤āĨ‡.", "check_logs": "⤞āĨ‰ā¤—āĨā¤œ ⤤ā¤Ēā¤žā¤¸ā¤ž", - "checksum": "⤚āĨ‡ā¤•ā¤¸ā¤Ž", - "choose": "⤍ā¤ŋā¤ĩā¤Ąā¤ž", "choose_matching_people_to_merge": "ā¤ĩā¤ŋ⤞āĨ€ā¤¨ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤜āĨā¤ŗā¤Ŗā¤žā¤°āĨ‡ ⤞āĨ‹ā¤• ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "city": "ā¤ļā¤šā¤°", - "cleanup_confirm_description": "Immich ⤍āĨ‡ {count} ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ({date} ā¤ĒāĨ‚⤰āĨā¤ĩāĨ€ ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž) ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ā¤Ē⤪āĨ‡ ā¤ŦāĨ…⤕⤅ā¤Ē ⤕āĨ‡ā¤˛āĨā¤¯ā¤ž ⤆ā¤ĸ⤺⤞āĨā¤¯ā¤ž. ā¤¯ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤Žā¤§āĨ€ā¤˛ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ĒāĨā¤°ā¤¤āĨ€ ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤žā¤ĩāĨā¤¯ā¤žā¤¤ ā¤•ā¤ž?", - "cleanup_confirm_prompt_title": "ā¤¯ā¤ž ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤¤āĨ‚⤍ ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤žā¤¯ā¤šāĨ‡?", - "cleanup_deleted_assets": "{count} ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤šāĨā¤¯ā¤ž ā¤•ā¤šā¤ąāĨā¤¯ā¤žā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨā¤¯ā¤ž", - "cleanup_deleting": "ā¤•ā¤šā¤ąāĨā¤¯ā¤žā¤¤ ā¤šā¤˛ā¤ĩ⤤ ā¤†ā¤šāĨ‡...", - "cleanup_found_assets": "{count} ā¤ŦāĨ…⤕⤅ā¤Ē ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ⤆ā¤ĸ⤺⤞āĨā¤¯ā¤ž", - "cleanup_found_assets_with_size": "{count} ā¤ŦāĨ…⤕⤅ā¤Ē ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ⤆ā¤ĸ⤺⤞āĨā¤¯ā¤ž ({size})", - "cleanup_icloud_shared_albums_excluded": "iCloud ā¤¸ā¤žā¤Žā¤žā¤¯ā¤ŋ⤕ ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸ ⤏āĨā¤•āĨ…ā¤¨ā¤Žā¤§āĨ‚⤍ ā¤ĩ⤗⤺⤞āĨ‡ ā¤œā¤žā¤¤ā¤žā¤¤", - "cleanup_no_assets_found": "ā¤ĩ⤰āĨ€ā¤˛ ⤍ā¤ŋā¤•ā¤ˇā¤žā¤‚ā¤¨āĨā¤¸ā¤žā¤° ⤕āĨ‹ā¤Ŗā¤¤āĨ€ā¤šāĨ€ ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨ€ ā¤¨ā¤žā¤šāĨ€. ā¤ĢāĨā¤°āĨ€ ⤅ā¤Ē ⤏āĨā¤ĒāĨ‡ā¤¸ ā¤Ģ⤕āĨā¤¤ ⤤āĨ€ ā¤Žā¤žā¤˛ā¤Žā¤¤āĨā¤¤ā¤ž ā¤šā¤Ÿā¤ĩāĨ‚ ā¤ļ⤕⤤āĨ‡ ⤜āĨā¤¯ā¤žā¤šāĨ‡ ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ā¤ŦāĨ…⤕⤅ā¤Ē ⤘āĨ‡ā¤¤ā¤˛āĨ‡ ⤗āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡ā¤¤", - "cleanup_preview_title": "ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ā¤šāĨ€ ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ({count})", - "cleanup_step3_description": "⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ā¤¤ā¤žā¤°ā¤–āĨ‡ā¤¨āĨā¤¸ā¤žā¤° ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ā¤žā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ ⤏āĨā¤•āĨ…⤍ ā¤•ā¤°ā¤ž ⤆⤪ā¤ŋ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ⤜⤤⤍ ā¤•ā¤°ā¤ž.", - "cleanup_step4_summary": "{count} ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ({date} ā¤ĒāĨ‚⤰āĨā¤ĩāĨ€ ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€) ⤆ā¤Ē⤞āĨā¤¯ā¤ž ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤¤āĨ‚⤍ ā¤•ā¤žā¤ĸ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€. ā¤ĢāĨ‹ā¤ŸāĨ‹ Immich ⤅āĨ…ā¤Ēā¤Žā¤§āĨ‚⤍ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤°ā¤žā¤šā¤¤āĨ€ā¤˛.", - "cleanup_trash_hint": "ā¤¸ā¤žā¤ ā¤ĩ⤪āĨā¤•āĨ€ā¤šāĨ€ ā¤œā¤žā¤—ā¤ž ā¤ĒāĨ‚⤰āĨā¤Ŗā¤Ē⤪āĨ‡ ā¤Ē⤰⤤ ā¤Žā¤ŋ⤺ā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€, ⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤗āĨ…⤞⤰āĨ€ ⤅āĨ…ā¤Ē ā¤‰ā¤˜ā¤Ąā¤ž ⤆⤪ā¤ŋ ā¤•ā¤šā¤°ā¤ž ⤰ā¤ŋā¤•ā¤žā¤ŽāĨ€ ā¤•ā¤°ā¤ž", "clear": "ā¤¸ā¤žā¤Ģ ā¤•ā¤°ā¤ž", "clear_all": "⤏⤰āĨā¤ĩ ā¤¸ā¤žā¤Ģ ā¤•ā¤°ā¤ž", "clear_all_recent_searches": "⤏⤰āĨā¤ĩ ā¤ļāĨ‹ā¤§ ⤇⤤ā¤ŋā¤šā¤žā¤¸ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", - "clear_failed_count": "ā¤šā¤Ÿā¤ĩ⤪āĨ‡ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€ ā¤ā¤žā¤˛āĨ‡ ({count})", "clear_file_cache": "ā¤Ģā¤žā¤ˆā¤˛ ⤕āĨ…ā¤ļ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", "clear_message": "⤏⤂ā¤ĻāĨ‡ā¤ļ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", "clear_value": "ā¤ŽāĨ‚⤞āĨā¤¯ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", + "client_cert_dialog_msg_confirm": "⤠āĨ€ā¤• ā¤†ā¤šāĨ‡", + "client_cert_enter_password": "⤏⤂⤕āĨ‡ā¤¤ā¤ļā¤ŦāĨā¤Ļ ā¤Ÿā¤žā¤•ā¤ž", "client_cert_import": "ā¤†ā¤¯ā¤žā¤¤ ā¤•ā¤°ā¤ž", "client_cert_import_success_msg": "⤕āĨā¤˛ā¤žā¤¯ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤†ā¤¯ā¤žā¤¤ ā¤ā¤žā¤˛āĨ‡", "client_cert_invalid_msg": "⤅ā¤ĩāĨˆā¤§ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤Ģā¤žā¤ˆā¤˛ ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤚āĨā¤•āĨ€ā¤šā¤ž ⤏⤂⤕āĨ‡ā¤¤ā¤ļā¤ŦāĨā¤Ļ", - "client_cert_password_message": "ā¤¯ā¤ž ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤°ā¤žā¤¸ā¤žā¤ āĨ€ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤ĒāĨā¤°ā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", - "client_cert_password_title": "ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ⤏⤂⤕āĨ‡ā¤¤ā¤ļā¤ŦāĨā¤Ļ", "client_cert_remove_msg": "⤕āĨā¤˛ā¤žā¤¯ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤•ā¤žā¤ĸāĨ‚⤍ ā¤Ÿā¤žā¤•ā¤˛āĨ‡", "client_cert_subtitle": "ā¤Ģ⤕āĨā¤¤ PKCS12 (.p12, .pfx) ā¤ĢāĨ‰ā¤°ā¤ŽāĨ…ā¤Ÿā¤˛ā¤ž ā¤¸ā¤Žā¤°āĨā¤Ĩ⤍ ā¤†ā¤šāĨ‡. ⤏⤰āĨā¤Ÿā¤ŋā¤Ģā¤ŋ⤕āĨ‡ā¤Ÿ ā¤†ā¤¯ā¤žā¤¤/ā¤•ā¤žā¤ĸ⤪āĨ‡ ā¤Ģ⤕āĨā¤¤ ⤞āĨ‰ā¤—ā¤ŋ⤍ā¤ĒāĨ‚⤰āĨā¤ĩāĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤†ā¤šāĨ‡", "client_cert_title": "SSL ⤕āĨā¤˛ā¤žā¤¯ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° [ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕]", + "clockwise": "ā¤¤ā¤žā¤¸ā¤žā¤šāĨā¤¯ā¤ž ā¤Ļā¤ŋā¤ļāĨ‡ā¤¨āĨ‡", "close": "ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž", "collapse": "⤏⤂⤕āĨā¤šā¤ŋ⤤ ā¤•ā¤°ā¤ž", "collapse_all": "⤏⤰āĨā¤ĩ ⤏⤂⤕āĨā¤šā¤ŋ⤤ ā¤•ā¤°ā¤ž", "color": "⤰⤂⤗", - "command": "⤆ā¤ĻāĨ‡ā¤ļ", - "command_palette_prompt": "ā¤ĒāĨƒā¤ˇāĨā¤ āĨ‡, ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤆ā¤ĻāĨ‡ā¤ļ ā¤Ēā¤Ÿā¤•ā¤¨ ā¤ļāĨ‹ā¤§ā¤ž", - "command_palette_to_close": "ā¤Ŧ⤂ā¤Ļ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€", - "command_palette_to_navigate": "ā¤ĒāĨā¤°ā¤ĩāĨ‡ā¤ļ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€", - "command_palette_to_select": "⤍ā¤ŋā¤ĩā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€", - "command_palette_to_show_all": "⤏⤰āĨā¤ĩ ā¤Ļā¤žā¤–ā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€", + "color_theme": "⤰⤂⤗ ā¤ĨāĨ€ā¤Ž", "comment_deleted": "⤟ā¤ŋā¤ĒāĨā¤Ē⤪āĨ€ ā¤šā¤Ÿā¤ĩ⤞āĨ€", "comment_options": "⤟ā¤ŋā¤ĒāĨā¤Ē⤪āĨ€ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", "comments_and_likes": "⤟ā¤ŋā¤ĒāĨā¤Ē⤪āĨā¤¯ā¤ž & ā¤˛ā¤žā¤ˆā¤•āĨā¤¸", "comments_are_disabled": "⤟ā¤ŋā¤ĒāĨā¤Ē⤪āĨā¤¯ā¤ž ⤅⤕āĨā¤ˇā¤Ž ā¤†ā¤šāĨ‡ā¤¤", "common_create_new_album": "⤍ā¤ĩāĨ€ā¤¨ ⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "completed": "ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨ‡", - "configuration": "ā¤¸ā¤‚ā¤°ā¤šā¤¨ā¤ž", "confirm": "ā¤ĒāĨā¤ˇāĨā¤ŸāĨ€ ā¤•ā¤°ā¤ž", "confirm_admin_password": "ā¤ā¤Ąā¤Žā¤ŋ⤍ ⤏⤂⤕āĨ‡ā¤¤ā¤ļā¤ŦāĨā¤Ļ ā¤ĒāĨā¤ˇāĨā¤ŸāĨ€ ā¤•ā¤°ā¤ž", "confirm_delete_face": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž {name} ⤚āĨ‡ ⤚āĨ‡ā¤šā¤°ā¤ž ā¤¯ā¤ž ā¤Ģā¤žā¤ˆā¤˛ā¤Žā¤§āĨ‚⤍ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", @@ -726,16 +747,19 @@ "contain": "ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", "context": "⤏⤂ā¤Ļ⤰āĨā¤­", "continue": "ā¤ĒāĨā¤ĸāĨ‡", - "control_bottom_app_bar_add_tags": "⤟āĨ…⤗ ⤜āĨ‹ā¤Ąā¤ž", + "control_bottom_app_bar_create_new_album": "⤍ā¤ĩāĨ€ā¤¨ ⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", + "control_bottom_app_bar_delete_from_immich": "Immich ā¤Žā¤§āĨ‚⤍ ā¤šā¤Ÿā¤ĩā¤ž", "control_bottom_app_bar_delete_from_local": "⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤¤āĨ‚⤍ ā¤šā¤Ÿā¤ĩā¤ž", "control_bottom_app_bar_edit_location": "⤏āĨā¤Ĩā¤žā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "control_bottom_app_bar_edit_time": "ā¤¤ā¤žā¤°āĨ€ā¤– ā¤ĩ ā¤ĩāĨ‡ā¤ŗ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "control_bottom_app_bar_share_link": "⤞ā¤ŋ⤂⤕ ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤ž", + "control_bottom_app_bar_share_to": "⤝āĨ‡ā¤ĨāĨ‡ ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤ž", "control_bottom_app_bar_trash_from_immich": "⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩā¤ž", "copied_image_to_clipboard": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ąā¤ĩ⤰ ⤕āĨ‰ā¤ĒāĨ€ ⤕āĨ‡ā¤˛āĨ€āĨ¤", "copied_to_clipboard": "⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ąā¤ĩ⤰ ⤕āĨ‰ā¤ĒāĨ€ ā¤ā¤žā¤˛āĨ‡!", "copy_error": "⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", + "copy_file_path": "ā¤Ģā¤žā¤ˆā¤˛ā¤šā¤ž ā¤Žā¤žā¤°āĨā¤— ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", "copy_image": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", - "copy_json": "JSON ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", "copy_link": "⤞ā¤ŋ⤂⤕ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", "copy_link_to_clipboard": "⤞ā¤ŋ⤂⤕ ⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ąā¤ĩ⤰ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", "copy_password": "⤏⤂⤕āĨ‡ā¤¤ā¤ļā¤ŦāĨā¤Ļ ⤕āĨ‰ā¤ĒāĨ€ ā¤•ā¤°ā¤ž", @@ -747,7 +771,6 @@ "create_album": "⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_album_page_untitled": "ā¤ļāĨ€ā¤°āĨā¤ˇā¤•āĨ‡ā¤¤ā¤°", "create_api_key": "API ⤕āĨ€ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", - "create_first_workflow": "ā¤Ēā¤šā¤ŋā¤˛ā¤ž ā¤ĩ⤰āĨā¤•ā¤ĢāĨā¤˛āĨ‹ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_library": "ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_link": "⤞ā¤ŋ⤂⤕ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_link_to_share": "ā¤ļāĨ‡ā¤…⤰ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤞ā¤ŋ⤂⤕ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", @@ -756,46 +779,37 @@ "create_new_person": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_new_person_hint": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤍ā¤ĩāĨ€ā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤ļāĨ€ ⤜āĨ‹ā¤Ąā¤ž", "create_new_user": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", - "create_person": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", - "create_person_subtitle": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤆⤪ā¤ŋ ⤟āĨ…⤗ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤚āĨ‡ā¤šā¤ąāĨā¤¯ā¤žā¤ĩ⤰ ā¤¨ā¤žā¤ĩ ⤜āĨ‹ā¤Ąā¤ž", "create_shared_album_page_share_add_assets": "ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤜āĨ‹ā¤Ąā¤ž", "create_shared_album_page_share_select_photos": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "create_shared_link": "ā¤ļāĨ‡ā¤…⤰ ⤞ā¤ŋ⤂⤕ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_tag": "⤟āĨ…⤗ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "create_tag_description": "⤍ā¤ĩāĨ€ā¤¨ ⤟āĨ…⤗ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž. ⤏ā¤Ŧ⤟āĨ…ā¤—ā¤¸ā¤žā¤ āĨ€ ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤Ēā¤žā¤Ĩā¤¸ā¤šā¤ŋ⤤ ā¤¨ā¤žā¤ĩ ā¤Ÿā¤žā¤•ā¤žāĨ¤", "create_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", - "create_workflow": "ā¤•ā¤žā¤°āĨā¤¯ā¤ĒāĨā¤°ā¤ĩā¤žā¤š ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "created": "ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡", "created_at": "⤍ā¤ŋ⤰āĨā¤Žā¤ŋ⤤āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", "creating_linked_albums": "⤞ā¤ŋ⤂⤕ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤤ ā¤†ā¤šāĨ‡...", "crop": "ā¤›ā¤žā¤Ÿā¤ŖāĨ€ ā¤•ā¤°ā¤ž", - "crop_aspect_ratio_free": "⤰ā¤ŋ⤕āĨā¤¤", - "crop_aspect_ratio_original": "ā¤ŽāĨ‚⤺", - "crop_aspect_ratio_square": "⤚āĨŒā¤°ā¤¸", + "curated_object_page_title": "⤗āĨ‹ā¤ˇāĨā¤ŸāĨ€", "current_device": "ā¤ĩ⤰āĨā¤¤ā¤Žā¤žā¤¨ ⤉ā¤Ē⤕⤰⤪", "current_pin_code": "ā¤šā¤žā¤˛āĨ‚ PIN ⤕āĨ‹ā¤Ą", "current_server_address": "⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤Ē⤤āĨā¤¤ā¤ž", - "custom_date": "ā¤¸ā¤žā¤¨āĨā¤•āĨ‚⤞ ā¤¤ā¤žā¤°āĨ€ā¤–", "custom_locale": "ā¤­ā¤žā¤ˇā¤ž ā¤ĩ ⤕āĨā¤ˇāĨ‡ā¤¤āĨā¤°", "custom_locale_description": "ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ā¤ĩ ⤏⤂⤖āĨā¤¯ā¤ž ā¤­ā¤žā¤ˇāĨ‡ā¤¨āĨā¤¸ā¤žā¤° ā¤ĩ ⤕āĨā¤ˇāĨ‡ā¤¤āĨā¤°ā¤žā¤¨āĨā¤¸ā¤žā¤° format ā¤•ā¤°ā¤ž", - "cutoff_date_description": "ā¤ļāĨ‡ā¤ĩ⤟⤚āĨā¤¯ā¤žâ€Ļ ā¤Ēā¤žā¤¸āĨ‚ā¤¨ā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤠āĨ‡ā¤ĩā¤žâ€Ļ", - "cutoff_day": "{count, plural, one {ā¤Ļā¤ŋā¤ĩ⤏} other {ā¤Ļā¤ŋā¤ĩ⤏}}", - "cutoff_year": "{count, plural, one {ā¤ĩ⤰āĨā¤ˇ} other {ā¤ĩ⤰āĨā¤ˇ}}", + "custom_url": "ā¤¸ā¤žā¤¨āĨā¤•āĨ‚⤞ URL", + "daily_title_text_date": "⤈, ā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤ĄāĨ€ā¤ĄāĨ€", + "daily_title_text_date_year": "⤈, ā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤Ļā¤ŋā¤ĩ⤏, ā¤ĩ⤰āĨā¤ˇ", "dark": "ā¤Ąā¤žā¤°āĨā¤•", "dark_theme": "ā¤Ąā¤žā¤°āĨā¤• ā¤ĨāĨ€ā¤Ž ā¤Ŧā¤Ļ⤞", "date": "ā¤¤ā¤žā¤°āĨ€ā¤–", "date_after": "ā¤¨ā¤‚ā¤¤ā¤°ā¤šāĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", "date_and_time": "ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ā¤ĩ ā¤ĩāĨ‡ā¤ŗ", "date_before": "ā¤ĒāĨ‚⤰āĨā¤ĩ⤚āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", - "date_of_birth": "⤜⤍āĨā¤Ž ā¤¤ā¤žā¤°āĨ€ā¤–", + "date_format": "⤈, ā¤ā¤˛ā¤ā¤˛ā¤ā¤˛ ā¤ĄāĨ€, ā¤ĩā¤žā¤ˆ â€ĸ ā¤ā¤š:ā¤ā¤Žā¤ā¤Ž ā¤", "date_of_birth_saved": "⤜⤍āĨā¤Žā¤¤ā¤žā¤°āĨ€ā¤– ⤜⤤⤍ ā¤ā¤žā¤˛āĨ€", "date_range": "ā¤¤ā¤žā¤°āĨ€ā¤– ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€", - "date_time_original": "ā¤ŽāĨ‚⤺ ā¤¤ā¤žā¤°āĨ€ā¤–/ā¤ĩāĨ‡ā¤ŗ", "day": "ā¤Ļā¤ŋā¤ĩ⤏", "days": "⤅⤍āĨ‡ā¤• ā¤Ļā¤ŋā¤ĩ⤏", "deduplicate_all": "⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤•ā¤žā¤ĸā¤ž", - "default_quality_subtitle": "ā¤ļāĨ‡ā¤…⤰ ⤟āĨ…ā¤Ē ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ā¤ĩā¤žā¤Ē⤰⤞āĨ€ ā¤œā¤žā¤Ŗā¤žā¤°āĨ€ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž. ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤ĩāĨ‡ā¤ŗāĨ€ ⤍ā¤ŋā¤ĩā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ļāĨ‡ā¤…⤰ ā¤Ŧ⤟⤪ ā¤ĻāĨ€ā¤°āĨā¤˜ā¤•ā¤žā¤ŗ ā¤Ļā¤žā¤Ŧā¤ž.", - "default_share_quality": "ā¤ĄāĨ€ā¤ĢāĨ‰ā¤˛āĨā¤Ÿ ā¤ļāĨ‡ā¤…⤰ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž", "delete": "ā¤šā¤Ÿā¤ĩā¤ž", "delete_action_confirmation_message": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ā¤šāĨ€ ā¤Ģā¤žā¤ˆā¤˛ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šāĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž? ā¤šāĨ€ ⤕āĨā¤°ā¤ŋā¤¯ā¤ž ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤šāĨā¤¯ā¤ž ⤟āĨā¤°āĨ…ā¤ļā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩāĨ‡ā¤˛ ⤆⤪ā¤ŋ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ā¤Ē⤪āĨ‡ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šāĨ‡ ā¤•ā¤ž ⤤āĨ‡ ā¤ĩā¤ŋā¤šā¤žā¤°āĨ‡ā¤˛", "delete_action_prompt": "{count} ā¤šā¤Ÿā¤ĩ⤞āĨ‡", @@ -804,6 +818,8 @@ "delete_dialog_alert": "ā¤šāĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ Immich ⤆⤪ā¤ŋ ⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩ⤞āĨā¤¯ā¤ž ā¤œā¤žā¤¤āĨ€ā¤˛", "delete_dialog_alert_local": "ā¤šāĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤ĩā¤ŋā¤ļā¤ŋ⤎āĨā¤Ÿā¤Ē⤪āĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨā¤¯ā¤ž ā¤œā¤žā¤¤āĨ€ā¤˛, ā¤Ē⤰⤂⤤āĨ Immich ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤°ā¤žā¤šā¤¤āĨ€ā¤˛", "delete_dialog_alert_local_non_backed_up": "ā¤•ā¤žā¤šāĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ Immich ā¤ĩ⤰ ā¤ŦāĨ…⤕⤅ā¤Ē ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤¨ā¤žā¤šāĨ€ā¤¤ ⤆⤪ā¤ŋ ⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ⤉ā¤Ēā¤•ā¤°ā¤Ŗā¤žā¤ĩ⤰āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤ĩā¤ŋā¤ļā¤ŋ⤎āĨā¤Ÿā¤Ē⤪āĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨā¤¯ā¤ž ā¤œā¤žā¤¤āĨ€ā¤˛", + "delete_dialog_alert_remote": "ā¤šāĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ Immich ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩ⤞āĨā¤¯ā¤ž ā¤œā¤žā¤¤āĨ€ā¤˛", + "delete_dialog_ok_force": "⤤⤰āĨ€ā¤šāĨ€ ā¤šā¤Ÿā¤ĩā¤ž", "delete_dialog_title": "ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩā¤ž", "delete_duplicates_confirmation": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ā¤šāĨ‡ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ā¤¤ ā¤•ā¤ž?", "delete_face": "⤚āĨ‡ā¤šā¤°ā¤ž ā¤šā¤Ÿā¤ĩā¤ž", @@ -822,16 +838,20 @@ "delete_tag_confirmation_prompt": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž {tagName} ⤟āĨ…⤗ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "delete_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤šā¤Ÿā¤ĩā¤ž", "deleted_shared_link": "ā¤ļāĨ‡ā¤…⤰ ⤞ā¤ŋ⤂⤕ ā¤šā¤Ÿā¤ĩ⤞āĨ‡", + "deletes_missing_assets": "ā¤Ąā¤ŋ⤏āĨā¤•ā¤ĩ⤰ ⤍⤏⤞āĨ‡ā¤˛āĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤šā¤Ÿā¤ĩā¤ž", "description": "ā¤ĩ⤰āĨā¤Ŗā¤¨", + "description_input_hint_text": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤜āĨ‹ā¤Ąā¤žâ€Ļ", + "description_input_submit_error": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€ - ⤤ā¤Ēā¤ļāĨ€ā¤˛ā¤žā¤‚ā¤¸ā¤žā¤ āĨ€ ⤞āĨ‰ā¤— ⤤ā¤Ēā¤žā¤¸ā¤ž", "deselect_all": "⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤Ą ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", "details": "⤤ā¤Ēā¤ļāĨ€ā¤˛", "direction": "ā¤Ļā¤ŋā¤ļā¤ž", - "disable": "⤅⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "disabled": "⤅⤕āĨā¤ˇā¤Ž", + "disallow_edits": "⤏⤂ā¤Ēā¤žā¤Ļ⤍ ⤅⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "discord": "ā¤Ąā¤ŋ⤏āĨā¤•āĨ‰ā¤°āĨā¤Ą", "discover": "ā¤ļāĨ‹ā¤§ā¤ž", "discovered_devices": "ā¤ļāĨ‹ā¤§ā¤ŋ⤞āĨ‡ā¤˛āĨ‡ ⤉ā¤Ē⤕⤰⤪āĨ‡", "dismiss_all_errors": "⤏⤰āĨā¤ĩ ⤤āĨā¤°āĨā¤ŸāĨ€ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", + "dismiss_error": "⤤āĨā¤°āĨā¤ŸāĨ€ ā¤Žā¤ŋ⤟ā¤ĩā¤ž", "display_options": "ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", "display_order": "ā¤ĒāĨā¤°ā¤Ļ⤰āĨā¤ļ⤍ ⤕āĨā¤°ā¤Ž", "display_original_photos": "ā¤ŽāĨ‚⤺ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤Ļā¤žā¤–ā¤ĩā¤ž", @@ -840,29 +860,29 @@ "documentation": "ā¤Ļ⤏āĨā¤¤ā¤ā¤ĩ⤜āĨ€ā¤•⤰⤪", "done": "ā¤ĒāĨ‚⤰āĨā¤Ŗ", "download": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", + "download_action_prompt": "{count} ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", "download_canceled": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤰ā¤ĻāĨā¤Ļ ā¤ā¤žā¤˛āĨ‡", "download_complete": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨ‡", "download_enqueue": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤°ā¤žā¤‚ā¤•āĨ‡ā¤¤ ⤜āĨ‹ā¤Ąā¤˛āĨ‡", + "download_error": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤤āĨā¤°āĨā¤ŸāĨ€", "download_failed": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "download_finished": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨ‡", "download_include_embedded_motion_videos": "ā¤ā¤ŽāĨā¤ŦāĨ‡ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", "download_include_embedded_motion_videos_description": "ā¤ŽāĨ‹ā¤ļ⤍ ā¤ĢāĨ‹ā¤ŸāĨ‹ā¤Žā¤§āĨ€ā¤˛ ā¤ā¤ŽāĨā¤ŦāĨ‡ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤏āĨā¤ĩ⤤⤂⤤āĨā¤° ā¤Ģā¤žā¤ˆā¤˛ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", "download_notfound": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤆ā¤ĸā¤ŗā¤˛ā¤ž ā¤¨ā¤žā¤šāĨ€", - "download_original": "ā¤ŽāĨ‚⤺ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", "download_paused": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤Ĩā¤žā¤‚ā¤Ŧā¤ĩā¤˛ā¤ž", "download_settings": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "download_settings_description": "ā¤Ģā¤žā¤ˆā¤˛ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏⤂ā¤Ŧ⤂⤧ā¤ŋ⤤ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "download_started": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏āĨā¤°āĨ‚ ā¤ā¤žā¤˛ā¤ž", + "download_sucess": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤝ā¤ļ⤏āĨā¤ĩāĨ€", + "download_sucess_android": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž DCIM/Immich ā¤Žā¤§āĨā¤¯āĨ‡ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤ā¤žā¤˛ā¤ž ā¤†ā¤šāĨ‡", "download_waiting_to_retry": "ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ⤕⤰⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇā¤ž", "downloading": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", "downloading_asset_filename": "{filename} ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", - "downloading_from_icloud": "iCloud ā¤ĩ⤰āĨ‚⤍ ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", "downloading_media": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤕⤰⤤ ā¤†ā¤šāĨ‡", - "drag_to_reorder": "ā¤ĒāĨā¤¨ā¤°āĨā¤•āĨā¤°ā¤Ž ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤓ā¤ĸā¤ž", "drop_files_to_upload": "⤅ā¤Ē⤞āĨ‹ā¤Ąā¤¸ā¤žā¤ āĨ€ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤇ā¤ĨāĨ‡ ā¤ĄāĨā¤°āĨ‰ā¤Ē ā¤•ā¤°ā¤ž", - "duplicate": "ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤞ā¤ŋā¤ĒāĨ€", - "duplicate_workflow": "ā¤ĒāĨā¤°ā¤¤ā¤ŋ⤞ā¤ŋā¤ĒāĨ€ ā¤•ā¤žā¤°āĨā¤¯ā¤ĒāĨā¤°ā¤ĩā¤žā¤š", "duplicates": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸", - "duplicates_description": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤—ā¤Ÿā¤žā¤¤ā¤˛āĨ‡ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤍ā¤ŋā¤ĩā¤Ąā¤ž.", + "duplicates_description": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤—ā¤Ÿā¤žā¤¤ā¤˛āĨ‡ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤Ÿ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "duration": "ā¤•ā¤žā¤˛ā¤žā¤ĩ⤧āĨ€", "edit": "⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "edit_album": "⤅⤞āĨā¤Ŧā¤Ž ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", @@ -872,7 +892,9 @@ "edit_date_and_time": "ā¤¤ā¤žā¤°āĨ€ā¤– ā¤ĩ ā¤ĩāĨ‡ā¤ŗ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "edit_date_and_time_action_prompt": "{count} ā¤¤ā¤žā¤°āĨ€ā¤– ⤆⤪ā¤ŋ ā¤ĩāĨ‡ā¤ŗ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤ā¤žā¤˛āĨ‡", "edit_date_and_time_by_offset": "⤑ā¤Ģ⤏āĨ‡ā¤Ÿā¤¨āĨā¤¸ā¤žā¤° ā¤¤ā¤žā¤°āĨ€ā¤– ā¤Ŧā¤Ļā¤˛ā¤ž", + "edit_date_and_time_by_offset_interval": "⤍ā¤ĩāĨ€ā¤¨ ā¤¤ā¤žā¤°āĨ€ā¤– ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€: {from}–{to}", "edit_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "edit_description_prompt": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤍ā¤ŋā¤ĩā¤Ąā¤ž:", "edit_exclusion_pattern": "ā¤ĩā¤—ā¤ŗā¤ž ā¤ĒāĨ…ā¤Ÿā¤°āĨā¤¨ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "edit_faces": "⤚āĨ‡ā¤šā¤ąāĨā¤¯ā¤žā¤‚ā¤ĩ⤰ ⤏⤂ā¤Ēā¤žā¤Ļ⤍ ā¤•ā¤°ā¤ž", "edit_key": "⤕āĨ€ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", @@ -885,15 +907,9 @@ "edit_tag": "⤟āĨ…⤗ ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "edit_title": "ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "edit_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", - "edit_workflow": "ā¤•ā¤žā¤°āĨā¤¯ā¤ĒāĨā¤°ā¤ĩā¤žā¤š ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", "editor": "ā¤ā¤Ąā¤ŋ⤟⤰", - "editor_discard_edits_confirm": "⤏⤂ā¤Ēā¤žā¤Ļ⤍ ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", - "editor_discard_edits_prompt": "⤆ā¤Ē⤞āĨā¤¯ā¤žā¤•ā¤ĄāĨ‡ ⤜⤤⤍ ⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ⤏⤂ā¤Ēā¤žā¤Ļā¤¨ā¤ž ā¤†ā¤šāĨ‡ā¤¤. ⤆ā¤Ē⤪ ⤤āĨā¤¯ā¤ž ā¤Ÿā¤žā¤•āĨ‚⤍ ā¤ĻāĨā¤¯ā¤žā¤¯ā¤šāĨā¤¯ā¤ž ā¤†ā¤šā¤žā¤¤ ā¤•ā¤ž?", - "editor_discard_edits_title": "ā¤Ŧā¤Ļ⤞ ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤•ā¤ž?", - "editor_edits_applied_error": "ā¤Ŧā¤Ļ⤞ ā¤˛ā¤žā¤—āĨ‚ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", - "editor_edits_applied_success": "ā¤Ŧā¤Ļ⤞ ā¤˛ā¤žā¤—āĨ‚ ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤝ā¤ļ⤏āĨā¤ĩāĨ€", - "editor_flip_horizontal": "ā¤†ā¤Ąā¤ĩāĨ‡ ā¤Ē⤞⤟ā¤ĩā¤ž", - "editor_flip_vertical": "⤉⤭āĨ‡ ā¤Ē⤞⤟ā¤ĩā¤ž", + "editor_close_without_save_prompt": "ā¤Ŧā¤Ļ⤞ ⤜⤤⤍ ā¤šāĨ‹ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€", + "editor_close_without_save_title": "ā¤ā¤Ąā¤ŋ⤟⤰ ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤žā¤¯ā¤šā¤ž ā¤•ā¤ž?", "email": "ā¤ˆā¤ŽāĨ‡ā¤˛", "email_notifications": "ā¤ˆā¤ŽāĨ‡ā¤˛ ⤏āĨ‚ā¤šā¤¨ā¤ž", "empty_folder": "ā¤šā¤ž ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤° ⤰ā¤ŋā¤•ā¤žā¤Žā¤ž ā¤†ā¤šāĨ‡", @@ -909,7 +925,9 @@ "enter_your_pin_code": "PIN ⤕āĨ‹ā¤Ą ā¤Ÿā¤žā¤•ā¤ž", "enter_your_pin_code_subtitle": "⤞āĨ‰ā¤• ⤕āĨ‡ā¤˛āĨ‡ā¤˛ā¤ž ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤° ā¤‰ā¤˜ā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ PIN ⤕āĨ‹ā¤Ą ā¤Ÿā¤žā¤•ā¤ž", "error": "⤤āĨā¤°āĨā¤ŸāĨ€", + "error_change_sort_album": "⤅⤞āĨā¤Ŧā¤Ž ⤕āĨā¤°ā¤Ž ā¤Ŧā¤Ļ⤞⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "error_delete_face": "ā¤Ģā¤žā¤ˆā¤˛ā¤Žā¤§āĨ‚⤍ ⤚āĨ‡ā¤šā¤°ā¤ž ā¤šā¤Ÿā¤ĩā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", + "error_getting_places": "⤠ā¤ŋā¤•ā¤žā¤ŖāĨ‡ ā¤Žā¤ŋ⤺ā¤ĩā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", "error_loading_image": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", "error_loading_partners": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤°āĨā¤¸ ⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€: {error}", "error_saving_image": "⤤āĨā¤°āĨā¤ŸāĨ€: {error}", @@ -1038,9 +1056,17 @@ "exif": "ā¤ā¤•āĨā¤¸ā¤ŋā¤Ģ", "exif_bottom_sheet_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤜āĨ‹ā¤Ąā¤žâ€Ļ", "exif_bottom_sheet_description_error": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", + "exif_bottom_sheet_details": "⤤ā¤Ēā¤ļāĨ€ā¤˛", + "exif_bottom_sheet_location": "⤏āĨā¤Ĩā¤žā¤¨", "exif_bottom_sheet_no_description": "ā¤ĩ⤰āĨā¤Ŗā¤¨ ā¤¨ā¤žā¤šāĨ€", + "exif_bottom_sheet_people": "⤞āĨ‹ā¤•", + "exif_bottom_sheet_person_add_person": "ā¤¨ā¤žā¤ĩ ⤜āĨ‹ā¤Ąā¤ž", "exit_slideshow": "⤏āĨā¤˛ā¤žā¤‡ā¤Ąā¤ļāĨ‹ ā¤Ŧ⤂ā¤Ļ ā¤•ā¤°ā¤ž", "expand_all": "⤏⤰āĨā¤ĩ ā¤ĩā¤ŋ⤏āĨā¤¤ā¤žā¤° ā¤•ā¤°ā¤ž", + "experimental_settings_new_asset_list_subtitle": "ā¤•ā¤žā¤Ž ⤏āĨā¤°āĨ‚ ā¤†ā¤šāĨ‡", + "experimental_settings_new_asset_list_title": "ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤗āĨā¤°ā¤ŋā¤Ą ā¤šā¤žā¤˛āĨ‚ ā¤•ā¤°ā¤ž", + "experimental_settings_subtitle": "⤏āĨā¤ĩ⤤⤃⤚āĨā¤¯ā¤ž ⤜ā¤Ŧā¤žā¤Ŧā¤Ļā¤žā¤°āĨ€ā¤ĩ⤰ ā¤ĩā¤žā¤Ēā¤°ā¤ž!", + "experimental_settings_title": "ā¤ĒāĨā¤°ā¤žā¤¯āĨ‹ā¤—ā¤ŋ⤕", "expire_after": "⤍⤂⤤⤰ ā¤•ā¤žā¤˛ā¤Ŧā¤žā¤šāĨā¤¯", "expired": "ā¤•ā¤žā¤˛ā¤Ŧā¤žā¤šāĨā¤¯", "expires_date": "{date} ā¤˛ā¤ž ā¤•ā¤žā¤˛ā¤Ŧā¤žā¤šāĨā¤¯", @@ -1064,8 +1090,10 @@ "favorite_action_prompt": "{count} ⤆ā¤ĩā¤ĄāĨ€ā¤‚ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡", "favorite_or_unfavorite_photo": "ā¤ĢāĨ‹ā¤ŸāĨ‹ â€˜ā¤†ā¤ĩā¤Ąā¤¤āĨ‡â€™ ā¤•ā¤°ā¤ž ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤•ā¤žā¤ĸā¤ž", "favorites": "⤆ā¤ĩā¤ĄāĨ€", + "favorites_page_no_favorites": "⤆ā¤ĩā¤ĄāĨ€ā¤¤āĨ€ā¤˛ ā¤Ģā¤žā¤ˆā¤˛āĨā¤¸ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨā¤¯ā¤ž ā¤¨ā¤žā¤šāĨ€ā¤¤", "feature_photo_updated": "ā¤ĢāĨ€ā¤šā¤° ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤅ā¤ĻāĨā¤¯ā¤¤ā¤¨ā¤ŋ⤤ ā¤ā¤žā¤˛ā¤ž", "features": "ā¤ĩāĨˆā¤ļā¤ŋ⤎āĨā¤ŸāĨā¤¯āĨ‡", + "features_in_development": "ā¤ĩā¤ŋā¤•ā¤žā¤¸ā¤žā¤§āĨ€ā¤¨ ā¤ĩāĨˆā¤ļā¤ŋ⤎āĨā¤ŸāĨā¤¯āĨ‡", "features_setting_description": "⤅āĨ…ā¤Ē⤚āĨ€ ā¤ĩāĨˆā¤ļā¤ŋ⤎āĨā¤ŸāĨā¤¯āĨ‡ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "file_name_or_extension": "ā¤Ģā¤žā¤ˆā¤˛ ā¤¨ā¤žā¤ĩ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤ā¤•āĨā¤¸āĨā¤ŸāĨ‡ā¤‚ā¤ļ⤍", "file_size": "ā¤Ģā¤žā¤‡ā¤˛ ā¤¸ā¤žā¤‡ā¤œā¤ŧ", @@ -1074,6 +1102,7 @@ "filter": "ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤°", "filter_people": "⤞āĨ‹ā¤• ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤° ā¤•ā¤°ā¤ž", "filter_places": "⤠ā¤ŋā¤•ā¤žā¤ŖāĨ‡ ā¤Ģā¤ŋ⤞āĨā¤Ÿā¤° ā¤•ā¤°ā¤ž", + "find_them_fast": "ā¤¨ā¤žā¤ĩā¤žā¤¨āĨ‡ ā¤Ēā¤Ÿā¤•ā¤¨ ā¤ļāĨ‹ā¤§ā¤ž", "first": "ā¤ĒāĨā¤°ā¤Ĩā¤Ž", "fix_incorrect_match": "⤚āĨā¤•āĨ€ā¤šāĨ€ ⤜āĨā¤ŗā¤ŖāĨ€ ā¤ĻāĨā¤°āĨā¤¸āĨā¤¤ ā¤•ā¤°ā¤ž", "folder": "ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°", @@ -1102,6 +1131,7 @@ "group_owner": "ā¤Žā¤žā¤˛ā¤•ā¤žā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿ ā¤•ā¤°ā¤ž", "group_places_by": "⤏āĨā¤Ĩ⤺āĨ‡ ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤•ā¤°ā¤ž: â€Ļ", "group_year": "ā¤ĩ⤰āĨā¤ˇā¤žā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤•ā¤°ā¤ž", + "haptic_feedback_switch": "ā¤šāĨ…ā¤ĒāĨā¤Ÿā¤ŋ⤕ ā¤ĢāĨ€ā¤Ąā¤ŦāĨ…⤕ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž", "haptic_feedback_title": "ā¤šāĨ…ā¤ĒāĨā¤Ÿā¤ŋ⤕ ā¤ĢāĨ€ā¤Ąā¤ŦāĨ…⤕", "has_quota": "⤕āĨ‹ā¤Ÿā¤ž ā¤†ā¤šāĨ‡", "hash_asset": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤•ā¤žā¤šā¤ž ā¤šāĨ…ā¤ļ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", @@ -1120,12 +1150,29 @@ "hide_person": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ⤞ā¤Ēā¤ĩā¤ž", "hide_text_recognition": "ā¤Žā¤œā¤•āĨ‚⤰ ⤓⤺⤖ ⤞ā¤Ēā¤ĩā¤ž", "hide_unnamed_people": "ā¤¨ā¤žā¤ĩ ⤍⤏⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ⤞ā¤Ēā¤ĩā¤ž", + "home_page_add_to_album_conflicts": "⤅⤞āĨā¤Ŧā¤Ž {album} ā¤Žā¤§āĨā¤¯āĨ‡ {added} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤜āĨ‹ā¤Ąā¤˛āĨ‡. {failed} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤆⤧āĨ€ā¤š ⤤āĨā¤¯ā¤ž ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤†ā¤šāĨ‡ā¤¤.", + "home_page_add_to_album_err_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤…ā¤œāĨ‚⤍ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_add_to_album_success": "⤅⤞āĨā¤Ŧā¤Ž {album} ā¤Žā¤§āĨā¤¯āĨ‡ {added} ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤜āĨ‹ā¤Ąā¤˛āĨ‡.", + "home_page_album_err_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤šāĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤…ā¤œāĨ‚⤍ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_archive_err_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤…ā¤œāĨ‚⤍ ⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_archive_err_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤šāĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", "home_page_building_timeline": "ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨ ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤤ ā¤†ā¤šāĨ‡", + "home_page_delete_err_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤šāĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤šā¤Ÿā¤ĩā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_delete_remote_err_local": "ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ā¤šā¤Ÿā¤ĩ⤪āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ⤍ā¤ŋā¤ĩā¤ĄāĨ€ā¤¤ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤†ā¤šāĨ‡ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_favorite_err_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤•ā¤žā¤‚ā¤¨ā¤ž ā¤…ā¤œāĨ‚⤍ ⤆ā¤ĩā¤ĄāĨ€ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_favorite_err_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤šāĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ā¤…ā¤œāĨ‚⤍ ⤆ā¤ĩā¤ĄāĨ€ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_first_time_notice": "ā¤…â€āĨ…ā¤Ē ā¤ĒāĨā¤°ā¤Ĩā¤Žā¤š ā¤ĩā¤žā¤Ē⤰⤤ ā¤…ā¤¸ā¤žā¤˛ ⤤⤰ ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨ā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤭⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤕āĨƒā¤Ēā¤¯ā¤ž ā¤ŦāĨ…⤕⤅ā¤Ē ⤅⤞āĨā¤Ŧā¤Ž ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "home_page_locked_error_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤞āĨ‰ā¤• ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_locked_error_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤šāĨ‡ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤞āĨ‰ā¤• ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_share_err_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤• ⤞ā¤ŋ⤂⤕ā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", + "home_page_upload_err_limit": "ā¤ā¤•ā¤žā¤ĩāĨ‡ā¤ŗāĨ€ ā¤•ā¤Žā¤žā¤˛ 30 ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤˜ā¤Ÿā¤•ā¤š ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤¤ā¤ž ⤝āĨ‡ā¤¤ā¤žā¤¤, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‡", "host": "ā¤šāĨ‹ā¤¸āĨā¤Ÿ", "hour": "ā¤¤ā¤žā¤¸", "hours": "ā¤¤ā¤žā¤¸", "id": "ID", "idle": "⤍ā¤ŋ⤎āĨā¤•āĨā¤°ā¤ŋ⤝", + "ignore_icloud_photos": "iCloud ā¤ĩ⤰āĨ€ā¤˛ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĻāĨā¤°āĨā¤˛ā¤•āĨā¤ˇā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "ignore_icloud_photos_description": "iCloud ā¤ĩ⤰ ā¤¸ā¤žā¤ ā¤ĩ⤞āĨ‡ā¤˛āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ Immich ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€ā¤¤", "image": "ā¤ĢāĨ‹ā¤ŸāĨ‹", "image_alt_text_date": "{isVideo, select, true {ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓} other {ā¤ĢāĨ‹ā¤ŸāĨ‹}} {date} ā¤˛ā¤ž ⤘āĨ‡ā¤¤ā¤˛āĨ‡", "image_alt_text_date_1_person": "{isVideo, select, true {ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓} other {ā¤ĢāĨ‹ā¤ŸāĨ‹}} {person1} ⤏āĨ‹ā¤Ŧ⤤ {date} ā¤˛ā¤ž ⤘āĨ‡ā¤¤ā¤˛āĨ‡", @@ -1137,6 +1184,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓} other {ā¤ĢāĨ‹ā¤ŸāĨ‹}} {city}, {country} ⤝āĨ‡ā¤ĨāĨ‡ {person1} ⤆⤪ā¤ŋ {person2} ⤏āĨ‹ā¤Ŧ⤤ {date} ⤰āĨ‹ā¤œāĨ€ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛ā¤ž", "image_alt_text_date_place_3_people": "{isVideo, select, true {ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓} other {ā¤ĢāĨ‹ā¤ŸāĨ‹}} {city}, {country} ⤝āĨ‡ā¤ĨāĨ‡ {person1}, {person2} ⤆⤪ā¤ŋ {person3} ⤏āĨ‹ā¤Ŧ⤤ {date} ⤰āĨ‹ā¤œāĨ€ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛ā¤ž", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓} other {ā¤ĢāĨ‹ā¤ŸāĨ‹}} {city}, {country} ⤝āĨ‡ā¤ĨāĨ‡ {person1}, {person2} ⤆⤪ā¤ŋ ⤇⤤⤰ {additionalCount, number} ā¤œā¤Ŗā¤žā¤‚ā¤¸āĨ‹ā¤Ŧ⤤ {date} ⤰āĨ‹ā¤œāĨ€ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛ā¤ž", + "image_saved_successfully": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤜⤤⤍ ⤕āĨ‡ā¤˛āĨ€", + "image_viewer_page_state_provider_download_started": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤏āĨā¤°āĨ‚ ā¤ā¤žā¤˛āĨ‡", + "image_viewer_page_state_provider_download_success": "ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ⤝ā¤ļ⤏āĨā¤ĩāĨ€ ā¤ā¤žā¤˛āĨ‡", + "image_viewer_page_state_provider_share_error": "ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", "immich_logo": "Immich ⤞āĨ‹ā¤—āĨ‹", "immich_web_interface": "Immich ā¤ĩāĨ‡ā¤Ŧ ā¤‡ā¤‚ā¤Ÿā¤°ā¤ĢāĨ‡ā¤¸", "import_from_json": "JSON ā¤Žā¤§āĨ‚⤍ ā¤†ā¤¯ā¤žā¤¤ ā¤•ā¤°ā¤ž", @@ -1161,6 +1212,12 @@ "invalid_date_format": "⤅ā¤ĩāĨˆā¤§ ā¤¤ā¤žā¤°āĨ€ā¤– ⤏āĨā¤ĩ⤰āĨ‚ā¤Ē", "invite_people": "⤞āĨ‹ā¤•ā¤žā¤‚ā¤¨ā¤ž ā¤†ā¤Žā¤‚ā¤¤āĨā¤°ā¤ŋ⤤ ā¤•ā¤°ā¤ž", "invite_to_album": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤†ā¤Žā¤‚ā¤¤āĨā¤°ā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "ios_debug_info_fetch_ran_at": "ā¤ĢāĨ‡ā¤š {dateTime} ā¤˛ā¤ž ā¤šā¤žā¤˛ā¤˛āĨ‡", + "ios_debug_info_last_sync_at": "ā¤ļāĨ‡ā¤ĩ⤟⤚āĨ‡ ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ {dateTime} ā¤˛ā¤ž", + "ios_debug_info_no_processes_queued": "⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇāĨ‡ā¤¤ ā¤¨ā¤žā¤šāĨ€ā¤¤", + "ios_debug_info_no_sync_yet": "ā¤…ā¤œāĨ‚⤍ ⤕āĨ‹ā¤Ŗā¤¤ā¤žā¤šāĨ€ ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ⤏ā¤ŋ⤂⤕ ⤜āĨ‰ā¤Ŧ ā¤šā¤žā¤˛ā¤˛āĨ‡ā¤˛ā¤ž ā¤¨ā¤žā¤šāĨ€", + "ios_debug_info_processes_queued": "{count, plural, one {{count} ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇāĨ‡ā¤¤} other {{count} ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇāĨ‡ā¤¤}}", + "ios_debug_info_processing_ran_at": "ā¤ĒāĨā¤°āĨ‹ā¤¸āĨ‡ā¤¸ā¤ŋ⤂⤗ {dateTime} ā¤˛ā¤ž ā¤šā¤žā¤˛ā¤˛āĨ‡", "items_count": "{count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž} }", "jobs": "⤜āĨ‰ā¤ŦāĨā¤¸", "keep": "⤠āĨ‡ā¤ĩā¤ž", @@ -1187,6 +1244,9 @@ "library": "ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€", "library_add_folder": "ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤° ⤜āĨ‹ā¤Ąā¤ž", "library_edit_folder": "ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤° ⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "library_options": "ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", + "library_page_device_albums": "ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰āĨ€ā¤˛ ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸", + "library_page_new_album": "⤍ā¤ĩāĨ€ā¤¨ ⤅⤞āĨā¤Ŧā¤Ž", "library_page_sort_asset_count": "⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ā¤šāĨ€ ⤏⤂⤖āĨā¤¯ā¤ž", "library_page_sort_created": "ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", "library_page_sort_last_modified": "ā¤ļāĨ‡ā¤ĩā¤Ÿā¤šā¤ž ā¤Ŧā¤Ļ⤞", @@ -1226,9 +1286,11 @@ "login": "⤞āĨ‰ā¤—ā¤ŋ⤍", "login_disabled": "⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤Ŧ⤂ā¤Ļ ⤕āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡", "login_form_api_exception": "API ⤅ā¤Ēā¤ĩā¤žā¤Ļ. ⤕āĨƒā¤Ēā¤¯ā¤ž ⤏⤰āĨā¤ĩāĨā¤šā¤° URL ⤤ā¤Ēā¤žā¤¸ā¤ž ⤆⤪ā¤ŋ ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĒāĨā¤°ā¤¯ā¤¤āĨā¤¨ ā¤•ā¤°ā¤ž.", + "login_form_back_button_text": "ā¤Žā¤žā¤—āĨ‡", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://⤤āĨā¤Žā¤šā¤ž-⤏⤰āĨā¤ĩāĨā¤šā¤°-IP:ā¤ĒāĨ‹ā¤°āĨā¤Ÿ", "login_form_endpoint_url": "⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤ā¤‚ā¤Ąā¤ĒāĨ‰ā¤‡ā¤‚ā¤Ÿ URL", + "login_form_err_http": "⤕āĨƒā¤Ēā¤¯ā¤ž http:// ⤕ā¤ŋ⤂ā¤ĩā¤ž https:// ā¤¨ā¤ŽāĨ‚ā¤Ļ ā¤•ā¤°ā¤ž", "login_form_err_invalid_email": "⤅ā¤ĩāĨˆā¤§ ā¤ˆā¤ŽāĨ‡ā¤˛", "login_form_err_invalid_url": "⤅ā¤ĩāĨˆā¤§ URL", "login_form_err_leading_whitespace": "⤏āĨā¤°āĨā¤ĩā¤žā¤¤āĨ€ā¤˛ā¤ž ⤏āĨā¤ĒāĨ‡ā¤¸ ā¤†ā¤šāĨ‡", @@ -1238,9 +1300,10 @@ "login_form_failed_login": "⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€, ⤏⤰āĨā¤ĩāĨā¤šā¤° URL, ā¤ˆā¤ŽāĨ‡ā¤˛ ⤆⤪ā¤ŋ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤤ā¤Ēā¤žā¤¸ā¤ž", "login_form_handshake_exception": "⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤¸ā¤š handshake exception ⤆⤞āĨ€. ⤤āĨā¤ŽāĨā¤šāĨ€ self-signed ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤ĩā¤žā¤Ē⤰⤤ ā¤…ā¤¸ā¤žā¤˛ ⤤⤰ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤Žā¤§āĨā¤¯āĨ‡ self-signed ā¤ĒāĨā¤°ā¤Žā¤žā¤Ŗā¤Ē⤤āĨā¤° ā¤¸ā¤Žā¤°āĨā¤Ĩ⤍ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž.", "login_form_password_hint": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", - "login_form_server_empty": "⤏⤰āĨā¤ĩāĨā¤šā¤° URL ā¤­ā¤°ā¤ž", - "login_form_server_error": "⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ļāĨ€ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤆⤞āĨ‡ ā¤¨ā¤žā¤šāĨ€", - "login_has_been_disabled": "⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤Ŧ⤂ā¤Ļ ⤕āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡", + "login_form_save_login": "⤞āĨ‰ā¤—ā¤ŋ⤍ ⤠āĨ‡ā¤ĩāĨ‚⤍ ā¤ĻāĨā¤¯ā¤ž", + "login_form_server_empty": "⤏⤰āĨā¤ĩāĨā¤šā¤° URL ā¤­ā¤°ā¤ž.", + "login_form_server_error": "⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ļāĨ€ ⤜āĨ‹ā¤Ąā¤¤ā¤ž ⤆⤞āĨ‡ ā¤¨ā¤žā¤šāĨ€.", + "login_has_been_disabled": "⤞āĨ‰ā¤—ā¤ŋ⤍ ā¤Ŧ⤂ā¤Ļ ⤕āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡.", "login_password_changed_error": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€ ⤆⤞āĨ€", "login_password_changed_success": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤Ē⤪āĨ‡ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ ā¤ā¤žā¤˛ā¤ž", "logout_all_device_confirmation": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ ⤏⤰āĨā¤ĩ ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸ā¤ĩ⤰āĨ‚⤍ ⤞āĨ‰ā¤— ā¤†ā¤Šā¤Ÿ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", @@ -1254,6 +1317,7 @@ "main_menu": "ā¤ŽāĨā¤–āĨā¤¯ ā¤ŽāĨ‡ā¤¨āĨ‚", "maintenance_description": "Immich ā¤˛ā¤ž ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ą ā¤Žā¤§āĨā¤¯āĨ‡ ⤠āĨ‡ā¤ĩ⤞āĨ‡ ā¤†ā¤šāĨ‡.", "maintenance_end": "ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ą ā¤¸ā¤Žā¤žā¤ĒāĨā¤¤ ā¤•ā¤°ā¤ž", + "maintenance_end_error": "ā¤ĻāĨ‡ā¤–ā¤­ā¤žā¤˛ ā¤ŽāĨ‹ā¤Ą ⤏⤂ā¤Ēā¤ĩ⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€.", "maintenance_logged_in_as": "⤏⤧āĨā¤¯ā¤ž {user} ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤞āĨ‰ā¤— ⤇⤍ ā¤†ā¤šāĨ‡", "maintenance_title": "ā¤¤ā¤žā¤¤āĨā¤ĒāĨā¤°ā¤¤āĨ‡ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€", "make": "ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", @@ -1262,6 +1326,7 @@ "manage_media_access_settings": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤‰ā¤˜ā¤Ąā¤ž", "manage_media_access_subtitle": "Immich ⤅āĨ…ā¤Ēā¤˛ā¤ž ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤ĩ ā¤šā¤˛ā¤ĩ⤪āĨā¤¯ā¤žā¤šāĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤ĻāĨā¤¯ā¤ž.", "manage_media_access_title": "ā¤ŽāĨ€ā¤Ąā¤ŋā¤¯ā¤ž ā¤ŽāĨ…⤍āĨ‡ā¤œā¤ŽāĨ‡ā¤‚ā¤Ÿ ā¤ĒāĨā¤°ā¤ĩāĨ‡ā¤ļ", + "manage_shared_links": "ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ⤞ā¤ŋ⤂⤕ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "manage_sharing_with_partners": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤°āĨā¤¸ā¤¸āĨ‹ā¤Ŧ⤤ ā¤ļāĨ‡ā¤…⤰āĨ€ā¤‚⤗ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "manage_the_app_settings": "⤅āĨ…ā¤Ē ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "manage_your_account": "⤤āĨā¤Žā¤šāĨ‡ ā¤–ā¤žā¤¤āĨ‡ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", @@ -1269,10 +1334,13 @@ "manage_your_devices": "⤤āĨā¤Žā¤šāĨ€ ⤞āĨ‰ā¤—ā¤ŋ⤍ ⤅⤏⤞āĨ‡ā¤˛āĨ€ ā¤Ąā¤ŋā¤ĩāĨā¤šā¤žā¤‡ā¤¸āĨ‡ā¤¸ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "manage_your_oauth_connection": "⤤āĨā¤Žā¤šāĨ‡ OAuth ⤕⤍āĨ‡ā¤•āĨā¤ļ⤍ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", "map": "ā¤¨ā¤•ā¤žā¤ļā¤ž", + "map_assets_in_bounds": "{count, plural, =0 {ā¤¯ā¤ž ā¤­ā¤žā¤—ā¤žā¤¤ ⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤¨ā¤žā¤šāĨ€ā¤¤} one {# ā¤ĢāĨ‹ā¤ŸāĨ‹} other {# ā¤ĢāĨ‹ā¤ŸāĨ‹}}", "map_cannot_get_user_location": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤šāĨ‡ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ā¤Žā¤ŋ⤺āĨ‚ ā¤ļ⤕⤞āĨ‡ ā¤¨ā¤žā¤šāĨ€", + "map_location_dialog_yes": "ā¤šāĨ‹ā¤¯", "map_location_picker_page_use_location": "ā¤šāĨ‡ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ā¤ĩā¤žā¤Ēā¤°ā¤ž", "map_location_service_disabled_content": "⤏⤧āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ā¤ĩ⤰āĨ€ā¤˛ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ā¤Ļā¤žā¤–ā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ⤏āĨ‡ā¤ĩā¤ž ⤏⤕āĨā¤ˇā¤Ž ⤅⤏⤪āĨ‡ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤†ā¤šāĨ‡. ⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤤āĨ€ ⤆⤤āĨā¤¤ā¤ž ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤žā¤¯ā¤šāĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "map_location_service_disabled_title": "⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ⤏āĨ‡ā¤ĩā¤ž ā¤Ŧ⤂ā¤Ļ ā¤†ā¤šāĨ‡", + "map_marker_for_images": "{city}, {country} ⤝āĨ‡ā¤ĨāĨ‡ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤žā¤‚ā¤¸ā¤žā¤ āĨ€ ā¤¨ā¤•ā¤žā¤ļā¤ž ā¤Žā¤žā¤°āĨā¤•⤰", "map_marker_with_image": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤ŽāĨ‡ā¤¸ā¤š ā¤¨ā¤•ā¤žā¤ļā¤ž ā¤Žā¤žā¤°āĨā¤•⤰", "map_no_location_permission_content": "⤏⤧āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ā¤ĩ⤰āĨ€ā¤˛ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ā¤Ļā¤žā¤–ā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤†ā¤šāĨ‡. ⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤤āĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ⤆⤤āĨā¤¤ā¤ž ā¤ĻāĨā¤¯ā¤žā¤¯ā¤šāĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "map_no_location_permission_title": "⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤¨ā¤žā¤•ā¤žā¤°ā¤˛āĨ€", @@ -1282,11 +1350,14 @@ "map_settings_date_range_option_days": "ā¤Žā¤žā¤—āĨ€ā¤˛ {days} ā¤Ļā¤ŋā¤ĩ⤏", "map_settings_date_range_option_year": "ā¤Žā¤žā¤—āĨ€ā¤˛ ā¤ĩ⤰āĨā¤ˇ", "map_settings_date_range_option_years": "ā¤Žā¤žā¤—āĨ€ā¤˛ {years} ā¤ĩ⤰āĨā¤ˇāĨ‡", + "map_settings_dialog_title": "ā¤¨ā¤•ā¤žā¤ļā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "map_settings_include_show_archived": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤š ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", "map_settings_include_show_partners": "ā¤Ēā¤žā¤°āĨā¤Ÿā¤¨ā¤°āĨā¤¸ ā¤¸ā¤Žā¤žā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", "map_settings_only_show_favorites": "ā¤Ģ⤕āĨā¤¤ ⤆ā¤ĩā¤Ąā¤¤āĨ‡ ā¤Ļā¤žā¤–ā¤ĩā¤ž", "map_settings_theme_settings": "ā¤¨ā¤•ā¤žā¤ļā¤ž ā¤ĨāĨ€ā¤Ž", + "map_zoom_to_see_photos": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤āĨ‚ā¤Ž ā¤†ā¤‰ā¤Ÿ ā¤•ā¤°ā¤ž", "mark_all_as_read": "⤏⤰āĨā¤ĩ ā¤ĩā¤žā¤šā¤˛āĨ‡ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤚ā¤ŋ⤍āĨā¤šā¤žā¤‚⤕ā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "mark_as_read": "ā¤ĩā¤žā¤šā¤˛āĨ‡ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤚ā¤ŋ⤍āĨā¤šā¤žā¤‚⤕ā¤ŋ⤤ ā¤•ā¤°ā¤ž", "marked_all_as_read": "⤏⤰āĨā¤ĩ ā¤ĩā¤žā¤šā¤˛āĨ‡ ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤚ā¤ŋ⤍āĨā¤šā¤žā¤‚⤕ā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡", "matches": "⤜āĨā¤ŗā¤Ŗā¤žā¤°āĨ‡", "matching_assets": "⤜āĨā¤ŗā¤Ŗā¤žā¤°āĨ‡ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸", @@ -1314,6 +1385,7 @@ "mobile_app_download_onboarding_note": "ā¤–ā¤žā¤˛āĨ€ ā¤Ļā¤ŋ⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Ē⤰āĨā¤¯ā¤žā¤¯ā¤žā¤‚ā¤šā¤ž ā¤ĩā¤žā¤Ē⤰ ⤕⤰āĨ‚⤍ ā¤¸ā¤žā¤ĨāĨ€ā¤Ļā¤žā¤° ā¤ŽāĨ‹ā¤Ŧā¤žā¤ˆā¤˛ ⤅āĨ…ā¤Ē ā¤Ąā¤žā¤‰ā¤¨ā¤˛āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", "model": "ā¤ŽāĨ‰ā¤ĄāĨ‡ā¤˛", "month": "ā¤Žā¤šā¤ŋā¤¨ā¤ž", + "monthly_title_text_date_format": "ā¤ā¤Žā¤ā¤Žā¤ā¤Žā¤ā¤Ž ā¤ĩā¤žā¤ˆ", "more": "⤅⤧ā¤ŋ⤕", "move": "ā¤šā¤˛ā¤ĩā¤ž", "move_off_locked_folder": "⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤šāĨā¤¯ā¤ž ā¤Ŧā¤žā¤šāĨ‡ā¤° ā¤šā¤˛ā¤ĩā¤ž", @@ -1321,7 +1393,11 @@ "move_to_lock_folder_action_prompt": "{count} ⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨā¤¯āĨ‡ ⤜āĨ‹ā¤Ąā¤˛āĨ‡", "move_to_locked_folder": "⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩā¤ž", "move_to_locked_folder_confirmation": "ā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤏⤰āĨā¤ĩ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤•ā¤žā¤ĸ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛ ⤆⤪ā¤ŋ ā¤Ģ⤕āĨā¤¤ ⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨ‚ā¤¨ā¤š ā¤Ļā¤ŋ⤏⤤āĨ€ā¤˛", + "moved_to_archive": "{count, plural, one {# ⤅āĨ…⤏āĨ‡ā¤Ÿ ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤šā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩā¤˛ā¤ž} other {# ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤šā¤Žā¤§āĨā¤¯āĨ‡ ā¤šā¤˛ā¤ĩ⤞āĨ‡}}", + "moved_to_library": "{count, plural, one {# ⤅āĨ…⤏āĨ‡ā¤Ÿ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ā¤¤ ā¤šā¤˛ā¤ĩā¤˛ā¤ž} other {# ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨ‡}}", "moved_to_trash": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤šā¤˛ā¤ĩ⤞āĨ‡", + "multiselect_grid_edit_date_time_err_read_only": "ā¤Ģ⤕āĨā¤¤-ā¤ĩā¤žā¤šā¤¨ (read-only) ⤅āĨ…⤏āĨ‡ā¤Ÿā¤šāĨ€ ā¤¤ā¤žā¤°āĨ€ā¤– ā¤Ŧā¤Ļ⤞āĨ‚ ā¤ļ⤕⤤ ā¤¨ā¤žā¤šāĨ€, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‹ā¤¤", + "multiselect_grid_edit_gps_err_read_only": "ā¤Ģ⤕āĨā¤¤-ā¤ĩā¤žā¤šā¤¨ (read-only) ⤅āĨ…⤏āĨ‡ā¤Ÿā¤šāĨ‡ ⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ā¤Ŧā¤Ļ⤞āĨ‚ ā¤ļ⤕⤤ ā¤¨ā¤žā¤šāĨ€, ā¤ĩ⤗⤺⤤ ā¤†ā¤šāĨ‹ā¤¤", "mute_memories": "⤆⤠ā¤ĩ⤪āĨ€ ā¤ŽāĨā¤¯āĨ‚ā¤Ÿ ā¤•ā¤°ā¤ž", "my_albums": "ā¤Žā¤žā¤āĨ‡ ⤅⤞āĨā¤Ŧā¤ŽāĨā¤¸", "name": "ā¤¨ā¤žā¤ĩ", @@ -1337,10 +1413,12 @@ "never": "⤕⤧āĨ€ā¤š ā¤¨ā¤žā¤šāĨ€", "new_album": "⤍ā¤ĩāĨ€ā¤¨ ⤅⤞āĨā¤Ŧā¤Ž", "new_api_key": "⤍ā¤ĩāĨ€ā¤¨ API ⤕āĨ€", + "new_date_range": "⤍ā¤ĩāĨ€ā¤¨ ā¤¤ā¤žā¤°āĨ€ā¤– ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€", "new_password": "⤍ā¤ĩāĨ€ā¤¨ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą", "new_person": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€", "new_pin_code": "⤍ā¤ĩāĨ€ā¤¨ PIN ⤕āĨ‹ā¤Ą", "new_pin_code_subtitle": "⤤āĨā¤ŽāĨā¤šāĨ€ ā¤ĒāĨā¤°ā¤Ĩā¤Žā¤š ⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤° ā¤ĩā¤žā¤Ē⤰⤤ ā¤†ā¤šā¤žā¤¤. ā¤šāĨ‡ ā¤Ēā¤žā¤¨ ⤏āĨā¤°ā¤•āĨā¤ˇā¤ŋ⤤ā¤Ē⤪āĨ‡ ā¤‰ā¤˜ā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ PIN ⤕āĨ‹ā¤Ą ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", + "new_timeline": "⤍ā¤ĩāĨ€ā¤¨ ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨", "new_update": "⤍ā¤ĩāĨ€ā¤¨ ⤅ā¤Ēā¤ĄāĨ‡ā¤Ÿ", "new_user_created": "⤍ā¤ĩāĨ€ā¤¨ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤¤ā¤¯ā¤žā¤° ā¤ā¤žā¤˛ā¤ž", "new_version_available": "⤍ā¤ĩāĨ€ā¤¨ ⤆ā¤ĩāĨƒā¤¤āĨā¤¤āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§", @@ -1364,6 +1442,7 @@ "no_favorites_message": "⤤āĨā¤Žā¤šāĨ‡ ⤏⤰āĨā¤ĩāĨ‹ā¤¤āĨā¤¤ā¤Ž ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤Ēā¤Ÿā¤•ā¤¨ ā¤ļāĨ‹ā¤§ā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤤āĨā¤¯ā¤žā¤‚ā¤¨ā¤ž ⤆ā¤ĩā¤Ąā¤¤āĨā¤¯ā¤žā¤‚⤤ ⤜āĨ‹ā¤Ąā¤ž", "no_libraries_message": "⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤Ŧā¤žā¤šāĨā¤¯ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "no_local_assets_found": "ā¤¯ā¤ž ⤚āĨ‡ā¤•ā¤¸ā¤Žā¤¸ā¤š ⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ⤅āĨ…⤏āĨ‡ā¤Ÿ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€", + "no_location_set": "⤞āĨ‹ā¤•āĨ‡ā¤ļ⤍ ⤏āĨ‡ā¤Ÿ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€", "no_locked_photos_message": "⤞āĨ‰ā¤• ā¤ĢāĨ‹ā¤˛āĨā¤Ąā¤°ā¤Žā¤§āĨ€ā¤˛ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤞ā¤Ēā¤ĩ⤞āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡ā¤¤ ⤆⤪ā¤ŋ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤ŦāĨā¤°ā¤žā¤‰ā¤œ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤ļāĨ‹ā¤§ā¤¤ā¤žā¤¨ā¤ž ā¤Ļā¤ŋā¤¸ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€ā¤¤.", "no_name": "⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ā¤¨ā¤žā¤ĩ ā¤¨ā¤žā¤šāĨ€", "no_notifications": "⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤¨ā¤žā¤šāĨ€ā¤¤", @@ -1373,6 +1452,7 @@ "no_results": "⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ⤍ā¤ŋā¤•ā¤žā¤˛ ā¤¨ā¤žā¤šāĨ€ā¤¤", "no_results_description": "ā¤¸ā¤Žā¤žā¤°āĨā¤Ĩ⤕ ā¤ļā¤ŦāĨā¤Ļ ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤅⤧ā¤ŋ⤕ ⤏⤰āĨā¤ĩā¤¸ā¤žā¤§ā¤žā¤°ā¤Ŗ ⤕āĨ€ā¤ĩ⤰āĨā¤Ą ā¤ĩā¤žā¤Ē⤰āĨ‚⤍ ā¤Ēā¤žā¤šā¤ž", "no_shared_albums_message": "⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤•ā¤Žā¤§āĨ€ā¤˛ ⤞āĨ‹ā¤•ā¤žā¤‚ā¤¸āĨ‹ā¤Ŧ⤤ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ļāĨ‡ā¤…⤰ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ā¤• ⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", + "no_uploads_in_progress": "⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ⤅ā¤Ē⤞āĨ‹ā¤Ą ⤏āĨā¤°āĨ‚ ā¤¨ā¤žā¤šāĨ€ā¤¤", "not_allowed": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤¨ā¤žā¤šāĨ€", "not_available": "⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€", "not_in_any_album": "⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤¨ā¤žā¤šāĨ€", @@ -1417,6 +1497,7 @@ "original": "ā¤ŽāĨ‚⤺", "other": "⤇⤤⤰", "other_devices": "⤇⤤⤰ ⤉ā¤Ē⤕⤰⤪āĨ‡", + "other_entities": "⤇⤤⤰ ā¤˜ā¤Ÿā¤•", "other_variables": "⤇⤤⤰ ⤚⤞", "owned": "ā¤Žā¤žā¤˛ā¤•āĨ€ā¤šāĨ‡", "owner": "ā¤Žā¤žā¤˛ā¤•", @@ -1425,9 +1506,12 @@ "partner_can_access_assets": "⤏⤂⤗āĨā¤°ā¤šā¤ŋ⤤ ā¤ĩ ā¤šā¤Ÿā¤ĩā¤ŋ⤞āĨ‡ā¤˛āĨ‡ ā¤ĩā¤—ā¤ŗā¤¤ā¤ž ⤤āĨā¤Žā¤šāĨ‡ ⤏⤰āĨā¤ĩ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", "partner_can_access_location": "⤜āĨā¤¯ā¤ž ⤠ā¤ŋā¤•ā¤žā¤ŖāĨ€ ⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤•ā¤žā¤ĸ⤞āĨ‡ ⤗āĨ‡ā¤˛āĨ‡ ⤤āĨ‡ ⤏āĨā¤Ĩā¤žā¤¨", "partner_list_user_photos": "{user} ⤚āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹", + "partner_list_view_all": "⤏⤰āĨā¤ĩ ā¤Ēā¤šā¤ž", "partner_page_empty_message": "⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤…ā¤œāĨ‚⤍ ⤕āĨ‹ā¤Ŗā¤¤āĨā¤¯ā¤žā¤šāĨ€ ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤žā¤¸āĨ‹ā¤Ŧ⤤ ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€ā¤¤.", "partner_page_no_more_users": "⤜āĨ‹ā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤆⤪⤖āĨ€ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨ‡ ā¤¨ā¤žā¤šāĨ€ā¤¤", + "partner_page_partner_add_failed": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤜āĨ‹ā¤Ąā¤ŖāĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "partner_page_select_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "partner_page_shared_to_title": "ā¤¯ā¤žā¤‚ā¤¨ā¤ž ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", "partner_page_stop_sharing_content": "{partner} ā¤†ā¤¤ā¤ž ⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤Ēā¤žā¤šāĨ‚ ā¤ļā¤•ā¤Ŗā¤žā¤° ā¤¨ā¤žā¤šāĨ€.", "partner_sharing": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤° ā¤ļāĨ‡ā¤…⤰ā¤ŋ⤂⤗", "partners": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°", @@ -1449,6 +1533,7 @@ "people": "⤞āĨ‹ā¤•", "people_edits_count": "⤏⤂ā¤Ēā¤žā¤Ļā¤ŋ⤤ {count, plural, one {# ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€} other {# ⤞āĨ‹ā¤•}}", "people_feature_description": "⤞āĨ‹ā¤•ā¤žā¤‚ā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ŦāĨā¤°ā¤žā¤‰ā¤ ā¤•ā¤°ā¤ž", + "people_sidebar_description": "ā¤¸ā¤žā¤‡ā¤Ąā¤Ŧā¤žā¤°ā¤Žā¤§āĨā¤¯āĨ‡ â€œā¤˛āĨ‹ā¤•” ā¤¸ā¤žā¤ āĨ€ ā¤ĻāĨā¤ĩā¤ž ā¤Ļā¤žā¤–ā¤ĩā¤ž", "permanent_deletion_warning": "ā¤•ā¤žā¤¯ā¤Žā¤¸āĨā¤ĩ⤰āĨ‚ā¤ĒāĨ€ ā¤ĩā¤ŋ⤞āĨ‹ā¤Ē⤍ ⤏āĨ‚ā¤šā¤¨ā¤ž", "permanent_deletion_warning_setting_description": "ā¤…â€āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩā¤¤ā¤žā¤¨ā¤ž ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤Ļā¤žā¤–ā¤ĩā¤ž", "permanently_delete": "ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩā¤ž", @@ -1458,6 +1543,14 @@ "permanently_deleted_assets_count": "ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨ‡ {count, plural, one {# ā¤…â€āĨ…⤏āĨ‡ā¤Ÿ} other {# ā¤…â€āĨ…⤏āĨ‡ā¤ŸāĨā¤¸}}", "permission": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€", "permission_empty": "⤤āĨā¤Žā¤šāĨ€ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ⤰ā¤ŋ⤕āĨā¤¤ ⤅⤏āĨ‚ ⤍⤝āĨ‡", + "permission_onboarding_back": "ā¤Žā¤žā¤—āĨ‡", + "permission_onboarding_continue_anyway": "⤤⤰āĨ€ā¤šāĨ€ ā¤ĒāĨā¤ĸāĨ‡ ā¤œā¤ž", + "permission_onboarding_get_started": "⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", + "permission_onboarding_go_to_settings": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤Žā¤§āĨā¤¯āĨ‡ ā¤œā¤ž", + "permission_onboarding_permission_denied": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤¨ā¤žā¤•ā¤žā¤°ā¤˛āĨ€. Immich ā¤ĩā¤žā¤Ē⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€, ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨā¤¯ā¤ž ā¤ĻāĨā¤¯ā¤ž.", + "permission_onboarding_permission_granted": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤Žā¤‚ā¤œāĨ‚⤰! ⤏⤰āĨā¤ĩ ā¤¤ā¤¯ā¤žā¤°.", + "permission_onboarding_permission_limited": "ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ā¤Žā¤°āĨā¤¯ā¤žā¤Ļā¤ŋ⤤. Immich ā¤˛ā¤ž ⤏⤂ā¤ĒāĨ‚⤰āĨā¤Ŗ ⤗āĨ…⤞⤰āĨ€ ⤏⤂⤗āĨā¤°ā¤šā¤žā¤šā¤ž ā¤ŦāĨ…⤕⤅ā¤Ē ā¤ĩ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ē⤍ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€, ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤Žā¤§āĨā¤¯āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨā¤¯ā¤ž ā¤ĻāĨā¤¯ā¤ž.", + "permission_onboarding_request": "⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ Immich ā¤˛ā¤ž ā¤Ē⤰ā¤ĩā¤žā¤¨ā¤—āĨ€ ⤆ā¤ĩā¤ļāĨā¤¯ā¤• ā¤†ā¤šāĨ‡.", "person": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€", "person_age_months": "{months, plural, one {# ā¤Žā¤šā¤ŋā¤¨ā¤ž} other {# ā¤Žā¤šā¤ŋ⤍āĨ‡}} ā¤ĩ⤝", "person_age_year_months": "1 ā¤ĩ⤰āĨā¤ˇ, {months, plural, one {# ā¤Žā¤šā¤ŋā¤¨ā¤ž} other {# ā¤Žā¤šā¤ŋ⤍āĨ‡}} ā¤ĩ⤝", @@ -1467,6 +1560,7 @@ "photo_shared_all_users": "⤤āĨā¤ŽāĨā¤šāĨ€ ⤏⤰āĨā¤ĩ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤‚⤏āĨ‹ā¤Ŧ⤤ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡ ā¤†ā¤šāĨ‡ā¤¤ ⤅⤏āĨ‡ ā¤Ļā¤ŋ⤏⤤āĨ‡ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤ļāĨ‡ā¤…⤰ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤕āĨ‹ā¤Ŗā¤¤ā¤žā¤šāĨ€ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤¨ā¤žā¤šāĨ€.", "photos": "ā¤ĢāĨ‹ā¤ŸāĨ‹", "photos_and_videos": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤆⤪ā¤ŋ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓", + "photos_count": "{count, plural, one {{count, number} ā¤ĢāĨ‹ā¤ŸāĨ‹} other {{count, number} ā¤ĢāĨ‹ā¤ŸāĨ‹}}", "photos_from_previous_years": "ā¤Žā¤žā¤—āĨ€ā¤˛ ā¤ĩ⤰āĨā¤ˇā¤žā¤‚⤤āĨ€ā¤˛ ā¤ĢāĨ‹ā¤ŸāĨ‹", "pick_a_location": "⤏āĨā¤Ĩā¤žā¤¨ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "pick_custom_range": "⤕⤏āĨā¤Ÿā¤Ž ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€", @@ -1502,6 +1596,7 @@ "privacy": "⤗āĨ‹ā¤Ē⤍āĨ€ā¤¯ā¤¤ā¤ž", "profile": "ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤žā¤‡ā¤˛", "profile_drawer_app_logs": "⤞āĨ‰ā¤—āĨā¤¸", + "profile_drawer_client_server_up_to_date": "⤕āĨā¤˛ā¤žā¤¯ā¤‚ā¤Ÿ ⤆⤪ā¤ŋ ⤏⤰āĨā¤ĩāĨā¤šā¤° ⤅ā¤ĻāĨā¤¯ā¤¯ā¤žā¤ĩ⤤ ā¤†ā¤šāĨ‡ā¤¤", "profile_drawer_github": "⤗ā¤ŋā¤Ÿā¤šā¤Ŧ", "profile_drawer_readonly_mode": "ā¤Ģ⤕āĨā¤¤-ā¤ĩā¤žā¤šā¤¨ ā¤ŽāĨ‹ā¤Ą ⤏⤕āĨā¤ˇā¤Ž. ā¤Ŧā¤žā¤šāĨ‡ā¤° ā¤Ēā¤Ąā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ⤅ā¤ĩā¤¤ā¤žā¤° ⤆⤝⤕āĨ‰ā¤¨ā¤ĩ⤰ ā¤˛ā¤žā¤‚ā¤Ŧ-ā¤ĒāĨā¤°āĨ‡ā¤¸ ā¤•ā¤°ā¤ž.", "profile_image_of_user": "{user} ⤚āĨ€ ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤žā¤‡ā¤˛ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž", @@ -1524,6 +1619,7 @@ "purchase_individual_description_2": "ā¤¸ā¤Žā¤°āĨā¤Ĩ⤕ ⤏āĨā¤Ĩā¤ŋ⤤āĨ€", "purchase_individual_title": "ā¤ĩāĨˆā¤¯ā¤•āĨā¤¤ā¤ŋ⤕", "purchase_input_suggestion": "ā¤ĒāĨā¤°āĨ‰ā¤Ąā¤•āĨā¤Ÿ ⤕āĨ€ ā¤†ā¤šāĨ‡? ā¤–ā¤žā¤˛āĨ€ ⤕āĨ€ ā¤Ÿā¤žā¤•ā¤ž", + "purchase_license_subtitle": "⤏āĨ‡ā¤ĩāĨ‡ā¤šāĨā¤¯ā¤ž ā¤ĒāĨā¤ĸāĨ€ā¤˛ ā¤ĩā¤ŋā¤•ā¤žā¤¸ā¤žā¤¸ā¤žā¤ āĨ€ Immich ⤖⤰āĨ‡ā¤ĻāĨ€ ⤕⤰āĨ‚⤍ ā¤¸ā¤žā¤Ĩ ā¤ĻāĨā¤¯ā¤ž", "purchase_lifetime_description": "⤆⤝āĨā¤ˇāĨā¤¯ā¤­ā¤°ā¤žā¤šāĨ€ ⤖⤰āĨ‡ā¤ĻāĨ€", "purchase_option_title": "⤖⤰āĨ‡ā¤ĻāĨ€ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", "purchase_panel_info_1": "Immich ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤪āĨ‡ ā¤ĩāĨ‡ā¤ŗā¤–ā¤žā¤Š ⤆⤪ā¤ŋ ⤕⤎āĨā¤Ÿā¤žā¤šāĨ‡ ā¤†ā¤šāĨ‡. ā¤†ā¤Žā¤šāĨ‡ ⤧āĨā¤¯āĨ‡ā¤¯ ā¤ŽāĨā¤•āĨā¤¤-⤏āĨā¤°āĨ‹ā¤¤ ⤏āĨ‰ā¤ĢāĨā¤Ÿā¤ĩāĨ‡ā¤…⤰ ā¤ĩ ⤍āĨˆā¤¤ā¤ŋ⤕ ā¤ĩāĨā¤¯ā¤žā¤ĩā¤¸ā¤žā¤¯ā¤ŋ⤕ ā¤Ēā¤ĻāĨā¤§ā¤¤āĨ€ā¤‚ā¤Žā¤§āĨ‚⤍ ⤟ā¤ŋā¤•ā¤žā¤Š ⤉⤤āĨā¤Ē⤍āĨā¤¨ ā¤Žā¤ŋ⤺ā¤ĩ⤪āĨ‡, ā¤ĩā¤ŋā¤•ā¤¸ā¤•ā¤žā¤‚ā¤¨ā¤ž ā¤†ā¤§ā¤žā¤° ā¤ĻāĨ‡ā¤ŖāĨ‡ ⤆⤪ā¤ŋ ā¤ļāĨ‹ā¤ˇā¤Ŗā¤•ā¤žā¤°āĨ€ ⤕āĨā¤˛ā¤žā¤‰ā¤Ą ⤏āĨ‡ā¤ĩā¤žā¤‚ā¤¨ā¤ž ā¤Ē⤰āĨā¤¯ā¤žā¤¯ ā¤ĻāĨ‡ā¤Ŗā¤žā¤°āĨ‡ ⤗āĨ‹ā¤Ē⤍āĨ€ā¤¯ā¤¤āĨ‡ā¤šā¤ž ā¤Žā¤žā¤¨ ā¤°ā¤žā¤–ā¤Ŗā¤žā¤°āĨ‡ ⤇⤕āĨ‹ā¤¸ā¤ŋ⤏āĨā¤Ÿā¤Ž ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤪āĨ‡ ā¤šāĨ‡ ā¤†ā¤šāĨ‡.", @@ -1555,16 +1651,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž}} ⤍ā¤ĩāĨā¤¯ā¤ž ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤•ā¤ĄāĨ‡ ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤍ā¤ŋ⤝āĨā¤•āĨā¤¤ ⤕āĨ‡ā¤˛āĨ‡", "reassing_hint": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨ‡ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤ĩā¤ŋā¤ĻāĨā¤¯ā¤Žā¤žā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤•ā¤ĄāĨ‡ ⤍ā¤ŋ⤝āĨā¤•āĨā¤¤ ā¤•ā¤°ā¤ž", "recent": "⤅⤞āĨ€ā¤•ā¤ĄāĨ€ā¤˛", + "recent_albums": "⤅⤞āĨ€ā¤•ā¤ĄāĨ€ā¤˛ ⤅⤞āĨā¤Ŧā¤Ž", "recent_searches": "⤅⤞āĨ€ā¤•ā¤ĄāĨ€ā¤˛ ā¤ļāĨ‹ā¤§", "recently_added": "⤍āĨā¤•⤤āĨ‡ā¤š ⤜āĨ‹ā¤Ąā¤˛āĨ‡ā¤˛āĨ‡", "recently_added_page_title": "⤍āĨā¤•⤤āĨ‡ā¤š ⤜āĨ‹ā¤Ąā¤˛āĨ‡ā¤˛āĨ‡", "recently_taken": "⤅⤞āĨ€ā¤•ā¤ĄāĨ‡ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛āĨ‡", + "recently_taken_page_title": "⤅⤞āĨ€ā¤•ā¤ĄāĨ‡ ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛āĨ‡", "refresh": "⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž", "refresh_encoded_videos": "ā¤ā¤¨āĨā¤•āĨ‹ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž", "refresh_faces": "⤚āĨ‡ā¤šā¤°āĨ‡ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž", "refresh_metadata": "ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž", "refresh_thumbnails": "ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž", "refreshed": "⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤ā¤žā¤˛āĨ‡", + "refreshes_every_file": "ā¤ĩā¤ŋā¤ĻāĨā¤¯ā¤Žā¤žā¤¨ ā¤ĩ ⤍ā¤ĩāĨ€ā¤¨ ⤏⤰āĨā¤ĩ ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤ĩā¤žā¤šā¤ž", "refreshing_encoded_video": "ā¤ā¤¨āĨā¤•āĨ‹ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ā¤˛ā¤ž ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ⤕⤰⤤ ā¤†ā¤šāĨ‡", "refreshing_faces": "⤚āĨ‡ā¤šā¤°āĨ‡ ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ⤕⤰⤤ ā¤†ā¤šāĨ‡", "refreshing_metadata": "ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ⤕⤰⤤ ā¤†ā¤šāĨ‡", @@ -1577,6 +1676,7 @@ "remove_assets_shared_link_confirmation": "ā¤¯ā¤ž ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ā¤ĻāĨā¤ĩāĨā¤¯ā¤žā¤¤āĨ‚⤍ {count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž}} ā¤•ā¤žā¤ĸā¤žā¤¯ā¤šāĨ‡ ā¤†ā¤šāĨ‡ā¤¤ ā¤•ā¤ž?", "remove_assets_title": "ā¤†ā¤¯ā¤Ÿā¤Ž ā¤•ā¤žā¤ĸā¤žā¤¯ā¤šāĨ‡?", "remove_custom_date_range": "ā¤¸ā¤žā¤¨āĨā¤•āĨ‚⤞ ā¤Ļā¤ŋā¤¨ā¤žā¤‚ā¤• ā¤ļāĨā¤°āĨ‡ā¤ŖāĨ€ ā¤•ā¤žā¤ĸā¤ž", + "remove_deleted_assets": "ā¤šā¤Ÿā¤ĩ⤞āĨ‡ā¤˛āĨ‡ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤•ā¤žā¤ĸā¤ž", "remove_from_album": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤•ā¤žā¤ĸā¤ž", "remove_from_album_action_prompt": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ {count} ā¤•ā¤žā¤ĸ⤞āĨ‡", "remove_from_favorites": "⤆ā¤ĩā¤ĄāĨ€ā¤¤āĨ‚⤍ ā¤•ā¤žā¤ĸā¤ž", @@ -1587,25 +1687,32 @@ "remove_memory": "ā¤ŽāĨ‡ā¤Žā¤°āĨ€ ā¤•ā¤žā¤ĸā¤ž", "remove_photo_from_memory": "ā¤¯ā¤ž ā¤ŽāĨ‡ā¤Žā¤°āĨ€ā¤¤āĨ‚⤍ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤•ā¤žā¤ĸā¤ž", "remove_tag": "⤟āĨ…⤗ ā¤•ā¤žā¤ĸā¤ž", + "remove_url": "URL ā¤•ā¤žā¤ĸā¤ž", "remove_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤•ā¤žā¤ĸā¤ž", "removed_api_key": "ā¤•ā¤žā¤ĸ⤞āĨ‡ā¤˛āĨ€ API ⤕āĨ€: {name}", "removed_from_archive": "⤆⤰āĨā¤•ā¤žā¤‡ā¤ĩāĨā¤šā¤Žā¤§āĨ‚⤍ ā¤•ā¤žā¤ĸ⤞āĨ‡", "removed_from_favorites": "⤆ā¤ĩā¤ĄāĨ€ā¤¤āĨ‚⤍ ā¤•ā¤žā¤ĸ⤞āĨ‡", "removed_from_favorites_count": "{count, plural, other {⤆ā¤ĩā¤ĄāĨ€ā¤¤āĨ‚⤍ # ā¤•ā¤žā¤ĸ⤞āĨ‡}}", "removed_memory": "ā¤ŽāĨ‡ā¤Žā¤°āĨ€ ā¤•ā¤žā¤ĸ⤞āĨ€", + "removed_photo_from_memory": "ā¤ŽāĨ‡ā¤Žā¤°āĨ€ā¤¤āĨ‚⤍ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤•ā¤žā¤ĸā¤˛ā¤ž", "removed_tagged_assets": "{count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Žā¤ĩ⤰āĨ‚⤍ ⤟āĨ…⤗ ā¤•ā¤žā¤ĸā¤˛ā¤ž} other {# ā¤†ā¤¯ā¤Ÿā¤Žā¤ĩ⤰āĨ‚⤍ ⤟āĨ…⤗ ā¤•ā¤žā¤ĸ⤞āĨ‡}}", "rename": "ā¤¨ā¤žā¤ĩ ā¤Ŧā¤Ļā¤˛ā¤ž", + "repair": "ā¤ĻāĨā¤°āĨā¤¸āĨā¤¤āĨ€", + "repair_no_results_message": "ā¤…ā¤¨ā¤ŸāĨā¤°āĨ…⤕āĨā¤Ą ā¤ĩ ā¤šā¤°ā¤ĩ⤞āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ ⤝āĨ‡ā¤ĨāĨ‡ ā¤Ļā¤ŋ⤏⤤āĨ€ā¤˛", + "replace_with_upload": "⤅ā¤Ē⤞āĨ‹ā¤Ąā¤¨āĨ‡ ā¤Ŧā¤Ļā¤˛ā¤ž", "repository": "⤰ā¤ŋā¤ĒāĨ‰ā¤ā¤ŋ⤟⤰āĨ€", "require_password": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤆ā¤ĩā¤ļāĨā¤¯ā¤•", + "require_user_to_change_password_on_first_login": "ā¤Ēā¤šā¤ŋ⤞āĨā¤¯ā¤ž ⤞āĨ‰ā¤—ā¤ŋ⤍ā¤ĩāĨ‡ā¤ŗāĨ€ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤¨āĨ‡ ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤Ŧā¤Ļ⤞⤪āĨ‡ ⤆ā¤ĩā¤ļāĨā¤¯ā¤•", "rescan": "ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤏āĨā¤•āĨ…⤍ ā¤•ā¤°ā¤ž", "reset": "⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "reset_password": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "reset_people_visibility": "⤞āĨ‹ā¤•ā¤žā¤‚ā¤šāĨ€ ā¤ĻāĨƒā¤ļāĨā¤¯ā¤¤ā¤ž ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "reset_pin_code": "PIN ⤕āĨ‹ā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "reset_pin_code_description": "⤤āĨā¤Žā¤šā¤ž PIN ā¤ĩā¤ŋā¤¸ā¤°ā¤˛ā¤ž ⤅⤏⤞āĨā¤¯ā¤žā¤¸, ⤤āĨ‹ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤏⤰āĨā¤ĩāĨā¤šā¤° ā¤ĒāĨā¤°ā¤ļā¤žā¤¸ā¤•ā¤žā¤ļāĨ€ ⤏⤂ā¤Ē⤰āĨā¤• ā¤¸ā¤žā¤§ā¤ž", + "reset_pin_code_success": "PIN ⤕āĨ‹ā¤Ą ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°āĨ€ā¤¤āĨā¤¯ā¤ž ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕āĨ‡ā¤˛ā¤ž", "reset_pin_code_with_password": "ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ąā¤¨āĨ‡ ⤤āĨā¤ŽāĨā¤šāĨ€ ⤍āĨ‡ā¤šā¤ŽāĨ€ PIN ⤕āĨ‹ā¤Ą ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕⤰āĨ‚ ā¤ļā¤•ā¤¤ā¤ž", "reset_sqlite": "SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", - "reset_sqlite_confirmation": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž? ā¤ĄāĨ‡ā¤Ÿā¤ž ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤ŋ⤤ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤞āĨ‰ā¤—ā¤†ā¤‰ā¤Ÿ ⤕⤰āĨ‚⤍ ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤞āĨ‰ā¤—⤇⤍ ā¤•ā¤°ā¤žā¤ĩāĨ‡ ā¤˛ā¤žā¤—āĨ‡ā¤˛.", + "reset_sqlite_confirmation": "⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤍⤕āĨā¤•āĨ€ SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤žā¤¯ā¤šā¤ž ā¤†ā¤šāĨ‡ ā¤•ā¤ž? ā¤ĄāĨ‡ā¤Ÿā¤ž ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤ŋ⤤ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤤āĨā¤ŽāĨā¤šā¤žā¤˛ā¤ž ⤞āĨ‰ā¤—ā¤†ā¤‰ā¤Ÿ ⤕⤰āĨ‚⤍ ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤞āĨ‰ā¤—⤇⤍ ā¤•ā¤°ā¤žā¤ĩāĨ‡ ā¤˛ā¤žā¤—āĨ‡ā¤˛", "reset_sqlite_success": "SQLite ā¤ĄāĨ‡ā¤Ÿā¤žā¤ŦāĨ‡ā¤¸ ⤝ā¤ļ⤏āĨā¤ĩāĨ€ā¤°āĨ€ā¤¤āĨā¤¯ā¤ž ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ⤕āĨ‡ā¤˛ā¤ž", "reset_to_default": "ā¤ĄāĨ€ā¤ĢāĨ‰ā¤˛āĨā¤Ÿā¤ĩ⤰ ⤰āĨ€ā¤¸āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "resolution": "⤰āĨ‡ā¤āĨ‹ā¤˛āĨā¤¯āĨ‚ā¤ļ⤍", @@ -1613,10 +1720,12 @@ "resolved_all_duplicates": "⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ ⤏āĨ‹ā¤Ąā¤ĩ⤞āĨ‡", "restore": "ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž", "restore_all": "⤏⤰āĨā¤ĩ ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "restore_trash_action_prompt": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤āĨ‚⤍ {count} ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ⤕āĨ‡ā¤˛āĨ‡", "restore_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤•ā¤°ā¤ž", "restored_asset": "ā¤ĒāĨā¤¨ā¤°āĨā¤¸ā¤‚ā¤šā¤¯ā¤ŋ⤤ ā¤†ā¤¯ā¤Ÿā¤Ž", "resume": "ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", "resume_paused_jobs": "{count, plural, one {# ā¤Ĩā¤žā¤‚ā¤Ŧā¤ĩ⤞āĨ‡ā¤˛āĨ‡ ā¤•ā¤žā¤Ž} other {# ā¤Ĩā¤žā¤‚ā¤Ŧā¤ĩ⤞āĨ‡ā¤˛āĨ€ ā¤•ā¤žā¤ŽāĨ‡}} ā¤ĒāĨā¤¨āĨā¤šā¤ž ⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", + "retry_upload": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤¨āĨā¤šā¤ž ā¤•ā¤°ā¤ž", "review_duplicates": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ā¤šāĨ‡ ā¤ĒāĨā¤¨ā¤°ā¤žā¤ĩ⤞āĨ‹ā¤•⤍ ā¤•ā¤°ā¤ž", "review_large_files": "ā¤ŽāĨ‹ā¤ āĨā¤¯ā¤ž ā¤Ģā¤žā¤‡ā¤˛āĨā¤¸ā¤šāĨ‡ ā¤ĒāĨā¤¨ā¤°ā¤žā¤ĩ⤞āĨ‹ā¤•⤍ ā¤•ā¤°ā¤ž", "role": "⤭āĨ‚ā¤Žā¤ŋā¤•ā¤ž", @@ -1624,6 +1733,7 @@ "role_viewer": "ā¤Ļ⤰āĨā¤ļ⤕", "running": "ā¤šā¤žā¤˛āĨ‚", "save": "⤜⤤⤍ ā¤•ā¤°ā¤ž", + "save_to_gallery": "⤗āĨ…⤞⤰āĨ€ā¤Žā¤§āĨā¤¯āĨ‡ ⤜⤤⤍ ā¤•ā¤°ā¤ž", "saved": "⤜⤤⤍ ⤕āĨ‡ā¤˛āĨ‡", "saved_api_key": "⤜⤤⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ API ⤕āĨ€", "saved_profile": "⤜⤤⤍ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ā¤ĒāĨā¤°āĨ‹ā¤Ģā¤žā¤‡ā¤˛", @@ -1632,6 +1742,7 @@ "scaffold_body_error_occurred": "⤤āĨā¤°āĨā¤ŸāĨ€ ⤆⤞āĨ€", "scan_all_libraries": "⤏⤰āĨā¤ĩ ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ⤏āĨā¤•āĨ…⤍ ā¤•ā¤°ā¤ž", "scan_library": "⤏āĨā¤•āĨ…⤍ ā¤•ā¤°ā¤ž", + "scan_settings": "⤏āĨā¤•āĨ…⤍ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "scanning_for_album": "⤅⤞āĨā¤Ŧā¤Žā¤¸ā¤žā¤ āĨ€ ⤏āĨā¤•āĨ…⤍ ⤕⤰⤤ ā¤†ā¤šāĨ‡...", "search": "ā¤ļāĨ‹ā¤§ā¤ž", "search_albums": "⤅⤞āĨā¤Ŧā¤Ž ā¤ļāĨ‹ā¤§ā¤ž", @@ -1659,6 +1770,7 @@ "search_filter_location_title": "⤏āĨā¤Ĩā¤žā¤¨ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "search_filter_media_type": "ā¤Žā¤žā¤§āĨā¤¯ā¤Ž ā¤ĒāĨā¤°ā¤•ā¤žā¤°", "search_filter_media_type_title": "ā¤Žā¤žā¤§āĨā¤¯ā¤Ž ā¤ĒāĨā¤°ā¤•ā¤žā¤° ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "search_filter_ocr": "OCR ā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤ļāĨ‹ā¤§ā¤ž", "search_filter_people_title": "⤞āĨ‹ā¤• ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "search_for": "ā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ļāĨ‹ā¤§ā¤ž", "search_for_existing_person": "ā¤ĩā¤ŋā¤ĻāĨā¤¯ā¤Žā¤žā¤¨ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤ļāĨ‹ā¤§ā¤ž", @@ -1667,13 +1779,25 @@ "search_no_people_named": "“{name}” ā¤¨ā¤žā¤ĩā¤žā¤šāĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨ€ ā¤¨ā¤žā¤šāĨ€", "search_no_result": "ā¤•ā¤žā¤šāĨ€ā¤šāĨ€ ā¤¸ā¤žā¤Ēā¤Ąā¤˛āĨ‡ ā¤¨ā¤žā¤šāĨ€; ā¤ĩāĨ‡ā¤—ā¤ŗā¤ž ā¤ļāĨ‹ā¤§ ā¤ļā¤ŦāĨā¤Ļ ⤕ā¤ŋ⤂ā¤ĩā¤ž ⤏⤂⤝āĨ‹ā¤œā¤¨ ā¤ĩā¤žā¤Ēā¤°ā¤ž", "search_options": "ā¤ļāĨ‹ā¤§ ā¤Ē⤰āĨā¤¯ā¤žā¤¯", + "search_page_categories": "ā¤ļāĨā¤°āĨ‡ā¤ŖāĨā¤¯ā¤ž", + "search_page_motion_photos": "ā¤ŽāĨ‹ā¤ļ⤍ ā¤ĢāĨ‹ā¤ŸāĨ‹", + "search_page_no_objects": "ā¤ĩ⤏āĨā¤¤āĨ‚ā¤‚ā¤šāĨ€ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€", + "search_page_no_places": "⤠ā¤ŋā¤•ā¤žā¤Ŗā¤žā¤‚ā¤šāĨ€ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€ ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤¨ā¤žā¤šāĨ€", + "search_page_screenshots": "⤏āĨā¤•āĨā¤°āĨ€ā¤¨ā¤ļāĨ‰ā¤ŸāĨā¤¸", "search_page_search_photos_videos": "⤤āĨā¤Žā¤šāĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ļāĨ‹ā¤§ā¤ž", + "search_page_selfies": "⤏āĨ‡ā¤˛āĨā¤ĢāĨ€ā¤œ", + "search_page_things": "ā¤ĩ⤏āĨā¤¤āĨ‚", "search_page_view_all_button": "⤏⤰āĨā¤ĩ ā¤Ēā¤šā¤ž", + "search_page_your_activity": "⤤āĨā¤Žā¤šāĨ‡ ⤕āĨā¤°ā¤ŋā¤¯ā¤žā¤•ā¤˛ā¤žā¤Ē", + "search_page_your_map": "⤤āĨā¤Žā¤šā¤ž ā¤¨ā¤•ā¤žā¤ļā¤ž", "search_people": "⤞āĨ‹ā¤• ā¤ļāĨ‹ā¤§ā¤ž", "search_places": "⤠ā¤ŋā¤•ā¤žā¤ŖāĨ‡ ā¤ļāĨ‹ā¤§ā¤ž", "search_rating": "⤰āĨ‡ā¤Ÿā¤ŋ⤂⤗⤍āĨā¤¸ā¤žā¤° ā¤ļāĨ‹ā¤§ā¤ž...", + "search_result_page_new_search_hint": "⤍ā¤ĩāĨ€ā¤¨ ā¤ļāĨ‹ā¤§", "search_settings": "ā¤ļāĨ‹ā¤§ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", "search_state": "ā¤°ā¤žā¤œāĨā¤¯/⤏āĨā¤ŸāĨ‡ā¤Ÿ ā¤ļāĨ‹ā¤§ā¤ž...", + "search_suggestion_list_smart_search_hint_1": "ā¤ĄāĨ€ā¤ĢāĨ‰ā¤˛āĨā¤Ÿā¤¨āĨ‡ ⤏āĨā¤Žā¤žā¤°āĨā¤Ÿ ⤏⤰āĨā¤š ⤏āĨā¤°āĨ‚ ā¤†ā¤šāĨ‡; ā¤ŽāĨ‡ā¤Ÿā¤žā¤ĄāĨ‡ā¤Ÿā¤ž ā¤ļāĨ‹ā¤§ā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤šāĨ€ ā¤°ā¤šā¤¨ā¤ž ā¤ĩā¤žā¤Ēā¤°ā¤ž. ", + "search_suggestion_list_smart_search_hint_2": "m:⤤āĨā¤Žā¤šā¤ž-ā¤ļāĨ‹ā¤§-ā¤ļā¤ŦāĨā¤Ļ", "search_tags": "⤟āĨ…⤗āĨā¤¸ ā¤ļāĨ‹ā¤§ā¤ž...", "search_timezone": "ā¤ĩāĨ‡ā¤ŗā¤•āĨā¤ˇāĨ‡ā¤¤āĨā¤° ā¤ļāĨ‹ā¤§ā¤ž...", "search_type": "ā¤ļāĨ‹ā¤§ ā¤ĒāĨā¤°ā¤•ā¤žā¤°", @@ -1685,15 +1809,18 @@ "select_album_cover": "⤅⤞āĨā¤Ŧā¤Ž ⤕ā¤ĩāĨā¤šā¤° ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_all": "⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_all_duplicates": "⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "select_all_in": "{group} ā¤Žā¤§āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_avatar_color": "⤅ā¤ĩā¤¤ā¤žā¤°ā¤šā¤ž ⤰⤂⤗ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_face": "⤚āĨ‡ā¤šā¤°ā¤ž ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_featured_photo": "ā¤Ģā¤ŋ⤚⤰āĨā¤Ą ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_from_computer": "⤕āĨ‰ā¤ŽāĨā¤ĒāĨā¤¯āĨā¤Ÿā¤°ā¤Žā¤§āĨ‚⤍ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_keep_all": "⤏⤰āĨā¤ĩ ⤠āĨ‡ā¤ĩ⤪āĨ‡ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "select_library_owner": "ā¤˛ā¤žā¤¯ā¤ŦāĨā¤°ā¤°āĨ€ ā¤Žā¤žā¤˛ā¤• ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_new_face": "⤍ā¤ĩā¤ž ⤚āĨ‡ā¤šā¤°ā¤ž ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_person_to_tag": "⤟āĨ…⤗ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_photos": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "select_trash_all": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "select_user_for_sharing_page_err_album": "⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤪āĨā¤¯ā¤žā¤¤ ⤅⤝ā¤ļ⤏āĨā¤ĩāĨ€", "selected": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨ‡", "selected_count": "{count, plural, other {# ⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡}}", "selected_gps_coordinates": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž GPS ⤏āĨā¤Ĩā¤žā¤¨ā¤¨ā¤ŋ⤰āĨā¤ĻāĨ‡ā¤ļā¤žā¤‚ā¤•", @@ -1723,20 +1850,33 @@ "setting_image_viewer_original_title": "ā¤ŽāĨ‚⤺ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", "setting_image_viewer_preview_subtitle": "ā¤Žā¤§āĨā¤¯ā¤Ž-⤰ā¤ŋā¤āĨ‹ā¤˛āĨā¤¯āĨ‚ā¤ļ⤍ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤏⤕āĨā¤ˇā¤Ž ā¤•ā¤°ā¤ž. ⤅⤕āĨā¤ˇā¤Ž ⤕āĨ‡ā¤˛āĨā¤¯ā¤žā¤¸ ā¤ĨāĨ‡ā¤Ÿ ā¤ŽāĨ‚⤺ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ā¤šāĨ‹ā¤ˆā¤˛ ⤕ā¤ŋ⤂ā¤ĩā¤ž ā¤Ģ⤕āĨā¤¤ ā¤Ĩ⤂ā¤Ŧ⤍āĨ‡ā¤˛ ā¤ĩā¤žā¤Ēā¤°ā¤˛ā¤ž ā¤œā¤žā¤ˆā¤˛.", "setting_image_viewer_preview_title": "ā¤ĒāĨā¤°ā¤ŋā¤ĩāĨā¤šāĨā¤¯āĨ‚ ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", + "setting_image_viewer_title": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž", "setting_languages_apply": "ā¤˛ā¤žā¤—āĨ‚ ā¤•ā¤°ā¤ž", "setting_languages_subtitle": "⤅āĨ…ā¤Ē⤚āĨ€ ā¤­ā¤žā¤ˇā¤ž ā¤Ŧā¤Ļā¤˛ā¤ž", + "setting_notifications_notify_failures_grace_period": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ŦāĨ…⤕⤅ā¤Ē ⤅ā¤Ē⤝ā¤ļā¤žā¤‚ā¤šāĨ€ ⤏āĨ‚ā¤šā¤¨ā¤ž: {duration}", + "setting_notifications_notify_hours": "{count} ā¤¤ā¤žā¤¸", + "setting_notifications_notify_immediately": "⤤⤤āĨā¤•ā¤žā¤ŗ", "setting_notifications_notify_minutes": "{count} ā¤Žā¤ŋ⤍ā¤ŋ⤟āĨ‡", + "setting_notifications_notify_never": "⤕⤧āĨ€ā¤š ā¤¨ā¤žā¤šāĨ€", "setting_notifications_notify_seconds": "{count} ⤏āĨ‡ā¤•⤂ā¤Ļ", + "setting_notifications_single_progress_subtitle": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤†ā¤¯ā¤Ÿā¤Žā¤¸ā¤žā¤ āĨ€ ⤤ā¤Ēā¤ļāĨ€ā¤˛ā¤ĩā¤žā¤° ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤°ā¤—⤤āĨ€ ā¤Žā¤žā¤šā¤ŋ⤤āĨ€", + "setting_notifications_single_progress_title": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ŦāĨ…⤕⤅ā¤Ē⤚āĨ€ ⤤ā¤Ēā¤ļāĨ€ā¤˛ā¤ĩā¤žā¤° ā¤ĒāĨā¤°ā¤—⤤āĨ€ ā¤Ļā¤žā¤–ā¤ĩā¤ž", "setting_notifications_subtitle": "⤤āĨā¤Žā¤šāĨ€ ⤏āĨ‚ā¤šā¤¨ā¤ž ā¤ĒāĨā¤°ā¤žā¤§ā¤žā¤¨āĨā¤¯āĨ‡ ā¤¸ā¤Žā¤žā¤¯āĨ‹ā¤œā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "setting_notifications_total_progress_subtitle": "ā¤ā¤•āĨ‚⤪ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨā¤°ā¤—⤤āĨ€ (ā¤ĒāĨ‚⤰āĨā¤Ŗ/ā¤ā¤•āĨ‚⤪ ā¤†ā¤¯ā¤Ÿā¤Ž)", + "setting_notifications_total_progress_title": "ā¤Ēā¤žā¤°āĨā¤ļāĨā¤ĩ⤭āĨ‚ā¤ŽāĨ€ ā¤ŦāĨ…⤕⤅ā¤Ē⤚āĨ€ ā¤ā¤•āĨ‚⤪ ā¤ĒāĨā¤°ā¤—⤤āĨ€ ā¤Ļā¤žā¤–ā¤ĩā¤ž", "setting_video_viewer_auto_play_subtitle": "ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤‰ā¤˜ā¤Ąā¤˛āĨā¤¯ā¤žā¤ĩ⤰ ⤆ā¤ĒāĨ‹ā¤†ā¤Ē ā¤ĒāĨā¤˛āĨ‡ ā¤•ā¤°ā¤ž", "setting_video_viewer_auto_play_title": "ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤‘ā¤ŸāĨ‹ā¤ĒāĨā¤˛āĨ‡", "setting_video_viewer_looping_title": "⤞āĨ‚ā¤Ēā¤ŋ⤂⤗", "setting_video_viewer_original_video_subtitle": "⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰āĨ‚⤍ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤏āĨā¤ŸāĨā¤°ā¤ŋā¤Ž ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤟āĨā¤°ā¤žā¤¨āĨā¤¸ā¤•āĨ‹ā¤Ą ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤…ā¤¸ā¤˛ā¤ž ⤤⤰āĨ€ ā¤ŽāĨ‚⤺ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ĒāĨā¤˛āĨ‡ ā¤•ā¤°ā¤ž. ā¤Ŧā¤Ģ⤰ā¤ŋ⤂⤗ ā¤šāĨ‹ā¤Š ā¤ļ⤕⤤āĨ‡. ⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕⤰āĨ€ā¤¤āĨā¤¯ā¤ž ⤉ā¤Ē⤞ā¤ŦāĨā¤§ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤¯ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ā¤Ēā¤žā¤¸āĨ‚⤍ ⤏āĨā¤ĩ⤤⤂⤤āĨā¤°ā¤Ē⤪āĨ‡ ā¤ŽāĨ‚⤺ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤āĨ‡ā¤¤ ā¤ĒāĨā¤˛āĨ‡ ā¤šāĨ‹ā¤¤ā¤žā¤¤.", "setting_video_viewer_original_video_title": "ā¤ŽāĨ‚⤺ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤏⤕āĨā¤¤āĨ€ā¤¨āĨ‡ ā¤ĒāĨā¤˛āĨ‡ ā¤•ā¤°ā¤ž", "settings": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ", + "settings_require_restart": "ā¤šāĨ€ ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ā¤˛ā¤žā¤—āĨ‚ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤕āĨƒā¤Ēā¤¯ā¤ž Immich ⤰āĨ€ā¤¸āĨā¤Ÿā¤žā¤°āĨā¤Ÿ ā¤•ā¤°ā¤ž", "settings_saved": "⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œ ⤜⤤⤍ ⤕āĨ‡ā¤˛āĨā¤¯ā¤ž", "setup_pin_code": "PIN ⤕āĨ‹ā¤Ą ⤏āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "share": "ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤ž", + "share_action_prompt": "{count} ā¤†ā¤¯ā¤Ÿā¤Ž ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", + "share_add_photos": "ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤜āĨ‹ā¤Ąā¤ž", + "share_assets_selected": "{count} ⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡", "share_dialog_preparing": "ā¤¤ā¤¯ā¤žā¤° ⤕⤰⤤ ā¤†ā¤šāĨ‡...", "share_link": "ā¤ļāĨ‡ā¤…⤰ ā¤ĻāĨā¤ĩā¤ž", "shared": "ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", @@ -1744,6 +1884,8 @@ "shared_album_activity_remove_content": "ā¤šāĨ€ ⤕āĨƒā¤¤āĨ€ ā¤šā¤Ÿā¤ĩā¤žā¤¯ā¤šāĨ€ ā¤†ā¤šāĨ‡ ā¤•ā¤ž?", "shared_album_activity_remove_title": "⤕āĨƒā¤¤āĨ€ ā¤šā¤Ÿā¤ĩā¤ž", "shared_album_section_people_action_error": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤Ŧā¤žā¤šāĨ‡ā¤° ā¤Ēā¤Ąā¤¤ā¤žā¤¨ā¤ž/ā¤•ā¤žā¤ĸā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", + "shared_album_section_people_action_leave": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤•ā¤žā¤ĸā¤ž", + "shared_album_section_people_action_remove_user": "⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨ‚⤍ ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤•ā¤žā¤ĸā¤ž", "shared_album_section_people_title": "⤞āĨ‹ā¤•", "shared_by": "ā¤¯ā¤žā¤‚ā¤¨āĨ€ ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", "shared_by_user": "{user} ā¤¯ā¤žā¤‚ā¤¨āĨ€ ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", @@ -1752,6 +1894,7 @@ "shared_intent_upload_button_progress_text": "{current}/{total} ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ā¤žā¤˛āĨ‡", "shared_link_app_bar_title": "ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ā¤ĻāĨā¤ĩāĨ‡", "shared_link_clipboard_copied_massage": "⤕āĨā¤˛ā¤ŋā¤Ēā¤ŦāĨ‹ā¤°āĨā¤Ąā¤ĩ⤰ ⤕āĨ‰ā¤ĒāĨ€ ⤕āĨ‡ā¤˛āĨ‡", + "shared_link_clipboard_text": "ā¤ĻāĨā¤ĩā¤ž: {link}\nā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą: {password}", "shared_link_create_error": "ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ā¤ĻāĨā¤ĩā¤ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤¤ā¤žā¤¨ā¤ž ⤤āĨā¤°āĨā¤ŸāĨ€", "shared_link_custom_url_description": "ā¤¸ā¤žā¤¨āĨā¤•āĨ‚⤞ URL ā¤ĻāĨā¤ĩā¤žā¤°āĨ‡ ā¤šā¤ž ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ā¤ĻāĨā¤ĩā¤ž ā¤‰ā¤˜ā¤Ąā¤ž", "shared_link_edit_description_hint": "ā¤ļāĨ‡ā¤…ā¤°ā¤šāĨ‡ ā¤ĩ⤰āĨā¤Ŗā¤¨ ā¤ĒāĨā¤°ā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž", @@ -1787,6 +1930,12 @@ "shared_with_partner": "{partner} ⤏āĨ‹ā¤Ŧ⤤ ā¤ļāĨ‡ā¤…⤰ ⤕āĨ‡ā¤˛āĨ‡", "sharing": "ā¤ļāĨ‡ā¤…⤰ā¤ŋ⤂⤗", "sharing_enter_password": "ā¤šāĨ‡ ā¤ĒāĨƒā¤ˇāĨā¤  ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⤕āĨƒā¤Ēā¤¯ā¤ž ā¤Ēā¤žā¤¸ā¤ĩ⤰āĨā¤Ą ā¤ĒāĨā¤°ā¤ĩā¤ŋ⤎āĨā¤Ÿ ā¤•ā¤°ā¤ž.", + "sharing_page_album": "ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ⤅⤞āĨā¤Ŧā¤Ž", + "sharing_page_description": "⤤āĨā¤Žā¤šāĨā¤¯ā¤ž ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤•ā¤Žā¤§āĨ€ā¤˛ ⤞āĨ‹ā¤•ā¤žā¤‚ā¤¸āĨ‹ā¤Ŧ⤤ ā¤ĢāĨ‹ā¤ŸāĨ‹-ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ļāĨ‡ā¤…⤰ ⤕⤰⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ⤅⤞āĨā¤Ŧā¤Ž ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž.", + "sharing_page_empty_list": "⤰ā¤ŋā¤•ā¤žā¤ŽāĨ€ ā¤¯ā¤žā¤ĻāĨ€", + "sharing_sidebar_description": "ā¤¸ā¤žā¤‡ā¤Ąā¤Ŧā¤žā¤°ā¤Žā¤§āĨā¤¯āĨ‡ ā¤ļāĨ‡ā¤…⤰ā¤ŋā¤‚ā¤—ā¤šā¤ž ā¤ĻāĨā¤ĩā¤ž ā¤Ļā¤žā¤–ā¤ĩā¤ž", + "sharing_silver_appbar_create_shared_album": "⤍ā¤ĩāĨ€ā¤¨ ā¤ļāĨ‡ā¤…⤰āĨā¤Ą ⤅⤞āĨā¤Ŧā¤Ž", + "sharing_silver_appbar_share_partner": "ā¤­ā¤žā¤—āĨ€ā¤Ļā¤žā¤°ā¤žā¤¸āĨ‹ā¤Ŧ⤤ ā¤ļāĨ‡ā¤…⤰ ā¤•ā¤°ā¤ž", "shift_to_permanent_delete": "⤅āĨ…⤏āĨ‡ā¤Ÿ ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩ⤪āĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ⇧ ā¤Ļā¤žā¤Ŧā¤ž", "show_album_options": "⤅⤞āĨā¤Ŧā¤Ž ā¤Ē⤰āĨā¤¯ā¤žā¤¯ ā¤Ļā¤žā¤–ā¤ĩā¤ž", "show_albums": "⤅⤞āĨā¤Ŧā¤Ž ā¤Ļā¤žā¤–ā¤ĩā¤ž", @@ -1825,6 +1974,7 @@ "sort_created": "ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", "sort_items": "ā¤†ā¤¯ā¤Ÿā¤Žā¤žā¤‚ā¤šāĨ€ ⤏⤂⤖āĨā¤¯ā¤ž", "sort_modified": "ā¤Ŧā¤Ļ⤞ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ€ ā¤¤ā¤žā¤°āĨ€ā¤–", + "sort_newest": "⤅⤞āĨ€ā¤•ā¤Ąā¤šā¤ž ā¤ĢāĨ‹ā¤ŸāĨ‹", "sort_oldest": "⤏⤰āĨā¤ĩā¤žā¤¤ ⤜āĨā¤¨ā¤ž ā¤ĢāĨ‹ā¤ŸāĨ‹", "sort_people_by_similarity": "ā¤¸ā¤žā¤ŽāĨā¤¯ā¤¤āĨ‡ā¤¨āĨā¤¸ā¤žā¤° ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤‚ā¤šā¤ž ⤕āĨā¤°ā¤Ž ā¤˛ā¤žā¤ĩā¤ž", "sort_recent": "⤍āĨā¤•ā¤¤ā¤žā¤š ⤘āĨ‡ā¤¤ā¤˛āĨ‡ā¤˛ā¤ž ā¤ĢāĨ‹ā¤ŸāĨ‹", @@ -1833,6 +1983,7 @@ "stack": "⤏āĨā¤ŸāĨ…⤕", "stack_action_prompt": "{count} ⤏āĨā¤ŸāĨ…⤕ ⤕āĨ‡ā¤˛āĨ‡", "stack_duplicates": "ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ ⤏āĨā¤ŸāĨ…⤕ ā¤•ā¤°ā¤ž", + "stack_select_one_photo": "⤏āĨā¤ŸāĨ…ā¤•ā¤¸ā¤žā¤ āĨ€ ā¤ā¤• ā¤ŽāĨā¤–āĨā¤¯ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", "stack_selected_photos": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ⤏āĨā¤ŸāĨ…⤕ ā¤•ā¤°ā¤ž", "stacked_assets_count": "⤏āĨā¤ŸāĨ…⤕ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ {count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž}}", "stacktrace": "⤏āĨā¤ŸāĨ…ā¤•ā¤ŸāĨā¤°āĨ‡ā¤¸", @@ -1860,6 +2011,7 @@ "swap_merge_direction": "ā¤Žā¤°āĨā¤œ ā¤Ļā¤ŋā¤ļāĨ‡ā¤šāĨ€ ⤅ā¤Ļā¤˛ā¤žā¤Ŧā¤Ļ⤞ ā¤•ā¤°ā¤ž", "sync": "ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ", "sync_albums": "⤅⤞āĨā¤Ŧā¤Ž ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "sync_albums_manual_subtitle": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ ⤏⤰āĨā¤ĩ ā¤ĢāĨ‹ā¤ŸāĨ‹-ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨā¤¯ā¤ž ā¤ŦāĨ…⤕⤅ā¤Ē ⤅⤞āĨā¤Ŧā¤Žā¤Žā¤§āĨā¤¯āĨ‡ ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤ŋ⤤ ā¤•ā¤°ā¤ž", "sync_local": "⤏āĨā¤Ĩā¤žā¤¨ā¤ŋ⤕ ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ", "sync_remote": "ā¤ĻāĨ‚⤰⤏āĨā¤Ĩ ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ", "sync_status": "ā¤¸ā¤Žā¤•āĨā¤°ā¤Žā¤Ŗ ⤏āĨā¤Ĩā¤ŋ⤤āĨ€", @@ -1869,6 +2021,7 @@ "tag_assets": "ā¤†ā¤¯ā¤Ÿā¤Žā¤¨ā¤ž ⤟āĨ…⤗ ā¤˛ā¤žā¤ĩā¤ž", "tag_created": "ā¤¤ā¤¯ā¤žā¤° ⤕āĨ‡ā¤˛āĨ‡ā¤˛ā¤ž ⤟āĨ…⤗: {tag}", "tag_feature_description": "ā¤¤ā¤žā¤°āĨā¤•ā¤ŋ⤕ ⤟āĨ…⤗ ā¤ĩā¤ŋā¤ˇā¤¯ā¤žā¤‚ā¤¨āĨā¤¸ā¤žā¤° ā¤—ā¤Ÿā¤Ŧā¤ĻāĨā¤§ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ā¤ŦāĨā¤°ā¤žā¤‰ā¤ ā¤•ā¤°ā¤ž", + "tag_not_found_question": "⤟āĨ…⤗ ā¤¸ā¤žā¤Ēā¤Ąā¤¤ ā¤¨ā¤žā¤šāĨ€? ⤍ā¤ĩā¤ž ⤟āĨ…⤗ ā¤¤ā¤¯ā¤žā¤° ā¤•ā¤°ā¤ž", "tag_people": "ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤‚ā¤¨ā¤ž ⤟āĨ…⤗ ā¤•ā¤°ā¤ž", "tag_updated": "⤅ā¤ĻāĨā¤¯ā¤¯ā¤žā¤ĩ⤤ ⤟āĨ…⤗: {tag}", "tagged_assets": "⤟āĨ…⤗ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ {count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž}}", @@ -1883,10 +2036,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ā¤ĒāĨā¤°ā¤¤āĨā¤¯āĨ‡ā¤• ā¤°ā¤žā¤‚ā¤—āĨ‡ā¤¤āĨ€ā¤˛ ⤅āĨ…⤏āĨ‡ā¤ŸāĨā¤¸ā¤šāĨ€ ⤏⤂⤖āĨā¤¯ā¤ž ({count})", "theme_setting_colorful_interface_subtitle": "ā¤ŦāĨ…⤕⤗āĨā¤°ā¤žā¤Šā¤‚ā¤Ą ā¤ĒāĨƒā¤ˇāĨā¤ ā¤­ā¤žā¤—ā¤žā¤‚ā¤ĩ⤰ ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤰⤂⤗ ā¤˛ā¤žā¤—āĨ‚ ā¤•ā¤°ā¤ž.", "theme_setting_colorful_interface_title": "⤰⤂⤗āĨ€ā¤ŦāĨ‡ā¤°ā¤‚⤗āĨ€ ā¤‡ā¤‚ā¤Ÿā¤°ā¤ĢāĨ‡ā¤¸", + "theme_setting_image_viewer_quality_subtitle": "ā¤ĄāĨ€ā¤ŸāĨ‡ā¤˛ ā¤‡ā¤ŽāĨ‡ā¤œ ā¤ĩāĨā¤šāĨā¤¯āĨ‚ā¤…ā¤°ā¤šāĨ€ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž ā¤¸ā¤Žā¤žā¤¯āĨ‹ā¤œā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "theme_setting_image_viewer_quality_title": "ā¤‡ā¤ŽāĨ‡ā¤œ ā¤ĩāĨā¤šāĨā¤¯āĨ‚⤅⤰ ⤗āĨā¤Ŗā¤ĩ⤤āĨā¤¤ā¤ž", "theme_setting_primary_color_subtitle": "ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤕āĨƒā¤¤āĨ€ ā¤ĩ ⤅āĨ…⤕āĨā¤¸āĨ‡ā¤‚ā¤Ÿā¤¸ā¤žā¤ āĨ€ ⤰⤂⤗ ⤍ā¤ŋā¤ĩā¤Ąā¤ž.", "theme_setting_primary_color_title": "ā¤ĒāĨā¤°ā¤žā¤Ĩā¤Žā¤ŋ⤕ ⤰⤂⤗", "theme_setting_system_primary_color_title": "⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤰⤂⤗ ā¤ĩā¤žā¤Ēā¤°ā¤ž", "theme_setting_system_theme_switch": "⤏āĨā¤ĩā¤¯ā¤‚ā¤šā¤˛ā¤ŋ⤤ (⤏ā¤ŋ⤏āĨā¤Ÿā¤Ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗āĨā¤œā¤¨āĨā¤¸ā¤žā¤°)", + "theme_setting_theme_subtitle": "⤅āĨ…ā¤Ē⤚āĨ€ ā¤ĨāĨ€ā¤Ž ⤏āĨ‡ā¤Ÿā¤ŋ⤂⤗ ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "theme_setting_three_stage_loading_subtitle": "ā¤ĨāĨā¤°āĨ€-⤏āĨā¤ŸāĨ‡ā¤œ ⤞āĨ‹ā¤Ąā¤ŋā¤‚ā¤—ā¤ŽāĨā¤ŗāĨ‡ ⤗⤤āĨ€ ā¤ĩā¤žā¤ĸāĨ‚ ā¤ļ⤕⤤āĨ‡; ā¤Ē⤰⤂⤤āĨ ⤍āĨ‡ā¤Ÿā¤ĩ⤰āĨā¤• ⤞āĨ‹ā¤Ą ⤞⤕āĨā¤ˇā¤ŖāĨ€ā¤¯ ā¤ĩā¤žā¤ĸ⤤āĨ‹", + "theme_setting_three_stage_loading_title": "ā¤ĨāĨā¤°āĨ€-⤏āĨā¤ŸāĨ‡ā¤œ ⤞āĨ‹ā¤Ąā¤ŋ⤂⤗ ⤏āĨā¤°āĨ‚ ā¤•ā¤°ā¤ž", "they_will_be_merged_together": "⤤āĨ‡ ā¤ā¤•ā¤¤āĨā¤° ā¤ĩā¤ŋ⤞āĨ€ā¤¨ ⤕āĨ‡ā¤˛āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛", "third_party_resources": "⤤āĨƒā¤¤āĨ€ā¤¯-ā¤Ē⤕āĨā¤ˇ ā¤¸ā¤‚ā¤¸ā¤žā¤§ā¤¨āĨ‡", "time": "ā¤ĩāĨ‡ā¤ŗ", @@ -1910,9 +2068,15 @@ "trash_all": "⤏⤰āĨā¤ĩ ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤Ÿā¤žā¤•ā¤ž", "trash_count": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ {count, number}", "trash_delete_asset": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤Ÿā¤žā¤•ā¤ž/⤅āĨ…⤏āĨ‡ā¤Ÿ ā¤šā¤Ÿā¤ĩā¤ž", + "trash_emptied": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ ⤰ā¤ŋā¤•ā¤žā¤ŽāĨ€ ⤕āĨ‡ā¤˛āĨ€", "trash_no_results_message": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ā¤Ÿā¤žā¤•ā¤˛āĨ‡ā¤˛āĨ‡ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤ĩ ā¤ĩāĨā¤šā¤ŋā¤Ąā¤ŋ⤓ ⤝āĨ‡ā¤ĨāĨ‡ ā¤Ļā¤ŋ⤏⤤āĨ€ā¤˛.", "trash_page_delete_all": "⤏⤰āĨā¤ĩ ā¤šā¤Ÿā¤ĩā¤ž", + "trash_page_empty_trash_dialog_content": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ ⤰ā¤ŋā¤•ā¤žā¤ŽāĨ€ ā¤•ā¤°ā¤žā¤¯ā¤šāĨ€ ā¤•ā¤ž? ā¤šāĨ‡ ā¤†ā¤¯ā¤Ÿā¤Ž Immich ā¤Žā¤§āĨ‚⤍ ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛", "trash_page_info": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤āĨ€ā¤˛ ā¤†ā¤¯ā¤Ÿā¤Ž {days} ā¤Ļā¤ŋā¤ĩā¤¸ā¤žā¤‚ā¤¨ā¤‚ā¤¤ā¤° ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛", + "trash_page_no_assets": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤ ⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤¨ā¤žā¤šāĨ€ā¤¤", + "trash_page_restore_all": "⤏⤰āĨā¤ĩ ā¤Ē⤰⤤ ā¤†ā¤Ŗā¤ž", + "trash_page_select_assets_btn": "ā¤†ā¤¯ā¤Ÿā¤Ž ⤍ā¤ŋā¤ĩā¤Ąā¤ž", + "trash_page_title": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ ({count})", "trashed_items_will_be_permanently_deleted_after": "ā¤•ā¤šā¤°ā¤žā¤ĒāĨ‡ā¤ŸāĨ€ā¤¤āĨ€ā¤˛ ā¤†ā¤¯ā¤Ÿā¤Ž {days, plural, one {# ā¤Ļā¤ŋā¤ĩā¤¸ā¤žā¤‚ā¤¨ā¤‚ā¤¤ā¤°} other {# ā¤Ļā¤ŋā¤ĩā¤¸ā¤žā¤‚ā¤¨ā¤‚ā¤¤ā¤°}} ā¤•ā¤žā¤¯ā¤Žā¤šāĨ‡ ā¤šā¤Ÿā¤ĩ⤞āĨ‡ ā¤œā¤žā¤¤āĨ€ā¤˛.", "troubleshoot": "ā¤¸ā¤Žā¤¸āĨā¤¯ā¤ž ⤍ā¤ŋā¤ĩā¤žā¤°ā¤Ŗ", "type": "ā¤ĒāĨā¤°ā¤•ā¤žā¤°", @@ -1940,6 +2104,7 @@ "unsaved_change": "⤍ ā¤¸ā¤žā¤ ā¤ĩ⤞āĨ‡ā¤˛ā¤ž ā¤Ŧā¤Ļ⤞", "unselect_all": "⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤ĄāĨ€ ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", "unselect_all_duplicates": "⤏⤰āĨā¤ĩ ā¤ĄāĨā¤ĒāĨā¤˛ā¤ŋ⤕āĨ‡ā¤ŸāĨā¤¸ā¤šāĨ€ ⤍ā¤ŋā¤ĩā¤Ą ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", + "unselect_all_in": "{group} ā¤Žā¤§āĨ€ā¤˛ ⤏⤰āĨā¤ĩ ⤍ā¤ŋā¤ĩā¤ĄāĨ€ ⤰ā¤ĻāĨā¤Ļ ā¤•ā¤°ā¤ž", "unstack": "⤏āĨā¤ŸāĨ…⤕ ā¤ĩāĨ‡ā¤—ā¤ŗā¤ž ā¤•ā¤°ā¤ž", "unstack_action_prompt": "{count} ⤅⤍⤏āĨā¤ŸāĨ…⤕ ⤕āĨ‡ā¤˛āĨ‡", "unstacked_assets_count": "⤅⤍⤏āĨā¤ŸāĨ…⤕ ⤕āĨ‡ā¤˛āĨ‡ā¤˛āĨ‡ {count, plural, one {# ā¤†ā¤¯ā¤Ÿā¤Ž} other {# ā¤†ā¤¯ā¤Ÿā¤Ž}}", @@ -1951,6 +2116,8 @@ "upload": "⤅ā¤Ē⤞āĨ‹ā¤Ą", "upload_concurrency": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤¸ā¤Žā¤žā¤‚ā¤¤ā¤°ā¤¤ā¤ž", "upload_details": "⤅ā¤Ē⤞āĨ‹ā¤Ą ⤤ā¤Ēā¤ļāĨ€ā¤˛", + "upload_dialog_info": "⤍ā¤ŋā¤ĩā¤Ąā¤˛āĨ‡ā¤˛āĨ‡ ā¤†ā¤¯ā¤Ÿā¤Ž ⤏⤰āĨā¤ĩāĨā¤šā¤°ā¤ĩ⤰ ā¤ŦāĨ…⤕⤅ā¤Ē ā¤•ā¤°ā¤žā¤¯ā¤šāĨ‡ ā¤•ā¤ž?", + "upload_dialog_title": "⤅āĨ…⤏āĨ‡ā¤Ÿ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤•ā¤°ā¤ž", "upload_errors": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨ‚⤰āĨā¤Ŗ ā¤ā¤žā¤˛āĨ‡; {count, plural, one {# ⤤āĨā¤°āĨā¤ŸāĨ€} other {# ⤤āĨā¤°āĨā¤ŸāĨ€}} ⤆ā¤ĸ⤺⤞āĨā¤¯ā¤ž. ⤍ā¤ĩāĨ€ā¤¨ ⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤†ā¤¯ā¤Ÿā¤Ž ā¤Ēā¤žā¤šā¤ŖāĨā¤¯ā¤žā¤¸ā¤žā¤ āĨ€ ā¤ĒāĨƒā¤ˇāĨā¤  ⤰āĨ€ā¤ĢāĨā¤°āĨ‡ā¤ļ ā¤•ā¤°ā¤ž.", "upload_finished": "⤅ā¤Ē⤞āĨ‹ā¤Ą ā¤ĒāĨ‚⤰āĨā¤Ŗ", "upload_progress": "⤉⤰āĨā¤ĩ⤰ā¤ŋ⤤ {remaining, number} — ā¤ĒāĨā¤°ā¤•āĨā¤°ā¤ŋā¤¯ā¤ž ā¤ā¤žā¤˛āĨ‡ā¤˛āĨ‡ {processed, number}/{total, number}", @@ -1976,6 +2143,7 @@ "user_privacy": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ⤗āĨ‹ā¤Ē⤍āĨ€ā¤¯ā¤¤ā¤ž", "user_purchase_settings": "⤖⤰āĨ‡ā¤ĻāĨ€", "user_purchase_settings_description": "⤤āĨā¤Žā¤šāĨ€ ⤖⤰āĨ‡ā¤ĻāĨ€ ā¤ĩāĨā¤¯ā¤ĩ⤏āĨā¤Ĩā¤žā¤Ēā¤ŋ⤤ ā¤•ā¤°ā¤ž", + "user_role_set": "{user} ā¤¯ā¤žā¤‚ā¤¨ā¤ž {role} ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ⤏āĨ‡ā¤Ÿ ā¤•ā¤°ā¤ž", "user_usage_detail": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ā¤ĩā¤žā¤Ēā¤°ā¤žā¤šā¤ž ⤤ā¤Ēā¤ļāĨ€ā¤˛", "user_usage_stats": "ā¤–ā¤žā¤¤āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ā¤ĩā¤žā¤Ēā¤°ā¤žā¤šāĨā¤¯ā¤ž ā¤¸ā¤žā¤‚ā¤–āĨā¤¯ā¤ŋ⤕āĨ€", "user_usage_stats_description": "ā¤–ā¤žā¤¤āĨā¤¯ā¤žā¤šāĨā¤¯ā¤ž ā¤ĩā¤žā¤Ēā¤°ā¤žā¤šāĨā¤¯ā¤ž ā¤¸ā¤žā¤‚ā¤–āĨā¤¯ā¤ŋ⤕āĨ€ ā¤Ēā¤šā¤ž", @@ -2003,13 +2171,17 @@ "view_details": "⤤ā¤Ēā¤ļāĨ€ā¤˛ ā¤Ēā¤šā¤ž", "view_in_timeline": "ā¤Ÿā¤žā¤‡ā¤Žā¤˛ā¤žā¤‡ā¤¨ā¤Žā¤§āĨā¤¯āĨ‡ ā¤Ēā¤šā¤ž", "view_link": "ā¤ĻāĨā¤ĩā¤ž ā¤Ēā¤šā¤ž", + "view_links": "ā¤ĻāĨā¤ĩāĨ‡ ā¤Ēā¤šā¤ž", "view_name": "ā¤Ēā¤šā¤ž", "view_next_asset": "ā¤ĒāĨā¤ĸāĨ€ā¤˛ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤Ēā¤šā¤ž", "view_previous_asset": "ā¤Žā¤žā¤—āĨ€ā¤˛ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤Ēā¤šā¤ž", "view_qr_code": "QR ⤕āĨ‹ā¤Ą ā¤Ēā¤šā¤ž", "view_similar_photos": "ā¤¸ā¤Žā¤žā¤¨ ā¤ĢāĨ‹ā¤ŸāĨ‹ ā¤Ēā¤šā¤ž", "view_stack": "⤏āĨā¤ŸāĨ…⤕ ā¤Ēā¤šā¤ž", + "view_user": "ā¤ĩā¤žā¤Ē⤰⤕⤰āĨā¤¤ā¤ž ā¤Ēā¤šā¤ž", "viewer_remove_from_stack": "⤏āĨā¤ŸāĨ…ā¤•ā¤Žā¤§āĨ‚⤍ ā¤•ā¤žā¤ĸā¤ž", + "viewer_stack_use_as_main_asset": "ā¤ŽāĨā¤–āĨā¤¯ ā¤†ā¤¯ā¤Ÿā¤Ž ā¤ŽāĨā¤šā¤ŖāĨ‚⤍ ā¤ĩā¤žā¤Ēā¤°ā¤ž", + "viewer_unstack": "⤏āĨā¤ŸāĨ…⤕ ā¤ĩāĨ‡ā¤—ā¤ŗā¤ž ā¤•ā¤°ā¤ž", "visibility_changed": "ā¤ĻāĨƒā¤ļāĨā¤¯ā¤¤ā¤ž {count, plural, one {# ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤¸ā¤žā¤ āĨ€ ā¤Ŧā¤Ļ⤞⤞āĨ€} other {# ā¤ĩāĨā¤¯ā¤•āĨā¤¤āĨ€ā¤‚ā¤¸ā¤žā¤ āĨ€ ā¤Ŧā¤Ļ⤞⤞āĨ€}}", "waiting": "ā¤ĒāĨā¤°ā¤¤āĨ€ā¤•āĨā¤ˇāĨ‡ā¤¤", "warning": "⤚āĨ‡ā¤¤ā¤žā¤ĩ⤪āĨ€", @@ -2023,5 +2195,6 @@ "yes": "ā¤šāĨ‹", "you_dont_have_any_shared_links": "⤆ā¤Ē⤞āĨā¤¯ā¤žā¤•ā¤ĄāĨ‡ ⤕āĨ‹ā¤Ŗā¤¤āĨ‡ā¤šāĨ€ ā¤¸ā¤žā¤Žā¤žā¤¯ā¤ŋ⤕ ā¤ĻāĨā¤ĩāĨ‡ ā¤¨ā¤žā¤šāĨ€ā¤¤", "your_wifi_name": "⤤āĨā¤Žā¤šāĨā¤¯ā¤ž Wi-Fi ⤚āĨ‡ ā¤¨ā¤žā¤ĩ", - "zoom_image": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ā¤āĨ‚ā¤Ž ā¤•ā¤°ā¤ž" + "zoom_image": "ā¤ĒāĨā¤°ā¤¤ā¤ŋā¤Žā¤ž ā¤āĨ‚ā¤Ž ā¤•ā¤°ā¤ž", + "zoom_to_bounds": "⤏āĨ€ā¤ŽāĨ‡ā¤Ē⤰āĨā¤¯ā¤‚⤤ ā¤āĨ‚ā¤Ž ā¤•ā¤°ā¤ž" } diff --git a/i18n/ms.json b/i18n/ms.json index 2190b115f1..5459b78450 100644 --- a/i18n/ms.json +++ b/i18n/ms.json @@ -10,6 +10,7 @@ "active": "Aktif", "active_count": "Aktif: {count}", "activity": "Aktiviti", + "activity_changed": "Aktiviti {enabled, select, true {enabled} other {disabled}}", "add": "Tambah", "add_a_description": "Tambah penerangan", "add_a_location": "Tambah lokasi", @@ -21,7 +22,9 @@ "add_endpoint": "Tambah titik akhir", "add_exclusion_pattern": "Tambahkan corak pengecualian", "add_location": "Tambah lokasi", + "add_more_users": "Tambah user lagi", "add_partner": "Tambah rakan", + "add_path": "Tambah laluan", "add_photos": "Tambah gambar", "add_tag": "Tambah tag", "add_to": "Tambah keâ€Ļ", @@ -29,9 +32,11 @@ "add_to_album_bottom_sheet_added": "Dimasukkan ke {album}", "add_to_album_bottom_sheet_already_exists": "Sudah ada di {album}", "add_to_album_bottom_sheet_some_local_assets": "Sesetengah aset lokal tidak ditambahkan pada album", + "add_to_album_toggle": "Togol pilihan untuk {album}", "add_to_albums": "Tambah pada album", "add_to_albums_count": "Tambah pada album ({count})", "add_to_bottom_bar": "Tambah ke", + "add_to_shared_album": "Tambah ke album yang dikongsi", "add_upload_to_stack": "Tambah muat naik ke timbunan", "add_url": "Tambah URL", "added_to_archive": "Tambah ke arkib", @@ -72,7 +77,6 @@ "cron_expression_description": "Tetapkan selang imbasan menggunakan format cron. Untuk maklumat lanjut, sila rujuk ke sebagai contoh Crontab Guru", "cron_expression_presets": "Pratetap-pratetap ungkapan Cron", "disable_login": "Lumpuhkan fungsi log masuk", - "download_csv": "Muat turun CSV", "duplicate_detection_job_description": "Jalankan pembelajaran mesin pada aset untuk mengesan imej yang serupa. Bergantung pada Carian Pintar", "exclusion_pattern_description": "Corak pengecualian membolehkan anda mengabaikan fail dan folder semasa mengimbas pustaka anda. Ini berguna jika anda mempunyai folder yang mengandungi fail yang anda tidak mahu import, seperti fail RAW.", "export_config_as_json_description": "Muat turun konfigurasi sistem semasa sebagai fail JSON", @@ -105,9 +109,6 @@ "image_thumbnail_quality_description": "Kualiti lakaran kenit daripada 1-100. Lebih tinggi adalah lebih baik, tetapi menghasilkan fail yang lebih besar dan boleh mengurangkan responsif apl.", "image_thumbnail_title": "Tetapan Lakaran Kenit", "import_config_from_json_description": "Import konfigurasi sistem melalui muat naik fail JSON", - "integrity_checks_checksum_files_time_limit": "Had masa", - "integrity_checks_missing_files": "Fail hilang", - "integrity_checks_missing_files_description": "Tetapkan pengaktifan dan kekerapan penyemak fail hilang", "job_concurrency": "Konkurensi {job}", "job_created": "Tugas yang dicipta", "job_not_concurrency_safe": "Konkurensi tugas ini tidak selamat.", @@ -135,7 +136,6 @@ "logging_enable_description": "Dayakan pengelogan", "logging_level_description": "Apabila didayakan, tahap log yang hendak digunakan.", "logging_settings": "Log", - "machine_learning_availability_checks_description": "Kesan dan pilih pelayan kecerdasan buatan secara automatik", "machine_learning_clip_model": "Model CLIP", "machine_learning_clip_model_description": "Nama model CLIP disenaraikan di sini. Ambil perhatian bahawa anda mesti menjalankan semula tugas 'Carian Pintar' untuk semua imej selepas menukar model.", "machine_learning_duplicate_detection": "Pengesanan Pendua", @@ -158,14 +158,11 @@ "machine_learning_min_detection_score_description": "Skor keyakinan minimum untuk wajah dikesan dari 0-1. Nilai yang lebih rendah akan mengesan lebih banyak muka tetapi mungkin menghasilkan positif palsu.", "machine_learning_min_recognized_faces": "Minimum mengenali wajah", "machine_learning_min_recognized_faces_description": "Bilangan minima wajah yang dikenali untuk seseorang dicipta. Peningkatan ini menjadikan Pengecaman Wajah lebih tepat atas kos meningkatkan peluang wajah tidak diberikan kepada seseorang.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "Gunakan kecerdasan buatan untuk mengesan tulisan dalam gambar", "machine_learning_ocr_enabled": "Dayakan OCR", "machine_learning_ocr_enabled_description": "Jika dinyahdayakan, imej tidak akan melalui pengecaman teks.", - "machine_learning_ocr_max_resolution": "Resolusi maksimum", + "machine_learning_ocr_max_resolution": "Resolusi Maksimum", "machine_learning_ocr_max_resolution_description": "Pratonton yang melebihi resolusi ini akan diubah saiz sambil mengekalkan nisbah aspek. Nilai yang lebih tinggi adalah lebih tepat, tetapi mengambil masa pemprosesan yang lebih lama dan menggunakan lebih banyak memori.", "machine_learning_ocr_model": "Model OCR", - "machine_learning_ocr_model_description": "Model \"server\" lebih tepat berbanding model \"mobile\", tetapi proses lebih panjang dan menggunakan lebih memori.", "machine_learning_settings": "Tetapan Pembelajaran Mesin", "machine_learning_settings_description": "Urus ciri dan tetapan pembelajaran mesin", "machine_learning_smart_search": "Carian Pintar", @@ -173,12 +170,6 @@ "machine_learning_smart_search_enabled": "Dayakan carian pintar", "machine_learning_smart_search_enabled_description": "Jika ditutup, gambar-gambar tidak akan dikodkan untuk carian pintar.", "machine_learning_url_description": "URL pelayan pembelajaran mesin. Jika lebih daripada satu URL disediakan, setiap pelayan akan dicuba satu demi satu mengikut turutan, dari yang pertama hingga yang terakhir, sehingga salah satu memberi maklum balas yang berjaya. Pelayan yang tidak memberi maklum balas akan diabaikan sementara sehingga ia kembali dalam talian.", - "maintenance_integrity_checksum_mismatch": "Checksum Tidak Sepadan", - "maintenance_integrity_missing_file": "Fail-fail Hilang", - "maintenance_integrity_missing_file_description": "Fail yang dikesan Immich didalam pangkalan data, tetapi tidak wujud didalam sistem.", - "maintenance_integrity_missing_file_job": "Periksa fail yang hilang", - "maintenance_integrity_missing_file_refresh_job": "Kemaskini laporan fail-fail hilang", - "maintenance_integrity_report": "Laporan integriti", "maintenance_settings": "Penyelenggaraan", "maintenance_settings_description": "Letak Immich ke dalam mod penyelenggaraan", "maintenance_start": "Mulakan mod penyelenggaraan", @@ -233,8 +224,6 @@ "notification_email_ignore_certificate_errors_description": "Abaikan ralat pengesahan sijil TLS (tidak disyorkan)", "notification_email_password_description": "Kata laluan untuk digunakan semasa membuat pengesahan dengan pelayan e-mel", "notification_email_port_description": "Port pelayan e-mel (cth 25, 465 atau 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "Gunakan SMTPS (SMTP diatas TLS)", "notification_email_sent_test_email_button": "Hantar e-mel ujian dan simpan", "notification_email_setting_description": "Tetapan-tetapan untuk menghantar notifikasi e-mel", "notification_email_test_email": "Hantar e-mel ujian", @@ -251,7 +240,6 @@ "oauth_button_text": "Teks butang", "oauth_client_secret_description": "Diperlukan jika PKCE (Proof Key for Code Exchange) tidak disokong oleh penyedia OAuth", "oauth_enable_description": "Log masuk dengan OAuth", - "oauth_end_session_url_description": "Alihkan pengguna ke URI ini apabila mereka log keluar.", "oauth_mobile_redirect_uri": "URI ubah hala mudah alih", "oauth_mobile_redirect_uri_override": "Penggantian URI ubah hala mudah alih", "oauth_mobile_redirect_uri_override_description": "Aktifkan apabila pembekal OAuth tidak membenarkan URI mudah alih, seperti ''{callback}''", @@ -268,7 +256,6 @@ "oauth_storage_quota_default_description": "Kuota dalam GiB yang akan digunakan jika tiada tuntutan disediakan.", "oauth_timeout": "Had Masa Permintaan", "oauth_timeout_description": "Had masa untuk permintaan dalam milisaat", - "ocr_job_description": "Gunakan kecerdasan buatan untuk mengesan teks didalam gambar", "password_enable_description": "Log masuk dengan e-mel dan kata laluan", "password_settings": "Kata Laluan Log Masuk", "password_settings_description": "Urus tetapan-tetapan kata laluan log masuk", @@ -278,7 +265,6 @@ "refreshing_all_libraries": "Menyegarkan semua perpustakaan", "registration": "Pendaftaran Pentadbir", "registration_description": "Memandangkan anda adalah pengguna pertama pada sistem, anda akan ditugaskan sebagai Pentadbir dan bertanggungjawab untuk tugas pentadbiran, serta pengguna tambahan yang akan anda tambah.", - "release_channel_release_candidate": "Release candidate", "require_password_change_on_login": "Perlukan pengguna menukar kata laluan ketika log masuk pertama", "reset_settings_to_default": "Tetapkan semula tetapan kepada lalai", "reset_settings_to_recent_saved": "Tetapkan semula tetapan kepada tetapan yang disimpan baru-baru ini", @@ -370,14 +356,6 @@ "transcoding_preferred_hardware_device_description": "Terpakai hanya untuk VAAPI dan QSV. Menetapkan nod dri yang digunakan untuk transkod perkakasan.", "transcoding_preset_preset": "Pratetap (-preset)", "transcoding_preset_preset_description": "Kelajuan mampatan. Pratetap yang lebih perlahan menghasilkan fail yang lebih kecil dan meningkatkan kualiti apabila pada kadar bit tertentu. VP9 mengabaikan kelajuan di atas 'lebih cepat'.", - "transcoding_realtime": "Transkod Masa Nyata [PERCUBAAN]", - "transcoding_realtime_description": "Benarkan transkod dibuat pada masa nyata semasa video sedang ditonton. Membolehkan kualiti ditukar, tetapi video mungkin lambat dimainkan dan tersangkut-sangkut bergantung kepada keupayaan pelayan.", - "transcoding_realtime_enabled": "Aktifkan transkod masa nyata", - "transcoding_realtime_enabled_description": "Jika dinyahaktifkan, pelayan akan enggan memulakan sesi transkod masa nyata.", - "transcoding_realtime_resolutions": "Resolusi", - "transcoding_realtime_resolutions_description": "Resolusi yang ditawarkan untuk transkod masa nyata. Resolusi tinggi mungkin akan menjadi isu jika pelayan tidak cukup berupaya.", - "transcoding_realtime_video_codecs": "Kodek video", - "transcoding_realtime_video_codecs_description": "Kodek video yang ditawarkan untuk transkod masa nyata. Pemain video akan menggunakan kodek terbaik yang disokong. AV1 lebih cekap berbanding HEVC, HEVC lebih cekap berbanding H.264. Bila gunakan pecutan perkakasan, hanya pilih kodek yang disokong pemecut. Bila transkod gunakan pecutan perisian, maklum bahawa H.264 lebih pantas berbanding AV1, yang lebih pantas berbanding HEVC.", "transcoding_reference_frames": "Bingkai rujukan", "transcoding_reference_frames_description": "Bilangan bingkai untuk dirujuk semasa memampatkan bingkai yang diberikan. Nilai yang lebih tinggi meningkatkan kecekapan mampatan, tetapi memperlahankan pengekodan. 0 menetapkan nilai ini secara automatik.", "transcoding_required_description": "Hanya untuk video yang tidak dalam format yang diterima", @@ -392,7 +370,7 @@ "transcoding_tone_mapping": "Pemetaan nada", "transcoding_tone_mapping_description": "Percubaan untuk mengekalkan penampilan video HDR apabila ditukar kepada SDR. Setiap algoritma membuat pertukaran yang berbeza untuk warna, perincian dan kecerahan. Hable mengekalkan perincian, Mobius mengekalkan warna, dan Reinhard mengekalkan kecerahan.", "transcoding_transcode_policy": "Dasar transkod", - "transcoding_transcode_policy_description": "Dasar untuk bila video perlu ditranskod. Video HDR serta video dengan format selain YUV 4:2:0 akan sentiasa ditranskod (kecuali jika transkod dinyahdayakan).", + "transcoding_transcode_policy_description": "Dasar untuk bila video perlu ditranskod. Video HDR akan sentiasa ditranskod (kecuali jika pengekodan semula dinyahdayakan).", "transcoding_two_pass_encoding": "Pengekodan dua lelaran", "transcoding_two_pass_encoding_setting_description": "Transkod dalam dua lelaran untuk menghasilkan video yang ditranskod dengan kualiti lebih baik. Apabila kadar bit maksimum diaktifkan (diperlukan untuk berfungsi dengan H.264 dan HEVC), mod ini akan menggunakan julat kadar bit berdasarkan kadar bit maksimum dan mengabaikan CRF. Untuk VP9, CRF boleh digunakan jika kadar bit maksimum dinyahdayakan.", "transcoding_video_codec": "Kodek video", @@ -416,9 +394,6 @@ "user_restore_scheduled_removal": "Pulihkan pengguna – pemadaman dijadualkan pada {date, date, long}", "user_settings": "Tetapan Pengguna", "user_settings_description": "Urus tetapan pengguna", - "user_successfully_removed": "Pengguna {email} berjaya dihapuskan.", - "version_check_channel": "Saluran pelepasan", - "version_check_channel_description": "Pilih saluran pelepasan perisian yang anda mahu untuk menerima pengumuman versi", "version_check_enabled_description": "Dayakan semakan versi", "version_check_implications": "Ciri semakan versi bergantung kepada komunikasi berkala dengan {server}", "version_check_settings": "Semakan Versi", @@ -430,14 +405,15 @@ "admin_password": "Kata laluan Pentadbir", "administration": "Pentadbiran", "advanced": "Lanjutan", - "advanced_settings_clear_image_cache": "Bersihkan Cache Gambar", - "advanced_settings_clear_image_cache_error": "Gagal bersihkan cache gambar", - "advanced_settings_clear_image_cache_success": "Berjaya bersihkan {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Gunakan pilihan ini untuk menapis media semasa penyegerakan berdasarkan kriteria alternatif. Hanya cuba jika anda menghadapi masalah dengan aplikasi mengesan semua album.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTAL] Gunakan penapis penyelarasan album peranti alternatif", "advanced_settings_log_level_title": "Tahap log: {level}", "advanced_settings_prefer_remote_subtitle": "Sesetengah peranti sangat perlahan untuk memuatkan imej kecil daripada aset lokal. Aktifkan tetapan ini untuk memuatkan imej dari jauh sebagai gantinya.", "advanced_settings_prefer_remote_title": "Utamakan imej jauh", "advanced_settings_proxy_headers_subtitle": "Tentukan pengepala proksi yang perlu dihantar oleh Immich dengan setiap permintaan rangkaian", "advanced_settings_proxy_headers_title": "Pengepala Proksi khusus [EKSPERIMEN]", + "advanced_settings_self_signed_ssl_subtitle": "Langkau pengesahan sijil SSL untuk titik hujung pelayan. Diperlukan untuk sijil yang ditandatangani sendiri.", + "advanced_settings_self_signed_ssl_title": "Benarkan sijil SSL self-signed [EKSPERIMEN]", "advanced_settings_sync_remote_deletions_subtitle": "Automatik memadam atau memulihkan satu asset di peranti ini apabila tindakan itu diambil di dalam laman sesawang", "advanced_settings_sync_remote_deletions_title": "Selaraskan pemadaman kawalan jauh [UJI KAJI]", "advanced_settings_tile_subtitle": "Tetapan lanjutan pengguna", @@ -445,234 +421,43 @@ "advanced_settings_troubleshooting_title": "Menyelesaikan masalah", "age_months": "Umur {bulan, plural, satu {# bulan} lain {# bulan}}", "age_year_months": "Umur 1 tahun, {bulan, plural, satu {# bulan} lain {# bulan}}", - "album": "Album", "album_added": "Album telah ditambah", "album_added_notification_setting_description": "Terima pemberitahuan e-mel apabila anda ditambah ke album perkongsian", "album_cover_updated": "Album dikemas kini", - "album_delete_confirmation": "Anda pasti untuk hapuskan album {album}?", - "album_delete_confirmation_description": "Jika album ini pernah dikongsi, pengguna lain tidak boleh mengaksesnya lagi.", - "album_deleted": "Album dihapuskan", - "album_info_updated": "Maklumat album dikemas kini", + "album_leave": "Tinggalkan album?", + "album_leave_confirmation": "Adakah anda pasti mahu meninggalkan {album} ini?", "album_name": "Nama Album", "album_remove_user": "Buang pengguna?", "album_remove_user_confirmation": "Adakah anda pasti mahu membuang {user}?", - "album_search_not_found": "Tiada album yang sepadan dengan carian", "album_share_no_users": "Nampaknya anda telah berkongsi album ini dengan semua pengguna atau anda tidak mempunyai mana-mana pengguna untuk dikongsi.", - "album_summary": "Ringkasan album", "album_updated": "Album dikemas kini", "album_updated_setting_description": "Terima pemberitahuan e-mel apabila album perkongsian mempunyai aset baharu", - "album_upload_assets": "Muat naik aset daripada komputer anda dan tambahkan kedalam album", - "album_viewer_appbar_share_err_delete": "Gagal menghapuskan album", - "album_viewer_page_share_add_users": "Tambah pengguna", + "album_user_left": "Kiri {album}", + "album_user_removed": "{user} telah dibuang", "album_with_link_access": "Benarkan sesiapa yang mempunyai pautan melihat foto dan individu dalam album ini.", - "albums": "Album-album", - "albums_default_sort_order": "Susunan lalai album", - "albums_default_sort_order_description": "Susunan lalai aset apabila membuat album baru.", - "albums_feature_description": "Koleksi aset yang boleh dikongsi dengan pengguna lain.", - "albums_on_device_count": "Album pada peranti ({count})", - "all": "Semua", - "all_albums": "Semua album", - "all_photos": "Semua gambar", - "all_videos": "Semua video", - "allow_dark_mode": "Benarkan mod gelap", - "allow_public_user_to_download": "Benarkan pengguna awam untuk muat turun", - "allow_public_user_to_upload": "Benarkan pengguna awam untuk muat naik", - "allowed": "Dibenarkan", - "alt_text_qr_code": "Gambar QR kod", - "api_key": "Kunci API", - "api_key_description": "Kunci ini hanya ditunjukkan sekali sahaja. Pastikan anda simpan sebelum tutup tetingkap ini.", - "api_key_empty": "Nama bagi Kekunci API perlu diisi", - "api_keys": "Kekunci API", - "app_bar_signout_dialog_content": "Adakah anda pasti untuk log keluar?", - "app_bar_signout_dialog_title": "Log keluar", - "app_download_links": "Pautan Muat Turun Aplikasi", - "app_settings": "Tetapan App", - "app_update_available": "Kemaskini aplikasi tersedia", - "archive": "Arkib", - "archive_action_prompt": "{count} ditambah ke Arkib", - "archive_or_unarchive_photo": "Arkibkan atau nyaharkib gambar", - "archive_size": "Saiz Arkib", - "archive_size_description": "Tetapkan saiz arkib untuk muat turun (GiB)", - "archived": "Diarkibkan", - "are_these_the_same_person": "Adakah ini orang yang sama?", - "are_you_sure_to_do_this": "Anda pasti?", - "asset_added_to_album": "Ditambah ke album", - "asset_adding_to_album": "Menambahkan ke albumâ€Ļ", - "asset_created": "Aset dicipta", - "asset_description_updated": "Penerangan aset dikemaskini", - "asset_list_layout_settings_group_by_month_day": "Bulan + hari", - "asset_list_layout_sub_title": "Susun atur", - "asset_not_found_on_device_android": "Aset tidak ditemui pada peranti", - "asset_not_found_on_device_ios": "Aset tidak ditemui pada peranti. Jika anda gunakan iCloud, aset mungkin tidak boleh diakses disebabkan oleh fail bermasalah yang disimpan di iCloud", - "asset_not_found_on_icloud": "Aset tidak ditemui pada iCloud, yang mungkin disebabkan oleh fail bermasalah yang disimpan di iCloud", - "asset_offline": "Aset Diluar Talian", - "asset_offline_description": "Aset luar tidak lagi dijumpai pada cakera. Hubungi admin Immich anda untuk bantuan.", - "asset_uploaded": "Telah dimuat naik", - "asset_uploading": "Dimuat naikâ€Ļ", - "assets": "Aset-aset", - "automatic_endpoint_switching_subtitle": "Sambung ke pelayan secara lokal melalui Wi-Fi yang ditetapkan dan gunakan sambungan alternatif selainnya", - "automatic_endpoint_switching_title": "Penukaran URL automatik", - "autoplay_slideshow": "Mainkan tayangan slaid", - "back": "Kembali", - "backup": "Sandaran", - "backup_album_selection_page_assets_scatter": "Aset mungkin tersebar pada banyak album. Oleh itu, album boleh dipilih atau dikecualikan semasa proses sandaran.", - "backup_album_selection_page_select_albums": "Pilih album", - "backup_album_selection_page_selection_info": "Maklumat pilihan", - "backup_background_service_complete_notification": "Sandaran aset selesai", - "backup_background_service_default_notification": "Sedang memeriksa aset baharuâ€Ļ", - "backup_background_service_in_progress_notification": "Sedang membuat sandaran aset andaâ€Ļ", - "backup_controller_page_albums": "Buat Sandaran Album", - "backup_controller_page_background_battery_info_link": "Tunjuk saya caranya", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_backup": "Sandaran", - "backup_controller_page_backup_selected": "Pilihan: ", - "backup_controller_page_excluded": "Pengecualian: ", - "backup_controller_page_none_selected": "Tiada yang dipilih", - "backup_controller_page_remainder": "Peringatan", - "backup_controller_page_remainder_sub": "Baki gambar dan video untuk sandaran daripada pilihan", - "backup_controller_page_server_storage": "Ruang Simpanan Pelayan", - "backup_controller_page_to_backup": "Album yang hendak disandarkan", - "backup_info_card_assets": "aset", - "backup_options": "Tetapan Sandaran", - "backup_settings_subtitle": "Tetapan muat naik", - "biometric_auth_enabled": "Pengesahan biometrik diaktifkan", - "biometric_not_available": "Pengesahan biometrik tidak tersedia pada peranti ini", - "birthdate_set_description": "Tarikh lahir digunakan untuk mengira umur beliau pada masa gambar ini diambil.", - "bugs_and_feature_requests": "Pepijat & Permintaan", - "buy": "Beli Immich", - "camera": "Kamera", - "camera_brand": "Jenama kamera", - "camera_model": "Model kamera", - "cancel": "Batal", - "cancel_search": "Batalkan carian", - "canceled": "Dibatalkan", - "canceling": "Sedang membatalkan", - "cannot_merge_people": "Tidak boleh gabung orang", - "cannot_undo_this_action": "Tindakan ini tidak boleh dibatalkan!", - "cannot_update_the_description": "Gagal kemaskini penerangan", - "change": "Ubah", - "change_date": "Ubah tarikh", - "change_description": "Ubah penerangan", - "change_display_order": "Ubah susunan paparan", - "change_location": "Ubah lokasi", - "change_name": "Ubah nama", - "change_name_successfully": "Nama telah ditukar", - "change_password": "Tukar Kata Laluan", - "change_password_description": "Katalaluan baru diperlukan kerana ini adalah kali pertama anda log masuk atau pemohonan kata laluan diubah pernah dibuat. Letakkan katalaluan baru dibawah.", - "change_password_form_confirm_password": "Sahkan Katalaluan", - "change_password_form_description": "Hi {name},\n\nKatalaluan perlu diubah kerana ini mungkin kali pertama anda log masuk atau permohonan untuk tukar katalaluan telah dibuat. Sila masukkan katalaluan baru dibawah.", - "change_password_form_log_out": "Log keluar kesemua peranti lain", - "change_password_form_log_out_description": "Disyorkan untuk log keluar kesemua peranti lain", - "change_password_form_new_password": "Katalaluan baru", - "change_password_form_password_mismatch": "Katalaluan tidak sepadan", - "change_password_form_reenter_new_password": "Masukkan semula Katalaluan", - "change_pin_code": "Tukar kod PIN", - "change_your_password": "Tukar Katalaluan", - "check_logs": "Periksa Log", - "choose": "Pilih", - "choose_matching_people_to_merge": "Pilih orang yang sepadan untuk digabungkan", - "city": "Bandar", - "cleanup_confirm_description": "Immich menemui{count} aset (dicipta sebelum {date}) yang selamat disimpan didalam pelayan. Buang salinan daripada peranti ini?", - "cleanup_confirm_prompt_title": "Buang daripada peranti ini?", - "cleanup_preview_title": "Aset untuk dibuang ({count})", - "clear": "Bersihkan", - "clear_all": "Bersihkan semua", - "clear_all_recent_searches": "Bersihkan semua carian terkini", - "clear_failed_count": "Gagal dibersihkan ({count})", - "clear_file_cache": "Bersihkan Cache Fail", - "clear_message": "Padam mesej", - "clear_value": "Padam nilai", - "client_cert_import": "Import", - "client_cert_password_message": "Masukkan katalaluan bagi sijil ini", - "client_cert_password_title": "Katalaluan Sijil", - "client_cert_subtitle": "Menyokong format PKCS12 (.p12, .pfx) sahaja. Import/hapus sijil hanya tersedia sebelum log masuk", - "client_cert_title": "Sijil SSL [PERCUBAAN]", - "close": "Tutup", - "color": "Warna", - "command": "Arahan", - "comment_deleted": "Arahan dipadam", - "comment_options": "Tetapan komen", - "comments_and_likes": "Komen & suka", - "comments_are_disabled": "Komen tidak diaktifkan", - "common_create_new_album": "Cipta album baru", - "completed": "Selesai", - "configuration": "Tetapan", - "confirm": "Pasti", - "confirm_delete_face": "Anda pasti untuk padam wajah {name} daripada aset?", - "confirm_delete_shared_link": "Anda pasti untuk menghapuskan pautan awam ini?", - "confirm_new_pin_code": "Sahkan PIN kod baru", - "confirm_password": "Sahkan katalaluan", - "confirm_tag_face": "Anda mahu tandakan wajah ini sebagai {name}?", - "confirm_tag_face_unnamed": "Adakah anda mahu tandakan wajah ini?", - "connected_device": "Peranti yang terhubung", - "context": "Konteks", - "control_bottom_app_bar_add_tags": "Tambah Tag", - "control_bottom_app_bar_delete_from_local": "Buang daripada peranti", - "control_bottom_app_bar_edit_location": "Ubah Lokasi", - "control_bottom_app_bar_edit_time": "Ubah Tarikh & Masa", - "control_bottom_app_bar_trash_from_immich": "Buang ke tong sampah", - "copy_image": "Salin Gambar", - "copy_json": "Salin JSON", - "copy_link": "Salin pautan", - "copy_password": "Salin katalaluan", - "country": "Negara", - "create": "Cipta", - "create_album": "Cipta album", - "create_api_key": "Cipta kunci API", - "create_first_workflow": "Bina aliran kerja", - "create_link": "Cipta pautan", - "create_link_to_share": "Cipta pautan untuk kongsi", - "create_link_to_share_description": "Semua yang mempunyai pautan boleh lihat gambar yang dipilih", - "create_new_person": "Tambah orang baru", - "create_new_user": "Tambah pengguna baru", - "create_person": "Tambah orang", - "create_person_subtitle": "Isikan nama pada wajah yang dipilih untuk tambah pada orang baru", - "create_shared_album_page_share_select_photos": "Pilih Gambar", - "create_shared_link": "Cipta pautan kongsi", - "crop_aspect_ratio_original": "Asal", - "current_device": "Peranti semasa", - "current_pin_code": "Kod PIN semasa", - "current_server_address": "Alamat pelayan semasa", - "dark": "Gelap", - "dark_theme": "Tukar ke tema gelap", - "date": "Tarikh", - "date_and_time": "Tarikh dan Masa", - "date_before": "Tarikh sebelum", - "date_of_birth": "Tarikh lahir", - "date_of_birth_saved": "Tarikh lahir berjaya disimpan", - "date_range": "Julat tarikh", - "date_time_original": "Tarikh/Masa Asli", - "day": "Hari", - "days": "Hari-hari", "delete": "Padam", - "delete_action_prompt": "{count} dipadam", "delete_album": "Padam album", "delete_api_key_prompt": "Adakah anda pasti mahu memadam kunci API ini?", - "delete_dialog_alert": "Item berikut akan dipadam selama-lamanya dari Immich dan peranti anda", - "delete_dialog_alert_local": "Item berikut akan dipadam selama-lamanya dari peranti anda, tetapi masih akan ada di pelayan Immich", - "delete_dialog_alert_local_non_backed_up": "Sebahagian item tiada sandaran di Immich dan akan dihapuskan secara kekal daripada peranti anda", - "delete_dialog_title": "Hapuskan Terus", "delete_duplicates_confirmation": "Adakah anda pasti mahu memadam pendua ini secara kekal?", - "delete_face": "Hapuskan wajah", "delete_key": "Padam kunci", "delete_library": "Padam Pustaka", "delete_link": "Padam pautan", "delete_others": "Padam yang lain", "delete_shared_link": "Padam pautan yang dikongsi", - "delete_shared_link_dialog_title": "Hapuskan pautan kongsi", "delete_tag": "Padam tag", "delete_tag_confirmation_prompt": "Adakah anda pasti mahu memadam tag {tagName}?", "delete_user": "Padam pengguna", "deleted_shared_link": "Pautan kongsi yang dipadamkan", + "deletes_missing_assets": "Memadamkan aset yang hilang daripada cakera", "description": "Penerangan", - "deselect_all": "Nyahpilih semua", "details": "Butiran", "direction": "Arah", "disabled": "Dilumpuhkan", + "disallow_edits": "Tolak pengeditan", "discord": "Perselisihan", "discover": "Terokai", - "discovered_devices": "Peranti yang ditemui", "dismiss_all_errors": "Tolak semua ralat", + "dismiss_error": "Tolak ralat", "display_options": "Pilihan paparan", "display_order": "Tertib paparan", "display_original_photos": "Paparkan foto asal", @@ -681,114 +466,11 @@ "documentation": "Dokumentasi", "done": "Selesai", "download": "Muat Turun", - "download_canceled": "Muat turun dibatalkan", - "download_complete": "Muat turun selesai", - "download_failed": "Muat turun gagal", - "download_finished": "Muat turun selesai", - "download_notfound": "Muat turun tidak ditemui", - "download_original": "Muat turun asli", "download_settings": "Muat Turun", "download_settings_description": "Urus tetapan yang berkaitan dengan muat turun aset", - "download_waiting_to_retry": "Tunggu untuk cubaan seterusnya", "downloading": "Memuat turun", - "downloading_asset_filename": "Sedang memuat turun aset {filename}", - "downloading_from_icloud": "Sedang memuat turun daripada iCloud", - "downloading_media": "Sedang memuat turun media", - "duration": "Tempoh", - "edit_location_dialog_title": "Lokasi", - "edit_name": "Ubah nama", - "edit_people": "Ubah orang", - "edit_tag": "Ubah tag", - "edit_title": "Ubah tajuk", - "editor": "Penyunting", - "editor_discard_edits_confirm": "Buang suntingan", - "editor_discard_edits_prompt": "Suntingan anda belum disimpan. Anda mahu membuangnya?", - "editor_discard_edits_title": "Buang suntingan?", - "editor_edits_applied_error": "Gagal simpan suntingan", - "editor_edits_applied_success": "Suntingan berjaya disimpan", - "editor_flip_horizontal": "Terbalikkan secara horizontal", - "editor_flip_vertical": "Terbalikkan secara vertikal", - "editor_reset_all_changes": "Tetapan semula", - "editor_rotate_left": "Pusing 90° lawan jam", - "editor_rotate_right": "Pusing 90° arah jam", - "email": "Emel", - "empty_folder": "Folder ini kosong", - "empty_trash": "Bersihkan tong sampah", - "empty_trash_confirmation": "Anda pasti mahu bersihkan tong sampah? Ia akan menghapuskan aset secara kekal daripada Immich.\nTindakan ini tidak boleh dibatalkan!", - "enable": "Aktifkan", - "enable_backup": "Aktifkan Sandaran", - "enable_biometric_auth_description": "Masukkan PIN kod anda untuk aktifkan pengesahan biometrik", - "enabled": "Diaktifkan", - "enter_wifi_name": "Masukkan nama Wi-Fi", - "enter_your_pin_code": "Masukkan PIN kod anda", - "enter_your_pin_code_subtitle": "Masukkan PIN kod untuk mengakses folder berkunci", - "error": "Ralat", - "error_delete_face": "Ralat hapuskan wajah daripada aset", - "error_loading_albums": "Ralat semasa memuat album", - "error_loading_image": "Ralat semasa memuat gambar", - "error_retrieving_asset_information": "Ralat memuat maklumat aset", - "error_saving_image": "Ralat: {error}", - "error_tag_face_bounding_box": "Ralat menanda wajah - gagal mendapatkan koordinat petak sempadan", - "error_title": "Ralat - Sesuatu tidak kena", - "error_while_navigating": "Ralat semasa navigasi ke aset", - "errors": { - "cannot_navigate_next_asset": "Tidak boleh navigasi ke aset berikutnya", - "cannot_navigate_previous_asset": "Tidak boleh navigasi ke aset sebelumnya", - "cant_apply_changes": "Tidak boleh membuat perubahan", - "cant_get_faces": "Gagal mendapakan wajah", - "cant_get_number_of_comments": "Gagal mendapatkan bilangan komen", - "error_adding_assets_to_album": "Ralat menambah aset kedalam album", - "error_adding_users_to_album": "Ralat menambah pengguna album", - "error_downloading": "Ralat memuat turun {filename}", - "error_hiding_buy_button": "Ralat menyorokkan butang beli", - "error_removing_assets_from_album": "Ralat membuang aset daripada album, lihat konsol untuk maklumat lanjut", - "error_selecting_all_assets": "Ralat memilih semua aset", - "exclusion_pattern_already_exists": "Corak pengecualian ini telah wujud.", - "failed_to_create_album": "Gagal membuat album baru", - "failed_to_create_shared_link": "Gagal mencipta pautan kongsi", - "failed_to_edit_shared_link": "Gagal menyunting pautan kongsi", - "failed_to_get_people": "Gagal mendapatkan maklumat orang", - "failed_to_load_asset": "Gagal memuat aset", - "failed_to_load_assets": "Gagal memuat aset-aset", - "failed_to_remove_product_key": "Gagal membuang kunci produk", - "failed_to_reset_pin_code": "Gagal menetapkan semula PIN kod", - "incorrect_email_or_password": "Emel atau katalaluan salah", - "page_not_found": "Halaman tidak ditemui" - }, - "language": "Bahasa", - "language_search_hint": "Cari bahasaâ€Ļ", - "last": "Akhir", - "latest_version": "Versi Terkini", - "latitude": "Latitud", - "leave": "Tinggalkan", - "leave_album": "Tinggalkan album", - "lens_model": "Model lensa", - "less": "Kurang", - "library_add_folder": "Tambah folder", - "library_edit_folder": "Sunting folder", - "library_page_sort_asset_count": "Bilangan aset", - "library_page_sort_title": "Tajuk album", - "light": "Cerah", - "light_theme": "Tukar ke tema cerah", - "like": "Suka", - "link": "Pautan", - "link_to_docs": "Untuk maklumat lanjut, rujuk dokumentasi.", - "link_to_oauth": "Pautkan dengan OAuth", - "linked_oauth_account": "Akaun OAuth yang terpaut", - "list": "Senarai", - "loading": "Sedang memuatkan", "search_by_description": "Carian secara huraian", "search_by_description_example": "Hari mendaki di Sapa", - "some_assets_already_have_a_location_warning": "Beberapa aset yang dipilih telah memiliki lokasi", - "sort_albums_by": "Susun album mengikut...", - "sort_created": "Tarikh dicipta", - "sort_items": "Bilanganitem", - "sort_modified": "Tarikh diubah", - "sort_oldest": "Foto Tertua", - "sort_people_by_similarity": "Susun orang mengikut kesamaan", - "sort_recent": "Foto paling terbaru", - "sort_title": "Tajuk", - "source": "Sumber", "text_recognition": "Pengecaman teks", "theme": "Tema", "theme_selection": "Pemilihan tema", @@ -806,5 +488,6 @@ "yes": "Ya", "you_dont_have_any_shared_links": "Anda tidak mempunyai apa-apa pautan yang dikongsi", "your_wifi_name": "Nama Wi-Fi anda", - "zoom_image": "Zum Gambar" + "zoom_image": "Zum Gambar", + "zoom_to_bounds": "Zum ke sempadan" } diff --git a/i18n/nb_NO.json b/i18n/nb_NO.json index b699f0e43a..1602707dd9 100644 --- a/i18n/nb_NO.json +++ b/i18n/nb_NO.json @@ -5,36 +5,44 @@ "acknowledge": "Bekreft", "action": "Handling", "action_common_update": "Oppdater", - "action_description": "Et sett med handlinger som skal utføres pÃĨ de filtrerte mediefilene", + "action_description": "Ett sett handlinger som skal utføres pÃĨ de filtrerte mediefilene", "actions": "Handlinger", "active": "Aktiv", "active_count": "Aktiv: {count}", "activity": "Aktivitet", + "activity_changed": "Aktiviteten er {enabled, select, true {aktivert} other {deaktivert}}", "add": "Legg til", "add_a_description": "Legg til beskrivelse", - "add_a_location": "Legg til plassering", + "add_a_location": "Legg til sted", "add_a_name": "Legg til navn", "add_a_title": "Legg til tittel", - "add_action": "Legg til handling", + "add_action": "Legg til hendelse", + "add_action_description": "Trykk for ÃĨ legge til en hendelse ÃĨ utføre", "add_assets": "Legg til mediefiler", "add_birthday": "Legg til bursdag", "add_endpoint": "Legg til endepunkt", "add_exclusion_pattern": "Legg til ekskluderingsmønster", - "add_location": "Legg til plassering", + "add_filter": "Legg til filter", + "add_filter_description": "Trykk for ÃĨ legge til filter begrensning", + "add_location": "Legg til sted", + "add_more_users": "Legg til flere brukere", "add_partner": "Legg til partner", + "add_path": "Legg til sti", "add_photos": "Legg til bilder", - "add_step": "Legg til steg", - "add_tag": "Legg til merke", + "add_tag": "Legg til merkelapp", "add_to": "Legg til iâ€Ļ", "add_to_album": "Legg til album", "add_to_album_bottom_sheet_added": "Lagt til i {album}", "add_to_album_bottom_sheet_already_exists": "Allerede i {album}", "add_to_album_bottom_sheet_some_local_assets": "Noen lokale filer kunne ikke legges til i albumet", + "add_to_album_toggle": "Avhuking for {album}", "add_to_albums": "Legg til i album", "add_to_albums_count": "Legg til i album ({count})", "add_to_bottom_bar": "Legg til i", - "add_upload_to_stack": "Legg opplastingen til i stabel", + "add_to_shared_album": "Legg til delt album", + "add_upload_to_stack": "Legg til opplasting i stakken", "add_url": "Legg til URL", + "add_workflow_step": "Trykk for ÃĨ legge til oppgave i arbeidsflyten", "added_to_archive": "Lagt til i arkivet", "added_to_favorites": "Lagt til favoritter", "added_to_favorites_count": "Lagt til {count, number} i favoritter", @@ -42,49 +50,48 @@ "add_exclusion_pattern_description": "Legg til ekskluderingsmønstre. Globbing med *, ** og ? støttes. For ÃĨ ignorere alle filer i en hvilken som helst mappe som heter \"Raw\", bruk \"**/Raw/**\". For ÃĨ ignorere alle filer som slutter pÃĨ \".tif\", bruk \"**/*.tif\". For ÃĨ ignorere en absolutt filplassering, bruk \"/filsti/til/ignorer/**\".", "admin_user": "Administrasjonsbruker", "asset_offline_description": "Dette eksterne bibliotekselementet finnes ikke lenger pÃĨ disk og har blitt flyttet til papirkurven. Hvis filen ble flyttet innad i biblioteket, se etter det tilsvarende elementet i tidslinjen din. For ÃĨ gjenopprette elementet, vennligst sørg for at filstien under er tilgjengelig for Immich og skann biblioteket.", - "authentication_settings": "Autentiseringsinnstillinger", - "authentication_settings_description": "Administrer passord, OAuth, og andre autentiseringsinnstillinger", + "authentication_settings": "Godkjenings Instillinger", + "authentication_settings_description": "Administrer passord, OAuth, og andre innstillinger for autentiserings Instilinger", "authentication_settings_disable_all": "Er du sikker pÃĨ at du ønsker ÃĨ deaktivere alle innloggingsmetoder? Innlogging vil bli fullstendig deaktivert.", "authentication_settings_reenable": "For ÃĨ aktivere pÃĨ nytt, bruk en Server Command.", - "background_task_job": "Bakgrunnsoppgaver", + "background_task_job": "Bakgrunnsjobber", "backup_database": "Opprett database-dump", "backup_database_enable_description": "Aktiver database-dump", "backup_keep_last_amount": "Antall database-dumps ÃĨ beholde", "backup_onboarding_1_description": "ekstern kopi i skyen eller pÃĨ et annet fysisk sted.", "backup_onboarding_2_description": "lokale kopier pÃĨ forskjellige enheter. Dette inkluderer hovedfilene og en lokal sikkerhetskopi av disse filene.", - "backup_onboarding_3_description": "komplette kopier av dataene dine, inkludert originalfilene. Dette inkluderer Ên ekstern kopi og to lokale kopier.", + "backup_onboarding_3_description": "totale kopier av dataene dine, inkludert originalfilene. Dette inkluderer Ên ekstern kopi og to lokale kopier.", "backup_onboarding_description": "En 3-2-1 sikkerhetskopieringsstrategi anbefales for ÃĨ beskytte dataene dine. Du bør beholde kopier av opplastede bilder/videoer samt Immich-databasen for en omfattende sikkerhetskopieringsløsning.", "backup_onboarding_footer": "For mer informasjon om sikkerhetskopiering av Immich, se dokumentasjonen.", "backup_onboarding_parts_title": "En 3-2-1 sikkerhetskopi inkluderer:", "backup_onboarding_title": "Sikkerhetskopier", - "backup_settings": "Database-dump innstillinger", - "backup_settings_description": "AdministrÊr innstillinger for database-dump.", - "cleared_jobs": "Ryddet opp oppgaver for: {job}", + "backup_settings": "Database-dump", + "backup_settings_description": "HÃĨndter innstillinger for database-dump.", + "cleared_jobs": "Ryddet opp jobber for: {job}", "config_set_by_file": "Konfigurasjonen er for øyeblikket satt av en konfigurasjonsfil", "confirm_delete_library": "Vil du virkelig slette biblioteket {library}?", - "confirm_delete_library_assets": "Vil du virkelig slette dette biblioteket? Dette vil slette alt innhold ({count, plural, one {# element} other {alle # elementer}}) og tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli pÃĨ disken.", + "confirm_delete_library_assets": "Vil du virkelig slette dette biblioteket? Dette vil slette alt innhold ({count, plural, one {# element} other {# elementer}}) og tilhørende eiendeler fra Immich og kan ikke angres. Filene vil forbli pÃĨ disken.", "confirm_email_below": "For ÃĨ bekrefte, skriv inn \"{email}\" nedenfor", "confirm_reprocess_all_faces": "Vil du virkelig behandle alle ansikter pÃĨ nytt? Dette vil ogsÃĨ fjerne navngitte personer.", "confirm_user_password_reset": "Vil du virkelig tilbakestille passordet til {user}?", "confirm_user_pin_code_reset": "Vil du virkelig tilbakestille PIN-koden til {user} ?", - "copy_config_to_clipboard_description": "KopiÊr nÃĨvÃĻrende systemkonfigurasjon som et JSON objekt til utklippsmenyen", - "create_job": "Opprett oppgave", - "cron_expression": "Cron-uttrykk", + "copy_config_to_clipboard_description": "Kopier nÃĨvÃĻrende systemkonfigurasjon som ett JSON objekt til utklippsmenyen", + "create_job": "Lag jobb", + "cron_expression": "Cron uttrykk", "cron_expression_description": "Still inn skanneintervallet med cron-formatet. For mer informasjon henvises til f.eks. Crontab Guru", "cron_expression_presets": "ForhÃĨndsinnstillinger for Cron-uttrykk", "disable_login": "Deaktiver innlogging", - "download_csv": "Last ned CSV", - "duplicate_detection_job_description": "Kjør maskinlÃĻring pÃĨ filer for ÃĨ oppdage lignende bilder. Krever bruk av smartsøk", - "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper nÃĨr du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke ønsker ÃĨ importere, som for eksempel RAW-filer.", + "duplicate_detection_job_description": "Kjør maskinlÃĻring pÃĨ filer for ÃĨ oppdage lignende bilder. Krever bruk av Smart Søk", + "exclusion_pattern_description": "Ekskluderingsmønstre lar deg ignorere filer og mapper nÃĨr du skanner biblioteket ditt. Dette er nyttig hvis du har mapper som inneholder filer du ikke vil importere, for eksempel RAW-filer.", "export_config_as_json_description": "Last ned nÃĨvÃĻrende systemkonfigurasjon som en JSON fil", "external_libraries_page_description": "Administrering for eksterne bibliotek", "face_detection": "Ansiktsgjenkjennelse", - "face_detection_description": "Oppdag ansikter i bilder ved hjelp av maskinlÃĻring. For videoer vurderes bare miniatyrbildet. \"Oppdater\" behandler alle mediefiler pÃĨ nytt. \"Tilbakestill\" fjerner i tillegg all gjeldende ansiktsdata. \"Mangler\" legger i kø filer som ikke har blitt behandlet enda. Oppdagede ansikter vil bli sendt til Ansiktsgjenkjenning etter at Ansiktsoppdagelse er ferdig, som vil gruppere dem til eksisterende eller nyer personer.", - "facial_recognition_job_description": "Kobler oppdagede ansikt til personer. Dette utføres etter at Ansiktsoppdagelse er fullført. \"Tilbakestill\" (om-)grupperer alle ansikt pÃĨ nytt. \"Mangler\" køer ansikter som ikke har blitt tilordnet en person ennÃĨ.", - "failed_job_command": "Kommandoen {command} feilet for oppgave: {job}", + "face_detection_description": "Finn ansikter i bilder ved hjelp av maskinlÃĻring. For videoer brukes bare miniatyrbildet. \"Alle\" gÃĨr gjennom alle bilder (igjen). \"Tilbakestill\" fjerner all gjeldende ansiktsdata. \"Mangler\" legger til filer som ikke har blitt behandlet enda i køen. Oppdagede ansikter vil blir sendt til ansiktsgjenkjenning, og koblet til eksisterende eller nye personer.", + "facial_recognition_job_description": "Kobler oppdagede ansikt til personer. Dette utføres etter at ansiktssøk er fullført. \"Tilbakestill\" (om-)grupperer alle ansikt pÃĨ nytt. \"Mangler\" stiller opp ansikt som ikke har blitt tilordnet en person ennÃĨ.", + "failed_job_command": "Kommandoen {command} feilet for jobb: {job}", "force_delete_user_warning": "ADVARSEL: Dette vil umiddelbart fjerne brukeren og alle data. Dette kan ikke angres, og filene kan ikke gjenopprettes.", "image_format": "Format", - "image_format_description": "WebP produserer mindre filer enn JPEG, men kodes langsommere.", + "image_format_description": "WebP gir mindre filer enn JPEG, men er tregere ÃĨ lage.", "image_fullsize_description": "Bilde i full størrelse med strippet metadata, brukt nÃĨr du zoomer inn", "image_fullsize_enabled": "Aktiver generering av bilder i full størrelse", "image_fullsize_enabled_description": "Generer bilder i full størrelse for ikke-nettvennlige formater. NÃĨr \"Foretrekk innebygd forhÃĨndsvisning\" er aktivert, brukes innebygde forhÃĨndsvisninger direkte uten konvertering. PÃĨvirker ikke nettvennlige formater som JPEG.", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Miniatyrbildekvalitet fra 1-100. Høyere er bedre, men produserer større filer og kan redusere appens respons.", "image_thumbnail_title": "Miniatyrbilde oppsett", "import_config_from_json_description": "Importer systemkonfigurasjon ved ÃĨ laste opp en JSON konfigurasjonsfil", - "integrity_checks_checksum_files": "Sjekksumfiler", - "integrity_checks_checksum_files_description": "Konfigurer sjekksumkontrollen", - "integrity_checks_checksum_files_enable_description": "Aktiver sjekksumkontrollen", - "integrity_checks_checksum_files_percentage_limit": "Prosentgrense", - "integrity_checks_checksum_files_percentage_limit_description": "Konfigurer den maksimale prosentsatsen mellom 0,01 og 1 for hvor mye sjekksumkontrollen skal kjøre hvert intervall.", - "integrity_checks_checksum_files_time_limit": "Tidsgrense", - "integrity_checks_checksum_files_time_limit_description": "Konfigurer den maksimale varigheten som sjekksumkontrollen skal kjøre hvert intervall. (ms)", - "integrity_checks_missing_files": "Manglende filer", - "integrity_checks_missing_files_description": "Konfigurer frekvensen og aktiver eller deaktiver kontrollen av manglende filer", - "integrity_checks_missing_files_enable_description": "Aktiver kontrollen for manglende filer", - "integrity_checks_settings": "Integritetskontroller", - "integrity_checks_settings_description": "Administrer intervaller for integritetskontroller", - "integrity_checks_untracked_files": "Usporede filer", - "integrity_checks_untracked_files_description": "Konfigurer frekvensen og aktiver eller deaktiver sjekken for usporede filer", - "integrity_checks_untracked_files_enable_description": "Aktiver kontrollen av usporede filer", "job_concurrency": "{job} samtidighet", "job_created": "Oppgave laget", "job_not_concurrency_safe": "Denne jobben er ikke samtidlighet sikker.", @@ -196,27 +188,11 @@ "machine_learning_smart_search": "Smart søk", "machine_learning_smart_search_description": "Søk etter bilder semantisk ved ÃĨ bruke CLIP-embeddings", "machine_learning_smart_search_enabled": "Aktiver smart søk", - "machine_learning_smart_search_enabled_description": "Hvis deaktivert sÃĨ blir ikke bilder kodet for smartsøk.", + "machine_learning_smart_search_enabled_description": "Hvis deaktivert, vil bilder ikke bli enkodet for smart søk.", "machine_learning_url_description": "URL til maskinlÃĻrings-serveren. Hvis mer enn en URL er lagt inn, hver server vill bli forsøkt en om gangen frem til en svarer suksessfullt, i rekkefølge fra først til sist. Servere som ikke svarer vil midlertidig bli oversett frem til dem svarer igjen.", - "maintenance_backup_management": "Administrasjon av sikkerhetskopier", "maintenance_delete_backup": "Slett sikkerhetskopi", "maintenance_delete_backup_description": "Denne filen vil bli permanent slettet.", "maintenance_delete_error": "Feilet ved sletting av sikkerhetskopi.", - "maintenance_integrity_check": "Kontroll", - "maintenance_integrity_check_all": "Velg alle", - "maintenance_integrity_checksum_mismatch": "Sjekksum er feil", - "maintenance_integrity_checksum_mismatch_description": "Filer med en sjekksum pÃĨ disken som ikke stemmer overens med sjekksummen Immich har lagret i databasen sin.", - "maintenance_integrity_checksum_mismatch_job": "Sjekk for feilede sjekksummer", - "maintenance_integrity_checksum_mismatch_refresh_job": "Oppdater feilede sjekksum rapporter", - "maintenance_integrity_missing_file": "Manglende filer", - "maintenance_integrity_missing_file_description": "Filer som Immich har sporet i databasen sin, men som ikke eksisterer i filsystemet.", - "maintenance_integrity_missing_file_job": "Sjekk etter manglende filer", - "maintenance_integrity_missing_file_refresh_job": "Oppdater rapporten for manglende filer", - "maintenance_integrity_report": "Integritetsrapport", - "maintenance_integrity_untracked_file": "Usporede filer", - "maintenance_integrity_untracked_file_description": "Filer i Immichs mapper som Immich ikke har noen oversikt over.", - "maintenance_integrity_untracked_file_job": "Sjekk etter usporede filer", - "maintenance_integrity_untracked_file_refresh_job": "Oppdater rapporten for usporede filer", "maintenance_restore_backup": "Gjenopprett Sikkerhetskopi", "maintenance_restore_backup_description": "Immich vil bli sletter og gjenopprettet fra en valgt sikkerhetskopi. En sikkerhetskopi vil utføres før handlingen fortsetter.", "maintenance_restore_backup_different_version": "Denne sikkerhetskopien ble laget med en annen versjon av Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Aktiver e-postvarsler", "notification_settings": "Varselinnstillinger", "notification_settings_description": "Administrer varselinnstillinger, inkludert e-post", - "oauth_allow_insecure_requests": "Tillat usikre tilkoblinger", - "oauth_allow_insecure_requests_description": "ADVARSEL: Dette deaktiverer validering av TLS-sertifikater for OAuth-forespørsler og kan utsette deg for MITM-angrep.", "oauth_auto_launch": "Automatisk oppstart", "oauth_auto_launch_description": "Start OAuth-innloggingsflyten automatisk nÃĨr du navigerer til innloggingssiden", "oauth_auto_register": "Automatisk registrering", @@ -300,11 +274,9 @@ "oauth_button_text": "Knappetekst", "oauth_client_secret_description": "Kreves for konfidensiell klient, eller hvis PKCE (Proof Key for Code Exchange) ikke støttes for offentlig klient.", "oauth_enable_description": "Logg inn med OAuth", - "oauth_end_session_url_description": "Videresend brukeren til denne URI nÃĨr de logger ut.", "oauth_mobile_redirect_uri": "Mobil omdirigerings-URI", "oauth_mobile_redirect_uri_override": "Mobil omdirigerings-URI overstyring", "oauth_mobile_redirect_uri_override_description": "Aktiver nÃĨr OAuth-leverandøren ikke tillater en mobil URI, som ''{callback}''", - "oauth_prompt_description": "Parameter for prompt (f.eks. select_account, login, consent)", "oauth_role_claim": "Krev Rolle", "oauth_role_claim_description": "Gi automatisk administratortilgang basert pÃĨ tilstedevÃĻrelsen av dette kravet. Kravet kan ha enten ÂĢbrukerÂģ eller ÂĢadministratorÂģ.", "oauth_settings": "OAuth", @@ -331,14 +303,12 @@ "refreshing_all_libraries": "Oppdaterer alle biblioteker", "registration": "Administrator registrering", "registration_description": "Siden du er den første brukeren pÃĨ systemet, vil du bli utnevnt til administrator og ha ansvar for administrative oppgaver. Du vil ogsÃĨ opprette eventuelle nye brukere.", - "release_channel_release_candidate": "Publiseringskandidat", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Fjern feilede jobber", "require_password_change_on_login": "Krev at brukeren endrer passord ved første pÃĨlogging", "reset_settings_to_default": "Tilbakestill innstillinger til standard", "reset_settings_to_recent_saved": "Tilbakestill innstillingene til de nylig lagrede innstillingene", - "scanning_library": "Bibliotekskanning", - "search_jobs": "Søkeprosesserâ€Ļ", + "scanning_library": "Søk biblioteket", + "search_jobs": "Søk etter jobberâ€Ļ", "send_welcome_email": "Send velkomst-e-post", "server_external_domain_settings": "Eksternt domene", "server_external_domain_settings_description": "Domene brukt for eksterne lenker", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Gjelder bare for VAAPI og QSV. Angir DRI-node brukt for maskinvaretranscoding.", "transcoding_preset_preset": "ForhÃĨndsinnstilling (-preset)", "transcoding_preset_preset_description": "Komprimeringshastighet. Tregere forhÃĨndsinnstillinger produserer mindre filer og øker kvaliteten nÃĨr man sikter mot en bestemt bithastighet. VP9 ignorerer hastigheter over \"faster\".", - "transcoding_realtime": "Sanntids-transkoding [Eksperimentell]", - "transcoding_realtime_description": "Tillater transkoding i sanntid mens videoen strømmes. Muliggjør kvalitetsbytte, men kan forÃĨrsake høyere avspillingsforsinkelse og hakking avhengig av serverkapasitet.", - "transcoding_realtime_enabled": "Aktiver sanntid-transcoding", - "transcoding_realtime_enabled_description": "Hvis deaktivert sÃĨ vil serveren nekte ÃĨ starte nye transkodingsøkter i sanntid.", - "transcoding_realtime_resolutions": "Oppløsninger", - "transcoding_realtime_resolutions_description": "Oppløsningene som tilbys for sanntidstranskoding. Høyere oppløsninger kan føre til problemer med avspillingen dersom serveren ikke klarer ÃĨ transkode dem raskt nok.", - "transcoding_realtime_video_codecs": "Videokodeker", - "transcoding_realtime_video_codecs_description": "De tilgjengelige videokodekene for sanntidstranskoding. Klienter vil velge det beste alternativet de støtter under avspilling. AV1 er mer effektiv enn HEVC, som igjen er mer effektiv enn H.264. Ved bruk av maskinvareakselerasjon velger du kun kodeker som maskinvaren kan kode. Ved bruk av programvarebasert transkoding bør du vÃĻre oppmerksom pÃĨ at H.264 er raskere enn AV1, som igjen er raskere enn HEVC.", "transcoding_reference_frames": "Referansebilder", "transcoding_reference_frames_description": "Antall bilder som skal refereres til nÃĨr en gitt ramme komprimeres. Høyere verdier forbedrer komprimeringseffektiviteten, men senker ned kodingen. 0 setter denne verdien automatisk.", "transcoding_required_description": "Bare videoer som ikke er i et akseptert format", @@ -463,7 +425,7 @@ "unlink_all_oauth_accounts_description": "Husk ÃĨ koble fra alle OAuth-kontoer før du migrerer til ny leverandør.", "unlink_all_oauth_accounts_prompt": "Vil du virkelig koble fra alle OAuth-kontoer? Dette vil nullstille OAuth ID for hver bruker, og kan ikke angres.", "user_cleanup_job": "Bruker opprydning", - "user_delete_delay": "{user}'s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", + "user_delete_delay": "{user}s konto og elementer vil legges i kø for permanent sletting om {delay, plural, one {# dag} other {# dager}}.", "user_delete_delay_settings": "Sletteforsinkelse", "user_delete_delay_settings_description": "Antall dager etter fjerning før en brukerkonto og dens filer permanent slettes. Brukerfjerningsjobben kjører ved midnatt for ÃĨ sjekke etter brukere som er klare for sletting. Endringer i denne innstillingen vil bli evaluert ved neste utførelse.", "user_delete_immediately": "{user}s konto og elementer vil bli lagt i kø for permanent sletting umiddelbart.", @@ -472,14 +434,12 @@ "user_management": "Brukeradministrasjon", "user_password_has_been_reset": "Passordet til brukeren har blitt tilbakestilt:", "user_password_reset_description": "Vennligst oppgi det midlertidige passordet til brukeren og informer dem om at de mÃĨ endre passordet ved neste pÃĨlogging.", - "user_restore_description": "{user}'s konto vil bli gjenopprettet.", + "user_restore_description": "{user}s konto vil bli gjenopprettet.", "user_restore_scheduled_removal": "Gjenopprett bruker - planlagt sletting den {date, date, long}", "user_settings": "Brukerinnstillinger", "user_settings_description": "Administrer brukerinnstillinger", "user_successfully_removed": "Bruker {email} har blitt fjernet.", "users_page_description": "Administrer brukere", - "version_check_channel": "Distribusjonskanal", - "version_check_channel_description": "Velg den distribusjonskanalen du ønsker ÃĨ motta versjonskunngjøringer for", "version_check_enabled_description": "Aktiver periodiske forespørsler til GitHub for ÃĨ sjekke etter nye utgivelser", "version_check_implications": "Versjonssjekkfunksjonen baserer seg pÃĨ periodisk kommunikasjon med {server}", "version_check_settings": "Versjonssjekk", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Tøm Bildecache", "advanced_settings_clear_image_cache_error": "Feiled ved tømming av bildecache", "advanced_settings_clear_image_cache_success": "Vellykket tømt {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Bruk denne innstillingen for ÃĨ filtrere mediefiler under synkronisering basert pÃĨ alternative kriterier. Bruk kun denne innstillingen dersom man opplever problemer med at applikasjonen ikke oppdager alle album.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTELT] Bruk alternativ enhet album synk filter", "advanced_settings_log_level_title": "LoggnivÃĨ: {level}", "advanced_settings_prefer_remote_subtitle": "Noen enheter er veldige trege til ÃĨ hente miniatyrbilder fra enheten. Aktiver denne innstillingen for ÃĨ hente de eksternt istedenfor.", "advanced_settings_prefer_remote_title": "Foretrekk eksterne bilder", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Proxy headere[EKSPERIMENTELL]", "advanced_settings_readonly_mode_subtitle": "Aktiverer skrivebeskyttet modus der bildene bare kan vises. Ting som ÃĨ velge flere bilder, dele, caste og slette er deaktivert. Aktiver/deaktiver skrivebeskyttet modus via brukerens avatar fra hovedskjermen", "advanced_settings_readonly_mode_title": "Skrivebeskyttet modus", + "advanced_settings_self_signed_ssl_subtitle": "Hopper over SSL sertifikatverifikasjon for server-endepunkt. PÃĨkrevet for selvsignerte sertifikater.", + "advanced_settings_self_signed_ssl_title": "Tillat selvsignerte SSL sertifikater [EKSPERIMENTELL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisk slette eller gjenopprette filer pÃĨ denne enheten hvis den handlingen har blitt gjort pÃĨ nettsiden", "advanced_settings_sync_remote_deletions_title": "Synk sletting fra nettsiden [EKSPERIMENTELT]", "advanced_settings_tile_subtitle": "Avanserte brukerinnstillinger", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Vil du virkelig slette albumet {album}?", "album_delete_confirmation_description": "Hvis dette albumet deles, vil andre brukere miste tilgangen til dette.", "album_deleted": "Album slettet", + "album_info_card_backup_album_excluded": "EKSKLUDERT", + "album_info_card_backup_album_included": "INKLUDERT", "album_info_updated": "Albuminformasjon oppdatert", + "album_leave": "Forlate album?", + "album_leave_confirmation": "Vil du virkelig forlate {album}?", "album_name": "Albumnavn", "album_options": "Albumalternativer", "album_remove_user": "Fjerne bruker?", "album_remove_user_confirmation": "Vil du virkelig fjerne {user}?", "album_search_not_found": "Ingen album ble funnet som traff ditt søk", + "album_selected": "Album valgt", "album_share_no_users": "Dette albumet er allerede delt med du har delt dette albumet med alle brukere, eller du ikke har noen brukere ÃĨ dele det med.", "album_summary": "Oppsummering av album", "album_updated": "Album oppdatert", "album_updated_setting_description": "Motta e-postvarsling nÃĨr et delt album fÃĨr nye filer", "album_upload_assets": "Last opp medier fra datamaskinen og legg til i album", + "album_user_left": "Forlot {album}", + "album_user_removed": "Fjernet {user}", + "album_viewer_appbar_delete_confirm": "Vil du virkelig slette dette albumet fra kontoen din?", "album_viewer_appbar_share_err_delete": "Kunne ikke slette albumet", + "album_viewer_appbar_share_err_leave": "Kunne ikke forlate albumet", + "album_viewer_appbar_share_err_remove": "Det oppstod et problem ved fjerning av elementer fra albumet", + "album_viewer_appbar_share_err_title": "Mislyktes ved endring av albumtittel", + "album_viewer_appbar_share_leave": "Forlat album", + "album_viewer_appbar_share_to": "Del til", "album_viewer_page_share_add_users": "Legg til brukere", "album_with_link_access": "La hvem som helst med lenken se bilder og personer i dette albumet.", "albums": "Album", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Standard sorteringsrekkefølge for bilder nÃĨr man lager et nytt album.", "albums_feature_description": "Samlinger av bilder som kan deles med andre brukere.", "albums_on_device_count": "Album pÃĨ enheten {count}", + "albums_selected": "{count, plural, one {# valgt album} other {# albumer valgt}}", "all": "Alle", "all_albums": "Alle album", "all_people": "Alle personer", "all_photos": "Alle bilder", "all_videos": "Alle videoer", "allow_dark_mode": "Tillat mørk modus", + "allow_edits": "Tillat redigering", "allow_public_user_to_download": "Tillat uautentiserte brukere ÃĨ laste ned", "allow_public_user_to_upload": "Tillat uautentiserte brukere ÃĨ laste opp", "allowed": "Tillatt", @@ -549,12 +528,14 @@ "always_keep": "Alltid behold", "always_keep_photos_hint": "Frigjør plass vil beholde alle bilder pÃĨ denne enheten.", "always_keep_videos_hint": "Frigjør plass til beholde alle videoer pÃĨ denne enheten.", + "anti_clockwise": "Mot klokken", "api_key": "API-nøkkel", "api_key_description": "Denne verdien vil vises kun Ên gang. Pass pÃĨ ÃĨ kopiere den før du lukker vinduet.", "api_key_empty": "API-nøkkelnavnet bør ikke vÃĻre tomt", "api_keys": "API-nøkler", "app_architecture_variant": "Variant (Arkitektur)", "app_bar_signout_dialog_content": "Vil du virkelig logge ut?", + "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logg ut", "app_download_links": "Nedlastingslinker for App", "app_settings": "Appinstillinger", @@ -565,19 +546,27 @@ "archive": "Arkiv", "archive_action_prompt": "{count} lagt til i arkivet", "archive_or_unarchive_photo": "Arkiver eller ta ut av arkivet", + "archive_page_no_archived_assets": "Ingen arkiverte elementer funnet", + "archive_page_title": "Arkiv ({count})", "archive_size": "Arkivstørrelse", "archive_size_description": "Konfigurer arkivstørrelsen for nedlastinger (i GiB)", "archived": "Arkivert", "archived_count": "{count, plural, other {Arkivert #}}", "are_these_the_same_person": "Er disse samme person?", "are_you_sure_to_do_this": "Vil du virkelig gjøre dette?", + "array_field_not_fully_supported": "Arrayfelter krever manuell JSON endring", + "asset_action_delete_err_read_only": "Kunne ikke slette element(er) med kun lese-rettighet, hopper over", + "asset_action_share_err_offline": "Kunne ikke hente offline element(er), hopper over", "asset_added_to_album": "Lagt til i album", "asset_adding_to_album": "Legger til i albumâ€Ļ", "asset_created": "Objekt opprettet", - "asset_day_count": "{date}: {count, plural, one {# mediefil} other {# mediefiler}}", "asset_description_updated": "Elementbeskrivelse har blitt oppdatert", + "asset_filename_is_offline": "Element {filename} er offline", + "asset_has_unassigned_faces": "Element har ikke-tilordnede ansikter", "asset_hashing": "Hasherâ€Ļ", "asset_list_group_by_sub_title": "Grupper etter", + "asset_list_layout_settings_dynamic_layout_title": "Dynamisk bildeorganisering", + "asset_list_layout_settings_group_automatically": "Automatisk", "asset_list_layout_settings_group_by": "Grupper bilder etter", "asset_list_layout_settings_group_by_month_day": "MÃĨned + dag", "asset_list_layout_sub_title": "Fordeling", @@ -587,28 +576,37 @@ "asset_not_found_on_device_ios": "Elementet ble ikke funnet pÃĨ enheten. Hvis du bruker iCloud, kan elementet vÃĻre utilgjengelig pÃĨ grunn av en feilaktig fil er lagret i iCloud", "asset_not_found_on_icloud": "Elementet ble ikke funnet pÃĨ iCloud. Elementet kan vÃĻre utilgjengelig fordi det ligger en feilaktig fil i iCloud", "asset_offline": "Fil utilgjengelig", - "asset_offline_description": "Dette eksterne elementet finnes ikke lenger pÃĨ disken. Vennligst kontakt din Immich-administrator for hjelp.", + "asset_offline_description": "Dette elementet er offline. Immich kan ikke finne dets possisjon. Vennligst pÃĨse at elementet er tilgjengelig og skann sÃĨ biblioteket pÃĨ nytt.", + "asset_restored_successfully": "Objekt(er) gjenopprettet", "asset_skipped": "Hoppet over", "asset_skipped_in_trash": "I papirkurven", + "asset_trashed": "Objekt slettet", "asset_troubleshoot": "Feilsøk element", "asset_uploaded": "Lastet opp", "asset_uploading": "Laster oppâ€Ļ", "asset_viewer_settings_subtitle": "Endre dine visningsinnstillinger for galleriet", "asset_viewer_settings_title": "Objektviser", "assets": "Filer", + "assets_added_count": "Lagt til {count, plural, one {# element} other {# elementer}}", "assets_added_to_album_count": "Lagt til {count, plural, one {# elementer} other {# element}} i album", - "assets_added_to_album_partial_count": "La til {successCount} av {totalCount} {totalCount, plural, one {element} other {elementer}} i albumet", "assets_added_to_albums_count": "Lagt til {assetTotal, plural, one {# asset} other {# assets}} til {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Objektet} other {Objektene}} kan ikke legges til i albumet", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} kan ikke legges til i noen av albumene", "assets_count": "{count, plural, one {# fil} other {# filer}}", + "assets_deleted_permanently": "{count} element(er) slettet permanent", + "assets_deleted_permanently_from_server": "{count} element(er) slettet permanent fra Immich-serveren", + "assets_downloaded_failed": "{count, plural, one {Nedlasting av # fil - {error} fil feilet} other {Nedlastede # filer - {error} filer feilet}}", + "assets_downloaded_successfully": "{count, plural, one {Nedlastet # fil vellykket} other {Nedlastede # filer vellykket}}", "assets_moved_to_trash_count": "Flyttet {count, plural, one {# element} other {# elementer}} til søppel", "assets_permanently_deleted_count": "Slettet {count, plural, one {# element} other {# elementer}} permanent", "assets_removed_count": "Slettet {count, plural, one {# element} other {# elementer}}", + "assets_removed_permanently_from_device": "{count} element(er) slettet permanent fra enheten din", "assets_restore_confirmation": "Vil du virkelig gjenopprette alle slettede eiendeler? Denne handlingen kan ikke angres! VÃĻr oppmerksom pÃĨ at frakoblede ressurser ikke kan gjenopprettes pÃĨ denne mÃĨten.", "assets_restored_count": "Gjenopprettet {count, plural, one {# element} other {# elementer}}", + "assets_restored_successfully": "{count} element(er) gjenopprettet", "assets_trashed": "{count} element(er) slettet", "assets_trashed_count": "Kastet {count, plural, one {# element} other {# elementer}}", + "assets_trashed_from_server": "{count} element(er) slettet fra Immich serveren", "assets_were_part_of_album_count": "{count, plural, one {Objektet} other {Objektene}} er allerede lagt til i albumet", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} allerede inkludert i albumet", "authorized_devices": "Autoriserte enheter", @@ -617,55 +615,108 @@ "autoplay_slideshow": "Autoavspilling av lysbildefremvisning", "back": "Tilbake", "back_close_deselect": "Tilbake, lukk eller fjern merking", + "background_backup_running_error": "Bakgrunnsbackup kjører, kan ikke starte manuell backup", "background_location_permission": "Bakgrunnstillatelse for plassering", "background_location_permission_content": "For ÃĨ bytte nettverk nÃĨr du kjører i bakgrunnen, mÃĨ Immich *alltid* ha presis posisjonstilgang slik at appen kan lese Wi-Fi-nettverkets navn", "background_options": "Bakgrunnsinnstillinger", "backup": "Sikkerhetskopiering", + "backup_album_selection_page_albums_device": "Album pÃĨ enhet ({count})", "backup_album_selection_page_albums_tap": "Trykk for ÃĨ inkludere, dobbelttrykk for ÃĨ ekskludere", "backup_album_selection_page_assets_scatter": "Objekter kan bli spredd over flere album. Album kan derfor bli inkludert eller ekskludert under sikkerhetskopieringen.", "backup_album_selection_page_select_albums": "Velg album", "backup_album_selection_page_selection_info": "Valginformasjon", + "backup_album_selection_page_total_assets": "Totalt antall unike elementer", "backup_albums_sync": "Synkronisering av sikkerhetskopialbum", + "backup_all": "Alle", + "backup_background_service_backup_failed_message": "Sikkerhetskopiering av elementer feilet. Prøver pÃĨ nyttâ€Ļ", "backup_background_service_complete_notification": "Backup av elementer fullført", + "backup_background_service_connection_failed_message": "Tilkobling til server feilet. Prøver pÃĨ nyttâ€Ļ", + "backup_background_service_current_upload_notification": "Laster opp {filename}", "backup_background_service_default_notification": "Ser etter nye elementerâ€Ļ", + "backup_background_service_error_title": "Feil under sikkerhetskopiering", "backup_background_service_in_progress_notification": "Sikkerhetskopierer elementerâ€Ļ", + "backup_background_service_upload_failure_notification": "Opplasting feilet {filename}", "backup_controller_page_albums": "Sikkerhetskopier album", + "backup_controller_page_background_app_refresh_disabled_content": "Aktiver bakgrunnsoppdatering i Innstillinger > Generelt > Bakgrunnsoppdatering for ÃĨ bruke sikkerhetskopiering i bakgrunnen.", + "backup_controller_page_background_app_refresh_disabled_title": "Bakgrunnsoppdateringer er deaktivert", + "backup_controller_page_background_app_refresh_enable_button_text": "GÃĨ til innstillinger", "backup_controller_page_background_battery_info_link": "Vis meg hvordan", "backup_controller_page_background_battery_info_message": "For at sikkerhetskopiering i bakgrunnen skal fungere optimalt, deaktiver enhver batterioptimalisering som kan begrense bakgrunnsaktiviteten til Immich.\n\nSiden dette er en enhetsspesifikk justering, mÃĨ du finne det i innstillingene pÃĨ enheten din.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Batterioptimalisering", + "backup_controller_page_background_charging": "Kun ved lading", + "backup_controller_page_background_configure_error": "Konfigurering av bakgrunnstjenesten feilet", "backup_controller_page_background_delay": "Forsink sikkerhetskopiering av nye elementer: {duration}", + "backup_controller_page_background_description": "Skru pÃĨ bakgrunnstjenesten for ÃĨ automatisk sikkerhetskopiere alle nye elementer uten ÃĨ mÃĨtte ÃĨpne appen", + "backup_controller_page_background_is_off": "Automatisk sikkerhetskopiering i bakgrunnen er deaktivert", + "backup_controller_page_background_is_on": "Automatisk sikkerhetskopiering i bakgrunnen er aktivert", + "backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten", + "backup_controller_page_background_turn_on": "Skru pÃĨ bakgrunnstjenesten", + "backup_controller_page_background_wifi": "Kun pÃĨ Wi-Fi", "backup_controller_page_backup": "Sikkerhetskopiere", "backup_controller_page_backup_selected": "Valgte: ", "backup_controller_page_backup_sub": "Opplastede bilder og videoer", + "backup_controller_page_created": "Opprettet: {date}", + "backup_controller_page_desc_backup": "SlÃĨ pÃĨ sikkerhetskopiering i forgrunnen for automatisk ÃĨ laste opp nye elementer til serveren nÃĨr du ÃĨpner appen.", "backup_controller_page_excluded": "Ekskludert: ", + "backup_controller_page_failed": "Mislyktes ({count})", + "backup_controller_page_filename": "Filnavn: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informasjon om sikkerhetskopi", "backup_controller_page_none_selected": "Ingen valgt", "backup_controller_page_remainder": "GjenstÃĨr", "backup_controller_page_remainder_sub": "GjenstÃĨende bilder og videoer ÃĨ laste opp fra utvalget", "backup_controller_page_server_storage": "Serverlagring", + "backup_controller_page_start_backup": "Start sikkerhetskopiering", + "backup_controller_page_status_off": "Automatisk sikkerhetskopiering i forgrunnen er av", + "backup_controller_page_status_on": "Automatisk sikkerhetskopiering i forgrunnen er pÃĨ", "backup_controller_page_storage_format": "{used} av {total} brukt", "backup_controller_page_to_backup": "Album som skal sikkerhetskopieres", "backup_controller_page_total_sub": "Alle unike bilder og videoer fra valgte album", + "backup_controller_page_turn_off": "SlÃĨ av sikkerhetskopiering i forgrunnen", + "backup_controller_page_turn_on": "SlÃĨ pÃĨ sikkerhetskopiering i forgrunnen", + "backup_controller_page_uploading_file_info": "Laster opp filinformasjon", + "backup_err_only_album": "Kunne ikke fjerne det eneste albumet", "backup_error_sync_failed": "Synkronisering feilet. Kunne ikke fortsette sikkerhetskopiering.", "backup_info_card_assets": "elementer", + "backup_manual_cancelled": "Avbrutt", + "backup_manual_in_progress": "Opplasting er allerede i gang. Prøv igjen om litt", + "backup_manual_success": "Vellykket", + "backup_manual_title": "Opplastingsstatus", "backup_options": "Backup innstillinger", + "backup_options_page_title": "Backupinnstillinger", + "backup_setting_subtitle": "Administrer opplastingsinnstillinger for bakgrunn og forgrunn", "backup_settings_subtitle": "HÃĨndter opplastingsinnstillinger", + "backup_upload_details_page_more_details": "Trykk for flere detaljer", "backward": "Bakover", - "battery_optimization_backup_reliability": "Å slÃĨ av batterioptimalisering kan gjøre at sikkerhetskopiering i bakgrunnen fungerer mer stabilt", "biometric_auth_enabled": "Biometrisk autentisering aktivert", "biometric_locked_out": "Du er lÃĨst ute av biometrisk verifisering", "biometric_no_options": "Ingen biometriske valg tilgjengelige", "biometric_not_available": "Biometrisk autentisering er ikke tilgjengelig pÃĨ denne enheten", + "birthdate_saved": "Fødselsdato er vellykket lagret", "birthdate_set_description": "Fødelsdatoen er brukt for ÃĨ beregne alderen til denne personen ved tidspunktet til bildet.", "blurred_background": "Uskarp bakgrunn", - "browse_templates": "Bla gjennom maler", "bugs_and_feature_requests": "Feil og funksjonsforespørsler", "build": "Bygg", - "build_image": "Byggbilde", + "build_image": "Lag Bilde", "bulk_delete_duplicates_confirmation": "Vil du virkelig slette {count, plural, one {# duplisert fil} other {# dupliserte filer}}? Dette vil beholde største filen fra hver gruppe og vil permanent slette alle andre duplikater. Du kan ikke angre denne handlingen!", "bulk_keep_duplicates_confirmation": "Vil du virkelig beholde {count, plural, one {# duplikat} other {# duplikater}}? Dette vil løse alle duplikatgrupper uten ÃĨ slette noe.", "bulk_trash_duplicates_confirmation": "Vil du virkelig ÃĨ slette {count, plural, one {# duplisert element} other {# dupliserte elementer}}? Dette vil beholde største filen fra hver gruppe, samt slette alle andre duplikater.", "buy": "Kjøp Immich", + "cache_settings_clear_cache_button": "Tøm buffer", + "cache_settings_clear_cache_button_title": "Tømmer app-ens buffer. Dette vil ha betydelig innvirkning pÃĨ appens ytelse inntil bufferen er gjenoppbygd.", + "cache_settings_duplicated_assets_clear_button": "TØM", + "cache_settings_duplicated_assets_subtitle": "Bilder og videoer som er ignorert av app'en", + "cache_settings_duplicated_assets_title": "Dupliserte elementer ({count})", + "cache_settings_statistics_album": "Bibliotekminiatyrbilder", + "cache_settings_statistics_full": "Originalbilder", + "cache_settings_statistics_shared": "Delte albumminiatyrbilder", + "cache_settings_statistics_thumbnail": "Miniatyrbilder", + "cache_settings_statistics_title": "Bufferbruk", + "cache_settings_subtitle": "Kontroller bufringsadferden til Immich-appen", + "cache_settings_tile_subtitle": "Kontroller lokal lagring", + "cache_settings_tile_title": "Lokal lagring", + "cache_settings_title": "Bufringsinnstillinger", "camera": "Kamera", "camera_brand": "Kameramerke", "camera_model": "Kameramodell", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Kunne ikke oppdatere beskrivelsen", "cast": "Strøm", "cast_description": "Konfigurer tilgjengelige cast-destinasjoner", - "change": "Endre", "change_date": "Endre dato", "change_description": "Endre beskrivelsen", "change_display_order": "Endre visningsrekkefølge", + "change_expiration_time": "Endre utløpstid", "change_location": "Endre sted", "change_name": "Endre navn", "change_name_successfully": "Navneendring vellykket", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Passordene stemmer ikke", "change_password_form_reenter_new_password": "Skriv nytt passord igjen", "change_pin_code": "Endre PIN-kode", + "change_trigger": "Endre utløser", + "change_trigger_prompt": "Er du sikker pÃĨ at du vil endre utløser? Dette vil fjerne alle eksisterende handlinger og filtre.", "change_your_password": "Endre passordet ditt", "changed_visibility_successfully": "Endret synlighet vellykket", "charging": "Lading", "charging_requirement_mobile_backup": "Bakgrunnsbackup krever at enheten lader", + "check_corrupt_asset_backup": "Sjekk etter korrupte backupelementer", + "check_corrupt_asset_backup_button": "Utfør sjekk", + "check_corrupt_asset_backup_description": "Kjør denne sjekken kun over Wi-Fi og nÃĨr alle elementer har blitt lastet opp. Denne sjekken kan ta noen minutter.", "check_logs": "Sjekk Logger", "checksum": "Sjekksum", - "choose": "Velg", "choose_matching_people_to_merge": "Velg personer som skal slÃĨs sammen", "city": "By", "cleanup_confirm_description": "Immich fant {count} mediefiler (opprettet før {date}) som er lastet opp til serveren. Vil du fjerne disse lokale kopiene fra denne enheten?", @@ -713,16 +768,17 @@ "cleanup_icloud_shared_albums_excluded": "iCloud delte albumer er ekskludert fra skanningen", "cleanup_no_assets_found": "Ingen opplastede mediefiler funnet som treffer dine søkekriterier. Frigjør plass kan kun fjerne objekter som har blitt sikkerhetskopiert", "cleanup_preview_title": "Mediefiler ÃĨ fjerne ({count})", - "cleanup_step3_description": "Søk etter bilder og videoer ved ÃĨ velge sluttdato og filter i søkeinnstillinger.", + "cleanup_step3_description": "Skann etter bilder og videoer ved ÃĨ velge sluttdato og filter i søkeinnstillinger.", "cleanup_step4_summary": "{count} mediefiler (opprettet før {date}= er plassert i kø for fjerning fra enheten. Bildene vil vÃĻre tilgjengelige fra Immich appen.", "cleanup_trash_hint": "For ÃĨ frigjøre lagringsplassen helt, ÃĨpne systemgalleri-appen og tøm papirkurven", "clear": "Tøm", "clear_all": "Tøm alt", "clear_all_recent_searches": "Fjern alle nylige søk", - "clear_failed_count": "Fjerning feilet {count}", "clear_file_cache": "Tøm filcache", "clear_message": "Fjern melding", "clear_value": "Fjern verdi", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Skriv inn passord", "client_cert_import": "Importer", "client_cert_import_success_msg": "Klient sertifikat er importert", "client_cert_invalid_msg": "Ugyldig sertifikat eller feil passord", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Klient sertifikat er fjernet", "client_cert_subtitle": "Støtter kun PKCS12 (.p12, .pfx) formater. Importering/Fjerning av sertifikater er kun mulig før innlogging", "client_cert_title": "SSL Klient sertifikat [EKSPERIMENTELL]", + "clockwise": "Med urviseren", "close": "Lukk", "collapse": "Trekk sammen", "collapse_all": "Kollaps alt", "color": "Farge", + "color_theme": "Fargetema", "command": "Kommando", "command_palette_prompt": "Finn sider, handlinger, eller kommandoer raskt", "command_palette_to_close": "ÃĨ lukke", @@ -747,7 +805,6 @@ "comments_are_disabled": "Kommentarer er deaktivert", "common_create_new_album": "Lag nytt album", "completed": "Fullført", - "configuration": "Oppsett", "confirm": "Bekreft", "confirm_admin_password": "Bekreft administratorpassord", "confirm_delete_face": "Vil du virkelig slette {name} sitt ansikt fra ativia?", @@ -762,16 +819,19 @@ "contain": "Inneholder", "context": "Kontekst", "continue": "Fortsett", - "control_bottom_app_bar_add_tags": "Legg til emneknagger", + "control_bottom_app_bar_create_new_album": "Lag nytt album", + "control_bottom_app_bar_delete_from_immich": "Slett fra Immich", "control_bottom_app_bar_delete_from_local": "Slett fra enhet", "control_bottom_app_bar_edit_location": "Endre lokasjon", "control_bottom_app_bar_edit_time": "Endre Dato og tid", + "control_bottom_app_bar_share_link": "Del Lenke", + "control_bottom_app_bar_share_to": "Del til", "control_bottom_app_bar_trash_from_immich": "Flytt til papirkurv", "copied_image_to_clipboard": "Bildet er kopiert til utklippstavlen.", "copied_to_clipboard": "Kopiert til utklippstavlen!", "copy_error": "Kopi feil", + "copy_file_path": "Kopier filplassering", "copy_image": "Kopier bilde", - "copy_json": "Kopier JSON", "copy_link": "Kopier lenke", "copy_link_to_clipboard": "Kopier lenke til utklippstavlen", "copy_password": "Kopier passord", @@ -789,6 +849,7 @@ "create_link_to_share": "Opprett delelink", "create_link_to_share_description": "La alle med lenken se de(t) valgte bildet/bildene", "create_new": "LAG NY", + "create_new_face": "Opprett nytt ansikt", "create_new_person": "Opprett ny person", "create_new_person_hint": "Tildel valgte eiendeler til en ny person", "create_new_user": "Opprett ny bruker", @@ -797,7 +858,7 @@ "create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER", "create_shared_album_page_share_select_photos": "Velg bilder", "create_shared_link": "Opprett delt lenke", - "create_tag": "Lag merke", + "create_tag": "Lag merkelapp", "create_tag_description": "Lag en ny tag. For undertag, vennligst fullfør hele stien til taggen, inkludert forovervendt skrÃĨstrek.", "create_user": "Opprett Bruker", "create_workflow": "Opprett arbeidsflyt", @@ -805,33 +866,37 @@ "created_at": "Laget", "creating_linked_albums": "Oppretter sammenkoblede album...", "crop": "BeskjÃĻr", + "crop_aspect_ratio_fixed": "Fikset", "crop_aspect_ratio_free": "Lagret", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Firkant", + "curated_object_page_title": "Ting", "current_device": "NÃĨvÃĻrende enhet", "current_pin_code": "NÃĨvÃĻrende PIN kode", "current_server_address": "NÃĨvÃĻrende serveradresse", "custom_date": "Egendefinert dato", "custom_locale": "Tilpasset lokalisering", "custom_locale_description": "Formater datoer, tid og tall basert pÃĨ det valgte sprÃĨket og regionen", + "custom_url": "Tilpasset URL", "cutoff_date_description": "Fjern bilder som er eldre ennâ€Ļ", "cutoff_day": "{count, plural, one {dag} other {dager}}", "cutoff_year": "{count, plural, one {ÃĨr} other {ÃĨr}}", + "daily_title_text_date": "E MMM. dd", + "daily_title_text_date_year": "E MMM. dddd, yyyy", "dark": "Mørk", "dark_theme": "Skift til mørkt tema", "date": "Dato", "date_after": "Dato etter", "date_and_time": "Dato og tid", "date_before": "Dato før", - "date_of_birth": "Fødselsdato", + "date_format": "d LLL. E y â€ĸ hh:mm", "date_of_birth_saved": "Fødselsdatoen ble lagret vellykket", "date_range": "DatoomrÃĨde", - "date_time_original": "Dato/Tid Orginal", "day": "Dag", "days": "Dager", "deduplicate_all": "De-dupliser alle", - "default_quality_subtitle": "Kvalitet brukt nÃĨr du trykker pÃĨ del. Trykk lenge pÃĨ deleknappen for ÃĨ velge hver gang.", - "default_share_quality": "Standard delingskvalitet", + "default_locale": "StandardsprÃĨk", + "default_locale_description": "Formater datoer og tall basert pÃĨ din nettlesers sprÃĨkinnstillinger", "delete": "Slett", "delete_action_confirmation_message": "Vil du virkelig slette dette elementet? Dette vil flytte elementet til papirkurven og vil gi deg beskjed om du vil slette det lokalt", "delete_action_prompt": "{count} slettet", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Disse elementene vil bli slettet permanent fra Immich og fra enheten din", "delete_dialog_alert_local": "Disse elementene vil bli permanent slettet fra enheten din, men vil fortsatt vÃĻre tilgjengelige fra Immich serveren", "delete_dialog_alert_local_non_backed_up": "Noen av elementene er ikke sikkerhetskopiert til Immich og vil bli permanent fjernet fra enheten din", + "delete_dialog_alert_remote": "Disse elementene vil bli permanent slettet fra Immich serveren", + "delete_dialog_ok_force": "Slett uansett", "delete_dialog_title": "Slett permanent", "delete_duplicates_confirmation": "Vil du virkelig slette disse duplikatene permanent?", "delete_face": "Slett ansik", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Vil du virkelig slette {tagName} tag?", "delete_user": "Slett bruker", "deleted_shared_link": "Slettet delt lenke", + "deletes_missing_assets": "Slett eiendeler som mangler fra disk", "description": "Beskrivelse", + "description_input_hint_text": "Legg til beskrivelse ...", + "description_input_submit_error": "Feil ved oppdatering av beskrivelse, sjekk loggen for flere detaljer", "deselect_all": "Avmerk alle", "details": "Detaljer", "direction": "Retning", "disable": "Deaktiver", "disabled": "Deaktivert", + "disallow_edits": "Forby redigering", "discord": "Discord", "discover": "Oppdag", "discovered_devices": "Oppdagede enheter", "dismiss_all_errors": "Avvis alle feil", + "dismiss_error": "Avvis feil", "display_options": "Visningsalternativer", "display_order": "Visningsrekkefølge", "display_original_photos": "Vis opprinnelige bilder", @@ -876,9 +948,11 @@ "documentation": "Dokumentasjon", "done": "Ferdig", "download": "Last ned", + "download_action_prompt": "Laster ned {count} elementer", "download_canceled": "Nedlasting avbrutt", "download_complete": "Nedlasting fullført", "download_enqueue": "Nedlasting satt i kø", + "download_error": "Nedlasting feilet", "download_failed": "Nedlasting feilet", "download_finished": "Nedlasting fullført", "download_include_embedded_motion_videos": "Innebygde videoer", @@ -888,15 +962,15 @@ "download_paused": "Nedlasting pauset", "download_settings": "Last ned", "download_settings_description": "Administrer innstillinger relatert til nedlasting av filer", + "download_started": "Nedlasting startet", + "download_sucess": "Nedlasting vellykket", + "download_sucess_android": "Objektet har blitt lastet ned til DCIM/Immich", "download_waiting_to_retry": "Venter pÃĨ nytt forsøk", "downloading": "Laster ned", "downloading_asset_filename": "Last ned {filename}", "downloading_from_icloud": "Laster ned fra iCloud", "downloading_media": "Laster ned media", - "drag_to_reorder": "Dra for ÃĨ endre rekkefølgen", "drop_files_to_upload": "Slipp filer hvor som helst for ÃĨ laste opp", - "duplicate": "Duplikat", - "duplicate_workflow": "Dupliser arbeidsflyt", "duplicates": "Duplikater", "duplicates_description": "Løs hver gruppe ved ÃĨ angi hvilke, hvis noen, er duplikater.", "duration": "Varighet", @@ -908,7 +982,9 @@ "edit_date_and_time": "Rediger dato og tid", "edit_date_and_time_action_prompt": "{count} dato og tid endret", "edit_date_and_time_by_offset": "Endre dato med forskyvning", + "edit_date_and_time_by_offset_interval": "Nytt datointervall: {from} - {to}", "edit_description": "Endre beskrivelse", + "edit_description_prompt": "Vennligst velg en ny beskrivelse:", "edit_exclusion_pattern": "Rediger utelukkelsesmønster", "edit_faces": "Rediger ansikter", "edit_key": "Rediger nøkkel", @@ -922,7 +998,10 @@ "edit_title": "Rediger tittel", "edit_user": "Rediger bruker", "edit_workflow": "Endre arbeidsflyt", - "editor": "Bildebehandling", + "editor": "Redaktør", + "editor_close_without_save_prompt": "Endringene vil ikke bli lagret", + "editor_close_without_save_title": "Lukk redigering?", + "editor_confirm_reset_all_changes": "Er du sikker pÃĨ at du vil tilbakestille alle endringer?", "editor_discard_edits_confirm": "Kast bort endringer", "editor_discard_edits_prompt": "Du har ulagrede endringer. Er du sikker pÃĨ at du vil kaste dem bort?", "editor_discard_edits_title": "Forkast endringer?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Skriv inn din PIN-kode", "enter_your_pin_code_subtitle": "Skriv inn din PIN-kode for ÃĨ fÃĨ tilgang til lÃĨst mappe", "error": "Feil", + "error_change_sort_album": "Mislyktes ved endring av sorteringsrekkefølge pÃĨ album", "error_delete_face": "Feil ved sletting av ansikt fra aktivia", + "error_getting_places": "Feil ved henting av steder", "error_loading_albums": "Feil ved lasting av albumer", "error_loading_image": "Feil ved lasting av bilde", "error_loading_partners": "Feil ved lasting av partnere: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Mislyktes med ÃĨ ta bort produkt nøkkel", "failed_to_reset_pin_code": "Kunne ikke tilbakestille PIN-koden", "failed_to_stack_assets": "Mislyktes med ÃĨ stable bilder", - "failed_to_tag_assets": "Feil ved merking av ressurser", "failed_to_unstack_assets": "Mislyktes med ÃĨ avstable bilder", "failed_to_update_notification_status": "Kunne ikke oppdatere varslingsstatusen", "incorrect_email_or_password": "Feil epost eller passord", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Legg til beskrivelse ...", "exif_bottom_sheet_description_error": "Feil ved oppdatering av beskrivelsen", + "exif_bottom_sheet_details": "DETALJER", + "exif_bottom_sheet_location": "PLASSERING", "exif_bottom_sheet_no_description": "Ingen beskrivelse", + "exif_bottom_sheet_people": "MENNESKER", + "exif_bottom_sheet_person_add_person": "Legg til navn", "exit_slideshow": "Avslutt lysbildefremvisning", "expand": "Utvid", "expand_all": "Utvid alle", + "experimental_settings_new_asset_list_subtitle": "Under utvikling", + "experimental_settings_new_asset_list_title": "Aktiver eksperimentell rutenettsvisning", + "experimental_settings_subtitle": "Bruk pÃĨ egen risiko!", + "experimental_settings_title": "Eksperimentelt", "expire_after": "UtgÃĨ etter", "expired": "UtgÃĨtt", "expires_date": "Utløper {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Eksporter som JSON", "export_database": "Eksporter database", "export_database_description": "Eksporter SQLite databasen", - "exposure_time": "Eksponeringstid", "extension": "Utvidelse", "external": "Ekstern", "external_libraries": "Eksterne Bibliotek", "external_network": "Eksternt nettverk", "external_network_sheet_info": "NÃĨr du ikke er pÃĨ det foretrukne Wi-Fi-nettverket, vil appen koble seg til serveren via den første av URL-ene nedenfor den kan nÃĨ, fra topp til bunn", - "f_number": "Blendertall", "face_unassigned": "Ikke tilordnet", "failed": "Mislyktes", "failed_count": "Feilede: {count}", "failed_to_authenticate": "Kunne ikke autentisere", - "failed_to_delete_file": "Feilet sletting av fil", "failed_to_load_assets": "Mislyktes med ÃĨ laste fil", "failed_to_load_folder": "Kunne ikke laste inn mappe", "favorite": "Favoritt", "favorite_action_prompt": "{count} lagt til i favoritter", "favorite_or_unfavorite_photo": "Merk som favoritt eller fjern som favoritt", "favorites": "Favoritter", + "favorites_page_no_favorites": "Ingen favorittelementer funnet", "feature_photo_updated": "Fremhevet bilde oppdatert", "features": "Funksjoner", + "features_in_development": "Funksjoner under utvikling", "features_setting_description": "Administrer funksjoner for appen", "file_name_or_extension": "Filnavn eller filtype", "file_name_text": "Filnavn", + "file_name_with_value": "Filnavn: {file_name}", "file_size": "Filstørrelse", "filename": "Filnavn", "filetype": "Filtype", "filter": "Filter", + "filter_description": "Betingelser for ÃĨ filtrere objekter", "filter_people": "Filtrer personer", "filter_places": "Filtrer steder", "filter_tags": "Filtrer emner", "filters": "Filtre", + "find_them_fast": "Finn dem raskt ved søking av navn", "first": "Første", "fix_incorrect_match": "Fiks feilaktig match", - "focal_length": "Brennvidde", "folder": "Mappe", "folder_not_found": "Fant ikke mappe", "folders": "Mapper", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Flytt sikkerhetskopierte bilder og videoer til enhetens papirkurv for ÃĨ frigjøre plass. Kopiene dine pÃĨ serveren forblir trygge.", "free_up_space_settings_subtitle": "Frigjør lagringsplass pÃĨ enheten", "full_path": "Full sti: {path}", - "full_path_or_folder": "Full sti eller mappe", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Denne funksjonen laster eksterne ressurser fra Google for ÃĨ fungere.", "general": "Generelt", @@ -1168,6 +1256,7 @@ "group_owner": "Grupper etter eiere", "group_places_by": "Grupper plasser etter...", "group_year": "Grupper etter ÃĨr", + "haptic_feedback_switch": "Aktivert haptisk tilbakemelding", "haptic_feedback_title": "Haptisk tilbakemelding", "has_quota": "Kvote", "hash_asset": "Hash elementer", @@ -1188,12 +1277,29 @@ "hide_schema": "Skjul skjema", "hide_text_recognition": "Skjul tekstgjenkjenning", "hide_unnamed_people": "Skjul mennesker uten navn", + "home_page_add_to_album_conflicts": "Lagt til {added} elementer til album {album}. {failed} elementer er allerede i albumet.", + "home_page_add_to_album_err_local": "Kunne ikke legge til lokale elementer til album enda, hopper over", + "home_page_add_to_album_success": "Lagt til {added} elementer til album {album}.", + "home_page_album_err_partner": "Kunne ikke legge til partnerelementer i album enda, hopper over", + "home_page_archive_err_local": "Kunne ikke arkivere lokale elementer enda, hopper over", + "home_page_archive_err_partner": "Kunne ikke arkivere partnerelementer, hopper over", "home_page_building_timeline": "Genererer tidslinjen", + "home_page_delete_err_partner": "Kunne ikke slette partnerelementer, hopper over", + "home_page_delete_remote_err_local": "Lokale elementer i fjernslettingsvalgene, hopper over", + "home_page_favorite_err_local": "Kunne ikke sette favoritt pÃĨ lokale elementer enda, hopper over", + "home_page_favorite_err_partner": "Kunne ikke merke partnerelementer som favoritt enda, hopper over", + "home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, velg et album (eller flere) for sikkerhetskopiering, slik at tidslinjen kan fylles med dine bilder og videoer", + "home_page_locked_error_local": "Kunne ikke flytte lokale elementer til lÃĨst mappe, hopper over", + "home_page_locked_error_partner": "Kunne ikke flytte partner elementer til lÃĨst mappe, hopper over", + "home_page_share_err_local": "Kunne ikke dele lokale elementer via link, hopper over", + "home_page_upload_err_limit": "Maksimalt 30 elementer kan lastes opp om gangen, hopper over", "host": "Vert", "hour": "Time", "hours": "Timer", "id": "ID", "idle": "Uvirksom", + "ignore_icloud_photos": "Ignorer iCloud bilder", + "ignore_icloud_photos_description": "Bilder som er lagret pÃĨ iCloud vil ikke lastes opp til Immich", "image": "Bilde", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} tatt pÃĨ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tatt med {person1} den {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1} og {person2} den {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, og {person3} den {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tatt i {city}, {country} med {person1}, {person2}, ok {additionalCount, number} andre den {date}", + "image_saved_successfully": "Bilde lagret", + "image_viewer_page_state_provider_download_started": "Nedlasting startet", + "image_viewer_page_state_provider_download_success": "Nedlasting vellykket", + "image_viewer_page_state_provider_share_error": "Delingsfeil", "immich_logo": "Immich Logo", "immich_web_interface": "Immich webgrensesnitt", "import_from_json": "Importer fra JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Individuell deling", "individual_shares": "Individuelle delinger", "info": "Info", - "integrity_checks": "Integritetssjekker", "interval": { "day_at_onepm": "Hver dag klokken 13:00", "hours": "Hver {hours, plural, one {time} other {{hours, number} timer}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Ugyldig datoformat", "invite_people": "Inviter Personer", "invite_to_album": "Inviter til album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Fetch kjørte {dateTime}", + "ios_debug_info_last_sync_at": "Siste synkronisering {dateTime}", + "ios_debug_info_no_processes_queued": "Ingen bakgrunnsprosesser i kø", + "ios_debug_info_no_sync_yet": "Ingen bakgrunnssynkroniseringsjobb har kjørt ennÃĨ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} bakgrunnsprosess lagt i kø} other {{count} bakgrunnsprosesser lagt i kø}}", + "ios_debug_info_processing_ran_at": "Behandlingen ble kjørt {dateTime}", "items_count": "{count, plural, one {# gjenstand} other {# gjenstander}}", "jobs": "Oppgaver", + "json_editor": "JSON endrer", + "json_error": "JSON feil", "keep": "Behold", "keep_albums": "Behold albumer", "keep_albums_count": "Beholder {count} {count, plural, one {album} other {albumer}}", @@ -1259,12 +1375,14 @@ "leave": "Forlat", "leave_album": "Forlat album", "lens_model": "Objektiv", - "less": "Mindre", "let_others_respond": "La andre respondere", "level": "NivÃĨ", "library": "Bibliotek", "library_add_folder": "Legg til mappe", "library_edit_folder": "Endre mappe", + "library_options": "Bibliotekalternativer", + "library_page_device_albums": "Album pÃĨ enheten", + "library_page_new_album": "Nytt album", "library_page_sort_asset_count": "Antall elementer", "library_page_sort_created": "Nylig opplastet", "library_page_sort_last_modified": "Sist endret", @@ -1274,14 +1392,11 @@ "light_theme": "Skift til lyst tema", "like": "Lik", "like_deleted": "Som slettede", - "link": "Lenke", "link_motion_video": "Koble bevegelsesvideo", "link_to_docs": "For mer informasjon, se dokumentasjonen.", "link_to_oauth": "Lenke til OAuth", "linked_oauth_account": "Lenket til OAuth-konto", "list": "Liste", - "live": "Direkte", - "load_more": "Last mer", "loading": "Laster", "loading_search_results_failed": "Klarte ikke ÃĨ laste inn søkeresultater", "local": "Lokal", @@ -1310,9 +1425,11 @@ "login": "Logg inn", "login_disabled": "Innlogging har blitt deaktivert", "login_form_api_exception": "API-feil. Sjekk URL-en til serveren og prøv igjen.", + "login_form_back_button_text": "Tilbake", "login_form_email_hint": "dinepost@epost.no", "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Serverendepunkt-URL", + "login_form_err_http": "Vennligst spesifiser http:// eller https://", "login_form_err_invalid_email": "Ugyldig e-postadresse", "login_form_err_invalid_url": "Ugyldig URL", "login_form_err_leading_whitespace": "Ledende mellomrom", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Feil ved innlogging, sjekk serverens URL, e-post og passord", "login_form_handshake_exception": "Det var et unntak for handshake med serveren. Aktiver støtte for selvsignerte sertifikater i innstillingene hvis du bruker et selvsignert sertifikat.", "login_form_password_hint": "passord", - "login_form_server_empty": "Skriv inn en server-URL", - "login_form_server_error": "Kunne ikke koble til server", - "login_has_been_disabled": "Innlogging har blitt deaktivert", + "login_form_save_login": "Forbli innlogget", + "login_form_server_empty": "Skriv inn en server-URL.", + "login_form_server_error": "Kunne ikke koble til server.", + "login_has_been_disabled": "Innlogging har blitt deaktivert.", "login_password_changed_error": "Det skjedde en feil ved oppdatering av passordet", "login_password_changed_success": "Passord oppdatert", "logout_all_device_confirmation": "Vil du virkelig logge ut av alle enheter?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Gjenoppretter database", "maintenance_description": "Immich er i Vedlikeholdsmodus.", "maintenance_end": "Avslutt vedlikeholdsmodus", + "maintenance_end_error": "Kunne ikke avslutte vedlikeholdsmodus.", "maintenance_logged_in_as": "Logged inn som {user}", "maintenance_restore_from_backup": "Gjenopprett fra sikkerhetskopi", "maintenance_restore_library": "Gjenopprett biblioteket", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Åpne innstillinger", "manage_media_access_subtitle": "Tillatt Immich appen ÃĨ hÃĨndtere og flytte mediefiler.", "manage_media_access_title": "Media hÃĨndteringstilgang", + "manage_shared_links": "HÃĨndter delte linker", "manage_sharing_with_partners": "Administrer deling med partnere", "manage_the_app_settings": "Administrer appinnstillingene", "manage_your_account": "Administrer kontoen din", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Administrer dine innloggede enheter", "manage_your_oauth_connection": "Administrer tilkoblingen din med OAuth", "map": "Kart", + "map_assets_in_bounds": "{count, plural, =0 {Ingen bilder i dette omrÃĨdet} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Kan ikke hente brukerens plassering", + "map_location_dialog_yes": "Ja", "map_location_picker_page_use_location": "Bruk dette stedet", "map_location_service_disabled_content": "Lokasjonstjeneste mÃĨ vÃĻre aktivert for ÃĨ vise elementer fra din nÃĨvÃĻrende lokasjon. Vil du aktivere det nÃĨ?", "map_location_service_disabled_title": "Lokasjonstjeneste deaktivert", - "map_marker_for_image": "Kartmarkør for bilde tatt i {city}, {country}", + "map_marker_for_images": "Kart makeringer for bilder tatt i {city}, {country}", "map_marker_with_image": "Kartmarkør med bilde", "map_no_location_permission_content": "Lokasjonstilgang er pÃĨkrevet for ÃĨ vise elementer fra din nÃĨvÃĻrende lokasjon. Vil du tillate det nÃĨ?", "map_no_location_permission_title": "Lokasjonstilgang avvist", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Siste {days} dager", "map_settings_date_range_option_year": "Siste ÃĨr", "map_settings_date_range_option_years": "Siste {years} ÃĨr", + "map_settings_dialog_title": "Kartinnstillinger", "map_settings_include_show_archived": "Inkluder arkiverte", "map_settings_include_show_partners": "Inkluder partner", "map_settings_only_show_favorites": "Vis kun favoritter", "map_settings_theme_settings": "Karttema", + "map_zoom_to_see_photos": "Zoom ut for ÃĨ se bilder", "mark_all_as_read": "Merk alle som lest", + "mark_as_read": "Merk som lest", "marked_all_as_read": "Merket alle som lest", "matches": "Samsvarende", "matching_assets": "Matchende elementer", - "media_chrome": { - "auto": "Automatisk", - "captions": "Undertekst", - "captions_off": "Av", - "closed_captions": "Teksting for hørselshemmede", - "decode_error": "Dekodingsfeil", - "disable_captions": "SlÃĨ av undertekst", - "enable_captions": "SlÃĨ pÃĨ undertekst", - "enter_fullscreen_mode": "GÃĨ i fullskjerm modus", - "exit_fullscreen_mode": "GÃĨ ut av fullskjerm modus", - "loop": "Gjenta", - "media_error_description": "En mediefeil fikk avspillingen til ÃĨ stanse. Mediet kan vÃĻre korrupt eller sÃĨ støtter ikke nettleseren din dette formatet.", - "media_loading": "innhold laster", - "mute": "Dempe lyd", - "network_error": "Nettverksfeil", - "network_error_description": "En nettverksfeil gjorde at nedlastningen av media sluttet.", - "not_supported_error": "Kilde ikke støttet", - "playback_rate": "Avspillingshastighet", - "playback_rate_current": "NÃĨvÃĻrende avspillingshastighet", - "playback_rate_value": "Avspillingshastighet {playbackRate}", - "playback_time": "Avspillingstid", - "quality": "Kvalitet", - "second": "sekund", - "seconds": "sekunder", - "time_value_of_total_time": "{currentTime} av {totalTime}", - "time_value_remaining": "{time} gjenvÃĻrende", - "unmute": "Opphev dempingen", - "unsupported_error_description": "En ikke-støttet feil oppstod. Serveren eller nettverket feilet, eller sÃĨ støtter ikke nettleseren din dette formatet.", - "video_not_loaded_unknown_time": "Video lastet ikke, ukjent tid.", - "video_player": "filmspiller", - "volume": "volum" - }, "media_type": "Mediatype", "memories": "Minner", "memories_all_caught_up": "Alt utført", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Vil du slÃĨ sammen disse personene? Denne handlingen kan ikke reverseres.", "merge_people_successfully": "SammenslÃĨing av personer var vellykket", "merged_people_count": "SammenslÃĨtt {count, plural, one {# person} other {# people}}", - "minFaces": "Minimum ansikter", - "minFaces_description": "Minimum antall gjenkjente ansikter for at en person skal vises", "minimize": "Minimer", "minute": "Minutt", "minutes": "Minutter", + "mirror_horizontal": "Horisontal", + "mirror_vertical": "Vertikal", "missing": "Mangler", "mobile_app": "Mobilapp", "mobile_app_download_onboarding_note": "Last ned den tilhørende mobilappen ved ÃĨ bruke følgende alternativer", "model": "Modell", - "modify_date": "Endre Dato", "month": "MÃĨned", + "monthly_title_text_date_format": "MMMM y", "more": "Mer", - "motion": "Bevegelse", "move": "Flytt", + "move_down": "Flytt ned", "move_off_locked_folder": "Flytt ut av lÃĨst mappe", "move_to": "Flytt til", "move_to_device_trash": "Flytt til enhetens søppelkasse", "move_to_lock_folder_action_prompt": "{count} lagt til i lÃĨst mappe", "move_to_locked_folder": "Flytt til lÃĨst mappe", "move_to_locked_folder_confirmation": "Disse bildene og videoene vil bli fjernet fra alle album, og kun tilgjengelige via den lÃĨste mappen", + "move_up": "Flytt opp", + "moved_to_archive": "Flyttet {count, plural, one {# element} other {# elementer}} til arkivet", + "moved_to_library": "Flyttet {count, plural, one {# element} other {# elementer}} til biblioteket", "moved_to_trash": "Flyttet til papirkurven", + "multiselect_grid_edit_date_time_err_read_only": "Kunne ikke endre dato pÃĨ element(er) med kun lese-rettigheter, hopper over", + "multiselect_grid_edit_gps_err_read_only": "Kunne ikke endre lokasjon pÃĨ element(er) med kun lese-rettigheter, hopper over", "mute_memories": "Demp minner", "my_albums": "Mine album", - "my_immich_description": "Kopier gjeldende side som en My Immich-lenke", - "my_immich_title": "My Immich-lenke", "name": "Navn", "name_or_nickname": "Navn eller kallenavn", "name_required": "Navn er pÃĨkrevd", @@ -1480,11 +1576,12 @@ "never": "aldri", "new_album": "Nytt Album", "new_api_key": "Ny API-nøkkel", - "new_feature": "Ny funksjon", + "new_date_range": "Nytt datointervall", "new_password": "Nytt passord", "new_person": "Ny person", "new_pin_code": "Ny PIN-kode", "new_pin_code_subtitle": "Dette er første gang du ÃĨpner den lÃĨste mappen. Lag en PIN-kode for ÃĨ sikre tilgangen til denne siden", + "new_timeline": "Ny tidslinje", "new_update": "Ny oppdatering", "new_user_created": "Ny bruker opprettet", "new_version_available": "NY VERSJON TILGJENGELIG", @@ -1492,6 +1589,7 @@ "next": "Neste", "next_memory": "Neste minne", "no": "Nei", + "no_actions_added": "Ingen hendelser lagt til enda", "no_albums_found": "Ingen albumer funnet", "no_albums_message": "Opprett et album for ÃĨ organisere bildene og videoene dine", "no_albums_with_name_yet": "Det ser ut som om det ikke finnes noen album med dette navnet enda.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Ingen caste-enheter oppdaget", "no_checksum_local": "Ingen sjekksum tilgjengelig - Kunne ikke hente lokale elementer", "no_checksum_remote": "Ingen sjekksum tilgjengelig - Kunne ikke hente eksterne elementer", + "no_configuration_needed": "Ingen konfigurasjon nødvendig", "no_devices": "Ingen autoriserte enheter", "no_duplicates_found": "Ingen duplikater ble funnet.", "no_exif_info_available": "Ingen Exif-informasjon tilgjengelig", "no_explore_results_message": "Last opp flere bilder for ÃĨ utforske samlingen din.", "no_favorites_message": "Legg til favoritter for ÃĨ finne dine beste bilder og videoer raskt", + "no_filters_added": "Ingen filtre lagt til enda", "no_libraries_message": "Opprett et eksternt bibliotek for ÃĨ se bildene og videoene dine", "no_local_assets_found": "Ingen lokale elementer funnet med denne sjekksummen", + "no_location_set": "Ingen lokasjon satt", "no_locked_photos_message": "Bilder og videoer i den lÃĨste mappen er skjult og vil ikke vises nÃĨr du blar i biblioteket.", "no_name": "Ingen navn", "no_notifications": "Ingen varsler", @@ -1518,19 +1619,14 @@ "no_results": "Ingen resultater", "no_results_description": "Prøv et synonym eller mer generelt søkeord", "no_shared_albums_message": "Opprett et album for ÃĨ dele bilder og videoer med personer i nettverket ditt", - "no_steps": "Ingen trinn lagt til enda", + "no_uploads_in_progress": "Ingen opplasting pÃĨgÃĨr", "none": "Ingen", "not_allowed": "Ikke tillatt", "not_available": "Ikke tilgjengelig", - "not_in_any_album": "Ikke i album", + "not_in_any_album": "Ikke i noe album", "not_selected": "Ikke valgt", - "not_set": "Ikke angitt", "notes": "Notater", "nothing_here_yet": "Ingenting her enda", - "notification_backup_reliability": "SlÃĨ pÃĨ notifikasjoner for mer stabil sikkerhetskopiering i bakgrunnen", - "notification_enabled_list_tile_content": "Immich bruker varsler for sikkerhetskopiering i bakgrunnen. Du kan administrere disse i enhetsinnstillingene dine.", - "notification_enabled_list_tile_open_button": "Åpne innstillinger", - "notification_enabled_list_tile_title": "Varsler aktivert", "notification_permission_dialog_content": "For ÃĨ aktivere notifikasjoner, gÃĨ til Innstillinger og velg tillat.", "notification_permission_list_tile_content": "Gi tilgang for ÃĨ aktivere notifikasjoner.", "notification_permission_list_tile_enable_button": "Aktiver notifikasjoner", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Obtainium konfigurator", "obtainium_configurator_instructions": "Bruk Obtainium for ÃĨ installere og oppdatere Android appen direkte fra Immich sin Github utgivelse. Opprett en API nøkkel og velg en variant for ÃĨ lage din Obtainium konfigurasjonslink", "ocr": "Tekstgjenkjenning", - "ocr_body": "Immich leser nÃĨ teksten i bildene dine, slik at du kan søke etter dem basert pÃĨ hva det stÃĨr i dem.", - "ocr_title": "Søk etter tekst i bildene dine", "official_immich_resources": "Offisielle Immich-ressurser", "offline": "Frakoblet", "offset": "Forskyving", @@ -1562,8 +1656,6 @@ "open": "Åpne", "open_calendar": "Åpne kalender", "open_in_browser": "Åpne i nettleser", - "open_in_immich_body": "Angi Immich som galleri-app pÃĨ Android for ÃĨ ÃĨpne bilder rett fra andre apper.", - "open_in_immich_title": "Åpner bilder i Immich", "open_in_map_view": "Åpne i kartvisning", "open_in_openstreetmap": "Åpne i OpenStreetMap", "open_the_search_filters": "Åpne søkefiltrene", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organiser til album", "organize_into_albums_description": "Plasser eksisterende bilder i album ved ÃĨ bruke synkroniseringsinnstillinger", "organize_your_library": "Organiser biblioteket ditt", - "orientation": "Orientering", "original": "original", "other": "Annet", "other_devices": "Andre enheter", + "other_entities": "Andre elementer", "other_variables": "Andre variabler", "owned": "Dine", "owner": "Eier", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Alle bildene og videoene dine unntatt de i arkivert og slettet tilstand", "partner_can_access_location": "Stedet der bildene dine ble tatt", "partner_list_user_photos": "{user}'s bilder", + "partner_list_view_all": "Vis alle", "partner_page_empty_message": "Dine bilder deles ikke med noen partner.", "partner_page_no_more_users": "Ingen flere brukere ÃĨ legge til", + "partner_page_partner_add_failed": "Klarte ikke ÃĨ legge til partner", "partner_page_select_partner": "Velg partner", + "partner_page_shared_to_title": "Delt med", "partner_page_stop_sharing_content": "{partner} vil ikke lenger ha tilgang til dine bilder.", "partner_sharing": "Partnerdeling", "partners": "Partnere", @@ -1609,6 +1704,8 @@ "people": "Personer", "people_edits_count": "Endret {count, plural, one {# person} other {# people}}", "people_feature_description": "Utforsk bilder og videoer gruppert etter mennesker", + "people_selected": "{count, plural, one {# person valgt} other {# personer valgt}}", + "people_sidebar_description": "Vis en lenke til Personer i sidepanelet", "permanent_deletion_warning": "Advarsel om permanent sletting", "permanent_deletion_warning_setting_description": "Vis en advarsel ved permanent sletting av filer", "permanently_delete": "Slett permanent", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Permanent slett {count, plural, one {# element} other {# elementer}}", "permission": "Tillatelse", "permission_empty": "Dine tillatelser burde ikke vÃĻre tomme", + "permission_onboarding_back": "Tilbake", + "permission_onboarding_continue_anyway": "Fortsett uansett", + "permission_onboarding_get_started": "Kom i gang", + "permission_onboarding_go_to_settings": "GÃĨ til innstillinger", + "permission_onboarding_permission_denied": "Tilgang avvist. For ÃĨ bruke Immich, tillat ÃĨ vise bilde og videoer i Innstillinger.", + "permission_onboarding_permission_granted": "Tilgang gitt! Du er i gang.", + "permission_onboarding_permission_limited": "Begrenset tilgang. For ÃĨ la Immich sikkerhetskopiere og hÃĨndtere galleriet, tillatt bilde- og video-tilgang i Innstillinger.", + "permission_onboarding_request": "Immich trenger tilgang til ÃĨ se dine bilder og videoer.", "person": "Person", "person_age_months": "{months, plural, one {# mÃĨned} other {# mÃĨneder}} gammel", "person_age_year_months": "1 ÃĨr, {months, plural, one {# mÃĨned} other {# mÃĨneder}} gammel", - "person_age_years": "{years, plural, one {# ÃĨr} other {# ÃĨr}} gammel", + "person_age_years": "{years, plural, other {# ÃĨr}} gammel", "person_birthdate": "Født den {date}", "person_hidden": "{name}{hidden, select, true { (skjult)} other {}}", "person_recognized": "Person gjenkjent", + "person_selected": "Person valgt", "photo_shared_all_users": "Det ser ut som om du deler bildene med alle brukere eller det er ingen brukere ÃĨ dele med.", "photos": "Bilder", "photos_and_videos": "Bilder & Videoer", + "photos_count": "{count, plural, one {{count, number} Bilde} other {{count, number} Bilder}}", "photos_from_previous_years": "Bilder fra tidliger ÃĨr", + "photos_only": "Kun bilder", "pick_a_location": "Velg et sted", "pick_custom_range": "Tilpasset omrÃĨde", "pick_date_range": "Velg ett datoomrÃĨde", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Foretrekk avspilling av originalvideoer istedenfor omkodede videoer. Hvis originalvideo ikke er kompatibel kan avspillingsproblemer oppstÃĨ.", "play_transcoded_video": "Spill av omkodet video", "please_auth_to_access": "Vennligst autentiser for ÃĨ fortsette", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Denne metoden kan filtrere hendelser og stoppe pÃĨfølgende steg basert pÃĨ betingelser", "port": "Port", "preferences_settings_subtitle": "Administrer appens preferanser", "preferences_settings_title": "Innstillinger", @@ -1665,11 +1771,11 @@ "privacy": "Privat", "profile": "Profil", "profile_drawer_app_logs": "Logg", + "profile_drawer_client_server_up_to_date": "Klient og server er oppdatert", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivebeskyttet modus er aktivert. Langttrykk pÃĨ brukerens avatarikon for ÃĨ avslutte.", "profile_image_of_user": "Profil bilde av {user}", "profile_picture_set": "Profilbildet er satt.", - "projection_type": "Projeksjonstype", "public_album": "Offentlige album", "public_share": "Offentlig deling", "purchase_account_info": "Støttespiller", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Støttespiller status", "purchase_individual_title": "Individuell", "purchase_input_suggestion": "Har du en produktnøkkel? Legg til denne under", + "purchase_license_subtitle": "Kjøp Immich for ÃĨ støtte den videre utviklingen av systemet", "purchase_lifetime_description": "Kjøp for livstid", "purchase_option_title": "KJØPSVALG", "purchase_panel_info_1": "Å lage Immich tar mye tid og energi, og nÃĨ har vi en fulltidsansatt utvikler som jobber med ÃĨ gjøre produktet sÃĨ godt vi kan. VÃĨrt oppdrag er for ÃĨpen-kildekode programvare og etisk virksomhets praktisk ÃĨ kunne bli bÃĻrekraftig inntekt for utviklere og for ÃĨ lage privat repekterte økesystem med mulighet for ÃĨ tilby skytjeneste.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Flyttet {count, plural, one {# element} other {# elementer}} til en ny person", "reassing_hint": "Tilordne valgte eiendeler til en eksisterende person", "recent": "Nylig", + "recent_albums": "Nylige album", "recent_searches": "Nylige søk", "recently_added": "Nylig lagt til", - "recently_added_body": "Se alt det nyeste innholdet ditt samlet pÃĨ Ên egen side.", - "recently_added_description": "Utforsk bildene og videoene dine sortert etter nÃĨr de ble lastet opp til Immich", "recently_added_page_title": "Nylig oppført", - "recently_added_title": "Nylig lagt til", "recently_taken": "Nylig tatt", + "recently_taken_page_title": "Nylig Tatt", "refresh": "Oppdater", "refresh_encoded_videos": "Oppdater kodete videoer", "refresh_faces": "Oppdater ansikter", "refresh_metadata": "Oppdater metadata", "refresh_thumbnails": "Oppdater miniatyrbilder", "refreshed": "Oppdatert", + "refreshes_every_file": "Oppdaterer alle filer", "refreshing_encoded_video": "Oppdaterer kodete video", "refreshing_faces": "Oppdaterer ansikter", "refreshing_metadata": "Oppdaterer metadata", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Vil du virkelig slette {count, plural, one {# element} other {# elementer}} fra den delte lenken?", "remove_assets_title": "Vil du fjerne eiendeler?", "remove_custom_date_range": "Fjern egendefinert datoperiode", - "remove_filter": "Fjern filter", + "remove_deleted_assets": "Fjern fra frakoblede filer", "remove_from_album": "Fjern fra album", "remove_from_album_action_prompt": "{count} fjernet fra albumet", "remove_from_favorites": "Fjern fra favoritter", @@ -1756,22 +1863,29 @@ "remove_memory": "Slett minne", "remove_photo_from_memory": "Slett bilde fra dette minne", "remove_tag": "Fjern tag", + "remove_url": "Fjern URL", "remove_user": "Fjern bruker", "removed_api_key": "Fjernet API-nøkkel: {name}", "removed_from_archive": "Fjernet fra arkivet", "removed_from_favorites": "Fjernet fra favoritter", "removed_from_favorites_count": "{count, plural, other {Removed #}} fra favoritter", "removed_memory": "Slettet minne", + "removed_photo_from_memory": "Slettet bilde fra minne", "removed_tagged_assets": "Fjern tag fra {count, plural, one {# element} other {# elementer}}", "rename": "Gi nytt navn", + "repair": "Reparer", + "repair_no_results_message": "Usporrede og savnede filer vil vises her", + "replace_with_upload": "Erstatte med opplasting", "repository": "Depot", "require_password": "Krev passord", + "require_user_to_change_password_on_first_login": "Krev at brukeren endrer passord ved første pÃĨlogging", "rescan": "Skann pÃĨ nytt", "reset": "Tilbakestill", "reset_password": "Tilbakestill passord", "reset_people_visibility": "Tilbakestill personsynlighet", "reset_pin_code": "Resett PINkode", "reset_pin_code_description": "Hvis du har glemt PIN-koden din, kan du kontakte serveradministratoren for ÃĨ fÃĨ den tilbakestilt", + "reset_pin_code_success": "PIN-koden er tilbakestilt", "reset_pin_code_with_password": "Du kan alltid tilbakestiller PIN-koden med passordet ditt", "reset_sqlite": "Reset SQLite Databasen", "reset_sqlite_clear_app_data": "Slett data", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Løste alle duplikater", "restore": "Gjenopprett", "restore_all": "Gjenopprett alle", + "restore_trash_action_prompt": "{count} gjenopprettet fra papirkurven", "restore_user": "Gjenopprett bruker", "restored_asset": "Gjenopprettet ressurs", "resume": "Fortsett", "resume_paused_jobs": "Fortsett {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "Prøv opplasting pÃĨ nytt", "review_duplicates": "GjennomgÃĨ duplikater", "review_large_files": "Se gjennom store filer", "role": "Rolle", @@ -1796,6 +1912,7 @@ "role_viewer": "Visning", "running": "Kjører", "save": "Lagre", + "save_to_gallery": "Lagre til galleriet", "saved": "Lagret", "saved_api_key": "Lagret API-nøkkel", "saved_profile": "Lagret profil", @@ -1806,10 +1923,9 @@ "scan": "Skann", "scan_all_libraries": "Skann alle biblioteker", "scan_library": "Skann", + "scan_settings": "Skanneinnstillinger", "scanning": "Skanner", "scanning_for_album": "Skanner etter album...", - "screencast_mode_description": "Vis indikatorer for tastatur- og musehendelser pÃĨ skjermen", - "screencast_mode_title": "Aktiver/deaktiver skjermbildefremvisning", "search": "Søk", "search_albums": "Søk i album", "search_by_context": "Søk etter kontekst", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Turdag i Sapa", "search_by_filename": "Søk etter filnavn og filtype", "search_by_filename_example": "f.eks. IMG_1234.JPG eller PNG", - "search_by_full_path": "Søk med full sti eller mappe", - "search_by_full_path_example": "/Johan/Prosjekt/3D_Printing/2026-07-01 - du kan søke etter Prosjekt, 3D, Printing 2026 osv.", "search_by_ocr": "Søk med tekstgjenkjenning", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Søk etter objektivmodell...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Velg lokasjon", "search_filter_media_type": "Medietype", "search_filter_media_type_title": "Velg medietype", + "search_filter_ocr": "Søk etter tekst i bilde", "search_filter_people_title": "Velg mennesker", "search_filter_star_rating": "Stjernerating", "search_filter_tags_title": "Velg emner", @@ -1847,19 +1962,31 @@ "search_no_people": "Ingen personer", "search_no_people_named": "Ingen personer med navnet \"{name}\"", "search_no_result": "Ingen resultater funnet, prøv ett annet søkeord eller kombinasjon", - "search_options": "Søkealternativer", + "search_options": "Søke alternativer", + "search_page_categories": "Kategorier", + "search_page_motion_photos": "Bevegelige bilder", + "search_page_no_objects": "Ingen elementinfo tilgjengelig", + "search_page_no_places": "Ingen stedsinformasjon er tilgjengelig", + "search_page_screenshots": "Skjermbilder", "search_page_search_photos_videos": "Søk etter dine bilder og videoer", + "search_page_selfies": "Selfier", + "search_page_things": "Ting", "search_page_view_all_button": "Vis alle", + "search_page_your_activity": "Din aktivitet", + "search_page_your_map": "Ditt kart", "search_people": "Søk personer", "search_places": "Søk steder", "search_rating": "Søk etter vurdering...", + "search_result_page_new_search_hint": "Nytt søk", "search_settings": "Søke instillinger", "search_state": "Søk etter fylke...", - "search_tags": "Søk merker...", + "search_suggestion_list_smart_search_hint_1": "Smartsøk er aktivert som standard, for ÃĨ søke etter metadata bruk syntaksen ", + "search_suggestion_list_smart_search_hint_2": "m:ditt-søkeord", + "search_tags": "Søk tags...", "search_timezone": "Søk etter tidssone....", "search_type": "Søk etter type", "search_your_photos": "Søk i dine bilder", - "searching_locales": "Søker regionalinnstillinger...", + "searching_locales": "Søker lokaler...", "second": "Sekund", "see_all_people": "Vis alle mennesker", "select": "Velg", @@ -1868,6 +1995,7 @@ "select_albums": "Velg albumer", "select_all": "Velg alle", "select_all_duplicates": "Velg alle duplikater", + "select_all_in": "Velg alt i {group}", "select_avatar_color": "Velg avatarfarge", "select_count": "{count, plural, one {Velg #} other {Valgt #}}", "select_cutoff_date": "Velg frist", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Velg fremhevet bilde", "select_from_computer": "Velg fra datamaskin", "select_keep_all": "Velg beholde alle", + "select_library_owner": "Velg bibliotekseier", "select_new_face": "Velg nytt ansikt", "select_people": "Velg mennesker", "select_person": "Valgt person", "select_person_to_tag": "Velg en person ÃĨ tagge", "select_photos": "Velg bilder", - "select_quality": "Velg kvalitet", "select_trash_all": "Velg ÃĨ flytte alt til papirkurven", + "select_user_for_sharing_page_err_album": "Mislyktes ved oppretting av album", "selected": "Valgt", "selected_count": "{count, plural, other {# valgt}}", "selected_gps_coordinates": "Valgte GPS-koordinater", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Last originalbildet", "setting_image_viewer_preview_subtitle": "Aktiver for ÃĨ laste et bilde av medium oppløsning. Deaktiver for ÃĨ enten direkte laste inn originalen eller kun benytte miniatyrbilde.", "setting_image_viewer_preview_title": "Last forhÃĨndsvisningsbilde", + "setting_image_viewer_title": "Bilder", "setting_languages_apply": "Bekreft", "setting_languages_subtitle": "Endre app-sprÃĨk", + "setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {duration}", + "setting_notifications_notify_hours": "{count} timer", + "setting_notifications_notify_immediately": "umiddelbart", "setting_notifications_notify_minutes": "{count} minutter", + "setting_notifications_notify_never": "aldri", "setting_notifications_notify_seconds": "{count} sekunder", + "setting_notifications_single_progress_subtitle": "Detaljert opplastingsinformasjon per element", + "setting_notifications_single_progress_title": "Vis detaljert status pÃĨ sikkerhetskopiering i bakgrunnen", "setting_notifications_subtitle": "Juster notifikasjonsinnstillinger", + "setting_notifications_total_progress_subtitle": "Total opplastingsstatus (fullført/totalt elementer)", + "setting_notifications_total_progress_title": "Vis status pÃĨ sikkerhetskopiering i bakgrunnen", "setting_video_viewer_auto_play_subtitle": "Automatisk avspilling av videoer nÃĨr de ÃĨpnes", "setting_video_viewer_auto_play_title": "Automatisk avspilling av videoer", "setting_video_viewer_looping_title": "Looping", "setting_video_viewer_original_video_subtitle": "NÃĨr det streames en video fra serveren, spill originalkvaliteten selv om en omkodet versjon finnes. Dette kan medføre buffring. Videoer som er lagret lokalt pÃĨ enheten spilles i originalkvalitet uavhengig av denne innstillingen.", "setting_video_viewer_original_video_title": "Tving original video", "settings": "Innstillinger", + "settings_require_restart": "Vennligst restart Immich for ÃĨ aktivere denne innstillingen", "settings_saved": "Innstillinger lagret", "setup_pin_code": "Sett opp en PINkode", "share": "Del", + "share_action_prompt": "Delte {count} elementer", + "share_add_photos": "Legg til bilder", + "share_assets_selected": "{count} valgt", "share_dialog_preparing": "Forbereder ...", "share_link": "Del link", - "share_original": "Bruk original (stor)", - "share_preview": "Bruk miniatyrbilde (lite)", - "share_quality_body": "Trykk og hold pÃĨ deleknappen for ÃĨ velge bildekvalitet før du deler.", - "share_quality_title": "Velg kvalitet for deling", "shared": "Delt", "shared_album_activities_input_disable": "Kommenterer er deaktivert", "shared_album_activity_remove_content": "Vil du slette denne aktiviteten?", "shared_album_activity_remove_title": "Slett aktivitet", "shared_album_section_people_action_error": "Feil ved fjerning/sletting fra album", + "shared_album_section_people_action_leave": "Fjern bruker fra album", + "shared_album_section_people_action_remove_user": "Fjern bruker fra album", "shared_album_section_people_title": "MENNESKER", "shared_by": "Delt av", "shared_by_user": "Delt av {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Lastet opp", "shared_link_app_bar_title": "Delte linker", "shared_link_clipboard_copied_massage": "Kopiert til utklippslisten", + "shared_link_clipboard_text": "Link: {link}\nPassord: {password}", "shared_link_create_error": "Feil ved oppretting av delbar link", - "shared_link_custom_url_description": "Gi denne dele-lenken en brukerdefinert URL", - "shared_link_custom_url_title": "Egendefinert URL", - "shared_link_custom_url_warning": "Advarsel: Et egendefinert URL-navn med en skrÃĨstrek oppfører seg kanskje ikke som forventet.", + "shared_link_custom_url_description": "FÃĨ tilgang til denne delte lenken med en egendefinert URL", "shared_link_edit_description_hint": "Endre delebeskrivelse", "shared_link_edit_expire_after_option_day": "1 dag", "shared_link_edit_expire_after_option_days": "{count} dager", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Delt med {partner}", "sharing": "Deling", "sharing_enter_password": "Vennligst skriv inn passordet for ÃĨ se denne siden.", + "sharing_page_album": "Delte album", + "sharing_page_description": "Lag delte album for ÃĨ dele bilder og videoer med personer i nettverket ditt.", + "sharing_page_empty_list": "TOM LISTE", + "sharing_sidebar_description": "Vis en lenke til Deling i sidepanelet", + "sharing_silver_appbar_create_shared_album": "Lag delt album", + "sharing_silver_appbar_share_partner": "Del med partner", "shift_to_permanent_delete": "trykk ⇧ for ÃĨ slette eiendeler permanent", "show_album_options": "Vis albumalternativer", "show_albums": "Vis album", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Vis i tidslinje", "show_in_timeline_setting_description": "Vis bilder og videoer fra denne brukeren i tidslinjen din", "show_keyboard_shortcuts": "Vis tastatursnarveier", - "show_less": "Vis mindre", "show_metadata": "Vis metadata", - "show_more_fields": "{count, plural, one {Vis # felt til} other {Vis # flere felter}}", "show_or_hide_info": "Vis eller skjul informasjon", "show_password": "Vis passord", "show_person_options": "Vis personalternativer", @@ -2005,7 +2148,6 @@ "show_schema": "Vis skjema", "show_search_options": "Vis søkealternativer", "show_shared_links": "Vis delte lenker", - "show_slideshow_metadata_overlay": "Vis bildeinformasjonsoverlegg", "show_slideshow_transition": "Vis overgang til lysbildefremvisning", "show_supporter_badge": "Supportermerke", "show_supporter_badge_description": "Vis et supportermerke", @@ -2017,45 +2159,35 @@ "sign_out": "Logg ut", "sign_up": "Registrer deg", "size": "Størrelse", - "skip": "Hopp over", "skip_to_content": "GÃĨ til innhold", "skip_to_folders": "Hopp til mapper", "skip_to_tags": "Hopp til tagger", "slideshow": "Lysbildefremvisning", - "slideshow_body": "Len deg tilbake og se bildene dine som lysbildefremvisning i fullskjerm.", - "slideshow_metadata_overlay_mode": "Overleggsinnhold", - "slideshow_metadata_overlay_mode_description_only": "Bare beskrivelse", - "slideshow_metadata_overlay_mode_full": "Full", "slideshow_repeat": "Gjenta lysbildefremvisning", "slideshow_repeat_description": "GÃĨ tilbake til begynnelsen nÃĨr lysbildeserien er slutt", "slideshow_settings": "Lysbildefremvisning innstillinger", - "slideshow_title": "Lysbildevisning", - "smart_album": "Smartalbum", - "some_assets_already_have_a_location_warning": "Noen av de valgte objektene har allerede en lokasjon", "sort_albums_by": "Sorter album etter...", "sort_created": "Dato opprettet", "sort_items": "Antall enheter", "sort_modified": "Dato modifisert", + "sort_newest": "Nyeste bilde", "sort_oldest": "Eldste bilde", "sort_people_by_similarity": "Sorter personer etter likhet", "sort_recent": "Nyeste bilde", "sort_title": "Tittel", "source": "Kilde", - "stack": "Stabel", - "stack_action_prompt": "{count} stablet", + "stack": "Stable", + "stack_action_prompt": "{count} stakket", "stack_duplicates": "Stable duplikater", + "stack_select_one_photo": "Velg hovedbilde for bildestabbel", "stack_selected_photos": "Stable valgte bilder", - "stacked_assets_count": "Stablet {count, plural, one {# element} other {# elementer}}", + "stacked_assets_count": "Stable {count, plural, one {# element} other {# elementer}}", "stacktrace": "Stakkspor", "start": "Start", "start_date": "Startdato", "start_date_before_end_date": "Startdato mÃĨ vÃĻre før sluttdato", "state": "Fylke", "status": "Status", - "step_delete": "Slett trinn", - "step_delete_confirm": "Er du sikker pÃĨ at du vil slette dette trinnet?", - "step_details": "Detaljer om trinn", - "steps": "Trinn", "stop_casting": "Stopp casting", "stop_motion_photo": "Stopmotionbilde", "stop_photo_sharing": "Stopp deling av bildene dine?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Bytt retning pÃĨ sammenslÃĨingen", "sync": "Synkroniser", "sync_albums": "Synkroniser album", + "sync_albums_manual_subtitle": "Synkroniser alle opplastede videoer og bilder til det valgte backupalbumet", "sync_local": "Synkroniser lokalt", "sync_remote": "Synkroniser eksternt", "sync_status": "Synkroniseringsstatus", "sync_status_subtitle": "Vis og hÃĨndter synkronisering", "sync_upload_album_setting_subtitle": "Opprett og last opp dine bilder og videoer til det valgte albumet pÃĨ Immich", - "system_theme": "Systemtema", - "system_theme_command_description": "Bruk systemtema ({value})", "tag": "Tagg", "tag_assets": "Merk ressurser", "tag_created": "Lag merke: {tag}", "tag_face": "Tagg ansikt", "tag_feature_description": "Bla gjennom bilder og videoer gruppert etter logiske merke-emner", + "tag_not_found_question": "Finner du ikke en merke? Opprett en nytt merke.", "tag_people": "Tag personer", "tag_updated": "Oppdater merke: {tag}", "tagged_assets": "Merket {count, plural, one {# element} other {# elementer}}", @@ -2097,15 +2229,20 @@ "text_recognition": "Tekstgjenkjenning", "theme": "Tema", "theme_selection": "Temavalg", - "theme_selection_description": "Sett tema automatisk til lyst eller mørkt basert pÃĨ nettleserens systeminnstilling", + "theme_selection_description": "Automatisk sett tema til lys eller mørk basert pÃĨ nettleserens systeminnstilling", "theme_setting_asset_list_storage_indicator_title": "Vis lagringsindiaktor pÃĨ elementer i fotorutenettet", "theme_setting_asset_list_tiles_per_row_title": "Antall elementer per rad ({count})", "theme_setting_colorful_interface_subtitle": "Angi primÃĻrfarge til bakgrunner.", "theme_setting_colorful_interface_title": "Fargefullt grensesnitt", + "theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten pÃĨ bilder i detaljvisning", + "theme_setting_image_viewer_quality_title": "Kvalitet pÃĨ bildevisning", "theme_setting_primary_color_subtitle": "Velg en farge for primÃĻrhendelser og etterfølgende.", "theme_setting_primary_color_title": "PrimÃĻrfarge", "theme_setting_system_primary_color_title": "Bruk systemfarge", "theme_setting_system_theme_switch": "Automatisk (følg systeminnstillinger)", + "theme_setting_theme_subtitle": "Velg app-ens temainnstilling", + "theme_setting_three_stage_loading_subtitle": "Tre-trinns innlasting kan øke lasteytelsen, men forÃĨrsaker betydelig høyere nettverksbelastning", + "theme_setting_three_stage_loading_title": "Aktiver tre-trinns innlasting", "then": "Da", "they_will_be_merged_together": "De vil bli slÃĨtt sammen", "third_party_resources": "Tredjeparts Ressurser", @@ -2131,17 +2268,23 @@ "trash_all": "Slett alt", "trash_count": "Slett {count, number}", "trash_delete_asset": "Slett element", + "trash_emptied": "Søppelbøtte Tømt", "trash_no_results_message": "Her vises bilder og videoer som er flyttet til papirkurven.", "trash_page_delete_all": "Slett alt", + "trash_page_empty_trash_dialog_content": "Vil du Tømme papirkurven? Objektene vil bli permanent fjernet fra Immich", "trash_page_info": "Objekter i papirkurven blir permanent fjernet etter {days} dager", + "trash_page_no_assets": "Ingen forkastede elementer", + "trash_page_restore_all": "Gjenopprett alt", + "trash_page_select_assets_btn": "Velg elementer", + "trash_page_title": "Søppelbøtte ({count})", "trashed_items_will_be_permanently_deleted_after": "Elementer i papirkurven vil bli permanent slettet etter {days, plural, one {# dag} other {# dager}}.", "trigger": "Utløser", - "trigger_asset_metadata_extraction": "Hent ut metadata fra objekter", - "trigger_asset_metadata_extraction_description": "Trigges nÃĨr EXIF metadata fra ett objekt blir uthentet", - "trigger_asset_uploaded": "Last opp objekt", + "trigger_asset_uploaded": "Objekt lastet opp", "trigger_asset_uploaded_description": "Utløser nÃĨr ett nytt objekt er lastet opp", + "trigger_description": "En hendelse som utløser arbeidsflyten", "trigger_person_recognized": "Person gjenkjent", "trigger_person_recognized_description": "Utløses nÃĨr en person blir gjenkjent", + "trigger_type": "Utløsertype", "troubleshoot": "Feilsøk", "type": "Type", "unable_to_change_pin_code": "Klarte ikke ÃĨ endre PIN-kode", @@ -2157,7 +2300,6 @@ "unknown": "Ukjent", "unknown_country": "Ukjent Land", "unknown_date": "Ukjent dato", - "unknown_schema": "Ukjent skjema", "unknown_year": "Ukjent ÃĨr", "unlimited": "Ubegrenset", "unlink_motion_video": "Koble fra bevegelsesvideo", @@ -2170,20 +2312,23 @@ "unsaved_change": "Ulagrede endringer", "unselect_all": "Fjern alle valg", "unselect_all_duplicates": "Fjern markeringen av alle duplikater", - "unstack": "Avstable", + "unselect_all_in": "Fjern velging av alle i {group}", + "unstack": "avstable", "unstack_action_prompt": "{count} ustakket", - "unstacked_assets_count": "Avstablet {count, plural, one {# element} other {# elementer}}", + "unstacked_assets_count": "Ikke stablet {count, plural, one {# element} other {# elementer}}", "unsupported_field_type": "Ustøttede felttyper", "unsupported_file_type": "Filen {file} kan ikke lastes opp fordi filtypen {type} ikke støttes.", "untagged": "Umerket", + "untitled_workflow": "Arbeidsflyt uten navn", "up_next": "Neste", "update_location_action_prompt": "Oppdater plasseringen til {count} valgte elementer med:", "updated_at": "Oppdatert", "updated_password": "Passord oppdatert", "upload": "Last opp", "upload_concurrency": "Samtidig opplastning", - "upload_day_count": "{date}: {count, plural, one {# opplasting} other {# opplastinger}}", "upload_details": "Opplastingsdetaljer", + "upload_dialog_info": "Vil du utføre backup av valgte element(er) til serveren?", + "upload_dialog_title": "Last opp element", "upload_error_with_count": "Opplastningsfeil for {count, plural, one {# element} other {# elementer}}", "upload_errors": "Opplasting fullført med {count, plural, one {# error} other {# errors}}, oppdater siden for ÃĨ se nye opplastingsressurser.", "upload_finished": "Opplasting fullført", @@ -2193,21 +2338,16 @@ "upload_status_errors": "Feil", "upload_status_uploaded": "Opplastet", "upload_success": "Opplasting vellykket, oppdater siden for ÃĨ se nye opplastninger.", - "upload_to_album_body": "For brukere som ikke benytter manuell opplasting, kan du nÃĨ velge ÃĨ legge til lokale bilder direkte i et album samtidig som du laster dem opp. Du slipper dermed ÃĨ laste dem opp først for sÃĨ ÃĨ legge dem til i et album etterpÃĨ.", - "upload_to_album_title": "Last opp direkte til et album", "upload_to_immich": "Last opp til Immich ({count})", "uploading": "Laster opp", "uploading_media": "Laster opp media", - "uploads": "Opplastinger", - "uploads_count": "{count, plural, one {# opplasting} other {# opplastinger}}", "url": "URL", "usage": "Bruk", "use_biometric": "Bruk biometri", - "use_browser_locale": "Bruk nettleserens regionalinnstillinger", - "use_browser_locale_description": "Formater datoer, klokkeslett, og tall basert pÃĨ nettleserens lokale", + "use_browser_locale": "Bruk nettleser lokale", + "use_browser_locale_description": "Formater datoer, tider, og tall basert pÃĨ nettleserens lokale", "use_current_connection": "Bruk nÃĨvÃĻrende tilkobling", "use_custom_date_range": "Bruk egendefinert datoperiode i stedet", - "use_template": "Bruk mal", "user": "Bruker", "user_has_been_deleted": "Denne brukeren har blitt slettet.", "user_id": "Bruker ID", @@ -2217,6 +2357,7 @@ "user_privacy": "Personverninnstillinger", "user_purchase_settings": "Kjøpe", "user_purchase_settings_description": "Administrer dine kjøp", + "user_role_set": "Sett {user} som {role}", "user_usage_detail": "Detaljer av brukernes forbruk", "user_usage_stats": "Kontobruksstatistikk", "user_usage_stats_description": "Vis kontobruksstatistikk", @@ -2226,18 +2367,19 @@ "utilities": "Verktøy", "validate": "Valider", "validate_endpoint_error": "Skriv inn en gyldig URL", + "validation_error": "valideringsfeil", "variables": "Variabler", "version": "Versjon", "version_announcement_closing": "Din venn, Alex", "version_announcement_message": "Hei! En ny versjon av Immich er tilgjengelig. Vennligst ta deg tid til ÃĨ lese utgivelsesnotatene for ÃĨ sikre at oppsettet ditt er oppdatert for ÃĨ forhindre feilkonfigurasjoner, spesielt hvis du bruker WatchTower eller en annen mekanisme som hÃĨndterer oppdatering av Immich-forekomsten din automatisk.", "version_history": "Versjonshistorikk", - "version_history_item": "Installerte {version} den {date}", + "version_history_item": "Installert {version} den {date}", "video": "Video", "video_hover_setting": "Spill av forhÃĨndsvisining mens du holder over musepekeren", "video_hover_setting_description": "Spill av forhÃĨndsvisning mens en musepeker er over elementet. Selv nÃĨr den er deaktivert, kan avspilling startes ved ÃĨ holde musepekeren over avspillingsikonet.", - "video_quality": "Videokvalitet", "videos": "Videoer", "videos_count": "{count, plural, one {# Video} other {# Videoer}}", + "videos_only": "Kun videoer", "view": "Vis", "view_album": "Vis album", "view_all": "Vis alle", @@ -2246,29 +2388,29 @@ "view_details": "Vis detaljer", "view_in_timeline": "Vis i tidslinje", "view_link": "Vis lenke", + "view_links": "Vis lenker", "view_name": "Vis", "view_next_asset": "Vis neste fil", "view_previous_asset": "Vis forrige fil", "view_qr_code": "Vis QR-kode", "view_similar_photos": "Vis lignende bilder", "view_stack": "Vis stabel", - "viewer_remove_from_stack": "Fjern fra stabel", + "view_user": "Vis bruker", + "viewer_remove_from_stack": "Fjern fra stabling", + "viewer_stack_use_as_main_asset": "Bruk som hovedelement", + "viewer_unstack": "avstable", "visibility": "Synlighet", "visibility_changed": "Synlighet endret for {count, plural, one {# person} other {# people}}", "visual": "Visuell", + "visual_builder": "Visuell oppbygging", "waiting": "Venter", "waiting_count": "Ventende: {count}", "warning": "Advarsel", "week": "Uke", "welcome": "Velkommen", "welcome_to_immich": "Velkommen til Immich", - "whats_new": "Hva er nytt", - "whats_new_settings_subtitle": "Se hva som er nytt i Immich", - "whats_new_version": "Versjon {version}", - "when": "NÃĨr", "width": "Bredde", "wifi_name": "Wi-Fi-navn", - "workflow": "Arbeidsflyt", "workflow_delete_prompt": "Er du sikker pÃĨ at du vil slette denne arbeidsflyten?", "workflow_deleted": "Arbeidsflyt slettet", "workflow_description": "Beskrivelse av arbeidsflyt", @@ -2278,17 +2420,17 @@ "workflow_name": "Navn pÃĨ arbeidsflyt", "workflow_navigation_prompt": "Er du sikker pÃĨ at du vil forlate uten ÃĨ lagre endringene?", "workflow_summary": "Oppsummering av arbeidsflyt", - "workflow_templates": "Arbeidsflytmaler", "workflow_update_success": "Vellykket oppdatering av arbeidsflyt", + "workflow_updated": "Arbeidsflyt oppdatert", "workflows": "Arbeidsflyter", "workflows_help_text": "Arbeidsflyter automatiserer hendelser pÃĨ dine mediefiler basert pÃĨ dine utløsere og filtre", "wrong_pin_code": "Feil PIN-kode", - "x_of_total": "{x} av {total}", "year": "År", "years_ago": "{years, plural, one {# ÃĨr} other {# ÃĨr}} siden", "yes": "Ja", "you_dont_have_any_shared_links": "Du har ingen delte lenker", "your_wifi_name": "Ditt Wi-Fi-navn", "zero_to_clear_rating": "Trykk 0 for ÃĨ fjerne vurdering", - "zoom_image": "Zoom Bilde" + "zoom_image": "Zoom Bilde", + "zoom_to_bounds": "Zoom til grensene" } diff --git a/i18n/ne.json b/i18n/ne.json deleted file mode 100644 index d528718015..0000000000 --- a/i18n/ne.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "about": "ā¤Ŧā¤žā¤°āĨ‡", - "account": "ā¤–ā¤žā¤¤ā¤ž", - "account_settings": "ā¤–ā¤žā¤¤ā¤ž ⤏āĨ‡ā¤Ÿā¤ŋ⤙", - "acknowledge": "⤏āĨā¤ĩāĨ€ā¤•ā¤žā¤°", - "action": "ā¤•ā¤žā¤°āĨā¤¯", - "action_description": "ā¤›ā¤žā¤¨ā¤ŋ⤝āĨ‡ā¤•āĨ‹ ⤚ā¤ŋā¤œā¤Žā¤ž ā¤¸ā¤žā¤ŽāĨā¤šā¤ŋ⤕ ā¤•ā¤žā¤°āĨā¤¯", - "active": "ā¤•ā¤žā¤°āĨā¤¯ā¤šā¤°āĨ‚", - "activity": "⤗⤤ā¤ŋā¤ĩā¤ŋ⤧ā¤ŋ", - "add": "ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_a_description": "ā¤Ĩā¤Ē ā¤ĩā¤ŋā¤ĩ⤰⤪", - "add_a_location": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_a_name": "ā¤¨ā¤žā¤Ž ā¤šā¤žā¤˛āĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_a_title": "ā¤ļāĨ€ā¤°āĨā¤ˇā¤• ā¤šā¤žā¤˛āĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_action": "ā¤•ā¤žā¤°āĨā¤¯ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_assets": "ā¤ĢāĨ‹ā¤ŸāĨ‹ā¤šā¤°āĨ‚ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_birthday": "⤜⤍āĨā¤Žā¤Ļā¤ŋ⤍ ā¤šā¤žā¤˛āĨā¤¨āĨā¤šāĨ‹ā¤¸", - "add_endpoint": "⤅⤍āĨā¤¤ā¤ŋā¤Ž ā¤Ŧā¤ŋ⤍āĨā¤ĻāĨ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_exclusion_pattern": "ā¤Ŧā¤šā¤ŋ⤎āĨā¤•⤰⤪ ā¤ĸā¤žā¤ā¤šā¤ž ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_location": "⤏āĨā¤Ĩā¤žā¤¨ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_partner": "ā¤¸ā¤žā¤āĨ‡ā¤Ļā¤žā¤° ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ", - "add_step": "⤚⤰⤪ ā¤Ĩā¤ĒāĨā¤¨āĨā¤šāĨ‹ā¤¸āĨ" -} diff --git a/i18n/nl.json b/i18n/nl.json index 8907985ad1..951638d470 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -2,7 +2,7 @@ "about": "Over", "account": "Account", "account_settings": "Accountinstellingen", - "acknowledge": "Begrepen", + "acknowledge": "Bevestigen", "action": "Actie", "action_common_update": "Bijwerken", "action_description": "Een groep acties om uit te voeren op de gefilterde items", @@ -10,31 +10,39 @@ "active": "Actief", "active_count": "Actief: {count}", "activity": "Activiteit", + "activity_changed": "Activiteit is {enabled, select, true {ingeschakeld} other {uitgeschakeld}}", "add": "Toevoegen", "add_a_description": "Beschrijving toevoegen", - "add_a_location": "Locatie toevoegen", + "add_a_location": "Een locatie toevoegen", "add_a_name": "Naam toevoegen", "add_a_title": "Titel toevoegen", "add_action": "Actie toevoegen", + "add_action_description": "Klik om een uit te voeren actie toe te voegen", "add_assets": "Items toevoegen", "add_birthday": "Verjaardag toevoegen", "add_endpoint": "Server toevoegen", "add_exclusion_pattern": "Uitsluitingspatroon toevoegen", + "add_filter": "Filter toevoegen", + "add_filter_description": "Klik om een filter voorwaarde toe te voegen", "add_location": "Locatie toevoegen", + "add_more_users": "Meer gebruikers toevoegen", "add_partner": "Partner toevoegen", + "add_path": "Pad toevoegen", "add_photos": "Foto's toevoegen", - "add_step": "Stap toevoegen", "add_tag": "Tag toevoegen", "add_to": "Toevoegen aanâ€Ļ", "add_to_album": "Aan album toevoegen", "add_to_album_bottom_sheet_added": "Toegevoegd aan {album}", "add_to_album_bottom_sheet_already_exists": "Staat al in {album}", "add_to_album_bottom_sheet_some_local_assets": "Sommige lokale items konden niet aan album toegevoegd worden", + "add_to_album_toggle": "Selectie inschakelen voor {album}", "add_to_albums": "Toevoegen aan albums", "add_to_albums_count": "Toevoegen aan albums ({count})", "add_to_bottom_bar": "Toevoegen aan", + "add_to_shared_album": "Aan gedeeld album toevoegen", "add_upload_to_stack": "Voeg upload toe aan stack", "add_url": "URL toevoegen", + "add_workflow_step": "Stap aan workflow toevoegen", "added_to_archive": "Toegevoegd aan archief", "added_to_favorites": "Toegevoegd aan favorieten", "added_to_favorites_count": "{count, number} toegevoegd aan favorieten", @@ -57,8 +65,8 @@ "backup_onboarding_footer": "Raadpleeg de documentatie voor meer informatie over het maken van back-ups van Immich.", "backup_onboarding_parts_title": "Een 3-2-1 back-up omvat:", "backup_onboarding_title": "Back-ups", - "backup_settings": "Database back-up instellingen", - "backup_settings_description": "Beheer database back-up instellingen.", + "backup_settings": "Database dump instellingen", + "backup_settings_description": "Beheer database dump instellingen.", "cleared_jobs": "Taken gewist voor: {job}", "config_set_by_file": "Instellingen worden momenteel beheerd door een configuratiebestand", "confirm_delete_library": "Weet je zeker dat je de bibliotheek {library} wilt verwijderen?", @@ -73,7 +81,6 @@ "cron_expression_description": "Stel het scaninterval in met het cron-formaat. Voor meer informatie kun je bijvoorbeeld kijken naar Crontab Guru", "cron_expression_presets": "Cron-expressie presets", "disable_login": "Inloggen uitschakelen", - "download_csv": "CSV downloaden", "duplicate_detection_job_description": "Machine learning uitvoeren op items om vergelijkbare items te vinden. Dit is gebaseerd op Slim Zoeken", "exclusion_pattern_description": "Met uitsluitingspatronen kun je bestanden en mappen negeren bij het scannen van je bibliotheek. Dit is handig als je mappen hebt met bestanden die je niet wilt importeren, zoals RAW bestanden.", "export_config_as_json_description": "Download de huidige systeem­configuratie als een JSON-bestand", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Thumbnail kwaliteit van 1-100. Hoger is beter, maar produceert grotere bestanden en kan de app vertragen.", "image_thumbnail_title": "Thumbnailinstellingen", "import_config_from_json_description": "Importeer de systeem­configuratie door een JSON-configuratie­bestand te uploaden", - "integrity_checks_checksum_files": "Controlegetalbestanden", - "integrity_checks_checksum_files_description": "Configureer de check van controlegetallen", - "integrity_checks_checksum_files_enable_description": "Schakel de check van controlegetallen in", - "integrity_checks_checksum_files_percentage_limit": "Percentage limiet", - "integrity_checks_checksum_files_percentage_limit_description": "Stel het maximale percentage in tussen 0.01 en 1 voor hoe vaak de check van controlegetallen per interval moet worden uitgevoerd.", - "integrity_checks_checksum_files_time_limit": "Tijdslimiet", - "integrity_checks_checksum_files_time_limit_description": "Stel de maximale duur in waarin de check van controlegetallen per interval moet worden uitgevoerd. (ms)", - "integrity_checks_missing_files": "Ontbrekende bestanden", - "integrity_checks_missing_files_description": "Configureer de frequentie en schakel de controle op ontbrekende bestanden in of uit", - "integrity_checks_missing_files_enable_description": "Schakel de controle op ontbrekende bestanden in", - "integrity_checks_settings": "Integriteitscontroles", - "integrity_checks_settings_description": "Beheer de intervallen voor integriteitscontroles", - "integrity_checks_untracked_files": "Niet getraceerde bestanden", - "integrity_checks_untracked_files_description": "Configureer de frequentie en schakel de controle op niet getraceerde bestanden in of uit", - "integrity_checks_untracked_files_enable_description": "Schakel de controle op niet getraceerde bestanden in", "job_concurrency": "{job} gelijktijdigheid", "job_created": "Taak aangemaakt", "job_not_concurrency_safe": "Deze taak kan niet parallel worden uitgevoerd.", @@ -198,36 +190,20 @@ "machine_learning_smart_search_enabled": "Slim zoeken inschakelen", "machine_learning_smart_search_enabled_description": "Indien uitgeschakeld, worden afbeeldingen niet verwerkt voor slim zoeken.", "machine_learning_url_description": "De URL van de machine learning server. Als er meer dan ÊÊn URL is opgegeven, wordt elke server geprobeerd totdat er een succesvol reageert, op volgorde van eerste tot laatste. Servers die geen reactie geven zullen tijdelijk genegeerd worden tot zij terug online komen.", - "maintenance_backup_management": "Back-upbeheer", "maintenance_delete_backup": "Backup verwijderen", "maintenance_delete_backup_description": "Dit bestand wordt onomkeerbaar verwijderd.", "maintenance_delete_error": "Backup verwijderen mislukt.", - "maintenance_integrity_check": "Controleer", - "maintenance_integrity_check_all": "Alles checken", - "maintenance_integrity_checksum_mismatch": "Controlegetalafwijking", - "maintenance_integrity_checksum_mismatch_description": "Bestanden waarvan het controlegetal afwijkt van het getal dat Immich in de database heeft opgeslagen.", - "maintenance_integrity_checksum_mismatch_job": "Controleer op afwijkingen in controlegetallen", - "maintenance_integrity_checksum_mismatch_refresh_job": "Rapport over controlegetalafwijkingen vernieuwen", - "maintenance_integrity_missing_file": "Ontbrekende bestanden", - "maintenance_integrity_missing_file_description": "Bestanden die in de database zijn opgeslagen, maar in het bestandssysteem niet bestaan.", - "maintenance_integrity_missing_file_job": "Controleer op ontbrekende bestanden", - "maintenance_integrity_missing_file_refresh_job": "Rapport over ontbrekende bestanden vernieuwen", - "maintenance_integrity_report": "Integriteitsstatus", - "maintenance_integrity_untracked_file": "Zwerfbestanden", - "maintenance_integrity_untracked_file_description": "Bestanden die in Immich' mappen voorkomen, maar niet in de database geregistreerd zijn.", - "maintenance_integrity_untracked_file_job": "Controleer op zwerfbestanden", - "maintenance_integrity_untracked_file_refresh_job": "Rapport over zwerfbestanden vernieuwen", "maintenance_restore_backup": "Backup herstellen", "maintenance_restore_backup_description": "Immich wordt gereset en hersteld vanaf de gekozen backup. Er wordt een backup gemaakt voor deze actie uitgevoerd wordt.", "maintenance_restore_backup_different_version": "Deze backup is gemaakt met een andere versie van Immich!", "maintenance_restore_backup_unknown_version": "Kan versie van backup niet bepalen.", - "maintenance_restore_database_backup": "Database back-up terugzetten", - "maintenance_restore_database_backup_description": "Een eerdere versie van de database terugzetten door middel van een back-up bestand", + "maintenance_restore_database_backup": "Database backup terugzetten", + "maintenance_restore_database_backup_description": "Een eerdere versie van de database terugzetten door middel van een backup bestand", "maintenance_settings": "Onderhoud", "maintenance_settings_description": "Zet Immich in onderhouds­modus.", "maintenance_start": "Onderhouds­modus activeren", "maintenance_start_error": "Onderhouds­modus starten mislukt.", - "maintenance_upload_backup": "Upload database back-up", + "maintenance_upload_backup": "Upload database backup bestand", "maintenance_upload_backup_error": "Kon backup niet uploaden, is het een .sql/.sql.gz bestand?", "manage_concurrency": "Beheer gelijktijdigheid", "manage_concurrency_description": "Navigeer naar de taken­pagina om de gelijk­tÄŗdigheid van taken te beheren", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "E-mailmeldingen inschakelen", "notification_settings": "Meldingsinstellingen", "notification_settings_description": "Beheer meldingsinstellingen, inclusief e-mail", - "oauth_allow_insecure_requests": "Onveilige verzoeken toestaan", - "oauth_allow_insecure_requests_description": "WAARSCHUWING: Dit schakelt TLS-certificaat validatie voor OAuth verzoeken uit en kan je vatbaar maken voor MITM aanvallen.", "oauth_auto_launch": "Automatisch starten", "oauth_auto_launch_description": "Automatisch inloggen met OAuth bij het navigeren naar de inlogpagina", "oauth_auto_register": "Automatisch registreren", @@ -300,11 +274,9 @@ "oauth_button_text": "Knoptekst", "oauth_client_secret_description": "Vereist voor een confidentiÃĢle client, of als PKCE (Proof Key for Code Exchange) niet wordt ondersteund door de publieke client.", "oauth_enable_description": "Inloggen met OAuth", - "oauth_end_session_url_description": "Leidt de gebruiker om naar deze URI tijdens uitloggen.", "oauth_mobile_redirect_uri": "Omleidings-URI voor mobiel", "oauth_mobile_redirect_uri_override": "Omleidings-URI voor mobiele app overschrijven", "oauth_mobile_redirect_uri_override_description": "Inschakelen wanneer de OAuth-provider geen mobiele URI toestaat, zoals ''{callback}''", - "oauth_prompt_description": "Prompt variabele (bijv. select_account, login, consent)", "oauth_role_claim": "Rol claim", "oauth_role_claim_description": "Automatisch admin toegang geven als deze claim aanwezig is. De claim kan 'user' of 'admin' zijn.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Alle bibliotheken aan het vernieuwen", "registration": "Beheerdersregistratie", "registration_description": "Omdat je de eerste gebruiker in het systeem bent, word je toegewezen als beheerder en ben je verantwoordelijk voor administratieve taken. Extra gebruikers kunnen door jou worden aangemaakt.", - "release_channel_release_candidate": "Releasekandidaat", - "release_channel_stable": "Stabiel", "remove_failed_jobs": "Verwijder mislukte taken", "require_password_change_on_login": "Vereisen dat de gebruiker het wachtwoord wijzigt bij de eerste keer inloggen", "reset_settings_to_default": "Instellingen teruggezet naar standaard", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Geldt alleen voor VAAPI en QSV. Stelt de dri node in die wordt gebruikt voor hardwaretranscodering.", "transcoding_preset_preset": "Voorkeuze (-preset)", "transcoding_preset_preset_description": "Compressiesnelheid. Langzamere presets produceren kleinere bestanden en verhogen de kwaliteit bij het targeten van een bepaalde bitrate. VP9 negeert snelheden boven 'faster'.", - "transcoding_realtime": "Realtime transcodering [EXPERIMENTEEL]", - "transcoding_realtime_description": "Maakt het mogelijk om transcodering in realtime uit te voeren terwijl de video wordt gestreamd. Maakt het wisselen van kwaliteit mogelijk, maar kan een hogere afspeelvertraging en haperingen veroorzaken, afhankelijk van de capaciteiten van de server.", - "transcoding_realtime_enabled": "Realtime transcodering inschakelen", - "transcoding_realtime_enabled_description": "Indien uitgeschakeld, weigert de server om nieuwe realtime transcodeersessies te starten.", - "transcoding_realtime_resolutions": "Resoluties", - "transcoding_realtime_resolutions_description": "De resoluties die beschikbaar zijn voor realtime transcodering. Hogere resoluties kunnen afspeelproblemen veroorzaken als de server deze niet snel genoeg kan transcoderen.", - "transcoding_realtime_video_codecs": "Videocodecs", - "transcoding_realtime_video_codecs_description": "De videocodecs die beschikbaar zijn voor realtime transcodering. Clients kiezen tijdens het afspelen automatisch de beste codec die zij ondersteunen. AV1 is efficiÃĢnter dan HEVC, en HEVC is efficiÃĢnter dan H.264. Bij gebruik van hardwareversnelling selecteer je alleen de codecs die door de hardwareversneller kunnen worden gecodeerd. Bij softwarematige transcodering geldt dat H.264 sneller is dan AV1, en AV1 sneller is dan HEVC.", "transcoding_reference_frames": "Referentie frames", "transcoding_reference_frames_description": "Het aantal frames om naar te verwijzen bij het comprimeren van een bepaald frame. Hogere waarden verbeteren de compressie-efficiÃĢntie, maar vertragen de codering. Bij 0 wordt deze waarde automatisch ingesteld.", "transcoding_required_description": "Alleen video's die geen geaccepteerd formaat hebben", @@ -478,8 +440,6 @@ "user_settings_description": "Gebruikersinstellingen beheren", "user_successfully_removed": "Gebruiker {email} is succesvol verwÄŗderd.", "users_page_description": "Gebruikers­pagina voor administrators", - "version_check_channel": "Releasekanaal", - "version_check_channel_description": "Kies het releasekanaal waarvoor je versie-aankondigingen wilt ontvangen", "version_check_enabled_description": "Versiecontrole inschakelen", "version_check_implications": "De versiecontrole is afhankelijk van periodieke communicatie met {server}", "version_check_settings": "Versiecontrole", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Wis afbeeldingscache", "advanced_settings_clear_image_cache_error": "Het wissen van de afbeeldingscache is mislukt", "advanced_settings_clear_image_cache_success": "{size} succesvol gewist", + "advanced_settings_enable_alternate_media_filter_subtitle": "Gebruik deze optie om media te filteren tijdens de synchronisatie op basis van alternatieve criteria. Gebruik dit enkel als de app problemen heeft met het detecteren van albums.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTEEL] Gebruik een alternatieve album synchronisatie filter", "advanced_settings_log_level_title": "Logniveau: {level}", "advanced_settings_prefer_remote_subtitle": "Sommige apparaten zijn traag met het laden van lokale afbeeldingen. Activeer deze instelling om in plaats daarvan externe afbeeldingen te laden.", "advanced_settings_prefer_remote_title": "Externe afbeeldingen laden", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Proxy Headers [EXPERIMENTEEL]", "advanced_settings_readonly_mode_subtitle": "Schakelt de alleen-lezenmodus in, waarbij de foto's alleen bekeken kunnen worden. Dingen zoals het selecteren van meerdere afbeeldingen, delen, casten en verwijderen zijn allemaal uitgeschakeld. Schakel alleen-lezen in of uit via de gebruikers avatar vanaf het hoofdscherm", "advanced_settings_readonly_mode_title": "Alleen-lezen mode", + "advanced_settings_self_signed_ssl_subtitle": "Slaat SSL-certificaatverificatie voor de connectie met de server over. Deze optie is vereist voor zelfondertekende certificaten.", + "advanced_settings_self_signed_ssl_title": "Zelfondertekende SSL-certificaten toestaan [EXPERIMENTEEL]", "advanced_settings_sync_remote_deletions_subtitle": "Automatisch bestanden verwijderen of herstellen op dit apparaat als die actie op het web is ondernomen", "advanced_settings_sync_remote_deletions_title": "Synchroniseer verwijderingen op afstand [EXPERIMENTEEL]", "advanced_settings_tile_subtitle": "Geavanceerde gebruikersinstellingen", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Weet je zeker dat je het album {album} wilt verwijderen?", "album_delete_confirmation_description": "Als dit album gedeeld is, zullen andere gebruikers geen toegang meer hebben.", "album_deleted": "Album verwijderd", + "album_info_card_backup_album_excluded": "UITGESLOTEN", + "album_info_card_backup_album_included": "INBEGREPEN", "album_info_updated": "Albumgegevens bijgewerkt", + "album_leave": "Album verlaten?", + "album_leave_confirmation": "Weet je zeker dat je {album} wilt verlaten?", "album_name": "Albumnaam", "album_options": "Albumopties", "album_remove_user": "Gebruiker verwijderen?", "album_remove_user_confirmation": "Weet je zeker dat je {user} wilt verwijderen?", "album_search_not_found": "Geen albums gevonden die aan je zoekopdracht voldoen", + "album_selected": "Album geselecteerd", "album_share_no_users": "Het lijkt erop dat je dit album met alle gebruikers hebt gedeeld, of dat je geen gebruikers hebt om mee te delen.", "album_summary": "Album samenvatting", "album_updated": "Album bijgewerkt", "album_updated_setting_description": "Ontvang een e-mailmelding wanneer een gedeeld album nieuwe items heeft", "album_upload_assets": "Items uploaden van je computer en aan album toevoegen", + "album_user_left": "{album} verlaten", + "album_user_removed": "{user} verwijderd", + "album_viewer_appbar_delete_confirm": "Weet je zeker dat je dit album uit je account wilt verwijderen?", "album_viewer_appbar_share_err_delete": "Verwijderen album mislukt", + "album_viewer_appbar_share_err_leave": "Verlaten album mislukt", + "album_viewer_appbar_share_err_remove": "Er gaat iets mis bij het verwijderen van items uit het album", + "album_viewer_appbar_share_err_title": "Albumtitel wijzigen mislukt", + "album_viewer_appbar_share_leave": "Verlaat album", + "album_viewer_appbar_share_to": "Delen via", "album_viewer_page_share_add_users": "Gebruikers toevoegen", "album_with_link_access": "Iedereen met de link kan de foto's en mensen in dit album bekijken.", "albums": "Albums", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "InitiÃĢle sorteervolgorde bij het maken van nieuwe albums.", "albums_feature_description": "Collectie van items die je kan delen met andere gebruikers.", "albums_on_device_count": "Albums op apparaat ({count})", + "albums_selected": "{count, plural, one {# album geselecteerd} other {# albums geselecteerd}}", "all": "Alle", "all_albums": "Alle albums", "all_people": "Alle mensen", "all_photos": "Alle foto's", "all_videos": "Alle video's", "allow_dark_mode": "Donkere modus toestaan", + "allow_edits": "Bewerkingen toestaan", "allow_public_user_to_download": "Sta openbare gebruiker toe om te downloaden", "allow_public_user_to_upload": "Sta openbare gebruiker toe om te uploaden", "allowed": "Toegestaan", @@ -549,12 +528,14 @@ "always_keep": "Altijd bewaren", "always_keep_photos_hint": "Met Free Up Space blijven alle foto's op dit apparaat bewaard.", "always_keep_videos_hint": "Met Free Up Space worden alle video's op dit apparaat bewaard.", + "anti_clockwise": "Linksom", "api_key": "API-sleutel", "api_key_description": "Deze waarde wordt slechts ÊÊn keer getoond. Zorg ervoor dat je deze kopieert voordat je het venster sluit.", "api_key_empty": "De naam van uw API-sleutel mag niet leeg zijn", "api_keys": "API-sleutels", "app_architecture_variant": "Variant (architectuur)", "app_bar_signout_dialog_content": "Weet je zeker dat je wilt uitloggen?", + "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Log uit", "app_download_links": "Download-verwijzingen van de app", "app_settings": "App instellingen", @@ -563,21 +544,29 @@ "appears_in": "Komt voor in", "apply_count": "Toepassen ({count, number})", "archive": "Archief", - "archive_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan het archief", + "archive_action_prompt": "{count} item(s) toegevoegd aan het archief", "archive_or_unarchive_photo": "Foto archiveren of uit het archief halen", + "archive_page_no_archived_assets": "Geen gearchiveerde items gevonden", + "archive_page_title": "Archief ({count})", "archive_size": "Archiefgrootte", "archive_size_description": "Configureer de archiefgrootte voor downloads (in GiB)", "archived": "Gearchiveerd", "archived_count": "{count, plural, other {# gearchiveerd}}", "are_these_the_same_person": "Zijn dit dezelfde personen?", "are_you_sure_to_do_this": "Weet je zeker dat je dit wilt doen?", + "array_field_not_fully_supported": "Array velden vereisen handmatige JSON bewerking", + "asset_action_delete_err_read_only": "Kan alleen-lezen item(s) niet verwijderen, overslaan", + "asset_action_share_err_offline": "Kan offline item(s) niet ophalen, overslaan", "asset_added_to_album": "Toegevoegd aan album", "asset_adding_to_album": "Toevoegen aan albumâ€Ļ", "asset_created": "Item aangemaakt", - "asset_day_count": "{date}: {count, plural, one {# item} other {# items}}", "asset_description_updated": "Item beschrijving is bijgewerkt", + "asset_filename_is_offline": "Item {filename} is offline", + "asset_has_unassigned_faces": "Item heeft niet-toegewezen gezichten", "asset_hashing": "Hashenâ€Ļ", "asset_list_group_by_sub_title": "Groepeer op", + "asset_list_layout_settings_dynamic_layout_title": "Dynamische layout", + "asset_list_layout_settings_group_automatically": "Automatisch", "asset_list_layout_settings_group_by": "Groepeer items per", "asset_list_layout_settings_group_by_month_day": "Maand + dag", "asset_list_layout_sub_title": "Layout", @@ -588,77 +577,125 @@ "asset_not_found_on_icloud": "Item niet gevonden in iCloud. Het item kan ontoegankelijk zijn door een slecht bestand op iCloud", "asset_offline": "Item offline", "asset_offline_description": "Dit externe item is niet meer op de schijf te vinden. Neem contact op met de Immich beheerder voor hulp.", + "asset_restored_successfully": "Item succesvol hersteld", "asset_skipped": "Overgeslagen", "asset_skipped_in_trash": "In prullenbak", - "asset_troubleshoot": "Item probleemoplossing", + "asset_trashed": "Asset verwijderd", + "asset_troubleshoot": "Asset probleemoplossing", "asset_uploaded": "GeÃŧpload", "asset_uploading": "Uploadenâ€Ļ", "asset_viewer_settings_subtitle": "Beheer je instellingen voor galerijweergave", "asset_viewer_settings_title": "Fotoweergave", "assets": "Items", + "assets_added_count": "{count, plural, one {# item} other {# items}} toegevoegd", "assets_added_to_album_count": "{count, plural, one {# item} other {# items}} aan het album toegevoegd", - "assets_added_to_album_partial_count": "{successCount} van de {totalCount} {totalCount, plural, one {asset} other {assets}} toegevoegd aan het album", "assets_added_to_albums_count": "{assetTotal, plural, one {# asset} other {# assets}} toegevoegd aan {albumTotal, plural, one {# album} other {#albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {# item} other {# items}} konden niet aan album toegevoegd worden", "assets_cannot_be_added_to_albums": "{count, plural, one {Item kan} other {Items kunnen}} niet toegevoegd worden aan de albums", "assets_count": "{count, plural, one {# item} other {# items}}", + "assets_deleted_permanently": "{count} item(s) permanent verwijderd", + "assets_deleted_permanently_from_server": "{count} item(s) permanent verwijderd van de Immich server", + "assets_downloaded_failed": "{count, plural, one {# bestand gedownload - {error} bestand mislukt} other {# bestanden gedownload - {error} bestanden mislukt}}", + "assets_downloaded_successfully": "{count, plural, one {# bestand succesvol gedownload} other {# bestanden succesvol gedownload}}", "assets_moved_to_trash_count": "{count, plural, one {# item} other {# items}} verplaatst naar prullenbak", "assets_permanently_deleted_count": "{count, plural, one {# item} other {# items}} permanent verwijderd", "assets_removed_count": "{count, plural, one {# item} other {# items}} verwijderd", + "assets_removed_permanently_from_device": "{count} item(s) permanent verwijderd van je apparaat", "assets_restore_confirmation": "Weet je zeker dat je alle verwijderde items wilt herstellen? Je kunt deze actie niet ongedaan maken! Offline items kunnen op deze manier niet worden hersteld.", "assets_restored_count": "{count, plural, one {# item} other {# items}} hersteld", - "assets_trashed": "{count, plural, one {# item} other {# items}} naar de prullenbak verplaatst", + "assets_restored_successfully": "{count} item(s) succesvol hersteld", + "assets_trashed": "{count} item(s) naar de prullenbak verplaatst", "assets_trashed_count": "{count, plural, one {# item} other {# items}} naar prullenbak verplaatst", + "assets_trashed_from_server": "{count} item(s) naar de prullenbak verplaatst op de Immich server", "assets_were_part_of_album_count": "{count, plural, one {Item was} other {Items waren}} al onderdeel van het album", "assets_were_part_of_albums_count": "{count, plural, one {Item is} other {Items zijn}} al onderdeel van de albums", "authorized_devices": "Geautoriseerde apparaten", - "automatic_endpoint_switching_subtitle": "Maak indien beschikbaar lokaal verbinding via het aangewezen wifinetwerk en gebruik elders alternatieve verbindingen", + "automatic_endpoint_switching_subtitle": "Maak indien beschikbaar lokaal verbinding via het aangewezen wifi-netwerk en gebruik elders alternatieve verbindingen", "automatic_endpoint_switching_title": "Automatische serverwissel", "autoplay_slideshow": "Diavoorstelling automatisch afspelen", "back": "Terug", "back_close_deselect": "Terug, sluiten of deselecteren", + "background_backup_running_error": "Back-up draait op de achtergrond, handmatige back-up kan niet worden gestart", "background_location_permission": "Achtergrond locatie toestemming", - "background_location_permission_content": "Om van netwerk te wisselen terwijl de app op de achtergrond draait, heeft Immich *altijd* toegang tot de exacte locatie nodig om de naam van het wifi­netwerk te kunnen lezen", + "background_location_permission_content": "Om van netwerk te wisselen terwijl de app op de achtergrond draait, heeft Immich *altijd* toegang tot de exacte locatie nodig om de naam van het WiFi-netwerk te kunnen lezen", "background_options": "Achtergrond opties", "backup": "Back-up", + "backup_album_selection_page_albums_device": "Albums op apparaat ({count})", "backup_album_selection_page_albums_tap": "Tik om op te nemen, dubbel tik om uit te sluiten", "backup_album_selection_page_assets_scatter": "Items kunnen over verschillende albums verdeeld zijn, dus albums kunnen inbegrepen of uitgesloten zijn van het back-up proces.", "backup_album_selection_page_select_albums": "Selecteer albums", "backup_album_selection_page_selection_info": "Selectie info", + "backup_album_selection_page_total_assets": "Totaal unieke items", "backup_albums_sync": "Backup Albums Synchronisatie", + "backup_all": "Alle", + "backup_background_service_backup_failed_message": "Fout bij het back-uppen van de items. Opnieuw proberenâ€Ļ", "backup_background_service_complete_notification": "Backup voltooid", + "backup_background_service_connection_failed_message": "Fout bij het verbinden met de server. Opnieuw proberenâ€Ļ", + "backup_background_service_current_upload_notification": "{filename} wordt geÃŧpload", "backup_background_service_default_notification": "Controleren op nieuwe itemsâ€Ļ", + "backup_background_service_error_title": "Back-up fout", "backup_background_service_in_progress_notification": "Back-up van items makenâ€Ļ", + "backup_background_service_upload_failure_notification": "Fout bij het uploaden van {filename}", "backup_controller_page_albums": "Back-up albums", + "backup_controller_page_background_app_refresh_disabled_content": "Schakel verversen op de achtergrond in via 'Instellingen > Algemeen > Ververs op achtergrond', om back-ups op de achtergrond te maken.", + "backup_controller_page_background_app_refresh_disabled_title": "Verversen op achtergrond is uitgeschakeld", + "backup_controller_page_background_app_refresh_enable_button_text": "Ga naar instellingen", "backup_controller_page_background_battery_info_link": "Laat zien hoe", "backup_controller_page_background_battery_info_message": "Voor de beste achtergrond back-up ervaring schakelt u alle batterij optimalisaties uit die de achtergrondactiviteit voor Immich kunnen beperken.\n\nAangezien dit apparaat specifiek is, raden we aan om de vereiste informatie op te zoeken bij de fabrikant van je apparaat.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Batterij optimalisaties", + "backup_controller_page_background_charging": "Alleen tijdens opladen", + "backup_controller_page_background_configure_error": "Achtergrondservice configuratie mislukt", "backup_controller_page_background_delay": "Back-up vertraging voor nieuwe items: {duration}", + "backup_controller_page_background_description": "Schakel de achtergrondservice in om automatisch een back-up te maken van nieuwe items zonder de app te hoeven openen", + "backup_controller_page_background_is_off": "Automatische achtergrond back-up staat uit", + "backup_controller_page_background_is_on": "Automatische achtergrond back-up staat aan", + "backup_controller_page_background_turn_off": "Achtergrondservice uitzetten", + "backup_controller_page_background_turn_on": "Achtergrondservice aanzetten", + "backup_controller_page_background_wifi": "Alleen op WiFi", "backup_controller_page_backup": "Back-up", "backup_controller_page_backup_selected": "Geselecteerd: ", "backup_controller_page_backup_sub": "Geback-upte foto's en video's", + "backup_controller_page_created": "Gemaakt op: {date}", + "backup_controller_page_desc_backup": "Schakel back-up op de voorgrond in om automatisch nieuwe items naar de server te uploaden bij het openen van de app.", "backup_controller_page_excluded": "Uitgezonderd: ", + "backup_controller_page_failed": "Mislukt ({count})", + "backup_controller_page_filename": "Bestandsnaam: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Back-up informatie", "backup_controller_page_none_selected": "Geen geselecteerd", "backup_controller_page_remainder": "Resterend", "backup_controller_page_remainder_sub": "Resterende foto's en video's om een back-up van te maken uit selectie", "backup_controller_page_server_storage": "Serveropslag", + "backup_controller_page_start_backup": "Back-up uitvoeren", + "backup_controller_page_status_off": "Automatische back-up op de voorgrond staat uit", + "backup_controller_page_status_on": "Automatische back-up op de voorgrond staat aan", "backup_controller_page_storage_format": "{used} van {total} gebruikt", "backup_controller_page_to_backup": "Albums om een back-up van te maken", "backup_controller_page_total_sub": "Alle unieke foto's en video's uit geselecteerde albums", + "backup_controller_page_turn_off": "Back-up op de voorgrond uitzetten", + "backup_controller_page_turn_on": "Back-up op de voorgrond aanzetten", + "backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden", + "backup_err_only_album": "Kan het enige album niet verwijderen", "backup_error_sync_failed": "Synchronisatie mislukt. Kan back-up niet verwerken.", "backup_info_card_assets": "bestanden", + "backup_manual_cancelled": "Geannuleerd", + "backup_manual_in_progress": "Het uploaden is al bezig. Probeer het na een tijdje", + "backup_manual_success": "Gelukt", + "backup_manual_title": "Uploadstatus", "backup_options": "Backup opties", + "backup_options_page_title": "Back-up instellingen", + "backup_setting_subtitle": "Beheer achtergrond en voorgrond uploadinstellingen", "backup_settings_subtitle": "Beheer upload instellingen", + "backup_upload_details_page_more_details": "Tik voor meer details", "backward": "Achteruit", - "battery_optimization_backup_reliability": "Het uitschakelen van batterijoptimalisaties kan de betrouwbaarheid van de back-up op de achtergrond verbeteren", "biometric_auth_enabled": "Biometrische authenticatie ingeschakeld", "biometric_locked_out": "Biometrische authenticatie is vergrendeld", "biometric_no_options": "Geen biometrische opties beschikbaar", "biometric_not_available": "Biometrische authenticatie is niet beschikbaar op dit apparaat", + "birthdate_saved": "Geboortedatum succesvol opgeslagen", "birthdate_set_description": "De geboortedatum wordt gebruikt om de leeftijd van deze persoon op het moment van de foto te berekenen.", "blurred_background": "Vervaagde achtergrond", - "browse_templates": "Blader door templates", "bugs_and_feature_requests": "Bugs & functieverzoeken", "build": "Build", "build_image": "Build image", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# dubbel item} other {# dubbele items}} wilt behouden? Dit zal alle groepen met duplicaten oplossen zonder iets te verwijderen.", "bulk_trash_duplicates_confirmation": "Weet je zeker dat je {count, plural, one {# dubbel item} other {# dubbele items}} in bulk naar de prullenbak wilt verplaatsen? Dit zal de grootste item van elke groep behouden en alle andere duplicaten naar de prullenbak verplaatsen.", "buy": "Immich kopen", + "cache_settings_clear_cache_button": "Cache wissen", + "cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.", + "cache_settings_duplicated_assets_clear_button": "MAAK VRIJ", + "cache_settings_duplicated_assets_subtitle": "Foto’s en video's die de app negeert", + "cache_settings_duplicated_assets_title": "Gedupliceerde items ({count})", + "cache_settings_statistics_album": "Bibliotheekthumbnails", + "cache_settings_statistics_full": "Volledige afbeeldingen", + "cache_settings_statistics_shared": "Gedeeld-albumthumbnails", + "cache_settings_statistics_thumbnail": "Minaturen", + "cache_settings_statistics_title": "Cachegebruik", + "cache_settings_subtitle": "Beheer het cachegedrag van de Immich app", + "cache_settings_tile_subtitle": "Beheer het gedrag van lokale opslag", + "cache_settings_tile_title": "Lokale opslag", + "cache_settings_title": "Cache-instellingen", "camera": "Camera", "camera_brand": "Cameramerk", "camera_model": "Cameramodel", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Kan de beschrijving niet bijwerken", "cast": "Cast", "cast_description": "Configureer beschikbare cast bestemmingen", - "change": "Wijzig", "change_date": "Wijzig datum", "change_description": "Wijzig beschrijving", "change_display_order": "Weergavevolgorde wijzigen", + "change_expiration_time": "Verlooptijd wijzigen", "change_location": "Wijzig locatie", "change_name": "Naam wijzigen", "change_name_successfully": "Naam succesvol gewijzigd", @@ -695,46 +746,53 @@ "change_password_form_password_mismatch": "Wachtwoorden komen niet overeen", "change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in", "change_pin_code": "Wijzig pincode", + "change_trigger": "Wijzig trigger", + "change_trigger_prompt": "Weet u zeker dat u deze trigger wilt wijzigen? Dit verwijdert alle bestaande acties en filters.", "change_your_password": "Wijzig je wachtwoord", "changed_visibility_successfully": "Zichtbaarheid succesvol gewijzigd", "charging": "Opladen", "charging_requirement_mobile_backup": "Achtergrond backup vereist dat het apparaat wordt opgeladen", + "check_corrupt_asset_backup": "Controleer op corrupte back-ups van items", + "check_corrupt_asset_backup_button": "Controle uitvoeren", + "check_corrupt_asset_backup_description": "Voer deze controle alleen uit via WiFi en nadat alle items zijn geback-upt. De procedure kan een paar minuten duren.", "check_logs": "Controleer logboek", "checksum": "Controlegetal", - "choose": "Kies", "choose_matching_people_to_merge": "Kies overeenkomende mensen om samen te voegen", "city": "Stad", "cleanup_confirm_description": "Immich heeft {count} items (gemaakt voor {date}) opgeslagen op de server. Lokale kopieÃĢn van dit apparaat verwijderen?", "cleanup_confirm_prompt_title": "Van dit apparaat verwijderen?", "cleanup_deleted_assets": "{count} items verplaats naar prullenbak van apparaat", "cleanup_deleting": "Naar prullenbak verplaatsen...", - "cleanup_found_assets": "Er zijn {count} back-up­bestanden gevonden", - "cleanup_found_assets_with_size": "Er zijn {count} back-up­bestanden gevonden ({size})", + "cleanup_found_assets": "Er zijn {count} backup bestanden gevonden", + "cleanup_found_assets_with_size": "Er zijn {count} back-upbestanden gevonden ({size})", "cleanup_icloud_shared_albums_excluded": "Gedeelde albums van iCloud zijn uitgesloten van de scan", "cleanup_no_assets_found": "Er zijn geen bestanden gevonden die aan bovenstaande criteria voldoen. Free Up Space kan alleen bestanden verwijderen die op de server zijn geback-upt", "cleanup_preview_title": "Bestanden te verwijderen ({count})", "cleanup_step3_description": "Scan naar back-upbestanden die overeenkomen met uw datum en behoud uw instellingen.", "cleanup_step4_summary": "{count} bestanden (gemaakt vÃŗÃŗr {date}) die van uw lokale apparaat moeten worden verwijderd. Foto's blijven toegankelijk via de Immich-app.", - "cleanup_trash_hint": "Open de galerij-app en leeg de prullenbak om de opslagruimte volledig vrij te maken", + "cleanup_trash_hint": "Om de opslagruimte volledig vrij te maken, opent u de systeemgalerij-app en leegt u de prullenbak", "clear": "Wissen", "clear_all": "Alles wissen", "clear_all_recent_searches": "Wis alle recente zoekopdrachten", - "clear_failed_count": "Wis mislukte ({count})", "clear_file_cache": "Bestandcache leegmaken", "clear_message": "Bericht wissen", "clear_value": "Waarde wissen", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Voer wachtwoord in", "client_cert_import": "Importeren", "client_cert_import_success_msg": "CliÃĢntcertificaat is geïmporteerd", - "client_cert_invalid_msg": "Ongeldig certificaat­bestand of verkeerd wachtwoord", + "client_cert_invalid_msg": "Ongeldig certificaatbestand of verkeerd wachtwoord", "client_cert_password_message": "Voer het wachtwoord voor dit certificaat in", "client_cert_password_title": "Certificaat wachtwoord", "client_cert_remove_msg": "Clientcertificaat is verwijderd", "client_cert_subtitle": "Ondersteunt alleen PKCS12-formaat (.p12, .pfx). Het importeren/verwijderen van certificaten is alleen beschikbaar vÃŗÃŗr het inloggen", "client_cert_title": "SSL clientcertificaat [EXPERIMENTEEL]", + "clockwise": "Rechtsom", "close": "Sluiten", "collapse": "Inklappen", "collapse_all": "Alles inklappen", "color": "Kleur", + "color_theme": "Kleurenthema", "command": "Commando", "command_palette_prompt": "Vind snel pagina's, acties of commando's", "command_palette_to_close": "om te sluiten", @@ -747,7 +805,6 @@ "comments_are_disabled": "Opmerkingen zijn uitgeschakeld", "common_create_new_album": "Nieuw album maken", "completed": "Voltooid", - "configuration": "Configuratie", "confirm": "Bevestigen", "confirm_admin_password": "Bevestig beheerder wachtwoord", "confirm_delete_face": "Weet je zeker dat je het gezicht van {name} wilt verwijderen uit het item?", @@ -759,19 +816,22 @@ "confirm_tag_face_unnamed": "Wil je dit gezicht taggen?", "connected_device": "Verbonden apparaat", "connected_to": "Verbonden met", - "contain": "Passend", + "contain": "Bevat", "context": "Context", "continue": "Doorgaan", - "control_bottom_app_bar_add_tags": "Tags toevoegen", - "control_bottom_app_bar_delete_from_local": "Verwijder van apparaat", + "control_bottom_app_bar_create_new_album": "Nieuw album maken", + "control_bottom_app_bar_delete_from_immich": "Verwijderen van Immich", + "control_bottom_app_bar_delete_from_local": "Verwijderen van apparaat", "control_bottom_app_bar_edit_location": "Locatie bewerken", "control_bottom_app_bar_edit_time": "Datum & tijd bewerken", - "control_bottom_app_bar_trash_from_immich": "Verplaats naar prullenbak", + "control_bottom_app_bar_share_link": "Link delen", + "control_bottom_app_bar_share_to": "Delen met", + "control_bottom_app_bar_trash_from_immich": "Verwijderen van Immich", "copied_image_to_clipboard": "Afbeelding gekopieerd naar klembord.", "copied_to_clipboard": "Gekopieerd naar klembord!", "copy_error": "Fout bij kopiÃĢren", + "copy_file_path": "Kopieer bestandspad", "copy_image": "Kopieer afbeelding", - "copy_json": "JSON kopiÃĢren", "copy_link": "Kopieer link", "copy_link_to_clipboard": "Kopieer link naar klembord", "copy_password": "Kopieer wachtwoord", @@ -789,6 +849,7 @@ "create_link_to_share": "Gedeelde link maken", "create_link_to_share_description": "Laat iedereen met de link de geselecteerde foto(s) zien", "create_new": "MAAK NIEUW", + "create_new_face": "Nieuw gezicht aanmaken", "create_new_person": "Nieuwe persoon aanmaken", "create_new_person_hint": "Geselecteerde items toewijzen aan een nieuwe persoon", "create_new_user": "Nieuwe gebruiker aanmaken", @@ -805,69 +866,80 @@ "created_at": "Aangemaakt", "creating_linked_albums": "Gekoppelde albums worden aangemaakt...", "crop": "Bijsnijden", + "crop_aspect_ratio_fixed": "Vast", "crop_aspect_ratio_free": "Vrij", "crop_aspect_ratio_original": "Origineel", "crop_aspect_ratio_square": "Vierkant", + "curated_object_page_title": "Dingen", "current_device": "Huidig apparaat", "current_pin_code": "Huidige pincode", "current_server_address": "Huidig serveradres", "custom_date": "Aangepaste datum", "custom_locale": "Aangepaste landinstelling", - "custom_locale_description": "Formatteer datums, tijden, en getallen op basis van de geselecteerde taal en regio", + "custom_locale_description": "Formatteer datums, tijden en getallen op basis van de geselecteerde taal en de regio", + "custom_url": "Aangepaste URL", "cutoff_date_description": "Bewaar foto's van de laatsteâ€Ļ", "cutoff_day": "{count, plural, one {dag} other {dagen}}", "cutoff_year": "{count, plural, one {jaar} other {jaren}}", + "daily_title_text_date": "E dd MMM", + "daily_title_text_date_year": "E dd MMM yyyy", "dark": "Donker", "dark_theme": "Wissel naar donker thema", "date": "Datum", "date_after": "Datum na", "date_and_time": "Datum en tijd", "date_before": "Datum voor", - "date_of_birth": "Geboortedatum", + "date_format": "E d LLL y â€ĸ H:mm", "date_of_birth_saved": "Geboortedatum succesvol opgeslagen", "date_range": "Datumbereik", - "date_time_original": "Datum/Tijd Origineel", "day": "Dag", "days": "Dagen", "deduplicate_all": "Alles dedupliceren", - "default_quality_subtitle": "Standaard beeldkwaliteit bij het delen. Houd de deelknop ingedrukt om dit per keer te kiezen.", - "default_share_quality": "Standaardkwaliteit bij delen", - "delete": "Verwijder", + "default_locale": "Standaard landinstelling", + "default_locale_description": "Formatteer datums en getallen op basis van de taalinstellingen van uw browser", + "delete": "Verwijderen", "delete_action_confirmation_message": "Weet je zeker dat je dit item wilt verwijderen? Deze actie zorgt ervoor dat het item naar de prullenbak van de server wordt verplaatst en je wordt gevraagd of je deze ook lokaal wilt verwijderen", - "delete_action_prompt": "{count} verwijderd", + "delete_action_prompt": "{count} item(s) verwijderd", "delete_album": "Album verwijderen", "delete_api_key_prompt": "Weet je zeker dat je deze API-sleutel wilt verwijderen?", "delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat", "delete_dialog_alert_local": "Deze items worden permanent verwijderd van je apparaat, maar blijven beschikbaar op de Immich server", "delete_dialog_alert_local_non_backed_up": "Van sommige items is geen back-up gemaakt in Immich en zullen permanent van je apparaat worden verwijderd", + "delete_dialog_alert_remote": "Deze items worden permanent verwijderd van de Immich server", + "delete_dialog_ok_force": "Toch verwijderen", "delete_dialog_title": "Permanent verwijderen", "delete_duplicates_confirmation": "Weet je zeker dat je deze duplicaten permanent wilt verwijderen?", "delete_face": "Gezicht verwijderen", "delete_key": "Verwijder key", "delete_library": "Verwijder bibliotheek", "delete_link": "Verwijder link", - "delete_local_action_prompt": "{count} lokaal verwijderd", + "delete_local_action_prompt": "{count} item(s) lokaal verwijderd", "delete_local_dialog_ok_backed_up_only": "Verwijder alleen met back-up", "delete_local_dialog_ok_force": "Toch verwijderen", "delete_others": "Andere verwijderen", "delete_permanently": "Permanent verwijderen", - "delete_permanently_action_prompt": "{count} permanent verwijderd", + "delete_permanently_action_prompt": "{count} item(s) permanent verwijderd", "delete_shared_link": "Verwijder gedeelde link", "delete_shared_link_dialog_title": "Verwijder gedeelde link", "delete_tag": "Tag verwijderen", "delete_tag_confirmation_prompt": "Weet je zeker dat je de tag {tagName} wilt verwijderen?", "delete_user": "Verwijder gebruiker", "deleted_shared_link": "Gedeelde link verwijderd", + "deletes_missing_assets": "Verwijdert items die ontbreken op de schijf", "description": "Beschrijving", + "description_input_hint_text": "Beschrijving toevoegen...", + "description_input_submit_error": "Beschrijving bijwerken mislukt, controleer het logboek voor meer details", "deselect_all": "Alles deselecteren", "details": "Details", "direction": "Richting", "disable": "Uitschakelen", "disabled": "Uitgeschakeld", + "disallow_edits": "Geen bewerkingen toestaan", "discord": "Discord", "discover": "Zoek", "discovered_devices": "Gevonden apparaten", "dismiss_all_errors": "Negeer alle fouten", + "dismiss_error": "Negeer fout", "display_options": "Weergaveopties", "display_order": "Weergavevolgorde", "display_original_photos": "Toon originele foto's", @@ -876,9 +948,11 @@ "documentation": "Documentatie", "done": "Klaar", "download": "Downloaden", + "download_action_prompt": "{count} item(s) aan het downloaden", "download_canceled": "Download geannuleerd", "download_complete": "Download voltooid", "download_enqueue": "Download in wachtrij", + "download_error": "Fout bij downloaden", "download_failed": "Download mislukt", "download_finished": "Download voltooid", "download_include_embedded_motion_videos": "Ingesloten video's", @@ -888,15 +962,15 @@ "download_paused": "Download gepauseerd", "download_settings": "Downloaden", "download_settings_description": "Beheer instellingen voor het downloaden van items", + "download_started": "Download gestart", + "download_sucess": "Succesvol gedownload", + "download_sucess_android": "Het bestand is gedownload naar DCIM/Immich", "download_waiting_to_retry": "Wachten om opnieuw te proberen", "downloading": "Downloaden", "downloading_asset_filename": "Downloaden asset {filename}", "downloading_from_icloud": "Media aan het downloaden van iCloud", "downloading_media": "Media aan het downloaden", - "drag_to_reorder": "Sleep om te herschikken", "drop_files_to_upload": "Zet bestanden ergens neer om ze te uploaden", - "duplicate": "Kopieer", - "duplicate_workflow": "Kopieer werkstroom", "duplicates": "Duplicaten", "duplicates_description": "Kies voor iedere groep welke, indien aanwezig, duplicaten zijn.", "duration": "Tijdsduur", @@ -906,15 +980,17 @@ "edit_birthday": "Wijzig verjaardag", "edit_date": "Datum bewerken", "edit_date_and_time": "Datum en tijd bewerken", - "edit_date_and_time_action_prompt": "Datum en tijd bijgewerkt van {count, plural, one {# item} other {# items}}", + "edit_date_and_time_action_prompt": "Datum en tijd bijgewerkt van {count} item(s)", "edit_date_and_time_by_offset": "Wijzigen datum door verschuiving", + "edit_date_and_time_by_offset_interval": "Nieuw datuminterval: {from}-{to}", "edit_description": "Beschrijving bewerken", + "edit_description_prompt": "Selecteer een nieuwe beschrijving:", "edit_exclusion_pattern": "Uitsluitingspatroon bewerken", "edit_faces": "Gezichten bewerken", "edit_key": "Key bewerken", "edit_link": "Link bewerken", "edit_location": "Locatie bewerken", - "edit_location_action_prompt": "Locatie bijgewerkt van {count, plural, one {# item} other {# items}}", + "edit_location_action_prompt": "Locatie bijgewerkt van {count} item(s)", "edit_location_dialog_title": "Locatie", "edit_name": "Naam bewerken", "edit_people": "Mensen bewerken", @@ -923,6 +999,9 @@ "edit_user": "Gebruiker bewerken", "edit_workflow": "Werkstroom bewerken", "editor": "Bewerker", + "editor_close_without_save_prompt": "De wijzigingen worden niet opgeslagen", + "editor_close_without_save_title": "Editor sluiten?", + "editor_confirm_reset_all_changes": "Weet u zeker dat u alle wijzigingen wilt resetten?", "editor_discard_edits_confirm": "Wijzigingen verwijderen", "editor_discard_edits_prompt": "U heeft wijzigingen aangebracht die nog niet zijn opgeslagen. Weet u zeker dat u deze wilt verwijderen?", "editor_discard_edits_title": "Wijzigingen verwijderen?", @@ -947,11 +1026,13 @@ "enabled": "Ingeschakeld", "end_date": "Einddatum", "enqueued": "In de wachtrij", - "enter_wifi_name": "Voer de naam van het wifinetwerk in", + "enter_wifi_name": "Voer de WiFi-naam in", "enter_your_pin_code": "Voer uw pincode in", "enter_your_pin_code_subtitle": "Voer uw pincode in om toegang te krijgen tot de vergrendelde map", "error": "Fout", + "error_change_sort_album": "Sorteervolgorde van album wijzigen mislukt", "error_delete_face": "Fout bij verwijderen van gezicht uit het item", + "error_getting_places": "Fout bij ophalen plaatsen", "error_loading_albums": "Fout bij het laden van albums", "error_loading_image": "Fout bij laden afbeelding", "error_loading_partners": "Fout bij ophalen partners: {error}", @@ -991,9 +1072,8 @@ "failed_to_remove_product_key": "Fout bij het verwijderen van de licentiesleutel", "failed_to_reset_pin_code": "Resetten van pincode mislukt", "failed_to_stack_assets": "Fout bij stapelen van items", - "failed_to_tag_assets": "Fout bij taggen van items", "failed_to_unstack_assets": "Fout bij ontstapelen van items", - "failed_to_update_notification_status": "Kan notificatie­status niet updaten", + "failed_to_update_notification_status": "Kon notificatiestatus niet updaten", "incorrect_email_or_password": "Onjuist e-mailadres of wachtwoord", "library_folder_already_exists": "Dit import­pad bestaat al.", "page_not_found": "Pagina niet gevonden", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Beschrijving toevoegen...", "exif_bottom_sheet_description_error": "Fout bij het bijwerken van de beschrijving", + "exif_bottom_sheet_details": "DETAILS", + "exif_bottom_sheet_location": "LOCATIE", "exif_bottom_sheet_no_description": "Geen beschrijving", + "exif_bottom_sheet_people": "MENSEN", + "exif_bottom_sheet_person_add_person": "Naam toevoegen", "exit_slideshow": "Diavoorstelling sluiten", "expand": "Uitklappen", "expand_all": "Alles uitvouwen", + "experimental_settings_new_asset_list_subtitle": "Werk in uitvoering", + "experimental_settings_new_asset_list_title": "Experimenteel fotoraster inschakelen", + "experimental_settings_subtitle": "Gebruik op eigen risico!", + "experimental_settings_title": "Experimenteel", "expire_after": "Verloopt na", "expired": "Verlopen", "expires_date": "Verloopt {date}", @@ -1103,58 +1191,58 @@ "export_as_json": "Exporteren als JSON", "export_database": "Exporteer database", "export_database_description": "Exporteer de SQLite database", - "exposure_time": "Belichtingstijd", "extension": "Extensie", "external": "Extern", "external_libraries": "Externe bibliotheken", "external_network": "Extern netwerk", - "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven wifinetwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", - "f_number": "Diafragma", + "external_network_sheet_info": "Als je niet verbonden bent met het opgegeven WiFi-netwerk, maakt de app verbinding met de server via de eerst bereikbare URL in de onderstaande lijst, van boven naar beneden", "face_unassigned": "Niet toegewezen", "failed": "Mislukt", "failed_count": "Mislukt: {count}", "failed_to_authenticate": "Authenticatie mislukt", - "failed_to_delete_file": "Bestand verwijderen mislukt", "failed_to_load_assets": "Kan items niet laden", "failed_to_load_folder": "Laden van map mislukt", "favorite": "Favoriet", - "favorite_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan je favorieten", + "favorite_action_prompt": "{count} item(s) toegevoegd aan je favorieten", "favorite_or_unfavorite_photo": "Foto markeren als of verwijderen uit favorieten", "favorites": "Favorieten", + "favorites_page_no_favorites": "Geen favoriete items gevonden", "feature_photo_updated": "Uitgelichte afbeelding bijgewerkt", "features": "Functies", + "features_in_development": "Functies in ontwikkeling", "features_setting_description": "Beheer de app functies", "file_name_or_extension": "Bestandsnaam of extensie", "file_name_text": "Bestandsnaam", + "file_name_with_value": "Bestandsnaam: {file_name}", "file_size": "Bestandsgrootte", "filename": "Bestandsnaam", "filetype": "Bestandstype", "filter": "Filter", + "filter_description": "Filtervoorwaarden voor doel items", "filter_people": "Filteren op persoon", "filter_places": "Filteren op locatie", "filter_tags": "Filteren op label", "filters": "Filters", + "find_them_fast": "Vind ze snel op naam door te zoeken", "first": "Eerste", "fix_incorrect_match": "Onjuiste overeenkomst corrigeren", - "focal_length": "Brandpuntsafstand", "folder": "Map", "folder_not_found": "Map niet gevonden", "folders": "Mappen", - "folders_feature_description": "Bladeren door de map­weergave van de foto's en video's op het bestands­systeem", + "folders_feature_description": "Bladeren door de mapweergave van de foto's en video's op het bestandssysteem", "forgot_pin_code_question": "Pincode vergeten?", "forward": "Vooruit", "free_up_space": "Maak opslag vrij", "free_up_space_description": "Verplaats back-ups van foto's en video's naar de prullenbak van uw apparaat om ruimte vrij te maken. Uw kopieÃĢn op de server blijven veilig.", "free_up_space_settings_subtitle": "Maak opslagruimte vrij op uw apparaat", "full_path": "Volledig pad: {path}", - "full_path_or_folder": "Volledig pad of map", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Deze functie gebruikt externe bronnen van Google om te kunnen werken.", "general": "Algemeen", "geolocation_instruction_location": "Klik op een item met gps-coÃļrdinaten om de locatie te gebruiken, of kies een locatie direct op de kaart", "get_help": "Hulp vragen", "get_people_error": "Fout bij ophalen mensen", - "get_wifiname_error": "Kon de naam van het netwerk niet ophalen. Zorg ervoor dat je de benodigde machtig­ingen hebt verleend en verbonden bent met een wifinetwerk", + "get_wifiname_error": "Kon de WiFi-naam niet ophalen. Zorg ervoor dat je de benodigde machtigingen hebt verleend en verbonden bent met een WiFi-netwerk", "getting_started": "Aan de slag", "go_back": "Ga terug", "go_to_folder": "Ga naar map", @@ -1168,6 +1256,7 @@ "group_owner": "Groepeer op eigenaar", "group_places_by": "Groepeer plaatsen op...", "group_year": "Groepeer op jaar", + "haptic_feedback_switch": "Aanraaktrillingen inschakelen", "haptic_feedback_title": "Aanraaktrillingen", "has_quota": "Heeft limiet", "hash_asset": "Hash item", @@ -1188,12 +1277,29 @@ "hide_schema": "Schema verbergen", "hide_text_recognition": "Tekst­herkenning verbergen", "hide_unnamed_people": "Verberg mensen zonder naam", + "home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.", + "home_page_add_to_album_err_local": "Lokale items kunnen nog niet aan albums worden toegevoegd, overslaan", + "home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.", + "home_page_album_err_partner": "Partner items kunnen nog niet toegevoegd worden aan een album, overslaan", + "home_page_archive_err_local": "Lokale items kunnen nog niet gearchiveerd worden, overslaan", + "home_page_archive_err_partner": "Partner items kunnen niet gearchiveerd worden, overslaan", "home_page_building_timeline": "Tijdlijn opbouwen", + "home_page_delete_err_partner": "Partner items kunnen niet verwijderd worden, overslaan", + "home_page_delete_remote_err_local": "Lokale items staan in verwijder selectie externe items, overslaan", + "home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan", + "home_page_favorite_err_partner": "Partner items kunnen nog niet als favoriet gemarkeerd worden, overslaan", + "home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album", + "home_page_locked_error_local": "Kan lokale bestanden niet naar de vergrendelde map verplaatsen, sla over", + "home_page_locked_error_partner": "Kan partnerbestanden niet naar de vergrendelde map verplaatsen, sla over", + "home_page_share_err_local": "Lokale items kunnen niet via een link gedeeld worden, overslaan", + "home_page_upload_err_limit": "Kan maximaal 30 items tegelijk uploaden, overslaan", "host": "Host", "hour": "Uur", "hours": "Uren", "id": "ID", "idle": "Inactief", + "ignore_icloud_photos": "Negeer iCloud foto's", + "ignore_icloud_photos_description": "Foto's die op iCloud zijn opgeslagen, worden niet geÃŧpload naar de Immich server", "image": "Afbeelding", "image_alt_text_date": "{isVideo, select, true {Video} other {Afbeelding}} genomen op {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Afbeelding}} genomen met {person1} op {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1} en {person2} op {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1}, {person2}, en {person3} op {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Afbeelding}} genomen in {city}, {country} met {person1}, {person2}, en {additionalCount, number} anderen op {date}", + "image_saved_successfully": "Afbeelding opgeslagen", + "image_viewer_page_state_provider_download_started": "Download gestart", + "image_viewer_page_state_provider_download_success": "Download succesvol", + "image_viewer_page_state_provider_share_error": "Error bij delen", "immich_logo": "Immich-logo", "immich_web_interface": "Immich Web Interface", "import_from_json": "Importeren vanuit JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Individuele deellink", "individual_shares": "Individuele deellinks", "info": "Info", - "integrity_checks": "Integriteitscontroles", "interval": { "day_at_onepm": "Iedere dag om 13 uur", "hours": "{hours, plural, one {Ieder uur} other {Iedere {hours, number} uren}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Ongeldig datumformaat", "invite_people": "Mensen uitnodigen", "invite_to_album": "Uitnodigen voor album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Ophalen gelukt op {dateTime}", + "ios_debug_info_last_sync_at": "Laatst gesynchroniseerd {dateTime}", + "ios_debug_info_no_processes_queued": "Geen achtergrondprocessen in de wachtrij", + "ios_debug_info_no_sync_yet": "Er is nog geen achtergrondsynchronisatie uitgevoerd", + "ios_debug_info_processes_queued": "{count, plural, one {{count} achtergrondproces in de wachtrij} other {{count} achtergrondprocessen in de wachtrij}}", + "ios_debug_info_processing_ran_at": "Verwerking uitgevoerd op {dateTime}", "items_count": "{count, plural, one {# item} other {# items}}", "jobs": "Taken", + "json_editor": "JSON bewerker", + "json_error": "JSON fout", "keep": "Behouden", "keep_albums": "Houd albums bij", "keep_albums_count": "Het behouden van {count} {count, plural, one {album} other {albums}}", @@ -1259,29 +1375,28 @@ "leave": "Verlaten", "leave_album": "Verlaat album", "lens_model": "Lensmodel", - "less": "Minder", "let_others_respond": "Laat anderen reageren", "level": "Niveau", "library": "Bibliotheek", "library_add_folder": "Map toevoegen", "library_edit_folder": "Map bewerken", + "library_options": "Bibliotheek opties", + "library_page_device_albums": "Albums op apparaat", + "library_page_new_album": "Nieuw album", "library_page_sort_asset_count": "Aantal items", "library_page_sort_created": "Meest recent gemaakt", "library_page_sort_last_modified": "Laatst aangepast", "library_page_sort_title": "Albumtitel", "licenses": "Licenties", "light": "Licht", - "light_theme": "Wissel naar licht thema", + "light_theme": "Wissel naar lichte thema", "like": "Vind ik leuk", "like_deleted": "Like verwijderd", - "link": "Link", "link_motion_video": "Koppel bewegende video", "link_to_docs": "Raadpleeg voor meer informatie de documentatie.", "link_to_oauth": "Koppel OAuth", "linked_oauth_account": "Gekoppeld OAuth account", "list": "Lijst", - "live": "Operationeel", - "load_more": "Meer laden", "loading": "Laden", "loading_search_results_failed": "Laden van zoekresultaten mislukt", "local": "Lokaal", @@ -1290,10 +1405,10 @@ "local_id": "Lokaal ID", "local_media_summary": "Lokale media samenvatting", "local_network": "Lokaal netwerk", - "local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven wifinetwerk wordt gebruikt", + "local_network_sheet_info": "De app maakt verbinding met de server via deze URL wanneer het opgegeven WiFi-netwerk wordt gebruikt", "location": "Locatie", "location_permission": "Locatietoestemming", - "location_permission_content": "Om de functie voor automatische serverwissel te gebruiken, heeft Immich toegang tot de exacte locatie nodig om de naam van het huidige wifinetwerk te kunnen bepalen", + "location_permission_content": "Om de functie voor automatische serverwissel te gebruiken, heeft Immich toegang tot de exacte locatie nodig om de naam van het huidige WiFi-netwerk te kunnen bepalen", "location_picker_choose_on_map": "Kies op kaart", "location_picker_latitude_error": "Voer een geldige breedtegraad in", "location_picker_latitude_hint": "Voer hier je breedtegraad in", @@ -1310,9 +1425,11 @@ "login": "Inloggen", "login_disabled": "Aanmelding uitgeschakeld", "login_form_api_exception": "API fout. Controleer de server URL en probeer opnieuw.", + "login_form_back_button_text": "Terug", "login_form_email_hint": "jouwemail@email.com", "login_form_endpoint_hint": "http://jouw-server-ip:poort", "login_form_endpoint_url": "Server-URL", + "login_form_err_http": "Voer http:// of https:// in", "login_form_err_invalid_email": "Ongeldig e-mailadres", "login_form_err_invalid_url": "Ongeldige URL", "login_form_err_leading_whitespace": "Spatie aan het begin", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Fout bij inloggen; controleer server-URL, e-mailadres en wachtwoord", "login_form_handshake_exception": "Er was een Handshake Exception met de server. Schakel ondersteuning voor zelfondertekende certificaten in bij de instellingen als u een zelfondertekend certificaat gebruikt.", "login_form_password_hint": "wachtwoord", - "login_form_server_empty": "Voer een server-URL in", - "login_form_server_error": "Kan geen verbinding maken met de server", - "login_has_been_disabled": "Inloggen is uitgeschakeld", + "login_form_save_login": "Ingelogd blijven", + "login_form_server_empty": "Voer een server-URL in.", + "login_form_server_error": "Kan geen verbinding maken met de server.", + "login_has_been_disabled": "Inloggen is uitgeschakeld.", "login_password_changed_error": "Er is een fout opgetreden bij het bijwerken van je wachtwoord", "login_password_changed_success": "Wachtwoord succesvol bijgewerkt", "logout_all_device_confirmation": "Weet je zeker dat je wilt uitloggen op alle apparaten?", @@ -1333,17 +1451,18 @@ "longitude": "Lengtegraad", "look": "Uiterlijk", "loop_videos": "Video's herhalen", - "loop_videos_description": "Inschakelen om video's automatisch te herhalen in de detail­weergave.", - "main_branch_warning": "Je gebruikt een ontwikkelings­versie. We raden je ten zeerste aan een release­versie te gebruiken!", + "loop_videos_description": "Inschakelen om video's automatisch te herhalen in de detailweergave.", + "main_branch_warning": "Je gebruikt een ontwikkelingsversie. We raden je ten zeerste aan een releaseversie te gebruiken!", "main_menu": "Hoofdmenu", - "maintenance_action_restore": "Database wordt hersteld", + "maintenance_action_restore": "Database herstellen", "maintenance_description": "Immich is in de onderhouds­modus gezet.", "maintenance_end": "Onderhouds­modus beÃĢindigen", + "maintenance_end_error": "Onderhouds­modus beÃĢindigen mislukt.", "maintenance_logged_in_as": "Momenteel ingelogd als {user}", "maintenance_restore_from_backup": "Herstellen vanaf backup", "maintenance_restore_library": "Bibliotheek herstellen", - "maintenance_restore_library_confirm": "Als dit er goed uit ziet, ga dan verder om de back-up terug te zetten!", - "maintenance_restore_library_description": "Database wordt hersteld", + "maintenance_restore_library_confirm": "Als dit er goed uit ziet ga dan verder om de backup terug te zetten!", + "maintenance_restore_library_description": "Database herstellen", "maintenance_restore_library_folder_has_files": "{folder} heeft {count} map(pen)", "maintenance_restore_library_folder_no_files": "{folder} mist bestanden!", "maintenance_restore_library_folder_pass": "leesbaar en schrijfbaar", @@ -1353,8 +1472,8 @@ "maintenance_restore_library_hint_regenerate_later": "Deze kun je later opnieuw genereren in de instellingen", "maintenance_restore_library_hint_storage_template_missing_files": "Gebruik je een opslagtemplate? Je mist misschien bestanden", "maintenance_restore_library_loading": "Integriteitscontrole en heuristieken ladenâ€Ļ", - "maintenance_task_backup": "Back-up van bestaande database makenâ€Ļ", - "maintenance_task_migrations": "Bezig met databasemigratiesâ€Ļ", + "maintenance_task_backup": "Backup van bestaande database makenâ€Ļ", + "maintenance_task_migrations": "Bezig met database migratiesâ€Ļ", "maintenance_task_restore": "De gekozen backup terugzettenâ€Ļ", "maintenance_task_rollback": "Terugzetten backup mislukt, herstelpunt terugzettenâ€Ļ", "maintenance_title": "TÄŗdelÄŗk niet beschikbaar", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Open instellingen", "manage_media_access_subtitle": "Toestaan dat de Immich app mediabestanden mag beheren en verplaatsen.", "manage_media_access_title": "Toegang tot mediabeheer", + "manage_shared_links": "Beheer gedeelde links", "manage_sharing_with_partners": "Beheer delen met partners", "manage_the_app_settings": "Beheer de appinstellingen", "manage_your_account": "Beheer je account", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Beheer je ingelogde apparaten", "manage_your_oauth_connection": "Beheer je OAuth-koppeling", "map": "Kaart", + "map_assets_in_bounds": "{count, plural, =0 {Geen fotos in dit gebied}one {# foto} other {# foto's}}", "map_cannot_get_user_location": "Kan locatie van de gebruiker niet ophalen", + "map_location_dialog_yes": "Ja", "map_location_picker_page_use_location": "Gebruik deze locatie", "map_location_service_disabled_content": "Locatie service moet ingeschakeld zijn om items van je huidige locatie weer te geven. Wil je het nu inschakelen?", "map_location_service_disabled_title": "Locatie service uitgeschakeld", - "map_marker_for_image": "Kaartmarkering voor afbeelding gemaakt in {city}, {country}", + "map_marker_for_images": "Kaartmarkering voor afbeeldingen gemaakt in {city}, {country}", "map_marker_with_image": "Kaartmarkering met afbeelding", "map_no_location_permission_content": "Locatietoestemming is nodig om items van je huidige locatie weer te geven. Wil je dit nu toestaan?", "map_no_location_permission_title": "Locatietoestemming geweigerd", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Afgelopen {days} dagen", "map_settings_date_range_option_year": "Afgelopen jaar", "map_settings_date_range_option_years": "Afgelopen {years} jaar", + "map_settings_dialog_title": "Kaartinstellingen", "map_settings_include_show_archived": "Toon gearchiveerde", "map_settings_include_show_partners": "Toon partners", "map_settings_only_show_favorites": "Toon enkel favorieten", "map_settings_theme_settings": "Kaartthema", + "map_zoom_to_see_photos": "Zoom uit om foto's te zien", "mark_all_as_read": "Alles markeren als gelezen", + "mark_as_read": "Markeren als gelezen", "marked_all_as_read": "Allen gemarkeerd als gelezen", "matches": "Overeenkomsten", "matching_assets": "Overeenkomende assets", - "media_chrome": { - "auto": "Automatisch", - "captions": "Ondertiteling", - "captions_off": "Uit", - "closed_captions": "Ondertiteling", - "decode_error": "Decodeerfout", - "disable_captions": "Ondertiteling uitzetten", - "enable_captions": "Ondertiteling aanzetten", - "enter_fullscreen_mode": "Volledig scherm openen", - "exit_fullscreen_mode": "Volledig scherm sluiten", - "loop": "Herhalen", - "media_error_description": "Een mediafout heeft ervoor gezorgd dat het afspelen is afgebroken. De media is mogelijk beschadigd, of uw browser ondersteunt dit formaat niet.", - "media_loading": "media wordt geladen", - "mute": "Dempen", - "network_error": "Netwerkfout", - "network_error_description": "Vanwege een netwerkfout is het downloaden van de media mislukt.", - "not_supported_error": "Niet ondersteunde bron", - "playback_rate": "Afspeelsnelheid", - "playback_rate_current": "huidige afspeelsnelheid", - "playback_rate_value": "Afspeelsnelheid {playbackRate}", - "playback_time": "afspeeltijd", - "quality": "Kwaliteit", - "second": "seconde", - "seconds": "seconden", - "time_value_of_total_time": "{currentTime} van {totalTime}", - "time_value_remaining": "nog {time} te gaan", - "unmute": "Dempen opheffen", - "unsupported_error_description": "Er is een niet-ondersteunde fout opgetreden. De server of het netwerk is uitgevallen, of je browser ondersteunt dit formaat niet.", - "video_not_loaded_unknown_time": "video niet geladen, onbekende tijd.", - "video_player": "videospeler", - "volume": "volume" - }, "media_type": "Mediatype", "memories": "Herinneringen", "memories_all_caught_up": "Je bent helemaal bij", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Wil je deze mensen samenvoegen? Deze actie is onomkeerbaar.", "merge_people_successfully": "Mensen succesvol samengevoegd", "merged_people_count": "{count, plural, one {# person} other {# mensen}} samengevoegd", - "minFaces": "Minimum gezichten", - "minFaces_description": "Het minimale aantal herkende gezichten om een persoon weer te geven", "minimize": "Minimaliseren", "minute": "Minuut", "minutes": "Minuten", + "mirror_horizontal": "Horizontaal", + "mirror_vertical": "Verticaal", "missing": "Missend", "mobile_app": "Mobiele app", "mobile_app_download_onboarding_note": "Download de mobiele app via de onderstaande opties", "model": "Model", - "modify_date": "Bewerkt", "month": "Maand", + "monthly_title_text_date_format": "MMMM y", "more": "Meer", - "motion": "Beweging", "move": "Verplaats", + "move_down": "Naar beneden verplaatsen", "move_off_locked_folder": "Verplaats uit vergrendelde map", "move_to": "Verplaatsen naar", "move_to_device_trash": "Naar prullenbak van apparaat", - "move_to_lock_folder_action_prompt": "{count, plural, one {# item} other {# items}} toegevoegd aan de vergrendelde map", + "move_to_lock_folder_action_prompt": "{count} item(s) toegevoegd aan de vergrendelde map", "move_to_locked_folder": "Verplaats naar vergrendelde map", "move_to_locked_folder_confirmation": "Deze foto’s en video’s worden uit alle albums verwijderd en zijn alleen te bekijken in de vergrendelde map", + "move_up": "Naar boven verplaatsen", + "moved_to_archive": "{count, plural, one {# item} other {# items}} verplaatst naar archief", + "moved_to_library": "{count, plural, one {# item} other {# items}} verplaatst naar bibliotheek", "moved_to_trash": "Naar de prullenbak verplaatst", + "multiselect_grid_edit_date_time_err_read_only": "Kan datum van alleen-lezen item(s) niet wijzigen, overslaan", + "multiselect_grid_edit_gps_err_read_only": "Kan locatie van alleen-lezen item(s) niet wijzigen, overslaan", "mute_memories": "Herinneringen dempen", "my_albums": "Mijn albums", - "my_immich_description": "Huidige pagina kopiÃĢren als Mijn Immich link", - "my_immich_title": "Mijn Immich link", "name": "Naam", "name_or_nickname": "Naam of gebruikersnaam", "name_required": "Naam is verplicht", @@ -1480,11 +1576,12 @@ "never": "Nooit", "new_album": "Nieuw album", "new_api_key": "Nieuwe API-sleutel", - "new_feature": "Nieuwe functie", + "new_date_range": "Nieuw datumbereik", "new_password": "Nieuw wachtwoord", "new_person": "Nieuw persoon", "new_pin_code": "Nieuwe pincode", "new_pin_code_subtitle": "Dit is de eerste keer dat u de vergrendelde map opent. Stel een pincode in om deze pagina veilig te openen", + "new_timeline": "Nieuwe tijdlijn", "new_update": "Nieuwe update", "new_user_created": "Nieuwe gebruiker aangemaakt", "new_version_available": "NIEUWE VERSIE BESCHIKBAAR", @@ -1492,6 +1589,7 @@ "next": "Volgende", "next_memory": "Volgende herinnering", "no": "Nee", + "no_actions_added": "Geen acties toegevoegd", "no_albums_found": "Geen albums gevonden", "no_albums_message": "Maak een album om je foto's en video's te organiseren", "no_albums_with_name_yet": "Het lijkt erop dat je nog geen albums met deze naam hebt.", @@ -1500,37 +1598,35 @@ "no_assets_message": "Klik hier om je eerste foto te uploaden", "no_assets_to_show": "Geen foto's om te laten zien", "no_cast_devices_found": "Geen cast-apparaten gevonden", - "no_checksum_local": "Geen controlegetal beschikbaar - kan lokale assets niet ophalen", - "no_checksum_remote": "Geen controlegetal beschikbaar - kan online assets niet ophalen", + "no_checksum_local": "Geen checksum beschikbaar - kan lokale assets niet ophalen", + "no_checksum_remote": "Geen checksum beschikbaar - kan online assets niet ophalen", + "no_configuration_needed": "Geen configuratie nodig", "no_devices": "Geen geautoriseerde apparaten", "no_duplicates_found": "Er zijn geen duplicaten gevonden.", "no_exif_info_available": "Geen exif info beschikbaar", "no_explore_results_message": "Upload meer foto's om je verzameling te verkennen.", "no_favorites_message": "Voeg favorieten toe om snel je beste foto's en video's te vinden", + "no_filters_added": "Geen filters toegevoegd", "no_libraries_message": "Maak een externe bibliotheek om je foto's en video's te bekijken", - "no_local_assets_found": "Geen lokale assets gevonden met dit controlegetal", + "no_local_assets_found": "Geen lokale assets gevonden met deze checksum", + "no_location_set": "Geen locatie ingesteld", "no_locked_photos_message": "Foto’s en video’s in de vergrendelde map zijn verborgen en worden niet weergegeven wanneer je door je bibliotheek bladert of zoekt.", "no_name": "Geen naam", "no_notifications": "Geen meldingen", "no_people_found": "Geen mensen gevonden", "no_places": "Geen plaatsen", - "no_remote_assets_found": "Geen online assets gevonden met dit controlegetal", + "no_remote_assets_found": "Geen online assets gevonden met deze checksum", "no_results": "Geen resultaten", "no_results_description": "Probeer een synoniem of een algemener zoekwoord", "no_shared_albums_message": "Maak een album om foto's en video's te delen met mensen in je netwerk", - "no_steps": "Nog geen stappen toegevoegd", + "no_uploads_in_progress": "Geen uploads bezig", "none": "Geen", "not_allowed": "Niet toegestaan", "not_available": "n.v.t.", "not_in_any_album": "Niet in een album", "not_selected": "Niet geselecteerd", - "not_set": "Nog niet ingesteld", "notes": "Opmerkingen", "nothing_here_yet": "Hier staan nog geen items", - "notification_backup_reliability": "Schakel meldingen in om de betrouwbaarheid van back-ups op de achtergrond te verbeteren", - "notification_enabled_list_tile_content": "Immich gebruikt notificaties voor achtergrondback-ups. Beheer deze in je apparaatinstellingen.", - "notification_enabled_list_tile_open_button": "Instellingen openen", - "notification_enabled_list_tile_title": "Notificaties ingeschakeld", "notification_permission_dialog_content": "Om meldingen in te schakelen, ga naar Instellingen en selecteer toestaan.", "notification_permission_list_tile_content": "Geef toestemming om meldingen te versturen.", "notification_permission_list_tile_enable_button": "Meldingen inschakelen", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Obtainium instellen", "obtainium_configurator_instructions": "Gebruik Obtainium om de Android-app recht­streeks vanuit Immich's GitHub-releases te installeren en bij te werken. Maak een API-sleutel aan en selecteer een variant om je Obtainium-configuratielink te maken", "ocr": "OCR", - "ocr_body": "Immich leest nu de tekst in je foto's, zodat je ze kunt zoeken op basis van de inhoud.", - "ocr_title": "Zoek tekst in je foto's", "official_immich_resources": "OfficiÃĢle Immich bronnen", "offline": "Offline", "offset": "Verrekening", @@ -1562,8 +1656,6 @@ "open": "Openen", "open_calendar": "Open kalender", "open_in_browser": "Openen in browser", - "open_in_immich_body": "Stel Immich in als je galerij op Android om foto's rechtstreeks vanuit andere apps te openen.", - "open_in_immich_title": "Open foto's in Immich", "open_in_map_view": "Openen in kaartweergave", "open_in_openstreetmap": "Openen in OpenStreetMap", "open_the_search_filters": "Open de zoekfilters", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organiseren in albums", "organize_into_albums_description": "Bestaande foto's in albums plaatsen met de huidige synchronisatie-instellingen", "organize_your_library": "Organiseer je bibliotheek", - "orientation": "OriÃĢntatie", "original": "origineel", "other": "Overige", "other_devices": "Andere apparaten", + "other_entities": "Andere entities", "other_variables": "Andere variabelen", "owned": "Eigenaar", "owner": "Eigenaar", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Al je foto's en video's behalve die in het archief of de prullenbak", "partner_can_access_location": "De locatie waar je foto's zijn genomen", "partner_list_user_photos": "Foto's van {user}", + "partner_list_view_all": "Bekijk alle", "partner_page_empty_message": "Je foto's zijn nog niet gedeeld met een partner.", "partner_page_no_more_users": "Geen gebruikers meer om toe te voegen", + "partner_page_partner_add_failed": "Partner toevoegen mislukt", "partner_page_select_partner": "Selecteer partner", + "partner_page_shared_to_title": "Gedeeld met", "partner_page_stop_sharing_content": "{partner} zal geen toegang meer hebben tot je fotos's.", "partner_sharing": "Delen met partner", "partners": "Partners", @@ -1609,6 +1704,8 @@ "people": "Mensen", "people_edits_count": "{count, plural, one {# persoon} other {# mensen}} bijgewerkt", "people_feature_description": "Bladeren door foto's en video's gegroepeerd op personen", + "people_selected": "{count, plural, one {# persoon geselecteerd} other {# mensen geselecteerd}}", + "people_sidebar_description": "Toon een link naar Mensen in de zijbalk", "permanent_deletion_warning": "Waarschuwing voor permanent verwijderen", "permanent_deletion_warning_setting_description": "Toon een waarschuwing bij het permanent verwijderen van items", "permanently_delete": "Permanent verwijderen", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# item} other {# items}} permanent verwijderd", "permission": "Rechten", "permission_empty": "Je rechten mogen niet leeg zijn", + "permission_onboarding_back": "Terug", + "permission_onboarding_continue_anyway": "Toch doorgaan", + "permission_onboarding_get_started": "Aan de slag", + "permission_onboarding_go_to_settings": "Ga naar instellingen", + "permission_onboarding_permission_denied": "Toestemming geweigerd. Geef toestemming tot foto's en video's in je Instellingen om Immich te kunnen gebruiken.", + "permission_onboarding_permission_granted": "Toestemming verleend. Je bent helemaal klaar.", + "permission_onboarding_permission_limited": "Beperkte toestemming. Geef toestemming tot foto's en video's in Instellingen om Immich een back-up te laten maken van je galerij en deze te beheren.", + "permission_onboarding_request": "Immich heeft toestemming nodig om je foto's en video's te bekijken.", "person": "Persoon", "person_age_months": "{months, plural, one {# maand} other {# maanden}} oud", - "person_age_year_months": "1 jaar, {months, plural, one {# maand} other {# maanden}} oud", + "person_age_year_months": "1 year, {months, plural, one {# maand} other {# maanden}} oud", "person_age_years": "{years, plural, other {# jaar}} oud", "person_birthdate": "Geboren op {date}", "person_hidden": "{name}{hidden, select, true { (verborgen)} other {}}", "person_recognized": "Persoon herkend", + "person_selected": "Persoon geselecteerd", "photo_shared_all_users": "Het lijkt erop dat je foto's met alle gebruikers zijn gedeeld, of dat je geen gebruikers hebt om mee te delen.", "photos": "Foto's", "photos_and_videos": "Foto's & video's", + "photos_count": "{count, plural, one {{count, number} foto} other {{count, number} foto's}}", "photos_from_previous_years": "Foto's van voorgaande jaren", + "photos_only": "Enkel foto's", "pick_a_location": "Kies een locatie", "pick_custom_range": "Aangepast bereik", "pick_date_range": "Selecteer een datumbereik", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Originele video afspelen in plaats van de getranscodeerde video's. Als het origineel niet compatibel is, zou deze verkeerd weergegeven kunnen worden.", "play_transcoded_video": "Getranscodeerde video afspelen", "please_auth_to_access": "Verifieer om toegang te krijgen", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Met een filter kunnen vervolgstappen van gebeurtenissen voorkomen worden", "port": "Poort", "preferences_settings_subtitle": "Beheer de voorkeuren van de app", "preferences_settings_title": "Voorkeuren", @@ -1665,11 +1771,11 @@ "privacy": "Privacy", "profile": "Profiel", "profile_drawer_app_logs": "Logs", + "profile_drawer_client_server_up_to_date": "App en server zijn up-to-date", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Alleen-lezen-modus ingeschakeld. Druk lang op je profielfoto om te verlaten.", "profile_image_of_user": "Profielfoto van {user}", "profile_picture_set": "Profielfoto ingesteld.", - "projection_type": "Projectie", "public_album": "Openbaar album", "public_share": "Openbare deellink", "purchase_account_info": "Ondersteuner", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Supporterstatus", "purchase_individual_title": "Gebruiker", "purchase_input_suggestion": "Heb je een licentiesleutel? Voer deze hieronder in", + "purchase_license_subtitle": "Koop Immich om de verdere ontwikkeling van de service te ondersteunen", "purchase_lifetime_description": "Levenslange aankoop", "purchase_option_title": "AANKOOPMOGELIJKHEDEN", "purchase_panel_info_1": "Het bouwen van Immich kost veel tijd en moeite, en we hebben fulltime engineers die eraan werken om het zo goed mogelijk te maken. Onze missie is om open-source software en ethische bedrijfspraktijken een duurzame inkomstenbron te laten worden voor ontwikkelaars en een ecosysteem te creÃĢren dat de privacy respecteert met echte alternatieven voor uitbuitende cloudservices.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# item} other {# items}} opnieuw toegewezen aan een nieuw persoon", "reassing_hint": "Geselecteerde items toewijzen aan een bestaand persoon", "recent": "Recent", + "recent_albums": "Recente albums", "recent_searches": "Recente zoekopdrachten", "recently_added": "Onlangs toegevoegd", - "recently_added_body": "Ga direct naar een aparte pagina met al je recent toegevoegde items.", - "recently_added_description": "Blader door je items, op volgorde van wanneer ze zijn toegevoegd aan Immich", "recently_added_page_title": "Recent toegevoegd", - "recently_added_title": "Recent toegevoegd", "recently_taken": "Recent genomen", + "recently_taken_page_title": "Recent Genomen", "refresh": "Vernieuwen", "refresh_encoded_videos": "Vernieuw gecodeerde video's", "refresh_faces": "Vernieuw gezichten", "refresh_metadata": "Vernieuw metadata", "refresh_thumbnails": "Vernieuw thumbnails", "refreshed": "Verniewd", + "refreshes_every_file": "Vernieuwt alle bestaande en nieuwe bestanden", "refreshing_encoded_video": "Gecodeerde video aan het vernieuwen", "refreshing_faces": "Gezichten aan het vernieuwen", "refreshing_metadata": "Metadata aan het vernieuwen", @@ -1745,38 +1852,45 @@ "remove_assets_shared_link_confirmation": "Weet je zeker dat je {count, plural, one {# item} other {# items}} uit deze gedeelde link wilt verwijderen?", "remove_assets_title": "Items verwijderen?", "remove_custom_date_range": "Aangepast datumbereik verwijderen", - "remove_filter": "Verwijder filter", + "remove_deleted_assets": "Verwijder offline bestanden", "remove_from_album": "Verwijderen uit album", - "remove_from_album_action_prompt": "{count, plural, one {# item} other {# items}} verwijderd uit het album", + "remove_from_album_action_prompt": "{count} item(s) verwijderd uit het album", "remove_from_favorites": "Verwijderen uit favorieten", - "remove_from_lock_folder_action_prompt": "{count, plural, one {# item} other {# items}} verwijderd uit de vergrendelde map", + "remove_from_lock_folder_action_prompt": "{count} item(s) verwijderd uit de vergrendelde map", "remove_from_locked_folder": "Verwijder uit de vergrendelde map", "remove_from_locked_folder_confirmation": "Weet je zeker dat je deze foto's en video's uit de vergrendelde map wilt verplaatsen? Ze zijn dan weer zichtbaar in je bibliotheek.", "remove_from_shared_link": "Verwijderen uit gedeelde link", "remove_memory": "Herinnering verwijderen", "remove_photo_from_memory": "Foto uit deze herinnering verwijderen", "remove_tag": "Verwijder tag", + "remove_url": "Verwijder URL", "remove_user": "Verwijder gebruiker", "removed_api_key": "API-sleutel verwijderd: {name}", "removed_from_archive": "Verwijderd uit archief", "removed_from_favorites": "Verwijderd uit favorieten", "removed_from_favorites_count": "{count, plural, other {# verwijderd}} uit favorieten", "removed_memory": "Herinnering verwijderd", + "removed_photo_from_memory": "Foto verwijderd uit herinnering", "removed_tagged_assets": "Tag verwijderd van {count, plural, one {# item} other {# items}}", "rename": "Hernoemen", + "repair": "Repareren", + "repair_no_results_message": "Niet bijgehouden en ontbrekende bestanden zullen hier verschijnen", + "replace_with_upload": "Vervangen door upload", "repository": "Repository", "require_password": "Wachtwoord vereisen", + "require_user_to_change_password_on_first_login": "Vereisen dat de gebruiker het wachtwoord wijzigt bij de eerste keer inloggen", "rescan": "Herscannen", "reset": "Resetten", "reset_password": "Wachtwoord resetten", "reset_people_visibility": "Zichtbaarheid mensen resetten", "reset_pin_code": "Reset pincode", "reset_pin_code_description": "Als je jouw pincode bent vergeten, neem dan contact op met de administrator van de server om deze te resetten", + "reset_pin_code_success": "Pincode succesvol gereset", "reset_pin_code_with_password": "Je kan je pincode altijd resetten met je wachtwoord", "reset_sqlite": "SQLite database resetten", "reset_sqlite_clear_app_data": "Wis gegevens", "reset_sqlite_confirmation": "Weet je zeker dat je de app-gegevens wilt wissen? Hiermee worden alle instellingen verwijderd en word je uitgelogd.", - "reset_sqlite_confirmation_note": "Let op: je moet de app opnieuw opstarten nadat je deze hebt gewist.", + "reset_sqlite_confirmation_note": "Let op: Je moet de app opnieuw opstarten nadat je deze hebt gewist.", "reset_sqlite_done": "App data is gewist. Start Immich opnieuw op en log opnieuw in.", "reset_sqlite_success": "De SQLite database is succesvol gereset", "reset_to_default": "Resetten naar standaard", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Alle duplicaten opgelost", "restore": "Herstellen", "restore_all": "Herstel alle", + "restore_trash_action_prompt": "{count} item(s) teruggehaald uit de prullenbak", "restore_user": "Gebruiker herstellen", "restored_asset": "Item hersteld", "resume": "Hervatten", - "resume_paused_jobs": "{count, plural, one {# gepauzeerde taak} other {# gepauzeerde taken}} hervatten", + "resume_paused_jobs": "Hervat {count, plural, one {# gepauseerde taak} other {# gepauseerde taken}}", + "retry_upload": "Opnieuw uploaden", "review_duplicates": "Controleer duplicaten", "review_large_files": "Grote bestanden beoordelen", "role": "Rol", @@ -1796,6 +1912,7 @@ "role_viewer": "Bekijker", "running": "Actief", "save": "Opslaan", + "save_to_gallery": "Opslaan in galerij", "saved": "Opgeslagen", "saved_api_key": "API-sleutel opgeslagen", "saved_profile": "Profiel opgeslagen", @@ -1806,10 +1923,9 @@ "scan": "Scan", "scan_all_libraries": "Scan alle bibliotheken", "scan_library": "Scan", + "scan_settings": "Scaninstellingen", "scanning": "Scannen", "scanning_for_album": "Scannen voor album...", - "screencast_mode_description": "Toetsenbord en muis indicatoren laten zien op het scherm", - "screencast_mode_title": "Schakel de schermopnamemodus in/uit", "search": "Zoeken", "search_albums": "Zoek albums", "search_by_context": "Zoeken op context", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Wandelen in Sapa", "search_by_filename": "Zoeken op bestandsnaam of -extensie", "search_by_filename_example": "b.v. IMG_1234.JPG of PNG", - "search_by_full_path": "Zoeken op volledig pad of map", - "search_by_full_path_example": "/John/Projects/3D_Printen/2026-07-01 - je kunt zoeken op Projecten, 3D, Printen, 2026 enz.", "search_by_ocr": "Zoeken op tekst herkend door OCR", "search_by_ocr_example": "Kaneel", "search_camera_lens_model": "Zoek cameralensâ€Ļ", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Selecteer locatie", "search_filter_media_type": "Mediatype", "search_filter_media_type_title": "Selecteer mediatype", + "search_filter_ocr": "Zoeken op tekst herkend door OCR", "search_filter_people_title": "Selecteer mensen", "search_filter_star_rating": "Sterbeoordeling", "search_filter_tags_title": "Labels selecteren", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Geen mensen genaamd \"{name}\"", "search_no_result": "Geen resultaten gevonden, probeer een andere zoekterm of combinatie", "search_options": "Zoekopties", + "search_page_categories": "CategorieÃĢn", + "search_page_motion_photos": "Bewegende foto's", + "search_page_no_objects": "Geen objectgegevens beschikbaar", + "search_page_no_places": "Geen locatiegegevens beschikbaar", + "search_page_screenshots": "Schermafbeelding", "search_page_search_photos_videos": "Zoek naar je foto's en video's", + "search_page_selfies": "Selfies", + "search_page_things": "Dingen", "search_page_view_all_button": "Bekijk alle", + "search_page_your_activity": "Je activiteit", + "search_page_your_map": "Jouw kaart", "search_people": "Zoek mensen", "search_places": "Zoek plaatsen", "search_rating": "Zoeken op beoordeling...", + "search_result_page_new_search_hint": "Nieuwe zoekopdracht", "search_settings": "Zoek instellingen", "search_state": "Zoek staat...", + "search_suggestion_list_smart_search_hint_1": "Slim zoeken is standaard ingeschakeld, om naar metadata te zoeken gebruik ", + "search_suggestion_list_smart_search_hint_2": "m:je-zoekterm", "search_tags": "Tags zoeken...", "search_timezone": "Zoek tijdzone...", "search_type": "Type zoekopdracht", @@ -1868,6 +1995,7 @@ "select_albums": "Selecteer albums", "select_all": "Alles selecteren", "select_all_duplicates": "Selecteer alle duplicaten", + "select_all_in": "Selecteer alles in {group}", "select_avatar_color": "Selecteer avatarkleur", "select_count": "{count, plural, one {Selecteer #} other {Selecteer #}}", "select_cutoff_date": "Selecteer einddatum", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Selecteer uitgelichte foto", "select_from_computer": "Selecteer van computer", "select_keep_all": "Selecteer alles behouden", + "select_library_owner": "Selecteer bibliotheekeigenaar", "select_new_face": "Selecteer nieuw gezicht", "select_people": "Selecteer mensen", "select_person": "Selecteer persoon", "select_person_to_tag": "Selecteer een persoon om te taggen", "select_photos": "Selecteer foto's", - "select_quality": "Selecteer kwaliteit", "select_trash_all": "Selecteer alles naar prullenbak verplaatsen", + "select_user_for_sharing_page_err_album": "Album aanmaken mislukt", "selected": "Geselecteerd", "selected_count": "{count, plural, other {# geselecteerd}}", "selected_gps_coordinates": "Geselecteerde gps-coÃļrdinaten", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Originele afbeelding laden", "setting_image_viewer_preview_subtitle": "Schakel in om een afbeelding met middelgrote resolutie te laden. Schakel uit om alleen het origineel direct te laden of alleen de thumbnail te gebruiken.", "setting_image_viewer_preview_title": "Voorbeeldafbeelding laden", + "setting_image_viewer_title": "Afbeeldingen", "setting_languages_apply": "Toepassen", "setting_languages_subtitle": "Wijzig de taal van de app", + "setting_notifications_notify_failures_grace_period": "Fouten van de achtergrond back-up melden: {duration}", + "setting_notifications_notify_hours": "{count} uur", + "setting_notifications_notify_immediately": "meteen", "setting_notifications_notify_minutes": "{count} minuten", + "setting_notifications_notify_never": "nooit", "setting_notifications_notify_seconds": "{count} seconden", + "setting_notifications_single_progress_subtitle": "Gedetailleerde informatie over de uploadvoortgang per item", + "setting_notifications_single_progress_title": "Gedetailleerde informatie over achtergrond back-ups tonen", "setting_notifications_subtitle": "Voorkeuren voor meldingen beheren", + "setting_notifications_total_progress_subtitle": "Algehele uploadvoortgang (voltooid/totaal aantal items)", + "setting_notifications_total_progress_title": "Totale voortgang van achtergrond back-up tonen", "setting_video_viewer_auto_play_subtitle": "Speel video's automatisch af zodra ze geopend worden", "setting_video_viewer_auto_play_title": "Video's automatisch afspelen", "setting_video_viewer_looping_title": "Herhalen", "setting_video_viewer_original_video_subtitle": "Speel video's altijd in originele kwaliteit af, zelfs als er een getranscodeerd bestand beschikbaar is op de server. Dit kan leiden tot buffering. Video's die lokaal beschikbaar zijn, worden altijd in originele kwaliteit afgespeeld, ongeacht deze instelling.", "setting_video_viewer_original_video_title": "Forceer originele videokwaliteit", "settings": "Instellingen", + "settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen", "settings_saved": "Instellingen opgeslagen", "setup_pin_code": "Stel een pincode in", "share": "Delen", + "share_action_prompt": "{count} item(s) gedeeld", + "share_add_photos": "Foto's toevoegen", + "share_assets_selected": "{count} item(s) geselecteerd", "share_dialog_preparing": "Voorbereiden...", "share_link": "Link delen", - "share_original": "Origineel gebruiken (groot)", - "share_preview": "Voorbeeldafbeelding gebruiken (klein)", - "share_quality_body": "Houd de deelknop ingedrukt om de beeldkwaliteit te kiezen voordat je deelt.", - "share_quality_title": "Kies de gewenste deel kwaliteit", "shared": "Gedeeld", "shared_album_activities_input_disable": "Reactie is uitgeschakeld", "shared_album_activity_remove_content": "Wil je deze activiteit verwijderen?", "shared_album_activity_remove_title": "Verwijder activiteit", "shared_album_section_people_action_error": "Fout bij verlaten/verwijderen uit album", + "shared_album_section_people_action_leave": "Verlaat album", + "shared_album_section_people_action_remove_user": "Verwijder gebruiker van album", "shared_album_section_people_title": "MENSEN", "shared_by": "Gedeeld door", "shared_by_user": "Gedeeld door {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} geÃŧpload", "shared_link_app_bar_title": "Gedeelde links", "shared_link_clipboard_copied_massage": "Gekopieerd naar klembord", + "shared_link_clipboard_text": "Link: {link}\nWachtwoord: {password}", "shared_link_create_error": "Fout bij het maken van een gedeelde link", - "shared_link_custom_url_description": "Krijg toegang tot deze gedeelde link met een aangepaste URL-naam", - "shared_link_custom_url_title": "Aangepaste URL", - "shared_link_custom_url_warning": "Waarschuwing: een aangepaste URL naam met een voorwaartse slash kan zich anders gedragen dan verwacht.", + "shared_link_custom_url_description": "Krijg toegang tot deze gedeelde link met een aangepaste URL", "shared_link_edit_description_hint": "Voer beschrijving voor de gedeelde link in", "shared_link_edit_expire_after_option_day": "1 dag", "shared_link_edit_expire_after_option_days": "{count} dagen", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Gedeeld met {partner}", "sharing": "Delen", "sharing_enter_password": "Voer het wachtwoord in om deze pagina te bekijken.", + "sharing_page_album": "Gedeelde albums", + "sharing_page_description": "Maak gedeelde albums om foto's en video's te delen met mensen in je netwerk.", + "sharing_page_empty_list": "LEGE LIJST", + "sharing_sidebar_description": "Toon een link naar Delen in de zijbalk", + "sharing_silver_appbar_create_shared_album": "Gedeeld album maken", + "sharing_silver_appbar_share_partner": "Delen met partner", "shift_to_permanent_delete": "druk op ⇧ om items permanent te verwijderen", "show_album_options": "Toon albumopties", "show_albums": "Toon albums", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Toon in tijdlijn", "show_in_timeline_setting_description": "Toon foto's en video's van deze gebruiker in je tijdlijn", "show_keyboard_shortcuts": "Sneltoetsen tonen", - "show_less": "Laat minder zien", "show_metadata": "Toon metadata", - "show_more_fields": "Laat nog {count, plural, one {# veld} other {# velden}} zien", "show_or_hide_info": "Toon of verberg info", "show_password": "Toon wachtwoord", "show_person_options": "Toon persoonopties", @@ -2005,7 +2148,6 @@ "show_schema": "Toon schema", "show_search_options": "Zoekopties weergeven", "show_shared_links": "Toon gedeelde links", - "show_slideshow_metadata_overlay": "Toon afbeeldings info overlay", "show_slideshow_transition": "Diavoorstellingsovergang tonen", "show_supporter_badge": "Supportersbadge", "show_supporter_badge_description": "Toon een supportersbadge", @@ -2017,33 +2159,27 @@ "sign_out": "Uitloggen", "sign_up": "Registreren", "size": "Grootte", - "skip": "Overslaan", "skip_to_content": "Doorgaan naar inhoud", "skip_to_folders": "Doorgaan naar mappen", "skip_to_tags": "Doorgaan naar tags", "slideshow": "Diavoorstelling", - "slideshow_body": "Leun achterover en bekijk hoe je foto's in een diavoorstelling op volledig scherm worden afgespeeld.", - "slideshow_metadata_overlay_mode": "Metadata bijschrift", - "slideshow_metadata_overlay_mode_description_only": "Enkel beschrijving", - "slideshow_metadata_overlay_mode_full": "Volledig", "slideshow_repeat": "Herhaal diavoorstelling", "slideshow_repeat_description": "Keer terug naar het begin wanneer de diavoorstelling eindigt", "slideshow_settings": "Diavoorstelling instellingen", - "slideshow_title": "Diavoorstelling", - "smart_album": "Slim album", - "some_assets_already_have_a_location_warning": "Sommige geselecteerde items hebben al een locatie", "sort_albums_by": "Sorteer albums op...", "sort_created": "Datum aangemaakt", "sort_items": "Aantal items", "sort_modified": "Datum aangepast", + "sort_newest": "Nieuwste foto", "sort_oldest": "Oudste foto", "sort_people_by_similarity": "Sorteer personen op gelijkenis", "sort_recent": "Meest recente foto", "sort_title": "Titel", "source": "Bron", "stack": "Stapel", - "stack_action_prompt": "{count} items gestapeld", + "stack_action_prompt": "{count} item(s) gestapeld", "stack_duplicates": "Stapel duplicaten", + "stack_select_one_photo": "Selecteer ÊÊn primaire foto voor de stapel", "stack_selected_photos": "Geselecteerde foto's stapelen", "stacked_assets_count": "{count, plural, one {# item} other {# items}} gestapeld", "stacktrace": "Stacktrace", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Startdatum moet voor einddatum liggen", "state": "Staat", "status": "Status", - "step_delete": "Verwijder stap", - "step_delete_confirm": "Weet je zeker dat je deze stap wil verwijderen?", - "step_details": "Stap details", - "steps": "Stappen", "stop_casting": "Stop met casten", "stop_motion_photo": "Bewegingsfoto stoppen", "stop_photo_sharing": "Stoppen met het delen van je foto's?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Wissel richting voor samenvoegen om", "sync": "Synchroniseren", "sync_albums": "Albums synchroniseren", + "sync_albums_manual_subtitle": "Synchroniseer alle geÃŧploade video’s en foto’s naar de geselecteerde back-up albums", "sync_local": "Lokaal synchroniseren", "sync_remote": "Op afstand synchroniseren", "sync_status": "Synchronisatiestatus", "sync_status_subtitle": "Bekijk en beheer het synchronisatie systeem", "sync_upload_album_setting_subtitle": "Maak en upload je foto's en video's naar de geselecteerde albums op Immich", - "system_theme": "Systeem thema", - "system_theme_command_description": "Gebruik systeem thema ({value})", "tag": "Tag", "tag_assets": "Items taggen", "tag_created": "Tag aangemaakt: {tag}", "tag_face": "Gezicht labelen", "tag_feature_description": "Bladeren door foto's en video's gegroepeerd op tags", + "tag_not_found_question": "Kun je een tag niet vinden? Maak een nieuwe tag.", "tag_people": "Mensen taggen", "tag_updated": "Tag bijgewerkt: {tag}", "tagged_assets": "{count, plural, one {# item} other {# items}} getagd", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Aantal items per rij ({count})", "theme_setting_colorful_interface_subtitle": "Pas primaire kleuren toe op achtergronden.", "theme_setting_colorful_interface_title": "Kleurrijke interface", + "theme_setting_image_viewer_quality_subtitle": "De kwaliteit van de gedetailleerde-fotoweergave aanpassen", + "theme_setting_image_viewer_quality_title": "Fotoweergavekwaliteit", "theme_setting_primary_color_subtitle": "Kies een kleur voor primaire acties en accenten.", "theme_setting_primary_color_title": "Primaire kleur", "theme_setting_system_primary_color_title": "Gebruik systeemkleur", "theme_setting_system_theme_switch": "Automatisch (systeeminstelling volgen)", + "theme_setting_theme_subtitle": "De thema-instelling van de app kiezen", + "theme_setting_three_stage_loading_subtitle": "Laden in drie fasen kan de laadprestaties verbeteren, maar veroorzaakt een aanzienlijk hogere netwerkbelasting", + "theme_setting_three_stage_loading_title": "Laden in drie fasen inschakelen", "then": "Dan", "they_will_be_merged_together": "Zij zullen worden samengevoegd", "third_party_resources": "Bronnen van derden", @@ -2127,21 +2264,27 @@ "total": "Totaal", "total_usage": "Totaal gebruik", "trash": "Prullenbak", - "trash_action_prompt": "{count, plural, one {# item} other {# items}} verplaatst naar de prullenbak", + "trash_action_prompt": "{count} item(s) verplaatst naar de prullenbak", "trash_all": "Verplaats alle naar prullenbak", "trash_count": "{count, number} naar prullenbak", "trash_delete_asset": "Items naar prullenbak verplaatsen of verwijderen", + "trash_emptied": "Prullenbak geleegd", "trash_no_results_message": "Hier verschijnen foto's en video's die in de prullenbak zijn geplaatst.", "trash_page_delete_all": "Verwijder alle", + "trash_page_empty_trash_dialog_content": "Wil je de prullenbak leegmaken? Deze items worden permanent verwijderd van Immich", "trash_page_info": "Verwijderde items worden permanent verwijderd na {days} dagen", + "trash_page_no_assets": "Geen verwijderde items", + "trash_page_restore_all": "Herstel alle", + "trash_page_select_assets_btn": "Selecteer items", + "trash_page_title": "Prullenbak ({count})", "trashed_items_will_be_permanently_deleted_after": "Items in de prullenbak worden na {days, plural, one {# dag} other {# dagen}} permanent verwijderd.", "trigger": "Trigger", - "trigger_asset_metadata_extraction": "Metadata van item opgehaald", - "trigger_asset_metadata_extraction_description": "Wordt geactiveerd wanneer de exifmetadata van een item is opgehaald", "trigger_asset_uploaded": "Item geÃŧpload", - "trigger_asset_uploaded_description": "Wordt geactiveerd wanneer een nieuw item geÃŧpload wordt", + "trigger_asset_uploaded_description": "Getriggerd wanneer een nieuw item geÃŧpload wordt", + "trigger_description": "Een gebeurtenis die het proces start", "trigger_person_recognized": "Persoon herkend", - "trigger_person_recognized_description": "Wordt geactiveerd wanneer een persoon herkend is", + "trigger_person_recognized_description": "Getriggerd wanneer een persoon herkend is", + "trigger_type": "Trigger type", "troubleshoot": "Problemen oplossen", "type": "Type", "unable_to_change_pin_code": "Pincode kan niet gewijzigd worden", @@ -2157,7 +2300,6 @@ "unknown": "Onbekend", "unknown_country": "Onbekend Land", "unknown_date": "Onbekende datum", - "unknown_schema": "Onbekend schema", "unknown_year": "Onbekend jaar", "unlimited": "Onbeperkt", "unlink_motion_video": "Ontkoppel bewegende video", @@ -2170,20 +2312,23 @@ "unsaved_change": "Niet-opgeslagen wijziging", "unselect_all": "Alles deselecteren", "unselect_all_duplicates": "Deselecteer alle duplicaten", + "unselect_all_in": "Deselecteer alles in {group}", "unstack": "Ontstapelen", - "unstack_action_prompt": "{count} items ontstapeld", + "unstack_action_prompt": "{count} item(s) ontstapeld", "unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapeld", "unsupported_field_type": "Veldtype niet ondersteund", "unsupported_file_type": "Bestand {file} kan niet worden geÃŧpload omdat het bestandstype {type} niet wordt ondersteund.", "untagged": "Zonder tags", + "untitled_workflow": "Naamloze werkstroom", "up_next": "Volgende", "update_location_action_prompt": "Werk de locatie bij van {count} geselecteerde items met:", "updated_at": "GeÃŧpdatet", "updated_password": "Wachtwoord bijgewerkt", "upload": "Uploaden", "upload_concurrency": "Aantal gelijktijdige uploads", - "upload_day_count": "{date}: {count, plural, one {# upload} other {# uploads}}", "upload_details": "Uploaddetails", + "upload_dialog_info": "Wil je een backup maken van de geselecteerde item(s) op de server?", + "upload_dialog_title": "Item uploaden", "upload_error_with_count": "Upload fout voor {count, plural, one {# item} other {# items}}", "upload_errors": "Upload voltooid met {count, plural, one {# fout} other {# fouten}}, vernieuw de pagina om de nieuwe items te zien.", "upload_finished": "Uploaden is voltooid", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Fouten", "upload_status_uploaded": "GeÃŧpload", "upload_success": "Uploaden gelukt, vernieuw de pagina om de nieuwe items te zien.", - "upload_to_album_body": "Voor gebruikers die de handmatige uploadfunctie niet gebruiken is het nu mogelijk om lokale foto's direct aan een album toe te voegen tijdens het uploaden. Het is dus niet meer nodig om foto's eerst te uploaden en ze later aan een album toe te voegen.", - "upload_to_album_title": "Upload direct naar een album", "upload_to_immich": "Uploaden naar Immich ({count})", "uploading": "Aan het uploaden", "uploading_media": "Media wordt geÃŧpload", - "uploads": "Uploads", - "uploads_count": "{count, plural, one {# upload} other {# uploads}}", "url": "URL", "usage": "Gebruik", "use_biometric": "Gebruik biometrische authenticatie", @@ -2207,18 +2348,18 @@ "use_browser_locale_description": "Formatteer datums, tijden en getallen op basis van de landinstelling van je browser", "use_current_connection": "Gebruik huidige verbinding", "use_custom_date_range": "Gebruik in plaats daarvan een aangepast datumbereik", - "use_template": "Gebruik template", "user": "Gebruiker", "user_has_been_deleted": "Deze gebruiker is verwijderd.", - "user_id": "Gebruikers-ID", - "user_liked": "{user} vindt {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} leuk", + "user_id": "Gebruikers ID", + "user_liked": "{user} heeft {type, select, photo {deze foto} video {deze video} asset {} other {dit item}} geliket", "user_pin_code_settings": "Pincode", "user_pin_code_settings_description": "Beheer je pincode", "user_privacy": "Gebruikersprivacy", "user_purchase_settings": "Kopen", "user_purchase_settings_description": "Beheer je aankoop", + "user_role_set": "{user} instellen als {role}", "user_usage_detail": "Gedetailleerd gebruik van gebruikers", - "user_usage_stats": "Accountstatistieken", + "user_usage_stats": "Statistieken van accountgebruik", "user_usage_stats_description": "Bekijk statistieken van accountgebruik", "username": "Gebruikersnaam", "users": "Gebruikers", @@ -2226,6 +2367,7 @@ "utilities": "Gereedschap", "validate": "Valideren", "validate_endpoint_error": "Vul een geldige URL in", + "validation_error": "Validatiefout", "variables": "Variabelen", "version": "Versie", "version_announcement_closing": "Je vriend, Alex", @@ -2235,9 +2377,9 @@ "video": "Video", "video_hover_setting": "Speel videominiatuur af bij hoveren", "video_hover_setting_description": "Speel videominiatuur af wanneer de muis over het item beweegt. Zelfs wanneer uitgeschakeld, kan het afspelen worden gestart door de muis over het afspeelpictogram te bewegen.", - "video_quality": "Videokwaliteit", "videos": "Video's", "videos_count": "{count, plural, one {# video} other {# video's}}", + "videos_only": "Enkel video's", "view": "Bekijken", "view_album": "Bekijk album", "view_all": "Bekijk alle", @@ -2246,29 +2388,29 @@ "view_details": "Bekijk details", "view_in_timeline": "Bekijk in tijdlijn", "view_link": "Bekijk link", + "view_links": "Links bekijken", "view_name": "Bekijken", "view_next_asset": "Bekijk volgend item", "view_previous_asset": "Bekijk vorig item", "view_qr_code": "QR-code bekijken", "view_similar_photos": "Bekijk vergelijkbare foto's", "view_stack": "Bekijk stapel", + "view_user": "Bekijk gebruiker", "viewer_remove_from_stack": "Verwijder van stapel", + "viewer_stack_use_as_main_asset": "Zet bovenaan de stapel", + "viewer_unstack": "Ontstapel", "visibility": "Zichtbaarheid", "visibility_changed": "Zichtbaarheid gewijzigd voor {count, plural, one {# persoon} other {# mensen}}", "visual": "Visueel", + "visual_builder": "Visuele bouwer", "waiting": "Wachtend", "waiting_count": "In de wacht: {count}", "warning": "Waarschuwing", "week": "Week", "welcome": "Welkom", "welcome_to_immich": "Welkom bij Immich", - "whats_new": "Wat is er nieuw", - "whats_new_settings_subtitle": "Ontdek wat er nieuw is in Immich", - "whats_new_version": "Versie {version}", - "when": "Wanneer", "width": "Breedte", - "wifi_name": "Wifinetwerk", - "workflow": "Werkstroom", + "wifi_name": "WiFi-naam", "workflow_delete_prompt": "Weet je zeker dat je deze werkstroom wilt verwijderen?", "workflow_deleted": "Werkstroom verwijderd", "workflow_description": "Werkstroom omschrijving", @@ -2278,17 +2420,17 @@ "workflow_name": "Werkstroom naam", "workflow_navigation_prompt": "Weet je zeker dat je weg wilt navigeren zonder je wijzigingen op te slaan?", "workflow_summary": "Werkstroom samenvatting", - "workflow_templates": "Werkstroomtemplates", "workflow_update_success": "Werkstroom succesvol bijgewerkt", + "workflow_updated": "Werkstroom bijgewerkt", "workflows": "Werkstromen", "workflows_help_text": "Werkstromen automatiseren acties op je items gebaseerd op triggers en filters", "wrong_pin_code": "Onjuiste pincode", - "x_of_total": "{x}/{total}", "year": "Jaar", "years_ago": "{years, plural, one {Een jaar} other {# jaar}} geleden", "yes": "Ja", "you_dont_have_any_shared_links": "Je hebt geen gedeelde links", - "your_wifi_name": "Je wifinetwerk", + "your_wifi_name": "Je WiFi-naam", "zero_to_clear_rating": "druk op 0 om de sterwaardering te verwijderen", - "zoom_image": "Inzoomen" + "zoom_image": "Inzoomen", + "zoom_to_bounds": "Zoom naar randen" } diff --git a/i18n/nn.json b/i18n/nn.json index dc44e5f417..7a0471c574 100644 --- a/i18n/nn.json +++ b/i18n/nn.json @@ -10,20 +10,25 @@ "active": "Aktive", "active_count": "Aktive: {count}", "activity": "Aktivitet", + "activity_changed": "Aktivitet er {enabled, select, true {aktivert} other {deaktivert}}", "add": "Legg til", "add_a_description": "Legg til ei skildring", "add_a_location": "Legg til ein stad", "add_a_name": "Legg til eit namn", "add_a_title": "Legg til ein tittel", "add_action": "Legg til handling", + "add_action_description": "Trykk for ÃĨ leggja til ei handling som skal utføras", "add_assets": "Legg til ressursar", "add_birthday": "Legg til ein fødselsdag", "add_endpoint": "Legg til endepunkt", "add_exclusion_pattern": "Legg til unnlatingsmønster", + "add_filter": "Legg til filter", + "add_filter_description": "Trykk for ÃĨ leggja til eit filtervilkÃĨr", "add_location": "Legg til stad", + "add_more_users": "Legg til fleire brukarar", "add_partner": "Legg til partnar", + "add_path": "Legg til sti", "add_photos": "Legg til bilete", - "add_step": "Legg til steg", "add_tag": "Legg til tagg", "add_to": "Legg tilâ€Ļ", "add_to_album": "Legg til i album", @@ -33,7 +38,9 @@ "add_to_albums": "Legg til i album", "add_to_albums_count": "Legg til i album ({count})", "add_to_bottom_bar": "Legg til i", + "add_to_shared_album": "Legg til i delt album", "add_url": "Legg til URL", + "add_workflow_step": "Legg til steg i arbeidsflyt", "added_to_archive": "Lagt til i arkiv", "added_to_favorites": "Lagt til i favorittar", "added_to_favorites_count": "La til {count, number} i favorittar", @@ -274,6 +281,7 @@ "album_with_link_access": "Lat kven som helst med lenka sjÃĨ bilete og folk i dette albumet.", "albums": "Album", "all": "Alle", + "anti_clockwise": "Mot klokka", "archive": "Arkiv", "asset_skipped": "Hoppa over", "asset_uploaded": "Opplasta", @@ -289,16 +297,19 @@ "clear_all_recent_searches": "Tøm alle nylige søk", "clear_message": "Tøm melding", "clear_value": "Tøm verdi", + "client_cert_enter_password": "Oppgi passord", "client_cert_import": "Importer", "client_cert_import_success_msg": "Klientsertifikat vart importert", "client_cert_invalid_msg": "Ugyldig sertifikatfil eller feil passord", "client_cert_remove_msg": "Klientsertifikat er fjerna", "client_cert_subtitle": "Støttar berre PKCS12-formatet (.p12, .pfx). Import og fjerning av sertifikat er berre tilgjengeleg før innlogging", "client_cert_title": "SSL-klientsertifikat", + "clockwise": "Med klokka", "close": "Lukk", "collapse": "Gøym", "collapse_all": "Gøym alle", "color": "Farge", + "color_theme": "Fargetema", "comment_deleted": "Kommentar vart sletta", "comment_options": "Kommentarval", "comments_and_likes": "Kommentarar og likerklikk", @@ -314,6 +325,7 @@ "contain": "Tilpass til vindauget", "context": "Samanheng", "continue": "Hald fram", + "control_bottom_app_bar_create_new_album": "Lag nytt album", "country": "Land", "cover": "Dekk", "covers": "Dekker", @@ -415,6 +427,7 @@ "refresh_metadata": "Oppfrisk metadata", "refresh_thumbnails": "Oppfrisk miniatyrbilete", "refreshed": "Oppdatert", + "refreshes_every_file": "Les alle eksisterande og nye filer pÃĨ nytt", "refreshing_encoded_video": "Lastar inn ferdigbehandla video pÃĨ nytt", "refreshing_faces": "Oppfriskar ansiktsdata", "refreshing_metadata": "Oppfriskar metadata", @@ -424,21 +437,28 @@ "remove_assets_shared_link_confirmation": "Er du sikker pÃĨ at du vil fjerne {count, plural, one {# asset} other {# assets}} frÃĨ denne delte lenka?", "remove_assets_title": "Fjern ressursar?", "remove_custom_date_range": "Fjern egendefinert datoperiode", + "remove_deleted_assets": "Fjern sletta ressursar", "remove_from_album": "Fjern frÃĨ album", "remove_from_favorites": "Fjern frÃĨ favorittar", "remove_from_shared_link": "Fjern frÃĨ delt lenke", "remove_memory": "Fjern minne", "remove_photo_from_memory": "Fjern bilete frÃĨ dette minne", + "remove_url": "Fjern URL", "remove_user": "Fjern brukar", "removed_api_key": "Fjerna API-nøkkel: {name}", "removed_from_archive": "Fjerna frÃĨ arkiv", "removed_from_favorites": "Fjerna frÃĨ favorittar", "removed_from_favorites_count": "{count, plural, other {Fjerna #}} frÃĨ favorittar", "removed_memory": "Fjerna minne", + "removed_photo_from_memory": "Fjerna bilete frÃĨ minne", "removed_tagged_assets": "Fjerna tagg frÃĨ {count, plural, one {# ressurs} other {# ressursar}}", "rename": "Gi nytt namn", + "repair": "Reparer", + "repair_no_results_message": "Uspora og manglande filer vil visast her", + "replace_with_upload": "Erstatt med opplasting", "repository": "Lager", "require_password": "Krev passord", + "require_user_to_change_password_on_first_login": "Krev at brukaren endrar passord ved første innlogging", "rescan": "Skann pÃĨ nytt", "reset": "Tilbakestill", "reset_password": "Tilbakestill passord", @@ -451,11 +471,13 @@ "restore_user": "Gjenopprett brukar", "restored_asset": "Ressurs gjenoppretta", "resume": "Gjenoppta", + "retry_upload": "Prøv opplasting pÃĨ nytt", "review_duplicates": "GÃĨ gjennom duplikat", "role": "Rolle", "role_editor": "Redaktør", "role_viewer": "Observatør", "save": "Lagre", + "save_to_gallery": "Lagre til galleri", "saved_api_key": "API-nøkkel lagra", "saved_profile": "Profil lagra", "saved_settings": "Innstillingar lagra", @@ -463,6 +485,7 @@ "scaffold_body_error_occurred": "Det oppstod ein feil", "scan_all_libraries": "Skann gjennom alle bibliotek", "scan_library": "Skann", + "scan_settings": "Skann innstillingar", "scanning_for_album": "Skanning for album...", "search": "Søk", "search_albums": "Søk album", @@ -495,13 +518,25 @@ "search_no_people_named": "Ingen personar ved namn \"{name}\"", "search_no_result": "Fann ingen resultat – prøv eit anna søkjeord eller ei anna kombinasjon", "search_options": "Søkjeval", + "search_page_categories": "Kategoriar", + "search_page_motion_photos": "Levande bilete", + "search_page_no_objects": "Ingen objektinformasjon tilgjengeleg", + "search_page_no_places": "Ingen stadinformasjon tilgjengeleg", + "search_page_screenshots": "Skjermbilete", "search_page_search_photos_videos": "Søk etter bileta og videoane dine", + "search_page_selfies": "Sjølvbilete", + "search_page_things": "Ting", "search_page_view_all_button": "Vis alle", + "search_page_your_activity": "Din aktivitet", + "search_page_your_map": "Ditt kart", "search_people": "Søk etter personar", "search_places": "Søk etter stad", "search_rating": "Søk etter vurdering â€Ļ", + "search_result_page_new_search_hint": "Nytt søk", "search_settings": "Søkjeinnstillingar", "search_state": "Søk etter fylke â€Ļ", + "search_suggestion_list_smart_search_hint_1": "Smart søk er aktivert som standard. For ÃĨ søkje etter metadata, bruk denne syntaksen: ", + "search_suggestion_list_smart_search_hint_2": "m:søkjeord", "search_tags": "Søk etter taggar â€Ļ", "search_timezone": "Søk etter tidssone â€Ļ", "search_type": "Søketype", @@ -518,9 +553,11 @@ "select_featured_photo": "Vel framheva bilete", "select_from_computer": "Vel frÃĨ datamaskin", "select_keep_all": "Vel ÃĨ behald alle", + "select_library_owner": "Vel bibliotekeigar", "select_new_face": "Vel nytt ansikt", "select_photos": "Vel bilete", "select_trash_all": "Vel fjern alle", + "select_user_for_sharing_page_err_album": "Feil ved oppretting av album", "selected": "Valgt", "selected_count": "{count, plural, other {# valgt}}", "send_message": "Send melding", @@ -593,6 +630,7 @@ "view_all": "SjÃĨ alle", "view_all_users": "SjÃĨ alle brukarar", "view_in_timeline": "SjÃĨ pÃĨ tidslinja", + "view_links": "Vis lenkjer", "view_name": "Vis", "view_next_asset": "Vis neste fil", "view_previous_asset": "Vis forrige fil", diff --git a/i18n/pa.json b/i18n/pa.json index ccdd6e4847..52b1430134 100644 --- a/i18n/pa.json +++ b/i18n/pa.json @@ -2,30 +2,19 @@ "about": "⍐ā¨Ē ā¨Ŧā¨žā¨°āŠ‡", "account": "⍖ā¨ŧā¨žā¨¤ā¨ž", "account_settings": "⍖ā¨ŧā¨žā¨¤ā¨ž ā¨¸āŠˆā¨Ÿā¨ŋāŠ°ā¨—ā¨žā¨‚", - "acknowledge": "⍏ā¨ĩāŠ€ā¨•ā¨žā¨°", "action": "ā¨•ā¨žā¨°ā¨ĩā¨žā¨ˆ", "action_common_update": "ā¨…āŠąā¨Ēā¨ĄāŠ‡ā¨Ÿ", - "action_description": "ā¨šāŠ‹ā¨Ŗ ā¨•āŠ€ā¨¤āŠ€ā¨†ā¨‚ ā¨œā¨žā¨‡ā¨Ļā¨žā¨Ļā¨žā¨‚ 'ā¨¤āŠ‡ ⍕⍰⍍ ⍞⍈ ā¨•ā¨žā¨°ā¨ĩā¨žā¨ˆā¨†ā¨‚ ā¨Ļā¨ž ā¨‡āŠąā¨• ā¨¸āŠˆāŠąā¨Ÿ", "actions": "ā¨•ā¨žā¨°ā¨ĩā¨žā¨ˆā¨†ā¨‚", "active": "⍕ā¨ŋ⍰ā¨ŋ⍆⍏ā¨ŧāŠ€ā¨˛", - "active_count": "ā¨šā¨žā¨˛āŠ‚: {count}", "activity": "ā¨—ā¨¤āŠ€ā¨ĩā¨ŋā¨§āŠ€", "add": "⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_a_description": "ā¨ĩāŠ‡ā¨°ā¨ĩā¨ž ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_a_location": "ā¨‡āŠąā¨• ⍏ā¨Ĩā¨žā¨¨ ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_a_name": "ā¨¨ā¨žā¨Ž ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_a_title": "⍏ā¨ŋā¨°ā¨˛āŠ‡ā¨– ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", - "add_action": "ā¨•āŠā¨°ā¨ŋ⍆ ā¨œāŠ‹āŠœāŠ‹", "add_birthday": "ā¨œā¨¨ā¨Žā¨Ļā¨ŋ⍍ ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_endpoint": "ā¨ā¨‚ā¨Ąā¨ĒāŠā¨†ā¨‡āŠ°ā¨Ÿ ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_exclusion_pattern": "ā¨…ā¨˛ā¨šā¨ŋā¨Ļā¨—āŠ€ ā¨ĒāŠˆā¨Ÿā¨°ā¨¨ ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", "add_location": "⍏ā¨Ĩā¨žā¨¨ ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹", - "add_partner": "ā¨¸ā¨žā¨ĨāŠ€ ā¨œāŠ‹āŠœāŠ‹", - "add_photos": "ā¨ĢāŠ‹ā¨ŸāŠ‹ā¨†ā¨‚ ā¨œāŠ‹āŠœāŠ‹", - "add_step": "⍕ā¨Ļā¨Ž ā¨œāŠ‹āŠœāŠ‹", - "add_tag": "ā¨ŸāŠˆā¨— ā¨œāŠ‹āŠœāŠ‹", - "add_to": "â€Ļā¨ĩā¨ŋ⍚ ā¨œāŠ‹āŠœāŠ‹", - "add_to_album": "⍐⍞ā¨Ŧā¨Ž ā¨ĩā¨ŋāŠąā¨š ā¨œāŠ‹āŠœāŠ‹", - "add_to_album_bottom_sheet_added": "{album} ā¨ĩā¨ŋ⍚ ⍏ā¨ŧā¨žā¨Žā¨ŋ⍞ ā¨•āŠ€ā¨¤ā¨ž", - "add_to_album_bottom_sheet_already_exists": "ā¨Ēā¨šā¨ŋā¨˛ā¨žā¨‚ ā¨šāŠ€ {album} ā¨ĩā¨ŋāŠąā¨š" + "add_more_users": "ā¨šāŠ‹ā¨° ⍉ā¨Ēā¨­āŠ‹ā¨—ā¨¤ā¨ž ⍏ā¨ŧā¨žā¨Žā¨˛ ā¨•ā¨°āŠ‹" } diff --git a/i18n/package.json b/i18n/package.json new file mode 100644 index 0000000000..8de66f51b1 --- /dev/null +++ b/i18n/package.json @@ -0,0 +1,13 @@ +{ + "name": "immich-i18n", + "version": "2.7.3", + "private": true, + "scripts": { + "format": "prettier --cache --check .", + "format:fix": "prettier --cache --write --list-different ." + }, + "devDependencies": { + "prettier": "^3.7.4", + "prettier-plugin-sort-json": "^4.1.1" + } +} diff --git a/i18n/pl.json b/i18n/pl.json index 54a9da4194..0f45c82e4a 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -10,31 +10,39 @@ "active": "Aktywne", "active_count": "Aktywne: {count}", "activity": "Aktywność", + "activity_changed": "Aktywność jest {enabled, select, true {włączona} other {wyłączona}}", "add": "Dodaj", "add_a_description": "Dodaj opis", "add_a_location": "Dodaj lokalizację", "add_a_name": "Dodaj nazwę", "add_a_title": "Dodaj tytuł", "add_action": "Dodaj akcję", + "add_action_description": "Kliknij, aby dodać akcję do wykonania", "add_assets": "Dodaj zasoby", "add_birthday": "Dodaj datę urodzin", "add_endpoint": "Dodaj punkt końcowy", "add_exclusion_pattern": "Dodaj wzÃŗr wykluczający", + "add_filter": "Dodaj filtr", + "add_filter_description": "Kliknij, aby dodać warunek filtrowania", "add_location": "Dodaj lokalizację", + "add_more_users": "Dodaj więcej uÅŧytkownikÃŗw", "add_partner": "Dodaj partnera", + "add_path": "Dodaj ścieÅŧkę", "add_photos": "Dodaj zdjęcia", - "add_step": "Dodaj krok", "add_tag": "Dodaj tag", "add_to": "Dodaj doâ€Ļ", "add_to_album": "Dodaj do albumu", "add_to_album_bottom_sheet_added": "Dodano do {album}", "add_to_album_bottom_sheet_already_exists": "JuÅŧ jest w {album}", "add_to_album_bottom_sheet_some_local_assets": "NiektÃŗre lokalne zasoby nie mogły zostać dodane do albumu", + "add_to_album_toggle": "Przełącz wybieranie dla {album}", "add_to_albums": "Dodaj do albumÃŗw", "add_to_albums_count": "Dodaj do albumÃŗw ({count})", "add_to_bottom_bar": "Dodaj do", + "add_to_shared_album": "Dodaj do udostępnionego albumu", "add_upload_to_stack": "Dodaj przesłane do stosu", "add_url": "Dodaj URL", + "add_workflow_step": "Dodaj krok przepływu pracy", "added_to_archive": "Dodano do archiwum", "added_to_favorites": "Dodano do ulubionych", "added_to_favorites_count": "Dodano {count, number} do ulubionych", @@ -53,7 +61,7 @@ "backup_onboarding_1_description": "kopia offsite w chmurze lub w innej fizycznej lokalizacji.", "backup_onboarding_2_description": "kopie lokalne na rÃŗÅŧnych urządzeniach. Obejmuje to gÅ‚Ãŗwne pliki i lokalną kopię zapasową tych plikÃŗw.", "backup_onboarding_3_description": "wszystkie kopie danych, w tym oryginalne pliki. Obejmuje to 1 kopię zewnętrzną i 2 kopie lokalne.", - "backup_onboarding_description": "W celu ochrony danych zalecana jest strategia tworzenia kopii zapasowych 3-2-1. NaleÅŧy zachować kopie przesłanych zdjęć/filmÃŗw, a wraz z nimi bazę danych Immich, aby uzyskać kompleksowe rozwiązanie przy tworzeniu kopii zapasowych.", + "backup_onboarding_description": "W celu ochrony danych zalecana jest strategia tworzenia kopii zapasowych 3-2-1. Powinieneś zachować kopie przesłanych zdjęć/filmÃŗw, a wraz z nimi bazę danych Immich, aby uzyskać kompleksowe rozwiązanie przy tworzeniu kopii zapasowych.", "backup_onboarding_footer": "Więcej informacji na temat tworzenia kopii zapasowych Immich moÅŧna znaleÅēć w dokumentacji.", "backup_onboarding_parts_title": "Kopia zapasowa 3-2-1 obejmuje:", "backup_onboarding_title": "Kopie zapasowe", @@ -73,7 +81,6 @@ "cron_expression_description": "Ustaw interwał skanowania przy pomocy formatu Cron'a. Po więcej informacji na temat formatu Cron zobacz . Crontab Guru", "cron_expression_presets": "Predefiniowane wyraÅŧenia Cron'a", "disable_login": "Wyłącz logowanie", - "download_csv": "Pobierz plik CSV", "duplicate_detection_job_description": "Włącz uczenie maszynowe na zasobie aby wykrywać podobne obrazy. Ta funkcja opiera się na inteligentnym wyszukiwaniu", "exclusion_pattern_description": "Wzory wykluczające pozwalają na ignorowanie plikÃŗw i folderÃŗw podczas skanowania Twojej biblioteki. Są one przydatne na przykład gdy nie chcesz importować zdjęć w formacie RAW.", "export_config_as_json_description": "Pobierz aktualną konfigurację systemu jako plik JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Jakość miniatur od 1 do 100. WyÅŧsza jest lepsza, ale tworzy większe pliki i moÅŧe spowolnić reakcję aplikacji.", "image_thumbnail_title": "Ustawienia miniatur", "import_config_from_json_description": "Zaimportuj konfigurację systemu poprzez wczytanie pliku konfiguracyjnego JSON", - "integrity_checks_checksum_files": "Weryfikacja sum kontrolnych", - "integrity_checks_checksum_files_description": "Skonfiguruj weryfikację sumy kontrolnej plikÃŗw", - "integrity_checks_checksum_files_enable_description": "Włącz weryfikację sum kontrolnych", - "integrity_checks_checksum_files_percentage_limit": "Ograniczenie procentowe", - "integrity_checks_checksum_files_percentage_limit_description": "Ustaw maksymalną wartość procentową w przedziale od 0,01 do 1, określającą, jaka część weryfikacji sumy kontrolnej ma być przeprowadzana w kaÅŧdym przedziale czasowym.", - "integrity_checks_checksum_files_time_limit": "Ograniczenie czasowe", - "integrity_checks_checksum_files_time_limit_description": "Skonfiguruj maksymalny czas trwania weryfikacji sumy kontrolnej w kaÅŧdym przedziale czasowym. (ms)", - "integrity_checks_missing_files": "Brakujące pliki", - "integrity_checks_missing_files_description": "Skonfiguruj częstotliwość oraz włącz lub wyłącz sprawdzanie brakujących plikÃŗw", - "integrity_checks_missing_files_enable_description": "Włącz sprawdzanie brakujących plikÃŗw", - "integrity_checks_settings": "Kontrole integralności", - "integrity_checks_settings_description": "Zarządzanie częstotliwością kontroli integralności", - "integrity_checks_untracked_files": "Pliki nieśledzone", - "integrity_checks_untracked_files_description": "Skonfiguruj częstotliwość i włącz lub wyłącz sprawdzanie plikÃŗw nieśledzonych", - "integrity_checks_untracked_files_enable_description": "Włącz sprawdzanie plikÃŗw nieśledzonych", "job_concurrency": "{job} wspÃŗÅ‚bieÅŧność", "job_created": "Zadanie utworzone", "job_not_concurrency_safe": "To zadanie nie moÅŧe zostać wykonane w wielu wątkach.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Włącz inteligentne wyszukiwanie", "machine_learning_smart_search_enabled_description": "JeÅŧeli wyłączone, obrazy nie będą przygotowywane do inteligentnego wyszukiwania.", "machine_learning_url_description": "URL serwera uczenia maszynowego. JeÅŧeli podano więcej niÅŧ jeden URL, do kaÅŧdego serwera po kolei będzie wysłane Åŧądanie dopÃŗki chociaÅŧ jeden nie odpowie, w kolejności od pierwszego do ostatniego. Serwery ktÃŗre nie odpowiedzą, zostaną tymczasowo ignorowane aÅŧ do momentu ich przejścia w stan online.", - "maintenance_backup_management": "Zarządzanie kopiami zapasowymi", "maintenance_delete_backup": "Usuń kopię zapasową", "maintenance_delete_backup_description": "Ten plik zostanie nieodwracalnie usunięty.", "maintenance_delete_error": "Nie udało się usunąć kopii zapasowej.", - "maintenance_integrity_check": "SprawdÅē", - "maintenance_integrity_check_all": "SprawdÅē wszystko", - "maintenance_integrity_checksum_mismatch": "Niezgodność sumy kontrolnej", - "maintenance_integrity_checksum_mismatch_description": "Pliki, ktÃŗrych suma kontrolna na dysku nie zgadza się z sumą kontrolną przechowywaną przez Immich w bazie danych.", - "maintenance_integrity_checksum_mismatch_job": "SprawdÅē, czy nie występują rozbieÅŧności w sumach kontrolnych", - "maintenance_integrity_checksum_mismatch_refresh_job": "OdświeÅŧ raporty dotyczące niezgodności sum kontrolnych", - "maintenance_integrity_missing_file": "Brakujące pliki", - "maintenance_integrity_missing_file_description": "Pliki, ktÃŗre Immich zlokalizował w swojej bazie danych, ale ktÃŗre nie istnieją w systemie plikÃŗw.", - "maintenance_integrity_missing_file_job": "SprawdÅē, czy nie brakuje Åŧadnych plikÃŗw", - "maintenance_integrity_missing_file_refresh_job": "OdświeÅŧ raporty dotyczące brakujących plikÃŗw", - "maintenance_integrity_report": "Raport dotyczący integralności", - "maintenance_integrity_untracked_file": "Pliki nieśledzone", - "maintenance_integrity_untracked_file_description": "Pliki znajdujące się w katalogach Immicha, o ktÃŗrych Immich nie ma Åŧadnych informacji.", - "maintenance_integrity_untracked_file_job": "SprawdÅē, czy są pliki, ktÃŗre nie są śledzone", - "maintenance_integrity_untracked_file_refresh_job": "OdświeÅŧ raporty dotyczące plikÃŗw, ktÃŗre nie są śledzone", "maintenance_restore_backup": "PrzywrÃŗÄ‡ kopię zapasową", "maintenance_restore_backup_description": "Immich zostanie wyczyszczony i przywrÃŗcony z wybranej kopii zapasowej. Przed rozpoczęciem operacji zostanie utworzona kopia zapasowa.", "maintenance_restore_backup_different_version": "Ta kopia zapasowa została utworzona przy uÅŧyciu innej wersji Immich!", @@ -238,7 +214,7 @@ "map_gps_settings_description": "Zarządzaj mapą oraz ustawieniami odwrÃŗconego geokodowania", "map_implications": "Funkcja mapy opiera się na zewnętrznej usłudze kafelkÃŗw (tiles.immich.cloud)", "map_light_style": "Styl jasny", - "map_manage_reverse_geocoding_settings": "Zarządzaj Ustawieniem Odwrotne Geokodowanie", + "map_manage_reverse_geocoding_settings": "Zarządzaj Ustawieniem Odwrotne Geokodowanie", "map_reverse_geocoding": "Odwrotne Geokodowanie", "map_reverse_geocoding_enable_description": "Włącz odwrotne geokodowanie", "map_reverse_geocoding_settings": "Ustawienia odwrotnego geokodowania", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Włącz powiadomienia e-mail", "notification_settings": "Ustawienia Powiadomień", "notification_settings_description": "Zarządzaj ustawieniami powiadomień, włączając w to powiadomienia e-mail", - "oauth_allow_insecure_requests": "ZezwÃŗl na niezabezpieczone Åŧądania", - "oauth_allow_insecure_requests_description": "UWAGA: Spowoduje to wyłączenie weryfikacji certyfikatÃŗw TLS dla Åŧądań OAuth i moÅŧe narazić uÅŧytkownika na ataki typu MITM.", "oauth_auto_launch": "Automatyczne Uruchamianie", "oauth_auto_launch_description": "Rozpocznij proces logowania OAuth automatycznie po przejściu na stronę logowania", "oauth_auto_register": "Automatyczna rejestracja", @@ -300,11 +274,9 @@ "oauth_button_text": "Tekst na przycisku", "oauth_client_secret_description": "Wymagane dla poufnego klienta lub jeśli PKCE (Proof Key for Code Exchange) nie jest obsługiwane dla klienta publicznego.", "oauth_enable_description": "Loguj się za pomocą OAuth", - "oauth_end_session_url_description": "Po wylogowaniu przekieruj uÅŧytkownika na ten adres URI.", "oauth_mobile_redirect_uri": "Mobilny adres zwrotny", "oauth_mobile_redirect_uri_override": "Zapasowy URI przekierowania mobilnego", "oauth_mobile_redirect_uri_override_description": "Włącz, gdy dostawca OAuth nie pozwala na mobilne identyfikatory URI typu ''{callback}''", - "oauth_prompt_description": "Parametr podpowiedzi (np. wybierz_konto, login, zgoda)", "oauth_role_claim": "Oświadczenie roli", "oauth_role_claim_description": "Automatycznie przyznaj dostęp administratora na podstawie obecności tego oświadczenia. Oświadczenie moÅŧe mieć wartość „uÅŧytkownik” lub „administrator”.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Wszystkie biblioteki zostaną odświeÅŧone", "registration": "Rejestracja Administratora", "registration_description": "Jesteś pierwszym uÅŧytkownikiem aplikacji, więc twoje konto jest administratorem. MoÅŧesz zarządzać platformą, w tym dodawać nowych uÅŧytkownikÃŗw.", - "release_channel_release_candidate": "Kandydat do wydania", - "release_channel_stable": "Stabilny", "remove_failed_jobs": "Usuń nieudane zadania", "require_password_change_on_login": "Wymagaj zmiany hasła po pierwszym zalogowaniu", "reset_settings_to_default": "PrzywrÃŗÄ‡ ustawienia fabryczne", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Dotyczy tylko VAAPI i QSV. Ustawia węzeł dri uÅŧywany do transkodowania sprzętowego.", "transcoding_preset_preset": "Ustawienie wstępne (-preset)", "transcoding_preset_preset_description": "Szybkość kompresji. Wolniejsze ustawienia tworzą mniejsze pliki i zwiększają jakość, jeśli są ustawione na określoną szybkość transmisji. Kodowanie VP9 ignoruje prędkości powyÅŧej „szybciej”.", - "transcoding_realtime": "Transkodowanie w czasie rzeczywistym [EKSPERYMENTALNE]", - "transcoding_realtime_description": "UmoÅŧliwia transkodowanie w czasie rzeczywistym podczas strumieniowego przesyłania wideo. Pozwala na zmianę jakości, ale w zaleÅŧności od moÅŧliwości serwera moÅŧe powodować większe opÃŗÅēnienia w odtwarzaniu i zacinanie się obrazu.", - "transcoding_realtime_enabled": "Włącz transkodowanie w czasie rzeczywistym", - "transcoding_realtime_enabled_description": "Gdy wyłączone, serwer odmÃŗwi uruchamiania nowych sesji transkodowania w czasie rzeczywistym.", - "transcoding_realtime_resolutions": "Rozdzielczości", - "transcoding_realtime_resolutions_description": "Rozdzielczości dostępne dla transkodowania w czasie rzeczywistym. WyÅŧsze rozdzielczości mogą powodować problemy z odtwarzaniem jeśli serwer nie będzie w stanie ich transkodować odpowiednio szybko.", - "transcoding_realtime_video_codecs": "Kodeki Wideo", - "transcoding_realtime_video_codecs_description": "Kodeki wideo dostępne dla transkodowania w czasie rzeczywistym. Aplikacje klienckie wybiorą najlepszą opcję wspierane przez system w trakcie odtwarzania. AVI jest wydajniejsze niÅŧ HEVC, ktÃŗry jest wydajniejszy niÅŧ H.264. Mając aktywne przyspieszenie sprzętowe wybierz tylko te kodeki, ktÃŗre są wspierane przez twoje urządzenie. Transkodując programowo pamiętaj, Åŧe H.264 jest szybsze niÅŧ AVI, a HEVC jest od niego wolniejszy.", "transcoding_reference_frames": "Ramki referencyjne", "transcoding_reference_frames_description": "Liczba klatek uÅŧywana do odnoszenia się podczas kompresowania danej klatki. WyÅŧsze wartości poprawiają efektywność kompresji, ale spowalniają kodowanie. 0 ustawia tą wartość automatycznie.", "transcoding_required_description": "Tylko filmy w nieakceptowanym formacie", @@ -478,8 +440,6 @@ "user_settings_description": "Zarządzaj ustawieniami uÅŧytkownika", "user_successfully_removed": "UÅŧytkownik {email} został pomyślnie usunięty.", "users_page_description": "Strona administracyjna do zarządzania uÅŧytkownikami", - "version_check_channel": "Kanał dystrybucji", - "version_check_channel_description": "Wybierz kanał dystrybucji, dla ktÃŗrego chcesz otrzymywać powiadomienia o wersjach", "version_check_enabled_description": "Włącz sprawdzanie wersji", "version_check_implications": "Funkcja sprawdzania wersji opiera się na okresowej komunikacji z {server}", "version_check_settings": "Sprawdzenie Wersji", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Wyczyść pamięć podręczną obrazÃŗw", "advanced_settings_clear_image_cache_error": "Nie udało się wyczyścić pamięci podręcznej obrazÃŗw", "advanced_settings_clear_image_cache_success": "Pomyślnie wyczyszczono {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "UÅŧyj tej opcji do filtrowania mediÃŗw podczas synchronizacji opartej na alternatywnych kryteriach. UÅŧywaj tylko wtedy gdy aplikacja ma problemy z wykrywaniem wszystkich albumÃŗw.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERYMENTALNE] UÅŧyj alternatywnego filtra synchronizacji albumÃŗw na urządzeniu", "advanced_settings_log_level_title": "Poziom szczegÃŗÅ‚owości dziennika: {level}", "advanced_settings_prefer_remote_subtitle": "NiektÃŗre urządzenia bardzo wolno ładują miniatury z lokalnych zasobÃŗw. Aktywuj to ustawienie, aby ładować zdalne obrazy.", "advanced_settings_prefer_remote_title": "Preferuj obrazy zdalne", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Niestandardowe nagÅ‚Ãŗwki proxy [EKSPERYMENTALNE]", "advanced_settings_readonly_mode_subtitle": "Włącza tryb tylko do odczytu, w ktÃŗrym zdjęcia moÅŧna tylko przeglądać, a takie czynności jak wybieranie wielu obrazÃŗw, udostępnianie, przesyłanie i usuwanie są wyłączone. Włącz/wyłącz tryb tylko do odczytu za pomocą awatara uÅŧytkownika na ekranie gÅ‚Ãŗwnym", "advanced_settings_readonly_mode_title": "Tryb tylko do odczytu", + "advanced_settings_self_signed_ssl_subtitle": "Pomija weryfikację certyfikatu SSL dla punktu końcowego serwera. Wymagane w przypadku certyfikatÃŗw z podpisem własnym.", + "advanced_settings_self_signed_ssl_title": "ZezwÃŗl na certyfikaty SSL z podpisem własnym [EKSPERYMENTALNE]", "advanced_settings_sync_remote_deletions_subtitle": "Automatycznie usuń lub przywrÃŗÄ‡ zasÃŗb na tym urządzeniu po wykonaniu tej czynności w interfejsie webowym", "advanced_settings_sync_remote_deletions_title": "Synchronizuj zdalne usunięcia [EKSPERYMENTALNE]", "advanced_settings_tile_subtitle": "Zaawansowane ustawienia uÅŧytkownika", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Czy na pewno chcesz usunąć album {album}?", "album_delete_confirmation_description": "JeÅŧeli album jest udostępniany, inny stracą do niego dostęp.", "album_deleted": "Album usunięty", + "album_info_card_backup_album_excluded": "WYKLUCZONE", + "album_info_card_backup_album_included": "WŁĄCZONE", "album_info_updated": "SzczegÃŗÅ‚y albumu zostały zaktualizowane", + "album_leave": "Opuścić album?", + "album_leave_confirmation": "Na pewno chcesz opuścić {album}?", "album_name": "Nazwa Albumu", "album_options": "Opcje albumu", "album_remove_user": "Usunąć uÅŧytkownika?", "album_remove_user_confirmation": "Na pewno chcesz usunąć {user}?", "album_search_not_found": "Nie znaleziono albumÃŗw pasujących do Twojego wyszukiwania", + "album_selected": "Wybrany album", "album_share_no_users": "Wygląda na to, Åŧe ten album albo udostępniono wszystkim uÅŧytkownikom, albo nie ma komu go udostępnić.", "album_summary": "Podsumowanie albumu", "album_updated": "Album zaktualizowany", "album_updated_setting_description": "Otrzymaj powiadomienie e-mail, gdy do udostępnionego Ci albumu zostaną dodane nowe zasoby", "album_upload_assets": "Prześlij zasoby ze swojego komputera i dodaj je do albumu", + "album_user_left": "Opuszczono {album}", + "album_user_removed": "Usunięto {user}", + "album_viewer_appbar_delete_confirm": "Czy na pewno chcesz usunąć ten album ze swojego konta?", "album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu", + "album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu", + "album_viewer_appbar_share_err_remove": "Wystąpiły problemy z usunięciem plikÃŗw z albumu", + "album_viewer_appbar_share_err_title": "Nie udało się zmienić tytułu albumu", + "album_viewer_appbar_share_leave": "Opuść album", + "album_viewer_appbar_share_to": "Udostępnij", "album_viewer_page_share_add_users": "Dodaj uÅŧytkownikÃŗw", "album_with_link_access": "PozwÃŗl kaÅŧdemu z dostępem do linku zobaczyć zdjęcia i osoby w tym albumie.", "albums": "Albumy", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Początkowa kolejność sortowania zasobÃŗw przy tworzeniu nowych albumÃŗw.", "albums_feature_description": "Kolekcje zasobÃŗw, ktÃŗre moÅŧna udostępniać innym uÅŧytkownikom.", "albums_on_device_count": "Albumy na urządzeniu ({count})", + "albums_selected": "{count, plural, one {# wybrany album} few {# wybrane albumy} other {# wybranych albumÃŗw}}", "all": "Wszystkie", "all_albums": "Wszystkie albumy", "all_people": "Wszystkie osoby", "all_photos": "Wszystkie zdjęcia", "all_videos": "Wszystkie filmy", "allow_dark_mode": "Zezwalaj na tryb ciemny", + "allow_edits": "PozwÃŗl edytować", "allow_public_user_to_download": "ZezwÃŗl uÅŧytkownikowi publicznemu na pobieranie", "allow_public_user_to_upload": "ZezwÃŗl uÅŧytkownikowi publicznemu na przesyłanie plikÃŗw", "allowed": "Dozwolone", @@ -549,12 +528,14 @@ "always_keep": "Zawsze zachowuj", "always_keep_photos_hint": "Zwolnij Miejsce zachowa wszystkie zdjęcia na tym urządzeniu.", "always_keep_videos_hint": "Zwolnij Miejsce zachowa wszystkie filmy na tym urządzeniu.", + "anti_clockwise": "Przeciwnie do ruchu wskazÃŗwek zegara", "api_key": "Klucz API", "api_key_description": "Widzisz tę wartość po raz pierwszy i ostatni, więc lepiej ją skopiuj przed zamknięciem okna.", "api_key_empty": "TwÃŗj Klucz API nie powinien być pusty", "api_keys": "Klucze API", "app_architecture_variant": "Wariant (Architektura)", "app_bar_signout_dialog_content": "Czy na pewno chcesz się wylogować?", + "app_bar_signout_dialog_ok": "Tak", "app_bar_signout_dialog_title": "Wyloguj się", "app_download_links": "Linki do pobrania aplikacji", "app_settings": "Ustawienia aplikacji", @@ -565,19 +546,27 @@ "archive": "Archiwum", "archive_action_prompt": "{count} dodanych do Archiwum", "archive_or_unarchive_photo": "Dodaj lub usuń zasÃŗb z archiwum", + "archive_page_no_archived_assets": "Nie znaleziono zarchiwizowanych zasobÃŗw", + "archive_page_title": "Archiwum {count}", "archive_size": "Rozmiar archiwum", "archive_size_description": "Podziel pobierane pliki na więcej niÅŧ jedno archiwum, jeÅŧeli rozmiar archiwum przekroczy tę wartość w GiB", "archived": "Archiwum", "archived_count": "{count, plural, other {Zarchiwizowano #}}", "are_these_the_same_person": "Czy to jedna i ta sama osoba?", "are_you_sure_to_do_this": "Czy aby na pewno chcesz to zrobić?", + "array_field_not_fully_supported": "Elementy tablicy wymagają ręcznej edycji JSON", + "asset_action_delete_err_read_only": "Nie moÅŧna usunąć zasobÃŗw tylko do odczytu, pomijam", + "asset_action_share_err_offline": "Nie moÅŧna pobrać zasobÃŗw offline, pomijam", "asset_added_to_album": "Dodano do albumu", "asset_adding_to_album": "Dodawanie do albumuâ€Ļ", "asset_created": "Utworzono zasÃŗb", - "asset_day_count": "{date}: {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", "asset_description_updated": "Zaktualizowano opis zasobu", + "asset_filename_is_offline": "ZasÃŗb {filename} jest offline", + "asset_has_unassigned_faces": "ZasÃŗb ma nieprzypisane twarze", "asset_hashing": "Hashowanieâ€Ļ", "asset_list_group_by_sub_title": "Grupuj według", + "asset_list_layout_settings_dynamic_layout_title": "Układ dynamiczny", + "asset_list_layout_settings_group_automatically": "Automatyczny", "asset_list_layout_settings_group_by": "Grupuj zasoby według", "asset_list_layout_settings_group_by_month_day": "Miesiąc + dzień", "asset_list_layout_sub_title": "Układ", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Nie znaleziono zasobu w usłudze iCloud. ZasÃŗb moÅŧe być niedostępny z powodu uszkodzonego pliku przechowywanego w usłudze iCloud", "asset_offline": "ZasÃŗb niedostępny", "asset_offline_description": "Ten zewnętrzny zasÃŗb nie jest juÅŧ dostępny na dysku. Aby uzyskać pomoc, skontaktuj się z administratorem Immich.", + "asset_restored_successfully": "ZasÃŗb został pomyślnie przywrÃŗcony", "asset_skipped": "Pominięto", "asset_skipped_in_trash": "W koszu", + "asset_trashed": "ZasÃŗb wrzucono do kosza", "asset_troubleshoot": "Rozwiązywanie problemÃŗw z zasobami", "asset_uploaded": "Przesłano", "asset_uploading": "Przesyłanieâ€Ļ", "asset_viewer_settings_subtitle": "Zarządzaj ustawieniami przeglądarki galerii", "asset_viewer_settings_title": "Przeglądarka zasobÃŗw", "assets": "Zasoby", + "assets_added_count": "Dodano {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", "assets_added_to_album_count": "Dodano {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do albumu", - "assets_added_to_album_partial_count": "Dodano {successCount} z {totalCount, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do albumu", "assets_added_to_albums_count": "Dodano {assetTotal, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do {albumTotal, plural, one {# albumu} other {# albumÃŗw}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ZasÃŗb nie moÅŧe zostać dodany} other {Zasoby nie mogą zostać dodane}} do albumu", "assets_cannot_be_added_to_albums": "{count, plural, one {ZasÃŗb nie moÅŧe być dodany} other {Zasoby nie mogą być dodane}} do Åŧadnego z albumÃŗw", "assets_count": "{count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", + "assets_deleted_permanently": "{count} zostało trwale usuniętych", + "assets_deleted_permanently_from_server": "{count} zostało trwale usuniętych z serwera Immich", + "assets_downloaded_failed": "{count, plural, one {Pomyślnie pobrano # plik - {error} plik nie powiÃŗdł się} few {Pomyślnie pobrano # pliki - {error} pliki nie powiodły się} other {Pomyślnie pobrano # plikÃŗw - {error} pliki nie powiodły się}}", + "assets_downloaded_successfully": "{count, plural, one {Pomyślnie pobrano # plik} few {Pomyślnie pobrano # pliki} other {Pomyślnie pobrano # plikÃŗw}}", "assets_moved_to_trash_count": "Przeniesiono {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do kosza", "assets_permanently_deleted_count": "Trwale usunięto {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", "assets_removed_count": "Usunięto {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", + "assets_removed_permanently_from_device": "{count} zostało trwale usuniętych z Twojego urządzenia", "assets_restore_confirmation": "Na pewno chcesz przywrÃŗcić wszystkie zasoby z kosza? Nie da się tego cofnąć! NaleÅŧy pamiętać, Åŧe w ten sposÃŗb nie moÅŧna przywrÃŗcić zasobÃŗw offline.", "assets_restored_count": "PrzywrÃŗcono {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", + "assets_restored_successfully": "{count} pomyślnie przywrÃŗcono", "assets_trashed": "{count} szt. zostało wrzucone do kosza", "assets_trashed_count": "Wrzucono do kosza {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", + "assets_trashed_from_server": "{count} szt. usuniętych z serwera Immich", "assets_were_part_of_album_count": "{count, plural, one {ZasÃŗb był} other {Zasoby były}} juÅŧ częścią albumu", "assets_were_part_of_albums_count": "{count, plural, one {ZasÃŗb był} other {Zasoby były}} juÅŧ częścią albumÃŗw", "authorized_devices": "Autoryzowane urządzenia", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Automatyczne odtwarzanie pokazu slajdÃŗw", "back": "Wstecz", "back_close_deselect": "WrÃŗÄ‡, zamknij lub odznacz", + "background_backup_running_error": "Tworzenie kopii zapasowej w tle jest obecnie w toku, nie moÅŧna rozpocząć ręcznego tworzenia kopii zapasowej", "background_location_permission": "Uprawnienia do lokalizacji w tle", "background_location_permission_content": "Aby mÃŗc przełączać sieć podczas pracy w tle, Immich musi *zawsze* mieć dostęp do dokładnej lokalizacji, aby aplikacja mogła odczytać nazwę sieci Wi-Fi", "background_options": "Opcje w tle", "backup": "Kopia zapasowa", + "backup_album_selection_page_albums_device": "Albumy na urządzeniu ({count})", "backup_album_selection_page_albums_tap": "Stuknij, aby włączyć, stuknij dwukrotnie, aby wykluczyć", "backup_album_selection_page_assets_scatter": "Pliki mogą być rozproszone w wielu albumach. Dzięki temu albumy mogą być włączane lub wyłączane podczas procesu tworzenia kopii zapasowej.", "backup_album_selection_page_select_albums": "Wybierz albumy", "backup_album_selection_page_selection_info": "Info o wyborze", + "backup_album_selection_page_total_assets": "Łącznie unikalnych plikÃŗw", "backup_albums_sync": "Synchronizacja kopii zapasowych albumÃŗw", + "backup_all": "Wszystkie", + "backup_background_service_backup_failed_message": "Nie udało się wykonać kopii zapasowej zasobÃŗw. Ponowna prÃŗbaâ€Ļ", "backup_background_service_complete_notification": "Kopia zapasowa zasobu zakończona", + "backup_background_service_connection_failed_message": "Nie udało się połączyć z serwerem. Ponowna prÃŗbaâ€Ļ", + "backup_background_service_current_upload_notification": "Przesyłanie {filename}", "backup_background_service_default_notification": "Sprawdzanie nowych zasobÃŗwâ€Ļ", + "backup_background_service_error_title": "Błąd kopii zapasowej", "backup_background_service_in_progress_notification": "Tworzenie kopii zapasowej twoich zasobÃŗwâ€Ļ", + "backup_background_service_upload_failure_notification": "Błąd przesyłania {filename}", "backup_controller_page_albums": "Albumy z włączoną kopią zapasową", + "backup_controller_page_background_app_refresh_disabled_content": "Włącz odświeÅŧanie aplikacji w tle w Ustawienia > OgÃŗlne > OdświeÅŧanie aplikacji w tle, aby mÃŗc korzystać z kopii zapasowej w tle.", + "backup_controller_page_background_app_refresh_disabled_title": "OdświeÅŧanie aplikacji w tle wyłączone", + "backup_controller_page_background_app_refresh_enable_button_text": "PrzejdÅē do ustawień", "backup_controller_page_background_battery_info_link": "PokaÅŧ mi jak", "backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, naleÅŧy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPoniewaÅŧ jest to zaleÅŧne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Optymalizacja Baterii", + "backup_controller_page_background_charging": "Tylko podczas ładowania", + "backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle", "backup_controller_page_background_delay": "OpÃŗÅēnienie tworzenia kopii zapasowych nowych zasobÃŗw: {duration}", + "backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobÃŗw bez konieczności otwierania aplikacji", + "backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona", + "backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona", + "backup_controller_page_background_turn_off": "Wyłącz usługę w tle", + "backup_controller_page_background_turn_on": "Włącz usługę w tle", + "backup_controller_page_background_wifi": "Tylko Wi-Fi", "backup_controller_page_backup": "Kopia zapasowa", "backup_controller_page_backup_selected": "Wybrane: ", "backup_controller_page_backup_sub": "Zdjęcia i filmy z utworzoną kopią zapasową", + "backup_controller_page_created": "Utworzono dnia: {date}", + "backup_controller_page_desc_backup": "Włącz kopię zapasową na pierwszym planie, aby automatycznie przesyłać nowe zasoby na serwer po otworzeniu aplikacji.", "backup_controller_page_excluded": "Wykluczone: ", + "backup_controller_page_failed": "Nieudane ({count})", + "backup_controller_page_filename": "Nazwa pliku: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informacje o kopii zapasowej", "backup_controller_page_none_selected": "Nic nie wybrano", "backup_controller_page_remainder": "Pozostałe", "backup_controller_page_remainder_sub": "Pozostałe zdjęcia i filmy wybrane do wykonania kopii zapasowej", "backup_controller_page_server_storage": "Pamięć Serwera", + "backup_controller_page_start_backup": "Rozpocznij Kopię Zapasową", + "backup_controller_page_status_off": "Automatyczne tworzenie kopii zapasowej na pierwszym planie jest wyłączone", + "backup_controller_page_status_on": "Automatyczne tworzenie kopii zapasowej na pierwszym planie jest włączone", "backup_controller_page_storage_format": "Wykorzystano {used} z {total}", "backup_controller_page_to_backup": "Albumy, dla ktÃŗrych ma być tworzona kopia zapasowa", "backup_controller_page_total_sub": "Wszystkie unikalne zdjęcia i filmy z wybranych albumÃŗw", + "backup_controller_page_turn_off": "Wyłącz kopię zapasową na pierwszym planie", + "backup_controller_page_turn_on": "Włącz kopię zapasową na pierwszym planie", + "backup_controller_page_uploading_file_info": "Informacje o przesyłanym pliku", + "backup_err_only_album": "Nie moÅŧna usunąć jedynego albumu", "backup_error_sync_failed": "Synchronizacja nie powiodła się. Nie moÅŧna wykonać kopii zapasowej.", "backup_info_card_assets": "zasoby", + "backup_manual_cancelled": "Anulowano", + "backup_manual_in_progress": "Przesyłanie juÅŧ trwa. SprÃŗbuj po pewnym czasie", + "backup_manual_success": "Sukces", + "backup_manual_title": "Stan przesyłania", "backup_options": "Opcje kopii zapasowej", + "backup_options_page_title": "Opcje kopii zapasowej", + "backup_setting_subtitle": "Zarządzaj ustawieniami przesyłania w tle i na pierwszym planie", "backup_settings_subtitle": "Zarządzanie ustawieniami przesyłania", + "backup_upload_details_page_more_details": "Dotknij, aby wyświetlić więcej szczegÃŗÅ‚Ãŗw", "backward": "Do tyłu", - "battery_optimization_backup_reliability": "Wyłączenie optymalizacji baterii moÅŧe zwiększyć niezawodność tworzenia kopii zapasowych w tle", "biometric_auth_enabled": "Włączono logowanie biometryczne", "biometric_locked_out": "Uwierzytelnianie biometryczne jest dla Ciebie zablokowane", "biometric_no_options": "Brak moÅŧliwości biometrii", "biometric_not_available": "Logowanie biometryczne nie jest dostępne na tym urządzeniu", + "birthdate_saved": "Data urodzenia zapisana pomyślnie", "birthdate_set_description": "Data urodzenia jest uÅŧywana do obliczenia wieku danej osoby podczas wykonania zdjęcia.", "blurred_background": "Rozmyte tło", - "browse_templates": "Przeglądaj szablony", "bugs_and_feature_requests": "Błędy i prośby o funkcje", "build": "Kompilacja", "build_image": "Obraz Buildu", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Czy na pewno chcesz zachować {count, plural, one {# zduplikowany zasÃŗb} few {# zduplikowane zasoby} other {# zduplikowanych zasobÃŗw}}? To spowoduje rozwiązanie wszystkich grup duplikatÃŗw bez usuwania czegokolwiek.", "bulk_trash_duplicates_confirmation": "Czy na pewno chcesz wrzucić do kosza {count, plural, one {# zduplikowany zasÃŗb} few {# zduplikowane zasoby} other {# zduplikowanych zasobÃŗw}}? Zostanie zachowany największy zasÃŗb z kaÅŧdej grupy, a wszystkie pozostałe duplikaty zostaną wrzucone do kosza.", "buy": "Kup Immich", + "cache_settings_clear_cache_button": "Wyczyść Cache", + "cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopÃŗki pamięć podręczna nie zostanie odbudowana.", + "cache_settings_duplicated_assets_clear_button": "WYCZYŚĆ", + "cache_settings_duplicated_assets_subtitle": "Zdjęcia i filmy umieszczone na liście ignorowanych w aplikacji", + "cache_settings_duplicated_assets_title": "Zduplikowane zasoby ({count})", + "cache_settings_statistics_album": "Biblioteka miniatur", + "cache_settings_statistics_full": "Pełne Zdjęcia", + "cache_settings_statistics_shared": "Udostępnione miniatury albumÃŗw", + "cache_settings_statistics_thumbnail": "Miniatury", + "cache_settings_statistics_title": "UÅŧycie Cache", + "cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich", + "cache_settings_tile_subtitle": "Kontroluj zachowanie lokalnego magazynu", + "cache_settings_tile_title": "Lokalny magazyn", + "cache_settings_title": "Ustawienia Buforowania", "camera": "Aparat", "camera_brand": "Marka aparatu", "camera_model": "Model aparatu", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Nie moÅŧna zaktualizować opisu", "cast": "OdtwÃŗrz na telewizorze", "cast_description": "Skonfiguruj dostępne cele do przesyłania", - "change": "Zmień", "change_date": "Zmień datę", "change_description": "Zmień opis", "change_display_order": "Zmień kolejność wyświetlania", + "change_expiration_time": "Zmień czas waÅŧności", "change_location": "Zmień lokalizację", "change_name": "Zmień nazwę", "change_name_successfully": "Pomyślnie zmieniono nazwę", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Hasła nie są zgodne", "change_password_form_reenter_new_password": "WprowadÅē ponownie Nowe Hasło", "change_pin_code": "Zmień kod PIN", + "change_trigger": "Zmień wyzwalacz", + "change_trigger_prompt": "Czy na pewno chcesz zmienić wyzwalacz? Spowoduje to usunięcie wszystkich istniejących akcji i filtrÃŗw.", "change_your_password": "Zmień swoje hasło", "changed_visibility_successfully": "Pomyślnie zmieniono widoczność", "charging": "Ładowanie", "charging_requirement_mobile_backup": "Tworzenie kopii zapasowej w tle wymaga by urządzenie było podłączone do ładowania", + "check_corrupt_asset_backup": "SprawdÅē, czy kopie zapasowe zasobÃŗw nie są uszkodzone", + "check_corrupt_asset_backup_button": "Wykonaj sprawdzenie", + "check_corrupt_asset_backup_description": "Uruchom sprawdzenie tylko przez Wi-Fi i po utworzeniu kopii zapasowej wszystkich zasobÃŗw. Procedura moÅŧe potrwać kilka minut.", "check_logs": "Diagnostyka", "checksum": "Suma kontrolna", - "choose": "Wybierz", "choose_matching_people_to_merge": "Wybierz osoby, aby złączyć je w jedną", "city": "Miasto", "cleanup_confirm_description": "Immich znalazł {count} zasobÃŗw (utworzonych przed {date}) z kopią zapasową bezpiecznie przesłaną na serwer. Czy chcesz usunąć lokalne kopie z tego urządzenia?", @@ -719,10 +774,11 @@ "clear": "Wyczyść", "clear_all": "Wyczyść wszystko", "clear_all_recent_searches": "Usuń ostatnio wyszukiwane", - "clear_failed_count": "Wyczyść nieudane ({count})", "clear_file_cache": "Wyczyść pamięć podręczną plikÃŗw", "clear_message": "Wyczyść wiadomość", "clear_value": "Wyczyść wartość", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "WprowadÅē hasło", "client_cert_import": "Importuj", "client_cert_import_success_msg": "Certyfikat klienta został zaimportowany", "client_cert_invalid_msg": "Nieprawidłowy plik certyfikatu lub nieprawidłowe hasło", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certyfikat klienta został usunięty", "client_cert_subtitle": "Obsługuje wyłącznie format PKCS12 (.p12, .pfx). Importowanie/usuwanie certyfikatÃŗw jest dostępne wyłącznie przed zalogowaniem", "client_cert_title": "Certyfikat klienta SSL [EKSPERYMENTALNE]", + "clockwise": "Zgodnie z ruchem wskazÃŗwek zegara", "close": "Zamknij", "collapse": "Zwiń", "collapse_all": "Zwiń wszystko", "color": "Kolor", + "color_theme": "Motyw kolorÃŗw", "command": "Polecenie", "command_palette_prompt": "Szybko znajdÅē strony, działania lub polecenia", "command_palette_to_close": "aby zamknąć", @@ -747,7 +805,6 @@ "comments_are_disabled": "Komentarze są wyłączone", "common_create_new_album": "UtwÃŗrz nowy album", "completed": "Ukończono", - "configuration": "Konfiguracja", "confirm": "PotwierdÅē", "confirm_admin_password": "PotwierdÅē Hasło Administratora", "confirm_delete_face": "Czy na pewno chcesz usunąć twarz {name} z zasobÃŗw?", @@ -762,16 +819,19 @@ "contain": "Zawiera", "context": "Kontekst", "continue": "Kontynuuj", - "control_bottom_app_bar_add_tags": "Dodaj tagi", + "control_bottom_app_bar_create_new_album": "UtwÃŗrz nowy album", + "control_bottom_app_bar_delete_from_immich": "Usuń z Immicha", "control_bottom_app_bar_delete_from_local": "Usuń z urządzenia", "control_bottom_app_bar_edit_location": "Edytuj lokalizację", "control_bottom_app_bar_edit_time": "Edytuj datę i godzinę", + "control_bottom_app_bar_share_link": "Udostępnij link", + "control_bottom_app_bar_share_to": "Wyślij", "control_bottom_app_bar_trash_from_immich": "Przenieś do kosza", "copied_image_to_clipboard": "Skopiowano obraz do schowka.", "copied_to_clipboard": "Skopiowano do schowka!", "copy_error": "Błąd kopiowania", + "copy_file_path": "Skopiuj ścieÅŧkę pliku", "copy_image": "Skopiuj obraz", - "copy_json": "Skopiuj JSON", "copy_link": "Skopiuj link", "copy_link_to_clipboard": "Skopiuj link do schowka", "copy_password": "Skopiuj hasło", @@ -789,6 +849,7 @@ "create_link_to_share": "UtwÃŗrz link do udostępnienia", "create_link_to_share_description": "PozwÃŗl kaÅŧdemu z dostępem do linku zobaczyć wybrane zdjęcie/zdjęcia", "create_new": "UTWÓRZ NOWY", + "create_new_face": "UtwÃŗrz nową twarz", "create_new_person": "StwÃŗrz nową osobę", "create_new_person_hint": "Przypisz wybrane zasoby do nowej osoby", "create_new_user": "StwÃŗrz nowego uÅŧytkownika", @@ -805,33 +866,37 @@ "created_at": "Utworzony", "creating_linked_albums": "Tworzenie połączonych albumÃŗw...", "crop": "Przytnij", + "crop_aspect_ratio_fixed": "Stałe", "crop_aspect_ratio_free": "Dowolne", "crop_aspect_ratio_original": "Oryginalne", "crop_aspect_ratio_square": "Kwadrat", + "curated_object_page_title": "Rzeczy", "current_device": "Obecne urządzenie", "current_pin_code": "Aktualny kod PIN", "current_server_address": "Aktualny adres serwera", "custom_date": "Data niestandardowa", "custom_locale": "Niestandardowe ustawienia regionalne", "custom_locale_description": "Formatuj daty, godziny i liczby zgodnie z wybranym językiem i regionem", + "custom_url": "Niestandardowy URL", "cutoff_date_description": "Zachowaj zdjęcia z ostatnichâ€Ļ", "cutoff_day": "{count, plural, one {dzień} other {dni}}", "cutoff_year": "{count, plural, one {rok} few {lata} other {lat}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Ciemny", "dark_theme": "Przełącz na ciemny motyw", "date": "Data", "date_after": "Data po", "date_and_time": "Data i godzina", "date_before": "Data przed", - "date_of_birth": "Data urodzenia", + "date_format": "E d. LLL y â€ĸ hh:mm", "date_of_birth_saved": "Data urodzenia zapisana pomyślnie", "date_range": "Zakres dat", - "date_time_original": "Data/godzina oryginału", "day": "Dzień", "days": "Dni", "deduplicate_all": "Usuń duplikaty", - "default_quality_subtitle": "Jakość wykorzystywana przy klikaniu przycisku udostępniania. Aby wybrać opcję, naleÅŧy za kaÅŧdym razem długo nacisnąć przycisk udostępniania.", - "default_share_quality": "Domyślna jakość udostępniania", + "default_locale": "Domyślne ustawienia regionalne", + "default_locale_description": "Formatuj daty i liczby zgodnie z ustawieniami regionalnymi przeglądarki", "delete": "Usuń", "delete_action_confirmation_message": "Jesteś pewien, Åŧe chcesz usunąć ten zasÃŗb? Ta czynność przeniesie zasÃŗb do kosza na serwerze i wyświetli komunikat z pytaniem, czy chcesz go usunąć lokalnie", "delete_action_prompt": "{count} usuniętych", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Te elementy zostaną trwale usunięte z Immich i z Twojego urządzenia", "delete_dialog_alert_local": "Elementy te zostaną trwale usunięte z Twojego urządzenia, ale nadal będą dostępne na serwerze Immich", "delete_dialog_alert_local_non_backed_up": "NiektÃŗre elementy nie mają kopii zapasowej w Immich i zostaną trwale usunięte z Twojego urządzenia", + "delete_dialog_alert_remote": "Elementy te zostaną trwale usunięte z serwera Immich", + "delete_dialog_ok_force": "Usuń mimo to", "delete_dialog_title": "Usuń trwale", "delete_duplicates_confirmation": "Czy na pewno chcesz trwale usunąć te duplikaty?", "delete_face": "Usuń twarz", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Czy na pewno chcesz usunąć etykietę {tagName}?", "delete_user": "Usuń uÅŧytkownika", "deleted_shared_link": "Pomyślnie usunięto udostępniony link", + "deletes_missing_assets": "Usuwa brakujące zasoby z dysku", "description": "Opis", + "description_input_hint_text": "Dodaj opis...", + "description_input_submit_error": "Błąd aktualizacji opisu, sprawdÅē dziennik, aby uzyskać więcej szczegÃŗÅ‚Ãŗw", "deselect_all": "Odznacz wszystkie", "details": "SzczegÃŗÅ‚y", "direction": "Kierunek", "disable": "Wyłącz", - "disabled": "Nieaktywny", + "disabled": "Wyłączone", + "disallow_edits": "Nie pozwalaj edytować", "discord": "Discord", "discover": "Odkryj", "discovered_devices": "Wykryte urządzenia", "dismiss_all_errors": "Odrzuć wszystkie błędy", + "dismiss_error": "Odrzuć błąd", "display_options": "Opcje wyświetlania", "display_order": "Kolejność wyświetlania", "display_original_photos": "Wyświetlaj oryginalne zdjęcia", @@ -876,9 +948,11 @@ "documentation": "Dokumentacja", "done": "Gotowe", "download": "Pobierz", + "download_action_prompt": "Pobieranie {count} zasobÃŗw", "download_canceled": "Pobieranie anulowane", "download_complete": "Pobieranie zakończone", "download_enqueue": "Pobieranie w kolejce", + "download_error": "Błąd pobierania", "download_failed": "Pobieranie nieudane", "download_finished": "Pobieranie zakończone", "download_include_embedded_motion_videos": "Pobierz filmy ruchomych zdjęć", @@ -888,15 +962,15 @@ "download_paused": "Pobieranie wstrzymane", "download_settings": "Pobierz", "download_settings_description": "Zarządzaj pobieraniem zasobÃŗw", + "download_started": "Pobieranie rozpoczęte", + "download_sucess": "Udane pobieranie", + "download_sucess_android": "Media zostały pobrane do DCIM/Immich", "download_waiting_to_retry": "Oczekiwanie na ponowną prÃŗbę", "downloading": "Pobieranie", "downloading_asset_filename": "Pobieranie zasobu {filename}", "downloading_from_icloud": "Pobieranie z iCloud", "downloading_media": "Pobieranie multimediÃŗw", - "drag_to_reorder": "Przeciągnij, aby zmienić kolejność", "drop_files_to_upload": "Upuść pliki w dowolnym miejscu, aby je przesłać", - "duplicate": "Duplikuj", - "duplicate_workflow": "Duplikuj przepływ pracy", "duplicates": "Duplikaty", "duplicates_description": "Rozstrzygnij kaÅŧdą grupę, określając, ktÃŗre zasoby są duplikatami, jeÅŧeli są duplikatami.", "duration": "Czas trwania", @@ -908,7 +982,9 @@ "edit_date_and_time": "Edytuj datę i czas", "edit_date_and_time_action_prompt": "{count} daty i godziny zmodyfikowane", "edit_date_and_time_by_offset": "Zmień datę o przesunięcie", + "edit_date_and_time_by_offset_interval": "Nowy zakres dat: {from} - {to}", "edit_description": "Edycja opisu", + "edit_description_prompt": "Wybierz nowy opis:", "edit_exclusion_pattern": "Edytuj wzÃŗr wykluczający", "edit_faces": "Edytuj twarze", "edit_key": "Edytuj klucz", @@ -923,6 +999,9 @@ "edit_user": "Edytuj uÅŧytkownika", "edit_workflow": "Edytuj przepływ pracy", "editor": "Edytor", + "editor_close_without_save_prompt": "Zmiany nie zostaną zapisane", + "editor_close_without_save_title": "Zamknąć edytor?", + "editor_confirm_reset_all_changes": "Czy na pewno chcesz zresetować wszystkie zmiany?", "editor_discard_edits_confirm": "Odrzuć zmiany", "editor_discard_edits_prompt": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?", "editor_discard_edits_title": "Odrzucić zmiany?", @@ -930,8 +1009,8 @@ "editor_edits_applied_success": "Zmiany zostały pomyślnie zastosowane", "editor_flip_horizontal": "OdwrÃŗÄ‡ poziomo", "editor_flip_vertical": "OdwrÃŗÄ‡ pionowo", - "editor_handle_corner": "{corner, select, top_left {GÃŗrny lewy} top_right {GÃŗrny prawy} bottom_left {Dolny lewy} bottom_right {Dolny prawy} other {Jakiś}} uchwyt naroÅŧny", - "editor_handle_edge": "{edge, select, top {GÃŗrny} bottom {Dolny} left {Lewy} right {Prawy} other {Jakiś}} uchwyt krawędziowy", + "editor_handle_corner": "{corner, select, top_left {Top-left} top_right {Top-right} bottom_left {Bottom-left} bottom_right {Bottom-right} other {A}} uchwyt naroÅŧny", + "editor_handle_edge": "{edge, select, top {Top} bottom {Bottom} left {Left} right {Right} other {An}} uchwyt krawędziowy", "editor_orientation": "Orientacja", "editor_reset_all_changes": "Zresetuj zmiany", "editor_rotate_left": "ObrÃŗÄ‡ o 90° przeciwnie do ruchu wskazÃŗwek zegara", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Wpisz swÃŗj kod PIN", "enter_your_pin_code_subtitle": "WprowadÅē twÃŗj kod PIN, aby uzyskać dostęp do folderu zablokowanego", "error": "Błąd", + "error_change_sort_album": "Nie udało się zmienić kolejności sortowania albumÃŗw", "error_delete_face": "Błąd podczas usuwania twarzy z zasobÃŗw", + "error_getting_places": "Błąd podczas pozyskiwania lokalizacji", "error_loading_albums": "Błąd podczas ładowania albumÃŗw", "error_loading_image": "Błąd podczas ładowania zdjęcia", "error_loading_partners": "Błąd podczas ładowania partnerÃŗw: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Nie udało się usunąć klucza produktu", "failed_to_reset_pin_code": "Nie udało się zresetować kodu PIN", "failed_to_stack_assets": "Nie udało się utworzyć stosu z zasobÃŗw", - "failed_to_tag_assets": "Nie udało się opanować zasobÃŗw", "failed_to_unstack_assets": "Nie udało się rozdzielić zasobÃŗw", "failed_to_update_notification_status": "Nie udało się zaktualizować stanu powiadomienia", "incorrect_email_or_password": "Nieprawidłowy e-mail lub hasło", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Dodaj Opis...", "exif_bottom_sheet_description_error": "Wystąpił błąd podczas aktualizacji opisu", + "exif_bottom_sheet_details": "SZCZEGÓŁY", + "exif_bottom_sheet_location": "LOKALIZACJA", "exif_bottom_sheet_no_description": "Brak opisu", + "exif_bottom_sheet_people": "LUDZIE", + "exif_bottom_sheet_person_add_person": "Dodaj nazwę", "exit_slideshow": "Zamknij Pokaz SlajdÃŗw", "expand": "Rozwiń", "expand_all": "Rozwiń wszystko", + "experimental_settings_new_asset_list_subtitle": "Praca w toku", + "experimental_settings_new_asset_list_title": "Włącz eksperymentalny układ zdjęć", + "experimental_settings_subtitle": "UÅŧywaj na własne ryzyko!", + "experimental_settings_title": "Eksperymentalny", "expire_after": "Wygasa po", "expired": "Wygasły", "expires_date": "Wygasa {date}", @@ -1103,41 +1191,42 @@ "export_as_json": "Eksportuj jako JSON", "export_database": "Exportuj bazę danych", "export_database_description": "Exportuj bazę danych SQLite", - "exposure_time": "Czas ekspozycji", "extension": "Rozszerzenie", "external": "Zewnętrzny", "external_libraries": "Biblioteki Zewnętrzne", "external_network": "Sieć zewnętrzna", "external_network_sheet_info": "Jeśli nie korzystasz z preferowanej sieci Wi-Fi aplikacja połączy się z serwerem za pośrednictwem pierwszego z dostępnych poniÅŧej adresÃŗw URL, zaczynając od gÃŗry do dołu", - "f_number": "Wartość przysłony", "face_unassigned": "Nieprzypisany", "failed": "Niepowodzenie", "failed_count": "Nie powiodło się: {count}", "failed_to_authenticate": "Nie udało się uwierzytelnić", - "failed_to_delete_file": "Nie udało się usunąć pliku", "failed_to_load_assets": "Nie udało się załadować zasobÃŗw", "failed_to_load_folder": "Nie udało się załadować folderu", "favorite": "Ulubione", "favorite_action_prompt": "{count} dodane do ulubionych", "favorite_or_unfavorite_photo": "Dodaj lub usuń z ulubionych", "favorites": "Ulubione", + "favorites_page_no_favorites": "Nie znaleziono ulubionych zasobÃŗw", "feature_photo_updated": "Zdjęcie gÅ‚Ãŗwne zaktualizowane pomyślnie", "features": "Funkcje", + "features_in_development": "Funkcje w fazie rozwoju", "features_setting_description": "Zarządzaj funkcjami aplikacji", "file_name_or_extension": "Nazwie lub rozszerzeniu pliku", "file_name_text": "Nazwa pliku", + "file_name_with_value": "Nazwa pliku: {file_name}", "file_size": "Rozmiar pliku", "filename": "Nazwa pliku", "filetype": "Typ pliku", "filter": "Filtr", + "filter_description": "Warunki filtrowania wybranych zasobÃŗw", "filter_people": "Szukaj osoby", "filter_places": "Filtruj miejsca", "filter_tags": "Filtruj tagi", "filters": "Filtry", + "find_them_fast": "Wyszukuj szybciej przypisując nazwę", "first": "Pierwszy", "fix_incorrect_match": "Napraw nieprawidłowe dopasowanie", - "focal_length": "Ogniskowa", - "folder": "Katalog", + "folder": "Folder", "folder_not_found": "Nie znaleziono folderu", "folders": "Foldery", "folders_feature_description": "Przeglądanie zdjęć i filmÃŗw w widoku folderÃŗw", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Przenieś zdjęcia i filmy z kopią zapasową do kosza urządzenia, aby zwolnić miejsce. Twoje kopie na serwerze pozostają bezpieczne.", "free_up_space_settings_subtitle": "Zwolnij miejsce w pamięci urządzenia", "full_path": "Pełna ścieÅŧka: {path}", - "full_path_or_folder": "Pełna ścieÅŧka lub folder", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Ta funkcja , aby działać, ładuje zewnętrzne zasoby z Google.", "general": "OgÃŗlne", @@ -1168,6 +1256,7 @@ "group_owner": "Grupuj według właściciela", "group_places_by": "Grupuj miejsca według...", "group_year": "Grupuj według roku", + "haptic_feedback_switch": "Włącz technologię haptyczną", "haptic_feedback_title": "Technologia haptyczna", "has_quota": "Ma limit", "hash_asset": "Hashuj zasÃŗb", @@ -1188,12 +1277,29 @@ "hide_schema": "Ukryj schemat", "hide_text_recognition": "Ukryj rozpoznawanie tekstu", "hide_unnamed_people": "Ukryj nienazwaną osobę", + "home_page_add_to_album_conflicts": "Dodano {added} zasoby do albumu {album}. {failed} zasobÃŗw jest juÅŧ w albumie.", + "home_page_add_to_album_err_local": "Nie moÅŧna dodawać zasobÃŗw lokalnych do albumÃŗw, pomijam", + "home_page_add_to_album_success": "Dodano {added} zasoby do albumu {album}.", + "home_page_album_err_partner": "Nie moÅŧna jeszcze dodać zasobÃŗw partnera do albumu, pomijam", + "home_page_archive_err_local": "Nie moÅŧna jeszcze zarchiwizować zasobÃŗw lokalnych, pomijanie", + "home_page_archive_err_partner": "Nie moÅŧna zarchiwizować zasobÃŗw partnera, pomijam", "home_page_building_timeline": "Budowanie osi czasu", + "home_page_delete_err_partner": "Nie moÅŧna usunąć zasobÃŗw partnera, pomijam", + "home_page_delete_remote_err_local": "Lokalne zasoby w wyborze do zdalnego usunięcia, pomijam", + "home_page_favorite_err_local": "Nie moÅŧna jeszcze dodać do ulubionych lokalnych zasobÃŗw, pomijam", + "home_page_favorite_err_partner": "Nie moÅŧna jeszcze dodać do ulubionych zasobÃŗw partnera, pomijam", + "home_page_first_time_notice": "Jeśli korzystasz z aplikacji po raz pierwszy, pamiętaj o wybraniu albumÃŗw do kopii zapasowej, aby oś czasu mogła wypełnić się zdjęciami i filmami", + "home_page_locked_error_local": "Nie moÅŧna przenieść zasobÃŗw lokalnych do folderu zablokowanego, pomijam", + "home_page_locked_error_partner": "Nie moÅŧna przenieść zasobÃŗw partnera do folderu zablokowanego, pomijam", + "home_page_share_err_local": "Nie moÅŧna udostępnić zasobÃŗw lokalnych za pośrednictwem linku, pomijam", + "home_page_upload_err_limit": "MoÅŧna przesłać maksymalnie 30 zasobÃŗw jednocześnie, pomijanie", "host": "Host", "hour": "Godzina", "hours": "Godziny", "id": "ID", "idle": "Bezczynny", + "ignore_icloud_photos": "Ignoruj zdjęcia w iCloud", + "ignore_icloud_photos_description": "Zdjęcia przechowywane w usłudze iCloud nie zostaną przesłane na serwer Immich", "image": "Zdjęcie", "image_alt_text_date": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione dnia {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione z {person1} dnia {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1} i {person2} dnia {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1}, {person2} i {person3} dnia {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Wideo} other {Zdjęcie}} zrobione w {city}, {country} z {person1}, {person2} i {additionalCount, number} innymi dnia {date}", + "image_saved_successfully": "Obraz zapisany", + "image_viewer_page_state_provider_download_started": "Pobieranie rozpoczęte", + "image_viewer_page_state_provider_download_success": "Pobieranie zakończone", + "image_viewer_page_state_provider_share_error": "Udostępnij błąd", "immich_logo": "Logo Immich", "immich_web_interface": "Interfejs internetowy Immich", "import_from_json": "Wczytaj z JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Udostępniony zasÃŗb", "individual_shares": "Indywidualne udziały", "info": "Informacje", - "integrity_checks": "Kontrole integralności", "interval": { "day_at_onepm": "Codziennie o 13:00", "hours": "Co {hours, plural, one {godzinę} few {{hours, number} godziny} other {{hours, number} godzin}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Nieprawidłowy format daty", "invite_people": "Zaproś Osoby", "invite_to_album": "Zaproś do albumu", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Pobieranie przebiegło {dateTime}", + "ios_debug_info_last_sync_at": "Ostatnia synchronizacja {dateTime}", + "ios_debug_info_no_processes_queued": "Brak procesÃŗw w tle w kolejce", + "ios_debug_info_no_sync_yet": "Nie uruchomiono jeszcze Åŧadnego zadania synchronizacji w tle", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proces w tle w kolejce} few {{count} procesy w tle w kolejce} other {{count} procesÃŗw w tle w kolejce}}", + "ios_debug_info_processing_ran_at": "Przetwarzanie przebiegło {dateTime}", "items_count": "{count, plural, one {# element} few {# elementy} other {# elementÃŗw}}", "jobs": "Zadania", + "json_editor": "Edytor JSON", + "json_error": "Błąd JSON", "keep": "Zachowaj", "keep_albums": "Zachowaj albumy", "keep_albums_count": "Zachowuję {count} {count, plural, one {album} few {albumy} other {albumÃŗw}}", @@ -1259,12 +1375,14 @@ "leave": "Opuść", "leave_album": "Opuść album", "lens_model": "Model obiektywu", - "less": "Mniej", "let_others_respond": "PozwÃŗl innym reagować", "level": "Poziom", "library": "Biblioteka", "library_add_folder": "Dodaj folder", "library_edit_folder": "Edytuj folder", + "library_options": "Opcje biblioteki", + "library_page_device_albums": "Albumy na Urządzeniu", + "library_page_new_album": "Nowy album", "library_page_sort_asset_count": "Liczba zasobÃŗw", "library_page_sort_created": "Ostatnio utworzone", "library_page_sort_last_modified": "Ostatnio zmodyfikowany", @@ -1274,14 +1392,11 @@ "light_theme": "Przełącz na jasny motyw", "like": "Polub", "like_deleted": "Polubienie usunięte", - "link": "Link", "link_motion_video": "Podłącz ruchome wideo", "link_to_docs": "Więcej informacji znajdziesz w dokumentacji.", "link_to_oauth": "Połącz z OAuth", "linked_oauth_account": "Połączone konto OAuth", "list": "Lista", - "live": "Na Åŧywo", - "load_more": "Załaduj więcej", "loading": "Ładowanie", "loading_search_results_failed": "Ładowanie wynikÃŗw wyszukiwania nie powiodło się", "local": "Lokalny", @@ -1310,9 +1425,11 @@ "login": "Logowanie", "login_disabled": "Logowanie zostało wyłączone", "login_form_api_exception": "Wyjątek API. SprawdÅē adres URL serwera i sprÃŗbuj ponownie.", + "login_form_back_button_text": "Cofnij", "login_form_email_hint": "twojmail@email.com", "login_form_endpoint_hint": "http://ip-twojego-serwera:port", "login_form_endpoint_url": "URL Serwera", + "login_form_err_http": "Proszę określić http:// lub https://", "login_form_err_invalid_email": "Niepoprawny Email", "login_form_err_invalid_url": "Nieprawidłowy URL", "login_form_err_leading_whitespace": "Białe znaki", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Błąd logowania, sprawdÅē adres url serwera, email i hasło", "login_form_handshake_exception": "Wystąpił wyjątek uzgadniania z serwerem. Włącz obsługę certyfikatÃŗw z podpisem własnym w ustawieniach, jeśli uÅŧywasz certyfikatu z podpisem własnym.", "login_form_password_hint": "hasło", - "login_form_server_empty": "WprowadÅē adres URL serwera", - "login_form_server_error": "Nie moÅŧna połączyć się z serwerem", - "login_has_been_disabled": "Logowanie zostało wyłączone", + "login_form_save_login": "Pozostań zalogowany", + "login_form_server_empty": "WprowadÅē adres URL serwera.", + "login_form_server_error": "Nie moÅŧna połączyć się z serwerem.", + "login_has_been_disabled": "Logowanie zostało wyłączone.", "login_password_changed_error": "Wystąpił błąd podczas aktualizacji hasła", "login_password_changed_success": "Hasło zostało zmienione", "logout_all_device_confirmation": "Czy na pewno chcesz wylogować się ze wszystkich urządzeń?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Przywracanie bazy danych", "maintenance_description": "Immich został przełączony w tryb konserwacji.", "maintenance_end": "Zakończ tryb konserwacji", + "maintenance_end_error": "Nie udało się zakończyć trybu konserwacji.", "maintenance_logged_in_as": "Obecnie zalogowano jako {user}", "maintenance_restore_from_backup": "PrzywrÃŗÄ‡ z kopii zapasowej", "maintenance_restore_library": "PrzywrÃŗÄ‡ swoją bibliotekę", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "OtwÃŗrz ustawienia", "manage_media_access_subtitle": "ZezwÃŗl aplikacji Immich na zarządzanie plikami multimedialnymi i przenoszenie ich.", "manage_media_access_title": "Dostęp do zarządzania mediami", + "manage_shared_links": "Zarządzaj udostępnionymi linkami", "manage_sharing_with_partners": "Zarządzaj dzieleniem z partnerami", "manage_the_app_settings": "Zarządzaj ustawieniami aplikacji", "manage_your_account": "Zarządzaj swoim kontem", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Zarządzaj swoimi zalogowanymi urządzeniami", "manage_your_oauth_connection": "Zarządzaj swoim połączeniem OAuth", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {Brak zdjęć w tym obszarze} one {# zdjęcie} other {# zdjęć}}", "map_cannot_get_user_location": "Nie moÅŧna uzyskać lokalizacji uÅŧytkownika", + "map_location_dialog_yes": "Tak", "map_location_picker_page_use_location": "UÅŧyj tej lokalizacji", "map_location_service_disabled_content": "Aby wyświetlić zasoby z Twojej bieÅŧącej lokalizacji, naleÅŧy włączyć usługę lokalizacyjną. Czy chcesz to teraz włączyć?", "map_location_service_disabled_title": "Usługa lokalizacji wyłączona", - "map_marker_for_image": "Znacznik na mapie dla zdjęcia wykonanego w {city}, {country}", + "map_marker_for_images": "WskaÅēnik mapy dla zdjęć zrobionych w {city}, {country}", "map_marker_with_image": "Znacznik na mapie ze zdjęciem", "map_no_location_permission_content": "Aby wyświetlić zasoby z Twojej bieÅŧącej lokalizacji, potrzebne jest pozwolenie na lokalizację. Czy chcesz teraz na to pozwolić?", "map_no_location_permission_title": "Odmowa dostępu do lokalizacji", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Minione {days} dni", "map_settings_date_range_option_year": "Miniony rok", "map_settings_date_range_option_years": "Ostatnie {years} lat", + "map_settings_dialog_title": "Ustawienia mapy", "map_settings_include_show_archived": "Uwzględnij zarchiwizowane", "map_settings_include_show_partners": "Uwzględnij partnerÃŗw", "map_settings_only_show_favorites": "PokaÅŧ tylko ulubione", "map_settings_theme_settings": "Motyw mapy", + "map_zoom_to_see_photos": "Pomniejsz, aby zobaczyć zdjęcia", "mark_all_as_read": "Zaznacz wszystkie jako odczytane", + "mark_as_read": "Zaznacz jako odczytane", "marked_all_as_read": "Zaznaczono wszystkie jako przeczytane", "matches": "Powiązania", "matching_assets": "Pasujące zasoby", - "media_chrome": { - "auto": "Auto", - "captions": "Napisy", - "captions_off": "Wyłączone", - "closed_captions": "napisy zamknięte", - "decode_error": "Błąd dekodowania", - "disable_captions": "Wyłącz napisy", - "enable_captions": "Włącz napisy", - "enter_fullscreen_mode": "PrzejdÅē do trybu pełnoekranowego", - "exit_fullscreen_mode": "WyjdÅē z trybu pełnoekranowego", - "loop": "Pętla", - "media_error_description": "Wystąpił błąd pliku multimedialnego, co spowodowało przerwanie odtwarzania. Plik moÅŧe być uszkodzony lub Twoja przeglądarka nie obsługuje tego formatu.", - "media_loading": "ładowanie multimediÃŗw", - "mute": "Wycisz", - "network_error": "Błąd sieci", - "network_error_description": "Pobieranie pliku multimedialnego nie powiodło się z powodu błędu sieciowego.", - "not_supported_error": "ÅšrÃŗdło nieobsługiwane", - "playback_rate": "Szybkość odtwarzania", - "playback_rate_current": "aktualna prędkość odtwarzania", - "playback_rate_value": "Prędkość odtwarzania {playbackRate}", - "playback_time": "czas odtwarzania", - "quality": "Jakość", - "second": "sekunda", - "seconds": "sekundy", - "time_value_of_total_time": "{currentTime} z {totalTime}", - "time_value_remaining": "Pozostało {time}", - "unmute": "Włącz dÅēwięk", - "unsupported_error_description": "Wystąpił nieobsługiwany błąd. ZawiÃŗdł serwer lub sieć albo twoja przeglądarka nie obsługuje tego formatu.", - "video_not_loaded_unknown_time": "Film nie został załadowany, czas nieznany.", - "video_player": "odtwarzacz filmÃŗw", - "volume": "głośność" - }, "media_type": "Typ zasobu", "memories": "Wspomnienia", "memories_all_caught_up": "Wszystko złapane", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Czy chcesz połączyć te osoby? Ta czynność jest nieodwracalna.", "merge_people_successfully": "Pomyślnie złączono osoby", "merged_people_count": "Połączono {count, plural, one {# osobę} few {# osoby} other {# osÃŗb}}", - "minFaces": "Minimalna liczba twarzy", - "minFaces_description": "Minimalna liczba rozpoznanych twarzy, aby dana osoba została wyświetlona", "minimize": "Zminimalizuj", "minute": "Minuta", "minutes": "Minuty", + "mirror_horizontal": "Poziomo", + "mirror_vertical": "Pionowo", "missing": "Brakujące", "mobile_app": "Aplikacja mobilna", "mobile_app_download_onboarding_note": "Pobierz towarzyszącą aplikację mobilną, korzystając z następujących opcji", "model": "Model", - "modify_date": "Data modyfikacji", "month": "Miesiąc", + "monthly_title_text_date_format": "MMMM y", "more": "Więcej", - "motion": "Ruch", "move": "Przenieś", + "move_down": "Przesuń w dÃŗÅ‚", "move_off_locked_folder": "Przenieś z folderu zablokowanego", "move_to": "Przenieś do", "move_to_device_trash": "Przenieś do kosza urządzenia", "move_to_lock_folder_action_prompt": "{count} dodanych do folderu zablokowanego", "move_to_locked_folder": "Przenieś do folderu zablokowanego", "move_to_locked_folder_confirmation": "Te zdjęcia i filmy zostaną usunięte ze wszystkich albumÃŗw i będą widzialne tylko w folderze zablokowanym", + "move_up": "Przesuń w gÃŗrę", + "moved_to_archive": "Przeniesiono {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do archiwum", + "moved_to_library": "Przeniesiono {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}} do biblioteki", "moved_to_trash": "Przeniesiono do kosza", + "multiselect_grid_edit_date_time_err_read_only": "Nie moÅŧna edytować daty zasobÃŗw tylko do odczytu, pomijanie", + "multiselect_grid_edit_gps_err_read_only": "Nie moÅŧna edytować lokalizacji zasobÃŗw tylko do odczytu, pomijanie", "mute_memories": "Wycisz wspomnienia", "my_albums": "Moje albumy", - "my_immich_description": "Skopiuj bieÅŧącą stronę jako link „MÃŗj Immich”", - "my_immich_title": "MÃŗj Immich link", "name": "Nazwa", "name_or_nickname": "Nazwa lub pseudonim", "name_required": "Imię jest wymagane", @@ -1480,11 +1576,12 @@ "never": "Nigdy", "new_album": "Nowy album", "new_api_key": "Nowy Klucz API", - "new_feature": "Nowa funkcjonalność", + "new_date_range": "Nowy zakres dat", "new_password": "Nowe hasło", "new_person": "Nowa osoba", "new_pin_code": "Nowy kod PIN", "new_pin_code_subtitle": "Jest to pierwszy raz, kiedy wchodzisz do folderu zablokowanego. UtwÃŗrz kod PIN, aby bezpiecznie korzystać z tej strony", + "new_timeline": "Nowa oś czasu", "new_update": "Nowa aktualizacja", "new_user_created": "Pomyślnie stworzono nowego uÅŧytkownika", "new_version_available": "NOWA WERSJA DOSTĘPNA", @@ -1492,6 +1589,7 @@ "next": "Dalej", "next_memory": "Następne wspomnienie", "no": "Nie", + "no_actions_added": "Nie dodano jeszcze Åŧadnych akcji", "no_albums_found": "Nie znaleziono albumÃŗw", "no_albums_message": "StwÃŗrz album, aby organizować Twoje zdjęcia i filmy", "no_albums_with_name_yet": "Wygląda na to, Åŧe nie masz jeszcze Åŧadnych albumÃŗw o tej nazwie.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Nie znaleziono urządzeń do przesyłania strumieniowego", "no_checksum_local": "Brak sumy kontrolnej - nie moÅŧna pobrać lokalnych zasobÃŗw", "no_checksum_remote": "Brak sumy kontrolnej - nie moÅŧna pobrać zdalnego zasobu", + "no_configuration_needed": "Nie wymaga konfiguracji", "no_devices": "Brak autoryzowanych urządzeń", "no_duplicates_found": "Nie znaleziono duplikatÃŗw.", "no_exif_info_available": "Nie znaleziono informacji exif", "no_explore_results_message": "Prześlij więcej zdjęć, aby przeglądać swÃŗj zbiÃŗr.", "no_favorites_message": "Dodaj ulubione aby szybko znaleÅēć swoje najlepsze zdjęcia i filmy", + "no_filters_added": "Nie dodano jeszcze Åŧadnych filtrÃŗw", "no_libraries_message": "StwÃŗrz bibliotekę zewnętrzną, aby przeglądać swoje zdjęcia i filmy", "no_local_assets_found": "Nie znaleziono Åŧadnych lokalnych zasobÃŗw o tej sumie kontrolnej", + "no_location_set": "Nie ustawiono lokalizacji", "no_locked_photos_message": "Zdjęcia i filmy w folderze zablokowanym są ukryte i nie będą wyświetlane podczas przeglądania biblioteki.", "no_name": "Brak Nazwy", "no_notifications": "Brak powiadomień", @@ -1518,19 +1619,14 @@ "no_results": "Brak wynikÃŗw", "no_results_description": "SprÃŗbuj uÅŧyć synonimu lub bardziej ogÃŗlnego słowa kluczowego", "no_shared_albums_message": "StwÃŗrz album aby udostępnić zdjęcia i filmy osobom w Twojej sieci", - "no_steps": "Nie dodano jeszcze Åŧadnych krokÃŗw", + "no_uploads_in_progress": "Brak przesyłań w toku", "none": "Åģadne", "not_allowed": "Niedozwolone", "not_available": "Nie dotyczy", "not_in_any_album": "Bez albumu", "not_selected": "Nie wybrano", - "not_set": "Nie ustawiono", "notes": "Uwagi", "nothing_here_yet": "Nic tu jeszcze nie ma", - "notification_backup_reliability": "Włącz powiadomienia, aby zwiększyć niezawodność tworzenia kopii zapasowych w tle", - "notification_enabled_list_tile_content": "Immich wykorzystuje powiadomienia do tworzenia kopii zapasowej w tle. Zarządzaj nimi w ustawieniach urządzenia.", - "notification_enabled_list_tile_open_button": "OtwÃŗrz ustawienia", - "notification_enabled_list_tile_title": "Powiadomienia włączone", "notification_permission_dialog_content": "Aby włączyć powiadomienia, przejdÅē do Ustawień i wybierz opcję Zezwalaj.", "notification_permission_list_tile_content": "Przyznaj uprawnienia, aby włączyć powiadomienia.", "notification_permission_list_tile_enable_button": "Włącz Powiadomienia", @@ -1540,10 +1636,8 @@ "notifications_setting_description": "Zarządzanie powiadomieniami", "oauth": "OAuth", "obtainium_configurator": "Konfigurator Obtainium", - "obtainium_configurator_instructions": "UÅŧyj Obtainium, aby zainstalować i aktualizować aplikację na Androida bezpośrednio z wydania GitHuba Immich. UtwÃŗrz klucz API i wybierz wariant, aby utworzyć link konfiguracyjny Obtainium", + "obtainium_configurator_instructions": "UÅŧyj Obtainium, aby zainstalować i zaktualizować aplikację na Androida bezpośrednio z wydania GitHuba Immich. UtwÃŗrz klucz API i wybierz wariant, aby utworzyć link konfiguracyjny Obtainium", "ocr": "OCR", - "ocr_body": "Immich odczytuje teraz tekst znajdujący się na twoich zdjęciach, dzięki czemu moÅŧesz je wyszukiwać na podstawie treści.", - "ocr_title": "Wyszukaj tekst na twoich zdjęciach", "official_immich_resources": "Oficjalne zasoby Immicha", "offline": "Offline", "offset": "Przesunięcie", @@ -1562,8 +1656,6 @@ "open": "OtwÃŗrz", "open_calendar": "OtwÃŗrz kalendarz", "open_in_browser": "OtwÃŗrz w przeglądarce", - "open_in_immich_body": "Ustaw Immich jako domyślną galerię w systemie Android, aby otwierać zdjęcia bezpośrednio z innych aplikacji.", - "open_in_immich_title": "OtwÃŗrz zdjęcia w Immich", "open_in_map_view": "OtwÃŗrz w widoku mapy", "open_in_openstreetmap": "OtwÃŗrz w OpenStreetMap", "open_the_search_filters": "OtwÃŗrz filtry wyszukiwania", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Uporządkuj w albumy", "organize_into_albums_description": "Umieść istniejące zdjęcia w albumach przy uÅŧyciu bieÅŧących ustawień synchronizacji", "organize_your_library": "Organizuj swoją bibliotekę", - "orientation": "Orientacja", "original": "oryginalny", "other": "Inne", "other_devices": "Inne urządzenia", + "other_entities": "Inne byty", "other_variables": "Inne zmienne", "owned": "Posiadane", "owner": "Właściciel", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Wszystkie Twoje zdjęcia i filmy, oprÃŗcz tych w Archiwum i Koszu", "partner_can_access_location": "Informacji o tym, gdzie zostały zrobione Twoje zdjęcia", "partner_list_user_photos": "Zdjęcia naleÅŧące do {user}", + "partner_list_view_all": "PokaÅŧ wszystkie", "partner_page_empty_message": "Twoje zdjęcia nie są udostępnione Åŧadnemu partnerowi.", "partner_page_no_more_users": "Brak uÅŧytkownikÃŗw do dodania", + "partner_page_partner_add_failed": "Nie udało się dodać partnera", "partner_page_select_partner": "Wybierz partnera", + "partner_page_shared_to_title": "Udostępniono", "partner_page_stop_sharing_content": "{partner} nie będzie juÅŧ mieć dostępu do Twoich zdjęć.", "partner_sharing": "Dzielenie z partnerami", "partners": "Partnerzy", @@ -1609,6 +1704,8 @@ "people": "Osoby", "people_edits_count": "Edytowano {count, plural, one {# osoba} few {# osoby} many {# osÃŗb} other {# osÃŗb}}", "people_feature_description": "Przeglądanie zdjęć i filmÃŗw pogrupowanych według osÃŗb", + "people_selected": "{count, plural, one {# osoba wybrana} few {# osoby wybrane} other {# osÃŗb wybranych}}", + "people_sidebar_description": "Pokazuj link do OsÃŗb w panelu bocznym", "permanent_deletion_warning": "OstrzeÅŧenie o trwałym usunięciu", "permanent_deletion_warning_setting_description": "PokaÅŧ ostrzeÅŧenie przy trwałym usuwaniu zasobÃŗw", "permanently_delete": "Usuń trwale", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Trwale usunięto {count, plural, one {# zasÃŗb} other {# zasobÃŗw}}", "permission": "Pozwolenie", "permission_empty": "Twoje pozwolenie nie powinno być puste", + "permission_onboarding_back": "Cofnij", + "permission_onboarding_continue_anyway": "Kontynuuj mimo to", + "permission_onboarding_get_started": "Rozpocznij", + "permission_onboarding_go_to_settings": "PrzejdÅē do ustawień", + "permission_onboarding_permission_denied": "Odmowa pozwolenia. Aby korzystać z Immich, przyznaj uprawnienia do zdjęć i filmÃŗw w Ustawieniach.", + "permission_onboarding_permission_granted": "Pozwolenie udzielone! Wszystko gotowe.", + "permission_onboarding_permission_limited": "Pozwolenie ograniczone. Aby umoÅŧliwić Immichowi tworzenie kopii zapasowych całej kolekcji galerii i zarządzanie nią, przyznaj uprawnienia do zdjęć i filmÃŗw w Ustawieniach.", + "permission_onboarding_request": "Immich potrzebuje pozwolenia na przeglądanie Twoich zdjęć i filmÃŗw.", "person": "Osoba", "person_age_months": "{months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", "person_age_year_months": "1 rok, {months, plural, one {# miesiąc} few {# miesiące} many {# miesięcy} other {# miesięcy}}", - "person_age_years": "{years, plural, one {# rok} few {# lata} other {# lat}}", + "person_age_years": "{years, plural, one {# rok} few {# lata} many {# lat} other {# lat}}", "person_birthdate": "Urodzony {date}", "person_hidden": "{name}{hidden, select, true { (ukryty)} other {}}", "person_recognized": "Osoba rozpoznana", + "person_selected": "Osoba wybrana", "photo_shared_all_users": "Wygląda na to, Åŧe udostępniłeś swoje zdjęcia wszystkim uÅŧytkownikom lub nie masz Åŧadnego uÅŧytkownika, z ktÃŗrym moÅŧna by było je udostępnić.", "photos": "Zdjęcia", "photos_and_videos": "Zdjęcia i Filmy", + "photos_count": "{count, plural, one {{count, number} Zdjęcie} few {{count, number} Zdjęcia} other {{count, number} Zdjęć}}", "photos_from_previous_years": "Zdjęcia z ubiegłych lat", + "photos_only": "Tylko zdjęcia", "pick_a_location": "Oznacz lokalizację", "pick_custom_range": "Zakres niestandardowy", "pick_date_range": "Wybierz zakres dat", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Preferuj odtwarzanie oryginalnych nagrań wideo zamiast nagrań transkodowanych. Jeśli oryginalny zasÃŗb nie jest kompatybilny, moÅŧe nie być odtwarzany poprawnie.", "play_transcoded_video": "OdtwÃŗrz transkodowane wideo", "please_auth_to_access": "Uwierzytelnij się, aby uzyskać dostęp", - "plugin_method_filter_type": "Filtr", - "plugin_method_filter_type_description": "Ta metoda pozwala filtrować zdarzenia i warunkowo blokować wykonywanie kolejnych krokÃŗw", "port": "Port", "preferences_settings_subtitle": "Zarządzaj preferencjami aplikacji", "preferences_settings_title": "Ustawienia", @@ -1665,11 +1771,11 @@ "privacy": "Prywatność", "profile": "Profil", "profile_drawer_app_logs": "Logi", + "profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Włączono tryb tylko do odczytu. Aby wyjść, naciśnij i przytrzymaj ikonę awatara uÅŧytkownika.", "profile_image_of_user": "Zdjęcie profilowe {user}", "profile_picture_set": "Zdjęcie profilowe ustawione.", - "projection_type": "Typ projekcji", "public_album": "Publiczny album", "public_share": "Udostępnienie publiczne", "purchase_account_info": "Wspierający", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Status wspierającego", "purchase_individual_title": "Osoba Prywatna", "purchase_input_suggestion": "Posiadasz klucz produktu? Wpisz go poniÅŧej", + "purchase_license_subtitle": "Kup Immich, aby wesprzeć jego dalszy rozwÃŗj", "purchase_lifetime_description": "Jednorazowy zakup", "purchase_option_title": "OPCJE ZAKUPU", "purchase_panel_info_1": "Tworzenie Immich wymaga duÅŧo czasu i wysiłku, a nasi inÅŧynierowie pracują nad tym na pełen etat, aby uczynić go jak najlepszym. Naszą misją jest, aby oprogramowanie open-source i etyczne praktyki biznesowe stały się zrÃŗwnowaÅŧonym ÅērÃŗdłem dochodu dla deweloperÃŗw oraz stworzyć ekosystem szanujący prywatność z prawdziwymi alternatywami dla eksploatacyjnych usług w chmurze.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Przypisano ponownie {count, plural, one {# zasÃŗb} other {# zasobÃŗw}} do nowej osoby", "reassing_hint": "Przypisz wybrane zasoby do istniejącej osoby", "recent": "Ostatnie", + "recent_albums": "Ostatnie albumy", "recent_searches": "Ostatnie wyszukiwania", "recently_added": "Ostatnio dodane", - "recently_added_body": "PrzejdÅē od razu do wszystkich elementÃŗw, ktÃŗre ostatnio dodałeś, na specjalnej stronie.", - "recently_added_description": "Przeglądaj swoje zasoby posortowane według daty przesłania ich do Immich", "recently_added_page_title": "Ostatnio Dodane", - "recently_added_title": "Ostatnio dodane", "recently_taken": "Ostatnio wykonane", + "recently_taken_page_title": "Ostatnio Wykonane", "refresh": "OdświeÅŧ", "refresh_encoded_videos": "OdświeÅŧ enkodowane wideo", "refresh_faces": "OdświeÅŧ twarze", "refresh_metadata": "OdświeÅŧ metadane", "refresh_thumbnails": "OdświeÅŧ miniatury", "refreshed": "OdświeÅŧone", + "refreshes_every_file": "Ponownie odczytuje wszystkie istniejące i nowe pliki", "refreshing_encoded_video": "OdświeÅŧanie enkodowanych wideo", "refreshing_faces": "OdświeÅŧanie twarzy", "refreshing_metadata": "OdświeÅŧanie metadanych", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Czy na pewno chcesz usunąć {count, plural, one {# zasÃŗb} other {# zasoby}} z tego udostępnionego linku?", "remove_assets_title": "Usunąć zasoby?", "remove_custom_date_range": "Usuń niestandardowy zakres dat", - "remove_filter": "Usuń filtr", + "remove_deleted_assets": "Usuń Niedostępne Pliki", "remove_from_album": "Usuń z albumu", "remove_from_album_action_prompt": "{count} usunięto z albumu", "remove_from_favorites": "Usuń z ulubionych", @@ -1756,22 +1863,29 @@ "remove_memory": "Usuń wspomnienie", "remove_photo_from_memory": "Usuń zdjęcia z tych wspomnień", "remove_tag": "Usuń tag", + "remove_url": "Usuń URL", "remove_user": "Usuń uÅŧytkownika", "removed_api_key": "Usunięto Klucz API: {name}", "removed_from_archive": "Usunięto z archiwum", "removed_from_favorites": "Usunięto z ulubionych", "removed_from_favorites_count": "{count, plural, other {Usunięto #}} z ulubionych", "removed_memory": "Wspomnienie usunięte", + "removed_photo_from_memory": "Usunięto zdjęcie ze wspomnień", "removed_tagged_assets": "Usunięto etykietę z {count, plural, one {# zasobu} other {# zasobÃŗw}}", "rename": "Zmień nazwę", + "repair": "Napraw", + "repair_no_results_message": "Tutaj pojawią się nieśledzone i brakujące pliki", + "replace_with_upload": "Prześlij nową wersję", "repository": "Repozytorium", "require_password": "Wymagaj hasła", + "require_user_to_change_password_on_first_login": "Zmuś uÅŧytkownika do zmiany hasła podczas następnego logowania", "rescan": "Ponowne skanowanie", "reset": "Reset", "reset_password": "Resetuj hasło", "reset_people_visibility": "Zresetuj widoczność osÃŗb", "reset_pin_code": "Zresetuj kod PIN", "reset_pin_code_description": "Jeśli zapomniałeś swojego kodu PIN, moÅŧesz skontaktować się z administratorem serwera, aby go zresetować", + "reset_pin_code_success": "Pomyślnie zresetowano kod PIN", "reset_pin_code_with_password": "Zawsze moÅŧesz zresetować swÃŗj kod PIN za pomocą hasła", "reset_sqlite": "Zresetuj bazę danych SQLite", "reset_sqlite_clear_app_data": "Wyczyść dane", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Rozwiązano wszystkie duplikaty", "restore": "PrzywrÃŗcić", "restore_all": "PrzywrÃŗÄ‡ wszystko", + "restore_trash_action_prompt": "{count} przywrÃŗcono z kosza", "restore_user": "PrzywrÃŗÄ‡ uÅŧytkownika", "restored_asset": "PrzywrÃŗcony zasÃŗb", "resume": "WznÃŗw", "resume_paused_jobs": "WznÃŗw {count, plural, one {# wstrzymane zadanie} few {# wstrzymane zadania} other {# wstrzymanych zadań}}", + "retry_upload": "Prześlij ponownie", "review_duplicates": "Przejrzyj duplikaty", "review_large_files": "Przejrzyj duÅŧe pliki", "role": "Rola", @@ -1796,6 +1912,7 @@ "role_viewer": "Widz", "running": "W trakcie", "save": "Zapisz", + "save_to_gallery": "Zapisz w galerii", "saved": "Zapisano", "saved_api_key": "Zapisany klucz API", "saved_profile": "Zapisany profil", @@ -1806,10 +1923,9 @@ "scan": "Skanuj", "scan_all_libraries": "Skanuj wszystkie biblioteki", "scan_library": "Skanuj", + "scan_settings": "Ustawienia Skanowania", "scanning": "Skanowanie", "scanning_for_album": "Skanuję album...", - "screencast_mode_description": "Wyświetlaj na ekranie wskaÅēniki zdarzeń klawiatury i myszy", - "screencast_mode_title": "Włącz tryb prezentacji ekranu", "search": "Szukaj", "search_albums": "Przeszukaj albumy", "search_by_context": "Wyszukaj według treści", @@ -1817,10 +1933,8 @@ "search_by_description_example": "Całodniowa wycieczka w Bieszczady", "search_by_filename": "Szukaj według nazwy pliku lub rozszerzenia", "search_by_filename_example": "np. IMG_1234.JPG lub PNG", - "search_by_full_path": "Wyszukaj według pełnej ścieÅŧki lub folderu", - "search_by_full_path_example": "/John/Projekty/Drukowanie_3D/2026-07-01 – moÅŧesz wyszukiwać hasła takie jak Projekty, 3D, Drukowanie, 2026 itp.", "search_by_ocr": "Wyszukaj przy uÅŧyciu OCR", - "search_by_ocr_example": "Kawa, trampolina", + "search_by_ocr_example": "Latte", "search_camera_lens_model": "Wyszukaj model obiektywu...", "search_camera_make": "Wyszukaj markę aparatu...", "search_camera_model": "Wyszukaj model aparatu...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Wybierz lokalizację", "search_filter_media_type": "Typ multimediÃŗw", "search_filter_media_type_title": "Wybierz typ multimediÃŗw", + "search_filter_ocr": "Wyszukaj przy uÅŧyciu OCR", "search_filter_people_title": "Wybierz osoby", "search_filter_star_rating": "Ocena gwiazdkowa", "search_filter_tags_title": "Wybierz tagi", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Brak osÃŗb nazwanych \"{name}\"", "search_no_result": "Nie znaleziono wynikÃŗw, sprÃŗbuj innego terminu wyszukiwania lub kombinacji", "search_options": "Opcje wyszukiwania", + "search_page_categories": "Kategorie", + "search_page_motion_photos": "Zdjęcia ruchome", + "search_page_no_objects": "Brak informacji o obiektach", + "search_page_no_places": "Brak informacji o miejscu", + "search_page_screenshots": "Zrzuty ekranu", "search_page_search_photos_videos": "Wyszukaj swoje zdjęcia i filmy", + "search_page_selfies": "Selfiki", + "search_page_things": "Rzeczy", "search_page_view_all_button": "PokaÅŧ wszystkie", + "search_page_your_activity": "Twoja aktywność", + "search_page_your_map": "Twoja mapa", "search_people": "Wyszukaj osoby", "search_places": "Wyszukaj miejsca", "search_rating": "Wyszukaj według ocen...", + "search_result_page_new_search_hint": "Nowe wyszukiwanie", "search_settings": "Ustawienia przeszukiwania", "search_state": "Wyszukaj wojewÃŗdztwo...", + "search_suggestion_list_smart_search_hint_1": "Inteligentne wyszukiwanie jest domyślnie włączone, aby wyszukiwać metadane, uÅŧyj składni ", + "search_suggestion_list_smart_search_hint_2": "m:wyszukiwane hasło", "search_tags": "Wyszukaj etykiety...", "search_timezone": "Wyszukaj strefę czasową...", "search_type": "Wyszukaj w", @@ -1868,6 +1995,7 @@ "select_albums": "Wybierz albumy", "select_all": "Zaznacz wszystko", "select_all_duplicates": "Wybierz wszystkie duplikaty", + "select_all_in": "Wybierz wszystkie w {group}", "select_avatar_color": "Wybierz kolor awatara", "select_count": "{count, plural, one {Wybierz #} other {Wybierz #}}", "select_cutoff_date": "Wybierz datę graniczną", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Zmień gÅ‚Ãŗwne zdjęcie", "select_from_computer": "Wybierz z komputera", "select_keep_all": "Zaznacz zachowaj wszystko", + "select_library_owner": "Wybierz właściciela biblioteki", "select_new_face": "Wybierz nową twarz", "select_people": "Wybierz osoby", "select_person": "Wybierz osobę", "select_person_to_tag": "Wybierz osobę do oznaczenia", "select_photos": "Wybierz zdjęcia", - "select_quality": "Wybierz jakość", "select_trash_all": "Zaznacz wszystko do kosza", + "select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu", "selected": "Wybrane", "selected_count": "{count, plural, other {# wybrane}}", "selected_gps_coordinates": "Wybrane WspÃŗÅ‚rzędne GPS", @@ -1914,43 +2043,53 @@ "setting_image_viewer_original_title": "Załaduj oryginalny obraz", "setting_image_viewer_preview_subtitle": "Włącz, aby załadować obraz w średniej rozdzielczości. Wyłącz, aby załadować bezpośrednio oryginał lub uÅŧywać tylko miniatury.", "setting_image_viewer_preview_title": "Załaduj podgląd obrazu", + "setting_image_viewer_title": "Zdjęcia", "setting_languages_apply": "Zastosuj", "setting_languages_subtitle": "Zmień język aplikacji", + "setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {duration}", + "setting_notifications_notify_hours": "{count} godzin", + "setting_notifications_notify_immediately": "natychmiast", "setting_notifications_notify_minutes": "{count} minut", + "setting_notifications_notify_never": "nigdy", "setting_notifications_notify_seconds": "{count} sekund", + "setting_notifications_single_progress_subtitle": "SzczegÃŗÅ‚owe informacje o postępie przesyłania dla kaÅŧdego zasobu", + "setting_notifications_single_progress_title": "PokaÅŧ szczegÃŗÅ‚y postępu kopii zapasowej w tle", "setting_notifications_subtitle": "Dostosuj preferencje powiadomień", + "setting_notifications_total_progress_subtitle": "OgÃŗlny postęp przesyłania (gotowe/całkowite zasoby)", + "setting_notifications_total_progress_title": "PokaÅŧ całkowity postęp tworzenia kopii zapasowej w tle", "setting_video_viewer_auto_play_subtitle": "Automatycznie rozpoczynaj odtwarzanie filmÃŗw po ich otwarciu", "setting_video_viewer_auto_play_title": "Automatycznie odtwarzaj filmy", "setting_video_viewer_looping_title": "Zapętlanie", "setting_video_viewer_original_video_subtitle": "Podczas strumieniowego przesyłania wideo z serwera odtwarzaj oryginał, nawet jeśli transkodowanie jest dostępne. MoÅŧe to prowadzić do buforowania. Filmy dostępne lokalnie są odtwarzane w oryginalnej jakości niezaleÅŧnie od tego ustawienia.", "setting_video_viewer_original_video_title": "Wymuś oryginalne wideo", "settings": "Ustawienia", + "settings_require_restart": "Aby zastosować to ustawienie, uruchom ponownie Immich", "settings_saved": "Ustawienia zapisane", "setup_pin_code": "Ustaw kod PIN", "share": "Udostępnij", + "share_action_prompt": "Udostępniono {count} zasobÃŗw", + "share_add_photos": "Dodaj zdjęcia", + "share_assets_selected": "Wybrano {count}", "share_dialog_preparing": "Przygotowywanieâ€Ļ", "share_link": "Udostępnij link", - "share_original": "UÅŧyj orginału (duÅŧy)", - "share_preview": "UÅŧyj miniatury (mała)", - "share_quality_body": "Naciśnij i przytrzymaj przycisk udostępniania, aby wybrać jakość obrazu przed udostępnieniem.", - "share_quality_title": "Wybierz jakość udostępnianych zasobÃŗw", "shared": "Udostępnione", "shared_album_activities_input_disable": "Komentarz jest wyłączony", "shared_album_activity_remove_content": "Czy chcesz usunąć tę aktywność?", "shared_album_activity_remove_title": "Usuń aktywność", "shared_album_section_people_action_error": "Błąd podczas opuszczania/usuwania z albumu", + "shared_album_section_people_action_leave": "Usuń uÅŧytkownika z albumu", + "shared_album_section_people_action_remove_user": "Usuń uÅŧytkownika z albumu", "shared_album_section_people_title": "LUDZIE", "shared_by": "Udostępnione przez", "shared_by_user": "Udostępnione przez {user}", "shared_by_you": "Udostępnione przez ciebie", "shared_from_partner": "Zdjęcia od {partner}", "shared_intent_upload_button_progress_text": "{current} / {total} Przesłano", - "shared_link_app_bar_title": "Udostępnione linki", + "shared_link_app_bar_title": "Udostępnione", "shared_link_clipboard_copied_massage": "Skopiowane do schowka", + "shared_link_clipboard_text": "Link: {link}\nHasło: {password}", "shared_link_create_error": "Błąd podczas tworzenia linka do udostępnienia", "shared_link_custom_url_description": "OtwÃŗrz udostępniony link z niestandardowym adresem URL", - "shared_link_custom_url_title": "Niestandardowy adres URL", - "shared_link_custom_url_warning": "OstrzeÅŧenie: niestandardowy adres URL z prawym ukośnikiem moÅŧe nie działać zgodnie z oczekiwaniami.", "shared_link_edit_description_hint": "WprowadÅē opis udostępnienia", "shared_link_edit_expire_after_option_day": "1 dniu", "shared_link_edit_expire_after_option_days": "{count} dniach", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Dzielisz się z {partner}", "sharing": "Udostępnianie", "sharing_enter_password": "WprowadÅē hasło, aby wyświetlić tę stronę.", + "sharing_page_album": "Udostępnione albumy", + "sharing_page_description": "TwÃŗrz wspÃŗÅ‚dzielone albumy, aby udostępniać zdjęcia i filmy osobom w twojej sieci.", + "sharing_page_empty_list": "PUSTA LISTA", + "sharing_sidebar_description": "Wyświetl link do udostępniania na pasku bocznym", + "sharing_silver_appbar_create_shared_album": "UtwÃŗrz wspÃŗÅ‚dzielony album", + "sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi", "shift_to_permanent_delete": "naciśnij ⇧, aby trwale usunąć zasÃŗb", "show_album_options": "PokaÅŧ opcje albumu", "show_albums": "PokaÅŧ albumy", @@ -1995,9 +2140,7 @@ "show_in_timeline": "PokaÅŧ na osi czasu", "show_in_timeline_setting_description": "PokaÅŧ zdjęcia i filmy tego uÅŧytkownika na swojej osi czasu", "show_keyboard_shortcuts": "PokaÅŧ skrÃŗty klawiaturowe", - "show_less": "PokaÅŧ mniej", "show_metadata": "PokaÅŧ metadane", - "show_more_fields": "{count, plural, one {PokaÅŧ # kolejne pole} few {PokaÅŧ # kolejne pola} other {PokaÅŧ # kolejnych pÃŗl}}", "show_or_hide_info": "PokaÅŧ lub ukryj informacje", "show_password": "PokaÅŧ hasło", "show_person_options": "PokaÅŧ opcje osoby", @@ -2005,7 +2148,6 @@ "show_schema": "PokaÅŧ schemat", "show_search_options": "Wyświetl opcje wyszukiwania", "show_shared_links": "PokaÅŧ udostępniane linki", - "show_slideshow_metadata_overlay": "PokaÅŧ nakładkę z informacjami o zdjęciu", "show_slideshow_transition": "PokaÅŧ przejście pokazu slajdÃŗw", "show_supporter_badge": "Odznaka wspierającego", "show_supporter_badge_description": "PokaÅŧ odznakę wspierającego", @@ -2017,25 +2159,18 @@ "sign_out": "Wyloguj się", "sign_up": "Zarejestruj się", "size": "Rozmiar", - "skip": "Pomiń", "skip_to_content": "PrzejdÅē do treści", "skip_to_folders": "PrzejdÅē do folderÃŗw", "skip_to_tags": "PrzejdÅē do tagÃŗw", "slideshow": "Pokaz slajdÃŗw", - "slideshow_body": "UsiądÅē wygodnie i obejrzyj swoje zdjęcia w formie pokazu slajdÃŗw na pełnym ekranie.", - "slideshow_metadata_overlay_mode": "Treść nakładki", - "slideshow_metadata_overlay_mode_description_only": "Tylko opis", - "slideshow_metadata_overlay_mode_full": "Pełna", "slideshow_repeat": "PowtÃŗrz pokaz slajdÃŗw", "slideshow_repeat_description": "Zapętl pokaz slajdÃŗw", "slideshow_settings": "Ustawienia pokazu slajdÃŗw", - "slideshow_title": "Pokaz slajdÃŗw", - "smart_album": "Album inteligentny", - "some_assets_already_have_a_location_warning": "NiektÃŗre z wybranych zasobÃŗw mają juÅŧ lokalizację", "sort_albums_by": "Sortuj albumy według...", "sort_created": "Data utworzenia", "sort_items": "Liczba rzeczy", "sort_modified": "Data modyfikacji", + "sort_newest": "Najnowsze zdjęcie", "sort_oldest": "Najstarsze zdjęcie", "sort_people_by_similarity": "Sortuj twarze według cech podobnych", "sort_recent": "Najnowsze zdjęcie", @@ -2044,6 +2179,7 @@ "stack": "Stos", "stack_action_prompt": "{count} zgrupowano", "stack_duplicates": "Stos duplikatÃŗw", + "stack_select_one_photo": "Wybierz jedno gÅ‚Ãŗwne zdjęcie dla stosu", "stack_selected_photos": "UtwÃŗrz stos z wybranych zdjęć", "stacked_assets_count": "Utworzono stos z {count, plural, one {# zasobu} other {# zasobÃŗw}}", "stacktrace": "Ślad stosu", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Data początkowa musi być wcześniejsza niÅŧ data końcowa", "state": "WojewÃŗdztwo", "status": "Status", - "step_delete": "Usuń krok", - "step_delete_confirm": "Czy na pewno chcesz usunąć ten krok?", - "step_details": "SzczegÃŗÅ‚y kroku", - "steps": "Kroki", "stop_casting": "Zatrzymaj strumieniowanie", "stop_motion_photo": "Zatrzymaj zdjęcie w ruchu", "stop_photo_sharing": "Przestać udostępniać swoje zdjęcia?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Zmień kierunek złączenia", "sync": "Synchronizuj", "sync_albums": "Synchronizuj albumy", + "sync_albums_manual_subtitle": "Zsynchronizuj wszystkie przesłane filmy i zdjęcia z wybranymi albumami z włączoną kopią zapasową", "sync_local": "Synchronizacja lokalna", "sync_remote": "Synchronizacja zdalna", "sync_status": "Stan synchronizacji", "sync_status_subtitle": "Wyświetl i zarządzaj systemem synchronizacji", "sync_upload_album_setting_subtitle": "TwÃŗrz i przesyłaj swoje zdjęcia i filmy do wybranych albumÃŗw w Immich", - "system_theme": "Motyw systemu", - "system_theme_command_description": "UÅŧyj motywu systemowego ({value})", "tag": "Etykieta", "tag_assets": "Ustaw etykiety zasobÃŗw", "tag_created": "Stworzono etykietę: {tag}", "tag_face": "Oznacz twarz", "tag_feature_description": "Przeglądanie zdjęć i filmÃŗw pogrupowanych według logicznych etykiet wskazujących temat", + "tag_not_found_question": "Nie moÅŧesz znaleÅēć etykiety? UtwÃŗrz ją tutaj", "tag_people": "Dodaj etykiety osÃŗb", "tag_updated": "Uaktualniono etykietę: {tag}", "tagged_assets": "Przypisano etykietę {count, plural, one {# zasobowi} other {# zasobom}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Liczba zasobÃŗw w wierszu ({count})", "theme_setting_colorful_interface_subtitle": "Zastosuj kolor podstawowy do powierzchni tła.", "theme_setting_colorful_interface_title": "Kolorowy interfejs", + "theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegÃŗÅ‚owości", + "theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazÃŗw", "theme_setting_primary_color_subtitle": "Wybierz kolor dla gÅ‚Ãŗwnych działań i akcentÃŗw.", "theme_setting_primary_color_title": "Kolor podstawowy", "theme_setting_system_primary_color_title": "UÅŧyj koloru systemowego", "theme_setting_system_theme_switch": "Automatyczny (Postępuj zgodnie z ustawieniami systemu)", + "theme_setting_theme_subtitle": "Wybierz ustawienia motywu aplikacji", + "theme_setting_three_stage_loading_subtitle": "TrÃŗjstopniowe ładowanie moÅŧe zwiększyć wydajność ładowania, ale powoduje znacznie większe obciąÅŧenie sieci", + "theme_setting_three_stage_loading_title": "Włączenie trÃŗjstopniowego ładowania", "then": "Wtedy", "they_will_be_merged_together": "Zostaną one ze sobą połączone", "third_party_resources": "Zasoby stron trzecich", @@ -2131,17 +2268,23 @@ "trash_all": "Usuń wszystkie", "trash_count": "Usuń {count, number}", "trash_delete_asset": "Kosz/Usuń zasÃŗb", + "trash_emptied": "OprÃŗÅŧnione śmieci", "trash_no_results_message": "Tu znajdziesz wyrzucone zdjęcia i filmy.", "trash_page_delete_all": "Usuń wszystko", + "trash_page_empty_trash_dialog_content": "Czy chcesz oprÃŗÅŧnić swoje usunięte zasoby? Przedmioty te zostaną trwale usunięte z Immich", "trash_page_info": "Elementy przeniesione do kosza zostaną trwale usunięte po {days} dniach", + "trash_page_no_assets": "Brak usuniętych zasobÃŗw", + "trash_page_restore_all": "PrzywrÃŗcić wszystkie", + "trash_page_select_assets_btn": "Wybierz zasoby", + "trash_page_title": "Kosz ({count})", "trashed_items_will_be_permanently_deleted_after": "Wyrzucone zasoby zostaną trwale usunięte po {days, plural, one {jednym dniu} other {# dniach}}.", "trigger": "Wyzwalacz", - "trigger_asset_metadata_extraction": "Ekstrakcja metadanych zasobÃŗw", - "trigger_asset_metadata_extraction_description": "Wyzwalane po wyodrębnieniu metadanych EXIF z zasobu", - "trigger_asset_uploaded": "Przesyłanie zasobu", + "trigger_asset_uploaded": "Przesłano zasÃŗb", "trigger_asset_uploaded_description": "Wyzwalane gdy przesłany zostanie nowy zasÃŗb", + "trigger_description": "Wydarzenie, ktÃŗre uruchamia przepływ pracy", "trigger_person_recognized": "Osoba rozpoznana", - "trigger_person_recognized_description": "Wyzwalane gdy osoba zostanie rozpoznana", + "trigger_person_recognized_description": "Wyzwalane gdy zostanie wykryta osoba", + "trigger_type": "Rodzaj wyzwalacza", "troubleshoot": "RozwiąÅŧ problemy", "type": "Typ", "unable_to_change_pin_code": "Nie moÅŧna zmienić kodu PIN", @@ -2157,7 +2300,6 @@ "unknown": "Nieznany", "unknown_country": "Nieznane państwo", "unknown_date": "Nieznana data", - "unknown_schema": "Nieznany schemat", "unknown_year": "Rok nieznany", "unlimited": "Nieograniczony", "unlink_motion_video": "Rozłącz ruchome wideo", @@ -2170,20 +2312,23 @@ "unsaved_change": "Niezapisana zmiana", "unselect_all": "Odznacz wszystko", "unselect_all_duplicates": "Odznacz wszystkie duplikaty", + "unselect_all_in": "Odznacz wszystkie w {group}", "unstack": "Rozdziel stos", "unstack_action_prompt": "{count} rozdzielono", "unstacked_assets_count": "Rozdzielono {count, plural, one {# zasÃŗb} few {# zasoby} other {# zasobÃŗw}}", "unsupported_field_type": "Nieobsługiwany typ pola", "unsupported_file_type": "Nie moÅŧna przesłać pliku {file}, poniewaÅŧ jego typ {type} nie jest obsługiwany.", "untagged": "Nieoznaczone", + "untitled_workflow": "Przepływ pracy bez tytułu", "up_next": "Do następnego", "update_location_action_prompt": "Zaktualizuj lokalizację {count} wybranych zasobÃŗw na:", "updated_at": "Zaktualizowany", "updated_password": "Pomyślnie zaktualizowano hasło", "upload": "Prześlij", "upload_concurrency": "WspÃŗÅ‚bieÅŧność wysyłania", - "upload_day_count": "{date}: {count, plural, one {# przesłany materiał} few {# przesłane materiały} other {# przesłanych materiaÅ‚Ãŗw}}", "upload_details": "SzczegÃŗÅ‚y przesyłania", + "upload_dialog_info": "Czy chcesz wykonać kopię zapasową wybranych zasobÃŗw na serwerze?", + "upload_dialog_title": "Prześlij ZasÃŗb", "upload_error_with_count": "Błąd przesyłania dla {count, plural, one {# zasobu} other {# zasobÃŗw}}", "upload_errors": "Przesyłanie zakończone z {count, plural, one {# błędem} other {# błędami}}. OdświeÅŧ stronę, aby zobaczyć nowo przesłane zasoby.", "upload_finished": "Przesyłanie zakończone", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Błędy", "upload_status_uploaded": "Przesłano", "upload_success": "Przesyłanie powiodło się, odświeÅŧ stronę, aby zobaczyć nowo przesłane zasoby.", - "upload_to_album_body": "UÅŧytkownicy, ktÃŗrzy nie korzystają z funkcji ręcznego przesyłania, mogą teraz dodawać lokalne zdjęcia bezpośrednio do albumu podczas przesyłania. Nie ma juÅŧ potrzeby przesyłania i pÃŗÅēniejszego dodawania zdjęć do albumu.", - "upload_to_album_title": "Prześlij bezpośrednio do albumu", "upload_to_immich": "Prześlij do Immich ({count})", "uploading": "Przesyłanie", "uploading_media": "Przesyłanie multimediÃŗw", - "uploads": "Przesłane materiały", - "uploads_count": "{count, plural, one {# przesłany materiał} few {# przesłane materiały} other {# przesłanych materiaÅ‚Ãŗw}}", "url": "URL", "usage": "UÅŧycie", "use_biometric": "UÅŧyj biometrii", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Formatuj daty, godziny i liczby zgodnie z ustawieniami regionalnymi przeglądarki", "use_current_connection": "UÅŧyj bieÅŧącego połączenia", "use_custom_date_range": "Zamiast tego uÅŧyj niestandardowego zakresu dat", - "use_template": "UÅŧyj szablonu", "user": "UÅŧytkownik", "user_has_been_deleted": "Ten uÅŧytkownik został usunięty.", "user_id": "ID uÅŧytkownika", @@ -2217,6 +2357,7 @@ "user_privacy": "Ochrona prywatności uÅŧytkownika", "user_purchase_settings": "Zakup", "user_purchase_settings_description": "Zarządzaj swoim zakupem", + "user_role_set": "Ustaw {user} jako {role}", "user_usage_detail": "SzczegÃŗÅ‚y uÅŧywania przez uÅŧytkownika", "user_usage_stats": "Statystyki uÅŧytkowania konta", "user_usage_stats_description": "Wyświetl statystyki uÅŧytkowania konta", @@ -2226,6 +2367,7 @@ "utilities": "Narzędzia", "validate": "Walidacja", "validate_endpoint_error": "Proszę wprowadzić prawidłowy adres URL", + "validation_error": "Błąd walidacji", "variables": "Zmienne", "version": "Wersja", "version_announcement_closing": "TwÃŗj przyjaciel Aleks", @@ -2235,9 +2377,9 @@ "video": "Wideo", "video_hover_setting": "OdtwÃŗrz miniaturę wideo po najechaniu kursorem", "video_hover_setting_description": "OdtwÃŗrz miniaturę wideo po najechaniu myszką na element. Nawet jeśli jest wyłączone, odtwarzanie moÅŧna rozpocząć, najeÅŧdÅŧając kursorem na ikonę odtwarzania.", - "video_quality": "Jakość video", "videos": "Filmy", "videos_count": "{count, plural, one {# Film} few {# Filmy} other {# FilmÃŗw}}", + "videos_only": "Tylko filmy", "view": "Widok", "view_album": "Wyświetl Album", "view_all": "PokaÅŧ wszystkie", @@ -2246,29 +2388,29 @@ "view_details": "Zobacz szczegÃŗÅ‚y", "view_in_timeline": "PokaÅŧ na osi czasu", "view_link": "Zobacz link", + "view_links": "PokaÅŧ łącza", "view_name": "Widok", "view_next_asset": "Wyświetl następny zasÃŗb", "view_previous_asset": "Wyświetl poprzedni zasÃŗb", "view_qr_code": "PokaÅŧ kod QR", "view_similar_photos": "Zobacz podobne zdjęcia", "view_stack": "Zobacz stos", + "view_user": "Wyświetl uÅŧytkownika", "viewer_remove_from_stack": "Usuń ze stosu", + "viewer_stack_use_as_main_asset": "UÅŧyj jako gÅ‚Ãŗwnego zasobu", + "viewer_unstack": "Rozdziel stos", "visibility": "Widoczność", "visibility_changed": "Zmieniono widoczność dla {count, plural, one {# osoby} other {# osÃŗb}}", "visual": "Wizualny", + "visual_builder": "Edytor wizualny", "waiting": "Oczekujące", "waiting_count": "W oczekiwaniu: {count}", "warning": "OstrzeÅŧenie", "week": "Tydzień", "welcome": "Witaj", "welcome_to_immich": "Witamy w immich", - "whats_new": "Co nowego", - "whats_new_settings_subtitle": "Zobacz, co nowego w Immich", - "whats_new_version": "Wersja {version}", - "when": "Kiedy", "width": "Szerokość", "wifi_name": "Nazwa Wi-Fi", - "workflow": "Przepływ pracy", "workflow_delete_prompt": "Czy jesteś pewien, Åŧe chcesz usunąć ten przepływ pracy?", "workflow_deleted": "Przepływ pracy usunięty", "workflow_description": "Opis przepływu pracy", @@ -2278,17 +2420,17 @@ "workflow_name": "Nazwa przepływu pracy", "workflow_navigation_prompt": "Czy na pewno chcesz wyjść bez zapisania zmian?", "workflow_summary": "Podsumowanie przepływu pracy", - "workflow_templates": "Szablony przepływu pracy", "workflow_update_success": "Przepływ pracy zaktualizowany pomyślnie", + "workflow_updated": "Zaktualizowano przepływ pracy", "workflows": "Przepływy pracy", "workflows_help_text": "Przepływy pracy automatyzują działania na twoich zasobach w oparciu o wyzwalacze i filtry", "wrong_pin_code": "Nieprawidłowy kod PIN", - "x_of_total": "{x} z {total}", "year": "Rok", "years_ago": "{years, plural, one {# rok} few {# lata} other {# lat}} temu", "yes": "Tak", "you_dont_have_any_shared_links": "Nie masz Åŧadnych udostępnionych linkÃŗw", "your_wifi_name": "Twoja nazwa Wi-Fi", "zero_to_clear_rating": "naciśnij 0, aby wyczyścić ocenę zasobu", - "zoom_image": "Powiększ obraz" + "zoom_image": "Powiększ obraz", + "zoom_to_bounds": "Powiększ do krawędzi" } diff --git a/i18n/pt.json b/i18n/pt.json index 43981a6fd2..7511ed58a5 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -10,31 +10,39 @@ "active": "Em execuÃ§ÃŖo", "active_count": "Ativas: {count}", "activity": "Atividade", + "activity_changed": "A atividade estÃĄ {enabled, select, true {ativada} other {desativada}}", "add": "Adicionar", - "add_a_description": "Adicionar descriÃ§ÃŖo", + "add_a_description": "Adicionar uma descriÃ§ÃŖo", "add_a_location": "Adicionar localizaÃ§ÃŖo", "add_a_name": "Adicionar um nome", "add_a_title": "Adicionar um título", "add_action": "Adicionar aÃ§ÃŖo", + "add_action_description": "Faça clique para adicionar uma aÃ§ÃŖo a executar", "add_assets": "Adicionar ficheiros", "add_birthday": "Definir aniversÃĄrio", "add_endpoint": "Adicionar URL", "add_exclusion_pattern": "Adicionar um padrÃŖo de exclusÃŖo", + "add_filter": "Adicionar filtro", + "add_filter_description": "Faça clique para adicionar uma condiÃ§ÃŖo para o filtro", "add_location": "Adicionar localizaÃ§ÃŖo", + "add_more_users": "Adicionar mais utilizadores", "add_partner": "Adicionar parceiro", + "add_path": "Adicionar caminho", "add_photos": "Adicionar fotos", - "add_step": "Adicionar passo", "add_tag": "Adicionar Marcador", "add_to": "Adicionar aâ€Ļ", "add_to_album": "Adicionar ao ÃĄlbum", "add_to_album_bottom_sheet_added": "Adicionado a {album}", "add_to_album_bottom_sheet_already_exists": "JÃĄ existe em {album}", "add_to_album_bottom_sheet_some_local_assets": "Alguns conteÃēdos locais nÃŖo puderam ser adicionados no ÃĄlbum", + "add_to_album_toggle": "Alternar seleÃ§ÃŖo para {album}", "add_to_albums": "Adicionar aos ÃĄlbuns", "add_to_albums_count": "Adicionar aos ÃĄlbuns ({count})", "add_to_bottom_bar": "Adicionar a", + "add_to_shared_album": "Adicionar ao ÃĄlbum partilhado", "add_upload_to_stack": "Adicionar carregamento à fila", "add_url": "Adicionar URL", + "add_workflow_step": "Adicionar passo de fluxo de trabalho", "added_to_archive": "Adicionado ao arquivo", "added_to_favorites": "Adicionado aos favoritos", "added_to_favorites_count": "{count, plural, one {{count, number} adicionado aos favoritos} other {{count, number} adicionados aos favoritos}}", @@ -50,7 +58,7 @@ "backup_database": "Criar CÃŗpia da Base de Dados", "backup_database_enable_description": "Ativar cÃŗpias da base de dados", "backup_keep_last_amount": "Quantidade de cÃŗpias anteriores a manter", - "backup_onboarding_1_description": "Uma cÃŗpia remota na cloud ou noutra localizaÃ§ÃŖo física.", + "backup_onboarding_1_description": "Uma cÃŗpia remota na cloud ou outra localizaÃ§ÃŖo física.", "backup_onboarding_2_description": "CÃŗpias locais em dispositivos diferentes, incluindo os ficheiros principais e uma cÃŗpia de segurança local dos mesmos.", "backup_onboarding_3_description": "CÃŗpias completas dos seus dados, incluindo os ficheiros originais. Inclui uma cÃŗpia remota e duas cÃŗpias locais.", "backup_onboarding_description": "É recomendada a estratÊgia de cÃŗpia de segurança 3-2-1 para proteger os seus dados. Para uma soluÃ§ÃŖo de cÃŗpia de segurança completa, deve manter cÃŗpias das suas fotos e vídeos tal como da base de dados do Immich.", @@ -62,9 +70,9 @@ "cleared_jobs": "Eliminadas as tarefas de: {job}", "config_set_by_file": "A configuraÃ§ÃŖo estÃĄ atualmente definida por um ficheiro de configuraÃ§ÃŖo", "confirm_delete_library": "Tem a certeza de que deseja eliminar a biblioteca {library} ?", - "confirm_delete_library_assets": "Tem a certeza de que deseja eliminar esta biblioteca? IrÃĄ eliminar {count, plural, one {# ficheiro incluído} other {todos os # ficheiros incluídos}} nesta do Immich e esta aÃ§ÃŖo nÃŖo pode ser anulada. Os ficheiros permanecerÃŖo no disco.", + "confirm_delete_library_assets": "Tem a certeza de que deseja eliminar esta biblioteca? Isto eliminarÃĄ {count, plural, one {# ficheiro incluído} other {todos os # ficheiros incluídos}} do Immich e esta aÃ§ÃŖo nÃŖo pode ser anulada. Os ficheiros permanecerÃŖo no disco.", "confirm_email_below": "Para confirmar, escreva \"{email}\" abaixo", - "confirm_reprocess_all_faces": "Tem a certeza de que deseja reprocessar todos os rostos? TambÊm irÃĄ limpar os nomes das pessoas.", + "confirm_reprocess_all_faces": "Tem a certeza de que deseja reprocessar todos os rostos? Isto tambÊm limparÃĄ os nomes das pessoas.", "confirm_user_password_reset": "Tem a certeza de que deseja redefinir a palavra-passe de {user}?", "confirm_user_pin_code_reset": "Tem a certeza de que quer repor o cÃŗdigo PIN de {user}?", "copy_config_to_clipboard_description": "Copiar a configuraÃ§ÃŖo atual do sistema como um objeto JSON para a ÃĄrea de transferÃĒncia", @@ -73,16 +81,15 @@ "cron_expression_description": "Definir o intervalo de anÃĄlise utilizando o formato Cron. Para mais informaçÃĩes, por favor consulte o Crontab Guru", "cron_expression_presets": "PredefiniçÃĩes das expressÃĩes Cron", "disable_login": "Desativar inicio de sessÃŖo", - "download_csv": "Descarregar CSV", - "duplicate_detection_job_description": "Executa a aprendizagem de mÃĄquina nos ficheiros para detetar imagens semelhantes. Depende da Pesquisa Inteligente", - "exclusion_pattern_description": "Os padrÃĩes de exclusÃŖo permitem ignorar ficheiros e pastas ao analisar a sua biblioteca. É Ãētil se tiver pastas que contenham ficheiros que nÃŖo deseja importar, como ficheiros RAW.", + "duplicate_detection_job_description": "Executa a aprendizagem de mÃĄquina em ficheiros para detetar imagens semelhantes. Depende da Pesquisa Inteligente", + "exclusion_pattern_description": "Os padrÃĩes de exclusÃŖo permitem ignorar ficheiros e pastas ao analisar a sua biblioteca. Isto Ê Ãētil se tiver pastas que contenham ficheiros que nÃŖo deseja importar, como ficheiros RAW.", "export_config_as_json_description": "Descarregar a configuraÃ§ÃŖo atual do sistema como um ficheiro JSON", "external_libraries_page_description": "PÃĄgina de administrador de bibliotecas externas", "face_detection": "DeteÃ§ÃŖo de Rostos", "face_detection_description": "Deteta rostos em ficheiros utilizando aprendizagem automÃĄtica. Para vídeos, apenas a miniatura Ê considerada. \"Atualizar\" (re)processa todos os ficheiros, enquanto \"Redefinir\" elimina todos os dados de rostos. \"Em falta\" coloca em fila ficheiros que ainda nÃŖo foram processados. Os rostos detetados serÃŖo colocados em fila para Reconhecimento Facial apÃŗs a conclusÃŖo da DeteÃ§ÃŖo de Rostos, agrupando-os em pessoas novas ou jÃĄ existentes.", "facial_recognition_job_description": "Agrupa rostos detetadas em pessoas. Esta etapa Ê executada apÃŗs a conclusÃŖo da DeteÃ§ÃŖo de Rostos. \"Redefinir\" (re)agrupa todos os rostos. \"Em falta\" coloca em fila rostos que ainda nÃŖo tÃĒm uma pessoa atribuída.", "failed_job_command": "Ocorreu um erro no comando {command} para a tarefa: {job}", - "force_delete_user_warning": "AVISO: Esta aÃ§ÃŖo irÃĄ remover imediatamente o utilizador e todos os ficheiros. Esta aÃ§ÃŖo nÃŖo pode ser anulada e os ficheiros nÃŖo poderÃŖo ser recuperados.", + "force_delete_user_warning": "AVISO: Isto removerÃĄ imediatamente o utilizador e todos os ficheiros. Esta aÃ§ÃŖo nÃŖo pode ser anulada e os ficheiros nÃŖo poderÃŖo ser recuperados.", "image_format": "Formato", "image_format_description": "WebP produz ficheiros mais pequenos do que JPEG, mas Ê mais lento para codificar.", "image_fullsize_description": "Imagem de tamanho inteiro sem meta dados, utilizada quando esta for ampliada", @@ -91,9 +98,9 @@ "image_fullsize_quality_description": "Qualidade da imagem de tamanho inteiro de 1 a 100. Valores mais altos sÃŖo melhores, mas produzem ficheiros maiores.", "image_fullsize_title": "DefiniçÃĩes de imagem de tamanho inteiro", "image_prefer_embedded_preview": "Preferir visualizaÃ§ÃŖo incorporada", - "image_prefer_embedded_preview_setting_description": "Utilizar visualizaçÃĩes incorporadas em fotos RAW como entrada para processamento de imagem e quando disponível. PoderÃĄ produzir cores mais precisas para algumas imagens, mas a qualidade da visualizaÃ§ÃŖo depende da cÃĸmara e a imagem pode ter mais artefactos de compressÃŖo.", + "image_prefer_embedded_preview_setting_description": "Utilizar visualizaçÃĩes incorporadas em fotos RAW como entrada para processamento de imagem e quando disponível. Isto pode produzir cores mais precisas para algumas imagens, mas a qualidade da visualizaÃ§ÃŖo depende da cÃĸmara e a imagem pode ter mais artefatos de compressÃŖo.", "image_prefer_wide_gamut": "Prefira ampla gama", - "image_prefer_wide_gamut_setting_description": "Utilizar Display P3 para miniaturas. IrÃĄ preservar melhor a vibrÃĸncia das imagens com espaços de cores amplos, mas as imagens podem aparecer de maneira diferente em dispositivos antigos com uma versÃŖo antiga do navegador. As imagens sRGB sÃŖo mantidas como sRGB para evitar mudanças de cores.", + "image_prefer_wide_gamut_setting_description": "Utilizar Display P3 para miniaturas. Isto preserva melhor a vibrÃĸncia das imagens com espaços de cores amplos, mas as imagens podem aparecer de maneira diferente em dispositivos antigos com uma versÃŖo antiga do navegador. As imagens sRGB sÃŖo mantidas como sRGB para evitar mudanças de cores.", "image_preview_description": "Imagem de tamanho mÊdio sem metadados, utilizada ao visualizar um Ãēnico ficheiro e pela aprendizagem de mÃĄquina", "image_preview_quality_description": "Qualidade de prÊ-visualizaÃ§ÃŖo de 1 a 100. Maior Ê melhor, mas produz ficheiros maiores e pode reduzir a capacidade de resposta da aplicaÃ§ÃŖo. Definir um valor demasiado baixo pode afetar a qualidade da aprendizagem de mÃĄquina.", "image_preview_title": "DefiniçÃĩes de PrÊ-visualizaÃ§ÃŖo", @@ -108,22 +115,7 @@ "image_thumbnail_quality_description": "Qualidade das miniaturas de 1 a 100. Maior Ê melhor, mas produz tamanhos de ficheiro maiores e podem reduzir a capacidade de resposta da aplicaÃ§ÃŖo.", "image_thumbnail_title": "DefiniçÃĩes de Miniaturas", "import_config_from_json_description": "Importar configuraÃ§ÃŖo do sistema ao carregar um ficheiro JSON", - "integrity_checks_checksum_files": "Checksum de ficheiros", - "integrity_checks_checksum_files_description": "Configurar a verificaÃ§ÃŖo de checksum", - "integrity_checks_checksum_files_enable_description": "Ativar a verificaÃ§ÃŖo de checksum", - "integrity_checks_checksum_files_percentage_limit": "Limite percentual", - "integrity_checks_checksum_files_percentage_limit_description": "Configurar a percentagem mÃĄxima entre 0.01 e 1 para o quanto da verificaÃ§ÃŖo checksum deve ser executada a cada intervalo.", - "integrity_checks_checksum_files_time_limit": "Tempo limite", - "integrity_checks_checksum_files_time_limit_description": "Configurar a duraÃ§ÃŖo mÃĄxima que cada verificaÃ§ÃŖo de checksum deve executar para cada intervalo. (ms)", - "integrity_checks_missing_files": "Ficheiros em falta", - "integrity_checks_missing_files_description": "Configurar a frequÃĒncia e ativar ou desativar a verificaÃ§ÃŖo de ficheiros em falta", - "integrity_checks_missing_files_enable_description": "Ativar a verificaÃ§ÃŖo de ficheiros em falta", - "integrity_checks_settings": "VerificaçÃĩes de integridade", - "integrity_checks_settings_description": "Gerir os intervalos das verificaçÃĩes de integridade", - "integrity_checks_untracked_files": "Ficheiros nÃŖo monitorizados", - "integrity_checks_untracked_files_description": "Configurar a frequÃĒncia e ativar ou desativar a verificaÃ§ÃŖo de ficheiros nÃŖo monitorizados", - "integrity_checks_untracked_files_enable_description": "Ativar a verificaÃ§ÃŖo de ficheiros nÃŖo monitorizados", - "job_concurrency": "\"{job}\" em simultÃĸneo", + "job_concurrency": "{job} em simultÃĸneo", "job_created": "Tarefa criada", "job_not_concurrency_safe": "Esta tarefa nÃŖo pode ser executada em simultÃĸneo.", "job_settings": "DefiniçÃĩes de Tarefas", @@ -178,7 +170,7 @@ "machine_learning_min_detection_score": "PontuaÃ§ÃŖo mínima de deteÃ§ÃŖo", "machine_learning_min_detection_score_description": "PontuaÃ§ÃŖo mínima de confiança para um rosto ser detetado, de 0 a 1. Valores mais baixos detetam mais rostos, mas poderÃŖo resultar em falsos positivos.", "machine_learning_min_recognized_faces": "Mínimo de rostos reconhecidos", - "machine_learning_min_recognized_faces_description": "O nÃēmero mínimo de faces reconhecidas para uma pessoa ser criada na lista. AumentÃĄ-lo torna o Reconhecimento Facial mais preciso, no entanto aumenta a probabilidade de um rosto nÃŖo ser atribuído a uma pessoa.", + "machine_learning_min_recognized_faces_description": "O nÃēmero mínimo de faces reconhecidas para uma pessoa ser criada na lista. Aumentar isto torna o Reconhecimento Facial mais preciso, no entanto aumenta a probabilidade de um rosto nÃŖo ser atribuído a uma pessoa.", "machine_learning_ocr": "Reconhecimento Ótico de Caracteres (OCR)", "machine_learning_ocr_description": "Utilizar aprendizagem de mÃĄquina para reconhecer texto dentro de imagens", "machine_learning_ocr_enabled": "Ativar OCR", @@ -190,7 +182,7 @@ "machine_learning_ocr_min_recognition_score": "PontuaÃ§ÃŖo mínima de reconhecimento", "machine_learning_ocr_min_score_recognition_description": "PontuaÃ§ÃŖo mínima de confiança para o texto ser reconhecido de 0 a 1. Valores mais baixos vÃŖo reconhecer mais texto mas podem resultar em falsos positivos.", "machine_learning_ocr_model": "Modelo de OCR", - "machine_learning_ocr_model_description": "Os modelos de servidor tÃĒm mais precisÃŖo do que os modelos de dispositivos mÃŗveis, mas demoram mais tempo a processar e utilizam mais memÃŗria.", + "machine_learning_ocr_model_description": "Modelos do servidor tÃĒm mais precisÃŖo do que modelos mÃŗveis, mas demoram mais tempo a processar e utilizam mais memÃŗria.", "machine_learning_settings": "DefiniçÃĩes de aprendizagem de mÃĄquina (Machine Learning)", "machine_learning_settings_description": "Gerir funcionalidades e definiçÃĩes de aprendizagem de mÃĄquina", "machine_learning_smart_search": "Pesquisa Inteligente", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Ativar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens nÃŖo serÃŖo codificadas para Pesquisa Inteligente.", "machine_learning_url_description": "A URL do servidor de aprendizagem de mÃĄquina. Se for fornecido mais do que um URL, cada servidor serÃĄ testado, um a um, atÊ um deles responder com sucesso, por ordem do primeiro ao Ãēltimo. Servidores que nÃŖo responderem serÃŖo temporariamente ignorados atÊ voltarem a estar online.", - "maintenance_backup_management": "GestÃŖo de cÃŗpias de segurança", "maintenance_delete_backup": "Eliminar CÃŗpia de Segurança", "maintenance_delete_backup_description": "Este ficheiro irÃĄ ser apagado para sempre.", "maintenance_delete_error": "Ocorreu um erro ao eliminar a cÃŗpia de segurança.", - "maintenance_integrity_check": "Verificar", - "maintenance_integrity_check_all": "Verificar tudo", - "maintenance_integrity_checksum_mismatch": "Checksum nÃŖo corresponde", - "maintenance_integrity_checksum_mismatch_description": "Ficheiros dos quais a verificaÃ§ÃŖo checksum no disco nÃŖo condiz com a que o Immich tem armazenado na base de dados.", - "maintenance_integrity_checksum_mismatch_job": "Verificar se existem erros de checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Atualizar relatÃŗrios de erros de checksum", - "maintenance_integrity_missing_file": "Ficheiros em falta", - "maintenance_integrity_missing_file_description": "Ficheiros dos quais o Immich estÃĄ a monitorizar na sua base de dados mas que nÃŖo existem no sistema de ficheiros.", - "maintenance_integrity_missing_file_job": "Verificar se existem ficheiros em falta", - "maintenance_integrity_missing_file_refresh_job": "Atualizar relatÃŗrios de ficheiros em falta", - "maintenance_integrity_report": "RelatÃŗrio de integridade", - "maintenance_integrity_untracked_file": "Ficheiros nÃŖo monitorizados", - "maintenance_integrity_untracked_file_description": "Ficheiros nos diretÃŗrios do Immich dos quais nÃŖo existe qualquer registo.", - "maintenance_integrity_untracked_file_job": "Verificar se existem ficheiros nÃŖo monitorizados", - "maintenance_integrity_untracked_file_refresh_job": "Atualizar relatÃŗrios de ficheiros nÃŖo monitorizados", "maintenance_restore_backup": "Restaurar CÃŗpia de Segurança", "maintenance_restore_backup_description": "O Immich irÃĄ ser apagado e de seguida restaurado a partir da cÃŗpia de segurança selecionada. IrÃĄ ser criada uma cÃŗpia de segurança antes de continuar.", "maintenance_restore_backup_different_version": "Esta cÃŗpia de segurança foi criada com uma versÃŖo diferente do Immich!", @@ -272,7 +248,7 @@ "no_paths_added": "Nenhum caminho adicionado", "no_pattern_added": "Nenhum padrÃŖo adicionado", "note_apply_storage_label_previous_assets": "ObservaÃ§ÃŖo: Para aplicar o RÃŗtulo de Armazenamento a ficheiros carregados anteriormente, execute a", - "note_cannot_be_changed_later": "NOTA: Isto nÃŖo poderÃĄ ser alterado posteriormente!", + "note_cannot_be_changed_later": "NOTA: Isto nÃŖo pode ser alterado posteriormente!", "notification_email_from_address": "A partir do endereço", "notification_email_from_address_description": "Endereço de e-mail do remetente, por exemplo: \"Servidor de Fotos Immich \". Certifique-se de que utiliza um endereço atravÊs do qual pode enviar e-mails.", "notification_email_host_description": "Host do servidor de e-mail (por exemplo, smtp.immich.app)", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Ativar notificaçÃĩes por e-mail", "notification_settings": "DefiniçÃĩes de notificaçÃĩes", "notification_settings_description": "Gerir definiçÃĩes de notificaçÃĩes, incluindo e-mail", - "oauth_allow_insecure_requests": "Permitir pedidos inseguros", - "oauth_allow_insecure_requests_description": "AVISO: IrÃĄ desativar a validaÃ§ÃŖo de certificados TLS para pedidos OAuth e poderÃĄ ficar exposto a ataques MITM (\"Man-in-the-middle\").", "oauth_auto_launch": "Arranque automÃĄtico", "oauth_auto_launch_description": "Iniciar o fluxo de sessÃŖo por OAuth automaticamente ao navegar atÊ a pÃĄgina de inicio de sessÃŖo", "oauth_auto_register": "Registo automÃĄtico", @@ -300,11 +274,9 @@ "oauth_button_text": "Texto do botÃŖo", "oauth_client_secret_description": "ObrigatÃŗrio para o cliente confidencial, ou se a PKCE (Proof Key for Code Exchange) nÃŖo for suportada para cliente pÃēblico.", "oauth_enable_description": "Iniciar sessÃŖo com o OAuth", - "oauth_end_session_url_description": "Redirecionar o utilizador para este URI quando estes terminam a sessÃŖo.", "oauth_mobile_redirect_uri": "URI de redirecionamento mÃŗvel", "oauth_mobile_redirect_uri_override": "SubstituiÃ§ÃŖo de URI de redirecionamento mÃŗvel", "oauth_mobile_redirect_uri_override_description": "Ative quando o provedor do OAuth nÃŖo permite um URI mÃŗvel, como ''{callback}''", - "oauth_prompt_description": "ParÃĸmetro de pedido (e.x. select_account, login, consent)", "oauth_role_claim": "ReivindicaÃ§ÃŖo de FunçÃĩes", "oauth_role_claim_description": "Automaticamente concede acesso de administrador, com base na presença desta reivindicaÃ§ÃŖo. A reivindicaÃ§ÃŖo tanto pode ter \"user\" como \"admin\".", "oauth_settings": "OAuth", @@ -318,7 +290,7 @@ "oauth_storage_quota_default_description": "Quota em GiB a ser usada quando nenhuma reivindicaÃ§ÃŖo for fornecida.", "oauth_timeout": "Tempo Limite de RequisiÃ§ÃŖo", "oauth_timeout_description": "Tempo limite para requisiçÃĩes, em milissegundos", - "ocr_job_description": "Utiliza a aprendizagem de mÃĄquina para reconhecer texto em imagens", + "ocr_job_description": "Utilizar aprendizagem de mÃĄquina para reconhecer texto em imagens", "password_enable_description": "Iniciar sessÃŖo com e-mail e palavra-passe", "password_settings": "Palavra-passe de acesso", "password_settings_description": "Gerir definiçÃĩes de inicio de sessÃŖo e palavra-passe", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "A atualizar todas as bibliotecas", "registration": "Registo de Administrador", "registration_description": "Como Ê o primeiro utilizador no sistema, serÃĄ marcado como administrador, e serÃĄ responsÃĄvel pelas tarefas administrativas, sendo que utilizadores adicionais serÃŖo criados por si.", - "release_channel_release_candidate": "Candidato de lançamento", - "release_channel_stable": "EstÃĄvel", "remove_failed_jobs": "Remover tarefas falhadas", "require_password_change_on_login": "Exigir que o utilizador altere a palavra-passe no primeiro início de sessÃŖo", "reset_settings_to_default": "Redefinir as definiçÃĩes para o padrÃŖo", @@ -351,9 +321,9 @@ "server_welcome_message_description": "Uma mensagem que Ê exibida na pÃĄgina de inicio de sessÃŖo.", "settings_page_description": "PÃĄgina de administrador das definiçÃĩes", "sidecar_job": "Metadados secundÃĄrios", - "sidecar_job_description": "Descobre ou sincroniza metadados secundÃĄrios a partir do sistema de ficheiros", + "sidecar_job_description": "Descobrir ou sincronizar metadados secundÃĄrios a partir do sistema de ficheiros", "slideshow_duration_description": "Tempo em segundos para exibir cada imagem", - "smart_search_job_description": "Executa a aprendizagem de mÃĄquina nos ficheiros para oferecer apoio à Pesquisa Inteligente", + "smart_search_job_description": "Execute a aprendizagem automÃĄtica em ficheiros para oferecer apoio à Pesquisa Inteligente", "storage_template_date_time_description": "O registo de data e hora de criaÃ§ÃŖo do ficheiro Ê utilizado para preencher estas informaçÃĩes", "storage_template_date_time_sample": "Exemplo de data/hora {date}", "storage_template_enable_description": "Ativar mecanismo de modelo de armazenamento", @@ -361,11 +331,11 @@ "storage_template_hash_verification_enabled_description": "Ativa a verificaÃ§ÃŖo de hash, nÃŖo desative esta opÃ§ÃŖo a menos que tenha a certeza das implicaçÃĩes", "storage_template_migration": "MigraÃ§ÃŖo de modelo de armazenamento", "storage_template_migration_description": "Aplica o {template} atual para ficheiros previamente carregados", - "storage_template_migration_info": "O modelo de armazenamento irÃĄ converter todas as extensÃĩes para letra minÃēscula. As mudanças do modelo apenas se aplicarÃŖo a novos ficheiros. Para aplicar o modelo retroativamente para todos os ficheiros carregados anteriormente, execute a {job}.", + "storage_template_migration_info": "O modelo de armazenamento irÃĄ converter todas as extensÃĩes para letra minÃēscula. As mudanças do modelo apenas se aplicarÃŖo a novos ficheiros. Para aplicar o modelo retroativamente para os ficheiros carregados anteriormente, execute o {job}.", "storage_template_migration_job": "Tarefa de MigraÃ§ÃŖo do Modelo de Armazenamento", "storage_template_more_details": "Para mais informaçÃĩes sobre esta funcionalidade, dirija-se a Modelo de Armazenamento e às suas implicaçÃĩes", "storage_template_onboarding_description_v2": "Quando ativada, estÃĄ funÃ§ÃŖo irÃĄ automaticamente organizar ficheiros com base num modelo definido pelo utilizador. Para mais informaçÃĩes, consulte a documentaÃ§ÃŖo.", - "storage_template_path_length": "Limite aproximado do tamanho do caminho: {length, number}/{limit, number}", + "storage_template_path_length": "Limite aproximado do tamanho do caminho: {length, number}{limit, number}", "storage_template_settings": "Modelo de Armazenamento", "storage_template_settings_description": "Gerir a estrutura de pastas e o nome do ficheiro carregado", "storage_template_user_label": "{label} Ê o RÃŗtulo do Armazenamento do utilizador", @@ -398,7 +368,7 @@ "transcoding_accepted_containers_description": "Selecione os formatos de contentores que nÃŖo precisam de ser remisturados para MP4. Usado apenas para algumas políticas de transcodificaÃ§ÃŖo.", "transcoding_accepted_video_codecs": "Codecs de vídeo aceitos", "transcoding_accepted_video_codecs_description": "Selecione quais os codecs de vídeo que nÃŖo precisam de ser transcodificados. Usado apenas para determinadas políticas de transcodificaÃ§ÃŖo.", - "transcoding_advanced_options_description": "OpçÃĩes que a maioria dos utilizadores nÃŖo deverÃŖo precisar de alterar", + "transcoding_advanced_options_description": "OpçÃĩes que a maioria dos utilizadores nÃŖo deverÃĄ precisar de alterar", "transcoding_audio_codec": "Codec de ÃĄudio", "transcoding_audio_codec_description": "Opus Ê a opÃ§ÃŖo de mais alta qualidade, mas tem menor compatibilidade com dispositivos ou software antigos.", "transcoding_bitrate_description": "Vídeos com taxa de bits superior à mÃĄxima ou que nÃŖo estÃŖo num formato aceite", @@ -426,15 +396,7 @@ "transcoding_preferred_hardware_device": "Dispositivo de hardware preferido", "transcoding_preferred_hardware_device_description": "Aplica-se apenas a VAAPI e QSV. Define o nÃŗ dri usado para transcodificaÃ§ÃŖo de hardware.", "transcoding_preset_preset": "PredefiniÃ§ÃŖo (-preset)", - "transcoding_preset_preset_description": "Velocidade de compressÃŖo. PredefiniçÃĩes mais lentas produzem ficheiros menores e aumentam a qualidade ao atingir uma determinada taxa de bits. VP9 ignora velocidades acima de \"faster\".", - "transcoding_realtime": "TranscodificaÃ§ÃŖo em Tempo Real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permite que a transcodificaÃ§ÃŖo seja efetuada em tempo real enquanto o vídeo a estÃĄ a ser transmitido. Permite a alteraÃ§ÃŖo de qualidade, mas pode causar um maior atraso na reproduÃ§ÃŖo e lentidÃŖo dependendo da capacidade do servidor.", - "transcoding_realtime_enabled": "Ativar transcodificaÃ§ÃŖo em tempo real", - "transcoding_realtime_enabled_description": "Se estiver desativado, o servidor irÃĄ recusar o início de novas sessÃĩes de transcodificaÃ§ÃŖo em tempo real.", - "transcoding_realtime_resolutions": "ResoluçÃĩes", - "transcoding_realtime_resolutions_description": "As resoluçÃĩes que sÃŖo disponibilizadas para transcodificaÃ§ÃŖo em tempo real. ResoluçÃĩes mais altas podem causar problemas de reproduÃ§ÃŖo se o servidor nÃŖo conseguir transcodificÃĄ-las com rapidez suficiente.", - "transcoding_realtime_video_codecs": "Codecs de vídeo", - "transcoding_realtime_video_codecs_description": "Os codecs de vídeo que sÃŖo disponibilizados para transcodificaÃ§ÃŖo em tempo real. Os clientes vÃŖo escolher a melhor opÃ§ÃŖo que suportam durante a reproduÃ§ÃŖo. AV1 Ê mais eficiente do que HEVC, que por sua vez Ê mais eficiente do que H.264. Ao utilizar aceleraÃ§ÃŖo por hardware, selecione apenas os codecs que o acelerador consegue codificar. Ao utilizar transcodificaÃ§ÃŖo por software, observe que H.264 Ê mais rÃĄpido do que AV1, que por sua vez Ê mais rÃĄpido do que HEVC.", + "transcoding_preset_preset_description": "Velocidade de compressÃŖo. PredefiniçÃĩes mais lentas produzem ficheiros menores e aumentam a qualidade ao atingir uma determinada taxa de bits. VP9 ignora velocidades acima de \"mais rÃĄpido\".", "transcoding_reference_frames": "Quadros de referÃĒncia", "transcoding_reference_frames_description": "O nÃēmero de quadros a serem referenciados ao comprimir um determinado quadro. Valores mais altos melhoram a eficiÃĒncia da compressÃŖo, mas tornam a codificaÃ§ÃŖo mais lenta. 0 define esse valor automaticamente.", "transcoding_required_description": "Apenas vídeos que nÃŖo estejam num formato aceite", @@ -461,7 +423,7 @@ "trash_settings_description": "Gerir definiçÃĩes da reciclagem", "unlink_all_oauth_accounts": "Desvincular todas as contas OAuth", "unlink_all_oauth_accounts_description": "Lembre-se de desvincular todas as contas OAuth antes de migrar para um novo provedor.", - "unlink_all_oauth_accounts_prompt": "Tem a certeza de que deseja desvincular todas as contas OAuth? IrÃĄ redefinir o ID OAuth de cada utilizador e nÃŖo poderÃĄ ser anulado.", + "unlink_all_oauth_accounts_prompt": "Tem a certeza de que deseja desvincular todas as contas OAuth? Isto irÃĄ redefinir o ID OAuth de cada utilizador e nÃŖo poderÃĄ ser anulado.", "user_cleanup_job": "Limpeza de utilizadores", "user_delete_delay": "A conta e os ficheiros de {user} serÃŖo agendados para eliminaÃ§ÃŖo permanente dentro de {delay, plural, one {# dia} other {# dias}}.", "user_delete_delay_settings": "Atraso de eliminaÃ§ÃŖo", @@ -478,8 +440,6 @@ "user_settings_description": "Gerir definiçÃĩes do utilizador", "user_successfully_removed": "O utilizador {email} foi removido com sucesso.", "users_page_description": "PÃĄgina de administador de utilizadores", - "version_check_channel": "Canal de lançamento", - "version_check_channel_description": "Escolha o canal de lançamento do qual quer ser notificado para novas versÃĩes", "version_check_enabled_description": "Ativa verificaÃ§ÃŖo de novas versÃĩes", "version_check_implications": "A funcionalidade de verificaÃ§ÃŖo da versÃŖo necessita de comunicaÃ§ÃŖo periÃŗdica com o {server}", "version_check_settings": "VerificaÃ§ÃŖo de versÃŖo", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Limpar a Cache de Imagens", "advanced_settings_clear_image_cache_error": "Ocorreu um erro ao limpar a cache de imagens", "advanced_settings_clear_image_cache_success": "Limpeza concluída com sucesso {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Utilize esta definiÃ§ÃŖo para filtrar ficheiros durante a sincronizaÃ§ÃŖo baseada em critÊrios alternativos. Utilize apenas se a aplicaÃ§ÃŖo estiver com problemas a detetar todos os ÃĄlbuns.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizar um filtro alternativo de sincronizaÃ§ÃŖo de ÃĄlbuns em dispositivos", "advanced_settings_log_level_title": "Nível de registo: {level}", "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos sÃŖo extremamente lentos a carregar miniaturas da memÃŗria interna. Ative esta opÃ§ÃŖo para preferir imagens do servidor.", "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Cabeçalhos do proxy personalizados [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Ativa o modo sÃŗ de leitura, onde as fotos apenas podem ser visualizadas. FunçÃĩes como selecionar vÃĄrias imagens, partilhar, transmitir e eliminar ficam deactivadas. Pode ativar ou desativar o modo sÃŗ de leitura atravÊs da imagem de perfil do utilizador na janela principal", "advanced_settings_readonly_mode_title": "Modo sÃŗ de leitura", + "advanced_settings_self_signed_ssl_subtitle": "NÃŖo validar o certificado SSL com o endereço do servidor. Isto Ê necessÃĄrio para certificados auto-assinados.", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL autoassinados", "advanced_settings_sync_remote_deletions_subtitle": "Automaticamente eliminar ou restaurar um ficheiro neste dispositivo quando essa mesma aÃ§ÃŖo for efetuada na web", "advanced_settings_sync_remote_deletions_title": "Sincronizar ficheiros eliminados remotamente [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "DefiniçÃĩes avançadas do utilizador", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Tem a certeza de que quer eliminar o ÃĄlbum {album}?", "album_delete_confirmation_description": "Se este ÃĄlbum for partilhado, os outros utilizadores deixam de o poder aceder.", "album_deleted": "Álbum eliminado", + "album_info_card_backup_album_excluded": "EXCLUÍDO", + "album_info_card_backup_album_included": "INCLUÍDO", "album_info_updated": "InformaçÃĩes do ÃĄlbum atualizadas", + "album_leave": "Sair do ÃĄlbum?", + "album_leave_confirmation": "Tem a certeza de que quer sair de {album}?", "album_name": "Nome do ÃĄlbum", "album_options": "OpçÃĩes de ÃĄlbum", "album_remove_user": "Remover utilizador?", "album_remove_user_confirmation": "Tem a certeza de que quer remover {user}?", "album_search_not_found": "Nenhum ÃĄlbum encontrado segundo a pesquisa", + "album_selected": "Álbum selecionado", "album_share_no_users": "Parece que tem este ÃĄlbum partilhado com todos os utilizadores ou que nÃŖo existem utilizadores com quem o partilhar.", "album_summary": "Resumo do ÃĄlbum", "album_updated": "Álbum atualizado", "album_updated_setting_description": "Receber uma notificaÃ§ÃŖo por e-mail quando um ÃĄlbum partilhado tiver novos ficheiros", "album_upload_assets": "Carregar ficheiros a partir do seu computador e adicionÃĄ-los ao ÃĄlbum", + "album_user_left": "Saíu do {album}", + "album_user_removed": "Utilizador {user} removido", + "album_viewer_appbar_delete_confirm": "Tem certeza que deseja excluir este ÃĄlbum da sua conta?", "album_viewer_appbar_share_err_delete": "Ocorreu um erro ao eliminar ÃĄlbum", + "album_viewer_appbar_share_err_leave": "Ocorreu um erro ao sair do ÃĄlbum", + "album_viewer_appbar_share_err_remove": "Ocorreu um erro ao remover ficheiros do ÃĄlbum", + "album_viewer_appbar_share_err_title": "Ocorreu um erro ao alterar o título do ÃĄlbum", + "album_viewer_appbar_share_leave": "Deixar ÃĄlbum", + "album_viewer_appbar_share_to": "Compartilhar com", "album_viewer_page_share_add_users": "Adicionar utilzadores", "album_with_link_access": "Permite o acesso a fotos e pessoas deste ÃĄlbum por qualquer pessoa com o link.", "albums": "Álbuns", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Ordem inicial dos ficheiros ao criar novos ÃĄlbuns.", "albums_feature_description": "ColeçÃĩes de ficheiros que podem ser partilhados com outros utilizadores.", "albums_on_device_count": "Álbums no dispositivo ({count})", + "albums_selected": "{count, plural, one {# ÃĄlbum selecionado} other {# ÃĄlbuns selecionados}}", "all": "Todos", "all_albums": "Todos os ÃĄlbuns", "all_people": "Todas as pessoas", "all_photos": "Todas as fotos", "all_videos": "Todos os vídeos", "allow_dark_mode": "Permitir modo escuro", + "allow_edits": "Permitir ediçÃĩes", "allow_public_user_to_download": "Permitir que utilizadores pÃēblicos façam transferÃĒncias", "allow_public_user_to_upload": "Permitir que utilizadores pÃēblicos façam carregamentos", "allowed": "Permitido", @@ -549,12 +528,14 @@ "always_keep": "Manter sempre", "always_keep_photos_hint": "Libertar Espaço irÃĄ manter todas as fotos neste dispositivo.", "always_keep_videos_hint": "Libertar Espaço irÃĄ manter todos os vídeos neste dispositivo.", + "anti_clockwise": "Sentido anti-horÃĄrio", "api_key": "Chave de API", "api_key_description": "Este valor serÃĄ apresentado apenas uma Ãēnica vez. Por favor, certifique-se que o copiou antes de fechar a janela.", - "api_key_empty": "O nome da chave de API nÃŖo pode estar vazio", + "api_key_empty": "O nome da chave a API nÃŖo pode estar vazio", "api_keys": "Chaves de API", "app_architecture_variant": "Variante (Arquitetura)", "app_bar_signout_dialog_content": "Tem certeza que deseja sair?", + "app_bar_signout_dialog_ok": "Sim", "app_bar_signout_dialog_title": "Sair", "app_download_links": "LigaçÃĩes de Descarga da App", "app_settings": "DefiniçÃĩes da AplicaÃ§ÃŖo", @@ -565,50 +546,67 @@ "archive": "Arquivo", "archive_action_prompt": "{count} adicionados ao Arquivo", "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", + "archive_page_no_archived_assets": "Nenhum ficheiro arquivado encontrado", + "archive_page_title": "Arquivo ({count})", "archive_size": "Tamanho do arquivo", "archive_size_description": "Configure o tamanho do arquivo para transferÃĒncias (em GiB)", "archived": "Arquivado", "archived_count": "{count, plural, one {#Arquivado # item} other {Arquivados # itens}}", "are_these_the_same_person": "Estas pessoas sÃŖo a mesma pessoa?", "are_you_sure_to_do_this": "Tem a certeza de que quer fazer isto?", + "array_field_not_fully_supported": "Campos de listas necessitam de ediÃ§ÃŖo manual JSON", + "asset_action_delete_err_read_only": "NÃŖo Ê possível eliminar ficheiro sÃŗ de leitura, a ignorar", + "asset_action_share_err_offline": "NÃŖo foi possível obter os ficheiros offline, a ignorar", "asset_added_to_album": "Adicionado ao ÃĄlbum", "asset_adding_to_album": "A adicionar ao ÃĄlbumâ€Ļ", "asset_created": "Ficheiro criado", - "asset_day_count": "{date}: {count, plural, one {# ficheiro} other {# ficheiros}}", "asset_description_updated": "A descriÃ§ÃŖo do ficheiro foi atualizada", + "asset_filename_is_offline": "O ficheiro {filename} nÃŖo estÃĄ disponível", + "asset_has_unassigned_faces": "O ficheiro tem rostos nÃŖo atribuídas", "asset_hashing": "A criar hashâ€Ļ", "asset_list_group_by_sub_title": "Agrupar por", + "asset_list_layout_settings_dynamic_layout_title": "Layout dinÃĸmico", + "asset_list_layout_settings_group_automatically": "AutomÃĄtico", "asset_list_layout_settings_group_by": "Agrupar ficheiros por", "asset_list_layout_settings_group_by_month_day": "MÃĒs + dia", "asset_list_layout_sub_title": "DisposiÃ§ÃŖo", - "asset_list_settings_subtitle": "DefiniçÃĩes de disposiÃ§ÃŖo da grade de fotos", + "asset_list_settings_subtitle": "ConfiguraçÃĩes de disposiÃ§ÃŖo da grade de fotos", "asset_list_settings_title": "Grade de fotos", "asset_not_found_on_device_android": "Ficheiro nÃŖo encontrado no dispositivo", "asset_not_found_on_device_ios": "Ficheiro nÃŖo encontrado no dispositivo. Se estiver a utilizar o iCloud, o ficheiro pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud", "asset_not_found_on_icloud": "Ficheiro nÃŖo encontrado no iCloud. Este pode estar inacessível devido a um ficheiro corrompido armazenado no iCloud", "asset_offline": "Ficheiro Indisponível", "asset_offline_description": "Este ficheiro externo deixou de estar disponível no disco. Contacte o seu administrador do Immich para obter ajuda.", + "asset_restored_successfully": "FIcheiro restaurado com sucesso", "asset_skipped": "Ignorado", "asset_skipped_in_trash": "Na reciclagem", + "asset_trashed": "Ficheiro apagado", "asset_troubleshoot": "ResoluÃ§ÃŖo de problemas com conteÃēdos", "asset_uploaded": "Enviado", "asset_uploading": "A enviarâ€Ļ", - "asset_viewer_settings_subtitle": "Gerir as definiçÃĩes do visualizador da galeria", + "asset_viewer_settings_subtitle": "Gerenciar as configuraçÃĩes do visualizador da galeria", "asset_viewer_settings_title": "Visualizador", "assets": "Ficheiros", + "assets_added_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}}", "assets_added_to_album_count": "{count, plural, one {# ficheiro adicionado} other {# ficheiros adicionados}} ao ÃĄlbum", - "assets_added_to_album_partial_count": "{totalCount, plural, one {Foi adicionado} other {Foram adicionados}} {successCount} de {totalCount} {totalCount, plural, one {ficheiro} other {ficheiros}} ao ÃĄlbum", "assets_added_to_albums_count": "{assetTotal, plural, one {Foi adicionado # ficheiro} other {Foram adiciondos # ficheiros}} a {albumTotal, plural, one {# ÃĄlbum} other {# albuns}}", "assets_cannot_be_added_to_album_count": "NÃŖo foi possível adicionar {count, plural, one {ficheiro} other {ficheiros}} ao ÃĄlbum", "assets_cannot_be_added_to_albums": "{count, plural, one {Ficheiro nÃŖo pode ser adicionado} other {Ficheiros nÃŖo podem ser adiciondos}} a nenhum dos ÃĄlbuns", "assets_count": "{count, plural, one {# ficheiro} other {# ficheiros}}", + "assets_deleted_permanently": "{count} ficheiro(s) eliminado(s) permanentemente", + "assets_deleted_permanently_from_server": "{count} ficheiro(s) eliminado(s) permanentemente do servidor Immich", + "assets_downloaded_failed": "{count, plural, one {# ficheiro transferido - {error} ficheiro falhou} other {# ficheiros transferidos - {error} ficheiros falharam}}", + "assets_downloaded_successfully": "{count, plural, one {# ficheiro transferido com sucesso} other {# ficheiros transferidos com sucesso}}", "assets_moved_to_trash_count": "{count, plural, one {# ficheiro movido} other {# ficheiros movidos}} para a reciclagem", "assets_permanently_deleted_count": "{count, plural, one {# ficheiro} other {# ficheiros}} eliminados permanentemente", "assets_removed_count": "{count, plural, one {# ficheiro eliminado} other {# ficheiros eliminados}}", + "assets_removed_permanently_from_device": "{count} ficheiro(s) removido(s) permanentemente do seu dispositivo", "assets_restore_confirmation": "Tem a certeza de que quer recuperar todos os ficheiros apagados? NÃŖo Ê possível anular esta aÃ§ÃŖo! Tenha em conta de que quaisquer ficheiros indisponíveis nÃŖo podem ser restaurados desta forma.", "assets_restored_count": "{count, plural, one {# ficheiro restaurado} other {# ficheiros restaurados}}", + "assets_restored_successfully": "{count} ficheiro(s) restaurados com sucesso", "assets_trashed": "{count} ficheiro(s) enviado(s) para a reciclagem", "assets_trashed_count": "{count, plural, one {# ficheiro enviado} other {# ficheiros enviados}} para a reciclagem", + "assets_trashed_from_server": "{count} ficheiro(s) do servidor Immich foi/foram enviados para a reciclagem", "assets_were_part_of_album_count": "{count, plural, one {O ficheiro jÃĄ fazia} other {Os ficheiros jÃĄ faziam}} parte do ÃĄlbum", "assets_were_part_of_albums_count": "{count, plural, one {Ficheiro jÃĄ fazia} other {Ficheiros jÃĄ faziam}} parte dos ÃĄlbuns", "authorized_devices": "Dispositivos Autorizados", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ApresentaÃ§ÃŖo automÃĄtica de diapositivos", "back": "Voltar", "back_close_deselect": "Voltar, fechar ou desmarcar", + "background_backup_running_error": "Com a cÃŗpia de segurança de fundo em execuÃ§ÃŖo, nÃŖo Ê possível inicar uma manual", "background_location_permission": "PermissÃŖo de localizaÃ§ÃŖo em segundo plano", "background_location_permission_content": "Para que seja possível trocar de redes enquanto executa em segundo plano, o Immich deve *sempre* ter a permissÃŖo de localizaÃ§ÃŖo precisa para que a aplicaÃ§ÃŖo consiga ler o nome da rede Wi-Fi", "background_options": "OpçÃĩes de fundo", "backup": "CÃŗpia de segurança", + "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({count})", "backup_album_selection_page_albums_tap": "Toque para incluir, duplo toque para excluir", - "backup_album_selection_page_assets_scatter": "Os ficheiros podem estar espalhados por vÃĄrios ÃĄlbuns. Assim, os ÃĄlbuns podem ser incluídos ou excluídos durante o processo de cÃŗpia de segurança.", + "backup_album_selection_page_assets_scatter": "Os ficheros podem estar espalhados por vÃĄrios ÃĄlbuns. Desta forma, os ÃĄlbuns podem ser incluídos ou excluídos durante o processo de cÃŗpia de segurança.", "backup_album_selection_page_select_albums": "Selecione Álbuns", "backup_album_selection_page_selection_info": "InformaçÃĩes da SeleÃ§ÃŖo", + "backup_album_selection_page_total_assets": "Total de ficheiros Ãēnicos", "backup_albums_sync": "CÃŗpia de Segurança de SincronizaÃ§ÃŖo de Álbuns", + "backup_all": "Tudo", + "backup_background_service_backup_failed_message": "Ocorreu um erro ao efetuar cÃŗpia de segurança dos ficheiros. A tentar de novoâ€Ļ", "backup_background_service_complete_notification": "CÃŗpia de conteÃēdos concluída", - "backup_background_service_default_notification": "A verificar a existÃĒncia de novos ficheirosâ€Ļ", + "backup_background_service_connection_failed_message": "Ocorreu um erro na ligaÃ§ÃŖo ao servidor. A tentar de novoâ€Ļ", + "backup_background_service_current_upload_notification": "A enviar {filename}", + "backup_background_service_default_notification": "A verificar se hÃĄ novos ficheirosâ€Ļ", + "backup_background_service_error_title": "Erro de backup", "backup_background_service_in_progress_notification": "A fazer cÃŗpia de segurança dos seus ficheirosâ€Ļ", - "backup_controller_page_albums": "Fazer cÃŗpia de segurança dos Álbuns", + "backup_background_service_upload_failure_notification": "Ocorreu um erro ao enviar {filename}", + "backup_controller_page_albums": "Backup Álbuns", + "backup_controller_page_background_app_refresh_disabled_content": "Para utilizar a cÃŗpia de segurança em segundo plano, ative a atualizaÃ§ÃŖo da aplicaÃ§ÃŖo em segundo plano em DefiniçÃĩes > Geral > AtualizaÃ§ÃŖo da aplicaÃ§ÃŖo em segundo plano.", + "backup_controller_page_background_app_refresh_disabled_title": "AtualizaÃ§ÃŖo do app em segundo plano desativada", + "backup_controller_page_background_app_refresh_enable_button_text": "Ir para as configuraçÃĩes", "backup_controller_page_background_battery_info_link": "Mostre-me como", "backup_controller_page_background_battery_info_message": "Para obter a melhor experiÃĒncia de backup em segundo plano, desative todas as otimizaçÃĩes de bateria que restrinjam a atividade em segundo plano do Immich.\n\nComo isso Ê específico por dispositivo, consulte as informaçÃĩes de como fazer isso com o fabricante do dispositivo.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "OtimizaçÃĩes de bateria", + "backup_controller_page_background_charging": "Apenas enquanto carrega a bateria", + "backup_controller_page_background_configure_error": "Ocorreu um erro ao configurar o serviço em segundo plano", "backup_controller_page_background_delay": "Atraso da cÃŗpia de segurança de novos ficheiros: {duration}", + "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer cÃŗpia de segurança automÃĄtica de novos ficheiros sem precisar de abrir a aplicaÃ§ÃŖo", + "backup_controller_page_background_is_off": "O backup automÃĄtico em segundo plano estÃĄ desativado", + "backup_controller_page_background_is_on": "O backup automÃĄtico em segundo plano estÃĄ ativado", + "backup_controller_page_background_turn_off": "Desativar o serviço em segundo plano", + "backup_controller_page_background_turn_on": "Ativar o serviço em segundo plano", + "backup_controller_page_background_wifi": "Apenas em Wi-Fi", "backup_controller_page_backup": "CÃŗpia de segurança", "backup_controller_page_backup_selected": "Selecionado: ", "backup_controller_page_backup_sub": "Fotos e vídeos salvos em backup", + "backup_controller_page_created": "Criado em: {date}", + "backup_controller_page_desc_backup": "Ative a cÃŗpia de segurança em primeiro plano para enviar novos ficheiros automaticamente ao abrir a aplicaÃ§ÃŖo.", "backup_controller_page_excluded": "Eliminado: ", + "backup_controller_page_failed": "Falhou ({count})", + "backup_controller_page_filename": "Nome do ficheiro: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "InformaçÃĩes do backup", "backup_controller_page_none_selected": "Nenhum selecionado", "backup_controller_page_remainder": "Restante", "backup_controller_page_remainder_sub": "Fotos e vídeos selecionados restantes para fazer backup", "backup_controller_page_server_storage": "Armazenamento no servidor", + "backup_controller_page_start_backup": "Iniciar CÃŗpia de Segurança", + "backup_controller_page_status_off": "Backup automÃĄtico desativado", + "backup_controller_page_status_on": "Backup automÃĄtico ativado", "backup_controller_page_storage_format": "{used} de {total} utilizado", - "backup_controller_page_to_backup": "Escolha os ÃĄlbuns para fazer cÃŗpia de segurança", + "backup_controller_page_to_backup": "Álbuns para fazer backup", "backup_controller_page_total_sub": "Todas as fotos e vídeos dos ÃĄlbuns selecionados", + "backup_controller_page_turn_off": "Desativar backup", + "backup_controller_page_turn_on": "Ativar backup", + "backup_controller_page_uploading_file_info": "A enviar informaçÃĩes do ficheiro", + "backup_err_only_album": "NÃŖo Ê possível remover apenas o ÃĄlbum", "backup_error_sync_failed": "A sincronizaÃ§ÃŖo falhou. NÃŖo Ê possível fazer a cÃŗpia de segurança.", "backup_info_card_assets": "ficheiros", + "backup_manual_cancelled": "Cancelado", + "backup_manual_in_progress": "Envio jÃĄ estÃĄ em progresso. Tente novamente mais tarde", + "backup_manual_success": "Sucesso", + "backup_manual_title": "Estado do envio", "backup_options": "DefiniçÃĩes de cÃŗpia de segurança", + "backup_options_page_title": "OpçÃĩes de cÃŗpia de segurança", + "backup_setting_subtitle": "Gerenciar as configuraçÃĩes de envio em primeiro e segundo plano", "backup_settings_subtitle": "Gerir definiçÃĩes de carregamento", + "backup_upload_details_page_more_details": "Toque para mais detalhes", "backward": "Para trÃĄs", - "battery_optimization_backup_reliability": "Desativar otimizaçÃĩes de bateria pode melhorar a fiabilidade da cÃŗpia de segurança em segundo plano", "biometric_auth_enabled": "AutenticaÃ§ÃŖo biomÊtrica ativada", "biometric_locked_out": "EstÃĄ impedido de utilizar a autenticaÃ§ÃŖo biomÊtrica", "biometric_no_options": "Sem opçÃĩes biomÊtricas disponíveis", "biometric_not_available": "A autenticaÃ§ÃŖo biomÊtrica nÃŖo estÃĄ disponível neste dispositivo", + "birthdate_saved": "Data de nascimento guardada com sucesso", "birthdate_set_description": "A data de nascimento Ê utilizada para calcular a idade desta pessoa no momento em que uma fotografia foi tirada.", "blurred_background": "Fundo desfocado", - "browse_templates": "Ver modelos", "bugs_and_feature_requests": "Relatar problemas ou pedir novas funcionalidades", "build": "VersÃŖo de compilaÃ§ÃŖo", "build_image": "Imagem de compilaÃ§ÃŖo", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Tem a certeza de que deseja manter {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Isto resolverÃĄ todos os grupos duplicados sem eliminar nada.", "bulk_trash_duplicates_confirmation": "Tem a certeza de que deseja mover para a reciclagem {count, plural, one {# ficheiro duplicado} other {# ficheiros duplicados}}? Isto manterÃĄ o maior ficheiro de cada grupo e irÃĄ mover para a reciclagem todos os outros duplicados.", "buy": "Comprar Immich", + "cache_settings_clear_cache_button": "Limpar cache", + "cache_settings_clear_cache_button_title": "Limpa a cache da aplicaÃ§ÃŖo. Isto irÃĄ afetar significativamente o desempenho da mesma atÊ que a cache seja reconstruída.", + "cache_settings_duplicated_assets_clear_button": "LIMPAR", + "cache_settings_duplicated_assets_subtitle": "Fotos e vídeos que estÃŖo na lista de bloqueio da aplicaÃ§ÃŖo", + "cache_settings_duplicated_assets_title": "Ficheiros duplicados ({count})", + "cache_settings_statistics_album": "Miniaturas da biblioteca", + "cache_settings_statistics_full": "Imagens completas", + "cache_settings_statistics_shared": "Miniaturas de ÃĄlbuns compartilhados", + "cache_settings_statistics_thumbnail": "Miniaturas", + "cache_settings_statistics_title": "Uso de cache", + "cache_settings_subtitle": "Controlar o comportamento da cache da aplicaÃ§ÃŖo Immich", + "cache_settings_tile_subtitle": "Controlar o comportamento do armazenamento local", + "cache_settings_tile_title": "Armazenamento local", + "cache_settings_title": "ConfiguraçÃĩes de cache", "camera": "CÃĸmara", "camera_brand": "Marca da cÃĸmara", "camera_model": "Modelo da cÃĸmara", @@ -678,10 +729,10 @@ "cannot_update_the_description": "NÃŖo foi possível atualizar a descriÃ§ÃŖo", "cast": "Transmitir", "cast_description": "Configurar destinos de reproduÃ§ÃŖo remota disponíveis", - "change": "Alterar", "change_date": "Alterar data", "change_description": "Alterar descriÃ§ÃŖo", "change_display_order": "Mudar ordem de exibiÃ§ÃŖo", + "change_expiration_time": "Alterar o prazo de validade", "change_location": "Alterar localizaÃ§ÃŖo", "change_name": "Alterar nome", "change_name_successfully": "Nome alterado com sucesso", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "As palavras-passe nÃŖo condizem", "change_password_form_reenter_new_password": "Confirme a nova palavra-passe", "change_pin_code": "Alterar cÃŗdigo PIN", + "change_trigger": "Alterar ativador", + "change_trigger_prompt": "Tem a certeza de que quer alterar o ativador? Isto irÃĄ remover todas as açÃĩes e filtros.", "change_your_password": "Alterar a sua palavra-passe", "changed_visibility_successfully": "Visibilidade alterada com sucesso", "charging": "A carregar", "charging_requirement_mobile_backup": "CÃŗpia de segurança de fundo necessita que o dispositivo esteja a carregar", + "check_corrupt_asset_backup": "Verificar por backups corrompidos", + "check_corrupt_asset_backup_button": "Verificar", + "check_corrupt_asset_backup_description": "Execute esta verificaÃ§ÃŖo apenas numa rede Wi-Fi e quando a cÃŗpia de segurança de todos os ficheiros jÃĄ estiver concluída. O processo pode demorar alguns minutos.", "check_logs": "Verificar registos", "checksum": "Teste de soma de dados", - "choose": "Escolher", "choose_matching_people_to_merge": "Escolha pessoas correspondentes para unir", "city": "Cidade/Localidade", "cleanup_confirm_description": "O Immich encontrou {count} ficheiro(s) (criados antes de {date}) que tÃĒm cÃŗpia de segurança neste servidor. Quer remover as cÃŗpias locais deste dispositivo?", @@ -719,10 +774,11 @@ "clear": "Limpar", "clear_all": "Limpar tudo", "clear_all_recent_searches": "Limpar todas as pesquisas recentes", - "clear_failed_count": "Limpar ({count}) falhas", "clear_file_cache": "Limpar cache de ficheiros", "clear_message": "Limpar mensagem", "clear_value": "Limpar valor", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Escrever palavra-passe", "client_cert_import": "Importar", "client_cert_import_success_msg": "Certificado do cliente foi importado", "client_cert_invalid_msg": "Certificado invÃĄlido ou palavra-passe incorreta", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certificado do cliente foi removido", "client_cert_subtitle": "Apenas hÃĄ suporte ao formato PKCS12 (.p12, .pfx). Importar/Remover certificados estÃĄ disponível apenas antes do início de sessÃŖo", "client_cert_title": "Certificado de Cliente SSL [EXPERIMENTAL]", + "clockwise": "Sentido horÃĄrio", "close": "Fechar", "collapse": "Colapsar", "collapse_all": "Colapsar tudo", "color": "Cor", + "color_theme": "Esquema de cores", "command": "Comando", "command_palette_prompt": "Encontre de forma rÃĄpida pÃĄginas, açÃĩes ou comandos", "command_palette_to_close": "para fechar", @@ -747,7 +805,6 @@ "comments_are_disabled": "ComentÃĄrios estÃŖo desativados", "common_create_new_album": "Criar novo ÃĄlbum", "completed": "Sucesso", - "configuration": "ConfiguraÃ§ÃŖo", "confirm": "Confirmar", "confirm_admin_password": "Confirmar palavra-passe de administrador", "confirm_delete_face": "Tem a certeza de que deseja remover o rosto de {name} deste ficheiro?", @@ -762,16 +819,19 @@ "contain": "Ajustar", "context": "Contexto", "continue": "Continuar", - "control_bottom_app_bar_add_tags": "Adicionar etiquetas", + "control_bottom_app_bar_create_new_album": "Criar novo ÃĄlbum", + "control_bottom_app_bar_delete_from_immich": "Excluir do Immich", "control_bottom_app_bar_delete_from_local": "Excluir do dispositivo", "control_bottom_app_bar_edit_location": "Editar LocalizaÃ§ÃŖo", "control_bottom_app_bar_edit_time": "Editar Data & Hora", + "control_bottom_app_bar_share_link": "Partilhar ligaÃ§ÃŖo", + "control_bottom_app_bar_share_to": "Compartilhar com", "control_bottom_app_bar_trash_from_immich": "Mover para a lixeira", "copied_image_to_clipboard": "Imagem copiada para a ÃĄrea de transferÃĒncia.", "copied_to_clipboard": "Copiado para a ÃĄrea de transferÃĒncia!", "copy_error": "Copiar erro", + "copy_file_path": "Copiar caminho do ficheiro", "copy_image": "Copiar Imagem", - "copy_json": "Copiar JSON", "copy_link": "Copiar link", "copy_link_to_clipboard": "Copiar link para a ÃĄrea de transferÃĒncia", "copy_password": "Copiar palavra-passe", @@ -789,6 +849,7 @@ "create_link_to_share": "Criar link para partilhar", "create_link_to_share_description": "Permitir a visualizaÃ§ÃŖo desta(s) imagem(s) a qualquer pessoa com o link", "create_new": "CRIAR NOVO", + "create_new_face": "Criar novo rosto", "create_new_person": "Criar nova pessoa", "create_new_person_hint": "Associe os ficheiros a uma nova pessoa", "create_new_user": "Criar novo utilizador", @@ -805,33 +866,37 @@ "created_at": "Criado a", "creating_linked_albums": "A criar albuns ligados...", "crop": "Cortar", + "crop_aspect_ratio_fixed": "Fixo", "crop_aspect_ratio_free": "Livre", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Quadrado", + "curated_object_page_title": "Objetos", "current_device": "Dispositivo atual", "current_pin_code": "CÃŗdigo PIN atual", "current_server_address": "Endereço atual do servidor", "custom_date": "Data personalizada", "custom_locale": "LocalizaÃ§ÃŖo personalizada", "custom_locale_description": "Formatar datas, horas e nÃēmeros baseados na língua e regiÃŖo selecionadas", + "custom_url": "URL personalizado", "cutoff_date_description": "Manter fotos dos Ãēltimosâ€Ļ", "cutoff_day": "{count, plural, one {dia} other {dias}}", "cutoff_year": "{count, plural, one {ano} other {anos}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Escuro", "dark_theme": "Alterar para o tema escuro", "date": "Data", "date_after": "Data apÃŗs", "date_and_time": "Data e Hora", "date_before": "Data antes", - "date_of_birth": "Data de Nascimento", + "date_format": "E, d LLL, y â€ĸ h:mm a", "date_of_birth_saved": "Data de nascimento guardada com sucesso", "date_range": "Intervalo de datas", - "date_time_original": "Data/Hora Original", "day": "Dia", "days": "Dias", "deduplicate_all": "Remover todos os duplicados", - "default_quality_subtitle": "Qualidade utilizada ao tocar no botÃŖo de partilhar. Toque e segure no botÃŖo de partilhar para escolher a qualidade a cada vez.", - "default_share_quality": "Qualidade de partilha padrÃŖo", + "default_locale": "LocalizaÃ§ÃŖo PadrÃŖo", + "default_locale_description": "Formatar datas e nÃēmeros baseados na definiÃ§ÃŖo de localizaÃ§ÃŖo do navegador", "delete": "Eliminar", "delete_action_confirmation_message": "Tem a certeza de que quer eliminar este ficheiro? EstÃĄ aÃ§ÃŖo irÃĄ mover o ficheiro para a reciclagem do servidor e perguntar se quer apagÃĄ-lo localmente", "delete_action_prompt": "{count} eliminados", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Estes ficheiros serÃŖo eliminados permanentemente do Immich e do seu dispositivo", "delete_dialog_alert_local": "Estes ficheiros serÃŖo eliminados permanentemente do seu dispositivo, mas continuarÃŖo disponíveis no servidor Immich", "delete_dialog_alert_local_non_backed_up": "Alguns dos ficheiros nÃŖo tÃĒm cÃŗpia de segurança no Immich e serÃŖo eliminados permanentemente do seu dispositivo", + "delete_dialog_alert_remote": "Estes ficheiros serÃŖo eliminados permanentemente do servidor Immich", + "delete_dialog_ok_force": "Confirmo que quero excluir", "delete_dialog_title": "Excluir Permanentemente", "delete_duplicates_confirmation": "Tem a certeza de que deseja eliminar permanentemente estes itens duplicados?", "delete_face": "Remover rosto", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Tem a certeza de que pretende eliminar a etiqueta {tagName} ?", "delete_user": "Eliminar utilizador", "deleted_shared_link": "Link de partilha eliminado", + "deletes_missing_assets": "Elimina os ficheiros que estejam em falta no disco", "description": "DescriÃ§ÃŖo", + "description_input_hint_text": "Adicionar descriÃ§ÃŖo...", + "description_input_submit_error": "Erro ao atualizar a descriÃ§ÃŖo, verifique o registo para obter mais detalhes", "deselect_all": "Remover seleÃ§ÃŖo de tudo", "details": "Detalhes", "direction": "DireÃ§ÃŖo", "disable": "Desativar", "disabled": "Desativado", + "disallow_edits": "NÃŖo permitir ediçÃĩes", "discord": "Discord", "discover": "Descobrir", "discovered_devices": "Dispositivos descobertos", "dismiss_all_errors": "Dispensar todos os erros", + "dismiss_error": "Dispensar erro", "display_options": "OpçÃĩes de exibiÃ§ÃŖo", "display_order": "Ordem de exibiÃ§ÃŖo", "display_original_photos": "Exibir fotos originais", @@ -876,9 +948,11 @@ "documentation": "DocumentaÃ§ÃŖo", "done": "Feito", "download": "Transferir", + "download_action_prompt": "A descarregar {count} ficheiros", "download_canceled": "Cancelado", "download_complete": "Sucesso", "download_enqueue": "Na fila", + "download_error": "Erro ao baixar", "download_failed": "Ocorreu um erro ao descarregar", "download_finished": "Concluído", "download_include_embedded_motion_videos": "Vídeos incorporados", @@ -888,15 +962,15 @@ "download_paused": "Pausado", "download_settings": "Transferir", "download_settings_description": "Gerir definiçÃĩes relacionadas com a transferÃĒncia de ficheiros", + "download_started": "Descarregamento iniciado", + "download_sucess": "Baixado com sucesso", + "download_sucess_android": "O ficheiro foi descarregado para a pasta DCIM/Immich", "download_waiting_to_retry": "Tentando novamente", "downloading": "A transferir", "downloading_asset_filename": "A transferir o ficheiro {filename}", "downloading_from_icloud": "A descarregar do iCloud", "downloading_media": "A descarregar ficheiro", - "drag_to_reorder": "Arraste para reordenar", "drop_files_to_upload": "Solte os ficheiros em qualquer lugar para os enviar", - "duplicate": "Duplicar", - "duplicate_workflow": "Duplicar fluxo de trabalho", "duplicates": "Itens duplicados", "duplicates_description": "Marca cada grupo ao indicar quais ficheiros, se algum, sÃŖo duplicados.", "duration": "DuraÃ§ÃŖo", @@ -908,7 +982,9 @@ "edit_date_and_time": "Editar data e hora", "edit_date_and_time_action_prompt": "Alterada a data e hora de {count} ficheiros", "edit_date_and_time_by_offset": "Alterar data com diferença", + "edit_date_and_time_by_offset_interval": "Novo período: {from} - {to}", "edit_description": "Editar descriÃ§ÃŖo", + "edit_description_prompt": "Por favor selecione uma nova descriÃ§ÃŖo:", "edit_exclusion_pattern": "Editar o padrÃŖo de exclusÃŖo", "edit_faces": "Editar rostos", "edit_key": "Editar chave", @@ -923,6 +999,9 @@ "edit_user": "Editar utilizador", "edit_workflow": "Editar fluxo de trabalho", "editor": "Editar", + "editor_close_without_save_prompt": "As alteraçÃĩes nÃŖo serÃŖo guardadas", + "editor_close_without_save_title": "Fechar editor?", + "editor_confirm_reset_all_changes": "Tem a certeza de que quer desfazer todas as alteraçÃĩes?", "editor_discard_edits_confirm": "Descartar AlteraçÃĩes", "editor_discard_edits_prompt": "Tem alteraçÃĩes nÃŖo guardadas. Tem a certeza de que quer descartÃĄ-las?", "editor_discard_edits_title": "Descartar alteraçÃĩes?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Insira o cÃŗdigo PIN", "enter_your_pin_code_subtitle": "Insira o cÃŗdigo PIN para aceder à pasta trancada", "error": "Erro", + "error_change_sort_album": "Ocorreu um erro ao mudar a ordem de exibiÃ§ÃŖo", "error_delete_face": "Falha ao remover rosto do ficheiro", + "error_getting_places": "Erro ao obter locais", "error_loading_albums": "Ocorreu um erro ao carregar os ÃĄlbuns", "error_loading_image": "Erro ao carregar a imagem", "error_loading_partners": "Erro ao carregar parceiros: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Ocorreu um erro ao remover chave de produto", "failed_to_reset_pin_code": "Ocorreu um erro ao repor o cÃŗdigo PIN", "failed_to_stack_assets": "Ocorreu um erro ao empilhar os ficheiros", - "failed_to_tag_assets": "Ocorreu um erro ao etiquetar recursos", "failed_to_unstack_assets": "Ocorreu um erro ao desempilhar ficheiros", "failed_to_update_notification_status": "Ocorreu um erro ao atualizar o estado das notificaçÃĩes", "incorrect_email_or_password": "Email ou palavra-passe incorretos", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Adicionar DescriÃ§ÃŖo...", "exif_bottom_sheet_description_error": "Ocorreu um erro ao alterar a descriÃ§ÃŖo", + "exif_bottom_sheet_details": "DETALHES", + "exif_bottom_sheet_location": "LOCALIZAÇÃO", "exif_bottom_sheet_no_description": "Sem descriÃ§ÃŖo", + "exif_bottom_sheet_people": "PESSOAS", + "exif_bottom_sheet_person_add_person": "Adicionar nome", "exit_slideshow": "Sair da apresentaÃ§ÃŖo", "expand": "Expandir", "expand_all": "Expandir tudo", + "experimental_settings_new_asset_list_subtitle": "Trabalho em andamento", + "experimental_settings_new_asset_list_title": "Ativar visualizaÃ§ÃŖo de grade experimental", + "experimental_settings_subtitle": "Use por sua conta e risco!", + "experimental_settings_title": "Experimental", "expire_after": "Expira apÃŗs", "expired": "Expirou", "expires_date": "Expira a {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Exportar como JSON", "export_database": "Exportar Base de Dados", "export_database_description": "Exportar a Base de Dados SQLite", - "exposure_time": "Tempo de exposiÃ§ÃŖo", "extension": "ExtensÃŖo", "external": "Externo", "external_libraries": "Bibliotecas externas", "external_network": "Rede externa", "external_network_sheet_info": "Quando nÃŖo estiver ligado à rede Wi-Fi especificada, a aplicaÃ§ÃŖo irÃĄ ligar-se utilizando o primeiro URL abaixo que conseguir aceder, a começar do topo da lista para baixo", - "f_number": "Abertura", "face_unassigned": "Sem atribuiÃ§ÃŖo", "failed": "Ocorreu um erro", "failed_count": "Falhadas: {count}", "failed_to_authenticate": "NÃŖo foi possível autenticar", - "failed_to_delete_file": "Ocorreu um erro ao eliminar ficheiro", "failed_to_load_assets": "Ocorreu um erro ao carregar ficheiros", "failed_to_load_folder": "Ocorreu um erro ao carregar a pasta", "favorite": "Favorito", "favorite_action_prompt": "{count} adicionados aos favoritos", "favorite_or_unfavorite_photo": "Marcar ou desmarcar a foto como favorita", "favorites": "Favoritos", + "favorites_page_no_favorites": "Nenhum favorito encontrado", "feature_photo_updated": "Foto principal atualizada", "features": "Funcionalidades", + "features_in_development": "Funcionalidades em Desenvolvimento", "features_setting_description": "Configurar as funcionalidades da aplicaÃ§ÃŖo", "file_name_or_extension": "Nome do ficheiro ou extensÃŖo", "file_name_text": "Nome do ficheiro", + "file_name_with_value": "Nome do ficheiro: {file_name}", "file_size": "Tamanho do ficheiro", "filename": "Nome do ficheiro", "filetype": "Tipo de ficheiro", "filter": "Filtro", + "filter_description": "CondiçÃĩes para filtrar os ficheiros alvo", "filter_people": "Filtrar pessoas", "filter_places": "Filtrar lugares", "filter_tags": "Filtrar etiquetas", "filters": "Filtros", + "find_them_fast": "Encontre-as mais rapidamente pelo nome numa pesquisa", "first": "Primeiro", "fix_incorrect_match": "Corrigir correspondÃĒncia incorreta", - "focal_length": "DistÃĸncia Focal", "folder": "Pasta", "folder_not_found": "Pasta nÃŖo encontrada", "folders": "Pastas", @@ -1144,10 +1233,9 @@ "forgot_pin_code_question": "Esqueceu-se do seu PIN?", "forward": "Para a frente", "free_up_space": "Libertar Espaço", - "free_up_space_description": "Mover fotos e vídeos que jÃĄ tenham sido copiados para o servidor para a reciclagem do seu dispositivo para libertar espaço. As cÃŗpias no servidor mantÃĒm-se seguras.", + "free_up_space_description": "Mover fotos e vídeos que tenham sido copiados para o servidor para a reciclagem do seu dispositivo para libertar espaço. As cÃŗpias no servidor mantÃĒm-se seguras.", "free_up_space_settings_subtitle": "Libertar espaço no dispositivo", "full_path": "Caminho completo: {path}", - "full_path_or_folder": "Caminho completo ou nome da pasta", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidade requer o carregamento de recursos externos da Google para poder funcionar.", "general": "Geral", @@ -1168,6 +1256,7 @@ "group_owner": "Agrupar por dono", "group_places_by": "Agrupar lugares por...", "group_year": "Agrupar por ano", + "haptic_feedback_switch": "Ativar vibraÃ§ÃŖo", "haptic_feedback_title": "VibraÃ§ÃŖo", "has_quota": "Tem quota", "hash_asset": "Criptografar ficheiro", @@ -1188,12 +1277,29 @@ "hide_schema": "Ocultar esquema", "hide_text_recognition": "Esconder reconhecimento de texto", "hide_unnamed_people": "Ocultar pessoas sem nome", + "home_page_add_to_album_conflicts": "Foram adicionados {added} ficheiros ao ÃĄlbum {album}. {failed} ficheiros jÃĄ estÃŖo no ÃĄlbum.", + "home_page_add_to_album_err_local": "Ainda nÃŖo Ê possível adicionar recursos locais aos ÃĄlbuns, ignorando", + "home_page_add_to_album_success": "{added} ficheiros foram adicionados ao ÃĄlbum {album}.", + "home_page_album_err_partner": "Ainda nÃŖo Ê possível adicionar ficheiros do parceiro a um ÃĄlbum, a ignorar", + "home_page_archive_err_local": "Ainda nÃŖo Ê possível arquivar recursos locais, ignorando", + "home_page_archive_err_partner": "NÃŖo Ê possível arquivar Fotos e Videos do parceiro, ignorando", "home_page_building_timeline": "Construindo a linha do tempo", + "home_page_delete_err_partner": "NÃŖo Ê possível eliminar ficheiros do parceiro, a ignorar", + "home_page_delete_remote_err_local": "Foram selecionados ficheiros locais para excluir remotamente, a ignorar", + "home_page_favorite_err_local": "Ainda nÃŖo Ê possível adicionar recursos locais favoritos, ignorando", + "home_page_favorite_err_partner": "Ainda nÃŖo Ê possível marcar ficheiros do parceiro como favoritos, a ignorar", + "home_page_first_time_notice": "Se Ê a primeira vez que utiliza a aplicaÃ§ÃŖo, certifique-se de que marca pelo menos um ÃĄlbum do dispositivo para cÃŗpia de segurança, para a linha do tempo poder ser preenchida com fotos e vídeos", + "home_page_locked_error_local": "NÃŖo foi possível mover ficheiros locais para a pasta trancada, a continuar", + "home_page_locked_error_partner": "NÃŖo foi possível mover ficheiros do parceiro para a pasta trancada, a continuar", + "home_page_share_err_local": "NÃŖo Ê possível partilhar ficheiros locais com um link, a ignorar", + "home_page_upload_err_limit": "SÃŗ Ê possível enviar um mÃĄximo de 30 ficheiros de cada vez, a ignorar", "host": "Servidor", "hour": "Hora", "hours": "Horas", "id": "ID", "idle": "Em espera", + "ignore_icloud_photos": "ignorar fotos no iCloud", + "ignore_icloud_photos_description": "Fotos que estÃŖo armazenadas no iCloud nÃŖo serÃŖo carregadas para o servidor do Immich", "image": "Imagem", "image_alt_text_date": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} em {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} e {person2} em {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {person3} em {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e outras {additionalCount, number} pessoas em {date}", + "image_saved_successfully": "Imagem salva", + "image_viewer_page_state_provider_download_started": "Descarregamento Iniciado", + "image_viewer_page_state_provider_download_success": "Baixado com sucesso", + "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", "immich_logo": "Logotipo do Immich", "immich_web_interface": "Interface Web do Immich", "import_from_json": "Importar a partir de JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Partilha individual", "individual_shares": "Partilhas individuais", "info": "InformaçÃĩes", - "integrity_checks": "VerificaçÃĩes de integridade", "interval": { "day_at_onepm": "Todos os dias, às 13:00", "hours": "A cada {hours, plural, one {hora} other {{hours, number} horas}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Formato de data invÃĄlido", "invite_people": "Convidar Pessoas", "invite_to_album": "Convidar para o ÃĄlbum", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Busca ocorreu a {dateTime}", + "ios_debug_info_last_sync_at": "Última sincronizaÃ§ÃŖo a {dateTime}", + "ios_debug_info_no_processes_queued": "NÃŖo existem processos de segundo plano em fila", + "ios_debug_info_no_sync_yet": "Ainda nÃŖo foi executada nenhuma tarefa de sincronizaÃ§ÃŖo em segundo plano", + "ios_debug_info_processes_queued": "{count, plural, one {{count} processo de segundo plano em fila} other {{count} processos de segundo plano em fila}}", + "ios_debug_info_processing_ran_at": "Processamento executado em {dateTime}", "items_count": "{count, plural, one {item #} other {itens #}}", "jobs": "Tarefas", + "json_editor": "Editor JSON", + "json_error": "Erro JSON", "keep": "Manter", "keep_albums": "Manter ÃĄlbuns", "keep_albums_count": "A manter {count} {count, plural, one {ÃĄlbum} other {ÃĄlbuns}}", @@ -1259,12 +1375,14 @@ "leave": "Sair", "leave_album": "Sair do ÃĄlbum", "lens_model": "Modelo de lente", - "less": "Menos", "let_others_respond": "Permitir respostas", "level": "Nível", "library": "Biblioteca", "library_add_folder": "Adicionar pasta", "library_edit_folder": "Editar pasta", + "library_options": "OpçÃĩes da biblioteca", + "library_page_device_albums": "Álbuns no dispositivo", + "library_page_new_album": "Novo ÃĄlbum", "library_page_sort_asset_count": "Quantidade de ficheiros", "library_page_sort_created": "Data de criaÃ§ÃŖo", "library_page_sort_last_modified": "Última modificaÃ§ÃŖo", @@ -1274,14 +1392,11 @@ "light_theme": "Alterar para o tema claro", "like": "Gosto", "like_deleted": "Gosto removido", - "link": "LigaÃ§ÃŖo", "link_motion_video": "Relacionar video animado", "link_to_docs": "Para mais informaçÃĩes, veja a documentaÃ§ÃŖo.", "link_to_oauth": "Link do OAuth", "linked_oauth_account": "Conta OAuth Associada", "list": "Lista", - "live": "Em direto", - "load_more": "Carregar Mais", "loading": "A Carregar", "loading_search_results_failed": "Ocorreu um erro ao carregar os resultados da pesquisa", "local": "Local", @@ -1310,9 +1425,11 @@ "login": "Iniciar SessÃŖo", "login_disabled": "Início de sessÃŖo desativado", "login_form_api_exception": "Erro de API. Verifique a URL do servidor e tente novamente.", + "login_form_back_button_text": "Voltar", "login_form_email_hint": "oseuemail@email.com", "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta", "login_form_endpoint_url": "URL do servidor", + "login_form_err_http": "Por favor especifique http:// ou https://", "login_form_err_invalid_email": "Email InvÃĄlido", "login_form_err_invalid_url": "URL invÃĄlido", "login_form_err_leading_whitespace": "Espaço em branco no início", @@ -1320,11 +1437,12 @@ "login_form_failed_get_oauth_server_config": "Ocorreu um erro ao iniciar sessÃŖo com o OAuth, verifique o URL do servidor", "login_form_failed_get_oauth_server_disable": "A funÃ§ÃŖo OAuth nÃŖo estÃĄ disponível neste servidor", "login_form_failed_login": "Ocorreu um erro ao iniciar sessÃŖo, verifique o URL do servidor, o e-mail e a palavra-passe", - "login_form_handshake_exception": "Ocorreu um erro ao autenticar com o servidor. Ative o suporte para certificados auto-assinados nas definiçÃĩes se estiver a utilizar um certificado auto-assinado.", + "login_form_handshake_exception": "Erro ao conectar com o servidor. Ative o suporte para certificados auto-assinados nas configuraçÃĩes se estiver utilizando um certificado auto-assinado.", "login_form_password_hint": "Palavra-passe", - "login_form_server_empty": "Insira a URL do servidor", - "login_form_server_error": "NÃŖo foi possível ligar ao servidor", - "login_has_been_disabled": "O início de sessÃŖo foi desativado", + "login_form_save_login": "Manter sessÃŖo iniciada", + "login_form_server_empty": "Insira a URL do servidor.", + "login_form_server_error": "NÃŖo foi possível ligar ao servidor.", + "login_has_been_disabled": "O início de sessÃŖo foi desativado.", "login_password_changed_error": "Ocorreu um erro ao atualizar a sua palavra-passe", "login_password_changed_success": "Palavra-passe atualizada com sucesso", "logout_all_device_confirmation": "Tem a certeza de que deseja terminar a sessÃŖo em todos os dispositivos?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "A Restaurar Base de Dados", "maintenance_description": "O Immich foi colocado em modo de manutenÃ§ÃŖo.", "maintenance_end": "Desativar modo de manutenÃ§ÃŖo", + "maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenÃ§ÃŖo.", "maintenance_logged_in_as": "SessÃŖo iniciada como {user}", "maintenance_restore_from_backup": "Restaurar a partir de uma cÃŗpia de segurança", "maintenance_restore_library": "Restaurar a Sua Biblioteca", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Abrir definiçÃĩes", "manage_media_access_subtitle": "Autorizar que aplicaÃ§ÃŖo Immich faça a gestÃŖo e movimente ficheiros.", "manage_media_access_title": "Acesso à GestÃŖo de Ficheiros", + "manage_shared_links": "Gerir links partilhados", "manage_sharing_with_partners": "Gerir partilha com parceiros", "manage_the_app_settings": "Gerir definiçÃĩes da aplicaÃ§ÃŖo", "manage_your_account": "Gerir a sua conta", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Gerir os seus dispositivos com sessÃŖo iniciada", "manage_your_oauth_connection": "Gerir a sua ligaÃ§ÃŖo ao OAuth", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {Sem fotos nesta ÃĄrea} one {# foto} other {# fotos}}", "map_cannot_get_user_location": "Impossível obter a sua localizaÃ§ÃŖo", + "map_location_dialog_yes": "Sim", "map_location_picker_page_use_location": "Utilizar esta localizaÃ§ÃŖo", "map_location_service_disabled_content": "Serviço de localizaÃ§ÃŖo precisa de estar ativado para mostrar recursos da localizaÃ§ÃŖo atual. Deseja ativar agora?", "map_location_service_disabled_title": "Serviço de localizaÃ§ÃŖo desativado", - "map_marker_for_image": "Marcador do mapa para a foto tirada em {city}, {country}", + "map_marker_for_images": "Marcador no mapa para fotos tiradas em {city}, {country}", "map_marker_with_image": "Marcador de mapa com imagem", "map_no_location_permission_content": "A permissÃŖo da localizaÃ§ÃŖo Ê necessÃĄria para mostrar recursos da localizaÃ§ÃŖo atual. Deseja conceder a permissÃŖo agora?", "map_no_location_permission_title": "PermissÃŖo de localizaÃ§ÃŖo foi negada", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Últimos {days} dias", "map_settings_date_range_option_year": "Último ano", "map_settings_date_range_option_years": "Últimos {years} anos", + "map_settings_dialog_title": "ConfiguraçÃĩes do mapa", "map_settings_include_show_archived": "Incluir arquivados", "map_settings_include_show_partners": "Incluir parceiros", "map_settings_only_show_favorites": "Mostrar apenas favoritos", "map_settings_theme_settings": "Tema do mapa", + "map_zoom_to_see_photos": "Diminua o zoom para ver mais fotos", "mark_all_as_read": "Marcar tudo como lido", + "mark_as_read": "Marcar como lido", "marked_all_as_read": "Tudo marcado como lido", "matches": "CorrespondÃĒncias", "matching_assets": "ConteÃēdos coincidentes", - "media_chrome": { - "auto": "Auto", - "captions": "Legendas", - "captions_off": "Desativado", - "closed_captions": "legendas ocultas", - "decode_error": "Erro de decodificaÃ§ÃŖo", - "disable_captions": "Desativar legendas", - "enable_captions": "Ativar legendas", - "enter_fullscreen_mode": "Entrar no modo de ecrÃŖ inteiro", - "exit_fullscreen_mode": "Sair do modo de ecrÃŖ inteiro", - "loop": "Repetir", - "media_error_description": "Um erro de mídia fez com que a reproduÃ§ÃŖo fosse interrompida. O conteÃēdo pode estar corrompido ou seu navegador nÃŖo suporta este formato.", - "media_loading": "carregando mídia", - "mute": "Silenciar", - "network_error": "Erro de rede", - "network_error_description": "Um erro de rede causou a falha na transferÃĒncia do conteÃēdo.", - "not_supported_error": "Fonte nÃŖo suportada", - "playback_rate": "Taxa de reproduÃ§ÃŖo", - "playback_rate_current": "taxa de reproduÃ§ÃŖo atual", - "playback_rate_value": "Taxa de reproduÃ§ÃŖo {playbackRate}", - "playback_time": "tempo de reproduÃ§ÃŖo", - "quality": "Qualidade", - "second": "segundo", - "seconds": "segundos", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} restante", - "unmute": "Ativar Som", - "unsupported_error_description": "Ocorreu um erro de incompatibilidade. O servidor ou a rede falharam, ou o seu navegador nÃŖo suporta este formato.", - "video_not_loaded_unknown_time": "vídeo nÃŖo carregado, tempo desconhecido.", - "video_player": "reprodutor de vídeo", - "volume": "volume" - }, "media_type": "Tipo de mÊdia", "memories": "MemÃŗrias", "memories_all_caught_up": "Finalizamos por hoje", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Tem a certeza de que deseja unir estas pessoas? Esta aÃ§ÃŖo Ê irreversível.", "merge_people_successfully": "Pessoas unidas com sucesso", "merged_people_count": "Unidas {count, plural, one {# pessoa} other {# pessoas}}", - "minFaces": "Mínimo de caras", - "minFaces_description": "O nÃēmero mínimo de reconhecimentos faciais para uma pessoa ser exibida", "minimize": "Minimizar", "minute": "Minuto", "minutes": "Minutos", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Em falta", "mobile_app": "App mÃŗvel", "mobile_app_download_onboarding_note": "Descarregue a aplicaÃ§ÃŖo para dispositivos mÃŗveis com as seguintes opçÃĩes", "model": "Modelo", - "modify_date": "Modificar Data", "month": "MÃĒs", + "monthly_title_text_date_format": "MMMM y", "more": "Mais", - "motion": "Movimento", "move": "Mover", + "move_down": "Mover para baixo", "move_off_locked_folder": "Mover para fora da pasta trancada", "move_to": "Mover para", "move_to_device_trash": "Mover para a reciclagem do dispositivo", "move_to_lock_folder_action_prompt": "{count} adicionados à pasta trancada", "move_to_locked_folder": "Mover para a pasta trancada", "move_to_locked_folder_confirmation": "Estas fotos e vídeos serÃŖo removidas de todos os ÃĄlbuns, e sÃŗ serÃŖo visíveis na pasta trancada", + "move_up": "Mover para cima", + "moved_to_archive": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para o arquivo", + "moved_to_library": "{count, plural, one {Foi movido # ficheiro} other {Foram movidos # ficheiros}} para a biblioteca", "moved_to_trash": "Enviado para a reciclagem", + "multiselect_grid_edit_date_time_err_read_only": "NÃŖo Ê possível editar a data de um ficheiro sÃŗ de leitura, a ignorar", + "multiselect_grid_edit_gps_err_read_only": "NÃŖo Ê possível editar a localizaÃ§ÃŖo de um ficheiro sÃŗ de leitura, a ignorar", "mute_memories": "Silenciar MemÃŗrias", "my_albums": "Os meus ÃĄlbuns", - "my_immich_description": "Copiar a pÃĄgina atual como uma ligaÃ§ÃŖo do My Immich", - "my_immich_title": "LigaÃ§ÃŖo My Immich", "name": "Nome", "name_or_nickname": "Nome ou alcunha", "name_required": "O nome Ê obrigatÃŗrio", @@ -1480,11 +1576,12 @@ "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave de API", - "new_feature": "Nova Funcionalidade", + "new_date_range": "Nova faixa de datas", "new_password": "Nova palavra-passe", "new_person": "Nova Pessoa", "new_pin_code": "Novo cÃŗdigo PIN", "new_pin_code_subtitle": "Esta Ê a primeira vez que acede à pasta trancada. Crie um cÃŗdigo PIN para aceder a esta pÃĄgina de forma segura", + "new_timeline": "Nova Linha do Tempo", "new_update": "Nova atualizaÃ§ÃŖo", "new_user_created": "Novo utilizador criado", "new_version_available": "NOVA VERSÃO DISPONÍVEL", @@ -1492,6 +1589,7 @@ "next": "Avançar", "next_memory": "PrÃŗxima memÃŗria", "no": "NÃŖo", + "no_actions_added": "Ainda nÃŖo foram adicionadas açÃĩes", "no_albums_found": "Nenhum ÃĄlbum encontrado", "no_albums_message": "Crie um ÃĄlbum para organizar as suas fotos e vídeos", "no_albums_with_name_yet": "Parece que ainda nÃŖo tem nenhum ÃĄlbum com este nome.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Nenhum dispositivo de transmissÃŖo encontrado", "no_checksum_local": "Sem cÃĄlculo de verificaÃ§ÃŖo disponível - nÃŖo pode capturar conteÃēdos locais", "no_checksum_remote": "Soma de verificaÃ§ÃŖo (checksum) nÃŖo disponível - nÃŖo Ê possível obter o recurso remoto", + "no_configuration_needed": "ConfiguraÃ§ÃŖo nÃŖo Ê necessÃĄria", "no_devices": "Nenhum dispositivo autorizado", "no_duplicates_found": "Nenhum item duplicado foi encontrado.", "no_exif_info_available": "Sem informaçÃĩes exif disponíveis", "no_explore_results_message": "Carregue mais fotos para explorar a sua coleÃ§ÃŖo.", - "no_favorites_message": "Adicione aos favoritos para encontrar as suas melhores fotos e vídeos de forma rÃĄpida", + "no_favorites_message": "Adicione aos favoritos para encontrar as suas melhores fotos e vídeos rapidamente", + "no_filters_added": "Ainda nÃŖo foram adicionados filtros", "no_libraries_message": "Crie uma biblioteca externa para ver as suas fotos e vídeos", "no_local_assets_found": "Sem cÃĄlculo de verificaÃ§ÃŖo disponível", + "no_location_set": "Sem localizaÃ§ÃŖo definida", "no_locked_photos_message": "Fotos e vídeos na pasta trancada estÃŖo ocultos e nÃŖo serÃŖo exibidos enquanto explora ou pesquisa na biblioteca.", "no_name": "Sem nome", "no_notifications": "Sem notificaçÃĩes", @@ -1518,7 +1619,7 @@ "no_results": "Sem resultados", "no_results_description": "Tente um sinÃŗnimo ou uma palavra-chave mais comum", "no_shared_albums_message": "Crie um ÃĄlbum para partilhar fotos e vídeos com pessoas na sua rede", - "no_steps": "Sem passos adicionados ainda", + "no_uploads_in_progress": "Nenhum carregamento em curso", "none": "Nenhum", "not_allowed": "NÃŖo permitido", "not_available": "N/A", @@ -1526,11 +1627,7 @@ "not_selected": "NÃŖo selecionado", "notes": "Notas", "nothing_here_yet": "Ainda nÃŖo existe nada aqui", - "notification_backup_reliability": "Ativar notificaçÃĩes para melhorar a fiabilidade da cÃŗpia de segurança em segundo plano", - "notification_enabled_list_tile_content": "O Immich utiliza notificaçÃĩes para cÃŗpias de segurança em segundo plano. Faça a gestÃŖo destas nas definiçÃĩes do seu dispositivo.", - "notification_enabled_list_tile_open_button": "Abrir DefiniçÃĩes", - "notification_enabled_list_tile_title": "NotificaçÃĩes ativadas", - "notification_permission_dialog_content": "Para ativar as notificaçÃĩes, aceda às DefiniçÃĩes e selecione permitir.", + "notification_permission_dialog_content": "Para ativar as notificaçÃĩes, vÃĄ em ConfiguraçÃĩes e selecione permitir.", "notification_permission_list_tile_content": "Conceder permissÃĩes para ativar notificaçÃĩes.", "notification_permission_list_tile_enable_button": "Ativar notificaçÃĩes", "notification_permission_list_tile_title": "PermissÃŖo de notificaçÃĩes", @@ -1541,8 +1638,6 @@ "obtainium_configurator": "Configurador Obtainium", "obtainium_configurator_instructions": "Utilize o Obtainium para instalar e atualizar a aplicaÃ§ÃŖo Android diretamente a partir do lançamento do Immich no Github. Crie uma chave API e selecione uma variante para criar a sua ligaÃ§ÃŖo de configuraÃ§ÃŖo do Obtainium", "ocr": "Reconhecimento Ótico de Caracteres (OCR)", - "ocr_body": "O Immich agora lÃĒ o texto dentro das suas fotos, para que possa pesquisÃĄ-las pelo que estÃĄ escrito nelas.", - "ocr_title": "Pesquisar por texto nas suas fotos", "official_immich_resources": "Recursos oficiais do Immich", "offline": "Offline", "offset": "Desvio", @@ -1561,8 +1656,6 @@ "open": "Abrir", "open_calendar": "Abrir calendÃĄrio", "open_in_browser": "Abrir no navegador", - "open_in_immich_body": "Defina o Immich como a sua aplicaÃ§ÃŖo de galeria no Android para poder abrir fotos diretamente de outras aplicaçÃĩes.", - "open_in_immich_title": "Abrir fotos no Immich", "open_in_map_view": "Abrir na visualizaÃ§ÃŖo de mapa", "open_in_openstreetmap": "Abrir no OpenStreetMap", "open_the_search_filters": "Abrir os filtros de pesquisa", @@ -1571,12 +1664,12 @@ "organize_into_albums": "Organizar em ÃĄlbuns", "organize_into_albums_description": "Colocar fotos existentes em ÃĄlbuns utilizando as definiçÃĩes atuais de sincronizaÃ§ÃŖo", "organize_your_library": "Organizar a sua biblioteca", - "orientation": "OrientaÃ§ÃŖo", "original": "original", "other": "Outro", "other_devices": "Outros dispositivos", + "other_entities": "Outras entidades", "other_variables": "Outras variÃĄveis", - "owned": "Seu", + "owned": "Seus", "owner": "Dono", "page": "PÃĄgina", "partner": "Parceiro", @@ -1584,9 +1677,12 @@ "partner_can_access_assets": "Todas as suas fotos e vídeos, exceto os Arquivados ou Eliminados", "partner_can_access_location": "A localizaÃ§ÃŖo onde as fotos foram tiradas", "partner_list_user_photos": "Fotos de {user}", + "partner_list_view_all": "Ver tudo", "partner_page_empty_message": "As suas fotos ainda nÃŖo foram compartilhadas com nenhum parceiro.", "partner_page_no_more_users": "NÃŖo existem mais utilizadores para adicionar", + "partner_page_partner_add_failed": "Ocorreu um erro ao adicionar parceiro", "partner_page_select_partner": "Selecionar parceiro", + "partner_page_shared_to_title": "Compartilhar com", "partner_page_stop_sharing_content": "{partner} irÃĄ deixar de ter acesso às suas fotos.", "partner_sharing": "Partilha com Parceiro", "partners": "Parceiros", @@ -1608,26 +1704,39 @@ "people": "Pessoas", "people_edits_count": "{count, plural, one {# pessoa editada} other {# pessoas editadas}}", "people_feature_description": "Navegar por fotos e vídeos agrupados por pessoas", + "people_selected": "{count, plural, one {# pessoa selecionada} other {# pessoas selecionadas}}", + "people_sidebar_description": "Exibir o link Pessoas na barra lateral", "permanent_deletion_warning": "Aviso de eliminaÃ§ÃŖo permanente", "permanent_deletion_warning_setting_description": "Exibir um aviso ao eliminar ficheiros de forma permanente", "permanently_delete": "Eliminar permanentemente", "permanently_delete_assets_count": "Eliminar permanentemente {count, plural, one {ficheiro} other {ficheiros}}", - "permanently_delete_assets_prompt": "Tem a certeza de que deseja eliminar permanentemente {count, plural, one {este ficheiro?} other {estes # ficheiros?}} Esta aÃ§ÃŖo tambÊm irÃĄ {count, plural, one {removÃĒ-lo do ÃĄlbum} other {removÃĒ-los dos ÃĄlbuns}}.", + "permanently_delete_assets_prompt": "Tem a certeza de que deseja eliminar permanentemente {count, plural, one {este ficheiro?} other {estes # ficheiros?}} Esta aÃ§ÃŖo tambÊm removerÃĄ {count, plural, one {isto do ÃĄlbum} other {isto dos ÃĄlbuns}}.", "permanently_deleted_asset": "Ficheiro eliminado permanentemente", "permanently_deleted_assets_count": "{count, plural, one {# Ficheiro eliminado} other {# Ficheiros eliminados}} permanentemente", "permission": "PermissÃŖo", "permission_empty": "A sua permissÃŖo nÃŖo pode estar vazia", + "permission_onboarding_back": "Voltar", + "permission_onboarding_continue_anyway": "Continuar mesmo assim", + "permission_onboarding_get_started": "Começar", + "permission_onboarding_go_to_settings": "Ir para as configuraçÃĩes", + "permission_onboarding_permission_denied": "PermissÃŖo negada. Para utilizar o Immich, conceda permissÃĩes de fotos e vídeo nas configuraçÃĩes.", + "permission_onboarding_permission_granted": "PermissÃŖo concedida! EstÃĄ tudo pronto.", + "permission_onboarding_permission_limited": "PermissÃŖo limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissÃĩes para fotos e vídeos nas configuraçÃĩes.", + "permission_onboarding_request": "O Immich requer autorizaÃ§ÃŖo para ver as suas fotos e vídeos.", "person": "Pessoa", "person_age_months": "{months, plural, one {# month} other {# months}} de idade", "person_age_year_months": "1 ano, {months, plural, one {# month} other {# months}} de idade", - "person_age_years": "{years, plural, one {# ano} other {# anos}} de idade", + "person_age_years": "{years, plural, other {# anos}} de idade", "person_birthdate": "Nasceu a {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", "person_recognized": "Pessoa reconhecida", + "person_selected": "Pessoa selecionada", "photo_shared_all_users": "Parece que jÃĄ partilhou as suas fotos com todos os utilizadores ou nÃŖo tem nenhum utilizador com quem partilhar.", "photos": "Fotos", "photos_and_videos": "Fotos & Vídeos", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de anos anteriores", + "photos_only": "Apenas fotografias", "pick_a_location": "Selecione uma localizaÃ§ÃŖo", "pick_custom_range": "Intervalo personalizado", "pick_date_range": "Selecione um intervalo de datas", @@ -1646,8 +1755,6 @@ "play_original_video_setting_description": "Preferir a reproduÃ§ÃŖo de vídeos originais em vez de vídeos transcodificados. Se o ficheiro original nÃŖo for compatível, este pode nÃŖo ser reproduzido corretamente.", "play_transcoded_video": "Reproduzir vídeo transcodificado", "please_auth_to_access": "Por favor autentique-se para aceder", - "plugin_method_filter_type": "Filtrar", - "plugin_method_filter_type_description": "Este mÊtodo pode filtrar eventos e condicionalmente impedir a execuÃ§ÃŖo de passos subsequentes", "port": "Porta", "preferences_settings_subtitle": "Gerir as preferÃĒncias da aplicaÃ§ÃŖo", "preferences_settings_title": "PreferÃĒncias", @@ -1664,11 +1771,11 @@ "privacy": "Privacidade", "profile": "Perfil", "profile_drawer_app_logs": "Registo", + "profile_drawer_client_server_up_to_date": "Cliente e Servidor atualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo sÃŗ de leitura ativado. Faça um toque longo no ícone do perfil do utilizador para sair.", "profile_image_of_user": "Imagem de perfil de {user}", "profile_picture_set": "Foto de perfil definida.", - "projection_type": "Tipo de ProjeÃ§ÃŖo", "public_album": "Álbum pÃēblico", "public_share": "Partilhar Publicamente", "purchase_account_info": "Apoiante", @@ -1687,9 +1794,10 @@ "purchase_individual_description_2": "Status de apoiante", "purchase_individual_title": "Particular", "purchase_input_suggestion": "Tem uma chave de produto? Insira a chave abaixo", + "purchase_license_subtitle": "Compre o Immich para apoiar o desenvolvimento contínuo do serviço", "purchase_lifetime_description": "Compra vitalícia", "purchase_option_title": "OPÇÕES DE COMPRA", - "purchase_panel_info_1": "O desenvolvimento do Immich requer muito tempo e esforço, e temos engenheiros a tempo inteiro a trabalhar para tornÃĄ-lo o melhor possível. A nossa missÃŖo Ê para que o software de cÃŗdigo aberto e as prÃĄticas de negÃŗcio Êticas se tornem numa fonte de rendimento sustentÃĄvel para os desenvolvedores e criar um ecossistema que respeite a privacidade dos utilizadores e que ofereça alternativas reais a serviços cloud exploratÃŗrios.", + "purchase_panel_info_1": "O desenvolvimento do Immich requer muito tempo e esforço, e temos engenheiros a tempo inteiro a trabalhar nele para melhorÃĄ-lo quanto possível. A nossa missÃŖo Ê para que o software de cÃŗdigo aberto e prÃĄticas de negÃŗcio Êticas se tornem numa fonte de rendimento sustentÃĄvel para os desenvolvedores e criar um ecossistema que respeite a privacidade dos utilizadores e que ofereça alternativas reais a serviços cloud explorativos.", "purchase_panel_info_2": "Uma vez que estamos empenhados em nÃŖo adicionar barreiras de pagamento, esta compra nÃŖo lhe darÃĄ quaisquer funcionalidades adicionais no Immich. Contamos com utilizadores como vocÃĒ para apoiar o desenvolvimento contínuo do Immich.", "purchase_panel_title": "Apoie o projeto", "purchase_per_server": "Por servidor", @@ -1719,19 +1827,19 @@ "reassigned_assets_to_new_person": "Reatribuído {count, plural, one {# ficheiro} other {# ficheiros}} a uma nova pessoa", "reassing_hint": "Atribuir ficheiros selecionados a uma pessoa existente", "recent": "Recentes", + "recent_albums": "Álbuns recentes", "recent_searches": "Pesquisas recentes", - "recently_added": "Adicionadas Recentemente", - "recently_added_body": "Aceda diretamente a tudo o que adicionou recentemente numa pÃĄgina dedicada.", - "recently_added_description": "Navegue pelos seus ficheiros ordenados pela data em que foram carregados para o Immich", + "recently_added": "Adicionados Recentemente", "recently_added_page_title": "Adicionado recentemente", - "recently_added_title": "Adicionado recentemente", - "recently_taken": "Tiradas recentemente", + "recently_taken": "Tirada recentemente", + "recently_taken_page_title": "Tiradas recentemente", "refresh": "Atualizar", "refresh_encoded_videos": "Atualizar vídeos codificados", "refresh_faces": "Atualizar rostos", "refresh_metadata": "Atualizar metadados", "refresh_thumbnails": "Atualizar miniaturas", "refreshed": "Atualizado", + "refreshes_every_file": "Recarrega todos os ficheiros jÃĄ existentes e novos", "refreshing_encoded_video": "A atualizar vídeo codificado", "refreshing_faces": "A atualizar rostos", "refreshing_metadata": "A atualizar metadados", @@ -1744,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Tem certeza de que deseja remover {count, plural, one {# ficheiro} other {# ficheiros}} deste link partilhado?", "remove_assets_title": "Remover ficheiros?", "remove_custom_date_range": "Remover intervalo de datas personalizado", - "remove_filter": "Remover filtro", + "remove_deleted_assets": "Remover ficheiros indisponíveis", "remove_from_album": "Remover do ÃĄlbum", "remove_from_album_action_prompt": "{count} removido(s) do ÃĄlbum", "remove_from_favorites": "Remover dos favoritos", @@ -1755,22 +1863,29 @@ "remove_memory": "Remover memÃŗria", "remove_photo_from_memory": "Remover foto desta memÃŗria", "remove_tag": "Remover marcador", + "remove_url": "Remover URL", "remove_user": "Remover utilizador", "removed_api_key": "Foi removida a Chave de API: {name}", "removed_from_archive": "Removido do arquivo", "removed_from_favorites": "Removido dos favoritos", "removed_from_favorites_count": "{count, plural, other {Removidos #}} dos favoritos", "removed_memory": "MemÃŗria removida", + "removed_photo_from_memory": "Foto removida da memÃŗria", "removed_tagged_assets": "Removida a etiqueta de {count, plural, one {# ficheiro} other {# ficheiros}}", "rename": "Mudar o nome", + "repair": "Reparar", + "repair_no_results_message": "Ficheiros em falta ou nÃŖo monitorizados irÃŖo aparecer aqui", + "replace_with_upload": "Substituir pelo ficheiro carregado", "repository": "RepositÃŗrio", "require_password": "Proteger com palavra-passe", + "require_user_to_change_password_on_first_login": "Obrigar utilizador a alterar a palavra-passe apÃŗs o primeiro início de sessÃŖo", "rescan": "Reanalisar", "reset": "Redefinir", "reset_password": "Redefinir palavra-passe", "reset_people_visibility": "Redefinir pessoas ocultas", "reset_pin_code": "Repor cÃŗdigo PIN", "reset_pin_code_description": "Se esqueceu o seu cÃŗdigo PIN, pode entrar em contacto com o administrador do servidor para o repor", + "reset_pin_code_success": "CÃŗdigo PIN redefinido com sucesso", "reset_pin_code_with_password": "Pode sempre repor o seu cÃŗdigo PIN com a sua palavra-passe", "reset_sqlite": "Reiniciar Base de Dados SQLite", "reset_sqlite_clear_app_data": "Limpar Dados", @@ -1784,10 +1899,12 @@ "resolved_all_duplicates": "Todos os itens duplicados resolvidos", "restore": "Restaurar", "restore_all": "Restaurar tudo", + "restore_trash_action_prompt": "{count} restaurados da reciclagem", "restore_user": "Restaurar utilizador", "restored_asset": "Ficheiro restaurado", "resume": "Continuar", "resume_paused_jobs": "Continuar {count, plural, one {# trabalho em pausa} other {# trabalhos em pausa}}", + "retry_upload": "Tentar carregar novamente", "review_duplicates": "Rever itens duplicados", "review_large_files": "Rever ficheiros grandes", "role": "FunÃ§ÃŖo", @@ -1795,6 +1912,7 @@ "role_viewer": "Visualizador", "running": "A executar", "save": "Guardar", + "save_to_gallery": "Salvar na galeria", "saved": "Guardado", "saved_api_key": "Chave de API guardada", "saved_profile": "Perfil guardado", @@ -1805,10 +1923,9 @@ "scan": "Analisar", "scan_all_libraries": "Analisar todas as bibliotecas", "scan_library": "Analisar", + "scan_settings": "OpçÃĩes de anÃĄlise", "scanning": "A analisar", "scanning_for_album": "A analisar por ÃĄlbum...", - "screencast_mode_description": "Mostrar indicadores de eventos de teclado e rato no ecrÃŖ", - "screencast_mode_title": "Alternar modo de transmissÃŖo para ecrÃŖ", "search": "Pesquisar", "search_albums": "Pesquisar ÃĄlbuns", "search_by_context": "Pesquisar por contexto", @@ -1816,8 +1933,6 @@ "search_by_description_example": "Dia de caminhada em Leiria", "search_by_filename": "Pesquisar por nome de ficheiro ou extensÃŖo", "search_by_filename_example": "por exemplo, IMG_1234.JPG ou PNG", - "search_by_full_path": "Procurar utilizando o caminho completo ou o nome da pasta", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - pode procurar por Projects, 3D, Printing, 2026, etc.", "search_by_ocr": "Pesquisar por OCR", "search_by_ocr_example": "GalÃŖo", "search_camera_lens_model": "Pesquisar por modelo de lente...", @@ -1837,6 +1952,7 @@ "search_filter_location_title": "Selecione a localizaÃ§ÃŖo", "search_filter_media_type": "Tipo de ficheiro", "search_filter_media_type_title": "Selecione o tipo do ficheiro", + "search_filter_ocr": "Pesquisar por OCR", "search_filter_people_title": "Selecionar pessoas", "search_filter_star_rating": "ClassificaÃ§ÃŖo", "search_filter_tags_title": "Selecionar etiquetas", @@ -1847,13 +1963,25 @@ "search_no_people_named": "Nenhuma pessoa chamada \"{name}\"", "search_no_result": "Nenhum resultado encontrado, tente pesquisar por algo diferente", "search_options": "OpçÃĩes de pesquisa", - "search_page_search_photos_videos": "Pesquise pelas suas fotos e vídeos", + "search_page_categories": "Categorias", + "search_page_motion_photos": "Fotos com movimento", + "search_page_no_objects": "Nenhuma informaÃ§ÃŖo de objeto disponível", + "search_page_no_places": "Nenhuma informaÃ§ÃŖo de local disponível", + "search_page_screenshots": "Capturas de tela", + "search_page_search_photos_videos": "Pesquise suas fotos e vídeos", + "search_page_selfies": "Auto-retratos (Selfies)", + "search_page_things": "Objetos", "search_page_view_all_button": "Ver tudo", + "search_page_your_activity": "A sua atividade", + "search_page_your_map": "O seu mapa", "search_people": "Pesquisar pessoas", "search_places": "Pesquisar lugares", "search_rating": "Pesquisar por classificaÃ§ÃŖo...", + "search_result_page_new_search_hint": "Nova Pesquisa", "search_settings": "Pesquisar nas DefiniçÃĩes", "search_state": "Pesquisar estado/distrito...", + "search_suggestion_list_smart_search_hint_1": "A pesquisa inteligente estÃĄ ativada por omissÃŖo. Para pesquisar por metadados, utilize a sintaxe ", + "search_suggestion_list_smart_search_hint_2": "m:a-sua-pesquisa", "search_tags": "Pesquisar etiquetas...", "search_timezone": "Pesquisar fuso horÃĄrio...", "search_type": "Tipo de pesquisa", @@ -1867,6 +1995,7 @@ "select_albums": "Selecionar ÃĄlbuns", "select_all": "Selecionar todos", "select_all_duplicates": "Selecionar todos os itens duplicados", + "select_all_in": "Selecionar tudo em {group}", "select_avatar_color": "Selecionar cor do avatar", "select_count": "{count, plural, one {Selecionar #} other {Selecionar #}}", "select_cutoff_date": "Selecionar data limite", @@ -1874,20 +2003,21 @@ "select_featured_photo": "Selecionar foto principal", "select_from_computer": "Selecionar a partir do computador", "select_keep_all": "Selecionar manter todos", + "select_library_owner": "Selecionar o dono da biblioteca", "select_new_face": "Selecionar novo rosto", "select_people": "Selecionar pessoas", "select_person": "Selecionar pessoa", "select_person_to_tag": "Selecione uma pessoa para etiquetar", "select_photos": "Selecionar fotos", - "select_quality": "Escolha a qualidade", "select_trash_all": "Selecionar todos para reciclagem", + "select_user_for_sharing_page_err_album": "Ocorreu um erro ao criar o ÃĄlbum", "selected": "Selecionados", "selected_count": "{count, plural, other {# selecionados}}", "selected_gps_coordinates": "Coordenadas GPS selecionadas", "send_message": "Enviar mensagem", "send_welcome_email": "Enviar E-mail de boas vindas", "server_endpoint": "URL do servidor", - "server_info_box_app_version": "VersÃŖo da aplicaÃ§ÃŖo", + "server_info_box_app_version": "VersÃŖo do app", "server_info_box_server_url": "URL do servidor", "server_offline": "Servidor Offline", "server_online": "Servidor Online", @@ -1913,31 +2043,42 @@ "setting_image_viewer_original_title": "Carregar imagem original", "setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resoluÃ§ÃŖo mÊdia. Desative para carregar diretamente o original ou usar apenas a miniatura.", "setting_image_viewer_preview_title": "Carregar imagem de prÊ-visualizaÃ§ÃŖo", + "setting_image_viewer_title": "Imagens", "setting_languages_apply": "Aplicar", "setting_languages_subtitle": "Alterar o idioma da aplicaÃ§ÃŖo", + "setting_notifications_notify_failures_grace_period": "Notificar erros da cÃŗpia de segurança em segundo plano: {duration}", + "setting_notifications_notify_hours": "{count} horas", + "setting_notifications_notify_immediately": "imediatamente", "setting_notifications_notify_minutes": "{count} minutos", + "setting_notifications_notify_never": "Nunca", "setting_notifications_notify_seconds": "{count} segundos", + "setting_notifications_single_progress_subtitle": "InformaçÃĩes detalhadas sobre o progresso do envio por ficheiro", + "setting_notifications_single_progress_title": "Mostrar progresso detalhado do backup em segundo plano", "setting_notifications_subtitle": "Ajuste as preferÃĒncias de notificaÃ§ÃŖo", + "setting_notifications_total_progress_subtitle": "Progresso do envio de ficheiro (concluídos/total)", + "setting_notifications_total_progress_title": "Mostrar progresso total do backup em segundo plano", "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", "setting_video_viewer_looping_title": "Repetir", "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o ficheiro original, mesmo se uma versÃŖo transcodificada estiver disponível. Pode causar interrupçÃĩes. Vídeos disponíveis localmente sÃŖo exibidos na qualidade original independentemente desta definiÃ§ÃŖo.", "setting_video_viewer_original_video_title": "Forçar vídeo original", "settings": "DefiniçÃĩes", + "settings_require_restart": "Reinicie o Immich para aplicar essa configuraÃ§ÃŖo", "settings_saved": "DefiniçÃĩes guardadas", "setup_pin_code": "Configurar um cÃŗdigo PIN", "share": "Partilhar", + "share_action_prompt": "Partilhados {count} ficheiros", + "share_add_photos": "Adicionar fotos", + "share_assets_selected": "{count} selecionados", "share_dialog_preparing": "Preparando...", "share_link": "Partilhar ligaÃ§ÃŖo", - "share_original": "Utilizar original (grande)", - "share_preview": "Utilizar miniatura (pequeno)", - "share_quality_body": "Toque e segure no botÃŖo de partilha para escolher a qualidade da imagem antes de a partilhar.", - "share_quality_title": "Escolha a qualidade de partilha", "shared": "Partilhado", "shared_album_activities_input_disable": "ComentÃĄrios desativados", "shared_album_activity_remove_content": "Deseja apagar esta atividade?", "shared_album_activity_remove_title": "Apagar atividade", "shared_album_section_people_action_error": "Erro ao sair/remover do ÃĄlbum", + "shared_album_section_people_action_leave": "Sair do ÃĄlbum", + "shared_album_section_people_action_remove_user": "Remover utilizador do ÃĄlbum", "shared_album_section_people_title": "PESSOAS", "shared_by": "Partilhado por", "shared_by_user": "Partilhado por {user}", @@ -1946,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "Enviados {current} de {total}", "shared_link_app_bar_title": "Links compartilhados", "shared_link_clipboard_copied_massage": "Copiado para a ÃĄrea de transferÃĒncia", + "shared_link_clipboard_text": "LigaÃ§ÃŖo: {link}\nPalavra-passe: {password}", "shared_link_create_error": "Erro ao criar o link compartilhado", - "shared_link_custom_url_description": "Aceda a este link partilhado com um nome de URL personalizado", - "shared_link_custom_url_title": "URL Personalizado", - "shared_link_custom_url_warning": "Aviso: Um nome de URL personalizado com uma barra oblíqua pode causar resultados inesperados.", + "shared_link_custom_url_description": "Aceda a este link partilhado com um URL personalizado", "shared_link_edit_description_hint": "Digite a descriÃ§ÃŖo do compartilhamento", "shared_link_edit_expire_after_option_day": "1 dia", "shared_link_edit_expire_after_option_days": "{count} dias", @@ -1973,7 +2113,7 @@ "shared_link_expires_seconds": "Expira {count} segundos", "shared_link_individual_shared": "Compartilhamento Ãēnico", "shared_link_info_chip_metadata": "EXIF", - "shared_link_manage_links": "Gerir links partilhados", + "shared_link_manage_links": "Gerenciar links compartilhados", "shared_link_options": "OpçÃĩes de link partilhado", "shared_link_password_description": "Exigir uma palavra-passe para aceder a este link partilhado", "shared_links": "Links partilhados", @@ -1983,6 +2123,12 @@ "shared_with_partner": "Partilhado com {partner}", "sharing": "Partilha", "sharing_enter_password": "Por favor, insira a palavra-passe para ver esta pÃĄgina.", + "sharing_page_album": "Álbuns partilhados", + "sharing_page_description": "Crie ÃĄlbuns partilhados para partilhar fotos e vídeos com pessoas da sua rede.", + "sharing_page_empty_list": "LISTA VAZIA", + "sharing_sidebar_description": "Exibe o link para Partilhar na barra lateral", + "sharing_silver_appbar_create_shared_album": "Criar ÃĄlbum partilhado", + "sharing_silver_appbar_share_partner": "Compartilhar com parceiro", "shift_to_permanent_delete": "Pressione ⇧ para eliminar o ficheiro permanentemente", "show_album_options": "Exibir opçÃĩes do ÃĄlbum", "show_albums": "Mostrar ÃĄlbuns", @@ -1994,9 +2140,7 @@ "show_in_timeline": "Exibir na linha do tempo", "show_in_timeline_setting_description": "Exibe fotos e vídeos deste utilizador na sua linha do tempo", "show_keyboard_shortcuts": "Exibir atalhos do teclado", - "show_less": "Mostrar menos", "show_metadata": "Mostrar metadados", - "show_more_fields": "{count, plural, one {Mostrar mais # campo} other {Mostrar mais # campos}}", "show_or_hide_info": "Exibir ou ocultar informaçÃĩes", "show_password": "Mostrar palavra-passe", "show_person_options": "Exibir opçÃĩes da pessoa", @@ -2004,7 +2148,6 @@ "show_schema": "Mostrar esquema", "show_search_options": "Mostrar opçÃĩes de pesquisa", "show_shared_links": "Mostrar links partilhados", - "show_slideshow_metadata_overlay": "Sobrepor informaçÃĩes da imagem", "show_slideshow_transition": "Mostrar transiçÃĩes no Modo de ApresentaÃ§ÃŖo", "show_supporter_badge": "Emblema de apoiante", "show_supporter_badge_description": "Mostrar um emblema de apoiante", @@ -2016,25 +2159,18 @@ "sign_out": "Terminar sessÃŖo", "sign_up": "Criar conta", "size": "Tamanho", - "skip": "Saltar", "skip_to_content": "Saltar para o conteÃēdo", "skip_to_folders": "Saltar para pastas", "skip_to_tags": "Saltar para as etiquetas", "slideshow": "ApresentaÃ§ÃŖo", - "slideshow_body": "Descontraia e veja as suas fotos a serem reproduzidas numa apresentaÃ§ÃŖo em ecrÃŖ inteiro.", - "slideshow_metadata_overlay_mode": "Sobrepor conteÃēdo", - "slideshow_metadata_overlay_mode_description_only": "Apenas descriÃ§ÃŖo", - "slideshow_metadata_overlay_mode_full": "Completo", "slideshow_repeat": "Repetir apresentaÃ§ÃŖo de diapositivos", "slideshow_repeat_description": "Repetir do inicio quando a apresentaÃ§ÃŖo acabar", "slideshow_settings": "DefiniçÃĩes de apresentaÃ§ÃŖo", - "slideshow_title": "ApresentaÃ§ÃŖo de diapositivos", - "smart_album": "Álbum inteligente", - "some_assets_already_have_a_location_warning": "Alguns dos ficheiros selecionados jÃĄ possuem localizaÃ§ÃŖo", "sort_albums_by": "Ordenar ÃĄlbuns por...", "sort_created": "Data de criaÃ§ÃŖo", "sort_items": "NÃēmero de itens", "sort_modified": "Data de modificaÃ§ÃŖo", + "sort_newest": "A foto mais recente", "sort_oldest": "Foto mais antiga", "sort_people_by_similarity": "Ordenar pessoas por semelhança", "sort_recent": "Foto mais recente", @@ -2043,6 +2179,7 @@ "stack": "Empilhar", "stack_action_prompt": "{count} empilhados", "stack_duplicates": "Empilhar itens duplicados", + "stack_select_one_photo": "Selecione uma foto principal para a pilha", "stack_selected_photos": "Empilhar fotos selecionadas", "stacked_assets_count": "Empilhado {count, plural, one {# ficheiro} other {# ficheiros}}", "stacktrace": "Stacktrace", @@ -2051,10 +2188,6 @@ "start_date_before_end_date": "A data de início deve ser anterior à data de fim", "state": "Estado/Distrito", "status": "Estado", - "step_delete": "Apagar passo", - "step_delete_confirm": "Tem a certeza de que quer apagar este passo?", - "step_details": "Detalhes do Passo", - "steps": "Passos", "stop_casting": "Parar transmissÃŖo", "stop_motion_photo": "Parar foto em movimento", "stop_photo_sharing": "Deixar de partilhar as suas fotos?", @@ -2075,18 +2208,18 @@ "swap_merge_direction": "Alternar direÃ§ÃŖo da uniÃŖo", "sync": "Sincronizar", "sync_albums": "Sincronizar ÃĄlbuns", + "sync_albums_manual_subtitle": "Sincronizar todas as fotos e vídeos enviados para o ÃĄlbum de backup selecionado", "sync_local": "SincronizaÃ§ÃŖo Local", "sync_remote": "SincronizaÃ§ÃŖo Remota", "sync_status": "Estado da sincronizaÃ§ÃŖo", "sync_status_subtitle": "Ver e gerir o sistema de sincronizaÃ§ÃŖo", "sync_upload_album_setting_subtitle": "Crie e envie suas fotos e vídeos para o ÃĄlbum selecionado no Immich", - "system_theme": "Tema do sistema", - "system_theme_command_description": "Utilizar o tema do sistema ({value})", "tag": "Etiqueta", "tag_assets": "Etiquetar ficheiros", "tag_created": "Criada a etiqueta {tag}", "tag_face": "Etiquetar rosto", "tag_feature_description": "A mostrar fotos e videos agrupados por tÃŗpicos lÃŗgicos de etiquetas", + "tag_not_found_question": "NÃŖo consegue encontrar a etiqueta? Crie uma nova etiqueta.", "tag_people": "Etiquetar Pessoas", "tag_updated": "Atualizada a etiqueta: {tag}", "tagged_assets": "Etiquetado {count, plural, one {# ficheiros} other {# ficheiros}}", @@ -2101,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Quantidade de ficheiros por linha ({count})", "theme_setting_colorful_interface_subtitle": "Aplica a cor primÃĄria ao fundo.", "theme_setting_colorful_interface_title": "Interface colorida", + "theme_setting_image_viewer_quality_subtitle": "Ajuste a qualidade do visualizador de imagens detalhadas", + "theme_setting_image_viewer_quality_title": "Qualidade do visualizador de imagens", "theme_setting_primary_color_subtitle": "Selecione a cor primÃĄria, utilizada nas açÃĩes principais e nos realces.", "theme_setting_primary_color_title": "Cor primÃĄria", "theme_setting_system_primary_color_title": "Use a cor do sistema", "theme_setting_system_theme_switch": "AutomÃĄtico (Seguir a configuraÃ§ÃŖo do sistema)", + "theme_setting_theme_subtitle": "Escolha a configuraÃ§ÃŖo do tema da aplicaÃ§ÃŖo", + "theme_setting_three_stage_loading_subtitle": "O carregamento em trÃĒs estÃĄgios pode aumentar o desempenho do carregamento, mas causa uma carga de rede significativamente maior", + "theme_setting_three_stage_loading_title": "Habilitar carregamento em trÃĒs estÃĄgios", "then": "Depois", "they_will_be_merged_together": "Eles serÃŖo unidos", "third_party_resources": "Recursos de terceiros", @@ -2121,7 +2259,7 @@ "to_parent": "Subir um nível", "to_select": "seleccionar", "to_trash": "Reciclagem", - "toggle_settings": "Alternar definiçÃĩes", + "toggle_settings": "Alternar configuraçÃĩes", "toggle_theme_description": "Alternar tema", "total": "Total", "total_usage": "Total utilizado", @@ -2130,17 +2268,23 @@ "trash_all": "Mover todos para a reciclagem", "trash_count": "Reciclar {count, number}", "trash_delete_asset": "Eliminar ficheiro", + "trash_emptied": "Lixeira esvaziada", "trash_no_results_message": "Fotos e vídeos enviados para a reciclagem aparecem aqui.", "trash_page_delete_all": "Excluir tudo", + "trash_page_empty_trash_dialog_content": "Deseja esvaziar a reciclagem? Estes ficheiros serÃŖo apagados permanentemente do Immich", "trash_page_info": "Ficheiros na reciclagem irÃŖo ser eliminados permanentemente apÃŗs {days} dias", + "trash_page_no_assets": "Lixeira vazia", + "trash_page_restore_all": "Restaurar tudo", + "trash_page_select_assets_btn": "Selecionar ficheiros", + "trash_page_title": "Reciclagem ({count})", "trashed_items_will_be_permanently_deleted_after": "Os itens da reciclagem sÃŖo eliminados permanentemente apÃŗs {days, plural, one {# dia} other {# dias}}.", "trigger": "Ativador", - "trigger_asset_metadata_extraction": "ExtraÃ§ÃŖo de Metadados do Ficheiro", - "trigger_asset_metadata_extraction_description": "Acionado quando os metadados EXIF de um ficheiro forem extraídos", - "trigger_asset_uploaded": "Carregamento de ficheiro", + "trigger_asset_uploaded": "Ficheiro Carregado", "trigger_asset_uploaded_description": "Ativado quando um novo ficheiro Ê carregado", + "trigger_description": "Um evento que irÃĄ começar o fluxo de trabalho", "trigger_person_recognized": "Pessoa Reconhecida", - "trigger_person_recognized_description": "Acionado quando uma pessoa for reconhecida", + "trigger_person_recognized_description": "Ativado quando uma pessoa for detetada", + "trigger_type": "Tipo de ativador", "troubleshoot": "Diagnosticar problemas", "type": "Tipo", "unable_to_change_pin_code": "NÃŖo foi possível alterar o cÃŗdigo PIN", @@ -2156,7 +2300,6 @@ "unknown": "Desconhecido", "unknown_country": "País desconhecido", "unknown_date": "Data desconhecida", - "unknown_schema": "Esquema desconhecido", "unknown_year": "Ano desconhecido", "unlimited": "Ilimitado", "unlink_motion_video": "Remover relaÃ§ÃŖo com video animado", @@ -2169,20 +2312,23 @@ "unsaved_change": "AlteraÃ§ÃŖo nÃŖo guardada", "unselect_all": "Limpar seleÃ§ÃŖo", "unselect_all_duplicates": "Remover seleÃ§ÃŖo de todos os itens duplicados", + "unselect_all_in": "Remover seleÃ§ÃŖo de {group}", "unstack": "Desempilhar", "unstack_action_prompt": "{count} desempilhados", "unstacked_assets_count": "Desempilhados {count, plural, one {# ficheiro} other {# ficheiros}}", "unsupported_field_type": "Tipo de campo nÃŖo suportado", - "unsupported_file_type": "O ficheiro {file} nÃŖo pôde ser carregado porque o tipo de ficheiro {type} nÃŖo Ê suportado.", + "unsupported_file_type": "O ficheiro {file} nÃŖo pôde ser carregado porque o seu tipo de ficheiro {type} nÃŖo Ê suportado.", "untagged": "Sem etiqueta", + "untitled_workflow": "Fluxo de trabalho sem nome", "up_next": "A seguir", "update_location_action_prompt": "Atualize a localizaÃ§ÃŖo de {count} ficheiros selecionados com:", "updated_at": "Atualizado a", "updated_password": "Palavra-passe atualizada", "upload": "Carregar", "upload_concurrency": "Carregamentos em simultÃĸneo", - "upload_day_count": "{date}: {count, plural, one {# ficheiro carregado} other {# ficheiros carregados}}", "upload_details": "Detalhes do Carregamento", + "upload_dialog_info": "Deseja realizar uma cÃŗpia de segurança dos ficheiros selecionados para o servidor?", + "upload_dialog_title": "Enviar ficheiro", "upload_error_with_count": "Erro ao carregar {count, plural, one {# ficheiro} other {# ficheiros}}", "upload_errors": "Envio completo com {count, plural, one {# erro} other {# erros}}, atualize a pÃĄgina para ver os novos ficheiros enviados.", "upload_finished": "Carregamento acabado", @@ -2191,14 +2337,10 @@ "upload_status_duplicates": "Duplicados", "upload_status_errors": "Erros", "upload_status_uploaded": "Enviado", - "upload_success": "Carregamento realizado com sucesso, atualize a pÃĄgina para ver os novos ficheiros.", - "upload_to_album_body": "Para os utilizadores que nÃŖo utilizam a funcionalidade de carregamento manual, podem agora adicionar fotos guardadas localmente diretamente a um ÃĄlbum enquanto as carrega, jÃĄ nÃŖo Ê necessÃĄrio carregÃĄ-las primeiro para depois adicionar ao ÃĄlbum.", - "upload_to_album_title": "Carregue diretamente para um ÃĄlbum", + "upload_success": "Carregamento realizado com sucesso, atualize a pÃĄgina para ver os novos ficheiros carregados.", "upload_to_immich": "Enviar para o Immich ({count})", "uploading": "Enviando", "uploading_media": "A carregar media", - "uploads": "Carregamentos", - "uploads_count": "{count, plural, one {# carregamento} other {# carregamentos}}", "url": "URL", "usage": "UtilizaÃ§ÃŖo", "use_biometric": "Utilizar dados biomÊtricos", @@ -2206,7 +2348,6 @@ "use_browser_locale_description": "Formatar datas, horas e nÃēmeros com base na localizaÃ§ÃŖo do browser", "use_current_connection": "Utilizar a ligaÃ§ÃŖo atual", "use_custom_date_range": "Utilizar um intervalo de datas personalizado", - "use_template": "Usar modelo", "user": "Utilizador", "user_has_been_deleted": "Este utilizador for eliminado.", "user_id": "ID do utilizador", @@ -2216,6 +2357,7 @@ "user_privacy": "Privacidade de Utilizador", "user_purchase_settings": "Comprar", "user_purchase_settings_description": "Gerir a sua compra", + "user_role_set": "Definir {user} como {role}", "user_usage_detail": "Detalhes de utilizaÃ§ÃŖo por utilizador", "user_usage_stats": "Estatísticas de utilizaÃ§ÃŖo de conta", "user_usage_stats_description": "Ver estatísticas de utilizaÃ§ÃŖo de conta", @@ -2225,18 +2367,19 @@ "utilities": "Ferramentas", "validate": "Validar", "validate_endpoint_error": "Digite uma URL vÃĄlida", + "validation_error": "Erro de validaÃ§ÃŖo", "variables": "VariÃĄveis", "version": "VersÃŖo", "version_announcement_closing": "O seu amigo, Alex", - "version_announcement_message": "OlÃĄ! EstÃĄ disponível uma nova versÃŖo do Immich. Por favor leia as notas de lançamento para garantir que as suas definiçÃĩes estÃŖo atualizadas e para evitar quaisquer erros, especialmente se usar o WatchTower ou qualquer mecanismo que faça atualizaÃ§ÃŖo automÃĄtica do Immich.", + "version_announcement_message": "OlÃĄ! EstÃĄ disponível uma nova versÃŖo do Immich. Por favor leia as notas de lançamento para garantir que as suas configuraçÃĩes estÃŖo atualizadas e para evitar quaisquer erros, especialmente se usar o WatchTower ou qualquer mecanismo que lide com a atualizaÃ§ÃŖo automÃĄtica do Immich.", "version_history": "HistÃŗrico de versÃĩes", - "version_history_item": "VersÃŖo {version} instalada em {date}", + "version_history_item": "Instalado {version} em {date}", "video": "Vídeo", "video_hover_setting": "Reproduzir vídeo em miniatura quando passar com o cursor por cima", "video_hover_setting_description": "Reproduzir vídeo em miniatura quando o cursor estÃĄ sobre o item. Mesmo quando estÃĄ desativado, a reproduÃ§ÃŖo ainda pode ser iniciada passando sobre o ícone de reproduzir.", - "video_quality": "Qualidade do vídeo", "videos": "Vídeos", "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", + "videos_only": "Apenas vídeos", "view": "Ver", "view_album": "Ver Álbum", "view_all": "Ver tudo", @@ -2245,29 +2388,29 @@ "view_details": "Ver Detalhes", "view_in_timeline": "Ver na linha do tempo", "view_link": "Ver link", + "view_links": "Ver links", "view_name": "Ver", "view_next_asset": "Ver prÃŗximo ficheiro", "view_previous_asset": "Ver ficheiro anterior", "view_qr_code": "Ver cÃŗdigo QR", "view_similar_photos": "Ver fotos similares", "view_stack": "Ver pilha", + "view_user": "Ver utilizador", "viewer_remove_from_stack": "Remover da pilha", + "viewer_stack_use_as_main_asset": "Usar como foto principal", + "viewer_unstack": "Desempilhar", "visibility": "Visibilidade", "visibility_changed": "Visibilidade alterada para {count, plural, one {# pessoa} other {# pessoas}}", "visual": "Visual", + "visual_builder": "Construtor visual", "waiting": "Em fila", "waiting_count": "Em espera: {count}", "warning": "Aviso", "week": "Semana", "welcome": "Bem-vindo(a)", "welcome_to_immich": "Bem-vindo(a) ao Immich", - "whats_new": "O que hÃĄ de novo", - "whats_new_settings_subtitle": "Veja o que hÃĄ de novo no Immich", - "whats_new_version": "VersÃŖo {version}", - "when": "Quando", "width": "Largura", "wifi_name": "Nome da rede Wi-Fi", - "workflow": "Fluxo de trabalho", "workflow_delete_prompt": "Tem a certeza de que quer eliminar este fluxo de trabalho?", "workflow_deleted": "Fluxo de trabalho eliminado", "workflow_description": "DescriÃ§ÃŖo do fluxo de trabalho", @@ -2277,17 +2420,17 @@ "workflow_name": "Nome do fluxo de trabalho", "workflow_navigation_prompt": "Tem a certeza de que quer sair sem guardar as alteraçÃĩes?", "workflow_summary": "Resumo do fluxo de trabalho", - "workflow_templates": "Modelos de fluxos de trabalho", "workflow_update_success": "Fluxo de trabalho atualizado com sucesso", + "workflow_updated": "Fluxo de trabalho atualizado", "workflows": "Fluxos de trabalho", "workflows_help_text": "Fluxos de trabalho automatizam açÃĩes nos seus ficheiros baseados em ativadores e filtros", "wrong_pin_code": "CÃŗdigo PIN errado", - "x_of_total": "{x}/{total}", "year": "Ano", "years_ago": "HÃĄ {years, plural, one {# ano} other {# anos}}", "yes": "Sim", "you_dont_have_any_shared_links": "NÃŖo tem links partilhados", "your_wifi_name": "Nome da sua rede Wi-Fi", "zero_to_clear_rating": "Carregue no 0 para retirar a classificaÃ§ÃŖo", - "zoom_image": "Ampliar/Reduzir imagem" + "zoom_image": "Ampliar/Reduzir imagem", + "zoom_to_bounds": "Aproximar aos limites" } diff --git a/i18n/pt_BR.json b/i18n/pt_BR.json index 86e97d5db4..20d376289f 100644 --- a/i18n/pt_BR.json +++ b/i18n/pt_BR.json @@ -10,31 +10,39 @@ "active": "Em execuÃ§ÃŖo", "active_count": "Ativo: {count}", "activity": "Atividade", + "activity_changed": "Atividade foi {enabled, select, true {ativada} other {desativada}}", "add": "Adicionar", "add_a_description": "Adicionar uma descriÃ§ÃŖo", "add_a_location": "Adicionar uma localizaÃ§ÃŖo", "add_a_name": "Adicionar um nome", "add_a_title": "Adicionar um título", "add_action": "Adicionar aÃ§ÃŖo", + "add_action_description": "Clique para adicionar uma aÃ§ÃŖo", "add_assets": "Adicionar arquivos", "add_birthday": "Definir aniversÃĄrio", "add_endpoint": "Adicionar URL", "add_exclusion_pattern": "Adicionar padrÃŖo de exclusÃŖo", + "add_filter": "Adicionar filtro", + "add_filter_description": "Clique para adicional uma condiÃ§ÃŖo no filtro", "add_location": "Adicionar local", + "add_more_users": "Adicionar mais usuÃĄrios", "add_partner": "Adicionar parceiro", + "add_path": "Adicionar caminho", "add_photos": "Adicionar fotos", - "add_step": "Adicionar Etapa", "add_tag": "Adicionar Marcador", "add_to": "Adicionar aâ€Ļ", "add_to_album": "Adicionar ao ÃĄlbum", "add_to_album_bottom_sheet_added": "Adicionado ao {album}", "add_to_album_bottom_sheet_already_exists": "JÃĄ existe em {album}", "add_to_album_bottom_sheet_some_local_assets": "Alguns arquivos locais nÃŖo puderam ser adicionados ao ÃĄlbum", + "add_to_album_toggle": "Alternar a seleÃ§ÃŖo de {album}", "add_to_albums": "Adicionar aos ÃĄlbuns", "add_to_albums_count": "Adicionar aos ÃĄlbuns ({count})", "add_to_bottom_bar": "Incluir em", + "add_to_shared_album": "Adicionar ao ÃĄlbum compartilhado", "add_upload_to_stack": "Adicionar ao grupo", "add_url": "Adicionar URL", + "add_workflow_step": "Adicionar uma etapa no fluxo", "added_to_archive": "Adicionado ao arquivo", "added_to_favorites": "Adicionado aos favoritos", "added_to_favorites_count": "{count, plural, one {{count, number} adicionado aos favoritos} other {{count, number} adicionados aos favoritos}}", @@ -54,9 +62,9 @@ "backup_onboarding_2_description": "CÃŗpias em dispositivos diferentes. Incluindo os arquivos originais e o backup deles.", "backup_onboarding_3_description": "CÃŗpias completas de seus dados, com os arquivos originais. Inclusive 1 cÃŗpia externa e 2 cÃŗpias locais.", "backup_onboarding_description": "A estratÊgia de backup 3-2-1 Ê recomendada para proteger seus dados. Para uma soluÃ§ÃŖo completa de backup, vocÃĒ deve manter cÃŗpias de suas fotos, vídeos e backups do banco de dados do Immich.", - "backup_onboarding_footer": "Para mais informaçÃĩes sobre o backup do Immich, veja a documentaÃ§ÃŖo em documentaÃ§ÃŖo.", + "backup_onboarding_footer": "Para mais informaçÃĩes sobre o backup do Immich, leia a documentaÃ§ÃŖo.", "backup_onboarding_parts_title": "O backup 3-2-1 Ê definido por:", - "backup_onboarding_title": "cÃŗpias de segurança", + "backup_onboarding_title": "Backups", "backup_settings": "ConfiguraçÃĩes de backup", "backup_settings_description": "Gerenciar configuraçÃĩes de backup do banco de dados.", "cleared_jobs": "Tarefas removidas de: {job}", @@ -73,7 +81,6 @@ "cron_expression_description": "Defina o intervalo de anÃĄlise no formato Cron. Para mais informaçÃĩes, por favor veja o Crontab Guru", "cron_expression_presets": "SugestÃĩes de expressÃŖo Cron", "disable_login": "Desabilitar login", - "download_csv": "Baixar CSV", "duplicate_detection_job_description": "Execute o aprendizado de mÃĄquina em arquivos para detectar imagens semelhantes. Depende da Pesquisa Inteligente", "exclusion_pattern_description": "Os padrÃĩes de exclusÃŖo permitem ignorar arquivos e pastas ao escanear sua biblioteca. Isso Ê Ãētil se vocÃĒ tiver pastas que contenham arquivos que nÃŖo deseja importar, como arquivos RAW.", "export_config_as_json_description": "Baixar as configuraçÃĩes de sistema atuais para um arquivo JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Qualidade da miniatura, de 1 a 100. Maior Ê melhor, mas produz arquivos maiores e pode reduzir a velocidade do aplicativo.", "image_thumbnail_title": "ConfiguraçÃĩes de Miniaturas", "import_config_from_json_description": "Importar configuraÃ§ÃŖo do sistema enviando um arquivo JSON de configuraÃ§ÃŖo", - "integrity_checks_checksum_files": "Arquivos de checksum", - "integrity_checks_checksum_files_description": "Configure a verificaÃ§ÃŖo de checksum", - "integrity_checks_checksum_files_enable_description": "Ative a verificaÃ§ÃŖo de checksum", - "integrity_checks_checksum_files_percentage_limit": "Limite percentual", - "integrity_checks_checksum_files_percentage_limit_description": "Configure a porcentagem mÃĄxima entre 0,01 e 1 para a proporÃ§ÃŖo da verificaÃ§ÃŖo de checksum a ser executada em cada intervalo.", - "integrity_checks_checksum_files_time_limit": "Tempo limite", - "integrity_checks_checksum_files_time_limit_description": "Configure a duraÃ§ÃŖo mÃĄxima pela qual a verificaÃ§ÃŖo de checksum deve ser executada em cada intervalo. (ms)", - "integrity_checks_missing_files": "Arquivos ausentes", - "integrity_checks_missing_files_description": "Configure a frequÃĒncia e habilite ou desabilite a verificaÃ§ÃŖo de arquivos ausentes", - "integrity_checks_missing_files_enable_description": "Ativar a verificaÃ§ÃŖo de arquivos ausentes", - "integrity_checks_settings": "VerificaçÃĩes de integridade", - "integrity_checks_settings_description": "Gerenciar intervalos de verificaçÃĩes de integridade", - "integrity_checks_untracked_files": "Arquivos nÃŖo rastreados", - "integrity_checks_untracked_files_description": "Configure a frequÃĒncia e habilite ou desabilite a verificaÃ§ÃŖo de arquivos nÃŖo rastreados", - "integrity_checks_untracked_files_enable_description": "Habilitar a verificaÃ§ÃŖo de arquivos nÃŖo rastreados", "job_concurrency": "{job} simultÃĸneo", "job_created": "Tarefa criada", "job_not_concurrency_safe": "Esta tarefa nÃŖo Ê compatível com simultaneidade.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Habilitar a Pesquisa Inteligente", "machine_learning_smart_search_enabled_description": "Se desativado, as imagens nÃŖo serÃŖo codificadas para pesquisa inteligente.", "machine_learning_url_description": "A URL do servidor de aprendizado de mÃĄquina. Se mais de uma URL for fornecida, elas serÃŖo tentadas, uma de cada vez e na ordem indicada, atÊ que uma responda com sucesso. Servidores que nÃŖo responderem serÃŖo ignorados temporariamente atÊ voltarem a estar conectados.", - "maintenance_backup_management": "Gerenciamento de backup", "maintenance_delete_backup": "Excluir Backup", "maintenance_delete_backup_description": "Este arquivo serÃĄ excluído de forma irreversível.", "maintenance_delete_error": "Falha ao excluir o backup.", - "maintenance_integrity_check": "Verificar", - "maintenance_integrity_check_all": "Verificar tudo", - "maintenance_integrity_checksum_mismatch": "Checksum nÃŖo corresponde", - "maintenance_integrity_checksum_mismatch_description": "Arquivos cujo o checksum atual nÃŖo corresponde ao checksum que Immich armazenou no banco de dados.", - "maintenance_integrity_checksum_mismatch_job": "Verificar se hÃĄ erros de checksum", - "maintenance_integrity_checksum_mismatch_refresh_job": "Atualizar # de checksum sem correspondÃĒncia", - "maintenance_integrity_missing_file": "Arquivos nÃŖo encontrados", - "maintenance_integrity_missing_file_description": "Arquivos que Immich rastreou em seu banco de dados, mas que nÃŖo existem no sistema de arquivos.", - "maintenance_integrity_missing_file_job": "Verificar se hÃĄ arquivos nÃŖo encontrados", - "maintenance_integrity_missing_file_refresh_job": "Atualizar # de arquivos nÃŖo encontrados", - "maintenance_integrity_report": "RelatÃŗrio de integridade", - "maintenance_integrity_untracked_file": "Arquivos nÃŖo rastreados", - "maintenance_integrity_untracked_file_description": "Arquivos nÃŖo rastreados dentro dos diretÃŗrios do Immich.", - "maintenance_integrity_untracked_file_job": "Verificar se hÃĄ arquivos nÃŖo rastreados", - "maintenance_integrity_untracked_file_refresh_job": "Atualizar # de arquivos nÃŖo rastreados", "maintenance_restore_backup": "Restaurar Backup", "maintenance_restore_backup_description": "O Immich serÃĄ apagado e restaurado a partir do backup escolhido. Um backup serÃĄ criado antes de continuar.", "maintenance_restore_backup_different_version": "Este backup foi criado com uma versÃŖo diferente do Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Habilitar notificaçÃĩes por e-mail", "notification_settings": "ConfiguraçÃĩes de notificaÃ§ÃŖo", "notification_settings_description": "Gerenciar configuraçÃĩes de notificaÃ§ÃŖo, incluindo e-mail", - "oauth_allow_insecure_requests": "Permitir conexÃĩes inseguras", - "oauth_allow_insecure_requests_description": "AVISO: Isto irÃĄ desativar a validaÃ§ÃŖo de certificados TLS para conexÃĩes OAuth e poderÃĄ ficar exposto a ataques do tipo MITM (\"Man-in-the-middle\").", "oauth_auto_launch": "InicializaÃ§ÃŖo automÃĄtica", "oauth_auto_launch_description": "Inicie o fluxo de login do OAuth automaticamente ao navegar atÊ a pÃĄgina de login", "oauth_auto_register": "Registro automÃĄtico", @@ -300,11 +274,9 @@ "oauth_button_text": "BotÃŖo de texto", "oauth_client_secret_description": "ObrigatÃŗrio para cliente confidencial ou quando o PKCE (Proof Key for Code Exchange) nÃŖo Ê suportado para cliente pÃēblico.", "oauth_enable_description": "Faça login com OAuth", - "oauth_end_session_url_description": "Redirecionar o usuÃĄrio para esta URI apÃŗs saírem da conta.", "oauth_mobile_redirect_uri": "URI de redirecionamento mÃŗvel", "oauth_mobile_redirect_uri_override": "SubstituiÃ§ÃŖo de URI de redirecionamento mÃŗvel", "oauth_mobile_redirect_uri_override_description": "Ative quando o provedor do OAuth nÃŖo suportar uma URI de aplicativo, por exemplo ''{callback}''", - "oauth_prompt_description": "ParÃĸmetro da solicitaÃ§ÃŖo (exemplo: select_account, login, consent)", "oauth_role_claim": "DeclaraÃ§ÃŖo de funÃ§ÃŖo", "oauth_role_claim_description": "DÃĄ permissÃĩes de administrador baseado no valor desta declaraÃ§ÃŖo. A declaraÃ§ÃŖo pode conter os valores 'user' ou 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Atualizando todas as bibliotecas", "registration": "Registro de Administrador", "registration_description": "Como vocÃĒ ÃŠ o primeiro usuÃĄrio no sistema, serÃĄ designado como o Administrador e serÃĄ responsÃĄvel pelas tarefas administrativas. VocÃĒ tambÊm poderÃĄ criar usuÃĄrios adicionais.", - "release_channel_release_candidate": "Candidato à Lançamento", - "release_channel_stable": "EstÃĄvel", "remove_failed_jobs": "Remover tarefas que falharam", "require_password_change_on_login": "Exigir que o usuÃĄrio altere a senha no primeiro login", "reset_settings_to_default": "Redefinir as configuraçÃĩes para o padrÃŖo", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Aplica-se apenas a VAAPI e QSV. Define o nÃŗ dri usado para transcodificaÃ§ÃŖo de hardware.", "transcoding_preset_preset": "Predefinido (-preset)", "transcoding_preset_preset_description": "Velocidade de compressÃŖo. As opçÃĩes mais lentas produzem arquivos menores e aumentam a qualidade. VP9 ignora as velocidades acima de 'mais rÃĄpida'.", - "transcoding_realtime": "TranscodificaÃ§ÃŖo em tempo real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permite que a transcodificaÃ§ÃŖo seja realizada em tempo real enquanto o vídeo estÃĄ sendo transmitido. Permite a mudança de qualidade, mas pode causar maior latÃĒncia e artefatos visuais durante a reproduÃ§ÃŖo, dependendo dos recursos de processamento e renderizaÃ§ÃŖo do servidor.", - "transcoding_realtime_enabled": "Ativar transcodificaÃ§ÃŖo em tempo real", - "transcoding_realtime_enabled_description": "Se desativada, a opÃ§ÃŖo farÃĄ com que o servidor se recuse a iniciar novas sessÃĩes de transcodificaÃ§ÃŖo em tempo real.", - "transcoding_realtime_resolutions": "ResoluçÃĩes", - "transcoding_realtime_resolutions_description": "As resoluçÃĩes oferecidas para transcodificaÃ§ÃŖo em tempo real. ResoluçÃĩes mais altas podem causar problemas de reproduÃ§ÃŖo se o servidor nÃŖo conseguir transcodificÃĄ-las com rapidez suficiente.", - "transcoding_realtime_video_codecs": "Codecs de vídeo", - "transcoding_realtime_video_codecs_description": "Os codecs de vídeo oferecidos para transcodificaÃ§ÃŖo em tempo real. Os clientes escolherÃŖo a melhor opÃ§ÃŖo suportada por eles durante a reproduÃ§ÃŖo. O AV1 Ê mais eficiente que o HEVC, que Ê mais eficiente que o H.264. Ao utilizar aceleraÃ§ÃŖo de hardware, selecione apenas os codecs que o acelerador consegue codificar. Ao utilizar transcodificaÃ§ÃŖo via software, observe que o H.264 Ê mais rÃĄpido que o AV1, que Ê mais rÃĄpido que o HEVC.", "transcoding_reference_frames": "Quadros de referÃĒncia", "transcoding_reference_frames_description": "O nÃēmero de quadros a serem referenciados ao compactar um determinado quadro. Valores mais altos melhoram a eficiÃĒncia da compactaÃ§ÃŖo, mas retardam a codificaÃ§ÃŖo. 0 define esse valor automaticamente.", "transcoding_required_description": "Somente vídeos que nÃŖo estejam em um formato aceito", @@ -478,8 +440,6 @@ "user_settings_description": "Gerenciar configuraçÃĩes do usuÃĄrio", "user_successfully_removed": "UsuÃĄrio {email} foi removido com sucesso.", "users_page_description": "PÃĄgina de usuÃĄrios Admin", - "version_check_channel": "Tipo de lançamento", - "version_check_channel_description": "Escolha qual o tipo de lançamento que deseja receber notificaçÃĩes de nova versÃŖo", "version_check_enabled_description": "Ativa a verificaÃ§ÃŖo de versÃŖo", "version_check_implications": "A verificaÃ§ÃŖo de versÃŖo depende de uma comunicaÃ§ÃŖo periÃŗdica com {server}", "version_check_settings": "VerificaÃ§ÃŖo de versÃŖo", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Limpar cache de imagens", "advanced_settings_clear_image_cache_error": "Falha ao limpar o cache de imagens", "advanced_settings_clear_image_cache_success": "Foram liberados {size} com sucesso", + "advanced_settings_enable_alternate_media_filter_subtitle": "Use esta opÃ§ÃŖo para filtrar mídias durante a sincronizaÃ§ÃŖo com base em critÊrios alternativos. Tente esta opÃ§ÃŖo somente se o aplicativo estiver com problemas para detectar todos os ÃĄlbuns.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizar filtro alternativo de sincronizaÃ§ÃŖo de ÃĄlbum de dispositivo", "advanced_settings_log_level_title": "Nível de log: {level}", "advanced_settings_prefer_remote_subtitle": "Alguns dispositivos sÃŖo extremamente lentos para carregar as miniaturas locais. Ative esta opÃ§ÃŖo para preferir imagens do servidor.", "advanced_settings_prefer_remote_title": "Preferir imagens do servidor", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Cabeçalhos de proxy customizados [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Ativar o modo de apenas visualizaÃ§ÃŖo dos arquivos. As outras açÃĩes, como: selecionar vÃĄrias imagens, compartilhar, transmitir ou deletar serÃŖo desabilitadas. Ative ou Desative este modo clicando na foto do usuÃĄrio na tela principal", "advanced_settings_readonly_mode_title": "Modo de leitura apenas", + "advanced_settings_self_signed_ssl_subtitle": "Ignora a verificaÃ§ÃŖo do certificado SSL do servidor. ObrigatÃŗrio para certificados auto assinados.", + "advanced_settings_self_signed_ssl_title": "Permitir certificados SSL auto-assinados [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Excluir ou restaurar os arquivos automaticamente neste dispositivo quando essas açÃĩes forem realizada na interface web", "advanced_settings_sync_remote_deletions_title": "Sincronizar exclusÃĩes remotas [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "ConfiguraçÃĩes avançadas do usuÃĄrio", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Tem certeza de que deseja excluir o ÃĄlbum {album}?", "album_delete_confirmation_description": "Se este ÃĄlbum Ê compartilhado, os outros usuÃĄrios nÃŖo conseguiram mais acessÃĄ-lo.", "album_deleted": "Álbum deletado", + "album_info_card_backup_album_excluded": "EXCLUÍDO", + "album_info_card_backup_album_included": "INCLUÍDO", "album_info_updated": "InformaçÃĩes do ÃĄlbum atualizadas", + "album_leave": "Sair do ÃĄlbum?", + "album_leave_confirmation": "Tem certeza de que deseja sair de {album}?", "album_name": "Nome do ÃĄlbum", "album_options": "OpçÃĩes de ÃĄlbum", "album_remove_user": "Remover usuÃĄrio?", "album_remove_user_confirmation": "Tem certeza de que deseja remover {user}?", "album_search_not_found": "NÃŖo hÃĄ ÃĄlbum que corresponda à sua pesquisa", + "album_selected": "Álbum selecionado", "album_share_no_users": "Parece que vocÃĒ jÃĄ compartilhou este ÃĄlbum com todos os usuÃĄrios ou nÃŖo hÃĄ nenhum usuÃĄrio para compartilhar.", "album_summary": "Resumo do ÃĄlbum", "album_updated": "Álbum atualizado", "album_updated_setting_description": "Receba uma notificaÃ§ÃŖo por e-mail quando um ÃĄlbum compartilhado tiver novos recursos", "album_upload_assets": "Enviar arquivos do seu computador e adicionar ao ÃĄlbum", + "album_user_left": "Saiu do ÃĄlbum {album}", + "album_user_removed": "UsuÃĄrio {user} foi removido", + "album_viewer_appbar_delete_confirm": "Tem certeza de que deseja excluir este ÃĄlbum da sua conta?", "album_viewer_appbar_share_err_delete": "Falha ao excluir ÃĄlbum", + "album_viewer_appbar_share_err_leave": "Falha ao sair do ÃĄlbum", + "album_viewer_appbar_share_err_remove": "HÃĄ problemas ao remover recursos do ÃĄlbum", + "album_viewer_appbar_share_err_title": "Falha ao alterar o título do ÃĄlbum", + "album_viewer_appbar_share_leave": "Sair do ÃĄlbum", + "album_viewer_appbar_share_to": "Compartilhar", "album_viewer_page_share_add_users": "Adicionar usuÃĄrios", "album_with_link_access": "Permitir que qualquer pessoa com o link veja as fotos e as pessoas neste ÃĄlbum.", "albums": "Álbuns", @@ -536,25 +513,29 @@ "albums_default_sort_order_description": "Ordem padrÃŖo dos arquivos ao criar novos ÃĄlbuns.", "albums_feature_description": "ColeçÃĩes de arquivos que podem ser compartilhados com outros usuÃĄrios.", "albums_on_device_count": "Álbuns no dispositivo ({count})", + "albums_selected": "{count, plural, one {# ÃĄlbum selecionado} other {# ÃĄlbuns selecionados}}", "all": "Todos", "all_albums": "Todos os ÃĄlbuns", "all_people": "Todas as pessoas", "all_photos": "Todas as fotos", "all_videos": "Todos os vídeos", "allow_dark_mode": "Permitir modo escuro", + "allow_edits": "Permitir ediçÃĩes", "allow_public_user_to_download": "Permitir que usuÃĄrios pÃēblicos baixem os arquivos", "allow_public_user_to_upload": "Permitir que usuÃĄrios pÃēblicos enviem novos arquivos", "allowed": "Permitido", "alt_text_qr_code": "Imagem do cÃŗdigo QR", "always_keep": "Manter sempre", - "always_keep_photos_hint": "Todas as fotos serÃŖo mantidas neste dispositivo.", - "always_keep_videos_hint": "Todos os vídeos serÃŖo mantidas neste dispositivo.", + "always_keep_photos_hint": "Liberar espaço manterÃĄ todas as fotos neste dispositivo.", + "always_keep_videos_hint": "Liberar espaço manterÃĄ todos os vídeos neste dispositivo.", + "anti_clockwise": "Anti-horÃĄrio", "api_key": "Chave de API", "api_key_description": "Este valor serÃĄ mostrado apenas uma vez. Por favor, certifique-se de copiÃĄ-lo antes de fechar a janela.", "api_key_empty": "O nome da sua chave de API nÃŖo deve estar vazio", "api_keys": "Chaves de API", "app_architecture_variant": "Variante (Arquitetura)", "app_bar_signout_dialog_content": "Tem certeza de que deseja sair?", + "app_bar_signout_dialog_ok": "Sim", "app_bar_signout_dialog_title": "Sair", "app_download_links": "Links para baixar o aplicativo", "app_settings": "ConfiguraçÃĩes do Aplicativo", @@ -565,19 +546,27 @@ "archive": "Arquivar", "archive_action_prompt": "{count} mídias arquivadas", "archive_or_unarchive_photo": "Arquivar ou desarquivar foto", + "archive_page_no_archived_assets": "Nenhum arquivo encontrado", + "archive_page_title": "Arquivados ({count})", "archive_size": "Tamanho do arquivamento", "archive_size_description": "Configure o tamanho do arquivo para baixar (em GiB)", "archived": "Arquivado", "archived_count": "{count, plural, one {# Arquivado} other {# Arquivados}}", "are_these_the_same_person": "Essas pessoas sÃŖo a mesma pessoa?", "are_you_sure_to_do_this": "Tem certeza de que deseja fazer isso?", + "array_field_not_fully_supported": "Campos array exigem ediÃ§ÃŖo manual do JSON", + "asset_action_delete_err_read_only": "NÃŖo Ê possível excluir arquivo sÃŗ leitura, ignorando", + "asset_action_share_err_offline": "NÃŖo foi possível obter os arquivos indisponíveis, ignorando", "asset_added_to_album": "Adicionado ao ÃĄlbum", "asset_adding_to_album": "Adicionando ao ÃĄlbumâ€Ļ", "asset_created": "Arquivo foi criado", - "asset_day_count": "{date}: {count, plural, one {# arquivo} other {# arquivos}}", "asset_description_updated": "A descriÃ§ÃŖo do arquivo foi atualizada", + "asset_filename_is_offline": "O arquivo {filename} nÃŖo estÃĄ disponível", + "asset_has_unassigned_faces": "O arquivo tem rostos sem nomes", "asset_hashing": "Processandoâ€Ļ", "asset_list_group_by_sub_title": "Agrupar por", + "asset_list_layout_settings_dynamic_layout_title": "Layout dinÃĸmico", + "asset_list_layout_settings_group_automatically": "AutomÃĄtico", "asset_list_layout_settings_group_by": "Agrupar arquivos por", "asset_list_layout_settings_group_by_month_day": "MÃĒs + dia", "asset_list_layout_sub_title": "Layout", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Arquivo nÃŖo encontrado no iCloud. o arquivo pode estar inacessível devido a um arquivo corrompido armazenado no iCloud", "asset_offline": "Arquivo indisponível", "asset_offline_description": "Este arquivo externo nÃŖo estÃĄ mais disponível. Contate seu administrador do Immich para obter ajuda.", + "asset_restored_successfully": "Arquivo restaurado", "asset_skipped": "Ignorado", "asset_skipped_in_trash": "Na lixeira", + "asset_trashed": "Arquivo enviado para a lixeira", "asset_troubleshoot": "DiagnÃŗstico do arquivo", "asset_uploaded": "Enviado", "asset_uploading": "Enviandoâ€Ļ", "asset_viewer_settings_subtitle": "Gerenciar as configuraçÃĩes do visualizador da galeria", "asset_viewer_settings_title": "Visualizador de Mídia", "assets": "Arquivos", + "assets_added_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}}", "assets_added_to_album_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}} ao ÃĄlbum", - "assets_added_to_album_partial_count": "Adcionado {successCount} de {totalCount} {totalCount, plural, one {mídia} other {mídias}} ao ÃĄlbum", "assets_added_to_albums_count": "{assetTotal, plural, one {# Arquivo adicionado} other {# Arquivos adicionados}} {albumTotal, plural, one {# ao ÃĄlbum} other {# aos ÃĄlbuns}}", "assets_cannot_be_added_to_album_count": "NÃŖo foi possível adicionar {count, plural, one {o arquivo} other {os arquivos}} ao ÃĄlbum", "assets_cannot_be_added_to_albums": "{count, plural, one {Arquivo nÃŖo pode ser adicionado} other {Arquivos nÃŖo podem ser adicionados}} a nenhum ÃĄlbum", "assets_count": "{count, plural, one {# arquivo} other {# arquivos}}", + "assets_deleted_permanently": "{count} arquivo(s) deletado(s) permanentemente", + "assets_deleted_permanently_from_server": "{count} arquivo(s) deletado(s) permanentemente do servidor Immich", + "assets_downloaded_failed": "{count, plural, one {# arquivo transferido - {error} arquivo falhou} other {# arquivos transferidos - {error} arquivos falharam}}", + "assets_downloaded_successfully": "{count, plural, one {# arquivo transferido com sucesso} other {# arquivos transferidos com sucesso}}", "assets_moved_to_trash_count": "{count, plural, one {# arquivo movido} other {# arquivos movidos}} para a lixeira", "assets_permanently_deleted_count": "{count, plural, one {# arquivo excluído permanentemente} other {# arquivos excluídos permanentemente}}", "assets_removed_count": "{count, plural, one {# arquivo removido} other {# arquivos removidos}}", + "assets_removed_permanently_from_device": "{count} arquivo(s) removido(s) permanentemente do seu dispositivo", "assets_restore_confirmation": "Tem certeza de que deseja restaurar todos os seus arquivos na lixeira? Esta aÃ§ÃŖo nÃŖo pode ser desfeita! Nota: Arquivos externos nÃŖo podem ser restaurados desta maneira.", "assets_restored_count": "{count, plural, one {# arquivo restaurado} other {# arquivos restaurados}}", + "assets_restored_successfully": "{count} arquivo(s) restaurado(s)", "assets_trashed": "{count} arquivo enviado para a lixeira", "assets_trashed_count": "{count, plural, one {# arquivo movido para a lixeira} other {# arquivos movidos para a lixeira}}", + "assets_trashed_from_server": "{count} arquivos foram enviados para a lixeira", "assets_were_part_of_album_count": "{count, plural, one {O arquivo jÃĄ faz} other {Os arquivos jÃĄ fazem}} parte do ÃĄlbum", "assets_were_part_of_albums_count": "{count, plural, one {Arquivo jÃĄ existe} other {Arquivos jÃĄ existem}} nos ÃĄlbuns", "authorized_devices": "Dispositivos Autorizados", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ApresentaÃ§ÃŖo de slides automÃĄtica", "back": "Voltar", "back_close_deselect": "Voltar, fechar ou desmarcar", + "background_backup_running_error": "NÃŖo Ê possível iniciar o backup manual agora pois o backup em segundo plano jÃĄ estÃĄ sendo executado", "background_location_permission": "PermissÃŖo de localizaÃ§ÃŖo em segundo plano", "background_location_permission_content": "Para que seja possível trocar o endereço quando estiver executando em segundo plano, o Immich deve *sempre* ter a permissÃŖo de localizaÃ§ÃŖo precisa para que o aplicativo consiga ler o nome da rede Wi-Fi", "background_options": "OpçÃĩes de segundo plano", "backup": "Backup", + "backup_album_selection_page_albums_device": "Álbuns no dispositivo ({count})", "backup_album_selection_page_albums_tap": "Toque para incluir, toque duas vezes para excluir", "backup_album_selection_page_assets_scatter": "Os arquivos podem se espalhar por vÃĄrios ÃĄlbuns. Assim, os ÃĄlbuns podem ser incluídos ou excluídos durante o processo de backup.", "backup_album_selection_page_select_albums": "Selecionar ÃĄlbuns", "backup_album_selection_page_selection_info": "InformaçÃĩes da SeleÃ§ÃŖo", + "backup_album_selection_page_total_assets": "Total de arquivos Ãēnicos", "backup_albums_sync": "SincronizaÃ§ÃŖo de ÃĄlbuns", + "backup_all": "Todos", + "backup_background_service_backup_failed_message": "Falha ao fazer backup. Tentando novamenteâ€Ļ", "backup_background_service_complete_notification": "Backup dos arquivos concluído", + "backup_background_service_connection_failed_message": "Falha na conexÃŖo com o servidor. Tentando novamenteâ€Ļ", + "backup_background_service_current_upload_notification": "Enviando {filename}", "backup_background_service_default_notification": "Verificando se hÃĄ novos arquivosâ€Ļ", + "backup_background_service_error_title": "Erro no backup", "backup_background_service_in_progress_notification": "Fazendo backup de seus arquivosâ€Ļ", + "backup_background_service_upload_failure_notification": "Falha ao enviar {filename}", "backup_controller_page_albums": "Backup de ÃĄlbuns", + "backup_controller_page_background_app_refresh_disabled_content": "Para utilizar o backup em segundo plano, ative a atualizaÃ§ÃŖo da aplicaÃ§ÃŖo em segundo plano em ConfiguraçÃĩes > Geral > AtualizaÃ§ÃŖo em 2Âē plano.", + "backup_controller_page_background_app_refresh_disabled_title": "AtualizaÃ§ÃŖo em 2Âē plano desativada", + "backup_controller_page_background_app_refresh_enable_button_text": "Ir para as configuraçÃĩes", "backup_controller_page_background_battery_info_link": "Mostre-me como", "backup_controller_page_background_battery_info_message": "Para uma melhor experiÃĒncia de backup em segundo plano, desative todas as otimizaçÃĩes de bateria que restrinjam a atividade em segundo plano do Immich.\n\nComo isso Ê específico por dispositivo, consulte as informaçÃĩes de como fazer isso com o fabricante do seu dispositivo.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "OtimizaçÃĩes de bateria", + "backup_controller_page_background_charging": "Apenas enquanto carrega a bateria", + "backup_controller_page_background_configure_error": "Falha ao configurar o serviço em segundo plano", "backup_controller_page_background_delay": "Adiar backup de novos arquivos: {duration}", + "backup_controller_page_background_description": "Ative o serviço em segundo plano para fazer backup automÃĄtico de novos arquivos sem precisar abrir o aplicativo", + "backup_controller_page_background_is_off": "O backup automÃĄtico em segundo plano estÃĄ desativado", + "backup_controller_page_background_is_on": "O backup automÃĄtico em segundo plano estÃĄ ativado", + "backup_controller_page_background_turn_off": "Desativar o serviço em segundo plano", + "backup_controller_page_background_turn_on": "Ativar o serviço em segundo plano", + "backup_controller_page_background_wifi": "Apenas no Wi-Fi", "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selecionados: ", "backup_controller_page_backup_sub": "Total de mídias com backup", + "backup_controller_page_created": "Data: {date}", + "backup_controller_page_desc_backup": "Ative para fazer backup automÃĄtico dos novos arquivos ao abrir este aplicativo.", "backup_controller_page_excluded": "Ignorados: ", + "backup_controller_page_failed": "Falhou ({count})", + "backup_controller_page_filename": "Arquivo: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "InformaçÃĩes do backup", "backup_controller_page_none_selected": "Nenhum ÃĄlbum selecionado", "backup_controller_page_remainder": "Restante", "backup_controller_page_remainder_sub": "Mídias nos ÃĄlbuns selecionados que ainda nÃŖo tem backup", "backup_controller_page_server_storage": "Armazenamento do servidor", + "backup_controller_page_start_backup": "Iniciar backup manual", + "backup_controller_page_status_off": "O backup automÃĄtico estÃĄ desativado", + "backup_controller_page_status_on": "O backup automÃĄtico estÃĄ ativado", "backup_controller_page_storage_format": "{used} de {total} usados", "backup_controller_page_to_backup": "Escolha os ÃĄlbuns para fazer backup", "backup_controller_page_total_sub": "Total de mídias nos ÃĄlbuns selecionados", + "backup_controller_page_turn_off": "Desativar backup automÃĄtico", + "backup_controller_page_turn_on": "Ativar backup automÃĄtico", + "backup_controller_page_uploading_file_info": "InformaçÃĩes do arquivo", + "backup_err_only_album": "NÃŖo Ê possível remover o Ãēnico ÃĄlbum", "backup_error_sync_failed": "A sincronizaÃ§ÃŖo falhou. NÃŖo foi possível processar o backup.", "backup_info_card_assets": "arquivos", + "backup_manual_cancelled": "Cancelado", + "backup_manual_in_progress": "Envio jÃĄ estÃĄ em progresso. Tente novamente mais tarde", + "backup_manual_success": "Sucesso", + "backup_manual_title": "Estado do envio", "backup_options": "OpçÃĩes de backup", + "backup_options_page_title": "OpçÃĩes de backup", + "backup_setting_subtitle": "Gerenciar as configuraçÃĩes de envio em primeiro e segundo plano", "backup_settings_subtitle": "Gerenciar configuraçÃĩes de envio", + "backup_upload_details_page_more_details": "Toque para mais detalhes", "backward": "Para trÃĄs", - "battery_optimization_backup_reliability": "Desativar as otimizaçÃĩes de bateria ajuda a evitar problemas durante o backup em segundo plano", "biometric_auth_enabled": "AutenticaÃ§ÃŖo por biometria ativada", "biometric_locked_out": "Sua autenticaÃ§ÃŖo por biometria estÃĄ bloqueada", "biometric_no_options": "NÃŖo hÃĄ opçÃĩes de biometria disponíveis", "biometric_not_available": "A autenticaÃ§ÃŖo por biometria nÃŖo estÃĄ disponível neste dispositivo", + "birthdate_saved": "Data de nascimento salva com sucesso", "birthdate_set_description": "A data de nascimento Ê usada para calcular a idade da pessoa no momento em que a foto foi tirada.", "blurred_background": "Fundo desfocado", - "browse_templates": "Ver modelos", "bugs_and_feature_requests": "Relatar problemas & SugestÃĩes", "build": "VersÃŖo de compilaÃ§ÃŖo", "build_image": "Imagem de compilaÃ§ÃŖo", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Tem certeza de que deseja manter {count, plural, one {# arquivo duplicado} other {# arquivos duplicados}}? Isso resolverÃĄ todos os grupos duplicados sem excluir nada.", "bulk_trash_duplicates_confirmation": "Tem a certeza de que deseja mover para a lixeira {count, plural, one {# arquivo duplicado} other {# arquivos duplicados}}? Isso manterÃĄ o maior arquivo de cada grupo e moverÃĄ para a lixeira todas as outras duplicidades.", "buy": "Comprar o Immich", + "cache_settings_clear_cache_button": "Limpar o cache", + "cache_settings_clear_cache_button_title": "Limpa o cache do aplicativo. Isso afetarÃĄ significativamente o desempenho do aplicativo atÊ que o cache seja reconstruído.", + "cache_settings_duplicated_assets_clear_button": "LIMPAR", + "cache_settings_duplicated_assets_subtitle": "Mídias ignoradas pelo app", + "cache_settings_duplicated_assets_title": "Arquivos duplicados ({count})", + "cache_settings_statistics_album": "Miniaturas da biblioteca", + "cache_settings_statistics_full": "Imagens completas", + "cache_settings_statistics_shared": "Miniaturas de ÃĄlbuns compartilhados", + "cache_settings_statistics_thumbnail": "Miniaturas", + "cache_settings_statistics_title": "Uso do cache", + "cache_settings_subtitle": "Controle o comportamento de cache do aplicativo Immich", + "cache_settings_tile_subtitle": "Controle o comportamento do armazenamento local", + "cache_settings_tile_title": "Armazenamento Local", + "cache_settings_title": "ConfiguraçÃĩes de cache", "camera": "CÃĸmera", "camera_brand": "Marca da cÃĸmera", "camera_model": "Modelo da cÃĸmera", @@ -678,10 +729,10 @@ "cannot_update_the_description": "NÃŖo Ê possível atualizar a descriÃ§ÃŖo", "cast": "Transmitir", "cast_description": "Configure destinos de transmissÃŖo disponíveis", - "change": "Mudar", "change_date": "Alterar data", "change_description": "Alterar descriÃ§ÃŖo", "change_display_order": "Alterar ordem de exibiÃ§ÃŖo", + "change_expiration_time": "Alterar o prazo de validade", "change_location": "Alterar localizaÃ§ÃŖo", "change_name": "Alterar nome", "change_name_successfully": "Nome alterado com sucesso", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "As senhas nÃŖo estÃŖo iguais", "change_password_form_reenter_new_password": "Confirme a nova senha", "change_pin_code": "Alterar cÃŗdigo PIN", + "change_trigger": "Alterar gatilho", + "change_trigger_prompt": "Tem certeza de que deseja alterar o gatilho? Isso removerÃĄ todas as açÃĩes e filtros existentes.", "change_your_password": "Alterar sua senha", "changed_visibility_successfully": "Visibilidade alterada com sucesso", "charging": "Carregando", "charging_requirement_mobile_backup": "Backups em segundo plano requerem que o dispositivo esteja sendo carregado", + "check_corrupt_asset_backup": "Verifique se hÃĄ backups corrompidos", + "check_corrupt_asset_backup_button": "Verificar", + "check_corrupt_asset_backup_description": "Execute esta verificaÃ§ÃŖo somente em uma rede Wi-Fi e quando o backup de todos os arquivos jÃĄ estiver concluído. O processo demora alguns minutos.", "check_logs": "Ver logs", "checksum": "Checksum", - "choose": "Escolha", "choose_matching_people_to_merge": "Escolha pessoas correspondentes para mesclar", "city": "Cidade", "cleanup_confirm_description": "O Immich encontrou {count} arquivos (criados antes de {date}) salvos com segurança no servidor. Deseja remover as cÃŗpias locais deste dispositivo?", @@ -711,18 +766,19 @@ "cleanup_found_assets": "Encontrados {count} arquivos com backup", "cleanup_found_assets_with_size": "Foram encontrados {count} arquivos com backup ({size})", "cleanup_icloud_shared_albums_excluded": "Álbuns compartilhados do iCloud nÃŖo serÃŖo incluídos", - "cleanup_no_assets_found": "NÃŖo foram encontrados arquivos que correspondam aos seus critÊrios. SÃŗ Ê possível liberar o espaço de arquivos que jÃĄ foram enviados para o servidor", + "cleanup_no_assets_found": "NÃŖo foram encontrados arquivos que correspondam aos seus critÊrios. Liberar Espaço sÃŗ pode remover arquivos que foram copiados para o servidor", "cleanup_preview_title": "Remover {count} arquivos", - "cleanup_step3_description": "Buscar arquivos jÃĄ enviados ao servidor que sigam os critÊrios de data e se serÃŖo mantidos.", - "cleanup_step4_summary": "{count} arquivos criados antes de {date} foram selecionados para liberar espaço do seu dispositivo. Essas mídia ainda estarÃŖo acessíveis atravÊs do app do Immich.", + "cleanup_step3_description": "Procure por arquivos de backup que correspondam à sua data e manter configuraçÃĩes.", + "cleanup_step4_summary": "{count} arquivos criados antes de {date} foram selecionados para liberar espaço do seu dispositivo. Fotos permanecerÃŖo acessíveis atravÊs do app do Immich.", "cleanup_trash_hint": "Para liberar espaço imediatamente, abra a galeria de fotos original do dispositivo e esvazie a lixeira", "clear": "Limpar", "clear_all": "Limpar tudo", "clear_all_recent_searches": "Limpar todas as buscas recentes", - "clear_failed_count": "Limpar erros ({count})", "clear_file_cache": "Limpar cache arquivos", "clear_message": "Limpar mensagem", "clear_value": "Limpar valor", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Digite a senha", "client_cert_import": "Importar", "client_cert_import_success_msg": "Certificado do cliente importado", "client_cert_invalid_msg": "Arquivo de certificado invÃĄlido ou senha errada", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certificado do cliente removido", "client_cert_subtitle": "Suporta apenas o formato PKCS12 (.p12, .pfx). A importaÃ§ÃŖo/remoÃ§ÃŖo de certificados estÃĄ disponível apenas antes do login", "client_cert_title": "Certificado de cliente SSL [EXPERIMENTAL]", + "clockwise": "HorÃĄrio", "close": "Fechar", "collapse": "Recolher", "collapse_all": "Colapsar tudo", "color": "Cor", + "color_theme": "Tema de cores", "command": "Comando", "command_palette_prompt": "Encontre rapidamente pÃĄginas, açÃĩes ou comandos", "command_palette_to_close": "para fechar", @@ -747,7 +805,6 @@ "comments_are_disabled": "ComentÃĄrios estÃŖo desativados", "common_create_new_album": "Criar novo ÃĄlbum", "completed": "Completado", - "configuration": "ConfiguraÃ§ÃŖo", "confirm": "Confirmar", "confirm_admin_password": "Confirmar senha de administrador", "confirm_delete_face": "Tem certeza que deseja remover a rosto de {name} deste arquivo?", @@ -762,16 +819,19 @@ "contain": "Caber", "context": "Contexto", "continue": "Continuar", - "control_bottom_app_bar_add_tags": "Adicionar Marcadores", + "control_bottom_app_bar_create_new_album": "Criar novo ÃĄlbum", + "control_bottom_app_bar_delete_from_immich": "Excluir do Immich", "control_bottom_app_bar_delete_from_local": "Excluir do dispositivo", "control_bottom_app_bar_edit_location": "Alterar Local", "control_bottom_app_bar_edit_time": "Editar data e hora", + "control_bottom_app_bar_share_link": "Link", + "control_bottom_app_bar_share_to": "Compartilhar", "control_bottom_app_bar_trash_from_immich": "Mover para a Lixeira", "copied_image_to_clipboard": "Imagem copiada para a ÃĄrea de transferÃĒncia.", "copied_to_clipboard": "Copiado para a ÃĄrea de transferÃĒncia!", "copy_error": "Copiar erro", + "copy_file_path": "Copiar caminho do arquivo", "copy_image": "Copiar Imagem", - "copy_json": "Copiar JSON", "copy_link": "Copiar link", "copy_link_to_clipboard": "Copiar link para a ÃĄrea de transferÃĒncia", "copy_password": "Copiar senha", @@ -792,8 +852,6 @@ "create_new_person": "Criar nova pessoa", "create_new_person_hint": "Atribuir arquivos selecionados a uma nova pessoa", "create_new_user": "Criar novo usuÃĄrio", - "create_person": "Criar pessoa", - "create_person_subtitle": "Adicione um nome ao rosto selecionado para criar e marcar uma nova pessoa", "create_shared_album_page_share_add_assets": "ADICIONAR FOTOS", "create_shared_album_page_share_select_photos": "Selecionar fotos", "create_shared_link": "Criar link", @@ -805,33 +863,36 @@ "created_at": "Criado em", "creating_linked_albums": "Criando ÃĄlbuns relacionados...", "crop": "Cortar", + "crop_aspect_ratio_fixed": "Fixo", "crop_aspect_ratio_free": "Livre", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Quadrado", + "curated_object_page_title": "Objetos", "current_device": "Dispositivo atual", "current_pin_code": "CÃŗdigo PIN atual", "current_server_address": "Endereço atual do servidor", "custom_date": "Data específica", "custom_locale": "Localidade customizada", "custom_locale_description": "Formata as datas, horas e nÃēmeros baseado no idioma e regiÃŖo selecionada", - "cutoff_date_description": "Remover arquivos mais velhos queâ€Ļ", + "custom_url": "URL personalizada", + "cutoff_date_description": "Manter fotos dos Ãēltimosâ€Ļ", "cutoff_day": "{count, plural, one {dia} other {dias}}", "cutoff_year": "{count, plural, one {ano} other {anos}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Escuro", "dark_theme": "Usar tema escuro", "date": "Data", "date_after": "Data apÃŗs", "date_and_time": "Data e Hora", "date_before": "Data antes", - "date_of_birth": "Data de Nascimento", + "date_format": "E, d LLL, y â€ĸ h:mm a", "date_of_birth_saved": "Data de nascimento salvo com sucesso", "date_range": "Intervalo de datas", - "date_time_original": "Data/Hora Orginal", "day": "Dia", "days": "Dias", "deduplicate_all": "Limpar todas Duplicidades", - "default_quality_subtitle": "Qualidade usada por padrÃŖo ao tocar no botÃŖo de compartilhar arquivo. Toque e segure o botÃŖo para escolher a qualidade toda vez.", - "default_share_quality": "Qualidade ao compartilhar", + "default_locale": "Local padrÃŖo", "delete": "Excluir", "delete_action_confirmation_message": "Tem certeza? O arquivo serÃĄ enviado para a lixeira do servidor, depois vocÃĒ poderÃĄ confirmar se deseja tambÊm deletar do seu dispositivo local", "delete_action_prompt": "{count} deletados", @@ -840,6 +901,8 @@ "delete_dialog_alert": "Esses itens serÃŖo excluídos permanentemente do Immich e do seu dispositivo", "delete_dialog_alert_local": "Estes arquivos serÃŖo permanentemente excluídos do seu dispositivo, mas continuarÃŖo disponíveis no servidor Immich", "delete_dialog_alert_local_non_backed_up": "NÃŖo hÃĄ backup de alguns dos arquivos no servidor e eles serÃŖo excluídos permanentemente do seu dispositivo", + "delete_dialog_alert_remote": "Esses arquivos serÃŖo excluídos permanentemente do servidor Immich", + "delete_dialog_ok_force": "Excluir mesmo assim", "delete_dialog_title": "Excluir permanentemente", "delete_duplicates_confirmation": "Tem certeza de que deseja excluir permanentemente estas duplicidades?", "delete_face": "Remover rosto", @@ -858,16 +921,21 @@ "delete_tag_confirmation_prompt": "Tem certeza que deseja excluir o marcador {tagName} ?", "delete_user": "Excluir usuÃĄrio", "deleted_shared_link": "Link de compartilhamento excluído", + "deletes_missing_assets": "Excluir arquivos nÃŖo encontrados", "description": "DescriÃ§ÃŖo", + "description_input_hint_text": "Adicionar descriÃ§ÃŖo...", + "description_input_submit_error": "Erro ao atualizar a descriÃ§ÃŖo, verifique o log para mais detalhes", "deselect_all": "Desselecionar tudo", "details": "Detalhes", "direction": "DireÃ§ÃŖo", "disable": "Desativar", "disabled": "Desativado", + "disallow_edits": "NÃŖo permitir ediçÃĩes", "discord": "Discord", "discover": "Descobrir", "discovered_devices": "Dispositivos encontrados", "dismiss_all_errors": "Dispensar todos os erros", + "dismiss_error": "Dispensar erro", "display_options": "OpçÃĩes de exibiÃ§ÃŖo", "display_order": "Ordem de exibiÃ§ÃŖo", "display_original_photos": "Exibir fotos originais", @@ -876,9 +944,11 @@ "documentation": "DocumentaÃ§ÃŖo", "done": "Feito", "download": "Baixar", + "download_action_prompt": "Baixando {count} arquivos", "download_canceled": "Cancelado", "download_complete": "Sucesso", "download_enqueue": "Na fila", + "download_error": "Erro ao baixar", "download_failed": "Falha", "download_finished": "Concluído", "download_include_embedded_motion_videos": "Vídeos inclusos", @@ -888,17 +958,17 @@ "download_paused": "Pausado", "download_settings": "Baixar", "download_settings_description": "Gerenciar configuraçÃĩes relacionadas a transferÃĒncia de arquivos", + "download_started": "Baixando", + "download_sucess": "Baixado com sucesso", + "download_sucess_android": "O arquivo foi salvo em DCIM/Immich", "download_waiting_to_retry": "Aguardando para tentar novamente", "downloading": "Baixando", "downloading_asset_filename": "Baixando arquivo {filename}", "downloading_from_icloud": "Baixando do iCloud", "downloading_media": "Baixando mídia", - "drag_to_reorder": "Arraste para reordenar", "drop_files_to_upload": "Solte os arquivos em qualquer lugar para enviar", - "duplicate": "Duplicar", - "duplicate_workflow": "Duplicar fluxo", "duplicates": "Duplicados", - "duplicates_description": "Marque cada grupo indicando quais arquivos, se algum, sÃŖo duplicados.", + "duplicates_description": "Marque cada grupo indicando quais arquivos, se algum, sÃŖo duplicados", "duration": "DuraÃ§ÃŖo", "edit": "Editar", "edit_album": "Editar ÃĄlbum", @@ -908,7 +978,9 @@ "edit_date_and_time": "Editar data e hora", "edit_date_and_time_action_prompt": "Alterado data e hora de {count} arquivos", "edit_date_and_time_by_offset": "Alterar data por deslocamento", + "edit_date_and_time_by_offset_interval": "Novas datas: De {from} a {to}", "edit_description": "Editar descriÃ§ÃŖo", + "edit_description_prompt": "Por favor selecione uma nova descriÃ§ÃŖo:", "edit_exclusion_pattern": "Editar o padrÃŖo de exclusÃŖo", "edit_faces": "Editar rostos", "edit_key": "Editar chave", @@ -923,6 +995,9 @@ "edit_user": "Editar usuÃĄrio", "edit_workflow": "Editar fluxo", "editor": "Editar", + "editor_close_without_save_prompt": "As alteraçÃĩes nÃŖo serÃŖo salvas", + "editor_close_without_save_title": "Fechar editor?", + "editor_confirm_reset_all_changes": "Tem certeza que deseja desfazer todas alteraçÃĩes?", "editor_discard_edits_confirm": "Descartar alteraçÃĩes", "editor_discard_edits_prompt": "VocÃĒ possui alteraçÃĩes que nÃŖo foram salvas. Tem certeza que deseja descarta-las?", "editor_discard_edits_title": "Descartar as alteraçÃĩes?", @@ -951,7 +1026,9 @@ "enter_your_pin_code": "Insira seu cÃŗdigo PIN", "enter_your_pin_code_subtitle": "Insira seu cÃŗdigo PIN para acessar a pasta com senha", "error": "Erro", + "error_change_sort_album": "Falha ao alterar a ordem de exibiÃ§ÃŖo", "error_delete_face": "Erro ao remover face do arquivo", + "error_getting_places": "Erro ao buscar os locais", "error_loading_albums": "Erro ao carregar ÃĄlbuns", "error_loading_image": "Erro ao carregar a pÃĄgina", "error_loading_partners": "Erro ao carregar parceiros: {error}", @@ -991,7 +1068,6 @@ "failed_to_remove_product_key": "Falha ao remover a chave do produto", "failed_to_reset_pin_code": "Falha ao redefinir o CÃŗdigo PIN", "failed_to_stack_assets": "Falha ao agrupar arquivos", - "failed_to_tag_assets": "Erro ao marcar arquivos", "failed_to_unstack_assets": "Falha ao remover arquivos do grupo", "failed_to_update_notification_status": "Falha ao atualizar o status da notificaÃ§ÃŖo", "incorrect_email_or_password": "E-mail ou senha incorretos", @@ -1090,10 +1166,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Adicionar descriÃ§ÃŖo...", "exif_bottom_sheet_description_error": "Erro ao alterar a descriÃ§ÃŖo", + "exif_bottom_sheet_details": "DETALHES", + "exif_bottom_sheet_location": "LOCALIZAÇÃO", "exif_bottom_sheet_no_description": "Sem descriÃ§ÃŖo", + "exif_bottom_sheet_people": "PESSOAS", + "exif_bottom_sheet_person_add_person": "Adicionar nome", "exit_slideshow": "Sair da apresentaÃ§ÃŖo", "expand": "Expandir", "expand_all": "Expandir tudo", + "experimental_settings_new_asset_list_subtitle": "Em andamento", + "experimental_settings_new_asset_list_title": "Ativar grade de fotos experimental", + "experimental_settings_subtitle": "Use por sua conta e risco!", + "experimental_settings_title": "Experimental", "expire_after": "Expira depois", "expired": "Expirou", "expires_date": "Expira em {date}", @@ -1103,40 +1187,41 @@ "export_as_json": "Exportar como JSON", "export_database": "Exportar Banco de Dados", "export_database_description": "Exportar o Banco de Dados SQLite", - "exposure_time": "Tempo de ExposiÃ§ÃŖo", "extension": "ExtensÃŖo", "external": "Externo", "external_libraries": "Bibliotecas externas", "external_network": "Rede externa", "external_network_sheet_info": "Quando nÃŖo estiver na rede Wi-Fi especificada, o aplicativo irÃĄ se conectar usando o primeiro endereço abaixo que obtiver sucesso, começando do topo da lista para baixo", - "f_number": "Abertura do Diafragma", "face_unassigned": "Sem nome", "failed": "Falhou", "failed_count": "Falhas: {count}", "failed_to_authenticate": "NÃŖo foi possível autenticar", - "failed_to_delete_file": "Falha ao excluir arquivo", "failed_to_load_assets": "Falha ao carregar arquivos", "failed_to_load_folder": "Falha ao carregar a pasta", "favorite": "Favorito", "favorite_action_prompt": "{count} marcados como favorito", "favorite_or_unfavorite_photo": "Marque ou desmarque a foto como favorita", "favorites": "Favoritos", + "favorites_page_no_favorites": "Nenhuma mídia favorita encontrada", "feature_photo_updated": "Foto principal atualizada", "features": "Funcionalidades", + "features_in_development": "FunçÃĩes em desenvolvimento", "features_setting_description": "Gerenciar as funcionalidades da aplicaÃ§ÃŖo", "file_name_or_extension": "Nome do arquivo ou extensÃŖo", "file_name_text": "Nome do arquivo", + "file_name_with_value": "Nome do arquivo: {file_name}", "file_size": "Tamanho do arquivo", "filename": "Nome do arquivo", "filetype": "Tipo de arquivo", "filter": "Filtro", + "filter_description": "CondiçÃĩes para filtrar os arquivos enviados", "filter_people": "Filtrar pessoas", "filter_places": "Filtrar lugares", "filter_tags": "Filtrar marcadores", "filters": "Filtros", + "find_them_fast": "Encontre pelo nome em uma pesquisa", "first": "Primeiro", "fix_incorrect_match": "Corrigir correspondÃĒncia incorreta", - "focal_length": "Comprimento Focal", "folder": "Pasta", "folder_not_found": "Pasta nÃŖo encontrada", "folders": "Pastas", @@ -1144,10 +1229,9 @@ "forgot_pin_code_question": "Esqueceu seu PIN?", "forward": "Para frente", "free_up_space": "Liberar espaço", - "free_up_space_description": "Remova do seu dispositivo as fotos e vídeos que jÃĄ foram enviadas para o servidor. As cÃŗpias no servidor permanecem seguras.", + "free_up_space_description": "Mova as fotos e vídeos de backup para a lixeira do seu dispositivo para liberar espaço. Suas cÃŗpias no servidor permanecem seguras.", "free_up_space_settings_subtitle": "Liberar espaço no dispositivo", "full_path": "Caminho completo: {path}", - "full_path_or_folder": "Caminho completo ou pasta", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Esta funcionalidade carrega recursos externos do Google para funcionar.", "general": "Geral", @@ -1168,6 +1252,7 @@ "group_owner": "Agrupar por dono", "group_places_by": "Agrupar lugares por...", "group_year": "Agrupar por ano", + "haptic_feedback_switch": "Ativar vibraÃ§ÃŖo", "haptic_feedback_title": "VibraÃ§ÃŖo", "has_quota": "Cota", "hash_asset": "Calcular hash dos arquivos", @@ -1188,12 +1273,29 @@ "hide_schema": "Ocultar esquema", "hide_text_recognition": "Esconder reconhecimento de texto", "hide_unnamed_people": "Esconder pessoas sem nome", + "home_page_add_to_album_conflicts": "{added} arquivos adicionados ao ÃĄlbum {album}. {failed} arquivos jÃĄ estÃŖo no ÃĄlbum.", + "home_page_add_to_album_err_local": "Ainda nÃŖo Ê possível adicionar arquivos locais aos ÃĄlbuns, ignorando", + "home_page_add_to_album_success": "{added} arquivos adicionados ao ÃĄlbum {album}.", + "home_page_album_err_partner": "Ainda nÃŖo Ê possível adicionar arquivos de parceiros a um ÃĄlbum, ignorando", + "home_page_archive_err_local": "Ainda nÃŖo Ê possível arquivar mídias locais, ignorando", + "home_page_archive_err_partner": "NÃŖo Ê possível arquivar as mídias do parceiro, ignorando", "home_page_building_timeline": "Construindo a linha do tempo", + "home_page_delete_err_partner": "NÃŖo Ê possível excluir arquivos do parceiro, ignorando", + "home_page_delete_remote_err_local": "Foram selecionados arquivos locais para excluir remotamente, ignorando", + "home_page_favorite_err_local": "Ainda nÃŖo Ê possível adicionar arquivos locais aos favoritos, ignorando", + "home_page_favorite_err_partner": "Ainda nÃŖo Ê possível marcar arquivos do parceiro como favoritos, ignorando", + "home_page_first_time_notice": "Se Ê a primeira vez que utiliza o aplicativo, certifique-se de marcar um ou mais ÃĄlbuns do dispositivo para backup, assim a linha do tempo serÃĄ preenchida com as fotos e vídeos", + "home_page_locked_error_local": "NÃŖo Ê possível mover arquivos locais para a pasta com senha, ignorando", + "home_page_locked_error_partner": "NÃŖo Ê possível mover arquivos do parceiro para a pasta com senha, Ignorando", + "home_page_share_err_local": "NÃŖo Ê possível compartilhar arquivos locais com um link, ignorando", + "home_page_upload_err_limit": "SÃŗ Ê possível enviar 30 arquivos de cada vez, ignorando", "host": "Servidor", "hour": "Hora", "hours": "Horas", "id": "ID", "idle": "Inativo", + "ignore_icloud_photos": "Ignorar fotos do iCloud", + "ignore_icloud_photos_description": "Fotos que estÃŖo armazenadas no iCloud nÃŖo serÃŖo enviadas para o servidor do Immich", "image": "Imagem", "image_alt_text_date": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} com {person1} em {date}", @@ -1205,6 +1307,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1} e {person2} em {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {person3} em {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Vídeo gravado} other {Foto tirada}} em {city}, {country} com {person1}, {person2}, e {additionalCount, number} outros em {date}", + "image_saved_successfully": "Imagem salva", + "image_viewer_page_state_provider_download_started": "Baixando arquivo", + "image_viewer_page_state_provider_download_success": "Baixado com sucesso", + "image_viewer_page_state_provider_share_error": "Erro ao compartilhar", "immich_logo": "Logo do Immich", "immich_web_interface": "Interface Web do Immich", "import_from_json": "Importar do JSON", @@ -1219,7 +1325,6 @@ "individual_share": "Compartilhamento Ãēnico", "individual_shares": "Compartilhamentos individuais", "info": "InformaçÃĩes", - "integrity_checks": "VerificaçÃĩes de Integridade", "interval": { "day_at_onepm": "Todo dia, 1pm", "hours": "A cada {hours, plural, one {hora} other {{hours, number} horas}}", @@ -1230,17 +1335,24 @@ "invalid_date_format": "Formato de data invÃĄlido", "invite_people": "Convidar Pessoas", "invite_to_album": "Convidar para o ÃĄlbum", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Coleta ocorreu {dateTime}", + "ios_debug_info_last_sync_at": "Ultima sincronizaÃ§ÃŖo {dateTime}", + "ios_debug_info_no_processes_queued": "Nenhum processo de segundo plano enfileirado", + "ios_debug_info_no_sync_yet": "Nenhum serviço de sicronizaÃ§ÃŖo em segundo plano foi executado ainda", + "ios_debug_info_processes_queued": "{count, plural, one {{count} processo em segundo plano enfileirado} other {{count} processos em segundo plano enfileirados}}", + "ios_debug_info_processing_ran_at": "processamento executado em {dateTime}", "items_count": "{count, plural, one {# item} other {# itens}}", "jobs": "Tarefas", + "json_editor": "Editor JSON", + "json_error": "Erro no JSON", "keep": "Manter", - "keep_albums": "Manter dos ÃĄlbuns", + "keep_albums": "Manter ÃĄlbuns", "keep_albums_count": "Mantendo {count} {count, plural, one {ÃĄlbum} other {ÃĄlbuns}}", "keep_all": "Manter Todos", - "keep_description": "Escolha o que manter no seu dispositivo.", + "keep_description": "Escolha o que fica no seu dispositivo ao liberar espaço.", "keep_favorites": "Manter favoritos", "keep_on_device": "Manter no dispositivo", - "keep_on_device_hint": "Selecione o que deseja manter neste dispositivo", + "keep_on_device_hint": "Selecione os itens que deseja manter neste dispositivo", "keep_this_delete_others": "Manter este, excluir o resto", "keeping": "Mantendo: {items}", "kept_this_deleted_others": "Este foi mantido e {count, plural, one {# arquivo foi excluído} other {# arquivos foram excluídos}}", @@ -1259,12 +1371,14 @@ "leave": "Sair", "leave_album": "Sair do ÃĄlbum", "lens_model": "Modelo da lente", - "less": "Menos", "let_others_respond": "Permitir respostas", "level": "Nível", "library": "Biblioteca", "library_add_folder": "Adicionar pasta", "library_edit_folder": "Editar pasta", + "library_options": "OpçÃĩes da biblioteca", + "library_page_device_albums": "Álbuns no Dispositivo", + "library_page_new_album": "Novo album", "library_page_sort_asset_count": "Quantidade de arquivos", "library_page_sort_created": "Data de criaÃ§ÃŖo", "library_page_sort_last_modified": "Última modificaÃ§ÃŖo", @@ -1274,14 +1388,11 @@ "light_theme": "Mudar para tema claro", "like": "Curtir", "like_deleted": "Curtida excluída", - "link": "Link", "link_motion_video": "Relacionar video animado", - "link_to_docs": "Para mais informaçÃĩes, veja a documentaÃ§ÃŖo.", + "link_to_docs": "Para mais informaçÃĩes, veja", "link_to_oauth": "Link do OAuth", "linked_oauth_account": "Conta OAuth Vinculada", "list": "Lista", - "live": "Ao vivo", - "load_more": "Carregar Mais", "loading": "Carregando", "loading_search_results_failed": "Falha ao carregar os resultados da pesquisa", "local": "Local", @@ -1310,9 +1421,11 @@ "login": "Entrar", "login_disabled": "Login desativado", "login_form_api_exception": "Erro de API. Verifique a URL do servidor e tente novamente.", + "login_form_back_button_text": "Voltar", "login_form_email_hint": "seu@email.com", "login_form_endpoint_hint": "http://ip-do-seu-servidor:porta", "login_form_endpoint_url": "URL do servidor", + "login_form_err_http": "Por favor especifique http:// ou https://", "login_form_err_invalid_email": "E-mail invÃĄlido", "login_form_err_invalid_url": "URL InvÃĄlida", "login_form_err_leading_whitespace": "HÃĄ um espaço em branco no início", @@ -1322,9 +1435,10 @@ "login_form_failed_login": "Erro ao fazer login, verifique a URL do servidor, e-mail e senha", "login_form_handshake_exception": "Houve um erro de autorizaÃ§ÃŖo com o servidor. Se estiver utilizando um certificado auto assinado, ative o suporte a isso nas configuraçÃĩes.", "login_form_password_hint": "senha", - "login_form_server_empty": "Digite a URL do servido", - "login_form_server_error": "NÃŖo foi possível conectar ao servidor", - "login_has_been_disabled": "Login foi desativado", + "login_form_save_login": "Permaneçer conectado", + "login_form_server_empty": "Digite a URL do servidor.", + "login_form_server_error": "NÃŖo foi possível conectar ao servidor.", + "login_has_been_disabled": "Login foi desativado.", "login_password_changed_error": "Erro ao atualizar a sua senha", "login_password_changed_success": "Senha atualizada com sucesso", "logout_all_device_confirmation": "Tem certeza de que deseja sair de todos os dispositivos?", @@ -1339,6 +1453,7 @@ "maintenance_action_restore": "Restaurando Banco de Dados", "maintenance_description": "O Immich foi colocado em modo de manutenÃ§ÃŖo.", "maintenance_end": "Desativar modo de manutenÃ§ÃŖo", + "maintenance_end_error": "Ocorreu um erro ao desativar o modo de manutenÃ§ÃŖo.", "maintenance_logged_in_as": "UsuÃĄrio atual: {user}", "maintenance_restore_from_backup": "Restaurar a partir de Backup", "maintenance_restore_library": "Restaurar Sua Biblioteca", @@ -1364,6 +1479,7 @@ "manage_media_access_settings": "Abrir configuraçÃĩes", "manage_media_access_subtitle": "Permita que o aplicativo Immich gerencie e mova arquivos de mídia.", "manage_media_access_title": "Acesso para gerenciar mídias", + "manage_shared_links": "Gerir links partilhados", "manage_sharing_with_partners": "Gerenciar compartilhamento com parceiros", "manage_the_app_settings": "Gerenciar configuraçÃĩes do app", "manage_your_account": "Gerenciar sua conta", @@ -1371,11 +1487,13 @@ "manage_your_devices": "Gerenciar seus dispositivos conectados", "manage_your_oauth_connection": "Gerenciar sua conexÃŖo OAuth", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {Sem fotos nesta ÃĄrea} one {# foto} other {# fotos}}", "map_cannot_get_user_location": "NÃŖo foi possível obter a sua localizaÃ§ÃŖo", + "map_location_dialog_yes": "Sim", "map_location_picker_page_use_location": "Use esta localizaÃ§ÃŖo", "map_location_service_disabled_content": "O serviço de localizaÃ§ÃŖo precisa estar ativado para exibir os arquivos da sua localizaÃ§ÃŖo atual. Deseja ativar agora?", "map_location_service_disabled_title": "Serviço de localizaÃ§ÃŖo desativado", - "map_marker_for_image": "Marcador do mapa para a foto tirada em {city}, {country}", + "map_marker_for_images": "Marcador de mapa para imagens tiradas em {city}, {country}", "map_marker_with_image": "Marcador de mapa com imagem", "map_no_location_permission_content": "É necessÃĄria a permissÃŖo de localizaÃ§ÃŖo para exibir os arquivos da sua localizaÃ§ÃŖo atual. Deseja conceder a permissÃŖo agora?", "map_no_location_permission_title": "PermissÃŖo de localizaÃ§ÃŖo foi negada", @@ -1385,46 +1503,17 @@ "map_settings_date_range_option_days": "Últimos {days} dias", "map_settings_date_range_option_year": "Último ano", "map_settings_date_range_option_years": "Últimos {years} anos", + "map_settings_dialog_title": "ConfiguraçÃĩes do mapa", "map_settings_include_show_archived": "Incluir arquivados", "map_settings_include_show_partners": "Incluir parceiros", "map_settings_only_show_favorites": "Mostrar apenas favoritos", "map_settings_theme_settings": "Tema do mapa", + "map_zoom_to_see_photos": "Diminua o zoom para ver mais fotos", "mark_all_as_read": "Marcar tudo como lido", + "mark_as_read": "Marcar como lido", "marked_all_as_read": "Tudo marcado como lido", "matches": "CorrespondÃĒncias", "matching_assets": "Arquivos encontrados", - "media_chrome": { - "auto": "Auto", - "captions": "Legendas", - "captions_off": "Nenhuma", - "closed_captions": "Legendas Ocultas", - "decode_error": "Erro de decodificaÃ§ÃŖo", - "disable_captions": "Desativar legendas", - "enable_captions": "Ativar legendas", - "enter_fullscreen_mode": "Mostrar em tela cheia", - "exit_fullscreen_mode": "Sair da tela cheia", - "loop": "Repetir", - "media_error_description": "Um erro de mídia fez com que a reproduÃ§ÃŖo parasse. A mídia pode estar corrompida ou seu navegador nÃŖo suporta este formato.", - "media_loading": "Carregando mídia", - "mute": "Silenciar", - "network_error": "Erro de conexÃŖo", - "network_error_description": "Um erro de conexÃŖo causou uma falha enquanto baixava a mídia.", - "not_supported_error": "Formato de mídia nÃŖo suportada", - "playback_rate": "Velocidade", - "playback_rate_current": "Velocidade atual", - "playback_rate_value": "Velocidade {playbackRate}", - "playback_time": "Tempo de reproduÃ§ÃŖo", - "quality": "Qualidade", - "second": "segundo", - "seconds": "segundos", - "time_value_of_total_time": "{currentTime} de {totalTime}", - "time_value_remaining": "{time} restante", - "unmute": "Ativar Som", - "unsupported_error_description": "Ocorreu um erro de incompatibilidade. O servidor ou a conexÃŖo falhou, ou seu navegador nÃŖo suporta o formato da mídia.", - "video_not_loaded_unknown_time": "Nenhum vídeo carregado, o tempo Ê desconhecido.", - "video_player": "Visualizador de vídeos", - "volume": "Volume" - }, "media_type": "Tipo de mídia", "memories": "MemÃŗrias", "memories_all_caught_up": "Finalizamos por hoje", @@ -1441,31 +1530,34 @@ "merge_people_prompt": "Tem certeza que deseja mesclar estas pessoas? Esta aÃ§ÃŖo Ê irreversível.", "merge_people_successfully": "Pessoas mescladas com sucesso", "merged_people_count": "{count, plural, one {# pessoa foi mesclada} other {# pessoas foram mescladas}}", - "minFaces": "Mínimo de rostos", - "minFaces_description": "O nÃēmero mínimo de reconhecimentos faciais para uma pessoa ser exibida na lista", "minimize": "Minimizar", "minute": "Minuto", "minutes": "Minutos", + "mirror_horizontal": "Horizontal", + "mirror_vertical": "Vertical", "missing": "Faltando", "mobile_app": "Aplicativo MÃŗvel", "mobile_app_download_onboarding_note": "Baixe o aplicativo mÃŗvel usando as opçÃĩes abaixo", "model": "Modelo", - "modify_date": "Data de ModificaÃ§ÃŖo", "month": "MÃĒs", + "monthly_title_text_date_format": "MMMM y", "more": "Mais", - "motion": "Movimento", "move": "Mover", + "move_down": "Mover para baixo", "move_off_locked_folder": "Mover para fora da pasta com senha", "move_to": "Mover para", "move_to_device_trash": "Mover para lixeira", "move_to_lock_folder_action_prompt": "{count} adicionados à pasta com senha", "move_to_locked_folder": "Mover para a pasta com senha", "move_to_locked_folder_confirmation": "Estas fotos e vídeos serÃŖo removidos de todos os ÃĄlbuns e somente poderÃŖo ser visualizados de dentro da pasta com senha", + "move_up": "Mover para cima", + "moved_to_archive": "{count, plural, one {# mídia foi arquivada} other {# mídias foram arquivadas}}", + "moved_to_library": "{count, plural, one {# arquivo foi enviado} other {# arquivos foram enviados}} à biblioteca", "moved_to_trash": "Enviado para a lixeira", + "multiselect_grid_edit_date_time_err_read_only": "NÃŖo Ê possível editar a data do(s) arquivo(s) somente leitura, pulando", + "multiselect_grid_edit_gps_err_read_only": "NÃŖo Ê possível editar a localizaÃ§ÃŖo dos arquivos somente leitura, ignorando", "mute_memories": "Silenciar memÃŗrias", "my_albums": "Meus Álbuns", - "my_immich_description": "Copiar a URL da pÃĄgina atual como o Link do meu Immich", - "my_immich_title": "Link do meu Immich", "name": "Nome", "name_or_nickname": "Nome ou apelido", "name_required": "Nome Ê obrigatÃŗrio", @@ -1480,11 +1572,12 @@ "never": "Nunca", "new_album": "Novo Álbum", "new_api_key": "Nova Chave de API", - "new_feature": "Recurso novo", + "new_date_range": "Nova faixa de datas", "new_password": "Nova senha", "new_person": "Nova Pessoa", "new_pin_code": "Novo cÃŗdigo PIN", "new_pin_code_subtitle": "Esta Ê a primeira vez que estÃĄ acessando a pasta com senha. Crie um cÃŗdigo PIN para acessar esta pÃĄgina de forma segura", + "new_timeline": "Nova Linha do Tempo", "new_update": "Nova atualizaÃ§ÃŖo", "new_user_created": "Novo usuÃĄrio criado", "new_version_available": "NOVA VERSÃO DISPONÍVEL", @@ -1492,6 +1585,7 @@ "next": "Avançar", "next_memory": "PrÃŗxima memÃŗria", "no": "NÃŖo", + "no_actions_added": "Nenhuma aÃ§ÃŖo foi adicionada ainda", "no_albums_found": "Nenhum ÃĄlbum encontrado", "no_albums_message": "Crie um ÃĄlbum para organizar suas fotos e vídeos", "no_albums_with_name_yet": "Parece que vocÃĒ ainda nÃŖo tem nenhum ÃĄlbum com esse nome.", @@ -1502,13 +1596,16 @@ "no_cast_devices_found": "Nenhum dispositivo encontrado", "no_checksum_local": "Nenhum checksum disponível - nÃŖo foi possível carregar os arquivos locais", "no_checksum_remote": "Nenhum checksum disponível - nÃŖo foi possível carregar os arquivos remotos", + "no_configuration_needed": "Nenhuma configuraÃ§ÃŖo necessÃĄria", "no_devices": "Nenhum dispostivio autorizado", "no_duplicates_found": "Nenhuma duplicidade foi encontrada.", "no_exif_info_available": "Sem informaçÃĩes exif disponíveis", "no_explore_results_message": "Envie mais fotos para explorar sua coleÃ§ÃŖo.", "no_favorites_message": "Adicione aos favoritos para encontrar suas melhores fotos e vídeos rapidamente", + "no_filters_added": "Nenhum filtro adicionado ainda", "no_libraries_message": "Crie uma biblioteca externa para ver suas fotos e vídeos", "no_local_assets_found": "Nenhum arquivo local foi encontrado com este checksum", + "no_location_set": "Sem localizaÃ§ÃŖo", "no_locked_photos_message": "Fotos e vídeos na pasta com senha sÃŖo ocultos e nÃŖo serÃŖo exibidos enquanto explora ou pesquisa na biblioteca.", "no_name": "Sem Nome", "no_notifications": "Nenhuma notificaÃ§ÃŖo", @@ -1518,19 +1615,14 @@ "no_results": "Sem resultados", "no_results_description": "Tente um sinônimo ou uma palavra-chave mais geral", "no_shared_albums_message": "Crie um ÃĄlbum para compartilhar fotos e vídeos com pessoas em sua rede", - "no_steps": "Nenhuma etapa adicionada ainda", + "no_uploads_in_progress": "Nenhum envio em progresso", "none": "Nenhum", "not_allowed": "NÃŖo permitido", "not_available": "N/A", "not_in_any_album": "Fora de ÃĄlbum", "not_selected": "NÃŖo selecionado", - "not_set": "NÃŖo definido", "notes": "Notas", "nothing_here_yet": "Ainda nÃŖo existe nada aqui", - "notification_backup_reliability": "Ative as notificaçÃĩes para evitar problemas durante o backup em segundo plano", - "notification_enabled_list_tile_content": "O Immich utiliza notificaçÃĩes para o backup em segundo plano. Gerencie-as nas configuraçÃĩes do seu dispositivo.", - "notification_enabled_list_tile_open_button": "Abrir configuraçÃĩes", - "notification_enabled_list_tile_title": "NotificaçÃĩes ativadas", "notification_permission_dialog_content": "Para ativar as notificaçÃĩes, vÃĄ em ConfiguraçÃĩes e selecione permitir.", "notification_permission_list_tile_content": "Conceda permissÃŖo para ativar notificaçÃĩes.", "notification_permission_list_tile_enable_button": "Ativar notificaçÃĩes", @@ -1542,8 +1634,6 @@ "obtainium_configurator": "Configurador Obtainium", "obtainium_configurator_instructions": "Use o Obtainium para instalar e atualizar o aplicativo Android diretamente do lançamento do Immich no GitHub. Crie uma chave API e selecione a variante para criar o seu link de configuraÃ§ÃŖo Obtainium", "ocr": "OCR", - "ocr_body": "O Immich agora lÃĒ o texto dentro das suas fotos, para que vocÃĒ possa pesquisÃĄ-las pelo que estÃĄ escrito nelas.", - "ocr_title": "Pesquise texto em suas fotos", "official_immich_resources": "Recursos oficiais do Immich", "offline": "Desconectado", "offset": "Deslocamento", @@ -1562,8 +1652,6 @@ "open": "Abrir", "open_calendar": "Abrir calendÃĄrio", "open_in_browser": "Abrir no navegador", - "open_in_immich_body": "Defina o Immich como sua galeria no Android para abrir fotos diretamente de outros aplicativos.", - "open_in_immich_title": "Abrir fotos no Immich", "open_in_map_view": "Mostrar no mapa", "open_in_openstreetmap": "Abrir no OpenStreetMap", "open_the_search_filters": "Abre os filtros de pesquisa", @@ -1572,10 +1660,10 @@ "organize_into_albums": "Organizar em ÃĄlbuns", "organize_into_albums_description": "Colocar imagens existentes em ÃĄlbuns usando as configuraçÃĩes de sincronizaÃ§ÃŖo atuais", "organize_your_library": "Organize sua biblioteca", - "orientation": "OrientaÃ§ÃŖo", "original": "original", "other": "Outro", "other_devices": "Outros dispositivos", + "other_entities": "Outras entidades", "other_variables": "Outras variÃĄveis", "owned": "Seu", "owner": "Dono", @@ -1585,9 +1673,12 @@ "partner_can_access_assets": "Todas suas fotos e vídeos, excetos os Arquivados ou Excluídos", "partner_can_access_location": "A localizaÃ§ÃŖo onde as fotos foram tiradas", "partner_list_user_photos": "Fotos de {user}", + "partner_list_view_all": "Ver tudo", "partner_page_empty_message": "As suas fotos ainda nÃŖo foram compartilhadas com nenhum parceiro.", "partner_page_no_more_users": "NÃŖo hÃĄ mais usuÃĄrios para adicionar", + "partner_page_partner_add_failed": "Falha ao adicionar parceiro", "partner_page_select_partner": "Selecione o parceiro", + "partner_page_shared_to_title": "Compartilhado com", "partner_page_stop_sharing_content": "{partner} nÃŖo poderÃĄ mais acessar as suas fotos.", "partner_sharing": "Compartilhamento com Parceiro", "partners": "Parceiros", @@ -1609,6 +1700,8 @@ "people": "Pessoas", "people_edits_count": "{count, plural, one {# pessoa editada} other {# pessoas editadas}}", "people_feature_description": "Navegar por fotos e vídeos agrupados por pessoas", + "people_selected": "{count, plural, one {# pessoa selecionada} other {# pessoas selecionadas}}", + "people_sidebar_description": "Exibe o link Pessoas na barra lateral", "permanent_deletion_warning": "Aviso para deletar permanentemente", "permanent_deletion_warning_setting_description": "Exibe um aviso ao deletar arquivos de forma permanente", "permanently_delete": "Deletar permanentemente", @@ -1618,17 +1711,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# arquivo permanentemente excluído} other {# arquivos permanentemente excluídos}}", "permission": "PermissÃŖo", "permission_empty": "Sua permissÃŖo nÃŖo deveria ser vazia", + "permission_onboarding_back": "Voltar", + "permission_onboarding_continue_anyway": "Continuar assim mesmo", + "permission_onboarding_get_started": "Começar", + "permission_onboarding_go_to_settings": "Ir para as configuraçÃĩes", + "permission_onboarding_permission_denied": "PermissÃŖo negada. Para utilizar o Immich, conceda permissÃĩes de fotos e vídeo nas configuraçÃĩes.", + "permission_onboarding_permission_granted": "PermissÃŖo concedida! Tudo pronto.", + "permission_onboarding_permission_limited": "PermissÃŖo limitada. Para permitir que o Immich faça backups e gerencie sua galeria, conceda permissÃĩes para fotos e vídeos nas configuraçÃĩes.", + "permission_onboarding_request": "Immich requer permissÃŖo para visualizar suas fotos e vídeos.", "person": "Pessoa", "person_age_months": "{months, plural, one {# mÃĒs} other {# meses}} de idade", "person_age_year_months": "1 ano, {months, plural, one {# mÃĒs} other {# meses}} de idade", - "person_age_years": "{years, plural, one {# ano} other {# anos}} de idade", + "person_age_years": "{years, plural, other {# anos}}", "person_birthdate": "Nasceu em {date}", "person_hidden": "{name}{hidden, select, true { (oculto)} other {}}", "person_recognized": "Pessoa reconhecida", + "person_selected": "Pessoa selecionada", "photo_shared_all_users": "Parece que vocÃĒ compartilhou suas fotos com todos os usuÃĄrios ou nÃŖo tem nenhum usuÃĄrio com quem compartilhar.", "photos": "Fotos", "photos_and_videos": "Fotos e Vídeos", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Fotos}}", "photos_from_previous_years": "Fotos de anos anteriores", + "photos_only": "Somente fotos", "pick_a_location": "Selecione uma localizaÃ§ÃŖo", "pick_custom_range": "Intervalo customizado", "pick_date_range": "Selecione o intervalo de datas", @@ -1647,8 +1751,6 @@ "play_original_video_setting_description": "Preferir por reproduÃ§ÃŖo dos vídeos originais ao invÊs de vídeos transcodificados. Se o arquivo original nÃŖo for compatível, ele pode nÃŖo ser reproduzido corretamente.", "play_transcoded_video": "Reproduzir vídeo transcodificado", "please_auth_to_access": "Por favor autentique-se para acessar", - "plugin_method_filter_type": "Filtro", - "plugin_method_filter_type_description": "Este mÊtodo pode filtrar eventos e impedir condicionalmente que etapas subsequentes sejam executadas", "port": "Porta", "preferences_settings_subtitle": "Gerenciar as preferÃĒncias do aplicativo", "preferences_settings_title": "PreferÃĒncias", @@ -1665,11 +1767,11 @@ "privacy": "Privacidade", "profile": "Perfil", "profile_drawer_app_logs": "Registros", + "profile_drawer_client_server_up_to_date": "Cliente e Servidor estÃŖo atualizados", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Modo apenas leitura habilidato. DÃĒ um toque prolongado na foto do usuÃĄrio para sair deste modo.", "profile_image_of_user": "Imagem do perfil de {user}", "profile_picture_set": "Foto de perfil definida.", - "projection_type": "Tipo de ProjeÃ§ÃŖo", "public_album": "Álbum pÃēblico", "public_share": "Compartilhar Publicamente", "purchase_account_info": "Contribuidor", @@ -1688,6 +1790,7 @@ "purchase_individual_description_2": "Status de contribuidor", "purchase_individual_title": "Indivíduo", "purchase_input_suggestion": "Tem uma chave de produto? Insira a chave abaixo", + "purchase_license_subtitle": "Compre o Immich para apoiar o desenvolvimento contínuo do serviço", "purchase_lifetime_description": "Compra vitalícia", "purchase_option_title": "OPÇÕES DE COMPRA", "purchase_panel_info_1": "Construir o Immich leva muito tempo e esforço. Temos engenheiros trabalhando em tempo integral para tornÃĄ-lo o melhor possível. Nossa missÃŖo Ê fazer com que programas de cÃŗdigo aberto e prÃĄticas empresariais Êticas se tornem uma fonte de renda sustentÃĄvel para os desenvolvedores e tambÊm criar um ecossistema que respeite a privacidade, oferecendo alternativas reais aos serviços de nuvem exploratÃŗrios.", @@ -1720,19 +1823,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# arquivo reatribuído} other {# arquivos reatribuídos}} a uma nova pessoa", "reassing_hint": "Atribuir arquivos selecionados a uma pessoa existente", "recent": "Recente", + "recent_albums": "Álbuns recentes", "recent_searches": "Pesquisas recentes", "recently_added": "Adicionado recentemente", - "recently_added_body": "Acesse diretamente tudo o que vocÃĒ adicionou recentemente em uma pÃĄgina dedicada.", - "recently_added_description": "Navegue pelas suas mídias ordenadas pela data de upload para o Immich", "recently_added_page_title": "Adicionados recentemente", - "recently_added_title": "Adicionado recentemente", "recently_taken": "Tirada recentemente", + "recently_taken_page_title": "Tiradas recentemente", "refresh": "Atualizar", "refresh_encoded_videos": "Atualizar vídeos codificados", "refresh_faces": "Atualizar rostos", "refresh_metadata": "Atualizar metadados", "refresh_thumbnails": "Atualizar miniaturas", "refreshed": "Atualizado", + "refreshes_every_file": "Atualiza todos arquivos", "refreshing_encoded_video": "Atualizando vídeo codificado", "refreshing_faces": "Atualizando rostos", "refreshing_metadata": "Atualizando metadados", @@ -1745,7 +1848,7 @@ "remove_assets_shared_link_confirmation": "Tem certeza de que deseja remover {count, plural, one {# arquivo} other {# arquivos}} desse link compartilhado?", "remove_assets_title": "Remover arquivos?", "remove_custom_date_range": "Remover intervalo de datas personalizado", - "remove_filter": "Remover filtro", + "remove_deleted_assets": "Remover arquivos excluídos", "remove_from_album": "Remover do ÃĄlbum", "remove_from_album_action_prompt": "{count} removido do ÃĄlbum", "remove_from_favorites": "Remover dos favoritos", @@ -1756,22 +1859,29 @@ "remove_memory": "Remover memÃŗria", "remove_photo_from_memory": "Remover foto desta memÃŗria", "remove_tag": "Remover marcador", + "remove_url": "Remover URL", "remove_user": "Remover usuÃĄrio", "removed_api_key": "Removido a Chave de API: {name}", "removed_from_archive": "Removido do arquivo", "removed_from_favorites": "Removido dos favoritos", "removed_from_favorites_count": "{count, plural, one {# Removido} other {# Removidos}} dos favoritos", "removed_memory": "MemÃŗria removida", + "removed_photo_from_memory": "Foto removida da memÃŗria", "removed_tagged_assets": "Marcador removido de {count, plural, one {# arquivo} other {# arquivos}}", "rename": "Renomear", + "repair": "Reparar", + "repair_no_results_message": "Arquivos perdidos ou nÃŖo rastreados aparecem aqui", + "replace_with_upload": "Substituir", "repository": "RepositÃŗrio", "require_password": "Proteger com senha", + "require_user_to_change_password_on_first_login": "Obrigar usuÃĄrio a alterar a senha apÃŗs primeiro login", "rescan": "Reescanear", "reset": "Resetar", "reset_password": "Resetar senha", "reset_people_visibility": "Resetar pessoas ocultas", "reset_pin_code": "Redefinir cÃŗdigo PIN", "reset_pin_code_description": "Se esqueceu seu cÃŗdigo PIN, entre em contato com o administrador do Immich e peça para redefinir", + "reset_pin_code_success": "cÃŗdigo PIN alterado com sucesso", "reset_pin_code_with_password": "VocÃĒ sempre poderÃĄ redefinir seu cÃŗdigo PIN usando a sua senha", "reset_sqlite": "Redefinir o Banco de Dados SQLite", "reset_sqlite_clear_app_data": "Limpar dados", @@ -1785,10 +1895,12 @@ "resolved_all_duplicates": "Todas duplicidades resolvidas", "restore": "Restaurar", "restore_all": "Restaurar tudo", + "restore_trash_action_prompt": "{count} restaurados da lixeira", "restore_user": "Restaurar usuÃĄrio", "restored_asset": "Arquivo restaurado", "resume": "Continuar", "resume_paused_jobs": "Retomar {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "Tentar enviar novamente", "review_duplicates": "Revisar duplicidade", "review_large_files": "Ver arquivos grandes", "role": "FunÃ§ÃŖo", @@ -1796,6 +1908,7 @@ "role_viewer": "Visualizador", "running": "Executando", "save": "Salvar", + "save_to_gallery": "Salvar na galeria", "saved": "Salvo", "saved_api_key": "Chave de API salva", "saved_profile": "Perfil Salvo", @@ -1806,10 +1919,9 @@ "scan": "Escanear", "scan_all_libraries": "Escanear Todas Bibliotecas", "scan_library": "Escanear", + "scan_settings": "OpçÃĩes de escanear", "scanning": "Escaneando", "scanning_for_album": "Escaneando por ÃĄlbum...", - "screencast_mode_description": "Mostre indicadores de açÃĩes do mouse e do teclado na tela", - "screencast_mode_title": "Alterar para o modo de transmissÃŖo da tela", "search": "Pesquisar", "search_albums": "Pesquisar ÃĄlbuns", "search_by_context": "Pesquisar por contexto", @@ -1817,8 +1929,6 @@ "search_by_description_example": "Dia de caminhada no Ibirapuera", "search_by_filename": "Pesquisa por nome de arquivo ou extensÃŖo", "search_by_filename_example": "Por exemplo, IMG_1234.JPG ou PNG", - "search_by_full_path": "Pesquise pelo caminho completo ou nome da pasta", - "search_by_full_path_example": "/JoÃŖo/Projetos/Impressora_3D/2026-07-01 - VocÃĒ pode pesquisar por: Projetos, 3D, Impressora, 2026, etc.", "search_by_ocr": "Buscar por OCR", "search_by_ocr_example": "CafÊ com leite", "search_camera_lens_model": "Buscar por modelo de lente...", @@ -1838,6 +1948,7 @@ "search_filter_location_title": "Selecione a localizaÃ§ÃŖo", "search_filter_media_type": "Tipo de mídia", "search_filter_media_type_title": "Selecione o tipo de mídia", + "search_filter_ocr": "Buscar por OCR", "search_filter_people_title": "Selecione pessoas", "search_filter_star_rating": "AvaliaÃ§ÃŖo", "search_filter_tags_title": "Escolher marcadores", @@ -1848,13 +1959,25 @@ "search_no_people_named": "Nenhuma pessoa chamada \"{name}\"", "search_no_result": "Nenhum resultado encontrado, tente pesquisar por algo diferente", "search_options": "OpçÃĩes de pesquisa", + "search_page_categories": "Categorias", + "search_page_motion_photos": "Fotos com movimento", + "search_page_no_objects": "Nenhuma informaÃ§ÃŖo de objeto disponível", + "search_page_no_places": "Nenhuma informaÃ§ÃŖo de lugares disponível", + "search_page_screenshots": "Capturas de tela", "search_page_search_photos_videos": "Pesquise suas fotos e vídeos", + "search_page_selfies": "Selfies", + "search_page_things": "Coisas", "search_page_view_all_button": "Ver tudo", + "search_page_your_activity": "Sua atividade", + "search_page_your_map": "Seu mapa", "search_people": "Pesquisar pessoas", "search_places": "Pesquisar lugares", "search_rating": "Pesquisar por classificaÃ§ÃŖo...", + "search_result_page_new_search_hint": "Nova pesquisa", "search_settings": "Pesquisar nas ConfiguraçÃĩes", "search_state": "Pesquisar estado...", + "search_suggestion_list_smart_search_hint_1": "A pesquisa inteligente Ê utilizada por padrÃŖo, para pesquisar por metadados, use a sintaxe ", + "search_suggestion_list_smart_search_hint_2": "m:seu-termo-de-pesquisa", "search_tags": "Procurar marcadores...", "search_timezone": "Pesquisar fuso horÃĄrio...", "search_type": "Pesquisar tipo", @@ -1868,26 +1991,28 @@ "select_albums": "Selecionar ÃĄlbuns", "select_all": "Selecionar todos", "select_all_duplicates": "Selecionar todas as duplicatas", + "select_all_in": "Selecionar tudo em {group}", "select_avatar_color": "Selecionar cor do avatar", "select_count": "{count, plural, one {Selecionar #} other {Selecionar #}}", - "select_cutoff_date": "Selecione a data de corte", + "select_cutoff_date": "Selecione a data limite", "select_face": "Selecionar rosto", "select_featured_photo": "Selecionar foto principal", "select_from_computer": "Selecionar do computador", "select_keep_all": "Marcar manter em todos", + "select_library_owner": "Selecione o dono da biblioteca", "select_new_face": "Selecionar novo rosto", "select_people": "Selecionar pessoas", "select_person": "Selecionar pessoa", "select_person_to_tag": "Selecione uma pessoa para marcar", "select_photos": "Selecionar fotos", - "select_quality": "Escolha a qualidade", "select_trash_all": "Marcar lixo em todos", + "select_user_for_sharing_page_err_album": "Falha ao criar ÃĄlbum", "selected": "Selecionados", "selected_count": "{count, plural, one {# selecionado} other {# selecionados}}", "selected_gps_coordinates": "Coordenadas de GPS Selecionada", "send_message": "Enviar mensagem", "send_welcome_email": "Enviar E-mail de boas vindas", - "server_endpoint": "Terminal do Servidor", + "server_endpoint": "URL do Servidor", "server_info_box_app_version": "VersÃŖo do aplicativo", "server_info_box_server_url": "Endereço", "server_offline": "Servidor Indisponível", @@ -1914,31 +2039,42 @@ "setting_image_viewer_original_title": "Carregar imagem original", "setting_image_viewer_preview_subtitle": "Ative para carregar uma imagem de resoluÃ§ÃŖo mÊdia. Desative para carregar diretamente o original ou usar apenas a miniatura.", "setting_image_viewer_preview_title": "Carregar imagem de prÊ-visualizaÃ§ÃŖo", + "setting_image_viewer_title": "Imagens", "setting_languages_apply": "Aplicar", "setting_languages_subtitle": "Alterar o idioma do aplicativo", + "setting_notifications_notify_failures_grace_period": "Notifique falhas de backup em segundo plano: {duration}", + "setting_notifications_notify_hours": "{count} horas", + "setting_notifications_notify_immediately": "imediatamente", "setting_notifications_notify_minutes": "{count} minutos", + "setting_notifications_notify_never": "nunca", "setting_notifications_notify_seconds": "{count} segundos", + "setting_notifications_single_progress_subtitle": "InformaçÃĩes detalhadas sobre o progresso do envio por arquivo", + "setting_notifications_single_progress_title": "Mostrar detalhes do progresso do backup em segundo plano", "setting_notifications_subtitle": "Ajuste suas preferÃĒncias de notificaÃ§ÃŖo", + "setting_notifications_total_progress_subtitle": "Progresso do envio de arquivos (concluídos/total)", + "setting_notifications_total_progress_title": "Mostrar o progresso total do backup em segundo plano", "setting_video_viewer_auto_play_subtitle": "Reproduzir os vídeos automaticamente quando abertos", "setting_video_viewer_auto_play_title": "Reproduzir vídeos automaticamente", - "setting_video_viewer_looping_title": "Repetindo", + "setting_video_viewer_looping_title": "Repetir", "setting_video_viewer_original_video_subtitle": "Ao transmitir um vídeo do servidor, usar o arquivo original, mesmo quando uma versÃŖo transcodificada esteja disponível. Pode fazer com que o vídeo demore para carregar. Vídeos disponíveis localmente sÃŖo exibidos na qualidade original independente desta configuraÃ§ÃŖo.", "setting_video_viewer_original_video_title": "Forçar vídeo original", "settings": "ConfiguraçÃĩes", + "settings_require_restart": "Reinicie o Immich para aplicar esta configuraÃ§ÃŖo", "settings_saved": "ConfiguraçÃĩes salvas", "setup_pin_code": "Criar um cÃŗdigo PIN", "share": "Compartilhar", + "share_action_prompt": "{count} arquivos compartilhados", + "share_add_photos": "Adicionar fotos", + "share_assets_selected": "{count} selecionado", "share_dialog_preparing": "Preparando...", "share_link": "Criar Link", - "share_original": "Original (grande)", - "share_preview": "Miniatura (pequeno)", - "share_quality_body": "Pressione e segure o botÃŖo de compartilhamento para escolher a qualidade da imagem antes de compartilhar.", - "share_quality_title": "Escolha a qualidade do compartilhamento", "shared": "Compartilhado", "shared_album_activities_input_disable": "ComentÃĄrios desativados", "shared_album_activity_remove_content": "Deseja excluir esta atividade?", "shared_album_activity_remove_title": "Excluir atividade", "shared_album_section_people_action_error": "Erro ao sair/remover do ÃĄlbum", + "shared_album_section_people_action_leave": "Remover usuÃĄrio do ÃĄlbum", + "shared_album_section_people_action_remove_user": "Remover usuÃĄrio do ÃĄlbum", "shared_album_section_people_title": "PESSOAS", "shared_by": "Compartilhado por", "shared_by_user": "Compartilhado por {user}", @@ -1947,10 +2083,9 @@ "shared_intent_upload_button_progress_text": "Enviados {current} de {total}", "shared_link_app_bar_title": "Links compartilhados", "shared_link_clipboard_copied_massage": "Copiado para a ÃĄrea de transferÃĒncia", + "shared_link_clipboard_text": "Link: {link}\nSenha: {password}", "shared_link_create_error": "Erro ao criar o link compartilhado", "shared_link_custom_url_description": "Acessar este link com uma URL personalizada", - "shared_link_custom_url_title": "URL customizada", - "shared_link_custom_url_warning": "AtenÃ§ÃŖo: A URL customizada com uma barra pode nÃŖo se comportar como vocÃĒ espera.", "shared_link_edit_description_hint": "Digite a descriÃ§ÃŖo do compartilhamento", "shared_link_edit_expire_after_option_day": "1 dia", "shared_link_edit_expire_after_option_days": "{count} dias", @@ -1962,7 +2097,7 @@ "shared_link_edit_expire_after_option_year": "{count} ano", "shared_link_edit_password_hint": "Digite uma senha para proteger este link", "shared_link_edit_submit_button": "Atualizar link", - "shared_link_error_server_url_fetch": "NÃŖo foi possível obter o URL do servidor", + "shared_link_error_server_url_fetch": "Erro ao abrir a URL do servidor", "shared_link_expires_day": "Expira em {count} dia", "shared_link_expires_days": "Expira em {count} dias", "shared_link_expires_hour": "Expira em {count} hora", @@ -1984,6 +2119,12 @@ "shared_with_partner": "Compartilhado com {partner}", "sharing": "Compartilhamento", "sharing_enter_password": "Digite a senha para visualizar esta pÃĄgina.", + "sharing_page_album": "Álbuns compartilhados", + "sharing_page_description": "Crie ÃĄlbuns compartilhados para compartilhar fotos e vídeos com pessoas em sua rede.", + "sharing_page_empty_list": "LISTA VAZIA", + "sharing_sidebar_description": "Exibe um link para Compartilhamento na barra lateral", + "sharing_silver_appbar_create_shared_album": "Criar ÃĄlbum compartilhado", + "sharing_silver_appbar_share_partner": "Compartilhe com o parceiro", "shift_to_permanent_delete": "pressione ⇧ para excluir permanentemente o arquivo", "show_album_options": "Exibir opçÃĩes do ÃĄlbum", "show_albums": "Exibir ÃĄlbuns", @@ -1995,9 +2136,7 @@ "show_in_timeline": "Exibir na linha do tempo", "show_in_timeline_setting_description": "Exibe fotos e vídeos deste usuÃĄrio na sua linha do tempo", "show_keyboard_shortcuts": "Exibir atalhos do teclado", - "show_less": "Mostrar menos", "show_metadata": "Mostrar metadados", - "show_more_fields": "{count, plural, one {Mostrar # mais campo} other {Mostrar # mais campos}}", "show_or_hide_info": "Exibir ou ocultar informaçÃĩes", "show_password": "Exibir senha", "show_person_options": "Exibir opçÃĩes da pessoa", @@ -2005,7 +2144,6 @@ "show_schema": "Exibir esquema", "show_search_options": "Exibir opçÃĩes de pesquisa", "show_shared_links": "Mostrar links compartilhados", - "show_slideshow_metadata_overlay": "Exibir informaçÃĩes da imagem", "show_slideshow_transition": "Usar transiçÃĩes no modo de apresentaÃ§ÃŖo", "show_supporter_badge": "Insígnia de apoiador", "show_supporter_badge_description": "Mostrar uma insígnia de apoiador", @@ -2017,25 +2155,18 @@ "sign_out": "Sair", "sign_up": "Registrar", "size": "Tamanho", - "skip": "Pular", "skip_to_content": "Ir para o conteÃēdo", "skip_to_folders": "Ir para pastas", "skip_to_tags": "Ir para os marcadores", "slideshow": "ApresentaÃ§ÃŖo", - "slideshow_body": "Relaxe e assista às suas fotos em uma apresentaÃ§ÃŖo de slides em tela cheia.", - "slideshow_metadata_overlay_mode": "ConteÃēdo exibido", - "slideshow_metadata_overlay_mode_description_only": "Apenas a descriÃ§ÃŖo", - "slideshow_metadata_overlay_mode_full": "Tudo", "slideshow_repeat": "Repetir apresentaÃ§ÃŖo de slides", "slideshow_repeat_description": "Voltar para o início quando a apresentaÃ§ÃŖo terminar", "slideshow_settings": "OpçÃĩes de apresentaÃ§ÃŖo", - "slideshow_title": "ApresentaÃ§ÃŖo de slides", - "smart_album": "Álbum inteligente", - "some_assets_already_have_a_location_warning": "Alguns dos arquivos selecionados jÃĄ tem uma localizaÃ§ÃŖo", "sort_albums_by": "Ordenar ÃĄlbuns por...", "sort_created": "Data de criaÃ§ÃŖo", "sort_items": "NÃēmero de itens", "sort_modified": "Data de modificaÃ§ÃŖo", + "sort_newest": "Foto mais nova", "sort_oldest": "Foto mais antiga", "sort_people_by_similarity": "Ordenar pessoas por semelhança", "sort_recent": "Foto mais recente", @@ -2044,6 +2175,7 @@ "stack": "Agrupar", "stack_action_prompt": "{count} agrupados", "stack_duplicates": "Agrupar duplicados", + "stack_select_one_photo": "Selecione uma foto principal para o grupo", "stack_selected_photos": "Agrupar fotos selecionadas", "stacked_assets_count": "{count, plural, one {# arquivo adicionado} other {# arquivos adicionados}} ao grupo", "stacktrace": "Stacktrace", @@ -2052,10 +2184,6 @@ "start_date_before_end_date": "A data de início deve ser antes da data final", "state": "Estado", "status": "Status", - "step_delete": "Excluir etapa", - "step_delete_confirm": "Tem certeza que quer excluir essa etapa?", - "step_details": "Detalhes da etapa", - "steps": "Etapas", "stop_casting": "Parar transmissÃŖo", "stop_motion_photo": "Parar foto em movimento", "stop_photo_sharing": "Parar de compartilhar suas fotos?", @@ -2076,18 +2204,17 @@ "swap_merge_direction": "Alternar direÃ§ÃŖo da mesclagem", "sync": "Sincronizar", "sync_albums": "Sincronizar ÃĄlbuns", + "sync_albums_manual_subtitle": "Sincronize todos as fotos e vídeos enviados para os ÃĄlbuns de backup selecionados", "sync_local": "SincronizaÃ§ÃŖo Local", "sync_remote": "SincronizaÃ§ÃŖo Remota", "sync_status": "Status da SincronizaÃ§ÃŖo", "sync_status_subtitle": "Ver e gerenciar o sistema de sincronizaÃ§ÃŖo", "sync_upload_album_setting_subtitle": "Crie e envie suas fotos e vídeos para o ÃĄlbum selecionado no Immich", - "system_theme": "Tema do sistema", - "system_theme_command_description": "Use o tema padrÃŖo do sistema ({value})", "tag": "Marcador", "tag_assets": "Marcar arquivos", "tag_created": "Marcador criado: {tag}", - "tag_face": "Marcar rosto", "tag_feature_description": "Navegando por fotos e videos agrupados pelo tÃŗpico lÃŗgico do marcador", + "tag_not_found_question": "NÃŖo consegue encontrar o marcador? Crie uma novo aqui.", "tag_people": "Marcar pessoas", "tag_updated": "Marcador foi atualizado: {tag}", "tagged_assets": "{count, plural, one {# Arquivo marcado} other {# Arquivos marcados}}", @@ -2102,10 +2229,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Quantidade de arquivos por linha ({count})", "theme_setting_colorful_interface_subtitle": "Aplica a cor primÃĄria ao fundo.", "theme_setting_colorful_interface_title": "Interface colorida", + "theme_setting_image_viewer_quality_subtitle": "Ajuste a qualidade de imagens detalhadas do visualizador", + "theme_setting_image_viewer_quality_title": "Qualidade das imagens do visualizador", "theme_setting_primary_color_subtitle": "Selecione uma cor para açÃĩes principais e realces.", "theme_setting_primary_color_title": "Cor primÃĄria", "theme_setting_system_primary_color_title": "Usar a cor do sistema", "theme_setting_system_theme_switch": "AutomÃĄtico (seguir a configuraÃ§ÃŖo do sistema)", + "theme_setting_theme_subtitle": "Escolha a configuraÃ§ÃŖo de tema do app", + "theme_setting_three_stage_loading_subtitle": "O carregamento em trÃĒs estÃĄgios oferece a imagem de melhor qualidade em troca de uma velocidade de carregamento mais lenta", + "theme_setting_three_stage_loading_title": "Ative o carregamento em trÃĒs estÃĄgios", "then": "Antes", "they_will_be_merged_together": "Eles serÃŖo mesclados", "third_party_resources": "Recursos de terceiros", @@ -2131,17 +2263,23 @@ "trash_all": "Mover todos para o lixo", "trash_count": "Lixo {count, number}", "trash_delete_asset": "Jogar na lixeira/Excluir Arquivo", + "trash_emptied": "Lixeira esvaziada", "trash_no_results_message": "Fotos e vídeos enviados para o lixo aparecem aqui.", "trash_page_delete_all": "Excluir tudo", + "trash_page_empty_trash_dialog_content": "Deseja esvaziar a lixera? Estes arquivos serÃŖo apagados de forma permanente do Immich", "trash_page_info": "Os itens da lixeira sÃŖo excluídos de forma permanente apÃŗs {days} dias", + "trash_page_no_assets": "Lixeira vazia", + "trash_page_restore_all": "Restaurar tudo", + "trash_page_select_assets_btn": "Selecionar arquivos", + "trash_page_title": "Lixeira ({count})", "trashed_items_will_be_permanently_deleted_after": "Os itens da lixeira serÃŖo deletados permanentemente apÃŗs {days, plural, one {# dia} other {# dias}}.", "trigger": "Gatilho", - "trigger_asset_metadata_extraction": "Extrair Metadado do item", - "trigger_asset_metadata_extraction_description": "Acionado quando o metadado EXIF de um item for extraído", "trigger_asset_uploaded": "Arquivo enviado", "trigger_asset_uploaded_description": "Acionado quando um novo arquivo Ê enviado", + "trigger_description": "Um evento que dÃĄ início ao fluxo", "trigger_person_recognized": "Pessoa reconhecida", - "trigger_person_recognized_description": "Acionado quando uma pessoa Ê reconhecida", + "trigger_person_recognized_description": "Acionado quando uma pessoa Ê detectada", + "trigger_type": "Tipo de gatilho", "troubleshoot": "Diagnosticar", "type": "Tipo", "unable_to_change_pin_code": "NÃŖo foi possível alterar o cÃŗdigo PIN", @@ -2157,7 +2295,6 @@ "unknown": "Desconhecido", "unknown_country": "País desconhecido", "unknown_date": "Data desconhecida", - "unknown_schema": "Esquema desconhecido", "unknown_year": "Ano desconhecido", "unlimited": "Ilimitado", "unlink_motion_video": "Remover relaÃ§ÃŖo com video animado", @@ -2170,20 +2307,23 @@ "unsaved_change": "AlteraÃ§ÃŖo nÃŖo salva", "unselect_all": "Desselecionar todos", "unselect_all_duplicates": "Desselecionar todas as duplicatas", + "unselect_all_in": "Remover seleÃ§ÃŖo de {group}", "unstack": "Desagrupar", "unstack_action_prompt": "{count} desagrupados", "unstacked_assets_count": "{count, plural, one {# arquivo retirado} other {# arquivos retirados}} do grupo", "unsupported_field_type": "Tipo de campo nÃŖo suportado", "unsupported_file_type": "O arquivo {file} nÃŖo foi enviado porque o formato {type} nÃŖo Ê suportado.", "untagged": "Marcador removido", + "untitled_workflow": "Fluxo sem título", "up_next": "A seguir", "update_location_action_prompt": "Atualizar a localizaÃ§ÃŖo de {count} arquivos selecionados para:", "updated_at": "Atualizado em", "updated_password": "Senha atualizada", "upload": "Enviar", "upload_concurrency": "Envios simultÃĸneos", - "upload_day_count": "{date}: {count, plural, one {# envio} other {# envios}}", "upload_details": "Detalhes do envio", + "upload_dialog_info": "Deseja fazer o backup dos arquivos selecionados no servidor?", + "upload_dialog_title": "Enviar arquivo", "upload_error_with_count": "Erro de envio para {count, plural, one {# arquivo} other {# arquivos}}", "upload_errors": "Envio concluído com {count, plural, one {# erro} other {# erros}}, atualize a pÃĄgina para ver os novos arquivos.", "upload_finished": "Envio finalizado", @@ -2193,21 +2333,16 @@ "upload_status_errors": "Erros", "upload_status_uploaded": "Enviado", "upload_success": "Enviado com sucesso, atualize a pÃĄgina para ver os novos arquivos.", - "upload_to_album_body": "Para os usuÃĄrios que nÃŖo utilizam o recurso de upload manual, agora Ê possível adicionar fotos locais diretamente a um ÃĄlbum durante o upload; nÃŖo Ê mais necessÃĄrio fazer o upload para depois adicionÃĄ-las a um ÃĄlbum.", - "upload_to_album_title": "Faça o upload diretamente para um ÃĄlbum", "upload_to_immich": "Enviar para o Immich ({count})", "uploading": "Enviando", "uploading_media": "Enviando mídia", - "uploads": "Envios", - "uploads_count": "{count, plural, one {# envio} other {# envios}}", "url": "URL", "usage": "Uso", "use_biometric": "Usar biometria", - "use_browser_locale": "Utilizar a localidade do navegador", + "use_browser_locale": "Utilizar o locale do browser", "use_browser_locale_description": "Formatar datas, horas e nÃēmeros com base na localidade do navegador", "use_current_connection": "Usar a conexÃŖo atual", "use_custom_date_range": "Usar intervalo de datas personalizado", - "use_template": "Usar modelo", "user": "UsuÃĄrio", "user_has_been_deleted": "Este usuÃĄrio foi excluído.", "user_id": "ID do usuÃĄrio", @@ -2217,6 +2352,7 @@ "user_privacy": "Privacidade do usuÃĄrio", "user_purchase_settings": "Comprar", "user_purchase_settings_description": "Gerenciar sua compra", + "user_role_set": "Definir {user} como {role}", "user_usage_detail": "Detalhes de uso do usuÃĄrio", "user_usage_stats": "Estatísticas de utilizaÃ§ÃŖo da conta", "user_usage_stats_description": "Ver estatísticas de utilizaÃ§ÃŖo da conta", @@ -2226,6 +2362,7 @@ "utilities": "Ferramentas", "validate": "Validar", "validate_endpoint_error": "Digite uma URL vÃĄlida", + "validation_error": "Erro de validaÃ§ÃŖo", "variables": "VariÃĄveis", "version": "VersÃŖo", "version_announcement_closing": "De seu amigo, Alex", @@ -2235,40 +2372,40 @@ "video": "Vídeo", "video_hover_setting": "Reproduzir miniatura do vídeo ao passar o mouse", "video_hover_setting_description": "Reproduzir a miniatura do vídeo ao passar o mouse sobre o item. Mesmo quando desativado, a reproduÃ§ÃŖo pode ser iniciada ao passar o mouse sobre o ícone de reproduÃ§ÃŖo.", - "video_quality": "Qualidade do vídeo", "videos": "Vídeos", "videos_count": "{count, plural, one {# Vídeo} other {# Vídeos}}", + "videos_only": "Somente videos", "view": "Ver", "view_album": "Ver ÃĄlbum", "view_all": "Ver tudo", "view_all_users": "Ver todos os usuÃĄrios", - "view_asset_owners": "Visualizar donos do arquivo", + "view_asset_owners": "Vizualizar donos do arquivo", "view_details": "Ver Detalhes", "view_in_timeline": "Ver na linha do tempo", "view_link": "Ver link", + "view_links": "Ver links", "view_name": "Ver", "view_next_asset": "Ver prÃŗximo arquivo", "view_previous_asset": "Ver arquivo anterior", "view_qr_code": "Ver QR Code", "view_similar_photos": "Ver fotos similares", "view_stack": "Ver grupo", + "view_user": "Visualizar usuÃĄrio", "viewer_remove_from_stack": "Remover do grupo", + "viewer_stack_use_as_main_asset": "Usar como foto principal", + "viewer_unstack": "Desagrupar", "visibility": "Visibilidade", "visibility_changed": "A visibilidade de {count, plural, one {# pessoa foi alterada} other {# pessoas foram alteradas}}", "visual": "Visual", + "visual_builder": "Construtor visual", "waiting": "Na fila", "waiting_count": "Esperando: {count}", "warning": "Aviso", "week": "Semana", "welcome": "Bem-vindo(a)", "welcome_to_immich": "Bem-vindo(a) ao Immich", - "whats_new": "O que hÃĄ de novo", - "whats_new_settings_subtitle": "Veja as novidades do Immich", - "whats_new_version": "VersÃŖo {version}", - "when": "Quando", "width": "Largura", "wifi_name": "Nome do Wi-Fi", - "workflow": "Fluxo de Trabalho", "workflow_delete_prompt": "Tem certeza de que deseja excluir este fluxo?", "workflow_deleted": "Fluxo excluído", "workflow_description": "DescriÃ§ÃŖo do fluxo", @@ -2278,17 +2415,17 @@ "workflow_name": "Nome do fluxo", "workflow_navigation_prompt": "Tem certeza de que deseja sair sem salvar as alteraçÃĩes?", "workflow_summary": "Resumo do fluxo", - "workflow_templates": "Modelos de fluxo", "workflow_update_success": "Fluxo atualizado com sucesso", + "workflow_updated": "Fluxo atualizado", "workflows": "Fluxos", "workflows_help_text": "Fluxos utilizam gatilhos e filtros para automatizar açÃĩes sobre os arquivos", "wrong_pin_code": "CÃŗdigo PIN incorreto", - "x_of_total": "{x}/{total}", "year": "Ano", "years_ago": "{years, plural, one {# ano} other {# anos}} atrÃĄs", "yes": "Sim", "you_dont_have_any_shared_links": "NÃŖo hÃĄ links compartilhados", "your_wifi_name": "Nome do seu Wi-Fi", "zero_to_clear_rating": "Tecle 0 para remover a classificaÃ§ÃŖo", - "zoom_image": "Ampliar imagem" + "zoom_image": "Ampliar imagem", + "zoom_to_bounds": "Ampliar para preencher" } diff --git a/i18n/ro.json b/i18n/ro.json index e17aded3dc..a4f7e94eaa 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -10,31 +10,39 @@ "active": "Active", "active_count": "Activ: {count}", "activity": "Activitate", + "activity_changed": "Activitatea este {enabled, select, true {activată} other {dezactivată}}", "add": "Adaugă", "add_a_description": "Adaugă o descriere", "add_a_location": "Adaugă o locație", "add_a_name": "Adaugă un nume", "add_a_title": "Adaugă un titlu", "add_action": "Adaugă acÅŖiune", + "add_action_description": "Click pentru a adăuga o acțiune de rulat", "add_assets": "Adaugă elemente", "add_birthday": "Adaugă zi de naștere", "add_endpoint": "Adaugă punct final", "add_exclusion_pattern": "Adăugă un model de excludere", + "add_filter": "Adaugă filtru", + "add_filter_description": "Click pentru a adăuga o condiție de filtrare", "add_location": "Adaugă locație", + "add_more_users": "Adaugă mai mulți utilizatori", "add_partner": "Adaugă partener", + "add_path": "Adaugă o cale", "add_photos": "Adaugă fotografii", - "add_step": "AdaugĮŽ pas", "add_tag": "Adaugă etichetă", "add_to": "Adaugă laâ€Ļ", "add_to_album": "Adaugă ÃŽn album", "add_to_album_bottom_sheet_added": "Adăugat ÃŽn {album}", "add_to_album_bottom_sheet_already_exists": "Deja ÃŽn {album}", "add_to_album_bottom_sheet_some_local_assets": "Unele resurse locale nu au putut fi adăugate la album", + "add_to_album_toggle": "Selectează/deselectează {album}", "add_to_albums": "Adaugă la albume", "add_to_albums_count": "Adaugă la albume ({count})", "add_to_bottom_bar": "Adaugă la", + "add_to_shared_album": "Adaugă la album partajat", "add_upload_to_stack": "Încarcă și adaugă la stivă", "add_url": "Adaugă adresa URL", + "add_workflow_step": "Adaugă un pas ÃŽn workflow", "added_to_archive": "Adăugat la arhivă", "added_to_favorites": "Adăugat la favorite", "added_to_favorites_count": "Adăugat {count, number} la favorite", @@ -53,7 +61,7 @@ "backup_onboarding_1_description": "copie externă ÃŽn cloud sau ÃŽntr-o altă locație fizică.", "backup_onboarding_2_description": "copii locale pe diferite dispozitive. Include fișierele principale și o copie de rezervă a acestor fișiere la nivel local.", "backup_onboarding_3_description": "numărul total de copii ale datelor dvs., inclusiv fișierele originale. Aceasta include 1 copie externă și 2 copii locale.", - "backup_onboarding_description": "Pentru a vă proteja datele, vă recomandăm să utilizați o strategie de backup 3-2-1. Pentru o soluție completă de backup, ar trebui să păstrați copii ale fotografiilor/videoclipurilor ÃŽncărcate, precum și ale bazei de date Immich.", + "backup_onboarding_description": "Pentru a vă proteja datele, vă recomandăm să utilizați strategia de backup 3-2-1. Pentru o soluție completă de backup, ar trebui să păstrați copii ale fotografiilor/videoclipurilor ÃŽncărcate, precum și ale bazei de date Immich.", "backup_onboarding_footer": "Pentru mai multe informații despre copierea de rezervă a Immich, consultați documentația.", "backup_onboarding_parts_title": "O copie de rezervă 3-2-1 include:", "backup_onboarding_title": "Copii de rezervă", @@ -73,7 +81,6 @@ "cron_expression_description": "Setați intervalul de scanare folosind formatul cron. Pentru mai multe informații, consultați de ex. Crontab Guru", "cron_expression_presets": "Presetări de expresie cron", "disable_login": "Dezactivați autentificarea", - "download_csv": "Descarcă CSV", "duplicate_detection_job_description": "Rulați ÃŽnvățarea automată pe materiale pentru a detecta imagini similare. Se bazează pe Căutare Inteligentă", "exclusion_pattern_description": "Modelele de excludere vă permit să ignorați fișierele și folderele atunci cÃĸnd vă scanați biblioteca. Acest lucru este util dacă aveți foldere care conțin fișiere pe care nu doriți să le importați, cum ar fi fișierele RAW.", "export_config_as_json_description": "Descărcați configurația actuală a sistemului ca fișier JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Calitatea miniaturii de la 1 la 100. O valoare mai mare oferă o calitate mai bună, dar produce fișiere mai mari și poate reduce receptivitatea aplicației.", "image_thumbnail_title": "Setari miniaturi", "import_config_from_json_description": "Importați configurația sistemului ÃŽncărcÃĸnd un fișier de configurare JSON", - "integrity_checks_checksum_files": "Fișiere sumă de control", - "integrity_checks_checksum_files_description": "Configurează verificarea sumei de control", - "integrity_checks_checksum_files_enable_description": "Activează verificarea sumei de control", - "integrity_checks_checksum_files_percentage_limit": "Limită procentuală", - "integrity_checks_checksum_files_percentage_limit_description": "Configurează procentajul maxim, cuprins ÃŽntre 0,01 și 1, care indică frecvența cu care trebuie să se efectueze verificarea sumei de control la fiecare interval.", - "integrity_checks_checksum_files_time_limit": "Timp limită", - "integrity_checks_checksum_files_time_limit_description": "Configurează durata maximă pentru care verificarea sumei de control ar trebui să ruleze la fiecare interval. (ms)", - "integrity_checks_missing_files": "Fișiere lipsă", - "integrity_checks_missing_files_description": "Configurează frecvența și activează sau dezactivează verificarea fișierelor lipsă", - "integrity_checks_missing_files_enable_description": "Activează verificarea fișierelor lipsă", - "integrity_checks_settings": "Verificări de integritate", - "integrity_checks_settings_description": "Gestionează intervalele de verificare a integrității", - "integrity_checks_untracked_files": "Fișiere neurmărite", - "integrity_checks_untracked_files_description": "Configurează frecvența și activează sau dezactivează verificarea fișierelor neurmărite", - "integrity_checks_untracked_files_enable_description": "Activează verificarea fișierelor neurmărite", "job_concurrency": "Concurență {job}", "job_created": "Sarcină creată", "job_not_concurrency_safe": "Această sarcină nu este sigură pentru a rula ÃŽn concurență.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Activează căutarea inteligentă", "machine_learning_smart_search_enabled_description": "Dacă este dezactivată, imaginile nu vor fi codificate pentru căutarea inteligentă.", "machine_learning_url_description": "URL-ul serverului de ÃŽnvățare automată. Dacă sunt furnizate mai multe URL-uri, fiecare server va fi ÃŽncercat pe rÃĸnd, pÃĸnă cÃĸnd unul răspunde cu succes, ÃŽn ordine de la primul pÃĸnă la ultimul. Serverele care nu răspund vor fi ignorate temporar pÃĸnă revin online.", - "maintenance_backup_management": "Gestionare backup", "maintenance_delete_backup": "Sterge Backup", "maintenance_delete_backup_description": "Acest fisier va fi sters permanent.", "maintenance_delete_error": "Stergerea backup-ului nu a reusit.", - "maintenance_integrity_check": "Verifică", - "maintenance_integrity_check_all": "Verifică tot", - "maintenance_integrity_checksum_mismatch": "Neconcordanță sumă de control", - "maintenance_integrity_checksum_mismatch_description": "Fișiere a căror sumă de control de pe disc nu se potrivește cu suma de control stocată de Immich ÃŽn baza sa de date.", - "maintenance_integrity_checksum_mismatch_job": "Verificați neconcordanțele sumelor de control", - "maintenance_integrity_checksum_mismatch_refresh_job": "ReÃŽmprospătarea rapoartelor privind neconcordanța sumelor de control", - "maintenance_integrity_missing_file": "Fișiere lipsă", - "maintenance_integrity_missing_file_description": "Fișiere pe care Immich le-a urmărit ÃŽn baza sa de date, dar care nu există ÃŽn sistemul de fișiere.", - "maintenance_integrity_missing_file_job": "Verifică dacă există fișiere lipsă", - "maintenance_integrity_missing_file_refresh_job": "ReÃŽmprospătați rapoartele privind fișierele lipsă", - "maintenance_integrity_report": "Raport de integritate", - "maintenance_integrity_untracked_file": "Fișiere neurmărite", - "maintenance_integrity_untracked_file_description": "Fișiere din directoarele lui Immich despre care Immich nu are nicio ÃŽnregistrare.", - "maintenance_integrity_untracked_file_job": "Verifică dacă există fișiere neurmărite", - "maintenance_integrity_untracked_file_refresh_job": "ReÃŽmprospătează rapoartele fișierelor neurmărite", "maintenance_restore_backup": "Restaureaza Backup", "maintenance_restore_backup_description": "Immich va fi șters si restaurat din backup-ul ales. Va fi creat un nou backup ÃŽnainte de a continua.", "maintenance_restore_backup_different_version": "Acest backup a fost creat folosind o versiune diferita de Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Activare notificări pe email", "notification_settings": "Setări notificare", "notification_settings_description": "Gestionează setările pentru notificări, inclusiv adresa de email", - "oauth_allow_insecure_requests": "Permite cereri nesigure", - "oauth_allow_insecure_requests_description": "AVERTISMENT: Acest lucru dezactivează validarea certificatelor TLS pentru cererile OAuth și te poate expune la atacuri MITM.", "oauth_auto_launch": "Pornire automată", "oauth_auto_launch_description": "Lansează automat autorizarea OAuth la accesarea paginii de login", "oauth_auto_register": "Auto ÃŽnregistrare", @@ -300,11 +274,9 @@ "oauth_button_text": "Text buton", "oauth_client_secret_description": "Necesar pentru un client confidențial sau dacă PKCE (Proof Key for Code Exchange) nu este suportat pentru un client public.", "oauth_enable_description": "Autentifică-te cu OAuth", - "oauth_end_session_url_description": "Redirecționează utilizatorul către acest URI atunci cÃĸnd se deconectează.", "oauth_mobile_redirect_uri": "URI de redirecționare mobilă", "oauth_mobile_redirect_uri_override": "Înlocuire URI de redirecționare mobilă", "oauth_mobile_redirect_uri_override_description": "Activați atunci cÃĸnd furnizorul OAuth nu permite un URI mobil, precum ''{callback}''", - "oauth_prompt_description": "Parametrul promptului (de exemplu, select_account, login, consent)", "oauth_role_claim": "Revendicare de rol", "oauth_role_claim_description": "Acordă automat acces de administrator ÃŽn funcție de prezența acestei revendicări. Revendicarea poate avea fie 'utilizator', fie 'administrator'.", "oauth_settings": "OAuth", @@ -320,7 +292,7 @@ "oauth_timeout_description": "Timp de expirare pentru solicitări ÃŽn milisecunde", "ocr_job_description": "Folosește ÃŽnvățarea automată pentru recunoașterea textului din imagini", "password_enable_description": "Autentificare cu email și parolĮŽ", - "password_settings": "Autentificare cu parolĮŽ", + "password_settings": "Autentificare cu ParolĮŽ", "password_settings_description": "GestioneazĮŽ setĮŽrile de autentificare cu parola", "paths_validated_successfully": "Toate cĮŽile au fost validate cu succes", "person_cleanup_job": "Ștergere persoane", @@ -329,10 +301,8 @@ "queues_page_description": "Pagina cu cozi de sarcini administrative", "quota_size_gib": "Spațiu de stocare alocat (GiB)", "refreshing_all_libraries": "Bibliotecile sunt ÃŽn curs de reÃŽmprospĮŽtare", - "registration": "Înregistrare administratori", + "registration": "Înregistrare Administratori", "registration_description": "Deoarece sunteți primul utilizator de pe sistem, veți fi desemnat ca administrator și sunteți responsabil pentru sarcinile administrative, iar utilizatorii suplimentari vor fi creați de dumneavoastră.", - "release_channel_release_candidate": "Candidat pentru lansare", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Elimina sarcinile eșuate", "require_password_change_on_login": "ObligĮŽ utilizatorul sĮŽ ÃŽČ™i schimbe parola la prima autentificare", "reset_settings_to_default": "ReseteazĮŽ setĮŽrile la valorile implicite", @@ -344,7 +314,7 @@ "server_external_domain_settings_description": "Domeniu utilizat pentru link-uri externe", "server_public_users": "Utilizatori publici", "server_public_users_description": "Toți utilizatorii (nume și e-mail) sunt listați atunci cÃĸnd adăugați un utilizator la albumele partajate. CÃĸnd este dezactivată, lista de utilizatori va fi disponibilă numai pentru utilizatorii admin.", - "server_settings": "SetĮŽri server", + "server_settings": "SetĮŽri Server", "server_settings_description": "GestioneazĮŽ setĮŽrile serverului", "server_stats_page_description": "Pagina cu statistici ale serverului de administrare", "server_welcome_message": "Mesaj de bun-venit", @@ -381,7 +351,7 @@ "template_settings": "Șabloane de Notificare", "template_settings_description": "Gestionați șabloanele personalizate pentru notificări", "theme_custom_css_settings": "CSS personalizat", - "theme_custom_css_settings_description": "Designul Immich poate fi personalizat prin utilizarea foilor de stil CSS.", + "theme_custom_css_settings_description": "Foile de stil ÃŽn cascadă (CSS) permit personalizarea designului Immich.", "theme_settings": "Setări temă", "theme_settings_description": "Gestionează personalizarea interfeței web Immich", "thumbnail_generation_job": "Generare miniaturi", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Se aplică doar la VAAPI și QSV. Setează nodul DRI utilizat pentru transcodarea hardware.", "transcoding_preset_preset": "Presetare (-preset)", "transcoding_preset_preset_description": "Viteza de compresie. Presetările mai lente produc fișiere mai mici și ÃŽmbunătățesc calitatea atunci cÃĸnd vizezi o anumită rată de biți. VP9 ignoră vitezele de compresie mai mari decÃĸt 'mai rapid'.", - "transcoding_realtime": "Transcodare ÃŽn timp real [EXPERIMENTAL]", - "transcoding_realtime_description": "Permite transcodarea ÃŽn timp real ÃŽn timpul redării videoclipului. Activează schimbarea calității, dar poate cauza latență mai mare și sacadări, ÃŽn funcție de capacitățile serverului.", - "transcoding_realtime_enabled": "Activează transcodarea ÃŽn timp real", - "transcoding_realtime_enabled_description": "Dacă este dezactivat, serverul nu va iniția noi sesiuni de transcodare ÃŽn timp real.", - "transcoding_realtime_resolutions": "Rezoluții", - "transcoding_realtime_resolutions_description": "Rezoluțiile disponibile pentru transcodare ÃŽn timp real. Rezoluțiile mai mari pot cauza probleme la redare dacă serverul nu le poate transcoda suficient de repede.", - "transcoding_realtime_video_codecs": "Codecuri video", - "transcoding_realtime_video_codecs_description": "Codecurile video oferite pentru transcodare ÃŽn timp real. Clienții vor alege cea mai bună opțiune pe care o acceptă ÃŽn timpul redării. AV1 este mai eficient decÃĸt HEVC, care este mai eficient decÃĸt H.264. CÃĸnd utilizezi accelerarea hardware, selectează doar codecurile pe care acceleratorul le poate codifica. CÃĸnd utilizezi transcodare software, reține că H.264 este mai rapid decÃĸt AV1, care este mai rapid decÃĸt HEVC.", "transcoding_reference_frames": "Cadre de referință", "transcoding_reference_frames_description": "Numărul de cadre de referință atunci cÃĸnd se comprimă un cadru dat. Valorile mai mari ÃŽmbunătățesc eficiența compresiei, dar ÃŽncetinesc codarea. 0 setează această valoare automat.", "transcoding_required_description": "Numai videoclipuri care nu sunt ÃŽntr-un format acceptat", @@ -478,8 +440,6 @@ "user_settings_description": "GestioneazĮŽ setĮŽrile utilizatorului", "user_successfully_removed": "Utilizatorul {email} a fost șters cu succes.", "users_page_description": "Pagina utilizatorilor administratori", - "version_check_channel": "Canal de lansare", - "version_check_channel_description": "Alege canalul de lansare pentru care dorești să primești notificări despre versiuni noi", "version_check_enabled_description": "Activează verificarea versiunii", "version_check_implications": "Funcția de verificare a versiunii se bazează pe comunicarea periodică cu {server}", "version_check_settings": "Verificare versiune", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Șterge cache-ul", "advanced_settings_clear_image_cache_error": "Ștergerea cache-ului de imagini a eșuat", "advanced_settings_clear_image_cache_success": "{size} șterși cu succes", + "advanced_settings_enable_alternate_media_filter_subtitle": "Utilizați această opțiune pentru a filtra conținutul media ÃŽn timpul sincronizării pe baza unor criterii alternative. Încercați numai dacă ÃŽntÃĸmpinați probleme cu aplicația la detectarea tuturor albumelor.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTAL] Utilizați filtrul alternativ de sincronizare a albumelor de pe dispozitiv", "advanced_settings_log_level_title": "Nivel log: {level}", "advanced_settings_prefer_remote_subtitle": "Unele dispozitive ÃŽncarcă extrem de lent miniaturile din resursele locale. Activați această setare pentru a ÃŽncărca imagini la distanță.", "advanced_settings_prefer_remote_title": "Preferă fotografii la distanță", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Headere proxy personalizate [EXPERIMENTAL]", "advanced_settings_readonly_mode_subtitle": "Activează modul doar-citire, ÃŽn care fotografiile pot fi doar vizualizate, iar acțiuni precum selectarea mai multor imagini, partajarea, redarea pe alt dispozitiv sau ștergerea sunt dezactivate. Activează/Dezactivează modul doar-citire din avatarul utilizatorului de pe ecranul principal", "advanced_settings_readonly_mode_title": "Mod doar citire", + "advanced_settings_self_signed_ssl_subtitle": "Omite verificare certificate SSL pentru distinația server-ului, necesar pentru certificate auto-semnate.", + "advanced_settings_self_signed_ssl_title": "Permite certificate SSL auto-semnate [EXPERIMENTAL]", "advanced_settings_sync_remote_deletions_subtitle": "Ștergeți sau restaurați automat un element de pe acest dispozitiv atunci cÃĸnd acțiunea este efectuată pe web", "advanced_settings_sync_remote_deletions_title": "Sincronizează stergerile efectuate la distanță [EXPERIMENTAL]", "advanced_settings_tile_subtitle": "Setări avansate pentru utilizator", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ești sigur că vrei să ștergi albumul {album}?", "album_delete_confirmation_description": "Dacă acest album este partajat, alți utilizatori nu vor mai putea accesa.", "album_deleted": "Album șters", + "album_info_card_backup_album_excluded": "EXCLUSE", + "album_info_card_backup_album_included": "INCLUSE", "album_info_updated": "Informații album actualizate", + "album_leave": "Părăsiți albumul?", + "album_leave_confirmation": "Sigur doriți să părăsiți {album}?", "album_name": "Nume Album", "album_options": "Opțiuni album", "album_remove_user": "Eliminare utilizator?", "album_remove_user_confirmation": "Ești sigur că dorești eliminarea {user}?", "album_search_not_found": "Nu s-au găsit albume care să corespundă căutării dumneavoastră", + "album_selected": "Album selectat", "album_share_no_users": "Se pare că ai partajat acest album cu toți utilizatorii sau nu ai niciun utilizator cu care să-l partajezi.", "album_summary": "Rezumat album", "album_updated": "Album actualizat", "album_updated_setting_description": "Primiți o notificare prin e-mail cÃĸnd un album partajat are elemente noi", "album_upload_assets": "Încarcă elemente din calculatorul personal și adaugă in album", + "album_user_left": "A părăsit {album}", + "album_user_removed": "{user} eliminat", + "album_viewer_appbar_delete_confirm": "Ești sigur că vrei să ștergi acest album din contul tău?", "album_viewer_appbar_share_err_delete": "Ștergere album eșuată", + "album_viewer_appbar_share_err_leave": "Părăsire album eșuată", + "album_viewer_appbar_share_err_remove": "Probleme la ștergerea resurselor din album", + "album_viewer_appbar_share_err_title": "Schimbare titlu album eșuată", + "album_viewer_appbar_share_leave": "Părăsește album", + "album_viewer_appbar_share_to": "Distribuire către", "album_viewer_page_share_add_users": "Adaugă utilizatori", "album_with_link_access": "Permite oricui cu link-ul să vadă fotografiile și persoanele din acest album.", "albums": "Albume", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Ordinea inițială de sortare a pozelor la crearea de albume noi.", "albums_feature_description": "Colecții de date care pot fi partajate cu alți utilizatori.", "albums_on_device_count": "{count} albume pe dispozitiv", + "albums_selected": "{count, plural, one {# album selectat} few {# albume selectate} other {# de albume selectate}}", "all": "Toate", "all_albums": "Toate albumele", "all_people": "Toți oamenii", "all_photos": "Toate fotografiile", "all_videos": "Toate videoclipurile", "allow_dark_mode": "Permite mod ÃŽntunecat", + "allow_edits": "Permite editări", "allow_public_user_to_download": "Permite utilizatorului public să descarce", "allow_public_user_to_upload": "Permite utilizatorului public să ÃŽncarce", "allowed": "Permis", @@ -549,12 +528,14 @@ "always_keep": "Păstrează ÃŽntotdeauna", "always_keep_photos_hint": "Eliberează Spațiu va păstra toate fotografiile de pe acest dispozitiv.", "always_keep_videos_hint": "Eliberează Spațiu va păstra toate video-urile de pe acest dispozitiv.", + "anti_clockwise": "În sens invers acelor de ceasornic", "api_key": "Cheie API", "api_key_description": "Această valoare va fi afișată o singură dată. Vă rugăm să vă asigurați că o copiați ÃŽnainte de a ÃŽnchide fereastra.", "api_key_empty": "Numele cheii API nu trebuie să fie gol", "api_keys": "Chei API", "app_architecture_variant": "Variantă (Arhitectură)", "app_bar_signout_dialog_content": "Ești sigur că vrei să te deconectezi?", + "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Deconectare", "app_download_links": "Linkuri de descărcare ÃŽn aplicație", "app_settings": "Setări aplicație", @@ -565,19 +546,27 @@ "archive": "Arhivă", "archive_action_prompt": "{count} adăugate la Arhivă", "archive_or_unarchive_photo": "ArhiveazĮŽ sau dezarhiveazĮŽ fotografia", + "archive_page_no_archived_assets": "Nu au fost găsite resurse favorite", + "archive_page_title": "Arhivă ({count})", "archive_size": "Mărime arhivă", "archive_size_description": "Configurează dimensiunea arhivei pentru descărcări (ÃŽn GiB)", "archived": "Arhivat", "archived_count": "{count, plural, one {Arhivat} few {# arhivate} other {# arhivate}}", "are_these_the_same_person": "Sunt aceștia aceeași persoană?", "are_you_sure_to_do_this": "Sunteți sigur că doriți să faceți acest lucru?", + "array_field_not_fully_supported": "CÃĸmpurile necesită editare manuală JSON", + "asset_action_delete_err_read_only": "Fișierele cu permisiuni doar de citire nu au putut fi șterse, omitere", + "asset_action_share_err_offline": "Fișierele offline nu au putut accesate, omitere", "asset_added_to_album": "Adăugat la album", "asset_adding_to_album": "Se adaugă la albumâ€Ļ", "asset_created": "Resurse create", - "asset_day_count": "{date}: {count, plural, one {# resursă} few {# resurse} other {# de resurse}}", "asset_description_updated": "Descrierea resursei a fost actualizată", + "asset_filename_is_offline": "Resursa {filename} este offline", + "asset_has_unassigned_faces": "Resursa are fețe neatribuite", "asset_hashing": "Calculare amprentă digitalăâ€Ļ", "asset_list_group_by_sub_title": "Grupare după", + "asset_list_layout_settings_dynamic_layout_title": "Aspect dinamic", + "asset_list_layout_settings_group_automatically": "Automat", "asset_list_layout_settings_group_by": "Grupează resurse după", "asset_list_layout_settings_group_by_month_day": "Lună + zi", "asset_list_layout_sub_title": "Aspect", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Obiect negăsit pe iCloud. Obiectul poate fi inaccesibil din cauza stocării incorecte pe iCloud", "asset_offline": "Resursă Offline", "asset_offline_description": "Această resursă externă nu mai este găsită pe disc. Contactează te rog administratorul tău Immich pentru ajutor.", + "asset_restored_successfully": "Date restaurate cu succes", "asset_skipped": "Sărit", "asset_skipped_in_trash": "În coșul de gunoi", + "asset_trashed": "Resursă ștearsă", "asset_troubleshoot": "Depanare resursă", "asset_uploaded": "Încărcat", "asset_uploading": "Se incarcăâ€Ļ", "asset_viewer_settings_subtitle": "Gestionați setările de vizualizare a galeriei", "asset_viewer_settings_title": "Vizualizator resurse", "assets": "Resurse", + "assets_added_count": "Adăugat {count, plural, one {# resursă} other {# resurse}}", "assets_added_to_album_count": "Am adăugat {count, plural, one {# resursă} other {# resurse}} ÃŽn album", - "assets_added_to_album_partial_count": "Am adăugat {successCount} din {totalCount} {totalCount, plural, one {resursă} few {resurse} other {de resurse}} ÃŽn album", "assets_added_to_albums_count": "Au fost adăugate {assetTotal, plural, one {# element} other {# elemente}} la {albumTotal, plural, one {# album} other {# albume}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} nu pot fi adăugate ÃŽn album", "assets_cannot_be_added_to_albums": "{count, plural, one {Elementul} other {Elementele}} nu poate fi adăugat la niciunul dintre albume", "assets_count": "{count, plural, one {# resursă} other {# resurse}}", + "assets_deleted_permanently": "{count} poză/poze ștearsă/șterse permanent", + "assets_deleted_permanently_from_server": "{count} poză/poze ștearsă/șterse permanent din serverul Immich", + "assets_downloaded_failed": "{count, plural, one {S-a descărcat # fișier – {error} fișier eșuat} other {S-au descărcat # fișiere – {error} fișiere eșuate}}", + "assets_downloaded_successfully": "{count, plural, one {S-a descărcat cu succes # fișier} other {S-au descărcat cu succes # fișiere}}", "assets_moved_to_trash_count": "Am mutat {count, plural, one {# resursă} other {# resurse}} ÃŽn coșul de gunoi", "assets_permanently_deleted_count": "Șters permanent {count, plural, one {# resursă} other {# resurse}}", "assets_removed_count": "Eliminat {count, plural, one {# resursă} other {# resurse}}", + "assets_removed_permanently_from_device": "{count} resurse eliminate permanent de pe dispozitivul dvs", "assets_restore_confirmation": "Ești sigur că vrei să restaurezi toate resursele tale din coșul de gunoi? Nu poți anula această acțiune! Ține minte că resursele offline nu se restaurează astfel.", "assets_restored_count": "Restaurat {count, plural, one {# resursă} other {# resurse}}", + "assets_restored_successfully": "{count} resursă(e) restaurate cu succes", "assets_trashed": "{count} resursă(e) eliminate", "assets_trashed_count": "Mutat ÃŽn coșul de gunoi {count, plural, one {# resursă} other {# resurse}}", + "assets_trashed_from_server": "{count} resursă(e) eliminate de pe serverul Immich", "assets_were_part_of_album_count": "{count, plural, one {Resursa era} other {Resursele erau}} deja parte din album", "assets_were_part_of_albums_count": "{count, plural, one {Asset was} other {Assets were}} deja parte din albume", "authorized_devices": "Dispozitive Autorizate", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Derulare slideshow automat", "back": "Înapoi", "back_close_deselect": "Înapoi, ÃŽnchidere sau deselectare", + "background_backup_running_error": "Procesul de backup ÃŽn fundal este activ, nu se poate porni backup manual", "background_location_permission": "Permisiune locație ÃŽn fundal", "background_location_permission_content": "Pentru a putea schimba rețeaua activă ÃŽn fundal, Immich are nevoie de acces *permanent* la locația precisă pentru a citi numele rețelei Wi-Fi", "background_options": "Opțiuni de fundal", "backup": "Backup", + "backup_album_selection_page_albums_device": "Albume ÃŽn dispozitiv ({count})", "backup_album_selection_page_albums_tap": "Apasă odata pentru a include, de două ori pentru a exclude", "backup_album_selection_page_assets_scatter": "Resursele pot fi ÃŽmprăștiate ÃŽn mai multe albume. Prin urmare, albumele pot fi incluse sau excluse ÃŽn timpul procesului de backup.", "backup_album_selection_page_select_albums": "Selectează albume", "backup_album_selection_page_selection_info": "Informații selecție", + "backup_album_selection_page_total_assets": "Total resurse unice", "backup_albums_sync": "Sincronizarea albumelor de rezervă", + "backup_all": "Toate", + "backup_background_service_backup_failed_message": "Eșuare backup resurse. ReÃŽncercareâ€Ļ", "backup_background_service_complete_notification": "Backup resurse finalizat", + "backup_background_service_connection_failed_message": "Conectare la server eșuată. ReÃŽncercareâ€Ļ", + "backup_background_service_current_upload_notification": "Încărcare {filename}", "backup_background_service_default_notification": "Verificare resurse noiâ€Ļ", + "backup_background_service_error_title": "Eroare backup", "backup_background_service_in_progress_notification": "Se face backup al resurselor taleâ€Ļ", + "backup_background_service_upload_failure_notification": "Încărcare eșuată {filename}", "backup_controller_page_albums": "Backup albume", + "backup_controller_page_background_app_refresh_disabled_content": "Activează reÃŽmprospătarea aplicației ÃŽn fundal ÃŽn Setări > General > ReÃŽmprospătare aplicații ÃŽn fundal pentru a folosi copia de siguranță ÃŽn fundal.", + "backup_controller_page_background_app_refresh_disabled_title": "ReÃŽmprospătarea aplicației ÃŽn fundal este dezactivată", + "backup_controller_page_background_app_refresh_enable_button_text": "Mergi la setări", "backup_controller_page_background_battery_info_link": "Arată-mi cum", "backup_controller_page_background_battery_info_message": "Pentru cea mai bună experiență a backup-ului ÃŽn fundal, te rugăm să dezactivezi orice optimizare pentru baterie care restricționează activitatea ÃŽn fundal pentru Immich.\n\nDeoarece aceasta este specifică fiecărui dispozitiv, te rugăm verifică informațiile necesare tipului tău de dispozitiv.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Optimizări baterie", + "backup_controller_page_background_charging": "Doar ÃŽn timpul ÃŽncărcării", + "backup_controller_page_background_configure_error": "Configurare serviciu ÃŽn fundal eșuată", "backup_controller_page_background_delay": "AmÃĸnare copie de siguranță a resurselor noi: {duration}", + "backup_controller_page_background_description": "Activează backup-ul ÃŽn fundal pentru a ÃŽncărca resursele automat pe server fără a fi nevoie să deschizi aplicația", + "backup_controller_page_background_is_off": "Backup-ul automat ÃŽn fundal este dezactivat", + "backup_controller_page_background_is_on": "Backup-ul automat ÃŽn fundal este activat", + "backup_controller_page_background_turn_off": "Dezactivează serviciul ÃŽn fundal", + "backup_controller_page_background_turn_on": "Activează serviciul ÃŽn fundal", + "backup_controller_page_background_wifi": "Numai prin Wi-Fi", "backup_controller_page_backup": "Backup", "backup_controller_page_backup_selected": "Selectat(e): ", "backup_controller_page_backup_sub": "S-a făcut backup pentru fotografii și videoclipuri", + "backup_controller_page_created": "Creat la: {date}", + "backup_controller_page_desc_backup": "Activează backup-ul ÃŽn prim-plan pentru a ÃŽncărca resursele pe server cÃĸnd deschizi aplicația.", "backup_controller_page_excluded": "Exclus(e): ", + "backup_controller_page_failed": "Eșuate ({count})", + "backup_controller_page_filename": "Nume fișier: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informații backup", "backup_controller_page_none_selected": "Nici o selecție", "backup_controller_page_remainder": "Rămas(e)", "backup_controller_page_remainder_sub": "Fotografii și videoclipuri din selecție rămase pentru backup", "backup_controller_page_server_storage": "Stocare server", + "backup_controller_page_start_backup": "Începe copia de rezervă", + "backup_controller_page_status_off": "Backup-ul automat ÃŽn prim-plan este oprit", + "backup_controller_page_status_on": "Backup-ul automat ÃŽn prim-plan este pornit", "backup_controller_page_storage_format": "{used} din {total} folosit", "backup_controller_page_to_backup": "Albume pentru backup", "backup_controller_page_total_sub": "Toate fotografiile și videoclipurile unice din albumele selectate", + "backup_controller_page_turn_off": "Dezactivează backup-ul ÃŽn prim-plan", + "backup_controller_page_turn_on": "Activează backup-ul ÃŽn prim-plan", + "backup_controller_page_uploading_file_info": "Informații ÃŽncărcare fișier", + "backup_err_only_album": "Nu poți șterge singurul album", "backup_error_sync_failed": "Sincronizarea a eșuat. Nu se poate procesa copia de rezervă.", "backup_info_card_assets": "resurse", + "backup_manual_cancelled": "Anulat", + "backup_manual_in_progress": "Încărcarea este deja ÃŽn curs. Încearcă din nou mai tÃĸrziu", + "backup_manual_success": "Succes", + "backup_manual_title": "Status ÃŽncărcare", "backup_options": "Opțiuni copie de rezervă", + "backup_options_page_title": "Opțiuni copie de rezervă", + "backup_setting_subtitle": "Schimbă opțiuni pentru backup ÃŽn prim-plan și ÃŽn fundal", "backup_settings_subtitle": "Gestionați setările de ÃŽncărcare", + "backup_upload_details_page_more_details": "Apasa pentru mai multe detalii", "backward": "În sens invers", - "battery_optimization_backup_reliability": "Dezactivarea optimizărilor bateriei poate ÃŽmbunătăți fiabilitatea copierii de rezervă ÃŽn fundal", "biometric_auth_enabled": "Autentificare biometrică activată", "biometric_locked_out": "Sunteți blocați de la autentificare biometrică", "biometric_no_options": "Nu sunt disponibile opțiuni biometrice", "biometric_not_available": "Autentificarea biometrică nu este disponibilă pe acest dispozitiv", + "birthdate_saved": "Data nașterii salvată cu succes", "birthdate_set_description": "Data nașterii este utilizată pentru a calcula vÃĸrsta acestei persoane la momentul realizării fotografiei.", "blurred_background": "Fundal neclar", - "browse_templates": "Răsfoiește șabloanele", "bugs_and_feature_requests": "Erori și solicitări de caracteristici", "build": "Versiunea", "build_image": "Versiune Imagine", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Ești sigur că vrei să păstrezi {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va rezolva toate grupurile duplicate fără a șterge nimic.", "bulk_trash_duplicates_confirmation": "Ești sigur că vrei să muți ÃŽn coșul de gunoi {count, plural, one {# resursă duplicată} other {# resurse duplicate}}? Aceasta va păstra cea mai mare resursă din fiecare grup și va muta ÃŽn coșul de gunoi toate celelalte duplicate.", "buy": "Achiziționați Immich", + "cache_settings_clear_cache_button": "Șterge cache", + "cache_settings_clear_cache_button_title": "Șterge memoria cache a aplicatiei. Performanța aplicației va fi semnificativ afectată pÃĸnă cÃĸnd va fi reconstruită.", + "cache_settings_duplicated_assets_clear_button": "ȘTERGE", + "cache_settings_duplicated_assets_subtitle": "Fotografii și videoclipuri ignorate ÃŽn lista aplicației", + "cache_settings_duplicated_assets_title": "Resurse duplicate ({count})", + "cache_settings_statistics_album": "Miniaturi pentru librării", + "cache_settings_statistics_full": "Fotografii complete", + "cache_settings_statistics_shared": "Miniaturi pentru albumele distribuite", + "cache_settings_statistics_thumbnail": "Miniaturi", + "cache_settings_statistics_title": "Memorie cache utilizată", + "cache_settings_subtitle": "Controlează modul de cache al aplicației Immich", + "cache_settings_tile_subtitle": "Controlează modul stocării locale", + "cache_settings_tile_title": "Stocare locală", + "cache_settings_title": "Setări pentru memoria cache", "camera": "CamerĮŽ", "camera_brand": "MarcĮŽ cameră", "camera_model": "Model cameră", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Nu se poate actualiza descrierea", "cast": "Partajare", "cast_description": "Configurați destinațiile de difuzare disponibile", - "change": "Modifică", "change_date": "Schimbați data", "change_description": "Schimbă descrierea", "change_display_order": "Schimbați ordinea de afișare", + "change_expiration_time": "Schimbați data expirare", "change_location": "Schimbați locația", "change_name": "Schimbați nume", "change_name_successfully": "Schimbare a numelui făcută cu succes", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Parolele nu se potrivesc", "change_password_form_reenter_new_password": "Reintrodu noua parolă", "change_pin_code": "Schimbă codul PIN", + "change_trigger": "mecanism de schimbare", + "change_trigger_prompt": "Ești sigur ca vrei sa schimbi mecanismul? Aceasta va șterge toate actiunile și filtrele existente.", "change_your_password": "Schimbă-ți parola", "changed_visibility_successfully": "Schimbare vizibilitate cu succes", "charging": "Încărcare", "charging_requirement_mobile_backup": "Pentru copia de rezervă ÃŽn fundal, dispozitivul trebuie să fie ÃŽn curs de ÃŽncărcare", + "check_corrupt_asset_backup": "Verifică copii de rezervă a resurselor corupte", + "check_corrupt_asset_backup_button": "Efectuează verificarea", + "check_corrupt_asset_backup_description": "Rulează această verificare doar prin Wi-Fi și doar după ce toate resursele au fost salvate ÃŽn copia de rezerva. Procedura poate dura cÃĸteva minute.", "check_logs": "Verificați Jurnale", "checksum": "Suma de control", - "choose": "Alege", "choose_matching_people_to_merge": "Alegeți persoanele care se potrivesc pentru a le fuziona", "city": "Oraș", "cleanup_confirm_description": "Immich a găsit {count} materiale (create ÃŽnainte de {date}) salvate ÃŽn siguranță pe server. Eliminați copiile locale de pe acest dispozitiv?", @@ -719,10 +774,11 @@ "clear": "Șterge", "clear_all": "Șterge tot", "clear_all_recent_searches": "Șterge toate căutările recente", - "clear_failed_count": "Șterge eșecurile ({count})", "clear_file_cache": "Ștergeți memoria cache a fișierelor", "clear_message": "Ștergeți mesajul", "clear_value": "Ștergeți valoarea", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Introdu Parola", "client_cert_import": "Importă", "client_cert_import_success_msg": "Certificatul de client este importat", "client_cert_invalid_msg": "Fisier cu certificat invalid sau parola este greșită", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Certificatul de client este șters", "client_cert_subtitle": "Este suportat doar formatul PKCS12 (.p12, .pfx). Importul/ștergerea certificatului este disponibil(ă) doar ÃŽnainte de autentificare", "client_cert_title": "Certificat SSL pentru client [EXPERIMENTAL]", + "clockwise": "În sensul acelor de ceas", "close": "Închideți", "collapse": "RestrÃĸngeți", "collapse_all": "RestrÃĸngeți toate", "color": "Culoare", + "color_theme": "Tema de culoare", "command": "Comandă", "command_palette_prompt": "Găsește rapid pagini, acțiuni sau comenzi", "command_palette_to_close": "pentru ÃŽnchidere", @@ -747,7 +805,6 @@ "comments_are_disabled": "Comentariile sunt dezactivate", "common_create_new_album": "Creează album nou", "completed": "Finalizat", - "configuration": "Configurare", "confirm": "Confirmați", "confirm_admin_password": "Confirmați Parola de Administrator", "confirm_delete_face": "Ești sigur ca vrei sa ștergi {name} din activ?", @@ -762,16 +819,19 @@ "contain": "Încadrează", "context": "Context", "continue": "Continuați", - "control_bottom_app_bar_add_tags": "Adaugă etichete", + "control_bottom_app_bar_create_new_album": "Creează album nou", + "control_bottom_app_bar_delete_from_immich": "Șterge din Immich", "control_bottom_app_bar_delete_from_local": "Șterge din dispozitiv", "control_bottom_app_bar_edit_location": "Editează locație", "control_bottom_app_bar_edit_time": "Editează data și ora", + "control_bottom_app_bar_share_link": "Partajează linkul", + "control_bottom_app_bar_share_to": "Distribuire către", "control_bottom_app_bar_trash_from_immich": "Mută ÃŽn coș", "copied_image_to_clipboard": "Imagine copiată ÃŽn clipboard.", "copied_to_clipboard": "Copiat ÃŽn clipboard!", "copy_error": "Eroare de copiere", + "copy_file_path": "Copiați calea fișierului", "copy_image": "Copiere imagine", - "copy_json": "Copiază JSON", "copy_link": "Copiere link", "copy_link_to_clipboard": "Copiere link ÃŽn clipboard", "copy_password": "Copiere parola", @@ -792,8 +852,6 @@ "create_new_person": "Creați o persoană nouă", "create_new_person_hint": "Atribuiți resursele selectate unei persoane noi", "create_new_user": "Creează utilizator nou", - "create_person": "Crează persoană", - "create_person_subtitle": "Adaugă un nume pentru fața selectată pentru a crea și a eticheta noua persoană", "create_shared_album_page_share_add_assets": "ADAUGĂ RESURSE", "create_shared_album_page_share_select_photos": "Selectează fotografii", "create_shared_link": "Creați un link partajat", @@ -805,33 +863,37 @@ "created_at": "Creat", "creating_linked_albums": "Crearea albumelor cu link...", "crop": "Decupează", + "crop_aspect_ratio_fixed": "Reparat", "crop_aspect_ratio_free": "Liber", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Pătrat", + "curated_object_page_title": "Obiecte", "current_device": "Dispozitiv curent", "current_pin_code": "Codul PIN actual", "current_server_address": "Adresa actuală a serverului", "custom_date": "Data personalizată", "custom_locale": "Setare regională personalizată", "custom_locale_description": "Formatați datele, timpul și numerele ÃŽn funcție de limbă și regiune", + "custom_url": "URL personalizat", "cutoff_date_description": "Păstrează fotografiile din ultimeleâ€Ļ", "cutoff_day": "{count, plural, one {# zi} few {# zile} other {# de zile}}", "cutoff_year": "{count, plural, =0 {0 ani} one {# an} few {# ani} other {# de ani}}", + "daily_title_text_date": "E, LLL zz", + "daily_title_text_date_year": "E, LLL zz, aaaa", "dark": "Întunecat", "dark_theme": "Comută la tema ÃŽntunecată", "date": "Dată", "date_after": "După data", "date_and_time": "Dată și oră", "date_before": "Anterior datei", - "date_of_birth": "Data nașterii", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Data nașterii salvată cu succes", "date_range": "Interval de date", - "date_time_original": "Data/Ora originală", "day": "Zi", "days": "Zile", "deduplicate_all": "Deduplicați Toate", - "default_quality_subtitle": "Calitatea utilizată la accesarea opțiunii de partajare. Apasă lung butonul de partajare pentru a alege de fiecare dată.", - "default_share_quality": "Calitatea implicită a partajării", + "default_locale": "Localizare implicită", + "default_locale_description": "Formatează datele și numerele ÃŽn funcție de localizarea browser-ului", "delete": "Ștergere", "delete_action_confirmation_message": "Sigur vrei să ștergi acest element? Această acțiune va muta elementul ÃŽn coșul de gunoi al serverului și te va ÃŽntreba dacă vrei să-l ștergi local", "delete_action_prompt": "{count} șterse", @@ -840,6 +902,8 @@ "delete_dialog_alert": "Aceste elemente vor fi șterse permanent de pe server-ul Immich și din dispozitivul tău", "delete_dialog_alert_local": "Aceste fișiere vor fi șterse permanent din dispozitiv, dar vor fi disponibile pe server-ul Immich", "delete_dialog_alert_local_non_backed_up": "Pentru unele fișere nu s-a făcut backup ÃŽn Immich și vor fi șterse permanent din dispozitiv", + "delete_dialog_alert_remote": "Aceste fișiere vor fi șterse permanent de pe server-ul Immich", + "delete_dialog_ok_force": "Șterge oricum", "delete_dialog_title": "Șterge permanent", "delete_duplicates_confirmation": "Sunteți sigur că doriți să ștergeți permanent aceste duplicate?", "delete_face": "Șterge fața", @@ -858,16 +922,21 @@ "delete_tag_confirmation_prompt": "Ești sigur că vrei să ștergi eticheta {tagName} ?", "delete_user": "Ștergere utilizator", "deleted_shared_link": "Link partajat șters", + "deletes_missing_assets": "Ștergere resurse lipsă de pe disc", "description": "Descriere", + "description_input_hint_text": "Adaugă descriere...", + "description_input_submit_error": "Eroare actualizare descriere, verifică log-urile pentru mai multe detalii", "deselect_all": "Deselectează toate", "details": "Detalii", "direction": "Direcție", "disable": "Dezactivare", "disabled": "Dezactivat", + "disallow_edits": "Interzice modificările", "discord": "Server Discord", "discover": "Descoperiți", "discovered_devices": "Dispozititve descoperite", "dismiss_all_errors": "Ignorați toate erorile", + "dismiss_error": "Ignorați eroarea", "display_options": "Opțiuni de afișare", "display_order": "Ordine de afișare", "display_original_photos": "Afișați fotografiile originale", @@ -876,9 +945,11 @@ "documentation": "Documentație", "done": "Gata", "download": "Descărcați", + "download_action_prompt": "Se descarcă {count} elemente", "download_canceled": "Descărcare anulată", "download_complete": "Descărcare completă", "download_enqueue": "Descărcare ÃŽn coadă", + "download_error": "Eroare de descărcare", "download_failed": "Descărcare eșuată", "download_finished": "Descărcare finalizată", "download_include_embedded_motion_videos": "Videoclipuri ÃŽncorporate", @@ -888,15 +959,15 @@ "download_paused": "Descărcarea a fost ÃŽntreruptă", "download_settings": "Descărcați", "download_settings_description": "Gestionați setările legate de descărcarea resurselor", + "download_started": "Descărcarea a ÃŽnceput", + "download_sucess": "Descărcare reușită", + "download_sucess_android": "Fișierul media a fost descărcat ÃŽn DCIM/Immich", "download_waiting_to_retry": "Se așteaptă o nouă ÃŽncercare", "downloading": "Se descarcă", "downloading_asset_filename": "Se descarcă resursa {filename}", "downloading_from_icloud": "Se descarcă din iCloud", "downloading_media": "Se descarcă fișierele media", - "drag_to_reorder": "Trage pentru a reordona", "drop_files_to_upload": "Trageți fișierele aici pentru a le ÃŽncărca", - "duplicate": "Duplică", - "duplicate_workflow": "Duplică fluxul de lucru", "duplicates": "Duplicate", "duplicates_description": "Rezolvați fiecare grup indicÃĸnd care sunt duplicate, dacă există.", "duration": "Durată", @@ -908,7 +979,9 @@ "edit_date_and_time": "Editare dată și oră", "edit_date_and_time_action_prompt": "{count} data și ora modificării", "edit_date_and_time_by_offset": "Schimbă data prin decalaj", + "edit_date_and_time_by_offset_interval": "Noul interval de date: {from} - {to}", "edit_description": "Editează descrierea", + "edit_description_prompt": "Vă rugăm să selectați o descriere nouă:", "edit_exclusion_pattern": "Editarea modelului de excludere", "edit_faces": "Editare fețe", "edit_key": "Tastă de editare", @@ -923,6 +996,9 @@ "edit_user": "Editare utilizator", "edit_workflow": "Modifică fluxul de lucru", "editor": "Editor", + "editor_close_without_save_prompt": "Schimbările nu vor fi salvate", + "editor_close_without_save_title": "Închideți editorul?", + "editor_confirm_reset_all_changes": "Sigur vrei să resetezi toate modificările?", "editor_discard_edits_confirm": "Renunță modificările", "editor_discard_edits_prompt": "Ai modificări nesalvate. Ești sigur că vrei să le renunți?", "editor_discard_edits_title": "Renunți la modificări?", @@ -930,8 +1006,6 @@ "editor_edits_applied_success": "Modificările au fost aplicate cu succes", "editor_flip_horizontal": "Întoarceți orizontal", "editor_flip_vertical": "Întoarceți vertical", - "editor_handle_corner": "{corner, select, top_left {Top-left} top_right {Top-right} bottom_left {Bottom-left} bottom_right {Bottom-right} other {A}} punct de control colț", - "editor_handle_edge": "{edge, select, top {Top} bottom {Bottom} left {Left} right {Right} other {An}} punct de control margine", "editor_orientation": "Orientare", "editor_reset_all_changes": "Resetați modificările", "editor_rotate_left": "Rotiți cu 90° ÃŽn sens invers acelor de ceasornic", @@ -951,7 +1025,9 @@ "enter_your_pin_code": "Introduceți codul PIN", "enter_your_pin_code_subtitle": "Introdu codul PIN pentru a accesa dosarul blocat", "error": "Eroare", + "error_change_sort_album": "Nu s-a putut modifica ordinea de sortare a albumului", "error_delete_face": "Eroare la ștergerea feței din activ", + "error_getting_places": "Eroare la obținerea locațiilor", "error_loading_albums": "Eroare la ÃŽncărcarea albumelor", "error_loading_image": "Eroare la ÃŽncărcarea imaginii", "error_loading_partners": "Eroare la ÃŽncărcarea partenerilor: {error}", @@ -991,7 +1067,6 @@ "failed_to_remove_product_key": "Eșec la eliminarea cheii de produs", "failed_to_reset_pin_code": "Nu s-a reușit resetarea codului PIN", "failed_to_stack_assets": "Eșec la combinarea resurselor", - "failed_to_tag_assets": "Etichetarea a eșuat", "failed_to_unstack_assets": "Eșec la desfășurarea resurselor", "failed_to_update_notification_status": "Nu s-a putut actualiza starea notificării", "incorrect_email_or_password": "E-mail sau parolă incorect/ă", @@ -1090,10 +1165,18 @@ "exif": "Format comutabil pentru fișiere imagine", "exif_bottom_sheet_description": "Adaugă Descriere...", "exif_bottom_sheet_description_error": "Eroare la actualizarea descrierii", + "exif_bottom_sheet_details": "DETALII", + "exif_bottom_sheet_location": "LOCAȚIE", "exif_bottom_sheet_no_description": "Fără descriere", + "exif_bottom_sheet_people": "PERSOANE", + "exif_bottom_sheet_person_add_person": "Adaugă nume", "exit_slideshow": "Ieșire din Prezentare", "expand": "Extinde", "expand_all": "Extindeți-le pe toate", + "experimental_settings_new_asset_list_subtitle": "Acțiune ÃŽn desfășurare", + "experimental_settings_new_asset_list_title": "Activează grila experimentală de fotografii", + "experimental_settings_subtitle": "Folosește pe propria răspundere!", + "experimental_settings_title": "Experimental", "expire_after": "Expiră după", "expired": "Expirat", "expires_date": "Expiră la {date}", @@ -1103,40 +1186,41 @@ "export_as_json": "Exportare ca JSON", "export_database": "Exportați baza de date", "export_database_description": "Exportați baza de date SQLite", - "exposure_time": "Timp de expunere", "extension": "Extensie", "external": "Extern", "external_libraries": "Biblioteci externe", "external_network": "Rețea externă", "external_network_sheet_info": "CÃĸnd nu se află ÃŽn rețeaua Wi-Fi preferată, aplicația se va conecta la server prin prima dintre adresele URL de mai jos pe care o poate accesa, ÃŽncepÃĸnd de sus ÃŽn jos", - "f_number": "Număr F", "face_unassigned": "Nealocat", "failed": "Eșuat", "failed_count": "Eșuat: {count}", "failed_to_authenticate": "Autentificarea nu a reușit", - "failed_to_delete_file": "Nu s-a putut șterge fișierul", "failed_to_load_assets": "Nu s-au ÃŽncărcat activele", "failed_to_load_folder": "Nu s-a putut ÃŽncărca folderul", "favorite": "Favorit", "favorite_action_prompt": "{count} adăugate la Favorite", "favorite_or_unfavorite_photo": "Fotografie preferată sau nepreferată", "favorites": "Favorite", + "favorites_page_no_favorites": "Nu au fost găsite resurse favorite", "feature_photo_updated": "Fotografie caracteristică actualizată", "features": "Caracteristici", + "features_in_development": "Funcții ÃŽn dezvoltare", "features_setting_description": "Gestionați funcțiile aplicației", "file_name_or_extension": "Numele sau extensia fișierului", "file_name_text": "Nume fișier", + "file_name_with_value": "Nume fișier: {file_name}", "file_size": "Mărime fișier", "filename": "Numele fișierului", "filetype": "Tipul fișierului", "filter": "Filtre", + "filter_description": "Condiții pentru filtrarea activelor țintă", "filter_people": "Filtrați persoanele", "filter_places": "Filtrează locurile", "filter_tags": "Filtrează etichete", "filters": "Filtre", + "find_them_fast": "Găsiți-le rapid prin căutare după nume", "first": "Primul", "fix_incorrect_match": "Remediați potrivirea incorectă", - "focal_length": "Distanță focală", "folder": "Dosar", "folder_not_found": "Dosar negăsit", "folders": "Fișiere", @@ -1147,7 +1231,6 @@ "free_up_space_description": "Mută fotografiile și videoclipurile salvate ÃŽn coșul de gunoi al dispozitivului pentru a elibera spațiu. Copiile tale de pe server rămÃĸn ÃŽn siguranță.", "free_up_space_settings_subtitle": "Eliberați spațiul de stocare al dispozitivului", "full_path": "Calea completă: {path}", - "full_path_or_folder": "Cale completă sau dosar", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Această funcție ÃŽncarcă resurse externe de la Google pentru a funcționa.", "general": "General", @@ -1168,6 +1251,7 @@ "group_owner": "Grupați după proprietar", "group_places_by": "Grupare locuri după...", "group_year": "Grupați după an", + "haptic_feedback_switch": "Activează feedback-ul haptic", "haptic_feedback_title": "Feedback haptic", "has_quota": "Are spațiu de stocare", "hash_asset": "Hash-ul resursei", @@ -1188,12 +1272,29 @@ "hide_schema": "Ascunde schema", "hide_text_recognition": "Ascunde recunoașterea textului", "hide_unnamed_people": "Ascundeți persoanele fără nume", + "home_page_add_to_album_conflicts": "Au fost adăugate {added} de resurse ÃŽn albumul {album}. {failed} de resurse sunt deja adăugate ÃŽn album.", + "home_page_add_to_album_err_local": "Resursele locale nu pot fi adăugate ÃŽn albume ÃŽncă, omitere", + "home_page_add_to_album_success": "Au fost adăugate {added} de resurse ÃŽn albumul {album}.", + "home_page_album_err_partner": "Momentan nu se pot adăuga fișierele partenerului ÃŽntr-un album, omitere", + "home_page_archive_err_local": "Resursele locale nu s-au putut arhiva ÃŽncă, omitere", + "home_page_archive_err_partner": "Nu se pot arhiva fișierele partenerului, omitere", "home_page_building_timeline": "Construire cronologie", + "home_page_delete_err_partner": "Nu se pot șterge fișierele partenerului, omitere", + "home_page_delete_remote_err_local": "Resursele locale sunt ÃŽn selecția pentru ștergere la distanță, omitere", + "home_page_favorite_err_local": "Resursele locale nu pot fi adăugate la favorite ÃŽncă, omitere", + "home_page_favorite_err_partner": "Momentan nu se pot adăuga fișierele partenerului la favorite, omitere", + "home_page_first_time_notice": "Dacă este prima dată cÃĸnd utilizezi aplicația, te rugăm să te asiguri că alegi unul sau mai multe albume de backup, astfel ÃŽncÃĸt cronologia să poată fi populată cu fotografiile și videoclipurile din aceste albume", + "home_page_locked_error_local": "Nu se pot muta resursele locale ÃŽn dosarul blocat, se omit", + "home_page_locked_error_partner": "Nu se pot muta resursele partenerului ÃŽn dosarul blocat, omit", + "home_page_share_err_local": "Nu se pot distribui fișiere locale prin link, omitere", + "home_page_upload_err_limit": "Se pot ÃŽncărca maxim 30 de resurse odată, omitere", "host": "Gazdă", "hour": "Oră", "hours": "Ore", "id": "ID", "idle": "Inactiv", + "ignore_icloud_photos": "Ignoră fotografiile din iCloud", + "ignore_icloud_photos_description": "Fotografiile stocate pe iCloud nu vor fi ÃŽncărcate pe serverul Immich", "image": "Imagine", "image_alt_text_date": "{isVideo, select, true {Video} other {imagine}} preluată ÃŽn {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {imagine}} preluată cu {person1} ÃŽn {date}", @@ -1205,6 +1306,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {imagine}} preluată ÃŽn {city}, {country} cu {person1} și {person2} ÃŽn {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {imagine}} preluată ÃŽn {city}, {country} cu {person1}, {person2}, și {person3} ÃŽn {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {imagine}} preluată ÃŽn {city}, {country} cu {person1}, {person2}, și {additionalCount, number} alții ÃŽn {date}", + "image_saved_successfully": "Imaginea a fost salvată", + "image_viewer_page_state_provider_download_started": "Descărcare ÃŽncepută", + "image_viewer_page_state_provider_download_success": "Descărcare cu succes", + "image_viewer_page_state_provider_share_error": "Eroare distribuire", "immich_logo": "Logo Immich", "immich_web_interface": "Interfața Web Immich", "import_from_json": "Importă din JSON", @@ -1219,7 +1324,6 @@ "individual_share": "Cota individuală", "individual_shares": "Partajări individuale", "info": "Informație", - "integrity_checks": "Verificări de integritate", "interval": { "day_at_onepm": "În fiecare zi la ora 13.00", "hours": "La fiecare {hours, plural, one {oră} other {{hours, number} ore}}", @@ -1230,9 +1334,16 @@ "invalid_date_format": "Format de dată invalid", "invite_people": "Invitați Persoane", "invite_to_album": "Invitați ÃŽn album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Fetch a funcționat la {dateTime}", + "ios_debug_info_last_sync_at": "Ultima sincronizare {dateTime}", + "ios_debug_info_no_processes_queued": "Niciun proces ÃŽn fundal pus ÃŽn coadă", + "ios_debug_info_no_sync_yet": "Nicio sarcină de sincronizare ÃŽn fundal nu a fost ÃŽncă executată", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proces ÃŽn fundal pus ÃŽn coadă} other {{count} procese ÃŽn fundal puse ÃŽn coadă}}", + "ios_debug_info_processing_ran_at": "Procesarea a rulat {dateTime}", "items_count": "{count, plural, one {# element} other{# elemente}}", "jobs": "Sarcini", + "json_editor": "Editor JSON", + "json_error": "Eroare JSON", "keep": "Păstrați", "keep_albums": "Păstreaza albume", "keep_albums_count": "Păstrez {count} {count, plural, one {album} few {albume} other {de albume}}", @@ -1259,29 +1370,26 @@ "leave": "Părăsiți", "leave_album": "Părăsește albumul", "lens_model": "Model obiectiv", - "less": "Mai puțin", "let_others_respond": "Permite altora să răspundă", "level": "Nivel", "library": "Librărie", "library_add_folder": "Adaugă folder", "library_edit_folder": "Editați folderul", + "library_options": "Opțiuni de bibliotecă", + "library_page_device_albums": "Albume ÃŽn dispozitiv", + "library_page_new_album": "Album nou", "library_page_sort_asset_count": "Numărul resurselor", "library_page_sort_created": "Data creării", "library_page_sort_last_modified": "Ultima dată modificat", "library_page_sort_title": "Titlu album", "licenses": "Licențe", "light": "Lumină", - "light_theme": "Comutare la tema luminoasă", "like": "Îmi place", "like_deleted": "Preferat șters", - "link": "Link", "link_motion_video": "Link video ÃŽn mișcare", - "link_to_docs": "Pentru mai multe informații, consultați documentația.", "link_to_oauth": "Link către OAuth", "linked_oauth_account": "Cont OAuth conectat", "list": "Listă", - "live": "În direct", - "load_more": "Afișează mai multe", "loading": "Încărcare", "loading_search_results_failed": "Încărcarea rezultatelor căutării nu a reușit", "local": "Local", @@ -1310,9 +1418,11 @@ "login": "Conectare", "login_disabled": "Logarea a fost dezactivată", "login_form_api_exception": "Excepție API. Te rugăm să verifici URL-ul server-ului și să ÃŽncerci din nou.", + "login_form_back_button_text": "Înapoi", "login_form_email_hint": "email-ultau@email.com", "login_form_endpoint_hint": "http://ip-server:port", "login_form_endpoint_url": "URL-ul destinației sever-ului", + "login_form_err_http": "Te rugăm specifică http:// sau https://", "login_form_err_invalid_email": "Email invalid", "login_form_err_invalid_url": "URL invalid", "login_form_err_leading_whitespace": "Spațiu alb ÃŽn față", @@ -1322,9 +1432,10 @@ "login_form_failed_login": "Eroare la autentificare, verifică URL-ul server-ului, adresa de email și parola", "login_form_handshake_exception": "A apărut o excepție Handshake cu server-ul. Activează suportul pentru certificate auto-semnate ÃŽn setări dacă folosești un certificat auto-semnat.", "login_form_password_hint": "parolă", - "login_form_server_empty": "Introdu adresa URL a unui server", - "login_form_server_error": "Nu s-a putut realiza conexiunea la server", - "login_has_been_disabled": "Conectarea a fost dezactivată", + "login_form_save_login": "RămÃĸi conectat", + "login_form_server_empty": "Introdu URL-ul server-ului.", + "login_form_server_error": "Nu s-a putut realiza conexiunea la server.", + "login_has_been_disabled": "Conectarea a fost dezactivată.", "login_password_changed_error": "A intervenit o eroare la actualizarea parolei", "login_password_changed_success": "Parola a fost actualizată cu succes", "logout_all_device_confirmation": "Sigur doriți să deconectați toate dispozitivele?", @@ -1339,6 +1450,7 @@ "maintenance_action_restore": "Restaurare bază de date", "maintenance_description": "Immich a fost pus ÃŽn modul de ÃŽntreținere.", "maintenance_end": "Ieșire din modul de ÃŽntreținere", + "maintenance_end_error": "Nu s-a reușit ieșirea din modul de ÃŽntreținere.", "maintenance_logged_in_as": "Conectat ÃŽn prezent ca {user}", "maintenance_restore_from_backup": "Restaurează din backup", "maintenance_restore_library": "Restaurează-ți biblioteca", @@ -1364,6 +1476,7 @@ "manage_media_access_settings": "Deschideți setările", "manage_media_access_subtitle": "Permiteți aplicației Immich să gestioneze și să mute fișierele media.", "manage_media_access_title": "Acces la gestionarea mediilor", + "manage_shared_links": "Administrați link-urile distribuite", "manage_sharing_with_partners": "Gestionați partajarea cu partenerii", "manage_the_app_settings": "Gestionați setările aplicației", "manage_your_account": "Gestionați-vă contul", @@ -1371,11 +1484,13 @@ "manage_your_devices": "Gestionați-vă dispozitivele conectate", "manage_your_oauth_connection": "Gestionați-vă conexiunea OAuth", "map": "Hartă", + "map_assets_in_bounds": "{count, plural, =0 {Nu există fotografii ÃŽn această zonă} one {# fotografie} other {# fotografii}}", "map_cannot_get_user_location": "Nu se poate obține locația utilizatorului", + "map_location_dialog_yes": "Da", "map_location_picker_page_use_location": "Folosește această locație", "map_location_service_disabled_content": "Serviciul de localizare trebuie să fie activat pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", "map_location_service_disabled_title": "Serviciul de localizare este dezactivat", - "map_marker_for_image": "Marcaj pe hartă pentru o fotografie realizată ÃŽn {city}, {country}", + "map_marker_for_images": "Marcator de hartă pentru imaginile realizate ÃŽn {city}, {country}", "map_marker_with_image": "Marcator de hartă cu imagine", "map_no_location_permission_content": "Permisiunea de localizare este necesară pentru a afișa resursele din locația actuală. Dorești să o activezi acum?", "map_no_location_permission_title": "Permisiunea de localizare este dezactivată", @@ -1385,46 +1500,17 @@ "map_settings_date_range_option_days": "Ultimele {days} zile", "map_settings_date_range_option_year": "Ultimul an", "map_settings_date_range_option_years": "Ultimii {years} ani", + "map_settings_dialog_title": "Setările hărții", "map_settings_include_show_archived": "Include resursele arhivate", "map_settings_include_show_partners": "Includeți partenerii", "map_settings_only_show_favorites": "Arată doar favorite", "map_settings_theme_settings": "Stilul hărții", + "map_zoom_to_see_photos": "Zoom out pentru a vedea fotografii", "mark_all_as_read": "Marchează toate ca citite", + "mark_as_read": "Marchează ca citit", "marked_all_as_read": "Marcate toate ca citite", "matches": "Corespunde", "matching_assets": "Resurse similare", - "media_chrome": { - "auto": "Automat", - "captions": "Subtitrări", - "captions_off": "Oprit", - "closed_captions": "subtitrări pentru persoane cu deficiențe de auz", - "decode_error": "Eroare de decodare", - "disable_captions": "Dezactivează subtitrările", - "enable_captions": "Activează subtitrările", - "enter_fullscreen_mode": "Intră ÃŽn modul ecran complet", - "exit_fullscreen_mode": "Ieși din modul ecran complet", - "loop": "BuclĮŽ", - "media_error_description": "Redarea a fost ÃŽntreruptă din cauza unei erori media. Fișierul poate fi corupt sau browserul nu acceptă acest format.", - "media_loading": "ÃŽncărcare media", - "mute": "Dezactivează sunetul", - "network_error": "Eroare de rețea", - "network_error_description": "Descărcarea fișierului media a eșuat din cauza unei erori de rețea.", - "not_supported_error": "Sursa nu este suportatĮŽ", - "playback_rate": "RatĮŽ de redare", - "playback_rate_current": "ratĮŽ de redare actualĮŽ", - "playback_rate_value": "Viteza de redare {playbackRate}", - "playback_time": "timp de redare", - "quality": "Calitate", - "second": "secundĮŽ", - "seconds": "secunde", - "time_value_of_total_time": "{currentTime} din {totalTime}", - "time_value_remaining": "{time} rămas", - "unmute": "Activează sunetul", - "unsupported_error_description": "A apărut o eroare neprevăzută. Serverul sau rețeaua au eșuat sau browserul tău nu acceptă acest format.", - "video_not_loaded_unknown_time": "videoclipul nu s-a ÃŽncărcat, durată necunoscută.", - "video_player": "player video", - "volume": "volum" - }, "media_type": "Tip media", "memories": "Amintiri", "memories_all_caught_up": "Sunteți la zi", @@ -1441,31 +1527,34 @@ "merge_people_prompt": "Vreți să ÃŽmbinați aceste persoane? Această acțiune este ireversibilă.", "merge_people_successfully": "Persoane ÃŽmbinate cu succes", "merged_people_count": "Imbinate {count, plural, one {# persoană} other {# persoane}}", - "minFaces": "Număr minim de fețe", - "minFaces_description": "Numărul minim de fețe recunoscute necesar pentru afișarea unei persoane", "minimize": "Minimizare", "minute": "Minut", "minutes": "Minute", + "mirror_horizontal": "Orizontal", + "mirror_vertical": "Vertical", "missing": "Lipsă", "mobile_app": "Aplicație Mobilă", "mobile_app_download_onboarding_note": "Descarcă aplicația mobilă folosind următoarele opțiuni", "model": "Model", - "modify_date": "Data modificării", "month": "Lună", + "monthly_title_text_date_format": "LLLL a", "more": "Mai mult", - "motion": "Mişcare", "move": "Mută", + "move_down": "Mută ÃŽn jos", "move_off_locked_folder": "Mutați din dosarul blocat", "move_to": "Mutare la", "move_to_device_trash": "Mutare ÃŽn coșul de gunoi al dispozitivului", "move_to_lock_folder_action_prompt": "{count} adăugate ÃŽn dosarul blocat", "move_to_locked_folder": "Mută ÃŽn dosarul blocat", "move_to_locked_folder_confirmation": "Aceste fotografii și videoclipuri vor fi eliminate din toate albumele și vor putea fi vizualizate doar din dosarul blocat", + "move_up": "Mută sus", + "moved_to_archive": "Au fost mutate {count, plural, one {# element} other {# elemente}} ÃŽn arhivă", + "moved_to_library": "Au fost mutate {count, plural, one {# element} other {# elemente}} la bibliotecă", "moved_to_trash": "Mutat ÃŽn coșul de gunoi", + "multiselect_grid_edit_date_time_err_read_only": "Nu se poate edita data fișierului(lor) cu permisiuni doar pentru citire, omitere", + "multiselect_grid_edit_gps_err_read_only": "Nu se poate edita locația fișierului(lor) cu permisiuni doar pentru citire, omitere", "mute_memories": "Amuțește amintirile", "my_albums": "Albumele mele", - "my_immich_description": "Copiază pagina curentă ca link My Immich", - "my_immich_title": "Linkul meu Immich", "name": "Nume", "name_or_nickname": "Nume sau poreclĮŽ", "name_required": "Numele este obligatoriu", @@ -1480,11 +1569,12 @@ "never": "Niciodată", "new_album": "Album Nou", "new_api_key": "Cheie API nouĮŽ", - "new_feature": "Funcție nouă", + "new_date_range": "Interval de dată nou", "new_password": "Parolă nouă", "new_person": "PersoanĮŽ nouĮŽ", "new_pin_code": "Cod PIN nou", "new_pin_code_subtitle": "Aceasta este prima dată cÃĸnd accesați dosarul blocat. Creați un cod PIN pentru a accesa ÃŽn siguranță această pagină", + "new_timeline": "Noua cronologie", "new_update": "Nouă actualizare", "new_user_created": "Utilizator nou creat", "new_version_available": "VERSIUNE NOUĂ DISPONIBILĂ", @@ -1492,6 +1582,7 @@ "next": "Următorul", "next_memory": "Următoarea amintire", "no": "Nu", + "no_actions_added": "Nu s-au adăugat ÃŽncă acțiuni", "no_albums_found": "Niciun album găsit", "no_albums_message": "Creați un album pentru a vă organiza fotografiile și videoclipurile", "no_albums_with_name_yet": "Se pare că nu aveți ÃŽncă niciun album cu acest nume.", @@ -1502,13 +1593,16 @@ "no_cast_devices_found": "Nu s-au găsit dispozitive de difuzare", "no_checksum_local": "Nu există checksum – nu se pot prelua resursele locale", "no_checksum_remote": "Nu există checksum – nu se pot prelua resursele la distanță", + "no_configuration_needed": "Nu este necesară nicio configurare", "no_devices": "Nu există dispozitive autorizate", "no_duplicates_found": "Nu au fost găsite duplicate.", "no_exif_info_available": "Nu există informații exif disponibile", "no_explore_results_message": "Încarcați mai multe fotografii pentru a vă explora colecția.", "no_favorites_message": "Adaugă favorite pentru a găsi rapid cele mai bune fotografii și videoclipuri", + "no_filters_added": "Nu s-au adăugat ÃŽncă filtre", "no_libraries_message": "Creați o bibliotecă externă pentru a vă vizualiza fotografiile și videoclipurile", "no_local_assets_found": "Nicio resursă locală găsită cu acest checksum", + "no_location_set": "Locație neconfigurată", "no_locked_photos_message": "Fotografiile și videoclipurile din dosarul blocat sunt ascunse și nu vor apărea atunci cÃĸnd răsfoiți sau căutați ÃŽn bibliotecă.", "no_name": "Fără Nume", "no_notifications": "Nicio notificare", @@ -1518,7 +1612,7 @@ "no_results": "Fără rezultate", "no_results_description": "Încercați un sinonim sau un cuvÃĸnt cheie mai general", "no_shared_albums_message": "Creați un album pentru a partaja fotografii și videoclipuri cu persoanele din rețeaua dvs", - "no_steps": "Niciun pas adăugat ÃŽncă", + "no_uploads_in_progress": "Nicio ÃŽncărcare ÃŽn curs", "none": "Niciunul", "not_allowed": "Nu este permis", "not_available": "N/A", @@ -1526,10 +1620,6 @@ "not_selected": "Neselectat", "notes": "Note", "nothing_here_yet": "Nimic aici ÃŽncă", - "notification_backup_reliability": "Activează notificările pentru a ÃŽmbunătăți fiabilitatea copierii de rezervă ÃŽn fundal", - "notification_enabled_list_tile_content": "Immich utilizează notificări pentru efectuarea copiilor de siguranță ÃŽn fundal. Le poți gestiona din setările dispozitivului.", - "notification_enabled_list_tile_open_button": "Deschide setările", - "notification_enabled_list_tile_title": "Notificările sunt activate", "notification_permission_dialog_content": "Pentru a activa notificările, mergi ÃŽn Setări > Immich și selectează permite.", "notification_permission_list_tile_content": "Acordă permisiunea pentru a activa notificările.", "notification_permission_list_tile_enable_button": "Activează notificările", @@ -1541,8 +1631,6 @@ "obtainium_configurator": "Configurator Obtainium", "obtainium_configurator_instructions": "Folosește Obtainium pentru a instala și actualiza aplicația Android direct din release-urile Immich de pe GitHub. Creează o cheie API și selectează o variantă pentru a genera linkul de configurare Obtainium", "ocr": "OCR", - "ocr_body": "Immich citește acum textul din fotografiile tale, astfel ÃŽncÃĸt să le poți căuta după ceea ce scrie ÃŽn ele.", - "ocr_title": "Caută text ÃŽn fotografiile tale", "official_immich_resources": "Resurse Oficiale Immich", "offline": "Offline", "offset": "Decalaj", @@ -1561,8 +1649,6 @@ "open": "Deschide", "open_calendar": "Deschide calendarul", "open_in_browser": "Deschide ÃŽn browser", - "open_in_immich_body": "Setează Immich ca galerie pe Android pentru a deschide fotografiile direct din alte aplicații.", - "open_in_immich_title": "Deschide fotografiile ÃŽn Immich", "open_in_map_view": "Deschideți ÃŽn vizualizarea hărții", "open_in_openstreetmap": "Deschideți ÃŽn OpenStreetMap", "open_the_search_filters": "Deschideți filtrele de căutare", @@ -1571,10 +1657,10 @@ "organize_into_albums": "Organizați ÃŽn albume", "organize_into_albums_description": "Pune fotografiile existente ÃŽn albume folosind setările curente de sincronizare", "organize_your_library": "Organizează-ți biblioteca", - "orientation": "Orientare", "original": "original", "other": "Alte", "other_devices": "Alte dispozitive", + "other_entities": "Alte entități", "other_variables": "Alte variabile", "owned": "Deținut", "owner": "Proprietar", @@ -1584,9 +1670,12 @@ "partner_can_access_assets": "Toate fotografiile și videoclipurile tale, cu excepția celor din arhivate și sterse", "partner_can_access_location": "Locația ÃŽn care au fost făcute fotografiile dvs", "partner_list_user_photos": "Fotografiile lui {user}", + "partner_list_view_all": "Vezi toate", "partner_page_empty_message": "Fotografiile tale nu sunt ÃŽncă distribuite cu nici un partener.", "partner_page_no_more_users": "Nu mai sunt utilizatori de adăugat", + "partner_page_partner_add_failed": "Eșuare adăugare partener", "partner_page_select_partner": "Selectează partener", + "partner_page_shared_to_title": "Distribuit cu", "partner_page_stop_sharing_content": "{partner} nu va mai putea accesa fotografiile tale.", "partner_sharing": "Partajarea Partenerilor", "partners": "Parteneri", @@ -1608,6 +1697,8 @@ "people": "Persoane", "people_edits_count": "Editat {count, plural, one {# persoană} other {# persoane}}", "people_feature_description": "Răsfoiți fotografii și videoclipuri grupate după persoane", + "people_selected": "{count, plural,one {# persoană selectată} few {# persoane selectate}other {# de persoane selectate}}", + "people_sidebar_description": "Afișează un link către persoane ÃŽn bara laterală", "permanent_deletion_warning": "Avertisment de ștergere permanentă", "permanent_deletion_warning_setting_description": "Afișează un avertisment la ștergerea definitivă a resurselor", "permanently_delete": "Ștergeți definitiv", @@ -1617,6 +1708,14 @@ "permanently_deleted_assets_count": "S-au șters definitiv {count, plural, one {# resursă} other {# resurse}}", "permission": "Permisiune", "permission_empty": "Permisiunea dvs. nu trebuie să fie goală", + "permission_onboarding_back": "Înapoi", + "permission_onboarding_continue_anyway": "Continuă oricum", + "permission_onboarding_get_started": "Începe", + "permission_onboarding_go_to_settings": "Mergi la setări", + "permission_onboarding_permission_denied": "Permisiune refuzată. Pentru a utiliza Immich, acordă permisiuni pentru fotografii și videoclipuri ÃŽn Setări.", + "permission_onboarding_permission_granted": "Permisiune acordată! Sunteți gata.", + "permission_onboarding_permission_limited": "Permisiune limitată. Pentru a permite Immich să facă copii de siguranță și să gestioneze ÃŽntreaga colecție de galerii, acordă permisiuni pentru fotografii și videoclipuri ÃŽn Setări.", + "permission_onboarding_request": "Immich necesită permisiunea de a vizualiza fotografiile și videoclipurile tale.", "person": "PersoanĮŽ", "person_age_months": "{months, plural, one {# lună} other {# luni}}", "person_age_year_months": "1 an, {months, plural, one {# lună} other {# luni}}", @@ -1624,35 +1723,36 @@ "person_birthdate": "Născut pe {date}", "person_hidden": "{name}{hidden, select, true { (ascuns)} other {}}", "person_recognized": "Persoană recunoscută", + "person_selected": "Persoana selectată", "photo_shared_all_users": "Se pare că ți-ai partajat fotografiile tuturor utilizatorilor sau că nu ai niciun utilizator căruia să le distribui.", "photos": "Fotografii", "photos_and_videos": "Fotografii și Videoclipuri", + "photos_count": "{count, plural, one {{count, number} imagine} other{{count, number} imagini}}", "photos_from_previous_years": "Fotografii din anii anteriori", + "photos_only": "Numai fotografii", "pick_a_location": "Alegeți o locație", "pick_custom_range": "Interval personalizat", - "pick_date_range": "Selectați un interval de dată", + "pick_date_range": "Selectați un interval de date", "pin_code_changed_successfully": "Codul PIN a fost modificat cu succes", "pin_code_reset_successfully": "Codul PIN a fost resetat cu succes", - "pin_code_setup_successfully": "Codul PIN a fost configurat cu succes", + "pin_code_setup_successfully": "Configurarea cu succes a unui cod PIN", "pin_verification": "Verificarea codului PIN", "place": "Loc", - "places": "Locuri", + "places": "Locații", "places_count": "{count, plural, one {{count, number} Loc} other {{count, number} Locuri}}", "play": "Redare", - "play_memories": "Redă amintiri", - "play_motion_photo": "Redă fotografie ÃŽn mișcare", + "play_memories": "Redare amintiri", + "play_motion_photo": "Redare Fotografie ÃŽn Mișcare", "play_or_pause_video": "Redați sau ÃŽntrerupeți videoclipul", "play_original_video": "Redă video original", - "play_original_video_setting_description": "Se preferă redarea videoclipurilor originale ÃŽn locul celor transcodate. Dacă fișierul original nu este compatibil, redarea s-ar putea să nu fie ÃŽn regula.", + "play_original_video_setting_description": "Se preferă redarea videoclipurilor originale ÃŽn locul celor transcodate. Dacă fișierul original nu este compatibil, redarea s-ar putea să nu fie corectă.", "play_transcoded_video": "Redă video transcodificat", "please_auth_to_access": "Vă rugăm să vă autentificați pentru a accesa", - "plugin_method_filter_type": "Filtru", - "plugin_method_filter_type_description": "Această metodă poate filtra evenimentele și poate ÃŽmpiedica condiționat rularea pașilor următori", "port": "Port", "preferences_settings_subtitle": "Gestionați preferințele aplicației", "preferences_settings_title": "Preferințe", "preparing": "Se prepară", - "preset": "Presetare", + "preset": "Presetat", "preview": "Previzualizare", "previous": "Anterior", "previous_memory": "Memoria anterioară", @@ -1664,11 +1764,11 @@ "privacy": "Confidențialitate", "profile": "Profil", "profile_drawer_app_logs": "Log-uri", + "profile_drawer_client_server_up_to_date": "Aplicația client și server-ul sunt actualizate", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Mod doar citire activat. Ține apăsat pe pictograma avatarului utilizatorului pentru a ieși.", "profile_image_of_user": "Imagine de profil a lui {user}", "profile_picture_set": "Poză de profil setată.", - "projection_type": "Tipul proiecției", "public_album": "Album public", "public_share": "Distribuire Publică", "purchase_account_info": "Suporter", @@ -1687,6 +1787,7 @@ "purchase_individual_description_2": "Statutul de suporter", "purchase_individual_title": "Individual", "purchase_input_suggestion": "Aveți o cheie de produs? Introduceți cheia mai jos", + "purchase_license_subtitle": "Cumpărați Immich pentru a sprijini dezvoltarea continuă a serviciului", "purchase_lifetime_description": "Achiziție pe viață", "purchase_option_title": "OPȚIUNI DE CUMPĂRARE", "purchase_panel_info_1": "Dezvoltarea programului Immich necesită mult timp și efort și avem ingineri cu normă ÃŽntreagă care lucrează la el pentru a-l face cÃĸt se poate de bun. Misiunea noastră este ca software-ul open-source și practicile de afaceri etice să devină o sursă de venit durabilă pentru dezvoltatori și să se creeze un ecosistem care să respecte confidențialitatea utilizatorilor, cu alternative reale la serviciile cloud care exploatează utilizatorii.", @@ -1719,19 +1820,19 @@ "reassigned_assets_to_new_person": "Re-alocat {count, plural, one {# resursă} other {# resurse}} unei noi persoane", "reassing_hint": "Atribuiți resursele selectate unei persoane existente", "recent": "Recent", + "recent_albums": "Albume recente", "recent_searches": "Căutări recente", "recently_added": "Adăugate recent", - "recently_added_body": "Accesează direct tot ce ai adăugat recent pe o pagină dedicată.", - "recently_added_description": "Răsfoiește resursele sortate după momentul ÃŽn care au fost ÃŽncărcate ÃŽn Immich", "recently_added_page_title": "Adăugate recent", - "recently_added_title": "Adăugate recent", "recently_taken": "Recent realizate", + "recently_taken_page_title": "Recent realizate", "refresh": "ReÃŽmprospătare", "refresh_encoded_videos": "Actualizează videoclipurile encodate", "refresh_faces": "ReÃŽmprospătați fețele", "refresh_metadata": "Actualizați metadatele", "refresh_thumbnails": "ReÃŽmprospătați miniaturile", "refreshed": "ReÃŽmprospătat", + "refreshes_every_file": "Recitește toate fișierele existente și noi", "refreshing_encoded_video": "Se reÃŽmprospătează videoclipul encodat", "refreshing_faces": "Se reÃŽmprospătează fețele", "refreshing_metadata": "Se reÃŽmprospătează metadatele", @@ -1744,7 +1845,7 @@ "remove_assets_shared_link_confirmation": "Sigur doriți să eliminați {count, plural, one {# resursă} other {# resurse}} din acest link comun?", "remove_assets_title": "Eliminați resursele?", "remove_custom_date_range": "Eliminați intervalul de date personalizat", - "remove_filter": "Elimină filtrul", + "remove_deleted_assets": "Eliminați Resursele Șterse", "remove_from_album": "Ștergeți din album", "remove_from_album_action_prompt": "{count} șters(e) din album", "remove_from_favorites": "Eliminați din favorite", @@ -1755,22 +1856,29 @@ "remove_memory": "Șterge amintirea", "remove_photo_from_memory": "Șterge fotografia din această amintire", "remove_tag": "Eliminați ticheta", + "remove_url": "Eliminați adresa URL", "remove_user": "Eliminați utilizatorul", "removed_api_key": "Cheie API eliminată: {name}", "removed_from_archive": "Eliminat din arhivă", "removed_from_favorites": "Eliminat din favorite", "removed_from_favorites_count": "{count, plural, other {Eliminat #}} din favorite", "removed_memory": "Amintire ștearsă", + "removed_photo_from_memory": "Fotografie ștearsă din amintire", "removed_tagged_assets": "Eticheta a fost eliminată din {count, plural, one {# resursă} other {# resurse}}", "rename": "Redenumiți", + "repair": "Reparați", + "repair_no_results_message": "Fișierele neurmărite și lipsă vor apărea aici", + "replace_with_upload": "Înlocuiți cu ÃŽncărcare", "repository": "Repertoriu", "require_password": "Necesită parolă", + "require_user_to_change_password_on_first_login": "Solicitați utilizatorului să schimbe parola la prima conectare", "rescan": "Rescanează", "reset": "Resetare", "reset_password": "Resetare parolă", "reset_people_visibility": "Resetați vizibilitatea persoanelor", "reset_pin_code": "Resetare cod PIN", "reset_pin_code_description": "Dacă ți-ai uitat codul PIN, poți contacta administratorul serverului pentru a-l reseta", + "reset_pin_code_success": "Codul PIN a fost resetat cu succes", "reset_pin_code_with_password": "Puteți reseta oricÃĸnd codul PIN cu ajutorul parolei", "reset_sqlite": "Resetare bază de date SQLite", "reset_sqlite_clear_app_data": "Șterge date", @@ -1784,10 +1892,12 @@ "resolved_all_duplicates": "Rezolvați toate duplicatele", "restore": "Restaurați", "restore_all": "Restaurați toate", + "restore_trash_action_prompt": "{count} restaurate din gunoi", "restore_user": "Restabiliți utilizatorul", "restored_asset": "Resursă restaurată", "resume": "Reluare", "resume_paused_jobs": "Reluați {count, plural, one {# paused job} other {# paused jobs}}", + "retry_upload": "ReÃŽncercați ÃŽncărcarea", "review_duplicates": "Examinați duplicatele", "review_large_files": "Revizuirea fișierelor mari", "role": "Rol", @@ -1795,6 +1905,7 @@ "role_viewer": "Vizualizator", "running": "Rulează", "save": "Salvați", + "save_to_gallery": "Salvați ÃŽn galerie", "saved": "Salvat", "saved_api_key": "Cheie API salvată", "saved_profile": "Profil salvat", @@ -1805,10 +1916,9 @@ "scan": "Scanare", "scan_all_libraries": "Scanați toate bibliotecile", "scan_library": "Scanare", + "scan_settings": "Setări Scanare", "scanning": "Scanare", "scanning_for_album": "Se scanează după album...", - "screencast_mode_description": "Afișează indicatorii de acțiune pentru tastatură și mouse", - "screencast_mode_title": "Comută modul screencast", "search": "Căutați", "search_albums": "Căutați albume", "search_by_context": "Căutați după context", @@ -1816,8 +1926,6 @@ "search_by_description_example": "Zi de drumeție ÃŽn Sapa", "search_by_filename": "Căutați după numele fișierului sau extensie", "search_by_filename_example": "i.e. IMG_1234.JPG sau PNG", - "search_by_full_path": "Caută după calea completă sau dosar", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - poți căuta după Proiecte, 3D, Imprimare, 2026 etc.", "search_by_ocr": "Caută folosind OCR", "search_by_ocr_example": "Factură", "search_camera_lens_model": "Caută modelul lentilei...", @@ -1837,6 +1945,7 @@ "search_filter_location_title": "Selectați locația", "search_filter_media_type": "Tip media", "search_filter_media_type_title": "Selectați tipul media", + "search_filter_ocr": "Caută dupa OCR", "search_filter_people_title": "Selectați persoane", "search_filter_star_rating": "După rating ÃŽn stele", "search_filter_tags_title": "Selectează etichete", @@ -1847,13 +1956,25 @@ "search_no_people_named": "Nicio persoană numită \"{name}\"", "search_no_result": "Nu s-au găsit rezultate, ÃŽncercați un alt termen sau o altă combinație de termeni de căutare", "search_options": "Opțiuni de căutare", + "search_page_categories": "Categorii", + "search_page_motion_photos": "Fotografii ÃŽn mișcare", + "search_page_no_objects": "Nu sunt informații disponibile despre obiecte", + "search_page_no_places": "Nici o informație disponibilă despre locuri", + "search_page_screenshots": "Capturi de ecran", "search_page_search_photos_videos": "Caută fotografiile și videoclipurile tale", + "search_page_selfies": "Selfie-uri", + "search_page_things": "Obiecte", "search_page_view_all_button": "Vezi toate", + "search_page_your_activity": "Activitatea ta", + "search_page_your_map": "Harta ta", "search_people": "Căutați oameni", "search_places": "Căutați locuri", "search_rating": "Caută după notă...", + "search_result_page_new_search_hint": "Căutare nouă", "search_settings": "Setări de căutare", "search_state": "Caută ÃŽn Stat/Județ...", + "search_suggestion_list_smart_search_hint_1": "Căutarea inteligentă este activată ÃŽn mod implicit, pentru a căuta metadata, utilizează sintaxa ", + "search_suggestion_list_smart_search_hint_2": "m:termen-de-căutare", "search_tags": "Căutați etichete...", "search_timezone": "Căutați fusul orar...", "search_type": "Tip cĮŽutare", @@ -1867,6 +1988,7 @@ "select_albums": "Selectează albume", "select_all": "Selectați tot", "select_all_duplicates": "Selectați toate duplicatele", + "select_all_in": "Selectați tot ÃŽn {group}", "select_avatar_color": "Selectați culoarea avatarului", "select_count": "{count, plural, one {Selectează #} few {Selectează #} other {Selectează #}}", "select_cutoff_date": "Selectează data limită", @@ -1874,13 +1996,14 @@ "select_featured_photo": "Selectați fotografia recomandată", "select_from_computer": "Selectați din calculator", "select_keep_all": "Selectați tot pentru păstrare", + "select_library_owner": "Selectați proprietarul bibliotecii", "select_new_face": "Selectați o nouĮŽ fațĮŽ", "select_people": "Selectează oameni", "select_person": "Selectează persoana", "select_person_to_tag": "Selectați o persoană pentru a o eticheta", "select_photos": "Selectați fotografii", - "select_quality": "Selectează calitatea", "select_trash_all": "Selectați tot pentru ștergere", + "select_user_for_sharing_page_err_album": "Creare album eșuată", "selected": "Selectat", "selected_count": "{count, plural, other {# selectat}}", "selected_gps_coordinates": "Coordonate GPS selectate", @@ -1913,31 +2036,42 @@ "setting_image_viewer_original_title": "Încarcă fotografia originală", "setting_image_viewer_preview_subtitle": "Activează pentru a ÃŽncărca o imagine ÃŽn rezoluție medie. Dezactivează pentru a ÃŽncărca direct imaginea originală sau doar a utiliza miniatura.", "setting_image_viewer_preview_title": "Încarcă imaginea de previzualizare", + "setting_image_viewer_title": "Imagini", "setting_languages_apply": "Aplică", "setting_languages_subtitle": "Schimbați limba aplicației", + "setting_notifications_notify_failures_grace_period": "Notificare eșuări backup ÃŽn fundal: {duration}", + "setting_notifications_notify_hours": "{count} ore", + "setting_notifications_notify_immediately": "imediat", "setting_notifications_notify_minutes": "{count} minute", + "setting_notifications_notify_never": "niciodată", "setting_notifications_notify_seconds": "{count} secunde", + "setting_notifications_single_progress_subtitle": "Informații detaliate despre progresul ÃŽncărcării pentru fiecare resursă", + "setting_notifications_single_progress_title": "Afișează progresul detaliat al copiilor de siguranță ÃŽn fundal", "setting_notifications_subtitle": "Ajustează preferințele pentru notificări", + "setting_notifications_total_progress_subtitle": "Progresul general al ÃŽncărcării (resurse finalizate/total)", + "setting_notifications_total_progress_title": "Afișează progresul total al copiilor de siguranță ÃŽn fundal", "setting_video_viewer_auto_play_subtitle": "Pornește automat redarea videoclipurilor cÃĸnd sunt deschise", "setting_video_viewer_auto_play_title": "Redare automată a videoclipurilor", "setting_video_viewer_looping_title": "Buclă", "setting_video_viewer_original_video_subtitle": "CÃĸnd redați ÃŽn flux un videoclip de pe server, redați originalul chiar și atunci cÃĸnd este disponibilă o transcodare. Poate duce la ÃŽncărcare temporară. Videoclipurile disponibile local sunt redate la calitatea originală indiferent de această setare.", "setting_video_viewer_original_video_title": "Forțează videoclipul original", "settings": "Setări", + "settings_require_restart": "Te rugăm să repornești Immich pentru a aplica această setare", "settings_saved": "Setările au fost salvate", "setup_pin_code": "Configurați un cod PIN", "share": "Distribuiți", + "share_action_prompt": "{count} elemente partajate", + "share_add_photos": "Adaugă fotografii", + "share_assets_selected": "{count} selectat(e)", "share_dialog_preparing": "Se pregătește...", "share_link": "Partajați linkul", - "share_original": "Folosește varianta originală (mare)", - "share_preview": "Utilizează miniatura (mică)", - "share_quality_body": "Ține apăsat butonul de partajare pentru a alege calitatea imaginii ÃŽnainte de a o partaja.", - "share_quality_title": "Alege calitatea partajării", "shared": "Partajat", "shared_album_activities_input_disable": "Cometariile sunt dezactivate", "shared_album_activity_remove_content": "Dorești să ștergi această activitate?", "shared_album_activity_remove_title": "Șterge activitate", "shared_album_section_people_action_error": "Eroare la părăsirea/ștergerea din album", + "shared_album_section_people_action_leave": "Șterge utilizator din album", + "shared_album_section_people_action_remove_user": "Șterge utilizator din album", "shared_album_section_people_title": "PERSOANE", "shared_by": "Partajat de", "shared_by_user": "Partajat de {user}", @@ -1946,10 +2080,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ÃŽncărcate", "shared_link_app_bar_title": "Link-uri distribuite", "shared_link_clipboard_copied_massage": "Copiat ÃŽn clipboard", + "shared_link_clipboard_text": "Link: {link}\nParolă: {password}", "shared_link_create_error": "Eroare ÃŽn timpul creării linkului de distribuire", "shared_link_custom_url_description": "Accesează acest link partajat cu un URL personalizat", - "shared_link_custom_url_title": "URL personalizat", - "shared_link_custom_url_warning": "Avertisment: un nume URL personalizat cu o bară ÃŽnainte poate să nu se comporte așa cum vă așteptați.", "shared_link_edit_description_hint": "Introdu descrierea distribuirii", "shared_link_edit_expire_after_option_day": "1 zi", "shared_link_edit_expire_after_option_days": "{count} zile", @@ -1983,6 +2116,12 @@ "shared_with_partner": "Partajat cu {partner}", "sharing": "Distribuire", "sharing_enter_password": "Vă rugăm să introduceți parola pentru a vizualiza această pagină.", + "sharing_page_album": "Albume distribuite", + "sharing_page_description": "Creeză albume de distribuire pentru a distribui fotografii și videoclipuri cu persoanele din rețeaua ta.", + "sharing_page_empty_list": "LISTĂ GOALĂ", + "sharing_sidebar_description": "Afișați un link către Partajare ÃŽn bara laterală", + "sharing_silver_appbar_create_shared_album": "Album nou distribuit", + "sharing_silver_appbar_share_partner": "Distribuie cu partenerul", "shift_to_permanent_delete": "apăsați ⇧ pentru a șterge definitiv elementul", "show_album_options": "Afișați opțiunile de album", "show_albums": "Afișați albume", @@ -1994,9 +2133,7 @@ "show_in_timeline": "Afișați ÃŽn cronologie", "show_in_timeline_setting_description": "Afișați fotografii și videoclipuri de la acest utilizator ÃŽn cronologia dvs", "show_keyboard_shortcuts": "Afișați comenzile rapide de la tastatură", - "show_less": "AratĮŽ mai puțin", "show_metadata": "Arătați metadatele", - "show_more_fields": "{count, plural, one {Afișează ÃŽncă # cÃĸmp} few {Afișează ÃŽncă # cÃĸmpuri} other {Afișează ÃŽncă # de cÃĸmpuri}}", "show_or_hide_info": "Afișați sau ascundeți informații", "show_password": "Afișați parola", "show_person_options": "Afișați opțiunile persoanelor", @@ -2004,7 +2141,6 @@ "show_schema": "Arată schema", "show_search_options": "Afișați opțiunile de căutare", "show_shared_links": "Afișare linkuri partajate", - "show_slideshow_metadata_overlay": "Afișează suprapunerea cu informații despre imagine", "show_slideshow_transition": "Afișați tranziția de prezentare", "show_supporter_badge": "Insigna suporterului", "show_supporter_badge_description": "Arată o insignă de suporter", @@ -2016,25 +2152,18 @@ "sign_out": "Deconectare", "sign_up": "Vă ÃŽnregistrați", "size": "Dimensiune", - "skip": "Sari peste", "skip_to_content": "Treceți la conținut", "skip_to_folders": "Treceți la foldere", "skip_to_tags": "Treceți la etichete", "slideshow": "Prezentare de diapozitive", - "slideshow_body": "Relaxează-te și urmărește-ți fotografiile ÃŽntr-o prezentare de diapozitive pe tot ecranul.", - "slideshow_metadata_overlay_mode": "Conținut suprapus", - "slideshow_metadata_overlay_mode_description_only": "Doar descrierea", - "slideshow_metadata_overlay_mode_full": "Complet", "slideshow_repeat": "Repetă prezentarea", "slideshow_repeat_description": "ReÃŽntoarce-te la ÃŽnceput cand prezentarea se ÃŽncheie", "slideshow_settings": "Setări pentru prezentarea de diapozitive", - "slideshow_title": "Prezentare de diapozitive", - "smart_album": "Album inteligent", - "some_assets_already_have_a_location_warning": "Unele dintre resursele selectate au deja o locație", "sort_albums_by": "Sortați albumele după...", "sort_created": "Data creării", "sort_items": "Numărul de articole", "sort_modified": "Data modificării", + "sort_newest": "Cea mai nouă fotografie", "sort_oldest": "Cea mai veche fotografie", "sort_people_by_similarity": "Sortează oameni după asemanare", "sort_recent": "Cea mai recentă fotografie", @@ -2043,6 +2172,7 @@ "stack": "Stivă", "stack_action_prompt": "{count} suprapuse", "stack_duplicates": "Duplicate stive", + "stack_select_one_photo": "Selectați o fotografie principală pentru stivă", "stack_selected_photos": "Fotografie stivă selectată", "stacked_assets_count": "Stivuite {count, plural, one {# resursă} other {# resurse}}", "stacktrace": "Urmă stivă", @@ -2051,10 +2181,6 @@ "start_date_before_end_date": "Data de ÃŽnceput trebuie să fie ÃŽnainte de data de sfÃĸrșit", "state": "Stat/Județ", "status": "Stare", - "step_delete": "Şterge pas", - "step_delete_confirm": "Sigur vrei să ștergi acest pas?", - "step_details": "Detalii despre pas", - "steps": "Paşi", "stop_casting": "Opriți difuzarea", "stop_motion_photo": "Opriți Fotografia in Mișcare", "stop_photo_sharing": "Încetați distribuirea fotografiilor?", @@ -2075,18 +2201,17 @@ "swap_merge_direction": "Schimbați direcția de ÃŽmbinare", "sync": "Sincronizare", "sync_albums": "Sincronizează albumele", + "sync_albums_manual_subtitle": "Sincronizează toate videoclipurile și fotografiile ÃŽncărcate cu albumele de rezervă selectate", "sync_local": "Sincronizare locală", "sync_remote": "Sincronizare la distanță", "sync_status": "Status-ul sincronizării", "sync_status_subtitle": "Vizualizează și gestionează sistemul de sincronizare", "sync_upload_album_setting_subtitle": "Creează și ÃŽncarcă fotografiile și videoclipurile tale ÃŽn albumele selectate de pe Immich", - "system_theme": "Tema sistemului", - "system_theme_command_description": "Folosește tema sistemului ({value})", "tag": "Etichetă", "tag_assets": "Eticheta resurselor", "tag_created": "Etichetă creată: {tag}", - "tag_face": "Etichetează fața", "tag_feature_description": "Răsfoirea fotografiilor și videoclipurilor grupate după subiecte de etichete logice", + "tag_not_found_question": "Nu puteți găsi o etichetă? Creați o etichetă nouă.", "tag_people": "Etichetează Persoane", "tag_updated": "Etichetă actualizată: {tag}", "tagged_assets": "Etichetat {count, plural, one {# resursă} other {# resurse}}", @@ -2101,10 +2226,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Număr de resurse pe rÃĸnd ({count})", "theme_setting_colorful_interface_subtitle": "Aplicați culoarea primară pe suprafețele de fundal.", "theme_setting_colorful_interface_title": "Interfață colorată", + "theme_setting_image_viewer_quality_subtitle": "Ajustează calitatea detaliilor vizualizatorului de imagine", + "theme_setting_image_viewer_quality_title": "Calitate vizualizator de imagine", "theme_setting_primary_color_subtitle": "Alege o culoare pentru acțiunile și accentele principale.", "theme_setting_primary_color_title": "Culoare primară", "theme_setting_system_primary_color_title": "Folosește culoarea sistemului", "theme_setting_system_theme_switch": "Automat (La fel ca setarea sistemului)", + "theme_setting_theme_subtitle": "Alege tema aplicației", + "theme_setting_three_stage_loading_subtitle": "Încărcarea ÃŽn trei etape are putea crește performanța ÃŽncărcării dar generează un volum semnificativ mai mare de trafic pe rețea", + "theme_setting_three_stage_loading_title": "Pornește ÃŽncărcarea ÃŽn 3 etape", "then": "Atunci", "they_will_be_merged_together": "Vor fi ÃŽmbinate ÃŽmpreună", "third_party_resources": "Resurse Terță Parte", @@ -2130,17 +2260,23 @@ "trash_all": "Ștergeți Tot", "trash_count": "Ștergeți {count, number}", "trash_delete_asset": "Coș de gunoi/Ștergeți resursa", + "trash_emptied": "Coș de gunoi golit", "trash_no_results_message": "Fotografiile și videoclipurile mutate ÃŽn coșul de gunoi vor apărea aici.", "trash_page_delete_all": "Șterge tot", + "trash_page_empty_trash_dialog_content": "Dorești să golești coșul? Aceste fișiere vor fi șterse permanent din Immich", "trash_page_info": "Resursele din coș vor fi șterse permanent după {days} zile", + "trash_page_no_assets": "Nici o resursă in coș", + "trash_page_restore_all": "Restaurează toate fișierele", + "trash_page_select_assets_btn": "Selectează resurse", + "trash_page_title": "Coș ({count})", "trashed_items_will_be_permanently_deleted_after": "Elementele din coșul de gunoi vor fi șterse definitiv după {days, plural, one {# zi} other {# zile}}.", "trigger": "Declanșator", - "trigger_asset_metadata_extraction": "Extragerea metadatelor resurselor", - "trigger_asset_metadata_extraction_description": "Se declanșează atunci cÃĸnd metadatele EXIF ale unui resurse sunt extrase", "trigger_asset_uploaded": "Fișier ÃŽncărcat", "trigger_asset_uploaded_description": "Declanșează cand un fișier este ÃŽncarcat", + "trigger_description": "Un eveniment care declanșează fluxul de lucru", "trigger_person_recognized": "Persoană Recunoscută", "trigger_person_recognized_description": "Declanșat atunci cÃĸnd este detectată o persoană", + "trigger_type": "Tip de declanșare", "troubleshoot": "Depanați", "type": "Tip", "unable_to_change_pin_code": "Nu se poate schimba codul PIN", @@ -2156,7 +2292,6 @@ "unknown": "Necunoscut", "unknown_country": "Țară necunoscută", "unknown_date": "Dată necunoscută", - "unknown_schema": "Schemă necunoscută", "unknown_year": "An Necunoscut", "unlimited": "Nelimitat", "unlink_motion_video": "Deconectați videoclipul ÃŽn mișcare", @@ -2169,20 +2304,23 @@ "unsaved_change": "Modificare nesalvată", "unselect_all": "Deselectați toate", "unselect_all_duplicates": "Deselectați toate duplicatele", + "unselect_all_in": "Deselectați toate din {group}", "unstack": "Dezasamblați", "unstack_action_prompt": "{count} neÃŽmpachetate", "unstacked_assets_count": "Nestivuit {count, plural, one {# resursă} other {# resurse}}", "unsupported_field_type": "Tip de cÃĸmp neacceptat", "unsupported_file_type": "Fișierul {file} nu poate fi ÃŽncărcat deoarece extensia sa {type} nu este suportată.", "untagged": "Neetichetat", + "untitled_workflow": "Flux de lucru fără titlu", "up_next": "Mai departe", "update_location_action_prompt": "Actualizează locația pentru {count} resurse selectate cu:", "updated_at": "Actualizat", "updated_password": "Parolă actualizată", "upload": "Încărcați", "upload_concurrency": "Încărcați simultan", - "upload_day_count": "{date}: {count, plural, one {# ÃŽncărcare} few {# ÃŽncărcări} other {# de ÃŽncărcări}}", "upload_details": "Detalii ÃŽncărcare", + "upload_dialog_info": "Vrei să backup resursele selectate pe server?", + "upload_dialog_title": "Încarcă resursă", "upload_error_with_count": "Eroare la ÃŽncărcare pentru {count, plural, one {# fișier} other {# fișiere}}", "upload_errors": "Încărcare finalizată cu {count, plural, one {# eroare} other {# erori}}, reÃŽmprospătați pagina pentru a reÃŽncărca noile resurse.", "upload_finished": "Încărcarea s-a finalizat", @@ -2192,13 +2330,9 @@ "upload_status_errors": "Erori", "upload_status_uploaded": "Încărcat", "upload_success": "Încărcare reușită, reÃŽmprospătați pagina pentru a vedea resursele noi ÃŽncărcate.", - "upload_to_album_body": "Pentru utilizatorii care nu utilizează funcția de ÃŽncărcare manuală, acum poți alege să adaugi fotografii locale direct ÃŽntr-un album pe măsură ce le ÃŽncarci, nemaifiind nevoie să le ÃŽncarci și să le adaugi ulterior ÃŽntr-un album.", - "upload_to_album_title": "Încarcă direct ÃŽntr-un album", "upload_to_immich": "Încărcați pe Immich ({count})", "uploading": "Se ÃŽncarcă", "uploading_media": "Se ÃŽncarcă fișierele media", - "uploads": "Încărcări", - "uploads_count": "{count, plural, one {# ÃŽncărcare} few {# ÃŽncărcări} other {# de ÃŽncărcări}}", "url": "URL", "usage": "Utilizare", "use_biometric": "Folosește biometrice", @@ -2206,7 +2340,6 @@ "use_browser_locale_description": "Formatează date, timp și numere bazat pe setările de limbă din browser", "use_current_connection": "Folosește conexiunea curentă", "use_custom_date_range": "Utilizați ÃŽn schimb un interval de date personalizat", - "use_template": "Folosește şablon", "user": "Utilizator", "user_has_been_deleted": "Acest utilizator a fost șters.", "user_id": "ID utilizator", @@ -2216,6 +2349,7 @@ "user_privacy": "Confidențialitatea utilizatorilor", "user_purchase_settings": "Cumpărare", "user_purchase_settings_description": "Gestionați-vă achiziția", + "user_role_set": "Setați {user} ca {role}", "user_usage_detail": "Detalii despre utilizare", "user_usage_stats": "Statistici de utilizare a contului", "user_usage_stats_description": "Vedeți statisticile de utilizare a contului", @@ -2225,6 +2359,7 @@ "utilities": "UtilitĮŽČ›i", "validate": "Validați", "validate_endpoint_error": "Vă rugăm să introduceți o adresă URL validă", + "validation_error": "Eroare de validare", "variables": "Variabile", "version": "Versiune", "version_announcement_closing": "Prietenul tĮŽu, Alex", @@ -2234,9 +2369,9 @@ "video": "Videoclip", "video_hover_setting": "Redați miniatura video la trecerea cursorului", "video_hover_setting_description": "Redați miniatura video cÃĸnd mouse-ul trece peste element. Chiar și atunci cÃĸnd este dezactivată, redarea poate fi pornită trecÃĸnd cu mouse-ul peste pictograma de redare.", - "video_quality": "Calitate video", "videos": "Videoclipuri", "videos_count": "{count, plural, one {# Videoclip} other {# Videoclipuri}}", + "videos_only": "Doar videoclipuri", "view": "Secțiune", "view_album": "Vizualizează Album", "view_all": "Vizualizează Tot", @@ -2245,29 +2380,28 @@ "view_details": "Vedeți detaliile", "view_in_timeline": "Vizualizează ÃŽn cronologie", "view_link": "Vezi link", + "view_links": "Vizualizează link-urile", "view_name": "Vizualizare", "view_next_asset": "Vizualizează următoarea resursă", "view_previous_asset": "Vizualizează resursa anterioară", "view_qr_code": "Vezi cod QR", "view_similar_photos": "Vizualizează poze similare", "view_stack": "Vizualizare stivă", + "view_user": "Vizualizare utilizator", "viewer_remove_from_stack": "Șterge din grup", - "visibility": "Vizibilitate", + "viewer_stack_use_as_main_asset": "Folosește ca resursă principală", + "viewer_unstack": "Anulează grup", "visibility_changed": "Vizibilitatea schimbată pentru {count, plural, one {# persoană} other {# persoane}}", "visual": "Vizual", + "visual_builder": "Constructor vizual", "waiting": "În așteptare", "waiting_count": "În așteptare: {count}", "warning": "Avertisment", "week": "SĮŽptĮŽmÃĸnĮŽ", "welcome": "Bun venit", "welcome_to_immich": "Bun venit la Immich", - "whats_new": "Ce este nou", - "whats_new_settings_subtitle": "Descoperă noutățile din Immich", - "whats_new_version": "Versiunea {version}", - "when": "CÃĸnd", "width": "Lățime", "wifi_name": "Nume Wi-Fi", - "workflow": "Flux de lucru", "workflow_delete_prompt": "Ești sigur că vrei să ștergi acest flux de lucru?", "workflow_deleted": "Flux de lucru șters", "workflow_description": "Descrierea fluxului de lucru", @@ -2277,17 +2411,17 @@ "workflow_name": "Numele fluxului de lucru", "workflow_navigation_prompt": "Ești sigur că vrei să părăsești fără să salvezi modificările?", "workflow_summary": "Rezumatul fluxului de lucru", - "workflow_templates": "Șablon flux de lucru", "workflow_update_success": "Fluxul de lucru a fost actualizat cu succes", + "workflow_updated": "Fluxul de lucru a fost actualizat", "workflows": "Fluxuri de lucru", "workflows_help_text": "Fluxurile de lucru automatizează acțiuni pe resurse, folosind declanșatori și filtre", "wrong_pin_code": "Cod PIN greșit", - "x_of_total": "{x} din {total}", "year": "An", "years_ago": "acum {years, plural, one {# an} other {# ani}} ÃŽn urmă", "yes": "Da", "you_dont_have_any_shared_links": "Nu aveți linkuri partajate", "your_wifi_name": "Numele rețelei tale WiFi", "zero_to_clear_rating": "apasă 0 pentru a reseta evaluarea resursei", - "zoom_image": "Măriți Imaginea" + "zoom_image": "Măriți Imaginea", + "zoom_to_bounds": "Mărește la margini" } diff --git a/i18n/ru.json b/i18n/ru.json index 6946b308cc..799861ebc2 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -10,31 +10,39 @@ "active": "Đ’Ņ‹ĐŋĐžĐģĐŊŅĐĩŅ‚ŅŅ", "active_count": "Đ’Ņ‹ĐŋĐžĐģĐŊŅŅŽŅ‚ŅŅ: {count}", "activity": "ДĐĩĐšŅŅ‚Đ˛Đ¸Ņ", + "activity_changed": "АĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ {enabled, select, true {вĐēĐģŅŽŅ‡ĐĩĐŊа} other {ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊа}}", "add": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ", "add_a_description": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", "add_a_location": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "add_a_name": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ иĐŧŅ", "add_a_title": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŊаСваĐŊиĐĩ", "add_action": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ", + "add_action_description": "НаĐļĐŧĐ¸Ņ‚Đĩ Đ´ĐģŅ дОйавĐģĐĩĐŊĐ¸Ņ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", "add_assets": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ", "add_birthday": "ĐŖĐēĐ°ĐˇĐ°Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ", "add_endpoint": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Đ°Đ´Ņ€Đĩҁ", "add_exclusion_pattern": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ", + "add_filter": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ„Đ¸ĐģŅŒŅ‚Ņ€", + "add_filter_description": "НаĐļĐŧĐ¸Ņ‚Đĩ Đ´ĐģŅ дОйавĐģĐĩĐŊĐ¸Ņ ҃ҁĐģĐžĐ˛Đ¸Ņ ĐžŅ‚ĐąĐžŅ€Đ°", "add_location": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", + "add_more_users": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Đĩ҉ґ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš", "add_partner": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", + "add_path": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŋŅƒŅ‚ŅŒ", "add_photos": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ„ĐžŅ‚Đž", - "add_step": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ŅˆĐ°Đŗ", "add_tag": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗ", "add_to": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ вâ€Ļ", "add_to_album": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в аĐģŅŒĐąĐžĐŧ", "add_to_album_bottom_sheet_added": "ДобавĐģĐĩĐŊĐž в аĐģŅŒĐąĐžĐŧ {album}", "add_to_album_bottom_sheet_already_exists": "ĐŖĐļĐĩ в аĐģŅŒĐąĐžĐŧĐĩ {album}", "add_to_album_bottom_sheet_some_local_assets": "НĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊĐĩ дОйавĐģĐĩĐŊŅ‹ в аĐģŅŒĐąĐžĐŧ, ĐŋĐžŅĐēĐžĐģҌĐē҃ Đĩ҉Đĩ ĐŊĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊŅ‹ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€", + "add_to_album_toggle": "ПĐĩŅ€ĐĩĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Đ˛Ņ‹Đ´ĐĩĐģĐĩĐŊиĐĩ Đ´ĐģŅ аĐģŅŒĐąĐžĐŧа {album}", "add_to_albums": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в аĐģŅŒĐąĐžĐŧŅ‹", "add_to_albums_count": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в аĐģŅŒĐąĐžĐŧŅ‹ ({count})", "add_to_bottom_bar": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в", + "add_to_shared_album": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в ĐžĐąŅ‰Đ¸Đš аĐģŅŒĐąĐžĐŧ", "add_upload_to_stack": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ и Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в ĐŗŅ€ŅƒĐŋĐŋ҃", "add_url": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ URL", + "add_workflow_step": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ŅˆĐ°Đŗ Ņ€Đ°ĐąĐžŅ‡ĐĩĐŗĐž ĐŋŅ€ĐžŅ†ĐĩŅŅĐ°", "added_to_archive": "ДобавĐģĐĩĐŊĐž в Đ°Ņ€Ņ…Đ¸Đ˛", "added_to_favorites": "ДобавĐģĐĩĐŊĐž в Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ", "added_to_favorites_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ дОйавĐģĐĩĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ дОйавĐģĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° дОйавĐģĐĩĐŊĐž}} в Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ", @@ -73,7 +81,6 @@ "cron_expression_description": "Đ§Đ°ŅŅ‚ĐžŅ‚Đ° и Đ˛Ņ€ĐĩĐŧŅ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ ĐˇĐ°Đ´Đ°Ņ‡Đ¸ в Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ ĐŋĐģаĐŊĐ¸Ņ€ĐžĐ˛Ņ‰Đ¸Đēа cron. Đ’ĐžŅĐŋĐžĐģŅŒĐˇŅƒĐšŅ‚ĐĩҁҌ ĐŋŅ€Đ¸ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐžŅŅ‚Đ¸ Đ˛Đ¸ĐˇŅƒĐ°ĐģҌĐŊŅ‹Đŧ Ņ€ĐĩдаĐēŅ‚ĐžŅ€ĐžĐŧ Crontab Guru", "cron_expression_presets": "Đ Đ°ŅĐŋĐ¸ŅĐ°ĐŊиĐĩ (ĐŋŅ€ĐĩĐ´ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐŊŅ‹Đĩ Đ˛Đ°Ņ€Đ¸Đ°ĐŊ҂ҋ)", "disable_login": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Đ˛Ņ…ĐžĐ´", - "download_csv": "ĐĄĐēĐ°Ņ‡Đ°Ņ‚ŅŒ CSV", "duplicate_detection_job_description": "ЗаĐŋ҃ҁĐēаĐĩŅ‚ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊиĐĩ ĐŋĐžŅ…ĐžĐļĐ¸Ņ… Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиК ĐŋŅ€Đ¸ ĐŋĐžĐŧĐžŅ‰Đ¸ ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐžĐŗĐž ĐˇŅ€ĐĩĐŊĐ¸Ņ (ĐˇĐ°Đ˛Đ¸ŅĐ¸Ņ‚ ĐžŅ‚ ҃ĐŧĐŊĐžĐŗĐž ĐŋĐžĐ¸ŅĐēа)", "exclusion_pattern_description": "ШайĐģĐžĐŊŅ‹ Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊиК ĐŋОСвОĐģŅŅŽŅ‚ Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŊĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ Ņ„Đ°ĐšĐģŅ‹ и ĐŋаĐŋĐēи ĐŋŅ€Đ¸ ҁĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°ĐŊии йийĐģĐ¸ĐžŅ‚ĐĩĐēи. Đ­Ņ‚Đž ĐŋĐžĐģĐĩСĐŊĐž, ĐĩҁĐģи в ĐŋаĐŋĐēĐĩ ĐĩŅŅ‚ŅŒ Ņ„Đ°ĐšĐģŅ‹, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŊĐĩ ĐŊ҃ĐļĐŊĐž иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ. НаĐŋŅ€Đ¸ĐŧĐĩŅ€ RAW-Ņ„Đ°ĐšĐģŅ‹.", "export_config_as_json_description": "ĐĄĐžŅ…Ņ€Đ°ĐŊĐ¸Ņ‚ŅŒ Ņ‚ĐĩĐēŅƒŅ‰ŅƒŅŽ ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸ŅŽ ŅĐ¸ŅŅ‚ĐĩĐŧŅ‹ в Ņ„Đ°ĐšĐģ JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€ ĐžŅ‚ 1 Đ´Đž 100. ЧĐĩĐŧ Đ˛Ņ‹ŅˆĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž, Ņ‚ĐĩĐŧ ĐģŅƒŅ‡ŅˆĐĩ, ĐŊĐž ĐŋŅ€Đ¸ ŅŅ‚ĐžĐŧ ŅĐžĐˇĐ´Đ°ŅŽŅ‚ŅŅ Ņ„Đ°ĐšĐģŅ‹ йОĐģҌ҈ĐĩĐŗĐž Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° и ĐŧĐžĐļĐĩŅ‚ ҁĐŊĐ¸ĐˇĐ¸Ņ‚ŅŒŅŅ ҁĐēĐžŅ€ĐžŅŅ‚ŅŒ ĐžŅ‚ĐēĐģиĐēа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ.", "image_thumbnail_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€", "import_config_from_json_description": "ИĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸ŅŽ ŅĐ¸ŅŅ‚ĐĩĐŧŅ‹, ĐˇĐ°ĐŗŅ€ŅƒĐˇĐ¸Đ˛ JSON Ņ„Đ°ĐšĐģ ĐŊĐ°ŅŅ‚Ņ€ĐžĐĩĐē", - "integrity_checks_checksum_files": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊŅ‹Đĩ ҁ҃ĐŧĐŧŅ‹ Ņ„Đ°ĐšĐģОв", - "integrity_checks_checksum_files_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēи ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ", - "integrity_checks_checksum_files_enable_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đē҃ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ", - "integrity_checks_checksum_files_percentage_limit": "ĐŸŅ€ĐžŅ†ĐĩĐŊŅ‚ĐŊŅ‹Đš ĐģиĐŧĐ¸Ņ‚", - "integrity_checks_checksum_files_percentage_limit_description": "МаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐĩ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ в ĐŋŅ€ĐžŅ†ĐĩĐŊŅ‚Đ°Ņ… ĐžŅ‚ 0,01 Đ´Đž 1, ĐžĐąŅ€Đ°ĐąĐ°Ņ‚Ņ‹Đ˛Đ°ĐĩĐŧҋ҅ Са Ņ€Đ°Đˇ ĐŋŅ€Đ¸ ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐĩ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ.", - "integrity_checks_checksum_files_time_limit": "Đ’Ņ€ĐĩĐŧĐĩĐŊĐŊОК ĐģиĐŧĐ¸Ņ‚", - "integrity_checks_checksum_files_time_limit_description": "МаĐēŅĐ¸ĐŧаĐģҌĐŊĐ°Ņ ĐŋŅ€ĐžĐ´ĐžĐģĐļĐ¸Ņ‚ĐĩĐģҌĐŊĐžŅŅ‚ŅŒ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ Đ¸Ņ‚ĐĩŅ€Đ°Ņ†Đ¸Đ¸ ĐŋĐž ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐĩ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ (Đŧҁ)", - "integrity_checks_missing_files": "ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ Ņ„Đ°ĐšĐģŅ‹", - "integrity_checks_missing_files_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа Ņ‡Đ°ŅŅ‚ĐžŅ‚Ņ‹ и вĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ/ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ ĐŋĐžĐ¸ŅĐēа ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Ņ… Ņ„Đ°ĐšĐģОв", - "integrity_checks_missing_files_enable_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ„ŅƒĐŊĐēŅ†Đ¸ŅŽ ĐŋĐžĐ¸ŅĐēа ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Ņ… Ņ„Đ°ĐšĐģОв", - "integrity_checks_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ҆ĐĩĐģĐžŅŅ‚ĐŊĐžŅŅ‚Đ¸", - "integrity_checks_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ иĐŊŅ‚ĐĩŅ€Đ˛Đ°ĐģаĐŧи Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēи ҆ĐĩĐģĐžŅŅ‚ĐŊĐžŅŅ‚Đ¸", - "integrity_checks_untracked_files": "НĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧŅ‹Đĩ Ņ„Đ°ĐšĐģŅ‹", - "integrity_checks_untracked_files_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа Ņ‡Đ°ŅŅ‚ĐžŅ‚Ņ‹ и вĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ/ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ ĐŋĐžĐ¸ŅĐēа ĐŊĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧҋ҅ Ņ„Đ°ĐšĐģОв", - "integrity_checks_untracked_files_enable_description": "ВĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ ĐŋĐžĐ¸ŅĐēа ĐŊĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧҋ҅ Ņ„Đ°ĐšĐģОв", "job_concurrency": "Đ§Đ¸ŅĐģĐž ĐŋĐ°Ņ€Đ°ĐģĐģĐĩĐģҌĐŊҋ҅ ĐŋĐžŅ‚ĐžĐēОв ĐˇĐ°Đ´Đ°Ņ‡Đ¸ {job}", "job_created": "Đ—Đ°Đ´Đ°Ņ‡Đ° ŅĐžĐˇĐ´Đ°ĐŊа", "job_not_concurrency_safe": "Đ­Ņ‚Đ° ĐˇĐ°Đ´Đ°Ņ‡Đ° ĐŊĐĩ ОйĐĩҁĐŋĐĩŅ‡Đ¸Đ˛Đ°ĐĩŅ‚ ĐąĐĩСОĐŋĐ°ŅĐŊĐžŅŅ‚ŅŒ ĐŋĐ°Ņ€Đ°ĐģĐģĐĩĐģҌĐŊĐžŅŅ‚Đ¸ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ иĐŊŅ‚ĐĩĐģĐģĐĩĐēŅ‚ŅƒĐ°ĐģҌĐŊŅ‹Đš ĐŋĐžĐ¸ŅĐē", "machine_learning_smart_search_enabled_description": "ĐŸŅ€Đ¸ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊии ŅŅ‚ĐžĐš Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ‚ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒŅŅ Đ´ĐģŅ иĐŊŅ‚ĐĩĐģĐģĐĩĐēŅ‚ŅƒĐ°ĐģҌĐŊĐžĐŗĐž ĐŋĐžĐ¸ŅĐēа.", "machine_learning_url_description": "URL-Đ°Đ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐžĐŗĐž ĐžĐąŅƒŅ‡ĐĩĐŊĐ¸Ņ. Đ•ŅĐģи ҃ĐēаСаĐŊĐž ĐŊĐĩҁĐēĐžĐģҌĐēĐž, СаĐŋŅ€ĐžŅŅ‹ ĐąŅƒĐ´ŅƒŅ‚ ĐžŅ‚ĐŋŅ€Đ°Đ˛ĐģŅŅ‚ŅŒŅŅ ĐŋĐž ĐžŅ‡ĐĩŅ€Đĩди ĐŊа ĐēаĐļĐ´Ņ‹Đš, ĐŋĐžĐēа ĐžŅ‚ ОдĐŊĐžĐŗĐž иС ĐŊĐ¸Ņ… ĐŊĐĩ ĐąŅƒĐ´ĐĩŅ‚ ĐŋĐžĐģŅƒŅ‡ĐĩĐŊ ҃ҁĐŋĐĩ҈ĐŊŅ‹Đš ĐžŅ‚Đ˛ĐĩŅ‚. ĐĄĐĩŅ€Đ˛ĐĩҀҋ, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŊĐĩ ĐžŅ‚Đ˛ĐĩŅ‡Đ°ŅŽŅ‚, ĐąŅƒĐ´ŅƒŅ‚ Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐž Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒŅŅ Đ´Đž Ņ‚ĐĩŅ… ĐŋĐžŅ€, ĐŋĐžĐēа ĐŊĐĩ ŅŅ‚Đ°ĐŊŅƒŅ‚ ҁĐŊОва Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹.", - "maintenance_backup_management": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅ‹Đŧ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩĐŧ", "maintenance_delete_backup": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅƒŅŽ ĐēĐžĐŋĐ¸ŅŽ", "maintenance_delete_backup_description": "Đ­Ņ‚Đ° Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ°Ņ ĐēĐžĐŋĐ¸Ņ ĐąŅƒĐ´ĐĩŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊа.", "maintenance_delete_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅƒŅŽ ĐēĐžĐŋĐ¸ŅŽ.", - "maintenance_integrity_check": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚ŅŒ", - "maintenance_integrity_check_all": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚ŅŒ Đ˛ŅĐĩ", - "maintenance_integrity_checksum_mismatch": "ĐžŅˆĐ¸ĐąĐēи ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ", - "maintenance_integrity_checksum_mismatch_description": "ФаКĐģŅ‹, ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃ĐŧĐŧа ĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… ĐŊĐĩ ŅĐžĐ˛ĐŋадаĐĩŅ‚ ҁ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊОК ҁ҃ĐŧĐŧОК, Ņ…Ņ€Đ°ĐŊŅŅ‰ĐĩĐšŅŅ в йаСĐĩ даĐŊĐŊҋ҅ Immich.", - "maintenance_integrity_checksum_mismatch_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ŅĐžĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ¸Ņ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ", - "maintenance_integrity_checksum_mismatch_refresh_job": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐžŅˆĐ¸ĐąĐžĐē ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊҋ҅ ҁ҃ĐŧĐŧ", - "maintenance_integrity_missing_file": "ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_integrity_missing_file_description": "ФаКĐģŅ‹, СаĐŋĐ¸ŅĐ¸ Đž ĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… иĐŧĐĩŅŽŅ‚ŅŅ в йаСĐĩ даĐŊĐŊҋ҅ Immich, ĐŊĐž ŅĐ°Đŧи Ņ„Đ°ĐšĐģŅ‹ ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‚ ĐŊа Đ´Đ¸ŅĐēĐĩ.", - "maintenance_integrity_missing_file_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊаĐģĐ¸Ņ‡Đ¸Ņ ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Ņ… Ņ„Đ°ĐšĐģОв", - "maintenance_integrity_missing_file_refresh_job": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Ņ… Ņ„Đ°ĐšĐģОв", - "maintenance_integrity_report": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ҆ĐĩĐģĐžŅŅ‚ĐŊĐžŅŅ‚Đ¸", - "maintenance_integrity_untracked_file": "НĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧŅ‹Đĩ Ņ„Đ°ĐšĐģŅ‹", - "maintenance_integrity_untracked_file_description": "ФаКĐģŅ‹, ĐŊĐ°Ņ…ĐžĐ´ŅŅ‰Đ¸ĐĩŅŅ в ĐēĐ°Ņ‚Đ°ĐģĐžĐŗĐ°Ņ… Immich, ĐŊĐž Đž ĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… в йаСĐĩ даĐŊĐŊҋ҅ Immich ĐŊĐĩŅ‚ ĐŊиĐēаĐēĐ¸Ņ… ŅĐ˛ĐĩĐ´ĐĩĐŊиК.", - "maintenance_integrity_untracked_file_job": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŊаĐģĐ¸Ņ‡Đ¸Ņ ĐŊĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧҋ҅ Ņ„Đ°ĐšĐģОв", - "maintenance_integrity_untracked_file_refresh_job": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŊĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧҋ҅ Ņ„Đ°ĐšĐģОв", "maintenance_restore_backup": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅƒŅŽ ĐēĐžĐŋĐ¸ŅŽ", "maintenance_restore_backup_description": "База даĐŊĐŊҋ҅ Immich ĐąŅƒĐ´ĐĩŅ‚ ĐžŅ‡Đ¸Ņ‰ĐĩĐŊа и ĐˇĐ°Ņ‚ĐĩĐŧ Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊа иС Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊОК Ņ€ĐĩСĐĩŅ€Đ˛ĐŊОК ĐēĐžĐŋии. ĐĸĐĩĐēŅƒŅ‰ĐĩĐĩ ŅĐžŅŅ‚ĐžŅĐŊиĐĩ Ņ‚ĐžĐļĐĩ ĐąŅƒĐ´ĐĩŅ‚ ĐŋŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģҌĐŊĐž ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐž.", "maintenance_restore_backup_different_version": "Đ­Ņ‚Đ° Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ°Ņ ĐēĐžĐŋĐ¸Ņ ĐąŅ‹Đģа ŅĐ´ĐĩĐģаĐŊа ĐŊа Đ´Ņ€ŅƒĐŗĐžĐš вĐĩŅ€ŅĐ¸Đ¸ Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ ĐŋĐž ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊОК ĐŋĐžŅ‡Ņ‚Đĩ", "notification_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК", "notification_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧи ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК, вĐēĐģŅŽŅ‡Đ°Ņ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊŅƒŅŽ ĐŋĐžŅ‡Ņ‚Ņƒ", - "oauth_allow_insecure_requests": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ ĐŊĐĩĐąĐĩСОĐŋĐ°ŅĐŊŅ‹Đĩ СаĐŋŅ€ĐžŅŅ‹", - "oauth_allow_insecure_requests_description": "ВНИМАНИЕ: Đ­Ņ‚ĐžŅ‚ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ ĐžŅ‚ĐēĐģŅŽŅ‡Đ°ĐĩŅ‚ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đē҃ TLS-ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ĐžĐ˛ Đ´ĐģŅ OAuth СаĐŋŅ€ĐžŅĐžĐ˛, Ņ‡Ņ‚Đž ĐŧĐžĐļĐĩŅ‚ ŅĐ´ĐĩĐģĐ°Ņ‚ŅŒ ŅĐ¸ŅŅ‚ĐĩĐŧ҃ ŅƒŅĐˇĐ˛Đ¸ĐŧОК Đ´ĐģŅ MITM Đ°Ņ‚Đ°Đē.", "oauth_auto_launch": "ĐĐ˛Ņ‚ĐžĐˇĐ°Đŋ҃ҁĐē", "oauth_auto_launch_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēиК СаĐŋ҃ҁĐē ĐŋŅ€ĐžŅ†ĐĩŅŅĐ° Đ˛Ņ…ĐžĐ´Đ° в ŅĐ¸ŅŅ‚ĐĩĐŧ҃ ҇ĐĩŅ€ĐĩС OAuth ĐŋŅ€Đ¸ ĐŋĐĩŅ€ĐĩŅ…ĐžĐ´Đĩ ĐŊа ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņƒ Đ˛Ņ…ĐžĐ´Đ°", "oauth_auto_register": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐ°Ņ Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ", @@ -300,11 +274,9 @@ "oauth_button_text": "ĐĸĐĩĐēҁ҂ ĐēĐŊĐžĐŋĐēи", "oauth_client_secret_description": "ĐĸŅ€ĐĩĐąŅƒĐĩŅ‚ŅŅ Đ´ĐģŅ ĐēĐžĐŊŅ„Đ¸Đ´ĐĩĐŊŅ†Đ¸Đ°ĐģҌĐŊҋ҅ ĐēĐģиĐĩĐŊŅ‚ĐžĐ˛ иĐģи ĐĩҁĐģи PKCE (Proof Key for Code Exchange) ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ Đ´ĐģŅ ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊҋ҅ ĐēĐģиĐĩĐŊŅ‚ĐžĐ˛.", "oauth_enable_description": "Đ’Ņ…ĐžĐ´ ҁ ĐŋĐžĐŧĐžŅ‰ŅŒŅŽ OAuth", - "oauth_end_session_url_description": "ПĐĩŅ€ĐĩĐŊаĐŋŅ€Đ°Đ˛Đ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ ĐŊа ŅŅ‚ĐžŅ‚ URI ĐŋŅ€Đ¸ Đ˛Ņ‹Ņ…ĐžĐ´Đĩ иС ŅĐ¸ŅŅ‚ĐĩĐŧŅ‹.", "oauth_mobile_redirect_uri": "URI Ņ€ĐĩĐ´Đ¸Ņ€ĐĩĐēŅ‚Đ° Đ´ĐģŅ ĐŧОйиĐģҌĐŊҋ҅", "oauth_mobile_redirect_uri_override": "ПĐĩŅ€ĐĩĐŊаĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ URI Đ´ĐģŅ ĐŧОйиĐģҌĐŊҋ҅ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛", "oauth_mobile_redirect_uri_override_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ, ĐĩҁĐģи ĐŋĐžŅŅ‚Đ°Đ˛Ņ‰Đ¸Đē OAuth ĐŊĐĩ Ņ€Đ°ĐˇŅ€ĐĩŅˆĐ°ĐĩŅ‚ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ ĐŧОйиĐģҌĐŊĐžĐŗĐž URI, ĐŊаĐŋŅ€Đ¸ĐŧĐĩŅ€, ''{callback}''", - "oauth_prompt_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ СаĐŋŅ€ĐžŅĐ° (ĐŊаĐŋŅ€Đ¸ĐŧĐĩŅ€, select_account, login, consent)", "oauth_role_claim": "ĐŖŅ‚Đ˛ĐĩŅ€ĐļĐ´ĐĩĐŊиĐĩ Ņ€ĐžĐģи", "oauth_role_claim_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ĐģĐĩĐŊиĐĩ Đ´ĐžŅŅ‚ŅƒĐŋа адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ĐŊа ĐžŅĐŊОвĐĩ ĐŊаĐģĐ¸Ņ‡Đ¸Ņ ŅŅ‚ĐžĐŗĐž ŅƒŅ‚Đ˛ĐĩŅ€ĐļĐ´ĐĩĐŊĐ¸Ņ. ĐŖŅ‚Đ˛ĐĩŅ€ĐļĐ´ĐĩĐŊиĐĩ ĐŧĐžĐļĐĩŅ‚ иĐŧĐĩŅ‚ŅŒ СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 'user' иĐģи 'admin'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "ОбĐŊОвĐģĐĩĐŊиĐĩ Đ˛ŅĐĩŅ… йийĐģĐ¸ĐžŅ‚ĐĩĐē", "registration": "Đ ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "registration_description": "ПĐĩŅ€Đ˛Ņ‹Đš ĐˇĐ°Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊŅ‹Đš ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ ĐąŅƒĐ´ĐĩŅ‚ ĐŊаСĐŊĐ°Ņ‡ĐĩĐŊ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ĐžĐŧ. ОĐŊ ҁĐŧĐžĐļĐĩŅ‚ ҃ĐŋŅ€Đ°Đ˛ĐģŅŅ‚ŅŒ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ и ŅĐžĐˇĐ´Đ°Đ˛Đ°Ņ‚ŅŒ Đ´ĐžĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊҋ҅ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš.", - "release_channel_release_candidate": "ĐŸŅ€ĐĩĐ´Đ˛Đ°Ņ€Đ¸Ņ‚ĐĩĐģҌĐŊŅ‹Đĩ вĐĩŅ€ŅĐ¸Đ¸", - "release_channel_stable": "ĐĄŅ‚Đ°ĐąĐ¸ĐģҌĐŊŅ‹Đĩ Ņ€ĐĩĐģĐ¸ĐˇŅ‹", "remove_failed_jobs": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐˇĐ°Đ´Đ°Ņ‡Đ¸ ҁ ĐžŅˆĐ¸ĐąĐēаĐŧи", "require_password_change_on_login": "ĐĸŅ€ĐĩĐąĐžĐ˛Đ°Ņ‚ŅŒ ҁĐŧĐĩĐŊ҃ ĐŋĐ°Ņ€ĐžĐģŅ ĐŋŅ€Đ¸ ĐŋĐĩŅ€Đ˛ĐžĐŧ Đ˛Ņ…ĐžĐ´Đĩ", "reset_settings_to_default": "ĐĄĐąŅ€ĐžŅ ĐŊĐ°ŅŅ‚Ņ€ĐžĐĩĐē Đ´Đž СĐŊĐ°Ņ‡ĐĩĐŊиК ĐŋĐž ҃ĐŧĐžĐģŅ‡Đ°ĐŊĐ¸ŅŽ", @@ -392,52 +362,44 @@ "transcoding_acceleration_qsv": "Quick Sync (҂ҀĐĩĐąŅƒĐĩŅ‚ŅŅ ĐŋŅ€ĐžŅ†ĐĩŅŅĐžŅ€ Intel 7-ĐŗĐž ĐŋĐžĐēĐžĐģĐĩĐŊĐ¸Ņ иĐģи ĐŊОвĐĩĐĩ)", "transcoding_acceleration_rkmpp": "RKMPP (Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ SOC Rockchip)", "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "ДоĐŋŅƒŅŅ‚Đ¸ĐŧŅ‹Đĩ Đ°ŅƒĐ´Đ¸ĐžĐēОдĐĩĐēи", - "transcoding_accepted_audio_codecs_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Đ°ŅƒĐ´Đ¸ĐžĐēОдĐĩĐēи, видĐĩĐž ҁ ĐēĐžŅ‚ĐžŅ€Ņ‹Đŧи ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ. Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊҋ҅ ĐŋĐžĐģĐ¸Ņ‚Đ¸Đē ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", - "transcoding_accepted_containers": "ДоĐŋŅƒŅŅ‚Đ¸ĐŧŅ‹Đĩ ĐēĐžĐŊŅ‚ĐĩĐšĐŊĐĩҀҋ", + "transcoding_accepted_audio_codecs": "ПоддĐĩŅ€ĐļиваĐĩĐŧŅ‹Đĩ Đ°ŅƒĐ´Đ¸ĐžĐēОдĐĩĐēи", + "transcoding_accepted_audio_codecs_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Đ°ŅƒĐ´Đ¸ĐžĐēОдĐĩĐēи, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ. Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊҋ҅ ĐŋĐžĐģĐ¸Ņ‚Đ¸Đē ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", + "transcoding_accepted_containers": "ПоддĐĩŅ€ĐļиваĐĩĐŧŅ‹Đĩ ĐēĐžĐŊŅ‚ĐĩĐšĐŊĐĩҀҋ", "transcoding_accepted_containers_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ, ĐēаĐēиĐĩ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ‹ ĐēĐžĐŊŅ‚ĐĩĐšĐŊĐĩŅ€ĐžĐ˛ ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇĐžĐ˛Ņ‹Đ˛Đ°Ņ‚ŅŒ в MP4. Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊҋ҅ ĐŋĐžĐģĐ¸Ņ‚Đ¸Đē ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", - "transcoding_accepted_video_codecs": "ДоĐŋŅƒŅŅ‚Đ¸ĐŧŅ‹Đĩ видĐĩĐžĐēОдĐĩĐēи", - "transcoding_accepted_video_codecs_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ видĐĩĐžĐēОдĐĩĐēи, видĐĩĐž ҁ ĐēĐžŅ‚ĐžŅ€Ņ‹Đŧи ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ. Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊҋ҅ ĐŋĐžĐģĐ¸Ņ‚Đ¸Đē ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", - "transcoding_advanced_options_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ йОĐģŅŒŅˆĐ¸ĐŊŅŅ‚Đ˛Ņƒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš ĐŊĐĩ ҁĐģĐĩĐ´ŅƒĐĩŅ‚ иСĐŧĐĩĐŊŅŅ‚ŅŒ", + "transcoding_accepted_video_codecs": "ПоддĐĩŅ€ĐļиваĐĩĐŧŅ‹Đĩ видĐĩĐžĐēОдĐĩĐēи", + "transcoding_accepted_video_codecs_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ видĐĩĐžĐēОдĐĩĐēи, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ. Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊҋ҅ ĐŋĐžĐģĐ¸Ņ‚Đ¸Đē ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", + "transcoding_advanced_options_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ йОĐģŅŒŅˆĐ¸ĐŊŅŅ‚Đ˛Ņƒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš ĐŊĐĩ ҁĐģĐĩĐ´ŅƒĐĩŅ‚ ĐŧĐĩĐŊŅŅ‚ŅŒ", "transcoding_audio_codec": "ĐŅƒĐ´Đ¸ĐžĐēОдĐĩĐē", "transcoding_audio_codec_description": "Opus — Đ˛Đ°Ņ€Đ¸Đ°ĐŊŅ‚ ŅĐ°ĐŧĐžĐŗĐž Đ˛Ņ‹ŅĐžĐēĐžĐŗĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ°, ĐŊĐž иĐŧĐĩĐĩŅ‚ ĐŧĐĩĐŊŅŒŅˆŅƒŅŽ ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ŅŒ ŅĐž ŅŅ‚Đ°Ņ€Ņ‹Đŧи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Đŧи иĐģи ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐŧĐŊŅ‹Đŧ ОйĐĩҁĐŋĐĩ҇ĐĩĐŊиĐĩĐŧ.", - "transcoding_bitrate_description": "ВидĐĩĐž ҁ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ĐžĐŧ Đ˛Ņ‹ŅˆĐĩ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐŗĐž иĐģи в ĐŊĐĩĐ´ĐžĐŋŅƒŅŅ‚Đ¸ĐŧĐžĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ", + "transcoding_bitrate_description": "ВидĐĩĐž ҁ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ĐžĐŧ Đ˛Ņ‹ŅˆĐĩ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐŗĐž иĐģи в ĐŊĐĩĐŋĐžĐ´Ņ…ĐžĐ´ŅŅ‰ĐĩĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ ĐŧĐžĐļĐĩŅ‚ Đ˛Ņ‹ĐˇĐ˛Đ°Ņ‚ŅŒ ĐŋŅ€ĐžĐąĐģĐĩĐŧŅ‹", "transcoding_codecs_learn_more": "ДĐģŅ ĐŋĐžĐŊиĐŧаĐŊĐ¸Ņ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩĐŧОК Ņ‚ĐĩŅ€ĐŧиĐŊĐžĐģĐžĐŗĐ¸Đ¸ ĐžĐąŅ€Đ°Ņ‚Đ¸Ņ‚ĐĩҁҌ Đē Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Đ¸ FFmpeg ĐŋĐž ĐēОдĐĩĐēаĐŧ H.264, HEVC и VP9.", "transcoding_constant_quality_mode": "Đ ĐĩĐļиĐŧ ĐŋĐžŅŅ‚ĐžŅĐŊĐŊĐžĐŗĐž ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ°", "transcoding_constant_quality_mode_description": "Đ ĐĩĐļиĐŧ ICQ ĐģŅƒŅ‡ŅˆĐĩ, ҇ĐĩĐŧ CQP, ĐŊĐž ĐŊĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐŗĐž ҃ҁĐēĐžŅ€ĐĩĐŊĐ¸Ņ ĐĩĐŗĐž ĐŊĐĩ ĐŋОддĐĩŅ€ĐļĐ¸Đ˛Đ°ŅŽŅ‚. ĐŖŅŅ‚Đ°ĐŊОвĐēа ŅŅ‚ĐžĐš ĐžĐŋŅ†Đ¸Đ¸ ĐąŅƒĐ´ĐĩŅ‚ ĐžŅ‚Đ´Đ°Đ˛Đ°Ņ‚ŅŒ ĐŋŅ€ĐĩĐ´ĐŋĐžŅ‡Ņ‚ĐĩĐŊиĐĩ ҃ĐēаСаĐŊĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧ҃ ĐŋŅ€Đ¸ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊии ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ ĐŊа ĐžŅĐŊОвĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ°. NVENC ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ Ņ€ĐĩĐļиĐŧ ICQ.", - "transcoding_constant_rate_factor": "ĐŸĐžŅŅ‚ĐžŅĐŊĐŊŅ‹Đš ĐēĐžŅŅ„Ņ„Đ¸Ņ†Đ¸ĐĩĐŊŅ‚ ҁĐēĐžŅ€ĐžŅŅ‚Đ¸ (-crf)", + "transcoding_constant_rate_factor": "ĐšĐžŅŅ„Ņ„Đ¸Ņ†Đ¸ĐĩĐŊŅ‚ ĐŋĐžŅŅ‚ĐžŅĐŊĐŊОК ҁĐēĐžŅ€ĐžŅŅ‚Đ¸ (-crf)", "transcoding_constant_rate_factor_description": "ĐŖŅ€ĐžĐ˛ĐĩĐŊҌ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ° видĐĩĐž. ĐĸиĐŋĐ¸Ņ‡ĐŊŅ‹Đĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ: 23 Đ´ĐģŅ H.264, 28 Đ´ĐģŅ HEVC, 31 Đ´ĐģŅ VP9 и 35 Đ´ĐģŅ AV1. ЧĐĩĐŧ ĐŊиĐļĐĩ, Ņ‚ĐĩĐŧ ĐģŅƒŅ‡ŅˆĐĩ, ĐŊĐž ĐŋŅ€Đ¸ ŅŅ‚ĐžĐŧ ŅĐžĐˇĐ´Đ°ŅŽŅ‚ŅŅ Ņ„Đ°ĐšĐģŅ‹ йОĐģҌ҈ĐĩĐŗĐž Ņ€Đ°ĐˇĐŧĐĩŅ€Đ°.", - "transcoding_disabled_description": "НĐĩ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŊиĐēаĐēиĐĩ видĐĩĐž, ĐŧĐžĐļĐĩŅ‚ ĐŋŅ€Đ¸Đ˛ĐĩŅŅ‚Đ¸ Đē ĐŋŅ€ĐžĐąĐģĐĩĐŧаĐŧ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ ĐŊа ĐŊĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… ĐēĐģиĐĩĐŊŅ‚Đ°Ņ…", + "transcoding_disabled_description": "НĐĩ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ видĐĩĐž, ŅŅ‚Đž ĐŧĐžĐļĐĩŅ‚ ĐŋŅ€Đ¸Đ˛ĐĩŅŅ‚Đ¸ Đē ŅĐąĐžŅŽ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ ĐŊа ĐŊĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… ĐēĐģиĐĩĐŊŅ‚Đ°Ņ…", "transcoding_encoding_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", - "transcoding_encoding_options_description": "КодĐĩĐēи, Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ, ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž и Đ´Ņ€ŅƒĐŗĐ¸Đĩ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Đ´ĐģŅ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ видĐĩĐž", + "transcoding_encoding_options_description": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ ĐēОдĐĩĐēи, Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ, ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž и Đ´Ņ€ŅƒĐŗĐ¸Đĩ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Đ´ĐģŅ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊĐžĐŗĐž видĐĩĐž", "transcoding_hardware_acceleration": "АĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐĩ ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩ", "transcoding_hardware_acceleration_description": "Đ­ĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊĐž: йОĐģĐĩĐĩ ĐąŅ‹ŅŅ‚Ņ€ĐžĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ, ĐŊĐž ҁ вОСĐŧĐžĐļĐŊŅ‹Đŧ ŅƒŅ…ŅƒĐ´ŅˆĐĩĐŊиĐĩĐŧ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ° ĐŋŅ€Đ¸ Ņ‚ĐžĐŧ ĐļĐĩ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đĩ", "transcoding_hardware_decoding": "АĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐĩ Đ´ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", "transcoding_hardware_decoding_setting_description": "ДоĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊĐž ҃ҁĐēĐžŅ€ŅĐĩŅ‚ Đ´ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ, а ĐŊĐĩ Ņ‚ĐžĐģҌĐēĐž ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. МоĐļĐĩŅ‚ Ņ€Đ°ĐąĐžŅ‚Đ°Ņ‚ŅŒ ĐŊĐĩ ŅĐž Đ˛ŅĐĩĐŧи видĐĩĐž.", - "transcoding_max_b_frames": "МаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐĩ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€ĐžĐŧĐĩĐļŅƒŅ‚ĐžŅ‡ĐŊҋ҅ ĐēĐ°Đ´Ņ€ĐžĐ˛", - "transcoding_max_b_frames_description": "БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž СаĐŧĐĩĐ´ĐģŅŅŽŅ‚ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. МоĐļĐĩŅ‚ ĐąŅ‹Ņ‚ŅŒ ĐŊĐĩŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐž ҁ аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊŅ‹Đŧ ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩĐŧ ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Ņ…. ЗĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0 ĐŋĐžĐģĐŊĐžŅŅ‚ŅŒŅŽ ĐžŅ‚ĐēĐģŅŽŅ‡Đ°ĐĩŅ‚ ŅĐžĐˇĐ´Đ°ĐŊиĐĩ ĐŋŅ€ĐžĐŧĐĩĐļŅƒŅ‚ĐžŅ‡ĐŊҋ҅ B-ĐēĐ°Đ´Ņ€ĐžĐ˛, а -1 - ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊиĐĩ Đ¸Ņ… ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đ°.", + "transcoding_max_b_frames": "МаĐēŅĐ¸ĐŧаĐģҌĐŊĐž ĐŋŅ€ĐžĐŧĐĩĐļŅƒŅ‚ĐžŅ‡ĐŊҋ҅ ĐēĐ°Đ´Ņ€ĐžĐ˛", + "transcoding_max_b_frames_description": "БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž СаĐŧĐĩĐ´ĐģŅŅŽŅ‚ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. МоĐļĐĩŅ‚ ĐąŅ‹Ņ‚ŅŒ ĐŊĐĩŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐž ҁ аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊŅ‹Đŧ ҃ҁĐēĐžŅ€ĐĩĐŊиĐĩĐŧ ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Ņ…. 0 ĐžŅ‚ĐēĐģŅŽŅ‡Đ°ĐĩŅ‚ B-ĐēĐ°Đ´Ņ€Ņ‹, а -1 ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ ŅŅ‚Đž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи.", "transcoding_max_bitrate": "МаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚", "transcoding_max_bitrate_description": "ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ° ĐŧĐžĐļĐĩŅ‚ ŅĐ´ĐĩĐģĐ°Ņ‚ŅŒ Ņ€Đ°ĐˇĐŧĐĩŅ€ Ņ„Đ°ĐšĐģа йОĐģĐĩĐĩ ĐŋŅ€ĐĩĐ´ŅĐēĐ°ĐˇŅƒĐĩĐŧŅ‹Đŧ ĐŋŅ€Đ¸ ĐŊĐĩСĐŊĐ°Ņ‡Đ¸Ņ‚ĐĩĐģҌĐŊĐžĐŧ ҁĐŊиĐļĐĩĐŊии ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ°. ĐŸŅ€Đ¸ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊии 720p Ņ‚Đ¸ĐŋĐ¸Ņ‡ĐŊŅ‹Đŧи СĐŊĐ°Ņ‡ĐĩĐŊĐ¸ŅĐŧи ŅĐ˛ĐģŅŅŽŅ‚ŅŅ 2600 ĐēĐąĐ¸Ņ‚/ҁ Đ´ĐģŅ ĐēОдĐĩĐēОв VP9 иĐģи HEVC и 4500 ĐēĐąĐ¸Ņ‚/ҁ Đ´ĐģŅ H.264. ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊиĐĩ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž, ĐĩҁĐģи СадаĐŊĐž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0. ĐšĐžĐŗĐ´Đ° ĐĩдиĐŊĐ¸Ņ†Đ° иСĐŧĐĩŅ€ĐĩĐŊĐ¸Ņ ĐŊĐĩ ҃ĐēаСаĐŊа, ĐŋŅ€ĐĩĐ´ĐŋĐžĐģĐ°ĐŗĐ°ĐĩŅ‚ŅŅ k (ĐēĐąĐ¸Ņ‚/ҁ); ĐŋĐžŅŅ‚ĐžĐŧ҃ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ 5000, 5000k и 5M (Đ´ĐģŅ ĐœĐąĐ¸Ņ‚/ҁ) ŅĐēвиваĐģĐĩĐŊŅ‚ĐŊŅ‹.", "transcoding_max_keyframe_interval": "МаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš иĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ ĐēĐģŅŽŅ‡ĐĩĐ˛Ņ‹Ņ… ĐēĐ°Đ´Ņ€ĐžĐ˛", - "transcoding_max_keyframe_interval_description": "ĐŖŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐĩ Ņ€Đ°ŅŅŅ‚ĐžŅĐŊиĐĩ ĐŧĐĩĐļĐ´Ņƒ ĐēĐģŅŽŅ‡ĐĩĐ˛Ņ‹Đŧи ĐēĐ°Đ´Ņ€Đ°Đŧи. БоĐģĐĩĐĩ ĐŊиСĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ŅƒŅ…ŅƒĐ´ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž ŅĐžĐēŅ€Đ°Ņ‰Đ°ŅŽŅ‚ Đ˛Ņ€ĐĩĐŧŅ ĐŋĐžĐ¸ŅĐēа и ĐŧĐžĐŗŅƒŅ‚ ҃ĐģŅƒŅ‡ŅˆĐ¸Ņ‚ŅŒ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž в ҁ҆ĐĩĐŊĐ°Ņ… ҁ ĐąŅ‹ŅŅ‚Ņ€Ņ‹Đŧ двиĐļĐĩĐŊиĐĩĐŧ. ЗĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0 ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊиĐĩ.", - "transcoding_optimal_description": "ВидĐĩĐž ҁ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸ŅĐŧи Đ˛Ņ‹ŅˆĐĩ ҆ĐĩĐģĐĩĐ˛ĐžĐŗĐž иĐģи в ĐŊĐĩĐ´ĐžĐŋŅƒŅŅ‚Đ¸ĐŧĐžĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ", - "transcoding_policy": "ПоĐģĐ¸Ņ‚Đ¸Đēа ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", - "transcoding_policy_description": "КаĐēиĐĩ видĐĩĐž Đ´ĐžĐģĐļĐŊŅ‹ ĐŋОдвĐĩŅ€ĐŗĐ°Ņ‚ŅŒŅŅ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸ŅŽ", + "transcoding_max_keyframe_interval_description": "ĐŖŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐĩ Ņ€Đ°ŅŅŅ‚ĐžŅĐŊиĐĩ ĐŧĐĩĐļĐ´Ņƒ ĐēĐģŅŽŅ‡ĐĩĐ˛Ņ‹Đŧи ĐēĐ°Đ´Ņ€Đ°Đŧи. БоĐģĐĩĐĩ ĐŊиСĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ŅƒŅ…ŅƒĐ´ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž ŅĐžĐēŅ€Đ°Ņ‰Đ°ŅŽŅ‚ Đ˛Ņ€ĐĩĐŧŅ ĐŋĐžĐ¸ŅĐēа и ĐŧĐžĐŗŅƒŅ‚ ҃ĐģŅƒŅ‡ŅˆĐ¸Ņ‚ŅŒ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž в ҁ҆ĐĩĐŊĐ°Ņ… ҁ ĐąŅ‹ŅŅ‚Ņ€Ņ‹Đŧ двиĐļĐĩĐŊиĐĩĐŧ. 0 ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ ŅŅ‚Đž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи.", + "transcoding_optimal_description": "ВидĐĩĐž ҁ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩĐŧ Đ˛Ņ‹ŅˆĐĩ ҆ĐĩĐģĐĩĐ˛ĐžĐŗĐž иĐģи ĐŊĐĩ в ĐŋŅ€Đ¸ĐŊŅŅ‚ĐžĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ", + "transcoding_policy": "ПоĐģĐ¸Ņ‚Đ¸Đēа ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đēи", + "transcoding_policy_description": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ, ĐēĐžĐŗĐ´Đ° видĐĩĐž ĐąŅƒĐ´ĐĩŅ‚ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐž", "transcoding_preferred_hardware_device": "ĐŸŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°ĐĩĐŧĐžĐĩ аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž", "transcoding_preferred_hardware_device_description": "ĐŸŅ€Đ¸ĐŧĐĩĐŊŅĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Đē VAAPI и QSV. ĐŖŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ dri-ŅƒĐˇĐĩĐģ, Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩĐŧŅ‹Đš Đ´ĐģŅ аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐŗĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", - "transcoding_preset_preset": "ĐŸŅ€ĐžŅ„Đ¸ĐģҌ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ (-preset)", - "transcoding_preset_preset_description": "ОĐŋŅ€ĐĩĐ´ĐĩĐģŅŅŽŅ‚ ҁĐēĐžŅ€ĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ. БоĐģĐĩĐĩ ĐŧĐĩĐ´ĐģĐĩĐŊĐŊŅ‹Đĩ ĐŋŅ€ĐžŅ„Đ¸Đģи ĐŋŅ€Đ¸Đ˛ĐžĐ´ŅŅ‚ Đē ŅĐžĐˇĐ´Đ°ĐŊĐ¸ŅŽ Ņ„Đ°ĐšĐģОв ĐŧĐĩĐŊҌ҈ĐĩĐŗĐž Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° и ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€Đ¸ ŅƒŅŅ‚Đ°ĐŊОвĐēĐĩ Đ´ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐžĐŗĐž ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ°. VP9 Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ŅƒĐĩŅ‚ ĐŋŅ€ĐžŅ„Đ¸Đģи Đ˛Ņ‹ŅˆĐĩ “faster”.", - "transcoding_realtime": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊи", - "transcoding_realtime_description": "ПозвоĐģŅĐĩŅ‚ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ видĐĩĐž в Ņ€ĐĩĐļиĐŧĐĩ Ņ€ĐĩаĐģҌĐŊĐžĐŗĐž Đ˛Ņ€ĐĩĐŧĐĩĐŊи вО Đ˛Ņ€ĐĩĐŧŅ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ. ПоддĐĩŅ€ĐļиваĐĩŅ‚ ĐŋĐĩŅ€ĐĩĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ°, ĐŊĐž ĐŧĐžĐļĐĩŅ‚ Đ˛Ņ‹ĐˇŅ‹Đ˛Đ°Ņ‚ŅŒ СадĐĩŅ€ĐļĐēи Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ и ĐŋĐžĐ´Ņ‚ĐžŅ€ĐŧаĐļиваĐŊĐ¸Ņ в ҁĐģŅƒŅ‡Đ°Đĩ ĐŊĐĩĐ´ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊОК ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģҌĐŊĐžŅŅ‚Đ¸ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", - "transcoding_realtime_enabled": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊи", - "transcoding_realtime_enabled_description": "ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ ĐŋŅ€Đ¸Đ˛ĐĩĐ´Ņ‘Ņ‚ Đē ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊĐžŅŅ‚Đ¸ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊи.", - "transcoding_realtime_resolutions": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ", - "transcoding_realtime_resolutions_description": "Đ”ĐžŅŅ‚ŅƒĐŋĐŊŅ‹Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊи. Đ’Ņ‹ŅĐžĐēиĐĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ ĐŧĐžĐŗŅƒŅ‚ Đ˛Ņ‹ĐˇĐ˛Đ°Ņ‚ŅŒ ĐŋŅ€ĐžĐąĐģĐĩĐŧŅ‹ ҁ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩĐŧ, ĐĩҁĐģи вОСĐŧĐžĐļĐŊĐžŅŅ‚ĐĩĐš ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐąŅƒĐ´ĐĩŅ‚ ĐŊĐĩĐ´ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž.", - "transcoding_realtime_video_codecs": "ВидĐĩĐžĐēОдĐĩĐēи", - "transcoding_realtime_video_codecs_description": "Đ”ĐžŅŅ‚ŅƒĐŋĐŊŅ‹Đĩ видĐĩĐžĐēОдĐĩĐēи Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊи. КĐģиĐĩĐŊ҂ҋ ĐąŅƒĐ´ŅƒŅ‚ Đ˛Ņ‹ĐąĐ¸Ņ€Đ°Ņ‚ŅŒ ĐŊаийОĐģĐĩĐĩ ĐŋĐžĐ´Ņ…ĐžĐ´ŅŅ‰Đ¸Đš ĐēОдĐĩĐē вО Đ˛Ņ€ĐĩĐŧŅ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ. AV1 ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐĩĐĩ, ҇ĐĩĐŧ HEVC, ĐēĐžŅ‚ĐžŅ€Ņ‹Đš, в ŅĐ˛ĐžŅŽ ĐžŅ‡ĐĩŅ€ĐĩĐ´ŅŒ, ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐĩĐĩ, ҇ĐĩĐŧ H.264. ĐŸŅ€Đ¸ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊии аĐŋĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐŗĐž ҃ҁĐēĐžŅ€ĐĩĐŊĐ¸Ņ Đ˛Ņ‹ĐąĐ¸Ņ€Đ°ĐšŅ‚Đĩ Ņ‚ĐžĐģҌĐēĐž Ņ‚Đĩ ĐēОдĐĩĐēи, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ ҃ҁĐēĐžŅ€Đ¸Ņ‚ĐĩĐģҌ. ĐŸŅ€Đ¸ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊии ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐŧĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ ĐžĐąŅ€Đ°Ņ‚Đ¸Ņ‚Đĩ вĐŊиĐŧаĐŊиĐĩ, Ņ‡Ņ‚Đž H.264 ĐąŅ‹ŅŅ‚Ņ€ĐĩĐĩ, ҇ĐĩĐŧ AV1, ĐēĐžŅ‚ĐžŅ€Ņ‹Đš, в ŅĐ˛ĐžŅŽ ĐžŅ‡ĐĩŅ€ĐĩĐ´ŅŒ, ĐąŅ‹ŅŅ‚Ņ€ĐĩĐĩ, ҇ĐĩĐŧ HEVC.", + "transcoding_preset_preset": "ĐŸŅ€ĐĩĐ´ŅƒŅŅ‚Đ°ĐŊОвĐēа", + "transcoding_preset_preset_description": "ĐĄĐēĐžŅ€ĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ. МĐĩĐ´ĐģĐĩĐŊĐŊŅ‹Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ŅĐžĐˇĐ´Đ°ŅŽŅ‚ йОĐģĐĩĐĩ ĐŧаĐģĐĩĐŊҌĐēиĐĩ Ņ„Đ°ĐšĐģŅ‹ и ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€Đ¸ Đ´ĐžŅŅ‚Đ¸ĐļĐĩĐŊии ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐŊĐžĐŗĐž ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ°. VP9 Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ŅƒĐĩŅ‚ ҁĐēĐžŅ€ĐžŅŅ‚Đ¸ Đ˛Ņ‹ŅˆĐĩ “faster”.", "transcoding_reference_frames": "ОĐŋĐžŅ€ĐŊŅ‹Đĩ ĐēĐ°Đ´Ņ€Ņ‹", - "transcoding_reference_frames_description": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐēĐ°Đ´Ņ€ĐžĐ˛, ĐŊа ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ҁĐģĐĩĐ´ŅƒĐĩŅ‚ ҁҁҋĐģĐ°Ņ‚ŅŒŅŅ ĐŋŅ€Đ¸ ҁĐļĐ°Ņ‚Đ¸Đ¸ Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐŗĐž ĐēĐ°Đ´Ņ€Đ°. БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž СаĐŧĐĩĐ´ĐģŅŅŽŅ‚ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. ЗĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0 ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊиĐĩ Đ¸Ņ… ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đ°.", - "transcoding_required_description": "ĐĸĐžĐģҌĐēĐž видĐĩĐž в ĐŊĐĩĐ´ĐžĐŋŅƒŅŅ‚Đ¸ĐŧĐžĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ", + "transcoding_reference_frames_description": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐēĐ°Đ´Ņ€ĐžĐ˛, ĐŊа ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ҁĐģĐĩĐ´ŅƒĐĩŅ‚ ҁҁҋĐģĐ°Ņ‚ŅŒŅŅ ĐŋŅ€Đ¸ ҁĐļĐ°Ņ‚Đ¸Đ¸ даĐŊĐŊĐžĐŗĐž ĐēĐ°Đ´Ņ€Đ°. БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋĐžĐ˛Ņ‹ŅˆĐ°ŅŽŅ‚ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ҁĐļĐ°Ņ‚Đ¸Ņ, ĐŊĐž СаĐŧĐĩĐ´ĐģŅŅŽŅ‚ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. 0 ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩŅ‚ ŅŅ‚Đž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи.", + "transcoding_required_description": "ĐĸĐžĐģҌĐēĐž видĐĩĐž в ĐŊĐĩŅŅ‚Đ°ĐŊĐ´Đ°Ņ€Ņ‚ĐŊĐžĐŧ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ", "transcoding_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ видĐĩĐž", "transcoding_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐšŅ‚Đĩ Ņ‚ĐĩĐŧ, ĐēаĐēиĐĩ видĐĩĐž ĐŊ҃ĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ и ĐēаĐē Đ¸Ņ… ĐžĐąŅ€Đ°ĐąĐ°Ņ‚Ņ‹Đ˛Đ°Ņ‚ŅŒ", "transcoding_target_resolution": "ĐĻĐĩĐģĐĩвОĐĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ", @@ -445,15 +407,15 @@ "transcoding_temporal_aq": "Temporal Adaptive Quantization (Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐžĐĩ адаĐŋŅ‚Đ¸Đ˛ĐŊĐžĐĩ ĐēваĐŊŅ‚ĐžĐ˛Đ°ĐŊиĐĩ)", "transcoding_temporal_aq_description": "ĐŸŅ€Đ¸ĐŧĐĩĐŊиĐŧĐž Ņ‚ĐžĐģҌĐēĐž Đē NVENC (NVIDIA Encoder). ĐĸĐĩŅ…ĐŊĐžĐģĐžĐŗĐ¸Ņ ĐŋĐžĐ˛Ņ‹ŅˆĐ°ĐĩŅ‚ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž Đ˛Ņ‹ŅĐžĐēОдĐĩŅ‚Đ°ĐģĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊҋ҅ ĐŊĐĩдиĐŊаĐŧĐ¸Ņ‡ĐŊҋ҅ ҁ҆ĐĩĐŊ. МоĐļĐĩŅ‚ ĐąŅ‹Ņ‚ŅŒ ĐŊĐĩŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸Đŧа ŅĐž ŅŅ‚Đ°Ņ€Ņ‹Đŧи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Đŧи.", "transcoding_threads": "ĐŸĐžŅ‚ĐžĐēи", - "transcoding_threads_description": "БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋŅ€Đ¸Đ˛ĐžĐ´ŅŅ‚ Đē йОĐģĐĩĐĩ ĐąŅ‹ŅŅ‚Ņ€ĐžĐŧ҃ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸ŅŽ, ĐŊĐž ĐžŅ‚ĐŊиĐŧĐ°ŅŽŅ‚ йОĐģҌ҈Đĩ Ņ€ĐĩŅŅƒŅ€ŅĐžĐ˛ ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Đ´ĐģŅ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ Đ´Ņ€ŅƒĐŗĐ¸Ņ… ĐˇĐ°Đ´Đ°Ņ‡. ЗĐŊĐ°Ņ‡ĐĩĐŊиĐĩ ĐŊĐĩ Đ´ĐžĐģĐļĐŊĐž ĐŋŅ€ĐĩĐ˛Ņ‹ŅˆĐ°Ņ‚ŅŒ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ŅĐ´ĐĩŅ€ ĐŋŅ€ĐžŅ†ĐĩŅŅĐžŅ€Đ°. ЗĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0 ĐŧаĐēŅĐ¸ĐŧĐ¸ĐˇĐ¸Ņ€ŅƒĐĩŅ‚ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ Ņ€ĐĩŅŅƒŅ€ŅĐžĐ˛.", + "transcoding_threads_description": "БоĐģĐĩĐĩ Đ˛Ņ‹ŅĐžĐēиĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐ¸Ņ ĐŋŅ€Đ¸Đ˛ĐžĐ´ŅŅ‚ Đē йОĐģĐĩĐĩ ĐąŅ‹ŅŅ‚Ņ€ĐžĐŧ҃ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸ŅŽ, ĐŊĐž ĐžŅŅ‚Đ°Đ˛ĐģŅŅŽŅ‚ ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ ĐŧĐĩĐŊҌ҈Đĩ ĐŧĐĩŅŅ‚Đ° Đ´ĐģŅ ĐžĐąŅ€Đ°ĐąĐžŅ‚Đēи Đ´Ņ€ŅƒĐŗĐ¸Ņ… ĐˇĐ°Đ´Đ°Ņ‡ вО Đ˛Ņ€ĐĩĐŧŅ аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚Đ¸. Đ­Ņ‚Đž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ ĐŊĐĩ Đ´ĐžĐģĐļĐŊĐž ĐŋŅ€ĐĩĐ˛Ņ‹ŅˆĐ°Ņ‚ŅŒ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ŅĐ´ĐĩŅ€ ĐŋŅ€ĐžŅ†ĐĩŅŅĐžŅ€Đ°. МаĐēŅĐ¸ĐŧĐ¸ĐˇĐ¸Ņ€ŅƒĐĩŅ‚ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ, ĐĩҁĐģи ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ 0.", "transcoding_tone_mapping": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ Ņ‚ĐžĐŊОв", - "transcoding_tone_mapping_description": "ĐĸĐĩŅ…ĐŊĐžĐģĐžĐŗĐ¸Ņ ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ HDR-видĐĩĐž ĐŋŅ€Đ¸ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇĐžĐ˛Đ°ĐŊии в SDR. КаĐļĐ´Ņ‹Đš аĐģĐŗĐžŅ€Đ¸Ņ‚Đŧ ĐŋŅ€ĐĩĐ´ĐģĐ°ĐŗĐ°ĐĩŅ‚ Ņ€Đ°ĐˇĐŊŅ‹Đĩ ĐēĐžĐŧĐŋŅ€ĐžĐŧĐ¸ŅŅŅ‹ ĐŧĐĩĐļĐ´Ņƒ Ņ†Đ˛ĐĩŅ‚ĐžĐŧ, Đ´ĐĩŅ‚Đ°ĐģĐ¸ĐˇĐ°Ņ†Đ¸ĐĩĐš и ŅŅ€ĐēĐžŅŅ‚ŅŒŅŽ. Hable ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ Đ´ĐĩŅ‚Đ°Đģи, Mobius ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ Ņ†Đ˛ĐĩŅ‚, а Reinhard ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ ŅŅ€ĐēĐžŅŅ‚ŅŒ.", + "transcoding_tone_mapping_description": "ĐŸŅ‹Ņ‚Đ°ĐĩŅ‚ŅŅ ŅĐžŅ…Ņ€Đ°ĐŊĐ¸Ņ‚ŅŒ вĐŊĐĩ҈ĐŊиК вид HDR-видĐĩĐž ĐŋŅ€Đ¸ ĐŋŅ€ĐĩĐžĐąŅ€Đ°ĐˇĐžĐ˛Đ°ĐŊии в SDR. КаĐļĐ´Ņ‹Đš аĐģĐŗĐžŅ€Đ¸Ņ‚Đŧ Đ´ĐĩĐģаĐĩŅ‚ Ņ€Đ°ĐˇĐŊŅ‹Đĩ ĐēĐžĐŧĐŋŅ€ĐžĐŧĐ¸ŅŅŅ‹ ĐŧĐĩĐļĐ´Ņƒ Ņ†Đ˛ĐĩŅ‚ĐžĐŧ, Đ´ĐĩŅ‚Đ°ĐģĐ¸ĐˇĐ°Ņ†Đ¸ĐĩĐš и ŅŅ€ĐēĐžŅŅ‚ŅŒŅŽ. Hable ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ Đ´ĐĩŅ‚Đ°Đģи, Mobius ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ Ņ†Đ˛ĐĩŅ‚, а Reinhard ŅĐžŅ…Ņ€Đ°ĐŊŅĐĩŅ‚ ŅŅ€ĐēĐžŅŅ‚ŅŒ.", "transcoding_transcode_policy": "ПоĐģĐ¸Ņ‚Đ¸Đēа ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", - "transcoding_transcode_policy_description": "ОĐŋŅ€ĐĩĐ´ĐĩĐģŅĐĩŅ‚ ĐēаĐēиĐĩ видĐĩĐž Đ´ĐžĐģĐļĐŊŅ‹ ĐąŅ‹Ņ‚ŅŒ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹. HDR-видĐĩĐž и видĐĩĐž ҁ Ņ„ĐžŅ€ĐŧĐ°Ņ‚ĐžĐŧ ĐŋиĐēҁĐĩĐģĐĩĐš, ĐžŅ‚ĐģĐ¸Ņ‡ĐŊŅ‹Đŧ ĐžŅ‚ YUV 4:2:0, Đ˛ŅĐĩĐŗĐ´Đ° ĐąŅƒĐ´ŅƒŅ‚ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒŅŅ (Са Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊиĐĩĐŧ ҁĐģŅƒŅ‡Đ°Đĩв, ĐēĐžĐŗĐ´Đ° ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž).", + "transcoding_transcode_policy_description": "ĐŸŅ€Đ°Đ˛Đ¸Đģа, ĐžĐŋŅ€ĐĩĐ´ĐĩĐģŅŅŽŅ‰Đ¸Đĩ, ĐēаĐēиĐĩ видĐĩĐž Đ´ĐžĐģĐļĐŊŅ‹ ĐąŅ‹Ņ‚ŅŒ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹. HDR-видĐĩĐž и видĐĩĐž ҁ Ņ„ĐžŅ€ĐŧĐ°Ņ‚ĐžĐŧ ĐŋиĐēҁĐĩĐģĐĩĐš, ĐžŅ‚ĐģĐ¸Ņ‡ĐŊŅ‹Đŧ ĐžŅ‚ YUV 4:2:0, Đ˛ŅĐĩĐŗĐ´Đ° ĐąŅƒĐ´ŅƒŅ‚ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒŅŅ (Са Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊиĐĩĐŧ ҁĐģŅƒŅ‡Đ°Đĩв, ĐēĐžĐŗĐ´Đ° ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž).", "transcoding_two_pass_encoding": "Đ”Đ˛ŅƒŅ…ĐŋŅ€ĐžŅ…ĐžĐ´ĐŊĐžĐĩ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", - "transcoding_two_pass_encoding_setting_description": "ПĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в два ĐŋŅ€ĐžŅ…ĐžĐ´Đ° Đ´ĐģŅ ĐŋĐžĐģŅƒŅ‡ĐĩĐŊĐ¸Ņ йОĐģĐĩĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐĩĐŊĐŊĐžĐŗĐž видĐĩĐž. ĐšĐžĐŗĐ´Đ° вĐēĐģŅŽŅ‡Ņ‘ĐŊ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ (҂ҀĐĩĐąŅƒĐĩŅ‚ŅŅ Đ´ĐģŅ Ņ€Đ°ĐąĐžŅ‚Ņ‹ ҁ H.264 и HEVC), ŅŅ‚ĐžŅ‚ Ņ€ĐĩĐļиĐŧ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ диаĐŋаСОĐŊ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ°, ĐžĐŋĐ¸Ņ€Đ°ŅŅŅŒ ĐŊа СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐŗĐž ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ°, и Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ŅƒĐĩŅ‚ CRF (ĐŋĐžŅŅ‚ĐžŅĐŊĐŊŅ‹Đš ĐēĐžŅŅ„Ņ„Đ¸Ņ†Đ¸ĐĩĐŊŅ‚ ҁĐēĐžŅ€ĐžŅŅ‚Đ¸). ДĐģŅ VP9 CRF ĐŧĐžĐļĐĩŅ‚ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒŅŅ Ņ‚ĐžĐģҌĐēĐž ĐĩҁĐģи ĐžŅ‚ĐēĐģŅŽŅ‡Ņ‘ĐŊ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚.", + "transcoding_two_pass_encoding_setting_description": "ПĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ŅƒĐšŅ‚Đĩ Са два ĐŋŅ€ĐžŅ…ĐžĐ´Đ°, Ņ‡Ņ‚ĐžĐąŅ‹ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ йОĐģĐĩĐĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛ĐĩĐŊĐŊĐžĐĩ ĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ видĐĩĐž. ĐšĐžĐŗĐ´Đ° вĐēĐģŅŽŅ‡ĐĩĐŊ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚ (ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸Đŧ Đ´ĐģŅ Ņ€Đ°ĐąĐžŅ‚Ņ‹ ҁ H.264 и HEVC), в ŅŅ‚ĐžĐŧ Ņ€ĐĩĐļиĐŧĐĩ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ диаĐŋаСОĐŊ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đ°, ĐžŅĐŊОваĐŊĐŊŅ‹Đš ĐŊа ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊĐžĐŧ ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚Đĩ, и Đ¸ĐŗĐŊĐžŅ€Đ¸Ņ€ŅƒĐĩŅ‚ŅŅ CRF. ДĐģŅ VP9 ĐŧĐžĐļĐŊĐž Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ CRF, ĐĩҁĐģи ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊŅ‹Đš ĐąĐ¸Ņ‚Ņ€ĐĩĐšŅ‚.", "transcoding_video_codec": "ВидĐĩĐžĐēОдĐĩĐē", - "transcoding_video_codec_description": "VP9 ОйĐģадаĐĩŅ‚ Đ˛Ņ‹ŅĐžĐēОК ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒŅŽ и вĐĩĐą-ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ŅŒŅŽ, ĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ СаĐŊиĐŧаĐĩŅ‚ йОĐģҌ҈Đĩ Đ˛Ņ€ĐĩĐŧĐĩĐŊи. HEVC Ņ€Đ°ĐąĐžŅ‚Đ°ĐĩŅ‚ аĐŊаĐģĐžĐŗĐ¸Ņ‡ĐŊĐž, ĐŊĐž иĐŧĐĩĐĩŅ‚ ĐŧĐĩĐŊŅŒŅˆŅƒŅŽ вĐĩĐą-ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ŅŒ. H.264 ŅˆĐ¸Ņ€ĐžĐēĐž ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸Đŧ и ĐąŅ‹ŅŅ‚Ņ€Đž ĐēĐžĐ´Đ¸Ņ€ŅƒĐĩŅ‚ŅŅ, ĐŊĐž ŅĐžĐˇĐ´Đ°Ņ‘Ņ‚ Ņ„Đ°ĐšĐģŅ‹ ĐŗĐžŅ€Đ°ĐˇĐ´Đž йОĐģҌ҈ĐĩĐŗĐž Ņ€Đ°ĐˇĐŧĐĩŅ€Đ°. AV1 — ĐŊаийОĐģĐĩĐĩ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊŅ‹Đš ĐēОдĐĩĐē, ĐŊĐž ĐžĐŊ ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ ŅŅ‚Đ°Ņ€Ņ‹Đŧи ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Đŧи.", + "transcoding_video_codec_description": "VP9 ОйĐģадаĐĩŅ‚ Đ˛Ņ‹ŅĐžĐēОК ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒŅŽ и вĐĩĐą-ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ŅŒŅŽ, ĐŊĐž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ СаĐŊиĐŧаĐĩŅ‚ йОĐģҌ҈Đĩ Đ˛Ņ€ĐĩĐŧĐĩĐŊи. HEVC Ņ€Đ°ĐąĐžŅ‚Đ°ĐĩŅ‚ аĐŊаĐģĐžĐŗĐ¸Ņ‡ĐŊĐž, ĐŊĐž иĐŧĐĩĐĩŅ‚ ĐŧĐĩĐŊŅŒŅˆŅƒŅŽ вĐĩĐą-ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸ĐŧĐžŅŅ‚ŅŒ. H.264 ŅˆĐ¸Ņ€ĐžĐēĐž ŅĐžĐ˛ĐŧĐĩŅŅ‚Đ¸Đŧ и ĐąŅ‹ŅŅ‚Ņ€Đž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ŅƒĐĩŅ‚ŅŅ, ĐŊĐž ŅĐžĐˇĐ´Đ°ĐĩŅ‚ Ņ„Đ°ĐšĐģŅ‹ ĐŗĐžŅ€Đ°ĐˇĐ´Đž йОĐģҌ҈ĐĩĐŗĐž Ņ€Đ°ĐˇĐŧĐĩŅ€Đ°. AV1 — ĐŊаийОĐģĐĩĐĩ ŅŅ„Ņ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊŅ‹Đš ĐēОдĐĩĐē, ĐŊĐž ĐžĐŊ ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ ĐŊа ŅŅ‚Đ°Ņ€Ņ‹Ņ… ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Ņ….", "trash_enabled_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", "trash_number_of_days": "ĐĄŅ€ĐžĐē Ņ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ", "trash_number_of_days_description": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž Đ´ĐŊĐĩĐš, в Ņ‚Đĩ҇ĐĩĐŊиĐĩ ĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… Ņ„Đ°ĐšĐģŅ‹ ĐąŅƒĐ´ŅƒŅ‚ Ņ…Ņ€Đ°ĐŊĐ¸Ņ‚ŅŒŅŅ в ĐēĐžŅ€ĐˇĐ¸ĐŊĐĩ Đ´Đž ĐžĐēĐžĐŊŅ‡Đ°Ņ‚ĐĩĐģҌĐŊĐžĐŗĐž ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ", @@ -478,8 +440,6 @@ "user_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧи ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš", "user_successfully_removed": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ {email} ҃ҁĐŋĐĩ҈ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊ.", "users_page_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅĐŧи ŅĐ¸ŅŅ‚ĐĩĐŧŅ‹", - "version_check_channel": "КаĐŊаĐģ ОйĐŊОвĐģĐĩĐŊиК", - "version_check_channel_description": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐēаĐŊаĐģ ĐŋĐž ĐēĐžŅ‚ĐžŅ€ĐžĐŧ҃ ĐŋĐžĐģŅƒŅ‡Đ°Ņ‚ŅŒ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ Đž ĐŊĐžĐ˛Ņ‹Ņ… вĐĩŅ€ŅĐ¸ŅŅ…", "version_check_enabled_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đē҃ ĐŊаĐģĐ¸Ņ‡Đ¸Ņ ĐŊĐžĐ˛Ņ‹Ņ… вĐĩŅ€ŅĐ¸Đš", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Đ¸Ņ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đēи вĐĩŅ€ŅĐ¸Đ¸ ĐŋĐĩŅ€Đ¸ĐžĐ´Đ¸Ņ‡ĐĩҁĐēи ĐžĐąŅ€Đ°Ņ‰Đ°ĐĩŅ‚ŅŅ Đē ŅĐ°ĐšŅ‚Ņƒ {server}", "version_check_settings": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа вĐĩŅ€ŅĐ¸Đ¸", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐēŅŅˆ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиК", "advanced_settings_clear_image_cache_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐēŅŅˆ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиК", "advanced_settings_clear_image_cache_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐžŅ‡Đ¸Ņ‰ĐĩĐŊĐž {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "ĐŸĐžĐ´ĐąĐžŅ€ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ Đ´ĐģŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ ĐŊа ĐžŅĐŊОвĐĩ аĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊҋ҅ ĐēŅ€Đ¸Ņ‚ĐĩŅ€Đ¸Đĩв. ĐŸŅ€ĐžĐąŅƒĐšŅ‚Đĩ вĐēĐģŅŽŅ‡Đ°Ņ‚ŅŒ Ņ‚ĐžĐģҌĐēĐž в Ņ‚ĐžĐŧ ҁĐģŅƒŅ‡Đ°Đĩ, ĐĩҁĐģи в ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊии ĐĩŅŅ‚ŅŒ ĐŋŅ€ĐžĐąĐģĐĩĐŧŅ‹ ҁ ОйĐŊĐ°Ņ€ŅƒĐļĐĩĐŊиĐĩĐŧ Đ˛ŅĐĩŅ… аĐģŅŒĐąĐžĐŧОв.", + "advanced_settings_enable_alternate_media_filter_title": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ аĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊĐžĐŗĐž ҁĐŋĐžŅĐžĐąĐ° ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ аĐģŅŒĐąĐžĐŧОв ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", "advanced_settings_log_level_title": "ĐŖŅ€ĐžĐ˛ĐĩĐŊҌ ĐģĐžĐŗĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ: {level}", "advanced_settings_prefer_remote_subtitle": "НĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° ĐžŅ‡ĐĩĐŊҌ ĐŧĐĩĐ´ĐģĐĩĐŊĐŊĐž ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°ŅŽŅ‚ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņ‹. АĐēŅ‚Đ¸Đ˛Đ¸Ņ€ŅƒĐšŅ‚Đĩ ŅŅ‚Ņƒ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐē҃, Ņ‡Ņ‚ĐžĐąŅ‹ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ Đ˛ŅĐĩĐŗĐ´Đ° ĐˇĐ°ĐŗŅ€ŅƒĐļаĐģĐ¸ŅŅŒ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", "advanced_settings_prefer_remote_title": "ĐŸŅ€ĐĩĐ´ĐŋĐžŅ‡Đ¸Ņ‚Đ°Ņ‚ŅŒ Ņ„ĐžŅ‚Đž ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌҁĐēиĐĩ ĐˇĐ°ĐŗĐžĐģОвĐēи ĐŋŅ€ĐžĐēŅĐ¸", "advanced_settings_readonly_mode_subtitle": "ВĐēĐģŅŽŅ‡Đ°ĐĩŅ‚ Ņ€ĐĩĐļиĐŧ, в ĐēĐžŅ‚ĐžŅ€ĐžĐŧ ĐŧĐžĐļĐŊĐž Ņ‚ĐžĐģҌĐēĐž ĐŋŅ€ĐžŅĐŧĐ°Ņ‚Ņ€Đ¸Đ˛Đ°Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ. Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸ Đ˛Ņ‹ĐąĐžŅ€Đ° ĐŊĐĩҁĐēĐžĐģҌĐēĐ¸Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛, ĐŋŅƒĐąĐģиĐēĐ°Ņ†Đ¸Đ¸, Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Đ¸Đ¸ и ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ ĐąŅƒĐ´ŅƒŅ‚ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹. ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ/ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅŅ‚ĐžŅ‚ Ņ€ĐĩĐļиĐŧ ĐŧĐžĐļĐŊĐž ŅƒĐ´ĐĩŅ€ĐļĐ¸Đ˛Đ°Ņ СĐŊĐ°Ņ‡ĐžĐē Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ° ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ ĐŊа ĐŗĐģавĐŊĐžĐŧ ŅĐēŅ€Đ°ĐŊĐĩ.", "advanced_settings_readonly_mode_title": "Đ ĐĩĐļиĐŧ ÂĢŅ‚ĐžĐģҌĐēĐž ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Âģ", + "advanced_settings_self_signed_ssl_subtitle": "ĐŸŅ€ĐžĐŋ҃ҁĐēĐ°Ņ‚ŅŒ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đē҃ SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°. ĐĸŅ€ĐĩĐąŅƒĐĩŅ‚ŅŅ Đ´ĐģŅ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊĐŊҋ҅ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ĐžĐ˛.", + "advanced_settings_self_signed_ssl_title": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊĐŊŅ‹Đĩ SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Ņ‹", "advanced_settings_sync_remote_deletions_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи ŅƒĐ´Đ°ĐģŅŅ‚ŅŒ иĐģи Đ˛ĐžŅŅŅ‚Đ°ĐŊавĐģĐ¸Đ˛Đ°Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊа ŅŅ‚ĐžĐŧ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ, ĐēĐžĐŗĐ´Đ° ŅŅ‚Đž Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ Đ˛Ņ‹ĐŋĐžĐģĐŊŅĐĩŅ‚ŅŅ ҇ĐĩŅ€ĐĩС вĐĩĐą-иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ", "advanced_settings_sync_remote_deletions_title": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "advanced_settings_tile_subtitle": "Đ Đ°ŅŅˆĐ¸Ņ€ĐĩĐŊĐŊŅ‹Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ {album}?", "album_delete_confirmation_description": "Đ•ŅĐģи аĐģŅŒĐąĐžĐŧ ĐąŅ‹Đģ ĐžĐąŅ‰Đ¸Đŧ, Đ´Ņ€ŅƒĐŗĐ¸Đĩ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģи йОĐģҌ҈Đĩ ĐŊĐĩ ҁĐŧĐžĐŗŅƒŅ‚ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ Đē ĐŊĐĩĐŧ҃ Đ´ĐžŅŅ‚ŅƒĐŋ.", "album_deleted": "АĐģŅŒĐąĐžĐŧ ŅƒĐ´Đ°ĐģŅ‘ĐŊ", + "album_info_card_backup_album_excluded": "ИСКЛЮЧЕН", + "album_info_card_backup_album_included": "ВКЛЮЧЕН", "album_info_updated": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Ой аĐģŅŒĐąĐžĐŧĐĩ ОйĐŊОвĐģĐĩĐŊа", + "album_leave": "ПоĐēиĐŊŅƒŅ‚ŅŒ аĐģŅŒĐąĐžĐŧ?", + "album_leave_confirmation": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐŋĐžĐēиĐŊŅƒŅ‚ŅŒ {album}?", "album_name": "НазваĐŊиĐĩ аĐģŅŒĐąĐžĐŧа", "album_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ аĐģŅŒĐąĐžĐŧа", "album_remove_user": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ?", "album_remove_user_confirmation": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ {user}?", "album_search_not_found": "НĐĩ ĐŊаКдĐĩĐŊĐž аĐģŅŒĐąĐžĐŧОв ĐŋĐž Đ˛Đ°ŅˆĐĩĐŧ҃ СаĐŋŅ€ĐžŅŅƒ", + "album_selected": "АĐģŅŒĐąĐžĐŧ Đ˛Ņ‹ĐąŅ€Đ°ĐŊ", "album_share_no_users": "НĐĩŅ‚ Đ´ĐžŅŅ‚ŅƒĐŋĐŊҋ҅ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš, ҁ ĐēĐžŅ‚ĐžŅ€Ņ‹Đŧи ĐŧĐžĐļĐŊĐž ĐŋОдĐĩĐģĐ¸Ņ‚ŅŒŅŅ аĐģŅŒĐąĐžĐŧĐžĐŧ.", "album_summary": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Ой аĐģŅŒĐąĐžĐŧĐĩ", "album_updated": "АĐģŅŒĐąĐžĐŧ ОйĐŊОвĐģŅ‘ĐŊ", "album_updated_setting_description": "ПоĐģŅƒŅ‡Đ°Ņ‚ŅŒ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиĐĩ ĐŋĐž ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊОК ĐŋĐžŅ‡Ņ‚Đĩ ĐŋŅ€Đ¸ дОйавĐģĐĩĐŊии ĐŊĐžĐ˛Ņ‹Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ в ĐžĐąŅ‰Đ¸Đš аĐģŅŒĐąĐžĐŧ", "album_upload_assets": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ҁ ĐēĐžĐŧĐŋŅŒŅŽŅ‚ĐĩŅ€Đ° и Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Đ¸Ņ… в аĐģŅŒĐąĐžĐŧ", + "album_user_left": "Đ’Ņ‹ ĐŋĐžĐēиĐŊ҃Đģи {album}", + "album_user_removed": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ {user} ŅƒĐ´Đ°ĐģĐĩĐŊ", + "album_viewer_appbar_delete_confirm": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ иС ŅĐ˛ĐžĐĩĐš ŅƒŅ‡ĐĩŅ‚ĐŊОК СаĐŋĐ¸ŅĐ¸?", "album_viewer_appbar_share_err_delete": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_err_leave": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŋĐžĐēиĐŊŅƒŅ‚ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_err_remove": "ВозĐŊиĐēĐģи ĐŋŅ€ĐžĐąĐģĐĩĐŧŅ‹ ҁ ŅƒĐ´Đ°ĐģĐĩĐŊиĐĩĐŧ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ иС аĐģŅŒĐąĐžĐŧа", + "album_viewer_appbar_share_err_title": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŋĐĩŅ€ĐĩиĐŧĐĩĐŊĐžĐ˛Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_leave": "ПоĐēиĐŊŅƒŅ‚ŅŒ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_to": "ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ ҁ", "album_viewer_page_share_add_users": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš", "album_with_link_access": "ПодĐĩĐģĐ¸Ņ‚ĐĩҁҌ ҁҁҋĐģĐēОК ĐŊа аĐģŅŒĐąĐžĐŧ, Ņ‡Ņ‚ĐžĐąŅ‹ Đ˛Đ°ŅˆĐ¸ Đ´Ņ€ŅƒĐˇŅŒŅ ĐŧĐžĐŗĐģи ĐĩĐŗĐž ĐŋĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ.", "albums": "АĐģŅŒĐąĐžĐŧŅ‹", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "ПĐĩŅ€Đ˛ĐžĐŊĐ°Ņ‡Đ°ĐģҌĐŊŅ‹Đš ĐŋĐžŅ€ŅĐ´ĐžĐē ŅĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đēи, ŅƒŅŅ‚Đ°ĐŊавĐģиваĐĩĐŧŅ‹Đš в ĐŊĐžĐ˛Ņ‹Ņ… аĐģŅŒĐąĐžĐŧĐ°Ņ….", "albums_feature_description": "КоĐģĐģĐĩĐēŅ†Đ¸Đ¸ Ņ„ĐžŅ‚Đž и видĐĩĐž, ĐēĐžŅ‚ĐžŅ€Ņ‹Đŧи ĐŧĐžĐļĐŊĐž Đ´ĐĩĐģĐ¸Ņ‚ŅŒŅŅ ҁ Đ´Ņ€ŅƒĐŗĐ¸Đŧи ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅĐŧи.", "albums_on_device_count": "АĐģŅŒĐąĐžĐŧŅ‹ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ ({count})", + "albums_selected": "{count, plural, one {Đ’Ņ‹ĐąŅ€Đ°ĐŊ # аĐģŅŒĐąĐžĐŧ} many {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # аĐģŅŒĐąĐžĐŧОв} other {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # аĐģŅŒĐąĐžĐŧа}}", "all": "Đ’ŅĐĩ", "all_albums": "Đ’ŅĐĩ аĐģŅŒĐąĐžĐŧŅ‹", "all_people": "Đ’ŅĐĩ ĐģŅŽĐ´Đ¸", "all_photos": "Đ’ŅĐĩ Ņ„ĐžŅ‚Đž", "all_videos": "Đ’ŅĐĩ видĐĩĐž", "allow_dark_mode": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ ҂ґĐŧĐŊŅ‹Đš Ņ€ĐĩĐļиĐŧ", + "allow_edits": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", "allow_public_user_to_download": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ ҁĐēĐ°Ņ‡Đ¸Đ˛Đ°ĐŊиĐĩ", "allow_public_user_to_upload": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ дОйавĐģĐĩĐŊиĐĩ Ņ„Đ°ĐšĐģОв", "allowed": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐž", @@ -549,12 +528,14 @@ "always_keep": "Đ’ŅĐĩĐŗĐ´Đ° ĐžŅŅ‚Đ°Đ˛ĐģŅŅ‚ŅŒ", "always_keep_photos_hint": "Đ¤ŅƒĐŊĐēŅ†Đ¸Ņ ĐžŅĐ˛ĐžĐąĐžĐļĐ´ĐĩĐŊĐ¸Ņ ĐŧĐĩŅŅ‚Đ° ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ Đ˛ŅĐĩ Ņ„ĐžŅ‚Đž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ.", "always_keep_videos_hint": "Đ¤ŅƒĐŊĐēŅ†Đ¸Ņ ĐžŅĐ˛ĐžĐąĐžĐļĐ´ĐĩĐŊĐ¸Ņ ĐŧĐĩŅŅ‚Đ° ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ Đ˛ŅĐĩ видĐĩĐž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ.", + "anti_clockwise": "ĐŸŅ€ĐžŅ‚Đ¸Đ˛ Ņ‡Đ°ŅĐžĐ˛ĐžĐš", "api_key": "API ĐēĐģŅŽŅ‡", "api_key_description": "Đ­Ņ‚Đž СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ ĐąŅƒĐ´ĐĩŅ‚ ĐŋĐžĐēаСаĐŊĐž Ņ‚ĐžĐģҌĐēĐž ОдиĐŊ Ņ€Đ°Đˇ. ПоĐļаĐģŅƒĐšŅŅ‚Đ°, ŅƒĐąĐĩĐ´Đ¸Ņ‚ĐĩҁҌ, Ņ‡Ņ‚Đž ҁĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°Đģи ĐĩĐŗĐž ĐŋĐĩŅ€ĐĩĐ´ СаĐēŅ€Ņ‹Ņ‚Đ¸ĐĩĐŧ ĐžĐēĐŊа.", "api_key_empty": "ИĐŧŅ API ĐēĐģŅŽŅ‡Đ° ĐŊĐĩ Đ´ĐžĐģĐļĐŊĐž ĐąŅ‹Ņ‚ŅŒ ĐŋŅƒŅŅ‚Ņ‹Đŧ", "api_keys": "КĐģŅŽŅ‡Đ¸ API", "app_architecture_variant": "ĐŅ€Ņ…Đ¸Ņ‚ĐĩĐēŅ‚ŅƒŅ€Đ° ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", "app_bar_signout_dialog_content": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ Đ˛Ņ‹ĐšŅ‚Đ¸?", + "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "Đ’Ņ‹ĐšŅ‚Đ¸", "app_download_links": "ĐĄŅŅ‹ĐģĐēи Đ´ĐģŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи ĐŧОйиĐģҌĐŊĐžĐŗĐž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", "app_settings": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", @@ -565,19 +546,27 @@ "archive": "ĐŅ€Ņ…Đ¸Đ˛", "archive_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ дОйавĐģĐĩĐŊŅ‹ в ĐŅ€Ņ…Đ¸Đ˛ ({count} ŅˆŅ‚.)", "archive_or_unarchive_photo": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ иĐģи Ņ€Đ°ĐˇĐ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Ņ„ĐžŅ‚Đž", + "archive_page_no_archived_assets": "В Đ°Ņ€Ņ…Đ¸Đ˛Đĩ ҁĐĩĐšŅ‡Đ°Ņ ĐŋŅƒŅŅ‚Đž", + "archive_page_title": "ĐŅ€Ņ…Đ¸Đ˛ ({count})", "archive_size": "РаСĐŧĐĩŅ€ Đ°Ņ€Ņ…Đ¸Đ˛Đ°", "archive_size_description": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа Ņ€Đ°ĐˇĐŧĐĩŅ€Đ° Đ°Ņ€Ņ…Đ¸Đ˛Đ° Đ´ĐģŅ ҁĐēĐ°Ņ‡Đ¸Đ˛Đ°ĐŊĐ¸Ņ (в GiB)", "archived": "ĐŅ€Ņ…Đ¸Đ˛", "archived_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐŋĐĩŅ€ĐĩĐŊĐĩҁґĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐŋĐĩŅ€ĐĩĐŊĐĩҁĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐŋĐĩŅ€ĐĩĐŊĐĩҁĐĩĐŊĐž}} в Đ°Ņ€Ņ…Đ¸Đ˛", "are_these_the_same_person": "Đ­Ņ‚Đž ОдиĐŊ и Ņ‚ĐžŅ‚ ĐļĐĩ ҇ĐĩĐģОвĐĩĐē?", "are_you_sure_to_do_this": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅŅ‚Đž ŅĐ´ĐĩĐģĐ°Ņ‚ŅŒ?", + "array_field_not_fully_supported": "ПоĐģŅ ĐŧĐ°ŅŅĐ¸Đ˛ĐžĐ˛ ҂ҀĐĩĐąŅƒŅŽŅ‚ Ņ€ŅƒŅ‡ĐŊĐžĐŗĐž Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ JSON", + "asset_action_delete_err_read_only": "НĐĩвОСĐŧĐžĐļĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐēŅ‚(Ņ‹) Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ҇҂ĐĩĐŊĐ¸Ņ, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "asset_action_share_err_offline": "НĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ ĐžŅ„Ņ„ĐģаКĐŊ-ĐžĐąŅŠĐĩĐēŅ‚(Ņ‹), ĐŋŅ€ĐžĐŋ҃ҁĐē", "asset_added_to_album": "ДобавĐģĐĩĐŊĐž в аĐģŅŒĐąĐžĐŧ", "asset_adding_to_album": "ДобавĐģĐĩĐŊиĐĩ в аĐģŅŒĐąĐžĐŧâ€Ļ", "asset_created": "ĐžĐąŅŠĐĩĐēŅ‚ ŅĐžĐˇĐ´Đ°ĐŊ", - "asset_day_count": "{date}: {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", "asset_description_updated": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ ОйĐŊОвĐģĐĩĐŊĐž", + "asset_filename_is_offline": "ĐžĐąŅŠĐĩĐēŅ‚ {filename} ĐŊĐ°Ņ…ĐžĐ´Đ¸Ņ‚ŅŅ в ĐžŅ„ĐģаКĐŊ-Ņ€ĐĩĐļиĐŧĐĩ", + "asset_has_unassigned_faces": "Đ•ŅŅ‚ŅŒ ĐŊĐĩ Ņ€Đ°ŅĐŋОСĐŊаĐŊĐŊŅ‹Đĩ ĐģĐ¸Ņ†Đ°", "asset_hashing": "ĐĨĐĩŅˆĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩâ€Ļ", "asset_list_group_by_sub_title": "Đ“Ņ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŋĐž", + "asset_list_layout_settings_dynamic_layout_title": "ДиĐŊаĐŧĐ¸Ņ‡ĐĩҁĐēĐžĐĩ Ņ€Đ°ŅĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", + "asset_list_layout_settings_group_automatically": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи", "asset_list_layout_settings_group_by": "Đ“Ņ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐž", "asset_list_layout_settings_group_by_month_day": "МĐĩŅŅŅ†Ņƒ и Đ´ĐŊŅŽ", "asset_list_layout_sub_title": "РаСĐŧĐĩŅ‚Đēа", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "ĐžĐąŅŠĐĩĐēŅ‚ ĐŊĐĩ ĐŊаКдĐĩĐŊ в iCloud. ВозĐŧĐžĐļĐŊĐž, Ņ„Đ°ĐšĐģ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐĩĐŊ иС-Са ĐŊĐĩĐēĐžŅ€Ņ€ĐĩĐēŅ‚ĐŊĐžĐŗĐž Ņ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ в iCloud.", "asset_offline": "ĐžĐąŅŠĐĩĐēŅ‚ ĐžŅ‚ĐēĐģŅŽŅ‡Ņ‘ĐŊ", "asset_offline_description": "Đ­Ņ‚ĐžŅ‚ вĐŊĐĩ҈ĐŊиК Ņ„Đ°ĐšĐģ ĐŊĐĩ ĐŊаКдĐĩĐŊ ĐŊа Đ´Đ¸ŅĐēĐĩ. ПоĐļаĐģŅƒĐšŅŅ‚Đ°, ŅĐ˛ŅĐļĐ¸Ņ‚ĐĩҁҌ ҁ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ĐžĐŧ Immich Đ´ĐģŅ ĐŋĐžĐģŅƒŅ‡ĐĩĐŊĐ¸Ņ ĐŋĐžĐŧĐžŅ‰Đ¸.", + "asset_restored_successfully": "ĐžĐąŅŠĐĩĐēŅ‚ ҃ҁĐŋĐĩ҈ĐŊĐž Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊ", "asset_skipped": "ĐŸŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "asset_skipped_in_trash": "В ĐēĐžŅ€ĐˇĐ¸ĐŊĐĩ", + "asset_trashed": "ĐžĐąŅŠĐĩĐēŅ‚ ŅƒĐ´Đ°ĐģŅ‘ĐŊ", "asset_troubleshoot": "ДаĐŊĐŊŅ‹Đĩ Đ´ĐģŅ Đ´Đ¸Đ°ĐŗĐŊĐžŅŅ‚Đ¸Đēи", "asset_uploaded": "Đ—Đ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž", "asset_uploading": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēаâ€Ļ", "asset_viewer_settings_subtitle": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "asset_viewer_settings_title": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "assets": "ĐžĐąŅŠĐĩĐē҂ҋ", + "assets_added_count": "{count, plural, one {ДобавĐģĐĩĐŊ # ĐžĐąŅŠĐĩĐēŅ‚} many {ДобавĐģĐĩĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {ДобавĐģĐĩĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", "assets_added_to_album_count": "В аĐģŅŒĐąĐžĐŧ {count, plural, one {дОйавĐģĐĩĐŊ # ĐžĐąŅŠĐĩĐēŅ‚} many {дОйавĐģĐĩĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {дОйавĐģĐĩĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", - "assets_added_to_album_partial_count": "{successCount} иС {totalCount, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚Đ°} other {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛}} дОйавĐģĐĩĐŊĐž в аĐģŅŒĐąĐžĐŧ", "assets_added_to_albums_count": "{assetTotal, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ дОйавĐģĐĩĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ дОйавĐģĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° дОйавĐģĐĩĐŊŅ‹}} в {albumTotal, plural, one {# аĐģŅŒĐąĐžĐŧ} many {# аĐģŅŒĐąĐžĐŧОв} other {# аĐģŅŒĐąĐžĐŧа}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ĐžĐąŅŠĐĩĐēŅ‚ ĐŊĐĩ ĐŧĐžĐļĐĩŅ‚ ĐąŅ‹Ņ‚ŅŒ дОйавĐģĐĩĐŊ} other {ĐžĐąŅŠĐĩĐē҂ҋ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ‚ ĐąŅ‹Ņ‚ŅŒ дОйавĐģĐĩĐŊŅ‹}} в аĐģŅŒĐąĐžĐŧ", "assets_cannot_be_added_to_albums": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}} ĐŊĐĩ ĐŧĐžĐŗŅƒŅ‚ ĐąŅ‹Ņ‚ŅŒ дОйавĐģĐĩĐŊŅ‹ ĐŊи в ОдиĐŊ аĐģŅŒĐąĐžĐŧ", "assets_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", + "assets_deleted_permanently": "ĐžĐąŅŠĐĩĐē҂ҋ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ({count} ŅˆŅ‚.)", + "assets_deleted_permanently_from_server": "ĐžĐąŅŠĐĩĐē҂ҋ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Immich ({count} ŅˆŅ‚.)", + "assets_downloaded_failed": "{count, plural, one {ĐĄĐēĐ°Ņ‡Đ°ĐŊ # Ņ„Đ°ĐšĐģ} many {ĐĄĐēĐ°Ņ‡Đ°ĐŊĐž # Ņ„Đ°ĐšĐģОв} other {ĐĄĐēĐ°Ņ‡Đ°ĐŊĐž # Ņ„Đ°ĐšĐģа}}, {error} - ŅĐąĐžĐš", + "assets_downloaded_successfully": "ĐŖŅĐŋĐĩ҈ĐŊĐž {count, plural, one {ҁĐēĐ°Ņ‡Đ°ĐŊ # Ņ„Đ°ĐšĐģ} many {ҁĐēĐ°Ņ‡Đ°ĐŊĐž # Ņ„Đ°ĐšĐģОв} other {ҁĐēĐ°Ņ‡Đ°ĐŊĐž # Ņ„Đ°ĐšĐģа}}", "assets_moved_to_trash_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ґĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹}} в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", "assets_permanently_deleted_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģŅ‘ĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹}}", "assets_removed_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ŅƒĐ´Đ°ĐģŅ‘ĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ŅƒĐ´Đ°ĐģĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ŅƒĐ´Đ°ĐģĐĩĐŊĐž}}", + "assets_removed_permanently_from_device": "ĐžĐąŅŠĐĩĐē҂ҋ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° ({count} ŅˆŅ‚.)", "assets_restore_confirmation": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ Đ˛ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Đ˛ŅĐĩ ĐžĐąŅŠĐĩĐē҂ҋ иС ĐēĐžŅ€ĐˇĐ¸ĐŊŅ‹? Đ­Ņ‚Đž Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ ĐŊĐĩĐģŅŒĐˇŅ ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ! ĐžĐąŅ€Đ°Ņ‚Đ¸Ņ‚Đĩ вĐŊиĐŧаĐŊиĐĩ, ĐžĐąŅŠĐĩĐē҂ҋ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ‚ Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊŅ‹ Ņ‚Đ°ĐēиĐŧ ҁĐŋĐžŅĐžĐąĐžĐŧ.", "assets_restored_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊŅ‹}}", + "assets_restored_successfully": "ĐžĐąŅŠĐĩĐē҂ҋ ҃ҁĐŋĐĩ҈ĐŊĐž Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊŅ‹ ({count} ŅˆŅ‚.)", "assets_trashed": "ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ ({count} ŅˆŅ‚.)", "assets_trashed_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ґĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹}} в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", + "assets_trashed_from_server": "ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ Immich ({count} ŅˆŅ‚.)", "assets_were_part_of_album_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}} ҃ĐļĐĩ в аĐģŅŒĐąĐžĐŧĐĩ", "assets_were_part_of_albums_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}} ҃ĐļĐĩ в аĐģŅŒĐąĐžĐŧĐ°Ņ…", "authorized_devices": "ĐĐ˛Ņ‚ĐžŅ€Đ¸ĐˇĐžĐ˛Đ°ĐŊĐŊŅ‹Đĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ĐĐ˛Ņ‚ĐžĐ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ", "back": "Назад", "back_close_deselect": "Назад, СаĐēŅ€Ņ‹Ņ‚ŅŒ иĐģи ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ˛Ņ‹ĐąĐžŅ€", + "background_backup_running_error": "Đ’Ņ‹ĐŋĐžĐģĐŊŅĐĩŅ‚ŅŅ Ņ„ĐžĐŊОвОĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ, СаĐŋ҃ҁĐē Đ˛Ņ€ŅƒŅ‡ĐŊŅƒŅŽ ĐŋĐžĐēа ĐŊĐĩвОСĐŧĐžĐļĐĩĐŊ", "background_location_permission": "Đ”ĐžŅŅ‚ŅƒĐŋ Đē ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸ŅŽ в Ņ„ĐžĐŊĐĩ", "background_location_permission_content": "Đ§Ņ‚ĐžĐąŅ‹ ŅŅ‡Đ¸Ņ‚Ņ‹Đ˛Đ°Ņ‚ŅŒ иĐŧŅ Wi-Fi ҁĐĩŅ‚Đ¸ в Ņ„ĐžĐŊĐĩ, ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸ŅŽ *Đ˛ŅĐĩĐŗĐ´Đ°* ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸Đŧ Đ´ĐžŅŅ‚ŅƒĐŋ Đē Ņ‚ĐžŅ‡ĐŊĐžĐŧ҃ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸ŅŽ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "background_options": "Đ’Ņ‹ĐŋĐžĐģĐŊĐĩĐŊиĐĩ Ņ„ĐžĐŊĐžĐ˛Ņ‹Ņ… ĐˇĐ°Đ´Đ°Ņ‡", "backup": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", + "backup_album_selection_page_albums_device": "АĐģŅŒĐąĐžĐŧŅ‹ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ ({count})", "backup_album_selection_page_albums_tap": "НаĐļĐŧĐ¸Ņ‚Đĩ, Ņ‡Ņ‚ĐžĐąŅ‹ вĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ, дваĐļĐ´Ņ‹, Ņ‡Ņ‚ĐžĐąŅ‹ Đ¸ŅĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ", "backup_album_selection_page_assets_scatter": "Đ’Đ°ŅˆĐ¸ Ņ„ĐžŅ‚Đž и видĐĩĐž ĐŧĐžĐŗŅƒŅ‚ ĐŊĐ°Ņ…ĐžĐ´Đ¸Ņ‚ŅŒŅŅ в Ņ€Đ°ĐˇĐŊҋ҅ аĐģŅŒĐąĐžĐŧĐ°Ņ…/ĐŋаĐŋĐēĐ°Ņ… ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ. Đ’Ņ‹ ĐŧĐžĐļĐĩŅ‚Đĩ Đ˛Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ, ĐēаĐēиĐĩ аĐģŅŒĐąĐžĐŧŅ‹ вĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ, а ĐēаĐēиĐĩ Đ¸ŅĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ иС Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ.", "backup_album_selection_page_select_albums": "Đ’Ņ‹ĐąĐžŅ€ аĐģŅŒĐąĐžĐŧОв", "backup_album_selection_page_selection_info": "Đ’Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ аĐģŅŒĐąĐžĐŧŅ‹", + "backup_album_selection_page_total_assets": "Đ’ŅĐĩĐŗĐž ҃ĐŊиĐēаĐģҌĐŊҋ҅ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "backup_albums_sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ аĐģŅŒĐąĐžĐŧОв", + "backup_all": "Đ’ŅĐĩ", + "backup_background_service_backup_failed_message": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ Đ˛Ņ‹ĐŋĐžĐģĐŊĐ¸Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ. ĐŸĐžĐ˛Ņ‚ĐžŅ€ĐŊĐ°Ņ ĐŋĐžĐŋҋ҂Đēаâ€Ļ", "backup_background_service_complete_notification": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ СавĐĩŅ€ŅˆĐĩĐŊĐž", + "backup_background_service_connection_failed_message": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŋОдĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒŅŅ Đē ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ. ĐŸĐžĐ˛Ņ‚ĐžŅ€ĐŊĐ°Ņ ĐŋĐžĐŋҋ҂Đēаâ€Ļ", + "backup_background_service_current_upload_notification": "Đ—Đ°ĐŗŅ€ŅƒĐļаĐĩŅ‚ŅŅ {filename}", "backup_background_service_default_notification": "ĐŸĐžĐ¸ŅĐē ĐŊĐžĐ˛Ņ‹Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛â€Ļ", + "backup_background_service_error_title": "ĐžŅˆĐ¸ĐąĐēа Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "backup_background_service_in_progress_notification": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛â€Ļ", + "backup_background_service_upload_failure_notification": "ĐžŅˆĐ¸ĐąĐēа ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи {filename}", "backup_controller_page_albums": "АĐģŅŒĐąĐžĐŧŅ‹", + "backup_controller_page_background_app_refresh_disabled_content": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ Ņ„ĐžĐŊОвОĐĩ ОйĐŊОвĐģĐĩĐŊиĐĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ в ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи > ĐžĐąŅ‰Đ¸Đĩ > ФОĐŊОвОĐĩ ОйĐŊОвĐģĐĩĐŊиĐĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиК, Ņ‡Ņ‚ĐžĐąŅ‹ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ Ņ„ĐžĐŊОвОĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ.", + "backup_controller_page_background_app_refresh_disabled_title": "ФОĐŊОвОĐĩ ОйĐŊОвĐģĐĩĐŊиĐĩ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_app_refresh_enable_button_text": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "backup_controller_page_background_battery_info_link": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐĩĐĩ", "backup_controller_page_background_battery_info_message": "ДĐģŅ ŅŅ‚Đ°ĐąĐ¸ĐģҌĐŊĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ в Ņ„ĐžĐŊОвОĐŧ Ņ€ĐĩĐļиĐŧĐĩ, ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ ĐģŅŽĐąŅ‹Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐžĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ ĐąĐ°Ņ‚Đ°Ņ€Đĩи, ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡Đ¸Đ˛Đ°ŅŽŅ‰Đ¸Đĩ Ņ„ĐžĐŊĐžĐ˛ŅƒŅŽ аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ŅŒ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ.\n\nĐŸĐžŅĐēĐžĐģҌĐē҃ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐˇĐ°Đ˛Đ¸ŅŅŅ‚ ĐžŅ‚ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°, ĐŊĐ°ĐšĐ´Đ¸Ņ‚Đĩ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧŅƒŅŽ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸ŅŽ Đ´ĐģŅ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģŅ Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°.", "backup_controller_page_background_battery_info_ok": "ОК", "backup_controller_page_background_battery_info_title": "ОĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Ņ ĐąĐ°Ņ‚Đ°Ņ€Đĩи", + "backup_controller_page_background_charging": "ĐĸĐžĐģҌĐēĐž вО Đ˛Ņ€ĐĩĐŧŅ ĐˇĐ°Ņ€ŅĐ´Đēи", + "backup_controller_page_background_configure_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŊĐ°ŅŅ‚Ņ€ĐžĐ¸Ņ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛ŅƒŅŽ ҁĐģ҃ĐļĐąŅƒ", "backup_controller_page_background_delay": "ЗадĐĩŅ€ĐļĐēа ĐŋĐĩŅ€ĐĩĐ´ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēОК ĐŊĐžĐ˛Ņ‹Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛: {duration}", + "backup_controller_page_background_description": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ Ņ„ĐžĐŊĐžĐ˛ŅƒŅŽ ҁĐģ҃ĐļĐąŅƒ Đ´ĐģŅ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ ĐģŅŽĐąŅ‹Ņ… ĐŊĐžĐ˛Ņ‹Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐąĐĩС ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐžŅŅ‚Đ¸ ĐžŅ‚ĐēŅ€Ņ‹Đ˛Đ°Ņ‚ŅŒ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ", + "backup_controller_page_background_is_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в Ņ„ĐžĐŊОвОĐŧ Ņ€ĐĩĐļиĐŧĐĩ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_is_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в Ņ„ĐžĐŊОвОĐŧ Ņ€ĐĩĐļиĐŧĐĩ вĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_turn_off": "Đ’Ņ‹ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛ŅƒŅŽ ҁĐģ҃ĐļĐąŅƒ", + "backup_controller_page_background_turn_on": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛ŅƒŅŽ ҁĐģ҃ĐļĐąŅƒ", + "backup_controller_page_background_wifi": "ĐĸĐžĐģҌĐēĐž ҇ĐĩŅ€ĐĩС Wi-Fi", "backup_controller_page_backup": "Đ—Đ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž", "backup_controller_page_backup_selected": "Đ’Ņ‹ĐąŅ€Đ°ĐŊŅ‹: ", "backup_controller_page_backup_sub": "Đ—Đ°ĐŗŅ€ŅƒĐļĐĩĐŊĐŊŅ‹Đĩ Ņ„ĐžŅ‚Đž и видĐĩĐž", + "backup_controller_page_created": "ХОСдаĐŊĐž: {date}", + "backup_controller_page_desc_backup": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ Ņ€ĐĩĐļиĐŧĐĩ, Ņ‡Ņ‚ĐžĐąŅ‹ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ ĐŊĐžĐ˛Ņ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋŅ€Đ¸ ĐžŅ‚ĐēŅ€Ņ‹Ņ‚Đ¸Đ¸ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ.", "backup_controller_page_excluded": "Đ˜ŅĐēĐģŅŽŅ‡ĐĩĐŊŅ‹: ", + "backup_controller_page_failed": "НĐĩŅƒĐ´Đ°Ņ‡ĐŊҋ҅ ({count})", + "backup_controller_page_filename": "ИĐŧŅ Ņ„Đ°ĐšĐģа: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŧ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊии", "backup_controller_page_none_selected": "НĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐž", "backup_controller_page_remainder": "ĐžŅŅ‚Đ°ĐģĐžŅŅŒ", "backup_controller_page_remainder_sub": "Đ¤ĐžŅ‚Đž и видĐĩĐž Đ´ĐģŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", "backup_controller_page_server_storage": "ĐĨŅ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ", + "backup_controller_page_start_backup": "ĐĐ°Ņ‡Đ°Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", + "backup_controller_page_status_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ Ņ€ĐĩĐļиĐŧĐĩ Đ˛Ņ‹ĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "backup_controller_page_status_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēĐžĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ Ņ€ĐĩĐļиĐŧĐĩ вĐēĐģŅŽŅ‡ĐĩĐŊĐž", "backup_controller_page_storage_format": "{used} иС {total}", "backup_controller_page_to_backup": "АĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "backup_controller_page_total_sub": "Đ’ŅĐĩ ҃ĐŊиĐēаĐģҌĐŊŅ‹Đĩ Ņ„ĐžŅ‚Đž и видĐĩĐž иС Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊҋ҅ аĐģŅŒĐąĐžĐŧОв", + "backup_controller_page_turn_off": "Đ’Ņ‹ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ", + "backup_controller_page_turn_on": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ", + "backup_controller_page_uploading_file_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž ĐˇĐ°ĐŗŅ€ŅƒĐļаĐĩĐŧĐžĐŧ Ņ„Đ°ĐšĐģĐĩ", + "backup_err_only_album": "НĐĩвОСĐŧĐžĐļĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐĩдиĐŊŅŅ‚Đ˛ĐĩĐŊĐŊŅ‹Đš аĐģŅŒĐąĐžĐŧ", "backup_error_sync_failed": "ХйОК ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸. НĐĩвОСĐŧĐžĐļĐŊĐž Đ˛Ņ‹ĐŋĐžĐģĐŊĐ¸Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ.", "backup_info_card_assets": "ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", + "backup_manual_cancelled": "ĐžŅ‚ĐŧĐĩĐŊĐĩĐŊĐž", + "backup_manual_in_progress": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа в ĐŋŅ€ĐžŅ†ĐĩҁҁĐĩ. ПоĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ ĐŋОСĐļĐĩ", + "backup_manual_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž", + "backup_manual_title": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", "backup_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", + "backup_options_page_title": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", + "backup_setting_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŗĐž и Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "backup_settings_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", + "backup_upload_details_page_more_details": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐ°Ņ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", "backward": "Назад", - "battery_optimization_backup_reliability": "ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ ĐžĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ ŅĐŊĐĩŅ€ĐŗĐžĐŋĐžŅ‚Ņ€ĐĩĐąĐģĐĩĐŊĐ¸Ņ ĐŧĐžĐļĐĩŅ‚ ĐŋĐžĐ˛Ņ‹ŅĐ¸Ņ‚ŅŒ ĐŊадĐĩĐļĐŊĐžŅŅ‚ŅŒ Ņ„ĐžĐŊОвОК ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", "biometric_auth_enabled": "БиоĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐĩҁĐēĐ°Ņ Đ°ŅƒŅ‚ĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Ņ вĐēĐģŅŽŅ‡ĐĩĐŊа", "biometric_locked_out": "ВаĐŧ СаĐēҀҋ҂ Đ´ĐžŅŅ‚ŅƒĐŋ Đē йиОĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐĩҁĐēОК Đ°ŅƒŅ‚ĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Đ¸", "biometric_no_options": "БиоĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐĩҁĐēĐ°Ņ Đ°ŅƒŅ‚ĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Ņ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊа", "biometric_not_available": "БиоĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐĩҁĐēĐ°Ņ Đ°ŅƒŅ‚ĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Ņ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊа ĐŊа ŅŅ‚ĐžĐŧ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", + "birthdate_saved": "Đ”Đ°Ņ‚Đ° Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊа", "birthdate_set_description": "Đ”Đ°Ņ‚Đ° Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Đ´ĐģŅ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐ¸Ņ Đ˛ĐžĐˇŅ€Đ°ŅŅ‚Đ° ҇ĐĩĐģОвĐĩĐēа ĐŊа ĐŧĐžĐŧĐĩĐŊŅ‚ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸.", "blurred_background": "РаСĐŧŅ‹Ņ‚Ņ‹Đš Ņ„ĐžĐŊ", - "browse_templates": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ ŅˆĐ°ĐąĐģĐžĐŊŅ‹", "bugs_and_feature_requests": "ĐžŅˆĐ¸ĐąĐēи и СаĐŋŅ€ĐžŅŅ‹", "build": "ĐĄĐąĐžŅ€Đēа", "build_image": "ВĐĩŅ€ŅĐ¸Ņ ŅĐąĐžŅ€Đēи", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ {count, plural, one {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸ĐšŅŅ ĐžĐąŅŠĐĩĐēŅ‚} many {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸Ņ…ŅŅ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸Ņ…ŅŅ ĐžĐąŅŠĐĩĐēŅ‚Đ°}}? Đ­Ņ‚Đž ŅĐžŅ…Ņ€Đ°ĐŊĐ¸Ņ‚ Đ˛ŅĐĩ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹.", "bulk_trash_duplicates_confirmation": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐŋĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ {count, plural, one {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸ĐšŅŅ ĐžĐąŅŠĐĩĐēŅ‚} many {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸Ņ…ŅŅ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# Đ´ŅƒĐąĐģĐ¸Ņ€ŅƒŅŽŅ‰Đ¸Ņ…ŅŅ ĐžĐąŅŠĐĩĐēŅ‚Đ°}}? Đ‘ŅƒĐ´ĐĩŅ‚ ŅĐžŅ…Ņ€Đ°ĐŊŅ‘ĐŊ ŅĐ°ĐŧŅ‹Đš йОĐģŅŒŅˆĐžĐš Ņ„Đ°ĐšĐģ в ĐēаĐļдОК ĐŗŅ€ŅƒĐŋĐŋĐĩ, а ĐĩĐŗĐž Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃.", "buy": "ĐŸŅ€Đ¸ĐžĐąŅ€ĐĩŅ‚ĐĩĐŊиĐĩ ĐģĐ¸Ņ†ĐĩĐŊСии Immich", + "cache_settings_clear_cache_button": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐēŅŅˆ", + "cache_settings_clear_cache_button_title": "ĐžŅ‡Đ¸Ņ‰Đ°ĐĩŅ‚ ĐēŅŅˆ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ. Đ­Ņ‚Đž ĐŊĐĩĐŗĐ°Ņ‚Đ¸Đ˛ĐŊĐž ĐŋОвĐģĐ¸ŅĐĩŅ‚ ĐŊа ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģҌĐŊĐžŅŅ‚ŅŒ, ĐŋĐžĐēа ĐēŅŅˆ ĐŊĐĩ ĐąŅƒĐ´ĐĩŅ‚ ŅĐžĐˇĐ´Đ°ĐŊ СаĐŊОвО.", + "cache_settings_duplicated_assets_clear_button": "ОЧИСĐĸИĐĸĐŦ", + "cache_settings_duplicated_assets_subtitle": "Đ¤ĐžŅ‚Đž и видĐĩĐž, ĐŋŅ€ĐžĐŋ҃ҁĐēаĐĩĐŧŅ‹Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩĐŧ", + "cache_settings_duplicated_assets_title": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹ ({count})", + "cache_settings_statistics_album": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņ‹ йийĐģĐ¸ĐžŅ‚ĐĩĐēи", + "cache_settings_statistics_full": "ПоĐģĐŊŅ‹Đĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", + "cache_settings_statistics_shared": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņ‹ ĐžĐąŅ‰Đ¸Ņ… аĐģŅŒĐąĐžĐŧОв", + "cache_settings_statistics_thumbnail": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņ‹", + "cache_settings_statistics_title": "РаСĐŧĐĩŅ€ ĐēŅŅˆĐ°", + "cache_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐēŅŅˆĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩĐŧ ĐŧОйиĐģҌĐŊĐžĐŗĐž ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", + "cache_settings_tile_subtitle": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐģĐžĐēаĐģҌĐŊŅ‹Đŧ Ņ…Ņ€Đ°ĐŊиĐģĐ¸Ņ‰ĐĩĐŧ", + "cache_settings_tile_title": "ЛоĐēаĐģҌĐŊĐžĐĩ Ņ…Ņ€Đ°ĐŊиĐģĐ¸Ņ‰Đĩ", + "cache_settings_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐēŅŅˆĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "camera": "КаĐŧĐĩŅ€Đ°", "camera_brand": "ĐŸŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģҌ", "camera_model": "МодĐĩĐģҌ", @@ -678,10 +729,10 @@ "cannot_update_the_description": "НĐĩвОСĐŧĐžĐļĐŊĐž ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", "cast": "ĐĸŅ€Đ°ĐŊҁĐģŅŅ†Đ¸Ņ", "cast_description": "Đ’Ņ‹ĐąĐžŅ€ Đ´ĐžŅŅ‚ŅƒĐŋĐŊҋ҅ ҁĐŋĐžŅĐžĐąĐžĐ˛ Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Đ¸Đ¸", - "change": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ", "change_date": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ", "change_description": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", "change_display_order": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐžŅ€ŅĐ´ĐžĐē ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", + "change_expiration_time": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ŅŅ€ĐžĐē Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", "change_location": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "change_name": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ иĐŧŅ", "change_name_successfully": "ИĐŧŅ ҃ҁĐŋĐĩ҈ĐŊĐž иСĐŧĐĩĐŊĐĩĐŊĐž", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "ĐŸĐ°Ņ€ĐžĐģи ĐŊĐĩ ŅĐžĐ˛ĐŋĐ°Đ´Đ°ŅŽŅ‚", "change_password_form_reenter_new_password": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ĐŊĐž ввĐĩĐ´Đ¸Ņ‚Đĩ ĐŊĐžĐ˛Ņ‹Đš ĐŋĐ°Ņ€ĐžĐģҌ", "change_pin_code": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ PIN-ĐēОд", + "change_trigger": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Ņ‚Ņ€Đ¸ĐŗĐŗĐĩŅ€", + "change_trigger_prompt": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ŅŅ‚Đž ŅĐžĐąŅ‹Ņ‚Đ¸Đĩ? ИСĐŧĐĩĐŊĐĩĐŊиĐĩ ŅĐžĐąŅ‹Ņ‚Đ¸Ņ ĐŋŅ€Đ¸Đ˛ĐĩĐ´Ņ‘Ņ‚ Đē ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸ŅŽ ҃ĐļĐĩ ŅĐžĐˇĐ´Đ°ĐŊĐŊҋ҅ ĐžŅ‚ĐąĐžŅ€ĐžĐ˛ и Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đš.", "change_your_password": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ŅĐ˛ĐžĐš ĐŋĐ°Ņ€ĐžĐģҌ", "changed_visibility_successfully": "ВидиĐŧĐžŅŅ‚ŅŒ ҃ҁĐŋĐĩ҈ĐŊĐž иСĐŧĐĩĐŊĐĩĐŊа", "charging": "ĐŸŅ€Đ¸ ĐˇĐ°Ņ€ŅĐ´ĐēĐĩ", "charging_requirement_mobile_backup": "ЗаĐŋ҃ҁĐēĐ°Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐĩ ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ Ņ‚ĐžĐģҌĐēĐž вО Đ˛Ņ€ĐĩĐŧŅ ĐˇĐ°Ņ€ŅĐ´Đēи", + "check_corrupt_asset_backup": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ĐŋĐžĐ˛Ņ€ĐĩĐļĐ´ĐĩĐŊĐŊҋ҅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊҋ҅ ĐēĐžĐŋиК", + "check_corrupt_asset_backup_button": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚ŅŒ", + "check_corrupt_asset_backup_description": "ЗаĐŋ҃ҁĐēĐ°ĐšŅ‚Đĩ ĐŋŅ€ĐžĐ˛ĐĩŅ€Đē҃ Ņ‚ĐžĐģҌĐēĐž ҇ĐĩŅ€ĐĩС Wi-Fi и ĐŋĐžŅĐģĐĩ ŅĐžĐˇĐ´Đ°ĐŊĐ¸Ņ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊОК ĐēĐžĐŋии Đ˛ŅĐĩŅ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛. ОĐŋĐĩŅ€Đ°Ņ†Đ¸Ņ ĐŧĐžĐļĐĩŅ‚ СаĐŊŅŅ‚ŅŒ ĐŊĐĩҁĐēĐžĐģҌĐēĐž ĐŧиĐŊŅƒŅ‚.", "check_logs": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚ŅŒ ĐļŅƒŅ€ĐŊаĐģŅ‹", "checksum": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ°Ņ ҁ҃ĐŧĐŧа", - "choose": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ", "choose_matching_people_to_merge": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐŋĐžĐ´Ņ…ĐžĐ´ŅŅ‰Đ¸Ņ… ĐģŅŽĐ´ĐĩĐš Đ´ĐģŅ ҁĐģĐ¸ŅĐŊĐ¸Ņ", "city": "Đ“ĐžŅ€ĐžĐ´", "cleanup_confirm_description": "ОбĐŊĐ°Ņ€ŅƒĐļĐĩĐŊŅ‹ ĐžĐąŅŠĐĩĐē҂ҋ ({count} ŅˆŅ‚.), ŅĐžĐˇĐ´Đ°ĐŊĐŊŅ‹Đĩ Đ´Đž {date} и ҃ĐļĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊĐŊŅ‹Đĩ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€. ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Đ¸Ņ… ĐēĐžĐŋии ҁ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°?", @@ -719,10 +774,11 @@ "clear": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ", "clear_all": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ Đ˛ŅŅ‘", "clear_all_recent_searches": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ Đ˛ŅĐĩ ĐŊĐĩдавĐŊиĐĩ Ņ€ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚Ņ‹ ĐŋĐžĐ¸ŅĐēа", - "clear_failed_count": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ({count})", "clear_file_cache": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ Ņ„Đ°ĐšĐģĐžĐ˛Ņ‹Đš ĐēŅŅˆ", "clear_message": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ŅĐžĐžĐąŅ‰ĐĩĐŊиĐĩ", "clear_value": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ СĐŊĐ°Ņ‡ĐĩĐŊиĐĩ", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "ВвĐĩĐ´Đ¸Ņ‚Đĩ ĐŋĐ°Ņ€ĐžĐģҌ", "client_cert_import": "ИĐŧĐŋĐžŅ€Ņ‚", "client_cert_import_success_msg": "КĐģиĐĩĐŊ҂ҁĐēиК ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ иĐŧĐŋĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊ", "client_cert_invalid_msg": "НĐĩвĐĩŅ€ĐŊŅ‹Đš Ņ„Đ°ĐšĐģ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° иĐģи ĐŊĐĩвĐĩŅ€ĐŊŅ‹Đš ĐŋĐ°Ņ€ĐžĐģҌ", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "КĐģиĐĩĐŊ҂ҁĐēиК ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ ŅƒĐ´Đ°ĐģĐĩĐŊ", "client_cert_subtitle": "ПоддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ Ņ‚ĐžĐģҌĐēĐž Ņ„ĐžŅ€ĐŧĐ°Ņ‚ PKCS12 (.p12, .pfx). ИĐŧĐŋĐžŅ€Ņ‚/ŅƒĐ´Đ°ĐģĐĩĐŊиĐĩ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐž Ņ‚ĐžĐģҌĐēĐž ĐŋĐĩŅ€ĐĩĐ´ Đ˛Ņ…ĐžĐ´ĐžĐŧ в ŅĐ¸ŅŅ‚ĐĩĐŧ҃.", "client_cert_title": "[ЭКСПЕРИМЕНĐĸАЛĐŦНО] КĐģиĐĩĐŊ҂ҁĐēиК SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚", + "clockwise": "По Ņ‡Đ°ŅĐžĐ˛ĐžĐš", "close": "ЗаĐēŅ€Ņ‹Ņ‚ŅŒ", "collapse": "ХвĐĩŅ€ĐŊŅƒŅ‚ŅŒ", "collapse_all": "ХвĐĩŅ€ĐŊŅƒŅ‚ŅŒ Đ˛ŅŅ‘", "color": "ĐĻвĐĩŅ‚", + "color_theme": "ĐĻвĐĩŅ‚ĐžĐ˛Đ°Ņ Ņ‚ĐĩĐŧа", "command": "КоĐŧаĐŊда", "command_palette_prompt": "Đ‘Ņ‹ŅŅ‚Ņ€Ņ‹Đš ĐŋĐžĐ¸ŅĐē ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†, Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đš иĐģи ĐēĐžĐŧаĐŊĐ´", "command_palette_to_close": "СаĐēŅ€Ņ‹Ņ‚ŅŒ", @@ -747,7 +805,6 @@ "comments_are_disabled": "КоĐŧĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸Đ¸ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊŅ‹", "common_create_new_album": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŊĐžĐ˛Ņ‹Đš аĐģŅŒĐąĐžĐŧ", "completed": "ЗавĐĩŅ€ŅˆĐĩĐŊĐž", - "configuration": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ", "confirm": "ĐŸĐžĐ´Ņ‚Đ˛ĐĩŅ€Đ´Đ¸Ņ‚ŅŒ", "confirm_admin_password": "ĐŸĐžĐ´Ņ‚Đ˛ĐĩŅ€ĐļĐ´ĐĩĐŊиĐĩ ĐŋĐ°Ņ€ĐžĐģŅ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "confirm_delete_face": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐģĐ¸Ņ†Đž ҇ĐĩĐģОвĐĩĐēа {name} иС ŅŅ‚ĐžĐŗĐž ĐžĐąŅŠĐĩĐēŅ‚Đ°?", @@ -762,16 +819,19 @@ "contain": "ВĐŋĐ¸ŅĐ°Ņ‚ŅŒ", "context": "КоĐŊŅ‚ĐĩĐēҁ҂", "continue": "ĐŸŅ€ĐžĐ´ĐžĐģĐļĐ¸Ņ‚ŅŒ", - "control_bottom_app_bar_add_tags": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗĐ¸", + "control_bottom_app_bar_create_new_album": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", + "control_bottom_app_bar_delete_from_immich": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ иС Immich", "control_bottom_app_bar_delete_from_local": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ҁ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "control_bottom_app_bar_edit_location": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚Đž", "control_bottom_app_bar_edit_time": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ", + "control_bottom_app_bar_share_link": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃", + "control_bottom_app_bar_share_to": "ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ ҁ", "control_bottom_app_bar_trash_from_immich": "В ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", "copied_image_to_clipboard": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ҁĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐž в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧĐĩĐŊа.", "copied_to_clipboard": "ĐĄĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐž в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧĐĩĐŊа!", "copy_error": "ĐĄĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžŅˆĐ¸ĐąĐē҃", + "copy_file_path": "КоĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŋŅƒŅ‚ŅŒ Đē Ņ„Đ°ĐšĐģ҃", "copy_image": "КоĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", - "copy_json": "КоĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ JSON", "copy_link": "КоĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃", "copy_link_to_clipboard": "ĐĄĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃ в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧĐĩĐŊа", "copy_password": "ĐĄĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", @@ -783,12 +843,13 @@ "create_album": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", "create_album_page_untitled": "БĐĩС ĐŊаСваĐŊĐ¸Ņ", "create_api_key": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ API ĐēĐģŅŽŅ‡", - "create_first_workflow": "НаĐļĐŧĐ¸Ņ‚Đĩ Đ´ĐģŅ ŅĐžĐˇĐ´Đ°ĐŊĐ¸Ņ ĐŋĐĩŅ€Đ˛ĐžĐŗĐž ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ", + "create_first_workflow": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŋĐĩŅ€Đ˛Ņ‹Đš Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "create_library": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ йийĐģĐ¸ĐžŅ‚ĐĩĐē҃", "create_link": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃", "create_link_to_share": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃ ĐžĐąŅ‰ĐĩĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа", "create_link_to_share_description": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ Đ˛ŅĐĩĐŧ, ҃ ĐēĐžĐŗĐž ĐĩŅŅ‚ŅŒ ҁҁҋĐģĐēа, ĐŋŅ€ĐžŅĐŧĐ°Ņ‚Ņ€Đ¸Đ˛Đ°Ņ‚ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸", "create_new": "СОЗДАĐĸĐŦ НОВĐĢЙ", + "create_new_face": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŊОвОĐĩ ĐģĐ¸Ņ†Đž", "create_new_person": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŊĐžĐ˛ĐžĐŗĐž ҇ĐĩĐģОвĐĩĐēа", "create_new_person_hint": "НазĐŊĐ°Ņ‡Đ¸Ņ‚ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊа ĐŊĐžĐ˛ĐžĐŗĐž ҇ĐĩĐģОвĐĩĐēа", "create_new_user": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŊĐžĐ˛ĐžĐŗĐž ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", @@ -800,38 +861,42 @@ "create_tag": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ Ņ‚ĐĩĐŗ", "create_tag_description": "ĐĄĐžĐˇĐ´Đ°ĐšŅ‚Đĩ ĐŊĐžĐ˛Ņ‹Đš Ņ‚ĐĩĐŗ. ДĐģŅ вĐģĐžĐļĐĩĐŊĐŊҋ҅ Ņ‚ĐĩĐŗĐžĐ˛ ввĐĩĐ´Đ¸Ņ‚Đĩ ĐŋĐžĐģĐŊŅ‹Đš ĐŋŅƒŅ‚ŅŒ Đē Ņ‚ĐĩĐŗŅƒ, вĐēĐģŅŽŅ‡Đ°Ņ ҁĐģŅŅˆĐ¸.", "create_user": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", - "create_workflow": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš", + "create_workflow": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "created": "ХОСдаĐŊ", "created_at": "ХОСдаĐŊ", "creating_linked_albums": "ХОСдаĐŊиĐĩ ŅĐ˛ŅĐˇĐ°ĐŊĐŊҋ҅ аĐģŅŒĐąĐžĐŧОв...", "crop": "ĐžĐąŅ€ĐĩĐˇĐ°Ņ‚ŅŒ", + "crop_aspect_ratio_fixed": "ФиĐēŅĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊŅ‹Đš", "crop_aspect_ratio_free": "ХвОйОдĐŊĐž", "crop_aspect_ratio_original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ", "crop_aspect_ratio_square": "ĐšĐ˛Đ°Đ´Ņ€Đ°Ņ‚", + "curated_object_page_title": "ĐŸŅ€ĐĩĐ´ĐŧĐĩ҂ҋ", "current_device": "ĐĸĐĩĐēŅƒŅ‰ĐĩĐĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž", "current_pin_code": "ĐĸĐĩĐēŅƒŅ‰Đ¸Đš PIN-ĐēОд", "current_server_address": "ĐĸĐĩĐēŅƒŅ‰Đ¸Đš Đ°Đ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "custom_date": "ĐŸŅ€ĐžĐ¸ĐˇĐ˛ĐžĐģҌĐŊĐ°Ņ Đ´Đ°Ņ‚Đ°", "custom_locale": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌҁĐēиĐĩ ŅĐˇŅ‹ĐēĐžĐ˛Ņ‹Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ Đ´Đ°Ņ‚, Đ˛Ņ€ĐĩĐŧĐĩĐŊи и Ņ‡Đ¸ŅĐĩĐģ ĐŊа ĐžŅĐŊОваĐŊии Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊĐžĐŗĐž ŅĐˇŅ‹Đēа и Ņ€ĐĩĐŗĐ¸ĐžĐŊа", + "custom_url": "ХвОК URL", "cutoff_date_description": "ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ Ņ„ĐžŅ‚Đž Са ĐŋĐžŅĐģĐĩĐ´ĐŊиĐĩâ€Ļ", "cutoff_day": "{count, plural, one {Đ´ĐĩĐŊҌ} many {Đ´ĐŊĐĩĐš} other {Đ´ĐŊŅ}}", "cutoff_year": "{count, plural, one {ĐŗĐžĐ´} many {ĐģĐĩŅ‚} other {ĐŗĐžĐ´Đ°}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "ĐĸŅ‘ĐŧĐŊĐ°Ņ", "dark_theme": "ПĐĩŅ€ĐĩĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒŅŅ ĐŊа ҂ґĐŧĐŊŅƒŅŽ Ņ‚ĐĩĐŧ҃", "date": "Đ”Đ°Ņ‚Đ°", "date_after": "Đ”Đ°Ņ‚Đ° ĐŋĐžŅĐģĐĩ", "date_and_time": "Đ”Đ°Ņ‚Đ° и Đ˛Ņ€ĐĩĐŧŅ", "date_before": "Đ”Đ°Ņ‚Đ° Đ´Đž", - "date_of_birth": "Đ”Đ°Ņ‚Đ° Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Đ”Đ°Ņ‚Đ° Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊа", "date_range": "ДиаĐŋаСОĐŊ Đ´Đ°Ņ‚", - "date_time_original": "Đ”Đ°Ņ‚Đ°/Đ˛Ņ€ĐĩĐŧŅ", "day": "ДĐĩĐŊҌ", "days": "ДĐŊи", "deduplicate_all": "ĐŖĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹", - "default_quality_subtitle": "Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ Ņ„ŅƒĐŊĐēŅ†Đ¸ĐĩĐš ÂĢПодĐĩĐģĐ¸Ņ‚ŅŒŅŅÂģ. ĐŖĐ´ĐĩŅ€ĐļĐ¸Đ˛Đ°ĐšŅ‚Đĩ ĐēĐŊĐžĐŋĐē҃ ÂĢПодĐĩĐģĐ¸Ņ‚ŅŒŅŅÂģ Đ´ĐģŅ Đ˛Ņ‹ĐąĐžŅ€Đ° ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ° ĐŋĐĩŅ€ĐĩĐ´ ĐžŅ‚ĐŋŅ€Đ°Đ˛ĐēОК.", - "default_share_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€Đ¸ ĐžŅ‚ĐŋŅ€Đ°Đ˛ĐēĐĩ", + "default_locale": "ЛоĐēаĐģҌ ĐŋĐž ҃ĐŧĐžĐģŅ‡Đ°ĐŊĐ¸ŅŽ", + "default_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ Đ´Đ°Ņ‚ и Ņ‡Đ¸ŅĐĩĐģ в ŅĐžĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ¸Đ¸ ҁ ŅĐˇŅ‹ĐēĐžĐ˛Ņ‹Đŧи ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧи Đ˛Đ°ŅˆĐĩĐŗĐž ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đ°", "delete": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ", "delete_action_confirmation_message": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅŅ‚ĐžŅ‚ ĐžĐąŅŠĐĩĐēŅ‚? Đ­Ņ‚Đž Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ ĐŋĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ ĐžĐąŅŠĐĩĐēŅ‚ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° и ĐŋĐžĐŋŅ€ĐžĐąŅƒĐĩŅ‚ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐĩĐŗĐž ĐģĐžĐēаĐģҌĐŊĐž.", "delete_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ({count} ŅˆŅ‚.)", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Đ­Ņ‚Đ¸ ŅĐģĐĩĐŧĐĩĐŊ҂ҋ ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, а Ņ‚Đ°ĐēĐļĐĩ ҁ Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "delete_dialog_alert_local": "Đ­Ņ‚Đ¸ ĐžĐąŅŠĐĩĐē҂ҋ ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°, ĐŊĐž ĐŋĐž-ĐŋŅ€ĐĩĐļĐŊĐĩĐŧ҃ ĐąŅƒĐ´ŅƒŅ‚ Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ Immich", "delete_dialog_alert_local_non_backed_up": "НĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊĐĩ ĐąŅ‹Đģи ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊŅ‹ в Immich и ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", + "delete_dialog_alert_remote": "Đ­Ņ‚Đ¸ ĐžĐąŅŠĐĩĐē҂ҋ ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Immich", + "delete_dialog_ok_force": "Đ’ŅĐĩ Ņ€Đ°Đ˛ĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ", "delete_dialog_title": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŊĐ°Đ˛ŅĐĩĐŗĐ´Đ°", "delete_duplicates_confirmation": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅŅ‚Đ¸ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹?", "delete_face": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐģĐ¸Ņ†Đž", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Đ’Ņ‹ ŅƒĐ˛ĐĩŅ€ĐĩĐŊŅ‹, Ņ‡Ņ‚Đž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗ {tagName}?", "delete_user": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", "deleted_shared_link": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊĐ°Ņ ҁҁҋĐģĐēа ŅƒĐ´Đ°ĐģĐĩĐŊа", + "deletes_missing_assets": "ĐŖĐ´Đ°ĐģŅĐĩŅ‚ ĐžĐąŅŠĐĩĐē҂ҋ, ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ ĐŊа Đ´Đ¸ŅĐēĐĩ", "description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "description_input_hint_text": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ...", + "description_input_submit_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ, ĐŋŅ€ĐžĐ˛ĐĩŅ€ŅŒŅ‚Đĩ ĐģĐžĐŗĐ¸, Ņ‡Ņ‚ĐžĐąŅ‹ ŅƒĐˇĐŊĐ°Ņ‚ŅŒ ĐŋŅ€Đ¸Ņ‡Đ¸ĐŊ҃", "deselect_all": "ĐĄĐŊŅŅ‚ŅŒ Đ˛Ņ‹Đ´ĐĩĐģĐĩĐŊиĐĩ", "details": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸", "direction": "НаĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ", "disable": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ", "disabled": "ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐž", + "disallow_edits": "ЗаĐŋŅ€ĐĩŅ‚Đ¸Ņ‚ŅŒ Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", "discord": "Discord", "discover": "ОбĐŊĐ°Ņ€ŅƒĐļĐ¸Ņ‚ŅŒ", "discovered_devices": "ОбĐŊĐ°Ņ€ŅƒĐļĐĩĐŊĐŊŅ‹Đĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "dismiss_all_errors": "ĐĄĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ ĐžŅˆĐ¸ĐąĐēи", + "dismiss_error": "ĐĄĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ ĐžŅˆĐ¸ĐąĐē҃", "display_options": "ДоĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊĐž", "display_order": "ĐŸĐžŅ€ŅĐ´ĐžĐē ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "display_original_photos": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģҌĐŊҋ҅ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš", @@ -876,9 +948,11 @@ "documentation": "ДоĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ", "done": "Đ“ĐžŅ‚ĐžĐ˛Đž", "download": "ĐĄĐēĐ°Ņ‡Đ°Ņ‚ŅŒ", + "download_action_prompt": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°ŅŽŅ‚ŅŅ {count} ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "download_canceled": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐžŅ‚ĐŧĐĩĐŊĐĩĐŊа", "download_complete": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐžĐēĐžĐŊ҇ĐĩĐŊа", "download_enqueue": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа в ĐžŅ‡ĐĩŅ€Đĩди", + "download_error": "ĐžŅˆĐ¸ĐąĐēа ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", "download_failed": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŊĐĩ ŅƒĐ´Đ°ĐģĐ°ŅŅŒ", "download_finished": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐžĐēĐžĐŊ҇ĐĩĐŊа", "download_include_embedded_motion_videos": "Đ’ŅŅ‚Ņ€ĐžĐĩĐŊĐŊŅ‹Đĩ видĐĩĐž", @@ -888,15 +962,15 @@ "download_paused": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŋŅ€Đ¸ĐžŅŅ‚Đ°ĐŊОвĐģĐĩĐŊа", "download_settings": "ĐĄĐēĐ°Ņ‡Đ¸Đ˛Đ°ĐŊиĐĩ", "download_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧи ҁĐēĐ°Ņ‡Đ¸Đ˛Đ°ĐŊĐ¸Ņ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", + "download_started": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŊĐ°Ņ‡Đ°ĐģĐ°ŅŅŒ", + "download_sucess": "ĐŖŅĐŋĐĩ҈ĐŊĐ°Ņ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа", + "download_sucess_android": "МĐĩĐ´Đ¸Đ°Ņ„Đ°ĐšĐģŅ‹ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊŅ‹ в DCIM/Immich", "download_waiting_to_retry": "ОĐļидаĐŊиĐĩ ĐŋĐžĐ˛Ņ‚ĐžŅ€ĐŊОК ĐŋĐžĐŋҋ҂Đēи", "downloading": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа", "downloading_asset_filename": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐžĐąŅŠĐĩĐēŅ‚Đ° {filename}", "downloading_from_icloud": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа иС iCloud", "downloading_media": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŧĐĩдиа", - "drag_to_reorder": "ПĐĩŅ€ĐĩŅ‚Đ°Ņ‰Đ¸Ņ‚Đĩ, Ņ‡Ņ‚ĐžĐąŅ‹ иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐžŅ€ŅĐ´ĐžĐē", "drop_files_to_upload": "ПĐĩŅ€ĐĩĐŊĐĩŅĐ¸Ņ‚Đĩ Ņ„Đ°ĐšĐģŅ‹ в ĐģŅŽĐąĐžĐĩ ĐŧĐĩŅŅ‚Đž Đ´ĐģŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", - "duplicate": "Đ”ŅƒĐąĐģĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", - "duplicate_workflow": "Đ”ŅƒĐąĐģĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ", "duplicates": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹", "duplicates_description": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ¸Ņ‚Đĩ ĐŊаКдĐĩĐŊĐŊŅ‹Đĩ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹ и в ĐēаĐļдОК ĐŗŅ€ŅƒĐŋĐŋĐĩ ҃ĐēаĐļĐ¸Ņ‚Đĩ, ĐēаĐēиĐĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ, а ĐēаĐēиĐĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ.", "duration": "ĐŸŅ€ĐžĐ´ĐžĐģĐļĐ¸Ņ‚ĐĩĐģҌĐŊĐžŅŅ‚ŅŒ", @@ -908,7 +982,9 @@ "edit_date_and_time": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ и Đ˛Ņ€ĐĩĐŧŅ", "edit_date_and_time_action_prompt": "Đ”Đ°Ņ‚Đ° и Đ˛Ņ€ĐĩĐŧŅ иСĐŧĐĩĐŊĐĩĐŊŅ‹ ҃ {count} ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "edit_date_and_time_by_offset": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ ĐŋĐž ҁĐŧĐĩ҉ĐĩĐŊĐ¸ŅŽ", + "edit_date_and_time_by_offset_interval": "ĐĐžĐ˛Ņ‹Đš диаĐŋаСОĐŊ Đ´Đ°Ņ‚: {from} - {to}", "edit_description": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "edit_description_prompt": "ĐŖĐēаĐļĐ¸Ņ‚Đĩ ĐŊОвОĐĩ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ:", "edit_exclusion_pattern": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ŅˆĐ°ĐąĐģĐžĐŊа Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ", "edit_faces": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐģĐ¸Ņ†", "edit_key": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐēĐģŅŽŅ‡", @@ -921,8 +997,11 @@ "edit_tag": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗ", "edit_title": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐˇĐ°ĐŗĐžĐģОвОĐē", "edit_user": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", - "edit_workflow": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš", + "edit_workflow": "Đ ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "editor": "Đ ĐĩдаĐēŅ‚ĐžŅ€", + "editor_close_without_save_prompt": "ИСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ‚ ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊŅ‹", + "editor_close_without_save_title": "ЗаĐēŅ€Ņ‹Ņ‚ŅŒ Ņ€ĐĩдаĐēŅ‚ĐžŅ€?", + "editor_confirm_reset_all_changes": "ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ ŅĐ´ĐĩĐģаĐŊĐŊŅ‹Đĩ иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ?", "editor_discard_edits_confirm": "ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ", "editor_discard_edits_prompt": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ ĐŊĐĩ ŅĐžŅ…Ņ€Đ°ĐŊŅ‘ĐŊĐŊŅ‹Đĩ иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ?", "editor_discard_edits_title": "ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "ВвĐĩĐ´Đ¸Ņ‚Đĩ Đ˛Đ°Ņˆ PIN-ĐēОд", "enter_your_pin_code_subtitle": "ВвĐĩĐ´Đ¸Ņ‚Đĩ ŅĐ˛ĐžĐš PIN-ĐēОд Đ´ĐģŅ Đ´ĐžŅŅ‚ŅƒĐŋа Đē ĐģĐ¸Ņ‡ĐŊОК ĐŋаĐŋĐēĐĩ", "error": "ĐžŅˆĐ¸ĐąĐēа", + "error_change_sort_album": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐžŅ€ŅĐ´ĐžĐē ŅĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đēи аĐģŅŒĐąĐžĐŧа", "error_delete_face": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ŅƒĐ´Đ°ĐģĐĩĐŊии ĐģĐ¸Ņ†Đ° иС ĐžĐąŅŠĐĩĐēŅ‚Đ°", + "error_getting_places": "ĐžŅˆĐ¸ĐąĐēа ĐŋĐžĐģŅƒŅ‡ĐĩĐŊĐ¸Ņ ĐŧĐĩҁ҂", "error_loading_albums": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēĐĩ аĐģŅŒĐąĐžĐŧОв", "error_loading_image": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēĐĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "error_loading_partners": "ĐžŅˆĐ¸ĐąĐēа ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€ĐžĐ˛: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐēĐģŅŽŅ‡ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚Đ°", "failed_to_reset_pin_code": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ PIN-ĐēОд", "failed_to_stack_assets": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ", - "failed_to_tag_assets": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗĐ¸", "failed_to_unstack_assets": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ Ņ€Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ", "failed_to_update_notification_status": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ŅŅ‚Đ°Ņ‚ŅƒŅ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ", "incorrect_email_or_password": "НĐĩвĐĩŅ€ĐŊŅ‹Đš Đ°Đ´Ņ€Đĩҁ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊОК ĐŋĐžŅ‡Ņ‚Ņ‹ иĐģи ĐŋĐ°Ņ€ĐžĐģҌ", @@ -1019,7 +1099,7 @@ "unable_to_complete_oauth_login": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ Đ˛Ņ‹ĐŋĐžĐģĐŊĐ¸Ņ‚ŅŒ Đ˛Ņ…ĐžĐ´ ҁ ĐŋĐžĐŧĐžŅ‰ŅŒŅŽ OAuth", "unable_to_connect": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŋОдĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒŅŅ", "unable_to_copy_to_clipboard": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ҁĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧĐĩĐŊа, ŅƒĐąĐĩĐ´Đ¸Ņ‚ĐĩҁҌ, Ņ‡Ņ‚Đž Đ˛Ņ‹ ĐŋĐžĐģŅƒŅ‡Đ°ĐĩŅ‚Đĩ Đ´ĐžŅŅ‚ŅƒĐŋ Đē ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đĩ ĐŋĐž ĐŋŅ€ĐžŅ‚ĐžĐēĐžĐģ҃ https", - "unable_to_create": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš", + "unable_to_create": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "unable_to_create_admin_account": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ ŅƒŅ‡ĐĩŅ‚ĐŊŅƒŅŽ СаĐŋĐ¸ŅŅŒ адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "unable_to_create_api_key": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐŊĐžĐ˛Ņ‹Đš API ĐēĐģŅŽŅ‡", "unable_to_create_library": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ йийĐģĐ¸ĐžŅ‚ĐĩĐē҃", @@ -1030,7 +1110,7 @@ "unable_to_delete_exclusion_pattern": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ", "unable_to_delete_shared_link": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊŅƒŅŽ ҁҁҋĐģĐē҃", "unable_to_delete_user": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", - "unable_to_delete_workflow": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš", + "unable_to_delete_workflow": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "unable_to_download_files": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ҁĐēĐ°Ņ‡Đ°Ņ‚ŅŒ Ņ„Đ°ĐšĐģŅ‹", "unable_to_edit_exclusion_pattern": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐžŅ‚Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ Đ¸ŅĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ", "unable_to_empty_trash": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", @@ -1082,7 +1162,7 @@ "unable_to_update_settings": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", "unable_to_update_timeline_display_status": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ŅŅ‚Đ°Ņ‚ŅƒŅ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ĐŊа ҈ĐēаĐģĐĩ Đ˛Ņ€ĐĩĐŧĐĩĐŊи", "unable_to_update_user": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", - "unable_to_update_workflow": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš", + "unable_to_update_workflow": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "unable_to_upload_file": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ Ņ„Đ°ĐšĐģ" }, "errors_text": "ĐžŅˆĐ¸ĐąĐēи", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ...", "exif_bottom_sheet_description_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ОйĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", + "exif_bottom_sheet_details": "ПОДРОБНОСĐĸИ", + "exif_bottom_sheet_location": "МЕСĐĸО", "exif_bottom_sheet_no_description": "НĐĩŅ‚ ĐžĐŋĐ¸ŅĐ°ĐŊĐ¸Ņ", + "exif_bottom_sheet_people": "ЛЮДИ", + "exif_bottom_sheet_person_add_person": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ иĐŧŅ", "exit_slideshow": "Đ’Ņ‹ĐšŅ‚Đ¸ иС ҁĐģаКд-ŅˆĐžŅƒ", "expand": "РаСвĐĩŅ€ĐŊŅƒŅ‚ŅŒ", "expand_all": "РаСвĐĩŅ€ĐŊŅƒŅ‚ŅŒ Đ˛ŅŅ‘", + "experimental_settings_new_asset_list_subtitle": "В Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚ĐēĐĩ", + "experimental_settings_new_asset_list_title": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊŅƒŅŽ ҁĐĩŅ‚Đē҃ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš", + "experimental_settings_subtitle": "Đ˜ŅĐŋĐžĐģŅŒĐˇŅƒĐšŅ‚Đĩ ĐŊа ŅĐ˛ĐžĐš ŅŅ‚Ņ€Đ°Ņ… и Ņ€Đ¸ŅĐē!", + "experimental_settings_title": "Đ­ĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊŅ‹Đĩ Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸", "expire_after": "Đ˜ŅŅ‚ĐĩĐēаĐĩŅ‚ ҇ĐĩŅ€ĐĩС", "expired": "ĐĄŅ€ĐžĐē Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ Đ¸ŅŅ‚Ņ‘Đē", "expires_date": "ĐĄŅ€ĐžĐē Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ Đ´Đž {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Đ­ĐēҁĐŋĐžŅ€Ņ‚ в JSON", "export_database": "Đ­ĐēҁĐŋĐžŅ€Ņ‚ ĐąĐ°ĐˇŅ‹ даĐŊĐŊҋ҅", "export_database_description": "Đ­ĐēҁĐŋĐžŅ€Ņ‚ ĐąĐ°ĐˇŅ‹ даĐŊĐŊҋ҅ SQLite", - "exposure_time": "Đ’Ņ‹Đ´ĐĩŅ€ĐļĐēа", "extension": "Đ Đ°ŅŅˆĐ¸Ņ€ĐĩĐŊиĐĩ", "external": "ВĐŊĐĩ҈ĐŊиК", "external_libraries": "ВĐŊĐĩ҈ĐŊиĐĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "external_network": "ВĐŊĐĩ҈ĐŊŅŅ ҁĐĩŅ‚ŅŒ", "external_network_sheet_info": "ĐšĐžĐŗĐ´Đ° ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đž ĐŊĐĩ ĐŋОдĐēĐģŅŽŅ‡ĐĩĐŊĐž Đē ҃ĐēаСаĐŊĐŊОК Wi-Fi ҁĐĩŅ‚Đ¸, ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ ĐąŅƒĐ´ĐĩŅ‚ ĐŋŅ‹Ņ‚Đ°Ņ‚ŅŒŅŅ ĐŋОдĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒŅŅ Đē ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ ĐŋĐž Đ°Đ´Ņ€ĐĩŅĐ°Đŧ ĐŊиĐļĐĩ, ŅĐ˛ĐĩŅ€Ņ…Ņƒ вĐŊиС Đ´Đž ҃ҁĐŋĐĩ҈ĐŊĐžĐŗĐž ĐŋОдĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ", - "f_number": "F-Ņ‡Đ¸ŅĐģĐž", "face_unassigned": "НĐĩ ĐŊаСĐŊĐ°Ņ‡ĐĩĐŊĐž", "failed": "ĐžŅˆĐ¸ĐąĐēа", "failed_count": "ЗавĐĩŅ€ŅˆĐĩĐŊŅ‹ ҁ ĐžŅˆĐ¸ĐąĐēаĐŧи: {count}", "failed_to_authenticate": "ĐžŅˆĐ¸ĐąĐēа Đ°ŅƒŅ‚ĐĩĐŊŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Đ¸", - "failed_to_delete_file": "ĐžŅˆĐ¸ĐąĐēа ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ Ņ„Đ°ĐšĐģа", "failed_to_load_assets": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ", "failed_to_load_folder": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēĐĩ ĐŋаĐŋĐēи", "favorite": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ", "favorite_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ дОйавĐģĐĩĐŊŅ‹ в Đ˜ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ ({count} ŅˆŅ‚.)", "favorite_or_unfavorite_photo": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ иĐģи ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅŽ иС Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐŗĐž", "favorites": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ", + "favorites_page_no_favorites": "В Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐŧ ҁĐĩĐšŅ‡Đ°Ņ ĐŋŅƒŅŅ‚Đž", "feature_photo_updated": "Đ˜ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ Ņ„ĐžŅ‚Đž ОйĐŊОвĐģĐĩĐŊĐž", "features": "ДоĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊŅ‹Đĩ вОСĐŧĐžĐļĐŊĐžŅŅ‚Đ¸", + "features_in_development": "Đ¤ŅƒĐŊĐēŅ†Đ¸Đ¸ в Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚ĐēĐĩ", "features_setting_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ Đ´ĐžĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊŅ‹Đŧи вОСĐŧĐžĐļĐŊĐžŅŅ‚ŅĐŧи ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", "file_name_or_extension": "ИĐŧŅ Ņ„Đ°ĐšĐģа иĐģи Ņ€Đ°ŅŅˆĐ¸Ņ€ĐĩĐŊиĐĩ", "file_name_text": "ИĐŧŅ Ņ„Đ°ĐšĐģа", + "file_name_with_value": "ИĐŧŅ Ņ„Đ°ĐšĐģа: {file_name}", "file_size": "РаСĐŧĐĩŅ€ Ņ„Đ°ĐšĐģа", "filename": "ИĐŧŅ Ņ„Đ°ĐšĐģа", "filetype": "ĐĸиĐŋ Ņ„Đ°ĐšĐģа", "filter": "ФиĐģŅŒŅ‚Ņ€", + "filter_description": "ĐŖŅĐģĐžĐ˛Đ¸Ņ ĐžŅ‚ĐąĐžŅ€Đ° ҆ĐĩĐģĐĩĐ˛Ņ‹Ņ… ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "filter_people": "ФиĐģŅŒŅ‚Ņ€ ĐŋĐž ĐģŅŽĐ´ŅĐŧ", "filter_places": "ФиĐģŅŒŅ‚Ņ€ ĐŋĐž ĐŧĐĩŅŅ‚Đ°Đŧ", "filter_tags": "ФиĐģŅŒŅ‚Ņ€ ĐŋĐž Ņ‚ĐĩĐŗĐ°Đŧ", "filters": "ФиĐģŅŒŅ‚Ņ€Ņ‹", + "find_them_fast": "Đ‘Ņ‹ŅŅ‚Ņ€Đž ĐŊĐ°ĐšĐ´Đ¸Ņ‚Đĩ Đ¸Ņ… ĐŋĐž иĐŧĐĩĐŊи ҁ ĐŋĐžĐŧĐžŅ‰ŅŒŅŽ ĐŋĐžĐ¸ŅĐēа", "first": "ПĐĩŅ€Đ˛Ņ‹Đš", "fix_incorrect_match": "Đ˜ŅĐŋŅ€Đ°Đ˛Đ¸Ņ‚ŅŒ ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊĐžĐĩ ŅĐžĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛Đ¸Đĩ", - "focal_length": "ФОĐē҃ҁĐŊĐžĐĩ Ņ€Đ°ŅŅŅ‚ĐžŅĐŊиĐĩ", "folder": "ПаĐŋĐēа", "folder_not_found": "ПаĐŋĐēа ĐŊĐĩ ĐŊаКдĐĩĐŊа", "folders": "ПаĐŋĐēи", @@ -1147,7 +1236,6 @@ "free_up_space_description": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ ҁĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊŅ‹Đĩ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€ Ņ„ĐžŅ‚Đž и видĐĩĐž в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° Đ´ĐģŅ ĐžŅĐ˛ĐžĐąĐžĐļĐ´ĐĩĐŊĐ¸Ņ ĐŧĐĩŅŅ‚Đ°. КоĐŋии ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ ĐžŅŅ‚Đ°ĐŊŅƒŅ‚ŅŅ ĐŊĐĩŅ‚Ņ€ĐžĐŊŅƒŅ‚Ņ‹Đŧи.", "free_up_space_settings_subtitle": "ĐžŅĐ˛ĐžĐąĐžĐ´Đ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚Đž ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", "full_path": "ПоĐģĐŊŅ‹Đš ĐŋŅƒŅ‚ŅŒ: {path}", - "full_path_or_folder": "ĐŸŅƒŅ‚ŅŒ иĐģи ĐŋаĐŋĐēа", "gcast_enabled": "Google Cast", "gcast_enabled_description": "ДĐģŅ Ņ€Đ°ĐąĐžŅ‚Ņ‹ ҂ҀĐĩĐąŅƒĐĩŅ‚ŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа вĐŊĐĩ҈ĐŊĐ¸Ņ… Ņ€ĐĩŅŅƒŅ€ŅĐžĐ˛ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐ˛ Google.", "general": "ĐžĐąŅ‰Đ¸Đĩ", @@ -1168,6 +1256,7 @@ "group_owner": "Đ“Ņ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŋĐž вĐģадĐĩĐģŅŒŅ†Ņƒ", "group_places_by": "Đ“Ņ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŧĐĩŅŅ‚Đ° ĐŋĐž...", "group_year": "Đ“Ņ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐŋĐž ĐŗĐžĐ´Đ°Đŧ", + "haptic_feedback_switch": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ‚Đ°ĐēŅ‚Đ¸ĐģҌĐŊŅƒŅŽ ĐžŅ‚Đ´Đ°Ņ‡Ņƒ", "haptic_feedback_title": "ĐĸаĐēŅ‚Đ¸ĐģҌĐŊĐ°Ņ ĐžŅ‚Đ´Đ°Ņ‡Đ°", "has_quota": "ĐšĐ˛ĐžŅ‚Đ°", "hash_asset": "ĐĨĐĩŅˆĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", @@ -1188,12 +1277,29 @@ "hide_schema": "ĐĄĐēŅ€Ņ‹Ņ‚ŅŒ ҁ҅ĐĩĐŧ҃", "hide_text_recognition": "ĐĄĐēŅ€Ņ‹Ņ‚ŅŒ Ņ€Đ°ŅĐŋОСĐŊаĐŊĐŊŅ‹Đš Ņ‚ĐĩĐēҁ҂", "hide_unnamed_people": "ĐĄĐēŅ€Ņ‹Ņ‚ŅŒ ĐģŅŽĐ´ĐĩĐš ĐąĐĩС иĐŧĐĩĐŊи", + "home_page_add_to_album_conflicts": "ДобавĐģĐĩĐŊĐž {added} ĐŧĐĩдиа в аĐģŅŒĐąĐžĐŧ {album}. {failed} ĐŧĐĩдиа ҃ĐļĐĩ в аĐģŅŒĐąĐžĐŧĐĩ.", + "home_page_add_to_album_err_local": "ПоĐēа ĐŊĐĩĐģŅŒĐˇŅ дОйавĐģŅŅ‚ŅŒ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ в аĐģŅŒĐąĐžĐŧŅ‹, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_add_to_album_success": "ДобавĐģĐĩĐŊĐž {added} ĐŧĐĩдиа в аĐģŅŒĐąĐžĐŧ {album}.", + "home_page_album_err_partner": "НĐĩвОСĐŧĐžĐļĐŊĐž Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ° в аĐģŅŒĐąĐžĐŧ, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_archive_err_local": "ПоĐēа ĐŊĐĩĐģŅŒĐˇŅ Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ Ņ„Đ°ĐšĐģŅ‹ в Đ°Ņ€Ņ…Đ¸Đ˛, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_archive_err_partner": "НĐĩвОСĐŧĐžĐļĐŊĐž Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ° в Đ°Ņ€Ņ…Đ¸Đ˛, ĐŋŅ€ĐžĐŋ҃ҁĐē", "home_page_building_timeline": "ĐŸĐžŅŅ‚Ņ€ĐžĐĩĐŊиĐĩ Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗĐ¸Đ¸", + "home_page_delete_err_partner": "НĐĩвОСĐŧĐžĐļĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_delete_remote_err_local": "НĐĩвОСĐŧĐžĐļĐŊĐž ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ Ņ„Đ°ĐšĐģŅ‹ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_favorite_err_local": "ПоĐēа ĐŊĐĩĐģŅŒĐˇŅ Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ в Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ Ņ„Đ°ĐšĐģŅ‹, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_favorite_err_partner": "НĐĩвОСĐŧĐžĐļĐŊĐž Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ° в Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_first_time_notice": "ПĐĩŅ€ĐĩĐ´ ĐŊĐ°Ņ‡Đ°ĐģĐžĐŧ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊĐ¸Ņ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ Đ˛Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ аĐģŅŒĐąĐžĐŧ ҁ ĐžĐąŅŠĐĩĐēŅ‚Đ°Đŧи Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ, Ņ‡Ņ‚ĐžĐąŅ‹ ĐžĐŊи ĐžŅ‚ĐžĐąŅ€Đ°ĐˇĐ¸ĐģĐ¸ŅŅŒ ĐŊа Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊОК ҈ĐēаĐģĐĩ", + "home_page_locked_error_local": "НĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ ĐģĐžĐēаĐģҌĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ в ĐģĐ¸Ņ‡ĐŊŅƒŅŽ ĐŋаĐŋĐē҃, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_locked_error_partner": "НĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ° в ĐģĐ¸Ņ‡ĐŊŅƒŅŽ ĐŋаĐŋĐē҃, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_share_err_local": "НĐĩĐģŅŒĐˇŅ ĐŋОдĐĩĐģĐ¸Ņ‚ŅŒŅŅ ĐģĐžĐēаĐģҌĐŊŅ‹Đŧи Ņ„Đ°ĐšĐģаĐŧи ĐŋĐž ҁҁҋĐģĐēĐĩ, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "home_page_upload_err_limit": "Đ’Ņ‹ ĐŧĐžĐļĐĩŅ‚Đĩ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ ĐŧаĐēŅĐ¸Đŧ҃Đŧ 30 Ņ„Đ°ĐšĐģОв Са Ņ€Đ°Đˇ, ĐŋŅ€ĐžĐŋ҃ҁĐē", "host": "ĐĨĐžŅŅ‚", "hour": "Đ§Đ°Ņ", "hours": "Đ§Đ°ŅŅ‹", "id": "ID", "idle": "В ĐžĐļидаĐŊии", + "ignore_icloud_photos": "ĐŸŅ€ĐžĐŋ҃ҁĐēĐ°Ņ‚ŅŒ Ņ„Đ°ĐšĐģŅ‹ иС iCloud", + "ignore_icloud_photos_description": "НĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ Ņ„Đ°ĐšĐģŅ‹ в Immich, ĐĩҁĐģи ĐžĐŊи Ņ…Ņ€Đ°ĐŊŅŅ‚ŅŅ в iCloud", "image": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "image_alt_text_date": "ХОвĐŧĐĩҁ҂ĐŊĐžĐĩ {isVideo, select, true {Video} other {Image}} {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} ŅĐžĐ˛ĐŧĐĩҁ҂ĐŊĐž ҁ {person1} {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} ҁĐŊŅŅ‚ĐžĐĩ в {city}, {country} ҁ {person1} и {person2} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} ҁĐŊŅŅ‚ĐžĐĩ в {city}, {country} ҁ {person1}, {person2}, и {person3} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} ҁĐŊŅŅ‚ĐžĐĩ в {city}, {country} ҁ {person1}, {person2}, и Đĩ҉Đĩ ҁ {additionalCount, number} ĐģŅŽĐ´ŅŒĐŧи {date}", + "image_saved_successfully": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐž", + "image_viewer_page_state_provider_download_started": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŊĐ°Ņ‡Đ°ĐģĐ°ŅŅŒ", + "image_viewer_page_state_provider_download_success": "ĐŖŅĐŋĐĩ҈ĐŊĐž ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž", + "image_viewer_page_state_provider_share_error": "ĐžŅˆĐ¸ĐąĐēа ĐžĐąŅ‰ĐĩĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа", "immich_logo": "Đ›ĐžĐŗĐž Immich", "immich_web_interface": "ВĐĩĐą иĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ Immich", "import_from_json": "ИĐŧĐŋĐžŅ€Ņ‚ иС JSON", @@ -1219,7 +1329,6 @@ "individual_share": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģҌĐŊĐ°Ņ ĐŋĐžĐ´ĐąĐžŅ€Đēа", "individual_shares": "ĐŸĐžĐ´ĐąĐžŅ€Đēи", "info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ", - "integrity_checks": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đēа ҆ĐĩĐģĐžŅŅ‚ĐŊĐžŅŅ‚Đ¸", "interval": { "day_at_onepm": "КаĐļĐ´Ņ‹Đš Đ´ĐĩĐŊҌ в 13:00", "hours": "{hours, plural, one {КаĐļĐ´Ņ‹Đš Ņ‡Đ°Ņ} many {КаĐļĐ´Ņ‹Đĩ # Ņ‡Đ°ŅĐžĐ˛} other {КаĐļĐ´Ņ‹Đĩ # Ņ‡Đ°ŅĐ°}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "НĐĩвĐĩŅ€ĐŊŅ‹Đš Ņ„ĐžŅ€ĐŧĐ°Ņ‚ Đ´Đ°Ņ‚Ņ‹", "invite_people": "ĐŸŅ€Đ¸ĐŗĐģĐ°ŅĐ¸Ņ‚ŅŒ ŅƒŅ‡Đ°ŅŅ‚ĐŊиĐēа", "invite_to_album": "ĐŸŅ€Đ¸ĐŗĐģĐ°ŅĐ¸Ņ‚ŅŒ в аĐģŅŒĐąĐžĐŧ", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Đ’Ņ‹ĐąĐžŅ€Đēа СаĐŋŅƒŅ‰ĐĩĐŊа {dateTime}", + "ios_debug_info_last_sync_at": "ĐŸĐžŅĐģĐĩĐ´ĐŊŅŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ {dateTime}", + "ios_debug_info_no_processes_queued": "НĐĩŅ‚ Ņ„ĐžĐŊĐžĐ˛Ņ‹Ņ… ĐŋŅ€ĐžŅ†ĐĩŅŅĐžĐ˛ в ĐžŅ‡ĐĩŅ€Đĩди", + "ios_debug_info_no_sync_yet": "ФОĐŊĐžĐ˛Đ°Ņ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ Đĩ҉Đĩ ĐŊĐĩ Đ˛Ņ‹ĐŋĐžĐģĐŊŅĐģĐ°ŅŅŒ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} Ņ„ĐžĐŊĐžĐ˛Ņ‹Đš ĐŋŅ€ĐžŅ†Đĩҁҁ} many {{count} Ņ„ĐžĐŊĐžĐ˛Ņ‹Ņ… ĐŋŅ€ĐžŅ†ĐĩŅŅĐžĐ˛} other {{count} Ņ„ĐžĐŊĐžĐ˛Ņ‹Ņ… ĐŋŅ€ĐžŅ†ĐĩŅŅĐ°}} в ĐžŅ‡ĐĩŅ€Đĩди", + "ios_debug_info_processing_ran_at": "ĐžĐąŅ€Đ°ĐąĐžŅ‚Đēа СаĐŋŅƒŅ‰ĐĩĐŊа {dateTime}", "items_count": "{count, plural, one {# ŅĐģĐĩĐŧĐĩĐŊŅ‚} many {# ŅĐģĐĩĐŧĐĩĐŊŅ‚ĐžĐ˛} other {# ŅĐģĐĩĐŧĐĩĐŊŅ‚Đ°}}", "jobs": "Đ—Đ°Đ´Đ°Ņ‡Đ¸", + "json_editor": "Đ ĐĩдаĐēŅ‚ĐžŅ€ JSON", + "json_error": "ĐžŅˆĐ¸ĐąĐēа JSON", "keep": "ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ", "keep_albums": "ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", "keep_albums_count": "ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ {count, plural, one {# аĐģŅŒĐąĐžĐŧ} many {# аĐģŅŒĐąĐžĐŧОв} other {# аĐģŅŒĐąĐžĐŧа}}", @@ -1259,12 +1375,14 @@ "leave": "ПоĐēиĐŊŅƒŅ‚ŅŒ", "leave_album": "ПоĐēиĐŊŅƒŅ‚ŅŒ аĐģŅŒĐąĐžĐŧ", "lens_model": "МодĐĩĐģҌ ĐžĐąŅŠĐĩĐēŅ‚Đ¸Đ˛Đ°", - "less": "МĐĩĐŊҌ҈Đĩ", "let_others_respond": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒ Đ´Ņ€ŅƒĐŗĐ¸Đŧ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅĐŧ дОйавĐģŅŅ‚ŅŒ ĐēĐžĐŧĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸Đ¸ и ĐžŅ‚ĐŧĐĩŅ‚Đēи \"ĐŊŅ€Đ°Đ˛Đ¸Ņ‚ŅŅ\"", "level": "ĐŖŅ€ĐžĐ˛ĐĩĐŊҌ", "library": "БибĐģĐ¸ĐžŅ‚ĐĩĐēа", "library_add_folder": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŋаĐŋĐē҃", "library_edit_folder": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋŅƒŅ‚ŅŒ Đē ĐŋаĐŋĐēĐĩ", + "library_options": "ДĐĩĐšŅŅ‚Đ˛Đ¸Ņ ҁ йийĐģĐ¸ĐžŅ‚ĐĩĐēОК", + "library_page_device_albums": "АĐģŅŒĐąĐžĐŧŅ‹ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", + "library_page_new_album": "ĐĐžĐ˛Ņ‹Đš аĐģŅŒĐąĐžĐŧ", "library_page_sort_asset_count": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "library_page_sort_created": "НĐĩдавĐŊĐž ŅĐžĐˇĐ´Đ°ĐŊĐŊŅ‹Đĩ", "library_page_sort_last_modified": "ĐŸĐžŅĐģĐĩĐ´ĐŊĐĩĐĩ иСĐŧĐĩĐŊĐĩĐŊиĐĩ", @@ -1274,14 +1392,11 @@ "light_theme": "ПĐĩŅ€ĐĩĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒŅŅ ĐŊа ŅĐ˛ĐĩŅ‚ĐģŅƒŅŽ Ņ‚ĐĩĐŧ҃", "like": "ĐŅ€Đ°Đ˛Đ¸Ņ‚ŅŅ", "like_deleted": "ЛайĐē ŅƒĐ´Đ°ĐģĐĩĐŊ", - "link": "ĐĄŅŅ‹ĐģĐēа", "link_motion_video": "ĐĄŅŅ‹ĐģĐēа ĐŊа двиĐļŅƒŅ‰ĐĩĐĩŅŅ видĐĩĐž", "link_to_docs": "ДоĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊĐ°Ņ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đ´ĐžŅŅ‚ŅƒĐŋĐŊа в Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Đ¸Đ¸.", "link_to_oauth": "ĐŸŅ€Đ¸ŅĐžĐĩдиĐŊĐĩĐŊиĐĩ Đē OAuth", "linked_oauth_account": "ĐŸŅ€Đ¸ŅĐžĐĩдиĐŊŅ‘ĐŊĐŊŅ‹Đš аĐēĐēĐ°ŅƒĐŊŅ‚ OAuth", "list": "ĐĄĐŋĐ¸ŅĐžĐē", - "live": "Live", - "load_more": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ Đĩ҉ґ", "loading": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа", "loading_search_results_failed": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа Ņ€ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚ĐžĐ˛ ĐŋĐžĐ¸ŅĐēа ĐŊĐĩ ŅƒĐ´Đ°ĐģĐ°ŅŅŒ", "local": "На ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", @@ -1310,9 +1425,11 @@ "login": "Đ’ĐžĐšŅ‚Đ¸", "login_disabled": "Đ’Ņ…ĐžĐ´ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊ", "login_form_api_exception": "ĐžŅˆĐ¸ĐąĐēа ĐŋОдĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ Đē ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ. ĐŸŅ€ĐžĐ˛ĐĩŅ€ŅŒŅ‚Đĩ URL-Đ°Đ´Ņ€Đĩҁ и ĐŋĐžĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ Đĩ҉Đĩ Ņ€Đ°Đˇ.", + "login_form_back_button_text": "Назад", "login_form_email_hint": "youremail@email.com", - "login_form_endpoint_hint": "http://ip-Đ˛Đ°ŅˆĐĩĐŗĐž-ҁĐĩŅ€Đ˛ĐĩŅ€Đ°:ĐŋĐžŅ€Ņ‚", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "URL-aĐ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", + "login_form_err_http": "ПоĐļаĐģŅƒĐšŅŅ‚Đ°, ҃ĐēаĐļĐ¸Ņ‚Đĩ ĐŋŅ€ĐžŅ‚ĐžĐēĐžĐģ http:// иĐģи https://", "login_form_err_invalid_email": "НĐĩĐēĐžŅ€Ņ€ĐĩĐēŅ‚ĐŊŅ‹Đš Đ°Đ´Ņ€Đĩҁ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊОК ĐŋĐžŅ‡Ņ‚Ņ‹", "login_form_err_invalid_url": "НĐĩĐēĐžŅ€Ņ€ĐĩĐēŅ‚ĐŊŅ‹Đš URL", "login_form_err_leading_whitespace": "ĐŸŅ€ĐžĐąĐĩĐģ Đ´Đž", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ Đ˛Ņ…ĐžĐ´Đĩ, ĐŋŅ€ĐžĐ˛ĐĩŅ€ŅŒŅ‚Đĩ URL-Đ°Đ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, Đ°Đ´Ņ€Đĩҁ ŅĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊОК ĐŋĐžŅ‡Ņ‚Ņ‹ и ĐŋĐ°Ņ€ĐžĐģҌ", "login_form_handshake_exception": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊии ĐˇĐ°Ņ‰Đ¸Ņ‰Ņ‘ĐŊĐŊĐžĐŗĐž ŅĐžĐĩдиĐŊĐĩĐŊĐ¸Ņ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ. Đ•ŅĐģи Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ŅŅ ŅĐ°ĐŧĐžĐŋОдĐŋĐ¸ŅĐ°ĐŊĐŊŅ‹Đš ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚, вĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ ĐĩĐŗĐž ĐŋОддĐĩŅ€ĐļĐē҃ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ°Ņ….", "login_form_password_hint": "ĐŋĐ°Ņ€ĐžĐģҌ", - "login_form_server_empty": "ВвĐĩĐ´Đ¸Ņ‚Đĩ URL-Đ°Đ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_form_server_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ŅĐžĐĩдиĐŊĐĩĐŊиĐĩ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", - "login_has_been_disabled": "Đ’Ņ…ĐžĐ´ в ŅĐ¸ŅŅ‚ĐĩĐŧ҃ ĐąŅ‹Đģ ĐžŅ‚ĐēĐģŅŽŅ‡Ņ‘ĐŊ", + "login_form_save_login": "ĐžŅŅ‚Đ°Đ˛Đ°Ņ‚ŅŒŅŅ в ŅĐ¸ŅŅ‚ĐĩĐŧĐĩ", + "login_form_server_empty": "ВвĐĩĐ´Đ¸Ņ‚Đĩ URL-Đ°Đ´Ņ€Đĩҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", + "login_form_server_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅƒŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ŅĐžĐĩдиĐŊĐĩĐŊиĐĩ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ.", + "login_has_been_disabled": "Đ’Ņ…ĐžĐ´ в ŅĐ¸ŅŅ‚ĐĩĐŧ҃ ĐąŅ‹Đģ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊ.", "login_password_changed_error": "ĐŸŅ€ĐžĐ¸ĐˇĐžŅˆĐģа ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ОйĐŊОвĐģĐĩĐŊии ĐŋĐ°Ņ€ĐžĐģŅ", "login_password_changed_success": "ĐŸĐ°Ņ€ĐžĐģҌ ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊОвĐģĐĩĐŊ", "logout_all_device_confirmation": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ СавĐĩŅ€ŅˆĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ ҁĐĩаĐŊҁҋ, ĐēŅ€ĐžĐŧĐĩ Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐŗĐž?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Đ’ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊиĐĩ ĐąĐ°ĐˇŅ‹ даĐŊĐŊҋ҅", "maintenance_description": "ĐĄĐĩŅ€Đ˛ĐĩŅ€ Immich ĐŋĐĩŅ€ĐĩвĐĩĐ´Ņ‘ĐŊ в Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģ҃ĐļиваĐŊĐ¸Ņ.", "maintenance_end": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģ҃ĐļиваĐŊĐ¸Ņ", + "maintenance_end_error": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģ҃ĐļиваĐŊĐ¸Ņ.", "maintenance_logged_in_as": "В ĐŊĐ°ŅŅ‚ĐžŅŅ‰ĐĩĐĩ Đ˛Ņ€ĐĩĐŧŅ Đ˛Ņ‹ Đ˛ĐžŅˆĐģи в ŅĐ¸ŅŅ‚ĐĩĐŧ҃ ĐēаĐē {user}", "maintenance_restore_from_backup": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ иС Ņ€ĐĩСĐĩŅ€Đ˛ĐŊОК ĐēĐžĐŋии", "maintenance_restore_library": "Đ’ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊиĐĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēи", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đē ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧ", "manage_media_access_subtitle": "Đ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸ŅŽ Immich ҃ĐŋŅ€Đ°Đ˛ĐģŅŅ‚ŅŒ ĐŧĐĩĐ´Đ¸Đ°Ņ„Đ°ĐšĐģаĐŧи.", "manage_media_access_title": "Đ”ĐžŅŅ‚ŅƒĐŋ Đē ҃ĐŋŅ€Đ°Đ˛ĐģĐĩĐŊĐ¸ŅŽ ĐŧĐĩĐ´Đ¸Đ°Ņ„Đ°ĐšĐģаĐŧи", + "manage_shared_links": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊŅ‹Đŧи ҁҁҋĐģĐēаĐŧи", "manage_sharing_with_partners": "Đ¤ŅƒĐŊĐēŅ†Đ¸Ņ ŅĐžĐ˛ĐŧĐĩҁ҂ĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа Đē Ņ„ĐžŅ‚Đž и видĐĩĐž, ĐŋОСвОĐģŅŅŽŅ‰Đ°Ņ видĐĩŅ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€ĐžĐ˛, а Ņ‚Đ°ĐēĐļĐĩ ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ĐģŅŅ‚ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ Đē ŅĐ˛ĐžĐ¸Đŧ", "manage_the_app_settings": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēаĐŧи ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", "manage_your_account": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ŅƒŅ‡Ņ‘Ņ‚ĐŊОК СаĐŋĐ¸ŅŅŒŅŽ", @@ -1371,12 +1491,14 @@ "manage_your_devices": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°Đŧи, ĐŊа ĐēĐžŅ‚ĐžŅ€Ņ‹Ņ… Đ˛Ņ‹ Đ˛Ņ…ĐžĐ´Đ¸Đģи в ŅĐ˛ĐžĐš аĐēĐēĐ°ŅƒĐŊŅ‚", "manage_your_oauth_connection": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐŋОдĐēĐģŅŽŅ‡Ņ‘ĐŊĐŊĐžĐŗĐž OAuth", "map": "ĐšĐ°Ņ€Ņ‚Đ°", + "map_assets_in_bounds": "{count, plural, =0 {НĐĩŅ‚ Ņ„ĐžŅ‚Đž} other {# Ņ„ĐžŅ‚Đž}}", "map_cannot_get_user_location": "НĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", + "map_location_dialog_yes": "Да", "map_location_picker_page_use_location": "Đ­Ņ‚Đž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "map_location_service_disabled_content": "ДĐģŅ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ в Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐŧ ĐŧĐĩҁ҂Đĩ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐž вĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ҁĐģ҃ĐļĐąŅƒ ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐ¸Ņ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸Ņ. ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ?", "map_location_service_disabled_title": "ĐĄĐģ҃Đļйа ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊĐ¸Ņ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸Ņ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊа", - "map_marker_for_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đĩ Đ´ĐģŅ ĐžĐąŅŠĐĩĐēŅ‚Đ°, ŅĐ´ĐĩĐģаĐŊĐŊĐžĐŗĐž в {city}, {country}", - "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đĩ Đ´ĐģŅ ĐžĐąŅŠĐĩĐēŅ‚Đ°", + "map_marker_for_images": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đĩ Đ´ĐģŅ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиК, ŅĐ´ĐĩĐģаĐŊĐŊҋ҅ в {city}, {country}", + "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐēĐ°Ņ€Ņ‚Đĩ ҁ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩĐŧ", "map_no_location_permission_content": "ДĐģŅ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ в Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐŧ ĐŧĐĩҁ҂Đĩ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐž Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ĐŊа ĐžĐŋŅ€ĐĩĐ´ĐĩĐģĐĩĐŊиĐĩ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸Ņ. ĐŸŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛Đ¸Ņ‚ŅŒ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ?", "map_no_location_permission_title": "Đ”ĐžŅŅ‚ŅƒĐŋ Đē ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊĐ¸ŅŽ ĐžŅ‚ĐēĐģĐžĐŊĐĩĐŊ", "map_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐēĐ°Ņ€Ņ‚Ņ‹", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "ĐŸĐžŅĐģĐĩĐ´ĐŊиĐĩ {days} Đ´ĐŊĐĩĐš", "map_settings_date_range_option_year": "Год", "map_settings_date_range_option_years": "{years} ĐŗĐžĐ´Đ°", + "map_settings_dialog_title": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ĐēĐ°Ņ€Ņ‚Ņ‹", "map_settings_include_show_archived": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊĐžĐĩ", "map_settings_include_show_partners": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ ĐŧĐĩдиа ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", "map_settings_only_show_favorites": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ Ņ‚ĐžĐģҌĐēĐž Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ", "map_settings_theme_settings": "ĐĻвĐĩŅ‚ ĐēĐ°Ņ€Ņ‚Ņ‹", + "map_zoom_to_see_photos": "ĐŖĐŧĐĩĐŊҌ҈ĐĩĐŊиĐĩ ĐŧĐ°ŅŅˆŅ‚Đ°ĐąĐ° Đ´ĐģŅ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš", "mark_all_as_read": "ĐŸŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐž", + "mark_as_read": "ĐžŅ‚ĐŧĐĩŅ‚Đ¸Ņ‚ŅŒ ĐēаĐē ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐŊĐžĐĩ", "marked_all_as_read": "Đ’ŅĐĩ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ ĐžŅ‚ĐŧĐĩ҇ĐĩĐŊŅ‹ ĐēаĐē ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ‹Đĩ", "matches": "ХОвĐŋадĐĩĐŊĐ¸Ņ", "matching_assets": "ĐĄĐžĐžŅ‚Đ˛ĐĩŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ ĐžĐąŅŠĐĩĐē҂ҋ", - "media_chrome": { - "auto": "ĐĐ˛Ņ‚Đž", - "captions": "ĐĄŅƒĐąŅ‚Đ¸Ņ‚Ņ€Ņ‹", - "captions_off": "Đ’Ņ‹ĐēĐģŅŽŅ‡ĐĩĐŊĐž", - "closed_captions": "СаĐēҀҋ҂ҋĐĩ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Ņ‹", - "decode_error": "ĐžŅˆĐ¸ĐąĐēа Đ´ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", - "disable_captions": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Ņ‹", - "enable_captions": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Ņ‹", - "enter_fullscreen_mode": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ в ĐŋĐžĐģĐŊĐžŅĐēŅ€Đ°ĐŊĐŊŅ‹Đš Ņ€ĐĩĐļиĐŧ", - "exit_fullscreen_mode": "Đ’Ņ‹ĐšŅ‚Đ¸ иС ĐŋĐžĐģĐŊĐžŅĐēŅ€Đ°ĐŊĐŊĐžĐŗĐž Ņ€ĐĩĐļиĐŧа", - "loop": "Đ—Đ°Ņ†Đ¸ĐēĐģĐ¸Ņ‚ŅŒ", - "media_error_description": "Đ’ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ ĐŋŅ€ĐĩŅ€Đ˛Đ°ĐŊĐž иС-Са ĐžŅˆĐ¸ĐąĐēи Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ. ВозĐŧĐžĐļĐŊĐž, ĐŧĐĩĐ´Đ¸Đ°Ņ„Đ°ĐšĐģ ĐŋĐžĐ˛Ņ€ĐĩĐļĐ´ĐĩĐŊ иĐģи Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅƒĐˇĐĩŅ€ ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ ŅŅ‚ĐžŅ‚ Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "media_loading": "ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа ĐŧĐĩдиа", - "mute": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐˇĐ˛ŅƒĐē", - "network_error": "ĐĄĐĩŅ‚ĐĩĐ˛Đ°Ņ ĐžŅˆĐ¸ĐąĐēа", - "network_error_description": "ХйОК ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи ĐŧĐĩĐ´Đ¸Đ°Ņ„Đ°ĐšĐģОв иС-Са ĐžŅˆĐ¸ĐąĐēи ҁĐĩŅ‚Đ¸.", - "not_supported_error": "Đ˜ŅŅ‚ĐžŅ‡ĐŊиĐē ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ", - "playback_rate": "ĐĄĐēĐžŅ€ĐžŅŅ‚ŅŒ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ", - "playback_rate_current": "Ņ‚ĐĩĐēŅƒŅ‰Đ°Ņ ҁĐēĐžŅ€ĐžŅŅ‚ŅŒ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ", - "playback_rate_value": "ĐĄĐēĐžŅ€ĐžŅŅ‚ŅŒ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ {playbackRate}", - "playback_time": "Đ˛Ņ€ĐĩĐŧŅ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ", - "quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", - "second": "ҁĐĩĐē҃ĐŊда", - "seconds": "ҁĐĩĐē҃ĐŊĐ´Ņ‹", - "time_value_of_total_time": "{currentTime} иС {totalTime}", - "time_value_remaining": "{time} ĐžŅŅ‚Đ°ĐģĐžŅŅŒ", - "unmute": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐˇĐ˛ŅƒĐē", - "unsupported_error_description": "ĐŸŅ€ĐžĐ¸ĐˇĐžŅˆĐģа ĐŊĐĩĐŋОддĐĩŅ€ĐļиваĐĩĐŧĐ°Ņ ĐžŅˆĐ¸ĐąĐēа. ĐĄĐĩŅ€Đ˛ĐĩŅ€ иĐģи ҁĐĩŅ‚ŅŒ ĐŊĐĩ Ņ€Đ°ĐąĐžŅ‚Đ°ŅŽŅ‚, иĐģи Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅƒĐˇĐĩŅ€ ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ ŅŅ‚ĐžŅ‚ Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "video_not_loaded_unknown_time": "видĐĩĐž ĐŊĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž, Đ˛Ņ€ĐĩĐŧŅ ĐŊĐĩиСвĐĩҁ҂ĐŊĐž.", - "video_player": "видĐĩĐžĐŋĐģĐĩĐĩŅ€", - "volume": "ĐŗŅ€ĐžĐŧĐēĐžŅŅ‚ŅŒ" - }, "media_type": "ĐĸиĐŋ ĐŧĐĩдиа", "memories": "Đ’ĐžŅĐŋĐžĐŧиĐŊаĐŊĐ¸Ņ", "memories_all_caught_up": "Đ­Ņ‚Đž Đ˛ŅŅ‘ ĐŊа ҁĐĩĐŗĐžĐ´ĐŊŅ", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Đ’Ņ‹ Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐžĐąŅŠĐĩдиĐŊĐ¸Ņ‚ŅŒ ŅŅ‚Đ¸Ņ… ĐģŅŽĐ´ĐĩĐš? Đ­Ņ‚Đž Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Đĩ ĐŊĐĩĐžĐąŅ€Đ°Ņ‚Đ¸ĐŧĐž.", "merge_people_successfully": "Đ›Đ¸Ņ†Đ° ĐģŅŽĐ´ĐĩĐš ҃ҁĐŋĐĩ҈ĐŊĐž ĐžĐąŅŠĐĩдиĐŊĐĩĐŊŅ‹", "merged_people_count": "ĐžĐąŅŠĐĩдиĐŊĐĩĐŊĐž {count, plural, one {# ҇ĐĩĐģОвĐĩĐē} many {# ҇ĐĩĐģОвĐĩĐē} other {# ҇ĐĩĐģОвĐĩĐēа}}", - "minFaces": "МиĐŊиĐŧ҃Đŧ ĐģĐ¸Ņ†", - "minFaces_description": "МиĐŊиĐŧаĐģҌĐŊĐžĐĩ ĐēĐžĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž Ņ€Đ°ŅĐŋОСĐŊаĐŊĐŊҋ҅ ĐģĐ¸Ņ†, ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐžĐĩ Đ´ĐģŅ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ҇ĐĩĐģОвĐĩĐēа", "minimize": "МиĐŊиĐŧĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", "minute": "МиĐŊŅƒŅ‚Đ°", "minutes": "МиĐŊŅƒŅ‚Ņ‹", + "mirror_horizontal": "Đ“ĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģҌĐŊĐž", + "mirror_vertical": "ВĐĩŅ€Ņ‚Đ¸ĐēаĐģҌĐŊĐž", "missing": "ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ", "mobile_app": "МобиĐģҌĐŊĐžĐĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ", "mobile_app_download_onboarding_note": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚Đĩ ĐŧОйиĐģҌĐŊĐžĐĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ Immich ĐģŅŽĐąŅ‹Đŧ иС ҁĐģĐĩĐ´ŅƒŅŽŅ‰Đ¸Ņ… ҁĐŋĐžŅĐžĐąĐžĐ˛", "model": "МодĐĩĐģҌ", - "modify_date": "Đ”Đ°Ņ‚Đ° иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ", "month": "МĐĩŅŅŅ†", + "monthly_title_text_date_format": "MMMM y", "more": "ДоĐŋĐžĐģĐŊĐ¸Ņ‚ĐĩĐģҌĐŊŅ‹Đĩ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", - "motion": "ДвиĐļĐĩĐŊиĐĩ", "move": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ", + "move_down": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ вĐŊиС", "move_off_locked_folder": "ĐŖĐąŅ€Đ°Ņ‚ŅŒ иС ĐģĐ¸Ņ‡ĐŊОК ĐŋаĐŋĐēи", "move_to": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ в", "move_to_device_trash": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", "move_to_lock_folder_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ дОйавĐģĐĩĐŊŅ‹ в ĐģĐ¸Ņ‡ĐŊŅƒŅŽ ĐŋаĐŋĐē҃ ({count} ŅˆŅ‚.)", "move_to_locked_folder": "В ĐģĐ¸Ņ‡ĐŊŅƒŅŽ ĐŋаĐŋĐē҃", "move_to_locked_folder_confirmation": "Đ­Ņ‚Đ¸ Ņ„ĐžŅ‚Đž и видĐĩĐž ĐąŅƒĐ´ŅƒŅ‚ ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ иС Đ˛ŅĐĩŅ… аĐģŅŒĐąĐžĐŧОв и ĐąŅƒĐ´ŅƒŅ‚ Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹ Ņ‚ĐžĐģҌĐēĐž в ĐģĐ¸Ņ‡ĐŊОК ĐŋаĐŋĐēĐĩ", + "move_up": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ ĐŊавĐĩҀ҅", + "moved_to_archive": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ґĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹}} в Đ°Ņ€Ņ…Đ¸Đ˛", + "moved_to_library": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ґĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊŅ‹}} в йийĐģĐ¸ĐžŅ‚ĐĩĐē҃", "moved_to_trash": "ПĐĩŅ€ĐĩĐŊĐĩҁĐĩĐŊĐž в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", + "multiselect_grid_edit_date_time_err_read_only": "НĐĩвОСĐŧĐžĐļĐŊĐž иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ´Đ°Ņ‚Ņƒ Ņ„Đ°ĐšĐģОв Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ҇҂ĐĩĐŊĐ¸Ņ, ĐŋŅ€ĐžĐŋ҃ҁĐē", + "multiselect_grid_edit_gps_err_read_only": "НĐĩвОСĐŧĐžĐļĐŊĐž иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ Ņ„Đ°ĐšĐģОв Ņ‚ĐžĐģҌĐēĐž Đ´ĐģŅ ҇҂ĐĩĐŊĐ¸Ņ, ĐŋŅ€ĐžĐŋ҃ҁĐē", "mute_memories": "ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ĐˇĐ˛ŅƒĐē", "my_albums": "Мои аĐģŅŒĐąĐžĐŧŅ‹", - "my_immich_description": "КоĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Đ°Đ´Ņ€Đĩҁ Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐš ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņ‹ в ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đĩ ҁҁҋĐģĐēи ĐŊа Мой Immich", - "my_immich_title": "ĐĄŅŅ‹ĐģĐēа Мой Immich", "name": "ИĐŧŅ", "name_or_nickname": "ИĐŧŅ иĐģи ĐŊиĐē", "name_required": "ИĐŧŅ ĐžĐąŅĐˇĐ°Ņ‚ĐĩĐģҌĐŊĐž Đ´ĐģŅ СаĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ", @@ -1480,11 +1576,12 @@ "never": "ĐŊиĐēĐžĐŗĐ´Đ°", "new_album": "ĐĐžĐ˛Ņ‹Đš аĐģŅŒĐąĐžĐŧ", "new_api_key": "ĐĐžĐ˛Ņ‹Đš API ĐēĐģŅŽŅ‡", - "new_feature": "ĐĐžĐ˛Đ°Ņ Ņ„ŅƒĐŊĐēŅ†Đ¸ĐžĐŊаĐģҌĐŊĐžŅŅ‚ŅŒ", + "new_date_range": "ĐĐžĐ˛Ņ‹Đš диаĐŋаСОĐŊ Đ´Đ°Ņ‚", "new_password": "ĐĐžĐ˛Ņ‹Đš ĐŋĐ°Ņ€ĐžĐģҌ", "new_person": "ĐĐžĐ˛Ņ‹Đš ҇ĐĩĐģОвĐĩĐē", "new_pin_code": "ĐĐžĐ˛Ņ‹Đš PIN-ĐēОд", "new_pin_code_subtitle": "Đ­Ņ‚Đž Đ˛Đ°Ņˆ ĐŋĐĩŅ€Đ˛Ņ‹Đš Đ´ĐžŅŅ‚ŅƒĐŋ Đē ĐģĐ¸Ņ‡ĐŊОК ĐŋаĐŋĐēĐĩ. ĐĄĐžĐˇĐ´Đ°ĐšŅ‚Đĩ PIN-ĐēОд Đ´ĐģŅ ĐˇĐ°Ņ‰Đ¸Ņ‰ĐĩĐŊĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа Đē ŅŅ‚ĐžĐš ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đĩ.", + "new_timeline": "ĐĐžĐ˛Đ°Ņ ĐģĐĩĐŊŅ‚Đ°", "new_update": "НовоĐĩ ОйĐŊОвĐģĐĩĐŊиĐĩ", "new_user_created": "ĐĐžĐ˛Ņ‹Đš ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ ŅĐžĐˇĐ´Đ°ĐŊ", "new_version_available": "ДОСĐĸĐŖĐŸĐĐ ĐĐžĐ’ĐĐ¯ Đ’Đ•Đ ĐĄĐ˜Đ¯", @@ -1492,6 +1589,7 @@ "next": "ДаĐģĐĩĐĩ", "next_memory": "ĐĄĐģĐĩĐ´ŅƒŅŽŅ‰ĐĩĐĩ Đ˛ĐžŅĐŋĐžĐŧиĐŊаĐŊиĐĩ", "no": "НĐĩŅ‚", + "no_actions_added": "ДĐĩĐšŅŅ‚Đ˛Đ¸Đš ĐŋĐžĐēа ĐŊĐĩ дОйавĐģĐĩĐŊĐž", "no_albums_found": "АĐģŅŒĐąĐžĐŧОв ĐŊĐĩ ĐŊаКдĐĩĐŊĐž", "no_albums_message": "ĐĄĐžĐˇĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ аĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ ŅĐ¸ŅŅ‚ĐĩĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Đ¸Đ¸ Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš и видĐĩĐž", "no_albums_with_name_yet": "ĐŸĐžŅ…ĐžĐļĐĩ, ҃ Đ˛Đ°Ņ ĐŋĐžĐēа ĐŊĐĩŅ‚ аĐģŅŒĐąĐžĐŧОв ҁ Ņ‚Đ°ĐēиĐŧ ĐŊаСваĐŊиĐĩĐŧ.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "НĐĩ ĐŊаКдĐĩĐŊĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛ Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Đ¸Đ¸", "no_checksum_local": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊŅ‹Đĩ ҁ҃ĐŧĐŧŅ‹ ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‚ - ĐŊĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ", "no_checksum_remote": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊŅ‹Đĩ ҁ҃ĐŧĐŧŅ‹ ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‚ - ĐŊĐĩвОСĐŧĐžĐļĐŊĐž ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", + "no_configuration_needed": "КоĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸Ņ ĐŊĐĩ ҂ҀĐĩĐąŅƒĐĩŅ‚ŅŅ", "no_devices": "НĐĩŅ‚ Đ°Đ˛Ņ‚ĐžŅ€Đ¸ĐˇĐžĐ˛Đ°ĐŊĐŊҋ҅ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛", "no_duplicates_found": "Đ”ŅƒĐąĐģиĐēĐ°Ņ‚ĐžĐ˛ ĐŊĐĩ ОйĐŊĐ°Ņ€ŅƒĐļĐĩĐŊĐž.", "no_exif_info_available": "НĐĩŅ‚ Đ´ĐžŅŅ‚ŅƒĐŋĐŊОК иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Đ¸ exif", "no_explore_results_message": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°ĐšŅ‚Đĩ йОĐģҌ҈Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš, Ņ‡Ņ‚ĐžĐąŅ‹ ĐŊĐ°ŅĐģаĐļĐ´Đ°Ņ‚ŅŒŅŅ Đ˛Đ°ŅˆĐĩĐš ĐēĐžĐģĐģĐĩĐēŅ†Đ¸ĐĩĐš.", "no_favorites_message": "ДобавĐģŅĐšŅ‚Đĩ ĐžĐąŅŠĐĩĐē҂ҋ в Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ, Ņ‡Ņ‚ĐžĐąŅ‹ ĐąŅ‹ŅŅ‚Ņ€ĐĩĐĩ ĐŊĐ°Ņ…ĐžĐ´Đ¸Ņ‚ŅŒ ŅĐ˛ĐžĐ¸ ĐģŅƒŅ‡ŅˆĐ¸Đĩ Ņ„ĐžŅ‚Đž и видĐĩĐž", + "no_filters_added": "ФиĐģŅŒŅ‚Ņ€ĐžĐ˛ ĐŋĐžĐēа ĐŊĐĩ дОйавĐģĐĩĐŊĐž", "no_libraries_message": "ĐĄĐžĐˇĐ´Đ°ĐšŅ‚Đĩ вĐŊĐĩ҈ĐŊŅŽŅŽ йийĐģĐ¸ĐžŅ‚ĐĩĐē҃ Đ´ĐģŅ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° в Immich ŅŅ‚ĐžŅ€ĐžĐŊĐŊĐ¸Ņ… Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš и видĐĩĐž", "no_local_assets_found": "На ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ ĐŊĐĩ ĐŊаКдĐĩĐŊĐž ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ҁ Ņ‚Đ°ĐēОК ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊОК ҁ҃ĐŧĐŧОК", + "no_location_set": "МĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ ĐŊĐĩ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐž", "no_locked_photos_message": "Đ¤ĐžŅ‚Đž и видĐĩĐž, ĐŋĐĩŅ€ĐĩĐŧĐĩ҉ĐĩĐŊĐŊŅ‹Đĩ в ĐģĐ¸Ņ‡ĐŊŅƒŅŽ ĐŋаĐŋĐē҃, ҁĐēҀҋ҂ҋ и ĐŊĐĩ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°ŅŽŅ‚ŅŅ ĐŋŅ€Đ¸ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēи.", "no_name": "НĐĩŅ‚ иĐŧĐĩĐŊи", "no_notifications": "НĐĩŅ‚ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК", @@ -1518,19 +1619,14 @@ "no_results": "НĐĩŅ‚ Ņ€ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚ĐžĐ˛", "no_results_description": "ПоĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ŅĐ¸ĐŊĐžĐŊиĐŧŅ‹ иĐģи йОĐģĐĩĐĩ ĐžĐąŅ‰Đ¸Đĩ ҁĐģОва", "no_shared_albums_message": "ĐĄĐžĐˇĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ аĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ ОйĐŧĐĩĐŊа Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅĐŧи и видĐĩОСаĐŋĐ¸ŅŅĐŧи ҁ ĐģŅŽĐ´ŅŒĐŧи в Đ˛Đ°ŅˆĐĩĐš ҁĐĩŅ‚Đ¸", - "no_steps": "ПоĐēа ŅˆĐ°ĐŗĐžĐ˛ ĐŊĐĩ дОйавĐģĐĩĐŊĐž", + "no_uploads_in_progress": "НĐĩŅ‚ аĐēŅ‚Đ¸Đ˛ĐŊҋ҅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐžĐē", "none": "ĐĐ¸Ņ‡ĐĩĐŗĐž", "not_allowed": "ЗаĐŋŅ€Đĩ҉ĐĩĐŊĐž", "not_available": "НĐĩŅ‚ даĐŊĐŊҋ҅", "not_in_any_album": "Ни в ОдĐŊĐžĐŧ аĐģŅŒĐąĐžĐŧĐĩ", "not_selected": "НĐĩ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐž", - "not_set": "НĐĩ СадаĐŊĐž", "notes": "ĐŸŅ€Đ¸ĐŧĐĩŅ‡Đ°ĐŊиĐĩ", "nothing_here_yet": "ЗдĐĩҁҌ ĐŋĐžĐēа ĐŊĐ¸Ņ‡ĐĩĐŗĐž ĐŊĐĩŅ‚", - "notification_backup_reliability": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ Đ´ĐģŅ ĐŋĐžĐ˛Ņ‹ŅˆĐĩĐŊĐ¸Ņ ĐŊадĐĩĐļĐŊĐžŅŅ‚Đ¸ Ņ„ĐžĐŊОвОК ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", - "notification_enabled_list_tile_content": "Immich Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐĩŅ‚ ĐŧĐĩŅ…Đ°ĐŊиСĐŧ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК Đ´ĐģŅ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ Ņ„ĐžĐŊĐžĐ˛Ņ‹Ņ… ĐˇĐ°ĐŗŅ€ŅƒĐˇĐžĐē. ĐŖĐŋŅ€Đ°Đ˛ĐģŅĐšŅ‚Đĩ иĐŧи в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ°Ņ… Đ˛Đ°ŅˆĐĩĐŗĐž ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°.", - "notification_enabled_list_tile_open_button": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", - "notification_enabled_list_tile_title": "ĐŖĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ вĐēĐģŅŽŅ‡ĐĩĐŊŅ‹", "notification_permission_dialog_content": "Đ§Ņ‚ĐžĐąŅ‹ вĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ, ĐŋĐĩŅ€ĐĩĐšĐ´Đ¸Ņ‚Đĩ в ÂĢĐĐ°ŅŅ‚Ņ€ĐžĐšĐēиÂģ и Đ˛Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ÂĢĐ Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚ŅŒÂģ.", "notification_permission_list_tile_content": "ĐŸŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ŅŒŅ‚Đĩ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ĐŊа ĐŋĐžĐēаС ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК.", "notification_permission_list_tile_enable_button": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊĐ¸Ņ", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа Obtainium", "obtainium_configurator_instructions": "ДĐģŅ ŅƒŅŅ‚Đ°ĐŊОвĐēи и ОйĐŊОвĐģĐĩĐŊĐ¸Ņ Android ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ Immich ĐŊаĐŋŅ€ŅĐŧŅƒŅŽ иС Đ¸ŅŅ‚ĐžŅ‡ĐŊиĐēОв ĐŊа GitHub (ĐŧиĐŊŅƒŅ ĐŧĐ°ĐŗĐ°ĐˇĐ¸ĐŊŅ‹ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиК) ĐŧĐžĐļĐŊĐž Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ Obtainium. ĐĄĐžĐˇĐ´Đ°ĐšŅ‚Đĩ ĐŊĐžĐ˛Ņ‹Đš API ĐēĐģŅŽŅ‡ и ҃ĐēаĐļĐ¸Ņ‚Đĩ Đ°Ņ€Ņ…Đ¸Ņ‚ĐĩĐēŅ‚ŅƒŅ€Ņƒ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ Đ´ĐģŅ Ņ„ĐžŅ€ĐŧĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ ҁҁҋĐģĐēи Đ´ĐģŅ Obtainium.", "ocr": "ĐĸĐĩĐēҁ҂ (OCR)", - "ocr_body": "Immich ĐŊĐ°ŅƒŅ‡Đ¸ĐģŅŅ Ņ€Đ°ŅĐŋОСĐŊĐ°Đ˛Đ°Ņ‚ŅŒ Ņ‚ĐĩĐēҁ҂ ĐŊа Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸ŅŅ…, Ņ‚ĐĩĐŋĐĩŅ€ŅŒ ĐŋĐžĐ¸ŅĐē вОСĐŧĐžĐļĐĩĐŊ и ĐŋĐž ŅŅ‚ĐžĐŧ҃ ŅĐžĐ´ĐĩŅ€ĐļиĐŧĐžĐŧ҃.", - "ocr_title": "ĐŸĐžĐ¸ŅĐē Ņ‚ĐĩĐēŅŅ‚Đ° ĐŊа Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅŅ…", "official_immich_resources": "ĐžŅ„Đ¸Ņ†Đ¸Đ°ĐģҌĐŊŅ‹Đĩ Ņ€ĐĩŅŅƒŅ€ŅŅ‹ Immich", "offline": "НĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐĩĐŊ", "offset": "ĐĄĐŧĐĩ҉ĐĩĐŊиĐĩ", @@ -1562,8 +1656,6 @@ "open": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ", "open_calendar": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ ĐēаĐģĐĩĐŊĐ´Đ°Ņ€ŅŒ", "open_in_browser": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ в ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đĩ", - "open_in_immich_body": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đĩ Immich в ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đĩ ĐŗĐ°ĐģĐĩŅ€Đĩи ĐŋĐž ҃ĐŧĐžĐģŅ‡Đ°ĐŊĐ¸ŅŽ ĐŊа Android, Ņ‡Ņ‚ĐžĐąŅ‹ ĐžŅ‚ĐēŅ€Ņ‹Đ˛Đ°Ņ‚ŅŒ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸ в Immich иС Đ´Ņ€ŅƒĐŗĐ¸Ņ… ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиК.", - "open_in_immich_title": "ĐžŅ‚ĐēŅ€Ņ‹Đ˛Đ°Ņ‚ŅŒ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸ в Immich", "open_in_map_view": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ в Ņ€ĐĩĐļиĐŧĐĩ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° ĐēĐ°Ņ€Ņ‚Ņ‹", "open_in_openstreetmap": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ в OpenStreetMap", "open_the_search_filters": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ Ņ„Đ¸ĐģŅŒŅ‚Ņ€Ņ‹ ĐŋĐžĐ¸ŅĐēа", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Đ Đ°ŅĐŋŅ€ĐĩĐ´ĐĩĐģĐ¸Ņ‚ŅŒ ĐŋĐž аĐģŅŒĐąĐžĐŧаĐŧ", "organize_into_albums_description": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ҃ĐļĐĩ ŅŅƒŅ‰ĐĩŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ ĐžĐąŅŠĐĩĐē҂ҋ в аĐģŅŒĐąĐžĐŧŅ‹, Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒŅ Ņ‚ĐĩĐēŅƒŅ‰Đ¸Đĩ ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸", "organize_your_library": "ĐŸŅ€Đ¸Đ˛ĐĩĐ´Đ¸Ņ‚Đĩ в ĐŋĐžŅ€ŅĐ´ĐžĐē ŅĐ˛ĐžŅŽ йийĐģĐ¸ĐžŅ‚ĐĩĐē҃", - "orientation": "ĐžŅ€Đ¸ĐĩĐŊŅ‚Đ°Ņ†Đ¸Ņ", "original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ", "other": "Đ”Ņ€ŅƒĐŗĐžĐĩ", "other_devices": "Đ”Ņ€ŅƒĐŗĐ¸Đĩ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ°", + "other_entities": "Đ”Ņ€ŅƒĐŗĐ¸Đĩ ĐžĐąŅŠĐĩĐē҂ҋ", "other_variables": "Đ”Ņ€ŅƒĐŗĐ¸Đĩ ĐŋĐĩŅ€ĐĩĐŧĐĩĐŊĐŊŅ‹Đĩ", "owned": "Мои", "owner": "ВĐģадĐĩĐģĐĩ҆", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Đ’ŅĐĩ Đ˛Đ°ŅˆĐ¸ Ņ„ĐžŅ‚Đž и видĐĩĐž, ĐēŅ€ĐžĐŧĐĩ Ņ‚ĐĩŅ…, Ņ‡Ņ‚Đž ĐŊĐ°Ņ…ĐžĐ´ŅŅ‚ŅŅ в Đ°Ņ€Ņ…Đ¸Đ˛Đĩ и ĐēĐžŅ€ĐˇĐ¸ĐŊĐĩ", "partner_can_access_location": "МĐĩŅŅ‚Đ°, ĐŗĐ´Đĩ ĐąŅ‹Đģи ŅĐ´ĐĩĐģаĐŊŅ‹ Đ˛Đ°ŅˆĐ¸ Ņ„ĐžŅ‚Đž и видĐĩĐž", "partner_list_user_photos": "Đ¤ĐžŅ‚Đž и видĐĩĐž ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ {user}", + "partner_list_view_all": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ Đ˛ŅĐĩ", "partner_page_empty_message": "Đ’Ņ‹ ĐŋĐžĐēа ĐŊиĐēĐžĐŧ҃ иС ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€ĐžĐ˛ ĐŊĐĩ ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛Đ¸Đģи Đ´ĐžŅŅ‚ŅƒĐŋ Đē ŅĐ˛ĐžĐ¸Đŧ Ņ„ĐžŅ‚Đž и видĐĩĐž.", "partner_page_no_more_users": "Đ’Ņ‹ĐąŅ€Đ°ĐŊŅ‹ Đ˛ŅĐĩ Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹Đĩ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģи", + "partner_page_partner_add_failed": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ Đ´ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", "partner_page_select_partner": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Đ°", + "partner_page_shared_to_title": "ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ ҁ...", "partner_page_stop_sharing_content": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ {partner} йОĐģҌ҈Đĩ ĐŊĐĩ ĐąŅƒĐ´ĐĩŅ‚ иĐŧĐĩŅ‚ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ Đē Đ˛Đ°ŅˆĐ¸Đŧ Ņ„ĐžŅ‚Đž и видĐĩĐž.", "partner_sharing": "ХОвĐŧĐĩҁ҂ĐŊĐžĐĩ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ", "partners": "ĐŸĐ°Ņ€Ņ‚ĐŊґҀҋ", @@ -1609,6 +1704,8 @@ "people": "Đ›ŅŽĐ´Đ¸", "people_edits_count": "{count, plural, one {ИСĐŧĐĩĐŊŅ‘ĐŊ # ҇ĐĩĐģОвĐĩĐē} many {ИСĐŧĐĩĐŊĐĩĐŊĐž # ҇ĐĩĐģОвĐĩĐē} other {ИСĐŧĐĩĐŊĐĩĐŊĐž # ҇ĐĩĐģОвĐĩĐēа}}", "people_feature_description": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ Ņ„ĐžŅ‚Đž и видĐĩĐž, ŅĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊҋ҅ ĐŋĐž ĐģŅŽĐ´ŅĐŧ", + "people_selected": "{count, plural, one {Đ’Ņ‹ĐąŅ€Đ°ĐŊ # ҇ĐĩĐģОвĐĩĐē} many {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # ҇ĐĩĐģОвĐĩĐē} other {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # ҇ĐĩĐģОвĐĩĐēа}}", + "people_sidebar_description": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ Đŋ҃ĐŊĐēŅ‚ ĐŧĐĩĐŊŅŽ \"Đ›ŅŽĐ´Đ¸\" в йОĐēОвОК ĐŋаĐŊĐĩĐģи", "permanent_deletion_warning": "ĐŸŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ Ой ŅƒĐ´Đ°ĐģĐĩĐŊии", "permanent_deletion_warning_setting_description": "ĐŸŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´Đ°Ņ‚ŅŒ ĐŋĐĩŅ€ĐĩĐ´ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊŅ‹Đŧ ŅƒĐ´Đ°ĐģĐĩĐŊиĐĩĐŧ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "permanently_delete": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŊĐ°Đ˛ŅĐĩĐŗĐ´Đ°", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģŅ‘ĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹}}", "permission": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ", "permission_empty": "Đ Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ ĐŊĐĩ ĐŧĐžĐŗŅƒŅ‚ ĐąŅ‹Ņ‚ŅŒ ĐŋŅƒŅŅ‚Ņ‹Đŧи", + "permission_onboarding_back": "Назад", + "permission_onboarding_continue_anyway": "Đ’ŅĐĩ Ņ€Đ°Đ˛ĐŊĐž ĐŋŅ€ĐžĐ´ĐžĐģĐļĐ¸Ņ‚ŅŒ", + "permission_onboarding_get_started": "Đ”Đ°Đ˛Đ°ĐšŅ‚Đĩ ĐŊĐ°Ņ‡ĐŊŅ‘Đŧ", + "permission_onboarding_go_to_settings": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēи", + "permission_onboarding_permission_denied": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ĐŋĐžĐģŅƒŅ‡Đ¸Ņ‚ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ. Đ§Ņ‚ĐžĐąŅ‹ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ, Ņ€Đ°ĐˇŅ€ĐĩŅˆĐ¸Ņ‚Đĩ Đ´ĐžŅŅ‚ŅƒĐŋ Đē \"Đ¤ĐžŅ‚Đž и видĐĩĐž\" в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ°Ņ….", + "permission_onboarding_permission_granted": "Đ”ĐžŅŅ‚ŅƒĐŋ ĐŋĐžĐģŅƒŅ‡ĐĩĐŊ! Đ’ŅŅ‘ ĐŗĐžŅ‚ĐžĐ˛Đž.", + "permission_onboarding_permission_limited": "Đ”ĐžŅŅ‚ŅƒĐŋ Đē Ņ„Đ°ĐšĐģаĐŧ ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊ. Đ§Ņ‚ĐžĐąŅ‹ Immich ĐŧĐžĐŗ ŅĐžĐˇĐ´Đ°Đ˛Đ°Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅ‹Đĩ ĐēĐžĐŋии и ҃ĐŋŅ€Đ°Đ˛ĐģŅŅ‚ŅŒ Đ˛Đ°ŅˆĐĩĐš ĐŗĐ°ĐģĐĩŅ€ĐĩĐĩĐš, ĐŋĐžĐļаĐģŅƒĐšŅŅ‚Đ°, ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛ŅŒŅ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸ŅŽ Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ĐŊа Đ´ĐžŅŅ‚ŅƒĐŋ Đē \"Đ¤ĐžŅ‚Đž и видĐĩĐž\" в ĐŊĐ°ŅŅ‚Ņ€ĐžĐšĐēĐ°Ņ….", + "permission_onboarding_request": "ĐŸŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸ŅŽ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐž Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊиĐĩ ĐŊа Đ´ĐžŅŅ‚ŅƒĐŋ Đē Đ˛Đ°ŅˆĐ¸Đŧ Ņ„ĐžŅ‚Đž и видĐĩĐž.", "person": "ЧĐĩĐģОвĐĩĐē", "person_age_months": "{months, plural, one {# ĐŧĐĩŅŅŅ†} many {# ĐŧĐĩŅŅŅ†Đĩв} other {# ĐŧĐĩŅŅŅ†Đ°}}", "person_age_year_months": "1 ĐŗĐžĐ´, {months, plural, one {# ĐŧĐĩŅŅŅ†} many {# ĐŧĐĩŅŅŅ†Đĩв} other {# ĐŧĐĩŅŅŅ†Đ°}}", @@ -1625,10 +1730,13 @@ "person_birthdate": "Đ”Đ°Ņ‚Đ° Ņ€ĐžĐļĐ´ĐĩĐŊĐ¸Ņ: {date}", "person_hidden": "{name}{hidden, select, true { (ҁĐēҀҋ҂)} other {}}", "person_recognized": "ЧĐĩĐģОвĐĩĐē Ņ€Đ°ŅĐŋОСĐŊаĐŊ", + "person_selected": "ЧĐĩĐģОвĐĩĐē Đ˛Ņ‹ĐąŅ€Đ°ĐŊ", "photo_shared_all_users": "ĐŸĐžŅ…ĐžĐļĐĩ, Ņ‡Ņ‚Đž Đ˛Ņ‹ ĐŋОдĐĩĐģиĐģĐ¸ŅŅŒ ŅĐ˛ĐžĐ¸Đŧи Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅĐŧи ŅĐž Đ˛ŅĐĩĐŧи ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅĐŧи иĐģи ҃ Đ˛Đ°Ņ ĐŊĐĩŅ‚ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš, ҁ ĐēĐžŅ‚ĐžŅ€Ņ‹Đŧи ĐŧĐžĐļĐŊĐž ĐŋОдĐĩĐģĐ¸Ņ‚ŅŒŅŅ.", "photos": "Đ¤ĐžŅ‚Đž", "photos_and_videos": "Đ¤ĐžŅ‚Đž и видĐĩĐž", + "photos_count": "{count, plural, one {{count, number} Ņ„ĐžŅ‚Đž} other {{count, number} Ņ„ĐžŅ‚Đž}}", "photos_from_previous_years": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸ ĐŋŅ€ĐžŅˆĐģҋ҅ ĐģĐĩŅ‚ в ŅŅ‚ĐžŅ‚ Đ´ĐĩĐŊҌ", + "photos_only": "ĐĸĐžĐģҌĐēĐž Ņ„ĐžŅ‚Đž", "pick_a_location": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊиĐĩ", "pick_custom_range": "ĐŸŅ€ĐžĐ¸ĐˇĐ˛ĐžĐģҌĐŊŅ‹Đš ĐŋĐĩŅ€Đ¸ĐžĐ´", "pick_date_range": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐŋĐĩŅ€Đ¸ĐžĐ´", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "ĐŸŅ€ĐžĐ¸ĐŗŅ€Ņ‹Đ˛Đ°Ņ‚ŅŒ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģҌĐŊŅ‹Đĩ видĐĩĐž вĐŧĐĩŅŅ‚Đž ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊҋ҅. Đ•ŅĐģи Đ¸ŅŅ…ĐžĐ´ĐŊŅ‹Đš Ņ„ĐžŅ€ĐŧĐ°Ņ‚ ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ, видĐĩĐž ĐŧĐžĐļĐĩŅ‚ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ŅŒŅŅ ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊĐž.", "play_transcoded_video": "Đ’ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩŅŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊĐžĐĩ видĐĩĐž", "please_auth_to_access": "ПоĐļаĐģŅƒĐšŅŅ‚Đ°, Đ°Đ˛Ņ‚ĐžŅ€Đ¸ĐˇŅƒĐšŅ‚ĐĩҁҌ", - "plugin_method_filter_type": "ĐžŅ‚ĐąĐžŅ€", - "plugin_method_filter_type_description": "Đ­Ņ‚ĐžŅ‚ ĐŧĐĩŅ‚ĐžĐ´ ĐŋОСвОĐģŅĐĩŅ‚ ĐžŅ‚ĐąĐ¸Ņ€Đ°Ņ‚ŅŒ ĐŊ҃ĐļĐŊŅ‹Đĩ ŅĐžĐąŅ‹Ņ‚Đ¸Ņ и ĐžĐŋŅ€ĐĩĐ´ĐĩĐģŅŅ‚ŅŒ ĐŊĐĩĐžĐąŅ…ĐžĐ´Đ¸ĐŧĐžŅŅ‚ŅŒ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ ĐŋĐžŅĐģĐĩĐ´ŅƒŅŽŅ‰Đ¸Ņ… ŅˆĐ°ĐŗĐžĐ˛", "port": "ĐŸĐžŅ€Ņ‚", "preferences_settings_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа вĐŊĐĩ҈ĐŊĐĩĐŗĐž вида", "preferences_settings_title": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ", @@ -1665,11 +1771,11 @@ "privacy": "КоĐŊŅ„Đ¸Đ´ĐĩĐŊŅ†Đ¸Đ°ĐģҌĐŊĐžŅŅ‚ŅŒ", "profile": "ĐŸŅ€ĐžŅ„Đ¸ĐģҌ", "profile_drawer_app_logs": "Đ–ŅƒŅ€ĐŊаĐģ ŅĐžĐąŅ‹Ņ‚Đ¸Đš", + "profile_drawer_client_server_up_to_date": "КĐģиĐĩĐŊŅ‚ и ҁĐĩŅ€Đ˛ĐĩŅ€ ОйĐŊОвĐģĐĩĐŊŅ‹", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "ВĐēĐģŅŽŅ‡Ņ‘ĐŊ Ņ€ĐĩĐļиĐŧ ÂĢŅ‚ĐžĐģҌĐēĐž ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Âģ. ĐŖĐ´ĐĩŅ€ĐļĐ¸Đ˛Đ°ĐšŅ‚Đĩ СĐŊĐ°Ņ‡ĐžĐē Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ° ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ Đ´ĐģŅ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊĐ¸Ņ.", "profile_image_of_user": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ ĐŋŅ€ĐžŅ„Đ¸ĐģŅ {user}", "profile_picture_set": "Đ¤ĐžŅ‚Đž ĐŋŅ€ĐžŅ„Đ¸ĐģŅ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐž.", - "projection_type": "Вид ĐŋŅ€ĐžĐĩĐēŅ†Đ¸Đ¸", "public_album": "ĐžĐąŅ‰Đ¸Đš аĐģŅŒĐąĐžĐŧ", "public_share": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊŅ‹Đš Đ´ĐžŅŅ‚ŅƒĐŋ", "purchase_account_info": "ПоддĐĩŅ€ĐļĐēа", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "ĐĄĐžŅŅ‚ĐžŅĐŊиĐĩ ĐŋОддĐĩŅ€ĐļĐēи", "purchase_individual_title": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģҌĐŊŅ‹Đš", "purchase_input_suggestion": "ĐŖ Đ˛Đ°Ņ ĐĩŅŅ‚ŅŒ ĐēĐģŅŽŅ‡ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚Đ°? ВвĐĩĐ´Đ¸Ņ‚Đĩ ŅŅ‚ĐžŅ‚ ĐēĐģŅŽŅ‡ ĐŊиĐļĐĩ", + "purchase_license_subtitle": "ĐŸŅ€Đ¸ĐžĐąŅ€ĐĩŅ‚Đ¸Ņ‚Đĩ Immich, Ņ‡Ņ‚ĐžĐąŅ‹ ĐŋОддĐĩŅ€ĐļĐ°Ņ‚ŅŒ даĐģҌĐŊĐĩĐšŅˆĐĩĐĩ Ņ€Đ°ĐˇĐ˛Đ¸Ņ‚Đ¸Đĩ ҁĐĩŅ€Đ˛Đ¸ŅĐ°", "purchase_lifetime_description": "ЕдиĐŊĐžĐ˛Ņ€ĐĩĐŧĐĩĐŊĐŊĐ°Ņ ĐŋĐžĐē҃ĐŋĐēа", "purchase_option_title": "Đ’Đ°Ņ€Đ¸Đ°ĐŊ҂ҋ ĐŋĐžĐē҃ĐŋĐēи", "purchase_panel_info_1": "ХОСдаĐŊиĐĩ Immich ĐžŅ‚ĐŊиĐŧаĐĩŅ‚ ĐŧĐŊĐžĐŗĐž Đ˛Ņ€ĐĩĐŧĐĩĐŊи и ŅƒŅĐ¸ĐģиК, и ҃ ĐŊĐ°Ņ ĐĩŅŅ‚ŅŒ ŅˆŅ‚Đ°Ņ‚ĐŊŅ‹Đĩ Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚Ņ‡Đ¸Đēи, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ Ņ€Đ°ĐąĐžŅ‚Đ°ŅŽŅ‚ ĐŊад Ņ‚ĐĩĐŧ, Ņ‡Ņ‚ĐžĐąŅ‹ ŅĐ´ĐĩĐģĐ°Ņ‚ŅŒ ĐĩĐŗĐž ĐŊĐ°ŅŅ‚ĐžĐģҌĐēĐž Ņ…ĐžŅ€ĐžŅˆĐ¸Đŧ, ĐŊĐ°ŅĐēĐžĐģҌĐēĐž ŅŅ‚Đž вОСĐŧĐžĐļĐŊĐž. ĐĐ°ŅˆĐ° ĐŧĐ¸ŅŅĐ¸Ņ СаĐēĐģŅŽŅ‡Đ°ĐĩŅ‚ŅŅ в Ņ‚ĐžĐŧ, Ņ‡Ņ‚ĐžĐąŅ‹ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐŧĐŊĐžĐĩ ОйĐĩҁĐŋĐĩ҇ĐĩĐŊиĐĩ ҁ ĐžŅ‚ĐēҀҋ҂ҋĐŧ Đ¸ŅŅ…ĐžĐ´ĐŊŅ‹Đŧ ĐēОдОĐŧ и ŅŅ‚Đ¸Ņ‡ĐĩҁĐēиĐĩ ĐŧĐĩŅ‚ĐžĐ´Ņ‹ вĐĩĐ´ĐĩĐŊĐ¸Ņ йиСĐŊĐĩŅĐ° ŅŅ‚Đ°Đģи ŅƒŅŅ‚ĐžĐšŅ‡Đ¸Đ˛Ņ‹Đŧ Đ¸ŅŅ‚ĐžŅ‡ĐŊиĐēĐžĐŧ Đ´ĐžŅ…ĐžĐ´Đ° Đ´ĐģŅ Ņ€Đ°ĐˇŅ€Đ°ĐąĐžŅ‚Ņ‡Đ¸ĐēОв и Ņ‡Ņ‚ĐžĐąŅ‹ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ ŅĐēĐžŅĐ¸ŅŅ‚ĐĩĐŧ҃, ŅƒĐ˛Đ°ĐļĐ°ŅŽŅ‰ŅƒŅŽ ĐēĐžĐŊŅ„Đ¸Đ´ĐĩĐŊŅ†Đ¸Đ°ĐģҌĐŊĐžŅŅ‚ŅŒ, ҁ Ņ€ĐĩаĐģҌĐŊŅ‹Đŧи аĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛Đ°Đŧи ŅĐēҁĐŋĐģŅƒĐ°Ņ‚Đ°Ņ‚ĐžŅ€ŅĐēиĐŧ ОйĐģĐ°Ņ‡ĐŊŅ‹Đŧ ҁĐĩŅ€Đ˛Đ¸ŅĐ°Đŧ.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Đ›Đ¸Ņ†Đ° ĐŊа {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚Đĩ} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°Ņ…}} ĐŋĐĩŅ€ĐĩĐŊаСĐŊĐ°Ņ‡ĐĩĐŊŅ‹ ĐŊа ĐŊĐžĐ˛ĐžĐŗĐž ҇ĐĩĐģОвĐĩĐēа", "reassing_hint": "НазĐŊĐ°Ņ‡Đ¸Ņ‚ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ҃ĐēаСаĐŊĐŊĐžĐŧ҃ ҇ĐĩĐģОвĐĩĐē҃", "recent": "НĐĩдавĐŊиĐĩ", + "recent_albums": "НĐĩдавĐŊиĐĩ аĐģŅŒĐąĐžĐŧŅ‹", "recent_searches": "НĐĩдавĐŊиĐĩ ĐŋĐžĐ¸ŅĐēĐžĐ˛Ņ‹Đĩ СаĐŋŅ€ĐžŅŅ‹", "recently_added": "НĐĩдавĐŊĐž дОйавĐģĐĩĐŊĐŊŅ‹Đĩ", - "recently_added_body": "ПĐĩŅ€ĐĩŅ…ĐžĐ´ Đē ĐžŅ‚Đ´ĐĩĐģҌĐŊОК ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Đĩ ҁ ĐŊĐĩдавĐŊĐž дОйавĐģĐĩĐŊĐŊŅ‹Đŧи ĐžĐąŅŠĐĩĐēŅ‚Đ°Đŧи.", - "recently_added_description": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛, ĐžŅ‚ŅĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊҋ҅ ĐŋĐž Đ´Đ°Ņ‚Đĩ Đ¸Ņ… ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи в Immich", "recently_added_page_title": "НĐĩдавĐŊĐž дОйавĐģĐĩĐŊĐŊŅ‹Đĩ", - "recently_added_title": "НĐĩдавĐŊĐž дОйавĐģĐĩĐŊĐŊŅ‹Đĩ", "recently_taken": "НĐĩдавĐŊĐž ҁĐŊŅŅ‚Đž", + "recently_taken_page_title": "НĐĩдавĐŊĐž ĐĄĐŊŅŅ‚Đž", "refresh": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ", - "refresh_encoded_videos": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŋĐĩŅ€ĐĩĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊŅ‹Đĩ видĐĩĐž", + "refresh_encoded_videos": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ СаĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊŅ‹Đĩ видĐĩĐž", "refresh_faces": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐģĐ¸Ņ†Đ°", "refresh_metadata": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊŅ‹Đĩ", "refresh_thumbnails": "ОбĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņ‹", "refreshed": "ОбĐŊОвĐģĐĩĐŊĐž", + "refreshes_every_file": "ОбĐŊОвĐģŅĐĩŅ‚ Đ˛ŅĐĩ ŅŅƒŅ‰ĐĩŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ и ĐŊĐžĐ˛Ņ‹Đĩ Ņ„Đ°ĐšĐģŅ‹", "refreshing_encoded_video": "ОбĐŊОвĐģĐĩĐŊиĐĩ СаĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊĐžĐŗĐž видĐĩĐž", "refreshing_faces": "ОбĐŊОвĐģĐĩĐŊиĐĩ ĐģĐ¸Ņ†", "refreshing_metadata": "ОбĐŊОвĐģĐĩĐŊиĐĩ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊҋ҅", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ°}} иС ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа ĐŋĐž ŅŅ‚ĐžĐš ҁҁҋĐģĐēĐĩ?", "remove_assets_title": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐē҂ҋ?", "remove_custom_date_range": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌҁĐēиК диаĐŋаСОĐŊ Đ´Đ°Ņ‚", - "remove_filter": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ„Đ¸ĐģŅŒŅ‚Ņ€", + "remove_deleted_assets": "ĐŖĐ´Đ°ĐģĐĩĐŊиĐĩ Đ°Đ˛Ņ‚ĐžĐŊĐžĐŧĐŊҋ҅ Ņ„Đ°ĐšĐģОв", "remove_from_album": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ иС аĐģŅŒĐąĐžĐŧа", "remove_from_album_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ иС аĐģŅŒĐąĐžĐŧа ({count} ŅˆŅ‚.)", "remove_from_favorites": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ иС Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐŗĐž", @@ -1756,22 +1863,29 @@ "remove_memory": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Đ˛ĐžŅĐŋĐžĐŧиĐŊаĐŊиĐĩ", "remove_photo_from_memory": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ„ĐžŅ‚Đž иС Đ˛ĐžŅĐŋĐžĐŧиĐŊаĐŊĐ¸Ņ", "remove_tag": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗ", + "remove_url": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ URL", "remove_user": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", "removed_api_key": "API ĐēĐģŅŽŅ‡ \"{name}\" ŅƒĐ´Đ°ĐģŅ‘ĐŊ", "removed_from_archive": "ĐŖĐ´Đ°ĐģĐĩĐŊ иС Đ°Ņ€Ņ…Đ¸Đ˛Đ°", "removed_from_favorites": "ĐŖĐ´Đ°ĐģĐĩĐŊĐž иС Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐŗĐž", "removed_from_favorites_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ŅƒĐ´Đ°ĐģŅ‘ĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ŅƒĐ´Đ°ĐģĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ŅƒĐ´Đ°ĐģĐĩĐŊĐž}} иС Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐŗĐž", "removed_memory": "Đ’ĐžŅĐŋĐžĐŧиĐŊаĐŊиĐĩ ŅƒĐ´Đ°ĐģĐĩĐŊĐž", + "removed_photo_from_memory": "Đ¤ĐžŅ‚Đž ŅƒĐ´Đ°ĐģĐĩĐŊĐž иС Đ˛ĐžŅĐŋĐžĐŧиĐŊаĐŊĐ¸Ņ", "removed_tagged_assets": "ĐĸĐĩĐŗ ŅƒĐ´Đ°ĐģŅ‘ĐŊ ҃ {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚Đ°} other {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛}}", "rename": "ПĐĩŅ€ĐĩиĐŧĐĩĐŊĐžĐ˛Đ°Ņ‚ŅŒ", + "repair": "Đ ĐĩĐŧĐžĐŊŅ‚", + "repair_no_results_message": "ЗдĐĩҁҌ ĐąŅƒĐ´ŅƒŅ‚ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒŅŅ ĐŊĐĩĐžŅ‚ŅĐģĐĩĐļиваĐĩĐŧŅ‹Đĩ и ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒŅŽŅ‰Đ¸Đĩ Ņ„Đ°ĐšĐģŅ‹", + "replace_with_upload": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ и СаĐŧĐĩĐŊĐ¸Ņ‚ŅŒ", "repository": "Đ ĐĩĐŋĐžĐˇĐ¸Ņ‚ĐžŅ€Đ¸Đš", "require_password": "ĐĸŅ€ĐĩĐąŅƒĐĩŅ‚ŅŅ ĐŋĐ°Ņ€ĐžĐģҌ", + "require_user_to_change_password_on_first_login": "ĐĸŅ€ĐĩĐąĐžĐ˛Đ°Ņ‚ŅŒ ҃ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ ҁĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ ĐŋŅ€Đ¸ ĐŋĐĩŅ€Đ˛ĐžĐŧ Đ˛Ņ…ĐžĐ´Đĩ", "rescan": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ĐŊĐžĐĩ ҁĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ", "reset": "ĐĄĐąŅ€ĐžŅ", "reset_password": "ĐĄĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", "reset_people_visibility": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ видиĐŧĐžŅŅ‚ŅŒ ĐģŅŽĐ´ĐĩĐš", "reset_pin_code": "ĐĄĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ PIN-ĐēОд", "reset_pin_code_description": "Đ•ŅĐģи Đ˛Ņ‹ ĐˇĐ°ĐąŅ‹Đģи ŅĐ˛ĐžĐš PIN-ĐēОд, Đ˛Ņ‹ ĐŧĐžĐļĐĩŅ‚Đĩ ĐžĐąŅ€Đ°Ņ‚Đ¸Ņ‚ŅŒŅŅ Đē адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Ņƒ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, Ņ‡Ņ‚ĐžĐąŅ‹ ĐĩĐŗĐž ŅĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ", + "reset_pin_code_success": "PIN-ĐēОд ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐąŅ€ĐžŅˆĐĩĐŊ", "reset_pin_code_with_password": "Đ’Ņ‹ Đ˛ŅĐĩĐŗĐ´Đ° ĐŧĐžĐļĐĩŅ‚Đĩ ŅĐąŅ€ĐžŅĐ¸Ņ‚ŅŒ PIN-ĐēОд ҁ ĐŋĐžĐŧĐžŅ‰ŅŒŅŽ ĐŋĐ°Ņ€ĐžĐģŅ", "reset_sqlite": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐąĐ°ĐˇŅƒ даĐŊĐŊҋ҅ SQLite", "reset_sqlite_clear_app_data": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ даĐŊĐŊŅ‹Đĩ", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "Đ’ŅĐĩ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹ ŅƒŅŅ‚Ņ€Đ°ĐŊĐĩĐŊŅ‹", "restore": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ", "restore_all": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Đ˛ŅĐĩ", + "restore_trash_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ Đ˛ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊŅ‹ иС ĐēĐžŅ€ĐˇĐ¸ĐŊŅ‹ ({count} ŅˆŅ‚.)", "restore_user": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", "restored_asset": "Đ’ĐžŅŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐŊŅ‹Đš ĐžĐąŅŠĐĩĐēŅ‚", "resume": "ĐŸŅ€ĐžĐ´ĐžĐģĐļĐ¸Ņ‚ŅŒ", "resume_paused_jobs": "ВозобĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊиĐĩ {count, plural, one {# ĐˇĐ°Đ´Đ°Ņ‡Đ¸} other {# ĐˇĐ°Đ´Đ°Ņ‡}}", + "retry_upload": "ĐŸĐžĐ˛Ņ‚ĐžŅ€Đ¸Ņ‚ŅŒ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐē҃", "review_duplicates": "Đ Đ°ĐˇĐąĐžŅ€ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚ĐžĐ˛", "review_large_files": "ĐžĐąĐˇĐžŅ€ йОĐģŅŒŅˆĐ¸Ņ… Ņ„Đ°ĐšĐģОв", "role": "Đ ĐžĐģҌ", @@ -1796,6 +1912,7 @@ "role_viewer": "Đ—Ņ€Đ¸Ņ‚ĐĩĐģҌ", "running": "Đ’Ņ‹ĐŋĐžĐģĐŊŅĐĩŅ‚ŅŅ", "save": "ĐĄĐžŅ…Ņ€Đ°ĐŊĐ¸Ņ‚ŅŒ", + "save_to_gallery": "ĐĄĐžŅ…Ņ€Đ°ĐŊĐ¸Ņ‚ŅŒ в ĐŗĐ°ĐģĐĩŅ€ĐĩŅŽ", "saved": "ĐĄĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐž", "saved_api_key": "API ĐēĐģŅŽŅ‡ иСĐŧĐĩĐŊŅ‘ĐŊ", "saved_profile": "ĐŸŅ€ĐžŅ„Đ¸ĐģҌ ŅĐžŅ…Ņ€Đ°ĐŊŅ‘ĐŊ", @@ -1806,10 +1923,9 @@ "scan": "ĐŸĐžĐ¸ŅĐē", "scan_all_libraries": "ĐĄĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Đ˛ŅĐĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "scan_library": "ĐĄĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", + "scan_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ҁĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "scanning": "ĐŸĐžĐ¸ŅĐē ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛", "scanning_for_album": "ĐĄĐēаĐŊĐ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ аĐģŅŒĐąĐžĐŧа...", - "screencast_mode_description": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ ĐŊа ŅĐēŅ€Đ°ĐŊĐĩ иĐŊдиĐēĐ°Ņ‚ĐžŅ€Ņ‹ ŅĐžĐąŅ‹Ņ‚Đ¸Đš ĐēĐģĐ°Đ˛Đ¸Đ°Ņ‚ŅƒŅ€Ņ‹ и ĐŧŅ‹ŅˆĐ¸", - "screencast_mode_title": "ПĐĩŅ€ĐĩĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ Ņ€ĐĩĐļиĐŧ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Đ¸Đ¸ ŅĐēŅ€Đ°ĐŊа", "search": "ĐŸĐžĐ¸ŅĐē", "search_albums": "ĐŸĐžĐ¸ŅĐē аĐģŅŒĐąĐžĐŧОв", "search_by_context": "ĐŸĐžĐ¸ŅĐē ĐŋĐž ĐēĐžĐŊŅ‚ĐĩĐēŅŅ‚Ņƒ", @@ -1817,8 +1933,6 @@ "search_by_description_example": "ДĐĩĐŊҌ ĐŋĐĩ҈ĐĩĐŗĐž Ņ‚ŅƒŅ€Đ¸ĐˇĐŧа в ХаĐŋĐĩ", "search_by_filename": "ĐŸĐžĐ¸ŅĐē ĐŋĐž иĐŧĐĩĐŊи Ņ„Đ°ĐšĐģа иĐģи Ņ€Đ°ŅŅˆĐ¸Ņ€ĐĩĐŊĐ¸ŅŽ", "search_by_filename_example": "ĐŊаĐŋŅ€Đ¸ĐŧĐĩŅ€, IMG_1234.JPG иĐģи PNG", - "search_by_full_path": "ĐŸĐžĐ¸ŅĐē ĐŋĐž ĐŋĐžĐģĐŊĐžĐŧ҃ ĐŋŅƒŅ‚Đ¸ иĐģи ĐŊаиĐŧĐĩĐŊОваĐŊĐ¸ŅŽ ĐŋаĐŋĐēи", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - ĐŧĐžĐļĐŊĐž Đ¸ŅĐēĐ°Ņ‚ŅŒ Projects, 3D, Printing, 2026 и Ņ‚.Đ´.", "search_by_ocr": "ĐŸĐžĐ¸ŅĐē Ņ‚ĐĩĐēŅŅ‚Đ°", "search_by_ocr_example": "Đ›Đ°Ņ‚Ņ‚Đĩ", "search_camera_lens_model": "ĐŸĐžĐ¸ŅĐē ĐŧОдĐĩĐģи ĐžĐąŅŠĐĩĐēŅ‚Đ¸Đ˛Đ°...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐŧĐĩŅŅ‚Đž", "search_filter_media_type": "ĐĸиĐŋ Ņ„Đ°ĐšĐģа", "search_filter_media_type_title": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Ņ‚Đ¸Đŋ ĐŧĐĩдиа", + "search_filter_ocr": "ĐŸĐžĐ¸ŅĐē Ņ‚ĐĩĐēŅŅ‚Đ°", "search_filter_people_title": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐģŅŽĐ´ĐĩĐš", "search_filter_star_rating": "Đ ĐĩĐšŅ‚Đ¸ĐŊĐŗ", "search_filter_tags_title": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Ņ‚ĐĩĐŗĐ¸", @@ -1848,13 +1963,25 @@ "search_no_people_named": "НĐĩŅ‚ ĐģŅŽĐ´ĐĩĐš ҁ иĐŧĐĩĐŊĐĩĐŧ \"{name}\"", "search_no_result": "ĐĐ¸Ņ‡ĐĩĐŗĐž ĐŊĐĩ ĐŊаКдĐĩĐŊĐž, ĐŋĐžĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ иСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŋĐžĐ¸ŅĐēĐžĐ˛Ņ‹Đš СаĐŋŅ€ĐžŅ", "search_options": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐŋĐžĐ¸ŅĐēа", + "search_page_categories": "ĐšĐ°Ņ‚ĐĩĐŗĐžŅ€Đ¸Đ¸", + "search_page_motion_photos": "ДиĐŊаĐŧĐ¸Ņ‡ĐĩҁĐēиĐĩ Ņ„ĐžŅ‚Đž", + "search_page_no_objects": "НĐĩŅ‚ Đ´ĐžŅŅ‚ŅƒĐŋĐŊОК иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Đ¸ Ой ĐžĐąŅŠĐĩĐēŅ‚Đ°Ņ…", + "search_page_no_places": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž ĐŧĐĩŅŅ‚Đ°Ņ… ĐžŅ‚ŅŅƒŅ‚ŅŅ‚Đ˛ŅƒĐĩŅ‚", + "search_page_screenshots": "ĐĄĐŊиĐŧĐēи ŅĐēŅ€Đ°ĐŊа", "search_page_search_photos_videos": "ĐŸĐžĐ¸ŅĐē ĐŋĐž Ņ„ĐžŅ‚Đž и видĐĩĐž", + "search_page_selfies": "ĐĄĐĩĐģŅ„Đ¸", + "search_page_things": "ĐŸŅ€ĐĩĐ´ĐŧĐĩ҂ҋ", "search_page_view_all_button": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ Đ˛ŅĐĩ", + "search_page_your_activity": "Đ’Đ°ŅˆĐ¸ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ", + "search_page_your_map": "Đ’Đ°ŅˆĐ° ĐēĐ°Ņ€Ņ‚Đ°", "search_people": "ĐŸĐžĐ¸ŅĐē ĐģŅŽĐ´ĐĩĐš", "search_places": "ĐŸĐžĐ¸ŅĐē ĐŧĐĩҁ҂", "search_rating": "ĐŸĐžĐ¸ŅĐē ĐŋĐž Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗŅƒ...", + "search_result_page_new_search_hint": "ĐĐžĐ˛Ņ‹Đš ĐŋĐžĐ¸ŅĐē", "search_settings": "ĐŸĐžĐ¸ŅĐē ĐŊĐ°ŅŅ‚Ņ€ĐžĐĩĐē", "search_state": "ĐŸĐžĐ¸ŅĐē Ņ€ĐĩĐŗĐ¸ĐžĐŊа...", + "search_suggestion_list_smart_search_hint_1": "ИĐŊŅ‚ĐĩĐģĐģĐĩĐēŅ‚ŅƒĐ°ĐģҌĐŊŅ‹Đš ĐŋĐžĐ¸ŅĐē вĐēĐģŅŽŅ‡ĐĩĐŊ ĐŋĐž ҃ĐŧĐžĐģŅ‡Đ°ĐŊĐ¸ŅŽ, Đ´ĐģŅ ĐŋĐžĐ¸ŅĐēа ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊҋ҅ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒĐšŅ‚Đĩ ҁĐŋĐĩŅ†Đ¸Đ°ĐģҌĐŊŅ‹Đš ŅĐ¸ĐŊŅ‚Đ°ĐēŅĐ¸Ņ ", + "search_suggestion_list_smart_search_hint_2": "m:Đ˛Đ°Ņˆ-ĐŋĐžĐ¸ŅĐēĐžĐ˛Ņ‹Đš-СаĐŋŅ€ĐžŅ", "search_tags": "ĐŸĐžĐ¸ŅĐē ĐŋĐž Ņ‚ĐĩĐŗĐ°Đŧ...", "search_timezone": "ĐŸĐžĐ¸ŅĐē Ņ‡Đ°ŅĐžĐ˛ĐžĐŗĐž ĐŋĐžŅŅĐ°...", "search_type": "ĐĸиĐŋ ĐŋĐžĐ¸ŅĐēа", @@ -1868,6 +1995,7 @@ "select_albums": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ аĐģŅŒĐąĐžĐŧŅ‹", "select_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ", "select_all_duplicates": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ Đ´ĐģŅ ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ", + "select_all_in": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ в {group}", "select_avatar_color": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Ņ†Đ˛ĐĩŅ‚ Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ°", "select_count": "Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž: {count, plural, other {#}}", "select_cutoff_date": "ĐŖĐēаĐļĐ¸Ņ‚Đĩ Đ´Đ°Ņ‚Ņƒ ĐžŅ‚ŅĐĩ҇ĐĩĐŊĐ¸Ņ", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ¸ĐˇĐąŅ€Đ°ĐŊĐŊĐžĐĩ Ņ„ĐžŅ‚Đž", "select_from_computer": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ ҁ ĐēĐžĐŧĐŋŅŒŅŽŅ‚ĐĩŅ€Đ°", "select_keep_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ Đ´ĐģŅ ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ", + "select_library_owner": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ вĐģадĐĩĐģŅŒŅ†Đ° йийĐģĐ¸ĐžŅ‚ĐĩĐēи", "select_new_face": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ´Ņ€ŅƒĐŗĐžĐŗĐž ҇ĐĩĐģОвĐĩĐēа", "select_people": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐģŅŽĐ´ĐĩĐš", "select_person": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ҇ĐĩĐģОвĐĩĐēа", "select_person_to_tag": "Đ’Ņ‹Đ´ĐĩĐģĐ¸Ņ‚Đĩ ĐģĐ¸Ņ†Đž ҇ĐĩĐģОвĐĩĐēа, ĐēĐžŅ‚ĐžŅ€ĐžĐŗĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ĐžŅ‚ĐŧĐĩŅ‚Đ¸Ņ‚ŅŒ", "select_photos": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸", - "select_quality": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž", "select_trash_all": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ Đ´ĐģŅ ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ", + "select_user_for_sharing_page_err_album": "НĐĩ ŅƒĐ´Đ°ĐģĐžŅŅŒ ŅĐžĐˇĐ´Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", "selected": "Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž", "selected_count": "{count, plural, one {Đ’Ņ‹ĐąŅ€Đ°ĐŊ # ĐžĐąŅŠĐĩĐēŅ‚} many {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {Đ’Ņ‹ĐąŅ€Đ°ĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", "selected_gps_coordinates": "Đ’Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Ņ‹", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ Đ¸ŅŅ…ĐžĐ´ĐŊĐžĐĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", "setting_image_viewer_preview_subtitle": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ Đ´ĐģŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ҁҀĐĩĐ´ĐŊĐĩĐŗĐž Ņ€Đ°ĐˇŅ€Đĩ҈ĐĩĐŊĐ¸Ņ. ĐžŅ‚ĐēĐģŅŽŅ‡Đ¸Ņ‚Đĩ, Ņ‡Ņ‚ĐžĐąŅ‹ ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ Ņ‚ĐžĐģҌĐēĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ иĐģи ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņƒ.", "setting_image_viewer_preview_title": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ ҃ĐŧĐĩĐŊҌ҈ĐĩĐŊĐŊĐžĐĩ Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиĐĩ", + "setting_image_viewer_title": "Đ˜ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", "setting_languages_apply": "ĐŸŅ€Đ¸ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ", "setting_languages_subtitle": "ИСĐŧĐĩĐŊĐĩĐŊиĐĩ ŅĐˇŅ‹Đēа ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", + "setting_notifications_notify_failures_grace_period": "ĐŖĐ˛ĐĩĐ´ĐžĐŧĐģŅŅ‚ŅŒ Ой ĐžŅˆĐ¸ĐąĐēĐ°Ņ… Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ: {duration}", + "setting_notifications_notify_hours": "{count} ҇.", + "setting_notifications_notify_immediately": "ĐŊĐĩĐŧĐĩĐ´ĐģĐĩĐŊĐŊĐž", "setting_notifications_notify_minutes": "{count} ĐŧиĐŊ.", + "setting_notifications_notify_never": "ĐŊиĐēĐžĐŗĐ´Đ°", "setting_notifications_notify_seconds": "{count} ҁĐĩĐē.", + "setting_notifications_single_progress_subtitle": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐ°Ņ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž Ņ…ĐžĐ´Đĩ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи Đ´ĐģŅ ĐēаĐļĐ´ĐžĐŗĐž ĐžĐąŅŠĐĩĐēŅ‚Đ°", + "setting_notifications_single_progress_title": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ Ņ…ĐžĐ´ Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐ¸Ņ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "setting_notifications_subtitle": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ŅƒĐ˛ĐĩĐ´ĐžĐŧĐģĐĩĐŊиК", + "setting_notifications_total_progress_subtitle": "ĐžĐąŅ‰Đ¸Đš ĐŋŅ€ĐžĐŗŅ€Đĩҁҁ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи (Đ˛Ņ‹ĐŋĐžĐģĐŊĐĩĐŊĐž/Đ˛ŅĐĩĐŗĐž ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛)", + "setting_notifications_total_progress_title": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐžĐąŅ‰Đ¸Đš ĐŋŅ€ĐžĐŗŅ€Đĩҁҁ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "setting_video_viewer_auto_play_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи СаĐŋ҃ҁĐēĐ°Ņ‚ŅŒ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ видĐĩĐž ĐŋŅ€Đ¸ ĐžŅ‚ĐēŅ€Ņ‹Ņ‚Đ¸Đ¸", "setting_video_viewer_auto_play_title": "ĐĐ˛Ņ‚ĐžĐ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ видĐĩĐž", "setting_video_viewer_looping_title": "ĐĻиĐēĐģĐ¸Ņ‡ĐĩҁĐēĐžĐĩ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ", "setting_video_viewer_original_video_subtitle": "ĐŸŅ€Đ¸ Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊии видĐĩĐž ҁ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ, даĐļĐĩ ĐĩҁĐģи Đ´ĐžŅŅ‚ŅƒĐŋĐŊа Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊĐ°Ņ вĐĩŅ€ŅĐ¸Ņ. МоĐļĐĩŅ‚ ĐŋŅ€Đ¸Đ˛ĐĩŅŅ‚Đ¸ Đē ĐąŅƒŅ„ĐĩŅ€Đ¸ĐˇĐ°Ņ†Đ¸Đ¸. ВидĐĩĐž, Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ‹Đĩ ĐģĐžĐēаĐģҌĐŊĐž, Đ˛ŅĐĩĐŗĐ´Đ° Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´ŅŅ‚ŅŅ в Đ¸ŅŅ…ĐžĐ´ĐŊĐžĐŧ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đĩ.", "setting_video_viewer_original_video_title": "ĐĸĐžĐģҌĐēĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģҌĐŊĐžĐĩ видĐĩĐž", "settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи", + "settings_require_restart": "ПоĐļаĐģŅƒĐšŅŅ‚Đ°, ĐŋĐĩŅ€ĐĩСаĐŋŅƒŅŅ‚Đ¸Ņ‚Đĩ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊиĐĩ, Ņ‡Ņ‚ĐžĐąŅ‹ иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ Đ˛ŅŅ‚ŅƒĐŋиĐģи в ŅĐ¸Đģ҃", "settings_saved": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊŅ‹", "setup_pin_code": "ХОСдаĐŊиĐĩ PIN-ĐēОда", "share": "ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ", + "share_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ в ĐžĐąŅ‰ĐĩĐŧ Đ´ĐžŅŅ‚ŅƒĐŋĐĩ ({count} ŅˆŅ‚.)", + "share_add_photos": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ„ĐžŅ‚Đž", + "share_assets_selected": "{count} Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐž", "share_dialog_preparing": "ĐŸĐžĐ´ĐŗĐžŅ‚ĐžĐ˛Đēа...", "share_link": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ҁҁҋĐģĐē҃", - "share_original": "ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ (йОĐģŅŒŅˆĐžĐš Ņ€Đ°ĐˇĐŧĐĩŅ€)", - "share_preview": "МиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Đ° (ĐŧаĐģĐĩĐŊҌĐēиК Ņ€Đ°ĐˇĐŧĐĩŅ€)", - "share_quality_body": "НаĐļĐŧĐ¸Ņ‚Đĩ и ŅƒĐ´ĐĩŅ€ĐļĐ¸Đ˛Đ°ĐšŅ‚Đĩ ĐēĐŊĐžĐŋĐē҃ ÂĢПодĐĩĐģĐ¸Ņ‚ŅŒŅŅÂģ, Ņ‡Ņ‚ĐžĐąŅ‹ Đ˛Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ ĐŋĐĩŅ€ĐĩĐ´ ĐžŅ‚ĐŋŅ€Đ°Đ˛ĐēОК.", - "share_quality_title": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž Đ´ĐģŅ Ņ„ŅƒĐŊĐēŅ†Đ¸Đ¸ \"ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ\"", "shared": "ĐžĐąŅ‰Đ¸e", "shared_album_activities_input_disable": "КоĐŧĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸Đ¸ ĐžŅ‚ĐēĐģŅŽŅ‡ĐĩĐŊŅ‹", "shared_album_activity_remove_content": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅĐžĐžĐąŅ‰ĐĩĐŊиĐĩ?", "shared_album_activity_remove_title": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ", "shared_album_section_people_action_error": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ Đ˛Ņ‹Ņ…ĐžĐ´Đĩ/ŅƒĐ´Đ°ĐģĐĩĐŊии иС аĐģŅŒĐąĐžĐŧа", + "shared_album_section_people_action_leave": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ иС аĐģŅŒĐąĐžĐŧа", + "shared_album_section_people_action_remove_user": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ иС аĐģŅŒĐąĐžĐŧа", "shared_album_section_people_title": "ЛЮДИ", "shared_by": "ПодĐĩĐģиĐģŅŅ", "shared_by_user": "ВĐģадĐĩĐģĐĩ҆: {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Đ—Đ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž", "shared_link_app_bar_title": "ĐŸŅƒĐąĐģĐ¸Ņ‡ĐŊŅ‹Đĩ ҁҁҋĐģĐēи", "shared_link_clipboard_copied_massage": "ĐĄĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐž в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧĐĩĐŊа", + "shared_link_clipboard_text": "ĐĄŅŅ‹ĐģĐēа: {link}\nĐŸĐ°Ņ€ĐžĐģҌ: {password}", "shared_link_create_error": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ŅĐžĐˇĐ´Đ°ĐŊии ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊОК ҁҁҋĐģĐēи", - "shared_link_custom_url_description": "ВвĐĩŅŅ‚Đ¸ ŅĐ˛ĐžĐš URL-Đ°Đ´Ņ€Đĩҁ Đ´ĐģŅ ĐžĐąŅ‰ĐĩĐš ҁҁҋĐģĐēи", - "shared_link_custom_url_title": "ХвОК URL-Đ°Đ´Ņ€Đĩҁ", - "shared_link_custom_url_warning": "ВĐŊиĐŧаĐŊиĐĩ: ĐŋŅ€Đ¸ ҃ĐēаСаĐŊии URL-Đ°Đ´Ņ€ĐĩŅĐ°, ŅĐžĐ´ĐĩŅ€ĐļĐ°Ņ‰ĐĩĐŗĐž ĐēĐžŅŅƒŅŽ ҇ĐĩŅ€Ņ‚Ņƒ, ҁҁҋĐģĐēа ĐŧĐžĐļĐĩŅ‚ Ņ€Đ°ĐąĐžŅ‚Đ°Ņ‚ŅŒ ĐŊĐĩ Ņ‚Đ°Đē, ĐēаĐē ĐžĐļидаĐĩŅ‚ŅŅ.", + "shared_link_custom_url_description": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌҁĐēиК URL-Đ°Đ´Ņ€Đĩҁ ĐžĐąŅ‰ĐĩĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋа", "shared_link_edit_description_hint": "ВвĐĩĐ´Đ¸Ņ‚Đĩ ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", "shared_link_edit_expire_after_option_day": "1 Đ´ĐĩĐŊҌ", "shared_link_edit_expire_after_option_days": "{count} Đ´ĐŊĐĩĐš", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Đ’Ņ‹ ĐŋŅ€ĐĩĐ´ĐžŅŅ‚Đ°Đ˛Đ¸Đģи Đ´ĐžŅŅ‚ŅƒĐŋ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅŽ {partner}", "sharing": "ĐžĐąŅ‰Đ¸Đĩ", "sharing_enter_password": "ПоĐļаĐģŅƒĐšŅŅ‚Đ°, ввĐĩĐ´Đ¸Ņ‚Đĩ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° ŅŅ‚ĐžĐš ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņ‹.", + "sharing_page_album": "ĐžĐąŅ‰Đ¸Đĩ аĐģŅŒĐąĐžĐŧŅ‹", + "sharing_page_description": "ĐĄĐžĐˇĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ ĐžĐąŅ‰Đ¸Đĩ аĐģŅŒĐąĐžĐŧŅ‹, Ņ‡Ņ‚ĐžĐąŅ‹ Đ´ĐĩĐģĐ¸Ņ‚ŅŒŅŅ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅĐŧи и видĐĩĐž ҁ ĐģŅŽĐ´ŅŒĐŧи в Đ˛Đ°ŅˆĐĩĐš ҁĐĩŅ‚Đ¸.", + "sharing_page_empty_list": "ĐŸĐŖĐĄĐĸОЙ СПИСОК", + "sharing_sidebar_description": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ Đŋ҃ĐŊĐēŅ‚ ĐŧĐĩĐŊŅŽ \"ĐžĐąŅ‰Đ¸Đĩ\" в йОĐēОвОК ĐŋаĐŊĐĩĐģи", + "sharing_silver_appbar_create_shared_album": "ĐĄĐžĐˇĐ´Đ°Ņ‚ŅŒ ĐžĐąŅ‰Đ¸Đš аĐģŅŒĐąĐžĐŧ", + "sharing_silver_appbar_share_partner": "ПодĐĩĐģĐ¸Ņ‚ŅŒŅŅ ҁ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€ĐžĐŧ", "shift_to_permanent_delete": "ĐŊаĐļĐŧĐ¸Ņ‚Đĩ ⇧ Ņ‡Ņ‚ĐžĐąŅ‹ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐēŅ‚ ĐŊĐ°Đ˛ŅĐĩĐŗĐ´Đ°", "show_album_options": "ДĐĩĐšŅŅ‚Đ˛Đ¸Ņ ҁ аĐģŅŒĐąĐžĐŧĐžĐŧ", "show_albums": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", @@ -1995,9 +2140,7 @@ "show_in_timeline": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŊа Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊОК ҈ĐēаĐģĐĩ", "show_in_timeline_setting_description": "ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒ Ņ„ĐžŅ‚Đž и видĐĩĐž ŅŅ‚ĐžĐŗĐž ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ ĐŊа ŅĐ˛ĐžĐĩĐš Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊОК ҈ĐēаĐģĐĩ", "show_keyboard_shortcuts": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ŅĐžŅ‡ĐĩŅ‚Đ°ĐŊĐ¸Ņ ĐēĐģĐ°Đ˛Đ¸Ņˆ", - "show_less": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŧĐĩĐŊҌ҈Đĩ", "show_metadata": "ПоĐēĐ°ĐˇŅ‹Đ˛Đ°Ņ‚ŅŒ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊŅ‹Đĩ", - "show_more_fields": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ Đĩ҉ґ {count, plural, one {# ĐŋĐžĐģĐĩ} many {# ĐŋĐžĐģĐĩĐš} other {# ĐŋĐžĐģŅ}}", "show_or_hide_info": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ иĐģи ҁĐēŅ€Ņ‹Ņ‚ŅŒ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸ŅŽ", "show_password": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", "show_person_options": "ДĐĩĐšŅŅ‚Đ˛Đ¸Ņ ҁ ҇ĐĩĐģОвĐĩĐēĐžĐŧ", @@ -2005,7 +2148,6 @@ "show_schema": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ҁ҅ĐĩĐŧ҃", "show_search_options": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁҋ ĐŋĐžĐ¸ŅĐēа", "show_shared_links": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊŅ‹Đĩ ҁҁҋĐģĐēи", - "show_slideshow_metadata_overlay": "Đ’Ņ‹Đ˛ĐžĐ´Đ¸Ņ‚ŅŒ ҁĐģОК иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Đ¸", "show_slideshow_transition": "ПĐģавĐŊŅ‹Đš ĐŋĐĩŅ€ĐĩŅ…ĐžĐ´", "show_supporter_badge": "ЗĐŊĐ°Ņ‡ĐžĐē ҁĐŋĐžĐŊŅĐžŅ€ŅŅ‚Đ˛Đ°", "show_supporter_badge_description": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ СĐŊĐ°Ņ‡ĐžĐē ĐŋОддĐĩŅ€ĐļĐēи", @@ -2017,25 +2159,18 @@ "sign_out": "Đ’Ņ‹Ņ…ĐžĐ´", "sign_up": "Đ—Đ°Ņ€ĐĩĐŗĐ¸ŅŅ‚Ņ€Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒŅŅ", "size": "РаСĐŧĐĩŅ€", - "skip": "ĐŸŅ€ĐžĐŋŅƒŅŅ‚Đ¸Ņ‚ŅŒ", "skip_to_content": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đē ŅĐžĐ´ĐĩŅ€ĐļаĐŊĐ¸ŅŽ", "skip_to_folders": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đē ĐŋаĐŋĐēаĐŧ", "skip_to_tags": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đē Ņ‚ĐĩĐŗĐ°Đŧ", "slideshow": "ĐĄĐģаКд-ŅˆĐžŅƒ", - "slideshow_body": "ĐŖŅŅ‚Ņ€ĐžĐšŅ‚ĐĩҁҌ ĐŋĐžŅƒĐ´ĐžĐąĐŊĐĩĐĩ и ĐŊĐ°ŅĐģаĐļĐ´Đ°ĐšŅ‚ĐĩҁҌ ĐŋĐžĐģĐŊĐžŅĐēŅ€Đ°ĐŊĐŊŅ‹Đŧ ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€ĐžĐŧ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš в Ņ€ĐĩĐļиĐŧĐĩ ҁĐģаКд-ŅˆĐžŅƒ.", - "slideshow_metadata_overlay_mode": "Вид иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Đ¸", - "slideshow_metadata_overlay_mode_description_only": "ĐĸĐžĐģҌĐēĐž ĐžĐŋĐ¸ŅĐ°ĐŊиĐĩ", - "slideshow_metadata_overlay_mode_full": "ПоĐģĐŊŅ‹Đš", "slideshow_repeat": "Đ—Đ°Ņ†Đ¸ĐēĐģĐ¸Ņ‚ŅŒ ҁĐģаКд-ŅˆĐžŅƒ", "slideshow_repeat_description": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŅ‚ŅŒ ҁĐģаКд-ŅˆĐžŅƒ ĐŋĐžŅĐģĐĩ ĐĩĐŗĐž ĐžĐēĐžĐŊŅ‡Đ°ĐŊĐ¸Ņ", "slideshow_settings": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēи ҁĐģаКд-ŅˆĐžŅƒ", - "slideshow_title": "ĐĄĐģĐ°ĐšĐ´ŅˆĐžŅƒ", - "smart_album": "ĐĄĐŧĐ°Ņ€Ņ‚-аĐģŅŒĐąĐžĐŧ", - "some_assets_already_have_a_location_warning": "НĐĩĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ иС Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊҋ҅ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ҃ĐļĐĩ ŅĐžĐ´ĐĩŅ€ĐļĐ°Ņ‚ даĐŊĐŊŅ‹Đĩ Đž ĐŧĐĩŅŅ‚ĐžĐŋĐžĐģĐžĐļĐĩĐŊии", "sort_albums_by": "ĐĄĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧŅ‹ ĐŋĐž...", "sort_created": "Đ”Đ°Ņ‚Đ° ŅĐžĐˇĐ´Đ°ĐŊĐ¸Ņ", "sort_items": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ŅĐģĐĩĐŧĐĩĐŊŅ‚ĐžĐ˛", "sort_modified": "Đ”Đ°Ņ‚Đ° иСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ", + "sort_newest": "ĐŸĐžŅĐģĐĩĐ´ĐŊĐĩĐĩ Ņ„ĐžŅ‚Đž", "sort_oldest": "ĐĄŅ‚Đ°Ņ€ĐĩĐšŅˆĐĩĐĩ Ņ„ĐžŅ‚Đž", "sort_people_by_similarity": "ĐĄĐžŅ€Ņ‚Đ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐģŅŽĐ´ĐĩĐš ĐŋĐž ŅŅ…ĐžĐ´ŅŅ‚Đ˛Ņƒ", "sort_recent": "НĐĩдавĐŊиĐĩ Ņ„ĐžŅ‚Đž", @@ -2044,6 +2179,7 @@ "stack": "ĐĄĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", "stack_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ ŅĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹ ({count} ŅˆŅ‚.)", "stack_duplicates": "ĐĄĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Đ´ŅƒĐąĐģиĐēĐ°Ņ‚Ņ‹", + "stack_select_one_photo": "Đ’Ņ‹ĐąĐĩŅ€Đ¸Ņ‚Đĩ ĐŗĐģавĐŊŅƒŅŽ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸ŅŽ Đ´ĐģŅ ĐŗŅ€ŅƒĐŋĐŋŅ‹", "stack_selected_photos": "ĐĄĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ", "stacked_assets_count": "{count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚ ĐžĐąŅŠĐĩдиĐŊĐĩĐŊ} many {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ĐžĐąŅŠĐĩдиĐŊĐĩĐŊĐž} other {# ĐžĐąŅŠĐĩĐēŅ‚Đ° ĐžĐąŅŠĐĩдиĐŊĐĩĐŊĐž}} в ĐŗŅ€ŅƒĐŋĐŋ҃", "stacktrace": "ĐĸŅ€Đ°ŅŅĐ¸Ņ€ĐžĐ˛Đēа ҁ҂ĐĩĐēа", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ‡Đ°Đģа Đ´ĐžĐģĐļĐŊа ĐąŅ‹Ņ‚ŅŒ ĐŧĐĩĐŊҌ҈Đĩ Đ´Đ°Ņ‚Ņ‹ ĐžĐēĐžĐŊŅ‡Đ°ĐŊĐ¸Ņ", "state": "Đ ĐĩĐŗĐ¸ĐžĐŊ", "status": "ĐĄĐžŅŅ‚ĐžŅĐŊиĐĩ", - "step_delete": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅˆĐ°Đŗ", - "step_delete_confirm": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅŅ‚ĐžŅ‚ ŅˆĐ°Đŗ?", - "step_details": "ДĐĩŅ‚Đ°Đģи ŅˆĐ°ĐŗĐ°", - "steps": "Đ¨Đ°ĐŗĐ¸", "stop_casting": "ĐžŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Đ¸ŅŽ", "stop_motion_photo": "ПоĐēĐ°Đ´Ņ€ĐžĐ˛Đ°Ņ аĐŊиĐŧĐ°Ņ†Đ¸Ņ", "stop_photo_sharing": "ЗаĐēŅ€Ņ‹Ņ‚ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋ ĐŋĐ°Ņ€Ņ‚ĐŊŅ‘Ņ€Ņƒ?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŊаĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ҁĐģĐ¸ŅĐŊĐ¸Ņ", "sync": "ХиĐŊŅ…Ņ€.", "sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧŅ‹", + "sync_albums_manual_subtitle": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ Đ˛ŅĐĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊĐŊŅ‹Đĩ Ņ„ĐžŅ‚Đž и видĐĩĐž в Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ аĐģŅŒĐąĐžĐŧŅ‹ Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐ¸Ņ", "sync_local": "ЛоĐēаĐģҌĐŊĐ°Ņ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ", "sync_remote": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ ҁ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", "sync_status": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸", "sync_status_subtitle": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ и ҃ĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ŅĐ¸ŅŅ‚ĐĩĐŧОК ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Đ¸", "sync_upload_album_setting_subtitle": "ĐĄĐžĐˇĐ´Đ°Đ˛Đ°Ņ‚ŅŒ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Đĩ Ņ‚Đ°ĐēиĐĩ ĐļĐĩ аĐģŅŒĐąĐžĐŧŅ‹, ĐēаĐē Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐŊа ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đĩ, и ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ в ĐŊĐ¸Ņ… Ņ„ĐžŅ‚Đž и видĐĩĐž", - "system_theme": "ĐĄĐ¸ŅŅ‚ĐĩĐŧĐŊĐ°Ņ Ņ‚ĐĩĐŧа", - "system_theme_command_description": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ŅĐ¸ŅŅ‚ĐĩĐŧĐŊŅƒŅŽ Ņ‚ĐĩĐŧ҃ ({value})", "tag": "ĐĸĐĩĐŗ", "tag_assets": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ Ņ‚ĐĩĐŗĐ¸", "tag_created": "ĐĸĐĩĐŗ {tag} ŅĐžĐˇĐ´Đ°ĐŊ", "tag_face": "ĐžŅ‚ĐŧĐĩŅ‚Đ¸Ņ‚ŅŒ ҇ĐĩĐģОвĐĩĐēа", "tag_feature_description": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đš и видĐĩĐž, ŅĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐŊҋ҅ ĐŋĐž Ņ‚ĐĩĐŗĐ°Đŧ", + "tag_not_found_question": "НĐĩ ŅƒĐ´Đ°ĐĩŅ‚ŅŅ ĐŊĐ°ĐšŅ‚Đ¸ Ņ‚ĐĩĐŗ? ĐĄĐžĐˇĐ´Đ°ĐšŅ‚Đĩ ĐŊĐžĐ˛Ņ‹Đš Ņ‚ĐĩĐŗ.", "tag_people": "ĐžŅ‚ĐŧĐĩŅ‚Đ¸Ņ‚ŅŒ ҇ĐĩĐģОвĐĩĐēа", "tag_updated": "ĐĸĐĩĐŗ {tag} иСĐŧĐĩĐŊĐĩĐŊ", "tagged_assets": "ĐĸĐĩĐŗ ĐŊаСĐŊĐ°Ņ‡ĐĩĐŊ Đ´ĐģŅ {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚Đ°} other {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "КоĐģĐ¸Ņ‡ĐĩŅŅ‚Đ˛Đž ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ в ŅŅ‚Ņ€ĐžĐēĐĩ ({count})", "theme_setting_colorful_interface_subtitle": "Đ”ĐžĐąĐ°Đ˛Đ¸Ņ‚ŅŒ ĐžŅ‚Ņ‚ĐĩĐŊĐžĐē Đē Ņ„ĐžĐŊ҃.", "theme_setting_colorful_interface_title": "ĐĻвĐĩŅ‚ Ņ„ĐžĐŊа", + "theme_setting_image_viewer_quality_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đ° ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐ¸Ņ", + "theme_setting_image_viewer_quality_title": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž ĐŋŅ€ĐžŅĐŧĐžŅ‚Ņ€Đ° Đ¸ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊиК", "theme_setting_primary_color_subtitle": "ĐžŅĐŊОвĐŊОК Ņ†Đ˛ĐĩŅ‚ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ.", "theme_setting_primary_color_title": "ĐžŅĐŊОвĐŊОК Ņ†Đ˛ĐĩŅ‚", "theme_setting_system_primary_color_title": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ŅĐ¸ŅŅ‚ĐĩĐŧĐŊŅ‹Đš Ņ†Đ˛ĐĩŅ‚", "theme_setting_system_theme_switch": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи (ĐēаĐē в ŅĐ¸ŅŅ‚ĐĩĐŧĐĩ)", + "theme_setting_theme_subtitle": "ĐĐ°ŅŅ‚Ņ€ĐžĐšĐēа Ņ‚ĐĩĐŧŅ‹ ĐŋŅ€Đ¸ĐģĐžĐļĐĩĐŊĐ¸Ņ", + "theme_setting_three_stage_loading_subtitle": "ĐĸŅ€ĐĩŅ…ŅŅ‚Đ°ĐŋĐŊĐ°Ņ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа ĐŧĐžĐļĐĩŅ‚ ĐŋĐžĐ˛Ņ‹ŅĐ¸Ņ‚ŅŒ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ĐĩĐģҌĐŊĐžŅŅ‚ŅŒ, ĐŊĐž СĐŊĐ°Ņ‡Đ¸Ņ‚ĐĩĐģҌĐŊĐž ĐŊĐ°ĐŗŅ€ŅƒĐļаĐĩŅ‚ ҁĐĩŅ‚ŅŒ", + "theme_setting_three_stage_loading_title": "ВĐēĐģŅŽŅ‡Đ¸Ņ‚ŅŒ ҂ҀĐĩŅ…ŅŅ‚Đ°ĐŋĐŊŅƒŅŽ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐē҃", "then": "Đ—Đ°Ņ‚ĐĩĐŧ", "they_will_be_merged_together": "ОĐŊи ĐąŅƒĐ´ŅƒŅ‚ ĐžĐąŅŠĐĩдиĐŊĐĩĐŊŅ‹ вĐŧĐĩҁ҂Đĩ", "third_party_resources": "ĐĄŅ‚ĐžŅ€ĐžĐŊĐŊиĐĩ Ņ€ĐĩŅŅƒŅ€ŅŅ‹", @@ -2131,17 +2268,23 @@ "trash_all": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ", "trash_count": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ {count, number}", "trash_delete_asset": "ПĐĩŅ€ĐĩĐŧĐĩŅŅ‚Đ¸Ņ‚ŅŒ в ĐēĐžŅ€ĐˇĐ¸ĐŊ҃", + "trash_emptied": "ĐšĐžŅ€ĐˇĐ¸ĐŊа ĐžŅ‡Đ¸Ņ‰ĐĩĐŊа", "trash_no_results_message": "ЗдĐĩҁҌ ĐąŅƒĐ´ŅƒŅ‚ ĐžŅ‚ĐžĐąŅ€Đ°ĐļĐ°Ņ‚ŅŒŅŅ ŅƒĐ´Đ°ĐģŅ‘ĐŊĐŊŅ‹Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸ и видĐĩĐž.", "trash_page_delete_all": "ĐŖĐ´Đ°ĐģĐ¸Ņ‚ŅŒ Đ˛ŅĐĩ", + "trash_page_empty_trash_dialog_content": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚ŅŒ ĐēĐžŅ€ĐˇĐ¸ĐŊ҃? ĐžĐąŅŠĐĩĐē҂ҋ в ĐŊĐĩĐš ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ иС Immich.", "trash_page_info": "ĐžĐąŅŠĐĩĐē҂ҋ в ĐēĐžŅ€ĐˇĐ¸ĐŊĐĩ ĐąŅƒĐ´ŅƒŅ‚ ĐąĐĩĐˇĐ˛ĐžĐˇĐ˛Ņ€Đ°Ņ‚ĐŊĐž ŅƒĐ´Đ°ĐģĐĩĐŊŅ‹ ҇ĐĩŅ€ĐĩС {days} Đ´ĐŊĐĩĐš", + "trash_page_no_assets": "ĐšĐžŅ€ĐˇĐ¸ĐŊа ĐŋŅƒŅŅ‚Đ°", + "trash_page_restore_all": "Đ’ĐžŅŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ Đ˛ŅĐĩ", + "trash_page_select_assets_btn": "Đ’Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ", + "trash_page_title": "ĐšĐžŅ€ĐˇĐ¸ĐŊа ({count})", "trashed_items_will_be_permanently_deleted_after": "ĐžĐąŅŠĐĩĐē҂ҋ, Ņ…Ņ€Đ°ĐŊŅŅ‰Đ¸ĐĩŅŅ в ĐēĐžŅ€ĐˇĐ¸ĐŊĐĩ йОĐģĐĩĐĩ {days, plural, one {# Đ´ĐŊŅ} other {# Đ´ĐŊĐĩĐš}}, ŅƒĐ´Đ°ĐģŅŅŽŅ‚ŅŅ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐĩҁĐēи.", "trigger": "ĐĸŅ€Đ¸ĐŗĐŗĐĩŅ€", - "trigger_asset_metadata_extraction": "ИСвĐģĐĩ҇ĐĩĐŊиĐĩ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊҋ҅ ĐžĐąŅŠĐĩĐēŅ‚Đ°", - "trigger_asset_metadata_extraction_description": "ĐĄŅ€Đ°ĐąĐ°Ņ‚Ņ‹Đ˛Đ°ĐĩŅ‚ ĐŋŅ€Đ¸ иСвĐģĐĩ҇ĐĩĐŊии EXIF ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐŊҋ҅ ĐžĐąŅŠĐĩĐēŅ‚Đ°", "trigger_asset_uploaded": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐžĐąŅŠĐĩĐēŅ‚Đ°", "trigger_asset_uploaded_description": "ĐĄŅ€Đ°ĐąĐ°Ņ‚Ņ‹Đ˛Đ°ĐĩŅ‚ ĐŋŅ€Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēĐĩ ĐŊĐžĐ˛ĐžĐŗĐž ĐžĐąŅŠĐĩĐēŅ‚Đ°", + "trigger_description": "ĐĄĐžĐąŅ‹Ņ‚Đ¸Đĩ, ĐēĐžŅ‚ĐžŅ€ĐžĐĩ СаĐŋ҃ҁĐēаĐĩŅ‚ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ", "trigger_person_recognized": "Đ Đ°ŅĐŋОСĐŊаваĐŊиĐĩ ҇ĐĩĐģОвĐĩĐēа", "trigger_person_recognized_description": "ĐĄŅ€Đ°ĐąĐ°Ņ‚Ņ‹Đ˛Đ°ĐĩŅ‚ ĐŋŅ€Đ¸ Ņ€Đ°ŅĐŋОСĐŊаваĐŊии ҇ĐĩĐģОвĐĩĐēа", + "trigger_type": "ĐĸиĐŋ Ņ‚Ņ€Đ¸ĐŗĐŗĐĩŅ€Đ°", "troubleshoot": "Đ”Đ¸Đ°ĐŗĐŊĐžŅŅ‚Đ¸Đēа", "type": "ĐĸиĐŋ", "unable_to_change_pin_code": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ иСĐŧĐĩĐŊĐĩĐŊии PIN-ĐēОда", @@ -2157,7 +2300,6 @@ "unknown": "НĐĩиСвĐĩҁ҂ĐŊĐž", "unknown_country": "НĐĩиСвĐĩҁ҂ĐŊĐ°Ņ ŅŅ‚Ņ€Đ°ĐŊа", "unknown_date": "Đ”Đ°Ņ‚Đ° ĐŊĐĩиСвĐĩҁ҂ĐŊа", - "unknown_schema": "НĐĩиСвĐĩҁ҂ĐŊĐ°Ņ ҁ҅ĐĩĐŧа", "unknown_year": "НĐĩиСвĐĩҁ҂ĐŊŅ‹Đš Год", "unlimited": "НĐĩ ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊĐž", "unlink_motion_video": "ĐžŅ‚ŅĐžĐĩдиĐŊĐ¸Ņ‚ŅŒ двиĐļŅƒŅ‰ĐĩĐĩŅŅ видĐĩĐž", @@ -2170,20 +2312,23 @@ "unsaved_change": "НĐĩŅĐžŅ…Ņ€Đ°ĐŊŅ‘ĐŊĐŊĐžĐĩ иСĐŧĐĩĐŊĐĩĐŊиĐĩ", "unselect_all": "ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ˛Ņ‹Đ´ĐĩĐģĐĩĐŊиĐĩ", "unselect_all_duplicates": "Đ’Ņ‹ĐąŅ€Đ°Ņ‚ŅŒ Đ˛ŅĐĩ Đ´ĐģŅ ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ", + "unselect_all_in": "ĐžŅ‚ĐŧĐĩĐŊĐ¸Ņ‚ŅŒ Đ˛Ņ‹Đ´ĐĩĐģĐĩĐŊиĐĩ в {group}", "unstack": "Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", "unstack_action_prompt": "ĐžĐąŅŠĐĩĐē҂ҋ Ņ€Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹ ({count} ŅˆŅ‚.)", "unstacked_assets_count": "{count, plural, one {Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊ # ĐžĐąŅŠĐĩĐēŅ‚} many {Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛} other {Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°ĐŊĐž # ĐžĐąŅŠĐĩĐēŅ‚Đ°}}", "unsupported_field_type": "НĐĩĐŋОддĐĩŅ€ĐļиваĐĩĐŧŅ‹Đš Ņ‚Đ¸Đŋ ĐŋĐžĐģŅ", "unsupported_file_type": "ФаКĐģ {file} ĐŊĐĩ ĐŧĐžĐļĐĩŅ‚ ĐąŅ‹Ņ‚ŅŒ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊ, Ņ‚Đ¸Đŋ {type} ĐŊĐĩ ĐŋОддĐĩŅ€ĐļиваĐĩŅ‚ŅŅ.", "untagged": "БĐĩС Ņ‚ĐĩĐŗĐžĐ˛", + "untitled_workflow": "Đ Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ ĐąĐĩС ĐŊаСваĐŊĐ¸Ņ", "up_next": "ĐĄĐģĐĩĐ´ŅƒŅŽŅ‰ĐĩĐĩ", "update_location_action_prompt": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ ҁĐģĐĩĐ´ŅƒŅŽŅ‰Đ¸Đĩ ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Ņ‹ ҃ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊҋ҅ ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛ ({count} ŅˆŅ‚.):", "updated_at": "ОбĐŊОвĐģŅ‘ĐŊ", "updated_password": "ĐŸĐ°Ņ€ĐžĐģҌ иСĐŧĐĩĐŊŅ‘ĐŊ", "upload": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ", "upload_concurrency": "ĐŸĐ°Ņ€Đ°ĐģĐģĐĩĐģҌĐŊĐžŅŅ‚ŅŒ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", - "upload_day_count": "{date}: {count, plural, one {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа} many {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐžĐē} other {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи}}", "upload_details": "ĐŸĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи", + "upload_dialog_info": "ĐĨĐžŅ‚Đ¸Ņ‚Đĩ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ Đ˛Ņ‹ĐąŅ€Đ°ĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€?", + "upload_dialog_title": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐ¸Ņ‚ŅŒ ĐžĐąŅŠĐĩĐēŅ‚", "upload_error_with_count": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēĐĩ {count, plural, one {# ĐžĐąŅŠĐĩĐēŅ‚Đ°} other {# ĐžĐąŅŠĐĩĐēŅ‚ĐžĐ˛}}", "upload_errors": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа СавĐĩŅ€ŅˆĐĩĐŊа ҁ {count, plural, one {# ĐžŅˆĐ¸ĐąĐēОК} other {# ĐžŅˆĐ¸ĐąĐēаĐŧи}}, ОйĐŊĐžĐ˛Đ¸Ņ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņƒ, Ņ‡Ņ‚ĐžĐąŅ‹ ŅƒĐ˛Đ¸Đ´ĐĩŅ‚ŅŒ ĐŊĐžĐ˛Ņ‹Đĩ ĐˇĐ°ĐŗŅ€ŅƒĐļĐĩĐŊĐŊŅ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ.", "upload_finished": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа СавĐĩŅ€ŅˆĐĩĐŊа", @@ -2193,13 +2338,9 @@ "upload_status_errors": "ĐžŅˆĐ¸ĐąĐēи", "upload_status_uploaded": "Đ—Đ°ĐŗŅ€ŅƒĐļĐĩĐŊĐž", "upload_success": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа ĐŋŅ€ĐžŅˆĐģа ҃ҁĐŋĐĩ҈ĐŊĐž. ОбĐŊĐžĐ˛Đ¸Ņ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņƒ, Ņ‡Ņ‚ĐžĐąŅ‹ ŅƒĐ˛Đ¸Đ´ĐĩŅ‚ŅŒ ĐŊĐžĐ˛Ņ‹Đĩ ĐžĐąŅŠĐĩĐē҂ҋ.", - "upload_to_album_body": "ДĐģŅ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģĐĩĐš, ĐēĐžŅ‚ĐžŅ€Ņ‹Đĩ ĐŊĐĩ Đ¸ŅĐŋĐžĐģŅŒĐˇŅƒŅŽŅ‚ Ņ„ŅƒĐŊĐēŅ†Đ¸ŅŽ Ņ€ŅƒŅ‡ĐŊОК ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи, Ņ‚ĐĩĐŋĐĩŅ€ŅŒ ĐŧĐžĐļĐŊĐž дОйавĐģŅŅ‚ŅŒ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Đ¸ ҁ ŅƒŅŅ‚Ņ€ĐžĐšŅŅ‚Đ˛Đ° ŅŅ€Đ°ĐˇŅƒ в ĐŊ҃ĐļĐŊŅ‹Đš аĐģŅŒĐąĐžĐŧ ĐŋĐž ĐŧĐĩŅ€Đĩ Đ¸Ņ… ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи. БоĐģҌ҈Đĩ ĐŊĐĩ ĐŊ҃ĐļĐŊĐž ҁĐŊĐ°Ņ‡Đ°Đģа ĐˇĐ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ Đ¸Ņ…, а ĐˇĐ°Ņ‚ĐĩĐŧ дОйавĐģŅŅ‚ŅŒ в аĐģŅŒĐąĐžĐŧ.", - "upload_to_album_title": "Đ—Đ°ĐŗŅ€ŅƒĐļĐ°Ņ‚ŅŒ ŅŅ€Đ°ĐˇŅƒ в аĐģŅŒĐąĐžĐŧ", "upload_to_immich": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēа в Immich ({count})", "uploading": "Đ—Đ°ĐŗŅ€ŅƒĐļаĐĩŅ‚ŅŅ", "uploading_media": "Đ’Ņ‹ĐŋĐžĐģĐŊŅĐĩŅ‚ŅŅ ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа", - "uploads": "Đ—Đ°ĐŗŅ€ŅƒĐˇĐēи", - "uploads_count": "{count, plural, one {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēа} many {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐžĐē} other {# ĐˇĐ°ĐŗŅ€ŅƒĐˇĐēи}}", "url": "URL", "usage": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊиĐĩ", "use_biometric": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ йиОĐŧĐĩŅ‚Ņ€Đ¸ŅŽ", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€ĐžĐ˛Đ°ĐŊиĐĩ Đ´Đ°Ņ‚, Đ˛Ņ€ĐĩĐŧĐĩĐŊи и Ņ‡Đ¸ŅĐĩĐģ ĐŊа ĐžŅĐŊОваĐŊии ŅĐˇŅ‹ĐēĐžĐ˛Ņ‹Ņ… ĐŊĐ°ŅŅ‚Ņ€ĐžĐĩĐē ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đ°", "use_current_connection": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ Ņ‚ĐĩĐēŅƒŅ‰ĐĩĐĩ ĐŋОдĐēĐģŅŽŅ‡ĐĩĐŊиĐĩ", "use_custom_date_range": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌҁĐēиК диаĐŋаСОĐŊ Đ´Đ°Ņ‚", - "use_template": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ ŅˆĐ°ĐąĐģĐžĐŊ", "user": "ПоĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ", "user_has_been_deleted": "Đ­Ņ‚ĐžŅ‚ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģҌ ĐąŅ‹Đģ ŅƒĐ´Đ°ĐģŅ‘ĐŊ.", "user_id": "ID ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", @@ -2217,6 +2357,7 @@ "user_privacy": "КоĐŊŅ„Đ¸Đ´ĐĩĐŊŅ†Đ¸Đ°ĐģҌĐŊĐžŅŅ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", "user_purchase_settings": "ПоĐē҃ĐŋĐēа", "user_purchase_settings_description": "ĐŖĐŋŅ€Đ°Đ˛ĐģĐĩĐŊиĐĩ ĐŋĐžĐē҃ĐŋĐēОК", + "user_role_set": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚ŅŒ {user} в ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đĩ {role}", "user_usage_detail": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ ĐŋĐž ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅĐŧ", "user_usage_stats": "ĐĄŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đēа Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊĐ¸Ņ аĐēĐēĐ°ŅƒĐŊŅ‚Đ°", "user_usage_stats_description": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ ŅŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đē҃ Đ¸ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°ĐŊĐ¸Ņ аĐēĐēĐ°ŅƒĐŊŅ‚Đ°", @@ -2226,6 +2367,7 @@ "utilities": "ĐŖŅ‚Đ¸ĐģĐ¸Ņ‚Ņ‹", "validate": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚ŅŒ", "validate_endpoint_error": "ВвĐĩĐ´Đ¸Ņ‚Đĩ ĐēĐžŅ€Ņ€ĐĩĐēŅ‚ĐŊŅ‹Đš URL", + "validation_error": "ĐžŅˆĐ¸ĐąĐēа ĐŋŅ€Đ¸ ĐŋŅ€ĐžĐ˛ĐĩŅ€ĐēĐĩ", "variables": "ПĐĩŅ€ĐĩĐŧĐĩĐŊĐŊŅ‹Đĩ", "version": "ВĐĩŅ€ŅĐ¸Ņ", "version_announcement_closing": "ĐĸвОК Đ´Ņ€ŅƒĐŗ АĐģĐĩĐēҁ", @@ -2235,9 +2377,9 @@ "video": "ВидĐĩĐž", "video_hover_setting": "Đ’ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ видĐĩĐž ĐŋŅ€Đ¸ ĐŊавĐĩĐ´ĐĩĐŊии ĐēŅƒŅ€ŅĐžŅ€Đ° ĐŧŅ‹ŅˆĐ¸", "video_hover_setting_description": "Đ’ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ¸Ņ‚ŅŒ видĐĩĐž ĐŋŅ€Đ¸ ĐŊавĐĩĐ´ĐĩĐŊии ĐēŅƒŅ€ŅĐžŅ€Đ° ĐŧŅ‹ŅˆĐ¸ ĐŊа ĐŧиĐŊĐ¸Đ°Ņ‚ŅŽŅ€Ņƒ. ДаĐļĐĩ ĐĩҁĐģи ŅŅ‚Đ° Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ Đ˛Ņ‹ĐēĐģŅŽŅ‡ĐĩĐŊа, Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊиĐĩ ĐŧĐžĐļĐŊĐž СаĐŋŅƒŅŅ‚Đ¸Ņ‚ŅŒ, ĐŊавĐĩĐ´Ņ ĐēŅƒŅ€ŅĐžŅ€ ĐŊа СĐŊĐ°Ņ‡ĐžĐē Đ˛ĐžŅĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐĩĐ´ĐĩĐŊĐ¸Ņ.", - "video_quality": "ĐšĐ°Ņ‡ĐĩŅŅ‚Đ˛Đž видĐĩĐž", "videos": "ВидĐĩĐž", "videos_count": "{count, plural, one {# видĐĩĐž} other {# видĐĩĐž}}", + "videos_only": "ĐĸĐžĐģҌĐēĐž видĐĩĐž", "view": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€", "view_album": "ĐžŅ‚ĐēŅ€Ņ‹Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ", "view_all": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ Đ˛ŅŅ‘", @@ -2246,49 +2388,49 @@ "view_details": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ ĐŋĐžĐ´Ņ€ĐžĐąĐŊĐžŅŅ‚Đ¸", "view_in_timeline": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŊа Đ˛Ņ€ĐĩĐŧĐĩĐŊĐŊОК ҈ĐēаĐģĐĩ", "view_link": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ҁҁҋĐģĐē҃", + "view_links": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ҁҁҋĐģĐēи", "view_name": "Вид", "view_next_asset": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ҁĐģĐĩĐ´ŅƒŅŽŅ‰Đ¸Đš ĐžĐąŅŠĐĩĐēŅ‚", "view_previous_asset": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŋŅ€ĐĩĐ´Ņ‹Đ´ŅƒŅ‰Đ¸Đš ĐžĐąŅŠĐĩĐēŅ‚", "view_qr_code": "ĐŸĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ QR ĐēОд", "view_similar_photos": "ĐĐ°ĐšŅ‚Đ¸ ĐŋĐžŅ…ĐžĐļиĐĩ", "view_stack": "ПоĐēĐ°ĐˇĐ°Ņ‚ŅŒ ĐŗŅ€ŅƒĐŋĐŋ҃", + "view_user": "ĐŸŅ€ĐžŅĐŧĐžŅ‚Ņ€ĐĩŅ‚ŅŒ ĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ĐĩĐģŅ", "viewer_remove_from_stack": "ĐŖĐąŅ€Đ°Ņ‚ŅŒ иС ĐŗŅ€ŅƒĐŋĐŋŅ‹", + "viewer_stack_use_as_main_asset": "Đ˜ŅĐŋĐžĐģŅŒĐˇĐžĐ˛Đ°Ņ‚ŅŒ в ĐēĐ°Ņ‡ĐĩŅŅ‚Đ˛Đĩ ĐžŅĐŊОвĐŊĐžĐŗĐž ĐžĐąŅŠĐĩĐēŅ‚Đ°", + "viewer_unstack": "Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐŋĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ", "visibility": "ВидиĐŧĐžŅŅ‚ŅŒ", "visibility_changed": "ИСĐŧĐĩĐŊĐĩĐŊа видиĐŧĐžŅŅ‚ŅŒ ҃ {count, plural, one {# ҇ĐĩĐģОвĐĩĐēа} other {# ҇ĐĩĐģОвĐĩĐē}}", "visual": "Đ’Đ¸ĐˇŅƒĐ°ĐģҌĐŊŅ‹Đš", + "visual_builder": "Đ’Đ¸ĐˇŅƒĐ°ĐģҌĐŊŅ‹Đš ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€", "waiting": "В ĐžŅ‡ĐĩŅ€Đĩди", "waiting_count": "ОĐļĐ¸Đ´Đ°ŅŽŅ‚: {count}", "warning": "ĐŸŅ€ĐĩĐ´ŅƒĐŋŅ€ĐĩĐļĐ´ĐĩĐŊиĐĩ", "week": "НĐĩĐ´ĐĩĐģŅ", "welcome": "Đ”ĐžĐąŅ€Đž ĐŋĐžĐļаĐģĐžĐ˛Đ°Ņ‚ŅŒ", "welcome_to_immich": "Đ”ĐžĐąŅ€Đž ĐŋĐžĐļаĐģĐžĐ˛Đ°Ņ‚ŅŒ в Immich", - "whats_new": "Đ§Ņ‚Đž ĐŊĐžĐ˛ĐžĐŗĐž", - "whats_new_settings_subtitle": "ĐŖĐˇĐŊĐ°ĐšŅ‚Đĩ Ой ĐŊОвОввĐĩĐ´ĐĩĐŊĐ¸ŅŅ… в Immich", - "whats_new_version": "ВĐĩŅ€ŅĐ¸Ņ {version}", - "when": "ĐšĐžĐŗĐ´Đ°", "width": "Đ¨Đ¸Ņ€Đ¸ĐŊа", "wifi_name": "ИĐŧŅ ҁĐĩŅ‚Đ¸", - "workflow": "ĐĄŅ†ĐĩĐŊĐ°Ņ€Đ¸Đš", - "workflow_delete_prompt": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅŅ‚ĐžŅ‚ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đš?", - "workflow_deleted": "ĐĄŅ†ĐĩĐŊĐ°Ņ€Đ¸Đš ŅƒĐ´Đ°ĐģŅ‘ĐŊ", - "workflow_description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ", - "workflow_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đ¸", - "workflow_json": "JSON-даĐŊĐŊŅ‹Đĩ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ", - "workflow_json_help": "ĐžŅ‚Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ŅƒĐšŅ‚Đĩ ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸ŅŽ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ в JSON Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ. ИСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ ĐąŅƒĐ´ŅƒŅ‚ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹ в Đ˛Đ¸ĐˇŅƒĐ°ĐģҌĐŊĐžĐŧ ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€Đĩ.", - "workflow_name": "ИĐŧŅ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Ņ", + "workflow_delete_prompt": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ ŅƒĐ´Đ°ĐģĐ¸Ņ‚ŅŒ ŅŅ‚ĐžŅ‚ Ņ€Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ?", + "workflow_deleted": "Đ Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ ŅƒĐ´Đ°ĐģŅ‘ĐŊ", + "workflow_description": "ОĐŋĐ¸ŅĐ°ĐŊиĐĩ Ņ€Đ°ĐąĐžŅ‡ĐĩĐŗĐž ĐŋŅ€ĐžŅ†ĐĩŅŅĐ°", + "workflow_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž Ņ€Đ°ĐąĐžŅ‡ĐĩĐŧ ĐŋŅ€ĐžŅ†ĐĩҁҁĐĩ", + "workflow_json": "JSON Ņ€Đ°ĐąĐžŅ‡ĐĩĐŗĐž ĐŋŅ€ĐžŅ†ĐĩŅŅĐ°", + "workflow_json_help": "ĐžŅ‚Ņ€ĐĩдаĐēŅ‚Đ¸Ņ€ŅƒĐšŅ‚Đĩ ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ°Ņ†Đ¸ŅŽ Ņ€Đ°ĐąĐžŅ‡ĐĩĐŗĐž ĐŋŅ€ĐžŅ†ĐĩŅŅĐ° в JSON Ņ„ĐžŅ€ĐŧĐ°Ņ‚Đĩ. ИСĐŧĐĩĐŊĐĩĐŊĐ¸Ņ ĐąŅƒĐ´ŅƒŅ‚ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°ĐŊŅ‹ в Đ˛Đ¸ĐˇŅƒĐ°ĐģҌĐŊŅ‹Đš ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€.", + "workflow_name": "ИĐŧŅ Ņ€Đ°ĐąĐžŅ‡ĐĩĐŗĐž ĐŋŅ€ĐžŅ†ĐĩŅŅĐ°", "workflow_navigation_prompt": "Đ’Ņ‹ Đ´ĐĩĐšŅŅ‚Đ˛Đ¸Ņ‚ĐĩĐģҌĐŊĐž Ņ…ĐžŅ‚Đ¸Ņ‚Đĩ Đ˛Ņ‹ĐšŅ‚Đ¸ ĐąĐĩС ŅĐžŅ…Ņ€Đ°ĐŊĐĩĐŊĐ¸Ņ иСĐŧĐĩĐŊĐĩĐŊиК?", - "workflow_summary": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đ¸", - "workflow_templates": "ШайĐģĐžĐŊŅ‹ ҁ҆ĐĩĐŊĐ°Ņ€Đ¸Đĩв", - "workflow_update_success": "ĐĄŅ†ĐĩĐŊĐ°Ņ€Đ¸Đš ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊОвĐģŅ‘ĐŊ", - "workflows": "ĐĄŅ†ĐĩĐŊĐ°Ņ€Đ¸Đ¸", - "workflows_help_text": "ĐĄŅ†ĐĩĐŊĐ°Ņ€Đ¸Đ¸ ĐŋОСвОĐģŅŅŽŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžĐŋĐĩŅ€Đ°Ņ†Đ¸Đ¸ ҁ ĐžĐąŅŠĐĩĐēŅ‚Đ°Đŧи ĐŊа ĐžŅĐŊОваĐŊии ŅĐžĐąŅ‹Ņ‚Đ¸Đš и Ņ„Đ¸ĐģŅŒŅ‚Ņ€ĐžĐ˛", + "workflow_summary": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ Đž Ņ€Đ°ĐąĐžŅ‡ĐĩĐŧ ĐŋŅ€ĐžŅ†ĐĩҁҁĐĩ", + "workflow_update_success": "Đ Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ ҃ҁĐŋĐĩ҈ĐŊĐž ОйĐŊОвĐģŅ‘ĐŊ", + "workflow_updated": "Đ Đ°ĐąĐžŅ‡Đ¸Đš ĐŋŅ€ĐžŅ†Đĩҁҁ ОйĐŊОвĐģŅ‘ĐŊ", + "workflows": "Đ Đ°ĐąĐžŅ‡Đ¸Đĩ ĐŋŅ€ĐžŅ†Đĩҁҁҋ", + "workflows_help_text": "Đ Đ°ĐąĐžŅ‡Đ¸Đĩ ĐŋŅ€ĐžŅ†Đĩҁҁҋ ĐŋОСвОĐģŅŅŽŅ‚ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ¸Ņ€ĐžĐ˛Đ°Ņ‚ŅŒ ĐžĐŋĐĩŅ€Đ°Ņ†Đ¸Đ¸ ҁ ĐžĐąŅŠĐĩĐēŅ‚Đ°Đŧи ĐŊа ĐžŅĐŊОваĐŊии ŅĐžĐąŅ‹Ņ‚Đ¸Đš и Ņ„Đ¸ĐģŅŒŅ‚Ņ€ĐžĐ˛", "wrong_pin_code": "НĐĩвĐĩŅ€ĐŊŅ‹Đš PIN-ĐēОд", - "x_of_total": "{x}/{total}", "year": "Год", "years_ago": "{years, plural, one {# ĐŗĐžĐ´} few {# ĐŗĐžĐ´Đ°} many {# ĐģĐĩŅ‚} other {# ĐŗĐžĐ´Đ°}} ĐŊаСад", "yes": "Да", "you_dont_have_any_shared_links": "ĐŖ Đ˛Đ°Ņ ĐŊĐĩŅ‚ ĐŋŅƒĐąĐģĐ¸Ņ‡ĐŊҋ҅ ҁҁҋĐģĐžĐē", "your_wifi_name": "ИĐŧŅ Đ˛Đ°ŅˆĐĩĐš Wi-Fi ҁĐĩŅ‚Đ¸", "zero_to_clear_rating": "ĐŊаĐļĐŧĐ¸Ņ‚Đĩ 0 Đ´ĐģŅ ŅƒĐ´Đ°ĐģĐĩĐŊĐ¸Ņ Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗĐ°", - "zoom_image": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŧĐ°ŅŅˆŅ‚Đ°Đą" + "zoom_image": "ИСĐŧĐĩĐŊĐ¸Ņ‚ŅŒ ĐŧĐ°ŅŅˆŅ‚Đ°Đą", + "zoom_to_bounds": "ĐŖĐ˛ĐĩĐģĐ¸Ņ‡Đ¸Ņ‚ŅŒ Đ´Đž ĐŗŅ€Đ°ĐŊĐ¸Ņ†" } diff --git a/i18n/sk.json b/i18n/sk.json index c40dfa7e0b..d18c2fc23b 100644 --- a/i18n/sk.json +++ b/i18n/sk.json @@ -10,31 +10,39 @@ "active": "Aktívne", "active_count": "Aktívne: {count}", "activity": "Aktivita", + "activity_changed": "Aktivita je {enabled, select, true{povolenÃĄ} other {zakÃĄzanÃĄ}}", "add": "PridaÅĨ", "add_a_description": "PridaÅĨ popis", "add_a_location": "PridaÅĨ polohu", "add_a_name": "PridaÅĨ meno", "add_a_title": "PridaÅĨ nÃĄzov", "add_action": "PridaÅĨ akciu", + "add_action_description": "Kliknutím pridÃĄte akciu, ktorÃē chcete vykonaÅĨ", "add_assets": "PridaÅĨ poloÅžky", "add_birthday": "PridaÅĨ narodeniny", "add_endpoint": "PridaÅĨ koncovÃŊ bod", "add_exclusion_pattern": "PridaÅĨ vzor vylÃēčenia", + "add_filter": "PridaÅĨ filter", + "add_filter_description": "Kliknutím pridÃĄte podmienku filtra", "add_location": "PridaÅĨ polohu", + "add_more_users": "PridaÅĨ viac pouŞívateÄžov", "add_partner": "PridaÅĨ partnera", + "add_path": "PridaÅĨ cestu", "add_photos": "PridaÅĨ fotografie", - "add_step": "PridaÅĨ krok", "add_tag": "PridaÅĨ ÅĄtítok", "add_to": "PridaÅĨ doâ€Ļ", "add_to_album": "PridaÅĨ do albumu", "add_to_album_bottom_sheet_added": "PridanÊ do {album}", "add_to_album_bottom_sheet_already_exists": "UÅž je v {album}", "add_to_album_bottom_sheet_some_local_assets": "NiektorÊ lokÃĄlne sÃēbory nebolo moÅžnÊ pridaÅĨ do albumu", + "add_to_album_toggle": "PrepnÃēÅĨ vÃŊber pre {album}", "add_to_albums": "PridaÅĨ do albumov", "add_to_albums_count": "PridaÅĨ do albumov ({count})", "add_to_bottom_bar": "PridaÅĨ do", + "add_to_shared_album": "PridaÅĨ do zdieÄžanÊho albumu", "add_upload_to_stack": "NahraÅĨ a pridaÅĨ do zoskupenÃŊch", "add_url": "PridaÅĨ URL", + "add_workflow_step": "PridaÅĨ krok pracovnÊho postupu", "added_to_archive": "PridanÊ do archívu", "added_to_favorites": "PridanÊ do obÄžÃēbenÃŊch", "added_to_favorites_count": "PridanÊ {count, number} do obÄžÃēbenÃŊch", @@ -73,7 +81,6 @@ "cron_expression_description": "Nastavte interval skenovania pomocou formÃĄtu cron. Pre viac informÃĄcií navÅĄtívte Crontab Guru", "cron_expression_presets": "PredvoÄžby vÃŊrazov Cron", "disable_login": "ZakÃĄzaÅĨ prihlÃĄsenie", - "download_csv": "StiahnuÅĨ CSV", "duplicate_detection_job_description": "Spustite strojovÊ učenie na poloÅžkÃĄch pre detekciu podobnÃŊch obrÃĄzkov. Spolieha sa na inteligentnÊ vyhÄžadÃĄvanie", "exclusion_pattern_description": "Vylučovacie vzory VÃĄm umoŞňujÃē ignorovaÅĨ sÃēbory a priečinky pri skenovaní VaÅĄej kniÅžnice. Toto je uÅžitočnÊ, ak mÃĄte priečinky obsahujÃēce sÃēbory, ktorÊ nechcete importovaÅĨ, napríklad RAW sÃēbory.", "export_config_as_json_description": "StiahnuÅĨ aktuÃĄlnu konfigurÃĄciu systÊmu ako sÃēbor JSON", @@ -108,22 +115,7 @@ "image_thumbnail_quality_description": "Kvalita miniatÃēry v stupnici od 1 do 100. VyÅĄÅĄia hodnota znamenÃĄ lepÅĄiu kvalitu, ale produkuje vÃ¤ÄÅĄie sÃēbory a môŞe zníŞiÅĨ odozvu aplikÃĄcie.", "image_thumbnail_title": "MiniatÃēry", "import_config_from_json_description": "ImportovaÅĨ konfigurÃĄciu systÊmu nahraním konfiguračnÊho sÃēboru JSON", - "integrity_checks_checksum_files": "SÃēbory s kontrolnÃŊmi sÃēčtami", - "integrity_checks_checksum_files_description": "NastaviÅĨ kontroly kontrolnÃŊch sÃēčtov", - "integrity_checks_checksum_files_enable_description": "ZapnÃēÅĨ kontrolu kontrolnÃŊch sÃēčtov", - "integrity_checks_checksum_files_percentage_limit": "PercentuÃĄlny limit", - "integrity_checks_checksum_files_percentage_limit_description": "Nastavte maximÃĄlnu percentuÃĄlnu hodnotu v rozsahu 0.01 aÅž 1. TÃĄ určí ako často sa mÃĄ kontrolovaÅĨ kontrolnÃŊ sÃēčet v kaÅždom intervale.", - "integrity_checks_checksum_files_time_limit": "ČasovÃŊ limit", - "integrity_checks_checksum_files_time_limit_description": "Nastavte maximÃĄlnu dobu ako dlho môŞe prebiehaÅĨ kontrola kontrolnÊho sÃēčtu v kaÅždom intervale. (ms)", - "integrity_checks_missing_files": "ChÃŊbajÃēce sÃēbory", - "integrity_checks_missing_files_description": "Nastavte frekvenciu a zapnite alebo vypnite kontrolu chÃŊbajÃēcich sÃēborov", - "integrity_checks_missing_files_enable_description": "ZapnÃēÅĨ kontrolu chÃŊbajÃēcich sÃēborov", - "integrity_checks_settings": "Kontroly integrity", - "integrity_checks_settings_description": "Spravovanie intervalov kontrol integrity", - "integrity_checks_untracked_files": "NesledovanÊ sÃēbory", - "integrity_checks_untracked_files_description": "Nastavte frekvenciu a zapnite alebo vypnite kontrolu nesledovanÃŊch sÃēborov", - "integrity_checks_untracked_files_enable_description": "ZapnÃēÅĨ kontrolu nesledovanÃŊch sÃēborov", - "job_concurrency": "SÃēbeÅžnÊ Ãēlohy - {job}", + "job_concurrency": "SÃēbeÅžnosÅĨ Ãēlohy - {job}", "job_created": "Úloha bola vytvorenÃĄ", "job_not_concurrency_safe": "TÃĄto Ãēloha nie je bezpečnÃĄ pre sÃēbeÅžnÊ spracovanie.", "job_settings": "Úlohy", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "PovoliÅĨ inteligentnÊ vyhÄžadÃĄvanie", "machine_learning_smart_search_enabled_description": "Ak je vypnutÊ, obrÃĄzky nebudÃē spracovanÊ pre inteligentnÊ vyhÄžadÃĄvanie.", "machine_learning_url_description": "URL adresa servera strojovÊho učenia. Ak je zadanÃŊch viacero adries URL, kaÅždÃŊ server bude testovanÃŊ postupne, kÃŊm jeden z nich neodpovie ÃēspeÅĄne, v poradí od prvÊho po poslednÃŊ. Servery, ktorÊ neodpovedajÃē, budÃē dočasne ignorovanÊ, kÃŊm nebudÃē opäÅĨ online.", - "maintenance_backup_management": "Spravovanie zÃĄloh", "maintenance_delete_backup": "VymazaÅĨ zÃĄlohu", "maintenance_delete_backup_description": "Tento sÃēbor bude nezvratne vymazanÃŊ.", "maintenance_delete_error": "Nepodarilo sa vymazaÅĨ zÃĄlohu.", - "maintenance_integrity_check": "SkontrolovaÅĨ", - "maintenance_integrity_check_all": "SkontrolovaÅĨ vÅĄetko", - "maintenance_integrity_checksum_mismatch": "NesÃēlad kontrolnÊho sÃēčtu", - "maintenance_integrity_checksum_mismatch_description": "SÃēbory, ktorÃŊch kontrolnÃŊ sÃēčet na disku sa nezhoduje s kontrolnÃŊm sÃēčtom, ktorÃŊ Immich uloÅžil vo svojej databÃĄze.", - "maintenance_integrity_checksum_mismatch_job": "SkontrolovaÅĨ, či sa kontrolnÊ sÃēčty zhodujÃē", - "maintenance_integrity_checksum_mismatch_refresh_job": "ObnoviÅĨ sprÃĄvy o nezhode kontrolnÊho sÃēčtu", - "maintenance_integrity_missing_file": "ChÃŊbajÃēce sÃēbory", - "maintenance_integrity_missing_file_description": "SÃēbory, ktorÊ Immich zaznamenal vo svojej databÃĄze, ale ktorÊ v sÃēborovom systÊme neexistujÃē.", - "maintenance_integrity_missing_file_job": "SkontrolovaÅĨ chÃŊbajÃēce sÃēbory", - "maintenance_integrity_missing_file_refresh_job": "ObnoviÅĨ hlÃĄsenia o chÃŊbajÃēcich sÃēboroch", - "maintenance_integrity_report": "HlÃĄsenie o integrite", - "maintenance_integrity_untracked_file": "NesledovanÊ sÃēbory", - "maintenance_integrity_untracked_file_description": "SÃēbory v adresÃĄroch Immich, o ktorÃŊch Immich nemÃĄ Åžiadne zÃĄznamy.", - "maintenance_integrity_untracked_file_job": "SkontrolovaÅĨ nesledovanÊ sÃēbory", - "maintenance_integrity_untracked_file_refresh_job": "ObnoviÅĨ hlÃĄsenia o nesledovanÃŊch sÃēboroch", "maintenance_restore_backup": "ObnoviÅĨ zÃĄlohu", "maintenance_restore_backup_description": "Immich bude vymazanÃŊ a obnovenÃŊ zo zvolenej zÃĄlohy. Pred pokračovaním bude vytvorenÃĄ zÃĄloha.", "maintenance_restore_backup_different_version": "TÃĄto zÃĄloha bola vytvorenÃĄ pomocou inej verzie aplikÃĄcie Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "PovoliÅĨ e-mailovÊ upozornenia", "notification_settings": "Upozornenia", "notification_settings_description": "SpravovaÅĨ nastavenia upozornení, vrÃĄtane emailu", - "oauth_allow_insecure_requests": "PovoliÅĨ nezabezpečenÊ poÅžiadavky", - "oauth_allow_insecure_requests_description": "UPOZORNENIE: TÃŊmto sa deaktivuje overovanie certifikÃĄtov TLS pre poÅžiadavky OAuth, čo vÃĄs môŞe vystaviÅĨ riziku Ãētokov typu MITM.", "oauth_auto_launch": "AutomatickÊ spustenie", "oauth_auto_launch_description": "AutomatickÊ spustenie OAuth prihlasovacieho toku pri otvorení prihlasovacej strÃĄnky", "oauth_auto_register": "AutomatickÃĄ regristrÃĄcia", @@ -300,11 +274,9 @@ "oauth_button_text": "Text tlačítka", "oauth_client_secret_description": "VyÅžadovanÊ pre dôvernÊho klienta alebo ak OAuth nepodporuje PKCE (Proof Key for Code Exchange).", "oauth_enable_description": "PrihlÃĄsiÅĨ sa pomocou OAuth", - "oauth_end_session_url_description": "Pri odhlÃĄsení presmerovaÅĨ pouŞívateÄža na tÃēto adresu URI.", "oauth_mobile_redirect_uri": "URI mobilnÊho presmerovania", "oauth_mobile_redirect_uri_override": "Prepísanie URI mobilnÊho presmerovania", "oauth_mobile_redirect_uri_override_description": "PovoÄžte, keď poskytovateÄž protokolu OAuth nepovoÄžuje identifikÃĄtor URI pre mobilnÊ zariadenia, napríklad ''{callback}''", - "oauth_prompt_description": "Parameter vÃŊzvy (napr. select_account, login, consent)", "oauth_role_claim": "PoÅžiadavka na rolu", "oauth_role_claim_description": "Automaticky udeliÅĨ prístup sprÃĄvcu na zÃĄklade prítomnosti tejto poÅžiadavky. PoÅžiadavka môŞe maÅĨ príznak „user“ alebo „admin“.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "ObnovujÃē sa vÅĄetky kniÅžnice", "registration": "RegistrÃĄcia administrÃĄtora", "registration_description": "KeďŞe ste prvÃŊm pouŞívateÄžom v systÊme, budÃē vÃĄm pridelenÊ sprÃĄvcovskÊ prÃĄva na vykonÃĄvanie vÅĄetkÃŊch Ãēloh a vrÃĄtane tvorby novÃŊch pouŞívateÄžov.", - "release_channel_release_candidate": "PredbeÅžnÃĄ verzia", - "release_channel_stable": "StabilnÃĄ", "remove_failed_jobs": "OdstrÃĄniÅĨ neÃēspeÅĄnÊ Ãēlohy", "require_password_change_on_login": "VyÅžadovaÅĨ od pouŞívateÄža zmenu hesla pri prvom prihlÃĄsení", "reset_settings_to_default": "ObnoviÅĨ pôvodnÊ nastavenia", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Platí len pre VAAPI a QSV. Nastavuje uzol dri, ktorÃŊ sa pouŞíva na hardvÊrovÊ prekÃŗdovanie.", "transcoding_preset_preset": "PredvoÄžba (-preset)", "transcoding_preset_preset_description": "RÃŊchlosÅĨ kompresie. PomalÅĄie predvoÄžby vytvÃĄrajÃē menÅĄie sÃēbory a zvyÅĄujÃē kvalitu, keď sa zameriavajÃē na určitÃŊ dÃĄtovÃŊ tok. VP9 ignoruje rÃŊchlosti vyÅĄÅĄie ako „rÃŊchlejÅĄie“.", - "transcoding_realtime": "PrekÃŗdovanie v reÃĄlnom čase [EXPERIMENTÁLNE]", - "transcoding_realtime_description": "UmoŞňuje transkÃŗdovanie v reÃĄlnom čase počas streamovania videa. UmoŞňuje zmenu kvality, avÅĄak v zÃĄvislosti od vÃŊkonnosti servera môŞe spôsobiÅĨ vyÅĄÅĄiu latenciu prehrÃĄvania a trhanÊ prehrÃĄvanie.", - "transcoding_realtime_enabled": "ZapnÃēÅĨ prekÃŗdovanie v reÃĄlnom čase", - "transcoding_realtime_enabled_description": "Ak je tÃĄto funkcia vypnutÃĄ, server odmietne spustiÅĨ novÊ relÃĄcie transkÃŗdovania v reÃĄlnom čase.", - "transcoding_realtime_resolutions": "RozlÃ­ÅĄenia", - "transcoding_realtime_resolutions_description": "RozlÃ­ÅĄenia ponÃēkanÊ pre transkÃŗdovanie v reÃĄlnom čase. VyÅĄÅĄie rozlÃ­ÅĄenia môŞu spôsobiÅĨ problÊmy s prehrÃĄvaním, ak ich server nedokÃĄÅže transkÃŗdovaÅĨ dostatočne rÃŊchlo.", - "transcoding_realtime_video_codecs": "Video kodeky", - "transcoding_realtime_video_codecs_description": "Video kodeky ponÃēkanÊ na transkÃŗdovanie v reÃĄlnom čase. Klienti si počas prehrÃĄvania vyberÃē najlepÅĄiu moÅžnosÅĨ, ktorÃē podporujÃē. AV1 je efektívnejÅĄÃ­ ako HEVC, ktorÃŊ je zase efektívnejÅĄÃ­ ako H.264. Pri pouÅžití hardvÊrovej akcelerÃĄcie vyberte len tie kodeky, ktorÊ akcelerÃĄtor dokÃĄÅže kÃŗdovaÅĨ. Pri pouÅžití softvÊrovÊho transkÃŗdovania majte na pamäti, Åže H.264 je rÃŊchlejÅĄÃ­ ako AV1, ktorÃŊ je zase rÃŊchlejÅĄÃ­ ako HEVC.", "transcoding_reference_frames": "ReferenčnÊ snímky", "transcoding_reference_frames_description": "Počet snímok, na ktorÊ sa mÃĄ odkazovaÅĨ pri kompresii danÊho snímku. VyÅĄÅĄie hodnoty zvyÅĄujÃē ÃēčinnosÅĨ kompresie, ale spomaÄžujÃē kÃŗdovanie. Hodnota 0 sa nastavuje automaticky.", "transcoding_required_description": "Iba videÃĄ, ktorÊ nie sÃē v prijateÄžnom formÃĄte", @@ -478,8 +440,6 @@ "user_settings_description": "SpravovaÅĨ pouŞívateÄžskÊ nastavenia", "user_successfully_removed": "PouŞívateÄž {email} bol ÃēspeÅĄne odstrÃĄnenÃŊ.", "users_page_description": "StrÃĄnka pouŞívateÄžov pre sprÃĄvcu", - "version_check_channel": "DistribučnÃŊ kanÃĄl", - "version_check_channel_description": "Vyberte distribučnÃŊ kanÃĄl, pre ktorÃŊ chcete dostÃĄvaÅĨ oznÃĄmenia o novÃŊch verziÃĄch", "version_check_enabled_description": "PovoliÅĨ kontrolu verzie", "version_check_implications": "Funkcia kontroly verzie sa spolieha na pravidelnÃē komunikÃĄciu s {server}", "version_check_settings": "Kontrola verzie", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "VyčistiÅĨ vyrovnÃĄvaciu pamäÅĨ obrÃĄzkov", "advanced_settings_clear_image_cache_error": "Nepodarilo sa vyčistiÅĨ vyrovnÃĄvaciu pamäÅĨ obrÃĄzkov", "advanced_settings_clear_image_cache_success": "ÚspeÅĄne vyčistenÃŊch {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "TÃēto moÅžnosÅĨ pouÅžite na filtrovanie mÊdií počas synchronizÃĄcie na zÃĄklade alternatívnych kritÊrií. TÃēto moÅžnosÅĨ vyskÃēÅĄajte len vtedy, ak mÃĄte problÊmy s detekciou vÅĄetkÃŊch albumov v aplikÃĄcii.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTÁLNE] PouÅžiÅĨ alternatívny filter synchronizÃĄcie albumu zariadenia", "advanced_settings_log_level_title": "Úroveň ukladania zÃĄznamov: {level}", "advanced_settings_prefer_remote_subtitle": "V niektorÃŊch zariadeniach sa miniatÃēry z miestnych poloÅžiek načítavajÃē veÄžmi pomaly. Aktivovaním tohto nastavenia sa namiesto toho načítajÃē vzdialenÊ obrÃĄzky.", "advanced_settings_prefer_remote_title": "UprednostniÅĨ vzdialenÊ obrÃĄzky", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "VlastnÊ proxy hlavičky [EXPERIMENTÁLNE]", "advanced_settings_readonly_mode_subtitle": "Aktivuje reÅžim iba na čítanie, v ktorom je moÅžnÊ fotografie iba prezeraÅĨ, pričom funkcie ako vÃŊber viacerÃŊch obrÃĄzkov, zdieÄžanie, prenÃĄÅĄanie a mazanie sÃē deaktivovanÊ. AktivÃĄcia/deaktivÃĄcia reÅžimu iba na čítanie prostredníctvom obrÃĄzku pouŞívateÄža na hlavnej obrazovke", "advanced_settings_readonly_mode_title": "ReÅžim iba na čítanie", + "advanced_settings_self_signed_ssl_subtitle": "Preskakuje overovanie SSL certifikÃĄtom zo strany servera. VyÅžaduje sa pre samo-podpísanÊ certifikÃĄty.", + "advanced_settings_self_signed_ssl_title": "PovoliÅĨ samo-podpísanÊ SSL certifikÃĄty [EXPERIMENTÁLNE]", "advanced_settings_sync_remote_deletions_subtitle": "Automaticky vymazaÅĨ alebo obnoviÅĨ poloÅžku na tomto zariadení, keď sa tÃĄto akcia vykonÃĄ na webe", "advanced_settings_sync_remote_deletions_title": "SynchronizovaÅĨ vzdialenÊ vymazania [EXPERIMENTÁLNE]", "advanced_settings_tile_subtitle": "PokročilÊ nastavenia pouŞívateÄža", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ste si istÃŊ, Åže chcete odstrÃĄniÅĨ album {album}?", "album_delete_confirmation_description": "Ak je tento album zdieÄžanÃŊ, ostatní pouŞívatelia k nemu uÅž nebudÃē maÅĨ prístup.", "album_deleted": "Album bol vymazanÃŊ", + "album_info_card_backup_album_excluded": "VYLÚČENÉ", + "album_info_card_backup_album_included": "ZAHRNUTÉ", "album_info_updated": "InformÃĄcie albumu aktualizovanÊ", + "album_leave": "OpustiÅĨ album?", + "album_leave_confirmation": "Ste si istÃŊ, Åže chcete opustiÅĨ album {album}?", "album_name": "NÃĄzov albumu", "album_options": "Nastavenia albumu", "album_remove_user": "OdstrÃĄniÅĨ pouŞívateÄža?", "album_remove_user_confirmation": "Ste si istÃŊ, Åže chcete odstrÃĄniÅĨ pouŞívateÄža {user}?", "album_search_not_found": "Neboli nÃĄjdenÊ Åžiadne albumy zodpovedajÃēce vÃĄÅĄmu hÄžadaniu", + "album_selected": "VybranÃŊ album", "album_share_no_users": "VyzerÃĄ to, Åže ste tento album zdieÄžali so vÅĄetkÃŊmi pouŞívateÄžmi alebo nemÃĄte Åžiadneho pouŞívateÄža, s ktorÃŊm by ste ho mohli zdieÄžaÅĨ.", "album_summary": "SÃēhrn albumu", "album_updated": "Album bol aktualizovanÃŊ", "album_updated_setting_description": "ObdrÅžaÅĨ e-mailovÊ upozornenie, keď v zdieÄžanom albume pribudnÃē novÊ poloÅžky", "album_upload_assets": "Nahrajte sÃēbory zo svojho počítača a pridajte ich do albumu", + "album_user_left": "Opustil {album}", + "album_user_removed": "OdstrÃĄnenÃŊ {user}", + "album_viewer_appbar_delete_confirm": "Ste si istÃŊ Åže chcete vymazaÅĨ tento album z vÃĄÅĄho Ãēčtu?", "album_viewer_appbar_share_err_delete": "Nepodarilo sa odstrÃĄniÅĨ album", + "album_viewer_appbar_share_err_leave": "Nepodarilo sa ukončiÅĨ album", + "album_viewer_appbar_share_err_remove": "Pri odstraňovaní sÃēborov z albumu sa vyskytli problÊmy", + "album_viewer_appbar_share_err_title": "Nepodarilo sa zmeniÅĨ nÃĄzov albumu", + "album_viewer_appbar_share_leave": "OpustiÅĨ album", + "album_viewer_appbar_share_to": "ZdieÄžaÅĨ s", "album_viewer_page_share_add_users": "PridaÅĨ pouŞívateÄžov", "album_with_link_access": "UmoÅžnite komukoÄžvek s odkazom pozrieÅĨ si fotky a Äžudí v tomto albume.", "albums": "Albumy", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "PočiatočnÊ poradie triedenia poloÅžiek pri vytvÃĄraní novÃŊch albumov.", "albums_feature_description": "Zbierky mÊdií, ktorÊ moÅžno zdieÄžaÅĨ s ostatnÃŊmi pouŞívateÄžmi.", "albums_on_device_count": "Albumy v zariadení ({count})", + "albums_selected": "{count, plural, one {# vybranÃŊ album} few {# vybranÊ albumy} other {# vybranÃŊch albumov}}", "all": "VÅĄetko", "all_albums": "VÅĄetky albumy", "all_people": "VÅĄetci Äžudia", "all_photos": "VÅĄetky fotky", "all_videos": "VÅĄetky videa", "allow_dark_mode": "PovoliÅĨ tmavÃŊ reÅžim", + "allow_edits": "PovoliÅĨ Ãēpravy", "allow_public_user_to_download": "PovoliÅĨ verejnÊmu pouŞívateÄžovi stiahnutie", "allow_public_user_to_upload": "UmoÅžniÅĨ verejnÊmu pouŞívateÄžovi nahraÅĨ", "allowed": "PovolenÊ", @@ -549,12 +528,14 @@ "always_keep": "VÅždy ponechaÅĨ", "always_keep_photos_hint": "Funkcia UvoÄžniÅĨ miesto ponechÃĄ vÅĄetky fotografie v tomto zariadení.", "always_keep_videos_hint": "Funkcia UvoÄžniÅĨ miesto ponechÃĄ vÅĄetky videÃĄ v tomto zariadení.", + "anti_clockwise": "Proti smeru hodinovÃŊch ručičiek", "api_key": "API KlÃēč", "api_key_description": "TÃĄto hodnota sa zobrazí iba raz. Pred zatvorením okna ju určite skopírujte.", "api_key_empty": "NÃĄzov vÃĄÅĄho API kÄēuča by nemal byÅĨ prÃĄzdny", "api_keys": "API KÄžÃēče", "app_architecture_variant": "Variant (ArchitektÃēra)", "app_bar_signout_dialog_content": "Skutočne sa chcete odhlÃĄsiÅĨ?", + "app_bar_signout_dialog_ok": "Áno", "app_bar_signout_dialog_title": "OdhlÃĄsiÅĨ sa", "app_download_links": "Odkazy na stiahnutie aplikÃĄcie", "app_settings": "Nastavenia aplikÃĄcie", @@ -565,19 +546,27 @@ "archive": "Archív", "archive_action_prompt": "{count} pridanÃŊch do archívu", "archive_or_unarchive_photo": "ArchivÃĄcia alebo odarchivovanie fotografie", + "archive_page_no_archived_assets": "ÅŊiadne archivovanÊ mÊdiÃĄ", + "archive_page_title": "Archív ({count})", "archive_size": "VeÄžkosÅĨ archívu", "archive_size_description": "NastaviÅĨ veÄžkosÅĨ archívu na stiahnutie (v GiB)", "archived": "ArchivovanÊ", "archived_count": "{count, plural, one {ArchivovanÃŊ #} few {ArchivovanÊ #} other {ArchivovanÃŊch #}}", "are_these_the_same_person": "Ide o tÃē istÃē osobu?", "are_you_sure_to_do_this": "Ste si istÃŊ, Åže to chcete urobiÅĨ?", + "array_field_not_fully_supported": "Polia vyÅžadujÃē ručnÊ Ãēpravy JSON", + "asset_action_delete_err_read_only": "NemoÅžno vymazaÅĨ poloÅžku len na čítanie, preskakujem", + "asset_action_share_err_offline": "NemoÅžno načítaÅĨ offline poloÅžku, preskakujem", "asset_added_to_album": "PridanÊ do albumu", "asset_adding_to_album": "PridÃĄva sa do albumuâ€Ļ", "asset_created": "PoloÅžka bola vytvorenÃĄ", - "asset_day_count": "{date}: {count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžiek}}", "asset_description_updated": "Popis mÊdia bol aktualizovanÃŊ", + "asset_filename_is_offline": "MÊdium {filename} je offline", + "asset_has_unassigned_faces": "PoloÅžka mÃĄ nepriradenÊ tvÃĄre", "asset_hashing": "HaÅĄovanieâ€Ļ", "asset_list_group_by_sub_title": "ZoskupiÅĨ podÄža", + "asset_list_layout_settings_dynamic_layout_title": "DynamickÊ rozloÅženie", + "asset_list_layout_settings_group_automatically": "Automaticky", "asset_list_layout_settings_group_by": "ZoskupiÅĨ poloÅžky podÄža", "asset_list_layout_settings_group_by_month_day": "Mesiac + deň", "asset_list_layout_sub_title": "RozloÅženie", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "PoloÅžka nebola nÃĄjdenÃĄ v iCloude. PoloÅžka môŞe byÅĨ nedostupnÃĄ kvôli poÅĄkodenÊmu sÃēboru uloÅženÊmu v iCloude", "asset_offline": "MÊdium je offline", "asset_offline_description": "Tento externÃĄ poloÅžka sa uÅž nenachÃĄdza na disku. Pre pomoc sa prosím obrÃĄÅĨte na sprÃĄvcu systÊmu Immich.", + "asset_restored_successfully": "PoloÅžky boli ÃēspeÅĄne obnovenÊ", "asset_skipped": "PreskočenÊ", "asset_skipped_in_trash": "V koÅĄi", + "asset_trashed": "PoloÅžka bola vyhodenÃĄ", "asset_troubleshoot": "RieÅĄenie problÊmov s poloÅžkami", "asset_uploaded": "NahranÊ", "asset_uploading": "NahrÃĄva saâ€Ļ", "asset_viewer_settings_subtitle": "Spravujte nastavenia prehliadača galÊrie", "asset_viewer_settings_title": "Prehliadač mÊdií", "assets": "PoloÅžky", + "assets_added_count": "{count, plural, one {PridanÃĄ # poloÅžka} few {PridanÊ # poloÅžky} other {PridanÃŊch # poloÅžek}}", "assets_added_to_album_count": "Do albumu {count, plural, one {bola pridanÃĄ # poloÅžka} few {boli pridanÊ # poloÅžky} other {bolo pridanÃŊch # poloÅžiek}}", - "assets_added_to_album_partial_count": "Do albumu bolo pridanÃŊch {successCount} z {totalCount} {totalCount, plural, one {poloÅžky} other {poloÅžiek}}", "assets_added_to_albums_count": "{assetTotal, plural, one {PridanÃĄ # poloÅžka} few {PridanÊ # poloÅžky} other {PridanÃŊch # poloÅžiek}} do {albumTotal, plural, one {# albumu} other {# albumov}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {poloÅžku} other {poloÅžiek}} nie je moÅžnÊ pridaÅĨ do albumu", "assets_cannot_be_added_to_albums": "{count, plural, one {poloÅžka} few {poloÅžky} other {poloÅžiek}} nie je moÅžnÊ pridaÅĨ do Åžiadneho albumu", "assets_count": "{count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžiek}}", + "assets_deleted_permanently": "{count} poloÅžka(iek) natrvalo vymazanÃĄ(ÃŊch)", + "assets_deleted_permanently_from_server": "{count} poloÅžka(iek) natrvalo vymazanÃĄ(ÃŊch) zo servera Immich", + "assets_downloaded_failed": "{count, plural, one {StiahnutÃŊ # sÃēbor - {error} sÃēbor zlyhal} few {StiahnutÊ # sÃēbory - {error} sÃēbory zlyhali} other {StiahnutÃŊch # sÃēborov - {error} sÃēborov zlyhalo}}", + "assets_downloaded_successfully": "{count, plural, one {# sÃēbor bol ÃēspeÅĄne stiahnutÃŊ} few {# sÃēbory boli ÃēspeÅĄne stiahnutÊ} other {# sÃēborov bolo ÃēspeÅĄne stiahnutÃŊch}}", "assets_moved_to_trash_count": "Do koÅĄa {count, plural, one {bola presunutÃĄ # poloÅžka} few {boli presunutÊ # poloÅžky} other {bolo presunutÃŊch # poloÅžiek}}", "assets_permanently_deleted_count": "Trvalo {count, plural, one {vymazanÃĄ # poloÅžka} few {vymazanÊ # poloÅžky} other {vymazanÃŊch # poloÅžiek}}", "assets_removed_count": "{count, plural, one {OdstrÃĄnenÃĄ # poloÅžka} few {OdstrÃĄnenÊ # poloÅžky} other {OdstrÃĄnenÃŊch # poloÅžiek}}", + "assets_removed_permanently_from_device": "{count} poloÅžka(iek) natrvalo vymazanÃĄ(ÃŊch) z vÃĄÅĄho zariadenia", "assets_restore_confirmation": "Naozaj chcete obnoviÅĨ vÅĄetky vyhodenÊ poloÅžky? TÃēto akciu nie je moÅžnÊ vrÃĄtiÅĨ späÅĨ! Upozorňujeme, Åže tÃŊmto spôsobom nie je moÅžnÊ obnoviÅĨ Åžiadne offline poloÅžky.", "assets_restored_count": "{count, plural, one {ObnovenÃĄ # poloÅžka} few {ObnovenÊ # poloÅžky} other {ObnovenÃŊch # poloÅžiek}}", + "assets_restored_successfully": "{count} mÊdií ÃēspeÅĄne obnovenÃŊch", "assets_trashed": "{count} poloÅžka(iek) vyhodenÃĄ(ÃŊch) do koÅĄa", "assets_trashed_count": "{count, plural, one {OdstrÃĄnenÃĄ # poloÅžka} few {OdstrÃĄnenÊ # poloÅžky} other {OdstrÃĄnenÃŊch # poloÅžiek}}", + "assets_trashed_from_server": "{count} poloÅžka(iek) vyhodenÃĄ(ÃŊch) do koÅĄa zo servera Immich", "assets_were_part_of_album_count": "{count, plural, one {PoloÅžka uÅž bola} other {PoloÅžky uÅž boli}} sÃēčasÅĨou albumu", "assets_were_part_of_albums_count": "{count, plural, one {poloÅžka} few {poloÅžky} other {poloÅžiek}} sÃē uÅž sÃēčasÅĨou albumov", "authorized_devices": "AutorizovanÊ zariadenia", @@ -617,48 +615,87 @@ "autoplay_slideshow": "AutomatickÊ prehrÃĄvanie prezentÃĄcie", "back": "SpäÅĨ", "back_close_deselect": "SpäÅĨ, zavrieÅĨ alebo zruÅĄiÅĨ vÃŊber", + "background_backup_running_error": "V sÃēčasnosti prebieha zÃĄlohovanie na pozadí, nie je moÅžnÊ spustiÅĨ ručnÊ zÃĄlohovanie", "background_location_permission": "Povolenie na určenie polohy na pozadí", "background_location_permission_content": "Aby bolo moÅžnÊ prepínaÅĨ siete pri spustení na pozadí, musí maÅĨ aplikÃĄcia Immich *vÅždy* presnÃŊ prístup k polohe, aby mohla prečítaÅĨ nÃĄzov siete Wi-Fi", "background_options": "MoÅžnosti pozadia", "backup": "ZÃĄlohovanie", + "backup_album_selection_page_albums_device": "Albumy v zariadení ({count})", "backup_album_selection_page_albums_tap": "Ťuknutím na poloÅžku ju zahrniete, dvojitÃŊm ÅĨuknutím ju vylÃēčite", "backup_album_selection_page_assets_scatter": "SÃēbory môŞu byÅĨ roztrÃēsenÊ vo viacerÃŊch albumoch. To umoŞňuje zahrnÃēÅĨ alebo vylÃēčiÅĨ albumy počas procesu zÃĄlohovania.", "backup_album_selection_page_select_albums": "VybraÅĨ albumy", "backup_album_selection_page_selection_info": "InformÃĄcie o vÃŊbere", + "backup_album_selection_page_total_assets": "CelkovÃŊ počet jedinečnÃŊch sÃēborov", "backup_albums_sync": "SynchronizÃĄcia zÃĄlohovanÃŊch albumov", + "backup_all": "VÅĄetko", + "backup_background_service_backup_failed_message": "ZÃĄlohovanie mÊdií zlyhalo. SkÃēÅĄam to znovaâ€Ļ", "backup_background_service_complete_notification": "ZÃĄlohovanie poloÅžiek dokončenÊ", + "backup_background_service_connection_failed_message": "Nepodarilo sa pripojiÅĨ k serveru. SkÃēÅĄam to znovaâ€Ļ", + "backup_background_service_current_upload_notification": "NahrÃĄvanie {filename}", "backup_background_service_default_notification": "Kontrola novÃŊch zdrojovâ€Ļ", + "backup_background_service_error_title": "Chyba zÃĄlohovania", "backup_background_service_in_progress_notification": "VytvÃĄram kÃŗpiu vaÅĄich mÊdiíâ€Ļ", + "backup_background_service_upload_failure_notification": "Nepodarilo sa nahraÅĨ {filename}", "backup_controller_page_albums": "ZÃĄlohovanÊ albumy", + "backup_controller_page_background_app_refresh_disabled_content": "Ak chcete pouŞívaÅĨ zÃĄlohovanie na pozadí, povoÄžte obnovovanie aplikÃĄcií na pozadí v ponuke Nastavenia > VÅĄeobecnÊ > Obnovovanie aplikÃĄcií na pozadí.", + "backup_controller_page_background_app_refresh_disabled_title": "Obnovovanie aplikÃĄcií na pozadí je vypnutÊ", + "backup_controller_page_background_app_refresh_enable_button_text": "PrejsÅĨ do nastavení", "backup_controller_page_background_battery_info_link": "UkÃĄÅž mi ako", "backup_controller_page_background_battery_info_message": "Ak chcete dosiahnuÅĨ najlepÅĄie vÃŊsledky pri zÃĄlohovaní na pozadí, vypnite vÅĄetky optimalizÃĄcie batÊrie, ktorÊ obmedzujÃē aktivitu na pozadí pre Immich. \n\nKeďŞe to zÃĄvisí od zariadenia, skontrolujte poÅžadovanÊ informÃĄcie pre vÃŊrobcu vÃĄÅĄho zariadenia.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "OptimalizÃĄcia batÊrie", + "backup_controller_page_background_charging": "Len počas nabíjania", + "backup_controller_page_background_configure_error": "Nepodarilo sa nakonfigurovaÅĨ sluÅžbu na pozadí", "backup_controller_page_background_delay": "Oneskorenie zÃĄlohovania novÃŊch mÊdií: {duration}", + "backup_controller_page_background_description": "PovoÄžte sluÅžbu na pozadí na automatickÊ zÃĄlohovanie vÅĄetkÃŊch novÃŊch poloÅžiek bez nutnosti otvorenia aplikÃĄcie", + "backup_controller_page_background_is_off": "AutomatickÊ zÃĄlohovanie na pozadí je vypnutÊ", + "backup_controller_page_background_is_on": "AutomatickÊ zÃĄlohovanie na pozadí je zapnutÊ", + "backup_controller_page_background_turn_off": "VypnÃēÅĨ zÃĄlohovanie na pozadí", + "backup_controller_page_background_turn_on": "PovoliÅĨ zÃĄlohovanie na pozadí", + "backup_controller_page_background_wifi": "Len cez WiFi", "backup_controller_page_backup": "ZÃĄlohovanie", "backup_controller_page_backup_selected": "VybranÊ: ", "backup_controller_page_backup_sub": "ZÃĄlohovanÊ fotografie a videa", + "backup_controller_page_created": "VytvorenÊ: {date}", + "backup_controller_page_desc_backup": "Zapnite zÃĄlohovanie na popredí, aby sa novÊ poloÅžky automaticky nahrÃĄvali na server pri otvorení aplikÃĄcie.", "backup_controller_page_excluded": "VylÃēčenÊ: ", + "backup_controller_page_failed": "Nepodarilo sa ({count})", + "backup_controller_page_filename": "NÃĄzov sÃēboru: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "InformÃĄcie o zÃĄlohovaní", "backup_controller_page_none_selected": "ÅŊiadne vybranÊ", "backup_controller_page_remainder": "ZostÃĄva", "backup_controller_page_remainder_sub": "ZostÃĄvajÃēce fotografie a videÃĄ, ktorÊ sa majÃē zÃĄlohovaÅĨ z vÃŊberu", "backup_controller_page_server_storage": "ServerovÊ ÃēloÅžisko", + "backup_controller_page_start_backup": "SpustiÅĨ zÃĄlohovanie", + "backup_controller_page_status_off": "AutomatickÊ zÃĄlohovanie na popredí je vypnutÊ", + "backup_controller_page_status_on": "AutomatickÊ zÃĄlohovanie na popredí je zapnutÊ", "backup_controller_page_storage_format": "{used} z {total} pouÅžitÃŊch", "backup_controller_page_to_backup": "Albumy ktorÊ budÃē zÃĄlohovanÊ", "backup_controller_page_total_sub": "VÅĄetky jedinečnÊ fotografie a videÃĄ z vybranÃŊch albumov", + "backup_controller_page_turn_off": "VypnÃēÅĨ zÃĄlohovanie na popredí", + "backup_controller_page_turn_on": "PovoliÅĨ zÃĄlohovanie na popredí", + "backup_controller_page_uploading_file_info": "NahrÃĄvanÃŊ sÃēbor", + "backup_err_only_album": "Nie je moÅžnÊ odstrÃĄniÅĨ jedinÃŊ vybranÃŊ album", "backup_error_sync_failed": "SynchronizÃĄcia sa nepodarila. Nie je moÅžnÊ spracovaÅĨ zÃĄlohu.", "backup_info_card_assets": "poloÅžiek", + "backup_manual_cancelled": "ZruÅĄenÊ", + "backup_manual_in_progress": "NahrÃĄvanie uÅž prebieha. VyskÃēÅĄajte neskôr", + "backup_manual_success": "Hotovo", + "backup_manual_title": "Stav nahrÃĄvania", "backup_options": "MoÅžnosti zÃĄlohovania", + "backup_options_page_title": "MoÅžnosti zÃĄlohovania", + "backup_setting_subtitle": "SpravovaÅĨ nastavenia odosielania na pozadí a v popredí", "backup_settings_subtitle": "SpravovaÅĨ nastavenia nahrÃĄvania", + "backup_upload_details_page_more_details": "Ťukni pre viac info", "backward": "Dozadu", - "battery_optimization_backup_reliability": "Vypnutie optimalizÃĄcie batÊrie môŞe zvÃŊÅĄiÅĨ spoÄžahlivosÅĨ zÃĄlohovania na pozadí", "biometric_auth_enabled": "BiometrickÊ overovanie je povolenÊ", "biometric_locked_out": "Ste vymknutí z biometrickÊho overovania", "biometric_no_options": "Nie sÃē k dispozícii Åžiadne biometrickÊ moÅžnosti", "biometric_not_available": "BiometrickÊ overenie nie je v tomto zariadení k dispozícii", + "birthdate_saved": "DÃĄtum narodenia bol ÃēspeÅĄne uloÅženÃŊ", "birthdate_set_description": "DÃĄtum narodenia sa pouŞíva na vÃŊpočet veku tejto osoby v čase fotografie.", "blurred_background": "RozmazanÊ pozadie", - "browse_templates": "PrezeraÅĨ ÅĄablÃŗny", "bugs_and_feature_requests": "Chyby a poÅžiadavky na funkcie", "build": "Zostava", "build_image": "Obraz zostavy", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Naozaj chcete ponechaÅĨ {count, plural, one {# duplicitnÃē poloÅžku} few {# duplicitnÊ poloÅžky} other {# duplicitnÃŊch poloÅžiek}}? TÃŊmto sa vyrieÅĄia vÅĄetky duplicitnÊ skupiny bez toho, aby sa čokoÄžvek odstrÃĄnilo.", "bulk_trash_duplicates_confirmation": "Naozaj chcete hromadne vymazaÅĨ {count, plural, one {# duplicitnÃē poloÅžku} few {# duplicitnÊ poloÅžky} other {# duplicitnÃŊch poloÅžiek}}? TÃŊmto sa zachovÃĄ najvÃ¤ÄÅĄia poloÅžka z kaÅždej skupiny a vÅĄetky ostatnÊ duplicitnÊ poloÅžky sa vyhodia.", "buy": "KÃēpiÅĨ Immich", + "cache_settings_clear_cache_button": "VymazaÅĨ vyrovnÃĄvaciu pamäÅĨ", + "cache_settings_clear_cache_button_title": "VymaÅže vyrovnÃĄvaciu pamäÅĨ aplikÃĄcie. To vÃŊrazne ovplyvní vÃŊkon aplikÃĄcie, kÃŊm sa vyrovnÃĄvacia pamäÅĨ neobnoví.", + "cache_settings_duplicated_assets_clear_button": "VYČISTIŤ", + "cache_settings_duplicated_assets_subtitle": "Fotografie a videÃĄ, ktorÊ aplikÃĄcia ignoruje podÄža zoznamu", + "cache_settings_duplicated_assets_title": "DuplicitnÊ poloÅžky ({count})", + "cache_settings_statistics_album": "KniÅžnica nÃĄhÄžadov", + "cache_settings_statistics_full": "KompletnÊ fotografie", + "cache_settings_statistics_shared": "ZdieÄžanÊ nÃĄhÄžady albumov", + "cache_settings_statistics_thumbnail": "NÃĄhÄžady", + "cache_settings_statistics_title": "PouÅžitie vyrovnÃĄvacej pamäte", + "cache_settings_subtitle": "OvlÃĄdanie sprÃĄvania mobilnej aplikÃĄcie Immich v medzipamäti", + "cache_settings_tile_subtitle": "OvlÃĄdanie sprÃĄvania lokÃĄlneho ÃēloÅžiska", + "cache_settings_tile_title": "LokÃĄlne ÃēloÅžisko", + "cache_settings_title": "Nastavenia vyrovnÃĄvacej pamäte", "camera": "FotoaparÃĄt", "camera_brand": "Značka fotoaparÃĄtu", "camera_model": "Model fotoaparÃĄtu", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Popis nie je moÅžnÊ aktualizovaÅĨ", "cast": "Prenos (cast)", "cast_description": "Nastavte dostupnÊ ciele prenosu", - "change": "ZmeniÅĨ", "change_date": "UpraviÅĨ dÃĄtum", "change_description": "ZmeniÅĨ popis", "change_display_order": "ZmeniÅĨ poradie zobrazenia", + "change_expiration_time": "ZmeniÅĨ čas vyprÅĄania", "change_location": "ZmeniÅĨ polohu", "change_name": "UpraviÅĨ meno", "change_name_successfully": "Meno bolo zmenenÊ", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "HeslÃĄ sa nezhodujÃē", "change_password_form_reenter_new_password": "Znova zadajte novÊ heslo", "change_pin_code": "ZmeniÅĨ PIN kÃŗd", + "change_trigger": "ZmeniÅĨ spÃēÅĄtač", + "change_trigger_prompt": "Naozaj chcete zmeniÅĨ spÃēÅĄÅĨač? TÃŊmto krokom sa odstrÃĄnia vÅĄetky existujÃēce akcie a filtre.", "change_your_password": "ZmeniÅĨ heslo", "changed_visibility_successfully": "ViditeÄžnosÅĨ bola ÃēspeÅĄne zmenenÃĄ", "charging": "Nabíja sa", "charging_requirement_mobile_backup": "ZÃĄlohovanie na pozadí vyÅžaduje, aby bolo zariadenie nabíjanÊ", + "check_corrupt_asset_backup": "SkontrolovaÅĨ, či nie sÃē poÅĄkodenÊ zÃĄlohy poloÅžiek", + "check_corrupt_asset_backup_button": "VykonaÅĨ kontrolu", + "check_corrupt_asset_backup_description": "SpustiÅĨ tÃēto kontrolu len cez Wi-Fi a po zÃĄlohovaní vÅĄetkÃŊch poloÅžiek. Tento postup môŞe trvaÅĨ niekoÄžko minÃēt.", "check_logs": "SkontrolovaÅĨ logy", "checksum": "KontrolnÃŊ sÃēčet", - "choose": "VybraÅĨ", "choose_matching_people_to_merge": "Vyberte rovnakÃŊch Äžudí na zlÃēčenie", "city": "Mesto", "cleanup_confirm_description": "Immich naÅĄiel {count} poloÅžiek (vytvorenÃŊch pred {date}) bezpečne zÃĄlohovanÃŊch na serveri. OdstrÃĄniÅĨ lokÃĄlne kÃŗpie z tohto zariadenia?", @@ -719,10 +774,11 @@ "clear": "VyčistiÅĨ", "clear_all": "VyčistiÅĨ vÅĄetko", "clear_all_recent_searches": "VyčistiÅĨ nedÃĄvne vyhÄžadÃĄvania", - "clear_failed_count": "Vyčistenie sa nepodarilo ({count})", "clear_file_cache": "VyčistiÅĨ vyrovnÃĄvaciu pamäÅĨ sÃēborov", "clear_message": "VymazaÅĨ sprÃĄvu", "clear_value": "VymazaÅĨ hodnotu", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "ZadaÅĨ heslo", "client_cert_import": "ImportovaÅĨ", "client_cert_import_success_msg": "CertifikÃĄt klienta je naimportovanÃŊ", "client_cert_invalid_msg": "NeplatnÃŊ sÃēbor certifikÃĄtu alebo nesprÃĄvne heslo", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "CertifikÃĄt klienta je odstrÃĄnenÃŊ", "client_cert_subtitle": "Podporuje iba formÃĄt PKCS12 (.p12, .pfx). Importovanie/odstrÃĄnenie certifikÃĄtu je k dispozícii len pred prihlÃĄsením", "client_cert_title": "SSL certifikÃĄt klienta [EXPERIMENTÁLNE]", + "clockwise": "V smere hodinovÃŊch ručičiek", "close": "ZatvoriÅĨ", "collapse": "ZbaliÅĨ", "collapse_all": "ZbaliÅĨ vÅĄetko", "color": "Farba", + "color_theme": "Farba tÊmy", "command": "Príkaz", "command_palette_prompt": "RÃŊchlo vyhÄžadajte strÃĄnky, akcie alebo príkazy ako", "command_palette_to_close": "zatvoriÅĨ", @@ -747,7 +805,6 @@ "comments_are_disabled": "KomentÃĄre sÃē vypnutÊ", "common_create_new_album": "VytvoriÅĨ novÃŊ album", "completed": "DokončenÊ", - "configuration": "Nastavenie", "confirm": "PotvrdiÅĨ", "confirm_admin_password": "PotvrdiÅĨ heslo sprÃĄvcu", "confirm_delete_face": "Naozaj chcete z poloÅžky odstrÃĄniÅĨ tvÃĄr osoby {name}?", @@ -762,16 +819,19 @@ "contain": "PrispôsobiÅĨ", "context": "Kontext", "continue": "PokračovaÅĨ", - "control_bottom_app_bar_add_tags": "PridaÅĨ ÅĄtítky", + "control_bottom_app_bar_create_new_album": "VytvoriÅĨ novÃŊ album", + "control_bottom_app_bar_delete_from_immich": "VymazaÅĨ z aplikÃĄcie Immich", "control_bottom_app_bar_delete_from_local": "VymazaÅĨ zo zariadenia", "control_bottom_app_bar_edit_location": "UpraviÅĨ polohu", "control_bottom_app_bar_edit_time": "UpraviÅĨ dÃĄtum a čas", + "control_bottom_app_bar_share_link": "ZdieÄžaÅĨ odkaz", + "control_bottom_app_bar_share_to": "ZdieÄžaÅĨ cez", "control_bottom_app_bar_trash_from_immich": "PresunÃēÅĨ do koÅĄa", "copied_image_to_clipboard": "ObrÃĄzok skopírovanÃŊ do schrÃĄnky.", "copied_to_clipboard": "SkopírovanÊ do schrÃĄnky!", "copy_error": "Chyba pri kopírovaní", + "copy_file_path": "KopírovaÅĨ cestu odkazu", "copy_image": "SkopírovaÅĨ obrÃĄzok", - "copy_json": "KopírovaÅĨ JSON", "copy_link": "SkopírovaÅĨ odkaz", "copy_link_to_clipboard": "SkopírovaÅĨ do schrÃĄnky", "copy_password": "SkopírovaÅĨ heslo", @@ -789,6 +849,7 @@ "create_link_to_share": "VytvoriÅĨ odkaz na zdieÄžanie", "create_link_to_share_description": "UmoÅžniÅĨ kaÅždÊmu, kto mÃĄ odkaz, zobraziÅĨ vybranÊ fotografie", "create_new": "VYTVORIŤ NOVÉ", + "create_new_face": "VytvoriÅĨ novÃē tvÃĄr", "create_new_person": "VytvoriÅĨ novÃē osobu", "create_new_person_hint": "PriradiÅĨ vybranÊ poloÅžky novej osobe", "create_new_user": "Vytvorenie novÊho pouŞívateÄža", @@ -805,33 +866,37 @@ "created_at": "VytvorenÊ", "creating_linked_albums": "VytvÃĄranie prepojenÃŊch albumov...", "crop": "OrezaÅĨ", + "crop_aspect_ratio_fixed": "PevnÃŊ pomer", "crop_aspect_ratio_free": "VoÄžnÃŊ", "crop_aspect_ratio_original": "OriginÃĄlny", "crop_aspect_ratio_square": "Å tvorec", + "curated_object_page_title": "Veci", "current_device": "SÃēčasnÊ zariadenie", "current_pin_code": "AktuÃĄlny PIN kÃŗd", "current_server_address": "AktuÃĄlna adresa servera", "custom_date": "VlastnÃŊ dÃĄtum", "custom_locale": "VlastnÊ nastavenie jazyka", "custom_locale_description": "FormÃĄtovaÅĨ dÃĄtumy, časy a čísla podÄža vybranÊho jazyka a regiÃŗnu", + "custom_url": "VlastnÃĄ URL adresa", "cutoff_date_description": "PonechaÅĨ fotografie z poslednÊho obdobiaâ€Ļ", "cutoff_day": "{count, plural, one {deň} few {dni} other {dní}}", "cutoff_year": "{count, plural, one {rok} few {roky} other {rokov}}", + "daily_title_text_date": "EEEE, d. MMMM", + "daily_title_text_date_year": "EEEE, d. MMMM y", "dark": "TmavÃĄ", "dark_theme": "PrepnÃēÅĨ na tmavÃē tÊmu", "date": "DÃĄtum", "date_after": "DÃĄtum po", "date_and_time": "DÃĄtum a Čas", "date_before": "DÃĄtum pred", - "date_of_birth": "DÃĄtum narodenia", + "date_format": "EEEE, d. MMMM y â€ĸ H:mm", "date_of_birth_saved": "DÃĄtum narodenia ÃēspeÅĄne uloÅženÃŊ", "date_range": "Rozsah dÃĄtumu", - "date_time_original": "PôvodnÃŊ dÃĄtum a čas", "day": "Deň", "days": "Dní", "deduplicate_all": "DeduplikovaÅĨ vÅĄetko", - "default_quality_subtitle": "Kvalita pouÅžitÃĄ pri zdieÄžaní. Na vÃŊber stačí vÅždy dlho stlačiÅĨ tlačidlo zdieÄžania.", - "default_share_quality": "PredvolenÃĄ kvalita zdieÄžania", + "default_locale": "PredvolenÃŊ jazyk", + "default_locale_description": "FormÃĄtovaÅĨ dÃĄtumy a čísla podÄža jazyka vÃĄÅĄho prehliadača", "delete": "VymazaÅĨ", "delete_action_confirmation_message": "Naozaj chcete tÃēto poloÅžku odstrÃĄniÅĨ? TÃĄto akcia presunie poloÅžku do koÅĄa na serveri a zobrazí sa otÃĄzka, či ju chcete odstrÃĄniÅĨ aj lokÃĄlne", "delete_action_prompt": "{count} vymazanÃŊch", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Tieto poloÅžky budÃē natrvalo odstrÃĄnenÊ z aplikÃĄcie Immich a z vÃĄÅĄho zariadenia", "delete_dialog_alert_local": "Tieto poloÅžky budÃē permanentne vymazanÊ z vaÅĄeho zariadenia, ale budÃē stÃĄle k dispozícií na serveri Immich", "delete_dialog_alert_local_non_backed_up": "NiektorÊ poloÅžky nie sÃē zÃĄlohovanÊ na Immich a budÃē permanentne odstrÃĄnenÊ z vÃĄÅĄho zariadenia", + "delete_dialog_alert_remote": "Tieto poloÅžky budÃē permanentne vymazanÊ zo serveru Immich", + "delete_dialog_ok_force": "Napriek tomu vymazaÅĨ", "delete_dialog_title": "VymazaÅĨ natrvalo", "delete_duplicates_confirmation": "Naozaj chcete nenÃĄvratne odstrÃĄniÅĨ tieto duplikÃĄty?", "delete_face": "OdstrÃĄniÅĨ tvÃĄr", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Naozaj chcete odstrÃĄniÅĨ ÅĄtítok menom {tagName}?", "delete_user": "VymazaÅĨ pouŞívateÄža", "deleted_shared_link": "VymazanÃŊ zdieÄžanÃŊ odkaz", + "deletes_missing_assets": "OdstrÃĄni poloÅžky chÃŊbajÃēce na disku", "description": "Popis", + "description_input_hint_text": "PridaÅĨ popis...", + "description_input_submit_error": "Chyba pri aktualizovaní popisu, zobrazte log pre viac detailov", "deselect_all": "ZruÅĄiÅĨ vÃŊber vÅĄetkÃŊch", "details": "Podrobnosti", "direction": "Smer", "disable": "VypnÃēÅĨ", "disabled": "VypnutÊ", + "disallow_edits": "ZakÃĄzaÅĨ Ãēpravy", "discord": "Discord", "discover": "ObjaviÅĨ", "discovered_devices": "ObjavenÊ zariadenia", "dismiss_all_errors": "ZamietnuÅĨ vÅĄetky chyby", + "dismiss_error": "ZamietnuÅĨ chybu", "display_options": "MoÅžnosti zobrazenia", "display_order": "Poradie zobrazenia", "display_original_photos": "ZobraziÅĨ pôvodnÊ fotky", @@ -876,9 +948,11 @@ "documentation": "DokumentÃĄcia", "done": "Hotovo", "download": "StiahnuÅĨ", + "download_action_prompt": "SÅĨahuje sa {count} poloÅžiek", "download_canceled": "Stiahnutie zruÅĄenÊ", "download_complete": "Stiahnutie dokončenÊ", "download_enqueue": "Stiahnutie v poradí", + "download_error": "Chyba sÅĨahovania", "download_failed": "Stiahnutie sa nepodarilo", "download_finished": "Stiahnutie dokončenÊ", "download_include_embedded_motion_videos": "VloÅženÊ videÃĄ", @@ -888,15 +962,15 @@ "download_paused": "Stiahnutie pozastavenÊ", "download_settings": "StiahnuÅĨ", "download_settings_description": "SpravovaÅĨ nastavenia sÃēvisiace so sÅĨahovaním poloÅžiek", + "download_started": "SÅĨahovanie spustenÊ", + "download_sucess": "Stiahnutie ÃēspeÅĄnÊ", + "download_sucess_android": "MÊdiÃĄ boli stiahnutÊ do DCIM/Immich", "download_waiting_to_retry": "ČakÃĄ sa na opakovanie pokusu", "downloading": "SÅĨahuje sa", "downloading_asset_filename": "SÅĨahuje sa poloÅžka {filename}", "downloading_from_icloud": "SÅĨahuje sa z iCloud", "downloading_media": "SÅĨahovanie mÊdií", - "drag_to_reorder": "Potiahnite pre zmenu poradia", "drop_files_to_upload": "Umiestnite sÃēbory kamkoÄžvek na nahratie", - "duplicate": "DuplikovaÅĨ", - "duplicate_workflow": "DuplikovaÅĨ pracovnÃŊ postup", "duplicates": "DuplikÃĄty", "duplicates_description": "VyrieÅĄiÅĨ jednotlivÊ skupiny tak, Åže sa označia tie, ktorÊ z nich sÃē duplicitnÊ, ak nejakÊ sÃē.", "duration": "Trvanie", @@ -908,7 +982,9 @@ "edit_date_and_time": "UpraviÅĨ dÃĄtum a čas", "edit_date_and_time_action_prompt": "{count} dÃĄtum a čas upravenÃŊ", "edit_date_and_time_by_offset": "ZmeniÅĨ dÃĄtum podÄža posunu", + "edit_date_and_time_by_offset_interval": "NovÃŊ rozsah dÃĄtumov: {from} - {to}", "edit_description": "UpraviÅĨ popis", + "edit_description_prompt": "Vyberte prosím novÃŊ popis:", "edit_exclusion_pattern": "UpraviÅĨ vzor vylÃēčenia", "edit_faces": "UpraviÅĨ tvÃĄre", "edit_key": "UpraviÅĨ kÄžÃēč", @@ -923,6 +999,9 @@ "edit_user": "UpraviÅĨ pouŞívateÄža", "edit_workflow": "UpraviÅĨ pracovnÃŊ postup", "editor": "Editor", + "editor_close_without_save_prompt": "Úpravy nebudÃē uloÅženÊ", + "editor_close_without_save_title": "ZavrieÅĨ editor?", + "editor_confirm_reset_all_changes": "Naozaj chcete zruÅĄiÅĨ vÅĄetky zmeny?", "editor_discard_edits_confirm": "ZruÅĄiÅĨ Ãēpravy", "editor_discard_edits_prompt": "MÃĄte neuloÅženÊ Ãēpravy. Naozaj ich chcete zruÅĄiÅĨ?", "editor_discard_edits_title": "ZruÅĄiÅĨ Ãēpravy?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Zadajte svoj PIN kÃŗd", "enter_your_pin_code_subtitle": "Zadaním kÃŗdu PIN získate prístup k zamknutÊmu priečinku", "error": "Chyba", + "error_change_sort_album": "Nepodarilo sa zmeniÅĨ poradie albumu", "error_delete_face": "Chyba pri odstraňovaní tvÃĄre z poloÅžky", + "error_getting_places": "Chyba pri získavaní polôh", "error_loading_albums": "Chyba pri načítaní albumov", "error_loading_image": "Nepodarilo sa načítaÅĨ obrÃĄzok", "error_loading_partners": "Chyba pri načítaní partnerov: {error}", @@ -976,7 +1057,7 @@ "error_deleting_shared_user": "Chyba pri odstraňovaní zdieÄžanÊho pouŞívateÄža", "error_downloading": "Nepodarilo sa stiahnuÅĨ {filename}", "error_hiding_buy_button": "Nepodarilo sa skryÅĨ tlačidlo kÃēpiÅĨ", - "error_removing_assets_from_album": "Nepodarilo sa odobraÅĨ poloÅžku z albumu, podrobnejÅĄie informÃĄcie nÃĄjdete v konzole", + "error_removing_assets_from_album": "Nepodarilo sa odstrÃĄniÅĨ poloÅžku z albumu, podrobnejÅĄie informÃĄcie nÃĄjdete v konzole", "error_selecting_all_assets": "Chyba pri vÃŊbere vÅĄetkÃŊch poloÅžiek", "exclusion_pattern_already_exists": "Tento vzor vylÃēčenia uÅž existuje.", "failed_to_create_album": "Nepodarilo sa vytvoriÅĨ album", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Nepodarilo sa odstrÃĄniÅĨ produktovÃŊ kÄžÃēč", "failed_to_reset_pin_code": "PIN kÃŗd sa nepodarilo obnoviÅĨ", "failed_to_stack_assets": "Nepodarilo sa zoskupiÅĨ poloÅžky", - "failed_to_tag_assets": "Nepodarilo sa priradiÅĨ ÅĄtítky k poloÅžkÃĄm", "failed_to_unstack_assets": "Nepodarilo sa zruÅĄiÅĨ zoskupenie poloÅžiek", "failed_to_update_notification_status": "Nepodarilo sa aktualizovaÅĨ stav oznÃĄmenia", "incorrect_email_or_password": "NesprÃĄvny e-mail alebo heslo", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "PridaÅĨ popis...", "exif_bottom_sheet_description_error": "Chyba pri aktualizÃĄcii popisu", + "exif_bottom_sheet_details": "PODROBNOSTI", + "exif_bottom_sheet_location": "POLOHA", "exif_bottom_sheet_no_description": "ÅŊiadny popis", + "exif_bottom_sheet_people": "ÄŊUDIA", + "exif_bottom_sheet_person_add_person": "PridaÅĨ meno", "exit_slideshow": "OpustiÅĨ prezentÃĄciu", "expand": "RozbaliÅĨ", "expand_all": "RozbaliÅĨ vÅĄetko", + "experimental_settings_new_asset_list_subtitle": "PrebiehajÃēca prÃĄca", + "experimental_settings_new_asset_list_title": "Povolenie experimentÃĄlnej mrieÅžky fotografií", + "experimental_settings_subtitle": "PouŞívajte na vlastnÊ riziko!", + "experimental_settings_title": "ExperimentÃĄlne", "expire_after": "PlatnosÅĨ vyprÅĄÃ­", "expired": "VyprÅĄalo", "expires_date": "Expiruje {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "ExportovaÅĨ do JSON", "export_database": "ExportovaÅĨ databÃĄzu", "export_database_description": "ExportovaÅĨ databÃĄzu SQLite", - "exposure_time": "Doba expozície", "extension": "Prípona", "external": "ExternÃĄ", "external_libraries": "ExternÊ kniÅžnice", "external_network": "ExternÃĄ sieÅĨ", "external_network_sheet_info": "Ak nie ste v preferovanej sieti Wi-Fi, aplikÃĄcia sa pripojí k serveru prostredníctvom prvej z niÅžÅĄie uvedenÃŊch adries URL, na ktorÃē sa dostane, počnÃēc zhora nadol", - "f_number": "Číslo clony", "face_unassigned": "NepriradenÃĄ", "failed": "NeÃēspeÅĄnÊ", "failed_count": "Zlyhalo: {count}", "failed_to_authenticate": "Nepodarilo sa overiÅĨ", - "failed_to_delete_file": "OdstrÃĄnenie sÃēboru sa nepodarilo", "failed_to_load_assets": "Nepodarilo sa načítaÅĨ poloÅžky", "failed_to_load_folder": "Nepodarilo sa načítaÅĨ priečinok", "favorite": "ObÄžÃēbenÊ", "favorite_action_prompt": "{count} pridanÊ do obÄžÃēbenÃŊch", "favorite_or_unfavorite_photo": "OznačiÅĨ fotku ako obÄžÃēbenÃē alebo neobÄžÃēbenÃē", "favorites": "ObÄžÃēbenÊ", + "favorites_page_no_favorites": "ÅŊiadne obÄžÃēbenÊ mÊdiÃĄ", "feature_photo_updated": "ProfilovÃĄ fotka bola aktualizovanÃĄ", "features": "Funkcie", + "features_in_development": "Funkcie vo vÃŊvoji", "features_setting_description": "SpravovaÅĨ funkcie aplikÃĄcie", "file_name_or_extension": "NÃĄzov alebo prípona sÃēboru", "file_name_text": "NÃĄzov sÃēboru", + "file_name_with_value": "NÃĄzov sÃēboru: {file_name}", "file_size": "VeÄžkosÅĨ sÃēboru", "filename": "NÃĄzov sÃēboru", "filetype": "Typ sÃēboru", "filter": "Filter", + "filter_description": "Podmienky na filtrovanie cieÄžovÃŊch poloÅžiek", "filter_people": "FiltrovaÅĨ Äžudí", "filter_places": "FiltrovaÅĨ miesta", "filter_tags": "FiltrovaÅĨ značky", "filters": "Filtre", + "find_them_fast": "NÃĄjdite ich rÃŊchlejÅĄie podÄža mena", "first": "PrvÊ", "fix_incorrect_match": "OpraviÅĨ nesprÃĄvnu zhodu", - "focal_length": "OhniskovÃĄ vzdialenosÅĨ", "folder": "Priečinok", "folder_not_found": "Priečinok nebol nÃĄjdenÃŊ", "folders": "Priečinky", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Presuňte zÃĄlohovanÊ fotografie a videÃĄ do koÅĄa vÃĄÅĄho zariadenia, aby ste uvoÄžnili miesto. VaÅĄe kÃŗpie na serveri zostanÃē v bezpečí.", "free_up_space_settings_subtitle": "UvoÄžniÅĨ ÃēloÅžisko zariadenia", "full_path": "CelÃĄ cesta: {path}", - "full_path_or_folder": "ÚplnÃĄ cesta alebo priečinok", "gcast_enabled": "Google Cast", "gcast_enabled_description": "TÃĄto funkcia načítava externÊ zdroje zo spoločnosti Google, aby mohla fungovaÅĨ.", "general": "VÅĄeobecnÊ", @@ -1168,6 +1256,7 @@ "group_owner": "ZoskupiÅĨ podÄža vlastníka", "group_places_by": "Zoskupte miesta podÄža...", "group_year": "ZoskupiÅĨ podÄža roku", + "haptic_feedback_switch": "PovoliÅĨ hmatovÃē odozvu", "haptic_feedback_title": "HmatovÃĄ odozva", "has_quota": "MÃĄ kvÃŗtu", "hash_asset": "HashovaÅĨ poloÅžku", @@ -1188,12 +1277,29 @@ "hide_schema": "SkryÅĨ schÊmu", "hide_text_recognition": "SkryÅĨ rozpoznÃĄvanie textu", "hide_unnamed_people": "SkryÅĨ osoby bez mena", + "home_page_add_to_album_conflicts": "PridanÊ {added} poloÅžiek do albumu {album}. {failed} poloÅžiek uÅž je v albume.", + "home_page_add_to_album_err_local": "ZatiaÄž nie je moÅžnÊ pridaÅĨ lokÃĄlne mÊdia do albumov, preskakuje sa", + "home_page_add_to_album_success": "PridanÊ {added} poloÅžky do albumu {album}.", + "home_page_album_err_partner": "Na teraz nemôŞete pridaÅĨ partnerove mÊdiÃĄ do albumov", + "home_page_archive_err_local": "ZatiaÄž nemoÅžno archivovaÅĨ lokÃĄlne mÊdiÃĄ, preskakuje sa", + "home_page_archive_err_partner": "NemoÅžno archivovaÅĨ partnerskÊ poloÅžky, preskakuje sa", "home_page_building_timeline": "VytvÃĄranie časovej osi", + "home_page_delete_err_partner": "Nie je moÅžnÊ vymazaÅĨ poloÅžky partnera, preskakuje sa", + "home_page_delete_remote_err_local": "Miestne poloÅžky vo vÃŊbere vzdialenÊho odstrÃĄnenia, preskakuje sa", + "home_page_favorite_err_local": "ZatiaÄž nie je moÅžnÊ zaradiÅĨ lokÃĄlne mÊdia medzi obÄžÃēbenÊ, preskakuje sa", + "home_page_favorite_err_partner": "Na teraz nemôŞete pridaÅĨ partnerove mÊdiÃĄ medzi obÄžÃēbenÊ", + "home_page_first_time_notice": "Ak aplikÃĄciu pouŞívate prvÃŊkrÃĄt, uistite sa, Åže ste si vybrali zÃĄloÅžnÃŊ album, aby sa na časovej osi mohli zobrazovaÅĨ fotografie a videÃĄ", + "home_page_locked_error_local": "Nie je moÅžnÊ presunÃēÅĨ miestne poloÅžky do zamknutÊho priečinka, preskakuje sa", + "home_page_locked_error_partner": "Nie je moÅžnÊ presunÃēÅĨ partnerskÊ poloÅžky do zamknutÊho priečinka, preskakuje sa", + "home_page_share_err_local": "NemoÅžno zdieÄžaÅĨ lokÃĄlne mÊdiÃĄ pomocou odkazu", + "home_page_upload_err_limit": "Naraz môŞete nahraÅĨ len 30 mÊdií, preskakuje sa", "host": "HostiteÄž", "hour": "Hodina", "hours": "Hodín", "id": "ID", "idle": "NečinnÊ", + "ignore_icloud_photos": "IgnorovaÅĨ fotky v sluÅžbe iCloud", + "ignore_icloud_photos_description": "Fotografie uloÅženÊ v sluÅžbe iCloud sa nebudÃē odosielaÅĨ na server Immich", "image": "ObrÃĄzok", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} nasnímanÊ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} nasnímanÊ s {person1} dňa {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video nasnímanÊ} other {ObrÃĄzok odfotenÃŊ}} v {city}, {country} s {person1} a {person2} dňa {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video nasnímanÊ} other {ObrÃĄzok odfotenÃŊ}} dňa {date} v {city}, {country} s {person1}, {person2} a {person3}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video nasnímamÊ} other {ObrÃĄzok odfotenÃŊ}} v {city}, {country} s {person1}, {person2} a {additionalCount, number} inÃŊmi dňa {date}", + "image_saved_successfully": "ObrÃĄzok bol uloÅženÃŊ", + "image_viewer_page_state_provider_download_started": "SÅĨahovanie sa začalo", + "image_viewer_page_state_provider_download_success": "SÅĨahovanie bolo ÃēspeÅĄnÊ", + "image_viewer_page_state_provider_share_error": "Chyba zdieÄžania", "immich_logo": "Logo Immich", "immich_web_interface": "WebovÊ rozhranie Immich", "import_from_json": "ImportovaÅĨ z JSON", @@ -1219,7 +1329,6 @@ "individual_share": "ZdieÄžanie jednotlivÃŊch poloÅžiek", "individual_shares": "IndividuÃĄlne zdieÄžanie", "info": "InformÃĄcie", - "integrity_checks": "Kontroly integrity", "interval": { "day_at_onepm": "KaÅždÃŊ deň v 13:00", "hours": "{hours, plural, one {KaÅždÃē hodinu} few {KaÅždÊ {hours, number} hodiny} other {KaÅždÃŊch {hours, number} hodín}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "NeplatnÃŊ formÃĄt dÃĄtumu", "invite_people": "PozvaÅĨ Äžudí", "invite_to_album": "PozvaÅĨ do albumu", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Načítanie prebehlo {dateTime}", + "ios_debug_info_last_sync_at": "PoslednÃĄ synchronizÃĄcia {dateTime}", + "ios_debug_info_no_processes_queued": "ÅŊiadne procesy nie sÃē v poradí na pozadí", + "ios_debug_info_no_sync_yet": "ZatiaÄž nebola spustenÃĄ Åžiadna Ãēloha synchronizÃĄcie na pozadí", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proces na pozadí v poradí} few {{count} procesy na pozadí v poradí} other {{count} procesov na pozadí v poradí}}", + "ios_debug_info_processing_ran_at": "Spracovanie prebehlo {dateTime}", "items_count": "{count, plural, one {# poloÅžka} few {# poloÅžky} other {# poloÅžiek}}", "jobs": "Úlohy", + "json_editor": "Editor JSON", + "json_error": "Chyba JSON", "keep": "PonechaÅĨ", "keep_albums": "PonechaÅĨ albumy", "keep_albums_count": "PonechÃĄ sa {count} {count, plural, one {album} few {albumy} other {albumov}}", @@ -1259,12 +1375,14 @@ "leave": "OpustiÅĨ", "leave_album": "OpustiÅĨ album", "lens_model": "Model objektívu", - "less": "Menej", "let_others_respond": "Nechajte ostatnÃŊch reagovaÅĨ", "level": "Úroveň", "library": "KniÅžnica", "library_add_folder": "PridaÅĨ priečinok", "library_edit_folder": "UpraviÅĨ priečinok", + "library_options": "MoÅžnosti kniÅžnice", + "library_page_device_albums": "Albumy v zariadení", + "library_page_new_album": "NovÃŊ album", "library_page_sort_asset_count": "Počet poloÅžiek", "library_page_sort_created": "NajnovÅĄie vytvorenÊ", "library_page_sort_last_modified": "Naposledy upravenÊ", @@ -1274,14 +1392,11 @@ "light_theme": "PrepnÃēÅĨ na svetlÃē tÊmu", "like": "PÃĄÄi sa mi", "like_deleted": "Like odstrÃĄnenÃŊ", - "link": "Odkaz", "link_motion_video": "PripojiÅĨ pohyblivÊ video", "link_to_docs": "ĎalÅĄie informÃĄcie nÃĄjdete v dokumentÃĄcii.", "link_to_oauth": "PrepojiÅĨ s OAuth", "linked_oauth_account": "PripojenÃŊ OAuth Ãēčet", "list": "Zoznam", - "live": "ÅŊivÊ", - "load_more": "NačítaÅĨ viac", "loading": "Načítavanie", "loading_search_results_failed": "Načítanie vÃŊsledkov hÄžadania sa nepodarilo", "local": "LokÃĄlne", @@ -1310,9 +1425,11 @@ "login": "PrihlÃĄsenie", "login_disabled": "Prihlasovanie bolo vypnutÊ", "login_form_api_exception": "Chyba API. Skontrolujte adresu URL servera a skÃēste to znova.", + "login_form_back_button_text": "SpäÅĨ", "login_form_email_hint": "tvojmail@email.com", "login_form_endpoint_hint": "http://ip-tvojho-servera:port", "login_form_endpoint_url": "URL adresa koncovÊho bodu servera", + "login_form_err_http": "Prosím, uveďte http:// alebo https://", "login_form_err_invalid_email": "NeplatnÃŊ e-mail", "login_form_err_invalid_url": "NeplatnÃĄ URL adresa", "login_form_err_leading_whitespace": "ÚvodnÃĄ medzera", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Chyba prihlÃĄsenia, skontrolujte url adresu servera, e-mail a heslo", "login_form_handshake_exception": "DoÅĄlo k vÃŊnimke Handshake so serverom. Ak pouŞívate certifikÃĄt s vlastnÃŊm podpisom, povoÄžte v nastaveniach podporu certifikÃĄtov s vlastnÃŊm podpisom.", "login_form_password_hint": "heslo", - "login_form_server_empty": "Zadajte URL adresu servera", - "login_form_server_error": "Nepodarilo sa pripojiÅĨ k serveru", - "login_has_been_disabled": "PrihlÃĄsenie bolo deaktivovanÊ", + "login_form_save_login": "ZostaÅĨ prihlÃĄsenÃŊ", + "login_form_server_empty": "Zadajte URL adresu servera.", + "login_form_server_error": "NemoÅžno pripojiÅĨ na server.", + "login_has_been_disabled": "PrihlÃĄsenie bolo vypnutÊ.", "login_password_changed_error": "Nastala chyba pri aktualizovaní hesla", "login_password_changed_success": "AktualizÃĄcia hesla prebehla ÃēspeÅĄne", "logout_all_device_confirmation": "Ste si istÃŊ, Åže sa chcete odhlÃĄsiÅĨ zo vÅĄetkÃŊch zariadení?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Obnovuje sa databÃĄza", "maintenance_description": "Immich bol prepnutÃŊ do reÅžimu ÃēdrÅžby.", "maintenance_end": "UkončiÅĨ reÅžim ÃēdrÅžby", + "maintenance_end_error": "Nepodarilo sa ukončiÅĨ reÅžim ÃēdrÅžby.", "maintenance_logged_in_as": "AktuÃĄlne prihlÃĄsenÃŊ ako {user}", "maintenance_restore_from_backup": "ObnoviÅĨ zo zÃĄlohy", "maintenance_restore_library": "Obnovte svoju kniÅžnicu", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "OtvoriÅĨ nastavenia", "manage_media_access_subtitle": "PovoliÅĨ aplikÃĄcii Immich spravovaÅĨ a presÃēvaÅĨ mediÃĄlne sÃēbory.", "manage_media_access_title": "Prístup k sprÃĄve mÊdií", + "manage_shared_links": "SpravovaÅĨ zdieÄžanÊ odkazy", "manage_sharing_with_partners": "SpravovaÅĨ zdieÄžanie s partnermi", "manage_the_app_settings": "SpravovaÅĨ nastavenia aplikÃĄcie", "manage_your_account": "SpravovaÅĨ vÃĄÅĄ Ãēčet", @@ -1371,11 +1491,13 @@ "manage_your_devices": "SpravovaÅĨ vaÅĄe prihlÃĄsenÊ zariadenia", "manage_your_oauth_connection": "SpravovaÅĨ vaÅĄe OAuth spojenia", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {ÅŊiadne fotky v tejto sekcii} one {# fotka} few {# fotky} other {# fotiek}}", "map_cannot_get_user_location": "Nie je moÅžnÊ získaÅĨ polohu pouŞívateÄža", + "map_location_dialog_yes": "Áno", "map_location_picker_page_use_location": "PouÅžiÅĨ tÃēto polohu", "map_location_service_disabled_content": "SluÅžba určovania polohy musí byÅĨ povolenÃĄ, aby sa zobrazovali poloÅžky z vaÅĄej aktuÃĄlnej polohy. Chcete ju teraz zapnÃēÅĨ?", "map_location_service_disabled_title": "SluÅžba určovania polohy vypnutÃĄ", - "map_marker_for_image": "Značka na mape pre fotografiu z miesta {city}, {country}", + "map_marker_for_images": "Značka na mape pre obrÃĄzky odfotenÊ v {city}, {country}", "map_marker_with_image": "MapovÃĄ značka pre obrÃĄzok", "map_no_location_permission_content": "Na zobrazenie poloÅžiek z vaÅĄej aktuÃĄlnej polohy je potrebnÊ povolenie na polohu. Chcete to teraz povoliÅĨ?", "map_no_location_permission_title": "Povolenie polohy zamietnutÊ", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Po {days} dňoch", "map_settings_date_range_option_year": "UplynulÃŊ rok", "map_settings_date_range_option_years": "Po {years} rokoch", + "map_settings_dialog_title": "Nastavenia mÃĄp", "map_settings_include_show_archived": "ZahrnÃēÅĨ archivovanÊ", "map_settings_include_show_partners": "ZahrnÃēÅĨ partnerov", "map_settings_only_show_favorites": "ZobraziÅĨ iba obÄžÃēbenÊ", "map_settings_theme_settings": "TÊma mapy", + "map_zoom_to_see_photos": "OddiaÄžte priblíŞenie aby ste videli fotky", "mark_all_as_read": "OznačiÅĨ vÅĄetko ako prečítanÊ", + "mark_as_read": "OznačiÅĨ ako prečítanÊ", "marked_all_as_read": "OznačenÊ vÅĄetko ako prečítanÊ", "matches": "Zhody", "matching_assets": "VyhovujÃēce poloÅžky", - "media_chrome": { - "auto": "Automaticky", - "captions": "Titulky", - "captions_off": "VypnutÊ", - "closed_captions": "skrytÊ titulky", - "decode_error": "Chyba dekÃŗdovania", - "disable_captions": "VypnÃēÅĨ titulky", - "enable_captions": "ZapnÃēÅĨ titulky", - "enter_fullscreen_mode": "PrejsÅĨ do reÅžimu celej obrazovky", - "exit_fullscreen_mode": "UkončiÅĨ reÅžim celej obrazovky", - "loop": "OpakovaÅĨ", - "media_error_description": "PrehrÃĄvanie bolo preruÅĄenÊ kvôli chybe sÃēboru. SÃēbor môŞe byÅĨ poÅĄkodenÃŊ alebo vÃĄÅĄ prehliadač tento formÃĄt nepodporuje.", - "media_loading": "načítavanie mÊdia", - "mute": "StlmiÅĨ", - "network_error": "Chyba siete", - "network_error_description": "Stiahnutie sÃēboru zlyhalo kvôli chybe siete.", - "not_supported_error": "Zdroj nie je podporovanÃŊ", - "playback_rate": "RÃŊchlosÅĨ prehrÃĄvania", - "playback_rate_current": "aktuÃĄlna rÃŊchlosÅĨ prehrÃĄvania", - "playback_rate_value": "RÃŊchlosÅĨ prehrÃĄvania {playbackRate}", - "playback_time": "dÄēÅžka prehrÃĄvania", - "quality": "Kvalita", - "second": "sekunda", - "seconds": "sekÃēnd", - "time_value_of_total_time": "{currentTime} z {totalTime}", - "time_value_remaining": "zostÃĄva {time}", - "unmute": "ZapnÃēÅĨ zvuk", - "unsupported_error_description": "Vyskytla sa nepodporovanÃĄ chyba. DoÅĄlo k poruche servera alebo siete, alebo vÃĄÅĄ prehliadač tento formÃĄt nepodporuje.", - "video_not_loaded_unknown_time": "video sa nenačítalo, neznÃĄmy čas.", - "video_player": "prehrÃĄvač videa", - "volume": "hlasitosÅĨ" - }, "media_type": "Typ mÊdia", "memories": "Spomienky", "memories_all_caught_up": "Na dnes to je vÅĄetko", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Chcete zlÃēčiÅĨ tÃŊchto Äžudí? TÃĄto akcia sa nedÃĄ vrÃĄtiÅĨ.", "merge_people_successfully": "ZlÃēčenie Äžudí sa podarilo", "merged_people_count": "{count, plural, one {ZlÃēčenÃĄ # osoba} few {ZlÃēčenÊ # osoby} other {ZlÃēčenÃŊch # osôb}}", - "minFaces": "MinimÃĄlny počet tvÃĄrí", - "minFaces_description": "MinimÃĄlny počet rozpoznanÃŊch tvÃĄrí, aby sa danÃĄ osoba zobrazila", "minimize": "MinimalizovaÅĨ", "minute": "MinÃēta", "minutes": "MinÃēt", + "mirror_horizontal": "HorizontÃĄlne", + "mirror_vertical": "VertikÃĄlne", "missing": "ChÃŊbajÃēce", "mobile_app": "MobilnÃĄ aplikÃĄcia", "mobile_app_download_onboarding_note": "Stiahnite si sprievodnÃē mobilnÃē aplikÃĄciu pomocou nasledujÃēcich moÅžností", "model": "Model", - "modify_date": "DÃĄtum Ãēpravy", "month": "Mesiac", + "monthly_title_text_date_format": "LLLL y", "more": "Viac", - "motion": "Pohyb", "move": "PresunÃēÅĨ", + "move_down": "PresunÃēÅĨ dole", "move_off_locked_folder": "PresunÃēÅĨ zo zamknutÊho priečinka", "move_to": "PresunÃēÅĨ do", "move_to_device_trash": "PresunÃēÅĨ do koÅĄa na zariadení", "move_to_lock_folder_action_prompt": "{count} pridanÃŊch do zamknutÊho priečinka", "move_to_locked_folder": "PresunÃēÅĨ do zamknutÊho priečinka", "move_to_locked_folder_confirmation": "Tieto fotografie a videÃĄ budÃē odobranÊ zo vÅĄetkÃŊch albumov a bude ich moÅžnÊ zobraziÅĨ len v zamknutom priečinku", + "move_up": "PresunÃēÅĨ hore", + "moved_to_archive": "{count, plural, one {PresunutÃĄ # poloÅžka} few {PresunutÊ # poloÅžky} other {PresunutÃŊch # poloÅžiek}} do archívu", + "moved_to_library": "{count, plural, one {PresunutÃĄ # poloÅžka} few {PresunutÊ # poloÅžky} other {PresunutÃŊch # poloÅžiek}} do kniÅžnice", "moved_to_trash": "PresunutÊ do koÅĄa", + "multiselect_grid_edit_date_time_err_read_only": "NemoÅžno upraviÅĨ dÃĄtum poloÅžky len na čítanie, preskakujem", + "multiselect_grid_edit_gps_err_read_only": "Nie je moÅžnÊ upraviÅĨ polohu poloÅžky (poloÅžiek), ktorÃĄ je len na čítanie, preskakuje sa", "mute_memories": "Vyblednutie spomienok", "my_albums": "Moje albumy", - "my_immich_description": "SkopírovaÅĨ aktuÃĄlnu strÃĄnku ako Môj Immich odkaz", - "my_immich_title": "Môj Immich odkaz", "name": "Meno", "name_or_nickname": "Meno alebo prezÃŊvka", "name_required": "Meno je povinnÊ", @@ -1480,11 +1576,12 @@ "never": "nikdy", "new_album": "NovÃŊ album", "new_api_key": "NovÃŊ API kÄžÃēč", - "new_feature": "NovÃĄ funkcia", + "new_date_range": "NovÃŊ rozsah dÃĄtumov", "new_password": "NovÊ heslo", "new_person": "NovÃĄ osoba", "new_pin_code": "NovÃŊ PIN kÃŗd", "new_pin_code_subtitle": "Toto je vÃĄÅĄ prvÃŊ prístup k zamknutÊmu priečinku. Vytvorte si PIN kÃŗd na bezpečnÃŊ prístup k tejto strÃĄnke", + "new_timeline": "NovÃĄ časovÃĄ os", "new_update": "NovÃĄ aktualizÃĄcia", "new_user_created": "NovÃŊ pouŞívateÄž vytvorenÃŊ", "new_version_available": "JE DOSTUPNÁ NOVÁ VERZIA", @@ -1492,6 +1589,7 @@ "next": "Ďalej", "next_memory": "ĎalÅĄia spomienka", "no": "Nie", + "no_actions_added": "ZatiaÄž neboli pridanÊ Åžiadne akcie", "no_albums_found": "NenaÅĄli sa Åžiadne albumy", "no_albums_message": "Vytvorte album na usporiadanie svojich fotiek a videí", "no_albums_with_name_yet": "VyzerÃĄ, Åže zatiaÄž nemÃĄte album s tÃŊmto nÃĄzvom.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "NenaÅĄli sa Åžiadne zariadenia na prenos", "no_checksum_local": "Kontrola sÃēčtu nie je k dispozícii – nie je moÅžnÊ načítaÅĨ lokÃĄlne poloÅžky", "no_checksum_remote": "Kontrola sÃēčtu nie je k dispozícii – nie je moÅžnÊ načítaÅĨ vzdialenÊ poloÅžky", + "no_configuration_needed": "Nie je potrebnÃĄ Åžiadna konfigurÃĄcia", "no_devices": "ÅŊiadne autorizovanÊ zariadenia", "no_duplicates_found": "NenaÅĄli sa Åžiadne duplicity.", "no_exif_info_available": "Nie sÃē dostupnÊ exif Ãēdaje", "no_explore_results_message": "Nahrajte viac fotiek na objavovanie vaÅĄej zbierky.", "no_favorites_message": "Pridajte si obÄžÃēbenÊ, aby ste rÃŊchlo naÅĄli svoje najlepÅĄie obrÃĄzky a videÃĄ", + "no_filters_added": "ZatiaÄž neboli pridanÊ Åžiadne filtre", "no_libraries_message": "Vytvorte externÃē kniÅžnicu na prezeranie fotiek a videí", "no_local_assets_found": "Neboli nÃĄjdenÊ Åžiadne lokÃĄlne poloÅžky s touto kontrolnou sumou", + "no_location_set": "Nie je nastavenÃĄ Åžiadna poloha", "no_locked_photos_message": "Fotografie a videÃĄ v zamknutom priečinku sÃē skrytÊ a nezobrazujÃē sa pri prehÄžadÃĄvaní alebo vyhÄžadÃĄvaní v kniÅžnici.", "no_name": "Bez mena", "no_notifications": "ÅŊiadne oznÃĄmenia", @@ -1518,19 +1619,14 @@ "no_results": "ÅŊiadne vÃŊsledky", "no_results_description": "SkÃēste synonymum alebo vÅĄeobecnejÅĄÃ­ vÃŊraz", "no_shared_albums_message": "Vytvorte album na zdieÄžanie fotiek a videí s Äžuďmi vo vaÅĄej sieti", - "no_steps": "ZatiaÄž neboli pridanÊ Åžiadne kroky", + "no_uploads_in_progress": "ÅŊiadne prebiehajÃēce nahrÃĄvanie", "none": "ÅŊiadne", "not_allowed": "NepovolenÊ", "not_available": "NedostupnÊ", "not_in_any_album": "Nie je v Åžiadnom albume", "not_selected": "NevybranÊ", - "not_set": "NenastavenÊ", "notes": "PoznÃĄmky", "nothing_here_yet": "ZatiaÄž tu nič nie je", - "notification_backup_reliability": "ZapnÃēÅĨ upozornenia na zlepÅĄenie spoÄžahlivosti zÃĄlohovania na pozadí", - "notification_enabled_list_tile_content": "AplikÃĄcia Immich vyuŞíva upozornenia na zÃĄlohovanie na pozadí. MôŞete ich spravovaÅĨ v nastaveniach zariadenia.", - "notification_enabled_list_tile_open_button": "OtvoriÅĨ nastavenia", - "notification_enabled_list_tile_title": "OznÃĄmenia sÃē zapnutÊ", "notification_permission_dialog_content": "Ak chcete povoliÅĨ upozornenia, prejdite do Nastavenia a vyberte moÅžnosÅĨ PovoliÅĨ.", "notification_permission_list_tile_content": "UdeÄžte povolenie na zapnutie oznÃĄmení.", "notification_permission_list_tile_enable_button": "PovoliÅĨ oznÃĄmenia", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "KonfigurÃĄtor Obtainium", "obtainium_configurator_instructions": "PouÅžite Obtainium na inÅĄtalÃĄciu a aktualizÃĄciu aplikÃĄcie pre Android priamo z verzie Immich v sluÅžbe GitHub. Vytvorte kÄžÃēč API a vyberte variantu, aby ste vytvorili konfiguračnÃŊ odkaz Obtainium", "ocr": "OCR", - "ocr_body": "Immich teraz detekuje text vo VaÅĄich fotkÃĄch, takÅže môŞete vyhÄžadÃĄvaÅĨ aj v tÃŊchto textoch.", - "ocr_title": "VyhÄžadÃĄvaÅĨ text vo VaÅĄich fotkÃĄch", "official_immich_resources": "OficiÃĄlne Immich zdroje", "offline": "Offline", "offset": "Posun", @@ -1562,8 +1656,6 @@ "open": "OtvoriÅĨ", "open_calendar": "OtvoriÅĨ kalendÃĄr", "open_in_browser": "OtvoriÅĨ v prehliadači", - "open_in_immich_body": "Nastavte vo svojom Androide aplikÃĄciu Immich ako svoju galÊriu, aby ste mohli otvÃĄrat fotografie priamo z inÃŊch aplikÃĄcií.", - "open_in_immich_title": "OtvoriÅĨ fotografie v Immich", "open_in_map_view": "OtvoriÅĨ v mape", "open_in_openstreetmap": "OtvoriÅĨ v OpenStreetMap", "open_the_search_filters": "OtvoriÅĨ vyhÄžadÃĄvacie filtre", @@ -1572,10 +1664,10 @@ "organize_into_albums": "UsporiadaÅĨ do albumov", "organize_into_albums_description": "VloÅžiÅĨ existujÃēce fotky do albumov podÄža aktuÃĄlnych nastavení synchronizÃĄcie", "organize_your_library": "Usporiadajte svoju kniÅžnicu", - "orientation": "OrientÃĄcia", "original": "originÃĄl", "other": "OstatnÊ", "other_devices": "ĎalÅĄie zariadenia", + "other_entities": "OstatnÊ subjekty", "other_variables": "OstatnÊ premennÊ", "owned": "VlastnÊ", "owner": "Vlastník", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "VÅĄetky vaÅĄe fotky a videÃĄ, okrem ArchivovanÃŊch a OdstrÃĄnenÃŊch", "partner_can_access_location": "Poloha, kde boli vaÅĄe fotografie nasnímanÊ", "partner_list_user_photos": "Fotky pouŞívateÄža {user}", + "partner_list_view_all": "ZobraziÅĨ vÅĄetky", "partner_page_empty_message": "VaÅĄe fotky zatiaÄž nie sÃē zdieÄžanÊ so Åžiadnym partnerom.", "partner_page_no_more_users": "ÅŊiadni ďalÅĄÃ­ pouŞívatelia na pridanie", + "partner_page_partner_add_failed": "PridÃĄvanie partnera zlyhalo", "partner_page_select_partner": "ZvoliÅĨ partnera", + "partner_page_shared_to_title": "ZdieÄžanÊ pre", "partner_page_stop_sharing_content": "{partner} uÅž nebude maÅĨ prístup ku vaÅĄim fotkÃĄm.", "partner_sharing": "ZdieÄžanie s partnerom", "partners": "Partneri", @@ -1609,6 +1704,8 @@ "people": "ÄŊudia", "people_edits_count": "{count, plural, one {UpravenÃĄ # osoba} few {UpravenÊ # osoby} other {UpravenÃŊch # osôb}}", "people_feature_description": "Prehliadanie fotiek a videí zoskupenÃŊch podÄža Äžudí", + "people_selected": "{count, plural, one {# vybranÃĄ osoba} few {# vybranÊ osoby} other {# vybranÃŊch osôb}}", + "people_sidebar_description": "ZobraziÅĨ odkaz na ÄŊudí v bočnom paneli", "permanent_deletion_warning": "Varovanie o trvalom zmazaní", "permanent_deletion_warning_setting_description": "ZobraziÅĨ varovanie pri trvalom zmazaní poloÅžky", "permanently_delete": "Trvalo zmazaÅĨ", @@ -1618,6 +1715,14 @@ "permanently_deleted_assets_count": "Natrvalo {count, plural, one {odstrÃĄnenÃĄ # poloÅžka} few {odstrÃĄnenÊ # poloÅžky} other {odstrÃĄnenÃŊch # poloÅžiek}}", "permission": "Povolenie", "permission_empty": "VaÅĄe povolenie by nemalo byÅĨ prÃĄzdne", + "permission_onboarding_back": "SpäÅĨ", + "permission_onboarding_continue_anyway": "PokračovaÅĨ aj tak", + "permission_onboarding_get_started": "ZačaÅĨ", + "permission_onboarding_go_to_settings": "PrejsÅĨ do nastavení", + "permission_onboarding_permission_denied": "Prístup zamietnutÃŊ. Ak chcete pouŞívaÅĨ Immich, udeÄžte v Nastaveniach povolenia na fotografie a videÃĄ.", + "permission_onboarding_permission_granted": "Povolenie udelenÊ! VÅĄetko je nastavenÊ.", + "permission_onboarding_permission_limited": "Povolenie obmedzenÊ. Ak chcete, aby Immich zÃĄlohoval a spravoval celÃē vaÅĄu zbierku galÊrie, udeÄžte v Nastaveniach povolenia na fotografie a videÃĄ.", + "permission_onboarding_request": "Immich vyÅžaduje povolenie na prezeranie vaÅĄich fotografií a videí.", "person": "Osoba", "person_age_months": "mÃĄ {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", "person_age_year_months": "mÃĄ 1 rok, {months, plural, one {# mesiac} few {# mesiace} other {# mesiacov}}", @@ -1625,10 +1730,13 @@ "person_birthdate": "NarodenÃŊ/ÃĄ dňa {date}", "person_hidden": "{name}{hidden, select, true { (skrytÊ)} other {}}", "person_recognized": "Osoba rozpoznanÃĄ", + "person_selected": "Osoba vybranÃĄ", "photo_shared_all_users": "VyzerÃĄ, Åže zdieÄžate svoje fotky so vÅĄetkÃŊmi pouŞívateÄžmi alebo nemÃĄte Åžiadnych pouŞívateÄžov.", "photos": "Fotografie", "photos_and_videos": "Fotografie a videÃĄ", + "photos_count": "{count, plural, one {{count, number} fotka} few {{count, number} fotky} other {{count, number} fotiek}}", "photos_from_previous_years": "Fotky z minulÃŊch rokov", + "photos_only": "Iba fotky", "pick_a_location": "Vyberte polohu", "pick_custom_range": "VlastnÃŊ rozsah", "pick_date_range": "VybraÅĨ rozsah dÃĄtumov", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Uprednostňujte prehrÃĄvanie originÃĄlnych videí pred prekÃŗdovanÃŊmi videami. Ak originÃĄlny sÃēbor nie je kompatibilnÃŊ, nemusí sa sprÃĄvne prehraÅĨ.", "play_transcoded_video": "PrehraÅĨ prekÃŗdovanÊ video", "please_auth_to_access": "Prosím, potvrďte overenie pre prístup", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "TÃĄto metÃŗda dokÃĄÅže filtrovaÅĨ udalosti a podmienečne zabrÃĄniÅĨ spusteniu nasledujÃēcich krokov", "port": "Port", "preferences_settings_subtitle": "SpravovaÅĨ predvoÄžby aplikÃĄcie", "preferences_settings_title": "PredvoÄžby", @@ -1665,11 +1771,11 @@ "privacy": "SÃēkromie", "profile": "Profil", "profile_drawer_app_logs": "ZÃĄznamy", + "profile_drawer_client_server_up_to_date": "Klient a server sÃē aktuÃĄlne", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "ReÅžim iba na čítanie je aktivovanÃŊ. DlhÃŊm stlačením ikony obrÃĄzku pouŞívateÄža reÅžim opustíte.", "profile_image_of_user": "ProfilovÃŊ obrÃĄzok pouŞívateÄža {user}", "profile_picture_set": "ProfilovÃŊ obrÃĄzok nastavenÃŊ.", - "projection_type": "Typ projekcie", "public_album": "VerejnÃŊ album", "public_share": "VerejnÊ zdieÄžanie", "purchase_account_info": "PodporovateÄž", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Å tatÃēt podporovateÄža", "purchase_individual_title": "Jednotlivec", "purchase_input_suggestion": "MÃĄte produktovÃŊ kÄžÃēč? Zadajte ho niÅžÅĄie", + "purchase_license_subtitle": "KÃēpte si Immich a podporte neustÃĄly vÃŊvoj tejto sluÅžby", "purchase_lifetime_description": "DoÅživotnÃĄ platnosÅĨ", "purchase_option_title": "MOÅŊNOSTI NÁKUPU", "purchase_panel_info_1": "VÃŊvoj aplikÃĄcie Immich zaberÃĄ veÄža času a Ãēsilia, pričom na ňom pracujÃē inÅžinieri na plnÃŊ Ãēväzok, aby bol čo najlepÅĄÃ­. NaÅĄÃ­m poslaním je, aby sa softvÊr s otvorenÃŊm zdrojovÃŊm kÃŗdom a etickÊ obchodnÊ postupy stali udrÅžateÄžnÃŊm zdrojom príjmov pre vÃŊvojÃĄrov a aby sme vytvorili ekosystÊm reÅĄpektujÃēci sÃēkromie so skutočnÃŊmi alternatívami k zneuŞívajÃēcim cloudovÃŊm sluÅžbÃĄm.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Opätovne {count, plural, one {priradenÃĄ # poloÅžka} few {priradenÊ # poloÅžky} other {priradenÃŊch # poloÅžiek}} novej osobe", "reassing_hint": "Priradí zvolenÃē poloÅžku k existujÃēcej osobe", "recent": "NedÃĄvne", + "recent_albums": "PoslednÊ albumy", "recent_searches": "PoslednÊ vyhÄžadÃĄvania", "recently_added": "NedÃĄvno pridanÊ", - "recently_added_body": "Prejdite priamo na strÃĄnku, kde nÃĄjdete naposledy pridanÊ fotky.", - "recently_added_description": "PrezeraÅĨ fotky zoradenÊ podÄža dÃĄtumu pridania do Immich", "recently_added_page_title": "NedÃĄvno pridanÊ", - "recently_added_title": "Naposledy pridanÊ", "recently_taken": "NedÃĄvno nasnímanÊ", + "recently_taken_page_title": "NedÃĄvno zhotovenÊ", "refresh": "AktualizovaÅĨ", "refresh_encoded_videos": "ObnoviÅĨ enkÃŗdovanÊ videÃĄ", "refresh_faces": "ObnoviÅĨ tvÃĄre", "refresh_metadata": "ObnoviÅĨ metadÃĄta", "refresh_thumbnails": "ObnoviÅĨ miniatÃēry", "refreshed": "ObnovenÊ", + "refreshes_every_file": "Znova prečíta vÅĄetky existujÃēce a novÊ sÃēbory", "refreshing_encoded_video": "Obnovovanie enkÃŗdovanÃŊch videí", "refreshing_faces": "Obnovovanie tvÃĄrí", "refreshing_metadata": "Obnovovanie metadÃĄt", @@ -1741,12 +1848,12 @@ "remote_assets": "VzdialenÊ poloÅžky", "remote_media_summary": "SÃēhrn vzdialenÃŊch mÊdií", "remove": "OdstrÃĄniÅĨ", - "remove_assets_album_confirmation": "Naozaj chcete odobraÅĨ {count, plural, one {# poloÅžku} few {# poloÅžky} other {# poloÅžiek}} z albumu?", + "remove_assets_album_confirmation": "Naozaj chcete odstrÃĄniÅĨ {count, plural, one {# poloÅžku} few {# poloÅžky} other {# poloÅžiek}} z albumu?", "remove_assets_shared_link_confirmation": "Naozaj chcete odstrÃĄniÅĨ {count, plural, one {# poloÅžku} few {# poloÅžky} other {# poloÅžiek}} z tohoto zdieÄžanÊho odkazu?", "remove_assets_title": "OdstrÃĄniÅĨ poloÅžky?", "remove_custom_date_range": "OdstrÃĄniÅĨ vlastnÃŊ rozsah dÃĄtumov", - "remove_filter": "OdstrÃĄniÅĨ filter", - "remove_from_album": "OdobraÅĨ z albumu", + "remove_deleted_assets": "OdstrÃĄniÅĨ vymazanÊ poloÅžky", + "remove_from_album": "OdstrÃĄniÅĨ z albumu", "remove_from_album_action_prompt": "{count} odstrÃĄnenÊ z albumu", "remove_from_favorites": "OdstrÃĄniÅĨ z obÄžÃēbenÃŊch", "remove_from_lock_folder_action_prompt": "{count} odobranÊ zo zamknutÊho priečinka", @@ -1756,22 +1863,29 @@ "remove_memory": "OdstrÃĄniÅĨ spomienku", "remove_photo_from_memory": "OdstrÃĄniÅĨ fotografiu z tejto spomienky", "remove_tag": "OdstrÃĄniÅĨ ÅĄtítok", + "remove_url": "OdstrÃĄniÅĨ URL", "remove_user": "OdstrÃĄniÅĨ pouŞívateÄža", "removed_api_key": "OdstrÃĄniÅĨ API kÄžÃēč: {name}", "removed_from_archive": "OdstrÃĄnenÊ z archívu", "removed_from_favorites": "OdstrÃĄnenÊ z obÄžÃēbenÃŊch", "removed_from_favorites_count": "{count, plural, other {OdstrÃĄnenÃŊch #}} z obÄžÃēbenÃŊch", "removed_memory": "OdstrÃĄnenÃĄ pamäÅĨ", + "removed_photo_from_memory": "Fotografia odstrÃĄnenÃĄ z pamäte", "removed_tagged_assets": "OdstrÃĄnenÃŊ ÅĄtítok z {count, plural, one {# poloÅžky} other {# poloÅžiek}}", "rename": "PremenovaÅĨ", + "repair": "OpraviÅĨ", + "repair_no_results_message": "NesledovanÊ a chÃŊbajÃēce sÃēbory sa zobrazia tu", + "replace_with_upload": "NahradiÅĨ nahraním", "repository": "RepozitÃĄr", "require_password": "VyÅžadovaÅĨ heslo", + "require_user_to_change_password_on_first_login": "VyÅžadovaÅĨ zmenu hesla po prvom prihlÃĄsení", "rescan": "OpätovnÊ vyhÄžadÃĄvanie", "reset": "ObnoviÅĨ", "reset_password": "ObnoviÅĨ heslo", "reset_people_visibility": "ObnoviÅĨ viditeÄžnosÅĨ Äžudí", "reset_pin_code": "ObnoviÅĨ PIN kÃŗd", "reset_pin_code_description": "Ak ste zabudli svoj PIN kÃŗd, môŞete kontaktovaÅĨ sprÃĄvcu servera, aby ho obnovil", + "reset_pin_code_success": "PIN kÃŗd bol ÃēspeÅĄne obnovenÃŊ", "reset_pin_code_with_password": "Svoj PIN kÃŗd môŞete kedykoÄžvek obnoviÅĨ pomocou vÃĄÅĄho hesla", "reset_sqlite": "ObnoviÅĨ SQLite databÃĄzu", "reset_sqlite_clear_app_data": "VyčistiÅĨ Ãēdaje", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "VyrieÅĄenÊ vÅĄetky duplicity", "restore": "NavrÃĄtiÅĨ", "restore_all": "NavrÃĄtiÅĨ vÅĄetko", + "restore_trash_action_prompt": "{count} obnovenÃŊch z koÅĄa", "restore_user": "NavrÃĄtiÅĨ pouŞívateÄža", "restored_asset": "NavrÃĄtenÃĄ poloÅžka", "resume": "PokračovaÅĨ", "resume_paused_jobs": "PokračovaÅĨ v {count, plural, one {# pozastavenej Ãēlohe} other {# pozastavenÃŊch ÃēlohÃĄch}}", + "retry_upload": "ZopakovaÅĨ nahrÃĄvanie", "review_duplicates": "PreskÃēmaÅĨ duplikÃĄty", "review_large_files": "SkontrolovaÅĨ veÄžkÊ sÃēbory", "role": "Rola", @@ -1796,6 +1912,7 @@ "role_viewer": "DivÃĄk", "running": "SpustenÊ", "save": "UloÅžiÅĨ", + "save_to_gallery": "UloÅžiÅĨ do galÊrie", "saved": "UloÅženÊ", "saved_api_key": "UloÅženÃŊ API KÄžÃēč", "saved_profile": "UloÅženÃŊ profil", @@ -1806,10 +1923,9 @@ "scan": "SkenovaÅĨ", "scan_all_libraries": "PreskenovaÅĨ vÅĄetky kniÅžnice", "scan_library": "SkenovaÅĨ", + "scan_settings": "Nastavenia skenovania", "scanning": "Skenovanie", "scanning_for_album": "Skenujem pre album...", - "screencast_mode_description": "ZobraziÅĨ indikÃĄtory myÅĄi a klÃĄvesnice na obrazovke", - "screencast_mode_title": "PrepnÃēÅĨ screencast mÃŗd", "search": "HÄžadaÅĨ", "search_albums": "HÄžadaÅĨ albumy", "search_by_context": "HÄžadaÅĨ s kontextom", @@ -1817,8 +1933,6 @@ "search_by_description_example": "PeÅĄia turistika v Sape", "search_by_filename": "HÄžadaÅĨ podÄža nÃĄzvu alebo prípony sÃēboru", "search_by_filename_example": "napr. IMG_1234.JPG alebo PNG", - "search_by_full_path": "VyhÄžadÃĄvanie podÄža Ãēplnej cesty alebo priečinka", - "search_by_full_path_example": "/Jan/Projekty/3D_tlac/2026-07-01 – môŞete vyhÄžadÃĄvaÅĨ podÄža kÄžÃēčovÃŊch slov ako Projekty, 3D, Tlač, 2026 atď.", "search_by_ocr": "HÄžadaÅĨ podÄža OCR", "search_by_ocr_example": "LattÊ", "search_camera_lens_model": "HÄžadaÅĨ model objektívu...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Vyberte polohu", "search_filter_media_type": "Typ mÊdia", "search_filter_media_type_title": "Vyberte typ mÊdia", + "search_filter_ocr": "HÄžadaÅĨ podÄža OCR", "search_filter_people_title": "Vyberte Äžudí", "search_filter_star_rating": "Hodnotenie hviezdičkami", "search_filter_tags_title": "Vyberte značky", @@ -1848,13 +1963,25 @@ "search_no_people_named": "ÅŊiadne osoby menom \"{name}\"", "search_no_result": "NenaÅĄli sa Åžiadne vÃŊsledky, skÃēste pouÅžiÅĨ inÃŊ vyhÄžadÃĄvací vÃŊraz alebo kombinÃĄciu", "search_options": "MoÅžnosti hÄžadania", + "search_page_categories": "KategÃŗrie", + "search_page_motion_photos": "PohyblivÊ fotky", + "search_page_no_objects": "ÅŊiadne informÃĄcie o objektoch", + "search_page_no_places": "ÅŊiadne informÃĄcie o mieste", + "search_page_screenshots": "Snímky obrazovky", "search_page_search_photos_videos": "VyhÄžadajte svoje fotografie a videÃĄ", + "search_page_selfies": "AutoportrÊty", + "search_page_things": "Veci", "search_page_view_all_button": "ZobraziÅĨ vÅĄetky", + "search_page_your_activity": "VaÅĄa aktivita", + "search_page_your_map": "VaÅĄa mapa", "search_people": "HÄžadaÅĨ osoby", "search_places": "HÄžadaÅĨ miesta", "search_rating": "VyhÄžadÃĄvanie podÄža hodnotenia...", + "search_result_page_new_search_hint": "NovÊ vyhÄžadÃĄvanie", "search_settings": "HÄžadaÅĨ v nastaveniach", "search_state": "HÄžadaÅĨ ÅĄtÃĄty...", + "search_suggestion_list_smart_search_hint_1": "InteligentnÊ vyhÄžadÃĄvanie je predvolene povolenÊ, na vyhÄžadÃĄvanie metadÃĄt pouÅžite syntax ", + "search_suggestion_list_smart_search_hint_2": "m:vÃĄÅĄ-hÄžadanÃŊ-vÃŊraz", "search_tags": "HÄžadaÅĨ ÅĄtítky...", "search_timezone": "HÄžadaÅĨ časovÃē zÃŗnu...", "search_type": "Typ hÄžadania", @@ -1868,6 +1995,7 @@ "select_albums": "VybraÅĨ albumy", "select_all": "VybraÅĨ vÅĄetko", "select_all_duplicates": "VybraÅĨ vÅĄetky duplikÃĄty", + "select_all_in": "OznačiÅĨ vÅĄetky v {group}", "select_avatar_color": "Vyberte farbu avatara", "select_count": "{count, plural, one {VybraÅĨ #} other {VybraÅĨ #}}", "select_cutoff_date": "VybraÅĨ cieÄžovÃŊ dÃĄtum", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Vyberte nÃĄhÄžadovÃē fotku", "select_from_computer": "VybraÅĨ z počítača", "select_keep_all": "VybraÅĨ ponechaÅĨ vÅĄetky", + "select_library_owner": "VybraÅĨ vlastníka kniÅžnice", "select_new_face": "VybraÅĨ novÃē tvÃĄr", "select_people": "VybraÅĨ osoby", "select_person": "VybraÅĨ osobu", "select_person_to_tag": "Vyberte osobu, ktorÃē chcete označiÅĨ", "select_photos": "VybraÅĨ fotky", - "select_quality": "Vyberte kvalitu", "select_trash_all": "VybraÅĨ zahodiÅĨ vÅĄetky", + "select_user_for_sharing_page_err_album": "Nepodarilo sa vytvoriÅĨ album", "selected": "VybranÊ", "selected_count": "{count, plural, one {# vybranÃĄ} few {# vybranÊ} other {# vybranÃŊch}}", "selected_gps_coordinates": "VybranÊ GPS sÃēradnice", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "NačítaÅĨ pôvodnÃŊ obrÃĄzok", "setting_image_viewer_preview_subtitle": "Povolením umoÅžníte načítaÅĨ obrÃĄzok so strednÃŊm rozlÃ­ÅĄením. ZakÃĄÅžte, ak chcete priamo načítaÅĨ originÃĄl alebo pouÅžiÅĨ iba miniatÃēru.", "setting_image_viewer_preview_title": "NačítaÅĨ nÃĄhÄžad obrÃĄzka", + "setting_image_viewer_title": "ObrÃĄzky", "setting_languages_apply": "PouÅžiÅĨ", "setting_languages_subtitle": "ZmeniÅĨ jazyk aplikÃĄcie", + "setting_notifications_notify_failures_grace_period": "UpozorniÅĨ na zlyhanie zÃĄlohovania na pozadí: {duration}", + "setting_notifications_notify_hours": "{count} hodín", + "setting_notifications_notify_immediately": "okamÅžite", "setting_notifications_notify_minutes": "{count} minÃēt", + "setting_notifications_notify_never": "nikdy", "setting_notifications_notify_seconds": "{count} sekÃēnd", + "setting_notifications_single_progress_subtitle": "PodrobnÊ informÃĄcie o priebehu nahrÃĄvania pre poloÅžku", + "setting_notifications_single_progress_title": "ZobraziÅĨ priebeh detailov zÃĄlohovania na pozadí", "setting_notifications_subtitle": "Upravte svoje nastavenia oznÃĄmení", + "setting_notifications_total_progress_subtitle": "CelkovÃŊ priebeh nahrÃĄvania (nahranÃŊch/celkovo)", + "setting_notifications_total_progress_title": "ZobraziÅĨ celkovÃŊ priebeh zÃĄlohovania na pozadí", "setting_video_viewer_auto_play_subtitle": "Automaticky spustiÅĨ prehrÃĄvanie videí po ich otvorení", "setting_video_viewer_auto_play_title": "Automaticky prehraÅĨ videÃĄ", "setting_video_viewer_looping_title": "Opakovanie", "setting_video_viewer_original_video_subtitle": "Pri streamovaní videa zo servera prehraÅĨ originÃĄl, aj keď je k dispozícii prekÃŗdovanÊ video. MôŞe to viesÅĨ k preruÅĄovanÊmu prehrÃĄvaniu videa. VideÃĄ dostupnÊ lokÃĄlne sa prehrajÃē v pôvodnej kvalite bez ohÄžadu na toto nastavenie.", "setting_video_viewer_original_video_title": "VynÃētiÅĨ pôvodnÊ video", "settings": "Nastavenia", + "settings_require_restart": "Na pouÅžitie tohto nastavenia reÅĄtartujte Immich", "settings_saved": "Nastavenia boli uloÅženÊ", "setup_pin_code": "Nastavte si PIN kÃŗd", "share": "ZdieÄžaÅĨ", + "share_action_prompt": "{count} poloÅžiek zdieÄžanÃŊch", + "share_add_photos": "PridaÅĨ fotografie", + "share_assets_selected": "{count} označenÃŊch", "share_dialog_preparing": "Pripravujem...", "share_link": "ZdieÄžaÅĨ odkaz", - "share_original": "PouÅžiÅĨ originÃĄl (veÄžkÃŊ)", - "share_preview": "PouÅžiÅĨ miniatÃēru (malÃŊ)", - "share_quality_body": "Stlačte a podrÅžte tlačidlo zdieÄžaÅĨ pre vÃŊber kvality obrÃĄzka.", - "share_quality_title": "Vyberte kvalitu obrÃĄzka na zdieÄžanie", "shared": "ZdieÄžanÊ", "shared_album_activities_input_disable": "KomentÃĄr je zakÃĄzanÃŊ", "shared_album_activity_remove_content": "Chcete vymazaÅĨ tÃēto aktivitu?", "shared_album_activity_remove_title": "VymazaÅĨ aktivitu", "shared_album_section_people_action_error": "Vyskytla sa chyba pri opustení/odobratí z albumu", + "shared_album_section_people_action_leave": "OdstrÃĄniÅĨ pouŞívateÄža z albumu", + "shared_album_section_people_action_remove_user": "OdstrÃĄniÅĨ pouŞívateÄža z albumu", "shared_album_section_people_title": "ÄŊUDIA", "shared_by": "ZdieÄža", "shared_by_user": "ZdieÄža {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} nahranÃŊch", "shared_link_app_bar_title": "ZdieÄžanÊ odkazy", "shared_link_clipboard_copied_massage": "SkopírovanÊ do schrÃĄnky", + "shared_link_clipboard_text": "Odkaz: {link}\nHeslo: {password}", "shared_link_create_error": "Vyskytla sa chyba behom vytvÃĄrania zdieÄžanÊho odkazu", - "shared_link_custom_url_description": "Prístup k tomuto zdieÄžanÊmu odkazu pomocou vlastnÊho nÃĄzvu URL adresy", - "shared_link_custom_url_title": "VlastnÃĄ URL adresa", - "shared_link_custom_url_warning": "Upozornenie: vlastnÃŊ nÃĄzov URL s lomítkom sa nemusí sprÃĄvaÅĨ tak, ako očakÃĄvate.", + "shared_link_custom_url_description": "Prístup k tomuto zdieÄžanÊmu odkazu pomocou vlastnej URL adresy", "shared_link_edit_description_hint": "Zadajte popis zdieÄžania", "shared_link_edit_expire_after_option_day": "1 deň", "shared_link_edit_expire_after_option_days": "{count} dní", @@ -1982,8 +2121,14 @@ "shared_photos_and_videos_count": "{assetCount, plural, few {# zdieÄžanÊ fotky a videÃĄ.} other {# zdieÄžanÃŊch fotiek a videí.}}", "shared_with_me": "ZdieÄžanÊ so mnou", "shared_with_partner": "ZdieÄžanÊ s {partner}", - "sharing": "ZdieÄžanÊ", + "sharing": "ZdieÄžanie", "sharing_enter_password": "Ak chcete zobraziÅĨ tÃēto strÃĄnku, prosím, zadajte heslo.", + "sharing_page_album": "ZdieÄžanÊ albumy", + "sharing_page_description": "VytvÃĄrajte zdieÄžanÊ albumy a zdieÄžajte fotografie a videÃĄ s Äžuďmi vo vaÅĄej sieti.", + "sharing_page_empty_list": "PrÃĄzdny list", + "sharing_sidebar_description": "ZobraziÅĨ odkaz na ZdieÄžanie v bočnom paneli", + "sharing_silver_appbar_create_shared_album": "VytvoriÅĨ zdieÄžanÃŊ album", + "sharing_silver_appbar_share_partner": "ZdieÄžaÅĨ s partnerom", "shift_to_permanent_delete": "stlačte ⇧ na trvalÊ vymazanie poloÅžky", "show_album_options": "ZobraziÅĨ moÅžnosti albumu", "show_albums": "ZobraziÅĨ albumy", @@ -1995,9 +2140,7 @@ "show_in_timeline": "ZobraziÅĨ na časovej osi", "show_in_timeline_setting_description": "ZobraziÅĨ fotky a videÃĄ tohoto pouŞívateÄža na vaÅĄej časovej osi", "show_keyboard_shortcuts": "ZobraziÅĨ klÃĄvesovÊ skratky", - "show_less": "ZobraziÅĨ menej", "show_metadata": "ZobraziÅĨ metadÃĄta", - "show_more_fields": "{count, plural, one {ZobraziÅĨ # ďalÅĄie pole} few {ZobraziÅĨ ďalÅĄie # polia} other {ZobraziÅĨ ďalÅĄÃ­ch # polí}}", "show_or_hide_info": "ZobraziÅĨ alebo skryÅĨ informÃĄcie", "show_password": "ZobraziÅĨ heslo", "show_person_options": "ZobraziÅĨ moÅžnosti osoby", @@ -2005,7 +2148,6 @@ "show_schema": "ZobraziÅĨ schÊmu", "show_search_options": "ZobraziÅĨ moÅžnosti vyhÄžadÃĄvania", "show_shared_links": "ZobraziÅĨ zdieÄžanÊ odkazy", - "show_slideshow_metadata_overlay": "ZobraziÅĨ informačnÃŊ panel k obrÃĄzku", "show_slideshow_transition": "ZobraziÅĨ prechody v prezentÃĄcii", "show_supporter_badge": "Odznak podporovateÄža", "show_supporter_badge_description": "ZobraziÅĨ odznak podporovateÄža", @@ -2017,25 +2159,18 @@ "sign_out": "OdhlÃĄsiÅĨ sa", "sign_up": "RegistrovaÅĨ", "size": "VeÄžkosÅĨ", - "skip": "PreskočiÅĨ", "skip_to_content": "PreskočiÅĨ na obsah", "skip_to_folders": "PreskočiÅĨ do priečinkov", "skip_to_tags": "PreskočiÅĨ ku ÅĄtítkom", "slideshow": "PrezentÃĄcia", - "slideshow_body": "Sledujte ako sa VaÅĄe fotografie premietajÃē v prezentÃĄcii na celej obrazovke.", - "slideshow_metadata_overlay_mode": "Obsah prekrytia obrÃĄzku", - "slideshow_metadata_overlay_mode_description_only": "Iba popis", - "slideshow_metadata_overlay_mode_full": "ÚplnÃŊ", "slideshow_repeat": "OpakovaÅĨ prezentÃĄciu", "slideshow_repeat_description": "Po skončení prezentÃĄcie sa vrÃĄtiÅĨ späÅĨ na začiatok", "slideshow_settings": "Nastavenia prezentÃĄcie", - "slideshow_title": "PrezentÃĄcia", - "smart_album": "InteligentnÃŊ album", - "some_assets_already_have_a_location_warning": "NiektorÊ z vybranÃŊch objektov uÅž majÃē určenÃē polohu", "sort_albums_by": "ZoradiÅĨ albumy podÄža...", "sort_created": "DÃĄtum vytvorenia", "sort_items": "Počet poloÅžiek", "sort_modified": "DÃĄtum Ãēpravy", + "sort_newest": "NajnovÅĄia fotka", "sort_oldest": "NajstarÅĄia fotografia", "sort_people_by_similarity": "ZoradiÅĨ Äžudí podÄža podobnosti", "sort_recent": "NajnovÅĄia fotografia", @@ -2044,6 +2179,7 @@ "stack": "ZoskupiÅĨ", "stack_action_prompt": "{count} zoskupenÃŊch", "stack_duplicates": "ZoskupiÅĨ duplikÃĄty", + "stack_select_one_photo": "Vyberte jednu hlavnÃē fotku pre zoskupenie", "stack_selected_photos": "ZoskupiÅĨ vybratÊ fotky", "stacked_assets_count": "{count, plural, one {ZoskupenÃĄ # poloÅžka} few {ZoskupenÊ # poloÅžky} other {ZoskupenÃŊch # poloÅžiek}}", "stacktrace": "VÃŊpis zÃĄsobníku", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "DÃĄtum začiatku musí byÅĨ pred dÃĄtumom ukončenia", "state": "Å tÃĄt", "status": "Stav", - "step_delete": "VymazaÅĨ krok", - "step_delete_confirm": "Naozaj chcete tento krok vymazaÅĨ?", - "step_details": "Podrobnosti o kroku", - "steps": "Kroky", "stop_casting": "ZastaviÅĨ prenos", "stop_motion_photo": "Stopmotion fotka", "stop_photo_sharing": "ZastaviÅĨ zdieÄžanie vaÅĄich fotografií?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "VymeniÅĨ smer zlÃēčenia", "sync": "SynchronizovaÅĨ", "sync_albums": "SynchronizovaÅĨ albumy", + "sync_albums_manual_subtitle": "Synchronizujte vÅĄetky nahranÊ videÃĄ a fotografie s vybranÃŊmi zÃĄloÅžnÃŊmi albumami", "sync_local": "SynchronizovaÅĨ lokÃĄlne", "sync_remote": "SynchronizovaÅĨ vzdialenÊ", "sync_status": "Stav synchronizÃĄcie", "sync_status_subtitle": "ZobraziÅĨ a spravovaÅĨ systÊm synchronizÃĄcie", "sync_upload_album_setting_subtitle": "VytvÃĄrajte a nahrÃĄvajte svoje fotografie a videÃĄ do vybranÃŊch albumov na Immich", - "system_theme": "SystÊmovÃĄ tÊma", - "system_theme_command_description": "PouÅžiÅĨ systÊmovÃē tÊmu ({value})", "tag": "Å títok", "tag_assets": "PridaÅĨ ÅĄtítky", "tag_created": "VytvorenÃŊ ÅĄtítok: {tag}", "tag_face": "OznačiÅĨ tvÃĄr", "tag_feature_description": "Prehliadanie fotiek a videÃĄ zoskupenÃŊch podÄža tematickÃŊch ÅĄtítkov", + "tag_not_found_question": "Neviete nÃĄjsÅĨ ÅĄtítok? Vytvorte novÃŊ ÅĄtítok.", "tag_people": "OznačiÅĨ Äžudí", "tag_updated": "UpravenÃŊ ÅĄtítok: {tag}", "tagged_assets": "Å títok priradenÃŊ {count, plural, one {# poloÅžke} other {# poloÅžkÃĄm}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Počet poloÅžiek na riadok ({count})", "theme_setting_colorful_interface_subtitle": "PouÅžiÅĨ zÃĄkladnÃē farbu na plochy na pozadí.", "theme_setting_colorful_interface_title": "FarebnÊ rozhranie", + "theme_setting_image_viewer_quality_subtitle": "Upravte kvalitu detailnÊho prehliadača obrÃĄzkov", + "theme_setting_image_viewer_quality_title": "Kvalita prehliadača obrÃĄzkov", "theme_setting_primary_color_subtitle": "Vyberte si farbu pre zÃĄkladnÊ akcie a dôrazy.", "theme_setting_primary_color_title": "ZÃĄkladnÃĄ farba", "theme_setting_system_primary_color_title": "PouÅžiÅĨ systÊmovÃē farbu", "theme_setting_system_theme_switch": "Automaticky (podÄža systÊmovÊho nastavenia)", + "theme_setting_theme_subtitle": "Vyberte nastavenia tÊmy aplikÃĄcie", + "theme_setting_three_stage_loading_subtitle": "TrojstupňovÊ načítanie môŞe zvÃŊÅĄiÅĨ vÃŊkonnosÅĨ načítania, ale vedie k vÃŊrazne vyÅĄÅĄiemu zaÅĨaÅženiu siete", + "theme_setting_three_stage_loading_title": "Povolenie trojstupňovÊho načítavania", "then": "Potom", "they_will_be_merged_together": "ZlÃēčia sa dokopy", "third_party_resources": "Zdroje tretích strÃĄn", @@ -2131,17 +2268,23 @@ "trash_all": "VÅĄetko do koÅĄa", "trash_count": "{count, number} do koÅĄa", "trash_delete_asset": "PoloÅžky do koÅĄa/odstrÃĄniÅĨ", + "trash_emptied": "KÃ´ÅĄ vyprÃĄzdnenÃŊ", "trash_no_results_message": "VymazanÊ fotografie a videÃĄ sa zobrazia tu.", "trash_page_delete_all": "VymazaÅĨ vÅĄetky", + "trash_page_empty_trash_dialog_content": "Skutočne chcete vyprÃĄzdniÅĨ kÃ´ÅĄ? Tieto poloÅžky budÃē natrvalo odstrÃĄnenÊ z aplikÃĄcie Immich", "trash_page_info": "MÊdiÃĄ v koÅĄi sa permanentne odstrÃĄnia po {days} dňoch", + "trash_page_no_assets": "ÅŊiadne mÊdiÃĄ v koÅĄi", + "trash_page_restore_all": "ObnoviÅĨ vÅĄetky", + "trash_page_select_assets_btn": "VybraÅĨ mÊdiÃĄ", + "trash_page_title": "KÃ´ÅĄ ({count})", "trashed_items_will_be_permanently_deleted_after": "PoloÅžky v koÅĄi sa natrvalo vymaÅžÃē po {days, plural, one {# dni} other {# dňoch}}.", "trigger": "SpÃēÅĄÅĨač", - "trigger_asset_metadata_extraction": "Extrakcia metadÃĄt poloÅžky", - "trigger_asset_metadata_extraction_description": "Spustí sa pri extrakcii EXIF metadÃĄt z mediÃĄlneho sÃēboru", - "trigger_asset_uploaded": "Nahranie poloÅžiek", + "trigger_asset_uploaded": "PoloÅžky boli nahranÊ", "trigger_asset_uploaded_description": "Spustí sa pri nahratí novej poloÅžky", + "trigger_description": "UdalosÅĨ, ktorÃĄ spustí pracovnÃŊ postup", "trigger_person_recognized": "Osoba bola rozpoznanÃĄ", - "trigger_person_recognized_description": "Spustí sa, keď sa rozpoznÃĄ osoba", + "trigger_person_recognized_description": "Spustí sa, keď bude objavenÃĄ osoba", + "trigger_type": "Typ spÃēÅĄÅĨača", "troubleshoot": "RieÅĄenie problÊmov", "type": "Typ", "unable_to_change_pin_code": "Nie je moÅžnÊ zmeniÅĨ PIN kÃŗd", @@ -2157,7 +2300,6 @@ "unknown": "NeznÃĄme", "unknown_country": "NeznÃĄma krajina", "unknown_date": "NeznÃĄmy dÃĄtum", - "unknown_schema": "NeznÃĄma schÊma", "unknown_year": "NeznÃĄmy rok", "unlimited": "NeobmedzenÊ", "unlink_motion_video": "OdpojiÅĨ pohyblivÊ video", @@ -2170,20 +2312,23 @@ "unsaved_change": "NeuloÅženÃĄ zmena", "unselect_all": "ZruÅĄiÅĨ vÃŊber vÅĄetkÃŊch", "unselect_all_duplicates": "ZruÅĄiÅĨ vÃŊber vÅĄetkÃŊch duplicít", + "unselect_all_in": "ZruÅĄiÅĨ vÃŊber vÅĄetkÃŊch v {group}", "unstack": "ZruÅĄiÅĨ zoskupenie", "unstack_action_prompt": "{count} nezoskupenÃŊch", "unstacked_assets_count": "ZruÅĄenÊ zoskupenia pre {count, plural, one {# poloÅžku} few {# poloÅžky} other {# poloÅžiek}}", "unsupported_field_type": "NepodporovanÃŊ typ poÄža", "unsupported_file_type": "SÃēbor {file} nie je moÅžnÊ nahraÅĨ, lebo typ {type} nie je podporovanÃŊ.", "untagged": "Bez ÅĄtítku", + "untitled_workflow": "PracovnÃŊ postup bez nÃĄzvu", "up_next": "To je vÅĄetko", "update_location_action_prompt": "AktualizovaÅĨ polohu {count} vybranÃŊch poloÅžiek pomocou:", "updated_at": "AktualizovanÊ", "updated_password": "Heslo zmenenÊ", "upload": "NahraÅĨ", "upload_concurrency": "SÃēbeÅžnosÅĨ nahrÃĄvania", - "upload_day_count": "{date}: {count, plural, one {# nahratÃĄ poloÅžka} few {# nahratÊ poloÅžky} other {# nahratÃŊch poloÅžiek}}", "upload_details": "Podrobnosti o nahrÃĄvaní", + "upload_dialog_info": "Chcete zÃĄlohovaÅĨ zvolenÊ mÊdiÃĄ na server?", + "upload_dialog_title": "NahraÅĨ mÊdiÃĄ", "upload_error_with_count": "Chyba pri nahrÃĄvaní {count, plural, one {# poloÅžky} few {# poloÅžiek} other {# poloÅžiek}}", "upload_errors": "NahrÃĄvanie ukončenÊ s {count, plural, one {# chybou} other {# chybami}}, obnovte strÃĄnku, aby sa zobrazili novÊ poloÅžky.", "upload_finished": "NahrÃĄvanie dokončenÊ", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Chyby", "upload_status_uploaded": "NahranÊ", "upload_success": "NahrÃĄvanie ÃēspeÅĄnÊ, pridanÊ sÃēbory sa zobrazia po obnovení strÃĄnky.", - "upload_to_album_body": "UŞívatelia, ktorí nevyuŞívajÃē ručnÊ pridÃĄvanie obrÃĄzkov, si môŞu zvoliÅĨ, či chcÃē lokÃĄlne fotografie pridÃĄvaÅĨ priamo do albumu uÅž pri ich pridÃĄvaní. UÅž nie je potrebnÊ ich najprv pridaÅĨ do Immich a aÅž potom pridaÅĨ do albumu.", - "upload_to_album_title": "NahraÅĨ priamo do albumu", "upload_to_immich": "NahraÅĨ na Immich ({count})", "uploading": "NahrÃĄvanie", "uploading_media": "NahrÃĄvanie mÊdií", - "uploads": "NahratÊ poloÅžky", - "uploads_count": "{count, plural, one {# nahratÃĄ poloÅžka} few {# nahratÊ poloÅžky} other {# nahratÃŊch poloÅžiek}}", "url": "Odkaz URL", "usage": "PouÅžitie", "use_biometric": "PouÅžiÅĨ biometrickÊ Ãēdaje", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "FormÃĄtovaÅĨ dÃĄtumy, časy a čísla podÄža nastavenia vÃĄÅĄho prehliadača", "use_current_connection": "PouÅžiÅĨ aktuÃĄlne pripojenie", "use_custom_date_range": "PouÅžiÅĨ radÅĄej vlastnÃŊ rozsah dÃĄtumov", - "use_template": "PouÅžiÅĨ ÅĄablÃŗnu", "user": "PouŞívateÄž", "user_has_been_deleted": "Tento pouŞívateÄž bol vymazanÃŊ.", "user_id": "ID pouŞívateÄža", @@ -2217,6 +2357,7 @@ "user_privacy": "Ochrana osobnÃŊch Ãēdajov pouŞívateÄža", "user_purchase_settings": "NÃĄkup", "user_purchase_settings_description": "Spravujte svoj nÃĄkup", + "user_role_set": "Nastav {user} ako {role}", "user_usage_detail": "Podrobnosti o vyuŞívaní pouŞívateÄžmi", "user_usage_stats": "Å tatistiky vyuÅžitia Ãēčtu", "user_usage_stats_description": "ZobraziÅĨ ÅĄtatistiky vyuÅžitia Ãēčtu", @@ -2226,6 +2367,7 @@ "utilities": "NÃĄstroje", "validate": "OveriÅĨ", "validate_endpoint_error": "Zadajte prosím platnÃē URL adresu", + "validation_error": "Chyba overenia", "variables": "PremennÊ", "version": "Verzia", "version_announcement_closing": "Tvoj kamarÃĄt, Alex", @@ -2235,10 +2377,10 @@ "video": "Video", "video_hover_setting": "PrehrÃĄvaÅĨ video nÃĄhÄžad pri nabehnutí myÅĄou", "video_hover_setting_description": "PrehrÃĄ video nÃĄhÄžad keď kurzor myÅĄi prejde cez poloÅžku. Aj keď je vypnutÊ, prehrÃĄvanie sa môŞe spustiÅĨ nabehnutí cez ikonu PrehraÅĨ.", - "video_quality": "Kvalita videa", "videos": "VideÃĄ", "videos_count": "{count, plural, one {# Video} few {# VideÃĄ} other {# Videí}}", - "view": "ZobraziÅĨ", + "videos_only": "Iba videÃĄ", + "view": "Zobrazenie", "view_album": "ZobraziÅĨ Album", "view_all": "ZobraziÅĨ vÅĄetky", "view_all_users": "ZobraziÅĨ vÅĄetkÃŊch pouŞívateÄžov", @@ -2246,29 +2388,29 @@ "view_details": "ZobraziÅĨ podrobnosti", "view_in_timeline": "ZobraziÅĨ v časovej osi", "view_link": "ZobraziÅĨ odkaz", + "view_links": "ZobraziÅĨ odkazy", "view_name": "ZobraziÅĨ", "view_next_asset": "ZobraziÅĨ nasledujÃēci sÃēbor", "view_previous_asset": "ZobraziÅĨ predchÃĄdzajÃēci sÃēbor", "view_qr_code": "ZobraziÅĨ QR kÃŗd", "view_similar_photos": "ZobraziÅĨ podobnÊ fotografie", "view_stack": "ZobraziÅĨ zoskupenie", + "view_user": "ZobraziÅĨ pouŞívateÄža", "viewer_remove_from_stack": "OdstrÃĄniÅĨ zo zoskupenia", + "viewer_stack_use_as_main_asset": "PouÅžiÅĨ ako hlavnÃē fotku", + "viewer_unstack": "ZruÅĄiÅĨ zoskupenie", "visibility": "ViditeÄžnosÅĨ", "visibility_changed": "ViditeÄžnosÅĨ zmenenÃĄ pre {count, plural, one {# osobu} few {# osoby} other {# osôb}}", "visual": "VizuÃĄlny", + "visual_builder": "VizuÃĄlny nÃĄstroj na tvorbu", "waiting": "ČakajÃēce", "waiting_count": "V poradí: {count}", "warning": "Varovanie", "week": "TÃŊÅždeň", "welcome": "Vitajte", "welcome_to_immich": "Vitajte v Immich", - "whats_new": "Čo je novÊ", - "whats_new_settings_subtitle": "Čo je novÊ v Immich", - "whats_new_version": "Verzia {version}", - "when": "Kedy", "width": "Šírka", "wifi_name": "NÃĄzov Wi-Fi", - "workflow": "PracovnÃŊ postup", "workflow_delete_prompt": "Naozaj chcete odstrÃĄniÅĨ tento pracovnÃŊ postup?", "workflow_deleted": "PracovnÃŊ postup bol vymazanÃŊ", "workflow_description": "Popis pracovnÊho postupu", @@ -2278,17 +2420,17 @@ "workflow_name": "NÃĄzov pracovnÊho postupu", "workflow_navigation_prompt": "Naozaj chcete odísÅĨ bez uloÅženia zmien?", "workflow_summary": "SÃēhrn pracovnÊho postupu", - "workflow_templates": "Å ablÃŗny pracovnÃŊch postupov", "workflow_update_success": "PracovnÃŊ postup bol ÃēspeÅĄne aktualizovanÃŊ", + "workflow_updated": "PracovnÃŊ postup bol aktualizovanÃŊ", "workflows": "PracovnÊ postupy", "workflows_help_text": "PracovnÊ postupy automatizujÃē akcie tÃŊkajÃēce sa vaÅĄich poloÅžiek na zÃĄklade spÃēÅĄÅĨačov a filtrov", "wrong_pin_code": "NesprÃĄvny PIN kÃŗd", - "x_of_total": "{x} z {total}", "year": "Rok", "years_ago": "pred {years, plural, one {# rokom} other {# rokmi}}", "yes": "Áno", "you_dont_have_any_shared_links": "NemÃĄte Åžiadne zdielanÊ odkazy", "your_wifi_name": "VÃĄÅĄ nÃĄzov siete Wi-Fi", "zero_to_clear_rating": "stlačte 0 pre vyčistenie hodnotenia poloÅžky", - "zoom_image": "PriblíŞiÅĨ obrÃĄzok" + "zoom_image": "PriblíŞiÅĨ obrÃĄzok", + "zoom_to_bounds": "ZvÃ¤ÄÅĄiÅĨ na okraje" } diff --git a/i18n/sl.json b/i18n/sl.json index c2100294ce..fa6e04201e 100644 --- a/i18n/sl.json +++ b/i18n/sl.json @@ -10,31 +10,39 @@ "active": "Aktivno", "active_count": "Aktivno: {count}", "activity": "Aktivnost", + "activity_changed": "Aktivnost je {enabled, select, true {omogočena} other {onemogočena}}", "add": "Dodaj", "add_a_description": "Dodaj opis", "add_a_location": "Dodaj lokacijo", "add_a_name": "Dodaj ime", "add_a_title": "Dodaj naslov", "add_action": "Dodaj dejanje", + "add_action_description": "Kliknite, če Åželite dodati dejanje, ki ga Åželite izvesti", "add_assets": "Dodaj sredstva", "add_birthday": "Dodaj rojstni dan", "add_endpoint": "Dodaj končno točko", "add_exclusion_pattern": "Dodaj vzorec izključitve", + "add_filter": "Dodaj filter", + "add_filter_description": "Kliknite za dodajanje pogoja filtra", "add_location": "Dodaj lokacijo", + "add_more_users": "Dodaj več uporabnikov", "add_partner": "Dodaj partnerja", + "add_path": "Dodaj pot", "add_photos": "Dodaj fotografije", - "add_step": "Dodaj korak", "add_tag": "Dodaj oznako", "add_to": "Dodaj vâ€Ļ", "add_to_album": "Dodaj v album", "add_to_album_bottom_sheet_added": "Dodano v {album}", "add_to_album_bottom_sheet_already_exists": "ÅŊe v {album}", "add_to_album_bottom_sheet_some_local_assets": "Nekaterih lokalnih sredstev ni bilo mogoče dodati v album", + "add_to_album_toggle": "Preklopi izbiro za {album}", "add_to_albums": "Dodaj v albume", "add_to_albums_count": "Dodaj v albume ({count})", "add_to_bottom_bar": "Dodaj v", + "add_to_shared_album": "Dodaj k deljenemu albumu", "add_upload_to_stack": "Dodaj nalaganje v sklad", "add_url": "Dodaj URL", + "add_workflow_step": "Dodaj korak poteka dela", "added_to_archive": "Dodano v arhiv", "added_to_favorites": "Dodano med priljubljene", "added_to_favorites_count": "{count, number} dodanih med priljubljene", @@ -73,7 +81,6 @@ "cron_expression_description": "Nastavite interval skeniranja z uporabo zapisa cron. Za več informacij poglej npr. Crontab Guru", "cron_expression_presets": "Prednastavitve izraza Cron", "disable_login": "Onemogoči prijavo", - "download_csv": "Prenesi CSV", "duplicate_detection_job_description": "ZaÅženite strojno učenje na sredstvih, da zaznate podobne slike. ZanaÅĄa se na Pametno Iskanje", "exclusion_pattern_description": "Vzorci izključitev vam omogočajo, da prezrete datoteke in mape pri skeniranju knjiÅžnice. To je uporabno, če imate mape z datotekami, ki jih ne Åželite uvoziti, na primer datoteke RAW.", "export_config_as_json_description": "Prenesite trenutno konfiguracijo sistema kot datoteko JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Kakovost sličic od 1-100. ViÅĄje je boljÅĄe, vendar ustvarja večje datoteke in lahko zmanjÅĄa odzivnost aplikacije.", "image_thumbnail_title": "Nastavitve sličic", "import_config_from_json_description": "Uvozite sistemsko konfiguracijo z nalaganjem konfiguracijske datoteke JSON", - "integrity_checks_checksum_files": "Datoteke kontrolnih vsot", - "integrity_checks_checksum_files_description": "Konfigurirajte preverjanje kontrolne vsote", - "integrity_checks_checksum_files_enable_description": "Omogočite preverjanje kontrolne vsote", - "integrity_checks_checksum_files_percentage_limit": "Odstotna omejitev", - "integrity_checks_checksum_files_percentage_limit_description": "Konfigurirajte največji odstotek med 0,01 in 1 za to, kolikokrat naj se preverjanje kontrolne vsote izvaja v vsakem intervalu.", - "integrity_checks_checksum_files_time_limit": "Časovna omejitev", - "integrity_checks_checksum_files_time_limit_description": "Konfigurirajte najdaljÅĄe trajanje preverjanja kontrolne vsote v vsakem intervalu. (ms)", - "integrity_checks_missing_files": "Manjkajoče datoteke", - "integrity_checks_missing_files_description": "Konfigurirajte pogostost in omogočite ali onemogočite preverjanje manjkajočih datotek", - "integrity_checks_missing_files_enable_description": "Omogočite preverjanje manjkajočih datotek", - "integrity_checks_settings": "Preverjanja integritete", - "integrity_checks_settings_description": "Upravljanje intervalov preverjanj integritete", - "integrity_checks_untracked_files": "Nesledljive datoteke", - "integrity_checks_untracked_files_description": "Konfigurirajte pogostost in omogočite ali onemogočite preverjanje nesledljivih datotek", - "integrity_checks_untracked_files_enable_description": "Omogoči preverjanje nesledljivih datotek", "job_concurrency": "{job} sočasnost", "job_created": "Opravilo ustvarjeno", "job_not_concurrency_safe": "To delo ni varno za sočasnost.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "Omogoči pametno iskanje", "machine_learning_smart_search_enabled_description": "Če je onemogočeno, slike ne bodo kodirane za pametno iskanje.", "machine_learning_url_description": "URL streÅžnika za strojno učenje. Če je na voljo več kot en URL, bo vsak streÅžnik poskusen posamično, dokler se eden ne odzove uspeÅĄno, v vrstnem redu od prvega do zadnjega. StreÅžniki, ki se ne odzovejo, bodo začasno prezrti, dokler se spet ne vzpostavijo.", - "maintenance_backup_management": "Upravljanje varnostnih kopij", "maintenance_delete_backup": "IzbriÅĄi varnostno kopijo", "maintenance_delete_backup_description": "Ta datoteka bo nepreklicno izbrisana.", "maintenance_delete_error": "Varnostne kopije ni bilo mogoče izbrisati.", - "maintenance_integrity_check": "Preveri", - "maintenance_integrity_check_all": "Označi vse", - "maintenance_integrity_checksum_mismatch": "Neujemanje kontrolne vsote", - "maintenance_integrity_checksum_mismatch_description": "Datoteke, katerih kontrolna vsota na disku se ne ujema s kontrolno vsoto, ki jo je Immich shranil v svojo zbirko podatkov.", - "maintenance_integrity_checksum_mismatch_job": "Preverite neujemanje kontrolnih vsot", - "maintenance_integrity_checksum_mismatch_refresh_job": "OsveÅži poročila o neujemanju kontrolnih vsot", - "maintenance_integrity_missing_file": "Manjkajoče datoteke", - "maintenance_integrity_missing_file_description": "Datoteke, ki jih je Immich sledil v svoji bazi podatkov, vendar ne obstajajo v datotečnem sistemu.", - "maintenance_integrity_missing_file_job": "Preverite manjkajoče datoteke", - "maintenance_integrity_missing_file_refresh_job": "OsveÅži poročila o manjkajočih datotekah", - "maintenance_integrity_report": "Poročilo o integriteti", - "maintenance_integrity_untracked_file": "Nesledljive datoteke", - "maintenance_integrity_untracked_file_description": "Datoteke v Immichovih imenikih, o katerih Immich nima nobenih zapisov.", - "maintenance_integrity_untracked_file_job": "Preveri nesledljive datoteke", - "maintenance_integrity_untracked_file_refresh_job": "OsveÅži poročila o nesledenih datotekah", "maintenance_restore_backup": "Obnovi varnostno kopijo", "maintenance_restore_backup_description": "Immich bo izbrisan in obnovljen iz izbrane varnostne kopije. Pred nadaljevanjem bo ustvarjena varnostna kopija.", "maintenance_restore_backup_different_version": "Ta varnostna kopija je bila ustvarjena z drugačno različico programa Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Omogoči e-poÅĄtna obvestila", "notification_settings": "Nastavitve obvestil", "notification_settings_description": "Upravljaj z nastavitvami obvestil, vključno z e-poÅĄto", - "oauth_allow_insecure_requests": "Dovoli nezaÅĄÄitene zahteve", - "oauth_allow_insecure_requests_description": "OPOZORILO: To onemogoči preverjanje veljavnosti potrdila TLS za zahteve OAuth in vas lahko izpostavi napadom MITM.", "oauth_auto_launch": "Samodejni zagon", "oauth_auto_launch_description": "Samodejno zaÅženite tok prijave OAuth, ko obiÅĄÄete stran za prijavo", "oauth_auto_register": "Samodejna registracija", @@ -300,11 +274,9 @@ "oauth_button_text": "Besedilo gumba", "oauth_client_secret_description": "Zahtevano za zaupnega odjemalca ali če PKCE (dokazni ključ za izmenjavo kode) ni podprt za javnega odjemalca.", "oauth_enable_description": "Prijava z OAuth", - "oauth_end_session_url_description": "Preusmerite uporabnika na ta URI, ko se odjavi.", "oauth_mobile_redirect_uri": "Mobilni preusmeritveni URI", "oauth_mobile_redirect_uri_override": "Preglasitev URI preusmeritve za mobilne naprave", "oauth_mobile_redirect_uri_override_description": "Omogoči, ko ponudnik OAuth ne dovoli mobilnega URI-ja, kot je ''{callback}''", - "oauth_prompt_description": "Parameter poziva (npr. select_account, login, consent)", "oauth_role_claim": "Zahteva za vlogo", "oauth_role_claim_description": "Samodejno dodeli skrbniÅĄki dostop na podlagi prisotnosti tega zahtevka. Zahtevek ima lahko ÂģuporabnikÂĢ ali ÂģskrbnikÂĢ.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "OsveÅževanje vseh knjiÅžnic", "registration": "Registracija administratorja", "registration_description": "Ker ste prvi uporabnik v sistemu, boste dodeljeni kot skrbnik in ste odgovorni za skrbniÅĄka opravila, dodatne uporabnike pa boste ustvarili sami.", - "release_channel_release_candidate": "Predizdaja", - "release_channel_stable": "Stabilno", "remove_failed_jobs": "Odstrani neuspeÅĄna opravila", "require_password_change_on_login": "Od uporabnika zahtevajte spremembo gesla ob prvi prijavi", "reset_settings_to_default": "Ponastavi nastavitve na privzete", @@ -381,7 +351,7 @@ "template_settings": "Predloge obvestil", "template_settings_description": "Upravljanje predlog po meri za obvestila", "theme_custom_css_settings": "CSS po meri", - "theme_custom_css_settings_description": "Kaskadni slogovni listi (CSS) omogočajo prilagajanje izlgeda Immicha.", + "theme_custom_css_settings_description": "Kaskadni slogovni listi (CSS) omogočajo prilagajanje oblikovanja Immicha.", "theme_settings": "Nastavitve teme", "theme_settings_description": "Upravljanje prilagajanja spletnega vmesnika Immich", "thumbnail_generation_job": "Ustvarite sličice", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Velja samo za VAAPI in QSV. Nastavi dri vozliÅĄÄe, ki se uporablja za strojno prekodiranje.", "transcoding_preset_preset": "Prednastavitev (-preset)", "transcoding_preset_preset_description": "Hitrost stiskanja. PočasnejÅĄe prednastavitve ustvarijo manjÅĄe datoteke in povečajo kakovost pri ciljanju na določeno bitno hitrost. VP9 ignorira hitrosti nad 'hitreje'.", - "transcoding_realtime": "Prekodiranje v realnem času [POSKUSNO]", - "transcoding_realtime_description": "Omogoča prekodiranje v realnem času med pretakanjem videa. Omogoča preklapljanje kakovosti, vendar lahko povzroči večjo zakasnitev predvajanja in zatikanje, odvisno od zmogljivosti streÅžnika.", - "transcoding_realtime_enabled": "Omogoči prekodiranje v realnem času", - "transcoding_realtime_enabled_description": "Če je onemogočeno, bo streÅžnik zavrnil začetek novih sej prekodiranja v realnem času.", - "transcoding_realtime_resolutions": "Ločljivosti", - "transcoding_realtime_resolutions_description": "Ločljivosti, ki so na voljo za transkodiranje v realnem času. ViÅĄje ločljivosti lahko povzročijo teÅžave s predvajanjem, če jih streÅžnik ne more dovolj hitro transkodirati.", - "transcoding_realtime_video_codecs": "Video kodeki", - "transcoding_realtime_video_codecs_description": "Video kodeki, ki so na voljo za transkodiranje v realnem času. Stranke bodo med predvajanjem izbrale najboljÅĄo moÅžnost, ki jo podpirajo. AV1 je učinkovitejÅĄi od HEVC, ki je učinkovitejÅĄi od H.264. Pri uporabi strojnega pospeÅĄevanja izberite samo kodeke, ki jih pospeÅĄevalnik lahko kodira. Pri uporabi programskega transkodiranja upoÅĄtevajte, da je H.264 hitrejÅĄi od AV1, ki je hitrejÅĄi od HEVC.", "transcoding_reference_frames": "Referenčni okvirji", "transcoding_reference_frames_description": "Å tevilo okvirjev, na katere se sklicujete pri stiskanju danega okvira. ViÅĄje vrednosti izboljÅĄajo učinkovitost stiskanja, vendar upočasnijo kodiranje. 0 samodejno nastavi to vrednost.", "transcoding_required_description": "Samo videoposnetki, ki niso v sprejemljivi obliki", @@ -478,8 +440,6 @@ "user_settings_description": "Upravljanje uporabniÅĄkih nastavitev", "user_successfully_removed": "Uporabnik {email} je bil uspeÅĄno odstranjen.", "users_page_description": "Stran skrbniÅĄkih uporabnikov", - "version_check_channel": "Izdajni kanal", - "version_check_channel_description": "Izberite kanal izdaje, za katerega Åželite prejemati obvestila o različicah", "version_check_enabled_description": "Omogoči preverjanje različice", "version_check_implications": "Funkcija preverjanja različic se opira na občasno komunikacijo z {server}", "version_check_settings": "Preverjanje različice", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "Počisti predpomnilnik slik", "advanced_settings_clear_image_cache_error": "Brisanje predpomnilnika slik ni uspelo", "advanced_settings_clear_image_cache_success": "UspeÅĄno počiÅĄÄeno {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Uporabite to moÅžnost za filtriranje medijev med sinhronizacijo na podlagi alternativnih meril. To poskusite le, če imate teÅžave z aplikacijo, ki zaznava vse albume.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Uporabite alternativni filter za sinhronizacijo albuma v napravi", "advanced_settings_log_level_title": "Nivo dnevnika: {level}", "advanced_settings_prefer_remote_subtitle": "Nekatere naprave zelo počasi nalagajo sličice iz lokalnih sredstev. Aktivirajte to nastavitev, če Åželite namesto tega naloÅžiti oddaljene slike.", "advanced_settings_prefer_remote_title": "Uporabi raje oddaljene slike", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Proxy glave po meri [POSKUSNO]", "advanced_settings_readonly_mode_subtitle": "Omogoči način samo za branje, kjer si je mogoče fotografije samo ogledati, funkcije, kot so izbiranje več slik, deljenje, predvajanje in brisanje, so onemogočene. Omogoči/onemogoči način samo za branje prek uporabniÅĄkega avatarja na glavnem zaslonu", "advanced_settings_readonly_mode_title": "Način samo za branje", + "advanced_settings_self_signed_ssl_subtitle": "Preskoči preverjanje potrdila SSL za končno točko streÅžnika. Zahtevano za samopodpisana potrdila.", + "advanced_settings_self_signed_ssl_title": "Dovoli samopodpisana potrdila SSL [POSKUSNO]", "advanced_settings_sync_remote_deletions_subtitle": "Samodejno izbriÅĄi ali obnovi sredstvo v tej napravi, ko je to dejanje izvedeno v spletu", "advanced_settings_sync_remote_deletions_title": "Sinhroniziraj oddaljene izbrise [EKSPERIMENTALNO]", "advanced_settings_tile_subtitle": "Napredne uporabniÅĄke nastavitve", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ali ste prepričani, da Åželite izbrisati album {album}?", "album_delete_confirmation_description": "Če je ta album v skupni rabi, drugi uporabniki ne bodo mogli več dostopati do njega.", "album_deleted": "Album izbrisan", + "album_info_card_backup_album_excluded": "IZKLJUČENO", + "album_info_card_backup_album_included": "VKLJUČENO", "album_info_updated": "Podatki o albumu posodobljeni", + "album_leave": "Zapusti album?", + "album_leave_confirmation": "Ali ste prepričani, da Åželite zapustiti {album}?", "album_name": "Ime albuma", "album_options": "MoÅžnosti albuma", "album_remove_user": "Odstrani uporabnika?", "album_remove_user_confirmation": "Ali ste prepričani, da Åželite odstraniti {user}?", "album_search_not_found": "Ni najdenih albumov, ki bi ustrezali vaÅĄemu iskanju", + "album_selected": "Izbran album", "album_share_no_users": "Videti je, da ste ta album dali v skupno rabo z vsemi uporabniki ali pa nimate nobenega uporabnika, s katerim bi ga lahko delili.", "album_summary": "Povzetek albuma", "album_updated": "Album posodobljen", "album_updated_setting_description": "Prejmite e-poÅĄtno obvestilo, ko ima album v skupni rabi nova sredstva", "album_upload_assets": "NaloÅžite sredstva iz računalnika in jih dodajte v album", + "album_user_left": "Zapustil {album}", + "album_user_removed": "Odstranjen {user}", + "album_viewer_appbar_delete_confirm": "Ali ste prepričani, da Åželite izbrisati ta album iz svojega računa?", "album_viewer_appbar_share_err_delete": "Albuma ni bilo mogoče izbrisati", + "album_viewer_appbar_share_err_leave": "Albuma ni bilo mogoče zapustiti", + "album_viewer_appbar_share_err_remove": "Pri odstranjevanju sredstev iz albuma so teÅžave", + "album_viewer_appbar_share_err_title": "Naslova albuma ni bilo mogoče spremeniti", + "album_viewer_appbar_share_leave": "Zapusti album", + "album_viewer_appbar_share_to": "Deli s/z", "album_viewer_page_share_add_users": "Dodaj uporabnike", "album_with_link_access": "Omogočite vsem s povezavo ogled fotografij in ljudi v tem albumu.", "albums": "Albumi", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Začetni vrstni red razvrÅĄÄanja sredstev pri ustvarjanju novih albumov.", "albums_feature_description": "Zbirke sredstev, ki jih je mogoče deliti z drugimi uporabniki.", "albums_on_device_count": "Albumi v napravi ({count})", + "albums_selected": "{count, plural, one {izbran # album} two {izbrana # albuma} few {izbrani # albumi} other {izbranih # albumov}}", "all": "Vse", "all_albums": "Vsi albumi", "all_people": "Vsi ljudje", "all_photos": "Vse fotografije", "all_videos": "Vsi videi", "allow_dark_mode": "Dovoli temni način", + "allow_edits": "Dovoli urejanja", "allow_public_user_to_download": "Dovoli javnemu uporabniku prenos", "allow_public_user_to_upload": "Dovolite javnemu uporabniku nalaganje", "allowed": "Dovoljeno", @@ -549,12 +528,14 @@ "always_keep": "Vedno ohrani", "always_keep_photos_hint": "S funkcijo \"Sprosti prostor\" bodo vse fotografije shranjene v tej napravi.", "always_keep_videos_hint": "S funkcijo \"Sprosti prostor\" bodo vsi videoposnetki shranjeni v tej napravi.", + "anti_clockwise": "V nasprotni smeri urinega kazalca", "api_key": "API ključ", "api_key_description": "Ta vrednost bo prikazana samo enkrat. Ne pozabite jo kopirati, preden zaprete okno.", "api_key_empty": "Ime ključa API ne sme biti prazno", "api_keys": "API ključi", "app_architecture_variant": "Različica (Arhitektura)", "app_bar_signout_dialog_content": "Ste prepričani, da se Åželite odjaviti?", + "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Odjava", "app_download_links": "Povezave za prenos aplikacij", "app_settings": "Nastavitve aplikacije", @@ -565,19 +546,27 @@ "archive": "Arhiv", "archive_action_prompt": "v arhiv je dodanih {count}", "archive_or_unarchive_photo": "Arhivirajte ali odstranite fotografijo iz arhiva", + "archive_page_no_archived_assets": "Ni arhiviranih sredstev", + "archive_page_title": "Arhiv ({count})", "archive_size": "Velikost arhiva", "archive_size_description": "Konfigurirajte velikost arhiva za prenose (v GiB)", "archived": "Arhivirano", "archived_count": "{count, plural, one {# arhiviran} two {# arhivirana} few {# arhivirani} other {# arhiviranih}}", "are_these_the_same_person": "Ali je to ista oseba?", "are_you_sure_to_do_this": "Ste prepričani, da Åželite to narediti?", + "array_field_not_fully_supported": "Polja matrike zahtevajo ročno urejanje JSON", + "asset_action_delete_err_read_only": "Sredstev samo za branje ni mogoče izbrisati, preskočim", + "asset_action_share_err_offline": "Ni mogoče pridobiti sredstev brez povezave, preskočim", "asset_added_to_album": "Dodano v album", "asset_adding_to_album": "Dodajanje v albumâ€Ļ", "asset_created": "Sredstvo ustvarjeno", - "asset_day_count": "{date}: {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "asset_description_updated": "Opis sredstva je posodobljen", + "asset_filename_is_offline": "Sredstvo {filename} je brez povezave", + "asset_has_unassigned_faces": "Sredstvo ima nedodeljene obraze", "asset_hashing": "ZgoÅĄÄevanjeâ€Ļ", "asset_list_group_by_sub_title": "ZdruÅži po", + "asset_list_layout_settings_dynamic_layout_title": "Dinamična postavitev", + "asset_list_layout_settings_group_automatically": "Samodejno", "asset_list_layout_settings_group_by": "ZdruÅži sredstva po", "asset_list_layout_settings_group_by_month_day": "Mesec + dan", "asset_list_layout_sub_title": "Postavitev", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Sredstva ni bilo mogoče najti v iCloudu. Sredstvo morda ni dostopno zaradi napačne datoteke, shranjene v iCloudu", "asset_offline": "Sredstvo brez povezave", "asset_offline_description": "Tega zunanjega sredstva ni več mogoče najti na disku. Za pomoč kontaktirajte Immich skrbnika.", + "asset_restored_successfully": "Sredstvo uspeÅĄno obnovljeno", "asset_skipped": "Preskočeno", "asset_skipped_in_trash": "V smetnjak", + "asset_trashed": "Sredstvo je bilo premaknjeno v koÅĄ", "asset_troubleshoot": "Odpravljanje teÅžav s sredstvi", "asset_uploaded": "NaloÅženo", "asset_uploading": "Nalaganjeâ€Ļ", "asset_viewer_settings_subtitle": "Upravljaj nastavitve pregledovalnika galerije", "asset_viewer_settings_title": "Pregledovalnik sredstev", "assets": "Sredstva", + "assets_added_count": "Dodano{count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "assets_added_to_album_count": "Dodano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v album", - "assets_added_to_album_partial_count": "V album je bilo dodanih {successCount} od {totalCount} {totalCount, plural, one {sredstva} other {sredstev}}", "assets_added_to_albums_count": "Dodano {assetTotal, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v {albumTotal, plural, one {# album} two {# albuma} few {# albume} other {# albumov}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Sredstvo} two {Sredstvi} few {Sredstva} other {Sredstev}} ni mogoče dodati v album", "assets_cannot_be_added_to_albums": "{count, plural, one {Sredstvo} two {Sredstvi} few {Sredstva} other {Sredstev}} ni mogoče dodati v noben album", "assets_count": "{count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", + "assets_deleted_permanently": "trajno izrisana sredstva {count}", + "assets_deleted_permanently_from_server": "trajno izbrisana sredstva iz streÅžnika Immich {count}", + "assets_downloaded_failed": "{count, plural, one {PreneÅĄena # datoteka - {error} datoteka ni uspela} two {PreneÅĄeni # datoteki - {error} datoteki nista uspeli} few {PreneÅĄene # datoteke - {error} datoteke niso uspele} other {PreneÅĄenih # datotek - {error} datoteke niso uspele}}", + "assets_downloaded_successfully": "{count, plural, one {UspeÅĄno preneÅĄena # datoteka} two {UspeÅĄno preneÅĄeni # datoteki} few {UspeÅĄno preneÅĄene # datoteke} other {UspeÅĄno preneÅĄenih # datotek}}", "assets_moved_to_trash_count": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v smetnjak", "assets_permanently_deleted_count": "Trajno izbrisano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "assets_removed_count": "Odstranjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", + "assets_removed_permanently_from_device": "trajno odstranjena sredstva iz naprave {count}", "assets_restore_confirmation": "Ali ste prepričani, da Åželite obnoviti vsa sredstva, ki ste jih odstranili? Tega dejanja ne morete razveljaviti! UpoÅĄtevajte, da sredstev brez povezave ni mogoče obnoviti na ta način.", "assets_restored_count": "Obnovljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", + "assets_restored_successfully": "uspeÅĄno obnovljena sredstva {count}", "assets_trashed": "sredstva v smetnjaku {count}", "assets_trashed_count": "V smetnjak {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", + "assets_trashed_from_server": "sredstva iz streÅžnika Immich v smetnjaku {count}", "assets_were_part_of_album_count": "{count, plural, one {sredstvo je} two {sredstvi sta} few {sredstva so} other {sredstev je}} Åže del albuma", "assets_were_part_of_albums_count": "{count, plural, one {Sredstvo je} two {Sredstvi sta} few {Sredstva so} other {Sredstev je}} Åže del albumov", "authorized_devices": "PooblaÅĄÄene naprave", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Samodejno predvajanje diaprojekcije", "back": "Nazaj", "back_close_deselect": "Nazaj, zaprite ali prekličite izbiro", + "background_backup_running_error": "Varnostno kopiranje v ozadju se trenutno izvaja, ročnega varnostnega kopiranja ni mogoče zagnati", "background_location_permission": "Dovoljenje za iskanje lokacije v ozadju", "background_location_permission_content": "Ko deluje v ozadju mora imeti Immich za zamenjavo omreÅžij, *vedno* dostop do natančne lokacije, da lahko aplikacija prebere ime omreÅžja Wi-Fi", "background_options": "MoÅžnosti ozadja", "backup": "Varnostna kopija", + "backup_album_selection_page_albums_device": "Albumi v napravi ({count})", "backup_album_selection_page_albums_tap": "Tapnite za vključitev, dvakrat tapnite za izključitev", "backup_album_selection_page_assets_scatter": "Sredstva so lahko razprÅĄena po več albumih. Tako je mogoče med postopkom varnostnega kopiranja albume vključiti ali izključiti.", "backup_album_selection_page_select_albums": "Izberi albume", "backup_album_selection_page_selection_info": "Informacije o izbiri", + "backup_album_selection_page_total_assets": "Skupaj unikatnih sredstev", "backup_albums_sync": "Sinhronizacija varnostnih kopij albumov", + "backup_all": "Vse", + "backup_background_service_backup_failed_message": "Varnostno kopiranje sredstev ni uspelo. Ponovno poskuÅĄamâ€Ļ", "backup_background_service_complete_notification": "Varnostno kopiranje sredstev končano", + "backup_background_service_connection_failed_message": "Povezava s streÅžnikom ni uspela. Ponovno poskuÅĄamâ€Ļ", + "backup_background_service_current_upload_notification": "Nalagam {filename}", "backup_background_service_default_notification": "Preverjam za novimi sredstviâ€Ļ", + "backup_background_service_error_title": "Napaka varnostnega kopiranja", "backup_background_service_in_progress_notification": "Varnostno kopiranje vaÅĄih sredstevâ€Ļ", + "backup_background_service_upload_failure_notification": "Nalaganje {filename} ni uspelo", "backup_controller_page_albums": "Varnostno kopiranje albumov", + "backup_controller_page_background_app_refresh_disabled_content": "Omogočite osveÅževanje aplikacij v ozadju v Nastavitve > SploÅĄno > OsveÅžitev aplikacij v ozadju, če Åželite uporabiti varnostno kopiranje v ozadju.", + "backup_controller_page_background_app_refresh_disabled_title": "OsveÅževanje aplikacije v ozadju je onemogočeno", + "backup_controller_page_background_app_refresh_enable_button_text": "Pojdi na nastavitve", "backup_controller_page_background_battery_info_link": "PokaÅži mi kako", "backup_controller_page_background_battery_info_message": "Za najboljÅĄo izkuÅĄnjo varnostnega kopiranja v ozadju onemogočite vse optimizacije baterije, ki omejujejo dejavnost v ozadju za Immich.\n\nKer je to odvisno od naprave, poiÅĄÄite zahtevane informacije za proizvajalca vaÅĄe naprave.", "backup_controller_page_background_battery_info_ok": "V redu", "backup_controller_page_background_battery_info_title": "Optimizacije baterije", + "backup_controller_page_background_charging": "Samo med polnjenjem", + "backup_controller_page_background_configure_error": "Storitve v ozadju ni bilo mogoče nastaviti", "backup_controller_page_background_delay": "Zakasni varnostno kopiranje novih sredstev: {duration}", + "backup_controller_page_background_description": "Vklopite storitev v ozadju za samodejno varnostno kopiranje novih sredstev, ne da bi morali odpreti aplikacijo", + "backup_controller_page_background_is_off": "Samodejno varnostno kopiranje v ozadju je izklopljeno", + "backup_controller_page_background_is_on": "Samodejno varnostno kopiranje v ozadju je vklopljeno", + "backup_controller_page_background_turn_off": "Izklopi storitev v ozadju", + "backup_controller_page_background_turn_on": "Vklopi storitev v ozadju", + "backup_controller_page_background_wifi": "Samo na Wi-Fi", "backup_controller_page_backup": "Varnostna kopija", "backup_controller_page_backup_selected": "Izbrano: ", "backup_controller_page_backup_sub": "Varnostno kopirane fotografije in videoposnetki", + "backup_controller_page_created": "Ustvarjeno: {date}", + "backup_controller_page_desc_backup": "Vklopite varnostno kopiranje v ospredju za samodejno nalaganje novih sredstev na streÅžnik, ko odprete aplikacijo.", "backup_controller_page_excluded": "Izključeno: ", + "backup_controller_page_failed": "NeuspeÅĄno ({count})", + "backup_controller_page_filename": "Ime datoteke: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Informacija o varnostnem kopiranju", "backup_controller_page_none_selected": "Noben izbran", "backup_controller_page_remainder": "Ostanek", "backup_controller_page_remainder_sub": "Å e preostale fotografije in videoposnetki za varnostno kopiranje iz izbora", "backup_controller_page_server_storage": "Shramba streÅžnika", + "backup_controller_page_start_backup": "ZaÅženi varnostno kopiranje", + "backup_controller_page_status_off": "Samodejno varnostno kopiranje v ospredju je izklopljeno", + "backup_controller_page_status_on": "Samodejno varnostno kopiranje v ospredju je vklopljeno", "backup_controller_page_storage_format": "Uporabljeno {used} od {total}", "backup_controller_page_to_backup": "Albumi, ki bodo varnostno kopirani", "backup_controller_page_total_sub": "Vse edinstvene fotografije in videi iz izbranih albumov", + "backup_controller_page_turn_off": "Izklopite varnostno kopiranje v ospredju", + "backup_controller_page_turn_on": "Vklopite varnostno kopiranje v ospredju", + "backup_controller_page_uploading_file_info": "Nalaganje podatkov o datoteki", + "backup_err_only_album": "Edinega albuma ni mogoče odstraniti", "backup_error_sync_failed": "Sinhronizacija ni uspela. Varnostne kopije ni mogoče obdelati.", "backup_info_card_assets": "sredstva", + "backup_manual_cancelled": "Preklicano", + "backup_manual_in_progress": "Nalaganje Åže poteka. Poskusite čez nekaj časa", + "backup_manual_success": "Uspeh", + "backup_manual_title": "Status nalaganja", "backup_options": "MoÅžnosti varnostnega kopiranja", + "backup_options_page_title": "MoÅžnosti varnostne kopije", + "backup_setting_subtitle": "Upravljaj nastavitve nalaganja v ozadju in ospredju", "backup_settings_subtitle": "Upravljanje nastavitev nalaganja", + "backup_upload_details_page_more_details": "Dotaknite se za več podrobnosti", "backward": "Nazaj", - "battery_optimization_backup_reliability": "Onemogočanje optimizacij baterije lahko izboljÅĄa zanesljivost varnostnega kopiranja v ozadju", "biometric_auth_enabled": "Biometrična avtentikacija omogočena", "biometric_locked_out": "Biometrična avtentikacija vam je onemogočena", "biometric_no_options": "Biometrične moÅžnosti niso na voljo", "biometric_not_available": "Biometrično preverjanje pristnosti ni na voljo v tej napravi", + "birthdate_saved": "Datum rojstva je uspeÅĄno shranjen", "birthdate_set_description": "Datum rojstva se uporablja za izračun starosti te osebe v času fotografije.", "blurred_background": "Zamegljeno ozadje", - "browse_templates": "Brskaj po predlogah", "bugs_and_feature_requests": "Napake in zahteve po funkcijah", "build": "Različica", "build_image": "Različica slike", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Ali ste prepričani, da Åželite obdrÅžati {count, plural, one {# dvojnik} two {# dvojnika} few {# dvojnike} other {# dvojnikov}}? S tem boste razreÅĄili vse podvojene skupine, ne da bi karkoli izbrisali.", "bulk_trash_duplicates_confirmation": "Ali ste prepričani, da Åželite mnoÅžično vreči v smetnjak {count, plural, one {# dvojnik} two {# dvojnika} few {# dvojnike} other {# dvojnikov}}? S tem boste obdrÅžali največje sredstvo vsake skupine in odstranili vse druge dvojnike.", "buy": "Kupi Immich", + "cache_settings_clear_cache_button": "Počisti predpomnilnik", + "cache_settings_clear_cache_button_title": "Počisti predpomnilnik aplikacije. To bo znatno vplivalo na delovanje aplikacije, dokler se predpomnilnik ne obnovi.", + "cache_settings_duplicated_assets_clear_button": "POČISTI", + "cache_settings_duplicated_assets_subtitle": "Fotografije in videoposnetki, ki so prezrti s strani aplikacije", + "cache_settings_duplicated_assets_title": "Podvojena sredstva ({count})", + "cache_settings_statistics_album": "Sličice knjiÅžnice", + "cache_settings_statistics_full": "Izvirne slike", + "cache_settings_statistics_shared": "Sličice albuma v skupni rabi", + "cache_settings_statistics_thumbnail": "Sličice", + "cache_settings_statistics_title": "Uporaba predpomnilnika", + "cache_settings_subtitle": "Nadzirajte delovanje predpomnjenja mobilne aplikacije Immich", + "cache_settings_tile_subtitle": "Nadzoruj vedenje lokalnega shranjevanja", + "cache_settings_tile_title": "Lokalna shramba", + "cache_settings_title": "Nastavitve predpomnjenja", "camera": "Kamera", "camera_brand": "Znamka kamere", "camera_model": "Model kamere", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Opisa ni mogoče posodobiti", "cast": "Pretakaj", "cast_description": "Konfigurirajte razpoloÅžljive cilje za predvajanje", - "change": "Spremeni", "change_date": "Spremeni datum", "change_description": "Spremeni opis", "change_display_order": "Spremeni vrstni red prikaza", + "change_expiration_time": "Spremeni čas poteka", "change_location": "Spremeni lokacijo", "change_name": "Spremeni ime", "change_name_successfully": "Ime uspeÅĄno spremenjeno", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Gesli se ne ujemata", "change_password_form_reenter_new_password": "Znova vnesi novo geslo", "change_pin_code": "Spremeni PIN kodo", + "change_trigger": "Spremeni sproÅžilec", + "change_trigger_prompt": "Ali ste prepričani, da Åželite spremeniti sproÅžilec? S tem boste odstranili vsa obstoječa dejanja in filtre.", "change_your_password": "Spremenite geslo", "changed_visibility_successfully": "UspeÅĄno spremenjena vidnost", "charging": "Polnjenje", "charging_requirement_mobile_backup": "Za varnostno kopiranje v ozadju je potrebno polnjenje naprave", + "check_corrupt_asset_backup": "Preverite poÅĄkodovane varnostne kopije sredstev", + "check_corrupt_asset_backup_button": "Izvedi preverjanje", + "check_corrupt_asset_backup_description": "To preverjanje zaÅženite samo prek omreÅžja Wi-Fi in potem, ko so vsa sredstva varnostno kopirana. Postopek lahko traja nekaj minut.", "check_logs": "Preverite dnevnike", "checksum": "Kontrolna vsota", - "choose": "Izberi", "choose_matching_people_to_merge": "Izberite ujemajoče se osebe za zdruÅžitev", "city": "Mesto", "cleanup_confirm_description": "Immich je naÅĄel {count} sredstev (ustvarjenih pred {date}), ki so varno varnostno shranjena na streÅžniku. Ali ÅželiÅĄ odstraniti lokalne kopije iz te naprave?", @@ -719,10 +774,11 @@ "clear": "Počisti", "clear_all": "Počisti vse", "clear_all_recent_searches": "Počisti vsa nedavna iskanja", - "clear_failed_count": "Brisanje ni uspelo ({count})", "clear_file_cache": "Počisti predpomnilnik datotek", "clear_message": "Počisti sporočilo", "clear_value": "Počisti vrednost", + "client_cert_dialog_msg_confirm": "V redu", + "client_cert_enter_password": "Vnesi geslo", "client_cert_import": "Uvozi", "client_cert_import_success_msg": "Potrdilo odjemalca je uvoÅženo", "client_cert_invalid_msg": "Neveljavna datoteka potrdila ali napačno geslo", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Potrdilo odjemalca je odstranjeno", "client_cert_subtitle": "Podpira samo format PKCS12 (.p12, .pfx). Uvoz/odstranitev potrdila je na voljo samo pred prijavo", "client_cert_title": "Potrdilo odjemalca SSL [POSKUSNO]", + "clockwise": "V smeri urinega kazalca", "close": "Zapri", "collapse": "Strni", "collapse_all": "Strni vse", "color": "Barva", + "color_theme": "Barva teme", "command": "Ukaz", "command_palette_prompt": "Hitro iskanje strani, dejanj ali ukazov", "command_palette_to_close": "zapreti", @@ -747,7 +805,6 @@ "comments_are_disabled": "Komentarji so onemogočeni", "common_create_new_album": "Ustvari nov album", "completed": "Končano", - "configuration": "Konfiguracija", "confirm": "Potrdi", "confirm_admin_password": "Potrdite skrbniÅĄko geslo", "confirm_delete_face": "Ali ste prepričani, da Åželite izbrisati obraz osebe {name} iz sredstva?", @@ -762,16 +819,19 @@ "contain": "Vsebuje", "context": "Kontekst", "continue": "Nadaljuj", - "control_bottom_app_bar_add_tags": "Dodaj oznake", + "control_bottom_app_bar_create_new_album": "Ustvari nov album", + "control_bottom_app_bar_delete_from_immich": "IzbriÅĄi iz Immicha", "control_bottom_app_bar_delete_from_local": "IzbriÅĄi iz naprave", "control_bottom_app_bar_edit_location": "Uredi lokacijo", "control_bottom_app_bar_edit_time": "Uredi datum & uro", + "control_bottom_app_bar_share_link": "Deli povezavo", + "control_bottom_app_bar_share_to": "Deli s/z", "control_bottom_app_bar_trash_from_immich": "Prestavi v smetnjak", "copied_image_to_clipboard": "Slika kopirana v odloÅžiÅĄÄe.", "copied_to_clipboard": "Kopirano v odloÅžiÅĄÄe!", "copy_error": "Napaka pri kopiranju", + "copy_file_path": "Kopiraj pot datoteke", "copy_image": "Kopiraj sliko", - "copy_json": "Kopiraj JSON", "copy_link": "Kopiraj povezavo", "copy_link_to_clipboard": "Kopiraj povezavo v odloÅžiÅĄÄe", "copy_password": "Kopiraj geslo", @@ -789,6 +849,7 @@ "create_link_to_share": "Ustvari povezavo za skupno rabo", "create_link_to_share_description": "Omogoči vsem s povezavo ogled izbranih fotografij", "create_new": "USTVARI NOVEGA", + "create_new_face": "Ustvari nov obraz", "create_new_person": "Ustvari novo osebo", "create_new_person_hint": "Dodeli izbrana sredstva novi osebi", "create_new_user": "Ustvari novega uporabnika", @@ -805,33 +866,37 @@ "created_at": "Ustvarjeno", "creating_linked_albums": "Ustvarjanje povezanih albumov ...", "crop": "Obrezovanje", + "crop_aspect_ratio_fixed": "Fiksno", "crop_aspect_ratio_free": "Poljubno", "crop_aspect_ratio_original": "Izvirno", "crop_aspect_ratio_square": "Kvadrat", + "curated_object_page_title": "Stvari", "current_device": "Trenutna naprava", "current_pin_code": "Trenutna PIN koda", "current_server_address": "Trenutni naslov streÅžnika", "custom_date": "Datum po meri", "custom_locale": "Jezik po meri", "custom_locale_description": "Oblikovanje datuma, časa in ÅĄtevilk glede na izbrani jezik in regijo", + "custom_url": "URL po meri", "cutoff_date_description": "Shranite fotografije iz zadnjegaâ€Ļ", "cutoff_day": "{count, plural, one {dan} other {dni}}", "cutoff_year": "{count, plural, one {leto} two {leti} few {leta} other {let}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "E, MMM dd, yyyy", "dark": "Temno", "dark_theme": "Preklopi na temno temo", "date": "Datum", "date_after": "Datum po", "date_and_time": "Datum in ura", "date_before": "Datum pred", - "date_of_birth": "Datum rojstva", + "date_format": "E, LLL d, y â€ĸ h:mm a", "date_of_birth_saved": "Datum rojstva je uspeÅĄno shranjen", "date_range": "Časovno obdobje", - "date_time_original": "Izvrini datum/ura", "day": "Dan", "days": "Dnevi", "deduplicate_all": "Odstrani vse podvojene", - "default_quality_subtitle": "Kakovost se uporabi ob dotiku deljenja. Vsakič izberite z dolgim pritiskom na gumb za deljenje.", - "default_share_quality": "Privzeta kakovost deljenja", + "default_locale": "Privzete jezikovne nastavitve", + "default_locale_description": "Oblikujte datume in ÅĄtevilke glede na jezikovne nastavitve brskalnika", "delete": "IzbriÅĄi", "delete_action_confirmation_message": "Ali ste prepričani, da Åželite izbrisati to sredstvo? S tem dejanjem boste sredstvo premaknili v koÅĄ na streÅžniku in vas pozvali, ali ga Åželite izbrisati lokalno", "delete_action_prompt": "izbrisano {count}", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Ti elementi bodo trajno izbrisani iz Immicha in vaÅĄe naprave", "delete_dialog_alert_local": "Ti elementi bodo trajno odstranjeni iz vaÅĄe naprave, vendar bodo ÅĄe vedno na voljo na streÅžniku Immich", "delete_dialog_alert_local_non_backed_up": "Nekateri elementi niso varnostno kopirani v Immich in bodo trajno odstranjeni iz vaÅĄe naprave", + "delete_dialog_alert_remote": "Ti elementi bodo trajno izbrisani iz streÅžnika Immich", + "delete_dialog_ok_force": "Vseeno izbriÅĄi", "delete_dialog_title": "Trajno izbriÅĄi", "delete_duplicates_confirmation": "Ali ste prepričani, da Åželite trajno izbrisati te dvojnike?", "delete_face": "IzbriÅĄi obraz", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Ali ste prepričani, da Åželite izbrisati oznako {tagName}?", "delete_user": "IzbriÅĄi uporabnika", "deleted_shared_link": "Izbrisana skupna povezava", + "deletes_missing_assets": "IzbriÅĄe sredstva, ki manjkajo na disku", "description": "Opis", + "description_input_hint_text": "Dodaj opis ...", + "description_input_submit_error": "Napaka pri posodabljanju opisa, preverite dnevnik za več podrobnosti", "deselect_all": "Prekliči vse", "details": "Podrobnosti", "direction": "Usmeritev", "disable": "Onemogoči", "disabled": "Onemogočeno", + "disallow_edits": "Onemogoči urejanje", "discord": "Discord", "discover": "Odkrij", "discovered_devices": "Odkrite naprave", "dismiss_all_errors": "Opusti vse napake", + "dismiss_error": "Opusti napako", "display_options": "MoÅžnosti prikaza", "display_order": "Vrstni red prikaza", "display_original_photos": "PrikaÅži izvirne fotografije", @@ -876,9 +948,11 @@ "documentation": "Dokumentacija", "done": "Končano", "download": "Prenesi", + "download_action_prompt": "PrenaÅĄanje {count} sredstev", "download_canceled": "Prenos preklican", "download_complete": "Prenos končan", "download_enqueue": "Prenos v čakalni vrsti", + "download_error": "Napaka pri prenosu", "download_failed": "Prenos ni uspel", "download_finished": "Prenos zaključen", "download_include_embedded_motion_videos": "Vdelani videoposnetki", @@ -888,15 +962,15 @@ "download_paused": "Prenos zaustavljen", "download_settings": "Prenos", "download_settings_description": "Upravljajte nastavitve, povezane s prenosom sredstev", + "download_started": "Prenos se je začel", + "download_sucess": "Prenos uspeÅĄen", + "download_sucess_android": "Medij je bil prenesen v DCIM/Immich", "download_waiting_to_retry": "Čakam na ponovni poskus", "downloading": "PrenaÅĄanje", "downloading_asset_filename": "PrenaÅĄanje sredstva {filename}", "downloading_from_icloud": "Prenos iz iClouda", "downloading_media": "PrenaÅĄanje medijev", - "drag_to_reorder": "Povlecite za spremembo vrstnega reda", "drop_files_to_upload": "Spustite datoteke kamor koli, da jih naloÅžite", - "duplicate": "Podvoji", - "duplicate_workflow": "Podvojen potek dela", "duplicates": "Dvojniki", "duplicates_description": "Vsako skupino razreÅĄite tako, da navedete, kateri so, če sploh, dvojniki.", "duration": "Trajanje", @@ -908,7 +982,9 @@ "edit_date_and_time": "Uredi datum in uro", "edit_date_and_time_action_prompt": "{count} datum in ura urejeno", "edit_date_and_time_by_offset": "Spremeni datum z odmikom", + "edit_date_and_time_by_offset_interval": "Novo obdobje: {from} - {to}", "edit_description": "Uredi opis", + "edit_description_prompt": "Izberite nov opis:", "edit_exclusion_pattern": "Uredi vzorec izključitve", "edit_faces": "Uredi obraze", "edit_key": "Uredi ključ", @@ -923,6 +999,9 @@ "edit_user": "Uredi uporabnika", "edit_workflow": "Urejanje poteka dela", "editor": "Urejevalnik", + "editor_close_without_save_prompt": "Spremembe ne bodo shranjene", + "editor_close_without_save_title": "Zapri urejevalnik?", + "editor_confirm_reset_all_changes": "Ali ste prepričani, da Åželite ponastaviti vse spremembe?", "editor_discard_edits_confirm": "Zavrzi urejanja", "editor_discard_edits_prompt": "Imate neshranjene spremembe. Ste prepričani, da jih Åželite zavreči?", "editor_discard_edits_title": "ZavrÅžem urejanja?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "Vnesite svojo PIN kodo", "enter_your_pin_code_subtitle": "Vnesite svojo PIN kodo za dostop do zaklenjene mape", "error": "Napaka", + "error_change_sort_album": "Vrstnega reda albuma ni bilo mogoče spremeniti", "error_delete_face": "Napaka pri brisanju obraza iz sredstva", + "error_getting_places": "Napaka pri pridobivanju mest", "error_loading_albums": "Napaka pri nalaganju albumov", "error_loading_image": "Napaka pri nalaganju slike", "error_loading_partners": "Napaka pri nalaganju partnerjev: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "Ključa izdelka ni bilo mogoče odstraniti", "failed_to_reset_pin_code": "Ponastavitev PIN-kode ni uspela", "failed_to_stack_assets": "Zlaganje sredstev ni uspelo", - "failed_to_tag_assets": "Označevanje sredstev ni uspelo", "failed_to_unstack_assets": "Sredstev ni bilo mogoče razloÅžiti", "failed_to_update_notification_status": "Stanja obvestila ni bilo mogoče posodobiti", "incorrect_email_or_password": "Napačen e-poÅĄtni naslov ali geslo", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Dodaj opis..", "exif_bottom_sheet_description_error": "Napaka pri posodabljanju opisa", + "exif_bottom_sheet_details": "PODROBNOSTI", + "exif_bottom_sheet_location": "LOKACIJA", "exif_bottom_sheet_no_description": "Ni opisa", + "exif_bottom_sheet_people": "OSEBE", + "exif_bottom_sheet_person_add_person": "Dodaj ime", "exit_slideshow": "Zapustite diaprojekcijo", "expand": "RazÅĄiri", "expand_all": "RazÅĄiri vse", + "experimental_settings_new_asset_list_subtitle": "Delo v teku", + "experimental_settings_new_asset_list_title": "Omogoči eksperimentalno mreÅžo fotografij", + "experimental_settings_subtitle": "Uporabljajte na lastno odgovornost!", + "experimental_settings_title": "Eksperimentalno", "expire_after": "Poteče čez", "expired": "Poteklo", "expires_date": "Poteče {date}", @@ -1103,40 +1191,41 @@ "export_as_json": "Izvozi kot JSON", "export_database": "Izvoz baze podatkov", "export_database_description": "Izvozite bazo podatkov SQLite", - "exposure_time": "Čas izpostavljenosti", "extension": "RazÅĄiritev", "external": "Zunanji", "external_libraries": "Zunanje knjiÅžnice", "external_network": "Zunanje omreÅžje", "external_network_sheet_info": "Ko aplikacija ni v Åželenem omreÅžju Wi-Fi, se bo povezala s streÅžnikom prek prvega od spodnjih URL-jev, ki jih lahko doseÅže, začenÅĄi od zgoraj navzdol", - "f_number": "F-ÅĄtevilka", "face_unassigned": "Nedodeljen", "failed": "Ni uspelo", "failed_count": "NeuspeÅĄno: {count}", "failed_to_authenticate": "Preverjanje pristnosti ni uspelo", - "failed_to_delete_file": "Brisanje datoteke ni uspelo", "failed_to_load_assets": "Sredstev ni bilo mogoče naloÅžiti", "failed_to_load_folder": "Mape ni bilo mogoče naloÅžiti", "favorite": "Priljubljen", "favorite_action_prompt": "med priljubljene je dodanih {count}", "favorite_or_unfavorite_photo": "Priljubljena ali nepriljubljena fotografija", "favorites": "Priljubljene", + "favorites_page_no_favorites": "Ni priljubljenih sredstev", "feature_photo_updated": "Funkcijska fotografija je posodobljena", "features": "Funkcije", + "features_in_development": "Funkcije v razvoju", "features_setting_description": "Upravljaj funkcije aplikacije", "file_name_or_extension": "Ime ali končnica datoteke", "file_name_text": "Ime datoteke", + "file_name_with_value": "Ime datoteke: {file_name}", "file_size": "Velikost datoteke", "filename": "Ime datoteke", "filetype": "Vrsta datoteke", "filter": "Filter", + "filter_description": "Pogoji za filtriranje ciljnih sredstev", "filter_people": "Filtriraj ljudi", "filter_places": "Filtriraj kraje", "filter_tags": "Filtriraj oznake", "filters": "Filtri", + "find_them_fast": "Z iskanjem jih hitro poiÅĄÄite po imenu", "first": "Prvi", "fix_incorrect_match": "Popravi napačno ujemanje", - "focal_length": "GoriÅĄÄna razdalja", "folder": "Mapa", "folder_not_found": "Ne najdem mape", "folders": "Mape", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Varnostno kopirane fotografije in videoposnetke premaknite v koÅĄ v napravi, da sprostite prostor. VaÅĄe kopije na streÅžniku ostanejo varne.", "free_up_space_settings_subtitle": "Sprostite prostor v napravi", "full_path": "Celotna pot: {path}", - "full_path_or_folder": "Celotna pot ali mapa", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Ta funkcija za delovanje nalaga zunanje vire iz Googla.", "general": "SploÅĄno", @@ -1168,6 +1256,7 @@ "group_owner": "ZdruÅži po lastniku", "group_places_by": "ZdruÅži kraje po...", "group_year": "ZdruÅži po letih", + "haptic_feedback_switch": "Uporabi haptičen odziv", "haptic_feedback_title": "Haptičen odziv", "has_quota": "Ima kvoto", "hash_asset": "ZgoÅĄÄeno sredstvo", @@ -1188,12 +1277,29 @@ "hide_schema": "Skrij shemo", "hide_text_recognition": "Skrij prepoznavanje besedila", "hide_unnamed_people": "Skrij osebe brez imen", + "home_page_add_to_album_conflicts": "Dodanih {added} sredstev v album {album}. {failed} sredstev je Åže v albumu.", + "home_page_add_to_album_err_local": "Lokalnih sredstev ÅĄe ni mogoče dodati v albume, preskakujem", + "home_page_add_to_album_success": "Dodanih {added} sredstev v album {album}.", + "home_page_album_err_partner": "Partnerskih sredstev ÅĄe ni mogoče dodati v album, preskakujem", + "home_page_archive_err_local": "Lokalnih sredstev ÅĄe ni mogoče arhivirati, preskakujem", + "home_page_archive_err_partner": "Sredstev partnerja ni mogoče arhivirati, preskakujem", "home_page_building_timeline": "Gradnja časovnice", + "home_page_delete_err_partner": "Sredstev partnerja ni mogoče izbrisati, preskakujem", + "home_page_delete_remote_err_local": "Lokalna sredstva pri brisanju oddaljenega izbora, preskakujem", + "home_page_favorite_err_local": "Lokalnih sredstev ÅĄe ni mogoče dodati med priljubljene, preskakujem", + "home_page_favorite_err_partner": "Sredstev partnerja ÅĄe ni mogoče dodati med priljubljene, preskakujem", + "home_page_first_time_notice": "Če aplikacijo uporabljate prvič, se prepričajte, da ste izbrali rezervne albume, tako da lahko časovna premica zapolni fotografije in videoposnetke v albumih", + "home_page_locked_error_local": "Lokalnih sredstev ni mogoče premakniti v zaklenjeno mapo, preskakovanje", + "home_page_locked_error_partner": "Sredstev partnerjev ni mogoče premakniti v zaklenjeno mapo, preskakovanje", + "home_page_share_err_local": "Lokalnih sredstev ni mogoče deliti prek povezave, preskakujem", + "home_page_upload_err_limit": "Hkrati lahko naloÅžite največ 30 sredstev, preskakujem", "host": "Gostitelj", "hour": "Ura", "hours": "Ure", "id": "ID", "idle": "Nedejavnost", + "ignore_icloud_photos": "Ignoriraj fotografije iCloud", + "ignore_icloud_photos_description": "Fotografije, shranjene v iCloud, ne bodo naloÅžene na streÅžnik Immich", "image": "Slika", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} zajet {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} zajet z osebo {person1} dne {date}", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}}zajet/a/e/i v/na {city}, {country} s/z {person1} in {person2} dne {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} zajet/a/e/i v/na {city}, {country} s/z {person1}, {person2} in {person3} dne {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} zajet/a/e/i v/na {city}, {country} s/z {person1}, {person2} on ostalimi {additionalCount, number} osebami dne {date}", + "image_saved_successfully": "Slika shranjena", + "image_viewer_page_state_provider_download_started": "Prenos se je začel", + "image_viewer_page_state_provider_download_success": "Prenos je uspel", + "image_viewer_page_state_provider_share_error": "Napaka skupne rabe", "immich_logo": "Immich logo", "immich_web_interface": "Immich spletni vmesnik", "import_from_json": "Uvoz iz JSON", @@ -1219,7 +1329,6 @@ "individual_share": "Samostojna delitev", "individual_shares": "Posamezne delitve", "info": "Info", - "integrity_checks": "Preverjanja integritete", "interval": { "day_at_onepm": "Vsak dan ob 13h", "hours": "Vsakih {hours, plural, one {uro} two {uri} few {ure} other {{hours, number} ur}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Neveljavna oblika datuma", "invite_people": "Povabi ljudi", "invite_to_album": "Povabi v album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Pridobivanje se je izvedlo {dateTime}", + "ios_debug_info_last_sync_at": "Zadnja sinhronizacija {dateTime}", + "ios_debug_info_no_processes_queued": "Ni čakalnih vrst v ozadju", + "ios_debug_info_no_sync_yet": "Ni bilo ÅĄe izvedeno nobeno opravilo sinhronizacije v ozadju", + "ios_debug_info_processes_queued": "{count, plural, one {{count} proces v ozadju v čakalni vrsti} two {{count} procesa v ozadju v čakalni vrsti} few {{count} procesi v ozadju v čakalni vrsti} other {{count} procesov v ozadju v čakalni vrsti}}", + "ios_debug_info_processing_ran_at": "Obdelava je potekala {dateTime}", "items_count": "{count, plural, one {# predmet} two {# predmeta} few {# predmeti} other {# predmetov}}", "jobs": "Opravila", + "json_editor": "Urejevalnik JSON", + "json_error": "Napaka JSON", "keep": "ObdrÅži", "keep_albums": "Ohrani albume", "keep_albums_count": "Ohrani {count} {count, plural, one {album} two {albuma} few {albume} other {albumov}}", @@ -1259,12 +1375,14 @@ "leave": "Zapusti", "leave_album": "Zapusti album", "lens_model": "Model leč", - "less": "Manj", "let_others_respond": "Naj drugi odgovorijo", "level": "Raven", "library": "KnjiÅžnica", "library_add_folder": "Dodaj mapo", "library_edit_folder": "Uredi mapo", + "library_options": "MoÅžnosti knjiÅžnice", + "library_page_device_albums": "Albumi v napravi", + "library_page_new_album": "Nov album", "library_page_sort_asset_count": "Å tevilo sredstev", "library_page_sort_created": "Nazadnje ustvarjeno", "library_page_sort_last_modified": "Nazadnje spremenjeno", @@ -1274,14 +1392,11 @@ "light_theme": "Preklopi na svetlo temo", "like": "VÅĄeč mi je", "like_deleted": "VÅĄeček izbrisan", - "link": "Povezava", "link_motion_video": "Povezava videa gibanja", "link_to_docs": "Za več informacij glejte dokumentacijo.", "link_to_oauth": "Povezava do OAuth", "linked_oauth_account": "Povezan račun OAuth", "list": "Seznam", - "live": "V Åživo", - "load_more": "NaloÅži več", "loading": "Nalaganje", "loading_search_results_failed": "Nalaganje rezultatov iskanja ni uspelo", "local": "Lokalno", @@ -1310,9 +1425,11 @@ "login": "Prijava", "login_disabled": "Prijava je bila onemogočena", "login_form_api_exception": "API napaka. Preverite URL streÅžnika in poskusite znova.", + "login_form_back_button_text": "Nazaj", "login_form_email_hint": "vaÅĄemail@email.com", "login_form_endpoint_hint": "http://ip-vaÅĄega-streÅžnika:vrata", "login_form_endpoint_url": "URL končne točke streÅžnika", + "login_form_err_http": "Navedi http:// ali https://", "login_form_err_invalid_email": "Neveljaven e-poÅĄtni naslov", "login_form_err_invalid_url": "Neveljaven URL", "login_form_err_leading_whitespace": "Vodilni presledek", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Napaka pri prijavi, preverite URL streÅžnika, e-poÅĄto in geslo", "login_form_handshake_exception": "PriÅĄlo je do napake pri rokovanju s streÅžnikom. Če uporabljate samopodpisano potrdilo, v nastavitvah omogočite podporo za samopodpisano potrdilo.", "login_form_password_hint": "geslo", - "login_form_server_empty": "Vnesite URL streÅžnika", - "login_form_server_error": "NeuspeÅĄna povezava s streÅžnikom", - "login_has_been_disabled": "Prijava je bila onemogočena", + "login_form_save_login": "Ostani prijavljen", + "login_form_server_empty": "Vnesite URL streÅžnika.", + "login_form_server_error": "NeuspeÅĄna povezava s streÅžnikom.", + "login_has_been_disabled": "Prijava je bila onemogočena.", "login_password_changed_error": "Pri posodabljanju gesla je priÅĄlo do napake", "login_password_changed_success": "Geslo je bilo uspeÅĄno posodobljeno", "logout_all_device_confirmation": "Ali ste prepričani, da Åželite odjaviti vse naprave?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Obnavljanje baze podatkov", "maintenance_description": "Immich je bil preklopljen v vzdrÅževalni način.", "maintenance_end": "Konec vzdrÅževalnega načina", + "maintenance_end_error": "VzdrÅževalnega načina ni bilo mogoče končati.", "maintenance_logged_in_as": "Trenutno prijavljen kot {user}", "maintenance_restore_from_backup": "Obnovi iz varnostne kopije", "maintenance_restore_library": "Obnovi svojo knjiÅžnico", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Odpri nastavitve", "manage_media_access_subtitle": "Dovolite aplikaciji Immich upravljanje in premikanje medijskih datotek.", "manage_media_access_title": "Dostop do upravljanja medijev", + "manage_shared_links": "Upravljanje povezav v skupni rabi", "manage_sharing_with_partners": "Upravljajte skupno rabo s partnerji", "manage_the_app_settings": "Upravljajte nastavitve aplikacije", "manage_your_account": "Upravljajte svoj račun", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Upravljajte svoje prijavljene naprave", "manage_your_oauth_connection": "Upravljajte svojo OAuth povezavo", "map": "Zemljevid", + "map_assets_in_bounds": "{count, plural, =0 {Na tem območju ni fotografij} one {# slika} two {# sliki} few {# slike} other {# slik}}", "map_cannot_get_user_location": "Lokacije uporabnika ni mogoče dobiti", + "map_location_dialog_yes": "Da", "map_location_picker_page_use_location": "Uporabi to lokacijo", "map_location_service_disabled_content": "Lokacijska storitev mora biti omogočena za prikaz sredstev z vaÅĄe trenutne lokacije. Ali jo Åželite takoj omogočiti?", "map_location_service_disabled_title": "Lokacijska storitev onemogočena", - "map_marker_for_image": "Oznaka na zemljevidu za sliko, posneto v {city}, {country}", + "map_marker_for_images": "Oznaka zemljevida za slike, posnete v {city}, {country}", "map_marker_with_image": "Oznaka zemljevida s sliko", "map_no_location_permission_content": "Za prikaz sredstev z vaÅĄe trenutne lokacije je potrebno dovoljenje za lokacijo. Ali to Åželite takoj dovoliti?", "map_no_location_permission_title": "Dovoljenje za lokacijo je zavrnjeno", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Zadnjih {days} dni", "map_settings_date_range_option_year": "Zadnje leto", "map_settings_date_range_option_years": "Zadnjih {years} let", + "map_settings_dialog_title": "Nastavitve zemljevida", "map_settings_include_show_archived": "Vključi arhivirane", "map_settings_include_show_partners": "Vključi partnerjeve", "map_settings_only_show_favorites": "PokaÅži samo priljubljene", "map_settings_theme_settings": "Tema zemljevida", + "map_zoom_to_see_photos": "PomanjÅĄajte za ogled fotografij", "mark_all_as_read": "Označi vse kot prebrano", + "mark_as_read": "Označi kot prebrano", "marked_all_as_read": "Označeno vse kot prebrano", "matches": "Ujemanja", "matching_assets": "Ujemajoča se sredstva", - "media_chrome": { - "auto": "Samodejno", - "captions": "Podnapisi", - "captions_off": "Izklopljeno", - "closed_captions": "podnapisi", - "decode_error": "Napaka pri dekodiranju", - "disable_captions": "Onemogoči podnapise", - "enable_captions": "Omogoči podnapise", - "enter_fullscreen_mode": "Vstopi v celozaslonski način", - "exit_fullscreen_mode": "Izhod iz celozaslonskega načina", - "loop": "Zanka", - "media_error_description": "Napaka v mediju je povzročila prekinitev predvajanja. Medij je morda poÅĄkodovan ali pa vaÅĄ brskalnik ne podpira te oblike.", - "media_loading": "nalaganje medijev", - "mute": "Izklopi zvok", - "network_error": "Napaka omreÅžja", - "network_error_description": "Napaka omreÅžja je povzročila neuspeh prenosa medijev.", - "not_supported_error": "Vir ni podprt", - "playback_rate": "Hitrost predvajanja", - "playback_rate_current": "trenutna hitrost predvajanja", - "playback_rate_value": "Hitrost predvajanja {playbackRate}", - "playback_time": "čas predvajanja", - "quality": "Kakovost", - "second": "skunda", - "seconds": "sekunde", - "time_value_of_total_time": "{currentTime} od {totalTime}", - "time_value_remaining": "preostanek {time}", - "unmute": "Vklopi zvok", - "unsupported_error_description": "PriÅĄlo je do nepodprte napake. StreÅžnik ali omreÅžje je odpovedalo ali pa vaÅĄ brskalnik ne podpira te oblike.", - "video_not_loaded_unknown_time": "Videoposnetek se ni naloÅžil, neznan čas.", - "video_player": "video predvajalnik", - "volume": "glasnost" - }, "media_type": "Vrsta medija", "memories": "Spomini", "memories_all_caught_up": "Vse dohiteno", @@ -1441,31 +1534,34 @@ "merge_people_prompt": "Ali Åželite zdruÅžiti te osebe? To dejanje je nepovratno.", "merge_people_successfully": "ZdruÅžitev ljudi uspeÅĄno", "merged_people_count": "ZdruÅženo {count, plural, one {# oseba} two {# osebi} few {# osebe} other {# oseb}}", - "minFaces": "Najmanj obrazov", - "minFaces_description": "NajmanjÅĄe ÅĄtevilo prepoznanih obrazov za prikaz osebe", "minimize": "ZmanjÅĄaj", "minute": "minuta", "minutes": "Minute", + "mirror_horizontal": "Vodoravno", + "mirror_vertical": "Navpično", "missing": "manjka", "mobile_app": "Mobilna aplikacija", "mobile_app_download_onboarding_note": "Prenesite spremljevalno mobilno aplikacijo z uporabo naslednjih moÅžnosti", "model": "Model", - "modify_date": "Spremeni datum", "month": "Mesec", + "monthly_title_text_date_format": "MMMM y", "more": "Več", - "motion": "Gibanje", "move": "Premakni", + "move_down": "Premakni navzdol", "move_off_locked_folder": "Premakni iz zaklenjene mape", "move_to": "Premakni v", "move_to_device_trash": "Premakni v koÅĄ naprave", "move_to_lock_folder_action_prompt": "V zaklenjeno mapo je bilo dodanih {count}", "move_to_locked_folder": "Premakni v zaklenjeno mapo", "move_to_locked_folder_confirmation": "Te fotografije in videoposnetki bodo odstranjeni iz vseh albumov in si jih bo mogoče ogledati le v zaklenjeni mapi", + "move_up": "Premakni navzgor", + "moved_to_archive": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v arhiv", + "moved_to_library": "Premaknjeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} v knjiÅžnico", "moved_to_trash": "Premaknjeno v smetnjak", + "multiselect_grid_edit_date_time_err_read_only": "Ni mogoče urediti datuma sredstev samo za branje, preskočim", + "multiselect_grid_edit_gps_err_read_only": "Ni mogoče urediti lokacije sredstev samo za branje, preskočim", "mute_memories": "UtiÅĄaj spomine", "my_albums": "Moji albumi", - "my_immich_description": "Kopiraj trenutno stran kot povezavo Moj Immich", - "my_immich_title": "Povezava do Moj Immich", "name": "Ime", "name_or_nickname": "Ime ali vzdevek", "name_required": "Ime je obvezno", @@ -1480,11 +1576,12 @@ "never": "nikoli", "new_album": "Nov album", "new_api_key": "Nov API ključ", - "new_feature": "Nova funkcija", + "new_date_range": "Novo časovno obdobje", "new_password": "Novo geslo", "new_person": "Nova oseba", "new_pin_code": "Nova PIN koda", "new_pin_code_subtitle": "To je vaÅĄ prvi dostop do zaklenjene mape. Ustvarite PIN kodo za varen dostop do te strani", + "new_timeline": "Nova časovnica", "new_update": "Nova posodobitev", "new_user_created": "Nov uporabnik ustvarjen", "new_version_available": "NA VOLJO JE NOVA RAZLIČICA", @@ -1492,6 +1589,7 @@ "next": "Naslednji", "next_memory": "Naslednji spomin", "no": "Ne", + "no_actions_added": "Ni ÅĄe dodanih dejanj", "no_albums_found": "Ni najdenih albumov", "no_albums_message": "Ustvarite album za organiziranje svojih fotografij in videoposnetkov", "no_albums_with_name_yet": "Videti je, da ÅĄe nimate nobenega albuma s tem imenom.", @@ -1502,13 +1600,16 @@ "no_cast_devices_found": "Naprav za predvajanje ni bilo mogoče najti", "no_checksum_local": "Kontrolna vsota ni na voljo – lokalnih sredstev ni mogoče pridobiti", "no_checksum_remote": "Kontrolna vsota ni na voljo – oddaljenega sredstva ni mogoče pridobiti", + "no_configuration_needed": "Konfiguracija ni potrebna", "no_devices": "Ni pooblaÅĄÄenih naprav", "no_duplicates_found": "Najden ni bil noben dvojnik.", "no_exif_info_available": "Podatki o exif niso na voljo", "no_explore_results_message": "NaloÅžite več fotografij, da raziÅĄÄete svojo zbirko.", "no_favorites_message": "Dodajte priljubljene, da hitreje najdete svoje najboljÅĄe slike in videoposnetke", + "no_filters_added": "Ni ÅĄe dodanih filtrov", "no_libraries_message": "Ustvarite zunanjo knjiÅžnico za ogled svojih fotografij in videoposnetkov", "no_local_assets_found": "S to kontrolno vsoto ni bilo najdenih lokalnih sredstev", + "no_location_set": "Lokacija ni nastavljena", "no_locked_photos_message": "Fotografije in videoposnetki v zaklenjeni mapi so skriti in se ne bodo prikazali med brskanjem ali iskanjem po knjiÅžnici.", "no_name": "Brez imena", "no_notifications": "Ni obvestil", @@ -1518,19 +1619,14 @@ "no_results": "Brez rezultatov", "no_results_description": "Poskusite s sinonimom ali bolj sploÅĄno ključno besedo", "no_shared_albums_message": "Ustvarite album za skupno rabo fotografij in videoposnetkov z osebami v vaÅĄem omreÅžju", - "no_steps": "Ni ÅĄe dodanih korakov", + "no_uploads_in_progress": "Ni nalaganj v teku", "none": "Nič", "not_allowed": "Ni dovoljeno", "not_available": "Ni na voljo", "not_in_any_album": "Ni v nobenem albumu", "not_selected": "Ni izbrano", - "not_set": "Ni nastavljeno", "notes": "Opombe", "nothing_here_yet": "Tukaj ÅĄe ni ničesar", - "notification_backup_reliability": "Omogočite obvestila za izboljÅĄanje zanesljivosti varnostnega kopiranja v ozadju", - "notification_enabled_list_tile_content": "Immich uporablja obvestila za varnostno kopiranje v ozadju. Upravljajte jih v nastavitvah naprave.", - "notification_enabled_list_tile_open_button": "Odpri nastavitve", - "notification_enabled_list_tile_title": "Obvestila omogočena", "notification_permission_dialog_content": "Če Åželite omogočiti obvestila, pojdite v Nastavitve in izberite Dovoli.", "notification_permission_list_tile_content": "Dodaj dovoljenje za poÅĄiljanje obvestil.", "notification_permission_list_tile_enable_button": "Omogoči obvestila", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Konfigurator Obtainium", "obtainium_configurator_instructions": "Z Obtainium namestite in posodobite aplikacijo za Android neposredno iz izdaje Immich GitHub. Ustvarite ključ API in izberite različico, da ustvarite svojo konfiguracijsko povezavo Obtainium", "ocr": "Optično prepoznavanje znakov (OCR)", - "ocr_body": "Immich zdaj bere besedilo v vaÅĄih fotografijah, tako da jih lahko iÅĄÄete po tem, kaj piÅĄejo.", - "ocr_title": "Iskanje besedila na fotografijah", "official_immich_resources": "Immich uradni viri", "offline": "Brez povezave", "offset": "Odmik", @@ -1562,8 +1656,6 @@ "open": "Odpri", "open_calendar": "Odpri koledar", "open_in_browser": "Odpri v brskalniku", - "open_in_immich_body": "Nastavite Immich kot galerijo v sistemu Android, da boste lahko fotografije odpirali neposredno iz drugih aplikacij.", - "open_in_immich_title": "Odpri fotografije v Immichu", "open_in_map_view": "Odpri v pogledu zemljevida", "open_in_openstreetmap": "Odpri v OpenStreetMap", "open_the_search_filters": "Odpri iskalne filtre", @@ -1572,10 +1664,10 @@ "organize_into_albums": "Organiziraj v albume", "organize_into_albums_description": "Dodaj obstoječe fotografije v albume z uporabo trenutnih nastavitev sinhronizacije", "organize_your_library": "Organiziraj svojo knjiÅžnico", - "orientation": "Usmerjenost", "original": "izvirnik", "other": "drugo", "other_devices": "Druge naprave", + "other_entities": "Drugi subjekti", "other_variables": "Druge spremenljivke", "owned": "V lasti", "owner": "Lastnik", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Vse vaÅĄe fotografije in videoposnetki, razen tistih v arhivu in izbrisanih", "partner_can_access_location": "Lokacija, kjer so bile vaÅĄe fotografije posnete", "partner_list_user_photos": "fotografije od {user}", + "partner_list_view_all": "Poglej vse", "partner_page_empty_message": "VaÅĄe fotografije ÅĄe niso v skupni rabi z nobenim partnerjem.", "partner_page_no_more_users": "Ni več uporabnikov za dodajanje", + "partner_page_partner_add_failed": "Partnerja ni bilo mogoče dodati", "partner_page_select_partner": "Izberi partnerja", + "partner_page_shared_to_title": "V skupni rabi z", "partner_page_stop_sharing_content": "{partner} ne bo imel več dostopa do vaÅĄih fotografij.", "partner_sharing": "Skupna raba s partnerjem", "partners": "Partnerji", @@ -1609,6 +1704,8 @@ "people": "Osebe", "people_edits_count": "{count, plural, one {Urejena # oseba} two {Urejeni # osebi} few {Urejene # osebe} other {Urejenih # oseb}}", "people_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrÅĄÄenih po osebah", + "people_selected": "{count, plural, one {izbrana # oseba} two {izbrani # osebi} few {izbrane # osebe} other {izbranih # oseb}}", + "people_sidebar_description": "PrikaÅžite povezavo do Ljudje v stranski vrstici", "permanent_deletion_warning": "Opozorilo o trajnem izbrisu", "permanent_deletion_warning_setting_description": "PokaÅži opozorilo pri trajnem brisanju sredstev", "permanently_delete": "Trajno izbriÅĄi", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "Trajno izbrisano {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "permission": "Dovoljenje", "permission_empty": "VaÅĄe dovoljenje ne sme biti prazno", + "permission_onboarding_back": "Sredstev partnerja ni mogoče izbrisati, preskakujem", + "permission_onboarding_continue_anyway": "Vseeno nadaljuj", + "permission_onboarding_get_started": "Začnimo", + "permission_onboarding_go_to_settings": "Pojdite na nastavitve", + "permission_onboarding_permission_denied": "Dovoljenje zavrnjeno. Če Åželite uporabljati Immich, v nastavitvah dodajte dovoljenja za fotografije in videoposnetke.", + "permission_onboarding_permission_granted": "Dovoljenje ste dodali! Vse je pripravljeno.", + "permission_onboarding_permission_limited": "Dovoljenje je omejeno. Če Åželite Immichu dovoliti varnostno kopiranje in upravljanje vaÅĄe celotne zbirke galerij, v nastavitvah podelite dovoljenja za fotografije in videoposnetke.", + "permission_onboarding_request": "Immich potrebuje dovoljenje za ogled vaÅĄih fotografij in videoposnetkov.", "person": "Oseba", "person_age_months": "{months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}} star/a", "person_age_year_months": "1 leto, {months, plural, one {# mesec} two {# meseca} few {# mesece} other {# mesecev}} star/a", - "person_age_years": "{years, plural, one {# leto} two {# leti} few {# leta} other {# let}} star/a", + "person_age_years": "{years, plural, two {# leti} few {# leta} other {# let}} star/a", "person_birthdate": "Rojen dne {date}", "person_hidden": "{name}{hidden, select, true { (skrita)} other {}}", "person_recognized": "Oseba prepoznana", + "person_selected": "Oseba izbrana", "photo_shared_all_users": "Videti je, da ste svoje fotografije delili z vsemi uporabniki ali pa nimate nobenega uporabnika, s katerim bi jih delili.", "photos": "Slike", "photos_and_videos": "Fotografije & videi", + "photos_count": "{count, plural, one {{count, number} slika} two {{count, number} sliki} few {{count, number} slike} other {{count, number} slik}}", "photos_from_previous_years": "Fotografije iz prejÅĄnjih let", + "photos_only": "Samo fotografije", "pick_a_location": "Izberi lokacijo", "pick_custom_range": "Obseg po meri", "pick_date_range": "Izberi časovno obdobje", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "Prednostno predvajajte originalne videoposnetke pred prekodiranimi. Če originalno sredstvo ni zdruÅžljivo, se morda ne bo pravilno predvajalo.", "play_transcoded_video": "Predvajaj prekodiran video", "please_auth_to_access": "Za dostop se prijavite", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Ta metoda lahko filtrira dogodke in pogojno prepreči izvajanje nadaljnjih korakov", "port": "Vrata", "preferences_settings_subtitle": "Upravljaj nastavitve aplikacije", "preferences_settings_title": "Nastavitve", @@ -1665,11 +1771,11 @@ "privacy": "Zasebnost", "profile": "Profil", "profile_drawer_app_logs": "Dnevniki", + "profile_drawer_client_server_up_to_date": "Odjemalec in streÅžnik sta posodobljena", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Način samo za branje je omogočen. Za izhod dolgo pritisnite ikono uporabniÅĄkega avatarja.", "profile_image_of_user": "Profilna slika uporabnika {user}", "profile_picture_set": "Profilna slika nastavljena.", - "projection_type": "Vrsta projekcije", "public_album": "Javni album", "public_share": "Javno deljenje", "purchase_account_info": "Podpornik", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Status podpornika", "purchase_individual_title": "Posamezno", "purchase_input_suggestion": "Ali imate ključ izdelka? Spodaj vnesite ključ", + "purchase_license_subtitle": "Kupite Immich, da podprete nadaljnji razvoj storitve", "purchase_lifetime_description": "DoÅživljenjski nakup", "purchase_option_title": "MOÅŊNOSTI NAKUPA", "purchase_panel_info_1": "Gradnja Immicha zahteva veliko časa in truda, zato imamo zaposlene inÅženirje, ki delajo na tem, da bi bil čim boljÅĄi. NaÅĄe poslanstvo je, da odprtokodna programska oprema in etične poslovne prakse, ki bi postale trajnostni vir dohodka za razvijalce in ustvarjanje ekosistema, ki spoÅĄtuje zasebnost z resničnimi alternativami izkoriÅĄÄevalskim storitvam v oblaku.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "Ponovno dodeljeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} za novo osebo", "reassing_hint": "Dodeli izbrana sredstva obstoječi osebi", "recent": "Nedavno", + "recent_albums": "Zadnji albumi", "recent_searches": "Nedavna iskanja", "recently_added": "Nedavno dodano", - "recently_added_body": "Skočite neposredno na vse, kar ste nedavno dodali, na namenski strani.", - "recently_added_description": "Brskajte po svojih sredstvih, razvrÅĄÄenih po času nalaganja v Immich", "recently_added_page_title": "Nedavno dodano", - "recently_added_title": "Nedavno dodano", "recently_taken": "Nedavno posneto", + "recently_taken_page_title": "Nedavno Uporabljen", "refresh": "OsveÅži", "refresh_encoded_videos": "OsveÅži kodirane videoposnetke", "refresh_faces": "OsveÅži obraze", "refresh_metadata": "OsveÅži metapodatke", "refresh_thumbnails": "OsveÅži sličice", "refreshed": "OsveÅženo", + "refreshes_every_file": "Ponovno prebere vse obstoječe in nove datoteke", "refreshing_encoded_video": "OsveÅževanje kodiranega videa", "refreshing_faces": "OsveÅževanje obrazev", "refreshing_metadata": "OsveÅževanje metapodatkov", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "Ali ste prepričani, da Åželite odstraniti {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}} iz te skupne povezave?", "remove_assets_title": "Odstrani sredstva?", "remove_custom_date_range": "Odstrani časovno obdobje po meri", - "remove_filter": "Odstrani filter", + "remove_deleted_assets": "Odstrani izbrisana sredstva", "remove_from_album": "Odstrani iz albuma", "remove_from_album_action_prompt": "{count} odstranjenih iz albuma", "remove_from_favorites": "Odstrani iz priljubljenih", @@ -1756,22 +1863,29 @@ "remove_memory": "Odstrani spomin", "remove_photo_from_memory": "Odstrani fotografijo iz tega spomina", "remove_tag": "Odstrani oznako", + "remove_url": "Odstrani URL", "remove_user": "Odstrani uporabnika", "removed_api_key": "Odstranjen ključ API-ja: {name}", "removed_from_archive": "Odstranjeno iz arhiva", "removed_from_favorites": "Odstranjeno iz priljubljenih", "removed_from_favorites_count": "{count, plural, other {odstranen/ih #}} iz priljubljenih", "removed_memory": "Odstranjen spomin", + "removed_photo_from_memory": "Odstranjena fotografija iz spomina", "removed_tagged_assets": "Odstranjena oznaka iz {count, plural, one {# sredstva} other {# sredstev}}", "rename": "Preimenuj", + "repair": "Popravi", + "repair_no_results_message": "Datoteke, ki jim ni sledi in manjkajo, bodo prikazane tukaj", + "replace_with_upload": "Zamenjaj z nalaganjem", "repository": "Repozitorij", "require_password": "Zahtevaj geslo", + "require_user_to_change_password_on_first_login": "Od uporabnika zahtevajte spremembo gesla ob prvi prijavi", "rescan": "Ponovno skeniraj", "reset": "Ponastavi", "reset_password": "Ponastavi geslo", "reset_people_visibility": "Ponastavi vidnost ljudi", "reset_pin_code": "Ponastavi PIN kodo", "reset_pin_code_description": "Če ste pozabili PIN kodo, se lahko za ponastavitev obrnete na skrbnika streÅžnika", + "reset_pin_code_success": "PIN koda je bila uspeÅĄno ponastavljena", "reset_pin_code_with_password": "PIN-kodo lahko vedno ponastavite z geslom", "reset_sqlite": "Ponastavi bazo podatkov SQLite", "reset_sqlite_clear_app_data": "Počisti podatke", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "RazreÅĄeni vsi dvojniki", "restore": "Obnovi", "restore_all": "Obnovi vse", + "restore_trash_action_prompt": "{count} obnovljenih iz koÅĄa", "restore_user": "Obnovi uporabnika", "restored_asset": "Obnovljeno sredstvo", "resume": "Nadaljuj", "resume_paused_jobs": "Nadaljuj {count, plural, one {# zaustavljeno opravilo} two {# zaustavljeni opravili} few {# zaustavljena opravila} other {# zaustavljenih opravil}}", + "retry_upload": "Poskusite znova naloÅžiti", "review_duplicates": "Pregled dvojnikov", "review_large_files": "Pregled velikih datotek", "role": "Vloga", @@ -1796,6 +1912,7 @@ "role_viewer": "Gledalec", "running": "V teku", "save": "Shrani", + "save_to_gallery": "Shrani v galerijo", "saved": "Shranjeno", "saved_api_key": "Shranjen API ključ", "saved_profile": "Shranjen profil", @@ -1806,10 +1923,9 @@ "scan": "Skeniraj", "scan_all_libraries": "Preglej vse knjiÅžnice", "scan_library": "Pregled", + "scan_settings": "Nastavitve pregleda", "scanning": "Skeniranje", "scanning_for_album": "Iskanje albuma...", - "screencast_mode_description": "PrikaÅži indikatorje dogodkov tipkovnice in miÅĄke na zaslonu", - "screencast_mode_title": "Preklopi način predvajanja zaslona", "search": "Iskanje", "search_albums": "Iskanje albumov", "search_by_context": "Iskanje po kontekstu", @@ -1817,8 +1933,6 @@ "search_by_description_example": "PohodniÅĄki dan v Sapi", "search_by_filename": "Iskanje po imenu datoteke ali priponi", "search_by_filename_example": "na primer IMG_1234.JPG ali PNG", - "search_by_full_path": "Iskanje po celotni poti ali mapi", - "search_by_full_path_example": "/Janez/Projekti/3D_Tiskanje/01-07-2026 - iÅĄÄete lahko Projekti, 3D, Tiskanje, 2026, itd.", "search_by_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", "search_by_ocr_example": "Bela kava", "search_camera_lens_model": "Iskanje modela objektiva...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Izberi lokacijo", "search_filter_media_type": "Vrsta medija", "search_filter_media_type_title": "Izberi vrsto medija", + "search_filter_ocr": "Iskanje po optičnem prepoznavanju znakov (OCR)", "search_filter_people_title": "Izberi osebe", "search_filter_star_rating": "Ocena z zvezdicami", "search_filter_tags_title": "Izberite oznake", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Ni oseb z imenom \"{name}\"", "search_no_result": "Ni rezultatov, poskusite z drugim iskalnim izrazom ali kombinacijo", "search_options": "MoÅžnosti iskanja", + "search_page_categories": "Kategorije", + "search_page_motion_photos": "Fotografije v gibanju", + "search_page_no_objects": "Informacije o predmetih niso na voljo", + "search_page_no_places": "Informacije o lokacijah niso na voljo", + "search_page_screenshots": "Posnetki zaslona", "search_page_search_photos_videos": "PoiÅĄÄi svoje fotografije in videoposnetke", + "search_page_selfies": "Selfiji", + "search_page_things": "Stvari", "search_page_view_all_button": "Poglej vse", + "search_page_your_activity": "VaÅĄa dejavnost", + "search_page_your_map": "Tvoj zemljevid", "search_people": "Iskanje oseb", "search_places": "Iskanje krajev", "search_rating": "IÅĄÄi po oceni ...", + "search_result_page_new_search_hint": "Novo iskanje", "search_settings": "Nastavitve iskanja", "search_state": "Iskanje deÅžele...", + "search_suggestion_list_smart_search_hint_1": "Pametno iskanje je privzeto omogočeno, za iskanje metapodatkov uporabite sintakso ", + "search_suggestion_list_smart_search_hint_2": "m:vaÅĄ-iskani-pojem", "search_tags": "Iskanje oznak...", "search_timezone": "Iskanje časovnega pasu...", "search_type": "Vrsta iskanja", @@ -1868,6 +1995,7 @@ "select_albums": "Izberi albume", "select_all": "Izberi vse", "select_all_duplicates": "Izberi vse dvojnike", + "select_all_in": "Izberi vse v {group}", "select_avatar_color": "Izberi barvo avatarja", "select_count": "{count, plural, one {# izbran} two {# izbrana} few {# izbrani} other {# izbranih}}", "select_cutoff_date": "Izberite datum zaključka", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Izberi predstavljeno fotografijo", "select_from_computer": "Izberi iz računalnika", "select_keep_all": "Izberi obdrÅži vse", + "select_library_owner": "Izberi lastnika knjiÅžnice", "select_new_face": "Izberi nov obraz", "select_people": "Izberi osebe", "select_person": "Izberi osebo", "select_person_to_tag": "Izberite osebo, ki jo Åželite označiti", "select_photos": "Izberi fotografije", - "select_quality": "Izberite kakovost", "select_trash_all": "Izberi vse v smetnjak", + "select_user_for_sharing_page_err_album": "Albuma ni bilo mogoče ustvariti", "selected": "Izbrano", "selected_count": "{count, plural, other {# izbranih}}", "selected_gps_coordinates": "izbrane GPS koordinate", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "NaloÅži originalno sliko", "setting_image_viewer_preview_subtitle": "Omogoči nalaganje slike srednje ločljivosti. Onemogočite neposredno nalaganje izvirnika ali uporabo samo sličice.", "setting_image_viewer_preview_title": "NaloÅži predogled slike", + "setting_image_viewer_title": "Slike", "setting_languages_apply": "Uporabi", "setting_languages_subtitle": "Spremeni jezik aplikacije", + "setting_notifications_notify_failures_grace_period": "Obvesti o napakah varnostnega kopiranja v ozadju: {duration}", + "setting_notifications_notify_hours": "{count} ur", + "setting_notifications_notify_immediately": "takoj", "setting_notifications_notify_minutes": "{count} minut", + "setting_notifications_notify_never": "nikoli", "setting_notifications_notify_seconds": "{count} sekund", + "setting_notifications_single_progress_subtitle": "Podrobne informacije o napredku nalaganja po sredstvih", + "setting_notifications_single_progress_title": "PokaÅži napredek varnostnega kopiranja v ozadju", "setting_notifications_subtitle": "Prilagodite svoje nastavitve obvestil", + "setting_notifications_total_progress_subtitle": "SploÅĄni napredek nalaganja (končano/skupaj sredstev)", + "setting_notifications_total_progress_title": "PrikaÅži skupni napredek varnostnega kopiranja v ozadju", "setting_video_viewer_auto_play_subtitle": "Samodejno začni predvajati videoposnetke, ko jih odpreÅĄ", "setting_video_viewer_auto_play_title": "Samodejno predvajanje videoposnetkov", "setting_video_viewer_looping_title": "V zanki", "setting_video_viewer_original_video_subtitle": "Ko pretakate video iz streÅžnika, predvajajte izvirnik, tudi če je na voljo prekodiranje. Lahko povzroči medpomnjenje. Videoposnetki, ki so na voljo lokalno, se predvajajo v izvirni kakovosti ne glede na to nastavitev.", "setting_video_viewer_original_video_title": "Vsili izvirni video", "settings": "Nastavitve", + "settings_require_restart": "Znova zaÅženite Immich, da uporabite to nastavitev", "settings_saved": "Nastavitve shranjene", "setup_pin_code": "Nastavi PIN kodo", "share": "Deli", + "share_action_prompt": "Deljena sredstva {count}", + "share_add_photos": "Dodaj fotografije", + "share_assets_selected": "{count} izbrano", "share_dialog_preparing": "Priprava...", "share_link": "Deli povezavo", - "share_original": "Uporabi izvirno (veliko)", - "share_preview": "Uporabi sličico (majhno)", - "share_quality_body": "Pritisnite in drÅžite gumb za deljenje, da izberete kakovost slike, preden jo delite.", - "share_quality_title": "Izberite kakovost delitve", "shared": "V skupni rabi", "shared_album_activities_input_disable": "Komentiranje je onemogočeno", "shared_album_activity_remove_content": "Ali Åželite izbrisati to dejavnost?", "shared_album_activity_remove_title": "IzbriÅĄi dejavnost", "shared_album_section_people_action_error": "Napaka pri zapuÅĄÄanju/odstranjevanju iz albuma", + "shared_album_section_people_action_leave": "Odstrani uporabnika iz albuma", + "shared_album_section_people_action_remove_user": "Odstrani uporabnika iz albuma", "shared_album_section_people_title": "OSEBE", "shared_by": "Skupna raba s/z", "shared_by_user": "Skupna raba s/z {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} naloÅženo", "shared_link_app_bar_title": "Povezave v skupni rabi", "shared_link_clipboard_copied_massage": "Kopirano v odloÅžiÅĄÄe", + "shared_link_clipboard_text": "Povezava: {link}\nGeslo: {password}", "shared_link_create_error": "Napaka pri ustvarjanju povezave skupne rabe", - "shared_link_custom_url_description": "Dostop do te deljene povezave z imenom URL-ja po meri", - "shared_link_custom_url_title": "URL po meri", - "shared_link_custom_url_warning": "Opozorilo: ime URL-ja po meri s poÅĄevnico naprej morda ne bo delovalo po pričakovanjih.", + "shared_link_custom_url_description": "Dostop do te deljene povezave z URL-jem po meri", "shared_link_edit_description_hint": "Vnesi opis skupne rabe", "shared_link_edit_expire_after_option_day": "1 dan", "shared_link_edit_expire_after_option_days": "{count} dni", @@ -1984,6 +2123,12 @@ "shared_with_partner": "V skupni rabi s/z {partner}", "sharing": "Skupna raba", "sharing_enter_password": "Za ogled te strani vnesi geslo.", + "sharing_page_album": "Albumi v skupni rabi", + "sharing_page_description": "Ustvarite albume za skupno rabo fotografij in videoposnetkov z osebami v vaÅĄem omreÅžju.", + "sharing_page_empty_list": "PRAZEN SEZNAM", + "sharing_sidebar_description": "PrikaÅžite povezavo do skupne rabe v stranski vrstici", + "sharing_silver_appbar_create_shared_album": "Ustvari album v skupni rabi", + "sharing_silver_appbar_share_partner": "Deli z partnerjem", "shift_to_permanent_delete": "pritisni ⇧ za trajno brisanje sredstva", "show_album_options": "PrikaÅži moÅžnosti albuma", "show_albums": "PrikaÅži albume", @@ -1995,9 +2140,7 @@ "show_in_timeline": "PokaÅži na časovnici", "show_in_timeline_setting_description": "PrikaÅži fotografije in videoposnetke tega uporabnika na svoji časovnici", "show_keyboard_shortcuts": "PrikaÅži bliÅžnjice na tipkovnici", - "show_less": "PrikaÅži manj", "show_metadata": "PokaÅži metapodatke", - "show_more_fields": "{count, plural, one {PrikaÅži ÅĄe # polje} two {PrikaÅži ÅĄe # polji} few {PrikaÅži ÅĄe # polja} other {PrikaÅži ÅĄe # polj}}", "show_or_hide_info": "PokaÅži ali skrij podatke", "show_password": "PrikaÅži geslo", "show_person_options": "PrikaÅži moÅžnosti osebe", @@ -2005,7 +2148,6 @@ "show_schema": "PrikaÅži shemo", "show_search_options": "PrikaÅži moÅžnosti iskanja", "show_shared_links": "PokaÅži povezave v skupni rabi", - "show_slideshow_metadata_overlay": "PrikaÅži prekrivno plast s podatki o sliki", "show_slideshow_transition": "PrikaÅži prehod diaprojekcije", "show_supporter_badge": "Značka podpornika", "show_supporter_badge_description": "PrikaÅži značko podpornika", @@ -2017,25 +2159,18 @@ "sign_out": "Odjavi se", "sign_up": "Prijavi se", "size": "Velikost", - "skip": "Preskoči", "skip_to_content": "Preskoči na vsebino", "skip_to_folders": "Preskoči na mape", "skip_to_tags": "Preskoči na oznake", "slideshow": "Diaprojekcija", - "slideshow_body": "Udobno se namestite in si oglejte svoje fotografije v celozaslonskem prikazu diaprojekcije.", - "slideshow_metadata_overlay_mode": "Prekrivna vsebina", - "slideshow_metadata_overlay_mode_description_only": "Samo opis", - "slideshow_metadata_overlay_mode_full": "Polno", "slideshow_repeat": "Ponavljanje diaprojekcije", "slideshow_repeat_description": "Po koncu diaprojekcije se zanka vrne na začetek", "slideshow_settings": "Nastavitve diaprojekcije", - "slideshow_title": "Diaprojekcija", - "smart_album": "Pametni album", - "some_assets_already_have_a_location_warning": "Nekatera izbrana sredstva Åže imajo lokacijo", "sort_albums_by": "Razvrsti albume po...", "sort_created": "Datum nastanka", "sort_items": "Å tevilo predmetov", "sort_modified": "Datum spremembe", + "sort_newest": "NajnovejÅĄa fotografija", "sort_oldest": "NajstarejÅĄa fotografija", "sort_people_by_similarity": "Razvrsti ljudi po podobnosti", "sort_recent": "NajnovejÅĄa fotografija", @@ -2044,6 +2179,7 @@ "stack": "Sklad", "stack_action_prompt": "{count} naloÅženih", "stack_duplicates": "Nabor dvojnikov", + "stack_select_one_photo": "Izberite eno glavno fotografijo za nabor", "stack_selected_photos": "Nabor izbranih fotografij", "stacked_assets_count": "Nabor {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "stacktrace": "Sled nabora", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "Začetni datum mora biti pred končnim datumom", "state": "DeÅžela", "status": "Status", - "step_delete": "IzbriÅĄi korak", - "step_delete_confirm": "Ali ste prepričani, da Åželite izbrisati ta korak?", - "step_details": "Podrobnosti koraka", - "steps": "Koraki", "stop_casting": "Ustavi predvajanje", "stop_motion_photo": "Zaustavi gibljivo fotografijo", "stop_photo_sharing": "ÅŊelite prenehati deliti svoje fotografije?", @@ -2076,18 +2208,18 @@ "swap_merge_direction": "Zamenjaj smer zdruÅževanja", "sync": "Sinhronizacija", "sync_albums": "Sinhronizacija albumov", + "sync_albums_manual_subtitle": "Sinhronizirajte vse naloÅžene videoposnetke in fotografije v izbrane varnostne albume", "sync_local": "Sinhroniziraj lokalno", "sync_remote": "Sinhroniziraj oddaljeno", "sync_status": "Stanje sinhronizacije", "sync_status_subtitle": "Ogled in upravljanje sistema sinhronizacije", "sync_upload_album_setting_subtitle": "Ustvarite in naloÅžite svoje fotografije in videoposnetke v izbrane albume na Immich", - "system_theme": "Sistemska tema", - "system_theme_command_description": "Uporabi sistemsko temo ({value})", "tag": "Oznaka", "tag_assets": "Označi sredstva", "tag_created": "Ustvarjena oznaka: {tag}", "tag_face": "Označi obraz", "tag_feature_description": "Brskanje po fotografijah in videoposnetkih, razvrÅĄÄenih po temah logičnih oznak", + "tag_not_found_question": "Ne najdete oznake? Ustvarite novo oznako.", "tag_people": "Označi osebe", "tag_updated": "Posodobljena oznaka: {tag}", "tagged_assets": "Označeno {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Å tevilo sredstev na vrstico ({count})", "theme_setting_colorful_interface_subtitle": "Nanesi primarno barvo na povrÅĄine ozadja.", "theme_setting_colorful_interface_title": "Barvit vmesnik", + "theme_setting_image_viewer_quality_subtitle": "Prilagodite kakovost podrobnega pregledovalnika slik", + "theme_setting_image_viewer_quality_title": "Kakovost pregledovalnika slik", "theme_setting_primary_color_subtitle": "Izberi barvo za primarna dejanja in poudarke.", "theme_setting_primary_color_title": "Primarna barva", "theme_setting_system_primary_color_title": "Uporabi sistemsko barvo", "theme_setting_system_theme_switch": "Samodejno (Sledi nastavitvi sistema)", + "theme_setting_theme_subtitle": "Izberi nastavitev teme aplikacije", + "theme_setting_three_stage_loading_subtitle": "Tristopenjsko nalaganje lahko poveča zmogljivost nalaganja, vendar povzroči znatno večjo obremenitev omreÅžja", + "theme_setting_three_stage_loading_title": "Omogoči tristopenjsko nalaganje", "then": "Potem", "they_will_be_merged_together": "ZdruÅženi bodo skupaj", "third_party_resources": "Viri tretjih oseb", @@ -2131,17 +2268,23 @@ "trash_all": "Vse v smetnjak", "trash_count": "Smetnjak {count, number}", "trash_delete_asset": "V smetnjak/izbriÅĄi sredstvo", + "trash_emptied": "Smetnjak je izpraznjen", "trash_no_results_message": "Fotografije in videoposnetki, ki so v smetnjaku, bodo prikazani tukaj.", "trash_page_delete_all": "IzbriÅĄi vse", + "trash_page_empty_trash_dialog_content": "Ali Åželite izprazniti svoja sredstva v smeti? Ti elementi bodo trajno odstranjeni iz Immicha", "trash_page_info": "Elementi v smeteh bodo trajno izbrisani po {days} dneh", + "trash_page_no_assets": "Ni sredstev v smeteh", + "trash_page_restore_all": "Obnovi vse", + "trash_page_select_assets_btn": "Izberite sredstva", + "trash_page_title": "Smetnjak ({count})", "trashed_items_will_be_permanently_deleted_after": "Elementi v smetnjaku bodo trajno izbrisani po {days, plural, one {# dnevu} two {# dnevih} few {# dnevih} other {# dneh}}.", "trigger": "SproÅžilec", - "trigger_asset_metadata_extraction": "Pridobivanje metapodatkov sredstev", - "trigger_asset_metadata_extraction_description": "SproÅži se, ko so izvlečeni metapodatki EXIF sredstva", - "trigger_asset_uploaded": "Nalaganje sredstev", + "trigger_asset_uploaded": "Sredstvo je naloÅženo", "trigger_asset_uploaded_description": "SproÅži se ob nalaganju novega sredstva", + "trigger_description": "Dogodek, ki sproÅži delovni proces", "trigger_person_recognized": "Oseba prepoznana", - "trigger_person_recognized_description": "SproÅži se, ko je oseba prepoznana", + "trigger_person_recognized_description": "SproÅži se, ko je zaznana oseba", + "trigger_type": "Vrsta sproÅžilca", "troubleshoot": "Odpravljanje teÅžav", "type": "Vrsta", "unable_to_change_pin_code": "PIN kode ni mogoče spremeniti", @@ -2157,7 +2300,6 @@ "unknown": "Neznano", "unknown_country": "Neznana drÅžava", "unknown_date": "Neznan datum", - "unknown_schema": "Neznana shema", "unknown_year": "Neznano leto", "unlimited": "Neomejeno", "unlink_motion_video": "Prekini povezavo videoposnetka gibanja", @@ -2170,20 +2312,23 @@ "unsaved_change": "Neshranjena sprememba", "unselect_all": "Odznači vse", "unselect_all_duplicates": "Odznači vse dvojnike", + "unselect_all_in": "Prekliči izbor vseh v {group}", "unstack": "Razklad", "unstack_action_prompt": "{count} razloÅženih", "unstacked_assets_count": "RazloÅži {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "unsupported_field_type": "Nepodprta vrsta polja", "unsupported_file_type": "Datoteke {file} ni mogoče naloÅžiti, ker njena vrsta datoteke {type} ni podprta.", "untagged": "Neoznačeno", + "untitled_workflow": "Neimenovani potek dela", "up_next": "Naslednja", "update_location_action_prompt": "Posodobi lokacijo izbranih sredstev {count} s/z:", "updated_at": "Posodobljeno", "updated_password": "Posodobljeno geslo", "upload": "NaloÅži", "upload_concurrency": "Sočasnost nalaganja", - "upload_day_count": "{date}: {count, plural, one {# nalaganje} two {# nalaganji} few {# nalaganja} other {# nalaganj}}", "upload_details": "Podrobnosti o nalaganju", + "upload_dialog_info": "Ali Åželite varnostno kopirati izbrana sredstva na streÅžnik?", + "upload_dialog_title": "NaloÅži sredstvo", "upload_error_with_count": "Napaka pri prilaganju za {count, plural, one {# sredstvo} two {# sredstvi} few {# sredstva} other {# sredstev}}", "upload_errors": "Nalaganje je končano s/z {count, plural, one {# napako} two {# napakama} other {# napakami}}, osveÅžite stran, da vidite nova sredstva za nalaganje.", "upload_finished": "Nalaganje končano", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Napake", "upload_status_uploaded": "NaloÅženo", "upload_success": "Nalaganje je uspelo, osveÅžite stran, da vidite nova sredstva za nalaganje.", - "upload_to_album_body": "Uporabniki, ki ne uporabljajo funkcije ročnega nalaganja, lahko zdaj dodajo lokalne fotografije neposredno v album med nalaganjem, tako da jih ni več treba nalagati in nato dodajati v album pozneje.", - "upload_to_album_title": "NaloÅži neposredno v album", "upload_to_immich": "NaloÅži v Immich ({count})", "uploading": "Nalagam", "uploading_media": "Nalaganje medijev", - "uploads": "Nalaganja", - "uploads_count": "{count, plural, one {# nalaganje} two {# nalaganji} few {# nalaganja} other {# nalaganj}}", "url": "URL", "usage": "Uporaba", "use_biometric": "Uporabite biometrične podatke", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Oblikujte datume, čase in ÅĄtevilke glede na jezikovne nastavitve brskalnika", "use_current_connection": "Uporabi trenutno povezavo", "use_custom_date_range": "Namesto tega uporabite časovno obdobje po meri", - "use_template": "Uporabi predlogo", "user": "Uporabnik", "user_has_been_deleted": "Ta uporabnik je bil izbrisan.", "user_id": "ID uporabnika", @@ -2217,6 +2357,7 @@ "user_privacy": "Zasebnost uporabnika", "user_purchase_settings": "Nakup", "user_purchase_settings_description": "Upravljajte svoj nakup", + "user_role_set": "Nastavi {user} kot {role}", "user_usage_detail": "Podrobnosti o uporabi uporabnika", "user_usage_stats": "Statistika uporabe računa", "user_usage_stats_description": "Oglejte si statistiko uporabe računa", @@ -2226,6 +2367,7 @@ "utilities": "Pripomočki", "validate": "Potrdi", "validate_endpoint_error": "Vnesite veljaven URL", + "validation_error": "Napaka pri preverjanju", "variables": "Spremenljivke", "version": "Različica", "version_announcement_closing": "Tvoj prijatelj, Alex", @@ -2235,9 +2377,9 @@ "video": "Video", "video_hover_setting": "Predvajaj sličico videoposnetka ob lebdenju", "video_hover_setting_description": "Predvajaj sličico videoposnetka, ko se miÅĄka pomakne nad element. Tudi ko je onemogočeno, lahko predvajanje začnete tako, da miÅĄkin kazalec premaknete nad ikono za predvajanje.", - "video_quality": "Kakovost videa", "videos": "Videoposnetki", "videos_count": "{count, plural, one {# video} two {# videa} few {# videi} other {# videov}}", + "videos_only": "Samo videoposnetki", "view": "Ogled", "view_album": "Ogled albuma", "view_all": "Poglej vse", @@ -2246,29 +2388,29 @@ "view_details": "Ogled podrobnosti", "view_in_timeline": "Ogled na časovnici", "view_link": "Odpri povezavo", + "view_links": "Ogled povezav", "view_name": "Pogled", "view_next_asset": "Ogled naslednjega sredstva", "view_previous_asset": "Ogled prejÅĄnjega sredstva", "view_qr_code": "Oglej si kodo QR", "view_similar_photos": "Oglejte si podobne fotografije", "view_stack": "Ogled sklada", + "view_user": "Poglej uporabnika", "viewer_remove_from_stack": "Odstrani iz sklada", + "viewer_stack_use_as_main_asset": "Uporabi kot glavno sredstvo", + "viewer_unstack": "Razkladi", "visibility": "Vidljivost", "visibility_changed": "Vidnost spremenjena za {count, plural, one {# osebo} two {# osebi} few {# osebe} other {# oseb}}", "visual": "Vizualno", + "visual_builder": "Vizualni graditelj", "waiting": "Čakanje", "waiting_count": "Čakanje: {count}", "warning": "Opozorilo", "week": "Teden", "welcome": "DobrodoÅĄli", "welcome_to_immich": "DobrodoÅĄli v Immich", - "whats_new": "Kaj je novega", - "whats_new_settings_subtitle": "Oglejte si novosti v Immichu", - "whats_new_version": "Različica {version}", - "when": "Ko", "width": "Å irina", "wifi_name": "Wi-Fi ime", - "workflow": "Potek dela", "workflow_delete_prompt": "Ali ste prepričani, da Åželite izbrisati ta potek dela?", "workflow_deleted": "Potek dela izbrisan", "workflow_description": "Opis poteka dela", @@ -2278,17 +2420,17 @@ "workflow_name": "Ime poteka dela", "workflow_navigation_prompt": "Ali ste prepričani, da Åželite zapustiti stran brez shranjevanja sprememb?", "workflow_summary": "Povzetek poteka dela", - "workflow_templates": "Predloge poteka dela", "workflow_update_success": "Potek dela je bil uspeÅĄno posodobljen", + "workflow_updated": "Potek dela posodobljen", "workflows": "Poteki dela", "workflows_help_text": "Poteki dela avtomatizirajo dejanja na vaÅĄih sredstvih na podlagi sproÅžilcev in filtrov", "wrong_pin_code": "Napačna PIN koda", - "x_of_total": "{x}/{total}", "year": "Leto", "years_ago": "{years, plural, one {# leto} two {# leti} few {# leta} other {# let}} nazaj", "yes": "Da", "you_dont_have_any_shared_links": "Nimate nobenih skupnih povezav", "your_wifi_name": "VaÅĄe ime Wi-Fi", "zero_to_clear_rating": "Pritisnite 0 za brisanje ocene sredstva", - "zoom_image": "Povečava slike" + "zoom_image": "Povečava slike", + "zoom_to_bounds": "Povečaj do meja" } diff --git a/i18n/sq.json b/i18n/sq.json index 39ff274665..ba90837839 100644 --- a/i18n/sq.json +++ b/i18n/sq.json @@ -6,35 +6,43 @@ "action": "Aksion", "action_common_update": "PÃĢrditÃĢso", "action_description": "NjÃĢ grup veprimesh pÃĢr t'u kryer nÃĢ asetet e filtruara", - "actions": "Veprime", + "actions": "Aksione", "active": "Aktiv", "active_count": "Aktive: {count}", "activity": "Aktivitet", + "activity_changed": "Aktiviteti ÃĢshtÃĢ {enabled, select, true {aktivizuar} other {çaktivizuar}}", "add": "Shto", "add_a_description": "Shto njÃĢ pÃĢrshkrim", "add_a_location": "Shto njÃĢ vendndodhje", "add_a_name": "Shto njÃĢ emÃĢr", "add_a_title": "Shto njÃĢ titull", "add_action": "Shto veprim", + "add_action_description": "Klikoni pÃĢr tÃĢ shtuar njÃĢ veprim pÃĢr t'u kryer", "add_assets": "Shto asete", "add_birthday": "Shto njÃĢ ditÃĢlindje", "add_endpoint": "Shto njÃĢ endpoint", "add_exclusion_pattern": "Shto model pÃĢrjashtimi", + "add_filter": "Shto filtÃĢr", + "add_filter_description": "Klikoni pÃĢr tÃĢ shtuar njÃĢ kusht filtri", "add_location": "Shto vendndodhje", + "add_more_users": "Shto mÃĢ shumÃĢ pÃĢrdorues", "add_partner": "Shto partner", + "add_path": "Shto path", "add_photos": "Shto foto", - "add_step": "Shto hap", "add_tag": "Shto tag", "add_to": "Shto nÃĢâ€Ļ", "add_to_album": "Shto nÃĢ album", "add_to_album_bottom_sheet_added": "Shtuar nÃĢ {album}", "add_to_album_bottom_sheet_already_exists": "Existon nÃĢ {album}", "add_to_album_bottom_sheet_some_local_assets": "Disa asete lokale nuk mund tÃĢ shtoheshin nÃĢ album", + "add_to_album_toggle": "Aktivizo/çaktivizo zgjedhjen pÃĢr {album}", "add_to_albums": "Shto nÃĢ albume", "add_to_albums_count": "Shto nÃĢ albume ({count})", "add_to_bottom_bar": "Shto nÃĢ", + "add_to_shared_album": "Shto nÃĢ album tÃĢ hapur", "add_upload_to_stack": "Shto ngarkimin nÃĢ stivÃĢ", "add_url": "Shto URL", + "add_workflow_step": "Shto hap workflow", "added_to_archive": "Shtuar nÃĢ arkiv", "added_to_favorites": "Shtuar tek tÃĢ preferuarat", "added_to_favorites_count": "Shtuar {count, number} nÃĢ tÃĢ preferuarat", @@ -69,1250 +77,58 @@ "confirm_user_pin_code_reset": "A jeni i sigurt qÃĢ dÃĢshironi tÃĢ rivendosni kodin PIN tÃĢ {user}?", "copy_config_to_clipboard_description": "Kopjo konfigurimin aktual tÃĢ sistemit si objekt JSON nÃĢ clipboard", "create_job": "Krijo punÃĢ", - "cron_expression": "Shprehja Cron", "cron_expression_description": "Vendosni intervalin e skanimit duke pÃĢrdorur formatin Cron. PÃĢr mÃĢ shumÃĢ informacion, ju lutem shikoni p.sh. Crontab Guru", - "cron_expression_presets": "Paracaktimet e shprehjeve Cron", "disable_login": "Çaktivizo hyrjen", - "download_csv": "Shkarko CSV-nÃĢ", "duplicate_detection_job_description": "Ekzekuto mÃĢsimin makinerik mbi skedarÃĢt pÃĢr tÃĢ zbuluar imazhe tÃĢ ngjashme. Bazohet nÃĢ Smart Search", "exclusion_pattern_description": "Modelet e pÃĢrjashtimit ju lejojnÃĢ tÃĢ injoroni skedarÃĢ dhe dosje gjatÃĢ skanimit tÃĢ bibliotekÃĢs suaj. Kjo ÃĢshtÃĢ e dobishme nÃĢse keni dosje qÃĢ pÃĢrmbajnÃĢ skedarÃĢ qÃĢ nuk dÃĢshironi tÃĢ importoni, si p.sh. skedarÃĢt e papÃĢrpunuara.", "export_config_as_json_description": "Shkarkoni konfigurimin aktual tÃĢ sistemit si njÃĢ skedar JSON", "external_libraries_page_description": "Faqja e bibliotekÃĢs sÃĢ jashtme pÃĢr administratorin", "face_detection": "Zbulimi i fytyrave", "face_detection_description": "Zbulo fytyrat nÃĢ skedarÃĢ duke pÃĢrdorur mÃĢsimin makinerik. PÃĢr videot, konsiderohet vetÃĢm miniatura. “Rifresko” (Refresh) pÃĢrpunon pÃĢrsÃĢri tÃĢ gjithÃĢ skedarÃĢt. “Rivendos” (Reset) gjithashtu fshin tÃĢ gjitha tÃĢ dhÃĢnat aktuale tÃĢ fytyrave. “Mungon” (Missing) vendos nÃĢ pritje skedarÃĢt qÃĢ ende nuk janÃĢ pÃĢrpunuar. Fytyrat e zbuluara do tÃĢ vendosen nÃĢ pritje pÃĢr Njohjen e Fytyrave pas pÃĢrfundimit tÃĢ Zbulimit tÃĢ Fytyrave, duke i grupuar ato te personat ekzistues ose tÃĢ rinj.", - "facial_recognition_job_description": "Gruponi fytyrat e zbuluara nÃĢ persona. Ky hap ekzekutohet pasi tÃĢ pÃĢrfundojÃĢ Zbulimi i FytyrÃĢs. \"Rivendos\" (ri-)grupon tÃĢ gjitha fytyrat. \"Mungon\" vendos nÃĢ radhÃĢ fytyrat qÃĢ nuk kanÃĢ njÃĢ person tÃĢ caktuar.", "failed_job_command": "Komanda {command} dÃĢshtoi pÃĢr punÃĢn: {job}", "force_delete_user_warning": "KUJDES: Kjo do tÃĢ heqÃĢ menjÃĢherÃĢ pÃĢrdoruesin dhe tÃĢ gjithÃĢ skedarÃĢt e tij. Ky veprim nuk mund tÃĢ zhbÃĢhet dhe skedarÃĢt nuk mund tÃĢ rikuperohen.", "image_format": "Formati", "image_format_description": "WebP prodhon skedarÃĢ mÃĢ tÃĢ vegjÃĢl se JPEG, por kodimi i tij ÃĢshtÃĢ mÃĢ i ngadaltÃĢ.", "image_fullsize_description": "Imazh me madhÃĢsi tÃĢ plotÃĢ pa metadata, pÃĢrdoret kur zmadhohet", "image_fullsize_enabled": "Aktivizo gjenerimin e imazhit me madhÃĢsi tÃĢ plotÃĢ", - "image_fullsize_enabled_description": "Gjeneroni imazh me madhÃĢsi tÃĢ plotÃĢ pÃĢr formatet jo tÃĢ pÃĢrshtatshme pÃĢr uebin. Kur aktivizohet \"Preferoni pamjen paraprake tÃĢ integruar\", pamjet paraprake tÃĢ integruara pÃĢrdoren direkt pa konvertim. Nuk ndikon nÃĢ formatet e pÃĢrshtatshme pÃĢr uebin si JPEG.", "image_fullsize_quality_description": "CilÃĢsia e imazhit me madhÃĢsi tÃĢ plotÃĢ nga 1-100. Sa mÃĢ e lartÃĢ, aq mÃĢ e mirÃĢ, por krijon skedarÃĢ mÃĢ tÃĢ mÃĢdhenj.", "image_fullsize_title": "CilÃĢsimet e imazhit me madhÃĢsi tÃĢ plotÃĢ", "image_prefer_embedded_preview": "Prefero parapamjen e integruar", "image_prefer_embedded_preview_setting_description": "PÃĢrdor parapamjet e integruara nÃĢ fotot te papÃĢrpunuara si hyrje pÃĢr pÃĢrpunimin e imazhit, kur janÃĢ tÃĢ disponueshme. Kjo mund tÃĢ japÃĢ ngjyra mÃĢ tÃĢ sakta pÃĢr disa imazhe, por cilÃĢsia e parapamjes varet nga kamera dhe imazhi mund tÃĢ ketÃĢ mÃĢ shumÃĢ artefakte tÃĢ kompresimit.", "image_prefer_wide_gamut": "Prefero gamÃĢn e gjerÃĢ tÃĢ ngjyrave", - "image_prefer_wide_gamut_setting_description": "PÃĢrdorni Display P3 pÃĢr miniaturat. Kjo ruan mÃĢ mirÃĢ gjallÃĢrinÃĢ e imazheve me hapÃĢsira tÃĢ gjera ngjyrash, por imazhet mund tÃĢ shfaqen ndryshe nÃĢ pajisjet e vjetra me njÃĢ version tÃĢ vjetÃĢr tÃĢ shfletuesit. Imazhet sRGB mbahen si sRGB pÃĢr tÃĢ shmangur ndryshimet e ngjyrave.", - "image_preview_description": "Imazh me madhÃĢsi mesatare me meta tÃĢ hequra, i pÃĢrdorur kur shikohet njÃĢ aset i vetÃĢm dhe pÃĢr tÃĢ mÃĢsuarit automatik", "image_preview_quality_description": "CilÃĢsia e shikimit paraprak nga 1-100. NjÃĢ cilÃĢsi mÃĢ e lartÃĢ ÃĢshtÃĢ mÃĢ e mirÃĢ, por prodhon skedarÃĢ mÃĢ tÃĢ mÃĢdhenj dhe mund tÃĢ zvogÃĢlojÃĢ reagimin e aplikacionit. Vendosja e njÃĢ vlere tÃĢ ulÃĢt mund tÃĢ ndikojÃĢ nÃĢ cilÃĢsinÃĢ e tÃĢ mÃĢsuarit automatik.", "image_preview_title": "CilÃĢsimet e parapamjes", - "image_progressive": "Progresive", "image_progressive_description": "Kodimi i imazheve JPEG bÃĢhet nÃĢ mÃĢnyrÃĢ progresive pÃĢr njÃĢ shfaqje me ngarkim gradual. Kjo nuk ka efekt nÃĢ imazhet WebP.", "image_quality": "CilÃĢsia", "image_resolution": "Rezolucioni", "image_resolution_description": "Rezolucionet mÃĢ tÃĢ larta mund tÃĢ ruajnÃĢ mÃĢ shumÃĢ detaje, por kÃĢrkojnÃĢ mÃĢ shumÃĢ kohÃĢ pÃĢr t'u koduar, kanÃĢ madhÃĢsi mÃĢ tÃĢ mÃĢdha skedarÃĢsh dhe mund tÃĢ zvogÃĢlojnÃĢ reagimin e aplikacionit.", "image_settings": "CilÃĢsimet e imazhit", "image_settings_description": "Menaxhoni cilÃĢsinÃĢ dhe rezolucionin e imazheve tÃĢ gjeneruara", - "image_thumbnail_description": "ParathÃĢnie e vogÃĢl me metadata tÃĢ hequra, e pÃĢrdorur kur shikoni grupe fotografish si nÃĢ linjÃĢn kryesore kohore", "image_thumbnail_quality_description": "CilÃĢsia e miniaturave nga 1-100. Sa mÃĢ e lartÃĢ tÃĢ jetÃĢ aq mÃĢ mirÃĢ, por prodhon skedarÃĢ mÃĢ tÃĢ mÃĢdhenj dhe mund tÃĢ zvogÃĢlojÃĢ reagimin e aplikacionit.", "image_thumbnail_title": "CilÃĢsimet e miniaturÃĢs", "import_config_from_json_description": "Importo konfigurimin e sistemit duke ngarkuar njÃĢ skedar konfigurimi JSON", - "integrity_checks_checksum_files": "SkedarÃĢ checksum", - "integrity_checks_checksum_files_description": "Konfiguro kontrollin e checksum-it", - "integrity_checks_checksum_files_enable_description": "Aktivizo kontrollin e checksum-it", - "integrity_checks_checksum_files_percentage_limit": "Kufi pÃĢrqindjeje", - "integrity_checks_checksum_files_percentage_limit_description": "Konfiguro pÃĢrqindjen maksimale midis 0.01 dhe 1 pÃĢr sa duhet tÃĢ ekzekutohet kontrolli i checksum-it nÃĢ Ã§do interval.", - "integrity_checks_checksum_files_time_limit": "Kufi kohor", - "integrity_checks_checksum_files_time_limit_description": "Konfiguro kohÃĢzgjatjen maksimale pÃĢr tÃĢ cilÃĢn duhet tÃĢ ekzekutohet kontrolli i checksum-it nÃĢ Ã§do interval. (ms)", - "integrity_checks_missing_files": "SkedarÃĢ qÃĢ mungojnÃĢ", - "integrity_checks_missing_files_description": "Konfiguro shpeshtÃĢsinÃĢ dhe aktivizo ose çaktivizo kontrollin e skedarÃĢve qÃĢ mungojnÃĢ", - "integrity_checks_missing_files_enable_description": "Aktivizo kontrollin e skedarÃĢve qÃĢ mungojnÃĢ", - "integrity_checks_settings": "Kontrolle integriteti", - "integrity_checks_settings_description": "Menaxho intervalet e kontrolleve tÃĢ integritetit", - "integrity_checks_untracked_files": "SkedarÃĢ tÃĢ pandjekur", - "integrity_checks_untracked_files_description": "Konfiguro shpeshtÃĢsinÃĢ dhe aktivizo ose çaktivizo kontrollin e skedarÃĢve tÃĢ pandjekur", - "integrity_checks_untracked_files_enable_description": "Aktivizo kontrollin e skedarÃĢve tÃĢ pandjekur", "job_concurrency": "{job} paralele", "job_created": "Puna u krijua", "job_not_concurrency_safe": "Kjo punÃĢ nuk ÃĢshtÃĢ e sigurt pÃĢr paralelizmin.", "job_settings": "CilÃĢsimet e punÃĢs", "job_settings_description": "Menaxhoni konkurencÃĢn e punÃĢs", - "jobs_delayed": "{jobCount, plural, other {# delayed}}", - "jobs_failed": "{jobCount, plural, other {# failed}}", - "jobs_over_time": "PunÃĢt me kalimin e kohÃĢs", - "library_created": "Krijuar bibliotekÃĢ: {library}", - "library_deleted": "Biblioteka u fshi", - "library_details": "Detajet e bibliotekÃĢs", - "library_folder_description": "Specifikoni njÃĢ dosje pÃĢr importim. Kjo dosje, pÃĢrfshirÃĢ nÃĢndosjet, do tÃĢ skanohet pÃĢr imazhe dhe video.", - "library_remove_exclusion_pattern_prompt": "A jeni tÃĢ sigurt qÃĢ dÃĢshironi tÃĢ hiqni kÃĢtÃĢ model pÃĢrjashtimi?", - "library_remove_folder_prompt": "A jeni tÃĢ sigurt qÃĢ dÃĢshironi tÃĢ hiqni kÃĢtÃĢ dosje importi?", "library_scanning": "Skanimi periodik", "library_scanning_description": "Konfiguro skanimin periodik tÃĢ bibliotekÃĢs", - "library_scanning_enable_description": "Aktivizo skanimin periodik tÃĢ bibliotekÃĢs", - "library_settings": "Biblioteka e jashtme", - "library_settings_description": "Menaxho cilÃĢsimet e bibliotekÃĢs sÃĢ jashtme", - "library_tasks_description": "Skano bibliotekat e jashtme pÃĢr asete tÃĢ reja dhe/ose tÃĢ ndryshuara", - "library_updated": "BibliotekÃĢ e pÃĢrditÃĢsuar", - "library_watching_enable_description": "VÃĢzhgoni bibliotekat e jashtme pÃĢr ndryshime tÃĢ skedarÃĢve", - "library_watching_settings": "VÃĢzhgimi i bibliotekÃĢs [EKSPERIMENTALE]", - "library_watching_settings_description": "VÃĢzhgoni automatikisht skedarÃĢt e ndryshuar", - "logging_enable_description": "Aktivizo regjistrimin", - "logging_level_description": "Kur ÃĢshtÃĢ aktivizuar, cilin nivel logu tÃĢ pÃĢrdorÃĢ.", - "logging_settings": "Regjistrimi", - "machine_learning_availability_checks": "Kontrollet e disponueshmÃĢrisÃĢ", - "machine_learning_availability_checks_description": "TÃĢ zbulojÃĢ automatikisht dhe tÃĢ preferojÃĢ serverÃĢt e disponueshÃĢm tÃĢ mÃĢsimit makinerik", - "machine_learning_availability_checks_enabled": "Aktivizo kontrollet e disponueshmÃĢrisÃĢ", - "machine_learning_availability_checks_interval": "Kontrolli i intervalit", - "machine_learning_availability_checks_interval_description": "Intervali nÃĢ milisekonda midis kontrolleve tÃĢ disponueshmÃĢrisÃĢ", - "machine_learning_availability_checks_timeout": "KÃĢrko timeout", - "machine_learning_availability_checks_timeout_description": "KohÃĢzgjatja nÃĢ milisekonda pÃĢr kontrollet e disponueshmÃĢrisÃĢ", - "machine_learning_clip_model": "Modeli CLIP", - "machine_learning_clip_model_description": "Emri i njÃĢ modeli CLIP ÃĢshtÃĢ listuar kÃĢtu. Vini re se duhet ta riekzekutoni punÃĢn 'KÃĢrkim i Mençur' pÃĢr tÃĢ gjitha imazhet pas ndryshimit tÃĢ njÃĢ modeli.", - "machine_learning_duplicate_detection": "Zbulimi i dublikatave", - "machine_learning_duplicate_detection_enabled": "Aktivizo zbulimin e dublikatave", - "machine_learning_duplicate_detection_enabled_description": "NÃĢse çaktivizohet, asetet saktÃĢsisht identike do tÃĢ hiqen nga dublikimet.", - "machine_learning_duplicate_detection_setting_description": "PÃĢrdorni ngulitje CLIP pÃĢr tÃĢ gjetur dublikata tÃĢ mundshme", - "machine_learning_enabled": "Aktivizo tÃĢ mÃĢsuarit automatik", - "machine_learning_enabled_description": "NÃĢse çaktivizohet, tÃĢ gjitha veçoritÃĢ e ML do tÃĢ Ã§aktivizohen pavarÃĢsisht nga cilÃĢsimet e mÃĢposhtme.", - "machine_learning_facial_recognition": "Njohja e fytyrÃĢs", - "machine_learning_facial_recognition_description": "Zbuloni, njihni dhe gruponi fytyrat nÃĢ imazhe", - "machine_learning_facial_recognition_model": "Modeli i njohjes sÃĢ fytyrÃĢs", - "machine_learning_facial_recognition_model_description": "Modelet renditen nÃĢ rend zbritÃĢs tÃĢ madhÃĢsisÃĢ. Modelet mÃĢ tÃĢ mÃĢdha janÃĢ mÃĢ tÃĢ ngadalta dhe pÃĢrdorin mÃĢ shumÃĢ memorie, por japin rezultate mÃĢ tÃĢ mira. Vini re se duhet ta riekzekutoni punÃĢn e Zbulimit tÃĢ FytyrÃĢs pÃĢr tÃĢ gjitha imazhet pas ndryshimit tÃĢ njÃĢ modeli.", - "machine_learning_facial_recognition_setting": "Aktivizo njohjen e fytyrÃĢs", - "machine_learning_facial_recognition_setting_description": "NÃĢse çaktivizohet, imazhet nuk do tÃĢ kodohen pÃĢr njohjen e fytyrÃĢs dhe nuk do tÃĢ popullojnÃĢ seksionin e NjerÃĢzve nÃĢ faqen Eksploro.", - "machine_learning_max_detection_distance": "Distanca maksimale e zbulimit", - "machine_learning_max_detection_distance_description": "Distanca maksimale midis dy imazheve pÃĢr t'i konsideruar ato si dublikate, duke filluar nga 0.001-0.1. Vlerat mÃĢ tÃĢ larta do tÃĢ zbulojnÃĢ mÃĢ shumÃĢ dublikate, por mund tÃĢ rezultojnÃĢ nÃĢ pozitive tÃĢ rreme.", - "machine_learning_max_recognition_distance": "Distanca maksimale e njohjes", - "machine_learning_max_recognition_distance_description": "Distanca maksimale midis dy fytyrave pÃĢr t'u konsideruar i njÃĢjti person, varion nga 0-2. Ulja e kÃĢsaj distance mund tÃĢ parandalojÃĢ etiketimin e dy personave si i njÃĢjti person, ndÃĢrsa rritja e saj mund tÃĢ parandalojÃĢ etiketimin e tÃĢ njÃĢjtit person si dy persona tÃĢ ndryshÃĢm. Vini re se ÃĢshtÃĢ mÃĢ e lehtÃĢ tÃĢ bashkoni dy persona sesa tÃĢ ndani njÃĢ person nÃĢ dy, prandaj zgjidhni njÃĢ prag mÃĢ tÃĢ ulÃĢt kur ÃĢshtÃĢ e mundur.", - "machine_learning_min_detection_score": "Rezultati minimal i zbulimit", - "machine_learning_min_detection_score_description": "Rezultati minimal i besimit pÃĢr njÃĢ fytyrÃĢ qÃĢ do tÃĢ zbulohet ÃĢshtÃĢ nga 0-1. Vlerat mÃĢ tÃĢ ulÃĢta do tÃĢ zbulojnÃĢ mÃĢ shumÃĢ fytyra, por mund tÃĢ rezultojnÃĢ nÃĢ rezultate tÃĢ rreme pozitive.", - "machine_learning_min_recognized_faces": "Fytyrat minimale tÃĢ njohura", - "machine_learning_min_recognized_faces_description": "Numri minimal i fytyrave tÃĢ njohura qÃĢ duhet tÃĢ krijohet pÃĢr njÃĢ person. Rritja e kÃĢsaj e bÃĢn Njohjen e FytyrÃĢs mÃĢ tÃĢ saktÃĢ me koston e rritjes sÃĢ mundÃĢsisÃĢ qÃĢ njÃĢ fytyrÃĢ tÃĢ mos i caktohet njÃĢ personi.", - "machine_learning_ocr": "OCR", - "machine_learning_ocr_description": "PÃĢrdorni tÃĢ mÃĢsuarit automatik pÃĢr tÃĢ njohur tekstin nÃĢ imazhe", - "machine_learning_ocr_enabled": "Aktivizo OCR-nÃĢ", - "machine_learning_ocr_enabled_description": "NÃĢse çaktivizohet, imazhet nuk do t'i nÃĢnshtrohen njohjes sÃĢ tekstit.", - "machine_learning_ocr_max_resolution": "Rezolucioni maksimal", - "machine_learning_ocr_max_resolution_description": "Pamjet paraprake mbi kÃĢtÃĢ rezolucion do tÃĢ ndryshohen nÃĢ madhÃĢsi duke ruajtur raportin e aspektit. Vlerat mÃĢ tÃĢ larta janÃĢ mÃĢ tÃĢ sakta, por kÃĢrkojnÃĢ mÃĢ shumÃĢ kohÃĢ pÃĢr t'u pÃĢrpunuar dhe pÃĢrdorin mÃĢ shumÃĢ memorie.", - "machine_learning_ocr_min_detection_score": "Rezultati minimal i zbulimit", - "machine_learning_ocr_min_detection_score_description": "Rezultati minimal i besimit pÃĢr tekstin qÃĢ duhet zbuluar ÃĢshtÃĢ nga 0-1. Vlerat mÃĢ tÃĢ ulÃĢta do tÃĢ zbulojnÃĢ mÃĢ shumÃĢ tekst, por mund tÃĢ rezultojnÃĢ nÃĢ pozitive tÃĢ rreme.", - "machine_learning_ocr_min_recognition_score": "Rezultati minimal i njohjes", - "machine_learning_ocr_min_score_recognition_description": "Rezultati minimal i besimit pÃĢr tekstin e zbuluar qÃĢ duhet tÃĢ njihet nga 0-1. Vlerat mÃĢ tÃĢ ulÃĢta do tÃĢ njohin mÃĢ shumÃĢ tekst, por mund tÃĢ rezultojnÃĢ nÃĢ pozitive tÃĢ rreme.", - "machine_learning_ocr_model": "Modeli OCR", - "machine_learning_ocr_model_description": "Modelet e serverit janÃĢ mÃĢ tÃĢ sakta se modelet mobile, por kÃĢrkojnÃĢ mÃĢ shumÃĢ kohÃĢ pÃĢr t'u pÃĢrpunuar dhe pÃĢrdorin mÃĢ shumÃĢ memorie.", - "machine_learning_settings": "CilÃĢsimet e tÃĢ MÃĢsuarit Automatik", - "machine_learning_settings_description": "Menaxho veçoritÃĢ dhe cilÃĢsimet e tÃĢ mÃĢsuarit automatik", - "machine_learning_smart_search": "KÃĢrkim i Mençur", - "machine_learning_smart_search_description": "KÃĢrko pÃĢr imazhe semantikisht duke pÃĢrdorur ngulitje CLIP", - "machine_learning_smart_search_enabled": "Aktivizo kÃĢrkimin inteligjent", - "machine_learning_smart_search_enabled_description": "NÃĢse çaktivizohet, imazhet nuk do tÃĢ kodohen pÃĢr kÃĢrkim inteligjent.", - "machine_learning_url_description": "URL-ja e serverit tÃĢ tÃĢ mÃĢsuarit automatik. NÃĢse jepet mÃĢ shumÃĢ se njÃĢ URL, secili server do tÃĢ provohet njÃĢ nga njÃĢ derisa njÃĢri tÃĢ pÃĢrgjigjet me sukses, nÃĢ rend nga i pari tek i fundit. ServerÃĢt qÃĢ nuk pÃĢrgjigjen do tÃĢ injorohen pÃĢrkohÃĢsisht derisa tÃĢ kthehen nÃĢ linjÃĢ.", - "maintenance_backup_management": "Menaxhimi i kopjeve rezervÃĢ", - "maintenance_delete_backup": "Fshi kopjen rezervÃĢ", - "maintenance_delete_backup_description": "Ky skedar do tÃĢ fshihet nÃĢ mÃĢnyrÃĢ tÃĢ pakthyeshme.", - "maintenance_delete_error": "Fshirja e kopjes rezervÃĢ dÃĢshtoi.", - "maintenance_integrity_check": "Kontrollo", - "maintenance_integrity_check_all": "Kontrollo tÃĢ gjitha", - "maintenance_integrity_checksum_mismatch": "MospÃĢrputhje e shumÃĢs sÃĢ kontrollit", - "maintenance_integrity_checksum_mismatch_description": "SkedarÃĢt pÃĢr tÃĢ cilÃĢt checksumi nÃĢ disk nuk pÃĢrputhet me checksumin qÃĢ Immich ka ruajtur nÃĢ bazÃĢn e tÃĢ dhÃĢnave tÃĢ tij.", - "maintenance_integrity_checksum_mismatch_job": "Kontrolloni pÃĢr mospÃĢrputhje tÃĢ shumÃĢs sÃĢ kontrollit", - "maintenance_integrity_checksum_mismatch_refresh_job": "Rifresko raportet e mospÃĢrputhjes sÃĢ shumÃĢs sÃĢ kontrollit", - "maintenance_integrity_missing_file": "SkedarÃĢt qÃĢ mungojnÃĢ", - "maintenance_integrity_missing_file_description": "SkedarÃĢt qÃĢ Immich i ka gjurmuar nÃĢ bazÃĢn e tÃĢ dhÃĢnave tÃĢ tij, por qÃĢ nuk ekzistojnÃĢ nÃĢ sistemin e skedarÃĢve.", - "maintenance_integrity_missing_file_job": "Kontrolloni pÃĢr skedarÃĢ qÃĢ mungojnÃĢ", - "maintenance_integrity_missing_file_refresh_job": "Rifresko raportet e skedarÃĢve qÃĢ mungojnÃĢ", - "maintenance_integrity_report": "Raporti i Integritetit", - "maintenance_integrity_untracked_file": "SkedarÃĢt e Pagjurmuar", - "maintenance_integrity_untracked_file_description": "SkedarÃĢt nÃĢ direktoritÃĢ e Immich-it pÃĢr tÃĢ cilÃĢt Immich nuk ka asnjÃĢ regjistÃĢr.", - "maintenance_integrity_untracked_file_job": "Kontrolloni pÃĢr skedarÃĢ tÃĢ pagjurmuar", - "maintenance_integrity_untracked_file_refresh_job": "Rifresko raportet e skedarÃĢve tÃĢ pagjurmuar", - "maintenance_restore_backup": "Rivendos rezervÃĢn", - "maintenance_restore_backup_description": "Immich do tÃĢ fshihet dhe do tÃĢ rikthehet nga kopja rezervÃĢ e zgjedhur. NjÃĢ kopje rezervÃĢ do tÃĢ krijohet pÃĢrpara se tÃĢ vazhdohet.", - "maintenance_restore_backup_different_version": "Ky kopje rezervÃĢ u krijua me njÃĢ version tÃĢ ndryshÃĢm tÃĢ Immich!", - "maintenance_restore_backup_unknown_version": "Nuk mund tÃĢ pÃĢrcaktohej versioni i rezervimit.", - "maintenance_restore_database_backup": "Rivendos kopjen rezervÃĢ tÃĢ bazÃĢs sÃĢ tÃĢ dhÃĢnave", - "maintenance_restore_database_backup_description": "Rikthim nÃĢ njÃĢ gjendje mÃĢ tÃĢ hershme tÃĢ bazÃĢs sÃĢ tÃĢ dhÃĢnave duke pÃĢrdorur njÃĢ skedar rezervÃĢ", - "maintenance_settings": "MirÃĢmbajtje", - "maintenance_settings_description": "Vendos Immich nÃĢ modalitetin e mirÃĢmbajtjes.", - "maintenance_start": "Kaloni nÃĢ modalitetin e mirÃĢmbajtjes", - "maintenance_start_error": "DÃĢshtoi nÃĢ nisjen e modalitetit tÃĢ mirÃĢmbajtjes.", - "maintenance_upload_backup": "Ngarko skedarin rezervÃĢ tÃĢ bazÃĢs sÃĢ tÃĢ dhÃĢnave", - "maintenance_upload_backup_error": "Nuk mund tÃĢ ngarkohet kopja rezervÃĢ, a ÃĢshtÃĢ njÃĢ skedar .sql/.sql.gz?", - "manage_concurrency": "Menaxho KonkurrencÃĢn", - "manage_concurrency_description": "Shko te faqja e punÃĢve pÃĢr tÃĢ menaxhuar paralelisht punÃĢn", - "manage_log_settings": "Menaxho cilÃĢsimet e regjistrit", - "map_dark_style": "Stil i errÃĢt", - "map_enable_description": "Aktivizo veçoritÃĢ e hartÃĢs", - "map_gps_settings": "CilÃĢsimet e HartÃĢs dhe GPS-it", - "map_gps_settings_description": "Menaxho CilÃĢsimet e HartÃĢs dhe GPS-it (Gjeokodimi i AnasjelltÃĢ)", - "map_implications": "Funksioni i hartÃĢs mbÃĢshtetet nÃĢ njÃĢ shÃĢrbim tÃĢ jashtÃĢm me pllaka (tiles.immich.cloud)", - "map_light_style": "Stil i lehtÃĢ", - "map_manage_reverse_geocoding_settings": "Menaxho cilÃĢsimet e Gjeokodimit tÃĢ kundÃĢrt", - "map_reverse_geocoding": "Gjeokodimi i kundÃĢrt", - "map_reverse_geocoding_enable_description": "Aktivizo gjeokodimin e kundÃĢrt", - "map_reverse_geocoding_settings": "CilÃĢsimet e Gjeokodimit tÃĢ AnasjelltÃĢ", - "map_settings": "HartÃĢ", - "map_settings_description": "Menaxho cilÃĢsimet e hartÃĢs", - "map_style_description": "URL-ja e njÃĢ teme harte style.json", - "memory_cleanup_job": "Pastrimi i kujtesÃĢs", - "memory_generate_job": "Gjenerimi i kujtesÃĢs", - "metadata_extraction_job": "Ekstraktoni metadatat", - "metadata_extraction_job_description": "Nxirrni informacionin e meta-tadatave nga çdo aset, siç janÃĢ GPS, fytyrat dhe rezolucioni", - "metadata_faces_import_setting": "Aktivizo importimin e fytyrÃĢs", - "metadata_faces_import_setting_description": "Importo fytyrat nga tÃĢ dhÃĢnat EXIF tÃĢ imazhit dhe skedarÃĢt e karrocÃĢs anÃĢsore", - "metadata_settings": "CilÃĢsimet e meta tÃĢ dhÃĢnave", - "metadata_settings_description": "Menaxho cilÃĢsimet e meta-tÃĢ dhÃĢnave", - "migration_job": "Migrimi", - "migration_job_description": "Migro miniaturat pÃĢr asetet dhe fytyrat nÃĢ strukturÃĢn mÃĢ tÃĢ fundit tÃĢ dosjeve", - "nightly_tasks_cluster_faces_setting_description": "Ekzekutoni njohjen e fytyrÃĢs nÃĢ fytyrat e zbuluara rishtazi", - "nightly_tasks_cluster_new_faces_setting": "Grumbulloni fytyra tÃĢ reja", - "nightly_tasks_database_cleanup_setting": "Detyrat e pastrimit tÃĢ bazÃĢs sÃĢ tÃĢ dhÃĢnave", - "nightly_tasks_database_cleanup_setting_description": "Pastroni tÃĢ dhÃĢnat e vjetra dhe tÃĢ skaduara nga baza e tÃĢ dhÃĢnave", - "nightly_tasks_generate_memories_setting": "Gjeneroni kujtime", - "nightly_tasks_generate_memories_setting_description": "Krijo kujtime tÃĢ reja nga asetet", - "nightly_tasks_missing_thumbnails_setting": "Gjenero miniaturat qÃĢ mungojnÃĢ", - "nightly_tasks_missing_thumbnails_setting_description": "Vendosni nÃĢ radhÃĢ asetet pa miniatura pÃĢr gjenerimin e miniaturave", - "nightly_tasks_settings": "CilÃĢsimet e Detyrave tÃĢ NatÃĢs", - "nightly_tasks_settings_description": "Menaxho detyrat e natÃĢs", - "nightly_tasks_start_time_setting": "Ora e fillimit", - "nightly_tasks_start_time_setting_description": "Koha nÃĢ tÃĢ cilÃĢn serveri fillon tÃĢ ekzekutojÃĢ detyrat e natÃĢs", - "nightly_tasks_sync_quota_usage_setting": "PÃĢrdorimi i kuotÃĢs sÃĢ sinkronizimit", - "nightly_tasks_sync_quota_usage_setting_description": "PÃĢrditÃĢso kuotÃĢn e ruajtjes sÃĢ pÃĢrdoruesit, bazuar nÃĢ pÃĢrdorimin aktual", - "no_paths_added": "Nuk janÃĢ shtuar shtigje", - "no_pattern_added": "Nuk ÃĢshtÃĢ shtuar asnjÃĢ model", - "note_apply_storage_label_previous_assets": "ShÃĢnim: PÃĢr tÃĢ aplikuar EtiketÃĢn e Ruajtjes nÃĢ asetet e ngarkuara mÃĢ parÃĢ, ekzekutoni", - "note_cannot_be_changed_later": "SHËNIM: Kjo nuk mund tÃĢ ndryshohet mÃĢ vonÃĢ!", - "notification_email_from_address": "Adresa nga", - "notification_email_from_address_description": "Adresa e email-it tÃĢ dÃĢrguesit, pÃĢr shembull: \"Immich Photo Server \". Sigurohuni qÃĢ tÃĢ pÃĢrdorni njÃĢ adresÃĢ nga e cila lejoheni tÃĢ dÃĢrgoni email-e.", - "notification_email_host_description": "Strehuesi i serverit tÃĢ email-it (p.sh. smtp.immich.app)", - "notification_email_ignore_certificate_errors": "Injoro gabimet e certifikatÃĢs", - "notification_email_ignore_certificate_errors_description": "Injoroni gabimet e validimit tÃĢ certifikatÃĢs TLS (nuk rekomandohet)", - "notification_email_password_description": "FjalÃĢkalimi qÃĢ duhet pÃĢrdorur gjatÃĢ autentifikimit me serverin e email-it", - "notification_email_port_description": "Porta e serverit tÃĢ email-it (p.sh. 25, 465 ose 587)", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "PÃĢrdorni SMTPS (SMTP mbi TLS)", - "notification_email_sent_test_email_button": "DÃĢrgo email-in e provÃĢs dhe ruaj", - "notification_email_setting_description": "CilÃĢsimet pÃĢr dÃĢrgimin e njoftimeve me email", - "notification_email_test_email": "DÃĢrgo email testimi", - "notification_email_test_email_failed": "DÃĢrgimi i email-it tÃĢ testimit dÃĢshtoi, kontrolloni vlerat tuaja", - "notification_email_test_email_sent": "NjÃĢ email provÃĢ ÃĢshtÃĢ dÃĢrguar te {email}. Ju lutemi kontrolloni kutinÃĢ tuaj hyrÃĢse.", - "notification_email_username_description": "Emri i pÃĢrdoruesit qÃĢ do tÃĢ pÃĢrdoret gjatÃĢ autentifikimit me serverin e email-it", - "notification_enable_email_notifications": "Aktivizo njoftimet me email", - "notification_settings": "CilÃĢsimet e njoftimeve", - "notification_settings_description": "Menaxho cilÃĢsimet e njoftimeve, duke pÃĢrfshirÃĢ email-in", - "oauth_allow_insecure_requests": "Lejo kÃĢrkesa tÃĢ pasigurta", - "oauth_allow_insecure_requests_description": "KUJDES: Kjo çaktivizon validimin e certifikatÃĢs TLS pÃĢr kÃĢrkesat OAuth dhe mund t'ju ekspozojÃĢ ndaj sulmeve MITM.", - "oauth_auto_launch": "Nisja automatike", - "oauth_auto_launch_description": "Filloni automatikisht rrjedhÃĢn e hyrjes nÃĢ OAuth sapo tÃĢ shkoni nÃĢ faqen e hyrjes", - "oauth_auto_register": "Regjistrim automatik", - "oauth_auto_register_description": "Regjistro automatikisht pÃĢrdoruesit e rinj pas hyrjes me OAuth", - "oauth_button_text": "Teksti i butonit", - "oauth_client_secret_description": "E detyrueshme pÃĢr klient konfidencial, ose nÃĢse PKCE (ÇelÃĢsi i ProvÃĢs pÃĢr ShkÃĢmbimin e Kodit) nuk mbÃĢshtetet pÃĢr klientin publik.", - "oauth_enable_description": "Kyçu me OAuth", - "oauth_end_session_url_description": "Ridrejto pÃĢrdoruesin nÃĢ kÃĢtÃĢ URI kur tÃĢ dalÃĢ.", - "oauth_mobile_redirect_uri": "URL-ja e ridrejtimit pÃĢr celular", - "oauth_mobile_redirect_uri_override": "Mbivendosja e URI-sÃĢ sÃĢ ridrejtimit celular", - "oauth_mobile_redirect_uri_override_description": "Aktivizo kur ofruesi i OAuth nuk lejon njÃĢ URI celular, si p.sh. ''{callback}''", - "oauth_prompt_description": "Parametri i kÃĢrkesÃĢs (p.sh. select_account, login, consent)", - "oauth_role_claim": "Pretendimi pÃĢr Rol", - "oauth_role_claim_description": "Jep automatikisht akses administratori bazuar nÃĢ praninÃĢ e kÃĢtij pretendimi. Pretendimi mund tÃĢ ketÃĢ ose 'pÃĢrdorues' ose 'administrator'.", - "oauth_settings": "OAuth", - "oauth_settings_description": "Menaxho cilÃĢsimet e hyrjes nÃĢ OAuth", - "oauth_settings_more_details": "PÃĢr mÃĢ shumÃĢ detaje rreth kÃĢsaj veçorie, referojuni dokumenteve.", - "oauth_storage_label_claim": "Pretendimi pÃĢr etiketÃĢn e ruajtjes", - "oauth_storage_label_claim_description": "Cakto automatikisht etiketÃĢn e ruajtjes sÃĢ pÃĢrdoruesit nÃĢ vlerÃĢn e kÃĢtij pretendimi.", - "oauth_storage_quota_claim": "Pretendim pÃĢr kuotÃĢn e ruajtjes sÃĢ tÃĢ dhÃĢnave", - "oauth_storage_quota_claim_description": "Cakto automatikisht kuotÃĢn e ruajtjes sÃĢ pÃĢrdoruesit nÃĢ vlerÃĢn e kÃĢtij pretendimi.", - "oauth_storage_quota_default": "Kuota e parazgjedhur e ruajtjes (GiB)", - "oauth_storage_quota_default_description": "Kuota nÃĢ GiB do tÃĢ pÃĢrdoret kur nuk paraqitet asnjÃĢ kÃĢrkesÃĢ.", - "oauth_timeout": "KÃĢrkesÃĢ pÃĢr skadimin e kohÃĢs", - "oauth_timeout_description": "KohÃĢzgjatja e pritjes pÃĢr kÃĢrkesat nÃĢ milisekonda", - "ocr_job_description": "PÃĢrdorni tÃĢ mÃĢsuarit automatik pÃĢr tÃĢ njohur tekstin nÃĢ imazhe", - "password_enable_description": "Kyçu me email dhe fjalÃĢkalim", - "password_settings": "FjalÃĢkalimi Hyrje", - "password_settings_description": "Menaxho cilÃĢsimet e hyrjes me fjalÃĢkalim", - "paths_validated_successfully": "TÃĢ gjitha shtigjet u validuan me sukses", - "person_cleanup_job": "Pastrim personash", - "queue_details": "Detajet e radhÃĢs", - "queues": "RadhÃĢt e PunÃĢs", - "queues_page_description": "Faqja e radhÃĢve tÃĢ punÃĢs sÃĢ administratorit", - "quota_size_gib": "MadhÃĢsia e KuotÃĢs (GiB)", - "refreshing_all_libraries": "Duke rifreskuar tÃĢ gjitha bibliotekat", - "registration": "Regjistrimi i Administratorit", - "registration_description": "MeqenÃĢse jeni pÃĢrdoruesi i parÃĢ nÃĢ sistem, do tÃĢ caktoheni si Administrator dhe do tÃĢ jeni pÃĢrgjegjÃĢs pÃĢr detyrat administrative, dhe pÃĢrdorues tÃĢ tjerÃĢ do tÃĢ krijohen nga ju.", - "release_channel_release_candidate": "Kandidat pÃĢr lirim", - "release_channel_stable": "I QÃĢndrueshÃĢm", - "remove_failed_jobs": "Hiq punÃĢt e dÃĢshtuara", - "require_password_change_on_login": "KÃĢrkon qÃĢ pÃĢrdoruesi tÃĢ ndryshojÃĢ fjalÃĢkalimin nÃĢ hyrjen e parÃĢ", - "reset_settings_to_default": "Rivendos cilÃĢsimet nÃĢ ato tÃĢ parazgjedhura", - "reset_settings_to_recent_saved": "Rivendos cilÃĢsimet nÃĢ cilÃĢsimet e ruajtura sÃĢ fundmi", - "scanning_library": "Duke skanuar bibliotekÃĢn", - "search_jobs": "KÃĢrko punÃĢâ€Ļ", - "send_welcome_email": "DÃĢrgo email mirÃĢseardhjeje", - "server_external_domain_settings": "Domeni i jashtÃĢm", - "server_external_domain_settings_description": "Domeni i pÃĢrdorur pÃĢr lidhjet e jashtme", - "server_public_users": "PÃĢrdoruesit publikÃĢ", - "server_public_users_description": "TÃĢ gjithÃĢ pÃĢrdoruesit (emri dhe emaili) shfaqen kur shtohet njÃĢ pÃĢrdorues nÃĢ albumet e pÃĢrbashkÃĢta. Kur ÃĢshtÃĢ Ã§aktivizuar, lista e pÃĢrdoruesve do tÃĢ jetÃĢ e disponueshme vetÃĢm pÃĢr pÃĢrdoruesit admin.", - "server_settings": "Caktimet e serverit", - "server_settings_description": "Menaxho cilÃĢsimet e serverit", - "server_stats_page_description": "Faqja e statistikave tÃĢ serverit administrativ", - "server_welcome_message": "Mesazh mirÃĢseardhjeje", - "server_welcome_message_description": "NjÃĢ mesazh qÃĢ shfaqet nÃĢ faqen e hyrjes.", - "settings_page_description": "Faqja e cilÃĢsimeve tÃĢ administrimit", - "sidecar_job": "Metadata sidecar", - "sidecar_job_description": "Zbulo ose sinkronizo metadatat sidecar nga sistemi i skedarÃĢve", - "slideshow_duration_description": "Numri i sekondave pÃĢr me shfaqÃĢ Ã§do imazh", - "smart_search_job_description": "Ekzekuto mÃĢsim makine mbi asetet pÃĢr me mbÃĢshtetÃĢ kÃĢrkimin inteligjent", - "storage_template_date_time_description": "Markatimpi i krijimit tÃĢ asetit pÃĢrdoret pÃĢr informacionin e datÃĢs dhe orÃĢs", - "storage_template_date_time_sample": "Koha e mostrÃĢs {date}", - "storage_template_enable_description": "Aktivizo motorin e shabllonit tÃĢ ruajtjes", - "storage_template_hash_verification_enabled": "Verifikimi i hash-it asht aktivizuar", - "storage_template_hash_verification_enabled_description": "Aktivizon verifikimin e hash-it, mos e çaktivizo kÃĢtÃĢ nÃĢse nuk je i sigurt pÃĢr implikacionet", - "storage_template_migration": "Migrimi i shabllonit tÃĢ ruajtjes", - "storage_template_migration_description": "Apliko {template} aktual te asetet e ngarkuara ma parÃĢ", - "storage_template_migration_info": "Shablloni i ruajtjes do t'i konvertojÃĢ tÃĢ gjitha shtesat nÃĢ shkronja tÃĢ vogla. Ndryshimet e shabllonit do tÃĢ aplikohen vetÃĢm pÃĢr asetet e reja. PÃĢr t'i aplikuar retroaktivisht te asetet e ngarkuara ma parÃĢ, ekzekuto {job}.", - "storage_template_migration_job": "Puna e migrimit tÃĢ shabllonit tÃĢ ruajtjes", - "storage_template_more_details": "PÃĢr ma shumÃĢ detaje rreth kÃĢsaj veçorie, referoju Shabllonit tÃĢ Ruajtjes dhe implikacioneve tÃĢ tij", - "storage_template_onboarding_description_v2": "Kur aktivizohet, kjo veçori do t'i organizojÃĢ automatikisht skedarÃĢt bazuar nÃĢ njÃĢ shabllon tÃĢ definuar nga pÃĢrdoruesi. PÃĢr ma shumÃĢ informacion, shih dokumentacionin.", - "storage_template_path_length": "Kufiri afÃĢrsisht i gjatÃĢsisÃĢ sÃĢ shtegut: {length, number}/{limit, number}", - "storage_template_settings": "Shablloni i ruajtjes", - "storage_template_settings_description": "Menaxho strukturÃĢn e dosjeve dhe emrin e skedarit tÃĢ asetit tÃĢ ngarkuar", - "storage_template_user_label": "{label} asht Etiketa e Ruajtjes e pÃĢrdoruesit", - "system_settings": "CilÃĢsimet e sistemit", - "tag_cleanup_job": "Pastrimi i etiketave", - "template_email_available_tags": "Mund tÃĢ pÃĢrdorÃĢsh variablat e mÃĢposhtme nÃĢ shabllonin tÃĢnd: {tags}", - "template_email_if_empty": "NÃĢse shablloni asht bosh, do tÃĢ pÃĢrdoret emaili i parazgjedhur.", - "template_email_invite_album": "Shablloni i ftesÃĢs nÃĢ album", - "template_email_preview": "Pamja paraprake", - "template_email_settings": "Shabllonet e emailit", - "template_email_update_album": "Shablloni i pÃĢrditÃĢsimit tÃĢ albumit", - "template_email_welcome": "Shablloni i emailit tÃĢ mirÃĢseardhjes", - "template_settings": "Shablonet e njoftimeve", - "template_settings_description": "Menaxho shablonet e personalizuara pÃĢr njoftimet", - "theme_custom_css_settings": "CSS i personalizuar", - "theme_custom_css_settings_description": "Cascading Style Sheets lejojnÃĢ personalizimin e dizajnit tÃĢ Immich.", - "theme_settings": "CilÃĢsimet e temÃĢs", - "theme_settings_description": "Menaxho personalizimin e ndÃĢrfaqes web tÃĢ Immich", - "thumbnail_generation_job": "Gjenero miniatura", - "thumbnail_generation_job_description": "Gjenero miniatura tÃĢ mÃĢdha, tÃĢ vogla dhe tÃĢ turbullta pÃĢr çdo aset, si dhe miniatura pÃĢr çdo person", - "transcoding_acceleration_api": "API-ja e pÃĢrshpejtimit", - "transcoding_acceleration_api_description": "API-ja qÃĢ do tÃĢ ndÃĢrveprojÃĢ me pajisjen tÃĢnde pÃĢr me pÃĢrshpejtuar transkodimin. Ky cilÃĢsim asht 'best effort': do tÃĢ kthehet te transkodimi softuerik nÃĢse dÃĢshton. VP9 mund tÃĢ funksionojÃĢ ose jo varÃĢsisht nga hardueri yt.", - "transcoding_acceleration_nvenc": "NVENC (kÃĢrkon GPU NVIDIA)", - "transcoding_acceleration_qsv": "Quick Sync (kÃĢrkon CPU Intel brez 7 ose ma tÃĢ ri)", - "transcoding_acceleration_rkmpp": "RKMPP (vetÃĢm nÃĢ SOC-et Rockchip)", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "KodeckÃĢt e audios tÃĢ pranuara", - "transcoding_accepted_audio_codecs_description": "Zgjidh cilÃĢt kodeckÃĢ audio nuk kanÃĢ nevojÃĢ tÃĢ transkodihen. PÃĢrdoret vetÃĢm pÃĢr politika tÃĢ caktuara transkodimi.", - "transcoding_accepted_containers": "KontejnerÃĢt e pranuar", - "transcoding_accepted_containers_description": "Zgjidh cilÃĢt formate kontejneri nuk kanÃĢ nevojÃĢ tÃĢ ripakohen nÃĢ MP4. PÃĢrdoret vetÃĢm pÃĢr politika tÃĢ caktuara transkodimi.", - "transcoding_accepted_video_codecs": "KodeckÃĢt e videos tÃĢ pranuara", - "transcoding_accepted_video_codecs_description": "Zgjidh cilÃĢt kodeckÃĢ video nuk kanÃĢ nevojÃĢ tÃĢ transkodihen. PÃĢrdoret vetÃĢm pÃĢr politika tÃĢ caktuara transkodimi.", - "transcoding_advanced_options_description": "Opsione qÃĢ shumica e pÃĢrdoruesve nuk kanÃĢ nevojÃĢ t'i ndryshojnÃĢ", - "transcoding_audio_codec": "Kodeku i audios", - "transcoding_audio_codec_description": "Opus asht opsioni me cilÃĢsi ma tÃĢ lartÃĢ, por ka pÃĢrputhshmÃĢri ma tÃĢ ulÃĢt me pajisje ose softuer tÃĢ vjetÃĢr.", - "transcoding_bitrate_description": "Videot ma tÃĢ larta se bitrate maksimal ose jo nÃĢ format tÃĢ pranuar", - "transcoding_codecs_learn_more": "PÃĢr me mÃĢsu ma shumÃĢ rreth terminologjisÃĢ kÃĢtu, referoju dokumentacionit FFmpeg pÃĢr kodekun H.264, kodekun HEVC dhe kodekun VP9.", - "transcoding_constant_quality_mode": "MÃĢnyra e cilÃĢsisÃĢ konstante", - "transcoding_constant_quality_mode_description": "ICQ asht ma e mirÃĢ se CQP, por disa pajisje tÃĢ pÃĢrshpejtimit harduerik nuk e mbÃĢshtesin kÃĢtÃĢ mÃĢnyrÃĢ. Ky opsion do tÃĢ preferojÃĢ mÃĢnyrÃĢn e specifikuar kur pÃĢrdor kodim bazuar nÃĢ cilÃĢsi. Injorohet nga NVENC pasi nuk mbÃĢshtet ICQ.", - "transcoding_constant_rate_factor": "Faktori i normÃĢs konstante (-crf)", - "transcoding_constant_rate_factor_description": "Niveli i cilÃĢsisÃĢ sÃĢ videos. Vlerat tipike janÃĢ 23 pÃĢr H.264, 28 pÃĢr HEVC, 31 pÃĢr VP9 dhe 35 pÃĢr AV1. Vlera ma e ulÃĢt asht ma e mirÃĢ, por prodhon skedarÃĢ ma tÃĢ mÃĢdhenj.", - "transcoding_disabled_description": "Mos transkodo asnjÃĢ video, mund tÃĢ prish luajtjen nÃĢ disa klientÃĢ", - "transcoding_encoding_options": "Opsionet e kodimit", - "transcoding_encoding_options_description": "Vendos kodeckÃĢ, rezolucion, cilÃĢsi dhe opsione tÃĢ tjera pÃĢr videot e koduara", - "transcoding_hardware_acceleration": "PÃĢrshpejtimi harduerik", - "transcoding_hardware_acceleration_description": "Eksperimental: transkodim ma i shpejtÃĢ por mund tÃĢ ulÃĢ cilÃĢsinÃĢ me tÃĢ njÃĢjtin bitrate", - "transcoding_hardware_decoding": "Dekodimi harduerik", - "transcoding_hardware_decoding_setting_description": "Aktivizon pÃĢrshpejtimin nga fillimi nÃĢ fund nÃĢ vend se tÃĢ pÃĢrshpejtojÃĢ vetÃĢm kodimin. Mund tÃĢ mos funksionojÃĢ me tÃĢ gjitha videot.", - "transcoding_max_b_frames": "B-frames maksimale", - "transcoding_max_b_frames_description": "Vlerat ma tÃĢ larta pÃĢrmirÃĢsojnÃĢ efikasitetin e kompresimit, por ngadalÃĢsojnÃĢ kodimin. Mund tÃĢ mos jetÃĢ i pÃĢrputhshÃĢm me pÃĢrshpejtimin harduerik nÃĢ pajisje tÃĢ vjetra. 0 çaktivizonte B-frames, ndÃĢrsa -1 e vendos kÃĢtÃĢ vlerÃĢ automatikisht.", - "transcoding_max_bitrate": "Bitrate maksimal", - "transcoding_max_bitrate_description": "Vendosja e bitrate-it maksimal mund t'i bÃĢjÃĢ madhÃĢsitÃĢ e skedarÃĢve ma tÃĢ parashikueshme me kosto tÃĢ vogÃĢl nÃĢ cilÃĢsi. NÃĢ 720p, vlerat tipike janÃĢ 2600 kbit/s pÃĢr VP9 ose HEVC, ose 4500 kbit/s pÃĢr H.264. Çaktivizohet nÃĢse vendoset nÃĢ 0. Kur nuk specifikohet njÃĢsia, supozohet k (kbit/s).", - "transcoding_max_keyframe_interval": "Intervali maksimal i keyframe", - "transcoding_max_keyframe_interval_description": "Vendos distancÃĢn maksimale tÃĢ kornizave ndÃĢrmjet keyframe-ve. Vlerat ma tÃĢ ulÃĢta pÃĢrkeqÃĢsojnÃĢ efikasitetin e kompresimit, por pÃĢrmirÃĢsojnÃĢ kohÃĢt e kÃĢrkimit dhe mund tÃĢ pÃĢrmirÃĢsojnÃĢ cilÃĢsinÃĢ nÃĢ skenat me lÃĢvizje tÃĢ shpejtÃĢ. 0 e vendos kÃĢtÃĢ vlerÃĢ automatikisht.", - "transcoding_optimal_description": "Videot ma tÃĢ larta se rezolucioni i synuar ose jo nÃĢ format tÃĢ pranuar", - "transcoding_policy": "Politika e transkodimit", - "transcoding_policy_description": "Vendos kur do tÃĢ transkodihet njÃĢ video", - "transcoding_preferred_hardware_device": "Pajisja harduerike e preferuar", - "transcoding_preferred_hardware_device_description": "Aplikohet vetÃĢm pÃĢr VAAPI dhe QSV. Vendos nyjen dri tÃĢ pÃĢrdorur pÃĢr transkodim harduerik.", - "transcoding_preset_preset": "Paravendosja (-preset)", - "transcoding_preset_preset_description": "ShpejtÃĢsia e kompresimit. Paravendosjet ma tÃĢ ngadalta prodhojnÃĢ skedarÃĢ ma tÃĢ vegjÃĢl dhe rrisin cilÃĢsinÃĢ kur synon njÃĢ bitrate tÃĢ caktuar. VP9 injoron shpejtÃĢsi mbi 'faster'.", - "transcoding_realtime": "Transkodimi nÃĢ kohÃĢ reale [EKSPERIMENTAL]", - "transcoding_realtime_description": "Lejon transkodimin tÃĢ kryhet nÃĢ kohÃĢ reale ndÃĢrsa videoja transmetohet. Aktivizon ndÃĢrrimin e cilÃĢsisÃĢ, por mund tÃĢ shkaktojÃĢ vonesÃĢ ma tÃĢ lartÃĢ luajtjeje dhe ngecje varÃĢsisht nga kapacitetet e serverit.", - "transcoding_realtime_enabled": "Aktivizo transkodimin nÃĢ kohÃĢ reale", - "transcoding_realtime_enabled_description": "NÃĢse çaktivizohet, serveri do tÃĢ refuzojÃĢ me fillu sesione tÃĢ reja transkodimi nÃĢ kohÃĢ reale.", - "transcoding_realtime_resolutions": "Rezolucionet", - "transcoding_realtime_resolutions_description": "Rezolucionet e ofruara pÃĢr transkodimin nÃĢ kohÃĢ reale. NjÃĢ variant ofrohet vetÃĢm kur rezolucioni i tij nuk ÃĢshtÃĢ mÃĢ i madh se burimi. Rezolucionet mÃĢ tÃĢ larta mund tÃĢ shkaktojnÃĢ probleme nÃĢ riprodhim nÃĢse serveri nuk arrin t'i transkodojÃĢ mjaftueshÃĢm shpejt.", - "transcoding_realtime_video_codecs": "KodekÃĢ videoje", - "transcoding_realtime_video_codecs_description": "KodekÃĢt e videos tÃĢ ofruar pÃĢr transkodimin nÃĢ kohÃĢ reale. KlientÃĢt do tÃĢ zgjedhin opsionin mÃĢ tÃĢ mirÃĢ qÃĢ mbÃĢshtesin gjatÃĢ riprodhimit. AV1 ÃĢshtÃĢ mÃĢ efikas se HEVC, i cili ÃĢshtÃĢ mÃĢ efikas se H.264. Kur pÃĢrdorni pÃĢrshpejtimin harduerik, zgjidhni vetÃĢm kodekÃĢt qÃĢ pÃĢrshpejtuesi mund tÃĢ kodojÃĢ. Kur pÃĢrdorni transkodimin softuerik, vini re se H.264 ÃĢshtÃĢ mÃĢ i shpejtÃĢ se AV1, i cili ÃĢshtÃĢ mÃĢ i shpejtÃĢ se HEVC.", - "transcoding_reference_frames": "Kornizat referencÃĢ", - "transcoding_reference_frames_description": "Numri i kornizave pÃĢr t'u referuar kur kompresohet njÃĢ kornizÃĢ e dhÃĢnÃĢ. Vlerat ma tÃĢ larta pÃĢrmirÃĢsojnÃĢ efikasitetin e kompresimit, por ngadalÃĢsojnÃĢ kodimin. 0 e vendos kÃĢtÃĢ vlerÃĢ automatikisht.", - "transcoding_required_description": "VetÃĢm videot jo nÃĢ format tÃĢ pranuar", - "transcoding_settings": "CilÃĢsimet e transkodimit tÃĢ videos", - "transcoding_settings_description": "Menaxho cilat video tÃĢ transkodihen dhe si t'i procesosh ato", - "transcoding_target_resolution": "Rezolucioni i synuar", - "transcoding_target_resolution_description": "Rezolucionet ma tÃĢ larta mund tÃĢ ruajnÃĢ ma shumÃĢ detaje por marrin ma shumÃĢ kohÃĢ pÃĢr t'u koduar, kanÃĢ madhÃĢsi skedarÃĢsh ma tÃĢ mÃĢdha dhe mund tÃĢ reduktojnÃĢ reagimin e aplikacionit.", - "transcoding_temporal_aq": "Temporal AQ", - "transcoding_temporal_aq_description": "Aplikohet vetÃĢm pÃĢr NVENC. Kuantizimi adaptiv temporal rrit cilÃĢsinÃĢ e skenave me shumÃĢ detaje dhe lÃĢvizje tÃĢ ulÃĢt. Mund tÃĢ mos jetÃĢ i pÃĢrputhshÃĢm me pajisje tÃĢ vjetra.", - "transcoding_threads": "Threads", - "transcoding_threads_description": "Vlerat ma tÃĢ larta çojnÃĢ nÃĢ kodim ma tÃĢ shpejtÃĢ, por lÃĢnÃĢ ma pak hapÃĢsirÃĢ pÃĢr serverin me procesuar detyra tÃĢ tjera ndÃĢrsa asht aktiv. Kjo vlerÃĢ nuk duhet tÃĢ jetÃĢ ma e madhe se numri i bÃĢrthamave CPU. Maksimizon pÃĢrdorimin nÃĢse vendoset nÃĢ 0.", - "transcoding_tone_mapping": "Tone-mapping", - "transcoding_tone_mapping_description": "PÃĢrpiqet tÃĢ ruajÃĢ pamjen e videove HDR kur konvertohen nÃĢ SDR. Çdo algoritÃĢm bÃĢn kompromise tÃĢ ndryshme pÃĢr ngjyrÃĢn, detajet dhe ndriçimin. Hable ruan detajet, Mobius ruan ngjyrÃĢn dhe Reinhard ruan ndriçimin.", - "transcoding_transcode_policy": "Politika e transkodimit", - "transcoding_transcode_policy_description": "Politika pÃĢr kur duhet tÃĢ transkodihet njÃĢ video. Videot HDR dhe videot me format pikselÃĢsh tjetÃĢr se YUV 4:2:0 do tÃĢ transkodihen gjithmonÃĢ (pÃĢrveç nÃĢse transkodimi asht çaktivizuar).", - "transcoding_two_pass_encoding": "Kodim me dy kalime", - "transcoding_two_pass_encoding_setting_description": "Transkodo nÃĢ dy kalime pÃĢr me prodhuar video ma mirÃĢ tÃĢ koduara. Kur bitrate maksimal asht aktivizuar (i nevojshÃĢm pÃĢr tÃĢ punuar me H.264 dhe HEVC), kjo mÃĢnyrÃĢ pÃĢrdor njÃĢ gamÃĢ bitrate bazuar nÃĢ bitrate maksimal dhe injoron CRF. PÃĢr VP9, CRF mund tÃĢ pÃĢrdoret nÃĢse bitrate maksimal asht çaktivizuar.", - "transcoding_video_codec": "Kodeku i videos", - "transcoding_video_codec_description": "VP9 ka efikasitet tÃĢ lartÃĢ dhe pÃĢrputhshmÃĢri web, por merr ma shumÃĢ kohÃĢ pÃĢr t'u transkoduar. HEVC funksionon ngjashÃĢm, por ka pÃĢrputhshmÃĢri ma tÃĢ ulÃĢt web. H.264 asht i pÃĢrputhshÃĢm gjerÃĢsisht dhe i shpejtÃĢ pÃĢr t'u transkoduar, por prodhon skedarÃĢ shumÃĢ ma tÃĢ mÃĢdhenj. AV1 asht kodeku ma efikas por mungon mbÃĢshtetja nÃĢ pajisje tÃĢ vjetra.", - "trash_enabled_description": "Aktivizo veçoritÃĢ e koshit", - "trash_number_of_days": "Numri i ditÃĢve", - "trash_number_of_days_description": "Numri i ditÃĢve pÃĢr tÃĢ mbajtur asetet nÃĢ kosh para fshirjes pÃĢrgjithmonÃĢ", - "trash_settings": "CilÃĢsimet e koshit", - "trash_settings_description": "Menaxho cilÃĢsimet e koshit", - "unlink_all_oauth_accounts": "Shkyç tÃĢ gjitha llogaritÃĢ OAuth", - "unlink_all_oauth_accounts_description": "Mos harro me shkyçÃĢ tÃĢ gjitha llogaritÃĢ OAuth para migrimit te njÃĢ ofrues i ri.", - "unlink_all_oauth_accounts_prompt": "A je i sigurt qÃĢ dÃĢshiron tÃĢ shkyçÃĢsh tÃĢ gjitha llogaritÃĢ OAuth? Kjo do tÃĢ rivendosÃĢ OAuth ID-nÃĢ pÃĢr çdo pÃĢrdorues dhe nuk mund tÃĢ zhbÃĢhet.", - "user_cleanup_job": "Pastrimi i pÃĢrdoruesve", - "user_delete_delay": "Llogaria dhe asetet e {user} do tÃĢ planifikohen pÃĢr fshirje tÃĢ pÃĢrhershme pas {delay, plural, one {# ditÃĢ} other {# ditÃĢsh}}.", - "user_delete_delay_settings": "Vonesa e fshirjes", - "user_delete_delay_settings_description": "Numri i ditÃĢve pas heqjes pÃĢr me fshirÃĢ pÃĢrgjithmonÃĢ llogarinÃĢ dhe asetet e njÃĢ pÃĢrdoruesi. Puna e fshirjes sÃĢ pÃĢrdoruesit ekzekutohet nÃĢ mesnatÃĢ pÃĢr tÃĢ kontrolluar pÃĢrdoruesit gati pÃĢr fshirje. Ndryshimet e kÃĢtij cilÃĢsimi do tÃĢ vlerÃĢsohen nÃĢ ekzekutimin tjetÃĢr.", - "user_delete_immediately": "Llogaria dhe asetet e {user} do tÃĢ radhiten pÃĢr fshirje tÃĢ pÃĢrhershme menjÃĢherÃĢ.", - "user_delete_immediately_checkbox": "Radho pÃĢrdoruesin dhe asetet pÃĢr fshirje tÃĢ menjÃĢhershme", - "user_details": "Detajet e pÃĢrdoruesit", - "user_management": "Menaxhimi i pÃĢrdoruesve", - "user_password_has_been_reset": "FjalÃĢkalimi i pÃĢrdoruesit asht rivendosur:", - "user_password_reset_description": "Ju lutem jepja pÃĢrdoruesit fjalÃĢkalimin e pÃĢrkohshÃĢm dhe informoje se do tÃĢ duhet ta ndryshojÃĢ fjalÃĢkalimin nÃĢ hyrjen e tij tÃĢ ardhshme.", - "user_restore_description": "Llogaria e {user} do tÃĢ restaurohet.", - "user_restore_scheduled_removal": "Restauro pÃĢrdoruesin - heqja e planifikuar mÃĢ {date, date, long}", - "user_settings": "CilÃĢsimet e pÃĢrdoruesit", - "user_settings_description": "Menaxho cilÃĢsimet e pÃĢrdoruesit", - "user_successfully_removed": "PÃĢrdoruesi {email} u hoq me sukses.", - "users_page_description": "Faqja e administratorit pÃĢr pÃĢrdoruesit", - "version_check_channel": "Kanali i lÃĢshimit", - "version_check_channel_description": "Zgjidh kanalin e lÃĢshimit pÃĢr tÃĢ cilin dÃĢshiron tÃĢ marrÃĢsh njoftime versioni", - "version_check_enabled_description": "Aktivizo kontrollin e versionit", - "version_check_implications": "Veçoria e kontrollit tÃĢ versionit mbÃĢshtetet nÃĢ komunikim periodik me {server}", - "version_check_settings": "Kontrolli i versionit", - "version_check_settings_description": "Aktivizo/çaktivizo njoftimin e versionit tÃĢ ri", - "video_conversion_job": "Transkodo videot", - "video_conversion_job_description": "Transkodo videot pÃĢr pÃĢrputhshmÃĢri ma tÃĢ gjerÃĢ me shfletuesit dhe pajisjet" + "library_scanning_enable_description": "Aktivizo skanimin periodik tÃĢ bibliotekÃĢs" }, - "admin_email": "Email i administratorit", - "admin_password": "FjalÃĢkalimi i administratorit", - "administration": "Administrimi", - "advanced": "TÃĢ avancuara", - "advanced_settings_clear_image_cache": "Pastro cache-in e imazheve", - "advanced_settings_clear_image_cache_error": "DÃĢshtoi pastrimi i cache-it tÃĢ imazheve", - "advanced_settings_clear_image_cache_success": "U pastrua me sukses {size}", - "advanced_settings_log_level_title": "Niveli i regjistrit: {level}", - "advanced_settings_prefer_remote_subtitle": "Disa pajisje janÃĢ shumÃĢ tÃĢ ngadalta pÃĢr tÃĢ ngarkuar miniatura nga asetet lokale. Aktivizo kÃĢtÃĢ cilÃĢsim pÃĢr tÃĢ ngarkuar imazhe nga distanca.", - "advanced_settings_prefer_remote_title": "Prefero imazhe nga distanca", - "advanced_settings_proxy_headers_subtitle": "Defino header-at proxy qÃĢ Immich duhet t'i dÃĢrgojÃĢ me çdo kÃĢrkesÃĢ rrjeti", - "advanced_settings_proxy_headers_title": "Header-at e personalizuara proxy [EKSPERIMENTAL]", - "advanced_settings_readonly_mode_subtitle": "Aktivizon mÃĢnyrÃĢn vetÃĢm-lexim ku fotot mund tÃĢ shikohen vetÃĢm, gjÃĢra si zgjedhja e imazheve tÃĢ shumta, ndarja, transmetimi, fshirja janÃĢ tÃĢ Ã§aktivizuara. Aktivizo/çaktivizo vetÃĢm-lexim nÃĢpÃĢrmjet avatarit tÃĢ pÃĢrdoruesit nga ekrani kryesor", - "advanced_settings_readonly_mode_title": "MÃĢnyra vetÃĢm-lexim", - "advanced_settings_sync_remote_deletions_subtitle": "Fshi ose restauro automatikisht njÃĢ aset nÃĢ kÃĢtÃĢ pajisje kur kjo veprim kryhet nÃĢ web", - "advanced_settings_sync_remote_deletions_title": "Sinkronizo fshirjet nga distanca [EKSPERIMENTAL]", - "advanced_settings_tile_subtitle": "CilÃĢsimet e avancuara tÃĢ pÃĢrdoruesit", - "advanced_settings_troubleshooting_subtitle": "Aktivizo veçori shtesÃĢ pÃĢr zgjidhjen e problemeve", - "advanced_settings_troubleshooting_title": "Zgjidhja e problemeve", - "age_months": "Mosha {months, plural, one {# muaj} other {# muaj}}", - "age_year_months": "Mosha 1 vit, {months, plural, one {# muaj} other {# muaj}}", - "age_years": "{years, plural, other {Mosha #}}", - "album": "Album", - "album_added": "Albumi u shtua", - "album_added_notification_setting_description": "Merr njoftim me email kur shtohet nÃĢ njÃĢ album tÃĢ ndarÃĢ", - "album_cover_updated": "Kopertina e albumit u pÃĢrditÃĢsua", - "album_delete_confirmation": "A je i sigurt qÃĢ dÃĢshiron tÃĢ fshish albumin {album}?", - "album_delete_confirmation_description": "NÃĢse ky album asht i ndarÃĢ, pÃĢrdoruesit e tjerÃĢ nuk do tÃĢ mund ta aksesojnÃĢ ma.", - "album_deleted": "Albumi u fshi", - "album_info_updated": "Informacioni i albumit u pÃĢrditÃĢsua", - "album_name": "Emri i albumit", - "album_options": "Opsionet e albumit", - "album_remove_user": "Hiq pÃĢrdoruesin?", - "album_remove_user_confirmation": "A je i sigurt qÃĢ dÃĢshiron tÃĢ heqÃĢsh {user}?", - "album_search_not_found": "Nuk u gjet asnjÃĢ album qÃĢ pÃĢrputhet me kÃĢrkimin tÃĢnd", - "album_share_no_users": "Duket se e ke ndarÃĢ kÃĢtÃĢ album me tÃĢ gjithÃĢ pÃĢrdoruesit ose nuk ke asnjÃĢ pÃĢrdorues me tÃĢ cilin tÃĢ ndash.", - "album_summary": "PÃĢrmbledhja e albumit", - "album_updated": "Albumi u pÃĢrditÃĢsua", - "album_updated_setting_description": "Merr njoftim me email kur njÃĢ album i ndarÃĢ ka asete tÃĢ reja", - "album_upload_assets": "Ngarko asete nga kompjuteri yt dhe shto nÃĢ album", - "album_viewer_appbar_share_err_delete": "DÃĢshtoi fshirja e albumit", - "album_viewer_page_share_add_users": "Shto pÃĢrdorues", - "album_with_link_access": "Lejo kÃĢdo me lidhjen tÃĢ shohÃĢ fotot dhe njerÃĢzit nÃĢ kÃĢtÃĢ album.", - "albums": "Albumet", - "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Albume}}", - "albums_default_sort_order": "Rendi i parazgjedhur i albumeve", - "albums_default_sort_order_description": "Rendi fillestar i aseteve kur krijohen albume tÃĢ reja.", - "albums_feature_description": "Koleksione asetesh qÃĢ mund tÃĢ ndahen me pÃĢrdoruesit e tjerÃĢ.", - "albums_on_device_count": "Albumet nÃĢ pajisje ({count})", - "all": "TÃĢ gjithÃĢ", - "all_albums": "TÃĢ gjitha albumet", - "all_people": "TÃĢ gjithÃĢ njerÃĢzit", - "all_photos": "TÃĢ gjitha fotot", - "all_videos": "TÃĢ gjitha videot", - "allow_dark_mode": "Lejo mÃĢnyrÃĢn e errÃĢt", - "allow_public_user_to_download": "Lejo pÃĢrdoruesin publik tÃĢ shkarkojÃĢ", - "allow_public_user_to_upload": "Lejo pÃĢrdoruesin publik tÃĢ ngarkojÃĢ", - "allowed": "I lejuar", - "alt_text_qr_code": "Imazh i kodit QR", - "always_keep": "Mbaj gjithmonÃĢ", - "always_keep_photos_hint": "Lirimi i hapÃĢsirÃĢs do t'i mbajÃĢ tÃĢ gjitha fotot nÃĢ kÃĢtÃĢ pajisje.", - "always_keep_videos_hint": "Lirimi i hapÃĢsirÃĢs do t'i mbajÃĢ tÃĢ gjitha videot nÃĢ kÃĢtÃĢ pajisje.", - "api_key": "ÇelÃĢsi API", - "api_key_description": "Kjo vlerÃĢ do tÃĢ tregohet vetÃĢm njÃĢ herÃĢ. Sigurohu me e kopjuar para se tÃĢ mbyllÃĢsh dritaren.", - "api_key_empty": "Emri i çelÃĢsit API nuk duhet tÃĢ jetÃĢ bosh", - "api_keys": "ÇelÃĢsat API", - "app_architecture_variant": "Varianta (Arkitektura)", - "app_bar_signout_dialog_content": "A je i sigurt qÃĢ dÃĢshiron tÃĢ dalÃĢsh?", - "app_bar_signout_dialog_title": "Dil", - "app_download_links": "Lidhje shkarkimi tÃĢ aplikacionit", - "app_settings": "CilÃĢsimet e aplikacionit", - "app_stores": "Dyqanet e aplikacioneve", - "app_update_available": "Ka njÃĢ pÃĢrditÃĢsim tÃĢ aplikacionit", - "appears_in": "Shfaqet nÃĢ", - "apply_count": "Zbato ({count, number})", - "archive": "Arkiv", - "archive_action_prompt": "{count} u shtua nÃĢ Arkiv", - "archive_or_unarchive_photo": "Arkivo ose çarkivo foton", - "archive_size": "MadhÃĢsia e arkivit", - "archive_size_description": "Konfiguro madhÃĢsinÃĢ e arkivit pÃĢr shkarkimet (nÃĢ GiB)", - "archived": "Arkivuar", - "archived_count": "{count, plural, other {Arkivuar #}}", - "are_these_the_same_person": "A janÃĢ kÃĢta i njÃĢjti person?", - "are_you_sure_to_do_this": "Je i sigurt qÃĢ dÃĢshiron ta bÃĢsh kÃĢtÃĢ?", - "asset_added_to_album": "U shtua nÃĢ album", - "asset_adding_to_album": "Po shtohet nÃĢ albumâ€Ļ", - "asset_created": "Aseti u krijua", - "asset_day_count": "{date}: {count, plural, one {# aset} other {# asete}}", - "asset_description_updated": "PÃĢrshkrimi i asetit u pÃĢrditÃĢsua", - "asset_hashing": "Po llogaritet hash-iâ€Ļ", - "asset_list_group_by_sub_title": "Grupo sipas", - "asset_list_layout_settings_group_by": "Grupo asetet sipas", - "asset_list_layout_settings_group_by_month_day": "Muaj + ditÃĢ", - "asset_list_layout_sub_title": "Struktura", - "asset_list_settings_subtitle": "CilÃĢsimet e strukturÃĢs sÃĢ rrjetÃĢs sÃĢ fotove", - "asset_list_settings_title": "RrjetÃĢ fotosh", - "asset_not_found_on_device_android": "Aseti nuk u gjet nÃĢ pajisje", - "asset_not_found_on_device_ios": "Aseti nuk u gjet nÃĢ pajisje. NÃĢse po pÃĢrdor iCloud, aseti mund tÃĢ jetÃĢ i paarritshÃĢm pÃĢr shkak tÃĢ njÃĢ skedari tÃĢ dÃĢmtuar tÃĢ ruajtur nÃĢ iCloud", - "asset_not_found_on_icloud": "Aseti nuk u gjet nÃĢ iCloud. Aseti mund tÃĢ jetÃĢ i paarritshÃĢm pÃĢr shkak tÃĢ njÃĢ skedari tÃĢ dÃĢmtuar tÃĢ ruajtur nÃĢ iCloud", - "asset_offline": "Aseti ÃĢshtÃĢ jashtÃĢ linje", - "asset_offline_description": "Ky aset i jashtÃĢm nuk gjendet mÃĢ nÃĢ disk. TÃĢ lutem kontakto administratorin tÃĢnd tÃĢ Immich pÃĢr ndihmÃĢ.", - "asset_skipped": "U anashkalua", - "asset_skipped_in_trash": "NÃĢ kosh", - "asset_troubleshoot": "Zgjidhja e problemeve tÃĢ asetit", - "asset_uploaded": "Ngarkuar", - "asset_uploading": "Po ngarkohetâ€Ļ", - "asset_viewer_settings_subtitle": "Menaxho cilÃĢsimet e shikuesit tÃĢ galerisÃĢ tÃĢnde", - "asset_viewer_settings_title": "Shikuesi i asetit", - "assets": "Asete", - "assets_added_to_album_count": "U shtuan {count, plural, one {# aset} other {# asete}} nÃĢ album", - "assets_added_to_album_partial_count": "U shtuan {successCount} nga {totalCount} {totalCount, plural, one {aset} other {asete}} nÃĢ album", - "assets_added_to_albums_count": "U shtuan {assetTotal, plural, one {# aset} other {# asete}} nÃĢ {albumTotal, plural, one {# album} other {# albume}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Aseti} other {Asetet}} nuk mund tÃĢ shtohen nÃĢ album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Aseti} other {Asetet}} nuk mund tÃĢ shtohen nÃĢ asnjÃĢ nga albumet", - "assets_count": "{count, plural, one {# aset} other {# asete}}", - "assets_moved_to_trash_count": "U zhvendosÃĢn {count, plural, one {# aset} other {# asete}} nÃĢ kosh", - "assets_permanently_deleted_count": "U fshinÃĢ pÃĢrgjithmonÃĢ {count, plural, one {# aset} other {# asete}}", - "assets_removed_count": "U hoqÃĢn {count, plural, one {# aset} other {# asete}}", - "assets_restore_confirmation": "Je i sigurt qÃĢ dÃĢshiron t'i rikthesh tÃĢ gjitha asetet e koshit? Nuk mund ta zhbÃĢsh kÃĢtÃĢ veprim! Vini re se asetet jashtÃĢ linje nuk mund tÃĢ rikthehen nÃĢ kÃĢtÃĢ mÃĢnyrÃĢ.", - "assets_restored_count": "U rikthyen {count, plural, one {# aset} other {# asete}}", - "assets_trashed": "{count} aset(e) u hodhÃĢn nÃĢ kosh", - "assets_trashed_count": "U hodhÃĢn nÃĢ kosh {count, plural, one {# aset} other {# asete}}", - "assets_were_part_of_album_count": "{count, plural, one {Aseti ishte tashmÃĢ} other {Asetet ishin tashmÃĢ}} pjesÃĢ e albumit", - "assets_were_part_of_albums_count": "{count, plural, one {Aseti ishte tashmÃĢ} other {Asetet ishin tashmÃĢ}} pjesÃĢ e albumeve", - "authorized_devices": "Pajisje tÃĢ autorizuara", - "automatic_endpoint_switching_subtitle": "Lidhu nÃĢ rrjet lokal pÃĢrmes Wi-Fi-t tÃĢ caktuar kur ÃĢshtÃĢ i disponueshÃĢm dhe pÃĢrdor lidhje alternative gjetkÃĢ", - "automatic_endpoint_switching_title": "NdÃĢrrim automatik i URL-sÃĢ", - "autoplay_slideshow": "Riprodho automatikisht shfaqjen e diapozitivave", - "back": "Mbrapa", - "back_close_deselect": "Kthehu, mbyll ose çzgjidh", - "background_location_permission": "Leje vendndodhjeje nÃĢ sfond", - "background_location_permission_content": "PÃĢr tÃĢ ndÃĢrruar rrjete kur ekzekutohet nÃĢ sfond, Immich duhet *gjithmonÃĢ* tÃĢ ketÃĢ qasje tÃĢ saktÃĢ nÃĢ vendndodhje qÃĢ aplikacioni tÃĢ mund tÃĢ lexojÃĢ emrin e rrjetit Wi-Fi", - "background_options": "Opsionet e sfondit", - "backup": "Kopje rezervÃĢ", - "backup_album_selection_page_albums_tap": "Trokit pÃĢr tÃĢ pÃĢrfshirÃĢ, trokit dy herÃĢ pÃĢr tÃĢ pÃĢrjashtuar", - "backup_album_selection_page_assets_scatter": "Asetet mund tÃĢ shpÃĢrndahen nÃĢpÃĢr shumÃĢ albume. KÃĢshtu, albumet mund tÃĢ pÃĢrfshihen ose pÃĢrjashtohen gjatÃĢ procesit tÃĢ kopjimit rezervÃĢ.", - "backup_album_selection_page_select_albums": "Zgjidh albume", - "backup_album_selection_page_selection_info": "Informacioni i pÃĢrzgjedhjes", - "backup_albums_sync": "Sinkronizimi i albumeve tÃĢ kopjimit rezervÃĢ", - "backup_background_service_complete_notification": "Kopja rezervÃĢ e asetit u pÃĢrfundua", - "backup_background_service_default_notification": "Po kontrollohet pÃĢr asete tÃĢ rejaâ€Ļ", - "backup_background_service_in_progress_notification": "Po bÃĢhet kopja rezervÃĢ e aseteve tÃĢ tuaâ€Ļ", - "backup_controller_page_albums": "Albume tÃĢ kopjimit rezervÃĢ", - "backup_controller_page_background_battery_info_link": "MÃĢ trego si", - "backup_controller_page_background_battery_info_message": "PÃĢr pÃĢrvojÃĢn mÃĢ tÃĢ mirÃĢ tÃĢ kopjimit rezervÃĢ nÃĢ sfond, tÃĢ lutem çaktivizo çdo optimizim baterie qÃĢ kufizon aktivitetin nÃĢ sfond pÃĢr Immich.\n\nMeqenÃĢse kjo varet nga pajisja, tÃĢ lutem kÃĢrko informacionin e nevojshÃĢm pÃĢr prodhuesin e pajisjes tÃĢnde.", - "backup_controller_page_background_battery_info_ok": "NÃĢ rregull", - "backup_controller_page_background_battery_info_title": "Optimizime baterie", - "backup_controller_page_background_delay": "Vono kopjimin rezervÃĢ tÃĢ aseteve tÃĢ reja: {duration}", - "backup_controller_page_backup": "Kopje rezervÃĢ", - "backup_controller_page_backup_selected": "PÃĢrzgjedhur: ", - "backup_controller_page_backup_sub": "Fotot dhe videot me kopje rezervÃĢ", - "backup_controller_page_excluded": "PÃĢrjashtuar: ", - "backup_controller_page_none_selected": "AsnjÃĢ i pÃĢrzgjedhur", - "backup_controller_page_remainder": "Pjesa e mbetur", - "backup_controller_page_remainder_sub": "Fotot dhe videot e mbetura pÃĢr t'u kopjuar nga pÃĢrzgjedhja", - "backup_controller_page_server_storage": "Ruajtja e serverit", - "backup_controller_page_storage_format": "{used} nga {total} tÃĢ pÃĢrdorura", - "backup_controller_page_to_backup": "Albumet qÃĢ do tÃĢ kopjohen rezervÃĢ", - "backup_controller_page_total_sub": "TÃĢ gjitha fotot dhe videot unike nga albumet e pÃĢrzgjedhura", - "backup_error_sync_failed": "Sinkronizimi dÃĢshtoi. Nuk mund tÃĢ pÃĢrpunohet kopja rezervÃĢ.", - "backup_info_card_assets": "asete", - "backup_options": "Opsionet e kopjimit rezervÃĢ", - "backup_settings_subtitle": "Menaxho cilÃĢsimet e ngarkimit", - "backward": "Mbrapa", - "battery_optimization_backup_reliability": "Çaktivizimi i optimizimeve tÃĢ baterisÃĢ mund tÃĢ pÃĢrmirÃĢsojÃĢ besueshmÃĢrinÃĢ e kopjimit rezervÃĢ nÃĢ sfond", - "biometric_auth_enabled": "VÃĢrtetimi biometrik u aktivizua", - "biometric_locked_out": "Je bllokuar nga vÃĢrtetimi biometrik", - "biometric_no_options": "Nuk ka opsione biometrike tÃĢ disponueshme", - "biometric_not_available": "VÃĢrtetimi biometrik nuk ÃĢshtÃĢ i disponueshÃĢm nÃĢ kÃĢtÃĢ pajisje", - "birthdate_set_description": "Data e lindjes pÃĢrdoret pÃĢr tÃĢ llogaritur moshÃĢn e kÃĢtij personi nÃĢ kohÃĢn e njÃĢ fotoje.", - "blurred_background": "Sfond i turbullt", - "browse_templates": "Shfleto shabllonet", - "bugs_and_feature_requests": "Defekte & kÃĢrkesa pÃĢr veçori", - "build": "NdÃĢrto", - "build_image": "NdÃĢrto imazhin", - "bulk_delete_duplicates_confirmation": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish nÃĢ grup {count, plural, one {# aset duplikat} other {# asete duplikate}}? Kjo do tÃĢ mbajÃĢ asetin mÃĢ tÃĢ madh tÃĢ Ã§do grupi dhe do tÃĢ fshijÃĢ pÃĢrgjithmonÃĢ tÃĢ gjithÃĢ duplikatÃĢt e tjerÃĢ. Nuk mund ta zhbÃĢsh kÃĢtÃĢ veprim!", - "bulk_keep_duplicates_confirmation": "Je i sigurt qÃĢ dÃĢshiron tÃĢ mbash {count, plural, one {# aset duplikat} other {# asete duplikate}}? Kjo do tÃĢ zgjidhÃĢ tÃĢ gjitha grupet e duplikatÃĢve pa fshirÃĢ asgjÃĢ.", - "bulk_trash_duplicates_confirmation": "Je i sigurt qÃĢ dÃĢshiron tÃĢ hedhÃĢsh nÃĢ kosh nÃĢ grup {count, plural, one {# aset duplikat} other {# asete duplikate}}? Kjo do tÃĢ mbajÃĢ asetin mÃĢ tÃĢ madh tÃĢ Ã§do grupi dhe do tÃĢ hedhÃĢ nÃĢ kosh tÃĢ gjithÃĢ duplikatÃĢt e tjerÃĢ.", - "buy": "Bli Immich", - "camera": "Kamera", - "camera_brand": "Marka e kamerÃĢs", - "camera_model": "Modeli i kamerÃĢs", - "cancel": "Anulo", - "cancel_search": "Anulo kÃĢrkimin", - "canceled": "U anulua", - "canceling": "Po anulohet", - "cannot_merge_people": "Nuk mund tÃĢ bashkohen personat", - "cannot_undo_this_action": "Nuk mund ta zhbÃĢsh kÃĢtÃĢ veprim!", - "cannot_update_the_description": "Nuk mund tÃĢ pÃĢrditÃĢsohet pÃĢrshkrimi", - "cast": "Transmeto", - "cast_description": "Konfiguro destinacionet e disponueshme tÃĢ transmetimit", - "change": "Ndrysho", - "change_date": "Ndrysho datÃĢn", - "change_description": "Ndrysho pÃĢrshkrimin", - "change_display_order": "Ndrysho renditjen e shfaqjes", - "change_location": "Ndrysho vendndodhjen", - "change_name": "Ndrysho emrin", - "change_name_successfully": "Emri u ndryshua me sukses", - "change_password": "Ndrysho fjalÃĢkalimin", - "change_password_description": "Kjo ÃĢshtÃĢ ose hera e parÃĢ qÃĢ hyn nÃĢ sistem ose ÃĢshtÃĢ bÃĢrÃĢ njÃĢ kÃĢrkesÃĢ pÃĢr tÃĢ ndryshuar fjalÃĢkalimin tÃĢnd. TÃĢ lutem vendos fjalÃĢkalimin e ri mÃĢ poshtÃĢ.", - "change_password_form_confirm_password": "Konfirmo fjalÃĢkalimin", - "change_password_form_description": "PÃĢrshÃĢndetje {name},\n\nKjo ÃĢshtÃĢ ose hera e parÃĢ qÃĢ hyn nÃĢ sistem ose ÃĢshtÃĢ bÃĢrÃĢ njÃĢ kÃĢrkesÃĢ pÃĢr tÃĢ ndryshuar fjalÃĢkalimin tÃĢnd. TÃĢ lutem vendos fjalÃĢkalimin e ri mÃĢ poshtÃĢ.", - "change_password_form_log_out": "Dil nga tÃĢ gjitha pajisjet e tjera", - "change_password_form_log_out_description": "Rekomandohet tÃĢ dalÃĢsh nga tÃĢ gjitha pajisjet e tjera", - "change_password_form_new_password": "FjalÃĢkalim i ri", - "change_password_form_password_mismatch": "FjalÃĢkalimet nuk pÃĢrputhen", - "change_password_form_reenter_new_password": "Rivendos fjalÃĢkalimin e ri", - "change_pin_code": "Ndrysho kodin PIN", - "change_your_password": "Ndrysho fjalÃĢkalimin tÃĢnd", - "changed_visibility_successfully": "DukshmÃĢria u ndryshua me sukses", - "charging": "Po karikohet", - "charging_requirement_mobile_backup": "Kopjimi rezervÃĢ nÃĢ sfond kÃĢrkon qÃĢ pajisja tÃĢ jetÃĢ duke u karikuar", - "check_logs": "Kontrollo regjistrat", - "checksum": "Checksum", - "choose": "Zgjidh", - "choose_matching_people_to_merge": "Zgjidh personat pÃĢrputhÃĢs pÃĢr t'i bashkuar", - "city": "Qyteti", - "cleanup_confirm_description": "Immich gjeti {count} asete (tÃĢ krijuara para {date}) tÃĢ ruajtura nÃĢ mÃĢnyrÃĢ tÃĢ sigurt me kopje rezervÃĢ nÃĢ server. TÃĢ hiqen kopjet lokale nga kjo pajisje?", - "cleanup_confirm_prompt_title": "TÃĢ hiqet nga kjo pajisje?", - "cleanup_deleted_assets": "U zhvendosÃĢn {count} asete nÃĢ koshin e pajisjes", - "cleanup_deleting": "Po zhvendoset nÃĢ kosh...", - "cleanup_found_assets": "U gjetÃĢn {count} asete me kopje rezervÃĢ", - "cleanup_found_assets_with_size": "U gjetÃĢn {count} asete me kopje rezervÃĢ ({size})", - "cleanup_icloud_shared_albums_excluded": "Albumet e ndara tÃĢ iCloud pÃĢrjashtohen nga skanimi", - "cleanup_no_assets_found": "Nuk u gjetÃĢn asete qÃĢ pÃĢrputhen me kriteret e mÃĢsipÃĢrme. Lirimi i hapÃĢsirÃĢs mund tÃĢ heqÃĢ vetÃĢm asetet qÃĢ janÃĢ ruajtur me kopje rezervÃĢ nÃĢ server", - "cleanup_preview_title": "Asete pÃĢr t'u hequr ({count})", - "cleanup_step3_description": "Skano pÃĢr asete me kopje rezervÃĢ qÃĢ pÃĢrputhen me cilÃĢsimet e tua tÃĢ datÃĢs dhe mbajtjes.", - "cleanup_step4_summary": "{count} asete (tÃĢ krijuara para {date}) pÃĢr t'u hequr nga pajisja jote lokale. Fotot do tÃĢ mbeten tÃĢ arritshme nga aplikacioni Immich.", - "cleanup_trash_hint": "PÃĢr tÃĢ rikuperuar plotÃĢsisht hapÃĢsirÃĢn e ruajtjes, hap aplikacionin e galerisÃĢ sÃĢ sistemit dhe zbraz koshin", - "clear": "Pastro", - "clear_all": "Pastro tÃĢ gjitha", - "clear_all_recent_searches": "Pastro tÃĢ gjitha kÃĢrkimet e fundit", - "clear_failed_count": "Pastro tÃĢ dÃĢshtuarat ({count})", - "clear_file_cache": "Pastro memorien e skedarÃĢve", - "clear_message": "Pastro mesazhin", - "clear_value": "Pastro vlerÃĢn", - "client_cert_import": "Importo", - "client_cert_import_success_msg": "Certifikata e klientit u importua", - "client_cert_invalid_msg": "Skedar certifikate i pavlefshÃĢm ose fjalÃĢkalim i gabuar", - "client_cert_password_message": "Vendos fjalÃĢkalimin pÃĢr kÃĢtÃĢ certifikatÃĢ", - "client_cert_password_title": "FjalÃĢkalimi i certifikatÃĢs", - "client_cert_remove_msg": "Certifikata e klientit u hoq", - "client_cert_subtitle": "MbÃĢshtet vetÃĢm formatin PKCS12 (.p12, .pfx). Importimi/heqja e certifikatÃĢs ÃĢshtÃĢ e disponueshme vetÃĢm para hyrjes", - "client_cert_title": "CertifikatÃĢ klienti SSL [EKSPERIMENTALE]", - "close": "Mbyll", - "collapse": "Palos", - "collapse_all": "Palos tÃĢ gjitha", - "color": "Ngjyra", - "command": "KomandÃĢ", - "command_palette_prompt": "Gjej shpejt faqe, veprime ose komanda", - "command_palette_to_close": "pÃĢr tÃĢ mbyllur", - "command_palette_to_navigate": "pÃĢr tÃĢ hyrÃĢ", - "command_palette_to_select": "pÃĢr tÃĢ zgjedhur", - "command_palette_to_show_all": "pÃĢr tÃĢ shfaqur tÃĢ gjitha", - "comment_deleted": "Komenti u fshi", - "comment_options": "Opsionet e komentit", - "comments_and_likes": "Komente & pÃĢlqime", - "comments_are_disabled": "Komentet janÃĢ tÃĢ Ã§aktivizuara", - "common_create_new_album": "Krijo album tÃĢ ri", - "completed": "U pÃĢrfundua", - "configuration": "Konfigurimi", - "confirm": "Konfirmo", - "confirm_admin_password": "Konfirmo fjalÃĢkalimin e administratorit", - "confirm_delete_face": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish fytyrÃĢn {name} nga aseti?", - "confirm_delete_shared_link": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish kÃĢtÃĢ lidhje tÃĢ ndarÃĢ?", - "confirm_keep_this_delete_others": "TÃĢ gjitha asetet e tjera nÃĢ pirg do tÃĢ fshihen pÃĢrveç kÃĢtij aseti. Je i sigurt qÃĢ dÃĢshiron tÃĢ vazhdosh?", - "confirm_new_pin_code": "Konfirmo kodin e ri PIN", - "confirm_password": "Konfirmo fjalÃĢkalimin", - "confirm_tag_face": "DÃĢshiron ta etiketosh kÃĢtÃĢ fytyrÃĢ si {name}?", - "confirm_tag_face_unnamed": "DÃĢshiron ta etiketosh kÃĢtÃĢ fytyrÃĢ?", - "connected_device": "Pajisje e lidhur", - "connected_to": "Lidhur me", - "contain": "PÃĢrmbaj", - "context": "Konteksti", - "continue": "Vazhdo", - "control_bottom_app_bar_add_tags": "Shto etiketa", - "control_bottom_app_bar_delete_from_local": "Fshi nga pajisja", - "control_bottom_app_bar_edit_location": "Modifiko vendndodhjen", - "control_bottom_app_bar_edit_time": "Modifiko datÃĢn & orÃĢn", - "control_bottom_app_bar_trash_from_immich": "Zhvendos nÃĢ kosh", - "copied_image_to_clipboard": "Imazhi u kopjua nÃĢ kujtesÃĢn e fragmenteve.", - "copied_to_clipboard": "U kopjua nÃĢ kujtesÃĢn e fragmenteve!", - "copy_error": "Gabim kopjimi", - "copy_image": "Kopjo imazhin", - "copy_json": "Kopjo JSON-in", - "copy_link": "Kopjo lidhjen", - "copy_link_to_clipboard": "Kopjo lidhjen nÃĢ kujtesÃĢn e fragmenteve", - "copy_password": "Kopjo fjalÃĢkalimin", - "copy_to_clipboard": "Kopjo nÃĢ kujtesÃĢn e fragmenteve", - "country": "Shteti", - "cover": "Kopertina", - "covers": "Kopertinat", - "create": "Krijo", - "create_album": "Krijo album", - "create_album_page_untitled": "Pa titull", - "create_api_key": "Krijo çelÃĢs API", - "create_first_workflow": "Krijo rrjedhÃĢn e parÃĢ tÃĢ punÃĢs", - "create_library": "Krijo bibliotekÃĢ", - "create_link": "Krijo lidhje", - "create_link_to_share": "Krijo lidhje pÃĢr tÃĢ ndarÃĢ", - "create_link_to_share_description": "Lejo kÃĢdo qÃĢ ka lidhjen tÃĢ shohÃĢ foton(t) e pÃĢrzgjedhura", - "create_new": "KRIJO TË RE", - "create_new_person": "Krijo person tÃĢ ri", - "create_new_person_hint": "Cakto asetet e pÃĢrzgjedhura te njÃĢ person i ri", - "create_new_user": "Krijo pÃĢrdorues tÃĢ ri", - "create_person": "Krijo person", - "create_person_subtitle": "Shto njÃĢ emÃĢr te fytyra e pÃĢrzgjedhur pÃĢr tÃĢ krijuar dhe etiketuar personin e ri", - "create_shared_album_page_share_add_assets": "SHTO ASETE", - "create_shared_album_page_share_select_photos": "Zgjidh foto", - "create_shared_link": "Krijo lidhje tÃĢ ndarÃĢ", - "create_tag": "Krijo etiketÃĢ", - "create_tag_description": "Krijo njÃĢ etiketÃĢ tÃĢ re. PÃĢr etiketa tÃĢ ndÃĢrthurura, tÃĢ lutem vendos shtegun e plotÃĢ tÃĢ etiketÃĢs duke pÃĢrfshirÃĢ vijat e pjerrÃĢta.", - "create_user": "Krijo pÃĢrdorues", - "create_workflow": "Krijo rrjedhÃĢ pune", - "created": "Krijuar", - "created_at": "Krijuar", - "creating_linked_albums": "Po krijohen albume tÃĢ lidhura...", - "crop": "Prit", - "crop_aspect_ratio_free": "I lirÃĢ", - "crop_aspect_ratio_original": "Origjinal", - "crop_aspect_ratio_square": "Katror", - "current_device": "Pajisja aktuale", - "current_pin_code": "Kodi aktual PIN", - "current_server_address": "Adresa aktuale e serverit", - "custom_date": "DatÃĢ e personalizuar", - "custom_locale": "Lokale e personalizuar", - "custom_locale_description": "Formato datat, orÃĢt dhe numrat bazuar nÃĢ gjuhÃĢn dhe rajonin e pÃĢrzgjedhur", - "cutoff_date_description": "Mbaj fotot ngaâ€Ļ", - "cutoff_day": "{count, plural, one {ditÃĢ} other {ditÃĢ}}", - "cutoff_year": "{count, plural, one {vit} other {vjet}}", - "dark": "E errÃĢt", - "dark_theme": "Kalo nÃĢ temÃĢn e errÃĢt", - "date": "Data", - "date_after": "Data pas", - "date_and_time": "Data dhe ora", - "date_before": "Data para", - "date_of_birth": "Data e lindjes", - "date_of_birth_saved": "Data e lindjes u ruajt me sukses", - "date_range": "Intervali i datave", - "date_time_original": "Data/Ora origjinale", - "day": "DitÃĢ", - "days": "DitÃĢ", - "deduplicate_all": "Deduplikoji tÃĢ gjitha", - "default_quality_subtitle": "CilÃĢsia e pÃĢrdorur kur trokitet te ndarja. Shtyp gjatÃĢ butonin e ndarjes pÃĢr tÃĢ zgjedhur çdo herÃĢ.", - "default_share_quality": "CilÃĢsia e paracaktuar e ndarjes", - "delete": "Fshi", - "delete_action_confirmation_message": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish kÃĢtÃĢ aset? Ky veprim do ta zhvendosÃĢ asetin nÃĢ koshin e serverit dhe do tÃĢ tÃĢ pyesÃĢ nÃĢse dÃĢshiron ta fshish edhe nÃĢ mÃĢnyrÃĢ lokale", - "delete_action_prompt": "{count} u fshinÃĢ", - "delete_album": "Fshi albumin", - "delete_api_key_prompt": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish kÃĢtÃĢ Ã§elÃĢs API?", - "delete_dialog_alert": "KÃĢto artikuj do tÃĢ fshihen pÃĢrgjithmonÃĢ nga Immich dhe nga pajisja jote", - "delete_dialog_alert_local": "KÃĢto artikuj do tÃĢ hiqen pÃĢrgjithmonÃĢ nga pajisja jote, por do tÃĢ mbeten tÃĢ disponueshÃĢm nÃĢ serverin Immich", - "delete_dialog_alert_local_non_backed_up": "Disa nga artikujt nuk janÃĢ ruajtur me kopje rezervÃĢ nÃĢ Immich dhe do tÃĢ hiqen pÃĢrgjithmonÃĢ nga pajisja jote", - "delete_dialog_title": "Fshi pÃĢrgjithmonÃĢ", - "delete_duplicates_confirmation": "Je i sigurt qÃĢ dÃĢshiron t'i fshish pÃĢrgjithmonÃĢ kÃĢta duplikatÃĢ?", - "delete_face": "Fshi fytyrÃĢn", - "delete_key": "Fshi çelÃĢsin", - "delete_library": "Fshi bibliotekÃĢn", - "delete_link": "Fshi lidhjen", - "delete_local_action_prompt": "{count} u fshinÃĢ nÃĢ mÃĢnyrÃĢ lokale", - "delete_local_dialog_ok_backed_up_only": "Fshi vetÃĢm ato me kopje rezervÃĢ", - "delete_local_dialog_ok_force": "Fshi gjithsesi", - "delete_others": "Fshi tÃĢ tjerat", - "delete_permanently": "Fshi pÃĢrgjithmonÃĢ", - "delete_permanently_action_prompt": "{count} u fshinÃĢ pÃĢrgjithmonÃĢ", - "delete_shared_link": "Fshi lidhjen e ndarÃĢ", - "delete_shared_link_dialog_title": "Fshi lidhjen e ndarÃĢ", - "delete_tag": "Fshi etiketÃĢn", - "delete_tag_confirmation_prompt": "Je i sigurt qÃĢ dÃĢshiron tÃĢ fshish etiketÃĢn {tagName}?", - "delete_user": "Fshi pÃĢrdoruesin", - "deleted_shared_link": "Lidhja e ndarÃĢ u fshi", - "description": "PÃĢrshkrimi", - "deselect_all": "Çzgjidh tÃĢ gjitha", - "details": "Detajet", - "direction": "Drejtimi", - "disable": "Çaktivizo", - "disabled": "I çaktivizuar", - "discord": "Discord", - "discover": "Zbulo", - "discovered_devices": "Pajisjet e zbuluara", - "dismiss_all_errors": "Hiqi tÃĢ gjitha gabimet", - "display_options": "Opsionet e shfaqjes", - "display_order": "Renditja e shfaqjes", - "display_original_photos": "Shfaq fotot origjinale", - "display_original_photos_setting_description": "Preferohet tÃĢ shfaqet fotoja origjinale gjatÃĢ shikimit tÃĢ njÃĢ aseti nÃĢ vend tÃĢ miniaturave kur aseti origjinal ÃĢshtÃĢ i pajtueshÃĢm me ueb-in. Kjo mund tÃĢ rezultojÃĢ nÃĢ shpejtÃĢsi mÃĢ tÃĢ ngadalta tÃĢ shfaqjes sÃĢ fotove.", - "do_not_show_again": "Mos e shfaq mÃĢ kÃĢtÃĢ mesazh", - "documentation": "Dokumentimi", - "done": "U krye", - "download": "Shkarko", - "download_canceled": "Shkarkimi u anulua", - "download_complete": "Shkarkimi u pÃĢrfundua", - "download_enqueue": "Shkarkimi u vendos nÃĢ radhÃĢ", - "download_failed": "Shkarkimi dÃĢshtoi", - "download_finished": "Shkarkimi pÃĢrfundoi", - "download_include_embedded_motion_videos": "Video tÃĢ inkorporuara", - "download_include_embedded_motion_videos_description": "PÃĢrfshi videot e inkorporuara nÃĢ fotot me lÃĢvizje si njÃĢ skedar tÃĢ veçantÃĢ", - "download_notfound": "Shkarkimi nuk u gjet", "download_original": "Shkarko origjinalin", "download_paused": "Shkarkimi u pezullua", "download_settings": "Shkarko", - "download_settings_description": "Menaxho cilÃĢsimet qÃĢ lidhen me shkarkimin e aseteve", + "download_started": "Shkarkimi filloi", + "download_sucess": "Shkarkimi u krye me sukses", + "download_sucess_android": "Media u shkarkua tek DCIM/Immich", "download_waiting_to_retry": "Duke pritur pÃĢr ta provuar pÃĢrsÃĢri", "downloading": "Duke u shkarkuar", "downloading_asset_filename": "Duke shkarkuar asetin {filename}", "downloading_from_icloud": "Duke shkarkuar nga iCloud", "downloading_media": "Duke shkarkuar median", - "drag_to_reorder": "TÃĢrhiq pÃĢr tÃĢ rirenditur", - "drop_files_to_upload": "LÃĢsho skedarÃĢt kudo pÃĢr t'i ngarkuar", - "duplicate": "Duplikat", - "duplicate_workflow": "Dupliko rrjedhÃĢn e punÃĢs", - "duplicates": "DuplikatÃĢ", - "duplicates_description": "Zgjidh çdo grup duke treguar cilÃĢt, nÃĢse ka, janÃĢ duplikatÃĢ.", - "duration": "KohÃĢzgjatja", - "edit": "Modifiko", - "edit_album": "Modifiko albumin", - "edit_avatar": "Modifiko avatarin", - "edit_birthday": "Modifiko ditÃĢlindjen", - "edit_date": "Modifiko datÃĢn", - "edit_date_and_time": "Modifiko datÃĢn dhe orÃĢn", - "edit_date_and_time_action_prompt": "{count} data dhe ora u modifikuan", - "edit_date_and_time_by_offset": "Ndrysho datÃĢn sipas kompensimit", - "edit_description": "Modifiko pÃĢrshkrimin", - "edit_exclusion_pattern": "Modifiko modelin e pÃĢrjashtimit", - "edit_faces": "Modifiko fytyrat", - "edit_key": "Modifiko çelÃĢsin", - "edit_link": "Modifiko lidhjen", - "edit_location": "Modifiko vendndodhjen", - "edit_location_action_prompt": "{count} vendndodhje u modifikua", - "edit_location_dialog_title": "Vendndodhja", - "edit_name": "Modifiko emrin", - "edit_people": "Modifiko personat", - "edit_tag": "Modifiko etiketÃĢn", - "edit_title": "Modifiko titullin", - "edit_user": "Modifiko pÃĢrdoruesin", - "edit_workflow": "Modifiko rrjedhÃĢn e punÃĢs", - "editor": "Modifikuesi", - "editor_discard_edits_confirm": "Hidhi modifikimet", - "editor_discard_edits_prompt": "Ke modifikime tÃĢ paruajtura. Je i sigurt qÃĢ dÃĢshiron t'i hedhÃĢsh?", - "editor_discard_edits_title": "TÃĢ hidhen modifikimet?", - "editor_edits_applied_error": "Zbatimi i modifikimeve dÃĢshtoi", - "editor_edits_applied_success": "Modifikimet u zbatuan me sukses", - "editor_flip_horizontal": "Kthe horizontalisht", - "editor_flip_vertical": "Kthe vertikalisht", - "editor_handle_corner": "Doreza e cepit {corner, select, top_left {sipÃĢr-majtas} top_right {sipÃĢr-djathtas} bottom_left {poshtÃĢ-majtas} bottom_right {poshtÃĢ-djathtas} other {A}}", - "editor_handle_edge": "Doreza e skajit {edge, select, top {sipÃĢr} bottom {poshtÃĢ} left {majtas} right {djathtas} other {An}}", - "editor_orientation": "Orientimi", - "editor_reset_all_changes": "Rivendos ndryshimet", - "editor_rotate_left": "Rrotullo 90° nÃĢ drejtim kundÃĢrorar", - "editor_rotate_right": "Rrotullo 90° nÃĢ drejtim orar", - "email": "Email", - "email_notifications": "Njoftime me email", - "empty_folder": "Kjo dosje ÃĢshtÃĢ bosh", - "empty_trash": "Zbraz koshin", - "empty_trash_confirmation": "Je i sigurt qÃĢ dÃĢshiron ta zbrazÃĢsh koshin? Kjo do tÃĢ heqÃĢ pÃĢrgjithmonÃĢ tÃĢ gjitha asetet nÃĢ kosh nga Immich.\nNuk mund ta zhbÃĢsh kÃĢtÃĢ veprim!", - "enable": "Aktivizo", - "enable_backup": "Aktivizo kopjimin rezervÃĢ", - "enable_biometric_auth_description": "Vendos kodin tÃĢnd PIN pÃĢr tÃĢ aktivizuar vÃĢrtetimin biometrik", - "enabled": "Aktivizuar", - "end_date": "Data e mbarimit", - "enqueued": "NÃĢ radhÃĢ", - "enter_wifi_name": "Vendos emrin e Wi-Fi-t", - "enter_your_pin_code": "Vendos kodin tÃĢnd PIN", - "enter_your_pin_code_subtitle": "Vendos kodin tÃĢnd PIN pÃĢr tÃĢ hyrÃĢ te dosja e kyçur", - "error": "Gabim", - "error_delete_face": "Gabim gjatÃĢ fshirjes sÃĢ fytyrÃĢs nga aseti", - "error_loading_albums": "Gabim gjatÃĢ ngarkimit tÃĢ albumeve", - "error_loading_image": "Gabim gjatÃĢ ngarkimit tÃĢ imazhit", - "error_loading_partners": "Gabim gjatÃĢ ngarkimit tÃĢ partnerÃĢve: {error}", - "error_retrieving_asset_information": "Gabim gjatÃĢ marrjes sÃĢ informacionit tÃĢ asetit", - "error_saving_image": "Gabim: {error}", - "error_tag_face_bounding_box": "Gabim gjatÃĢ etiketimit tÃĢ fytyrÃĢs — nuk merren dot koordinatat e kutisÃĢ kufizuese", - "error_title": "Gabim — Diçka shkoi keq", - "error_while_navigating": "Gabim gjatÃĢ navigimit te aseti", - "errors": { - "cannot_navigate_next_asset": "Nuk mund tÃĢ navigohet te aseti tjetÃĢr", - "cannot_navigate_previous_asset": "Nuk mund tÃĢ navigohet te aseti i mÃĢparshÃĢm", - "cant_apply_changes": "Nuk mund tÃĢ zbatohen ndryshimet", - "cant_change_activity": "Nuk mund tÃĢ {enabled, select, true {çaktivizohet} other {aktivizohet}} aktiviteti", - "cant_change_asset_favorite": "Nuk mund tÃĢ ndryshohet i preferuari pÃĢr asetin", - "cant_change_metadata_assets_count": "Nuk mund tÃĢ ndryshohen meta tÃĢ dhÃĢnat e {count, plural, one {# aseti} other {# aseteve}}", - "cant_get_faces": "Nuk mund tÃĢ merren fytyrat", - "cant_get_number_of_comments": "Nuk mund tÃĢ merret numri i komenteve", - "cant_search_people": "Nuk mund tÃĢ kÃĢrkohen persona", - "cant_search_places": "Nuk mund tÃĢ kÃĢrkohen vende", - "error_adding_assets_to_album": "Gabim gjatÃĢ shtimit tÃĢ aseteve nÃĢ album", - "error_adding_users_to_album": "Gabim gjatÃĢ shtimit tÃĢ pÃĢrdoruesve nÃĢ album", - "error_deleting_shared_user": "Gabim gjatÃĢ fshirjes sÃĢ pÃĢrdoruesit tÃĢ ndarÃĢ", - "error_downloading": "Gabim gjatÃĢ shkarkimit tÃĢ {filename}", - "error_hiding_buy_button": "Gabim gjatÃĢ fshehjes sÃĢ butonit tÃĢ blerjes", - "error_removing_assets_from_album": "Gabim gjatÃĢ heqjes sÃĢ aseteve nga albumi, kontrollo konsolÃĢn pÃĢr mÃĢ shumÃĢ detaje", - "error_selecting_all_assets": "Gabim gjatÃĢ pÃĢrzgjedhjes sÃĢ tÃĢ gjitha aseteve", - "exclusion_pattern_already_exists": "Ky model pÃĢrjashtimi ekziston tashmÃĢ.", - "failed_to_create_album": "Krijimi i albumit dÃĢshtoi", - "failed_to_create_shared_link": "Krijimi i lidhjes sÃĢ ndarÃĢ dÃĢshtoi", - "failed_to_edit_shared_link": "Modifikimi i lidhjes sÃĢ ndarÃĢ dÃĢshtoi", - "failed_to_get_people": "Marrja e personave dÃĢshtoi", - "failed_to_keep_this_delete_others": "Ruajtja e kÃĢtij aseti dhe fshirja e aseteve tÃĢ tjera dÃĢshtoi", - "failed_to_load_asset": "Ngarkimi i asetit dÃĢshtoi", - "failed_to_load_assets": "Ngarkimi i aseteve dÃĢshtoi", - "failed_to_load_notifications": "Ngarkimi i njoftimeve dÃĢshtoi", - "failed_to_load_people": "Ngarkimi i personave dÃĢshtoi", - "failed_to_remove_product_key": "Heqja e çelÃĢsit tÃĢ produktit dÃĢshtoi", - "failed_to_reset_pin_code": "Rivendosja e kodit PIN dÃĢshtoi", - "failed_to_stack_assets": "Stivosja e aseteve dÃĢshtoi", - "failed_to_tag_assets": "Etiketimi i aseteve dÃĢshtoi", - "failed_to_unstack_assets": "Çstivosja e aseteve dÃĢshtoi", - "failed_to_update_notification_status": "PÃĢrditÃĢsimi i statusit tÃĢ njoftimit dÃĢshtoi", - "incorrect_email_or_password": "Email ose fjalÃĢkalim i pasaktÃĢ", - "library_folder_already_exists": "Ky shteg importimi ekziston tashmÃĢ.", - "page_not_found": "Faqja nuk u gjet", - "paths_validation_failed": "{paths, plural, one {# shteg dÃĢshtoi} other {# shtigje dÃĢshtuan}} nÃĢ vlerÃĢsim", - "profile_picture_transparent_pixels": "Fotot e profilit nuk mund tÃĢ kenÃĢ piksele transparente. TÃĢ lutem zmadho dhe/ose zhvendos imazhin.", - "quota_higher_than_disk_size": "Vendose njÃĢ kuotÃĢ mÃĢ tÃĢ lartÃĢ se madhÃĢsia e diskut", - "something_went_wrong": "Diçka shkoi keq", - "unable_to_add_album_users": "Nuk arrihet tÃĢ shtohen pÃĢrdorues nÃĢ album", - "unable_to_add_assets_to_shared_link": "Nuk arrihet tÃĢ shtohen asete te lidhja e ndarÃĢ", - "unable_to_add_comment": "Nuk arrihet tÃĢ shtohet koment", - "unable_to_add_exclusion_pattern": "Nuk arrihet tÃĢ shtohet model pÃĢrjashtimi", - "unable_to_add_partners": "Nuk arrihet tÃĢ shtohen partnerÃĢ", - "unable_to_add_remove_archive": "Nuk arrihet tÃĢ {archived, select, true {hiqet aseti nga} other {shtohet aseti te}} arkivi", - "unable_to_add_remove_favorites": "Nuk arrihet tÃĢ {favorite, select, true {shtohet aseti te} other {hiqet aseti nga}} tÃĢ preferuarat", - "unable_to_archive_unarchive": "Nuk arrihet tÃĢ {archived, select, true {arkivohet} other {çarkivohet}}", - "unable_to_change_album_user_role": "Nuk arrihet tÃĢ ndryshohet roli i pÃĢrdoruesit tÃĢ albumit", - "unable_to_change_date": "Nuk arrihet tÃĢ ndryshohet data", - "unable_to_change_description": "Nuk arrihet tÃĢ ndryshohet pÃĢrshkrimi", - "unable_to_change_favorite": "Nuk arrihet tÃĢ ndryshohet i preferuari pÃĢr asetin", - "unable_to_change_location": "Nuk arrihet tÃĢ ndryshohet vendndodhja", - "unable_to_change_password": "Nuk arrihet tÃĢ ndryshohet fjalÃĢkalimi", - "unable_to_change_visibility": "Nuk arrihet tÃĢ ndryshohet dukshmÃĢria pÃĢr {count, plural, one {# person} other {# persona}}", - "unable_to_complete_oauth_login": "Nuk arrihet tÃĢ pÃĢrfundohet hyrja me OAuth", - "unable_to_connect": "Nuk arrihet lidhja", - "unable_to_copy_to_clipboard": "Nuk mund tÃĢ kopjohet nÃĢ kujtesÃĢn e fragmenteve, sigurohu qÃĢ po e akseson faqen pÃĢrmes https", - "unable_to_create": "Nuk arrihet tÃĢ krijohet rrjedha e punÃĢs", - "unable_to_create_admin_account": "Nuk arrihet tÃĢ krijohet llogaria e administratorit", - "unable_to_create_api_key": "Nuk arrihet tÃĢ krijohet njÃĢ Ã§elÃĢs i ri API", - "unable_to_create_library": "Nuk arrihet tÃĢ krijohet biblioteka", - "unable_to_create_user": "Nuk arrihet tÃĢ krijohet pÃĢrdoruesi", - "unable_to_delete_album": "Nuk arrihet tÃĢ fshihet albumi", - "unable_to_delete_asset": "Nuk arrihet tÃĢ fshihet aseti", - "unable_to_delete_assets": "Gabim gjatÃĢ fshirjes sÃĢ aseteve", - "unable_to_delete_exclusion_pattern": "Nuk arrihet tÃĢ fshihet modeli i pÃĢrjashtimit", - "unable_to_delete_shared_link": "Nuk arrihet tÃĢ fshihet lidhja e ndarÃĢ", - "unable_to_delete_user": "Nuk arrihet tÃĢ fshihet pÃĢrdoruesi", - "unable_to_delete_workflow": "Nuk arrihet tÃĢ fshihet rrjedha e punÃĢs", - "unable_to_download_files": "Nuk arrihet tÃĢ shkarkohen skedarÃĢt", - "unable_to_edit_exclusion_pattern": "Nuk arrihet tÃĢ modifikohet modeli i pÃĢrjashtimit", - "unable_to_empty_trash": "Nuk arrihet tÃĢ zbrazet koshi", - "unable_to_enter_fullscreen": "Nuk arrihet tÃĢ hyhet nÃĢ ekran tÃĢ plotÃĢ", - "unable_to_exit_fullscreen": "Nuk arrihet tÃĢ dilet nga ekrani i plotÃĢ", - "unable_to_get_comments_number": "Nuk arrihet tÃĢ merret numri i komenteve", - "unable_to_get_shared_link": "Marrja e lidhjes sÃĢ ndarÃĢ dÃĢshtoi", - "unable_to_hide_person": "Nuk arrihet tÃĢ fshihet personi", - "unable_to_link_motion_video": "Nuk arrihet tÃĢ lidhet videoja me lÃĢvizje", - "unable_to_link_oauth_account": "Nuk arrihet tÃĢ lidhet llogaria OAuth", - "unable_to_log_out_all_devices": "Nuk arrihet tÃĢ dilet nga tÃĢ gjitha pajisjet", - "unable_to_log_out_device": "Nuk arrihet tÃĢ dilet nga pajisja", - "unable_to_login_with_oauth": "Nuk arrihet tÃĢ hyhet me OAuth", - "unable_to_play_video": "Nuk arrihet tÃĢ luhet videoja", - "unable_to_reassign_assets_existing_person": "Nuk arrihet tÃĢ ricaktohen asetet te {name, select, null {njÃĢ person ekzistues} other {{name}}}", - "unable_to_reassign_assets_new_person": "Nuk arrihet tÃĢ ricaktohen asetet te njÃĢ person i ri", - "unable_to_refresh_user": "Nuk arrihet tÃĢ rifreskohet pÃĢrdoruesi", - "unable_to_remove_album_users": "Nuk arrihet tÃĢ hiqen pÃĢrdoruesit nga albumi", - "unable_to_remove_api_key": "Nuk arrihet tÃĢ hiqet çelÃĢsi API", - "unable_to_remove_assets_from_shared_link": "Nuk arrihet tÃĢ hiqen asetet nga lidhja e ndarÃĢ", - "unable_to_remove_library": "Nuk arrihet tÃĢ hiqet biblioteka", - "unable_to_remove_partner": "Nuk arrihet tÃĢ hiqet partneri", - "unable_to_remove_reaction": "Nuk arrihet tÃĢ hiqet reagimi", - "unable_to_reset_password": "Nuk arrihet tÃĢ rivendoset fjalÃĢkalimi", - "unable_to_reset_pin_code": "Nuk arrihet tÃĢ rivendoset kodi PIN", - "unable_to_resolve_duplicate": "Nuk arrihet tÃĢ zgjidhet duplikati", - "unable_to_restore_assets": "Nuk arrihet tÃĢ rikthehen asetet", - "unable_to_restore_trash": "Nuk arrihet tÃĢ rikthehet koshi", - "unable_to_restore_user": "Nuk arrihet tÃĢ rikthehet pÃĢrdoruesi", - "unable_to_save_album": "Nuk arrihet tÃĢ ruhet albumi", - "unable_to_save_api_key": "Nuk arrihet tÃĢ ruhet çelÃĢsi API", - "unable_to_save_date_of_birth": "Nuk arrihet tÃĢ ruhet data e lindjes", - "unable_to_save_name": "Nuk arrihet tÃĢ ruhet emri", - "unable_to_save_profile": "Nuk arrihet tÃĢ ruhet profili", - "unable_to_save_settings": "Nuk arrihet tÃĢ ruhen cilÃĢsimet", - "unable_to_scan_libraries": "Nuk arrihet tÃĢ skanohen bibliotekat", - "unable_to_scan_library": "Nuk arrihet tÃĢ skanohet biblioteka", - "unable_to_set_feature_photo": "Nuk arrihet tÃĢ caktohet fotoja e veçorisÃĢ", - "unable_to_set_profile_picture": "Nuk arrihet tÃĢ caktohet fotoja e profilit", - "unable_to_set_rating": "Nuk arrihet tÃĢ caktohet vlerÃĢsimi", - "unable_to_submit_job": "Nuk arrihet tÃĢ dorÃĢzohet puna", - "unable_to_trash_asset": "Nuk arrihet tÃĢ hidhet aseti nÃĢ kosh", - "unable_to_unlink_account": "Nuk arrihet tÃĢ shkÃĢputet llogaria", - "unable_to_unlink_motion_video": "Nuk arrihet tÃĢ shkÃĢputet videoja me lÃĢvizje", - "unable_to_update_album_cover": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet kopertina e albumit", - "unable_to_update_album_info": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet informacioni i albumit", - "unable_to_update_library": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet biblioteka", - "unable_to_update_location": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet vendndodhja", - "unable_to_update_settings": "Nuk arrihet tÃĢ pÃĢrditÃĢsohen cilÃĢsimet", - "unable_to_update_timeline_display_status": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet statusi i shfaqjes sÃĢ kronologjisÃĢ", - "unable_to_update_user": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet pÃĢrdoruesi", - "unable_to_update_workflow": "Nuk arrihet tÃĢ pÃĢrditÃĢsohet rrjedha e punÃĢs", - "unable_to_upload_file": "Nuk arrihet tÃĢ ngarkohet skedari" - }, - "errors_text": "Gabime", - "exclusion_pattern": "Modeli i pÃĢrjashtimit", - "exif": "Exif", - "exif_bottom_sheet_description": "Shto pÃĢrshkrim...", - "exif_bottom_sheet_description_error": "Gabim gjatÃĢ pÃĢrditÃĢsimit tÃĢ pÃĢrshkrimit", - "exif_bottom_sheet_no_description": "Pa pÃĢrshkrim", - "exit_slideshow": "Dil nga shfaqja e diapozitivave", - "expand": "Zgjero", - "expand_all": "Zgjero tÃĢ gjitha", - "expire_after": "Skadon pas", - "expired": "Skaduar", - "expires_date": "Skadon mÃĢ {date}", - "explore": "Eksploro", - "explorer": "Eksploruesi", - "export": "Eksporto", - "export_as_json": "Eksporto si JSON", - "export_database": "Eksporto bazÃĢn e tÃĢ dhÃĢnave", - "export_database_description": "Eksporto bazÃĢn e tÃĢ dhÃĢnave SQLite", - "exposure_time": "Koha e ekspozimit", - "extension": "Zgjatimi", - "external": "I jashtÃĢm", - "external_libraries": "Biblioteka tÃĢ jashtme", - "external_network": "Rrjet i jashtÃĢm", - "external_network_sheet_info": "Kur nuk je nÃĢ rrjetin Wi-Fi tÃĢ preferuar, aplikacioni do tÃĢ lidhet me serverin pÃĢrmes URL-sÃĢ sÃĢ parÃĢ mÃĢ poshtÃĢ qÃĢ arrin tÃĢ kontaktojÃĢ, duke filluar nga lart poshtÃĢ", - "f_number": "Numri-F", - "face_unassigned": "I pacaktuar", - "failed": "DÃĢshtoi", - "failed_count": "DÃĢshtoi: {count}", - "failed_to_authenticate": "VÃĢrtetimi dÃĢshtoi", - "failed_to_delete_file": "Fshirja e skedarit dÃĢshtoi", - "failed_to_load_assets": "Ngarkimi i aseteve dÃĢshtoi", - "failed_to_load_folder": "Ngarkimi i dosjes dÃĢshtoi", - "favorite": "I preferuar", - "favorite_action_prompt": "{count} u shtua te tÃĢ preferuarat", - "favorite_or_unfavorite_photo": "Shto ose hiq foton nga tÃĢ preferuarat", - "favorites": "TÃĢ preferuarat", - "feature_photo_updated": "Fotoja e veçorisÃĢ u pÃĢrditÃĢsua", - "features": "VeçoritÃĢ", - "features_setting_description": "Menaxho veçoritÃĢ e aplikacionit", - "file_name_or_extension": "Emri i skedarit ose zgjatimi", - "file_name_text": "Emri i skedarit", - "file_size": "MadhÃĢsia e skedarit", - "filename": "Emri i skedarit", - "filetype": "Lloji i skedarit", - "filter": "Filtro", - "filter_people": "Filtro personat", - "filter_places": "Filtro vendet", - "filter_tags": "Filtro etiketat", - "filters": "Filtra", - "first": "I pari", - "fix_incorrect_match": "Rregullo pÃĢrputhjen e pasaktÃĢ", - "focal_length": "GjatÃĢsia fokale", - "folder": "Dosje", - "folder_not_found": "Dosja nuk u gjet", - "folders": "Dosje", - "folders_feature_description": "Shfletimi i pamjes sÃĢ dosjeve pÃĢr fotot dhe videot nÃĢ sistemin e skedarÃĢve", - "forgot_pin_code_question": "Harrove PIN-in?", - "forward": "PÃĢrcill", - "free_up_space": "Liro hapÃĢsirÃĢ", - "free_up_space_description": "Zhvendos fotot dhe videot me kopje rezervÃĢ te koshi i pajisjes pÃĢr tÃĢ liruar hapÃĢsirÃĢ. Kopjet e tua nÃĢ server mbeten tÃĢ sigurta.", - "free_up_space_settings_subtitle": "Liro hapÃĢsirÃĢn e pajisjes", - "full_path": "Shtegu i plotÃĢ: {path}", - "full_path_or_folder": "Shtegu i plotÃĢ ose dosja", - "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Kjo veçori ngarkon burime tÃĢ jashtme nga Google pÃĢr tÃĢ funksionuar.", - "general": "TÃĢ pÃĢrgjithshme", - "geolocation_instruction_location": "Kliko mbi njÃĢ aset me koordinata GPS pÃĢr tÃĢ pÃĢrdorur vendndodhjen e tij, ose zgjidh njÃĢ vendndodhje drejtpÃĢrdrejt nga harta", - "get_help": "Merr ndihmÃĢ", - "get_people_error": "Gabim gjatÃĢ marrjes sÃĢ personave", - "get_wifiname_error": "Nuk u mor dot emri i Wi-Fi-t. Sigurohu qÃĢ ke dhÃĢnÃĢ lejet e nevojshme dhe je i lidhur me njÃĢ rrjet Wi-Fi", - "getting_started": "Fillimi", - "go_back": "Kthehu", - "go_to_folder": "Shko te dosja", - "go_to_search": "Shko te kÃĢrkimi", - "gps": "GPS", - "gps_missing": "Pa GPS", - "grant_permission": "Jep leje", - "group_albums_by": "Grupo albumet sipas...", - "group_country": "Grupo sipas vendit", - "group_no": "Pa grupim", - "group_owner": "Grupo sipas pronarit", - "group_places_by": "Grupo vendet sipas...", - "group_year": "Grupo sipas vitit", - "haptic_feedback_title": "Reagim haptik", - "has_quota": "Ka kuotÃĢ", - "hash_asset": "BÃĢj hash asetin", - "hashed_assets": "Asete tÃĢ hashuara", - "hashing": "Po bÃĢhet hashimi", - "header_settings_add_header_tip": "Shto header", - "header_settings_field_validator_msg": "Vlera nuk mund tÃĢ jetÃĢ bosh", - "header_settings_header_name_input": "Emri i header-it", - "header_settings_header_value_input": "Vlera e header-it", - "headers_settings_tile_title": "Header-a tÃĢ personalizuar proxy", - "height": "LartÃĢsia", - "hi_user": "PÃĢrshÃĢndetje {name} ({email})", - "hide_all_people": "Fshih tÃĢ gjithÃĢ personat", - "hide_gallery": "Fshih galerinÃĢ", - "hide_named_person": "Fshih personin {name}", - "hide_password": "Fshih fjalÃĢkalimin", - "hide_person": "Fshih personin", - "hide_schema": "Fshih skemÃĢn", - "hide_text_recognition": "Fshih njohjen e tekstit", - "hide_unnamed_people": "Fshih personat pa emÃĢr", - "home_page_building_timeline": "Po ndÃĢrtohet kronologjia", - "host": "Host", - "hour": "Ora", - "hours": "OrÃĢ", - "id": "ID", - "idle": "Joaktiv", - "image": "Imazhi", - "image_alt_text_date": "{isVideo, select, true {Video} other {Imazh}} i realizuar mÃĢ {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Imazh}} i realizuar me {person1} mÃĢ {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar me {person1} dhe {person2} mÃĢ {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar me {person1}, {person2} dhe {person3} mÃĢ {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar me {person1}, {person2} dhe {additionalCount, number} tÃĢ tjerÃĢ mÃĢ {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Imazh}} i realizuar nÃĢ {city}, {country} mÃĢ {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Imazh}} i realizuar nÃĢ {city}, {country} me {person1} mÃĢ {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar nÃĢ {city}, {country} me {person1} dhe {person2} mÃĢ {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar nÃĢ {city}, {country} me {person1}, {person2} dhe {person3} mÃĢ {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Imazh}} i realizuar nÃĢ {city}, {country} me {person1}, {person2} dhe {additionalCount, number} tÃĢ tjerÃĢ mÃĢ {date}", - "immich_logo": "Logoja e Immich", - "immich_web_interface": "NdÃĢrfaqja Web e Immich", - "import_from_json": "Importo nga JSON", - "import_path": "Shtegu i importimit", - "in_albums": "NÃĢ {count, plural, one {# album} other {# albume}}", - "in_archive": "NÃĢ arkiv", - "in_year": "NÃĢ {year}", - "in_year_selector": "NÃĢ", - "include_archived": "PÃĢrfshi tÃĢ arkivuarat", - "include_shared_albums": "PÃĢrfshi albumet e ndara", - "include_shared_partner_assets": "PÃĢrfshi asetet e ndara tÃĢ partnerit", - "individual_share": "Ndarje individuale", - "individual_shares": "Ndarje individuale", - "info": "Info", - "integrity_checks": "Kontrolle integriteti", - "interval": { - "day_at_onepm": "Çdo ditÃĢ nÃĢ orÃĢn 13:00", - "hours": "Çdo {hours, plural, one {orÃĢ} other {{hours, number} orÃĢ}}", - "night_at_midnight": "Çdo natÃĢ nÃĢ mesnatÃĢ", - "night_at_twoam": "Çdo natÃĢ nÃĢ orÃĢn 2:00" - }, - "invalid_date": "DatÃĢ e pavlefshme", - "invalid_date_format": "Format date i pavlefshÃĢm", - "invite_people": "Fto persona", - "invite_to_album": "Fto nÃĢ album", - "iso": "ISO", - "items_count": "{count, plural, one {# artikull} other {# artikuj}}", - "jobs": "PunÃĢt", - "keep": "Mbaj", - "keep_albums": "Mbaj albumet", - "keep_albums_count": "Po mbahen {count} {count, plural, one {album} other {albume}}", - "keep_all": "Mbaji tÃĢ gjitha", - "keep_description": "Zgjidh çfarÃĢ mbetet nÃĢ pajisjen tÃĢnde kur liron hapÃĢsirÃĢ.", - "keep_favorites": "Mbaj tÃĢ preferuarat", - "keep_on_device": "Mbaj nÃĢ pajisje", - "keep_on_device_hint": "Zgjidh artikujt pÃĢr tÃĢ mbajtur nÃĢ kÃĢtÃĢ pajisje", - "keep_this_delete_others": "Mbaj kÃĢtÃĢ, fshi tÃĢ tjerat", - "keeping": "Po mbahen: {items}", - "kept_this_deleted_others": "U mbajt ky aset dhe u fshinÃĢ {count, plural, one {# aset} other {# asete}}", - "keyboard_shortcuts": "Shkurtore tastiere", - "language": "Gjuha", - "language_no_results_subtitle": "Provo tÃĢ rregullosh termin tÃĢnd tÃĢ kÃĢrkimit", - "language_no_results_title": "Nuk u gjet asnjÃĢ gjuhÃĢ", - "language_search_hint": "KÃĢrko gjuhÃĢ...", - "language_setting_description": "Zgjidh gjuhÃĢn tÃĢnde tÃĢ preferuar", - "large_files": "SkedarÃĢ tÃĢ mÃĢdhenj", - "last": "I fundit", - "last_months": "{count, plural, one {Muajin e kaluar} other {# muajt e kaluar}}", - "last_seen": "ParÃĢ sÃĢ fundmi", - "latest_version": "Versioni mÃĢ i fundit", - "latitude": "GjerÃĢsia gjeografike", - "leave": "Largohu", - "leave_album": "Largohu nga albumi", - "lens_model": "Modeli i objektivit", - "less": "MÃĢ pak", - "let_others_respond": "Lejo tÃĢ tjerÃĢt tÃĢ pÃĢrgjigjen", - "level": "Niveli", - "library": "Biblioteka", - "library_add_folder": "Shto dosje", - "library_edit_folder": "Modifiko dosjen", - "library_page_sort_asset_count": "Numri i aseteve", - "library_page_sort_created": "Data e krijimit", - "library_page_sort_last_modified": "Modifikuar sÃĢ fundmi", - "library_page_sort_title": "Titulli i albumit", - "licenses": "Licencat", - "light": "E çelÃĢt", - "light_theme": "Kalo nÃĢ temÃĢn e çelÃĢt", - "like": "PÃĢlqe", - "like_deleted": "PÃĢlqimi u fshi", - "link": "Lidhje", - "link_motion_video": "Lidh videon me lÃĢvizje", - "link_to_docs": "PÃĢr mÃĢ shumÃĢ informacion, referoju dokumentacionit.", - "link_to_oauth": "Lidh me OAuth", - "linked_oauth_account": "Llogaria OAuth e lidhur", - "list": "ListÃĢ", - "live": "DrejtpÃĢrdrejt", - "location": "Vendndodhja", - "model": "Modeli", - "name": "Emri", - "none": "AsnjÃĢ", - "offline": "JashtÃĢ linje", - "ok": "NÃĢ rregull", - "online": "Online", - "orientation": "Orientimi", - "path": "Shtegu", - "permanently_deleted_assets_count": "U fshinÃĢ pÃĢrgjithmonÃĢ {count, plural, one {# aset} other {# asete}}", - "refresh": "Rifresko", - "rename": "RiemÃĢrto", - "role_editor": "Modifikuesi", - "search": "KÃĢrko", - "search_filter_date": "Data", - "search_filter_location": "Vendndodhja", - "select_albums": "Zgjidh albume", - "settings": "CilÃĢsimet", - "shared_link_custom_url_title": "URL e personalizuar", - "size": "MadhÃĢsia", - "status": "Statusi", - "to_archive": "Arkiv", - "to_select": "pÃĢr tÃĢ zgjedhur", - "type": "Lloji", - "unknown": "E panjohur", - "upload_finished": "Ngarkimi pÃĢrfundoi", - "upload_status_duplicates": "DuplikatÃĢ", - "upload_status_errors": "Gabime", - "upload_status_uploaded": "Ngarkuar", - "username": "Emri i pÃĢrdoruesit", - "version": "Versioni", "you_dont_have_any_shared_links": "Nuk keni asnjÃĢ link tÃĢ shpÃĢrndarÃĢ", "your_wifi_name": "Emri i Wi-Fi tuaj", - "zoom_image": "Zmadho imazhin" + "zoom_image": "Zmadho imazhin", + "zoom_to_bounds": "Zmadho sipas kufijve" } diff --git a/i18n/sr_Cyrl.json b/i18n/sr_Cyrl.json index fdb5e16bc2..1472b111a4 100644 --- a/i18n/sr_Cyrl.json +++ b/i18n/sr_Cyrl.json @@ -9,6 +9,7 @@ "active": "АĐēŅ‚Đ¸Đ˛ĐŊи", "active_count": "АĐēŅ‚Đ¸Đ˛ĐŊĐž:{count}", "activity": "АĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚", + "activity_changed": "АĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚ ҘĐĩ {enabled, select, true {ĐžĐŧĐžĐŗŅƒŅ›ĐĩĐŊа} other {ĐžĐŊĐĩĐŧĐžĐŗŅƒŅ›ĐĩĐŊа}}", "add": "Đ”ĐžĐ´Đ°Ņ˜", "add_a_description": "Đ”ĐžĐ´Đ°Ņ˜ ĐžĐŋĐ¸Ņ", "add_a_location": "Đ”ĐžĐ´Đ°Ņ˜ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", @@ -19,8 +20,11 @@ "add_birthday": "Đ”ĐžĐ´Đ°Ņ˜ Ņ€ĐžŅ’ĐĩĐŊдаĐŊ", "add_endpoint": "Đ”ĐžĐ´Đ°Ņ˜ Đ°Đ´Ņ€Đĩҁ҃", "add_exclusion_pattern": "Đ”ĐžĐ´Đ°Ņ˜ ĐžĐąŅ€Đ°ĐˇĐ°Ņ† Đ¸ĐˇŅƒĐˇĐ¸ĐŧĐ°ŅšĐ°", + "add_filter": "Đ”ĐžĐ´Đ°Ņ˜ Ņ„Đ¸ĐģŅ‚ĐĩŅ€", "add_location": "Đ”ĐžĐ´Đ°Ņ˜ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", + "add_more_users": "Đ”ĐžĐ´Đ°Ņ˜ ĐēĐžŅ€Đ¸ŅĐŊиĐēĐĩ", "add_partner": "Đ”ĐžĐ´Đ°Ņ˜ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", + "add_path": "Đ”ĐžĐ´Đ°Ņ˜ ĐŋŅƒŅ‚Đ°ŅšŅƒ", "add_photos": "Đ”ĐžĐ´Đ°Ņ˜ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", "add_tag": "Đ”ĐžĐ´Đ°Ņ˜ ОСĐŊаĐē҃", "add_to": "Đ”ĐžĐ´Đ°Ņ˜ ҃â€Ļ", @@ -28,8 +32,10 @@ "add_to_album_bottom_sheet_added": "Đ”ĐžĐ´Đ°Ņ‚Đž ҃ {album}", "add_to_album_bottom_sheet_already_exists": "ВĐĩŅ› ҃ {album}", "add_to_album_bottom_sheet_some_local_assets": "НĐĩĐēĐĩ ĐģĐžĐēаĐģĐŊĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ĐŊĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ҃ аĐģĐąŅƒĐŧ", + "add_to_album_toggle": "ĐžĐąŅ€ĐŊи Đ¸ĐˇĐąĐžŅ€ Са {album}", "add_to_albums": "Đ”ĐžĐ´Đ°Ņ˜ ҃ аĐģĐąŅƒĐŧĐĩ", "add_to_albums_count": "Đ”ĐžĐ´Đ°Ņ˜ ҃ аĐģĐąŅƒĐŧĐĩ ({count})", + "add_to_shared_album": "Đ”ĐžĐ´Đ°Ņ˜ ҃ Đ´ĐĩŅ™ĐĩĐŊи аĐģĐąŅƒĐŧ", "add_upload_to_stack": "Đ”ĐžĐ´Đ°Ņ˜ ĐŋŅ€ĐĩĐŊĐĩҁĐĩĐŊĐž ҃ ĐŗŅ€ŅƒĐŋ҃", "add_url": "Đ”ĐžĐ´Đ°Ņ˜ URL", "added_to_archive": "Đ”ĐžĐ´Đ°Ņ‚Đž ҃ Đ°Ņ€Ņ…Đ¸Đ˛Ņƒ", @@ -374,11 +380,15 @@ "admin_password": "АдĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ŅĐēа ЛозиĐŊĐēа", "administration": "АдĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ†Đ¸Ņ˜Đ°", "advanced": "НаĐŋŅ€ĐĩĐ´ĐŊĐž", + "advanced_settings_enable_alternate_media_filter_subtitle": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ ĐžĐ˛Ņƒ ĐžĐŋŅ†Đ¸Ņ˜Ņƒ Са Ņ„Đ¸ĐģŅ‚Ņ€Đ¸Ņ€Đ°ŅšĐĩ ĐŧĐĩĐ´Đ¸Ņ˜Đ° Ņ‚ĐžĐēĐžĐŧ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ˜Đĩ ĐŊа ĐžŅĐŊĐžĐ˛Ņƒ аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊĐ¸Ņ… ĐēŅ€Đ¸Ņ‚ĐĩŅ€Đ¸Ņ˜ŅƒĐŧа. ПоĐēŅƒŅˆĐ°Ņ˜Ņ‚Đĩ ОвО ŅĐ°ĐŧĐž аĐēĐž иĐŧĐ°Ņ‚Đĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧа ŅĐ° аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜ĐžĐŧ да ĐžŅ‚ĐēŅ€Đ¸Ņ˜Đĩ ŅĐ˛Đĩ аĐģĐąŅƒĐŧĐĩ.", + "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНĐĸАЛНО] ĐšĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ Ņ„Đ¸ĐģŅ‚ĐĩŅ€ Са ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ˜Ņƒ аĐģĐąŅƒĐŧа ĐŊа аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊĐžĐŧ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Ņƒ", "advanced_settings_log_level_title": "Ниво ĐĩвидĐĩĐŊŅ†Đ¸Ņ˜Đĩ (ĐģĐžĐŗ): {level}", "advanced_settings_prefer_remote_subtitle": "НĐĩĐēи ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ¸ вĐĩĐžĐŧа ҁĐŋĐžŅ€Đž ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°Ņ˜Ņƒ ҁĐģĐ¸Ņ‡Đ¸Ņ†Đĩ ĐģĐžĐēаĐģĐŊĐ¸Ņ… Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа. ĐŖĐēŅ™ŅƒŅ‡Đ¸ ОвО ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐĩ да йи ҁĐĩ ҃ĐŧĐĩŅŅ‚Đž ŅšĐ¸Ņ… ĐēĐžŅ€Đ¸ŅŅ‚Đ¸ĐģĐĩ ҁĐģиĐēĐĩ ŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", "advanced_settings_prefer_remote_title": "ĐŸŅ€ĐĩŅ„ĐĩŅ€Đ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ŅƒĐ´Đ°Ņ™ĐĩĐŊĐĩ ҁĐģиĐēĐĩ", "advanced_settings_proxy_headers_subtitle": "ДĐĩŅ„Đ¸ĐŊĐ¸ŅˆĐ¸Ņ‚Đĩ ĐŋŅ€ĐžĐēŅĐ¸ ĐˇĐ°ĐŗĐģĐ°Đ˛Ņ™Đ° ĐēĐžŅ˜Đĩ Immich ҂ҀĐĩйа да ĐŋĐžŅˆĐ°Ņ™Đĩ ŅĐ° ŅĐ˛Đ°ĐēиĐŧ ĐŧŅ€ĐĩĐļĐŊиĐŧ ĐˇĐ°Ņ…Ņ‚ĐĩвОĐŧ", "advanced_settings_proxy_headers_title": "ĐŸĐžŅĐĩĐąĐŊа ĐŋŅ€ĐžĐēŅĐ¸ ĐˇĐ°ĐŗĐģĐ°Đ˛Ņ™Đ° (headers) [ЕКСПЕРИМЕНĐĸАЛНО]", + "advanced_settings_self_signed_ssl_subtitle": "ĐŸŅ€ĐĩҁĐēĐ°Ņ‡Đĩ вĐĩŅ€Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Ņ˜Ņƒ SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° Са ĐēŅ€Đ°Ņ˜ŅšŅƒ Ņ‚Đ°Ņ‡Đē҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°. ОбавĐĩСĐŊĐž Са ŅĐ°ĐŧĐžĐŋĐžŅ‚ĐŋĐ¸ŅĐ°ĐŊĐĩ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đĩ.", + "advanced_settings_self_signed_ssl_title": "ДозвоĐģи ŅĐ°ĐŧĐžĐŋĐžŅ‚ĐŋĐ¸ŅĐ°ĐŊĐĩ SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đĩ [ЕКСПЕРИМЕНĐĸАЛНО]", "advanced_settings_sync_remote_deletions_subtitle": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēи Đ¸ĐˇĐąŅ€Đ¸ŅˆĐ¸Ņ‚Đĩ иĐģи Đ˛Ņ€Đ°Ņ‚Đ¸Ņ‚Đĩ ҁҀĐĩĐ´ŅŅ‚Đ˛Đž ĐŊа ОвОĐŧ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Ņƒ Đēада ҁĐĩ Ņ‚Đ° Ņ€Đ°Đ´ŅšĐ° ĐŋŅ€ĐĩĐ´ŅƒĐˇĐŧĐĩ ĐŊа вĐĩĐąŅƒ", "advanced_settings_sync_remote_deletions_title": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇŅƒŅ˜Ņ‚Đĩ ŅƒĐ´Đ°Ņ™ĐĩĐŊа ĐąŅ€Đ¸ŅĐ°ŅšĐ° [ЕКСПЕРИМЕНĐĸАЛНО]", "advanced_settings_tile_subtitle": "НаĐŋŅ€ĐĩĐ´ĐŊа ĐēĐžŅ€Đ¸ŅĐŊĐ¸Ņ‡Đēа ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", @@ -393,7 +403,11 @@ "album_delete_confirmation": "Да Đģи ŅŅ‚Đ˛Đ°Ņ€ĐŊĐž ĐļĐĩĐģĐ¸Ņ‚Đĩ да Đ¸ĐˇĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ аĐģĐąŅƒĐŧ {album}?", "album_delete_confirmation_description": "АĐēĐž ҁĐĩ ĐžĐ˛Đ°Ņ˜ аĐģĐąŅƒĐŧ Đ´ĐĩĐģи, Đ´Ņ€ŅƒĐŗĐ¸ ĐēĐžŅ€Đ¸ŅĐŊĐ¸Ņ†Đ¸ Đ˛Đ¸ŅˆĐĩ ĐŊĐĩŅ›Đĩ ĐŧĐžŅ›Đ¸ да Đŧ҃ ĐŋŅ€Đ¸ŅŅ‚ŅƒĐŋĐĩ.", "album_deleted": "АĐģĐąŅƒĐŧ ĐžĐąŅ€Đ¸ŅĐ°ĐŊ", + "album_info_card_backup_album_excluded": "Đ˜ĐĄĐšĐ›ĐˆĐŖĐ§Đ•ĐĐž", + "album_info_card_backup_album_included": "ĐŖĐšĐ›ĐˆĐŖĐ§Đ•ĐĐž", "album_info_updated": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đ° аĐģĐąŅƒĐŧа аĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊа", + "album_leave": "НаĐŋŅƒŅŅ‚Đ¸Ņ‚Đ¸ аĐģĐąŅƒĐŧ?", + "album_leave_confirmation": "Да Đģи ŅŅ‚Đ˛Đ°Ņ€ĐŊĐž ĐļĐĩĐģĐ¸Ņ‚Đĩ да ĐŊаĐŋŅƒŅŅ‚Đ¸Ņ‚Đĩ {album}?", "album_name": "ИĐŧĐĩ аĐģĐąŅƒĐŧа", "album_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ аĐģĐąŅƒĐŧа", "album_remove_user": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅĐŊиĐēа?", @@ -402,7 +416,15 @@ "album_summary": "ХаĐļĐĩŅ‚Đ°Đē аĐģĐąŅƒĐŧа", "album_updated": "АĐģĐąŅƒĐŧ аĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊ", "album_updated_setting_description": "ĐŸŅ€Đ¸ĐŧĐ¸Ņ‚Đĩ ОйавĐĩŅˆŅ‚ĐĩҚĐĩ Đĩ-ĐŋĐžŅˆŅ‚ĐžĐŧ Đēада Đ´ĐĩŅ™ĐĩĐŊи аĐģĐąŅƒĐŧ иĐŧа ĐŊОва ŅĐ˛ĐžŅ˜ŅŅ‚Đ˛Đ°", + "album_user_left": "НаĐŋŅƒŅŅ‚Đ¸Đž/Đģа {album}", + "album_user_removed": "ĐŖĐēĐģĐžŅšĐĩĐŊ {user}", + "album_viewer_appbar_delete_confirm": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да Đ¸ĐˇĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ ĐžĐ˛Đ°Ņ˜ аĐģĐąŅƒĐŧ ŅĐ° ŅĐ˛ĐžĐŗ ĐŊаĐģĐžĐŗĐ°?", "album_viewer_appbar_share_err_delete": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐąŅ€Đ¸ŅĐ°ŅšĐĩ аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_err_leave": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž иСĐģаĐļĐĩҚĐĩ иС аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_err_remove": "ĐŸŅ€ĐžĐąĐģĐĩĐŧи ŅĐ° ĐąŅ€Đ¸ŅĐ°ŅšĐĩĐŧ СаĐŋĐ¸ŅĐ° иС аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_err_title": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐŧĐĩŅšĐ°ŅšĐĩ ĐŊаСива аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_leave": "Đ˜ĐˇĐ°Ņ’Đ¸ иС аĐģĐąŅƒĐŧа", + "album_viewer_appbar_share_to": "ПодĐĩĐģи ŅĐ°", "album_viewer_page_share_add_users": "Đ”ĐžĐ´Đ°Ņ˜ ĐēĐžŅ€Đ¸ŅĐŊиĐēĐĩ", "album_with_link_access": "НĐĩĐēа ŅĐ˛Đ°ĐēĐž ĐēĐž иĐŧа вĐĩĐˇŅƒ види Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и Ņ™ŅƒĐ´Đĩ ҃ ОвОĐŧ аĐģĐąŅƒĐŧ҃.", "albums": "АĐģĐąŅƒĐŧи", @@ -413,15 +435,18 @@ "all_people": "ХвĐĩ ĐžŅĐžĐąĐĩ", "all_videos": "Хви видĐĩĐž ҁĐŊиĐŧŅ†Đ¸", "allow_dark_mode": "ДозвоĐģи Ņ‚Đ°ĐŧĐŊи Ņ€ĐĩĐļиĐŧ", + "allow_edits": "ДозвоĐģи ŅƒŅ€ĐĩŅ’ĐĩҚĐĩ", "allow_public_user_to_download": "ДозвоĐģĐ¸Ņ‚Đĩ Ņ˜Đ°Đ˛ĐŊĐžĐŧ ĐēĐžŅ€Đ¸ŅĐŊиĐē҃ да ĐŋŅ€ĐĩŅƒĐˇĐŧĐĩ (Đ´ĐžwĐŊĐģОад-҃ҘĐĩ)", "allow_public_user_to_upload": "ДозвоĐģи Ņ˜Đ°Đ˛ĐŊĐžĐŧ ĐēĐžŅ€Đ¸ŅĐŊиĐē҃ да ĐžŅ‚ĐŋŅ€ĐĩĐŧи (҃ĐŋĐģОад-҃ҘĐĩ)", "alt_text_qr_code": "ĐĄĐģиĐēа QĐ  ĐēОда", + "anti_clockwise": "ĐŖ ҁĐŧĐĩŅ€Ņƒ ҁ҃ĐŋŅ€ĐžŅ‚ĐŊĐžĐŧ Од ĐēĐ°ĐˇĐ°Ņ™ĐēĐĩ ĐŊа ŅĐ°Ņ‚Ņƒ", "api_key": "АПИ ĐēŅ™ŅƒŅ‡ (ĐēĐĩy)", "api_key_description": "Ова Đ˛Ņ€ĐĩĐ´ĐŊĐžŅŅ‚ Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ ĐŋŅ€Đ¸ĐēаСаĐŊа ŅĐ°ĐŧĐž ҘĐĩĐ´ĐŊĐžĐŧ. ОбавĐĩСĐŊĐž ĐēĐžĐŋĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ĐŋŅ€Đĩ ĐŊĐĩĐŗĐž ŅˆŅ‚Đž ĐˇĐ°Ņ‚Đ˛ĐžŅ€Đ¸Ņ‚Đĩ ĐŋŅ€ĐžĐˇĐžŅ€.", "api_key_empty": "ИĐŧĐĩ Đ˛Đ°ŅˆĐĩĐŗ АПИ ĐēŅ™ŅƒŅ‡Đ° ĐŊĐĩ йи ҂ҀĐĩйаĐģĐž да ĐąŅƒĐ´Đĩ ĐŋŅ€Đ°ĐˇĐŊĐž", "api_keys": "АПИ ĐēŅ™ŅƒŅ‡Đĩви (ĐēĐĩyҁ)", "app_architecture_variant": "Đ’Đ°Ņ€Đ¸Ņ˜Đ°ĐŊŅ‚Đ° (Đ°Ņ€Ņ…Đ¸Ņ‚ĐĩĐēŅ‚ŅƒŅ€Đ°)", "app_bar_signout_dialog_content": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да ҁĐĩ ĐžĐ´Ņ˜Đ°Đ˛Đ¸Ņ‚Đĩ?", + "app_bar_signout_dialog_ok": "Да", "app_bar_signout_dialog_title": "ĐžĐ´Ņ˜Đ°Đ˛Đ¸Ņ‚Đĩ ҁĐĩ", "app_settings": "ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ° аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ", "app_stores": "ĐŸŅ€ĐžĐ´Đ°Đ˛ĐŊĐ¸Ņ†Đĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đ°", @@ -431,17 +456,25 @@ "archive": "ĐŅ€Ņ…Đ¸Đ˛Đ°", "archive_action_prompt": "{count} Đ´ĐžĐ´Đ°Ņ‚Đž ҃ Đ°Ņ€Ņ…Đ¸Đ˛Ņƒ", "archive_or_unarchive_photo": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜Ņ‚Đĩ иĐģи ĐŋĐžĐŊĐ¸ŅˆŅ‚Đ¸Ņ‚Đĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ŅšĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", + "archive_page_no_archived_assets": "ĐĐ¸ŅŅƒ ĐŋŅ€ĐžĐŊĐ°Ņ’ĐĩĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ°", + "archive_page_title": "ĐŅ€Ņ…Đ¸Đ˛Đ° ({count})", "archive_size": "ВĐĩĐģĐ¸Ņ‡Đ¸ĐŊа Đ°Ņ€Ņ…Đ¸Đ˛Đĩ", "archive_size_description": "ПодĐĩŅĐ¸ вĐĩĐģĐ¸Ņ‡Đ¸ĐŊ҃ Đ°Ņ€Ņ…Đ¸Đ˛Đĩ Са ĐŋŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ (҃ ГиБ)", "archived": "ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž", "archived_count": "{count, plural, other {ĐŅ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž #}}", "are_these_the_same_person": "Да Đģи ҁ҃ ОвО Đ¸ŅŅ‚Đ° ĐžŅĐžĐąĐ°?", "are_you_sure_to_do_this": "ЈĐĩҁ҂Đĩ Đģи ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ ОвО да ŅƒŅ€Đ°Đ´Đ¸Ņ‚Đĩ?", + "asset_action_delete_err_read_only": "НĐĩ ĐŧĐžĐŗŅƒ да ĐžĐąŅ€Đ¸ŅˆĐĩĐŧ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚(Đĩ) ŅĐ°ĐŧĐž Са Ņ‡Đ¸Ņ‚Đ°ŅšĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "asset_action_share_err_offline": "ĐĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ ĐŋŅ€ĐĩŅƒĐˇĐĩŅ‚Đ¸ ĐžŅ„ĐģĐ°Ņ˜ĐŊ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃(Đĩ), ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", "asset_added_to_album": "Đ”ĐžĐ´Đ°Ņ‚Đž ҃ аĐģĐąŅƒĐŧ", "asset_adding_to_album": "Đ”ĐžĐ´Đ°Ņ˜Đĩ ҁĐĩ ҃ аĐģĐąŅƒĐŧâ€Ļ", "asset_description_updated": "ОĐŋĐ¸Ņ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ҘĐĩ аĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊ", + "asset_filename_is_offline": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа {filename} ҘĐĩ ваĐŊ ĐŧŅ€ĐĩĐļĐĩ (offline)", + "asset_has_unassigned_faces": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа иĐŧа ĐŊĐĩдОдĐĩŅ™ĐĩĐŊа ĐģĐ¸Ņ†Đ°", "asset_hashing": "ĐĨĐĩŅˆĐ¸Ņ€Đ°ŅšĐĩâ€Ļ", "asset_list_group_by_sub_title": "Đ“Ņ€ŅƒĐŋĐ¸ŅˆĐ¸ ĐŋĐž", + "asset_list_layout_settings_dynamic_layout_title": "ДиĐŊаĐŧĐ¸Ņ‡ĐŊи Ņ€Đ°ŅĐŋĐžŅ€ĐĩĐ´", + "asset_list_layout_settings_group_automatically": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēи", "asset_list_layout_settings_group_by": "Đ“Ņ€ŅƒĐŋĐ¸ŅˆĐ¸ СаĐŋĐ¸ŅĐĩ ĐŋĐž", "asset_list_layout_settings_group_by_month_day": "МĐĩҁĐĩ҆ + ДаĐŊ", "asset_list_layout_sub_title": "ЛаyĐžŅƒŅ‚", @@ -449,22 +482,30 @@ "asset_list_settings_title": "ĐœŅ€ĐĩĐļĐŊи ĐŋŅ€Đ¸ĐēаС Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°", "asset_offline": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа ĐžĐ´ŅŅƒŅ‚ĐŊа", "asset_offline_description": "Ова ҁĐŋĐžŅ™ĐŊа Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа ҁĐĩ Đ˛Đ¸ŅˆĐĩ ĐŊĐĩ ĐŊаĐģаСи ĐŊа Đ´Đ¸ŅĐē҃. МоĐģиĐŧĐž ĐēĐžĐŊŅ‚Đ°ĐēŅ‚Đ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ŅĐ˛ĐžĐŗ Immich адĐŧиĐŊĐ¸ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° Са ĐŋĐžĐŧĐžŅ›.", + "asset_restored_successfully": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа ҘĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž Đ˛Ņ€Đ°Ņ›ĐĩĐŊа", "asset_skipped": "ĐŸŅ€ĐĩҁĐēĐžŅ‡ĐĩĐŊĐž", "asset_skipped_in_trash": "ĐŖ ĐžŅ‚Đŋад", + "asset_trashed": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа ĐąĐ°Ņ‡ĐĩĐŊа ҃ ĐžŅ‚Đŋад", "asset_uploaded": "ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐž (ĐŖĐŋĐģОадĐĩĐ´)", "asset_uploading": "ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩâ€Ļ", "asset_viewer_settings_subtitle": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа ĐŋŅ€ĐĩĐŗĐģĐĩĐ´Đ°Ņ‡Đ° ĐŗĐ°ĐģĐĩŅ€Đ¸Ņ˜Đĩ", "asset_viewer_settings_title": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´Đ°Ņ‡ иĐŧОвиĐŊĐĩ", "assets": "ЗаĐŋĐ¸ŅĐ¸", + "assets_added_count": "Đ”ĐžĐ´Đ°Ņ‚Đž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", "assets_added_to_album_count": "Đ”ĐžĐ´Đ°Ņ‚Đž ҘĐĩ {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}} ҃ аĐģĐąŅƒĐŧ", "assets_count": "{count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", + "assets_deleted_permanently": "{count} ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ĐžĐąŅ€Đ¸ŅĐ°ĐŊĐž", + "assets_deleted_permanently_from_server": "{count} Ņ€ĐĩŅŅƒŅ€Ņ(а) Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ĐžĐąŅ€Đ¸ŅĐ°ĐŊ(а) ŅĐ° Immich ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "assets_moved_to_trash_count": "ĐŸŅ€ĐĩĐŧĐĩŅˆŅ‚ĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}} ҃ ĐžŅ‚Đŋад", "assets_permanently_deleted_count": "ĐĸŅ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", "assets_removed_count": "ĐŖĐēĐģĐžŅšĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", + "assets_removed_permanently_from_device": "{count} ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ҃ĐēĐģĐžŅšĐĩĐŊĐž ŅĐ° Đ˛Đ°ŅˆĐĩĐŗ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°", "assets_restore_confirmation": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да Đ˛Ņ€Đ°Ņ‚Đ¸Ņ‚Đĩ ŅĐ˛Đĩ ŅĐ˛ĐžŅ˜Đĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ĐēĐžŅ˜Đĩ ҁ҃ ҃ ĐžŅ‚ĐŋĐ°Đ´Ņƒ? НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ ĐŋĐžĐŊĐ¸ŅˆŅ‚Đ¸Ņ‚Đ¸ ĐžĐ˛Ņƒ Ņ€Đ°Đ´ŅšŅƒ! ИĐŧĐ°Ņ˜Ņ‚Đĩ ĐŊа ҃Đŧ҃ да ҁĐĩ ваĐŊĐŧŅ€ĐĩĐļĐŊа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ĐŊĐĩ ĐŧĐžĐŗŅƒ Đ˛Ņ€Đ°Ņ‚Đ¸Ņ‚Đ¸ ĐŊа ĐžĐ˛Đ°Ņ˜ ĐŊĐ°Ņ‡Đ¸ĐŊ.", "assets_restored_count": "Đ’Ņ€Đ°Ņ›ĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", + "assets_restored_successfully": "{count} Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа ҃ҁĐŋĐĩ҈ĐŊĐž Đ˛Ņ€Đ°Ņ›ĐĩĐŊĐž", "assets_trashed": "{count} ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ҘĐĩ ĐŋŅ€ĐĩĐąĐ°Ņ‡ĐĩĐŊĐž ҃ ĐžŅ‚Đŋад", "assets_trashed_count": "Đ‘Đ°Ņ‡ĐĩĐŊĐž ҃ ĐžŅ‚Đŋад {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} few{# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", + "assets_trashed_from_server": "{count} Ņ€ĐĩŅŅƒŅ€Ņ(а) ĐžĐąŅ€Đ¸ŅĐ°ĐŊĐ¸Ņ… ŅĐ° Immich ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "assets_were_part_of_album_count": "{count, plural, one {Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēа ҘĐĩ} other {Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ҁ҃}} вĐĩŅ› Đ´ĐĩĐž аĐģĐąŅƒĐŧа", "authorized_devices": "ОвĐģĐ°ŅˆŅ›ĐĩĐŊи ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ¸", "automatic_endpoint_switching_subtitle": "ПовĐĩĐļĐ¸Ņ‚Đĩ ҁĐĩ ĐģĐžĐēаĐģĐŊĐž ĐŋŅ€ĐĩĐēĐž ĐžĐ´Ņ€ĐĩŅ’ĐĩĐŊĐžĐŗ Wi-Fi-Ņ˜Đ° Đēада ҘĐĩ Đ´ĐžŅŅ‚ŅƒĐŋаĐŊ и ĐēĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊĐĩ вĐĩСĐĩ ĐŊа Đ´Ņ€ŅƒĐŗĐ¸Đŧ ĐŧĐĩŅŅ‚Đ¸Đŧа", @@ -475,33 +516,72 @@ "background_location_permission_content": "Да йи ҁĐĩ ĐŧĐĩŅšĐ°ĐģĐĩ ĐŧŅ€ĐĩĐļĐĩ Đ´ĐžĐē ҁĐĩ Ņ€Đ°Đ´Đ¸ ҃ ĐŋОСадиĐŊи, ИĐŧĐ¸Ņ… ĐŧĐžŅ€Đ° *ŅƒĐ˛ĐĩĐē* иĐŧĐ°Ņ‚Đ¸ ĐŋŅ€ĐĩŅ†Đ¸ĐˇĐ°ĐŊ ĐŋŅ€Đ¸ŅŅ‚ŅƒĐŋ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đ¸ ĐēаĐēĐž йи аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đ° ĐŧĐžĐŗĐģа да ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ° иĐŧĐĩ Wi-Fi ĐŧŅ€ĐĩĐļĐĩ", "background_options": "ПозадиĐŊҁĐēĐĩ ĐžĐŋŅ†Đ¸Ņ˜Đĩ", "backup": "НаĐŋŅ€Đ°Đ˛Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋĐ¸Ņ˜Ņƒ", + "backup_album_selection_page_albums_device": "АĐģĐąŅƒĐŧа ĐŊа ŅƒŅ€ĐĩŅ’Đ°Ņ˜Ņƒ ({count})", "backup_album_selection_page_albums_tap": "Đ”ĐžĐ´Đ¸Ņ€ĐŊи да ҃ĐēŅ™ŅƒŅ‡Đ¸Ņˆ, Đ´ĐžĐ´Đ¸Ņ€ĐŊи дваĐŋŅƒŅ‚ да Đ¸ŅĐēŅ™ŅƒŅ‡Đ¸Ņˆ", "backup_album_selection_page_assets_scatter": "ЗаĐŋĐ¸ŅĐ¸ ҁĐĩ ĐŧĐžĐŗŅƒ ĐŊĐ°Ņ›Đ¸ ҃ Đ˛Đ¸ŅˆĐĩ Ņ€Đ°ĐˇĐģĐ¸Ņ‡Đ¸Ņ‚Đ¸Ņ… аĐģĐąŅƒĐŧа. ĐžĐ´Đ°Ņ‚ĐģĐĩ аĐģĐąŅƒĐŧи ҁĐĩ ĐŧĐžĐŗŅƒ ҃ĐēŅ™ŅƒŅ‡Đ¸Ņ‚Đ¸ иĐģи Đ¸ŅĐēŅ™ŅƒŅ‡Đ¸Ņ‚Đ¸ Ņ‚ĐžĐēĐžĐŧ ĐŋŅ€ĐžŅ†ĐĩŅĐ° ĐŋŅ€Đ°Đ˛Ņ™ĐĩŅšĐ° ĐŋОСадиĐŊҁĐēĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ°.", "backup_album_selection_page_select_albums": "ОдабĐĩŅ€Đ¸ аĐģĐąŅƒĐŧĐĩ", "backup_album_selection_page_selection_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đĩ Đž ҁĐĩĐģĐĩĐēŅ†Đ¸Ņ˜Đ¸", + "backup_album_selection_page_total_assets": "ĐŖĐē҃ĐŋĐŊĐž ҘĐĩдиĐŊŅŅ‚Đ˛ĐĩĐŊĐ¸Ņ… ***", + "backup_all": "ХвĐĩ", + "backup_background_service_backup_failed_message": "ĐŸŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ĐŊĐ¸Ņ˜Đĩ ҃ҁĐŋĐĩĐģĐž. ПоĐēŅƒŅˆĐ°Đ˛Đ° ҁĐĩ ĐŋĐžĐŊОвОâ€Ļ", "backup_background_service_complete_notification": "Đ—Đ°Đ˛Ņ€ŅˆĐĩĐŊĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ", + "backup_background_service_connection_failed_message": "ПовĐĩĐˇĐ¸Đ˛Đ°ŅšĐĩ ŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ ĐŊĐ¸Ņ˜Đĩ ҃ҁĐŋĐĩĐģĐž. ПоĐēŅƒŅˆĐ°Đ˛Đ°Đŧ ĐŋĐžĐŊОвОâ€Ļ", + "backup_background_service_current_upload_notification": "ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ {filename}", "backup_background_service_default_notification": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ°Đ˛Đ°ŅšĐĩ ĐŊĐžĐ˛Đ¸Ņ… СаĐŋĐ¸ŅĐ°â€Ļ", + "backup_background_service_error_title": "Đ“Ņ€Đĩ҈Đēа ҃ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚ҃ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ°", "backup_background_service_in_progress_notification": "ĐŸŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° СаĐŋĐ¸ŅĐ°â€Ļ", + "backup_background_service_upload_failure_notification": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐž: {filename}", "backup_controller_page_albums": "НаĐŋŅ€Đ°Đ˛Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋĐ¸Ņ˜Ņƒ аĐģĐąŅƒĐŧа", + "backup_controller_page_background_app_refresh_disabled_content": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜ ĐŋОСадиĐŊҁĐēĐž ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ ҃ ОĐŋŅ†Đ¸Ņ˜Đĩ > ГĐĩĐŊĐĩŅ€Đ°ĐģĐŊĐĩ > ПозадиĐŊҁĐēĐž ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ ĐēаĐēĐž йи ĐŊаĐŋŅ€Đ°Đ˛Đ¸Đģи Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ ҃ ĐŋОСадиĐŊи.", + "backup_controller_page_background_app_refresh_disabled_title": "ПозадиĐŊҁĐēĐž ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ Đ¸ŅĐēŅ™ŅƒŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_app_refresh_enable_button_text": "Иди ҃ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", "backup_controller_page_background_battery_info_link": "ПоĐēаĐļи Đŧи ĐēаĐēĐž", "backup_controller_page_background_battery_info_message": "За ĐŊĐ°Ņ˜ĐŋĐžŅƒĐˇĐ´Đ°ĐŊĐ¸Ņ˜Đĩ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ°, ŅƒĐŗĐ°ŅĐ¸Ņ‚Đĩ йиĐģĐž ĐēĐžŅ˜Ņƒ ĐžĐŋŅ†Đ¸Ņ˜Ņƒ ҃ ĐžĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Ņ˜Đ°Đŧа ĐēĐžŅ˜Đĩ йи ҁĐŋŅ€ĐĩŅ‡Đ°Đ˛Đ°ĐģĐĩ Immich ŅĐ° ĐŋŅ€Đ°Đ˛Đ¸ĐģĐŊиĐŧ Ņ€Đ°Đ´ĐžĐŧ.\n\nĐžĐ˛Đ°Ņ˜ ĐŋĐžŅŅ‚ŅƒĐŋаĐē Đ˛Đ°Ņ€Đ¸Ņ€Đ° Од ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ° Đ´Đž ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°, ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚Đĩ ĐŋĐžŅ‚Ņ€ĐĩĐąĐŊĐĩ ĐēĐžŅ€Đ°ĐēĐĩ Са Đ’Đ°Ņˆ ŅƒŅ€ĐĩŅ’Đ°Ņ˜.", "backup_controller_page_background_battery_info_ok": "ОК", "backup_controller_page_background_battery_info_title": "ОĐŋŅ‚Đ¸ĐŧĐ¸ĐˇĐ°Ņ†Đ¸Ņ˜Đ° Đ‘Đ°Ņ‚ĐĩŅ€Đ¸Ņ˜Đĩ", + "backup_controller_page_background_charging": "ХаĐŧĐž Ņ‚ĐžĐēĐžĐŧ Đŋ҃ҚĐĩŅšĐ°", + "backup_controller_page_background_configure_error": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐēĐžĐŊŅ„Đ¸ĐŗŅƒŅ€Đ¸ŅĐ°ŅšĐĩ ĐŋОСадиĐŊҁĐēĐžĐŗ ҁĐĩŅ€Đ˛Đ¸ŅĐ°", "backup_controller_page_background_delay": "Đ’Ņ€ĐĩĐŧĐĩ иСĐŧĐĩŅ’Ņƒ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҘĐŊа Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° СаĐŋĐ¸ŅĐ°: {duration}", + "backup_controller_page_background_description": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ ĐŋОСадиĐŊҁĐēи ҁĐĩŅ€Đ˛Đ¸Ņ да Đ°ŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēи ĐŋŅ€Đ°Đ˛Đ¸Ņˆ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ, ĐąĐĩС да ĐžŅ‚Đ˛Đ°Ņ€Đ°Ņˆ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", + "backup_controller_page_background_is_off": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋОСадиĐŊи ҘĐĩ Đ¸ŅĐēŅ™ŅƒŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_is_on": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋОСадиĐŊи ҘĐĩ ҃ĐēŅ™ŅƒŅ‡ĐĩĐŊĐž", + "backup_controller_page_background_turn_off": "Đ˜ŅĐēŅ™ŅƒŅ‡Đ¸ ĐŋОСадиĐŊҁĐēи ҁĐĩŅ€Đ˛Đ¸Ņ", + "backup_controller_page_background_turn_on": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ ĐŋОСадиĐŊҁĐēи ҁĐĩŅ€Đ˛Đ¸Ņ", + "backup_controller_page_background_wifi": "ХаĐŧĐž ĐŊа Wi-Fi", "backup_controller_page_backup": "НаĐŋŅ€Đ°Đ˛Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋĐ¸Ņ˜Ņƒ", "backup_controller_page_backup_selected": "ĐžĐ´Đ°ĐąŅ€Đ°ĐŊĐž: ", "backup_controller_page_backup_sub": "Đ—Đ°Đ˛Ņ€ŅˆĐĩĐŊĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° и видĐĩа", + "backup_controller_page_created": "НаĐŋŅ€Đ°Đ˛Ņ™ĐĩĐŊĐž:{date}", + "backup_controller_page_desc_backup": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋŅ€Đ˛ĐžĐŧ ĐŋĐģаĐŊ҃ да Đ°ŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēи ĐŊаĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ Đēада ĐžŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Ņƒ.", "backup_controller_page_excluded": "Đ˜ŅĐēŅ™ŅƒŅ‡ĐĩĐŊĐž: ", + "backup_controller_page_failed": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ({count})", + "backup_controller_page_filename": "ИĐŧĐĩ Ņ„Đ°Ņ˜Đģа: {filename} [{size}]", + "backup_controller_page_id": "ИД:{id}", + "backup_controller_page_info": "ИĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đĩ", "backup_controller_page_none_selected": "ĐĐ¸ŅˆŅ‚Đ° ĐžĐ´Đ°ĐąŅ€Đ°ĐŊĐž", "backup_controller_page_remainder": "ĐŸŅ€ĐĩĐžŅŅ‚Đ°ĐģĐž", "backup_controller_page_remainder_sub": "ĐžŅŅ‚Đ°ĐģĐž Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° и видĐĩа да ҁĐĩ ŅĐ°Ņ‡ŅƒĐ˛Đ° Од Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊĐ¸Ņ…", "backup_controller_page_server_storage": "ĐŸŅ€ĐžŅŅ‚ĐžŅ€ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ", + "backup_controller_page_start_backup": "ПоĐēŅ€ĐĩĐŊи ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ", + "backup_controller_page_status_off": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋŅ€Đ˛ĐžĐŧ ĐŋĐģаĐŊ҃ ҘĐĩ Đ¸ŅĐēŅ™ŅƒŅ‡ĐĩĐŊĐž", + "backup_controller_page_status_on": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēĐž ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋŅ€Đ˛ĐžĐŧ ĐŋĐģаĐŊ҃ ҘĐĩ ҃ĐēŅ™ŅƒŅ‡ĐĩĐŊĐž", "backup_controller_page_storage_format": "{used} Од {total} Đ¸ŅĐēĐžŅ€Đ¸ŅˆŅ›ĐĩĐŊĐž", "backup_controller_page_to_backup": "АĐģĐąŅƒĐŧи ĐēĐžŅ˜Đ¸ Ņ›Đĩ ҁĐĩ ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ¸Ņ‚Đ¸", "backup_controller_page_total_sub": "ХвĐĩ ҘĐĩдиĐŊŅŅ‚Đ˛ĐĩĐŊĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩи иС ĐžĐ´Đ°ĐąŅ€Đ°ĐŊĐ¸Ņ… аĐģĐąŅƒĐŧа", + "backup_controller_page_turn_off": "Đ˜ŅĐēŅ™ŅƒŅ‡Đ¸ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋŅ€Đ˛ĐžĐŧ ĐŋĐģаĐŊ҃", + "backup_controller_page_turn_on": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋŅ€Đ˛ĐžĐŧ ĐŋĐģаĐŊ҃", + "backup_controller_page_uploading_file_info": "ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ ŅĐ˛ĐžŅ˜ŅŅ‚Đ°Đ˛Đ° Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ", + "backup_err_only_album": "НĐĩĐŧĐžĐŗŅƒŅ›Đĩ ĐąŅ€Đ¸ŅĐ°ŅšĐĩ ҘĐĩдиĐŊĐžĐŗ аĐģĐąŅƒĐŧа", "backup_info_card_assets": "СаĐŋĐ¸ŅĐ¸", + "backup_manual_cancelled": "ĐžŅ‚ĐēаСаĐŊĐž", + "backup_manual_in_progress": "ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ ҘĐĩ вĐĩŅ› ҃ Ņ‚ĐžĐē҃. ПоĐēŅƒŅˆĐ°Ņ˜Ņ‚Đĩ ĐēĐ°ŅĐŊĐ¸Ņ˜Đĩ", + "backup_manual_success": "ĐŖŅĐŋĐĩŅ…", + "backup_manual_title": "ĐŖĐŋĐģОад ŅŅ‚Đ°Ņ‚ŅƒŅ", "backup_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ", + "backup_options_page_title": "Đ‘Đ°Ņ†Đē҃Đŋ ĐžĐŋŅ‚Đ¸ĐžĐŊҁ", + "backup_setting_subtitle": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐ° ҃ ĐŋОСадиĐŊи и ĐŋŅ€ĐĩĐ´ŅšĐĩĐŧ ĐŋĐģаĐŊ҃", "backward": "ĐŖĐŊаСад", + "birthdate_saved": "Đ”Đ°Ņ‚ŅƒĐŧ Ņ€ĐžŅ’ĐĩŅšĐ° ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊ", "birthdate_set_description": "Đ”Đ°Ņ‚ŅƒĐŧ Ņ€ĐžŅ’ĐĩŅšĐ° ҁĐĩ ĐēĐžŅ€Đ¸ŅŅ‚Đ¸ да йи ҁĐĩ Đ¸ĐˇŅ€Đ°Ņ‡ŅƒĐŊаĐģĐĩ ĐŗĐžĐ´Đ¸ĐŊĐĩ ОвĐĩ ĐžŅĐžĐąĐĩ ҃ Đ´ĐžĐąŅƒ ĐžĐ´Ņ€ĐĩŅ’ĐĩĐŊĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ.", "blurred_background": "ЗаĐŧŅƒŅ›ĐĩĐŊа ĐŋОСадиĐŊа", "bugs_and_feature_requests": "Đ“Ņ€Đĩ҈ĐēĐĩ (ĐąŅƒĐŗŅ) и ĐˇĐ°Ņ…Ņ‚Đĩви Са Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ˜Đĩ", @@ -511,6 +591,20 @@ "bulk_keep_duplicates_confirmation": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да ĐˇĐ°Đ´Ņ€ĐļĐ¸Ņ‚Đĩ {count, plural, one {1 Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊ҃ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃} few {# Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊĐ¸Ņ… Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}? Ово Ņ›Đĩ Ņ€ĐĩŅˆĐ¸Ņ‚Đ¸ ŅĐ˛Đĩ Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊĐĩ ĐŗŅ€ŅƒĐŋĐĩ ĐąĐĩС ĐąŅ€Đ¸ŅĐ°ŅšĐ° йиĐģĐž ҇ĐĩĐŗĐ°.", "bulk_trash_duplicates_confirmation": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ ĐŗŅ€ŅƒĐŋĐŊĐž да ĐžĐ´ĐąĐ°Ņ†Đ¸Ņ‚Đĩ {count, plural, one {1 Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊ҃ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃} few {# Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊĐ¸Ņ… Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}? Ово Ņ›Đĩ ĐˇĐ°Đ´Ņ€ĐļĐ°Ņ‚Đ¸ ĐŊĐ°Ņ˜Đ˛ĐĩŅ›Ņƒ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃ ŅĐ˛Đ°ĐēĐĩ ĐŗŅ€ŅƒĐŋĐĩ и ĐžĐ´ĐąĐ°Ņ†Đ¸Ņ‚Đ¸ ŅĐ˛Đĩ ĐžŅŅ‚Đ°ĐģĐĩ Đ´ŅƒĐŋĐģиĐēĐ°Ņ‚Đĩ.", "buy": "ĐšŅƒĐŋĐ¸Ņ‚Đĩ ĐģĐ¸Ņ†ĐĩĐŊŅ†Ņƒ Immich-a", + "cache_settings_clear_cache_button": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ĐēĐĩ҈ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Ņƒ", + "cache_settings_clear_cache_button_title": "Ова ĐžĐŋŅ†Đ¸Ņ˜Đ° ĐąŅ€Đ¸ŅˆĐĩ ĐēĐĩ҈ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Ņƒ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ. Ово Ņ›Đĩ ĐąĐ¸Ņ‚ĐŊĐž ŅƒŅ‚Đ¸Ņ†Đ°Ņ‚Đ¸ ĐŊа ĐŋĐĩŅ€Ņ„ĐžŅ€ĐŧаĐŊҁĐĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ Đ´ĐžĐē ҁĐĩ ĐēĐĩ҈ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đ° ĐŊĐĩ ŅƒŅ‡Đ¸Ņ‚Đ° ĐŋĐžĐŊОвО.", + "cache_settings_duplicated_assets_clear_button": "ĐĻЛЕАР", + "cache_settings_duplicated_assets_subtitle": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž ҁĐŊиĐŧŅ†Đ¸ ĐēĐžŅ˜Đĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đ° Đ¸ĐŗĐŊĐžŅ€Đ¸ŅˆĐĩ", + "cache_settings_duplicated_assets_title": "Đ”ŅƒĐŋĐģĐ¸Ņ€Đ°ĐŊи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ({count})", + "cache_settings_statistics_album": "МиĐŊĐ¸Ņ˜Đ°Ņ‚ŅƒŅ€Đĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēа", + "cache_settings_statistics_full": "ĐŸŅƒĐŊĐĩ ҁĐģиĐēĐĩ", + "cache_settings_statistics_shared": "МиĐŊĐ¸Ņ˜Đ°Ņ‚ŅƒŅ€Đĩ Đ´ĐĩŅ™ĐĩĐŊĐ¸Ņ… аĐģĐąŅƒĐŧа", + "cache_settings_statistics_thumbnail": "МиĐŊĐ¸Ņ˜Đ°Ņ‚ŅƒŅ€Đĩ", + "cache_settings_statistics_title": "Đ˜ŅĐēĐžŅ€Đ¸ŅˆŅ›ĐĩĐŊа ĐēĐĩ҈ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đ°", + "cache_settings_subtitle": "КоĐŊŅ‚role Са ĐēĐĩ҈ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Ņƒ ĐŧОйиĐģĐŊĐĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ Immich", + "cache_settings_tile_subtitle": "КоĐŊŅ‚Ņ€ĐžĐģĐ¸ŅˆĐ¸Ņ‚Đĩ ĐŋĐžĐŊĐ°ŅˆĐ°ŅšĐĩ ĐģĐžĐēаĐģĐŊĐžĐŗ ҁĐēĐģĐ°Đ´Đ¸ŅˆŅ‚ĐĩŅšĐ°", + "cache_settings_tile_title": "ЛоĐēаĐģĐŊа ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đ°", + "cache_settings_title": "ОĐŋŅ†Đ¸Ņ˜Đĩ Са ĐēĐĩŅˆĐ¸Ņ€Đ°ŅšĐĩ", "camera": "КаĐŧĐĩŅ€Đ°", "camera_brand": "Đ‘Ņ€ĐĩĐŊĐ´ ĐēаĐŧĐĩŅ€Đĩ", "camera_model": "МодĐĩĐģ ĐēаĐŧĐĩŅ€Đĩ", @@ -525,6 +619,7 @@ "change_date": "ĐŸŅ€ĐžĐŧĐĩĐŊи Đ´Đ°Ņ‚ŅƒĐŧ", "change_description": "ĐŸŅ€ĐžĐŧĐĩĐŊи ĐžĐŋĐ¸Ņ", "change_display_order": "ĐŸŅ€ĐžĐŧĐĩĐŊи Ņ€ĐĩĐ´ĐžŅĐģĐĩĐ´ ĐŋŅ€Đ¸ĐēаСа", + "change_expiration_time": "ĐŸŅ€ĐžĐŧĐĩĐŊи Đ˛Ņ€ĐĩĐŧĐĩ Đ¸ŅŅ‚ĐĩĐēа", "change_location": "ĐŸŅ€ĐžĐŧĐĩĐŊи ĐŧĐĩŅŅ‚Đž", "change_name": "ĐŸŅ€ĐžĐŧĐĩĐŊи иĐŧĐĩ", "change_name_successfully": "ИĐŧĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐžĐŧĐĩҚĐĩĐŊĐž", @@ -539,24 +634,31 @@ "change_your_password": "ĐŸŅ€ĐžĐŧĐĩĐŊи ŅĐ˛ĐžŅ˜Ņƒ ŅˆĐ¸Ņ„Ņ€Ņƒ", "changed_visibility_successfully": "Đ’Đ¸Đ´Ņ™Đ¸Đ˛ĐžŅŅ‚ ҘĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐžĐŧĐĩҚĐĩĐŊа", "charging": "ĐŸŅƒŅšĐĩҚĐĩ", + "check_corrupt_asset_backup": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸ да Đģи ĐŋĐžŅŅ‚ĐžŅ˜Đĩ ĐžŅˆŅ‚ĐĩŅ›ĐĩĐŊĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа", + "check_corrupt_asset_backup_button": "Đ˜ĐˇĐ˛Ņ€ŅˆĐ¸Ņ‚Đĩ ĐŋŅ€ĐžĐ˛ĐĩŅ€Ņƒ", + "check_corrupt_asset_backup_description": "ПоĐēŅ€ĐĩĐŊĐ¸Ņ‚Đĩ ĐžĐ˛Ņƒ ĐŋŅ€ĐžĐ˛ĐĩŅ€Ņƒ ŅĐ°ĐŧĐž ĐŋŅ€ĐĩĐēĐž Wi-Fi ĐŧŅ€ĐĩĐļĐĩ и ĐŊаĐēĐžĐŊ ŅˆŅ‚Đž ҁĐĩ ĐŊаĐŋŅ€Đ°Đ˛Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊа ĐēĐžĐŋĐ¸Ņ˜Đ° ŅĐ˛Đ¸Ņ… ĐŋĐžĐ´Đ°Ņ‚Đ°Đēа. ĐŸĐžŅŅ‚ŅƒĐŋаĐē ĐŧĐžĐļĐĩ ĐŋĐžŅ‚Ņ€Đ°Ņ˜Đ°Ņ‚Đ¸ ĐŊĐĩĐēĐžĐģиĐēĐž ĐŧиĐŊŅƒŅ‚Đ°.", "check_logs": "ĐŸŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚Đĩ Đ´ĐŊĐĩвĐŊиĐēĐĩ (ĐģĐžĐŗŅ)", "choose_matching_people_to_merge": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ĐžĐ´ĐŗĐžĐ˛Đ°Ņ€Đ°Ņ˜ŅƒŅ›Đĩ ĐžŅĐžĐąĐĩ Са ҁĐŋĐ°Ņ˜Đ°ŅšĐĩ", "city": "Đ“Ņ€Đ°Đ´", - "clear": "ĐžŅ‡Đ¸ŅŅ‚Đ¸", + "clear": "ĐˆĐ°ŅĐŊĐž", "clear_all": "Đ˜ĐˇĐąŅ€Đ¸ŅˆĐ¸ ŅĐ˛Đĩ", "clear_all_recent_searches": "ĐžĐąŅ€Đ¸ŅˆĐ¸Ņ‚Đĩ ŅĐ˛Đĩ ĐŊĐĩдавĐŊĐĩ ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐĩ", "clear_message": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ĐŋĐžŅ€ŅƒĐē҃", - "clear_value": "ĐžŅ‡Đ¸ŅŅ‚Đ¸ Đ˛Ņ€ĐĩĐ´ĐŊĐžŅŅ‚", - "client_cert_import": "ĐŖĐ˛ĐĩСи", + "clear_value": "ĐˆĐ°ŅĐŊа Đ˛Ņ€ĐĩĐ´ĐŊĐžŅŅ‚", + "client_cert_dialog_msg_confirm": "ОК", + "client_cert_enter_password": "ЕĐŊŅ‚ĐĩŅ€ Password", + "client_cert_import": "ИĐŧĐŋĐžŅ€Ņ‚", "client_cert_import_success_msg": "ĐĄĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ ĐēĐģĐ¸Ņ˜ĐĩĐŊŅ‚Đ° ҘĐĩ ŅƒĐ˛ĐĩСĐĩĐŊ", "client_cert_invalid_msg": "НĐĩваĐļĐĩŅ›Đ° Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° иĐģи ĐŋĐžĐŗŅ€Đĩ҈ĐŊа ĐģОСиĐŊĐēа", "client_cert_remove_msg": "ĐĄĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ ĐēĐģĐ¸Ņ˜ĐĩĐŊŅ‚Đ° ҘĐĩ ҃ĐēĐģĐžŅšĐĩĐŊ", "client_cert_subtitle": "ĐŸĐžĐ´Ņ€Đļава ŅĐ°ĐŧĐž PKCS12 (.p12, .pfx) Ņ„ĐžŅ€ĐŧĐ°Ņ‚. ĐŖĐ˛ĐžĐˇ/҃ĐēĐģĐ°ŅšĐ°ŅšĐĩ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đ° ҘĐĩ Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐž ŅĐ°ĐŧĐž ĐŋŅ€Đĩ ĐŋŅ€Đ¸Ņ˜Đ°Đ˛Đĩ", "client_cert_title": "КĐģĐ¸Ņ˜ĐĩĐŊ҂ҁĐēи SSL ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚ [ЕКСПЕРИМЕНĐĸАЛНО]", + "clockwise": "ĐŖ ҁĐŧĐĩŅ€Ņƒ ĐēĐ°ĐˇĐ°Ņ™ĐēĐĩ", "close": "Đ—Đ°Ņ‚Đ˛ĐžŅ€Đ¸", "collapse": "ĐĄĐē҃Đŋи", "collapse_all": "ĐĄĐē҃Đŋи ŅĐ˛Đĩ", "color": "Đ‘ĐžŅ˜Đ°", + "color_theme": "Đ ĐĩĐļиĐŧ ĐąĐžŅ˜Đ°", "comment_deleted": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€ ĐžĐąŅ€Đ¸ŅĐ°ĐŊ", "comment_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ ĐēĐžĐŧĐĩĐŊŅ‚Đ°Ņ€Đ°", "comments_and_likes": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸ и ĐģĐ°Ņ˜ĐēОви", @@ -574,13 +676,18 @@ "contain": "ĐžĐąŅƒŅ…Đ˛Đ°Ņ‚Đ¸", "context": "КоĐŊŅ‚ĐĩĐēҁ҂", "continue": "ĐĐ°ŅŅ‚Đ°Đ˛Đ¸", + "control_bottom_app_bar_create_new_album": "ĐšŅ€ĐĩĐ¸Ņ€Đ°Ņ˜ ĐŊОви аĐģĐąŅƒĐŧ", + "control_bottom_app_bar_delete_from_immich": "ĐžĐąŅ€Đ¸ŅˆĐ¸ иС Immich-a", "control_bottom_app_bar_delete_from_local": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ŅĐ° ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°", "control_bottom_app_bar_edit_location": "ИСĐŧĐĩĐŊи ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", "control_bottom_app_bar_edit_time": "ИСĐŧĐĩĐŊи Đ´Đ°Ņ‚ŅƒĐŧ и Đ˛Ņ€ĐĩĐŧĐĩ", + "control_bottom_app_bar_share_link": "ДĐĩĐģи link", + "control_bottom_app_bar_share_to": "ПодĐĩĐģи ŅĐ°", "control_bottom_app_bar_trash_from_immich": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸ ҃ ĐžŅ‚Đŋад", "copied_image_to_clipboard": "КоĐŋĐ¸Ņ€Đ°ĐŊа ҁĐģиĐēа ҃ ĐŧĐĩŅ’ŅƒŅĐŋŅ€ĐĩĐŧĐŊиĐē (҆ĐģиĐŋĐąĐžĐ°Ņ€Đ´).", "copied_to_clipboard": "КоĐŋĐ¸Ņ€Đ°ĐŊĐž ҃ ĐŧĐĩŅ’ŅƒŅĐŋŅ€ĐĩĐŧĐŊиĐē (҆ĐģиĐŋĐąĐžĐ°Ņ€Đ´)!", "copy_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐēĐžĐŋĐ¸Ņ€Đ°ŅšŅƒ", + "copy_file_path": "КоĐŋĐ¸Ņ€Đ°Ņ˜ ĐŋŅƒŅ‚Đ°ŅšŅƒ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ", "copy_image": "КоĐŋĐ¸Ņ€Đ°Ņ˜ ҁĐģиĐē҃", "copy_link": "КоĐŋĐ¸Ņ€Đ°Ņ˜ вĐĩĐˇŅƒ", "copy_link_to_clipboard": "КоĐŋĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ вĐĩĐˇŅƒ ҃ ĐŧĐĩŅ’ŅƒŅĐŋŅ€ĐĩĐŧĐŊиĐē (҆ĐģиĐŋĐąĐžĐ°Ņ€Đ´)", @@ -608,15 +715,19 @@ "created": "НаĐŋŅ€Đ°Đ˛Ņ™ĐĩĐŊ", "created_at": "ĐšŅ€ĐĩĐ¸Ņ€Đ°ĐŊĐž", "crop": "ĐžĐąŅ€ĐĩĐˇĐ¸Đ˛Đ°ŅšĐĩ", + "curated_object_page_title": "ĐĄŅ‚Đ˛Đ°Ņ€Đ¸", "current_device": "ĐĸŅ€ĐĩĐŊŅƒŅ‚ĐŊи ŅƒŅ€ĐĩŅ’Đ°Ņ˜", "current_pin_code": "ĐĸŅ€ĐĩĐŊŅƒŅ‚ĐŊи ПИН ĐēОд", "current_server_address": "ĐĸŅ€ĐĩĐŊŅƒŅ‚ĐŊа Đ°Đ´Ņ€ĐĩŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "custom_locale": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐžŅ’ĐĩĐŊа ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đ° (ĐģĐžŅ†Đ°ĐģĐĩ)", "custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚Đ¸Ņ€Đ°Ņ˜Ņ‚Đĩ Đ´Đ°Ņ‚ŅƒĐŧĐĩ и ĐąŅ€ĐžŅ˜ĐĩвĐĩ ĐŊа ĐžŅĐŊĐžĐ˛Ņƒ ҘĐĩСиĐēа и Ņ€ĐĩĐŗĐ¸ĐžĐŊа", + "daily_title_text_date": "Е Đ´Đ´ МММ", + "daily_title_text_date_year": "Е Đ´Đ´ МММ yyyy", "dark": "ĐĸаĐŧĐŊĐž", "date_after": "Đ”Đ°Ņ‚ŅƒĐŧ ĐŋĐžŅĐģĐĩ", "date_and_time": "Đ”Đ°Ņ‚ŅƒĐŧ и Đ’Ņ€ĐĩĐŧĐĩ", "date_before": "Đ”Đ°Ņ‚ŅƒĐŧ ĐŋŅ€Đĩ", + "date_format": "Е Đ´ ЛЛЛ y â€ĸ ĐĨ:ĐŧĐŧ", "date_of_birth_saved": "Đ”Đ°Ņ‚ŅƒĐŧ Ņ€ĐžŅ’ĐĩŅšĐ° ҃ҁĐŋĐĩ҈ĐŊĐž ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊ", "date_range": "Đ Đ°ŅĐŋĐžĐŊ Đ´Đ°Ņ‚ŅƒĐŧа", "day": "ДаĐŊ", @@ -628,6 +739,8 @@ "delete_dialog_alert": "ОвĐĩ ŅŅ‚Đ˛Đ°Ņ€Đ¸ Ņ›Đĩ ĐŋĐĩŅ€ĐŧаĐŊĐĩĐŊŅ‚ĐŊĐž ĐąĐ¸Ņ‚Đ¸ ĐžĐąŅ€Đ¸ŅĐ°ĐŊĐĩ ŅĐ° Immich-a и Đ’Đ°ŅˆĐĩĐŗ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°", "delete_dialog_alert_local": "ОвĐĩ ŅŅ‚Đ°Đ˛ĐēĐĩ Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ҃ĐēĐģĐžŅšĐĩĐŊĐĩ ŅĐ° Đ˛Đ°ŅˆĐĩĐŗ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°, аĐģи Ņ›Đĩ и Đ´Đ°Ņ™Đĩ ĐąĐ¸Ņ‚Đ¸ Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐĩ ĐŊа Immich ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ", "delete_dialog_alert_local_non_backed_up": "НĐĩĐēĐĩ ŅŅ‚Đ°Đ˛ĐēĐĩ ĐŊĐ¸ŅŅƒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐž ĐēĐžĐŋĐ¸Ņ€Đ°ĐŊĐĩ ĐŊа Immich-u и ĐąĐ¸Ņ›Đĩ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ҃ĐēĐģĐžŅšĐĩĐŊĐĩ ŅĐ° Đ˛Đ°ŅˆĐĩĐŗ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ°", + "delete_dialog_alert_remote": "ОвĐĩ ŅŅ‚Đ°Đ˛ĐēĐĩ Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊĐĩ ŅĐ° Immich ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", + "delete_dialog_ok_force": "ИĐŋаĐē ĐžĐąŅ€Đ¸ŅˆĐ¸", "delete_dialog_title": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ĐŋĐĩŅ€ĐŧаĐŊĐĩĐŊŅ‚ĐŊĐž", "delete_duplicates_confirmation": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да Ņ‚Ņ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ ОвĐĩ Đ´ŅƒĐŋĐģиĐēĐ°Ņ‚Đĩ?", "delete_face": "Đ˜ĐˇĐąŅ€Đ¸ŅˆĐ¸ ĐžŅĐžĐąŅƒ", @@ -643,13 +756,18 @@ "delete_tag_confirmation_prompt": "Да Đģи ŅŅ‚Đ˛Đ°Ņ€ĐŊĐž ĐļĐĩĐģĐ¸Ņ‚Đĩ да Đ¸ĐˇĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ ОСĐŊаĐē҃ {tagName}?", "delete_user": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "deleted_shared_link": "ĐžĐąŅ€Đ¸ŅˆĐĩĐŊа Đ´ĐĩŅ™ĐĩĐŊа вĐĩСа", + "deletes_missing_assets": "Đ‘Ņ€Đ¸ŅˆĐĩ ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ĐēĐžŅ˜Đ° ĐŊĐĩĐ´ĐžŅŅ‚Đ°Ņ˜Ņƒ ŅĐ° Đ´Đ¸ŅĐēа", "description": "ОĐŋĐ¸Ņ", + "description_input_hint_text": "Адд Đ´ĐĩŅŅ†Ņ€Đ¸ĐŋŅ‚Đ¸ĐžĐŊ...", + "description_input_submit_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ аĐļŅƒŅ€Đ¸Ņ€Đ°ŅšŅƒ ĐžĐŋĐ¸ŅĐ°, ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚Đĩ Đ´ĐŊĐĩвĐŊиĐē Са Đ˛Đ¸ŅˆĐĩ Đ´ĐĩŅ‚Đ°Ņ™Đ°", "details": "ДĐĩŅ‚Đ°Ņ™Đ¸", "direction": "ĐĄĐŧĐĩŅ€", "disabled": "ОĐŊĐĩĐŧĐžĐŗŅƒŅ›ĐĩĐŊĐž", + "disallow_edits": "Đ—Đ°ĐąŅ€Đ°ĐŊи иСĐŧĐĩĐŊĐĩ", "discord": "Đ”Đ¸ŅĐēĐžŅ€Đ´", "discover": "ĐžŅ‚ĐēŅ€Đ¸Ņ˜Ņ‚Đĩ", "dismiss_all_errors": "ĐžĐ´ĐąĐ°Ņ†Đ¸Ņ‚Đĩ ŅĐ˛Đĩ ĐŗŅ€Đĩ҈ĐēĐĩ", + "dismiss_error": "ĐžĐ´ĐąĐ°Ņ†Đ¸ ĐŗŅ€Đĩ҈Đē҃", "display_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ ĐŋŅ€Đ¸ĐēаСа", "display_order": "Đ ĐĩĐ´ĐžŅĐģĐĩĐ´ ĐŋŅ€Đ¸ĐēаСа", "display_original_photos": "ĐŸŅ€Đ¸ĐēаĐļĐ¸Ņ‚Đĩ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", @@ -661,6 +779,7 @@ "download_canceled": "ĐŸŅ€ĐĩŅƒĐˇĐŧи ĐžŅ‚ĐēаСаĐŊĐž", "download_complete": "ĐŸŅ€ĐĩŅƒĐˇĐŧи ĐˇĐ°Đ˛Ņ€ŅˆĐĩĐŊĐž", "download_enqueue": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҘĐĩ ŅŅ‚Đ°Đ˛Ņ™ĐĩĐŊĐž ҃ Ņ€ĐĩĐ´", + "download_error": "ДоwĐŊĐģОад Đ•Ņ€Ņ€ĐžŅ€", "download_failed": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ĐŊĐ¸Ņ˜Đĩ ҃ҁĐŋĐĩĐģĐž", "download_finished": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ĐˇĐ°Đ˛Ņ€ŅˆĐĩĐŊĐž", "download_include_embedded_motion_videos": "ĐŖĐŗŅ€Đ°Ņ’ĐĩĐŊи видĐĩĐž ҁĐŊиĐŧŅ†Đ¸", @@ -669,6 +788,9 @@ "download_paused": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҘĐĩ ĐŋĐ°ŅƒĐˇĐ¸Ņ€Đ°ĐŊĐž", "download_settings": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ", "download_settings_description": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа вĐĩСаĐŊиĐŧ Са ĐŋŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа", + "download_started": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҘĐĩ СаĐŋĐžŅ‡ĐĩŅ‚Đž", + "download_sucess": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҘĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž", + "download_sucess_android": "МĐĩĐ´Đ¸Ņ˜Đ¸ ҁ҃ ĐŋŅ€ĐĩŅƒĐˇĐĩŅ‚Đ¸ ĐŊа ДĐĻИМ/Immich", "download_waiting_to_retry": "ЧĐĩĐēĐ°ŅšĐĩ ĐŊа ĐŋĐžĐŊОвĐŊи ĐŋĐžĐēŅƒŅˆĐ°Ņ˜", "downloading": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҃ Ņ‚ĐžĐē҃", "downloading_asset_filename": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ {filename}", @@ -695,6 +817,8 @@ "edit_title": "ĐŖŅ€Đĩди Ņ‚Đ¸Ņ‚ŅƒĐģ҃", "edit_user": "ĐŖŅ€Đĩди ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "editor": "ĐŖŅ€ĐĩĐ´ĐŊиĐē", + "editor_close_without_save_prompt": "ĐŸŅ€ĐžĐŧĐĩĐŊĐĩ ĐŊĐĩŅ›Đĩ ĐąĐ¸Ņ‚Đ¸ ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊĐĩ", + "editor_close_without_save_title": "Đ—Đ°Ņ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ŅƒŅ€ĐĩŅ’Đ¸Đ˛Đ°Ņ‡?", "email": "Е-ĐŋĐžŅˆŅ‚Đ°", "email_notifications": "ОбавĐĩŅˆŅ‚ĐĩŅšĐ° Đĩ-ĐŋĐžŅˆŅ‚ĐžĐŧ", "empty_folder": "Ова ĐŧаĐŋа ҘĐĩ ĐŋŅ€Đ°ĐˇĐŊа", @@ -706,6 +830,7 @@ "enqueued": "ĐĄŅ‚Đ°Đ˛Ņ™ĐĩĐŊĐž ҃ Ņ€ĐĩĐ´", "enter_wifi_name": "ĐŖĐŊĐĩŅĐ¸Ņ‚Đĩ ĐŊаСив Wi-Fi ĐŧŅ€ĐĩĐļĐĩ", "error": "Đ“Ņ€Đĩ҈Đēа", + "error_change_sort_album": "ĐŸŅ€ĐžĐŧĐĩĐŊа Ņ€ĐĩĐ´ĐžŅĐģĐĩда ŅĐžŅ€Ņ‚Đ¸Ņ€Đ°ŅšĐ° аĐģĐąŅƒĐŧа ĐŊĐ¸Ņ˜Đĩ ҃ҁĐŋĐĩĐģа", "error_delete_face": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐąŅ€Đ¸ŅĐ°ŅšŅƒ ĐžŅĐžĐąĐĩ иС Đ´ĐĩĐģа", "error_loading_image": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°ŅšŅƒ ҁĐģиĐēĐĩ", "error_saving_image": "Đ“Ņ€Đĩ҈Đēа: {error}", @@ -827,8 +952,16 @@ }, "exif": "Exif", "exif_bottom_sheet_description": "Đ”ĐžĐ´Đ°Ņ˜ ĐžĐŋĐ¸Ņ...", + "exif_bottom_sheet_details": "ДЕĐĸАЛЈИ", + "exif_bottom_sheet_location": "ЛОКАĐĻИЈА", + "exif_bottom_sheet_people": "ПЕОПЛЕ", + "exif_bottom_sheet_person_add_person": "Адд name", "exit_slideshow": "Đ˜ĐˇĐ°Ņ’Đ¸ иС ĐŋŅ€ĐžŅ˜ĐĩĐēŅ†Đ¸Ņ˜Đĩ ҁĐģĐ°Ņ˜Đ´ĐžĐ˛Đ°", "expand_all": "ĐŸŅ€ĐžŅˆĐ¸Ņ€Đ¸ ŅĐ˛Đĩ", + "experimental_settings_new_asset_list_subtitle": "ĐŖ Đ¸ĐˇŅ€Đ°Đ´Đ¸", + "experimental_settings_new_asset_list_title": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜ ĐĩĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊи ĐŧŅ€ĐĩĐļĐŊи ĐŋŅ€Đ¸ĐēаС Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°", + "experimental_settings_subtitle": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐŊа ŅĐžĐŋŅŅ‚Đ˛ĐĩĐŊ҃ ĐžĐ´ĐŗĐžĐ˛ĐžŅ€ĐŊĐžŅŅ‚!", + "experimental_settings_title": "ЕĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģĐŊĐž", "expire_after": "Да Đ¸ŅŅ‚ĐĩĐēĐŊĐĩ ĐŊаĐēĐžĐŊ", "expired": "Đ˜ŅŅ‚ĐĩĐēĐģĐž", "expires_date": "Đ˜ŅŅ‚Đ¸Ņ‡Đĩ {date}", @@ -848,6 +981,7 @@ "favorite": "Đ¤Đ°Đ˛ĐžŅ€Đ¸Ņ‚", "favorite_or_unfavorite_photo": "ОĐŧĐ¸Ņ™ĐĩĐŊа иĐģи ĐŊĐĩĐžĐŧĐ¸Ņ™ĐĩĐŊа Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°", "favorites": "Đ¤Đ°Đ˛ĐžŅ€Đ¸Ņ‚Đ¸", + "favorites_page_no_favorites": "ĐĐ¸Ņ˜Đĩ ĐŋŅ€ĐžĐŊĐ°Ņ’ĐĩĐŊ ĐŊĐ¸Ņ˜ĐĩдаĐŊ ĐžĐŧĐ¸Ņ™ĐĩĐŊи ĐŧĐ°Ņ‚ĐĩŅ€Đ¸Ņ˜Đ°Đģ", "feature_photo_updated": "ГĐģавĐŊа Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° ҘĐĩ аĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊа", "features": "Đ¤ŅƒĐŊĐēŅ†Đ¸Ņ˜Đĩ (Ņ„ĐĩĐ°Ņ‚ŅƒŅ€Đĩҁ)", "features_setting_description": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ Ņ„ŅƒĐŊĐēŅ†Đ¸Ņ˜Đ°Đŧа аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ", @@ -857,6 +991,7 @@ "filter": "ФиĐģŅ‚ĐĩŅ€", "filter_people": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°ŅšĐĩ ĐžŅĐžĐąĐ°", "filter_places": "ФиĐģŅ‚Ņ€Đ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ĐŧĐĩŅŅ‚Đ°", + "find_them_fast": "Đ‘Ņ€ĐˇĐž Đ¸Ņ… ĐŋŅ€ĐžĐŊĐ°Ņ’Đ¸Ņ‚Đĩ ĐŋĐž иĐŧĐĩĐŊ҃ ĐŋĐžĐŧĐžŅ›Ņƒ ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐĩ", "first": "ĐŸŅ€Đ˛Đ¸", "fix_incorrect_match": "Đ˜ŅĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đĩ ĐŊĐĩŅ‚Đ°Ņ‡ĐŊĐž ĐŋĐžĐ´ŅƒĐ´Đ°Ņ€Đ°ŅšĐĩ", "folder": "Đ¤Đ°ŅŅ†Đ¸ĐēĐģа", @@ -880,6 +1015,7 @@ "group_owner": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ĐŋĐž вĐģĐ°ŅĐŊиĐē҃", "group_places_by": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ĐŧĐĩŅŅ‚Đ° ĐŋĐž...", "group_year": "Đ“Ņ€ŅƒĐŋĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ ĐŋĐž ĐŗĐžĐ´Đ¸ĐŊи", + "haptic_feedback_switch": "ОĐŧĐžĐŗŅƒŅ›Đ¸ Ņ…Đ°ĐŋŅ‚Đ¸Ņ‡Đē҃ ĐŋĐžĐ˛Ņ€Đ°Ņ‚ĐŊ҃ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Ņƒ", "haptic_feedback_title": "ĐĨаĐŋŅ‚Đ¸Ņ‡ĐēĐĩ ĐŋĐžĐ˛Ņ€Đ°Ņ‚ĐŊĐĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đĩ", "has_quota": "ИĐŧа ĐēĐ˛ĐžŅ‚Ņƒ", "hashing": "ĐĨĐĩŅˆĐ¸Ņ€Đ°ŅšĐĩ", @@ -895,12 +1031,27 @@ "hide_password": "ХаĐēŅ€Đ¸Ņ˜ ĐģОСиĐŊĐē҃", "hide_person": "ХаĐēŅ€Đ¸Ņ˜ ĐžŅĐžĐąŅƒ", "hide_unnamed_people": "ХаĐēŅ€Đ¸Ņ˜ ĐŊĐĩиĐŧĐĩĐŊОваĐŊĐĩ ĐžŅĐžĐąĐĩ", + "home_page_add_to_album_conflicts": "Đ”ĐžĐ´Đ°Ņ‚ {added} СаĐŋĐ¸Ņ ҃ аĐģĐąŅƒĐŧ {album}. {failed} СаĐŋĐ¸ŅĐ¸ ҁ҃ вĐĩŅ› ҃ аĐģĐąŅƒĐŧ҃.", + "home_page_add_to_album_err_local": "ĐĸŅ€ĐĩĐŊŅƒŅ‚ĐŊĐž ĐŊĐĩĐŧĐžĐŗŅƒŅ›Đĩ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģĐŊĐĩ СаĐŋĐ¸ŅĐĩ ҃ аĐģĐąŅƒĐŧĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ†Ņƒ ҁĐĩ", + "home_page_add_to_album_success": "Доdate {added} ŅŅ‚Đ°Đ˛ĐēĐĩ ҃ аĐģĐąŅƒĐŧ {album}.", + "home_page_album_err_partner": "ĐˆĐžŅˆ ŅƒĐ˛ĐĩĐē ĐŊĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐēа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ҃ аĐģĐąŅƒĐŧ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "home_page_archive_err_local": "ĐˆĐžŅˆ ŅƒĐ˛ĐĩĐē ĐŊĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģĐŊĐĩ Ņ€ĐĩŅŅƒŅ€ŅĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "home_page_archive_err_partner": "НĐĩ ĐŧĐžĐŗŅƒ да Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°Đŧ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐē҃ иĐŧОвиĐŊ҃, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", "home_page_building_timeline": "ĐšŅ€ĐĩĐ¸Ņ€Đ°ŅšĐĩ Ņ…Ņ€ĐžĐŊĐžĐģĐžŅˆĐēĐĩ ĐģиĐŊĐ¸Ņ˜Đĩ", + "home_page_delete_err_partner": "НĐĩ ĐŧĐžĐŗŅƒ да ĐžĐąŅ€Đ¸ŅˆĐĩĐŧ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐē҃ иĐŧОвиĐŊ҃, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "home_page_delete_remote_err_local": "ЛоĐēаĐģĐŊа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ҃ ĐžĐąŅ€Đ¸ŅĐ°Đ˛Đ°ŅšŅƒ ŅƒĐ´Đ°Ņ™ĐĩĐŊĐžĐŗ Đ¸ĐˇĐąĐžŅ€Đ°, ĐŋŅ€ĐĩҁĐēаĐēĐ°ŅšĐĩ", + "home_page_favorite_err_local": "ĐĸŅ€ĐĩĐŊŅƒŅ‚ĐŊĐž ĐŊĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ†Đĩ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģĐŊĐĩ СаĐŋĐ¸ŅĐĩ ҃ Ņ„Đ°Đ˛ĐžŅ€Đ¸Ņ‚Đĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ†Ņƒ ҁĐĩ", + "home_page_favorite_err_partner": "ĐˆĐžŅˆ ŅƒĐ˛ĐĩĐē ĐŊĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ ОСĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐēĐĩ Ņ€ĐĩŅŅƒŅ€ŅĐĩ ĐēаО ĐžĐŧĐ¸Ņ™ĐĩĐŊĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "home_page_first_time_notice": "АĐēĐž ҘĐĩ ОвО ĐŋŅ€Đ˛Đ¸ ĐŋŅƒŅ‚ да ĐēĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Ņƒ, ĐŧĐžĐģиĐŧĐž Đ’Đ°Ņ да ОдайĐĩŅ€ĐĩŅ‚Đĩ аĐģĐąŅƒĐŧĐĩ ĐēĐžŅ˜Đĩ ĐļĐĩĐģĐ¸Ņ‚Đĩ да ŅĐ°Ņ‡ŅƒĐ˛Đ°Ņ‚Đĩ", + "home_page_share_err_local": "НĐĩ ĐŧĐžĐŗŅƒ да Đ´ĐĩĐģиĐŧ ĐģĐžĐēаĐģĐŊĐĩ Ņ€ĐĩŅŅƒŅ€ŅĐĩ ĐŋŅ€ĐĩĐēĐž linkа, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "home_page_upload_err_limit": "МоĐļĐĩŅ‚Đĩ ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ¸Ņ‚Đ¸ ĐŊĐ°Ņ˜Đ˛Đ¸ŅˆĐĩ 30 ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° Đ¸ŅŅ‚ĐžĐ˛Ņ€ĐĩĐŧĐĩĐŊĐž, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ŅƒŅ›Đ¸", "host": "ДоĐŧĐ°Ņ›Đ¸ĐŊ (ĐĨĐžŅŅ‚)", "hour": "ĐĄĐ°Ņ‚", "hours": "ĐĄĐ°Ņ‚Đ¸", "id": "ИД", "idle": "НĐĩаĐēŅ‚Đ¸Đ˛ĐŊĐž", + "ignore_icloud_photos": "Đ˜ĐŗĐŊĐžŅ€Đ¸ŅˆĐ¸Ņ‚Đĩ иĐĻĐģĐžŅƒĐ´ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", + "ignore_icloud_photos_description": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ĐēĐžŅ˜Đĩ ҁ҃ ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊĐĩ ĐŊа иĐĻĐģĐžŅƒĐ´-҃ ĐŊĐĩŅ›Đĩ ĐąĐ¸Ņ‚Đ¸ ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐĩ ĐŊа Immich ҁĐĩŅ€Đ˛ĐĩŅ€", "image": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°", "image_alt_text_date": "{isVideo, select, true {ВидĐĩĐž} other {Image}} ҁĐŊиĐŧŅ™ĐĩĐŊĐž {date}", "image_alt_text_date_1_person": "{isVideo, select, true {ВидĐĩĐž} other {Image}} ҁĐŊиĐŧŅ™ĐĩĐŊĐž ŅĐ° {person1} {date}", @@ -912,6 +1063,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {ВидĐĩĐž} other {Image}} ҁĐŊиĐŧŅ™ĐĩĐŊĐž ҃ {city}, {country} ŅĐ° {person1} и {person2} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {ВидĐĩĐž} other {Image}} ҁĐŊиĐŧŅ™ĐĩĐŊĐžŅƒ {city}, {country} ŅĐ° {person1}, {person2} и {person3} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {ВидĐĩĐž} other {Image}} ҁĐŊиĐŧŅ™ĐĩĐŊĐž ҃ {city}, {country} ŅĐ° {person1}, {person2} и Ņ˜ĐžŅˆ {additionalCount, number} Đ´Ņ€ŅƒĐŗĐ¸Ņ… {date}", + "image_saved_successfully": "ĐĄĐģиĐēа ҘĐĩ ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊа", + "image_viewer_page_state_provider_download_started": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ҘĐĩ СаĐŋĐžŅ‡ĐĩŅ‚Đž", + "image_viewer_page_state_provider_download_success": "ĐŸŅ€ĐĩŅƒĐˇĐ¸ĐŧĐ°ŅšĐĩ ĐŖŅĐŋĐĩ҈ĐŊĐž", + "image_viewer_page_state_provider_share_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ Đ´ĐĩŅ™ĐĩҚ҃", "immich_logo": "Đ›ĐžĐŗĐž Immich-a", "immich_web_interface": "WĐĩĐą иĐŊŅ‚ĐĩҀ҄ĐĩҘҁ Immich-a", "import_from_json": "ĐŖĐ˛ĐĩСи иС ЈСОН-а", @@ -934,6 +1089,7 @@ "invalid_date_format": "НĐĩваĐļĐĩŅ›Đ¸ Ņ„ĐžŅ€ĐŧĐ°Ņ‚ Đ´Đ°Ņ‚ŅƒĐŧа", "invite_people": "ĐŸĐžĐˇĐžĐ˛Đ¸Ņ‚Đĩ Ņ™ŅƒĐ´Đĩ", "invite_to_album": "Позови ĐŊа аĐģĐąŅƒĐŧ", + "ios_debug_info_fetch_ran_at": "Đ”ĐžŅ…Đ˛Đ°Ņ‚Đ°ŅšĐĩ ĐŋĐžĐēŅ€ĐĩĐŊŅƒŅ‚Đž {dateTime}", "items_count": "{count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", "jobs": "ĐŸĐžŅĐģОви", "keep": "Đ—Đ°Đ´Ņ€Đļи", @@ -952,6 +1108,9 @@ "let_others_respond": "ДозвоĐģи да Đ´Ņ€ŅƒĐŗĐ¸ ĐēĐžĐŧĐĩĐŊŅ‚Đ°Ņ€Đ¸ŅˆŅƒ", "level": "Ниво", "library": "БибĐģĐ¸ĐžŅ‚ĐĩĐēа", + "library_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēĐĩ", + "library_page_device_albums": "АĐģĐąŅƒĐŧи ĐŊа ŅƒŅ€ĐĩŅ’Đ°Ņ˜Ņƒ", + "library_page_new_album": "Нови аĐģĐąŅƒĐŧ", "library_page_sort_asset_count": "Đ‘Ņ€ĐžŅ˜ ҁҀĐĩĐ´ŅŅ‚Đ°Đ˛Đ°", "library_page_sort_created": "ĐĐ°Ņ˜ĐŊĐžĐ˛Đ¸Ņ˜Đĩ ĐēŅ€ĐĩĐ¸Ņ€Đ°ĐŊĐž", "library_page_sort_last_modified": "ĐŸĐžŅĐģĐĩĐ´ŅšĐ° иСĐŧĐĩĐŊа", @@ -985,9 +1144,11 @@ "login": "ĐŸŅ€Đ¸Ņ˜Đ°Đ˛Đ°", "login_disabled": "ĐŸŅ€Đ¸Ņ˜Đ°Đ˛Đ° ҘĐĩ ĐžĐŊĐĩĐŧĐžĐŗŅƒŅ›ĐĩĐŊа", "login_form_api_exception": "Đ˜ĐˇŅƒĐˇĐĩŅ‚Đ°Đē АПИ-Ņ˜Đ°. МоĐģиĐŧĐž Đ˛Đ°Ņ да ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸Ņ‚Đĩ URL Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° и ĐŋĐžĐēŅƒŅˆĐ°Ņ‚Đĩ ĐŋĐžĐŊОвО.", + "login_form_back_button_text": "Назад", "login_form_email_hint": "Đ˛Đ°Ņˆemail@email.Ņ†ĐžĐŧ", "login_form_endpoint_hint": "҅҂҂Đŋ://иĐŋ-Đ˛Đ°ŅˆĐĩĐŗ-ҁĐĩŅ€Đ˛ĐĩŅ€Đ°:ĐŋĐžŅ€Ņ‚", "login_form_endpoint_url": "URL ĐĄĐĩŅ€Đ˛ĐĩŅ€Đ°", + "login_form_err_http": "ДоĐŋĐ¸ŅˆĐ¸ ҅҂҂Đŋ:// иĐģи ҅҂҂Đŋҁ://", "login_form_err_invalid_email": "НĐĩваĐļĐĩŅ›Đ¸ ЕĐŧаиĐģ", "login_form_err_invalid_url": "НĐĩ ваĐļĐĩŅ›Đ¸ link (URL)", "login_form_err_leading_whitespace": "РаСĐŧаĐē Đ¸ŅĐŋŅ€ĐĩĐ´", @@ -997,6 +1158,7 @@ "login_form_failed_login": "НĐĩ҃ҁĐŋĐĩ҈ĐŊа ĐŋŅ€Đ¸Ņ˜Đ°Đ˛Đ°, ĐŋŅ€ĐžĐ˛ĐĩŅ€Đ¸ URL ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, email и ŅˆĐ¸Ņ„Ņ€Ņƒ", "login_form_handshake_exception": "Đ”ĐžŅˆĐģĐž ҘĐĩ Đ´Đž Đ¸ĐˇŅƒĐˇĐĩŅ‚Đēа Ņ€ŅƒĐēĐžŅŅ‚Đ¸ŅĐēĐ°ŅšĐ° ŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ. ОĐŧĐžĐŗŅƒŅ›Đ¸Ņ‚Đĩ ĐŋĐžĐ´Ņ€ŅˆĐē҃ Са ŅĐ°ĐŧĐžĐŋĐžŅ‚ĐŋĐ¸ŅĐ°ĐŊĐĩ ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚Đĩ ҃ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа аĐēĐž ĐēĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ ŅĐ°ĐŧĐžĐŋĐžŅ‚ĐŋĐ¸ŅĐ°ĐŊи ҁĐĩŅ€Ņ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ‚.", "login_form_password_hint": "ŅˆĐ¸Ņ„Ņ€Đ°", + "login_form_save_login": "ĐžŅŅ‚Đ°ĐŊи ĐŋŅ€Đ¸Ņ˜Đ°Đ˛Ņ™ĐĩĐŊ", "login_form_server_empty": "ЕĐŊŅ‚ĐĩŅ€ а ҁĐĩŅ€Đ˛ĐĩŅ€ URL.", "login_form_server_error": "ĐĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ ĐŋОвĐĩĐˇĐ°Ņ‚Đ¸ ҁĐĩ ŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ.", "login_has_been_disabled": "ĐŸŅ€Đ¸Ņ˜Đ°Đ˛Đ° ҘĐĩ ĐžĐŊĐĩĐŧĐžĐŗŅƒŅ›ĐĩĐŊа.", @@ -1012,6 +1174,7 @@ "main_branch_warning": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸Ņˆ Ņ€Đ°ĐˇĐ˛ĐžŅ˜ĐŊ҃ вĐĩŅ€ĐˇĐ¸Ņ˜Ņƒ; ŅŅ‚Ņ€ĐžĐŗĐž ĐŋŅ€ĐĩĐŋĐžŅ€ŅƒŅ‡ŅƒŅ˜ĐĩĐŧĐž ҃ĐŋĐžŅ‚Ņ€ĐĩĐąŅƒ Đ¸ĐˇĐ´Đ°Ņ‚Đĩ вĐĩŅ€ĐˇĐ¸Ņ˜Đĩ!", "main_menu": "ГĐģавĐŊи ĐŧĐĩĐŊи", "make": "ĐšŅ€ĐĩĐ¸Ņ€Đ°Ņ˜", + "manage_shared_links": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ Đ´ĐĩŅ™ĐĩĐŊиĐŧ вĐĩСаĐŧа", "manage_sharing_with_partners": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ Đ´ĐĩŅ™ĐĩҚĐĩĐŧ ŅĐ° ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ¸Đŧа", "manage_the_app_settings": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ", "manage_your_account": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ Đ˛Đ°ŅˆĐ¸Đŧ ĐŋŅ€ĐžŅ„Đ¸ĐģĐžĐŧ", @@ -1019,10 +1182,13 @@ "manage_your_devices": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ŅĐ˛ĐžŅ˜Đ¸Đŧ ĐŋŅ€Đ¸Ņ˜Đ°Đ˛Ņ™ĐĩĐŊиĐŧ ŅƒŅ€ĐĩŅ’Đ°Ņ˜Đ¸Đŧа", "manage_your_oauth_connection": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ ŅĐ˛ĐžŅ˜ĐžĐŧ OAuth вĐĩСОĐŧ", "map": "МаĐŋа", + "map_assets_in_bounds": "{count, plural, =0 {НĐĩĐŧа Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° ҃ ОвОĐŧ ĐŋĐžĐ´Ņ€ŅƒŅ‡Ņ˜Ņƒ} one {# Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°} few {# Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ} other {# Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°}}", "map_cannot_get_user_location": "ĐĐ¸Ņ˜Đĩ ĐŧĐžĐŗŅƒŅ›Đĩ Đ´ĐžĐąĐ¸Ņ‚Đ¸ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ ĐēĐžŅ€Đ¸ŅĐŊиĐēа", + "map_location_dialog_yes": "Да", "map_location_picker_page_use_location": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ ĐžĐ˛Ņƒ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", "map_location_service_disabled_content": "ĐŖŅĐģŅƒĐŗĐ° ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đĩ ĐŧĐžŅ€Đ° ĐąĐ¸Ņ‚Đ¸ ĐžĐŧĐžĐŗŅƒŅ›ĐĩĐŊа да йи ҁĐĩ ĐŋŅ€Đ¸ĐēаСиваĐģа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ŅĐ° Đ˛Đ°ŅˆĐĩ ҂ҀĐĩĐŊŅƒŅ‚ĐŊĐĩ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đĩ. Да Đģи ĐļĐĩĐģĐ¸Ņ‚Đĩ да ҘĐĩ ŅĐ°Đ´Đ° ĐžĐŧĐžĐŗŅƒŅ›Đ¸Ņ‚Đĩ?", "map_location_service_disabled_title": "ĐŖŅĐģŅƒĐŗĐ° ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đĩ ҘĐĩ ĐžĐŊĐĩĐŧĐžĐŗŅƒŅ›ĐĩĐŊа", + "map_marker_for_images": "ОзĐŊĐ°Ņ‡Đ¸Đ˛Đ°Ņ‡ ĐŊа ĐŧаĐŋи Са ҁĐģиĐēĐĩ ҁĐŊиĐŧŅ™ĐĩĐŊĐĩ ҃ {city}, {country}", "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐŧаĐŋи ŅĐ° ҁĐģиĐēĐžĐŧ", "map_no_location_permission_content": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐŊа ҘĐĩ дОСвОĐģа Са ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ да йи ҁĐĩ ĐŋŅ€Đ¸ĐēаСаĐģи Ņ€ĐĩŅŅƒŅ€ŅĐ¸ ŅĐ° Đ˛Đ°ŅˆĐĩ ҂ҀĐĩĐŊŅƒŅ‚ĐŊĐĩ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Đĩ. Да Đģи ĐļĐĩĐģĐ¸Ņ‚Đĩ да ҘĐĩ ŅĐ°Đ´Đ° дОСвОĐģĐ¸Ņ‚Đĩ?", "map_no_location_permission_title": "ДозвоĐģа Са ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ ҘĐĩ ĐžĐ´ĐąĐ¸Ņ˜ĐĩĐŊа", @@ -1032,11 +1198,14 @@ "map_settings_date_range_option_days": "ĐŸŅ€ĐĩŅ‚Ņ…ĐžĐ´ĐŊĐ¸Ņ… {days} даĐŊа", "map_settings_date_range_option_year": "ĐŸŅ€ĐžŅˆĐģа ĐŗĐžĐ´Đ¸ĐŊа", "map_settings_date_range_option_years": "ĐŸŅ€ĐžŅ‚ĐĩĐēĐģĐ¸Ņ… {years} ĐŗĐžĐ´Đ¸ĐŊа", + "map_settings_dialog_title": "ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ° МаĐŋĐĩ", "map_settings_include_show_archived": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊĐž", "map_settings_include_show_partners": "ĐŖĐēŅ™ŅƒŅ‡Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đĩ", "map_settings_only_show_favorites": "ĐŸŅ€Đ¸ĐēаĐļи ŅĐ°ĐŧĐž ĐžĐŧĐ¸Ņ™ĐĩĐŊĐĩ", "map_settings_theme_settings": "ĐĸĐĩĐŧа ĐŧаĐŋĐĩ", + "map_zoom_to_see_photos": "ĐŖĐŧĐ°ŅšĐ¸Ņ‚Đĩ да ĐąĐ¸ŅŅ‚Đĩ видĐĩĐģи Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", "mark_all_as_read": "ОзĐŊĐ°Ņ‡Đ¸ ŅĐ˛Đĩ ĐēаО ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐž", + "mark_as_read": "ОзĐŊĐ°Ņ‡Đ¸ ĐēаО ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐž", "marked_all_as_read": "ХвĐĩ ҘĐĩ ОСĐŊĐ°Ņ‡ĐĩĐŊĐž ĐēаО ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐž", "matches": "ĐŸĐžĐ´ŅƒĐ´Đ°Ņ€Đ°ŅšĐ°", "media_type": "Đ’Ņ€ŅŅ‚Đ° ĐŧĐĩĐ´Đ¸Ņ˜Đ°", @@ -1061,9 +1230,14 @@ "missing": "НĐĩĐ´ĐžŅŅ‚Đ°Ņ˜Đĩ", "model": "МодĐĩĐģ", "month": "МĐĩҁĐĩ҆", + "monthly_title_text_date_format": "ММММ y", "more": "Đ’Đ¸ŅˆĐĩ", "move": "ĐŸŅ€ĐĩĐŧĐĩŅŅ‚Đ¸", + "moved_to_archive": "ĐŸŅ€ĐĩĐŧĐĩŅˆŅ‚ĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}} ҃ Đ°Ņ€Ņ…Đ¸Đ˛Ņƒ", + "moved_to_library": "ĐŸŅ€ĐĩĐŧĐĩŅˆŅ‚ĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}} ҃ йийĐģĐ¸ĐžŅ‚ĐĩĐē҃", "moved_to_trash": "ĐŸŅ€ĐĩĐŧĐĩŅˆŅ‚ĐĩĐŊĐž ҃ ҁĐŧĐĩŅ›Đĩ", + "multiselect_grid_edit_date_time_err_read_only": "НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да иСĐŧĐĩĐŊĐ¸Ņ‚Đĩ Đ´Đ°Ņ‚ŅƒĐŧ ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ŅĐ°ĐŧĐž Са Ņ‡Đ¸Ņ‚Đ°ŅšĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", + "multiselect_grid_edit_gps_err_read_only": "НĐĩ ĐŧĐžĐŗŅƒ да иСĐŧĐĩĐŊиĐŧ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ŅĐ°ĐŧĐž Са Ņ‡Đ¸Ņ‚Đ°ŅšĐĩ, ĐŋŅ€ĐĩҁĐēĐ°Ņ‡ĐĩĐŧ", "mute_memories": "ĐŸŅ€Đ¸ĐŗŅƒŅˆĐ¸ ҁĐĩŅ›Đ°ŅšĐ°", "my_albums": "ĐœĐžŅ˜Đ¸ аĐģĐąŅƒĐŧи", "name": "ИĐŧĐĩ", @@ -1143,9 +1317,12 @@ "partner_can_access_assets": "ХвĐĩ Đ˛Đ°ŅˆĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž ҁĐŊиĐŧŅ†Đ¸ ĐžŅĐ¸Đŧ ĐžĐŊĐ¸Ņ… ҃ Đ°Ņ€Ņ…Đ¸Đ˛Đ¸Ņ€Đ°ĐŊиĐŧ и Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊиĐŧ", "partner_can_access_location": "ЛоĐēĐ°Ņ†Đ¸Ņ˜Đ° ĐŊа ĐēĐžŅ˜ĐžŅ˜ ҁ҃ Đ˛Đ°ŅˆĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ҁĐŊиĐŧŅ™ĐĩĐŊĐĩ", "partner_list_user_photos": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ĐēĐžŅ€Đ¸ŅĐŊиĐēа {user}", + "partner_list_view_all": "ĐŸŅ€Đ¸ĐēаĐļи ŅĐ˛Đĩ", "partner_page_empty_message": "Đ’Đ°ŅˆĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ Ņ˜ĐžŅˆ ŅƒĐ˛ĐĩĐē ĐŊĐ¸ŅŅƒ Đ´ĐĩŅ™ĐĩĐŊĐĩ ĐŊи ŅĐ° ҘĐĩĐ´ĐŊиĐŧ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€ĐžĐŧ.", "partner_page_no_more_users": "НĐĩĐŧа Đ˛Đ¸ŅˆĐĩ ĐēĐžŅ€Đ¸ŅĐŊиĐēа Са Đ´ĐžĐ´Đ°Đ˛Đ°ŅšĐĩ", + "partner_page_partner_add_failed": "Đ”ĐžĐ´Đ°Đ˛Đ°ŅšĐĩ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ° ĐŊĐ¸Ņ˜Đĩ ҃ҁĐŋĐĩĐģĐž", "partner_page_select_partner": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", + "partner_page_shared_to_title": "ДĐĩŅ™ĐĩĐŊĐž ŅĐ°", "partner_page_stop_sharing_content": "{partner} Đ˛Đ¸ŅˆĐĩ ĐŊĐĩŅ›Đĩ ĐŧĐžŅ›Đ¸ да ĐŋŅ€Đ¸ŅŅ‚ŅƒĐŋи Đ˛Đ°ŅˆĐ¸Đŧ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°Đŧа.", "partner_sharing": "ĐŸĐ°Ņ€Ņ‚ĐŊĐĩҀҁĐēĐž Đ´ĐĩŅ™ĐĩҚĐĩ", "partners": "ĐŸĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ¸", @@ -1167,6 +1344,7 @@ "people": "ĐžŅĐžĐąĐĩ", "people_edits_count": "ИСĐŧĐĩҚĐĩĐŊĐž {count, plural, one {# ĐžŅĐžĐąĐ°} other {# ĐžŅĐžĐąĐĩ}}", "people_feature_description": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´Đ°Đ˛Đ°ŅšĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° и видĐĩĐž ҁĐŊиĐŧаĐēа ĐŗŅ€ŅƒĐŋĐ¸ŅĐ°ĐŊĐ¸Ņ… ĐŋĐž ĐžŅĐžĐąĐ°Đŧа", + "people_sidebar_description": "ĐŸŅ€Đ¸ĐēаĐļĐ¸Ņ‚Đĩ вĐĩĐˇŅƒ Đ´Đž ĐžŅĐžĐąĐ° ĐŊа ĐąĐžŅ‡ĐŊĐžŅ˜ Ņ‚Ņ€Đ°Ņ†Đ¸", "permanent_deletion_warning": "ĐŖĐŋĐžĐˇĐžŅ€ĐĩҚĐĩ Са Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ĐąŅ€Đ¸ŅĐ°ŅšĐĩ", "permanent_deletion_warning_setting_description": "ĐŸŅ€Đ¸ĐēаĐļи ҃ĐŋĐžĐˇĐžŅ€ĐĩҚĐĩ Đēада Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ", "permanently_delete": "ĐĸŅ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°Ņ‚Đ¸", @@ -1175,12 +1353,21 @@ "permanently_deleted_asset": "ĐĸŅ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊа Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа", "permanently_deleted_assets_count": "ĐĸŅ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}}", "permission": "ДозвоĐģа", + "permission_onboarding_back": "Назад", + "permission_onboarding_continue_anyway": "ИĐŋаĐē ĐŊĐ°ŅŅ‚Đ°Đ˛Đ¸", + "permission_onboarding_get_started": "ЗаĐŋĐžŅ‡ĐŊĐ¸Ņ‚Đĩ", + "permission_onboarding_go_to_settings": "Иди ĐŊа ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", + "permission_onboarding_permission_denied": "ДозвоĐģа ĐžĐ´ĐąĐ¸Ņ˜ĐĩĐŊа. Да ĐąĐ¸ŅŅ‚Đĩ ĐēĐžŅ€Đ¸ŅŅ‚Đ¸Đģи Immich, дОдĐĩĐģĐ¸Ņ‚Đĩ дОСвОĐģĐĩ Са Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ ҃ ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа.", + "permission_onboarding_permission_granted": "ДозвоĐģа ĐžĐ´ĐžĐąŅ€ĐĩĐŊа! ĐĄĐŋŅ€ĐĩĐŧĐŊи ҁ҂Đĩ.", + "permission_onboarding_permission_limited": "ДозвоĐģа ĐžĐŗŅ€Đ°ĐŊĐ¸Ņ‡ĐĩĐŊа. Да ĐąĐ¸ŅŅ‚Đĩ ĐžĐŧĐžĐŗŅƒŅ›Đ¸Đģи Immich-u да ĐŋŅ€Đ°Đ˛Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋĐ¸Ņ˜Đĩ и ҃ĐŋŅ€Đ°Đ˛Ņ™Đ° ҆ĐĩĐģĐžĐŧ Đ˛Đ°ŅˆĐžĐŧ ĐēĐžĐģĐĩĐēŅ†Đ¸Ņ˜ĐžĐŧ ĐŗĐ°ĐģĐĩŅ€Đ¸Ņ˜Đĩ, дОдĐĩĐģĐ¸Ņ‚Đĩ дОСвОĐģĐĩ Са Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ ҃ ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ¸Đŧа.", + "permission_onboarding_request": "Immich ĐˇĐ°Ņ…Ņ‚Đĩва дОСвОĐģ҃ да види Đ˛Đ°ŅˆĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ.", "person": "ĐžŅĐžĐąĐ°", "person_birthdate": "Đ ĐžŅ’ĐĩĐŊ(а) {date}", "person_hidden": "{name}{hidden, select, true { (ҁĐēŅ€Đ¸Đ˛ĐĩĐŊĐž)} other {}}", "photo_shared_all_users": "Đ˜ĐˇĐŗĐģĐĩда да ҁ҂Đĩ ĐŋОдĐĩĐģиĐģи ŅĐ˛ĐžŅ˜Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ŅĐ° ŅĐ˛Đ¸Đŧ ĐēĐžŅ€Đ¸ŅĐŊĐ¸Ņ†Đ¸Đŧа иĐģи да ĐŊĐĩĐŧĐ°Ņ‚Đĩ ĐŊĐ¸Ņ˜ĐĩĐ´ĐŊĐžĐŗ ĐēĐžŅ€Đ¸ŅĐŊиĐēа ŅĐ° ĐēĐžŅ˜Đ¸Đŧ ĐąĐ¸ŅŅ‚Đĩ Đ´ĐĩĐģиĐģи.", "photos": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", "photos_and_videos": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ & ВидĐĩĐž СаĐŋĐ¸ŅĐ¸", + "photos_count": "{count, plural, one {{count, number} Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°} few {{count, number} Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ} other {{count, number} Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ°}}", "photos_from_previous_years": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ иС ĐŋŅ€ĐĩŅ‚Ņ…ĐžĐ´ĐŊĐ¸Ņ… ĐŗĐžĐ´Đ¸ĐŊа", "pick_a_location": "ОдабĐĩŅ€Đ¸ ĐģĐžĐēĐ°Ņ†Đ¸Ņ˜Ņƒ", "pin_code_changed_successfully": "ПИН ĐēОд ҘĐĩ ҃ҁĐŋĐĩ҈ĐŊĐž ĐŋŅ€ĐžĐŧĐĩҚĐĩĐŊ", @@ -1209,6 +1396,7 @@ "privacy": "ĐŸŅ€Đ¸Đ˛Đ°Ņ‚ĐŊĐžŅŅ‚", "profile": "ĐŸŅ€ĐžŅ„Đ¸Đģ", "profile_drawer_app_logs": "ЕвидĐĩĐŊŅ†Đ¸Ņ˜Đ°", + "profile_drawer_client_server_up_to_date": "КĐģĐ¸Ņ˜ĐĩĐŊŅ‚ и ҁĐĩŅ€Đ˛ĐĩŅ€ ҁ҃ ĐŊĐ°Ņ˜ĐŊĐžĐ˛Đ¸Ņ˜Đĩ вĐĩŅ€ĐˇĐ¸Ņ˜Đĩ", "profile_drawer_github": "Đ“Đ¸Ņ‚ĐĨŅƒĐą", "profile_image_of_user": "ĐĄĐģиĐēа ĐŋŅ€ĐžŅ„Đ¸Đģа Од ĐēĐžŅ€Đ¸ŅĐŊиĐēа {user}", "profile_picture_set": "ĐŸŅ€ĐžŅ„Đ¸ĐģĐŊа ҁĐģиĐēа ĐŋĐžŅŅ‚Đ°Đ˛Ņ™ĐĩĐŊа.", @@ -1230,6 +1418,7 @@ "purchase_individual_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŋĐžĐ´Ņ€ŅˆĐēĐĩ", "purchase_individual_title": "ИĐŊĐ´Đ¸Đ˛Đ¸Đ´ŅƒĐ°ĐģĐŊа ĐģĐ¸Ņ†ĐĩĐŊŅ†Đ°", "purchase_input_suggestion": "ИĐŧĐ°Ņ‚Đĩ ĐēŅ™ŅƒŅ‡ ĐŋŅ€ĐžĐ¸ĐˇĐ˛ĐžĐ´Đ°? ĐŖĐŊĐĩŅĐ¸Ņ‚Đĩ ĐēŅ™ŅƒŅ‡ Đ¸ŅĐŋОд", + "purchase_license_subtitle": "ĐšŅƒĐŋĐ¸Ņ‚Đĩ Immich да ĐąĐ¸ŅŅ‚Đĩ ĐŋĐžĐ´Ņ€ĐļаĐģи ĐēĐžĐŊŅ‚Đ¸ĐŊŅƒĐ¸Ņ€Đ°ĐŊи Ņ€Đ°ĐˇĐ˛ĐžŅ˜ ҃ҁĐģŅƒĐŗĐĩ", "purchase_lifetime_description": "ДоĐļĐ¸Đ˛ĐžŅ‚ĐŊа ĐģĐ¸Ņ†ĐĩĐŊŅ†Đ°", "purchase_option_title": "ОПĐĻИЈЕ ĐšĐŖĐŸĐžĐ’Đ˜ĐĐ•", "purchase_panel_info_1": "Đ˜ĐˇĐŗŅ€Đ°Đ´ŅšĐ° Immich-a ĐˇĐ°Ņ…Ņ‚Đĩва ĐŧĐŊĐžĐŗĐž Đ˛Ņ€ĐĩĐŧĐĩĐŊа и Ņ‚Ņ€ŅƒĐ´Đ°, а иĐŧаĐŧĐž иĐŊĐļĐĩҚĐĩŅ€Đĩ ĐēĐžŅ˜Đ¸ Ņ€Đ°Đ´Đĩ ĐŊа Ņ‚ĐžĐŧĐĩ ŅĐ° Đŋ҃ĐŊиĐŧ Ņ€Đ°Đ´ĐŊиĐŧ Đ˛Ņ€ĐĩĐŧĐĩĐŊĐžĐŧ ĐēаĐēĐž ĐąĐ¸ŅĐŧĐž ҘĐĩ ŅƒŅ‡Đ¸ĐŊиĐģи ŅˆŅ‚Đž ҘĐĩ ĐŧĐžĐŗŅƒŅ›Đĩ ĐąĐžŅ™ĐžĐŧ. ĐĐ°ŅˆĐ° ĐŧĐ¸ŅĐ¸Ņ˜Đ° ҘĐĩ да ŅĐžŅ„Ņ‚Đ˛ĐĩŅ€ ĐžŅ‚Đ˛ĐžŅ€ĐĩĐŊĐžĐŗ ĐēОда и ĐĩŅ‚Đ¸Ņ‡ĐēĐĩ ĐŋĐžŅĐģОвĐŊĐĩ ĐŋŅ€Đ°ĐēҁĐĩ ĐŋĐžŅŅ‚Đ°ĐŊ҃ ĐžĐ´Ņ€Đļив Đ¸ĐˇĐ˛ĐžŅ€ ĐŋŅ€Đ¸Ņ…ĐžĐ´Đ° Са ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐĩŅ€Đĩ и да ŅŅ‚Đ˛ĐžŅ€Đ¸ĐŧĐž ĐĩĐēĐžŅĐ¸ŅŅ‚ĐĩĐŧ ĐēĐžŅ˜Đ¸ ĐŋĐžŅˆŅ‚ŅƒŅ˜Đĩ ĐŋŅ€Đ¸Đ˛Đ°Ņ‚ĐŊĐžŅŅ‚ ŅĐ° ŅŅ‚Đ˛Đ°Ņ€ĐŊиĐŧ аĐģŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛Đ°Đŧа ĐĩĐēҁĐŋĐģĐžĐ°Ņ‚Đ°Ņ‚Đ¸Đ˛ĐŊиĐŧ ҃ҁĐģŅƒĐŗĐ°Đŧа ҃ ОйĐģаĐē҃.", @@ -1256,16 +1445,19 @@ "reassigned_assets_to_new_person": "ПоĐŊОвО дОдĐĩŅ™ĐĩĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}} ĐŊĐžĐ˛ĐžŅ˜ ĐžŅĐžĐąĐ¸", "reassing_hint": "ДодĐĩĐģĐ¸Ņ‚Đĩ Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ° ĐŋĐžŅŅ‚ĐžŅ˜ĐĩŅ›ĐžŅ˜ ĐžŅĐžĐąĐ¸", "recent": "ĐĄĐēĐžŅ€Đ°ŅˆŅšĐ¸", + "recent_albums": "НĐĩдавĐŊи аĐģĐąŅƒĐŧи", "recent_searches": "ĐĄĐēĐžŅ€Đ°ŅˆŅšĐĩ ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐĩ", "recently_added": "НĐĩдавĐŊĐž Đ´ĐžĐ´Đ°Ņ‚Đž", "recently_added_page_title": "НĐĩдавĐŊĐž Đ”ĐžĐ´Đ°Ņ‚Đž", "recently_taken": "НĐĩдавĐŊĐž ҁĐŊиĐŧŅ™ĐĩĐŊĐž", + "recently_taken_page_title": "НĐĩдавĐŊĐž ĐĄĐŊиĐŧŅ™ĐĩĐŊĐž", "refresh": "ĐžŅĐ˛ĐĩĐļи", "refresh_encoded_videos": "ĐžŅĐ˛ĐĩĐļĐ¸Ņ‚Đĩ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐĩ (ĐĩĐŊcodeĐ´) видĐĩĐž СаĐŋĐ¸ŅĐĩ", "refresh_faces": "ĐžŅĐ˛ĐĩĐļи ĐģĐ¸Ņ†Đ°", "refresh_metadata": "ĐžŅĐ˛ĐĩĐļĐ¸Ņ‚Đĩ ĐŧĐĩŅ‚Đ°ĐŋĐžĐ´Đ°Ņ‚ĐēĐĩ", "refresh_thumbnails": "ĐžŅĐ˛ĐĩĐļĐ¸Ņ‚Đĩ ҁĐģĐ¸Ņ‡Đ¸Ņ†Đĩ", "refreshed": "ĐžŅĐ˛ĐĩĐļĐĩĐŊĐž", + "refreshes_every_file": "ПоĐŊОвО Ņ‡Đ¸Ņ‚Đ° ŅĐ˛Đĩ ĐŋĐžŅŅ‚ĐžŅ˜ĐĩŅ›Đĩ и ĐŊОвĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ", "refreshing_encoded_video": "ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ ĐēĐžĐ´Đ¸Ņ€Đ°ĐŊĐžĐŗ (ĐĩĐŊcodeĐ´) видĐĩа", "refreshing_faces": "ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ ĐģĐ¸Ņ†Đ°", "refreshing_metadata": "ĐžŅĐ˛ĐĩĐļĐ°Đ˛Đ°ŅšĐĩ ĐŧĐĩŅ‚Đ°-ĐŋĐžĐ´Đ°Ņ‚Đ°Đēа", @@ -1276,21 +1468,28 @@ "remove_assets_shared_link_confirmation": "Да Đģи ҁ҂Đĩ ŅĐ¸ĐŗŅƒŅ€ĐŊи да ĐļĐĩĐģĐ¸Ņ‚Đĩ да ҃ĐēĐģĐžĐŊĐ¸Ņ‚Đĩ {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}} ŅĐ° ОвĐĩ Đ´ĐĩŅ™ĐĩĐŊĐĩ вĐĩСĐĩ?", "remove_assets_title": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đ¸ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ?", "remove_custom_date_range": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đĩ ĐŋŅ€Đ¸ĐģĐ°ĐŗĐžŅ’ĐĩĐŊи ĐŋĐĩŅ€Đ¸ĐžĐ´", + "remove_deleted_assets": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đĩ ваĐŊĐŧŅ€ĐĩĐļĐŊĐĩ (offline) Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ", "remove_from_album": "ĐžĐąŅ€Đ¸ŅˆĐ¸ иС аĐģĐąŅƒĐŧа", "remove_from_favorites": "ĐŖĐēĐģĐžĐŊи иС Ņ„Đ°Đ˛ĐžŅ€Đ¸Ņ‚Đ°", "remove_from_shared_link": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đĩ ŅĐ° Đ´ĐĩŅ™ĐĩĐŊĐĩ вĐĩСĐĩ", "remove_memory": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đĩ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Ņƒ", "remove_photo_from_memory": "ĐŖĐēĐģĐžĐŊĐ¸Ņ‚Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Ņƒ иС ОвĐĩ ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đĩ", + "remove_url": "ĐŖĐēĐģĐžĐŊи URL", "remove_user": "ĐŖĐēĐģĐžĐŊи ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "removed_api_key": "ĐŖĐēĐģĐžŅšĐĩĐŊ АПИ ĐēŅ™ŅƒŅ‡ (ĐēĐĩy): {name}", "removed_from_archive": "ĐŖĐēĐģĐžŅšĐĩĐŊĐž иС Đ°Ņ€Ņ…Đ¸Đ˛Đĩ", "removed_from_favorites": "ĐŖĐēĐģĐžŅšĐĩĐŊĐž иС ĐžĐŧĐ¸Ņ™ĐĩĐŊĐ¸Ņ… (Ņ„Đ°Đ˛ĐžŅ€Đ¸Ņ‚Đĩҁ)", "removed_from_favorites_count": "{count, plural, other {ĐŖĐēĐģĐžŅšĐĩĐŊĐž #}} иС ĐžĐŧĐ¸Ņ™ĐĩĐŊĐ¸Ņ…", "removed_memory": "ĐŖĐēĐģĐžŅšĐĩĐŊа ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đ°", + "removed_photo_from_memory": "ĐĄĐģиĐēа ҘĐĩ ҃ĐēĐģĐžŅšĐĩĐŊа иС ĐŧĐĩĐŧĐžŅ€Đ¸Ņ˜Đĩ", "removed_tagged_assets": "ĐŖĐēĐģĐžŅšĐĩĐŊа ОСĐŊаĐēа иС {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", "rename": "ĐŸŅ€ĐĩиĐŧĐĩĐŊ҃Ҙ", + "repair": "ПоĐŋŅ€Đ°Đ˛Đ¸", + "repair_no_results_message": "ОвдĐĩ Ņ›Đĩ ҁĐĩ ĐŋĐžŅ˜Đ°Đ˛Đ¸Ņ‚Đ¸ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ĐēĐžŅ˜Đĩ ĐŊĐ¸ŅŅƒ ĐŋŅ€Đ°Ņ›ĐĩĐŊĐĩ и ĐŊĐĩĐ´ĐžŅŅ‚Đ°Ņ˜Ņƒ", + "replace_with_upload": "ЗаĐŧĐĩĐŊĐ¸Ņ‚Đĩ ŅĐ° ҃ĐŋĐģОад-ĐžĐŧ", "repository": "Đ ĐĩĐŋĐžĐˇĐ¸Ņ‚ĐžŅ€Đ¸Ņ˜ŅƒĐŧ (Đ ĐĩĐŋĐžŅĐ¸Ņ‚ĐžŅ€y)", "require_password": "ĐŸĐžŅ‚Ņ€ĐĩĐąĐŊа ĐģОСиĐŊĐēа", + "require_user_to_change_password_on_first_login": "Đ—Đ°Ņ…Ņ‚ĐĩĐ˛Đ°Ņ‚Đ¸ Од ĐēĐžŅ€Đ¸ŅĐŊиĐēа да ĐŋŅ€ĐžĐŧĐĩĐŊи ĐģОСиĐŊĐē҃ ĐŋŅ€Đ¸ ĐŋŅ€Đ˛ĐžĐŧ ĐŋŅ€Đ¸Ņ˜Đ°Đ˛Ņ™Đ¸Đ˛Đ°ŅšŅƒ", "rescan": "ПоĐŊОвО ҁĐēĐĩĐŊĐ¸Ņ€Đ°Ņ˜", "reset": "Đ ĐĩҁĐĩŅ‚ĐžĐ˛Đ°Ņ‚Đ¸", "reset_password": "Đ ĐĩҁĐĩŅ‚ĐžĐ˛Đ°Ņ‚Đ¸ ĐģОСиĐŊĐē҃", @@ -1304,12 +1503,14 @@ "restore_user": "ĐŸĐžĐ˛Ņ€Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "restored_asset": "ĐŸĐžĐ˛Ņ€Đ°Ņ›ĐĩĐŊĐž ҁҀĐĩĐ´ŅŅ‚Đ˛Đž", "resume": "ПоĐŊОвО ĐŋĐžĐēŅ€ĐĩĐŊи", + "retry_upload": "ПоĐēŅƒŅˆĐ°Ņ˜Ņ‚Đĩ ĐŋĐžĐŊОвО да ҃ĐŋĐģĐžĐ°Đ´ŅƒŅ˜ĐĩŅ‚Đĩ", "review_duplicates": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´Đ°Ņ˜Ņ‚Đĩ Đ´ŅƒĐŋĐģиĐēĐ°Ņ‚Đĩ", "role": "ĐŖĐģĐžĐŗĐ°", "role_editor": "ĐŖŅ€ĐĩĐ´ĐŊиĐē", "role_viewer": "ГĐģĐĩдаĐģĐ°Ņ†", "running": "ĐŖ Ņ‚ĐžĐē҃", "save": "ĐĄĐ°Ņ‡ŅƒĐ˛Đ°Ņ˜", + "save_to_gallery": "ĐĄĐ°Ņ‡ŅƒĐ˛Đ°Ņ˜ ҃ ĐŗĐ°ĐģĐĩŅ€Đ¸Ņ˜Ņƒ", "saved_api_key": "ĐĄĐ°Ņ‡ŅƒĐ˛Đ°ĐŊ АПИ ĐēŅ™ŅƒŅ‡ (ĐēĐĩy)", "saved_profile": "ĐĄĐ°Ņ‡ŅƒĐ˛Đ°ĐŊ ĐŋŅ€ĐžŅ„Đ¸Đģ", "saved_settings": "ĐĄĐ°Ņ‡ŅƒĐ˛Đ°ĐŊа ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", @@ -1317,6 +1518,7 @@ "scaffold_body_error_occurred": "Đ”ĐžŅˆĐģĐž ҘĐĩ Đ´Đž ĐŗŅ€Đĩ҈ĐēĐĩ", "scan_all_libraries": "ĐĄĐēĐĩĐŊĐ¸Ņ€Đ°Ņ˜ ŅĐ˛Đĩ йийĐģĐ¸ĐžŅ‚ĐĩĐēĐĩ", "scan_library": "ĐĄĐēĐĩĐŊĐ¸Ņ€Đ°Ņ˜", + "scan_settings": "ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ° ҁĐēĐĩĐŊĐ¸Ņ€Đ°ŅšĐ°", "scanning_for_album": "ĐĄĐēĐĩĐŊĐ¸Ņ€Đ°ŅšĐĩ аĐģĐąŅƒĐŧа...", "search": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐ°", "search_albums": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°Đļи аĐģĐąŅƒĐŧĐĩ", @@ -1349,13 +1551,25 @@ "search_no_people_named": "НĐĩĐŧа ĐžŅĐžĐąĐ° ŅĐ° иĐŧĐĩĐŊĐžĐŧ „{name}“", "search_no_result": "ĐĐ¸ŅŅƒ ĐŋŅ€ĐžĐŊĐ°Ņ’ĐĩĐŊи Ņ€ĐĩĐˇŅƒĐģŅ‚Đ°Ņ‚Đ¸, ĐŋĐžĐēŅƒŅˆĐ°Ņ˜Ņ‚Đĩ ŅĐ° Đ´Ņ€ŅƒĐŗĐ¸Đŧ Ņ‚ĐĩŅ€ĐŧиĐŊĐžĐŧ Са ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗŅƒ иĐģи ĐēĐžĐŧйиĐŊĐ°Ņ†Đ¸Ņ˜ĐžĐŧ", "search_options": "ОĐŋŅ†Đ¸Ņ˜Đĩ ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐĩ", + "search_page_categories": "ĐšĐ°Ņ‚ĐĩĐŗĐžŅ€Đ¸Ņ˜Đĩ", + "search_page_motion_photos": "Đ¤ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ҃ ĐŋĐžĐēŅ€ĐĩŅ‚Ņƒ", + "search_page_no_objects": "БĐĩС иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đ°", + "search_page_no_places": "НĐĩĐŧа иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đ° Đž ĐŧĐĩŅŅ‚Ņƒ", + "search_page_screenshots": "ĐĄĐŊиĐŧŅ†Đ¸ ĐĩĐēŅ€Đ°ĐŊа", "search_page_search_photos_videos": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°ĐļĐ¸Ņ‚Đĩ ŅĐ˛ĐžŅ˜Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ", + "search_page_selfies": "ĐĄĐĩĐģŅ„Đ¸Ņ˜Đ¸", + "search_page_things": "ĐĄŅ‚Đ˛Đ°Ņ€Đ¸", "search_page_view_all_button": "ĐŸŅ€Đ¸ĐēаĐļи ŅĐ˛Đĩ", + "search_page_your_activity": "Đ’Đ°ŅˆĐ° аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚", + "search_page_your_map": "Đ’Đ°ŅˆĐ° ĐŧаĐŋа", "search_people": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°Đļи ĐžŅĐžĐąĐĩ", "search_places": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°Đļи ĐŧĐĩŅŅ‚Đ°", "search_rating": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐ° ĐŋĐž ĐžŅ†ĐĩĐŊи...", + "search_result_page_new_search_hint": "Нова ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐ°", "search_settings": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐ° ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", "search_state": "ĐĸŅ€Đ°Đļи Ņ€ĐĩĐŗĐ¸ĐžĐŊ...", + "search_suggestion_list_smart_search_hint_1": "ПаĐŧĐĩŅ‚ĐŊа ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐ° ҘĐĩ ĐŋĐžĐ´Ņ€Đ°ĐˇŅƒĐŧĐĩваĐŊĐž ĐžĐŧĐžĐŗŅƒŅ›ĐĩĐŊа, Са ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗŅƒ ĐŧĐĩŅ‚Đ°ĐŋĐžĐ´Đ°Ņ‚Đ°Đēа ĐēĐžŅ€Đ¸ŅŅ‚Đ¸Ņ‚Đĩ ŅĐ¸ĐŊŅ‚Đ°Đēҁ҃ ", + "search_suggestion_list_smart_search_hint_2": "Đŧ:Đ˛Đ°Ņˆ-ĐŋĐžŅ˜Đ°Đŧ-Са-ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗŅƒ", "search_tags": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°Đļи ОСĐŊаĐēĐĩ (tags)...", "search_timezone": "ĐŸŅ€ĐĩŅ‚Ņ€Đ°Đļи Đ˛Ņ€ĐĩĐŧĐĩĐŊҁĐē҃ СОĐŊ҃...", "search_type": "Đ’Ņ€ŅŅ‚Đ° ĐŋŅ€ĐĩŅ‚Ņ€Đ°ĐŗĐĩ", @@ -1372,10 +1586,12 @@ "select_featured_photo": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ Đ¸ŅŅ‚Đ°ĐēĐŊŅƒŅ‚Ņƒ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Ņƒ", "select_from_computer": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ŅĐ° Ņ€Đ°Ņ‡ŅƒĐŊĐ°Ņ€Đ°", "select_keep_all": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ да ĐˇĐ°Đ´Ņ€ĐļĐ¸Ņ‚Đĩ ŅĐ˛Đĩ", + "select_library_owner": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ вĐģĐ°ŅĐŊиĐēа йийĐģĐ¸ĐžŅ‚ĐĩĐēĐĩ", "select_new_face": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ĐŊОвО ĐģĐ¸Ņ†Đĩ", "select_person_to_tag": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ĐžŅĐžĐąŅƒ Са ОСĐŊĐ°Ņ‡Đ°Đ˛Đ°ŅšĐĩ", "select_photos": "ОдабĐĩŅ€Đ¸ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", "select_trash_all": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ да ŅĐ˛Đĩ ĐąĐ°Ņ†Đ¸Ņ‚Đĩ ĐŊа ĐžŅ‚Đŋад", + "select_user_for_sharing_page_err_album": "НĐĩ҃ҁĐŋĐĩ҈ĐŊĐž ĐēŅ€ĐĩĐ¸Ņ€Đ°ŅšĐĩ аĐģĐąŅƒĐŧа", "selected": "ĐžĐ´Đ°ĐąŅ€Đ°ĐŊĐž", "selected_count": "{count, plural, other {# Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊĐž}}", "send_message": "ĐŸĐžŅˆĐ°Ņ™Đ¸ ĐŋĐžŅ€ŅƒĐē҃", @@ -1401,18 +1617,30 @@ "setting_image_viewer_original_title": "ĐŖŅ‡Đ¸Ņ‚Đ°Ņ˜ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊ҃ ҁĐģиĐē҃", "setting_image_viewer_preview_subtitle": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜ ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°ŅšĐĩ ҁĐģиĐēа ҃ ҁҀĐĩĐ´ŅšĐžŅ˜ Ņ€ĐĩСОĐģŅƒŅ†Đ¸Ņ˜Đ¸. ДĐĩаĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜ да ҁĐĩ Đ´Đ¸Ņ€ĐĩĐēŅ‚ĐŊĐž ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ° ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ, иĐģи да ҁĐĩ ŅĐ°ĐŧĐž ĐēĐžŅ€Đ¸ŅŅ‚Đ¸ ĐŧиĐŊĐ¸Ņ˜Đ°Ņ‚ŅƒŅ€Đ°.", "setting_image_viewer_preview_title": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´Đ°Ņ˜ ҁĐģиĐē҃", + "setting_image_viewer_title": "ĐĄĐģиĐēĐĩ", "setting_languages_apply": "ĐŸŅ€Đ¸ĐŧĐĩĐŊи", "setting_languages_subtitle": "ĐŸŅ€ĐžĐŧĐĩĐŊĐ¸Ņ‚Đĩ ҘĐĩСиĐē аĐŋĐģиĐēĐ°Ņ†Đ¸Ņ˜Đĩ", + "setting_notifications_notify_failures_grace_period": "ОбавĐĩŅŅ‚Đ¸ Đž ĐŗŅ€Đĩ҈ĐēаĐŧа ҃ ĐŋŅ€Đ°Đ˛Ņ™ĐĩҚ҃ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋОСадиĐŊи: {duration}", + "setting_notifications_notify_hours": "{count} ŅĐ°Ņ‚Đ¸", + "setting_notifications_notify_immediately": "ОдĐŧĐ°Ņ…", "setting_notifications_notify_minutes": "{count} ĐŧиĐŊŅƒŅ‚Đ°", + "setting_notifications_notify_never": "ĐŊиĐēада", "setting_notifications_notify_seconds": "{count} ҁĐĩĐē҃ĐŊди", + "setting_notifications_single_progress_subtitle": "ДĐĩŅ‚Đ°Ņ™ĐŊĐĩ иĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Đ¸Ņ˜Đĩ Đž ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšŅƒ, ĐŋĐž СаĐŋĐ¸ŅŅƒ", + "setting_notifications_single_progress_title": "ĐŸŅ€Đ¸ĐēаĐļи Đ´ĐĩŅ‚Đ°Ņ™Đĩ ĐŋОСадиĐŊҁĐēĐžĐŗ ĐŋŅ€Đ°Đ˛Ņ™ĐĩŅšĐ° Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ°", "setting_notifications_subtitle": "ИСĐŧĐĩĐŊи ĐŊĐžŅ‚Đ¸Ņ„Đ¸ĐēĐ°Ņ†Đ¸Ņ˜Đĩ", + "setting_notifications_total_progress_subtitle": "ĐŖĐē҃ĐŋĐŊĐž ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐ¸Ņ… ŅŅ‚Đ°Đ˛Đēи (ĐˇĐ°Đ˛Ņ€ŅˆĐĩĐŊĐž/҃Đē҃ĐŋĐŊĐž ŅŅ‚Đ°Đ˛Đēи)", + "setting_notifications_total_progress_title": "ĐŸŅ€Đ¸ĐēаĐļи ҃Đē҃ĐŋаĐŊ ĐŊаĐŋŅ€ĐĩдаĐē ĐŋŅ€Đ°Đ˛Ņ™ĐĩŅšĐ° Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋĐ¸Ņ˜Đ° ҃ ĐŋОСадиĐŊи", "setting_video_viewer_looping_title": "ПĐĩŅ‚Ņ™Đ°ŅšĐĩ (ЛооĐŋиĐŊĐŗ)", "setting_video_viewer_original_video_subtitle": "ĐŸŅ€Đ¸ĐģиĐēĐžĐŧ ŅŅ‚Ņ€Đ¸ĐŧĐžĐ˛Đ°ŅšĐ° видĐĩа ŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, Ņ€ĐĩĐŋŅ€ĐžĐ´ŅƒĐēŅƒŅ˜Ņ‚Đĩ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģ Ņ‡Đ°Đē и Đēада ҘĐĩ Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´Đ¸Ņ€Đ°ŅšĐĩ. МоĐļĐĩ дОвĐĩŅŅ‚Đ¸ Đ´Đž ĐąĐ°Ņ„ĐĩŅ€ĐžĐ˛Đ°ŅšĐ°. ВидĐĩĐž ҁĐŊиĐŧŅ†Đ¸ Đ´ĐžŅŅ‚ŅƒĐŋĐŊи ĐģĐžĐēаĐģĐŊĐž ҁĐĩ Ņ€ĐĩĐŋŅ€ĐžĐ´ŅƒĐē҃Ҙ҃ ҃ ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊĐžĐŧ ĐēваĐģĐ¸Ņ‚ĐĩŅ‚Ņƒ ĐąĐĩС ĐžĐąĐˇĐ¸Ņ€Đ° ĐŊа ОвО ĐŋОдĐĩŅˆĐ°Đ˛Đ°ŅšĐĩ.", "setting_video_viewer_original_video_title": "ĐŸŅ€Đ¸ŅĐ¸ĐģĐŊĐž ĐžŅ€Đ¸ĐŗĐ¸ĐŊаĐģĐŊи видĐĩĐž", "settings": "ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ°", + "settings_require_restart": "Đ ĐĩŅŅ‚Đ°Ņ€Ņ‚ŅƒŅ˜Ņ‚Đĩ Immich да ĐŋŅ€Đ¸ĐŧĐĩĐŊĐ¸Ņ‚Đĩ ĐžĐ˛Ņƒ ĐŋŅ€ĐžĐŧĐĩĐŊ҃", "settings_saved": "ПодĐĩŅˆĐ°Đ˛Đ°ŅšĐ° ŅĐ°Ņ‡ŅƒĐ˛Đ°ĐŊа", "setup_pin_code": "ПодĐĩŅĐ¸Ņ‚Đĩ ПИН ĐēОд", "share": "ПодĐĩĐģи", + "share_add_photos": "Đ”ĐžĐ´Đ°Ņ˜ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", + "share_assets_selected": "Đ˜ĐˇĐ°ĐąŅ€Đ°ĐŊĐž ҘĐĩ {count}", "share_dialog_preparing": "ĐŸŅ€Đ¸ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ...", "share_link": "ПодĐĩĐģи вĐĩĐˇŅƒ", "shared": "ДĐĩŅ™ĐĩĐŊĐž", @@ -1420,6 +1648,8 @@ "shared_album_activity_remove_content": "Да Đģи ĐļĐĩĐģĐ¸Ņ‚Đĩ да ĐžĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ ĐžĐ˛Ņƒ аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚?", "shared_album_activity_remove_title": "ĐžĐąŅ€Đ¸ŅˆĐ¸ аĐēŅ‚Đ¸Đ˛ĐŊĐžŅŅ‚", "shared_album_section_people_action_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐŊаĐŋŅƒŅˆŅ‚Đ°ŅšŅƒ/҃ĐēĐģĐ°ŅšĐ°ŅšŅƒ иС аĐģĐąŅƒĐŧа", + "shared_album_section_people_action_leave": "ĐŖĐēĐģĐžĐŊи ĐēĐžŅ€Đ¸ŅĐŊиĐēа иС аĐģĐąŅƒĐŧа", + "shared_album_section_people_action_remove_user": "ĐŖĐēĐģĐžĐŊи ĐēĐžŅ€Đ¸ŅĐŊиĐēа иС аĐģĐąŅƒĐŧа", "shared_album_section_people_title": "ПЕОПЛЕ", "shared_by": "ПодĐĩĐģиО", "shared_by_user": "ДĐĩĐģи {user}", @@ -1428,6 +1658,7 @@ "shared_intent_upload_button_progress_text": "ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐž ҘĐĩ {current} / {total}", "shared_link_app_bar_title": "ДĐĩŅ™ĐĩĐŊи linkОви", "shared_link_clipboard_copied_massage": "КоĐŋĐ¸Ņ€Đ°ĐŊĐž ҃ ĐŧĐĩŅ’ŅƒŅĐŋŅ€ĐĩĐŧĐŊиĐē (҆ĐģиĐŋĐąĐžĐ°Ņ€Đ´)", + "shared_link_clipboard_text": "ЛиĐŊĐē: {link}\nЛозиĐŊĐēа: {password}", "shared_link_create_error": "Đ“Ņ€Đĩ҈Đēа ĐŋŅ€Đ¸ ĐēŅ€ĐĩĐ¸Ņ€Đ°ŅšŅƒ Đ´ĐĩŅ™ĐĩĐŊĐžĐŗ linkа", "shared_link_edit_description_hint": "ĐŖĐŊĐĩŅĐ¸Ņ‚Đĩ ĐžĐŋĐ¸Ņ Đ´ĐĩŅ™ĐĩŅšĐ°", "shared_link_edit_expire_after_option_day": "1 даĐŊ", @@ -1461,6 +1692,12 @@ "shared_with_partner": "ДĐĩĐģи ҁĐĩ ŅĐ° {partner}", "sharing": "ДĐĩŅ™ĐĩҚĐĩ", "sharing_enter_password": "ĐŖĐŊĐĩŅĐ¸Ņ‚Đĩ ĐģОСиĐŊĐē҃ да ĐąĐ¸ŅŅ‚Đĩ видĐĩĐģи ĐžĐ˛Ņƒ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņƒ.", + "sharing_page_album": "ДĐĩŅ™ĐĩĐŊи аĐģĐąŅƒĐŧи", + "sharing_page_description": "НаĐŋŅ€Đ°Đ˛Đ¸ Đ´ĐĩŅ™ĐĩĐŊĐĩ аĐģĐąŅƒĐŧĐĩ да Đ´ĐĩĐģĐ¸Ņˆ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ ŅĐ° Ņ™ŅƒĐ´Đ¸Đŧа ĐŊа Ņ‚Đ˛ĐžŅ˜ĐžŅ˜ ĐŧŅ€ĐĩĐļи.", + "sharing_page_empty_list": "ПРАЗНА ЛИСĐĸА", + "sharing_sidebar_description": "ĐŸŅ€Đ¸ĐēаĐļĐ¸Ņ‚Đĩ вĐĩĐˇŅƒ Đ´Đž ДĐĩŅ™ĐĩŅšĐ° ĐŊа ĐąĐžŅ‡ĐŊĐžŅ˜ Ņ‚Ņ€Đ°Ņ†Đ¸", + "sharing_silver_appbar_create_shared_album": "НаĐŋŅ€Đ°Đ˛Đ¸ Đ´ĐĩŅ™ĐĩĐŊи аĐģĐąŅƒĐŧ", + "sharing_silver_appbar_share_partner": "ПодĐĩĐģи ŅĐ° ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€ĐžĐŧ", "shift_to_permanent_delete": "ĐŋŅ€Đ¸Ņ‚Đ¸ŅĐŊĐ¸Ņ‚Đĩ ⇧ да Ņ‚Ņ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅˆĐĩŅ‚Đĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃", "show_album_options": "ĐŸŅ€Đ¸ĐēаĐļи ĐžĐŋŅ†Đ¸Ņ˜Đĩ аĐģĐąŅƒĐŧа", "show_albums": "ĐŸŅ€Đ¸ĐēаĐļи аĐģĐąŅƒĐŧĐĩ", @@ -1505,6 +1742,7 @@ "stack": "Đ“Ņ€ŅƒĐŋĐ¸ŅˆĐ¸", "stack_action_prompt": "{count} ĐŗŅ€ŅƒĐŋĐ¸ŅĐ°ĐŊĐž", "stack_duplicates": "Đ“Ņ€ŅƒĐŋĐ¸ŅˆĐ¸ Đ´ŅƒĐŋĐģиĐēĐ°Ņ‚Đĩ", + "stack_select_one_photo": "ИСайĐĩŅ€Đ¸ ҘĐĩĐ´ĐŊ҃ ĐŗĐģавĐŊ҃ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Ņƒ Са ĐŗŅ€ŅƒĐŋ҃", "stack_selected_photos": "Đ“Ņ€ŅƒĐŋĐ¸ŅˆĐ¸ Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ", "stacked_assets_count": "Đ“Ņ€ŅƒĐŋĐ¸ŅĐ°ĐŊĐž {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}}", "stacktrace": "ĐĄŅ‚ĐĩĐē҂ҀĐĩҘҁ", @@ -1530,11 +1768,13 @@ "swap_merge_direction": "ЗаĐŧĐĩĐŊĐ¸Ņ‚Đĩ ĐŋŅ€Đ°Đ˛Đ°Ņ† ҁĐŋĐ°Ņ˜Đ°ŅšĐ°", "sync": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇĐ°Ņ†Đ¸Ņ˜Đ°", "sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇŅƒŅ˜ аĐģĐąŅƒĐŧĐĩ", + "sync_albums_manual_subtitle": "ХиĐŊŅ…Ņ€ĐžĐŊĐ¸ĐˇŅƒŅ˜Ņ‚Đĩ ŅĐ˛Đĩ ĐžŅ‚ĐŋŅ€ĐĩĐŧŅ™ĐĩĐŊĐĩ видĐĩĐž СаĐŋĐ¸ŅĐĩ и Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ ŅĐ° Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊиĐŧ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊиĐŧ аĐģĐąŅƒĐŧиĐŧа", "sync_upload_album_setting_subtitle": "ĐšŅ€ĐĩĐ¸Ņ€Đ°Ņ˜Ņ‚Đĩ и ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ¸Ņ‚Đĩ ŅĐ˛ĐžŅ˜Đĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đĩ и видĐĩĐž СаĐŋĐ¸ŅĐĩ ҃ ĐžĐ´Đ°ĐąŅ€Đ°ĐŊĐĩ аĐģĐąŅƒĐŧĐĩ ĐŊа Immich-u", "tag": "ОзĐŊаĐēа (tag)", "tag_assets": "ОзĐŊĐ°Ņ‡Đ¸Ņ‚Đĩ (tag) ҁҀĐĩĐ´ŅŅ‚Đ˛Đ°", "tag_created": "НаĐŋŅ€Đ°Đ˛Ņ™ĐĩĐŊа ОСĐŊаĐēа (tag): {tag}", "tag_feature_description": "ĐŸŅ€ĐĩĐŗĐģĐĩĐ´Đ°Đ˛Đ°ŅšĐĩ Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Đ¸Ņ˜Đ° и видĐĩĐž ҁĐŊиĐŧаĐēа ĐŗŅ€ŅƒĐŋĐ¸ŅĐ°ĐŊĐ¸Ņ… ĐŋĐž ĐģĐžĐŗĐ¸Ņ‡ĐŊиĐŧ Ņ‚ĐĩĐŧаĐŧа ОСĐŊаĐēа", + "tag_not_found_question": "НĐĩ ĐŧĐžĐļĐĩŅ‚Đĩ да ĐŋŅ€ĐžĐŊĐ°Ņ’ĐĩŅ‚Đĩ ОСĐŊаĐē҃ (tag)? НаĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đĩ ĐŊĐžĐ˛Ņƒ ОСĐŊаĐē҃", "tag_people": "ОзĐŊĐ°Ņ‡Đ¸Ņ‚Đĩ Ņ™ŅƒĐ´Đĩ", "tag_updated": "АĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊа ОСĐŊаĐēа (tag): {tag}", "tagged_assets": "ОзĐŊĐ°Ņ‡ĐĩĐŊĐž (tagĐŗĐĩĐ´) {count, plural, one {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ}}", @@ -1547,10 +1787,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Đ‘Ņ€ĐžŅ˜ СаĐŋĐ¸ŅĐ° ĐŋĐž Ņ€ĐĩĐ´Ņƒ {count}", "theme_setting_colorful_interface_subtitle": "НаĐŊĐĩŅĐ¸Ņ‚Đĩ ĐžŅĐŊОвĐŊ҃ ĐąĐžŅ˜Ņƒ ĐŊа ĐŋОСадиĐŊҁĐēĐĩ ĐŋĐžĐ˛Ņ€ŅˆĐ¸ĐŊĐĩ.", "theme_setting_colorful_interface_title": "Đ¨Đ°Ņ€ĐĩĐŊи иĐŊŅ‚ĐĩҀ҄ĐĩҘҁ", + "theme_setting_image_viewer_quality_subtitle": "ĐŸŅ€Đ¸ĐģĐ°ĐŗĐžĐ´Đ¸Ņ‚Đĩ ĐēваĐģĐ¸Ņ‚ĐĩŅ‚ ĐŋŅ€Đ¸ĐēаСа Са Đ´ĐĩŅ‚Đ°Ņ™ĐŊĐž ĐŋŅ€ĐĩĐŗĐģĐĩĐ´Đ°Đ˛Đ°ŅšĐĩ ҁĐģиĐēĐĩ", + "theme_setting_image_viewer_quality_title": "КваĐģĐ¸Ņ‚ĐĩŅ‚ ĐŋŅ€ĐĩĐŗĐģĐĩĐ´Đ°Ņ‡Đ° ҁĐģиĐēа", "theme_setting_primary_color_subtitle": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ĐąĐžŅ˜Ņƒ Са ĐŗĐģавĐŊĐĩ Ņ€Đ°Đ´ŅšĐĩ и аĐē҆ĐĩĐŊŅ‚Đĩ.", "theme_setting_primary_color_title": "ĐŸŅ€Đ¸ĐŧĐ°Ņ€ĐŊа ĐąĐžŅ˜Đ°", "theme_setting_system_primary_color_title": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸ ŅĐ¸ŅŅ‚ĐĩĐŧҁĐē҃ ĐąĐžŅ˜Ņƒ", "theme_setting_system_theme_switch": "ĐŅƒŅ‚ĐžĐŧĐ°Ņ‚ŅĐēи (ĐŸŅ€Đ°Ņ‚Đ¸ ĐžĐŋŅ†Đ¸Ņ˜Đĩ ŅĐ¸ŅŅ‚ĐĩĐŧа)", + "theme_setting_theme_subtitle": "ОдабĐĩŅ€Đ¸ Ņ‚ĐĩĐŧ҃ ŅĐ¸ŅŅ‚ĐĩĐŧа", + "theme_setting_three_stage_loading_subtitle": "ĐĸŅ€ĐžŅŅ‚ĐĩĐŋĐĩĐŊĐž ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°ŅšĐĩ ĐŧĐžĐļда ŅƒĐąŅ€ĐˇĐ° ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°ŅšĐĩ, ĐŋĐž ҆ĐĩĐŊ҃ ĐŋĐžŅ‚Ņ€ĐžŅˆŅšĐĩ ĐŋĐžĐ´Đ°Ņ‚Đ°Đēа", + "theme_setting_three_stage_loading_title": "АĐēŅ‚Đ¸Đ˛Đ¸Ņ€Đ°Ņ˜ Ņ‚Ņ€ĐžŅŅ‚ĐĩĐŋĐĩĐŊĐž ŅƒŅ‡Đ¸Ņ‚Đ°Đ˛Đ°ŅšĐĩ", "they_will_be_merged_together": "ОĐŊи Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ ҁĐŋĐžŅ˜ĐĩĐŊи ĐˇĐ°Ņ˜ĐĩĐ´ĐŊĐž", "third_party_resources": "Đ ĐĩŅŅƒŅ€ŅĐ¸ ҂ҀĐĩŅ›Đ¸Ņ… ŅŅ‚Ņ€Đ°ĐŊа", "time_based_memories": "ĐĄĐĩŅ›Đ°ŅšĐ° ĐˇĐ°ŅĐŊОваĐŊа ĐŊа Đ˛Ņ€ĐĩĐŧĐĩĐŊ҃", @@ -1569,9 +1814,15 @@ "trash_all": "Đ‘Đ°Ņ†Đ¸ ŅĐ˛Đĩ ҃ ĐžŅ‚Đŋад", "trash_count": "ĐžŅ‚Đŋад {count, number}", "trash_delete_asset": "ĐžŅ‚Đŋад/Đ˜ĐˇĐąŅ€Đ¸ŅˆĐ¸ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃", + "trash_emptied": "Đ˜ŅĐŋŅ€Đ°ĐˇĐŊиО ҁĐŧĐĩŅ›Đĩ", "trash_no_results_message": "ĐĄĐģиĐēĐĩ и видĐĩĐž СаĐŋĐ¸ŅĐ¸ ҃ ĐžŅ‚ĐŋĐ°Đ´Ņƒ Ņ›Đĩ ҁĐĩ ĐŋĐžŅ˜Đ°Đ˛Đ¸Ņ‚Đ¸ ОвдĐĩ.", "trash_page_delete_all": "ĐžĐąŅ€Đ¸ŅˆĐ¸ ŅĐ˛Đĩ", + "trash_page_empty_trash_dialog_content": "Да Đģи ĐļĐĩĐģĐ¸Ņ‚Đĩ да Đ¸ŅĐŋŅ€Đ°ĐˇĐŊĐ¸Ņ‚Đĩ ŅĐ˛ĐžŅ˜Đ° ĐŋŅ€ĐĩĐŧĐĩŅˆŅ‚ĐĩĐŊа ҁҀĐĩĐ´ŅŅ‚Đ˛Đ°? Ови ĐŋŅ€ĐĩĐ´ĐŧĐĩŅ‚Đ¸ Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ҃ĐēĐģĐžŅšĐĩĐŊи иС Immich-a", "trash_page_info": "ĐĄŅ‚Đ°Đ˛ĐēĐĩ Đ¸ĐˇĐąĐ°Ņ‡ĐĩĐŊĐĩ иС ĐžŅ‚Đŋада ĐąĐ¸Ņ›Đĩ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž ĐžĐąŅ€Đ¸ŅĐ°ĐŊĐĩ ĐŊаĐēĐžĐŊ {days} даĐŊа", + "trash_page_no_assets": "НĐĩĐŧа ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ҃ ĐžŅ‚ĐŋĐ°Đ´Ņƒ", + "trash_page_restore_all": "Đ’Ņ€Đ°Ņ‚Đ¸ ŅĐ˛Đĩ", + "trash_page_select_assets_btn": "ИСайĐĩŅ€Đ¸Ņ‚Đĩ ҁҀĐĩĐ´ŅŅ‚Đ˛Đ°", + "trash_page_title": "ĐžŅ‚Đŋад ({count})", "trashed_items_will_be_permanently_deleted_after": "Đ”Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ ҃ ĐžŅ‚ĐŋĐ°Đ´Ņƒ Ņ›Đĩ ĐąĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ°Ņ˜ĐŊĐž Đ¸ĐˇĐąŅ€Đ¸ŅĐ°ĐŊĐĩ ĐŊаĐēĐžĐŊ {days, plural, one {# даĐŊ} few {# даĐŊа} other {# даĐŊа}}.", "troubleshoot": "Đ ĐĩŅˆĐ°Đ˛Đ°ŅšĐĩ ĐŋŅ€ĐžĐąĐģĐĩĐŧа", "type": "Đ’Ņ€ŅŅ‚Đ°", @@ -1605,6 +1856,8 @@ "updated_password": "АĐļŅƒŅ€Đ¸Ņ€Đ°ĐŊа ĐģОСиĐŊĐēа", "upload": "ĐŖĐŋĐģĐžĐ°Đ´ŅƒŅ˜", "upload_concurrency": "ĐŸĐ°Ņ€Đ°ĐģĐĩĐģĐŊĐž ҃ĐŋĐģĐžĐ°Đ´ĐžĐ˛Đ°ŅšĐĩ", + "upload_dialog_info": "Да Đģи ĐļĐĩĐģĐ¸Ņ‚Đĩ да ĐŊаĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋĐ¸Ņ˜Ņƒ Đ¸ĐˇĐ°ĐąŅ€Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊĐ°Ņ‚Đ° ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€Ņƒ?", + "upload_dialog_title": "ĐžŅ‚ĐŋŅ€ĐĩĐŧи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "upload_errors": "ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ ҘĐĩ ĐˇĐ°Đ˛Ņ€ŅˆĐĩĐŊĐž ŅĐ° {count, plural, one {# ĐŗŅ€Đĩ҈ĐēĐžĐŧ} other {# ĐŗŅ€ĐĩŅˆĐ°Đēа}}, ĐžŅĐ˛ĐĩĐļĐ¸Ņ‚Đĩ ŅŅ‚Ņ€Đ°ĐŊĐ¸Ņ†Ņƒ да ĐąĐ¸ŅŅ‚Đĩ видĐĩĐģи ĐŊОвĐĩ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēĐĩ Са ĐžŅ‚ĐŋŅ€ĐĩĐŧĐ°ŅšĐĩ (҃ĐŋĐģОад).", "upload_progress": "ĐŸŅ€ĐĩĐžŅŅ‚Đ°ĐģĐž {remaining, number} – ĐžĐąŅ€Đ°Ņ’ĐĩĐŊĐž {processed, number}/{total, number}", "upload_skipped_duplicates": "ĐŸŅ€ĐĩҁĐēĐžŅ‡ĐĩĐŊĐž {count, plural, one {# Đ´ŅƒĐŋĐģа Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа} other {# Đ´ŅƒĐŋĐģĐ¸Ņ… Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐēа}}", @@ -1627,6 +1880,7 @@ "user_privacy": "ĐŸŅ€Đ¸Đ˛Đ°Ņ‚ĐŊĐžŅŅ‚ ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "user_purchase_settings": "ĐšŅƒĐŋОвиĐŊа", "user_purchase_settings_description": "ĐŖĐŋŅ€Đ°Đ˛Ņ™Đ°Ņ˜Ņ‚Đĩ Đē҃ĐŋОвиĐŊĐžĐŧ", + "user_role_set": "ĐŸĐžŅŅ‚Đ°Đ˛Đ¸ {user} ĐēаО {role}", "user_usage_detail": "ДĐĩŅ‚Đ°Ņ™Đ¸ ĐēĐžŅ€Đ¸ŅˆŅ›ĐĩŅšĐ° ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "user_usage_stats": "ĐĄŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đēа ĐēĐžŅ€Đ¸ŅˆŅ›ĐĩŅšĐ° ĐŊаĐģĐžĐŗĐ°", "user_usage_stats_description": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ˜Ņ‚Đĩ ŅŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đē҃ ĐēĐžŅ€Đ¸ŅˆŅ›ĐĩŅšĐ° ĐŊаĐģĐžĐŗĐ°", @@ -1652,12 +1906,16 @@ "view_all_users": "ĐŸŅ€Đ¸ĐēаĐļи ŅĐ˛Đĩ ĐēĐžŅ€Đ¸ŅĐŊиĐēĐĩ", "view_in_timeline": "ĐŸŅ€Đ¸ĐēаĐļи ҃ Đ˛Ņ€ĐĩĐŧĐĩĐŊҁĐēĐžŅ˜ ĐģиĐŊĐ¸Ņ˜Đ¸", "view_link": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ˜ вĐĩĐˇŅƒ", + "view_links": "ĐŸŅ€Đ¸ĐēаĐļи вĐĩСĐĩ", "view_name": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ‚Đ¸", "view_next_asset": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ˜Ņ‚Đĩ ҁĐģĐĩĐ´ĐĩŅ›Ņƒ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃", "view_previous_asset": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ˜ ĐŋŅ€ĐĩŅ‚Ņ…ĐžĐ´ĐŊ҃ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃", "view_qr_code": "ĐŸĐžĐŗĐģĐĩĐ´Đ°Ņ˜Ņ‚Đĩ QĐ  ĐēОд", "view_stack": "ĐŸŅ€Đ¸ĐēаĐļи ĐŗŅ€ŅƒĐŋ҃", + "view_user": "ĐŸŅ€Đ¸ĐēаĐļи ĐēĐžŅ€Đ¸ŅĐŊиĐēа", "viewer_remove_from_stack": "ĐŖĐēĐģĐžĐŊи иС ĐŗŅ€ŅƒĐŋĐĩ", + "viewer_stack_use_as_main_asset": "ĐšĐžŅ€Đ¸ŅŅ‚Đ¸ ĐēаО ĐŗĐģавĐŊ҃ Đ´Đ°Ņ‚ĐžŅ‚ĐĩĐē҃", + "viewer_unstack": "Đ Đ°ĐˇĐŗŅ€ŅƒĐŋĐ¸ŅˆĐ¸", "visibility_changed": "Đ’Đ¸Đ´Ņ™Đ¸Đ˛ĐžŅŅ‚ ҘĐĩ ĐŋŅ€ĐžĐŧĐĩҚĐĩĐŊа Са {count, plural, one {# ĐžŅĐžĐąŅƒ} other {# ĐžŅĐžĐąĐĩ}}", "waiting": "На ҇ĐĩĐēĐ°ŅšŅƒ", "warning": "ĐŖĐŋĐžĐˇĐžŅ€ĐĩҚĐĩ", diff --git a/i18n/sr_Latn.json b/i18n/sr_Latn.json index 4979e5f137..b7f71ba4b8 100644 --- a/i18n/sr_Latn.json +++ b/i18n/sr_Latn.json @@ -1,7 +1,7 @@ { "about": "O aplikaciji", "account": "Nalog", - "account_settings": "PodeÅĄavanja profila", + "account_settings": "PodeÅĄavanja za Profil", "acknowledge": "Potvrdi", "action": "Postupak", "action_common_update": "AÅžuriraj", @@ -10,31 +10,39 @@ "active": "Aktivni", "active_count": "Aktivno: {count}", "activity": "Aktivnost", + "activity_changed": "Aktivnost je {enabled, select, true {omogucˁena} other {onemogucˁena}}", "add": "Dodaj", "add_a_description": "Dodaj opis", "add_a_location": "Dodaj lokaciju", "add_a_name": "Dodaj ime", "add_a_title": "Dodaj naslov", "add_action": "Dodaj akciju", + "add_action_description": "Klikni da dodas akciju", "add_assets": "Dodaj aktive", "add_birthday": "Dodaj rođendan", "add_endpoint": "Dodajte krajnju tačku", "add_exclusion_pattern": "Dodajte obrazac izuzimanja", + "add_filter": "Dodaj filter", + "add_filter_description": "Klikni da dodas stanje filtera", "add_location": "Dodaj lokaciju", + "add_more_users": "Dodaj korisnike", "add_partner": "Dodaj partner", + "add_path": "Dodaj putanju", "add_photos": "Dodaj fotografije", - "add_step": "Dodaj korak", "add_tag": "Dodaj oznaku", "add_to": "Dodaj uâ€Ļ", "add_to_album": "Dodaj u album", "add_to_album_bottom_sheet_added": "Dodato u {album}", "add_to_album_bottom_sheet_already_exists": "Već u {album}", "add_to_album_bottom_sheet_some_local_assets": "Neki lokalni aktivi se ne mogu dodati u album", + "add_to_album_toggle": "Uključi/isključi izbor za {album}", "add_to_albums": "Dodaj u albume", "add_to_albums_count": "Dodaj u albume ({count})", "add_to_bottom_bar": "Dodaj u", + "add_to_shared_album": "Dodaj u deljen album", "add_upload_to_stack": "Dodaj fajl u snop", "add_url": "Dodaj URL", + "add_workflow_step": "Dodaj korak toka posla", "added_to_archive": "Dodato u arhivu", "added_to_favorites": "Dodato u favorite", "added_to_favorites_count": "Dodato {count, number} u favorite", @@ -73,7 +81,6 @@ "cron_expression_description": "Podesite interval skeniranja koristecˁi cron format. Za viÅĄe informacija pogledajte npr. Crontab Guru", "cron_expression_presets": "Predefinisana podeÅĄavanja Cron izraza (expression)", "disable_login": "Onemogucˁi prijavu", - "download_csv": "Preuzmite CSV", "duplicate_detection_job_description": "Pokrenite maÅĄinsko učenje na sredstvima da biste otkrili slične slike. Oslanja se na pametnu pretragu", "exclusion_pattern_description": "Obrasci izuzimanja vam omogucˁavaju da ignoriÅĄete datoteke i fascikle kada skenirate biblioteku. Ovo je korisno ako imate fascikle koje sadrÅže datoteke koje ne Åželite da uvezete, kao ÅĄto su RAW datoteke.", "export_config_as_json_description": "Skini trenutnu sistemsku konfiguraciju kao JSON fajl", @@ -260,8 +267,6 @@ "notification_enable_email_notifications": "Omogucˁite obaveÅĄtenja putem e-poÅĄte", "notification_settings": "PodeÅĄavanja obaveÅĄtenja", "notification_settings_description": "Upravljajte podeÅĄavanjima obaveÅĄtenja, uključujucˁi e-poÅĄtu", - "oauth_allow_insecure_requests": "Dozvoli nebezbedne zahteve", - "oauth_allow_insecure_requests_description": "UPOZORENJE: Ovo će onesposobiti validaciju TLS sertifikata za OAuth zahteve i moÅže Vas učiniti podloÅžnim MITM napadima.", "oauth_auto_launch": "Automatsko pokretanje", "oauth_auto_launch_description": "Pokrenite OAuth tok prijavljivanja automatski nakon navigacije na stranicu za prijavu", "oauth_auto_register": "Automatska registracija", @@ -269,7 +274,6 @@ "oauth_button_text": "Tekst dugmeta", "oauth_client_secret_description": "Obavezno za poverljivog klijenta ili ako PKCE (Proof Key for Code Exchange) nije podrÅžan za javnog klijenta.", "oauth_enable_description": "Prijavite se pomocˁu OAuth-a", - "oauth_end_session_url_description": "Preusmeri korisnika na ovu URI adresu kada se izloguju.", "oauth_mobile_redirect_uri": "URI za preusmeravanje mobilnih uređaja", "oauth_mobile_redirect_uri_override": "Zamena URI-ja mobilnog preusmeravanja", "oauth_mobile_redirect_uri_override_description": "Omogucˁi kada OAuth dobavljač (provider) ne dozvoljava mobilni URI, kao ÅĄto je ''{callback}''", @@ -299,8 +303,6 @@ "refreshing_all_libraries": "OsveÅžavanje svih biblioteka", "registration": "Registracija administratora", "registration_description": "PoÅĄto ste prvi korisnik na sistemu, bicˁete dodeljeni kao Admin i odgovorni ste za administrativne zadatke, a dodatne korisnike cˁete kreirati vi.", - "release_channel_release_candidate": "Kandidat za objavljivanje", - "release_channel_stable": "Stabilno", "remove_failed_jobs": "Uklonite neuspeÅĄne zadatke", "require_password_change_on_login": "Zahtevati od korisnika da promeni lozinku pri prvom prijavljivanju", "reset_settings_to_default": "Resetujte podeÅĄavanja na podrazumevane vrednosti", @@ -395,10 +397,6 @@ "transcoding_preferred_hardware_device_description": "Odnosi se samo na VAAPI i QSV. Postavlja dri node koji se koristi za hardversko transkodiranje.", "transcoding_preset_preset": "Unapred podeÅĄena podeÅĄavanja (-preset)", "transcoding_preset_preset_description": "Brzina kompresije. Sporije unapred podeÅĄene vrednosti proizvode manje datoteke i povecˁavaju kvalitet kada ciljate određenu brzinu prenosa. VP9 ignoriÅĄe brzine iznad 'brÅže'.", - "transcoding_realtime": "Transkodiranje u realnom vremenu [EKSPERIMENTALNO]", - "transcoding_realtime_description": "Omogucˁava transkodovanje u realnom vremenu dok se video strimuje. Omogucˁava promenu kvaliteta, ali moÅže izazvati vecˁu latenciju reprodukcije i zastoje u zavisnosti od mogucˁnosti servera.", - "transcoding_realtime_enabled": "Omogucˁi transkodovanje u realnom vremenu", - "transcoding_realtime_enabled_description": "Ako je onemogucˁeno, server cˁe odbiti da pokrene nova transkodiranja u realnom vremenu.", "transcoding_reference_frames": "Referentni okviri (frames)", "transcoding_reference_frames_description": "Broj okvira (frames) za referencu prilikom kompresije datog okvira. ViÅĄe vrednosti poboljÅĄavaju efikasnost kompresije, ali usporavaju kodiranje. 0 automatski postavlja ovu vrednost.", "transcoding_required_description": "Samo video snimci koji nisu u prihvacˁenom formatu", @@ -442,8 +440,6 @@ "user_settings_description": "Upravljajte korisničkim podeÅĄavanjima", "user_successfully_removed": "Korisnik {email} je uspeÅĄno uklonjen.", "users_page_description": "Stranica administratorskih korisnika", - "version_check_channel": "Kanal izdanja", - "version_check_channel_description": "Izaberite kanal izdanja za koji Åželite da dobijate obaveÅĄtenja o verzijama", "version_check_enabled_description": "Omogucˁite proveru novih izdanja", "version_check_implications": "Funkcija provere verzije se oslanja na periodičnu komunikaciju sa {server}", "version_check_settings": "Provera verzije", @@ -458,6 +454,8 @@ "advanced_settings_clear_image_cache": "ObriÅĄi keÅĄ slike", "advanced_settings_clear_image_cache_error": "Brisanje keÅĄ memorije slika nije uspelo", "advanced_settings_clear_image_cache_success": "UspeÅĄno obrisano {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Koristite ovu opciju za filtriranje medija tokom sinhronizacije na osnovu alternativnih kriterijuma. PokuÅĄajte ovo samo ako imate problema sa aplikacijom da otkrije sve albume.", + "advanced_settings_enable_alternate_media_filter_title": "[EKSPERIMENTALNO] Koristite filter za sinhronizaciju albuma na alternativnom uređaju", "advanced_settings_log_level_title": "Nivo evidencije (log): {level}", "advanced_settings_prefer_remote_subtitle": "Neki uređaji izuzetno sporo učitavaju sličice sa lokalnih resursa. Aktivirajte ovo podeÅĄavanje da biste umesto toga učitavali udaljene slike.", "advanced_settings_prefer_remote_title": "Preferirajte udaljene slike", @@ -465,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Prilagođeni proksi zaglavci [EKSPERIMENTALNO]", "advanced_settings_readonly_mode_subtitle": "Omogucˁava reÅžim samo za čitanje gde se fotografije mogu samo pregledati, stvari poput odabira viÅĄe slika, deljenja, emitovanja i brisanja su onemogucˁene. Omogucˁi/onemogucˁi reÅžim samo za čitanje preko korisničkog avatara sa glavnog ekrana", "advanced_settings_readonly_mode_title": "ReÅžim samo za čitanje", + "advanced_settings_self_signed_ssl_subtitle": "Preskače verifikaciju SSL sertifikata za krajnju tačku servera. Obavezno za samopotpisane sertifikate.", + "advanced_settings_self_signed_ssl_title": "Dozvoli samopotpisane SSL sertifikate [EKSPERIMENTALNO]", "advanced_settings_sync_remote_deletions_subtitle": "Automatski izbriÅĄite ili vratite sredstvo na ovom uređaju kada se ta radnja preduzme na vebu", "advanced_settings_sync_remote_deletions_title": "Sinhronizujte udaljena brisanja [EKSPERIMENTALNO]", "advanced_settings_tile_subtitle": "Napredna korisnička podeÅĄavanja", @@ -480,18 +480,31 @@ "album_delete_confirmation": "Da li stvarno Åželite da izbriÅĄete album {album}?", "album_delete_confirmation_description": "Ako se ovaj album deli, drugi korisnici viÅĄe necˁe mocˁi da mu pristupe.", "album_deleted": "Album obrisan", + "album_info_card_backup_album_excluded": "ISKLJUČENO", + "album_info_card_backup_album_included": "UKLJUČENO", "album_info_updated": "Informacija albuma aÅžurirana", + "album_leave": "Napustiti album?", + "album_leave_confirmation": "Da li stvarno Åželite da napustite {album}?", "album_name": "Ime albuma", "album_options": "Opcije albuma", "album_remove_user": "Ukloniti korisnika?", "album_remove_user_confirmation": "Da li ste sigurni da Åželite da uklonite {user}?", "album_search_not_found": "Nije pronađen nijedan album koji odgovara vaÅĄoj pretrazi", + "album_selected": "Album selektovan", "album_share_no_users": "Izgleda da ste podelili ovaj album sa svim korisnicima ili da nemate nijednog korisnika sa kojim biste delili.", "album_summary": "Rezime albuma", "album_updated": "Album aÅžuriran", "album_updated_setting_description": "Primite obaveÅĄtenje e-poÅĄtom kada deljeni album ima nova svojstva", "album_upload_assets": "Otpremite sadrÅžaj sa računara i dodajte ga u album", + "album_user_left": "Napustio/la {album}", + "album_user_removed": "Uklonjen {user}", + "album_viewer_appbar_delete_confirm": "Da li ste sigurni da Åželite da izbriÅĄete ovaj album sa svog naloga?", "album_viewer_appbar_share_err_delete": "NeuspeÅĄno brisanje albuma", + "album_viewer_appbar_share_err_leave": "NeuspeÅĄno izlaÅženje iz albuma", + "album_viewer_appbar_share_err_remove": "Problemi sa brisanjem zapisa iz albuma", + "album_viewer_appbar_share_err_title": "NeuspeÅĄno menjanje naziva albuma", + "album_viewer_appbar_share_leave": "Izađi iz albuma", + "album_viewer_appbar_share_to": "Podeli sa", "album_viewer_page_share_add_users": "Dodaj korisnike", "album_with_link_access": "Neka svako ko ima vezu vidi fotografije i ljude u ovom albumu.", "albums": "Albumi", @@ -500,12 +513,14 @@ "albums_default_sort_order_description": "Početni redosled sortiranja elemenata pri kreiranju novih albuma.", "albums_feature_description": "Kolekcije imovine koje se mogu deliti sa drugim korisnicima.", "albums_on_device_count": "Albumi na uređaju ({count})", + "albums_selected": "{count, plural, one {# izabranih albuma} other {# izabranih albuma}}", "all": "Sve", "all_albums": "Svi albumi", "all_people": "Sve osobe", "all_photos": "Sve fotografije", "all_videos": "Svi video snimci", "allow_dark_mode": "Dozvoli tamni reÅžim", + "allow_edits": "Dozvoli uređenje", "allow_public_user_to_download": "Dozvolite javnom korisniku da preuzme (download-uje)", "allow_public_user_to_upload": "Dozvoli javnom korisniku da otpremi (upload-uje)", "allowed": "Dozvoljeno", @@ -513,12 +528,14 @@ "always_keep": "Uvek sačuvaj", "always_keep_photos_hint": "Oslobodi Prostor će sačuvati sve slike na ovom uređaju.", "always_keep_videos_hint": "Oslobodi Prostor će sačuvati sve snimke na ovom uređaju.", + "anti_clockwise": "U smeru suprotnom od kazaljke na satu", "api_key": "API ključ (key)", "api_key_description": "Ova vrednost cˁe biti prikazana samo jednom. Obavezno kopirajte pre nego ÅĄto zatvorite prozor.", "api_key_empty": "Ime vaÅĄeg API ključa ne bi trebalo da bude prazno", "api_keys": "API ključevi (keys)", "app_architecture_variant": "Varijanta (Arhitektura)", "app_bar_signout_dialog_content": "Da li ste sigurni da Åželite da se odjavite?", + "app_bar_signout_dialog_ok": "Da", "app_bar_signout_dialog_title": "Odjavite se", "app_download_links": "Linkovi za preuzimanje aplikacije", "app_settings": "PodeÅĄavanja aplikacije", @@ -529,18 +546,27 @@ "archive": "Arhiva", "archive_action_prompt": "{count} je dodato u Arhivu", "archive_or_unarchive_photo": "Arhivirajte ili poniÅĄtite arhiviranje fotografije", + "archive_page_no_archived_assets": "Nisu pronađena arhivirana sredstva", + "archive_page_title": "Arhiva ({count})", "archive_size": "Veličina arhive", "archive_size_description": "Podesi veličinu arhive za preuzimanje (u GiB)", "archived": "Arhivirano", "archived_count": "{count, plural, other {Arhivirano #}}", "are_these_the_same_person": "Da li su ovo ista osoba?", "are_you_sure_to_do_this": "Jeste li sigurni da Åželite ovo da uradite?", + "array_field_not_fully_supported": "Polja niza zahtevaju ručne JSON izmene", + "asset_action_delete_err_read_only": "Ne mogu da obriÅĄem element(e) samo za čitanje, preskačem", + "asset_action_share_err_offline": "Nije mogucˁe preuzeti oflajn resurs(e), preskačem", "asset_added_to_album": "Dodato u album", "asset_adding_to_album": "Dodaje se u albumâ€Ļ", "asset_created": "Stvar kreirana", "asset_description_updated": "Opis datoteke je aÅžuriran", + "asset_filename_is_offline": "Datoteka {filename} je van mreÅže (offline)", + "asset_has_unassigned_faces": "Datoteka ima nedodeljena lica", "asset_hashing": "HeÅĄiranjeâ€Ļ", "asset_list_group_by_sub_title": "GrupiÅĄi po", + "asset_list_layout_settings_dynamic_layout_title": "Dinamični raspored", + "asset_list_layout_settings_group_automatically": "Automatski", "asset_list_layout_settings_group_by": "GrupiÅĄi zapise po", "asset_list_layout_settings_group_by_month_day": "Mesec + Dan", "asset_list_layout_sub_title": "Raspored", @@ -551,22 +577,30 @@ "asset_not_found_on_icloud": "Stvar nije pronađena na iCloud. Stvar moÅže biti nedostupna ako je loÅĄ fajl sačuvan na iCloud", "asset_offline": "Datoteka odsutna", "asset_offline_description": "Ova vanjska datoteka se viÅĄe ne nalazi na disku. Molimo kontaktirajte svog Immich administratora za pomocˁ.", + "asset_restored_successfully": "Imovina je uspeÅĄno vracˁena", "asset_skipped": "Preskočeno", "asset_skipped_in_trash": "U otpad", + "asset_trashed": "Stvar ubačena u korpu", "asset_uploaded": "Otpremljeno (Uploaded)", "asset_uploading": "Otpremanjeâ€Ļ", "asset_viewer_settings_subtitle": "Upravljajte podeÅĄavanjima pregledača galerije", "asset_viewer_settings_title": "Pregledač imovine", "assets": "Zapisi", + "assets_added_count": "Dodato {count, plural, one {# datoteka} other {# datoteka}}", "assets_added_to_album_count": "Dodato je {count, plural, one {# datoteka} other {# datoteka}} u album", "assets_count": "{count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", + "assets_deleted_permanently": "{count} elemenata trajno obrisano", + "assets_deleted_permanently_from_server": "{count} resurs(a) trajno obrisan(a) sa Immich servera", "assets_moved_to_trash_count": "PremeÅĄteno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}} u otpad", "assets_permanently_deleted_count": "Trajno izbrisano {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", "assets_removed_count": "Uklonjeno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", + "assets_removed_permanently_from_device": "{count} elemenata trajno uklonjeno sa vaÅĄeg uređaja", "assets_restore_confirmation": "Da li ste sigurni da Åželite da vratite sve svoje datoteke koje su u otpadu? Ne moÅžete poniÅĄtiti ovu radnju! Imajte na umu da se vanmreÅžna sredstva ne mogu vratiti na ovaj način.", "assets_restored_count": "Vracˁeno {count, plural, one {# datoteka} few {# datoteke} other {# datoteka}}", + "assets_restored_successfully": "{count} elemenata uspeÅĄno vracˁeno", "assets_trashed": "{count} elemenata je prebačeno u otpad", "assets_trashed_count": "Bačeno u otpad {count, plural, one {# datoteka} few{# datoteke} other {# datoteka}}", + "assets_trashed_from_server": "{count} resurs(a) obrisanih sa Immich servera", "assets_were_part_of_album_count": "{count, plural, one {Datoteka je} other {Datoteke su}} vecˁ deo albuma", "authorized_devices": "OvlaÅĄcˁeni uređaji", "automatic_endpoint_switching_subtitle": "PoveÅžite se lokalno preko određenog Wi-Fi-ja kada je dostupan i koristite alternativne veze na drugim mestima", @@ -574,44 +608,85 @@ "autoplay_slideshow": "Automatska projekcija", "back": "Nazad", "back_close_deselect": "Nazad, zatvorite ili opozovite izbor", + "background_backup_running_error": "Pozadinska rezervna kopija se trenutno izvrÅĄava, nije moguće pokrenuti ručnu rezervnu kopiju", "background_location_permission": "Dozvola za lokaciju u pozadini", "background_location_permission_content": "Da bi se menjale mreÅže dok se radi u pozadini, Imih mora *uvek* imati precizan pristup lokaciji kako bi aplikacija mogla da pročita ime Wi-Fi mreÅže", "background_options": "Pozadinske opcije", "backup": "Napravi rezervnu kopiju", + "backup_album_selection_page_albums_device": "Albuma na uređaju ({count})", "backup_album_selection_page_albums_tap": "Dodirni da uključiÅĄ, dodirni dvaput da isključiÅĄ", "backup_album_selection_page_assets_scatter": "Zapisi se mogu naći u viÅĄe različitih albuma. Odatle albumi se mogu uključiti ili isključiti tokom procesa pravljenja pozadinskih kopija.", "backup_album_selection_page_select_albums": "Odaberi albume", "backup_album_selection_page_selection_info": "Informacije o selekciji", + "backup_album_selection_page_total_assets": "Ukupno jedinstvenih ***", "backup_albums_sync": "Sinhronizacija Albuma Rezervne Kopije", + "backup_all": "Sve", + "backup_background_service_backup_failed_message": "Pravljenje rezervne kopije elemenata nije uspelo. PokuÅĄava se ponovoâ€Ļ", "backup_background_service_complete_notification": "Rezervna kopija stvari zavrÅĄena", + "backup_background_service_connection_failed_message": "Povezivanje sa serverom nije uspelo. PokuÅĄavam ponovoâ€Ļ", + "backup_background_service_current_upload_notification": "Otpremanje {filename}", "backup_background_service_default_notification": "Proveravanje novih zapisaâ€Ļ", + "backup_background_service_error_title": "GreÅĄka u pravljenju rezervnih kopija", "backup_background_service_in_progress_notification": "Pravljenje rezervnih kopija zapisaâ€Ļ", + "backup_background_service_upload_failure_notification": "NeuspeÅĄno otpremljeno: {filename}", "backup_controller_page_albums": "Napravi rezervnu kopiju albuma", + "backup_controller_page_background_app_refresh_disabled_content": "Aktiviraj pozadinsko osveÅžavanje u Opcije > Generalne > Pozadinsko OsveÅžavanje kako bi napravili rezervne kopije u pozadini.", + "backup_controller_page_background_app_refresh_disabled_title": "Pozadinsko osveÅžavanje isključeno", + "backup_controller_page_background_app_refresh_enable_button_text": "Idi u podeÅĄavanja", "backup_controller_page_background_battery_info_link": "PokaÅži mi kako", "backup_controller_page_background_battery_info_message": "Za najpouzdanije pravljenje rezervnih kopija, ugasite bilo koju opciju u optimizacijama koje bi sprečavale Immich sa pravilnim radom.\n\nOvaj postupak varira od uređaja do uređaja, proverite potrebne korake za VaÅĄ uređaj.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Optimizacija Baterije", + "backup_controller_page_background_charging": "Samo tokom punjenja", + "backup_controller_page_background_configure_error": "NeuspeÅĄno konfigurisanje pozadinskog servisa", "backup_controller_page_background_delay": "Vreme između pravljejna rezervnih kopija zapisa: {duration}", + "backup_controller_page_background_description": "Uključi pozadinski servis da automatski praviÅĄ rezervne kopije, bez da otvaraÅĄ aplikaciju", + "backup_controller_page_background_is_off": "Automatsko pravljenje rezervnih kopija u pozadini je isključeno", + "backup_controller_page_background_is_on": "Automatsko pravljenje rezervnih kopija u pozadini je uključeno", + "backup_controller_page_background_turn_off": "Isključi pozadinski servis", + "backup_controller_page_background_turn_on": "Uključi pozadinski servis", + "backup_controller_page_background_wifi": "Samo na Wi-Fi", "backup_controller_page_backup": "Rezervne kopije", "backup_controller_page_backup_selected": "Odabrano: ", "backup_controller_page_backup_sub": "ZavrÅĄeno pravljenje rezervne kopije fotografija i videa", + "backup_controller_page_created": "Napravljeno:{date}", + "backup_controller_page_desc_backup": "Uključi pravljenje rezervnih kopija u prvom planu da automatski napravite rezervne kopije kada otvorite aplikaciju.", "backup_controller_page_excluded": "Isključeno: ", + "backup_controller_page_failed": "NeuspeÅĄno ({count})", + "backup_controller_page_filename": "Ime fajla: {filename} [{size}]", + "backup_controller_page_id": "ID:{id}", + "backup_controller_page_info": "Informacije", "backup_controller_page_none_selected": "NiÅĄta odabrano", "backup_controller_page_remainder": "Ostatak", "backup_controller_page_remainder_sub": "Ostale fotografije i video snimci za otpremanje od selekcije", "backup_controller_page_server_storage": "Prostor na serveru", + "backup_controller_page_start_backup": "Pokreni pravljenje rezervne kopije", + "backup_controller_page_status_off": "Automatsko pravljenje rezervnih kopija u prvom planu je isključeno", + "backup_controller_page_status_on": "Automatsko pravljenje rezervnih kopija u prvom planu je uključeno", "backup_controller_page_storage_format": "{used} od {total} iskoriÅĄÄ‡eno", "backup_controller_page_to_backup": "Albumi koji će se otpremiti", "backup_controller_page_total_sub": "Sve jedinstvene fotografije i videi iz odabranih albuma", + "backup_controller_page_turn_off": "Isključi pravljenje rezervnih kopija u prvom planu", + "backup_controller_page_turn_on": "Uključi pravljenje rezervnih kopija u prvom planu", + "backup_controller_page_uploading_file_info": "Otpremanje svojstava datoteke", + "backup_err_only_album": "Nemoguće brisanje jedinog albuma", "backup_error_sync_failed": "Sinhronizacija nije uspela. Nije moguće procesuirati rezervnu kopiju.", "backup_info_card_assets": "zapisi", + "backup_manual_cancelled": "Otkazano", + "backup_manual_in_progress": "Otpremanje je vecˁ u toku. PokuÅĄajte kasnije", + "backup_manual_success": "Uspeh", + "backup_manual_title": "Status otpremanja", "backup_options": "Opcije Rezervne Kopije", + "backup_options_page_title": "Opcije rezervne kopije", + "backup_setting_subtitle": "Upravljajte podeÅĄavanjima otpremanja u pozadini i prednjem planu", "backup_settings_subtitle": "Upravljanje opcijama otpremanja", + "backup_upload_details_page_more_details": "Pritisni za viÅĄe detalja", "backward": "Unazad", "biometric_auth_enabled": "Biometrijska autentifikacija je uključena", "biometric_locked_out": "Onemogućena ti je biometrijska autentifikacija", "biometric_no_options": "Nema dostupnih biometrijskih opcija", "biometric_not_available": "Biometrijska autentifikacija nije podrÅžana na ovom uređaju", + "birthdate_saved": "Datum rođenja uspeÅĄno sačuvan", "birthdate_set_description": "Datum rođenja se koristi da bi se izračunale godine ove osobe u dobu određene fotografije.", "blurred_background": "Zamucˁena pozadina", "bugs_and_feature_requests": "GreÅĄke (bugs) i zahtevi za funkcije", @@ -621,6 +696,20 @@ "bulk_keep_duplicates_confirmation": "Da li ste sigurni da Åželite da zadrÅžite {count, plural, one {1 dupliranu datoteku} few {# duplirane datoteke} other {# dupliranih datoteka}}? Ovo cˁe reÅĄiti sve duplirane grupe bez brisanja bilo čega.", "bulk_trash_duplicates_confirmation": "Da li ste sigurni da Åželite grupno da odbacite {count, plural, one {1 dupliranu datoteku} few {# duplirane datoteke} other {# dupliranih datoteka}}? Ovo cˁe zadrÅžati najvecˁu datoteku svake grupe i odbaciti sve ostale duplikate.", "buy": "Kupite licencu Immich-a", + "cache_settings_clear_cache_button": "ObriÅĄi keÅĄ memoriju", + "cache_settings_clear_cache_button_title": "Ova opcija briÅĄe keÅĄ memoriju aplikacije. Ovo će bitno uticati na performanse aplikacije dok se keÅĄ memorija ne učita ponovo.", + "cache_settings_duplicated_assets_clear_button": "OČISTI", + "cache_settings_duplicated_assets_subtitle": "Fotografije i video snimci koje aplikacija ignoriÅĄe su navedeni", + "cache_settings_duplicated_assets_title": "Duplirani elementi ({count})", + "cache_settings_statistics_album": "Minijature biblioteka", + "cache_settings_statistics_full": "Pune slike", + "cache_settings_statistics_shared": "Minijature deljenih albuma", + "cache_settings_statistics_thumbnail": "Minijature", + "cache_settings_statistics_title": "IskoriÅĄÄ‡ena keÅĄ memorija", + "cache_settings_subtitle": "Kontrole za keÅĄ memoriju mobilne aplikacije Immich", + "cache_settings_tile_subtitle": "KontroliÅĄite ponaÅĄanje lokalnog skladiÅĄtenja", + "cache_settings_tile_title": "Lokalna memorija", + "cache_settings_title": "Opcije za keÅĄiranje", "camera": "Kamera", "camera_brand": "Brend kamere", "camera_model": "Model kamere", @@ -634,6 +723,7 @@ "change_date": "Promeni datum", "change_description": "Promeni opis", "change_display_order": "Promeni redosled prikaza", + "change_expiration_time": "Promeni vreme isteka", "change_location": "Promeni mesto", "change_name": "Promeni ime", "change_name_successfully": "Ime je uspeÅĄno promenjeno", @@ -647,10 +737,15 @@ "change_password_form_password_mismatch": "Å ifre se ne podudaraju", "change_password_form_reenter_new_password": "Ponovo unesite novu ÅĄifru", "change_pin_code": "Promena PIN koda", + "change_trigger": "Promeni okidač", + "change_trigger_prompt": "Da li zaista ÅželiÅĄ da iznemiÅĄ okidač? Ovo će ukloniti sve postojeće akcije i filtere.", "change_your_password": "Promeni svoju ÅĄifru", "changed_visibility_successfully": "Vidljivost je uspeÅĄno promenjena", "charging": "Puni se", "charging_requirement_mobile_backup": "Pozadinska rezervna kopija zahteva da uređaj bude na punjenju", + "check_corrupt_asset_backup": "Proverite da li postoje oÅĄtecˁene rezervne kopije imovine", + "check_corrupt_asset_backup_button": "IzvrÅĄite proveru", + "check_corrupt_asset_backup_description": "Pokrenite ovu proveru samo preko Wi-Fi mreÅže i nakon ÅĄto se napravi rezervna kopija svih podataka. Postupak moÅže potrajati nekoliko minuta.", "check_logs": "Proverite dnevnike (logs)", "checksum": "Kontrolni zbir", "choose_matching_people_to_merge": "Izaberite odgovarajucˁe osobe za spajanje", @@ -672,6 +767,8 @@ "clear_all_recent_searches": "ObriÅĄite sve nedavne pretrage", "clear_message": "ObriÅĄi poruku", "clear_value": "Jasna vrednost", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Unesi Å ifru", "client_cert_import": "Uvezi", "client_cert_import_success_msg": "Sertifikat klijenta je uvezen", "client_cert_invalid_msg": "NevaÅžecˁa datoteka sertifikata ili pogreÅĄna lozinka", @@ -680,10 +777,12 @@ "client_cert_remove_msg": "Sertifikat klijenta je uklonjen", "client_cert_subtitle": "PodrÅžava samo PKCS12 (.p12, .pfx) format. Uvoz/uklanjanje sertifikata je dostupno samo pre prijave", "client_cert_title": "SSL klijentski sertifikat [EKSPERIMENTALNO]", + "clockwise": "U smeru kazaljke", "close": "Zatvori", "collapse": "Skupi", "collapse_all": "Skupi sve", "color": "Boja", + "color_theme": "ReÅžim boja", "command": "Komanda", "command_palette_prompt": "Brzo pronađi stranice, akcije ili komande", "command_palette_to_close": "za zatvaranje", @@ -710,13 +809,18 @@ "contain": "Obuhvati", "context": "Kontekst", "continue": "Nastavi", + "control_bottom_app_bar_create_new_album": "Kreiraj novi album", + "control_bottom_app_bar_delete_from_immich": "ObriÅĄi iz Immich-a", "control_bottom_app_bar_delete_from_local": "ObriÅĄi sa uređaja", "control_bottom_app_bar_edit_location": "Izmeni lokaciju", "control_bottom_app_bar_edit_time": "Izmeni datum i vreme", + "control_bottom_app_bar_share_link": "Deli link", + "control_bottom_app_bar_share_to": "Podeli sa", "control_bottom_app_bar_trash_from_immich": "Premesti u otpad", "copied_image_to_clipboard": "Kopirana slika u međuspremnik (clipboard).", "copied_to_clipboard": "Kopirano u međuspremnik (clipboard)!", "copy_error": "GreÅĄka pri kopiranju", + "copy_file_path": "Kopiraj putanju datoteke", "copy_image": "Kopiraj sliku", "copy_link": "Kopiraj vezu", "copy_link_to_clipboard": "Kopirajte vezu u međuspremnik (clipboard)", @@ -749,21 +853,27 @@ "created_at": "Kreirano", "creating_linked_albums": "Kreiraju se povezani albumi...", "crop": "Obrezivanje", + "crop_aspect_ratio_fixed": "Fiksiran", "crop_aspect_ratio_free": "Slobodan", "crop_aspect_ratio_original": "Originalni", + "curated_object_page_title": "Stvari", "current_device": "Trenutni uređaj", "current_pin_code": "Trenutni PIN kod", "current_server_address": "Trenutna adresa servera", "custom_date": "Odabir datuma", "custom_locale": "Prilagođeni lokalitet", "custom_locale_description": "Formatirajte datume, vreme i brojeve na osnovu izabranog jezika i regiona", + "custom_url": "Odabir URL", "cutoff_date_description": "Sačuvaj slike od prethodnihâ€Ļ", + "daily_title_text_date": "E dd MMM", + "daily_title_text_date_year": "E dd MMM yyyy", "dark": "Tamno", "dark_theme": "Primeni tamnu temu", "date": "Datum", "date_after": "Datum posle", "date_and_time": "Datum i Vreme", "date_before": "Datum pre", + "date_format": "E d LLL y â€ĸ H:mm", "date_of_birth_saved": "Datum rođenja uspeÅĄno sačuvan", "date_range": "Raspon datuma", "day": "Dan", @@ -777,6 +887,8 @@ "delete_dialog_alert": "Ove stvari će permanentno biti obrisane sa Immich-a i VaÅĄeg uređaja", "delete_dialog_alert_local": "Ove stavke cˁe biti trajno uklonjene sa vaÅĄeg uređaja, ali cˁe i dalje biti dostupne na Immich serveru", "delete_dialog_alert_local_non_backed_up": "Neke stavke nisu rezervno kopirane na Immich-u i bicˁe trajno uklonjene sa vaÅĄeg uređaja", + "delete_dialog_alert_remote": "Ove stavke cˁe biti trajno izbrisane sa Immich servera", + "delete_dialog_ok_force": "Ipak obriÅĄi", "delete_dialog_title": "ObriÅĄi permanentno", "delete_duplicates_confirmation": "Da li ste sigurni da Åželite da trajno izbriÅĄete ove duplikate?", "delete_face": "IzbriÅĄi osobu", @@ -795,16 +907,21 @@ "delete_tag_confirmation_prompt": "Da li stvarno Åželite da izbriÅĄete oznaku {tagName}?", "delete_user": "ObriÅĄi korisnika", "deleted_shared_link": "ObriÅĄena deljena veza", + "deletes_missing_assets": "BriÅĄe sredstva koja nedostaju sa diska", "description": "Opis", + "description_input_hint_text": "Dodaj opis...", + "description_input_submit_error": "GreÅĄka pri aÅžuriranju opisa, proverite dnevnik za viÅĄe detalja", "deselect_all": "PoniÅĄti Izbor Svega", "details": "Detalji", "direction": "Smer", "disable": "Onemogući", "disabled": "Onemogucˁeno", + "disallow_edits": "Zabrani izmene", "discord": "Diskord", "discover": "Otkrijte", "discovered_devices": "Pronađeni uređaji", "dismiss_all_errors": "Odbacite sve greÅĄke", + "dismiss_error": "Odbaci greÅĄku", "display_options": "Opcije prikaza", "display_order": "Redosled prikaza", "display_original_photos": "PrikaÅžite originalne fotografije", @@ -813,9 +930,11 @@ "documentation": "Dokumentacija", "done": "Urađeno", "download": "Preuzmi", + "download_action_prompt": "Preuzimanje {count} stvari", "download_canceled": "Preuzmi otkazano", "download_complete": "Preuzmi zavrÅĄeno", "download_enqueue": "Preuzimanje je stavljeno u red", + "download_error": "GreÅĄka pri preuzimanju", "download_failed": "Preuzimanje nije uspelo", "download_finished": "Preuzimanje zavrÅĄeno", "download_include_embedded_motion_videos": "Ugrađeni video snimci", @@ -825,6 +944,9 @@ "download_paused": "Preuzimanje je pauzirano", "download_settings": "Preuzimanje", "download_settings_description": "Upravljajte podeÅĄavanjima vezanim za preuzimanje datoteka", + "download_started": "Preuzimanje je započeto", + "download_sucess": "Preuzimanje je uspeÅĄno", + "download_sucess_android": "Mediji su preuzeti na DCIM/Immich", "download_waiting_to_retry": "Čekanje na ponovni pokuÅĄaj", "downloading": "Preuzimanje u toku", "downloading_asset_filename": "Preuzimanje datoteke {filename}", @@ -832,7 +954,7 @@ "downloading_media": "Preuzimanje medija", "drop_files_to_upload": "Ubacite datoteke bilo gde da ih otpremite (upload-ujete)", "duplicates": "Duplikati", - "duplicates_description": "RazreÅĄite svaku grupu tako ÅĄto cˁete navesti duplikate, ako ih ima.", + "duplicates_description": "RazreÅĄite svaku grupu tako ÅĄto cˁete navesti duplikate, ako ih ima", "duration": "Trajanje", "edit": "Uredi", "edit_album": "Uredi album", @@ -841,6 +963,7 @@ "edit_date": "Uredi datum", "edit_date_and_time": "Uredi datum i vreme", "edit_date_and_time_action_prompt": "{count} datuma i vremena izmenjeno", + "edit_date_and_time_by_offset_interval": "Novi raspon datuma: {from} - {to}", "edit_description": "Izmeni opis", "edit_exclusion_pattern": "Izmenite obrazac izuzimanja", "edit_faces": "Uredi lica", @@ -856,6 +979,9 @@ "edit_user": "Uredi korisnika", "edit_workflow": "Izmeni radni tok", "editor": "Urednik", + "editor_close_without_save_prompt": "Promene necˁe biti sačuvane", + "editor_close_without_save_title": "Zatvoriti uređivač?", + "editor_confirm_reset_all_changes": "Da li sigurno ÅželiÅĄ da sve promene vratiÅĄ na početna podeÅĄavanja?", "editor_discard_edits_confirm": "Odbaci izmene", "editor_discard_edits_prompt": "ImaÅĄ nesačuvane izneme. Da li sigurno zeliÅĄ da ih odbaciÅĄ?", "editor_discard_edits_title": "Odbaci izmene?", @@ -876,6 +1002,7 @@ "enter_wifi_name": "Unesite naziv Wi-Fi mreÅže", "enter_your_pin_code_subtitle": "Unesite VaÅĄ PIN da bi ste pristupili zaključanoj fascikli", "error": "GreÅĄka", + "error_change_sort_album": "Promena redosleda sortiranja albuma nije uspela", "error_delete_face": "GreÅĄka pri brisanju osobe iz dela", "error_loading_image": "GreÅĄka pri učitavanju slike", "error_saving_image": "GreÅĄka: {error}", @@ -1007,10 +1134,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Dodaj Opis...", "exif_bottom_sheet_description_error": "GreÅĄka pri aÅžuriranju opisa", + "exif_bottom_sheet_details": "DETALJI", + "exif_bottom_sheet_location": "LOKACIJA", "exif_bottom_sheet_no_description": "Bez opisa", + "exif_bottom_sheet_people": "LJUDI", + "exif_bottom_sheet_person_add_person": "Dodaj ime", "exit_slideshow": "Izađi iz projekcije slajdova", "expand": "RaÅĄiri", "expand_all": "ProÅĄiri sve", + "experimental_settings_new_asset_list_subtitle": "U izradi", + "experimental_settings_new_asset_list_title": "Aktiviraj eksperimentalni mreÅžni prikaz fotografija", + "experimental_settings_subtitle": "Koristiti na sopstvenu odgovornost!", + "experimental_settings_title": "Eksperimentalno", "expire_after": "Da istekne nakon", "expired": "Isteklo", "expires_date": "Ističe {date}", @@ -1035,6 +1170,7 @@ "favorite_action_prompt": "{count} dodato u Omiljene", "favorite_or_unfavorite_photo": "Omiljena ili neomiljena fotografija", "favorites": "Favoriti", + "favorites_page_no_favorites": "Nije pronađen nijedan omiljeni materijal", "feature_photo_updated": "Glavna fotografija je aÅžurirana", "features": "Funkcije (features)", "features_setting_description": "Upravljajte funkcijama aplikacije", @@ -1043,6 +1179,7 @@ "filetype": "Vrsta dokumenta", "filter_people": "Filtriranje osoba", "filter_places": "Filtrirajte mesta", + "find_them_fast": "Brzo ih pronađite po imenu pomocˁu pretrage", "fix_incorrect_match": "Ispravite netačno podudaranje", "folder": "Fascikla", "folder_not_found": "Fascikla nije pronađena", @@ -1063,6 +1200,7 @@ "group_owner": "Grupirajte po vlasniku", "group_places_by": "Grupirajte mesta po...", "group_year": "Grupirajte po godini", + "haptic_feedback_switch": "Omogucˁi haptičku povratnu informaciju", "haptic_feedback_title": "Haptičke povratne informacije", "has_quota": "Ima kvotu", "header_settings_add_header_tip": "Dodaj zaglavlje", @@ -1080,10 +1218,25 @@ "hide_schema": "Sakrij ÅĄemu", "hide_text_recognition": "Sakrij prepoznavanje teksta", "hide_unnamed_people": "Sakrij neimenovane osobe", + "home_page_add_to_album_conflicts": "Dodat {added} zapis u album {album}. {failed} zapisi su već u albumu.", + "home_page_add_to_album_err_local": "Trenutno nemoguće dodati lokalne zapise u albume, preskacu se", + "home_page_add_to_album_success": "Dodate {added} stavke u album {album}.", + "home_page_album_err_partner": "JoÅĄ uvek nije mogucˁe dodati partnerska sredstva u album, preskačem", + "home_page_archive_err_local": "JoÅĄ uvek nije mogucˁe arhivirati lokalne resurse, preskačem", + "home_page_archive_err_partner": "Ne mogu da arhiviram partnersku imovinu, preskačem", "home_page_building_timeline": "Kreiranje hronoloÅĄke linije", + "home_page_delete_err_partner": "Ne mogu da obriÅĄem partnersku imovinu, preskačem", + "home_page_delete_remote_err_local": "Lokalna sredstva u obrisavanju udaljenog izbora, preskakanje", + "home_page_favorite_err_local": "Trenutno nije moguce dodati lokalne zapise u favorite, preskacu se", + "home_page_favorite_err_partner": "JoÅĄ uvek nije mogucˁe označiti partnerske resurse kao omiljene, preskačem", + "home_page_first_time_notice": "Ako je ovo prvi put da koristite aplikaciju, molimo Vas da odaberete albume koje Åželite da sačuvate", + "home_page_share_err_local": "Ne mogu da delim lokalne resurse preko linka, preskačem", + "home_page_upload_err_limit": "MoÅžete otpremiti najviÅĄe 30 elemenata istovremeno, preskačucˁi", "host": "Domacˁin (Host)", "hour": "Sat", "id": "ID", + "ignore_icloud_photos": "IgnoriÅĄite iCloud fotografije", + "ignore_icloud_photos_description": "Fotografije koje su sačuvane na iCloud-u necˁe biti otpremljene na Immich server", "image": "Fotografija", "image_alt_text_date": "{isVideo, select, true {Video} other {Image}} snimljeno {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} snimljeno sa {person1} {date}", @@ -1095,6 +1248,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} sa {person1} i {person2} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} snimljenou {city}, {country} sa {person1}, {person2} i {person3} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} snimljeno u {city}, {country} sa {person1}, {person2} i joÅĄ {additionalCount, number} drugih {date}", + "image_saved_successfully": "Slika je sačuvana", + "image_viewer_page_state_provider_download_started": "Preuzimanje je započeto", + "image_viewer_page_state_provider_download_success": "Preuzimanje UspeÅĄno", + "image_viewer_page_state_provider_share_error": "GreÅĄka pri deljenju", "immich_logo": "Logo Immich-a", "immich_web_interface": "Web interfejs Immich-a", "import_from_json": "Uvezi iz JSON-a", @@ -1134,6 +1291,9 @@ "let_others_respond": "Dozvoli da drugi komentariÅĄu", "level": "Nivo", "library": "Biblioteka", + "library_options": "Opcije biblioteke", + "library_page_device_albums": "Albumi na uređaju", + "library_page_new_album": "Novi album", "library_page_sort_asset_count": "Broj sredstava", "library_page_sort_created": "Najnovije kreirano", "library_page_sort_last_modified": "Poslednja izmena", @@ -1162,9 +1322,11 @@ "login": "Prijava", "login_disabled": "Prijava je onemogucˁena", "login_form_api_exception": "Izuzetak API-ja. Molimo vas da proverite URL adresu servera i pokuÅĄate ponovo.", + "login_form_back_button_text": "Nazad", "login_form_email_hint": "vaÅĄemail@email.com", "login_form_endpoint_hint": "http://ip-vaÅĄeg-servera:port", "login_form_endpoint_url": "URL Servera", + "login_form_err_http": "DopiÅĄi http:// ili https://", "login_form_err_invalid_email": "NevaÅžeći Email", "login_form_err_invalid_url": "Ne vaÅžeći link (URL)", "login_form_err_leading_whitespace": "Razmak ispred", @@ -1174,6 +1336,7 @@ "login_form_failed_login": "NeuspeÅĄna prijava, proveri URL servera, email i ÅĄifru", "login_form_handshake_exception": "DoÅĄlo je do izuzetka rukostiskanja sa serverom. Omogucˁite podrÅĄku za samopotpisane sertifikate u podeÅĄavanjima ako koristite samopotpisani sertifikat.", "login_form_password_hint": "ÅĄifra", + "login_form_save_login": "Ostani prijavljen", "login_form_server_error": "Nije mogucˁe povezati se sa serverom.", "login_has_been_disabled": "Prijava je onemogucˁena.", "login_password_changed_error": "DoÅĄlo je do greÅĄke prilikom aÅžuriranja lozinke", @@ -1187,6 +1350,7 @@ "main_branch_warning": "Koristite razvojnu verziju; toplo preporučujemo da koristite objavljenu verziju!", "main_menu": "Glavni meni", "make": "Kreiraj", + "manage_shared_links": "Upravljajte deljenim vezama", "manage_sharing_with_partners": "Upravljajte deljenjem sa partnerima", "manage_the_app_settings": "Upravljajte podeÅĄavanjima aplikacije", "manage_your_account": "Upravljajte vaÅĄim profilom", @@ -1194,10 +1358,13 @@ "manage_your_devices": "Upravljajte svojim prijavljenim uređajima", "manage_your_oauth_connection": "Upravljajte svojom OAuth vezom", "map": "Mapa", + "map_assets_in_bounds": "{count, plural, =0 {Nema fotografija u ovoj oblasti} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Nije mogucˁe dobiti lokaciju korisnika", + "map_location_dialog_yes": "Da", "map_location_picker_page_use_location": "Koristite ovu lokaciju", "map_location_service_disabled_content": "Usluga lokacije mora biti omogucˁena da bi se prikazivala sredstva sa vaÅĄe trenutne lokacije. Da li Åželite da je sada omogucˁite?", "map_location_service_disabled_title": "Usluga lokacije je onemogucˁena", + "map_marker_for_images": "Označivač na mapi za slike snimljene u {city}, {country}", "map_marker_with_image": "Marker na mapi sa slikom", "map_no_location_permission_content": "Potrebna je dozvola za lokaciju da bi se prikazali resursi sa vaÅĄe trenutne lokacije. Da li Åželite da je sada dozvolite?", "map_no_location_permission_title": "Dozvola za lokaciju je odbijena", @@ -1207,11 +1374,14 @@ "map_settings_date_range_option_days": "Prethodnih {days} dana", "map_settings_date_range_option_year": "ProÅĄla godina", "map_settings_date_range_option_years": "Proteklih {years} godina", + "map_settings_dialog_title": "PodeÅĄavanja Mape", "map_settings_include_show_archived": "Uključi arhivirano", "map_settings_include_show_partners": "Uključi partnere", "map_settings_only_show_favorites": "PrikaÅži samo omiljene", "map_settings_theme_settings": "Tema mape", + "map_zoom_to_see_photos": "Umanjite da biste videli fotografije", "mark_all_as_read": "Označi sve kao pročitano", + "mark_as_read": "Označi kao pročitano", "marked_all_as_read": "Sve je označeno kao pročitano", "matches": "Podudaranja", "media_type": "Vrsta medija", @@ -1237,7 +1407,11 @@ "more": "ViÅĄe", "move_off_locked_folder": "Premesti iz zaključane fascikle", "move_to_locked_folder": "Premesti u zaključanu fasciklu", + "moved_to_archive": "PremeÅĄteno {count, plural, one {# datoteka} other {# datoteke}} u arhivu", + "moved_to_library": "PremeÅĄteno {count, plural, one {# datoteka} other {# datoteke}} u biblioteku", "moved_to_trash": "PremeÅĄteno u smecˁe", + "multiselect_grid_edit_date_time_err_read_only": "Ne moÅžete da izmenite datum elemenata samo za čitanje, preskačem", + "multiselect_grid_edit_gps_err_read_only": "Ne mogu da izmenim lokaciju elemenata samo za čitanje, preskačem", "mute_memories": "PriguÅĄi secˁanja", "my_albums": "Moji albumi", "name": "Ime", @@ -1312,9 +1486,12 @@ "partner_can_access_assets": "Sve vaÅĄe fotografije i video snimci osim onih u arhiviranim i izbrisanim", "partner_can_access_location": "Lokacija na kojoj su vaÅĄe fotografije snimljene", "partner_list_user_photos": "Fotografije korisnika {user}", + "partner_list_view_all": "PrikaÅži sve", "partner_page_empty_message": "VaÅĄe fotografije joÅĄ uvek nisu deljene ni sa jednim partnerom.", "partner_page_no_more_users": "Nema viÅĄe korisnika za dodavanje", + "partner_page_partner_add_failed": "Dodavanje partnera nije uspelo", "partner_page_select_partner": "Izaberite partnera", + "partner_page_shared_to_title": "Deljeno sa", "partner_page_stop_sharing_content": "{partner} viÅĄe necˁe mocˁi da pristupi vaÅĄim fotografijama.", "partner_sharing": "Partnersko deljenje", "partners": "Partneri", @@ -1336,6 +1513,7 @@ "people": "Osobe", "people_edits_count": "Izmenjeno {count, plural, one {# osoba} other {# osobe}}", "people_feature_description": "Pregledavanje fotografija i video snimaka grupisanih po osobama", + "people_sidebar_description": "PrikaÅžite vezu do osoba na bočnoj traci", "permanent_deletion_warning": "Upozorenje za trajno brisanje", "permanent_deletion_warning_setting_description": "PrikaÅži upozorenje kada trajno briÅĄete datoteke", "permanently_delete": "Trajno izbrisati", @@ -1343,12 +1521,21 @@ "permanently_delete_assets_prompt": "Da li ste sigurni da Åželite da trajno izbriÅĄete {count, plural, one {ovu datoteku?} other {ove # datoteke?}}Ovo cˁe ih takođe ukloniti {count, plural, one {iz njihovog} other {iz njihovih}} albuma.", "permanently_deleted_asset": "Trajno izbrisana datoteka", "permanently_deleted_assets_count": "Trajno izbrisano {count, plural, one {# datoteka} other {# datoteke}}", + "permission_onboarding_back": "Nazad", + "permission_onboarding_continue_anyway": "Ipak nastavi", + "permission_onboarding_get_started": "Započnite", + "permission_onboarding_go_to_settings": "Idi na podeÅĄavanja", + "permission_onboarding_permission_denied": "Dozvola odbijena. Da biste koristili Immich, dodelite dozvole za fotografije i video zapise u PodeÅĄavanjima.", + "permission_onboarding_permission_granted": "Dozvola odobrena! Spremni ste.", + "permission_onboarding_permission_limited": "Dozvola ograničena. Da biste omogucˁili Immich-u da pravi rezervne kopije i upravlja celom vaÅĄom kolekcijom galerije, dodelite dozvole za fotografije i video zapise u PodeÅĄavanjima.", + "permission_onboarding_request": "Immich zahteva dozvolu da vidi vaÅĄe fotografije i video zapise.", "person": "Osoba", "person_birthdate": "Rođen(a) {date}", "person_hidden": "{name}{hidden, select, true { (skriveno)} other {}}", "photo_shared_all_users": "Izgleda da ste podelili svoje fotografije sa svim korisnicima ili da nemate nijednog korisnika sa kojim biste delili.", "photos": "Fotografije", "photos_and_videos": "Fotografije & Video zapisi", + "photos_count": "{count, plural, one {{count, number} fotografija} few {{count, number} fotografije} other {{count, number} fotografija}}", "photos_from_previous_years": "Fotografije iz prethodnih godina", "pick_a_location": "Odaberi lokaciju", "pin_code_changed_successfully": "PIN kod je uspeÅĄno promenjen", @@ -1373,6 +1560,7 @@ "privacy": "Privatnost", "profile": "Profil", "profile_drawer_app_logs": "Evidencija", + "profile_drawer_client_server_up_to_date": "Klijent i server su najnovije verzije", "profile_image_of_user": "Slika profila od korisnika {user}", "profile_picture_set": "Profilna slika postavljena.", "public_album": "Javni album", @@ -1393,6 +1581,7 @@ "purchase_individual_description_2": "Status podrÅĄke", "purchase_individual_title": "Individualna licenca", "purchase_input_suggestion": "Imate ključ proizvoda? Unesite ključ ispod", + "purchase_license_subtitle": "Kupite Immich da biste podrÅžali kontinuirani razvoj usluge", "purchase_lifetime_description": "DoÅživotna licenca", "purchase_option_title": "OPCIJE KUPOVINE", "purchase_panel_info_1": "Izgradnja Immich-a zahteva mnogo vremena i truda, a imamo inÅženjere koji rade na tome sa punim radnim vremenom kako bismo je učinili ÅĄto je mogucˁe boljom. NaÅĄa misija je da softver otvorenog koda i etičke poslovne prakse postanu odrÅživ izvor prihoda za programere i da stvorimo ekosistem koji poÅĄtuje privatnost sa stvarnim alternativama eksploatativnim uslugama u oblaku.", @@ -1418,16 +1607,19 @@ "reassigned_assets_to_new_person": "Ponovo dodeljeno {count, plural, one {# datoteka} other {# datoteke}} novoj osobi", "reassing_hint": "Dodelite izabrana sredstva postojecˁoj osobi", "recent": "SkoraÅĄnji", + "recent_albums": "Nedavni albumi", "recent_searches": "SkoraÅĄnje pretrage", "recently_added": "Nedavno dodato", "recently_added_page_title": "Nedavno Dodato", "recently_taken": "Nedavno snimljeno", + "recently_taken_page_title": "Nedavno Snimljeno", "refresh": "OsveÅži", "refresh_encoded_videos": "OsveÅžite kodirane (encoded) video zapise", "refresh_faces": "OsveÅži lica", "refresh_metadata": "OsveÅžite metapodatke", "refresh_thumbnails": "OsveÅžite sličice", "refreshed": "OsveÅženo", + "refreshes_every_file": "Ponovo čita sve postojecˁe i nove datoteke", "refreshing_encoded_video": "OsveÅžavanje kodiranog (encoded) videa", "refreshing_faces": "OsveÅžavanje lica", "refreshing_metadata": "OsveÅžavanje meta-podataka", @@ -1437,21 +1629,28 @@ "remove_assets_shared_link_confirmation": "Da li ste sigurni da Åželite da uklonite {count, plural, one {# datoteku} other {# datoteke}} sa ove deljene veze?", "remove_assets_title": "Ukloniti datoteke?", "remove_custom_date_range": "Uklonite prilagođeni period", + "remove_deleted_assets": "Uklonite vanmreÅžne (offline) datoteke", "remove_from_album": "ObriÅĄi iz albuma", "remove_from_favorites": "Ukloni iz favorita", "remove_from_shared_link": "Uklonite sa deljene veze", "remove_memory": "Uklonite memoriju", "remove_photo_from_memory": "Uklonite fotografiju iz ove memorije", + "remove_url": "Ukloni URL", "remove_user": "Ukloni korisnika", "removed_api_key": "Uklonjen API ključ (key): {name}", "removed_from_archive": "Uklonjeno iz arhive", "removed_from_favorites": "Uklonjeno iz omiljenih (favorites)", "removed_from_favorites_count": "{count, plural, other {Uklonjeno #}} iz omiljenih", "removed_memory": "Uklonjena memorija", + "removed_photo_from_memory": "Slika je uklonjena iz memorije", "removed_tagged_assets": "Uklonjena oznaka iz {count, plural, one {# datoteke} other {# datoteka}}", "rename": "Preimenuj", + "repair": "Popravi", + "repair_no_results_message": "Ovde cˁe se pojaviti datoteke koje nisu pracˁene i nedostaju", + "replace_with_upload": "Zamenite sa upload-om", "repository": "Repozitorijum (Repository)", "require_password": "Potrebna lozinka", + "require_user_to_change_password_on_first_login": "Zahtevati od korisnika da promeni lozinku pri prvom prijavljivanju", "rescan": "Ponovo skeniraj", "reset": "Resetovati", "reset_password": "Resetovati lozinku", @@ -1465,11 +1664,13 @@ "restore_user": "Povrati korisnika", "restored_asset": "Povracˁeno sredstvo", "resume": "Ponovo pokreni", + "retry_upload": "PokuÅĄajte ponovo da uploadujete", "review_duplicates": "Pregledajte duplikate", "role": "Uloga", "role_editor": "Urednik", "role_viewer": "Gledalac", "save": "Sačuvaj", + "save_to_gallery": "Sačuvaj u galeriju", "saved_api_key": "Sačuvan API ključ (key)", "saved_profile": "Sačuvan profil", "saved_settings": "Sačuvana podeÅĄavanja", @@ -1477,6 +1678,7 @@ "scaffold_body_error_occurred": "DoÅĄlo je do greÅĄke", "scan_all_libraries": "Skeniraj sve biblioteke", "scan_library": "Skeniraj", + "scan_settings": "PodeÅĄavanja skeniranja", "scanning_for_album": "Skeniranje albuma...", "search": "Pretraga", "search_albums": "PretraÅži albume", @@ -1505,13 +1707,25 @@ "search_no_people_named": "Nema osoba sa imenom „{name}“", "search_no_result": "Nisu pronađeni rezultati, pokuÅĄajte sa drugim terminom za pretragu ili kombinacijom", "search_options": "Opcije pretrage", + "search_page_categories": "Kategorije", + "search_page_motion_photos": "Fotografije u pokretu", + "search_page_no_objects": "Bez informacija", + "search_page_no_places": "Nema informacija o mestu", + "search_page_screenshots": "Snimci ekrana", "search_page_search_photos_videos": "PretraÅžite svoje fotografije i video zapise", + "search_page_selfies": "Selfiji", + "search_page_things": "Stvari", "search_page_view_all_button": "PrikaÅži sve", + "search_page_your_activity": "VaÅĄa aktivnost", + "search_page_your_map": "VaÅĄa mapa", "search_people": "PretraÅži osobe", "search_places": "PretraÅži mesta", "search_rating": "Pretraga po oceni...", + "search_result_page_new_search_hint": "Nova pretraga", "search_settings": "Pretraga podeÅĄavanja", "search_state": "TraÅži region...", + "search_suggestion_list_smart_search_hint_1": "Pametna pretraga je podrazumevano omogucˁena, za pretragu metapodataka koristite sintaksu ", + "search_suggestion_list_smart_search_hint_2": "m:vaÅĄ-pojam-za-pretragu", "search_tags": "PretraÅži oznake (tags)...", "search_timezone": "PretraÅži vremensku zonu...", "search_type": "Vrsta pretrage", @@ -1528,10 +1742,12 @@ "select_featured_photo": "Izaberite istaknutu fotografiju", "select_from_computer": "Izaberite sa računara", "select_keep_all": "Izaberite da zadrÅžite sve", + "select_library_owner": "Izaberite vlasnika biblioteke", "select_new_face": "Izaberite novo lice", "select_person_to_tag": "Izaberite osobu za označavanje", "select_photos": "Odaberi fotografije", "select_trash_all": "Izaberite da sve bacite na otpad", + "select_user_for_sharing_page_err_album": "NeuspeÅĄno kreiranje albuma", "selected": "Odabrano", "selected_count": "{count, plural, other {# izabrano}}", "send_message": "PoÅĄalji poruku", @@ -1554,24 +1770,38 @@ "setting_image_viewer_original_title": "Učitaj originalnu sliku", "setting_image_viewer_preview_subtitle": "Aktiviraj učitavanje slika u srednjoj rezoluciji. Deaktiviraj da se direktno učitava original, ili da se samo koristi minijatura.", "setting_image_viewer_preview_title": "Pregledaj sliku", + "setting_image_viewer_title": "Slike", "setting_languages_apply": "Primeni", "setting_languages_subtitle": "Promenite jezik aplikacije", + "setting_notifications_notify_failures_grace_period": "Obavesti o greÅĄkama u pravljenju rezervnih kopija u pozadini: {duration}", + "setting_notifications_notify_hours": "{count} sati", + "setting_notifications_notify_immediately": "odmah", "setting_notifications_notify_minutes": "{count} minuta", + "setting_notifications_notify_never": "nikada", "setting_notifications_notify_seconds": "{count} sekundi", + "setting_notifications_single_progress_subtitle": "Detaljne informacije o otpremanju, po zapisu", + "setting_notifications_single_progress_title": "PrikaÅži detalje pozadinskog pravljenja rezervnih kopija", "setting_notifications_subtitle": "Izmeni notifikacije", + "setting_notifications_total_progress_subtitle": "Ukupno otpremljenih stavki (zavrÅĄeno/ukupno stavki)", + "setting_notifications_total_progress_title": "PrikaÅži ukupan napredak pravljenja rezervnih kopija u pozadini", "setting_video_viewer_looping_title": "Petljanje (Looping)", "setting_video_viewer_original_video_subtitle": "Prilikom strimovanja videa sa servera, reprodukujte original čak i kada je dostupno transkodiranje. MoÅže dovesti do baferovanja. Video snimci dostupni lokalno se reprodukuju u originalnom kvalitetu bez obzira na ovo podeÅĄavanje.", "setting_video_viewer_original_video_title": "Prisilno originalni video", "settings": "PodeÅĄavanja", + "settings_require_restart": "Restartujte Immich da primenite ovu promenu", "settings_saved": "PodeÅĄavanja sačuvana", "setup_pin_code": "Podesite PIN kod", "share": "Podeli", + "share_add_photos": "Dodaj fotografije", + "share_assets_selected": "Izabrano je {count}", "share_dialog_preparing": "Pripremanje...", "shared": "Deljeno", "shared_album_activities_input_disable": "Komentar je onemogucˁen", "shared_album_activity_remove_content": "Da li Åželite da obriÅĄete ovu aktivnost?", "shared_album_activity_remove_title": "ObriÅĄi aktivnost", "shared_album_section_people_action_error": "GreÅĄka pri napuÅĄtanju/uklanjanju iz albuma", + "shared_album_section_people_action_leave": "Ukloni korisnika iz albuma", + "shared_album_section_people_action_remove_user": "Ukloni korisnika iz albuma", "shared_by": "Podelio", "shared_by_user": "Deli {user}", "shared_by_you": "Vi delite", @@ -1579,6 +1809,7 @@ "shared_intent_upload_button_progress_text": "Otpremljeno je {current} / {total}", "shared_link_app_bar_title": "Deljeni linkovi", "shared_link_clipboard_copied_massage": "Kopirano u međuspremnik (clipboard)", + "shared_link_clipboard_text": "Link: {link}\nLozinka: {password}", "shared_link_create_error": "GreÅĄka pri kreiranju deljenog linka", "shared_link_edit_description_hint": "Unesite opis deljenja", "shared_link_edit_expire_after_option_days": "{count} dana", @@ -1608,6 +1839,12 @@ "shared_with_partner": "Deli se sa {partner}", "sharing": "Deljenje", "sharing_enter_password": "Unesite lozinku da biste videli ovu stranicu.", + "sharing_page_album": "Deljeni albumi", + "sharing_page_description": "Napravi deljene albume da deliÅĄ fotografije i video zapise sa ljudima na tvojoj mreÅži.", + "sharing_page_empty_list": "PRAZNA LISTA", + "sharing_sidebar_description": "PrikaÅžite vezu do Deljenja na bočnoj traci", + "sharing_silver_appbar_create_shared_album": "Napravi deljeni album", + "sharing_silver_appbar_share_partner": "Podeli sa partnerom", "shift_to_permanent_delete": "pritisnite ⇧ da trajno izbriÅĄete datoteku", "show_album_options": "PrikaÅži opcije albuma", "show_albums": "PrikaÅži albume", @@ -1651,6 +1888,7 @@ "source": "Izvor", "stack": "Slaganje", "stack_duplicates": "Duplikati gomile", + "stack_select_one_photo": "Izaberite jednu glavnu fotografiju za gomilu", "stack_selected_photos": "SloÅžite izabrane fotografije", "stacked_assets_count": "Naslagano {count, plural, one {# datoteka} other {# datoteke}}", "stacktrace": "Veza do gomile", @@ -1674,11 +1912,13 @@ "swap_merge_direction": "Zamenite pravac spajanja", "sync": "Sinhronizacija", "sync_albums": "Sinhronizuj albume", + "sync_albums_manual_subtitle": "Sinhronizujte sve otpremljene video zapise i fotografije sa izabranim rezervnim albumima", "sync_upload_album_setting_subtitle": "Kreirajte i otpremite svoje fotografije i video zapise u odabrane albume na Immich-u", "tag": "Oznaka (tag)", "tag_assets": "Označite (tag) sredstva", "tag_created": "Napravljena oznaka (tag): {tag}", "tag_feature_description": "Pregledavanje fotografija i video snimaka grupisanih po logičnim temama oznaka", + "tag_not_found_question": "Ne moÅžete da pronađete oznaku? Napravite novu oznaku.", "tag_people": "Označite ljude", "tag_updated": "AÅžurirana oznaka (tag): {tag}", "tagged_assets": "Označeno (tagged) {count, plural, one {# datoteka} other {# datoteke}}", @@ -1691,10 +1931,15 @@ "theme_setting_asset_list_tiles_per_row_title": "Broj zapisa po redu {count}", "theme_setting_colorful_interface_subtitle": "Nanesite osnovnu boju na pozadinske povrÅĄine.", "theme_setting_colorful_interface_title": "Å areni interfejs", + "theme_setting_image_viewer_quality_subtitle": "Prilagodite kvalitet prikaza za detaljno pregledavanje slike", + "theme_setting_image_viewer_quality_title": "Kvalitet pregledača slika", "theme_setting_primary_color_subtitle": "Izaberite boju za glavne radnje i akcente.", "theme_setting_primary_color_title": "Primarna boja", "theme_setting_system_primary_color_title": "Koristi sistemsku boju", "theme_setting_system_theme_switch": "Automatski (Prati opcije sistema)", + "theme_setting_theme_subtitle": "Odaberi temu sistema", + "theme_setting_three_stage_loading_subtitle": "Trostepeno učitavanje moÅžda ubrza učitavanje, po cenu potroÅĄnje podataka", + "theme_setting_three_stage_loading_title": "Aktiviraj trostepeno učitavanje", "they_will_be_merged_together": "Oni cˁe biti spojeni zajedno", "third_party_resources": "Resursi trecˁih strana", "time_based_memories": "Secˁanja zasnovana na vremenu", @@ -1713,9 +1958,15 @@ "trash_all": "Baci sve u otpad", "trash_count": "Otpad {count, number}", "trash_delete_asset": "Otpad/IzbriÅĄi datoteku", + "trash_emptied": "Ispraznio smecˁe", "trash_no_results_message": "Slike i video zapisi u otpadu cˁe se pojaviti ovde.", "trash_page_delete_all": "ObriÅĄi sve", + "trash_page_empty_trash_dialog_content": "Da li Åželite da ispraznite svoja premeÅĄtena sredstva? Ovi predmeti cˁe biti trajno uklonjeni iz Immich-a", "trash_page_info": "Stavke izbačene iz otpada bicˁe trajno obrisane nakon {days} dana", + "trash_page_no_assets": "Nema elemenata u otpadu", + "trash_page_restore_all": "Vrati sve", + "trash_page_select_assets_btn": "Izaberite sredstva", + "trash_page_title": "Otpad ({count})", "trashed_items_will_be_permanently_deleted_after": "Datoteke u otpadu cˁe biti trajno izbrisane nakon {days, plural, one {# dan} few {# dana} other {# dana}}.", "type": "Vrsta", "unable_to_change_pin_code": "Nije mogucˁe promeniti PIN kod", @@ -1745,6 +1996,8 @@ "updated_password": "AÅžurirana lozinka", "upload": "Uploaduj", "upload_concurrency": "Paralelno uploadovanje", + "upload_dialog_info": "Da li Åželite da napravite rezervnu kopiju izabranih elemenata na serveru?", + "upload_dialog_title": "Otpremi element", "upload_errors": "Otpremanje je zavrÅĄeno sa {count, plural, one {# greÅĄkom} other {# greÅĄaka}}, osveÅžite stranicu da biste videli nove datoteke za otpremanje (upload).", "upload_progress": "Preostalo {remaining, number} – Obrađeno {processed, number}/{total, number}", "upload_skipped_duplicates": "Preskočeno {count, plural, one {# dupla datoteka} other {# duplih datoteka}}", @@ -1764,6 +2017,7 @@ "user_pin_code_settings_description": "Upravljajte svojim PIN kodom", "user_purchase_settings": "Kupovina", "user_purchase_settings_description": "Upravljajte kupovinom", + "user_role_set": "Postavi {user} kao {role}", "user_usage_detail": "Detalji koriÅĄcˁenja korisnika", "user_usage_stats": "Statistika koriÅĄcˁenja naloga", "user_usage_stats_description": "Pogledajte statistiku koriÅĄcˁenja naloga", @@ -1789,12 +2043,14 @@ "view_all_users": "PrikaÅži sve korisnike", "view_in_timeline": "PrikaÅži u vremenskoj liniji", "view_link": "Pogledaj vezu", + "view_links": "PrikaÅži veze", "view_name": "Pogledati", "view_next_asset": "Pogledajte sledecˁu datoteku", "view_previous_asset": "Pogledaj prethodnu datoteku", "view_qr_code": "Pogledajte QR kod", "view_stack": "PrikaÅži gomilu", "viewer_remove_from_stack": "Ukloni iz steka", + "viewer_stack_use_as_main_asset": "Koristi kao glavni resurs", "visibility_changed": "Vidljivost je promenjena za {count, plural, one {# osobu} other {# osobe}}", "waiting": "Čekam", "warning": "Upozorenje", diff --git a/i18n/sv.json b/i18n/sv.json index 596411673a..a9c63cc836 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -6,92 +6,99 @@ "action": "Åtgärd", "action_common_update": "Uppdatera", "action_description": "En uppsättning ÃĨtgärder som ska utfÃļras pÃĨ de filtrerade objekten", - "actions": "Åtgärder", + "actions": "Händelser", "active": "Aktiv", "active_count": "Aktiva: {count}", "activity": "Aktivitet", + "activity_changed": "Aktiviteten är {enabled, select, true {aktiverad} other {inaktiverad}}", "add": "Lägg till", "add_a_description": "Lägg till en beskrivning", "add_a_location": "Lägg till en plats", "add_a_name": "Lägg till ett namn", "add_a_title": "Lägg till en titel", "add_action": "Lägg till ÃĨtgärd", + "add_action_description": "Klicka fÃļr att lägga till en ÃĨtgärd att utfÃļra", "add_assets": "Lägg till objekt", "add_birthday": "Lägg till fÃļdelsedag", "add_endpoint": "Lägg till ändpunkt", "add_exclusion_pattern": "Lägg till uteslutningsmÃļnster", + "add_filter": "Lägg till filter", + "add_filter_description": "Klicka fÃļr att lägga till ett filtervillkor", "add_location": "Lägg till plats", + "add_more_users": "Lägg till fler användare", "add_partner": "Lägg till partner", + "add_path": "Lägg till sÃļkväg", "add_photos": "Lägg till foton", - "add_step": "Lägg till steg", "add_tag": "Lägg till tagg", "add_to": "Lägg till iâ€Ļ", "add_to_album": "Lägg till i album", "add_to_album_bottom_sheet_added": "Tillagd till {album}", "add_to_album_bottom_sheet_already_exists": "Redan i {album}", "add_to_album_bottom_sheet_some_local_assets": "Vissa lokala objekt kunde inte läggas till i albumet", + "add_to_album_toggle": "Växla val fÃļr {album}", "add_to_albums": "Lägg till i album", "add_to_albums_count": "Lägg till i album ({count})", "add_to_bottom_bar": "Lägg till i", + "add_to_shared_album": "Lägg till i delat album", "add_upload_to_stack": "Lägg till uppladdning till stack", "add_url": "Lägg till URL", + "add_workflow_step": "Lägg till arbetsflÃļdessteg", "added_to_archive": "Tillagd i arkiv", "added_to_favorites": "Tillagd till favoriter", "added_to_favorites_count": "{count, number} tillagda till favoriter", "admin": { - "add_exclusion_pattern_description": "Lägg till uteslutningsmÃļnster. Jokertecknen *, ** och ? stÃļds. Använd \"**/Raw/**\" fÃļr att ignorera alla filer i alla kataloger med namnet \"Raw\". Använd \"**/*.tif\" fÃļr att ignorera alla filer som slutar med \".tif\". Använd \"/path/to/ignore/**\" fÃļr att ignorera en absolut sÃļkväg.", + "add_exclusion_pattern_description": "Lägg till exkluderande mÃļnster. Matchning med jokertecken *, ** samt ? stÃļdjs. FÃļr att ignorera alla filer i samtliga mappar som heter \"Raw\", använd \"**/Raw/**\". FÃļr att ignorera alla filer som slutar med \".tif\", använd \"**/*.tif\". FÃļr att ignorera en absolut sÃļkväg, använd \"/sÃļkväg/att/ignorera/**\".", "admin_user": "Adminanvändare", "asset_offline_description": "Detta externa biblioteksobjekt finns inte längre pÃĨ disken och har flyttats till papperskorgen. Om filen flyttades inom biblioteket, kontrollera din tidslinje fÃļr det nya motsvarande objektet. FÃļr att ÃĨterställa detta objekt, se till att filsÃļkvägen nedan kan nÃĨs av Immich och skanna biblioteket.", "authentication_settings": "Autentiseringsinställningar", - "authentication_settings_description": "Hantera lÃļsenord, OAuth och andra autentiseringsinställningar", - "authentication_settings_disable_all": "Är du säker pÃĨ att du vill inaktivera alla inloggningsmetoder? Inloggningen inaktiveras helt.", - "authentication_settings_reenable": "Använd ett serverkommando fÃļr att aktivera dem igen.", - "background_task_job": "Bakgrundsuppgifter", - "backup_database": "Skapa databasdump", - "backup_database_enable_description": "Aktivera databasdumpar", + "authentication_settings_description": "Hantera lÃļsenord, OAuth, och andra autentiseringsinställningar", + "authentication_settings_disable_all": "Är du säker pÃĨ att du vill inaktivera alla inloggningsmetoder? Inloggning kommer att helt inaktiveras.", + "authentication_settings_reenable": "FÃļr att ÃĨteraktivera, använd Server Command.", + "background_task_job": "Bakgrundsaktiviteter", + "backup_database": "Skapa Databasdump", + "backup_database_enable_description": "Aktivera dumpning av databas", "backup_keep_last_amount": "Antal databasdumpar att behÃĨlla", - "backup_onboarding_1_description": "extern säkerhetskopia i molnet eller pÃĨ en annan fysisk plats.", - "backup_onboarding_2_description": "lokala kopior pÃĨ olika enheter. Detta omfattar huvudfilerna och en lokal säkerhetskopia av dem.", - "backup_onboarding_3_description": "kopior av dina data totalt, inklusive originalfilerna. Detta omfattar en extern kopia och tvÃĨ lokala kopior.", - "backup_onboarding_description": "En 3-2-1-strategi fÃļr säkerhetskopiering rekommenderas fÃļr att skydda dina data. FÃļr en heltäckande säkerhetskopiering bÃļr du behÃĨlla kopior av uppladdade foton och videor samt av Immich-databasen.", + "backup_onboarding_1_description": "extern kopia i molnet eller pÃĨ en annan fysisk plats.", + "backup_onboarding_2_description": "lokala kopior pÃĨ olika enheter. Detta inkluderar huvudfilerna och en lokal säkerhetskopia av dessa filer.", + "backup_onboarding_3_description": "totala kopior av dina data, inklusive originalfilerna. Detta inkluderar 1 extern kopia och 2 lokala kopior.", + "backup_onboarding_description": "En 3-2-1 säkerhetskopieringsstrategi rekommenderas fÃļr att skydda din data. Du bÃļr även spara kopior av dina uppladdade foton/videor samt Immich-databasen fÃļr en heltäckande säkerhetskopieringslÃļsning.", "backup_onboarding_footer": "FÃļr mer information om säkerhetskopiering av Immich, se dokumentationen.", - "backup_onboarding_parts_title": "En 3-2-1-säkerhetskopiering omfattar:", + "backup_onboarding_parts_title": "En 3-2-1 säkerhetskopiering inkluderar:", "backup_onboarding_title": "Säkerhetskopior", - "backup_settings": "Inställningar fÃļr databasdump", + "backup_settings": "Inställningar databasdump", "backup_settings_description": "Hantera inställningar fÃļr databasdumpning.", - "cleared_jobs": "Rensade jobb fÃļr: {job}", - "config_set_by_file": "Konfigurationen anges fÃļr närvarande i en konfigurationsfil", - "confirm_delete_library": "Är du säker pÃĨ att du vill ta bort biblioteket {library}?", - "confirm_delete_library_assets": "Är du säker pÃĨ att du vill ta bort det här biblioteket? DÃĨ tas {count, plural, one {# objekt i biblioteket} other {alla # objekt i biblioteket}} bort frÃĨn Immich. Åtgärden kan inte ÃĨngras. Filerna ligger kvar pÃĨ disken.", + "cleared_jobs": "Rensade jobben fÃļr: {job}", + "config_set_by_file": "Konfigurationen är satt av en konfigurationsfil", + "confirm_delete_library": "Är du säker pÃĨ att du vill radera {library} album?", + "confirm_delete_library_assets": "Är du säker pÃĨ att du vill radera detta album? {count, plural, one {# objekt} other {Samtliga # objekt}} kommer att tas bort frÃĨn Immich och ÃĨtgärden kan inte ÃĨngras. Filerna kommer att behÃĨllas pÃĨ hÃĨrddisken.", "confirm_email_below": "FÃļr att bekräfta, skriv ”{email}” nedan", - "confirm_reprocess_all_faces": "Är du säker pÃĨ att du vill bearbeta alla ansikten igen? Namnen pÃĨ personer rensas ocksÃĨ.", + "confirm_reprocess_all_faces": "Är du säker pÃĨ att du vill ÃĨterprocessa alla ansikten? Detta kommer ocksÃĨ rensa namngivna personer.", "confirm_user_password_reset": "Är du säker pÃĨ att du vill ÃĨterställa lÃļsenordet fÃļr {user}?", "confirm_user_pin_code_reset": "Är du säker pÃĨ att du vill ÃĨterställa PIN-koden fÃļr {user}?", - "copy_config_to_clipboard_description": "Kopiera den aktuella systemkonfigurationen som ett JSON-objekt till urklippet", + "copy_config_to_clipboard_description": "Kopiera den aktuella systemkonfigurationen som ett JSON-objekt till urklipp", "create_job": "Skapa jobb", "cron_expression": "Cron-uttryck", - "cron_expression_description": "Ange skanningsintervallet i cron-format. Mer information finns exempelvis pÃĨ Crontab Guru", - "cron_expression_presets": "FÃļrval fÃļr cron-uttryck", + "cron_expression_description": "Sätt skanningsintervallet genom att använda cron-format. FÃļr mer information se Crontab Guru", + "cron_expression_presets": "Cron-uttryck fÃļrinställningar", "disable_login": "Inaktivera inloggning", - "download_csv": "Ladda ner CSV", - "duplicate_detection_job_description": "KÃļr maskininlärning pÃĨ objekt fÃļr att hitta liknande bilder. Bygger pÃĨ smart sÃļkning", - "exclusion_pattern_description": "Med uteslutningsmÃļnster kan du ignorera filer och mappar när biblioteket skannas. Det är användbart fÃļr mappar som innehÃĨller filer som inte ska importeras, exempelvis RAW-filer.", + "duplicate_detection_job_description": "KÃļr maskininlärning pÃĨ objekt fÃļr att upptäcka liknande bilder. Bygger pÃĨ Smart Search", + "exclusion_pattern_description": "ExkluderingsmÃļnster tillÃĨter dig att ignorera filer och mappar när skanning gÃļrs av ditt album. Detta är användbart om du har mappar som innehÃĨller filer som du inte vill importera, t.ex. RAW-filer.", "export_config_as_json_description": "Ladda ner den aktuella systemkonfigurationen som en JSON-fil", - "external_libraries_page_description": "Administrationssida fÃļr externa bibliotek", + "external_libraries_page_description": "AdministratÃļrs externa bibliotekssida", "face_detection": "Ansiktsdetektering", - "face_detection_description": "Detektera ansikten i objekt med hjälp av maskininlärning. FÃļr videor används endast miniatyrbilden. ”Uppdatera” bearbetar alla objekt igen. Ӂterställ” rensar dessutom alla befintliga ansiktsdata. ”Saknade” kÃļplacerar objekt som ännu inte har bearbetats. När ansiktsdetekteringen är klar kÃļplaceras de detekterade ansiktena fÃļr ansiktsigenkänning och grupperas som befintliga eller nya personer.", - "facial_recognition_job_description": "Gruppera detekterade ansikten efter person. Steget kÃļrs efter att ansiktsdetekteringen är klar. Ӂterställ” grupperar om alla ansikten. ”Saknade” kÃļplacerar ansikten som ännu inte har tilldelats en person.", - "failed_job_command": "Kommandot {command} misslyckades fÃļr jobbet {job}", - "force_delete_user_warning": "VARNING: Användaren och alla objekt tas bort omedelbart. Åtgärden kan inte ÃĨngras och filerna kan inte ÃĨterställas.", + "face_detection_description": "Identifiera ansikten i foton med hjälp av maskininlärning. FÃļr videor används endast miniatyrbilden. \"Ladda om\" gÃļr om sÃļkningen fÃļr alla objekt. \"Återställ\" rensar all gällande ansikts-data. \"Saknade\" letar i de objekt som ännu inte sÃļkts igenom. Alla identifierade ansikten läggs sedan i jobbkÃļn fÃļr ansiktsigenkänning där de mappas till nya eller befintliga personer.", + "facial_recognition_job_description": "Gruppera upptäckta ansikten till personer. Det här steget kÃļrs efter att ansiktsdetektering är klar. \"Återställ\" (ÃĨter-)grupperar alla ansikten. \"Saknade\" kÃļar ansikten som inte har en person tilldelad.", + "failed_job_command": "Kommando {command} misslyckades fÃļr jobb: {job}", + "force_delete_user_warning": "VARNING: Detta tar omedelbart bort användaren och alla mediafiler. Detta kan inte ÃĨngras och filerna kan inte ÃĨterställas.", "image_format": "Format", "image_format_description": "WebP producerar mindre filer än JPEG, men kodas lÃĨngsammare.", - "image_fullsize_description": "Bild i full storlek utan metadata, används vid inzoomning", - "image_fullsize_enabled": "Aktivera generering av bilder i full storlek", - "image_fullsize_enabled_description": "Generera bilder i full storlek fÃļr format som inte är webbvänliga. När \"FÃļredra inbäddad fÃļrhandsvisning\" är aktiverat används inbäddade fÃļrhandsvisningar direkt utan konvertering. Webbvänliga format som JPEG pÃĨverkas inte.", - "image_fullsize_quality_description": "Kvalitet fÃļr bilder i full storlek, frÃĨn 1 till 100. HÃļgre värden ger bättre kvalitet men stÃļrre filer.", + "image_fullsize_description": "Fullstor bild med borttagen metadata, används vid inzoomning", + "image_fullsize_enabled": "Använd fullstor bildgenerering", + "image_fullsize_enabled_description": "Generera fullstor bild fÃļr icke webbvänliga format. När \"Använd inbäddade fÃļrhandsvisningar\" är aktiverat används inbäddad fÃļrhandsvisning utan konvertering. PÃĨverkar inte webbvänliga format som JPEG.", + "image_fullsize_quality_description": "Bildkvalitet fÃļr fullstora bilder 1-100. HÃļgre värde ger bättre kvalitet men stÃļrre filer.", "image_fullsize_title": "Inställningar fÃļr fullstora bilder", "image_prefer_embedded_preview": "FÃļredra inbäddad fÃļrhandsgranskning", - "image_prefer_embedded_preview_setting_description": "Använd inbäddade fÃļrhandsvisningar i RAW-foton som underlag fÃļr bildbehandling när de finns. Det kan ge mer korrekta färger i vissa bilder, men fÃļrhandsvisningens kvalitet beror pÃĨ kameran och bilden kan innehÃĨlla fler komprimeringsartefakter.", + "image_prefer_embedded_preview_setting_description": "Använd inbäddade fÃļrhandsvisningar i RAW-foton som indata till bildbehandling och när det är tillgängligt. Detta kan ge mer exakta färger fÃļr vissa bilder, men kvaliteten pÃĨ fÃļrhandsgranskningen är kameraberoende och bilden kan ha fler komprimeringsartefakter.", "image_prefer_wide_gamut": "FÃļredra brett färgomfÃĨng", "image_prefer_wide_gamut_setting_description": "Använd Display P3 fÃļr miniatyrer. Detta bevarar livfullheten bättre hos bilder med bred färgrymd, men bilder kan se annorlunda ut pÃĨ gamla enheter med en gammal webbläsarversion. sRGB-bilder behÃĨlls som sRGB fÃļr att undvika färgskiftningar.", "image_preview_description": "Mellanstor bild med avskalad metadata, används vid visning av ett enskilt objekt och fÃļr maskininlärning", @@ -108,77 +115,62 @@ "image_thumbnail_quality_description": "Miniatyrkvalitet frÃĨn 1-100. HÃļgre är bättre, men ger stÃļrre filer och kan minska appens fÃļljsamhet.", "image_thumbnail_title": "Miniatyrbildsinställningar", "import_config_from_json_description": "Importera systemkonfiguration genom att ladda upp en JSON-konfigurationsfil", - "integrity_checks_checksum_files": "Kontrollsummor fÃļr filer", - "integrity_checks_checksum_files_description": "Konfigurera kontrollen av filers kontrollsummor", - "integrity_checks_checksum_files_enable_description": "Aktivera kontroll av filers kontrollsummor", - "integrity_checks_checksum_files_percentage_limit": "Procentuell gräns", - "integrity_checks_checksum_files_percentage_limit_description": "Ange den hÃļgsta andelen, mellan 0,01 och 1, som kontrollsummekontrollen fÃĨr bearbeta vid varje intervall.", - "integrity_checks_checksum_files_time_limit": "Tidsgräns", - "integrity_checks_checksum_files_time_limit_description": "Ange hur länge kontrollsummekontrollen hÃļgst fÃĨr kÃļras vid varje intervall. (ms)", - "integrity_checks_missing_files": "Saknade filer", - "integrity_checks_missing_files_description": "Konfigurera frekvensen och aktivera eller inaktivera kontrollen av saknade filer", - "integrity_checks_missing_files_enable_description": "Aktivera kontroll av saknade filer", - "integrity_checks_settings": "Integritetskontroller", - "integrity_checks_settings_description": "Hantera intervall fÃļr integritetskontroller", - "integrity_checks_untracked_files": "OspÃĨrade filer", - "integrity_checks_untracked_files_description": "Konfigurera frekvensen och aktivera eller inaktivera kontrollen av ospÃĨrade filer", - "integrity_checks_untracked_files_enable_description": "Aktivera kontroll av ospÃĨrade filer", "job_concurrency": "{job} samtidighet", "job_created": "Jobb skapat", "job_not_concurrency_safe": "Det här jobbet är inte samtidighetssäkert.", "job_settings": "Jobbinställningar", "job_settings_description": "Hantera samtidiga jobb", "jobs_delayed": "{jobCount, plural, one{# fÃļrsenad} other {# fÃļrsenade}}", - "jobs_failed": "{jobCount, plural, other {# misslyckade}}", + "jobs_failed": "{jobCount, plural, other {# misslyckades}}", "jobs_over_time": "Jobb Ãļver tid", - "library_created": "Bibliotek skapat: {library}", + "library_created": "Skapat bibliotek: {library}", "library_deleted": "Biblioteket har tagits bort", "library_details": "Biblioteksdetaljer", - "library_folder_description": "Ange en mapp som ska importeras. Mappen och dess undermappar skannas efter bilder och videor.", + "library_folder_description": "Välj en mapp att importera. Denna mapp, inklusive undermappar, kommer sÃļkas igenom efter bilder och videor.", "library_remove_exclusion_pattern_prompt": "Är du säker pÃĨ att du vill ta bort detta exkluderingsmÃļnster?", "library_remove_folder_prompt": "Är du säker pÃĨ att du vill ta bort denna importmapp?", "library_scanning": "Periodisk skanning", - "library_scanning_description": "Konfigurera periodisk skanning av bibliotek", - "library_scanning_enable_description": "Aktivera periodisk skanning av bibliotek", + "library_scanning_description": "Konfigurera periodisk biblioteksskanning", + "library_scanning_enable_description": "Aktivera periodisk biblioteksskanning", "library_settings": "Externa bibliotek", "library_settings_description": "Hantera inställningar fÃļr externa bibliotek", - "library_tasks_description": "Skanna externa bibliotek efter nya och ändrade objekt", + "library_tasks_description": "SÃļk igenom externa bibliotek efter nya och/eller fÃļrändrade objekt", "library_updated": "Bibliotek uppdaterat", - "library_watching_enable_description": "Bevaka filändringar i externa bibliotek", - "library_watching_settings": "Biblioteksbevakning [EXPERIMENTELLT]", - "library_watching_settings_description": "Bevaka ändrade filer automatiskt", + "library_watching_enable_description": "Bevaka externa bibliotek fÃļr filändringar", + "library_watching_settings": "Bevaka bibliotek [EXPERIMENTELLT]", + "library_watching_settings_description": "Bevaka automatiskt filfÃļrändringar", "logging_enable_description": "Aktivera loggning", - "logging_level_description": "Ange vilken loggnivÃĨ som ska användas när loggning är aktiverad.", + "logging_level_description": "Vilken loggnivÃĨ som ska användas vid aktivering.", "logging_settings": "Loggning", "machine_learning_availability_checks": "Tillgänglighetskontroller", - "machine_learning_availability_checks_description": "Identifiera automatiskt tillgängliga maskininlärningsservrar och använd dem i fÃļrsta hand", + "machine_learning_availability_checks_description": "Upptäck och fÃļredrar automatiskt tillgängliga maskininlärningsservrar", "machine_learning_availability_checks_enabled": "Aktivera tillgänglighetskontroller", - "machine_learning_availability_checks_interval": "Kontrollintervall", + "machine_learning_availability_checks_interval": "Kontrollera intervall", "machine_learning_availability_checks_interval_description": "Intervall i millisekunder mellan tillgänglighetskontroller", - "machine_learning_availability_checks_timeout": "Tidsgräns fÃļr begäran", - "machine_learning_availability_checks_timeout_description": "Tidsgräns i millisekunder fÃļr tillgänglighetskontroller", + "machine_learning_availability_checks_timeout": "Begär timeout", + "machine_learning_availability_checks_timeout_description": "Timeout i millisekunder fÃļr tillgänglighetskontroller", "machine_learning_clip_model": "CLIP-modell", - "machine_learning_clip_model_description": "Namnet pÃĨ en CLIP-modell som anges här. När modellen ändras mÃĨste jobbet ”Smart sÃļkning” kÃļras igen fÃļr alla bilder.", + "machine_learning_clip_model_description": "Namnet pÃĨ en CLIP-modell listad här. Observera att du mÃĨste kÃļra ett \"Smart SÃļkning\" jobb fÃļr alla bilder när du ändrar modell.", "machine_learning_duplicate_detection": "Dubblettdetektering", "machine_learning_duplicate_detection_enabled": "Aktivera dubblettdetektering", - "machine_learning_duplicate_detection_enabled_description": "Även om funktionen är inaktiverad dedupliceras exakt identiska objekt.", + "machine_learning_duplicate_detection_enabled_description": "Om inaktiverad, kommer identiska objekt fortfarande att vara deduplicerade.", "machine_learning_duplicate_detection_setting_description": "Använd CLIP-inbäddningar fÃļr att hitta troliga dubbletter", "machine_learning_enabled": "Aktivera maskininlärning", "machine_learning_enabled_description": "Om det är inaktiverat kommer alla ML-funktioner att inaktiveras oavsett inställningarna nedan.", "machine_learning_facial_recognition": "Ansiktsigenkänning", - "machine_learning_facial_recognition_description": "Upptäck, känn igen och gruppera ansikten i bilder", + "machine_learning_facial_recognition_description": "Upptäck, känna igen och gruppera ansikten i bilder", "machine_learning_facial_recognition_model": "Ansiktsigenkänningsmodell", - "machine_learning_facial_recognition_model_description": "Modellerna anges i fallande storleksordning. StÃļrre modeller är lÃĨngsammare och använder mer minne men ger bättre resultat. När modellen ändras mÃĨste ansiktsdetekteringsjobbet kÃļras igen fÃļr alla bilder.", + "machine_learning_facial_recognition_model_description": "Modeller är listade i fallande storleksordning. StÃļrre modeller är lÃĨngsammare och använder mer minne, men ger bättre resultat. Observera att du mÃĨste kÃļra Face Detection-jobbet fÃļr alla bilder när du ändrar en modell.", "machine_learning_facial_recognition_setting": "Aktivera ansiktsigenkänning", - "machine_learning_facial_recognition_setting_description": "När funktionen är inaktiverad kodas bilder inte fÃļr ansiktsigenkänning och visas därfÃļr inte under Personer pÃĨ sidan Utforska.", - "machine_learning_max_detection_distance": "StÃļrsta detekteringsavstÃĨnd", - "machine_learning_max_detection_distance_description": "StÃļrsta avstÃĨnd mellan tvÃĨ bilder fÃļr att de ska betraktas som dubbletter, frÃĨn 0,001 till 0,1. HÃļgre värden hittar fler dubbletter men kan ge falska positiva resultat.", + "machine_learning_facial_recognition_setting_description": "Om avmarkerad kommer bilder inte att kodas till ansiktsigenkänningen vilket innebär att bilder inte kommer att läggas till i listan av igenkända personer pÃĨ sidan Utforska.", + "machine_learning_max_detection_distance": "Maximal detektions avstÃĨnd", + "machine_learning_max_detection_distance_description": "Maximalt avstÃĨnd mellan tvÃĨ bilder fÃļr att Ãļverväga dem dubbletter, frÃĨn 0,001-0,1. HÃļgre värden kommer att upptäcka fler dubbletter, men kan leda till falsk positivt.", "machine_learning_max_recognition_distance": "Maximalt igenkänningsavstÃĨnd", - "machine_learning_max_recognition_distance_description": "StÃļrsta avstÃĨnd mellan tvÃĨ ansikten fÃļr att de ska betraktas som samma person, frÃĨn 0 till 2. Ett lägre värde minskar risken att tvÃĨ personer behandlas som samma person, medan ett hÃļgre värde minskar risken att samma person behandlas som tvÃĨ personer. Det är enklare att slÃĨ samman tvÃĨ personer än att dela en person i tvÃĨ, sÃĨ välj helst ett lägre trÃļskelvärde.", - "machine_learning_min_detection_score": "Lägsta detekteringspoäng", - "machine_learning_min_detection_score_description": "Lägsta konfidenspoäng, frÃĨn 0 till 1, fÃļr att ett ansikte ska detekteras. Lägre värden hittar fler ansikten men kan ge falska positiva resultat.", - "machine_learning_min_recognized_faces": "Minsta antal igenkända ansikten", - "machine_learning_min_recognized_faces_description": "Minsta antal igenkända ansikten som krävs fÃļr att skapa en person. Ett hÃļgre värde gÃļr ansiktsigenkänningen mer exakt men Ãļkar risken att ett ansikte inte tilldelas en person.", + "machine_learning_max_recognition_distance_description": "Det maximala avstÃĨndet mellan tvÃĨ ansikten fÃļr att anses som samma person, frÃĨn 0-2. Sänkning av denna kan medfÃļra märkning av tvÃĨ personer som samma person, samtidigt som det kan fÃļrhindra att märkning av samma person som tvÃĨ olika personer. Observera att det är lättare att slÃĨ samman tvÃĨ personer än att dela en person i tvÃĨ, sÃĨ ligg hellre närmare den lägre trÃļskel om det är mÃļjligt.", + "machine_learning_min_detection_score": "Minsta detektions poäng", + "machine_learning_min_detection_score_description": "Lägsta självsäkerhetsnivÃĨ fÃļr att en sida ska upptäckas mellan 0-1. Lägre värden upptäcker fler sidor men kan resultera i falska positiv.", + "machine_learning_min_recognized_faces": "Minsta identifierade ansikten", + "machine_learning_min_recognized_faces_description": "Minsta antal identifierade ansikten fÃļr att en person ska kunna skapas. Om detta Ãļkas blir ansiktsigenkänningen mer exakt, men risken fÃļr att ett ansikte inte kopplas till en person Ãļkar.", "machine_learning_ocr": "OCR", "machine_learning_ocr_description": "Använd maskininlärning fÃļr att känna igen text i bilder", "machine_learning_ocr_enabled": "Aktivera OCR", @@ -186,47 +178,31 @@ "machine_learning_ocr_max_resolution": "Maximal upplÃļsning", "machine_learning_ocr_max_resolution_description": "FÃļrhandsvisningar Ãļver denna upplÃļsning kommer att ändras i storlek samtidigt som bildfÃļrhÃĨllandet behÃĨlls. HÃļgre värden är mer exakta, men tar längre tid att bearbeta och använder mer minne.", "machine_learning_ocr_min_detection_score": "Lägsta detektionspoäng", - "machine_learning_ocr_min_detection_score_description": "Lägsta konfidenspoäng, frÃĨn 0 till 1, fÃļr att text ska detekteras. Lägre värden hittar mer text men kan ge falska positiva resultat.", + "machine_learning_ocr_min_detection_score_description": "Lägsta konfidenspoäng fÃļr att text ska kunna detekteras är mellan 0 och 1. Lägre värden kommer att detektera mer text men kan resultera i falska positiva resultat.", "machine_learning_ocr_min_recognition_score": "Lägsta igenkänningspoäng", - "machine_learning_ocr_min_score_recognition_description": "Lägsta konfidenspoäng, frÃĨn 0 till 1, fÃļr att detekterad text ska kännas igen. Lägre värden känner igen mer text men kan ge falska positiva resultat.", + "machine_learning_ocr_min_score_recognition_description": "Lägsta konfidenspoäng fÃļr att detekterad text ska kunna identifieras är 0–1. Lägre värden identifierar mer text men kan resultera i falska positiva resultat.", "machine_learning_ocr_model": "OCR-modell", "machine_learning_ocr_model_description": "Servermodeller är mer exakta än mobilmodeller men tar längre tid att bearbeta och använder mer minne.", - "machine_learning_settings": "Inställningar fÃļr maskininlärning", + "machine_learning_settings": "Inställningar FÃļr Maskininlärning", "machine_learning_settings_description": "Hantera funktioner och inställningar fÃļr maskininlärning", - "machine_learning_smart_search": "Smart sÃļkning", + "machine_learning_smart_search": "Smart SÃļkning", "machine_learning_smart_search_description": "SÃļk semantiskt efter bilder med hjälp av CLIP-inbäddningar", "machine_learning_smart_search_enabled": "Aktivera smart sÃļkning", "machine_learning_smart_search_enabled_description": "Om inaktiverat kommer bilder inte att kodas fÃļr smart sÃļkning.", - "machine_learning_url_description": "Maskininlärningsserverns URL. Om flera URL:er anges provas servrarna en i taget i angiven ordning tills en svarar. Servrar som inte svarar ignoreras tillfälligt tills de är tillgängliga igen.", - "maintenance_backup_management": "Hantering av säkerhetskopior", + "machine_learning_url_description": "Maskininlärningsserverns URL. Om det är mer än en URL tillagd sÃĨ kommer ett fÃļrsÃļk per URL att utfÃļras tills nÃĨgon av dom svarar, fÃļrsÃļken gÃļrs i kronologisk ordning. Servrar som inte svarar kommer tillfälligt ignoreras tills de är nÃĨbara igen.", "maintenance_delete_backup": "Ta bort säkerhetskopia", - "maintenance_delete_backup_description": "Filen tas bort oÃĨterkalleligen.", + "maintenance_delete_backup_description": "Den här filen kommer att raderas oÃĨterkalleligt.", "maintenance_delete_error": "Det gick inte att ta bort säkerhetskopian.", - "maintenance_integrity_check": "Kontrollera", - "maintenance_integrity_check_all": "Kontrollera alla", - "maintenance_integrity_checksum_mismatch": "Kontrollsummeavvikelse", - "maintenance_integrity_checksum_mismatch_description": "Filer vars kontrollsumma pÃĨ disken inte stämmer Ãļverens med den kontrollsumma som Immich har lagrat i sin databas.", - "maintenance_integrity_checksum_mismatch_job": "Kontrollera avvikande kontrollsummor", - "maintenance_integrity_checksum_mismatch_refresh_job": "Uppdatera rapporter om avvikande kontrollsummor", - "maintenance_integrity_missing_file": "Filer saknas", - "maintenance_integrity_missing_file_description": "Filer som har registrerats i Immichs databas men som saknas i filsystemet.", - "maintenance_integrity_missing_file_job": "Kontrollera om filer saknas", - "maintenance_integrity_missing_file_refresh_job": "Uppdatera rapporter om saknade filer", - "maintenance_integrity_report": "Integritetsrapport", - "maintenance_integrity_untracked_file": "OspÃĨrade filer", - "maintenance_integrity_untracked_file_description": "Filer i Immichs kataloger som saknar en post i Immichs databas.", - "maintenance_integrity_untracked_file_job": "Kontrollera ospÃĨrade filer", - "maintenance_integrity_untracked_file_refresh_job": "Uppdatera rapporter om ospÃĨrade filer", "maintenance_restore_backup": "Återställ säkerhetskopia", - "maintenance_restore_backup_description": "Immich rensas och ÃĨterställs frÃĨn den valda säkerhetskopian. En ny säkerhetskopia skapas innan ÃĨterställningen fortsätter.", + "maintenance_restore_backup_description": "Immich kommer att ÃĨterställas frÃĨn den valda säkerhetskopian. En ny säkerhetskopia kommer att skapas innan du fortsätter.", "maintenance_restore_backup_different_version": "Denna säkerhetskopia skapades med en annan version av Immich!", - "maintenance_restore_backup_unknown_version": "Det gick inte att fastställa säkerhetskopians version.", + "maintenance_restore_backup_unknown_version": "Kunde inte fastställa säkerhetskopians verison.", "maintenance_restore_database_backup": "Återställ databasens säkerhetskopia", "maintenance_restore_database_backup_description": "Återställ till ett tidigare databasläge med hjälp av en säkerhetskopia", "maintenance_settings": "UnderhÃĨll", "maintenance_settings_description": "FÃļrsätt Immich i underhÃĨllsläge.", "maintenance_start": "Växla till underhÃĨllsläge", - "maintenance_start_error": "Det gick inte att starta underhÃĨllsläget.", + "maintenance_start_error": "Misslyckades att starta underhÃĨllsläget.", "maintenance_upload_backup": "Ladda upp en säkerhetskopia av databasen", "maintenance_upload_backup_error": "Det gick inte att ladda upp säkerhetskopian. Är det en .sql/.sql.gz-fil?", "manage_concurrency": "Hantera samtidighet", @@ -234,96 +210,92 @@ "manage_log_settings": "Hantera logginställningar", "map_dark_style": "MÃļrk stil", "map_enable_description": "Aktivera kartfunktioner", - "map_gps_settings": "Inställningar fÃļr karta och GPS", - "map_gps_settings_description": "Hantera inställningar fÃļr karta, GPS och omvänd geokodning", + "map_gps_settings": "Karta & GPS Inställningar", + "map_gps_settings_description": "Ändra kartor & GPS (Omvänd geokodning) inställningar", "map_implications": "Kartfunktionen är beroende av en extern kartbitstjänst (tiles.immich.cloud)", "map_light_style": "Ljus stil", - "map_manage_reverse_geocoding_settings": "Hantera inställningar fÃļr omvänd geokodning", - "map_reverse_geocoding": "Omvänd geokodning", + "map_manage_reverse_geocoding_settings": "Hantera inställningar fÃļr Omvänd geokodning", + "map_reverse_geocoding": "Omvänd Geokodning", "map_reverse_geocoding_enable_description": "Aktivera omvänd geokodning", "map_reverse_geocoding_settings": "Inställningar fÃļr omvänd geokodning", "map_settings": "Karta", "map_settings_description": "Hantera kartinställningar", - "map_style_description": "URL till ett karttema i formatet style.json", + "map_style_description": "URL till en style.json-karto tema", "memory_cleanup_job": "Rensa minnen", "memory_generate_job": "Generera minnen", "metadata_extraction_job": "Extrahera metadata", - "metadata_extraction_job_description": "Läs in metadata (t.ex. GPS, ansikten och upplÃļsning) fÃļr varje objekt", + "metadata_extraction_job_description": "Läs in metadata (t.ex. GPS, ansikten och upplÃļsning) fÃļr varje resurs", "metadata_faces_import_setting": "Aktivera import av ansikten", - "metadata_faces_import_setting_description": "Importera ansikten frÃĨn bildens Exif-data och sidofiler", - "metadata_settings": "Metadatainställningar", - "metadata_settings_description": "Hantera metadatainställningar", + "metadata_faces_import_setting_description": "Importera ansikten frÃĨn bildens EXIF-data och sidecar-fil", + "metadata_settings": "Metadata-inställningar", + "metadata_settings_description": "Hantera metadata-inställningar", "migration_job": "Migrering", - "migration_job_description": "Migrera miniatyrbilder fÃļr objekt och ansikten till den senaste mappstrukturen", + "migration_job_description": "Migrera miniatyrbilder fÃļr resurser och ansikten till den senaste mappstrukturen", "nightly_tasks_cluster_faces_setting_description": "KÃļr ansiktsigenkänning pÃĨ nyligen upptäckta ansikten", - "nightly_tasks_cluster_new_faces_setting": "Gruppera nya ansikten", - "nightly_tasks_database_cleanup_setting": "Databasrensning", - "nightly_tasks_database_cleanup_setting_description": "Rensa gamla och utgÃĨngna data frÃĨn databasen", + "nightly_tasks_cluster_new_faces_setting": "Kluster nya ansikten", + "nightly_tasks_database_cleanup_setting": "Uppgifter fÃļr databassanering", + "nightly_tasks_database_cleanup_setting_description": "Rensa bort gammal, utgÃĨngen data frÃĨn databasen", "nightly_tasks_generate_memories_setting": "Generera minnen", "nightly_tasks_generate_memories_setting_description": "Skapa nya minnen frÃĨn objekt", "nightly_tasks_missing_thumbnails_setting": "Generera saknade miniatyrbilder", - "nightly_tasks_missing_thumbnails_setting_description": "KÃļa objekt utan miniatyrer fÃļr generering av miniatyrer", + "nightly_tasks_missing_thumbnails_setting_description": "KÃļa resurser utan miniatyrer fÃļr generering av miniatyrer", "nightly_tasks_settings": "Inställningar fÃļr nattliga uppgifter", "nightly_tasks_settings_description": "Hantera nattliga uppgifter", "nightly_tasks_start_time_setting": "Starttid", "nightly_tasks_start_time_setting_description": "Tidpunkten dÃĨ servern bÃļrjar kÃļra de nattliga uppgifterna", - "nightly_tasks_sync_quota_usage_setting": "Synkronisera kvotanvändning", - "nightly_tasks_sync_quota_usage_setting_description": "Uppdatera användarens lagringskvot utifrÃĨn den aktuella användningen", - "no_paths_added": "Inga sÃļkvägar har lagts till", - "no_pattern_added": "Inga mÃļnster har lagts till", + "nightly_tasks_sync_quota_usage_setting": "Synkroniseringskvotanvändning", + "nightly_tasks_sync_quota_usage_setting_description": "Uppdatera användarlagringskvot baserat pÃĨ aktuell användning", + "no_paths_added": "Inga vägar tillagda", + "no_pattern_added": "Inga mÃļnster tillagda", "note_apply_storage_label_previous_assets": "Obs: Om du vill använda lagringsetiketten pÃĨ tidigare uppladdade objekt, kÃļr du", "note_cannot_be_changed_later": "OBS: Detta kan inte ändras i efterhand!", - "notification_email_from_address": "Avsändaradress", - "notification_email_from_address_description": "Avsändarens e-postadress, exempelvis \"Immich Photo Server \". Använd en adress som du har rätt att skicka e-post frÃĨn.", - "notification_email_host_description": "E-postserverns värdnamn (t.ex. smtp.immich.app)", + "notification_email_from_address": "FrÃĨn adress", + "notification_email_from_address_description": "Avsändarens e-post, till exempel: \"Immich Fotoserver \". Säkerställ att du använder en adress som du har tillÃĨtelse att skicka e-post frÃĨn.", + "notification_email_host_description": "Värd fÃļr epostservern (t.ex. smtp.immich.app)", "notification_email_ignore_certificate_errors": "Ignorera certifikatfel", - "notification_email_ignore_certificate_errors_description": "Ignorera valideringsfel fÃļr TLS-certifikat (rekommenderas inte)", - "notification_email_password_description": "LÃļsenord som används vid autentisering mot e-postservern", - "notification_email_port_description": "E-postserverns port (t.ex. 25, 465 eller 587)", + "notification_email_ignore_certificate_errors_description": "Ignorera valideringsfel fÃļr TLS-certifikat (rekommenderas ej)", + "notification_email_password_description": "LÃļsenord att använda fÃļr att verifiera identitet med epostservern", + "notification_email_port_description": "Port pÃĨ epostservern (t.ex. 25, 465 eller 587)", "notification_email_secure": "SMTPS", "notification_email_secure_description": "Använd SMTPS (SMTP Ãļver TLS)", - "notification_email_sent_test_email_button": "Skicka testmeddelande och spara", - "notification_email_setting_description": "Inställningar fÃļr att skicka e-postaviseringar", - "notification_email_test_email": "Skicka testmeddelande", - "notification_email_test_email_failed": "Det gick inte att skicka testmeddelandet. Kontrollera värdena", - "notification_email_test_email_sent": "Ett testmeddelande har skickats till {email}. Kontrollera inkorgen.", - "notification_email_username_description": "Användarnamn som används vid autentisering mot e-postservern", - "notification_enable_email_notifications": "Aktivera e-postaviseringar", - "notification_settings": "Aviseringsinställningar", - "notification_settings_description": "Hantera aviseringsinställningar, inklusive e-post", - "oauth_allow_insecure_requests": "TillÃĨt osäkra fÃļrfrÃĨgningar", - "oauth_allow_insecure_requests_description": "VARNING: Detta inaktiverar TLS-certifikatvalidering fÃļr OAuth-fÃļrfrÃĨgningar och kan utsätta dig fÃļr MITM-attacker.", - "oauth_auto_launch": "Starta automatiskt", - "oauth_auto_launch_description": "Starta OAuth-inloggningen automatiskt när inloggningssidan Ãļppnas", - "oauth_auto_register": "Registrera automatiskt", + "notification_email_sent_test_email_button": "Skicka test-epost och spara", + "notification_email_setting_description": "Inställningar fÃļr att skicka epostnotiser", + "notification_email_test_email": "Skicka test-epost", + "notification_email_test_email_failed": "Misslyckades med att skicka test-epost, undersÃļk dina värden", + "notification_email_test_email_sent": "Ett testmail har skickats till {email}. Kontrollera din inkorg.", + "notification_email_username_description": "Användarnamn att använda vid autentisering med epost-servern", + "notification_enable_email_notifications": "Aktivera epost-notiser", + "notification_settings": "Notisinställningar", + "notification_settings_description": "Hantera notisinställingar, inklusive epost", + "oauth_auto_launch": "Autostart", + "oauth_auto_launch_description": "Starta OAuth-loginflÃļdet automatiskt vid navigering till loginsidan", + "oauth_auto_register": "Autoregistrera", "oauth_auto_register_description": "Registrera nya användare automatiskt efter inloggning med OAuth", "oauth_button_text": "Knapptext", "oauth_client_secret_description": "Krävs fÃļr konfidentiell klient, eller om PKCE (Proof Key for Code Exchange) inte stÃļds fÃļr publik klient.", "oauth_enable_description": "Logga in med OAuth", - "oauth_end_session_url_description": "Omdirigera användaren till denna URI vid utloggning.", - "oauth_mobile_redirect_uri": "Omdirigerings-URI fÃļr mobil", - "oauth_mobile_redirect_uri_override": "Åsidosättning av omdirigerings-URI fÃļr mobil", - "oauth_mobile_redirect_uri_override_description": "Aktivera när OAuth-leverantÃļren inte tillÃĨter en mobil URI, exempelvis ''{callback}''", - "oauth_prompt_description": "Promptparameter (t.ex. select_account, login, consent)", + "oauth_mobile_redirect_uri": "Telefonomdirigernings-URI", + "oauth_mobile_redirect_uri_override": "Telefonomdirigerings-URI Ãļverrskridning", + "oauth_mobile_redirect_uri_override_description": "Aktivera om OAuth-leverantÃļren inte tillÃĨter mobila URI:er, sÃĨ som ''{callback}''", "oauth_role_claim": "RollansprÃĨk", - "oauth_role_claim_description": "Bevilja automatiskt administratÃļrsÃĨtkomst utifrÃĨn detta ansprÃĨk. AnsprÃĨket kan ha värdet 'user' eller 'admin'.", + "oauth_role_claim_description": "Bevilja administratÃļrsÃĨtkomst automatiskt baserat pÃĨ fÃļrekomsten av detta pÃĨstÃĨende. PÃĨstÃĨendet kan innehÃĨlla antingen 'user' eller 'admin'.", "oauth_settings": "OAuth", - "oauth_settings_description": "Hantera inställningar fÃļr OAuth-inloggning", + "oauth_settings_description": "Hantera OAuth-logininställningar", "oauth_settings_more_details": "FÃļr ytterligare detaljer om denna funktion, se dokumentationen.", - "oauth_storage_label_claim": "AnsprÃĨk fÃļr lagringsetikett", - "oauth_storage_label_claim_description": "Ange automatiskt användarens lagringsetikett till värdet i detta ansprÃĨk.", - "oauth_storage_quota_claim": "AnsprÃĨk fÃļr lagringskvot", - "oauth_storage_quota_claim_description": "Ange automatiskt användarens lagringskvot till värdet i detta ansprÃĨk.", + "oauth_storage_label_claim": "Användaranknuten lagringsetikett", + "oauth_storage_label_claim_description": "Sätter automatiskt angiven användares lagringsetikett.", + "oauth_storage_quota_claim": "Användaranknuten lagringskvot", + "oauth_storage_quota_claim_description": "Sätter automatiskt angiven användares lagringskvot.", "oauth_storage_quota_default": "Standardlagringskvot (GiB)", - "oauth_storage_quota_default_description": "Kvot i GiB som används när inget ansprÃĨk anges.", - "oauth_timeout": "Tidsgräns fÃļr begäran", - "oauth_timeout_description": "Tidsgräns fÃļr begäranden i millisekunder", + "oauth_storage_quota_default_description": "Kvot i GiB som används när ingen fordran angetts.", + "oauth_timeout": "Begäran tog fÃļr lÃĨng tid", + "oauth_timeout_description": "Timeout fÃļr fÃļrfrÃĨgningar i millisekunder", "ocr_job_description": "Använd maskininlärning fÃļr att känna igen text i bilder", - "password_enable_description": "Logga in med e-postadress och lÃļsenord", + "password_enable_description": "Logga in med epost och lÃļsenord", "password_settings": "LÃļsenordsinloggning", - "password_settings_description": "Hantera inställningar fÃļr lÃļsenordsinloggning", + "password_settings_description": "Hantera inställningar fÃļr lÃļsenords-inloggning", "paths_validated_successfully": "Samtliga sÃļkvägar kunde bekräftas", - "person_cleanup_job": "Personrensning", + "person_cleanup_job": "Person rensning", "queue_details": "KÃļinformation", "queues": "JobbkÃļer", "queues_page_description": "Sida fÃļr administratÃļrsjobbkÃļer", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Samtliga bibliotek uppdateras", "registration": "AdministratÃļrsregistrering", "registration_description": "Du utses till administratÃļr eftersom du är systemets fÃļrsta användare. Du ansvarar fÃļr administration och kan skapa ytterligare användare.", - "release_channel_release_candidate": "Släppkandidat", - "release_channel_stable": "Stabil", "remove_failed_jobs": "Ta bort misslyckade jobb", "require_password_change_on_login": "Kräv av användaren att byta lÃļsenord vid fÃļrsta inloggning", "reset_settings_to_default": "Återställ inställningar till standard", @@ -342,8 +312,8 @@ "send_welcome_email": "Skicka välkomstmail", "server_external_domain_settings": "Extern domän", "server_external_domain_settings_description": "Domän som används fÃļr externa länkar", - "server_public_users": "Offentliga användare", - "server_public_users_description": "Alla användare, med namn och e-postadress, visas när en användare läggs till i ett delat album. När funktionen är inaktiverad är användarlistan endast tillgänglig fÃļr administratÃļrer.", + "server_public_users": "Vanlig användare", + "server_public_users_description": "Alla användare (namn och e-post) är listade när man lägger till en användare till ett delat album. Om inaktiverat, kommer användarlistan endast vara synlig fÃļr administratÃļrer.", "server_settings": "Serverinställningar", "server_settings_description": "Hantera serverinställningar", "server_stats_page_description": "Statistiksida fÃļr administratÃļrsservern", @@ -354,52 +324,52 @@ "sidecar_job_description": "Upptäck eller synkronisera medfÃļljande metadata frÃĨn filsystemet", "slideshow_duration_description": "Antal sekunder att visa varje bild", "smart_search_job_description": "KÃļr maskininlärning pÃĨ objekt fÃļr att stÃļdja smart sÃļkning", - "storage_template_date_time_description": "Tidsstämpel fÃļr objektets skapande används fÃļr datum och tidsinformation", + "storage_template_date_time_description": "Tidsstämpel fÃļr resursens skapande används fÃļr datum och tidsinformation", "storage_template_date_time_sample": "Exempeltid {date}", - "storage_template_enable_description": "Aktivera motorn fÃļr lagringsmallar", - "storage_template_hash_verification_enabled": "Hashverifiering aktiverad", - "storage_template_hash_verification_enabled_description": "Aktiverar hashverifiering. Inaktivera inte funktionen om du inte är säker pÃĨ konsekvenserna", - "storage_template_migration": "Migrering av lagringsmall", - "storage_template_migration_description": "Tillämpa den aktuella {template} pÃĨ tidigare uppladdade objekt", - "storage_template_migration_info": "Lagringsmallen konverterar alla filändelser till gemener. Malländringar gäller endast nya objekt. KÃļr {job} fÃļr att tillämpa mallen retroaktivt pÃĨ tidigare uppladdade objekt.", - "storage_template_migration_job": "Migreringsjobb fÃļr lagringsmall", - "storage_template_more_details": "Mer information om den här funktionen finns under Lagringsmall och konsekvenser", - "storage_template_onboarding_description_v2": "När funktionen är aktiverad ordnas filer automatiskt utifrÃĨn en användardefinierad mall. Mer information finns i dokumentationen.", - "storage_template_path_length": "Ungefärlig gräns fÃļr sÃļkvägens längd: {length, number}/{limit, number}", + "storage_template_enable_description": "Aktivera mallmotor fÃļr lagring", + "storage_template_hash_verification_enabled": "Hash-verifiering aktiverat", + "storage_template_hash_verification_enabled_description": "Aktiverar hash-verifiering, deaktiviera inte om du inte är säker pÃĨ implikationerna", + "storage_template_migration": "Migrering av Lagringsmallar", + "storage_template_migration_description": "Applicera aktiv {template} till tidigare uppladdade resurser", + "storage_template_migration_info": "Lagringsmallen kommer konvertera alla filändelser till gemena bokstäver. Ändringar gäller endast fÃļr nya resurser, fÃļr att retoaktivt tillämpa mallen pÃĨ befintliga resurser kÃļr {job}.", + "storage_template_migration_job": "Lagringsmall migreringsjobb", + "storage_template_more_details": "FÃļr mer information om den här funktionen se Lagringsmall och dess konsekvenser", + "storage_template_onboarding_description_v2": "Denna funktion kommer när den är aktiverad att auto-organisera filer baserat pÃĨ en användardefinierad mall. FÃļr mer information se dokumentationen.", + "storage_template_path_length": "Uppskattad längdbegränsning pÃĨ sÃļkväg: {length, number}/{limit, number}", "storage_template_settings": "Lagringsmall", - "storage_template_settings_description": "Hantera mappstruktur och filnamn fÃļr uppladdade objekt", - "storage_template_user_label": "{label} är användarens lagringsetikett", + "storage_template_settings_description": "Hantera mappstruktur och filnamn fÃļr uppladdade resurser", + "storage_template_user_label": "{label} är användarens lagringsmärkning", "system_settings": "Systeminställningar", - "tag_cleanup_job": "Taggrensning", + "tag_cleanup_job": "Markera fÃļr rensning", "template_email_available_tags": "Du kan använda fÃļljande variablar i din mall: {tags}", - "template_email_if_empty": "Om mallen är tom används standardmeddelandet.", - "template_email_invite_album": "Mall fÃļr albuminbjudan", + "template_email_if_empty": "Om mallen är tom, kommer standard e-post att användas.", + "template_email_invite_album": "Inbjudan Album Mall", "template_email_preview": "FÃļrhandsgranskning", - "template_email_settings": "E-postmallar", - "template_email_update_album": "Mall fÃļr albumuppdatering", - "template_email_welcome": "Mall fÃļr välkomstmeddelande", - "template_settings": "Aviseringsmallar", - "template_settings_description": "Hantera anpassade aviseringsmallar", + "template_email_settings": "E-post mall", + "template_email_update_album": "Uppdatera Album Mall", + "template_email_welcome": "Välkommen e-post mall", + "template_settings": "Notifikations Mall", + "template_settings_description": "Hantera anpassade mallar fÃļr notifikationer", "theme_custom_css_settings": "Anpassad CSS", - "theme_custom_css_settings_description": "Med CSS-formatmallar kan Immichs utseende anpassas.", + "theme_custom_css_settings_description": "Cascading Style Sheets mÃļjliggÃļr designanpassningar av Immich.", "theme_settings": "Temainställningar", "theme_settings_description": "Hantera anpassningar av webbgränssnittet fÃļr Immich", "thumbnail_generation_job": "Generera Miniatyrer", "thumbnail_generation_job_description": "Generera stora, smÃĨ och suddiga miniatyrer fÃļr varje objekt, samt fÃļr varje person", "transcoding_acceleration_api": "Accelerations-API", - "transcoding_acceleration_api_description": "Det API som kommunicerar med enheten fÃļr att accelerera omkodning. Inställningen används efter bästa fÃļrmÃĨga och faller tillbaka pÃĨ programvarubaserad omkodning vid fel. Om VP9 fungerar beror pÃĨ maskinvaran.", + "transcoding_acceleration_api_description": "API som kommer att interagera med din enhet fÃļr att accelerera omkodning. Inställning är 'best effort': vid fel kommer den att ÃĨtergÃĨ till mjukvarubaserad omkodning. VP9 kan fungera eller inte, beroende pÃĨ din hÃĨrdvara.", "transcoding_acceleration_nvenc": "NVENC (kräver NVIDIA GPU)", - "transcoding_acceleration_qsv": "Quick Sync (kräver en Intel-processor av sjunde generationen eller senare)", - "transcoding_acceleration_rkmpp": "RKMPP (endast fÃļr Rockchip-SoC:er)", + "transcoding_acceleration_qsv": "Quick Sync (kräver 7 generationens Intel CPU eller senare)", + "transcoding_acceleration_rkmpp": "RKMPP (bara med Rockchip SOCs)", "transcoding_acceleration_vaapi": "VAAPI", "transcoding_accepted_audio_codecs": "Accepterade ljud-codecs", "transcoding_accepted_audio_codecs_description": "Välj vilka ljud-codecs som inte behÃļver omkodas. Används endast fÃļr vissa omkodningspolicyer.", "transcoding_accepted_containers": "Accepterade behÃĨllare", - "transcoding_accepted_containers_description": "Välj vilka containerformat som inte behÃļver muxas om till MP4. Används endast fÃļr vissa omkodningsprinciper.", + "transcoding_accepted_containers_description": "Välj vilka kontainerformat som inte behÃļver remuxas till MP4. Endast används fÃļr vissa transcoding-politischer.", "transcoding_accepted_video_codecs": "Accepterade video-codecs", "transcoding_accepted_video_codecs_description": "Välj vilka video-codecs som inte behÃļver omkodas. Används endast fÃļr vissa omkodningspolicyer.", "transcoding_advanced_options_description": "Val som de flesta användare inte bÃļr behÃļva ändra", - "transcoding_audio_codec": "Ljudkodek", + "transcoding_audio_codec": "Ljud-codec", "transcoding_audio_codec_description": "Opus är bästa kvalitetsvalet, men är inte lika kompatibelt med äldre enheter eller mjukvara.", "transcoding_bitrate_description": "Videor som är i hÃļgre än max bithastighet eller inte i ett accepterat format", "transcoding_codecs_learn_more": "FÃļr att läsa mer om terminologin här se FFmpeg-dokumentationen fÃļr H.264 kodek, HEVC kodek och VP9 kodek.", @@ -409,15 +379,15 @@ "transcoding_constant_rate_factor_description": "NivÃĨ pÃĨ videokvalitet. Typiska värden är 23 fÃļr H.264, 28 fÃļr HEVC, 31 fÃļr VP9 och 35 fÃļr AV1. Lägre är bättre, men producerar stÃļrre filer.", "transcoding_disabled_description": "Omkoda inte videofiler, detta kan stÃļra uppspelning pÃĨ vissa klienter", "transcoding_encoding_options": "Kodningsval", - "transcoding_encoding_options_description": "Ange kodekar, upplÃļsning, kvalitet och andra alternativ fÃļr kodade videor", + "transcoding_encoding_options_description": "Välj codec, upplÃļsning, kvalitet och andra val fÃļr kodade videor", "transcoding_hardware_acceleration": "HÃĨrdvaruacceleration", "transcoding_hardware_acceleration_description": "Experimentell: snabbare transkodning men kan minska kvaliteten vid samma bithastighet", "transcoding_hardware_decoding": "HÃĨrdvaruavkodning", - "transcoding_hardware_decoding_setting_description": "Aktiverar acceleration i hela bearbetningskedjan i stället fÃļr att endast accelerera kodningen. Fungerar kanske inte med alla videor.", + "transcoding_hardware_decoding_setting_description": "Aktiverar end-to-end accelerering i stället fÃļr endast kodningsacceleration. Fungerar inte med alla videor.", "transcoding_max_b_frames": "Max B-ramar", - "transcoding_max_b_frames_description": "HÃļgre värden fÃļrbättrar komprimeringseffektiviteten men gÃļr kodningen lÃĨngsammare. Kan vara inkompatibelt med maskinvaruacceleration pÃĨ äldre enheter. Värdet 0 inaktiverar B-bildrutor och -1 anger värdet automatiskt.", + "transcoding_max_b_frames_description": "HÃļgre värden fÃļrbättrar kompressionseffektiviteten, men saktar ner kodningen. Kan vara inkompatibel med hÃĨrdvaruacceleration pÃĨ äldre enheter. 0 avaktiverar B-frames, medan -1 anger detta värde automatiskt.", "transcoding_max_bitrate": "Max bithastighet", - "transcoding_max_bitrate_description": "En hÃļgsta bithastighet gÃļr filstorlekarna mer fÃļrutsägbara till priset av en liten kvalitetsfÃļrlust. Vid 720p är typiska värden 2600 kbit/s fÃļr VP9 eller HEVC och 4500 kbit/s fÃļr H.264. Funktionen inaktiveras med värdet 0. Om ingen enhet anges antas kbit/s, sÃĨ 5000, 5000k och 5M är likvärdiga.", + "transcoding_max_bitrate_description": "En maximal bitrate kan gÃļra filstorlekar mer fÃļrutsägbara till en liten kostnad pÃĨ kvalitet. Vid 720p är typiska värden 2600 kbit/s fÃļr VP9 eller HEVC, eller 4500 kbit/s fÃļr H.264. Inaktiverad om satt till 0. När ingen enhet anges sÃĨ kommer k (fÃļr kbit/s) användas; därfÃļr är 5000, 5000k och 5M (fÃļr Mbit/s) likvärdiga.", "transcoding_max_keyframe_interval": "Max nyckelbildruteintervall", "transcoding_max_keyframe_interval_description": "Sätter det maximala bildruteavstÃĨndet mellan nyckelbildrutor. Lägre värden fÃļrsämrar kompressionseffektiviteten, men fÃļrbättrar sÃļktiderna och kan fÃļrbättra kvaliteten i scener med snabb rÃļrelse. 0 ställer in detta värde automatiskt.", "transcoding_optimal_description": "Videor som är hÃļgre än mÃĨllÃļsning eller inte i ett accepterat format", @@ -425,35 +395,27 @@ "transcoding_policy_description": "Välj när en video ska omkodas", "transcoding_preferred_hardware_device": "FÃļredragen hÃĨrdvaruenhet", "transcoding_preferred_hardware_device_description": "Gäller enbart VAAPI och QSV. Ställer in dri-läget som används fÃļr hÃĨrdvaruomkodning.", - "transcoding_preset_preset": "FÃļrval (-preset)", - "transcoding_preset_preset_description": "Komprimeringshastighet. LÃĨngsammare fÃļrval ger mindre filer och hÃļgre kvalitet vid en viss bithastighet. VP9 ignorerar hastigheter Ãļver 'faster'.", - "transcoding_realtime": "Realtidskodning [EXPERIMENTELL]", - "transcoding_realtime_description": "TillÃĨter omkodning i realtid medan videon streamas. MÃļjliggÃļr kvalitetsväxling, men kan orsaka hÃļgre uppspelningsfÃļrdrÃļjning och hackning beroende pÃĨ serverns kapacitet.", - "transcoding_realtime_enabled": "Aktivera realtidsomkodning", - "transcoding_realtime_enabled_description": "Om den är inaktiverad vägrar servern att starta nya realtidsomkodning sessioner i realtid.", - "transcoding_realtime_resolutions": "UpplÃļsningar", - "transcoding_realtime_resolutions_description": "De upplÃļsningar som erbjuds fÃļr realtidstranskodning. HÃļgre upplÃļsningar kan orsaka uppspelningsproblem om servern inte kan transkoda dem tillräckligt snabbt.", - "transcoding_realtime_video_codecs": "Videokodekar", - "transcoding_realtime_video_codecs_description": "De videokodekar som erbjuds fÃļr realtidstranskodning. Klienter väljer det bästa alternativet de stÃļdjer under uppspelning. AV1 är effektivare än HEVC, som är effektivare än H.264. Vid hÃĨrdvaruacceleration ska du endast välja de kodekar som acceleratorn kan koda. Vid mjukvarutranskodning bÃļr du tänka pÃĨ att H.264 är snabbare än AV1, som är snabbare än HEVC.", + "transcoding_preset_preset": "FÃļrinställning (-preset)", + "transcoding_preset_preset_description": "Kompressionshastighet. LÃĨngsammare preset ger mindre filer och hÃļgre kvalitet fÃļr en given bitrate. VP9 ignorerar hastigheter hÃļgre än 'faster'.", "transcoding_reference_frames": "Referensbildrutor", "transcoding_reference_frames_description": "Antalet bildrutor som tas i beaktande när en given bildruta ska komprimeras. HÃļgre värden ger effektivare kompression pÃĨ bekostnad av lÃĨngsammare kodning. 0 ställer in detta värde automatiskt.", - "transcoding_required_description": "Endast videor som inte har ett godkänt format", + "transcoding_required_description": "Enbart videos som inte är ett accepterat format", "transcoding_settings": "Inställningar fÃļr omkodning av video", "transcoding_settings_description": "Hantera vilka videor som ska omkodas och hur de ska behandlas", "transcoding_target_resolution": "FÃļrväntad upplÃļsning", "transcoding_target_resolution_description": "En hÃļgre upplÃļsning kan bevara fler detaljer men kan ta längre tid at koda, ha stÃļrre fil storlek och kan fÃļrsämra appens svarstid.", "transcoding_temporal_aq": "Temporär AQ", - "transcoding_temporal_aq_description": "Gäller endast NVENC. Tidsadaptiv kvantisering fÃļrbättrar kvaliteten i detaljrika scener med lite rÃļrelse. Kan vara inkompatibel med äldre enheter.", + "transcoding_temporal_aq_description": "Gäller endast NVENC. Temporal adaptiv kvantisering Ãļkar kvaliteten pÃĨ scener med hÃļg detaljrikedom och lÃĨg rÃļrelse. Kan vara inkompatibel med äldre enheter.", "transcoding_threads": "TrÃĨdar", "transcoding_threads_description": "HÃļgre värden leder till snabbare kodning, men lämnar mindre utrymme fÃļr servern att bearbeta andra uppgifter medan den är aktiv. Detta värde bÃļr inte vara mer än antalet CPU-kärnor. Maximerar användningen om den är inställd pÃĨ 0.", "transcoding_tone_mapping": "Ton mappning", "transcoding_tone_mapping_description": "FÃļrsÃļker att bevara utseendet pÃĨ HDR-videor när de konverteras till SDR. Varje algoritm gÃļr olika avvägningar fÃļr färg, detaljer och ljusstyrka. Hable bevarar detaljer, Mobius bevarar färg och Reinhard bevarar ljusstyrkan.", "transcoding_transcode_policy": "Omkodningspolicy", - "transcoding_transcode_policy_description": "Princip fÃļr när en video ska kodas om. HDR-videor och videor med ett annat pixelformat än YUV 4:2:0 kodas alltid om, utom när omkodning är inaktiverad.", + "transcoding_transcode_policy_description": "Policy fÃļr när en video ska omkodas. HDR-videor och videor med ett annat pixelformat än YUV 4:2:0 kommer alltid att omkodas (fÃļrutom om omkodning är inaktiverad).", "transcoding_two_pass_encoding": "TvÃĨ-pass kodning", - "transcoding_two_pass_encoding_setting_description": "Koda om i tvÃĨ pass fÃļr att skapa bättre kodade videor. När hÃļgsta bithastighet är aktiverad, vilket krävs fÃļr H.264 och HEVC, använder läget ett bithastighetsintervall baserat pÃĨ den hÃļgsta bithastigheten och ignorerar CRF. FÃļr VP9 kan CRF användas när hÃļgsta bithastighet är inaktiverad.", - "transcoding_video_codec": "Videokodek", - "transcoding_video_codec_description": "VP9 är effektivt och har god webbkompatibilitet men tar längre tid att koda om. HEVC fungerar pÃĨ liknande sätt men har sämre webbkompatibilitet. H.264 har bred kompatibilitet och kodas om snabbt men ger betydligt stÃļrre filer. AV1 är den effektivaste kodeken men stÃļds inte av alla äldre enheter.", + "transcoding_two_pass_encoding_setting_description": "Koda om i tvÃĨ omgÃĨngar fÃļr att producera bättre kodade videor. När max bitrate är aktiverat (krävs fÃļr att det ska fungera med H.264 och HEVC), använder det här läget ett bithastighetsomrÃĨde baserat pÃĨ max bitrate och ignorerar CRF. FÃļr VP9 kan CRF användas om max bitrate är inaktiverat.", + "transcoding_video_codec": "Video codec", + "transcoding_video_codec_description": "VP9 har hÃļg effektivitet och webbkompatibilitet, men tar längre tid att omkoda. HEVC fungerar pÃĨ liknande sätt, men har lägre webbkompatibilitet. H.264 är allmänt kompatibel och snabb att omkoda, men producerar mycket stÃļrre filer. AV1 är den mest effektiva codec men saknar stÃļd pÃĨ äldre enheter.", "trash_enabled_description": "Aktivera papperskorgen", "trash_number_of_days": "Antal dagar", "trash_number_of_days_description": "Antal dagar att fÃļrvara objekten i papperskorgen innan de tas bort permanent", @@ -463,10 +425,10 @@ "unlink_all_oauth_accounts_description": "Kom ihÃĨg att ta bort länken till alla OAuth-konton innan du migrerar till en ny leverantÃļr.", "unlink_all_oauth_accounts_prompt": "Är du säker pÃĨ att du vill ta bort länken till alla OAuth-konton? Detta ÃĨterställer OAuth-ID:t fÃļr varje användare och kan inte ÃĨngras.", "user_cleanup_job": "Användarrensning", - "user_delete_delay": "{user}s konto och objekt schemaläggs fÃļr permanent borttagning om {delay, plural, one {# day} other {# days}}.", + "user_delete_delay": "{user}s konto och resurser kommer att schemaläggas fÃļr permanent radering om {delay, plural, one {# dag} other {# dagar}}.", "user_delete_delay_settings": "BorttagningsfÃļrdrÃļjning", "user_delete_delay_settings_description": "Antal dagar efter borttagning fÃļr att permanent radera en användares konto och objekt. Arbetet med borttagning av användare kÃļrs vid midnatt fÃļr att sÃļka efter användare som är redo fÃļr radering. Ändringar av denna inställning kommer att utvärderas vid nästa kÃļrning.", - "user_delete_immediately": "{user}s konto och objekt kÃļplaceras fÃļr omedelbar permanent borttagning.", + "user_delete_immediately": "{user}s konto och resurser kommer att stÃĨ i kÃļ fÃļr omedelbar permanent radering.", "user_delete_immediately_checkbox": "KÃļa användare och objekt fÃļr omedelbar radering", "user_details": "Användardetaljer", "user_management": "Användarhantering", @@ -478,22 +440,22 @@ "user_settings_description": "Hantera användarinställningar", "user_successfully_removed": "Användaren {email} har tagits bort.", "users_page_description": "AdministratÃļrsanvändare", - "version_check_channel": "Släppkanal", - "version_check_channel_description": "Välj den släppkanal du vill fÃĨ versionsannonseringar fÃļr", "version_check_enabled_description": "Aktivera versionskontroll", "version_check_implications": "Funktionen fÃļr versionskontroll är beroende av periodisk kommunikation med {server}", "version_check_settings": "Versionskontroll", - "version_check_settings_description": "Aktivera eller inaktivera aviseringar om nya versioner", + "version_check_settings_description": "Aktivera/inaktivera notis om ny version", "video_conversion_job": "Omkoda videor", "video_conversion_job_description": "Koda om videor fÃļr bredare kompatibilitet med webbläsare och enheter" }, "admin_email": "E-postadress fÃļr administratÃļr", - "admin_password": "AdministratÃļrslÃļsenord", + "admin_password": "Admin LÃļsenord", "administration": "Administration", "advanced": "Avancerat", "advanced_settings_clear_image_cache": "Rensa bild-cache", "advanced_settings_clear_image_cache_error": "Misslyckades med att rensa bild-cachen", "advanced_settings_clear_image_cache_success": "{size} har rensats", + "advanced_settings_enable_alternate_media_filter_subtitle": "Använd det här alternativet fÃļr att filtrera media under synkronisering baserat pÃĨ alternativa kriterier. Prova detta endast om du har problem med att appen inte hittar alla album.", + "advanced_settings_enable_alternate_media_filter_title": "[EXPERIMENTELLT] Använd alternativ enhetsalbum-synkroniseringsfilter", "advanced_settings_log_level_title": "LoggnivÃĨ: {level}", "advanced_settings_prefer_remote_subtitle": "Vissa enheter är mycket lÃĨngsamma pÃĨ att ladda miniatyrer frÃĨn objekt pÃĨ enheten. Aktivera den här inställningen fÃļr att ladda bilder frÃĨn servern istället.", "advanced_settings_prefer_remote_title": "FÃļredra bilder frÃĨn servern", @@ -501,7 +463,9 @@ "advanced_settings_proxy_headers_title": "Anpassade proxyheaders [EXPERIMENTELLT]", "advanced_settings_readonly_mode_subtitle": "Aktiverar skrivskyddat-läge där foton endast kan visas. FÃļljande funktioner inaktiveras: välj flera bilder, dela, casta, ta bort bilder. Aktivera/inaktivera skrivskyddat läge via profilbilden pÃĨ appens hemskärm", "advanced_settings_readonly_mode_title": "Skrivskyddat läge", - "advanced_settings_sync_remote_deletions_subtitle": "Radera eller ÃĨterställ automatiskt ett objekt pÃĨ den här enheten när den ÃĨtgärden utfÃļrs pÃĨ webben", + "advanced_settings_self_signed_ssl_subtitle": "Hoppar Ãļver verifiering av serverns SSL-certifikat. Krävs fÃļr självsignerade certifikat.", + "advanced_settings_self_signed_ssl_title": "TillÃĨt självsignerade SSL-certifikat [EXPERIMENTELLT]", + "advanced_settings_sync_remote_deletions_subtitle": "Radera eller ÃĨterställ automatiskt en resurs pÃĨ den här enheten när den ÃĨtgärden utfÃļrs pÃĨ webben", "advanced_settings_sync_remote_deletions_title": "Synkronisera fjärradering [EXPERIMENTELL]", "advanced_settings_tile_subtitle": "Avancerade användarinställningar", "advanced_settings_troubleshooting_subtitle": "Aktivera funktioner fÃļr felsÃļkning", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Är du säker pÃĨ att du vill ta bort albumet {album}?", "album_delete_confirmation_description": "Om det här albumet delas kommer andra användare inte att kunna komma ÃĨt det längre.", "album_deleted": "Albumet raderat", + "album_info_card_backup_album_excluded": "EXKLUDERAD", + "album_info_card_backup_album_included": "INKLUDERAD", "album_info_updated": "Albuminformation uppdaterad", + "album_leave": "Lämna albumet?", + "album_leave_confirmation": "Är du säker pÃĨ att du vill lämna {album}?", "album_name": "Albumnamn", "album_options": "Albumalternativ", "album_remove_user": "Ta bort användare?", "album_remove_user_confirmation": "Är du säker pÃĨ att du vill ta bort {user}?", "album_search_not_found": "Inga album hittades som matchade din sÃļkning", + "album_selected": "Album valt", "album_share_no_users": "Det verkar som att du har delat det här albumet med alla användare eller sÃĨ har du inte nÃĨgon användare att dela med.", "album_summary": "Albumsammanfattning", "album_updated": "Albumet uppdaterat", "album_updated_setting_description": "FÃĨ ett e-postmeddelande när ett delat album har nya objekt", "album_upload_assets": "Ladda upp material frÃĨn din dator och lägg till i album", + "album_user_left": "Lämnade {album}", + "album_user_removed": "Tog bort {user}", + "album_viewer_appbar_delete_confirm": "Är du säker pÃĨ att du vill ta bort albumet frÃĨn ditt konto?", "album_viewer_appbar_share_err_delete": "Kunde inte radera album", + "album_viewer_appbar_share_err_leave": "Kunde inte lämna album", + "album_viewer_appbar_share_err_remove": "Kunde inte ta bort objekt frÃĨn album", + "album_viewer_appbar_share_err_title": "Kunde inte ändra albumtitel", + "album_viewer_appbar_share_leave": "Lämna album", + "album_viewer_appbar_share_to": "Dela Till", "album_viewer_page_share_add_users": "Lägg till användare", "album_with_link_access": "LÃĨt alla med länken se foton och personer i det här albumet.", "albums": "Album", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Standard sorteringsordning fÃļr mediefiler vid skapande av nytt album.", "albums_feature_description": "Samlingar av mediefiler som kan delas med andra användare.", "albums_on_device_count": "Album pÃĨ enheten ({count})", + "albums_selected": "{count, plural, one {# album valt} other {# album valda}}", "all": "Allt", "all_albums": "Alla album", "all_people": "Alla personer", "all_photos": "Alla foton", "all_videos": "Alla videor", "allow_dark_mode": "TillÃĨt mÃļrkt läge", + "allow_edits": "TillÃĨt redigeringar", "allow_public_user_to_download": "TillÃĨt offentlig användare att ladda ner", "allow_public_user_to_upload": "TillÃĨt en offentlig användare att ladda upp", "allowed": "TillÃĨten", @@ -549,12 +528,14 @@ "always_keep": "BehÃĨll alltid", "always_keep_photos_hint": "FrigÃļr utrymme behÃĨller alla foton pÃĨ den här enheten.", "always_keep_videos_hint": "FrigÃļr utrymme behÃĨller alla videor pÃĨ den här enheten.", - "api_key": "API-nyckel", + "anti_clockwise": "Moturs", + "api_key": "API Nyckel", "api_key_description": "Detta värde kommer bara att visas en gÃĨng. Se till att kopiera det innan du stänger fÃļnstret.", "api_key_empty": "Ditt API-nyckelnamn ska inte vara tomt", - "api_keys": "API-nycklar", + "api_keys": "API-Nycklar", "app_architecture_variant": "Variant (Arkitektur)", "app_bar_signout_dialog_content": "Är du säker pÃĨ att du vill logga ut?", + "app_bar_signout_dialog_ok": "Ja", "app_bar_signout_dialog_title": "Logga ut", "app_download_links": "Länkar fÃļr appnedladdning", "app_settings": "Appinställningar", @@ -565,20 +546,28 @@ "archive": "Arkiv", "archive_action_prompt": "{count} adderade till Arkiv", "archive_or_unarchive_photo": "Arkivera eller oarkivera fotot", + "archive_page_no_archived_assets": "Inga arkiverade objekt hittade", + "archive_page_title": "Arkiv ({count})", "archive_size": "Arkivstorlek", "archive_size_description": "Konfigurera arkivstorleken fÃļr nedladdningar (i GiB)", "archived": "Arkiverade", "archived_count": "{count, plural, other {Arkiverade #}}", "are_these_the_same_person": "Är det samma person?", "are_you_sure_to_do_this": "Är du säker pÃĨ att du vill gÃļra det här?", + "array_field_not_fully_supported": "Arrayfält kräver manuell JSON-redigering", + "asset_action_delete_err_read_only": "Kan inte ta bort skrivskyddade objekt, hoppar Ãļver", + "asset_action_share_err_offline": "Kan inte hämta offline-objekt, hoppar Ãļver", "asset_added_to_album": "Lades till i album", "asset_adding_to_album": "Lägger till i albumâ€Ļ", "asset_created": "Objekt skapad", - "asset_day_count": "{date}: {count, plural, one {# objekt} other {# objekt}}", "asset_description_updated": "Objektbeskrivning har uppdaterats", - "asset_hashing": "Hasharâ€Ļ", + "asset_filename_is_offline": "Objektet {filename} är offline", + "asset_has_unassigned_faces": "Objektet har otilldelade ansikten", + "asset_hashing": "Hashningâ€Ļ", "asset_list_group_by_sub_title": "Gruppera pÃĨ", - "asset_list_layout_settings_group_by": "Gruppera objekt efter", + "asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout", + "asset_list_layout_settings_group_automatically": "Automatiskt", + "asset_list_layout_settings_group_by": "Gruppera resurser efter", "asset_list_layout_settings_group_by_month_day": "MÃĨnad + dag", "asset_list_layout_sub_title": "Layout", "asset_list_settings_subtitle": "Layoutinställningar fÃļr bildrutnät", @@ -588,77 +577,125 @@ "asset_not_found_on_icloud": "Objektet hittades inte pÃĨ iCloud. Objektet kan vara oÃĨtkomligt pÃĨ grund av en felaktig fil som lagras pÃĨ iCloud", "asset_offline": "Objekt offline", "asset_offline_description": "Detta externa objekt finns inte längre pÃĨ disken. Kontakta din Immich-administratÃļr fÃļr hjälp.", + "asset_restored_successfully": "Objekt ÃĨterställt", "asset_skipped": "Överhoppad", "asset_skipped_in_trash": "I papperskorgen", + "asset_trashed": "Objekt kasserat", "asset_troubleshoot": "FelsÃļkning av objekt", "asset_uploaded": "Uppladdad", "asset_uploading": "Laddar uppâ€Ļ", "asset_viewer_settings_subtitle": "Hantera inställningar fÃļr gallerivisning", "asset_viewer_settings_title": "Objektvisare", "assets": "Objekt", - "assets_added_to_album_count": "Lade till {count, plural, one {# objekt} other {# objekt}} i albumet", - "assets_added_to_album_partial_count": "Lade till {successCount} av {totalCount} {totalCount, plural, one {objekt} other {objekt}} i albumet", - "assets_added_to_albums_count": "Lade till {assetTotal, plural, one {# objekt} other {# objekt}} i {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Objektet} other {Objekten}} kan inte läggas till i albumet", - "assets_cannot_be_added_to_albums": "{count, plural, one {Objektet} other {Objekten}} kan inte läggas till i nÃĨgot av albumen", + "assets_added_count": "Lade till {count, plural, one {# resurs} other {# resurser}}", + "assets_added_to_album_count": "Lade till {count, plural, one {# resurs} other {# resurser}} i albumet", + "assets_added_to_albums_count": "Lade till {assetTotal, plural, one {# resurs} other {# resurser}} till {albumTotal, plural, one {# album} other {# album}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Resurs} other {Resurser}} kan inte läggas till i albumet", + "assets_cannot_be_added_to_albums": "{count, plural, one {Resurs} other {Resurser}} kan inte läggas till i nÃĨgot av albumen", "assets_count": "{count, plural, one {# objekt} other {# objekt}}", - "assets_moved_to_trash_count": "Flyttade {count, plural, one {# objekt} other {# objekt}} till papperskorgen", - "assets_permanently_deleted_count": "Tog bort {count, plural, one {# objekt} other {# objekt}} permanent", - "assets_removed_count": "Tog bort {count, plural, one {# objekt} other {# objekt}}", - "assets_restore_confirmation": "Är du säker pÃĨ att du vill ÃĨterställa alla dina slängda objekt? Du kan inte ÃĨngra den här ÃĨtgärden! Observera att offlineobjekt inte kan ÃĨterställas pÃĨ detta sätt.", - "assets_restored_count": "Återställde {count, plural, one {# objekt} other {# objekt}}", - "assets_trashed": "{count} objekt flyttades till papperskorgen", - "assets_trashed_count": "{count, plural, one {# objekt flyttades} other {# objekt flyttades}} till papperskorgen", - "assets_were_part_of_album_count": "{count, plural, one {Objektet} other {Objekten}} tillhÃļr redan albumet", - "assets_were_part_of_albums_count": "{count, plural, one {Objektet} other {Objekten}} tillhÃļr redan albumen", + "assets_deleted_permanently": "{count, plural, one {# resurs} other {# resurser}} har raderats permanent", + "assets_deleted_permanently_from_server": "{count, plural, one {# resurs} other {# resurser}} har permanent raderats frÃĨn Immich-servern", + "assets_downloaded_failed": "{count, plural, one {Nerladdning av # fil - {error} fil misslyckades} other {Nerladdning av # filer - {error} filer misslyckades}}", + "assets_downloaded_successfully": "{count, plural, one {# fil framgÃĨngsrikt nerladdad} other {# filer framgÃĨngsrikt nerladdade}}", + "assets_moved_to_trash_count": "Flyttade {count, plural, one {# resurs} other {# resurser}} till papperskorgen", + "assets_permanently_deleted_count": "{count, plural, one {# resurs} other {# resurser}} permanent raderade", + "assets_removed_count": "Tog bort {count, plural, one {# resurs} other {# resurser}}", + "assets_removed_permanently_from_device": "{count, plural, one {# resurs} other {# resurser}} har raderats permanent frÃĨn din enhet", + "assets_restore_confirmation": "Är du säker pÃĨ att du vill ÃĨterställa alla dina slängda resurser? Du kan inte ÃĨngra den här ÃĨtgärden! Observera att offlineresurser inte kan ÃĨterställas pÃĨ detta sätt.", + "assets_restored_count": "Återställde {count, plural, one {# resurs} other {# resurser}}", + "assets_restored_successfully": "{count, plural, one {# resurs} other {# resurser}} har framgÃĨngsrikt ÃĨterställts", + "assets_trashed": "{count, plural, one {# resurs} other {# resurser}} {count, plural, one {flyttad} other {flyttade}} till papperskorgen", + "assets_trashed_count": "{count, plural, one {# resurs} other {# resurser}} {count, plural, one {flyttad} other {flyttade}} till papperskorgen", + "assets_trashed_from_server": "{count} objekt raderade frÃĨn Immich-servern", + "assets_were_part_of_album_count": "{count, plural, one {Resursen} other {Resurserna}} tillhÃļr redan albumet", + "assets_were_part_of_albums_count": "{count, plural, one {Resursen} other {Resurserna}} tillhÃļr redan albumen", "authorized_devices": "Auktoriserade enheter", "automatic_endpoint_switching_subtitle": "Anslut lokalt via det angivna Wi-Fi-nätverket när det är tillgängligt och använd alternativa anslutningar pÃĨ andra platser", "automatic_endpoint_switching_title": "Automatisk URL-växling", "autoplay_slideshow": "Spela upp bildspel automatiskt", "back": "BakÃĨt", "back_close_deselect": "Tillbaka, stäng eller avmarkera", + "background_backup_running_error": "Bakgrundssäkerhetskopiering kÃļrs fÃļr närvarande, kan inte starta manuell säkerhetskopiering", "background_location_permission": "TillÃĨtelse fÃļr bakgrundsplats", "background_location_permission_content": "FÃļr att kunna byta nätverk när appen kÃļrs i bakgrunden mÃĨste Immich *alltid* ha ÃĨtkomst till exakt plats sÃĨ att appen kan läsa av Wi-Fi-nätverkets namn", "background_options": "Bakgrundsalternativ", "backup": "Säkerhetskopiera", + "backup_album_selection_page_albums_device": "Album pÃĨ enhet ({count})", "backup_album_selection_page_albums_tap": "Tryck en gÃĨng fÃļr att inkludera, tryck tvÃĨ gÃĨnger fÃļr att exkludera", - "backup_album_selection_page_assets_scatter": "Objekten kan vara utspridda Ãļver flera album. DärfÃļr kan album inkluderas eller exkluderas under säkerhetskopieringsprocessen.", + "backup_album_selection_page_assets_scatter": "Resurser kan vara utspridda Ãļver flera album. DärfÃļr kan album inkluderas eller exkluderas under säkerhetskopieringsprocessen.", "backup_album_selection_page_select_albums": "Välj album", "backup_album_selection_page_selection_info": "Info om valda objekt", - "backup_albums_sync": "Albumsynkronisering", - "backup_background_service_complete_notification": "Säkerhetskopiering av objekt slutfÃļrd", + "backup_album_selection_page_total_assets": "Antal unika resurser", + "backup_albums_sync": "Backup-albumsynkronisering", + "backup_all": "Allt", + "backup_background_service_backup_failed_message": "Säkerhetskopiering av resurser misslyckades. FÃļrsÃļker igenâ€Ļ", + "backup_background_service_complete_notification": "Säkerhetskopiering av resurser slutfÃļrt", + "backup_background_service_connection_failed_message": "Anslutning till servern misslyckades. FÃļrsÃļker igenâ€Ļ", + "backup_background_service_current_upload_notification": "Laddar upp {filename}", "backup_background_service_default_notification": "SÃļker efter nya objektâ€Ļ", + "backup_background_service_error_title": "Fel vid säkerhetskopiering", "backup_background_service_in_progress_notification": "Säkerhetskopierar dina foton och videorâ€Ļ", + "backup_background_service_upload_failure_notification": "Kunde inte ladda upp {filename}", "backup_controller_page_albums": "Säkerhetskopiera album", + "backup_controller_page_background_app_refresh_disabled_content": "Aktivera uppdatering i bakgrunden i Inställningar > Allmänt > Uppdatering I Bakgrunden fÃļr att använda säkerhetskopiering i bakgrunden.", + "backup_controller_page_background_app_refresh_disabled_title": "Uppdatering i bakgrunden är avaktiverat", + "backup_controller_page_background_app_refresh_enable_button_text": "GÃĨ till inställningar", "backup_controller_page_background_battery_info_link": "Visa mig hur", "backup_controller_page_background_battery_info_message": "FÃļr optimal säkerhetskopiering i bakgrunden bÃļr du stänga av batterioptimering som begränsar bakgrundsaktivitet fÃļr Immich.\n\nEftersom detta är enhetsspecifikt sÃĨ bÃļr du sÃļka instruktioner frÃĨn din enhetstillverkare.", "backup_controller_page_background_battery_info_ok": "OK", "backup_controller_page_background_battery_info_title": "Batterioptimering", + "backup_controller_page_background_charging": "Endast vid laddning", + "backup_controller_page_background_configure_error": "Kunde inte konfigurera bakgrundstjänsten", "backup_controller_page_background_delay": "Skjut upp säkerhetskopiering av nya foton och videor: {duration}", - "backup_controller_page_backup": "Säkerhetskopior", + "backup_controller_page_background_description": "Aktivera säkerhetskopiering i bakgrunden fÃļr att automatiskt säkerhetskopiera nya foton och videor utan att Ãļppna appen", + "backup_controller_page_background_is_off": "Automatisk säkerhetskopiering i bakgrunden är avstängd", + "backup_controller_page_background_is_on": "Automatisk säkerhetskopiering i bakgrunden är aktiverad", + "backup_controller_page_background_turn_off": "Stäng av säkerhetskopiering i bakgrunden", + "backup_controller_page_background_turn_on": "Aktivera säkerhetskopiering i bakgrunden", + "backup_controller_page_background_wifi": "Endast med Wi-Fi", + "backup_controller_page_backup": "Säkerhetskopiera", "backup_controller_page_backup_selected": "Valt: ", "backup_controller_page_backup_sub": "Säkerhetskopierade foton och videor", + "backup_controller_page_created": "Skapad: {date}", + "backup_controller_page_desc_backup": "Aktivera fÃļrgrunds-säkerhetskopiering fÃļr att automatiskt ladda upp nya foton och videor när du Ãļppnar appen.", "backup_controller_page_excluded": "Exkluderat: ", + "backup_controller_page_failed": "Misslyckade ({count})", + "backup_controller_page_filename": "Filnamn: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Säkerhetskopieringsinformation", "backup_controller_page_none_selected": "Ingenting valt", "backup_controller_page_remainder": "Resterande", "backup_controller_page_remainder_sub": "ÅterstÃĨende foton och album att säkerhetskopiera frÃĨn valda", "backup_controller_page_server_storage": "Serverlagring", + "backup_controller_page_start_backup": "Starta säkerhetskopiering", + "backup_controller_page_status_off": "Automatisk säkerhetskopiering är avstängd", + "backup_controller_page_status_on": "Automatisk säkerhetskopiering är aktiverad", "backup_controller_page_storage_format": "{used} av {total} använt", "backup_controller_page_to_backup": "Album att säkerhetskopiera", "backup_controller_page_total_sub": "Alla unika foton och videor frÃĨn valda album", + "backup_controller_page_turn_off": "Stäng av automatisk säkerhetskopiering", + "backup_controller_page_turn_on": "Aktivera automatisk säkerhetskopiering", + "backup_controller_page_uploading_file_info": "Laddar upp filinformation", + "backup_err_only_album": "Kan inte ta bort det enda albumet", "backup_error_sync_failed": "Synkroniseringen misslyckades. Det gÃĨr inte att bearbeta säkerhetskopian.", - "backup_info_card_assets": "objekt", + "backup_info_card_assets": "resurser", + "backup_manual_cancelled": "Avbrutet", + "backup_manual_in_progress": "Uppladdning pÃĨgÃĨr redan. FÃļrsÃļk igen om en liten stund", + "backup_manual_success": "Klart", + "backup_manual_title": "Uppladdningsstatus", "backup_options": "Säkerhetskopieringsalternativ", + "backup_options_page_title": "Säkerhetskopieringsinställningar", + "backup_setting_subtitle": "Hantera inställningar fÃļr fÃļr- och bakgrundsuppladdning", "backup_settings_subtitle": "Hantera uppladdningsinställningar", + "backup_upload_details_page_more_details": "Tryck fÃļr mer detaljer", "backward": "BakÃĨt", - "battery_optimization_backup_reliability": "Inaktivera batterioptimeringar fÃļr stabilare säkerhetskopiering i bakgrunden", "biometric_auth_enabled": "Biometrisk autentisering aktiverad", "biometric_locked_out": "Du är utelÃĨst frÃĨn biometrisk autentisering", "biometric_no_options": "Inga biometriska alternativ tillgängliga", "biometric_not_available": "Biometrisk autentisering är inte tillgänglig pÃĨ denna enhet", + "birthdate_saved": "FÃļdelsedatumet har sparats", "birthdate_set_description": "FÃļdelsedatum används fÃļr att beräkna ÃĨldern pÃĨ denna person vid tidpunkten fÃļr ett foto.", "blurred_background": "Suddig bakgrund", - "browse_templates": "Bläddra bland mallar", "bugs_and_feature_requests": "Buggar och funktionsfÃļrfrÃĨgningar", "build": "Bygge", "build_image": "Byggfil", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Är du säker pÃĨ att du vill behÃĨlla {count, plural, one {# dublett} other {# dubletter}}? Detta kommer att lÃļsa alla grupper av dubbletter utan att ta bort nÃĨgonting.", "bulk_trash_duplicates_confirmation": "Är du säker pÃĨ att du vill skicka {count, plural, one {# dublett} other {# dubletter}} till papperskorgen? Detta kommer att behÃĨlla det stÃļrsta objektet i varje grupp och alla andra dubbletter kasseras.", "buy": "KÃļp Immich", + "cache_settings_clear_cache_button": "Rensa cacheminnet", + "cache_settings_clear_cache_button_title": "Rensar appens cacheminne. Detta kommer att avsevärt pÃĨverka appens prestanda tills cachen har byggts om.", + "cache_settings_duplicated_assets_clear_button": "RENSA", + "cache_settings_duplicated_assets_subtitle": "Foton och videor som är ignorerade, listas av appen", + "cache_settings_duplicated_assets_title": "Duplicerade Objekt ({count})", + "cache_settings_statistics_album": "Miniatyrbilder fÃļr bibliotek", + "cache_settings_statistics_full": "Hela bilder", + "cache_settings_statistics_shared": "Miniatyrbilder till delat album", + "cache_settings_statistics_thumbnail": "Miniatyrbilder", + "cache_settings_statistics_title": "CachefÃļrbrukning", + "cache_settings_subtitle": "Hantera cachebeteendet fÃļr Immich-appen", + "cache_settings_tile_subtitle": "Kontrollera beteende fÃļr lokal lagring", + "cache_settings_tile_title": "Lokal Lagring", + "cache_settings_title": "Cache Inställningar", "camera": "Kamera", "camera_brand": "Kameramärke", "camera_model": "Kameramodell", @@ -678,10 +729,10 @@ "cannot_update_the_description": "Det gÃĨr inte att uppdatera beskrivningen", "cast": "Casta", "cast_description": "Konfigurera tillgängliga cast-destinationer", - "change": "Ändra", "change_date": "Ändra datum", "change_description": "Ändra beskrivning", "change_display_order": "Ändra visningsordning", + "change_expiration_time": "Ändra utgÃĨngstid", "change_location": "Ändra plats", "change_name": "Byt namn", "change_name_successfully": "Bytte namn framgÃĨngsrikt", @@ -695,34 +746,39 @@ "change_password_form_password_mismatch": "LÃļsenorden matchar inte", "change_password_form_reenter_new_password": "Ange Nytt LÃļsenord Igen", "change_pin_code": "Ändra PIN-kod", + "change_trigger": "Ändra utlÃļsare", + "change_trigger_prompt": "Är du säker pÃĨ att du vill ändra utlÃļsaren? Detta tar bort alla befintliga ÃĨtgärder och filter.", "change_your_password": "Ändra ditt lÃļsenord", "changed_visibility_successfully": "Synligheten har ändrats", "charging": "Laddar", "charging_requirement_mobile_backup": "Bakgrundssäkerhetskopiering kräver att enheten laddas", + "check_corrupt_asset_backup": "Kontrollera om det finns korrupta resursbackuper", + "check_corrupt_asset_backup_button": "Kontrollera", + "check_corrupt_asset_backup_description": "KÃļr kontrollen endast Ãļver Wi-Fi och när alla resurser har säkerhetskopierats. Det kan ta nÃĨgra minuter.", "check_logs": "Kontrollera loggar", "checksum": "Checksumma", - "choose": "Välj", "choose_matching_people_to_merge": "Välj matchande personer att slÃĨ samman", "city": "Stad", - "cleanup_confirm_description": "Immich hittade {count} objekt (skapade fÃļre {date}) som säkerhetskopierats säkert till servern. Ta bort de lokala kopiorna frÃĨn den här enheten?", + "cleanup_confirm_description": "Immich hittade {count} resurser (skapade fÃļre {date}) som säkerhetskopierats säkert till servern. Ta bort de lokala kopiorna frÃĨn den här enheten?", "cleanup_confirm_prompt_title": "Ta bort frÃĨn den här enheten?", - "cleanup_deleted_assets": "Flyttade {count} objekt till enhetens papperskorg", + "cleanup_deleted_assets": "Flyttade {count, plural, one {# resurs} other {# resurser}} till enhetens papperskorg", "cleanup_deleting": "Flyttar till papperskorg...", - "cleanup_found_assets": "Hittade {count} säkerhetskopierade objekt", + "cleanup_found_assets": "Hittade {count} {count, plural, one {säkerhetskopierad resurs} other {säkerhetskopierade resurser}}", "cleanup_found_assets_with_size": "Hittade {count} säkerhetskopierade objekt ({size})", "cleanup_icloud_shared_albums_excluded": "iCloud delade album exkluderas frÃĨn skanningen", "cleanup_no_assets_found": "Inga objekt hittades som matchar kriterierna ovan. FrigÃļr Utrymme kan bara ta bort objekt som har säkerhetskopierats till servern", - "cleanup_preview_title": "Objekt att ta bort ({count})", + "cleanup_preview_title": "Resurser att ta bort ({count})", "cleanup_step3_description": "Skanna efter säkerhetskopierade objekt som matchar ditt datum och behÃĨll inställningarna.", "cleanup_step4_summary": "{count} objekt (skapade fÃļre {date}) att tas bort frÃĨn din lokala enhet. Foton kommer att fÃļrbli tillgängliga frÃĨn Immich-appen.", "cleanup_trash_hint": "FÃļr att helt frigÃļra lagringsutrymme, Ãļppna systemgalleriappen och tÃļm papperskorgen", "clear": "Rensa", "clear_all": "Rensa allt", "clear_all_recent_searches": "Rensa alla senaste sÃļkningar", - "clear_failed_count": "Rensa misslyckades ({count})", "clear_file_cache": "Rensa filcachen", "clear_message": "Rensa meddelande", "clear_value": "Rensa värde", + "client_cert_dialog_msg_confirm": "OK", + "client_cert_enter_password": "Ange LÃļsenord", "client_cert_import": "Importera", "client_cert_import_success_msg": "Klientcertifikatet är importerat", "client_cert_invalid_msg": "Felaktig certifikatfil eller fel lÃļsenord", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "Klientcertifikatet är borttaget", "client_cert_subtitle": "StÃļdjer endast formatet PKCS12 (.p12, .pfx). import/borttagning av certifikat är tillgängligt endast fÃļre inloggning", "client_cert_title": "SSL klientcertifikat [EXPERIMENTELLT]", + "clockwise": "Medsols", "close": "Stäng", "collapse": "Kollapsa", "collapse_all": "Kollapsa alla", "color": "Färg", + "color_theme": "Färgtema", "command": "Kommando", "command_palette_prompt": "Hitta snabbt sidor, ÃĨtgärder eller kommandon", "command_palette_to_close": "att stänga", @@ -743,11 +801,10 @@ "command_palette_to_show_all": "fÃļr att visa alla", "comment_deleted": "Kommentar raderad", "comment_options": "Kommentarsalternativ", - "comments_and_likes": "Kommentarer och gillamarkeringar", + "comments_and_likes": "Kommentarer & likes", "comments_are_disabled": "Kommentarer är avstängda", "common_create_new_album": "Skapa ett nytt album", "completed": "Klar", - "configuration": "Konfiguration", "confirm": "Bekräfta", "confirm_admin_password": "Bekräfta administratÃļrslÃļsenord", "confirm_delete_face": "Är du säker pÃĨ att du vill ta bort {name}s ansikte frÃĨn objektet?", @@ -762,16 +819,19 @@ "contain": "Anpassa", "context": "Sammanhang", "continue": "Fortsätt", - "control_bottom_app_bar_add_tags": "Lägg till taggar", + "control_bottom_app_bar_create_new_album": "Skapa nytt album", + "control_bottom_app_bar_delete_from_immich": "Ta bort frÃĨn Immich", "control_bottom_app_bar_delete_from_local": "Ta bort frÃĨn enhet", "control_bottom_app_bar_edit_location": "Redigera plats", "control_bottom_app_bar_edit_time": "Redigera Datum & Tid", + "control_bottom_app_bar_share_link": "Dela Länk", + "control_bottom_app_bar_share_to": "Dela Till", "control_bottom_app_bar_trash_from_immich": "Flytta till Papperskorgen", "copied_image_to_clipboard": "Kopierade bilden till urklipp.", "copied_to_clipboard": "Kopierat till urklipp!", "copy_error": "Kopieringsfel", + "copy_file_path": "Kopiera filsÃļkväg", "copy_image": "Kopiera Bild", - "copy_json": "Kopiera JSON", "copy_link": "Kopiera länk", "copy_link_to_clipboard": "Kopiera länken till urklipp", "copy_password": "Kopiera lÃļsenord", @@ -789,6 +849,7 @@ "create_link_to_share": "Skapa länk att dela", "create_link_to_share_description": "LÃĨt alla med länken se de valda fotona", "create_new": "SKAPA NY", + "create_new_face": "Skapa nytt ansikte", "create_new_person": "Skapa ny person", "create_new_person_hint": "Tilldela valda objekt till en ny person", "create_new_user": "Skapa en ny användare", @@ -805,33 +866,37 @@ "created_at": "Skapad", "creating_linked_albums": "Skapar länkade album...", "crop": "Beskär", + "crop_aspect_ratio_fixed": "Fixat", "crop_aspect_ratio_free": "Fritt", "crop_aspect_ratio_original": "Original", "crop_aspect_ratio_square": "Kvadrat", + "curated_object_page_title": "Objekt", "current_device": "Aktuell enhet", "current_pin_code": "Nuvarande PIN-kod", "current_server_address": "Aktuell server-adress", "custom_date": "Anpassat datum", "custom_locale": "Anpassad sprÃĨkinställning", "custom_locale_description": "Formatera datum, tider och siffror baserat pÃĨ valt sprÃĨk och region", + "custom_url": "Anpassad URL", "cutoff_date_description": "BehÃĨll bilder frÃĨnâ€Ļ", "cutoff_day": "{count, plural, one {dag} other {dagar}}", "cutoff_year": "{count, plural, one {ÃĨr} other {ÃĨr}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "MÃļrk", "dark_theme": "Växla till mÃļrkt tema", "date": "Datum", "date_after": "Datum efter", "date_and_time": "Datum och Tid", "date_before": "Datum fÃļre", - "date_of_birth": "FÃļdelsedatum", + "date_format": "E d. LLL y â€ĸ hh:mm", "date_of_birth_saved": "FÃļdelsedatumet har sparats", "date_range": "Datumintervall", - "date_time_original": "Datum/tid original", "day": "Dag", "days": "Dagar", "deduplicate_all": "Deduplicera alla", - "default_quality_subtitle": "Kvalitet som används vid delning. HÃĨll ner delaknappen fÃļr att välja kvalitet varje gÃĨng.", - "default_share_quality": "Standarddelningskvalitet", + "default_locale": "StandardsprÃĨk", + "default_locale_description": "Formatera datum och siffror baserat pÃĨ din webbläsares sprÃĨkinställningar", "delete": "Radera", "delete_action_confirmation_message": "Är du säker pÃĨ att du vill ta bort det här objektet? Den här ÃĨtgärden flyttar objektet till serverns papperskorg och frÃĨgar om du vill ta bort den lokalt", "delete_action_prompt": "{count} raderade", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Dessa objekt kommer att raderas permanent frÃĨn Immich och din enhet", "delete_dialog_alert_local": "Dessa objekt kommer att tas bort frÃĨn din enhet men fortsatt vara tillgängliga pÃĨ Immich-servern", "delete_dialog_alert_local_non_backed_up": "NÃĨgra objekt har inte säkerhetskopierats till Immich och kommer att tas bort permanent frÃĨn din enhet", + "delete_dialog_alert_remote": "Dessa objekt kommer att tas bort permanent frÃĨn Immich-servern", + "delete_dialog_ok_force": "Ta Bort ÄndÃĨ", "delete_dialog_title": "Radera permanent", "delete_duplicates_confirmation": "Är du säker pÃĨ att du vill ta bort dessa dubbletter permanent?", "delete_face": "Ta bort ansikte", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "Är du säker pÃĨ att du vill ta bort {tagName}-taggen?", "delete_user": "Ta bort användare", "deleted_shared_link": "Ta bort delad länk", + "deletes_missing_assets": "Tar bort objekt som saknas pÃĨ disken", "description": "Beskrivning", + "description_input_hint_text": "Lägg till beskrivning...", + "description_input_submit_error": "Fel vid uppdatering av beskrivning, se loggen fÃļr fler detaljer", "deselect_all": "Avmarkera alla", "details": "Detaljer", "direction": "Riktning", "disable": "inaktivera", "disabled": "Inaktiverad", + "disallow_edits": "TillÃĨt inte redigeringar", "discord": "Discord", "discover": "Upptäck", "discovered_devices": "Funna enheter", "dismiss_all_errors": "Avvisa alla fel", + "dismiss_error": "Avvisa fel", "display_options": "Visningsalternativ", "display_order": "Visa Ordning", "display_original_photos": "Visa originalfoton", @@ -876,9 +948,11 @@ "documentation": "Dokumentation", "done": "Klart", "download": "Ladda ner", + "download_action_prompt": "Laddar ner {count} resurser", "download_canceled": "Nedladdning avbruten", "download_complete": "Nedladdning slutfÃļrd", "download_enqueue": "Nedladdning kÃļad", + "download_error": "Fel vid nedladdning", "download_failed": "Nedladdning misslyckades", "download_finished": "Nedladdning klar", "download_include_embedded_motion_videos": "Inbäddade videor", @@ -887,16 +961,16 @@ "download_original": "Ladda ner ursprunglig fil", "download_paused": "Nedladdning pausad", "download_settings": "Ladda ner", - "download_settings_description": "Hantera inställningar relaterade till nedladdning av objekt", + "download_settings_description": "Hantera inställningar relaterade till nedladdning av resurser", + "download_started": "Nedladdning pÃĨbÃļrjad", + "download_sucess": "Nedladdning lyckades", + "download_sucess_android": "Media har laddats ner till DCIM/Immich", "download_waiting_to_retry": "Väntar pÃĨ omfÃļrsÃļk", "downloading": "Laddar ner", "downloading_asset_filename": "Laddar ner objekt {filename}", "downloading_from_icloud": "Laddar ner frÃĨn iCloud", "downloading_media": "Laddar ner media", - "drag_to_reorder": "Dra fÃļr att ändra ordning", "drop_files_to_upload": "Släpp filer var som helst fÃļr att ladda upp", - "duplicate": "Duplicera", - "duplicate_workflow": "Duplicera arbetsflÃļde", "duplicates": "Dubletter", "duplicates_description": "LÃļs varje grupp genom att ange vilka, om nÃĨgra, är dubbletter.", "duration": "Varaktighet", @@ -908,7 +982,9 @@ "edit_date_and_time": "Redigera datum och tid", "edit_date_and_time_action_prompt": "{count} datum och tid redigerad", "edit_date_and_time_by_offset": "Ändra datum med fÃļrskjutning", + "edit_date_and_time_by_offset_interval": "Nytt datumintervall: {from} - {to}", "edit_description": "Redigera beskrivning", + "edit_description_prompt": "Vänligen välj en ny beskrivning:", "edit_exclusion_pattern": "Redigera uteslutningsmÃļnster", "edit_faces": "Redigera ansikten", "edit_key": "Redigera nyckel", @@ -923,6 +999,9 @@ "edit_user": "Redigera användare", "edit_workflow": "Redigera arbetsflÃļde", "editor": "Redigerare", + "editor_close_without_save_prompt": "Ändringarna kommer inte att sparas", + "editor_close_without_save_title": "Stäng redigeraren?", + "editor_confirm_reset_all_changes": "Är du säker pÃĨ att du vill ÃĨterställa alla ändringar?", "editor_discard_edits_confirm": "Ignorera redigeringar", "editor_discard_edits_prompt": "Du har redigeringar som inte har sparats. Är du säker pÃĨ att du vill slänga dem?", "editor_discard_edits_title": "Släng redigeringar?", @@ -930,28 +1009,30 @@ "editor_edits_applied_success": "Redigeringarna har tillämpats framgÃĨngsrikt", "editor_flip_horizontal": "Vänd horisontellt", "editor_flip_vertical": "Vänd vertikalt", - "editor_handle_corner": "{corner, select, top_left {Övre vänstra} top_right {Övre hÃļgra} bottom_left {Nedre vänstra} bottom_right {Nedre hÃļgra} other {Ett}} hÃļrnhandtag", - "editor_handle_edge": "{edge, select, top {Övre} bottom {Nedre} left {Vänster} right {HÃļger} other {Ett}} kanthandtag", + "editor_handle_corner": "{corner, select, top_left {Övre vänstra} top_right {Övre hÃļgra} bottom_left {Nedre vänstra} bottom_right {Nedre hÃļgra} other {A}} hÃļrn handtag", + "editor_handle_edge": "{edge, select, top {Övre} bottom {Nedre} left {Vänster} right {HÃļger} other {En}} hÃļrnhandtag", "editor_orientation": "Orientering", "editor_reset_all_changes": "Återställ ändringar", "editor_rotate_left": "Rotera 90° moturs", "editor_rotate_right": "Rotera 90° medurs", - "email": "E-post", + "email": "Epost", "email_notifications": "E-postaviseringar", "empty_folder": "Mappen är tom", "empty_trash": "TÃļm papperskorg", "empty_trash_confirmation": "Är du säker pÃĨ att du vill tÃļmma papperskorgen? Detta tar bort alla objekt i papperskorgen permanent frÃĨn Immich.\nDu kan inte ÃĨngra den här ÃĨtgärden!", "enable": "Aktivera", "enable_backup": "Aktivera säkerhetskopiering", - "enable_biometric_auth_description": "Skriv in din PIN-kod fÃļr att aktivera biometrisk autentisering", + "enable_biometric_auth_description": "Skriv in din pinkod fÃļr att aktivera biometrisk autentisering", "enabled": "Aktiverad", "end_date": "Slutdatum", "enqueued": "KÃļad", "enter_wifi_name": "Ange Wi-Fi-namn", - "enter_your_pin_code": "Skriv in din PIN-kod", - "enter_your_pin_code_subtitle": "Skriv in din PIN-kod fÃļr att komma ÃĨt lÃĨst mapp", + "enter_your_pin_code": "Skriv in din pinkod", + "enter_your_pin_code_subtitle": "Skriv in din pinkod fÃļr att komma ÃĨt lÃĨst mapp", "error": "Fel", + "error_change_sort_album": "Kunde inte ändra sorteringsordning fÃļr album", "error_delete_face": "Fel uppstod när ansikte skulle tas bort frÃĨn objektet", + "error_getting_places": "Det gick inte att hämta platser", "error_loading_albums": "Fel vid laddning av album", "error_loading_image": "Fel vid bildladdning", "error_loading_partners": "Fel vid inläsning av partner: {error}", @@ -966,7 +1047,7 @@ "cant_apply_changes": "Det gÃĨr inte att tillämpa ändringar", "cant_change_activity": "Kan inte {enabled, select, true {avaktivera} other {aktivera}} aktivitet", "cant_change_asset_favorite": "Det gÃĨr inte att byta favorit fÃļr objekt", - "cant_change_metadata_assets_count": "Det gÃĨr inte att ändra metadata fÃļr {count, plural, one {# objekt} other {# objekt}}", + "cant_change_metadata_assets_count": "Det gÃĨr inte att ändra metadata fÃļr {count, plural, one {# resurs} other {# resurser}}", "cant_get_faces": "Kan inte fÃĨ ansikten", "cant_get_number_of_comments": "Kan inte fÃĨ antal kommentarer", "cant_search_people": "Kan inte sÃļka efter personer", @@ -986,12 +1067,11 @@ "failed_to_keep_this_delete_others": "Misslyckades att behÃĨlla detta objekt och radera Ãļvriga objekt", "failed_to_load_asset": "Det gick inte att ladda objekt", "failed_to_load_assets": "Det gick inte att ladda objekten", - "failed_to_load_notifications": "Det gick inte att läsa in aviseringar", + "failed_to_load_notifications": "Misslyckades med att ladda notifikationer", "failed_to_load_people": "Det gick inte att ladda personer", "failed_to_remove_product_key": "Det gick inte att ta bort produktnyckeln", "failed_to_reset_pin_code": "Misslyckades med att ÃĨterställa PIN-koden", "failed_to_stack_assets": "Det gick inte att stapla objekt", - "failed_to_tag_assets": "Misslyckades med att tagga objekt", "failed_to_unstack_assets": "Det gick inte att avstapla objekt", "failed_to_update_notification_status": "Misslyckades med att uppdatera aviseringens status", "incorrect_email_or_password": "Felaktig e-postadress eller lÃļsenord", @@ -1007,15 +1087,15 @@ "unable_to_add_exclusion_pattern": "Det gick inte att lägga till uteslutningsmÃļnster", "unable_to_add_partners": "Kunde inte lägga till partners", "unable_to_add_remove_archive": "Det gÃĨr inte att {archived, select, true {ta bort objekt frÃĨn} other {lägga till objekt till}} arkiv", - "unable_to_add_remove_favorites": "Det gÃĨr inte att {favorite, select, true {lägga till objektet i} other {ta bort objektet frÃĨn}} favoriter", - "unable_to_archive_unarchive": "Det gÃĨr inte att {archived, select, true {arkivera} other {ta bort frÃĨn arkivet}}", + "unable_to_add_remove_favorites": "Det gÃĨr inte att {favorite, select, true {add asset to} other {remove asset from}} favoriter", + "unable_to_archive_unarchive": "Det gÃĨr inte att {archived, select, true {archive} other {archive}}", "unable_to_change_album_user_role": "Kunde inte ändra albumanvändarens roll", "unable_to_change_date": "Kunde inte ändra datum", "unable_to_change_description": "Kunde inte ändra beskrivning", "unable_to_change_favorite": "Det gÃĨr inte att ändra favorit fÃļr objekt", "unable_to_change_location": "Kunde inte ändra plats", "unable_to_change_password": "Det gÃĨr inte att ändra lÃļsenord", - "unable_to_change_visibility": "Det gick inte att ändra synligheten fÃļr {count, plural, one {# person} other {# personer}}", + "unable_to_change_visibility": "Det gick inte att ändra synligheten fÃļr {count, plural, one {# person} other {# people}}", "unable_to_complete_oauth_login": "Det gick inte att slutfÃļra OAuth-inloggning", "unable_to_connect": "Det gÃĨr inte att ansluta", "unable_to_copy_to_clipboard": "Kan inte kopiera till urklipp, se till att du kommer ÃĨt sidan via https", @@ -1045,23 +1125,23 @@ "unable_to_log_out_device": "Det gick inte att logga ut enheten", "unable_to_login_with_oauth": "Det gick inte att logga in med OAuth", "unable_to_play_video": "Kunde inte spela upp video", - "unable_to_reassign_assets_existing_person": "Det gÃĨr inte att tilldela objekten till {name, select, null {en befintlig person} other {{name}}}", + "unable_to_reassign_assets_existing_person": "Det gÃĨr inte att tilldela objekt till {name, select, null {an existing person} other {{name}}}", "unable_to_reassign_assets_new_person": "Kunde inte tilldela objekt till en annan person", "unable_to_refresh_user": "Kunde inte ladda om användaren", "unable_to_remove_album_users": "Kunde inte ta bort personen frÃĨn albumet", - "unable_to_remove_api_key": "Det gick inte att ta bort API-nyckeln", + "unable_to_remove_api_key": "Det gick inte att ta bort API Keyet", "unable_to_remove_assets_from_shared_link": "Kunde inte ta bort objekt frÃĨn delade länkar", "unable_to_remove_library": "Kunde inte ta bort bibliotek", "unable_to_remove_partner": "Kunde inte ta bort partner", "unable_to_remove_reaction": "Kunde inte ta bort reaktion", "unable_to_reset_password": "Kunde inte ÃĨterställa lÃļsenord", - "unable_to_reset_pin_code": "Kunde inte ÃĨterställa PIN-kod:en", + "unable_to_reset_pin_code": "Kunde inte ÃĨterställa pinkod", "unable_to_resolve_duplicate": "Det gÃĨr inte att lÃļsa dubbletter", "unable_to_restore_assets": "Det gÃĨr inte att ÃĨterställa objekt", "unable_to_restore_trash": "Det gick inte att ÃĨterställa papperskorgen", "unable_to_restore_user": "Kunde inte ÃĨterställa användare", "unable_to_save_album": "Kunde inte spara album", - "unable_to_save_api_key": "Det gick inte att spara API-nyckeln", + "unable_to_save_api_key": "Det gÃĨr inte att spara API Nyckel", "unable_to_save_date_of_birth": "Det gÃĨr inte att spara fÃļdelsedatum", "unable_to_save_name": "Kunde inte spara namn", "unable_to_save_profile": "Kunde inte spara profil", @@ -1072,7 +1152,7 @@ "unable_to_set_profile_picture": "Det gÃĨr inte att ställa in profilbilden", "unable_to_set_rating": "Det gick inte att sätta betyg", "unable_to_submit_job": "Det gÃĨr inte att skicka jobbet", - "unable_to_trash_asset": "Det gÃĨr inte att slänga objektet", + "unable_to_trash_asset": "Det gÃĨr inte att slänga resursen", "unable_to_unlink_account": "Det gÃĨr inte att ta bort länken till kontot", "unable_to_unlink_motion_video": "Det gÃĨr inte att ta bort länken till rÃļrelsevideo", "unable_to_update_album_cover": "Det gÃĨr inte att uppdatera albumomslaget", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Lägg till beskrivning...", "exif_bottom_sheet_description_error": "Fel vid uppdatering av beskrivningen", + "exif_bottom_sheet_details": "DETALJER", + "exif_bottom_sheet_location": "PLATS", "exif_bottom_sheet_no_description": "Ingen beskrivning", + "exif_bottom_sheet_people": "PERSONER", + "exif_bottom_sheet_person_add_person": "Lägg till namn", "exit_slideshow": "Avsluta bildspel", "expand": "Expandera", "expand_all": "Expandera alla", + "experimental_settings_new_asset_list_subtitle": "Under uppbyggnad", + "experimental_settings_new_asset_list_title": "Aktivera experimentellt fotorutnät", + "experimental_settings_subtitle": "Använd pÃĨ egen risk!", + "experimental_settings_title": "Experimentellt", "expire_after": "GÃĨr ut efter", "expired": "GÃĨtt ut", "expires_date": "GÃĨr ut {date}", @@ -1103,53 +1191,53 @@ "export_as_json": "Exportera som JSON", "export_database": "Exportera databas", "export_database_description": "Exportera SQLite-databasen", - "exposure_time": "Exponeringstid", "extension": "FÃļrlängning", "external": "Externt", "external_libraries": "Externa Bibliotek", "external_network": "Externt nätverk", "external_network_sheet_info": "När appen inte är ansluten till det fÃļredragna Wi-Fi-nätverket, kommer den att ansluta till servern via den fÃļrsta av fÃļljande URL:er den kan nÃĨ, frÃĨn toppen till botten", - "f_number": "F-nummer", "face_unassigned": "Otilldelade", "failed": "Misslyckades", "failed_count": "Misslyckade: {count}", "failed_to_authenticate": "Misslyckades med autentisering", - "failed_to_delete_file": "Misslyckades att radera fil", - "failed_to_load_assets": "Det gick inte att läsa in objekten", + "failed_to_load_assets": "Det gick inte att läsa in resurser", "failed_to_load_folder": "Kunde inte ladda mappen", "favorite": "Favorit", "favorite_action_prompt": "{count} har lagts till i favoriter", "favorite_or_unfavorite_photo": "Favoritfoto eller icke-favoritfoto", "favorites": "Favoriter", + "favorites_page_no_favorites": "Inga favoritobjekt hittades", "feature_photo_updated": "Funktionsfoto uppdaterad", "features": "Funktioner", + "features_in_development": "Funktioner i utveckling", "features_setting_description": "Hantera appens funktioner", "file_name_or_extension": "Filnamn eller -tillägg", "file_name_text": "Filnamn", + "file_name_with_value": "Filnamn: {file_name}", "file_size": "Filstorlek", "filename": "Filnamn", "filetype": "Filtyp", "filter": "Filter", + "filter_description": "Villkor fÃļr att filtrera objekten", "filter_people": "Filtrera personer", "filter_places": "Filtrera platser", "filter_tags": "Filtrera taggar", "filters": "Filter", + "find_them_fast": "Hitta dem snabbt efter namn med sÃļk", "first": "FÃļrst", "fix_incorrect_match": "Fixa inkorrekt matchning", - "focal_length": "Brännvidd", "folder": "Mapp", "folder_not_found": "Mappen hittade inte", "folders": "Mappar", "folders_feature_description": "Bläddra i mappvyn fÃļr foton och videoklipp i filsystemet", - "forgot_pin_code_question": "GlÃļmt din PIN-kod?", + "forgot_pin_code_question": "GlÃļmt din pinkod?", "forward": "FramÃĨt", "free_up_space": "FrigÃļr utrymme", "free_up_space_description": "Flytta säkerhetskopierade foton och videor till din enhets papperskorg fÃļr att frigÃļra utrymme. Dina kopior pÃĨ servern fÃļrblir säkra.", "free_up_space_settings_subtitle": "FrigÃļr lagringsutrymme pÃĨ enheten", "full_path": "Fullständig sÃļkväg: {path}", - "full_path_or_folder": "Fullständig sÃļkväg eller mapp", "gcast_enabled": "Google-Cast", - "gcast_enabled_description": "Denna funktion läser in externa objekt frÃĨn Google fÃļr att fungera.", + "gcast_enabled_description": "Denna funktion läser in externa resurser frÃĨn Google fÃļr att fungera.", "general": "Allmänt", "geolocation_instruction_location": "Klicka pÃĨ ett objekt med GPS-koordinater fÃļr att använda dess plats, eller välj en plats direkt frÃĨn kartan", "get_help": "FÃĨ hjälp", @@ -1168,12 +1256,13 @@ "group_owner": "Grupper efter ägare", "group_places_by": "Gruppera platser efterâ€Ļ", "group_year": "Gruppera efter ÃĨrtal", - "haptic_feedback_title": "Haptisk ÃĨterkoppling", + "haptic_feedback_switch": "Aktivera haptisk feedback", + "haptic_feedback_title": "Haptisk Feedback", "has_quota": "Har kvot", "hash_asset": "Hash-objekt", "hashed_assets": "Hashade-objekt", "hashing": "Hashning", - "header_settings_add_header_tip": "Lägg till rubrik", + "header_settings_add_header_tip": "Lägg till header", "header_settings_field_validator_msg": "Värdet kan inte vara tomt", "header_settings_header_name_input": "Header-namn", "header_settings_header_value_input": "Header-värde", @@ -1188,38 +1277,58 @@ "hide_schema": "GÃļm schema", "hide_text_recognition": "DÃļlj textigenkänning", "hide_unnamed_people": "GÃļm personer utan namn", + "home_page_add_to_album_conflicts": "Lade till {added} resurser i albumet {album}. {failed} resurser finns redan i albumet.", + "home_page_add_to_album_err_local": "Kan inte lägga till lokala objekt till album ännu, hoppar Ãļver", + "home_page_add_to_album_success": "Lade till {added} resurser i albumet {album}.", + "home_page_album_err_partner": "Kan inte lägga till partner-objekt till album ännu, hoppar Ãļver", + "home_page_archive_err_local": "Kan inte arkivera lokala objekt ännu, hoppar Ãļver", + "home_page_archive_err_partner": "Kan inte arkivera partner-objekt, hoppar Ãļver", "home_page_building_timeline": "Bygger tidslinjen", + "home_page_delete_err_partner": "Kan inte ta bort partner-objekt, hoppar Ãļver", + "home_page_delete_remote_err_local": "Lokala objekt i urvalet fÃļr att ta bort frÃĨn servern, hoppar Ãļver", + "home_page_favorite_err_local": "Kan inte favoritmarkera lokala objekt ännu, hoppar Ãļver", + "home_page_favorite_err_partner": "Kan inte favoritmarkera partner-objekt ännu, hoppar Ãļver", + "home_page_first_time_notice": "Om det här är fÃļrsta gÃĨngen du använder appen, välj ett eller flera backup-album sÃĨ att tidslinjen kan fyllas med foton och videor frÃĨn albumen", + "home_page_locked_error_local": "Kan inte flytta lokala resurser till lÃĨst mapp, hoppar Ãļver", + "home_page_locked_error_partner": "Kan inte flytta partnerresurser till lÃĨst mapp, hoppar Ãļver", + "home_page_share_err_local": "Kan inte dela lokalt objekt via länk, hoppar Ãļver", + "home_page_upload_err_limit": "Kan bara ladda upp max 30 objekt ÃĨt gÃĨngen, hoppar Ãļver", "host": "Värd", "hour": "Timme", "hours": "Timmar", "id": "ID", "idle": "Inaktiv", + "ignore_icloud_photos": "Ignorera iCloud-foton", + "ignore_icloud_photos_description": "Foton lagrade i iCloud kommer inte laddas upp till Immich-servern", "image": "Bild", - "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} tagen den {date}", - "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Bild}} tagen med {person1} den {date}", - "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Bild}} tagen med {person1} och {person2} den {date}", - "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Bild}} tagen med {person1}, {person2} och {person3} den {date}", - "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} tagen med {person1}, {person2} och {additionalCount, number} andra den {date}", - "image_alt_text_date_place": "{isVideo, select, true {Video} other {Bild}} tagen i {city}, {country} den {date}", - "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Bild}} tagen i {city}, {country} med {person1} den {date}", - "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Bild}} tagen i {city}, {country} med {person1} och {person2} den {date}", - "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Bild}} tagen i {city}, {country} med {person1}, {person2} och {person3} den {date}", - "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Bild}} tagen i {city}, {country} med {person1}, {person2} och {additionalCount, number} andra den {date}", + "image_alt_text_date": "{isVideo, select, true {Video} other {Bild}} tagen {date}", + "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Image}} tagen med {person1} den {date}", + "image_alt_text_date_2_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1} och {person2} den {date}", + "image_alt_text_date_3_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1}, {person2}, och {person3} den {date}", + "image_alt_text_date_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tagen med {person1}, {person2}, och {additionalCount, number} andra den {date}", + "image_alt_text_date_place": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} den {date}", + "image_alt_text_date_place_1_person": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1} den {date}", + "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1} och {person2} den {date}", + "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1}, {person2}, och {person3} den {date}", + "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Image}} tagen i {city}, {country} med {person1}, {person2}, och {additionalCount, number} andre den {date}", + "image_saved_successfully": "Bild sparad", + "image_viewer_page_state_provider_download_started": "Nedladdning PÃĨbÃļrjad", + "image_viewer_page_state_provider_download_success": "Nedladdningen Lyckades", + "image_viewer_page_state_provider_share_error": "Delningsfel", "immich_logo": "Immich-logga", "immich_web_interface": "Immich Web gränssnitt", "import_from_json": "Importera frÃĨn JSON", "import_path": "ImportsÃļkväg", - "in_albums": "I {count, plural, one {# album} other {# album}}", + "in_albums": "I {count, plural, one {# album} other {# albums}}", "in_archive": "I arkivet", "in_year": "I {year}", "in_year_selector": "In", "include_archived": "Inkludera arkiverade", "include_shared_albums": "Inkludera delade album", - "include_shared_partner_assets": "Inkludera partnerdelade objekt", + "include_shared_partner_assets": "Inkludera partnerdelade resurser", "individual_share": "Enskild delning", "individual_shares": "Individuella delningar", "info": "Information", - "integrity_checks": "Integritetskontroller", "interval": { "day_at_onepm": "Alla dagar vid kl 13.00", "hours": "Vid varje {hours, plural, one {hour} other {{hours, number} hours}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Felaktigt datumformat", "invite_people": "Bjud in personer", "invite_to_album": "Bjuder in till album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Hämtning kÃļrdes {dateTime}", + "ios_debug_info_last_sync_at": "Senaste synkning {dateTime}", + "ios_debug_info_no_processes_queued": "Inga bakgrundsprocesser kÃļade", + "ios_debug_info_no_sync_yet": "Inget bakgrundssynkroniseringsjobb har kÃļrts ännu", + "ios_debug_info_processes_queued": "{count, plural, one {{count} bakgrundsprocess kÃļad} other {{count} bakgrundsprocesser kÃļade}}", + "ios_debug_info_processing_ran_at": "Bearbetningen kÃļrdes {dateTime}", "items_count": "{count, plural, one {# objekt} other {# objekt}}", "jobs": "Jobb", + "json_editor": "JSON-redigerare", + "json_error": "JSON-fel", "keep": "BehÃĨll", "keep_albums": "BehÃĨll album", "keep_albums_count": "BehÃĨller {count} {count, plural, one {album} other {album}}", @@ -1243,7 +1359,7 @@ "keep_on_device_hint": "Välj objekt som ska behÃĨllas pÃĨ denna enhet", "keep_this_delete_others": "BehÃĨll denna, radera Ãļvriga", "keeping": "BehÃĨller: {items}", - "kept_this_deleted_others": "BehÃļll det här objektet och tog bort {count, plural, one {# objekt} other {# objekt}}", + "kept_this_deleted_others": "BehÃĨll detta objekt och borttagna {count, plural, one {# objekt} other {# objekt}}", "keyboard_shortcuts": "Kortkommandon", "language": "SprÃĨk", "language_no_results_subtitle": "FÃļrsÃļk att justera ditt sÃļkord", @@ -1259,12 +1375,14 @@ "leave": "Lämna", "leave_album": "Lämna albumet", "lens_model": "Objektiv", - "less": "Mindre", "let_others_respond": "LÃĨt andra svara", "level": "NivÃĨ", "library": "Bibliotek", "library_add_folder": "Lägg till mapp", "library_edit_folder": "Redigera mapp", + "library_options": "NivÃĨ alternativ", + "library_page_device_albums": "Album pÃĨ Enheten", + "library_page_new_album": "Nytt album", "library_page_sort_asset_count": "Antal objekt", "library_page_sort_created": "Senast skapat", "library_page_sort_last_modified": "Senast ändrad", @@ -1274,14 +1392,11 @@ "light_theme": "Ändra till ljust tema", "like": "Gilla", "like_deleted": "Gilla borttagen", - "link": "Länk", "link_motion_video": "Länka rÃļrlig video", "link_to_docs": "FÃļr mer information, se dokumentationen.", "link_to_oauth": "Länk till OAuth", "linked_oauth_account": "Länkat OAuth konto", "list": "Lista", - "live": "Live", - "load_more": "Ladda mer", "loading": "Inläsning", "loading_search_results_failed": "Det gick inte att läsa in sÃļkresultat", "local": "Lokalt", @@ -1300,7 +1415,7 @@ "location_picker_longitude_error": "Ange en giltig longitud", "location_picker_longitude_hint": "Ange din longitud här", "lock": "LÃĨs", - "locked_folder": "LÃĨst mapp", + "locked_folder": "LÃĨst Mapp", "log_detail_title": "Loggdetalj", "log_out": "Logga ut", "log_out_all_devices": "Logga ut alla enheter", @@ -1310,21 +1425,24 @@ "login": "Logga in", "login_disabled": "Inloggning har inaktiverats", "login_form_api_exception": "API-undantag. Kontrollera server-URL:en och fÃļrsÃļk igen.", + "login_form_back_button_text": "BakÃĨt", "login_form_email_hint": "din.email@email.com", "login_form_endpoint_hint": "http://din-server-ip:port", "login_form_endpoint_url": "Slutpunkt-URL fÃļr server", - "login_form_err_invalid_email": "Ogiltig e-postadress", + "login_form_err_http": "Var god ange http:// eller https://", + "login_form_err_invalid_email": "Ogiltig email", "login_form_err_invalid_url": "Ogiltig webbadress", "login_form_err_leading_whitespace": "Mellanrum fÃļre", "login_form_err_trailing_whitespace": "Mellanrum efter", "login_form_failed_get_oauth_server_config": "Kunde inte logga in med OAuth. Kontrollera serverns webbadress", "login_form_failed_get_oauth_server_disable": "OAuth är inte tillgänglig pÃĨ den här servern", - "login_form_failed_login": "Det gick inte att logga in. Kontrollera serverns URL, e-postadress och lÃļsenord", + "login_form_failed_login": "Kunde inte logga in. Kontrollera serverns webbadress, email och lÃļsenord", "login_form_handshake_exception": "Ett Undantag vid Handskakning med servern har skett. Aktivera stÃļd fÃļr självsignerade certifikat i inställningar om du använder ett självsignerat certifikat.", "login_form_password_hint": "lÃļsenord", - "login_form_server_empty": "Ange en server-URL", - "login_form_server_error": "Kunde inte ansluta till servern", - "login_has_been_disabled": "Inloggning har blivit inaktiverat", + "login_form_save_login": "HÃĨll mig inloggad", + "login_form_server_empty": "Ange en server-URL.", + "login_form_server_error": "Kunde inte ansluta till servern.", + "login_has_been_disabled": "Inloggning har blivit inaktiverat.", "login_password_changed_error": "Ett fel uppstod vid uppdatering av ditt lÃļsenord", "login_password_changed_success": "Uppdatering av lÃļsenord lyckades", "logout_all_device_confirmation": "Är du säker pÃĨ att du vill logga ut frÃĨn alla enheter?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "Återställer databasen", "maintenance_description": "Immich har fÃļrsatts i underhÃĨllsläge.", "maintenance_end": "Avsluta underhÃĨllsläge", + "maintenance_end_error": "Misslyckades att avsluta underhÃĨllsläge.", "maintenance_logged_in_as": "FÃļr närvarande inloggad som {user}", "maintenance_restore_from_backup": "Återställ frÃĨn säkerhetskopia", "maintenance_restore_library": "Återställ ditt bibliotek", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "Öppna inställningar", "manage_media_access_subtitle": "TillÃĨt Immich-appen att hantera och flytta mediefiler.", "manage_media_access_title": "Åtkomst till mediehantering", + "manage_shared_links": "Hantera Delade länkar", "manage_sharing_with_partners": "Hantera delning med partner", "manage_the_app_settings": "Hantera appinställningarna", "manage_your_account": "Hantera ditt konto", @@ -1371,11 +1491,13 @@ "manage_your_devices": "Hantera dina inloggade enheter", "manage_your_oauth_connection": "Hantera din OAuth-anslutning", "map": "Karta", + "map_assets_in_bounds": "{count, plural, =0 {Inga foton i detta omrÃĨde} one {# photo} other {# photos}}", "map_cannot_get_user_location": "Kan inte hämta användarens plats", + "map_location_dialog_yes": "Ja", "map_location_picker_page_use_location": "Använd den här platsen", "map_location_service_disabled_content": "Platstjänst mÃĨste vara aktiverad fÃļr att visa objekt frÃĨn din nuvarande plats. Vill du aktivera den nu?", "map_location_service_disabled_title": "Platstjänst inaktiverad", - "map_marker_for_image": "KartmarkÃļr fÃļr bild tagen i {city}, {country}", + "map_marker_for_images": "Kartmarkering fÃļr bilder tagna i {city}, {country}", "map_marker_with_image": "KartmarkÃļr med bild", "map_no_location_permission_content": "Platsrättighet är nÃļdvändigt fÃļr att kunna visa objekt frÃĨn din nuvarande plats. Vill du tillÃĨta det nu?", "map_no_location_permission_title": "Platsrättighet nekad", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Senaste {days} dagarna", "map_settings_date_range_option_year": "Senaste ÃĨret", "map_settings_date_range_option_years": "Senaste {years} ÃĨren", + "map_settings_dialog_title": "Kartinställningar", "map_settings_include_show_archived": "Inkludera Arkiverade", - "map_settings_include_show_partners": "Inkludera partner", + "map_settings_include_show_partners": "Inkludera Partners", "map_settings_only_show_favorites": "Visa Endast Favoriter", "map_settings_theme_settings": "Kart-tema", + "map_zoom_to_see_photos": "Zooma ut fÃļr att se foton", "mark_all_as_read": "Markera alla som lästa", + "mark_as_read": "Markera som läst", "marked_all_as_read": "Markerade alla som lästa", "matches": "Matchar", "matching_assets": "Matchande objekt", - "media_chrome": { - "auto": "Automatisk", - "captions": "Undertexter", - "captions_off": "Av", - "closed_captions": "textning fÃļr hÃļrselskadade", - "decode_error": "Avkodningsfel", - "disable_captions": "Inaktivera undertexter", - "enable_captions": "Aktivera undertexter", - "enter_fullscreen_mode": "GÃĨ in i helskärmsläge", - "exit_fullscreen_mode": "Avsluta helskärmsläge", - "loop": "Slinga", - "media_error_description": "Ett mediefel orsakade att uppspelningen avbrÃļts. Mediet kan vara skadat eller sÃĨ stÃļder inte din webbläsare det här formatet.", - "media_loading": "Media läses in", - "mute": "Tysta", - "network_error": "Nätverksfel", - "network_error_description": "Ett nätverksfel orsakade att medianedladdningen misslyckades.", - "not_supported_error": "Källa stÃļds inte", - "playback_rate": "Uppspelningshastighet", - "playback_rate_current": "nuvarande uppspelningshastighet", - "playback_rate_value": "Uppspelningshastighet {playbackRate}", - "playback_time": "uppspelningstid", - "quality": "Kvalitet", - "second": "sekund", - "seconds": "sekunder", - "time_value_of_total_time": "{currentTime} av {totalTime}", - "time_value_remaining": "{time} ÃĨterstÃĨende", - "unmute": "SlÃĨ pÃĨ ljudet", - "unsupported_error_description": "Ett fel uppstod som inte stÃļds. Servern eller nätverket misslyckades, eller sÃĨ stÃļder inte din webbläsare det här formatet.", - "video_not_loaded_unknown_time": "videon laddades inte, okänd tid.", - "video_player": "videospelare", - "volume": "volym" - }, "media_type": "Mediatyp", "memories": "Minnen", "memories_all_caught_up": "Du är ikapp", @@ -1438,34 +1531,37 @@ "merge": "Sammanfoga", "merge_people": "Sammanfoga personer", "merge_people_limit": "Du kan maximalt sammanfoga 5 ansikten pÃĨ samma gÃĨng", - "merge_people_prompt": "Vill du sammanfoga de här personerna? Åtgärden kan inte ÃĨngras.", - "merge_people_successfully": "Personerna har sammanfogats", - "merged_people_count": "Sammanfogade {count, plural, one {# person} other {# personer}}", - "minFaces": "Minimiantal ansikten", - "minFaces_description": "Minimiantal erkända ansikten fÃļr att en person ska visas", + "merge_people_prompt": "Vill du sammanfoga dessa personer? Denna handling är oÃĨterkallelig.", + "merge_people_successfully": "Personer sammanfogades framgÃĨngsrikt", + "merged_people_count": "Sammanfogat {count, plural, one {# person} other {# people}}", "minimize": "Minimera", "minute": "Minut", "minutes": "Minuter", + "mirror_horizontal": "Horisontell", + "mirror_vertical": "Vertikallt", "missing": "Saknade", "mobile_app": "Mobilapp", "mobile_app_download_onboarding_note": "Ladda ner den medfÃļljande mobilappen med fÃļljande alternativ", "model": "Modell", - "modify_date": "Modifieringsdatum", "month": "MÃĨnad", + "monthly_title_text_date_format": "MMMM y", "more": "Mer", - "motion": "RÃļrelse", "move": "Flytta", + "move_down": "Flytta nedÃĨt", "move_off_locked_folder": "Flytta frÃĨn lÃĨst mapp", "move_to": "Flytta till", "move_to_device_trash": "Flytta till enhetens papperskorg", "move_to_lock_folder_action_prompt": "{count} adderades till lÃĨst mapp", "move_to_locked_folder": "Flytta till lÃĨst mapp", - "move_to_locked_folder_confirmation": "Dessa foton och videor kommer tas bort frÃĨn alla album och gÃĨr endast att se i den lÃĨsta mappen", + "move_to_locked_folder_confirmation": "Dessa foton och videor kommer tas bort frÃĨn alla album och gÃĨr endast se i lÃĨsta mappen", + "move_up": "Flytta uppÃĨt", + "moved_to_archive": "Flyttade {count, plural, one {# resurs} other {# assets}} till arkivet", + "moved_to_library": "\"Flyttade {count, plural, one {# asset} other {# assets}} till biblioteket.\"", "moved_to_trash": "Flyttad till papperskorgen", + "multiselect_grid_edit_date_time_err_read_only": "Kan inte ändra datum pÃĨ skrivskyddade objekt, hoppar Ãļver", + "multiselect_grid_edit_gps_err_read_only": "Kan inte ändra plats pÃĨ skrivskyddade objekt, hoppar Ãļver", "mute_memories": "Tysta minnen", "my_albums": "Mina album", - "my_immich_description": "Kopiera aktuell sida som en Min Immich-länk", - "my_immich_title": "Min Immich-länk", "name": "Namn", "name_or_nickname": "Namn eller smeknamn", "name_required": "Namn krävs", @@ -1480,11 +1576,12 @@ "never": "aldrig", "new_album": "Nytt album", "new_api_key": "Ny API-nyckel", - "new_feature": "Ny funktion", + "new_date_range": "Nytt datumintervall", "new_password": "Nytt lÃļsenord", "new_person": "Ny person", "new_pin_code": "Ny PIN-kod", "new_pin_code_subtitle": "Det här är fÃļrsta gÃĨngen du Ãļppnar den lÃĨsta mappen. Skapa en PIN-kod fÃļr att säkert fÃĨ ÃĨtkomst till den här sidan", + "new_timeline": "Ny tidslinje", "new_update": "Ny uppdatering", "new_user_created": "Ny användare skapad", "new_version_available": "NY VERSION TILLGÄNGLIG", @@ -1492,6 +1589,7 @@ "next": "Nästa", "next_memory": "Nästa minne", "no": "Nej", + "no_actions_added": "Inga ÃĨtgärder tillagda än", "no_albums_found": "Inga album hittades", "no_albums_message": "Skapa ett album fÃļr att organisera dina foton och videor", "no_albums_with_name_yet": "Du verkar inte ha nÃĨgra album med det här namnet ännu.", @@ -1502,48 +1600,44 @@ "no_cast_devices_found": "Inga Cast-enheter hittades", "no_checksum_local": "Ingen kontrollsumma tillgänglig - kan inte hämta lokala objekt", "no_checksum_remote": "Ingen kontrollsumma tillgänglig - kan inte hämta fjärrobjekt", + "no_configuration_needed": "Ingen konfiguration behÃļvs", "no_devices": "Inga auktoriserade enheter", "no_duplicates_found": "Inga dubbletter hittades.", - "no_exif_info_available": "Ingen Exif-information tillgänglig", + "no_exif_info_available": "Exif-information ej tillgänglig", "no_explore_results_message": "Ladda upp fler bilder fÃļr att utforska din samling.", "no_favorites_message": "Lägg till favoriter fÃļr att snabbt hitta dina bästa bilder och videor", + "no_filters_added": "Inga filter tillagda än", "no_libraries_message": "Skapa ett externt bibliotek fÃļr att se dina bilder och videor", "no_local_assets_found": "Inga lokala objekt hittades med denna kontrollsumma", + "no_location_set": "Ingen plats satt", "no_locked_photos_message": "Foton och videor i den lÃĨsta mappen är dolda och visas inte när du bläddrar eller sÃļker i ditt bibliotek.", "no_name": "Inget namn", "no_notifications": "Inga aviseringar", - "no_people_found": "Inga matchande personer hittades", + "no_people_found": "Inga matchande personer hittade", "no_places": "Inga platser", "no_remote_assets_found": "Inga fjärrobjekt hittades med denna kontrollsumma", "no_results": "Inga resultat", "no_results_description": "PrÃļva en synonym eller ett annat mer allmänt sÃļkord", "no_shared_albums_message": "Skapa ett album fÃļr att dela bilder och videor med andra personer", - "no_steps": "Inga steg har lagts till ännu", + "no_uploads_in_progress": "Inga uppladdningar pÃĨgÃĨr", "none": "Inga", "not_allowed": "Inte tillÃĨten", "not_available": "N/A", "not_in_any_album": "Inte i nÃĨgot album", - "not_selected": "Inte vald", - "not_set": "Inte angiven", + "not_selected": "Ej vald", "notes": "Notera", "nothing_here_yet": "Inget här ännu", - "notification_backup_reliability": "Aktivera aviseringar fÃļr att fÃļrbättra säkerhetskopieringen i bakgrunden", - "notification_enabled_list_tile_content": "Immich använder aviseringar fÃļr säkerhetskopiering i bakgrunden. Hantera dem i enhetens inställningar.", - "notification_enabled_list_tile_open_button": "Öppna inställningar", - "notification_enabled_list_tile_title": "Aviseringar aktiverade", - "notification_permission_dialog_content": "GÃĨ till Inställningar och tillÃĨt aviseringar fÃļr att aktivera dem.", - "notification_permission_list_tile_content": "Ge behÃļrighet att visa aviseringar.", - "notification_permission_list_tile_enable_button": "Aktivera aviseringar", - "notification_permission_list_tile_title": "BehÃļrighet fÃļr aviseringar", + "notification_permission_dialog_content": "FÃļr att aktivera notiser, gÃĨ till Inställningar och välj tillÃĨt.", + "notification_permission_list_tile_content": "TillÃĨt rättighet fÃļr att slÃĨ pÃĨ notiser.", + "notification_permission_list_tile_enable_button": "Aktivera Notiser", + "notification_permission_list_tile_title": "Notisrättighet", "notification_toggle_setting_description": "Aktivera e-postaviseringar", - "notifications": "Aviseringar", + "notifications": "Notifikationer", "notifications_setting_description": "Hantera aviseringar", "oauth": "OAuth", "obtainium_configurator": "Obtainium-konfigurator", "obtainium_configurator_instructions": "Använd Obtainium fÃļr att installera och uppdatera Android-appen direkt frÃĨn Immichs GitHub-version. Skapa en API-nyckel och välj en variant fÃļr att skapa din Obtainium-konfigurationslänk", "ocr": "OCR", - "ocr_body": "Immich läser nu text inuti dina foton, sÃĨ att du kan sÃļka efter dem utifrÃĨn vad de säger.", - "ocr_title": "SÃļk text i dina foton", "official_immich_resources": "Officiella Immich-resurser", "offline": "FrÃĨnkopplad", "offset": "FÃļrskjutning", @@ -1562,20 +1656,18 @@ "open": "Öppen", "open_calendar": "Öppna kalender", "open_in_browser": "Öppna i webbläsare", - "open_in_immich_body": "Ställ in Immich som ditt galleri pÃĨ Android fÃļr att Ãļppna foton direkt frÃĨn andra appar.", - "open_in_immich_title": "Öppna foton i Immich", "open_in_map_view": "Öppna kartvy", - "open_in_openstreetmap": "Öppna i OpenStreetMap", + "open_in_openstreetmap": "Öppna med OpenStreetMap", "open_the_search_filters": "Öppna sÃļkfilter", "options": "Val", "or": "eller", "organize_into_albums": "Organisera i album", "organize_into_albums_description": "Lägg befintliga foton i album med aktuella synkroniseringsinställningar", "organize_your_library": "Organisera ditt bibliotek", - "orientation": "Orientering", "original": "original", "other": "Övrigt", "other_devices": "Andra enheter", + "other_entities": "Andra enheter", "other_variables": "Andra variabler", "owned": "Ägd", "owner": "Ägare", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "Alla dina foton och videoklipp fÃļrutom de i Arkiverade och Raderade", "partner_can_access_location": "Platsen där dina foton togs", "partner_list_user_photos": "{user}s foton", + "partner_list_view_all": "Visa alla", "partner_page_empty_message": "Dina foton delas inte med nÃĨgon partner ännu.", "partner_page_no_more_users": "Inga fler användare att lägga till", + "partner_page_partner_add_failed": "Misslyckades med att lägga till partner", "partner_page_select_partner": "Välj partner", + "partner_page_shared_to_title": "Delad till", "partner_page_stop_sharing_content": "{partner} kommer inte längre att komma ÃĨt dina foton.", "partner_sharing": "Partnerdelning", "partners": "Partners", @@ -1596,39 +1691,52 @@ "password_required": "LÃļsenord krävs", "password_reset_success": "LÃļsenord ÃĨterställt", "past_durations": { - "days": "Senaste {days, plural, one {dagen} other {# dagarna}}", - "hours": "Senaste {hours, plural, one {timmen} other {# timmarna}}", - "years": "Senaste {years, plural, one {ÃĨret} other {# ÃĨren}}" + "days": "Senaste {days, plural, one {day} other {# days}}", + "hours": "Senaste {hours, plural, one {hour} other {# hours}}", + "years": "Senaste {years, plural, one {year} other {# years}}" }, "path": "SÃļkväg", "pattern": "MÃļnster", - "pause": "Pausa", + "pause": "Pause", "pause_memories": "Pausa minnen", "paused": "Pausad", "pending": "Väntande", "people": "Personer", - "people_edits_count": "Redigerade {count, plural, one {# person} other {# personer}}", + "people_edits_count": "Redigerad {count, plural, one {# person} other {# personer}}", "people_feature_description": "Visar foton och videor grupperade efter personer", + "people_selected": "{count, plural, one {# person vald} other {# personer valda}}", + "people_sidebar_description": "Visa en länk till Personer i sidopanelen", "permanent_deletion_warning": "Varning om permanent radering", "permanent_deletion_warning_setting_description": "Visa en varning när objekt raderas permanent", "permanently_delete": "Radera permanent", - "permanently_delete_assets_count": "Ta bort {count, plural, one {objektet} other {objekten}} permanent", - "permanently_delete_assets_prompt": "Är du säker pÃĨ att du vill ta bort {count, plural, one {det här objektet permanent?} other {de här # objekten permanent?}} DÃĨ tas {count, plural, one {det även bort frÃĨn sitt} other {de även bort frÃĨn sina}} album.", + "permanently_delete_assets_count": "Radera {count, plural, one {asset} other {assets}} permanent", + "permanently_delete_assets_prompt": "Är du säker pÃĨ att du permanent vill ta bort {count, plural, one {denna fil?} other{these # filer?}} Detta kommer ocksÃĨ ta bort {count, plural, one {dem frÃĨn } other{them from their}} album.", "permanently_deleted_asset": "Permanent raderat objekt", - "permanently_deleted_assets_count": "Tog bort {count, plural, one {# objekt} other {# objekt}} permanent", + "permanently_deleted_assets_count": "Permanent borttagning av {count, plural, one {# asset} other {# assets}}", "permission": "BehÃļrigheter", "permission_empty": "Dina behÃļrighet kan inte vara blankt", - "person": "Person", + "permission_onboarding_back": "BakÃĨt", + "permission_onboarding_continue_anyway": "Fortsätt ändÃĨ", + "permission_onboarding_get_started": "Kom igÃĨng", + "permission_onboarding_go_to_settings": "GÃĨ till inställningar", + "permission_onboarding_permission_denied": "Rättighet nekad. FÃļr att använda Immich, tillÃĨt foto- och video-rättigheter i Inställningar.", + "permission_onboarding_permission_granted": "Rättigheten beviljad! Du är klar.", + "permission_onboarding_permission_limited": "Rättighet begränsad. FÃļr att lÃĨta Immich säkerhetskopiera och hantera hela ditt galleri, tillÃĨt foto- och video-rättigheter i Inställningar.", + "permission_onboarding_request": "Immich kräver tillstÃĨnd fÃļr att se dina foton och videor.", + "person": "Individ", "person_age_months": "{months, plural, one {# mÃĨnad} other {# mÃĨnader}} gammal", "person_age_year_months": "1 ÃĨr, {months, plural, one {# mÃĨnad} other {# mÃĨnader}} gammal", - "person_age_years": "{years, plural, one {# ÃĨr gammal} other {# ÃĨr gammal}}", + "person_age_years": "{years, plural, other {# ÃĨr}} gammal", "person_birthdate": "FÃļdd {date}", "person_hidden": "{name}{hidden, select, true { (dold)} other {}}", "person_recognized": "Person igenkänd", + "person_selected": "Person vald", "photo_shared_all_users": "Du har antingen delat dina foton med alla användare eller sÃĨ har du inga användare att dela dem med.", "photos": "Foton", "photos_and_videos": "Foton & videor", + "photos_count": "{count, plural, one {{count, number} Foto} other {{count, number} Foton}}", "photos_from_previous_years": "Foton frÃĨn tidigare ÃĨr", + "photos_only": "Foton endast", "pick_a_location": "Välj en plats", "pick_custom_range": "Anpassat intervall", "pick_date_range": "Välj ett datumintervall", @@ -1644,11 +1752,9 @@ "play_motion_photo": "Spela upp rÃļrligt foto", "play_or_pause_video": "Spela upp eller pausa video", "play_original_video": "Spela upp originalvideo", - "play_original_video_setting_description": "FÃļredra uppspelning av originalvideor framfÃļr omkodade videor. Om originalobjektet inte är kompatibelt kanske den inte spelas upp korrekt.", + "play_original_video_setting_description": "FÃļredra uppspelning av originalvideor framfÃļr omkodade videor. Om originalresursen inte är kompatibel kanske den inte spelas upp korrekt.", "play_transcoded_video": "Spela upp omkodad video", "please_auth_to_access": "Vänligen autentisera fÃļr att fÃĨ ÃĨtkomst", - "plugin_method_filter_type": "Filter", - "plugin_method_filter_type_description": "Denna metod kan filtrera händelser och villkorligt fÃļrhindra att efterfÃļljande steg kÃļrs", "port": "Port", "preferences_settings_subtitle": "Hantera appens inställningar", "preferences_settings_title": "Inställningar", @@ -1665,11 +1771,11 @@ "privacy": "Sekretess", "profile": "Profil", "profile_drawer_app_logs": "Loggar", + "profile_drawer_client_server_up_to_date": "Klient och server är uppdaterade", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Skrivskyddat läge aktiverat. LÃĨngtryck användaravatarikonen fÃļr att avsluta.", "profile_image_of_user": "{user} profilbild", "profile_picture_set": "Profilbild vald.", - "projection_type": "Projektionstyp", "public_album": "Publikt album", "public_share": "Offentlig delning", "purchase_account_info": "Anhängare", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Supporterstatus", "purchase_individual_title": "Individuell", "purchase_input_suggestion": "Har du en produktnyckel? Ange nyckeln nedan", + "purchase_license_subtitle": "KÃļp Immich fÃļr att stÃļdja den fortsatta utvecklingen av tjänsten", "purchase_lifetime_description": "LivstidskÃļp", "purchase_option_title": "KÖPALTERNATIV", "purchase_panel_info_1": "Att bygga Immich kräver mycket tid och engagemang och vÃĨra tekniker jobbar heltid fÃļr att gÃļra det sÃĨ bra som vi mÃļjligt kan. VÃĨrt mÃĨl är att open source-mjukvara och etiska affärsmetoder ska bli en hÃĨllbar inkomstkälla fÃļr utvecklare och att skapa ett ekosystem som repekterar personlig integritet med verkliga alternativ till exploaterande molntjänster.", @@ -1708,44 +1815,44 @@ "rate_asset": "Betygsätt materialet", "rating": "Antal stjärnor", "rating_clear": "Ta bort betyg", - "rating_count": "{count, plural, =0 {Inte betygsatt} one {# stjärna} other {# stjärnor}}", - "rating_description": "Visa Exif-betyget i informationspanelen", + "rating_count": "{count, plural, =0 {Ej betygsatt} one {# stjärna} other {# stjärnor}}", + "rating_description": "Visa EXIF betyget i informationspanelen", "reaction_options": "Alternativ fÃļr reaktion", "read_changelog": "Läs ändringslogg", "readonly_mode_disabled": "Skrivskyddat läge inaktiverat", "readonly_mode_enabled": "Skrivskyddat läge aktiverat", "ready_for_upload": "Redo fÃļr uppladdning", "reassign": "OmfÃļrdela", - "reassigned_assets_to_existing_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till {name, select, null {en befintlig person} other {{name}}}", - "reassigned_assets_to_new_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till en ny person", + "reassigned_assets_to_existing_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till {name, select, null {an existing person} other {{name}}}", + "reassigned_assets_to_new_person": "Tilldelade om {count, plural, one {# objekt} other {# objekt}} till en ny persson", "reassing_hint": "Tilldela valda objekt till en befintlig person", "recent": "Nyligen", + "recent_albums": "Senaste album", "recent_searches": "Senaste sÃļkningar", "recently_added": "Nyligen tillagda", - "recently_added_body": "Hoppa direkt till allt du har lagt till nyligen pÃĨ en dedikerad sida.", - "recently_added_description": "Bläddra bland dina objekt sorterade efter när de laddades upp till Immich", "recently_added_page_title": "Nyligen tillagda", - "recently_added_title": "Nyligen tillagda", "recently_taken": "Nyligen tagna", + "recently_taken_page_title": "Nyligen Tagna", "refresh": "Ladda om", "refresh_encoded_videos": "Ladda om kodade videor", "refresh_faces": "Ladda om ansikten", "refresh_metadata": "Ladda om metadata", "refresh_thumbnails": "Uppdatera miniatyrer", "refreshed": "Omladdad", + "refreshes_every_file": "Läser in alla existerande och nya filer pÃĨ nytt", "refreshing_encoded_video": "Återladdar kodad video", "refreshing_faces": "Återladdar ansikten", "refreshing_metadata": "Återladdar metadata", "regenerating_thumbnails": "Uppdaterar miniatyrer", - "remote": "Fjärr", + "remote": "Fjärrr", "remote_assets": "Fjärrobjekt", "remote_media_summary": "Sammanfattning av fjärrmedia", "remove": "Ta bort", - "remove_assets_album_confirmation": "Är du säker pÃĨ att du vill ta bort {count, plural, one {# objekt} other {# objekt}} frÃĨn albumet?", - "remove_assets_shared_link_confirmation": "Är du säker pÃĨ att du vill ta bort {count, plural, one {# objekt} other {# objekt}} frÃĨn den här delade länken?", + "remove_assets_album_confirmation": "Är du säker pÃĨ att du vill ta bort {count, plural, one {# asset} other {# assets}} frÃĨn albumet?", + "remove_assets_shared_link_confirmation": "Är du säker pÃĨ att du vill ta bort {count, plural, one {# asset} other {# assets}} frÃĨn denna delade länk?", "remove_assets_title": "Ta bort filer?", "remove_custom_date_range": "Ta bort anpassat datumintervall", - "remove_filter": "Ta bort filter", + "remove_deleted_assets": "Ta bort borttagna objekt", "remove_from_album": "Ta bort frÃĨn album", "remove_from_album_action_prompt": "{count} borttaget frÃĨn albumet", "remove_from_favorites": "Ta bort frÃĨn favoriter", @@ -1756,22 +1863,29 @@ "remove_memory": "Ta bort minne", "remove_photo_from_memory": "Ta bort fotot frÃĨn detta minnet", "remove_tag": "Ta bort taggen", + "remove_url": "Ta bort URL", "remove_user": "Ta bort användare", - "removed_api_key": "Tog bort API-nyckeln: {name}", + "removed_api_key": "Tog bort API nyckel: {name}", "removed_from_archive": "Borttagen frÃĨn arkivet", "removed_from_favorites": "Borttagen frÃĨn favoriter", "removed_from_favorites_count": "{count, plural, other {Tog bort #}} frÃĨn favoriter", "removed_memory": "Tog bort minne", + "removed_photo_from_memory": "Tog bort foto frÃĨn minnet", "removed_tagged_assets": "Tog bort tagg frÃĨn {count, plural, one {# objekt} other {# objekt}}", "rename": "DÃļp om", + "repair": "Reparera", + "repair_no_results_message": "OspÃĨrade och saknade filer kommer att dyka upp här", + "replace_with_upload": "Ersätt med uppladdning", "repository": "FÃļrvar", "require_password": "Kräver lÃļsenord", + "require_user_to_change_password_on_first_login": "Kräv att användaren ändrar lÃļsenord vid fÃļrsta inloggning", "rescan": "Skanna igen", "reset": "Återställ", "reset_password": "Nollställ lÃļsenord", "reset_people_visibility": "Återställ personers synlighet", "reset_pin_code": "Återställ PIN-kod", "reset_pin_code_description": "Om du har glÃļmt din PIN-kod kan du kontakta serveradministratÃļren fÃļr att ÃĨterställa den", + "reset_pin_code_success": "PIN-koden har ÃĨterställts", "reset_pin_code_with_password": "Du kan alltid ÃĨterställa din PIN-kod med ditt lÃļsenord", "reset_sqlite": "Återställ SQLite-databasen", "reset_sqlite_clear_app_data": "Rensa data", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "LÃļs alla dubletter", "restore": "Återställ", "restore_all": "Återställ alla", + "restore_trash_action_prompt": "{count} ÃĨterställd frÃĨn papperskorgen", "restore_user": "Återställ användare", "restored_asset": "Återställ objekt", "resume": "Återuppta", "resume_paused_jobs": "Återuppta {count, plural, one {# pausat jobb} other {# pausade jobb}}", + "retry_upload": "Ladda upp igen", "review_duplicates": "Granska dubbletter", "review_large_files": "Granska stora filer", "role": "Roll", @@ -1796,6 +1912,7 @@ "role_viewer": "Visare", "running": "IgÃĨngsatt", "save": "Spara", + "save_to_gallery": "Spara i galleri", "saved": "Sparad", "saved_api_key": "Sparad API-nyckel", "saved_profile": "Sparade profil", @@ -1806,10 +1923,9 @@ "scan": "Skanna", "scan_all_libraries": "Skanna alla bibliotek", "scan_library": "Skanna", + "scan_settings": "Skanningsinställningar", "scanning": "Skannar", "scanning_for_album": "SÃļker efter album...", - "screencast_mode_description": "Visa tangentbord och mushändelseindikatorer pÃĨ skärmen", - "screencast_mode_title": "Växla skärmsändningsläge", "search": "SÃļk", "search_albums": "SÃļk album", "search_by_context": "SÃļk efter sammanhang", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Vandringsdag i Sapa", "search_by_filename": "SÃļk efter filnamn eller filändelse", "search_by_filename_example": "t.ex. IMG_1234.JPG eller PNG", - "search_by_full_path": "SÃļk efter fullständig sÃļkväg eller mapp", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - du kan sÃļka efter Projects, 3D, Printing, 2026 etc.", "search_by_ocr": "SÃļk text i bild", "search_by_ocr_example": "Latte", "search_camera_lens_model": "SÃļk kameraobjektiv...", @@ -1831,13 +1945,14 @@ "search_filter_date": "Datum", "search_filter_date_interval": "{start} till {end}", "search_filter_date_title": "Välj datumintervall", - "search_filter_display_option_not_in_album": "Inte i nÃĨgot album", + "search_filter_display_option_not_in_album": "Ej i album", "search_filter_display_options": "Visningsalternativ", "search_filter_filename": "SÃļk pÃĨ filnamn", "search_filter_location": "Plats", "search_filter_location_title": "Välj plats", "search_filter_media_type": "Mediatyp", "search_filter_media_type_title": "Välj mediatyp", + "search_filter_ocr": "SÃļk efter OCR", "search_filter_people_title": "Välj personer", "search_filter_star_rating": "Stjärnbetyg", "search_filter_tags_title": "Välj taggar", @@ -1848,13 +1963,25 @@ "search_no_people_named": "Inga personer med namnet \"{name}\"", "search_no_result": "Inga resultat, fÃļrsÃļk med annan sÃļkterm eller kombination", "search_options": "SÃļkinställningar", + "search_page_categories": "Kategorier", + "search_page_motion_photos": "RÃļrelsefoton", + "search_page_no_objects": "Inga objekt är tillgängliga", + "search_page_no_places": "Ingen platsinformation finns tillgänglig", + "search_page_screenshots": "Skärmdumpar", "search_page_search_photos_videos": "SÃļk efter dina foton och videor", + "search_page_selfies": "Självporträtt", + "search_page_things": "Objekt", "search_page_view_all_button": "Visa alla", + "search_page_your_activity": "Dina aktiviteter", + "search_page_your_map": "Din Karta", "search_people": "SÃļk personer", "search_places": "SÃļk platser", "search_rating": "SÃļk efter betyg...", + "search_result_page_new_search_hint": "Ny sÃļkning", "search_settings": "SÃļk inställningar", "search_state": "SÃļk stat...", + "search_suggestion_list_smart_search_hint_1": "SmartsÃļk är aktiverat som standard, fÃļr att sÃļka efter metadata, använd syntaxen ", + "search_suggestion_list_smart_search_hint_2": "m:ditt-sÃļkord", "search_tags": "SÃļk taggar...", "search_timezone": "SÃļk tidszon...", "search_type": "SÃļktyp", @@ -1865,9 +1992,10 @@ "select": "Välj", "select_album": "Välj album", "select_album_cover": "Välj albumomslag", - "select_albums": "Välj album", + "select_albums": "Välj albums", "select_all": "Välj alla", "select_all_duplicates": "Välj alla dubletter", + "select_all_in": "Markera alla i {group}", "select_avatar_color": "Välj färg fÃļr avatar", "select_count": "{count, plural, one {Välj #} other {Välj #}}", "select_cutoff_date": "Välj slutdatum", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Välj utvald bild", "select_from_computer": "Välj frÃĨn datorn", "select_keep_all": "Spara alla", + "select_library_owner": "Välj biblioteksägare", "select_new_face": "Välj nytt ansikte", "select_people": "Välj personer", "select_person": "Välj person", "select_person_to_tag": "Välj en person att tagga", "select_photos": "Välj foton", - "select_quality": "Välj kvalitet", "select_trash_all": "Släng alla", + "select_user_for_sharing_page_err_album": "Kunde inte skapa nytt album", "selected": "Valda", "selected_count": "{count, plural, other {# valda}}", "selected_gps_coordinates": "Valda GPS-koordinater", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Ladda originalbilden", "setting_image_viewer_preview_subtitle": "Aktivera fÃļr att ladda en mellanstor bild. Stäng av fÃļr att antingen ladda originalet direkt eller bara använda miniatyrbilden.", "setting_image_viewer_preview_title": "Ladda fÃļrhandsgranskning av bild", + "setting_image_viewer_title": "Bilder", "setting_languages_apply": "Verkställ", "setting_languages_subtitle": "Ändra appens sprÃĨk", + "setting_notifications_notify_failures_grace_period": "Rapportera säkerhetskopieringsfel i bakgrunden: {duration}", + "setting_notifications_notify_hours": "{count} timmar", + "setting_notifications_notify_immediately": "omedelbart", "setting_notifications_notify_minutes": "{count} minuter", + "setting_notifications_notify_never": "aldrig", "setting_notifications_notify_seconds": "{count} sekunder", - "setting_notifications_subtitle": "Anpassa aviseringsinställningarna", + "setting_notifications_single_progress_subtitle": "Detaljerad uppladdningsstatus per bild och video", + "setting_notifications_single_progress_title": "Visa detaljerat uppladdningsfÃļrlopp", + "setting_notifications_subtitle": "Anpassa dina notis-inställningar", + "setting_notifications_total_progress_subtitle": "Övergripande uppladdningsfÃļrlopp (klar/totala objekt)", + "setting_notifications_total_progress_title": "Visa totalt uppladdningsfÃļrlopp", "setting_video_viewer_auto_play_subtitle": "BÃļrja automatiskt spela upp videor när de Ãļppnas", "setting_video_viewer_auto_play_title": "Automatisk uppspelning av video", "setting_video_viewer_looping_title": "Loopar", "setting_video_viewer_original_video_subtitle": "Spela originalet när en video strÃļmmas frÃĨn servern, även när en transkodad version är tillgänglig. Kan leda till buffring. Videor som är tillgängliga lokalt spelas i originalkvalitet oavsett denna inställning.", "setting_video_viewer_original_video_title": "Tvinga orginalvideo", "settings": "Inställningar", + "settings_require_restart": "Starta om Immich fÃļr att tillämpa den här inställningen", "settings_saved": "Inställningar sparade", - "setup_pin_code": "Konfigurera PIN-kod", + "setup_pin_code": "Konfigurera pinkod", "share": "Dela", + "share_action_prompt": "Delade {count} objekt", + "share_add_photos": "Lägg till foton", + "share_assets_selected": "{count} valda", "share_dialog_preparing": "FÃļrbereder...", "share_link": "Dela Länk", - "share_original": "Använd original (stor)", - "share_preview": "Använd miniatyrbild (liten)", - "share_quality_body": "Tryck och hÃĨll ned delningsknappen fÃļr att välja bildkvalitet innan du delar.", - "share_quality_title": "Välj din delningskvalitet", "shared": "Delad", "shared_album_activities_input_disable": "Kommentar är inaktiverad", "shared_album_activity_remove_content": "Vill du ta bort den här aktiviteten?", "shared_album_activity_remove_title": "Ta Bort Aktivitet", "shared_album_section_people_action_error": "Fel vid lämnande/borttagning frÃĨn album", + "shared_album_section_people_action_leave": "Ta bort användare frÃĨn album", + "shared_album_section_people_action_remove_user": "Ta bort användare frÃĨn album", "shared_album_section_people_title": "PERSONER", "shared_by": "Delad av", "shared_by_user": "Delad av {user}", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} Uppladdat", "shared_link_app_bar_title": "Delade Länkar", "shared_link_clipboard_copied_massage": "Kopierad till urklipp", + "shared_link_clipboard_text": "Länk: {link}\nLÃļsenord: {password}", "shared_link_create_error": "Fel vid skapande av delad länk", - "shared_link_custom_url_description": "FÃĨ ÃĨtkomst till den här delade länken med ett anpassat URL namn", - "shared_link_custom_url_title": "anpassad URL", - "shared_link_custom_url_warning": "Varning: ett anpassat URL-namn med ett snedstreck kanske inte fungerar som fÃļrväntat.", + "shared_link_custom_url_description": "FÃĨ ÃĨtkomst till den här delade länken med en anpassad URL", "shared_link_edit_description_hint": "Lägg till delnings-beskrivningen", "shared_link_edit_expire_after_option_day": "1 dag", "shared_link_edit_expire_after_option_days": "{count} dagar", @@ -1984,6 +2123,12 @@ "shared_with_partner": "Delad med {partner}", "sharing": "Delning", "sharing_enter_password": "Ange lÃļsenord fÃļr att visa denna sidan.", + "sharing_page_album": "Delade album", + "sharing_page_description": "Skapa delade album fÃļr att dela foton och video med personer i ditt nätverk.", + "sharing_page_empty_list": "TOM LISTA", + "sharing_sidebar_description": "Visa en länk till Delning i sidopanelen", + "sharing_silver_appbar_create_shared_album": "Skapa delat album", + "sharing_silver_appbar_share_partner": "Dela med partner", "shift_to_permanent_delete": "tryck pÃĨ ⇧ fÃļr att permanent radera objektet", "show_album_options": "Visa albumalternativ", "show_albums": "Visa album", @@ -1993,11 +2138,9 @@ "show_gallery": "Visa galleri", "show_hidden_people": "Visa gÃļmda personer", "show_in_timeline": "Visa pÃĨ tidslinje", - "show_in_timeline_setting_description": "Visa foton och videor frÃĨn den här användaren pÃĨ din tidslinje", + "show_in_timeline_setting_description": "Visa foton och videor frÃĨn denna användaren pÃĨ din tidslinje", "show_keyboard_shortcuts": "Visa kortkommandon", - "show_less": "Visa mindre", "show_metadata": "Visa metadata", - "show_more_fields": "{count, plural, one {Visa ytterligare # fält} other {Visa ytterligare # fält}}", "show_or_hide_info": "Visa eller gÃļm information", "show_password": "Visa lÃļsenord", "show_person_options": "Visa alternativ fÃļr person", @@ -2005,7 +2148,6 @@ "show_schema": "Visa schema", "show_search_options": "Visa sÃļkalternativ", "show_shared_links": "Visa delade länkar", - "show_slideshow_metadata_overlay": "Visa bildinformationsÃļverlägg", "show_slideshow_transition": "Visa bildspelsÃļvergÃĨng", "show_supporter_badge": "Supporteremblem", "show_supporter_badge_description": "Visa supporteremblem", @@ -2017,25 +2159,18 @@ "sign_out": "Logga ut", "sign_up": "Registrera dig", "size": "Storlek", - "skip": "Hoppa Ãļver", "skip_to_content": "Hoppa till innehÃĨll", "skip_to_folders": "Hoppa till mapp", "skip_to_tags": "Hoppa till taggar", "slideshow": "Bildspel", - "slideshow_body": "Luta dig tillbaka och titta pÃĨ dina foton som spelas upp i ett helskärms bildspel.", - "slideshow_metadata_overlay_mode": "ÖverläggsinnehÃĨll", - "slideshow_metadata_overlay_mode_description_only": "Endast beskrivning", - "slideshow_metadata_overlay_mode_full": "Fullständig", "slideshow_repeat": "Upprepa bildspel", "slideshow_repeat_description": "GÃĨ tillbaka till bÃļrjan när bildspelet slutar", "slideshow_settings": "Bildspelsinställningar", - "slideshow_title": "Bildspel", - "smart_album": "Smart album", - "some_assets_already_have_a_location_warning": "Vissa av de valda objekten har redan platsdata", "sort_albums_by": "Sortera album efter...", "sort_created": "Skapat datum", "sort_items": "Antal artiklar", "sort_modified": "Datum ändrat", + "sort_newest": "Nyaste foto", "sort_oldest": "Äldsta foto", "sort_people_by_similarity": "Sortera människor efter likhet", "sort_recent": "Senaste fotot", @@ -2044,25 +2179,22 @@ "stack": "Stapel", "stack_action_prompt": "{count} staplade", "stack_duplicates": "Stapla dubletter", + "stack_select_one_photo": "Välj ett huvudfoto fÃļr stapeln", "stack_selected_photos": "Stapla valda foton", - "stacked_assets_count": "Staplade {count, plural, one {# objekt} other {# objekt}}", + "stacked_assets_count": "Staplade {count, plural, one {# asset} other {# assets}}", "stacktrace": "StapelspÃĨrning", "start": "Starta", "start_date": "Startdatum", "start_date_before_end_date": "Startdatumet mÃĨste vara fÃļre slutdatumet", "state": "Stat", "status": "Status", - "step_delete": "Ta bort steg", - "step_delete_confirm": "Är du säker pÃĨ att du vill ta bort det här steget?", - "step_details": "Stegdetaljer", - "steps": "Steg", "stop_casting": "Sluta casta", "stop_motion_photo": "Stanna rÃļrligt foto", "stop_photo_sharing": "Sluta dela dina foton?", - "stop_photo_sharing_description": "{partner} kommer inte längre att ha tillgÃĨng till dina foton.", - "stop_sharing_photos_with_user": "Sluta dela dina bilder med den här användaren", + "stop_photo_sharing_description": "{partner} kommer inte länga ha tillgÃĨng till dina foton.", + "stop_sharing_photos_with_user": "Sluta dela dina bilder med denna användaren", "storage": "Lagring", - "storage_label": "Lagringsetikett", + "storage_label": "FÃļrvaringsetikett", "storage_quota": "Lagringskvot", "storage_usage": "{used} av {available} används", "submit": "Skicka", @@ -2070,25 +2202,25 @@ "suggestions": "FÃļrslag", "sunrise_on_the_beach": "Exempel: SoluppgÃĨng pÃĨ stranden", "support": "Support", - "support_and_feedback": "Support och ÃĨterkoppling", + "support_and_feedback": "Support och Feedback", "support_third_party_description": "Din Immich-installation paketerades av en tredje part. Problem som du upplever kan orsakas av det paketet, sÃĨ vänligen ta upp problem med dem i fÃļrsta hand med hjälp av länkarna nedan.", "supporter": "StÃļdjare", "swap_merge_direction": "Byt sammanfogningsriktning", "sync": "Synka", "sync_albums": "Synka album", + "sync_albums_manual_subtitle": "Synka alla uppladdade videor och foton till valda backup-album", "sync_local": "Synkronisera lokalt", "sync_remote": "Synkronisera fjärrserver", - "sync_status": "Synkstatus", + "sync_status": "Synk Status", "sync_status_subtitle": "Visa och hantera synkroniseringssystemet", "sync_upload_album_setting_subtitle": "Skapa och ladda upp dina foton och videor till de valda albumen pÃĨ Immich", - "system_theme": "Systemtema", - "system_theme_command_description": "Använd systemtemat {value}", "tag": "Tagg", "tag_assets": "Tagga objekt", "tag_created": "Skapade tagg: {tag}", "tag_face": "Tagga ansikte", "tag_feature_description": "Bläddra bland foton och videor grupperade efter logiska taggar", - "tag_people": "Tagga personer", + "tag_not_found_question": "Kan du inte hitta en tagg? Skapa en ny tagg.", + "tag_people": "Tagga Personer", "tag_updated": "Uppdaterade tagg: {tag}", "tagged_assets": "Taggade {count, plural, one {# objekt} other {# objekt}}", "tags": "Taggar", @@ -2100,12 +2232,17 @@ "theme_selection_description": "Ställ in temat automatiskt till ljust eller mÃļrkt baserat pÃĨ din webbläsares inställningar", "theme_setting_asset_list_storage_indicator_title": "Visa lagringsindikator pÃĨ filer", "theme_setting_asset_list_tiles_per_row_title": "Antal bilder och videor per rad ({count})", - "theme_setting_colorful_interface_subtitle": "Tillämpa primärfärgen pÃĨ bakgrundsytor.", + "theme_setting_colorful_interface_subtitle": "Applicera primärfärgen pÃĨ bakgrundsytor.", "theme_setting_colorful_interface_title": "Färgglatt gränssnitt", + "theme_setting_image_viewer_quality_subtitle": "Justera kvaliteten i bildvisaren", + "theme_setting_image_viewer_quality_title": "Bildvisarens kvalitet", "theme_setting_primary_color_subtitle": "Välj en färg fÃļr primära ÃĨtgärder och accenter.", "theme_setting_primary_color_title": "Primärfärg", "theme_setting_system_primary_color_title": "Använd systemfärg", "theme_setting_system_theme_switch": "Automatisk (FÃļlj systeminställningar)", + "theme_setting_theme_subtitle": "Välj inställning fÃļr appens tema", + "theme_setting_three_stage_loading_subtitle": "Trestegsladdning kan Ãļka prestandan, men kan ocksÃĨ leda till signifikant hÃļgre nätverksbelastning", + "theme_setting_three_stage_loading_title": "Aktivera trestegsladdning", "then": "Sedan", "they_will_be_merged_together": "De kommer att slÃĨs samman", "third_party_resources": "Tredjepartsresurser", @@ -2131,25 +2268,31 @@ "trash_all": "Kasta alla", "trash_count": "Papperskorg {count, number}", "trash_delete_asset": "Kassera/Ta bort objekt", + "trash_emptied": "TÃļmd papperskorg", "trash_no_results_message": "Borttagna foton och videor kommer att visas här.", "trash_page_delete_all": "Ta Bort Alla", + "trash_page_empty_trash_dialog_content": "Vill du ta bort dina slängda objekt? De kommer att tas bort permanent frÃĨn Immich", "trash_page_info": "Objekt i papperskorgen tas bort permanent efter {days} dagar", + "trash_page_no_assets": "Inga slängda objekt", + "trash_page_restore_all": "Återställ Alla", + "trash_page_select_assets_btn": "Välj objekt", + "trash_page_title": "Papperskorg ({count})", "trashed_items_will_be_permanently_deleted_after": "Objekt i papperskorgen raderas permanent efter {days, plural, one {# dag} other {# dagar}}.", "trigger": "UtlÃļsare", - "trigger_asset_metadata_extraction": "Hämtning av objektmetadata", - "trigger_asset_metadata_extraction_description": "Aktiveras när EXIF-metadata hämtas frÃĨn ett objekt", - "trigger_asset_uploaded": "Objekt uppladdning", + "trigger_asset_uploaded": "Objekt uppladdat", "trigger_asset_uploaded_description": "UtlÃļses när ett nytt objekt laddas upp", + "trigger_description": "Ett evenemang som sätter igÃĨng arbetsflÃļdet", "trigger_person_recognized": "Person igenkänd", "trigger_person_recognized_description": "UtlÃļses när en person upptäcks", + "trigger_type": "UtlÃļsningstyp", "troubleshoot": "FelsÃļk", "type": "Typ", - "unable_to_change_pin_code": "Kunde inte ändra PIN-kod:en", + "unable_to_change_pin_code": "Kunde inte ändra pinkod", "unable_to_check_version": "Det gÃĨr inte att kontrollera app- eller serverversionen", - "unable_to_setup_pin_code": "Kunde inte konfigurera PIN-kod:en", + "unable_to_setup_pin_code": "Kunde inte konfigurera pinkod", "unarchive": "Ångra arkivering", "unarchive_action_prompt": "{count} borttagen frÃĨn arkivet", - "unarchived_count": "{count, plural, other {Tog bort # frÃĨn arkivet}}", + "unarchived_count": "{count, plural, one {# borttagen frÃĨn arkiv} other {# borttagna frÃĨn arkiv}}", "undo": "Ångra", "unfavorite": "Avfavorisera", "unfavorite_action_prompt": "{count} borttagen frÃĨn Favoriter", @@ -2157,7 +2300,6 @@ "unknown": "Okänd", "unknown_country": "Okänt Land", "unknown_date": "Okänt datum", - "unknown_schema": "Okänt schema", "unknown_year": "Okänt ÃĨr", "unlimited": "Obegränsat", "unlink_motion_video": "Ta bort länken till rÃļrlig video", @@ -2170,21 +2312,24 @@ "unsaved_change": "Osparade ändringar", "unselect_all": "Avmarkera alla", "unselect_all_duplicates": "Avmarkera alla dubletter", + "unselect_all_in": "Avmarkera alla i {group}", "unstack": "Stapla Av", "unstack_action_prompt": "{count} ostaplade", - "unstacked_assets_count": "Tog bort {count, plural, one {# objekt} other {# objekt}} frÃĨn stapeln", + "unstacked_assets_count": "Avstaplade {count, plural, one {# asset} other {# assets}}", "unsupported_field_type": "Fälttyp som inte stÃļds", "unsupported_file_type": "Fil {file} kan inte laddas upp eftersom dess filtyp {type} inte stÃļds.", "untagged": "Otaggad", + "untitled_workflow": "NamnlÃļst arbetsflÃļde", "up_next": "Kommande", "update_location_action_prompt": "Uppdatera platsen fÃļr {count} valda objekt med:", "updated_at": "Uppdaterat", "updated_password": "LÃļsenordet har uppdaterats", "upload": "Ladda upp", "upload_concurrency": "Uppladdning samtidighet", - "upload_day_count": "{date}: {count, plural, one {# uppladdning} other {# uppladdningar}}", "upload_details": "Uppladdningsdetaljer", - "upload_error_with_count": "Uppladdningsfel fÃļr {count, plural, one {# objekt} other {# objekt}}", + "upload_dialog_info": "Vill du säkerhetskopiera de valda objekten till servern?", + "upload_dialog_title": "Ladda Upp Objekt", + "upload_error_with_count": "Uppladdningsfel fÃļr {count, plural, one {# asset} other {# assets}}", "upload_errors": "Uppladdning klar med {count, plural, one {# fel} other {# fel}}, ladda om sidan fÃļr att se nya objekt.", "upload_finished": "Uppladdningen är klar", "upload_progress": "ÅterstÃĨende {remaining, number} - Bearbetade {processed, number}/{total, number}", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Fel", "upload_status_uploaded": "Uppladdad", "upload_success": "Uppladdning lyckades, ladda om sidan fÃļr att se nya objekt.", - "upload_to_album_body": "FÃļr användare som inte använder funktionen fÃļr manuell uppladdning kan ni nu välja att lägga till lokala foton direkt i ett album när ni laddar upp dem. Ni behÃļver inte längre ladda upp dem och lägga till dem i ett album senare.", - "upload_to_album_title": "Ladda upp direkt till ett album", "upload_to_immich": "Ladda upp till Immich ({count})", "uploading": "Laddar upp", "uploading_media": "Uppladdning av media", - "uploads": "Uppladdningar", - "uploads_count": "{count, plural, one {# uppladdning} other {# uppladdningar}}", "url": "URL", "usage": "Användning", "use_biometric": "Använd biometri", @@ -2207,37 +2348,38 @@ "use_browser_locale_description": "Formatera datum, tider och siffror baserat pÃĨ din webbläsares sprÃĨkinställning", "use_current_connection": "Använd aktuell anslutning", "use_custom_date_range": "Använd anpassat datumintervall istället", - "use_template": "Använd mall", "user": "Användare", "user_has_been_deleted": "Den här användaren har raderats.", "user_id": "Användar-ID", - "user_liked": "{user} gillade {type, select, photo {det här fotot} video {den här videon} asset {det här objektet} other {det}}", - "user_pin_code_settings": "PIN-kod", - "user_pin_code_settings_description": "Hantera PIN-kod", + "user_liked": "{user} gillade {type, select, photo {detta fotot} video {denna filmen} asset {detta objekt} other {detta}}", + "user_pin_code_settings": "Pinkod", + "user_pin_code_settings_description": "Hantera pinkod", "user_privacy": "Användarsekretess", "user_purchase_settings": "KÃļp", "user_purchase_settings_description": "Hantera dina kÃļp", + "user_role_set": "Sätt {user} som {role}", "user_usage_detail": "Användaranvändningsdetaljer", "user_usage_stats": "Kontoinformation - statistik", "user_usage_stats_description": "Se statistik - kontoanvändande", "username": "Användarnamn", "users": "Användare", - "users_added_to_album_count": "Lade till {count, plural, one {# användare} other {# användare}} i albumet", + "users_added_to_album_count": "Lade till {count, plural, one {# user} other {# users}} i albumet", "utilities": "Verktyg", "validate": "Validera", "validate_endpoint_error": "Ange en giltig URL", + "validation_error": "Valideringsskräck", "variables": "Variabler", "version": "Version", "version_announcement_closing": "Din vän, Alex", - "version_announcement_message": "Hej! En ny version av Immich är tillgänglig. Läs versionsinformationen sÃĨ att installationen är uppdaterad och felkonfigurationer undviks, särskilt om du använder WatchTower eller nÃĨgon annan mekanism som uppdaterar Immich-instansen automatiskt.", + "version_announcement_message": "Hej där! En ny version av Immich är tillgänglig. Ta dig tid att läsa versionsfakta fÃļr att säkerställa att dina inställningar är uppdaterade fÃļr att fÃļrhindra eventuella felkonfigurationer, särskilt om du använder WatchTower eller nÃĨgon mekanism som hanterar uppdatering av din Immich instans automatiskt.", "version_history": "Versionshistorik", "version_history_item": "Version {version} installerad {date}", "video": "Video", "video_hover_setting": "Spela upp videotumnagel när muspekaren är Ãļver den", - "video_hover_setting_description": "Spela upp videominiatyren när muspekaren hÃĨlls Ãļver den. Även när funktionen är inaktiverad kan uppspelningen startas genom att hÃĨlla muspekaren Ãļver uppspelningsikonen.", - "video_quality": "Videokvalitet", + "video_hover_setting_description": "Spela upp videotumnagel när muspekaren är Ãļver den. Även när den är deaktiverad kan uppspelning startas när muspekaren är Ãļver play-ikonen.", "videos": "Videor", "videos_count": "{count, plural, one {# Video} other {# Videor}}", + "videos_only": "Videor endast", "view": "Visa", "view_album": "Visa Album", "view_all": "Visa alla", @@ -2246,29 +2388,29 @@ "view_details": "Visa detaljer", "view_in_timeline": "Visa i tidslinjen", "view_link": "Visa länk", + "view_links": "Visa länkar", "view_name": "Visa", "view_next_asset": "Visa nästa objekt", "view_previous_asset": "Visa fÃļregÃĨende objekt", "view_qr_code": "Visa QR-kod", "view_similar_photos": "Visa liknande foton", - "view_stack": "Visa stapel", - "viewer_remove_from_stack": "Ta bort frÃĨn stapeln", + "view_stack": "Visa Stapel", + "view_user": "Visa Användare", + "viewer_remove_from_stack": "Ta bort frÃĨn Stapeln", + "viewer_stack_use_as_main_asset": "Använd som Huvudobjekt", + "viewer_unstack": "Stapla Av", "visibility": "Synlighet", - "visibility_changed": "Ändrade synligheten fÃļr {count, plural, one {# person} other {# personer}}", + "visibility_changed": "Synlighet ändrad fÃļr {count, plural, one {# person} other {# personer}}", "visual": "Visuellt", + "visual_builder": "Visuell byggare", "waiting": "Väntar", "waiting_count": "Väntande: {count}", "warning": "Varning", "week": "Vecka", "welcome": "Välkommen", "welcome_to_immich": "Välkommen till Immich", - "whats_new": "Vad är nytt", - "whats_new_settings_subtitle": "Se vad som är nytt i Immich", - "whats_new_version": "Version {version}", - "when": "När", "width": "Bredd", "wifi_name": "Wi-Fi-namn", - "workflow": "ArbetsflÃļde", "workflow_delete_prompt": "Är du säker pÃĨ att du vill ta bort det här arbetsflÃļdet?", "workflow_deleted": "ArbetsflÃļdet raderat", "workflow_description": "Beskrivning av arbetsflÃļdet", @@ -2278,17 +2420,17 @@ "workflow_name": "ArbetsflÃļdesnamn", "workflow_navigation_prompt": "Är du säker pÃĨ att du vill avsluta utan att spara dina ändringar?", "workflow_summary": "Sammanfattning av arbetsflÃļde", - "workflow_templates": "ArbetsflÃļdesmallar", "workflow_update_success": "ArbetsflÃļdet har uppdaterats", + "workflow_updated": "ArbetsflÃļdet uppdaterades", "workflows": "ArbetsflÃļden", - "workflows_help_text": "ArbetsflÃļden automatiserar ÃĨtgärder pÃĨ dina objekt baserat pÃĨ utlÃļsare och filter", - "wrong_pin_code": "Fel PIN-kod", - "x_of_total": "{x}/{total}", + "workflows_help_text": "ArbetsflÃļden automatiserar ÃĨtgärder pÃĨ dina resurser baserat pÃĨ utlÃļsare och filter", + "wrong_pin_code": "Fel pinkod", "year": "År", "years_ago": "{years, plural, one {# ÃĨr} other {# ÃĨr}} sedan", "yes": "Ja", "you_dont_have_any_shared_links": "Du har inga delade länkar", "your_wifi_name": "Ditt Wi-Fi-namn", "zero_to_clear_rating": "Tryck 0 fÃļr att rensa betygsättningen", - "zoom_image": "Zooma bild" + "zoom_image": "Zooma bild", + "zoom_to_bounds": "Zooma till gränser" } diff --git a/i18n/sw.json b/i18n/sw.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/sw.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/swg.json b/i18n/swg.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/swg.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/ta.json b/i18n/ta.json index d48b7c9429..482be2e993 100644 --- a/i18n/ta.json +++ b/i18n/ta.json @@ -10,31 +10,39 @@ "active": "āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽ˛ā¯", "active_count": "āŽšā¯†āŽ¯āŽ˛āŽŋāŽ˛ā¯: {count}", "activity": "āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽ•āŽŗā¯", + "activity_changed": "āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯ {enabled, select, true {āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯} other {āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯}}", "add": "āŽšā¯‡āŽ°ā¯", "add_a_description": "āŽĩāŽŋāŽĩāŽ°āŽŽā¯ āŽšā¯‡āŽ°ā¯", "add_a_location": "āŽ‡āŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_a_name": "āŽĒā¯†āŽ¯āŽ°ā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_a_title": "āŽ¤āŽ˛ā¯ˆāŽĒā¯āŽĒ❁ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_action": "āŽšā¯†āŽ¯āŽ˛ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯", + "add_action_description": "āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩā¯‡āŽŖā¯āŽŸāŽŋāŽ¯ āŽšā¯†āŽ¯āŽ˛ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽ•āŽŋāŽŗāŽŋāŽ•ā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "add_assets": "āŽŠāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_birthday": "āŽĒāŽŋāŽąāŽ¨ā¯āŽ¤āŽ¨āŽžāŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_endpoint": "āŽšā¯‡āŽĩ❈ āŽ¨āŽŋāŽ°āŽ˛ā¯ˆ āŽšā¯‡āŽ°ā¯", "add_exclusion_pattern": "āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•ā¯ āŽĩāŽŸāŽŋāŽĩāŽ¤ā¯āŽ¤ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_filter": "āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸāŽŋāŽ¯ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_filter_description": "āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸāŽŋ āŽ¨āŽŋāŽĒāŽ¨ā¯āŽ¤āŽŠā¯ˆāŽ¯ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽ•āŽŋāŽŗāŽŋāŽ•ā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "add_location": "āŽ‡āŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_more_users": "āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_partner": "āŽ¤ā¯āŽŖā¯ˆāŽ¯ā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_path": "āŽĒāŽžāŽ¤ā¯ˆ (āŽĒāŽžāŽ¤ā¯) āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "add_photos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", - "add_step": "āŽĒāŽŸāŽŋāŽ•āŽŗā¯ˆ āŽšā¯‡āŽ°ā¯", "add_tag": "āŽ•ā¯āŽąāŽŋāŽ¯ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•", "add_to": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯â€Ļ", "add_to_album": "āŽ†āŽ˛ā¯āŽĒāŽŽāŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•", "add_to_album_bottom_sheet_added": "{album}-āŽ‡āŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "add_to_album_bottom_sheet_already_exists": "āŽāŽąā¯āŽ•āŽŠāŽĩ❇ {album}-āŽ‡āŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", "add_to_album_bottom_sheet_some_local_assets": "āŽšāŽŋāŽ˛ āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "add_to_album_toggle": "{album} āŽ•ā¯āŽ•āŽžāŽŠ āŽ¤ā¯‡āŽ°ā¯āŽĩ❈ āŽŽāŽžāŽąā¯āŽąā¯", "add_to_albums": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯", "add_to_albums_count": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯({count})", "add_to_bottom_bar": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_to_shared_album": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽŽāŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•", "add_upload_to_stack": "āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽŋāŽ˛ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯", "add_url": "URL āŽāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "add_workflow_step": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁āŽĒā¯ āŽĒāŽŸāŽŋāŽ¯ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "added_to_archive": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "added_to_favorites": "āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ (āŽĒ❇āŽĩāŽ°āŽŋāŽŸā¯āŽ¸ā¯) āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "added_to_favorites_count": "āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ {count, number} āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -73,7 +81,6 @@ "cron_expression_description": "āŽ•ā¯āŽ°ā¯‹āŽŠā¯ āŽĩāŽŸāŽŋāŽĩāŽŽā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽŋ āŽšā¯āŽ•ā¯‡āŽŠāŽŋāŽ™ā¯ āŽ‡āŽŸā¯ˆāŽĩā¯†āŽŗāŽŋāŽ¯ā¯ˆ āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯. āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽ¤āŽ•āŽĩāŽ˛ā¯āŽ•ā¯āŽ•ā¯ āŽŽ.āŽ•āŽž. āŽ•ā¯āŽ°ā¯‹āŽŠā¯āŽŸāŽžāŽĒā¯ āŽ•ā¯āŽ°ā¯ ", "cron_expression_presets": "āŽ•ā¯āŽ°ā¯‹āŽŠā¯ āŽĩā¯†āŽŗāŽŋāŽĒā¯āŽĒāŽžāŽŸā¯ āŽŽā¯āŽŠā¯āŽŠāŽŽā¯ˆāŽĩā¯āŽ•āŽŗā¯", "disable_login": "āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩ❈ āŽŽā¯āŽŸāŽ•ā¯āŽ•ā¯", - "download_csv": "CSV-āŽāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "duplicate_detection_job_description": "āŽ’āŽ¤ā¯āŽ¤ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽŖā¯āŽŸāŽąāŽŋāŽ¯, āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽ‡āŽ¯āŽ¨ā¯āŽ¤āŽŋāŽ°āŽ•ā¯ āŽ•āŽąā¯āŽąāŽ˛ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯. āŽ¸ā¯āŽŽāŽžāŽ°ā¯āŽŸā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯ˆ āŽ¨āŽŽā¯āŽĒāŽŋāŽ¯ā¯āŽŗā¯āŽŗāŽ¤ā¯", "exclusion_pattern_description": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯‚āŽ˛āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ¸ā¯āŽ•ā¯‡āŽŠā¯ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒā¯āŽąāŽ•ā¯āŽ•āŽŖāŽŋāŽ•ā¯āŽ• āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•ā¯ āŽĩāŽŸāŽŋāŽĩāŽ™ā¯āŽ•āŽŗā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ. RAW āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽĒā¯‹āŽŠā¯āŽą āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽąāŽ•ā¯āŽ•ā¯āŽŽāŽ¤āŽŋ āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒāŽžāŽ¤ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ•āŽŗā¯ āŽ‰āŽ™ā¯āŽ•āŽŗāŽŋāŽŸāŽŽā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ˛ā¯ āŽ‡āŽ¤ā¯ āŽĒāŽ¯āŽŠā¯āŽŗā¯āŽŗāŽ¤āŽžāŽ• āŽ‡āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯.", "export_config_as_json_description": "āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽ•āŽŖāŽŋāŽŠāŽŋ āŽ‰āŽŗā¯āŽŗāŽŽā¯ˆāŽĩ❈ JSON āŽ•ā¯‹āŽĒā¯āŽĒāŽžāŽ•āŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -183,15 +190,9 @@ "machine_learning_smart_search_enabled": "āŽ¸ā¯āŽŽāŽžāŽ°ā¯āŽŸā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•ā¯", "machine_learning_smart_search_enabled_description": "āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŋāŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ˛ā¯, āŽ¸ā¯āŽŽāŽžāŽ°ā¯āŽŸā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯āŽ•ā¯āŽ•āŽžāŽ• āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯āŽžāŽ•ā¯āŽ•āŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸāŽžāŽ¤ā¯.", "machine_learning_url_description": "āŽ‡āŽ¯āŽ¨ā¯āŽ¤āŽŋāŽ° āŽ•āŽąā¯āŽąāŽ˛ā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ. āŽ’āŽŠā¯āŽąā¯āŽ•ā¯āŽ•ā¯ āŽŽā¯‡āŽąā¯āŽĒāŽŸā¯āŽŸ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ āŽĩāŽ´āŽ™ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ˛ā¯, āŽ’āŽĩā¯āŽĩā¯ŠāŽ°ā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽŽā¯āŽŽā¯ āŽ’āŽĩā¯āŽĩā¯ŠāŽŠā¯āŽąāŽžāŽ• āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽĒāŽ¤āŽŋāŽ˛āŽŗāŽŋāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĩāŽ°ā¯ˆ, āŽŽā¯āŽ¤āŽ˛āŽŋāŽ˛ā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•āŽŸā¯ˆāŽšāŽŋ āŽĩāŽ°ā¯ˆ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯. āŽĒāŽ¤āŽŋāŽ˛āŽŗāŽŋāŽ•ā¯āŽ•āŽžāŽ¤ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽ†āŽŠā¯āŽ˛ā¯ˆāŽŠāŽŋāŽ˛ā¯ āŽĩāŽ°ā¯āŽŽā¯ āŽĩāŽ°ā¯ˆ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ•āŽŽāŽžāŽ•āŽĒā¯ āŽĒā¯āŽąāŽ•ā¯āŽ•āŽŖāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯.", - "maintenance_backup_management": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽā¯‡āŽ˛āŽžāŽŖā¯āŽŽā¯ˆ", "maintenance_delete_backup": "āŽ•āŽžāŽĒā¯āŽĒā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "maintenance_delete_backup_description": "āŽ‡āŽ¨ā¯āŽ¤āŽ•ā¯ āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽŽā¯€āŽŗāŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽŽāŽ˛ā¯ āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯.", "maintenance_delete_error": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", - "maintenance_integrity_check": "āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", - "maintenance_integrity_check_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", - "maintenance_integrity_checksum_mismatch": "āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❁ āŽŽāŽŖā¯ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", - "maintenance_integrity_checksum_mismatch_description": "āŽĩāŽŸā¯āŽŸāŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❁ āŽŽāŽŖā¯āŽŖā¯āŽŽā¯, āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽ¤āŽ°āŽĩā¯āŽ¤ā¯āŽ¤āŽŗāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽŽāŽŋāŽ¤ā¯āŽ¤ā¯āŽŗā¯āŽŗ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❁ āŽŽāŽŖā¯āŽŖā¯āŽŽā¯ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ¤ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯.", - "maintenance_integrity_checksum_mismatch_job": "āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❁ āŽŽāŽŖā¯ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽŽā¯ˆāŽ•āŽŗā¯ˆāŽšā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "maintenance_restore_backup": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆ", "maintenance_restore_backup_description": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽ¤ā¯āŽŸā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯. āŽ¤ā¯ŠāŽŸāŽ°ā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽŽā¯āŽŠā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯.", "maintenance_restore_backup_different_version": "āŽ‡āŽ¨ā¯āŽ¤ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšāŽŋāŽŠā¯ āŽĩā¯‡āŽąā¯āŽĒāŽŸā¯āŽŸ āŽĒāŽ¤āŽŋāŽĒā¯āŽĒā¯ˆāŽ•ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸā¯ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯!", @@ -266,8 +267,6 @@ "notification_enable_email_notifications": "āŽŽāŽŋāŽŠā¯āŽŠāŽžā¯āŽšāŽ˛ā¯ āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "notification_settings": "āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒ❁ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", "notification_settings_description": "āŽŽāŽŋāŽŠā¯āŽŠāŽžā¯āŽšāŽ˛ā¯ āŽ‰āŽŸā¯āŽĒāŽŸ āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒ❁ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", - "oauth_allow_insecure_requests": "āŽĒāŽžāŽ¤ā¯āŽ•āŽžāŽĒā¯āŽĒāŽŋāŽ˛ā¯āŽ˛āŽž āŽ•ā¯‹āŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆāŽ•āŽŗā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋ", - "oauth_allow_insecure_requests_description": "āŽŽāŽšā¯āŽšāŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆ: āŽ‡āŽšā¯āŽšā¯†āŽ¯āŽ˛ā¯ OAuth āŽ•ā¯‹āŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆāŽ•āŽŗā¯āŽ•ā¯āŽ•āŽžāŽŠ TLS āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ˛ā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❈ āŽšā¯†āŽ¯āŽ˛ā¯ āŽ‡āŽ´āŽ•ā¯āŽ•āŽšā¯ āŽšā¯†āŽ¯ā¯āŽĩāŽ¤ā¯āŽŸāŽŠā¯ MITM āŽ¤āŽžāŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĩāŽ´āŽŋāŽĩāŽ•ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯.", "oauth_auto_launch": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯€āŽŸā¯", "oauth_auto_launch_description": "āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩ❁āŽĒā¯ āŽĒāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯āŽšā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ¤āŽžāŽŠāŽžāŽ•āŽĩ❇ OAuth āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩ❁ āŽ“āŽŸā¯āŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", "oauth_auto_register": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•ā¯ āŽĒāŽ¤āŽŋāŽĩ❁", @@ -304,7 +303,6 @@ "refreshing_all_libraries": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨ā¯‚āŽ˛āŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "registration": "āŽ¨āŽŋāŽ°ā¯āŽĩāŽžāŽ• āŽĒāŽ¤āŽŋāŽĩ❁", "registration_description": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŖāŽŋāŽŠāŽŋāŽ¯āŽŋāŽ˛ā¯ āŽŽā¯āŽ¤āŽ˛ā¯ āŽĒāŽ¯āŽŠāŽ°āŽžāŽ• āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽ¤āŽžāŽ˛ā¯, āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽ°ā¯āŽĩāŽžāŽ•āŽŋāŽ¯āŽžāŽ• āŽ¨āŽŋāŽ¯āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽĩā¯€āŽ°ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨āŽŋāŽ°ā¯āŽĩāŽžāŽ•āŽĒā¯ āŽĒāŽŖāŽŋāŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯āŽĒā¯ āŽĒā¯ŠāŽąā¯āŽĒā¯āŽĒāŽžāŽĩā¯€āŽ°ā¯āŽ•āŽŗā¯, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽ‰āŽ™ā¯āŽ•āŽŗāŽžāŽ˛ā¯ āŽ•ā¯‚āŽŸā¯āŽ¤āŽ˛ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽĩāŽžāŽ°ā¯āŽ•āŽŗā¯.", - "release_channel_stable": "āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯āŽžāŽŠ", "remove_failed_jobs": "āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯ā¯āŽąā¯āŽą āŽĩā¯‡āŽ˛ā¯ˆāŽ•āŽŗā¯ˆ āŽ…āŽ•āŽąā¯āŽąāŽĩā¯āŽŽā¯", "require_password_change_on_login": "āŽŽā¯āŽ¤āŽ˛ā¯ āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩāŽŋāŽ˛ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽāŽžāŽąā¯āŽą āŽĩā¯‡āŽŖā¯āŽŸā¯āŽŽā¯", "reset_settings_to_default": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ‡āŽ¯āŽ˛ā¯āŽĒā¯āŽ¨āŽŋāŽ˛ā¯ˆāŽ•ā¯āŽ•ā¯ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -399,7 +397,6 @@ "transcoding_preferred_hardware_device_description": "VAAPI āŽŽāŽąā¯āŽąā¯āŽŽā¯ QSV āŽ•ā¯āŽ•ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤ā¯āŽŽā¯. āŽĩāŽŠā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯ āŽŸāŽŋāŽ°āŽžāŽŠā¯āŽšā¯āŽ•ā¯‹āŽŸāŽŋāŽ™ā¯āŽ•āŽŋāŽąā¯āŽ•ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŽā¯ āŽŸā¯āŽ°ā¯ˆ āŽŽā¯āŽŠā¯ˆāŽ¯ā¯ˆ āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯.", "transcoding_preset_preset": "āŽŽā¯āŽŠā¯āŽŠāŽŽā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ (-āŽĒāŽŋāŽ°āŽšā¯†āŽŸā¯)", "transcoding_preset_preset_description": "āŽšā¯āŽ°ā¯āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯ˆāŽĩ❁. āŽŽā¯†āŽ¤ā¯āŽĩāŽžāŽŠ āŽŽā¯āŽŠā¯āŽŠāŽŽā¯ˆāŽĩā¯āŽ•āŽŗā¯ āŽšāŽŋāŽąāŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽ’āŽ°ā¯ āŽ•ā¯āŽąāŽŋāŽĒā¯āŽĒāŽŋāŽŸā¯āŽŸ āŽĒāŽŋāŽŸā¯āŽ°ā¯‡āŽŸā¯āŽŸā¯ˆ āŽ•ā¯āŽąāŽŋāŽĩā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽ¤āŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ¤āŽŋāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ. VP9 'āŽĩā¯‡āŽ•āŽŽāŽžāŽ•' āŽŽā¯‡āŽ˛ā¯‡ āŽ‰āŽŗā¯āŽŗ āŽĩā¯‡āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽĒā¯āŽąāŽ•ā¯āŽ•āŽŖāŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯.", - "transcoding_realtime_description": "āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹ āŽ¸ā¯āŽŸā¯āŽ°ā¯€āŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯‡, āŽ¨āŽŋāŽ•āŽ´ā¯āŽ¨ā¯‡āŽ°āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽŸāŽŋāŽ°āŽžāŽŠā¯āŽ¸ā¯āŽ•ā¯‹āŽŸāŽŋāŽ™ā¯ āŽšā¯†āŽ¯ā¯āŽ¯ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯. āŽ¤āŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽžāŽąā¯āŽąā¯āŽĩāŽ¤ā¯ˆ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ•āŽŋāŽąāŽ¤ā¯, āŽ†āŽŠāŽžāŽ˛ā¯ āŽšāŽ°ā¯āŽĩāŽ°āŽŋāŽŠā¯ āŽ¤āŽŋāŽąāŽŠā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒā¯ŠāŽąā¯āŽ¤ā¯āŽ¤ā¯, āŽ…āŽ¤āŽŋāŽ• āŽĒāŽŋāŽŗā¯‡āŽĒā¯‡āŽ•ā¯ āŽ¤āŽžāŽŽāŽ¤āŽŽā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¤āŽŸā¯āŽŽāŽžāŽąā¯āŽąāŽ¤ā¯āŽ¤ā¯ˆ āŽāŽąā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽ•ā¯āŽ•ā¯‚āŽŸā¯āŽŽā¯.", "transcoding_reference_frames": "āŽ•ā¯āŽąāŽŋāŽĒā¯āŽĒ❁ āŽĒāŽŋāŽ°ā¯‡āŽŽā¯āŽ•āŽŗā¯", "transcoding_reference_frames_description": "āŽ•ā¯ŠāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽŸā¯āŽŸāŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽšā¯āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ•ā¯āŽąāŽŋāŽĒā¯āŽĒāŽŋāŽŸ āŽĩā¯‡āŽŖā¯āŽŸāŽŋāŽ¯ āŽĒāŽŋāŽ°ā¯‡āŽŽā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽŽāŽŖā¯āŽŖāŽŋāŽ•ā¯āŽ•ā¯ˆ. āŽ…āŽ¤āŽŋāŽ• āŽŽāŽ¤āŽŋāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽšā¯āŽ°ā¯āŽ•ā¯āŽ• āŽšā¯†āŽ¯āŽ˛ā¯āŽ¤āŽŋāŽąāŽŠā¯ˆ āŽŽā¯‡āŽŽā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ, āŽ†āŽŠāŽžāŽ˛ā¯ āŽ•ā¯āŽąāŽŋāŽ¯āŽžāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽŽā¯†āŽ¤ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ. 0 āŽ‡āŽ¨ā¯āŽ¤ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒ❈ āŽ¤āŽžāŽŠāŽžāŽ• āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯.", "transcoding_required_description": "āŽāŽąā¯āŽąā¯āŽ•ā¯āŽ•ā¯ŠāŽŗā¯āŽŗāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩāŽŸāŽŋāŽĩāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‡āŽ˛ā¯āŽ˛āŽžāŽ¤ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡", @@ -457,6 +454,8 @@ "advanced_settings_clear_image_cache": "āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❈ āŽ…āŽ´āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "advanced_settings_clear_image_cache_error": "āŽĒāŽŸāŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❈ āŽ…āŽ´āŽŋāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "advanced_settings_clear_image_cache_success": "{size} āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽ…āŽ´āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "advanced_settings_enable_alternate_media_filter_subtitle": "āŽŽāŽžāŽąā¯āŽąā¯ āŽ…āŽŗāŽĩā¯āŽ•ā¯‹āŽ˛ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ…āŽŸāŽŋāŽĒā¯āŽĒāŽŸā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩāŽŋāŽŠā¯ āŽĒā¯‹āŽ¤ā¯ āŽŽā¯€āŽŸāŽŋāŽ¯āŽžāŽĩ❈ āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸ āŽ‡āŽ¨ā¯āŽ¤ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯. āŽŽāŽ˛ā¯āŽ˛āŽž āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ†āŽĒā¯āŽ¸ā¯ āŽ•āŽŖā¯āŽŸāŽąāŽŋāŽĩāŽ¤āŽŋāŽ˛ā¯ āŽšāŽŋāŽ•ā¯āŽ•āŽ˛ā¯āŽ•āŽŗā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ˛ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽ‡āŽ¤ā¯ˆ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", + "advanced_settings_enable_alternate_media_filter_title": "[āŽĒāŽ°āŽŋāŽšā¯‹āŽ¤āŽŠā¯ˆāŽ•ā¯āŽ•ā¯ āŽ‰āŽŸā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯] āŽŽāŽžāŽąā¯āŽąā¯ āŽšāŽžāŽ¤āŽŠ āŽ†āŽ˛ā¯āŽĒ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁ āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽžāŽŠā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "advanced_settings_log_level_title": "āŽĒāŽ¤āŽŋāŽĩ❁ āŽ¨āŽŋāŽ˛ā¯ˆ: {level}", "advanced_settings_prefer_remote_subtitle": "āŽšāŽŋāŽ˛ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯ āŽ‰āŽŗā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽšāŽŋāŽąā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽāŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽŽāŽŋāŽ•āŽĩā¯āŽŽā¯ āŽŽā¯†āŽ¤ā¯āŽĩāŽžāŽ• āŽ‡āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯. āŽ…āŽ¤āŽąā¯āŽ•ā¯ āŽĒāŽ¤āŽŋāŽ˛āŽžāŽ• āŽšāŽ°ā¯āŽĩāŽ°ā¯ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽāŽąā¯āŽą āŽ‡āŽ¨ā¯āŽ¤ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯ˆāŽšā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯.", "advanced_settings_prefer_remote_title": "āŽ°āŽŋāŽŽā¯‹āŽŸā¯ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽŽā¯āŽŠā¯āŽŠā¯āŽ°āŽŋāŽŽā¯ˆ āŽ•ā¯ŠāŽŸā¯", @@ -464,6 +463,8 @@ "advanced_settings_proxy_headers_title": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽĒāŽ¤āŽŋāŽ˛āŽžāŽŗā¯ āŽ¤āŽ˛ā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ [āŽĒāŽ°āŽŋāŽšā¯‹āŽ¤āŽŠā¯ˆ]", "advanced_settings_readonly_mode_subtitle": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒāŽ¤āŽąā¯āŽ•ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĒāŽŸāŽŋāŽĒā¯āŽĒāŽ¤āŽąā¯āŽ•ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯, āŽĒāŽ˛ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽĒā¯āŽĒāŽ¤ā¯, āŽĒāŽ•āŽŋāŽ°ā¯āŽ¤āŽ˛ā¯, āŽ…āŽŠā¯āŽĒā¯āŽĒā¯āŽ¤āŽ˛ā¯, āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯ āŽĒā¯‹āŽŠā¯āŽą āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯āŽŽā¯ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽŠ. āŽĒāŽŋāŽ°āŽ¤āŽžāŽŠ āŽ¤āŽŋāŽ°ā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ…āŽĩāŽ¤āŽžāŽ°ā¯ āŽĩāŽ´āŽŋāŽ¯āŽžāŽ• āŽĒāŽŸāŽŋāŽ•ā¯āŽ• āŽŽāŽŸā¯āŽŸā¯āŽŽā¯ āŽŽāŽŠā¯āŽĒāŽ¤ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•ā¯/āŽŽā¯āŽŸāŽ•ā¯āŽ•ā¯", "advanced_settings_readonly_mode_title": "āŽĒāŽŸāŽŋāŽ•ā¯āŽ•-āŽŽāŽŸā¯āŽŸā¯āŽŽā¯ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆ", + "advanced_settings_self_signed_ssl_subtitle": "āŽšāŽ°ā¯āŽĩāŽ°ā¯ āŽŽāŽŖā¯āŽŸā¯āŽĒāŽžāŽ¯āŽŋāŽŖā¯āŽŸāŽŋāŽąā¯āŽ•āŽžāŽŠ SSL āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❈ āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯. āŽšā¯āŽ¯ āŽ•ā¯ˆāŽ¯ā¯ŠāŽĒā¯āŽĒāŽŽāŽŋāŽŸā¯āŽŸ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ‡āŽ¤ā¯ āŽ¤ā¯‡āŽĩā¯ˆāŽ¯āŽžāŽŠāŽ¤ā¯.", + "advanced_settings_self_signed_ssl_title": "āŽ¤āŽŠā¯āŽĩāŽ¯ āŽ•ā¯ˆāŽ¯ā¯ŠāŽĒā¯āŽĒāŽŽāŽŋāŽŸā¯āŽŸ SSL āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯āŽ•āŽŗā¯ˆ āŽ‡āŽšā¯ˆāŽĩ❁ [āŽĒāŽ°āŽŋāŽšā¯‹āŽ¤āŽŠā¯ˆ]", "advanced_settings_sync_remote_deletions_subtitle": "āŽ‡āŽŖā¯ˆāŽ¯āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ¨āŽŸāŽĩāŽŸāŽŋāŽ•ā¯āŽ•ā¯ˆ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯, āŽ‡āŽ¨ā¯āŽ¤āŽšā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽ’āŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆ āŽ¤āŽžāŽŠāŽžāŽ•āŽĩ❇ āŽ¨ā¯€āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "advanced_settings_sync_remote_deletions_title": "āŽ¤ā¯ŠāŽ˛ā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯āŽ•āŽŗā¯ˆ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ [āŽĒāŽ°āŽŋāŽšā¯‹āŽ¤āŽŠā¯ˆāŽ•ā¯āŽ•ā¯ āŽ‰āŽŸā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯]", "advanced_settings_tile_subtitle": "āŽŽā¯‡āŽŽā¯āŽĒāŽŸā¯āŽŸ āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", @@ -479,18 +480,31 @@ "album_delete_confirmation": "{album} āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "album_delete_confirmation_description": "āŽ‡āŽ¨ā¯āŽ¤ āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ˛ā¯, āŽŽāŽąā¯āŽą āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗāŽžāŽ˛ā¯ āŽ‡āŽ¤ā¯ˆ āŽ…āŽŖā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯.", "album_deleted": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "album_info_card_backup_album_excluded": "āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "album_info_card_backup_album_included": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", "album_info_updated": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "album_leave": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽĩāŽŋāŽŸā¯āŽ™ā¯āŽ•āŽŗā¯?", + "album_leave_confirmation": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• {album}āŽ āŽĩāŽŋāŽŸā¯āŽŸā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "album_name": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽĒā¯†āŽ¯āŽ°ā¯", "album_options": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "album_remove_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽ…āŽ•āŽąā¯āŽąāŽĩāŽž?", "album_remove_user_confirmation": "{user} āŽ āŽ…āŽ•āŽąā¯āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "album_search_not_found": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯āŽŸāŽŠā¯ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽ•ā¯āŽ•ā¯‚āŽŸāŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "album_selected": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "album_share_no_users": "āŽ‡āŽ¨ā¯āŽ¤ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ˛ā¯āŽ˛āŽž āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯āŽŽā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸāŽ¤āŽžāŽ•āŽ¤ā¯ āŽ¤ā¯†āŽ°āŽŋāŽ•āŽŋāŽąāŽ¤ā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽ‰āŽ™ā¯āŽ•āŽŗāŽŋāŽŸāŽŽā¯ āŽŽāŽ¨ā¯āŽ¤ āŽĒāŽ¯āŽŠāŽ°ā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ.", "album_summary": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽšā¯āŽ°ā¯āŽ•ā¯āŽ•āŽŽā¯", "album_updated": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "album_updated_setting_description": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽ‡āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽŽāŽŋāŽŠā¯āŽŠāŽžā¯āŽšāŽ˛ā¯ āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒā¯†āŽąā¯āŽ™ā¯āŽ•āŽŗā¯", "album_upload_assets": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŖāŽŋāŽŠāŽŋāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽŋ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "album_user_left": "āŽ‡āŽŸāŽ¤ā¯ {album}", + "album_user_removed": "āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ {user}", + "album_viewer_appbar_delete_confirm": "āŽ‡āŽ¨ā¯āŽ¤ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŖāŽ•ā¯āŽ•āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨ā¯€āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "album_viewer_appbar_share_err_delete": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "album_viewer_appbar_share_err_leave": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĩāŽŋāŽŸā¯āŽŸā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "album_viewer_appbar_share_err_remove": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽšāŽŋāŽ•ā¯āŽ•āŽ˛ā¯āŽ•āŽŗā¯ āŽ‰āŽŗā¯āŽŗāŽŠ", + "album_viewer_appbar_share_err_title": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽ¤āŽ˛ā¯ˆāŽĒā¯āŽĒ❈ āŽŽāŽžāŽąā¯āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "album_viewer_appbar_share_leave": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽĩāŽŋāŽŸā¯āŽŸā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽąā¯", + "album_viewer_appbar_share_to": "āŽĒāŽ•āŽŋāŽ°ā¯", "album_viewer_page_share_add_users": "āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "album_with_link_access": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❁ āŽ‰āŽŗā¯āŽŗ āŽŽāŽĩāŽ°ā¯āŽŽā¯ āŽ‡āŽ¨ā¯āŽ¤ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¨āŽĒāŽ°ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽŸā¯āŽŸā¯āŽŽā¯.", "albums": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯", @@ -499,12 +513,14 @@ "albums_default_sort_order_description": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ†āŽ°āŽŽā¯āŽĒ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĩāŽ°āŽŋāŽšā¯ˆāŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽ˛ā¯ āŽĩāŽ°āŽŋāŽšā¯ˆ.", "albums_feature_description": "āŽĒāŽŋāŽą āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŗā¯āŽŗāŽ•ā¯āŽ•ā¯‚āŽŸāŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒā¯āŽ•āŽŗā¯.", "albums_on_device_count": "āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ ({count})", + "albums_selected": "{count, plural, one {# āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯} other {# āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ}}", "all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯āŽŽā¯", "all_albums": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯āŽŽā¯", "all_people": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽŽāŽ•ā¯āŽ•āŽŗā¯āŽŽā¯", "all_photos": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯āŽŽā¯", "all_videos": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯āŽŽā¯", "allow_dark_mode": "āŽ‡āŽ°ā¯āŽŖā¯āŽŸ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "allow_edits": "āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "allow_public_user_to_download": "āŽĒā¯ŠāŽ¤ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¯ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "allow_public_user_to_upload": "āŽĒā¯ŠāŽ¤ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "allowed": "āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ¤ā¯āŽ¤", @@ -512,12 +528,14 @@ "always_keep": "āŽŽāŽĒā¯āŽĒā¯‹āŽ¤ā¯āŽŽā¯ āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯", "always_keep_photos_hint": "āŽ‡āŽ¨ā¯āŽ¤āŽšā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽŽāŽ˛ā¯āŽ˛āŽžāŽĒā¯ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•āŽžāŽ˛āŽŋāŽ¯āŽžāŽ•ā¯āŽ•ā¯āŽ™ā¯āŽ•āŽŗā¯.", "always_keep_videos_hint": "āŽ‡āŽ¨ā¯āŽ¤āŽšā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽŽāŽ˛ā¯āŽ˛āŽž āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•āŽžāŽ˛āŽŋāŽ¯āŽžāŽ•ā¯āŽ•ā¯āŽŽā¯.", + "anti_clockwise": "āŽ•āŽŸāŽŋāŽ•āŽžāŽ° āŽŽāŽ¤āŽŋāŽ°ā¯āŽĒā¯āŽĒ❁", "api_key": "āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆ", "api_key_description": "āŽ‡āŽ¨ā¯āŽ¤ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒ❁ āŽ’āŽ°ā¯ āŽŽā¯āŽąā¯ˆ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽ•āŽžāŽŖā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯. āŽšāŽžāŽŗāŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽŽā¯‚āŽŸā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽŽā¯āŽŠā¯ āŽ…āŽ¤ā¯ˆ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ• āŽŽāŽąāŽ•ā¯āŽ•āŽžāŽ¤ā¯€āŽ°ā¯āŽ•āŽŗā¯.", "api_key_empty": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆ āŽĒā¯†āŽ¯āŽ°ā¯ āŽ•āŽžāŽ˛āŽŋāŽ¯āŽžāŽ• āŽ‡āŽ°ā¯āŽ•ā¯āŽ•āŽ•ā¯āŽ•ā¯‚āŽŸāŽžāŽ¤ā¯", "api_keys": "āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆāŽ•āŽŗā¯", "app_architecture_variant": "āŽŽāŽžāŽąā¯āŽĒāŽžāŽŸā¯ (āŽ•āŽŸā¯āŽŸāŽŋāŽŸāŽ•ā¯āŽ•āŽ˛ā¯ˆ)", "app_bar_signout_dialog_content": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", + "app_bar_signout_dialog_ok": "āŽ†āŽŽā¯", "app_bar_signout_dialog_title": "āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽąā¯", "app_download_links": "āŽ†āŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ• āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", "app_settings": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", @@ -528,18 +546,27 @@ "archive": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽŽā¯", "archive_action_prompt": "{count} āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "archive_or_unarchive_photo": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽŽā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽšā¯†āŽ¯āŽ˛āŽąā¯āŽą āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯", + "archive_page_no_archived_assets": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "archive_page_title": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽŽā¯ ({count})", "archive_size": "āŽ•āŽžāŽĒā¯āŽĒāŽ• āŽ…āŽŗāŽĩ❁", "archive_size_description": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•āŽžāŽŠ āŽ•āŽžāŽĒā¯āŽĒāŽ• āŽ…āŽŗāŽĩ❈ āŽ‰āŽŗā¯āŽŗāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ (āŽ•āŽŋāŽĒāŽŋāŽ˛ā¯)", "archived": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "archived_count": "{count, plural, other {āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ #}}", "are_these_the_same_person": "āŽ‡āŽĩāŽ°ā¯āŽ•āŽŗā¯ āŽ’āŽ°ā¯‡ āŽ¨āŽĒāŽ°āŽž?", "are_you_sure_to_do_this": "āŽ‡āŽ¤ā¯ˆ āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", + "array_field_not_fully_supported": "āŽĩāŽ°āŽŋāŽšā¯ˆ āŽĒā¯āŽ˛āŽ™ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ•ā¯ˆāŽŽā¯āŽąā¯ˆāŽ¯āŽžāŽ• āŽšāŽžāŽ¤ā¯ŠāŽĒā¯ŠāŽ•ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽŽā¯ āŽ¤ā¯‡āŽĩ❈", + "asset_action_delete_err_read_only": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯ˆ) āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽŸāŽŋāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", + "asset_action_share_err_offline": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒāŽŋāŽ˛ā¯āŽ˛āŽžāŽ¤ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯ˆ) āŽĒā¯†āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "asset_added_to_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "asset_adding_to_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯â€Ļ", "asset_created": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "asset_description_updated": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽŽā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", + "asset_filename_is_offline": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ {filename} āŽ†āŽƒāŽĒā¯āŽ˛ā¯ˆāŽŠāŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", + "asset_has_unassigned_faces": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ’āŽ¤ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽŽā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", "asset_hashing": "āŽāŽšāŽŋāŽ™ā¯â€Ļ", "asset_list_group_by_sub_title": "āŽ•ā¯āŽ´ā¯", + "asset_list_layout_settings_dynamic_layout_title": "āŽŽāŽžāŽąā¯āŽŽā¯ āŽ¤āŽŗāŽĩāŽŽā¯ˆāŽĒā¯āŽĒ❁", + "asset_list_layout_settings_group_automatically": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ", "asset_list_layout_settings_group_by": "āŽŽā¯‚āŽ˛āŽŽā¯ āŽ•ā¯āŽ´ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", "asset_list_layout_settings_group_by_month_day": "āŽŽāŽžāŽ¤āŽŽā¯ + āŽ¨āŽžāŽŗā¯", "asset_list_layout_sub_title": "āŽŽāŽŠā¯ˆāŽ¯āŽŽā¯ˆāŽĩ❁", @@ -550,26 +577,36 @@ "asset_not_found_on_icloud": "iCloud āŽ‡āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ. iCloud āŽ‡āŽ˛ā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗ āŽŽā¯‹āŽšāŽŽāŽžāŽŠ āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ•āŽžāŽ°āŽŖāŽŽāŽžāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ…āŽŖā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤āŽ¤āŽžāŽ• āŽ‡āŽ°ā¯āŽ•ā¯āŽ•āŽ˛āŽžāŽŽā¯", "asset_offline": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ†āŽƒāŽĒā¯āŽ˛ā¯ˆāŽŠāŽŋāŽ˛ā¯", "asset_offline_description": "āŽ‡āŽ¨ā¯āŽ¤ āŽĩā¯†āŽŗāŽŋāŽĒā¯āŽĒā¯āŽą āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ‡āŽŠāŽŋ āŽĩāŽŸā¯āŽŸāŽŋāŽ˛ā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ. āŽ‰āŽ¤āŽĩāŽŋāŽ•ā¯āŽ•ā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽ¨āŽŋāŽ°ā¯āŽĩāŽžāŽ•āŽŋāŽ¯ā¯ˆ āŽ¤ā¯ŠāŽŸāŽ°ā¯āŽĒ❁ āŽ•ā¯ŠāŽŗā¯āŽŗāŽĩā¯āŽŽā¯.", + "asset_restored_successfully": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "asset_skipped": "āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "asset_skipped_in_trash": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽŋāŽ˛ā¯", + "asset_trashed": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ•ā¯āŽĒā¯āŽĒ❈", "asset_troubleshoot": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¤āŽ˛ā¯", "asset_uploaded": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "asset_uploading": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽ¤āŽ˛ā¯â€Ļ", "asset_viewer_settings_subtitle": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•ā¯‡āŽ˛āŽ°āŽŋ āŽĒāŽžāŽ°ā¯āŽĩā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "asset_viewer_settings_title": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽžāŽ°ā¯āŽĩā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯", "assets": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", + "assets_added_count": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", "assets_added_to_album_count": "āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒāŽŋāŽ˛ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_added_to_albums_count": "Added {assetTotal, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} to {albumTotal, plural, one {# āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒ❁} other {# āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒā¯āŽ•āŽŗā¯}}āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_cannot_be_added_to_album_count": "{count, plural, one {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽšā¯†āŽ°ā¯āŽ•ā¯‡āŽŸā¯āŽŸāŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} āŽŽāŽ¨ā¯āŽ¤āŽšā¯ āŽšā¯†āŽ°ā¯āŽ•ā¯‡āŽŸā¯āŽŸāŽŋāŽ˛ā¯āŽŽā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "assets_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", + "assets_deleted_permanently": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "assets_deleted_permanently_from_server": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "assets_downloaded_failed": "{count, plural, one {# āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ - {error} āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯āŽŸā¯ˆāŽ¨ā¯āŽ¤āŽ¤ā¯} other {# āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ - {error} āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯āŽŸā¯ˆāŽ¨ā¯āŽ¤āŽŠ}}", + "assets_downloaded_successfully": "{count, plural, one {# āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ•āŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯} other {# āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ•āŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯}}", "assets_moved_to_trash_count": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ•ā¯āŽ•ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_permanently_deleted_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_removed_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "assets_removed_permanently_from_device": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_restore_confirmation": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸā¯āŽŸāŽŋāŽ•āŽŗā¯ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž? āŽ‡āŽ¨ā¯āŽ¤ āŽšā¯†āŽ¯āŽ˛ā¯ˆ āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯! āŽŽāŽ¨ā¯āŽ¤āŽĩā¯ŠāŽ°ā¯ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒāŽŋāŽ˛ā¯āŽ˛āŽžāŽ¤ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ‡āŽ¨ā¯āŽ¤ āŽĩāŽ´āŽŋāŽ¯āŽŋāŽ˛ā¯ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯ āŽŽāŽŠā¯āŽĒāŽ¤ā¯ˆ āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽŋāŽ˛ā¯ āŽ•ā¯ŠāŽŗā¯āŽ•.", "assets_restored_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "assets_restored_successfully": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "assets_trashed": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽ•ā¯āŽĒā¯āŽĒ❈", "assets_trashed_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽžāŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "assets_trashed_from_server": "{count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯) āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸā¯āŽŸāŽŋāŽ¯āŽŋāŽ˛ā¯", "assets_were_part_of_album_count": "{count, plural, one {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒāŽŋāŽŠā¯ āŽ’āŽ°ā¯ āŽĒāŽ•ā¯āŽ¤āŽŋ", "assets_were_part_of_albums_count": "{count, plural, one {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ’āŽ°ā¯ āŽĒāŽ•ā¯āŽ¤āŽŋ", "authorized_devices": "āŽ…āŽ™ā¯āŽ•ā¯€āŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯", @@ -578,44 +615,85 @@ "autoplay_slideshow": "āŽ†āŽŸā¯āŽŸā¯‹āŽĒāŽŋāŽŗā¯‡ āŽšā¯āŽ˛ā¯ˆāŽŸā¯āŽšā¯‹", "back": "āŽĒāŽŋāŽŠā¯", "back_close_deselect": "āŽĒāŽŋāŽŠā¯, āŽŽā¯‚āŽŸā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩā¯āŽ¨ā¯€āŽ•ā¯āŽ•āŽŽā¯", + "background_backup_running_error": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ āŽ‡āŽ¯āŽ™ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯, āŽ•ā¯ˆāŽŽā¯āŽąā¯ˆ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "background_location_permission": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽ‡āŽšā¯ˆāŽĩ❁", "background_location_permission_content": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋāŽ¯āŽŋāŽ˛ā¯ āŽ‡āŽ¯āŽ™ā¯āŽ•ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽ¨ā¯†āŽŸā¯āŽĩā¯ŠāŽ°ā¯āŽ•ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽŽāŽžāŽąā¯āŽą, āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ *āŽŽāŽĒā¯āŽĒā¯‹āŽ¤ā¯āŽŽā¯* āŽ¤ā¯āŽ˛ā¯āŽ˛āŽŋāŽ¯āŽŽāŽžāŽŠ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽ…āŽŖā¯āŽ•āŽ˛ā¯ˆāŽ•ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸāŽŋāŽ°ā¯āŽ•ā¯āŽ• āŽĩā¯‡āŽŖā¯āŽŸā¯āŽŽā¯, āŽŽāŽŠāŽĩ❇ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯ āŽĩā¯ˆāŽƒāŽĒ❈ āŽ¨ā¯†āŽŸā¯āŽĩā¯ŠāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽŠā¯ āŽĒā¯†āŽ¯āŽ°ā¯ˆāŽĒā¯ āŽĒāŽŸāŽŋāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯ā¯āŽŽā¯", "background_options": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "backup": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ", + "backup_album_selection_page_albums_device": "āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ ({count})", "backup_album_selection_page_albums_tap": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽ¤āŽŸā¯āŽŸāŽĩā¯āŽŽā¯, āŽĩāŽŋāŽ˛āŽ•ā¯āŽ• āŽ‡āŽ°āŽŸā¯āŽŸā¯ˆ āŽ¤āŽŸā¯āŽŸāŽĩā¯āŽŽā¯", "backup_album_selection_page_assets_scatter": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽĒāŽ˛ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽšāŽŋāŽ¤āŽąāŽ•ā¯āŽ•ā¯‚āŽŸā¯āŽŽā¯. āŽŽāŽŠāŽĩ❇, āŽ•āŽžāŽĒā¯āŽĒ❁ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽĒā¯‹āŽ¤ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽ˛āŽžāŽŽā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽ˛āŽžāŽŽā¯.", "backup_album_selection_page_select_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "backup_album_selection_page_selection_info": "āŽ¤ā¯‡āŽ°ā¯āŽĩ❁ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ", + "backup_album_selection_page_total_assets": "āŽŽā¯ŠāŽ¤ā¯āŽ¤ āŽ¤āŽŠāŽŋāŽ¤ā¯āŽ¤ā¯āŽĩāŽŽāŽžāŽŠ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", "backup_albums_sync": "āŽ•āŽžāŽĒā¯āŽĒ❁ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁", + "backup_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯āŽŽā¯", + "backup_background_service_backup_failed_message": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯. āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽĒā¯āŽĒāŽ¤ā¯â€Ļ", "backup_background_service_complete_notification": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯", + "backup_background_service_connection_failed_message": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤ā¯āŽŸāŽŠā¯ āŽ‡āŽŖā¯ˆāŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯. āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽĒā¯āŽĒāŽ¤ā¯â€Ļ", + "backup_background_service_current_upload_notification": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽ¤āŽ˛ā¯ {filename}", "backup_background_service_default_notification": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯â€Ļ", + "backup_background_service_error_title": "āŽ•āŽžāŽĒā¯āŽĒ❁ āŽĒāŽŋāŽ´ā¯ˆ", "backup_background_service_in_progress_notification": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯â€Ļ", + "backup_background_service_upload_failure_notification": "{filename} āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋ", "backup_controller_page_albums": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "backup_controller_page_background_app_refresh_disabled_content": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽĩāŽ¤āŽąā¯āŽ•āŽžāŽ• āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯> āŽĒā¯ŠāŽ¤ā¯> āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒ❁.", + "backup_controller_page_background_app_refresh_disabled_title": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒ❁ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "backup_controller_page_background_app_refresh_enable_button_text": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯āŽšā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽ™ā¯āŽ•āŽŗā¯", "backup_controller_page_background_battery_info_link": "āŽŽāŽĒā¯āŽĒāŽŸāŽŋ āŽŽāŽŠā¯āŽąā¯ āŽŽāŽŠāŽ•ā¯āŽ•ā¯āŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "backup_controller_page_background_battery_info_message": "āŽšāŽŋāŽąāŽ¨ā¯āŽ¤ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ…āŽŠā¯āŽĒāŽĩāŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯, āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšāŽŋāŽąā¯āŽ•āŽžāŽŠ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆāŽ•ā¯ āŽ•āŽŸā¯āŽŸā¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽŽā¯ āŽŽāŽ¨ā¯āŽ¤ āŽĒā¯‡āŽŸā¯āŽŸāŽ°āŽŋ āŽŽā¯‡āŽŽā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯. \n\nāŽ‡āŽ¤ā¯ āŽšāŽžāŽ¤āŽŠāŽŽā¯ āŽšāŽžāŽ°ā¯āŽ¨ā¯āŽ¤āŽ¤āŽžāŽ• āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽ¤āŽžāŽ˛ā¯, āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠ āŽ‰āŽąā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ¯āŽžāŽŗāŽ°ā¯āŽ•ā¯āŽ•ā¯ āŽ¤ā¯‡āŽĩā¯ˆāŽ¯āŽžāŽŠ āŽ¤āŽ•āŽĩāŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯.", "backup_controller_page_background_battery_info_ok": "āŽšāŽ°āŽŋ", "backup_controller_page_background_battery_info_title": "āŽĒā¯‡āŽŸā¯āŽŸāŽ°āŽŋ āŽŽā¯‡āŽŽā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽ˛ā¯āŽ•āŽŗā¯", + "backup_controller_page_background_charging": "āŽ•āŽŸā¯āŽŸāŽŖāŽŽā¯ āŽĩāŽšā¯‚āŽ˛āŽŋāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡", + "backup_controller_page_background_configure_error": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯‡āŽĩā¯ˆāŽ¯ā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŽā¯ˆāŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯", "backup_controller_page_background_delay": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¤āŽžāŽŽāŽ¤āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ™ā¯āŽ•āŽŗā¯: {duration}", + "backup_controller_page_background_description": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽąāŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤ā¯‡āŽĩā¯ˆāŽ¯āŽŋāŽ˛ā¯āŽ˛āŽžāŽŽāŽ˛ā¯ āŽŽāŽ¨ā¯āŽ¤ āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤āŽžāŽŠāŽžāŽ• āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ• āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯‡āŽĩā¯ˆāŽ¯ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_background_is_off": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", + "backup_controller_page_background_is_on": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", + "backup_controller_page_background_turn_off": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯‡āŽĩā¯ˆāŽ¯ā¯ˆ āŽ…āŽŖā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_background_turn_on": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯‡āŽĩā¯ˆāŽ¯ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_background_wifi": "āŽĩā¯ˆāŽƒāŽĒ❈ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡", "backup_controller_page_backup": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ", "backup_controller_page_backup_selected": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯: ", "backup_controller_page_backup_sub": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_created": "āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯: {date}", + "backup_controller_page_desc_backup": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽąāŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ¤āŽžāŽŠāŽžāŽ• āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", "backup_controller_page_excluded": "āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯: ", + "backup_controller_page_failed": "āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯ā¯āŽąā¯āŽąāŽ¤ā¯ ({count})", + "backup_controller_page_filename": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒā¯†āŽ¯āŽ°ā¯: {filename} [{size}]", + "backup_controller_page_id": "āŽāŽŸāŽŋ: {id}", + "backup_controller_page_info": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ", "backup_controller_page_none_selected": "āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "backup_controller_page_remainder": "āŽŽā¯€āŽ¤āŽŽā¯āŽŗā¯āŽŗ", "backup_controller_page_remainder_sub": "āŽ¤ā¯‡āŽ°ā¯āŽĩāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ• āŽŽā¯€āŽ¤āŽŽā¯āŽŗā¯āŽŗ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", "backup_controller_page_server_storage": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❁", + "backup_controller_page_start_backup": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_status_off": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", + "backup_controller_page_status_on": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", "backup_controller_page_storage_format": "{total} āŽ‡āŽ˛ā¯ {used} āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "backup_controller_page_to_backup": "āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸ āŽĩā¯‡āŽŖā¯āŽŸāŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "backup_controller_page_total_sub": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¤āŽŠāŽŋāŽ¤ā¯āŽ¤ā¯āŽĩāŽŽāŽžāŽŠ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", + "backup_controller_page_turn_off": "āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽ…āŽŖā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_turn_on": "āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_controller_page_uploading_file_info": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ¤āŽ•āŽĩāŽ˛ā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽ•āŽŋāŽąāŽ¤ā¯", + "backup_err_only_album": "āŽ’āŽ°ā¯‡ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ•āŽąā¯āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "backup_error_sync_failed": "āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯ā¯āŽąā¯āŽąāŽ¤ā¯. āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯ā¯ˆ āŽšā¯†āŽ¯āŽ˛āŽžāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯.", "backup_info_card_assets": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", + "backup_manual_cancelled": "āŽ°āŽ¤ā¯āŽ¤ā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "backup_manual_in_progress": "āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĩā¯āŽŽā¯. āŽšāŽŋāŽąāŽŋāŽ¤ā¯ āŽ¨ā¯‡āŽ°āŽŽā¯ āŽ•āŽ´āŽŋāŽ¤ā¯āŽ¤ā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_manual_success": "āŽšā¯†āŽ¯ā¯", + "backup_manual_title": "āŽ¨āŽŋāŽ˛ā¯ˆ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽŽā¯ āŽ¨āŽŋāŽ˛ā¯ˆ", "backup_options": "āŽ•āŽžāŽĒā¯āŽĒ❁ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "backup_options_page_title": "āŽ•āŽžāŽĒā¯āŽĒ❁ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "backup_setting_subtitle": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽŽā¯āŽŠā¯āŽĒā¯āŽą āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "backup_settings_subtitle": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "backup_upload_details_page_more_details": "āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ¤āŽŸā¯āŽŸāŽĩā¯āŽŽā¯", "backward": "āŽĒāŽŋāŽŠā¯āŽŠā¯‹āŽ•ā¯āŽ•ā¯", "biometric_auth_enabled": "āŽĒāŽ¯ā¯‹āŽŽā¯†āŽŸā¯āŽ°āŽŋāŽ•ā¯ āŽāŽąā¯āŽĒ❁ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "biometric_locked_out": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽĒāŽ¯ā¯‹āŽŽā¯†āŽŸā¯āŽ°āŽŋāŽ•ā¯ āŽ…āŽ™ā¯āŽ•ā¯€āŽ•āŽžāŽ°āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗā¯", "biometric_no_options": "āŽĒāŽ¯ā¯‹āŽŽā¯†āŽŸā¯āŽ°āŽŋāŽ•ā¯ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "biometric_not_available": "āŽ‡āŽ¨ā¯āŽ¤ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒāŽ¯ā¯‹āŽŽā¯†āŽŸā¯āŽ°āŽŋāŽ•ā¯ āŽāŽąā¯āŽĒ❁ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "birthdate_saved": "āŽĒāŽŋāŽąāŽ¨ā¯āŽ¤ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "birthdate_set_description": "āŽ’āŽ°ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽĒā¯‹āŽ¤ā¯ āŽ‡āŽ¨ā¯āŽ¤ āŽ¨āŽĒāŽ°āŽŋāŽŠā¯ āŽĩāŽ¯āŽ¤ā¯ˆāŽ•ā¯ āŽ•āŽŖāŽ•ā¯āŽ•āŽŋāŽŸ āŽĒāŽŋāŽąāŽ¨ā¯āŽ¤ āŽ¤ā¯‡āŽ¤āŽŋ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽąāŽ¤ā¯.", "blurred_background": "āŽŽāŽ™ā¯āŽ•āŽ˛āŽžāŽŠ āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ", "bugs_and_feature_requests": "āŽĒāŽŋāŽ´ā¯ˆāŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ…āŽŽā¯āŽš āŽ•ā¯‹āŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆāŽ•āŽŗā¯", @@ -625,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• {count, plural, one {# āŽ¨āŽ•āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽ¨āŽ•āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž? āŽ‡āŽ¤ā¯ āŽŽāŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ•āŽžāŽŽāŽ˛ā¯ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨āŽ•āŽ˛ā¯ āŽ•ā¯āŽ´ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯€āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯.", "bulk_trash_duplicates_confirmation": "āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽŽā¯ŠāŽ¤ā¯āŽ¤āŽŽāŽžāŽ•āŽ•ā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽĒā¯‹āŽŸ āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž {count, plural, one {# āŽ¨āŽ•āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽ¨āŽ•āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}? āŽ‡āŽ¤ā¯ āŽ’āŽĩā¯āŽĩā¯ŠāŽ°ā¯ āŽ•ā¯āŽ´ā¯āŽĩāŽŋāŽŠā¯ āŽŽāŽŋāŽ•āŽĒā¯āŽĒā¯†āŽ°āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯, āŽŽāŽąā¯āŽą āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽĒā¯‹āŽŸā¯āŽŽā¯.", "buy": "āŽ‡āŽŽā¯āŽŽāŽŋāŽ¯ā¯ˆ āŽĩāŽžāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", + "cache_settings_clear_cache_button": "āŽ¤ā¯†āŽŗāŽŋāŽĩāŽžāŽŠ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❁", + "cache_settings_clear_cache_button_title": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❈ āŽ…āŽ´āŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯. āŽ•ā¯‡āŽšā¯ āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽ•āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŽā¯ āŽĩāŽ°ā¯ˆ āŽ‡āŽ¤ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ¤āŽŋāŽąāŽŠā¯ˆ āŽ•āŽŖāŽŋāŽšāŽŽāŽžāŽ• āŽĒāŽžāŽ¤āŽŋāŽ•ā¯āŽ•ā¯āŽŽā¯.", + "cache_settings_duplicated_assets_clear_button": "āŽ¤ā¯†āŽŗāŽŋāŽĩāŽžāŽŠ", + "cache_settings_duplicated_assets_subtitle": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽžāŽ˛ā¯ āŽĒāŽŸā¯āŽŸāŽŋāŽ¯āŽ˛āŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĒā¯āŽąāŽ•ā¯āŽ•āŽŖāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", + "cache_settings_duplicated_assets_title": "āŽ¨āŽ•āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ ({count})", + "cache_settings_statistics_album": "āŽ¨ā¯‚āŽ˛āŽ• āŽšāŽŋāŽąā¯ āŽ‰āŽ°ā¯āŽĩāŽ™ā¯āŽ•āŽŗā¯", + "cache_settings_statistics_full": "āŽŽā¯āŽ´ā¯ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "cache_settings_statistics_shared": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽšāŽŋāŽąā¯āŽĒāŽŸāŽŽā¯", + "cache_settings_statistics_thumbnail": "āŽšāŽŋāŽąā¯ āŽ‰āŽ°ā¯āŽĩāŽ™ā¯āŽ•āŽŗā¯", + "cache_settings_statistics_title": "āŽ•ā¯‡āŽšā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯", + "cache_settings_subtitle": "āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽŽā¯ŠāŽĒā¯ˆāŽ˛ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❁ āŽ¨āŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯ˆ āŽ•āŽŸā¯āŽŸā¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", + "cache_settings_tile_subtitle": "āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒāŽ• āŽ¨āŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯ˆ āŽ•āŽŸā¯āŽŸā¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", + "cache_settings_tile_title": "āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❁", + "cache_settings_title": "āŽ¤ā¯‡āŽ•ā¯āŽ•āŽ• āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", "camera": "āŽ•ā¯‡āŽŽāŽ°āŽž", "camera_brand": "āŽ•ā¯‡āŽŽāŽ°āŽž āŽšā¯‚āŽŸā¯āŽŸā¯āŽ•ā¯āŽ•ā¯āŽąāŽŋ", "camera_model": "āŽ•ā¯‡āŽŽāŽ°āŽž āŽŽāŽžāŽ¤āŽŋāŽ°āŽŋ", @@ -640,6 +732,7 @@ "change_date": "āŽ¤ā¯‡āŽ¤āŽŋāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "change_description": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "change_display_order": "āŽ•āŽžāŽŸā¯āŽšāŽŋ āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "change_expiration_time": "āŽ•āŽžāŽ˛āŽžāŽĩāŽ¤āŽŋ āŽ¨ā¯‡āŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "change_location": "āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "change_name": "āŽĒā¯†āŽ¯āŽ°ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "change_name_successfully": "āŽĒā¯†āŽ¯āŽ°ā¯ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽŽāŽžāŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -653,10 +746,15 @@ "change_password_form_password_mismatch": "āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽąā¯āŽ•āŽŗā¯ āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "change_password_form_reenter_new_password": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", "change_pin_code": "āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "change_trigger": "āŽ¤ā¯‚āŽŖā¯āŽŸā¯āŽ¤āŽ˛ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "change_trigger_prompt": "āŽ¤ā¯‚āŽŖā¯āŽŸā¯āŽ¤āŽ˛ā¯ˆ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽŽāŽžāŽąā¯āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž? āŽ‡āŽ¤ā¯ āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ‰āŽŗā¯āŽŗ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽžāŽŠā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ…āŽ•āŽąā¯āŽąā¯āŽŽā¯.", "change_your_password": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "changed_visibility_successfully": "āŽ¤ā¯†āŽ°āŽŋāŽĩā¯āŽ¨āŽŋāŽ˛ā¯ˆ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽŽāŽžāŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "charging": "āŽšāŽžāŽ°ā¯āŽšāŽŋāŽ™ā¯", "charging_requirement_mobile_backup": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ•ā¯āŽ•ā¯ āŽšāŽžāŽ¤āŽŠāŽŽā¯ āŽšāŽžāŽ°ā¯āŽšā¯ āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩā¯‡āŽŖā¯āŽŸā¯āŽŽā¯", + "check_corrupt_asset_backup": "āŽŠāŽ´āŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ•āŽŗā¯ˆ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "check_corrupt_asset_backup_button": "āŽ•āŽžāŽšā¯‹āŽ˛ā¯ˆ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", + "check_corrupt_asset_backup_description": "āŽ‡āŽ¨ā¯āŽ¤ āŽ•āŽžāŽšā¯‹āŽ˛ā¯ˆāŽ¯ā¯ˆ āŽĩā¯ˆāŽƒāŽĒ❈ āŽŽā¯€āŽ¤ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯, āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯āŽŽā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽĩā¯āŽŸāŽŠā¯. āŽšā¯†āŽ¯āŽ˛ā¯āŽŽā¯āŽąā¯ˆ āŽšāŽŋāŽ˛ āŽ¨āŽŋāŽŽāŽŋāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽ†āŽ•āŽ˛āŽžāŽŽā¯.", "check_logs": "āŽĒāŽ¤āŽŋāŽĩā¯āŽ•āŽŗā¯ˆ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "checksum": "āŽšā¯†āŽ•ā¯āŽšāŽŽā¯", "choose_matching_people_to_merge": "āŽ’āŽŠā¯āŽąāŽŋāŽŖā¯ˆāŽ•ā¯āŽ• āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤āŽ•ā¯āŽ•ā¯‚āŽŸāŽŋāŽ¯ āŽ¨āŽĒāŽ°ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩā¯āŽšā¯†āŽ¯ā¯āŽ•", @@ -679,6 +777,8 @@ "clear_file_cache": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ¤āŽąā¯āŽ•āŽžāŽ˛āŽŋāŽ• āŽšā¯‡āŽŽāŽŋāŽĒā¯āŽĒ❈ āŽ…āŽ´āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "clear_message": "āŽ¤ā¯†āŽŗāŽŋāŽĩāŽžāŽŠ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ", "clear_value": "āŽ¤ā¯†āŽŗāŽŋāŽĩāŽžāŽŠ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒ❁", + "client_cert_dialog_msg_confirm": "āŽšāŽ°āŽŋ", + "client_cert_enter_password": "āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", "client_cert_import": "āŽ‡āŽąāŽ•ā¯āŽ•ā¯āŽŽāŽ¤āŽŋ", "client_cert_import_success_msg": "āŽ•āŽŋāŽŗā¯ˆāŽ¯āŽŠā¯āŽŸā¯ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽ‡āŽąāŽ•ā¯āŽ•ā¯āŽŽāŽ¤āŽŋ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽąāŽ¤ā¯", "client_cert_invalid_msg": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ¤āŽĩāŽąāŽžāŽŠ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯", @@ -687,10 +787,12 @@ "client_cert_remove_msg": "āŽ•āŽŋāŽŗā¯ˆāŽ¯āŽŠā¯āŽŸā¯ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "client_cert_subtitle": "PKCS12 (.p12, .pfx) āŽĩāŽŸāŽŋāŽĩāŽŽā¯ˆāŽĒā¯āŽĒ❈ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯. āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽŽā¯āŽŠā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽ‡āŽąāŽ•ā¯āŽ•ā¯āŽŽāŽ¤āŽŋ/āŽ…āŽ•āŽąā¯āŽąā¯āŽ¤āŽ˛ā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯", "client_cert_title": "SSL āŽ•āŽŋāŽŗā¯ˆāŽ¯āŽŠā¯āŽŸā¯ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ [āŽĒāŽ°āŽŋāŽšā¯‹āŽ¤āŽŠā¯ˆ]", + "clockwise": "Locklowsy", "close": "āŽŽā¯‚āŽŸā¯", "collapse": "āŽšāŽ°āŽŋāŽĩ❁", "collapse_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽšāŽ°āŽ•ā¯āŽ•ā¯", "color": "āŽ¨āŽŋāŽąāŽŽā¯", + "color_theme": "āŽĩāŽŖā¯āŽŖ āŽ•āŽ°ā¯āŽĒā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯", "command": "āŽ•āŽŸā¯āŽŸāŽŗā¯ˆ", "command_palette_prompt": "āŽĒāŽ•ā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯, āŽšā¯†āŽ¯āŽ˛ā¯āŽ•āŽŗā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ•āŽŸā¯āŽŸāŽŗā¯ˆāŽ•āŽŗā¯ˆ āŽĩāŽŋāŽ°ā¯ˆāŽĩāŽžāŽ•āŽ•ā¯ āŽ•āŽŖā¯āŽŸāŽąāŽŋāŽ¯āŽĩā¯āŽŽā¯", "command_palette_to_close": "āŽŽā¯‚āŽŸā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯", @@ -717,13 +819,18 @@ "contain": "āŽ•āŽŸā¯āŽŸā¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "context": "āŽšā¯‚āŽ´āŽ˛ā¯", "continue": "āŽ¤ā¯ŠāŽŸāŽ°āŽĩā¯āŽŽā¯", + "control_bottom_app_bar_create_new_album": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "control_bottom_app_bar_delete_from_immich": "āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "control_bottom_app_bar_delete_from_local": "āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "control_bottom_app_bar_edit_location": "āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "control_bottom_app_bar_edit_time": "āŽ¤ā¯‡āŽ¤āŽŋ & āŽ¨ā¯‡āŽ°āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", + "control_bottom_app_bar_share_link": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒāŽ•āŽŋāŽ°āŽĩā¯āŽŽā¯", + "control_bottom_app_bar_share_to": "āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŗā¯āŽŗā¯āŽ™ā¯āŽ•āŽŗā¯", "control_bottom_app_bar_trash_from_immich": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "copied_image_to_clipboard": "āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆāŽĒā¯āŽĒāŽ˛āŽ•ā¯ˆāŽ•ā¯āŽ•ā¯ āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ¤ā¯āŽ¤āŽ¤ā¯.", "copied_to_clipboard": "āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆāŽĒā¯āŽĒāŽ˛āŽ•ā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯!", "copy_error": "āŽ¨āŽ•āŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", + "copy_file_path": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒāŽžāŽ¤ā¯ˆāŽ¯ā¯ˆ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "copy_image": "āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "copy_link": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "copy_link_to_clipboard": "āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆāŽĒā¯āŽĒāŽ˛āŽ•ā¯ˆāŽ•ā¯āŽ•ā¯ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -756,23 +863,29 @@ "created_at": "āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "creating_linked_albums": "āŽ‡āŽŖā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯ ...", "crop": "āŽĒāŽ¯āŽŋāŽ°ā¯", + "crop_aspect_ratio_fixed": "āŽšāŽ°āŽŋ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "crop_aspect_ratio_free": "āŽ‡āŽ˛āŽĩāŽšāŽŽā¯", "crop_aspect_ratio_original": "āŽ…āŽšāŽ˛ā¯", + "curated_object_page_title": "āŽĩāŽŋāŽšāŽ¯āŽ™ā¯āŽ•āŽŗā¯", "current_device": "āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽšāŽžāŽ¤āŽŠāŽŽā¯", "current_pin_code": "āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯", "current_server_address": "āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽŽā¯āŽ•āŽĩāŽ°āŽŋ", "custom_date": "āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒ āŽ¤ā¯‡āŽ¤āŽŋ", "custom_locale": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽŽā¯ŠāŽ´āŽŋ", "custom_locale_description": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽŽā¯ŠāŽ´āŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĒāŽŋāŽ°āŽžāŽ¨ā¯āŽ¤āŽŋāŽ¯āŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽ…āŽŸāŽŋāŽĒā¯āŽĒāŽŸā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ¤ā¯‡āŽ¤āŽŋāŽ•āŽŗā¯, āŽ¨ā¯‡āŽ°āŽŽā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽŽāŽŖā¯āŽ•āŽŗā¯ˆ āŽĩāŽŸāŽŋāŽĩāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "custom_url": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ", "cutoff_date_description": "āŽ•āŽŸā¯ˆāŽšāŽŋ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯â€Ļ", "cutoff_day": "{count, plural, one {āŽ¨āŽžāŽŗā¯} other {āŽ¨āŽžāŽŗā¯āŽ•āŽŗā¯}}", "cutoff_year": "{count, plural, one {āŽ†āŽŖā¯āŽŸā¯} other {āŽ†āŽŖā¯āŽŸā¯āŽ•āŽŗā¯}}", + "daily_title_text_date": "E, mmm dd", + "daily_title_text_date_year": "E, mmm dd, yyyy", "dark": "āŽ‡āŽ°ā¯āŽŖā¯āŽŸ", "dark_theme": "āŽ‡āŽ°ā¯āŽŖā¯āŽŸ āŽ•āŽ°ā¯āŽĒā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "date": "āŽ¤ā¯‡āŽ¤āŽŋ", "date_after": "āŽ¤ā¯‡āŽ¤āŽŋ", "date_and_time": "āŽ¤ā¯‡āŽ¤āŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨ā¯‡āŽ°āŽŽā¯", "date_before": "āŽŽā¯āŽŠā¯ āŽ¤ā¯‡āŽ¤āŽŋ", + "date_format": "E, lll d, āŽ’āŽ¯ā¯ â€ĸ h: mm a", "date_of_birth_saved": "āŽĒāŽŋāŽąāŽ¨ā¯āŽ¤ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "date_range": "āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽ°āŽŽā¯āŽĒ❁", "day": "āŽ¨āŽžāŽŗā¯", @@ -786,6 +899,8 @@ "delete_dialog_alert": "āŽ‡āŽ¨ā¯āŽ¤ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯āŽŽā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯āŽŽā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯", "delete_dialog_alert_local": "āŽ‡āŽ¨ā¯āŽ¤ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŽā¯, āŽ†āŽŠāŽžāŽ˛ā¯ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯", "delete_dialog_alert_local_non_backed_up": "āŽšāŽŋāŽ˛ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽĩāŽ°ā¯ˆ āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽ…āŽĩ❈ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŽā¯", + "delete_dialog_alert_remote": "āŽ‡āŽ¨ā¯āŽ¤ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯", + "delete_dialog_ok_force": "āŽŽāŽĒā¯āŽĒāŽŸāŽŋāŽ¯ā¯āŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "delete_dialog_title": "āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "delete_duplicates_confirmation": "āŽ‡āŽ¨ā¯āŽ¤ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "delete_face": "āŽŽā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", @@ -804,16 +919,21 @@ "delete_tag_confirmation_prompt": "{tagName} āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "delete_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "deleted_shared_link": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ¨ā¯€āŽ•ā¯āŽ•āŽŋāŽ¯āŽ¤ā¯", + "deletes_missing_assets": "āŽĩāŽŸā¯āŽŸāŽŋāŽ˛ā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•āŽžāŽŖāŽžāŽŽāŽ˛ā¯ āŽĒā¯‹āŽŠ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "description": "āŽĩāŽŋāŽĩāŽ°āŽŽā¯", + "description_input_hint_text": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ ...", + "description_input_submit_error": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽĒāŽ¤āŽŋāŽĩā¯ˆāŽšā¯ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "deselect_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩ❁ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", "details": "āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯", "direction": "āŽ¤āŽŋāŽšā¯ˆ", "disable": "āŽŽā¯āŽŸāŽ•ā¯āŽ•ā¯", "disabled": "āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "disallow_edits": "āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "discord": "āŽŽā¯āŽ°āŽŖā¯āŽĒāŽžāŽŸā¯", "discover": "āŽ•āŽŖā¯āŽŸā¯āŽĒāŽŋāŽŸāŽŋ", "discovered_devices": "āŽ•āŽŖā¯āŽŸā¯āŽĒāŽŋāŽŸāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯", "dismiss_all_errors": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽĒāŽŋāŽ´ā¯ˆāŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¨āŽŋāŽ°āŽžāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "dismiss_error": "āŽĒāŽŋāŽ´ā¯ˆāŽ¯ā¯ˆ āŽ¨āŽŋāŽ°āŽžāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "display_options": "āŽ•āŽžāŽŸā¯āŽšāŽŋ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "display_order": "āŽ•āŽžāŽŸā¯āŽšāŽŋ āŽĩāŽ°āŽŋāŽšā¯ˆ", "display_original_photos": "āŽ…āŽšāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽĒāŽŋ", @@ -822,9 +942,11 @@ "documentation": "āŽ†āŽĩāŽŖāŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ¤āŽ˛ā¯", "done": "āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯", "download": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯", + "download_action_prompt": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ {count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", "download_canceled": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽ°āŽ¤ā¯āŽ¤ā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "download_complete": "āŽŽā¯āŽ´ā¯āŽŽā¯ˆāŽ¯āŽžāŽŠ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯", "download_enqueue": "Enqueuted āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•", + "download_error": "āŽĒāŽŋāŽ´ā¯ˆāŽ¯ā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "download_failed": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ¯āŽŸā¯ˆāŽ¨ā¯āŽ¤āŽ¤ā¯", "download_finished": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯", "download_include_embedded_motion_videos": "āŽ‰āŽŸā¯āŽĒā¯ŠāŽ¤āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", @@ -834,6 +956,9 @@ "download_paused": "āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽąā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "download_settings": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯", "download_settings_description": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽ¤ā¯ŠāŽŸāŽ°ā¯āŽĒāŽžāŽŠ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "download_started": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽŋāŽ¯āŽ¤ā¯", + "download_sucess": "āŽĩā¯†āŽąā¯āŽąāŽŋāŽ¯ā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "download_sucess_android": "āŽŠāŽŸāŽ•āŽ™ā¯āŽ•āŽŗā¯ DCIM/IMMich āŽ•ā¯āŽ•ā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽŠ", "download_waiting_to_retry": "āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ• āŽ•āŽžāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "downloading": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "downloading_asset_filename": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ {filename}", @@ -841,7 +966,7 @@ "downloading_media": "āŽŠāŽŸāŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "drop_files_to_upload": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽāŽ™ā¯āŽ•ā¯āŽŽā¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽĩāŽŋāŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "duplicates": "āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯", - "duplicates_description": "āŽ’āŽĩā¯āŽĩā¯ŠāŽ°ā¯ āŽ•ā¯āŽ´ā¯āŽĩāŽŋāŽ˛ā¯āŽŽā¯ āŽ‰āŽŗā¯āŽŗ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆ (āŽāŽ¤ā¯‡āŽŠā¯āŽŽā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ˛ā¯), āŽ…āŽŸā¯ˆāŽ¯āŽžāŽŗāŽŽā¯ āŽ•āŽŖā¯āŽŸā¯ āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯.", + "duplicates_description": "āŽ’āŽĩā¯āŽĩā¯ŠāŽ°ā¯ āŽ•ā¯āŽ´ā¯āŽĩā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽāŽ¨ā¯āŽ¤ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗāŽŋāŽ˛ā¯āŽŽā¯ āŽ•ā¯āŽąāŽŋāŽĒā¯āŽĒāŽŋāŽŸā¯āŽĩāŽ¤āŽŠā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ¤ā¯€āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "duration": "āŽ•āŽžāŽ˛āŽŽā¯", "edit": "āŽ¤ā¯†āŽžāŽ•ā¯", "edit_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯", @@ -851,7 +976,9 @@ "edit_date_and_time": "āŽ¤ā¯‡āŽ¤āŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨ā¯‡āŽ°āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "edit_date_and_time_action_prompt": "{count} āŽ¤ā¯‡āŽ¤āŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨ā¯‡āŽ°āŽŽā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "edit_date_and_time_by_offset": "āŽ†āŽƒāŽĒā¯āŽšā¯†āŽŸā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ¤ā¯‡āŽ¤āŽŋāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "edit_date_and_time_by_offset_interval": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽ°āŽŽā¯āŽĒ❁: {from} - {to}", "edit_description": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯", + "edit_description_prompt": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯:", "edit_exclusion_pattern": "āŽĩāŽŋāŽ˛āŽ•ā¯āŽ•ā¯ āŽŽā¯āŽąā¯ˆāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "edit_faces": "āŽŽā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "edit_key": "āŽ¤āŽŋāŽąāŽŠā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯", @@ -866,6 +993,9 @@ "edit_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯", "edit_workflow": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "editor": "āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽŋ", + "editor_close_without_save_prompt": "āŽŽāŽžāŽąā¯āŽąāŽ™ā¯āŽ•āŽŗā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ā¯", + "editor_close_without_save_title": "āŽŽā¯‚āŽŸā¯ āŽ†āŽšāŽŋāŽ°āŽŋāŽ¯āŽ°ā¯?", + "editor_confirm_reset_all_changes": "āŽŽāŽ˛ā¯āŽ˛āŽž āŽŽāŽžāŽąā¯āŽąāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "editor_discard_edits_confirm": "āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°āŽžāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "editor_discard_edits_prompt": "āŽ‰āŽ™ā¯āŽ•āŽŗāŽŋāŽŸāŽŽā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯ āŽ‰āŽŗā¯āŽŗāŽŠ. āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽ…āŽĩāŽąā¯āŽąā¯ˆ āŽ¨āŽŋāŽ°āŽžāŽ•āŽ°āŽŋāŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "editor_discard_edits_title": "āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°āŽžāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĩāŽž?", @@ -894,7 +1024,9 @@ "enter_your_pin_code": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", "enter_your_pin_code_subtitle": "āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽ…āŽŖā¯āŽ• āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", "error": "āŽĒāŽŋāŽ´ā¯ˆ", + "error_change_sort_album": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĩāŽ°āŽŋāŽšā¯ˆ āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯", "error_delete_face": "āŽšā¯ŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽŽā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", + "error_getting_places": "āŽ‡āŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒā¯†āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", "error_loading_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽāŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", "error_loading_image": "āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽāŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", "error_loading_partners": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽ•āŽŗā¯ˆ āŽāŽąā¯āŽąā¯āŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ: {error}", @@ -1032,10 +1164,18 @@ "exif": "āŽŽāŽ•ā¯āŽ¸āŽŋāŽƒāŽĒā¯", "exif_bottom_sheet_description": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ ...", "exif_bottom_sheet_description_error": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ", + "exif_bottom_sheet_details": "āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯", + "exif_bottom_sheet_location": "āŽ‡āŽŸāŽŽā¯", "exif_bottom_sheet_no_description": "āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "exif_bottom_sheet_people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯", + "exif_bottom_sheet_person_add_person": "āŽĒā¯†āŽ¯āŽ°ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "exit_slideshow": "āŽšā¯āŽ˛ā¯ˆāŽŸā¯āŽšā¯‹āŽĩāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽąāŽĩā¯āŽŽā¯", "expand": "āŽĩāŽŋāŽ°āŽŋāŽĩāŽžāŽ•ā¯āŽ•ā¯", "expand_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩāŽŋāŽ°āŽŋāŽĩāŽžāŽ•ā¯āŽ•ā¯āŽ™ā¯āŽ•āŽŗā¯", + "experimental_settings_new_asset_list_subtitle": "āŽĩā¯‡āŽ˛ā¯ˆ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", + "experimental_settings_new_asset_list_title": "āŽšā¯‹āŽ¤āŽŠā¯ˆ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸ āŽ•āŽŸā¯āŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "experimental_settings_subtitle": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯ŠāŽ¨ā¯āŽ¤ āŽ†āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯!", + "experimental_settings_title": "āŽšā¯‹āŽ¤āŽŠā¯ˆ", "expire_after": "āŽĒāŽŋāŽŠā¯āŽŠāŽ°ā¯ āŽ•āŽžāŽ˛āŽžāŽĩāŽ¤āŽŋāŽ¯āŽžāŽ•ā¯āŽ™ā¯āŽ•āŽŗā¯", "expired": "āŽ•āŽžāŽ˛āŽžāŽĩāŽ¤āŽŋāŽ¯āŽžāŽŠ", "expires_date": "{date} āŽ•āŽžāŽ˛āŽžāŽĩāŽ¤āŽŋāŽ¯āŽžāŽ•āŽŋāŽąāŽ¤ā¯", @@ -1060,19 +1200,24 @@ "favorite_action_prompt": "{count} āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩā¯ˆāŽ•āŽŗāŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "favorite_or_unfavorite_photo": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽšāŽžāŽ¤āŽ•āŽŽāŽąā¯āŽą āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯", "favorites": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩ❈", + "favorites_page_no_favorites": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "feature_photo_updated": "āŽ…āŽŽā¯āŽš āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "features": "āŽ¨āŽąā¯āŽĒā¯ŠāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯", + "features_in_development": "āŽĩāŽŗāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯āŽŋāŽ˛ā¯ āŽ¨āŽąā¯āŽĒā¯ŠāŽ°ā¯āŽ¤ā¯āŽ¤āŽ™ā¯āŽ•āŽŗā¯", "features_setting_description": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽ…āŽŽā¯āŽšāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "file_name_or_extension": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒā¯†āŽ¯āŽ°ā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ¨ā¯€āŽŸā¯āŽŸāŽŋāŽĒā¯āŽĒ❁", "file_name_text": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒā¯†āŽ¯āŽ°ā¯", + "file_name_with_value": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĒā¯†āŽ¯āŽ°ā¯: {file_name}", "file_size": "āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽ…āŽŗāŽĩ❁", "filename": "āŽ•ā¯‹āŽĒā¯āŽĒ❁āŽĒā¯āŽĒā¯†āŽ¯āŽ°ā¯", "filetype": "āŽĒā¯ˆāŽ˛ā¯āŽŸā¯ˆāŽĒā¯", "filter": "āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽŋ", + "filter_description": "āŽ‡āŽ˛āŽ•ā¯āŽ•ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸā¯āŽĩāŽ¤āŽąā¯āŽ•āŽžāŽŠ āŽ¨āŽŋāŽĒāŽ¨ā¯āŽ¤āŽŠā¯ˆāŽ•āŽŗā¯", "filter_people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆ āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸāŽĩā¯āŽŽā¯", "filter_places": "āŽ‡āŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸāŽĩā¯āŽŽā¯", "filter_tags": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽąā¯āŽ•āŽŗā¯ˆ āŽĩāŽŸāŽŋāŽ•āŽŸā¯āŽŸāŽŋ", "filters": "āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽžāŽŠā¯āŽ•āŽŗā¯", + "find_them_fast": "āŽ¤ā¯‡āŽŸāŽ˛ā¯āŽŸāŽŠā¯ āŽĒā¯†āŽ¯āŽ°āŽžāŽ˛ā¯ āŽĩā¯‡āŽ•āŽŽāŽžāŽ• āŽ…āŽĩāŽąā¯āŽąā¯ˆāŽ•ā¯ āŽ•āŽŖā¯āŽŸāŽąāŽŋāŽ¯āŽĩā¯āŽŽā¯", "first": "āŽŽā¯āŽ¤āŽ˛ā¯", "fix_incorrect_match": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽĒā¯‹āŽŸā¯āŽŸāŽŋāŽ¯ā¯ˆ āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "folder": "āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆ", @@ -1105,6 +1250,7 @@ "group_owner": "āŽ‰āŽ°āŽŋāŽŽā¯ˆāŽ¯āŽžāŽŗāŽ°āŽžāŽ˛ā¯ āŽ•ā¯āŽ´ā¯", "group_places_by": "āŽ•ā¯āŽ´ā¯ āŽ‡āŽŸāŽ™ā¯āŽ•āŽŗā¯ ...", "group_year": "āŽ†āŽŖā¯āŽŸā¯āŽ•ā¯āŽ•ā¯ āŽ•ā¯āŽ´ā¯", + "haptic_feedback_switch": "āŽ†āŽĒā¯āŽŸāŽŋāŽ•ā¯ āŽĒāŽŋāŽŠā¯āŽŠā¯‚āŽŸā¯āŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "haptic_feedback_title": "āŽ†āŽĒā¯āŽŸāŽŋāŽ•ā¯ āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤ā¯", "has_quota": "āŽ’āŽ¤ā¯āŽ•ā¯āŽ•ā¯€āŽŸā¯ āŽ‰āŽŗā¯āŽŗāŽ¤ā¯", "hash_asset": "āŽ†āŽšā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯", @@ -1125,12 +1271,29 @@ "hide_schema": "āŽ¤āŽŋāŽŸā¯āŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽąā¯ˆ", "hide_text_recognition": "āŽ‰āŽ°ā¯ˆ āŽ…āŽ™ā¯āŽ•ā¯€āŽ•āŽžāŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽąā¯ˆ", "hide_unnamed_people": "āŽĒā¯†āŽ¯āŽ°āŽŋāŽŸāŽĒā¯āŽĒāŽŸāŽžāŽ¤āŽĩāŽ°ā¯āŽ•āŽŗā¯ˆ āŽŽāŽąā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_add_to_album_conflicts": "{album} āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ {added} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ. {failed} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽŠ.", + "home_page_add_to_album_err_local": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_add_to_album_success": "{album} āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ {added} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ.", + "home_page_album_err_partner": "āŽ’āŽ°ā¯ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_archive_err_local": "āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_archive_err_partner": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "home_page_building_timeline": "āŽ•āŽžāŽ˛āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯", + "home_page_delete_err_partner": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_delete_remote_err_local": "āŽ¤ā¯ŠāŽ˛ā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆ āŽ¤ā¯‡āŽ°ā¯āŽĩ❈ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", + "home_page_favorite_err_local": "āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_favorite_err_partner": "āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤ āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤āŽĩāŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", + "home_page_first_time_notice": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽĩāŽ¤ā¯ āŽ‡āŽ¤ā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽ¤āŽ˛ā¯ āŽŽā¯āŽąā¯ˆāŽ¯āŽžāŽ• āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤āŽžāŽ˛ā¯, āŽ¤āŽ¯āŽĩā¯āŽšā¯†āŽ¯ā¯āŽ¤ā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩā¯āŽšā¯†āŽ¯ā¯āŽ•, āŽ‡āŽ¤āŽŠā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ•āŽžāŽ˛āŽĩāŽ°āŽŋāŽšā¯ˆ āŽ…āŽ¤āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩāŽŋāŽ°āŽŋāŽĩ❁āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯ā¯āŽŽā¯", + "home_page_locked_error_local": "āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĒā¯‚āŽŸā¯āŽŸāŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", + "home_page_locked_error_partner": "āŽĒāŽ™ā¯āŽ•ā¯āŽ¤āŽžāŽ°āŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĒā¯‚āŽŸā¯āŽŸāŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "home_page_share_err_local": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❁, āŽšā¯āŽ•āŽŋāŽĒā¯āŽĒāŽŋāŽ™ā¯ āŽĩāŽ´āŽŋāŽ¯āŽžāŽ• āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĒāŽ•āŽŋāŽ° āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", + "home_page_upload_err_limit": "āŽ’āŽ°ā¯ āŽ¨ā¯‡āŽ°āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ…āŽ¤āŽŋāŽ•āŽĒāŽŸā¯āŽšāŽŽā¯ 30 āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽā¯āŽŸāŽŋāŽ¯ā¯āŽŽā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "host": "āŽĩāŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯‹āŽŽā¯āŽĒāŽŋ", "hour": "āŽŽāŽŖāŽŋ", "hours": "āŽŽāŽŖāŽŋ", "id": "āŽāŽŸāŽŋ", "idle": "āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯āŽŋāŽ•ā¯āŽ•āŽŽā¯", + "ignore_icloud_photos": "āŽāŽŽā¯āŽ•āŽŋāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒā¯āŽąāŽ•ā¯āŽ•āŽŖāŽŋ", + "ignore_icloud_photos_description": "āŽāŽŽā¯āŽ•āŽŋāŽ˛ā¯ āŽ‡āŽ˛ā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯āŽĒāŽŸāŽžāŽ¤ā¯", "image": "āŽĒāŽŸāŽŽā¯", "image_alt_text_date": "{isVideo, select, true {āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {āŽĒāŽŸāŽŽā¯}} {date} āŽ…āŽŠā¯āŽąā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "image_alt_text_date_1_person": "{isVideo, select, true {āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {āŽĒāŽŸāŽŽā¯}} {person1} āŽ‰āŽŸāŽŠā¯ {date} āŽ…āŽŠā¯āŽąā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1142,6 +1305,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {āŽĒāŽŸāŽŽā¯}} {city}, {country} āŽ‡āŽ˛ā¯ {person1} āŽŽāŽąā¯āŽąā¯āŽŽā¯ {person2} āŽ‰āŽŸāŽŠā¯ {date} āŽ…āŽŠā¯āŽąā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "image_alt_text_date_place_3_people": "{isVideo, select, true {āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {āŽĒāŽŸāŽŽā¯}} {city}, {country} āŽ‡āŽ˛ā¯ {person1}, {person2}, āŽŽāŽąā¯āŽąā¯āŽŽā¯ {person3} āŽ‰āŽŸāŽŠā¯ {date} āŽ…āŽŠā¯āŽąā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {āŽĒāŽŸāŽŽā¯}} {city}, {country} āŽ‡āŽ˛ā¯ {person1}, {person2}, āŽŽāŽąā¯āŽąā¯āŽŽā¯ {additionalCount, number} āŽĒāŽŋāŽąāŽ°ā¯āŽŸāŽŠā¯ {date} āŽ…āŽŠā¯āŽąā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "image_saved_successfully": "āŽĒāŽŸāŽŽā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "image_viewer_page_state_provider_download_started": "āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽŽā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽŋāŽ¯āŽ¤ā¯", + "image_viewer_page_state_provider_download_success": "āŽĩā¯†āŽąā¯āŽąāŽŋāŽ¯ā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "image_viewer_page_state_provider_share_error": "āŽĒāŽŋāŽ´ā¯ˆ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩ❁", "immich_logo": "āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽ˛ā¯‹āŽ•ā¯‹", "immich_web_interface": "āŽ‡āŽŽā¯āŽ°āŽŋāŽšā¯ āŽĩāŽ˛ā¯ˆ āŽ‡āŽŸā¯ˆāŽŽā¯āŽ•āŽŽā¯", "import_from_json": "āŽšāŽžāŽ¤ā¯ŠāŽĒā¯ŠāŽ•ā¯ āŽ‡āŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ‡āŽąāŽ•ā¯āŽ•ā¯āŽŽāŽ¤āŽŋ", @@ -1166,8 +1333,16 @@ "invalid_date_format": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽŸāŽŋāŽĩāŽŽā¯", "invite_people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆ āŽ…āŽ´ā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "invite_to_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽ…āŽ´ā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "ios_debug_info_fetch_ran_at": "āŽ“āŽŸāŽŋāŽ¯āŽ¤ā¯ {dateTime}", + "ios_debug_info_last_sync_at": "āŽ•āŽŸā¯ˆāŽšāŽŋ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁ {dateTime}", + "ios_debug_info_no_processes_queued": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯†āŽ¯āŽ˛ā¯āŽŽā¯āŽąā¯ˆāŽ•āŽŗā¯ āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "ios_debug_info_no_sync_yet": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁ āŽĩā¯‡āŽ˛ā¯ˆ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽ‡āŽ¯āŽ™ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯†āŽ¯āŽ˛ā¯āŽŽā¯āŽąā¯ˆ āŽĩāŽ°āŽŋāŽšā¯ˆāŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯} other {{count} āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽšā¯†āŽ¯āŽ˛ā¯āŽŽā¯āŽąā¯ˆāŽ•āŽŗā¯ āŽĩāŽ°āŽŋāŽšā¯ˆāŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽŠ}}", + "ios_debug_info_processing_ran_at": "āŽšā¯†āŽ¯āŽ˛āŽžāŽ•ā¯āŽ•āŽŽā¯ {dateTime}", "items_count": "{count, plural, one {# āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋ} other {# āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯}}", "jobs": "āŽĩā¯‡āŽ˛ā¯ˆāŽ•āŽŗā¯", + "json_editor": "āŽšāŽžāŽ¤ā¯ŠāŽĒā¯ŠāŽ•ā¯ āŽ†āŽšāŽŋāŽ°āŽŋāŽ¯āŽ°ā¯", + "json_error": "āŽšāŽžāŽ¤ā¯ŠāŽĒā¯ŠāŽ•ā¯ āŽĒāŽŋāŽ´ā¯ˆ", "keep": "āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯", "keep_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯", "keep_albums_count": "{count} {count, plural, one {āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒ❁} other {āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒā¯āŽ•āŽŗā¯}} āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯", @@ -1199,6 +1374,9 @@ "library": "āŽ¨ā¯‚āŽ˛āŽ•āŽŽā¯", "library_add_folder": "āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯", "library_edit_folder": "āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ā¯", + "library_options": "āŽ¨ā¯‚āŽ˛āŽ• āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "library_page_device_albums": "āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "library_page_new_album": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽŽā¯", "library_page_sort_asset_count": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽŽāŽŖā¯āŽŖāŽŋāŽ•ā¯āŽ•ā¯ˆ", "library_page_sort_created": "āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ¤ā¯‡āŽ¤āŽŋ", "library_page_sort_last_modified": "āŽ•āŽŸā¯ˆāŽšāŽŋāŽ¯āŽžāŽ• āŽŽāŽžāŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1239,9 +1417,11 @@ "login": "āŽĒā¯āŽ•ā¯āŽĒāŽ¤āŽŋāŽĩ❁", "login_disabled": "āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩ❁ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", "login_form_api_exception": "āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽ¤āŽŋāŽĩāŽŋāŽ˛āŽ•ā¯āŽ•ā¯. āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽŽā¯āŽ•āŽĩāŽ°āŽŋ āŽ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ¤ā¯āŽ¤ā¯ āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", + "login_form_back_button_text": "āŽĒāŽŋāŽŠā¯", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http: // your-server-ip: āŽ¤ā¯āŽąā¯ˆāŽŽā¯āŽ•āŽŽā¯", "login_form_endpoint_url": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽŽāŽŖā¯āŽŸā¯āŽĒāŽžāŽ¯āŽŋāŽŖā¯āŽŸā¯ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ", + "login_form_err_http": "Http: // āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ https: // āŽāŽ•ā¯ āŽ•ā¯āŽąāŽŋāŽĒā¯āŽĒāŽŋāŽŸāŽĩā¯āŽŽā¯", "login_form_err_invalid_email": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽŽāŽŋāŽŠā¯āŽŠāŽžā¯āŽšāŽ˛ā¯", "login_form_err_invalid_url": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ", "login_form_err_leading_whitespace": "āŽŽā¯āŽŠā¯āŽŠāŽŖāŽŋ āŽĩāŽŋāŽŸā¯āŽšā¯āŽĒā¯‡āŽšā¯", @@ -1251,7 +1431,8 @@ "login_form_failed_login": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ, āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• URL, āŽŽāŽŋāŽŠā¯āŽŠāŽžā¯āŽšāŽ˛ā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "login_form_handshake_exception": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤ā¯āŽŸāŽŠā¯ āŽāŽŖā¯āŽŸā¯āŽšā¯‡āŽ•ā¯ āŽĩāŽŋāŽ¤āŽŋāŽĩāŽŋāŽ˛āŽ•ā¯āŽ•ā¯ āŽ‡āŽ°ā¯āŽ¨ā¯āŽ¤āŽ¤ā¯. āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ¤āŽŠā¯āŽĩāŽ¯ āŽ•ā¯ˆāŽ¯ā¯ŠāŽĒā¯āŽĒāŽŽāŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗā¯ āŽŽāŽŠā¯āŽąāŽžāŽ˛ā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽ¤āŽŠā¯āŽĩāŽ¯ āŽ•ā¯ˆāŽ¯ā¯ŠāŽĒā¯āŽĒāŽŽāŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽžāŽŠā¯āŽąāŽŋāŽ¤āŽ´ā¯ āŽ†āŽ¤āŽ°āŽĩ❈ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", "login_form_password_hint": "āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯", - "login_form_server_empty": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽŽā¯āŽ•āŽĩāŽ°āŽŋ-āŽ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯.", + "login_form_save_login": "āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽ¨ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯", + "login_form_server_empty": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽŽā¯āŽ•āŽĩāŽ°āŽŋ āŽ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯.", "login_form_server_error": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤ā¯āŽŸāŽŠā¯ āŽ‡āŽŖā¯ˆāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", "login_has_been_disabled": "āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩ❁ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯.", "login_password_changed_error": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆāŽĒā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒāŽ¤āŽŋāŽ˛ā¯ āŽĒāŽŋāŽ´ā¯ˆ āŽāŽąā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1268,6 +1449,7 @@ "maintenance_action_restore": "āŽ¤āŽ°āŽĩā¯āŽ¤ā¯āŽ¤āŽŗāŽ¤ā¯āŽ¤ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "maintenance_description": "āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯ āŽĒāŽ°āŽžāŽŽāŽ°āŽŋāŽĒā¯āŽĒ❁ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽĩā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯.", "maintenance_end": "āŽĒāŽ°āŽžāŽŽāŽ°āŽŋāŽĒā¯āŽĒ❁ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽŽā¯āŽŸāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "maintenance_end_error": "āŽĒāŽ°āŽžāŽŽāŽ°āŽŋāŽĒā¯āŽĒ❁ āŽŽā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽŽā¯āŽŸāŽŋāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", "maintenance_logged_in_as": "āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ {user} āŽ†āŽ• āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽ¨ā¯āŽ¤ā¯āŽŗā¯āŽŗā¯€āŽ°ā¯āŽ•āŽŗā¯", "maintenance_restore_from_backup": "āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆ", "maintenance_restore_library": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯‚āŽ˛āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1293,6 +1475,7 @@ "manage_media_access_settings": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽą", "manage_media_access_subtitle": "āŽ‡āŽŽā¯āŽŽāŽŋāŽ•ā¯ āŽšā¯†āŽ¯āŽ˛āŽŋ āŽŽā¯€āŽŸāŽŋāŽ¯āŽž āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", "manage_media_access_title": "āŽŽā¯€āŽŸāŽŋāŽ¯āŽž āŽŽā¯‡āŽ˛āŽžāŽŖā¯āŽŽā¯ˆ āŽ…āŽŖā¯āŽ•āŽ˛ā¯", + "manage_shared_links": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "manage_sharing_with_partners": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩ❈ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "manage_the_app_settings": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "manage_your_account": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŖāŽ•ā¯āŽ•ā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1300,10 +1483,13 @@ "manage_your_devices": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽ¨ā¯āŽ¤ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "manage_your_oauth_connection": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ OAuth āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "map": "āŽĩāŽ°ā¯ˆāŽĒāŽŸāŽŽā¯", + "map_assets_in_bounds": "{count, plural, =0 {āŽ‡āŽ¨ā¯āŽ¤āŽĒā¯ āŽĒāŽ•ā¯āŽ¤āŽŋāŽ¯āŽŋāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ} one {# āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯} other {# āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯}}", "map_cannot_get_user_location": "āŽĒāŽ¯āŽŠāŽ°āŽŋāŽŠā¯ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒā¯†āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", + "map_location_dialog_yes": "āŽ†āŽŽā¯", "map_location_picker_page_use_location": "āŽ‡āŽ¨ā¯āŽ¤ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "map_location_service_disabled_content": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽĒāŽŖāŽŋ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸ āŽĩā¯‡āŽŖā¯āŽŸā¯āŽŽā¯. āŽ‡āŽĒā¯āŽĒā¯‹āŽ¤ā¯ āŽ…āŽ¤ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "map_location_service_disabled_title": "āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽĒāŽŖāŽŋ āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "map_marker_for_images": "{city}, {country}", "map_marker_with_image": "āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯āŽŸāŽŠā¯ āŽĩāŽ°ā¯ˆāŽĒāŽŸ āŽŽāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽ°ā¯", "map_no_location_permission_content": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¤āŽąā¯āŽĒā¯‹āŽ¤ā¯ˆāŽ¯ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽ‡āŽšā¯ˆāŽĩ❁ āŽ¤ā¯‡āŽĩ❈. āŽ‡āŽĒā¯āŽĒā¯‹āŽ¤ā¯ āŽ…āŽ¤ā¯ˆ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "map_no_location_permission_title": "āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸ āŽ‡āŽšā¯ˆāŽĩ❁ āŽŽāŽąā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1313,11 +1499,14 @@ "map_settings_date_range_option_days": "āŽ•āŽŸāŽ¨ā¯āŽ¤ {days} āŽ¨āŽžāŽŸā¯āŽ•āŽŗā¯", "map_settings_date_range_option_year": "āŽ•āŽŸāŽ¨ā¯āŽ¤ āŽ†āŽŖā¯āŽŸā¯", "map_settings_date_range_option_years": "āŽ•āŽŸāŽ¨ā¯āŽ¤ {years} āŽ†āŽŖā¯āŽŸā¯āŽ•āŽŗā¯", + "map_settings_dialog_title": "āŽĩāŽ°ā¯ˆāŽĒāŽŸ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", "map_settings_include_show_archived": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽĩāŽ°ā¯", "map_settings_include_show_partners": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "map_settings_only_show_favorites": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽ¤ā¯ˆ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "map_settings_theme_settings": "āŽĩāŽ°ā¯ˆāŽĒāŽŸ āŽ•āŽ°ā¯āŽĒā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯", + "map_zoom_to_see_photos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖ āŽĒā¯†āŽ°āŽŋāŽ¤āŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "mark_all_as_read": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĒāŽŸāŽŋ āŽ•ā¯āŽąāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "mark_as_read": "āŽĒāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĒāŽŸāŽŋ āŽ•ā¯āŽąāŽŋ", "marked_all_as_read": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩāŽžāŽšāŽŋāŽĒā¯āŽĒāŽ¤āŽžāŽ• āŽ•ā¯āŽąāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", "matches": "āŽĒā¯‹āŽŸā¯āŽŸāŽŋāŽ•āŽŗā¯", "matching_assets": "āŽĒā¯ŠāŽ°ā¯āŽ¨ā¯āŽ¤ā¯āŽŽā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", @@ -1340,20 +1529,29 @@ "minimize": "āŽ•ā¯āŽąā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "minute": "āŽ¨āŽŋāŽŽāŽŋāŽŸāŽ™ā¯āŽ•āŽŗā¯", "minutes": "āŽ¨āŽŋāŽŽāŽŋāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "mirror_horizontal": "āŽ•āŽŋāŽŸā¯ˆāŽŽāŽŸā¯āŽŸ", + "mirror_vertical": "āŽšā¯†āŽ™ā¯āŽ•ā¯āŽ¤ā¯āŽ¤ā¯", "missing": "āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "mobile_app": "āŽŽā¯ŠāŽĒā¯ˆāŽ˛ā¯ āŽ†āŽĒā¯", "mobile_app_download_onboarding_note": "āŽĒāŽŋāŽŠā¯āŽĩāŽ°ā¯āŽŽā¯ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽŋ āŽ¤ā¯āŽŖā¯ˆ āŽŽā¯ŠāŽĒā¯ˆāŽ˛ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆāŽĒā¯ āŽĒāŽ¤āŽŋāŽĩāŽŋāŽąāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "model": "āŽŽāŽžāŽ¤āŽŋāŽ°āŽŋāŽ¯ā¯āŽ°ā¯", "month": "āŽŽāŽžāŽ¤āŽŽā¯", + "monthly_title_text_date_format": "Mmmm āŽ’āŽ¯ā¯", "more": "āŽŽā¯‡āŽ˛ā¯āŽŽā¯", "move": "āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", + "move_down": "āŽ•ā¯€āŽ´ā¯‡ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "move_off_locked_folder": "āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽąāŽĩā¯āŽŽā¯", "move_to": "āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤ā¯", "move_to_device_trash": "āŽšāŽžāŽ¤āŽŠāŽ¤ā¯āŽ¤āŽŋāŽŠā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "move_to_lock_folder_action_prompt": "āŽĒā¯‚āŽŸā¯āŽŸāŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛ā¯ {count} āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "move_to_locked_folder": "āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽ™ā¯āŽ•āŽŗā¯", "move_to_locked_folder_confirmation": "āŽ‡āŽ¨ā¯āŽ¤ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯āŽŽā¯ āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŽā¯, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯ā¯āŽŽā¯", + "move_up": "āŽŽā¯‡āŽ˛ā¯‡ āŽšā¯†āŽ˛ā¯āŽ˛āŽĩā¯āŽŽā¯", + "moved_to_archive": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "moved_to_library": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽ¨ā¯‚āŽ˛āŽ•āŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "moved_to_trash": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "multiselect_grid_edit_date_time_err_read_only": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯ˆ) āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽŸāŽŋāŽ¤ā¯āŽ¤ āŽ¤ā¯‡āŽ¤āŽŋāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽ¤āŽĩāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "multiselect_grid_edit_gps_err_read_only": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯ˆ) āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽŸāŽŋāŽ•ā¯āŽ•, āŽšā¯āŽ•āŽŋāŽĒā¯āŽĒāŽŋāŽ™ā¯ āŽ†āŽ•āŽŋāŽ¯āŽĩāŽąā¯āŽąā¯ˆāŽ¤ā¯ āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤ āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "mute_memories": "āŽŽā¯āŽŸāŽ•ā¯āŽ•ā¯ āŽ¨āŽŋāŽŠā¯ˆāŽĩā¯āŽ•āŽŗā¯", "my_albums": "āŽŽāŽŠāŽ¤ā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "name": "āŽĒā¯†āŽ¯āŽ°ā¯", @@ -1370,10 +1568,12 @@ "never": "āŽ’āŽ°ā¯āŽĒā¯‹āŽ¤ā¯āŽŽā¯", "new_album": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ†āŽ˛ā¯āŽĒāŽŽā¯", "new_api_key": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆ", + "new_date_range": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽ°āŽŽā¯āŽĒ❁", "new_password": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯", "new_person": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ¨āŽĒāŽ°ā¯", "new_pin_code": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯", "new_pin_code_subtitle": "āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯ā¯ˆ āŽ…āŽŖā¯āŽ• āŽ‡āŽ¤ā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽ¤āŽ˛ā¯ āŽŽā¯āŽąā¯ˆāŽ¯āŽžāŽ•ā¯āŽŽā¯. āŽ‡āŽ¨ā¯āŽ¤ āŽĒāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽĒāŽžāŽ¤ā¯āŽ•āŽžāŽĒā¯āŽĒāŽžāŽ• āŽ…āŽŖā¯āŽ• āŽ’āŽ°ā¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "new_timeline": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ•āŽžāŽ˛āŽĩāŽ°āŽŋāŽšā¯ˆ", "new_update": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒ❁", "new_user_created": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "new_version_available": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒāŽ¤āŽŋāŽĒā¯āŽĒ❁ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", @@ -1381,6 +1581,7 @@ "next": "āŽ…āŽŸā¯āŽ¤ā¯āŽ¤āŽ¤ā¯", "next_memory": "āŽ…āŽŸā¯āŽ¤ā¯āŽ¤ āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽ•āŽŽā¯", "no": "āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "no_actions_added": "āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_albums_found": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "no_albums_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ’āŽ´ā¯āŽ™ā¯āŽ•āŽŽā¯ˆāŽ•ā¯āŽ• āŽ’āŽ°ā¯ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "no_albums_with_name_yet": "āŽ‡āŽ¨ā¯āŽ¤ āŽĒā¯†āŽ¯āŽ°ā¯āŽŸāŽŠā¯ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ āŽŽāŽŠā¯āŽąā¯ āŽ¤ā¯†āŽ°āŽŋāŽ•āŽŋāŽąāŽ¤ā¯.", @@ -1391,13 +1592,16 @@ "no_cast_devices_found": "āŽ¨āŽŸāŽŋāŽ•āŽ°ā¯āŽ•āŽŗā¯ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_checksum_local": "āŽšā¯†āŽ•ā¯āŽšāŽŽā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ - āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒā¯†āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", "no_checksum_remote": "āŽšā¯†āŽ•ā¯āŽšāŽŽā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ - āŽ¤ā¯ŠāŽ˛ā¯ˆ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒā¯†āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯", + "no_configuration_needed": "āŽ•āŽŸā¯āŽŸāŽŽā¯ˆāŽĒā¯āŽĒ❁ āŽ¤ā¯‡āŽĩā¯ˆāŽ¯āŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_devices": "āŽ…āŽ™ā¯āŽ•ā¯€āŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "no_duplicates_found": "āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", "no_exif_info_available": "exif āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_explore_results_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¤ā¯ŠāŽ•ā¯āŽĒā¯āŽĒ❈ āŽ†āŽ°āŽžāŽ¯ āŽ•ā¯‚āŽŸā¯āŽ¤āŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĩā¯āŽŽā¯.", "no_favorites_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšāŽŋāŽąāŽ¨ā¯āŽ¤ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĩāŽŋāŽ°ā¯ˆāŽĩāŽžāŽ•āŽ•ā¯ āŽ•āŽŖā¯āŽŸā¯āŽĒāŽŋāŽŸāŽŋāŽ•ā¯āŽ• āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩā¯ˆāŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "no_filters_added": "āŽ‡āŽ¤ā¯āŽĩāŽ°ā¯ˆ āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽžāŽŠā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_libraries_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖ āŽĩā¯†āŽŗāŽŋāŽĒā¯āŽĒā¯āŽą āŽ¨ā¯‚āŽ˛āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "no_local_assets_found": "āŽ‡āŽ¨ā¯āŽ¤ āŽšā¯†āŽ•ā¯āŽšāŽŽā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ‰āŽŗā¯āŽŗāŽ• āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "no_location_set": "āŽ‡āŽŸāŽŽā¯ āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "no_locked_photos_message": "āŽĒā¯‚āŽŸā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽąā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽŠ, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯‚āŽ˛āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ˛āŽžāŽĩā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ•āŽžāŽŖā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ā¯.", "no_name": "āŽĒā¯†āŽ¯āŽ°ā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "no_notifications": "āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", @@ -1407,6 +1611,7 @@ "no_results": "āŽŽā¯āŽŸāŽŋāŽĩā¯āŽ•āŽŗā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "no_results_description": "āŽ’āŽ°ā¯ āŽ’āŽ¤ā¯āŽ¤ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽĒā¯ŠāŽ¤ā¯āŽĩāŽžāŽŠ āŽŽā¯āŽ•ā¯āŽ•āŽŋāŽ¯ āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "no_shared_albums_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯†āŽŸā¯āŽĩā¯ŠāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽĩāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŗā¯āŽŗ āŽ’āŽ°ā¯ āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "no_uploads_in_progress": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "none": "āŽŽāŽ¤ā¯āŽĩā¯āŽŽāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "not_allowed": "āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", "not_available": "āŽ‡āŽ¤āŽąā¯āŽ•āŽŋāŽ˛ā¯āŽ˛ā¯ˆ", @@ -1454,6 +1659,7 @@ "original": "āŽ…āŽšāŽ˛ā¯", "other": "āŽŽāŽąā¯āŽąā¯ŠāŽŠā¯āŽąā¯", "other_devices": "āŽĒāŽŋāŽą āŽšāŽžāŽ¤āŽŠāŽ™ā¯āŽ•āŽŗā¯", + "other_entities": "āŽĒāŽŋāŽą āŽ¨āŽŋāŽąā¯āŽĩāŽŠāŽ™ā¯āŽ•āŽŗā¯", "other_variables": "āŽĒāŽŋāŽą āŽŽāŽžāŽąāŽŋāŽ•āŽŗā¯", "owned": "āŽšā¯ŠāŽ¨ā¯āŽ¤āŽŽāŽžāŽŠāŽ¤ā¯", "owner": "āŽ‰āŽ°āŽŋāŽŽā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯", @@ -1463,9 +1669,12 @@ "partner_can_access_assets": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽĩ❈ āŽ¤āŽĩāŽŋāŽ° āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯āŽŽā¯", "partner_can_access_location": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŸāŽŽā¯", "partner_list_user_photos": "{user} āŽ‡āŽŠā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "partner_list_view_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•āŽžāŽŖā¯āŽ•", "partner_page_empty_message": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽŠā¯āŽŠā¯āŽŽā¯ āŽŽāŽ¨ā¯āŽ¤ āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽŸāŽŠā¯āŽŽā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", "partner_page_no_more_users": "āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ• āŽ‡āŽŠāŽŋ āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "partner_page_partner_add_failed": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯", "partner_page_select_partner": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "partner_page_shared_to_title": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "partner_page_stop_sharing_content": "{partner} āŽ‡āŽŠāŽŋ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ…āŽŖā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯.", "partner_sharing": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩ❁", "partners": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽ•āŽŗā¯", @@ -1487,6 +1696,8 @@ "people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯", "people_edits_count": "{count, plural, one {# āŽ¨āŽĒāŽ°ā¯} other {# āŽĒā¯‡āŽ°ā¯}} āŽ¤āŽŋāŽ°ā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "people_feature_description": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ āŽ¤ā¯ŠāŽ•ā¯āŽ¤ā¯āŽ¤ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ˛āŽžāŽĩā¯āŽ¤āŽ˛ā¯", + "people_selected": "{count, plural, one {# āŽ¨āŽĒāŽ°ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ°ā¯} other {# āŽĒā¯‡āŽ°ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠāŽ°ā¯}}", + "people_sidebar_description": "āŽĒāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŋāŽ¯āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽĩāŽ°ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ’āŽ°ā¯ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽĒāŽŋ", "permanent_deletion_warning": "āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ° āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯ āŽŽāŽšā¯āŽšāŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆ", "permanent_deletion_warning_setting_description": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ’āŽ°ā¯ āŽŽāŽšā¯āŽšāŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆāŽ¯ā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "permanently_delete": "āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", @@ -1496,6 +1707,14 @@ "permanently_deleted_assets_count": "āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", "permission": "āŽ‡āŽšā¯ˆāŽĩ❁", "permission_empty": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ‡āŽšā¯ˆāŽĩ❁ āŽ•āŽžāŽ˛āŽŋāŽ¯āŽžāŽ• āŽ‡āŽ°ā¯āŽ•ā¯āŽ•āŽ•ā¯āŽ•ā¯‚āŽŸāŽžāŽ¤ā¯", + "permission_onboarding_back": "āŽĒāŽŋāŽŠā¯", + "permission_onboarding_continue_anyway": "āŽŽāŽĒā¯āŽĒāŽŸāŽŋāŽ¯ā¯āŽŽā¯ āŽ¤ā¯ŠāŽŸāŽ°āŽĩā¯āŽŽā¯", + "permission_onboarding_get_started": "āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", + "permission_onboarding_go_to_settings": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯āŽšā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽ™ā¯āŽ•āŽŗā¯", + "permission_onboarding_permission_denied": "āŽ‡āŽšā¯ˆāŽĩ❁ āŽŽāŽąā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯. āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤, āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•āŽŗā¯ˆ āŽĩāŽ´āŽ™ā¯āŽ•āŽĩā¯āŽŽā¯.", + "permission_onboarding_permission_granted": "āŽ‡āŽšā¯ˆāŽĩ❁ āŽĩāŽ´āŽ™ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯! āŽ¨ā¯€āŽ™ā¯āŽ•āŽŗā¯ āŽ…āŽŠā¯ˆāŽĩāŽ°ā¯āŽŽā¯ āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗā¯.", + "permission_onboarding_permission_limited": "āŽ‡āŽšā¯ˆāŽĩ❁ āŽ˛āŽŋāŽŽāŽŋāŽŸā¯†āŽŸā¯. āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽ´ā¯ āŽ•ā¯‡āŽ˛āŽ°āŽŋ āŽšā¯‡āŽ•āŽ°āŽŋāŽĒā¯āŽĒā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯, āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹ āŽ…āŽŠā¯āŽŽāŽ¤āŽŋāŽ•āŽŗā¯ˆ āŽĩāŽ´āŽ™ā¯āŽ•āŽĩā¯āŽŽā¯.", + "permission_onboarding_request": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšā¯āŽ•ā¯āŽ•ā¯ āŽ‡āŽšā¯ˆāŽĩ❁ āŽ¤ā¯‡āŽĩ❈.", "person": "āŽ†āŽŗā¯", "person_age_months": "{months, plural, one {# āŽ¤āŽŋāŽ™ā¯āŽ•āŽŗā¯} other {# āŽ¤āŽŋāŽ™ā¯āŽ•āŽŗā¯āŽ•āŽŗā¯}} āŽ…āŽ•āŽĩ❈", "person_age_year_months": "1 āŽĩāŽ°ā¯āŽŸāŽŽā¯, {months, plural, one {# āŽ¤āŽŋāŽ™ā¯āŽ•āŽŗā¯} other {# āŽ¤āŽŋāŽ™ā¯āŽ•āŽŗā¯āŽ•āŽŗā¯}} āŽ…āŽ•āŽĩ❈", @@ -1503,10 +1722,13 @@ "person_birthdate": "{date} āŽ‡āŽ˛ā¯ āŽĒāŽŋāŽąāŽ¨ā¯āŽ¤āŽžāŽ°ā¯", "person_hidden": "{name}{hidden, select, true { (āŽŽāŽąā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ)} other {}}", "person_recognized": "āŽ…āŽŸā¯ˆāŽ¯āŽžāŽŗāŽŽā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ¨āŽĒāŽ°ā¯", + "person_selected": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ¨āŽĒāŽ°ā¯", "photo_shared_all_users": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽŽāŽ˛ā¯āŽ˛āŽž āŽĒāŽ¯āŽŠāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯āŽŽā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸāŽ¤āŽžāŽ•āŽ¤ā¯ āŽ¤ā¯†āŽ°āŽŋāŽ•āŽŋāŽąāŽ¤ā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽ‰āŽ™ā¯āŽ•āŽŗāŽŋāŽŸāŽŽā¯ āŽŽāŽ¨ā¯āŽ¤ āŽĒāŽ¯āŽŠāŽ°ā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ.", "photos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", "photos_and_videos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ & āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", + "photos_count": "{count, plural, one {{count, number} āŽĒāŽŸāŽŽā¯} other {{count, number} āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯}}", "photos_from_previous_years": "āŽŽā¯āŽ¨ā¯āŽ¤ā¯ˆāŽ¯ āŽ†āŽŖā¯āŽŸā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "photos_only": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡", "pick_a_location": "āŽ’āŽ°ā¯ āŽ‡āŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "pick_custom_range": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽĩāŽ°āŽŽā¯āŽĒ❁", "pick_date_range": "āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽ°āŽŽā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1541,6 +1763,7 @@ "privacy": "āŽ¤āŽŠāŽŋāŽ¯ā¯āŽ°āŽŋāŽŽā¯ˆ", "profile": "āŽšā¯āŽ¯āŽĩāŽŋāŽĩāŽ°āŽŽā¯", "profile_drawer_app_logs": "āŽĒāŽ¤āŽŋāŽĩā¯āŽ•āŽŗā¯", + "profile_drawer_client_server_up_to_date": "āŽĩāŽžāŽ™ā¯āŽ•āŽŋ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽŽā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ¤ā¯āŽ¤ āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽŠ", "profile_drawer_github": "āŽ•āŽŋāŽŸā¯āŽšāŽĒā¯", "profile_drawer_readonly_mode": "āŽĒāŽŸāŽŋāŽ•ā¯āŽ• āŽŽāŽŸā¯āŽŸā¯āŽŽā¯ āŽĒāŽ¯āŽŠā¯āŽŽā¯āŽąā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯. āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽą āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ…āŽĩāŽ¤āŽžāŽ°ā¯ āŽāŽ•āŽžāŽŠā¯ˆ āŽ¨ā¯€āŽŖā¯āŽŸ āŽ¨ā¯‡āŽ°āŽŽā¯ āŽ…āŽ´ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯.", "profile_image_of_user": "{user} āŽ‡āŽŠā¯ āŽšā¯āŽ¯āŽĩāŽŋāŽĩāŽ°āŽĒā¯ āŽĒāŽŸāŽŽā¯", @@ -1563,6 +1786,7 @@ "purchase_individual_description_2": "āŽ†āŽ¤āŽ°āŽĩāŽžāŽŗāŽ°ā¯ āŽ¨āŽŋāŽ˛ā¯ˆ", "purchase_individual_title": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽŸā¯āŽŸ", "purchase_input_suggestion": "āŽ¤āŽ¯āŽžāŽ°āŽŋāŽĒā¯āŽĒ❁ āŽĩāŽŋāŽšā¯ˆ āŽ‰āŽŗā¯āŽŗāŽ¤āŽž? āŽ•ā¯€āŽ´ā¯‡ āŽ‰āŽŗā¯āŽŗ āŽĩāŽŋāŽšā¯ˆāŽ¯ā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", + "purchase_license_subtitle": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽŋāŽŠā¯ āŽ¤ā¯ŠāŽŸāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯āŽžāŽŠ āŽĩāŽŗāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯ā¯ˆ āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ• āŽ‡āŽŽā¯āŽŽāŽŋāŽ¯ā¯ˆ āŽĩāŽžāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", "purchase_lifetime_description": "āŽĩāŽžāŽ´ā¯āŽ¨āŽžāŽŗā¯ āŽ•ā¯ŠāŽŗā¯āŽŽā¯āŽ¤āŽ˛ā¯", "purchase_option_title": "āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĩāŽžāŽ™ā¯āŽ•āŽĩā¯āŽŽā¯", "purchase_panel_info_1": "āŽ‡āŽŽā¯āŽŽāŽŋāŽ¯ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽĩāŽ¤āŽąā¯āŽ•ā¯ āŽ¨āŽŋāŽąā¯ˆāŽ¯ āŽ¨ā¯‡āŽ°āŽŽā¯āŽŽā¯ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽĩ❈āŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽąāŽ¤ā¯, āŽŽā¯‡āŽ˛ā¯āŽŽā¯ āŽŽā¯āŽ´ā¯āŽ¨ā¯‡āŽ° āŽĒā¯ŠāŽąāŽŋāŽ¯āŽŋāŽ¯āŽ˛āŽžāŽŗāŽ°ā¯āŽ•āŽŗā¯ āŽ…āŽ¤ā¯ˆ āŽŽāŽ™ā¯āŽ•āŽŗāŽžāŽ˛ā¯ āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽĩāŽ°ā¯ˆ āŽšāŽŋāŽąāŽĒā¯āŽĒāŽžāŽ•āŽšā¯ āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩā¯‡āŽ˛ā¯ˆ āŽšā¯†āŽ¯ā¯āŽ•āŽŋāŽąāŽžāŽ°ā¯āŽ•āŽŗā¯. āŽŽāŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯‹āŽ•ā¯āŽ•āŽŽā¯ āŽ¤āŽŋāŽąāŽ¨ā¯āŽ¤ āŽŽā¯‚āŽ˛ āŽŽā¯†āŽŠā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ¨ā¯†āŽąāŽŋāŽŽā¯āŽąā¯ˆ āŽĩāŽŖāŽŋāŽ• āŽ¨āŽŸā¯ˆāŽŽā¯āŽąā¯ˆāŽ•āŽŗā¯ āŽŸā¯†āŽĩāŽ˛āŽĒā¯āŽĒāŽ°ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•āŽžāŽŠ āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯āŽžāŽŠ āŽĩāŽ°ā¯āŽŽāŽžāŽŠ āŽ†āŽ¤āŽžāŽ°āŽŽāŽžāŽ• āŽŽāŽžāŽąā¯āŽĩāŽ¤ā¯āŽŽā¯, āŽšā¯āŽ°āŽŖā¯āŽŸāŽ˛ā¯ āŽŽā¯āŽ•āŽŋāŽ˛ā¯ āŽšā¯‡āŽĩā¯ˆāŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ‰āŽŖā¯āŽŽā¯ˆāŽ¯āŽžāŽŠ āŽŽāŽžāŽąā¯āŽąā¯āŽ•āŽŗā¯āŽŸāŽŠā¯ āŽ¤āŽŠāŽŋāŽ¯ā¯āŽ°āŽŋāŽŽā¯ˆ-āŽŽāŽ°āŽŋāŽ¯āŽžāŽ¤ā¯ˆāŽ•ā¯āŽ•ā¯āŽ°āŽŋāŽ¯ āŽšā¯āŽąā¯āŽąā¯āŽšā¯āŽšā¯‚āŽ´āŽ˛ā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒ❈ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽĩāŽ¤ā¯āŽŽā¯ āŽ†āŽ•ā¯āŽŽā¯.", @@ -1595,16 +1819,19 @@ "reassigned_assets_to_new_person": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ¨āŽĒāŽ°ā¯āŽ•ā¯āŽ•ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•āŽŗā¯}} āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽ’āŽ¤ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "reassing_hint": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ‡āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯ āŽ¨āŽĒāŽ°ā¯āŽ•ā¯āŽ•ā¯ āŽ’āŽ¤ā¯āŽ•ā¯āŽ•ā¯āŽ™ā¯āŽ•āŽŗā¯", "recent": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛", + "recent_albums": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", "recent_searches": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛ āŽ¤ā¯‡āŽŸāŽ˛ā¯āŽ•āŽŗā¯", "recently_added": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "recently_added_page_title": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "recently_taken": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "recently_taken_page_title": "āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽŽāŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "refresh": "āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽĒā¯āŽĒ❁", "refresh_encoded_videos": "āŽ•ā¯āŽąāŽŋāŽ¯āŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "refresh_faces": "āŽŽā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "refresh_metadata": "āŽŽā¯†āŽŸā¯āŽŸāŽžāŽŸā¯‡āŽŸā¯āŽŸāŽžāŽĩ❈ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "refresh_thumbnails": "āŽšāŽŋāŽąā¯ āŽ‰āŽ°ā¯āŽĩāŽ™ā¯āŽ•āŽŗā¯ˆ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "refreshed": "āŽĒā¯āŽ¤ā¯āŽ¤ā¯āŽŖāŽ°ā¯āŽšā¯āŽšāŽŋ", + "refreshes_every_file": "āŽāŽąā¯āŽ•āŽŠāŽĩ❇ āŽ‰āŽŗā¯āŽŗ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽĒāŽŸāŽŋāŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "refreshing_encoded_video": "āŽĒā¯āŽ¤ā¯āŽ¤ā¯āŽŖāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯ā¯‚āŽŸā¯āŽŸā¯āŽŽā¯ āŽ•ā¯āŽąāŽŋāŽ¯āŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹", "refreshing_faces": "āŽĒā¯āŽ¤ā¯āŽ¤ā¯āŽŖāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯ā¯‚āŽŸā¯āŽŸā¯āŽŽā¯ āŽŽā¯āŽ•āŽ™ā¯āŽ•āŽŗā¯", "refreshing_metadata": "āŽĒā¯āŽ¤ā¯āŽ¤ā¯āŽŖāŽ°ā¯āŽšā¯āŽšāŽŋāŽ¯ā¯‚āŽŸā¯āŽŸā¯āŽŽā¯ āŽŽā¯‡āŽŠāŽŋāŽ˛ā¯ˆ āŽ¤āŽ°āŽĩ❁", @@ -1617,6 +1844,7 @@ "remove_assets_shared_link_confirmation": "āŽ‡āŽ¨ā¯āŽ¤āŽĒā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ}} āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽ…āŽ•āŽąā¯āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "remove_assets_title": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ…āŽ•āŽąā¯āŽąāŽĩāŽž?", "remove_custom_date_range": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽ¤ā¯‡āŽ¤āŽŋ āŽĩāŽ°āŽŽā¯āŽĒ❈ āŽ…āŽ•āŽąā¯āŽąā¯", + "remove_deleted_assets": "āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ…āŽ•āŽąā¯āŽąāŽĩā¯āŽŽā¯", "remove_from_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽ•āŽąā¯āŽąā¯", "remove_from_album_action_prompt": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ {count} āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "remove_from_favorites": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩā¯ˆāŽ•āŽŗāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽ•āŽąā¯āŽąā¯", @@ -1627,22 +1855,29 @@ "remove_memory": "āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", "remove_photo_from_memory": "āŽ‡āŽ¨ā¯āŽ¤ āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", "remove_tag": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", + "remove_url": "āŽŽā¯āŽ•āŽĩāŽ°āŽŋ āŽ āŽ…āŽ•āŽąā¯āŽąā¯", "remove_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", "removed_api_key": "āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆ: {name}", "removed_from_archive": "āŽ•āŽžāŽĒā¯āŽĒāŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "removed_from_favorites": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩā¯ˆāŽ•āŽŗāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "removed_from_favorites_count": "āŽĒāŽŋāŽŸāŽŋāŽ¤ā¯āŽ¤āŽĩāŽąā¯āŽąāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ {count, plural, other {āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ #}}", "removed_memory": "āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽ•āŽŽā¯", + "removed_photo_from_memory": "āŽ¨āŽŋāŽŠā¯ˆāŽĩāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ•āŽąā¯āŽąāŽŋāŽ¯āŽ¤ā¯", "removed_tagged_assets": "{count, plural, one {# asset} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}} āŽŽāŽŠā¯āŽĒāŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯ āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "rename": "āŽŽāŽąā¯āŽĒā¯†āŽ¯āŽ°āŽŋāŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", + "repair": "āŽĒāŽ´ā¯āŽ¤ā¯", + "repair_no_results_message": "āŽ•āŽŸā¯āŽŸā¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ•āŽžāŽŖāŽžāŽŽāŽ˛ā¯ āŽĒā¯‹āŽŠ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽ‡āŽ™ā¯āŽ•ā¯‡ āŽ•āŽžāŽŖā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯", + "replace_with_upload": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤ā¯āŽŸāŽŠā¯ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "repository": "āŽ•āŽŗāŽžā¯āŽšāŽŋāŽ¯āŽŽā¯", "require_password": "āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯ āŽ¤ā¯‡āŽĩ❈", + "require_user_to_change_password_on_first_login": "āŽŽā¯āŽ¤āŽ˛ā¯ āŽ‰āŽŗā¯āŽ¨ā¯āŽ´ā¯ˆāŽĩāŽŋāŽ˛ā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽāŽžāŽąā¯āŽą āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ¤ā¯‡āŽĩ❈", "rescan": "āŽ°ā¯†āŽšā¯āŽ•āŽžāŽŠā¯", "reset": "āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆ", "reset_password": "āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "reset_people_visibility": "āŽŽāŽ•ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ¤ā¯†āŽ°āŽŋāŽĩā¯āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "reset_pin_code": "āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "reset_pin_code_description": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽŽāŽąāŽ¨ā¯āŽ¤ā¯āŽĩāŽŋāŽŸā¯āŽŸāŽžāŽ˛ā¯, āŽ…āŽ¤ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ• āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ• āŽ¨āŽŋāŽ°ā¯āŽĩāŽžāŽ•āŽŋāŽ¯ā¯ˆ āŽ¤ā¯ŠāŽŸāŽ°ā¯āŽĒ❁ āŽ•ā¯ŠāŽŗā¯āŽŗāŽ˛āŽžāŽŽā¯", + "reset_pin_code_success": "āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "reset_pin_code_with_password": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽŽāŽĒā¯āŽĒā¯‹āŽ¤ā¯āŽŽā¯ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽ˛āŽžāŽŽā¯", "reset_sqlite": "SQLite āŽ¤āŽ°āŽĩā¯āŽ¤ā¯āŽ¤āŽŗāŽ¤ā¯āŽ¤ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "reset_sqlite_clear_app_data": "āŽ¤āŽ°āŽĩ❈ āŽ…āŽ´āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1656,10 +1891,12 @@ "resolved_all_duplicates": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯€āŽ°ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯", "restore": "āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆ", "restore_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "restore_trash_action_prompt": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ {count} āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "restore_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽŽā¯€āŽŸā¯āŽŸāŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "restored_asset": "āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯", "resume": "āŽŽā¯€āŽŖā¯āŽŸā¯āŽŽā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•ā¯āŽ™ā¯āŽ•āŽŗā¯", "resume_paused_jobs": "{count, plural, one {# āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽąā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩā¯‡āŽ˛ā¯ˆ} other {# āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽąā¯āŽ¤ā¯āŽ¤āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĩā¯‡āŽ˛ā¯ˆāŽ•āŽŗā¯}} āŽŽāŽąā¯āŽ¤ā¯ŠāŽŸāŽ•ā¯āŽ•āŽŽā¯", + "retry_upload": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "review_duplicates": "āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "review_large_files": "āŽĒā¯†āŽ°āŽŋāŽ¯ āŽ•ā¯‹āŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "role": "āŽĒāŽ™ā¯āŽ•ā¯", @@ -1667,6 +1904,7 @@ "role_viewer": "āŽĒāŽžāŽ°ā¯āŽĩā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯", "running": "āŽ‡āŽ¯āŽ™ā¯āŽ•ā¯āŽŽā¯", "save": "āŽšā¯‡āŽŽāŽŋ", + "save_to_gallery": "āŽ•ā¯‡āŽ˛āŽ°āŽŋāŽ¯āŽŋāŽ˛ā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "saved": "āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "saved_api_key": "āŽšā¯‡āŽŽāŽŋāŽ¤ā¯āŽ¤ āŽĒāŽ¨āŽŋāŽ‡ āŽĩāŽŋāŽšā¯ˆ", "saved_profile": "āŽšā¯‡āŽŽāŽŋāŽ¤ā¯āŽ¤ āŽšā¯āŽ¯āŽĩāŽŋāŽĩāŽ°āŽŽā¯", @@ -1677,6 +1915,7 @@ "scan": "āŽĩāŽ°ā¯āŽŸā¯ āŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", "scan_all_libraries": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨ā¯‚āŽ˛āŽ•āŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽšā¯āŽ•ā¯‡āŽŠā¯ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", "scan_library": "āŽšā¯āŽ•ā¯‡āŽŠā¯", + "scan_settings": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆ āŽšā¯āŽ•ā¯‡āŽŠā¯ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", "scanning": "āŽĩāŽ°ā¯āŽŸā¯ āŽšā¯†āŽ¯ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "scanning_for_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽšā¯āŽ•ā¯‡āŽŠāŽŋāŽ™ā¯ ...", "search": "āŽ¤ā¯‡āŽŸāŽ˛ā¯", @@ -1705,6 +1944,7 @@ "search_filter_location_title": "āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "search_filter_media_type": "āŽŠāŽŸāŽ• āŽĩāŽ•ā¯ˆ", "search_filter_media_type_title": "āŽŽā¯€āŽŸāŽŋāŽ¯āŽž āŽĩāŽ•ā¯ˆāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "search_filter_ocr": "āŽ“āŽšāŽŋāŽ†āŽ°ā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ¤ā¯‡āŽŸā¯", "search_filter_people_title": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "search_filter_star_rating": "āŽ¨āŽŸā¯āŽšāŽ¤ā¯āŽ¤āŽŋāŽ° āŽŽāŽ¤āŽŋāŽĒā¯āŽĒā¯€āŽŸā¯", "search_filter_tags_title": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽąā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1715,13 +1955,25 @@ "search_no_people_named": "\"{name}\" āŽŽāŽŠā¯āŽąā¯ āŽĒā¯†āŽ¯āŽ°āŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸāŽĩāŽ°ā¯āŽ•āŽŗā¯ āŽ¯āŽžāŽ°ā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "search_no_result": "āŽŽā¯āŽŸāŽŋāŽĩā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ, āŽĩā¯‡āŽąā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯ āŽ•āŽžāŽ˛āŽ¤ā¯āŽ¤ā¯ˆ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽ•āŽ˛āŽĩā¯ˆāŽ¯ā¯ˆ āŽŽā¯āŽ¯āŽąā¯āŽšāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "search_options": "āŽ¤ā¯‡āŽŸāŽ˛ā¯ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "search_page_categories": "āŽĩāŽ•ā¯ˆāŽ•āŽŗā¯", + "search_page_motion_photos": "āŽ‡āŽ¯āŽ•ā¯āŽ• āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "search_page_no_objects": "āŽĒā¯ŠāŽ°ā¯āŽŗā¯ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "search_page_no_places": "āŽ‡āŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", + "search_page_screenshots": "āŽ¤āŽŋāŽ°ā¯ˆāŽ•ā¯āŽ•āŽžāŽŸā¯āŽšāŽŋāŽ•āŽŗā¯", "search_page_search_photos_videos": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", + "search_page_selfies": "āŽšā¯†āŽ˛ā¯āŽƒāŽĒāŽŋāŽ•āŽŗā¯", + "search_page_things": "āŽĩāŽŋāŽšāŽ¯āŽ™ā¯āŽ•āŽŗā¯", "search_page_view_all_button": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•āŽžāŽŖā¯āŽ•", + "search_page_your_activity": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯", + "search_page_your_map": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĩāŽ°ā¯ˆāŽĒāŽŸāŽŽā¯", "search_people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "search_places": "āŽ‡āŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "search_rating": "āŽŽāŽ¤āŽŋāŽĒā¯āŽĒā¯€āŽŸā¯āŽŸāŽŋāŽŠā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯ ...", + "search_result_page_new_search_hint": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯", "search_settings": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯", "search_state": "āŽ¤ā¯‡āŽŸāŽ˛ā¯ āŽ¨āŽŋāŽ˛ā¯ˆ ...", + "search_suggestion_list_smart_search_hint_1": "āŽŽā¯†āŽŸā¯āŽŸāŽžāŽŸā¯‡āŽŸā¯āŽŸāŽžāŽĩā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸ, āŽ¨āŽŋāŽ•āŽ´ā¯āŽĩ❈āŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤, āŽ…āŽąāŽŋāŽĩā¯āŽŗā¯āŽŗ āŽ¤ā¯‡āŽŸāŽ˛ā¯ āŽ‡āŽ¯āŽ˛ā¯āŽĒā¯āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯āŽžāŽ• āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ ", + "search_suggestion_list_smart_search_hint_2": "āŽŽāŽŽā¯: āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¤ā¯‡āŽŸāŽ˛ā¯-āŽ•āŽžāŽ˛āŽ¨āŽŋāŽ˛ā¯ˆ", "search_tags": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽąā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯ ...", "search_timezone": "āŽ¨ā¯‡āŽ° āŽŽāŽŖā¯āŽŸāŽ˛āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯ ...", "search_type": "āŽ¤ā¯‡āŽŸāŽ˛ā¯ āŽĩāŽ•ā¯ˆ", @@ -1735,6 +1987,7 @@ "select_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯†āŽ°āŽŋāŽĩā¯āŽšā¯†āŽ¯ā¯", "select_all_duplicates": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "select_all_in": "{group} āŽ‡āŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_avatar_color": "āŽ…āŽĩāŽ¤āŽžāŽ°ā¯ āŽ¨āŽŋāŽąāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_count": "{count, plural, one {āŽ¤ā¯‡āŽ°ā¯āŽĩ❁ #} other {āŽ¤ā¯‡āŽ°ā¯āŽĩā¯āŽ•āŽŗā¯ #}}", "select_cutoff_date": "āŽĩā¯†āŽŸā¯āŽŸā¯ āŽ¤ā¯‡āŽ¤āŽŋāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1742,12 +1995,14 @@ "select_featured_photo": "āŽĒāŽŋāŽ°āŽ¤ā¯āŽ¯ā¯‡āŽ• āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_from_computer": "āŽ•āŽŖāŽŋāŽŠāŽŋāŽ¯āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_keep_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩā¯ˆāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽŠā¯āŽĒāŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "select_library_owner": "āŽ¨ā¯‚āŽ˛āŽ• āŽ‰āŽ°āŽŋāŽŽā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_new_face": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽŽā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_people": "āŽ¨āŽĒāŽ°ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_person": "āŽ¨āŽĒāŽ°ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_person_to_tag": "āŽ•ā¯āŽąāŽŋāŽ•ā¯āŽ• āŽ’āŽ°ā¯ āŽ¨āŽĒāŽ°ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_photos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "select_trash_all": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸā¯āŽŸāŽŋāŽ¯ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "select_user_for_sharing_page_err_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽ¤ā¯ āŽ¤āŽĩāŽąāŽŋāŽĩāŽŋāŽŸā¯āŽŸāŽ¤ā¯", "selected": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "selected_count": "{count, plural, other {# āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ}}", "selected_gps_coordinates": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšāŽŋ.āŽĒāŽŋ.āŽŽāŽšā¯ āŽ†āŽ¯āŽ¤ā¯āŽ¤ā¯ŠāŽ˛ā¯ˆāŽĩā¯āŽ•āŽŗā¯", @@ -1780,20 +2035,33 @@ "setting_image_viewer_original_title": "āŽ…āŽšāŽ˛ā¯ āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽāŽąā¯āŽąāŽĩā¯āŽŽā¯", "setting_image_viewer_preview_subtitle": "āŽ¨āŽŸā¯āŽ¤ā¯āŽ¤āŽ°-āŽ¤ā¯†āŽŗāŽŋāŽĩā¯āŽ¤ā¯āŽ¤āŽŋāŽąāŽŠā¯ āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽāŽąā¯āŽąāŽĩā¯āŽŽā¯. āŽ…āŽšāŽ˛ā¯ˆ āŽ¨ā¯‡āŽ°āŽŸāŽŋāŽ¯āŽžāŽ• āŽāŽąā¯āŽą āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯ āŽ…āŽ˛ā¯āŽ˛āŽ¤ā¯ āŽšāŽŋāŽąā¯āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯.", "setting_image_viewer_preview_title": "āŽŽā¯āŽŠā¯āŽŠā¯‹āŽŸā¯āŽŸāŽŽā¯ āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆ āŽāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "setting_image_viewer_title": "āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯", "setting_languages_apply": "āŽ‡āŽŸā¯", "setting_languages_subtitle": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽŽā¯ŠāŽ´āŽŋāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", + "setting_notifications_notify_failures_grace_period": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽ¤ā¯‹āŽ˛ā¯āŽĩāŽŋāŽ•āŽŗā¯ˆ āŽ…āŽąāŽŋāŽĩāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯: {duration}", + "setting_notifications_notify_hours": "{count} āŽŽāŽŖāŽŋāŽ¨ā¯‡āŽ°āŽŽā¯", + "setting_notifications_notify_immediately": "āŽ‰āŽŸāŽŠāŽŸāŽŋāŽ¯āŽžāŽ•", "setting_notifications_notify_minutes": "{count} āŽ¨āŽŋāŽŽāŽŋāŽŸāŽ™ā¯āŽ•āŽŗā¯", + "setting_notifications_notify_never": "āŽ’āŽ°ā¯āŽĒā¯‹āŽ¤ā¯āŽŽā¯", "setting_notifications_notify_seconds": "{count} āŽĩāŽŋāŽ¨āŽžāŽŸāŽŋāŽ•āŽŗā¯", + "setting_notifications_single_progress_subtitle": "āŽĩāŽŋāŽ°āŽŋāŽĩāŽžāŽŠ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽŽā¯ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽą āŽšā¯†āŽ¯ā¯āŽ¤āŽŋ āŽ’āŽ°ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•ā¯", + "setting_notifications_single_progress_title": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽĩāŽŋāŽĩāŽ°āŽŽā¯ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤ā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "setting_notifications_subtitle": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ…āŽąāŽŋāŽĩāŽŋāŽĒā¯āŽĒ❁ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", + "setting_notifications_total_progress_subtitle": "āŽ’āŽŸā¯āŽŸā¯āŽŽā¯ŠāŽ¤ā¯āŽ¤ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽŽā¯ (āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯/āŽŽā¯ŠāŽ¤ā¯āŽ¤ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯)", + "setting_notifications_total_progress_title": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽā¯ŠāŽ¤ā¯āŽ¤ āŽŽā¯āŽŠā¯āŽŠā¯‡āŽąā¯āŽąāŽ¤ā¯āŽ¤ā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "setting_video_viewer_auto_play_subtitle": "āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽ¤āŽŋāŽąāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽĩā¯āŽŸāŽŠā¯ āŽ¤āŽžāŽŠāŽžāŽ•āŽĩ❇ āŽ‡āŽ¯āŽ™ā¯āŽ•āŽ¤ā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•ā¯āŽŽā¯", "setting_video_viewer_auto_play_title": "āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ¤āŽžāŽŠāŽžāŽ• āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "setting_video_viewer_looping_title": "āŽ˛ā¯‚āŽĒā¯āŽĒāŽŋāŽ™ā¯", "setting_video_viewer_original_video_subtitle": "āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ’āŽ°ā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽĩ❈ āŽšā¯āŽŸā¯āŽ°ā¯€āŽŽāŽŋāŽ™ā¯ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯, āŽ’āŽ°ā¯ āŽŸāŽŋāŽ°āŽžāŽŠā¯āŽšā¯āŽ•ā¯‹āŽŸā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽ•ā¯‚āŽŸ āŽ…āŽšāŽ˛ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯. āŽ‡āŽŸā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽĩāŽ´āŽŋāŽĩāŽ•ā¯āŽ•ā¯āŽ•ā¯āŽŽā¯. āŽ‡āŽ¨ā¯āŽ¤ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒā¯ŠāŽ°ā¯āŽŸā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽžāŽŽāŽ˛ā¯ āŽ‰āŽŗā¯āŽ¨āŽžāŽŸā¯āŽŸāŽŋāŽ˛ā¯ āŽ•āŽŋāŽŸā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽ…āŽšāŽ˛ā¯ āŽ¤āŽ°āŽ¤ā¯āŽ¤āŽŋāŽ˛ā¯ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ.", "setting_video_viewer_original_video_title": "āŽ…āŽšāŽ˛ā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽĩ❈ āŽ•āŽŸā¯āŽŸāŽžāŽ¯āŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ™ā¯āŽ•āŽŗā¯", "settings": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", + "settings_require_restart": "āŽ‡āŽ¨ā¯āŽ¤ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ āŽ‡āŽŽā¯āŽŽāŽŋāŽ¯ā¯ˆ āŽŽāŽąā¯āŽ¤ā¯ŠāŽŸāŽ•ā¯āŽ•āŽŽā¯ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", "settings_saved": "āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŠ", "setup_pin_code": "āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽ…āŽŽā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "share": "āŽĒāŽ™ā¯āŽ•ā¯", + "share_action_prompt": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ {count} āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", + "share_add_photos": "āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "share_assets_selected": "{count} āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "share_dialog_preparing": "āŽ¤āŽ¯āŽžāŽ°āŽŋāŽ¤ā¯āŽ¤āŽ˛ā¯ ...", "share_link": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒāŽ•āŽŋāŽ°āŽĩā¯āŽŽā¯", "shared": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1801,6 +2069,8 @@ "shared_album_activity_remove_content": "āŽ‡āŽ¨ā¯āŽ¤āŽšā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "shared_album_activity_remove_title": "āŽšā¯†āŽ¯āŽ˛ā¯āŽĒāŽžāŽŸā¯āŽŸā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "shared_album_section_people_action_error": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽąā¯āŽ¤āŽ˛ā¯/āŽ¨ā¯€āŽ•ā¯āŽ•ā¯āŽ¤āŽ˛ā¯", + "shared_album_section_people_action_leave": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", + "shared_album_section_people_action_remove_user": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽĒāŽ¯āŽŠāŽ°ā¯ˆ āŽ…āŽ•āŽąā¯āŽąā¯", "shared_album_section_people_title": "āŽŽāŽ•ā¯āŽ•āŽŗā¯", "shared_by": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "shared_by_user": "{user} āŽ†āŽ˛ā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -1809,6 +2079,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "shared_link_app_bar_title": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯", "shared_link_clipboard_copied_massage": "āŽ‡āŽŸā¯ˆāŽ¨āŽŋāŽ˛ā¯ˆāŽĒā¯āŽĒāŽ˛āŽ•ā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ˛ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "shared_link_clipboard_text": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❁: {link} \nāŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯: {password}", "shared_link_create_error": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽĒāŽŋāŽ´ā¯ˆ", "shared_link_custom_url_description": "āŽ¤āŽŠāŽŋāŽĒā¯āŽĒāŽ¯āŽŠā¯ āŽŽā¯āŽ•āŽĩāŽ°āŽŋ āŽ‰āŽŸāŽŠā¯ āŽ‡āŽ¨ā¯āŽ¤ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒ❈ āŽ…āŽŖā¯āŽ•āŽĩā¯āŽŽā¯", "shared_link_edit_description_hint": "āŽĒāŽ™ā¯āŽ•ā¯ āŽĩāŽŋāŽŗāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", @@ -1844,6 +2115,12 @@ "shared_with_partner": "{partner} āŽ‰āŽŸāŽŠā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "sharing": "āŽĒāŽ•āŽŋāŽ°ā¯āŽĩ❁", "sharing_enter_password": "āŽ‡āŽ¨ā¯āŽ¤ āŽĒāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖ āŽ•āŽŸāŽĩā¯āŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯.", + "sharing_page_album": "āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯", + "sharing_page_description": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨ā¯†āŽŸā¯āŽĩā¯ŠāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗāŽĩāŽ°ā¯āŽ•āŽŗā¯āŽŸāŽŠā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŗā¯āŽŗ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", + "sharing_page_empty_list": "āŽĩā¯†āŽąā¯āŽąā¯ āŽĒāŽŸā¯āŽŸāŽŋāŽ¯āŽ˛ā¯", + "sharing_sidebar_description": "āŽĒāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽŋāŽ¯āŽŋāŽ˛ā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽĩāŽ¤āŽąā¯āŽ•āŽžāŽŠ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽĒāŽŋ", + "sharing_silver_appbar_create_shared_album": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ†āŽ˛ā¯āŽĒāŽŽā¯", + "sharing_silver_appbar_share_partner": "āŽ•ā¯‚āŽŸā¯āŽŸāŽžāŽŗāŽ°ā¯āŽŸāŽŠā¯ āŽĒāŽ•āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ•ā¯ŠāŽŗā¯āŽŗā¯āŽ™ā¯āŽ•āŽŗā¯", "shift_to_permanent_delete": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ• ⇧ āŽ āŽ…āŽ´ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", "show_album_options": "āŽ†āŽ˛ā¯āŽĒāŽŽā¯ āŽĩāŽŋāŽ°ā¯āŽĒā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "show_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", @@ -1885,6 +2162,7 @@ "sort_created": "āŽ¤ā¯‡āŽ¤āŽŋ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "sort_items": "āŽĒā¯ŠāŽ°ā¯āŽŸā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽŽāŽŖā¯āŽŖāŽŋāŽ•ā¯āŽ•ā¯ˆ", "sort_modified": "āŽ¤ā¯‡āŽ¤āŽŋ āŽŽāŽžāŽąā¯āŽąāŽŋāŽ¯āŽŽā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "sort_newest": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯", "sort_oldest": "āŽĒāŽ´āŽŽā¯ˆāŽ¯āŽžāŽŠ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯", "sort_people_by_similarity": "āŽ’āŽąā¯āŽąā¯āŽŽā¯ˆāŽ¯āŽžāŽ˛ā¯ āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆ āŽĩāŽ°āŽŋāŽšā¯ˆāŽĒā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ™ā¯āŽ•āŽŗā¯", "sort_recent": "āŽŽāŽŋāŽ• āŽ…āŽŖā¯āŽŽā¯ˆāŽ•ā¯ āŽ•āŽžāŽ˛ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽŽā¯", @@ -1893,6 +2171,7 @@ "stack": "āŽ…āŽŸā¯āŽ•ā¯āŽ•ā¯", "stack_action_prompt": "{count} āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽŋ āŽĩā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸā¯āŽŗā¯āŽŗāŽ¤ā¯", "stack_duplicates": "āŽ…āŽŸā¯āŽ•ā¯āŽ•ā¯ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯", + "stack_select_one_photo": "āŽ…āŽŸā¯āŽ•ā¯āŽ•ā¯āŽ•ā¯āŽ•ā¯ āŽ’āŽ°ā¯ āŽŽā¯āŽ•ā¯āŽ•āŽŋāŽ¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "stack_selected_photos": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽŋ āŽĩā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "stacked_assets_count": "āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", "stacktrace": "āŽšā¯āŽŸāŽžāŽ•ā¯ āŽŸā¯āŽ°ā¯‡āŽšā¯", @@ -1921,6 +2200,7 @@ "swap_merge_direction": "āŽ’āŽŠā¯āŽąāŽŋāŽŖā¯ˆāŽ•ā¯āŽ•ā¯āŽŽā¯ āŽ¤āŽŋāŽšā¯ˆāŽ¯ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĩā¯āŽŽā¯", "sync": "āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❁", "sync_albums": "āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗā¯ˆ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "sync_albums_manual_subtitle": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽŋāŽ¯ āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•āŽžāŽĒā¯āŽĒ❁ āŽ†āŽ˛ā¯āŽĒāŽ™ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "sync_local": "āŽ‰āŽŗā¯āŽŗāŽ• āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "sync_remote": "āŽ¤ā¯ŠāŽ˛ā¯ˆāŽ¤ā¯‚āŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "sync_status": "āŽ¨āŽŋāŽ˛ā¯ˆāŽ¯ā¯ˆ āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", @@ -1930,6 +2210,7 @@ "tag_assets": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯", "tag_created": "āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯: {tag}", "tag_feature_description": "āŽ¤āŽ°ā¯āŽ•ā¯āŽ•āŽ°ā¯€āŽ¤āŽŋāŽ¯āŽžāŽŠ āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯ āŽ¤āŽ˛ā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗāŽžāŽ˛ā¯ āŽ¤ā¯ŠāŽ•ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ˆ āŽ‰āŽ˛āŽžāŽĩā¯āŽ¤āŽ˛ā¯", + "tag_not_found_question": "āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆāŽ•ā¯ āŽ•āŽŖā¯āŽŸā¯āŽĒāŽŋāŽŸāŽŋāŽ•ā¯āŽ• āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆāŽ¯āŽž?āŽĒā¯āŽ¤āŽŋāŽ¯ āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯āŽ˛ā¯ˆ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", "tag_people": "āŽŽāŽ•ā¯āŽ•āŽŗā¯ˆ āŽ•ā¯āŽąāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "tag_updated": "āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ•ā¯āŽąāŽŋāŽšā¯āŽšā¯ŠāŽ˛ā¯: {tag}", "tagged_assets": "āŽ•ā¯āŽąāŽŋāŽ¯āŽŋāŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", @@ -1944,10 +2225,15 @@ "theme_setting_asset_list_tiles_per_row_title": "āŽ’āŽ°ā¯ āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽŽāŽŖā¯āŽŖāŽŋāŽ•ā¯āŽ•ā¯ˆ ({count})", "theme_setting_colorful_interface_subtitle": "āŽĒāŽŋāŽŠā¯āŽŠāŽŖāŽŋ āŽŽā¯‡āŽąā¯āŽĒāŽ°āŽĒā¯āŽĒā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽŽā¯āŽ¤āŽŠā¯āŽŽā¯ˆ āŽĩāŽŖā¯āŽŖāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ™ā¯āŽ•āŽŗā¯.", "theme_setting_colorful_interface_title": "āŽĩāŽŖā¯āŽŖāŽŽāŽ¯āŽŽāŽžāŽŠ āŽ‡āŽŸā¯ˆāŽŽā¯āŽ•āŽŽā¯", + "theme_setting_image_viewer_quality_subtitle": "āŽĩāŽŋāŽĩāŽ°āŽŽā¯ āŽĒāŽŸ āŽĒāŽžāŽ°ā¯āŽĩā¯ˆāŽ¯āŽžāŽŗāŽ°āŽŋāŽŠā¯ āŽ¤āŽ°āŽ¤ā¯āŽ¤ā¯ˆ āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¯āŽĩā¯āŽŽā¯", + "theme_setting_image_viewer_quality_title": "āŽĒāŽŸ āŽĒāŽžāŽ°ā¯āŽĩā¯ˆāŽ¯āŽžāŽŗāŽ°ā¯ āŽ¤āŽ•ā¯āŽ¤āŽŋ", "theme_setting_primary_color_subtitle": "āŽŽā¯āŽ¤āŽŠā¯āŽŽā¯ˆ āŽšā¯†āŽ¯āŽ˛ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽ‰āŽšā¯āŽšāŽ°āŽŋāŽĒā¯āŽĒā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯ āŽ’āŽ°ā¯ āŽĩāŽŖā¯āŽŖāŽ¤ā¯āŽ¤ā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ™ā¯āŽ•āŽŗā¯.", "theme_setting_primary_color_title": "āŽŽā¯āŽ¤āŽŠā¯āŽŽā¯ˆ āŽ¨āŽŋāŽąāŽŽā¯", "theme_setting_system_primary_color_title": "āŽ•āŽŖāŽŋāŽŠāŽŋ āŽ¨āŽŋāŽąāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ™ā¯āŽ•āŽŗā¯", "theme_setting_system_theme_switch": "āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•āŽŋ (āŽ•āŽŖāŽŋāŽŠāŽŋ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒ❈āŽĒā¯ āŽĒāŽŋāŽŠā¯āŽĒāŽąā¯āŽąāŽĩā¯āŽŽā¯)", + "theme_setting_theme_subtitle": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸāŽŋāŽŠā¯ āŽ•āŽ°ā¯āŽĒā¯āŽĒā¯ŠāŽ°ā¯āŽŗā¯ āŽ…āŽŽā¯ˆāŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩā¯āŽšā¯†āŽ¯ā¯āŽ•", + "theme_setting_three_stage_loading_subtitle": "āŽŽā¯‚āŽŠā¯āŽąā¯-āŽ¨āŽŋāŽ˛ā¯ˆ āŽāŽąā¯āŽąā¯āŽ¤āŽ˛ā¯ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽŋāŽŠāŽžāŽ˛ā¯ āŽāŽąā¯āŽąā¯āŽ¤āŽ˛ā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ¤āŽŋāŽąāŽŠā¯ˆ āŽ…āŽ¤āŽŋāŽ•āŽ°āŽŋāŽ•ā¯āŽ•āŽ•ā¯āŽ•ā¯‚āŽŸā¯āŽŽā¯, āŽ†āŽŠāŽžāŽ˛ā¯ āŽ•āŽŖāŽŋāŽšāŽŽāŽžāŽ• āŽŽāŽŋāŽ•ā¯ˆ āŽĒāŽŋāŽŖā¯ˆāŽ¯āŽšā¯ āŽšā¯āŽŽā¯ˆāŽ¯ā¯ˆ āŽāŽąā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ•āŽŋāŽąāŽ¤ā¯", + "theme_setting_three_stage_loading_title": "āŽŽā¯‚āŽŠā¯āŽąā¯-āŽ¨āŽŋāŽ˛ā¯ˆ āŽāŽąā¯āŽąā¯āŽ¤āŽ˛ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "then": "āŽĒāŽŋāŽąāŽ•ā¯", "they_will_be_merged_together": "āŽ…āŽĩāŽ°ā¯āŽ•āŽŗā¯ āŽ’āŽŠā¯āŽąāŽžāŽ• āŽ‡āŽŖā¯ˆāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽĩāŽžāŽ°ā¯āŽ•āŽŗā¯", "third_party_resources": "āŽŽā¯‚āŽŠā¯āŽąāŽžāŽŽā¯ āŽ¤āŽ°āŽĒā¯āŽĒ❁ āŽĩāŽŗāŽ™ā¯āŽ•āŽŗā¯", @@ -1973,15 +2259,23 @@ "trash_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ•ā¯āŽ•ā¯ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "trash_count": "āŽ•ā¯āŽĒā¯āŽĒ❈ {count, number}", "trash_delete_asset": "āŽ•ā¯āŽĒā¯āŽĒ❈/āŽ¨ā¯€āŽ•ā¯āŽ•ā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆ", + "trash_emptied": "āŽ•āŽžāŽ˛āŽŋāŽ¯āŽžāŽ• āŽ•ā¯āŽĒā¯āŽĒ❈", "trash_no_results_message": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸā¯āŽŸāŽŋāŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽ‡āŽ™ā¯āŽ•ā¯‡ āŽ•āŽžāŽŖā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯.", "trash_page_delete_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", + "trash_page_empty_trash_dialog_content": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ•ā¯āŽĒā¯āŽĒ❈ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆ āŽĩā¯†āŽąā¯āŽŽā¯ˆ āŽšā¯†āŽ¯ā¯āŽ¯ āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž? āŽ‡āŽ¨ā¯āŽ¤ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ āŽ‡āŽŽā¯āŽŽāŽŋāŽšā¯āŽšāŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ…āŽ•āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŽā¯", "trash_page_info": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸā¯āŽŸāŽŋāŽ•āŽŗā¯ {days} āŽ¨āŽžāŽŸā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯āŽĒā¯ āŽĒāŽŋāŽąāŽ•ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯", + "trash_page_no_assets": "āŽ•ā¯āŽĒā¯āŽĒ❈ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", + "trash_page_restore_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽŽā¯€āŽŸā¯āŽŸā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "trash_page_select_assets_btn": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯ˆāŽ¤ā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "trash_page_title": "({count})", "trashed_items_will_be_permanently_deleted_after": "āŽ•ā¯āŽĒā¯āŽĒā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ‰āŽŗā¯āŽŗ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ•āŽŗā¯ {days, plural, one {# āŽ¨āŽžāŽŗā¯āŽ•ā¯āŽ•ā¯} other {# āŽ¨āŽžāŽŸā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯}}āŽĒāŽŋāŽąāŽ•ā¯ āŽ¨āŽŋāŽ°āŽ¨ā¯āŽ¤āŽ°āŽŽāŽžāŽ• āŽ¨ā¯€āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŽā¯.", "trigger": "āŽ¤ā¯‚āŽŖā¯āŽŸā¯āŽ¤āŽ˛ā¯", - "trigger_asset_uploaded": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽŽā¯", + "trigger_asset_uploaded": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "trigger_asset_uploaded_description": "āŽĒā¯āŽ¤āŽŋāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŽā¯ āŽĒā¯‹āŽ¤ā¯ āŽ¤ā¯‚āŽŖā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "trigger_description": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•ā¯āŽŽā¯ āŽ’āŽ°ā¯ āŽ¨āŽŋāŽ•āŽ´ā¯āŽĩ❁", "trigger_person_recognized": "āŽ…āŽŸā¯ˆāŽ¯āŽžāŽŗāŽŽā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ¨āŽĒāŽ°ā¯", - "trigger_person_recognized_description": "āŽ’āŽ°ā¯ āŽ¨āŽĒāŽ°ā¯ āŽ…āŽŸā¯ˆāŽ¯āŽžāŽŗāŽŽā¯ āŽ•āŽžāŽŖāŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ˛ā¯ āŽ¤ā¯‚āŽŖā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽąāŽ¤ā¯", + "trigger_person_recognized_description": "āŽ’āŽ°ā¯ āŽ¨āŽĒāŽ°ā¯ āŽ•āŽŖā¯āŽŸāŽąāŽŋāŽ¯āŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ˛ā¯ āŽ¤ā¯‚āŽŖā¯āŽŸāŽĒā¯āŽĒāŽŸā¯āŽ•āŽŋāŽąāŽ¤ā¯", + "trigger_type": "āŽ¤ā¯‚āŽŖā¯āŽŸā¯āŽ¤āŽ˛ā¯ āŽĩāŽ•ā¯ˆ", "troubleshoot": "āŽšāŽ°āŽŋāŽšā¯†āŽ¯ā¯āŽ¤āŽ˛ā¯", "type": "āŽĩāŽ•ā¯ˆ", "unable_to_change_pin_code": "āŽŽā¯āŽŗā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆ āŽŽāŽžāŽąā¯āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ", @@ -2009,12 +2303,14 @@ "unsaved_change": "āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽŽāŽžāŽąā¯āŽąāŽŽā¯", "unselect_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽĩ❁ āŽšā¯†āŽ¯ā¯āŽ¯ā¯āŽ™ā¯āŽ•āŽŗā¯", "unselect_all_duplicates": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ āŽ¨āŽ•āŽ˛ā¯āŽ•āŽŗā¯ˆāŽ¯ā¯āŽŽā¯ āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "unselect_all_in": "{group}", "unstack": "āŽ…āŽŠā¯-āŽšā¯āŽŸāŽžāŽ•ā¯", "unstack_action_prompt": "{count} āŽ¤āŽŸā¯ˆāŽ¯āŽŋāŽŠā¯āŽąāŽŋ", "unstacked_assets_count": "āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ {count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯}}", "unsupported_field_type": "āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽĒā¯āŽ˛ āŽĩāŽ•ā¯ˆ", "unsupported_file_type": "āŽ•ā¯‹āŽĒā¯āŽĒ❈ {file} āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽŽā¯āŽŸāŽŋāŽ¯āŽžāŽ¤ā¯, āŽāŽŠā¯†āŽŠāŽŋāŽ˛ā¯ āŽ…āŽ¤āŽŠā¯ āŽ•ā¯‹āŽĒā¯āŽĒ❁ āŽĩāŽ•ā¯ˆ {type} āŽ†āŽ¤āŽ°āŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽĩāŽŋāŽ˛ā¯āŽ˛ā¯ˆ.", "untagged": "āŽ…āŽĩāŽŋāŽ´ā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸāŽžāŽ¤āŽ¤ā¯", + "untitled_workflow": "āŽĒā¯†āŽ¯āŽ°āŽŋāŽŸāŽĒā¯āŽĒāŽŸāŽžāŽ¤ āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁", "up_next": "āŽ…āŽŸā¯āŽ¤ā¯āŽ¤ā¯", "update_location_action_prompt": "{count} āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ‡āŽ°ā¯āŽĒā¯āŽĒāŽŋāŽŸāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯:", "updated_at": "āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", @@ -2022,6 +2318,8 @@ "upload": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąā¯āŽŽā¯", "upload_concurrency": "āŽ’āŽ¤ā¯āŽ¤āŽŋāŽšā¯ˆāŽĩ❈āŽĒā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĩā¯āŽŽā¯", "upload_details": "āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯ˆ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĩā¯āŽŽā¯", + "upload_dialog_info": "āŽ¤ā¯‡āŽ°ā¯āŽ¨ā¯āŽ¤ā¯†āŽŸā¯āŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ (āŽ•āŽŗā¯ˆ) āŽšā¯‡āŽĩā¯ˆāŽ¯āŽ•āŽ¤ā¯āŽ¤āŽŋāŽąā¯āŽ•ā¯ āŽ•āŽžāŽĒā¯āŽĒ❁āŽĒā¯ āŽĒāŽŋāŽ°āŽ¤āŽŋ āŽŽāŽŸā¯āŽ•ā¯āŽ• āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", + "upload_dialog_title": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĩā¯āŽŽā¯", "upload_error_with_count": "{count, plural, one {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•ā¯} other {# āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•ā¯āŽ•āŽŗā¯āŽ•ā¯āŽ•ā¯}} āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽĒā¯ āŽĒāŽŋāŽ´ā¯ˆ", "upload_errors": "{count, plural, one {# āŽĒāŽŋāŽ´ā¯ˆ} other {# āŽĒāŽŋāŽ´ā¯ˆāŽ•āŽŗā¯}}āŽŽā¯‚āŽ˛āŽŽā¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽŽā¯ āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯, āŽĒā¯āŽ¤āŽŋāŽ¯ āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽą āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĒā¯ āŽĒāŽ•ā¯āŽ•āŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯.", "upload_finished": "āŽĒāŽ¤āŽŋāŽĩā¯‡āŽąā¯āŽąāŽŽā¯ āŽŽā¯āŽŸāŽŋāŽ¨ā¯āŽ¤āŽ¤ā¯", @@ -2050,6 +2348,7 @@ "user_privacy": "āŽĒāŽ¯āŽŠāŽ°ā¯ āŽ¤āŽŠāŽŋāŽ¯ā¯āŽ°āŽŋāŽŽā¯ˆ", "user_purchase_settings": "āŽĩāŽžāŽ™ā¯āŽ•", "user_purchase_settings_description": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĩāŽžāŽ™ā¯āŽ•ā¯āŽ¤āŽ˛ā¯ˆ āŽ¨āŽŋāŽ°ā¯āŽĩāŽ•āŽŋāŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", + "user_role_set": "{user}āŽ {role} āŽ†āŽ• āŽ…āŽŽā¯ˆ", "user_usage_detail": "āŽĒāŽ¯āŽŠāŽ°ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽĩāŽŋāŽĩāŽ°āŽŽā¯", "user_usage_stats": "āŽ•āŽŖāŽ•ā¯āŽ•ā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽŸā¯ āŽĒā¯āŽŗā¯āŽŗāŽŋāŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯", "user_usage_stats_description": "āŽ•āŽŖāŽ•ā¯āŽ•ā¯ āŽ‰āŽĒāŽ¯ā¯‹āŽ•āŽĒā¯ āŽĒā¯āŽŗā¯āŽŗāŽŋāŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•", @@ -2059,6 +2358,7 @@ "utilities": "āŽĒāŽ¯āŽŠā¯āŽĒāŽžāŽŸā¯āŽ•āŽŗā¯", "validate": "āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "validate_endpoint_error": "āŽ¤āŽ¯āŽĩā¯āŽšā¯†āŽ¯ā¯āŽ¤ā¯ āŽ’āŽ°ā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽĒāŽŸāŽŋāŽ¯āŽžāŽ•ā¯āŽŽā¯ URL āŽ āŽ‰āŽŗā¯āŽŗāŽŋāŽŸāŽĩā¯āŽŽā¯", + "validation_error": "āŽšāŽ°āŽŋāŽĒāŽžāŽ°ā¯āŽĒā¯āŽĒ❁ āŽĒāŽŋāŽ´ā¯ˆ", "variables": "āŽŽāŽžāŽąāŽŋāŽ•āŽŗā¯", "version": "āŽĒāŽ¤āŽŋāŽĒā¯āŽĒ❁", "version_announcement_closing": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽ¨āŽŖā¯āŽĒāŽ°ā¯, āŽ…āŽ˛ā¯†āŽ•ā¯āŽšā¯", @@ -2070,6 +2370,7 @@ "video_hover_setting_description": "āŽŽāŽĩā¯āŽšā¯ āŽ‰āŽ°ā¯āŽĒā¯āŽĒāŽŸāŽŋāŽ¯ā¯ˆāŽ•ā¯ āŽ•ā¯ŠāŽŖā¯āŽŸā¯ āŽšā¯†āŽ˛ā¯āŽ˛ā¯āŽŽā¯āŽĒā¯‹āŽ¤ā¯ āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹ āŽšāŽŋāŽąā¯ āŽ‰āŽ°ā¯āŽĩāŽ¤ā¯āŽ¤ā¯ˆ āŽ‡āŽ¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯. āŽŽā¯āŽŸāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽžāŽ˛ā¯āŽŽā¯ āŽ•ā¯‚āŽŸ, āŽĒāŽŋāŽŗā¯‡ āŽāŽ•āŽžāŽŠā¯āŽ•ā¯āŽ•ā¯ āŽŽā¯‡āŽ˛ā¯ āŽšā¯āŽąā¯āŽąā¯āŽĩāŽ¤āŽŠā¯ āŽŽā¯‚āŽ˛āŽŽā¯ āŽĒāŽŋāŽŗā¯‡āŽĒā¯‡āŽ•ā¯āŽ•ā¯ˆāŽ¤ā¯ āŽ¤ā¯ŠāŽŸāŽ™ā¯āŽ•āŽ˛āŽžāŽŽā¯.", "videos": "āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯", "videos_count": "{count, plural, one {# āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋ} other {# āŽ•āŽžāŽŖā¯ŠāŽŗāŽŋāŽ•āŽŗā¯}}", + "videos_only": "āŽĩā¯€āŽŸāŽŋāŽ¯ā¯‹āŽ•ā¯āŽ•āŽŗā¯ āŽŽāŽŸā¯āŽŸā¯āŽŽā¯‡", "view": "āŽĒāŽžāŽ°ā¯āŽĩ❈", "view_album": "āŽ†āŽ˛ā¯āŽĒāŽ¤ā¯āŽ¤ā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽ•", "view_all": "āŽ…āŽŠā¯ˆāŽ¤ā¯āŽ¤ā¯ˆāŽ¯ā¯āŽŽā¯ āŽ•āŽžāŽŖā¯āŽ•", @@ -2078,15 +2379,20 @@ "view_details": "āŽĩāŽŋāŽĩāŽ°āŽ™ā¯āŽ•āŽŗā¯ˆāŽĒā¯ āŽĒāŽžāŽ°ā¯", "view_in_timeline": "āŽ•āŽžāŽ˛āŽĩāŽ°āŽŋāŽšā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽ•āŽžāŽŖā¯āŽ•", "view_link": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽ•", + "view_links": "āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŖā¯āŽ•", "view_name": "āŽĒāŽžāŽ°ā¯āŽĩ❈", "view_next_asset": "āŽ…āŽŸā¯āŽ¤ā¯āŽ¤ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆ āŽ•āŽžāŽŖā¯āŽ•", "view_previous_asset": "āŽŽā¯āŽ¨ā¯āŽ¤ā¯ˆāŽ¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ˆāŽĒā¯ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "view_qr_code": "QR āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯āŽŸā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "view_similar_photos": "āŽ‡āŽ¤ā¯‡ āŽĒā¯‹āŽŠā¯āŽą āŽĒā¯āŽ•ā¯ˆāŽĒā¯āŽĒāŽŸāŽ™ā¯āŽ•āŽŗā¯ˆāŽ•ā¯ āŽ•āŽžāŽŸā¯āŽŸā¯", "view_stack": "āŽ•āŽžāŽŖā¯āŽ• āŽ…āŽŸā¯āŽ•ā¯āŽ•ā¯", + "view_user": "āŽĒāŽ¯āŽŠāŽ°ā¯ˆāŽĒā¯ āŽĒāŽžāŽ°ā¯āŽ•ā¯āŽ•āŽĩā¯āŽŽā¯", "viewer_remove_from_stack": "āŽ…āŽŸā¯āŽ•ā¯āŽ•āŽŋāŽ˛āŽŋāŽ°ā¯āŽ¨ā¯āŽ¤ā¯ āŽ…āŽ•āŽąā¯āŽąā¯", + "viewer_stack_use_as_main_asset": "āŽĒāŽŋāŽ°āŽ¤āŽžāŽŠ āŽšā¯ŠāŽ¤ā¯āŽ¤āŽžāŽ•āŽĒā¯ āŽĒāŽ¯āŽŠā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", + "viewer_unstack": "āŽ…āŽŸā¯āŽ•ā¯āŽ•ā¯ˆ āŽ¨ā¯€āŽ•ā¯āŽ•ā¯", "visibility_changed": "{count, plural, one {# āŽ¨āŽĒāŽ°ā¯} other {# āŽ¨āŽĒāŽ°ā¯āŽ•āŽŗā¯}} āŽ•ā¯āŽ•āŽžāŽŠ āŽ¤ā¯†āŽ°āŽŋāŽĩā¯āŽ¨āŽŋāŽ˛ā¯ˆ āŽŽāŽžāŽąā¯āŽąāŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "visual": "āŽ•āŽžāŽŸā¯āŽšāŽŋ", + "visual_builder": "āŽ•āŽžāŽŸā¯āŽšāŽŋ āŽ‰āŽ°ā¯āŽĩāŽžāŽ•ā¯āŽ•ā¯āŽĒāŽĩāŽ°ā¯", "waiting": "āŽ•āŽžāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯", "waiting_count": "āŽ•āŽžāŽ¤ā¯āŽ¤āŽŋāŽ°ā¯āŽ•ā¯āŽ•āŽŋāŽąāŽ¤ā¯: {count}", "warning": "āŽŽāŽšā¯āŽšāŽ°āŽŋāŽ•ā¯āŽ•ā¯ˆ", @@ -2105,6 +2411,7 @@ "workflow_navigation_prompt": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽŽāŽžāŽąā¯āŽąāŽ™ā¯āŽ•āŽŗā¯ˆāŽšā¯ āŽšā¯‡āŽŽāŽŋāŽ•ā¯āŽ•āŽžāŽŽāŽ˛ā¯ āŽ¨āŽŋāŽšā¯āŽšāŽ¯āŽŽāŽžāŽ• āŽĩā¯†āŽŗāŽŋāŽ¯ā¯‡āŽą āŽĩāŽŋāŽ°ā¯āŽŽā¯āŽĒā¯āŽ•āŽŋāŽąā¯€āŽ°ā¯āŽ•āŽŗāŽž?", "workflow_summary": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽšā¯āŽ°ā¯āŽ•ā¯āŽ•āŽŽā¯", "workflow_update_success": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽĩā¯†āŽąā¯āŽąāŽŋāŽ•āŽ°āŽŽāŽžāŽ• āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", + "workflow_updated": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩ❁ āŽĒā¯āŽ¤ā¯āŽĒā¯āŽĒāŽŋāŽ•ā¯āŽ•āŽĒā¯āŽĒāŽŸā¯āŽŸāŽ¤ā¯", "workflows": "āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩā¯āŽ•āŽŗā¯", "workflows_help_text": "āŽ¤ā¯‚āŽŖā¯āŽŸā¯āŽ¤āŽ˛ā¯āŽ•āŽŗā¯ āŽŽāŽąā¯āŽąā¯āŽŽā¯ āŽĩāŽŸāŽŋāŽĒā¯āŽĒāŽžāŽŠā¯āŽ•āŽŗāŽŋāŽŠā¯ āŽ…āŽŸāŽŋāŽĒā¯āŽĒāŽŸā¯ˆāŽ¯āŽŋāŽ˛ā¯ āŽĒāŽŖāŽŋāŽĒā¯āŽĒāŽžāŽ¯ā¯āŽĩā¯āŽ•āŽŗā¯ āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯āŽ•āŽŗāŽŋāŽ˛ā¯ āŽšā¯†āŽ¯āŽ˛ā¯āŽ•āŽŗā¯ˆ āŽ¤āŽžāŽŠāŽŋāŽ¯āŽ™ā¯āŽ•ā¯āŽĒāŽŸā¯āŽ¤ā¯āŽ¤ā¯āŽ•āŽŋāŽŠā¯āŽąāŽŠ", "wrong_pin_code": "āŽ¤āŽĩāŽąāŽžāŽŠ āŽĒāŽŋāŽŠā¯ āŽ•ā¯āŽąāŽŋāŽ¯ā¯€āŽŸā¯", @@ -2114,5 +2421,6 @@ "you_dont_have_any_shared_links": "āŽ‰āŽ™ā¯āŽ•āŽŗāŽŋāŽŸāŽŽā¯ āŽĒāŽ•āŽŋāŽ°āŽĒā¯āŽĒāŽŸā¯āŽŸ āŽ‡āŽŖā¯ˆāŽĒā¯āŽĒā¯āŽ•āŽŗā¯ āŽŽāŽ¤ā¯āŽĩā¯āŽŽā¯ āŽ‡āŽ˛ā¯āŽ˛ā¯ˆ", "your_wifi_name": "āŽ‰āŽ™ā¯āŽ•āŽŗā¯ āŽĩā¯ˆāŽƒāŽĒ❈ āŽĒā¯†āŽ¯āŽ°ā¯", "zero_to_clear_rating": "āŽšā¯ŠāŽ¤ā¯āŽ¤ā¯ āŽŽāŽ¤āŽŋāŽĒā¯āŽĒā¯€āŽŸā¯āŽŸā¯ˆ āŽ…āŽ´āŽŋāŽ•ā¯āŽ• 0 āŽ āŽ…āŽ´ā¯āŽ¤ā¯āŽ¤āŽĩā¯āŽŽā¯", - "zoom_image": "āŽĒā¯†āŽ°āŽŋāŽ¤āŽžāŽ•ā¯āŽ• āŽĒāŽŸāŽŽā¯" + "zoom_image": "āŽĒā¯†āŽ°āŽŋāŽ¤āŽžāŽ•ā¯āŽ• āŽĒāŽŸāŽŽā¯", + "zoom_to_bounds": "āŽŽāŽ˛ā¯āŽ˛ā¯ˆāŽ•ā¯āŽ•ā¯ āŽĒā¯†āŽ°āŽŋāŽ¤āŽžāŽ•ā¯āŽ•ā¯" } diff --git a/i18n/te.json b/i18n/te.json index 15921d7dcc..73288c1a8e 100644 --- a/i18n/te.json +++ b/i18n/te.json @@ -8,30 +8,32 @@ "action_description": "ā°Ģā°ŋā°˛āąā°Ÿā°°āą ā°šāą‡ā°¸ā°ŋā°¨ ā°…ā°‚ā°ļā°žā°˛ā°Ēāąˆ ā°šāą‡ā°¯ā°žā°˛āąā°¸ā°ŋā°¨ ā°šā°°āąā°¯ā°˛āą", "actions": "ā°šā°°āąā°¯ā°˛āą", "active": "ā°šāąā°°āąā°•āąā°—ā°ž", - "active_count": "ā°•āąā°°ā°ŋā°¯ā°žā°ļāą€ā°˛ ā°¸ā°‚ā°–āąā°¯", "activity": "ā°•ā°žā°°āąā°¯ā°žā°šā°°ā°Ŗ", + "activity_changed": "ā°•ā°žā°°āąā°¯ā°•ā°˛ā°žā°Ēā°‚ {enabled, select, true {enabled} other {disabled}}", "add": "ā°œāą‹ā°Ąā°ŋā°‚ā°šāą", "add_a_description": "ā°ĩā°ŋā°ĩā°°ā°Ŗ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_a_location": "ā°¸āąā°Ĩā°žā°¨ā°žā°¨āąā°¨ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_a_name": "ā°Ēāą‡ā°°āąā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_a_title": "ā°ļāą€ā°°āąā°ˇā°ŋā°•ā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "add_action": "ā°•āąŠā°¤āąā°¤ ā°šā°°āąā°¯ā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_birthday": "ā°Ēāąā°Ÿāąā°Ÿā°ŋā°¨ā°°āą‹ā°œāąā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_endpoint": "ā°Žāąā°—ā°ŋā°‚ā°Ēāą ā°Ŧā°ŋā°‚ā°Ļāąā°ĩāąā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_exclusion_pattern": "ā°Žā°ŋā°¨ā°šā°žā°¯ā°ŋā°‚ā°Ēāą ā°¨ā°Žāą‚ā°¨ā°žā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_location": "ā°¸āąā°Ĩā°žā°¨ā°žā°¨āąā°¨ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", + "add_more_users": "ā°Žā°°ā°ŋā°‚ā°¤ ā°Žā°‚ā°Ļā°ŋ ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛ā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_partner": "ā°­ā°žā°—ā°¸āąā°ĩā°žā°Žā°ŋā°¨ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", + "add_path": "ā°Žā°žā°°āąā°—ā°žā°¨āąā°¨ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_photos": "ā°Ģāą‹ā°Ÿāą‹ā°˛ā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "add_step": "ā°•āąŠā°¤āąā°¤ ā°Ļā°ļā°¨āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_tag": "ā°Ÿāąā°¯ā°žā°—āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_to": "ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋâ€Ļ", "add_to_album": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_to_album_bottom_sheet_added": "{album} ā°˛āą‹ā°•ā°ŋ ā°šāą‡ā°°āąā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "add_to_album_bottom_sheet_already_exists": "ā°‡ā°Ēāąā°ĒⰟā°ŋā°•āą‡ {album} ā°˛āą‹ ⰉⰂā°Ļā°ŋ", "add_to_album_bottom_sheet_some_local_assets": "ā°•āąŠā°¨āąā°¨ā°ŋ ā°¸āąā°Ĩā°žā°¨ā°ŋā°• ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°Ąā°‚ ā°¸ā°žā°§āąā°¯ā°‚ ā°•ā°žā°˛āą‡ā°Ļāą.", + "add_to_album_toggle": "{album} ā°•āą‹ā°¸ā°‚ ā°Žā°‚ā°Ēā°ŋā°• ā°Ÿā°žā°—āąā°˛āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "add_to_albums": "ā°†ā°˛āąā°Ŧā°Žāąā°¸āąâ€Œā°˛āą‹ ā°šāą‡ā°°āąā°šāą", "add_to_albums_count": "ā°†ā°˛āąā°Ŧā°Žāąā°¸āąâ€Œā°˛āą‹ ā°šāą‡ā°°āąā°šāą ({count})", "add_to_bottom_bar": "ā°šāą‡ā°°āąā°šāą", + "add_to_shared_album": "ā°­ā°žā°—ā°¸āąā°ĩā°žā°Žāąā°¯ ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "add_upload_to_stack": "ā°…ā°Ēāąā°˛āą‹ā°Ąāąâ€Œā°¨āą ā°¸āąā°Ÿā°žā°•āąâ€Œā°˛āą‹ ā°šāą‡ā°°āąā°šāą", "add_url": "URLā°¨ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "added_to_archive": "ā°†ā°°āąā°•āąˆā°ĩāąâ€Œā°•ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", @@ -47,17 +49,17 @@ "authentication_settings_reenable": "ā°Žā°ŗāąā°˛āą€ ā°Ēāąā°°ā°žā°°ā°‚ā°Ŧā°ŋā°‚ā°šā°Ÿā°žā°¨ā°ŋā°•ā°ŋ, Server Commandā°¨ā°ŋ ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ.", "background_task_job": "ā°¨āą‡ā°Ēā°Ĩāąā°¯ ā°Ēā°¨āąā°˛āą", "backup_database": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ąā°‚ā°Ēāą ā°¸āąƒā°ˇāąā°Ÿā°ŋā°‚ā°šāą", - "backup_database_enable_description": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ąā°‚ā°Ēāąâ€Œā°˛ā°¨āą ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "backup_keep_last_amount": "ā°‰ā°‚ā°šā°žā°˛āąā°¸ā°ŋā°¨ ā°Žāąā°¨āąā°ĒⰟā°ŋ ā°Ąā°‚ā°Ēāąâ€Œā°˛ ā°¸ā°‚ā°–āąā°¯", + "backup_database_enable_description": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ēā°Ąā°ĩāą†ā°¯āąā°¯ā°Ąā°žā°¨āąā°¨āą€ ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", + "backup_keep_last_amount": "ā°‰ā°‚ā°šāąā°•āą‹ā°ĩā°žā°˛āąā°¸ā°ŋā°¨ ā°Žāąā°¨āąā°ĒⰟā°ŋ ā°Ēā°Ąā°ĩāą†ā°¯āąā°¯ā°Ąā°žā°˛āąā°˛ā°ž ā°ŽāąŠā°¤āąā°¤ā°‚", "backup_onboarding_1_description": "ā°•āąā°˛āąŒā°Ąāąâ€Œā°˛āą‹ ā°˛āą‡ā°Ļā°ž ā°Žā°°āą‹ ā°­āąŒā°¤ā°ŋā°• ā°Ēāąā°°ā°Ļāą‡ā°ļā°‚ā°˛āą‹ ā°†ā°Ģāąā°¸āąˆā°Ÿāą ā°•ā°žā°Ēāą€", - "backup_onboarding_2_description": "ā°ĩāą‡ā°°āąā°ĩāą‡ā°°āą ā°Ēā°°ā°ŋā°•ā°°ā°žā°˛āąā°˛āą‹ ā°¨ā°ŋā°˛āąā°ĩ ā°šāą‡ā°¸ā°ŋā°¨ ā°¸āąā°Ĩā°žā°¨ā°ŋā°• ā°•ā°žā°Ēāą€ā°˛āą. ⰇⰂā°Ļāąā°˛āą‹ ā°…ā°¸ā°˛āą ā°Ģāąˆā°˛āąâ€Œā°˛āą ā°Žā°°ā°ŋā°¯āą ā°ĩā°žā°Ÿā°ŋ ā°¸āąā°Ĩā°žā°¨ā°ŋā°• ā°Ŧāąā°¯ā°žā°•ā°Ēāą ā°•āą‚ā°Ąā°ž ā°‰ā°‚ā°Ÿā°žā°¯ā°ŋ.", + "backup_onboarding_2_description": "ā°ĩāą‡ā°°āąā°ĩāą‡ā°°āą ā°Ēā°°ā°ŋā°•ā°°ā°žā°˛āąā°˛āą‹ ā°˛āą‹ā°•ā°˛āą ā°•ā°žā°Ēāą€ā°˛āą. ⰇⰂā°Ļāąā°˛āą‹ ā°Ēāąā°°ā°§ā°žā°¨ ā°Ģāąˆā°ŗāąā°ŗāą ā°Žā°°ā°ŋā°¯āą ā°ĩā°žā°Ÿā°ŋā°•ā°ŋ ⰏⰂā°Ŧā°‚ā°§ā°ŋā°‚ā°šā°ŋā°¨ ā°˛āą‹ā°•ā°˛āą ā°Ŧāąā°¯ā°žā°•ā°Ēāą ā°•āą‚ā°Ąā°ž ā°‰ā°‚ā°Ÿā°žā°¯ā°ŋ", "backup_onboarding_3_description": "ā°Žāą€ ā°Ąāą‡ā°Ÿā°žā°•āą ā°ŽāąŠā°¤āąā°¤ā°‚ ā°•ā°žā°Ēāą€ā°˛āą, ā°…ā°¸ā°˛āą ā°Ģāąˆā°ŗāąā°ŗāą ā°¸ā°šā°ž. ⰇⰂā°Ļāąā°˛āą‹ 1 ā°†ā°Ģāąā°¸āąˆā°Ÿāą ā°•ā°žā°Ēāą€ ā°Žā°°ā°ŋā°¯āą 2 ā°˛āą‹ā°•ā°˛āą ā°•ā°žā°Ēāą€ā°˛āą ā°‰ā°‚ā°Ÿā°žā°¯ā°ŋ.", "backup_onboarding_description": "ā°Žāą€ ā°Ąāą‡ā°Ÿā°žā°¨āą ā°°ā°•āąā°ˇā°ŋā°‚ā°šā°Ąā°žā°¨ā°ŋā°•ā°ŋ 3-2-1 ā°Ŧāąā°¯ā°žā°•ā°Ēāą ā°ĩāąā°¯āą‚ā°šā°‚ ā°¸ā°ŋā°Ģā°žā°°āąā°¸āą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ. ā°Ēāą‚ā°°āąā°¤ā°ŋ ā°¸āąā°Ĩā°žā°¯ā°ŋ ā°Ŧāąā°¯ā°žā°•ā°Ēāą ā°Ēā°°ā°ŋā°ˇāąā°•ā°žā°°ā°‚ ā°•āą‹ā°¸ā°‚ ā°Žāą€ā°°āą ā°…ā°Ēāąā°˛āą‹ā°Ąāą ā°šāą‡ā°¸ā°ŋā°¨ ā°Ģāą‹ā°Ÿāą‹ā°˛āą/ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛ ā°•ā°žā°Ēāą€ā°˛āą ā°Žā°°ā°ŋā°¯āą Immich ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°•ā°žā°Ēāą€ā°¨ā°ŋ ā°‰ā°‚ā°šā°žā°˛ā°ŋ.", "backup_onboarding_footer": "Immich ā°Ŧāąā°¯ā°žā°•ā°Ēāąâ€Œā°Ēāąˆ ā°Žā°°ā°ŋā°¨āąā°¨ā°ŋ ā°ĩā°ŋā°ĩā°°ā°žā°˛ā°•āą ā°Ąā°žā°•āąā°¯āąā°Žāą†ā°‚ā°Ÿāą‡ā°ˇā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ.", "backup_onboarding_parts_title": "3-2-1 ā°Ŧāąā°¯ā°žā°•ā°Ēāąâ€Œā°˛āą‹:", "backup_onboarding_title": "ā°Ŧāąā°¯ā°žā°•ā°Ēāąā°˛āą", - "backup_settings": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ąā°‚ā°Ēāą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", - "backup_settings_description": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ąā°‚ā°Ēāą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛ā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ.", + "backup_settings": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ēā°Ąā°ĩāą†ā°¸āą‡ ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", + "backup_settings_description": "ā°Ąāą‡ā°Ÿā°žā°Ŧāą‡ā°¸āą ā°Ēā°Ąā°ĩāą†ā°¸āą‡ ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛ā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "cleared_jobs": "ā°Ļāą€ā°¨ā°ŋ ā°•āą‹ā°¸ā°‚ ā°‰ā°Ļāąā°¯āą‹ā°—ā°žā°˛āą ā°•āąā°˛ā°ŋā°¯ā°°āą ā°šāą‡ā°¯ā°Ŧā°Ąāąā°Ąā°žā°¯ā°ŋ: {job}", "config_set_by_file": "ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āą ā°Ēāąā°°ā°¸āąā°¤āąā°¤ā°‚ ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āą ā°Ģāąˆā°˛āą ā°Ļāąā°ĩā°žā°°ā°ž ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "confirm_delete_library": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {library} ā°˛āąˆā°Ŧāąā°°ā°°āą€ā°¨ā°ŋ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", @@ -65,19 +67,15 @@ "confirm_email_below": "ā°¨ā°ŋā°°āąā°§ā°žā°°ā°ŋā°‚ā°šā°Ąā°žā°¨ā°ŋā°•ā°ŋ, ā°•āąā°°ā°ŋā°‚ā°Ļ \"{email}\" ā°Ÿāąˆā°Ēāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "confirm_reprocess_all_faces": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž ā°…ā°¨āąā°¨ā°ŋ ā°Žāąā°–ā°žā°˛ā°¨āą ā°°āą€ā°Ēāąā°°ā°žā°¸āą†ā°¸āą ā°šāą‡ā°¯ā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž? ā°‡ā°Ļā°ŋ ā°Ēāą‡ā°°āąā°¨āąā°¨ ā°ĩāąā°¯ā°•āąā°¤āąā°˛ā°¨āą ā°•āą‚ā°Ąā°ž ā°•āąā°˛ā°ŋā°¯ā°°āą ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ.", "confirm_user_password_reset": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {user} ā°Ēā°žā°¸āąâ€Œā°ĩā°°āąā°Ąāąâ€Œā°¨ā°ŋ ā°°āą€ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", - "confirm_user_pin_code_reset": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {user} ā°¯āąŠā°•āąā°• PIN ā°•āą‹ā°Ąāąâ€Œā°¨āą ā°°āą€ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", - "copy_config_to_clipboard_description": "ā°Ēāąā°°ā°¸āąā°¤āąā°¤ ā°¸ā°ŋā°¸āąā°Ÿā°Žāą ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āąâ€Œā°¨āą JSON ā°†ā°Ŧāąā°œāą†ā°•āąā°Ÿāąâ€Œā°—ā°ž ā°•āąā°˛ā°ŋā°Ēāąâ€Œā°Ŧāą‹ā°°āąā°Ąāąâ€Œā°˛āą‹ā°•ā°ŋ ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", + "confirm_user_pin_code_reset": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {user} ā°¯āąŠā°•āąā°• ā°Ēā°ŋā°¨āą ā°•āą‹ā°Ąāą ā°¨āą€ ā°°āą€ā°¸āą†ā°Ÿāą ā°šāą‡ā°Ļāąā°Ļā°žā°Žā°¨ā°ŋā°…ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", "create_job": "ā°Ēā°¨ā°ŋā°¨ā°ŋ ā°¸āąƒā°ˇāąā°Ÿā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "cron_expression": "ā°•āąā°°ā°žā°¨āą ā°ĩāąā°¯ā°•āąā°¤āą€ā°•ā°°ā°Ŗ", "cron_expression_description": "ā°•āąā°°ā°žā°¨āą ā°Ģā°žā°°āąā°Žā°žā°Ÿāą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°ŋ ā°¸āąā°•ā°žā°¨ā°ŋā°‚ā°—āą ā°ĩā°ŋā°°ā°žā°Žā°žā°¨āąā°¨ā°ŋ ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ. ā°Žā°°ā°ŋā°¨āąā°¨ā°ŋ ā°ĩā°ŋā°ĩā°°ā°žā°˛ ā°•āą‹ā°¸ā°‚ ā°Ļā°¯ā°šāą‡ā°¸ā°ŋ ā°‰ā°Ļā°ž. ā°•āąā°°āą‹ā°‚ā°Ÿā°žā°Ŧāą ā°—āąā°°āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", "cron_expression_presets": "ā°Žāąā°‚ā°Ļāą‡ā°šāą‡ā°¸ā°ŋā°¨ ā°•āąā°°ā°žā°¨āą ā°ĩāąā°¯ā°•āąā°¤āą€ā°•ā°°ā°Ŗā°˛āą", "disable_login": "ā°˛ā°žā°—ā°ŋā°¨āąâ€Œā°¨āą ā°¨ā°ŋā°˛ā°ŋā°Ēā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", - "download_csv": "CSV ā°Ģāąˆā°˛āąâ€Œā°¨āą ā°ĄāąŒā°¨āąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "duplicate_detection_job_description": "ā°¸ā°žā°°āą‚ā°Ēāąā°¯ Ⱊā°ŋā°¤āąā°°ā°žā°˛ā°¨āą ā°—āąā°°āąā°¤ā°ŋā°‚ā°šā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°†ā°¸āąā°¤āąā°˛ā°Ēāąˆ ā°¯ā°‚ā°¤āąā°° ā°…ā°­āąā°¯ā°žā°¸ā°žā°¨āąā°¨ā°ŋ ā°…ā°Žā°˛āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ. ā°¸āąā°Žā°žā°°āąā°Ÿāą ā°ļāą‹ā°§ā°¨ā°Ēāąˆ ā°†ā°§ā°žā°°ā°Ēā°Ąāąā°¤āąā°‚ā°Ļā°ŋ", "exclusion_pattern_description": "ā°Žā°ŋā°¨ā°šā°žā°¯ā°ŋā°‚ā°Ēāą ā°¨ā°Žāą‚ā°¨ā°žā°˛āą ā°Žāą€ ā°˛āąˆā°Ŧāąā°°ā°°āą€ā°¨ā°ŋ ā°¸āąā°•ā°žā°¨āą ā°šāą‡ā°¸āąā°¤āąā°¨āąā°¨ā°Ēāąā°Ēāąā°Ąāą ā°Ģāąˆā°˛āąâ€Œā°˛āą ā°Žā°°ā°ŋā°¯āą ā°Ģāą‹ā°˛āąā°Ąā°°āąâ€Œā°˛ā°¨āą ā°ĩā°ŋā°¸āąā°Žā°°ā°ŋā°‚ā°šā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Žā°ŋā°Žāąā°Žā°˛āąā°¨ā°ŋ ā°…ā°¨āąā°Žā°¤ā°ŋā°¸āąā°¤ā°žā°¯ā°ŋ. ā°Žāą€ā°°āą ā°Ļā°ŋā°—āąā°Žā°¤ā°ŋ ā°šāą‡ā°¯ā°•āą‚ā°Ąā°Ļā°¨āąā°•āąā°¨āą‡ RAW ā°Ģāąˆā°˛āąâ€Œā°˛āą ā°ĩā°‚ā°Ÿā°ŋ ā°Ģāą‹ā°˛āąā°Ąā°°āąâ€Œā°˛ā°¨āą ā°•ā°˛ā°ŋā°—ā°ŋ ā°‰ā°¨āąā°¨ā°Ÿāąā°˛ā°¯ā°ŋā°¤āą‡ ā°‡ā°Ļā°ŋ ā°‰ā°Ēā°¯āą‹ā°—ā°•ā°°ā°‚ā°—ā°ž ā°‰ā°‚ā°Ÿāąā°‚ā°Ļā°ŋ.", - "export_config_as_json_description": "ā°Ēāąā°°ā°¸āąā°¤āąā°¤ ā°¸ā°ŋā°¸āąā°Ÿā°Žāą ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āąâ€Œā°¨āą JSON ā°Ģāąˆā°˛āąâ€Œā°—ā°ž ā°ĄāąŒā°¨āąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", - "external_libraries_page_description": "ā°¨ā°ŋā°°āąā°ĩā°žā°šā°•āąā°Ąāą - ā°Žā°•āąā°¸āąâ€Œā°Ÿā°°āąā°¨ā°˛āą ā°˛āąˆā°Ŧāąā°°ā°°āą€ ā°Ēāą‡ā°œāą€", - "face_detection": "ā°Žāąā°–ā°žā°˛ ā°—āąā°°āąā°¤ā°ŋā°‚ā°Ēāą", + "face_detection": "ā°Žāąā°– ā°—ā°Žā°¨ā°ŋā°‚ā°Ēāą", "face_detection_description": "ā°Žāą†ā°ˇā°ŋā°¨āą ā°˛āą†ā°°āąā°¨ā°ŋā°‚ā°—āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°ŋ ā°†ā°¸āąā°¤āąā°˛ā°˛āą‹ ā°Žāąā°–ā°žā°˛ā°¨āą ā°—āąā°°āąā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ. ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛ ā°•āą‹ā°¸ā°‚, ā°¸āą‚ā°•āąā°ˇāąā°Žā°šā°ŋā°¤āąā°°ā°‚ ā°Žā°žā°¤āąā°°ā°Žāą‡ ā°Ēā°°ā°ŋā°—ā°Ŗā°ŋā°‚ā°šā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ. \"ā°…ā°¨āąā°¨āą€\" (ā°Ēāąā°¨ā°ƒ) ā°…ā°¨āąā°¨ā°ŋ ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°Ēāąā°°ā°žā°¸āą†ā°¸āą ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ. ā°‡ā°‚ā°•ā°ž ā°Ēāąā°°ā°žā°¸āą†ā°¸āą ā°šāą‡ā°¯ā°¨ā°ŋ ā°†ā°¸āąā°¤āąā°˛ā°¨āą \"ā°Žā°ŋā°¸āąā°¸ā°ŋā°‚ā°—āą\" ā°•āąā°¯āą‚ā°˛āą ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ. ā°—āąā°°āąā°¤ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°¨ ā°Žāąā°–ā°žā°˛āą ā°‡ā°Ēāąā°ĒⰟā°ŋā°•āą‡ ā°‰ā°¨āąā°¨ ā°˛āą‡ā°Ļā°ž ā°•āąŠā°¤āąā°¤ ā°ĩāąā°¯ā°•āąā°¤āąā°˛ā°¤āą‹ ā°¸ā°Žāą‚ā°šā°Ēā°°ā°šā°Ąā°‚ ā°Ēāą‚ā°°āąā°¤ā°¯ā°ŋā°¨ ā°¤ā°°āąā°ĩā°žā°¤ ā°Žāąā°– ā°—āąā°°āąā°¤ā°ŋā°‚ā°Ēāą ā°•āą‹ā°¸ā°‚ ā°•āąā°¯āą‚ā°˛āą‹ ā°‰ā°‚ā°šā°Ŧā°Ąā°¤ā°žā°¯ā°ŋ.", "facial_recognition_job_description": "ā°¸ā°Žāą‚ā°šā°‚ ā°ĩāąā°¯ā°•āąā°¤āąā°˛ ā°Žāąā°–ā°žā°˛ā°¨āą ā°—āąā°°āąā°¤ā°ŋā°‚ā°šā°ŋā°‚ā°Ļā°ŋ. ā°Ģāą‡ā°¸āą ā°Ąā°ŋā°Ÿāą†ā°•āąā°ˇā°¨āą ā°Ēāą‚ā°°āąā°¤ā°¯ā°ŋā°¨ ā°¤ā°°āąā°ĩā°žā°¤ Ⰸ ā°Ļā°ļ ā°…ā°Žā°˛ā°ĩāąā°¤āąā°‚ā°Ļā°ŋ. \"ā°…ā°¨āąā°¨ā°ŋ\" (ā°Ēāąā°¨ā°ƒ) ā°…ā°¨āąā°¨ā°ŋ ā°Žāąā°–ā°žā°˛ā°¨āą ā°•āąā°˛ā°¸āąā°Ÿā°°āąâ€Œā°˛āą ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ. \"ā°¤ā°Ēāąā°Ēā°ŋā°Ēāą‹ā°¯ā°ŋā°¨\" ā°ĩāąā°¯ā°•āąā°¤ā°ŋā°¨ā°ŋ ā°•āą‡ā°Ÿā°žā°¯ā°ŋā°‚ā°šā°¨ā°ŋ ā°Žāąā°–ā°žā°˛ā°¨āą ā°•āąā°¯āą‚ā°˛āą‹ ā°‰ā°‚ā°šāąā°¤āąā°‚ā°Ļā°ŋ.", "failed_job_command": "ā°‰ā°Ļāąā°¯āą‹ā°—ā°‚ ā°•āą‹ā°¸ā°‚ ā°•ā°Žā°žā°‚ā°Ąāą {command} ā°ĩā°ŋā°Ģā°˛ā°Žāąˆā°‚ā°Ļā°ŋ: {job}", @@ -87,7 +85,6 @@ "image_fullsize_description": "ā°œāą‚ā°Žāą ā°šāą‡ā°¸ā°ŋā°¨ā°Ēāąā°Ēāąā°Ąāą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šāą‡, ā°Žāą†ā°Ÿā°žā°Ąāą‡ā°Ÿā°ž ā°¤āą€ā°¸ā°ŋā°¨ ā°Ēāą‚ā°°āąā°¤ā°ŋ ā°Ēā°°ā°ŋā°Žā°žā°Ŗ Ⱊā°ŋā°¤āąā°°ā°Žāą", "image_fullsize_enabled": "ā°Ģāąā°˛āą-ā°¸āąˆā°œāą Ⱊā°ŋā°¤āąā°°ā°‚ ā°¤ā°¯ā°žā°°āą€ā°¨ā°ŋ ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šāą", "image_fullsize_enabled_description": "ā°ĩāą†ā°Ŧāąâ€Œā°•āą ā°…ā°¨āąā°•āą‚ā°˛ā°‚ ā°•ā°žā°¨ā°ŋ ā°Ģā°žā°°āąā°Žā°žā°Ÿāąā°˛ ā°•āą‹ā°¸ā°‚ ā°Ģāąā°˛āą-ā°¸āąˆā°œāą Ⱊā°ŋā°¤āąā°°ā°žā°¨āąā°¨ā°ŋ ā°¸āąƒā°ˇāąā°Ÿā°ŋā°‚ā°šāą. \"ā°Žā°‚ā°Ŧāą†ā°Ąāąā°Ąāą†ā°Ąāą ā°Ēāąā°°āą€ā°ĩāąā°¯āą‚ ā°Ēāąā°°ā°ŋā°Ģā°°āą\" ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šā°ŋā°¨ā°Ēāąā°Ēāąā°Ąāą, ā°Žā°‚ā°Ŧāą†ā°Ąāąā°Ąāą†ā°Ąāą ā°Ēāąā°°āą€ā°ĩāąā°¯āą‚ā°˛ā°¨āą ā°Žā°žā°°āąā°Ēā°ŋā°Ąā°ŋā°˛āą‡ā°•āąā°‚ā°Ąā°ž ā°¨āą‡ā°°āąā°—ā°ž ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°¸āąā°¤ā°žā°°āą. JPEG ā°ĩā°‚ā°Ÿā°ŋ ā°ĩāą†ā°Ŧāą-ā°Ģāąā°°āą†ā°‚ā°Ąāąā°˛āą€ ā°Ģā°žā°°āąā°Žā°žā°Ÿāąā°˛ā°Ēāąˆ ā°‡ā°Ļā°ŋ ā°Ēāąā°°ā°­ā°žā°ĩā°‚ ā°šāą‚ā°Ēā°Ļāą.", - "image_fullsize_title": "ā°Ēāą‚ā°°āąā°¤ā°ŋ ā°Ēā°°ā°ŋā°Žā°žā°Ŗ Ⱊā°ŋā°¤āąā°° ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", "image_prefer_embedded_preview": "ā°ĒāąŠā°‚ā°Ļāąā°Ēā°°ā°ŋⰚā°ŋā°¨ ā°Ēā°°ā°ŋā°Ļāąƒā°ļāąā°¯ā°žā°¨ā°ŋā°•ā°ŋ ā°Ēāąā°°ā°žā°§ā°žā°¨āąā°¯ā°¤ ā°‡ā°ĩāąā°ĩā°‚ā°Ąā°ŋ", "image_prefer_embedded_preview_setting_description": "ā°…ā°‚ā°Ļāąā°Ŧā°žā°Ÿāąā°˛āą‹ ā°‰ā°¨āąā°¨ā°Ēāąā°Ēāąā°Ąāą ā°Žā°°ā°ŋā°¯āą ā°‡ā°Žāą‡ā°œāą ā°Ēāąā°°ā°žā°¸āą†ā°¸ā°ŋā°‚ā°—āąâ€Œā°•āą ā°‡ā°¨āąâ€Œā°Ēāąā°Ÿāąâ€Œā°—ā°ž RAW ā°Ģāą‹ā°Ÿāą‹ā°˛ā°˛āą‹ ā°Žā°‚ā°Ŧāą†ā°Ąāą†ā°Ąāą ā°Ēāąā°°ā°ŋā°ĩāąā°¯āą‚ā°˛ā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ. ā°‡ā°Ļā°ŋ ā°•āąŠā°¨āąā°¨ā°ŋ Ⱊā°ŋā°¤āąā°°ā°žā°˛ā°•āą ā°Žā°°ā°ŋā°‚ā°¤ ā°–ā°šāąā°šā°ŋā°¤ā°Žāąˆā°¨ ā°°ā°‚ā°—āąā°˛ā°¨āą ā°‰ā°¤āąā°Ēā°¤āąā°¤ā°ŋ ā°šāą‡ā°¯ā°—ā°˛ā°Ļāą, ā°…ā°¯ā°ŋā°¤āą‡ ā°Ēāąā°°ā°ŋā°ĩāąā°¯āą‚ ā°¨ā°žā°Ŗāąā°¯ā°¤ ā°•āą†ā°Žāą†ā°°ā°žā°Ēāąˆ ā°†ā°§ā°žā°°ā°Ēā°Ąā°ŋ ā°‰ā°‚ā°Ÿāąā°‚ā°Ļā°ŋ ā°Žā°°ā°ŋā°¯āą Ⱊā°ŋā°¤āąā°°ā°‚ ā°Žā°°ā°ŋā°¨āąā°¨ā°ŋ ā°•āąā°Ļā°ŋā°‚ā°Ēāą ā°•ā°ŗā°žā°–ā°‚ā°Ąā°žā°˛ā°¨āą ā°•ā°˛ā°ŋā°—ā°ŋ ā°‰ā°‚ā°Ąā°ĩā°šāąā°šāą.", "image_prefer_wide_gamut": "ā°ĩā°ŋā°¸āąā°¤āąƒā°¤ ā°¸āąā°ĩā°°ā°¸ā°Ēāąā°¤ā°•ā°žā°¨ā°ŋā°•ā°ŋ ā°Ēāąā°°ā°žā°§ā°žā°¨āąā°¯ā°¤ ā°‡ā°ĩāąā°ĩā°‚ā°Ąā°ŋ", @@ -95,8 +92,6 @@ "image_preview_description": "ā°’ā°•āą‡ ā°†ā°¸āąā°¤ā°ŋā°¨ā°ŋ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šāą‡ā°Ÿā°Ēāąā°Ēāąā°Ąāą ā°Žā°°ā°ŋā°¯āą ā°¯ā°‚ā°¤āąā°° ā°…ā°­āąā°¯ā°žā°¸ā°‚ ā°•āą‹ā°¸ā°‚ ā°Žāą†ā°Ÿā°žā°Ąāą‡ā°Ÿā°ž ā°˛āą‡ā°¨ā°ŋ ā°Žā°§āąā°¯ā°¸āąā°Ĩ-ā°Ēā°°ā°ŋā°Žā°žā°Ŗ Ⱊā°ŋā°¤āąā°°ā°‚ ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ", "image_preview_quality_description": "1-100 ā°ĩā°°ā°•āą ā°Ēāąā°°ā°ŋā°ĩāąā°¯āą‚ ā°¨ā°žā°Ŗāąā°¯ā°¤. ā°Žā°•āąā°•āąā°ĩ ā°‰ā°‚ā°Ÿāą‡ ā°Žā°‚ā°šā°ŋā°Ļā°ŋ, ā°•ā°žā°¨āą€ ā°Ēāą†ā°Ļāąā°Ļ ā°Ģāąˆā°ŗāąā°˛ā°¨āą ā°‰ā°¤āąā°Ēā°¤āąā°¤ā°ŋ ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ ā°Žā°°ā°ŋā°¯āą ā°¯ā°žā°Ēāą ā°Ēāąā°°ā°¤ā°ŋā°¸āąā°Ēā°‚ā°Ļā°¨ā°¨āą ā°¤ā°—āąā°—ā°ŋā°¸āąā°¤āąā°‚ā°Ļā°ŋ. ā°¤ā°•āąā°•āąā°ĩ ā°ĩā°ŋā°˛āąā°ĩā°¨āą ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°Ąā°‚ ā°ĩā°˛āąā°˛ ā°Žāą†ā°ˇā°ŋā°¨āą ā°˛āą†ā°°āąā°¨ā°ŋā°‚ā°—āą ā°¨ā°žā°Ŗāąā°¯ā°¤ ā°Ēāąā°°ā°­ā°žā°ĩā°ŋⰤⰂ ā°•ā°žā°ĩā°šāąā°šāą.", "image_preview_title": "ā°…ā°Žā°°ā°ŋā°•ā°˛ ā°Ēāąā°°ā°ŋā°ĩāąā°¯āą‚", - "image_progressive": "ā°Ēāąā°°āą‹ā°—āąā°°āą†ā°¸ā°ŋā°ĩāą", - "image_progressive_description": "ā°•āąā°°ā°Žā°‚ā°—ā°ž ā°˛āą‹ā°Ąāą ā°…ā°ĩāąā°¤āą‚ ā°Ēāąā°°ā°Ļā°°āąā°ļā°ŋā°‚ā°šā°Ŧā°Ąāą‡ā°˛ā°ž JPEG Ⱊā°ŋā°¤āąā°°ā°žā°˛ā°¨āą ā°Ēāąā°°āą‹ā°—āąā°°āą†ā°¸ā°ŋā°ĩāąâ€Œā°—ā°ž ā°Žā°¨āąâ€Œā°•āą‹ā°Ąāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ. ā°‡ā°Ļā°ŋ WebP Ⱊā°ŋā°¤āąā°°ā°žā°˛ā°Ēāąˆ ā°Žā°˛ā°žā°‚ā°Ÿā°ŋ ā°Ēāąā°°ā°­ā°žā°ĩā°‚ ā°šāą‚ā°Ēā°Ļāą.", "image_quality": "ā°¨ā°žā°Ŗāąā°¯ā°¤", "image_resolution": "ā°°ā°ŋā°œā°˛āąā°¯āą‚ā°ˇā°¨āą", "image_resolution_description": "ā°…ā°§ā°ŋā°• ā°°ā°ŋā°œā°˛āąā°¯āą‚ā°ˇā°¨āąâ€Œā°˛āą ā°Žā°°ā°ŋā°‚ā°¤ ā°ĩā°ŋā°ĩā°°ā°žā°˛ā°¨āą ā°­ā°Ļāąā°°ā°Ēā°°ā°šā°—ā°˛ā°ĩāą ā°•ā°žā°¨āą€ ā°Žā°¨āąâ€Œā°•āą‹ā°Ąāą ā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Žā°•āąā°•āąā°ĩ ā°¸ā°Žā°¯ā°‚ ā°Ēā°Ąāąā°¤āąā°‚ā°Ļā°ŋ, ā°Ēāą†ā°Ļāąā°Ļ ā°Ģāąˆā°˛āą ā°Ēā°°ā°ŋā°Žā°žā°Ŗā°žā°˛ā°¨āą ā°•ā°˛ā°ŋā°—ā°ŋ ā°‰ā°‚ā°Ÿā°žā°¯ā°ŋ ā°Žā°°ā°ŋā°¯āą ā°¯ā°žā°Ēāą ā°Ēāąā°°ā°¤ā°ŋā°¸āąā°Ēā°‚ā°Ļā°¨ā°¨āą ā°¤ā°—āąā°—ā°ŋā°‚ā°šā°ĩā°šāąā°šāą.", @@ -105,8 +100,6 @@ "image_thumbnail_description": "ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¸ā°ŋā°¨ ā°Žāą†ā°Ÿā°žā°Ąāą‡ā°Ÿā°žā°¤āą‹ ā°•āą‚ā°Ąā°ŋā°¨ Ⱊā°ŋā°¨āąā°¨ ā°¸āą‚ā°•āąā°ˇāąā°Žā°šā°ŋā°¤āąā°°ā°‚, ā°Ēāąā°°ā°§ā°žā°¨ ā°Ÿāąˆā°Žāąâ€Œā°˛āąˆā°¨āą ā°ĩā°‚ā°Ÿā°ŋ ā°Ģāą‹ā°Ÿāą‹ā°˛ ā°¸ā°Žāą‚ā°šā°žā°˛ā°¨āą ā°ĩāą€ā°•āąā°ˇā°ŋā°¸āąā°¤āąā°¨āąā°¨ā°Ēāąā°Ēāąā°Ąāą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ", "image_thumbnail_quality_description": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āą ā°¨ā°žā°Ŗāąā°¯ā°¤ 1-100 ā°¨āąā°‚ā°Ąā°ŋ. ā°…ā°§ā°ŋā°•ā°Žāąˆā°¨ā°Ļā°ŋ ā°‰ā°¤āąā°¤ā°Žā°‚, ā°•ā°žā°¨āą€ ā°Ēāą†ā°Ļāąā°Ļ ā°Ģāąˆā°˛āąâ€Œā°˛ā°¨āą ā°‰ā°¤āąā°Ēā°¤āąā°¤ā°ŋ ā°šāą‡ā°¸āąā°¤āąā°‚ā°Ļā°ŋ ā°Žā°°ā°ŋā°¯āą ā°¯ā°žā°Ēāą ā°Ēāąā°°ā°¤ā°ŋā°¸āąā°Ēā°‚ā°Ļā°¨ā°¨āą ā°¤ā°—āąā°—ā°ŋā°¸āąā°¤āąā°‚ā°Ļā°ŋ.", "image_thumbnail_title": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", - "import_config_from_json_description": "JSON ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āą ā°Ģāąˆā°˛āąâ€Œā°¨āą ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¸ā°ŋ ā°¸ā°ŋā°¸āąā°Ÿā°Žāą ā°•ā°žā°¨āąā°Ģā°ŋā°—ā°°āą‡ā°ˇā°¨āąâ€Œā°¨āą ā°Ļā°ŋā°—āąā°Žā°¤ā°ŋ ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", - "integrity_checks_checksum_files_time_limit": "ā°¸ā°Žā°¯ ā°Ēā°°ā°ŋā°Žā°ŋā°¤ā°ŋ", "job_concurrency": "{job} ā°¸ā°Žāąā°Žā°¤ā°ŋ", "job_created": "ā°Ēā°¨ā°ŋ ā°¸āąƒā°ˇāąā°Ÿā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "job_not_concurrency_safe": "Ⰸ ā°‰ā°Ļāąā°¯āą‹ā°—ā°‚ ā°¸ā°Žāąā°Žā°¤ā°ŋ-ā°¸āąā°°ā°•āąā°ˇā°ŋā°¤ā°Žāąˆā°¨ā°Ļā°ŋ ā°•ā°žā°Ļāą.", @@ -376,6 +369,8 @@ "album_delete_confirmation": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {album} ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°¨āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", "album_delete_confirmation_description": "Ⰸ ā°†ā°˛āąā°Ŧā°Žāą ā°ˇāą‡ā°°āą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°¨ā°Ÿāąā°˛ā°¯ā°ŋā°¤āą‡, ⰇⰤⰰ ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛āą ā°Ļāą€ā°¨āąā°¨ā°ŋ ⰇⰕā°Ēāąˆ ā°¯ā°žā°•āąā°¸āą†ā°¸āą ā°šāą‡ā°¯ā°˛āą‡ā°°āą.", "album_info_updated": "ā°†ā°˛āąā°Ŧā°Žāą ā°¸ā°Žā°žā°šā°žā°°ā°‚ ā°…ā°Ēāąā°Ąāą‡ā°Ÿāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "album_leave": "ā°†ā°˛āąā°Ŧā°Žāą ā°¨āąā°‚ā°Ąā°ŋ ā°Ŧā°¯ā°Ÿā°•āą ā°°ā°žā°ĩā°žā°˛ā°ž?", + "album_leave_confirmation": "ā°Žāą€ā°°āą ā°¨ā°ŋā°œā°‚ā°—ā°ž {album} ā°¨āąā°‚ā°Ąā°ŋ ā°Ŧā°¯ā°Ÿā°•āą ā°°ā°žā°ĩā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", "album_name": "ā°†ā°˛āąā°Ŧā°Žāą ā°Ēāą‡ā°°āą", "album_options": "ā°†ā°˛āąā°Ŧā°Žāą ā°Žā°‚ā°Ēā°ŋā°•ā°˛āą", "album_remove_user": "ā°ĩā°žā°Ąāąā°•ā°°ā°ŋā°¨ā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°žā°˛ā°ž?", @@ -383,6 +378,8 @@ "album_share_no_users": "ā°Žāą€ā°°āą Ⰸ ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°¨āą ā°…ā°¨āąā°¨ā°ŋ ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛ā°¤āą‹ ā°ˇāą‡ā°°āą ā°šāą‡ā°¸ā°ŋā°¨ā°Ÿāąā°˛āąā°—ā°ž ⰉⰂā°Ļā°ŋ ā°˛āą‡ā°Ļā°ž ā°Žāą€ ā°ĩā°Ļāąā°Ļ ā°ˇāą‡ā°°āą ā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Žā°ĩā°°āą‚ ā°˛āą‡ā°°āą.", "album_updated": "ā°†ā°˛āąā°Ŧā°Žāą ā°…ā°Ēāąā°Ąāą‡ā°Ÿāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "album_updated_setting_description": "ā°ˇāą‡ā°°āą ā°šāą‡ā°¸ā°ŋā°¨ ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą‹ ā°•āąŠā°¤āąā°¤ ā°…ā°‚ā°ļā°žā°˛āą ā°‰ā°¨āąā°¨ā°Ēāąā°Ēāąā°Ąāą ā°‡ā°Žāą†ā°¯ā°ŋā°˛āą ā°¨āą‹ā°Ÿā°ŋā°Ģā°ŋā°•āą‡ā°ˇā°¨āąâ€Œā°¨āą ā°¸āąā°ĩāą€ā°•ā°°ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", + "album_user_left": "{album} ā°¨āąā°‚ā°Ąā°ŋ ā°Ŧā°¯ā°Ÿā°•āą ā°ĩā°šāąā°šā°žā°°āą", + "album_user_removed": "{user} ā°¨āą ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°ļā°žā°°āą", "album_with_link_access": "Ⰸ ā°˛ā°ŋā°‚ā°•āą ā°‰ā°¨āąā°¨ ā°Žā°ĩā°°ā°ŋā°•āąˆā°¨ā°ž Ⰸ ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą‹ā°¨ā°ŋ ā°Ģāą‹ā°Ÿāą‹ā°˛āą ā°Žā°°ā°ŋā°¯āą ā°ĩāąā°¯ā°•āąā°¤āąā°˛ā°¨āą ā°šāą‚ā°Ąā°Ÿā°žā°¨ā°ŋā°•ā°ŋ ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ.", "albums": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą", "albums_count": "{count, plural, one {{count, number} ā°†ā°˛āąā°Ŧā°Žāą} other {{count, number} ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą}}", @@ -391,9 +388,11 @@ "all_people": "ā°…ā°¨āąā°¨ā°ŋ ā°ĩāąā°¯ā°•āąā°¤āąā°˛āą", "all_videos": "ā°…ā°¨āąā°¨ā°ŋ ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛āą", "allow_dark_mode": "ā°šāą€ā°•ā°Ÿā°ŋ ā°Žāą‹ā°Ąāąâ€Œā°¨āą ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", + "allow_edits": "ā°Žā°žā°°āąā°Ēāąā°˛ā°¨āą ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "allow_public_user_to_download": "ā°Ēā°Ŧāąā°˛ā°ŋā°•āą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°Ąāą ā°ĄāąŒā°¨āąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¸āą‡ā°‚ā°Ļāąā°•āą ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "allow_public_user_to_upload": "ā°Ēā°Ŧāąā°˛ā°ŋā°•āą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°Ąāą ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¸āą‡ā°‚ā°Ļāąā°•āą ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "alt_text_qr_code": "ā°•āąā°¯āą‚ā°†ā°°āą ā°•āą‹ā°Ąāą Ⱊā°ŋā°¤āąā°°ā°‚", + "anti_clockwise": "ā°…ā°Ēā°¸ā°ĩāąā°¯-ā°Ļā°ŋā°ļ", "api_key": "API ā°•āą€", "api_key_description": "Ⰸ ā°ĩā°ŋā°˛āąā°ĩ ā°’ā°•āąā°•ā°¸ā°žā°°ā°ŋ ā°Žā°žā°¤āąā°°ā°Žāą‡ ā°šāą‚ā°Ēā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ. ā°ĩā°ŋā°‚ā°Ąāą‹ā°¨āą ā°Žāą‚ā°¸ā°ŋā°ĩāą‡ā°¸āą‡ ā°Žāąā°‚ā°Ļāą ā°Ļā°¯ā°šāą‡ā°¸ā°ŋ ā°Ļāą€ā°¨ā°ŋā°¨ā°ŋ ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž ā°•ā°žā°Ēāą€ ā°šāą‡ā°¸ā°ŋ ā°Žā°•āąā°•ā°Ąāąˆā°¨ā°ž ā°­ā°Ļāąā°°ā°Ēā°°ā°šā°‚ā°Ąā°ŋ.", "api_key_empty": "ā°Žāą€ API ā°•āą€ ā°Ēāą‡ā°°āą ā°–ā°žā°ŗāą€ā°—ā°ž ā°‰ā°‚ā°Ąā°•āą‚ā°Ąā°Ļāą", @@ -410,6 +409,8 @@ "asset_added_to_album": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•āą ā°œāą‹ā°Ąā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "asset_adding_to_album": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•āą ā°œāą‹ā°Ąā°ŋā°¸āąā°¤āąā°¨āąā°¨ā°žā°Žāąâ€Ļ", "asset_description_updated": "ā°†ā°¸āąā°¤ā°ŋ ā°ĩā°ŋā°ĩā°°ā°Ŗ ā°…ā°Ēāąā°Ąāą‡ā°Ÿāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "asset_filename_is_offline": "ā°†ā°¸āąā°¤ā°ŋ {filename} ā°†ā°Ģāąâ€Œā°˛āąˆā°¨āąâ€Œā°˛āą‹ ⰉⰂā°Ļā°ŋ", + "asset_has_unassigned_faces": "ā°†ā°¸āąā°¤ā°ŋā°•ā°ŋ ā°•āą‡ā°Ÿā°žā°¯ā°ŋā°‚ā°šā°¨ā°ŋ ā°Žāąā°–ā°žā°˛āą ā°‰ā°¨āąā°¨ā°žā°¯ā°ŋ", "asset_hashing": "ā°šā°žā°ˇā°ŋā°‚ā°—āą ā°šāą‡ā°¸āąā°¤āąā°¨āąā°¨ā°žā°Žāąâ€Ļ", "asset_offline": "ā°†ā°¸āąā°¤ā°ŋ ā°†ā°Ģāąâ€Œā°˛āąˆā°¨āąâ€Œā°˛āą‹ ⰉⰂā°Ļā°ŋ", "asset_offline_description": "Ⰸ ā°Ŧā°žā°šāąā°¯ ā°†ā°¸āąā°¤ā°ŋ ⰇⰕā°Ēāąˆ ā°Ąā°ŋā°¸āąā°•āąâ€Œā°˛āą‹ ā°•ā°¨ā°Ŧā°Ąā°Ÿā°‚ ā°˛āą‡ā°Ļāą. ā°¸ā°šā°žā°¯ā°žā°¨ā°ŋā°•ā°ŋ ā°Ļā°¯ā°šāą‡ā°¸ā°ŋ ā°Žāą€ Immich ā°¨ā°ŋā°°āąā°ĩā°žā°šā°•āąā°Ąā°ŋā°¨ā°ŋ ⰏⰂā°Ēāąā°°ā°Ļā°ŋā°‚ā°šā°‚ā°Ąā°ŋ.", @@ -418,6 +419,7 @@ "asset_uploaded": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "asset_uploading": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°œā°°āąā°—āąā°¤āą‹ā°‚ā°Ļā°ŋâ€Ļ", "assets": "ā°†ā°¸āąā°¤āąā°˛āą", + "assets_added_count": "ā°œāą‹ā°Ąā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°¨ā°ĩā°ŋ {count, plural, one {# ā°†ā°¸āąā°¤ā°ŋ} other {# ā°†ā°¸āąā°¤āąā°˛āą}}", "assets_added_to_album_count": "{count, plural, one {# ā°†ā°¸āąā°¤ā°ŋ} other {# ā°†ā°¸āąā°¤āąā°˛āą}} ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°•ā°ŋ ā°œāą‹ā°Ąā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°¨ā°ĩā°ŋ", "assets_count": "{count, plural, one {# ā°†ā°¸āąā°¤ā°ŋ} other {# ā°†ā°¸āąā°¤āąā°˛āą}}", "assets_moved_to_trash_count": "{count, plural, one {# ā°†ā°¸āąā°¤ā°ŋ} other {# ā°†ā°¸āąā°¤āąā°˛āą}} ā°šāą†ā°¤āąā°¤ā°Ŧāąā°Ÿāąā°Ÿā°˛āą‹ā°•ā°ŋ ⰤⰰⰞā°ŋā°‚ā°šā°žā°°āą", @@ -431,6 +433,7 @@ "back": "ā°¤ā°ŋā°°ā°ŋā°—ā°ŋ", "back_close_deselect": "ā°ĩāą†ā°¨ā°•āąā°•ā°ŋ, ā°Žāą‚ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ, ā°˛āą‡ā°Ļā°ž ā°Žā°‚ā°šāąā°•āąā°¨āąā°¨ā°Ļā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", "backward": "ā°ĩāą†ā°¨āąā°•ā°•āą", + "birthdate_saved": "ā°œā°¨āąā°Žā°¤āą‡ā°Ļā°ŋ ā°ĩā°ŋⰜⰝā°ĩā°‚ā°¤ā°‚ā°—ā°ž ā°¸āą‡ā°ĩāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "birthdate_set_description": "ā°œā°¨āąā°Žā°¤āą‡ā°Ļā°ŋ Ⰸ ā°ĩāąā°¯ā°•āąā°¤ā°ŋ ā°Ģāą‹ā°Ÿāą‹ ā°¤āą€ā°¸ā°ŋā°¨ ā°¸ā°Žā°¯ā°‚ā°˛āą‹ ā°ĩā°¯ā°¸āąā°¸āąā°¨āą ā°—ā°Ŗā°ŋā°‚ā°šāą‡ā°‚ā°Ļāąā°•āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ.", "blurred_background": "ā°Žāąā°Ļāąā°°āą ā°¨āą‡ā°Ēā°§āąā°¯ā°‚", "bugs_and_feature_requests": "ā°˛āą‹ā°Ēā°žā°˛āą & ā°Ģāą€ā°šā°°āą ā°…ā°­āąā°¯ā°°āąā°Ĩā°¨ā°˛āą", @@ -449,6 +452,7 @@ "cannot_undo_this_action": "ā°Žāą€ā°°āą Ⰸ ā°šā°°āąā°¯ā°¨āą ā°ĩāą†ā°¨āąā°•ā°•āą ā°¤āą€ā°¸āąā°•āą‹ā°˛āą‡ā°°āą!", "cannot_update_the_description": "ā°ĩā°ŋā°ĩā°°ā°Ŗā°¨āą ā°¨ā°ĩāą€ā°•ā°°ā°ŋā°‚ā°šā°˛āą‡ā°°āą", "change_date": "ā°¤āą‡ā°Ļāą€ ā°Žā°žā°°āąā°šāą", + "change_expiration_time": "ā°—ā°Ąāąā°ĩāą ā°¸ā°Žā°¯ā°žā°¨āąā°¨ā°ŋ ā°Žā°žā°°āąā°šāą", "change_location": "ā°¸āąā°Ĩā°žā°¨ā°žā°¨āąā°¨ā°ŋ ā°Žā°žā°°āąā°šāą", "change_name": "ā°Ēāą‡ā°°āą ā°Žā°žā°°āąā°šāą", "change_name_successfully": "ā°Ēāą‡ā°°āą ā°ĩā°ŋⰜⰝā°ĩā°‚ā°¤ā°‚ā°—ā°ž ā°Žā°žā°°āąā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", @@ -464,10 +468,12 @@ "clear_all_recent_searches": "ā°‡ā°Ÿāą€ā°ĩā°˛ ā°šāą‡ā°¸ā°ŋā°¨ ā°…ā°¨āąā°¨ā°ŋ ā°ļāą‹ā°§ā°¨ā°˛ā°¨āą ā°–ā°žā°ŗāą€ ā°šāą‡ā°¯ā°ŋ", "clear_message": "ⰏⰂā°Ļāą‡ā°ļā°žā°¨āąā°¨ā°ŋ ā°–ā°žā°ŗāą€ ā°šāą‡ā°¯ā°ŋ", "clear_value": "ā°ĩā°ŋā°˛āąā°ĩā°¨āą ā°–ā°žā°ŗāą€ ā°šāą‡ā°¯ā°ŋ", + "clockwise": "ā°¸ā°ĩāąā°¯ā°Ļā°ŋā°ļ", "close": "ā°Žāą‚ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", "collapse": "ā°¸ā°‚ā°•āąā°šā°ŋⰤⰂ ā°šāą‡ā°¯ā°ŋ", "collapse_all": "ā°…ā°¨āąā°¨āą€ ā°¸ā°‚ā°•āąā°šā°ŋⰤⰂ ā°šāą‡ā°¯ā°ŋ", "color": "ā°°ā°‚ā°—āą", + "color_theme": "ā°°ā°‚ā°—āą ā°Ĩāą€ā°Žāą", "comment_deleted": "ā°ĩāąā°¯ā°žā°–āąā°¯ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "comment_options": "ā°ĩāąā°¯ā°žā°–āąā°¯ ā°Žā°‚ā°Ēā°ŋā°•ā°˛āą", "comments_and_likes": "ā°ĩāąā°¯ā°žā°–āąā°¯ā°˛āą & ā°˛āąˆā°•āąâ€Œā°˛āą", @@ -484,6 +490,7 @@ "copied_image_to_clipboard": "Ⱊā°ŋā°¤āąā°°ā°žā°¨āąā°¨ā°ŋ ā°•āąā°˛ā°ŋā°Ēāąâ€Œā°Ŧāą‹ā°°āąā°Ąāąâ€Œā°•āą ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ.", "copied_to_clipboard": "ā°•āąā°˛ā°ŋā°Ēāąâ€Œā°Ŧāą‹ā°°āąā°Ąāąâ€Œā°•āą ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ!", "copy_error": "ā°•ā°žā°Ēāą€ ā°Žā°°āąā°°ā°°āą", + "copy_file_path": "ā°Ģāąˆā°˛āą ā°Ēā°žā°¤āąâ€Œā°¨āą ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "copy_image": "Ⱊā°ŋā°¤āąā°°ā°žā°¨āąā°¨ā°ŋ ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°ŋ", "copy_link": "ā°˛ā°ŋā°‚ā°•āąâ€Œā°¨āą ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°ŋ", "copy_link_to_clipboard": "ā°˛ā°ŋā°‚ā°•āąâ€Œā°¨āą ā°•āąā°˛ā°ŋā°Ēāąâ€Œā°Ŧāą‹ā°°āąā°Ąāąâ€Œā°•āą ā°•ā°žā°Ēāą€ ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", @@ -530,13 +537,16 @@ "delete_tag_confirmation_prompt": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž {tagName} ā°Ÿāąā°¯ā°žā°—āąâ€Œā°¨āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", "delete_user": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°¨ā°ŋ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šāą", "deleted_shared_link": "ā°ˇāą‡ā°°āą ā°šāą‡ā°¸ā°ŋā°¨ ā°˛ā°ŋā°‚ā°•āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "deletes_missing_assets": "ā°Ąā°ŋā°¸āąā°•āą ā°¨āąā°‚ā°Ąā°ŋ ā°¤ā°Ēāąā°Ēā°ŋā°Ēāą‹ā°¯ā°ŋā°¨ ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°¤āąŠā°˛ā°—ā°ŋā°¸āąā°¤āąā°‚ā°Ļā°ŋ", "description": "ā°ĩā°ŋā°ĩā°°ā°Ŗ", "details": "ā°ĩā°ŋā°ĩā°°ā°žā°˛āą", "direction": "ā°Ļā°ŋā°ļ", "disabled": "ā°¨ā°ŋā°˛ā°ŋā°Ēā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "disallow_edits": "ā°¸ā°ĩā°°ā°Ŗā°˛ā°¨āą ā°…ā°¨āąā°Žā°¤ā°ŋā°‚ā°šā°ĩā°Ļāąā°Ļāą", "discord": "ā°Ąā°ŋā°¸āąā°•āą‹ā°°āąā°Ąāą", "discover": "ā°•ā°¨āąā°—āąŠā°¨ā°‚ā°Ąā°ŋ", "dismiss_all_errors": "ā°…ā°¨āąā°¨ā°ŋ ā°˛āą‹ā°Ēā°žā°˛ā°¨āą ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", + "dismiss_error": "ā°˛āą‹ā°Ēā°žā°¨āąā°¨ā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", "display_options": "ā°Ēāąā°°ā°Ļā°°āąā°ļā°¨ ā°Žā°‚ā°Ēā°ŋā°•ā°˛āą", "display_order": "ā°Ēāąā°°ā°Ļā°°āąā°ļā°¨ ā°•āąā°°ā°Žā°‚", "display_original_photos": "ā°…ā°¸ā°˛āą ā°Ģāą‹ā°Ÿāą‹ā°˛ā°¨āą ā°Ēāąā°°ā°Ļā°°āąā°ļā°ŋā°‚ā°šāą", @@ -571,6 +581,8 @@ "edit_title": "ā°ļāą€ā°°āąā°ˇā°ŋā°•ā°¨āą ā°¸ā°ĩā°°ā°ŋā°‚ā°šāą", "edit_user": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°¨ā°ŋ ā°¸ā°ĩā°°ā°ŋā°‚ā°šāą", "editor": "ā°Žā°Ąā°ŋā°Ÿā°°āą", + "editor_close_without_save_prompt": "ā°Žā°žā°°āąā°Ēāąā°˛āą ā°¸āą‡ā°ĩāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ĩāą", + "editor_close_without_save_title": "ā°Žā°Ąā°ŋā°Ÿā°°āąâ€Œā°¨āą ā°Žāą‚ā°¸ā°ŋā°ĩāą‡ā°¯ā°žā°˛ā°ž?", "email": "ā°‡-ā°Žāą†ā°¯ā°ŋā°˛āą", "empty_trash": "ā°šāą†ā°¤āąā°¤ā°¨āą ā°–ā°žā°ŗāą€ ā°šāą‡ā°¯ā°ŋ", "empty_trash_confirmation": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž ā°Ÿāąā°°ā°žā°ˇāąâ€Œā°¨āą ā°–ā°žā°ŗāą€ ā°šāą‡ā°¯ā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž? ā°‡ā°Ļā°ŋ ā°Ÿāąā°°ā°žā°ˇāąâ€Œā°˛āą‹ā°¨ā°ŋ ā°…ā°¨āąā°¨ā°ŋ ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°‡ā°Žāąā°Žā°ŋā°šāą ā°¨āąā°‚ā°Ąā°ŋ ā°ļā°žā°ļāąā°ĩā°¤ā°‚ā°—ā°ž ā°¤āąŠā°˛ā°—ā°ŋā°¸āąā°¤āąā°‚ā°Ļā°ŋ.\nā°Žāą€ā°°āą Ⰸ ā°šā°°āąā°¯ā°¨āą ā°°ā°Ļāąā°Ļāą ā°šāą‡ā°¯ā°˛āą‡ā°°āą!", @@ -718,6 +730,7 @@ "filename": "ā°Ģāąˆā°˛āąā°Ēāą‡ā°°āą", "filetype": "ā°Ģāąˆā°˛āą ā°°ā°•ā°‚", "filter_people": "ā°ĩāąā°¯ā°•āąā°¤āąā°˛ā°¨āą ā°Ģā°ŋā°˛āąā°Ÿā°°āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", + "find_them_fast": "ā°Ēāą‡ā°°āąā°¤āą‹ ā°ļāą‹ā°§ā°¨ ā°Ļāąā°ĩā°žā°°ā°ž ā°ĩā°žā°Ÿā°ŋā°¨ā°ŋ ā°ĩāą‡ā°—ā°‚ā°—ā°ž ā°•ā°¨āąā°—āąŠā°¨ā°‚ā°Ąā°ŋ", "fix_incorrect_match": "ā°¤ā°Ēāąā°Ēāą Ⰿⰰā°ŋā°Ēāą‹ā°˛ā°ŋā°•ā°¨āą ā°Ēā°°ā°ŋā°ˇāąā°•ā°°ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "folders": "ā°Ģāą‹ā°˛āąā°Ąā°°āąā°˛āą", "folders_feature_description": "ā°Ģāąˆā°˛āą ā°¸ā°ŋā°¸āąā°Ÿā°Žāąâ€Œā°˛āą‹ā°¨ā°ŋ ā°Ģāą‹ā°Ÿāą‹ā°˛āą ā°Žā°°ā°ŋā°¯āą ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛ ā°•āą‹ā°¸ā°‚ ā°Ģāą‹ā°˛āąā°Ąā°°āą ā°ĩāą€ā°•āąā°ˇā°Ŗā°¨āą ā°Ŧāąā°°āąŒā°œāą ā°šāą‡ā°¯ā°Ąā°‚", @@ -792,6 +805,7 @@ "let_others_respond": "ā°‡ā°¤ā°°āąā°˛āą ā°Ēāąā°°ā°¤ā°ŋā°¸āąā°Ēā°‚ā°Ļā°ŋā°‚ā°šā°¨ā°ŋā°ĩāąā°ĩā°‚ā°Ąā°ŋ", "level": "ā°¸āąā°Ĩā°žā°¯ā°ŋ", "library": "ā°—āąā°°ā°‚ā°§ā°žā°˛ā°¯ā°‚", + "library_options": "ā°˛āąˆā°Ŧāąā°°ā°°āą€ ā°Žā°‚ā°Ēā°ŋā°•ā°˛āą", "light": "ā°ĩāą†ā°˛āąā°¤āąā°°āą", "like_deleted": "ā°‡ā°¸āąā°Ÿā°‚ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "link_motion_video": "ā°Žāą‹ā°ˇā°¨āą ā°ĩāą€ā°Ąā°ŋā°¯āą‹ ā°˛ā°ŋā°‚ā°•āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", @@ -814,6 +828,7 @@ "loop_videos_description": "ā°ĩā°ŋā°ĩā°°ā°žā°˛ ā°ĩāąā°¯āą‚ā°¯ā°°āąâ€Œā°˛āą‹ ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°¨āą ā°¸āąā°ĩā°¯ā°‚ā°šā°žā°˛ā°•ā°‚ā°—ā°ž ā°˛āą‚ā°Ēāą ā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ.", "main_branch_warning": "ā°Žāą€ā°°āą ā°Ąāą†ā°ĩā°˛ā°Ēāąâ€Œā°Žāą†ā°‚ā°Ÿāą ā°ĩāą†ā°°āąā°ˇā°¨āąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°¸āąā°¤āąā°¨āąā°¨ā°žā°°āą; ā°ĩā°ŋā°Ąāąā°Ļā°˛ ā°ĩāą†ā°°āąā°ˇā°¨āąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°Žā°¨ā°ŋ ā°Žāą‡ā°Žāą ā°—ā°Ÿāąā°Ÿā°ŋā°—ā°ž ā°¸ā°ŋā°Ģā°žā°°āąā°¸āą ā°šāą‡ā°¸āąā°¤āąā°¨āąā°¨ā°žā°Žāą!", "make": "ā°¤ā°¯ā°žā°°āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", + "manage_shared_links": "ā°­ā°žā°—ā°¸āąā°ĩā°žā°Žāąā°¯ ā°˛ā°ŋā°‚ā°•āąâ€Œā°˛ā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "manage_sharing_with_partners": "ā°­ā°žā°—ā°¸āąā°ĩā°žā°Žāąā°˛ā°¤āą‹ ā°­ā°žā°—ā°¸āąā°ĩā°žā°Žāąā°¯ā°žā°¨āąā°¨ā°ŋ ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "manage_the_app_settings": "ā°¯ā°žā°Ēāą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛ā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "manage_your_account": "ā°Žāą€ ā°–ā°žā°¤ā°ž ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šāąā°•āąŠā°¨ā°‚ā°Ąā°ŋ", @@ -821,6 +836,7 @@ "manage_your_devices": "ā°Žāą€ ā°˛ā°žā°—ā°ŋā°¨āą ā°…ā°¯ā°ŋā°¨ ā°Ēā°°ā°ŋā°•ā°°ā°žā°˛ā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "manage_your_oauth_connection": "ā°Žāą€ OAuth ā°•ā°¨āą†ā°•āąā°ˇā°¨āąâ€Œā°¨ā°ŋ ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "map": "ā°Žāąā°¯ā°žā°Ēāą", + "map_marker_for_images": "{city}, {country} ā°˛āą‹ ā°¤āą€ā°¸ā°ŋā°¨ Ⱊā°ŋā°¤āąā°°ā°žā°˛ ā°•āą‹ā°¸ā°‚ ā°Žāąā°¯ā°žā°Ēāą ā°Žā°žā°°āąā°•ā°°āą", "map_marker_with_image": "Ⱊā°ŋā°¤āąā°°ā°‚ā°¤āą‹ ā°Žāąā°¯ā°žā°Ēāą ā°Žā°žā°°āąā°•ā°°āą", "map_settings": "ā°Žāąā°¯ā°žā°Ēāą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", "matches": "ā°Žāąā°¯ā°žā°šāąâ€Œā°˛āą", @@ -924,6 +940,7 @@ "people": "ā°Ēāąā°°ā°œā°˛āą", "people_edits_count": "ā°¸ā°ĩā°°ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ {count, plural, one {# person} other {# people}}", "people_feature_description": "ā°ĩāąā°¯ā°•āąā°¤āąā°˛ ā°ĩā°žā°°āą€ā°—ā°ž ā°¸ā°Žāą‚ā°šā°ĒⰰⰚā°Ŧā°Ąā°ŋā°¨ ā°Ģāą‹ā°Ÿāą‹ā°˛āą ā°Žā°°ā°ŋā°¯āą ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛ā°¨āą ā°Ŧāąā°°āąŒā°œāą ā°šāą‡ā°¯ā°Ąā°‚", + "people_sidebar_description": "ā°¸āąˆā°Ąāąâ€Œā°Ŧā°žā°°āąâ€Œā°˛āą‹ ā°ĩāąā°¯ā°•āąā°¤āąā°˛ā°•āą ā°˛ā°ŋā°‚ā°•āąâ€Œā°¨āą ā°Ēāąā°°ā°Ļā°°āąā°ļā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "permanent_deletion_warning": "ā°ļā°žā°ļāąā°ĩā°¤ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°Ēāą ā°šāą†ā°šāąā°šā°°ā°ŋā°•", "permanent_deletion_warning_setting_description": "ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°ļā°žā°ļāąā°ĩā°¤ā°‚ā°—ā°ž ā°¤āąŠā°˛ā°—ā°ŋā°¸āąā°¤āąā°¨āąā°¨ā°Ēāąā°Ēāąā°Ąāą ā°šāą†ā°šāąā°šā°°ā°ŋā°•ā°¨āą ā°šāą‚ā°Ēā°ŋā°‚ā°šāą", "permanently_delete": "ā°ļā°žā°ļāąā°ĩā°¤ā°‚ā°—ā°ž ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šāą", @@ -937,6 +954,7 @@ "photo_shared_all_users": "ā°Žāą€ā°°āą ā°Žāą€ ā°Ģāą‹ā°Ÿāą‹ā°˛ā°¨āą ā°…ā°‚ā°Ļā°°āą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛ā°¤āą‹ ā°Ēā°‚ā°šāąā°•āąā°¨āąā°¨ā°Ÿāąā°˛āą ā°•ā°¨ā°ŋā°Ēā°ŋā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ ā°˛āą‡ā°Ļā°ž ā°Žāą€ā°•āą ā°ˇāą‡ā°°āą ā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Žā°ĩā°°āą‚ ā°˛āą‡ā°°āą.", "photos": "ā°Ģāą‹ā°Ÿāą‹ā°˛āą", "photos_and_videos": "ā°Ģāą‹ā°Ÿāą‹ā°˛āą & ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛āą", + "photos_count": "{count, plural, one {{count, number} Photo} other {{count, number} ā°Ģāą‹ā°Ÿāą‹ā°˛āą}}", "photos_from_previous_years": "ā°—ā°¤ ⰏⰂā°ĩā°¤āąā°¸ā°°ā°žā°˛ ā°Ģāą‹ā°Ÿāą‹ā°˛āą", "pick_a_location": "ā°’ā°• ā°¸āąā°Ĩā°žā°¨ā°žā°¨āąā°¨ā°ŋ ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "place": "ā°¸āąā°ĨⰞⰂ", @@ -973,6 +991,7 @@ "purchase_individual_description_2": "ā°Žā°Ļāąā°Ļā°¤āąā°Ļā°žā°°āą ā°¸āąā°Ĩā°ŋā°¤ā°ŋ", "purchase_individual_title": "ā°ĩāąā°¯ā°•āąā°¤ā°ŋā°—ā°¤", "purchase_input_suggestion": "ā°‰ā°¤āąā°Ēā°¤āąā°¤ā°ŋ ā°•āą€ ⰉⰂā°Ļā°ž? ā°•ā°ŋā°‚ā°Ļ ā°•āą€ā°¨ā°ŋ ā°¨ā°Žāą‹ā°Ļāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", + "purchase_license_subtitle": "ā°¸āą‡ā°ĩ ā°¯āąŠā°•āąā°• ā°¨ā°ŋⰰⰂⰤⰰ ā°…ā°­ā°ŋā°ĩāąƒā°Ļāąā°§ā°ŋā°•ā°ŋ ā°Žā°Ļāąā°Ļā°¤āą ā°‡ā°ĩāąā°ĩā°Ąā°žā°¨ā°ŋā°•ā°ŋ Immichā°¨āą ā°•āąŠā°¨āąā°—āą‹ā°˛āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "purchase_lifetime_description": "ā°œāą€ā°ĩā°ŋā°¤ā°•ā°žā°˛ ā°•āąŠā°¨āąā°—āą‹ā°˛āą", "purchase_option_title": "ā°•āąŠā°¨āąā°—āą‹ā°˛āą ā°Žā°‚ā°Ēā°ŋā°•ā°˛āą", "purchase_panel_info_1": "Immich ā°¨āą ā°¨ā°ŋā°°āąā°Žā°ŋā°‚ā°šā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°šā°žā°˛ā°ž ā°¸ā°Žā°¯ā°‚ ā°Žā°°ā°ŋā°¯āą ā°•āąƒā°ˇā°ŋ ā°…ā°ĩⰏⰰⰂ, ā°Žā°°ā°ŋā°¯āą ā°Žāą‡ā°Žāą ā°Ļā°žā°¨ā°ŋā°¨ā°ŋ ā°¸ā°žā°§āąā°¯ā°Žāąˆā°¨ā°‚ā°¤ ā°‰ā°¤āąā°¤ā°Žā°‚ā°—ā°ž ā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°Ēāą‚ā°°āąā°¤ā°ŋ ā°¸ā°Žā°¯ā°‚ ā°‡ā°‚ā°œā°¨āą€ā°°āąā°˛āą ā°Ļā°žā°¨ā°ŋā°Ēāąˆ ā°Ēā°¨ā°ŋā°šāą‡ā°¸āąā°¤āąā°¨āąā°¨ā°žā°°āą. ā°“ā°Ēāą†ā°¨āą-ā°¸āą‹ā°°āąā°¸āą ā°¸ā°žā°Ģāąā°Ÿāąâ€Œā°ĩāą‡ā°°āą ā°Žā°°ā°ŋā°¯āą ā°¨āąˆā°¤ā°ŋā°• ā°ĩāąā°¯ā°žā°Ēā°žā°° ā°Ēā°Ļāąā°§ā°¤āąā°˛āą ā°Ąāą†ā°ĩā°˛ā°Ēā°°āąâ€Œā°˛ā°•āą ā°¸āąā°Ĩā°ŋā°°ā°Žāąˆā°¨ ā°†ā°Ļā°žā°¯ ā°ĩā°¨ā°°āąā°—ā°ž ā°Žā°žā°°ā°Ąā°‚ ā°Žā°°ā°ŋā°¯āą ā°Ļāą‹ā°Ēā°ŋā°Ąāą€ ā°•āąā°˛āąŒā°Ąāą ā°¸āą‡ā°ĩā°˛ā°•āą ā°¨ā°ŋā°œā°Žāąˆā°¨ ā°Ēāąā°°ā°¤āąā°¯ā°žā°Žāąā°¨ā°žā°¯ā°žā°˛ā°¤āą‹ ā°—āą‹ā°Ēāąā°¯ā°¤ā°¨āą ā°—āąŒā°°ā°ĩā°ŋā°‚ā°šāą‡ ā°Ēā°°āąā°¯ā°žā°ĩā°°ā°Ŗ ā°ĩāąā°¯ā°ĩā°¸āąā°Ĩā°¨āą ā°¸āąƒā°ˇāąā°Ÿā°ŋā°‚ā°šā°Ąā°‚ ā°Žā°ž ā°˛ā°•āąā°ˇāąā°¯ā°‚.", @@ -996,6 +1015,7 @@ "reassign": "ā°¤ā°ŋā°°ā°ŋā°—ā°ŋ ā°•āą‡ā°Ÿā°žā°¯ā°ŋā°‚ā°šāą", "reassing_hint": "ā°Žā°‚ā°šāąā°•āąā°¨āąā°¨ ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°‡ā°Ēāąā°ĒⰟā°ŋā°•āą‡ ā°‰ā°¨āąā°¨ ā°ĩāąā°¯ā°•āąā°¤ā°ŋā°•ā°ŋ ā°•āą‡ā°Ÿā°žā°¯ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "recent": "ā°‡ā°Ÿāą€ā°ĩā°˛ā°ŋ", + "recent_albums": "ā°‡ā°Ÿāą€ā°ĩā°˛ā°ŋ ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą", "recent_searches": "ā°‡ā°Ÿāą€ā°ĩā°˛ā°ŋ ā°ļāą‹ā°§ā°¨ā°˛āą", "refresh": "ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°ŋ", "refresh_encoded_videos": "ā°Žā°¨āąâ€Œā°•āą‹ā°Ąāą ā°šāą‡ā°¸ā°ŋā°¨ ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛ā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", @@ -1003,25 +1023,33 @@ "refresh_metadata": "ā°Žāą†ā°Ÿā°žā°Ąāą‡ā°Ÿā°žā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°ŋ", "refresh_thumbnails": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āąā°¸āąâ€Œā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°ŋ", "refreshed": "ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "refreshes_every_file": "ā°‡ā°Ēāąā°ĒⰟā°ŋā°•āą‡ ā°‰ā°¨āąā°¨ ā°Žā°°ā°ŋā°¯āą ā°•āąŠā°¤āąā°¤ ā°…ā°¨āąā°¨ā°ŋ ā°Ģāąˆā°ŗāąā°ŗā°¨āą ā°¤ā°ŋā°°ā°ŋā°—ā°ŋ Ⱊā°Ļāąā°ĩāąā°¤āąā°‚ā°Ļā°ŋ", "refreshing_encoded_video": "ā°Žā°¨āąā°•āą‹ā°Ąāą ā°šāą‡ā°¸ā°ŋā°¨ ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ", "refreshing_faces": "ā°Žāąā°–ā°žā°˛ā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ", "refreshing_metadata": "ā°Žāą†ā°Ÿā°žā°Ąāą‡ā°Ÿā°žā°¨āą ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ", "regenerating_thumbnails": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āąâ€Œā°˛ā°¨āą ā°Ēāąā°¨ā°°āąā°œāąā°œāą€ā°ĩā°ŋā°‚ā°Ēā°œāą‡ā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ", "remove_assets_title": "ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°žā°˛ā°ž?", "remove_custom_date_range": "ā°…ā°¨āąā°•āą‚ā°˛ ā°¤āą‡ā°Ļāą€ ā°Ēā°°ā°ŋā°§ā°ŋā°¨ā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", + "remove_deleted_assets": "ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°¨ ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", "remove_from_album": "ā°†ā°˛āąā°Ŧā°Žāą ā°¨āąā°‚ā°Ąā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", "remove_from_favorites": "ā°‡ā°ˇāąā°Ÿā°Žāąˆā°¨ ā°ĩā°žā°Ÿā°ŋ ā°¨āąā°‚ā°Ąā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", "remove_from_shared_link": "ā°ˇāą‡ā°°āą ā°šāą‡ā°¸ā°ŋā°¨ ā°˛ā°ŋā°‚ā°•āą ā°¨āąā°‚ā°Ąā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", "remove_memory": "ā°Žāą†ā°Žā°°āą€ā°¨ā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", "remove_photo_from_memory": "Ⰸ ā°œāąā°žā°žā°Ēā°•ā°‚ ā°¨āąā°‚ā°Ąā°ŋ ā°Ģāą‹ā°Ÿāą‹ā°¨āą ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°‚ā°Ąā°ŋ", + "remove_url": "URL ā°¨āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šāą", "remove_user": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°¨ā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°ŋ", "removed_api_key": "ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°¨ API ā°•āą€: {name}", "removed_from_archive": "ā°†ā°°āąā°•āąˆā°ĩāą ā°¨āąā°‚ā°Ąā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "removed_from_favorites": "ā°‡ā°ˇāąā°Ÿā°Žāąˆā°¨ā°ĩā°ŋ ā°¨āąā°‚ā°Ąā°ŋ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "removed_memory": "ā°Žāą†ā°Žā°°āą€ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", + "removed_photo_from_memory": "ā°Žāą†ā°Žā°°āą€ ā°¨āąā°‚ā°Ąā°ŋ ā°Ģāą‹ā°Ÿāą‹ ā°¤āą€ā°¸ā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "rename": "ā°Ēāą‡ā°°āą ā°Žā°žā°°āąā°šā°‚ā°Ąā°ŋ", + "repair": "ā°Žā°°ā°Žāąā°Žā°¤āąā°¤āą", + "repair_no_results_message": "ā°Ÿāąā°°ā°žā°•āą ā°šāą‡ā°¯ā°¨ā°ŋ ā°Žā°°ā°ŋā°¯āą ā°¤ā°Ēāąā°Ēā°ŋā°Ēāą‹ā°¯ā°ŋā°¨ ā°Ģāąˆā°˛āąâ€Œā°˛āą ā°‡ā°•āąā°•ā°Ą ā°•ā°¨ā°ŋā°Ēā°ŋā°¸āąā°¤ā°žā°¯ā°ŋ", + "replace_with_upload": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāąâ€Œā°¤āą‹ ā°­ā°°āąā°¤āą€ ā°šāą‡ā°¯ā°ŋ", "repository": "ā°°ā°ŋā°Ēāą‹ā°œā°ŋā°Ÿā°°āą€", "require_password": "ā°Ēā°žā°¸āąâ€Œā°ĩā°°āąā°Ąāą ā°•ā°žā°ĩā°žā°˛ā°ŋ", + "require_user_to_change_password_on_first_login": "ā°ŽāąŠā°ĻⰟā°ŋ ā°˛ā°žā°—ā°ŋā°¨āąâ€Œā°˛āą‹ ā°¯āą‚ā°œā°°āą ā°Ēā°žā°¸āąâ€Œā°ĩā°°āąā°Ąāą ā°Žā°žā°°āąā°šā°ĩⰞⰏā°ŋ ā°‰ā°‚ā°Ÿāąā°‚ā°Ļā°ŋ", "rescan": "ā°Žā°ŗāąā°ŗāą€ ā°¸āąā°•ā°žā°¨āą ā°šāą‡ā°¯ā°ŋ", "reset": "ā°¤ā°ŋā°°ā°ŋā°—ā°ŋ ā°¨ā°ŋā°°āąā°Ļā°žā°°ā°ŋā°‚ā°šāą", "reset_password": "ā°Ēā°žā°¸āąâ€Œā°ĩā°°āąā°Ąāąâ€Œā°¨āą ā°°āą€ā°¸āą†ā°Ÿāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", @@ -1034,6 +1062,7 @@ "restore_user": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°¨ā°ŋ ā°Ēāąā°¨ā°°āąā°Ļāąā°§ā°°ā°ŋā°‚ā°šāą", "restored_asset": "ā°†ā°¸āąā°¤ā°ŋ ā°Ēāąā°¨ā°°āąā°Ļāąā°§ā°°ā°ŋā°‚ā°šā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "resume": "ā°Ēāąā°¨ā°ƒā°Ēāąā°°ā°žā°°ā°‚ā°­ā°‚", + "retry_upload": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāąâ€Œā°¨āą ā°Žā°ŗāąā°˛āą€ ā°Ēāąā°°ā°¯ā°¤āąā°¨ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "review_duplicates": "ⰍⰕā°ŋā°˛āą€ā°˛ā°¨āą ā°¸ā°Žāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "role": "ā°Ēā°žā°¤āąā°°", "role_editor": "ā°¸ā°ĩā°°ā°ŋā°‚ā°šā°—ā°˛ā°ŋā°—āą‡ā°ĩā°žā°°āą", @@ -1045,6 +1074,7 @@ "say_something": "ā°ā°Ļāąˆā°¨ā°ž ā°šāą†ā°Ēāąā°Ēāą", "scan_all_libraries": "ā°…ā°¨āąā°¨ā°ŋ ā°˛āąˆā°Ŧāąā°°ā°°āą€ā°˛ā°¨āą ā°¸āąā°•ā°žā°¨āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "scan_library": "ā°¸āąā°•ā°žā°¨āą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", + "scan_settings": "ā°¸āąā°•ā°žā°¨āą ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛āą", "scanning_for_album": "ā°†ā°˛āąā°Ŧā°Žāą ā°•āą‹ā°¸ā°‚ ā°¸āąā°•ā°žā°¨āą ā°šāą‡ā°¸āąā°¤āą‹ā°‚ā°Ļā°ŋ...", "search": "ā°ĩāą†ā°¤ā°•ā°‚ā°Ąā°ŋ", "search_albums": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛ā°¨āą ā°ļāą‹ā°§ā°ŋā°‚ā°šāą", @@ -1083,6 +1113,7 @@ "select_featured_photo": "ā°Ģāą€ā°šā°°āą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°¨ ā°Ģāą‹ā°Ÿāą‹ā°¨āą ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "select_from_computer": "ā°•ā°‚ā°Ēāąā°¯āą‚ā°Ÿā°°āą ā°¨āąā°‚ā°Ąā°ŋ ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "select_keep_all": "ā°…ā°¨āąā°¨āą€ ā°‰ā°‚ā°šāą ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", + "select_library_owner": "ā°˛āąˆā°Ŧāąā°°ā°°āą€ ā°¯ā°œā°Žā°žā°¨ā°ŋā°¨ā°ŋ ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "select_new_face": "ā°•āąŠā°¤āąā°¤ ā°Žāąā°–ā°žā°¨āąā°¨ā°ŋ ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "select_photos": "ā°Ģāą‹ā°Ÿāą‹ā°˛ā°¨āą ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", "select_trash_all": "ā°…ā°¨āąā°¨āą€ ā°šāą†ā°¤āąā°¤ā°•āą ā°Žā°‚ā°šāąā°•āą‹ā°‚ā°Ąā°ŋ", @@ -1110,71 +1141,32 @@ "upload_status_errors": "ā°˛āą‹ā°Ēā°žā°˛āą", "upload_status_uploaded": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "upload_success": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°ĩā°ŋⰜⰝā°ĩā°‚ā°¤ā°Žāąˆā°‚ā°Ļā°ŋ, ā°•āąŠā°¤āąā°¤ ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°†ā°¸āąā°¤āąā°˛ā°¨āą ā°šāą‚ā°Ąā°Ÿā°žā°¨ā°ŋā°•ā°ŋ ā°Ēāą‡ā°œāą€ā°¨ā°ŋ ā°°ā°ŋā°Ģāąā°°āą†ā°ˇāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ.", - "upload_to_album_title": "ā°¨āą‡ā°°āąā°—ā°ž ā°’ā°• ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°˛āą‹ā°•ā°ŋ ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", - "upload_to_immich": "Immichā°˛āą‹ā°•ā°ŋ ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ ({count})", - "uploading": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°…ā°ĩāąā°¤āą‹ā°‚ā°Ļā°ŋ", - "uploading_media": "ā°Žāą€ā°Ąā°ŋā°¯ā°ž ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą ā°…ā°ĩāąā°¤āą‹ā°‚ā°Ļā°ŋ", - "uploads": "ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāąâ€Œā°˛āą", - "uploads_count": "{count, plural, one {# ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāą} other {# ā°…ā°Ēāąâ€Œā°˛āą‹ā°Ąāąâ€Œā°˛āą}}", - "url": "URL", "usage": "ā°ĩā°žā°Ąāąā°•", - "use_biometric": "ā°Ŧā°¯āą‹ā°Žāą†ā°Ÿāąā°°ā°ŋā°•āąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "use_browser_locale": "ā°Ŧāąā°°āąŒā°œā°°āą ā°­ā°žā°ˇā°ž ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "use_browser_locale_description": "ā°Žāą€ ā°Ŧāąā°°āąŒā°œā°°āą ā°­ā°žā°ˇā°ž ā°¸āą†ā°Ÿāąā°Ÿā°ŋā°‚ā°—āąâ€Œā°˛ ā°†ā°§ā°žā°°ā°‚ā°—ā°ž ā°¤āą‡ā°Ļāą€ā°˛āą, ā°¸ā°Žā°¯ā°žā°˛āą ā°Žā°°ā°ŋā°¯āą ā°¸ā°‚ā°–āąā°¯ā°˛ā°¨āą ā°šāą‚ā°Ēā°ŋā°¸āąā°¤āąā°‚ā°Ļā°ŋ.", - "use_current_connection": "ā°Ēāąā°°ā°¸āąā°¤āąā°¤ ā°•ā°¨āą†ā°•āąā°ˇā°¨āąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "use_custom_date_range": "ā°Ŧā°Ļāąā°˛āąā°—ā°ž ā°…ā°¨āąā°•āą‚ā°˛ ā°¤āą‡ā°Ļāą€ ā°Ēā°°ā°ŋā°§ā°ŋā°¨ā°ŋ ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "use_template": "ā°Ÿāą†ā°‚ā°Ēāąā°˛āą‡ā°Ÿāąâ€Œā°¨āą ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "user": "ā°ĩā°ŋā°¨āąā°¯āą‹ā°—ā°§ā°žā°°ā°ŋ", - "user_has_been_deleted": "Ⰸ ā°¯āą‚ā°œā°°āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°Ŧā°Ąāąā°Ąā°žā°°āą.", "user_id": "ā°ĩā°ŋā°¨āąā°¯āą‹ā°—ā°§ā°žā°°ā°ŋ ā°—āąā°°āąā°¤ā°ŋā°‚ā°Ēāą", - "user_pin_code_settings": "ā°Ēā°ŋā°¨āą ā°•āą‹ā°Ąāą", "user_purchase_settings": "ā°•āąŠā°¨āąā°—āą‹ā°˛āą", "user_purchase_settings_description": "ā°Žāą€ ā°•āąŠā°¨āąā°—āą‹ā°˛āąā°¨āą ā°¨ā°ŋā°°āąā°ĩā°šā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "user_usage_detail": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°— ā°ĩā°ŋā°ĩā°°ā°žā°˛āą", - "user_usage_stats": "ā°…ā°•āąŒā°‚ā°Ÿāą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°— ā°—ā°Ŗā°žā°‚ā°•ā°žā°˛āą", - "user_usage_stats_description": "ā°…ā°•āąŒā°‚ā°Ÿāą ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°— ā°—ā°Ŗā°žā°‚ā°•ā°žā°˛ā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", "username": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°ā°ŋ ā°Ēāą‡ā°°āą", "users": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛āą", "utilities": "ā°¯āąā°Ÿā°ŋā°˛ā°ŋā°Ÿāą€ā°¸āą", "validate": "ā°§āąƒā°ĩāą€ā°•ā°°ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "validate_endpoint_error": "ā°Ļā°¯ā°šāą‡ā°¸ā°ŋ ā°¸ā°°āąˆā°¨ URLā°¨āą ā°¨ā°Žāą‹ā°Ļāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ", "variables": "ā°ĩāą‡ā°°ā°ŋā°¯ā°Ŧāąā°˛āąā°¸āą", - "version": "ā°ĩāą†ā°°āąā°ˇā°¨āą", - "version_announcement_closing": "ā°Žāą€ ā°¸āąā°¨āą‡ā°šā°ŋā°¤āąā°Ąāą, Alex", - "version_announcement_message": "ā°šā°žā°¯āą! Immich ā°¯āąŠā°•āąā°• ā°•āąŠā°¤āąā°¤ ā°ĩāą†ā°°āąā°ˇā°¨āą ā°…ā°‚ā°Ļāąā°Ŧā°žā°Ÿāąā°˛āą‹ ⰉⰂā°Ļā°ŋ. ā°•āąŠā°¤āąā°¤ ā°Žā°žā°°āąā°Ēāąā°˛ ā°—āąā°°ā°ŋā°‚ā°šā°ŋ ā°¤āą†ā°˛āąā°¸āąā°•āą‹ā°ĩā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°ĩā°ŋā°Ąāąā°Ļā°˛ ā°—ā°Žā°¨ā°ŋā°•ā°˛ā°¨āą Ⱊā°Ļā°ĩā°‚ā°Ąā°ŋ. ā°Žāąā°–āąā°¯ā°‚ā°—ā°ž, ā°Žāą€ā°°āą WatchTower ā°˛āą‡ā°Ļā°ž Immichā°¨āą ā°¸āąā°ĩā°¯ā°‚ā°šā°žā°˛ā°•ā°‚ā°—ā°ž ā°…ā°Ēāąâ€Œā°Ąāą‡ā°Ÿāą ā°šāą‡ā°¸āą‡ ā°ĩā°ŋā°§ā°žā°¨ā°žā°¨āąā°¨ā°ŋ ā°‰ā°Ēā°¯āą‹ā°—ā°ŋā°¸āąā°¤āąā°‚ā°Ÿāą‡, ā°Žāą€ ā°¸āą†ā°Ÿā°Ēāą Ⰿⰰā°ŋā°—āąā°—ā°ž ā°Ēā°¨ā°ŋā°šāą‡ā°¯ā°Ąā°žā°¨ā°ŋā°•ā°ŋ ā°‡ā°Ļā°ŋ ā°‰ā°Ēā°¯āą‹ā°—ā°Ēā°Ąāąā°¤āąā°‚ā°Ļā°ŋ.", - "version_history": "ā°ĩāą†ā°°āąā°ˇā°¨āą Ⱊⰰā°ŋā°¤āąā°°", - "version_history_item": "{date}ā°¨ {version} ā°‡ā°¨āąâ€Œā°¸āąā°Ÿā°žā°˛āą ā°šāą‡ā°¯ā°Ŧā°Ąā°ŋā°‚ā°Ļā°ŋ", "video": "ā°ĩāą€ā°Ąā°ŋā°¯āą‹", "video_hover_setting": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āą ā°Ēāąˆā°¨ā°ž ā°šāą‹ā°ĩā°°āą ā°šāą‡ā°¯ā°—ā°žā°¨āą‡ ā°ĩāą€ā°Ąā°ŋā°¯āą‹ ā°Ēāąā°˛āą‡ ā°šāą†ā°¯āą", "video_hover_setting_description": "ā°Ĩā°‚ā°Ŧāąâ€Œā°¨āą†ā°¯ā°ŋā°˛āą ā°Ēāąˆā°¨ā°ž ā°šāą‹ā°ĩā°°āą ā°šāą‡ā°¯ā°—ā°žā°¨āą‡ Ⱊā°ŋā°šāąā°¨ā°‚ ā°Ēāąā°˛āą‡ ā°šāą‡ā°¯āą. ā°¨ā°ŋā°˛ā°ŋā°Ēā°ŋā°ĩāą‡ā°¯ā°Ŧā°Ąā°ŋā°¨ā°Ēāąā°ĒⰟā°ŋā°•āą€, ā°Ēāąā°˛āą‡ Ⱊā°ŋā°šāąā°¨ā°‚ā°Ēāąˆ ā°šāą‹ā°ĩā°°āą ā°šāą‡ā°¯ā°Ąā°‚ ā°Ļāąā°ĩā°žā°°ā°ž ā°Ēāąā°˛āą‡ā°Ŧāąā°¯ā°žā°•āą ā°Ēāąā°°ā°žā°°ā°‚ā°­ā°ŋā°‚ā°šā°Ŧā°Ąāąā°¤āąā°‚ā°Ļā°ŋ.", - "video_quality": "ā°ĩāą€ā°Ąā°ŋā°¯āą‹ ā°¨ā°žā°Ŗāąā°¯ā°¤", "videos": "ā°ĩāą€ā°Ąā°ŋā°¯āą‹ā°˛āą", "view": "ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", "view_album": "ā°†ā°˛āąā°Ŧā°Žāąâ€Œā°¨ā°ŋ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "view_all": "ā°…ā°¨āąā°¨āą€ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "view_all_users": "ā°ĩā°ŋā°¨ā°ŋā°¯āą‹ā°—ā°Ļā°žā°°āąā°˛ā°‚ā°Ļā°°ā°ŋā°¨āą€ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "view_details": "ā°ĩā°ŋā°ĩā°°ā°žā°˛ā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", - "view_in_timeline": "ā°Ÿāąˆā°Žāąâ€Œā°˛āąˆā°¨āąâ€Œā°˛āą‹ ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", - "view_link": "ā°˛ā°ŋā°‚ā°•āąâ€Œā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", + "view_links": "ā°˛ā°ŋā°‚ā°•āąâ€Œā°˛ā°¨āą ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "view_next_asset": "ā°¤ā°Ļāąā°Ēā°°ā°ŋ ā°†ā°¸āąā°¤ā°ŋā°¨ā°ŋ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", "view_previous_asset": "ā°Žāąā°¨āąā°ĒⰟā°ŋ ā°†ā°¸āąā°¤ā°ŋā°¨ā°ŋ ā°ĩāą€ā°•āąā°ˇā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "view_qr_code": "QR ā°•āą‹ā°Ąāąâ€Œā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", - "view_similar_photos": "ā°‡ā°˛ā°žā°‚ā°Ÿā°ŋ ā°Ģāą‹ā°Ÿāą‹ā°˛ā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", "view_stack": "ā°¸āąā°Ÿā°žā°•āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", - "viewer_remove_from_stack": "ā°¸āąā°Ÿā°žā°•āąâ€Œā°˛āą‹ ā°¨āąā°‚ā°Ąā°ŋ ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°‚ā°Ąā°ŋ", - "visibility": "ā°Ļāąƒā°ļāąā°¯ā°Žā°žā°¨ā°¤", "waiting": "ā°ĩāą‡ā°šā°ŋ ⰉⰂā°Ļā°ŋ", - "waiting_count": "ā°ĩāą‡ā°šā°ŋ ā°‰ā°¨āąā°¨ā°ĩā°ŋ: {count}", "warning": "ā°šāą†ā°šāąā°šā°°ā°ŋā°•", "week": "ā°ĩā°žā°°ā°‚", - "welcome": "ā°¸āąā°ĩā°žā°—ā°¤ā°‚", - "welcome_to_immich": "Immichā°•āą ā°¸āąā°ĩā°žā°—ā°¤ā°‚", - "whats_new": "ā°•āąŠā°¤āąā°¤ ā°ĩā°ŋā°ļāą‡ā°ˇā°žā°˛āą", - "whats_new_settings_subtitle": "Immich ā°˛āą‹ā°¨ā°ŋ ā°•āąŠā°¤āąā°¤ ā°ĩā°ŋā°ļāą‡ā°ˇā°žā°˛ā°¨āą ā°šāą‚ā°Ąā°‚ā°Ąā°ŋ", - "whats_new_version": "ā°ĩāą†ā°°āąā°ˇā°¨āą {version}", - "wifi_name": "Wi-Fi ā°Ēāą‡ā°°āą", - "workflow_delete_prompt": "ā°Žāą€ā°°āą ā°–ā°šāąā°šā°ŋā°¤ā°‚ā°—ā°ž Ⰸ ā°ĩā°°āąā°•āąâ€Œā°Ģāąā°˛āą‹ā°¨āą ā°¤āąŠā°˛ā°—ā°ŋā°‚ā°šā°žā°˛ā°¨āąā°•āąā°‚ā°Ÿāąā°¨āąā°¨ā°žā°°ā°ž?", - "you_dont_have_any_shared_links": "ā°Žāą€ā°•āą ā°ˇāą‡ā°°āą ā°šāą‡ā°¸ā°ŋā°¨ ā°˛ā°ŋā°‚ā°•āąâ€Œā°˛āą ā°ā°ĩāą€ ā°˛āą‡ā°ĩāą", - "your_wifi_name": "ā°Žāą€ Wi-Fi ā°¨āą†ā°Ÿāąâ€Œā°ĩā°°āąā°•āą ā°Ēāą‡ā°°āą", - "zoom_image": "Ⱊā°ŋā°¤āąā°°ā°žā°¨āąā°¨ā°ŋ ā°œāą‚ā°Žāą ā°šāą‡ā°¯ā°‚ā°Ąā°ŋ" + "welcome": "ā°¸āąā°ĩā°žā°—ā°¤ā°‚" } diff --git a/i18n/th.json b/i18n/th.json index 0284d08a25..d8d34d024a 100644 --- a/i18n/th.json +++ b/i18n/th.json @@ -1,49 +1,52 @@ { "about": "āš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸ā¸ąā¸š", - "account": "ā¸šā¸ąā¸ā¸Šā¸ĩ", + "account": "ā¸šā¸ąā¸ā¸Šā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰", "account_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸šā¸ąā¸ā¸Šā¸ĩ", "acknowledge": "ā¸Ŗā¸ąā¸šā¸—ā¸Ŗā¸˛ā¸š", - "action": "⏁⏞⏪⏁⏪⏰⏗⏺", + "action": "ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗ", "action_common_update": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•", - "action_description": "ā¸Šā¸¸ā¸”ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸—ā¸ŗā¸ā¸ąā¸šā¸Ŗā¸˛ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸œāšˆā¸˛ā¸™ā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸­ā¸‡", - "actions": "⏁⏞⏪⏁⏪⏰⏗⏺", + "actions": "ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗ", "active": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸—ā¸ŗā¸‡ā¸˛ā¸™", "active_count": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸—ā¸ŗā¸‡ā¸˛ā¸™: {count}", "activity": "ā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ą", + "activity_changed": "ā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ą{enabled, select, true {āš€ā¸›ā¸´ā¸”} other {⏛⏴⏔}}⏭ā¸ĸā¸šāšˆ", "add": "āš€ā¸žā¸´āšˆā¸Ą", "add_a_description": "āš€ā¸žā¸´āšˆā¸Ąā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", "add_a_location": "āš€ā¸žā¸´āšˆā¸Ąā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", "add_a_name": "āš€ā¸žā¸´āšˆā¸Ąā¸Šā¸ˇāšˆā¸­", "add_a_title": "āš€ā¸žā¸´āšˆā¸Ąā¸Ģā¸ąā¸§ā¸‚āš‰ā¸­", - "add_action": "āš€ā¸žā¸´āšˆā¸Ąā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸°ā¸—ā¸ŗ", + "add_action": "āš€ā¸žā¸´āšˆā¸Ąā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗ", "add_assets": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­", "add_birthday": "āš€ā¸žā¸´āšˆā¸Ąā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”", "add_endpoint": "āš€ā¸žā¸´āšˆā¸Ąā¸›ā¸Ĩ⏞ā¸ĸ⏗⏞⏇", "add_exclusion_pattern": "āš€ā¸žā¸´āšˆā¸Ąā¸‚āš‰ā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™", + "add_filter": "āš€ā¸žā¸´āšˆā¸Ąā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", "add_location": "āš€ā¸žā¸´āšˆā¸Ąā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", + "add_more_users": "āš€ā¸žā¸´āšˆā¸Ąā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "add_partner": "āš€ā¸žā¸´āšˆā¸Ąā¸„ā¸šāšˆā¸Ģā¸š", + "add_path": "āš€ā¸žā¸´āšˆā¸Ąā¸žā¸˛ā¸—ā¸—ā¸ĩāšˆā¸•ā¸ąāš‰ā¸‡", "add_photos": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", - "add_step": "āš€ā¸žā¸´āšˆā¸Ąā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", "add_tag": "āš€ā¸žā¸´āšˆā¸Ąāšā¸—āš‡ā¸", "add_to": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡â€Ļ", "add_to_album": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "add_to_album_bottom_sheet_added": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ {album} āšā¸Ĩāš‰ā¸§", "add_to_album_bottom_sheet_already_exists": "⏭ā¸ĸā¸šāšˆāšƒā¸™ {album} ⏭ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", - "add_to_album_bottom_sheet_some_local_assets": "ā¸Ēā¸ˇāšˆā¸­āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸šā¸˛ā¸‡ā¸Ēāšˆā¸§ā¸™āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰", - "add_to_albums": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "add_to_album_bottom_sheet_some_local_assets": "āš„ā¸Ÿā¸ĨāšŒā¸šā¸˛ā¸‡ā¸Ēāšˆā¸§ā¸™āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰", + "add_to_albums": "āš€ā¸žā¸´āšˆā¸Ąāš€ā¸‚āš‰ā¸˛āšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "add_to_albums_count": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą ({count})", "add_to_bottom_bar": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡", - "add_upload_to_stack": "āš€ā¸žā¸´āšˆā¸Ąā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš€ā¸‚āš‰ā¸˛ Stack", + "add_to_shared_album": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", + "add_upload_to_stack": "āš€ā¸žā¸´āšˆā¸Ąā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš€ā¸‚āš‰ā¸˛ stack", "add_url": "āš€ā¸žā¸´āšˆā¸Ą URL", "added_to_archive": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "added_to_favorites": "āš€ā¸žā¸´āšˆā¸Ąāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", - "added_to_favorites_count": "āš€ā¸žā¸´āšˆā¸Ą {count, number} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", + "added_to_favorites_count": "āš€ā¸žā¸´āšˆā¸Ą {count, number} ā¸Ŗā¸šā¸›āš€ā¸‚āš‰ā¸˛ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", "admin": { - "add_exclusion_pattern_description": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸šā¸›āšā¸šā¸šā¸‚āš‰ā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™ ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ *, ** āšā¸Ĩ⏰ ? āšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸šāš„ā¸”āš‰ ā¸Ģā¸˛ā¸ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™āš„ā¸”āš€ā¸Ŗāš‡ā¸ā¸—ā¸­ā¸Ŗā¸ĩ⏗ā¸ĩāšˆā¸Šā¸ˇāšˆā¸­ā¸§āšˆā¸˛ \"Raw\" āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"**/Raw/**\" ā¸–āš‰ā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸—ā¸ĩāšˆā¸Ĩā¸‡ā¸—āš‰ā¸˛ā¸ĸā¸”āš‰ā¸§ā¸ĸ \".tif\" āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"**/*.tif\" ā¸–āš‰ā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™ā¸žā¸˛ā¸˜ā¸—ā¸ĩāšˆāš€ā¸Ŗā¸´āšˆā¸Ąā¸ˆā¸˛ā¸āš„ā¸”āš€ā¸Ŗā¸ā¸—ā¸­ā¸Ŗā¸ĩā¸šā¸™ā¸Ēā¸¸ā¸”āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"/ā¸žā¸˛ā¸˜/⏗ā¸ĩāšˆā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗ/ā¸Ĩā¸°āš€ā¸§āš‰ā¸™/**\"", + "add_exclusion_pattern_description": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸šā¸›āšā¸šā¸šā¸‚āš‰ā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™ ā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ *, ** āšā¸Ĩ⏰ ? ā¸Ģā¸˛ā¸ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™āš„ā¸”āš€ā¸Ŗāš‡ā¸ā¸—ā¸­ā¸Ŗā¸ĩ⏗ā¸ĩāšˆā¸Šā¸ˇāšˆā¸­ā¸§āšˆā¸˛ \"Raw\" āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"**/Raw/**\" ā¸–āš‰ā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸—ā¸ĩāšˆā¸Ĩā¸‡ā¸—āš‰ā¸˛ā¸ĸā¸”āš‰ā¸§ā¸ĸ \".tif\" āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"**/*.tif\" ā¸–āš‰ā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸°āš€ā¸§āš‰ā¸™ā¸žā¸˛ā¸˜ā¸—ā¸ĩāšˆāš€ā¸Ŗā¸´āšˆā¸Ąā¸ˆā¸˛ā¸āš„ā¸”āš€ā¸Ŗā¸ā¸—ā¸­ā¸Ŗā¸ĩā¸šā¸™ā¸Ēā¸¸ā¸”āšƒā¸Ģāš‰āšƒā¸Šāš‰ \"/ā¸žā¸˛ā¸˜/⏗ā¸ĩāšˆā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗ/ā¸Ĩā¸°āš€ā¸§āš‰ā¸™/**\"", "admin_user": "ā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩ", - "asset_offline_description": "āš„ā¸Ąāšˆā¸žā¸šāš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ⏠⏞ā¸ĸ⏙⏭⏁⏙ā¸ĩāš‰āšƒā¸™ā¸”ā¸´ā¸Ēā¸āšŒ āšā¸Ĩā¸°ā¸–ā¸šā¸ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸—ā¸ĩāšˆā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§ ā¸Ģā¸˛ā¸āš„ā¸Ÿā¸ĨāšŒā¸–ā¸šā¸ā¸ĸāš‰ā¸˛ā¸ĸ⏠⏞ā¸ĸāšƒā¸™āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš€ā¸žā¸ˇāšˆā¸­ā¸Ģ⏞ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸‚āš‰ā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ ā¸Ģā¸˛ā¸ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸§āšˆā¸˛ Immich ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸Ÿā¸ĨāšŒ ⏓ ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡āš„ā¸”āš‰ āšā¸Ĩ⏰⏗⏺⏁⏞⏪ā¸Ēāšā¸ā¸™āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ⏭ā¸ĩā¸ā¸„ā¸Ŗā¸ąāš‰ā¸‡", - "authentication_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸•ā¸™", - "authentication_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™, OAuth, āšā¸Ĩā¸°ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸ĸā¸ļ⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸•ā¸™ā¸­ā¸ˇāšˆā¸™āš†", + "asset_offline_description": "āš„ā¸Ąāšˆā¸žā¸šāš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ⏠⏞ā¸ĸ⏙⏭⏁⏙ā¸ĩāš‰āšƒā¸™ā¸”ā¸´ā¸Ēā¸āšŒ āšā¸Ĩā¸°ā¸–ā¸šā¸ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸—ā¸ĩāšˆā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§ ā¸Ģā¸˛ā¸āš„ā¸Ÿā¸ĨāšŒā¸–ā¸šā¸ā¸ĸāš‰ā¸˛ā¸ĸ⏠⏞ā¸ĸāšƒā¸™āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš€ā¸žā¸ˇāšˆā¸­ā¸Ģ⏞ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸‚āš‰ā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ ā¸Ģā¸˛ā¸ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸§āšˆā¸˛ Immich ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸Ÿā¸ĨāšŒā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡āš„ā¸”āš‰ āšā¸Ĩ⏰⏗⏺⏁⏞⏪ā¸Ēāšā¸ā¸™āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩ⏭ā¸ĩā¸ā¸„ā¸Ŗā¸ąāš‰ā¸‡", + "authentication_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇", + "authentication_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™, OAuth, āšā¸Ĩā¸°ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸­ā¸ˇāšˆā¸™āš†", "authentication_settings_disable_all": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸›ā¸´ā¸”ā¸§ā¸´ā¸˜ā¸ĩ⏁⏞⏪ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ? ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸ˆā¸°ā¸–ā¸šā¸ā¸›ā¸´ā¸”ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "authentication_settings_reenable": "āš€ā¸žā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”āšƒā¸Ģā¸Ąāšˆ āšƒā¸Ģāš‰āšƒā¸Šāš‰ā¸„ā¸ŗā¸Ēā¸ąāšˆā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", "background_task_job": "ā¸‡ā¸˛ā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", @@ -53,31 +56,28 @@ "backup_onboarding_1_description": "ā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸™ā¸­ā¸ā¸Ē⏖⏞⏙⏗ā¸ĩāšˆā¸šā¸™ā¸„ā¸Ĩā¸˛ā¸§ā¸”āšŒā¸Ģ⏪⏎⏭⏗ā¸ĩāšˆā¸•ā¸ąāš‰ā¸‡ā¸­ā¸ˇāšˆā¸™", "backup_onboarding_2_description": "ā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸—ā¸ĩāšˆā¸•āšˆā¸˛ā¸‡ā¸ā¸ąā¸™ ⏋ā¸ļāšˆā¸‡ā¸Ŗā¸§ā¸Ąā¸–ā¸ļā¸‡āš„ā¸Ÿā¸ĨāšŒā¸Ģā¸Ĩā¸ąā¸āšā¸Ĩā¸°āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", "backup_onboarding_3_description": "ā¸ˆā¸ŗā¸™ā¸§ā¸™ā¸Šā¸¸ā¸”ā¸‚ā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” ā¸Ŗā¸§ā¸Ąā¸–ā¸ļā¸‡āš„ā¸Ÿā¸ĨāšŒāš€ā¸”ā¸´ā¸Ą ⏋ā¸ļāšˆā¸‡ā¸Ŗā¸§ā¸Ąā¸–ā¸ļ⏇ 1 ā¸Šā¸¸ā¸”ā¸—ā¸ĩāšˆā¸•ā¸ąāš‰ā¸‡ā¸­ā¸ĸā¸šāšˆā¸„ā¸™ā¸Ĩā¸°ā¸–ā¸´āšˆā¸™ āšā¸Ĩ⏰ā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ 2 ā¸Šā¸¸ā¸”", - "backup_onboarding_description": "āšā¸™ā¸°ā¸™ā¸ŗāšƒā¸Ģāš‰āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸šā¸š 3-2-1 āš€ā¸žā¸ˇāšˆā¸­ā¸›ā¸ā¸›āš‰ā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ ā¸„ā¸§ā¸Ŗāš€ā¸āš‡ā¸šā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸‚ā¸­ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž/⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩā¸°ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏂⏭⏇ Immich āš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸—ā¸ąāšˆā¸§ā¸–ā¸ļ⏇", + "backup_onboarding_description": "āšā¸™ā¸°ā¸™ā¸ŗāšƒā¸Ģāš‰āšƒā¸Šāš‰ ⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸šā¸š 3-2-1āš€ā¸žā¸ˇāšˆā¸­ā¸›ā¸ā¸›āš‰ā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ ā¸„ā¸§ā¸Ŗāš€ā¸āš‡ā¸šā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸‚ā¸­ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž/⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩā¸°ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏂⏭⏇ Immich āš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸—ā¸ąāšˆā¸§ā¸–ā¸ļ⏇", "backup_onboarding_footer": "ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ąāš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸ā¸ąā¸šā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ Immich āš‚ā¸›ā¸Ŗā¸”ā¸”ā¸šāš€ā¸­ā¸ā¸Ēā¸˛ā¸Ŗā¸›ā¸Ŗā¸°ā¸ā¸­ā¸š", "backup_onboarding_parts_title": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸šā¸š 3-2-1 ā¸›ā¸Ŗā¸°ā¸ā¸­ā¸šāš„ā¸›ā¸”āš‰ā¸§ā¸ĸ:", "backup_onboarding_title": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_settings": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "cleared_jobs": "āš€ā¸„ā¸Ĩā¸ĩā¸ĸā¸ŖāšŒā¸‡ā¸˛ā¸™ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸š: {job}", - "config_set_by_file": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸ŗā¸Ĩā¸ąā¸‡ā¸–ā¸šā¸ā¸ā¸ŗā¸Ģā¸™ā¸”āš‚ā¸”ā¸ĸāš„ā¸Ÿā¸ĨāšŒā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", + "config_set_by_file": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸„ā¸­ā¸™ā¸Ÿā¸´ā¸ā¸ā¸ŗā¸Ĩā¸ąā¸‡ā¸–ā¸šā¸ā¸ā¸ŗā¸Ģā¸™ā¸”āš‚ā¸”ā¸ĸāš„ā¸Ÿā¸ĨāšŒā¸„ā¸­ā¸™ā¸Ÿā¸´ā¸", "confirm_delete_library": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸­ā¸ĸ⏞⏁ā¸Ĩā¸šā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž {library} ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", - "confirm_delete_library_assets": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ? ā¸Ēā¸ĩāšˆā¸­{count, plural, one { # ⏪⏞ā¸ĸ⏁⏞⏪} other {ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” # ⏪⏞ā¸ĸ⏁⏞⏪}}āšƒā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩ⏚⏭⏭⏁⏈⏞⏁ Immich āš‚ā¸”ā¸ĸ⏖⏞⏧⏪ āš‚ā¸”ā¸ĸāš„ā¸Ÿā¸ĨāšŒā¸ˆā¸°ā¸ĸā¸ąā¸‡ā¸„ā¸‡ā¸­ā¸ĸā¸šāšˆā¸šā¸™ā¸”ā¸´ā¸Ēā¸āšŒā¸­ā¸ĸā¸šāšˆ", + "confirm_delete_library_assets": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ? ā¸Ēā¸ĩāšˆā¸­{count, plural, one { # ⏪⏞ā¸ĸ⏁⏞⏪} other {ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” # ⏪⏞ā¸ĸ⏁⏞⏪}}āšƒā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩ⏚⏭⏭⏁⏈⏞⏁ Immich āš‚ā¸”ā¸ĸ⏖⏞⏧⏪ āš„ā¸Ÿā¸ĨāšŒā¸ˆā¸°ā¸ĸā¸ąā¸‡ā¸„ā¸‡ā¸­ā¸ĸā¸šāšˆā¸šā¸™ā¸”ā¸´ā¸Ēā¸āšŒ", "confirm_email_below": "āš‚ā¸›ā¸Ŗā¸”ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ āš‚ā¸”ā¸ĸā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ąā¸žāšŒ \"{email}\" ā¸‚āš‰ā¸˛ā¸‡ā¸Ĩāšˆā¸˛ā¸‡", "confirm_reprocess_all_faces": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸Ģā¸Ąāšˆ? ā¸Šā¸ˇāšˆā¸­ā¸„ā¸™ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šāš„ā¸›ā¸”āš‰ā¸§ā¸ĸ", "confirm_user_password_reset": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ā¸‚ā¸­ā¸‡ {user} ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "confirm_user_pin_code_reset": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ē PIN ⏂⏭⏇ {user}", - "copy_config_to_clipboard_description": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸°ā¸šā¸šā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™āšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸š JSON āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”", "create_job": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸‡ā¸˛ā¸™", "cron_expression": "ā¸Ŗā¸šā¸›āšā¸šā¸š cron", "cron_expression_description": "ā¸•ā¸ąāš‰ā¸‡ā¸Šāšˆā¸§ā¸‡āš€ā¸§ā¸Ĩā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗā¸Ēāšā¸ā¸™āš‚ā¸”ā¸ĸāšƒā¸Šāš‰ā¸Ŗā¸šā¸›āšā¸šā¸š cron ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ąā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸­ā¸´ā¸‡ Crontab Guru", - "cron_expression_presets": "ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ŗā¸šā¸›āšā¸šā¸š cron", + "cron_expression_presets": "ā¸žā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸šā¸›āšā¸šā¸š cron", "disable_login": "⏛⏴⏔⏁⏞⏪ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™", - "download_csv": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ CSV", "duplicate_detection_job_description": "āšƒā¸Šāš‰ machine learning ā¸ā¸ąā¸šā¸Ēā¸ĩāšˆā¸­āš€ā¸žā¸ˇāšˆā¸­ā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸„ā¸Ĩāš‰ā¸˛ā¸ĸā¸ā¸ąā¸™ āš‚ā¸”ā¸ĸāšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ąā¸ˆā¸‰ā¸Ŗā¸´ā¸ĸ⏰", "exclusion_pattern_description": "ā¸‚āš‰ā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸°āš€ā¸§āš‰ā¸™āš„ā¸Ÿā¸ĨāšŒāšā¸Ĩā¸°āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸‚ā¸“ā¸°ā¸Ēāšā¸ā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ ā¸Ąā¸ĩā¸›ā¸Ŗā¸°āš‚ā¸ĸā¸Šā¸™āšŒāš€ā¸Ąā¸ˇāšˆā¸­āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ąā¸ĩāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸­ā¸ĸā¸˛ā¸ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ āš€ā¸Šāšˆā¸™āš„ā¸Ÿā¸ĨāšŒ RAW", - "export_config_as_json_description": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸°ā¸šā¸šā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™āš„ā¸›ā¸ĸā¸ąā¸‡āš„ā¸Ÿā¸ĨāšŒāšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸š JSON", - "external_libraries_page_description": "ā¸Ģā¸™āš‰ā¸˛ā¸„ā¸Ĩā¸ąā¸‡āšā¸­ā¸”ā¸Ąā¸´ā¸™ā¸ ā¸˛ā¸ĸ⏙⏭⏁", + "external_libraries_page_description": "ā¸Ģā¸™āš‰ā¸˛ā¸•āšˆā¸˛ā¸‡ā¸„ā¸Ĩā¸ąā¸‡āšā¸­ā¸”ā¸Ąā¸´ā¸™ā¸ ā¸˛ā¸ĸ⏙⏭⏁", "face_detection": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛", "face_detection_description": "ā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛āšƒā¸™ā¸Ēā¸ĩāšˆā¸­āš‚ā¸”ā¸ĸāšƒā¸Šāš‰ machine learning ⏧⏴⏔ā¸ĩāš‚ā¸­ā¸ˆā¸°āšƒā¸Šāš‰ā¸ ā¸˛ā¸žā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸ˆā¸˛ā¸ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ \"ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”\" ā¸ˆā¸°ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸Ēā¸ĩāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” \"⏂⏞⏔ā¸Ģ⏞ā¸ĸ\" ā¸ˆā¸°ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸Ēā¸ĩāšˆā¸­ā¸—ā¸ĩāšˆā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩ āšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šāšā¸Ĩāš‰ā¸§ā¸ˆā¸°ā¸–ā¸šā¸āš€ā¸‚āš‰ā¸˛ā¸„ā¸´ā¸§ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸ā¸˛ā¸Ŗā¸ˆā¸”ā¸ˆā¸ŗāšƒā¸šā¸Ģā¸™āš‰ā¸˛ āš€ā¸žā¸´āšˆā¸Ąāš€ā¸‚āš‰ā¸˛āš„ā¸›āšƒā¸™ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§ā¸Ģā¸Ŗā¸ˇā¸­ā¸„ā¸™āšƒā¸Ģā¸Ąāšˆ", "facial_recognition_job_description": "ā¸™ā¸ŗāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šāš„ā¸”āš‰āš„ā¸›ā¸ˆā¸ąā¸šā¸ā¸Ĩā¸¸āšˆā¸Ąā¸•ā¸˛ā¸Ąā¸œā¸šāš‰ā¸„ā¸™ ā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™ā¸™ā¸ĩāš‰ā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸Ģā¸Ĩā¸ąā¸‡ā¸ˆā¸˛ā¸ā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ \"ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”\" ⏈⏰⏈⏺⏁ā¸Ĩā¸¸āšˆā¸Ąāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸Ģā¸Ąāšˆ \"⏂⏞⏔ā¸Ģ⏞ā¸ĸ\" ā¸ˆā¸°ā¸ˆā¸ąā¸”ā¸„ā¸´ā¸§āšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ŗā¸°ā¸šā¸¸ā¸„ā¸™", @@ -90,40 +90,22 @@ "image_fullsize_enabled_description": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš€ā¸•āš‡ā¸Ąā¸‚ā¸™ā¸˛ā¸”ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ŗā¸šā¸›āšā¸šā¸šā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šāš€ā¸§āš‡ā¸š āš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ \"ā¸–āš‰ā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸”ā¸šā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡\" ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡āšā¸šā¸šā¸ā¸ąā¸‡ā¸ˆā¸°ā¸–ā¸šā¸āšƒā¸Šāš‰āš‚ā¸”ā¸ĸā¸•ā¸Ŗā¸‡āš‚ā¸”ā¸ĸ⏕⏪⏇ āšā¸Ĩā¸°āš„ā¸Ąāšˆā¸Ēāšˆā¸‡ā¸œā¸Ĩā¸•āšˆā¸­ā¸Ŗā¸šā¸›āšā¸šā¸šāš„ā¸Ąāšˆāš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šāš€ā¸§āš‡ā¸š āš€ā¸Šāšˆā¸™ JPEG", "image_fullsize_quality_description": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”āš€ā¸•āš‡ā¸Ąā¸ˆā¸˛ā¸ 1-100 ā¸„āšˆā¸˛ā¸ĸā¸´āšˆā¸‡ā¸Ēā¸šā¸‡ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸ĸā¸´āšˆā¸‡ā¸Ēā¸šā¸‡ āšā¸•āšˆāšā¸Ĩā¸ā¸Ąā¸˛ā¸”āš‰ā¸§ā¸ĸā¸‚ā¸™ā¸˛ā¸”āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāšƒā¸Ģā¸āšˆā¸‚ā¸ļāš‰ā¸™", "image_fullsize_title": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”āš€ā¸•āš‡ā¸Ą", - "image_prefer_embedded_preview": "āšƒā¸Šāš‰ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡āšā¸šā¸šā¸ā¸ąā¸‡ā¸•ā¸ąā¸§", - "image_prefer_embedded_preview_setting_description": "āšƒā¸Šāš‰ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡āšā¸šā¸šā¸ā¸ąā¸‡ā¸•ā¸ąā¸§āšƒā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž RAW āšƒā¸™ā¸ā¸˛ā¸Ŗā¸§ā¸´āš€ā¸„ā¸Ŗā¸˛ā¸°ā¸ĢāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ģ⏞⏁ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšƒā¸Šāš‰āš„ā¸”āš‰ ā¸Ēā¸´āšˆā¸‡ā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Šāšˆā¸§ā¸ĸāšƒā¸Ģāš‰ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ąā¸ĩā¸Ēā¸ĩā¸Ēā¸ąā¸™ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡ā¸Ąā¸˛ā¸ā¸ĸā¸´āšˆā¸‡ā¸‚ā¸ļāš‰ā¸™ āšā¸•āšˆā¸­ā¸ĸāšˆā¸˛ā¸‡āš„ā¸Ŗā¸āš‡ā¸•ā¸˛ā¸Ą ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸ļāš‰ā¸™ā¸­ā¸ĸā¸šāšˆā¸ā¸ąā¸šā¸ā¸Ĩāš‰ā¸­ā¸‡ā¸–āšˆā¸˛ā¸ĸā¸Ŗā¸šā¸› āšā¸Ĩā¸°ā¸­ā¸˛ā¸ˆā¸ˆā¸°āš€ā¸ā¸´ā¸”ā¸Ŗāšˆā¸­ā¸‡ā¸Ŗā¸­ā¸ĸ ā¸Ĩ⏞ā¸ĸ ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸žā¸ļ⏇⏛⏪⏰ā¸Ēā¸‡ā¸„āšŒā¸šā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž ⏈⏞⏁⏁⏞⏪ā¸ĸāšˆā¸­ā¸‚ā¸™ā¸˛ā¸”āš„ā¸Ÿā¸ĨāšŒ", + "image_prefer_embedded_preview": "āšƒā¸Šāš‰ā¸žā¸Ŗā¸ĩā¸§ā¸´ā¸§āšā¸šā¸šā¸ā¸ąā¸‡ā¸•ā¸ąā¸§", + "image_prefer_embedded_preview_setting_description": "āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡ā¸ ā¸˛ā¸žāšā¸šā¸šā¸ā¸ąā¸‡ā¸•ā¸ąā¸§āšƒā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž RAW āšƒā¸™ā¸ā¸˛ā¸Ŗā¸§ā¸´āš€ā¸„ā¸Ŗā¸˛ā¸°ā¸ĢāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ģ⏞⏁ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšƒā¸Šāš‰āš„ā¸”āš‰ ā¸Ēā¸´āšˆā¸‡ā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Šāšˆā¸§ā¸ĸāšƒā¸Ģāš‰ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ąā¸ĩā¸Ēā¸ĩā¸Ēā¸ąā¸™ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡ā¸Ąā¸˛ā¸ā¸ĸā¸´āšˆā¸‡ā¸‚ā¸ļāš‰ā¸™ āšā¸•āšˆā¸­ā¸ĸāšˆā¸˛ā¸‡āš„ā¸Ŗā¸āš‡ā¸•ā¸˛ā¸Ą ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸ļāš‰ā¸™ā¸­ā¸ĸā¸šāšˆā¸ā¸ąā¸šā¸ā¸Ĩāš‰ā¸­ā¸‡ā¸–āšˆā¸˛ā¸ĸā¸Ŗā¸šā¸› āšā¸Ĩā¸°ā¸­ā¸˛ā¸ˆā¸ˆā¸°āš€ā¸ā¸´ā¸”ā¸Ŗāšˆā¸­ā¸‡ā¸Ŗā¸­ā¸ĸ ā¸Ĩ⏞ā¸ĸ ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸žā¸ļ⏇⏛⏪⏰ā¸Ēā¸‡ā¸„āšŒā¸šā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž ⏈⏞⏁⏁⏞⏪ā¸ĸāšˆā¸­ā¸‚ā¸™ā¸˛ā¸”āš„ā¸Ÿā¸ĨāšŒ", "image_prefer_wide_gamut": "āšƒā¸Šāš‰ā¸Šāšˆā¸§ā¸‡ā¸Ēā¸ĩā¸ā¸§āš‰ā¸˛ā¸‡", "image_prefer_wide_gamut_setting_description": "āšƒā¸Šāš‰ Display P3 ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ ā¸˛ā¸žā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ (thumbnails) āš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸ąā¸ā¸Šā¸˛ā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸”āšƒā¸Ēā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸Šāšˆā¸§ā¸‡ā¸Ēā¸ĩ⏗ā¸ĩāšˆā¸ā¸§āš‰ā¸˛ā¸‡ā¸‚ā¸ļāš‰ā¸™ ⏭ā¸ĸāšˆā¸˛ā¸‡āš„ā¸Ŗā¸āš‡ā¸•ā¸˛ā¸Ą ā¸ ā¸˛ā¸žā¸­ā¸˛ā¸ˆāšā¸Ēā¸”ā¸‡ā¸œā¸Ĩāšā¸•ā¸ā¸•āšˆā¸˛ā¸‡ā¸ā¸ąā¸™ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāš€ā¸āšˆā¸˛ā¸—ā¸ĩāšˆāšƒā¸Šāš‰āš€ā¸§āš‡ā¸šāš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒāš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™āš€ā¸āšˆā¸˛ ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ sRGB ⏈⏰ā¸ĸā¸ąā¸‡ā¸„ā¸‡āšƒā¸Šāš‰ sRGB ā¸•āšˆā¸­āš„ā¸›āš€ā¸žā¸ˇāšˆā¸­ā¸Ģā¸Ĩā¸ĩā¸āš€ā¸Ĩā¸ĩāšˆā¸ĸā¸‡ā¸ā¸˛ā¸Ŗāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩ⏇⏂⏭⏇ā¸Ēā¸ĩ", "image_preview_description": "ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸›ā¸˛ā¸™ā¸ā¸Ĩ⏞⏇⏗ā¸ĩāšˆā¸–ā¸šā¸ā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ āšƒā¸Šāš‰ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸šāšā¸­ā¸Ēāš€ā¸‹āš‡ā¸•āš€ā¸”ā¸ĩāšˆā¸ĸā¸§āšā¸Ĩ⏰ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāš€ā¸Ŗā¸ĩā¸ĸā¸™ā¸Ŗā¸šāš‰ā¸‚ā¸­ā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ (Machine Learning)", "image_preview_quality_description": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸•ā¸ąāš‰ā¸‡āšā¸•āšˆ 1-100 ā¸ĸā¸´āšˆā¸‡ā¸Ēā¸šā¸‡ā¸ĸā¸´āšˆā¸‡ā¸”ā¸ĩ āšā¸•āšˆā¸ˆā¸°ā¸—ā¸ŗāšƒā¸Ģāš‰āš„ā¸Ÿā¸ĨāšŒā¸Ąā¸ĩā¸‚ā¸™ā¸˛ā¸”āšƒā¸Ģā¸āšˆā¸‚ā¸ļāš‰ā¸™āšā¸Ĩā¸°ā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰āšā¸­ā¸›ā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡ā¸Šāš‰ā¸˛ā¸Ĩ⏇ ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸•āšˆā¸ŗā¸­ā¸˛ā¸ˆā¸Ēāšˆā¸‡ā¸œā¸Ĩā¸•āšˆā¸­ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸ž Machine Learning", "image_preview_title": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸žā¸Ŗā¸ĩ⏧⏴⏧", - "image_progressive": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸šā¸šāš‚ā¸›ā¸Ŗāš€ā¸ā¸Ŗā¸Ē⏋ā¸ĩ⏟", - "image_progressive_description": "āš€ā¸‚ā¸ĩā¸ĸā¸™ā¸ ā¸˛ā¸ž JPEG āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸Ēā¸”ā¸‡ā¸œā¸Ĩāšā¸šā¸šā¸„āšˆā¸­ā¸ĸāš† ā¸Šā¸ąā¸”ā¸‚ā¸ļāš‰ā¸™ā¸‚ā¸“ā¸°āš‚ā¸Ģā¸Ĩ⏔ ā¸—ā¸ąāš‰ā¸‡ā¸™ā¸ĩāš‰ā¸ˆā¸°āš„ā¸Ąāšˆā¸Ąā¸ĩ⏜ā¸Ĩā¸ā¸ąā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž WebP", "image_quality": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸ž", "image_resolution": "ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", - "image_resolution_description": "ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ēā¸šā¸‡ā¸ā¸§āšˆā¸˛ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸āš‡ā¸šā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš„ā¸”āš‰ā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛āšā¸•āšˆāšƒā¸Šāš‰āš€ā¸§ā¸Ĩ⏞ encode ā¸™ā¸˛ā¸™ā¸ā¸§āšˆā¸˛ ā¸Ąā¸ĩāš„ā¸Ÿā¸ĨāšŒāšƒā¸Ģā¸āšˆā¸ā¸§āšˆā¸˛ āšā¸Ĩ⏰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸”ā¸„ā¸§ā¸˛ā¸Ąā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡ā¸‚ā¸­ā¸‡āšā¸­ā¸›ā¸Ĩā¸‡āš„ā¸”āš‰", + "image_resolution_description": "ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ēā¸šā¸ā¸§āšˆā¸˛ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸āš‡ā¸šā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš„ā¸”āš‰ā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛āšā¸•āšˆāšƒā¸Šāš‰āš€ā¸§ā¸Ĩ⏞ encode ā¸™ā¸˛ā¸™ā¸ā¸§āšˆā¸˛ āš„ā¸Ÿā¸ĨāšŒāšƒā¸Ģā¸āšˆā¸ā¸§āšˆā¸˛ āšā¸Ĩ⏰ā¸Ĩā¸”ā¸„ā¸§ā¸˛ā¸Ąā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡ā¸‚ā¸­ā¸‡āšā¸­ā¸›", "image_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "image_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žāšā¸Ĩā¸°ā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸Ąā¸Šā¸ąā¸”ā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸‚ā¸ļāš‰ā¸™", - "image_thumbnail_description": "ā¸Ŗā¸šā¸›ā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏁⏞⏪ā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ā¸”ā¸˛ā¸•āš‰ā¸˛ āšƒā¸Šāš‰āš€ā¸Ąā¸ˇāšˆā¸­ā¸”ā¸šā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸāšƒā¸™ā¸ā¸Ĩā¸¸āšˆā¸Ą āš€ā¸Šāšˆā¸™ āšƒā¸™āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸Ģā¸Ĩā¸ąā¸", + "image_thumbnail_description": "ā¸Ŗā¸šā¸›ā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏁⏞⏪ā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ā¸”āš‰ā¸˛ā¸•āš‰ā¸˛ āšƒā¸Šāš‰āš€ā¸Ąā¸ˇāšˆā¸­ā¸”ā¸šā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸāšƒā¸™ā¸ā¸Ĩā¸¸āšˆā¸Ą āš€ā¸Šāšˆā¸™ āšƒā¸™āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸Ģā¸Ĩā¸ąā¸", "image_thumbnail_quality_description": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸•ā¸ąāš‰ā¸‡āšā¸•āšˆ 1-100 ā¸ĸā¸´āšˆā¸‡ā¸Ēā¸šā¸‡ā¸ĸā¸´āšˆā¸‡ā¸”ā¸ĩ āšā¸•āšˆā¸ˆā¸°ā¸—ā¸ŗāšƒā¸Ģāš‰āš„ā¸Ÿā¸ĨāšŒā¸Ąā¸ĩā¸‚ā¸™ā¸˛ā¸”āšƒā¸Ģā¸āšˆā¸‚ā¸ļāš‰ā¸™āšā¸Ĩā¸°ā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰āšā¸­ā¸›ā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡ā¸Šāš‰ā¸˛ā¸Ĩ⏇", "image_thumbnail_title": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ Thumbnail", - "import_config_from_json_description": "ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸°ā¸šā¸šāš‚ā¸”ā¸ĸā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸Ÿā¸ĨāšŒā¸„ā¸­ā¸™ā¸Ÿā¸´ā¸ JSON", - "integrity_checks_checksum_files": "āš„ā¸Ÿā¸ĨāšŒ Checksum", - "integrity_checks_checksum_files_description": "⏁⏺ā¸Ģā¸™ā¸”ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ Checksum", - "integrity_checks_checksum_files_enable_description": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ Checksum", - "integrity_checks_checksum_files_percentage_limit": "⏂ā¸ĩā¸”ā¸ˆā¸ŗā¸ā¸ąā¸”ā¸Ŗāš‰ā¸­ā¸ĸā¸Ĩ⏰", - "integrity_checks_checksum_files_percentage_limit_description": "⏁⏺ā¸Ģā¸™ā¸”ā¸„āšˆā¸˛ā¸Ŗāš‰ā¸­ā¸ĸā¸Ĩ⏰ā¸Ēā¸šā¸‡ā¸Ē⏏⏔⏪⏰ā¸Ģā¸§āšˆā¸˛ā¸‡ 0.01 ⏖ā¸ļ⏇ 1 ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ Checksum āšƒā¸™āšā¸•āšˆā¸Ĩ⏰⏪⏭⏚", - "integrity_checks_checksum_files_time_limit": "ā¸ˆā¸ŗā¸ā¸ąā¸”āš€ā¸§ā¸Ĩ⏞", - "integrity_checks_checksum_files_time_limit_description": "⏁⏺ā¸Ģ⏙⏔⏪⏰ā¸ĸā¸°āš€ā¸§ā¸Ĩ⏞ā¸Ēā¸šā¸‡ā¸Ē⏏⏔⏗ā¸ĩāšˆā¸„ā¸§ā¸Ŗāšƒā¸Šāš‰ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ Checksum āšƒā¸™āšā¸•āšˆā¸Ĩā¸°ā¸Ŗā¸­ā¸šā¸ā¸˛ā¸Ŗā¸—ā¸ŗā¸‡ā¸˛ā¸™ (ms)", - "integrity_checks_missing_files": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", - "integrity_checks_missing_files_description": "⏁⏺ā¸Ģā¸™ā¸”ā¸„ā¸§ā¸˛ā¸Ąā¸–ā¸ĩāšˆāšā¸Ĩā¸°āš€ā¸›ā¸´ā¸”ā¸Ģā¸Ŗā¸ˇā¸­ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", - "integrity_checks_missing_files_enable_description": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", - "integrity_checks_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸„ā¸§ā¸˛ā¸Ąā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡ā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒ", - "integrity_checks_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ŗā¸­ā¸šā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸„ā¸§ā¸˛ā¸Ąā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡ā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒ", - "integrity_checks_untracked_files": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ą", - "integrity_checks_untracked_files_description": "⏁⏺ā¸Ģā¸™ā¸”ā¸„ā¸§ā¸˛ā¸Ąā¸–ā¸ĩāšˆāšā¸Ĩā¸°āš€ā¸›ā¸´ā¸”ā¸Ģā¸Ŗā¸ˇā¸­ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ą", - "integrity_checks_untracked_files_enable_description": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ą", - "job_concurrency": "{job} ⏗ā¸ĩāšˆā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸™", + "job_concurrency": "{job} ā¸‡ā¸˛ā¸™ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸™", "job_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸‡ā¸˛ā¸™āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸ", "job_not_concurrency_safe": "⏇⏞⏙⏙ā¸ĩāš‰ā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸™āšā¸šā¸šā¸›ā¸Ĩā¸­ā¸”ā¸ ā¸ąā¸ĸāš„ā¸Ąāšˆāš„ā¸”āš‰", "job_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸‡ā¸˛ā¸™", @@ -198,25 +180,9 @@ "machine_learning_smart_search_enabled": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ąā¸ˆā¸‰ā¸Ŗā¸´ā¸ĸ⏰", "machine_learning_smart_search_enabled_description": "ā¸Ģā¸˛ā¸ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ ā¸ ā¸˛ā¸žā¸ˆā¸°āš„ā¸Ąāšˆā¸–ā¸šā¸āšƒā¸Šāš‰ā¸Ēāšā¸˛ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ąā¸ˆā¸‰ā¸Ŗā¸´ā¸ĸ⏰", "machine_learning_url_description": "URL ā¸‚ā¸­ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ machine learning ⏁⏪⏓ā¸ĩā¸Ąā¸ĩ URL ā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛ā¸Ģ⏙ā¸ļāšˆā¸‡ URL ā¸ˆā¸°ā¸—ā¸ŗā¸ā¸˛ā¸Ŗā¸—ā¸”ā¸Ĩ⏭⏇ā¸Ēāšˆā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ŗā¸ĩā¸ĸā¸‡āš„ā¸›ā¸—ā¸ĩā¸Ĩā¸°ā¸­ā¸ąā¸™ā¸•ā¸˛ā¸Ąā¸Ĩā¸ŗā¸”ā¸ąā¸šā¸ˆā¸™ā¸ā¸§āšˆā¸˛ā¸ˆā¸°ā¸žā¸š URL ⏗ā¸ĩāšˆā¸•ā¸­ā¸šā¸Ē⏙⏭⏇ āšā¸Ĩā¸°ā¸ˆā¸°āš€ā¸Ĩ⏴⏁ā¸Ēāšˆā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸Šā¸ąāšˆā¸§ā¸„ā¸Ŗā¸˛ā¸§āšƒā¸™ā¸Ēāšˆā¸§ā¸™ā¸‚ā¸­ā¸‡ URL ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸•ā¸­ā¸šā¸Ē⏙⏭⏇", - "maintenance_backup_management": "ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_delete_backup": "ā¸Ĩ⏚⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_delete_backup_description": "āš„ā¸Ÿā¸ĨāšŒā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šāšā¸Ĩā¸°āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šāš„ā¸”āš‰", "maintenance_delete_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", - "maintenance_integrity_check": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚", - "maintenance_integrity_check_all": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", - "maintenance_integrity_checksum_mismatch": "āš€ā¸Šāš‡ā¸„ā¸‹ā¸ąā¸Ąāš„ā¸Ąāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™", - "maintenance_integrity_checksum_mismatch_description": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸„āšˆā¸˛āš€ā¸Šāš‡ā¸„ā¸‹ā¸ąā¸Ąāšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš„ā¸Ąāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸šā¸„āšˆā¸˛ā¸—ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸š Immich ā¸šā¸ąā¸™ā¸—ā¸ļā¸āš„ā¸§āš‰āšƒā¸™ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", - "maintenance_integrity_checksum_mismatch_job": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ģā¸˛āš€ā¸Šāš‡ā¸„ā¸‹ā¸ąā¸Ąā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™", - "maintenance_integrity_checksum_mismatch_refresh_job": "āš‚ā¸Ģā¸Ĩ⏔⏪⏞ā¸ĸā¸‡ā¸˛ā¸™āš€ā¸Šāš‡ā¸„ā¸‹ā¸ąā¸Ąā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™āšƒā¸Ģā¸Ąāšˆ", - "maintenance_integrity_missing_file": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ēā¸šā¸ā¸Ģ⏞ā¸ĸ", - "maintenance_integrity_missing_file_description": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏂⏭⏇ Immich āšā¸•āšˆāš„ā¸Ąāšˆā¸Ąā¸ĩāš„ā¸Ÿā¸ĨāšŒā¸­ā¸ĸā¸šāšˆā¸šā¸™ā¸Ŗā¸°ā¸šā¸š", - "maintenance_integrity_missing_file_job": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ā¸Ģā¸˛āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ģ⏞ā¸ĸāš„ā¸›", - "maintenance_integrity_missing_file_refresh_job": "āš‚ā¸Ģā¸Ĩ⏔⏪⏞ā¸ĸā¸‡ā¸˛ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ģ⏞ā¸ĸāš„ā¸›āšƒā¸Ģā¸Ąāšˆ", - "maintenance_integrity_report": "⏪⏞ā¸ĸā¸‡ā¸˛ā¸™ā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒā¸‚ā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", - "maintenance_integrity_untracked_file": "āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ą", - "maintenance_integrity_untracked_file_description": "āš„ā¸Ÿā¸ĨāšŒāšƒā¸™āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸‚ā¸­ā¸‡ Immich ⏗ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸šāš„ā¸Ąāšˆā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸šā¸ąā¸™ā¸—ā¸ļā¸āš„ā¸§āš‰āš€ā¸Ĩā¸ĸ", - "maintenance_integrity_untracked_file_job": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ģā¸˛āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ą", - "maintenance_integrity_untracked_file_refresh_job": "āš‚ā¸Ģā¸Ĩ⏔⏪⏞ā¸ĸā¸‡ā¸˛ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰ā¸•ā¸´ā¸”ā¸•ā¸˛ā¸Ąāšƒā¸Ģā¸Ąāšˆ", "maintenance_restore_backup": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_restore_backup_description": "Immich ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩāš‰ā¸˛ā¸‡ āšā¸Ĩā¸°ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏈⏞⏁⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁ ⏪⏰⏚⏚⏈⏰ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸āšˆā¸­ā¸™ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­", "maintenance_restore_backup_different_version": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏙ā¸ĩāš‰ā¸–ā¸šā¸ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸”āš‰ā¸§ā¸ĸ Immich āš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™ā¸•āšˆā¸˛ā¸‡ā¸ā¸ąā¸™!", @@ -230,7 +196,6 @@ "maintenance_upload_backup": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_upload_backup_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸§āšˆā¸˛āš€ā¸›āš‡ā¸™āš„ā¸Ÿā¸ĨāšŒ .sql/.sql.gz", "manage_concurrency": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸™", - "manage_concurrency_description": "āš„ā¸›ā¸ĸā¸ąā¸‡ā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸‡ā¸˛ā¸™āš€ā¸žā¸ˇāšˆā¸­ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸‚ā¸™ā¸˛ā¸™ā¸ā¸ąā¸™ā¸‚ā¸­ā¸‡ā¸‡ā¸˛ā¸™ā¸Ŗā¸°ā¸šā¸š", "manage_log_settings": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ˆā¸”ā¸šā¸ąā¸™ā¸—ā¸ļ⏁", "map_dark_style": "āšā¸šā¸šā¸Ąā¸ˇā¸”", "map_enable_description": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšā¸œā¸™ā¸—ā¸ĩāšˆ", @@ -242,20 +207,19 @@ "map_reverse_geocoding": "ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸Šā¸ˇāšˆā¸­ā¸—ā¸˛ā¸‡ā¸ ā¸šā¸Ąā¸´ā¸¨ā¸˛ā¸Ēā¸•ā¸ŖāšŒ", "map_reverse_geocoding_enable_description": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸Šā¸ˇāšˆā¸­ā¸—ā¸˛ā¸‡ā¸ ā¸šā¸Ąā¸´ā¸¨ā¸˛ā¸Ēā¸•ā¸ŖāšŒ", "map_reverse_geocoding_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸Šā¸ˇāšˆā¸­ā¸—ā¸˛ā¸‡ā¸ ā¸šā¸Ąā¸´ā¸¨ā¸˛ā¸Ēā¸•ā¸ŖāšŒ", - "map_settings": "āšā¸œā¸™ā¸—ā¸ĩāšˆ", + "map_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸œā¸™ā¸—ā¸ĩāšˆāšā¸Ĩ⏰ GPS", "map_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸œā¸™ā¸—ā¸ĩāšˆ", "map_style_description": "URL āš„ā¸›ā¸ĸā¸ąā¸‡ā¸˜ā¸ĩā¸Ąāšā¸œā¸™ā¸—ā¸ĩāšˆ style.json", "memory_cleanup_job": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "memory_generate_job": "⏁⏞⏪ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "metadata_extraction_job": "⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ metadata", - "metadata_extraction_job_description": "⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ metadata ⏈⏞⏁ā¸Ēā¸ˇāšˆā¸­āšā¸•āšˆā¸Ĩā¸°ā¸Šā¸´āš‰ā¸™ āš€ā¸Šāšˆā¸™ GPS, āšƒā¸šā¸Ģā¸™āš‰ā¸˛ āšā¸Ĩā¸°ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", + "metadata_extraction_job_description": "⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ metadata ⏈⏞⏁ā¸Ēā¸ˇāšˆā¸­ āš€ā¸Šāšˆā¸™ GPS āšā¸Ĩā¸°ā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸Ąā¸Šā¸ąā¸”", "metadata_faces_import_setting": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸šā¸Ģā¸™āš‰ā¸˛", "metadata_faces_import_setting_description": "ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸˛ā¸ EXIF ā¸‚ā¸­ā¸‡āš„ā¸Ÿā¸ĨāšŒā¸ ā¸˛ā¸žāšā¸Ĩā¸°āš„ā¸Ÿā¸ĨāšŒā¸›ā¸Ŗā¸°ā¸ā¸­ā¸š", "metadata_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ Metadata", "metadata_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ Metadata", "migration_job": "ā¸ā¸˛ā¸Ŗāš‚ā¸ĸ⏁ā¸ĸāš‰ā¸˛ā¸ĸ", "migration_job_description": "ā¸ĸāš‰ā¸˛ā¸ĸā¸ ā¸˛ā¸žā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ēā¸ˇāšˆā¸­āšā¸Ĩā¸°āšƒā¸šā¸Ģā¸™āš‰ā¸˛āš„ā¸›ā¸ĸā¸ąā¸‡āš‚ā¸„ā¸Ŗā¸‡ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", - "nightly_tasks_cluster_faces_setting_description": "ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸ˆā¸”ā¸ˆā¸ŗāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸•ā¸Ŗā¸§ā¸ˆā¸žā¸šāšƒā¸Ģā¸Ąāšˆ", "nightly_tasks_cluster_new_faces_setting": "⏄ā¸Ĩā¸ąā¸Ēāš€ā¸•ā¸­ā¸ŖāšŒāšƒā¸šā¸Ģā¸™āš‰ā¸˛āšƒā¸Ģā¸Ąāšˆ", "nightly_tasks_database_cleanup_setting": "ā¸‡ā¸˛ā¸™āš€ā¸āš‡ā¸šā¸ā¸§ā¸˛ā¸”ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "nightly_tasks_database_cleanup_setting_description": "ā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆāš€ā¸āšˆā¸˛āšā¸Ĩ⏰ā¸Ģā¸Ąā¸”ā¸­ā¸˛ā¸ĸā¸¸ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", @@ -263,8 +227,6 @@ "nightly_tasks_generate_memories_setting_description": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗāšƒā¸Ģā¸Ąāšˆā¸ˆā¸˛ā¸ā¸Ēā¸ˇāšˆā¸­", "nightly_tasks_missing_thumbnails_setting": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸—ā¸ĩāšˆā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", "nightly_tasks_missing_thumbnails_setting_description": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ąā¸ĩā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸´ā¸§āš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­", - "nightly_tasks_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸‡ā¸˛ā¸™ā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸„ā¸ˇā¸™", - "nightly_tasks_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸‡ā¸˛ā¸™ā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸„ā¸ˇā¸™", "nightly_tasks_start_time_setting": "āš€ā¸§ā¸Ĩā¸˛āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™", "nightly_tasks_start_time_setting_description": "āš€ā¸§ā¸Ĩ⏞⏗ā¸ĩāšˆāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸ˆā¸°āš€ā¸Ŗā¸´āšˆā¸Ąā¸‡ā¸˛ā¸™ā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸„ā¸ˇā¸™", "nightly_tasks_sync_quota_usage_setting": "ā¸‹ā¸´ā¸‡ā¸āšŒāš‚ā¸„ā¸§ā¸•āš‰ā¸˛ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", @@ -291,8 +253,6 @@ "notification_enable_email_notifications": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ā¸œāšˆā¸˛ā¸™ā¸­ā¸ĩāš€ā¸Ąā¸Ĩ", "notification_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™", "notification_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ ā¸Ŗā¸§ā¸Ąā¸–ā¸ļ⏇⏭ā¸ĩāš€ā¸Ąā¸Ĩ", - "oauth_allow_insecure_requests": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•ā¸„ā¸ŗā¸‚ā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸›ā¸Ĩā¸­ā¸”ā¸ ā¸ąā¸ĸ", - "oauth_allow_insecure_requests_description": "ā¸„ā¸ŗāš€ā¸•ā¸ˇā¸­ā¸™: ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ TLS ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸„ā¸ŗā¸‚ā¸­ OAuth āšā¸Ĩā¸°ā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰ā¸„ā¸¸ā¸“āš€ā¸Ēā¸ĩāšˆā¸ĸā¸‡ā¸•āšˆā¸­ā¸ā¸˛ā¸Ŗāš‚ā¸ˆā¸Ąā¸•ā¸ĩāšā¸šā¸š MITM āš„ā¸”āš‰", "oauth_auto_launch": "āš€ā¸›ā¸´ā¸”ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", "oauth_auto_launch_description": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™ā¸ā¸˛ā¸Ŗā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ OAuth āš‚ā¸”ā¸ĸā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´āš€ā¸Ąā¸ˇāšˆā¸­ā¸–ā¸ļ⏇ā¸Ģā¸™āš‰ā¸˛ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™", "oauth_auto_register": "ā¸Ĩā¸‡ā¸—ā¸°āš€ā¸šā¸ĩā¸ĸā¸™ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", @@ -300,13 +260,9 @@ "oauth_button_text": "ā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ąā¸›ā¸¸āšˆā¸Ąā¸ā¸”", "oauth_client_secret_description": "ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸¸ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāš„ā¸„ā¸Ĩāš€ā¸­āš‡ā¸™ā¸•āšŒā¸Ĩā¸ąā¸š ā¸Ģ⏪⏎⏭ā¸Ģā¸˛ā¸āš„ā¸„ā¸Ĩāš€ā¸­āš‡ā¸™ā¸•āšŒā¸Ēā¸˛ā¸˜ā¸˛ā¸Ŗā¸“ā¸°āš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸š PKCE (Proof Key for Code Exchange)", "oauth_enable_description": "ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸œāšˆā¸˛ā¸™ OAuth", - "oauth_end_session_url_description": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸›ā¸ĸā¸ąā¸‡ URI ⏙ā¸ĩāš‰ āš€ā¸Ąā¸ˇāšˆā¸­ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸°ā¸šā¸š", "oauth_mobile_redirect_uri": "URI āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡ā¸šā¸™āš‚ā¸—ā¸Ŗā¸¨ā¸ąā¸žā¸—āšŒ", "oauth_mobile_redirect_uri_override": "āšā¸—ā¸™ā¸—ā¸ĩāšˆ URI āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡ā¸šā¸™āš‚ā¸—ā¸Ŗā¸¨ā¸ąā¸žā¸—āšŒ", "oauth_mobile_redirect_uri_override_description": "āš€ā¸›ā¸´ā¸”āš€ā¸Ąā¸ˇāšˆā¸­ā¸œā¸šāš‰āšƒā¸Ģāš‰ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗ OAuth āš„ā¸Ąāšˆā¸­ā¸™ā¸¸ā¸ā¸˛ā¸• URI āš€ā¸Šāšˆā¸™ \"{callback}\"", - "oauth_prompt_description": "ā¸žā¸˛ā¸Ŗā¸˛ā¸Ąā¸´āš€ā¸•ā¸­ā¸ŖāšŒ prompt (āš€ā¸Šāšˆā¸™ select_account, login, consent)", - "oauth_role_claim": "ā¸Ēā¸´ā¸—ā¸˜āšŒā¸šā¸—ā¸šā¸˛ā¸—", - "oauth_role_claim_description": "ā¸Ąā¸­ā¸šā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩā¸Ŗā¸°ā¸šā¸šāš‚ā¸”ā¸ĸā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´ā¸Ģā¸˛ā¸ā¸žā¸š Claim ⏙ā¸ĩāš‰ āš‚ā¸”ā¸ĸā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡ Claim ā¸­ā¸˛ā¸ˆāš€ā¸›āš‡ā¸™ 'user' ā¸Ģ⏪⏎⏭ 'admin'", "oauth_settings": "OAuth", "oauth_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸œāšˆā¸˛ā¸™ OAuth", "oauth_settings_more_details": "ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą āšƒā¸Ģāš‰ā¸­āš‰ā¸˛ā¸‡ā¸–ā¸ļā¸‡āš€ā¸­ā¸ā¸Ē⏞⏪", @@ -318,7 +274,6 @@ "oauth_storage_quota_default_description": "āš‚ā¸„ā¸§ā¸•āš‰ā¸˛āšƒā¸™ā¸Ģā¸™āšˆā¸§ā¸ĸ GiB ⏗ā¸ĩāšˆā¸ˆā¸°āšƒā¸Šāš‰āš€ā¸Ąā¸ˇāšˆā¸­āš„ā¸Ąāšˆā¸Ąā¸ĩā¸ā¸˛ā¸Ŗā¸­āš‰ā¸˛ā¸‡ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒ", "oauth_timeout": "ā¸Ģā¸Ąā¸”āš€ā¸§ā¸Ĩā¸˛ā¸ā¸˛ā¸Ŗā¸Ŗāš‰ā¸­ā¸‡ā¸‚ā¸­", "oauth_timeout_description": "⏪⏰ā¸ĸā¸°āš€ā¸§ā¸Ĩ⏞ā¸Ģā¸Ąā¸”āš€ā¸§ā¸Ĩ⏞ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸Ŗāš‰ā¸­ā¸‡ā¸‚ā¸­ (ā¸Ģā¸™āšˆā¸§ā¸ĸāš€ā¸›āš‡ā¸™ā¸Ąā¸´ā¸Ĩā¸Ĩ⏴⏧⏴⏙⏞⏗ā¸ĩ)", - "ocr_job_description": "āšƒā¸Šāš‰ machine learning āš€ā¸žā¸ˇāšˆā¸­ā¸ˆā¸”ā¸ˆā¸ŗā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ąāšƒā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "password_enable_description": "ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸ā¸ąā¸šā¸­ā¸ĩāš€ā¸Ąā¸Ĩāšā¸Ĩ⏰⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "password_settings": "ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸œāšˆā¸˛ā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "password_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸œāšˆā¸˛ā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", @@ -331,8 +286,6 @@ "refreshing_all_libraries": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "registration": "ā¸Ĩā¸‡ā¸—ā¸°āš€ā¸šā¸ĩā¸ĸā¸™ā¸œā¸šāš‰ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗ", "registration_description": "āš€ā¸™ā¸ˇāšˆā¸­ā¸‡ā¸ˆā¸˛ā¸ā¸„ā¸¸ā¸“āš€ā¸›āš‡ā¸™ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšā¸Ŗā¸ā¸‚ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸š ā¸„ā¸¸ā¸“ā¸ˆā¸°ā¸–ā¸šā¸āšā¸•āšˆā¸‡ā¸•ā¸ąāš‰ā¸‡āš€ā¸›āš‡ā¸™ā¸œā¸šāš‰ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗāšā¸Ĩā¸°ā¸Ŗā¸ąā¸šā¸œā¸´ā¸”ā¸Šā¸­ā¸šā¸‡ā¸˛ā¸™ā¸šā¸Ŗā¸´ā¸Ģ⏞⏪ ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ąā¸ˆā¸°ā¸–ā¸šā¸ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš‚ā¸”ā¸ĸ⏄⏏⏓", - "release_channel_release_candidate": "āš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™ Release Candidate", - "release_channel_stable": "āš€ā¸Ē⏖ā¸ĩā¸ĸ⏪", "remove_failed_jobs": "ā¸Ĩā¸šā¸‡ā¸˛ā¸™ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "require_password_change_on_login": "ā¸šā¸ąā¸‡ā¸„ā¸ąā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšƒā¸Ģāš‰āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šā¸„ā¸Ŗā¸ąāš‰ā¸‡āšā¸Ŗā¸", "reset_settings_to_default": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āš€ā¸›āš‡ā¸™ā¸„āšˆā¸˛āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™", @@ -427,14 +380,6 @@ "transcoding_preferred_hardware_device_description": "āšƒā¸Šāš‰āš„ā¸”āš‰ā¸ā¸ąā¸š VAAPI āšā¸Ĩ⏰ QSV āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āš‚ā¸Ģ⏙⏔ dri ⏗ā¸ĩāšˆāšƒā¸Šāš‰ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”ā¸Žā¸˛ā¸ŖāšŒā¸”āšā¸§ā¸ŖāšŒ", "transcoding_preset_preset": "ā¸žā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸• (-preset)", "transcoding_preset_preset_description": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§āšƒā¸™ā¸ā¸˛ā¸Ŗā¸šā¸ĩā¸šā¸­ā¸ąā¸” ā¸žā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸—ā¸ĩāšˆā¸Šāš‰ā¸˛ā¸ā¸§āšˆā¸˛ā¸ˆā¸°ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸‚ā¸™ā¸˛ā¸”āš€ā¸Ĩāš‡ā¸ā¸Ĩā¸‡āšā¸Ĩā¸°āš€ā¸žā¸´āšˆā¸Ąā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žāš€ā¸Ąā¸ˇāšˆā¸­ā¸ā¸ŗā¸Ģā¸™ā¸”āš€ā¸›āš‰ā¸˛ā¸Ģā¸Ąā¸˛ā¸ĸ⏗ā¸ĩāšˆā¸­ā¸ąā¸•ā¸Ŗā¸˛ā¸šā¸´ā¸•āš€ā¸Ŗā¸•ā¸—ā¸ĩāšˆā¸ā¸ŗā¸Ģ⏙⏔ VP9 ā¸ˆā¸°āš„ā¸Ąāšˆā¸Ēā¸™āšƒā¸ˆā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ā¸—ā¸ĩāšˆā¸Ēā¸šā¸‡ā¸ā¸§āšˆā¸˛ 'faster'", - "transcoding_realtime": "⏗⏪⏞⏙ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒ [EXPERIMENTAL]", - "transcoding_realtime_description": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒā¸‚ā¸“ā¸°ā¸—ā¸ĩāšˆā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸ā¸ŗā¸Ĩā¸ąā¸‡ā¸Ē⏕⏪ā¸ĩā¸Ą ā¸Šāšˆā¸§ā¸ĸāšƒā¸Ģāš‰āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­āš„ā¸”āš‰ āšā¸•āšˆā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰āš€ā¸ā¸´ā¸”ā¸„ā¸§ā¸˛ā¸Ąā¸Ģā¸™āšˆā¸§ā¸‡āšƒā¸™ā¸ā¸˛ā¸Ŗāš€ā¸Ĩāšˆā¸™ā¸Ēā¸šā¸‡ā¸‚ā¸ļāš‰ā¸™āšā¸Ĩ⏰⏁⏪⏰⏕⏏⏁ ⏂ā¸ļāš‰ā¸™ā¸­ā¸ĸā¸šāšˆā¸ā¸ąā¸šā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸‚ā¸­ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", - "transcoding_realtime_enabled": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒ", - "transcoding_realtime_enabled_description": "ā¸Ģ⏞⏁⏛⏴⏔ āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸ˆā¸°āš„ā¸Ąāšˆā¸ĸā¸­ā¸Ąāšƒā¸Ģāš‰āš€ā¸›ā¸´ā¸”āš€ā¸‹ā¸Ēā¸Šā¸ąā¸™ā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒāšƒā¸Ģā¸Ąāšˆ", - "transcoding_realtime_resolutions": "ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", - "transcoding_realtime_resolutions_description": "ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔⏗ā¸ĩāšˆā¸Ąā¸ĩāšƒā¸Ģāš‰āš€ā¸Ĩ⏎⏭⏁ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒ ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ēā¸šā¸‡ā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰āš€ā¸ā¸´ā¸”ā¸›ā¸ąā¸ā¸Ģā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗāš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ ā¸Ģā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āš„ā¸”āš‰āš€ā¸Ŗāš‡ā¸§ā¸žā¸­", - "transcoding_realtime_video_codecs": "āš‚ā¸„āš€ā¸”ā¸ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­", - "transcoding_realtime_video_codecs_description": "āš‚ā¸„āš€ā¸”ā¸ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩāšƒā¸Ģāš‰āš€ā¸Ĩ⏎⏭⏁ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”āšā¸šā¸šāš€ā¸Ŗā¸ĩā¸ĸā¸Ĩāš„ā¸—ā¸ĄāšŒ āš‚ā¸”ā¸ĸāš„ā¸„ā¸Ĩāš€ā¸­ā¸™ā¸•āšŒā¸ˆā¸°āš€ā¸Ĩā¸ˇā¸­ā¸ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁⏗ā¸ĩāšˆā¸”ā¸ĩ⏗ā¸ĩāšˆā¸Ē⏏⏔⏗ā¸ĩāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šāšƒā¸™ā¸Ŗā¸°ā¸Ģā¸§āšˆā¸˛ā¸‡ā¸ā¸˛ā¸Ŗāš€ā¸Ĩāšˆā¸™ ā¸—ā¸ąāš‰ā¸‡ā¸™ā¸ĩāš‰ AV1 ā¸Ąā¸ĩ⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛ HEVC ⏋ā¸ļāšˆā¸‡ā¸Ąā¸ĩ⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛ H.264 āš€ā¸Ąā¸ˇāšˆā¸­āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗāš€ā¸Ŗāšˆā¸‡ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ā¸”āš‰ā¸§ā¸ĸā¸Žā¸˛ā¸ŖāšŒā¸”āšā¸§ā¸ŖāšŒ āšƒā¸Ģāš‰āš€ā¸Ĩā¸ˇā¸­ā¸āš€ā¸‰ā¸žā¸˛ā¸°āš‚ā¸„āš€ā¸”ā¸ā¸—ā¸ĩāšˆā¸•ā¸ąā¸§āš€ā¸Ŗāšˆā¸‡ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸Ŗā¸Ģā¸ąā¸Ēāš„ā¸”āš‰āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ āšā¸•āšˆā¸Ģā¸˛ā¸āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗā¸—ā¸Ŗā¸˛ā¸™ā¸ĒāšŒāš‚ā¸„āš‰ā¸”ā¸”āš‰ā¸§ā¸ĸā¸‹ā¸­ā¸Ÿā¸•āšŒāšā¸§ā¸ŖāšŒ H.264 ā¸ˆā¸°ā¸—ā¸ŗā¸‡ā¸˛ā¸™āš„ā¸”āš‰āš€ā¸Ŗāš‡ā¸§ā¸ā¸§āšˆā¸˛ AV1 ⏋ā¸ļāšˆā¸‡āš€ā¸Ŗāš‡ā¸§ā¸ā¸§āšˆā¸˛ HEVC", "transcoding_reference_frames": "frames ā¸­āš‰ā¸˛ā¸‡ā¸­ā¸´ā¸‡", "transcoding_reference_frames_description": "ā¸ˆā¸ŗā¸™ā¸§ā¸™āš€ā¸Ÿā¸Ŗā¸Ąā¸—ā¸ĩāšˆā¸ˆā¸°ā¸­āš‰ā¸˛ā¸‡ā¸­ā¸´ā¸‡āš€ā¸Ąā¸ˇāšˆā¸­ā¸šā¸ĩā¸šā¸­ā¸ąā¸”āš€ā¸Ÿā¸Ŗā¸Ąā¸—ā¸ĩāšˆā¸ā¸ŗā¸Ģ⏙⏔ ā¸„āšˆā¸˛ā¸—ā¸ĩāšˆā¸Ēā¸šā¸‡ā¸‚ā¸ļāš‰ā¸™ā¸ˆā¸°ā¸Šāšˆā¸§ā¸ĸāš€ā¸žā¸´āšˆā¸Ąā¸›ā¸Ŗā¸°ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žāšƒā¸™ā¸ā¸˛ā¸Ŗā¸šā¸ĩā¸šā¸­ā¸ąā¸” āšā¸•āšˆā¸ˆā¸°ā¸—ā¸ŗāšƒā¸Ģāš‰ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸Ģā¸ąā¸Ēā¸Šāš‰ā¸˛ā¸Ĩ⏇ ā¸„āšˆā¸˛ 0 ā¸ˆā¸°ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰āš‚ā¸”ā¸ĸā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", "transcoding_required_description": "āš€ā¸‰ā¸žā¸˛ā¸°ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸šā¸—ā¸ĩāšˆā¸ĸā¸­ā¸Ąā¸Ŗā¸ąā¸šāš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", @@ -446,13 +391,13 @@ "transcoding_temporal_aq_description": "āš€ā¸‰ā¸žā¸˛ā¸° NVENC āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ Temporal Adaptive Quantization ā¸ˆā¸°āš€ā¸žā¸´āšˆā¸Ąā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸‰ā¸˛ā¸ā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ēā¸šā¸‡āšā¸Ĩā¸°ā¸ā¸˛ā¸Ŗāš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģā¸§ā¸•āšˆā¸ŗ ā¸­ā¸˛ā¸ˆāš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸—ā¸ĩāšˆāš€ā¸āšˆā¸˛ā¸ā¸§āšˆā¸˛", "transcoding_threads": "āš€ā¸˜ā¸Ŗā¸”", "transcoding_threads_description": "ā¸„āšˆā¸˛ā¸ĸā¸´āšˆā¸‡āš€ā¸ĸā¸­ā¸°ā¸ˆā¸°āšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒāš€ā¸Ŗāš‡ā¸§ā¸ā¸§āšˆā¸˛ āšā¸•āšˆā¸ˆā¸°āš€ā¸Ģā¸Ĩā¸ˇā¸­ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆāšƒā¸Ģāš‰āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩā¸‡ā¸˛ā¸™ā¸­ā¸ˇāšˆā¸™ā¸™āš‰ā¸­ā¸ĸā¸Ĩā¸‡āš€ā¸Ąā¸ˇāšˆā¸­ā¸—āšā¸˛ā¸‡ā¸˛ā¸™ā¸™ā¸ĩāš‰ ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰āš„ā¸Ąāšˆā¸„ā¸§ā¸Ŗā¸Ąā¸˛ā¸ā¸ā¸§āšˆā¸˛ā¸ˆāšā¸˛ā¸™ā¸§ā¸™ CPU core ā¸ˆā¸°ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩāš€ā¸•āš‡ā¸Ąā¸—ā¸ĩāšˆāš€ā¸Ąā¸ˇāšˆā¸­ā¸•ā¸ąāš‰ā¸‡āš€ā¸›āš‡ā¸™ 0", - "transcoding_tone_mapping": "⏁⏞⏪⏗⏺ Tone-mapping", + "transcoding_tone_mapping": "⏁⏞⏪⏉⏞ā¸ĸāš‚ā¸—ā¸™ā¸Ēā¸ĩ", "transcoding_tone_mapping_description": "ā¸žā¸ĸ⏞ā¸ĸā¸˛ā¸Ąā¸Ŗā¸ąā¸ā¸Šā¸˛ā¸Ŗā¸šā¸›āšā¸šā¸šā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ HDR āš€ā¸Ąā¸ˇāšˆā¸­āšā¸›ā¸Ĩā¸‡āš€ā¸›āš‡ā¸™ SDR ā¸­ā¸ąā¸Ĩ⏁⏭⏪⏴⏗ā¸ļā¸Ąāšā¸•āšˆā¸Ĩā¸°ā¸•ā¸ąā¸§ā¸ˆā¸°ā¸›ā¸Ŗā¸ąā¸šā¸Ēā¸ĩ,⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ āšā¸Ĩā¸°ā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸§āšˆā¸˛ā¸‡āšā¸•ā¸ā¸•āšˆā¸˛ā¸‡ā¸ā¸ąā¸™ Hable ā¸ˆā¸°ā¸Ŗā¸ąā¸ā¸Šā¸˛ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ Mobius ā¸ˆā¸°ā¸Ŗā¸ąā¸ā¸Šā¸˛ā¸Ēā¸ĩ āšā¸Ĩ⏰ Reinhard ā¸ˆā¸°ā¸Ŗā¸ąā¸ā¸Šā¸˛ā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸§āšˆā¸˛ā¸‡", "transcoding_transcode_policy": "ā¸ā¸Žā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒ", "transcoding_transcode_policy_description": "ā¸™āš‚ā¸ĸ⏚⏞ā¸ĸāš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸ā¸ąā¸šāš€ā¸§ā¸Ĩ⏞⏗ā¸ĩāšˆā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸ˆā¸°ā¸•āš‰ā¸­ā¸‡āš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩ⏇⏪ā¸Ģā¸ąā¸Ē ⏧⏴⏔ā¸ĩāš‚ā¸­ HDR āšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸Ŗā¸šā¸›āšā¸šā¸šā¸žā¸´ā¸āš€ā¸‹ā¸Ĩā¸­ā¸ˇāšˆā¸™ā¸™ā¸­ā¸ā¸ˆā¸˛ā¸ YUV 4:2:0 ā¸ˆā¸°āš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩ⏇⏪ā¸Ģā¸ąā¸Ēāš€ā¸Ēā¸Ąā¸­ (ā¸ĸā¸āš€ā¸§āš‰ā¸™āšƒā¸™ā¸ā¸Ŗā¸“ā¸ĩ⏗ā¸ĩāšˆā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩ⏇⏪ā¸Ģā¸ąā¸Ē)", - "transcoding_two_pass_encoding": "ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸Ģā¸ąā¸Ēāšā¸šā¸š Two-pass", + "transcoding_two_pass_encoding": "ā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸­ā¸‡ā¸Ŗā¸­ā¸š", "transcoding_two_pass_encoding_setting_description": "ā¸ā¸˛ā¸Ŗāšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸­ā¸‡ā¸Ŗā¸­ā¸šā¸ˆā¸°ā¸Šāšˆā¸§ā¸ĸāšƒā¸Ģāš‰āš„ā¸”āš‰ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸”ā¸ĩ⏂ā¸ļāš‰ā¸™ āš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ bitrate ā¸Ēā¸šā¸‡ā¸Ē⏏⏔ (ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāš„ā¸Ÿā¸ĨāšŒ H.264 āšā¸Ĩ⏰ HEVC) āš‚ā¸Ģā¸Ąā¸”ā¸™ā¸ĩāš‰ā¸ˆā¸°āšƒā¸Šāš‰ā¸Šāšˆā¸§ā¸‡ bitrate ⏗ā¸ĩāšˆā¸‚ā¸ļāš‰ā¸™ā¸­ā¸ĸā¸šāšˆā¸ā¸ąā¸šā¸„āšˆā¸˛ bitrate ā¸Ēā¸šā¸‡ā¸Ēā¸¸ā¸”āšā¸Ĩā¸°āš„ā¸Ąāšˆā¸Ēā¸™āšƒā¸ˆ CRF ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸š VP9 ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšƒā¸Šāš‰ā¸„āšˆā¸˛ CRF āš„ā¸”āš‰ā¸–āš‰ā¸˛ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ bitrate ā¸Ēā¸šā¸‡ā¸Ē⏏⏔", - "transcoding_video_codec": "āš‚ā¸„āš€ā¸”ā¸ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­", + "transcoding_video_codec": "āšā¸šā¸šāš„ā¸Ÿā¸ĨāšŒā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­", "transcoding_video_codec_description": "VP9 ā¸Ąā¸ĩ⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žā¸Ēā¸šā¸‡āšā¸Ĩā¸°āš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šāš€ā¸§āš‡ā¸šāš„ā¸”āš‰ā¸”ā¸ĩ āšā¸•āšˆāšƒā¸Šāš‰āš€ā¸§ā¸Ĩā¸˛āšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒā¸™ā¸˛ā¸™ā¸ā¸§āšˆā¸˛ HEVC ā¸Ąā¸ĩ⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žā¸„ā¸Ĩāš‰ā¸˛ā¸ĸā¸ā¸ąā¸™ āšā¸•āšˆāš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šāš€ā¸§āš‡ā¸šāš„ā¸”āš‰ā¸™āš‰ā¸­ā¸ĸā¸ā¸§āšˆā¸˛ H.264 āš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šā¸—ā¸¸ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ āšā¸Ĩā¸°āšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒāš€ā¸Ŗāš‡ā¸§ āšā¸•āšˆāš„ā¸”āš‰āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāšƒā¸Ģā¸āšˆā¸‚ā¸ļāš‰ā¸™ AV1 āš€ā¸›āš‡ā¸™āš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žā¸Ąā¸˛ā¸ā¸—ā¸ĩāšˆā¸Ē⏏⏔ āšā¸•āšˆāš„ā¸Ąāšˆāš€ā¸‚āš‰ā¸˛ā¸ā¸ąā¸™ā¸ā¸ąā¸šā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāš€ā¸āšˆā¸˛", "trash_enabled_description": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "trash_number_of_days": "ā¸ˆāšā¸˛ā¸™ā¸§ā¸™ā¸§ā¸ąā¸™", @@ -460,8 +405,6 @@ "trash_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "trash_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "unlink_all_oauth_accounts": "āš€ā¸Ĩ⏴⏁ā¸Ĩā¸´ā¸‡ā¸āšŒā¸šā¸ąā¸ā¸Šā¸ĩ OAuth ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", - "unlink_all_oauth_accounts_description": "⏭ā¸ĸāšˆā¸˛ā¸Ĩā¸ˇā¸Ąā¸›ā¸Ĩā¸”ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸šā¸ąā¸ā¸Šā¸ĩ OAuth ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸āšˆā¸­ā¸™ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸œā¸šāš‰āšƒā¸Ģāš‰ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗāšƒā¸Ģā¸Ąāšˆ", - "unlink_all_oauth_accounts_prompt": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸›ā¸Ĩā¸”ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸šā¸ąā¸ā¸Šā¸ĩ OAuth ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”? ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­ā¸ˆā¸°ā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸• OAuth ID ā¸‚ā¸­ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰āšā¸•āšˆā¸Ĩ⏰⏪⏞ā¸ĸ āšā¸Ĩā¸°āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šāš„ā¸”āš‰", "user_cleanup_job": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰", "user_delete_delay": "ā¸šā¸ąā¸ā¸Šā¸ĩāšā¸Ĩ⏰ā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ {user} ā¸ˆā¸°ā¸–ā¸šā¸ā¸•ā¸ąāš‰ā¸‡āš€ā¸§ā¸Ĩ⏞ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸–ā¸˛ā¸§ā¸Ŗāšƒā¸™ {delay, plural, one {# ā¸§ā¸ąā¸™} other {# ā¸§ā¸ąā¸™}}", "user_delete_delay_settings": "ā¸Ĩā¸šā¸ā¸˛ā¸Ŗā¸–āšˆā¸§ā¸‡āš€ā¸§ā¸Ĩ⏞", @@ -478,8 +421,6 @@ "user_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸œā¸šāš‰āšƒā¸Šāš‰", "user_successfully_removed": "ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ {email} āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒāšā¸Ĩāš‰ā¸§", "users_page_description": "ā¸Ģā¸™āš‰ā¸˛ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩ", - "version_check_channel": "ā¸Šāšˆā¸­ā¸‡ā¸—ā¸˛ā¸‡ā¸­ā¸­ā¸ā¸Ŗā¸¸āšˆā¸™āšƒā¸Ģā¸Ąāšˆ", - "version_check_channel_description": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Šāšˆā¸­ā¸‡ā¸—ā¸˛ā¸‡ā¸—ā¸ĩāšˆā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗāš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸›ā¸Ŗā¸°ā¸ā¸˛ā¸¨āš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąāšˆā¸™āšƒā¸Ģā¸Ąāšˆ", "version_check_enabled_description": "āš€ā¸Šāš‡ā¸„ GitHub āš€ā¸›āš‡ā¸™ā¸Ŗā¸°ā¸ĸ⏰ āš† āš€ā¸žā¸ˇāšˆā¸­ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸Ŗā¸¸āšˆā¸™āšƒā¸Ģā¸Ąāšˆ", "version_check_implications": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™āšƒā¸Ģā¸Ąāšˆā¸ˆā¸°ā¸•āš‰ā¸­ā¸‡ā¸•ā¸´ā¸”ā¸•āšˆā¸­ā¸ā¸ąā¸š {server} āš€ā¸›āš‡ā¸™ā¸Ŗā¸°ā¸ĸ⏰", "version_check_settings": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸Ŗā¸¸āšˆā¸™", @@ -494,15 +435,19 @@ "advanced_settings_clear_image_cache": "ā¸Ĩāš‰ā¸˛ā¸‡āšā¸„ā¸Šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "advanced_settings_clear_image_cache_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩāš‰ā¸˛ā¸‡āšā¸„ā¸Šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "advanced_settings_clear_image_cache_success": "āš€ā¸„ā¸Ĩā¸ĩā¸ĸā¸ŖāšŒā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆ {size} ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "advanced_settings_enable_alternate_media_filter_subtitle": "āšƒā¸Šāš‰ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁⏙ā¸ĩāš‰āš€ā¸žā¸ˇāšˆā¸­ā¸ā¸Ŗā¸­ā¸‡ā¸Ēā¸ˇāšˆā¸­ā¸Ŗā¸°ā¸Ģā¸§āšˆā¸˛ā¸‡ā¸ā¸˛ā¸Ŗā¸‹ā¸´ā¸‡ā¸āšŒāš‚ā¸”ā¸ĸā¸­ā¸´ā¸‡ā¸•ā¸˛ā¸Ąāš€ā¸‡ā¸ˇāšˆā¸­ā¸™āš„ā¸‚ā¸—ā¸˛ā¸‡āš€ā¸Ĩ⏎⏭⏁ āšā¸™ā¸°ā¸™ā¸ŗāšƒā¸Ģāš‰āšƒā¸Šāš‰āš€ā¸‰ā¸žā¸˛ā¸°āš€ā¸Ąā¸ˇāšˆā¸­āšā¸­ā¸›ā¸Ąā¸ĩā¸›ā¸ąā¸ā¸Ģā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "advanced_settings_enable_alternate_media_filter_title": "[⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸”ā¸Ĩ⏭⏇] āšƒā¸Šāš‰ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸‹ā¸´ā¸‡ā¸āšŒā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸‚ā¸­ā¸‡ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāšā¸šā¸šā¸—ā¸˛ā¸‡āš€ā¸Ĩ⏎⏭⏁", "advanced_settings_log_level_title": "ā¸Ŗā¸°ā¸”ā¸ąā¸šā¸ā¸˛ā¸Ŗ Log: {level}", - "advanced_settings_prefer_remote_subtitle": "ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸šā¸˛ā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš‚ā¸Ģā¸Ĩā¸”ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸ˆā¸˛ā¸ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Šāš‰ā¸˛ā¸Ąā¸˛ā¸ āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰āš€ā¸žā¸ˇāšˆā¸­āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸ˆā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāšā¸—ā¸™", - "advanced_settings_prefer_remote_title": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ ā¸˛ā¸žā¸ˆā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš€ā¸›āš‡ā¸™ā¸Ģā¸Ĩā¸ąā¸", + "advanced_settings_prefer_remote_subtitle": "ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸šā¸˛ā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš‚ā¸Ģā¸Ĩā¸”ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ā¸ˆā¸˛ā¸ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Šāš‰ā¸˛ā¸Ąā¸˛ā¸ āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰āš€ā¸žā¸ˇāšˆā¸­āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸˛ā¸‡āš„ā¸ā¸Ĩāšā¸—ā¸™", + "advanced_settings_prefer_remote_title": "āšƒā¸Ģāš‰ā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸ŗā¸„ā¸ąā¸ā¸ā¸ąā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ŗā¸ĩāš‚ā¸Ąā¸—", "advanced_settings_proxy_headers_subtitle": "⏁⏺ā¸Ģ⏙⏔ proxy headers ⏗ā¸ĩāšˆ Immich ⏄⏧⏪ā¸Ēāšˆā¸‡ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸šāšā¸•āšˆā¸Ĩā¸°ā¸„ā¸ŗā¸‚ā¸­āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ", "advanced_settings_proxy_headers_title": "ā¸Ēāšˆā¸§ā¸™ā¸Ģā¸ąā¸§ā¸žā¸Ŗāš‡ā¸­ā¸ā¸‹ā¸ĩ⏁⏺ā¸Ģā¸™ā¸”āš€ā¸­ā¸‡ [⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸”ā¸Ĩ⏭⏇]", "advanced_settings_readonly_mode_subtitle": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš‚ā¸Ģā¸Ąā¸”ā¸­āšˆā¸˛ā¸™āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ ⏋ā¸ļāšˆā¸‡ā¸ˆā¸°ā¸—ā¸ŗāšƒā¸Ģāš‰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧ āšā¸•āšˆāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ĩ⏎⏭⏁ā¸Ģā¸Ĩ⏞ā¸ĸā¸ ā¸˛ā¸ž, āšā¸Šā¸ŖāšŒ, āšā¸„ā¸Ēā¸•āšŒ, ā¸Ĩā¸šā¸ ā¸˛ā¸žāš„ā¸”āš‰ āš€ā¸›ā¸´ā¸”ā¸Ģā¸Ŗā¸ˇā¸­ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš‚ā¸Ģā¸Ąā¸”ā¸­āšˆā¸˛ā¸™āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™āš„ā¸”āš‰ā¸ˆā¸˛ā¸ā¸ ā¸˛ā¸žāš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒā¸šā¸™ā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­ā¸Ģā¸Ĩā¸ąā¸", "advanced_settings_readonly_mode_title": "āš‚ā¸Ģā¸Ąā¸”ā¸­āšˆā¸˛ā¸™āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", + "advanced_settings_self_signed_ssl_subtitle": "ā¸‚āš‰ā¸˛ā¸Ąā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ SSL ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡āšā¸šā¸š self-signed", + "advanced_settings_self_signed_ssl_title": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ SSL āšā¸šā¸š self-signed [⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸”ā¸Ĩ⏭⏇]", "advanced_settings_sync_remote_deletions_subtitle": "ā¸Ĩ⏚ā¸Ģā¸Ŗā¸ˇā¸­ā¸ā¸šāš‰ā¸„ā¸ˇā¸™āš„ā¸Ÿā¸ĨāšŒā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰āš‚ā¸”ā¸ĸā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´āš€ā¸Ąā¸ˇāšˆā¸­ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸”ā¸ąā¸‡ā¸ā¸Ĩāšˆā¸˛ā¸§ā¸œāšˆā¸˛ā¸™āš€ā¸§āš‡ā¸š", - "advanced_settings_sync_remote_deletions_title": "ā¸‹ā¸´ā¸‡ā¸āšŒā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸ˆā¸˛ā¸ā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ [⏗⏔ā¸Ĩā¸­ā¸‡āšƒā¸Šāš‰]", + "advanced_settings_sync_remote_deletions_title": "ā¸‹ā¸´ā¸‡ā¸āšŒā¸ā¸˛ā¸Ŗā¸Ĩ⏚⏈⏞⏁⏪⏰ā¸ĸā¸°āš„ā¸ā¸Ĩ [⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸”ā¸Ĩ⏭⏇]", "advanced_settings_tile_subtitle": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸‚ā¸ąāš‰ā¸™ā¸Ēā¸šā¸‡", "advanced_settings_troubleshooting_subtitle": "āš€ā¸›ā¸´ā¸”ā¸Ÿā¸ĩāš€ā¸ˆā¸­ā¸ŖāšŒāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ąāš€ā¸žā¸ˇāšˆā¸­āšā¸āš‰āš„ā¸‚ā¸›ā¸ąā¸ā¸Ģ⏞", "advanced_settings_troubleshooting_title": "āšā¸āš‰āš„ā¸‚ā¸›ā¸ąā¸ā¸Ģ⏞", @@ -516,18 +461,31 @@ "album_delete_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą {album} ⏙ā¸ĩāš‰ ?", "album_delete_confirmation_description": "ā¸Ģā¸˛ā¸āšā¸Šā¸ŖāšŒā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸™ā¸ĩāš‰ ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸Ŗā¸˛ā¸ĸā¸­ā¸ˇāšˆā¸™ā¸ˆā¸°āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸”āš‰ā¸­ā¸ĩ⏁", "album_deleted": "ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", + "album_info_card_backup_album_excluded": "ā¸–ā¸šā¸ā¸ĸā¸āš€ā¸§āš‰ā¸™", + "album_info_card_backup_album_included": "ā¸Ŗā¸§ā¸Ą", "album_info_updated": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", + "album_leave": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą ?", + "album_leave_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą {album} āšƒā¸Šāšˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "album_name": "ā¸Šā¸ˇāšˆā¸­ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "album_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "album_remove_user": "ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ ?", "album_remove_user_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ {user} ?", "album_search_not_found": "āš„ā¸Ąāšˆā¸žā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆā¸•ā¸Ŗā¸‡ā¸•ā¸˛ā¸Ąā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞⏂⏭⏇⏄⏏⏓", + "album_selected": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "album_share_no_users": "ā¸”ā¸šāš€ā¸Ģā¸Ąā¸ˇā¸­ā¸™ā¸§āšˆā¸˛ā¸„ā¸¸ā¸“āš„ā¸”āš‰āšā¸Šā¸ŖāšŒā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸™ā¸ĩāš‰ā¸ā¸ąā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšā¸Ĩāš‰ā¸§", "album_summary": "ā¸Ēā¸Ŗā¸¸ā¸›ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "album_updated": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "album_updated_setting_description": "ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ā¸­ā¸ĩāš€ā¸Ąā¸Ĩāš€ā¸Ąā¸ˇāšˆā¸­ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒā¸Ąā¸ĩā¸Ēā¸ˇāšˆā¸­āšƒā¸Ģā¸Ąāšˆ", "album_upload_assets": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­ā¸ˆā¸˛ā¸ā¸„ā¸­ā¸Ąā¸žā¸´ā¸§āš€ā¸•ā¸­ā¸ŖāšŒāš€ā¸žā¸ˇāšˆā¸­āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "album_user_left": "⏭⏭⏁⏈⏞⏁ {album}", + "album_user_removed": "ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ {user} āšā¸Ĩāš‰ā¸§", + "album_viewer_appbar_delete_confirm": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸­ā¸ĸ⏞⏁ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸™ā¸ĩāš‰ā¸ˆā¸˛ā¸ā¸šā¸ąā¸ā¸Šā¸ĩ⏄⏏⏓ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ", "album_viewer_appbar_share_err_delete": "ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "album_viewer_appbar_share_err_leave": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "album_viewer_appbar_share_err_remove": "ā¸Ąā¸ĩā¸›ā¸ąā¸ā¸Ģā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ŗā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "album_viewer_appbar_share_err_title": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸Šā¸ˇāšˆā¸­ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "album_viewer_appbar_share_leave": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "album_viewer_appbar_share_to": "āšā¸Šā¸ŖāšŒāšƒā¸Ģāš‰", "album_viewer_page_share_add_users": "āš€ā¸žā¸´āšˆā¸Ąā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "album_with_link_access": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰ā¸—ā¸¸ā¸ā¸„ā¸™ā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸Ĩā¸´ā¸‡ā¸āšŒā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩā¸°ā¸œā¸šāš‰ā¸„ā¸™ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸™ā¸ĩāš‰", "albums": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", @@ -536,12 +494,14 @@ "albums_default_sort_order_description": "ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡āšā¸­ā¸Ēāš€ā¸‹āš‡ā¸•āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™āš€ā¸Ąā¸ˇāšˆā¸­ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸Ģā¸Ąāšˆ", "albums_feature_description": "⏁ā¸Ĩā¸¸āšˆā¸Ąā¸‚ā¸­ā¸‡āšā¸­ā¸Ēāš€ā¸‹āš‡ā¸•ā¸—ā¸ĩāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēāšˆā¸‡āšƒā¸Ģāš‰ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸­ā¸ˇāšˆā¸™āš„ā¸”āš‰", "albums_on_device_count": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ ({count})", + "albums_selected": "{count, plural, one {āš€ā¸Ĩ⏎⏭⏁ # ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą} other {āš€ā¸Ĩ⏎⏭⏁ # ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą}}", "all": "ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "all_albums": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "all_people": "⏗⏏⏁⏄⏙", "all_photos": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "all_videos": "⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "allow_dark_mode": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āš‚ā¸Ģā¸Ąā¸”ā¸Ąā¸ˇā¸”", + "allow_edits": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰āšā¸āš‰āš„ā¸‚āš„ā¸”āš‰", "allow_public_user_to_download": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸Ēā¸˛ā¸˜ā¸˛ā¸Ŗā¸“ā¸°ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš„ā¸”āš‰", "allow_public_user_to_upload": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸Ēā¸˛ā¸˜ā¸˛ā¸Ŗā¸“ā¸°ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸”āš‰", "allowed": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", @@ -549,12 +509,14 @@ "always_keep": "āš€ā¸āš‡ā¸šāš€ā¸Ēā¸Ąā¸­", "always_keep_photos_hint": "\"āš€ā¸žā¸´āšˆā¸Ąā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸§āšˆā¸˛ā¸‡\" ā¸ˆā¸°āš€ā¸āš‡ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰", "always_keep_videos_hint": "\"āš€ā¸žā¸´āšˆā¸Ąā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸§āšˆā¸˛ā¸‡\" ā¸ˆā¸°āš€ā¸āš‡ā¸šā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰", - "api_key": "⏄ā¸ĩā¸ĸāšŒ API", + "anti_clockwise": "ā¸—ā¸§ā¸™āš€ā¸‚āš‡ā¸Ąā¸™ā¸˛ā¸Ŧ⏴⏁⏞", + "api_key": "API key", "api_key_description": "ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰ā¸ˆā¸°āšā¸Ēā¸”ā¸‡āš€ā¸žā¸ĩā¸ĸā¸‡ā¸„ā¸Ŗā¸ąāš‰ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧ āš‚ā¸›ā¸Ŗā¸”ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸āšˆā¸­ā¸™ā¸›ā¸´ā¸”ā¸Ģā¸™āš‰ā¸˛ā¸•āšˆā¸˛ā¸‡", - "api_key_empty": "ā¸Šā¸ˇāšˆā¸­ā¸„ā¸ĩā¸ĸāšŒ API ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸„ā¸§ā¸Ŗā¸§āšˆā¸˛ā¸‡āš€ā¸›ā¸Ĩāšˆā¸˛", - "api_keys": "⏄ā¸ĩā¸ĸāšŒ API", + "api_key_empty": "ā¸Šā¸ˇāšˆā¸­ API Key ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸„ā¸§ā¸Ŗā¸§āšˆā¸˛ā¸‡āš€ā¸›ā¸Ĩāšˆā¸˛", + "api_keys": "API Key", "app_architecture_variant": "ā¸Ŗā¸šā¸›āšā¸šā¸š (ā¸Ēā¸–ā¸˛ā¸›ā¸ąā¸•ā¸ĸā¸ā¸Ŗā¸Ŗā¸Ą)", "app_bar_signout_dialog_content": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸­ā¸ĸ⏞⏁⏭⏭⏁⏈⏞⏁⏪⏰⏚⏚", + "app_bar_signout_dialog_ok": "āšƒā¸Šāšˆ", "app_bar_signout_dialog_title": "⏭⏭⏁⏈⏞⏁⏪⏰⏚⏚", "app_download_links": "ā¸Ĩā¸´ā¸‡ā¸āšŒā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āšā¸­ā¸›", "app_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸­ā¸›", @@ -565,19 +527,26 @@ "archive": "āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "archive_action_prompt": "āš€ā¸žā¸´āšˆā¸Ą {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", "archive_or_unarchive_photo": "āš€ā¸āš‡ā¸š/āš„ā¸Ąāšˆāš€ā¸āš‡ā¸šā¸ ā¸˛ā¸žā¸–ā¸˛ā¸§ā¸Ŗ", + "archive_page_no_archived_assets": "āš„ā¸Ąāšˆā¸žā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸™ā¸—ā¸ĩāšˆāš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", + "archive_page_title": "āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ ({count})", "archive_size": "ā¸‚ā¸™ā¸˛ā¸”āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "archive_size_description": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸‚ā¸™ā¸˛ā¸”ā¸Ēā¸šā¸‡ā¸Ē⏏⏔ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ (GiB)", "archived": "āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", "archived_count": "{count, plural, other {āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ # ⏪⏞ā¸ĸ⏁⏞⏪}}", "are_these_the_same_person": "āš€ā¸›āš‡ā¸™ā¸šā¸¸ā¸„ā¸„ā¸Ĩāš€ā¸”ā¸ĩā¸ĸā¸§ā¸ā¸ąā¸™ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "are_you_sure_to_do_this": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ŗā¸Ēā¸´āšˆā¸‡ā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", + "asset_action_delete_err_read_only": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšā¸šā¸šā¸­āšˆā¸˛ā¸™ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸā¸§āš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "asset_action_share_err_offline": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", "asset_added_to_album": "āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "asset_adding_to_album": "⏁⏺ā¸Ĩā¸ąā¸‡āš€ā¸žā¸´āšˆā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąâ€Ļ", "asset_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ēā¸ˇāšˆā¸­āšā¸Ĩāš‰ā¸§", - "asset_day_count": "{date}: ā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}", "asset_description_updated": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "asset_filename_is_offline": "ā¸Ēā¸ˇāšˆā¸­ {filename} ā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒā¸­ā¸ĸā¸šāšˆ", + "asset_has_unassigned_faces": "ā¸Ēā¸ˇāšˆā¸­āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ŗā¸°ā¸šā¸¸āšƒā¸šā¸Ģā¸™āš‰ā¸˛", "asset_hashing": "⏁⏺ā¸Ĩā¸ąā¸‡āšā¸Žā¸Šâ€Ļ", "asset_list_group_by_sub_title": "⏁ā¸Ĩā¸¸āšˆā¸Ąāš‚ā¸”ā¸ĸ", + "asset_list_layout_settings_dynamic_layout_title": "āšā¸œā¸™ā¸œā¸ąā¸‡ā¸›ā¸Ŗā¸ąā¸šā¸•ā¸ąā¸§", + "asset_list_layout_settings_group_automatically": "ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", "asset_list_layout_settings_group_by": "ā¸ˆā¸ąā¸”ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāš‚ā¸”ā¸ĸ", "asset_list_layout_settings_group_by_month_day": "āš€ā¸”ā¸ˇā¸­ā¸™ + ā¸§ā¸ąā¸™", "asset_list_layout_sub_title": "ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”ā¸§ā¸˛ā¸‡", @@ -588,84 +557,146 @@ "asset_not_found_on_icloud": "āš„ā¸Ąāšˆā¸žā¸šā¸Ēā¸ˇāšˆā¸­ā¸šā¸™ iCloud ā¸Ēā¸ˇāšˆā¸­ā¸­ā¸˛ā¸ˆā¸ˆā¸°āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰āš€ā¸™ā¸ˇāšˆā¸­ā¸‡ā¸ˆā¸˛ā¸ iCloud āš€ā¸āš‡ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸”ā¸ĩāš„ā¸§āš‰", "asset_offline": "ā¸Ēā¸ˇāšˆā¸­ā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒ", "asset_offline_description": "āš„ā¸Ąāšˆā¸žā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏠⏞ā¸ĸ⏙⏭⏁⏙ā¸ĩāš‰āšƒā¸™ā¸”ā¸´ā¸Ēā¸āšŒā¸­ā¸ĩā¸ā¸•āšˆā¸­āš„ā¸› āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸´ā¸”ā¸•āšˆā¸­ā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩ⏪⏰⏚⏚ Immich ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš€ā¸žā¸ˇāšˆā¸­ā¸‚ā¸­ā¸„ā¸§ā¸˛ā¸Ąā¸Šāšˆā¸§ā¸ĸāš€ā¸Ģā¸Ĩ⏎⏭", + "asset_restored_successfully": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸Ēā¸ˇāšˆā¸­ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "asset_skipped": "ā¸‚āš‰ā¸˛ā¸Ąāšā¸Ĩāš‰ā¸§", "asset_skipped_in_trash": "āšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", + "asset_trashed": "ā¸ĸāš‰ā¸˛ā¸ĸā¸Ēā¸ˇāšˆā¸­āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§", "asset_troubleshoot": "āšā¸āš‰ā¸›ā¸ąā¸ā¸Ģ⏞ā¸Ēā¸ˇāšˆā¸­", "asset_uploaded": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩāš‰ā¸§", "asset_uploading": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔â€Ļ", "asset_viewer_settings_subtitle": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡āšā¸ā¸Ĩāš€ā¸Ĩ⏭⏪ā¸ĩ", "asset_viewer_settings_title": "ā¸•ā¸ąā¸§ā¸”ā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪", "assets": "ā¸Ēā¸ˇāšˆā¸­", + "assets_added_count": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ {count, plural, one{# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āšā¸Ĩāš‰ā¸§", "assets_added_to_album_count": "āš€ā¸žā¸´āšˆā¸Ą {count, plural, one {# asset} other {# assets}} āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "assets_added_to_album_partial_count": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ {successCount} ā¸ˆā¸˛ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” {totalCount} {totalCount, plural, one {⏪⏞ā¸ĸ⏁⏞⏪} other {⏪⏞ā¸ĸ⏁⏞⏪}} āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "assets_added_to_albums_count": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ {assetTotal, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}} āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą {albumTotal, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āšā¸Ĩāš‰ā¸§", "assets_cannot_be_added_to_album_count": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ą {count, plural, one {ā¸Ēā¸ˇāšˆā¸­} other {ā¸Ēā¸ˇāšˆā¸­}} āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "assets_cannot_be_added_to_albums": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ą{count, plural, one {ā¸Ēā¸ˇāšˆā¸­} other {ā¸Ēā¸ˇāšˆā¸­}}āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸” āš† āš„ā¸”āš‰", "assets_count": "ā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}", + "assets_deleted_permanently": "{count} ā¸Ēā¸ˇāšˆā¸­ā¸–ā¸šā¸ā¸Ĩ⏚⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", + "assets_deleted_permanently_from_server": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ Immich ⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", + "assets_downloaded_failed": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ {count, plural, one {āš„ā¸Ÿā¸ĨāšŒ} other {āš„ā¸Ÿā¸ĨāšŒ}} āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ - {error}", + "assets_downloaded_successfully": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ {count, plural, one {āš„ā¸Ÿā¸ĨāšŒ} other {āš„ā¸Ÿā¸ĨāšŒ}} ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "assets_moved_to_trash_count": "ā¸ĸāš‰ā¸˛ā¸ĸ {count, plural, one {# asset} other {# assets}} āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§", "assets_permanently_deleted_count": "ā¸Ĩ⏚ {count, plural, one {# asset} other {# assets}} ā¸—ā¸´āš‰ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", "assets_removed_count": "{count, plural, one {# asset} other {# assets}} ā¸–ā¸šā¸ā¸Ĩā¸šāšā¸Ĩāš‰ā¸§", - "assets_restore_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸—ā¸´āš‰ā¸‡āš„ā¸›ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”? ā¸„ā¸¸ā¸“ā¸ˆā¸°āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ā¸Ĩā¸ąā¸šāš„ā¸”āš‰ āš‚ā¸›ā¸Ŗā¸”ā¸—ā¸Ŗā¸˛ā¸šā¸§āšˆā¸˛ā¸Ēā¸ˇāšˆā¸­ā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒāšƒā¸”āš† āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸šāš‰ā¸„ā¸ˇā¸™āš„ā¸”āš‰ā¸”āš‰ā¸§ā¸ĸ⏧⏴⏘ā¸ĩ⏙ā¸ĩāš‰", + "assets_removed_permanently_from_device": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", + "assets_restore_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸—ā¸´āš‰ā¸‡ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”? ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰āš„ā¸”āš‰! āš‚ā¸›ā¸Ŗā¸”ā¸—ā¸Ŗā¸˛ā¸šā¸§āšˆā¸˛ā¸Ēā¸ˇāšˆā¸­ā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒāšƒā¸”āš† āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸šāš‰ā¸„ā¸ˇā¸™āš„ā¸”āš‰ā¸”āš‰ā¸§ā¸ĸ⏧⏴⏘ā¸ĩ⏙ā¸ĩāš‰", "assets_restored_count": "{count, plural, one {# asset} other {# assets}} ā¸„ā¸ˇā¸™ā¸„āšˆā¸˛", + "assets_restored_successfully": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ {count} ā¸Ēā¸ˇāšˆā¸­ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "assets_trashed": "ā¸ĸāš‰ā¸˛ā¸ĸ {count} ā¸Ēā¸ˇāšˆā¸­āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "assets_trashed_count": "{count, plural, one {# asset} other {# assets}} ā¸–ā¸šā¸ā¸Ĩ⏚", + "assets_trashed_from_server": "ā¸ĸāš‰ā¸˛ā¸ĸ {count} ā¸Ēā¸ˇāšˆā¸­ā¸ˆā¸˛ā¸ Immich āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "assets_were_part_of_album_count": "{count, plural, one {Asset was} other {Assets were}} ⏭ā¸ĸā¸šāšˆāšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸­ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", "assets_were_part_of_albums_count": "{count, plural, one {ā¸Ēā¸ˇāšˆā¸­} other {ā¸Ēā¸ˇāšˆā¸­}}āš€ā¸›āš‡ā¸™ā¸Ēāšˆā¸§ā¸™ā¸Ģ⏙ā¸ļāšˆā¸‡ā¸‚ā¸­ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸­ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", "authorized_devices": "ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸—ā¸ĩāšˆāš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", - "automatic_endpoint_switching_subtitle": "āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­āš‚ā¸”ā¸ĸā¸•ā¸Ŗā¸‡ā¸žāšˆā¸˛ā¸™ā¸§ā¸‡ Wi-Fi ⏗ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸¸āš„ā¸§āš‰ āšā¸Ĩā¸°āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸”āš‰ā¸§ā¸ĸ⏧⏴⏘ā¸ĩā¸­ā¸ˇāšˆā¸™āš€ā¸Ąā¸ˇāšˆā¸­ā¸­ā¸ĸā¸šāšˆā¸™ā¸­ā¸ Wi-Fi ⏗ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸¸āš„ā¸§āš‰", + "automatic_endpoint_switching_subtitle": "āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸”āš‰ā¸§ā¸ĸ LAN ⏠⏞ā¸ĸāšƒā¸™ā¸§ā¸‡ Wi-Fi ⏗ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸¸āš„ā¸§āš‰ āšā¸Ĩā¸°āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸”āš‰ā¸§ā¸ĸ⏧⏴⏘ā¸ĩā¸­ā¸ˇāšˆā¸™āš€ā¸Ąā¸ˇāšˆā¸­ā¸­ā¸ĸā¸šāšˆā¸™ā¸­ā¸ Wi-Fi ⏗ā¸ĩāšˆā¸Ŗā¸°ā¸šā¸¸āš„ā¸§āš‰", "automatic_endpoint_switching_title": "ā¸Ēā¸Ĩā¸ąā¸š URL ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", "autoplay_slideshow": "āš€ā¸Ĩāšˆā¸™ā¸Ēāš„ā¸Ĩā¸”āšŒāš‚ā¸Šā¸§āšŒ", "back": "⏁ā¸Ĩā¸ąā¸š", "back_close_deselect": "ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸š, ⏛⏴⏔, ā¸Ģ⏪⏎⏭ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Ĩ⏎⏭⏁", + "background_backup_running_error": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ā¸—ā¸ŗā¸‡ā¸˛ā¸™ā¸­ā¸ĸā¸šāšˆ āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ŗā¸´āšˆā¸Ąā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸”āš‰ā¸§ā¸ĸā¸•ā¸™āš€ā¸­ā¸‡āš„ā¸”āš‰", "background_location_permission": "ā¸ā¸˛ā¸Ŗā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•ā¸Ŗā¸°ā¸šā¸¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡", "background_location_permission_content": "āš€ā¸žā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ēā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸‚ā¸“ā¸°ā¸—ā¸ĩāšˆā¸Ŗā¸ąā¸™āšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡ Immich ā¸•āš‰ā¸­ā¸‡ā¸Ŗā¸šāš‰ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸—ā¸ĩāšˆāšā¸Ąāšˆā¸ĸ⏺⏕ā¸Ĩā¸­ā¸”āš€ā¸§ā¸Ĩ⏞ āš€ā¸žā¸ˇāšˆā¸­ā¸ˆā¸°ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­āšˆā¸˛ā¸™ā¸Šā¸ˇāšˆā¸­ Wi-Fi", "background_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗā¸—ā¸ŗā¸‡ā¸˛ā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", "backup": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "backup_album_selection_page_albums_device": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ ({count})", "backup_album_selection_page_albums_tap": "ā¸ā¸”āš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸§ā¸Ą ⏁⏔ā¸Ēā¸­ā¸‡ā¸„ā¸Ŗā¸ąāš‰ā¸‡āš€ā¸žā¸ˇāšˆā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™", "backup_album_selection_page_assets_scatter": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏞⏪ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸Ŗā¸°ā¸ˆā¸˛ā¸ĸāš„ā¸›āšƒā¸™ā¸Ģā¸Ĩ⏞ā¸ĸā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą ā¸”ā¸ąā¸‡ā¸™ā¸ąāš‰ā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸–ā¸šā¸ā¸Ŗā¸§ā¸Ąā¸Ģ⏪⏎⏭ā¸ĸā¸āš€ā¸§āš‰ā¸™āšƒā¸™ā¸ā¸Ŗā¸°ā¸šā¸§ā¸™ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_album_selection_page_select_albums": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "backup_album_selection_page_selection_info": "ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏂⏭⏇⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁", + "backup_album_selection_page_total_assets": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "backup_albums_sync": "ā¸ā¸˛ā¸Ŗā¸‹ā¸´ā¸‡ā¸āšŒā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "backup_all": "ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "backup_background_service_backup_failed_message": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ĩā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ...", "backup_background_service_complete_notification": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸Ēā¸ˇāšˆā¸­āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒ", + "backup_background_service_connection_failed_message": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸ā¸ąā¸šāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ĩā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ...", + "backup_background_service_current_upload_notification": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩ⏔ {filename}", "backup_background_service_default_notification": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ā¸Ģā¸˛ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģā¸Ąāšˆ...", + "backup_background_service_error_title": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", "backup_background_service_in_progress_notification": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏂⏭⏇⏄⏏⏓...", + "backup_background_service_upload_failure_notification": "ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩ⏔ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧ {filename}", "backup_controller_page_albums": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "backup_controller_page_background_app_refresh_disabled_content": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸”ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸­ā¸žā¸­ā¸ĸā¸šāšˆāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡āš‚ā¸”ā¸ĸā¸ā¸˛ā¸Ŗāš„ā¸›ā¸—ā¸ĩāšˆ ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ > ā¸—ā¸ąāšˆā¸§āš„ā¸› > ⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸­ā¸›ā¸­ā¸ĸā¸šāšˆāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸”ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", + "backup_controller_page_background_app_refresh_disabled_title": "⏁⏞⏪⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šāšā¸­ā¸žāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸Ĩā¸ąā¸‡ā¸›ā¸´ā¸”", + "backup_controller_page_background_app_refresh_enable_button_text": "āš„ā¸›ā¸ĸā¸ąā¸‡ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", "backup_controller_page_background_battery_info_link": "āšā¸Ēā¸”ā¸‡āšƒā¸Ģāš‰ā¸‰ā¸ąā¸™āš€ā¸Ģāš‡ā¸™", "backup_controller_page_background_battery_info_message": "āš€ā¸žā¸ˇāšˆā¸­ā¸›ā¸Ŗā¸°ā¸Ēā¸šā¸ā¸˛ā¸Ŗā¸“āšŒā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆā¸”ā¸ĩ⏗ā¸ĩāšˆā¸Ē⏏⏔ ā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸›ā¸Ŗā¸°ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žāšā¸šā¸•āš€ā¸•ā¸­ā¸Ŗā¸ĩāšˆā¸ˆā¸ŗā¸ā¸ąā¸”ā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ąāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸š Immich\n\nāš€ā¸™ā¸ˇāšˆā¸­ā¸‡ā¸ˆā¸˛ā¸ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸”ā¸ąā¸‡ā¸ā¸Ĩāšˆā¸˛ā¸§āš€ā¸‰ā¸žā¸˛ā¸°āš€ā¸ˆā¸˛ā¸°ā¸ˆā¸‡ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāš‚ā¸—ā¸Ŗā¸¨ā¸ąā¸žā¸—āšŒāšā¸•āšˆā¸Ĩā¸°āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ ā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸ˆā¸˛ā¸ā¸œā¸šāš‰ā¸œā¸Ĩā¸´ā¸•āš‚ā¸—ā¸Ŗā¸¨ā¸ąā¸žā¸—āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "backup_controller_page_background_battery_info_ok": "āš‚ā¸­āš€ā¸„", "backup_controller_page_background_battery_info_title": "⏛⏪⏰ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žāšā¸šā¸•āš€ā¸•ā¸­ā¸Ŗā¸ĩāšˆ", - "backup_controller_page_background_delay": "ā¸Ĩāšˆā¸˛ā¸Šāš‰ā¸˛ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģā¸Ąāšˆ: {duration}", + "backup_controller_page_background_charging": "ā¸‚ā¸“ā¸°ā¸Šā¸˛ā¸ŖāšŒā¸ˆā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧", + "backup_controller_page_background_configure_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸´ā¸”ā¸•ā¸ąāš‰ā¸‡ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", + "backup_controller_page_background_delay": "ā¸Ĩāšˆā¸˛ā¸Šāš‰ā¸˛ā¸ā¸˛ā¸Ŗā¸Ĩā¸ŗā¸Ŗā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģā¸Ąāšˆ: {duration}", + "backup_controller_page_background_description": "āš€ā¸›ā¸´ā¸”ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡āš€ā¸žā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģā¸Ąāšˆāš‚ā¸”ā¸ĸ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡āš€ā¸›ā¸´ā¸”āšā¸­ā¸›", + "backup_controller_page_background_is_off": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´ā¸›ā¸´ā¸”ā¸­ā¸ĸā¸šāšˆ", + "backup_controller_page_background_is_on": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´āš€ā¸›ā¸´ā¸”ā¸­ā¸ĸā¸šāšˆ", + "backup_controller_page_background_turn_off": "ā¸›ā¸´ā¸”ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", + "backup_controller_page_background_turn_on": "āš€ā¸›ā¸´ā¸”ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗāš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", + "backup_controller_page_background_wifi": "ā¸šā¸™ Wi-Fi āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", "backup_controller_page_backup": "ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_controller_page_backup_selected": "⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁: ", "backup_controller_page_backup_sub": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸Ēā¸ŗā¸Ŗā¸­ā¸‡āšā¸Ĩāš‰ā¸§", + "backup_controller_page_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš€ā¸Ąā¸ˇāšˆā¸­: {date}", + "backup_controller_page_desc_backup": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛āš€ā¸žā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩā¸”ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģā¸Ąāšˆāš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”āšā¸­ā¸ž", "backup_controller_page_excluded": "ā¸ĸā¸āš€ā¸§āš‰ā¸™: ", + "backup_controller_page_failed": "ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧ ({count})", + "backup_controller_page_filename": "ā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒ: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸ā¸ĩāšˆā¸ĸā¸§ā¸ā¸ąā¸šā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_controller_page_none_selected": "āš„ā¸Ąāšˆā¸Ąā¸ĩ⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁", "backup_controller_page_remainder": "⏗ā¸ĩāšˆāš€ā¸Ģā¸Ĩ⏎⏭", "backup_controller_page_remainder_sub": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁ā¸Ē⏺⏪⏭⏇⏗ā¸ĩāšˆāš€ā¸Ģā¸Ĩ⏎⏭", "backup_controller_page_server_storage": "ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", + "backup_controller_page_start_backup": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "backup_controller_page_status_off": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛ā¸›ā¸´ā¸”ā¸­ā¸ĸā¸šāšˆ", + "backup_controller_page_status_on": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛āš€ā¸›ā¸´ā¸”ā¸­ā¸ĸā¸šāšˆ", "backup_controller_page_storage_format": "{used} ⏈⏞⏁ {total} ā¸–ā¸šā¸āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "backup_controller_page_to_backup": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_controller_page_total_sub": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸‹āš‰ā¸ŗā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁", + "backup_controller_page_turn_off": "⏛⏴⏔⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛", + "backup_controller_page_turn_on": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛", + "backup_controller_page_uploading_file_info": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩā¸”ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš„ā¸Ÿā¸ĨāšŒ", + "backup_err_only_album": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸™ā¸ŗā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ēā¸¸ā¸”ā¸—āš‰ā¸˛ā¸ĸā¸­ā¸­ā¸āš„ā¸”āš‰", "backup_error_sync_failed": "ā¸ā¸˛ā¸Ŗā¸‹ā¸´ā¸‡ā¸āšŒāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩ⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "backup_info_card_assets": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪", + "backup_manual_cancelled": "ā¸–ā¸šā¸ā¸ĸā¸āš€ā¸Ĩ⏴⏁", + "backup_manual_in_progress": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸­ā¸ĸā¸šāšˆ āš‚ā¸›ā¸Ŗā¸”ā¸Ĩā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆāšƒā¸™ā¸Ēā¸ąā¸ā¸žā¸ąā¸", + "backup_manual_success": "ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "backup_manual_title": "ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩ⏔", "backup_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "backup_options_page_title": "ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "backup_setting_subtitle": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩā¸”āšƒā¸™ā¸‰ā¸˛ā¸ā¸Ģā¸™āš‰ā¸˛ āšā¸Ĩā¸°ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡", "backup_settings_subtitle": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", + "backup_upload_details_page_more_details": "āšā¸•ā¸°āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą", "backward": "⏁ā¸Ĩā¸ąā¸šā¸Ģā¸Ĩā¸ąā¸‡", - "battery_optimization_backup_reliability": "⏁⏞⏪⏛⏴⏔⏁⏞⏪⏛⏪⏰ā¸Ģā¸ĸā¸ąā¸”āšā¸šā¸•āš€ā¸•ā¸­ā¸Ŗā¸ĩāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸ŗāšƒā¸Ģāš‰ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡āš„ā¸›āš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ŗā¸˛ā¸šā¸Ŗā¸ˇāšˆā¸™ā¸‚ā¸ļāš‰ā¸™āš„ā¸”āš‰", "biometric_auth_enabled": "ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒāš€ā¸žā¸ˇāšˆā¸­ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸–ā¸šā¸āš€ā¸›ā¸´ā¸”", "biometric_locked_out": "ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒāš€ā¸žā¸ˇāšˆā¸­ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸–ā¸šā¸ā¸Ĩāš‡ā¸­ā¸„", "biometric_no_options": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒāš€ā¸žā¸ˇāšˆā¸­ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", "biometric_not_available": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒāš€ā¸žā¸ˇāšˆā¸­ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩāš„ā¸”āš‰ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰", + "birthdate_saved": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”āšā¸Ĩāš‰ā¸§", "birthdate_set_description": "ā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”ā¸ˆā¸°āšƒā¸Šāš‰āš€ā¸žā¸ˇāšˆā¸­ā¸„ā¸ŗā¸™ā¸§ā¸“ā¸­ā¸˛ā¸ĸā¸¸ā¸‚ā¸­ā¸‡ā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏙ā¸ĩāš‰āšƒā¸™ā¸‚ā¸“ā¸°ā¸—ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸā¸Ŗā¸šā¸›", "blurred_background": "ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡āšā¸šā¸šāš€ā¸šā¸Ĩ⏭", - "browse_templates": "ā¸„āš‰ā¸™ā¸Ģā¸˛āš€ā¸—āš‡ā¸Ąāš€ā¸žā¸Ĩ⏕", "bugs_and_feature_requests": "⏪⏞ā¸ĸā¸‡ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšā¸Ĩā¸°ā¸‚āš‰ā¸­āš€ā¸Ēā¸™ā¸­āšā¸™ā¸°", "build": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡", "build_image": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ Image", - "bulk_delete_duplicates_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩ⏚ {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™} other {# ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™}}āš€ā¸›āš‡ā¸™ā¸ā¸Ĩā¸¸āšˆā¸Ą ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°āš€ā¸āš‡ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāšƒā¸Ģā¸āšˆā¸—ā¸ĩāšˆā¸Ēā¸¸ā¸”ā¸‚ā¸­ā¸‡āšā¸•āšˆā¸Ĩ⏰⏁ā¸Ĩā¸¸āšˆā¸Ąāšā¸Ĩ⏰ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰āš„ā¸”āš‰!", - "bulk_keep_duplicates_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°āš€ā¸āš‡ā¸š {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™} other {# ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™}} āš„ā¸§āš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°āšā¸āš‰āš„ā¸‚ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āš‚ā¸”ā¸ĸāš„ā¸Ąāšˆā¸Ąā¸ĩ⏁⏞⏪ā¸Ĩ⏚ā¸Ēā¸´āšˆā¸‡āšƒā¸”āš€ā¸Ĩā¸ĸ", + "bulk_delete_duplicates_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩ⏚ {count, plural, one {# duplicate asset} other {# duplicate asset}} āš€ā¸›āš‡ā¸™ā¸ā¸Ĩā¸¸āšˆā¸Ą ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°āš€ā¸āš‡ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāšƒā¸Ģā¸āšˆā¸—ā¸ĩāšˆā¸Ēā¸¸ā¸”ā¸‚ā¸­ā¸‡āšā¸•āšˆā¸Ĩ⏰⏁ā¸Ĩā¸¸āšˆā¸Ąāšā¸Ĩ⏰ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰āš„ā¸”āš‰!", + "bulk_keep_duplicates_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗāš€ā¸āš‡ā¸š {count, plural, one {# duplicate asset} other {# duplicate asset}} āš„ā¸§āš‰ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°āšā¸āš‰āš„ā¸‚ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āš‚ā¸”ā¸ĸāš„ā¸Ąāšˆā¸•āš‰ā¸­ā¸‡ā¸Ĩ⏚ā¸Ēā¸´āšˆā¸‡āšƒā¸”āš€ā¸Ĩā¸ĸ", "bulk_trash_duplicates_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸ˆā¸ŗā¸™ā¸§ā¸™ā¸Ąā¸˛ā¸ {count, plural, one {# duplicate asset} other {# duplicate asset}} ā¸ā¸˛ā¸Ŗā¸—ā¸ŗāš€ā¸Šāšˆā¸™ā¸™ā¸ĩāš‰ā¸ˆā¸°āš€ā¸āš‡ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāšƒā¸Ģā¸āšˆā¸—ā¸ĩāšˆā¸Ēā¸¸ā¸”ā¸‚ā¸­ā¸‡āšā¸•āšˆā¸Ĩ⏰⏁ā¸Ĩā¸¸āšˆā¸Ąāšā¸Ĩ⏰ā¸Ĩā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸‹āš‰ā¸ŗā¸­ā¸ˇāšˆā¸™ āš† ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "buy": "ā¸‹ā¸ˇāš‰ā¸­ Immich", + "cache_settings_clear_cache_button": "ā¸Ĩāš‰ā¸˛ā¸‡āšā¸„ā¸Š", + "cache_settings_clear_cache_button_title": "ā¸Ĩāš‰ā¸˛ā¸‡āšā¸„ā¸Šā¸‚ā¸­ā¸‡āšā¸­ā¸ž ⏈⏰ā¸Ēāšˆā¸‡ā¸œā¸Ĩā¸ā¸Ŗā¸°ā¸—ā¸šā¸•āšˆā¸­ā¸›ā¸Ŗā¸°ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žāšā¸­ā¸žā¸ˆā¸™ā¸ā¸§āšˆā¸˛āšā¸„ā¸Šā¸ˆā¸°ā¸–ā¸šā¸ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšƒā¸Ģā¸Ąāšˆ", + "cache_settings_duplicated_assets_clear_button": "ā¸Ĩāš‰ā¸˛ā¸‡", + "cache_settings_duplicated_assets_subtitle": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš€ā¸žā¸´ā¸āš€ā¸‰ā¸ĸāš‚ā¸”ā¸ĸāšā¸­ā¸›", + "cache_settings_duplicated_assets_title": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏗ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ ({count})", + "cache_settings_statistics_album": "ā¸Ŗā¸šā¸›ā¸ĸāšˆā¸­ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž", + "cache_settings_statistics_full": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš€ā¸•āš‡ā¸Ą", + "cache_settings_statistics_shared": "ā¸Ŗā¸šā¸›ā¸ĸāšˆā¸­ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", + "cache_settings_statistics_thumbnail": "ā¸Ŗā¸šā¸›ā¸ĸāšˆā¸­", + "cache_settings_statistics_title": "ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšā¸„ā¸Š", + "cache_settings_subtitle": "ā¸„ā¸§ā¸šā¸„ā¸¸ā¸Ąā¸žā¸¤ā¸•ā¸´ā¸ā¸Ŗā¸Ŗā¸Ąā¸ā¸˛ā¸Ŗāšā¸„ā¸Šā¸‚ā¸­ā¸‡āšā¸­ā¸›ā¸žā¸Ĩā¸´āš€ā¸„ā¸Šā¸ąā¸™ Immich", + "cache_settings_tile_subtitle": "ā¸„ā¸§ā¸šā¸„ā¸¸ā¸Ąā¸žā¸¤ā¸•ā¸´ā¸ā¸Ŗā¸Ŗā¸Ąā¸‚ā¸­ā¸‡ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šāšƒā¸™ā¸•ā¸ąā¸§āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", + "cache_settings_tile_title": "⏗ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šāšƒā¸™ā¸•ā¸ąā¸§āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", + "cache_settings_title": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸„ā¸Š", "camera": "⏁ā¸Ĩāš‰ā¸­ā¸‡", "camera_brand": "ā¸ĸā¸ĩāšˆā¸Ģāš‰ā¸­ā¸ā¸Ĩāš‰ā¸­ā¸‡", "camera_model": "ā¸Ŗā¸¸āšˆā¸™ā¸ā¸Ĩāš‰ā¸­ā¸‡", @@ -675,13 +706,13 @@ "canceling": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸ĸā¸āš€ā¸Ĩ⏴⏁", "cannot_merge_people": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸§ā¸Ąā¸ā¸Ĩā¸¸āšˆā¸Ąā¸„ā¸™āš„ā¸”āš‰", "cannot_undo_this_action": "⏁⏞⏪⏁⏪⏰⏗⏺⏙ā¸ĩāš‰āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šāš„ā¸”āš‰!", - "cannot_update_the_description": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš€ā¸”ā¸—ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš„ā¸”āš‰", + "cannot_update_the_description": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸žāš€ā¸”ā¸—ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš„ā¸”āš‰", "cast": "āšā¸„ā¸Ēā¸•āšŒ", "cast_description": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸›ā¸Ĩ⏞ā¸ĸā¸—ā¸˛ā¸‡āšā¸„ā¸Ēā¸•āšŒ", - "change": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙", "change_date": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", "change_description": "āšā¸āš‰āš„ā¸‚ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", "change_display_order": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙ā¸Ĩā¸ŗā¸”ā¸ąā¸šā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡ā¸œā¸Ĩ", + "change_expiration_time": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸§ā¸Ĩ⏞ā¸Ģā¸Ąā¸”ā¸­ā¸˛ā¸ĸ⏏", "change_location": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸•āšā¸˛āšā¸Ģā¸™āšˆā¸‡", "change_name": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸Šā¸ˇāšˆā¸­", "change_name_successfully": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸Šā¸ˇāšˆā¸­ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", @@ -699,9 +730,10 @@ "changed_visibility_successfully": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸ā¸˛ā¸Ŗā¸Ąā¸­ā¸‡āš€ā¸Ģāš‡ā¸™āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸāšā¸Ĩāš‰ā¸§", "charging": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Šā¸˛ā¸ŖāšŒā¸ˆ", "charging_requirement_mobile_backup": "⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ā¸ˆā¸°ā¸—ā¸ŗā¸‡ā¸˛ā¸™āš€ā¸‰ā¸žā¸˛ā¸°āš€ā¸Ąā¸ˇāšˆā¸­ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸ā¸ŗā¸Ĩā¸ąā¸‡ā¸Šā¸˛ā¸ŖāšŒā¸ˆā¸­ā¸ĸā¸šāšˆ", + "check_corrupt_asset_backup": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ā¸Ē⏺⏪⏭⏇ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸œā¸´ā¸”ā¸›ā¸ā¸•ā¸´", + "check_corrupt_asset_backup_button": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚", + "check_corrupt_asset_backup_description": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ Wi-Fi āšā¸Ĩ⏰ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸–ā¸šā¸ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸Ĩāš‰ā¸§āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸­ā¸˛ā¸ˆāšƒā¸Šāš‰āš€ā¸§ā¸Ĩ⏞ā¸Ģā¸Ĩ⏞ā¸ĸ⏙⏞⏗ā¸ĩ", "check_logs": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸šā¸ąā¸™ā¸—ā¸ļ⏁", - "checksum": "Checksum", - "choose": "āš€ā¸Ĩ⏎⏭⏁", "choose_matching_people_to_merge": "āš€ā¸Ĩ⏎⏭⏁⏄⏙⏗ā¸ĩāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™āš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸§ā¸Ąāš€ā¸‚āš‰ā¸˛ā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™", "city": "āš€ā¸Ąā¸ˇā¸­ā¸‡", "cleanup_confirm_description": "Immich ā¸žā¸šā¸Ēā¸ˇāšˆā¸­ {count} ⏪⏞ā¸ĸ⏁⏞⏪ (ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸‚ā¸ļāš‰ā¸™ā¸āšˆā¸­ā¸™ {date}) ⏗ā¸ĩāšˆā¸–ā¸šā¸ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸›ā¸Ĩā¸­ā¸”ā¸ ā¸ąā¸ĸāšā¸Ĩāš‰ā¸§ ā¸Ĩ⏚ā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸•āš‰ā¸™ā¸—ā¸˛ā¸‡ā¸ˆā¸˛ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", @@ -715,14 +747,14 @@ "cleanup_preview_title": "ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚ ({count})", "cleanup_step3_description": "ā¸Ēāšā¸ā¸™ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸Ĩāš‰ā¸§ ⏗ā¸ĩāšˆā¸•ā¸Ŗā¸‡ā¸•ā¸˛ā¸Ąā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšā¸Ĩā¸°ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "cleanup_step4_summary": "ā¸Ēā¸ˇāšˆā¸­ {count} ⏪⏞ā¸ĸ⏁⏞⏪ (ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸āšˆā¸­ā¸™ {date}) ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸ˆā¸˛ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸•āš‰ā¸™ā¸—ā¸˛ā¸‡ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸ˆā¸°ā¸ĸā¸ąā¸‡āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸”āš‰ā¸ˆā¸˛ā¸āšā¸­ā¸› Immich", - "cleanup_trash_hint": "āš€ā¸›ā¸´ā¸”āšā¸­ā¸›āšā¸ā¸Ĩāš€ā¸Ĩ⏭⏪ā¸ĩā¸‚ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸šāšā¸Ĩ⏰ā¸Ĩāš‰ā¸˛ā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰āš„ā¸”āš‰ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸„ā¸ˇā¸™", "clear": "ā¸Ĩāš‰ā¸˛ā¸‡", "clear_all": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "clear_all_recent_searches": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸›ā¸Ŗā¸°ā¸§ā¸ąā¸•ā¸´ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞", - "clear_failed_count": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸—ā¸ĩāšˆā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧ ({count})", "clear_file_cache": "ā¸Ĩāš‰ā¸˛ā¸‡āšā¸„ā¸Šāš„ā¸Ÿā¸ĨāšŒ", "clear_message": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ą", "clear_value": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸„āšˆā¸˛", + "client_cert_dialog_msg_confirm": "āš€ā¸Ēā¸Ŗāš‡ā¸ˆ", + "client_cert_enter_password": "āšƒā¸Ēāšˆā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "client_cert_import": "ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛", "client_cert_import_success_msg": "ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛āšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "client_cert_invalid_msg": "āšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ ā¸Ģ⏪⏎⏭⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", @@ -731,10 +763,12 @@ "client_cert_remove_msg": "ā¸Ĩā¸šāšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "client_cert_subtitle": "ā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šāš€ā¸‰ā¸žā¸˛ā¸° PKCS12 (.p12, .pfx) āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™ ā¸ā¸˛ā¸Ŗā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛/ā¸Ĩā¸šāšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸ŗāš„ā¸”āš‰ā¸āšˆā¸­ā¸™āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šāš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", "client_cert_title": "āšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡ SSL āš„ā¸„ā¸Ĩāš€ā¸­ā¸™ā¸•āšŒ [⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸”ā¸Ĩ⏭⏇]", + "clockwise": "ā¸•ā¸˛ā¸Ąāš€ā¸‚āš‡ā¸Ąā¸™ā¸˛ā¸Ŧ⏴⏁⏞", "close": "⏛⏴⏔", "collapse": "ā¸ĸāšˆā¸­", "collapse_all": "ā¸ĸāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "color": "ā¸Ēā¸ĩ", + "color_theme": "ā¸Ēā¸ĩ⏘ā¸ĩā¸Ą", "command": "⏄⏺ā¸Ēā¸ąāšˆā¸‡", "command_palette_prompt": "ā¸„āš‰ā¸™ā¸Ģ⏞ā¸Ģā¸™āš‰ā¸˛ ⏁⏞⏪⏁⏪⏰⏗⏺ ā¸Ģ⏪⏎⏭⏄⏺ā¸Ēā¸ąāšˆā¸‡āš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ŗā¸§ā¸”āš€ā¸Ŗāš‡ā¸§", "command_palette_to_close": "āš€ā¸žā¸ˇāšˆā¸­ā¸›ā¸´ā¸”", @@ -747,12 +781,11 @@ "comments_are_disabled": "ā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸´ā¸”āš€ā¸Ģāš‡ā¸™ā¸–ā¸šā¸ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "common_create_new_album": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸Ģā¸Ąāšˆ", "completed": "ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", - "configuration": "⏁⏞⏪⏁⏺ā¸Ģā¸™ā¸”ā¸„āšˆā¸˛", "confirm": "ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™", "confirm_admin_password": "ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩ⏪⏰⏚⏚", "confirm_delete_face": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛{name}⏭⏭⏁ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "confirm_delete_shared_link": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāšƒā¸Šāšˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ?", - "confirm_keep_this_delete_others": "⏈⏰ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ Stack ā¸ĸā¸āš€ā¸§āš‰ā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ", + "confirm_keep_this_delete_others": "⏈⏰ā¸Ĩā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ā¸Ŗā¸˛ā¸ĸ⏁⏞⏪ āšā¸Ĩ⏰ā¸ĸā¸āš€ā¸§āš‰ā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆāšƒā¸Šāšˆāš„ā¸Ģā¸Ąā¸—ā¸ĩāšˆā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­?", "confirm_new_pin_code": "ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN)", "confirm_password": "ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "confirm_tag_face": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗāšā¸—āš‡ā¸āšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸™ā¸ĩāš‰ā¸”āš‰ā¸§ā¸ĸā¸Šā¸ˇāšˆā¸­ {name} ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ", @@ -762,16 +795,19 @@ "contain": "ā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆ", "context": "ā¸šā¸Ŗā¸´ā¸šā¸—", "continue": "ā¸•āšˆā¸­āš„ā¸›", - "control_bottom_app_bar_add_tags": "āš€ā¸žā¸´āšˆā¸Ąāšā¸—āš‡ā¸", + "control_bottom_app_bar_create_new_album": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸Ģā¸Ąāšˆ", + "control_bottom_app_bar_delete_from_immich": "ā¸Ĩ⏚⏈⏞⏁ Immich", "control_bottom_app_bar_delete_from_local": "ā¸Ĩā¸šā¸ˆā¸˛ā¸āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", "control_bottom_app_bar_edit_location": "āšā¸āš‰āš„ā¸‚ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", "control_bottom_app_bar_edit_time": "āšā¸āš‰āš„ā¸‚ā¸§ā¸ąā¸™āšā¸Ĩā¸°āš€ā¸§ā¸Ĩ⏞", + "control_bottom_app_bar_share_link": "āšā¸Šā¸ŖāšŒā¸Ĩā¸´ā¸‡ā¸āšŒ", + "control_bottom_app_bar_share_to": "āšā¸Šā¸ŖāšŒāšƒā¸Ģāš‰", "control_bottom_app_bar_trash_from_immich": "ā¸ĸāš‰ā¸˛ā¸ĸāš€ā¸‚āš‰ā¸˛ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "copied_image_to_clipboard": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸ ā¸˛ā¸žāš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”āšā¸Ĩāš‰ā¸§", "copied_to_clipboard": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”āšā¸Ĩāš‰ā¸§!", "copy_error": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", + "copy_file_path": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸žā¸˛ā¸˜ā¸‚ā¸­ā¸‡āš„ā¸Ÿā¸ĨāšŒ", "copy_image": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸ā¸ ā¸˛ā¸ž", - "copy_json": "ā¸„ā¸ąā¸”ā¸Ĩ⏭⏁ JSON", "copy_link": "ā¸„ā¸ąā¸”ā¸Ĩ⏭⏁ā¸Ĩā¸´ā¸‡ā¸āšŒ", "copy_link_to_clipboard": "ā¸„ā¸ąā¸”ā¸Ĩ⏭⏁ā¸Ĩā¸´ā¸‡ā¸āšŒāš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”", "copy_password": "ā¸„ā¸ąā¸”ā¸Ĩ⏭⏁⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", @@ -783,7 +819,6 @@ "create_album": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "create_album_page_untitled": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸Šā¸ˇāšˆā¸­", "create_api_key": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸ĩā¸ĸāšŒ API", - "create_first_workflow": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ Workflow ā¸­ā¸ąā¸™āšƒā¸Ģā¸Ąāšˆ", "create_library": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž", "create_link": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩā¸´ā¸‡ā¸āšŒ", "create_link_to_share": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩā¸´ā¸‡ā¸āšŒāš€ā¸žā¸ˇāšˆā¸­āšā¸Šā¸ŖāšŒ", @@ -792,54 +827,52 @@ "create_new_person": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸™āšƒā¸Ģā¸Ąāšˆ", "create_new_person_hint": "⏁⏺ā¸Ģ⏙⏔ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āšƒā¸Ģāš‰ā¸ā¸ąā¸šā¸„ā¸™āšƒā¸Ģā¸Ąāšˆ", "create_new_user": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšƒā¸Ģā¸Ąāšˆ", - "create_person": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", - "create_person_subtitle": "āš€ā¸žā¸´āšˆā¸Ąā¸Šā¸ˇāšˆā¸­āšƒā¸Ģāš‰ā¸ā¸ąā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸Ĩā¸°āšā¸—āš‡ā¸ā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸„ā¸™āšƒā¸Ģā¸Ąāšˆ", "create_shared_album_page_share_add_assets": "āš€ā¸žā¸´āšˆā¸Ąā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪", "create_shared_album_page_share_select_photos": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "create_shared_link": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", "create_tag": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸—āš‡ā¸āšƒā¸Ģā¸Ąāšˆ", "create_tag_description": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸—āš‡ā¸āšƒā¸Ģā¸Ąāšˆ ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāšā¸—āš‡ā¸ā¸—ā¸ĩāšˆā¸‹āš‰ā¸­ā¸™ā¸ā¸ąā¸™ āš‚ā¸›ā¸Ŗā¸”ā¸›āš‰ā¸­ā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸‚ā¸­ā¸‡āšā¸—āš‡ā¸ ā¸Ŗā¸§ā¸Ąā¸–ā¸ļā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ģā¸Ąā¸˛ā¸ĸā¸—ā¸ąā¸š", "create_user": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰", - "create_workflow": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ Workflow", "created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸Ĩāš‰ā¸§", "created_at": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš€ā¸Ąā¸ˇāšˆā¸­", - "creating_linked_albums": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸ā¸ąā¸™â€Ļ", "crop": "⏄⏪⏭⏛", + "crop_aspect_ratio_fixed": "⏄⏇⏗ā¸ĩāšˆ", "crop_aspect_ratio_free": "⏭⏴ā¸Ē⏪⏰", "crop_aspect_ratio_original": "ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", - "crop_aspect_ratio_square": "ā¸ˆā¸•ā¸¸ā¸Ŗā¸ąā¸Ē", + "curated_object_page_title": "ā¸Ēā¸´āšˆā¸‡ā¸‚ā¸­ā¸‡", "current_device": "ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "current_pin_code": "⏪ā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN) ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "current_server_address": "⏗ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "custom_date": "⏁⏺ā¸Ģā¸™ā¸”ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāš€ā¸­ā¸‡", "custom_locale": "⏁⏺ā¸Ģā¸™ā¸”ā¸ ā¸˛ā¸Šā¸˛ā¸—āš‰ā¸­ā¸‡ā¸–ā¸´āšˆā¸™āš€ā¸­ā¸‡", "custom_locale_description": "āšƒā¸Šāš‰ā¸Ŗā¸šā¸›āšā¸šā¸šā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ āš€ā¸§ā¸Ĩ⏞ āšā¸Ĩā¸°ā¸•ā¸ąā¸§āš€ā¸Ĩā¸‚ā¸ˆā¸˛ā¸ā¸ ā¸˛ā¸Šā¸˛āšā¸Ĩā¸°ā¸ ā¸šā¸Ąā¸´ā¸ ā¸˛ā¸„ā¸—ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁", + "custom_url": "URL ⏁⏺ā¸Ģā¸™ā¸”āš€ā¸­ā¸‡", "cutoff_date_description": "āš€ā¸āš‡ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸ˆā¸˛ā¸ā¸Šāšˆā¸§ā¸‡āš€ā¸§ā¸Ĩ⏞â€Ļ", "cutoff_day": "{count, plural, one {ā¸§ā¸ąā¸™} other {ā¸§ā¸ąā¸™}}", "cutoff_year": "{count, plural, one {⏛ā¸ĩ} other {⏛ā¸ĩ}}", + "daily_title_text_date": "E dd MMM", + "daily_title_text_date_year": "E dd MMM yyyy", "dark": "ā¸Ąā¸ˇā¸”", - "dark_theme": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸›āš‡ā¸™ā¸˜ā¸ĩā¸Ąā¸Ēā¸ĩā¸Ąā¸ˇā¸”", + "dark_theme": "āš€ā¸›ā¸´ā¸”-ā¸›ā¸´ā¸”ā¸˜ā¸ĩā¸Ąā¸Ēā¸ĩā¸Ąā¸ˇā¸”", "date": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", "date_after": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸Ģā¸Ĩā¸ąā¸‡ā¸ˆā¸˛ā¸", "date_and_time": "ā¸§ā¸ąā¸™āšā¸Ĩā¸°āš€ā¸§ā¸Ĩ⏞", "date_before": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸āšˆā¸­ā¸™", - "date_of_birth": "ā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”", + "date_format": "E d LLL y â€ĸ hh:mm", "date_of_birth_saved": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸāšā¸Ĩāš‰ā¸§", "date_range": "ā¸Šāšˆā¸§ā¸‡ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", - "date_time_original": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ/āš€ā¸§ā¸Ĩā¸˛ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", "day": "ā¸§ā¸ąā¸™", "days": "ā¸§ā¸ąā¸™", "deduplicate_all": "ā¸Ŗā¸§ā¸Ąāš€ā¸‚āš‰ā¸˛ā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", - "default_quality_subtitle": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸ˆā¸°āšƒā¸Šāš‰āš€ā¸Ąā¸ˇāšˆā¸­āšā¸•ā¸°ā¸›ā¸¸āšˆā¸Ąāšā¸Šā¸ŖāšŒ ā¸ā¸”ā¸„āš‰ā¸˛ā¸‡ā¸—ā¸ĩāšˆā¸›ā¸¸āšˆā¸Ąāšā¸Šā¸ŖāšŒāš€ā¸žā¸ˇāšˆā¸­āš€ā¸Ĩā¸ˇā¸­ā¸ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žāšƒā¸™āšā¸•āšˆā¸Ĩā¸°ā¸„ā¸Ŗā¸ąāš‰ā¸‡", - "default_share_quality": "ā¸„āšˆā¸˛āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒ", "delete": "ā¸Ĩ⏚⏭⏭⏁", - "delete_action_confirmation_message": "ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ⏁⏞⏪ā¸Ĩ⏚⏈⏰ā¸ĸāš‰ā¸˛ā¸ĸā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°ā¸‚ā¸­ā¸‡āš€ā¸‹ā¸Ŗā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāšā¸Ĩā¸°ā¸ˆā¸°ā¸–ā¸˛ā¸Ąā¸„ā¸¸ā¸“ā¸–āš‰ā¸˛ā¸„ā¸¸ā¸“ā¸­ā¸ĸ⏞⏁ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "delete_action_prompt": "ā¸Ĩ⏚ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāšā¸Ĩāš‰ā¸§", "delete_album": "ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "delete_api_key_prompt": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸„ā¸ĩā¸ĸāšŒ API ⏙ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", + "delete_api_key_prompt": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩ⏚ API ⏄ā¸ĩā¸ĸāšŒ ⏙ā¸ĩāš‰āšƒā¸Šāšˆāš„ā¸Ģā¸Ą ?", "delete_dialog_alert": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸”ā¸ąā¸‡ā¸ā¸Ĩāšˆā¸˛ā¸§ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩ⏚⏈⏞⏁ Immich āšā¸Ĩā¸°āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", "delete_dialog_alert_local": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸”ā¸ąā¸‡ā¸ā¸Ĩāšˆā¸˛ā¸§ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸ˆā¸˛ā¸āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ āšā¸•āšˆā¸ˆā¸°ā¸ĸā¸ąā¸‡ā¸„ā¸‡ā¸­ā¸ĸā¸šāšˆā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ Immich", "delete_dialog_alert_local_non_backed_up": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸šā¸˛ā¸‡ā¸•ā¸ąā¸§āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸–ā¸šā¸ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸šā¸™ Immich āšā¸Ĩā¸°ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸ˆā¸˛ā¸āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", + "delete_dialog_alert_remote": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸”ā¸ąā¸‡ā¸ā¸Ĩāšˆā¸˛ā¸§ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸ˆā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ Immich ⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", + "delete_dialog_ok_force": "ā¸Ĩā¸šā¸•āšˆā¸­āš„ā¸›", "delete_dialog_title": "ā¸Ĩā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "delete_duplicates_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸—ā¸ĩāšˆā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩ⏚⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸‹āš‰ā¸ŗā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗāšƒā¸Šāšˆāš„ā¸Ģā¸Ą ?", "delete_face": "ā¸Ĩā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛", @@ -858,16 +891,21 @@ "delete_tag_confirmation_prompt": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šāšā¸—āš‡ā¸ {tagName} āšƒā¸Šāšˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "delete_user": "ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰", "deleted_shared_link": "ā¸Ĩ⏚ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāšā¸Ĩāš‰ā¸§", + "deletes_missing_assets": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸Ģ⏞ā¸ĸāš„ā¸›ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸”ā¸´ā¸Ēā¸–āšŒ", "description": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", + "description_input_hint_text": "āš€ā¸žā¸ˇāšˆā¸Ąā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔...", + "description_input_submit_error": "ā¸­ā¸ąā¸žāš€ā¸”ā¸•ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ log āš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą", "deselect_all": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "details": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", "direction": "āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡", "disable": "ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "disabled": "ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", + "disallow_edits": "āš„ā¸Ąāšˆā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰āšā¸āš‰āš„ā¸‚", "discord": "⏔⏴ā¸Ēā¸„ā¸­ā¸ŖāšŒā¸”", "discover": "ā¸„āš‰ā¸™ā¸žā¸š", "discovered_devices": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ", "dismiss_all_errors": "ā¸›ā¸ā¸´āš€ā¸Ēā¸˜ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "dismiss_error": "ā¸›ā¸ā¸´āš€ā¸Ēā¸˜ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", "display_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡ā¸œā¸Ĩ", "display_order": "ā¸Ĩā¸ŗā¸”ā¸ąā¸šā¸ā¸˛ā¸Ŗāšā¸Ēā¸”ā¸‡ā¸œā¸Ĩ", "display_original_photos": "āšā¸Ēā¸”ā¸‡ā¸ ā¸˛ā¸žā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", @@ -876,9 +914,11 @@ "documentation": "āš€ā¸­ā¸ā¸Ēā¸˛ā¸Ŗā¸›ā¸Ŗā¸°ā¸ā¸­ā¸š", "done": "āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™", "download": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔", + "download_action_prompt": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ {count} ā¸Šā¸´āš‰ā¸™", "download_canceled": "ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ā¸ĸā¸āš€ā¸Ĩ⏴⏁", "download_complete": "ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™", "download_enqueue": "ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔⏭ā¸ĸā¸šāšˆāšƒā¸™ā¸„ā¸´ā¸§", + "download_error": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", "download_failed": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "download_finished": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™", "download_include_embedded_motion_videos": "ā¸Ŗā¸§ā¸Ąā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸ā¸ąā¸‡ā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸ ā¸˛ā¸žāš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģ⏧", @@ -888,15 +928,15 @@ "download_paused": "ā¸Ģā¸ĸā¸¸ā¸”ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”ā¸Šā¸ąāšˆā¸§ā¸„ā¸Ŗā¸˛ā¸§", "download_settings": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔", "download_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔", + "download_started": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸ā¸˛ā¸Ŗā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔", + "download_sucess": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "download_sucess_android": "ā¸Ēā¸ˇāšˆā¸­ā¸–ā¸šā¸ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš„ā¸›ā¸ĸā¸ąā¸‡ DCIM/Immich", "download_waiting_to_retry": "⏪⏭ā¸Ĩā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ", "downloading": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔", "downloading_asset_filename": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ {filename}", "downloading_from_icloud": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”ā¸ˆā¸˛ā¸āš„ā¸­ā¸„ā¸Ĩ⏞⏧", "downloading_media": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", - "drag_to_reorder": "ā¸Ĩā¸˛ā¸āš€ā¸žā¸ˇāšˆā¸­ā¸ˆā¸ąā¸”ā¸Ĩā¸ŗā¸”ā¸ąā¸š", "drop_files_to_upload": "ā¸§ā¸˛ā¸‡āš„ā¸Ÿā¸ĨāšŒāšƒā¸™ā¸Šāšˆā¸­ā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", - "duplicate": "⏗⏺ā¸Ēā¸ŗāš€ā¸™ā¸˛", - "duplicate_workflow": "⏗⏺ā¸Ēā¸ŗāš€ā¸™ā¸˛ Workflow", "duplicates": "⏪⏞ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™", "duplicates_description": "āšā¸āš‰āš„ā¸‚āšā¸•āšˆā¸Ĩ⏰⏁ā¸Ĩā¸¸āšˆā¸Ąāš‚ā¸”ā¸ĸā¸Ŗā¸°ā¸šā¸¸ā¸§āšˆā¸˛ā¸ā¸Ĩā¸¸āšˆā¸Ąāšƒā¸”ā¸‹āš‰ā¸ŗā¸ā¸ąā¸™ā¸Ģā¸˛ā¸ā¸Ąā¸ĩ", "duration": "⏪⏰ā¸ĸā¸°āš€ā¸§ā¸Ĩ⏞", @@ -907,8 +947,9 @@ "edit_date": "āšā¸āš‰āš„ā¸‚ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", "edit_date_and_time": "āšā¸āš‰āš„ā¸‚ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšā¸Ĩā¸°āš€ā¸§ā¸Ĩ⏞", "edit_date_and_time_action_prompt": "āšā¸āš‰āš„ā¸‚ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšā¸Ĩā¸°āš€ā¸§ā¸Ĩ⏞ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāšā¸Ĩāš‰ā¸§", - "edit_date_and_time_by_offset": "ā¸ĸāš‰ā¸˛ā¸ĸā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāš„ā¸›ā¸•ā¸˛ā¸Ąā¸›ā¸Ŗā¸´ā¸Ąā¸˛ā¸“", + "edit_date_and_time_by_offset_interval": "ā¸Šāšˆā¸§ā¸‡ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšƒā¸Ģā¸Ąāšˆ: {from} - {to}", "edit_description": "āšā¸āš‰āš„ā¸‚ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", + "edit_description_prompt": "āš‚ā¸›ā¸Ŗā¸”āš€ā¸Ĩā¸ˇāšˆā¸­ā¸ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸāšƒā¸Ģā¸Ąāšˆ", "edit_exclusion_pattern": "āšā¸āš‰āš„ā¸‚ā¸‚āš‰ā¸­ā¸ĸā¸āš€ā¸§āš‰ā¸™", "edit_faces": "āšā¸āš‰āš„ā¸‚ā¸Ģā¸™āš‰ā¸˛", "edit_key": "āšā¸āš‰āš„ā¸‚ā¸ā¸¸ā¸āšā¸ˆ", @@ -923,6 +964,9 @@ "edit_user": "āšā¸āš‰āš„ā¸‚ā¸œā¸šāš‰āšƒā¸Šāš‰", "edit_workflow": "āšā¸āš‰āš„ā¸‚ā¸ā¸Ŗā¸°ā¸šā¸§ā¸™ā¸ā¸˛ā¸Ŗā¸‡ā¸˛ā¸™", "editor": "ā¸œā¸šāš‰āšā¸āš‰āš„ā¸‚", + "editor_close_without_save_prompt": "ā¸ā¸˛ā¸Ŗāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩ⏇⏙ā¸ĩāš‰ā¸ˆā¸°āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸šā¸ąā¸™ā¸—ā¸ļ⏁", + "editor_close_without_save_title": "ā¸›ā¸´ā¸”āš‚ā¸›ā¸Ŗāšā¸ā¸Ŗā¸Ąāšā¸āš‰āš„ā¸‚?", + "editor_confirm_reset_all_changes": "āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸ˆā¸°ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "editor_discard_edits_confirm": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚", "editor_discard_edits_prompt": "ā¸„ā¸¸ā¸“ā¸Ąā¸ĩā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚ā¸—ā¸ĩāšˆā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸šā¸ąā¸™ā¸—ā¸ļ⏁ āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸ˆā¸°ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "editor_discard_edits_title": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚", @@ -930,8 +974,6 @@ "editor_edits_applied_success": "ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚ā¸–ā¸šā¸ā¸šā¸ąā¸™ā¸—ā¸ļ⏁ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆāšā¸Ĩāš‰ā¸§", "editor_flip_horizontal": "⏁ā¸Ĩā¸ąā¸šā¸”āš‰ā¸˛ā¸™ā¸—ā¸˛ā¸‡āšā¸™ā¸§ā¸™ā¸­ā¸™", "editor_flip_vertical": "⏁ā¸Ĩā¸ąā¸šā¸”āš‰ā¸˛ā¸™ā¸—ā¸˛ā¸‡āšā¸™ā¸§ā¸•ā¸ąāš‰ā¸‡", - "editor_handle_corner": "⏗ā¸ĩāšˆā¸ˆā¸ąā¸šā¸•ā¸Ŗā¸‡ā¸Ąā¸¸ā¸Ą{corner, select, top_left {ā¸šā¸™ā¸‹āš‰ā¸˛ā¸ĸ} top_right {ā¸šā¸™ā¸‚ā¸§ā¸˛} bottom_left {ā¸Ĩāšˆā¸˛ā¸‡ā¸‹āš‰ā¸˛ā¸ĸ} bottom_right {ā¸Ĩāšˆā¸˛ā¸‡ā¸‚ā¸§ā¸˛} other {}}", - "editor_handle_edge": "⏗ā¸ĩāšˆā¸ˆā¸ąā¸šā¸•ā¸Ŗā¸‡ā¸‚ā¸­ā¸š{edge, select, top {ā¸šā¸™} bottom {ā¸Ĩāšˆā¸˛ā¸‡} left {ā¸‹āš‰ā¸˛ā¸ĸ} right {⏂⏧⏞} other {}}", "editor_orientation": "ā¸ā¸˛ā¸Ŗā¸§ā¸˛ā¸‡āšā¸™ā¸§", "editor_reset_all_changes": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāšā¸āš‰āš„ā¸‚", "editor_rotate_left": "ā¸Ģā¸Ąā¸¸ā¸™ 90 ā¸­ā¸‡ā¸¨ā¸˛ā¸—ā¸§ā¸™āš€ā¸‚āš‡ā¸Ąā¸™ā¸˛ā¸Ŧ⏴⏁⏞", @@ -940,7 +982,7 @@ "email_notifications": "āšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ā¸œāšˆā¸˛ā¸™ā¸­ā¸ĩāš€ā¸Ąā¸Ĩ", "empty_folder": "āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸™ā¸ĩāš‰ā¸§āšˆā¸˛ā¸‡āš€ā¸›ā¸Ĩāšˆā¸˛", "empty_trash": "ā¸—ā¸´āš‰ā¸‡ā¸ˆā¸˛ā¸ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", - "empty_trash_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩāš‰ā¸˛ā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰⏭⏭⏁⏈⏞⏁ Immich ⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ\nā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰āš„ā¸”āš‰!", + "empty_trash_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩāš‰ā¸˛ā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Ĩā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰⏭⏭⏁⏈⏞⏁ Immich ⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ\nā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰āš„ā¸”āš‰!", "enable": "āš€ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "enable_backup": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "enable_biometric_auth_description": "āšƒā¸Ēāšˆā¸žā¸´ā¸™āš€ā¸žā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒāš€ā¸žā¸ˇāšˆā¸­ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", @@ -951,7 +993,9 @@ "enter_your_pin_code": "āšƒā¸Ēāšˆā¸žā¸´ā¸™āš‚ā¸„āš‰ā¸”", "enter_your_pin_code_subtitle": "āšƒā¸Ēāšˆā¸žā¸´ā¸™āš‚ā¸„āš‰ā¸”āš€ā¸žā¸ˇāšˆā¸­āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„", "error": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", + "error_change_sort_album": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸ā¸˛ā¸Ŗāš€ā¸Ŗā¸ĩā¸ĸ⏇ā¸Ĩā¸ŗā¸”ā¸ąā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "error_delete_face": "āš€ā¸ā¸´ā¸”āš€ā¸­ā¸­āš€ā¸Ŗā¸­ā¸ŖāšŒ āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸­ā¸­ā¸āš„ā¸”āš‰", + "error_getting_places": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏂⏓⏰⏔ā¸ļ⏇ā¸Ē⏖⏞⏙⏗ā¸ĩāšˆ", "error_loading_albums": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸‚ā¸“ā¸°āš‚ā¸Ģā¸Ĩā¸”ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "error_loading_image": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏪⏰ā¸Ģā¸§āšˆā¸˛ā¸‡āš‚ā¸Ģā¸Ĩā¸”ā¸ ā¸˛ā¸ž", "error_loading_partners": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸‚ā¸“ā¸°āš‚ā¸Ģā¸Ĩā¸”ā¸„ā¸šāšˆā¸Ģā¸š: {error}", @@ -964,7 +1008,7 @@ "cannot_navigate_next_asset": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡āš„ā¸”āš‰", "cannot_navigate_previous_asset": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸Ēāš‰ā¸™ā¸—ā¸˛ā¸‡ā¸āšˆā¸­ā¸™ā¸Ģā¸™āš‰ā¸˛āš„ā¸”āš‰", "cant_apply_changes": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšƒā¸™ā¸ā¸˛ā¸Ŗāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩ⏇", - "cant_change_activity": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–{enabled, select, true {⏛⏴⏔} other {āš€ā¸›ā¸´ā¸”}}ā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ąāš„ā¸”āš‰", + "cant_change_activity": "Can't {enabled, select, true {disable} other {enable}} activity", "cant_change_asset_favorite": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸Šā¸ˇāšˆā¸™ā¸Šā¸­ā¸šāš„ā¸”āš‰", "cant_change_metadata_assets_count": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰āš„ā¸‚ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ metadata ⏂⏭⏇ {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­} other {# ā¸Ēā¸ˇāšˆā¸­}}", "cant_get_faces": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšƒā¸™ā¸ā¸˛ā¸Ŗāš€ā¸Ŗā¸ĩā¸ĸā¸ā¸”ā¸šāšƒā¸šā¸Ģā¸™āš‰ā¸˛", @@ -990,13 +1034,11 @@ "failed_to_load_people": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš‚ā¸Ģā¸Ĩā¸”ā¸šā¸¸ā¸„ā¸„ā¸Ĩāš„ā¸”āš‰", "failed_to_remove_product_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚ product key āš„ā¸”āš‰", "failed_to_reset_pin_code": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ē PIN", - "failed_to_stack_assets": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸ŗā¸Ēā¸ˇāšˆā¸­āšƒā¸Ģāš‰āš€ā¸›āš‡ā¸™ Stack āš„ā¸”āš‰", - "failed_to_tag_assets": "ā¸ā¸˛ā¸Ŗāšā¸—āš‡ā¸ā¸Ēā¸ˇāšˆā¸­ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", - "failed_to_unstack_assets": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸•ā¸ā¸Ēā¸ˇāšˆā¸­ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ Stack āš„ā¸”āš‰", - "failed_to_update_notification_status": "ā¸­ā¸ąā¸›āš€ā¸”ā¸—ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "failed_to_stack_assets": "Failed to stack assets", + "failed_to_unstack_assets": "Failed to un-stack assets", + "failed_to_update_notification_status": "ā¸­ā¸ąā¸žāš€ā¸”ā¸—ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "incorrect_email_or_password": "⏭ā¸ĩāš€ā¸Ąā¸Ĩā¸Ģ⏪⏎⏭⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", - "library_folder_already_exists": "ā¸Ąā¸ĩā¸žā¸˛ā¸˜ā¸—ā¸ĩāšˆā¸Šā¸ĩāš‰āš„ā¸›āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩāšˆā¸™ā¸ĩāš‰ā¸­ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", - "page_not_found": "āš„ā¸Ąāšˆā¸žā¸šā¸Ģā¸™āš‰ā¸˛", + "page_not_found": "āš„ā¸Ąāšˆā¸žā¸šā¸Ģā¸™āš‰ā¸˛ :/", "paths_validation_failed": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ {paths, plural, one {# path} other {# paths}} ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "profile_picture_transparent_pixels": "ā¸Ŗā¸šā¸›āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ąā¸ĩā¸žā¸´ā¸āš€ā¸‹ā¸Ĩāš‚ā¸›ā¸Ŗāšˆā¸‡āšƒā¸Ēāš„ā¸”āš‰ āš‚ā¸›ā¸Ŗā¸”ā¸‹ā¸šā¸Ąāš€ā¸‚āš‰ā¸˛āšā¸Ĩ⏰/ā¸Ģ⏪⏎⏭ā¸ĸāš‰ā¸˛ā¸ĸā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "quota_higher_than_disk_size": "ā¸„ā¸¸ā¸“ā¸•ā¸ąāš‰ā¸‡āš‚ā¸„ā¸§ā¸•ā¸˛āš„ā¸§āš‰ā¸Ēā¸šā¸‡ā¸ā¸§āšˆā¸˛ā¸‚ā¸™ā¸˛ā¸”ā¸”ā¸´ā¸Ēā¸āšŒ", @@ -1019,9 +1061,8 @@ "unable_to_complete_oauth_login": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸ŗā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸š OAuth āšƒā¸Ģāš‰āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒāš„ā¸”āš‰", "unable_to_connect": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­āš„ā¸”āš‰", "unable_to_copy_to_clipboard": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”āš„ā¸”āš‰ ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāšƒā¸Ģāš‰āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸„ā¸¸ā¸“āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇ā¸Ģā¸™āš‰ā¸˛ā¸œāšˆā¸˛ā¸™ā¸—ā¸˛ā¸‡ https", - "unable_to_create": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ Workflow ⏙ā¸ĩāš‰āš„ā¸”āš‰", "unable_to_create_admin_account": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸šā¸ąā¸ā¸Šā¸ĩā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩā¸Ŗā¸°ā¸šā¸šāš„ā¸”āš‰", - "unable_to_create_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸ĩā¸ĸāšŒ API", + "unable_to_create_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ API ⏄ā¸ĩā¸ĸāšŒ āš„ā¸”āš‰", "unable_to_create_library": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žāš„ā¸”āš‰", "unable_to_create_user": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", "unable_to_delete_album": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰", @@ -1030,7 +1071,6 @@ "unable_to_delete_exclusion_pattern": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸Ŗā¸šā¸›āšā¸šā¸šā¸—ā¸ĩāšˆā¸ĸā¸āš€ā¸§āš‰ā¸™", "unable_to_delete_shared_link": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāš„ā¸”āš‰", "unable_to_delete_user": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", - "unable_to_delete_workflow": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚ Workflow ⏙ā¸ĩāš‰āš„ā¸”āš‰", "unable_to_download_files": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš„ā¸Ÿā¸ĨāšŒāš„ā¸”āš‰", "unable_to_edit_exclusion_pattern": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰āš„ā¸‚ā¸Ŗā¸šā¸›āšā¸šā¸šā¸ĸā¸āš€ā¸§āš‰ā¸™āš„ā¸”āš‰", "unable_to_empty_trash": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āš„ā¸”āš‰", @@ -1049,7 +1089,7 @@ "unable_to_reassign_assets_new_person": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ąā¸­ā¸šā¸Ģā¸Ąā¸˛ā¸ĸ āšƒā¸Ģāš‰ā¸ā¸ąā¸šā¸šā¸¸ā¸„ā¸„ā¸Ĩāšƒā¸Ģā¸Ąāšˆāš„ā¸”āš‰", "unable_to_refresh_user": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", "unable_to_remove_album_users": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰", - "unable_to_remove_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸„ā¸ĩā¸ĸāšŒ API", + "unable_to_remove_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚ API Key āš„ā¸”āš‰", "unable_to_remove_assets_from_shared_link": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚⏭⏭⏁⏈⏞⏁ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāš„ā¸”āš‰", "unable_to_remove_library": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žāš„ā¸”āš‰", "unable_to_remove_partner": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šā¸„ā¸šāšˆā¸Ģā¸šāš„ā¸”āš‰", @@ -1061,7 +1101,7 @@ "unable_to_restore_trash": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ŗā¸ĩā¸ĸā¸ā¸„ā¸ˇā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āš„ā¸”āš‰", "unable_to_restore_user": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ŗā¸ĩā¸ĸā¸ā¸„ā¸ˇā¸™ā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", "unable_to_save_album": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰", - "unable_to_save_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļ⏁⏄ā¸ĩā¸ĸāšŒ API", + "unable_to_save_api_key": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļ⏁ API ⏄ā¸ĩā¸ĸāšŒ āš„ā¸”āš‰", "unable_to_save_date_of_birth": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸§ā¸ąā¸™āš€ā¸ā¸´ā¸”āš„ā¸”āš‰", "unable_to_save_name": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸Šā¸ˇāšˆā¸­āš„ā¸”āš‰", "unable_to_save_profile": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸šā¸ąā¸™ā¸—ā¸ļā¸āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒāš„ā¸”āš‰", @@ -1070,7 +1110,6 @@ "unable_to_scan_library": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēāšā¸ā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žāš„ā¸”āš‰", "unable_to_set_feature_photo": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸ąāš‰ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš€ā¸›āš‡ā¸™ Feature āš„ā¸”āš‰", "unable_to_set_profile_picture": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸ąāš‰ā¸‡ā¸ ā¸˛ā¸žāš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒāš„ā¸”āš‰", - "unable_to_set_rating": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšƒā¸Ģāš‰ā¸„ā¸°āšā¸™ā¸™āš„ā¸”āš‰", "unable_to_submit_job": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēāšˆā¸‡ā¸‡ā¸˛ā¸™āš„ā¸”āš‰", "unable_to_trash_asset": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸´āš‰ā¸‡ā¸Ēā¸ˇāšˆā¸­āš„ā¸”āš‰", "unable_to_unlink_account": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąāš‚ā¸ĸā¸‡ā¸šā¸ąā¸ā¸Šā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", @@ -1082,18 +1121,24 @@ "unable_to_update_settings": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āš„ā¸”āš‰", "unable_to_update_timeline_display_status": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰āš„ā¸‚ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸ā¸˛ā¸Ŗāšā¸Ē⏔⏇ā¸Ĩā¸ŗā¸”ā¸ąā¸šāš€ā¸§ā¸Ĩā¸˛āš„ā¸”āš‰", "unable_to_update_user": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸œā¸šāš‰āšƒā¸Šāš‰āš„ā¸”āš‰", - "unable_to_update_workflow": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰āš„ā¸‚ Workflow ⏙ā¸ĩāš‰āš„ā¸”āš‰", "unable_to_upload_file": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸”āš‰" }, "errors_text": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", - "exclusion_pattern": "ā¸Ŗā¸šā¸›āšā¸šā¸šā¸ā¸˛ā¸Ŗā¸•ā¸ąā¸”ā¸­ā¸­ā¸", "exif": "Exif", "exif_bottom_sheet_description": "āš€ā¸žā¸´āšˆā¸Ąā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", "exif_bottom_sheet_description_error": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸‚ā¸“ā¸°ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", + "exif_bottom_sheet_details": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", + "exif_bottom_sheet_location": "ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", "exif_bottom_sheet_no_description": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", + "exif_bottom_sheet_people": "⏄⏙", + "exif_bottom_sheet_person_add_person": "āš€ā¸žā¸´āšˆā¸Ąā¸Šā¸ˇāšˆā¸­", "exit_slideshow": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸ā¸˛ā¸Ŗā¸™ā¸ŗāš€ā¸Ē⏙⏭", "expand": "⏂ā¸ĸ⏞ā¸ĸ", "expand_all": "⏂ā¸ĸ⏞ā¸ĸā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "experimental_settings_new_asset_list_subtitle": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸žā¸ąā¸’ā¸™ā¸˛", + "experimental_settings_new_asset_list_title": "āš€ā¸›ā¸´ā¸”ā¸•ā¸˛ā¸Ŗā¸˛ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸ā¸ŗā¸Ĩā¸ąā¸‡ā¸—ā¸”ā¸Ĩ⏭⏇", + "experimental_settings_subtitle": "āšƒā¸Šāš‰ā¸ ā¸˛ā¸ĸāšƒā¸•āš‰ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ēā¸ĩāšˆā¸ĸā¸‡ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš€ā¸­ā¸‡!", + "experimental_settings_title": "⏗⏔ā¸Ĩ⏭⏇", "expire_after": "ā¸Ģā¸Ąā¸”ā¸­ā¸˛ā¸ĸ⏏ā¸Ģā¸Ĩā¸ąā¸‡ā¸ˆā¸˛ā¸", "expired": "ā¸Ģā¸Ąā¸”ā¸­ā¸˛ā¸ĸā¸¸āšā¸Ĩāš‰ā¸§", "expires_date": "ā¸Ģā¸Ąā¸”ā¸­ā¸˛ā¸ĸā¸¸ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", @@ -1103,40 +1148,38 @@ "export_as_json": "ā¸Ēāšˆā¸‡ā¸­ā¸­ā¸āš€ā¸›āš‡ā¸™āš„ā¸Ÿā¸ĨāšŒ JSON", "export_database": "ā¸Ēāšˆā¸‡ā¸­ā¸­ā¸ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "export_database_description": "ā¸Ēāšˆā¸‡ā¸­ā¸­ā¸ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ SQLite", - "exposure_time": "āš€ā¸§ā¸Ĩā¸˛āš€ā¸›ā¸´ā¸”ā¸Ŗā¸ąā¸šāšā¸Ē⏇", "extension": "ā¸Ēāšˆā¸§ā¸™ā¸•āšˆā¸­ā¸‚ā¸ĸ⏞ā¸ĸ", "external": "⏠⏞ā¸ĸ⏙⏭⏁", "external_libraries": "⏄ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸ ā¸˛ā¸ĸ⏙⏭⏁", "external_network": "ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸ ā¸˛ā¸ĸ⏙⏭⏁", "external_network_sheet_info": "āš€ā¸Ąā¸ˇāšˆā¸­āš„ā¸Ąāšˆāš„ā¸”āš‰āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ Wi-Fi ⏗ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āš„ā¸§āš‰ āšā¸­ā¸›ā¸ˆā¸°āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸œāšˆā¸˛ā¸™ URL ā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡ā¸—ā¸ĩāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš„ā¸”āš‰ā¸•ā¸˛ā¸Ąā¸Ĩā¸ŗā¸”ā¸ąā¸šā¸ˆā¸˛ā¸ā¸šā¸™ā¸Ĩ⏇ā¸Ĩāšˆā¸˛ā¸‡", - "f_number": "āš€ā¸Ĩ⏂ F", "face_unassigned": "āš„ā¸Ąāšˆā¸ā¸ŗā¸Ģā¸™ā¸”ā¸Ąā¸­ā¸šā¸Ģā¸Ąā¸˛ā¸ĸ", "failed": "ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "failed_count": "āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ: {count}", "failed_to_authenticate": "⏁⏞⏪ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸•ā¸™āš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", - "failed_to_delete_file": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩā¸šāš„ā¸Ÿā¸ĨāšŒāš„ā¸”āš‰", "failed_to_load_assets": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšƒā¸™ā¸ā¸˛ā¸Ŗāš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", "failed_to_load_folder": "āš‚ā¸Ģā¸Ĩā¸”āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒāš„ā¸Ąāšˆā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "favorite": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", "favorite_action_prompt": "āš€ā¸žā¸´āšˆā¸Ą {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš„ā¸›ā¸ĸā¸ąā¸‡ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", "favorite_or_unfavorite_photo": "āš‚ā¸›ā¸Ŗā¸”ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆāš‚ā¸›ā¸Ŗā¸”ā¸ ā¸˛ā¸ž", "favorites": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", + "favorites_page_no_favorites": "āš„ā¸Ąāšˆā¸žā¸šā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸™ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", "feature_photo_updated": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•ā¸ ā¸˛ā¸žāš€ā¸”āšˆā¸™āšā¸Ĩāš‰ā¸§", "features": "⏟ā¸ĩāš€ā¸ˆā¸­ā¸ŖāšŒ", + "features_in_development": "⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆā¸Ŗā¸°ā¸Ģā¸§āšˆā¸˛ā¸‡ā¸ā¸˛ā¸Ŗā¸žā¸ąā¸’ā¸™ā¸˛", "features_setting_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ÿā¸ĩāš€ā¸ˆā¸­ā¸ŖāšŒāšā¸­ā¸›", "file_name_or_extension": "ā¸™ā¸˛ā¸Ąā¸Ē⏁⏏ā¸Ĩā¸Ģā¸Ŗā¸ˇā¸­ā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒ", "file_name_text": "ā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒ", + "file_name_with_value": "ā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒ: {file_name}", "file_size": "ā¸‚ā¸™ā¸˛ā¸”āš„ā¸Ÿā¸ĨāšŒ", "filename": "ā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒ", "filetype": "ā¸Šā¸™ā¸´ā¸”āš„ā¸Ÿā¸ĨāšŒ", "filter": "ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", "filter_people": "ā¸ā¸Ŗā¸­ā¸‡ā¸œā¸šāš‰ā¸„ā¸™", "filter_places": "⏁⏪⏭⏇ā¸Ē⏖⏞⏙⏗ā¸ĩāšˆ", - "filter_tags": "ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡āšā¸—āš‡ā¸", "filters": "ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", - "first": "āšā¸Ŗā¸ā¸Ē⏏⏔", + "find_them_fast": "ā¸„āš‰ā¸™ā¸Ģā¸˛āš‚ā¸”ā¸ĸā¸Šā¸ˇāšˆā¸­ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ŗā¸§ā¸”āš€ā¸Ŗāš‡ā¸§", "fix_incorrect_match": "āšā¸āš‰āš„ā¸‚ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸šā¸„ā¸šāšˆā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", - "focal_length": "⏪⏰ā¸ĸā¸°āš‚ā¸Ÿā¸ā¸ąā¸Ē", "folder": "āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", "folder_not_found": "āš„ā¸Ąāšˆā¸žā¸šāš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", "folders": "āš‚ā¸Ÿā¸ĨāšŒāš€ā¸”ā¸­ā¸ŖāšŒ", @@ -1146,15 +1189,13 @@ "free_up_space": "āš€ā¸žā¸´āšˆā¸Ąā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸§āšˆā¸˛ā¸‡", "free_up_space_description": "āš€ā¸žā¸´āšˆā¸Ąā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸§āšˆā¸˛ā¸‡āš‚ā¸”ā¸ĸ⏁⏞⏪ā¸ĸāš‰ā¸˛ā¸ĸā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸Ĩāš‰ā¸§āš„ā¸›ā¸ĸā¸ąā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°ā¸‚ā¸­ā¸‡ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ ā¸Ēā¸ŗāš€ā¸™ā¸˛ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸ĸā¸ąā¸‡ā¸„ā¸‡ā¸­ā¸ĸā¸šāšˆā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸›ā¸Ĩā¸­ā¸”ā¸ ā¸ąā¸ĸ", "free_up_space_settings_subtitle": "āš€ā¸žā¸´āšˆā¸Ąā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ", - "full_path": "ā¸žā¸˛ā¸˜āš€ā¸•āš‡ā¸Ą: {path}", - "full_path_or_folder": "ā¸žā¸˛ā¸˜āš€ā¸•āš‡ā¸Ąā¸Ģā¸Ŗā¸ˇā¸­āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", "gcast_enabled": "Google Cast", "gcast_enabled_description": "⏟ā¸ĩāš€ā¸ˆā¸­ā¸ŖāšŒā¸™ā¸ĩāš‰ā¸•āš‰ā¸­ā¸‡āš‚ā¸Ģā¸Ĩā¸”ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏈⏞⏁ Google āš€ā¸žā¸ˇāšˆā¸­ā¸—ā¸ŗā¸‡ā¸˛ā¸™", "general": "ā¸—ā¸ąāšˆā¸§āš„ā¸›", "geolocation_instruction_location": "⏄ā¸Ĩā¸´ā¸ā¸šā¸™ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸žā¸´ā¸ā¸ąā¸” GPS āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Šāš‰ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸™ā¸ąāš‰ā¸™ ā¸Ģā¸Ŗā¸ˇā¸­āš€ā¸Ĩā¸ˇā¸­ā¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸ˆā¸˛ā¸āšā¸œā¸™ā¸—ā¸ĩāšˆāš‚ā¸”ā¸ĸ⏕⏪⏇", "get_help": "ā¸‚ā¸­ā¸„ā¸§ā¸˛ā¸Ąā¸Šāšˆā¸§ā¸ĸāš€ā¸Ģā¸Ĩ⏎⏭", "get_people_error": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏂⏓⏰⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸œā¸šāš‰ā¸„ā¸™", - "get_wifiname_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸ąā¸šā¸Šā¸ˇāšˆā¸­ Wi-Fi ⏁⏪⏏⏓⏞ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸ā¸˛ā¸Ŗāšƒā¸Ģāš‰ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšā¸­ā¸› āšā¸Ĩ⏰ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸§āšˆā¸˛āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸ā¸ąā¸šāš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ Wi-Fi ⏭ā¸ĸā¸šāšˆ", + "get_wifiname_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸ąā¸šā¸Šā¸ˇāšˆā¸­ Wi-Fi ⏁⏪⏏⏓⏞ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸ā¸˛ā¸Ŗāšƒā¸Ģāš‰ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšā¸­ā¸ž āšā¸Ĩ⏰ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸§āšˆā¸˛ Wi-Fi āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸­ā¸ĸā¸šāšˆ", "getting_started": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "go_back": "⏁ā¸Ĩā¸ąā¸š", "go_to_folder": "āš„ā¸›ā¸—ā¸ĩāšˆāš‚ā¸Ÿā¸ĨāšŒāš€ā¸”ā¸­ā¸ŖāšŒ", @@ -1168,11 +1209,9 @@ "group_owner": "ā¸ˆā¸ąā¸”ā¸ā¸Ĩā¸¸āšˆā¸Ąāš‚ā¸”ā¸ĸāš€ā¸ˆāš‰ā¸˛ā¸‚ā¸­ā¸‡", "group_places_by": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸ⏇⏁ā¸Ĩā¸¸āšˆā¸Ąā¸‚ā¸­ā¸‡ā¸Ē⏖⏞⏙⏗ā¸ĩāšˆā¸”āš‰ā¸§ā¸ĸ⏁⏞⏪...", "group_year": "ā¸ˆā¸ąā¸”ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸•ā¸˛ā¸Ąā¸›ā¸ĩ", + "haptic_feedback_switch": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡āšā¸šā¸šā¸Ēā¸ąā¸Ąā¸œā¸ąā¸Ē", "haptic_feedback_title": "ā¸ā¸˛ā¸Ŗā¸•ā¸­ā¸šā¸Ēā¸™ā¸­ā¸‡āšā¸šā¸šā¸Ēā¸ąā¸Ąā¸œā¸ąā¸Ē", "has_quota": "āš€ā¸Ģā¸Ĩā¸ˇā¸­ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆ", - "hash_asset": "ā¸Ēā¸ˇāšˆā¸­ Hash", - "hashed_assets": "ā¸Ē⏎⏭⏗ā¸ĩāšˆ Hash āšā¸Ĩāš‰ā¸§", - "hashing": "⏁⏞⏪ Hash", "header_settings_add_header_tip": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēāšˆā¸§ā¸™ā¸Ģā¸ąā¸§", "header_settings_field_validator_msg": "ā¸„āšˆā¸˛ā¸•āš‰ā¸­ā¸‡āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸‡āš€ā¸›ā¸Ĩāšˆā¸˛", "header_settings_header_name_input": "ā¸Šā¸ˇāšˆā¸­ Header", @@ -1185,15 +1224,31 @@ "hide_named_person": "ā¸‹āšˆā¸­ā¸™ {name}", "hide_password": "ā¸‹āšˆā¸­ā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "hide_person": "ā¸‹āšˆā¸­ā¸™ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", - "hide_schema": "ā¸‹āšˆā¸­ā¸™ Schema", "hide_text_recognition": "ā¸‹āšˆā¸­ā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸ˆā¸ąā¸šā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ą", "hide_unnamed_people": "ā¸‹āšˆā¸­ā¸™ā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏗ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ŗā¸°ā¸šā¸¸ā¸Šā¸ˇāšˆā¸­", + "home_page_add_to_album_conflicts": "āš€ā¸žā¸´āšˆā¸Ą {added} ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą {album}. {failed} ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏭ā¸ĸā¸šāšˆāšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸­ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", + "home_page_add_to_album_err_local": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_add_to_album_success": "āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ {added} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą {album} āšā¸Ĩāš‰ā¸§", + "home_page_album_err_partner": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸šāšˆā¸Ģā¸šāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_archive_err_local": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_archive_err_partner": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸āš‡ā¸šā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸šāšˆā¸Ģā¸šāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", "home_page_building_timeline": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ timeline", + "home_page_delete_err_partner": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸šāšˆāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_delete_remote_err_local": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸Ĩ⏚⏈⏞⏁⏪ā¸ĩāš‚ā¸Ąā¸— ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_favorite_err_local": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸ąāš‰ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš€ā¸›āš‡ā¸™ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸” ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_favorite_err_partner": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸šāšˆā¸Ģā¸šāšƒā¸™ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_first_time_notice": "ā¸–āš‰ā¸˛ā¸„ā¸Ŗā¸ąāš‰ā¸‡ā¸™ā¸ĩāš‰āš€ā¸›āš‡ā¸™ā¸„ā¸Ŗā¸ąāš‰ā¸‡āšā¸Ŗā¸ā¸—ā¸ĩāšˆāšƒā¸Šāš‰āšā¸­ā¸›ā¸™ā¸ĩāš‰ ā¸ā¸Ŗā¸¸ā¸“ā¸˛āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸ˆā¸°āš„ā¸”āš‰āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "home_page_locked_error_local": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸˛ā¸ĸā¸Ēā¸ˇāšˆā¸­ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāš„ā¸›ā¸ĸā¸ąā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„ ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_locked_error_partner": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸žā¸´āšˆā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸šāšˆā¸Ģā¸šāš„ā¸›ā¸ĸā¸ąā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„āš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_share_err_local": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸Šā¸ŖāšŒā¸Ēā¸ˇāšˆā¸­āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸œāšˆā¸˛ā¸™ā¸Ĩā¸´ā¸‡ā¸āšŒāš„ā¸”āš‰ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "home_page_upload_err_limit": "ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩā¸”āš„ā¸”āš‰ā¸Ąā¸˛ā¸ā¸Ēā¸¸ā¸”ā¸„ā¸Ŗā¸ąāš‰ā¸‡ā¸Ĩ⏰ 30 ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", "host": "āš‚ā¸Žā¸Ēā¸•āšŒ", "hour": "ā¸Šā¸ąāšˆā¸§āš‚ā¸Ąā¸‡", "hours": "ā¸Šā¸ąāšˆā¸§āš‚ā¸Ąā¸‡", "id": "āš„ā¸­ā¸”ā¸ĩ", "idle": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸—ā¸ŗā¸‡ā¸˛ā¸™", + "ignore_icloud_photos": "ā¸‚āš‰ā¸˛ā¸Ąā¸ ā¸˛ā¸žā¸šā¸™ iCloud", + "ignore_icloud_photos_description": "ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸–ā¸šā¸āš€ā¸āš‡ā¸šā¸šā¸™ iCloud ā¸ˆā¸°āš„ā¸Ąāšˆā¸–ā¸šā¸ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩ⏔⏂ā¸ļāš‰ā¸™ Immich", "image": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "image_alt_text_date": "{isVideo, select, true {⏧⏴⏔ā¸ĩāš‚ā¸­} other {ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž}}⏗ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸāš€ā¸Ąā¸ˇāšˆā¸­ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {⏧⏴⏔ā¸ĩāš‚ā¸­} other {ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž}}⏗ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸā¸ā¸ąā¸š {person1} āš€ā¸Ąā¸ˇāšˆā¸­ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", @@ -1205,6 +1260,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {⏧⏴⏔ā¸ĩāš‚ā¸­} other {ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž}}⏗ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸāšƒā¸™ {city}, {country} ā¸ā¸ąā¸š {person1} āšā¸Ĩ⏰ {person2} āš€ā¸Ąā¸ˇāšˆā¸­ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {⏧⏴⏔ā¸ĩāš‚ā¸­} other {ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž}}⏗ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸāšƒā¸™ {city}, {country} ā¸ā¸ąā¸š {person1}, {person2}, āšā¸Ĩ⏰ {person3} āš€ā¸Ąā¸ˇāšˆā¸­ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {⏧⏴⏔ā¸ĩāš‚ā¸­} other {ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž}}⏗ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸāšƒā¸™ {city}, {country} ā¸ā¸ąā¸š {person1}, {person2}, āšā¸Ĩ⏰ {additionalCount, number} āš€ā¸Ąā¸ˇāšˆā¸­ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ {date}", + "image_saved_successfully": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩāš‰ā¸§", + "image_viewer_page_state_provider_download_started": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™", + "image_viewer_page_state_provider_download_success": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", + "image_viewer_page_state_provider_share_error": "āšā¸Šā¸ŖāšŒā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", "immich_logo": "āš‚ā¸Ĩāš‚ā¸āš‰ Immich", "immich_web_interface": "ā¸Ģā¸™āš‰ā¸˛ā¸•ā¸˛āš€ā¸§āš‡ā¸šāš„ā¸‹ā¸•āšŒ Immich", "import_from_json": "ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸ˆā¸˛ā¸ JSON", @@ -1219,7 +1278,6 @@ "individual_share": "āšā¸Šā¸ŖāšŒā¸Ēāšˆā¸§ā¸™ā¸•ā¸ąā¸§", "individual_shares": "ā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒāš€ā¸”ā¸ĩāšˆā¸ĸ⏧", "info": "ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", - "integrity_checks": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸„ā¸§ā¸˛ā¸Ąā¸Ēā¸Ąā¸šā¸šā¸Ŗā¸“āšŒ", "interval": { "day_at_onepm": "ā¸—ā¸¸ā¸ā¸§ā¸ąā¸™āš€ā¸§ā¸Ĩā¸˛ā¸šāšˆā¸˛ā¸ĸāš‚ā¸Ąā¸‡", "hours": "⏗⏏⏁ āš† {hours, plural, one {hour} other {{hours, number} hours}}", @@ -1230,9 +1288,16 @@ "invalid_date_format": "ā¸Ŗā¸šā¸›āšā¸šā¸šā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", "invite_people": "āš€ā¸Šā¸´ā¸ā¸œā¸šāš‰ā¸„ā¸™", "invite_to_album": "āš€ā¸Šā¸´ā¸āš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "ā¸Ŗā¸ąā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸ˇāšˆā¸­ {dateTime}", + "ios_debug_info_last_sync_at": "ā¸‹ā¸´ā¸‡ā¸„āšŒā¸Ĩāšˆā¸˛ā¸Ē⏏⏔ {dateTime}", + "ios_debug_info_no_processes_queued": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸„ā¸´ā¸§āšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡", + "ios_debug_info_no_sync_yet": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ąā¸ĩā¸‡ā¸˛ā¸™ā¸‹ā¸´ā¸‡ā¸„āšŒā¸Ŗā¸ąā¸™āšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡", + "ios_debug_info_processes_queued": "{count, plural, one {{count} āš‚ā¸žā¸Ŗāš€ā¸‹ā¸Ēā¸Ŗā¸­ā¸„ā¸´ā¸§āšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡} other {{count} āš‚ā¸žā¸Ŗāš€ā¸‹ā¸Ēā¸Ŗā¸­ā¸„ā¸´ā¸§āšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡}}", + "ios_debug_info_processing_ran_at": "āš‚ā¸žā¸Ŗāš€ā¸‹ā¸Ēā¸Ŗā¸ąā¸™āš€ā¸Ąā¸ˇāšˆā¸­ {dateTime}", "items_count": "{count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}", "jobs": "⏇⏞⏙", + "json_editor": "ā¸•ā¸ąā¸§āšā¸āš‰āš„ā¸‚ JSON", + "json_error": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ JSON", "keep": "āš€ā¸āš‡ā¸š", "keep_albums": "āš€ā¸āš‡ā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "keep_albums_count": "⏁⏺ā¸Ĩā¸ąā¸‡āš€ā¸āš‡ā¸š {count} {count, plural, one {ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą} other {ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą}}", @@ -1251,41 +1316,37 @@ "language_search_hint": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸ ā¸˛ā¸Šā¸˛...", "language_setting_description": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ ā¸˛ā¸Šā¸˛ā¸—ā¸ĩāšˆā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗ", "large_files": "āš„ā¸Ÿā¸ĨāšŒā¸‚ā¸™ā¸˛ā¸”āšƒā¸Ģā¸āšˆ", - "last": "ā¸Ēā¸¸ā¸”ā¸—āš‰ā¸˛ā¸ĸ", "last_months": "{count, plural, one {āš€ā¸”ā¸ˇā¸­ā¸™ā¸—ā¸ĩāšˆā¸œāšˆā¸˛ā¸™ā¸Ąā¸˛} other {# āš€ā¸”ā¸ˇā¸­ā¸™ā¸—ā¸ĩāšˆā¸œāšˆā¸˛ā¸™ā¸Ąā¸˛}}", "last_seen": "āš€ā¸Ģāš‡ā¸™ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "latest_version": "āš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "latitude": "ā¸Ĩā¸°ā¸•ā¸´ā¸ˆā¸šā¸”", "leave": "ā¸—ā¸´āš‰ā¸‡", "leave_album": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "lens_model": "ā¸Ŗā¸¸āšˆā¸™āš€ā¸Ĩ⏙ā¸ĒāšŒ", - "less": "ā¸™āš‰ā¸­ā¸ĸā¸Ĩ⏇", + "lens_model": "ā¸Ŗā¸šā¸›āšā¸šā¸šāš€ā¸Ĩā¸™ā¸ŠāšŒ", "let_others_respond": "āšƒā¸Ģāš‰ā¸„ā¸™ā¸­ā¸ˇāšˆā¸™ā¸•ā¸­ā¸š", "level": "ā¸Ŗā¸°ā¸”ā¸ąā¸š", "library": "⏄ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž", "library_add_folder": "āš€ā¸žā¸´āšˆā¸Ąāš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", "library_edit_folder": "āšā¸āš‰āš„ā¸‚āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", + "library_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁⏄ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž", + "library_page_device_albums": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", + "library_page_new_album": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸Ģā¸Ąāšˆ", "library_page_sort_asset_count": "ā¸ˆā¸ŗā¸™ā¸§ā¸™ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏗ā¸ĩāšˆā¸Ąā¸ĩ", "library_page_sort_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "library_page_sort_last_modified": "āšā¸āš‰āš„ā¸‚ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "library_page_sort_title": "ā¸Šā¸ˇāšˆā¸­ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "licenses": "ā¸Ēā¸ąā¸ā¸ā¸˛ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", "light": "ā¸Ēā¸§āšˆā¸˛ā¸‡", - "light_theme": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸›āš‡ā¸™ā¸˜ā¸ĩā¸Ąā¸Ēā¸§āšˆā¸˛ā¸‡", "like": "ā¸–ā¸šā¸āšƒā¸ˆ", "like_deleted": "ā¸Ĩā¸šā¸ā¸˛ā¸Ŗā¸–ā¸šā¸āšƒā¸ˆāšā¸Ĩāš‰ā¸§", - "link": "ā¸Ĩā¸´ā¸‡ā¸āšŒ", "link_motion_video": "ā¸Ĩā¸´ā¸‡ā¸āšŒā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­āš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģ⏧", - "link_to_docs": "ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą āš‚ā¸›ā¸Ŗā¸”ā¸”ā¸šāš€ā¸­ā¸ā¸Ēā¸˛ā¸Ŗā¸›ā¸Ŗā¸°ā¸ā¸­ā¸š", "link_to_oauth": "ā¸Ĩā¸´ā¸‡ā¸āšŒāš„ā¸›ā¸ĸā¸ąā¸‡ OAuth", "linked_oauth_account": "ā¸Ĩā¸´ā¸‡ā¸āšŒā¸šā¸ąā¸ā¸Šā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰ OAuth", "list": "⏪⏞ā¸ĸ⏁⏞⏪", - "live": "Live", - "load_more": "āš‚ā¸Ģā¸Ĩā¸”āš€ā¸žā¸´āšˆā¸Ą", "loading": "⏁⏺ā¸Ĩā¸ąā¸‡āš‚ā¸Ģā¸Ĩ⏔", "loading_search_results_failed": "āš‚ā¸Ģā¸Ĩā¸”ā¸œā¸Ĩā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "local": "āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", - "local_asset_cast_failed": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸„ā¸Ēā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", + "local_asset_cast_failed": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸„ā¸Ēā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸­ā¸ąā¸žāš‚ā¸Ģā¸Ĩā¸”āš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", "local_assets": "ā¸Ēā¸ˇāšˆā¸­āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", "local_id": "ID āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", "local_media_summary": "ā¸Ē⏪⏏⏛ā¸Ēā¸ˇāšˆā¸­āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", @@ -1301,7 +1362,6 @@ "location_picker_longitude_hint": "āš€ā¸žā¸´āšˆā¸Ąā¸Ĩā¸­ā¸‡ā¸ˆā¸´ā¸ˆā¸šā¸•ā¸•ā¸Ŗā¸‡ā¸™ā¸ĩāš‰", "lock": "ā¸Ĩāš‡ā¸­ā¸„", "locked_folder": "āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„", - "log_detail_title": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ĩāš‡ā¸­ā¸", "log_out": "⏭⏭⏁⏈⏞⏁⏪⏰⏚⏚", "log_out_all_devices": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸°ā¸šā¸šā¸—ā¸¸ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ", "logged_in_as": "{user} ⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ĩāš‡ā¸­ā¸„ā¸­ā¸´ā¸™", @@ -1310,9 +1370,11 @@ "login": "āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸š", "login_disabled": "ā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ā¸–ā¸šā¸ā¸›ā¸´ā¸”", "login_form_api_exception": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ API ā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ URL āšā¸Ĩāš‰ā¸§ā¸Ĩā¸­ā¸‡āšƒā¸Ģā¸Ąāšˆ", + "login_form_back_button_text": "⏁ā¸Ĩā¸ąā¸š", "login_form_email_hint": "⏭ā¸ĩāš€ā¸Ąā¸Ĩ⏄⏏⏓@⏭ā¸ĩāš€ā¸Ąā¸Ĩ.com", "login_form_endpoint_hint": "http://āš„ā¸­ā¸žā¸ĩāš€ā¸Šā¸­ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸„ā¸¸ā¸“:ā¸žā¸­ā¸ŖāšŒā¸•", "login_form_endpoint_url": "URL ⏛ā¸Ĩ⏞ā¸ĸā¸—ā¸˛ā¸‡ā¸‚ā¸­ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", + "login_form_err_http": "āš‚ā¸›ā¸Ŗā¸”ā¸Ŗā¸°ā¸šā¸¸ http:// ā¸Ģ⏪⏎⏭ https://", "login_form_err_invalid_email": "⏭ā¸ĩāš€ā¸Ąā¸Ĩāš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", "login_form_err_invalid_url": "URL āš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", "login_form_err_leading_whitespace": "āš€ā¸§āš‰ā¸™ā¸§āšˆā¸˛ā¸‡ā¸‚āš‰ā¸˛ā¸‡ā¸Ģā¸™āš‰ā¸˛", @@ -1322,14 +1384,14 @@ "login_form_failed_login": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšƒā¸™ā¸ā¸˛ā¸Ŗā¸Ĩāš‡ā¸­ā¸ā¸­ā¸´ā¸™ āš‚ā¸›ā¸Ŗā¸”ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚ URL ā¸‚ā¸­ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ ⏭ā¸ĩāš€ā¸Ąā¸Ĩ āšā¸Ĩ⏰⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "login_form_handshake_exception": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸ā¸Ŗā¸°ā¸šā¸§ā¸™ā¸ā¸˛ā¸Ŗ Handshake ā¸ā¸ąā¸šāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ ā¸–āš‰ā¸˛ā¸ā¸ŗā¸Ĩā¸ąā¸‡āšƒā¸Šāš‰āšƒā¸šā¸Ŗā¸ąā¸šā¸šā¸Ŗā¸­ā¸‡āšā¸šā¸š self-signed ā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸šā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡āšā¸šā¸š self-signed", "login_form_password_hint": "⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", + "login_form_save_login": "⏭ā¸ĸā¸šāšˆāšƒā¸™ā¸Ŗā¸°ā¸šā¸šā¸•āšˆā¸­āš„ā¸›", "login_form_server_empty": "⏁⏪⏭⏁ URL āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", - "login_form_server_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", - "login_has_been_disabled": "ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šāšā¸Ĩāš‰ā¸§", + "login_form_server_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸´ā¸”ā¸•āšˆā¸­ā¸ā¸ąā¸šāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", + "login_has_been_disabled": "ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šāšā¸Ĩāš‰ā¸§", "login_password_changed_error": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”ā¸‚ā¸“ā¸°āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "login_password_changed_success": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "logout_all_device_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸°ā¸šā¸šā¸—ā¸¸ā¸ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ āšƒā¸Šāšˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ?", "logout_this_device_confirmation": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸°ā¸šā¸šā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", - "logs": "ā¸Ĩāš‡ā¸­ā¸", "longitude": "ā¸Ĩā¸­ā¸‡ā¸ˆā¸´ā¸ˆā¸šā¸”", "look": "ā¸”ā¸š", "loop_videos": "⏧⏙⏧⏴⏔ā¸ĩāš‚ā¸­", @@ -1339,6 +1401,7 @@ "maintenance_action_restore": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_description": "Immich āš„ā¸”āš‰āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆāš‚ā¸Ģā¸Ąā¸”ā¸šā¸ŗā¸Ŗā¸¸ā¸‡ā¸Ŗā¸ąā¸ā¸Šā¸˛", "maintenance_end": "ā¸Ēā¸´āš‰ā¸™ā¸Ēā¸¸ā¸”āš‚ā¸Ģā¸Ąā¸”ā¸šā¸ŗā¸Ŗā¸¸ā¸‡ā¸Ŗā¸ąā¸ā¸Šā¸˛", + "maintenance_end_error": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸´āš‰ā¸™ā¸Ēā¸¸ā¸”āš‚ā¸Ģā¸Ąā¸”ā¸šā¸ŗā¸Ŗā¸¸ā¸‡ā¸Ŗā¸ąā¸ā¸Šā¸˛", "maintenance_logged_in_as": "āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šāšƒā¸™ā¸Šā¸ˇāšˆā¸­ {user} ⏭ā¸ĸā¸šāšˆ", "maintenance_restore_from_backup": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸ˆā¸˛ā¸ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "maintenance_restore_library": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", @@ -1352,7 +1415,6 @@ "maintenance_restore_library_hint_missing_files": "āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ŗā¸„ā¸ąā¸ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸­ā¸˛ā¸ˆā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", "maintenance_restore_library_hint_regenerate_later": "⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšƒā¸Ģā¸Ąāšˆāš„ā¸”āš‰āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", "maintenance_restore_library_hint_storage_template_missing_files": "ā¸Ģā¸˛ā¸ā¸„ā¸¸ā¸“āšƒā¸Šāš‰āš€ā¸—ā¸Ąāš€ā¸žā¸Ĩ⏕⏗ā¸ĩāšˆāš€ā¸āš‡ā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ āš„ā¸Ÿā¸ĨāšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸­ā¸˛ā¸ˆā¸ˆā¸°ā¸‚ā¸˛ā¸”ā¸Ģ⏞ā¸ĸāš„ā¸›", - "maintenance_restore_library_loading": "⏁⏺ā¸Ĩā¸ąā¸‡āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸°ā¸šā¸šā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāšā¸Ĩā¸°ā¸Žā¸´ā¸§ā¸Ŗā¸´ā¸Ē⏕⏴⏁â€Ļ", "maintenance_task_backup": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚ā¸­ā¸‡ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸”ā¸´ā¸Ąâ€Ļ", "maintenance_task_migrations": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸ĸāš‰ā¸˛ā¸ĸā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩâ€Ļ", "maintenance_task_restore": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁â€Ļ", @@ -1364,18 +1426,21 @@ "manage_media_access_settings": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", "manage_media_access_subtitle": "ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•āšƒā¸Ģāš‰āšā¸­ā¸› Immich ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗāšā¸Ĩ⏰ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ˇāšˆā¸­", "manage_media_access_title": "ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ēā¸ˇāšˆā¸­", + "manage_shared_links": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", "manage_sharing_with_partners": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒā¸ā¸ąā¸šā¸„ā¸šāšˆā¸Ģā¸š", "manage_the_app_settings": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸­ā¸›", "manage_your_account": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸šā¸ąā¸ā¸Šā¸ĩ⏂⏭⏇⏄⏏⏓", - "manage_your_api_keys": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸„ā¸ĩā¸ĸāšŒ API ⏂⏭⏇⏄⏏⏓", + "manage_your_api_keys": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸¸ā¸āšā¸ˆ API ⏂⏭⏇⏄⏏⏓", "manage_your_devices": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "manage_your_oauth_connection": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ OAuth ⏂⏭⏇⏄⏏⏓", "map": "āšā¸œā¸™ā¸—ā¸ĩāšˆ", + "map_assets_in_bounds": "{count, plural, =0 {āš„ā¸Ąāšˆā¸Ąā¸ĩā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸™ā¸ĩāš‰} one {# ā¸Ŗā¸šā¸›} other {# ā¸Ŗā¸šā¸›}}", "map_cannot_get_user_location": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ģā¸˛ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš„ā¸”āš‰", + "map_location_dialog_yes": "āšƒā¸Šāšˆ", "map_location_picker_page_use_location": "āšƒā¸Šāš‰ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸™ā¸ĩāš‰", "map_location_service_disabled_content": "ā¸•āš‰ā¸­ā¸‡āš€ā¸›ā¸´ā¸”ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡āš€ā¸žā¸ˇāšˆā¸­āšā¸Ēā¸”ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸ˆā¸˛ā¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™ āš€ā¸›ā¸´ā¸”ā¸•ā¸­ā¸™ā¸™ā¸ĩāš‰?", "map_location_service_disabled_title": "ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸–ā¸šā¸ā¸›ā¸´ā¸”", - "map_marker_for_image": "āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ģā¸Ąā¸˛ā¸ĸāšā¸œā¸™ā¸—ā¸ĩāšˆā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸–āšˆā¸˛ā¸ĸāšƒā¸™āš€ā¸Ąā¸ˇā¸­ā¸‡{city}, {country}", + "map_marker_for_images": "ā¸Ģā¸Ąā¸¸ā¸”āšā¸œā¸™ā¸—ā¸ĩāšˆā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ŗā¸šā¸›ā¸–āšˆā¸˛ā¸ĸ⏗ā¸ĩāšˆ {city}, {country}", "map_marker_with_image": "ā¸Ģā¸Ąā¸¸ā¸”āšā¸œā¸™ā¸—ā¸ĩāšˆā¸ā¸ąā¸šā¸Ŗā¸šā¸›ā¸–āšˆā¸˛ā¸ĸ", "map_no_location_permission_content": "ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡ā¸Ąā¸ĩā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡āš€ā¸žā¸ˇāšˆā¸­āšā¸Ēā¸”ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸ˆā¸˛ā¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™ ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•ā¸•ā¸­ā¸™ā¸™ā¸ĩāš‰?", "map_no_location_permission_title": "ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸–ā¸šā¸ā¸›ā¸ā¸´āš€ā¸Ē⏘", @@ -1385,46 +1450,17 @@ "map_settings_date_range_option_days": "{days} ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸œāšˆā¸˛ā¸™ā¸Ąā¸˛", "map_settings_date_range_option_year": "⏛ā¸ĩ⏗ā¸ĩāšˆā¸œāšˆā¸˛ā¸™ā¸Ąā¸˛", "map_settings_date_range_option_years": "{years} ⏛ā¸ĩā¸œāšˆā¸˛ā¸™ā¸Ąā¸˛", + "map_settings_dialog_title": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸œā¸™ā¸—ā¸ĩāšˆ", "map_settings_include_show_archived": "ā¸Ŗā¸§ā¸Ąāš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "map_settings_include_show_partners": "ā¸Ŗā¸§ā¸Ąā¸„ā¸šāšˆā¸Ģā¸š", "map_settings_only_show_favorites": "āšā¸Ē⏔⏇⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", "map_settings_theme_settings": "⏘ā¸ĩā¸Ąāšā¸œā¸™ā¸—ā¸ĩāšˆ", + "map_zoom_to_see_photos": "ā¸‹ā¸šā¸Ąā¸­ā¸­ā¸āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸Ŗā¸šā¸›", "mark_all_as_read": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āš€ā¸›āš‡ā¸™ā¸­āšˆā¸˛ā¸™āšā¸Ĩāš‰ā¸§", + "mark_as_read": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸›āš‡ā¸™ā¸­āšˆā¸˛ā¸™āšā¸Ĩāš‰ā¸§", "marked_all_as_read": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš€ā¸›āš‡ā¸™ā¸­āšˆā¸˛ā¸™āšā¸Ĩāš‰ā¸§ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšā¸Ĩāš‰ā¸§", "matches": "ā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™", "matching_assets": "ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™", - "media_chrome": { - "auto": "ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", - "captions": "ā¸„ā¸ŗā¸šā¸Ŗā¸Ŗā¸ĸ⏞ā¸ĸ", - "captions_off": "⏛⏴⏔", - "closed_captions": "ā¸„ā¸ŗā¸šā¸Ŗā¸Ŗā¸ĸ⏞ā¸ĸāšā¸—ā¸™āš€ā¸Ēā¸ĩā¸ĸ⏇", - "decode_error": "⏁⏞⏪⏖⏭⏔⏪ā¸Ģā¸ąā¸Ēā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", - "disable_captions": "ā¸›ā¸´ā¸”ā¸„ā¸ŗā¸šā¸Ŗā¸Ŗā¸ĸ⏞ā¸ĸ", - "enable_captions": "āš€ā¸›ā¸´ā¸”ā¸„ā¸ŗā¸šā¸Ŗā¸Ŗā¸ĸ⏞ā¸ĸ", - "enter_fullscreen_mode": "āš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆāš‚ā¸Ģā¸Ąā¸”āš€ā¸•āš‡ā¸Ąā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­", - "exit_fullscreen_mode": "ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸āš‚ā¸Ģā¸Ąā¸”āš€ā¸•āš‡ā¸Ąā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­", - "loop": "ā¸§ā¸™ā¸‹āš‰ā¸ŗ", - "media_error_description": "āš€ā¸ā¸´ā¸”ā¸›ā¸ąā¸ā¸Ģ⏞⏗ā¸ĩāšˆā¸—ā¸ŗāšƒā¸Ģāš‰āš€ā¸Ĩāšˆā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰ā¸•āšˆā¸­āš„ā¸Ąāšˆāš„ā¸”āš‰ ā¸­ā¸˛ā¸ˆā¸ˆā¸°āš€ā¸›āš‡ā¸™āš€ā¸žā¸Ŗā¸˛ā¸°ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰āš€ā¸Ēā¸ĩā¸ĸ ā¸Ģā¸Ŗā¸ˇā¸­āš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ĩāšˆā¸™ā¸Ēā¸ˇāšˆā¸­ā¸Šā¸™ā¸´ā¸”ā¸™ā¸ĩāš‰āš„ā¸”āš‰āš€ā¸­ā¸‡", - "media_loading": "⏁⏺ā¸Ĩā¸ąā¸‡āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", - "mute": "ā¸›ā¸´ā¸”āš€ā¸Ēā¸ĩā¸ĸ⏇", - "network_error": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ", - "network_error_description": "āš€ā¸ā¸´ā¸”ā¸›ā¸ąā¸ā¸Ģā¸˛āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸā¸—ā¸ŗāšƒā¸Ģāš‰āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”ā¸•āšˆā¸­āš„ā¸”āš‰", - "not_supported_error": "āš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸•āš‰ā¸™ā¸—ā¸˛ā¸‡", - "playback_rate": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ā¸ā¸˛ā¸Ŗāš€ā¸Ĩāšˆā¸™", - "playback_rate_current": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ā¸ā¸˛ā¸Ŗāš€ā¸Ĩāšˆā¸™ā¸•ā¸­ā¸™ā¸™ā¸ĩāš‰", - "playback_rate_value": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸Ŗāš‡ā¸§ {playbackRate}", - "playback_time": "āš€ā¸§ā¸Ĩā¸˛āš€ā¸Ĩāšˆā¸™", - "quality": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸ž", - "second": "⏧⏴⏙⏞⏗ā¸ĩ", - "seconds": "⏧⏴⏙⏞⏗ā¸ĩ", - "time_value_of_total_time": "{currentTime} ⏈⏞⏁ {totalTime}", - "time_value_remaining": "āš€ā¸Ģā¸Ĩ⏎⏭⏭ā¸ĩ⏁ {time}", - "unmute": "āš€ā¸›ā¸´ā¸”āš€ā¸Ēā¸ĩā¸ĸ⏇", - "unsupported_error_description": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸š āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸Ģā¸Ŗā¸ˇā¸­āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ ā¸Ģā¸Ŗā¸ˇā¸­āš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸Ēā¸ˇāšˆā¸­ā¸Ŗā¸šā¸›āšā¸šā¸šā¸™ā¸ĩāš‰", - "video_not_loaded_unknown_time": "⏧⏴⏔ā¸ĩāš‚ā¸­āš„ā¸Ąāšˆāš„ā¸”āš‰āš‚ā¸Ģā¸Ĩ⏔ āš„ā¸Ąāšˆā¸—ā¸Ŗā¸˛ā¸šāš€ā¸§ā¸Ĩ⏞", - "video_player": "ā¸•ā¸ąā¸§āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­", - "volume": "ā¸Ŗā¸°ā¸”ā¸ąā¸šāš€ā¸Ēā¸ĩā¸ĸ⏇" - }, "media_type": "ā¸›ā¸Ŗā¸°āš€ā¸ ā¸—ā¸Ēā¸ˇāšˆā¸­", "memories": "ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "memories_all_caught_up": "ā¸•ā¸˛ā¸Ąā¸—ā¸ąā¸™ā¸Ģā¸Ąā¸”āšā¸Ĩāš‰ā¸§", @@ -1441,31 +1477,32 @@ "merge_people_prompt": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ŗā¸§ā¸Ąā¸šā¸¸ā¸„ā¸„ā¸Ĩāš€ā¸Ģā¸Ĩāšˆā¸˛ā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ⏁⏞⏪⏁⏪⏰⏗⏺⏙ā¸ĩāš‰āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ĸāš‰ā¸­ā¸™ā¸ā¸Ĩā¸ąā¸šāš„ā¸”āš‰", "merge_people_successfully": "ā¸Ŗā¸§ā¸Ąā¸œā¸šāš‰ā¸„ā¸™āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸāšā¸Ĩāš‰ā¸§", "merged_people_count": "ā¸Ŗā¸§ā¸Ą {count, plural, one {# ⏄⏙} other {# ⏄⏙}}āš€ā¸‚āš‰ā¸˛ā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™āšā¸Ĩāš‰ā¸§", - "minFaces": "ā¸ˆā¸ŗā¸™ā¸§ā¸™āšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸‚ā¸ąāš‰ā¸™ā¸•āšˆā¸ŗ", - "minFaces_description": "ā¸ˆā¸ŗā¸™ā¸§ā¸™āšƒā¸šā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸•āš‰ā¸­ā¸‡āš€ā¸Ģāš‡ā¸™ā¸āšˆā¸­ā¸™ā¸—ā¸ĩāšˆā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸ˆā¸°ā¸–ā¸šā¸āšā¸Ē⏔⏇", "minimize": "ā¸ĸāšˆā¸­ā¸Ĩ⏇", "minute": "⏙⏞⏗ā¸ĩ", "minutes": "⏙⏞⏗ā¸ĩ", + "mirror_horizontal": "āšā¸™ā¸§ā¸™ā¸­ā¸™", + "mirror_vertical": "āšā¸™ā¸§ā¸•ā¸ąāš‰ā¸‡", "missing": "⏂⏞⏔ā¸Ģ⏞ā¸ĸ", "mobile_app": "āšā¸­ā¸›ā¸Ąā¸ˇā¸­ā¸–ā¸ˇā¸­", "mobile_app_download_onboarding_note": "ā¸”ā¸˛ā¸§ā¸™āšŒāš‚ā¸Ģā¸Ĩā¸”āšā¸­ā¸›ā¸Ąā¸ˇā¸­ā¸–ā¸ˇā¸­ā¸”āš‰ā¸§ā¸ĸā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸•āšˆā¸­āš„ā¸›ā¸™ā¸ĩāš‰", "model": "āš‚ā¸Ąāš€ā¸”ā¸Ĩ", - "modify_date": "ā¸›ā¸Ŗā¸ąā¸šāšā¸•āšˆā¸‡ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", "month": "āš€ā¸”ā¸ˇā¸­ā¸™", "more": "āš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą", - "motion": "āš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģ⏧", "move": "ā¸ĸāš‰ā¸˛ā¸ĸ", + "move_down": "ā¸ĸāš‰ā¸˛ā¸ĸā¸Ĩ⏇", "move_off_locked_folder": "ā¸ĸāš‰ā¸˛ā¸ĸā¸­ā¸­ā¸ā¸ˆā¸˛ā¸āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„", "move_to": "ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸ĸā¸ąā¸‡", "move_to_device_trash": "ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒ", "move_to_lock_folder_action_prompt": "āš€ā¸žā¸´āšˆā¸Ą {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš„ā¸›ā¸ĸā¸ąā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸āšā¸Ĩāš‰ā¸§", "move_to_locked_folder": "ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸„", "move_to_locked_folder_confirmation": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­āš€ā¸Ģā¸Ĩāšˆā¸˛ā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸—ā¸¸ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą āšā¸Ĩ⏰⏈⏰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸šāš„ā¸”āš‰ā¸ˆā¸˛ā¸āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", + "move_up": "ā¸ĸāš‰ā¸˛ā¸ĸ⏂ā¸ļāš‰ā¸™", + "moved_to_library": "ā¸ĸāš‰ā¸˛ā¸ĸā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žāšā¸Ĩāš‰ā¸§", "moved_to_trash": "ā¸—ā¸´āš‰ā¸‡ā¸Ĩā¸‡ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§", + "multiselect_grid_edit_date_time_err_read_only": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰āš„ā¸‚ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšā¸šā¸šā¸­āšˆā¸˛ā¸™ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", + "multiselect_grid_edit_gps_err_read_only": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸āš‰ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸‚ā¸­ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšā¸šā¸šā¸­āšˆā¸˛ā¸™ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧ ⏁⏺ā¸Ĩā¸ąā¸‡ā¸‚āš‰ā¸˛ā¸Ą", "mute_memories": "ā¸›ā¸´ā¸”āš€ā¸Ēā¸ĩā¸ĸā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "my_albums": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸‚ā¸­ā¸‡ā¸‰ā¸ąā¸™", - "my_immich_description": "ā¸„ā¸ąā¸”ā¸Ĩ⏭⏁ā¸Ģā¸™āš‰ā¸˛ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™āš€ā¸›āš‡ā¸™ā¸Ĩā¸´ā¸‡ā¸āšŒ My Immich", - "my_immich_title": "ā¸Ĩā¸´ā¸‡ā¸āšŒ My Immich", "name": "ā¸Šā¸ˇāšˆā¸­", "name_or_nickname": "ā¸Šā¸ˇāšˆā¸­ā¸Ģā¸Ŗā¸ˇā¸­ā¸Šā¸ˇāšˆā¸­āš€ā¸Ĩāšˆā¸™", "name_required": "ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡ā¸Ąā¸ĩā¸Šā¸ˇāšˆā¸­", @@ -1479,12 +1516,13 @@ "networking_subtitle": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸›ā¸Ĩ⏞ā¸ĸā¸—ā¸˛ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", "never": "āš„ā¸Ąāšˆāš€ā¸„ā¸ĸ", "new_album": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸Ģā¸Ąāšˆ", - "new_api_key": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸ĩā¸ĸāšŒ API āšƒā¸Ģā¸Ąāšˆ", - "new_feature": "⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´āšƒā¸Ģā¸Ąāšˆ", + "new_api_key": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ API ⏄ā¸ĩā¸ĸāšŒāšƒā¸Ģā¸Ąāšˆ", + "new_date_range": "ā¸Šāšˆā¸§ā¸‡ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšƒā¸Ģā¸Ąāšˆ", "new_password": "⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āšƒā¸Ģā¸Ąāšˆ", "new_person": "ā¸„ā¸™āšƒā¸Ģā¸Ąāšˆ", "new_pin_code": "⏪ā¸Ģā¸ąā¸Ē PIN āšƒā¸Ģā¸Ąāšˆ", "new_pin_code_subtitle": "⏄⏏⏓⏁⏺ā¸Ĩā¸ąā¸‡āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸āš€ā¸›āš‡ā¸™ā¸„ā¸Ŗā¸ąāš‰ā¸‡āšā¸Ŗā¸ āš‚ā¸›ā¸Ŗā¸”ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ŗā¸Ģā¸ąā¸Ē PIN āš€ā¸žā¸ˇāšˆā¸­āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇ā¸Ģā¸™āš‰ā¸˛ā¸™ā¸ĩāš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸›ā¸Ĩā¸­ā¸”ā¸ ā¸ąā¸ĸ", + "new_timeline": "āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒāšƒā¸Ģā¸Ąāšˆ", "new_update": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•āšƒā¸Ģā¸Ąāšˆ", "new_user_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰āšƒā¸Ģā¸Ąāšˆāšā¸Ĩāš‰ā¸§", "new_version_available": "ā¸Ąā¸ĩāš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™āšƒā¸Ģā¸Ąāšˆāšƒā¸Ģāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", @@ -1492,6 +1530,7 @@ "next": "ā¸•āšˆā¸­āš„ā¸›", "next_memory": "ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗā¸•āšˆā¸­āš„ā¸›", "no": "āš„ā¸Ąāšˆ", + "no_actions_added": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰āš€ā¸žā¸´āšˆā¸Ąā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸°ā¸—ā¸ŗ", "no_albums_found": "āš„ā¸Ąāšˆā¸žā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "no_albums_message": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš€ā¸žā¸ˇāšˆā¸­ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "no_albums_with_name_yet": "ā¸”ā¸šāš€ā¸Ģā¸Ąā¸ˇā¸­ā¸™ā¸§āšˆā¸˛ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ąā¸ĩā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸Ģ⏙⏗ā¸ĩāšˆāšƒā¸Šāš‰ā¸Šā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰", @@ -1500,37 +1539,30 @@ "no_assets_message": "⏄ā¸Ĩā¸´ā¸āš€ā¸žā¸ˇāšˆā¸­ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸ ā¸˛ā¸žāšā¸Ŗā¸ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "no_assets_to_show": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸Ģāš‰āšā¸Ē⏔⏇", "no_cast_devices_found": "āš„ā¸Ąāšˆā¸žā¸šā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒāšā¸„ā¸Ēā¸•āšŒ", - "no_checksum_local": "āš„ā¸Ąāšˆā¸Ąā¸ĩ Checksum ‐ āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸ļ⏇ā¸Ēā¸ˇāšˆā¸­ā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš„ā¸”āš‰", - "no_checksum_remote": "āš„ā¸Ąāšˆā¸Ąā¸ĩ Checksum - āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸”ā¸ļ⏇ā¸Ēā¸ˇāšˆā¸­ā¸ˆā¸˛ā¸āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš„ā¸”āš‰", + "no_configuration_needed": "āš„ā¸Ąāšˆā¸•āš‰ā¸­ā¸‡ā¸Ąā¸ĩ⏁⏞⏪⏁⏺ā¸Ģā¸™ā¸”ā¸„āšˆā¸˛āšƒā¸” āš†", "no_devices": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸—ā¸ĩāšˆāš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", "no_duplicates_found": "āš„ā¸Ąāšˆā¸žā¸šā¸Ŗā¸˛ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™", "no_exif_info_available": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ exif", "no_explore_results_message": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš€ā¸žā¸´āšˆā¸Ąāš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸ŗā¸Ŗā¸§ā¸ˆā¸„ā¸­ā¸Ĩāš€ā¸Ĩā¸ā¸Šā¸ąā¸™ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "no_favorites_message": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āš€ā¸žā¸ˇāšˆā¸­ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸”ā¸ĩ⏗ā¸ĩāšˆā¸Ē⏏⏔⏂⏭⏇⏄⏏⏓⏭ā¸ĸāšˆā¸˛ā¸‡ā¸Ŗā¸§ā¸”āš€ā¸Ŗāš‡ā¸§", + "no_filters_added": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ąā¸ĩā¸ā¸˛ā¸Ŗāš€ā¸žā¸´āšˆā¸Ąā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", "no_libraries_message": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸ ā¸˛ā¸ĸā¸™ā¸­ā¸āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸•āšˆā¸˛ā¸‡ āš† ⏂⏭⏇⏄⏏⏓", - "no_local_assets_found": "āš„ā¸Ąāšˆā¸žā¸šā¸Ēā¸ˇāšˆā¸­āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸—ā¸ĩāšˆā¸Ąā¸ĩ Checksum ⏙ā¸ĩāš‰", + "no_location_set": "āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ŗā¸°ā¸šā¸¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", "no_locked_photos_message": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­āšƒā¸™āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸ā¸ˆā¸°ā¸–ā¸šā¸ā¸‹āšˆā¸­ā¸™āš„ā¸§āš‰ āšā¸Ĩā¸°āš„ā¸Ąāšˆāšā¸Ē⏔⏇⏪⏰ā¸Ģā¸§āšˆā¸˛ā¸‡ā¸”ā¸šā¸Ģā¸Ŗā¸ˇā¸­ā¸„āš‰ā¸™ā¸Ģ⏞⏄ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "no_name": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸Šā¸ˇāšˆā¸­", "no_notifications": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™", "no_people_found": "āš„ā¸Ąāšˆā¸žā¸šā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏗ā¸ĩāšˆā¸•ā¸Ŗā¸‡ā¸ā¸ąā¸™", "no_places": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸Ē⏖⏞⏙⏗ā¸ĩāšˆ", - "no_remote_assets_found": "āš„ā¸Ąāšˆā¸žā¸šā¸Ēā¸ˇāšˆā¸­ā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩ Checksum ⏙ā¸ĩāš‰", "no_results": "āš„ā¸Ąāšˆā¸Ąā¸ĩ⏜ā¸Ĩā¸Ĩā¸ąā¸žā¸˜āšŒ", "no_results_description": "ā¸Ĩā¸­ā¸‡āšƒā¸Šāš‰ā¸„ā¸ŗā¸žāš‰ā¸­ā¸‡ā¸Ģ⏪⏎⏭⏄⏺ā¸Ģā¸Ĩā¸ąā¸ā¸—ā¸ĩāšˆā¸ā¸§āš‰ā¸˛ā¸‡ā¸ā¸§āšˆā¸˛ā¸™ā¸ĩāš‰", "no_shared_albums_message": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš€ā¸žā¸ˇāšˆā¸­āšā¸Šā¸ŖāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸ā¸ąā¸šā¸„ā¸™āšƒā¸™āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ⏂⏭⏇⏄⏏⏓", - "no_steps": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰āš€ā¸žā¸´āšˆā¸Ąā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", + "no_uploads_in_progress": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔⏗ā¸ĩāšˆā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸­ā¸ĸā¸šāšˆ", "none": "āš„ā¸Ąāšˆā¸Ąā¸ĩ", "not_allowed": "āš„ā¸Ąāšˆā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", - "not_available": "āš„ā¸Ąāšˆā¸Ąā¸ĩ", "not_in_any_album": "āš„ā¸Ąāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšƒā¸” āš†", "not_selected": "āš„ā¸Ąāšˆāš„ā¸”āš‰āš€ā¸Ĩ⏎⏭⏁", - "not_set": "āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸•ā¸ąāš‰ā¸‡", "notes": "ā¸Ģā¸Ąā¸˛ā¸ĸāš€ā¸Ģ⏕⏏", "nothing_here_yet": "⏕⏪⏇⏙ā¸ĩāš‰ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸Ąā¸ĩā¸­ā¸°āš„ā¸Ŗ", - "notification_backup_reliability": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡ā¸Ŗā¸˛ā¸šā¸Ŗā¸ˇāšˆā¸™ā¸‚ā¸ļāš‰ā¸™", - "notification_enabled_list_tile_content": "Immich āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āš€ā¸žā¸ˇāšˆā¸­ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™ā¸žā¸ˇāšˆā¸™ā¸Ģā¸Ĩā¸ąā¸‡ ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āš„ā¸”āš‰āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", - "notification_enabled_list_tile_open_button": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", - "notification_enabled_list_tile_title": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™āšā¸Ĩāš‰ā¸§", "notification_permission_dialog_content": "āš€ā¸žā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ āš€ā¸‚āš‰ā¸˛āš„ā¸›ā¸—ā¸ĩāšˆā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸Ĩāš‰ā¸§ā¸ā¸” ā¸­ā¸™ā¸¸ā¸ā¸˛ā¸•", "notification_permission_list_tile_content": "āšƒā¸Ģāš‰ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒāš€ā¸žā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™", "notification_permission_list_tile_enable_button": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸”ā¸ˇā¸­ā¸™", @@ -1542,17 +1574,14 @@ "obtainium_configurator": "ā¸•ā¸ąā¸§ā¸ā¸ŗā¸Ģā¸™ā¸”ā¸„āšˆā¸˛ Obtainium", "obtainium_configurator_instructions": "āšƒā¸Šāš‰ Obtainium āš€ā¸žā¸ˇāšˆā¸­ā¸•ā¸´ā¸”ā¸•ā¸ąāš‰ā¸‡āšā¸Ĩā¸°ā¸­ā¸ąā¸›āš€ā¸”ā¸•āšā¸­ā¸› Android āš‚ā¸”ā¸ĸā¸•ā¸Ŗā¸‡ā¸ˆā¸˛ā¸ Github ⏂⏭⏇ Immich ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸„ā¸ĩā¸ĸāšŒ API āšā¸Ĩā¸°āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Ŗā¸šā¸›āšā¸šā¸šāš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩā¸´ā¸‡ā¸āšŒā¸ā¸ŗā¸Ģā¸™ā¸”ā¸„āšˆā¸˛ Obtainium ⏂⏭⏇⏄⏏⏓", "ocr": "OCR", - "ocr_body": "Immich ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­āšˆā¸˛ā¸™ā¸•ā¸ąā¸§ā¸­ā¸ąā¸ā¸Šā¸Ŗāšƒā¸™ā¸ ā¸˛ā¸žāš„ā¸”āš‰āšā¸Ĩāš‰ā¸§ ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸ ā¸˛ā¸žāš„ā¸”āš‰ā¸•ā¸˛ā¸Ąā¸„ā¸ŗā¸—ā¸ĩāšˆā¸›ā¸Ŗā¸˛ā¸ā¸Žāšƒā¸™ā¸ ā¸˛ā¸ž", - "ocr_title": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "official_immich_resources": "āšā¸Ģā¸Ĩāšˆā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ Immich ⏭ā¸ĸāšˆā¸˛ā¸‡āš€ā¸›āš‡ā¸™ā¸—ā¸˛ā¸‡ā¸ā¸˛ā¸Ŗ", "offline": "ā¸­ā¸­ā¸Ÿāš„ā¸Ĩā¸™āšŒ", - "offset": "⏪⏰ā¸ĸ⏰ā¸Ģāšˆā¸˛ā¸‡", "ok": "⏕⏁ā¸Ĩ⏇", "oldest_first": "āš€ā¸Ŗā¸ĩā¸ĸā¸‡āš€ā¸āšˆā¸˛ā¸Ēā¸¸ā¸”ā¸āšˆā¸­ā¸™", "on_this_device": "ā¸šā¸™ā¸­ā¸¸ā¸›ā¸ā¸Ŗā¸“āšŒā¸™ā¸ĩāš‰", "onboarding": "ā¸ā¸˛ā¸Ŗāš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "onboarding_locale_description": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ ā¸˛ā¸Šā¸˛ā¸—ā¸ĩāšˆā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗ ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āš„ā¸”āš‰ā¸ ā¸˛ā¸ĸā¸Ģā¸Ĩā¸ąā¸‡āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", - "onboarding_privacy_description": "⏄⏏⏓ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ (ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁) ā¸•āšˆā¸­āš„ā¸›ā¸™ā¸ĩāš‰ā¸•āš‰ā¸­ā¸‡ā¸­ā¸˛ā¸¨ā¸ąā¸ĸ⏚⏪⏴⏁⏞⏪⏠⏞ā¸ĸ⏙⏭⏁ āšā¸Ĩ⏰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš„ā¸”āš‰ā¸•ā¸Ĩā¸­ā¸”āš€ā¸§ā¸Ĩā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", + "onboarding_privacy_description": "⏟ā¸ĩāš€ā¸ˆā¸­ā¸ŖāšŒ (ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁) ā¸•āšˆā¸­āš„ā¸›ā¸™ā¸ĩāš‰ā¸•āš‰ā¸­ā¸‡ā¸­ā¸˛ā¸¨ā¸ąā¸ĸ⏚⏪⏴⏁⏞⏪⏠⏞ā¸ĸ⏙⏭⏁ āšā¸Ĩ⏰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āš„ā¸”āš‰ā¸•ā¸Ĩā¸­ā¸”āš€ā¸§ā¸Ĩā¸˛āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗ", "onboarding_server_welcome_description": "ā¸Ąā¸˛ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸”āš‰ā¸§ā¸ĸā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸—ā¸ĩāšˆāšƒā¸Šāš‰ā¸šāšˆā¸­ā¸ĸā¸ā¸ąā¸™āš€ā¸–ā¸­ā¸°", "onboarding_theme_description": "āš€ā¸Ĩ⏎⏭⏁⏘ā¸ĩā¸Ąā¸Ēā¸ĩ ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩā¸‡āš„ā¸”āš‰āšƒā¸™ā¸ ā¸˛ā¸ĸā¸Ģā¸Ĩā¸ąā¸‡āšƒā¸™ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "onboarding_user_welcome_description": "ā¸Ąā¸˛āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™ā¸ā¸ąā¸™āš€ā¸–ā¸­ā¸°!", @@ -1561,18 +1590,13 @@ "only_favorites": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", "open": "āš€ā¸›ā¸´ā¸”", "open_calendar": "āš€ā¸›ā¸´ā¸”ā¸›ā¸ā¸´ā¸—ā¸´ā¸™", - "open_in_browser": "āš€ā¸›ā¸´ā¸”āšƒā¸™āš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒ", - "open_in_immich_body": "ā¸•ā¸ąāš‰ā¸‡ Immich āš€ā¸›āš‡ā¸™āšā¸­ā¸žā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšƒā¸™ Android āš€ā¸žā¸ˇāšˆā¸­āš€ā¸›ā¸´ā¸”ā¸Ŗā¸šā¸›ā¸ˆā¸˛ā¸āšā¸­ā¸žā¸­ā¸ˇāšˆā¸™āšƒā¸™ Immich", - "open_in_immich_title": "āš€ā¸›ā¸´ā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšƒā¸™ Immich", "open_in_map_view": "āš€ā¸›ā¸´ā¸”ā¸”ā¸šāšƒā¸™āšā¸œā¸™ā¸—ā¸ĩāšˆ", "open_in_openstreetmap": "āš€ā¸›ā¸´ā¸”āšƒā¸™ OpenStreetMap", "open_the_search_filters": "āš€ā¸›ā¸´ā¸”ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞", "options": "ā¸•ā¸ąā¸§āš€ā¸Ĩ⏎⏭⏁", "or": "ā¸Ģ⏪⏎⏭", "organize_into_albums": "ā¸ˆā¸ąā¸”ā¸Ŗā¸°āš€ā¸šā¸ĩā¸ĸā¸šāšƒā¸Ēāšˆā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "organize_into_albums_description": "ā¸™ā¸ŗā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§āš€ā¸‚āš‰ā¸˛āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš‚ā¸”ā¸ĸāšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸‹ā¸´ā¸‡ā¸āšŒā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "organize_your_library": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸ⏇⏄ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", - "orientation": "āšā¸™ā¸§", "original": "ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", "other": "ā¸­ā¸ˇāšˆā¸™ āš†", "other_devices": "āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸­ā¸ˇāšˆā¸™", @@ -1585,9 +1609,12 @@ "partner_can_access_assets": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”ā¸ĸā¸āš€ā¸§āš‰ā¸™ā¸—ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸—ā¸´āš‰ā¸‡", "partner_can_access_location": "ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸—ā¸ĩāšˆā¸Ŗā¸šā¸›ā¸–ā¸šā¸ā¸–āšˆā¸˛ā¸ĸ", "partner_list_user_photos": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ {user}", + "partner_list_view_all": "ā¸”ā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "partner_page_empty_message": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸–ā¸šā¸āšā¸Šā¸ŖāšŒā¸ā¸ąā¸šā¸„ā¸šāšˆā¸Ģā¸š", "partner_page_no_more_users": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšƒā¸Ģāš‰āš€ā¸žā¸´āšˆā¸Ą", + "partner_page_partner_add_failed": "ā¸ā¸˛ā¸Ŗāš€ā¸žā¸´āšˆā¸Ąā¸„ā¸šāšˆā¸Ģā¸šā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "partner_page_select_partner": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸„ā¸šāšˆā¸Ģā¸š", + "partner_page_shared_to_title": "āšā¸Šā¸ŖāšŒā¸ā¸ąā¸š", "partner_page_stop_sharing_content": "{partner} ā¸ˆā¸°āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "partner_sharing": "āšā¸Šā¸ŖāšŒā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸„ā¸šāšˆā¸Ģā¸š", "partners": "ā¸„ā¸šāšˆā¸Ģā¸š", @@ -1604,31 +1631,40 @@ "pattern": "ā¸Ŗā¸šā¸›āšā¸šā¸š", "pause": "ā¸Ģā¸ĸ⏏⏔", "pause_memories": "ā¸Ģā¸ĸā¸¸ā¸”ā¸”ā¸šā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆāšā¸˛", - "paused": "ā¸Ģā¸ĸā¸¸ā¸”ā¸Šā¸ąāšˆā¸§ā¸„ā¸Ŗā¸˛ā¸§", + "paused": "ā¸Ģā¸ĸ⏏⏔", "pending": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸­", "people": "ā¸œā¸šāš‰ā¸„ā¸™", "people_edits_count": "{count, plural, one {# person} other {# people}} ā¸–ā¸šā¸āšā¸āš‰āš„ā¸‚", "people_feature_description": "āš€ā¸Ŗā¸ĩā¸ĸā¸ā¸”ā¸šā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸•ā¸˛ā¸Ąā¸œā¸šāš‰ā¸„ā¸™", + "people_sidebar_description": "āšā¸Ē⏔⏇ā¸Ĩā¸´ā¸‡ā¸āšŒāš„ā¸›ā¸ĸā¸ąā¸‡ā¸œā¸šāš‰ā¸„ā¸™āšƒā¸™āšā¸–ā¸šā¸”āš‰ā¸˛ā¸™ā¸‚āš‰ā¸˛ā¸‡", "permanent_deletion_warning": "āšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "permanent_deletion_warning_setting_description": "āš€ā¸•ā¸ˇā¸­ā¸™āš€ā¸Ąā¸ˇāšˆā¸­ā¸ˆā¸°ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸–ā¸˛ā¸§ā¸Ŗ", "permanently_delete": "ā¸Ĩā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "permanently_delete_assets_count": "ā¸Ĩ⏚ {count, plural, one {asset} other {assets}} ā¸—ā¸´āš‰ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ", - "permanently_delete_assets_prompt": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚{count, plural, one {ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰} other { # ā¸Ēā¸ˇāšˆā¸­āš€ā¸Ģā¸Ĩāšˆā¸˛ā¸™ā¸ĩāš‰}}⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Ĩ⏚{count, plural, one {ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰} other {ā¸Ēā¸ˇāšˆā¸­āš€ā¸Ģā¸Ĩāšˆā¸˛ā¸™ā¸ĩāš‰}}ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸”āš‰ā¸§ā¸ĸ", + "permanently_delete_assets_prompt": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩ⏚ {count, plural, one {this asset?} other {these # asset?}}⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗ ā¸ā¸˛ā¸Ŗā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸Ĩ⏚ {count, plural, one {it from its} other {them from their}} ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸”āš‰ā¸§ā¸ĸ", "permanently_deleted_asset": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", "permanently_deleted_assets_count": "ā¸Ĩ⏚ {count, plural, one {# asset} other {# assets}} āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸāšā¸Ĩāš‰ā¸§", - "permission": "ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒ", - "permission_empty": "ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡āš„ā¸Ąāšˆāš€ā¸§āš‰ā¸™ā¸§āšˆā¸˛ā¸‡", + "permission_onboarding_back": "⏁ā¸Ĩā¸ąā¸š", + "permission_onboarding_continue_anyway": "ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­", + "permission_onboarding_get_started": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™", + "permission_onboarding_go_to_settings": "āš„ā¸›ā¸ĸā¸ąā¸‡ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", + "permission_onboarding_permission_denied": "āš„ā¸Ąāšˆā¸­ā¸™ā¸¸ā¸ā¸˛ā¸• ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒāš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ Immich", + "permission_onboarding_permission_granted": "āšƒā¸Ģāš‰ā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ ā¸„ā¸¸ā¸“ā¸žā¸Ŗāš‰ā¸­ā¸Ąāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™āšā¸Ĩāš‰ā¸§", + "permission_onboarding_permission_limited": "ā¸Ēā¸´ā¸—ā¸˜āšŒā¸ˆā¸ŗā¸ā¸ąā¸” āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰ Immich ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸Ĩā¸°ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žāš„ā¸”āš‰ ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ēā¸´ā¸—ā¸˜ā¸´āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­", + "permission_onboarding_request": "Immich ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸ˆā¸°ā¸•āš‰ā¸­ā¸‡āš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸Ēā¸´ā¸—ā¸˜ā¸´āšŒā¸”ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­", "person": "ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", "person_age_months": "⏭⏞ā¸ĸ⏏ {months, plural, one {# āš€ā¸”ā¸ˇā¸­ā¸™} other {# āš€ā¸”ā¸ˇā¸­ā¸™}}", "person_age_year_months": "⏭⏞ā¸ĸ⏏ 1 ⏛ā¸ĩ {months, plural, one {# āš€ā¸”ā¸ˇā¸­ā¸™} other {# āš€ā¸”ā¸ˇā¸­ā¸™}}", - "person_age_years": "⏭⏞ā¸ĸ⏏ {years, plural, one {# ⏛ā¸ĩ} other {# ⏛ā¸ĩ}}", + "person_age_years": "⏭⏞ā¸ĸ⏏ {years, plural, other {# ⏛ā¸ĩ}}", "person_birthdate": "āš€ā¸ā¸´ā¸”āš€ā¸Ąā¸ˇāšˆā¸­ {date}", "person_hidden": "{name}{hidden, select, true { (ā¸‹āšˆā¸­ā¸™ā¸­ā¸ĸā¸šāšˆ)} other {}}", - "person_recognized": "āš€ā¸Ģāš‡ā¸™ā¸šā¸¸ā¸„ā¸„ā¸Ĩāšā¸Ĩāš‰ā¸§", + "person_selected": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸šā¸¸ā¸„ā¸„ā¸Ĩāšā¸Ĩāš‰ā¸§", "photo_shared_all_users": "ā¸”ā¸šāš€ā¸Ģā¸Ąā¸ˇā¸­ā¸™ā¸§āšˆā¸˛ā¸„ā¸¸ā¸“āš„ā¸”āš‰āšā¸Šā¸ŖāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸ā¸ąā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” ā¸Ģā¸Ŗā¸ˇā¸­ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆā¸Ąā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰āšƒā¸”ā¸—ā¸ĩāšˆā¸ˆā¸°āšā¸Šā¸ŖāšŒā¸”āš‰ā¸§ā¸ĸ", "photos": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "photos_and_videos": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž āšā¸Ĩ⏰ ⏧⏴⏔ā¸ĩāš‚ā¸­", + "photos_count": "{count, plural, one {{count, number} ā¸Ŗā¸šā¸›} other {{count, number} ā¸Ŗā¸šā¸›}}", "photos_from_previous_years": "ā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸā¸ˆā¸˛ā¸ā¸›ā¸ĩā¸āšˆā¸­ā¸™", + "photos_only": "āš€ā¸‰ā¸žā¸˛ā¸°ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "pick_a_location": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸•āšā¸˛āšā¸Ģā¸™āšˆā¸‡", "pick_custom_range": "⏁⏺ā¸Ģā¸™ā¸”ā¸Šāšˆā¸§ā¸‡āš€ā¸­ā¸‡", "pick_date_range": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Šāšˆā¸§ā¸‡ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆ", @@ -1647,8 +1683,6 @@ "play_original_video_setting_description": "āš€ā¸™āš‰ā¸™āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸šāšā¸—ā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆāšā¸›ā¸Ĩā¸‡āš„ā¸Ÿā¸ĨāšŒāšā¸Ĩāš‰ā¸§ ā¸Ģ⏞⏁ā¸Ēā¸ˇāšˆā¸­ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸šāš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸­ā¸˛ā¸ˆāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ĩāšˆā¸™āš„ā¸”āš‰ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", "play_transcoded_video": "āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆāšā¸›ā¸Ĩā¸‡āšā¸Ĩāš‰ā¸§", "please_auth_to_access": "āš‚ā¸›ā¸Ŗā¸”ā¸ĸ⏎⏙ā¸ĸā¸ąā¸™ā¸•ā¸ąā¸§ā¸•ā¸™āš€ā¸žā¸ˇāšˆā¸­āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇", - "plugin_method_filter_type": "ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", - "plugin_method_filter_type_description": "⏧⏴⏘ā¸ĩ⏙ā¸ĩāš‰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸Ŗā¸­ā¸‡ā¸•ā¸˛ā¸Ąā¸­ā¸ĩāš€ā¸§ā¸™ā¸—āšŒāšā¸Ĩā¸°ā¸•ā¸ąāš‰ā¸‡āšƒā¸Ģāš‰ā¸ā¸ąā¸™ā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™ā¸–ā¸ąā¸”āš†āš„ā¸›āš„ā¸Ąāšˆāšƒā¸Ģāš‰ā¸—ā¸ŗā¸•āšˆā¸­āš„ā¸”āš‰", "port": "ā¸žā¸­ā¸ŖāšŒā¸•", "preferences_settings_subtitle": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸ā¸ŗā¸Ģā¸™ā¸”ā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡āšā¸­ā¸›", "preferences_settings_title": "ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", @@ -1665,11 +1699,10 @@ "privacy": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸›āš‡ā¸™ā¸Ēāšˆā¸§ā¸™ā¸•ā¸ąā¸§", "profile": "āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒ", "profile_drawer_app_logs": "ā¸ā¸˛ā¸Ŗā¸šā¸ąā¸™ā¸—ā¸ļ⏁", + "profile_drawer_client_server_up_to_date": "āš„ā¸„ā¸Ĩāš€ā¸­ā¸™ā¸•āšŒāšā¸Ĩā¸°āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš€ā¸›āš‡ā¸™ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "⏭ā¸ĸā¸šāšˆāšƒā¸™āš‚ā¸Ģā¸Ąā¸”ā¸­āšˆā¸˛ā¸™ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸”ā¸ĩā¸ĸ⏧⏭ā¸ĸā¸šāšˆ ā¸ā¸”ā¸ ā¸˛ā¸žā¸œā¸šāš‰āšƒā¸Šāš‰ā¸„āš‰ā¸˛ā¸‡āš€ā¸žā¸ˇāšˆā¸­ā¸­ā¸­ā¸", "profile_image_of_user": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒā¸‚ā¸­ā¸‡ {user}", "profile_picture_set": "ā¸•ā¸ąāš‰ā¸‡ā¸ ā¸˛ā¸žāš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒāšā¸Ĩāš‰ā¸§", - "projection_type": "⏧⏴⏘ā¸ĩ⏁⏞⏪⏉⏞ā¸ĸā¸ ā¸˛ā¸ž", "public_album": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ēā¸˛ā¸˜ā¸˛ā¸Ŗā¸“ā¸°", "public_share": "āšā¸Šā¸ŖāšŒāšā¸šā¸šā¸Ēā¸˛ā¸˜ā¸˛ā¸Ŗā¸“ā¸°", "purchase_account_info": "ā¸œā¸šāš‰ā¸Ēā¸™ā¸ąā¸šā¸Ē⏙⏏⏙", @@ -1688,6 +1721,7 @@ "purchase_individual_description_2": "ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸œā¸šāš‰ā¸Ēā¸™ā¸ąā¸šā¸Ē⏙⏏⏙", "purchase_individual_title": "ā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸—ā¸ąāšˆā¸§āš„ā¸›", "purchase_input_suggestion": "ā¸Ąā¸ĩ⏪ā¸Ģā¸ąā¸Ē⏜ā¸Ĩā¸´ā¸•ā¸ ā¸ąā¸“ā¸‘āšŒā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ? āšƒā¸Ēāšˆā¸Ŗā¸Ģā¸ąā¸Ēā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡", + "purchase_license_subtitle": "ā¸‹ā¸ˇāš‰ā¸­ Immich āš€ā¸žā¸ˇāšˆā¸­ā¸Ēā¸™ā¸ąā¸šā¸Ēā¸™ā¸¸ā¸™ā¸ā¸˛ā¸Ŗā¸žā¸ąā¸’ā¸™ā¸˛ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸•āšˆā¸­āš€ā¸™ā¸ˇāšˆā¸­ā¸‡", "purchase_lifetime_description": "ā¸‹ā¸ˇāš‰ā¸­ā¸•ā¸Ĩā¸­ā¸”ā¸Šā¸ĩā¸ž", "purchase_option_title": "ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗā¸‹ā¸ˇāš‰ā¸­", "purchase_panel_info_1": "ā¸ā¸˛ā¸Ŗā¸žā¸ąā¸’ā¸™ā¸˛ Immich ā¸•āš‰ā¸­ā¸‡āšƒā¸Šāš‰āš€ā¸§ā¸Ĩā¸˛āšā¸Ĩā¸°ā¸„ā¸§ā¸˛ā¸Ąā¸žā¸ĸ⏞ā¸ĸā¸˛ā¸Ąā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸Ąā¸˛ā¸ āšā¸Ĩā¸°āš€ā¸Ŗā¸˛ā¸Ąā¸ĩ⏧⏴⏍⏧⏁⏪⏗ā¸ĩāšˆā¸—ā¸ŗā¸‡ā¸˛ā¸™āš€ā¸•āš‡ā¸Ąāš€ā¸§ā¸Ĩā¸˛āš€ā¸žā¸ˇāšˆā¸­ā¸žā¸ąā¸’ā¸™ā¸˛āšƒā¸Ģāš‰ā¸”ā¸ĩ⏗ā¸ĩāšˆā¸Ēā¸¸ā¸”āš€ā¸—āšˆā¸˛ā¸—ā¸ĩāšˆā¸ˆā¸°ā¸—ā¸ŗāš„ā¸”āš‰ ā¸ ā¸˛ā¸Ŗā¸ā¸´ā¸ˆā¸‚ā¸­ā¸‡āš€ā¸Ŗā¸˛ā¸„ā¸ˇā¸­ā¸ā¸˛ā¸Ŗā¸—ā¸ŗāšƒā¸Ģāš‰ā¸‹ā¸­ā¸Ÿā¸•āšŒāšā¸§ā¸ŖāšŒāš‚ā¸­āš€ā¸žā¸™ā¸‹ā¸­ā¸ŖāšŒā¸Ēāšā¸Ĩā¸°āšā¸™ā¸§ā¸—ā¸˛ā¸‡ā¸›ā¸ā¸´ā¸šā¸ąā¸•ā¸´ā¸—ā¸˛ā¸‡ā¸˜ā¸¸ā¸Ŗā¸ā¸´ā¸ˆā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡ā¸•ā¸˛ā¸Ąā¸ˆā¸Ŗā¸´ā¸ĸā¸˜ā¸Ŗā¸Ŗā¸Ąā¸ā¸Ĩ⏞ā¸ĸāš€ā¸›āš‡ā¸™āšā¸Ģā¸Ĩāšˆā¸‡ā¸Ŗā¸˛ā¸ĸāš„ā¸”āš‰ā¸—ā¸ĩāšˆā¸ĸā¸ąāšˆā¸‡ā¸ĸ⏎⏙ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸™ā¸ąā¸ā¸žā¸ąā¸’ā¸™ā¸˛ āšā¸Ĩ⏰ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ŗā¸°ā¸šā¸šā¸™ā¸´āš€ā¸§ā¸¨ā¸—ā¸ĩāšˆāš€ā¸„ā¸˛ā¸Ŗā¸žā¸„ā¸§ā¸˛ā¸Ąāš€ā¸›āš‡ā¸™ā¸Ēāšˆā¸§ā¸™ā¸•ā¸ąā¸§ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸—ā¸˛ā¸‡āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ˇāšˆā¸™ā¸—ā¸ĩāšˆāš€ā¸›āš‡ā¸™ā¸Ŗā¸šā¸›ā¸˜ā¸Ŗā¸Ŗā¸Ąāšā¸—ā¸™ā¸šā¸Ŗā¸´ā¸ā¸˛ā¸Ŗā¸„ā¸Ĩā¸˛ā¸§ā¸”āšŒā¸—ā¸ĩāšˆāš€ā¸­ā¸˛ā¸Ŗā¸ąā¸”āš€ā¸­ā¸˛āš€ā¸›ā¸Ŗā¸ĩā¸ĸ⏚", @@ -1703,7 +1737,6 @@ "purchase_server_description_2": "ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸œā¸šāš‰ā¸Ēā¸™ā¸ąā¸šā¸Ē⏙⏏⏙", "purchase_server_title": "āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", "purchase_settings_server_activated": "⏪ā¸Ģā¸ąā¸Ē⏜ā¸Ĩā¸´ā¸•ā¸ ā¸ąā¸“ā¸‘āšŒāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗāš‚ā¸”ā¸ĸā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩ⏪⏰⏚⏚", - "query_asset_id": "ā¸„āš‰ā¸™ā¸Ģ⏞ ID ⏂⏭⏇ā¸Ēā¸ˇāšˆā¸­", "queue_status": "ā¸ˆā¸ŗā¸™ā¸§ā¸™ā¸„ā¸´ā¸§ {count}/{total}", "rate_asset": "āšƒā¸Ģāš‰ā¸„ā¸°āšā¸™ā¸™ā¸Ēā¸ˇāšˆā¸­", "rating": "ā¸ā¸˛ā¸Ŗāšƒā¸Ģāš‰ā¸„ā¸°āšā¸™ā¸™", @@ -1720,58 +1753,56 @@ "reassigned_assets_to_new_person": "ā¸Ąā¸­ā¸šā¸Ģā¸Ąā¸˛ā¸ĸ {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­} other {# ā¸Ēā¸ˇāšˆā¸­}} āšƒā¸Ģāš‰ā¸ā¸ąā¸šā¸šā¸¸ā¸„ā¸„ā¸Ĩāšƒā¸Ģā¸Ąāšˆ", "reassing_hint": "ā¸Ąā¸­ā¸šā¸Ģā¸Ąā¸˛ā¸ĸā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āšƒā¸Ģāš‰ā¸ā¸ąā¸šā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏗ā¸ĩāšˆā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆāšā¸Ĩāš‰ā¸§", "recent": "ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", + "recent_albums": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "recent_searches": "ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "recently_added": "āš€ā¸žā¸´āšˆā¸Ąā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", - "recently_added_body": "āš„ā¸›ā¸Ģā¸™āš‰ā¸˛ā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏗⏏⏁⏭ā¸ĸāšˆā¸˛ā¸‡ā¸—ā¸ĩāšˆā¸„ā¸¸ā¸“āš€ā¸žā¸ˇāšˆā¸‡āš€ā¸žā¸´āšˆā¸Ąā¸Ąā¸˛", - "recently_added_description": "ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ Immich āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸ˆā¸˛ā¸āš€ā¸§ā¸Ĩ⏞⏗ā¸ĩāšˆā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ąāš‰ā¸™ āš† ā¸–ā¸šā¸ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸Ąā¸˛", "recently_added_page_title": "āš€ā¸žā¸´āšˆā¸Ąā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", - "recently_added_title": "āš€ā¸žā¸´āšˆā¸Ąā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "recently_taken": "ā¸–āšˆā¸˛ā¸ĸā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", + "recently_taken_page_title": "ā¸–āšˆā¸˛ā¸ĸā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "refresh": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Š", "refresh_encoded_videos": "āš‚ā¸Ģā¸Ĩ⏔⏁⏞⏪ encoded ⏧⏴⏔ā¸ĩāš‚ā¸­āšƒā¸Ģā¸Ąāšˆ", "refresh_faces": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šāšƒā¸šā¸Ģā¸™āš‰ā¸˛", "refresh_metadata": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ā¸”ā¸˛ā¸•āš‰ā¸˛", "refresh_thumbnails": "⏪ā¸ĩāš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸› thumbnails", "refreshed": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Š", + "refreshes_every_file": "⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸—ā¸¸ā¸āš„ā¸Ÿā¸ĨāšŒ", "refreshing_encoded_video": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ŗā¸Ģā¸ąā¸Ē⏧⏴⏔ā¸ĩāš‚ā¸­", "refreshing_faces": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸ĩāš€ā¸Ÿā¸Ŗā¸Šāšƒā¸šā¸Ģā¸™āš‰ā¸˛", "refreshing_metadata": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ā¸”ā¸˛ā¸•āš‰ā¸˛", "regenerating_thumbnails": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ŗā¸šā¸› thumbnails āšƒā¸Ģā¸Ąāšˆ", - "remote": "ā¸‚āš‰ā¸˛ā¸‡ā¸™ā¸­ā¸", - "remote_assets": "ā¸Ēā¸ˇāšˆā¸­ā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", - "remote_media_summary": "ā¸ ā¸˛ā¸žā¸Ŗā¸§ā¸Ąā¸Ēā¸ˇāšˆā¸­ā¸šā¸™āš€ā¸‹ā¸Ŗā¸ŸāšŒāš€ā¸§ā¸­ā¸ŖāšŒ", "remove": "ā¸Ĩ⏚", "remove_assets_album_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚ {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­} other {# ā¸Ēā¸ˇāšˆā¸­}} ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą ?", "remove_assets_shared_link_confirmation": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}} ⏭⏭⏁⏈⏞⏁ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "remove_assets_title": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­āšƒā¸Šāšˆāš„ā¸Ģā¸Ą ?", "remove_custom_date_range": "ā¸Ĩā¸šā¸ā¸˛ā¸Ŗā¸›ā¸Ŗā¸ąā¸šā¸Šāšˆā¸§ā¸‡āš€ā¸§ā¸Ĩ⏞", - "remove_filter": "ā¸Ĩā¸šā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡", + "remove_deleted_assets": "ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸Ĩ⏚", "remove_from_album": "ā¸Ĩā¸šā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "remove_from_album_action_prompt": "ā¸Ĩ⏚ {count} ā¸ ā¸˛ā¸žā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "remove_from_favorites": "āš€ā¸­ā¸˛ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", - "remove_from_lock_folder_action_prompt": "ā¸Ĩ⏚ {count} ā¸ˆā¸˛ā¸āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸āšā¸Ĩāš‰ā¸§", - "remove_from_locked_folder": "ā¸Ĩā¸šā¸ˆā¸˛ā¸āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸", - "remove_from_locked_folder_confirmation": "ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸™ā¸ŗā¸ ā¸˛ā¸žāšā¸Ĩā¸°ā¸§ā¸´ā¸”ā¸´āš‚ā¸­ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒā¸Ĩāš‡ā¸­ā¸ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ ā¸Ēā¸ˇāšˆā¸­ā¸ˆā¸°āš„ā¸›ā¸›ā¸Ŗā¸˛ā¸ā¸āšƒā¸™āš„ā¸Ĩ⏚⏪⏞⏪ā¸ĩāšˆ", "remove_from_shared_link": "ā¸Ĩ⏚⏭⏭⏁⏈⏞⏁ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", "remove_memory": "ā¸Ĩā¸šā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "remove_photo_from_memory": "ā¸Ĩā¸šā¸Ŗā¸šā¸›ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗā¸™ā¸ĩāš‰", - "remove_tag": "ā¸Ĩā¸šāšā¸—āš‡ā¸", + "remove_url": "ā¸Ĩ⏚ URL", "remove_user": "ā¸Ĩā¸šā¸œā¸šāš‰āšƒā¸Šāš‰", - "removed_api_key": "ā¸Ĩā¸šā¸„ā¸ĩā¸ĸāšŒ API āšā¸Ĩāš‰ā¸§: {name}", + "removed_api_key": "API ⏄ā¸ĩā¸ĸāšŒā¸‚ā¸­ā¸‡: {name} ā¸–ā¸šā¸ā¸Ĩā¸šāšā¸Ĩāš‰ā¸§", "removed_from_archive": "ā¸Ĩā¸šā¸ˆā¸˛ā¸āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§", "removed_from_favorites": "ā¸Ĩ⏚⏈⏞⏁⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", "removed_from_favorites_count": "{count, plural, other {ā¸–ā¸šā¸ā¸Ĩ⏚#}} ⏈⏞⏁⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”āšā¸Ĩāš‰ā¸§", "removed_memory": "ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸Ĩ⏚", + "removed_photo_from_memory": "ā¸Ŗā¸šā¸›ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸Ĩā¸šā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", "removed_tagged_assets": "ā¸Ĩā¸šāšā¸—āš‡ā¸ā¸ˆā¸˛ā¸ {count, plural, one {# ā¸Ēā¸ˇāšˆā¸­} other {# ā¸Ēā¸ˇāšˆā¸­}}", "rename": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸Šā¸ˇāšˆā¸­", - "repository": "⏄ā¸Ĩā¸ąā¸‡āš€ā¸āš‡ā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", + "repair": "ā¸‹āšˆā¸­ā¸Ąāšā¸‹ā¸Ą", + "repair_no_results_message": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸‹āšˆā¸­ā¸Ąāšā¸‹ā¸Ąāš„ā¸”āš‰", + "replace_with_upload": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸—ā¸ąā¸šā¸Ŗā¸šā¸›ā¸Ģ⏪⏎⏭⏧⏴⏔ā¸ĩāš‚ā¸­ā¸™ā¸ĩāš‰", "require_password": "ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", + "require_user_to_change_password_on_first_login": "ā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ āšƒā¸™ā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ā¸Ēā¸šāšˆā¸Ŗā¸°ā¸šā¸šā¸„ā¸Ŗā¸ąāš‰ā¸‡āšā¸Ŗā¸", "rescan": "ā¸Ēāšā¸ā¸™āšƒā¸Ģā¸Ąāšˆ", "reset": "⏪ā¸ĩāš€ā¸‹āš‡ā¸•", "reset_password": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āšƒā¸Ģā¸Ąāšˆ", "reset_people_visibility": "ā¸›ā¸Ŗā¸ąā¸šā¸ā¸˛ā¸Ŗā¸Ąā¸­ā¸‡āš€ā¸Ģāš‡ā¸™āšƒā¸Ģā¸Ąāšˆ", "reset_pin_code": "ā¸•ā¸ąāš‰ā¸‡ā¸Ŗā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN) āšƒā¸Ģā¸Ąāšˆ", "reset_pin_code_description": "ā¸Ģ⏞⏁⏄⏏⏓ā¸Ĩā¸ˇā¸Ąā¸Ŗā¸Ģā¸ąā¸Ē PIN ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸´ā¸”ā¸•āšˆā¸­ā¸œā¸šāš‰ā¸”ā¸šāšā¸Ĩāš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒāš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ēāš„ā¸”āš‰", + "reset_pin_code_success": "⏪ā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ē PIN ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "reset_pin_code_with_password": "⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ŗā¸ĩāš€ā¸‹āš‡ā¸•ā¸Ŗā¸Ģā¸ąā¸Ē PIN ā¸”āš‰ā¸§ā¸ĸ⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸”āš‰āš€ā¸Ēā¸Ąā¸­", "reset_sqlite": "⏪ā¸ĩāš€ā¸‹āš‡ā¸•ā¸ā¸˛ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ SQLite", "reset_sqlite_clear_app_data": "ā¸Ĩāš‰ā¸˛ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", @@ -1785,10 +1816,12 @@ "resolved_all_duplicates": "āšā¸āš‰āš„ā¸‚ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸‹āš‰ā¸ŗā¸‹āš‰ā¸­ā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "restore": "āš€ā¸Ŗā¸ĩā¸ĸ⏁⏄⏎⏙", "restore_all": "āš€ā¸Ŗā¸ĩā¸ĸā¸ā¸„ā¸ˇā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "restore_trash_action_prompt": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸ˆā¸˛ā¸ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšā¸Ĩāš‰ā¸§", "restore_user": "āš€ā¸Ŗā¸ĩā¸ĸā¸ā¸„ā¸ˇā¸™ā¸œā¸šāš‰āšƒā¸Šāš‰", "restored_asset": "asset ā¸–ā¸šā¸ā¸„ā¸ˇā¸™ā¸„āšˆā¸˛", "resume": "ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸•āšˆā¸­", "resume_paused_jobs": "ā¸”ā¸ŗāš€ā¸™ā¸´ā¸™ā¸ā¸˛ā¸Ŗā¸•āšˆā¸­ {count, plural, one {# ⏇⏞⏙⏗ā¸ĩāšˆā¸Ģā¸ĸā¸¸ā¸”ā¸žā¸ąā¸āš„ā¸§āš‰} other {# ⏇⏞⏙⏗ā¸ĩāšˆā¸Ģā¸ĸā¸¸ā¸”ā¸žā¸ąā¸āš„ā¸§āš‰}}", + "retry_upload": "ā¸Ĩā¸­ā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšƒā¸Ģā¸Ąāšˆ", "review_duplicates": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚⏪⏞ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸ā¸ąā¸™", "review_large_files": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šāš„ā¸Ÿā¸ĨāšŒā¸—ā¸ĩāšˆā¸Ąā¸ĩā¸‚ā¸™ā¸˛ā¸”āšƒā¸Ģā¸āšˆ", "role": "ā¸šā¸—ā¸šā¸˛ā¸—", @@ -1796,20 +1829,20 @@ "role_viewer": "ā¸”ā¸š", "running": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸—ā¸ŗā¸‡ā¸˛ā¸™", "save": "ā¸šā¸ąā¸™ā¸—ā¸ļ⏁", + "save_to_gallery": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸āš„ā¸›ā¸ĸā¸ąā¸‡āšā¸ā¸Ĩāš€ā¸Ĩ⏭⏪ā¸ĩ", "saved": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸āšā¸Ĩāš‰ā¸§", - "saved_api_key": "ā¸šā¸ąā¸™ā¸—ā¸ļ⏁⏄ā¸ĩā¸ĸāšŒ API āšā¸Ĩāš‰ā¸§", + "saved_api_key": "ā¸šā¸ąā¸™ā¸—ā¸ļ⏁ API ⏄ā¸ĩā¸ĸāšŒ āšā¸Ĩāš‰ā¸§", "saved_profile": "āšā¸āš‰āš„ā¸‚āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "saved_settings": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ", "say_something": "ā¸žā¸šā¸”ā¸­ā¸°āš„ā¸Ŗā¸Ēā¸ąā¸ā¸­ā¸ĸāšˆā¸˛ā¸‡", "scaffold_body_error_occurred": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", - "scaffold_body_error_unrecoverable": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸šāš‰ā¸„ā¸ˇā¸™āš„ā¸”āš‰ āš‚ā¸›ā¸Ŗā¸”āšā¸Šā¸ŖāšŒā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšā¸Ĩ⏰ Stack trace āš„ā¸›ā¸ĸā¸ąā¸‡ Discord ā¸Ģ⏪⏎⏭ GitHub āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰āš€ā¸Ŗā¸˛ā¸Šāšˆā¸§ā¸ĸāš€ā¸Ģā¸Ĩ⏎⏭ ā¸Ģā¸˛ā¸āš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸„ā¸ŗāšā¸™ā¸°ā¸™ā¸ŗ ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩāš‰ā¸˛ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸­ā¸›āš„ā¸”āš‰ā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡", + "scaffold_body_error_unrecoverable": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸ā¸šāš‰ā¸„ā¸ˇā¸™āš„ā¸”āš‰ āš‚ā¸›ā¸Ŗā¸”āšā¸Šā¸ŖāšŒā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšā¸Ĩ⏰ stack trace āš„ā¸›ā¸ĸā¸ąā¸‡ Discord ā¸Ģ⏪⏎⏭ GitHub āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Ģāš‰āš€ā¸Ŗā¸˛ā¸Šāšˆā¸§ā¸ĸāš€ā¸Ģā¸Ĩ⏎⏭ ā¸Ģā¸˛ā¸āš„ā¸”āš‰ā¸Ŗā¸ąā¸šā¸„ā¸ŗāšā¸™ā¸°ā¸™ā¸ŗ ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ĩāš‰ā¸˛ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšā¸­ā¸›āš„ā¸”āš‰ā¸”āš‰ā¸˛ā¸™ā¸Ĩāšˆā¸˛ā¸‡", "scan": "ā¸Ēāšā¸ā¸™", "scan_all_libraries": "ā¸Ēāšā¸ā¸™ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸žā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "scan_library": "ā¸Ēāšā¸ā¸™", + "scan_settings": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸Ēāšā¸ā¸™", "scanning": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēāšā¸ā¸™", "scanning_for_album": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ēāšā¸ā¸™ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą...", - "screencast_mode_description": "āšƒā¸Ģāš‰āšā¸Ēā¸”ā¸‡ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸„ā¸ĩā¸ĸāšŒā¸šā¸­ā¸ŖāšŒā¸”ā¸ā¸ąā¸šāš€ā¸Ąā¸˛ā¸ĒāšŒā¸‚ā¸ļāš‰ā¸™ā¸šā¸™ā¸ˆā¸­", - "screencast_mode_title": "āš€ā¸›ā¸´ā¸”/ā¸›ā¸´ā¸”āš‚ā¸Ģā¸Ąā¸”ā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­", "search": "ā¸„āš‰ā¸™ā¸Ģ⏞", "search_albums": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "search_by_context": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąā¸šā¸Ŗā¸´ā¸šā¸—", @@ -1817,8 +1850,6 @@ "search_by_description_example": "ā¸§ā¸ąā¸™ā¸›ā¸ĩā¸™āš€ā¸‚ā¸˛āšƒā¸™ā¸‹ā¸˛ā¸›ā¸˛", "search_by_filename": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąā¸Šā¸ˇāšˆā¸­āš„ā¸Ÿā¸ĨāšŒā¸Ģā¸Ŗā¸ˇā¸­ā¸™ā¸˛ā¸Ąā¸Ē⏁⏏ā¸Ĩāš„ā¸Ÿā¸ĨāšŒ", "search_by_filename_example": "ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡. IMG_1234.JPG ā¸Ģ⏪⏎⏭ PNG", - "search_by_full_path": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸ˆā¸˛ā¸ā¸žā¸˛ā¸˜āš€ā¸•āš‡ā¸Ąā¸Ģā¸Ŗā¸ˇā¸­āš‚ā¸Ÿā¸Ĩāš€ā¸”ā¸­ā¸ŖāšŒ", - "search_by_full_path_example": "/ā¸Ēā¸Ąā¸Šā¸˛ā¸ĸ/⏇⏞⏙/ā¸›ā¸Ŗā¸´āš‰ā¸™3ā¸Ąā¸´ā¸•ā¸´/2569-07-01 ‐ ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸„ā¸ŗā¸§āšˆā¸˛ â€œā¸‡ā¸˛ā¸™â€ “3ā¸Ąā¸´ā¸•ā¸´â€ â€œā¸›ā¸Ŗā¸´āš‰ā¸™â€ “2569” āšā¸Ĩā¸°ā¸­ā¸ˇāšˆā¸™āš† āš„ā¸”āš‰", "search_by_ocr": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸”āš‰ā¸§ā¸ĸ OCR", "search_by_ocr_example": "ā¸Ĩā¸˛āš€ā¸•āš‰", "search_camera_lens_model": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸Ŗā¸¸āšˆā¸™āš€ā¸Ĩ⏙ā¸ĒāšŒ...", @@ -1838,9 +1869,9 @@ "search_filter_location_title": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡", "search_filter_media_type": "ā¸›ā¸Ŗā¸°āš€ā¸ ā¸—ā¸Ēā¸ˇāšˆā¸­", "search_filter_media_type_title": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸›ā¸Ŗā¸°āš€ā¸ ā¸—ā¸Ēā¸ˇāšˆā¸­", + "search_filter_ocr": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸”āš‰ā¸§ā¸ĸ OCR", "search_filter_people_title": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸œā¸šāš‰ā¸„ā¸™", "search_filter_star_rating": "ā¸„ā¸°āšā¸™ā¸™ā¸”ā¸˛ā¸§", - "search_filter_tags_title": "āš€ā¸Ĩā¸ˇā¸­ā¸āšā¸—āš‡ā¸", "search_for": "ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸š", "search_for_existing_person": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏗ā¸ĩāšˆā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆ", "search_no_more_result": "āš„ā¸Ąāšˆā¸Ąā¸ĩ⏜ā¸Ĩā¸Ĩā¸ąā¸žā¸˜āšŒāš€ā¸žā¸´āšˆā¸Ąāš€ā¸•ā¸´ā¸Ą", @@ -1848,13 +1879,25 @@ "search_no_people_named": "āš„ā¸Ąāšˆā¸žā¸šā¸œā¸šāš‰ā¸„ā¸™ā¸—ā¸ĩāšˆā¸Šā¸ˇāšˆā¸­ \"{name}\"", "search_no_result": "āš„ā¸Ąāšˆā¸žā¸šā¸œā¸Ĩā¸Ĩā¸ąā¸žā¸˜āšŒ ā¸Ĩā¸­ā¸‡āšƒā¸Šāš‰ā¸„ā¸ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ˇāšˆā¸™ā¸Ģ⏪⏎⏭⏁ā¸Ĩā¸¸āšˆā¸Ąā¸„ā¸ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ˇāšˆā¸™", "search_options": "ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞", + "search_page_categories": "ā¸Ģā¸Ąā¸§ā¸”ā¸Ģā¸Ąā¸šāšˆ", + "search_page_motion_photos": "ā¸ ā¸˛ā¸žāš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģ⏧", + "search_page_no_objects": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏪⏞ā¸ĸ⏁⏞⏪", + "search_page_no_places": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸Ē⏖⏞⏙⏗ā¸ĩāšˆ", + "search_page_screenshots": "āšā¸„ā¸›ā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­", "search_page_search_photos_videos": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", + "search_page_selfies": "āš€ā¸‹ā¸Ĩ⏟ā¸ĩāšˆ", + "search_page_things": "ā¸Ēā¸´āšˆā¸‡ā¸‚ā¸­ā¸‡", "search_page_view_all_button": "ā¸”ā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "search_page_your_activity": "ā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ąā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", + "search_page_your_map": "āšā¸œā¸™ā¸—ā¸ĩāšˆā¸„ā¸¸ā¸“", "search_people": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸œā¸šāš‰ā¸„ā¸™", "search_places": "ā¸„āš‰ā¸™ā¸Ģ⏞ā¸Ē⏖⏞⏙⏗ā¸ĩāšˆ", "search_rating": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąāš€ā¸Ŗā¸•ā¸•ā¸´āš‰ā¸‡...", + "search_result_page_new_search_hint": "ā¸„āš‰ā¸™ā¸Ģā¸˛āšƒā¸Ģā¸Ąāšˆ", "search_settings": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞", "search_state": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąā¸Ŗā¸ąā¸", + "search_suggestion_list_smart_search_hint_1": "ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģā¸˛ā¸­ā¸ąā¸ˆā¸‰ā¸Ŗā¸´ā¸ĸā¸°āš€ā¸›ā¸´ā¸”āš€ā¸›āš‡ā¸™ā¸„āšˆā¸˛āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™ āš€ā¸žā¸ˇāšˆā¸­ā¸„āš‰ā¸™ā¸Ģ⏞ metadata āšƒā¸Ģāš‰āšƒā¸Šāš‰āš„ā¸§ā¸ĸā¸˛ā¸ā¸Ŗā¸“āšŒ", + "search_suggestion_list_smart_search_hint_2": "m:ā¸„ā¸ŗā¸„āš‰ā¸™ā¸Ģ⏞", "search_tags": "ā¸„āš‰ā¸™ā¸Ģā¸˛āšā¸—āš‡ā¸", "search_timezone": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšā¸Ĩā¸°āš€ā¸§ā¸Ĩ⏞", "search_type": "ā¸„āš‰ā¸™ā¸Ģā¸˛ā¸•ā¸˛ā¸Ąā¸›ā¸Ŗā¸°āš€ā¸ ā¸—", @@ -1868,6 +1911,7 @@ "select_albums": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "select_all": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "select_all_duplicates": "āš€ā¸Ĩ⏎⏭⏁⏪⏞ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "select_all_in": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ {group}", "select_avatar_color": "āš€ā¸Ĩ⏎⏭⏁ā¸Ēā¸ĩā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡ā¸‚ā¸­ā¸‡ā¸Ŗā¸šā¸›āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒ", "select_count": "{count, plural, one {āš€ā¸Ĩ⏎⏭⏁ #} other {āš€ā¸Ĩ⏎⏭⏁ #}}", "select_cutoff_date": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸•ā¸ąā¸”ā¸Ŗā¸­ā¸š", @@ -1875,16 +1919,16 @@ "select_featured_photo": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ ā¸˛ā¸žāš€ā¸”āšˆā¸™", "select_from_computer": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ˆā¸˛ā¸ā¸„ā¸­ā¸Ąā¸žā¸´ā¸§āš€ā¸•ā¸­ā¸ŖāšŒ", "select_keep_all": "āš€ā¸Ĩā¸ˇā¸­ā¸āš€ā¸āš‡ā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "select_library_owner": "āš€ā¸Ĩā¸ˇā¸­ā¸āš€ā¸ˆāš‰ā¸˛ā¸‚ā¸­ā¸‡ā¸„ā¸Ĩā¸ąā¸‡ā¸ ā¸˛ā¸ž", "select_new_face": "āš€ā¸Ĩā¸ˇā¸­ā¸āšƒā¸šā¸Ģā¸™āš‰ā¸˛āšƒā¸Ģā¸Ąāšˆ", "select_people": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸œā¸šāš‰ā¸„ā¸™", "select_person": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", "select_person_to_tag": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", "select_photos": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", - "select_quality": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸ž", "select_trash_all": "āš€ā¸Ĩā¸ˇā¸­ā¸āšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "select_user_for_sharing_page_err_album": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "selected": "āš€ā¸Ĩ⏎⏭⏁", "selected_count": "{count, plural, other {# āš€ā¸Ĩā¸ˇā¸­ā¸āšā¸Ĩāš‰ā¸§}}", - "selected_gps_coordinates": "ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ GPS ⏗ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁", "send_message": "ā¸Ēāšˆā¸‡ā¸‚āš‰ā¸­ā¸„ā¸§ā¸˛ā¸Ą", "send_welcome_email": "ā¸Ēāšˆā¸‡ā¸­ā¸ĩāš€ā¸Ąā¸Ĩā¸•āš‰ā¸­ā¸™ā¸Ŗā¸ąā¸š", "server_endpoint": "⏛ā¸Ĩ⏞ā¸ĸā¸—ā¸˛ā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", @@ -1906,7 +1950,6 @@ "set_profile_picture": "ā¸•ā¸ąāš‰ā¸‡ā¸Ŗā¸šā¸›āš‚ā¸›ā¸Ŗāš„ā¸Ÿā¸ĨāšŒ", "set_slideshow_to_fullscreen": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗā¸™ā¸ŗāš€ā¸Ēā¸™ā¸­āš€ā¸•āš‡ā¸Ąā¸ˆā¸­", "set_stack_primary_asset": "ā¸•ā¸ąāš‰ā¸‡āš€ā¸›āš‡ā¸™ā¸Ēā¸ˇāšˆā¸­ā¸Ģā¸Ĩā¸ąā¸", - "setting_image_navigation_enable_subtitle": "ā¸–āš‰ā¸˛āš€ā¸›ā¸´ā¸” ⏄⏏⏓ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš„ā¸›ā¸ĸā¸ąā¸‡ā¸ ā¸˛ā¸žā¸āšˆā¸­ā¸™ā¸Ģā¸™āš‰ā¸˛ā¸Ģā¸Ŗā¸ˇā¸­ā¸ ā¸˛ā¸žā¸–ā¸ąā¸”āš„ā¸›āš„ā¸”āš‰ā¸”āš‰ā¸§ā¸ĸā¸ā¸˛ā¸Ŗāšā¸•ā¸°ā¸ā¸ąāšˆā¸‡ā¸‹āš‰ā¸˛ā¸ĸā¸Ē⏏⏔/⏂⏧⏞ā¸Ēā¸¸ā¸”ā¸‚ā¸­ā¸‡ā¸ˆā¸­", "setting_image_navigation_enable_title": "āšā¸•ā¸°āš€ā¸žā¸ˇāšˆā¸­ā¸™ā¸ŗā¸—ā¸˛ā¸‡", "setting_image_navigation_title": "ā¸ā¸˛ā¸Ŗā¸™ā¸ŗā¸—ā¸˛ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "setting_image_viewer_help": "ā¸•ā¸ąā¸§ā¸”ā¸šā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ĸāšˆā¸­ā¸āšˆā¸­ā¸™ āšā¸Ĩāš‰ā¸§ā¸ˆā¸°āš‚ā¸Ģā¸Ĩā¸”ā¸žā¸Ŗā¸ĩ⏧⏴⏧⏂⏙⏞⏔⏁ā¸Ĩ⏞⏇ (ā¸–āš‰ā¸˛āš€ā¸›ā¸´ā¸”) āšā¸Ĩāš‰ā¸§ā¸„āšˆā¸­ā¸ĸāš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š (ā¸–āš‰ā¸˛āš€ā¸›ā¸´ā¸”)", @@ -1914,31 +1957,41 @@ "setting_image_viewer_original_title": "āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", "setting_image_viewer_preview_subtitle": "āš€ā¸›ā¸´ā¸”āš€ā¸žā¸ˇāšˆā¸­āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸„ā¸§ā¸˛ā¸Ąā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔⏛⏞⏙⏁ā¸Ĩ⏞⏇ āš€ā¸›ā¸´ā¸”āš€ā¸žā¸ˇāšˆā¸­āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸šāš‚ā¸”ā¸ĸ⏕⏪⏇ā¸Ģā¸Ŗā¸ˇā¸­āšƒā¸Šāš‰āšā¸„āšˆā¸Ŗā¸šā¸›ā¸ĸāšˆā¸­", "setting_image_viewer_preview_title": "āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡", + "setting_image_viewer_title": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "setting_languages_apply": "ā¸šā¸ąā¸™ā¸—ā¸ļ⏁", "setting_languages_subtitle": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸ ā¸˛ā¸Šā¸˛ā¸‚ā¸­ā¸‡āšā¸­ā¸›", + "setting_notifications_notify_failures_grace_period": "āšā¸ˆāš‰ā¸‡ā¸ā¸˛ā¸Ŗā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧: {duration}", + "setting_notifications_notify_hours": "{count} ā¸Šā¸ąāšˆā¸§āš‚ā¸Ąā¸‡", + "setting_notifications_notify_immediately": "āš‚ā¸”ā¸ĸā¸—ā¸ąā¸™ā¸—ā¸ĩ", "setting_notifications_notify_minutes": "{count} ⏙⏞⏗ā¸ĩ", + "setting_notifications_notify_never": "āš„ā¸Ąāšˆāš€ā¸„ā¸ĸ", "setting_notifications_notify_seconds": "{count} ⏧⏴⏙⏞⏗ā¸ĩ", + "setting_notifications_single_progress_subtitle": "ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸ˇā¸šā¸Ģā¸™āš‰ā¸˛ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš‚ā¸”ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸•āšˆā¸­ā¸Ēā¸ˇāšˆā¸­", + "setting_notifications_single_progress_title": "āšā¸Ē⏔⏇⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔ā¸Ē⏖⏞⏙⏰⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡", "setting_notifications_subtitle": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ā¸˛ā¸Ŗāšā¸ˆāš‰ā¸‡āš€ā¸•ā¸ˇā¸­ā¸™", + "setting_notifications_total_progress_subtitle": "ā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸ˇā¸šā¸Ģā¸™āš‰ā¸˛ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš‚ā¸”ā¸ĸā¸Ŗā¸§ā¸Ą (āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™/ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”)", + "setting_notifications_total_progress_title": "āšā¸Ē⏔⏇ā¸Ē⏖⏞⏙⏰⏁⏞⏪ā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāšƒā¸™āš€ā¸šā¸ˇāš‰ā¸­ā¸‡ā¸Ģā¸Ĩā¸ąā¸‡ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "setting_video_viewer_auto_play_subtitle": "āš€ā¸Ŗā¸´āšˆā¸Ąāš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´ā¸Ģā¸Ĩā¸ąā¸‡āš€ā¸›ā¸´ā¸”āšā¸Ĩāš‰ā¸§", "setting_video_viewer_auto_play_title": "āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´", "setting_video_viewer_looping_title": "⏧⏙ā¸Ĩā¸šā¸›", - "setting_video_viewer_original_video_subtitle": "āš€ā¸Ąā¸ˇāšˆā¸­ā¸Ē⏕⏪ā¸ĩā¸Ąā¸§ā¸´ā¸”ā¸´āš‚ā¸­ā¸ˆā¸˛ā¸āš€ā¸‹ā¸Ŗā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ āšƒā¸Ģāš‰āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸´āš‚ā¸­ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸šā¸–ā¸ļā¸‡āšā¸Ąāš‰ā¸ˆā¸°ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āšā¸›ā¸Ĩā¸‡ā¸§ā¸´ā¸”ā¸´āš‚ā¸­āš„ā¸”āš‰āšā¸•āšˆā¸­ā¸˛ā¸ˆā¸—ā¸ŗāšƒā¸Ģāš‰āš€ā¸ā¸´ā¸”ā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸°ā¸•ā¸¸ā¸ ā¸§ā¸´ā¸”ā¸´āš‚ā¸­ā¸—ā¸ĩāšˆā¸Ąā¸ĩ⏭ā¸ĸā¸šāšˆā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸ˆā¸°āš€ā¸Ĩāšˆā¸™ā¸ˆā¸˛ā¸ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸šā¸•ā¸Ĩā¸­ā¸”āš‚ā¸”ā¸ĸāš„ā¸Ąāšˆāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸•ā¸˛ā¸Ąā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸™ā¸ĩāš‰", "setting_video_viewer_original_video_title": "ā¸šā¸ąā¸‡ā¸„ā¸ąā¸šā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š", "settings": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", + "settings_require_restart": "⏁⏪⏏⏓⏞⏪ā¸ĩā¸Ēā¸•ā¸˛ā¸ŖāšŒā¸— Immmich āš€ā¸žā¸ˇāšˆā¸­āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛", "settings_saved": "ā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšā¸Ĩāš‰ā¸§", "setup_pin_code": "ā¸•ā¸ąāš‰ā¸‡ā¸Ŗā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN)", "share": "āšā¸Šā¸ŖāšŒ", + "share_action_prompt": "āšā¸Šā¸ŖāšŒā¸Ēā¸ˇāšˆā¸­ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāšā¸Ĩāš‰ā¸§", + "share_add_photos": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", + "share_assets_selected": "{count} ā¸–ā¸šā¸āš€ā¸Ĩ⏎⏭⏁", "share_dialog_preparing": "⏁⏺ā¸Ĩā¸ąā¸‡āš€ā¸•ā¸Ŗā¸ĩā¸ĸā¸Ą...", "share_link": "āšā¸Šā¸ŖāšŒā¸Ĩā¸´ā¸‡ā¸āšŒ", - "share_original": "āšƒā¸Šāš‰ā¸•āš‰ā¸™ā¸‰ā¸šā¸ąā¸š (āšƒā¸Ģā¸āšˆ)", - "share_preview": "āšƒā¸Šāš‰ā¸ ā¸˛ā¸žā¸‚ā¸™ā¸˛ā¸”ā¸ĸāšˆā¸­ (āš€ā¸Ĩāš‡ā¸)", - "share_quality_body": "⏁⏔⏗ā¸ĩāšˆā¸›ā¸¸āšˆā¸Ąāšā¸Šā¸ŖāšŒā¸„āš‰ā¸˛ā¸‡āš„ā¸§āš‰āš€ā¸žā¸ˇāšˆā¸­āš€ā¸Ĩā¸ˇā¸­ā¸ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸‡ā¸ ā¸˛ā¸žā¸āšˆā¸­ā¸™ā¸—ā¸ĩāšˆā¸ˆā¸°āšā¸Šā¸ŖāšŒ", - "share_quality_title": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒ", "shared": "āšā¸Šā¸ŖāšŒ", "shared_album_activities_input_disable": "ā¸„ā¸­ā¸Ąāš€ā¸Ąā¸™ā¸•āšŒā¸–ā¸šā¸ā¸›ā¸´ā¸”", "shared_album_activity_remove_content": "ā¸¸ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ąā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", "shared_album_activity_remove_title": "ā¸Ĩā¸šā¸ā¸´ā¸ˆā¸ā¸Ŗā¸Ŗā¸Ą", "shared_album_section_people_action_error": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩā¸˛ā¸”āšƒā¸™ā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸/ā¸™ā¸ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "shared_album_section_people_action_leave": "ā¸™ā¸ŗā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "shared_album_section_people_action_remove_user": "ā¸™ā¸ŗā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "shared_album_section_people_title": "ā¸œā¸šāš‰ā¸„ā¸™", "shared_by": "āšā¸Šā¸ŖāšŒāš‚ā¸”ā¸ĸ", "shared_by_user": "āšā¸Šā¸ŖāšŒāš‚ā¸”ā¸ĸ {user}", @@ -1947,10 +2000,9 @@ "shared_intent_upload_button_progress_text": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩāš‰ā¸§ {current} / {total}", "shared_link_app_bar_title": "ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", "shared_link_clipboard_copied_massage": "ā¸„ā¸ąā¸”ā¸Ĩā¸­ā¸āš„ā¸›ā¸ĸā¸ąā¸‡ā¸„ā¸Ĩā¸´ā¸›ā¸šā¸­ā¸ŖāšŒā¸”āšā¸Ĩāš‰ā¸§", + "shared_link_clipboard_text": "ā¸Ĩā¸´ā¸‡ā¸āšŒ: {link}\n⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™: {password}", "shared_link_create_error": "āš€ā¸ā¸´ā¸”ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔⏂⏓⏰ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸Ĩā¸´ā¸‡ā¸āšŒāšā¸Šā¸ŖāšŒ", - "shared_link_custom_url_description": "āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒā¸™ā¸ĩāš‰ā¸”āš‰ā¸§ā¸ĸ URL āšā¸Ĩā¸°ā¸Šā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸ā¸ŗā¸Ģā¸™ā¸”āš€ā¸­ā¸‡", - "shared_link_custom_url_title": "URL ā¸Šā¸ˇāšˆā¸­ā¸ā¸ŗā¸Ģā¸™ā¸”āš€ā¸­ā¸‡", - "shared_link_custom_url_warning": "āš€ā¸•ā¸ˇā¸­ā¸™: ā¸Šā¸ˇāšˆā¸­ URL ⏁⏺ā¸Ģā¸™ā¸”āš€ā¸­ā¸‡ā¸—ā¸ĩāšˆā¸Ąā¸ĩāš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ģā¸Ąā¸˛ā¸ĸ “/” ā¸­ā¸˛ā¸ˆāš„ā¸Ąāšˆā¸—ā¸ŗā¸‡ā¸˛ā¸™āšƒā¸™āšā¸šā¸šā¸—ā¸ĩāšˆā¸„ā¸¸ā¸“ā¸„ā¸˛ā¸”ā¸„ā¸´ā¸”", + "shared_link_custom_url_description": "āš€ā¸‚āš‰ā¸˛ā¸–ā¸ļ⏇ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒā¸™ā¸ĩāš‰ā¸”āš‰ā¸§ā¸ĸ URL ⏗ā¸ĩāšˆā¸ā¸ŗā¸Ģā¸™ā¸”āš€ā¸­ā¸‡", "shared_link_edit_description_hint": "āš€ā¸žā¸´āšˆā¸Ąā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒ", "shared_link_edit_expire_after_option_day": "1 ā¸§ā¸ąā¸™", "shared_link_edit_expire_after_option_days": "{count} ā¸§ā¸ąā¸™", @@ -1984,6 +2036,12 @@ "shared_with_partner": "āšā¸Šā¸ŖāšŒā¸ā¸ąā¸š {partner}", "sharing": "ā¸ā¸˛ā¸Ŗāšā¸Šā¸ŖāšŒ", "sharing_enter_password": "āš‚ā¸›ā¸Ŗā¸”ā¸›āš‰ā¸­ā¸™ā¸Ŗā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™ ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šāš€ā¸›ā¸´ā¸”ā¸”ā¸šā¸Ģā¸™āš‰ā¸˛ā¸™ā¸ĩāš‰", + "sharing_page_album": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", + "sharing_page_description": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāš€ā¸žā¸ˇāšˆā¸­āšā¸Šā¸ŖāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­āšƒā¸Ģāš‰ā¸ā¸ąā¸šā¸„ā¸™ā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇā¸­ā¸‚āšˆā¸˛ā¸ĸ⏄⏏⏓", + "sharing_page_empty_list": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸§āšˆā¸˛ā¸‡āš€ā¸›ā¸Ĩāšˆā¸˛", + "sharing_sidebar_description": "āšā¸Ē⏔⏇ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāšƒā¸™āšā¸–ā¸šā¸”āš‰ā¸˛ā¸™ā¸‚āš‰ā¸˛ā¸‡", + "sharing_silver_appbar_create_shared_album": "ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒāšƒā¸Ģā¸Ąāšˆ", + "sharing_silver_appbar_share_partner": "āšā¸Šā¸ŖāšŒā¸ā¸ąā¸šā¸„ā¸šāšˆā¸Ģā¸š", "shift_to_permanent_delete": "⏁⏔ ⇧ āš€ā¸žā¸ˇāšˆā¸­ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­ā¸–ā¸˛ā¸§ā¸Ŗ", "show_album_options": "āšā¸Ēā¸”ā¸‡ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "show_albums": "āšā¸Ēā¸”ā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", @@ -1995,17 +2053,13 @@ "show_in_timeline": "āšā¸Ēā¸”ā¸‡āšƒā¸™āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒ", "show_in_timeline_setting_description": "āšā¸Ēā¸”ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸‚ā¸­ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸™ā¸ĩāš‰āšƒā¸™āš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "show_keyboard_shortcuts": "āšā¸Ēā¸”ā¸‡ā¸›ā¸¸āšˆā¸Ąā¸Ĩā¸ąā¸”āšā¸›āš‰ā¸™ā¸žā¸´ā¸Ąā¸žāšŒ", - "show_less": "āšā¸Ēā¸”ā¸‡ā¸™āš‰ā¸­ā¸ĸā¸Ĩ⏇", "show_metadata": "āšā¸Ē⏔⏇ metadata", - "show_more_fields": "{count, plural, one {āšā¸Ēā¸”ā¸‡ā¸Šāšˆā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ą # ā¸Šāšˆā¸­ā¸‡} other {āšā¸Ēā¸”ā¸‡ā¸Šāšˆā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸žā¸´āšˆā¸Ą # ā¸Šāšˆā¸­ā¸‡}}", "show_or_hide_info": "āšā¸Ē⏔⏇ā¸Ģā¸Ŗā¸ˇā¸­ā¸‹āšˆā¸­ā¸™ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ", "show_password": "āšā¸Ē⏔⏇⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™", "show_person_options": "āšā¸Ēā¸”ā¸‡ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸‚ā¸­ā¸‡ā¸•ā¸ąā¸§ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", "show_progress_bar": "āšā¸Ēā¸”ā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸ˇā¸šā¸Ģā¸™āš‰ā¸˛ āšā¸–ā¸š", - "show_schema": "āšā¸Ē⏔⏇ Schema", "show_search_options": "āšā¸Ēā¸”ā¸‡ā¸•ā¸ąā¸§āš€ā¸Ĩā¸ˇā¸­ā¸ā¸ā¸˛ā¸Ŗā¸„āš‰ā¸™ā¸Ģ⏞", "show_shared_links": "āšā¸Ē⏔⏇ā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", - "show_slideshow_metadata_overlay": "āšā¸Ēā¸”ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸ ā¸˛ā¸žāšƒā¸™ā¸Ģā¸™āš‰ā¸˛ā¸•āšˆā¸˛ā¸‡ā¸Ĩ⏭ā¸ĸ", "show_slideshow_transition": "āšā¸Ē⏔⏇ā¸Ēāš„ā¸Ĩā¸„āšŒāš‚ā¸Šā¸§āšŒ", "show_supporter_badge": "āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ģā¸Ąā¸˛ā¸ĸā¸œā¸šāš‰ā¸Ēā¸™ā¸ąā¸šā¸Ē⏙⏏⏙", "show_supporter_badge_description": "āšā¸Ēā¸”ā¸‡āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ģā¸Ąā¸˛ā¸ĸā¸œā¸šāš‰ā¸Ēā¸™ā¸ąā¸šā¸Ē⏙⏏⏙", @@ -2017,45 +2071,32 @@ "sign_out": "⏭⏭⏁⏈⏞⏁⏪⏰⏚⏚", "sign_up": "ā¸Ĩā¸‡ā¸—ā¸°āš€ā¸šā¸ĩā¸ĸ⏙", "size": "⏂⏙⏞⏔", - "skip": "ā¸‚āš‰ā¸˛ā¸Ą", "skip_to_content": "ā¸‚āš‰ā¸˛ā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸™ā¸ˇāš‰ā¸­ā¸Ģ⏞", "skip_to_folders": "ā¸‚āš‰ā¸˛ā¸Ąāš‚ā¸Ÿā¸ĨāšŒāš€ā¸”ā¸­ā¸ŖāšŒ", "skip_to_tags": "ā¸‚āš‰ā¸˛ā¸Ąāšā¸—āš‡ā¸", "slideshow": "ā¸Ēāš„ā¸Ĩā¸”āšŒ", - "slideshow_body": "ā¸™ā¸ąāšˆā¸‡ā¸žā¸ąā¸ā¸œāšˆā¸­ā¸™āšā¸Ĩāš‰ā¸§ā¸Šā¸Ąā¸ ā¸˛ā¸žā¸–āšˆā¸˛ā¸ĸā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš€ā¸Ĩāšˆā¸™āšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸šā¸Ēāš„ā¸Ĩā¸”āšŒāš‚ā¸Šā¸§āšŒāš€ā¸•āš‡ā¸Ąā¸Ģā¸™āš‰ā¸˛ā¸ˆā¸­", - "slideshow_metadata_overlay_mode": "āš€ā¸™ā¸ˇāš‰ā¸­ā¸Ģā¸˛āšƒā¸™ā¸Ģā¸™āš‰ā¸˛ā¸•āšˆā¸˛ā¸‡ā¸Ĩ⏭ā¸ĸ", - "slideshow_metadata_overlay_mode_description_only": "āš€ā¸‰ā¸žā¸˛ā¸°ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ", - "slideshow_metadata_overlay_mode_full": "āš€ā¸•āš‡ā¸Ą", - "slideshow_repeat": "āš€ā¸Ĩāšˆā¸™ā¸Ēāš„ā¸Ĩā¸”āšŒāš‚ā¸Šā¸§āšŒā¸‹ā¸ŗāš‰", - "slideshow_repeat_description": "⏁ā¸Ĩā¸ąā¸šāš„ā¸›āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™āšƒā¸Ģā¸Ąāšˆā¸Ģā¸Ĩā¸ąā¸‡āšā¸Ē⏔⏇ā¸Ēāš„ā¸Ĩā¸”āšŒāš‚ā¸Šā¸§āšŒā¸ˆā¸š", "slideshow_settings": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ēāš„ā¸Ĩā¸”āšŒ", - "slideshow_title": "ā¸Ēāš„ā¸Ĩā¸”āšŒāš‚ā¸Šā¸§āšŒ", - "smart_album": "ā¸Ēā¸Ąā¸˛ā¸ŖāšŒā¸•ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "some_assets_already_have_a_location_warning": "ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸Ĩ⏎⏭⏁⏭ā¸ĸā¸šāšˆā¸šā¸˛ā¸‡ā¸­ā¸ąā¸™ā¸Ąā¸ĩā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡āšā¸Ĩāš‰ā¸§", "sort_albums_by": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš‚ā¸”ā¸ĸ...", "sort_created": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸•ā¸˛ā¸Ąā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸Ēā¸Ŗāš‰ā¸˛ā¸‡", "sort_items": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸ⏇⏪⏞ā¸ĸ⏁⏞⏪", "sort_modified": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸•ā¸˛ā¸Ąā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāšā¸āš‰āš„ā¸‚", + "sort_newest": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšƒā¸Ģā¸Ąāšˆā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "sort_oldest": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸•ā¸˛ā¸Ąāš€ā¸āšˆā¸˛ā¸Ē⏏⏔", - "sort_people_by_similarity": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸œā¸šāš‰ā¸„ā¸™ā¸•ā¸˛ā¸Ąā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸Ĩāš‰ā¸˛ā¸ĸ⏄ā¸Ĩā¸ļ⏇", + "sort_people_by_similarity": "ā¸ˆā¸¸ā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡ā¸šā¸¸ā¸„ā¸„ā¸Ĩā¸•ā¸˛ā¸Ąā¸„ā¸§ā¸˛ā¸Ąā¸„ā¸Ĩāš‰ā¸˛ā¸ĸ⏄ā¸Ĩā¸ļ⏇", "sort_recent": "ā¸ˆā¸ąā¸”āš€ā¸Ŗā¸ĩā¸ĸā¸‡āšƒā¸Ģā¸Ąāšˆā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "sort_title": "āš„ā¸•āš€ā¸•ā¸´āš‰ā¸Ĩ", "source": "āšā¸Ģā¸Ĩāšˆā¸‡", - "stack": "Stack", - "stack_action_prompt": "⏙⏺ {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāš€ā¸‚āš‰ā¸˛ Stack āšā¸Ĩāš‰ā¸§", - "stack_duplicates": "⏙⏺ā¸Ēā¸´āšˆā¸‡ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸Ąā¸˛ā¸­ā¸ĸā¸šāšˆāšƒā¸™ Stack ā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™", - "stack_selected_photos": "ā¸™ā¸ŗā¸Ŗā¸šā¸›ā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ŗāš€ā¸›āš‡ā¸™ Stack", - "stacked_assets_count": "⏙⏺ā¸Ēā¸ˇāšˆā¸­{count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āš€ā¸‚āš‰ā¸˛ Stack āšā¸Ĩāš‰ā¸§", - "stacktrace": "Stacktrace", + "stack": "ā¸‹āš‰ā¸­ā¸™", + "stack_action_prompt": "ā¸‹āš‰ā¸­ā¸™ā¸—ā¸ąā¸š {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāšā¸Ĩāš‰ā¸§", + "stack_duplicates": "⏙⏺ā¸Ēā¸´āšˆā¸‡ā¸—ā¸ĩāšˆā¸‹āš‰ā¸ŗā¸Ąā¸˛ā¸‹āš‰ā¸­ā¸™ā¸­ā¸ĸā¸šāšˆā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™", + "stack_select_one_photo": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸Ŗā¸šā¸›ā¸Ģā¸Ĩā¸ąā¸ā¸Ģ⏙ā¸ļāšˆā¸‡ā¸Ŗā¸šā¸›ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ŗā¸šā¸›ā¸—ā¸ĩāšˆā¸‹āš‰ā¸­ā¸™ā¸ā¸ąā¸™ā¸™ā¸ĩāš‰", + "stack_selected_photos": "ā¸‹āš‰ā¸­ā¸™ā¸Ŗā¸šā¸›ā¸—ā¸ĩāšˆā¸–ā¸šā¸āš€ā¸Ĩ⏎⏭⏁", + "stacked_assets_count": "ā¸‹āš‰ā¸­ā¸™ā¸—ā¸ąā¸šā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āšā¸Ĩāš‰ā¸§", "start": "āš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™", "start_date": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāš€ā¸Ŗā¸´āšˆā¸Ą", "start_date_before_end_date": "ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆāš€ā¸Ŗā¸´āšˆā¸Ąā¸•āš‰ā¸™ā¸•āš‰ā¸­ā¸‡ā¸Ąā¸˛ā¸āšˆā¸­ā¸™ā¸§ā¸ąā¸™ā¸—ā¸ĩāšˆā¸Ēā¸´āš‰ā¸™ā¸Ē⏏⏔", "state": "ā¸Ŗā¸ąā¸", "status": "ā¸Ē⏖⏞⏙⏰", - "step_delete": "ā¸Ĩā¸šā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", - "step_delete_confirm": "ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸Ĩā¸šā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™ā¸™ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ", - "step_details": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", - "steps": "ā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", "stop_casting": "ā¸Ģā¸ĸā¸¸ā¸”āšā¸„ā¸Ēā¸•āšŒ", "stop_motion_photo": "ā¸ ā¸˛ā¸žā¸§ā¸ąā¸•ā¸–ā¸¸āš€ā¸„ā¸Ĩā¸ˇāšˆā¸­ā¸™āš„ā¸Ģ⏧", "stop_photo_sharing": "ā¸Ģā¸ĸā¸¸ā¸”āšā¸Šā¸ŖāšŒā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž?", @@ -2076,36 +2117,35 @@ "swap_merge_direction": "ā¸Ēā¸Ĩā¸ąā¸šā¸”āš‰ā¸˛ā¸™ā¸Ŗā¸§ā¸Ą", "sync": "ā¸‹ā¸´ā¸‡ā¸„āšŒ", "sync_albums": "ā¸‹ā¸´ā¸‡ā¸āšŒā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", - "sync_local": "ā¸‹ā¸´āš‰ā¸‡ā¸„āšŒā¸ā¸ąā¸šā¸šā¸™āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡", - "sync_remote": "ā¸‹ā¸´ā¸‡ā¸āšŒā¸Ēā¸ˇāšˆā¸­āš„ā¸›ā¸šā¸™āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒ", + "sync_albums_manual_subtitle": "ā¸‹ā¸´ā¸‡ā¸āšŒā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­āšā¸Ĩā¸°ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩāš‰ā¸§ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸Ēā¸ŗā¸Ŗā¸­ā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āš„ā¸§āš‰", "sync_status": "ā¸Ēā¸–ā¸˛ā¸™ā¸°ā¸‹ā¸´ā¸‡ā¸āšŒ", "sync_status_subtitle": "ā¸”ā¸šāšā¸Ĩā¸°ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸Ŗā¸°ā¸šā¸šā¸‹ā¸´ā¸‡ā¸āšŒ", "sync_upload_album_setting_subtitle": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸Ĩā¸°ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žāšā¸Ĩ⏰⏧⏴⏔ā¸ĩāš‚ā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸ā¸šā¸™ Immich", - "system_theme": "⏘ā¸ĩā¸Ąā¸Ēā¸ĩā¸‚ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸š", - "system_theme_command_description": "āšƒā¸Šāš‰ā¸˜ā¸ĩā¸Ąā¸Ēā¸ĩā¸‚ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸š ({value})", "tag": "āšā¸—āš‡ā¸", - "tag_assets": "ā¸•ā¸´ā¸”āšā¸—āš‡ā¸āšƒā¸Ģāš‰ā¸Ēā¸ˇāšˆā¸­", "tag_created": "ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸—āš‡ā¸: {tag}", "tag_face": "āšā¸—āš‡ā¸āšƒā¸šā¸Ģā¸™āš‰ā¸˛", "tag_feature_description": "ā¸”ā¸šā¸Ŗā¸šā¸›ā¸–āšˆā¸˛ā¸ĸāšā¸Ĩ⏰⏧ā¸ĩ⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸Ēā¸Ŗāš‰ā¸˛ā¸‡ā¸ā¸Ĩā¸¸āšˆā¸Ąā¸•ā¸˛ā¸Ąā¸Ģā¸ąā¸§ā¸‚āš‰ā¸­āšā¸—āš‡ā¸", + "tag_not_found_question": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸Ģā¸˛āšā¸—āš‡ā¸āš„ā¸”āš‰āšƒā¸Šāšˆā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸—āš‡ā¸āšƒā¸Ģā¸Ąāšˆ", "tag_people": "āšā¸—āš‡ā¸ā¸œā¸šāš‰ā¸„ā¸™", - "tag_updated": "āšā¸—āš‡ā¸ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸­ā¸ąā¸›āš€ā¸”ā¸•: {tag}", - "tagged_assets": "{count, plural, one {ā¸Ēā¸ˇāšˆā¸­} other {ā¸Ēā¸ˇāšˆā¸­ 5 ā¸Ēā¸ˇāšˆā¸­}}ā¸–ā¸šā¸ā¸•ā¸´ā¸”āšā¸—āš‡ā¸āšā¸Ĩāš‰ā¸§", + "tag_updated": "āšā¸—āš‡ā¸ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸­ā¸ąā¸žāš€ā¸”ā¸•: {tag}", + "tagged_assets": "⏗ā¸ĩāšˆā¸–ā¸šā¸āšā¸—āš‡ā¸", "tags": "āšā¸—āš‡ā¸", "tap_to_run_job": "āšā¸•ā¸°āš€ā¸žā¸ˇāšˆā¸­ā¸Ŗā¸ąā¸™ā¸‡ā¸˛ā¸™", "template": "āš€ā¸—āš‡ā¸Ąāš€ā¸žā¸Ĩ⏕", - "text_recognition": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ģā¸˛ā¸•ā¸ąā¸§ā¸­ā¸ąā¸ā¸Šā¸Ŗ", "theme": "⏘ā¸ĩā¸Ą", "theme_selection": "ā¸ā¸˛ā¸Ŗāš€ā¸Ĩ⏎⏭⏁⏘ā¸ĩā¸Ą", "theme_selection_description": "ā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸˜ā¸ĩā¸Ąāšƒā¸Ģāš‰ā¸Ēā¸§āšˆā¸˛ā¸‡ā¸Ģā¸Ŗā¸ˇā¸­ā¸Ąā¸ˇā¸”āš‚ā¸”ā¸ĸā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´ ā¸­ā¸´ā¸‡ā¸ˆā¸˛ā¸ā¸„āšˆā¸˛ā¸‚ā¸­ā¸‡āš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“", "theme_setting_asset_list_storage_indicator_title": "āšā¸Ēā¸”ā¸‡ā¸•ā¸ąā¸§ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸ˆā¸ąā¸”āš€ā¸āš‡ā¸šā¸šā¸™ā¸•ā¸˛ā¸Ŗā¸˛ā¸‡ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪", "theme_setting_asset_list_tiles_per_row_title": "ā¸ˆā¸ŗā¸™ā¸§ā¸™ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸•āšˆā¸­āšā¸–ā¸§ ({count})", - "theme_setting_colorful_interface_subtitle": "āšƒā¸Ēāšˆā¸Ēā¸ĩā¸Ģā¸Ĩā¸ąā¸āšƒā¸Ģāš‰ā¸žā¸ˇāš‰ā¸™ā¸Ģā¸Ĩā¸ąā¸‡", "theme_setting_colorful_interface_title": "ā¸­ā¸´ā¸™āš€ā¸•ā¸­ā¸ŖāšŒāš€ā¸Ÿā¸‹ā¸Ąā¸ĩā¸Ēā¸ĩā¸Ēā¸ąā¸™", - "theme_setting_primary_color_subtitle": "āš€ā¸Ĩ⏎⏭⏁ā¸Ēā¸ĩāš€ā¸žā¸ˇāšˆā¸­āšƒā¸Šāš‰āš€ā¸™āš‰ā¸™ā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸°ā¸—ā¸ŗā¸•āšˆā¸˛ā¸‡āš†", + "theme_setting_image_viewer_quality_subtitle": "ā¸›ā¸Ŗā¸ąā¸šā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸‚ā¸­ā¸•ā¸ąā¸§ā¸”ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", + "theme_setting_image_viewer_quality_title": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸•ā¸ąā¸‡ā¸”ā¸šā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", "theme_setting_primary_color_title": "ā¸Ēā¸ĩā¸Ģā¸Ĩā¸ąā¸", "theme_setting_system_primary_color_title": "āšƒā¸Šāš‰ā¸Ēā¸ĩā¸‚ā¸­ā¸‡ā¸Ŗā¸°ā¸šā¸š", "theme_setting_system_theme_switch": "ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´ (ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸Ŗā¸°ā¸šā¸š)", + "theme_setting_theme_subtitle": "āš€ā¸Ĩ⏎⏭⏁⏘ā¸ĩā¸Ąā¸‚ā¸­ā¸‡āšā¸­ā¸ž", + "theme_setting_three_stage_loading_subtitle": "ā¸ā¸˛ā¸Ŗāš‚ā¸Ģā¸Ĩā¸”āšā¸šā¸šā¸Ēā¸˛ā¸Ąā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™ā¸­ā¸˛ā¸ˆāš€ā¸žā¸´āšˆā¸Ąā¸›ā¸Ŗā¸°ā¸Ēā¸´ā¸—ā¸˜ā¸´ā¸ ā¸˛ā¸žāšƒā¸™ā¸ā¸˛ā¸Ŗāš‚ā¸Ģā¸Ĩā¸”āšā¸•āšˆā¸ˆā¸°ā¸—ā¸ŗāšƒā¸Ģāš‰āš‚ā¸Ģā¸Ĩā¸”āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‚āšˆā¸˛ā¸ĸāš€ā¸žā¸´āšˆā¸Ąā¸‚ā¸ļāš‰ā¸™ā¸Ąā¸˛ā¸", + "theme_setting_three_stage_loading_title": "āš€ā¸›ā¸´ā¸”ā¸ā¸˛ā¸Ŗāš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸˛ā¸Ąā¸‚ā¸ąāš‰ā¸™ā¸•ā¸­ā¸™", "then": "ā¸ˆā¸˛ā¸ā¸™ā¸ąāš‰ā¸™", "they_will_be_merged_together": "ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ŗā¸§ā¸Ąāš€ā¸‚āš‰ā¸˛ā¸”āš‰ā¸§ā¸ĸā¸ā¸ąā¸™", "third_party_resources": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸šā¸¸ā¸„ā¸„ā¸Ĩ⏗ā¸ĩāšˆā¸Ēā¸˛ā¸Ą", @@ -2131,17 +2171,17 @@ "trash_all": "ā¸—ā¸´āš‰ā¸‡ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", "trash_count": "{count, number} āšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰", "trash_delete_asset": "ā¸ĸāš‰ā¸˛ā¸ĸāš„ā¸›ā¸–ā¸ąā¸‡ā¸‚ā¸ĸ⏰/ā¸Ĩ⏚ā¸Ēā¸ˇāšˆā¸­", + "trash_emptied": "ā¸—ā¸ŗā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°āšƒā¸Ģāš‰ā¸§āšˆā¸˛ā¸‡āšā¸Ĩāš‰ā¸§", "trash_no_results_message": "ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸žā¸Ģ⏪⏎⏭⏧⏴⏔ā¸ĩāš‚ā¸­ā¸—ā¸ĩāšˆā¸–ā¸šā¸ā¸Ĩ⏚⏈⏰⏭ā¸ĸā¸šāšˆā¸—ā¸ĩāšˆā¸™ā¸ĩāšˆ", "trash_page_delete_all": "ā¸Ĩā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "trash_page_empty_trash_dialog_content": "ā¸„ā¸¸ā¸“āšā¸™āšˆāšƒā¸ˆā¸§āšˆā¸˛ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸´āš‰ā¸‡ā¸‚ā¸ĸā¸°ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸” ā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗā¸žā¸§ā¸ā¸™ā¸ĩāš‰ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩ⏚⏈⏞⏁ Immich ⏖⏞⏧⏪", "trash_page_info": "ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪⏗ā¸ĩāšˆā¸—ā¸´āš‰ā¸‡ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩā¸šā¸–ā¸˛ā¸§ā¸Ŗā¸Ģā¸Ĩā¸ąā¸‡ {days} ā¸§ā¸ąā¸™", + "trash_page_no_assets": "āš„ā¸Ąāšˆā¸Ąā¸ĩā¸—ā¸Ŗā¸ąā¸žā¸ĸā¸˛ā¸ā¸Ŗāšƒā¸™ā¸‚ā¸ĸ⏰", + "trash_page_restore_all": "ā¸ā¸šāš‰ā¸„ā¸ˇā¸™ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", + "trash_page_select_assets_btn": "āš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪", + "trash_page_title": "⏂ā¸ĸ⏰ ({count})", "trashed_items_will_be_permanently_deleted_after": "⏪⏞ā¸ĸ⏁⏞⏪⏗ā¸ĩāšˆā¸­ā¸ĸā¸šāšˆāšƒā¸™ā¸–ā¸ąā¸‡ā¸‚ā¸ĸā¸°ā¸ˆā¸°ā¸–ā¸šā¸ā¸Ĩ⏚⏭ā¸ĸāšˆā¸˛ā¸‡ā¸–ā¸˛ā¸§ā¸Ŗā¸Ģā¸Ĩā¸ąā¸‡ā¸œāšˆā¸˛ā¸™āš„ā¸› {days, plural, one {# ā¸§ā¸ąā¸™} other {# ā¸§ā¸ąā¸™}}", - "trigger": "ā¸—ā¸Ŗā¸´ā¸āš€ā¸ā¸­ā¸ŖāšŒ", - "trigger_asset_metadata_extraction": "⏁⏞⏪⏔ā¸ļā¸‡ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ā¸ˆā¸˛ā¸ā¸Ēā¸ˇāšˆā¸­", - "trigger_asset_metadata_extraction_description": "ā¸—ā¸Ŗā¸´ā¸āš€ā¸ā¸­ā¸ŖāšŒā¸•ā¸­ā¸™ā¸—ā¸ĩāšˆā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩāš€ā¸Ąā¸•ā¸˛ EXIF ā¸–ā¸šā¸ā¸”ā¸ļā¸‡ā¸­ā¸­ā¸ā¸Ąā¸˛ā¸ˆā¸˛ā¸ā¸Ēā¸ˇāšˆā¸­āšā¸Ĩāš‰ā¸§", - "trigger_asset_uploaded": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", - "trigger_asset_uploaded_description": "ā¸—ā¸Ŗā¸´ā¸āš€ā¸ā¸­ā¸ŖāšŒā¸•ā¸­ā¸™ā¸—ā¸ĩāšˆā¸Ēā¸ˇāšˆā¸­ā¸–ā¸šā¸ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", - "trigger_person_recognized": "ā¸Ŗā¸šāš‰ā¸ˆā¸ąā¸ā¸šā¸¸ā¸„ā¸„ā¸Ĩāšā¸Ĩāš‰ā¸§", - "trigger_person_recognized_description": "ā¸—ā¸Ŗā¸´ā¸āš€ā¸ā¸­ā¸ŖāšŒā¸•ā¸­ā¸™ā¸—ā¸ĩāšˆāš€ā¸Ģāš‡ā¸™āš€ā¸›āš‡ā¸™ā¸šā¸¸ā¸„ā¸„ā¸Ĩ", + "trigger_asset_uploaded": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­āšā¸Ĩāš‰ā¸§", "troubleshoot": "ā¸ā¸˛ā¸Ŗāšā¸āš‰ā¸›ā¸ąā¸ā¸Ģ⏞", "type": "ā¸›ā¸Ŗā¸°āš€ā¸ ā¸—", "unable_to_change_pin_code": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸ⏙⏪ā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN)", @@ -2149,7 +2189,6 @@ "unable_to_setup_pin_code": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸•ā¸ąāš‰ā¸‡ā¸Ŗā¸Ģā¸ąā¸Ēā¸›ā¸Ŗā¸°ā¸ˆā¸ŗā¸•ā¸ąā¸§ (PIN)", "unarchive": "ā¸™ā¸ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸—ā¸ĩāšˆāš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", "unarchive_action_prompt": "{count} ā¸–ā¸šā¸ā¸™ā¸ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸—ā¸ĩāšˆāš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗ", - "unarchived_count": "{count, plural, other {āš€ā¸āš‡ā¸šā¸–ā¸˛ā¸§ā¸Ŗāšā¸Ĩāš‰ā¸§ #}}", "undo": "āš€ā¸Ĩ⏴⏁⏗⏺", "unfavorite": "ā¸™ā¸ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", "unfavorite_action_prompt": "{count} ā¸–ā¸šā¸ā¸™ā¸ŗā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸Ŗā¸˛ā¸ĸā¸ā¸˛ā¸Ŗāš‚ā¸›ā¸Ŗā¸”", @@ -2157,10 +2196,8 @@ "unknown": "āš„ā¸Ąāšˆā¸—ā¸Ŗā¸˛ā¸š", "unknown_country": "āš„ā¸Ąāšˆā¸—ā¸Ŗā¸˛ā¸šā¸›ā¸Ŗā¸°āš€ā¸—ā¸¨", "unknown_date": "āš„ā¸Ąāšˆā¸—ā¸Ŗā¸˛ā¸šā¸§ā¸ąā¸™", - "unknown_schema": "Schema ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ŗā¸šāš‰ā¸ˆā¸ąā¸", "unknown_year": "āš„ā¸Ąāšˆā¸—ā¸Ŗā¸˛ā¸šā¸›ā¸ĩ", "unlimited": "āš„ā¸Ąāšˆā¸ˆā¸ŗā¸ā¸ąā¸”", - "unlink_motion_video": "ā¸Ĩā¸šā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ Motion video", "unlink_oauth": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸āš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ OAuth", "unlinked_oauth_account": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ OAuth", "unmute_memories": "āš€ā¸Ĩā¸´ā¸ā¸›ā¸´ā¸”āš€ā¸Ēā¸ĩā¸ĸā¸‡ā¸„ā¸§ā¸˛ā¸Ąā¸—ā¸Ŗā¸‡ā¸ˆā¸ŗ", @@ -2169,22 +2206,19 @@ "unnamed_share": "āšā¸Šā¸ŖāšŒāšā¸šā¸šāš„ā¸Ąāšˆā¸Ŗā¸°ā¸šā¸¸ā¸Šā¸ˇāšˆā¸­", "unsaved_change": "ā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸šā¸ąā¸™ā¸—ā¸ļ⏁", "unselect_all": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", - "unselect_all_duplicates": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸āš€ā¸Ĩ⏎⏭⏁⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸‹āš‰ā¸ŗā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", - "unstack": "āšā¸•ā¸ Stack", - "unstack_action_prompt": "āšā¸•ā¸ {count} ⏪⏞ā¸ĸ⏁⏞⏪⏭⏭⏁⏈⏞⏁ Stack āšā¸Ĩāš‰ā¸§", - "unstacked_assets_count": "āšā¸•ā¸ā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}⏭⏭⏁⏈⏞⏁ Stack āšā¸Ĩāš‰ā¸§", - "unsupported_field_type": "ā¸Šā¸™ā¸´ā¸”ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ⏗ā¸ĩāšˆāš„ā¸Ąāšˆā¸Ŗā¸ąā¸šā¸Ŗā¸­ā¸‡", + "unselect_all_in": "ā¸ĸā¸āš€ā¸Ĩā¸´ā¸ā¸ā¸˛ā¸Ŗāš€ā¸Ĩā¸ˇā¸­ā¸ā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”āšƒā¸™ {group}", + "unstack": "ā¸Ģā¸ĸā¸¸ā¸”ā¸‹āš‰ā¸­ā¸™", + "unstack_action_prompt": "āš€ā¸Ĩā¸´ā¸ā¸‹āš‰ā¸­ā¸™ā¸—ā¸ąā¸š {count} ⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗāšā¸Ĩāš‰ā¸§", + "unstacked_assets_count": "āš€ā¸Ĩā¸´ā¸ā¸‹āš‰ā¸­ā¸™ā¸—ā¸ąā¸š {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}āšā¸Ĩāš‰ā¸§", "unsupported_file_type": "āš„ā¸Ąāšˆā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸Ÿā¸ĨāšŒ {file} āš€ā¸™ā¸ˇāšˆā¸­ā¸‡ā¸ˆā¸˛ā¸ā¸Ŗā¸°ā¸šā¸šāš„ā¸Ąāšˆā¸Ŗā¸­ā¸‡ā¸Ŗā¸ąā¸šā¸›ā¸Ŗā¸°āš€ā¸ ā¸—āš„ā¸Ÿā¸ĨāšŒ {type} ⏗ā¸ĩāšˆāš„ā¸Ÿā¸ĨāšŒā¸™ā¸ąāš‰ā¸™āšƒā¸Šāš‰", - "untagged": "āš„ā¸Ąāšˆā¸Ąā¸ĩāšā¸—āš‡ā¸", "up_next": "ā¸•āšˆā¸­āš„ā¸›", - "update_location_action_prompt": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™ā¸•ā¸ŗāšā¸Ģā¸™āšˆā¸‡ā¸‚ā¸­ā¸‡ā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž {count} ā¸Ŗā¸šā¸›āš€ā¸›āš‡ā¸™:", "updated_at": "ā¸­ā¸ąā¸›āš€ā¸”ā¸•āš€ā¸Ąā¸ˇāšˆā¸­", "updated_password": "⏪ā¸Ģā¸ąā¸Ēā¸œāšˆā¸˛ā¸™āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸Ĩāš‰ā¸§", "upload": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", "upload_concurrency": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸žā¸Ŗāš‰ā¸­ā¸Ąā¸ā¸ąā¸™", - "upload_day_count": "{date}: ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}", "upload_details": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", - "upload_error_with_count": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸Ēā¸ˇāšˆā¸­ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}}", + "upload_dialog_info": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆāš€ā¸Ĩā¸ˇā¸­ā¸āš„ā¸›ā¸ĸā¸ąā¸‡āš€ā¸‹ā¸´ā¸ŖāšŒā¸Ÿāš€ā¸§ā¸­ā¸ŖāšŒā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", + "upload_dialog_title": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", "upload_errors": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™āš‚ā¸”ā¸ĸā¸Ąā¸ĩā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔ {count, plural, one {# ⏪⏞ā¸ĸ⏁⏞⏪} other {# ⏪⏞ā¸ĸ⏁⏞⏪}} ⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸Ģā¸™āš‰ā¸˛āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšƒā¸Ģā¸Ąāšˆ", "upload_finished": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš€ā¸Ēā¸Ŗāš‡ā¸ˆā¸Ēā¸´āš‰ā¸™", "upload_progress": "ā¸„ā¸‡āš€ā¸Ģā¸Ĩ⏎⏭ {remaining, number} - ā¸›ā¸Ŗā¸°ā¸Ąā¸§ā¸Ĩ⏜ā¸Ĩāšā¸Ĩāš‰ā¸§ {processed, number}/{total, number}", @@ -2192,22 +2226,15 @@ "upload_status_duplicates": "⏪⏞ā¸ĸā¸ā¸˛ā¸Ŗā¸‹āš‰ā¸ŗ", "upload_status_errors": "ā¸‚āš‰ā¸­ā¸œā¸´ā¸”ā¸žā¸Ĩ⏞⏔", "upload_status_uploaded": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšā¸Ĩāš‰ā¸§", - "upload_success": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ ⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸Ģā¸™āš‰ā¸˛āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­ā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āšƒā¸Ģā¸Ąāšˆ", - "upload_to_album_body": "ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸—ā¸ĩāšˆāš„ā¸Ąāšˆāš„ā¸”āš‰āšƒā¸Šāš‰ā¸„ā¸¸ā¸“ā¸Ēā¸Ąā¸šā¸ąā¸•ā¸´ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸”āš‰ā¸§ā¸ĸā¸•ā¸™āš€ā¸­ā¸‡ ⏕⏭⏙⏙ā¸ĩāš‰ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸Ŗā¸šā¸›ā¸ˆā¸˛ā¸āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡āš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāš„ā¸”āš‰āš‚ā¸”ā¸ĸā¸•ā¸Ŗā¸‡āšā¸Ĩāš‰ā¸§ āš„ā¸Ąāšˆā¸ˆā¸ŗāš€ā¸›āš‡ā¸™ā¸•āš‰ā¸­ā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸āšˆā¸­ā¸™āšā¸Ĩāš‰ā¸§ā¸™ā¸ŗāš€ā¸‚āš‰ā¸˛ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąā¸—ā¸ĩā¸Ģā¸Ĩā¸ąā¸‡ā¸­ā¸ĩā¸ā¸•āšˆā¸­āš„ā¸›", - "upload_to_album_title": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”ā¸•ā¸Ŗā¸‡āš„ā¸›ā¸ĸā¸ąā¸‡ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", + "upload_success": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ŗāš€ā¸Ŗāš‡ā¸ˆ ⏪ā¸ĩāš€ā¸Ÿā¸Ŗā¸Šā¸Ģā¸™āš‰ā¸˛āš€ā¸žā¸ˇāšˆā¸­ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­āšƒā¸Ģā¸Ąāšˆā¸—ā¸ĩāšˆā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ĩāšˆā¸˛ā¸Ē⏏⏔", "upload_to_immich": "ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩā¸”āš„ā¸›ā¸ĸā¸ąā¸‡ Immich ({count})", "uploading": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", "uploading_media": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔ā¸Ēā¸ˇāšˆā¸­", - "uploads": "ā¸ā¸˛ā¸Ŗā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔", - "uploads_count": "{count, plural, one {# ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔} other {# ā¸­ā¸ąā¸›āš‚ā¸Ģā¸Ĩ⏔}}", "url": "URL", "usage": "ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", "use_biometric": "āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸žā¸´ā¸Ēā¸šā¸ˆā¸™āšŒā¸­ā¸ąā¸•ā¸Ĩā¸ąā¸ā¸Šā¸“āšŒ", - "use_browser_locale": "āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛ā¸ ā¸˛ā¸Šā¸˛āšā¸Ĩā¸°ā¸žā¸ˇāš‰ā¸™ā¸—ā¸ĩāšˆā¸‚ā¸­ā¸‡āš€ā¸šā¸Ŗā¸˛āš€ā¸‹ā¸­ā¸ŖāšŒ", - "use_browser_locale_description": "ā¸ˆā¸ąā¸”ā¸Ŗā¸šā¸›ā¸§ā¸ąā¸™, āš€ā¸§ā¸Ĩ⏞ āšā¸Ĩā¸°ā¸•ā¸ąā¸§āš€ā¸Ĩā¸‚ā¸•ā¸˛ā¸Ąā¸ā¸˛ā¸Ŗā¸•ā¸ąāš‰ā¸‡ā¸„āšˆā¸˛āšƒā¸™āš€ā¸šā¸Ŗā¸˛ā¸§āšŒāš€ā¸‹ā¸­ā¸ŖāšŒ", "use_current_connection": "āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗāš€ā¸Šā¸ˇāšˆā¸­ā¸Ąā¸•āšˆā¸­ā¸›ā¸ąā¸ˆā¸ˆā¸¸ā¸šā¸ąā¸™", "use_custom_date_range": "āšƒā¸Šāš‰ā¸ā¸˛ā¸Ŗā¸›ā¸Ŗā¸ąā¸šāšā¸•āšˆā¸‡ā¸Šāšˆā¸§ā¸‡āš€ā¸§ā¸Ĩ⏞", - "use_template": "āšƒā¸Šāš‰āš€ā¸—āš‡ā¸Ąāš€ā¸žā¸Ĩ⏕", "user": "ā¸œā¸šāš‰āšƒā¸Šāš‰", "user_has_been_deleted": "ā¸œā¸šāš‰āšƒā¸Šāš‰ā¸Ŗā¸˛ā¸ĸ⏙ā¸ĩāš‰ā¸–ā¸šā¸ā¸Ĩā¸šāš„ā¸›āšā¸Ĩāš‰ā¸§", "user_id": "āš„ā¸­ā¸”ā¸ĩā¸œā¸šāš‰āšƒā¸Šāš‰", @@ -2217,15 +2244,16 @@ "user_privacy": "ā¸„ā¸§ā¸˛ā¸Ąāš€ā¸›āš‡ā¸™ā¸Ēāšˆā¸§ā¸™ā¸•ā¸ąā¸§ā¸œā¸šāš‰āšƒā¸Šāš‰", "user_purchase_settings": "ā¸‹ā¸ˇāš‰ā¸­", "user_purchase_settings_description": "ā¸ˆā¸ąā¸”ā¸ā¸˛ā¸Ŗā¸ā¸˛ā¸Ŗā¸‹ā¸ˇāš‰ā¸­", + "user_role_set": "ā¸•ā¸ąāš‰ā¸‡ {role} āšƒā¸Ģāš‰ā¸ā¸ąā¸š {user}", "user_usage_detail": "⏪⏞ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸā¸”ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸‚ā¸­ā¸‡ā¸œā¸šāš‰āšƒā¸Šāš‰", "user_usage_stats": "ā¸Ēā¸–ā¸´ā¸•ā¸´ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸šā¸ąā¸ā¸Šā¸ĩ", "user_usage_stats_description": "ā¸”ā¸šā¸Ēā¸–ā¸´ā¸•ā¸´ā¸ā¸˛ā¸Ŗāšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ā¸šā¸ąā¸ā¸Šā¸ĩ", "username": "ā¸Šā¸ˇāšˆā¸­ā¸œā¸šāš‰āšƒā¸Šāš‰", "users": "ā¸œā¸šāš‰āšƒā¸Šāš‰", - "users_added_to_album_count": "āš€ā¸žā¸ˇāšˆā¸Ąā¸œā¸šāš‰āšƒā¸Šāš‰ {count, plural, one {# ⏄⏙} other {# ⏄⏙}}āšƒā¸Ģāš‰ā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ąāšā¸Ĩāš‰ā¸§", "utilities": "āš€ā¸„ā¸Ŗā¸ˇāšˆā¸­ā¸‡ā¸Ąā¸ˇā¸­", "validate": "ā¸•ā¸Ŗā¸§ā¸ˆā¸Ē⏭⏚", "validate_endpoint_error": "ā¸ā¸Ŗā¸¸ā¸“ā¸˛ā¸Ŗā¸°ā¸šā¸¸ URL ⏗ā¸ĩāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", + "validation_error": "ā¸ā¸˛ā¸Ŗā¸•ā¸Ŗā¸§ā¸ˆā¸Ēā¸­ā¸šā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩā¸Ĩāš‰ā¸Ąāš€ā¸Ģā¸Ĩ⏧", "variables": "ā¸•ā¸ąā¸§āšā¸›ā¸Ŗ", "version": "ā¸Ŗā¸¸āšˆā¸™", "version_announcement_closing": "āš€ā¸žā¸ˇāšˆā¸­ā¸™ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ ā¸­āš€ā¸Ĩāš‡ā¸ā¸‹āšŒ", @@ -2235,9 +2263,8 @@ "video": "⏧⏴⏔ā¸ĩāš‚ā¸­", "video_hover_setting": "āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­āšā¸šā¸šā¸ĸāšˆā¸­āš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸Ĩā¸ˇāšˆā¸­ā¸™āš€ā¸Ąā¸˛ā¸ĒāšŒā¸­ā¸ĸā¸šāšˆā¸šā¸™", "video_hover_setting_description": "āš€ā¸Ĩāšˆā¸™ā¸§ā¸´ā¸”ā¸ĩāš‚ā¸­ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡āš€ā¸Ąā¸ˇāšˆā¸­āš€ā¸Ąā¸˛ā¸ĒāšŒā¸ˆāšˆā¸­ā¸‚āš‰ā¸˛ā¸‡ā¸šā¸™ āš€ā¸Ąā¸ˇāšˆā¸­ā¸›ā¸´ā¸”āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™ ⏧⏴⏔ā¸ĩāš‚ā¸­ā¸•ā¸ąā¸§ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸ĸā¸ąā¸‡ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–āš€ā¸Ĩāšˆā¸™āš„ā¸”āš‰āš‚ā¸”ā¸ĸā¸ā¸”ā¸›ā¸¸āšˆā¸Ąāš€ā¸Ĩāšˆā¸™", - "video_quality": "ā¸„ā¸¸ā¸“ā¸ ā¸˛ā¸žā¸§ā¸´ā¸”ā¸´āš‚ā¸­", "videos": "⏧⏴⏔ā¸ĩāš‚ā¸­", - "videos_count": "{count, plural, one {# ⏧⏴⏔ā¸ĩāš‚ā¸­} other {# ⏧⏴⏔ā¸ĩāš‚ā¸­}}", + "videos_only": "⏧⏴⏔ā¸ĩāš‚ā¸­āš€ā¸—āšˆā¸˛ā¸™ā¸ąāš‰ā¸™", "view": "ā¸”ā¸š", "view_album": "ā¸”ā¸šā¸­ā¸ąā¸Ĩā¸šā¸ąāš‰ā¸Ą", "view_all": "ā¸”ā¸šā¸—ā¸ąāš‰ā¸‡ā¸Ģā¸Ąā¸”", @@ -2246,49 +2273,34 @@ "view_details": "ā¸”ā¸šā¸Ŗā¸˛ā¸ĸā¸Ĩā¸°āš€ā¸­ā¸ĩā¸ĸ⏔", "view_in_timeline": "ā¸”ā¸šāš„ā¸—ā¸ĄāšŒāš„ā¸Ĩā¸™āšŒ", "view_link": "ā¸”ā¸šā¸Ĩā¸´ā¸‡ā¸āšŒ", + "view_links": "ā¸”ā¸šā¸Ĩā¸´ā¸‡ā¸āšŒ", "view_name": "ā¸”ā¸š", "view_next_asset": "ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­ā¸–ā¸ąā¸”āš„ā¸›", "view_previous_asset": "ā¸”ā¸šā¸Ēā¸ˇāšˆā¸­ā¸āšˆā¸­ā¸™ā¸Ģā¸™āš‰ā¸˛", "view_qr_code": "ā¸”ā¸šā¸„ā¸´ā¸§ā¸­ā¸˛ā¸ŖāšŒāš‚ā¸„āš‰ā¸”", "view_similar_photos": "ā¸”ā¸šā¸Ŗā¸šā¸›ā¸—ā¸ĩāšˆā¸„ā¸Ĩāš‰ā¸˛ā¸ĸā¸ā¸ąā¸™", - "view_stack": "ā¸”ā¸š Stack", - "viewer_remove_from_stack": "āš€ā¸­ā¸˛ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ Stack", - "visibility": "ā¸ā¸˛ā¸Ŗā¸Ąā¸­ā¸‡āš€ā¸Ģāš‡ā¸™", + "view_user": "ā¸”ā¸šā¸œā¸šāš‰āšƒā¸Šāš‰ā¸‡ā¸˛ā¸™", + "viewer_remove_from_stack": "āš€ā¸­ā¸˛ā¸­ā¸­ā¸ā¸ˆā¸˛ā¸ā¸—ā¸ĩāšˆā¸‹āš‰ā¸­ā¸™", + "viewer_stack_use_as_main_asset": "āšƒā¸Šāš‰āš€ā¸›āš‡ā¸™ā¸—ā¸Ŗā¸ąā¸žā¸ĸ⏞⏁⏪ā¸Ģā¸Ĩā¸ąā¸", + "viewer_unstack": "ā¸Ģā¸ĸā¸¸ā¸”ā¸‹āš‰ā¸­ā¸™", "visibility_changed": "āš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩā¸‡ā¸ā¸˛ā¸Ŗā¸Ąā¸­ā¸‡āš€ā¸Ģāš‡ā¸™ā¸Ē⏺ā¸Ģā¸Ŗā¸ąā¸š {count, plural, one {# ā¸šā¸¸ā¸„ā¸„ā¸Ĩ} other {# ā¸šā¸¸ā¸„ā¸„ā¸Ĩ}}", "visual": "āš€ā¸Ģāš‡ā¸™ā¸ ā¸˛ā¸ž", + "visual_builder": "ā¸•ā¸ąā¸§ā¸Ēā¸Ŗāš‰ā¸˛ā¸‡āšā¸šā¸šāš€ā¸Ģāš‡ā¸™ā¸ ā¸˛ā¸ž", "waiting": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸­", "waiting_count": "⏁⏺ā¸Ĩā¸ąā¸‡ā¸Ŗā¸­: {count}", "warning": "ā¸„ā¸ŗāš€ā¸•ā¸ˇā¸­ā¸™", "week": "ā¸Ēā¸ąā¸›ā¸”ā¸˛ā¸ĢāšŒ", "welcome": "ā¸ĸ⏴⏙⏔ā¸ĩā¸•āš‰ā¸­ā¸™ā¸Ŗā¸ąā¸š", "welcome_to_immich": "ā¸ĸ⏴⏙⏔ā¸ĩā¸•āš‰ā¸­ā¸™ā¸Ŗā¸ąā¸šā¸Ēā¸šāšˆ immich", - "whats_new": "ā¸Ąā¸ĩā¸­ā¸°āš„ā¸Ŗāšƒā¸Ģā¸Ąāšˆ", - "whats_new_settings_subtitle": "ā¸”ā¸šā¸§āšˆā¸˛ā¸Ąā¸ĩā¸­ā¸°āš„ā¸Ŗāšƒā¸Ģā¸Ąāšˆāšƒā¸™ Immich", - "whats_new_version": "āš€ā¸§ā¸­ā¸ŖāšŒā¸Šā¸ąā¸™ {version}", - "when": "āš€ā¸Ąā¸ˇāšˆā¸­", "width": "ā¸„ā¸§ā¸˛ā¸Ąā¸ā¸§āš‰ā¸˛ā¸‡", "wifi_name": "ā¸Šā¸ˇāšˆā¸­ Wi-Fi", - "workflow": "Workflow", - "workflow_delete_prompt": "ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸—ā¸ĩāšˆā¸ˆā¸°ā¸Ĩ⏚ Workflow ⏙ā¸ĩāš‰ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ", - "workflow_deleted": "ā¸Ĩ⏚ Workflow āšā¸Ĩāš‰ā¸§", - "workflow_description": "ā¸„ā¸ŗā¸­ā¸˜ā¸´ā¸šā¸˛ā¸ĸ Workflow", - "workflow_info": "ā¸‚āš‰ā¸­ā¸Ąā¸šā¸Ĩ Workflow", - "workflow_json": "Workflow āšƒā¸™ā¸Ŗā¸šā¸› JSON", - "workflow_json_help": "āšā¸āš‰āš„ā¸‚ Workflow āšƒā¸™ā¸Ŗā¸šā¸›āšā¸šā¸š JSON āš‚ā¸”ā¸ĸā¸ā¸˛ā¸Ŗāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩā¸‡āšƒā¸™ā¸™ā¸ĩāš‰ā¸ˆā¸°āš„ā¸›ā¸›ā¸Ŗā¸˛ā¸ā¸āšƒā¸™ā¸•ā¸ąā¸§āšā¸āš‰āš„ā¸‚āšā¸šā¸šā¸ ā¸˛ā¸ž", - "workflow_name": "ā¸Šā¸ˇāšˆā¸­ Workflow", "workflow_navigation_prompt": "ā¸„ā¸¸ā¸“ā¸•āš‰ā¸­ā¸‡ā¸ā¸˛ā¸Ŗā¸­ā¸­ā¸āš‚ā¸”ā¸ĸ⏗ā¸ĩāšˆā¸ĸā¸ąā¸‡āš„ā¸Ąāšˆā¸šā¸ąā¸™ā¸—ā¸ļā¸ā¸ā¸˛ā¸Ŗāš€ā¸›ā¸Ĩā¸ĩāšˆā¸ĸā¸™āšā¸›ā¸Ĩ⏇ā¸Ģā¸Ŗā¸ˇā¸­āš„ā¸Ąāšˆ?", - "workflow_summary": "ā¸ ā¸˛ā¸žā¸Ŗā¸§ā¸Ąā¸‚ā¸­ā¸‡ Workflow", - "workflow_templates": "āš€ā¸—āš‡ā¸Ąāš€ā¸žā¸Ĩ⏕ Workflow", - "workflow_update_success": "Workflow āš„ā¸”āš‰ā¸–ā¸šā¸āšā¸āš‰āš„ā¸‚āš€ā¸Ŗā¸ĩā¸ĸā¸šā¸Ŗāš‰ā¸­ā¸ĸ", - "workflows": "Workflows", - "workflows_help_text": "Workflow ā¸Ēā¸˛ā¸Ąā¸˛ā¸Ŗā¸–ā¸—ā¸ŗā¸ā¸˛ā¸Ŗā¸ā¸Ŗā¸°ā¸—ā¸ŗā¸šā¸™ā¸Ēā¸ˇāšˆā¸­ā¸‚ā¸­ā¸‡ā¸„ā¸¸ā¸“ā¸•ā¸˛ā¸Ąā¸—ā¸Ŗā¸´ā¸āš€ā¸ā¸­ā¸ŖāšŒāšā¸Ĩā¸°ā¸•ā¸ąā¸§ā¸ā¸Ŗā¸­ā¸‡ā¸­ā¸ĸāšˆā¸˛ā¸‡ā¸­ā¸ąā¸•āš‚ā¸™ā¸Ąā¸ąā¸•ā¸´āš„ā¸”āš‰", "wrong_pin_code": "⏪ā¸Ģā¸ąā¸Ē PIN āš„ā¸Ąāšˆā¸–ā¸šā¸ā¸•āš‰ā¸­ā¸‡", - "x_of_total": "{x}/{total}", "year": "⏛ā¸ĩ", "years_ago": "{years, plural, one {# ⏛ā¸ĩ} other {# ⏛ā¸ĩ}} ⏗ā¸ĩāšˆāšā¸Ĩāš‰ā¸§", "yes": "āšƒā¸Šāšˆ", "you_dont_have_any_shared_links": "ā¸„ā¸¸ā¸“āš„ā¸Ąāšˆāš„ā¸”āš‰ā¸Ąā¸ĩā¸Ĩā¸´ā¸‡ā¸āšŒā¸—ā¸ĩāšˆāšā¸Šā¸ŖāšŒ", "your_wifi_name": "ā¸Šā¸ˇāšˆā¸­ Wi-Fi", - "zero_to_clear_rating": "⏁⏔ 0 āš€ā¸žā¸ˇāšˆā¸­ā¸Ĩāš‰ā¸˛ā¸‡ā¸„ā¸°āšā¸™ā¸™ā¸šā¸™ā¸Ēā¸ˇāšˆā¸­ā¸™ā¸ĩāš‰", - "zoom_image": "ā¸‹ā¸šā¸Ąā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž" + "zoom_image": "ā¸‹ā¸šā¸Ąā¸Ŗā¸šā¸›ā¸ ā¸˛ā¸ž", + "zoom_to_bounds": "ā¸‹ā¸šā¸Ąāš„ā¸›ā¸ĸā¸ąā¸‡ā¸‚ā¸­ā¸šāš€ā¸‚ā¸•" } diff --git a/i18n/tl.json b/i18n/tl.json deleted file mode 100644 index 0967ef424b..0000000000 --- a/i18n/tl.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/i18n/tr.json b/i18n/tr.json index 2390d6e380..e728c73a22 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -10,38 +10,46 @@ "active": "Aktif", "active_count": "Aktif: {count}", "activity": "Etkinlik", + "activity_changed": "Etkinlik {enabled, select, true {etkin} other {devre dÄąÅŸÄą}}", "add": "Ekle", "add_a_description": "AÃ§Äąklama ekle", "add_a_location": "Bir konum ekle", - "add_a_name": "Ad ekle", + "add_a_name": "İsim ekle", "add_a_title": "Bir başlÄąk ekleyin", "add_action": "Eylem ekle", - "add_assets": "Öğe ekle", + "add_action_description": "Gerçekleştirmek istediğiniz eylemi eklemek için tÄąklayÄąn", + "add_assets": "VarlÄąk ekle", "add_birthday": "Doğum gÃŧnÃŧ ekle", "add_endpoint": "Uç nokta ekle", "add_exclusion_pattern": "Hariç tutma deseni ekle", + "add_filter": "Filtre ekle", + "add_filter_description": "Filtre koşulu eklemek için tÄąklayÄąn", "add_location": "Konum ekle", + "add_more_users": "Daha fazla kullanÄącÄą ekle", "add_partner": "Ortak ekle", + "add_path": "Yol ekle", "add_photos": "Fotoğraf ekle", - "add_step": "AdÄąm ekle", "add_tag": "Etiket ekle", "add_to": "Şuraya ekleâ€Ļ", "add_to_album": "AlbÃŧme ekle", "add_to_album_bottom_sheet_added": "{album} albÃŧmÃŧne eklendi", "add_to_album_bottom_sheet_already_exists": "Zaten {album} albÃŧmÃŧne ekli", "add_to_album_bottom_sheet_some_local_assets": "BazÄą yerel Ãļğeler albÃŧme eklenemedi", + "add_to_album_toggle": "{album} için seçimi değiştir", "add_to_albums": "AlbÃŧmlere ekle", "add_to_albums_count": "{count} albÃŧmlerine ekle", "add_to_bottom_bar": "Şuraya ekle", + "add_to_shared_album": "PaylaÅŸÄąlan albÃŧme ekle", "add_upload_to_stack": "YÃŧklemeyi yığına ekle", "add_url": "URL ekle", + "add_workflow_step": "İş akÄąÅŸÄą adÄąmÄą ekle", "added_to_archive": "Arşive eklendi", "added_to_favorites": "Favorilere eklendi", "added_to_favorites_count": "{count, number} fotoğraf favorilere eklendi", "admin": { "add_exclusion_pattern_description": "Hariç tutma desenleri ekleyin. *, ** ve ? kullanÄąlarak Globbing (temsili yer doldurucu karakter) desteklenir. Farzedelim \"Raw\" adlÄą bir dizininiz var, içinde ki tÃŧm dosyalarÄą yoksaymak için \"**/Raw/**\" şeklinde yazabilirsiniz. \".tif\" ile biten tÃŧm dosyalarÄą yoksaymak için \"**/*.tif\" yazabilirsiniz. Mutlak yolu yoksaymak için \"/yoksayÄąlacak/olan/yol/**\" şeklinde yazabilirsiniz.", "admin_user": "YÃļnetici KullanÄącÄą", - "asset_offline_description": "Bu harici kÃŧtÃŧphane Ãļgesi artÄąk diskte bulunmuyor ve çÃļp kutusuna taÅŸÄąndÄą. Dosya kÃŧtÃŧphane içinde taÅŸÄąndÄąysa, yeni karÅŸÄąlÄąk gelen Ãļge için zaman çizelgenizi kontrol edin. Bu Ãļgeyi geri yÃŧklemek için lÃŧtfen aşağıdaki dosya yolunun Immich tarafÄąndan erişilebilir olduğundan emin olun ve kÃŧtÃŧphaneyi tarayÄąn.", + "asset_offline_description": "Bu harici kÃŧtÃŧphane Ãļğesi artÄąk diskte bulunmuyor ve çÃļp kutusuna taÅŸÄąndÄą. Dosya kÃŧtÃŧphane içinde taÅŸÄąndÄąysa, yeni karÅŸÄąlÄąk gelen Ãļğe için zaman çizelgenizi kontrol edin. Bu Ãļğeyi geri yÃŧklemek için lÃŧtfen aşağıdaki dosya yolunun Immich tarafÄąndan erişilebilir olduğundan emin olun ve kÃŧtÃŧphaneyi tarayÄąn.", "authentication_settings": "Yetkilendirme AyarlarÄą", "authentication_settings_description": "Şifre, OAuth, ve diğer yetkilendirme ayarlarÄąnÄą yÃļnet", "authentication_settings_disable_all": "TÃŧm giriş yÃļntemlerini devre dÄąÅŸÄą bÄąrakmak istediğinize emin misiniz? Giriş yapma fonksiyonu tamamen devre dÄąÅŸÄą bÄąrakÄąlacak.", @@ -64,7 +72,7 @@ "confirm_delete_library": "{library} kÃŧtÃŧphanesini silmek istediğinize emin misiniz?", "confirm_delete_library_assets": "Bu kÃŧtÃŧphaneyi silmek istediğinize emin misiniz? Bu işlem {count, plural, one {# tane Ãļğeyi} other {all # tane Ãļğeyi}} Immich'den silecek ve bu işlem geri alÄąnamaz. Dosyalar diskte kalacaktÄąr.", "confirm_email_below": "Onaylamak için aşağıya \"{email}\" yazÄąn", - "confirm_reprocess_all_faces": "TÃŧm yÃŧzleri tekrardan işlemek istediğinize emin misiniz? Bu işlem adlandÄąrÄąlmÄąÅŸ insanlarÄą da silecek.", + "confirm_reprocess_all_faces": "TÃŧm yÃŧzleri tekrardan işlemek istediğinize emin misiniz? Bu işlem isimlendirilmiş insanlarÄą da silecek.", "confirm_user_password_reset": "{user} adlÄą kullanÄącÄąnÄąn şifresini sÄąfÄąrlamak istediğinize emin misiniz?", "confirm_user_pin_code_reset": "{user} adlÄą kullanÄącÄąnÄąn PIN kodunu sÄąfÄąrlamak istediğinize emin misiniz?", "copy_config_to_clipboard_description": "Geçerli sistem yapÄąlandÄąrmasÄąnÄą bir JSON nesnesi olarak panoya kopyala", @@ -73,12 +81,11 @@ "cron_expression_description": "Cron formatÄąnÄą kullanarak tarama aralığınÄą belirle. Daha fazla bilgi için Ãļrneğin Crontab Guru’ya bakÄąn", "cron_expression_presets": "Cron ifadesi Ãļn ayarlarÄą", "disable_login": "Girişi devre dÄąÅŸÄą bÄąrak", - "download_csv": "CSV’yi indir", "duplicate_detection_job_description": "Benzer fotoğraflarÄą bulmak için makine Ãļğrenmesini çalÄąÅŸtÄąr. Bu işlem AkÄąllÄą Arama'ya bağlÄądÄąr", "exclusion_pattern_description": "KÃŧtÃŧphaneyi tararken dosya ve klasÃļrleri gÃļrmezden gelmek için dÄąÅŸlama desenlerini kullanabilirsiniz. RAW dosyalarÄą gibi bazÄą dosya ve klasÃļrleri içe aktarmak istemediğinizde bu seçeneği kullanabilirsiniz.", "export_config_as_json_description": "Geçerli sistem yapÄąlandÄąrmasÄąnÄą JSON dosyasÄą olarak indir", "external_libraries_page_description": "YÃļnetici harici kÃŧtÃŧphane sayfasÄą", - "face_detection": "YÃŧz tespiti", + "face_detection": "YÃŧz tarama", "face_detection_description": "Makine Ãļğrenimi kullanarak varlÄąklardaki yÃŧzleri tespit et. Videolar için sadece kÃŧçÃŧk resim (thumbnail) dikkate alÄąnÄąr. 'Yenile' tÃŧm varlÄąklarÄą yeniden işler. 'SÄąfÄąrla', mevcut tÃŧm yÃŧz verilerini temizleyerek işlemi yeniden başlatÄąr. 'Eksik' henÃŧz işlenmemiş varlÄąklarÄą sÄąraya alÄąr. Tespit edilen yÃŧzler, YÃŧz TanÄąma işlemi tamamlandÄąktan sonra mevcut ya da yeni kişilere gruplanmak Ãŧzere YÃŧz TanÄąma için sÄąraya alÄąnacaktÄąr.", "facial_recognition_job_description": "AlgÄąlanan yÃŧzleri kişilere grupla. Bu adÄąm, YÃŧz Tespit işlemi tamamlandÄąktan sonra çalÄąÅŸÄąr. \"SÄąfÄąrla\", tÃŧm yÃŧzleri yeniden gruplandÄąrÄąr. \"Eksik\" ise henÃŧz bir kişiye atanmamÄąÅŸ yÃŧzleri sÄąraya alÄąr.", "failed_job_command": "{job} gÃļrevi için {command} komutu başarÄąsÄąz", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "KÃŧçÃŧk resim kalitesi 1-100 arasÄąnda. Daha yÃŧksek değerler daha iyidir, ancak daha bÃŧyÃŧk dosyalar Ãŧretir ve uygulamanÄąn yanÄąt hÄązÄąnÄą azaltabilir.", "image_thumbnail_title": "KÃŧçÃŧk Fotoğraf AyarlarÄą", "import_config_from_json_description": "JSON yapÄąlandÄąrma dosyasÄą yÃŧkleyerek sistem yapÄąlandÄąrmasÄąnÄą içe aktar", - "integrity_checks_checksum_files": "Doğrulama dosyalarÄą", - "integrity_checks_checksum_files_description": "Doğrulama kontrolÃŧnÃŧ yapÄąlandÄąr", - "integrity_checks_checksum_files_enable_description": "Doğrulama kontrolÃŧnÃŧ etkinleştirin", - "integrity_checks_checksum_files_percentage_limit": "YÃŧzdelik sÄąnÄąr", - "integrity_checks_checksum_files_percentage_limit_description": "Her aralÄąkta çalÄąÅŸacak doğrulama kontrolÃŧ için maksimum yÃŧzde sÄąnÄąrÄąnÄą 0.01 ile 1 arasÄąnda yapÄąlandÄąrÄąn.", - "integrity_checks_checksum_files_time_limit": "Zaman limiti", - "integrity_checks_checksum_files_time_limit_description": "Doğrulama kontrolÃŧnÃŧn her aralÄąkta çalÄąÅŸacağı maksimum sÃŧreyi yapÄąlandÄąrÄąn. (ms)", - "integrity_checks_missing_files": "Eksik dosyalar", - "integrity_checks_missing_files_description": "Eksik dosya kontrolÃŧnÃŧn sÄąklığınÄą yapÄąlandÄąrÄąn ve etkinleştirin veya devre dÄąÅŸÄą bÄąrakÄąn", - "integrity_checks_missing_files_enable_description": "Eksik dosya kontrolÃŧnÃŧ etkinleştirin", - "integrity_checks_settings": "BÃŧtÃŧnlÃŧğÃŧ doğrula", - "integrity_checks_settings_description": "BÃŧtÃŧnlÃŧk kontrol aralÄąklarÄąnÄą yÃļnetin", - "integrity_checks_untracked_files": "İzlenmeyen dosyalar", - "integrity_checks_untracked_files_description": "İzlenmeyen dosya kontrolÃŧ sÄąklığınÄą yapÄąlandÄąrÄąn ve Ãļzelliği aÃ§Äąn veya kapatÄąn", - "integrity_checks_untracked_files_enable_description": "İzlenmeyen dosyalar kontrolÃŧnÃŧ etkinleştirin", "job_concurrency": "{job} eşzamanlÄąlÄąk", "job_created": "GÃļrev oluşturuldu", "job_not_concurrency_safe": "Bu işlem eşzamanlÄąlÄąk aÃ§ÄąsÄąndan gÃŧvenli değil.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "AkÄąllÄą aramayÄą etkinleştir", "machine_learning_smart_search_enabled_description": "Eğer devre dÄąÅŸÄą bÄąrakÄąlÄąrsa fotoğraflar akÄąllÄą arama için işlenmeyecek.", "machine_learning_url_description": "Makine Ãļğrenimi sunucusunun URL’si. Birden fazla URL sağlanÄąrsa, her sunucu sÄąrayla tek tek denenir ve biri başarÄąlÄą yanÄąt verene kadar devam edilir. YanÄąt vermeyen sunucular, çevrimiçi duruma gelene kadar geçici olarak yok sayÄąlÄąr.", - "maintenance_backup_management": "Yedekleme sistemi", "maintenance_delete_backup": "Yedeği Sil", "maintenance_delete_backup_description": "Bu dosya geri alÄąnamaz şekilde silinecektir.", "maintenance_delete_error": "Yedek silinemedi.", - "maintenance_integrity_check": "Kontrol et", - "maintenance_integrity_check_all": "Hepsini kontrol et", - "maintenance_integrity_checksum_mismatch": "Doğrulama Uyuşmazlığı", - "maintenance_integrity_checksum_mismatch_description": "Disk Ãŧzerindeki sağlama toplamÄą, Immich'in veritabanÄąnda sakladığı sağlama toplamÄąyla uyuşmayan dosyalar.", - "maintenance_integrity_checksum_mismatch_job": "Sağlama toplamÄą uyuşmazlÄąklarÄąnÄą kontrol et", - "maintenance_integrity_checksum_mismatch_refresh_job": "Doğrulama uyuşmazlığı raporlarÄąnÄą yenile", - "maintenance_integrity_missing_file": "Eksik Dosyalar", - "maintenance_integrity_missing_file_description": "Immich'in veritabanÄąnda izlediği ancak dosya sisteminde bulunmayan dosyalar.", - "maintenance_integrity_missing_file_job": "Eksik dosyalarÄą kontrol et", - "maintenance_integrity_missing_file_refresh_job": "Eksik dosya raporlarÄąnÄą yenile", - "maintenance_integrity_report": "Entegrasyon Raporu", - "maintenance_integrity_untracked_file": "İzlenmeyen Dosyalar", - "maintenance_integrity_untracked_file_description": "Immich'in dizinlerinde bulunan ancak Immich'in hiçbir kaydÄąnÄąn bulunmadığı dosyalar.", - "maintenance_integrity_untracked_file_job": "İzlenmeyen dosyalarÄą kontrol et", - "maintenance_integrity_untracked_file_refresh_job": "İzlenmeyen dosya raporlarÄąnÄą yenile", "maintenance_restore_backup": "Yedeği Geri YÃŧkle", "maintenance_restore_backup_description": "Immich tamamen silinecek ve seçilen yedekten geri yÃŧklenecektir. İşleme devam etmeden Ãļnce bir yedek oluşturulacaktÄąr.", "maintenance_restore_backup_different_version": "Bu yedek, Immich’in farklÄą bir sÃŧrÃŧmÃŧyle oluşturulmuş!", @@ -234,8 +210,8 @@ "manage_log_settings": "GÃŧnlÃŧk ayarlarÄąnÄą yÃļnet", "map_dark_style": "Koyu stil", "map_enable_description": "Harita ayarlarÄąnÄą etkinleştir", - "map_gps_settings": "Harita ve GPS AyarlarÄą", - "map_gps_settings_description": "Harita YÃļnetimi ve GPS (Ters Jeokodlama) AyarlarÄą", + "map_gps_settings": "Harita & GPS AyarlarÄą", + "map_gps_settings_description": "Harita YÃļnetimi & GPS (Ters Jeokodlama) AyarlarÄą", "map_implications": "Harita Ãļzelliği, harici bir dÃļşeme hizmetine (tiles.immich.cloud) bağlÄądÄąr", "map_light_style": "AÃ§Äąk stil", "map_manage_reverse_geocoding_settings": "Coğrafi Kodlama ayarlarÄąnÄą yÃļnet", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Email bildirimlerini etkinleştir", "notification_settings": "Bildirim AyarlarÄą", "notification_settings_description": "Email ve bildirim ayarlarÄąnÄą yÃļnet", - "oauth_allow_insecure_requests": "GÃŧvenli olmayan istekleri kabul et", - "oauth_allow_insecure_requests_description": "UYARI: Bu işlem, OAuth istekleri için TLS sertifika doğrulamasÄąnÄą devre dÄąÅŸÄą bÄąrakÄąr ve sizi MITM saldÄąrÄąlarÄąna maruz bÄąrakabilir.", "oauth_auto_launch": "Otomatik başlat", "oauth_auto_launch_description": "Giriş sayfasÄąna girildiğinde OAuth akÄąÅŸÄąnÄą otomatik olarak başlat", "oauth_auto_register": "Otomatik kayÄąt", @@ -300,11 +274,9 @@ "oauth_button_text": "Buton yazÄąsÄą", "oauth_client_secret_description": "Gizli istemci için veya genel istemci için PKCE (Kod Değişimi için KanÄąt AnahtarÄą) desteklenmiyorsa gereklidir.", "oauth_enable_description": "OAuth ile giriş yap", - "oauth_end_session_url_description": "KullanÄącÄą oturumu kapattığında onu bu URI'ye yÃļnlendir.", "oauth_mobile_redirect_uri": "Mobil yÃļnlendirme URL'si", "oauth_mobile_redirect_uri_override": "Mobilde zorla kullanÄąlacak YÃļnlendirme Adresi", "oauth_mobile_redirect_uri_override_description": "Mobil URI'ye izin vermeyen OAuth sağlayÄącÄąsÄą olduğunda etkinleştir, Ãļrneğin ''{callback}''", - "oauth_prompt_description": "İstem parametresi (Ãļrneğin select_account, login, consent)", "oauth_role_claim": "Rol Talebi", "oauth_role_claim_description": "Bu iddianÄąn varlığına gÃļre otomatik olarak yÃļnetici erişimi verin. İddia, 'kullanÄącÄą' veya 'yÃļnetici' olabilir.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "TÃŧm kÃŧtÃŧphaneler yenileniyor", "registration": "YÃļnetici KaydÄą", "registration_description": "Sistemdeki ilk kullanÄącÄą olduğunuz için hesabÄąnÄąz YÃļnetici olarak ayarlandÄą. Yeni oluşturulan Ãŧyeliklerin, ve yÃļnetici gÃļrevlerinin sorumlusu olarak atandÄąnÄąz.", - "release_channel_release_candidate": "YayÄąn adayÄą", - "release_channel_stable": "Stabil", "remove_failed_jobs": "BaşarÄąsÄąz işleri kaldÄąr", "require_password_change_on_login": "KullanÄącÄąnÄąn ilk girişinde şifre değiştirmesini zorunlu kÄąl", "reset_settings_to_default": "AyarlarÄą varsayÄąlana sÄąfÄąrla", @@ -425,16 +395,8 @@ "transcoding_policy_description": "Bir videonun ne zaman kod dÃļnÃŧştÃŧrÃŧleceğini ayarlama", "transcoding_preferred_hardware_device": "Tercih edilen donanÄąm cihazÄą", "transcoding_preferred_hardware_device_description": "Sadece VAAPI ve QSV için uygulanÄąr. DonanÄąm kod çevrimi için DRI Node ayarlar.", - "transcoding_preset_preset": "Ön ayar (-preset)", + "transcoding_preset_preset": "Ön ayar (-Ãļn)", "transcoding_preset_preset_description": "SÄąkÄąÅŸtÄąrma hÄązÄą. Daha yavaş olan ayarlar belirli bitrate ayarlarÄą için daha kÃŧçÃŧk ve daha kaliteli dosya Ãŧretir. VP9 ayarÄą 'daha hÄązlÄą' ayarÄąnÄąn ÃŧstÃŧndeki ayarlarÄą gÃļrmezden gelir.", - "transcoding_realtime": "Gerçek ZamanlÄą Kod DÃļnÃŧştÃŧrme [DENEYSEL]", - "transcoding_realtime_description": "Videonun akÄąÅŸÄą sÄąrasÄąnda kod dÃļnÃŧştÃŧrme işleminin gerçek zamanlÄą olarak yapÄąlmasÄąnÄą sağlar. Kalite değiştirmeye olanak tanÄąr, ancak sunucu yeteneklerine bağlÄą olarak daha yÃŧksek oynatma gecikmesine ve takÄąlmalara neden olabilir.", - "transcoding_realtime_enabled": "Gerçek zamanlÄą kod dÃļnÃŧştÃŧrmeyi etkinleştirin", - "transcoding_realtime_enabled_description": "Devre dÄąÅŸÄą bÄąrakÄąlÄąrsa, sunucu yeni gerçek zamanlÄą kod dÃļnÃŧştÃŧrme oturumlarÄą başlatmayÄą reddedecektir.", - "transcoding_realtime_resolutions": "ÇÃļzÃŧm", - "transcoding_realtime_resolutions_description": "Gerçek zamanlÄą dÃļnÃŧştÃŧrme (transcoding) için sunulan çÃļzÃŧnÃŧrlÃŧkler. Sunucu veriyi yeterince hÄązlÄą dÃļnÃŧştÃŧremezse, yÃŧksek çÃļzÃŧnÃŧrlÃŧkler oynatma sorunlarÄąna yol açabilir.", - "transcoding_realtime_video_codecs": "Video kodekleri", - "transcoding_realtime_video_codecs_description": "AnlÄąk kod dÃļnÃŧştÃŧrme için sunulan video kodekleri. Cihazlar, oynatma esnasÄąnda destekledikleri en iyi kodeği seçecektir. Verimlilik sÄąralamasÄą AV1 > HEVC > H.264 şeklindedir. DonanÄąm hÄązlandÄąrma aktifken sadece ekran kartÄąnÄązÄąn desteklediği kodekleri seçiniz. YazÄąlÄąmsal dÃļnÃŧştÃŧrmede ise hÄąz sÄąralamasÄą H.264 > AV1 > HEVC şeklindedir.", "transcoding_reference_frames": "Referans kareler", "transcoding_reference_frames_description": "Belirli bir kareyi sÄąkÄąÅŸtÄąrÄąrken referans alÄąnacak kare sayÄąsÄą. Daha yÃŧksek değerler sÄąkÄąÅŸtÄąrma verimliliğini artÄąrÄąr, ancak kodlamayÄą yavaşlatÄąr. 0 bu değeri otomatik olarak ayarlar.", "transcoding_required_description": "YalnÄązca kabul edilen formatta olmayan videolar", @@ -478,8 +440,6 @@ "user_settings_description": "KullanÄącÄą ayarlarÄąnÄą yÃļnet", "user_successfully_removed": "KullanÄącÄą {email} başarÄąyla kaldÄąrÄąldÄą.", "users_page_description": "YÃļnetici kullanÄącÄąlar sayfasÄą", - "version_check_channel": "YayÄąn kanalÄą", - "version_check_channel_description": "SÃŧrÃŧm duyurularÄąnÄą almak istediğiniz yayÄąn kanalÄąnÄą seçin", "version_check_enabled_description": "SÃŧrÃŧm kontrolÃŧ etkin", "version_check_implications": "SÃŧrÃŧm kontrol Ãļzelliği, {server} ile periyodik iletişime dayanÄąr", "version_check_settings": "SÃŧrÃŧm KontrolÃŧ", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "GÃļrsel Önbelleğini Temizle", "advanced_settings_clear_image_cache_error": "GÃļrsel Ãļnbelleği temizlenemedi", "advanced_settings_clear_image_cache_success": "BaşarÄąyla temizlendi: {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "Bu seçeneği, eşitleme sÄąrasÄąnda medyayÄą alternatif ÃļlçÃŧtlere gÃļre filtrelemek için kullanÄąn. UygulamanÄąn tÃŧm albÃŧmleri algÄąlamasÄąnda sorun yaÅŸÄąyorsanÄąz yalnÄązca bu durumda deneyin.", + "advanced_settings_enable_alternate_media_filter_title": "[DENEYSEL] Alternatif cihaz albÃŧm eşitleme filtresini kullan", "advanced_settings_log_level_title": "GÃŧnlÃŧk seviyesi: {level}", "advanced_settings_prefer_remote_subtitle": "BazÄą cihazlar yerel Ãļğelerden kÃŧçÃŧk resimleri yÃŧklerken çok yavaş çalÄąÅŸÄąr. Bunun yerine uzak gÃļrÃŧntÃŧleri yÃŧklemek için bu ayarÄą etkinleştirin.", "advanced_settings_prefer_remote_title": "Uzak gÃļrÃŧntÃŧleri tercih et", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Özel proxy başlÄąklarÄą [DENEYSEL]", "advanced_settings_readonly_mode_subtitle": "FotoğraflarÄąn yalnÄązca gÃļrÃŧntÃŧlenebildiği salt okunur modu etkinleştirir; birden fazla gÃļrÃŧntÃŧ seçme, paylaşma, aktarma, silme gibi işlemler devre dÄąÅŸÄą bÄąrakÄąlÄąr. Ana ekrandan kullanÄącÄą avatarÄą aracÄąlığıyla salt okunur modu Etkinleştirin/Devre dÄąÅŸÄą bÄąrakÄąn", "advanced_settings_readonly_mode_title": "Salt okunur mod", + "advanced_settings_self_signed_ssl_subtitle": "Sunucu uç noktasÄą için SSL sertifika doğrulamasÄąnÄą atlar. Kendinden imzalÄą sertifikalar için gereklidir.", + "advanced_settings_self_signed_ssl_title": "Kendinden imzalÄą SSL sertifikalarÄąna izin ver [DENEYSEL]", "advanced_settings_sync_remote_deletions_subtitle": "Web Ãŧzerinde işlem yapÄąldığında, bu aygÄąttaki Ãļğeyi otomatik olarak sil veya geri yÃŧkle", "advanced_settings_sync_remote_deletions_title": "Uzaktan silme işlemlerini eşitle [DENEYSEL]", "advanced_settings_tile_subtitle": "Gelişmiş kullanÄącÄą ayarlarÄą", @@ -516,18 +480,31 @@ "album_delete_confirmation": "{album} albÃŧmÃŧnÃŧ silmek istediğinize emin misiniz?", "album_delete_confirmation_description": "AlbÃŧm paylaÅŸÄąlÄąyorsa, diğer kullanÄącÄąlar artÄąk bu albÃŧme erişemeyecektir.", "album_deleted": "AlbÃŧm silindi", + "album_info_card_backup_album_excluded": "HARİÇ", + "album_info_card_backup_album_included": "DAHİL", "album_info_updated": "AlbÃŧm bilgisi gÃŧncellendi", + "album_leave": "AlbÃŧmden ayrÄąl?", + "album_leave_confirmation": "{album} albÃŧmÃŧnden ayrÄąlmak istediğinize emin misiniz?", "album_name": "AlbÃŧm AdÄą", "album_options": "AlbÃŧm seçenekleri", "album_remove_user": "KullanÄącÄąyÄą kaldÄąr?", "album_remove_user_confirmation": "{user} kullanÄącÄąsÄąnÄą kaldÄąrmak istediğinize emin misiniz?", "album_search_not_found": "AramanÄązla eşleşen albÃŧm bulunamadÄą", + "album_selected": "Seçilen albÃŧm", "album_share_no_users": "GÃļrÃŧnÃŧşe gÃļre bu albÃŧmÃŧ tÃŧm kullanÄącÄąlarla paylaştÄąnÄąz veya paylaşacak herhangi bir başka kullanÄącÄąnÄąz yok.", "album_summary": "AlbÃŧm Ãļzeti", "album_updated": "AlbÃŧm gÃŧncellendi", "album_updated_setting_description": "PaylaÅŸÄąlan bir albÃŧme yeni bir Ãļğe eklendiğinde e-posta bildirimi alÄąn", "album_upload_assets": "BilgisayarÄąnÄązdan gÃļrseller yÃŧkleyin ve albÃŧme ekleyin", + "album_user_left": "{album}den ayrÄąldÄąnÄąz", + "album_user_removed": "{user} kaldÄąrÄąldÄą", + "album_viewer_appbar_delete_confirm": "Bu albÃŧmÃŧ hesabÄąnÄązdan silmek istediğinizden emin misiniz?", "album_viewer_appbar_share_err_delete": "AlbÃŧm silinemedi", + "album_viewer_appbar_share_err_leave": "AlbÃŧmden Ã§ÄąkÄąlamadÄą", + "album_viewer_appbar_share_err_remove": "AlbÃŧmden Ãļğeler kaldÄąrÄąrken sorunlar yaşanÄąyor", + "album_viewer_appbar_share_err_title": "AlbÃŧm başlığı değiştirilemedi", + "album_viewer_appbar_share_leave": "AlbÃŧmden Ã§Äąk", + "album_viewer_appbar_share_to": "Paylaşma", "album_viewer_page_share_add_users": "KullanÄącÄą ekle", "album_with_link_access": "Link'e sahip olan herhangi bir kişinin bu albÃŧmdeki fotoğraflarÄą ve kişileri gÃļrmesine izin ver.", "albums": "AlbÃŧmler", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "Yeni albÃŧm oluştururken kullanÄąlacak başlangÄąÃ§ Ãļğe sÄąralama dÃŧzeni.", "albums_feature_description": "Diğer kullanÄącÄąlarla paylaÅŸÄąlabilen Ãļğe koleksiyonlarÄą.", "albums_on_device_count": "Cihazdaki albÃŧmler ({count})", + "albums_selected": "{count, plural, one {# albÃŧm seçildi} other {# albÃŧm seçildi}}", "all": "TÃŧmÃŧ", "all_albums": "TÃŧm AlbÃŧmler", "all_people": "TÃŧm Kişiler", "all_photos": "TÃŧm fotoğraflar", "all_videos": "TÃŧm Videolar", "allow_dark_mode": "Koyu moda izin ver", + "allow_edits": "DÃŧzenlemeye izin ver", "allow_public_user_to_download": "Genel kullanÄącÄąnÄąn indirmesine aç", "allow_public_user_to_upload": "Genel kullanÄącÄąnÄąn yÃŧklemesine aç", "allowed": "İzin verildi", @@ -549,12 +528,14 @@ "always_keep": "Her zaman sakla", "always_keep_photos_hint": "Alan Aç, bu cihazdaki tÃŧm fotoğraflarÄą saklar.", "always_keep_videos_hint": "Alan Aç, bu cihazdaki tÃŧm videolarÄą saklar.", + "anti_clockwise": "Saat yÃļnÃŧnÃŧn tersine", "api_key": "API AnahtarÄą", "api_key_description": "Bu değer sadece bir kere gÃļsterilecek. LÃŧtfen bu pencereyi kapatmadan Ãļnce kopyaladığınÄąza emin olun.", "api_key_empty": "ApÄą AnahtarÄą isminiz boş olmamalÄą", "api_keys": "API AnahtarlarÄą", "app_architecture_variant": "Varyant (Mimari)", "app_bar_signout_dialog_content": "Ã‡ÄąkÄąÅŸ yapmak istediğinize emin misiniz?", + "app_bar_signout_dialog_ok": "Evet", "app_bar_signout_dialog_title": "Ã‡ÄąkÄąÅŸ", "app_download_links": "Uygulama İndirme Linkleri", "app_settings": "Uygulama AyarlarÄą", @@ -565,19 +546,27 @@ "archive": "Arşiv", "archive_action_prompt": "{count} arşive eklendi", "archive_or_unarchive_photo": "FotoğrafÄą arşivle/arşivden Ã§Äąkar", + "archive_page_no_archived_assets": "Arşivlenmiş Ãļğe bulunamadÄą", + "archive_page_title": "Arşiv ({count})", "archive_size": "Arşiv boyutu", "archive_size_description": "İndirmeler için arşiv boyutunu yapÄąlandÄąrÄąn (GiB cinsinden)", "archived": "Arşivlenen", "archived_count": "{count, plural, other {# arşivlendi}}", "are_these_the_same_person": "Bunlar aynÄą kişi mi?", "are_you_sure_to_do_this": "Bunu yapmak istediğinize emin misiniz?", + "array_field_not_fully_supported": "Dizi alanlarÄą manuel JSON dÃŧzenlemesi gerektirir", + "asset_action_delete_err_read_only": "Salt okunur Ãļğeler silinemez, atlanÄąyor", + "asset_action_share_err_offline": "ÇevrimdÄąÅŸÄą Ãļğeler alÄąnamÄąyor, atlanÄąyor", "asset_added_to_album": "AlbÃŧme eklendi", "asset_adding_to_album": "AlbÃŧme ekleniyorâ€Ļ", "asset_created": "Öğe oluşturuldu", - "asset_day_count": "{date}: {count, plural, one {# dosya} other {# dosya}}", "asset_description_updated": "Öğe aÃ§ÄąklamasÄą gÃŧncellendi", + "asset_filename_is_offline": "Öğe {filename} çevrimdÄąÅŸÄą", + "asset_has_unassigned_faces": "Öğe, atanmamÄąÅŸ yÃŧzler içeriyor", "asset_hashing": "Karma (hashleme) oluşturuluyorâ€Ļ", "asset_list_group_by_sub_title": "Grupla", + "asset_list_layout_settings_dynamic_layout_title": "Dinamik dÃŧzen", + "asset_list_layout_settings_group_automatically": "Otomatik", "asset_list_layout_settings_group_by": "Öğeleri grupla", "asset_list_layout_settings_group_by_month_day": "Ay + gÃŧn", "asset_list_layout_sub_title": "DÃŧzen", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "Öğe iCloud platformunda bulunamadÄą. Öğe, iCloud platformunda depolanan hatalÄą dosya nedeniyle erişilemeyebilir", "asset_offline": "Öğe Çevrim DÄąÅŸÄą", "asset_offline_description": "Bu harici Ãļğe artÄąk diskte bulunmuyor. YardÄąm için lÃŧtfen Immich yÃļneticinizle iletişime geçin.", + "asset_restored_successfully": "Öğe başarÄąyla geri yÃŧklendi", "asset_skipped": "AtlandÄą", "asset_skipped_in_trash": "ÇÃļpte", + "asset_trashed": "Öğe çÃļpe atÄąldÄą", "asset_troubleshoot": "Öğe Sorun Giderme", "asset_uploaded": "YÃŧklendi", "asset_uploading": "YÃŧkleniyorâ€Ļ", "asset_viewer_settings_subtitle": "Galeri gÃļrÃŧntÃŧleyici ayarlarÄąnÄą dÃŧzenle", "asset_viewer_settings_title": "İçerik GÃļrÃŧntÃŧleyici", "assets": "Öğeler", - "assets_added_to_album_count": "AlbÃŧme {count, plural, one {# Ãļge} other {# Ãļge}} eklendi", - "assets_added_to_album_partial_count": "AlbÃŧme {successCount} içerisinden {totalCount} {totalCount, plural, one {Ãļgeden} other {Ãļgeden}} tane eklendi", + "assets_added_count": "Eklendi {count, plural, one {# asset} other {# assets}}", + "assets_added_to_album_count": "AlbÃŧme {count, plural, one {# asset} other {# assets}} eklendi", "assets_added_to_albums_count": "Eklendi {assetTotal, plural, one {# asset} other {# assets}} buraya {albumTotal, plural, one {# album} other {# albums}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {Asset} other {Assets}} albÃŧme eklenemiyor", "assets_cannot_be_added_to_albums": "{count, plural, one {Asset} other {Assets}} hiçbir albÃŧme eklenemez", "assets_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}}", + "assets_deleted_permanently": "{count} Ãļğe kalÄącÄą olarak silindi", + "assets_deleted_permanently_from_server": "{count} Ãļğe kalÄącÄą olarak Immich sunucusundan silindi", + "assets_downloaded_failed": "{count, plural, one {İndirilen # dosya - {error} dosya başarÄąsÄąz} other {İndirilen # dosyalar - {error} dosyalar başarÄąsÄąz oldu}}", + "assets_downloaded_successfully": "{count, plural, one {# dosya başarÄąyla indirildi} other {# dosya başarÄąyla indirildi}}", "assets_moved_to_trash_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} çÃļpe taÅŸÄąndÄą", "assets_permanently_deleted_count": "KalÄącÄą olarak silindi {count, plural, one {# Ãļğe} other {# Ãļğeler}}", "assets_removed_count": "KaldÄąrÄąldÄą {count, plural, one {# Ãļğe} other {# Ãļğeler}}", + "assets_removed_permanently_from_device": "{count} Ãļğe cihazÄąnÄązdan kalÄącÄą olarak silindi", "assets_restore_confirmation": "TÃŧm çÃļp kutusundaki Ãļğeleri geri yÃŧklemek istediğinizden emin misiniz? Bu işlemi geri alamazsÄąnÄąz! AyrÄąca, çevrim dÄąÅŸÄą olan Ãļğelerin bu şekilde geri yÃŧklenemeyeceğini unutmayÄąn.", "assets_restored_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} geri yÃŧklendi", + "assets_restored_successfully": "{count} Ãļğe başarÄąyla geri yÃŧklendi", "assets_trashed": "{count} Ãļğe çÃļpe atÄąldÄą", "assets_trashed_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} çÃļp kutusuna taÅŸÄąndÄą", + "assets_trashed_from_server": "{count} Ãļğe Immich sunucusundan çÃļpe atÄąldÄą", "assets_were_part_of_album_count": "{count, plural, one {Öğe zaten} other {Öğeler zaten}} albÃŧmÃŧn parçasÄąydÄą", "assets_were_part_of_albums_count": "{count, plural, one {Öğe zaten} other {Öğeler zaten}} albÃŧmlerin bir parçasÄąydÄą", "authorized_devices": "Yetki Verilmiş Cihazlar", @@ -617,48 +615,87 @@ "autoplay_slideshow": "Otomatik slayt gÃļsterisi", "back": "Geri", "back_close_deselect": "Geri, kapat veya seçimi kaldÄąr", + "background_backup_running_error": "Arka plan yedekleme şu anda çalÄąÅŸÄąyor, manuel yedekleme başlatÄąlamÄąyor", "background_location_permission": "Arka plan konum izni", "background_location_permission_content": "Arka planda çalÄąÅŸÄąrken ağ değiştirmek için Immich'in *her zaman* tam konum erişimine sahip olmasÄą gerekir, bÃļylece uygulama Wi-Fi ağınÄąn adÄąnÄą okuyabilir", "background_options": "Arka Plan Seçenekleri", "backup": "Yedekle", + "backup_album_selection_page_albums_device": "Cihazdaki albÃŧmler ({count})", "backup_album_selection_page_albums_tap": "Seçmek için dokunun, hariç tutmak için çift dokunun", "backup_album_selection_page_assets_scatter": "Öğeler birden fazla albÃŧme dağılabilir. Bu nedenle, yedekleme işlemi sÄąrasÄąnda albÃŧmler dahil edilebilir veya hariç tutulabilir.", "backup_album_selection_page_select_albums": "AlbÃŧmleri seç", "backup_album_selection_page_selection_info": "Seçim Bilgileri", + "backup_album_selection_page_total_assets": "Toplam eşsiz Ãļğeler", "backup_albums_sync": "AlbÃŧm Yedeklerini Eşitleme", + "backup_all": "TÃŧmÃŧ", + "backup_background_service_backup_failed_message": "Yedekleme başarÄąsÄąz. Tekrar deneniyorâ€Ļ", "backup_background_service_complete_notification": "Öğe yedekleme tamamlandÄą", + "backup_background_service_connection_failed_message": "Sunucuya bağlanÄąlamadÄą. Tekrar deneniyorâ€Ļ", + "backup_background_service_current_upload_notification": "{filename} yÃŧkleniyor", "backup_background_service_default_notification": "Yeni Ãļğeler kontrol ediliyorâ€Ļ", + "backup_background_service_error_title": "Yedekleme hatasÄą", "backup_background_service_in_progress_notification": "Öğeleriniz yedekleniyorâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} yÃŧklemesi başarÄąsÄąz oldu", "backup_controller_page_albums": "Yedekleme AlbÃŧmleri", + "backup_controller_page_background_app_refresh_disabled_content": "Arka planda yedeklemeyi kullanabilmek için Ayarlar > Genel > Arka Planda Uygulama Yenileme bÃļlÃŧmÃŧnden arka planda uygulama yenilemeyi etkinleştirin.", + "backup_controller_page_background_app_refresh_disabled_title": "Arka planda uygulama yenileme devre dÄąÅŸÄą bÄąrakÄąldÄą", + "backup_controller_page_background_app_refresh_enable_button_text": "Ayarlara git", "backup_controller_page_background_battery_info_link": "Bana nasÄąl olduğunu gÃļster", "backup_controller_page_background_battery_info_message": "En iyi arka plan yedekleme deneyimi için lÃŧtfen Immich'in arka plan etkinliğini kÄąsÄątlayabilecek tÃŧm pil optimizasyonlarÄąnÄą devre dÄąÅŸÄą bÄąrakÄąn.\n\nBu ayarÄąn yeri cihaz modeline gÃļre değişiklik gÃļsterdiğinden cihazÄąnÄązda nerede olduğunu lÃŧtfen araştÄąrÄąn.", "backup_controller_page_background_battery_info_ok": "Tamam", "backup_controller_page_background_battery_info_title": "Pil optimizasyonlarÄą", + "backup_controller_page_background_charging": "Sadece şarjda", + "backup_controller_page_background_configure_error": "Arka plan hizmeti yapÄąlandÄąrÄąlamadÄą", "backup_controller_page_background_delay": "Yeni Ãļğelerin yedeklemesini geciktir: {duration}", + "backup_controller_page_background_description": "UygulamayÄą açmaya gerek kalmadan yeni Ãļğeleri otomatik olarak yedeklemek için arka plan hizmetini aÃ§Äąn", + "backup_controller_page_background_is_off": "Otomatik arka planda yedekleme kapalÄą", + "backup_controller_page_background_is_on": "Otomatik arka planda yedekleme aÃ§Äąk", + "backup_controller_page_background_turn_off": "Arka plan hizmetini kapat", + "backup_controller_page_background_turn_on": "Arka plan hizmetini aç", + "backup_controller_page_background_wifi": "Sadece Wi-Fi", "backup_controller_page_backup": "Yedek", "backup_controller_page_backup_selected": "Seçilen: ", "backup_controller_page_backup_sub": "Yedeklenen fotoğraflar ve videolar", + "backup_controller_page_created": "Oluşturma tarihi: {date}", + "backup_controller_page_desc_backup": "UygulamayÄą açtığınÄązda yeni Ãļğelerin sunucuya otomatik olarak yÃŧklenmesi için Ãļn planda yedeklemeyi aÃ§Äąn.", "backup_controller_page_excluded": "Hariç tutuldu: ", + "backup_controller_page_failed": "BaşarÄąsÄąz ({count})", + "backup_controller_page_filename": "Dosya adÄą: {filename} [{size}]", + "backup_controller_page_id": "KNu: {id}", + "backup_controller_page_info": "Yedekleme Bilgileri", "backup_controller_page_none_selected": "Hiçbiri seçilmedi", "backup_controller_page_remainder": "Kalan", "backup_controller_page_remainder_sub": "Seçili albÃŧmlerden yedeklenecek kalan Ãļğeler", "backup_controller_page_server_storage": "Sunucu Depolama AlanÄą", + "backup_controller_page_start_backup": "Yedeklemeye Başla", + "backup_controller_page_status_off": "Otomatik Ãļn planda yedekleme kapalÄą", + "backup_controller_page_status_on": "Otomatik Ãļn planda yedekleme aÃ§Äąk", "backup_controller_page_storage_format": "{used}/{total} kullanÄąlÄąyor", "backup_controller_page_to_backup": "Yedeklenecek albÃŧmler", "backup_controller_page_total_sub": "Seçili albÃŧmlerden tÃŧm benzersiz Ãļğeler", + "backup_controller_page_turn_off": "Ön planda yedeklemeyi kapat", + "backup_controller_page_turn_on": "Ön planda yedeklemeyi aç", + "backup_controller_page_uploading_file_info": "Dosya bilgisi yÃŧkleniyor", + "backup_err_only_album": "Tek albÃŧm kaldÄąrÄąlamaz", "backup_error_sync_failed": "Eşitleme başarÄąsÄąz. Yedekleme işlemi gerçekleştirilemiyor.", "backup_info_card_assets": "Ãļğeler", + "backup_manual_cancelled": "İptal Edildi", + "backup_manual_in_progress": "YÃŧkleme halihazÄąrda devam ediyor. Bir sÃŧre sonra deneyin", + "backup_manual_success": "BaşarÄąlÄą", + "backup_manual_title": "YÃŧkleme durumu", "backup_options": "Yedekleme Seçenekleri", + "backup_options_page_title": "Yedekleme seçenekleri", + "backup_setting_subtitle": "Arka planda ve Ãļn planda yÃŧkleme ayarlarÄąnÄą dÃŧzenle", "backup_settings_subtitle": "YÃŧkleme ayarlarÄąnÄą yÃļnet", + "backup_upload_details_page_more_details": "Daha fazla ayrÄąntÄą için dokunun", "backward": "Geriye doğru", - "battery_optimization_backup_reliability": "Pil optimizasyonlarÄąnÄą devre dÄąÅŸÄą bÄąrakmak, arka plan yedeklemesinin gÃŧvenilirliğini artÄąrabilir", "biometric_auth_enabled": "Biyometrik kimlik doğrulama etkin", "biometric_locked_out": "Biyometrik kimlik doğrulamasÄą kilitli", "biometric_no_options": "Biyometrik seçenek yok", "biometric_not_available": "Bu cihazda biyometrik kimlik doğrulama mevcut değil", + "birthdate_saved": "Doğum gÃŧnÃŧ başarÄąlÄą bir şekilde kaydedildi", "birthdate_set_description": "Doğum gÃŧnÃŧ, fotoğraftaki insanÄąn fotoğraf çekildiği zamandaki yaÅŸÄąnÄąn hesaplanmasÄą için kullanÄąlÄąr.", "blurred_background": "BulanÄąk arka plan", - "browse_templates": "Şablonlara gÃļz atÄąn", "bugs_and_feature_requests": "Hatalar ve Özellik Talepleri", "build": "YapÄą", "build_image": "GÃļrÃŧntÃŧ Oluştur", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "{count, plural, one {# kopya Ãļğeyi} other {# kopya Ãļğeleri}} tutmak istediğinizden emin misiniz? Bu işlem, hiçbir şeyi silmeden tÃŧm kopya gruplarÄąnÄą çÃļzecektir.", "bulk_trash_duplicates_confirmation": "{count, plural, one {# kopya Ãļğeyi} other {# kopya Ãļğeleri}} toplu olarak çÃļp kutusuna taÅŸÄąmak istediğinizden emin misiniz? Bu işlem, her grubun en bÃŧyÃŧk Ãļğesini tutacak ve diğer tÃŧm kopyalarÄą çÃļp kutusuna taÅŸÄąyacaktÄąr.", "buy": "Immich'i SatÄąn AlÄąn", + "cache_settings_clear_cache_button": "Önbelleği temizle", + "cache_settings_clear_cache_button_title": "UygulamanÄąn Ãļnbelleğini temizleyin. Önbellek yeniden oluşturulana kadar uygulamanÄąn performansÄąnÄą Ãļnemli ÃļlçÃŧde etkileyecektir.", + "cache_settings_duplicated_assets_clear_button": "TEMİZLE", + "cache_settings_duplicated_assets_subtitle": "Uygulama tarafÄąndan yok sayÄąlan fotoğraflar ve videolar", + "cache_settings_duplicated_assets_title": "Yinelenen Öğeler ({count})", + "cache_settings_statistics_album": "KÃŧtÃŧphane kÃŧçÃŧk resimleri", + "cache_settings_statistics_full": "Tam çÃļzÃŧnÃŧrlÃŧkte resimler", + "cache_settings_statistics_shared": "PaylaÅŸÄąlan albÃŧm kÃŧçÃŧk resimleri", + "cache_settings_statistics_thumbnail": "KÃŧçÃŧk resimler", + "cache_settings_statistics_title": "Önbellek kullanÄąmÄą", + "cache_settings_subtitle": "Immich mobil uygulamasÄąnÄąn Ãļnbelleğe alma davranÄąÅŸÄąnÄą kontrol edin", + "cache_settings_tile_subtitle": "Yerel depolama davranÄąÅŸÄąnÄą kontrol et", + "cache_settings_tile_title": "Yerel Depolama", + "cache_settings_title": "Önbellek AyarlarÄą", "camera": "Kamera", "camera_brand": "Kamera markasÄą", "camera_model": "Kamera modeli", @@ -678,12 +729,12 @@ "cannot_update_the_description": "AÃ§Äąklama gÃŧncellenemiyor", "cast": "YansÄąt", "cast_description": "KullanÄąlabilir yansÄątma hedeflerini yapÄąlandÄąr", - "change": "Değiştir", "change_date": "Tarihi değiştir", "change_description": "AÃ§ÄąklamayÄą değiştir", "change_display_order": "GÃļrÃŧntÃŧleme sÄąrasÄąnÄą değiştir", + "change_expiration_time": "Son kullanma sÃŧresini değiştir", "change_location": "Konumu değiştir", - "change_name": "AdÄą değiştir", + "change_name": "İsim değiştir", "change_name_successfully": "AdÄą başarÄąyla değiştirildi", "change_password": "Şifre Değiştir", "change_password_description": "Bu sisteme ilk kez giriş yapÄąyorsunuz veya şifrenizi değiştirmek için bir istekte bulunuldu. LÃŧtfen aşağıya yeni şifrenizi girin.", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "Şifreler eşleşmiyor", "change_password_form_reenter_new_password": "Yeni Şifreyi Tekrar Giriniz", "change_pin_code": "PIN kodunu değiştirin", + "change_trigger": "Tetikleyiciyi değiştir", + "change_trigger_prompt": "Tetikleyiciyi değiştirmek istediğinizden emin misiniz? Bu, mevcut tÃŧm eylemleri ve filtreleri kaldÄąracaktÄąr.", "change_your_password": "Şifreni değiştir", "changed_visibility_successfully": "GÃļrÃŧnÃŧrlÃŧk başarÄąyla değiştirildi", "charging": "Şarj oluyor", "charging_requirement_mobile_backup": "Arka plan yedekleme için cihazÄąn şarjda olmasÄą gerekir", + "check_corrupt_asset_backup": "Bozuk Ãļğe yedeklemelerini kontrol et", + "check_corrupt_asset_backup_button": "Kontrol et", + "check_corrupt_asset_backup_description": "Bu kontrolÃŧ yalnÄązca Wi-Fi Ãŧzerinden ve tÃŧm Ãļğeler yedeklendikten sonra çalÄąÅŸtÄąrÄąn. İşlem birkaç dakika sÃŧrebilir.", "check_logs": "GÃŧnlÃŧkleri Kontrol Et", "checksum": "Sağlama toplamÄą", - "choose": "Seçmek", "choose_matching_people_to_merge": "Birleştirmek için eşleşen kişileri seçiniz", "city": "Şehir", "cleanup_confirm_description": "Immich, sunucuya gÃŧvenli bir şekilde yedeklenmiş {count} adet gÃļrsel ( {date} tarihinden Ãļnce oluşturulmuş) buldu. Yerel kopyalarÄą bu cihazdan kaldÄąrmak istiyor musunuz?", @@ -719,10 +774,11 @@ "clear": "Temizle", "clear_all": "Hepsini temizle", "clear_all_recent_searches": "Son aramalarÄąn hepsini temizle", - "clear_failed_count": "Temizleme başarÄąsÄąz oldu ({count})", "clear_file_cache": "Dosya Önbelleğini Temizle", "clear_message": "MesajÄą temizle", "clear_value": "Değeri temizle", + "client_cert_dialog_msg_confirm": "Tamam", + "client_cert_enter_password": "Şifreyi Girin", "client_cert_import": "İçe Aktar", "client_cert_import_success_msg": "İstemci sertifikasÄą içe aktarÄąldÄą", "client_cert_invalid_msg": "Geçersiz sertifika dosyasÄą veya yanlÄąÅŸ şifre", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "İstemci sertifikasÄą kaldÄąrÄąldÄą", "client_cert_subtitle": "YalnÄązca PKCS12 (.p12, .pfx) formatÄąnÄą destekler. Sertifika içe aktarma/kaldÄąrma işlemi yalnÄązca oturum açmadan Ãļnce yapÄąlabilir", "client_cert_title": "SSL istemci sertifikasÄą [DENEYSEL]", + "clockwise": "Saat yÃļnÃŧ", "close": "Kapat", "collapse": "Daralt", "collapse_all": "TÃŧmÃŧnÃŧ Daralt", "color": "Renk", + "color_theme": "Renk temasÄą", "command": "Komut", "command_palette_prompt": "SayfalarÄą, eylemleri veya komutlarÄą hÄązlÄąca bulun", "command_palette_to_close": "kapatmak için", @@ -743,11 +801,10 @@ "command_palette_to_show_all": "tÃŧmÃŧnÃŧ gÃļster", "comment_deleted": "Yorum silindi", "comment_options": "Yorum seçenekleri", - "comments_and_likes": "Yorumlar ve beğeniler", + "comments_and_likes": "Yorumlar & beğeniler", "comments_are_disabled": "Yorumlar devre dÄąÅŸÄą", "common_create_new_album": "Yeni AlbÃŧm", "completed": "TamamlandÄą", - "configuration": "YapÄąlandÄąrma", "confirm": "Onayla", "confirm_admin_password": "YÃļnetici Şifresini Onayla", "confirm_delete_face": "Öğeden {name} yÃŧzÃŧnÃŧ silmek istediğinizden emin misiniz?", @@ -762,16 +819,19 @@ "contain": "SığdÄąr", "context": "Bağlam", "continue": "Devam et", - "control_bottom_app_bar_add_tags": "Etiket ekle", + "control_bottom_app_bar_create_new_album": "Yeni albÃŧm", + "control_bottom_app_bar_delete_from_immich": "Immich'ten sil", "control_bottom_app_bar_delete_from_local": "Cihazdan sil", "control_bottom_app_bar_edit_location": "Konumu DÃŧzenle", "control_bottom_app_bar_edit_time": "Tarih ve Saati DÃŧzenle", + "control_bottom_app_bar_share_link": "BağlantÄąyÄą Paylaş", + "control_bottom_app_bar_share_to": "Paylaşma", "control_bottom_app_bar_trash_from_immich": "ÇÃļp Kutusuna At", "copied_image_to_clipboard": "Resim, panoya kopyalandÄą.", "copied_to_clipboard": "Panoya kopyalandÄą!", "copy_error": "Kopyalama hatasÄą", + "copy_file_path": "Dosya yolunu kopyala", "copy_image": "Resmi Kopyala", - "copy_json": "JSON'dan kopyala", "copy_link": "BağlantÄąyÄą kopyala", "copy_link_to_clipboard": "BağlantÄąyÄą panoya kopyala", "copy_password": "Şifreyi kopyala", @@ -789,11 +849,12 @@ "create_link_to_share": "Paylaşmak için link oluştur", "create_link_to_share_description": "BağlantÄąya sahip olan herkesin seçilen fotoğraflarÄą gÃļrmesine izin ver", "create_new": "YENİ OLUŞTUR", + "create_new_face": "Yeni yÃŧz oluştur", "create_new_person": "Yeni kişi oluştur", "create_new_person_hint": "Seçili Ãļğeleri yeni bir kişiye atayÄąn", "create_new_user": "Yeni kullanÄącÄą oluştur", "create_person": "Kişi oluştur", - "create_person_subtitle": "Seçilen yÃŧze bir ad ekleyerek yeni kişiyi oluşturun ve etiketleyin", + "create_person_subtitle": "Seçilen yÃŧze bir isim ekleyerek yeni kişiyi oluşturun ve etiketleyin", "create_shared_album_page_share_add_assets": "ÖĞELER EKLE", "create_shared_album_page_share_select_photos": "FotoğraflarÄą Seç", "create_shared_link": "PaylaÅŸÄąlan bağlantÄą oluştur", @@ -805,33 +866,37 @@ "created_at": "Oluşturuldu", "creating_linked_albums": "BağlantÄąlÄą albÃŧmler oluşturuluyor...", "crop": "Kes", + "crop_aspect_ratio_fixed": "Sabitlenmiş", "crop_aspect_ratio_free": "Boş", "crop_aspect_ratio_original": "Orijinal", "crop_aspect_ratio_square": "Kare", + "curated_object_page_title": "Nesneler", "current_device": "Mevcut cihaz", "current_pin_code": "Mevcut PIN kodu", "current_server_address": "Mevcut sunucu adresi", "custom_date": "Özel tarih", "custom_locale": "Özel yerel ayar", "custom_locale_description": "Tarih, saat ve sayÄąlar seçilen dil ve bÃļlgeye gÃļre biçimlendirilsin", + "custom_url": "Özel URL", "cutoff_date_description": "Son dÃļneme ait fotoğraflarÄą tut â€Ļ", "cutoff_day": "{count, plural, one {gÃŧn} other {gÃŧn}}", "cutoff_year": "{count, plural, one {yÄąl} other {yÄąl}}", + "daily_title_text_date": "dd MMM E", + "daily_title_text_date_year": "dd MMM yyyy E", "dark": "Koyu", "dark_theme": "Koyu temaya geç", "date": "Tarih", "date_after": "Sonraki tarih", "date_and_time": "Tarih ve Zaman", "date_before": "Önceki tarih", - "date_of_birth": "Doğum tarihi", + "date_format": "LLL d, y E â€ĸ H:mm", "date_of_birth_saved": "Doğum gÃŧnÃŧ başarÄą ile kaydedildi", "date_range": "Tarih aralığı", - "date_time_original": "Orijinal Tarih/Zaman", "day": "GÃŧn", "days": "GÃŧnler", "deduplicate_all": "TÃŧm kopyalarÄą kaldÄąr", - "default_quality_subtitle": "Paylaş dÃŧğmesine dokunulduğunda kullanÄąlacak kalite. Her seferinde seçim yapmak için paylaş dÃŧğmesine uzun sÃŧre basÄąn.", - "default_share_quality": "VarsayÄąlan paylaÅŸÄąm kalitesi", + "default_locale": "VarsayÄąlan Dil", + "default_locale_description": "Tarih ve sayÄąlarÄą tarayÄącÄąnÄązÄąn yerel ayarlarÄąna gÃļre biçimlendirin", "delete": "Sil", "delete_action_confirmation_message": "Bu Ãļğeyi silmek istediğinizden emin misiniz? Bu işlem, Ãļğeyi sunucunun çÃļp kutusuna taÅŸÄąyacak ve yerel olarak silmek isteyip istemediğinizi soracaktÄąr", "delete_action_prompt": "{count} silindi", @@ -840,6 +905,8 @@ "delete_dialog_alert": "Bu Ãļğeler cihazÄąnÄązdan ve Immich'ten kalÄącÄą olarak silinecektir", "delete_dialog_alert_local": "Bu Ãļğeler cihazÄąnÄązdan kalÄącÄą olarak silinecek ancak Immich sunucusunda mevcut kalacaklardÄąr", "delete_dialog_alert_local_non_backed_up": "BazÄą Ãļğeler Immich'e yedeklenmemiş ve cihazÄąnÄązdan kalÄącÄą olarak silinecek", + "delete_dialog_alert_remote": "Bu Ãļğeler Immich sunucusundan kalÄącÄą olarak silinecektir", + "delete_dialog_ok_force": "Yine de Sil", "delete_dialog_title": "KalÄącÄą Olarak Sil", "delete_duplicates_confirmation": "Bu kopyalarÄą kalÄącÄą olarak silmek istediğinizden emin misiniz?", "delete_face": "YÃŧzÃŧ sil", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "{tagName} etiketini silmek istediğinizden emin misiniz?", "delete_user": "KullanÄącÄąyÄą sil", "deleted_shared_link": "PaylaÅŸÄąlan bağlantÄą silindi", + "deletes_missing_assets": "Diskte eksik olan Ãļğeleri siler", "description": "AÃ§Äąklama", + "description_input_hint_text": "AÃ§Äąklama ekle...", + "description_input_submit_error": "AÃ§Äąklama gÃŧncellenirken hata oluştu, daha fazla ayrÄąntÄą için gÃŧnlÃŧğÃŧ kontrol edin", "deselect_all": "TÃŧmÃŧnÃŧ Seçimi KaldÄąr", "details": "Detaylar", "direction": "YÃļn", "disable": "Devre dÄąÅŸÄą bÄąrak", "disabled": "Devre dÄąÅŸÄą bÄąrakÄąldÄą", + "disallow_edits": "Değişikliklere izin verme", "discord": "Discord", "discover": "Keşfet", "discovered_devices": "Keşfedilen aygÄątlar", "dismiss_all_errors": "TÃŧm hatalarÄą yoksay", + "dismiss_error": "HatayÄą yoksay", "display_options": "GÃļrÃŧntÃŧleme seçenekleri", "display_order": "GÃļsterim sÄąralamasÄą", "display_original_photos": "Orijinal fotoğraflarÄą gÃļster", @@ -876,9 +948,11 @@ "documentation": "DokÃŧmantasyon", "done": "Bitti", "download": "İndir", + "download_action_prompt": "{count} Ãļğe indiriliyor", "download_canceled": "İndirme iptal edildi", "download_complete": "İndirme tamamlandÄą", "download_enqueue": "İndirme sÄąraya alÄąndÄą", + "download_error": "İndirme HatasÄą", "download_failed": "İndirme başarÄąsÄąz oldu", "download_finished": "İndirme tamamlandÄą", "download_include_embedded_motion_videos": "GÃļmÃŧlÃŧ videolar", @@ -888,15 +962,15 @@ "download_paused": "İndirme duraklatÄąldÄą", "download_settings": "İndir", "download_settings_description": "Öğe indirme ile ilgili ayarlarÄą yÃļnetin", + "download_started": "İndirme başladÄą", + "download_sucess": "İndirme başarÄąlÄą", + "download_sucess_android": "Medya DCIM/Immich klasÃļrÃŧne indirildi", "download_waiting_to_retry": "Yeniden denemek için bekleniyor", "downloading": "İndiriliyor", "downloading_asset_filename": "Öğe indiriliyor {filename}", "downloading_from_icloud": "iCloud’dan indiriliyor", "downloading_media": "Medya indiriliyor", - "drag_to_reorder": "SÄąrayÄą değiştirmek için sÃŧrÃŧkleyin", "drop_files_to_upload": "DosyalarÄą yÃŧklemek için herhangi bir yere bÄąrakÄąn", - "duplicate": "Kopyala", - "duplicate_workflow": "İş akÄąÅŸÄąnÄą kopyala", "duplicates": "Kopyalar", "duplicates_description": "Her bir grubu, varsa tekrarlanan Ãļğeleri belirterek çÃļzÃŧmleyin.", "duration": "SÃŧre", @@ -908,7 +982,9 @@ "edit_date_and_time": "Tarih ve zamanÄą dÃŧzenleyin", "edit_date_and_time_action_prompt": "{count} tarih ve zaman dÃŧzenlendi", "edit_date_and_time_by_offset": "Tarihi ofset ile değiştir", + "edit_date_and_time_by_offset_interval": "Yeni tarih aralığı: {from}'dan {to}'a kadar", "edit_description": "AÃ§ÄąklamayÄą dÃŧzenle", + "edit_description_prompt": "LÃŧtfen yeni bir aÃ§Äąklama seçin:", "edit_exclusion_pattern": "Hariç tutma desenini dÃŧzenle", "edit_faces": "YÃŧzleri DÃŧzenleyin", "edit_key": "AnahtarÄą dÃŧzenle", @@ -923,6 +999,9 @@ "edit_user": "KullanÄącÄąyÄą dÃŧzenle", "edit_workflow": "İş akÄąÅŸÄąnÄą dÃŧzenle", "editor": "EditÃļr", + "editor_close_without_save_prompt": "Değişiklikler kaydedilmeyecek", + "editor_close_without_save_title": "DÃŧzenleyici kapatÄąlsÄąn mÄą?", + "editor_confirm_reset_all_changes": "TÃŧm değişikleri iptal edilecek. Emin misiniz?", "editor_discard_edits_confirm": "DÃŧzenlemeleri iptal et", "editor_discard_edits_prompt": "Kaydedilmemiş dÃŧzenlemeleriniz var. BunlarÄą iptal etmek istediğinizden emin misiniz?", "editor_discard_edits_title": "DÃŧzenlemeleri iptal edelim mi?", @@ -951,7 +1030,9 @@ "enter_your_pin_code": "PIN kodunuzu girin", "enter_your_pin_code_subtitle": "Kilitli klasÃļre erişmek için PIN kodunuzu girin", "error": "Hata", + "error_change_sort_album": "AlbÃŧm sÄąralama dÃŧzeni değiştirilemedi", "error_delete_face": "Öğeden yÃŧz silme hatasÄą", + "error_getting_places": "Konum bilgisi alÄąnÄąrken hata oluştu", "error_loading_albums": "AlbÃŧmler yÃŧklenirken hata oluştu", "error_loading_image": "Resim yÃŧklenirken hata oluştu", "error_loading_partners": "OrtaklarÄą yÃŧkleme hatasÄą: {error}", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "ÜrÃŧn anahtarÄą kaldÄąrÄąlamadÄą", "failed_to_reset_pin_code": "PIN kodu sÄąfÄąrlanamadÄą", "failed_to_stack_assets": "Öğeler yığınlanamadÄą", - "failed_to_tag_assets": "VarlÄąklarÄą etiketleme başarÄąsÄąz oldu", "failed_to_unstack_assets": "Öğelerin yığınÄą kaldÄąrÄąlamadÄą", "failed_to_update_notification_status": "Bildirim durumu gÃŧncellenemedi", "incorrect_email_or_password": "YanlÄąÅŸ e-posta veya şifre", @@ -1063,7 +1143,7 @@ "unable_to_save_album": "AlbÃŧm kaydedilemiyor", "unable_to_save_api_key": "API anahtarÄą kaydedilemiyor", "unable_to_save_date_of_birth": "Doğum gÃŧnÃŧ kaydedilemiyor", - "unable_to_save_name": "Ad kaydedilemiyor", + "unable_to_save_name": "İsim kaydedilemyor", "unable_to_save_profile": "Profil kaydedilemiyor", "unable_to_save_settings": "Ayarlar kaydedilemiyor", "unable_to_scan_libraries": "KÃŧtÃŧphaneler taranamÄąyor", @@ -1090,10 +1170,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "AÃ§Äąklama Ekle...", "exif_bottom_sheet_description_error": "AÃ§Äąklama gÃŧncelleme hatasÄą", + "exif_bottom_sheet_details": "DETAYLAR", + "exif_bottom_sheet_location": "KONUM", "exif_bottom_sheet_no_description": "AÃ§Äąklama yok", + "exif_bottom_sheet_people": "KİŞİLER", + "exif_bottom_sheet_person_add_person": "İsim ekle", "exit_slideshow": "Slayt gÃļsterisinden Ã§Äąk", "expand": "Genişlet", "expand_all": "Hepsini genişlet", + "experimental_settings_new_asset_list_subtitle": "ÇalÄąÅŸmalar devam ediyor", + "experimental_settings_new_asset_list_title": "Deneysel fotoğraf ÄązgarasÄąnÄą etkinleştir", + "experimental_settings_subtitle": "Riskleri kabul ederek kullanÄąn!", + "experimental_settings_title": "Deneysel", "expire_after": "Sonlanma sÃŧresi", "expired": "SÃŧresi dolmuş", "expires_date": "{date} tarihinde sona eriyor", @@ -1103,40 +1191,41 @@ "export_as_json": "JSON olarak DÄąÅŸa Aktar", "export_database": "VeritabanÄąnÄą DÄąÅŸa Aktar", "export_database_description": "SQLite veritabanÄąnÄą dÄąÅŸa aktarÄąn", - "exposure_time": "Poz SÃŧresi", "extension": "UzantÄą", "external": "Harici", "external_libraries": "Harici kÃŧtÃŧphaneler", "external_network": "Harici ağlar", "external_network_sheet_info": "Belirlenmiş Wi-Fi ağına bağlÄą olmadığında uygulama, yukarÄądan aşağıya doğru ulaşabileceği aşağıdaki URL'lerden ilki aracÄąlığıyla sunucuya bağlanacaktÄąr", - "f_number": "F-NumarasÄą", "face_unassigned": "YÃŧz atanmadÄą", "failed": "BaşarÄąsÄąz", "failed_count": "BaşarÄąsÄąz: {count}", "failed_to_authenticate": "Kimlik doğrulamasÄą yapÄąlamadÄą", - "failed_to_delete_file": "Dosya silme işlemi başarÄąsÄąz oldu", "failed_to_load_assets": "Öğeler yÃŧklenemedi", "failed_to_load_folder": "KlasÃļr yÃŧklenemedi", "favorite": "Favori", "favorite_action_prompt": "{count} Favorilere eklendi", "favorite_or_unfavorite_photo": "Favorilere ekle veya Ã§Äąkar", "favorites": "Favoriler", + "favorites_page_no_favorites": "Favori Ãļğe bulunamadÄą", "feature_photo_updated": "Öne Ã§Äąkan fotoğraf gÃŧncellendi", "features": "Özellikler", + "features_in_development": "Geliştirme AşamasÄąndaki Özellikler", "features_setting_description": "UygulamanÄąn Ãļzelliklerini yÃļnet", "file_name_or_extension": "Dosya adÄą veya uzantÄą", "file_name_text": "Dosya adÄą", + "file_name_with_value": "Dosya adÄą: {file_name}", "file_size": "Dosya boyutu", "filename": "Dosya AdÄą", "filetype": "Dosya tipi", "filter": "Filtre", + "filter_description": "Hedef Ãļğeleri filtreleme koşullarÄą", "filter_people": "Kişileri filtrele", "filter_places": "Yerleri sÃŧz", "filter_tags": "SÃŧzgeç etiketleri", "filters": "Filtreler", + "find_them_fast": "AdlarÄąna gÃļre hÄązlÄąca bul", "first": "İlk", "fix_incorrect_match": "YanlÄąÅŸ eşleştirmeyi dÃŧzelt", - "focal_length": "Odak Uzaklığı", "folder": "KlasÃļr", "folder_not_found": "KlasÃļr bulunamadÄą", "folders": "KlasÃļrler", @@ -1147,7 +1236,6 @@ "free_up_space_description": "Alan açmak için yedeklenmiş fotoğraf ve videolarÄą cihazÄąnÄązÄąn çÃļp kutusuna taÅŸÄąyÄąn. Sunucudaki kopyalarÄąnÄąz gÃŧvende kalÄąr.", "free_up_space_settings_subtitle": "Cihaz depolama alanÄąnÄą boşalt", "full_path": "Tam yol: {path}", - "full_path_or_folder": "Tam yol veya klasÃļr", "gcast_enabled": "Google Cast", "gcast_enabled_description": "Bu Ãļzellik, çalÄąÅŸabilmek için Google'dan harici kaynaklar yÃŧkler.", "general": "Genel", @@ -1168,6 +1256,7 @@ "group_owner": "Sahibe gÃļre gruplandÄąr", "group_places_by": "Yerleri gruplandÄąr...", "group_year": "YÄąla gÃļre grupla", + "haptic_feedback_switch": "Dokunsal geri bildirimi aç", "haptic_feedback_title": "Dokunsal Geri Bildirim (Haptic Feedback)", "has_quota": "KotasÄą var", "hash_asset": "Karma Ãļğe", @@ -1188,12 +1277,29 @@ "hide_schema": "ŞemayÄą gizle", "hide_text_recognition": "Metin tanÄąmayÄą gizle", "hide_unnamed_people": "İsimsiz kişileri gizle", + "home_page_add_to_album_conflicts": "{album} albÃŧmÃŧne {added} Ãļğe eklendi. {failed} Ãļğe zaten albÃŧmdeydi.", + "home_page_add_to_album_err_local": "Yerel Ãļğeler henÃŧz albÃŧmlere eklenemiyor, atlanÄąyor", + "home_page_add_to_album_success": "{album} albÃŧmÃŧne {added} Ãļğe eklendi.", + "home_page_album_err_partner": "Ortak Ãļğeler henÃŧz bir albÃŧme eklenemiyor, atlanÄąyor", + "home_page_archive_err_local": "Yerel Ãļğeler henÃŧz arşivlenemiyor, atlanÄąyor", + "home_page_archive_err_partner": "Ortak Ãļğeler henÃŧz arşivlenemiyor, atlanÄąyor", "home_page_building_timeline": "Zaman çizelgesi oluşturuluyor", + "home_page_delete_err_partner": "Ortak Ãļğeler silinemez, atlanÄąyor", + "home_page_delete_remote_err_local": "Uzaktan silme seçimindeki yerel Ãļğeler atlanÄąyor", + "home_page_favorite_err_local": "Yerel Ãļğeler henÃŧz favorilere eklenemiyor, atlanÄąyor", + "home_page_favorite_err_partner": "Ortak Ãļğeler henÃŧz favorilere eklenemiyor, atlanÄąyor", + "home_page_first_time_notice": "UygulamayÄą ilk kez kullanÄąyorsanÄąz, zaman çizelgesinin albÃŧmlerdeki fotoğraf ve videolar ile oluşturulabilmesi için lÃŧtfen yedekleme için albÃŧm seçtiğinizden emin olun", + "home_page_locked_error_local": "Yerel Ãļğeler kilitli klasÃļre taÅŸÄąnamÄąyor, atlanÄąyor", + "home_page_locked_error_partner": "Ortak Ãļğeler kilitli klasÃļre taÅŸÄąnamÄąyor, atlanÄąyor", + "home_page_share_err_local": "Yerel Ãļğeler bağlantÄą ile paylaÅŸÄąlamaz, atlanÄąyor", + "home_page_upload_err_limit": "AynÄą anda en fazla 30 Ãļğe yÃŧklenebilir, atlanabilir", "host": "Ana bilgisayar", "hour": "Saat", "hours": "Saatler", - "id": "KNu", + "id": "ID", "idle": "Boşta", + "ignore_icloud_photos": "iCloud FotoğraflarÄąnÄą Yok Say", + "ignore_icloud_photos_description": "iCloud'a yÃŧklenmiş fotoğraflar Immich sunucusuna yÃŧklenmesin", "image": "Resim", "image_alt_text_date": "{isVideo, select, true {Video} other {Fotoğraf}} {date} tarihinde çekildi", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {Fotoğraf}} {person1} ile {date} tarihinde çekildi", @@ -1205,6 +1311,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1} ve {person2} ile {date} tarihinde çekildi", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1}, {person2} ve {person3} ile {date} tarihinde çekildi", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {Fotoğraf}} {city}, {country} şehrinde {person1}, {person2} ve diğer {additionalCount, number} kişi ile {date} tarihinde çekildi", + "image_saved_successfully": "Resim kaydedildi", + "image_viewer_page_state_provider_download_started": "İndirme BaşladÄą", + "image_viewer_page_state_provider_download_success": "İndirme BaşarÄąlÄą", + "image_viewer_page_state_provider_share_error": "PaylaÅŸÄąm HatasÄą", "immich_logo": "Immich Logosu", "immich_web_interface": "Immich Web ArayÃŧzÃŧ", "import_from_json": "JSON'dan İçe Aktar", @@ -1219,7 +1329,6 @@ "individual_share": "Bireysel paylaÅŸÄąm", "individual_shares": "Kişisel paylaÅŸÄąmlar", "info": "Bilgi", - "integrity_checks": "BÃŧtÃŧnlÃŧk Kontrolleri", "interval": { "day_at_onepm": "Her gÃŧn saat 13:00'te", "hours": "{hours, plural, one {Her saat} other {Her {hours, number} saatte}}", @@ -1230,9 +1339,16 @@ "invalid_date_format": "Geçersiz tarih formatÄą", "invite_people": "Kişileri Davet Et", "invite_to_album": "AlbÃŧme davet et", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "Veri çekme {dateTime} tarihinde çalÄąÅŸtÄąrÄąldÄą", + "ios_debug_info_last_sync_at": "Son eşitleme {dateTime}", + "ios_debug_info_no_processes_queued": "Hiçbir arka plan işlemi kuyruğa alÄąnmadÄą", + "ios_debug_info_no_sync_yet": "HenÃŧz hiçbir arka plan eşitleme gÃļrevi çalÄąÅŸtÄąrÄąlmadÄą", + "ios_debug_info_processes_queued": "{count, plural, one {{count} arka plan işlemi kuyruğa alÄąndÄą} other {{count} arka plan işlemi kuyruğa alÄąndÄą}}", + "ios_debug_info_processing_ran_at": "İşleme {dateTime} tarihinde çalÄąÅŸtÄąrÄąldÄą", "items_count": "{count, plural, one {# Öğe} other {# Öğe}}", "jobs": "GÃļrevler", + "json_editor": "JSON dÃŧzenleyici", + "json_error": "JSON hatasÄą", "keep": "Koru", "keep_albums": "AlbÃŧmleri sakla", "keep_albums_count": "{count} {count, plural, one {albÃŧm} other {albÃŧm}} saklanÄąyor", @@ -1259,12 +1375,14 @@ "leave": "AyrÄąl", "leave_album": "AlbÃŧmden Ã§Äąk", "lens_model": "Mercek modeli", - "less": "Daha az", "let_others_respond": "Diğerlerinin yanÄąt vermesine izin ver", "level": "Seviye", "library": "KÃŧtÃŧphane", "library_add_folder": "KlasÃļr ekle", "library_edit_folder": "KlasÃļrÃŧ dÃŧzenle", + "library_options": "KÃŧtÃŧphane ayarlarÄą", + "library_page_device_albums": "Cihazdaki AlbÃŧmler", + "library_page_new_album": "Yeni albÃŧm", "library_page_sort_asset_count": "Öğe sayÄąsÄą", "library_page_sort_created": "Oluşturma tarihi", "library_page_sort_last_modified": "Son dÃŧzenleme", @@ -1274,14 +1392,11 @@ "light_theme": "AÃ§Äąk temaya geç", "like": "Beğen", "like_deleted": "Beğeni silindi", - "link": "Link", "link_motion_video": "Hareket videosunu bağla", "link_to_docs": "Daha fazla bilgi için belgelere bakÄąn.", "link_to_oauth": "OAuth'a bağla", "linked_oauth_account": "BağlÄą OAuth hesabÄą", "list": "Liste", - "live": "CanlÄą", - "load_more": "Daha Fazla YÃŧkle", "loading": "YÃŧkleniyor", "loading_search_results_failed": "Arama sonuçlarÄą yÃŧklenemedi", "local": "Yerel", @@ -1310,9 +1425,11 @@ "login": "Giriş yap", "login_disabled": "Giriş devre dÄąÅŸÄą bÄąrakÄąldÄą", "login_form_api_exception": "API hatasÄą. LÃŧtfen sunucu URL'sini kontrol edin ve tekrar deneyin.", + "login_form_back_button_text": "Geri", "login_form_email_hint": "mail@adresiniz.com", "login_form_endpoint_hint": "http://sunucu-ip:port", "login_form_endpoint_url": "Sunucu Uç Nokta URL", + "login_form_err_http": "LÃŧtfen http:// veya https:// olarak belirtin", "login_form_err_invalid_email": "Geçersiz E-posta", "login_form_err_invalid_url": "Geçersiz URL", "login_form_err_leading_whitespace": "Baştaki boşluk", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "Giriş yaparken hata oluştu, sunucu URL'sini, e-postayÄą ve şifreyi kontrol edin", "login_form_handshake_exception": "Sunucuda bir El SÄąkÄąÅŸma İstisnasÄą vardÄą. Kendi kendine imzalanmÄąÅŸ bir sertifika kullanÄąyorsanÄąz, ayarlar menÃŧsÃŧnden kendi kendine imzalanmÄąÅŸ sertifikalara izin verin.", "login_form_password_hint": "şifre", - "login_form_server_empty": "Sunucu URL'si girin", - "login_form_server_error": "Sunucuya bağlanÄąlamadÄą", - "login_has_been_disabled": "Giriş devre dÄąÅŸÄą bÄąrakÄąldÄą", + "login_form_save_login": "Oturum aÃ§Äąk kalsÄąn", + "login_form_server_empty": "Sunucu URL'si girin.", + "login_form_server_error": "Sunucuya bağlanÄąlamadÄą.", + "login_has_been_disabled": "Giriş devre dÄąÅŸÄą bÄąrakÄąldÄą.", "login_password_changed_error": "Şifreniz gÃŧncellenirken bir hata oluştu", "login_password_changed_success": "Şifre başarÄąyla gÃŧncellendi", "logout_all_device_confirmation": "TÃŧm cihazlarda oturum kapatmak istediğinizden emin misiniz?", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "VeritabanÄą geri yÃŧkleniyor", "maintenance_description": "Immich, bakÄąm moduna alÄąnmÄąÅŸtÄąr.", "maintenance_end": "BakÄąm modunu sonlandÄąr", + "maintenance_end_error": "BakÄąm modu sonlandÄąrÄąlamadÄą.", "maintenance_logged_in_as": "Şu anda {user} olarak oturum aÃ§ÄąlmÄąÅŸ durumda", "maintenance_restore_from_backup": "Yedekten geri yÃŧkle", "maintenance_restore_library": "KÃŧtÃŧphaneni Geri YÃŧkle", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "AyarlarÄą aç", "manage_media_access_subtitle": "Immich uygulamasÄąnÄąn medya dosyalarÄąnÄą yÃļnetmesine ve taÅŸÄąmasÄąna izin verin.", "manage_media_access_title": "Medya YÃļnetimi Erişimi", + "manage_shared_links": "PaylaÅŸÄąlan bağlantÄąlarÄą yÃļnet", "manage_sharing_with_partners": "Ortaklarla paylaÅŸÄąmÄą yÃļnet", "manage_the_app_settings": "Uygulama ayarlarÄąnÄą yÃļnet", "manage_your_account": "HesabÄąnÄązÄą yÃļnetin", @@ -1371,11 +1491,13 @@ "manage_your_devices": "CihazlarÄąnÄązÄą yÃļnetin", "manage_your_oauth_connection": "OAuth bağlantÄąnÄązÄą yÃļnetin", "map": "Harita", + "map_assets_in_bounds": "{count, plural, =0 {Bu alanda fotoğraf yok} one {# photo} other {# photos}}", "map_cannot_get_user_location": "KullanÄącÄąnÄąn konumu alÄąnamÄąyor", + "map_location_dialog_yes": "Evet", "map_location_picker_page_use_location": "Bu konumu kullan", "map_location_service_disabled_content": "Mevcut konumunuzdan Ãļğeleri gÃļrÃŧntÃŧlemek için konum hizmetinin etkinleştirilmesi gerekiyor. Şimdi etkinleştirmek istiyor musunuz?", "map_location_service_disabled_title": "Konum hizmeti devre dÄąÅŸÄą bÄąrakÄąldÄą", - "map_marker_for_image": "{city}, {country} konumunda çekilen fotoğraf için harita işareti", + "map_marker_for_images": "{city}, {country} şehrinde çekilen fotoğraflar için harita işaretleyicisi", "map_marker_with_image": "Resimli harita işaretleyicisi", "map_no_location_permission_content": "Mevcut konumunuzdan Ãļğeleri gÃļrÃŧntÃŧlemek için konum iznine ihtiyaç var. Şimdi izin vermek istiyor musunuz?", "map_no_location_permission_title": "Konum izni reddedildi", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "Son {days} gÃŧn", "map_settings_date_range_option_year": "Son yÄąl", "map_settings_date_range_option_years": "Son {years} yÄąl", + "map_settings_dialog_title": "Harita AyarlarÄą", "map_settings_include_show_archived": "Arşivdekileri dahil et", "map_settings_include_show_partners": "OrtaklarÄą Dahil Et", "map_settings_only_show_favorites": "Sadece Favorileri GÃļster", "map_settings_theme_settings": "Harita TemasÄą", + "map_zoom_to_see_photos": "FotoğraflarÄą gÃļrmek için uzaklaştÄąrÄąn", "mark_all_as_read": "TÃŧmÃŧnÃŧ okundu olarak işaretle", + "mark_as_read": "Okundu olarak işaretle", "marked_all_as_read": "TÃŧmÃŧ okundu olarak işaretlendi", "matches": "Eşleşenler", "matching_assets": "Eşleşen Öğeler", - "media_chrome": { - "auto": "Otomatik", - "captions": "AltyazÄąlar", - "captions_off": "KapalÄą", - "closed_captions": "kapalÄą altyazÄąlar", - "decode_error": "Kod çÃļzÃŧmleme hatasÄą", - "disable_captions": "AltyazÄąlarÄą devre dÄąÅŸÄą bÄąrak", - "enable_captions": "AltyazÄąlar aÃ§Äąk", - "enter_fullscreen_mode": "Tam ekran kipini aç", - "exit_fullscreen_mode": "Tam ekran kipini kapat", - "loop": "DÃļngÃŧ", - "media_error_description": "Bir medya hatasÄą nedeniyle oynatma iptal edildi. Medya dosyasÄą bozuk olabilir veya tarayÄącÄąnÄąz bu formatÄą desteklemiyor olabilir.", - "media_loading": "Medya yÃŧkleniyor", - "mute": "Sessiz", - "network_error": "Ağ hatasÄą", - "network_error_description": "Ağ hatasÄą nedeniyle medya indirme işlemi başarÄąsÄąz oldu.", - "not_supported_error": "Kaynak desteklenmiyor", - "playback_rate": "Oynatma oranÄą", - "playback_rate_current": "Mevcut oynatma hÄązÄą", - "playback_rate_value": "Oynatma oranÄą {playbackRate}", - "playback_time": "oynatma sÃŧresi", - "quality": "Kalite", - "second": "saniye", - "seconds": "saniyeler", - "time_value_of_total_time": "{currentTime} / {totalTime}", - "time_value_remaining": "{time} kaldÄą", - "unmute": "Sesini açmak", - "unsupported_error_description": "Desteklenmeyen bir hata oluştu. Sunucu veya ağ hatasÄą ya da tarayÄącÄąnÄąz bu formatÄą desteklemiyor.", - "video_not_loaded_unknown_time": "video yÃŧklenmedi, sÃŧre bilinmiyor.", - "video_player": "Video oynatÄącÄą", - "volume": "Ses" - }, "media_type": "Medya tÃŧrÃŧ", "memories": "AnÄąlar", "memories_all_caught_up": "TÃŧmÃŧ gÃļrÃŧldÃŧ", @@ -1441,33 +1534,36 @@ "merge_people_prompt": "Bu kişileri birleştirmek istiyor musunuz? Bu işlem geri alÄąnamaz.", "merge_people_successfully": "Kişiler başarÄąlÄą bir şekilde birleştirildi", "merged_people_count": "{count, plural, one {# kişi} other {# kişi}} birleştirildi", - "minFaces": "Minimum yÃŧzler", - "minFaces_description": "Bir kişinin gÃļrÃŧntÃŧlenebilmesi için minimum tanÄąnan yÃŧz sayÄąsÄą", "minimize": "KÃŧçÃŧlt", "minute": "Dakika", "minutes": "Dakika", + "mirror_horizontal": "Yatay", + "mirror_vertical": "Dikey", "missing": "Eksik", "mobile_app": "Mobil Uygulama", "mobile_app_download_onboarding_note": "Aşağıdaki seçenekleri kullanarak eşlik eden mobil uygulamayÄą indirin", "model": "Model", - "modify_date": "Değiştirme tarihi", "month": "Ay", + "monthly_title_text_date_format": "AAAA y", "more": "Daha fazla", - "motion": "Hareket", "move": "TaÅŸÄą", + "move_down": "Aşağı taÅŸÄą", "move_off_locked_folder": "Kilitli klasÃļrden taÅŸÄą", "move_to": "Şuraya taÅŸÄą", "move_to_device_trash": "Cihaz çÃļp kutusuna taÅŸÄą", "move_to_lock_folder_action_prompt": "{count} kilitli klasÃļre eklendi", "move_to_locked_folder": "Kilitli klasÃļre taÅŸÄą", "move_to_locked_folder_confirmation": "Bu fotoğraflar ve videolar tÃŧm albÃŧmlerden kaldÄąrÄąlacak ve yalnÄązca kilitli klasÃļrden gÃļrÃŧntÃŧlenebilecektir", + "move_up": "YukarÄą taÅŸÄą", + "moved_to_archive": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} arşive taÅŸÄąndÄą", + "moved_to_library": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} kitaplığa taÅŸÄąndÄą", "moved_to_trash": "ÇÃļp kutusuna taÅŸÄąndÄą", + "multiselect_grid_edit_date_time_err_read_only": "Salt okunur Ãļğelerin tarihi dÃŧzenlenemedi, atlanÄąyor", + "multiselect_grid_edit_gps_err_read_only": "Salt okunur Ãļğelerin konumu dÃŧzenlenemedi, atlanÄąyor", "mute_memories": "AnÄąlarÄą sessize al", "my_albums": "AlbÃŧmlerim", - "my_immich_description": "Mevcut sayfayÄą My Immich bağlantÄąsÄą olarak kopyala", - "my_immich_title": "Benim Immich linkim", - "name": "Ad", - "name_or_nickname": "Ad veya lakap", + "name": "İsim", + "name_or_nickname": "İsim veya takma isim", "name_required": "Ad girilmesi zorunludur", "navigate": "Gezin", "navigate_to_time": "Zamana Git", @@ -1480,11 +1576,12 @@ "never": "Asla", "new_album": "Yeni albÃŧm", "new_api_key": "Yeni API AnahtarÄą", - "new_feature": "Yeni Özellik", + "new_date_range": "Yeni tarih aralığı", "new_password": "Yeni şifre", "new_person": "Yeni kişi", "new_pin_code": "Yeni PIN kodu", "new_pin_code_subtitle": "Kilitli klasÃļre ilk kez erişiyorsunuz. Bu sayfaya gÃŧvenli erişim için bir PIN kodu oluşturun", + "new_timeline": "Yeni Zaman Çizelgesi", "new_update": "Yeni gÃŧncelleme", "new_user_created": "Yeni kullanÄącÄą oluşturuldu", "new_version_available": "YENİ SÜRÜM MEVCUT", @@ -1492,6 +1589,7 @@ "next": "Sonraki", "next_memory": "Sonraki anÄą", "no": "HayÄąr", + "no_actions_added": "HenÃŧz eklenen eylem yok", "no_albums_found": "AlbÃŧm bulunamadÄą", "no_albums_message": "Fotoğraf ve videolarÄąnÄązÄą dÃŧzenlemek için yeni bir albÃŧm oluşturun", "no_albums_with_name_yet": "HenÃŧz bu isimde bir albÃŧmÃŧnÃŧz bulunmuyor.", @@ -1502,15 +1600,18 @@ "no_cast_devices_found": "YansÄątÄąlacak cihaz bulunamadÄą", "no_checksum_local": "Sağlama toplamÄą mevcut değil - yerel varlÄąklarÄą alamÄąyor", "no_checksum_remote": "Sağlama toplamÄą mevcut değil - uzak varlÄąk alÄąnamÄąyor", + "no_configuration_needed": "YapÄąlandÄąrmaya gerek yok", "no_devices": "Yetkili cihaz yok", "no_duplicates_found": "Hiçbir kopya bulunamadÄą.", "no_exif_info_available": "Exif bilgisi mevcut değil", "no_explore_results_message": "Koleksiyonunuzu keşfetmek için daha fazla fotoğraf yÃŧkleyin.", "no_favorites_message": "En sevdiğiniz fotoğraf ve videolarÄą hÄązlÄąca bulmak için favorilere ekleyin", + "no_filters_added": "HenÃŧz filtre eklenmedi", "no_libraries_message": "Fotoğraf ve videolarÄąnÄązÄą gÃļrmek için bir harici kÃŧtÃŧphane oluşturun", "no_local_assets_found": "Bu sağlama toplamÄą ile yerel varlÄąk bulunamadÄą", + "no_location_set": "Konum ayarlanmadÄą", "no_locked_photos_message": "Kilitli klasÃļrdeki fotoğraf ve videolar gizlidir; kitaplığınÄązda gezinirken veya arama yaparken gÃļrÃŧnmezler.", - "no_name": "AdsÄąz", + "no_name": "İsim Yok", "no_notifications": "Bildirim yok", "no_people_found": "Eşleşen kişi bulunamadÄą", "no_places": "Yer yok", @@ -1518,19 +1619,14 @@ "no_results": "Sonuç bulunamadÄą", "no_results_description": "Eş anlamlÄą ya da daha genel anlamlÄą bir kelime deneyin", "no_shared_albums_message": "FotoğraflarÄą ve videolarÄą ağınÄązdaki kişilerle paylaşmak için bir albÃŧm oluşturun", - "no_steps": "HenÃŧz hiçbir adÄąm eklenmedi", + "no_uploads_in_progress": "YÃŧkleme işlemi yok", "none": "Yok", "not_allowed": "İzin verilmiyor", "not_available": "U/D", "not_in_any_album": "Hiçbir albÃŧmde değil", "not_selected": "Seçilmedi", - "not_set": "AyarlanmadÄą", "notes": "Notlar", "nothing_here_yet": "Burada henÃŧz bir şey yok", - "notification_backup_reliability": "Arka plan yedeklemelerinin gÃŧvenirliğini iyileştirmek için bildirimlere izin verin", - "notification_enabled_list_tile_content": "Immich, arka plan yedeklemesi için bildirimleri kullanÄąr. Bunu cihaz ayarlarÄąnÄązdan yÃļnetebilirsiniz.", - "notification_enabled_list_tile_open_button": "AyarlarÄą aç", - "notification_enabled_list_tile_title": "Bildirimler etkin", "notification_permission_dialog_content": "Bildirimleri etkinleştirmek için cihaz ayarlarÄąna gidin ve izin verin.", "notification_permission_list_tile_content": "Bildirimleri etkinleştirmek için izin verin.", "notification_permission_list_tile_enable_button": "Bildirimleri Etkinleştir", @@ -1542,8 +1638,6 @@ "obtainium_configurator": "Obtainium YapÄąlandÄąrÄącÄą", "obtainium_configurator_instructions": "Obtainium kullanarak Android uygulamasÄąnÄą doğrudan Immich GitHub sÃŧrÃŧmÃŧnden yÃŧkleyin ve gÃŧncelleyin. Bir API anahtarÄą oluşturun ve bir varyant seçerek Obtainium yapÄąlandÄąrma bağlantÄąnÄązÄą oluşturun", "ocr": "OCR", - "ocr_body": "Immich artÄąk fotoğraflarÄąnÄązÄąn içindeki metinleri okuyabiliyor, bÃļylece metinde yazanlara gÃļre arama yapabilirsiniz.", - "ocr_title": "Fotoğraflarda metin ara", "official_immich_resources": "Resmi Immich KaynaklarÄą", "offline": "Çevrim dÄąÅŸÄą", "offset": "Ofset", @@ -1562,8 +1656,6 @@ "open": "Aç", "open_calendar": "Takvimi aç", "open_in_browser": "TarayÄącÄąda aç", - "open_in_immich_body": "Immich'i Android'de galeri olarak ayarlayarak diğer uygulamalardan fotoğraflarÄą doğrudan açabilirsiniz.", - "open_in_immich_title": "FotoğraflarÄą Immich ile aç", "open_in_map_view": "Harita gÃļrÃŧnÃŧmÃŧnde aç", "open_in_openstreetmap": "OpenStreetMap'te Aç", "open_the_search_filters": "Arama filtrelerini aç", @@ -1572,10 +1664,10 @@ "organize_into_albums": "AlbÃŧmler halinde dÃŧzenle", "organize_into_albums_description": "Mevcut fotoğraflarÄą geçerli eşitleme ayarlarÄąnÄą kullanarak albÃŧmlere yerleştirin", "organize_your_library": "KÃŧtÃŧphanenizi dÃŧzenleyin", - "orientation": "YÃļnlendirme", "original": "orijinal", "other": "Diğer", "other_devices": "Diğer cihazlar", + "other_entities": "Diğer kuruluşlar", "other_variables": "Diğer değişkenler", "owned": "Sahip olunan", "owner": "Sahip", @@ -1583,11 +1675,14 @@ "partner": "Ortak", "partner_can_access": "{partner} erişebilir", "partner_can_access_assets": "Arşivlenenler ve Silinenler dÄąÅŸÄąndaki tÃŧm fotoğraf ve videolarÄąnÄąz", - "partner_can_access_location": "FotoğraflarÄąnÄązÄąn çekildiği konum", + "partner_can_access_location": "Fotoğraf ve videolarÄąnÄązÄąn çekildiği konum", "partner_list_user_photos": "{user} fotoğraflarÄą", + "partner_list_view_all": "TÃŧmÃŧnÃŧ gÃļr", "partner_page_empty_message": "FotoğraflarÄąnÄąz henÃŧz hiçbir ortakla paylaÅŸÄąlmadÄą.", "partner_page_no_more_users": "Eklenecek başka kullanÄącÄą yok", + "partner_page_partner_add_failed": "Ortak eklenemedi", "partner_page_select_partner": "Ortak seç", + "partner_page_shared_to_title": "PaylaÅŸÄąldÄą", "partner_page_stop_sharing_content": "{partner} artÄąk fotoğraflarÄąnÄąza erişemeyecek.", "partner_sharing": "Ortak PaylaÅŸÄąmÄą", "partners": "Ortaklar", @@ -1609,6 +1704,8 @@ "people": "Kişiler", "people_edits_count": "{count, plural, one {# kişi} other {# kişi}} dÃŧzenlendi", "people_feature_description": "Kişilere gÃļre gruplanmÄąÅŸ fotoğraflarÄą ve videolarÄą inceleyin", + "people_selected": "{count, plural, one {# kişi seçildi} other {# kişi seçildi}}", + "people_sidebar_description": "Yan panelde kişilere hÄązlÄą erişim bağlantÄąsÄą gÃļster", "permanent_deletion_warning": "KalÄącÄą silme uyarÄąsÄą", "permanent_deletion_warning_setting_description": "Öğeleri kalÄącÄą olarak silerken uyarÄą gÃļster", "permanently_delete": "KalÄącÄą olarak sil", @@ -1618,17 +1715,28 @@ "permanently_deleted_assets_count": "{count, plural, one {# Ãļğe} other {# Ãļğe}} kalÄącÄą olarak silindi", "permission": "İzin", "permission_empty": "İzniniz boş olmamalÄą", + "permission_onboarding_back": "Geri", + "permission_onboarding_continue_anyway": "Yine de devam et", + "permission_onboarding_get_started": "Haydi başlayalÄąm", + "permission_onboarding_go_to_settings": "Ayarlara git", + "permission_onboarding_permission_denied": "İzin reddedildi. Immich'i kullanmak için Ayarlar'da fotoğraf ve video izinlerini verin.", + "permission_onboarding_permission_granted": "İzin verildi! ArtÄąk hazÄąrsÄąnÄąz.", + "permission_onboarding_permission_limited": "SÄąnÄąrlÄą izin. Immich'in tÃŧm fotoğrav ve videolarÄąnÄązÄą yedeklemesine ve yÃļnetmesine izin vermek için Ayarlar'da fotoğraf ve video izinlerini verin.", + "permission_onboarding_request": "Immich'in fotoğraflarÄąnÄązÄą ve videolarÄąnÄązÄą gÃļrÃŧntÃŧleyebilmesi için izne ihtiyacÄą var.", "person": "Kişi", "person_age_months": "{months, plural, one {# aylÄąk} other {# aylÄąk}}", "person_age_year_months": "1 yÄąl, {months, plural, one {# aylÄąk} other {# aylÄąk}}", - "person_age_years": "{years, plural, one {# yÄąl} other {# yÄąl}} yaÅŸÄąnda", + "person_age_years": "{years, plural, other {# yaÅŸÄąnda}}", "person_birthdate": "{date} tarihinde doğdu", "person_hidden": "{name}{hidden, select, true { (gizli)} other {}}", "person_recognized": "TanÄąnan kişi", + "person_selected": "Seçilen kişi", "photo_shared_all_users": "FotoğraflarÄąnÄązÄą tÃŧm kullanÄącÄąlarla paylaştÄąnÄąz gibi gÃļrÃŧnÃŧyor veya paylaşacak kullanÄącÄą bulunmuyor.", "photos": "Fotoğraflar", - "photos_and_videos": "Fotoğraflar ve Videolar", + "photos_and_videos": "Fotoğraflar & Videolar", + "photos_count": "{count, plural, one {{count, number} fotoğraf} other {{count, number} fotoğraf}}", "photos_from_previous_years": "Önceki yÄąllardan fotoğraflar", + "photos_only": "Sadece Fotoğraflar", "pick_a_location": "Bir konum seçin", "pick_custom_range": "Özel aralÄąk", "pick_date_range": "Bir tarih aralığı seçin", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "KodlanmÄąÅŸ videolar yerine orijinal videolarÄąn oynatÄąlmasÄąnÄą tercih edin. Orijinal Ãļğe uyumlu değilse, doğru şekilde oynatÄąlmayabilir.", "play_transcoded_video": "KodlanmÄąÅŸ videoyu oynat", "please_auth_to_access": "Erişim için lÃŧtfen kimliğinizi doğrulayÄąn", - "plugin_method_filter_type": "SÃŧzgeç", - "plugin_method_filter_type_description": "Bu yÃļntem olaylarÄą filtreleyebilir ve koşullu olarak sonraki adÄąmlarÄąn çalÄąÅŸmasÄąnÄą engelleyebilir", "port": "Port", "preferences_settings_subtitle": "Uygulama tercihlerini dÃŧzenle", "preferences_settings_title": "Tercihler", @@ -1665,11 +1771,11 @@ "privacy": "Gizlilik", "profile": "Profil", "profile_drawer_app_logs": "GÃŧnlÃŧkler", + "profile_drawer_client_server_up_to_date": "Uygulama ve sunucu gÃŧncel", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Salt okunur mod etkinleştirildi. Ã‡Äąkmak için kullanÄącÄą avatar simgesine uzun basÄąn.", "profile_image_of_user": "{user} kullanÄącÄąsÄąnÄąn profil resmi", "profile_picture_set": "Profil resmi ayarlandÄą.", - "projection_type": "Projeksiyon tipi", "public_album": "Herkese aÃ§Äąk albÃŧm", "public_share": "Genel paylaÅŸÄąm", "purchase_account_info": "Destekçi", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "Destekçi statÃŧsÃŧ", "purchase_individual_title": "Bireysel", "purchase_input_suggestion": "Zaten bir ÃŧrÃŧn anahtarÄąnÄąz var mÄą? LÃŧtfen aşağıya girin", + "purchase_license_subtitle": "Immich'i satÄąn alarak devam eden gelişimini destekleyin", "purchase_lifetime_description": "ÖmÃŧr boyu geçerli", "purchase_option_title": "SATIN ALMA SEÇENEKLERİ", "purchase_panel_info_1": "Immich'i geliştirmek çok zaman ve çaba gerektiriyor ve mÃŧmkÃŧn olan en iyi hale getirmek için tam zamanlÄą mÃŧhendislerimiz bu konuda çalÄąÅŸÄąyor. Misyonumuz, aÃ§Äąk kaynaklÄą yazÄąlÄąm ve etik iş uygulamalarÄąnÄąn geliştiriciler için sÃŧrdÃŧrÃŧlebilir bir gelir kaynağı haline gelmesini sağlamak ve sÃļmÃŧrÃŧcÃŧ bulut hizmetlerine gerçek alternatifler sunan, gizliliğe saygÄąlÄą bir ekosistem yaratmaktÄąr.", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} yeni bir kişiye atandÄą", "reassing_hint": "Seçili Ãļğeleri mevcut bir kişiye atayÄąn", "recent": "Son", + "recent_albums": "Son kaydedilen albÃŧmler", "recent_searches": "Son aramalar", "recently_added": "Son eklenenler", - "recently_added_body": "Son zamanlarda eklediğiniz her şeye Ãļzel bir sayfadan doğrudan gÃļz atÄąn.", - "recently_added_description": "Ögelerinize Immich'e yÃŧklemeye gÃļre sÄąralanmÄąÅŸ olarak gÃļz atÄąn", "recently_added_page_title": "Son Eklenenler", - "recently_added_title": "Son eklenenler", "recently_taken": "Son çekilenler", + "recently_taken_page_title": "Son Çekilenler", "refresh": "Yenile", "refresh_encoded_videos": "KodlanmÄąÅŸ videolarÄą yenile", "refresh_faces": "YÃŧzleri yenile", "refresh_metadata": "Meta verileri yenile", "refresh_thumbnails": "KÃŧçÃŧk resimleri yenile", "refreshed": "Yenilendi", + "refreshes_every_file": "TÃŧm mevcut ve yeni dosyalarÄą tekrar yÃŧkler", "refreshing_encoded_video": "KodlanmÄąÅŸ videolar yenileniyor", "refreshing_faces": "YÃŧzler yenileniyor", "refreshing_metadata": "Meta veriler yenileniyor", @@ -1745,7 +1852,7 @@ "remove_assets_shared_link_confirmation": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} bu paylaÅŸÄąlan bağlantÄądan Ã§Äąkarmak istediğinizden emin misiniz?", "remove_assets_title": "Öğeleri Ã§Äąkar?", "remove_custom_date_range": "Özel tarih aralığınÄą kaldÄąr", - "remove_filter": "SÃŧzgeci kaldÄąr", + "remove_deleted_assets": "Silinen Öğeleri KaldÄąr", "remove_from_album": "AlbÃŧmden Ã§Äąkar", "remove_from_album_action_prompt": "{count} albÃŧmden kaldÄąrÄąldÄą", "remove_from_favorites": "Favorilerden Ã§Äąkar", @@ -1756,22 +1863,29 @@ "remove_memory": "AnÄąyÄą kaldÄąr", "remove_photo_from_memory": "Bu anÄądan fotoğrafÄą kaldÄąr", "remove_tag": "Etiketi kaldÄąr", + "remove_url": "BağlantÄąyÄą kaldÄąr", "remove_user": "KullanÄącÄąyÄą Ã§Äąkar", "removed_api_key": "API anahtarÄą {name} kaldÄąrÄąldÄą", "removed_from_archive": "Arşivden Ã§ÄąkarÄąldÄą", "removed_from_favorites": "Favorilerden kaldÄąrÄąldÄą", "removed_from_favorites_count": "{count, plural, other {#}} favorilerden Ã§ÄąkarÄąldÄą", "removed_memory": "AnÄą kaldÄąrÄąldÄą", + "removed_photo_from_memory": "Fotoğraf anÄądan kaldÄąrÄąldÄą", "removed_tagged_assets": "{count, plural, one {# Ãļğenin} other {# Ãļğelerin}} etiketleri kaldÄąrÄąldÄą", "rename": "Yeniden adlandÄąr", + "repair": "Onar", + "repair_no_results_message": "Bulunamayan ve eksik dosyalar burada listelenecektir", + "replace_with_upload": "YÃŧkleme ile değiştir", "repository": "Depo", "require_password": "Şifre gerekiyor", + "require_user_to_change_password_on_first_login": "KullanÄącÄą ilk girişte şifreyi değiştirmeli", "rescan": "Yeniden tara", "reset": "SÄąfÄąrla", "reset_password": "Şifreyi sÄąfÄąrla", "reset_people_visibility": "Kişilerin gÃļrÃŧnÃŧrlÃŧğÃŧnÃŧ sÄąfÄąrla", "reset_pin_code": "PIN kodunu sÄąfÄąrlayÄąn", "reset_pin_code_description": "PIN kodunuzu unuttuysanÄąz, sÄąfÄąrlamak için sunucu yÃļneticisiyle iletişime geçebilirsiniz", + "reset_pin_code_success": "PIN kodu başarÄąyla sÄąfÄąrlandÄą", "reset_pin_code_with_password": "PIN kodunuzu her zaman şifrenizle sÄąfÄąrlayabilirsiniz", "reset_sqlite": "SQLite VeritabanÄąnÄą SÄąfÄąrla", "reset_sqlite_clear_app_data": "Verileri temizle", @@ -1785,10 +1899,12 @@ "resolved_all_duplicates": "TÃŧm çiftler çÃļzÃŧldÃŧ", "restore": "Geri yÃŧkle", "restore_all": "TÃŧmÃŧnÃŧ geri yÃŧkle", + "restore_trash_action_prompt": "{count} çÃļp kutusundan geri yÃŧklendi", "restore_user": "KullanÄącÄąyÄą geri yÃŧkle", "restored_asset": "Öğe geri yÃŧklendi", "resume": "Devam et", "resume_paused_jobs": "SÃŧrdÃŧr {count, plural, one {# duraklatÄąlmÄąÅŸ iş} other {# duraklatÄąlmÄąÅŸ işler}}", + "retry_upload": "Yeniden yÃŧklemeyi dene", "review_duplicates": "KopyalarÄą gÃļzden geçir", "review_large_files": "BÃŧyÃŧk dosyalarÄą incele", "role": "Rol", @@ -1796,6 +1912,7 @@ "role_viewer": "GÃļrÃŧntÃŧleyici", "running": "ÇalÄąÅŸÄąyor", "save": "Kaydet", + "save_to_gallery": "Fotoğraflar'a kaydet", "saved": "Kaydedildi", "saved_api_key": "API anahtarÄą kaydedildi", "saved_profile": "Profil kaydedildi", @@ -1806,10 +1923,9 @@ "scan": "Tara", "scan_all_libraries": "TÃŧm KÃŧtÃŧphaneleri Tara", "scan_library": "KÃŧtÃŧphaneyi tara", + "scan_settings": "AyarlarÄą Tara", "scanning": "TaranÄąyor", "scanning_for_album": "AlbÃŧm için taranÄąyor...", - "screencast_mode_description": "Ekranda klavye ve fare olay gÃļstergelerini gÃļster", - "screencast_mode_title": "Ekran kaydÄą modunu aç/kapat", "search": "Ara", "search_albums": "AlbÃŧm ara", "search_by_context": "Bağlama gÃļre ara", @@ -1817,8 +1933,6 @@ "search_by_description_example": "Sapa'da yÃŧrÃŧyÃŧş gÃŧnÃŧ", "search_by_filename": "Dosya adÄąna veya uzantÄąsÄąna gÃļre ara", "search_by_filename_example": "Örn. IMG_1234.JPG veya PNG", - "search_by_full_path": "Tam dosya yolu veya klasÃļre gÃļre arama yapÄąn", - "search_by_full_path_example": "/John/Projeler/3D_BaskÄą/2026-07-01 - Projeler, 3D, BaskÄą, 2026 vb. kelimelerle arama yapabilirsiniz.", "search_by_ocr": "OCR'ye gÃļre ara", "search_by_ocr_example": "SÃŧtlÃŧ Kahve", "search_camera_lens_model": "Lens modelini ara...", @@ -1838,6 +1952,7 @@ "search_filter_location_title": "Konum seç", "search_filter_media_type": "Medya TÃŧrÃŧ", "search_filter_media_type_title": "Medya tÃŧrÃŧ seç", + "search_filter_ocr": "OCR'ye gÃļre ara", "search_filter_people_title": "Kişi seç", "search_filter_star_rating": "YÄąldÄąz PuanÄą", "search_filter_tags_title": "Etiketleri seç", @@ -1845,16 +1960,28 @@ "search_for_existing_person": "Mevcut bir kişiyi ara", "search_no_more_result": "Daha fazla sonuç yok", "search_no_people": "Kişi yok", - "search_no_people_named": "\"{name}\" adlÄą bir kişi yok", + "search_no_people_named": "\"{name}\" isimli bir kişi yok", "search_no_result": "Sonuç bulunamadÄą. FarklÄą bir arama terimi veya kombinasyon deneyin", "search_options": "Arama seçenekleri", + "search_page_categories": "Kategoriler", + "search_page_motion_photos": "CanlÄą Fotoğraflar", + "search_page_no_objects": "Hiçbir Nesne Bilgisi Mevcut Değil", + "search_page_no_places": "Konum Bilgisi BulunamadÄą", + "search_page_screenshots": "Ekran gÃļrÃŧntÃŧleri", "search_page_search_photos_videos": "Fotoğraf ve videolarda ara", + "search_page_selfies": "Selfie'ler", + "search_page_things": "Nesneler", "search_page_view_all_button": "TÃŧmÃŧ", + "search_page_your_activity": "Etkinliğiniz", + "search_page_your_map": "HaritanÄąz", "search_people": "Kişilere gÃļre ara", "search_places": "Yerleri ara", "search_rating": "Derecelendirerek arayÄąn...", + "search_result_page_new_search_hint": "Yeni Arama", "search_settings": "AyarlarÄą ara", "search_state": "Eyalet/İl ara...", + "search_suggestion_list_smart_search_hint_1": "AkÄąllÄą arama varsayÄąlan olarak etkindir, meta verileri aramak için şu sÃļzdizimini kullanÄąn ", + "search_suggestion_list_smart_search_hint_2": "m:meta-veri-aramasÄą", "search_tags": "Etiketleri ara...", "search_timezone": "Saat dilimi ara...", "search_type": "Arama tÃŧrÃŧ", @@ -1868,6 +1995,7 @@ "select_albums": "AlbÃŧmleri seç", "select_all": "TÃŧmÃŧnÃŧ seç", "select_all_duplicates": "TÃŧm çiftleri seç", + "select_all_in": "{group} içindekilerin tÃŧmÃŧnÃŧ seç", "select_avatar_color": "Avatar rengini seç", "select_count": "{count, plural, one {Seç #} other {Seç #}}", "select_cutoff_date": "Tarih sÄąnÄąrÄąnÄą seç", @@ -1875,13 +2003,14 @@ "select_featured_photo": "Öne Ã§Äąkan fotoğrafÄą seç", "select_from_computer": "Bilgisayardan seç", "select_keep_all": "Hepsini sakla", + "select_library_owner": "KÃŧtÃŧphane sahibini seç", "select_new_face": "Yeni yÃŧz seç", "select_people": "Kişi seç", "select_person": "Kişileri seç", "select_person_to_tag": "Etiketlemek için bir kişi seçin", "select_photos": "FotoğraflarÄą seç", - "select_quality": "Kaliteyi seçin", "select_trash_all": "Hepsini çÃļpe at", + "select_user_for_sharing_page_err_album": "AlbÃŧm oluşturulamadÄą", "selected": "Seçildi", "selected_count": "{count, plural, other {# seçildi}}", "selected_gps_coordinates": "Seçilen GPS KoordinatlarÄą", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "Orijinal gÃļrÃŧntÃŧyÃŧ yÃŧkle", "setting_image_viewer_preview_subtitle": "Orta çÃļzÃŧnÃŧrlÃŧklÃŧ bir gÃļrÃŧntÃŧ gÃļstermek için etkinleştirin. Orijinali doğrudan gÃļstermek veya yalnÄązca kÃŧçÃŧk resmi kullanmak için devre dÄąÅŸÄą bÄąrakÄąn.", "setting_image_viewer_preview_title": "Önizleme gÃļrÃŧntÃŧsÃŧnÃŧ yÃŧkle", + "setting_image_viewer_title": "Resimler", "setting_languages_apply": "Uygula", "setting_languages_subtitle": "Uygulama dilini değiştir", + "setting_notifications_notify_failures_grace_period": "Arka plan yedekleme hatalarÄąnÄą bildir: {duration}", + "setting_notifications_notify_hours": "{count} saat", + "setting_notifications_notify_immediately": "hemen", "setting_notifications_notify_minutes": "{count} dakika", + "setting_notifications_notify_never": "hiçbir zaman", "setting_notifications_notify_seconds": "{count} saniye", + "setting_notifications_single_progress_subtitle": "Öğe baÅŸÄąna ayrÄąntÄąlÄą yÃŧkleme ilerleme bilgisi", + "setting_notifications_single_progress_title": "Arka plan yedeklemesi ayrÄąntÄąlÄą ilerlemesini gÃļster", "setting_notifications_subtitle": "Bildirim tercihlerinizi dÃŧzenleyin", + "setting_notifications_total_progress_subtitle": "Toplam yÃŧkleme ilerlemesi (tamamlanan/toplam)", + "setting_notifications_total_progress_title": "Arka plan yedeklemesi toplam ilerlemesini gÃļster", "setting_video_viewer_auto_play_subtitle": "Videolar aÃ§Äąldığında otomatik olarak oynatmaya başla", "setting_video_viewer_auto_play_title": "VideolarÄą otomatik oynat", "setting_video_viewer_looping_title": "DÃļngÃŧ", "setting_video_viewer_original_video_subtitle": "Sunucudan video aktarÄąlÄąrken, transcode (dÃļnÃŧştÃŧrÃŧlmÃŧş) sÃŧrÃŧm mevcut olsa bile orijinal dosya oynatÄąlÄąr. Bu durum, arabelleğe alma (buffering) sorunlarÄąna yol açabilir. Videolar yerel olarak mevcutsa, bu ayardan bağımsÄąz olarak orijinal kalitede oynatÄąlÄąr.", "setting_video_viewer_original_video_title": "Orijinal videoyu zorla", "settings": "Ayarlar", + "settings_require_restart": "Bu ayarÄą uygulamak için lÃŧtfen Immich'i yeniden başlatÄąn", "settings_saved": "Ayarlar kaydedildi", "setup_pin_code": "PIN kodunu ayarlayÄąn", "share": "Paylaş", + "share_action_prompt": "PaylaÅŸÄąlan {count} Ãļğe", + "share_add_photos": "Fotoğraf ekle", + "share_assets_selected": "{count} seçili", "share_dialog_preparing": "HazÄąrlanÄąyor...", "share_link": "BağlantÄąyÄą Paylaş", - "share_original": "Orijinal (bÃŧyÃŧk) olanÄą kullanÄąn", - "share_preview": "KÃŧçÃŧk resmi kullan", - "share_quality_body": "Paylaşmadan Ãļnce gÃļrÃŧntÃŧ kalitesini seçmek için paylaş dÃŧğmesine basÄąlÄą tutun.", - "share_quality_title": "Paylaşma kalitesini seçin", "shared": "PaylaÅŸÄąlan", "shared_album_activities_input_disable": "Yoruma kapalÄą", "shared_album_activity_remove_content": "Bu etkinliği silmek istiyor musunuz?", "shared_album_activity_remove_title": "Etkinlik Sil", "shared_album_section_people_action_error": "AlbÃŧmden ayrÄąlÄąrken/kaldÄąrÄąlÄąrken hata oluştu", + "shared_album_section_people_action_leave": "KullanÄącÄąyÄą albÃŧmden kaldÄąr", + "shared_album_section_people_action_remove_user": "KullanÄącÄąyÄą albÃŧmden kaldÄąr", "shared_album_section_people_title": "KİŞİLER", "shared_by": "TarafÄąndan paylaÅŸÄąlan", "shared_by_user": "{user} tarafÄąndan paylaÅŸÄąldÄą", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} YÃŧklendi", "shared_link_app_bar_title": "PaylaÅŸÄąlan BağlantÄąlar", "shared_link_clipboard_copied_massage": "Panoya kopyalandÄą", + "shared_link_clipboard_text": "BağlantÄą: {link}\nŞifre: {password}", "shared_link_create_error": "PaylaÅŸÄąm bağlantÄąsÄą oluşturulurken hata oluştu", - "shared_link_custom_url_description": "Bu paylaÅŸÄąlan bağlantÄąya Ãļzel bir URL adÄąyla erişin", - "shared_link_custom_url_title": "Özel URL", - "shared_link_custom_url_warning": "UyarÄą: İleri eğik çizgi içeren Ãļzel bir URL adÄą beklediğiniz gibi çalÄąÅŸmayabilir.", + "shared_link_custom_url_description": "Özel bir URL ile bu paylaÅŸÄąlan bağlantÄąya erişin", "shared_link_edit_description_hint": "AÃ§Äąklama yazÄąn", "shared_link_edit_expire_after_option_day": "1 gÃŧn", "shared_link_edit_expire_after_option_days": "{count} gÃŧn", @@ -1979,11 +2118,17 @@ "shared_link_password_description": "Bu paylaÅŸÄąlan bağlantÄąya erişmek için şifre gereklidir", "shared_links": "PaylaÅŸÄąlan bağlantÄąlar", "shared_links_description": "Fotoğraf ve videolarÄą bir bağlantÄą ile paylaş", - "shared_photos_and_videos_count": "{assetCount, plural, other {# paylaÅŸÄąlan fotoğraflar ve videolar.}}", + "shared_photos_and_videos_count": "{assetCount, plural, other {# paylaÅŸÄąlan fotoğraflar & videolar.}}", "shared_with_me": "Benimle paylaÅŸÄąlanlar", "shared_with_partner": "{partner} ile paylaÅŸÄąldÄą", "sharing": "PaylaÅŸÄąm", "sharing_enter_password": "Bu sayfayÄą gÃļrebilmek için lÃŧtfen şifreyi giriniz.", + "sharing_page_album": "PaylaÅŸÄąlan albÃŧmler", + "sharing_page_description": "AğınÄązdaki kişilerle fotoğraf ve video paylaşmak için paylaÅŸÄąmlÄą albÃŧmler oluşturun.", + "sharing_page_empty_list": "LİSTEYİ BOŞALT", + "sharing_sidebar_description": "Yan panelde PaylaÅŸÄąm bağlantÄąsÄąnÄą gÃļster", + "sharing_silver_appbar_create_shared_album": "Yeni paylaÅŸÄąlan albÃŧm", + "sharing_silver_appbar_share_partner": "Ortakla paylaş", "shift_to_permanent_delete": "Öğeyi kalÄącÄą olarak silmek için ⇧ tuşuna basÄąn", "show_album_options": "AlbÃŧm ayarlarÄąnÄą gÃļster", "show_albums": "AlbÃŧmleri gÃļster", @@ -1995,9 +2140,7 @@ "show_in_timeline": "Zaman çizelgesinde gÃļster", "show_in_timeline_setting_description": "Bu kullanÄącÄąnÄąn fotoğraf ve videolarÄąnÄą zaman çizelgenizde gÃļsterin", "show_keyboard_shortcuts": "Klavye kÄąsayollarÄąnÄą gÃļster", - "show_less": "Daha az gÃļster", "show_metadata": "Meta verileri gÃļster", - "show_more_fields": "{count, plural, one {Show # alan daha gÃļster} other {Show # alan daha gÃļster}}", "show_or_hide_info": "Bilgileri gÃļster veya gizle", "show_password": "Şifreyi gÃļster", "show_person_options": "Kişi seçeneklerini gÃļster", @@ -2005,7 +2148,6 @@ "show_schema": "ŞemayÄą gÃļster", "show_search_options": "Arama seçeneklerini gÃļster", "show_shared_links": "PaylaÅŸÄąlan bağlantÄąlarÄą gÃļster", - "show_slideshow_metadata_overlay": "GÃļrsel bilgisi katmanÄąnÄą gÃļster", "show_slideshow_transition": "Slayt gÃļsterisi geçişini gÃļster", "show_supporter_badge": "Destekçi rozeti", "show_supporter_badge_description": "Destekçi rozetini gÃļster", @@ -2017,25 +2159,18 @@ "sign_out": "Oturumu Kapat", "sign_up": "Kaydol", "size": "Boyut", - "skip": "Atla", "skip_to_content": "İçeriğe atla", "skip_to_folders": "KlasÃļrlere atla", "skip_to_tags": "Etiketlere atla", "slideshow": "Slayt gÃļsterisi", - "slideshow_body": "ArkanÄąza yaslanÄąn ve fotoğraflarÄąnÄązÄąn tam ekran slayt gÃļsterisinde akÄąÅŸÄąnÄą izleyin.", - "slideshow_metadata_overlay_mode": "Yer paylaÅŸÄąmÄą içeriği", - "slideshow_metadata_overlay_mode_description_only": "Sadece aÃ§Äąklama", - "slideshow_metadata_overlay_mode_full": "Dolu", "slideshow_repeat": "Slayt gÃļsterisini tekrarla", "slideshow_repeat_description": "Slayt gÃļsterisi bittiğinde başa dÃļn", "slideshow_settings": "Slayt gÃļsterisi ayarlarÄą", - "slideshow_title": "Slayt gÃļsterisi", - "smart_album": "AkÄąllÄą albÃŧm", - "some_assets_already_have_a_location_warning": "Seçilen varlÄąklarÄąn bazÄąlarÄąnÄąn zaten bir konumu mevcut", "sort_albums_by": "AlbÃŧmleri sÄąrala...", "sort_created": "Oluşturulma tarihi", "sort_items": "Öğe sayÄąsÄą", "sort_modified": "Değişiklik tarihi", + "sort_newest": "En yeni fotoğraf", "sort_oldest": "En eski fotoğraf", "sort_people_by_similarity": "İnsanlarÄą benzerliğe gÃļre sÄąrala", "sort_recent": "En yeni fotoğraf", @@ -2044,6 +2179,7 @@ "stack": "Yığın", "stack_action_prompt": "{count} istiflenmiş", "stack_duplicates": "Çiftleri yığınla", + "stack_select_one_photo": "Yığın için ana fotoğrafÄą seç", "stack_selected_photos": "Seçili fotoğraflarÄą yığınla", "stacked_assets_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} yığınlandÄą", "stacktrace": "Yığın izi", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "BaşlangÄąÃ§ tarihi bitiş tarihinden Ãļnce olmalÄądÄąr", "state": "Eyalet/İl", "status": "Durum", - "step_delete": "AdÄąmÄą sil", - "step_delete_confirm": "Bu adÄąmÄą silmek istediğinizden emin misiniz?", - "step_details": "AdÄąm ayrÄąntÄąlarÄą", - "steps": "AdÄąmlar", "stop_casting": "YansÄątmayÄą durdur", "stop_motion_photo": "Hareketli fotoğrafÄą durdur", "stop_photo_sharing": "FotoğraflarÄąnÄązÄą paylaşmayÄą durdurmak mÄą istiyorsunuz?", @@ -2070,24 +2202,24 @@ "suggestions": "Öneriler", "sunrise_on_the_beach": "Plajda gÃŧn doğumu", "support": "Destek", - "support_and_feedback": "Destek ve Geri Bildirim", + "support_and_feedback": "Destek & Geri Bildirim", "support_third_party_description": "Immich kurulumu ÃŧçÃŧncÃŧ bir tarafça yapÄąldÄą. YaşadığınÄąz sorunlar bu paketle ilgili olabilir. LÃŧtfen Ãļncelikli olarak aşağıdaki bağlantÄąlarÄą kullanarak bu sağlayÄącÄąyla iletişime geçin.", "supporter": "Destekçi", "swap_merge_direction": "Birleştirme yÃļnÃŧnÃŧ değiştir", "sync": "Eşitle", "sync_albums": "AlbÃŧmleri eşitle", + "sync_albums_manual_subtitle": "YÃŧklenen tÃŧm videolarÄą ve fotoğraflarÄą seçilen yedekleme albÃŧmlerine eşitle", "sync_local": "Yerel Eşitleme", "sync_remote": "Uzaktan Eşitleme", "sync_status": "Eşitleme Durumu", "sync_status_subtitle": "Eşitleme sistemini gÃļrÃŧntÃŧleyin ve yÃļnetin", "sync_upload_album_setting_subtitle": "FotoğraflarÄąnÄązÄą ve videolarÄąnÄązÄą oluşturun ve Immich'te seçtiğiniz albÃŧmlere yÃŧkleyin", - "system_theme": "Sistem temasÄą", - "system_theme_command_description": "Sistem temasÄąnÄą kullan ({value})", "tag": "Etiket", "tag_assets": "Öğeleri etiketle", "tag_created": "Etiket oluşturuldu: {tag}", "tag_face": "YÃŧzÃŧ etiketle", "tag_feature_description": "Etiket temalarÄąna gÃļre gruplandÄąrÄąlmÄąÅŸ fotoğraf ve videolarÄą keşfedin", + "tag_not_found_question": "Etiket bulunamadÄą mÄą? Yeni bir etiket oluşturun.", "tag_people": "İnsanlarÄą etiketle", "tag_updated": "Etiket gÃŧncellendi: {tag}", "tagged_assets": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} etiketlendi", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "SatÄąr baÅŸÄąna Ãļğe sayÄąsÄą ({count})", "theme_setting_colorful_interface_subtitle": "Birincil rengi arka plan yÃŧzeylerine uygulayÄąn.", "theme_setting_colorful_interface_title": "Renkli arayÃŧz", + "theme_setting_image_viewer_quality_subtitle": "AyrÄąntÄąlÄą gÃļrÃŧntÃŧleyicinin kalitesini ayarla", + "theme_setting_image_viewer_quality_title": "Fotoğraf gÃļrÃŧntÃŧleyici kalite ayarÄą", "theme_setting_primary_color_subtitle": "Birincil eylemler ve vurgular için bir renk seçin.", "theme_setting_primary_color_title": "Ana renk", "theme_setting_system_primary_color_title": "Sistem rengini kullan", "theme_setting_system_theme_switch": "Otomatik (sistem ayarÄąna gÃļre)", + "theme_setting_theme_subtitle": "Uygulama temasÄą seç", + "theme_setting_three_stage_loading_subtitle": "Üç aşamalÄą yÃŧkleme, yÃŧkleme performansÄąnÄą artÄąrabilir ancak ağ yÃŧkÃŧnÃŧ Ãļnemli ÃļlçÃŧde artÄąrÄąr", + "theme_setting_three_stage_loading_title": "Üç aşamalÄą yÃŧklemeyi etkinleştir", "then": "Sonra", "they_will_be_merged_together": "Birlikte birleştirilecekler", "third_party_resources": "ÜçÃŧncÃŧ taraf kaynaklar", @@ -2131,17 +2268,23 @@ "trash_all": "Hepsini sil", "trash_count": "ÇÃļp kutusu {count, number}", "trash_delete_asset": "Öğeyi Sil/ÇÃļpe GÃļnder", + "trash_emptied": "ÇÃļp kutusu temizlendi", "trash_no_results_message": "Silinen fotoğraf ve videolar burada listelenecektir.", "trash_page_delete_all": "TÃŧmÃŧnÃŧ Sil", + "trash_page_empty_trash_dialog_content": "ÇÃļp kutusuna atÄąlmÄąÅŸ Ãļğeleri silmek istediğinize emin misiniz? Bu Ãļğeler Immich'ten kalÄącÄą olarak silinecek", "trash_page_info": "ÇÃļp kutusuna atÄąlan Ãļğeler {days} gÃŧn sonra kalÄącÄą olarak silinecektir", + "trash_page_no_assets": "ÇÃļp kutusuna atÄąlmÄąÅŸ Ãļğe yok", + "trash_page_restore_all": "TÃŧmÃŧnÃŧ Geri YÃŧkle", + "trash_page_select_assets_btn": "Öğeleri seç", + "trash_page_title": "ÇÃļp Kutusu ({count})", "trashed_items_will_be_permanently_deleted_after": "Silinen Ãļğeler {days, plural, one {# gÃŧn} other {# gÃŧn}} sonra kalÄącÄą olarak silinecek.", "trigger": "Tetikleyici", - "trigger_asset_metadata_extraction": "VarlÄąk Meta Veri Ã‡Äąkarma", - "trigger_asset_metadata_extraction_description": "Bir varlığın EXIF meta verileri Ã§ÄąkarÄąldığında tetiklenir", - "trigger_asset_uploaded": "Öğe KarÅŸÄąya YÃŧklenince", + "trigger_asset_uploaded": "Öğe KarÅŸÄąya YÃŧklendi", "trigger_asset_uploaded_description": "Yeni bir Ãļğe karÅŸÄąya yÃŧklendiğinde tetiklenir", + "trigger_description": "İş akÄąÅŸÄąnÄą başlatan bir olay", "trigger_person_recognized": "TanÄąnan Kişi", - "trigger_person_recognized_description": "Bir kişi tanÄąndığında tetiklenir", + "trigger_person_recognized_description": "Bir kişi algÄąlandığında tetiklenir", + "trigger_type": "Tetikleyici tÃŧrÃŧ", "troubleshoot": "Sorun giderme", "type": "TÃŧr", "unable_to_change_pin_code": "PIN kodu değiştirilemedi", @@ -2157,33 +2300,35 @@ "unknown": "Bilinmeyen", "unknown_country": "Bilinmeyen Ülke", "unknown_date": "Bilinmeyen tarih", - "unknown_schema": "Bilinmeyen şema", "unknown_year": "Bilinmeyen YÄąl", "unlimited": "SÄąnÄąrsÄąz", "unlink_motion_video": "Hareketli video bağlantÄąsÄąnÄą kaldÄąr", "unlink_oauth": "OAuth bağlantÄąsÄąnÄą kaldÄąr", "unlinked_oauth_account": "BağlantÄąsÄą kaldÄąrÄąlmÄąÅŸ OAuth hesabÄą", "unmute_memories": "AnÄąlarÄąn Sesini Aç", - "unnamed_album": "AdsÄąz AlbÃŧm", + "unnamed_album": "İsimsiz AlbÃŧm", "unnamed_album_delete_confirmation": "Bu albÃŧmÃŧ silmek istediğinizden emin misiniz?", - "unnamed_share": "AdsÄąz PaylaÅŸÄąm", + "unnamed_share": "İsimsiz PaylaÅŸÄąm", "unsaved_change": "Kaydedilmemiş değişiklik", "unselect_all": "TÃŧmÃŧnÃŧ seçimini kaldÄąr", "unselect_all_duplicates": "TÃŧm çiftlerin seçimini kaldÄąr", + "unselect_all_in": "{group} içindeki tÃŧm seçimleri kaldÄąr", "unstack": "YığınÄą kaldÄąr", "unstack_action_prompt": "{count} istiflenmemiş", "unstacked_assets_count": "{count, plural, one {# Ãļğenin} other {# Ãļğelerin}} yığınÄą kaldÄąrÄąldÄą", "unsupported_field_type": "Desteklenmeyen alan tÃŧrÃŧ", "unsupported_file_type": "{type} dosya tÃŧrÃŧ desteklenmediği için {file} dosyasÄą yÃŧklenemiyor.", "untagged": "Etiketlenmemiş", + "untitled_workflow": "BaşlÄąksÄąz iş akÄąÅŸÄą", "up_next": "SÄąradaki", "update_location_action_prompt": "Seçilen {count} Ãļğenin konumunu şu şekilde gÃŧncelleyin:", "updated_at": "GÃŧncellenme", "updated_password": "GÃŧncellenen şifre", "upload": "YÃŧkle", "upload_concurrency": "YÃŧkleme eşzamanlÄąlığı", - "upload_day_count": "{date}: {count, plural, one {# yÃŧkleme} other {# yÃŧkleme}}", "upload_details": "YÃŧkleme AyrÄąntÄąlarÄą", + "upload_dialog_info": "Seçili Ãļğeleri sunucuya yedeklemek istiyor musunuz?", + "upload_dialog_title": "Öğe YÃŧkle", "upload_error_with_count": "{count, plural, one {# Ãļğe} other {# Ãļğeler}} için yÃŧkleme hatasÄą", "upload_errors": "{count, plural, one {# hata} other {# hatayla}} yÃŧkleme tamamlandÄą, yeni yÃŧklenen Ãļğeleri gÃļrmek için sayfayÄą gÃŧncelleyin.", "upload_finished": "YÃŧkleme tamamlandÄą", @@ -2193,13 +2338,9 @@ "upload_status_errors": "Hatalar", "upload_status_uploaded": "YÃŧklendi", "upload_success": "YÃŧkleme başarÄąlÄą, yÃŧklenen yeni Ãļğeleri gÃļrebilmek için sayfayÄą yenileyin.", - "upload_to_album_body": "Manuel yÃŧkleme Ãļzelliğini kullanmayan kullanÄącÄąlar için, artÄąk fotoğraflarÄą yÃŧklerken bunlarÄą doğrudan bir albÃŧme ekleme seçeneğiniz var; artÄąk Ãļnce fotoğraflarÄą yÃŧkleyip daha sonra albÃŧme eklemenize gerek yok.", - "upload_to_album_title": "Doğrudan bir albÃŧme yÃŧkle", "upload_to_immich": "Immich'e YÃŧkle ({count})", "uploading": "YÃŧkleniyor", "uploading_media": "Medya yÃŧkleme", - "uploads": "YÃŧklemeler", - "uploads_count": "{count, plural, one {# yÃŧkleme} other {# yÃŧklemeler}}", "url": "URL", "usage": "KullanÄąm", "use_biometric": "Biyometri kullan", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "Tarih, saat ve sayÄąlar tarayÄącÄąnÄązÄąn yerel ayarlarÄąna gÃļre biçimlendirilsin", "use_current_connection": "Mevcut bağlantÄąyÄą kullan", "use_custom_date_range": "Bunun yerine Ãļzel tarih aralığınÄą kullan", - "use_template": "Şablonu kullan", "user": "KullanÄącÄą", "user_has_been_deleted": "Bu kullanÄącÄą silindi.", "user_id": "KullanÄącÄą ID", @@ -2217,6 +2357,7 @@ "user_privacy": "KullanÄącÄą Gizliliği", "user_purchase_settings": "SatÄąn Alma", "user_purchase_settings_description": "SatÄąn alma işlemlerini yÃļnet", + "user_role_set": "{user}, {role} olarak ayarlandÄą", "user_usage_detail": "KullanÄącÄą kullanÄąm detayÄą", "user_usage_stats": "Hesap kullanÄąm istatistikleri", "user_usage_stats_description": "Hesap kullanÄąm istatistiklerini gÃļster", @@ -2226,6 +2367,7 @@ "utilities": "YardÄąmcÄą Programlar", "validate": "Doğrula", "validate_endpoint_error": "LÃŧtfen geçerli bir URL girin", + "validation_error": "Doğrulama hatasÄą", "variables": "Değişkenler", "version": "SÃŧrÃŧm", "version_announcement_closing": "ArkadaÅŸÄąnÄąz, Alex", @@ -2235,9 +2377,9 @@ "video": "Video", "video_hover_setting": "Üzerinde durulduğunda video Ãļn izlemesi oynat", "video_hover_setting_description": "Öğe Ãŧzerinde fareyle durulduğunda video kÃŧçÃŧk resmini oynatÄąr. Bu Ãļzellik devre dÄąÅŸÄąyken, oynatma simgesine fareyle gidilerek oynatma başlatÄąlabilir.", - "video_quality": "Video kalitesi", "videos": "Videolar", "videos_count": "{count, plural, one {# video} other {# video}}", + "videos_only": "Sadece videolar", "view": "GÃļrÃŧnÃŧm", "view_album": "AlbÃŧmÃŧ gÃļrÃŧntÃŧle", "view_all": "TÃŧmÃŧnÃŧ gÃļr", @@ -2246,29 +2388,29 @@ "view_details": "AyrÄąntÄąlarÄą GÃļrÃŧntÃŧle", "view_in_timeline": "Zaman çizelgesinde gÃļrÃŧntÃŧle", "view_link": "BağlantÄąyÄą gÃļster", + "view_links": "BağlantÄąlarÄą gÃļster", "view_name": "GÃļrÃŧnÃŧm", "view_next_asset": "Sonraki Ãļğeyi gÃļrÃŧntÃŧle", "view_previous_asset": "Önceki Ãļğeyi gÃļrÃŧntÃŧle", "view_qr_code": "QR kodu gÃļrÃŧntÃŧle", "view_similar_photos": "Benzer fotoğraflarÄą gÃļrÃŧntÃŧle", "view_stack": "YığınÄą gÃļrÃŧntÃŧle", + "view_user": "KullanÄącÄąyÄą GÃļrÃŧntÃŧle", "viewer_remove_from_stack": "Yığından KaldÄąr", + "viewer_stack_use_as_main_asset": "Ana fotoğraf olarak kullan", + "viewer_unstack": "YığınÄą KaldÄąr", "visibility": "GÃļrÃŧnÃŧrlÃŧk", "visibility_changed": "GÃļrÃŧnÃŧrlÃŧk {count, plural, one {# kişi} other {# kişi}} için değiştirildi", "visual": "GÃļrsel", + "visual_builder": "GÃļrsel oluşturucu", "waiting": "Bekleniyor", "waiting_count": "Bekleyen: {count}", "warning": "UyarÄą", "week": "Hafta", "welcome": "Hoş geldiniz", "welcome_to_immich": "Immich'e hoş geldiniz", - "whats_new": "Yenilikler", - "whats_new_settings_subtitle": "Immich'te yeni neler olduğuna bak", - "whats_new_version": "Versiyon {version}", - "when": "Ne zaman", "width": "Genişlik", "wifi_name": "Wi-Fi AdÄą", - "workflow": "İş AkÄąÅŸÄą", "workflow_delete_prompt": "Bu iş akÄąÅŸÄąnÄą silmek istediğinizden emin misiniz?", "workflow_deleted": "İş akÄąÅŸÄą silindi", "workflow_description": "İş akÄąÅŸÄą aÃ§ÄąklamasÄą", @@ -2278,17 +2420,17 @@ "workflow_name": "İş akÄąÅŸÄą adÄą", "workflow_navigation_prompt": "Değişikliklerinizi kaydetmeden ayrÄąlmak istediğinizden emin misiniz?", "workflow_summary": "İş akÄąÅŸÄą Ãļzeti", - "workflow_templates": "İş akÄąÅŸÄą şablonlarÄą", "workflow_update_success": "İş akÄąÅŸÄą başarÄąyla gÃŧncellendi", + "workflow_updated": "İş akÄąÅŸÄą gÃŧncellendi", "workflows": "İş akÄąÅŸlarÄą", "workflows_help_text": "İş akÄąÅŸlarÄą, tetikleyicilere ve filtrelere dayalÄą olarak Ãļğelerinizdeki eylemleri otomatikleştirir", "wrong_pin_code": "YanlÄąÅŸ PIN kodu", - "x_of_total": "{x}/{total}", "year": "YÄąl", "years_ago": "{years, plural, one {bir yÄąl} other {# yÄąl}} Ãļnce", "yes": "Evet", "you_dont_have_any_shared_links": "Herhangi bir paylaÅŸÄąlan bağlantÄąnÄąz yok", "your_wifi_name": "Wi-Fi AdÄąnÄąz", "zero_to_clear_rating": "Öğe derecelendirmesini temizlemek için 0'a basÄąn", - "zoom_image": "GÃļrÃŧntÃŧyÃŧ yakÄąnlaştÄąr" + "zoom_image": "GÃļrÃŧntÃŧyÃŧ yakÄąnlaştÄąr", + "zoom_to_bounds": "SÄąnÄąrlara yakÄąnlaştÄąr" } diff --git a/i18n/uk.json b/i18n/uk.json index 3c5ebb7b04..c9ff3d568a 100644 --- a/i18n/uk.json +++ b/i18n/uk.json @@ -5,36 +5,44 @@ "acknowledge": "ĐŸŅ€Đ¸ĐšĐŊŅŅ‚Đ¸", "action": "Đ”Ņ–Ņ", "action_common_update": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸", - "action_description": "Đ”Ņ–Ņ— С Đ˛Ņ–Đ´Ņ„Ņ–ĐģŅŒŅ‚Ņ€ĐžĐ˛Đ°ĐŊиĐŧи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Đŧи", + "action_description": "ĐĐ°ĐąŅ–Ņ€ Đ´Ņ–Đš Đ´ĐģŅ виĐēĐžĐŊаĐŊĐŊŅ ĐŊад Đ˛Ņ–Đ´Ņ„Ņ–ĐģŅŒŅ‚Ņ€ĐžĐ˛Đ°ĐŊиĐŧи ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Đŧи", "actions": "Đ”Ņ–Ņ—", "active": "АĐēŅ‚Đ¸Đ˛ĐŊиК", "active_count": "АĐēŅ‚Đ¸Đ˛ĐŊŅ–: {count}", "activity": "АĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅŅ‚ŅŒ", + "activity_changed": "АĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅŅ‚ŅŒ {enabled, select, true {ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž} other {виĐŧĐēĐŊĐĩĐŊĐž}}", "add": "Đ”ĐžĐ´Đ°Ņ‚Đ¸", "add_a_description": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐžĐŋĐ¸Ņ", "add_a_location": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ", "add_a_name": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ–Đŧ'Ņ", "add_a_title": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐŊĐ°ĐˇĐ˛Ņƒ", "add_action": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Ņ–ŅŽ", + "add_action_description": "ĐĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą Đ´ĐžĐ´Đ°Ņ‚Đ¸ Đ´Ņ–ŅŽ", "add_assets": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "add_birthday": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´ĐĩĐŊҌ ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ", "add_endpoint": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "add_exclusion_pattern": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ŅˆĐ°ĐąĐģĐžĐŊ виĐŊŅŅ‚Đē҃", + "add_filter": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ҄ҖĐģŅŒŅ‚Ņ€", + "add_filter_description": "ĐĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą Đ´ĐžĐ´Đ°Ņ‚Đ¸ ҃ĐŧĐžĐ˛Ņƒ ҄ҖĐģŅŒŅ‚Ņ€Đ°", "add_location": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ", + "add_more_users": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛", "add_partner": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", + "add_path": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ҈ĐģŅŅ…", "add_photos": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž", - "add_step": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐēŅ€ĐžĐē", "add_tag": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ‚ĐĩĐŗ", "add_to": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ҃â€Ļ", "add_to_album": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧ҃", "add_to_album_bottom_sheet_added": "ДодаĐŊĐž Đ´Đž {album}", "add_to_album_bottom_sheet_already_exists": "ВĐļĐĩ Ņ” в {album}", "add_to_album_bottom_sheet_some_local_assets": "ДĐĩŅĐēŅ– ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊĐĩ вдаĐģĐžŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧ҃", + "add_to_album_toggle": "ПĐĩŅ€ĐĩĐŧиĐēаĐŊĐŊŅ Đ˛Đ¸ĐąĐžŅ€Ņƒ Đ´ĐģŅ {album}", "add_to_albums": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧŅ–Đ˛", "add_to_albums_count": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧŅ–Đ˛ ({count})", "add_to_bottom_bar": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž", + "add_to_shared_album": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž аĐģŅŒĐąĐžĐŧ҃", "add_upload_to_stack": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ виваĐŊŅ‚Đ°ĐļĐĩĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ Đ´Đž ҁ҂ĐĩĐē҃", "add_url": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ URL", + "add_workflow_step": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐēŅ€ĐžĐē Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ—", "added_to_archive": "ДодаĐŊĐž Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ", "added_to_favorites": "ДодаĐŊĐž Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž", "added_to_favorites_count": "{count, plural, one {ДодаĐŊĐž # ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} few {ДодаĐŊĐž # ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} many {ДодаĐŊĐž # ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} other {ДодаĐŊĐž # ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž}}", @@ -69,11 +77,10 @@ "confirm_user_pin_code_reset": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ҁĐēиĐŊŅƒŅ‚Đ¸ PIN-ĐēОд ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° {user}?", "copy_config_to_clipboard_description": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŋĐžŅ‚ĐžŅ‡ĐŊ҃ ĐēĐžĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ–ŅŽ ŅĐ¸ŅŅ‚ĐĩĐŧи ŅĐē Ой'Ņ”ĐēŅ‚ JSON ҃ ĐąŅƒŅ„ĐĩŅ€ ОйĐŧŅ–ĐŊ҃", "create_job": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ СавдаĐŊĐŊŅ", - "cron_expression": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ€ĐžĐˇĐēĐģĐ°Đ´Ņƒ", + "cron_expression": "Cron-Đ˛Đ¸Ņ€Đ°Đˇ", "cron_expression_description": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Ņ–Ņ‚ŅŒ Ņ–ĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ ҁĐēаĐŊŅƒĐ˛Đ°ĐŊĐŊŅ ҃ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ– cron. Đ”ĐžĐ´Đ°Ņ‚ĐēОва Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ: Crontab Guru", "cron_expression_presets": "ШайĐģĐžĐŊи Cron-Đ˛Đ¸Ņ€Đ°ĐˇŅ–Đ˛", "disable_login": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ Đ˛Ņ…Ņ–Đ´", - "download_csv": "ЗаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ CSV", "duplicate_detection_job_description": "ВиĐēĐžĐŊĐ°Ņ‚Đ¸ ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐĩ ĐŊĐ°Đ˛Ņ‡Đ°ĐŊĐŊŅ Đ´ĐģŅ Đ˛Đ¸ŅĐ˛ĐģĐĩĐŊĐŊŅ ŅŅ…ĐžĐļĐ¸Ņ… ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ. ĐŸĐžŅ‚Ņ€ĐĩĐąŅƒŅ” Ņ€ĐžĐˇŅƒĐŧĐŊĐžĐŗĐž ĐŋĐžŅˆŅƒĐē҃", "exclusion_pattern_description": "ШайĐģĐžĐŊи виĐŊŅŅ‚ĐēŅ–Đ˛ Đ´Đ°ŅŽŅ‚ŅŒ СĐŧĐžĐŗŅƒ Ņ–ĐŗĐŊĐžŅ€ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ„Đ°ĐšĐģи Ņ‚Đ° ĐŋаĐŋĐēи ĐŋŅ–Đ´ Ņ‡Đ°Ņ ҁĐēаĐŊŅƒĐ˛Đ°ĐŊĐŊŅ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи. ĐĻĐĩ ĐēĐžŅ€Đ¸ŅĐŊĐž Đ´ĐģŅ ĐŋаĐŋĐžĐē Ņ–Đˇ ĐŊĐĩйаĐļаĐŊиĐŧи Đ´ĐģŅ Ņ–ĐŧĐŋĐžŅ€Ņ‚Ņƒ Ņ„Đ°ĐšĐģаĐŧи, ĐŊаĐŋŅ€Đ¸ĐēĐģад Ņ„Đ°ĐšĐģаĐŧи RAW.", "export_config_as_json_description": "ЗаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐŋĐžŅ‚ĐžŅ‡ĐŊ҃ ĐēĐžĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ–ŅŽ ŅĐ¸ŅŅ‚ĐĩĐŧи ҃ Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņ– JSON", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "Đ¯ĐēŅ–ŅŅ‚ŅŒ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸ Đ˛Ņ–Đ´ 1 Đ´Đž 100. Đ’Đ¸Ņ‰Đĩ СĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ОСĐŊĐ°Ņ‡Đ°Ņ” ĐēŅ€Đ°Ņ‰Ņƒ ŅĐēŅ–ŅŅ‚ŅŒ, аĐģĐĩ ŅŅ‚Đ˛ĐžŅ€ŅŽŅ” ĐąŅ–ĐģŅŒŅˆŅ– Ņ„Đ°ĐšĐģи Ņ‚Đ° ĐŧĐžĐļĐĩ СĐŧĐĩĐŊŅˆĐ¸Ņ‚Đ¸ ŅˆĐ˛Đ¸Đ´ĐēŅ–ŅŅ‚ŅŒ Ņ€ĐžĐąĐžŅ‚Đ¸ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃.", "image_thumbnail_title": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€", "import_config_from_json_description": "ІĐŧĐŋĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐžĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ–ŅŽ ŅĐ¸ŅŅ‚ĐĩĐŧи, виваĐŊŅ‚Đ°ĐļĐ¸Đ˛ŅˆĐ¸ Ņ„Đ°ĐšĐģ ĐēĐžĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ–Ņ— JSON", - "integrity_checks_checksum_files": "ФаКĐģи ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ", - "integrity_checks_checksum_files_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ", - "integrity_checks_checksum_files_enable_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ", - "integrity_checks_checksum_files_percentage_limit": "Đ’Ņ–Đ´ŅĐžŅ‚ĐēОвиК ĐģŅ–ĐŧŅ–Ņ‚", - "integrity_checks_checksum_files_percentage_limit_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐšŅ‚Đĩ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊиК Đ˛Ņ–Đ´ŅĐžŅ‚ĐžĐē ĐŧŅ–Đļ 0.01 Ņ‚Đ° 1 Đ´ĐģŅ Ņ‚ĐžĐŗĐž ĐŊĐ°ŅĐēŅ–ĐģҌĐēи ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ ĐŋОвиĐŊĐŊа виĐēĐžĐŊŅƒĐ˛Đ°Ņ‚Đ¸ŅŅŒ ĐēĐžĐļĐŊиК Ņ–ĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ.", - "integrity_checks_checksum_files_time_limit": "Đ›Ņ–ĐŧŅ–Ņ‚ Ņ‡Đ°ŅŅƒ", - "integrity_checks_checksum_files_time_limit_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐšŅ‚Đĩ ĐŧаĐēŅĐ¸ĐŧаĐģҌĐŊ҃ Ņ‚Ņ€Đ¸Đ˛Đ°ĐģŅ–ŅŅ‚ŅŒ Đ´ĐģŅ ŅĐēĐžŅ— ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ ĐŋОвиĐŊĐŊа виĐēĐžĐŊŅƒĐ˛Đ°Ņ‚Đ¸ŅŅŒ ĐēĐžĐļĐŊиК Ņ–ĐŊŅ‚ĐĩŅ€Đ˛Đ°Đģ. (Đŧҁ)", - "integrity_checks_missing_files": "Đ’Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– Ņ„Đ°ĐšĐģи", - "integrity_checks_missing_files_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐšŅ‚Đĩ Ņ‡Đ°ŅŅ‚ĐžŅ‚Ņƒ Ņ‚Đ° ŅƒĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ айО виĐŧĐēĐŊŅ–Ņ‚ŅŒ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐŊа Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– Ņ„Đ°ĐšĐģи", - "integrity_checks_missing_files_enable_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐŊа Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– Ņ„Đ°ĐšĐģи", - "integrity_checks_settings": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēи ҆ҖĐģҖҁĐŊĐžŅŅ‚Ņ–", - "integrity_checks_settings_description": "КĐĩŅ€ŅƒĐšŅ‚Đĩ Ņ–ĐŊŅ‚ĐĩŅ€Đ˛Đ°ĐģаĐŧи ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€ĐžĐē ĐŊа ҆ҖĐģҖҁĐŊŅ–ŅŅ‚ŅŒ", - "integrity_checks_untracked_files": "НĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊŅ– Ņ„Đ°ĐšĐģи", - "integrity_checks_untracked_files_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ‡Đ°ŅŅ‚ĐžŅ‚Ņƒ Ņ‚Đ° ŅƒĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ айО виĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐŊа ĐŊĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļĐĩĐŊŅ– Ņ„Đ°ĐšĐģи", - "integrity_checks_untracked_files_enable_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐŊа ĐŊĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊŅ– Ņ„Đ°ĐšĐģи", "job_concurrency": "ĐŸĐ°Ņ€Đ°ĐģĐĩĐģҌĐŊŅ–ŅŅ‚ŅŒ {job}", "job_created": "ЗавдаĐŊĐŊŅ ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž", "job_not_concurrency_safe": "ĐĻĐĩ СавдаĐŊĐŊŅ ĐŊĐĩ Ņ” ĐąĐĩСĐŋĐĩ҇ĐŊиĐŧ Đ´ĐģŅ ОдĐŊĐžŅ‡Đ°ŅĐŊĐžĐŗĐž виĐēĐžĐŊаĐŊĐŊŅ.", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ€ĐžĐˇŅƒĐŧĐŊиК ĐŋĐžŅˆŅƒĐē", "machine_learning_smart_search_enabled_description": "Đ¯ĐēŅ‰Đž виĐŧĐēĐŊĐĩĐŊĐž, ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŊĐĩ ĐēĐžĐ´ŅƒĐ˛Đ°Ņ‚Đ¸ĐŧŅƒŅ‚ŅŒŅŅ Đ´ĐģŅ Ņ€ĐžĐˇŅƒĐŧĐŊĐžĐŗĐž ĐŋĐžŅˆŅƒĐē҃.", "machine_learning_url_description": "URL ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ĐŧĐ°ŅˆĐ¸ĐŊĐŊĐžĐŗĐž ĐŊĐ°Đ˛Ņ‡Đ°ĐŊĐŊŅ. Đ¯ĐēŅ‰Đž ĐŊадаĐŊĐž ĐąŅ–ĐģҌ҈Đĩ ОдĐŊĐžĐŗĐž URL, ҁĐĩŅ€Đ˛ĐĩŅ€Đ¸ ĐžĐŋĐ¸Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ĐŧŅƒŅ‚ŅŒŅŅ ĐŋĐž ҇ĐĩŅ€ĐˇŅ–, ĐŋĐžĐēи ОдиĐŊ С ĐŊĐ¸Ņ… ĐŊĐĩ Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–ŅŅ‚ŅŒ ҃ҁĐŋŅ–ŅˆĐŊĐž, ҃ ĐŋĐžŅ€ŅĐ´Đē҃ Đ˛Ņ–Đ´ ĐŋĐĩŅ€ŅˆĐžĐŗĐž Đ´Đž ĐžŅŅ‚Đ°ĐŊĐŊŅŒĐžĐŗĐž. ĐĄĐĩŅ€Đ˛ĐĩŅ€Đ¸, ŅĐēŅ– ĐŊĐĩ Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´Đ°ŅŽŅ‚ŅŒ, Ņ‚Đ¸ĐŧŅ‡Đ°ŅĐžĐ˛Đž Ņ–ĐŗĐŊĐžŅ€ŅƒĐ˛Đ°Ņ‚Đ¸ĐŧŅƒŅ‚ŅŒŅŅ, ĐŋĐžĐēи ĐŊĐĩ ŅŅ‚Đ°ĐŊŅƒŅ‚ŅŒ Đ´ĐžŅŅ‚ŅƒĐŋĐŊиĐŧи.", - "maintenance_backup_management": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊиĐŧи ĐēĐžĐŋŅ–ŅĐŧи", "maintenance_delete_backup": "ВидаĐģĐ¸Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ", "maintenance_delete_backup_description": "ĐĻĐĩĐš Ņ„Đ°ĐšĐģ ĐąŅƒĐ´Đĩ ĐąĐĩСĐŋĐžĐ˛ĐžŅ€ĐžŅ‚ĐŊĐž видаĐģĐĩĐŊĐž.", "maintenance_delete_error": "НĐĩ вдаĐģĐžŅŅ видаĐģĐ¸Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ.", - "maintenance_integrity_check": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đ¸Ņ‚Đ¸", - "maintenance_integrity_check_all": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đ¸Ņ‚Đ¸ Đ˛ŅĐĩ", - "maintenance_integrity_checksum_mismatch": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊа ҁ҃Đŧа ĐŊĐĩ ŅŅ…ĐžĐ´Đ¸Ņ‚ŅŒŅŅ", - "maintenance_integrity_checksum_mismatch_description": "ФаКĐģи, ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊа ҁ҃Đŧа ŅĐēĐ¸Ņ… ĐŊа Đ´Đ¸ŅĐē҃ ĐŊĐĩ ĐˇĐąŅ–ĐŗĐ°Ņ”Ņ‚ŅŒŅŅ С ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐžŅŽ ҁ҃ĐŧĐžŅŽ, СйĐĩŅ€ĐĩĐļĐĩĐŊĐžŅŽ в ĐąĐ°ĐˇŅ– даĐŊĐ¸Ņ… Immich.", - "maintenance_integrity_checksum_mismatch_job": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа ĐŊĐĩĐ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊĐžŅŅ‚Ņ– ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ", - "maintenance_integrity_checksum_mismatch_refresh_job": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐˇĐ˛Ņ–Ņ‚ ĐŋŅ€Đž ĐŊĐĩĐ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊŅ–ŅŅ‚ŅŒ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐ¸Ņ… ҁ҃Đŧ", - "maintenance_integrity_missing_file": "ФаКĐģи Ņ‰Đž Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ–", - "maintenance_integrity_missing_file_description": "ФаКĐģи, ŅĐēŅ– Immich Đ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒŅ” ҃ ŅĐ˛ĐžŅ—Đš ĐąĐ°ĐˇŅ– даĐŊĐ¸Ņ…, аĐģĐĩ ŅĐēĐ¸Ņ… ĐŊĐĩ ҖҁĐŊŅƒŅ” ҃ Ņ„Đ°ĐšĐģĐžĐ˛Ņ–Đš ŅĐ¸ŅŅ‚ĐĩĐŧŅ–.", - "maintenance_integrity_missing_file_job": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ–Ņ… Ņ„Đ°ĐšĐģŅ–Đ˛", - "maintenance_integrity_missing_file_refresh_job": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐˇĐ˛Ņ–Ņ‚ ĐŋŅ€Đž Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– Ņ„Đ°ĐšĐģи", - "maintenance_integrity_report": "Đ—Đ˛Ņ–Ņ‚ ĐŋŅ€Đž ҆ҖĐģҖҁĐŊŅ–ŅŅ‚ŅŒ", - "maintenance_integrity_untracked_file": "НĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊŅ– ФаКĐģи", - "maintenance_integrity_untracked_file_description": "ФаКĐģи в ĐŋаĐŋĐēĐ°Ņ… Immich, ĐŋŅ€Đž ŅĐēŅ– Immich ĐŊĐĩ ĐŧĐ°Ņ” ĐļОдĐŊĐ¸Ņ… СаĐŋĐ¸ŅŅ–Đ˛.", - "maintenance_integrity_untracked_file_job": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа ĐŊĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊĐ¸Ņ… Ņ„Đ°ĐšĐģŅ–Đ˛", - "maintenance_integrity_untracked_file_refresh_job": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐˇĐ˛Ņ–Ņ‚ ĐŋŅ€Đž ĐŊĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊŅ– Ņ„Đ°ĐšĐģи", "maintenance_restore_backup": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ", "maintenance_restore_backup_description": "Immich ĐąŅƒĐ´Đĩ ҁ҂ĐĩŅ€Ņ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžŅ— Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžŅ— ĐēĐžĐŋŅ–Ņ—. ПĐĩŅ€ĐĩĐ´ ĐŋŅ€ĐžĐ´ĐžĐ˛ĐļĐĩĐŊĐŊŅĐŧ ĐąŅƒĐ´Đĩ ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ.", "maintenance_restore_backup_different_version": "ĐĻŅŽ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ ĐąŅƒĐģĐž ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ Ņ–ĐŊŅˆĐžŅ— вĐĩҀҁҖҗ Immich!", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐžŅŽ ĐŋĐžŅˆŅ‚ĐžŅŽ", "notification_settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊҌ", "notification_settings_description": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊҌ, вĐēĐģŅŽŅ‡ĐŊĐž С ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐžŅŽ ĐŋĐžŅˆŅ‚ĐžŅŽ", - "oauth_allow_insecure_requests": "ДозвоĐģĐ¸Ņ‚Đ¸ ĐŊĐĩĐąĐĩСĐŋĐĩ҇ĐŊŅ– СаĐŋĐ¸Ņ‚Đ¸", - "oauth_allow_insecure_requests_description": "ĐŸĐžĐŸĐ•Đ Đ•Đ”Đ–Đ•ĐĐĐ¯: виĐŧиĐēĐ°Ņ” ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ TLS-ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ° Đ´ĐģŅ СаĐŋĐ¸Ņ‚Ņ–Đ˛ OAuth, ҇ĐĩŅ€ĐĩС Ņ‰Đž ви ĐŧĐžĐļĐĩŅ‚Đĩ ŅŅ‚Đ°Ņ‚Đ¸ Đ˛Ņ€Đ°ĐˇĐģивиĐŧи Đ´Đž Đ°Ņ‚Đ°Đē MITM.", "oauth_auto_launch": "ĐĐ˛Ņ‚ĐžĐˇĐ°Đŋ҃ҁĐē", "oauth_auto_launch_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž Ņ€ĐžĐˇĐŋĐžŅ‡Đ¸ĐŊĐ°Ņ‚Đ¸ Đ˛Ņ…Ņ–Đ´ ҇ĐĩŅ€ĐĩС OAuth ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐĩŅ€ĐĩŅ…ĐžĐ´Ņƒ ĐŊа ŅŅ‚ĐžŅ€Ņ–ĐŊĐē҃ Đ˛Ņ…ĐžĐ´Ņƒ", "oauth_auto_register": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊа Ņ€ĐĩŅ”ŅŅ‚Ņ€Đ°Ņ†Ņ–Ņ", @@ -300,11 +274,9 @@ "oauth_button_text": "ĐĸĐĩĐēҁ҂ ĐēĐŊĐžĐŋĐēи", "oauth_client_secret_description": "Обов'ŅĐˇĐēОвО Đ´ĐģŅ ĐēĐžĐŊŅ„Ņ–Đ´ĐĩĐŊŅ†Ņ–ĐšĐŊĐžĐŗĐž ĐēĐģŅ–Ņ”ĐŊŅ‚Đ° айО ŅĐēŅ‰Đž PKCE (ĐēĐģŅŽŅ‡ ĐŋŅ–Đ´Ņ‚Đ˛ĐĩŅ€Đ´ĐļĐĩĐŊĐŊŅ Đ´ĐģŅ ОйĐŧŅ–ĐŊ҃ ĐēОдОĐŧ) ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ”Ņ‚ŅŒŅŅ Đ´ĐģŅ ĐŋŅƒĐąĐģҖ҇ĐŊĐžĐŗĐž ĐēĐģŅ–Ņ”ĐŊŅ‚Đ°.", "oauth_enable_description": "Đ’Ņ…Ņ–Đ´ Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ OAuth", - "oauth_end_session_url_description": "ПĐĩŅ€ĐĩĐŊаĐŋŅ€Đ°Đ˛ĐģŅŅ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° ĐŊа Ņ†ŅŽ URI-Đ°Đ´Ņ€Đĩҁ҃ ĐŋŅ–Đ´ Ņ‡Đ°Ņ Đ˛Đ¸Ņ…ĐžĐ´Ņƒ.", "oauth_mobile_redirect_uri": "URI ĐŧĐžĐąŅ–ĐģҌĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŊаĐŋŅ€Đ°Đ˛ĐģĐĩĐŊĐŊŅ", "oauth_mobile_redirect_uri_override": "ПĐĩŅ€ĐĩвиСĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ URI ĐŧĐžĐąŅ–ĐģҌĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŊаĐŋŅ€Đ°Đ˛ĐģĐĩĐŊĐŊŅ", "oauth_mobile_redirect_uri_override_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸, ŅĐēŅ‰Đž OAuth-ĐŋŅ€ĐžĐ˛Đ°ĐšĐ´ĐĩŅ€ ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ” ĐŧĐžĐąŅ–ĐģҌĐŊиК URI, ŅĐē ''{callback}''", - "oauth_prompt_description": "ĐŸĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ prompt (ĐŊаĐŋŅ€. select_account, login, consent)", "oauth_role_claim": "ĐŅ‚Ņ€Đ¸ĐąŅƒŅ‚ Ņ€ĐžĐģŅ–", "oauth_role_claim_description": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ĐŊĐ°Đ´Đ°Đ˛Đ°Ņ‚Đ¸ ĐŋŅ€Đ°Đ˛Đ° адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ĐŊа ĐžŅĐŊĐžĐ˛Ņ– ĐŊĐ°ŅĐ˛ĐŊĐžŅŅ‚Ņ– Ņ†ŅŒĐžĐŗĐž Đ°Ņ‚Ņ€Đ¸ĐąŅƒŅ‚Ņƒ. ĐĻĐĩĐš Đ°Ņ‚Ņ€Đ¸ĐąŅƒŅ‚ ĐŧĐžĐļĐĩ ĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ СĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ‘user’ айО ‘admin’.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "ОĐŊОвĐģĐĩĐŊĐŊŅ Đ˛ŅŅ–Ņ… ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐē", "registration": "Đ ĐĩŅ”ŅŅ‚Ņ€Đ°Ņ†Ņ–Ņ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "registration_description": "ĐžŅĐēŅ–ĐģҌĐēи ви ĐŋĐĩŅ€ŅˆĐ¸Đš ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡ ҃ ŅĐ¸ŅŅ‚ĐĩĐŧŅ–, Đ˛Đ°Ņ ĐąŅƒĐ´Đĩ ĐŋŅ€Đ¸ĐˇĐŊĐ°Ņ‡ĐĩĐŊĐž адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€ĐžĐŧ Ņ– ви Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´Đ°Ņ‚Đ¸ĐŧĐĩŅ‚Đĩ Са адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚Đ¸Đ˛ĐŊŅ– СавдаĐŊĐŊŅ, а Đ´ĐžĐ´Đ°Ņ‚ĐēĐžĐ˛Đ¸Ņ… ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛ ŅŅ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ĐŧĐĩŅ‚Đĩ ви.", - "release_channel_release_candidate": "Đ ĐĩĐģŅ–Đˇ ĐēаĐŊĐ´Đ¸Đ´Đ°Ņ‚", - "release_channel_stable": "ĐĄŅ‚Đ°ĐąŅ–ĐģҌĐŊа", "remove_failed_jobs": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐŊĐĩвдаĐģŅ– СавдаĐŊĐŊŅ", "require_password_change_on_login": "Зобов'ŅĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐĩŅ€ŅˆĐžĐŗĐž Đ˛Ņ…ĐžĐ´Ņƒ", "reset_settings_to_default": "ĐĄĐēиĐŊŅƒŅ‚Đ¸ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Đ´Đž Ņ‚Đ¸ĐŋĐžĐ˛Đ¸Ņ…", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "Đ—Đ°ŅŅ‚ĐžŅĐžĐ˛ŅƒŅ”Ņ‚ŅŒŅŅ ĐģĐ¸ŅˆĐĩ Đ´Đž VAAPI Ņ– QSV. ĐŖŅŅ‚Đ°ĐŊОвĐģŅŽŅ” Đ˛ŅƒĐˇĐžĐģ DRI, ŅĐēиК виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚ŅŒŅŅ Đ´ĐģŅ аĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐžĐŗĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ.", "transcoding_preset_preset": "ĐŸŅ€ĐĩҁĐĩŅ‚ (-preset)", "transcoding_preset_preset_description": "ШвидĐēŅ–ŅŅ‚ŅŒ ŅŅ‚Đ¸ŅĐŊĐĩĐŊĐŊŅ. ĐŸĐžĐ˛Ņ–ĐģҌĐŊŅ–ŅˆŅ– ĐŋŅ€ĐĩҁĐĩŅ‚Đ¸ ŅŅ‚Đ˛ĐžŅ€ŅŽŅŽŅ‚ŅŒ ĐŧĐĩĐŊŅˆŅ– Ņ„Đ°ĐšĐģи Ņ– ĐŋŅ–Đ´Đ˛Đ¸Ņ‰ŅƒŅŽŅ‚ŅŒ ŅĐēŅ–ŅŅ‚ŅŒ Са ĐŋĐĩвĐŊĐžĐŗĐž ĐąŅ–Ņ‚Ņ€ĐĩĐšŅ‚Ņƒ. VP9 Ņ–ĐŗĐŊĐžŅ€ŅƒŅ” ŅˆĐ˛Đ¸Đ´ĐēĐžŅŅ‚Ņ– Đ˛Đ¸Ņ‰Đĩ 'faster'.", - "transcoding_realtime": "ĐĸŅ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ҃ Ņ‡Đ°ŅŅ– [ЕКСПЕРИМЕНĐĸАЛĐŦНЕ]", - "transcoding_realtime_description": "ДозвоĐģŅŅ” виĐēĐžĐŊŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩĐļиĐŧŅ– Ņ€ĐĩаĐģҌĐŊĐžĐŗĐž Ņ‡Đ°ŅŅƒ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐžŅ‚ĐžĐēĐžĐ˛ĐžŅ— ĐŋĐĩŅ€ĐĩĐ´Đ°Ņ‡Ņ– Đ˛Ņ–Đ´ĐĩĐž. ЗабĐĩСĐŋĐĩŅ‡ŅƒŅ” ĐŋĐĩŅ€ĐĩĐŧиĐēаĐŊĐŊŅ ŅĐēĐžŅŅ‚Ņ–, аĐģĐĩ ĐŧĐžĐļĐĩ ҁĐŋŅ€Đ¸Ņ‡Đ¸ĐŊŅŅ‚Đ¸ ĐąŅ–ĐģҌ҈҃ ĐˇĐ°Ņ‚Ņ€Đ¸ĐŧĐē҃ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ Ņ‚Đ° ĐˇĐ°Ņ—ĐēаĐŊĐŊŅ СаĐģĐĩĐļĐŊĐž Đ˛Ņ–Đ´ ĐŧĐžĐļĐģĐ¸Đ˛ĐžŅŅ‚ĐĩĐš ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", - "transcoding_realtime_enabled": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŋĐĩŅ€ĐĩĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ҃ Ņ‡Đ°ŅŅ–", - "transcoding_realtime_enabled_description": "Đ¯ĐēŅ‰Đž виĐŧĐēĐŊĐĩĐŊĐž, ҁĐĩŅ€Đ˛ĐĩŅ€ ĐŊĐĩ ĐąŅƒĐ´Đĩ СаĐŋ҃ҁĐēĐ°Ņ‚Đ¸ ĐŊĐžĐ˛Ņ– ҁĐĩаĐŊŅĐ¸ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ҃ Ņ‡Đ°ŅŅ–.", - "transcoding_realtime_resolutions": "Đ ĐžĐˇĐ´Ņ–ĐģҌĐŊŅ– ĐˇĐ´Đ°Ņ‚ĐŊĐžŅŅ‚Ņ–", - "transcoding_realtime_resolutions_description": "Đ ĐžĐˇĐ´Ņ–ĐģҌĐŊŅ– ĐˇĐ´Đ°Ņ‚ĐŊĐžŅŅ‚Ņ– ĐŋŅ€ĐžĐŋĐžĐŊŅƒŅŽŅ‚ŅŒŅŅ Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ҃-Ņ‡Đ°ŅŅ–. Đ’Đ¸ŅĐžĐēŅ– Ņ€ĐžĐˇĐ´Ņ–ĐģҌĐŊŅ– ĐˇĐ´Đ°Ņ‚ĐŊĐžŅŅ‚Ņ– ĐŧĐžĐļŅƒŅ‚ŅŒ ҁĐŋŅ€Đ¸Ņ‡Đ¸ĐŊĐ¸Ņ‚Đ¸ ĐŋĐžĐŧиĐģĐēи Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ŅĐēŅ‰Đž ҁĐĩŅ€Đ˛ĐĩŅ€ ĐŊĐĩ СĐŧĐžĐļĐĩ Ņ—Ņ… ŅˆĐ˛Đ¸Đ´ĐēĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°Ņ‚Đ¸.", - "transcoding_realtime_video_codecs": "Đ’Ņ–Đ´ĐĩĐž ĐēОдĐĩĐēи", - "transcoding_realtime_video_codecs_description": "Đ’Ņ–Đ´ĐĩĐž ĐēОдĐĩĐēи ĐŋŅ€ĐžĐŋĐžĐŊŅƒŅŽŅ‚ŅŒ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ в Ņ€ĐĩаĐģҌĐŊĐžĐŧ҃ Ņ‡Đ°ŅŅ–. КĐģŅ–Ņ”ĐŊŅ‚Đ¸ ОйĐĩŅ€ŅƒŅ‚ŅŒ ĐŊаКĐēŅ€Đ°Ņ‰Ņƒ ĐžĐŋŅ†Ņ–ŅŽ ŅĐē҃ вОĐŊи ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅŽŅ‚ŅŒ ĐŊа ĐŋŅ€ĐžŅ‚ŅĐˇŅ– Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ. AV1 Ņ” ĐĩŅ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅˆĐ¸Đŧ Ņ‡Đ¸Đŧ HEVC, ŅĐēиК Ņ” ĐĩŅ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅˆĐ¸Đŧ Ņ‡Đ¸Đŧ H.264. КоĐģи виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚ŅŒŅŅ аĐŋĐ°Ņ€Đ°Ņ‚ĐŊĐĩ ĐŋŅ€Đ¸ŅĐēĐžŅ€ĐĩĐŊĐŊŅ, ĐžĐąĐ¸Ņ€Đ°ĐšŅ‚Đĩ ҂ҖĐģҌĐēи ĐēОдĐĩĐēи ŅĐēŅ– ĐŋŅ€Đ¸ŅĐēĐžŅ€ŅŽĐ˛Đ°Ņ‡ ĐŧĐžĐļĐĩ ĐēĐžĐ´ŅƒĐ˛Đ°Ņ‚Đ¸. ĐŸŅ–Đ´ Ņ‡Đ°Ņ виĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐŊĐžĐŗĐž Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ, ĐˇĐ°ŅƒĐ˛Đ°ĐļŅ‚Đĩ, Ņ‰Đž H.264 Ņ” ŅˆĐ˛Đ¸Đ´ŅˆĐ¸Đŧ Са AV1, ĐēĐžŅ‚Ņ€Đ¸Đš Ņ” ŅˆĐ˛Đ¸Đ´ŅˆĐ¸Đŧ Са HEVC.", "transcoding_reference_frames": "ОĐŋĐžŅ€ĐŊŅ– ĐēĐ°Đ´Ņ€Đ¸", "transcoding_reference_frames_description": "ĐšŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ ĐēĐ°Đ´Ņ€Ņ–Đ˛, ĐŊа ŅĐēŅ– ĐŋĐžŅĐ¸ĐģĐ°Ņ”Ņ‚ŅŒŅŅ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ŅŅ‚Đ¸ŅĐŊĐĩĐŊĐŊŅ ĐŋĐĩвĐŊĐžĐŗĐž ĐēĐ°Đ´Ņ€Ņƒ. Đ’Đ¸Ņ‰Ņ– СĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ĐŋĐžĐēŅ€Đ°Ņ‰ŅƒŅŽŅ‚ŅŒ ĐĩŅ„ĐĩĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅŅ‚ŅŒ ŅŅ‚Đ¸ŅĐŊĐĩĐŊĐŊŅ, аĐģĐĩ ĐˇĐąŅ–ĐģŅŒŅˆŅƒŅŽŅ‚ŅŒ Ņ‡Đ°Ņ ĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ. ЗĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ 0 виСĐŊĐ°Ņ‡Đ°Ņ” ҆ĐĩĐš ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž.", "transcoding_required_description": "Đ›Đ¸ŅˆĐĩ Đ˛Ņ–Đ´ĐĩĐž, Ņ‰Đž ĐŊĐĩ ĐŧĐ°ŅŽŅ‚ŅŒ ĐŋŅ€Đ¸ĐšĐŊŅŅ‚ĐŊĐžĐŗĐž Ņ„ĐžŅ€ĐŧĐ°Ņ‚Ņƒ", @@ -478,8 +440,6 @@ "user_settings_description": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛", "user_successfully_removed": "ĐšĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° {email} виĐģŅƒŅ‡ĐĩĐŊĐž.", "users_page_description": "ĐĄŅ‚ĐžŅ€Ņ–ĐŊĐēа ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", - "version_check_channel": "КаĐŊаĐģ ĐžĐŊОвĐģĐĩĐŊҌ", - "version_check_channel_description": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ ĐēаĐŊаĐģ ĐžĐŊОвĐģĐĩĐŊҌ, Đ´ĐģŅ ŅĐēĐžĐŗĐž ви Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ĐžŅ‚Ņ€Đ¸ĐŧŅƒĐ˛Đ°Ņ‚Đ¸ ĐžĐŗĐžĐģĐžŅˆĐĩĐŊĐŊŅ ĐŋŅ€Đž ĐŊĐžĐ˛Ņ– вĐĩҀҁҖҗ", "version_check_enabled_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐŊŅ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēи вĐĩҀҁҖҗ", "version_check_implications": "Đ¤ŅƒĐŊĐēŅ†Ņ–Ņ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēи вĐĩҀҁҖҗ СаĐģĐĩĐļĐ¸Ņ‚ŅŒ Đ˛Ņ–Đ´ ĐŋĐĩŅ€Ņ–ĐžĐ´Đ¸Ņ‡ĐŊĐžŅ— ĐēĐžĐŧ҃ĐŊŅ–ĐēĐ°Ņ†Ņ–Ņ— С {server}", "version_check_settings": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēа вĐĩҀҁҖҗ", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐēĐĩ҈ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ", "advanced_settings_clear_image_cache_error": "НĐĩ вдаĐģĐžŅŅ ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐēĐĩ҈ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ", "advanced_settings_clear_image_cache_success": "ĐžŅ‡Đ¸Ņ‰ĐĩĐŊĐž {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐšŅ‚Đĩ ҆ĐĩĐš Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚ Đ´ĐģŅ ҄ҖĐģŅŒŅ‚Ņ€Đ°Ņ†Ņ–Ņ— ĐŧĐĩĐ´Ņ–Đ° ĐŋŅ–Đ´ Ņ‡Đ°Ņ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ— Са аĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊиĐŧи ĐēŅ€Đ¸Ņ‚ĐĩŅ€Ņ–ŅĐŧи. ĐĄĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ ҆Đĩ, ŅĐēŅ‰Đž ҃ Đ˛Đ°Ņ виĐŊиĐēĐ°ŅŽŅ‚ŅŒ ĐŋŅ€ĐžĐąĐģĐĩĐŧи С Ņ‚Đ¸Đŧ, Ņ‰Đž ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐžĐē ĐŊĐĩ Đ˛Đ¸ŅĐ˛ĐģŅŅ” Đ˛ŅŅ– аĐģŅŒĐąĐžĐŧи.", + "advanced_settings_enable_alternate_media_filter_title": "[ЕКСПЕРИМЕНĐĸАЛĐŦНО] АĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛ĐŊиК ҄ҖĐģŅŒŅ‚Ņ€ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ— аĐģŅŒĐąĐžĐŧŅ–Đ˛ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "advanced_settings_log_level_title": "Đ Ņ–Đ˛ĐĩĐŊҌ ĐļŅƒŅ€ĐŊаĐģŅŽĐ˛Đ°ĐŊĐŊŅ: {level}", "advanced_settings_prefer_remote_subtitle": "ДĐĩŅĐēŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ— Đ´ŅƒĐļĐĩ ĐŋĐžĐ˛Ņ–ĐģҌĐŊĐž СаваĐŊŅ‚Đ°ĐļŅƒŅŽŅ‚ŅŒ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸ С ĐģĐžĐēаĐģҌĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛. ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ ҆ĐĩĐš ĐŋĐ°Ņ€Đ°ĐŧĐĩ҂Ҁ, Ņ‰ĐžĐą ĐŊĐ°Ņ‚ĐžĐŧŅ–ŅŅ‚ŅŒ СаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛Ņ–Đ´Đ´Đ°ĐģĐĩĐŊŅ– ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ.", "advanced_settings_prefer_remote_title": "ĐĐ°Đ´Đ°Đ˛Đ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Đ°ĐŗŅƒ Đ˛Ņ–Đ´Đ´Đ°ĐģĐĩĐŊиĐŧ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅĐŧ", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "Đ”ĐžĐ˛Ņ–ĐģҌĐŊŅ– ĐŋŅ€ĐžĐēҁҖ-ĐˇĐ°ĐŗĐžĐģОвĐēи [ЕКСПЕРИМЕНĐĸАЛĐŦНО]", "advanced_settings_readonly_mode_subtitle": "ĐŖĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐŊŅ Ņ€ĐĩĐļиĐŧ҃ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ, ҃ ŅĐēĐžĐŧ҃ Ņ„ĐžŅ‚Đž ĐŧĐžĐļĐŊа ĐģĐ¸ŅˆĐĩ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‚Đ¸, а Ņ‚Đ°ĐēŅ– Ņ„ŅƒĐŊĐē҆Җҗ, ŅĐē Đ˛Đ¸ĐąŅ–Ņ€ ĐēŅ–ĐģҌĐēĐžŅ… ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ, ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ, Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ–Ņ, видаĐģĐĩĐŊĐŊŅ — виĐŧĐēĐŊĐĩĐŊĐž. ĐŖĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐŊŅ/виĐŧĐēĐŊĐĩĐŊĐŊŅ Ņ€ĐĩĐļиĐŧ҃ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ° ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° ĐŊа ĐŗĐžĐģОвĐŊĐžĐŧ҃ ĐĩĐēŅ€Đ°ĐŊŅ–", "advanced_settings_readonly_mode_title": "Đ ĐĩĐļиĐŧ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ", + "advanced_settings_self_signed_ssl_subtitle": "ĐŸŅ€ĐžĐŋ҃ҁĐēĐ°Ņ” ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°. ĐŸĐžŅ‚Ņ€Ņ–ĐąĐŊĐĩ Đ´ĐģŅ ŅĐ°ĐŧĐžĐŋŅ–Đ´ĐŋĐ¸ŅĐ°ĐŊĐ¸Ņ… ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Ņ–Đ˛.", + "advanced_settings_self_signed_ssl_title": "ХаĐŧĐžĐŋŅ–Đ´ĐŋĐ¸ŅĐ°ĐŊŅ– SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ¸ [ЕКСПЕРИМЕНĐĸАЛĐŦНО]", "advanced_settings_sync_remote_deletions_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž видаĐģŅŅ‚Đ¸ айО Đ˛Ņ–Đ´ĐŊОвĐģŅŽĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊа Ņ†ŅŒĐžĐŧ҃ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—, ĐēĐžĐģи Ņ†Ņ Đ´Ņ–Ņ виĐēĐžĐŊŅƒŅ”Ņ‚ŅŒŅŅ ҃ вĐĩĐą-Ņ–ĐŊŅ‚ĐĩҀ҄ĐĩĐšŅŅ–", "advanced_settings_sync_remote_deletions_title": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ Đ˛Ņ–Đ´Đ´Đ°ĐģĐĩĐŊĐ¸Ņ… видаĐģĐĩĐŊҌ [ЕКСПЕРИМЕНĐĸАЛĐŦНО]", "advanced_settings_tile_subtitle": "Đ ĐžĐˇŅˆĐ¸Ņ€ĐĩĐŊŅ– ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", @@ -516,18 +480,31 @@ "album_delete_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧ {album}?", "album_delete_confirmation_description": "Đ¯ĐēŅ‰Đž аĐģŅŒĐąĐžĐŧ Ņ” ҁĐŋŅ–ĐģҌĐŊиĐŧ, Ņ–ĐŊŅˆŅ– ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ– ĐąŅ–ĐģҌ҈Đĩ ĐŊĐĩ СĐŧĐžĐļŅƒŅ‚ŅŒ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐŊŅŒĐžĐŗĐž.", "album_deleted": "АĐģŅŒĐąĐžĐŧ видаĐģĐĩĐŊĐž", + "album_info_card_backup_album_excluded": "НЕ ВРАĐĨĐžĐ’ĐŖĐ„ĐĸĐŦĐĄĐ¯", + "album_info_card_backup_album_included": "ВРАĐĨĐžĐ’ĐŖĐ„ĐĸĐŦĐĄĐ¯", "album_info_updated": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–ŅŽ ĐŋŅ€Đž аĐģŅŒĐąĐžĐŧ ĐžĐŊОвĐģĐĩĐŊĐž", + "album_leave": "ПоĐēиĐŊŅƒŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ?", + "album_leave_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ĐŋĐžĐēиĐŊŅƒŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ {album}?", "album_name": "Назва аĐģŅŒĐąĐžĐŧ҃", "album_options": "Đ’Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ҃", "album_remove_user": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°?", "album_remove_user_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ {user}?", "album_search_not_found": "АĐģŅŒĐąĐžĐŧŅ–Đ˛, Ņ‰Đž Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´Đ°ŅŽŅ‚ŅŒ Đ˛Đ°ŅˆĐžĐŧ҃ СаĐŋĐ¸Ņ‚Ņƒ, ĐŊĐĩ СĐŊаКдĐĩĐŊĐž", + "album_selected": "АĐģŅŒĐąĐžĐŧ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž", "album_share_no_users": "ĐĄŅ…ĐžĐļĐĩ, ҆ĐĩĐš аĐģŅŒĐąĐžĐŧ вĐļĐĩ Đ´ĐžŅŅ‚ŅƒĐŋĐŊиК ŅƒŅŅ–Đŧ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°Đŧ, айО ĐŊĐĩĐŧĐ°Ņ” ĐēĐžĐŗĐž Đ´ĐžĐ´Đ°Ņ‚Đ¸.", "album_summary": "ЗвĐĩĐ´ĐĩĐŊĐŊŅ аĐģŅŒĐąĐžĐŧ҃", "album_updated": "АĐģŅŒĐąĐžĐŧ ĐžĐŊОвĐģĐĩĐŊĐž", "album_updated_setting_description": "ĐžŅ‚Ņ€Đ¸ĐŧŅƒĐ˛Đ°Ņ‚Đ¸ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐžŅŽ ĐŋĐžŅˆŅ‚ĐžŅŽ, ĐēĐžĐģи ҃ ҁĐŋŅ–ĐģҌĐŊĐžĐŧ҃ аĐģŅŒĐąĐžĐŧŅ– С'ŅĐ˛ĐģŅŅŽŅ‚ŅŒŅŅ ĐŊĐžĐ˛Ņ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", "album_upload_assets": "ВиваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐˇŅ– ŅĐ˛ĐžĐŗĐž ĐēĐžĐŧĐŋ'ŅŽŅ‚ĐĩŅ€Đ° Ņ‚Đ° Đ´ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧ҃", + "album_user_left": "Ви ĐŋĐžĐēиĐŊ҃Đģи {album}", + "album_user_removed": "ĐšĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° {user} виĐģŅƒŅ‡ĐĩĐŊĐž", + "album_viewer_appbar_delete_confirm": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ ҆ĐĩĐš аĐģŅŒĐąĐžĐŧ ĐˇŅ– ŅĐ˛ĐžĐŗĐž ОйĐģŅ–ĐēĐžĐ˛ĐžĐŗĐž СаĐŋĐ¸ŅŅƒ?", "album_viewer_appbar_share_err_delete": "НĐĩ вдаĐģĐžŅŅ видаĐģĐ¸Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧ", + "album_viewer_appbar_share_err_leave": "НĐĩ вдаĐģĐžŅŅ Đ˛Đ¸ĐšŅ‚Đ¸ С аĐģŅŒĐąĐžĐŧ҃", + "album_viewer_appbar_share_err_remove": "НĐĩ вдаĐģĐžŅŅ виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ С аĐģŅŒĐąĐžĐŧ҃", + "album_viewer_appbar_share_err_title": "НĐĩ вдаĐģĐžŅŅ СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŊĐ°ĐˇĐ˛Ņƒ аĐģŅŒĐąĐžĐŧ҃", + "album_viewer_appbar_share_leave": "Đ’Đ¸ĐšŅ‚Đ¸ С аĐģŅŒĐąĐžĐŧ҃", + "album_viewer_appbar_share_to": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ", "album_viewer_page_share_add_users": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛", "album_with_link_access": "Đ‘ŅƒĐ´ŅŒ-Ņ…Ņ‚Đž С ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅĐŧ ĐŧĐžĐļĐĩ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž Ņ‚Đ° ĐģŅŽĐ´ĐĩĐš ҃ Ņ†ŅŒĐžĐŧ҃ аĐģŅŒĐąĐžĐŧŅ–.", "albums": "АĐģŅŒĐąĐžĐŧи", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "ĐŸĐžŅ‡Đ°Ņ‚ĐēОвиК ĐŋĐžŅ€ŅĐ´ĐžĐē ŅĐžŅ€Ņ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐŊĐžĐ˛Đ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛.", "albums_feature_description": "КоĐģĐĩĐē҆Җҗ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛, ŅĐēиĐŧи ĐŧĐžĐļĐŊа Đ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ С Ņ–ĐŊŅˆĐ¸Đŧи ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°Đŧи.", "albums_on_device_count": "АĐģŅŒĐąĐžĐŧи ĐŊа ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ— ({count})", + "albums_selected": "{count, plural, one {# аĐģŅŒĐąĐžĐŧ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} few {# аĐģŅŒĐąĐžĐŧи Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} many {# аĐģŅŒĐąĐžĐŧŅ–Đ˛ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} other {# аĐģŅŒĐąĐžĐŧŅ–Đ˛ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž}}", "all": "ĐŖŅŅ–", "all_albums": "ĐŖŅŅ– аĐģŅŒĐąĐžĐŧи", "all_people": "ĐŖŅŅ– ĐģŅŽĐ´Đ¸", "all_photos": "ĐŖŅŅ– Ņ„ĐžŅ‚Đž", "all_videos": "ĐŖŅŅ– Đ˛Ņ–Đ´ĐĩĐž", "allow_dark_mode": "ĐĸĐĩĐŧĐŊа Ņ‚ĐĩĐŧа ĐžŅ„ĐžŅ€ĐŧĐģĐĩĐŊĐŊŅ", + "allow_edits": "Đ”Đ°Ņ‚Đ¸ СĐŧĐžĐŗŅƒ Ņ€ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸", "allow_public_user_to_download": "Đ”Đ°Ņ‚Đ¸ СĐŧĐžĐŗŅƒ ĐŋŅƒĐąĐģҖ҇ĐŊĐžĐŧ҃ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡ĐĩĐ˛Ņ– СаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "allow_public_user_to_upload": "Đ”Đ°Ņ‚Đ¸ СĐŧĐžĐŗŅƒ ĐŋŅƒĐąĐģҖ҇ĐŊĐžĐŧ҃ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡ĐĩĐ˛Ņ– виваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "allowed": "ДозвоĐģĐĩĐŊĐž", @@ -549,12 +528,14 @@ "always_keep": "ЗавĐļди СйĐĩŅ€Ņ–ĐŗĐ°Ņ‚Đ¸", "always_keep_photos_hint": "Đ¤ŅƒĐŊĐēŅ†Ņ–Ņ ÂĢĐ—Đ˛Ņ–ĐģҌĐŊĐ¸Ņ‚Đ¸ ĐŧҖҁ҆ĐĩÂģ СйĐĩŅ€ĐĩĐļĐĩ Đ˛ŅŅ– Ņ„ĐžŅ‚Đž ĐŊа Ņ†ŅŒĐžĐŧ҃ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—.", "always_keep_videos_hint": "Đ¤ŅƒĐŊĐēŅ†Ņ–Ņ ÂĢĐ—Đ˛Ņ–ĐģҌĐŊĐ¸Ņ‚Đ¸ ĐŧҖҁ҆ĐĩÂģ СйĐĩŅ€ĐĩĐļĐĩ Đ˛ŅŅ– Đ˛Ņ–Đ´ĐĩĐž ĐŊа Ņ†ŅŒĐžĐŧ҃ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—.", + "anti_clockwise": "ĐŸŅ€ĐžŅ‚Đ¸ ĐŗĐžĐ´Đ¸ĐŊĐŊиĐēĐžĐ˛ĐžŅ— ҁ҂ҀҖĐģĐēи", "api_key": "КĐģŅŽŅ‡ API", "api_key_description": "ĐĻĐĩ СĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ĐąŅƒĐ´Đĩ ĐŋĐžĐēаСаĐŊĐž ĐģĐ¸ŅˆĐĩ ОдиĐŊ Ņ€Đ°Đˇ. Обов'ŅĐˇĐēОвО ҁĐēĐžĐŋŅ–ŅŽĐšŅ‚Đĩ ĐšĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐ´ СаĐēŅ€Đ¸Ņ‚Ņ‚ŅĐŧ Đ˛Ņ–ĐēĐŊа.", "api_key_empty": "Назва Đ˛Đ°ŅˆĐžĐŗĐž ĐēĐģŅŽŅ‡Đ° API ĐŊĐĩ ĐŧĐžĐļĐĩ ĐąŅƒŅ‚Đ¸ ĐŋĐžŅ€ĐžĐļĐŊŅŒĐžŅŽ", "api_keys": "КĐģŅŽŅ‡Ņ– API", "app_architecture_variant": "Đ’Đ°Ņ€Ņ–Đ°ĐŊŅ‚ (ĐŅ€Ņ…Ņ–Ņ‚ĐĩĐēŅ‚ŅƒŅ€Đ°)", "app_bar_signout_dialog_content": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Đ˛Đ¸ĐšŅ‚Đ¸ С ОйĐģŅ–ĐēĐžĐ˛ĐžĐŗĐž СаĐŋĐ¸ŅŅƒ?", + "app_bar_signout_dialog_ok": "ĐĸаĐē", "app_bar_signout_dialog_title": "Đ’Đ¸ĐšŅ‚Đ¸", "app_download_links": "ĐŸĐžŅĐ¸ĐģаĐŊĐŊŅ Đ´ĐģŅ СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐēŅ–Đ˛", "app_settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", @@ -565,19 +546,27 @@ "archive": "ĐŅ€Ņ…Ņ–Đ˛ŅƒĐ˛Đ°Ņ‚Đ¸", "archive_action_prompt": "{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ дОдаĐŊĐž Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ дОдаĐŊĐž Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ дОдаĐŊĐž Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ дОдаĐŊĐž Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ}}", "archive_or_unarchive_photo": "ĐŅ€Ņ…Ņ–Đ˛ŅƒĐ˛Đ°Ņ‚Đ¸ айО виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ С Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ Ņ„ĐžŅ‚Đž", + "archive_page_no_archived_assets": "НĐĩĐŧĐ°Ņ” Đ°Ņ€Ņ…Ņ–Đ˛ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", + "archive_page_title": "ĐŅ€Ņ…Ņ–Đ˛ ({count})", "archive_size": "РОСĐŧŅ–Ņ€ Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ", "archive_size_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ€ĐžĐˇĐŧŅ–Ņ€ Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ Đ´ĐģŅ СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ (҃ GiB)", "archived": "Đ—Đ°Đ°Ņ€Ņ…Ņ–Đ˛ĐžĐ˛Đ°ĐŊĐž", "archived_count": "{count, plural, one {ĐŅ€Ņ…Ņ–Đ˛ĐžĐ˛Đ°ĐŊĐž #} few {ĐŅ€Ņ…Ņ–Đ˛ĐžĐ˛Đ°ĐŊĐž #} many {ĐŅ€Ņ…Ņ–Đ˛ĐžĐ˛Đ°ĐŊĐž #} other {ĐŅ€Ņ…Ņ–Đ˛ĐžĐ˛Đ°ĐŊĐž #}}", "are_these_the_same_person": "ĐĻĐĩ Ņ‚Đ° ŅĐ°Đŧа ĐģŅŽĐ´Đ¸ĐŊа?", "are_you_sure_to_do_this": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ҆Đĩ ĐˇŅ€ĐžĐąĐ¸Ņ‚Đ¸?", + "array_field_not_fully_supported": "ПоĐģŅ ĐŧĐ°ŅĐ¸Đ˛Ņƒ ĐŋĐžŅ‚Ņ€ĐĩĐąŅƒŅŽŅ‚ŅŒ Ņ€ŅƒŅ‡ĐŊĐžĐŗĐž Ņ€ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°ĐŊĐŊŅ JSON", + "asset_action_delete_err_read_only": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ видаĐģĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "asset_action_share_err_offline": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "asset_added_to_album": "ДодаĐŊĐž Đ´Đž аĐģŅŒĐąĐžĐŧ҃", "asset_adding_to_album": "ДодаваĐŊĐŊŅ Đ´Đž аĐģŅŒĐąĐžĐŧ҃â€Ļ", "asset_created": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž", - "asset_day_count": "{date}: {count, plural, one {# Ой'Ņ”ĐēŅ‚} other {# Ой'Ņ”ĐēŅ‚Đ°}}", "asset_description_updated": "ОĐŊОвĐģĐĩĐŊĐž ĐžĐŋĐ¸Ņ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", + "asset_filename_is_offline": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ {filename} ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊиК", + "asset_has_unassigned_faces": "Є ĐŊĐĩĐŋŅ€Đ¸ĐˇĐŊĐ°Ņ‡ĐĩĐŊŅ– ОйĐģĐ¸Ņ‡Ņ‡Ņ", "asset_hashing": "ĐĨĐĩŅˆŅƒĐ˛Đ°ĐŊĐŊŅâ€Ļ", "asset_list_group_by_sub_title": "Đ“Ņ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ Са", + "asset_list_layout_settings_dynamic_layout_title": "ДиĐŊаĐŧҖ҇ĐŊиК ĐŧаĐēĐĩŅ‚", + "asset_list_layout_settings_group_automatically": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž", "asset_list_layout_settings_group_by": "Đ“Ņ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Са", "asset_list_layout_settings_group_by_month_day": "ĐœŅ–ŅŅŅ†ŅŒ + Đ´ĐĩĐŊҌ", "asset_list_layout_sub_title": "МаĐēĐĩŅ‚", @@ -588,27 +577,36 @@ "asset_not_found_on_icloud": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊĐĩ СĐŊаКдĐĩĐŊĐž в iCloud. МоĐļĐģивО, ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊиК ҇ĐĩŅ€ĐĩС ĐŋĐžŅˆĐēОдĐļĐĩĐŊиК Ņ„Đ°ĐšĐģ, Ņ‰Đž СйĐĩŅ€Ņ–ĐŗĐ°Ņ”Ņ‚ŅŒŅŅ в iCloud", "asset_offline": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊиК", "asset_offline_description": "ĐĻĐĩĐš СОвĐŊŅ–ŅˆĐŊŅ–Đš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊĐĩ СĐŊаКдĐĩĐŊĐž ĐŊа Đ´Đ¸ŅĐē҃. Đ‘ŅƒĐ´ŅŒ ĐģĐ°ŅĐēа, СвĐĩŅ€ĐŊŅ–Ņ‚ŅŒŅŅ Đ´Đž адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° Immich Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ.", + "asset_restored_successfully": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ Đ˛Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž", "asset_skipped": "ĐŸŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "asset_skipped_in_trash": "ĐŖ ĐēĐžŅˆĐ¸Đē҃", + "asset_trashed": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ ĐŋĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа", "asset_troubleshoot": "Đ’Đ¸Ņ€Ņ–ŅˆĐĩĐŊĐŊŅ ĐŋŅ€ĐžĐąĐģĐĩĐŧ Ņ–Đˇ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Đŧи", "asset_uploaded": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", "asset_uploading": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅâ€Ļ", "asset_viewer_settings_subtitle": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‡Đ° ĐŗĐ°ĐģĐĩŅ€ĐĩŅ—", "asset_viewer_settings_title": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‡ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "assets": "ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", + "assets_added_count": "ДодаĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_added_to_album_count": "ДодаĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Đ´Đž аĐģŅŒĐąĐžĐŧ҃", - "assets_added_to_album_partial_count": "ДодаĐŊĐž {successCount} С {totalCount} {totalCount, plural, one {asset} other {assets}} Đ´Đž аĐģŅŒĐąĐžĐŧ҃", "assets_added_to_albums_count": "ДодаĐŊĐž {assetTotal, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Đ´Đž {albumTotal, plural, one {# аĐģŅŒĐąĐžĐŧ҃} few {# аĐģŅŒĐąĐžĐŧŅ–Đ˛} many {# аĐģŅŒĐąĐžĐŧŅ–Đ˛} other {# аĐģŅŒĐąĐžĐŧŅ–Đ˛}}", "assets_cannot_be_added_to_album_count": "{count, plural, one {ЕĐģĐĩĐŧĐĩĐŊŅ‚} few {ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧ҃", "assets_cannot_be_added_to_albums": "{count, plural, one {ЕĐģĐĩĐŧĐĩĐŊŅ‚} few {ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ Đ´Đž ĐļОдĐŊĐžĐŗĐž С аĐģŅŒĐąĐžĐŧŅ–Đ˛", "assets_count": "{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "assets_deleted_permanently": "НазавĐļди видаĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "assets_deleted_permanently_from_server": "ВидаĐģĐĩĐŊĐž ĐŊаСавĐļди {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Immich", + "assets_downloaded_failed": "{count, plural, one {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģ — ĐŊĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {error}} few {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģи — ĐŊĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {error}} many {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģŅ–Đ˛ — ĐŊĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {error}} other {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģŅ–Đ˛ — ĐŊĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {error}}}", + "assets_downloaded_successfully": "{count, plural, one {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģ} few {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģи} many {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģŅ–Đ˛} other {ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž # Ņ„Đ°ĐšĐģŅ–Đ˛}}", "assets_moved_to_trash_count": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Đ´Đž ĐēĐžŅˆĐ¸Đēа", "assets_permanently_deleted_count": "ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž видаĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_removed_count": "ВиĐģŅƒŅ‡ĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "assets_removed_permanently_from_device": "НазавĐļди виĐģŅƒŅ‡ĐĩĐŊĐž С Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_restore_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Đ˛Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ Đ˛ŅŅ– ŅĐ˛ĐžŅ— ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ С ĐēĐžŅˆĐ¸Đēа? ĐĻŅŽ Đ´Ņ–ŅŽ ĐŊĐĩ ĐŧĐžĐļĐŊа ҁĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸! ЗвĐĩŅ€ĐŊŅ–Ņ‚ŅŒ ŅƒĐ˛Đ°ĐŗŅƒ, Ņ‰Đž ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊĐĩ Đ˛Đ´Đ°ŅŅ‚ŅŒŅŅ Đ˛Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ Ņ‚Đ°ĐēиĐŧ Ņ‡Đ¸ĐŊĐžĐŧ.", "assets_restored_count": "Đ’Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "assets_restored_successfully": "Đ’Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_trashed": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_trashed_count": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "assets_trashed_from_server": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ Immich {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "assets_were_part_of_album_count": "{count, plural, one {ЕĐģĐĩĐŧĐĩĐŊŅ‚ ĐąŅƒĐ˛} few {ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐąŅƒĐģи} many {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐąŅƒĐģĐž} other {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐąŅƒĐģĐž}} вĐļĐĩ Ņ‡Đ°ŅŅ‚Đ¸ĐŊĐžŅŽ аĐģŅŒĐąĐžĐŧ҃", "assets_were_part_of_albums_count": "{count, plural, one {ЕĐģĐĩĐŧĐĩĐŊŅ‚ вĐļĐĩ ĐąŅƒĐ˛} few {ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ вĐļĐĩ ĐąŅƒĐģи} many {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ вĐļĐĩ ĐąŅƒĐģĐž} other {ЕĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ вĐļĐĩ ĐąŅƒĐģĐž}} Ņ‡Đ°ŅŅ‚Đ¸ĐŊĐžŅŽ аĐģŅŒĐąĐžĐŧŅ–Đ˛", "authorized_devices": "ĐĐ˛Ņ‚ĐžŅ€Đ¸ĐˇĐžĐ˛Đ°ĐŊŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", @@ -617,48 +615,87 @@ "autoplay_slideshow": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ҁĐģаКд-ŅˆĐžŅƒ", "back": "Назад", "back_close_deselect": "ПовĐĩŅ€ĐŊŅƒŅ‚Đ¸ŅŅ, СаĐēŅ€Đ¸Ņ‚Đ¸ айО ҁĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛Đ¸ĐąŅ–Ņ€", + "background_backup_running_error": "ĐĐ°Ņ€Đ°ĐˇŅ– виĐēĐžĐŊŅƒŅ”Ņ‚ŅŒŅŅ Ņ„ĐžĐŊОвĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ, ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Ņ€ĐžĐˇĐŋĐžŅ‡Đ°Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ Đ˛Ņ€ŅƒŅ‡ĐŊ҃", "background_location_permission": "Đ”ĐžĐˇĐ˛Ņ–Đģ ĐŊа виСĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ĐŧŅ–ŅŅ†Ņ ҃ Ņ„ĐžĐŊОвОĐŧ҃ Ņ€ĐĩĐļиĐŧŅ–", "background_location_permission_content": "ДĐģŅ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŧиĐēаĐŊĐŊŅ ĐŧŅ–Đļ ĐŧĐĩŅ€ĐĩĐļаĐŧи ҃ Ņ„ĐžĐŊОвОĐŧ҃ Ņ€ĐĩĐļиĐŧŅ– Immich ĐŋĐžŅ‚Ņ€ĐĩĐąŅƒŅ” *ĐŋĐžŅŅ‚Ņ–ĐšĐŊĐžĐŗĐž* Đ´ĐžŅŅ‚ŅƒĐŋ҃ Đ´Đž Ņ‚ĐžŅ‡ĐŊĐžĐŗĐž Ņ€ĐžĐˇŅ‚Đ°ŅˆŅƒĐ˛Đ°ĐŊĐŊŅ, Ņ‰ĐžĐą ĐˇŅ‡Đ¸Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŊĐ°ĐˇĐ˛Ņƒ ĐŧĐĩŅ€ĐĩĐļŅ– Wi-Fi", "background_options": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩĐļиĐŧ҃", "backup": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", + "backup_album_selection_page_albums_device": "АĐģŅŒĐąĐžĐŧи ĐŊа ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ— ({count})", "backup_album_selection_page_albums_tap": "ОдĐŊĐĩ Ņ‚ĐžŅ€ĐēаĐŊĐŊŅ — Đ´ĐžĐ´Đ°Ņ‚Đ¸, ĐŋĐžĐ´Đ˛Ņ–ĐšĐŊĐĩ — виĐģŅƒŅ‡Đ¸Ņ‚Đ¸", "backup_album_selection_page_assets_scatter": "ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŧĐžĐļŅƒŅ‚ŅŒ ĐŊаĐģĐĩĐļĐ°Ņ‚Đ¸ Đ´Đž ĐēŅ–ĐģҌĐēĐžŅ… аĐģŅŒĐąĐžĐŧŅ–Đ˛ вОдĐŊĐžŅ‡Đ°Ņ. ĐĸаĐēиĐŧ Ņ‡Đ¸ĐŊĐžĐŧ, аĐģŅŒĐąĐžĐŧи ĐŧĐžĐļĐŊа Đ˛Ņ€Đ°Ņ…ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ айО ĐŊĐĩ Đ˛Ņ€Đ°Ņ…ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋŅ–Đ´ Ņ‡Đ°Ņ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ.", "backup_album_selection_page_select_albums": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", "backup_album_selection_page_selection_info": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐĩ", + "backup_album_selection_page_total_assets": "Đ—Đ°ĐŗĐ°ĐģҌĐŊа ĐēŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ ҃ĐŊŅ–ĐēаĐģҌĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "backup_albums_sync": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐ¸Ņ… ĐēĐžĐŋŅ–Đš аĐģŅŒĐąĐžĐŧŅ–Đ˛", + "backup_all": "ĐŖŅŅ–", + "backup_background_service_backup_failed_message": "НĐĩ вдаĐģĐžŅŅ ĐˇŅ€ĐžĐąĐ¸Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛. ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŽŅŽâ€Ļ", "backup_background_service_complete_notification": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ СавĐĩŅ€ŅˆĐĩĐŊĐž", + "backup_background_service_connection_failed_message": "НĐĩ вдаĐģĐžŅŅ Св'ŅĐˇĐ°Ņ‚Đ¸ŅŅ Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ. ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŽŅŽâ€Ļ", + "backup_background_service_current_upload_notification": "ВиваĐŊŅ‚Đ°ĐļŅƒŅ”Ņ‚ŅŒŅŅ {filename}", "backup_background_service_default_notification": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€ŅŅŽ ĐŊĐ°ŅĐ˛ĐŊŅ–ŅŅ‚ŅŒ ĐŊĐžĐ˛Đ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛â€Ļ", + "backup_background_service_error_title": "Đ—ĐąŅ–Đš Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "backup_background_service_in_progress_notification": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ Đ˛Đ°ŅˆĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛â€Ļ", + "backup_background_service_upload_failure_notification": "НĐĩ вдаĐģĐžŅŅ виваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {filename}", "backup_controller_page_albums": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ аĐģŅŒĐąĐžĐŧŅ–Đ˛", + "backup_controller_page_background_app_refresh_disabled_content": "ДĐģŅ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ ŅƒĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ Ņ„ĐžĐŊОвĐĩ ĐžĐŊОвĐģĐĩĐŊĐŊŅ в ĐŧĐĩĐŊŅŽ ÂĢНаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ > Đ—Đ°ĐŗĐ°ĐģҌĐŊŅ– > ФОĐŊОвĐĩ ĐžĐŊОвĐģĐĩĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃Âģ.", + "backup_controller_page_background_app_refresh_disabled_title": "ФОĐŊОвĐĩ ĐžĐŊОвĐģĐĩĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃ виĐŧĐēĐŊĐĩĐŊĐž", + "backup_controller_page_background_app_refresh_enable_button_text": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊҌ", "backup_controller_page_background_battery_info_link": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ŅĐē", "backup_controller_page_background_battery_info_message": "ДĐģŅ ĐŊаКĐēŅ€Đ°Ņ‰ĐžĐŗĐž Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ виĐŧĐēĐŊŅ–Ņ‚ŅŒ ĐąŅƒĐ´ŅŒ-ŅĐē҃ ĐžĐŋŅ‚Đ¸ĐŧŅ–ĐˇĐ°Ņ†Ņ–ŅŽ аĐē҃Đŧ҃ĐģŅŅ‚ĐžŅ€Đ°, ŅĐēа ОйĐŧĐĩĐļŅƒŅ” Ņ„ĐžĐŊĐžĐ˛Ņƒ аĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅŅ‚ŅŒ Đ´ĐģŅ Immich.\n\nĐžŅĐēŅ–ĐģҌĐēи ҆Đĩ СаĐģĐĩĐļĐ¸Ņ‚ŅŒ Đ˛Ņ–Đ´ ĐēĐžĐŊĐēŅ€ĐĩŅ‚ĐŊĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ, СĐŊĐ°ĐšĐ´Ņ–Ņ‚ŅŒ ĐŊĐĩĐžĐąŅ…Ņ–Đ´ĐŊ҃ Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–ŅŽ ҃ Đ˛Đ¸Ņ€ĐžĐąĐŊиĐēа Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ.", "backup_controller_page_background_battery_info_ok": "ОК", "backup_controller_page_background_battery_info_title": "ОĐŋŅ‚Đ¸ĐŧŅ–ĐˇĐ°Ņ†Ņ–Ņ аĐē҃Đŧ҃ĐģŅŅ‚ĐžŅ€Đ°", + "backup_controller_page_background_charging": "Đ›Đ¸ŅˆĐĩ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐˇĐ°Ņ€ŅĐ´ĐļаĐŊĐŊŅ", + "backup_controller_page_background_configure_error": "НĐĩ вдаĐģĐžŅŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ„ĐžĐŊĐžĐ˛Ņƒ ҁĐģ҃ĐļĐąŅƒ", "backup_controller_page_background_delay": "Đ—Đ°Ņ‚Ņ€Đ¸ĐŧĐēа Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ ĐŊĐžĐ˛Đ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛: {duration}", + "backup_controller_page_background_description": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛Ņƒ ҁĐģ҃ĐļĐąŅƒ, Ņ‰ĐžĐą Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ŅŅ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅ– ĐēĐžĐŋŅ–Ņ— ĐąŅƒĐ´ŅŒ-ŅĐēĐ¸Ņ… ĐŊĐžĐ˛Đ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐąĐĩС ĐŋĐžŅ‚Ņ€Đĩйи Đ˛Ņ–Đ´ĐēŅ€Đ¸Đ˛Đ°Ņ‚Đ¸ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐžĐē", + "backup_controller_page_background_is_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Ņ„ĐžĐŊОвĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ виĐŧĐēĐŊĐĩĐŊĐž", + "backup_controller_page_background_is_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Ņ„ĐžĐŊОвĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž", + "backup_controller_page_background_turn_off": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ„ĐžĐŊĐžĐ˛Ņƒ ҁĐģ҃ĐļĐąŅƒ", + "backup_controller_page_background_turn_on": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ„ĐžĐŊĐžĐ˛Ņƒ ҁĐģ҃ĐļĐąŅƒ", + "backup_controller_page_background_wifi": "Đ›Đ¸ŅˆĐĩ ĐŊа Wi-Fi", "backup_controller_page_backup": "Đ ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "backup_controller_page_backup_selected": "Đ’Đ¸ĐąŅ€Đ°ĐŊĐž: ", "backup_controller_page_backup_sub": "Đ ĐĩСĐĩŅ€Đ˛ĐŊŅ– ĐēĐžĐŋŅ–Ņ— Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", + "backup_controller_page_created": "ĐĄŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž: {date}", + "backup_controller_page_desc_backup": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ–, Ņ‰ĐžĐą Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž виваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐŊĐžĐ˛Ņ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€ ĐŋŅ–Đ´ Ņ‡Đ°Ņ Đ˛Ņ–Đ´ĐēŅ€Đ¸Ņ‚Ņ‚Ņ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃.", "backup_controller_page_excluded": "ВиĐģŅƒŅ‡ĐĩĐŊĐž: ", + "backup_controller_page_failed": "НĐĩвдаĐģŅ– ({count})", + "backup_controller_page_filename": "Назва Ņ„Đ°ĐšĐģ҃: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ", "backup_controller_page_none_selected": "ĐŅ–Ņ‡ĐžĐŗĐž ĐŊĐĩ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž", "backup_controller_page_remainder": "ЗаĐģĐ¸ŅˆĐžĐē", "backup_controller_page_remainder_sub": "Đ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž, ŅĐēŅ– СаĐģĐ¸ŅˆĐ¸ĐģĐžŅŅ ҁĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛", "backup_controller_page_server_storage": "ĐĄŅ…ĐžĐ˛Đ¸Ņ‰Đĩ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", + "backup_controller_page_start_backup": "ĐŸĐžŅ‡Đ°Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", + "backup_controller_page_status_off": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ– виĐŧĐēĐŊĐĩĐŊĐž", + "backup_controller_page_status_on": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ– ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž", "backup_controller_page_storage_format": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐž: {used} С {total}", "backup_controller_page_to_backup": "АĐģŅŒĐąĐžĐŧи Đ´Đž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "backup_controller_page_total_sub": "ĐŖŅŅ– ҃ĐŊŅ–ĐēаĐģҌĐŊŅ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛", + "backup_controller_page_turn_off": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ–", + "backup_controller_page_turn_on": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ в аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ–", + "backup_controller_page_uploading_file_info": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž Ņ„Đ°ĐšĐģ, Ņ‰Đž виваĐŊŅ‚Đ°ĐļŅƒŅ”Ņ‚ŅŒŅŅ", + "backup_err_only_album": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ Ņ”Đ´Đ¸ĐŊиК аĐģŅŒĐąĐžĐŧ", "backup_error_sync_failed": "НĐĩ вдаĐģĐžŅŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸. НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ виĐēĐžĐŊĐ°Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐĩ ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ.", "backup_info_card_assets": "ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", + "backup_manual_cancelled": "ĐĄĐēĐ°ŅĐžĐ˛Đ°ĐŊĐž", + "backup_manual_in_progress": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ вĐļĐĩ Đ˛Ņ–Đ´ĐąŅƒĐ˛Đ°Ņ”Ņ‚ŅŒŅŅ. ĐĄĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ ĐˇĐŗĐžĐ´ĐžĐŧ", + "backup_manual_success": "Đ“ĐžŅ‚ĐžĐ˛Đž", + "backup_manual_title": "ĐĄŅ‚Đ°ĐŊ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "backup_options": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", + "backup_options_page_title": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", + "backup_setting_subtitle": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ҃ Ņ„ĐžĐŊОвОĐŧ҃ Ņ‚Đ° аĐēŅ‚Đ¸Đ˛ĐŊĐžĐŧ҃ Ņ€ĐĩĐļиĐŧŅ–", "backup_settings_subtitle": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", + "backup_upload_details_page_more_details": "ĐĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą Đ´Ņ–ĐˇĐŊĐ°Ņ‚Đ¸ŅŅ ĐąŅ–ĐģҌ҈Đĩ", "backward": "Назад", - "battery_optimization_backup_reliability": "ВиĐŧĐēĐŊĐĩĐŊĐŊŅ ĐžĐŋŅ‚Đ¸ĐŧŅ–ĐˇĐ°Ņ†Ņ–Ņ— ĐąĐ°Ņ‚Đ°Ņ€ĐĩŅ— ĐŧĐžĐļĐĩ ĐŋĐžĐēŅ€Đ°Ņ‰Đ¸Ņ‚Đ¸ ĐŊĐ°Đ´Ņ–ĐšĐŊŅ–ŅŅ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "biometric_auth_enabled": "Đ‘Ņ–ĐžĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊ҃ Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ†Ņ–ŅŽ ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž", "biometric_locked_out": "Đ”ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐąŅ–ĐžĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊĐžŅ— Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ†Ņ–Ņ— СайĐģĐžĐēОваĐŊĐž", "biometric_no_options": "Đ‘Ņ–ĐžĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊŅ– Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊŅ–", "biometric_not_available": "Đ‘Ņ–ĐžĐŧĐĩŅ‚Ņ€Đ¸Ņ‡ĐŊа Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ†Ņ–Ņ ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊа ĐŊа Ņ†ŅŒĐžĐŧ҃ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", + "birthdate_saved": "Đ”Đ°Ņ‚Ņƒ ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", "birthdate_set_description": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚ŅŒŅŅ Đ´ĐģŅ ĐžĐąŅ‡Đ¸ŅĐģĐĩĐŊĐŊŅ Đ˛Ņ–Đē҃ ҆ҖҔҗ ĐģŅŽĐ´Đ¸ĐŊи ĐŊа ĐŧĐžĐŧĐĩĐŊŅ‚ Ņ„ĐžŅ‚Đž.", "blurred_background": "РОСĐŧĐ¸Ņ‚Đ¸Đš Ņ„ĐžĐŊ", - "browse_templates": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ŅˆĐ°ĐąĐģĐžĐŊи", "bugs_and_feature_requests": "Đ—Đ˛Ņ–Ņ‚Đ¸ ĐŋŅ€Đž ĐŋĐžĐŧиĐģĐēи Ņ‚Đ° ĐŋОйаĐļаĐŊĐŊŅ", "build": "Đ—ĐąŅ–Ņ€Đēа", "build_image": "ĐžĐąŅ€Đ°Đˇ ĐˇĐąŅ–Ņ€Đēи", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ СаĐģĐ¸ŅˆĐ¸Ņ‚Đ¸ {count, plural, one {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}? ĐĻĐĩ Đ´Đ°ŅŅ‚ŅŒ СĐŧĐžĐŗŅƒ Đ˛Đ¸Ņ€Ņ–ŅˆĐ¸Ņ‚Đ¸ Đ˛ŅŅ– ĐŗŅ€ŅƒĐŋи Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ–Đ˛ ĐąĐĩС видаĐģĐĩĐŊĐŊŅ ĐąŅƒĐ´ŅŒ-Ņ‡ĐžĐŗĐž.", "bulk_trash_duplicates_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ĐŋĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đž ĐēĐžŅˆĐ¸Đēа {count, plural, one {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# Đ´ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}? ĐĻĐĩ СаĐģĐ¸ŅˆĐ¸Ņ‚ŅŒ ĐŊĐ°ĐšĐąŅ–ĐģŅŒŅˆĐ¸Đš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ҃ ĐēĐžĐļĐŊŅ–Đš ĐŗŅ€ŅƒĐŋŅ– Đš ĐŋĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚ŅŒ Đ´Đž ĐēĐžŅˆĐ¸Đēа Đ˛ŅŅ– Ņ–ĐŊŅˆŅ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸.", "buy": "ĐŸŅ€Đ¸Đ´ĐąĐ°Ņ‚Đ¸ Immich", + "cache_settings_clear_cache_button": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐēĐĩ҈", + "cache_settings_clear_cache_button_title": "ĐžŅ‡Đ¸Ņ‰Đ°Ņ” ĐēĐĩ҈ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃. ĐĻĐĩ ŅŅƒŅ‚Ņ‚Ņ”Đ˛Đž СĐŊĐ¸ĐˇĐ¸Ņ‚ŅŒ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚Đ¸Đ˛ĐŊŅ–ŅŅ‚ŅŒ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃, Đ´ĐžĐēи ĐēĐĩ҈ ĐŊĐĩ ĐąŅƒĐ´Đĩ ĐŋĐĩŅ€ĐĩĐąŅƒĐ´ĐžĐ˛Đ°ĐŊĐž.", + "cache_settings_duplicated_assets_clear_button": "ОЧИСĐĸИĐĸИ", + "cache_settings_duplicated_assets_subtitle": "Đ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž, вĐŊĐĩҁĐĩĐŊŅ– ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐēĐžĐŧ Đ´Đž ҁĐŋĐ¸ŅĐē҃ Ņ–ĐŗĐŊĐžŅ€ĐžĐ˛Đ°ĐŊĐ¸Ņ…", + "cache_settings_duplicated_assets_title": "Đ”ŅƒĐąĐģŅŒĐžĐ˛Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ({count})", + "cache_settings_statistics_album": "ĐœŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", + "cache_settings_statistics_full": "ПовĐŊĐžŅ€ĐžĐˇĐŧŅ–Ņ€ĐŊŅ– ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", + "cache_settings_statistics_shared": "ĐœŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸ ҁĐŋŅ–ĐģҌĐŊĐ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛", + "cache_settings_statistics_thumbnail": "ĐœŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸", + "cache_settings_statistics_title": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ ĐēĐĩ҈҃", + "cache_settings_subtitle": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐēĐĩŅˆŅƒĐ˛Đ°ĐŊĐŊŅĐŧ ҃ ĐŧĐžĐąŅ–ĐģҌĐŊĐžĐŧ҃ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃ Immich", + "cache_settings_tile_subtitle": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŋОвĐĩĐ´Ņ–ĐŊĐēĐžŅŽ ĐģĐžĐēаĐģҌĐŊĐžĐŗĐž ŅŅ…ĐžĐ˛Đ¸Ņ‰Đ°", + "cache_settings_tile_title": "ЛоĐēаĐģҌĐŊĐĩ ŅŅ…ĐžĐ˛Đ¸Ņ‰Đĩ", + "cache_settings_title": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐēĐĩŅˆŅƒĐ˛Đ°ĐŊĐŊŅ", "camera": "КаĐŧĐĩŅ€Đ°", "camera_brand": "ĐœĐ°Ņ€Đēа ĐēаĐŧĐĩŅ€Đ¸", "camera_model": "МодĐĩĐģҌ ĐēаĐŧĐĩŅ€Đ¸", @@ -678,10 +729,10 @@ "cannot_update_the_description": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐžĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐžĐŋĐ¸Ņ", "cast": "ĐĸŅ€Đ°ĐŊҁĐģŅŽĐ˛Đ°Ņ‚Đ¸", "cast_description": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ— Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ–Ņ—", - "change": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸", "change_date": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ", "change_description": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐžĐŋĐ¸Ņ", "change_display_order": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŋĐžŅ€ŅĐ´ĐžĐē Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", + "change_expiration_time": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ Ņ‚ĐĩŅ€ĐŧŅ–ĐŊ Đ´Ņ–Ņ—", "change_location": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŧҖҁ҆Đĩ", "change_name": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ Ņ–Đŧ'Ņ", "change_name_successfully": "ІĐŧ'Ņ СĐŧŅ–ĐŊĐĩĐŊĐž", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "ĐŸĐ°Ņ€ĐžĐģŅ– ĐŊĐĩ ĐˇĐąŅ–ĐŗĐ°ŅŽŅ‚ŅŒŅŅ", "change_password_form_reenter_new_password": "ĐŸĐžĐ˛Ņ‚ĐžŅ€Ņ–Ņ‚ŅŒ ĐŊОвиК ĐŋĐ°Ņ€ĐžĐģҌ", "change_pin_code": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ PIN-ĐēОд", + "change_trigger": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ¸ĐŗĐĩŅ€", + "change_trigger_prompt": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ¸ĐŗĐĩŅ€? ĐĻĐĩ видаĐģĐ¸Ņ‚ŅŒ ŅƒŅŅ– ĐŊĐ°ŅĐ˛ĐŊŅ– Đ´Ņ–Ņ— Ņ‚Đ° ҄ҖĐģŅŒŅ‚Ņ€Đ¸.", "change_your_password": "ЗĐŧŅ–ĐŊŅ–Ņ‚ŅŒ ŅĐ˛Ņ–Đš ĐŋĐ°Ņ€ĐžĐģҌ", "changed_visibility_successfully": "ВидиĐŧŅ–ŅŅ‚ŅŒ СĐŧŅ–ĐŊĐĩĐŊĐž", "charging": "Đ—Đ°Ņ€ŅĐ´ĐļĐ°Ņ”Ņ‚ŅŒŅŅ", "charging_requirement_mobile_backup": "ДĐģŅ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ ĐŋŅ€Đ¸ŅŅ‚Ņ€Ņ–Đš ĐŧĐ°Ņ” ĐˇĐ°Ņ€ŅĐ´ĐļĐ°Ņ‚Đ¸ŅŅ", + "check_corrupt_asset_backup": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đ¸Ņ‚Đ¸ ĐŊа ĐŋĐžŅˆĐēОдĐļĐĩĐŊŅ– Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅ– ĐēĐžĐŋŅ–Ņ— ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", + "check_corrupt_asset_backup_button": "ВиĐēĐžĐŊĐ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃", + "check_corrupt_asset_backup_description": "ВиĐēĐžĐŊŅƒĐšŅ‚Đĩ Ņ†ŅŽ ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đē҃ ĐģĐ¸ŅˆĐĩ ҇ĐĩŅ€ĐĩС Wi-Fi Ņ‚Đ° ĐŋҖҁĐģŅ СавĐĩŅ€ŅˆĐĩĐŊĐŊŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ Đ˛ŅŅ–Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛. ĐŸŅ€ĐžŅ†Đĩҁ ĐŧĐžĐļĐĩ СаКĐŊŅŅ‚Đ¸ ĐēŅ–ĐģҌĐēа Ņ…Đ˛Đ¸ĐģиĐŊ.", "check_logs": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đ¸Ņ‚Đ¸ ĐļŅƒŅ€ĐŊаĐģи", "checksum": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊа ҁ҃Đŧа", - "choose": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸", "choose_matching_people_to_merge": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ ĐģŅŽĐ´ĐĩĐš Đ´ĐģŅ Ой'Ņ”Đ´ĐŊаĐŊĐŊŅ", "city": "ĐœŅ–ŅŅ‚Đž", "cleanup_confirm_description": "Immich СĐŊĐ°ĐšŅˆĐžĐ˛ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} (ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐ¸Ņ… Đ´Đž {date}), ĐąĐĩСĐŋĐĩ҇ĐŊĐž СйĐĩŅ€ĐĩĐļĐĩĐŊĐ¸Ņ… ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ. ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐēĐžĐŋŅ–Ņ— С Ņ†ŅŒĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ?", @@ -719,10 +774,11 @@ "clear": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸", "clear_all": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ Đ˛ŅĐĩ", "clear_all_recent_searches": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ Đ˛ŅŅ– ĐžŅŅ‚Đ°ĐŊĐŊŅ– ĐŋĐžŅˆŅƒĐēĐžĐ˛Ņ– СаĐŋĐ¸Ņ‚Đ¸", - "clear_failed_count": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐŊĐĩвдаĐģŅ– ({count})", "clear_file_cache": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐēĐĩ҈ Ņ„Đ°ĐšĐģŅ–Đ˛", "clear_message": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ", "clear_value": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ СĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ", + "client_cert_dialog_msg_confirm": "ОК", + "client_cert_enter_password": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ", "client_cert_import": "ІĐŧĐŋĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸", "client_cert_import_success_msg": "КĐģŅ–Ņ”ĐŊŅ‚ŅŅŒĐēиК ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚ Ņ–ĐŧĐŋĐžŅ€Ņ‚ĐžĐ˛Đ°ĐŊĐž", "client_cert_invalid_msg": "НĐĩĐ´Ņ–ĐšŅĐŊиК Ņ„Đ°ĐšĐģ ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ° айО ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊиК ĐŋĐ°Ņ€ĐžĐģҌ", @@ -731,14 +787,16 @@ "client_cert_remove_msg": "КĐģŅ–Ņ”ĐŊŅ‚ŅŅŒĐēиК ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚ виĐģŅƒŅ‡ĐĩĐŊĐž", "client_cert_subtitle": "ĐŸŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ” ĐģĐ¸ŅˆĐĩ Ņ„ĐžŅ€ĐŧĐ°Ņ‚ PKCS12 (.p12, .pfx). ІĐŧĐŋĐžŅ€Ņ‚/виĐģŅƒŅ‡ĐĩĐŊĐŊŅ ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ° Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐĩ ĐģĐ¸ŅˆĐĩ ĐŋĐĩŅ€ĐĩĐ´ Đ˛Ņ…ĐžĐ´ĐžĐŧ ҃ ŅĐ¸ŅŅ‚ĐĩĐŧ҃", "client_cert_title": "SSL-ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚ ĐēĐģŅ–Ņ”ĐŊŅ‚Đ° [ЕКСПЕРИМЕНĐĸАЛĐŦНО]", + "clockwise": "По ĐŗĐžĐ´Đ¸ĐŊĐŊиĐēĐžĐ˛Ņ–Đš ҁ҂ҀҖĐģ҆Җ", "close": "ЗаĐēŅ€Đ¸Ņ‚Đ¸", "collapse": "Đ—ĐŗĐžŅ€ĐŊŅƒŅ‚Đ¸", "collapse_all": "Đ—ĐŗĐžŅ€ĐŊŅƒŅ‚Đ¸ Đ˛ŅĐĩ", "color": "КоĐģŅ–Ņ€", + "color_theme": "ĐĸĐĩĐŧа ĐžŅ„ĐžŅ€ĐŧĐģĐĩĐŊĐŊŅ", "command": "КоĐŧаĐŊда", "command_palette_prompt": "ШвидĐēиК ĐŋĐžŅˆŅƒĐē ŅŅ‚ĐžŅ€Ņ–ĐŊĐžĐē, Đ´Ņ–Đš Ņ‚Đ° ĐēĐžĐŧаĐŊĐ´", "command_palette_to_close": "СаĐēŅ€Đ¸Ņ‚Đ¸", - "command_palette_to_navigate": "ŅƒĐ˛Ņ–ĐšŅ‚Đ¸", + "command_palette_to_navigate": "ĐŋĐĩŅ€ĐĩĐšŅ‚Đ¸", "command_palette_to_select": "Đ˛Đ¸ĐąŅ€Đ°Ņ‚Đ¸", "command_palette_to_show_all": "ĐŋĐžĐēĐ°ĐˇĐ°Ņ‚Đ¸ Đ˛ŅĐĩ", "comment_deleted": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€ видаĐģĐĩĐŊĐž", @@ -747,7 +805,6 @@ "comments_are_disabled": "КоĐŧĐĩĐŊŅ‚Đ°Ņ€Ņ– виĐŧĐēĐŊĐĩĐŊĐž", "common_create_new_album": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŊОвиК аĐģŅŒĐąĐžĐŧ", "completed": "ЗавĐĩŅ€ŅˆĐĩĐŊĐž", - "configuration": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", "confirm": "ĐŸŅ–Đ´Ņ‚Đ˛ĐĩŅ€Đ´Đ¸Ņ‚Đ¸", "confirm_admin_password": "ĐŸŅ–Đ´Ņ‚Đ˛ĐĩŅ€Đ´Đ¸Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ°", "confirm_delete_face": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ ОйĐģĐ¸Ņ‡Ņ‡Ņ {name} С Ņ†ŅŒĐžĐŗĐž ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°?", @@ -762,16 +819,19 @@ "contain": "ВĐŋĐ¸ŅĐ°Ņ‚Đ¸", "context": "КоĐŊŅ‚ĐĩĐēҁ҂", "continue": "ĐŸŅ€ĐžĐ´ĐžĐ˛ĐļĐ¸Ņ‚Đ¸", - "control_bottom_app_bar_add_tags": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ‚ĐĩĐŗĐ¸", + "control_bottom_app_bar_create_new_album": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŊОвиК аĐģŅŒĐąĐžĐŧ", + "control_bottom_app_bar_delete_from_immich": "ВидаĐģĐ¸Ņ‚Đ¸ С Immich", "control_bottom_app_bar_delete_from_local": "ВидаĐģĐ¸Ņ‚Đ¸ С ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "control_bottom_app_bar_edit_location": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ", "control_bottom_app_bar_edit_time": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ Ņ‚Đ° Ņ‡Đ°Ņ", + "control_bottom_app_bar_share_link": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅĐŧ", + "control_bottom_app_bar_share_to": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ", "control_bottom_app_bar_trash_from_immich": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đž ĐēĐžŅˆĐ¸Đēа", "copied_image_to_clipboard": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ҁĐēĐžĐŋŅ–ĐšĐžĐ˛Đ°ĐŊĐž Đ´Đž ĐąŅƒŅ„ĐĩŅ€Đ° ОйĐŧŅ–ĐŊ҃.", "copied_to_clipboard": "ĐĄĐēĐžĐŋŅ–ĐšĐžĐ˛Đ°ĐŊĐž Đ´Đž ĐąŅƒŅ„ĐĩŅ€Đ° ОйĐŧŅ–ĐŊ҃!", "copy_error": "НĐĩ вдаĐģĐžŅŅ ҁĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸", + "copy_file_path": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ҈ĐģŅŅ… Đ´Đž Ņ„Đ°ĐšĐģ҃", "copy_image": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", - "copy_json": "КоĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ JSON", "copy_link": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", "copy_link_to_clipboard": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ Đ´Đž ĐąŅƒŅ„ĐĩŅ€Đ° ОйĐŧŅ–ĐŊ҃", "copy_password": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ", @@ -792,8 +852,6 @@ "create_new_person": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŊĐžĐ˛Ņƒ ĐģŅŽĐ´Đ¸ĐŊ҃", "create_new_person_hint": "ĐŸŅ€Đ¸ĐˇĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ Đ˛Đ¸ĐąŅ€Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊĐžĐ˛Ņ–Đš ĐģŅŽĐ´Đ¸ĐŊŅ–", "create_new_user": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŊĐžĐ˛ĐžĐŗĐž ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", - "create_person": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐģŅŽĐ´Đ¸ĐŊ҃", - "create_person_subtitle": "Đ”ĐžĐ´Đ°ĐšŅ‚Đĩ Ņ–Đŧ'Ņ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž ОйĐģĐ¸Ņ‡Ņ‡Ņ, Ņ‰ĐžĐą ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ Ņ‚Đ° ĐŋОСĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ ĐŊĐžĐ˛Ņƒ ĐžŅĐžĐąŅƒ", "create_shared_album_page_share_add_assets": "ДОДАĐĸИ ЕЛЕМЕНĐĸИ", "create_shared_album_page_share_select_photos": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž", "create_shared_link": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊĐĩ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", @@ -805,33 +863,37 @@ "created_at": "ĐĄŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž", "creating_linked_albums": "ĐĄŅ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐŋĐžĐ˛â€™ŅĐˇĐ°ĐŊĐ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛â€Ļ", "crop": "ĐšĐ°Đ´Ņ€ŅƒĐ˛Đ°Ņ‚Đ¸", + "crop_aspect_ratio_fixed": "Đ¤Ņ–ĐēŅĐžĐ˛Đ°ĐŊĐĩ", "crop_aspect_ratio_free": "Đ’Ņ–ĐģҌĐŊĐĩ", "crop_aspect_ratio_original": "ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊĐĩ", "crop_aspect_ratio_square": "ĐšĐ˛Đ°Đ´Ņ€Đ°Ņ‚ĐŊĐĩ", + "curated_object_page_title": "Đ Đĩ҇Җ", "current_device": "ĐŸĐžŅ‚ĐžŅ‡ĐŊиК ĐŋŅ€Đ¸ŅŅ‚Ņ€Ņ–Đš", "current_pin_code": "ĐŸĐžŅ‚ĐžŅ‡ĐŊиК PIN-ĐēОд", "current_server_address": "ĐŸĐžŅ‚ĐžŅ‡ĐŊа Đ°Đ´Ņ€ĐĩŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", "custom_date": "Đ”ĐžĐ˛Ņ–ĐģҌĐŊа Đ´Đ°Ņ‚Đ°", "custom_locale": "Đ”ĐžĐ˛Ņ–ĐģҌĐŊŅ– Ņ€ĐĩĐŗŅ–ĐžĐŊаĐģҌĐŊŅ– ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", "custom_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ, Ņ‡Đ°Ņ Ņ‚Đ° Ņ‡Đ¸ŅĐģа С ŅƒŅ€Đ°Ņ…ŅƒĐ˛Đ°ĐŊĐŊŅĐŧ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžŅ— ĐŧОви Ņ‚Đ° Ņ€ĐĩĐŗŅ–ĐžĐŊ҃", + "custom_url": "Đ”ĐžĐ˛Ņ–ĐģҌĐŊа URL-Đ°Đ´Ņ€ĐĩŅĐ°", "cutoff_date_description": "ЗбĐĩŅ€Ņ–ĐŗĐ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž Са ĐžŅŅ‚Đ°ĐŊĐŊŅ–â€Ļ", "cutoff_day": "{count, plural, one {Đ´ĐĩĐŊҌ} few {Đ´ĐŊŅ–} many {Đ´ĐŊŅ–Đ˛} other {Đ´ĐŊŅ–Đ˛}}", "cutoff_year": "{count, plural, one {ҀҖĐē} few {Ņ€ĐžĐēи} many {Ņ€ĐžĐēŅ–Đ˛} other {Ņ€ĐžĐēŅ–Đ˛}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM yyyy", "dark": "ĐĸĐĩĐŧĐŊа", "dark_theme": "ПĐĩŅ€ĐĩĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŊа Ņ‚ĐĩĐŧĐŊ҃ Ņ‚ĐĩĐŧ҃", "date": "Đ”Đ°Ņ‚Đ°", "date_after": "Đ”Đ°Ņ‚Đ° ĐŋҖҁĐģŅ", "date_and_time": "Đ”Đ°Ņ‚Đ° Đš Ņ‡Đ°Ņ", "date_before": "Đ”Đ°Ņ‚Đ° Đ´Đž", - "date_of_birth": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ", + "date_format": "E, d LLL y â€ĸ HH:mm", "date_of_birth_saved": "Đ”Đ°Ņ‚Ņƒ ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", "date_range": "Đ”Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚", - "date_time_original": "ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊа Đ´Đ°Ņ‚Đ°/Ņ‡Đ°Ņ", "day": "ДĐĩĐŊҌ", "days": "ДĐŊŅ–", "deduplicate_all": "ВидаĐģĐ¸Ņ‚Đ¸ Đ˛ŅŅ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", - "default_quality_subtitle": "Đ¯ĐēŅ–ŅŅ‚ŅŒ, Ņ‰Đž виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚ŅŒŅŅ, ĐēĐžĐģи ви Đ´Ņ–ĐģĐ¸Ņ‚ĐĩŅŅ. ĐŖŅ‚Ņ€Đ¸ĐŧŅƒĐšŅ‚Đĩ ĐēĐŊĐžĐŋĐē҃ ÂĢĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅÂģ, Ņ‰ĐžĐą ĐžĐąĐ¸Ņ€Đ°Ņ‚Đ¸ ŅĐēŅ–ŅŅ‚ŅŒ Ņ‰ĐžŅ€Đ°ĐˇŅƒ.", - "default_share_quality": "ĐĸиĐŋОва ŅĐēŅ–ŅŅ‚ŅŒ ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋ҃", + "default_locale": "ĐĸиĐŋОва ĐģĐžĐēаĐģҌ", + "default_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Đ¸ Ņ‚Đ° Ņ‡Đ¸ŅĐģа Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊĐž Đ´Đž ĐģĐžĐēаĐģŅ– Đ˛Đ°ŅˆĐžĐŗĐž ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đ°", "delete": "ВидаĐģĐ¸Ņ‚Đ¸", "delete_action_confirmation_message": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ ҆ĐĩĐš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚? Đ™ĐžĐŗĐž ĐąŅƒĐ´Đĩ ĐŋĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ, а Ņ‚Đ°ĐēĐžĐļ С'ŅĐ˛Đ¸Ņ‚ŅŒŅŅ СаĐŋĐ¸Ņ‚ ĐŊа ĐšĐžĐŗĐž видаĐģĐĩĐŊĐŊŅ С ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "delete_action_prompt": "ВидаĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", @@ -840,6 +902,8 @@ "delete_dialog_alert": "ĐĻŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐąŅƒĐ´Đĩ ĐŊаСавĐļди видаĐģĐĩĐŊĐž С Immich Ņ‚Đ° С Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "delete_dialog_alert_local": "ĐĻŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐąŅƒĐ´Đĩ ĐŊаСавĐļди видаĐģĐĩĐŊĐž С Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ, аĐģĐĩ вОĐŊи СаĐģĐ¸ŅˆĐ°Ņ‚ŅŒŅŅ ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ Immich", "delete_dialog_alert_local_non_backed_up": "ДĐĩŅĐēŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊĐĩ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ Immich, Ņ– Ņ—Ņ… ĐąŅƒĐ´Đĩ ĐŊаСавĐļди видаĐģĐĩĐŊĐž С Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", + "delete_dialog_alert_remote": "ĐĻŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐąŅƒĐ´Đĩ ĐŊаСавĐļди видаĐģĐĩĐŊĐž С ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Immich", + "delete_dialog_ok_force": "Đ’ŅĐĩ ОдĐŊĐž видаĐģĐ¸Ņ‚Đ¸", "delete_dialog_title": "ВидаĐģĐ¸Ņ‚Đ¸ ĐŊаСавĐļди", "delete_duplicates_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ĐŊаСавĐļди видаĐģĐ¸Ņ‚Đ¸ ҆Җ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸?", "delete_face": "ВидаĐģĐ¸Ņ‚Đ¸ ОйĐģĐ¸Ņ‡Ņ‡Ņ", @@ -858,16 +922,21 @@ "delete_tag_confirmation_prompt": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ Ņ‚ĐĩĐŗ {tagName}?", "delete_user": "ВидаĐģĐ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "deleted_shared_link": "ВидаĐģĐĩĐŊĐž ҁĐŋŅ–ĐģҌĐŊĐĩ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", + "deletes_missing_assets": "ВидаĐģŅŅ” ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, ŅĐēŅ– Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– ĐŊа Đ´Đ¸ŅĐē҃", "description": "ОĐŋĐ¸Ņ", + "description_input_hint_text": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐžĐŋĐ¸Ņâ€Ļ", + "description_input_submit_error": "НĐĩ вдаĐģĐžŅŅ ĐžĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐžĐŋĐ¸Ņ. ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅŒŅ‚Đĩ ĐļŅƒŅ€ĐŊаĐģ Đ´ĐģŅ Đ´ĐĩŅ‚Đ°ĐģĐĩĐš", "deselect_all": "ĐĄĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛Đ¸ĐąŅ–Ņ€ ŅƒŅŅ–Ņ…", "details": "ДĐĩŅ‚Đ°ĐģŅ–", "direction": "НаĐŋŅ€ŅĐŧ", "disable": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸", "disabled": "ВиĐŧĐēĐŊĐĩĐŊĐž", + "disallow_edits": "Đ—Đ°ĐąĐžŅ€ĐžĐŊĐ¸Ņ‚Đ¸ Ņ€ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°ĐŊĐŊŅ", "discord": "Discord", "discover": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Ņ‚Ņ", "discovered_devices": "Đ’Đ¸ŅĐ˛ĐģĐĩĐŊŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", "dismiss_all_errors": "ĐŸŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ Đ˛ŅŅ– ĐŋĐžĐŧиĐģĐēи", + "dismiss_error": "ĐŸŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ ĐŋĐžĐŧиĐģĐē҃", "display_options": "Đ’Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "display_order": "ĐŸĐžŅ€ŅĐ´ĐžĐē Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "display_original_photos": "Đ’Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊŅ– Ņ„ĐžŅ‚Đž", @@ -876,9 +945,11 @@ "documentation": "ДоĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ–Ņ", "done": "Đ“ĐžŅ‚ĐžĐ˛Đž", "download": "ЗаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸", + "download_action_prompt": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "download_canceled": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ҁĐēĐ°ŅĐžĐ˛Đ°ĐŊĐž", "download_complete": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ СаĐēŅ–ĐŊ҇ĐĩĐŊĐž", "download_enqueue": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŋĐžŅŅ‚Đ°Đ˛ĐģĐĩĐŊĐž в ҇ĐĩŅ€ĐŗŅƒ", + "download_error": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸", "download_failed": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸", "download_finished": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ СаĐēŅ–ĐŊ҇ĐĩĐŊĐž", "download_include_embedded_motion_videos": "Đ’ĐąŅƒĐ´ĐžĐ˛Đ°ĐŊŅ– Đ˛Ņ–Đ´ĐĩĐž", @@ -888,19 +959,19 @@ "download_paused": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŋŅ€Đ¸ĐˇŅƒĐŋиĐŊĐĩĐŊĐž", "download_settings": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "download_settings_description": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", + "download_started": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ Ņ€ĐžĐˇĐŋĐžŅ‡Đ°Ņ‚Đž", + "download_sucess": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", + "download_sucess_android": "МĐĩĐ´Ņ–Đ° СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž в DCIM/Immich", "download_waiting_to_retry": "ĐžŅ‡Ņ–ĐēŅƒĐ˛Đ°ĐŊĐŊŅ ĐŋĐžĐ˛Ņ‚ĐžŅ€ĐŊĐžŅ— ҁĐŋŅ€ĐžĐąĐ¸", "downloading": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "downloading_asset_filename": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ° {filename}", "downloading_from_icloud": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ С iCloud", "downloading_media": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŧĐĩĐ´Ņ–Đ°", - "drag_to_reorder": "ПĐĩŅ€ĐĩŅ‚ŅĐŗĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŋĐžŅ€ŅĐ´ĐžĐē", "drop_files_to_upload": "ПĐĩŅ€ĐĩŅ‚ŅĐŗĐŊŅ–Ņ‚ŅŒ Ņ„Đ°ĐšĐģи ĐąŅƒĐ´ŅŒ-ĐēŅƒĐ´Đ¸, Ņ‰ĐžĐą виваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸", - "duplicate": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚", - "duplicate_workflow": "Đ”ŅƒĐąĐģŅŽĐ˛Đ°Ņ‚Đ¸ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ", "duplicates": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", - "duplicates_description": "ОĐŋŅ€Đ°Ņ†ŅŽĐšŅ‚Đĩ ĐēĐžĐļĐŊ҃ ĐŗŅ€ŅƒĐŋ҃, ĐŋОСĐŊĐ°Ņ‡Đ¸Đ˛ŅˆĐ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, Ņ‰Đž Ņ” Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Đŧи (Са ĐŊĐ°ŅĐ˛ĐŊĐžŅŅ‚Ņ–)", + "duplicates_description": "ОĐŋŅ€Đ°Ņ†ŅŽĐšŅ‚Đĩ ĐēĐžĐļĐŊ҃ ĐŗŅ€ŅƒĐŋ҃, вĐēĐ°ĐˇĐ°Đ˛ŅˆĐ¸, ŅĐēŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, ŅĐēŅ‰Đž Ņ‚Đ°ĐēŅ– Ņ”, Ņ” Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ°Đŧи", "duration": "ĐĸŅ€Đ¸Đ˛Đ°ĐģŅ–ŅŅ‚ŅŒ", - "edit": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸", + "edit": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸", "edit_album": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧ", "edit_avatar": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ°Đ˛Đ°Ņ‚Đ°Ņ€", "edit_birthday": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ", @@ -908,7 +979,9 @@ "edit_date_and_time": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ Ņ‚Đ° Ņ‡Đ°Ņ", "edit_date_and_time_action_prompt": "ЗĐŧŅ–ĐŊĐĩĐŊĐž Đ´Đ°Ņ‚Ņƒ Ņ‚Đ° Ņ‡Đ°Ņ ҃ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊ҂Җ} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Ņ…} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Ņ…} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Ņ…}}", "edit_date_and_time_by_offset": "ЗĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ Са ĐˇŅŅƒĐ˛ĐžĐŧ", + "edit_date_and_time_by_offset_interval": "Новий Đ´Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚: {from} - {to}", "edit_description": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐžĐŋĐ¸Ņ", + "edit_description_prompt": "ОбĐĩŅ€Ņ–Ņ‚ŅŒ ĐŊОвиК ĐžĐŋĐ¸Ņ:", "edit_exclusion_pattern": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ŅˆĐ°ĐąĐģĐžĐŊ виĐēĐģŅŽŅ‡ĐĩĐŊҌ", "edit_faces": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ОйĐģĐ¸Ņ‡Ņ‡Ņ", "edit_key": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐģŅŽŅ‡", @@ -923,6 +996,9 @@ "edit_user": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "edit_workflow": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ", "editor": "Đ ĐĩдаĐēŅ‚ĐžŅ€", + "editor_close_without_save_prompt": "ЗĐŧŅ–ĐŊи ĐŊĐĩ ĐąŅƒĐ´Đĩ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", + "editor_close_without_save_title": "ЗаĐēŅ€Đ¸Ņ‚Đ¸ Ņ€ĐĩдаĐēŅ‚ĐžŅ€?", + "editor_confirm_reset_all_changes": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ ҁĐēиĐŊŅƒŅ‚Đ¸ Đ˛ŅŅ– СĐŧŅ–ĐŊи?", "editor_discard_edits_confirm": "ĐĄĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸ СĐŧŅ–ĐŊи", "editor_discard_edits_prompt": "ĐŖ Đ˛Đ°Ņ Ņ” ĐŊĐĩСйĐĩŅ€ĐĩĐļĐĩĐŊŅ– СĐŧŅ–ĐŊи. Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Ņ—Ņ… ҁĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸?", "editor_discard_edits_title": "ĐĄĐēĐ°ŅŅƒĐ˛Đ°Ņ‚Đ¸ СĐŧŅ–ĐŊи?", @@ -951,7 +1027,9 @@ "enter_your_pin_code": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ PIN-ĐēОд", "enter_your_pin_code_subtitle": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ PIN-ĐēОд, Ņ‰ĐžĐą ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи", "error": "ПоĐŧиĐģĐēа", + "error_change_sort_album": "НĐĩ вдаĐģĐžŅŅ СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŋĐžŅ€ŅĐ´ĐžĐē ŅĐžŅ€Ņ‚ŅƒĐ˛Đ°ĐŊĐŊŅ аĐģŅŒĐąĐžĐŧ҃", "error_delete_face": "НĐĩ вдаĐģĐžŅŅ видаĐģĐ¸Ņ‚Đ¸ ОйĐģĐ¸Ņ‡Ņ‡Ņ С ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", + "error_getting_places": "НĐĩ вдаĐģĐžŅŅ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ ĐŧŅ–ŅŅ†Ņ", "error_loading_albums": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", "error_loading_image": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "error_loading_partners": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Ņ–Đ˛: {error}", @@ -991,7 +1069,6 @@ "failed_to_remove_product_key": "НĐĩ вдаĐģĐžŅŅ виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐēĐģŅŽŅ‡ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚Ņƒ", "failed_to_reset_pin_code": "НĐĩ вдаĐģĐžŅŅ ҁĐēиĐŊŅƒŅ‚Đ¸ PIN-ĐēОд", "failed_to_stack_assets": "НĐĩ вдаĐģĐžŅŅ ĐˇĐŗŅ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", - "failed_to_tag_assets": "НĐĩ вдаĐģĐžŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ Ņ‚ĐĩĐŗĐ¸ Đ´Đž ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "failed_to_unstack_assets": "НĐĩ вдаĐģĐžŅŅ Ņ€ĐžĐˇĐŗŅ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "failed_to_update_notification_status": "НĐĩ вдаĐģĐžŅŅ ĐžĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ŅŅ‚Đ°Ņ‚ŅƒŅ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ", "incorrect_email_or_password": "НĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊа Đ°Đ´Ņ€ĐĩŅĐ° ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊĐžŅ— ĐŋĐžŅˆŅ‚Đ¸ айО ĐŋĐ°Ņ€ĐžĐģҌ", @@ -1090,10 +1167,18 @@ "exif": "Exif", "exif_bottom_sheet_description": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐžĐŋĐ¸Ņâ€Ļ", "exif_bottom_sheet_description_error": "НĐĩ вдаĐģĐžŅŅ ĐžĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐžĐŋĐ¸Ņ", + "exif_bottom_sheet_details": "ДЕĐĸАЛІ", + "exif_bottom_sheet_location": "МІСĐĻЕ", "exif_bottom_sheet_no_description": "БĐĩС ĐžĐŋĐ¸ŅŅƒ", + "exif_bottom_sheet_people": "ЛЮДИ", + "exif_bottom_sheet_person_add_person": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ–Đŧ'Ņ", "exit_slideshow": "Đ’Đ¸ĐšŅ‚Đ¸ ĐˇŅ– ҁĐģаКд-ŅˆĐžŅƒ", "expand": "Đ ĐžĐˇĐŗĐžŅ€ĐŊŅƒŅ‚Đ¸", "expand_all": "Đ ĐžĐˇĐŗĐžŅ€ĐŊŅƒŅ‚Đ¸ Đ˛ŅĐĩ", + "experimental_settings_new_asset_list_subtitle": "ĐŖ Ņ€ĐžĐˇŅ€ĐžĐąŅ†Ņ–", + "experimental_settings_new_asset_list_title": "ВĐŧиĐēĐ°Ņ‚Đ¸ ĐĩĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊ҃ ҁҖ҂Đē҃ Ņ„ĐžŅ‚Đž", + "experimental_settings_subtitle": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐšŅ‚Đĩ ĐŊа вĐģĐ°ŅĐŊиК Ņ€Đ¸ĐˇĐ¸Đē!", + "experimental_settings_title": "ЕĐēҁĐŋĐĩŅ€Đ¸ĐŧĐĩĐŊŅ‚Đ°ĐģҌĐŊŅ–", "expire_after": "ĐĄĐŋĐģĐ¸Đ˛Đ°Ņ” ҇ĐĩŅ€ĐĩС", "expired": "ЗаĐēŅ–ĐŊŅ‡Đ¸Đ˛ŅŅ Ņ‚ĐĩŅ€ĐŧŅ–ĐŊ Đ´Ņ–Ņ—", "expires_date": "ĐĸĐĩŅ€ĐŧŅ–ĐŊ Đ´Ņ–Ņ— СаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ {date}", @@ -1103,40 +1188,41 @@ "export_as_json": "ЕĐēҁĐŋĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ŅĐē JSON", "export_database": "ЕĐēҁĐŋĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐąĐ°ĐˇŅƒ даĐŊĐ¸Ņ…", "export_database_description": "ЕĐēҁĐŋĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐąĐ°ĐˇŅƒ даĐŊĐ¸Ņ… SQLite", - "exposure_time": "Đ§Đ°Ņ Đ˛Đ¸Ņ‚Ņ€Đ¸ĐŧĐēи", "extension": "Đ ĐžĐˇŅˆĐ¸Ņ€ĐĩĐŊĐŊŅ", "external": "ЗовĐŊŅ–ŅˆĐŊŅ–", "external_libraries": "ЗовĐŊŅ–ŅˆĐŊŅ– ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", "external_network": "ЗовĐŊŅ–ŅˆĐŊŅ ĐŧĐĩŅ€ĐĩĐļа", "external_network_sheet_info": "КоĐģи ви ĐŊĐĩ ĐŋŅ–Đ´'Ņ”Đ´ĐŊаĐŊŅ– Đ´Đž ĐžĐąŅ€Đ°ĐŊĐžŅ— ĐŧĐĩŅ€ĐĩĐļŅ– Wi-Fi, ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐžĐē ĐŋŅ–Đ´'Ņ”Đ´ĐŊŅƒĐ˛Đ°Ņ‚Đ¸ĐŧĐĩŅ‚ŅŒŅŅ Đ´Đž ҁĐĩŅ€Đ˛ĐĩŅ€Đ° ҇ĐĩŅ€ĐĩС ĐŋĐĩŅ€ŅˆŅƒ Đ´ĐžŅŅ‚ŅƒĐŋĐŊ҃ URL-Đ°Đ´Ņ€Đĩҁ҃ С ĐŊавĐĩĐ´ĐĩĐŊĐ¸Ņ… ĐŊиĐļ҇Đĩ, ĐŋĐžŅ‡Đ¸ĐŊĐ°ŅŽŅ‡Đ¸ СвĐĩŅ€Ņ…Ņƒ вĐŊиС", - "f_number": "Đ”Ņ–Đ°Ņ„Ņ€Đ°ĐŗĐŧа (f-Ņ‡Đ¸ŅĐģĐž)", "face_unassigned": "НĐĩ ĐŋŅ€Đ¸ĐˇĐŊĐ°Ņ‡ĐĩĐŊĐž", "failed": "НĐĩ вдаĐģĐžŅŅ", "failed_count": "НĐĩ вдаĐģĐžŅŅ: {count}", "failed_to_authenticate": "НĐĩ вдаĐģĐžŅŅ Đ°Đ˛Ņ‚ĐĩĐŊŅ‚Đ¸Ņ„Ņ–ĐēŅƒĐ˛Đ°Ņ‚Đ¸ŅŅ", - "failed_to_delete_file": "НĐĩ вдаĐģĐžŅŅ видаĐģĐ¸Ņ‚Đ¸ Ņ„Đ°ĐšĐģ", "failed_to_load_assets": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "failed_to_load_folder": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐŋаĐŋĐē҃", "favorite": "До Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž", "favorite_action_prompt": "{count} дОдаĐŊĐž Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž", "favorite_or_unfavorite_photo": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž айО виĐģŅƒŅ‡Đ¸Ņ‚Đ¸", "favorites": "Đ’Đ¸ĐąŅ€Đ°ĐŊĐĩ", + "favorites_page_no_favorites": "НĐĩĐŧĐ°Ņ” Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", "feature_photo_updated": "ГоĐģОвĐŊĐĩ Ņ„ĐžŅ‚Đž ĐžĐŊОвĐģĐĩĐŊĐž", "features": "Đ¤ŅƒĐŊĐē҆Җҗ", + "features_in_development": "Đ¤ŅƒĐŊĐē҆Җҗ в Ņ€ĐžĐˇŅ€ĐžĐąŅ†Ņ–", "features_setting_description": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ„ŅƒĐŊĐēŅ†Ņ–ŅĐŧи ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", "file_name_or_extension": "ІĐŧ'Ņ Ņ„Đ°ĐšĐģ҃ айО Ņ€ĐžĐˇŅˆĐ¸Ņ€ĐĩĐŊĐŊŅ", "file_name_text": "ІĐŧ'Ņ Ņ„Đ°ĐšĐģ҃", + "file_name_with_value": "ІĐŧ'Ņ Ņ„Đ°ĐšĐģ҃: {file_name}", "file_size": "РОСĐŧŅ–Ņ€ Ņ„Đ°ĐšĐģ҃", "filename": "ІĐŧ'Ņ Ņ„Đ°ĐšĐģ҃", "filetype": "ĐĸиĐŋ Ņ„Đ°ĐšĐģ҃", "filter": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€", + "filter_description": "ĐŖĐŧОви Đ´ĐģŅ ҄ҖĐģŅŒŅ‚Ņ€Đ°Ņ†Ņ–Ņ— ҆ҖĐģŅŒĐžĐ˛Đ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "filter_people": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Са ĐģŅŽĐ´ŅŒĐŧи", "filter_places": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Са ĐŧŅ–ŅŅ†ŅĐŧи", "filter_tags": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€ Са Ņ‚ĐĩĐŗĐ°Đŧи", "filters": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€Đ¸", + "find_them_fast": "ШвидĐēĐž СĐŊĐ°Ņ…ĐžĐ´ŅŒŅ‚Đĩ Ņ—Ņ… Са ĐŊĐ°ĐˇĐ˛ĐžŅŽ Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ ĐŋĐžŅˆŅƒĐē҃", "first": "ПĐĩŅ€ŅˆĐ¸Đš", "fix_incorrect_match": "ВиĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đ¸ ĐŊĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊиК ĐˇĐąŅ–Đŗ", - "focal_length": "ФОĐē҃ҁĐŊа Đ˛Ņ–Đ´ŅŅ‚Đ°ĐŊҌ", "folder": "ПаĐŋĐēа", "folder_not_found": "ПаĐŋĐē҃ ĐŊĐĩ СĐŊаКдĐĩĐŊĐž", "folders": "ПаĐŋĐēи", @@ -1147,7 +1233,6 @@ "free_up_space_description": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Ņ–Ņ‚ŅŒ Ņ„ĐžŅ‚Đž Đš Đ˛Ņ–Đ´ĐĩĐž, Đ´ĐģŅ ŅĐēĐ¸Ņ… Ņ” Ņ€ĐĩСĐĩŅ€Đ˛ĐŊа ĐēĐžĐŋŅ–Ņ, Đ´Đž ĐēĐžŅˆĐ¸Đēа Đ˛Đ°ŅˆĐžĐŗĐž ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ, Ņ‰ĐžĐą ĐˇĐ˛Ņ–ĐģҌĐŊĐ¸Ņ‚Đ¸ ĐŧҖҁ҆Đĩ. Đ’Đ°ŅˆŅ– ĐēĐžĐŋŅ–Ņ— ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ СаĐģĐ¸ŅˆĐ°ŅŽŅ‚ŅŒŅŅ в ĐąĐĩСĐŋĐĩ҆Җ.", "free_up_space_settings_subtitle": "Đ—Đ˛Ņ–ĐģҌĐŊĐ¸Ņ‚Đ¸ ŅŅ…ĐžĐ˛Đ¸Ņ‰Đĩ ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "full_path": "ПовĐŊиК ҈ĐģŅŅ…: {path}", - "full_path_or_folder": "ПовĐŊиК ҈ĐģŅŅ… айО ĐŋаĐŋĐēа", "gcast_enabled": "Google Cast", "gcast_enabled_description": "ĐĻŅ Ņ„ŅƒĐŊĐēŅ†Ņ–Ņ СаваĐŊŅ‚Đ°ĐļŅƒŅ” СОвĐŊŅ–ŅˆĐŊŅ– Ņ€ĐĩŅŅƒŅ€ŅĐ¸ С Google Đ´ĐģŅ ŅĐ˛ĐžŅ”Ņ— Ņ€ĐžĐąĐžŅ‚Đ¸.", "general": "Đ—Đ°ĐŗĐ°ĐģҌĐŊŅ–", @@ -1168,6 +1253,7 @@ "group_owner": "За вĐģĐ°ŅĐŊиĐēĐžĐŧ", "group_places_by": "Đ“Ņ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧŅ–ŅŅ†Ņ Саâ€Ļ", "group_year": "За Ņ€ĐžĐēĐžĐŧ", + "haptic_feedback_switch": "ВĐŧиĐēĐ°Ņ‚Đ¸ Ņ‚Đ°ĐēŅ‚Đ¸ĐģҌĐŊиК Đ˛Ņ–Đ´ĐŗŅƒĐē", "haptic_feedback_title": "ĐĸаĐēŅ‚Đ¸ĐģҌĐŊиК Đ˛Ņ–Đ´ĐŗŅƒĐē", "has_quota": "ĐœĐ°Ņ” ĐēĐ˛ĐžŅ‚Ņƒ", "hash_asset": "ĐĨĐĩŅˆŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", @@ -1188,12 +1274,29 @@ "hide_schema": "ĐŸŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ ҁ҅ĐĩĐŧ҃", "hide_text_recognition": "ĐŸŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ Ņ€ĐžĐˇĐŋŅ–ĐˇĐŊаваĐŊĐŊŅ Ņ‚ĐĩĐēŅŅ‚Ņƒ", "hide_unnamed_people": "ĐŸŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ ĐģŅŽĐ´ĐĩĐš ĐąĐĩС Ņ–ĐŧĐĩĐŊŅ–", + "home_page_add_to_album_conflicts": "ДодаĐŊĐž {added} ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ Đ´Đž аĐģŅŒĐąĐžĐŧ҃ {album}. {failed} ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ вĐļĐĩ Ņ” в аĐģŅŒĐąĐžĐŧŅ–.", + "home_page_add_to_album_err_local": "ĐĐ°Ņ€Đ°ĐˇŅ– ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧŅ–Đ˛, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_add_to_album_success": "ДодаĐŊĐž {added} ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ Đ´Đž аĐģŅŒĐąĐžĐŧ҃ {album}.", + "home_page_album_err_partner": "ĐĐ°Ņ€Đ°ĐˇŅ– ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ° Đ´Đž аĐģŅŒĐąĐžĐŧ҃, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_archive_err_local": "ĐĐ°Ņ€Đ°ĐˇŅ– ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ°Ņ€Ņ…Ņ–Đ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_archive_err_partner": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ°Ņ€Ņ…Ņ–Đ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "home_page_building_timeline": "ĐŸĐžĐąŅƒĐ´ĐžĐ˛Đ° Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ—", + "home_page_delete_err_partner": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ видаĐģĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_delete_remote_err_local": "ĐĄĐĩŅ€ĐĩĐ´ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… Đ´ĐģŅ видаĐģĐĩĐŊĐŊŅ С ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Ņ” ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_favorite_err_local": "ĐĐ°Ņ€Đ°ĐˇŅ– ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_favorite_err_partner": "ĐĐ°Ņ€Đ°ĐˇŅ– ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ° Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_first_time_notice": "Đ¯ĐēŅ‰Đž ви ĐēĐžŅ€Đ¸ŅŅ‚ŅƒŅ”Ņ‚ĐĩŅŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐēĐžĐŧ ҃ĐŋĐĩŅ€ŅˆĐĩ, ĐąŅƒĐ´ŅŒ ĐģĐ°ŅĐēа, ОйĐĩŅ€Ņ–Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ, Ņ‰ĐžĐą ҃ Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ— Đˇâ€™ŅĐ˛Đ¸ĐģĐ¸ŅŅ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", + "home_page_locked_error_local": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐŋĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_locked_error_partner": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐŋĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ° Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_share_err_local": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐŊĐ°Đ´Đ°Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐģĐžĐēаĐģҌĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ҇ĐĩŅ€ĐĩС ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "home_page_upload_err_limit": "МоĐļĐŊа виваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐŊĐĩ ĐąŅ–ĐģҌ҈Đĩ 30 ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ вОдĐŊĐžŅ‡Đ°Ņ, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "host": "ĐĨĐžŅŅ‚", "hour": "ГодиĐŊа", "hours": "ГодиĐŊи", "id": "ID", "idle": "ĐŸŅ€ĐžŅŅ‚Ņ–Đš", + "ignore_icloud_photos": "ĐŸŅ€ĐžĐŋ҃ҁĐēĐ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž С iCloud", + "ignore_icloud_photos_description": "Đ¤ĐžŅ‚Đž, Ņ‰Đž СйĐĩŅ€Ņ–ĐŗĐ°ŅŽŅ‚ŅŒŅŅ в iCloud, ĐŊĐĩ ĐąŅƒĐ´Đĩ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐž ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€ Immich", "image": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "image_alt_text_date": "{isVideo, select, true {Đ’Ņ–Đ´ĐĩĐž} other {Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ}} СĐŊŅŅ‚Đž {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Đ’Ņ–Đ´ĐĩĐž} other {Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ}} С {person1} СĐŊŅŅ‚Đž {date}", @@ -1205,6 +1308,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Đ’Ņ–Đ´ĐĩĐž} other {Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ}} СĐŊŅŅ‚Đž в {city}, {country} С {person1} Ņ‚Đ° {person2} {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Đ’Ņ–Đ´ĐĩĐž} other {Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ}} СĐŊŅŅ‚Đž в {city}, {country} С {person1}, {person2} Ņ‚Đ° {person3} {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Đ’Ņ–Đ´ĐĩĐž} other {Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ}} СĐŊŅŅ‚Đž в {city}, {country} С {person1}, {person2} Ņ‚Đ° ҉Đĩ {additionalCount, number} ĐģŅŽĐ´ŅŒĐŧи {date}", + "image_saved_successfully": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", + "image_viewer_page_state_provider_download_started": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŋĐžŅ‡Đ°ĐģĐžŅŅ", + "image_viewer_page_state_provider_download_success": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", + "image_viewer_page_state_provider_share_error": "НĐĩ вдаĐģĐžŅŅ ĐŊĐ°Đ´Đ°Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ", "immich_logo": "Đ›ĐžĐŗĐžŅ‚Đ¸Đŋ Immich", "immich_web_interface": "ВĐĩĐą-Ņ–ĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ Immich", "import_from_json": "ІĐŧĐŋĐžŅ€Ņ‚ Ņ–Đˇ JSON", @@ -1219,7 +1326,6 @@ "individual_share": "ОĐēŅ€ĐĩĐŧиК Đ´ĐžŅŅ‚ŅƒĐŋ", "individual_shares": "ОĐēŅ€ĐĩĐŧŅ– Đ´ĐžŅŅ‚ŅƒĐŋи", "info": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ", - "integrity_checks": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēи ĐĻŅ–ĐģҖҁĐŊĐžŅŅ‚Ņ–", "interval": { "day_at_onepm": "ЊОдĐŊŅ Đž 13:00", "hours": "КоĐļĐŊ҃ {hours, plural, one {# ĐŗĐžĐ´Đ¸ĐŊ҃} few {# ĐŗĐžĐ´Đ¸ĐŊи} many {# ĐŗĐžĐ´Đ¸ĐŊ} other {# ĐŗĐžĐ´Đ¸ĐŊ}}", @@ -1230,9 +1336,16 @@ "invalid_date_format": "НĐĩĐēĐžŅ€ĐĩĐēŅ‚ĐŊиК Ņ„ĐžŅ€ĐŧĐ°Ņ‚ Đ´Đ°Ņ‚Đ¸", "invite_people": "ЗаĐŋŅ€ĐžŅĐ¸Ņ‚Đ¸ ĐģŅŽĐ´ĐĩĐš", "invite_to_album": "ЗаĐŋŅ€ĐžŅĐ¸Ņ‚Đ¸ в аĐģŅŒĐąĐžĐŧ", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "ĐžŅ‚Ņ€Đ¸ĐŧаĐŊĐž даĐŊŅ– {dateTime}", + "ios_debug_info_last_sync_at": "ĐžŅŅ‚Đ°ĐŊĐŊŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ {dateTime}", + "ios_debug_info_no_processes_queued": "ФОĐŊĐžĐ˛Ņ– ĐŋŅ€ĐžŅ†ĐĩŅĐ¸ Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– в ҇ĐĩŅ€ĐˇŅ–", + "ios_debug_info_no_sync_yet": "ФОĐŊОвĐĩ СавдаĐŊĐŊŅ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ— ҉Đĩ ĐŊĐĩ виĐēĐžĐŊŅƒĐ˛Đ°ĐģĐžŅŅ", + "ios_debug_info_processes_queued": "{count, plural, one {{count} Ņ„ĐžĐŊОвиК ĐŋŅ€ĐžŅ†Đĩҁ ҃ ҇ĐĩŅ€ĐˇŅ–} few {{count} Ņ„ĐžĐŊĐžĐ˛Ņ– ĐŋŅ€ĐžŅ†ĐĩŅĐ¸ ҃ ҇ĐĩŅ€ĐˇŅ–} many {{count} Ņ„ĐžĐŊĐžĐ˛Đ¸Ņ… ĐŋŅ€ĐžŅ†ĐĩŅŅ–Đ˛ ҃ ҇ĐĩŅ€ĐˇŅ–} other {{count} Ņ„ĐžĐŊĐžĐ˛Đ¸Ņ… ĐŋŅ€ĐžŅ†ĐĩŅŅ–Đ˛ ҃ ҇ĐĩŅ€ĐˇŅ–}}", + "ios_debug_info_processing_ran_at": "ĐžĐąŅ€ĐžĐąĐē҃ виĐēĐžĐŊаĐŊĐž {dateTime}", "items_count": "{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "jobs": "ЗавдаĐŊĐŊŅ", + "json_editor": "JSON-Ņ€ĐĩдаĐēŅ‚ĐžŅ€", + "json_error": "ПоĐŧиĐģĐēа JSON", "keep": "ЗаĐģĐ¸ŅˆĐ¸Ņ‚Đ¸", "keep_albums": "ЗаĐģĐ¸ŅˆĐ¸Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", "keep_albums_count": "ЗаĐģĐ¸ŅˆĐ°Ņ”Ņ‚ŅŒŅŅ: {count} {count, plural, one {аĐģŅŒĐąĐžĐŧ} few {аĐģŅŒĐąĐžĐŧи} many {аĐģŅŒĐąĐžĐŧŅ–Đ˛} other {аĐģŅŒĐąĐžĐŧŅ–Đ˛}}", @@ -1259,12 +1372,14 @@ "leave": "ПоĐēиĐŊŅƒŅ‚Đ¸", "leave_album": "ПоĐēиĐŊŅƒŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ", "lens_model": "МодĐĩĐģҌ Ой'Ņ”ĐēŅ‚Đ¸Đ˛Đ°", - "less": "МĐĩĐŊ҈Đĩ", "let_others_respond": "Đ”Đ°Ņ‚Đ¸ СĐŧĐžĐŗŅƒ Ņ–ĐŊŅˆĐ¸Đŧ Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´Đ°Ņ‚Đ¸", "level": "Đ Ņ–Đ˛ĐĩĐŊҌ", "library": "Đ‘Ņ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēа", "library_add_folder": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ ĐŋаĐŋĐē҃", "library_edit_folder": "Đ ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋаĐŋĐē҃", + "library_options": "Đ’Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", + "library_page_device_albums": "АĐģŅŒĐąĐžĐŧи ĐŊа ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", + "library_page_new_album": "Новий аĐģŅŒĐąĐžĐŧ", "library_page_sort_asset_count": "ĐšŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "library_page_sort_created": "Đ”Đ°Ņ‚Đ° ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", "library_page_sort_last_modified": "ĐžŅŅ‚Đ°ĐŊĐŊŅ СĐŧŅ–ĐŊа", @@ -1274,14 +1389,11 @@ "light_theme": "ПĐĩŅ€ĐĩĐŧĐēĐŊŅƒŅ‚Đ¸ ĐŊа ŅĐ˛Ņ–Ņ‚Đģ҃ Ņ‚ĐĩĐŧ҃", "like": "ĐŸĐžĐ´ĐžĐąĐ°Ņ”Ņ‚ŅŒŅŅ", "like_deleted": "ВĐŋОдОйаĐŊĐŊŅ видаĐģĐĩĐŊĐž", - "link": "ĐŸĐžŅĐ¸ĐģаĐŊĐŊŅ", "link_motion_video": "ĐŸŅ€Đ¸Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ Ņ€ŅƒŅ…ĐžĐŧĐĩ Đ˛Ņ–Đ´ĐĩĐž", "link_to_docs": "ДоĐēĐģадĐŊŅ–ŅˆĐĩ Đ´Đ¸Đ˛Ņ–Ņ‚ŅŒŅŅ в Đ´ĐžĐē҃ĐŧĐĩĐŊŅ‚Đ°Ņ†Ņ–Ņ—.", "link_to_oauth": "ĐŸŅ€Đ¸Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ Đ´Đž OAuth", "linked_oauth_account": "ĐŸŅ€Đ¸Ņ”Đ´ĐŊаĐŊиК ОйĐģŅ–ĐēОвиК СаĐŋĐ¸Ņ OAuth", "list": "ĐĄĐŋĐ¸ŅĐžĐē", - "live": "ЖивĐĩ", - "load_more": "ЗаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ҉Đĩ", "loading": "ЗаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "loading_search_results_failed": "НĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ Ņ€ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚Đ¸ ĐŋĐžŅˆŅƒĐē҃", "local": "На ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", @@ -1310,9 +1422,11 @@ "login": "Đ’Ņ…Ņ–Đ´", "login_disabled": "Đ’Ņ…Ņ–Đ´ виĐŧĐēĐŊĐĩĐŊĐž", "login_form_api_exception": "ПоĐŧиĐģĐēа API. ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Ņ‚Đĩ Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Ņ– ҁĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ СĐŊĐžĐ˛Ņƒ.", - "login_form_email_hint": "Đ˛Đ°ŅˆĐ°ĐŋĐžŅˆŅ‚Đ°@example.com", - "login_form_endpoint_hint": "http://ip-Đ˛Đ°ŅˆĐžĐŗĐž-ҁĐĩŅ€Đ˛ĐĩŅ€Đ°:ĐŋĐžŅ€Ņ‚", + "login_form_back_button_text": "Назад", + "login_form_email_hint": "youremail@email.com", + "login_form_endpoint_hint": "http://your-server-ip:port", "login_form_endpoint_url": "ĐĐ´Ņ€ĐĩŅĐ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", + "login_form_err_http": "ВĐēаĐļŅ–Ņ‚ŅŒ http:// айО https://", "login_form_err_invalid_email": "НĐĩĐ´Ņ–ĐšŅĐŊа ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊа Đ°Đ´Ņ€ĐĩŅĐ°", "login_form_err_invalid_url": "НĐĩĐ´Ņ–ĐšŅĐŊиК URL", "login_form_err_leading_whitespace": "ĐŸŅ€ĐžĐąŅ–Đģ ĐŊа ĐŋĐžŅ‡Đ°Ņ‚Đē҃", @@ -1322,9 +1436,10 @@ "login_form_failed_login": "НĐĩ вдаĐģĐžŅŅ Đ˛Đ˛Ņ–ĐšŅ‚Đ¸, ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Ņ‚Đĩ URL-Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, ĐĩĐģĐĩĐēŅ‚Ņ€ĐžĐŊĐŊ҃ ĐŋĐžŅˆŅ‚Ņƒ Ņ‚Đ° ĐŋĐ°Ņ€ĐžĐģҌ", "login_form_handshake_exception": "НĐĩ вдаĐģĐžŅŅ ŅƒŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ С'Ņ”Đ´ĐŊаĐŊĐŊŅ Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ. ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧĐē҃ ŅĐ°ĐŧĐžĐŋŅ–Đ´ĐŋĐ¸ŅĐ°ĐŊĐžĐŗĐž ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚Đ° в ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅŅ…, ŅĐēŅ‰Đž ви виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚Đĩ ŅĐ°ĐŧĐžĐŋŅ–Đ´ĐŋĐ¸ŅĐ°ĐŊиК ҁĐĩŅ€Ņ‚Đ¸Ņ„Ņ–ĐēĐ°Ņ‚.", "login_form_password_hint": "ĐŋĐ°Ņ€ĐžĐģҌ", - "login_form_server_empty": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ URL-Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°", - "login_form_server_error": "НĐĩ вдаĐģĐžŅŅ С'Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ŅŅ Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", - "login_has_been_disabled": "Đ’Ņ…Ņ–Đ´ ĐąŅƒĐģĐž виĐŧĐēĐŊĐĩĐŊĐž", + "login_form_save_login": "ЗаĐģĐ¸ŅˆĐ°Ņ‚Đ¸ŅŅ ҃ ŅĐ¸ŅŅ‚ĐĩĐŧŅ–", + "login_form_server_empty": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ URL-Đ°Đ´Ņ€Đĩҁ҃ ҁĐĩŅ€Đ˛ĐĩŅ€Đ°.", + "login_form_server_error": "НĐĩ вдаĐģĐžŅŅ С'Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ŅŅ Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ.", + "login_has_been_disabled": "Đ’Ņ…Ņ–Đ´ ĐąŅƒĐģĐž виĐŧĐēĐŊĐĩĐŊĐž.", "login_password_changed_error": "НĐĩ вдаĐģĐžŅŅ ĐžĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ", "login_password_changed_success": "ĐŸĐ°Ņ€ĐžĐģҌ ĐžĐŊОвĐģĐĩĐŊĐž", "logout_all_device_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Đ˛Đ¸ĐšŅ‚Đ¸ С ŅƒŅŅ–Ņ… ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—Đ˛?", @@ -1334,11 +1449,12 @@ "look": "Đ’Đ¸ĐŗĐģŅĐ´", "loop_videos": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ Đ˛Ņ–Đ´ĐĩĐž", "loop_videos_description": "ВĐŧиĐēĐ°Ņ‚Đ¸ Ņ†Đ¸ĐēĐģҖ҇ĐŊĐĩ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ Đ˛Ņ–Đ´ĐĩĐž ĐŋŅ–Đ´ Ņ‡Đ°Ņ Đ´ĐĩŅ‚Đ°ĐģҌĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ.", - "main_branch_warning": "Ви виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚Đĩ вĐĩŅ€ŅŅ–ŅŽ Đ´ĐģŅ Ņ€ĐžĐˇŅ€ĐžĐąĐŊиĐēŅ–Đ˛; ĐŊаĐŋĐžĐģĐĩĐŗĐģивО Ņ€ĐĩĐēĐžĐŧĐĩĐŊĐ´ŅƒŅ”ĐŧĐž виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ€ĐĩĐģŅ–ĐˇĐŊ҃ вĐĩŅ€ŅŅ–ŅŽ!", + "main_branch_warning": "Ви виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ”Ņ‚Đĩ вĐĩŅ€ŅŅ–ŅŽ Đ´ĐģŅ Ņ€ĐžĐˇŅ€ĐžĐąĐŊиĐēŅ–Đ˛; ĐŊĐ°ŅŅ‚Ņ–ĐšĐŊĐž Ņ€ĐĩĐēĐžĐŧĐĩĐŊĐ´ŅƒŅ”ĐŧĐž виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ€ĐĩĐģŅ–ĐˇĐŊ҃ вĐĩŅ€ŅŅ–ŅŽ!", "main_menu": "ГоĐģОвĐŊĐĩ ĐŧĐĩĐŊŅŽ", "maintenance_action_restore": "Đ’Ņ–Đ´ĐŊОвĐģĐĩĐŊĐŊŅ йаСи даĐŊĐ¸Ņ…", "maintenance_description": "Immich ĐŋĐĩŅ€ĐĩвĐĩĐ´ĐĩĐŊĐž в Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģŅƒĐŗĐžĐ˛ŅƒĐ˛Đ°ĐŊĐŊŅ.", "maintenance_end": "ЗавĐĩŅ€ŅˆĐ¸Ņ‚Đ¸ Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģŅƒĐŗĐžĐ˛ŅƒĐ˛Đ°ĐŊĐŊŅ", + "maintenance_end_error": "НĐĩ вдаĐģĐžŅŅ СавĐĩŅ€ŅˆĐ¸Ņ‚Đ¸ Ņ€ĐĩĐļиĐŧ ĐžĐąŅĐģŅƒĐŗĐžĐ˛ŅƒĐ˛Đ°ĐŊĐŊŅ.", "maintenance_logged_in_as": "ĐĐ°Ņ€Đ°ĐˇŅ– ви Đ˛Đ˛Ņ–ĐšŅˆĐģи ŅĐē {user}", "maintenance_restore_from_backup": "Đ’Ņ–Đ´ĐŊОвĐģĐĩĐŊĐŊŅ С Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžŅ— ĐēĐžĐŋŅ–Ņ—", "maintenance_restore_library": "Đ’Ņ–Đ´ĐŊĐžĐ˛Ņ–Ņ‚ŅŒ ŅĐ˛ĐžŅŽ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐē҃", @@ -1364,6 +1480,7 @@ "manage_media_access_settings": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", "manage_media_access_subtitle": "Đ”Đ°ĐšŅ‚Đĩ СĐŧĐžĐŗŅƒ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃ Immich ĐēĐĩŅ€ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧĐĩĐ´Ņ–Đ°Ņ„Đ°ĐšĐģаĐŧи Ņ‚Đ° ĐŋĐĩŅ€ĐĩĐŧŅ–Ņ‰ŅƒĐ˛Đ°Ņ‚Đ¸ Ņ—Ņ….", "manage_media_access_title": "Đ”ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐēĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŧĐĩĐ´Ņ–Đ°", + "manage_shared_links": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐŋŅ–ĐģҌĐŊиĐŧи ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅĐŧи", "manage_sharing_with_partners": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐŋŅ–ĐģҌĐŊиĐŧ Đ´ĐžŅŅ‚ŅƒĐŋĐžĐŧ Ņ–Đˇ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°Đŧи", "manage_the_app_settings": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅĐŧи ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", "manage_your_account": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ОйĐģŅ–ĐēОвиĐŧ СаĐŋĐ¸ŅĐžĐŧ", @@ -1371,11 +1488,13 @@ "manage_your_devices": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ Đ°Đ˛Ņ‚ĐžŅ€Đ¸ĐˇĐžĐ˛Đ°ĐŊиĐŧи ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅĐŧи", "manage_your_oauth_connection": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŋŅ–Đ´'Ņ”Đ´ĐŊаĐŊĐŊŅĐŧ OAuth", "map": "МаĐŋа", + "map_assets_in_bounds": "{count, plural, =0 {НĐĩĐŧĐ°Ņ” Ņ„ĐžŅ‚Đž ҃ Ņ†Ņ–Đš ĐŧҖҁ҆ĐĩĐ˛ĐžŅŅ‚Ņ–} one {# Ņ„ĐžŅ‚Đž} few {# Ņ„ĐžŅ‚Đž} many {# Ņ„ĐžŅ‚Đž} other {# Ņ„ĐžŅ‚Đž}}", "map_cannot_get_user_location": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", + "map_location_dialog_yes": "ĐĸаĐē", "map_location_picker_page_use_location": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ҆Đĩ ĐŧҖҁ҆Đĩ", "map_location_service_disabled_content": "ĐĄĐģ҃Đļйа виСĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ĐŧŅ–ŅŅ†Ņ ĐŧĐ°Ņ” ĐąŅƒŅ‚Đ¸ ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊа, Ņ‰ĐžĐą Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ С Đ˛Đ°ŅˆĐžĐŗĐž ĐŋĐžŅ‚ĐžŅ‡ĐŊĐžĐŗĐž ĐŧŅ–ŅŅ†Ņ. ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ—Ņ— ĐˇĐ°Ņ€Đ°Đˇ?", "map_location_service_disabled_title": "ĐĄĐģ҃Đļйа виСĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ ĐŧŅ–ŅŅ†Ņ виĐŧĐēĐŊĐĩĐŊа", - "map_marker_for_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐŧаĐŋŅ– Đ´ĐģŅ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ, СĐŊŅŅ‚ĐžĐŗĐž в {city}, {country}", + "map_marker_for_images": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐŧаĐŋŅ– Đ´ĐģŅ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ, СĐŊŅŅ‚Đ¸Ņ… ҃ {city}, {country}", "map_marker_with_image": "ĐœĐ°Ņ€ĐēĐĩŅ€ ĐŊа ĐŧаĐŋŅ– Ņ–Đˇ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅĐŧ", "map_no_location_permission_content": "ĐŸĐžŅ‚Ņ€Ņ–ĐąĐĩĐŊ Đ´ĐžĐˇĐ˛Ņ–Đģ, Ņ‰ĐžĐą ĐŋĐžĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ Ņ–Đˇ ĐŋĐžŅ‚ĐžŅ‡ĐŊĐžĐŗĐž ĐŧŅ–ŅŅ†Ņ. ĐĐ°Đ´Đ°Ņ‚Đ¸ ĐšĐžĐŗĐž ĐˇĐ°Ņ€Đ°Đˇ?", "map_no_location_permission_title": "Đ”ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐŧŅ–ŅŅ†Ņ ĐŊĐĩ ĐŊадаĐŊĐž", @@ -1385,46 +1504,17 @@ "map_settings_date_range_option_days": "За ĐžŅŅ‚Đ°ĐŊĐŊŅ– {days} Đ´ĐŊŅ–Đ˛", "map_settings_date_range_option_year": "За ĐžŅŅ‚Đ°ĐŊĐŊŅ–Đš ҀҖĐē", "map_settings_date_range_option_years": "МиĐŊ҃ĐģŅ– {years} Ņ€ĐžĐēи", + "map_settings_dialog_title": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŧаĐŋи", "map_settings_include_show_archived": "Đ’Ņ€Đ°Ņ…ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Đ°Ņ€Ņ…Ņ–Đ˛", "map_settings_include_show_partners": "Đ’Ņ€Đ°Ņ…ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Ņ–Đ˛", "map_settings_only_show_favorites": "Đ’Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°Ņ‚Đ¸ ĐģĐ¸ŅˆĐĩ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐĩ", "map_settings_theme_settings": "ĐĸĐĩĐŧа ĐŧаĐŋи", + "map_zoom_to_see_photos": "ЗĐŧĐĩĐŊŅˆŅ‚Đĩ ĐŧĐ°ŅŅˆŅ‚Đ°Đą, Ņ‰ĐžĐą ĐŋĐžĐąĐ°Ņ‡Đ¸Ņ‚Đ¸ Ņ„ĐžŅ‚Đž", "mark_all_as_read": "ПозĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ Đ˛ŅŅ– ŅĐē ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊŅ–", + "mark_as_read": "ПозĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ ŅĐē ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊĐĩ", "marked_all_as_read": "ПозĐŊĐ°Ņ‡ĐĩĐŊĐž Đ˛ŅŅ– ŅĐē ĐŋŅ€ĐžŅ‡Đ¸Ņ‚Đ°ĐŊŅ–", "matches": "Đ—ĐąŅ–ĐŗĐ¸", "matching_assets": "Đ’Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", - "media_chrome": { - "auto": "ĐĐ˛Ņ‚Đž", - "captions": "ĐĄŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "captions_off": "ВиĐŧĐēĐŊĐĩĐŊĐž", - "closed_captions": "ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "decode_error": "ПоĐŧиĐģĐēа Đ´ĐĩĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ", - "disable_captions": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "enable_captions": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ŅŅƒĐąŅ‚Đ¸Ņ‚Ņ€Đ¸", - "enter_fullscreen_mode": "ĐŖĐ˛Ņ–ĐšŅ‚Đ¸ в ĐŋОвĐŊĐžĐĩĐēŅ€Đ°ĐŊĐŊиК Ņ€ĐĩĐļиĐŧ", - "exit_fullscreen_mode": "Đ’Đ¸ĐšŅ‚Đ¸ С ĐŋОвĐŊĐžĐĩĐēŅ€Đ°ĐŊĐŊĐžĐŗĐž Ņ€ĐĩĐļиĐŧ҃", - "loop": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸", - "media_error_description": "ЧĐĩŅ€ĐĩС ĐŋĐžĐŧиĐģĐē҃ ĐŧĐĩĐ´Ņ–Đ° Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐąŅƒĐģĐž ĐŋĐĩŅ€ĐĩŅ€Đ˛Đ°ĐŊĐž. МоĐļĐģивО, Ņ„Đ°ĐšĐģ ĐŋĐžŅˆĐēОдĐļĐĩĐŊĐž, айО Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅƒĐˇĐĩŅ€ ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ” ҆ĐĩĐš Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "media_loading": "СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŧĐĩĐ´Ņ–Đ°", - "mute": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ ĐˇĐ˛ŅƒĐē", - "network_error": "МĐĩŅ€ĐĩĐļĐĩва ĐŋĐžĐŧиĐģĐēа", - "network_error_description": "ЧĐĩŅ€ĐĩС ĐŋĐžĐŧиĐģĐē҃ ĐŧĐĩŅ€ĐĩĐļŅ– ĐŊĐĩ вдаĐģĐžŅŅ СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐŧĐĩĐ´Ņ–Đ°.", - "not_supported_error": "ДĐļĐĩŅ€ĐĩĐģĐž ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ”Ņ‚ŅŒŅŅ", - "playback_rate": "ШвидĐēŅ–ŅŅ‚ŅŒ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", - "playback_rate_current": "ĐŋĐžŅ‚ĐžŅ‡ĐŊа ŅˆĐ˛Đ¸Đ´ĐēŅ–ŅŅ‚ŅŒ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", - "playback_rate_value": "ШвидĐēŅ–ŅŅ‚ŅŒ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ {playbackRate}", - "playback_time": "Ņ‡Đ°Ņ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", - "quality": "Đ¯ĐēŅ–ŅŅ‚ŅŒ", - "second": "ҁĐĩĐē҃ĐŊда", - "seconds": "ҁĐĩĐē҃ĐŊĐ´", - "time_value_of_total_time": "{currentTime} С {totalTime}", - "time_value_remaining": "СаĐģĐ¸ŅˆĐ¸ĐģĐžŅŅŒ {time}", - "unmute": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ĐˇĐ˛ŅƒĐē", - "unsupported_error_description": "ĐĄŅ‚Đ°ĐģĐ°ŅŅ ĐŋĐžĐŧиĐģĐēа: Đ´ĐļĐĩŅ€ĐĩĐģĐž ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ”Ņ‚ŅŒŅŅ. ĐĄĐĩŅ€Đ˛ĐĩŅ€ айО ĐŧĐĩŅ€ĐĩĐļа СаСĐŊаĐģи ĐˇĐąĐžŅŽ, айО Đ˛Đ°Ņˆ ĐąŅ€Đ°ŅƒĐˇĐĩŅ€ ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ” ҆ĐĩĐš Ņ„ĐžŅ€ĐŧĐ°Ņ‚.", - "video_not_loaded_unknown_time": "Đ˛Ņ–Đ´ĐĩĐž ĐŊĐĩ СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐž, Ņ‡Đ°Ņ ĐŊĐĩĐ˛Ņ–Đ´ĐžĐŧиК.", - "video_player": "Đ˛Ņ–Đ´ĐĩĐžĐŋŅ€ĐžĐŗŅ€Đ°Đ˛Đ°Ņ‡", - "volume": "ĐŗŅƒŅ‡ĐŊŅ–ŅŅ‚ŅŒ" - }, "media_type": "ĐĸиĐŋ ĐŧĐĩĐ´Ņ–Đ°", "memories": "ĐĄĐŋĐžĐŗĐ°Đ´Đ¸", "memories_all_caught_up": "ĐĻĐĩ Đ˛ŅĐĩ ĐŊа ŅŅŒĐžĐŗĐžĐ´ĐŊŅ–", @@ -1441,36 +1531,39 @@ "merge_people_prompt": "Ви Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Ой'Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ Ņ†Đ¸Ņ… ĐģŅŽĐ´ĐĩĐš? ĐĻŅ Đ´Ņ–Ņ ĐŊĐĩĐˇĐ˛ĐžŅ€ĐžŅ‚ĐŊа.", "merge_people_successfully": "Đ›ŅŽĐ´ĐĩĐš Ой'Ņ”Đ´ĐŊаĐŊĐž", "merged_people_count": "Об'Ņ”Đ´ĐŊаĐŊĐž {count, plural, one {# ĐģŅŽĐ´Đ¸ĐŊ҃} few {# ĐģŅŽĐ´Đ¸ĐŊи} many {# ĐģŅŽĐ´ĐĩĐš} other {# ĐģŅŽĐ´ĐĩĐš}}", - "minFaces": "ĐœŅ–ĐŊŅ–Đŧ҃Đŧ ОйĐģĐ¸Ņ‡", - "minFaces_description": "ĐœŅ–ĐŊŅ–ĐŧаĐģҌĐŊа ĐēŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ Ņ€ĐžĐˇĐŋŅ–ĐˇĐŊаĐŊĐ¸Ņ… ОйĐģĐ¸Ņ‡ Đ´ĐģŅ Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐžŅĐžĐąĐ¸", "minimize": "Đ—ĐŗĐžŅ€ĐŊŅƒŅ‚Đ¸", "minute": "ĐĨвиĐģиĐŊа", "minutes": "ĐĨвиĐģиĐŊи", + "mirror_horizontal": "По ĐŗĐžŅ€Đ¸ĐˇĐžĐŊŅ‚Đ°ĐģŅ–", + "mirror_vertical": "По вĐĩŅ€Ņ‚Đ¸ĐēаĐģŅ–", "missing": "Đ’Ņ–Đ´ŅŅƒŅ‚ĐŊŅ–", "mobile_app": "ĐœĐžĐąŅ–ĐģҌĐŊиК ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐžĐē", "mobile_app_download_onboarding_note": "ЗаваĐŊŅ‚Đ°ĐļŅ‚Đĩ ĐŧĐžĐąŅ–ĐģҌĐŊиК ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐžĐē ОдĐŊиĐŧ Ņ–Đˇ ĐŊавĐĩĐ´ĐĩĐŊĐ¸Ņ… ĐŊиĐļ҇Đĩ ҁĐŋĐžŅĐžĐąŅ–Đ˛", "model": "МодĐĩĐģҌ", - "modify_date": "Đ”Đ°Ņ‚Đ° СĐŧŅ–ĐŊи", "month": "ĐœŅ–ŅŅŅ†ŅŒ", + "monthly_title_text_date_format": "MMMM y", "more": "Đ‘Ņ–ĐģҌ҈Đĩ", - "motion": "Đ ŅƒŅ…ĐžĐŧĐĩ", "move": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸", + "move_down": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ вĐŊиС", "move_off_locked_folder": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ С ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи", "move_to": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đž", "move_to_device_trash": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đž ĐēĐžŅˆĐ¸Đēа ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ", "move_to_lock_folder_action_prompt": "{count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ дОдаĐŊĐž Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ дОдаĐŊĐž Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ дОдаĐŊĐž Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ дОдаĐŊĐž Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи}}", "move_to_locked_folder": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи", "move_to_locked_folder_confirmation": "ĐĻŅ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž ĐąŅƒĐ´Đĩ виĐģŅƒŅ‡ĐĩĐŊĐž С ŅƒŅŅ–Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛ Ņ– Ņ—Ņ… ĐŧĐžĐļĐŊа ĐąŅƒĐ´Đĩ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‚Đ¸ ĐģĐ¸ŅˆĐĩ в ĐžŅĐžĐąĐ¸ŅŅ‚Ņ–Đš ĐŋаĐŋ҆Җ", + "move_up": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ˛ĐŗĐžŅ€Ņƒ", + "moved_to_archive": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Đ´Đž Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ", + "moved_to_library": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} Đ´Đž ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", "moved_to_trash": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊĐž Đ´Đž ĐēĐžŅˆĐ¸Đēа", + "multiselect_grid_edit_date_time_err_read_only": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Ņ€ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Ņƒ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", + "multiselect_grid_edit_gps_err_read_only": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ Ņ€ĐĩĐ´Đ°ĐŗŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ, ĐŋŅ€ĐžĐŋŅƒŅ‰ĐĩĐŊĐž", "mute_memories": "ВиĐŧĐēĐŊŅƒŅ‚Đ¸ ҁĐŋĐžĐŗĐ°Đ´Đ¸", "my_albums": "ĐœĐžŅ— аĐģŅŒĐąĐžĐŧи", - "my_immich_description": "ĐĄĐēĐžĐŋŅ–ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŋĐžŅ‚ĐžŅ‡ĐŊ҃ ŅŅ‚ĐžŅ€Ņ–ĐŊĐē҃ ŅĐē ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ My Immich", - "my_immich_title": "ĐŸĐžŅĐ¸ĐģаĐŊĐŊŅ My Immich", "name": "ІĐŧ'Ņ", "name_or_nickname": "ІĐŧ'Ņ айО ĐŋҁĐĩвдОĐŊŅ–Đŧ", "name_required": "ІĐŧ'Ņ ОйОв'ŅĐˇĐēОвĐĩ", "navigate": "ĐĐ°Đ˛Ņ–ĐŗĐ°Ņ†Ņ–Ņ", - "navigate_to_time": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž ĐŧĐžĐŧĐĩĐŊŅ‚Ņƒ", + "navigate_to_time": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž Ņ‡Đ°ŅŅƒ", "network_requirement_photos_upload": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧĐžĐąŅ–ĐģҌĐŊŅ– даĐŊŅ– Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ Ņ„ĐžŅ‚Đž", "network_requirement_videos_upload": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧĐžĐąŅ–ĐģҌĐŊŅ– даĐŊŅ– Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ Đ˛Ņ–Đ´ĐĩĐž", "network_requirements": "ВиĐŧĐžĐŗĐ¸ Đ´Đž ĐŧĐĩŅ€ĐĩĐļŅ–", @@ -1480,11 +1573,12 @@ "never": "ĐŅ–ĐēĐžĐģи", "new_album": "Новий аĐģŅŒĐąĐžĐŧ", "new_api_key": "Новий ĐēĐģŅŽŅ‡ API", - "new_feature": "Нова Đ¤ŅƒĐŊĐēŅ†Ņ–Ņ", + "new_date_range": "Новий Đ´Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚", "new_password": "Новий ĐŋĐ°Ņ€ĐžĐģҌ", "new_person": "Нова ĐģŅŽĐ´Đ¸ĐŊа", "new_pin_code": "Новий PIN-ĐēОд", "new_pin_code_subtitle": "Ви вĐŋĐĩŅ€ŅˆĐĩ ĐžŅ‚Ņ€Đ¸ĐŧŅƒŅ”Ņ‚Đĩ Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž ĐžŅĐžĐąĐ¸ŅŅ‚ĐžŅ— ĐŋаĐŋĐēи. ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ PIN-ĐēОд Đ´ĐģŅ ĐąĐĩСĐŋĐĩ҇ĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋ҃ Đ´Đž ҆ҖҔҗ ŅŅ‚ĐžŅ€Ņ–ĐŊĐēи", + "new_timeline": "Нова Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ", "new_update": "НовĐĩ ĐžĐŊОвĐģĐĩĐŊĐŊŅ", "new_user_created": "ĐĄŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž ĐŊĐžĐ˛ĐžĐŗĐž ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "new_version_available": "ДОСĐĸĐŖĐŸĐĐ НОВА Đ’Đ•Đ ĐĄĐ†Đ¯", @@ -1492,6 +1586,7 @@ "next": "ДаĐģŅ–", "next_memory": "ĐĐ°ŅŅ‚ŅƒĐŋĐŊиК ҁĐŋĐžĐŗĐ°Đ´", "no": "ĐŅ–", + "no_actions_added": "Đ”Ņ–Đš ҉Đĩ ĐŊĐĩ дОдаĐŊĐž", "no_albums_found": "АĐģŅŒĐąĐžĐŧи ĐŊĐĩ СĐŊаКдĐĩĐŊĐž", "no_albums_message": "ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ, Ņ‰ĐžĐą ҃ĐŋĐžŅ€ŅĐ´ĐēŅƒĐ˛Đ°Ņ‚Đ¸ ŅĐ˛ĐžŅ— Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", "no_albums_with_name_yet": "ĐĄŅ…ĐžĐļĐĩ, ҃ Đ˛Đ°Ņ ҉Đĩ ĐŊĐĩĐŧĐ°Ņ” аĐģŅŒĐąĐžĐŧŅ–Đ˛ Ņ–Đˇ Ņ‚Đ°ĐēĐžŅŽ ĐŊĐ°ĐˇĐ˛ĐžŅŽ.", @@ -1502,13 +1597,16 @@ "no_cast_devices_found": "ĐŸŅ€Đ¸ŅŅ‚Ņ€ĐžŅ— Đ´ĐģŅ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ–Ņ— ĐŊĐĩ СĐŊаКдĐĩĐŊĐž", "no_checksum_local": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊа ҁ҃Đŧа ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊа — ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "no_checksum_remote": "КоĐŊŅ‚Ņ€ĐžĐģҌĐŊа ҁ҃Đŧа ĐŊĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊа — ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ ĐžŅ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ Đ˛Ņ–Đ´Đ´Đ°ĐģĐĩĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", + "no_configuration_needed": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŊĐĩ ĐŋĐžŅ‚Ņ€Ņ–ĐąĐŊĐĩ", "no_devices": "НĐĩĐŧĐ°Ņ” Đ°Đ˛Ņ‚ĐžŅ€Đ¸ĐˇĐžĐ˛Đ°ĐŊĐ¸Ņ… ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—Đ˛", "no_duplicates_found": "Đ”ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ–Đ˛ ĐŊĐĩ Đ˛Đ¸ŅĐ˛ĐģĐĩĐŊĐž.", "no_exif_info_available": "Đ’Ņ–Đ´ŅŅƒŅ‚ĐŊŅ Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž Exif", "no_explore_results_message": "ВиваĐŊŅ‚Đ°ĐļŅƒĐšŅ‚Đĩ ĐąŅ–ĐģҌ҈Đĩ Ņ„ĐžŅ‚Đž, Ņ‰ĐžĐą Đ´ĐžŅĐģŅ–Đ´Đ¸Ņ‚Đ¸ ŅĐ˛ĐžŅŽ ĐēĐžĐģĐĩĐēŅ†Ņ–ŅŽ.", "no_favorites_message": "Đ”ĐžĐ´Đ°Đ˛Đ°ĐšŅ‚Đĩ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž ҃ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐĩ, Ņ‰ĐžĐą ŅˆĐ˛Đ¸Đ´ĐēĐž СĐŊĐ°Ņ…ĐžĐ´Đ¸Ņ‚Đ¸ ĐŊаКĐēŅ€Đ°Ņ‰Ņ–", + "no_filters_added": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€Đ¸ ҉Đĩ ĐŊĐĩ дОдаĐŊĐž", "no_libraries_message": "ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ СОвĐŊŅ–ŅˆĐŊŅŽ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐē҃ Đ´ĐģŅ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ Ņ„ĐžŅ‚Đž Ņ– Đ˛Ņ–Đ´ĐĩĐž", "no_local_assets_found": "З Ņ†Ņ–Ņ”ŅŽ ĐēĐžĐŊŅ‚Ņ€ĐžĐģҌĐŊĐžŅŽ ҁ҃ĐŧĐžŅŽ ĐŊĐĩ СĐŊаКдĐĩĐŊĐž ĐģĐžĐēаĐģҌĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", + "no_location_set": "ĐœŅ–ŅŅ†Đĩ ĐŊĐĩ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐž", "no_locked_photos_message": "Đ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž в ĐžŅĐžĐąĐ¸ŅŅ‚Ņ–Đš ĐŋаĐŋ҆Җ ĐŋŅ€Đ¸Ņ…ĐžĐ˛Đ°ĐŊŅ– Đš ĐŊĐĩ Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°ŅŽŅ‚ŅŒŅŅ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ Ņ‡Đ¸ ĐŋĐžŅˆŅƒĐē҃ ҃ Đ˛Đ°ŅˆŅ–Đš ĐąŅ–ĐąĐģŅ–ĐžŅ‚Đĩ҆Җ.", "no_name": "БĐĩС Ņ–ĐŧĐĩĐŊŅ–", "no_notifications": "НĐĩĐŧĐ°Ņ” ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊҌ", @@ -1518,7 +1616,7 @@ "no_results": "НĐĩĐŧĐ°Ņ” Ņ€ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚Ņ–Đ˛", "no_results_description": "ĐĄĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ŅĐ¸ĐŊĐžĐŊŅ–Đŧ айО ĐąŅ–ĐģҌ҈ ĐˇĐ°ĐŗĐ°ĐģҌĐŊĐĩ ĐēĐģŅŽŅ‡ĐžĐ˛Đĩ ҁĐģОвО", "no_shared_albums_message": "ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ аĐģŅŒĐąĐžĐŧ, Ņ‰ĐžĐą Đ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž С ĐģŅŽĐ´ŅŒĐŧи ҃ Đ˛Đ°ŅˆŅ–Đš ĐŧĐĩŅ€ĐĩĐļŅ–", - "no_steps": "ĐšŅ€ĐžĐēи ҉Đĩ ĐŊĐĩ дОдаĐŊĐž", + "no_uploads_in_progress": "НĐĩĐŧĐ°Ņ” аĐēŅ‚Đ¸Đ˛ĐŊĐ¸Ņ… виваĐŊŅ‚Đ°ĐļĐĩĐŊҌ", "none": "ЖодĐĩĐŊ", "not_allowed": "НĐĩ дОСвОĐģĐĩĐŊĐž", "not_available": "НĐĩĐŧĐ°Ņ” даĐŊĐ¸Ņ…", @@ -1526,10 +1624,6 @@ "not_selected": "НĐĩ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž", "notes": "ĐĐžŅ‚Đ°Ņ‚Đēи", "nothing_here_yet": "ĐĸŅƒŅ‚ ҉Đĩ ĐŊŅ–Ņ‡ĐžĐŗĐž ĐŊĐĩĐŧĐ°Ņ”", - "notification_backup_reliability": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ, Ņ‰ĐžĐą ĐŋĐžĐēŅ€Đ°Ņ‰Đ¸Ņ‚Đ¸ ĐŊĐ°Đ´Ņ–ĐšĐŊŅ–ŅŅ‚ŅŒ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", - "notification_enabled_list_tile_content": "Immich виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅ” ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ Đ´ĐģŅ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ. КĐĩŅ€ŅƒĐšŅ‚Đĩ ĐŊиĐŧи в ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅŅ… ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ.", - "notification_enabled_list_tile_open_button": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", - "notification_enabled_list_tile_title": "ĐĄĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž", "notification_permission_dialog_content": "ЊОй ŅƒĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ, ĐŋĐĩŅ€ĐĩĐšĐ´Ņ–Ņ‚ŅŒ Đ´Đž ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊҌ Ņ– ĐŊĐ°Đ´Đ°ĐšŅ‚Đĩ Đ´ĐžĐˇĐ˛Ņ–Đģ.", "notification_permission_list_tile_content": "ĐĐ°Đ´Đ°ĐšŅ‚Đĩ Đ´ĐžĐˇĐ˛Ņ–Đģ Đ´ĐģŅ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊҌ.", "notification_permission_list_tile_enable_button": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅƒŅ‚Đ¸ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊĐŊŅ", @@ -1541,8 +1635,6 @@ "obtainium_configurator": "КоĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ‚ĐžŅ€ Obtainium", "obtainium_configurator_instructions": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐšŅ‚Đĩ Obtainium Đ´ĐģŅ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐŊŅ Ņ‚Đ° ĐžĐŊОвĐģĐĩĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃ Android ĐąĐĩСĐŋĐžŅĐĩŅ€ĐĩĐ´ĐŊŅŒĐž С Ņ€ĐĩĐģŅ–ĐˇŅƒ Immich ĐŊа GitHub. ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ ĐēĐģŅŽŅ‡ API Ņ‚Đ° вийĐĩŅ€Ņ–Ņ‚ŅŒ Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚, Ņ‰ĐžĐą ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ ĐŊа ĐēĐžĐŊŅ„Ņ–ĐŗŅƒŅ€Đ°Ņ†Ņ–ŅŽ Obtainium", "ocr": "OCR", - "ocr_body": "Immich Ņ‚ĐĩĐŋĐĩŅ€ Ņ‡Đ¸Ņ‚Đ°Ņ” Ņ‚ĐĩĐēҁ҂ Đ˛ŅĐĩŅ€ĐĩдиĐŊŅ– Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚Đž, Ņ‰ĐžĐą ви ĐŧĐžĐŗĐģи ҈҃ĐēĐ°Ņ‚Đ¸ Ņ—Ņ… ĐŋĐž Ņ‚ĐžĐŧ҃, Ņ‰Đž Ņ‚Đ°Đŧ ĐŊаĐŋĐ¸ŅĐ°ĐŊĐž.", - "ocr_title": "ĐŸĐžŅˆŅƒĐēОвиК Ņ‚ĐĩĐēҁ҂ ҃ Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Ņ–ŅŅ…", "official_immich_resources": "ĐžŅ„Ņ–Ņ†Ņ–ĐšĐŊŅ– Ņ€ĐĩŅŅƒŅ€ŅĐ¸ Immich", "offline": "НĐĩĐ´ĐžŅŅ‚ŅƒĐŋĐŊиК", "offset": "Đ—ŅŅƒĐ˛", @@ -1561,8 +1653,6 @@ "open": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸", "open_calendar": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ ĐēаĐģĐĩĐŊĐ´Đ°Ņ€", "open_in_browser": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ в ĐąŅ€Đ°ŅƒĐˇĐĩҀҖ", - "open_in_immich_body": "Đ’ŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ Immich ŅĐē Đ˛Đ°ŅˆŅƒ ĐŗĐ°ĐģĐĩŅ€ĐĩŅŽ в Android Ņ‰ĐžĐą Đ˛Ņ–Đ´ĐēŅ€Đ¸Đ˛Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž ĐˇŅ€Đ°ĐˇŅƒ С Đ´ĐžĐ´Đ°Ņ‚Đē҃.", - "open_in_immich_title": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ Ņ„ĐžŅ‚Đž в Immich", "open_in_map_view": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ ĐŊа ĐŧаĐŋŅ–", "open_in_openstreetmap": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ в OpenStreetMap", "open_the_search_filters": "Đ’Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸ ҄ҖĐģŅŒŅ‚Ņ€Đ¸ ĐŋĐžŅˆŅƒĐē҃", @@ -1571,10 +1661,10 @@ "organize_into_albums": "ĐŖĐŋĐžŅ€ŅĐ´ĐēŅƒĐ˛Đ°Ņ‚Đ¸ в аĐģŅŒĐąĐžĐŧи", "organize_into_albums_description": "ПоĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ ĐŊĐ°ŅĐ˛ĐŊŅ– Ņ„ĐžŅ‚Đž в аĐģŅŒĐąĐžĐŧи Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊĐž Đ´Đž ĐŋĐžŅ‚ĐžŅ‡ĐŊĐ¸Ņ… ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊҌ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ—", "organize_your_library": "ĐŖĐŋĐžŅ€ŅĐ´ĐēŅƒĐšŅ‚Đĩ ŅĐ˛ĐžŅŽ ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐē҃", - "orientation": "ĐžŅ€Ņ–Ņ”ĐŊŅ‚Đ°Ņ†Ņ–Ņ", "original": "ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģ", "other": "ІĐŊ҈Đĩ", "other_devices": "ІĐŊŅˆŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", + "other_entities": "ІĐŊŅˆŅ– Ой'Ņ”ĐēŅ‚Đ¸", "other_variables": "ІĐŊŅˆŅ– СĐŧŅ–ĐŊĐŊŅ–", "owned": "ВĐģĐ°ŅĐŊŅ–", "owner": "ВĐģĐ°ŅĐŊиĐē", @@ -1584,9 +1674,12 @@ "partner_can_access_assets": "ĐŖŅŅ– Đ˛Đ°ŅˆŅ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž, ĐžĐēҀҖĐŧ Ņ‚Đ¸Ņ…, Ņ‰Đž в Đ°Ņ€Ņ…Ņ–Đ˛Ņ– Ņ‚Đ° ĐēĐžŅˆĐ¸Đē҃", "partner_can_access_location": "ĐœŅ–ŅŅ†Đĩ, Đ´Đĩ ĐˇŅ€ĐžĐąĐģĐĩĐŊĐž Đ˛Đ°ŅˆŅ– Ņ„ĐžŅ‚Đž", "partner_list_user_photos": "Đ¤ĐžŅ‚Đž {user}", + "partner_list_view_all": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ Đ˛ŅĐĩ", "partner_page_empty_message": "Ви ҉Đĩ ĐŊĐĩ ĐŋĐžĐ´Ņ–ĐģиĐģĐ¸ŅŅ Ņ„ĐžŅ‚Đž С ĐļОдĐŊиĐŧ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€ĐžĐŧ.", "partner_page_no_more_users": "Đ‘Ņ–ĐģҌ҈Đĩ ĐŊĐĩĐŧĐ°Ņ” ĐēĐžĐŗĐž Đ´ĐžĐ´Đ°Ņ‚Đ¸", + "partner_page_partner_add_failed": "НĐĩ вдаĐģĐžŅŅ Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", "partner_page_select_partner": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°", + "partner_page_shared_to_title": "Đ”ĐžŅŅ‚ŅƒĐŋ ĐŊадаĐŊĐž", "partner_page_stop_sharing_content": "{partner} ĐąŅ–ĐģҌ҈Đĩ ĐŊĐĩ ĐŧĐ°Ņ‚Đ¸ĐŧĐĩ Đ´ĐžŅŅ‚ŅƒĐŋ҃ Đ´Đž Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚Đž.", "partner_sharing": "ĐĄĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ Ņ–Đˇ ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ°Đŧи", "partners": "ĐŸĐ°Ņ€Ņ‚ĐŊĐĩŅ€Đ¸", @@ -1608,6 +1701,8 @@ "people": "Đ›ŅŽĐ´Đ¸", "people_edits_count": "Đ’Ņ–Đ´Ņ€ĐĩĐ´Đ°ĐŗĐžĐ˛Đ°ĐŊĐž {count, plural, one {# ĐģŅŽĐ´Đ¸ĐŊ҃} few {# ĐģŅŽĐ´Đ¸ĐŊи} many {# ĐģŅŽĐ´ĐĩĐš} other {# ĐģŅŽĐ´ĐĩĐš}}", "people_feature_description": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´ Ņ„ĐžŅ‚Đž Ņ– Đ˛Ņ–Đ´ĐĩĐž, ĐˇĐŗŅ€ŅƒĐŋОваĐŊĐ¸Ņ… Са ĐģŅŽĐ´ŅŒĐŧи", + "people_selected": "{count, plural, one {Đ’Đ¸ĐąŅ€Đ°ĐŊĐž # ĐģŅŽĐ´Đ¸ĐŊ҃} few {Đ’Đ¸ĐąŅ€Đ°ĐŊĐž # ĐģŅŽĐ´Đ¸ĐŊи} many {Đ’Đ¸ĐąŅ€Đ°ĐŊĐž # ĐģŅŽĐ´ĐĩĐš} other {Đ’Đ¸ĐąŅ€Đ°ĐŊĐž # ĐģŅŽĐ´ĐĩĐš}}", + "people_sidebar_description": "Đ’Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ ĐŊа ĐģŅŽĐ´ĐĩĐš ҃ ĐąŅ–Ņ‡ĐŊŅ–Đš ĐŋаĐŊĐĩĐģŅ–", "permanent_deletion_warning": "ПоĐŋĐĩŅ€ĐĩĐ´ĐļĐĩĐŊĐŊŅ ĐŋŅ€Đž ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐĩ видаĐģĐĩĐŊĐŊŅ", "permanent_deletion_warning_setting_description": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐžĐŋĐĩŅ€ĐĩĐ´ĐļĐĩĐŊĐŊŅ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐžĐŗĐž видаĐģĐĩĐŊĐŊŅ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "permanently_delete": "ВидаĐģĐ¸Ņ‚Đ¸ ĐŊаСавĐļди", @@ -1617,6 +1712,14 @@ "permanently_deleted_assets_count": "ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž видаĐģĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "permission": "Đ”ĐžĐˇĐ˛Ņ–Đģ", "permission_empty": "ДозвоĐģи ĐŊĐĩ ĐŧĐ°ŅŽŅ‚ŅŒ ĐąŅƒŅ‚Đ¸ ĐŋĐžŅ€ĐžĐļĐŊŅ–Đŧи", + "permission_onboarding_back": "Назад", + "permission_onboarding_continue_anyway": "Đ’ŅĐĩ ОдĐŊĐž ĐŋŅ€ĐžĐ´ĐžĐ˛ĐļĐ¸Ņ‚Đ¸", + "permission_onboarding_get_started": "РОСĐŋĐžŅ‡Đ°Ņ‚Đ¸", + "permission_onboarding_go_to_settings": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊҌ", + "permission_onboarding_permission_denied": "Đ”ĐžŅŅ‚ŅƒĐŋ ĐˇĐ°ĐąĐžŅ€ĐžĐŊĐĩĐŊĐž. ЊОй виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Immich, ĐŊĐ°Đ´Đ°ĐšŅ‚Đĩ дОСвОĐģи Đ´Đž ÂĢĐ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐžÂģ в ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅŅ….", + "permission_onboarding_permission_granted": "Đ”ĐžŅŅ‚ŅƒĐŋ ĐŊадаĐŊĐž! Đ’ŅĐĩ ĐŗĐžŅ‚ĐžĐ˛Đž.", + "permission_onboarding_permission_limited": "Đ”ĐžŅŅ‚ŅƒĐŋ ОйĐŧĐĩĐļĐĩĐŊĐž. ЊОй Đ´Đ°Ņ‚Đ¸ СĐŧĐžĐŗŅƒ Immich ŅŅ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊŅ– ĐēĐžĐŋŅ–Ņ— Ņ‚Đ° ĐēĐĩŅ€ŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛ŅŅ–Ņ”ŅŽ ĐŗĐ°ĐģĐĩŅ€ĐĩŅ”ŅŽ, ĐŊĐ°Đ´Đ°ĐšŅ‚Đĩ дОСвОĐģи ĐŊа Ņ„ĐžŅ‚Đž Đš Đ˛Ņ–Đ´ĐĩĐž в ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅŅ….", + "permission_onboarding_request": "Đ—Đ°ŅŅ‚ĐžŅŅƒĐŊĐē҃ Immich ĐŋĐžŅ‚Ņ€Ņ–ĐąĐĩĐŊ Đ´ĐžĐˇĐ˛Ņ–Đģ Đ´ĐģŅ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž.", "person": "Đ›ŅŽĐ´Đ¸ĐŊа", "person_age_months": "Đ’Ņ–Đē: {months, plural, one {# ĐŧŅ–ŅŅŅ†ŅŒ} few {# ĐŧŅ–ŅŅŅ†Ņ–} many {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛} other {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛}}", "person_age_year_months": "Đ’Ņ–Đē: 1 ҀҖĐē, {months, plural, one {# ĐŧŅ–ŅŅŅ†ŅŒ} few {# ĐŧŅ–ŅŅŅ†Ņ–} many {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛} other {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛}}", @@ -1624,10 +1727,13 @@ "person_birthdate": "Đ”Đ°Ņ‚Đ° ĐŊĐ°Ņ€ĐžĐ´ĐļĐĩĐŊĐŊŅ: {date}", "person_hidden": "{name}{hidden, select, true { (ĐŋŅ€Đ¸Ņ…ĐžĐ˛Đ°ĐŊĐž)} other {}}", "person_recognized": "Đ›ŅŽĐ´Đ¸ĐŊ҃ Ņ€ĐžĐˇĐŋŅ–ĐˇĐŊаĐŊĐž", + "person_selected": "Đ›ŅŽĐ´Đ¸ĐŊ҃ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž", "photo_shared_all_users": "ĐĄŅ…ĐžĐļĐĩ, ви вĐļĐĩ ĐŋĐžĐ´Ņ–ĐģиĐģĐ¸ŅŅ Ņ„ĐžŅ‚Đž С ŅƒŅŅ–Đŧа ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°Đŧи, айО ĐŊĐĩĐŧĐ°Ņ” С ĐēиĐŧ Đ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ.", "photos": "Đ¤ĐžŅ‚Đž", "photos_and_videos": "Đ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", + "photos_count": "{count, plural, one {{count, number} Ņ„ĐžŅ‚Đž} few {{count, number} Ņ„ĐžŅ‚Đž} many {{count, number} Ņ„ĐžŅ‚Đž} other {{count, number} Ņ„ĐžŅ‚Đž}}", "photos_from_previous_years": "Đ¤ĐžŅ‚Đž ĐŧиĐŊ҃ĐģĐ¸Ņ… Ņ€ĐžĐēŅ–Đ˛", + "photos_only": "Đ›Đ¸ŅˆĐĩ Ņ„ĐžŅ‚Đž", "pick_a_location": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐŧҖҁ҆Đĩ", "pick_custom_range": "Đ”ĐžĐ˛Ņ–ĐģҌĐŊиК Đ´Ņ–Đ°ĐŋаСОĐŊ", "pick_date_range": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Đ´Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚", @@ -1646,8 +1752,6 @@ "play_original_video_setting_description": "ĐĐ°Đ´Đ°Đ˛Đ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐ˛Đ°ĐŗŅƒ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅŽ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊĐ¸Ņ… Đ˛Ņ–Đ´ĐĩĐž, а ĐŊĐĩ ĐŋĐĩŅ€ĐĩĐēОдОваĐŊĐ¸Ņ…. Đ¯ĐēŅ‰Đž ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊĐĩҁ҃ĐŧҖҁĐŊиК, Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐŧĐžĐļĐĩ ĐąŅƒŅ‚Đ¸ ĐŊĐĩĐēĐžŅ€ĐĩĐēŅ‚ĐŊиĐŧ.", "play_transcoded_video": "Đ’Ņ–Đ´Ņ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩĐēОдОваĐŊĐĩ Đ˛Ņ–Đ´ĐĩĐž", "please_auth_to_access": "ĐĐ˛Ņ‚ĐĩĐŊŅ‚Đ¸Ņ„Ņ–ĐēŅƒĐšŅ‚ĐĩŅŅ Đ´ĐģŅ Đ´ĐžŅŅ‚ŅƒĐŋ҃", - "plugin_method_filter_type": "Đ¤Ņ–ĐģŅŒŅ‚Ņ€", - "plugin_method_filter_type_description": "ĐĻĐĩĐš ĐŧĐĩŅ‚ĐžĐ´ ĐŧĐžĐļĐĩ ҄ҖĐģŅŒŅ‚Ņ€ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐžĐ´Ņ–Ņ— Ņ‚Đ° ҃ĐŧОвĐŊĐž СаĐŋĐžĐąŅ–ĐŗĐ°Ņ‚Đ¸ виĐēĐžĐŊаĐŊĐŊŅŽ ĐŋОдаĐģŅŒŅˆĐ¸Ņ… ĐēŅ€ĐžĐēŅ–Đ˛", "port": "ĐŸĐžŅ€Ņ‚", "preferences_settings_subtitle": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ҃ĐŋОдОйаĐŊĐŊŅĐŧи ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", "preferences_settings_title": "ĐŖĐŋОдОйаĐŊĐŊŅ", @@ -1664,11 +1768,11 @@ "privacy": "КоĐŊŅ„Ņ–Đ´ĐĩĐŊŅ†Ņ–ĐšĐŊŅ–ŅŅ‚ŅŒ", "profile": "ĐŸŅ€ĐžŅ„Ņ–ĐģҌ", "profile_drawer_app_logs": "Đ–ŅƒŅ€ĐŊаĐģ", + "profile_drawer_client_server_up_to_date": "КĐģŅ–Ņ”ĐŊŅ‚ Ņ‚Đ° ҁĐĩŅ€Đ˛ĐĩŅ€ — аĐēŅ‚ŅƒĐ°ĐģҌĐŊŅ–", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "Đ ĐĩĐļиĐŧ ĐģĐ¸ŅˆĐĩ Đ´ĐģŅ Ņ‡Đ¸Ņ‚Đ°ĐŊĐŊŅ ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž. ЊОй Đ˛Đ¸ĐšŅ‚Đ¸, ŅƒŅ‚Ņ€Đ¸ĐŧŅƒĐšŅ‚Đĩ СĐŊĐ°Ņ‡ĐžĐē Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ°.", "profile_image_of_user": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ {user}", "profile_picture_set": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŋŅ€ĐžŅ„Ņ–ĐģŅŽ ŅƒŅŅ‚Đ°ĐŊОвĐģĐĩĐŊĐž.", - "projection_type": "ĐĸиĐŋ ĐŋŅ€ĐžŅ”Đē҆Җҗ", "public_album": "ĐŸŅƒĐąĐģҖ҇ĐŊиК аĐģŅŒĐąĐžĐŧ", "public_share": "ĐŸŅƒĐąĐģҖ҇ĐŊиК ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ", "purchase_account_info": "ĐŸŅ€Đ¸Ņ…Đ¸ĐģҌĐŊиĐē", @@ -1687,6 +1791,7 @@ "purchase_individual_description_2": "ĐĄŅ‚Đ°Ņ‚ŅƒŅ ĐŋŅ€Đ¸Ņ…Đ¸ĐģҌĐŊиĐēа", "purchase_individual_title": "ІĐŊĐ´Đ¸Đ˛Ņ–Đ´ŅƒĐ°ĐģҌĐŊиК", "purchase_input_suggestion": "ĐœĐ°Ņ”Ņ‚Đĩ ĐēĐģŅŽŅ‡ ĐŋŅ€ĐžĐ´ŅƒĐēŅ‚Ņƒ? ВвĐĩĐ´Ņ–Ņ‚ŅŒ ĐēĐģŅŽŅ‡ ĐŊиĐļ҇Đĩ", + "purchase_license_subtitle": "ĐšŅƒĐŋŅ–Ņ‚ŅŒ Immich, Ņ‰ĐžĐą ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧĐ°Ņ‚Đ¸ ĐŋОдаĐģŅŒŅˆĐ¸Đš Ņ€ĐžĐˇĐ˛Đ¸Ņ‚ĐžĐē ĐŋŅ€ĐžŅ”ĐēŅ‚Ņƒ", "purchase_lifetime_description": "БĐĩĐˇŅŅ‚Ņ€ĐžĐēОва Đē҃ĐŋŅ–Đ˛ĐģŅ", "purchase_option_title": "ВАРІАНĐĸИ ĐšĐŖĐŸĐ†Đ’Đ›Đ†", "purchase_panel_info_1": "Đ ĐžĐˇŅ€ĐžĐąĐēа Immich ĐŋĐžŅ‚Ņ€ĐĩĐąŅƒŅ” ĐąĐ°ĐŗĐ°Ņ‚Đž Ņ‡Đ°ŅŅƒ Ņ‚Đ° ĐˇŅƒŅĐ¸ĐģҌ. Ми ĐŧĐ°Ņ”ĐŧĐž ŅˆŅ‚Đ°Ņ‚ĐŊĐ¸Ņ… Ņ–ĐŊĐļĐĩĐŊĐĩŅ€Ņ–Đ˛, ŅĐēŅ– ĐŋŅ€Đ°Ņ†ŅŽŅŽŅ‚ŅŒ ĐŊад Ņ‚Đ¸Đŧ, Ņ‰ĐžĐą ĐˇŅ€ĐžĐąĐ¸Ņ‚Đ¸ ĐšĐžĐŗĐž ŅĐēĐžĐŧĐžĐŗĐ° ĐēŅ€Đ°Ņ‰Đ¸Đŧ. ĐĐ°ŅˆĐ° ĐŧŅ–ŅŅ–Ņ — ĐˇŅ€ĐžĐąĐ¸Ņ‚Đ¸ ĐŋŅ€ĐžĐŗŅ€Đ°ĐŧĐŊĐĩ СайĐĩСĐŋĐĩ҇ĐĩĐŊĐŊŅ С Đ˛Ņ–Đ´ĐēŅ€Đ¸Ņ‚Đ¸Đŧ ĐēОдОĐŧ Ņ‚Đ° ĐĩŅ‚Đ¸Ņ‡ĐŊŅ– ĐąŅ–ĐˇĐŊĐĩҁ-ĐŋŅ€Đ°ĐēŅ‚Đ¸Đēи ŅŅ‚Ņ–ĐšĐēиĐŧ Đ´ĐļĐĩŅ€ĐĩĐģĐžĐŧ Đ´ĐžŅ…ĐžĐ´Ņƒ Đ´ĐģŅ Ņ€ĐžĐˇŅ€ĐžĐąĐŊиĐēŅ–Đ˛ Ņ– ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ĐĩĐēĐžŅĐ¸ŅŅ‚ĐĩĐŧ҃, Ņ‰Đž ĐŋОваĐļĐ°Ņ” ĐēĐžĐŊŅ„Ņ–Đ´ĐĩĐŊŅ†Ņ–ĐšĐŊŅ–ŅŅ‚ŅŒ, С Ņ€ĐĩаĐģҌĐŊиĐŧи аĐģŅŒŅ‚ĐĩŅ€ĐŊĐ°Ņ‚Đ¸Đ˛Đ°Đŧи ĐĩĐēҁĐŋĐģŅƒĐ°Ņ‚Đ°Ņ‚ĐžŅ€ŅŅŒĐēиĐŧ Ņ…ĐŧĐ°Ņ€ĐŊиĐŧ ҁĐģ҃ĐļйаĐŧ.", @@ -1719,19 +1824,19 @@ "reassigned_assets_to_new_person": "ПĐĩŅ€ĐĩĐŋŅ€Đ¸ĐˇĐŊĐ°Ņ‡ĐĩĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} ĐŊĐžĐ˛Ņ–Đš ĐģŅŽĐ´Đ¸ĐŊŅ–", "reassing_hint": "ĐŸŅ€Đ¸ĐˇĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ Đ˛Đ¸ĐąŅ€Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐŊĐ°ŅĐ˛ĐŊŅ–Đš ĐģŅŽĐ´Đ¸ĐŊŅ–", "recent": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊĐž", + "recent_albums": "ĐžŅŅ‚Đ°ĐŊĐŊŅ– аĐģŅŒĐąĐžĐŧи", "recent_searches": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊŅ– ĐŋĐžŅˆŅƒĐēĐžĐ˛Ņ– СаĐŋĐ¸Ņ‚Đ¸", "recently_added": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊĐž дОдаĐŊŅ–", - "recently_added_body": "ĐĄŅ‚Ņ€Đ¸ĐąĐŊŅ–Ņ‚ŅŒ ĐˇŅ€Đ°ĐˇŅƒ Đ´Đž Đ˛ŅŅŒĐžĐŗĐž Ņ‰Đž ви дОдаĐģи ĐžŅŅ‚Đ°ĐŊĐŊŅ–Đŧ Ņ‡Đ°ŅĐžĐŧ ĐŊа Đ˛Đ¸Đ´Ņ–ĐģĐĩĐŊŅ–Đš ŅŅ‚ĐžŅ€Ņ–ĐŊ҆Җ.", - "recently_added_description": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°ĐšŅ‚Đĩ Đ˛Đ°ŅˆŅ– аĐēŅ‚Đ¸Đ˛Đ¸ ŅĐžŅ€Ņ‚ĐžĐ˛Đ°ĐŊиĐŧи Са Ņ‚Đ¸Đŧ ĐēĐžĐģи вОĐŊи ĐąŅƒĐģи СаваĐŊŅ‚Đ°ĐļĐĩĐŊŅ– в Immich", "recently_added_page_title": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊĐž дОдаĐŊŅ–", - "recently_added_title": "ДодаĐŊŅ– ĐžŅŅ‚Đ°ĐŊĐŊŅ–Đŧ Ņ‡Đ°ŅĐžĐŧ", "recently_taken": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊĐž СĐŊŅŅ‚Ņ–", + "recently_taken_page_title": "НĐĩŅ‰ĐžĐ´Đ°Đ˛ĐŊĐž СĐŊŅŅ‚Ņ–", "refresh": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸", "refresh_encoded_videos": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ СаĐēОдОваĐŊŅ– Đ˛Ņ–Đ´ĐĩĐž", "refresh_faces": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ОйĐģĐ¸Ņ‡Ņ‡Ņ", "refresh_metadata": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ–", "refresh_thumbnails": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸", "refreshed": "ОĐŊОвĐģĐĩĐŊĐž", + "refreshes_every_file": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ĐŊĐž ĐˇŅ‡Đ¸Ņ‚ŅƒŅ” Đ˛ŅŅ– ĐŊĐ°ŅĐ˛ĐŊŅ– Ņ‚Đ° ĐŊĐžĐ˛Ņ– Ņ„Đ°ĐšĐģи", "refreshing_encoded_video": "ОĐŊОвĐģĐĩĐŊĐŊŅ СаĐēОдОваĐŊĐžĐŗĐž Đ˛Ņ–Đ´ĐĩĐž", "refreshing_faces": "ОĐŊОвĐģĐĩĐŊĐŊŅ ОйĐģĐ¸Ņ‡", "refreshing_metadata": "ОĐŊОвĐģĐĩĐŊĐŊŅ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊĐ¸Ņ…", @@ -1744,7 +1849,7 @@ "remove_assets_shared_link_confirmation": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}} С Ņ†ŅŒĐžĐŗĐž ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ?", "remove_assets_title": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸?", "remove_custom_date_range": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ Đ´ĐžĐ˛Ņ–ĐģҌĐŊиК Đ´Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚", - "remove_filter": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ҄ҖĐģŅŒŅ‚Ņ€", + "remove_deleted_assets": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ видаĐģĐĩĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", "remove_from_album": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ С аĐģŅŒĐąĐžĐŧ҃", "remove_from_album_action_prompt": "{count} виĐģŅƒŅ‡ĐĩĐŊĐž С аĐģŅŒĐąĐžĐŧ҃", "remove_from_favorites": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž", @@ -1755,22 +1860,29 @@ "remove_memory": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ҁĐŋĐžĐŗĐ°Đ´", "remove_photo_from_memory": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ Ņ„ĐžŅ‚Đž С Ņ†ŅŒĐžĐŗĐž ҁĐŋĐžĐŗĐ°Đ´Ņƒ", "remove_tag": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ Ņ‚ĐĩĐŗ", + "remove_url": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ URL", "remove_user": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "removed_api_key": "ВиĐģŅƒŅ‡ĐĩĐŊĐž ĐēĐģŅŽŅ‡ API: {name}", "removed_from_archive": "ВиĐģŅƒŅ‡ĐĩĐŊĐž С Đ°Ņ€Ņ…Ņ–Đ˛Ņƒ", "removed_from_favorites": "ВиĐģŅƒŅ‡ĐĩĐŊĐž С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž", "removed_from_favorites_count": "{count, plural, one {ВиĐģŅƒŅ‡ĐĩĐŊĐž # С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} few {ВиĐģŅƒŅ‡ĐĩĐŊĐž # С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} many {ВиĐģŅƒŅ‡ĐĩĐŊĐž # С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž} other {ВиĐģŅƒŅ‡ĐĩĐŊĐž # С Đ˛Đ¸ĐąŅ€Đ°ĐŊĐžĐŗĐž}}", "removed_memory": "ВиĐģŅƒŅ‡ĐĩĐŊĐž ҁĐŋĐžĐŗĐ°Đ´", + "removed_photo_from_memory": "ВиĐģŅƒŅ‡ĐĩĐŊĐž Ņ„ĐžŅ‚Đž ĐˇŅ– ҁĐŋĐžĐŗĐ°Đ´Ņƒ", "removed_tagged_assets": "ВиĐģŅƒŅ‡ĐĩĐŊĐž Ņ‚ĐĩĐŗ Ņ–Đˇ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "rename": "ПĐĩŅ€ĐĩĐšĐŧĐĩĐŊŅƒĐ˛Đ°Ņ‚Đ¸", + "repair": "ВиĐŋŅ€Đ°Đ˛Đ¸Ņ‚Đ¸", + "repair_no_results_message": "НĐĩĐ˛Ņ–Đ´ŅŅ‚ĐĩĐļŅƒĐ˛Đ°ĐŊŅ– Ņ‚Đ° Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ– Ņ„Đ°ĐšĐģи С'ŅĐ˛ĐģŅŅ‚ŅŒŅŅ Ņ‚ŅƒŅ‚", + "replace_with_upload": "ЗаĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ виваĐŊŅ‚Đ°ĐļĐĩĐŊиĐŧ", "repository": "Đ ĐĩĐŋĐžĐˇĐ¸Ņ‚ĐžŅ€Ņ–Đš", "require_password": "ЗаĐŋĐ¸Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ", + "require_user_to_change_password_on_first_login": "Зобов'ŅĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐĩŅ€ŅˆĐžĐŗĐž Đ˛Ņ…ĐžĐ´Ņƒ", "rescan": "ПĐĩŅ€ĐĩҁĐēаĐŊŅƒĐ˛Đ°Ņ‚Đ¸", "reset": "ĐĄĐēиĐŊŅƒŅ‚Đ¸", "reset_password": "ĐĄĐēиĐŊŅƒŅ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ", "reset_people_visibility": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ видиĐŧŅ–ŅŅ‚ŅŒ ĐģŅŽĐ´ĐĩĐš", "reset_pin_code": "ĐĄĐēиĐŊŅƒŅ‚Đ¸ PIN-ĐēОд", "reset_pin_code_description": "Đ¯ĐēŅ‰Đž ви ĐˇĐ°ĐąŅƒĐģи ŅĐ˛Ņ–Đš PIN-ĐēОд, ви ĐŧĐžĐļĐĩŅ‚Đĩ СвĐĩŅ€ĐŊŅƒŅ‚Đ¸ŅŅ Đ´Đž адĐŧŅ–ĐŊŅ–ŅŅ‚Ņ€Đ°Ņ‚ĐžŅ€Đ° ҁĐĩŅ€Đ˛ĐĩŅ€Đ°, Ņ‰ĐžĐą ҁĐēиĐŊŅƒŅ‚Đ¸ ĐšĐžĐŗĐž", + "reset_pin_code_success": "PIN-ĐēОд ҁĐēиĐŊŅƒŅ‚Đž", "reset_pin_code_with_password": "Ви СавĐļди ĐŧĐžĐļĐĩŅ‚Đĩ ҁĐēиĐŊŅƒŅ‚Đ¸ ŅĐ˛Ņ–Đš PIN-ĐēОд Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ ĐŋĐ°Ņ€ĐžĐģŅ", "reset_sqlite": "ĐĄĐēиĐŊŅƒŅ‚Đ¸ ĐąĐ°ĐˇŅƒ даĐŊĐ¸Ņ… SQLite", "reset_sqlite_clear_app_data": "ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ даĐŊŅ–", @@ -1784,10 +1896,12 @@ "resolved_all_duplicates": "ĐŖŅŅ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸ ҃ҁ҃ĐŊŅƒŅ‚Đž", "restore": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸", "restore_all": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ Đ˛ŅĐĩ", + "restore_trash_action_prompt": "{count} Đ˛Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž С ĐēĐžŅˆĐ¸Đēа", "restore_user": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "restored_asset": "Đ’Ņ–Đ´ĐŊОвĐģĐĩĐŊĐž ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "resume": "ĐŸŅ€ĐžĐ´ĐžĐ˛ĐļĐ¸Ņ‚Đ¸", "resume_paused_jobs": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ {count, plural, one {# ĐŋŅ€Đ¸ĐˇŅƒĐŋиĐŊĐĩĐŊĐĩ СавдаĐŊĐŊŅ} few {# ĐŋŅ€Đ¸ĐˇŅƒĐŋиĐŊĐĩĐŊŅ– СавдаĐŊĐŊŅ} many {# ĐŋŅ€Đ¸ĐˇŅƒĐŋиĐŊĐĩĐŊĐ¸Ņ… СавдаĐŊҌ} other {# ĐŋŅ€Đ¸ĐˇŅƒĐŋиĐŊĐĩĐŊĐ¸Ņ… СавдаĐŊҌ}}", + "retry_upload": "ĐŸĐžĐ˛Ņ‚ĐžŅ€Đ¸Ņ‚Đ¸ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "review_duplicates": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", "review_large_files": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ вĐĩĐģиĐēŅ– Ņ„Đ°ĐšĐģи", "role": "Đ ĐžĐģҌ", @@ -1795,6 +1909,7 @@ "role_viewer": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‡", "running": "ВиĐēĐžĐŊŅƒŅ”Ņ‚ŅŒŅŅ", "save": "ЗбĐĩŅ€ĐĩĐŗŅ‚Đ¸", + "save_to_gallery": "ЗбĐĩŅ€ĐĩĐŗŅ‚Đ¸ в ĐŗĐ°ĐģĐĩŅ€ĐĩŅŽ", "saved": "ЗбĐĩŅ€ĐĩĐļĐĩĐŊĐž", "saved_api_key": "КĐģŅŽŅ‡ API СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", "saved_profile": "ĐŸŅ€ĐžŅ„Ņ–ĐģҌ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", @@ -1805,10 +1920,9 @@ "scan": "ĐĄĐēаĐŊŅƒĐ˛Đ°Ņ‚Đ¸", "scan_all_libraries": "ĐĄĐēаĐŊŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛ŅŅ– ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", "scan_library": "ĐĄĐēаĐŊŅƒĐ˛Đ°Ņ‚Đ¸", + "scan_settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐēаĐŊŅƒĐ˛Đ°ĐŊĐŊŅ", "scanning": "ĐĄĐēаĐŊŅƒĐ˛Đ°ĐŊĐŊŅ", "scanning_for_album": "ĐŸĐžŅˆŅƒĐē аĐģŅŒĐąĐžĐŧ҃â€Ļ", - "screencast_mode_description": "Đ’Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°Ņ‚Đ¸ Ņ–ĐŊдиĐēĐ°Ņ‚ĐžŅ€Đ¸ ĐŋĐžĐ´Ņ–Đš ĐēĐģĐ°Đ˛Ņ–Đ°Ņ‚ŅƒŅ€Đ¸ Ņ‚Đ° ĐŧĐ¸ŅˆŅ– ĐŊа ĐĩĐēŅ€Đ°ĐŊŅ–", - "screencast_mode_title": "ПĐĩŅ€ĐĩĐŧĐēĐŊŅƒŅ‚Đ¸ Ņ€ĐĩĐļиĐŧ ҁĐēҀҖĐŊĐēĐ°ŅŅ‚Ņƒ", "search": "ĐŸĐžŅˆŅƒĐē", "search_albums": "ĐŸĐžŅˆŅƒĐē аĐģŅŒĐąĐžĐŧŅ–Đ˛", "search_by_context": "ĐŸĐžŅˆŅƒĐē Са ĐēĐžĐŊŅ‚ĐĩĐēŅŅ‚ĐžĐŧ", @@ -1816,8 +1930,6 @@ "search_by_description_example": "ĐŸĐžŅ…Ņ–Đ´ ҃ ХаĐŋŅ–", "search_by_filename": "ĐŸĐžŅˆŅƒĐē Са ĐŊĐ°ĐˇĐ˛ĐžŅŽ Ņ„Đ°ĐšĐģ҃ айО Ņ€ĐžĐˇŅˆĐ¸Ņ€ĐĩĐŊĐŊŅĐŧ", "search_by_filename_example": "НаĐŋŅ€Đ¸ĐēĐģад, IMG_1234.JPG айО PNG", - "search_by_full_path": "ĐŸĐžŅˆŅƒĐē Са ĐŋОвĐŊиĐŧ ҈ĐģŅŅ…ĐžĐŧ айО ĐŋаĐŋĐēĐžŅŽ", - "search_by_full_path_example": "/ІваĐŊ/ĐŸŅ€ĐžŅ”ĐēŅ‚Đ¸/3D_Đ”Ņ€ŅƒĐē/2026-07-01 — ĐŧĐžĐļĐŊа ҈҃ĐēĐ°Ņ‚Đ¸ Са ĐŸŅ€ĐžŅ”ĐēŅ‚Đ¸, 3D, Đ”Ņ€ŅƒĐē, 2026 Ņ‚ĐžŅ‰Đž.", "search_by_ocr": "ĐŸĐžŅˆŅƒĐē Са OCR", "search_by_ocr_example": "Đ›Đ°Ņ‚Ņ‚Đĩ", "search_camera_lens_model": "ĐŸĐžŅˆŅƒĐē ĐŧОдĐĩĐģŅ– ĐžĐąâ€™Ņ”ĐēŅ‚Đ¸Đ˛Đ°â€Ļ", @@ -1837,6 +1949,7 @@ "search_filter_location_title": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ ĐŧҖҁ҆Đĩ", "search_filter_media_type": "ĐĸиĐŋ ĐŧĐĩĐ´Ņ–Đ°", "search_filter_media_type_title": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ Ņ‚Đ¸Đŋ ĐŧĐĩĐ´Ņ–Đ°", + "search_filter_ocr": "ĐŸĐžŅˆŅƒĐē Са OCR", "search_filter_people_title": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ ĐģŅŽĐ´ĐĩĐš", "search_filter_star_rating": "Đ ĐĩĐšŅ‚Đ¸ĐŊĐŗ ĐˇŅ–Ņ€ĐēаĐŧи", "search_filter_tags_title": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ Ņ‚ĐĩĐŗĐ¸", @@ -1847,13 +1960,25 @@ "search_no_people_named": "НĐĩĐŧĐ°Ņ” ĐģŅŽĐ´ĐĩĐš С Ņ–ĐŧĐĩĐŊĐĩĐŧ ÂĢ{name}Âģ", "search_no_result": "Đ ĐĩĐˇŅƒĐģŅŒŅ‚Đ°Ņ‚Ņ–Đ˛ ĐŊĐĩ СĐŊаКдĐĩĐŊĐž, ҁĐŋŅ€ĐžĐąŅƒĐšŅ‚Đĩ Ņ–ĐŊŅˆĐ¸Đš СаĐŋĐ¸Ņ‚ айО ĐēĐžĐŧĐąŅ–ĐŊĐ°Ņ†Ņ–ŅŽ", "search_options": "Đ’Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ ĐŋĐžŅˆŅƒĐē҃", + "search_page_categories": "ĐšĐ°Ņ‚ĐĩĐŗĐžŅ€Ņ–Ņ—", + "search_page_motion_photos": "Đ ŅƒŅ…ĐžĐŧŅ– Ņ„ĐžŅ‚Đž", + "search_page_no_objects": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž Ой'Ņ”ĐēŅ‚Đ¸ Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ", + "search_page_no_places": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž ĐŧŅ–ŅŅ†Ņ Đ˛Ņ–Đ´ŅŅƒŅ‚ĐŊŅ", + "search_page_screenshots": "ЗĐŊŅ–ĐŧĐēи ĐĩĐēŅ€Đ°ĐŊ҃", "search_page_search_photos_videos": "Đ¨ŅƒĐēĐ°ĐšŅ‚Đĩ Đ˛Đ°ŅˆŅ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž", + "search_page_selfies": "ĐĄĐĩĐģ҄Җ", + "search_page_things": "Đ Đĩ҇Җ", "search_page_view_all_button": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ Đ˛ŅĐĩ", + "search_page_your_activity": "Đ’Đ°ŅˆŅ– Đ´Ņ–Ņ—", + "search_page_your_map": "Đ’Đ°ŅˆĐ° ĐŧаĐŋа", "search_people": "ĐŸĐžŅˆŅƒĐē ĐģŅŽĐ´ĐĩĐš", "search_places": "ĐŸĐžŅˆŅƒĐē ĐŧŅ–ŅŅ†ŅŒ", "search_rating": "ĐŸĐžŅˆŅƒĐē Са Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗĐžĐŧâ€Ļ", + "search_result_page_new_search_hint": "Новий ĐŋĐžŅˆŅƒĐē", "search_settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ĐŋĐžŅˆŅƒĐē҃", "search_state": "ĐŸĐžŅˆŅƒĐē Ņ€ĐĩĐŗŅ–ĐžĐŊ҃â€Ļ", + "search_suggestion_list_smart_search_hint_1": "Đ ĐžĐˇŅƒĐŧĐŊиК ĐŋĐžŅˆŅƒĐē ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž Ņ‚Đ¸ĐŋОвО, Đ´ĐģŅ ĐŋĐžŅˆŅƒĐē҃ Са ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊиĐŧи виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐšŅ‚Đĩ ŅĐ¸ĐŊŅ‚Đ°ĐēŅĐ¸Ņ ", + "search_suggestion_list_smart_search_hint_2": "m:Đ˛Đ°Ņˆ-ĐŋĐžŅˆŅƒĐēОвиК-Ņ‚ĐĩŅ€ĐŧŅ–ĐŊ", "search_tags": "ĐŸĐžŅˆŅƒĐē Ņ‚ĐĩĐŗŅ–Đ˛â€Ļ", "search_timezone": "ĐŸĐžŅˆŅƒĐē Ņ‡Đ°ŅĐžĐ˛ĐžĐŗĐž ĐŋĐžŅŅŅƒâ€Ļ", "search_type": "ĐĸиĐŋ ĐŋĐžŅˆŅƒĐē҃", @@ -1867,6 +1992,7 @@ "select_albums": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", "select_all": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Đ˛ŅĐĩ", "select_all_duplicates": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Đ˛ŅŅ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", + "select_all_in": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Đ˛ŅĐĩ в {group}", "select_avatar_color": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐēĐžĐģŅ–Ņ€ Đ°Đ˛Đ°Ņ‚Đ°Ņ€Đ°", "select_count": "{count, plural, one {Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ #} few {Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ #} many {Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ #} other {Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ #}}", "select_cutoff_date": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐēŅ–ĐŊ҆ĐĩĐ˛Ņƒ Đ´Đ°Ņ‚Ņƒ", @@ -1874,13 +2000,14 @@ "select_featured_photo": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐŗĐžĐģОвĐŊĐĩ Ņ„ĐžŅ‚Đž", "select_from_computer": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ С ĐēĐžĐŧĐŋ'ŅŽŅ‚ĐĩŅ€Đ°", "select_keep_all": "ЗаĐģĐ¸ŅˆĐ¸Ņ‚Đ¸ Đ˛ŅŅ– Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", + "select_library_owner": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ вĐģĐ°ŅĐŊиĐēа ĐąŅ–ĐąĐģŅ–ĐžŅ‚ĐĩĐēи", "select_new_face": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐŊОвĐĩ ОйĐģĐ¸Ņ‡Ņ‡Ņ", "select_people": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐģŅŽĐ´ĐĩĐš", "select_person": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐģŅŽĐ´Đ¸ĐŊ҃", "select_person_to_tag": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐģŅŽĐ´Đ¸ĐŊ҃ Đ´ĐģŅ ĐŋОСĐŊĐ°Ņ‡ĐĩĐŊĐŊŅ", "select_photos": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž", - "select_quality": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ŅĐēŅ–ŅŅ‚ŅŒ", "select_trash_all": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ˛ŅŅ– Đ´Đž ĐēĐžŅˆĐ¸Đēа", + "select_user_for_sharing_page_err_album": "НĐĩ вдаĐģĐžŅŅ ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧ", "selected": "Đ’Đ¸ĐąŅ€Đ°ĐŊĐž", "selected_count": "{count, plural, one {# Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} few {# Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} many {# Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž} other {# Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž}}", "selected_gps_coordinates": "Đ’Đ¸ĐąŅ€Đ°ĐŊŅ– GPS-ĐēĐžĐžŅ€Đ´Đ¸ĐŊĐ°Ņ‚Đ¸", @@ -1906,38 +2033,49 @@ "set_slideshow_to_fullscreen": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ҁĐģаКд-ŅˆĐžŅƒ ĐŊа вĐĩҁҌ ĐĩĐēŅ€Đ°ĐŊ", "set_stack_primary_asset": "ĐŖŅŅ‚Đ°ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ŅĐē ĐžŅĐŊОвĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "setting_image_navigation_enable_subtitle": "Đ¯ĐēŅ‰Đž ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž, ви ĐŧĐžĐļĐĩŅ‚Đĩ ĐŋĐĩŅ€ĐĩŅ…ĐžĐ´Đ¸Ņ‚Đ¸ Đ´Đž ĐŋĐžĐŋĐĩŅ€ĐĩĐ´ĐŊŅŒĐžĐŗĐž айО ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊĐžĐŗĐž ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ, ĐŊĐ°Ņ‚Đ¸ŅĐēĐ°ŅŽŅ‡Đ¸ ĐŊа ĐēŅ€Đ°ĐšĐŊŅŽ ĐģŅ–Đ˛Ņƒ айО ĐŋŅ€Đ°Đ˛Ņƒ Ņ‡Đ˛ĐĩŅ€Ņ‚ŅŒ ĐĩĐēŅ€Đ°ĐŊа.", - "setting_image_navigation_enable_title": "ĐĸĐžŅ€ĐēĐŊŅ–Ņ‚ŅŒŅŅ Đ´ĐģŅ ĐŊĐ°Đ˛Ņ–ĐŗĐ°Ņ†Ņ–Ņ—", + "setting_image_navigation_enable_title": "ĐĸĐžŅ€ĐēĐŊŅ–Ņ‚ŅŒŅŅ, Ņ‰ĐžĐą ĐŋĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "setting_image_navigation_title": "ĐĐ°Đ˛Ņ–ĐŗĐ°Ņ†Ņ–Ņ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅĐŧи", "setting_image_viewer_help": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´Đ°Ņ‡ ҁĐŋĐžŅ‡Đ°Ņ‚Đē҃ СаваĐŊŅ‚Đ°ĐļŅƒŅ” ĐŧаĐģ҃ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Ņƒ, ĐŋĐžŅ‚Ņ–Đŧ ĐŋĐžĐŋĐĩŅ€ĐĩĐ´ĐŊŅ–Đš ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´ ҁĐĩŅ€ĐĩĐ´ĐŊŅŒĐžŅ— Ņ€ĐžĐˇĐ´Ņ–ĐģҌĐŊĐžŅ— ĐˇĐ´Đ°Ņ‚ĐŊĐžŅŅ‚Ņ– (ŅĐēŅ‰Đž ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž) Ņ– ĐˇŅ€ĐĩŅˆŅ‚ĐžŅŽ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģ (ŅĐēŅ‰Đž ŅƒĐ˛Ņ–ĐŧĐēĐŊĐĩĐŊĐž).", "setting_image_viewer_original_subtitle": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊĐĩ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ С ĐŋОвĐŊĐžŅŽ Ņ€ĐžĐˇĐ´Ņ–ĐģҌĐŊĐžŅŽ ĐˇĐ´Đ°Ņ‚ĐŊŅ–ŅŅ‚ŅŽ (вĐĩĐģиĐēĐĩ!). ВиĐŧĐēĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą СĐŧĐĩĐŊŅˆĐ¸Ņ‚Đ¸ виĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ даĐŊĐ¸Ņ… (ŅĐē ҇ĐĩŅ€ĐĩС ĐŧĐĩŅ€ĐĩĐļ҃, Ņ‚Đ°Đē Ņ– в ĐēĐĩŅˆŅ– ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅŽ).", "setting_image_viewer_original_title": "ЗаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊĐĩ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "setting_image_viewer_preview_subtitle": "ĐŖĐ˛Ņ–ĐŧĐēĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą СаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ҁĐĩŅ€ĐĩĐ´ĐŊŅŒĐžŅ— Ņ€ĐžĐˇĐ´Ņ–ĐģҌĐŊĐžŅ— ĐˇĐ´Đ°Ņ‚ĐŊĐžŅŅ‚Ņ–. ВиĐŧĐēĐŊŅ–Ņ‚ŅŒ, Ņ‰ĐžĐą СаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģ айО виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐģĐ¸ŅˆĐĩ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Ņƒ.", - "setting_image_viewer_preview_title": "ЗаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸", + "setting_image_viewer_preview_title": "ЗаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŋĐžĐŋĐĩŅ€ĐĩĐ´ĐŊŅŒĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ", + "setting_image_viewer_title": "Đ—ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "setting_languages_apply": "Đ—Đ°ŅŅ‚ĐžŅŅƒĐ˛Đ°Ņ‚Đ¸", "setting_languages_subtitle": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŧĐžĐ˛Ņƒ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", - "setting_notifications_notify_minutes": "{count} Ņ…Đ˛Đ¸ĐģиĐŊ", - "setting_notifications_notify_seconds": "{count} ҁĐĩĐē҃ĐŊĐ´", + "setting_notifications_notify_failures_grace_period": "ĐĄĐŋĐžĐ˛Ņ–Ņ‰Đ°Ņ‚Đ¸ ĐŋŅ€Đž ĐŋĐžĐŧиĐģĐēи Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ: {duration}", + "setting_notifications_notify_hours": "{count, plural, one {# ĐŗĐžĐ´Đ¸ĐŊа} few {# ĐŗĐžĐ´Đ¸ĐŊи} many {# ĐŗĐžĐ´Đ¸ĐŊ} other {# ĐŗĐžĐ´Đ¸ĐŊ}}", + "setting_notifications_notify_immediately": "ĐŊĐĩĐŗĐ°ĐšĐŊĐž", + "setting_notifications_notify_minutes": "{count, plural, one {# Ņ…Đ˛Đ¸ĐģиĐŊа} few {# Ņ…Đ˛Đ¸ĐģиĐŊи} many {# Ņ…Đ˛Đ¸ĐģиĐŊ} other {# Ņ…Đ˛Đ¸ĐģиĐŊ}}", + "setting_notifications_notify_never": "ĐŊŅ–ĐēĐžĐģи", + "setting_notifications_notify_seconds": "{count, plural, one {# ҁĐĩĐē҃ĐŊда} few {# ҁĐĩĐē҃ĐŊди} many {# ҁĐĩĐē҃ĐŊĐ´} other {# ҁĐĩĐē҃ĐŊĐ´}}", + "setting_notifications_single_progress_subtitle": "ДĐĩŅ‚Đ°ĐģҌĐŊа Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž ĐŋĐžŅŅ‚ŅƒĐŋ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ Đ´ĐģŅ ĐēĐžĐļĐŊĐžĐŗĐž ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", + "setting_notifications_single_progress_title": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ Đ´ĐĩŅ‚Đ°ĐģҌĐŊиК ĐŋĐžŅŅ‚ŅƒĐŋ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "setting_notifications_subtitle": "НаĐģĐ°ŅˆŅ‚ŅƒĐšŅ‚Đĩ ҃ĐŋОдОйаĐŊĐŊŅ ҁĐŋĐžĐ˛Ņ–Ņ‰ĐĩĐŊҌ", + "setting_notifications_total_progress_subtitle": "Đ—Đ°ĐŗĐ°ĐģҌĐŊиК ĐŋĐžŅŅ‚ŅƒĐŋ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ (виĐēĐžĐŊаĐŊĐž/ĐˇĐ°ĐŗĐ°ĐģĐžĐŧ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛)", + "setting_notifications_total_progress_title": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐˇĐ°ĐŗĐ°ĐģҌĐŊиК ĐŋĐžŅŅ‚ŅƒĐŋ Ņ„ĐžĐŊĐžĐ˛ĐžĐŗĐž Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "setting_video_viewer_auto_play_subtitle": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž ĐŋĐžŅ‡Đ¸ĐŊĐ°Ņ‚Đ¸ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ Đ˛Ņ–Đ´ĐĩĐž ĐŋŅ–Đ´ Ņ‡Đ°Ņ Ņ—Ņ… Đ˛Ņ–Đ´ĐēŅ€Đ¸Ņ‚Ņ‚Ņ", "setting_video_viewer_auto_play_title": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐĩ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ Đ˛Ņ–Đ´ĐĩĐž", "setting_video_viewer_looping_title": "ĐĻиĐēĐģҖ҇ĐŊĐĩ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", "setting_video_viewer_original_video_subtitle": "ĐŸŅ–Đ´ Ņ‡Đ°Ņ ĐŋĐžŅ‚ĐžĐēĐžĐ˛ĐžĐŗĐž Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ Đ˛Ņ–Đ´ĐĩĐž Ņ–Đˇ ҁĐĩŅ€Đ˛ĐĩŅ€Đ° Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģ, ĐŊĐ°Đ˛Ņ–Ņ‚ŅŒ ŅĐēŅ‰Đž Đ´ĐžŅŅ‚ŅƒĐŋĐŊĐĩ Ņ‚Ņ€Đ°ĐŊҁĐēĐžĐ´ŅƒĐ˛Đ°ĐŊĐŊŅ. МоĐļĐĩ ĐŋŅ€Đ¸ĐˇĐ˛ĐĩŅŅ‚Đ¸ Đ´Đž ĐąŅƒŅ„ĐĩŅ€Đ¸ĐˇĐ°Ņ†Ņ–Ņ—. Đ’Ņ–Đ´ĐĩĐž, Đ´ĐžŅŅ‚ŅƒĐŋĐŊŅ– ĐģĐžĐēаĐģҌĐŊĐž, Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ŅŽŅŽŅ‚ŅŒŅŅ в ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊŅ–Đš ŅĐēĐžŅŅ‚Ņ–, ĐŊĐĩСаĐģĐĩĐļĐŊĐž Đ˛Ņ–Đ´ Ņ†ŅŒĐžĐŗĐž ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ.", "setting_video_viewer_original_video_title": "ĐŸŅ€Đ¸ĐŧŅƒŅĐžĐ˛Đž Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģҌĐŊĐĩ Đ˛Ņ–Đ´ĐĩĐž", "settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", + "settings_require_restart": "ПĐĩŅ€ĐĩСаĐŋŅƒŅŅ‚Ņ–Ņ‚ŅŒ Immich, Ņ‰ĐžĐą ĐˇĐ°ŅŅ‚ĐžŅŅƒĐ˛Đ°Ņ‚Đ¸ ҆Đĩ ĐŊаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ", "settings_saved": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ СйĐĩŅ€ĐĩĐļĐĩĐŊĐž", "setup_pin_code": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°Ņ‚Đ¸ PIN-ĐēОд", - "share": "ĐŸĐžŅˆĐ¸Ņ€Đ¸Ņ‚Đ¸", + "share": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ", + "share_action_prompt": "НадаĐŊĐž ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", + "share_add_photos": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž", + "share_assets_selected": "{count} Đ˛Đ¸ĐąŅ€Đ°ĐŊĐž", "share_dialog_preparing": "ĐŸŅ–Đ´ĐŗĐžŅ‚ĐžĐ˛Đēаâ€Ļ", "share_link": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅĐŧ", - "share_original": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ĐžŅ€Đ¸ĐŗŅ–ĐŊаĐģ (вĐĩĐģиĐēиК)", - "share_preview": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Ņƒ (ĐŧаĐģ҃)", - "share_quality_body": "ĐĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ Ņ‚Đ° Ņ‚Ņ€Đ¸ĐŧĐ°ĐšŅ‚Đĩ ĐēĐŊĐžĐŋĐē҃ ĐŋĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ Ņ‰ĐžĐą ĐžĐąŅ€Đ°Ņ‚Đ¸ ŅĐēŅ–ŅŅ‚ŅŒ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ ĐŋĐĩŅ€ĐĩĐ´ Ņ‚Đ¸Đŧ ŅĐē ĐŋĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅŒ.", - "share_quality_title": "ОбĐĩŅ€Ņ–Ņ‚ŅŒ Đ˛Đ°ŅˆŅƒ ŅĐēŅ–ŅŅ‚ŅŒ Đ´ĐģŅ ĐŋĐžŅˆĐ¸Ņ€ĐĩĐŊĐŊŅ", "shared": "ĐĄĐŋŅ–ĐģҌĐŊŅ–", "shared_album_activities_input_disable": "КоĐŧĐĩĐŊŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ виĐŧĐēĐŊĐĩĐŊĐž", "shared_album_activity_remove_content": "Ви Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ ҆ĐĩĐš СаĐŋĐ¸Ņ?", "shared_album_activity_remove_title": "ВидаĐģĐ¸Ņ‚Đ¸ СаĐŋĐ¸Ņ", "shared_album_section_people_action_error": "НĐĩ вдаĐģĐžŅŅ Đ˛Đ¸ĐšŅ‚Đ¸ С аĐģŅŒĐąĐžĐŧ҃ айО виĐģŅƒŅ‡Đ¸Ņ‚Đ¸ С ĐŊŅŒĐžĐŗĐž", + "shared_album_section_people_action_leave": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° С аĐģŅŒĐąĐžĐŧ҃", + "shared_album_section_people_action_remove_user": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° С аĐģŅŒĐąĐžĐŧ҃", "shared_album_section_people_title": "ЛЮДИ", "shared_by": "НадаĐŊĐž Đ´ĐžŅŅ‚ŅƒĐŋ", "shared_by_user": "НадаĐŊĐž Đ´ĐžŅŅ‚ŅƒĐŋ: {user}", @@ -1946,6 +2084,7 @@ "shared_intent_upload_button_progress_text": "{current} / {total} ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", "shared_link_app_bar_title": "ĐĄĐŋŅ–ĐģҌĐŊŅ– ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", "shared_link_clipboard_copied_massage": "ĐĄĐēĐžĐŋŅ–ĐšĐžĐ˛Đ°ĐŊĐž в ĐąŅƒŅ„ĐĩŅ€ ОйĐŧŅ–ĐŊ҃", + "shared_link_clipboard_text": "ĐŸĐžŅĐ¸ĐģаĐŊĐŊŅ: {link}\nĐŸĐ°Ņ€ĐžĐģҌ: {password}", "shared_link_create_error": "НĐĩ вдаĐģĐžŅŅ ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊĐĩ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", "shared_link_custom_url_description": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´ĐžĐ˛Ņ–ĐģҌĐŊ҃ URL-Đ°Đ´Ņ€Đĩҁ҃ Đ´ĐģŅ Ņ†ŅŒĐžĐŗĐž ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", "shared_link_edit_description_hint": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ ĐžĐŋĐ¸Ņ Đ´ĐģŅ ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋ҃", @@ -1954,7 +2093,7 @@ "shared_link_edit_expire_after_option_hour": "1 ĐŗĐžĐ´Đ¸ĐŊ҃", "shared_link_edit_expire_after_option_hours": "{count, plural, one {# ĐŗĐžĐ´Đ¸ĐŊ҃} few {# ĐŗĐžĐ´Đ¸ĐŊи} many {# ĐŗĐžĐ´Đ¸ĐŊ} other {# ĐŗĐžĐ´Đ¸ĐŊ}}", "shared_link_edit_expire_after_option_minute": "1 Ņ…Đ˛Đ¸ĐģиĐŊ҃", - "shared_link_edit_expire_after_option_minutes": "{count} Ņ…Đ˛Đ¸ĐģиĐŊ", + "shared_link_edit_expire_after_option_minutes": "{count, plural, one {# Ņ…Đ˛Đ¸ĐģиĐŊ҃} few {# Ņ…Đ˛Đ¸ĐģиĐŊи} many {# Ņ…Đ˛Đ¸ĐģиĐŊ} other {# Ņ…Đ˛Đ¸ĐģиĐŊ}}", "shared_link_edit_expire_after_option_months": "{count, plural, one {# ĐŧŅ–ŅŅŅ†ŅŒ} few {# ĐŧŅ–ŅŅŅ†Ņ–} many {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛} other {# ĐŧŅ–ŅŅŅ†Ņ–Đ˛}}", "shared_link_edit_expire_after_option_year": "{count, plural, one {# ҀҖĐē} few {# Ņ€ĐžĐēи} many {# Ņ€ĐžĐēŅ–Đ˛} other {# Ņ€ĐžĐēŅ–Đ˛}}", "shared_link_edit_password_hint": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ ҁĐŋŅ–ĐģҌĐŊĐžĐŗĐž Đ´ĐžŅŅ‚ŅƒĐŋ҃", @@ -1964,11 +2103,11 @@ "shared_link_expires_days": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# Đ´ĐĩĐŊҌ} few {# Đ´ĐŊŅ–} many {# Đ´ĐŊŅ–Đ˛} other {# Đ´ĐŊŅ–Đ˛}}", "shared_link_expires_hour": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# ĐŗĐžĐ´Đ¸ĐŊ҃} few {# ĐŗĐžĐ´Đ¸ĐŊи} many {# ĐŗĐžĐ´Đ¸ĐŊ} other {# ĐŗĐžĐ´Đ¸ĐŊ}}", "shared_link_expires_hours": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# ĐŗĐžĐ´Đ¸ĐŊ҃} few {# ĐŗĐžĐ´Đ¸ĐŊи} many {# ĐŗĐžĐ´Đ¸ĐŊ} other {# ĐŗĐžĐ´Đ¸ĐŊ}}", - "shared_link_expires_minute": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count} Ņ…Đ˛Đ¸ĐģиĐŊ҃", - "shared_link_expires_minutes": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count} Ņ…Đ˛Đ¸ĐģиĐŊ", + "shared_link_expires_minute": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# Ņ…Đ˛Đ¸ĐģиĐŊ҃} few {# Ņ…Đ˛Đ¸ĐģиĐŊи} many {# Ņ…Đ˛Đ¸ĐģиĐŊ} other {# Ņ…Đ˛Đ¸ĐģиĐŊ}}", + "shared_link_expires_minutes": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# Ņ…Đ˛Đ¸ĐģиĐŊ҃} few {# Ņ…Đ˛Đ¸ĐģиĐŊи} many {# Ņ…Đ˛Đ¸ĐģиĐŊ} other {# Ņ…Đ˛Đ¸ĐģиĐŊ}}", "shared_link_expires_never": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ∞", - "shared_link_expires_second": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count} ҁĐĩĐē҃ĐŊĐ´Ņƒ", - "shared_link_expires_seconds": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count} ҁĐĩĐē҃ĐŊĐ´", + "shared_link_expires_second": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# ҁĐĩĐē҃ĐŊĐ´Ņƒ} few {# ҁĐĩĐē҃ĐŊди} many {# ҁĐĩĐē҃ĐŊĐ´} other {# ҁĐĩĐē҃ĐŊĐ´}}", + "shared_link_expires_seconds": "ЗаĐēŅ–ĐŊŅ‡ŅƒŅ”Ņ‚ŅŒŅŅ ҇ĐĩŅ€ĐĩС {count, plural, one {# ҁĐĩĐē҃ĐŊĐ´Ņƒ} few {# ҁĐĩĐē҃ĐŊди} many {# ҁĐĩĐē҃ĐŊĐ´} other {# ҁĐĩĐē҃ĐŊĐ´}}", "shared_link_individual_shared": "ІĐŊĐ´Đ¸Đ˛Ņ–Đ´ŅƒĐ°ĐģҌĐŊиК ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ", "shared_link_info_chip_metadata": "Exif", "shared_link_manage_links": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐŋŅ–ĐģҌĐŊиĐŧи ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅĐŧи", @@ -1981,6 +2120,12 @@ "shared_with_partner": "ĐĄĐŋŅ–ĐģҌĐŊĐž С {partner}", "sharing": "ĐĄĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ", "sharing_enter_password": "ВвĐĩĐ´Ņ–Ņ‚ŅŒ ĐŋĐ°Ņ€ĐžĐģҌ Đ´ĐģŅ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ ҆ҖҔҗ ŅŅ‚ĐžŅ€Ņ–ĐŊĐēи.", + "sharing_page_album": "ĐĄĐŋŅ–ĐģҌĐŊŅ– аĐģŅŒĐąĐžĐŧи", + "sharing_page_description": "ĐĄŅ‚Đ˛ĐžŅ€ŅŽĐšŅ‚Đĩ ҁĐŋŅ–ĐģҌĐŊŅ– аĐģŅŒĐąĐžĐŧи, Ņ‰ĐžĐą Đ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž С ĐģŅŽĐ´ŅŒĐŧи ĐˇŅ– ŅĐ˛ĐžŅ”Ņ— ĐŧĐĩŅ€ĐĩĐļŅ–.", + "sharing_page_empty_list": "ПОРОЖНІЙ СПИСОК", + "sharing_sidebar_description": "Đ’Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐ°Ņ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ ĐŊа ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ ҃ ĐąŅ–Ņ‡ĐŊŅ–Đš ĐŋаĐŊĐĩĐģŅ–", + "sharing_silver_appbar_create_shared_album": "ĐĄŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊиК аĐģŅŒĐąĐžĐŧ", + "sharing_silver_appbar_share_partner": "ĐŸĐžĐ´Ņ–ĐģĐ¸Ņ‚Đ¸ŅŅ С ĐŋĐ°Ņ€Ņ‚ĐŊĐĩŅ€ĐžĐŧ", "shift_to_permanent_delete": "ĐŊĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ ⇧, Ņ‰ĐžĐą видаĐģĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚ ĐŊаСавĐļди", "show_album_options": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ҃", "show_albums": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", @@ -1992,9 +2137,7 @@ "show_in_timeline": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ в Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ—", "show_in_timeline_setting_description": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž Ņ†ŅŒĐžĐŗĐž ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° ҃ Đ˛Đ°ŅˆŅ–Đš Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ—", "show_keyboard_shortcuts": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҁĐŋĐžĐģŅƒŅ‡ĐĩĐŊĐŊŅ ĐēĐģĐ°Đ˛Ņ–Ņˆ", - "show_less": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ĐŧĐĩĐŊ҈Đĩ", "show_metadata": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ–", - "show_more_fields": "{count, plural, one {ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҉Đĩ # ĐŋĐžĐģĐĩ} few {ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҉Đĩ # ĐŋĐžĐģŅ} many {ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҉Đĩ # ĐŋĐžĐģŅ–Đ˛} other {ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҉Đĩ # ĐŋĐžĐģŅ–Đ˛}}", "show_or_hide_info": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ айО ĐŋŅ€Đ¸Ņ…ĐžĐ˛Đ°Ņ‚Đ¸ Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–ŅŽ", "show_password": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ĐŋĐ°Ņ€ĐžĐģҌ", "show_person_options": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ ĐģŅŽĐ´Đ¸ĐŊи", @@ -2002,7 +2145,6 @@ "show_schema": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҁ҅ĐĩĐŧ҃", "show_search_options": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ Đ˛Đ°Ņ€Ņ–Đ°ĐŊŅ‚Đ¸ ĐŋĐžŅˆŅƒĐē҃", "show_shared_links": "ПоĐēĐ°ĐˇĐ°Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊŅ– ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", - "show_slideshow_metadata_overlay": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ОвĐĩŅ€ĐģĐĩĐš С Ņ–ĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ”ŅŽ ĐŋŅ€Đž ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", "show_slideshow_transition": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐŋĐĩŅ€ĐĩŅ…Ņ–Đ´ ҁĐģаКд-ŅˆĐžŅƒ", "show_supporter_badge": "ЗĐŊĐ°Ņ‡ĐžĐē ĐŋŅ€Đ¸Ņ…Đ¸ĐģҌĐŊиĐēа", "show_supporter_badge_description": "ПоĐēĐ°ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ СĐŊĐ°Ņ‡ĐžĐē ĐŋŅ€Đ¸Ņ…Đ¸ĐģҌĐŊиĐēа", @@ -2014,25 +2156,18 @@ "sign_out": "Đ’Đ¸ĐšŅ‚Đ¸", "sign_up": "Đ—Đ°Ņ€ĐĩŅ”ŅŅ‚Ņ€ŅƒĐ˛Đ°Ņ‚Đ¸ŅŅ", "size": "РОСĐŧŅ–Ņ€", - "skip": "ĐŸŅ€ĐžĐŋŅƒŅŅ‚Đ¸Ņ‚Đ¸", "skip_to_content": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž вĐŧŅ–ŅŅ‚Ņƒ", "skip_to_folders": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž ĐŋаĐŋĐžĐē", "skip_to_tags": "ПĐĩŅ€ĐĩĐšŅ‚Đ¸ Đ´Đž Ņ‚ĐĩĐŗŅ–Đ˛", "slideshow": "ĐĄĐģаКд-ŅˆĐžŅƒ", - "slideshow_body": "ĐĄŅĐ´ŅŒŅ‚Đĩ ĐˇŅ€ŅƒŅ‡ĐŊŅ–ŅˆĐĩ Ņ‚Đ° ĐŋĐžĐ´Đ¸Đ˛Ņ–Ņ‚ŅŒŅŅ ŅĐē Đ˛Đ°ŅˆŅ– Ņ„ĐžŅ‚ĐžĐŗŅ€Đ°Ņ„Ņ–Ņ— ĐŋŅ€ĐžĐŗŅ€Đ°ŅŽŅ‚ŅŒŅŅ в ĐŋОвĐŊĐžĐĩĐēŅ€Đ°ĐŊĐŊĐžĐŧ҃ ҁĐģаКд-ŅˆĐžŅƒ.", - "slideshow_metadata_overlay_mode": "ВĐŧҖҁ҂ ОвĐĩŅ€ĐģĐĩŅŽ", - "slideshow_metadata_overlay_mode_description_only": "Đ›Đ¸ŅˆĐĩ ĐžĐŋĐ¸Ņ", - "slideshow_metadata_overlay_mode_full": "ПовĐŊиК", "slideshow_repeat": "ĐŸĐžĐ˛Ņ‚ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ ҁĐģаКд-ŅˆĐžŅƒ", "slideshow_repeat_description": "ПовĐĩŅ€ĐŊĐĩĐŊĐŊŅ Đ´Đž ĐŋĐžŅ‡Đ°Ņ‚Đē҃ ĐŋҖҁĐģŅ СавĐĩŅ€ŅˆĐĩĐŊĐŊŅ ҁĐģаКд-ŅˆĐžŅƒ", "slideshow_settings": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ҁĐģаКд-ŅˆĐžŅƒ", - "slideshow_title": "ĐĄĐģаКд-ŅˆĐžŅƒ", - "smart_album": "Đ ĐžĐˇŅƒĐŧĐŊиК аĐģŅŒĐąĐžĐŧ", - "some_assets_already_have_a_location_warning": "ДĐģŅ Đ´ĐĩŅĐēĐ¸Ņ… Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ вĐļĐĩ вĐēаСаĐŊĐž ĐŧҖҁ҆Đĩ", "sort_albums_by": "ĐĄĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи Саâ€Ļ", "sort_created": "Đ”Đ°Ņ‚Đ° ŅŅ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ", "sort_items": "ĐšŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛", "sort_modified": "Đ”Đ°Ņ‚Đ° СĐŧŅ–ĐŊи", + "sort_newest": "НайĐŊĐžĐ˛Ņ–ŅˆĐĩ Ņ„ĐžŅ‚Đž", "sort_oldest": "ĐĐ°ĐšŅŅ‚Đ°Ņ€Ņ–ŅˆĐĩ Ņ„ĐžŅ‚Đž", "sort_people_by_similarity": "ĐĄĐžŅ€Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ ĐģŅŽĐ´ĐĩĐš Са ŅŅ…ĐžĐļŅ–ŅŅ‚ŅŽ", "sort_recent": "ĐĐ°ĐšŅĐ˛Ņ–ĐļŅ–ŅˆĐĩ Ņ„ĐžŅ‚Đž", @@ -2041,6 +2176,7 @@ "stack": "Đ—ĐŗŅ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸", "stack_action_prompt": "{count} ĐˇĐŗŅ€ŅƒĐŋОваĐŊĐž", "stack_duplicates": "Đ“Ņ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Đ¸", + "stack_select_one_photo": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ОдĐŊĐĩ ĐžŅĐŊОвĐŊĐĩ Ņ„ĐžŅ‚Đž Đ´ĐģŅ ĐŗŅ€ŅƒĐŋи", "stack_selected_photos": "Đ—ĐŗŅ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛Đ¸ĐąŅ€Đ°ĐŊŅ– Ņ„ĐžŅ‚Đž", "stacked_assets_count": "Đ—ĐŗŅ€ŅƒĐŋОваĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "stacktrace": "ĐĄŅ‚ĐĩĐē виĐēĐģиĐēŅ–Đ˛", @@ -2049,10 +2185,6 @@ "start_date_before_end_date": "Đ”Đ°Ņ‚Đ° ĐŋĐžŅ‡Đ°Ņ‚Đē҃ ĐŧĐ°Ņ” ĐąŅƒŅ‚Đ¸ Ņ€Đ°ĐŊŅ–ŅˆĐĩ Đ´Đ°Ņ‚Đ¸ СавĐĩŅ€ŅˆĐĩĐŊĐŊŅ", "state": "Đ ĐĩĐŗŅ–ĐžĐŊ", "status": "ĐĄŅ‚Đ°ĐŊ", - "step_delete": "ВидаĐģĐ¸Ņ‚Đ¸ ĐēŅ€ĐžĐē", - "step_delete_confirm": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ ҆ĐĩĐš ĐēŅ€ĐžĐē?", - "step_details": "ІĐŊŅ„ĐžŅ€ĐŧĐ°Ņ†Ņ–Ņ ĐŋŅ€Đž ĐēŅ€ĐžĐē", - "steps": "ĐšŅ€ĐžĐēи", "stop_casting": "Đ—ŅƒĐŋиĐŊĐ¸Ņ‚Đ¸ Ņ‚Ņ€Đ°ĐŊҁĐģŅŅ†Ņ–ŅŽ", "stop_motion_photo": "Đ—ŅƒĐŋиĐŊĐ¸Ņ‚Đ¸ Ņ€ŅƒŅ…ĐžĐŧĐĩ Ņ„ĐžŅ‚Đž", "stop_photo_sharing": "ĐŸŅ€Đ¸ĐŋиĐŊĐ¸Ņ‚Đ¸ ҁĐŋŅ–ĐģҌĐŊиК Đ´ĐžŅŅ‚ŅƒĐŋ Đ´Đž Đ˛Đ°ŅˆĐ¸Ņ… Ņ„ĐžŅ‚Đž?", @@ -2073,18 +2205,17 @@ "swap_merge_direction": "ЗĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ ĐŊаĐŋŅ€ŅĐŧĐžĐē Ой'Ņ”Đ´ĐŊаĐŊĐŊŅ", "sync": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸", "sync_albums": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ аĐģŅŒĐąĐžĐŧи", + "sync_albums_manual_subtitle": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ Đ˛ŅŅ– виваĐŊŅ‚Đ°ĐļĐĩĐŊŅ– Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž ҃ Đ˛Đ¸ĐąŅ€Đ°ĐŊŅ– аĐģŅŒĐąĐžĐŧи Đ´ĐģŅ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊĐžĐŗĐž ĐēĐžĐŋŅ–ŅŽĐ˛Đ°ĐŊĐŊŅ", "sync_local": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ ĐŊа ĐŋŅ€Đ¸ŅŅ‚Ņ€ĐžŅ—", "sync_remote": "ХиĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇŅƒĐ˛Đ°Ņ‚Đ¸ С ҁĐĩŅ€Đ˛ĐĩŅ€ĐžĐŧ", "sync_status": "ĐĄŅ‚Đ°ĐŊ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ—", "sync_status_subtitle": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´ Ņ‚Đ° ĐēĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ ŅĐ¸ŅŅ‚ĐĩĐŧĐžŅŽ ŅĐ¸ĐŊŅ…Ņ€ĐžĐŊŅ–ĐˇĐ°Ņ†Ņ–Ņ—", "sync_upload_album_setting_subtitle": "ĐĄŅ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ Ņ‚Đ° виваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ ŅĐ˛ĐžŅ— Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž Đ´Đž Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… аĐģŅŒĐąĐžĐŧŅ–Đ˛ ĐŊа ҁĐĩŅ€Đ˛ĐĩŅ€ Immich", - "system_theme": "ĐĄĐ¸ŅŅ‚ĐĩĐŧĐŊа Ņ‚ĐĩĐŧа", - "system_theme_command_description": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ŅĐ¸ŅŅ‚ĐĩĐŧĐŊ҃ Ņ‚ĐĩĐŧ҃ ({value})", "tag": "ĐĸĐĩĐŗ", "tag_assets": "Đ”ĐžĐ´Đ°Ņ‚Đ¸ Ņ‚ĐĩĐŗĐ¸", "tag_created": "ĐĄŅ‚Đ˛ĐžŅ€ĐĩĐŊĐž Ņ‚ĐĩĐŗ: {tag}", - "tag_face": "ĐĸĐĩĐŗ ОйĐģĐ¸Ņ‡Ņ‡Ņ", "tag_feature_description": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´ Ņ„ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž, ĐˇĐŗŅ€ŅƒĐŋОваĐŊĐ¸Ņ… Са ĐģĐžĐŗŅ–Ņ‡ĐŊиĐŧи Ņ‚ĐĩĐŧаĐŧи Ņ‚ĐĩĐŗŅ–Đ˛", + "tag_not_found_question": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ СĐŊĐ°ĐšŅ‚Đ¸ Ņ‚ĐĩĐŗ? ĐĄŅ‚Đ˛ĐžŅ€Ņ–Ņ‚ŅŒ ĐŊОвиК Ņ‚ĐĩĐŗ.", "tag_people": "ПозĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ ĐģŅŽĐ´ĐĩĐš", "tag_updated": "ОĐŊОвĐģĐĩĐŊĐž Ņ‚ĐĩĐŗ: {tag}", "tagged_assets": "ПозĐŊĐ°Ņ‡ĐĩĐŊĐž Ņ‚ĐĩĐŗĐžĐŧ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", @@ -2099,10 +2230,15 @@ "theme_setting_asset_list_tiles_per_row_title": "ĐšŅ–ĐģҌĐēŅ–ŅŅ‚ŅŒ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ҃ Ņ€ŅĐ´Đē҃ ({count})", "theme_setting_colorful_interface_subtitle": "Đ—Đ°ŅŅ‚ĐžŅĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐžŅĐŊОвĐŊиК ĐēĐžĐģŅ–Ņ€ Đ´Đž Ņ„ĐžĐŊĐžĐ˛Đ¸Ņ… ĐŋОвĐĩŅ€Ņ…ĐžĐŊҌ.", "theme_setting_colorful_interface_title": "Đ‘Đ°Ņ€Đ˛Đ¸ŅŅ‚Đ¸Đš Ņ–ĐŊŅ‚ĐĩҀ҄ĐĩĐšŅ", + "theme_setting_image_viewer_quality_subtitle": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ ŅĐēĐžŅŅ‚Ņ– Đ´ĐĩŅ‚Đ°ĐģҌĐŊĐžĐŗĐž ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ", + "theme_setting_image_viewer_quality_title": "Đ¯ĐēŅ–ŅŅ‚ŅŒ ĐŋĐĩŅ€ĐĩĐŗĐģŅĐ´Ņƒ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊҌ", "theme_setting_primary_color_subtitle": "ВибĐĩŅ€Ņ–Ņ‚ŅŒ ĐēĐžĐģŅ–Ņ€ Đ´ĐģŅ ĐžŅĐŊОвĐŊĐ¸Ņ… Đ´Ņ–Đš Ņ– аĐē҆ĐĩĐŊŅ‚Ņ–Đ˛.", "theme_setting_primary_color_title": "ĐžŅĐŊОвĐŊиК ĐēĐžĐģŅ–Ņ€", "theme_setting_system_primary_color_title": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐēĐžĐģŅ–Ņ€ ŅĐ¸ŅŅ‚ĐĩĐŧи", "theme_setting_system_theme_switch": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸Ņ‡ĐŊĐž (ŅĐē ҃ ŅĐ¸ŅŅ‚ĐĩĐŧŅ–)", + "theme_setting_theme_subtitle": "НаĐģĐ°ŅˆŅ‚ŅƒĐ˛Đ°ĐŊĐŊŅ Ņ‚ĐĩĐŧи ĐžŅ„ĐžŅ€ĐŧĐģĐĩĐŊĐŊŅ ĐˇĐ°ŅŅ‚ĐžŅŅƒĐŊĐē҃", + "theme_setting_three_stage_loading_subtitle": "ĐĸŅ€Đ¸ĐĩŅ‚Đ°ĐŋĐŊĐĩ СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŧĐžĐļĐĩ ĐŋŅ–Đ´Đ˛Đ¸Ņ‰Đ¸Ņ‚Đ¸ ŅˆĐ˛Đ¸Đ´ĐēŅ–ŅŅ‚ŅŒ Đ˛Ņ–Đ´ĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ, аĐģĐĩ СĐŊĐ°Ņ‡ĐŊĐž ĐˇĐąŅ–ĐģŅŒŅˆĐ¸Ņ‚ŅŒ ĐŊаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŊа ĐŧĐĩŅ€ĐĩĐļ҃", + "theme_setting_three_stage_loading_title": "ĐĸŅ€Đ¸ĐĩŅ‚Đ°ĐŋĐŊĐĩ СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "then": "ĐĸĐžĐ´Ņ–", "they_will_be_merged_together": "Đ‡Ņ… ĐąŅƒĐ´Đĩ Ой'Ņ”Đ´ĐŊаĐŊĐž", "third_party_resources": "ĐĄŅ‚ĐžŅ€ĐžĐŊĐŊŅ– Ņ€ĐĩŅŅƒŅ€ŅĐ¸", @@ -2128,17 +2264,23 @@ "trash_all": "ПĐĩŅ€ĐĩĐŧŅ–ŅŅ‚Đ¸Ņ‚Đ¸ Đ˛ŅĐĩ Đ´Đž ĐēĐžŅˆĐ¸Đēа", "trash_count": "ĐšĐžŅˆĐ¸Đē {count, number}", "trash_delete_asset": "ĐŖ ĐēĐžŅˆĐ¸Đē/ВидаĐģĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", + "trash_emptied": "ĐšĐžŅˆĐ¸Đē ĐžŅ‡Đ¸Ņ‰ĐĩĐŊĐž", "trash_no_results_message": "Đ¤ĐžŅ‚Đž Ņ‚Đ° Đ˛Ņ–Đ´ĐĩĐž С ĐēĐžŅˆĐ¸Đēа С'ŅĐ˛ĐģŅŅ‚Đ¸ĐŧŅƒŅ‚ŅŒŅŅ Ņ‚ŅƒŅ‚.", "trash_page_delete_all": "ВидаĐģĐ¸Ņ‚Đ¸ ҃ҁĐĩ", + "trash_page_empty_trash_dialog_content": "ĐĨĐžŅ‡ĐĩŅ‚Đĩ ĐžŅ‡Đ¸ŅŅ‚Đ¸Ņ‚Đ¸ ĐēĐžŅˆĐ¸Đē? ĐĻŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ĐąŅƒĐ´Đĩ ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž видаĐģĐĩĐŊĐž С Immich", "trash_page_info": "ПĐĩŅ€ĐĩĐŧҖ҉ĐĩĐŊŅ– Đ´Đž ĐēĐžŅˆĐ¸Đēа Ņ„Đ°ĐšĐģи ĐąŅƒĐ´Đĩ ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž видаĐģĐĩĐŊĐž ҇ĐĩŅ€ĐĩС {days} Đ´ĐŊŅ–Đ˛", + "trash_page_no_assets": "НĐĩĐŧĐ°Ņ” ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ҃ ĐēĐžŅˆĐ¸Đē҃", + "trash_page_restore_all": "Đ’Ņ–Đ´ĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ҃ҁĐĩ", + "trash_page_select_assets_btn": "Đ’Đ¸ĐąŅ€Đ°Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸", + "trash_page_title": "ĐšĐžŅˆĐ¸Đē ({count})", "trashed_items_will_be_permanently_deleted_after": "ЕĐģĐĩĐŧĐĩĐŊŅ‚Đ¸ ҃ ĐēĐžŅˆĐ¸Đē҃ ĐąŅƒĐ´Đĩ ĐžŅŅ‚Đ°Ņ‚ĐžŅ‡ĐŊĐž видаĐģĐĩĐŊĐž ҇ĐĩŅ€ĐĩС {days, plural, one {# Đ´ĐĩĐŊҌ} few {# Đ´ĐŊŅ–} many {# Đ´ĐŊŅ–Đ˛} other {# Đ´ĐŊŅ–Đ˛}}.", "trigger": "ĐĸŅ€Đ¸ĐŗĐĩŅ€", - "trigger_asset_metadata_extraction": "МĐĩŅ‚Đ°Đ´Đ°ĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ° Đ˛Đ¸Đ´ĐžĐąŅƒŅ‚Đž", - "trigger_asset_metadata_extraction_description": "ĐĄĐŋŅ€Đ°Ņ†ŅŒĐžĐ˛ŅƒŅ”, ĐēĐžĐģи Đ˛Đ¸Đ´ĐžĐąŅƒŅ‚Đž ĐŧĐĩŅ‚Đ°Đ´Đ°ĐŊŅ– Exif ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", "trigger_asset_uploaded": "ЕĐģĐĩĐŧĐĩĐŊŅ‚ виваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", "trigger_asset_uploaded_description": "ĐĄĐŋŅ€Đ°Ņ†ŅŒĐžĐ˛ŅƒŅ”, ĐēĐžĐģи виваĐŊŅ‚Đ°ĐļĐĩĐŊĐž ĐŊОвиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", + "trigger_description": "ĐŸĐžĐ´Ņ–Ņ, ŅĐēа СаĐŋ҃ҁĐēĐ°Ņ” Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ", "trigger_person_recognized": "Đ›ŅŽĐ´Đ¸ĐŊ҃ Ņ€ĐžĐˇĐŋŅ–ĐˇĐŊаĐŊĐž", "trigger_person_recognized_description": "ĐĄĐŋŅ€Đ°Ņ†ŅŒĐžĐ˛ŅƒŅ”, ĐēĐžĐģи Đ˛Đ¸ŅĐ˛ĐģŅŅ”Ņ‚ŅŒŅŅ ĐģŅŽĐ´Đ¸ĐŊа", + "trigger_type": "ĐĸиĐŋ Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Đ°", "troubleshoot": "ĐŖŅŅƒĐŊĐĩĐŊĐŊŅ ĐŊĐĩĐŋĐžĐģадОĐē", "type": "ĐĸиĐŋ", "unable_to_change_pin_code": "НĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ СĐŧŅ–ĐŊĐ¸Ņ‚Đ¸ PIN-ĐēОд", @@ -2154,7 +2296,6 @@ "unknown": "НĐĩĐ˛Ņ–Đ´ĐžĐŧĐž", "unknown_country": "НĐĩĐ˛Ņ–Đ´ĐžĐŧа ĐēŅ€Đ°Ņ—ĐŊа", "unknown_date": "НĐĩĐ˛Ņ–Đ´ĐžĐŧа Đ´Đ°Ņ‚Đ°", - "unknown_schema": "НĐĩĐ˛Ņ–Đ´ĐžĐŧа ҁ҅ĐĩĐŧа", "unknown_year": "НĐĩĐ˛Ņ–Đ´ĐžĐŧиК ҀҖĐē", "unlimited": "БĐĩС ОйĐŧĐĩĐļĐĩĐŊҌ", "unlink_motion_video": "Đ’Ņ–Đ´'Ņ”Đ´ĐŊĐ°Ņ‚Đ¸ Ņ€ŅƒŅ…ĐžĐŧĐĩ Đ˛Ņ–Đ´ĐĩĐž", @@ -2167,20 +2308,23 @@ "unsaved_change": "НĐĩСйĐĩŅ€ĐĩĐļĐĩĐŊа СĐŧŅ–ĐŊа", "unselect_all": "ЗĐŊŅŅ‚Đ¸ Đ˛Đ¸ĐąŅ–Ņ€ С ŅƒŅŅ–Ņ…", "unselect_all_duplicates": "ЗĐŊŅŅ‚Đ¸ Đ˛Đ¸ĐąŅ–Ņ€ С ŅƒŅŅ–Ņ… Đ´ŅƒĐąĐģŅ–ĐēĐ°Ņ‚Ņ–Đ˛", + "unselect_all_in": "ЗĐŊŅŅ‚Đ¸ Đ˛Đ¸ĐąŅ–Ņ€ ҃ {group}", "unstack": "Đ ĐžĐˇĐŗŅ€ŅƒĐŋŅƒĐ˛Đ°Ņ‚Đ¸", "unstack_action_prompt": "{count} — Ņ€ĐžĐˇĐŗŅ€ŅƒĐŋОваĐŊĐž", "unstacked_assets_count": "Đ ĐžĐˇĐŗŅ€ŅƒĐŋОваĐŊĐž {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "unsupported_field_type": "НĐĩĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒĐ˛Đ°ĐŊиК Ņ‚Đ¸Đŋ ĐŋĐžĐģŅ", "unsupported_file_type": "ФаКĐģ {file} ĐŊĐĩ Đ˛Đ´Đ°Ņ”Ņ‚ŅŒŅŅ виваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸, ĐžŅĐēŅ–ĐģҌĐēи Ņ‚Đ¸Đŋ Ņ„Đ°ĐšĐģ҃ {type} ĐŊĐĩ ĐŋŅ–Đ´Ņ‚Ņ€Đ¸ĐŧŅƒŅ”Ņ‚ŅŒŅŅ.", "untagged": "БĐĩС Ņ‚ĐĩĐŗŅ–Đ˛", + "untitled_workflow": "БĐĩĐˇŅ–ĐŧĐĩĐŊĐŊа Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ", "up_next": "ĐĐ°ŅŅ‚ŅƒĐŋĐŊĐĩ", "update_location_action_prompt": "ОĐŊĐžĐ˛Đ¸Ņ‚Đ¸ ĐŧҖҁ҆Đĩ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ({count}) Са Đ´ĐžĐŋĐžĐŧĐžĐŗĐžŅŽ:", "updated_at": "ОĐŊОвĐģĐĩĐŊĐž", "updated_password": "ĐŸĐ°Ņ€ĐžĐģҌ ĐžĐŊОвĐģĐĩĐŊĐž", "upload": "ВиваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸", "upload_concurrency": "ОдĐŊĐžŅ‡Đ°ŅĐŊŅ– виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", - "upload_day_count": "{date}: {count, plural, one {# СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ} other {# СаваĐŊŅ‚Đ°ĐļĐĩĐŊҌ}}", "upload_details": "ДĐĩŅ‚Đ°ĐģŅ– виваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", + "upload_dialog_info": "ĐĨĐžŅ‡ĐĩŅ‚Đĩ ŅŅ‚Đ˛ĐžŅ€Đ¸Ņ‚Đ¸ Ņ€ĐĩСĐĩŅ€Đ˛ĐŊ҃ ĐēĐžĐŋŅ–ŅŽ Đ˛Đ¸ĐąŅ€Đ°ĐŊĐ¸Ņ… ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛ ĐŊа ҁĐĩŅ€Đ˛ĐĩҀҖ?", + "upload_dialog_title": "ВиваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "upload_error_with_count": "НĐĩ вдаĐģĐžŅŅ виваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ {count, plural, one {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚} few {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸} many {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛} other {# ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Ņ–Đ˛}}", "upload_errors": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ СавĐĩŅ€ŅˆĐĩĐŊĐž С {count, plural, one {# ĐŋĐžĐŧиĐģĐēĐžŅŽ} few {# ĐŋĐžĐŧиĐģĐēаĐŧи} many {# ĐŋĐžĐŧиĐģĐēаĐŧи} other {# ĐŋĐžĐŧиĐģĐēаĐŧи}}, ĐžĐŊĐžĐ˛Ņ–Ņ‚ŅŒ ŅŅ‚ĐžŅ€Ņ–ĐŊĐē҃, Ņ‰ĐžĐą ĐŋĐžĐąĐ°Ņ‡Đ¸Ņ‚Đ¸ ĐŊĐžĐ˛Ņ– виваĐŊŅ‚Đ°ĐļĐĩĐŊŅ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸.", "upload_finished": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ СавĐĩŅ€ŅˆĐĩĐŊĐž", @@ -2190,13 +2334,9 @@ "upload_status_errors": "ПоĐŧиĐģĐēи", "upload_status_uploaded": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐž", "upload_success": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐž. ОĐŊĐžĐ˛Ņ–Ņ‚ŅŒ ŅŅ‚ĐžŅ€Ņ–ĐŊĐē҃, Ņ‰ĐžĐą ĐŋĐžĐąĐ°Ņ‡Đ¸Ņ‚Đ¸ ĐŊĐžĐ˛Ņ– ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ¸.", - "upload_to_album_body": "ДĐģŅ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Ņ–Đ˛ ŅĐēŅ– ĐŋОвĐŊĐžŅ†Ņ–ĐŊĐŊĐž ĐŊĐĩ виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒŅŽŅ‚ŅŒ Ņ„ŅƒĐŊĐēŅ†Ņ–ŅŽ Ņ€ŅƒŅ‡ĐŊĐžĐŗĐž СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ, Ņ‚ĐĩĐŋĐĩŅ€ ви ĐŧĐžĐļĐĩŅ‚Đĩ ĐžĐąŅ€Đ°Ņ‚Đ¸, Ņ‰ĐžĐą Đ´ĐžĐ´Đ°Ņ‚Đ¸ ĐģĐžĐēаĐģҌĐŊŅ– Ņ„ĐžŅ‚Đž ĐŊаĐŋŅ€ŅĐŧ҃ Đ´Đž аĐģŅŒĐąĐžĐŧ҃ ĐēĐžĐģи ви Ņ—Ņ… СаваĐŊŅ‚Đ°ĐļŅƒŅ”Ņ‚Đĩ, ĐąŅ–ĐģҌ҈Đĩ ĐŊĐĩ ҂ҀĐĩйа СаваĐŊŅ‚Đ°ĐļŅƒĐ˛Đ°Ņ‚Đ¸ Ņ‚Đ° ĐŋĐžŅ‚Ņ–Đŧ Đ´ĐžĐ´Đ°Đ˛Đ°Ņ‚Đ¸ Đ´Đž аĐģŅŒĐąĐžĐŧ҃ ĐŋŅ–ĐˇĐŊŅ–ŅˆĐĩ.", - "upload_to_album_title": "ЗаваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ ĐŊаĐŋŅ€ŅĐŧ҃ Đ´Đž аĐģŅŒĐąĐžĐŧ҃", "upload_to_immich": "ВиваĐŊŅ‚Đ°ĐļĐ¸Ņ‚Đ¸ в Immich ({count})", "uploading": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", "uploading_media": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ ĐŧĐĩĐ´Ņ–Đ°", - "uploads": "ВиваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ", - "uploads_count": "{count, plural, one {# СаваĐŊŅ‚Đ°ĐļĐĩĐŊĐŊŅ} other {# СаваĐŊŅ‚Đ°ĐļĐĩĐŊҌ}}", "url": "URL", "usage": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ", "use_biometric": "ВиĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ ĐąŅ–ĐžĐŧĐĩŅ‚Ņ€Ņ–ŅŽ", @@ -2204,7 +2344,6 @@ "use_browser_locale_description": "Đ¤ĐžŅ€ĐŧĐ°Ņ‚ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´Đ°Ņ‚Đ¸, Ņ‡Đ°Ņ Ņ‚Đ° Ņ‡Đ¸ŅĐģа Đ˛Ņ–Đ´ĐŋĐžĐ˛Ņ–Đ´ĐŊĐž Đ´Đž ĐģĐžĐēаĐģŅ– Đ˛Đ°ŅˆĐžĐŗĐž ĐąŅ€Đ°ŅƒĐˇĐĩŅ€Đ°", "use_current_connection": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ĐŋĐžŅ‚ĐžŅ‡ĐŊĐĩ С'Ņ”Đ´ĐŊаĐŊĐŊŅ", "use_custom_date_range": "ĐĐ°Ņ‚ĐžĐŧŅ–ŅŅ‚ŅŒ виĐēĐžŅ€Đ¸ŅŅ‚ĐžĐ˛ŅƒĐ˛Đ°Ņ‚Đ¸ Đ´ĐžĐ˛Ņ–ĐģҌĐŊиК Đ´Ņ–Đ°ĐŋаСОĐŊ Đ´Đ°Ņ‚", - "use_template": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ŅˆĐ°ĐąĐģĐžĐŊ", "user": "ĐšĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡", "user_has_been_deleted": "ĐšĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ° видаĐģĐĩĐŊĐž.", "user_id": "ID ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", @@ -2214,6 +2353,7 @@ "user_privacy": "КоĐŊŅ„Ņ–Đ´ĐĩĐŊŅ†Ņ–ĐšĐŊŅ–ŅŅ‚ŅŒ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "user_purchase_settings": "ĐŸŅ€Đ¸Đ´ĐąĐ°ĐŊĐŊŅ", "user_purchase_settings_description": "КĐĩŅ€ŅƒĐ˛Đ°ĐŊĐŊŅ Đē҃ĐŋŅ–Đ˛ĐģĐĩŅŽ", + "user_role_set": "ĐŸŅ€Đ¸ĐˇĐŊĐ°Ņ‡Đ¸Ņ‚Đ¸ {user} ĐŊа Ņ€ĐžĐģҌ {role}", "user_usage_detail": "ДĐĩŅ‚Đ°ĐģŅ– виĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "user_usage_stats": "ĐĄŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đēа виĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ ОйĐģŅ–ĐēĐžĐ˛ĐžĐŗĐž СаĐŋĐ¸ŅŅƒ", "user_usage_stats_description": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ŅŅ‚Đ°Ņ‚Đ¸ŅŅ‚Đ¸Đē҃ виĐēĐžŅ€Đ¸ŅŅ‚Đ°ĐŊĐŊŅ ОйĐģŅ–ĐēĐžĐ˛ĐžĐŗĐž СаĐŋĐ¸ŅŅƒ", @@ -2223,6 +2363,7 @@ "utilities": "ĐŖŅ‚Đ¸ĐģŅ–Ņ‚Đ¸", "validate": "ПĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đ¸Ņ‚Đ¸", "validate_endpoint_error": "Đ‘ŅƒĐ´ŅŒ ĐģĐ°ŅĐēа, ввĐĩĐ´Ņ–Ņ‚ŅŒ Đ´Ņ–ĐšŅĐŊ҃ URL-Đ°Đ´Ņ€Đĩҁ҃", + "validation_error": "ПоĐŧиĐģĐēа ĐŋĐĩŅ€ĐĩĐ˛Ņ–Ņ€Đēи", "variables": "ЗĐŧŅ–ĐŊĐŊŅ–", "version": "ВĐĩŅ€ŅŅ–Ņ", "version_announcement_closing": "Đ’Đ°Ņˆ Đ´Ņ€ŅƒĐŗ, АĐģĐĩĐēҁ", @@ -2232,9 +2373,9 @@ "video": "Đ’Ņ–Đ´ĐĩĐž", "video_hover_setting": "Đ’Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Đ¸ Đ˛Ņ–Đ´ĐĩĐž ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŊавĐĩĐ´ĐĩĐŊĐŊŅ ĐēŅƒŅ€ŅĐžŅ€Ņƒ ĐŧĐ¸ŅˆŅ–", "video_hover_setting_description": "Đ’Ņ–Đ´Ņ‚Đ˛ĐžŅ€ŅŽĐ˛Đ°Ņ‚Đ¸ ĐŧŅ–ĐŊŅ–Đ°Ņ‚ŅŽŅ€Ņƒ Đ˛Ņ–Đ´ĐĩĐž ĐŋŅ–Đ´ Ņ‡Đ°Ņ ĐŊавĐĩĐ´ĐĩĐŊĐŊŅ ĐēŅƒŅ€ŅĐžŅ€Đ° ĐŊа ĐĩĐģĐĩĐŧĐĩĐŊŅ‚. ĐĐ°Đ˛Ņ–Ņ‚ŅŒ ŅĐēŅ‰Đž виĐŧĐēĐŊĐĩĐŊĐž, Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ ĐŧĐžĐļĐŊа Ņ€ĐžĐˇĐŋĐžŅ‡Đ°Ņ‚Đ¸, ĐŊĐ°Đ˛Ņ–Đ˛ŅˆĐ¸ ĐēŅƒŅ€ŅĐžŅ€ ĐŊа ĐŋŅ–ĐēŅ‚ĐžĐŗŅ€Đ°Đŧ҃ Đ˛Ņ–Đ´Ņ‚Đ˛ĐžŅ€ĐĩĐŊĐŊŅ.", - "video_quality": "Đ¯ĐēŅ–ŅŅ‚ŅŒ Đ˛Ņ–Đ´ĐĩĐž", "videos": "Đ’Ņ–Đ´ĐĩĐž", "videos_count": "{count, plural, one {# Đ˛Ņ–Đ´ĐĩĐž} few {# Đ˛Ņ–Đ´ĐĩĐž} many {# Đ˛Ņ–Đ´ĐĩĐž} other {# Đ˛Ņ–Đ´ĐĩĐž}}", + "videos_only": "Đ›Đ¸ŅˆĐĩ Đ˛Ņ–Đ´ĐĩĐž", "view": "ПĐĩŅ€ĐĩĐŗĐģŅĐ´", "view_album": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ аĐģŅŒĐąĐžĐŧ", "view_all": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ Đ˛ŅĐĩ", @@ -2243,29 +2384,29 @@ "view_details": "ДĐĩŅ‚Đ°ĐģҌĐŊŅ–ŅˆĐĩ", "view_in_timeline": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ в Ņ…Ņ€ĐžĐŊĐžĐģĐžĐŗŅ–Ņ—", "view_link": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", + "view_links": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ĐŋĐžŅĐ¸ĐģаĐŊĐŊŅ", "view_name": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸", "view_next_asset": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ĐŊĐ°ŅŅ‚ŅƒĐŋĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "view_previous_asset": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ĐŋĐžĐŋĐĩŅ€ĐĩĐ´ĐŊŅ–Đš ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", "view_qr_code": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ QR-ĐēОд", "view_similar_photos": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ŅŅ…ĐžĐļŅ– Ņ„ĐžŅ‚Đž", "view_stack": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ҁ҂ĐĩĐē", + "view_user": "ПĐĩŅ€ĐĩĐŗĐģŅĐŊŅƒŅ‚Đ¸ ĐēĐžŅ€Đ¸ŅŅ‚ŅƒĐ˛Đ°Ņ‡Đ°", "viewer_remove_from_stack": "ВиĐģŅƒŅ‡Đ¸Ņ‚Đ¸ ĐˇŅ– ҁ҂ĐĩĐē҃", + "viewer_stack_use_as_main_asset": "ВиĐēĐžŅ€Đ¸ŅŅ‚Đ°Ņ‚Đ¸ ŅĐē ĐžŅĐŊОвĐŊиК ĐĩĐģĐĩĐŧĐĩĐŊŅ‚", + "viewer_unstack": "Đ ĐžĐˇĐąĐ¸Ņ‚Đ¸ ҁ҂ĐĩĐē", "visibility": "ВидиĐŧŅ–ŅŅ‚ŅŒ", "visibility_changed": "ВидиĐŧŅ–ŅŅ‚ŅŒ СĐŧŅ–ĐŊĐĩĐŊĐž Đ´ĐģŅ {count, plural, one {# ĐģŅŽĐ´Đ¸ĐŊи} few {# ĐģŅŽĐ´ĐĩĐš} many {# ĐģŅŽĐ´ĐĩĐš} other {# ĐģŅŽĐ´ĐĩĐš}}", "visual": "Đ’Ņ–ĐˇŅƒĐ°ĐģҌĐŊиК", + "visual_builder": "Đ’Ņ–ĐˇŅƒĐ°ĐģҌĐŊиК ĐēĐžĐŊŅŅ‚Ņ€ŅƒĐēŅ‚ĐžŅ€", "waiting": "ĐŖ ҇ĐĩŅ€ĐˇŅ–", "waiting_count": "ĐŖ ҇ĐĩŅ€ĐˇŅ–: {count}", "warning": "ПоĐŋĐĩŅ€ĐĩĐ´ĐļĐĩĐŊĐŊŅ", "week": "ĐĸиĐļĐ´ĐĩĐŊҌ", "welcome": "Đ›Đ°ŅĐēавО ĐŋŅ€ĐžŅĐ¸ĐŧĐž", "welcome_to_immich": "Đ›Đ°ŅĐēавО ĐŋŅ€ĐžŅĐ¸ĐŧĐž Đ´Đž Immich", - "whats_new": "ĐŠĐž ĐŊĐžĐ˛ĐžĐŗĐž", - "whats_new_settings_subtitle": "Đ”Đ¸Đ˛Ņ–Ņ‚ŅŒŅŅ, Ņ‰Đž Ņ” ĐŊĐžĐ˛ĐžĐŗĐž в Immich", - "whats_new_version": "ВĐĩŅ€ŅŅ–Ņ {version}", - "when": "КоĐģи", "width": "Đ¨Đ¸Ņ€Đ¸ĐŊа", "wifi_name": "Назва Wi-Fi", - "workflow": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ", "workflow_delete_prompt": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ видаĐģĐ¸Ņ‚Đ¸ Ņ†ŅŽ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ?", "workflow_deleted": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ видаĐģĐĩĐŊĐž", "workflow_description": "ОĐŋĐ¸Ņ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ—", @@ -2275,17 +2416,17 @@ "workflow_name": "Назва Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ—", "workflow_navigation_prompt": "Ви вĐŋĐĩвĐŊĐĩĐŊŅ–, Ņ‰Đž Ņ…ĐžŅ‡ĐĩŅ‚Đĩ Đ˛Đ¸ĐšŅ‚Đ¸ ĐąĐĩС СйĐĩŅ€ĐĩĐļĐĩĐŊĐŊŅ СĐŧŅ–ĐŊ?", "workflow_summary": "ЗвĐĩĐ´ĐĩĐŊĐŊŅ Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ—", - "workflow_templates": "ШайĐģĐžĐŊи Đ°Đ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Đš", "workflow_update_success": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ ĐžĐŊОвĐģĐĩĐŊĐž", + "workflow_updated": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–ŅŽ ĐžĐŊОвĐģĐĩĐŊĐž", "workflows": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ—", "workflows_help_text": "ĐĐ˛Ņ‚ĐžĐŧĐ°Ņ‚Đ¸ĐˇĐ°Ņ†Ņ–Ņ— виĐēĐžĐŊŅƒŅŽŅ‚ŅŒ Đ´Ņ–Ņ— С ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°Đŧи СаĐģĐĩĐļĐŊĐž Đ˛Ņ–Đ´ Ņ‚Ņ€Đ¸ĐŗĐĩŅ€Ņ–Đ˛ Ņ– ҄ҖĐģŅŒŅ‚Ņ€Ņ–Đ˛", "wrong_pin_code": "НĐĩĐŋŅ€Đ°Đ˛Đ¸ĐģҌĐŊиК PIN-ĐēОд", - "x_of_total": "{x}/{total}", "year": "Đ Ņ–Đē", "years_ago": "{years, plural, one {# ҀҖĐē} few {# Ņ€ĐžĐēи} many {# Ņ€ĐžĐēŅ–Đ˛} other {# Ņ€ĐžĐēŅ–Đ˛}} Ņ‚ĐžĐŧ҃", "yes": "ĐĸаĐē", "you_dont_have_any_shared_links": "ĐŖ Đ˛Đ°Ņ ĐŊĐĩĐŧĐ°Ņ” ҁĐŋŅ–ĐģҌĐŊĐ¸Ņ… ĐŋĐžŅĐ¸ĐģаĐŊҌ", "your_wifi_name": "Назва Đ˛Đ°ŅˆĐžŅ— Wi-Fi ĐŧĐĩŅ€ĐĩĐļŅ–", "zero_to_clear_rating": "ĐŊĐ°Ņ‚Đ¸ŅĐŊŅ–Ņ‚ŅŒ 0, Ņ‰ĐžĐą ҁĐēиĐŊŅƒŅ‚Đ¸ Ņ€ĐĩĐšŅ‚Đ¸ĐŊĐŗ ĐĩĐģĐĩĐŧĐĩĐŊŅ‚Đ°", - "zoom_image": "Đ—ĐąŅ–ĐģŅŒŅˆĐ¸Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ" + "zoom_image": "Đ—ĐąŅ–ĐģŅŒŅˆĐ¸Ņ‚Đ¸ ĐˇĐžĐąŅ€Đ°ĐļĐĩĐŊĐŊŅ", + "zoom_to_bounds": "Đ—ĐąŅ–ĐģŅŒŅˆĐ¸Ņ‚Đ¸ ĐŧĐ°ŅŅˆŅ‚Đ°Đą Đ´Đž ĐŧĐĩĐļ" } diff --git a/i18n/ur.json b/i18n/ur.json index 2f9012a9ed..5329f74c5c 100644 --- a/i18n/ur.json +++ b/i18n/ur.json @@ -10,31 +10,39 @@ "active": "ŲØšØ§Ų„", "active_count": "ŲØšØ§Ų„: {count}", "activity": "ØŗØąÚ¯ØąŲ…ÛŒ", + "activity_changed": "ØŗØąÚ¯ØąŲ…ÛŒ {enabled, select, true {ŲØšØ§Ų„ ہے} other {ØēÛŒØą ŲØšØ§Ų„ ہے}}", "add": "Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_a_description": "ØĒ؁ØĩÛŒŲ„ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_a_location": "Ų…Ų‚Ø§Ų… Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_a_name": "Ų†Ø§Ų… ڊا Ø§Ų†Ø¯ØąØ§ØŦ ÚŠØąÛŒÚē", "add_a_title": "ØšŲ†ŲˆØ§Ų† ڊا Ø§Ų†Ø¯ØąØ§ØŦ ÚŠØąÛŒÚē", "add_action": "ØšŲ…Ų„ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_action_description": "ØšŲ…Ų„ Ø´Ø§Ų…Ų„ ÚŠØąŲ†Û’ ÚŠÛ’ Ų„ÛŒÛ’ یہاÚē ÚŠŲ„ÚŠ ÚŠØąÛŒÚē", "add_assets": "اØĢاØĢے Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_birthday": "ØŗØ§Ų„Ú¯ØąÛ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_endpoint": "Ø§ÛŒŲ†Úˆ ŲžŲˆØ§ØĻŲ†Ųš Ø¯ØąØŦ ÚŠØąÛŒÚē", "add_exclusion_pattern": "ØŽØ§ØąØŦ ÚŠØąŲ†Û’ ڊا Ų†Ų…ŲˆŲ†Û Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_filter": "ŲŲ„ŲšØą Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_filter_description": "ŲŲ„ŲšØą ÚŠÛŒ Ø´ØąØˇ Ø´Ø§Ų…Ų„ ÚŠØąŲ†Û’ ÚŠÛ’ Ų„ÛŒÛ’ ÚŠŲ„ÚŠ ÚŠØąÛŒÚē", "add_location": "ØŦگہ Ø¯ØąØŦ ÚŠØąÛŒÚē", + "add_more_users": "Ų…Ø˛ÛŒØ¯ ØĩØ§ØąŲÛŒŲ† Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_partner": "ØŗØ§ØĒÚžÛŒ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_path": "ØąØ§ØŗØĒہ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_photos": "ØĒØĩØ§ŲˆÛŒØą Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", - "add_step": "Ų…ØąØ­Ų„Û Ø¨Ų†Ø§ØĻیÚē", "add_tag": "ŲšÛŒÚ¯ Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_to": "Ø§Øŗ Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚēâ€Ļ", "add_to_album": "Ø§Ų„Ø¨Ų… Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_to_album_bottom_sheet_added": "{album} Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąØ¯ÛŒØ§Ú¯ÛŒØ§", "add_to_album_bottom_sheet_already_exists": "ŲžÛŲ„Û’ ØŗÛ’ ہی {album} Ų…ÛŒÚē Ų…ŲˆØŦŲˆØ¯ ہے", "add_to_album_bottom_sheet_some_local_assets": "کچھ Ų…Ų‚Ø§Ų…ÛŒ اØĢاØĢے Ø§Ų„Ø¨Ų… Ų…ÛŒÚē Ø´Ø§Ų…Ų„ Ų†ÛÛŒÚē ڊیے ØŦا ØŗÚŠÛ’", + "add_to_album_toggle": "Ų…Ų†ØĒ؎ب ÚŠØąŲ†Û’ ڊا ØˇØąÛŒŲ‚Û {album} ÚŠÛ’ Ų„ÛŒÛ’", "add_to_albums": "Ø§Ų„Ø¨Ų…ŲˆÚē Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠÛŒØŦیے", "add_to_albums_count": "Ø§Ų„Ø¨Ų…ŲˆÚē Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠÛŒØŦیے ({count})", "add_to_bottom_bar": "Ø§Øŗ Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_to_shared_album": "Ų…Ø´ØĒØąÚŠÛ Ø§Ų„Ø¨Ų… Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_upload_to_stack": "Ø§Ųž Ų„ŲˆÚˆ ÚŠŲˆ Ø§ØŗŲšÛŒÚŠ Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "add_url": "URL Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", + "add_workflow_step": "ŲˆØąÚŠ ŲŲ„Ųˆ ڊا Ų…ØąØ­Ų„Û Ø´Ø§Ų…Ų„ ÚŠØąÛŒÚē", "added_to_archive": "ØĸØąÚŠØ§ØĻÛŒŲˆ Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØą دیا گیا", "added_to_favorites": "ŲžØŗŲ†Ø¯ÛŒØ¯Û Ų…ÛŒÚē Ø´Ø§Ų…Ų„ ÚŠØąØ¯ÛŒØ§ گیا", "added_to_favorites_count": "ŲžØŗŲ†Ø¯ÛŒØ¯Û Ų…ÛŒÚē {count, number} Ø´Ø§Ų…Ų„ ڊیے Ú¯ØĻے", @@ -65,11 +73,7 @@ "confirm_reprocess_all_faces": "ڊیا ØĸŲž ŲˆØ§Ų‚ØšÛŒ ØĒŲ…Ø§Ų… Ú†ÛØąŲˆÚē ÚŠŲˆ Ø¯ŲˆØ¨Ø§ØąÛ ŲžØąŲˆØŗÛŒØŗ ÚŠØąŲ†Ø§ چاہØĒے ہیÚ稟 Ø§Øŗ ØŗÛ’ Ų†Ø§Ų… ŲˆØ§Ų„Û’ Ø§ŲØąØ§Ø¯ بڞی ØĩØ§Ų ÛŲˆ ØŦاØĻیÚē گے۔", "confirm_user_password_reset": "ڊیا ØĸŲž {user} ڊا ŲžØ§Øŗ ŲˆØąÚˆ ØąÛŒ ØŗÛŒŲš ÚŠØąŲ†Ø§ چاہØĒے ہیÚ稟", "confirm_user_pin_code_reset": "ڊیا ØĸŲž {user} ڊا ŲžŲ† ÚŠŲˆÚˆ ØąÛŒ ØŗÛŒŲš ÚŠØąŲ†Ø§ چاہØĒے ہیÚ稟", - "copy_config_to_clipboard_description": "Ø§ŲžŲ†ÛŒ Ų…ŲˆØŦŲˆØ¯Û ØŗØŗŲšŲ… ÚŠÛ’ Ų†Ø¸Ø§Ų… ÚŠÛŒ ØĒØąØĒیب JSON ÚŠÛŒ Ø´ÚŠŲ„ Ų…ÛŒÚē ÚŠŲ„Ųž Ø¨ŲˆØąÚˆ Ų…ÛŒÚē ÚŠØ§ŲžÛŒ ÚŠØąÛŒÚē", "create_job": "ÚŠØ§Ų… Ø¨Ų†Ø§ØĻیÚē", - "disable_login": "Ų„Ø§Ú¯ Ø§Ų† Ø¨Ų†Ø¯ ÚŠØąÛŒÚē", - "download_csv": "CSV ÚˆØ§ŲˆŲ† Ų„ŲˆÚˆ ÚŠØąÛŒÚē", - "duplicate_detection_job_description": "Ø§ŲžŲ†ÛŒ اØŦØąØ§Û‚ ŲžØą Ų…Ø´ÛŒŲ† Ų„ØąŲ†Ų†Ú¯ Ú†Ų„Ø§ ÚŠØą ایڊ ØŦÛŒØŗÛŒ ØĒØĩØ§ŲˆÛŒØą ڊا ŲžØĒہ Ų„Ú¯Ø§ ØĻے۔ \"Smart Search\" ŲžØą Ø§Ų†Ø­ØĩØ§Øą ÚŠØąØĒا ہے", "face_detection": "Ú†ÛØąÛ’ ÚŠÛŒ ŲžÛÚ†Ø§Ų†", "failed_job_command": "ÚŠØ§Ų…: {job} ÚŠÛ’ Ų„ÛŒÛ’ ÚŠŲ…Ø§Ų†Úˆ: {command} Ų†Ø§ÚŠØ§Ų… ÛŲˆ Ú¯ØĻی", "image_preview_title": "ŲžÛŒØ´ Ų†Ø¸Ø§ØąÛ", @@ -78,6 +82,7 @@ }, "change_pin_code": "ŲžŲ† ÚŠŲˆÚˆ ØĒØ¨Ø¯ÛŒŲ„ ÚŠØąÛŒÚē", "confirm_new_pin_code": "Ų†ØĻے ŲžŲ† ÚŠŲˆÚˆ ÚŠÛŒ ØĒØĩØ¯ÛŒŲ‚ ÚŠØąÛŒÚē", + "crop_aspect_ratio_fixed": "Ų…Ų‚ØąØąÛ", "crop_aspect_ratio_free": "ØĸØ˛Ø§Ø¯", "crop_aspect_ratio_original": "اØĩŲ„", "current_pin_code": "Ų…ŲˆØŦŲˆØ¯Û ŲžŲ† ÚŠŲˆÚˆ", @@ -87,6 +92,7 @@ "free_up_space": "ØŦگہ ØŽØ§Ų„ÛŒ ÚŠØąÛŒÚē", "keep_favorites": "ŲžØŗŲ†Ø¯ÛŒØ¯Û ØąÚŠÚžÛŒÚē", "new_pin_code": "Ų†ÛŒØ§ ŲžŲ† ÚŠŲˆÚˆ", + "photos_only": "ØĩØąŲ ØĒØĩØ§ŲˆÛŒØą", "pin_code_changed_successfully": "ŲžŲ† ÚŠŲˆÚˆ ÚŠŲˆ ÚŠØ§Ų…ÛŒØ§Ø¨ÛŒ ØŗÛ’ ØĒØ¨Ø¯ÛŒŲ„ ÚŠØą دیا گیا", "pin_code_reset_successfully": "ŲžŲ† ÚŠŲˆÚˆ ÚŠØ§Ų…ÛŒØ§Ø¨ÛŒ ÚŠÛ’ ØŗØ§ØĒÚž ØąÛŒ ØŗÛŒŲš ÛŲˆ گیا", "pin_code_setup_successfully": "ŲžŲ† ÚŠŲˆÚˆ ÚŠØ§Ų…ÛŒØ§Ø¨ÛŒ ÚŠÛ’ ØŗØ§ØĒÚž ØŗÛŒŲš Ø§Ųž ÛŲˆ گیا", @@ -102,6 +108,7 @@ "version_announcement_closing": "ØĸŲž ڊا Ø¯ŲˆØŗØĒ، Ø§ÛŒŲ„ÚŠØŗ", "video": "ŲˆÛŒÚˆÛŒŲˆ", "videos": "ŲˆÛŒÚˆÛŒŲˆØ˛", + "videos_only": "ØĩØąŲ ŲˆÛŒÚˆÛŒŲˆØ˛", "view": "دیڊڞیÚē", "view_all": "ØŗØ¨ دیڊڞیÚē", "waiting": "Ø§Ų†ØĒØ¸Ø§Øą", diff --git a/i18n/uz.json b/i18n/uz.json index 35534f42b5..0967ef424b 100644 --- a/i18n/uz.json +++ b/i18n/uz.json @@ -1,15 +1 @@ -{ - "about": "Haqida", - "account": "Hisob", - "account_settings": "Hisob sozlamalari", - "acknowledge": "Tasdiqlash", - "action": "Amal", - "action_common_update": "Yangilash", - "action_description": "Filtrlangan resurslar ustida bajarish uchun amallar jamlanmasi", - "actions": "Amallar", - "active": "Faol", - "active_count": "Faol: {count}", - "activity": "Harakatlar", - "add": "QoĘģshish", - "add_a_description": "Tavsif kiriting" -} +{} diff --git a/i18n/vi.json b/i18n/vi.json index 308537d627..fa37d05fb2 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -5,43 +5,51 @@ "acknowledge": "Ghi nháē­n", "action": "Hành đáģ™ng", "action_common_update": "Cáē­p nháē­t", - "action_description": "Máģ™t táē­p háģŖp cÃĄc hành đáģ™ng cáē§n tháģąc hiáģ‡n trÃĒn cÃĄc tài nguyÃĒn Ä‘ÃŖ đưáģŖc láģc", + "action_description": "Máģ™t táē­p háģŖp cÃĄc hành đáģ™ng cáē§n tháģąc hiáģ‡n trÃĒn cÃĄc táģ‡p Ä‘ÃŖ đưáģŖc láģc", "actions": "Hành đáģ™ng", "active": "Đang hoáēĄt đáģ™ng", "active_count": "HoáēĄt đáģ™ng: {count}", "activity": "HoáēĄt đáģ™ng", + "activity_changed": "HoáēĄt đáģ™ng Ä‘ÃŖ {enabled, select, true {báē­t} other {táē¯t}}", "add": "ThÃĒm", "add_a_description": "ThÃĒm mô táēŖ", "add_a_location": "ThÃĒm đáģ‹a điáģƒm", "add_a_name": "ThÃĒm tÃĒn", "add_a_title": "ThÃĒm tÃĒn", "add_action": "ThÃĒm hành đáģ™ng", - "add_assets": "ThÃĒm tài nguyÃĒn", + "add_action_description": "NháēĨn đáģƒ thÃĒm hành đáģ™ng cáē§n tháģąc hiáģ‡n", + "add_assets": "ThÃĒm áēŖnh/video", "add_birthday": "ThÃĒm sinh nháē­t", "add_endpoint": "ThÃĒm endpoint", "add_exclusion_pattern": "ThÃĒm quy táē¯c loáēĄi tráģĢ", + "add_filter": "ThÃĒm báģ™ láģc", + "add_filter_description": "NháēĨn đáģƒ thÃĒm điáģu kiáģ‡n láģc", "add_location": "ThÃĒm đáģ‹a điáģƒm", + "add_more_users": "ThÃĒm ngưáģi dÚng", "add_partner": "ThÃĒm ngưáģi thÃĸn", + "add_path": "ThÃĒm đưáģng dáēĢn", "add_photos": "ThÃĒm áēŖnh", - "add_step": "ThÃĒm bưáģ›c", "add_tag": "ThÃĒm tháēģ", "add_to": "ThÃĒm vàoâ€Ļ", "add_to_album": "ThÃĒm vào album", "add_to_album_bottom_sheet_added": "ÄÃŖ thÃĒm vào {album}", "add_to_album_bottom_sheet_already_exists": "ÄÃŖ cÃŗ sáēĩn trong {album}", - "add_to_album_bottom_sheet_some_local_assets": "Máģ™t sáģ‘ tài nguyÃĒn trÃĒn thiáēŋt báģ‹ không tháģƒ Ä‘Æ°áģŖc thÃĒm vào album", + "add_to_album_bottom_sheet_some_local_assets": "Máģ™t sáģ‘ táģ‡p trÃĒn thiáēŋt báģ‹ không tháģƒ Ä‘Æ°áģŖc thÃĒm vào album", + "add_to_album_toggle": "Báē­t táē¯t tÚy cháģn cho {album}", "add_to_albums": "ThÃĒm vào album", - "add_to_albums_count": "ÄÃŖ thÃĒm vào ({count}) album", + "add_to_albums_count": "ÄÃŖ thÃĒm vào album {count}", "add_to_bottom_bar": "ThÃĒm vào", + "add_to_shared_album": "ThÃĒm vào album chia sáēģ", "add_upload_to_stack": "TáēŖi lÃĒn thÃĒm vào nhÃŗm", "add_url": "ThÃĒm URL", + "add_workflow_step": "ThÃĒm bưáģ›c workflow", "added_to_archive": "ÄÃŖ lưu tráģ¯", "added_to_favorites": "ÄÃŖ thích", "added_to_favorites_count": "ÄÃŖ thích {count, number} máģĨc", "admin": { "add_exclusion_pattern_description": "ThÃĒm quy táē¯c loáēĄi tráģĢ. Háģ— tráģŖ sáģ­ dáģĨng kÃŊ táģą *, **, và ?. Đáģƒ báģ qua báēĨt káģŗ táģ‡p trong thư máģĨc tÃĒn \"Raw\", hÃŖy dÚng \"**/Raw/**\". Đáģƒ báģ qua cÃĄc táģ‡p cÃŗ đuôi \".tif\", hÃŖy dÚng \"**/*.tif\". Đáģƒ báģ qua máģ™t đưáģng dáēĢn cáģ‘ Ä‘áģ‹nh, hÃŖy dÚng \"/path/to/ignore/**\".", "admin_user": "QuáēŖn tráģ‹ viÃĒn", - "asset_offline_description": "Tài nguyÃĒn thư viáģ‡n ngoài này không cÃ˛n trÃĒn áģ• Ä‘ÄŠa và Ä‘ÃŖ báģ‹ chuyáģƒn vào thÚng rÃĄc. Náēŋu táģ‡p Ä‘ÃŖ báģ‹ di chuyáģƒn trong thư viáģ‡n, kiáģƒm tra dÃ˛ng tháģi gian cáģ§a báēĄn đáģƒ tÃŦm tài nguyÃĒn máģ›i tÆ°ÆĄng áģŠng. Đáģƒ khôi pháģĨc tài nguyÃĒn, hÃŖy đáēŖm báēŖo Immich cÃŗ tháģƒ truy cáē­p đưáģng dáēĢn táģ‡p bÃĒn dưáģ›i và quÊt láēĄi thư viáģ‡n.", + "asset_offline_description": "Táģ‡p thư viáģ‡n bÃĒn ngoài này không cÃ˛n trÃĒn áģ• Ä‘ÄŠa và Ä‘ÃŖ báģ‹ chuyáģƒn vào thÚng rÃĄc. Náēŋu táģ‡p Ä‘ÃŖ báģ‹ di chuyáģƒn trong thư viáģ‡n, kiáģƒm tra dÃ˛ng tháģi gian cáģ§a báēĄn đáģƒ tÃŦm áēŖnh máģ›i tÆ°ÆĄng áģŠng. Đáģƒ khôi pháģĨc, hÃŖy đáēŖm báēŖo Immich cÃŗ tháģƒ truy cáē­p đưáģng dáēĢn táģ‡p bÃĒn dưáģ›i và quÊt láēĄi thư viáģ‡n.", "authentication_settings": "XÃĄc tháģąc", "authentication_settings_description": "QuáēŖn lÃŊ máē­t kháēŠu, OAuth và cÃĄc cài đáēˇt xÃĄc tháģąc khÃĄc", "authentication_settings_disable_all": "BáēĄn cÃŗ cháē¯c muáģ‘n vô hiáģ‡u hÃŗa máģi phÆ°ÆĄng tháģŠc đăng nháē­p? Đăng nháē­p sáēŊ báģ‹ vô hiáģ‡u hÃŗa hoàn toàn.", @@ -62,27 +70,26 @@ "cleared_jobs": "ÄÃŖ xÃŗa cÃĄc tÃĄc váģĨ: {job}", "config_set_by_file": "CáēĨu hÃŦnh hiáģ‡n táēĄi đang đưáģŖc đáēˇt báģŸi máģ™t táģ‡p cáēĨu hÃŦnh", "confirm_delete_library": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa thư viáģ‡n {library}?", - "confirm_delete_library_assets": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa thư viáģ‡n này? Thao tÃĄc này sáēŊ xÃŗa {count, plural, one {# táģ‡p} other {toàn báģ™ # tài nguyÃĒn}} kháģi Immich và không tháģƒ hoàn tÃĄc. CÃĄc táģ‡p sáēŊ váēĢn cÃ˛n trÃĒn áģ• Ä‘ÄŠa.", + "confirm_delete_library_assets": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa thư viáģ‡n này? Thao tÃĄc này sáēŊ xÃŗa {count, plural, one {# táģ‡p} other {táēĨt cáēŖ # táģ‡p}} kháģi Immich và không tháģƒ hoàn tÃĄc. CÃĄc táģ‡p sáēŊ váēĢn cÃ˛n trÃĒn áģ• Ä‘ÄŠa.", "confirm_email_below": "Đáģƒ xÃĄc nháē­n, nháē­p \"{email}\" bÃĒn dưáģ›i", "confirm_reprocess_all_faces": "BáēĄn cÃŗ cháē¯c muáģ‘n xáģ­ lÃŊ láēĄi táēĨt cáēŖ khuôn máēˇt? Thao tÃĄc này sáēŊ xÃŗa tÃĒn ngưáģi Ä‘ÃŖ đưáģŖc gÃĄn.", "confirm_user_password_reset": "BáēĄn cÃŗ cháē¯c muáģ‘n đáēˇt láēĄi máē­t kháēŠu cáģ§a {user}?", "confirm_user_pin_code_reset": "BáēĄn cÃŗ cháē¯c muáģ‘n đáēˇt láēĄi mÃŖ PIN cáģ§a {user}?", - "copy_config_to_clipboard_description": "Sao chÊp cáēĨu hÃŦnh háģ‡ tháģ‘ng hiáģ‡n táēĄi dưáģ›i dáēĄng đáģ‘i tưáģŖng JSON vào clipboard", + "copy_config_to_clipboard_description": "Sao chÊp cáēĨu hÃŦnh háģ‡ tháģ‘ng hiáģ‡n táēĄi dưáģ›i dáēĄng đáģ‘i tưáģŖng JSON vào báģ™ nháģ› táēĄm", "create_job": "TáēĄo tÃĄc váģĨ", "cron_expression": "Biáģƒu tháģŠc Cron", "cron_expression_description": "Thiáēŋt láē­p khoáēŖng tháģi gian đáģƒ quÊt báēąng biáģƒu tháģŠc cron. Tham kháēŖo Crontab Guru đáģƒ biáēŋt thÃĒm thông tin", "cron_expression_presets": "MáēĢu biáģƒu tháģŠc Cron", "disable_login": "Vô hiáģ‡u hÃŗa đăng nháē­p", - "download_csv": "TáēŖi xuáģ‘ng CSV", - "duplicate_detection_job_description": "CháēĄy háģc mÃĄy đáģƒ phÃĄt hiáģ‡n cÃĄc tài nguyÃĒn giáģ‘ng nhau. Dáģąa vào TÃŦm kiáēŋm Thông Minh", + "duplicate_detection_job_description": "CháēĄy háģc mÃĄy đáģƒ phÃĄt hiáģ‡n cÃĄc hÃŦnh áēŖnh giáģ‘ng nhau. Dáģąa vào TÃŦm kiáēŋm Thông Minh", "exclusion_pattern_description": "Quy táē¯c loáēĄi tráģĢ dÚng đáģƒ báģ qua cÃĄc táģ‡p và thư máģĨc khi quÊt thư viáģ‡n cáģ§a báēĄn. Điáģu này háģ¯u ích náēŋu báēĄn cÃŗ cÃĄc thư máģĨc cháģŠa táģ‡p báēĄn không muáģ‘n nháē­p, cháēŗng háēĄn như cÃĄc táģ‡p RAW.", "export_config_as_json_description": "TáēŖi xuáģ‘ng cáēĨu hÃŦnh háģ‡ tháģ‘ng hiáģ‡n táēĄi dưáģ›i dáēĄng táģ‡p JSON", - "external_libraries_page_description": "Trang thư viáģ‡n ngoài cáģ§a quáēŖn tráģ‹ viÃĒn", + "external_libraries_page_description": "Trang thư viáģ‡n bÃĒn ngoài cáģ§a quáēŖn tráģ‹ viÃĒn", "face_detection": "Nháē­n diáģ‡n khuôn máēˇt", - "face_detection_description": "Sáģ­ dáģĨng háģc mÃĄy đáģƒ nháē­n diáģ‡n khuôn máēˇt trong áēŖnh. Đáģ‘i váģ›i video, sáēŊ sáģ­ dáģĨng áēŖnh thu nháģ. \"Làm máģ›i\" sáēŊ xáģ­ lÃŊ láēĄi táēĨt cáēŖ táģ‡p. \"Đáēˇt láēĄi\" sáēŊ xÃŗa háēŋt táēĨt cáēŖ dáģ¯ liáģ‡u khuôn máēˇt và nháē­n dáēĄng láēĄi. \"CÃ˛n thiáēŋu\" sáēŊ xáģ­ lÃŊ cÃĄc áēŖnh cÃ˛n thiáēŋu. CÃĄc khuôn máēˇt đưáģŖc nháē­n diáģ‡n sáēŊ đưáģŖc xáģ­ lÃŊ báģŸi tÃĄc váģĨ Nháē­n Diáģ‡n Khuôn Máēˇt đáģƒ nhÃŗm chÃēng vào nháģ¯ng ngưáģi Ä‘ÃŖ cÃŗ hoáēˇc ngưáģi máģ›i.", + "face_detection_description": "Sáģ­ dáģĨng háģc mÃĄy đáģƒ nháē­n diáģ‡n khuôn máēˇt trong áēŖnh. Đáģ‘i váģ›i video, sáēŊ sáģ­ dáģĨng áēŖnh thu nháģ. \"Làm máģ›i\" sáēŊ xáģ­ lÃŊ láēĄi táēĨt cáēŖ táģ‡p. \"Đáēˇt láēĄi\" sáēŊ xÃŗa háēŋt táēĨt cáēŖ dáģ¯ liáģ‡u khuôn máēˇt và nháē­n dáēĄng láēĄi. \"CÃ˛n thiáēŋu\" sáēŊ xáģ­ lÃŊ cÃĄc áēŖnh cÃ˛n thiáēŋu. CÃĄc khuôn máēˇt đưáģŖc phÃĄt hiáģ‡n sáēŊ đưáģŖc xáģ­ lÃŊ báģŸi tÃĄc váģĨ Nháē­n diáģ‡n khuôn máēˇt đáģƒ nhÃŗm chÃēng vào nháģ¯ng ngưáģi Ä‘ÃŖ cÃŗ hoáēˇc ngưáģi máģ›i.", "facial_recognition_job_description": "Xáēŋp nhÃŗm nháģ¯ng khuôn máēˇt Ä‘ÃŖ nháē­n diáģ‡n thành ngưáģi. Bưáģ›c này đưáģŖc tháģąc hiáģ‡n sau khi tÃĄc váģĨ Nháē­n diáģ‡n khuôn máēˇt hoàn táēĨt. \"Đáēˇt láēĄi\" sáēŊ xáēŋp nhÃŗm láēĄi táēĨt cáēŖ khuôn máēˇt. \"CÃ˛n thiáēŋu\" sáēŊ xáģ­ lÃŊ cÃĄc khuôn máēˇt chưa gÃĄn váģ›i ngưáģi nào.", "failed_job_command": "Láģ‡nh {command} không tháģąc hiáģ‡n đưáģŖc tÃĄc váģĨ: {job}", - "force_delete_user_warning": "CáēĸNH BÁO: Thao tÃĄc này sáēŊ ngay láē­p táģŠc xÃŗa ngưáģi dÚng và toàn báģ™ tài nguyÃĒn. Điáģu không tháģƒ hoàn tÃĄc và cÃĄc táģ‡p không tháģƒ khôi pháģĨc.", + "force_delete_user_warning": "CáēĸNH BÁO: Thao tÃĄc này sáēŊ ngay láē­p táģŠc xÃŗa ngưáģi dÚng và táēĨt cáēŖ áēŖnh. Điáģu không tháģƒ hoàn tÃĄc và cÃĄc táģ‡p không tháģƒ khôi pháģĨc.", "image_format": "Đáģ‹nh dáēĄng", "image_format_description": "Đáģ‹nh dáēĄng WebP dung lưáģŖng nháģ hÆĄn JPEG, nhưng mÃŖ hÃŗa cháē­m hÆĄn.", "image_fullsize_description": "áēĸnh kích thưáģ›c đáēŠy đáģ§ váģ›i thông tin metadata báģ‹ loáēĄi báģ, đưáģŖc dÚng khi phÃŗng to", @@ -94,7 +101,7 @@ "image_prefer_embedded_preview_setting_description": "DÚng áēŖnh xem trưáģ›c trong áēŖnh RAW khi cÃŗ sáēĩn đáģƒ xáģ­ lÃŊ hÃŦnh áēŖnh. Điáģu này cÃŗ tháģƒ giÃēp tÃĄi táēĄo màu sáē¯c chính xÃĄc hÆĄn cho máģ™t sáģ‘ áēŖnh, nhưng cháēĨt lưáģŖng cáģ§a áēŖnh xem trưáģ›c pháģĨ thuáģ™c vào mÃĄy áēŖnh và cÃŗ tháģƒ báģ‹ nhiáģ…u do nÊn.", "image_prefer_wide_gamut": "Ưu tiÃĒn gam màu máģŸ ráģ™ng", "image_prefer_wide_gamut_setting_description": "DÚng gam màu Display P3 đáģƒ hiáģƒn tháģ‹ áēŖnh thu nháģ. Điáģu này giÃēp giáģ¯ màu sáē¯c ráģąc ráģĄ cáģ§a nháģ¯ng hÃŦnh áēŖnh cÃŗ gam màu ráģ™ng, nhưng áēŖnh cÃŗ tháģƒ trông khÃĄc trÃĒn cÃĄc thiáēŋt báģ‹ và trÃŦnh duyáģ‡t cÅŠ. áēĸnh sRGB đưáģŖc giáģ¯ nguyÃĒn đáģƒ trÃĄnh thay đáģ•i màu sáē¯c.", - "image_preview_description": "áēĸnh kích thưáģ›c trung bÃŦnh Ä‘ÃŖ loáēĄi báģ metadata, đưáģŖc sáģ­ dáģĨng khi xem máģ™t tài nguyÃĒn duy nháēĨt và cho háģc mÃĄy", + "image_preview_description": "áēĸnh kích thưáģ›c trung bÃŦnh Ä‘ÃŖ loáēĄi báģ metadata, đưáģŖc sáģ­ dáģĨng khi xem máģ™t táģ‡p duy nháēĨt và cho háģc mÃĄy", "image_preview_quality_description": "CháēĨt lưáģŖng xem trưáģ›c táģĢ 1-100. Càng cao càng táģ‘t, nhưng sáēŊ táēĄo ra cÃĄc táģ‡p láģ›n cÃŗ tháģƒ làm giáēŖm kháēŖ năng pháēŖn háģ“i cáģ§a app. Sáģ­ dáģĨng giÃĄ tráģ‹ tháēĨp cÃŗ tháģƒ áēŖnh hưáģŸng đáēŋn cháēĨt lưáģŖng tÃĄc váģĨ háģc mÃĄy.", "image_preview_title": "Cài đáēˇt Xem trưáģ›c", "image_progressive": "Mang tính táģ‹nh tiáēŋn", @@ -108,21 +115,6 @@ "image_thumbnail_quality_description": "CháēĨt lưáģŖng áēŖnh thu nháģ táģĢ 1-100. Càng cao càng táģ‘t, nhưng sáēŊ táēĄo ra cÃĄc táģ‡p láģ›n hÆĄn cÃŗ tháģƒ làm giáēŖm kháēŖ năng pháēŖn háģ“i cáģ§a app.", "image_thumbnail_title": "Cài đáēˇt áēĸnh thu nháģ", "import_config_from_json_description": "Nháē­p cáēĨu hÃŦnh háģ‡ tháģ‘ng báēąng cÃĄch táēŖi lÃĒn táģ‡p cáēĨu hÃŦnh JSON", - "integrity_checks_checksum_files": "CÃĄc táģ‡p checksum", - "integrity_checks_checksum_files_description": "Thiáēŋt láē­p kiáģƒm tra checksum", - "integrity_checks_checksum_files_enable_description": "Báē­t kiáģƒm tra checksum", - "integrity_checks_checksum_files_percentage_limit": "Giáģ›i háēĄn táģˇ láģ‡ pháē§n trăm", - "integrity_checks_checksum_files_percentage_limit_description": "Thiáēŋt láē­p táģˇ láģ‡ pháē§n trăm táģ‘i đa (trong khoáēŖng táģĢ 0.01 đáēŋn 1) cho táē§n suáēĨt tháģąc hiáģ‡n kiáģƒm tra checksum trong máģ—i khoáēŖng tháģi gian.", - "integrity_checks_checksum_files_time_limit": "Giáģ›i háēĄn tháģi gian", - "integrity_checks_checksum_files_time_limit_description": "Thiáēŋt láē­p tháģi lưáģŖng táģ‘i đa đáģƒ tháģąc hiáģ‡n kiáģƒm tra checksum trong máģ—i khoáēŖng tháģi gian. (ms)", - "integrity_checks_missing_files": "Nháģ¯ng táģ‡p báģ‹ thiáēŋu", - "integrity_checks_missing_files_description": "Thiáēŋt láē­p táē§n suáēĨt và báē­t/táē¯t tính năng kiáģƒm tra táģ‡p báģ‹ thiáēŋu", - "integrity_checks_missing_files_enable_description": "Báē­t tính năng kiáģƒm tra táģ‡p báģ‹ thiáēŋu", - "integrity_checks_settings": "Kiáģƒm tra tính toàn váēšn", - "integrity_checks_settings_description": "QuáēŖn lÃŊ khoáēŖng tháģi gian kiáģƒm tra tính toàn váēšn", - "integrity_checks_untracked_files": "Nháģ¯ng táģ‡p chưa đưáģŖc theo dÃĩi", - "integrity_checks_untracked_files_description": "Thiáēŋt láē­p táē§n suáēĨt và báē­t/táē¯t viáģ‡c kiáģƒm tra cÃĄc táģ‡p chưa đưáģŖc theo dÃĩi", - "integrity_checks_untracked_files_enable_description": "Báē­t tính năng kiáģƒm tra cÃĄc táģ‡p chưa đưáģŖc theo dÃĩi", "job_concurrency": "{job} cháēĄy đáģ“ng tháģi", "job_created": "TÃĄc váģĨ Ä‘ÃŖ đưáģŖc táēĄo", "job_not_concurrency_safe": "TÃĄc váģĨ này không an toàn đáģƒ cháēĄy đáģ“ng tháģi.", @@ -130,7 +122,7 @@ "job_settings_description": "QuáēŖn lÃŊ sáģ‘ lưáģŖng tÃĄc váģĨ cháēĄy đáģ“ng tháģi", "jobs_delayed": "{jobCount, plural, other {# báģ‹ hoÃŖn láēĄi}}", "jobs_failed": "{jobCount, plural, other {# tháēĨt báēĄi}}", - "jobs_over_time": "TÃĄc váģĨ qua tháģi gian", + "jobs_over_time": "TÃĄc váģĨ quÃĄ tháģi gian", "library_created": "ÄÃŖ táēĄo thư viáģ‡n: {library}", "library_deleted": "Thư viáģ‡n Ä‘ÃŖ báģ‹ xÃŗa", "library_details": "Chi tiáēŋt thư viáģ‡n", @@ -138,13 +130,13 @@ "library_remove_exclusion_pattern_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa máēĢu loáēĄi tráģĢ này?", "library_remove_folder_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n gáģĄ thư máģĨc nháē­p này?", "library_scanning": "QuÊt đáģ‹nh káģŗ", - "library_scanning_description": "Thiáēŋt láē­p quÊt thư viáģ‡n đáģ‹nh káģŗ", + "library_scanning_description": "CáēĨu hÃŦnh quÊt thư viáģ‡n đáģ‹nh káģŗ", "library_scanning_enable_description": "Báē­t quÊt thư viáģ‡n đáģ‹nh káģŗ", - "library_settings": "Thư viáģ‡n ngoài", - "library_settings_description": "QuáēŖn lÃŊ cài đáēˇt thư viáģ‡n ngoài", - "library_tasks_description": "QuÊt thư viáģ‡n ngoài đáģƒ tÃŦm tài nguyÃĒn máģ›i thÃĒm hoáēˇc báģ‹ thay đáģ•i", + "library_settings": "Thư viáģ‡n bÃĒn ngoài", + "library_settings_description": "QuáēŖn lÃŊ cài đáēˇt thư viáģ‡n bÃĒn ngoài", + "library_tasks_description": "QuÊt thư viáģ‡n ngoài đáģƒ tÃŦm áēŖnh máģ›i thÃĒm hoáēˇc báģ‹ thay đáģ•i", "library_updated": "ÄÃŖ cáē­p nháē­t thư viáģ‡n", - "library_watching_enable_description": "Táģą Ä‘áģ™ng cáē­p nháē­t cÃĄc táģ‡p báģ‹ thay đáģ•i trong thư viáģ‡n ngoài", + "library_watching_enable_description": "Táģą Ä‘áģ™ng cáē­p nháē­t cÃĄc táģ‡p báģ‹ thay đáģ•i trong thư viáģ‡n bÃĒn ngoài", "library_watching_settings": "Theo dÃĩi thư viáģ‡n (THáģŦ NGHIáģ†M)", "library_watching_settings_description": "Táģą Ä‘áģ™ng cáē­p nháē­t khi cÃĄc táģ‡p báģ‹ thay đáģ•i", "logging_enable_description": "Báē­t ghi log", @@ -161,7 +153,7 @@ "machine_learning_clip_model_description": "TÃĒn cáģ§a mô hÃŦnh CLIP đưáģŖc liáģ‡t kÃĒ táēĄi đÃĸy. BáēĄn cáē§n cháēĄy láēĄi tÃĄc váģĨ \"TÃŦm kiáēŋm thông minh\" cho táēĨt cáēŖ áēŖnh sau khi thay đáģ•i mô hÃŦnh.", "machine_learning_duplicate_detection": "TÃŦm trÚng láēˇp", "machine_learning_duplicate_detection_enabled": "Báē­t tÃŦm áēŖnh trÚng láēˇp", - "machine_learning_duplicate_detection_enabled_description": "Náēŋu báģ‹ táē¯t, cÃĄc tài nguyÃĒn trÚng láēˇp giáģ‘ng háģ‡t nhau váēĢn sáēŊ báģ‹ loáēĄi báģ.", + "machine_learning_duplicate_detection_enabled_description": "Náēŋu báģ‹ táē¯t, cÃĄc áēŖnh trÚng láēˇp giáģ‘ng háģ‡t nhau váēĢn sáēŊ báģ‹ loáēĄi báģ.", "machine_learning_duplicate_detection_setting_description": "Sáģ­ dáģĨng vector nhÃēng CLIP đáģƒ tÃŦm kiáēŋm áēŖnh trÚng láēˇp", "machine_learning_enabled": "Báē­t háģc mÃĄy", "machine_learning_enabled_description": "Náēŋu báģ‹ táē¯t, táēĨt cáēŖ tính năng và cài đáēˇt háģc mÃĄy sáēŊ báģ‹ loáēĄi báģ.", @@ -191,32 +183,16 @@ "machine_learning_ocr_min_score_recognition_description": "Điáģƒm tin cáē­y táģ‘i thiáģƒu đáģƒ nháē­n dáēĄng văn báēŖn đưáģŖc phÃĄt hiáģ‡n là táģĢ 0-1. GiÃĄ tráģ‹ tháēĨp hÆĄn sáēŊ nháē­n dáēĄng đưáģŖc nhiáģu văn báēŖn hÆĄn nhưng cÃŗ tháģƒ dáēĢn đáēŋn káēŋt quáēŖ dÆ°ÆĄng tính giáēŖ.", "machine_learning_ocr_model": "Mô hÃŦnh OCR", "machine_learning_ocr_model_description": "Mô hÃŦnh mÃĄy cháģ§ chính xÃĄc hÆĄn mô hÃŦnh di đáģ™ng, nhưng máēĨt nhiáģu tháģi gian xáģ­ lÃŊ hÆĄn và sáģ­ dáģĨng nhiáģu báģ™ nháģ› hÆĄn.", - "machine_learning_settings": "Háģc mÃĄy", + "machine_learning_settings": "Cài đáēˇt Háģc mÃĄy", "machine_learning_settings_description": "QuáēŖn lÃŊ cÃĄc tính năng và cài đáēˇt háģc mÃĄy", "machine_learning_smart_search": "TÃŦm kiáēŋm Thông minh", "machine_learning_smart_search_description": "TÃŦm kiáēŋm hÃŦnh áēŖnh theo ngáģ¯ cáēŖnh váģ›i CLIP", "machine_learning_smart_search_enabled": "Báē­t TÃŦm kiáēŋm Thông minh", "machine_learning_smart_search_enabled_description": "Náēŋu táē¯t, áēŖnh sáēŊ không đưáģŖc mÃŖ hÃŗa đáģƒ tÃŦm kiáēŋm thông minh.", "machine_learning_url_description": "Đáģ‹a cháģ‰ mÃĄy cháģ§ háģc mÃĄy. Náēŋu cÃŗ nhiáģu hÆĄn máģ™t đáģ‹a cháģ‰ Ä‘Æ°áģŖc cung cáēĨp, máģ—i mÃĄy cháģ§ sáēŊ đưáģŖc kiáģƒm tra máģ™t láē§n cho đáēŋn khi cÃŗ máģ™t mÃĄy cháģ§ tráēŖ láģi thành công, theo tháģŠ táģą táģĢ mÃĄy cháģ§ Ä‘áē§u tiÃĒn đáēŋn mÃĄy cháģ§ cuáģ‘i cÚng. MÃĄy cháģ§ không pháēŖn háģ“i sáēŊ táēĄm tháģi đưáģŖc báģ qua cho đáēŋn khi mÃĄy cháģ§ online tráģŸ láēĄi.", - "maintenance_backup_management": "QuáēŖn lÃŊ sao lưu", "maintenance_delete_backup": "XÃŗa báēŖn sao lưu", - "maintenance_delete_backup_description": "Táģ‡p này sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n.", + "maintenance_delete_backup_description": "Táģ‡p này sáēŊ báģ‹ xoÃĄ vÄŠnh viáģ…n.", "maintenance_delete_error": "Láģ—i khi xÃŗa báēŖn sao lưu.", - "maintenance_integrity_check": "Kiáģƒm tra", - "maintenance_integrity_check_all": "Kiáģƒm tra táēĨt cáēŖ", - "maintenance_integrity_checksum_mismatch": "Checksum không kháģ›p", - "maintenance_integrity_checksum_mismatch_description": "CÃĄc táģ‡p cÃŗ checksum trÃĒn đĩa không kháģ›p váģ›i checksummà Immich Ä‘ÃŖ lưu tráģ¯ trong cÆĄ sáģŸ dáģ¯ liáģ‡u cáģ§a mÃŦnh.", - "maintenance_integrity_checksum_mismatch_job": "Kiáģƒm tra checksum cÃŗ kháģ›p không", - "maintenance_integrity_checksum_mismatch_refresh_job": "Làm máģ›i bÃĄo cÃĄo trÚng kháģ›p checksum", - "maintenance_integrity_missing_file": "Táģ‡p báģ‹ thiáēŋu", - "maintenance_integrity_missing_file_description": "CÃĄc táģ‡p mà Immich Ä‘ÃŖ theo dÃĩi trong cÆĄ sáģŸ dáģ¯ liáģ‡u cáģ§a mÃŦnh nhưng không táģ“n táēĄi trÃĒn háģ‡ tháģ‘ng táģ‡p.", - "maintenance_integrity_missing_file_job": "Kiáģƒm tra cÃĄc táģ‡p báģ‹ thiáēŋu", - "maintenance_integrity_missing_file_refresh_job": "Làm máģ›i bÃĄo cÃĄo táģ‡p báģ‹ thiáēŋu", - "maintenance_integrity_report": "BÃĄo cÃĄo liÃĒm chính", - "maintenance_integrity_untracked_file": "Táģ‡p không báģ‹ theo dÃĩi", - "maintenance_integrity_untracked_file_description": "CÃĄc táģ‡p trong thư máģĨc cáģ§a Immich mà Immich không cÃŗ báēĨt káģŗ báēŖn ghi nào.", - "maintenance_integrity_untracked_file_job": "Kiáģƒm tra cÃĄc táģ‡p không theo dÃĩi", - "maintenance_integrity_untracked_file_refresh_job": "Làm máģ›i bÃĄo cÃĄo táģ‡p không theo dÃĩi", "maintenance_restore_backup": "Khôi pháģĨc sao lưu", "maintenance_restore_backup_description": "Immich sáēŊ xÃŗa sáēĄch toàn báģ™ dáģ¯ liáģ‡u hiáģ‡n táēĄi và khôi pháģĨc dáģ¯ liáģ‡u táģĢ báēŖn sao lưu Ä‘ÃŖ đưáģŖc cháģn. Háģ‡ tháģ‘ng sáēŊ táēĄo máģ™t báēŖn sao lưu cho dáģ¯ liáģ‡u hiáģ‡n táēĄi trưáģ›c khi báē¯t đáē§u.", "maintenance_restore_backup_different_version": "BáēŖn sao lưu này Ä‘ÃŖ đưáģŖc táēĄo ra báēąng máģ™t phiÃĒn báēŖn khÃĄc cáģ§a Immich!", @@ -248,21 +224,21 @@ "memory_cleanup_job": "Dáģn dáēšp káģˇ niáģ‡m", "memory_generate_job": "TáēĄo káģˇ niáģ‡m", "metadata_extraction_job": "Trích xuáēĨt Metadata", - "metadata_extraction_job_description": "Trích xuáēĨt Metadata táģĢ máģ—i tài nguyÃĒn, cháēŗng háēĄn như GPS, khuôn máēˇt và đáģ™ phÃĸn giáēŖi", + "metadata_extraction_job_description": "Trích xuáēĨt Metadata táģĢ máģ—i áēŖnh, cháēŗng háēĄn như GPS, khuôn máēˇt và đáģ™ phÃĸn giáēŖi", "metadata_faces_import_setting": "Báē­t tính năng nháē­p khuôn máēˇt", "metadata_faces_import_setting_description": "Nháē­p khuôn máēˇt táģĢ dáģ¯ liáģ‡u EXIF áēŖnh và táģ‡p đi kèm", "metadata_settings": "Cài đáēˇt Metadata", "metadata_settings_description": "QuáēŖn lÃŊ cài đáēˇt Metadata", "migration_job": "Di chuyáģƒn dáģ¯ liáģ‡u", - "migration_job_description": "Di chuyáģƒn áēŖnh thu nháģ cáģ§a tài nguyÃĒn và khuôn máēˇt sang cáēĨu trÃēc thư máģĨc máģ›i", + "migration_job_description": "Di chuyáģƒn áēŖnh thu nháģ cáģ§a áēŖnh và khuôn máēˇt sang cáēĨu trÃēc thư máģĨc máģ›i", "nightly_tasks_cluster_faces_setting_description": "CháēĄy nháē­n diáģ‡n khuôn máēˇt trÃĒn nháģ¯ng khuôn máēˇt máģ›i đưáģŖc phÃĄt hiáģ‡n", "nightly_tasks_cluster_new_faces_setting": "NhÃŗm cÃĄc khuôn máēˇt máģ›i", "nightly_tasks_database_cleanup_setting": "TÃĄc váģĨ dáģn dáēšp cÆĄ sáģŸ dáģ¯ liáģ‡u", "nightly_tasks_database_cleanup_setting_description": "Làm sáēĄch dáģ¯ liáģ‡u cÅŠ hoáēˇc/và háēŋt háēĄn trong cÆĄ sáģŸ dáģ¯ liáģ‡u", "nightly_tasks_generate_memories_setting": "TáēĄo káģˇ niáģ‡m", - "nightly_tasks_generate_memories_setting_description": "TáēĄo ra nháģ¯ng káģˇ niáģ‡m máģ›i táģĢ tài nguyÃĒn", + "nightly_tasks_generate_memories_setting_description": "TáēĄo ra nháģ¯ng káģˇ niáģ‡m máģ›i táģĢ táģ‡p", "nightly_tasks_missing_thumbnails_setting": "TáēĄo áēŖnh đáēĄi diáģ‡n báģ‹ thiáēŋu", - "nightly_tasks_missing_thumbnails_setting_description": "Xáēŋp hàng cÃĄc tài nguyÃĒn không cÃŗ áēŖnh thu nháģ Ä‘áģƒ táēĄo áēŖnh thu nháģ", + "nightly_tasks_missing_thumbnails_setting_description": "Xáēŋp hàng cÃĄc táģ‡p không cÃŗ áēŖnh thu nháģ Ä‘áģƒ táēĄo áēŖnh thu nháģ", "nightly_tasks_settings": "Cài đáēˇt cÃĄc tÃĄc váģĨ hàng đÃĒm", "nightly_tasks_settings_description": "QuáēŖn lÃŊ cÃĄc tÃĄc váģĨ hàng đÃĒm", "nightly_tasks_start_time_setting": "Tháģi gian báē¯t đáē§u", @@ -271,7 +247,7 @@ "nightly_tasks_sync_quota_usage_setting_description": "Cáē­p nháē­t háēĄn máģŠc dung lưáģŖng cáģ§a ngưáģi dÚng, dáģąa trÃĒn máģŠc sáģ­ dáģĨng hiáģ‡n táēĄi", "no_paths_added": "Không cÃŗ đưáģng dáēĢn nào đưáģŖc thÃĒm vào", "no_pattern_added": "Không cÃŗ quy táē¯c nào đưáģŖc thÃĒm vào", - "note_apply_storage_label_previous_assets": "Lưu ÃŊ: Đáģƒ ÃĄp dáģĨng NhÃŖn lưu tráģ¯ cho tài nguyÃĒn Ä‘ÃŖ táēŖi lÃĒn trưáģ›c Ä‘Ãŗ, hÃŖy cháēĄy", + "note_apply_storage_label_previous_assets": "Lưu ÃŊ: Đáģƒ ÃĄp dáģĨng NhÃŖn lưu tráģ¯ cho náģ™i dung Ä‘ÃŖ táēŖi lÃĒn trưáģ›c Ä‘Ãŗ, hÃŖy cháēĄy", "note_cannot_be_changed_later": "LƯU Ý: Cài đáēˇt này không tháģƒ thay đáģ•i đưáģŖc sau khi lưu!", "notification_email_from_address": "Đáģ‹a cháģ‰ email ngưáģi gáģ­i", "notification_email_from_address_description": "Đáģ‹a cháģ‰ email cáģ§a ngưáģi gáģ­i, ví dáģĨ: \"Immich Photo Server \". HÃŖy cháē¯c là báēĄn dÚng máģ™t đáģ‹a cháģ‰ mà báēĄn đưáģŖc phÊp gáģ­i email ra.", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "Báē­t thông bÃĄo qua email", "notification_settings": "Thông bÃĄo", "notification_settings_description": "QuáēŖn lÃŊ cÃĄc cài đáēˇt thông bÃĄo, bao gáģ“m email", - "oauth_allow_insecure_requests": "Cho phÊp cÃĄc yÃĒu cáē§u không an toàn", - "oauth_allow_insecure_requests_description": "CáēĸNH BÁO: Thao tÃĄc này vô hiáģ‡u hÃŗa viáģ‡c xÃĄc tháģąc cháģŠng cháģ‰ TLS cho cÃĄc yÃĒu cáē§u OAuth và cÃŗ tháģƒ khiáēŋn báēĄn dáģ… báģ‹ táēĨn công MITM (Man-in-the-Middle).", "oauth_auto_launch": "Táģą Ä‘áģ™ng kháģŸi cháēĄy", "oauth_auto_launch_description": "Táģą Ä‘áģ™ng đăng nháē­p báēąng tài khoáēŖn OAuth khi báēĄn truy cáē­p trang đăng nháē­p", "oauth_auto_register": "Táģą Ä‘áģ™ng đăng kÃŊ", @@ -300,11 +274,9 @@ "oauth_button_text": "Náģ™i dung nÃēt báēĨm", "oauth_client_secret_description": "Báē¯t buáģ™c đáģ‘i váģ›i ngưáģi sáģ­ dáģĨng áģŠng dáģĨng báēŖo máē­t, hoáēˇc náēŋu nhà cung cáēĨp áģŠng dáģĨng không háģ— tráģŖ PKCE (Proof Key for Code Exchange).", "oauth_enable_description": "Đăng nháē­p váģ›i OAuth", - "oauth_end_session_url_description": "Chuyáģƒn hưáģ›ng ngưáģi dÚng đáēŋn URI này khi háģ Ä‘Äƒng xuáēĨt.", "oauth_mobile_redirect_uri": "URI chuyáģƒn hưáģ›ng trÃĒn thiáēŋt báģ‹ di đáģ™ng", "oauth_mobile_redirect_uri_override": "Ghi đè URI chuyáģƒn hưáģ›ng cho thiáēŋt báģ‹ di đáģ™ng", "oauth_mobile_redirect_uri_override_description": "Báē­t khi nhà cung cáēĨp OAuth không cho phÊp URI di đáģ™ng, như ''{callback}''", - "oauth_prompt_description": "Tham sáģ‘ prompt (ví dáģĨ: select_account, login, consent)", "oauth_role_claim": "Vai trÃ˛ claim", "oauth_role_claim_description": "Táģą Ä‘áģ™ng cáēĨp quyáģn quáēŖn tráģ‹ dáģąa trÃĒn sáģą hiáģ‡n diáģ‡n cáģ§a yÃĒu cáē§u này. YÃĒu cáē§u này cÃŗ tháģƒ cÃŗ tÃĒn là 'ngưáģi dÚng' hoáēˇc 'quáēŖn tráģ‹ viÃĒn'.", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "Làm máģ›i táēĨt cáēŖ thư viáģ‡n", "registration": "Đăng kÃŊ QuáēŖn tráģ‹ viÃĒn", "registration_description": "VÃŦ báēĄn là ngưáģi dÚng đáē§u tiÃĒn, báēĄn sáēŊ tráģŸ thành QuáēŖn tráģ‹ viÃĒn và cháģ‹u trÃĄch nhiáģ‡m cho viáģ‡c quáēŖn lÃŊ háģ‡ tháģ‘ng. Ngoài ra, báēĄn cÃŗ tháģƒ thÃĒm cÃĄc ngưáģi dÚng khÃĄc.", - "release_channel_release_candidate": "PhÃĄt hành sáģ›m", - "release_channel_stable": "Chính tháģŠc", "remove_failed_jobs": "XÃŗa cÃĄc tÃĄc váģĨ không thành công", "require_password_change_on_login": "YÃĒu cáē§u ngưáģi dÚng thay đáģ•i máē­t kháēŠu trong láē§n đăng nháē­p đáē§u tiÃĒn", "reset_settings_to_default": "Đáēˇt láēĄi cài đáēˇt váģ máēˇc đáģ‹nh", @@ -341,7 +311,7 @@ "search_jobs": "TÃŦm kiáēŋm tÃĄc váģĨâ€Ļ", "send_welcome_email": "Gáģ­i email chào máģĢng", "server_external_domain_settings": "TÃĒn miáģn công khai", - "server_external_domain_settings_description": "TÃĒn miáģn dành cho cÃĄc link chia sáēģ công khai", + "server_external_domain_settings_description": "TÃĒn miáģn dành cho cÃĄc liÃĒn káēŋt chia sáēģ công khai", "server_public_users": "Ngưáģi dÚng công khai", "server_public_users_description": "TáēĨt cáēŖ ngưáģi dÚng (tÃĒn và email) đưáģŖc liáģ‡t kÃĒ khi thÃĒm máģ™t ngưáģi dÚng vào máģ™t album đưáģŖc chia sáēģ. Khi táē¯t láģąa cháģn này, danh sÃĄch ngưáģi dÚng cháģ‰ cÃŗ tháģƒ Ä‘Æ°áģŖc tháēĨy báģŸi ngưáģi dÚng quáēŖn tráģ‹.", "server_settings": "MÃĄy cháģ§", @@ -352,22 +322,22 @@ "settings_page_description": "Trang cài đáēˇt quáēŖn tráģ‹", "sidecar_job": "SiÃĒu dáģ¯ liáģ‡u đi kèm", "sidecar_job_description": "TÃŦm hoáēˇc đáģ“ng báģ™ cÃĄc táģ‡p siÃĒu dáģ¯ liáģ‡u đi kèm táģĢ háģ‡ tháģ‘ng", - "slideshow_duration_description": "Sáģ‘ giÃĸy hiáģƒn tháģ‹ máģ—i áēŖnh", - "smart_search_job_description": "CháēĄy háģc mÃĄy trÃĒn toàn báģ™ tài nguyÃĒn đáģƒ háģ— tráģŖ tÃŦm kiáēŋm thông minh", - "storage_template_date_time_description": "Máģ‘c tháģi gian táēĄo tài nguyÃĒn đưáģŖc dÚng làm thông tin ngày giáģ", + "slideshow_duration_description": "Sáģ‘ giÃĸy hiáģƒn tháģ‹ táģĢng áēŖnh", + "smart_search_job_description": "CháēĄy háģc mÃĄy trÃĒn toàn báģ™ áēŖnh đáģƒ háģ— tráģŖ tÃŦm kiáēŋm thông minh", + "storage_template_date_time_description": "Máģ‘c tháģi gian táēĄo táģ‡p đưáģŖc dÚng làm thông tin ngày giáģ", "storage_template_date_time_sample": "Tháģi gian máēĢu {date}", "storage_template_enable_description": "Báē­t công cáģĨ máēĢu lưu tráģ¯", "storage_template_hash_verification_enabled": "Báē­t xÃĄc minh băm", "storage_template_hash_verification_enabled_description": "Báē­t xÃĄc minh băm, không táē¯t tính năng này tráģĢ khi báēĄn cháē¯c cháē¯n váģ cÃĄc ráģ§i ro cÃŗ tháģƒ xáēŖy ra", "storage_template_migration": "Di chuyáģƒn máēĢu lưu tráģ¯", - "storage_template_migration_description": "Áp dáģĨng {template} hiáģ‡n táēĄi cho cÃĄc tài nguyÃĒn Ä‘ÃŖ đưáģŖc táēŖi lÃĒn trưáģ›c đÃĸy", - "storage_template_migration_info": "MáēĢu lưu tráģ¯ sáēŊ chuyáģƒn đáģ•i táēĨt cáēŖ cÃĄc pháē§n máģŸ ráģ™ng thành cháģ¯ thưáģng. Nháģ¯ng thay đáģ•i cáģ§a máēĢu này cháģ‰ ÃĄp dáģĨng cho cÃĄc tài nguyÃĒn máģ›i táēŖi lÃĒn. Đáģƒ ÃĄp dáģĨng máēĢu này cho cÃĄc tài nguyÃĒn Ä‘ÃŖ táēŖi lÃĒn trưáģ›c đÃĸy, hÃŖy cháēĄy {job}.", + "storage_template_migration_description": "Áp dáģĨng {template} hiáģ‡n táēĄi cho cÃĄc áēŖnh Ä‘ÃŖ đưáģŖc táēŖi lÃĒn trưáģ›c đÃĸy", + "storage_template_migration_info": "MáēĢu lưu tráģ¯ sáēŊ chuyáģƒn táēĨt cáēŖ Ä‘áģ‹nh dáēĄng táē­p tin thành cháģ¯ thưáģng. Nháģ¯ng thay đáģ•i cáģ§a máēĢu này cháģ‰ ÃĄp dáģĨng cho cÃĄc áēŖnh máģ›i táēŖi lÃĒn. Đáģƒ ÃĄp dáģĨng máēĢu này cho cÃĄc áēŖnh Ä‘ÃŖ táēŖi lÃĒn trưáģ›c đÃĸy, hÃŖy cháēĄy {job}.", "storage_template_migration_job": "TÃĄc váģĨ di chuyáģƒn máēĢu lưu tráģ¯", "storage_template_more_details": "Cáē§n thÃĒm thông tin chi tiáēŋt váģ tính năng này, vui lÃ˛ng tham kháēŖo MáēĢu lưu tráģ¯ và cÃĄc háģ‡ quáēŖ cáģ§a nÃŗ", "storage_template_onboarding_description_v2": "Khi đưáģŖc báē­t, tính năng này sáēŊ táģą Ä‘áģ™ng sáē¯p xáēŋp cÃĄc táģ‡p dáģąa trÃĒn máēĢu do ngưáģi dÚng xÃĄc đáģ‹nh. Đáģƒ biáēŋt thÃĒm thông tin, vui lÃ˛ng xem tài liáģ‡u.", "storage_template_path_length": "Giáģ›i háēĄn đáģ™ dài đưáģng dáēĢn xáēĨp xáģ‰: {length, number}/{limit, number}", "storage_template_settings": "MáēĢu lưu tráģ¯", - "storage_template_settings_description": "QuáēŖn lÃŊ cáēĨu trÃēc thư máģĨc và tÃĒn táģ‡p cáģ§a tài nguyÃĒn táēŖi lÃĒn", + "storage_template_settings_description": "QuáēŖn lÃŊ cáēĨu trÃēc thư máģĨc và tÃĒn táģ‡p cáģ§a áēŖnh táēŖi lÃĒn", "storage_template_user_label": "CáģĨm táģĢ {label} là NhÃŖn lưu tráģ¯ cáģ§a ngưáģi dÚng", "system_settings": "Cài đáēˇt háģ‡ tháģ‘ng", "tag_cleanup_job": "Dáģn dáēšp tháēģ", @@ -385,7 +355,7 @@ "theme_settings": "Cháģ§ Ä‘áģ", "theme_settings_description": "TÚy biáēŋn giao diáģ‡n web cáģ§a Immich", "thumbnail_generation_job": "TáēĄo áēŖnh thu nháģ", - "thumbnail_generation_job_description": "TáēĄo áēŖnh thu nháģ láģ›n, nháģ và máģ cho máģ—i tài nguyÃĒn, cÅŠng như áēŖnh thu nháģ cho máģ—i ngưáģi", + "thumbnail_generation_job_description": "TáēĄo áēŖnh thu nháģ láģ›n, nháģ và máģ cho máģ—i áēŖnh, cÅŠng như áēŖnh thu nháģ cho máģ—i ngưáģi", "transcoding_acceleration_api": "API Gia táģ‘c", "transcoding_acceleration_api_description": "API này sáēŊ tÆ°ÆĄng tÃĄc váģ›i thiáēŋt báģ‹ cáģ§a báēĄn đáģƒ gia táģ‘c quÃĄ trÃŦnh chuyáģƒn mÃŖ. Cài đáēˇt này hoáēĄt đáģ™ng theo nguyÃĒn táē¯c 'cáģ‘ gáē¯ng háēŋt sáģŠc'': nÃŗ sáēŊ quay láēĄi chuyáģƒn mÃŖ pháē§n máģm náēŋu gáēˇp láģ—i. VP9 cÃŗ tháģƒ hoáēĄt đáģ™ng hoáēˇc không tÚy thuáģ™c vào pháē§n cáģŠng cáģ§a báēĄn.", "transcoding_acceleration_nvenc": "NVENC (yÃĒu cáē§u GPU NVIDIA)", @@ -406,7 +376,7 @@ "transcoding_constant_quality_mode": "Cháēŋ đáģ™ cháēĨt lưáģŖng cáģ‘ Ä‘áģ‹nh", "transcoding_constant_quality_mode_description": "ICQ táģ‘t hÆĄn CQP, nhưng máģ™t sáģ‘ thiáēŋt báģ‹ tăng táģ‘c pháē§n cáģŠng không háģ— tráģŖ cháēŋ đáģ™ này. Cài đáēˇt tÚy cháģn này sáēŊ ưu tiÃĒn cháēŋ đáģ™ Ä‘Æ°áģŖc cháģ‰ Ä‘áģ‹nh khi sáģ­ dáģĨng mÃŖ hÃŗa dáģąa trÃĒn cháēĨt lưáģŖng. Báģ‹ báģ qua báģŸi NVENC vÃŦ nÃŗ không háģ— tráģŖ ICQ.", "transcoding_constant_rate_factor": "Háģ‡ sáģ‘ táģˇ láģ‡ cáģ‘ Ä‘áģ‹nh (-crf)", - "transcoding_constant_rate_factor_description": "CháēĨt lưáģŖng video. GiÃĄ tráģ‹ Ä‘iáģƒn hÃŦnh là 23 (H.264), 28 (HEVC), 31 (VP9) và 35 (AV1). GiÃĄ tráģ‹ tháēĨp hÆĄn thÃŦ táģ‘t hÆĄn, nhưng táēĄo ra cÃĄc táģ‡p láģ›n hÆĄn.", + "transcoding_constant_rate_factor_description": "MáģŠc cháēĨt lưáģŖng video. CÃĄc giÃĄ tráģ‹ Ä‘iáģƒn hÃŦnh là 23 cho H.264, 28 cho HEVC, 31 cho VP9 và 35 cho AV1. GiÃĄ tráģ‹ tháēĨp hÆĄn thÃŦ táģ‘t hÆĄn, nhưng táēĄo ra cÃĄc táē­p tin láģ›n hÆĄn.", "transcoding_disabled_description": "Không chuyáģƒn mÃŖ báēĨt káģŗ video nào, cÃŗ tháģƒ gÃĸy láģ—i phÃĄt láēĄi trÃĒn máģ™t sáģ‘ thiáēŋt báģ‹", "transcoding_encoding_options": "CÃĄc tÚy cháģn mÃŖ hÃŗa", "transcoding_encoding_options_description": "Thiáēŋt láē­p chuáēŠn nÊn video, đáģ™ phÃĸn giáēŖi, cháēĨt lưáģŖng và cÃĄc tÚy cháģn khÃĄc cho video", @@ -427,21 +397,13 @@ "transcoding_preferred_hardware_device_description": "Cháģ‰ ÃĄp dáģĨng cho VAAPI và QSV. Thiáēŋt láē­p nÃēt dri đưáģŖc sáģ­ dáģĨng cho chuyáģƒn mÃŖ pháē§n cáģŠng.", "transcoding_preset_preset": "MáēĢu cÃŗ sáēĩn (-preset)", "transcoding_preset_preset_description": "Táģ‘c đáģ™ nÊn. CÃĄc máēĢu cÃŗ sáēĩn cháē­m hÆĄn táēĄo ra cÃĄc táģ‡p nháģ hÆĄn và cáēŖi thiáģ‡n cháēĨt lưáģŖng khi máģĨc tiÃĒu là máģ™t bitrate cáģĨ tháģƒ. VP9 cháģ‰ háģ— tráģŖ cÃĄc máēĢu cÃŗ sáēĩn táģĢ 'ultrafast' đáēŋn 'faster'.", - "transcoding_realtime": "Chuyáģƒn mÃŖ tháģi gian tháģąc [THáģŦ NGHIáģ†M]", - "transcoding_realtime_description": "Cho phÊp chuyáģƒn mÃŖ video theo tháģi gian tháģąc khi video đang đưáģŖc phÃĄt tráģąc tuyáēŋn. Cho phÊp chuyáģƒn đáģ•i cháēĨt lưáģŖng, nhưng cÃŗ tháģƒ gÃĸy ra đáģ™ tráģ… phÃĄt láēĄi cao hÆĄn và hiáģ‡n tưáģŖng giáē­t hÃŦnh tÚy thuáģ™c vào kháēŖ năng cáģ§a mÃĄy cháģ§.", - "transcoding_realtime_enabled": "Cho phÊp chuyáģƒn mÃŖ tháģi gian tháģąc", - "transcoding_realtime_enabled_description": "Náēŋu táē¯t, mÃĄy cháģ§ sáēŊ táģĢ cháģ‘i báē¯t đáē§u cÃĄc phiÃĒn chuyáģƒn mÃŖ tháģi gian tháģąc máģ›i.", - "transcoding_realtime_resolutions": "Đáģ™ PhÃĸn GiáēŖi", - "transcoding_realtime_resolutions_description": "CÃĄc đáģ™ phÃĸn giáēŖi đưáģŖc cung cáēĨp cho quÃĄ trÃŦnh chuyáģƒn mÃŖ tháģi gian tháģąc. CÃĄc đáģ™ phÃĸn giáēŖi cao hÆĄn cÃŗ tháģƒ gÃĸy ra sáģą cáģ‘ khi phÃĄt láēĄi náēŋu mÃĄy cháģ§ không tháģƒ chuyáģƒn mÃŖ chÃēng đáģ§ nhanh.", - "transcoding_realtime_video_codecs": "Báģ™ giáēŖi mÃŖ video", - "transcoding_realtime_video_codecs_description": "CÃĄc codec video đưáģŖc cung cáēĨp cho quÃĄ trÃŦnh chuyáģƒn mÃŖ tháģi gian tháģąc. MÃĄy khÃĄch sáēŊ cháģn tÚy cháģn táģ‘t nháēĨt mà chÃēng háģ— tráģŖ trong quÃĄ trÃŦnh phÃĄt láēĄi. AV1 cÃŗ hiáģ‡u suáēĨt cao hÆĄn HEVC, và HEVC láēĄi cÃŗ hiáģ‡u suáēĨt cao hÆĄn H.264. Khi sáģ­ dáģĨng tính năng tăng táģ‘c pháē§n cáģŠng, cháģ‰ nÃĒn cháģn cÃĄc codec mà báģ™ tăng táģ‘c cÃŗ kháēŖ năng mÃŖ hÃŗa. Khi tháģąc hiáģ‡n chuyáģƒn mÃŖ báēąng pháē§n máģm, cáē§n lưu ÃŊ ráēąng H.264 cÃŗ táģ‘c đáģ™ xáģ­ lÃŊ nhanh hÆĄn AV1, và AV1 nhanh hÆĄn HEVC.", "transcoding_reference_frames": "Khung hÃŦnh tham chiáēŋu", "transcoding_reference_frames_description": "Sáģ‘ lưáģŖng khung hÃŦnh tham chiáēŋu khi nÊn máģ™t khung hÃŦnh nháēĨt đáģ‹nh. GiÃĄ tráģ‹ cao hÆĄn cáēŖi thiáģ‡n hiáģ‡u suáēĨt nÊn nhưng làm cháē­m quÃĄ trÃŦnh mÃŖ hÃŗa. GiÃĄ tráģ‹ 0 đáģƒ táģą Ä‘áģ™ng thiáēŋt láē­p giÃĄ tráģ‹ này.", "transcoding_required_description": "Cháģ‰ video không áģŸ Ä‘áģ‹nh dáēĄng đưáģŖc cháēĨp nháē­n", "transcoding_settings": "Chuyáģƒn mÃŖ video", "transcoding_settings_description": "QuáēŖn lÃŊ video chuyáģƒn mÃŖ và cÃĄch xáģ­ lÃŊ", "transcoding_target_resolution": "Đáģ™ phÃĸn giáēŖi máģĨc tiÃĒu", - "transcoding_target_resolution_description": "Đáģ™ phÃĸn giáēŖi cao hÆĄn cÃŗ tháģƒ giáģ¯ láēĄi nhiáģu chi tiáēŋt hÆĄn nhưng máēĨt nhiáģu tháģi gian hÆĄn đáģƒ mÃŖ hÃŗa, cÃŗ kích thưáģ›c táģ‡p láģ›n hÆĄn và cÃŗ tháģƒ làm giáēŖm kháēŖ năng pháēŖn háģ“i cáģ§a app.", + "transcoding_target_resolution_description": "Đáģ™ phÃĸn giáēŖi cao hÆĄn cÃŗ tháģƒ giáģ¯ láēĄi nhiáģu chi tiáēŋt hÆĄn nhưng máēĨt nhiáģu tháģi gian hÆĄn đáģƒ mÃŖ hÃŗa, cÃŗ kích thưáģ›c táē­p tin láģ›n hÆĄn và cÃŗ tháģƒ làm giáēŖm kháēŖ năng pháēŖn háģ“i cáģ§a app.", "transcoding_temporal_aq": "LưáģŖng táģ­ hÃŗa thích áģŠng (Temporal AQ)", "transcoding_temporal_aq_description": "Cháģ‰ ÃĄp dáģĨng cho NVENC. LưáģŖng táģ­ hÃŗa Thích áģŠng Tháģi gian tăng cháēĨt lưáģŖng cho cÃĄc cáēŖnh cÃŗ nhiáģu chi tiáēŋt và ít chuyáģƒn đáģ™ng. CÃŗ tháģƒ không tÆ°ÆĄng thích váģ›i cÃĄc thiáēŋt báģ‹ cÅŠ.", "transcoding_threads": "Luáģ“ng", @@ -449,25 +411,25 @@ "transcoding_tone_mapping": "Ánh XáēĄ Sáē¯c ThÃĄi (Tone-mapping)", "transcoding_tone_mapping_description": "Cáģ‘ gáē¯ng duy trÃŦ cháēĨt lưáģŖng video táģ‘t nháēĨt khi chuyáģƒn đáģ•i táģĢ HDR sang SDR. Máģ—i thuáē­t toÃĄn cÃŗ sáģą Ä‘ÃĄnh đáģ•i khÃĄc nhau váģ màu sáē¯c, chi tiáēŋt và đáģ™ sÃĄng. Hable giáģ¯ chi tiáēŋt, Mobius giáģ¯ màu sáē¯c và Reinhard giáģ¯ Ä‘áģ™ sÃĄng.", "transcoding_transcode_policy": "Quy táē¯c chuyáģƒn mÃŖ", - "transcoding_transcode_policy_description": "Quy táē¯c chuyáģƒn mÃŖ video. CÃĄc video HDR và video cÃŗ đáģ‹nh dáēĄng pixel khÃĄc YUV 4:2:0 luôn đưáģŖc chuyáģƒn mÃŖ (tráģĢ khi tính năng chuyáģƒn mÃŖ báģ‹ táē¯t).", + "transcoding_transcode_policy_description": "Quy táē¯c khi nào video nÃĒn đưáģŖc chuyáģƒn mÃŖ. CÃĄc video HDR luôn đưáģŖc chuyáģƒn mÃŖ (ngoáēĄi tráģĢ khi tính năng chuyáģƒn mÃŖ báģ‹ táē¯t).", "transcoding_two_pass_encoding": "MÃŖ hÃŗa hai láē§n", "transcoding_two_pass_encoding_setting_description": "Chuyáģƒn mÃŖ hai láē§n đáģƒ táēĄo ra video đưáģŖc mÃŖ hÃŗa táģ‘t hÆĄn. Khi bitrate táģ‘i đa đưáģŖc báē­t (báē¯t buáģ™c đáģƒ hoáēĄt đáģ™ng váģ›i H.264 và HEVC), cháēŋ đáģ™ này sáģ­ dáģĨng máģ™t pháēĄm vi bitrate dáģąa trÃĒn bitrate táģ‘i đa và báģ qua CRF. Đáģ‘i váģ›i VP9, CRF cÃŗ tháģƒ Ä‘Æ°áģŖc sáģ­ dáģĨng náēŋu bitrate táģ‘i đa báģ‹ táē¯t.", "transcoding_video_codec": "ChuáēŠn nÊn video", - "transcoding_video_codec_description": "VP9 cÃŗ hiáģ‡u suáēĨt cao và tÆ°ÆĄng thích web táģ‘t, nhưng tháģi gian chuyáģƒn mÃŖ lÃĸu hÆĄn. HEVC cÃŗ hiáģ‡u suáēĨt tÆ°ÆĄng táģą, nhưng tÆ°ÆĄng thích web tháēĨp hÆĄn. H.264 tÆ°ÆĄng thích ráģ™ng rÃŖi và chuyáģƒn mÃŖ nhanh, nhưng táēĄo ra cÃĄc táģ‡p cÃŗ kích thưáģ›c láģ›n. AV1 là codec hiáģ‡u quáēŖ nháēĨt nhưng không đưáģŖc háģ— tráģŖ trÃĒn cÃĄc thiáēŋt báģ‹ cÅŠ.", + "transcoding_video_codec_description": "VP9 cÃŗ hiáģ‡u suáēĨt cao và tÆ°ÆĄng thích táģ‘t váģ›i web, nhưng tháģi gian chuyáģƒn mÃŖ lÃĸu hÆĄn. HEVC cÃŗ hiáģ‡u suáēĨt tÆ°ÆĄng táģą, nhưng tÆ°ÆĄng thích web tháēĨp hÆĄn. H.264 tÆ°ÆĄng thích ráģ™ng rÃŖi và chuyáģƒn mÃŖ nhanh, nhưng táēĄo ra cÃĄc táē­p tin cÃŗ kích thưáģ›c láģ›n. AV1 là codec hiáģ‡u quáēŖ nháēĨt nhưng không đưáģŖc háģ— tráģŖ trÃĒn cÃĄc thiáēŋt báģ‹ cÅŠ.", "trash_enabled_description": "Báē­t tính năng ThÚng rÃĄc", "trash_number_of_days": "Sáģ‘ ngày", - "trash_number_of_days_description": "Sáģ‘ ngày giáģ¯ cÃĄc tài nguyÃĒn trong thÚng rÃĄc trưáģ›c khi xÃŗa chÃēng vÄŠnh viáģ…n", + "trash_number_of_days_description": "Sáģ‘ ngày giáģ¯ cÃĄc áēŖnh trong thÚng rÃĄc trưáģ›c khi xÃŗa chÃēng vÄŠnh viáģ…n", "trash_settings": "ThÚng rÃĄc", "trash_settings_description": "QuáēŖn lÃŊ cài đáēˇt thÚng rÃĄc", "unlink_all_oauth_accounts": "Háģ§y liÃĒn káēŋt táēĨt cáēŖ tài khoáēŖn OAuth", "unlink_all_oauth_accounts_description": "HÃŖy nháģ› háģ§y liÃĒn káēŋt táēĨt cáēŖ tài khoáēŖn OAuth trưáģ›c khi di chuyáģƒn sang nhà cung cáēĨp máģ›i.", "unlink_all_oauth_accounts_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n háģ§y liÃĒn káēŋt táēĨt cáēŖ tài khoáēŖn OAuth không? Thao tÃĄc này sáēŊ đáēˇt láēĄi ID OAuth cho máģ—i ngưáģi dÚng và không tháģƒ hoàn tÃĄc.", "user_cleanup_job": "Dáģn dáēšp ngưáģi dÚng", - "user_delete_delay": "Tài khoáēŖn và tài nguyÃĒn cáģ§a {user} sáēŊ đưáģŖc lÃĒn láģ‹ch xÃŗa vÄŠnh viáģ…n sau {delay, plural, one {# ngày} other {# ngày}}.", + "user_delete_delay": "Tài khoáēŖn và táģ‡p cáģ§a {user} sáēŊ đưáģŖc lÃĒn láģ‹ch xÃŗa vÄŠnh viáģ…n sau {delay, plural, one {# ngày} other {# ngày}}.", "user_delete_delay_settings": "Tháģi gian xÃŗa", - "user_delete_delay_settings_description": "Sáģ‘ ngày cháģ xÃŗa đáģƒ xÃŗa vÄŠnh viáģ…n tài khoáēŖn và tài nguyÃĒn cáģ§a ngưáģi dÚng. TÃĄc váģĨ xÃŗa ngưáģi dÚng cháēĄy vào giáģ¯a đÃĒm đáģƒ kiáģƒm tra cÃĄc ngưáģi dÚng sáēĩn sàng báģ‹ xÃŗa. Thay đáģ•i cài đáēˇt này sáēŊ đưáģŖc Ä‘ÃĄnh giÃĄ vào láē§n tháģąc hiáģ‡n tiáēŋp theo.", - "user_delete_immediately": "Tài khoáēŖn và tài nguyÃĒn cáģ§a {user} sáēŊ đưáģŖc đưa vào hàng đáģŖi đáģƒ xÃŗa vÄŠnh viáģ…n ngay láē­p táģŠc.", - "user_delete_immediately_checkbox": "Xáēŋp hàng ngưáģi dÚng và tài nguyÃĒn đáģƒ xÃŗa ngay láē­p táģŠc", + "user_delete_delay_settings_description": "Sáģ‘ ngày cháģ xÃŗa đáģƒ xÃŗa vÄŠnh viáģ…n tài khoáēŖn và táģ‡p cáģ§a ngưáģi dÚng. TÃĄc váģĨ xÃŗa ngưáģi dÚng cháēĄy vào giáģ¯a đÃĒm đáģƒ kiáģƒm tra cÃĄc ngưáģi dÚng sáēĩn sàng báģ‹ xÃŗa. Thay đáģ•i cài đáēˇt này sáēŊ đưáģŖc Ä‘ÃĄnh giÃĄ vào láē§n tháģąc hiáģ‡n tiáēŋp theo.", + "user_delete_immediately": "Tài khoáēŖn và áēŖnh cáģ§a {user} sáēŊ đưáģŖc đưa vào hàng đáģŖi đáģƒ xÃŗa vÄŠnh viáģ…n ngay láē­p táģŠc.", + "user_delete_immediately_checkbox": "Xáēŋp hàng ngưáģi dÚng và áēŖnh đáģƒ xÃŗa ngay láē­p táģŠc", "user_details": "Chi tiáēŋt Ngưáģi dÚng", "user_management": "QuáēŖn lÃŊ ngưáģi dÚng", "user_password_has_been_reset": "Máē­t kháēŠu cáģ§a ngưáģi dÚng Ä‘ÃŖ đưáģŖc đáēˇt láēĄi:", @@ -478,70 +440,87 @@ "user_settings_description": "QuáēŖn lÃŊ cài đáēˇt ngưáģi dÚng", "user_successfully_removed": "Ngưáģi dÚng {email} Ä‘ÃŖ đưáģŖc xÃŗa thành công.", "users_page_description": "Trang quáēŖn tráģ‹ ngưáģi dÚng", - "version_check_channel": "KÃĒnh phÃĄt hành", - "version_check_channel_description": "Cháģn kÃĒnh phÃĄt hành mà báēĄn muáģ‘n nháē­n thông bÃĄo váģ phiÃĒn báēŖn máģ›i", "version_check_enabled_description": "Báē­t kiáģƒm tra phiÃĒn báēŖn", "version_check_implications": "Tính năng kiáģƒm tra phiÃĒn báēŖn yÃĒu cáē§u káēŋt náģ‘i thưáģng xuyÃĒn đáēŋn {server}", "version_check_settings": "Kiáģƒm tra phiÃĒn báēŖn", "version_check_settings_description": "Báē­t/táē¯t thông bÃĄo phiÃĒn báēŖn máģ›i", "video_conversion_job": "Chuyáģƒn mÃŖ video", - "video_conversion_job_description": "Chuyáģƒn mÃŖ video đáģƒ tÆ°ÆĄng thích váģ›i nhiáģu trÃŦnh duyáģ‡t và thiáēŋt báģ‹ hÆĄn" + "video_conversion_job_description": "Chuyáģƒn đáģ•i đáģ‹nh dáēĄng video đáģƒ tÆ°ÆĄng thích ráģ™ng rÃŖi hÆĄn váģ›i trÃŦnh duyáģ‡t và thiáēŋt báģ‹" }, "admin_email": "Email QuáēŖn tráģ‹ viÃĒn", "admin_password": "Máē­t kháēŠu QuáēŖn tráģ‹ viÃĒn", "administration": "QuáēŖn tráģ‹", "advanced": "NÃĸng cao", - "advanced_settings_clear_image_cache": "XÃŗa báģ™ nháģ› Ä‘áģ‡m", - "advanced_settings_clear_image_cache_error": "Láģ—i khi xÃŗa báģ™ nháģ› Ä‘áģ‡m", + "advanced_settings_clear_image_cache": "GiáēŖi phÃŗng báģ™ nháģ› Ä‘áģ‡m", + "advanced_settings_clear_image_cache_error": "Láģ—i khi giáēŖi phÃŗng báģ™ nháģ› Ä‘áģ‡m", "advanced_settings_clear_image_cache_success": "ÄÃŖ giáēŖi phÃŗng thành công {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "DÚng tÚy cháģn này đáģƒ láģc phÆ°ÆĄng tiáģ‡n khi đáģ“ng báģ™ theo tiÃĒu chí khÃĄc. Cháģ‰ tháģ­ khi app không nháē­n diáģ‡n đưáģŖc táēĨt cáēŖ cÃĄc album.", + "advanced_settings_enable_alternate_media_filter_title": "[THáģŦ NGHIáģ†M] DÚng báģ™ láģc đáģ“ng báģ™ album thay tháēŋ", "advanced_settings_log_level_title": "PhÃĸn loáēĄi log: {level}", - "advanced_settings_prefer_remote_subtitle": "Máģ™t sáģ‘ thiáēŋt báģ‹ náēĄp áēŖnh thu nháģ táģĢ tài nguyÃĒn thiáēŋt báģ‹ ráēĨt cháē­m. Báē­t đáģƒ náēĄp áēŖnh táģĢ mÃĄy cháģ§.", + "advanced_settings_prefer_remote_subtitle": "Viáģ‡c táēŖi áēŖnh thu nháģ táģĢ áēŖnh trÃĒn máģ™t sáģ‘ thiáēŋt báģ‹ cÃŗ tháģƒ diáģ…n ra cháē­m. Kích hoáēĄt cài đáēˇt này đáģƒ táēŖi áēŖnh táģĢ mÃĄy cháģ§.", "advanced_settings_prefer_remote_title": "Ưu tiÃĒn áēŖnh táģĢ mÃĄy cháģ§", - "advanced_settings_proxy_headers_subtitle": "XÃĄc đáģ‹nh cÃĄc proxy header Immich sáēŊ gáģ­i kèm máģ—i yÃĒu cáē§u máēĄng", - "advanced_settings_proxy_headers_title": "TÚy cháģ‰nh proxy header [THáģŦ NGHIáģ†M]", - "advanced_settings_readonly_mode_subtitle": "Cháģ‰ dÚng áģŠng dáģĨng đáģƒ xem áēŖnh - cÃĄc tính năng khÃĄc như cháģn nhiáģu máģĨc, chia sáēģ, phÃĄt, xÃŗa đáģu báģ‹ vô hiáģ‡u hÃŗa. NháēĨn giáģ¯ áēŖnh đáēĄi diáģ‡n ngưáģi dÚng táģĢ màn hÃŦnh chính đáģƒ báē­t/táē¯t cháēŋ đáģ™.", + "advanced_settings_proxy_headers_subtitle": "XÃĄc đáģ‹nh cÃĄc tiÃĒu đáģ proxy Immich sáēŊ gáģ­i kèm máģ—i yÃĒu cáē§u máēĄng", + "advanced_settings_proxy_headers_title": "TÚy cháģ‰nh tiÃĒu đáģ proxy [THáģŦ NGHIáģ†M]", + "advanced_settings_readonly_mode_subtitle": "Cháēŋ đáģ™ cháģ‰-xem cháģ‰ cho phÊp xem áēŖnh, cÃĄc tính năng như cháģn nhiáģu áēŖnh, chia sáēģ, phÃĄt, xÃŗa đáģu báģ‹ vô hiáģ‡u hÃŗa. Báē­t/Táē¯t cháēŋ đáģ™ cháģ‰-xem thông qua áēŖnh đáēĄi diáģ‡n ngưáģi dÚng táģĢ màn hÃŦnh chính", "advanced_settings_readonly_mode_title": "Cháēŋ đáģ™ cháģ‰-xem", - "advanced_settings_sync_remote_deletions_subtitle": "Táģą Ä‘áģ™ng xÃŗa hoáēˇc khôi pháģĨc tài nguyÃĒn trÃĒn thiáēŋt báģ‹ này khi báēĄn thao tÃĄc trÃĒn web", + "advanced_settings_self_signed_ssl_subtitle": "Báģ qua xÃĄc minh cháģŠng cháģ‰ SSL cho endpoint mÃĄy cháģ§. YÃĒu cáē§u cho cháģŠng cháģ‰ táģą kÃŊ.", + "advanced_settings_self_signed_ssl_title": "Cho phÊp cháģŠng cháģ‰ SSL táģą kÃŊ [THáģŦ NGHIáģ†M]", + "advanced_settings_sync_remote_deletions_subtitle": "Táģą Ä‘áģ™ng xÃŗa hoáēˇc khôi pháģĨc dáģ¯ liáģ‡u trÃĒn thiáēŋt báģ‹ này khi báēĄn thao tÃĄc trÃĒn web", "advanced_settings_sync_remote_deletions_title": "Đáģ“ng báģ™ viáģ‡c xÃŗa táģĢ thiáēŋt báģ‹ khÃĄc [THáģŦ NGHIáģ†M]", "advanced_settings_tile_subtitle": "Dành cho ngưáģi dÚng nÃĸng cao", - "advanced_settings_troubleshooting_subtitle": "Báē­t tính năng báģ• sung đáģƒ xáģ­ lÃŊ sáģą cáģ‘", + "advanced_settings_troubleshooting_subtitle": "Báē­t cÃĄc tính năng báģ• sung đáģƒ xáģ­ lÃŊ sáģą cáģ‘", "advanced_settings_troubleshooting_title": "Xáģ­ lÃŊ sáģą cáģ‘", "age_months": "{months, plural, one {# thÃĄng} other {# thÃĄng}} tuáģ•i", "age_year_months": "1 tuáģ•i, {months, plural, one {# thÃĄng} other {# thÃĄng}}", "age_years": "{years, plural, other {# tuáģ•i}}", "album": "Album", - "album_added": "ĐưáģŖc thÃĒm vào album", + "album_added": "ÄÃŖ thÃĒm album", "album_added_notification_setting_description": "Nháē­n thông bÃĄo qua email khi báēĄn đưáģŖc thÃĒm vào máģ™t album chia sáēģ", "album_cover_updated": "ÄÃŖ cáē­p nháē­t áēŖnh bÃŦa album", "album_delete_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa album {album}?", "album_delete_confirmation_description": "Náēŋu album này đưáģŖc chia sáēģ, cÃĄc ngưáģi dÚng khÃĄc sáēŊ không cÃ˛n truy cáē­p đưáģŖc náģ¯a.", "album_deleted": "ÄÃŖ xÃŗa album", + "album_info_card_backup_album_excluded": "Đà BáģŽ QUA", + "album_info_card_backup_album_included": "Đà THÊM", "album_info_updated": "ÄÃŖ cáē­p nháē­t thông tin album", + "album_leave": "Ráģi album?", + "album_leave_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n ráģi kháģi {album}?", "album_name": "TÃĒn album", "album_options": "TÚy cháģn album", "album_remove_user": "XÃŗa ngưáģi dÚng?", "album_remove_user_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa {user}?", "album_search_not_found": "Không tÃŦm tháēĨy album trÚng kháģ›p", + "album_selected": "Album Ä‘ÃŖ cháģn", "album_share_no_users": "CÃŗ váēģ như báēĄn Ä‘ÃŖ chia sáēģ album này váģ›i táēĨt cáēŖ ngưáģi dÚng hoáēˇc báēĄn không cÃŗ ngưáģi dÚng nào đáģƒ chia sáēģ.", "album_summary": "Mô táēŖ album", - "album_updated": "Album cÃŗ cáē­p nháē­t", - "album_updated_setting_description": "Nháē­n thông bÃĄo qua email khi máģ™t album chia sáēģ cÃŗ cÃĄc tài nguyÃĒn máģ›i", - "album_upload_assets": "TáēŖi tài nguyÃĒn táģĢ mÃĄy tính cáģ§a báēĄn lÃĒn và thÃĒm vào album", + "album_updated": "ÄÃŖ cáē­p nháē­t album", + "album_updated_setting_description": "Nháē­n thông bÃĄo qua email khi máģ™t album chia sáēģ cÃŗ cÃĄc áēŖnh máģ›i", + "album_upload_assets": "TáēŖi áēŖnh/video táģĢ mÃĄy tính cáģ§a báēĄn lÃĒn và thÃĒm vào album", + "album_user_left": "ÄÃŖ ráģi kháģi {album}", + "album_user_removed": "ÄÃŖ xÃŗa {user}", + "album_viewer_appbar_delete_confirm": "BáēĄn cÃŗ muáģ‘n xÃŗa album này kháģi tài khoáēŖn cáģ§a mÃŦnh?", "album_viewer_appbar_share_err_delete": "XÃŗa album tháēĨt báēĄi", + "album_viewer_appbar_share_err_leave": "Ráģi kháģi album tháēĨt báēĄi", + "album_viewer_appbar_share_err_remove": "CÃŗ váēĨn đáģ khi xÃŗa áēŖnh kháģi album", + "album_viewer_appbar_share_err_title": "Thay đáģ•i tÃĒn album tháēĨt báēĄi", + "album_viewer_appbar_share_leave": "Ráģi kháģi album", + "album_viewer_appbar_share_to": "Chia sáēģ váģ›i", "album_viewer_page_share_add_users": "ThÃĒm ngưáģi dÚng", - "album_with_link_access": "Ai cÃŗ link sáēŊ xem đưáģŖc cÃĄc áēŖnh và ngưáģi trong album này.", + "album_with_link_access": "Ai cÃŗ liÃĒn káēŋt sáēŊ xem đưáģŖc cÃĄc áēŖnh và ngưáģi trong album này.", "albums": "Album", "albums_count": "{count, plural, one {{count, number} Album} other {{count, number} Album}}", "albums_default_sort_order": "TháģŠ táģą sáē¯p xáēŋp album máēˇc đáģ‹nh", - "albums_default_sort_order_description": "TháģŠ táģą sáē¯p xáēŋp ban đáē§u cho cÃĄc tài nguyÃĒn khi táēĄo album máģ›i.", - "albums_feature_description": "CÃĄc báģ™ sưu táē­p tài nguyÃĒn cÃŗ tháģƒ chia sáēģ đưáģŖc váģ›i ngưáģi khÃĄc", + "albums_default_sort_order_description": "TháģŠ táģą sáē¯p xáēŋp ban đáē§u cho cÃĄc áēŖnh khi táēĄo album máģ›i.", + "albums_feature_description": "CÃĄc báģ™ sưu táē­p táģ‡p cÃŗ tháģƒ Ä‘Æ°áģŖc chia sáēģ váģ›i nháģ¯ng ngưáģi dÚng khÃĄc.", "albums_on_device_count": "Album trÃĒn thiáēŋt báģ‹ ({count})", + "albums_selected": "{count, plural, one {# album Ä‘ÃŖ cháģn} other {# album Ä‘ÃŖ cháģn}}", "all": "TáēĨt cáēŖ", "all_albums": "TáēĨt cáēŖ album", "all_people": "TáēĨt cáēŖ máģi ngưáģi", "all_photos": "TáēĨt cáēŖ hÃŦnh áēŖnh", "all_videos": "TáēĨt cáēŖ video", "allow_dark_mode": "Cho phÊp cháēŋ đáģ™ táģ‘i", + "allow_edits": "Cho phÊp cháģ‰nh sáģ­a", "allow_public_user_to_download": "Cho phÊp táēŖi áēŖnh xuáģ‘ng", "allow_public_user_to_upload": "Cho phÊp táēŖi áēŖnh lÃĒn", "allowed": "Cho phÊp", @@ -549,123 +528,195 @@ "always_keep": "Luôn luôn giáģ¯ láēĄi", "always_keep_photos_hint": "Tính năng GiáēŖi phÃŗng dung lưáģŖng sáēŊ giáģ¯ láēĄi táēĨt cáēŖ áēŖnh trÃĒn thiáēŋt báģ‹ này.", "always_keep_videos_hint": "Tính năng GiáēŖi phÃŗng dung lưáģŖng sáēŊ giáģ¯ láēĄi táēĨt cáēŖ video trÃĒn thiáēŋt báģ‹ này.", + "anti_clockwise": "Xoay trÃĄi", "api_key": "KhÃŗa API", "api_key_description": "GiÃĄ tráģ‹ này cháģ‰ Ä‘Æ°áģŖc hiáģƒn tháģ‹ máģ™t láē§n. Vui lÃ˛ng sao chÊp nÃŗ trưáģ›c khi Ä‘Ãŗng cáģ­a sáģ•.", "api_key_empty": "TÃĒn khÃŗa API cáģ§a báēĄn không đưáģŖc đáģƒ tráģ‘ng", "api_keys": "KhÃŗa API", - "app_architecture_variant": "Biáēŋn tháģƒ (Kiáēŋn trÃēc)", + "app_architecture_variant": "Variant (Kiáēŋn trÃēc)", "app_bar_signout_dialog_content": "BáēĄn cÃŗ muáģ‘n đăng xuáēĨt?", + "app_bar_signout_dialog_ok": "CÃŗ", "app_bar_signout_dialog_title": "Đăng xuáēĨt", - "app_download_links": "LiÃĒn káēŋt táēŖi áģŠng dáģĨng", - "app_settings": "áģ¨ng dáģĨng", - "app_stores": "Cáģ­a hàng áģŠng dáģĨng", - "app_update_available": "ÄÃŖ cÃŗ báēŖn cáē­p nháē­t áģŠng dáģĨng", + "app_download_links": "LiÃĒn káēŋt táēŖi app", + "app_settings": "App", + "app_stores": "Cáģ­a hàng app", + "app_update_available": "ÄÃŖ cÃŗ báēŖn cáē­p nháē­t app", "appears_in": "XuáēĨt hiáģ‡n trong", "apply_count": "Áp dáģĨng ({count, number})", "archive": "Lưu tráģ¯", - "archive_action_prompt": "ÄÃŖ thÃĒm ({count}) vào Lưu tráģ¯", + "archive_action_prompt": "{count} Ä‘ÃŖ đưáģŖc thÃĒm vào Lưu tráģ¯", "archive_or_unarchive_photo": "Lưu tráģ¯ hoáēˇc báģ lưu tráģ¯ áēŖnh", - "archive_size": "Dung lưáģŖng táģ‡p nÊn", - "archive_size_description": "Thiáēŋt láē­p dung lưáģŖng táģ‡p nÊn táēŖi xuáģ‘ng (Ä‘ÆĄn váģ‹ GiB)", + "archive_page_no_archived_assets": "Không tÃŦm tháēĨy táģ‡p Ä‘ÃŖ lưu tráģ¯", + "archive_page_title": "Kho lưu tráģ¯ ({count})", + "archive_size": "Kích cáģĄ lưu tráģ¯", + "archive_size_description": "CáēĨu hÃŦnh kích cáģĄ nÊn cho cÃĄc táģ‡p táēŖi xuáģ‘ng (Ä‘ÆĄn váģ‹ GiB)", "archived": "Lưu tráģ¯", "archived_count": "{count, plural, other {ÄÃŖ lưu tráģ¯ # máģĨc}}", "are_these_the_same_person": "ĐÃĸy cÃŗ pháēŖi cÚng máģ™t ngưáģi không?", "are_you_sure_to_do_this": "BáēĄn cÃŗ cháē¯c muáģ‘n tháģąc hiáģ‡n điáģu này?", + "array_field_not_fully_supported": "CÃĄc trưáģng máēŖng yÃĒu cáē§u cháģ‰nh sáģ­a JSON tháģ§ công", + "asset_action_delete_err_read_only": "Không tháģƒ xÃŗa táģ‡p cháģ‰ cÃŗ quyáģn đáģc, báģ qua", + "asset_action_share_err_offline": "Không tháģƒ táēŖi táģ‡p ngoáēĄi tuyáēŋn, báģ qua", "asset_added_to_album": "ÄÃŖ thÃĒm vào album", - "asset_adding_to_album": "ThÃĒm vào albumâ€Ļ", - "asset_created": "ÄÃŖ táēĄo tài nguyÃĒn", - "asset_day_count": "{date}: {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "asset_description_updated": "Mô táēŖ tài nguyÃĒn Ä‘ÃŖ đưáģŖc cáē­p nháē­t", + "asset_adding_to_album": "Đang thÃĒm vào albumâ€Ļ", + "asset_created": "ÄÃŖ táēĄo táģ‡p", + "asset_description_updated": "Mô táēŖ áēŖnh Ä‘ÃŖ đưáģŖc cáē­p nháē­t", + "asset_filename_is_offline": "Táģ‡p {filename} đang ngoáēĄi tuyáēŋn", + "asset_has_unassigned_faces": "Táģ‡p chưa đưáģŖc gÃĄn khuôn máēˇt", "asset_hashing": "Đang bămâ€Ļ", "asset_list_group_by_sub_title": "NhÃŗm theo", - "asset_list_layout_settings_group_by": "NhÃŗm tài nguyÃĒn theo", + "asset_list_layout_settings_dynamic_layout_title": "Báģ‘ cáģĨc đáģ™ng", + "asset_list_layout_settings_group_automatically": "Táģą Ä‘áģ™ng", + "asset_list_layout_settings_group_by": "NhÃŗm táģ‡p theo", "asset_list_layout_settings_group_by_month_day": "ThÃĄng + ngày", "asset_list_layout_sub_title": "Báģ‘ cáģĨc", "asset_list_settings_subtitle": "Báģ‘ cáģĨc lưáģ›i áēŖnh", "asset_list_settings_title": "Lưáģ›i áēŖnh", - "asset_not_found_on_device_android": "Không tÃŦm tháēĨy tài nguyÃĒn trÃĒn thiáēŋt báģ‹", - "asset_not_found_on_device_ios": "Không tÃŦm tháēĨy tài nguyÃĒn trÃĒn thiáēŋt báģ‹. Náēŋu báēĄn đang dÚng iCloud, cÃŗ tháģƒ tài nguyÃĒn lưu trÃĒn iCloud báģ‹ láģ—i nÃĒn không tháģƒ Ä‘Æ°áģŖc truy cáē­p", - "asset_not_found_on_icloud": "Không tÃŦm tháēĨy tài nguyÃĒn trÃĒn iCloud. CÃŗ tháģƒ tài nguyÃĒn lưu trÃĒn iCloud báģ‹ láģ—i nÃĒn không tháģƒ Ä‘Æ°áģŖc truy cáē­p", - "asset_offline": "Tài nguyÃĒn NgoáēĄi tuyáēŋn", - "asset_offline_description": "Tài nguyÃĒn bÃĒn ngoài này không cÃ˛n trÃĒn áģ• Ä‘ÄŠa. Vui lÃ˛ng liÃĒn háģ‡ quáēŖn tráģ‹ viÃĒn Immich cáģ§a báēĄn đáģƒ Ä‘Æ°áģŖc tráģŖ giÃēp.", + "asset_not_found_on_device_android": "Không tÃŦm tháēĨy táģ‡p trÃĒn thiáēŋt báģ‹", + "asset_not_found_on_device_ios": "Không tÃŦm tháēĨy táģ‡p trÃĒn thiáēŋt báģ‹. Náēŋu báēĄn đang dÚng iCloud, cÃŗ tháģƒ táģ‡p lưu trÃĒn iCloud báģ‹ láģ—i nÃĒn không tháģƒ Ä‘Æ°áģŖc truy cáē­p", + "asset_not_found_on_icloud": "Không tÃŦm tháēĨy táģ‡p trÃĒn iCloud. CÃŗ tháģƒ táģ‡p lưu trÃĒn iCloud báģ‹ láģ—i nÃĒn không tháģƒ Ä‘Æ°áģŖc truy cáē­p", + "asset_offline": "Táģ‡p NgoáēĄi tuyáēŋn", + "asset_offline_description": "Táģ‡p bÃĒn ngoài này không cÃ˛n trÃĒn áģ• Ä‘ÄŠa. Vui lÃ˛ng liÃĒn háģ‡ quáēŖn tráģ‹ viÃĒn Immich cáģ§a báēĄn đáģƒ Ä‘Æ°áģŖc tráģŖ giÃēp.", + "asset_restored_successfully": "ÄÃŖ khôi pháģĨc táģ‡p thành công", "asset_skipped": "ÄÃŖ báģ qua", "asset_skipped_in_trash": "Trong thÚng rÃĄc", - "asset_troubleshoot": "Kháē¯c pháģĨc sáģą cáģ‘ tài nguyÃĒn", + "asset_trashed": "ÄÃŖ chuyáģƒn táģ‡p đáēŋn thÚng rÃĄc", + "asset_troubleshoot": "Kháē¯c pháģĨc sáģą cáģ‘ táģ‡p", "asset_uploaded": "ÄÃŖ táēŖi lÃĒn", "asset_uploading": "Đang táēŖi lÃĒnâ€Ļ", "asset_viewer_settings_subtitle": "CÃĄch thư viáģ‡n hiáģƒn tháģ‹", - "asset_viewer_settings_title": "Duyáģ‡t tài nguyÃĒn", - "assets": "Tài nguyÃĒn", - "assets_added_to_album_count": "ÄÃŖ thÃĒm {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} vào album", - "assets_added_to_album_partial_count": "ÄÃŖ thÃĒm {successCount} trong táģ•ng sáģ‘ {totalCount} {totalCount, plural, one {tài nguyÃĒn} other {tài nguyÃĒn}} vào album", - "assets_added_to_albums_count": "ÄÃŖ thÃĒm {assetTotal, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} vào {albumTotal, plural, one {# album} other {# album}}", - "assets_cannot_be_added_to_album_count": "{count, plural, one {Tài nguyÃĒn} other {Tài nguyÃĒn}} không tháģƒ thÃĒm vào album", - "assets_cannot_be_added_to_albums": "{count, plural, one {Tài nguyÃĒn} other {Tài nguyÃĒn}} không tháģƒ thÃĒm vào báēĨt káģŗ album nào", - "assets_count": "{count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "assets_moved_to_trash_count": "ÄÃŖ chuyáģƒn {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} vào thÚng rÃĄc", - "assets_permanently_deleted_count": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "assets_removed_count": "ÄÃŖ xÃŗa {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "assets_restore_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n khôi pháģĨc táēĨt cáēŖ tài nguyÃĒn Ä‘ÃŖ xÃŗa cáģ§a mÃŦnh không? BáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này! Lưu ÃŊ ráēąng không tháģƒ khôi pháģĨc cÃĄc tài nguyÃĒn ngoáēĄi tuyáēŋn theo cÃĄch này.", - "assets_restored_count": "ÄÃŖ khôi pháģĨc {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "assets_trashed": "ÄÃŖ chuyáģƒn {count} tài nguyÃĒn vào thÚng rÃĄc", - "assets_trashed_count": "ÄÃŖ chuyáģƒn {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} vào thÚng rÃĄc", - "assets_were_part_of_album_count": "{count, plural, one {Tài nguyÃĒn Ä‘ÃŖ} other {Tài nguyÃĒn Ä‘ÃŖ}} cÃŗ sáēĩn trong album", - "assets_were_part_of_albums_count": "{count, plural, one {Tài nguyÃĒn Ä‘ÃŖ} other {Tài nguyÃĒn}} cÃŗ sáēĩn trong album", + "asset_viewer_settings_title": "TrÃŦnh xem áēŖnh", + "assets": "Táģ‡p", + "assets_added_count": "ÄÃŖ thÃĒm {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "assets_added_to_album_count": "ÄÃŖ thÃĒm {count, plural, one {# táģ‡p} other {# táģ‡p}} vào album", + "assets_added_to_albums_count": "ÄÃŖ thÃĒm {assetTotal, plural, one {# táģ‡p} other {# táģ‡p}} vào {albumTotal, plural, one {# album} other {# album}}", + "assets_cannot_be_added_to_album_count": "{count, plural, one {Táģ‡p} other {Táģ‡p}} không tháģƒ thÃĒm vào album", + "assets_cannot_be_added_to_albums": "{count, plural, one {Táģ‡p} other {Táģ‡p}} không tháģƒ thÃĒm vào báēĨt káģŗ album nào", + "assets_count": "{count, plural, one {# táģ‡p} other {# táģ‡p}}", + "assets_deleted_permanently": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count} táģ‡p", + "assets_deleted_permanently_from_server": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count} táģ‡p kháģi mÃĄy cháģ§ Immich", + "assets_downloaded_failed": "{count, plural, one {ÄÃŖ táēŖi xuáģ‘ng # táģ‡p - {error} táģ‡p tháēĨt báēĄi} other {ÄÃŖ táēŖi xuáģ‘ng # táģ‡p - {error} táģ‡p tháēĨt báēĄi}}", + "assets_downloaded_successfully": "{count, plural, one {ÄÃŖ táēŖi xuáģ‘ng # táģ‡p thành công} other {ÄÃŖ táēŖi xuáģ‘ng # táģ‡p thành công}}", + "assets_moved_to_trash_count": "ÄÃŖ chuyáģƒn {count, plural, one {# táģ‡p} other {# táģ‡p}} vào thÚng rÃĄc", + "assets_permanently_deleted_count": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "assets_removed_count": "ÄÃŖ xÃŗa {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "assets_removed_permanently_from_device": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count} táģ‡p kháģi thiáēŋt báģ‹ cáģ§a báēĄn", + "assets_restore_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n khôi pháģĨc táēĨt cáēŖ máģĨc Ä‘ÃŖ xÃŗa cáģ§a mÃŦnh không? BáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này! Lưu ÃŊ ráēąng không tháģƒ khôi pháģĨc cÃĄc áēŖnh ngoáēĄi tuyáēŋn theo cÃĄch này.", + "assets_restored_count": "ÄÃŖ khôi pháģĨc {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "assets_restored_successfully": "ÄÃŖ khôi pháģĨc {count} táģ‡p thành công", + "assets_trashed": "ÄÃŖ chuyáģƒn {count} táģ‡p vào thÚng rÃĄc", + "assets_trashed_count": "ÄÃŖ chuyáģƒn {count, plural, one {# táģ‡p} other {# táģ‡p}} vào thÚng rÃĄc", + "assets_trashed_from_server": "ÄÃŖ chuyáģƒn {count} táģ‡p táģĢ mÃĄy cháģ§ Immich vào thÚng rÃĄc", + "assets_were_part_of_album_count": "{count, plural, one {Táģ‡p Ä‘ÃŖ} other {CÃĄc táģ‡p Ä‘ÃŖ}} cÃŗ sáēĩn trong album", + "assets_were_part_of_albums_count": "{count, plural, one {Táģ‡p Ä‘ÃŖ} other {Táģ‡p Ä‘ÃŖ}} cÃŗ sáēĩn trong album", "authorized_devices": "Thiáēŋt báģ‹", "automatic_endpoint_switching_subtitle": "Káēŋt náģ‘i náģ™i báģ™ qua Wi-Fi đưáģŖc cháģ‰ Ä‘áģ‹nh khi káēŋt náģ‘i đưáģŖc và sáģ­ dáģĨng cÃĄc káēŋt náģ‘i thay tháēŋ áģŸ nÆĄi khÃĄc", "automatic_endpoint_switching_title": "Táģą Ä‘áģ™ng chuyáģƒn đáģ•i đáģ‹a cháģ‰ mÃĄy cháģ§", "autoplay_slideshow": "Táģą Ä‘áģ™ng phÃĄt trÃŦnh chiáēŋu", "back": "Quay láēĄi", "back_close_deselect": "Quay láēĄi, Ä‘Ãŗng, hoáēˇc báģ cháģn", + "background_backup_running_error": "Sao lưu náģn hiáģ‡n đang cháēĄy, không tháģƒ báē¯t đáē§u sao lưu tháģ§ công", "background_location_permission": "Quyáģn truy cáē­p váģ‹ trí khi cháēĄy náģn", - "background_location_permission_content": "Đáģƒ chuyáģƒn đáģ•i máēĄng khi cháēĄy áģŸ cháēŋ đáģ™ náģn, Immich pháēŖi *luôn* cÃŗ quyáģn truy cáē­p váģ‹ trí chính xÃĄc đáģƒ cÃŗ tháģƒ Ä‘áģc tÃĒn máēĄng Wi-Fi", + "background_location_permission_content": "Đáģƒ chuyáģƒn đáģ•i máēĄng khi cháēĄy áģŸ cháēŋ đáģ™ náģn, Immich *luôn* pháēŖi cÃŗ quyáģn truy cáē­p váģ‹ trí chính xÃĄc đáģƒ cÃŗ tháģƒ Ä‘áģc tÃĒn máēĄng Wi-Fi", "background_options": "TÚy cháģn náģn", "backup": "Sao lưu", + "backup_album_selection_page_albums_device": "Album trÃĒn thiáēŋt báģ‹ ({count})", "backup_album_selection_page_albums_tap": "NháēĨn đáģƒ cháģn, nháēĨn đÃēp đáģƒ báģ qua", - "backup_album_selection_page_assets_scatter": "Tài nguyÃĒn cÃŗ tháģƒ cÃŗ trong nhiáģu album khÃĄc nhau. Trong quÃĄ trÃŦnh sao lưu, báēĄn cÃŗ tháģƒ cháģn đáģƒ sao lưu táēĨt cáēŖ cÃĄc album hoáēˇc cháģ‰ máģ™t sáģ‘ album nháēĨt đáģ‹nh.", + "backup_album_selection_page_assets_scatter": "áēĸnh cÃŗ tháģƒ cÃŗ trong nhiáģu album khÃĄc nhau. Trong quÃĄ trÃŦnh sao lưu, báēĄn cÃŗ tháģƒ cháģn đáģƒ sao lưu táēĨt cáēŖ cÃĄc album hoáēˇc cháģ‰ máģ™t sáģ‘ album nháēĨt đáģ‹nh.", "backup_album_selection_page_select_albums": "Cháģn album", "backup_album_selection_page_selection_info": "Thông tin cÃĄc máģĨc Ä‘ÃŖ cháģn", + "backup_album_selection_page_total_assets": "Táģ•ng sáģ‘ táģ‡p không trÚng láēˇp", "backup_albums_sync": "Đáģ“ng báģ™ hÃŗa báēŖn sao lưu album", - "backup_background_service_complete_notification": "Hoàn táēĨt sao lưu tài nguyÃĒn", - "backup_background_service_default_notification": "Đang kiáģƒm tra cÃĄc tài nguyÃĒn máģ›iâ€Ļ", - "backup_background_service_in_progress_notification": "Đang sao lưu tài nguyÃĒn cáģ§a báēĄnâ€Ļ", + "backup_all": "TáēĨt cáēŖ", + "backup_background_service_backup_failed_message": "Sao lưu táģ‡p tháēĨt báēĄi. Đang tháģ­ láēĄiâ€Ļ", + "backup_background_service_complete_notification": "Hoàn táēĨt sao lưu táģ‡p", + "backup_background_service_connection_failed_message": "Káēŋt náģ‘i táģ›i mÃĄy cháģ§ tháēĨt báēĄi. Đang tháģ­ láēĄiâ€Ļ", + "backup_background_service_current_upload_notification": "Đang táēŖi lÃĒn {filename}", + "backup_background_service_default_notification": "Đang kiáģƒm tra táģ‡p máģ›iâ€Ļ", + "backup_background_service_error_title": "Sao lưu không thành công", + "backup_background_service_in_progress_notification": "Đang sao lưu táģ‡p cáģ§a báēĄnâ€Ļ", + "backup_background_service_upload_failure_notification": "TáēŖi lÃĒn {filename} tháēĨt báēĄi", "backup_controller_page_albums": "Album sao lưu", + "backup_controller_page_background_app_refresh_disabled_content": "Báē­t làm máģ›i app trong náģn táēĄi Cài đáēˇt > Cài đáēˇt chung > Làm máģ›i app trong náģn đáģƒ dÚng sao lưu náģn.", + "backup_controller_page_background_app_refresh_disabled_title": "Làm máģ›i app trong náģn báģ‹ vô hiáģ‡u hoÃĄ", + "backup_controller_page_background_app_refresh_enable_button_text": "Đi táģ›i cài đáēˇt", "backup_controller_page_background_battery_info_link": "Hưáģ›ng dáēĢn tôi", "backup_controller_page_background_battery_info_message": "Đáģƒ cÃŗ tráēŖi nghiáģ‡m sao lưu náģn táģ‘t nháēĨt, vui lÃ˛ng vô hiáģ‡u hÃŗa báēĨt káģŗ táģ‘i ưu hÃŗa pin nào đang háēĄn cháēŋ hoáēĄt đáģ™ng náģn cáģ§a Immich.\n\nVÃŦ điáģu này pháģĨ thuáģ™c vào thiáēŋt báģ‹, vui lÃ˛ng tham kháēŖo thông tin cáē§n thiáēŋt cáģ§a nhà sáēŖn xuáēĨt thiáēŋt báģ‹ cáģ§a báēĄn.", "backup_controller_page_background_battery_info_ok": "Đáģ“ng ÃŊ", "backup_controller_page_background_battery_info_title": "Tiáēŋt kiáģ‡m pin", - "backup_controller_page_background_delay": "TrÃŦ hoÃŖn sao lưu tài nguyÃĒn máģ›i: {duration}", + "backup_controller_page_background_charging": "Cháģ‰ khi đang sáēĄc", + "backup_controller_page_background_configure_error": "CáēĨu hÃŦnh dáģ‹ch váģĨ náģn tháēĨt báēĄi", + "backup_controller_page_background_delay": "TrÃŦ hoÃŖn sao lưu táģ‡p máģ›i: {duration}", + "backup_controller_page_background_description": "Báē­t dáģ‹ch váģĨ náģn đáģƒ táģą Ä‘áģ™ng sao lưu táģ‡p máģ›i mà không cáē§n máģŸ app", + "backup_controller_page_background_is_off": "Sao lưu táģą Ä‘áģ™ng trong náģn đang táē¯t", + "backup_controller_page_background_is_on": "Sao lưu táģą Ä‘áģ™ng trong náģn đang báē­t", + "backup_controller_page_background_turn_off": "Táē¯t dáģ‹ch váģĨ náģn", + "backup_controller_page_background_turn_on": "Báē­t dáģ‹ch váģĨ náģn", + "backup_controller_page_background_wifi": "Cháģ‰ khi dÚng Wi-Fi", "backup_controller_page_backup": "Sao lưu", "backup_controller_page_backup_selected": "ÄÃŖ cháģn: ", "backup_controller_page_backup_sub": "áēĸnh và video Ä‘ÃŖ sao lưu", + "backup_controller_page_created": "TáēĄo vào: {date}", + "backup_controller_page_desc_backup": "Báē­t sao lưu khi app hoáēĄt đáģ™ng đáģƒ táģą Ä‘áģ™ng sao lưu táģ‡p máģ›i lÃĒn mÃĄy cháģ§ khi máģŸ app.", "backup_controller_page_excluded": "ÄÃŖ báģ qua: ", - "backup_controller_page_none_selected": "Chưa cháģn máģĨc nào", + "backup_controller_page_failed": "TháēĨt báēĄi ({count})", + "backup_controller_page_filename": "TÃĒn táģ‡p: {filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "Thông tin sao lưu", + "backup_controller_page_none_selected": "Không cÃŗ máģĨc nào đưáģŖc cháģn", "backup_controller_page_remainder": "CÃ˛n láēĄi", "backup_controller_page_remainder_sub": "Sáģ‘ lưáģŖng áēŖnh và video Ä‘ÃŖ cháģn chưa đưáģŖc sao lưu", "backup_controller_page_server_storage": "Dung lưáģŖng mÃĄy cháģ§", - "backup_controller_page_storage_format": "ÄÃŖ dÚng {used} trong {total}", - "backup_controller_page_to_backup": "CÃĄc album sáēŊ đưáģŖc sao lưu", + "backup_controller_page_start_backup": "Báē¯t đáē§u sao lưu", + "backup_controller_page_status_off": "Sao lưu táģą Ä‘áģ™ng khi app hoáēĄt đáģ™ng đang táē¯t", + "backup_controller_page_status_on": "Sao lưu táģą Ä‘áģ™ng khi app hoáēĄt đáģ™ng đang báē­t", + "backup_controller_page_storage_format": "ÄÃŖ dÚng {used} cáģ§a {total}", + "backup_controller_page_to_backup": "CÃĄc album cáē§n đưáģŖc sao lưu", "backup_controller_page_total_sub": "TáēĨt cáēŖ áēŖnh và video không trÚng láē­p táģĢ cÃĄc album đưáģŖc cháģn", + "backup_controller_page_turn_off": "Táē¯t sao lưu khi app hoáēĄt đáģ™ng", + "backup_controller_page_turn_on": "Báē­t sao lưu khi máģŸ app", + "backup_controller_page_uploading_file_info": "Thông tin táģ‡p đang táēŖi lÃĒn", + "backup_err_only_album": "Không tháģƒ xÃŗa album duy nháēĨt", "backup_error_sync_failed": "Đáģ“ng báģ™ tháēĨt báēĄi. Không tháģƒ tiáēŋn hành sao lưu.", - "backup_info_card_assets": "tài nguyÃĒn", + "backup_info_card_assets": "táģ‡p", + "backup_manual_cancelled": "ÄÃŖ háģ§y", + "backup_manual_in_progress": "Đang táēŖi lÃĒn. Vui lÃ˛ng tháģ­ láēĄi sau", + "backup_manual_success": "Thành công", + "backup_manual_title": "TráēĄng thÃĄi táēŖi lÃĒn", "backup_options": "TÚy cháģn sao lưu", - "backup_settings_subtitle": "Thiáēŋt láē­p viáģ‡c táēŖi lÃĒn", - "backward": "LÚi", - "battery_optimization_backup_reliability": "Táē¯t tính năng tiáēŋt kiáģ‡m pin đáģƒ Ä‘áēŖm báēŖo quÃĄ trÃŦnh sao lưu náģn", - "biometric_auth_enabled": "ÄÃŖ báē­t xÃĄc tháģąc vÃĸn tay", - "biometric_locked_out": "BáēĄn Ä‘ÃŖ báģ‹ khÃŗa xÃĄc tháģąc báēąng vÃĸn tay", - "biometric_no_options": "TÚy cháģn vÃĸn tay không kháēŖ dáģĨng", - "biometric_not_available": "XÃĄc tháģąc vÃĸn tay không kháēŖ dáģĨng trÃĒn thiáēŋt báģ‹ này", + "backup_options_page_title": "TÚy cháģn sao lưu", + "backup_setting_subtitle": "QuáēŖn lÃŊ cài đáēˇt táēŖi lÃĒn áģŸ cháēŋ đáģ™ náģn và khi đang máģŸ", + "backup_settings_subtitle": "Cài đáēˇt viáģ‡c táēŖi lÃĒn", + "backup_upload_details_page_more_details": "NháēĨn đáģƒ hiáģ‡n chi tiáēŋt", + "backward": "LÚi láēĄi", + "biometric_auth_enabled": "ÄÃŖ báē­t xÃĄc tháģąc sinh tráē¯c háģc", + "biometric_locked_out": "BáēĄn Ä‘ÃŖ báģ‹ khÃŗa xÃĄc tháģąc báēąng sinh tráē¯c háģc", + "biometric_no_options": "Không cÃŗ tÚy cháģn báēąng sinh tráē¯c háģc", + "biometric_not_available": "XÃĄc tháģąc sinh tráē¯c háģc không kháēŖ dáģĨng trÃĒn thiáēŋt báģ‹ này", + "birthdate_saved": "Sinh nháē­t Ä‘ÃŖ đưáģŖc lưu thành công", "birthdate_set_description": "Sinh nháē­t đưáģŖc sáģ­ dáģĨng đáģƒ tính tuáģ•i cáģ§a ngưáģi này táēĄi tháģi điáģƒm cháģĨp áēŖnh.", "blurred_background": "Náģn máģ", - "browse_templates": "Xem máēĢu cÃŗ sáēĩn", "bugs_and_feature_requests": "BÃĄo láģ—i & Đáģ xuáēĨt tính năng", - "build": "Build", + "build": "Dáģąng", "build_image": "BáēŖn dáģąng", - "bulk_delete_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa hàng loáēĄt {count, plural, one {# tài nguyÃĒn trÚng láēˇp} other {# tài nguyÃĒn trÚng láēˇp}}? Điáģu này sáēŊ giáģ¯ láēĄi tài nguyÃĒn láģ›n nháēĨt cáģ§a máģ—i nhÃŗm và xÃŗa vÄŠnh viáģ…n táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp khÃĄc. BáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này!", - "bulk_keep_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n giáģ¯ láēĄi {count, plural, one {# tài nguyÃĒn trÚng láēˇp} other {# tài nguyÃĒn trÚng láēˇp}}? Điáģu này sáēŊ xáģ­ lÃŊ táēĨt cáēŖ cÃĄc nhÃŗm áēŖnh trÚng láēˇp mà không xÃŗa báēĨt káģŗ tháģŠ gÃŦ.", - "bulk_trash_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n đưa {count, plural, one {# tài nguyÃĒn trÚng láēˇp} other {# tài nguyÃĒn trÚng láēˇp}} vào thÚng rÃĄc? Điáģu này sáēŊ giáģ¯ láēĄi tài nguyÃĒn láģ›n nháēĨt cáģ§a máģ—i nhÃŗm và đưa táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp khÃĄc vào thÚng rÃĄc.", + "bulk_delete_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa hàng loáēĄt {count, plural, one {# táģ‡p trÚng láēˇp} other {# táģ‡p trÚng láēˇp}}? Điáģu này sáēŊ giáģ¯ láēĄi áēŖnh cháēĨt lưáģŖng nháēĨt cáģ§a máģ—i nhÃŗm và xÃŗa vÄŠnh viáģ…n táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp khÃĄc. BáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này!", + "bulk_keep_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n giáģ¯ láēĄi {count, plural, one {# táģ‡p trÚng láēˇp} other {# táģ‡p trÚng láēˇp}}? Điáģu này sáēŊ xáģ­ lÃŊ táēĨt cáēŖ cÃĄc nhÃŗm áēŖnh trÚng láēˇp mà không xÃŗa báēĨt káģŗ tháģŠ gÃŦ.", + "bulk_trash_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n đưa {count, plural, one {# táģ‡p trÚng láēˇp} other {# táģ‡p trÚng láēˇp}} vào thÚng rÃĄc? Điáģu này sáēŊ giáģ¯ láēĄi áēŖnh cháēĨt lưáģŖng nháēĨt cáģ§a máģ—i nhÃŗm và đưa táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp khÃĄc vào thÚng rÃĄc.", "buy": "Mua Immich", + "cache_settings_clear_cache_button": "XÃŗa báģ™ nháģ› Ä‘áģ‡m", + "cache_settings_clear_cache_button_title": "XÃŗa báģ™ nháģ› Ä‘áģ‡m cáģ§a app. Điáģu này sáēŊ áēŖnh hưáģŸng đáēŋn hiáģ‡u suáēĨt cáģ§a app đáēŋn khi báģ™ nháģ› Ä‘áģ‡m đưáģŖc táēĄo láēĄi.", + "cache_settings_duplicated_assets_clear_button": "XÓA", + "cache_settings_duplicated_assets_subtitle": "áēĸnh và video không đưáģŖc phÊp hiáģƒn tháģ‹ trÃĒn app", + "cache_settings_duplicated_assets_title": "Táģ‡p báģ‹ trÚng ({count})", + "cache_settings_statistics_album": "áēĸnh thu nháģ thư viáģ‡n", + "cache_settings_statistics_full": "áēĸnh đáē§y đáģ§", + "cache_settings_statistics_shared": "áēĸnh thu nháģ album chia sáēģ", + "cache_settings_statistics_thumbnail": "áēĸnh thu nháģ", + "cache_settings_statistics_title": "MáģŠc sáģ­ dáģĨng báģ™ nháģ› Ä‘áģ‡m", + "cache_settings_subtitle": "Kiáģƒm soÃĄt hành vi báģ™ nháģ› Ä‘áģ‡m cáģ§a app Immich", + "cache_settings_tile_subtitle": "Kiáģƒm soÃĄt cÃĄch xáģ­ lÃŊ lưu tráģ¯ cáģĨc báģ™", + "cache_settings_tile_title": "Lưu tráģ¯ cáģĨc báģ™", + "cache_settings_title": "Cài đáēˇt báģ™ nháģ› Ä‘áģ‡m", "camera": "MÃĄy áēŖnh", "camera_brand": "ThÆ°ÆĄng hiáģ‡u mÃĄy áēŖnh", "camera_model": "DÃ˛ng mÃĄy áēŖnh", @@ -677,11 +728,11 @@ "cannot_undo_this_action": "BáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này!", "cannot_update_the_description": "Không tháģƒ cáē­p nháē­t mô táēŖ", "cast": "Chiáēŋu", - "cast_description": "Thiáēŋt láē­p thiáēŋt báģ‹ chiáēŋu kháēŖ dáģĨng", - "change": "Thay đáģ•i", + "cast_description": "CáēĨu hÃŦnh cÃĄc thiáēŋt báģ‹ chiáēŋu kháēŖ dáģĨng", "change_date": "Thay đáģ•i ngày", "change_description": "Thay đáģ•i mô táēŖ", "change_display_order": "Sáē¯p xáēŋp hiáģƒn tháģ‹", + "change_expiration_time": "Thay đáģ•i tháģi gian háēŋt háēĄn", "change_location": "Thay đáģ•i váģ‹ trí", "change_name": "Đáģ•i tÃĒn", "change_name_successfully": "ÄÃŖ đáģ•i tÃĒn thành công", @@ -695,34 +746,39 @@ "change_password_form_password_mismatch": "Máē­t kháēŠu không giáģ‘ng nhau", "change_password_form_reenter_new_password": "Nháē­p láēĄi máē­t kháēŠu máģ›i", "change_pin_code": "Thay đáģ•i mÃŖ PIN", + "change_trigger": "Thay đáģ•i trÃŦnh kích hoáēĄt", + "change_trigger_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n thay đáģ•i trÃŦnh kích hoáēĄt? Thao tÃĄc này sáēŊ xÃŗa táēĨt cáēŖ cÃĄc hành đáģ™ng và báģ™ láģc hiáģ‡n cÃŗ.", "change_your_password": "Đáģ•i máē­t kháēŠu cáģ§a báēĄn", "changed_visibility_successfully": "ÄÃŖ đáģ•i tráēĄng thÃĄi hiáģƒn tháģ‹ thành công", "charging": "SáēĄc", "charging_requirement_mobile_backup": "Sao lưu dưáģ›i náģn yÃĒu cáē§u thiáēŋt báģ‹ pháēŖi đang sáēĄc", + "check_corrupt_asset_backup": "Kiáģƒm tra táģ‡p báģ‹ háģng", + "check_corrupt_asset_backup_button": "Tiáēŋn hành kiáģƒm tra", + "check_corrupt_asset_backup_description": "Cháģ‰ cháēĄy kiáģƒm tra này khi cÃŗ Wi-Fi và sau khi Ä‘ÃŖ sao lưu toàn báģ™ dáģ¯ liáģ‡u. QuÃĄ trÃŦnh cÃŗ tháģƒ máēĨt vài phÃēt.", "check_logs": "Kiáģƒm tra log", "checksum": "Checksum", - "choose": "Cháģn", "choose_matching_people_to_merge": "Cháģn nháģ¯ng ngưáģi trÚng kháģ›p đáģƒ háģŖp nháēĨt", "city": "Thành pháģ‘", - "cleanup_confirm_description": "Immich tÃŦm tháēĨy {count} tài nguyÃĒn (đưáģŖc táēĄo ra trưáģ›c {date}) đưáģŖc sao lưu trÃĒn mÃĄy cháģ§. BáēĄn cÃŗ muáģ‘n xÃŗa phiÃĒn báēŖn cáģ§a chÃēng lưu trÃĒn thiáēŋt báģ‹ này không?", + "cleanup_confirm_description": "Immich phÃĄt hiáģ‡n {count} táģ‡p (đưáģŖc táēĄo ra trưáģ›c {date}) đưáģŖc sao lưu trÃĒn mÃĄy cháģ§. BáēĄn cÃŗ muáģ‘n xÃŗa báēŖn sao đưáģŖc lưu trÃĒn thiáēŋt báģ‹ này không?", "cleanup_confirm_prompt_title": "XÃŗa kháģi thiáēŋt báģ‹ này?", - "cleanup_deleted_assets": "ÄÃŖ chuyáģƒn {count} tài nguyÃĒn vào thÚng rÃĄc", + "cleanup_deleted_assets": "ÄÃŖ chuyáģƒn {count} táģ‡p vào thÚng rÃĄc", "cleanup_deleting": "Đang chuyáģƒn vào thÚng rÃĄc...", - "cleanup_found_assets": "TÃŦm tháēĨy {count} tài nguyÃĒn Ä‘ÃŖ sao lưu", - "cleanup_found_assets_with_size": "PhÃĄt hiáģ‡n {count} tài nguyÃĒn Ä‘ÃŖ sao lưu ({size})", + "cleanup_found_assets": "PhÃĄt hiáģ‡n {count} táģ‡p đưáģŖc sao lưu", + "cleanup_found_assets_with_size": "PhÃĄt hiáģ‡n {count} đưáģŖc sao lưu ({size})", "cleanup_icloud_shared_albums_excluded": "Nháģ¯ng album đưáģŖc chia sáēģ trÃĒn iCloud không náēąm trong pháēĄm vi quÊt", - "cleanup_no_assets_found": "Không tÃŦm tháēĨy tài nguyÃĒn nào phÚ háģŖp váģ›i điáģu kiáģ‡n trÃĒn. Tính năng GiáēŖi phÃŗng dung lưáģŖng cháģ‰ cÃŗ tháģƒ xÃŗa cÃĄc tài nguyÃĒn Ä‘ÃŖ đưáģŖc sao lưu lÃĒn mÃĄy cháģ§", - "cleanup_preview_title": "CÃĄc tài nguyÃĒn sáēŊ báģ‹ xÃŗa ({count})", - "cleanup_step3_description": "TÃŦm cÃĄc tài nguyÃĒn Ä‘ÃŖ đưáģŖc sao lưu theo báģ™ láģc ngày và giáģ¯ láēĄi cài đáēˇt cáģ§a báēĄn.", - "cleanup_step4_summary": "CÃŗ {count} ttài nguyÃĒn (đưáģŖc táēĄo trưáģ›c {date}) sáēŊ báģ‹ xÃŗa kháģi thiáēŋt báģ‹ cáģ§a báēĄn. áēĸnh váēĢn cÃŗ tháģƒ Ä‘Æ°áģŖc truy cáē­p báēąng áģŠng dáģĨng Immich.", - "cleanup_trash_hint": "Đáģƒ tiáēŋt kiáģ‡m táģ‘i đa báģ™ nháģ›, vui lÃ˛ng máģŸ áģŠng dáģĨng thư viáģ‡n áēŖnh cáģ§a háģ‡ tháģ‘ng và dáģn sáēĄch thÚng rÃĄc", + "cleanup_no_assets_found": "Không tÃŦm tháēĨy táģ‡p nào phÚ háģŖp váģ›i điáģu khiáģ‡n trÃĒn. Tính năng GiáēŖi phÃŗng dung lưáģŖng cháģ‰ cÃŗ tháģƒ xÃŗa táģ‡p Ä‘ÃŖ đưáģŖc sao lưu lÃĒn mÃĄy cháģ§", + "cleanup_preview_title": "CÃĄc táģ‡p sáēŊ báģ‹ xÃŗa ({count})", + "cleanup_step3_description": "TÃŦm cÃĄc táģ‡p Ä‘ÃŖ đưáģŖc sao lưu theo báģ™ láģc ngày và giáģ¯ láēĄi cài đáēˇt cáģ§a báēĄn.", + "cleanup_step4_summary": "CÃŗ {count} táģ‡p (đưáģŖc táēĄo trưáģ›c {date}) sáēŊ báģ‹ xÃŗa kháģi thiáēŋt báģ‹ cáģ§a báēĄn. áēĸnh váēĢn cÃŗ tháģƒ Ä‘Æ°áģŖc truy cáē­p báēąng áģŠng dáģĨng Immich.", + "cleanup_trash_hint": "Đáģƒ giáēŖi phÃŗng táģ‘i đa báģ™ nháģ›, vui lÃ˛ng máģŸ áģŠng dáģĨng thư viáģ‡n áēŖnh cáģ§a háģ‡ tháģ‘ng và dáģn sáēĄch thÚng rÃĄc", "clear": "XÃŗa", "clear_all": "XÃŗa táēĨt cáēŖ", "clear_all_recent_searches": "XÃŗa táēĨt cáēŖ tÃŦm kiáēŋm gáē§n đÃĸy", - "clear_failed_count": "XÃŗa tháēĨt báēĄi ({count})", "clear_file_cache": "XÃŗa báģ™ nháģ› Ä‘áģ‡m", "clear_message": "XÃŗa thông điáģ‡p", "clear_value": "XÃŗa giÃĄ tráģ‹", + "client_cert_dialog_msg_confirm": "Đáģ“ng ÃŊ", + "client_cert_enter_password": "Nháē­p máē­t kháēŠu", "client_cert_import": "Nháē­p", "client_cert_import_success_msg": "CháģŠng cháģ‰ khÃĄch Ä‘ÃŖ đưáģŖc nháē­p", "client_cert_invalid_msg": "Táģ‡p cháģŠng cháģ‰ không háģŖp láģ‡ hoáēˇc sai máē­t kháēŠu", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "CháģŠng cháģ‰ khÃĄch Ä‘ÃŖ báģ‹ xÃŗa", "client_cert_subtitle": "Cháģ‰ háģ— tráģŖ Ä‘áģ‹nh dáēĄng PKCS12 (.p12, .pfx). Cháģ‰ cÃŗ tháģƒ nháē­p/xÃŗa cháģŠng cháģ‰ trưáģ›c khi đăng nháē­p", "client_cert_title": "CháģŠng cháģ‰ khÃĄch SSL [THáģŦ NGHIáģ†M]", + "clockwise": "Xoay pháēŖi", "close": "ÄÃŗng", "collapse": "Thu gáģn", "collapse_all": "Thu gáģn táēĨt cáēŖ", "color": "Màu", + "color_theme": "Màu cháģ§ Ä‘áģ", "command": "DÃ˛ng láģ‡nh", "command_palette_prompt": "TÃŦm nhanh cÃĄc trang, thao tÃĄc, hoáēˇc láģ‡nh", "command_palette_to_close": "ÄÃŗng", @@ -747,35 +805,37 @@ "comments_are_disabled": "BÃŦnh luáē­n Ä‘ÃŖ báģ‹ táē¯t", "common_create_new_album": "TáēĄo album máģ›i", "completed": "Hoàn táēĨt", - "configuration": "CáēĨu hÃŦnh", "confirm": "XÃĄc nháē­n", "confirm_admin_password": "XÃĄc nháē­n máē­t kháēŠu quáēŖn tráģ‹ viÃĒn", - "confirm_delete_face": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa khuôn máēˇt {name} kháģi tài nguyÃĒn?", - "confirm_delete_shared_link": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa link chia sáēģ này?", - "confirm_keep_this_delete_others": "CÃĄc tài nguyÃĒn cÃ˛n láēĄi trong nhÃŗm sáēŊ báģ‹ xÃŗa ngoáēĄi tráģĢ tài nguyÃĒn này. BáēĄn cÃŗ cháē¯c muáģ‘n tiáēŋp táģĨc?", + "confirm_delete_face": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa khuôn máēˇt {name} kháģi táģ‡p?", + "confirm_delete_shared_link": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa liÃĒn káēŋt chia sáēģ này?", + "confirm_keep_this_delete_others": "CÃĄc áēŖnh cÃ˛n láēĄi trong nhÃŗm sáēŊ báģ‹ xÃŗa ngoáēĄi tráģĢ áēŖnh này. BáēĄn cÃŗ cháē¯c muáģ‘n tiáēŋp táģĨc?", "confirm_new_pin_code": "XÃĄc nháē­n mÃŖ PIN máģ›i", "confirm_password": "XÃĄc nháē­n máē­t kháēŠu", - "confirm_tag_face": "BáēĄn cÃŗ muáģ‘n gáē¯n tháēģ khuôn máēˇt này là cáģ§a {name}?", - "confirm_tag_face_unnamed": "BáēĄn cÃŗ muáģ‘n gáē¯n tháēģ khuôn máēˇt này?", + "confirm_tag_face": "BáēĄn cÃŗ muáģ‘n gáē¯n tháēģ gÆ°ÆĄng máēˇt này là {name}?", + "confirm_tag_face_unnamed": "BáēĄn cÃŗ muáģ‘n gáē¯n tháēģ gÆ°ÆĄng máēˇt này?", "connected_device": "Thiáēŋt báģ‹ Ä‘Æ°áģŖc káēŋt náģ‘i", "connected_to": "ÄÃŖ káēŋt náģ‘i táģ›i", "contain": "VáģĢa màn hÃŦnh", "context": "Ngáģ¯ cáēŖnh", "continue": "Tiáēŋp táģĨc", - "control_bottom_app_bar_add_tags": "ThÃĒm tháēģ", + "control_bottom_app_bar_create_new_album": "TáēĄo album máģ›i", + "control_bottom_app_bar_delete_from_immich": "XÃŗa kháģi Immich", "control_bottom_app_bar_delete_from_local": "XÃŗa kháģi thiáēŋt báģ‹", - "control_bottom_app_bar_edit_location": "Sáģ­a đáģ‹a điáģƒm", - "control_bottom_app_bar_edit_time": "Sáģ­a ngày", - "control_bottom_app_bar_trash_from_immich": "XÃŗa", - "copied_image_to_clipboard": "ÄÃŖ sao chÊp áēŖnh vào clipboard.", - "copied_to_clipboard": "ÄÃŖ sao chÊp vào clipboard!", + "control_bottom_app_bar_edit_location": "Cháģ‰nh sáģ­a váģ‹ trí", + "control_bottom_app_bar_edit_time": "Cháģ‰nh sáģ­a Ngày và Giáģ", + "control_bottom_app_bar_share_link": "Chia sáēģ liÃĒn káēŋt", + "control_bottom_app_bar_share_to": "Chia sáēģ váģ›i", + "control_bottom_app_bar_trash_from_immich": "Di chuyáģƒn đáēŋn ThÚng rÃĄc", + "copied_image_to_clipboard": "ÄÃŖ sao chÊp áēŖnh vào báģ™ nháģ› táēĄm.", + "copied_to_clipboard": "ÄÃŖ sao chÊp vào báģ™ nháģ› táēĄm!", "copy_error": "Sao chÊp láģ—i", + "copy_file_path": "Sao chÊp đưáģng dáēĢn táģ‡p", "copy_image": "Sao chÊp áēŖnh", - "copy_json": "Sao chÊp JSON", - "copy_link": "Sao chÊp link", - "copy_link_to_clipboard": "Sao chÊp liÃĒn káēŋt vào clipboard", + "copy_link": "Sao chÊp liÃĒn káēŋt", + "copy_link_to_clipboard": "Sao chÊp liÃĒn káēŋt vào báģ™ nháģ› táēĄm", "copy_password": "Sao chÊp máē­t kháēŠu", - "copy_to_clipboard": "Sao chÊp vào clipboard", + "copy_to_clipboard": "Sao chÊp vào báģ™ nháģ› táēĄm", "country": "Quáģ‘c gia", "cover": "Táģ‘i đa", "covers": "Lưáģ›i", @@ -785,18 +845,16 @@ "create_api_key": "TáēĄo khÃŗa API", "create_first_workflow": "TáēĄo workflow đáē§u tiÃĒn", "create_library": "TáēĄo thư viáģ‡n", - "create_link": "TáēĄo link", - "create_link_to_share": "TáēĄo link đáģƒ chia sáēģ", - "create_link_to_share_description": "Ai cÃŗ link sáēŊ xem đưáģŖc cÃĄc áēŖnh Ä‘ÃŖ cháģn", + "create_link": "TáēĄo liÃĒn káēŋt", + "create_link_to_share": "TáēĄo liÃĒn káēŋt đáģƒ chia sáēģ", + "create_link_to_share_description": "Ai cÃŗ liÃĒn káēŋt sáēŊ xem đưáģŖc cÃĄc áēŖnh Ä‘ÃŖ cháģn", "create_new": "Táē O MáģšI", "create_new_person": "TáēĄo ngưáģi máģ›i", - "create_new_person_hint": "GÃĄn cÃĄc tài nguyÃĒn Ä‘ÃŖ cháģn cho máģ™t ngưáģi máģ›i", + "create_new_person_hint": "GÃĄn cÃĄc áēŖnh Ä‘ÃŖ cháģn cho máģ™t ngưáģi máģ›i", "create_new_user": "TáēĄo ngưáģi dÚng máģ›i", - "create_person": "TáēĄo ngưáģi", - "create_person_subtitle": "ThÃĒm tÃĒn vào khuôn máēˇt Ä‘ÃŖ cháģn đáģƒ táēĄo và gáē¯n tháēģ ngưáģi máģ›i", - "create_shared_album_page_share_add_assets": "THÊM TÀI NGUYÊN", + "create_shared_album_page_share_add_assets": "THÊM Táģ†P", "create_shared_album_page_share_select_photos": "Cháģn áēŖnh", - "create_shared_link": "TáēĄo link chia sáēģ", + "create_shared_link": "Chia sáēģ qua liÃĒn káēŋt", "create_tag": "TáēĄo tháēģ", "create_tag_description": "TáēĄo tháēģ máģ›i. Váģ›i cÃĄc tháēģ láģ“ng nhau, vui lÃ˛ng nháē­p đưáģng dáēĢn đáē§y đáģ§ cáģ§a tháēģ bao gáģ“m dáēĨu gáēĄch chÊo.", "create_user": "TáēĄo ngưáģi dÚng", @@ -805,69 +863,77 @@ "created_at": "ÄÃŖ táēĄo", "creating_linked_albums": "Đang táēĄo album đưáģŖc liÃĒn káēŋt...", "crop": "Cáē¯t", + "crop_aspect_ratio_fixed": "Cáģ‘ Ä‘áģ‹nh", "crop_aspect_ratio_free": "Táģą do", - "crop_aspect_ratio_original": "Gáģ‘c", - "crop_aspect_ratio_square": "Vuông", + "crop_aspect_ratio_original": "NguyÃĒn báēŖn", + "curated_object_page_title": "Đáģ‘i tưáģŖng", "current_device": "Thiáēŋt báģ‹ hiáģ‡n táēĄi", "current_pin_code": "MÃŖ PIN hiáģ‡n táēĄi", "current_server_address": "Đáģ‹a cháģ§ mÃĄy cháģ§ hiáģ‡n táēĄi", - "custom_date": "TÚy cháģ‰nh máģ‘c", + "custom_date": "Thiáēŋt láē­p ngày tÚy cháģ‰nh", "custom_locale": "Khu váģąc tÚy cháģ‰nh", "custom_locale_description": "Đáģ‹nh dáēĄng ngày, tháģi gian và sáģ‘ dáģąa trÃĒn ngôn ngáģ¯ và khu váģąc Ä‘ÃŖ cháģn", - "cutoff_date_description": "Giáģ¯ láēĄi cÃĄc áēŖnh táģĢâ€Ļ", + "custom_url": "URL tÚy cháģ‰nh", + "cutoff_date_description": "Giáģ¯ láēĄi áēŖnh trong vÃ˛ngâ€Ļ", "cutoff_day": "{count, plural, one {ngày} other {ngày}}", "cutoff_year": "{count, plural, one {năm} other {năm}}", + "daily_title_text_date": "E, dd MMM", + "daily_title_text_date_year": "E, dd MMM, yyyy", "dark": "Táģ‘i", "dark_theme": "Chuyáģƒn sang cháģ§ Ä‘áģ táģ‘i", "date": "Ngày", "date_after": "Ngày sau", "date_and_time": "Ngày và giáģ", "date_before": "Ngày trưáģ›c", - "date_of_birth": "Ngày sinh", + "date_format": "E, d LLL, y â€ĸ h:mm a", "date_of_birth_saved": "Sinh nháē­t Ä‘ÃŖ đưáģŖc lưu thành công", "date_range": "KhoáēŖng tháģi gian", - "date_time_original": "Ngày/Giáģ gáģ‘c", "day": "Ngày", "days": "Ngày", "deduplicate_all": "XÃŗa táēĨt cáēŖ máģĨc trÚng láēˇp", - "default_quality_subtitle": "CháēĨt lưáģŖng khi nháēĨn chia sáēģ. NháēĨn giáģ¯ nÃēt chia sáēģ đáģƒ cháģn riÃĒng táģĢng láē§n.", - "default_share_quality": "CháēĨt lưáģŖng chia sáēģ máēˇc đáģ‹nh", "delete": "XÃŗa", - "delete_action_confirmation_message": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa tài nguyÃĒn này? Tài nguyÃĒn sáēŊ đưáģŖc chuyáģƒn vào thÚng rÃĄc cáģ§a mÃĄy cháģ§ và sáēŊ háģi báēĄn cÃŗ muáģ‘n xÃŗa nÃŗ trÃĒn Ä‘Ãŗ không", - "delete_action_prompt": "ÄÃŖ xÃŗa ({count})", + "delete_action_confirmation_message": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa táģ‡p này? Thao tÃĄc này sáēŊ chuyáģƒn táģ‡p vào thÚng rÃĄc cáģ§a mÃĄy cháģ§ và sáēŊ háģi báēĄn cÃŗ muáģ‘n xÃŗa nÃŗ cáģĨc báģ™ không", + "delete_action_prompt": "{count} Ä‘ÃŖ xÃŗa", "delete_album": "XÃŗa album", "delete_api_key_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa khÃŗa API này?", "delete_dialog_alert": "Nháģ¯ng máģĨc này sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n kháģi Immich và thiáēŋt báģ‹ cáģ§a báēĄn", "delete_dialog_alert_local": "Nháģ¯ng máģĨc này sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n kháģi thiáēŋt báģ‹ cáģ§a báēĄn nhưng váēĢn cÃ˛n lưu tráģ¯ trÃĒn mÃĄy cháģ§ Immich", "delete_dialog_alert_local_non_backed_up": "Máģ™t sáģ‘ máģĨc chưa đưáģŖc sao lưu lÃĒn Immich và sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n kháģi thiáēŋt báģ‹ cáģ§a báēĄn", + "delete_dialog_alert_remote": "Nháģ¯ng máģĨc này sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n kháģi mÃĄy cháģ§ Immich", + "delete_dialog_ok_force": "XÃŗa VÄŠnh Viáģ…n", "delete_dialog_title": "XÃŗa vÄŠnh viáģ…n", "delete_duplicates_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa vÄŠnh viáģ…n cÃĄc báēŖn trÚng láēˇp này?", "delete_face": "XÃŗa khuôn máēˇt", "delete_key": "XÃŗa khÃŗa", "delete_library": "XÃŗa Thư viáģ‡n", - "delete_link": "XÃŗa link", - "delete_local_action_prompt": "ÄÃŖ xÃŗa ({count}) trÃĒn thiáēŋt báģ‹", + "delete_link": "XÃŗa liÃĒn káēŋt", + "delete_local_action_prompt": "{count} Ä‘ÃŖ xÃŗa trÃĒn thiáēŋt báģ‹", "delete_local_dialog_ok_backed_up_only": "XÃŗa áēŖnh Ä‘ÃŖ sao lưu", "delete_local_dialog_ok_force": "VáēĢn xÃŗa", "delete_others": "XÃŗa áēŖnh cÃ˛n láēĄi", "delete_permanently": "XÃŗa vÄŠnh viáģ…n", - "delete_permanently_action_prompt": "ÄÃŖ xÃŗa vÄŠnh viáģ…n ({count})", - "delete_shared_link": "XÃŗa link Ä‘ÃŖ chia sáēģ", - "delete_shared_link_dialog_title": "XÃŗa link Ä‘ÃŖ chia sáēģ", + "delete_permanently_action_prompt": "{count} Ä‘ÃŖ xÃŗa vÄŠnh viáģ…n", + "delete_shared_link": "XÃŗa liÃĒn káēŋt chia sáēģ", + "delete_shared_link_dialog_title": "XÃŗa liÃĒn káēŋt Ä‘ÃŖ chia sáēģ", "delete_tag": "XÃŗa tháēģ", "delete_tag_confirmation_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa tháēģ {tagName}?", "delete_user": "XÃŗa ngưáģi dÚng", - "deleted_shared_link": "ÄÃŖ xÃŗa link chia sáēģ", + "deleted_shared_link": "ÄÃŖ xÃŗa liÃĒn káēŋt chia sáēģ", + "deletes_missing_assets": "XÃŗa cÃĄc áēŖnh không cÃ˛n táģ“n táēĄi trÃĒn áģ• Ä‘ÄŠa", "description": "Mô táēŖ", + "description_input_hint_text": "ThÃĒm mô táēŖ...", + "description_input_submit_error": "Cáē­p nháē­t mô táēŖ không thành công, vui lÃ˛ng kiáģƒm tra log đáģƒ biáēŋt thÃĒm chi tiáēŋt", "deselect_all": "Báģ cháģn táēĨt cáēŖ", "details": "Chi tiáēŋt", - "direction": "Điáģu hưáģ›ng", + "direction": "Hưáģ›ng", "disable": "Vô hiáģ‡u hÃŗa", "disabled": "ÄÃŖ táē¯t", + "disallow_edits": "Không cho phÊp cháģ‰nh sáģ­a", "discord": "Discord", "discover": "KhÃĄm phÃĄ", "discovered_devices": "KhÃĄm phÃĄ thiáēŋt báģ‹", "dismiss_all_errors": "Báģ qua táēĨt cáēŖ láģ—i", + "dismiss_error": "Báģ qua láģ—i", "display_options": "TÚy cháģn hiáģƒn tháģ‹", "display_order": "TháģŠ táģą hiáģƒn tháģ‹", "display_original_photos": "Hiáģƒn tháģ‹ áēŖnh gáģ‘c", @@ -876,29 +942,31 @@ "documentation": "Tài liáģ‡u", "done": "Xong", "download": "TáēŖi xuáģ‘ng", + "download_action_prompt": "Đang táēŖi {count} táģ‡p", "download_canceled": "ÄÃŖ háģ§y táēŖi xuáģ‘ng", "download_complete": "TáēŖi xuáģ‘ng hoàn táēĨt", "download_enqueue": "TáēŖi xuáģ‘ng đang cháģ", + "download_error": "Láģ—i táēŖi xuáģ‘ng", "download_failed": "TáēŖi xuáģ‘ng tháēĨt báēĄi", "download_finished": "TáēŖi xuáģ‘ng hoàn táēĨt", "download_include_embedded_motion_videos": "Video nhÃēng", - "download_include_embedded_motion_videos_description": "TÃĄch cÃĄc video đưáģŖc nhÃēng trong áēŖnh chuyáģƒn đáģ™ng thành máģ™t táģ‡p riÃĒng", + "download_include_embedded_motion_videos_description": "Gáģ“m cÃĄc video đưáģŖc nhÃēng trong áēŖnh chuyáģƒn đáģ™ng thành máģ™t táģ‡p riÃĒng", "download_notfound": "Không tÃŦm tháēĨy táēŖi xuáģ‘ng", "download_original": "TáēŖi xuáģ‘ng báēŖn gáģ‘c", "download_paused": "ÄÃŖ táēĄm dáģĢng táēŖi xuáģ‘ng", "download_settings": "TáēŖi xuáģ‘ng", - "download_settings_description": "QuáēŖn lÃŊ cài đáēˇt liÃĒn quan đáēŋn viáģ‡c táēŖi xuáģ‘ng tài nguyÃĒn", + "download_settings_description": "QuáēŖn lÃŊ cài đáēˇt liÃĒn quan đáēŋn viáģ‡c táēŖi áēŖnh xuáģ‘ng", + "download_started": "ÄÃŖ báē¯t đáē§u táēŖi xuáģ‘ng", + "download_sucess": "TáēŖi xuáģ‘ng thành công", + "download_sucess_android": "PhÆ°ÆĄng tiáģ‡n Ä‘ÃŖ đưáģŖc lưu vào DCIM/Immich", "download_waiting_to_retry": "Đang cháģ tháģ­ láēĄi", "downloading": "Đang táēŖi xuáģ‘ng", - "downloading_asset_filename": "Đang táēŖi xuáģ‘ng tài nguyÃĒn {filename}", + "downloading_asset_filename": "Đang táēŖi xuáģ‘ng táģ‡p {filename}", "downloading_from_icloud": "Đang táēŖi xuáģ‘ng táģĢ iCloud", "downloading_media": "Đang táēŖi xuáģ‘ng phÆ°ÆĄng tiáģ‡n", - "drag_to_reorder": "KÊo đáģƒ sáē¯p xáēŋp láēĄi", "drop_files_to_upload": "KÊo tháēŖ cÃĄc táģ‡p đáģƒ táēŖi lÃĒn", - "duplicate": "NhÃĸn báēŖn", - "duplicate_workflow": "NhÃĸn báēŖn workflow", "duplicates": "Táģ‡p trÚng láēˇp", - "duplicates_description": "GiáēŖi quyáēŋt táģĢng nhÃŗm báēąng cÃĄch cháģ‰ ra nhÃŗm nào, náēŋu cÃŗ, là nhÃŗm trÚng láēˇp.", + "duplicates_description": "Xem láēĄi cÃĄc nhÃŗm áēŖnh báģ‹ nghi ngáģ trÚng láēˇp và cháģn nháģ¯ng máģĨc báēĄn muáģ‘n giáģ¯ hoáēˇc xÃŗa", "duration": "Tháģi gian", "edit": "Cháģ‰nh sáģ­a", "edit_album": "Cháģ‰nh sáģ­a album", @@ -906,15 +974,17 @@ "edit_birthday": "Sáģ­a ngày sinh", "edit_date": "Cháģ‰nh sáģ­a ngày", "edit_date_and_time": "Cháģ‰nh sáģ­a ngày và giáģ", - "edit_date_and_time_action_prompt": "ÄÃŖ sáģ­a ngày và giáģ ({count})", + "edit_date_and_time_action_prompt": "{count} Ä‘ÃŖ sáģ­a ngày và giáģ", "edit_date_and_time_by_offset": "Thay đáģ•i ngày theo đáģ™ láģ‡ch", + "edit_date_and_time_by_offset_interval": "KhoáēŖng ngày máģ›i: {from} - {to}", "edit_description": "Cháģ‰nh sáģ­a mô táēŖ", + "edit_description_prompt": "Vui lÃ˛ng cháģn máģ™t mô táēŖ máģ›i:", "edit_exclusion_pattern": "Cháģ‰nh sáģ­a quy táē¯c loáēĄi tráģĢ", "edit_faces": "Cháģ‰nh sáģ­a khuôn máēˇt", "edit_key": "Cháģ‰nh sáģ­a khÃŗa", - "edit_link": "Cháģ‰nh sáģ­a link", + "edit_link": "Cháģ‰nh sáģ­a liÃĒn káēŋt", "edit_location": "Cháģ‰nh sáģ­a váģ‹ trí", - "edit_location_action_prompt": "ÄÃŖ sáģ­a đáģ‹a điáģƒm ({count})", + "edit_location_action_prompt": "{count} Ä‘ÃŖ sáģ­a váģ‹ trí", "edit_location_dialog_title": "Váģ‹ trí", "edit_name": "Cháģ‰nh sáģ­a tÃĒn", "edit_people": "Cháģ‰nh sáģ­a ngưáģi", @@ -923,76 +993,78 @@ "edit_user": "Cháģ‰nh sáģ­a ngưáģi dÚng", "edit_workflow": "Sáģ­a workflow", "editor": "TrÃŦnh cháģ‰nh sáģ­a", + "editor_close_without_save_prompt": "Nháģ¯ng thay đáģ•i sáēŊ không đưáģŖc lưu", + "editor_close_without_save_title": "ÄÃŗng trÃŦnh cháģ‰nh sáģ­a?", + "editor_confirm_reset_all_changes": "BáēĄn cÃŗ cháē¯c muáģ‘n đáēˇt láēĄi máģi thay đáģ•i không?", "editor_discard_edits_confirm": "Báģ thay đáģ•i", - "editor_discard_edits_prompt": "BáēĄn cÃŗ nháģ¯ng sáģ­a đáģ•i chưa đưáģŖc lưu. BáēĄn cÃŗ cháē¯c muáģ‘n háģ§y báģ chÃēng không?", + "editor_discard_edits_prompt": "BáēĄn cÃŗ nháģ¯ng thay đáģ•i chưa đưáģŖc lưu. BáēĄn cÃŗ cháē¯c cháē¯n muáģ‘n háģ§y báģ chÃēng không?", "editor_discard_edits_title": "Háģ§y thay đáģ•i?", - "editor_edits_applied_error": "Không tháģƒ ÃĄp dáģĨng cháģ‰nh sáģ­a", - "editor_edits_applied_success": "Cháģ‰nh sáģ­a đưáģŖc ÃĄp dáģĨng thành công", + "editor_edits_applied_error": "Láģ—i khi ÃĄp dáģĨng thay đáģ•i", + "editor_edits_applied_success": "Thay đáģ•i đưáģŖc ÃĄp dáģĨng thành công", "editor_flip_horizontal": "Láē­t ngang", "editor_flip_vertical": "Láē­t dáģc", - "editor_handle_corner": "{corner, select, top_left {GÃŗc trÃĒn bÃĒn trÃĄi} top_right {GÃŗc trÃĒn bÃĒn pháēŖi} bottom_left {GÃŗc dưáģ›i bÃĒn trÃĄi} bottom_right {GÃŗc dưáģ›i bÃĒn pháēŖi} other {A}} gÃŗc", - "editor_handle_edge": "{edge, select, top {TrÃĒn} bottom {Dưáģ›i} left {TrÃĄi} right {PháēŖi} other {An}} cáēĄnh", - "editor_orientation": "Chiáģu", - "editor_reset_all_changes": "Háģ§y báģ máģi thay đáģ•i", + "editor_orientation": "Đáģ‹nh hưáģ›ng", + "editor_reset_all_changes": "Hoàn tÃĄc táēĨt cáēŖ thay đáģ•i", "editor_rotate_left": "Xoay 90° ngưáģŖc chiáģu kim đáģ“ng háģ“", "editor_rotate_right": "Xoay 90° theo chiáģu kim đáģ“ng háģ“", "email": "Email", "email_notifications": "Thông bÃĄo qua email", "empty_folder": "Thư máģĨc tráģ‘ng", "empty_trash": "Dáģn sáēĄch thÚng rÃĄc", - "empty_trash_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n dáģn sáēĄch thÚng rÃĄc? Điáģu này sáēŊ xÃŗa vÄŠnh viáģ…n táēĨt cáēŖ cÃĄc tài nguyÃĒn trong thÚng rÃĄc kháģi Immich.\nBáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này!", + "empty_trash_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n dáģn sáēĄch thÚng rÃĄc? Điáģu này sáēŊ xÃŗa vÄŠnh viáģ…n táēĨt cáēŖ cÃĄc táģ‡p trong thÚng rÃĄc kháģi Immich.\nBáēĄn không tháģƒ hoàn tÃĄc hành đáģ™ng này!", "enable": "Báē­t", "enable_backup": "Báē­t sao lưu", - "enable_biometric_auth_description": "Nháē­p mÃŖ PIN cáģ§a báēĄn đáģƒ báē­t xÃĄc tháģąc vÃĸn tay", + "enable_biometric_auth_description": "Nháē­p mÃŖ PIN cáģ§a báēĄn đáģƒ báē­t xÃĄc tháģąc sinh tráē¯c háģc", "enabled": "ÄÃŖ báē­t", "end_date": "Ngày káēŋt thÃēc", "enqueued": "ÄÃŖ xáēŋp hàng", "enter_wifi_name": "Nháē­p tÃĒn Wi-Fi", "enter_your_pin_code": "Nháē­p mÃŖ PIN cáģ§a báēĄn", - "enter_your_pin_code_subtitle": "Nháē­p mÃŖ PIN cáģ§a báēĄn đáģƒ truy cáē­p thư máģĨc Bí máē­t", + "enter_your_pin_code_subtitle": "Nháē­p mÃŖ PIN cáģ§a báēĄn đáģƒ truy cáē­p thư máģĨc KhÃŗa", "error": "Láģ—i", - "error_delete_face": "Láģ—i khi xÃŗa khuôn máēˇt kháģi tài nguyÃĒn", - "error_loading_albums": "XáēŖy ra láģ—i khi táēŖi cÃĄc album", + "error_change_sort_album": "Thay đáģ•i tháģŠ táģą sáē¯p xáēŋp album tháēĨt báēĄi", + "error_delete_face": "Láģ—i khi xÃŗa khuôn máēˇt kháģi táģ‡p", + "error_getting_places": "Láģ—i khi láēĨy đáģ‹a điáģƒm", + "error_loading_albums": "Láģ—i khi táēŖi cÃĄc táē­p áēŖnh (album)", "error_loading_image": "Láģ—i táēŖi áēŖnh", - "error_loading_partners": "Láģ—i khi táēŖi ngưáģi thÃĸn: {error}", - "error_retrieving_asset_information": "Không tháģƒ truy xuáēĨt thông tin tài nguyÃĒn", + "error_loading_partners": "Láģ—i khi láēĨy ngưáģi thÃĸn: {error}", + "error_retrieving_asset_information": "Không tháģƒ truy xuáēĨt thông tin táģ‡p", "error_saving_image": "Láģ—i: {error}", "error_tag_face_bounding_box": "Láģ—i gáē¯n tháēģ khuôn máēˇt: - không tháģƒ láēĨy đưáģŖc táģa đáģ™ khung bao", "error_title": "Láģ—i - CÃŗ điáģu gÃŦ Ä‘Ãŗ không đÃēng", - "error_while_navigating": "Không tháģƒ Ä‘iáģu hưáģ›ng đáēŋn tài nguyÃĒn", + "error_while_navigating": "Không tháģƒ Ä‘iáģu hưáģ›ng đáēŋn táģ‡p", "errors": { - "cannot_navigate_next_asset": "Không tháģƒ chuyáģƒn đáēŋn tài nguyÃĒn tiáēŋp theo", - "cannot_navigate_previous_asset": "Không tháģƒ chuyáģƒn đáēŋn tài nguyÃĒn trưáģ›c Ä‘Ãŗ", + "cannot_navigate_next_asset": "Không tháģƒ chuyáģƒn đáēŋn táģ‡p tiáēŋp theo", + "cannot_navigate_previous_asset": "Không tháģƒ chuyáģƒn đáēŋn táģ‡p trưáģ›c Ä‘Ãŗ", "cant_apply_changes": "Không tháģƒ ÃĄp dáģĨng thay đáģ•i", "cant_change_activity": "Không tháģƒ {enabled, select, true {disable} other {enable}} hoáēĄt đáģ™ng", - "cant_change_asset_favorite": "Không tháģƒ thay đáģ•i viáģ‡c thích tài nguyÃĒn", - "cant_change_metadata_assets_count": "Không tháģƒ thay đáģ•i siÃĒu dáģ¯ liáģ‡u cáģ§a {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "cant_change_asset_favorite": "Không tháģƒ thay đáģ•i viáģ‡c thích táģ‡p", + "cant_change_metadata_assets_count": "Không tháģƒ thay đáģ•i siÃĒu dáģ¯ liáģ‡u cáģ§a {count, plural, one {# táģ‡p} other {# táģ‡p}}", "cant_get_faces": "Không tháģƒ táēŖi khuôn máēˇt", "cant_get_number_of_comments": "Không tháģƒ táēŖi sáģ‘ lưáģŖng bÃŦnh luáē­n", "cant_search_people": "Không tháģƒ tÃŦm ngưáģi", "cant_search_places": "Không tháģƒ tÃŦm kiáēŋm đáģ‹a điáģƒm", - "error_adding_assets_to_album": "Láģ—i khi thÃĒm tài nguyÃĒn vào album", + "error_adding_assets_to_album": "Láģ—i khi thÃĒm táģ‡p vào album", "error_adding_users_to_album": "Láģ—i khi thÃĒm ngưáģi dÚng vào album", "error_deleting_shared_user": "Láģ—i khi xÃŗa ngưáģi dÚng chia sáēģ", "error_downloading": "Láģ—i khi táēŖi xuáģ‘ng {filename}", "error_hiding_buy_button": "Láģ—i khi áēŠn nÃēt mua", - "error_removing_assets_from_album": "Láģ—i khi xÃŗa tài nguyÃĒn kháģi album, kiáģƒm tra báēŖng điáģu khiáģƒn đáģƒ biáēŋt thÃĒm chi tiáēŋt", - "error_selecting_all_assets": "Láģ—i khi cháģn táēĨt cáēŖ tài nguyÃĒn", + "error_removing_assets_from_album": "Láģ—i khi xÃŗa táģ‡p kháģi album, kiáģƒm tra báēŖng điáģu khiáģƒn đáģƒ biáēŋt thÃĒm chi tiáēŋt", + "error_selecting_all_assets": "Láģ—i khi cháģn táēĨt cáēŖ táģ‡p", "exclusion_pattern_already_exists": "Quy táē¯c loáēĄi tráģĢ này Ä‘ÃŖ táģ“n táēĄi.", "failed_to_create_album": "TáēĄo album tháēĨt báēĄi", - "failed_to_create_shared_link": "Không tháģƒ táēĄo link chia sáēģ", - "failed_to_edit_shared_link": "Không tháģƒ cháģ‰nh sáģ­a link chia sáēģ", + "failed_to_create_shared_link": "Không tháģƒ táēĄo liÃĒn káēŋt chia sáēģ", + "failed_to_edit_shared_link": "Không tháģƒ cháģ‰nh sáģ­a liÃĒn káēŋt chia sáēģ", "failed_to_get_people": "Không tháģƒ táēŖi ngưáģi", - "failed_to_keep_this_delete_others": "XáēŖy ra láģ—i trong quÃĄ trÃŦnh xÃŗa tài nguyÃĒn", - "failed_to_load_asset": "Không tháģƒ táēŖi tài nguyÃĒn", - "failed_to_load_assets": "Không tháģƒ táēŖi cÃĄc tài nguyÃĒn", + "failed_to_keep_this_delete_others": "XáēŖy ra láģ—i trong quÃĄ trÃŦnh xÃŗa táģ‡p", + "failed_to_load_asset": "Không tháģƒ táēŖi táģ‡p", + "failed_to_load_assets": "Không tháģƒ táēŖi cÃĄc táģ‡p", "failed_to_load_notifications": "Không tháģƒ táēŖi cÃĄc thông bÃĄo", "failed_to_load_people": "Không tháģƒ táēŖi ngưáģi", "failed_to_remove_product_key": "Không tháģƒ xÃŗa khÃŗa sáēŖn pháēŠm", "failed_to_reset_pin_code": "Đáēˇt láēĄi mÃŖ PIN không thành công", - "failed_to_stack_assets": "Không tháģƒ xáēŋp nhÃŗm tài nguyÃĒn", - "failed_to_tag_assets": "Không tháēģ gáē¯n tháēģ tài nguyÃĒn", - "failed_to_unstack_assets": "Không tháģƒ háģ§y xáēŋp nhÃŗm tài nguyÃĒn", + "failed_to_stack_assets": "Không tháģƒ xáēŋp nhÃŗm táģ‡p", + "failed_to_unstack_assets": "Không tháģƒ háģ§y xáēŋp nhÃŗm táģ‡p", "failed_to_update_notification_status": "Cáē­p nháē­t tráēĄng thÃĄi thông bÃĄo tháēĨt báēĄi", "incorrect_email_or_password": "Email hoáēˇc máē­t kháēŠu không chính xÃĄc", "library_folder_already_exists": "Đưáģng dáēĢn nháē­p này Ä‘ÃŖ táģ“n táēĄi.", @@ -1002,33 +1074,33 @@ "quota_higher_than_disk_size": "BáēĄn Ä‘ÃŖ đáēˇt háēĄn máģŠc cao hÆĄn dung lưáģŖng áģ• Ä‘ÄŠa", "something_went_wrong": "CÃŗ gÃŦ Ä‘Ãŗ không đÃēng", "unable_to_add_album_users": "Không tháģƒ thÃĒm ngưáģi dÚng vào album", - "unable_to_add_assets_to_shared_link": "Không tháģƒ thÃĒm tài nguyÃĒn vào liÃĒn káēŋt chia sáēģ", + "unable_to_add_assets_to_shared_link": "Không tháģƒ thÃĒm táģ‡p vào liÃĒn káēŋt chia sáēģ", "unable_to_add_comment": "Không tháģƒ thÃĒm bÃŦnh luáē­n", "unable_to_add_exclusion_pattern": "Không tháģƒ thÃĒm quy táē¯c loáēĄi tráģĢ", "unable_to_add_partners": "Không tháģƒ thÃĒm ngưáģi thÃĸn", - "unable_to_add_remove_archive": "Không tháģƒ {archived, select, true {xÃŗa tài nguyÃĒn kháģi} other {thÃĒm tài nguyÃĒn vào}} Kho lưu tráģ¯", - "unable_to_add_remove_favorites": "Không tháģƒ {favorite, select, true {thÃĒm tài nguyÃĒn vào} other {xÃŗa tài nguyÃĒn kháģi}} máģĨc YÃĒu thích", + "unable_to_add_remove_archive": "Không tháģƒ {archived, select, true {xÃŗa táģ‡p kháģi} other {thÃĒm táģ‡p vào}} Kho lưu tráģ¯", + "unable_to_add_remove_favorites": "Không tháģƒ {favorite, select, true {thÃĒm táģ‡p vào} other {xÃŗa táģ‡p kháģi}} MáģĨc yÃĒu thích", "unable_to_archive_unarchive": "Không tháģƒ {archived, select, true {lưu tráģ¯} other {báģ lưu tráģ¯}}", "unable_to_change_album_user_role": "Không tháģƒ thay đáģ•i vai trÃ˛ cáģ§a ngưáģi dÚng album", "unable_to_change_date": "Không tháģƒ thay đáģ•i ngày", "unable_to_change_description": "Không tháģƒ thay đáģ•i mô táēŖ", - "unable_to_change_favorite": "Không tháģƒ thay đáģ•i viáģ‡c thích tài nguyÃĒn", + "unable_to_change_favorite": "Không tháģƒ thay đáģ•i viáģ‡c thích táģ‡p", "unable_to_change_location": "Không tháģƒ thay đáģ•i váģ‹ trí", "unable_to_change_password": "Không tháģƒ thay đáģ•i máē­t kháēŠu", "unable_to_change_visibility": "Không tháģƒ thay đáģ•i tráēĄng thÃĄi hiáģƒn tháģ‹ cho {count, plural, one {# ngưáģi} other {# ngưáģi}}", "unable_to_complete_oauth_login": "Không tháģƒ hoàn táēĨt đăng nháē­p OAuth", "unable_to_connect": "Không tháģƒ káēŋt náģ‘i", - "unable_to_copy_to_clipboard": "Không tháģƒ sao chÊp vào clipboard, hÃŖy đáēŖm báēŖo báēĄn đang truy cáē­p trang qua https", + "unable_to_copy_to_clipboard": "Không tháģƒ sao chÊp vào báģ™ nháģ› táēĄm, hÃŖy đáēŖm báēŖo báēĄn đang truy cáē­p trang qua https", "unable_to_create": "Không tháģƒ táēĄo workflow", "unable_to_create_admin_account": "Không tháģƒ táēĄo tài khoáēŖn quáēŖn tráģ‹ viÃĒn", "unable_to_create_api_key": "Không tháģƒ táēĄo khÃŗa API máģ›i", "unable_to_create_library": "Không tháģƒ táēĄo thư viáģ‡n", "unable_to_create_user": "Không tháģƒ táēĄo ngưáģi dÚng", "unable_to_delete_album": "Không tháģƒ xÃŗa album", - "unable_to_delete_asset": "Không tháģƒ xÃŗa tài nguyÃĒn", - "unable_to_delete_assets": "Láģ—i khi xÃŗa cÃĄc tài nguyÃĒn", + "unable_to_delete_asset": "Không tháģƒ xÃŗa táģ‡p", + "unable_to_delete_assets": "Láģ—i khi xÃŗa cÃĄc táģ‡p", "unable_to_delete_exclusion_pattern": "Không tháģƒ xÃŗa quy táē¯c loáēĄi tráģĢ", - "unable_to_delete_shared_link": "Không tháģƒ xÃŗa link chia sáēģ", + "unable_to_delete_shared_link": "Không tháģƒ xÃŗa liÃĒn káēŋt chia sáēģ", "unable_to_delete_user": "Không tháģƒ xÃŗa ngưáģi dÚng", "unable_to_delete_workflow": "Không tháģƒ xÃŗa workflow", "unable_to_download_files": "Không tháģƒ táēŖi xuáģ‘ng táģ‡p", @@ -1037,7 +1109,7 @@ "unable_to_enter_fullscreen": "Không tháģƒ vào cháēŋ đáģ™ toàn màn hÃŦnh", "unable_to_exit_fullscreen": "Không tháģƒ thoÃĄt cháēŋ đáģ™ toàn màn hÃŦnh", "unable_to_get_comments_number": "Không tháģƒ láēĨy sáģ‘ lưáģŖng bÃŦnh luáē­n", - "unable_to_get_shared_link": "Không tháģƒ láēĨy link chia sáēģ", + "unable_to_get_shared_link": "Không tháģƒ láēĨy liÃĒn káēŋt chia sáēģ", "unable_to_hide_person": "Không tháģƒ áēŠn ngưáģi", "unable_to_link_motion_video": "Không tháģƒ liÃĒn káēŋt video chuyáģƒn đáģ™ng", "unable_to_link_oauth_account": "Không tháģƒ liÃĒn káēŋt tài khoáēŖn OAuth", @@ -1045,19 +1117,19 @@ "unable_to_log_out_device": "Không tháģƒ Ä‘Äƒng xuáēĨt kháģi thiáēŋt báģ‹", "unable_to_login_with_oauth": "Không tháģƒ Ä‘Äƒng nháē­p váģ›i OAuth", "unable_to_play_video": "Không tháģƒ phÃĄt video", - "unable_to_reassign_assets_existing_person": "Không tháģƒ gÃĄn láēĄi tài nguyÃĒn cho {name, select, null {máģ™t ngưáģi hiáģ‡n cÃŗ} other {{name}}}", - "unable_to_reassign_assets_new_person": "Không tháģƒ gÃĄn láēĄi tài nguyÃĒn cho máģ™t ngưáģi máģ›i", + "unable_to_reassign_assets_existing_person": "Không tháģƒ gÃĄn láēĄi táģ‡p cho {name, select, null {máģ™t ngưáģi hiáģ‡n cÃŗ} other {{name}}}", + "unable_to_reassign_assets_new_person": "Không tháģƒ gÃĄn láēĄi áēŖnh cho máģ™t ngưáģi máģ›i", "unable_to_refresh_user": "Không tháģƒ làm máģ›i ngưáģi dÚng", "unable_to_remove_album_users": "Không tháģƒ xÃŗa ngưáģi dÚng kháģi album", "unable_to_remove_api_key": "Không tháģƒ xÃŗa khÃŗa API", - "unable_to_remove_assets_from_shared_link": "Không tháģƒ xÃŗa cÃĄc tài nguyÃĒn kháģi link Ä‘ÃŖ chia sáēģ", + "unable_to_remove_assets_from_shared_link": "Không tháģƒ xÃŗa cÃĄc máģĨc Ä‘ÃŖ cháģn kháģi liÃĒn káēŋt chia sáēģ", "unable_to_remove_library": "Không tháģƒ xÃŗa thư viáģ‡n", "unable_to_remove_partner": "Không tháģƒ xÃŗa ngưáģi thÃĸn", "unable_to_remove_reaction": "Không tháģƒ xÃŗa pháēŖn áģŠng", "unable_to_reset_password": "Không tháģƒ Ä‘áēˇt láēĄi máē­t kháēŠu", "unable_to_reset_pin_code": "Không tháģƒ Ä‘áēˇt láēĄi mÃŖ PIN", "unable_to_resolve_duplicate": "Không tháģƒ xáģ­ lÃŊ trÚng láēˇp", - "unable_to_restore_assets": "Không tháģƒ khôi pháģĨc tài nguyÃĒn", + "unable_to_restore_assets": "Không tháģƒ khôi pháģĨc táģ‡p", "unable_to_restore_trash": "Không tháģƒ khôi pháģĨc thÚng rÃĄc", "unable_to_restore_user": "Không tháģƒ khôi pháģĨc ngưáģi dÚng", "unable_to_save_album": "Không tháģƒ lưu album", @@ -1070,9 +1142,9 @@ "unable_to_scan_library": "Không tháģƒ quÊt thư viáģ‡n", "unable_to_set_feature_photo": "Không tháģƒ Ä‘áēˇt áēŖnh náģ•i báē­t", "unable_to_set_profile_picture": "Không tháģƒ Ä‘áēˇt áēŖnh đáēĄi diáģ‡n", - "unable_to_set_rating": "Không tháģƒ Ä‘áēˇt xáēŋp háēĄng", + "unable_to_set_rating": "Không tháģƒ Ä‘áēˇt Ä‘ÃĄnh giÃĄ", "unable_to_submit_job": "Không tháģƒ gáģ­i tÃĄc váģĨ", - "unable_to_trash_asset": "Không tháģƒ chuyáģƒn tài nguyÃĒn vào thÚng rÃĄc", + "unable_to_trash_asset": "Không tháģƒ chuyáģƒn áēŖnh vào thÚng rÃĄc", "unable_to_unlink_account": "Không tháģƒ háģ§y liÃĒn káēŋt tài khoáēŖn", "unable_to_unlink_motion_video": "Không tháģƒ háģ§y liÃĒn káēŋt video chuyáģƒn đáģ™ng", "unable_to_update_album_cover": "Không tháģƒ cáē­p nháē­t áēŖnh bÃŦa album", @@ -1090,68 +1162,76 @@ "exif": "Exif", "exif_bottom_sheet_description": "ThÃĒm mô táēŖ...", "exif_bottom_sheet_description_error": "Láģ—i cáē­p nháē­t mô táēŖ", + "exif_bottom_sheet_details": "CHI TIáēžT", + "exif_bottom_sheet_location": "ĐáģŠA ĐIáģ‚M", "exif_bottom_sheet_no_description": "Không cÃŗ mô táēŖ", + "exif_bottom_sheet_people": "MáģŒI NGƯáģœI", + "exif_bottom_sheet_person_add_person": "ThÃĒm tÃĒn", "exit_slideshow": "ThoÃĄt trÃŦnh chiáēŋu", "expand": "Hiáģƒn tháģ‹ Ä‘áē§y đáģ§", "expand_all": "MáģŸ ráģ™ng táēĨt cáēŖ", + "experimental_settings_new_asset_list_subtitle": "Đang phÃĄt triáģƒn", + "experimental_settings_new_asset_list_title": "Báē­t lưáģ›i áēŖnh tháģ­ nghiáģ‡m", + "experimental_settings_subtitle": "Sáģ­ dáģĨng cÃŗ tháģƒ ráģ§i ro!", + "experimental_settings_title": "Tháģ­ nghiáģ‡m", "expire_after": "Háēŋt háēĄn sau", "expired": "Háēŋt háēĄn", - "expires_date": "Háēŋt háēĄn {date}", + "expires_date": "Háēŋt háēĄn vào {date}", "explore": "KhÃĄm phÃĄ", "explorer": "KhÃĄm phÃĄ", "export": "XuáēĨt", "export_as_json": "XuáēĨt dưáģ›i dáēĄng JSON", "export_database": "XuáēĨt cÆĄ sáģŸ dáģ¯ liáģ‡u", "export_database_description": "XuáēĨt cÆĄ sáģŸ dáģ¯ liáģ‡u SQLite", - "exposure_time": "PhÆĄi sÃĄng", "extension": "Pháē§n máģŸ ráģ™ng", "external": "BÃĒn ngoài", - "external_libraries": "Thư viáģ‡n ngoài", + "external_libraries": "Thư viáģ‡n bÃĒn ngoài", "external_network": "MáēĄng bÃĒn ngoài", "external_network_sheet_info": "Khi không áģŸ trÃĒn máēĄng Wi-Fi ưu tiÃĒn, app sáēŊ káēŋt náģ‘i váģ›i mÃĄy cháģ§ thông qua URL đáē§u tiÃĒn bÃĒn dưáģ›i mà nÃŗ cÃŗ tháģƒ truy cáē­p, báē¯t đáē§u táģĢ trÃĒn xuáģ‘ng dưáģ›i", - "f_number": "KháēŠu đáģ™", "face_unassigned": "Chưa đưáģŖc gÃĄn", "failed": "TháēĨt báēĄi", "failed_count": "TháēĨt báēĄi: {count}", "failed_to_authenticate": "XÃĄc tháģąc tháēĨt báēĄi", - "failed_to_delete_file": "Không tháģƒ xÃŗa táģ‡p", - "failed_to_load_assets": "Không táēŖi đưáģŖc tài nguyÃĒn", + "failed_to_load_assets": "Không táēŖi đưáģŖc táģ‡p", "failed_to_load_folder": "Không táēŖi đưáģŖc thư máģĨc", "favorite": "Thích", - "favorite_action_prompt": "ÄÃŖ thÃĒm ({count}) vào YÃĒu thích", + "favorite_action_prompt": "{count} Ä‘ÃŖ thÃĒm vào ÄÃŖ thích", "favorite_or_unfavorite_photo": "Thích hoáēˇc báģ thích áēŖnh", - "favorites": "YÃĒu thích", + "favorites": "ÄÃŖ thích", + "favorites_page_no_favorites": "Không tÃŦm tháēĨy táģ‡p yÃĒu thích", "feature_photo_updated": "ÄÃŖ cáē­p nháē­t áēŖnh náģ•i báē­t", "features": "Tính năng", - "features_setting_description": "QuáēŖn lÃŊ cÃĄc tính năng áģŠng dáģĨng", - "file_name_or_extension": "TÃĒn hoáēˇc pháē§n máģŸ ráģ™ng táģ‡p", + "features_in_development": "Tính năng đang đưáģŖc phÃĄt triáģƒn", + "features_setting_description": "QuáēŖn lÃŊ cÃĄc tính năng app", + "file_name_or_extension": "TÃĒn hoáēˇc pháē§n máģŸ ráģ™ng táē­p tin", "file_name_text": "TÃĒn táģ‡p", + "file_name_with_value": "TÃĒn táģ‡p: {file_name}", "file_size": "Kích cáģĄ táģ‡p tin", "filename": "TÃĒn táģ‡p", "filetype": "LoáēĄi táģ‡p", "filter": "Báģ™ láģc", + "filter_description": "Điáģu kiáģ‡n đáģƒ láģc táģ‡p máģĨc tiÃĒu", "filter_people": "Láģc ngưáģi", "filter_places": "Láģc đáģ‹a điáģƒm", "filter_tags": "Báģ™ láģc theo tháēģ", "filters": "Báģ™ láģc", + "find_them_fast": "TÃŦm nhanh báēąng tÃĒn váģ›i tÃŦm kiáēŋm", "first": "Đáē§u tiÃĒn", "fix_incorrect_match": "Sáģ­a láģ—i trÚng kháģ›p không chính xÃĄc", - "focal_length": "TiÃĒu cáģą", "folder": "Thư máģĨc", "folder_not_found": "Không tÃŦm tháēĨy thư máģĨc", "folders": "Thư máģĨc", "folders_feature_description": "Duyáģ‡t áēŖnh và video theo thư máģĨc trÃĒn háģ‡ tháģ‘ng táģ‡p", "forgot_pin_code_question": "QuÃĒn mÃŖ PIN?", - "forward": "Tiáēŋn", - "free_up_space": "GiáēŖi phÃŗng dung lưáģŖng", - "free_up_space_description": "Chuyáģƒn cÃĄc hÃŦnh áēŖnh và video Ä‘ÃŖ đưáģŖc sao lưu vào thÚng rÃĄc thiáēŋt báģ‹. BáēŖn sao lưu trÃĒn mÃĄy cháģ§ không báģ‹ áēŖnh hưáģŸng.", - "free_up_space_settings_subtitle": "Tiáēŋt kiáģ‡m báģ™ nháģ› thiáēŋt báģ‹", + "forward": "Tiáēŋn táģ›i", + "free_up_space": "GiáēŖi phÃŗng Báģ™ nháģ›", + "free_up_space_description": "Chuyáģƒn hÃŦnh áēŖnh và video Ä‘ÃŖ đưáģŖc sao lưu vào thÚng rÃĄc cáģ§a thiáēŋt báģ‹ Ä‘áģƒ giáēŖi phÃŗng dung lưáģŖng. BáēŖn sao lưu trÃĒn mÃĄy cháģ§ không báģ‹ áēŖnh hưáģŸng.", + "free_up_space_settings_subtitle": "GiáēŖi phÃŗng báģ™ nháģ› cáģ§a thiáēŋt báģ‹", "full_path": "Đưáģng dáēĢn đáē§y đáģ§: {path}", - "full_path_or_folder": "Đưáģng dáēĢn đáē§y đáģ§ hoáēˇc thư máģĨc", "gcast_enabled": "Google Cast", - "gcast_enabled_description": "Tính năng này táēŖi cÃĄc tài nguyÃĒn táģĢ Google đáģƒ hoáēĄt đáģ™ng", + "gcast_enabled_description": "Tính năng này táēŖi cÃĄc tài nguyÃĒn bÃĒn ngoài táģĢ Google đáģƒ hoáēĄt đáģ™ng.", "general": "Chung", - "geolocation_instruction_location": "NháēĨn vào máģ™t tài nguyÃĒn cÃŗ táģa đáģ™ GPS đáģƒ sáģ­ dáģĨng váģ‹ trí cáģ§a nÃŗ hoáēˇc cháģn váģ‹ trí tráģąc tiáēŋp táģĢ báēŖn đáģ“", + "geolocation_instruction_location": "NháēĨn vào máģ™t táģ‡p cÃŗ táģa đáģ™ GPS đáģƒ sáģ­ dáģĨng váģ‹ trí cáģ§a nÃŗ hoáēˇc cháģn váģ‹ trí tráģąc tiáēŋp táģĢ báēŖn đáģ“", "get_help": "Nháē­n tráģŖ giÃēp", "get_people_error": "Láģ—i khi láēĨy thông tin ngưáģi", "get_wifiname_error": "Không tháģƒ láēĨy tÃĒn Wi-Fi. HÃŖy đáēŖm báēŖo báēĄn Ä‘ÃŖ cáēĨp cÃĄc quyáģn cáē§n thiáēŋt và đưáģŖc káēŋt náģ‘i váģ›i máēĄng Wi-Fi", @@ -1168,16 +1248,17 @@ "group_owner": "Xáēŋp nhÃŗm theo cháģ§ sáģŸ háģ¯u", "group_places_by": "NhÃŗm đáģ‹a điáģƒm theoâ€Ļ", "group_year": "Xáēŋp nhÃŗm theo năm", - "haptic_feedback_title": "PháēŖn háģ“i Haptic", + "haptic_feedback_switch": "Báē­t pháēŖn háģ“i haptic", + "haptic_feedback_title": "PháēŖn háģ“i Hapic", "has_quota": "HáēĄn máģŠc", - "hash_asset": "MÃŖ hÃŗa tài nguyÃĒn", - "hashed_assets": "Tài nguyÃĒn Ä‘ÃŖ mÃŖ hÃŗa", + "hash_asset": "MÃŖ hÃŗa táģ‡p", + "hashed_assets": "Táģ‡p mÃŖ hÃŗa", "hashing": "Đang băm", "header_settings_add_header_tip": "ThÃĒm header", "header_settings_field_validator_msg": "Không đưáģŖc đáģƒ tráģ‘ng", "header_settings_header_name_input": "TÃĒn Header", "header_settings_header_value_input": "GiÃĄ tráģ‹ Header", - "headers_settings_tile_title": "TÚy cháģ‰nh proxy header", + "headers_settings_tile_title": "TiÃĒu đáģ proxy tÚy cháģ‰nh", "height": "Chiáģu cao", "hi_user": "Chào {name} ({email})", "hide_all_people": "áē¨n táēĨt cáēŖ máģi ngưáģi", @@ -1188,12 +1269,29 @@ "hide_schema": "áē¨n lưáģŖc đáģ“", "hide_text_recognition": "áē¨n nháē­n dáēĄng văn báēŖn", "hide_unnamed_people": "áē¨n nháģ¯ng ngưáģi không tÃĒn", + "home_page_add_to_album_conflicts": "ÄÃŖ thÃĒm {added} táģ‡p vào album {album}. {failed} táģ‡p Ä‘ÃŖ cÃŗ sáēĩn trong album.", + "home_page_add_to_album_err_local": "Không tháģƒ thÃĒm táģ‡p trÃĒn thiáēŋt báģ‹ vào album, báģ qua", + "home_page_add_to_album_success": "ÄÃŖ thÃĒm {added} táģ‡p vào album {album}.", + "home_page_album_err_partner": "Không tháģƒ thÃĒm táģ‡p cáģ§a nguáģi thÃĸn vào album, báģ qua", + "home_page_archive_err_local": "Không tháģƒ lưu tráģ¯ táģ‡p trÃĒn thiáēŋt báģ‹, báģ qua", + "home_page_archive_err_partner": "Không tháģƒ lưu tráģ¯ táģ‡p cáģ§a ngưáģi thÃĸn, báģ qua", "home_page_building_timeline": "Đang táēĄo dÃ˛ng tháģi gian áēŖnh", + "home_page_delete_err_partner": "Không tháģƒ xÃŗa táģ‡p cáģ§a ngưáģi thÃĸn, báģ qua", + "home_page_delete_remote_err_local": "Táģ‡p trÃĒn thiáēŋt báģ‹ trong láģąa cháģn xÃŗa táģĢ xa, báģ qua", + "home_page_favorite_err_local": "Không tháģƒ thích táģ‡p trÃĒn thiáēŋt báģ‹, báģ qua", + "home_page_favorite_err_partner": "Không tháģƒ thích táģ‡p cáģ§a ngưáģi thÃĸn, báģ qua", + "home_page_first_time_notice": "Náēŋu đÃĸy là láē§n đáē§u báēĄn dÚng app, hÃŖy cháģn máģ™t album sao lưu đáģƒ dÃ˛ng tháģi gian cÃŗ tháģƒ hiáģƒn tháģ‹ áēŖnh và video cáģ§a báēĄn", + "home_page_locked_error_local": "Không tháģƒ di chuyáģƒn táģ‡p trÃĒn thiáēŋt báģ‹ Ä‘áēŋn thư máģĨc KhÃŗa, báģ qua", + "home_page_locked_error_partner": "Không tháģƒ di chuyáģƒn táģ‡p cáģ§a ngưáģi thÃĸn đáēŋn thư máģĨc KhÃŗa, báģ qua", + "home_page_share_err_local": "Không tháģƒ chia sáēģ táģ‡p trÃĒn thiáēŋt báģ‹ qua liÃĒn káēŋt, báģ qua", + "home_page_upload_err_limit": "Cháģ‰ cÃŗ tháģƒ táēŖi lÃĒn táģ‘i đa 30 táģ‡p cÚng lÃēc, báģ qua", "host": "Host", "hour": "Giáģ", "hours": "Giáģ", "id": "ID", "idle": "Nhàn ráģ—i", + "ignore_icloud_photos": "Báģ qua áēŖnh iCloud", + "ignore_icloud_photos_description": "áēĸnh đưáģŖc lưu tráģ¯ trÃĒn iCloud sáēŊ không đưáģŖc táēŖi lÃĒn mÃĄy cháģ§ Immich", "image": "áēĸnh", "image_alt_text_date": "{isVideo, select, true {Video} other {áēĸnh}} đưáģŖc cháģĨp vào {date}", "image_alt_text_date_1_person": "{isVideo, select, true {Video} other {áēĸnh}} đưáģŖc cháģĨp váģ›i {person1} vào {date}", @@ -1205,6 +1303,10 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {Video} other {áēĸnh}} đưáģŖc cháģĨp táēĄi {city}, {country} váģ›i {person1} và {person2} vào {date}", "image_alt_text_date_place_3_people": "{isVideo, select, true {Video} other {áēĸnh}} đưáģŖc cháģĨp táēĄi {city}, {country} váģ›i {person1}, {person2}, và {person3} vào {date}", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {Video} other {áēĸnh}} đưáģŖc cháģĨp táēĄi {city}, {country} váģ›i {person1}, {person2}, và {additionalCount, number} ngưáģi khÃĄc vào {date}", + "image_saved_successfully": "ÄÃŖ lưu áēŖnh", + "image_viewer_page_state_provider_download_started": "ÄÃŖ báē¯t đáē§u táēŖi xuáģ‘ng", + "image_viewer_page_state_provider_download_success": "TáēŖi xuáģ‘ng thành công", + "image_viewer_page_state_provider_share_error": "Chia sáēģ không thành công", "immich_logo": "Logo Immich", "immich_web_interface": "Giao diáģ‡n web Immich", "import_from_json": "Nháē­p táģĢ JSON", @@ -1215,11 +1317,10 @@ "in_year_selector": "Năm", "include_archived": "Bao gáģ“m áēŖnh lưu tráģ¯", "include_shared_albums": "Bao gáģ“m album chia sáēģ", - "include_shared_partner_assets": "Bao gáģ“m tài nguyÃĒn ngưáģi thÃĸn chia sáēģ", + "include_shared_partner_assets": "Bao gáģ“m áēŖnh ngưáģi thÃĸn chia sáēģ", "individual_share": "Chia sáēģ riÃĒng láēģ", "individual_shares": "Chia sáēģ cÃĄ nhÃĸn", "info": "Thông tin", - "integrity_checks": "Kiáģƒm tra tính toàn váēšn", "interval": { "day_at_onepm": "Máģ—i ngày vào lÃēc 1 giáģ chiáģu", "hours": "Máģ—i {hours, plural, one {giáģ} other {{hours, number} giáģ}}", @@ -1230,20 +1331,27 @@ "invalid_date_format": "Đáģ‹nh dáēĄng ngày không háģŖp láģ‡", "invite_people": "Máģi máģi ngưáģi", "invite_to_album": "Máģi vào album", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "LáēĨy dáģ¯ liáģ‡u Ä‘ÃŖ cháēĄy vào {dateTime}", + "ios_debug_info_last_sync_at": "Đáģ“ng báģ™ láē§n cuáģ‘i vào {dateTime}", + "ios_debug_info_no_processes_queued": "Không cÃŗ tiáēŋn trÃŦnh náģn trong hàng đáģŖi", + "ios_debug_info_no_sync_yet": "Chưa cÃŗ tÃĄc váģĨ đáģ“ng báģ™ náģn cháēĄy", + "ios_debug_info_processes_queued": "{count, plural, one {{count} tiáēŋn trÃŦnh náģn Ä‘ÃŖ đưáģŖc đưa vào hàng đáģŖi} other {{count} tiáēŋn trÃŦnh náģn Ä‘ÃŖ đưáģŖc đưa vào hàng đáģŖi}}", + "ios_debug_info_processing_ran_at": "QuÃĄ trÃŦnh xáģ­ lÃŊ Ä‘ÃŖ cháēĄy vào {dateTime}", "items_count": "{count, plural, one {# máģĨc} other {# máģĨc}}", "jobs": "TÃĄc váģĨ", + "json_editor": "BiÃĒn táē­p JSON", + "json_error": "Láģ—i JSON", "keep": "Giáģ¯", - "keep_albums": "Giáģ¯ láēĄi cÃĄc album", - "keep_albums_count": "Giáģ¯ láēĄi {count} {count, plural, one {album} other {album}}", + "keep_albums": "Giáģ¯ láēĄi cÃĄc táē­p áēŖnh", + "keep_albums_count": "Giáģ¯ láēĄi {count} {count, plural, one {táē­p áēŖnh} other {táē­p áēŖnh}}", "keep_all": "Giáģ¯ táēĨt cáēŖ", - "keep_description": "Cháģn giáģ¯ láēĄi nháģ¯ng gÃŦ trÃĒn thiáēŋt báģ‹ khi giáēŖi phÃŗng dung lưáģŖng.", + "keep_description": "Cháģn nháģ¯ng gÃŦ sáēŊ đưáģŖc giáģ¯ láēĄi trÃĒn thiáēŋt báģ‹ khi giáēŖi phÃŗng báģ™ nháģ›.", "keep_favorites": "Giáģ¯ láēĄi cÃĄc máģĨc yÃĒu thích", "keep_on_device": "Giáģ¯ láēĄi trÃĒn thiáēŋt báģ‹", - "keep_on_device_hint": "Cháģn cÃĄc máģĨc sáēŊ không xÃŗa", + "keep_on_device_hint": "Cháģn cÃĄc táģ‡p sáēŊ đưáģŖc giáģ¯ láēĄi trÃĒn thiáēŋt báģ‹", "keep_this_delete_others": "Giáģ¯ táģ‡p này, xÃŗa cÃĄc táģ‡p khÃĄc", "keeping": "Giáģ¯ láēĄi: {items}", - "kept_this_deleted_others": "ÄÃŖ giáģ¯ láēĄi tài nguyÃĒn này và xÃŗa {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "kept_this_deleted_others": "ÄÃŖ giáģ¯ láēĄi táģ‡p này và xÃŗa {count, plural, one {# táģ‡p} other {# táģ‡p}}", "keyboard_shortcuts": "Phím táē¯t", "language": "Ngôn ngáģ¯", "language_no_results_subtitle": "Tháģ­ Ä‘iáģu cháģ‰nh quy táē¯c tÃŦm kiáēŋm", @@ -1259,38 +1367,35 @@ "leave": "Ráģi kháģi", "leave_album": "Ráģi kháģi album", "lens_model": "Lens", - "less": "Ít", "let_others_respond": "Cho phÊp bÃŦnh luáē­n", "level": "MáģŠc đáģ™", "library": "Thư viáģ‡n", "library_add_folder": "ThÃĒm thư máģĨc", "library_edit_folder": "Sáģ­a thư máģĨc", - "library_page_sort_asset_count": "Sáģ‘ lưáģŖng tài nguyÃĒn", + "library_options": "TÚy cháģn thư viáģ‡n", + "library_page_device_albums": "Album trÃĒn thiáēŋt báģ‹", + "library_page_new_album": "Album máģ›i", + "library_page_sort_asset_count": "Sáģ‘ lưáģŖng táģ‡p", "library_page_sort_created": "Máģ›i táēĄo gáē§n đÃĸy", "library_page_sort_last_modified": "Sáģ­a đáģ•i láē§n cuáģ‘i", "library_page_sort_title": "TÃĒn album", "licenses": "GiáēĨy phÊp", "light": "SÃĄng", - "light_theme": "Chuyáģƒn sang cháēŋ đáģ™ sÃĄng", "like": "Thích", "like_deleted": "ÄÃŖ báģ thích", - "link": "Link", "link_motion_video": "LiÃĒn káēŋt video chuyáģƒn đáģ™ng", - "link_to_docs": "Đáģƒ biáēŋt thÃĒm thông tin, tham kháēŖo tài liáģ‡u.", "link_to_oauth": "LiÃĒn káēŋt đáēŋn OAuth", "linked_oauth_account": "Tài khoáēŖn OAuth Ä‘ÃŖ liÃĒn káēŋt", "list": "Danh sÃĄch", - "live": "Đáģ™ng", - "load_more": "TáēŖi ThÃĒm", "loading": "Đang táēŖi", "loading_search_results_failed": "TáēŖi káēŋt quáēŖ tÃŦm kiáēŋm không thành công", "local": "TrÃĒn thiáēŋt báģ‹", - "local_asset_cast_failed": "Không tháģƒ chiáēŋu máģ™t tài nguyÃĒn chưa đưáģŖc táēŖi lÃĒn mÃĄy cháģ§", - "local_assets": "Tài nguyÃĒn trÃĒn thiáēŋt báģ‹", + "local_asset_cast_failed": "Không tháģƒ chiáēŋu náģ™i dung chưa đưáģŖc táēŖi lÃĒn mÃĄy cháģ§", + "local_assets": "Táģ‡p trÃĒn thiáēŋt báģ‹", "local_id": "ID cáģĨc báģ™", "local_media_summary": "Mô táēŖ phÆ°ÆĄng tiáģ‡n trÃĒn thiáēŋt báģ‹", "local_network": "MáēĄng náģ™i báģ™", - "local_network_sheet_info": "áģ¨ng dáģĨng sáēŊ káēŋt náģ‘i váģ›i mÃĄy cháģ§ qua URL này khi sáģ­ dáģĨng máēĄng Wi-Fi đưáģŖc cháģ‰ Ä‘áģ‹nh", + "local_network_sheet_info": "App sáēŊ káēŋt náģ‘i váģ›i mÃĄy cháģ§ qua URL này khi sáģ­ dáģĨng máēĄng Wi-Fi đưáģŖc cháģ‰ Ä‘áģ‹nh", "location": "Đáģ‹a điáģƒm", "location_permission": "Quyáģn truy cáē­p váģ‹ trí", "location_permission_content": "Đáģƒ sáģ­ dáģĨng tính năng táģą Ä‘áģ™ng chuyáģƒn đáģ•i, Immich cáē§n cÃŗ quyáģn váģ‹ trí chính xÃĄc đáģƒ cÃŗ tháģƒ Ä‘áģc tÃĒn cáģ§a máēĄng Wi-Fi hiáģ‡n táēĄi", @@ -1300,7 +1405,7 @@ "location_picker_longitude_error": "Nháē­p kinh đáģ™ háģŖp láģ‡", "location_picker_longitude_hint": "Nháē­p kinh đáģ™ cáģ§a báēĄn", "lock": "KhÃŗa", - "locked_folder": "Bí máē­t", + "locked_folder": "KhÃŗa", "log_detail_title": "Chi tiáēŋt log", "log_out": "Đăng xuáēĨt", "log_out_all_devices": "Đăng xuáēĨt táēĨt cáēŖ thiáēŋt báģ‹", @@ -1310,9 +1415,11 @@ "login": "Đăng nháē­p", "login_disabled": "Đăng nháē­p báģ‹ vô hiáģ‡u hoÃĄ", "login_form_api_exception": "Láģ—i API. Vui lÃ˛ng kiáģƒm tra đáģ‹a cháģ‰ mÃĄy cháģ§ và tháģ­ láēĄi.", + "login_form_back_button_text": "Quay láēĄi", "login_form_email_hint": "emailcuaban@email.com", "login_form_endpoint_hint": "http://đáģ‹a-cháģ‰-ip-mÃĄy-cháģ§-báēĄn:cáģ•ng", "login_form_endpoint_url": "Đáģ‹a cháģ‰ mÃĄy cháģ§", + "login_form_err_http": "Vui lÃ˛ng xÃĄc đáģ‹nh http:// hoáēˇc https://", "login_form_err_invalid_email": "Email không háģŖp láģ‡", "login_form_err_invalid_url": "Đáģ‹a cháģ‰ không háģŖp láģ‡", "login_form_err_leading_whitespace": "KhoáēŖng tráē¯ng trưáģ›c", @@ -1322,16 +1429,17 @@ "login_form_failed_login": "Đăng nháē­p láģ—i, vui lÃ˛ng kiáģƒm tra đáģ‹a cháģ‰ mÃĄy cháģ§, email và máē­t kháēŠu", "login_form_handshake_exception": "ÄÃŖ xáēŖy ra láģ—i Handshake Exception váģ›i mÃĄy cháģ§. Náēŋu báēĄn đang sáģ­ dáģĨng cháģŠng cháģ‰ táģą kÃŊ, hÃŖy báē­t háģ— tráģŖ cháģŠng cháģ‰ táģą kÃŊ trong cài đáēˇt.", "login_form_password_hint": "máē­t kháēŠu", - "login_form_server_empty": "Nháē­p đáģ‹a cháģ‰ mÃĄy cháģ§", - "login_form_server_error": "Không tháģƒ káēŋt náģ‘i táģ›i mÃĄy cháģ§", - "login_has_been_disabled": "Đăng nháē­p Ä‘ÃŖ báģ‹ vô hiáģ‡u hÃŗa", + "login_form_save_login": "Lưu tráēĄng thÃĄi đăng nháē­p", + "login_form_server_empty": "Nháē­p đáģ‹a cháģ‰ mÃĄy cháģ§.", + "login_form_server_error": "Không tháģƒ káēŋt náģ‘i táģ›i mÃĄy cháģ§.", + "login_has_been_disabled": "Đăng nháē­p Ä‘ÃŖ báģ‹ vô hiáģ‡u hÃŗa.", "login_password_changed_error": "Thay đáģ•i máē­t kháēŠu không thành công", "login_password_changed_success": "Cáē­p nháē­t máē­t kháēŠu thành công", "logout_all_device_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n đăng xuáēĨt táēĨt cáēŖ thiáēŋt báģ‹?", "logout_this_device_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n đăng xuáēĨt thiáēŋt báģ‹ này?", "logs": "Log", "longitude": "Kinh đáģ™", - "look": "Hiáģƒn tháģ‹", + "look": "Xem", "loop_videos": "Láēˇp video", "loop_videos_description": "Báē­t đáģƒ video táģą Ä‘áģ™ng láēˇp láēĄi trong trÃŦnh xem chi tiáēŋt.", "main_branch_warning": "BáēĄn đang dÚng phiÃĒn báēŖn đang phÃĄt triáģƒn; chÃēng tôi khuyÃĒn báēĄn nÃĒn dÚng phiÃĒn báēŖn phÃĄt hành!", @@ -1339,6 +1447,7 @@ "maintenance_action_restore": "Khôi pháģĨc CÆĄ sáģŸ dáģ¯ liáģ‡u", "maintenance_description": "Immich Ä‘ÃŖ đưáģŖc đáēˇt vào cháēŋ đáģ™ báēŖo trÃŦ.", "maintenance_end": "Káēŋt thÃēc cháēŋ đáģ™ báēŖo trÃŦ", + "maintenance_end_error": "Không tháģƒ káēŋt thÃēc cháēŋ đáģ™ báēŖo trÃŦ.", "maintenance_logged_in_as": "Hiáģ‡n táēĄi Ä‘ÃŖ đăng nháē­p {user}", "maintenance_restore_from_backup": "Khôi pháģĨc táģĢ báēŖn sao lưu", "maintenance_restore_library": "Khôi pháģĨc Thư viáģ‡n cáģ§a báēĄn", @@ -1360,71 +1469,45 @@ "maintenance_title": "TáēĄm tháģi không kháēŖ dáģĨng", "make": "ThÆ°ÆĄng hiáģ‡u", "manage_geolocation": "QuáēŖn lÃŊ đáģ‹a điáģƒm", - "manage_media_access_rationale": "Đáģƒ cÃŗ tháģƒ di chuyáģƒn tài nguyÃĒn vào thÚng rÃĄc và khôi pháģĨc chÃēng táģĢ Ä‘Ãŗ.", + "manage_media_access_rationale": "Đáģƒ cÃŗ tháģƒ di chuyáģƒn táģ‡p vào thÚng rÃĄc và khôi pháģĨc chÃēng táģĢ Ä‘Ãŗ.", "manage_media_access_settings": "MáģŸ cài đáēˇt", - "manage_media_access_subtitle": "Cho phÊp áģŠng dáģĨng [Immich] quáēŖn lÃŊ và di chuyáģƒn táģ‡p.", - "manage_media_access_title": "QuáēŖn lÃŊ media", + "manage_media_access_subtitle": "Cho phÊp app [Immich] quáēŖn lÃŊ và di chuyáģƒn táģ‡p.", + "manage_media_access_title": "QuáēŖn lÃŊ phÆ°ÆĄng tiáģ‡n", + "manage_shared_links": "QuáēŖn lÃŊ liÃĒn káēŋt chia sáēģ", "manage_sharing_with_partners": "QuáēŖn lÃŊ chia sáēģ váģ›i ngưáģi thÃĸn", - "manage_the_app_settings": "QuáēŖn lÃŊ cài đáēˇt áģŠng dáģĨng", + "manage_the_app_settings": "QuáēŖn lÃŊ cài đáēˇt app", "manage_your_account": "QuáēŖn lÃŊ tài khoáēŖn cáģ§a báēĄn", "manage_your_api_keys": "QuáēŖn lÃŊ cÃĄc khÃŗa API cáģ§a báēĄn", "manage_your_devices": "QuáēŖn lÃŊ cÃĄc thiáēŋt báģ‹ Ä‘ÃŖ đăng nháē­p cáģ§a báēĄn", "manage_your_oauth_connection": "QuáēŖn lÃŊ káēŋt náģ‘i OAuth cáģ§a báēĄn", "map": "BáēŖn đáģ“", + "map_assets_in_bounds": "{count, plural, =0 {Không cÃŗ áēŖnh áģŸ khu váģąc này} one {# áēŖnh} other {# áēŖnh}}", "map_cannot_get_user_location": "Không tháģƒ xÃĄc đáģ‹nh váģ‹ trí cáģ§a báēĄn", + "map_location_dialog_yes": "CÃŗ", "map_location_picker_page_use_location": "DÚng váģ‹ trí này", - "map_location_service_disabled_content": "Cáē§n báē­t dáģ‹ch váģĨ đáģ‹nh váģ‹ Ä‘áģƒ hiáģƒn tháģ‹ tài nguyÃĒn táģĢ váģ‹ trí hiáģ‡n táēĄi cáģ§a báēĄn. BáēĄn cÃŗ muáģ‘n báē­t nÃŗ ngay bÃĸy giáģ không?", + "map_location_service_disabled_content": "Cáē§n báē­t dáģ‹ch váģĨ đáģ‹nh váģ‹ Ä‘áģƒ hiáģƒn tháģ‹ áēŖnh hoáēˇc video táģĢ váģ‹ trí hiáģ‡n táēĄi cáģ§a báēĄn. BáēĄn cÃŗ muáģ‘n báē­t nÃŗ ngay bÃĸy giáģ không?", "map_location_service_disabled_title": "Dáģ‹ch váģĨ váģ‹ trí báģ‹ vô hiáģ‡u hoÃĄ", - "map_marker_for_image": "ÄÃĄnh dáēĨu điáģƒm trÃĒn báēŖn đáģ“ cho hÃŦnh áēŖnh đưáģŖc cháģĨp táēĄi {city}, {country}", + "map_marker_for_images": "ÄÃĄnh dáēĨu báēŖn đáģ“ cho áēŖnh cháģĨp táēĄi {city}, {country}", "map_marker_with_image": "ÄÃĄnh dáēĨu báēŖn đáģ“ váģ›i áēŖnh", - "map_no_location_permission_content": "Cáē§n quyáģn truy cáē­p váģ‹ trí đáģƒ hiáģƒn tháģ‹ tài nguyÃĒn táģĢ váģ‹ trí hiáģ‡n táēĄi cáģ§a báēĄn. BáēĄn cÃŗ muáģ‘n cho phÊp ngay bÃĸy giáģ không?", - "map_no_location_permission_title": "áģ¨ng dáģĨng không đưáģŖc phÊp truy cáē­p váģ‹ trí", + "map_no_location_permission_content": "Cáē§n quyáģn truy cáē­p váģ‹ trí đáģƒ hiáģƒn tháģ‹ táģ‡p táģĢ váģ‹ trí hiáģ‡n táēĄi cáģ§a báēĄn. BáēĄn cÃŗ muáģ‘n cho phÊp ngay bÃĸy giáģ không?", + "map_no_location_permission_title": "App không đưáģŖc phÊp truy cáē­p váģ‹ trí", "map_settings": "Cài đáēˇt báēŖn đáģ“", "map_settings_dark_mode": "Cháēŋ đáģ™ táģ‘i", "map_settings_date_range_option_day": "Trong vÃ˛ng 24 giáģ qua", "map_settings_date_range_option_days": "Trong {days} ngày qua", "map_settings_date_range_option_year": "Năm ngoÃĄi", "map_settings_date_range_option_years": "Trong {years} năm qua", + "map_settings_dialog_title": "Cài đáēˇt báēŖn đáģ“", "map_settings_include_show_archived": "Bao gáģ“m áēŖnh Ä‘ÃŖ lưu tráģ¯", "map_settings_include_show_partners": "Bao gáģ“m ngưáģi thÃĸn", - "map_settings_only_show_favorites": "Cháģ‰ hiáģƒn tháģ‹ máģĨc YÃĒu thích", + "map_settings_only_show_favorites": "Cháģ‰ hiáģƒn tháģ‹ máģĨc yÃĒu thích", "map_settings_theme_settings": "Giao diáģ‡n báēŖn đáģ“", + "map_zoom_to_see_photos": "Thu nháģ Ä‘áģƒ xem áēŖnh", "mark_all_as_read": "ÄÃĄnh dáēĨu Ä‘ÃŖ đáģc táēĨt cáēŖ", + "mark_as_read": "ÄÃĄnh dáēĨu Ä‘ÃŖ đáģc", "marked_all_as_read": "ÄÃŖ Ä‘ÃĄnh dáēĨu táēĨt cáēŖ Ä‘ÃŖ đáģc", "matches": "Kháģ›p", - "matching_assets": "Tài nguyÃĒn trÚng kháģ›p", - "media_chrome": { - "auto": "Táģą Ä‘áģ™ng", - "captions": "ChÃē thích", - "captions_off": "Táē¯t", - "closed_captions": "Ä‘ÃŖ Ä‘Ãŗng chÃē thích", - "decode_error": "Láģ—i giáēŖi mÃŖ", - "disable_captions": "Táē¯t chÃē thích", - "enable_captions": "Báē­t chÃē thích", - "enter_fullscreen_mode": "Cháēŋ đáģ™ toàn màn hÃŦnh", - "exit_fullscreen_mode": "ThoÃĄt cháēŋ đáģ™ toàn màn hÃŦnh", - "loop": "Láēˇp láēĄi", - "media_error_description": "Láģ—i phÆ°ÆĄng tiáģ‡n Ä‘ÃŖ khiáēŋn quÃĄ trÃŦnh phÃĄt láēĄi báģ‹ giÃĄn đoáēĄn. PhÆ°ÆĄng tiáģ‡n cÃŗ tháģƒ báģ‹ háģng hoáēˇc trÃŦnh duyáģ‡t cáģ§a báēĄn không háģ— tráģŖ Ä‘áģ‹nh dáēĄng này.", - "media_loading": "đang táēŖi phÆ°ÆĄng tiáģ‡n", - "mute": "Táē¯t tiáēŋng", - "network_error": "Láģ—i káēŋt náģ‘i máēĄng", - "network_error_description": "Láģ—i káēŋt náģ‘i máēĄng Ä‘ÃŖ khiáēŋn quÃĄ trÃŦnh táēŖi xuáģ‘ng phÆ°ÆĄng tiáģ‡n không thành công.", - "not_supported_error": "Nguáģ“n không đưáģŖc háģ— tráģŖ", - "playback_rate": "Táģ‘c đáģ™ phÃĄt láēĄi", - "playback_rate_current": "táģ‘c đáģ™ phÃĄt láēĄi hiáģ‡n táēĄi", - "playback_rate_value": "Táģ‘c đáģ™ phÃĄt láēĄi {playbackRate}", - "playback_time": "tháģi gian phÃĄt láēĄi", - "quality": "CháēĨt lưáģŖng", - "second": "giÃĸy", - "seconds": "giÃĸy", - "time_value_of_total_time": "{currentTime} trong {totalTime}", - "time_value_remaining": "{time} cÃ˛n láēĄi", - "unmute": "Báē­t tiáēŋng", - "unsupported_error_description": "ÄÃŖ xáēŖy ra láģ—i không đưáģŖc háģ— tráģŖ. MÃĄy cháģ§ hoáēˇc máēĄng gáēˇp sáģą cáģ‘, hoáēˇc trÃŦnh duyáģ‡t cáģ§a báēĄn không háģ— tráģŖ Ä‘áģ‹nh dáēĄng này.", - "video_not_loaded_unknown_time": "video không táēŖi đưáģŖc, tháģi gian không xÃĄc đáģ‹nh.", - "video_player": "trÃŦnh phÃĄt video", - "volume": "Ãĸm lưáģŖng" - }, + "matching_assets": "Táģ‡p trÚng kháģ›p", "media_type": "LoáēĄi phÆ°ÆĄng tiáģ‡n", "memories": "Káģˇ niáģ‡m", "memories_all_caught_up": "BáēĄn Ä‘ÃŖ xem háēŋt ráģ“i", @@ -1441,31 +1524,34 @@ "merge_people_prompt": "BáēĄn cÃŗ muáģ‘n háģŖp nháēĨt nháģ¯ng ngưáģi này? Hành đáģ™ng này không tháģƒ hoàn tÃĄc.", "merge_people_successfully": "HáģŖp nháēĨt ngưáģi thành công", "merged_people_count": "ÄÃŖ háģŖp nháēĨt {count, plural, one {# ngưáģi} other {# ngưáģi}}", - "minFaces": "Sáģ‘ khuôn máēˇt táģ‘i thiáģƒu", - "minFaces_description": "Sáģ‘ lưáģŖng khuôn máēˇt đưáģŖc nháē­n diáģ‡n táģ‘i thiáģƒu đáģƒ máģ™t ngưáģi đưáģŖc hiáģƒn tháģ‹", "minimize": "Thu nháģ", "minute": "PhÃēt", "minutes": "PhÃēt", + "mirror_horizontal": "Theo chiáģu ngang", + "mirror_vertical": "Dáģc", "missing": "Thiáēŋu", "mobile_app": "áģ¨ng dáģĨng di đáģ™ng", "mobile_app_download_onboarding_note": "TáēŖi xuáģ‘ng app đáģ“ng hành báēąng cÃĄc tÚy cháģn sau", "model": "DÃ˛ng", - "modify_date": "Ngày sáģ­a đáģ•i", "month": "ThÃĄng", + "monthly_title_text_date_format": "MMMM y", "more": "ThÃĒm", - "motion": "HoáēĄt áēŖnh", "move": "Di chuyáģƒn", - "move_off_locked_folder": "Chuyáģƒn ra kháģi thư máģĨc Bí máē­t", - "move_to": "Di chuyáģƒn đáēŋn", - "move_to_device_trash": "Chuyáģƒn vào thÚng rÃĄc thiáēŋt báģ‹", - "move_to_lock_folder_action_prompt": "ÄÃŖ thÃĒm {count} vào thư máģĨc Bí máē­t", - "move_to_locked_folder": "Chuyáģƒn đáēŋn Bí máē­t", - "move_to_locked_folder_confirmation": "áēĸnh và video này sáēŊ báģ‹ xÃŗa kháģi máģi album và cháģ‰ cÃŗ tháģƒ xem đưáģŖc trong thư máģĨc Bí máē­t", + "move_down": "Di chuyáģƒn xuáģ‘ng", + "move_off_locked_folder": "Di chuyáģƒn ra kháģi thư máģĨc KhÃŗa", + "move_to": "Chuyáģƒn đáēŋn", + "move_to_device_trash": "Chuyáģƒn đáēŋn thÚng rÃĄc cáģ§a thiáēŋt báģ‹", + "move_to_lock_folder_action_prompt": "{count} Ä‘ÃŖ đưáģŖc thÃĒm vào thư máģĨc KhÃŗa", + "move_to_locked_folder": "Di chuyáģƒn đáēŋn thư máģĨc KhÃŗa", + "move_to_locked_folder_confirmation": "áēĸnh và video này sáēŊ báģ‹ xÃŗa kháģi cÃĄc album, cháģ‰ cÃŗ tháģƒ xem đưáģŖc trong thư máģĨc KhÃŗa", + "move_up": "Di chuyáģƒn lÃĒn", + "moved_to_archive": "ÄÃŖ di chuyáģƒn {count, plural, one {# táģ‡p} other {# táģ‡p}} đáēŋn lưu tráģ¯", + "moved_to_library": "ÄÃŖ di chuyáģƒn {count, plural, one {# táģ‡p} other {# táģ‡p}} đáēŋn thư viáģ‡n", "moved_to_trash": "ÄÃŖ chuyáģƒn vào thÚng rÃĄc", + "multiselect_grid_edit_date_time_err_read_only": "Không tháģƒ cháģ‰nh sáģ­a ngày cáģ§a táģ‡p cháģ‰ cÃŗ quyáģn đáģc, báģ qua", + "multiselect_grid_edit_gps_err_read_only": "Không tháģƒ cháģ‰nh sáģ­a váģ‹ trí cáģ§a táģ‡p cháģ‰ cÃŗ quyáģn đáģc, báģ qua", "mute_memories": "Táē¯t tiáēŋng Káģˇ niáģ‡m", "my_albums": "Album cáģ§a tôi", - "my_immich_description": "Sao chÊp trang hiáģ‡n táēĄi dưáģ›i dáēĄng link My Immich", - "my_immich_title": "LiÃĒn káēŋt My Immich", "name": "TÃĒn", "name_or_nickname": "TÃĒn hoáēˇc biáģ‡t danh", "name_required": "Báē¯t buáģ™c nháē­p tÃĒn", @@ -1475,16 +1561,17 @@ "network_requirement_videos_upload": "DÚng dáģ¯ liáģ‡u di đáģ™ng sao lưu video", "network_requirements": "Káēŋt náģ‘i máēĄng", "network_requirements_updated": "Káēŋt náģ‘i máēĄng Ä‘ÃŖ thay đáģ•i, đang đáēˇt láēĄi hàng đáģŖi sao lưu", - "networking_settings": "Káēŋt náģ‘i máēĄng", + "networking_settings": "MáēĄng", "networking_subtitle": "CÃĄc đáģ‹a cháģ‰ mÃĄy cháģ§", "never": "Không bao giáģ", "new_album": "Album máģ›i", "new_api_key": "KhÃŗa API máģ›i", - "new_feature": "Tính năng máģ›i", + "new_date_range": "KhoáēŖng ngày máģ›i", "new_password": "Máē­t kháēŠu máģ›i", "new_person": "Ngưáģi máģ›i", "new_pin_code": "MÃŖ PIN máģ›i", - "new_pin_code_subtitle": "ĐÃĸy là láē§n đáē§u báēĄn vào thư máģĨc Bí máē­t. HÃŖy táēĄo mÃŖ PIN đáģƒ truy cáē­p an toàn", + "new_pin_code_subtitle": "ĐÃĸy là láē§n đáē§u báēĄn vào thư máģĨc KhÃŗa. HÃŖy táēĄo mÃŖ PIN đáģƒ truy cáē­p an toàn", + "new_timeline": "DÃ˛ng tháģi gian kiáģƒu máģ›i", "new_update": "Cáē­p nháē­t máģ›i", "new_user_created": "Ngưáģi dÚng máģ›i Ä‘ÃŖ đưáģŖc táēĄo", "new_version_available": "CÓ PHIÊN BáēĸN MáģšI", @@ -1492,45 +1579,44 @@ "next": "Tiáēŋp theo", "next_memory": "Káģˇ niáģ‡m tiáēŋp theo", "no": "Không", - "no_albums_found": "Không tÃŦm tháēĨy album nào", + "no_actions_added": "Chưa cÃŗ hành đáģ™ng nào đưáģŖc thÃĒm vào", + "no_albums_found": "Không phÃĄt hiáģ‡n táē­p áēŖnh nào", "no_albums_message": "TáēĄo album đáģƒ sáē¯p xáēŋp áēŖnh và video cáģ§a báēĄn", "no_albums_with_name_yet": "CÃŗ váēģ như báēĄn chưa cÃŗ báēĨt káģŗ album nào váģ›i tÃĒn này.", "no_albums_yet": "CÃŗ váēģ như báēĄn chưa cÃŗ báēĨt káģŗ album nào.", "no_archived_assets_message": "Lưu tráģ¯ áēŖnh và video đáģƒ áēŠn chÃēng kháģi thư viáģ‡n áēĸnh cáģ§a báēĄn", - "no_assets_message": "NháēĨn vào đÃĸy đáģƒ táēŖi áēŖnh đáē§u tiÃĒn cáģ§a báēĄn lÃĒn", - "no_assets_to_show": "Không cÃŗ tài nguyÃĒn đáģƒ hiáģƒn tháģ‹", + "no_assets_message": "NháēĨn vào đáģƒ táēŖi lÃĒn áēŖnh cáģ§a báēĄn láē§n đáē§u tiÃĒn", + "no_assets_to_show": "Không cÃŗ gÃŦ đáģƒ hiáģƒn tháģ‹", "no_cast_devices_found": "Không tÃŦm tháēĨy thiáēŋt báģ‹ chiáēŋu", - "no_checksum_local": "Không cÃŗ checksum kháēŖ dáģĨng - không tháģƒ truy xuáēĨt tài nguyÃĒn trÃĒn thiáēŋt báģ‹", - "no_checksum_remote": "Không cÃŗ checksum kháēŖ dáģĨng - không tháģƒ truy xuáēĨt tài nguyÃĒn trÃĒn mÃĸy", + "no_checksum_local": "Không cÃŗ checksum kháēŖ dáģĨng - không tháģƒ truy xuáēĨt táģ‡p trÃĒn thiáēŋt báģ‹", + "no_checksum_remote": "Không cÃŗ checksum kháēŖ dáģĨng - không tháģƒ truy xuáēĨt táģ‡p trÃĒn mÃĸy", + "no_configuration_needed": "Không cáē§n cáēĨu hÃŦnh", "no_devices": "Không cÃŗ thiáēŋt báģ‹ Ä‘Æ°áģŖc cáēĨp quyáģn", "no_duplicates_found": "Không tÃŦm tháēĨy cÃĄc máģĨc trÚng láēˇp.", "no_exif_info_available": "Không cÃŗ thông tin exif", "no_explore_results_message": "TáēŖi thÃĒm áēŖnh lÃĒn đáģƒ khÃĄm phÃĄ báģ™ sưu táē­p cáģ§a báēĄn.", "no_favorites_message": "ThÃĒm áēŖnh yÃĒu thích đáģƒ nhanh chÃŗng tÃŦm tháēĨy nháģ¯ng báģŠc áēŖnh và video đáēšp nháēĨt cáģ§a báēĄn", - "no_libraries_message": "TáēĄo máģ™t thư viáģ‡n ngoài đáģƒ xem áēŖnh và video cáģ§a báēĄn", - "no_local_assets_found": "Không tÃŦm tháēĨy tài nguyÃĒn trÃĒn thiáēŋt báģ‹ nào váģ›i checksum này", - "no_locked_photos_message": "áēĸnh và video trong thư máģĨc Bí máē­t sáēŊ đưáģŖc áēŠn đi và không hiáģƒn tháģ‹ khi báēĄn duyáģ‡t hay tÃŦm kiáēŋm trong thư viáģ‡n.", + "no_filters_added": "Chưa cÃŗ báģ™ láģc nào đưáģŖc thÃĒm vào", + "no_libraries_message": "TáēĄo máģ™t thư viáģ‡n bÃĒn ngoài đáģƒ xem áēŖnh và video cáģ§a báēĄn", + "no_local_assets_found": "Không tÃŦm tháēĨy táģ‡p trÃĒn thiáēŋt báģ‹ nào váģ›i checksum này", + "no_location_set": "Chưa cÃŗ đáģ‹a điáģƒm đưáģŖc đáēˇt", + "no_locked_photos_message": "áēĸnh và video trong thư máģĨc KhÃŗa sáēŊ đưáģŖc áēŠn đi và không hiáģƒn tháģ‹ khi báēĄn duyáģ‡t hay tÃŦm kiáēŋm trong thư viáģ‡n.", "no_name": "Không cÃŗ tÃĒn", "no_notifications": "Không cÃŗ thông bÃĄo", "no_people_found": "Không cÃŗ ngưáģi nào kháģ›p váģ›i tÃŦm kiáēŋm", "no_places": "Không cÃŗ đáģ‹a điáģƒm", - "no_remote_assets_found": "Không tÃŦm tháēĨy tài nguyÃĒn trÃĒn mÃĸy nào váģ›i checksum này", + "no_remote_assets_found": "Không tÃŦm tháēĨy táģ‡p trÃĒn mÃĸy nào váģ›i checksum này", "no_results": "Không cÃŗ káēŋt quáēŖ", "no_results_description": "Tháģ­ máģ™t táģĢ Ä‘áģ“ng nghÄŠa hoáēˇc táģĢ khÃŗa táģ•ng quÃĄt hÆĄn", "no_shared_albums_message": "TáēĄo máģ™t album đáģƒ chia sáēģ áēŖnh và video váģ›i máģi ngưáģi trong máēĄng cáģ§a báēĄn", - "no_steps": "Chưa cÃŗ bưáģ›c nào đưáģŖc thÃĒm vào", + "no_uploads_in_progress": "Không cÃŗ táēŖi lÃĒn nào đang tiáēŋn hành", "none": "Tráģ‘ng", "not_allowed": "Không cho phÊp", "not_available": "Thiáēŋu", "not_in_any_album": "Không thuáģ™c album nào", "not_selected": "Không đưáģŖc cháģn", - "not_set": "Chưa đáēˇt", "notes": "Lưu ÃŊ", "nothing_here_yet": "Chưa cÃŗ náģ™i dung nào", - "notification_backup_reliability": "Báē­t thông bÃĄo đáģƒ Ä‘áēŖm báēŖo quÃĄ trÃŦnh sao lưu náģn", - "notification_enabled_list_tile_content": "Immich dÚng thông bÃĄo cho sao lưu náģn. QuáēŖn lÃŊ chÃēng trong cài đáēˇt thiáēŋt báģ‹ cáģ§a báēĄn.", - "notification_enabled_list_tile_open_button": "MáģŸ cài đáēˇt", - "notification_enabled_list_tile_title": "ÄÃŖ báē­t thông bÃĄo", "notification_permission_dialog_content": "Đáģƒ báē­t thông bÃĄo, chuyáģƒn táģ›i Cài đáēˇt và cháģn cho phÊp.", "notification_permission_list_tile_content": "CáēĨp quyáģn đáģƒ báē­t thông bÃĄo.", "notification_permission_list_tile_enable_button": "Báē­t thông bÃĄo", @@ -1542,8 +1628,6 @@ "obtainium_configurator": "CáēĨu hÃŦnh Obtainium", "obtainium_configurator_instructions": "Sáģ­ dáģĨng Obtainium đáģƒ cài đáēˇt và cáē­p nháē­t áģŠng dáģĨng Android tráģąc tiáēŋp táģĢ báēŖn phÃĄt hành GitHub cáģ§a Immich. TáēĄo khÃŗa API và cháģn máģ™t biáēŋn tháģƒ Ä‘áģƒ táēĄo liÃĒn káēŋt cáēĨu hÃŦnh Obtainium cáģ§a báēĄn", "ocr": "OCR", - "ocr_body": "Immich hiáģ‡n cÃŗ tháģƒ Ä‘áģc văn báēŖn trong áēŖnh cáģ§a báēĄn, nháģ Ä‘Ãŗ báēĄn cÃŗ tháģƒ tÃŦm kiáēŋm áēŖnh dáģąa trÃĒn náģ™i dung văn báēŖn Ä‘Ãŗ.", - "ocr_title": "TÃŦm kiáēŋm văn báēŖn trong áēŖnh cáģ§a báēĄn", "official_immich_resources": "Tài nguyÃĒn chính tháģŠc cáģ§a Immich", "offline": "NgoáēĄi tuyáēŋn", "offset": "Đáģ™ láģ‡ch", @@ -1560,10 +1644,8 @@ "online": "Tráģąc tuyáēŋn", "only_favorites": "Cháģ‰ lưáģŖt thích", "open": "MáģŸ", - "open_calendar": "MáģŸ láģ‹ch", + "open_calendar": "Hiáģ‡n tháģ‹ láģ‹ch", "open_in_browser": "MáģŸ trong trÃŦnh duyáģ‡t", - "open_in_immich_body": "HÃŖy đáēˇt Immich làm áģŠng dáģĨng thư viáģ‡n áēŖnh trÃĒn Android đáģƒ máģŸ áēŖnh tráģąc tiáēŋp táģĢ cÃĄc áģŠng dáģĨng khÃĄc.", - "open_in_immich_title": "MáģŸ áēŖnh trong Immich", "open_in_map_view": "MáģŸ trong báēŖn đáģ“", "open_in_openstreetmap": "MáģŸ trong OpenStreetMap", "open_the_search_filters": "MáģŸ báģ™ láģc tÃŦm kiáēŋm", @@ -1572,22 +1654,25 @@ "organize_into_albums": "Sáē¯p xáēŋp thành album", "organize_into_albums_description": "Đưa áēŖnh hiáģ‡n cÃŗ vào album báēąng cÃĄch sáģ­ dáģĨng cài đáēˇt đáģ“ng báģ™ hiáģ‡n táēĄi", "organize_your_library": "Sáē¯p xáēŋp thư viáģ‡n cáģ§a báēĄn", - "orientation": "Hưáģ›ng", "original": "gáģ‘c", "other": "KhÃĄc", "other_devices": "Thiáēŋt báģ‹ khÃĄc", + "other_entities": "CÃĄc tháģąc tháģƒ khÃĄc", "other_variables": "CÃĄc tham sáģ‘ khÃĄc", "owned": "SáģŸ háģ¯u", "owner": "Cháģ§ sáģŸ háģ¯u", "page": "Trang", "partner": "Ngưáģi thÃĸn", "partner_can_access": "{partner} cÃŗ tháģƒ truy cáē­p", - "partner_can_access_assets": "TáēĨt cáēŖ áēŖnh và video cáģ§a báēĄn, ngoáēĄi tráģĢ máģĨc ÄÃŖ lưu tráģ¯ và ÄÃŖ xÃŗa", + "partner_can_access_assets": "TáēĨt cáēŖ áēŖnh và video cáģ§a báēĄn ngoáēĄi tráģĢ nháģ¯ng áēŖnh và video trong máģĨc ÄÃŖ lưu tráģ¯ và ÄÃŖ xÃŗa", "partner_can_access_location": "Váģ‹ trí nÆĄi áēŖnh cáģ§a báēĄn đưáģŖc cháģĨp", "partner_list_user_photos": "áēĸnh cáģ§a {user}", - "partner_page_empty_message": "áēĸnh cáģ§a báēĄn chưa đưáģŖc chia sáēģ váģ›i ngưáģi thÃĸn nào.", - "partner_page_no_more_users": "Không cÃ˛n ngưáģi thÃĸn nào đáģƒ thÃĒm", + "partner_list_view_all": "Xem táēĨt cáēŖ", + "partner_page_empty_message": "áēĸnh cáģ§a báēĄn chưa đưáģŖc chia sáēģ váģ›i báēĨt káģŗ ai.", + "partner_page_no_more_users": "Không cÃ˛n ngưáģi dÚng nào đáģƒ thÃĒm", + "partner_page_partner_add_failed": "ThÃĒm ngưáģi thÃĸn tháēĨt báēĄi", "partner_page_select_partner": "Cháģn ngưáģi thÃĸn", + "partner_page_shared_to_title": "Chia sáēģ váģ›i", "partner_page_stop_sharing_content": "{partner} sáēŊ không tháģƒ truy cáē­p áēŖnh cáģ§a báēĄn.", "partner_sharing": "Ngưáģi thÃĸn", "partners": "Ngưáģi thÃĸn", @@ -1608,27 +1693,40 @@ "pending": "Đang cháģ xáģ­ lÃŊ", "people": "Máģi ngưáģi", "people_edits_count": "ÄÃŖ cháģ‰nh sáģ­a {count, plural, one {# ngưáģi} other {# ngưáģi}}", - "people_feature_description": "Duyáģ‡t áēŖnh và video đưáģŖc phÃĸn loáēĄi theo ngưáģi", + "people_feature_description": "Duyáģ‡t áēŖnh và video đưáģŖc xáēŋp nhÃŗm theo ngưáģi", + "people_selected": "{count, plural, one {# ngưáģi Ä‘ÃŖ cháģn} other {# ngưáģi Ä‘ÃŖ cháģn}}", + "people_sidebar_description": "Hiáģƒn tháģ‹ máģĨc Máģi ngưáģi trong thanh bÃĒn", "permanent_deletion_warning": "CáēŖnh bÃĄo xÃŗa vÄŠnh viáģ…n", - "permanent_deletion_warning_setting_description": "Hiáģƒn tháģ‹ cáēŖnh bÃĄo khi xÃŗa vÄŠnh viáģ…n tài nguyÃĒn", + "permanent_deletion_warning_setting_description": "Hiáģƒn tháģ‹ cáēŖnh bÃĄo khi xÃŗa vÄŠnh viáģ…n áēŖnh", "permanently_delete": "XÃŗa vÄŠnh viáģ…n", - "permanently_delete_assets_count": "XÃŗa vÄŠnh viáģ…n {count, plural, one {tài nguyÃĒn} other {tài nguyÃĒn}}", - "permanently_delete_assets_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa vÄŠnh viáģ…n {count, plural, one {tài nguyÃĒn này?} other {#tài nguyÃĒn này?}} Điáģu này cÅŠng sáēŊ xÃŗa {count, plural, one {nÃŗ kháģi} other {chÃēng kháģi}} cÃĄc album.", - "permanently_deleted_asset": "Tài nguyÃĒn Ä‘ÃŖ báģ‹ xÃŗa vÄŠnh viáģ…n", - "permanently_deleted_assets_count": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "permanently_delete_assets_count": "XÃŗa vÄŠnh viáģ…n {count, plural, one {táģ‡p} other {táģ‡p}}", + "permanently_delete_assets_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa vÄŠnh viáģ…n {count, plural, one {táģ‡p này?} other {# táģ‡p này?}} Điáģu này cÅŠng sáēŊ xÃŗa {count, plural, one {nÃŗ kháģi} other {chÃēng kháģi}} cÃĄc album.", + "permanently_deleted_asset": "Táģ‡p Ä‘ÃŖ báģ‹ xÃŗa vÄŠnh viáģ…n", + "permanently_deleted_assets_count": "ÄÃŖ xÃŗa vÄŠnh viáģ…n {count, plural, one {# táģ‡p} other {# táģ‡p}}", "permission": "Quyáģn", "permission_empty": "Quyáģn cáģ§a báēĄn không đưáģŖc đáģƒ tráģ‘ng", + "permission_onboarding_back": "Quay láēĄi", + "permission_onboarding_continue_anyway": "VáēĢn tiáēŋp táģĨc", + "permission_onboarding_get_started": "Báē¯t đáē§u", + "permission_onboarding_go_to_settings": "Đi táģ›i cài đáēˇt", + "permission_onboarding_permission_denied": "Không cÃŗ quyáģn truy cáē­p vào áēŖnh cáģ§a báēĄn. Đáģƒ sáģ­ dáģĨng Immich, hÃŖy cáēĨp quyáģn truy cáē­p vào thư viáģ‡n áēŖnh trong Cài đáēˇt.", + "permission_onboarding_permission_granted": "CáēĨp quyáģn hoàn táēĨt! BáēĄn cÃŗ tháģƒ báē¯t đáē§u.", + "permission_onboarding_permission_limited": "Quyáģn truy cáē­p vào áēŖnh cáģ§a báēĄn báģ‹ háēĄn cháēŋ. Đáģƒ Immich sao lưu và quáēŖn lÃŊ toàn báģ™ thư viáģ‡n áēŖnh cáģ§a báēĄn, hÃŖy cáēĨp quyáģn truy cáē­p toàn báģ™ áēŖnh trong Cài đáēˇt.", + "permission_onboarding_request": "Immich cáē§n quyáģn đáģƒ xem áēŖnh và video cáģ§a báēĄn.", "person": "Máģi ngưáģi", "person_age_months": "{months, plural, one {# thÃĄng} other {# thÃĄng}} tuáģ•i", "person_age_year_months": "1 năm, {months, plural, one {# thÃĄng} other {# thÃĄng}} tuáģ•i", - "person_age_years": "{years, plural, one {# tuáģ•i} other {# tuáģ•i}}", + "person_age_years": "{years, plural, other {# năm}} tuáģ•i", "person_birthdate": "Sinh vào {date}", "person_hidden": "{name}{hidden, select, true { (Ä‘ÃŖ áēŠn)} other {}}", "person_recognized": "Ngưáģi đưáģŖc nháē­n diáģ‡n", + "person_selected": "Ngưáģi Ä‘ÃŖ cháģn", "photo_shared_all_users": "CÃŗ váēģ như báēĄn Ä‘ÃŖ chia sáēģ áēŖnh cáģ§a mÃŦnh váģ›i táēĨt cáēŖ ngưáģi dÚng hoáēˇc báēĄn không cÃŗ ngưáģi dÚng nào đáģƒ chia sáēģ.", "photos": "áēĸnh", "photos_and_videos": "áēĸnh & Video", + "photos_count": "{count, plural, one {{count, number} áēĸnh} other {{count, number} áēĸnh}}", "photos_from_previous_years": "áēĸnh táģĢ cÃĄc năm trưáģ›c", + "photos_only": "Cháģ‰ bao gáģ“m áēŖnh", "pick_a_location": "Cháģn máģ™t váģ‹ trí", "pick_custom_range": "TÚy cháģ‰nh khoáēŖng", "pick_date_range": "Cháģn khoáēŖng ngày", @@ -1644,13 +1742,11 @@ "play_motion_photo": "PhÃĄt áēŖnh chuyáģƒn đáģ™ng", "play_or_pause_video": "PhÃĄt hoáēˇc dáģĢng video", "play_original_video": "PhÃĄt video gáģ‘c", - "play_original_video_setting_description": "NÃĒn phÃĄt video gáģ‘c thay vÃŦ video Ä‘ÃŖ chuyáģƒn mÃŖ. Náēŋu tài nguyÃĒn gáģ‘c không tÆ°ÆĄng thích, video cÃŗ tháģƒ không phÃĄt đưáģŖc.", + "play_original_video_setting_description": "NÃĒn phÃĄt video gáģ‘c thay vÃŦ video Ä‘ÃŖ chuyáģƒn mÃŖ. Náēŋu táģ‡p gáģ‘c không tÆ°ÆĄng thích, video cÃŗ tháģƒ không phÃĄt đưáģŖc.", "play_transcoded_video": "PhÃĄt video Ä‘ÃŖ chuyáģƒn mÃŖ", "please_auth_to_access": "Vui lÃ˛ng xÃĄc tháģąc đáģƒ truy cáē­p", - "plugin_method_filter_type": "Báģ™ láģc", - "plugin_method_filter_type_description": "PhÆ°ÆĄng phÃĄp này cÃŗ tháģƒ láģc cÃĄc sáģą kiáģ‡n và ngăn cháēˇn cÃŗ điáģu kiáģ‡n cÃĄc bưáģ›c tiáēŋp theo không cho cháēĄy", "port": "Cáģ•ng", - "preferences_settings_subtitle": "TÚy cháģ‰nh tráēŖi nghiáģ‡m áģŠng dáģĨng", + "preferences_settings_subtitle": "TÚy cháģ‰nh tráēŖi nghiáģ‡m app", "preferences_settings_title": "CÃĄ nhÃĸn hÃŗa", "preparing": "Đang chuáēŠn báģ‹", "preset": "MáēĢu cÃŗ sáēĩn", @@ -1664,12 +1760,12 @@ "primary": "Chính", "privacy": "BáēŖo máē­t", "profile": "Háģ“ sÆĄ", - "profile_drawer_app_logs": "Nháē­t kÃŊ", + "profile_drawer_app_logs": "Log", + "profile_drawer_client_server_up_to_date": "MÃĄy khÃĄch và mÃĄy cháģ§ Ä‘ÃŖ cáē­p nháē­t", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "ÄÃŖ báē­t cháēŋ đáģ™ cháģ‰-xem. NháēĨn giáģ¯ áēŖnh đáēĄi diáģ‡n ngưáģi dÚng đáģƒ táē¯t.", "profile_image_of_user": "áēĸnh đáēĄi diáģ‡n cáģ§a {user}", "profile_picture_set": "áēĸnh đáēĄi diáģ‡n Ä‘ÃŖ đưáģŖc đáēˇt.", - "projection_type": "LoáēĄi chiáēŋu", "public_album": "Album công khai", "public_share": "Chia sáēģ công khai", "purchase_account_info": "Ngưáģi háģ— tráģŖ", @@ -1688,6 +1784,7 @@ "purchase_individual_description_2": "TráēĄng thÃĄi ngưáģi háģ— tráģŖ", "purchase_individual_title": "CÃĄ nhÃĸn", "purchase_input_suggestion": "CÃŗ khÃŗa sáēŖn pháēŠm? Nháē­p khÃŗa bÃĒn dưáģ›i", + "purchase_license_subtitle": "Mua Immich đáģƒ háģ— tráģŖ dáģ‹ch váģĨ liÃĒn táģĨc phÃĄt triáģƒn", "purchase_lifetime_description": "Mua tráģn đáģi", "purchase_option_title": "TÙY CHáģŒN MUA HÀNG", "purchase_panel_info_1": "Viáģ‡c xÃĸy dáģąng Immich táģ‘n nhiáģu tháģi gian và công sáģŠc, và chÃēng tôi cÃŗ cÃĄc káģš sư toàn tháģi gian làm viáģ‡c đáģƒ làm cho nÃŗ táģ‘t nháēĨt cÃŗ tháģƒ. SáģŠ máģ‡nh cáģ§a chÃēng tôi là pháē§n máģm mÃŖ nguáģ“n máģŸ và cÃĄc hoáēĄt đáģ™ng kinh doanh cÃŗ đáēĄo đáģŠc tráģŸ thành nguáģ“n thu nháē­p báģn váģ¯ng cho cÃĄc nhà phÃĄt triáģƒn, đáģ“ng tháģi táēĄo ra máģ™t háģ‡ sinh thÃĄi báēŖo váģ‡ quyáģn riÃĒng tư váģ›i cÃĄc láģąa cháģn thay tháēŋ tháģąc sáģą cho cÃĄc dáģ‹ch váģĨ Ä‘ÃĄm mÃĸy láģŖi dáģĨng ngưáģi dÚng.", @@ -1696,82 +1793,89 @@ "purchase_per_server": "Máģ—i mÃĄy cháģ§", "purchase_per_user": "Máģ—i ngưáģi dÚng", "purchase_remove_product_key": "XÃŗa khÃŗa sáēŖn pháēŠm", - "purchase_remove_product_key_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa khÃŗa sáēŖn pháēŠm?", + "purchase_remove_product_key_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xoÃĄ khÃŗa sáēŖn pháēŠm?", "purchase_remove_server_product_key": "XÃŗa khÃŗa sáēŖn pháēŠm mÃĄy cháģ§", - "purchase_remove_server_product_key_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa khÃŗa sáēŖn pháēŠm MÃĄy cháģ§?", + "purchase_remove_server_product_key_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xoÃĄ khÃŗa sáēŖn pháēŠm mÃĄy cháģ§?", "purchase_server_description_1": "Dành cho toàn báģ™ mÃĄy cháģ§", "purchase_server_description_2": "TráēĄng thÃĄi ngưáģi háģ— tráģŖ", "purchase_server_title": "MÃĄy cháģ§", "purchase_settings_server_activated": "KhÃŗa sáēŖn pháēŠm mÃĄy cháģ§ Ä‘Æ°áģŖc quáēŖn lÃŊ báģŸi quáēŖn tráģ‹ viÃĒn", - "query_asset_id": "ID tài nguyÃĒn truy váēĨn", + "query_asset_id": "Truy váēĨn ID táģ‡p", "queue_status": "Xáēŋp hàng {count}/{total}", - "rate_asset": "Xáēŋp háēĄng tài nguyÃĒn", + "rate_asset": "Asset ÄÃĄnh giÃĄ", "rating": "Xáēŋp háēĄng sao", "rating_clear": "XÃŗa xáēŋp háēĄng", "rating_count": "{count, plural, =0 {Chưa xáēŋp háēĄng} one {# sao} other {# sao}}", "rating_description": "Hiáģƒn tháģ‹ xáēŋp háēĄng EXIF trong báēŖng thông tin", "reaction_options": "TÚy cháģn pháēŖn áģŠng", - "read_changelog": "Đáģc Ghi chÃē PhÃĄt hành", + "read_changelog": "Đáģc nháē­t kÃŊ thay đáģ•i", "readonly_mode_disabled": "ÄÃŖ táē¯t cháēŋ đáģ™ cháģ‰-xem", "readonly_mode_enabled": "ÄÃŖ báē­t cháēŋ đáģ™ cháģ‰-xem", "ready_for_upload": "Sáēĩn sàng táēŖi lÃĒn", "reassign": "GÃĄn láēĄi", - "reassigned_assets_to_existing_person": "ÄÃŖ gÃĄn láēĄi {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} cho {name, select, null {máģ™t ngưáģi hiáģ‡n cÃŗ} other {{name}}}", - "reassigned_assets_to_new_person": "ÄÃŖ gÃĄn láēĄi {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} cho máģ™t ngưáģi máģ›i", - "reassing_hint": "GÃĄn cÃĄc tài nguyÃĒn Ä‘ÃŖ cháģn cho máģ™t ngưáģi hiáģ‡n cÃŗ", + "reassigned_assets_to_existing_person": "ÄÃŖ gÃĄn láēĄi {count, plural, one {# áēŖnh} other {# áēŖnh}} cho {name, select, null {máģ™t ngưáģi hiáģ‡n cÃŗ} other {{name}}}", + "reassigned_assets_to_new_person": "ÄÃŖ gÃĄn láēĄi {count, plural, one {# áēŖnh} other {# áēŖnh}} cho máģ™t ngưáģi máģ›i", + "reassing_hint": "GÃĄn cÃĄc áēŖnh Ä‘ÃŖ cháģn cho máģ™t ngưáģi hiáģ‡n cÃŗ", "recent": "Gáē§n đÃĸy", + "recent_albums": "Album gáē§n đÃĸy", "recent_searches": "TÃŦm kiáēŋm gáē§n đÃĸy", "recently_added": "ThÃĒm gáē§n đÃĸy", - "recently_added_body": "Truy cáē­p ngay vào táēĨt cáēŖ nháģ¯ng gÃŦ báēĄn Ä‘ÃŖ thÃĒm gáē§n đÃĸy trÃĒn máģ™t trang dành riÃĒng.", - "recently_added_description": "TÃŦm kiáēŋm cÃĄc tài nguyÃĒn cáģ§a báēĄn đưáģŖc sáē¯p xáēŋp theo tháģi gian táēŖi lÃĒn Immich", - "recently_added_page_title": "ThÃĒm gáē§n đÃĸy", - "recently_added_title": "ThÃĒm gáē§n đÃĸy", + "recently_added_page_title": "Máģ›i thÃĒm gáē§n đÃĸy", "recently_taken": "CháģĨp gáē§n đÃĸy", + "recently_taken_page_title": "CháģĨp Gáē§n đÃĸy", "refresh": "Làm máģ›i", "refresh_encoded_videos": "Làm máģ›i video Ä‘ÃŖ mÃŖ hÃŗa", "refresh_faces": "Làm máģ›i khuôn máēˇt", "refresh_metadata": "Làm máģ›i metadata", "refresh_thumbnails": "Làm máģ›i áēŖnh thu nháģ", "refreshed": "ÄÃŖ làm máģ›i", - "refreshing_encoded_video": "ÄÃŖ làm máģ›i video đưáģŖc chuyáģƒn mÃŖ", - "refreshing_faces": "ÄÃŖ làm máģ›i cÃĄc khuôn máēˇt", - "refreshing_metadata": "ÄÃŖ làm máģ›i metadata", + "refreshes_every_file": "Đáģc láēĄi táēĨt cáēŖ táģ‡p máģ›i và hiáģ‡n cÃŗ", + "refreshing_encoded_video": "Đang làm máģ›i video Ä‘ÃŖ mÃŖ hÃŗa", + "refreshing_faces": "Đang làm máģ›i khuôn máēˇt", + "refreshing_metadata": "Đang làm máģ›i metadata", "regenerating_thumbnails": "Đang táēĄo láēĄi áēŖnh thu nháģ", "remote": "TrÃĒn mÃĸy", - "remote_assets": "Tài nguyÃĒn trÃĒn mÃĸy", + "remote_assets": "Táģ‡p trÃĒn mÃĸy", "remote_media_summary": "Mô táēŖ phÆ°ÆĄng tiáģ‡n trÃĒn mÃĄy cháģ§", "remove": "XÃŗa", - "remove_assets_album_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} kháģi album?", - "remove_assets_shared_link_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}} kháģi liÃĒn káēŋt chia sáēģ này?", - "remove_assets_title": "XÃŗa tài nguyÃĒn?", + "remove_assets_album_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa {count, plural, one {# táģ‡p} other {# táģ‡p}} kháģi album?", + "remove_assets_shared_link_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa {count, plural, one {# táģ‡p} other {# táģ‡p}} kháģi liÃĒn káēŋt chia sáēģ này?", + "remove_assets_title": "XÃŗa táģ‡p?", "remove_custom_date_range": "Báģ cháģn khoáēŖng ngày tÚy cháģ‰nh", - "remove_filter": "XÃŗa báģ™ láģc", + "remove_deleted_assets": "LoáēĄi báģ táģ‡p ngoáēĄi tuyáēŋn", "remove_from_album": "XÃŗa kháģi album", - "remove_from_album_action_prompt": "ÄÃŖ gáģĄ ({count}) kháģi album", - "remove_from_favorites": "XÃŗa kháģi máģĨc YÃĒu thích", - "remove_from_lock_folder_action_prompt": "ÄÃŖ xÃŗa {count} kháģi thư máģĨc Bí máē­t", - "remove_from_locked_folder": "XÃŗa kháģi thư máģĨc Bí máē­t", - "remove_from_locked_folder_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n di chuyáģƒn áēŖnh và video này kháģi thư máģĨc Bí máē­t? ChÃēng sáēŊ hiáģ‡n trong thư viáģ‡n cáģ§a báēĄn.", - "remove_from_shared_link": "XÃŗa kháģi link chia sáēģ", + "remove_from_album_action_prompt": "{count} Ä‘ÃŖ gáģĄ kháģi album", + "remove_from_favorites": "XÃŗa kháģi MáģĨc yÃĒu thích", + "remove_from_lock_folder_action_prompt": "{count} Ä‘ÃŖ đưáģŖc xÃŗa kháģi thư máģĨc KhÃŗa", + "remove_from_locked_folder": "XÃŗa kháģi thư máģĨc KhÃŗa", + "remove_from_locked_folder_confirmation": "BáēĄn cÃŗ cháē¯c muáģ‘n di chuyáģƒn áēŖnh và video này kháģi thư máģĨc KhÃŗa? ChÃēng sáēŊ hiáģ‡n trong thư viáģ‡n cáģ§a báēĄn.", + "remove_from_shared_link": "XÃŗa kháģi liÃĒn káēŋt chia sáēģ", "remove_memory": "XÃŗa káģˇ niáģ‡m", "remove_photo_from_memory": "XÃŗa áēŖnh kháģi káģˇ niáģ‡m này", "remove_tag": "GáģĄ tháēģ", + "remove_url": "XÃŗa URL", "remove_user": "XÃŗa ngưáģi dÚng", "removed_api_key": "KhÃŗa API Ä‘ÃŖ xÃŗa: {name}", "removed_from_archive": "ÄÃŖ xÃŗa kháģi Kho lưu tráģ¯", - "removed_from_favorites": "ÄÃŖ xÃŗa kháģi máģĨc YÃĒu thích", - "removed_from_favorites_count": "{count, plural, other {ÄÃŖ xÃŗa #}} kháģi máģĨc YÃĒu thích", + "removed_from_favorites": "ÄÃŖ xÃŗa kháģi MáģĨc yÃĒu thích", + "removed_from_favorites_count": "{count, plural, other {ÄÃŖ xÃŗa #}} kháģi MáģĨc yÃĒu thích", "removed_memory": "ÄÃŖ xÃŗa káģˇ niáģ‡m", - "removed_tagged_assets": "ÄÃŖ xÃŗa tháēģ kháģi {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "removed_photo_from_memory": "ÄÃŖ xÃŗa áēŖnh kháģi káģˇ niáģ‡m", + "removed_tagged_assets": "ÄÃŖ xÃŗa tháēģ kháģi {count, plural, one {# táģ‡p} other {# táģ‡p}}", "rename": "Đáģ•i tÃĒn", - "repository": "Kho mÃŖ nguáģ“n", + "repair": "Sáģ­a cháģ¯a", + "repair_no_results_message": "CÃĄc táģ‡p không đưáģŖc theo dÃĩi và báģ‹ thiáēŋu sáēŊ xuáēĨt hiáģ‡n áģŸ Ä‘Ãĸy", + "replace_with_upload": "Thay tháēŋ táģ‡p khÃĄc", + "repository": "Kho lưu tráģ¯", "require_password": "YÃĒu cáē§u máē­t kháēŠu", + "require_user_to_change_password_on_first_login": "YÃĒu cáē§u ngưáģi dÚng thay đáģ•i máē­t kháēŠu áģŸ láē§n đáē§u đăng nháē­p", "rescan": "QuÊt láēĄi", "reset": "Đáēˇt láēĄi", "reset_password": "Đáēˇt láēĄi máē­t kháēŠu", "reset_people_visibility": "Đáēˇt láēĄi tráēĄng thÃĄi hiáģƒn tháģ‹ cáģ§a máģi ngưáģi", "reset_pin_code": "Đáēˇt láēĄi mÃŖ PIN", "reset_pin_code_description": "Náēŋu báēĄn quÃĒn mÃŖ PIN, báēĄn cÃŗ tháģƒ liÃĒn háģ‡ quáēŖn tráģ‹ viÃĒn đáģƒ Ä‘áēˇt láēĄi", + "reset_pin_code_success": "Đáēˇt láēĄi mÃŖ PIN thành công", "reset_pin_code_with_password": "BáēĄn luôn cÃŗ tháģƒ Ä‘áēˇt láēĄi mÃŖ PIN cáģ§a báēĄn báēąng máē­t kháēŠu cáģ§a báēĄn", "reset_sqlite": "Đáēˇt láēĄi cÆĄ sáģŸ dáģ¯ liáģ‡u SQLite", "reset_sqlite_clear_app_data": "XÃŗa dáģ¯ liáģ‡u", @@ -1785,41 +1889,40 @@ "resolved_all_duplicates": "ÄÃŖ xáģ­ lÃŊ táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp", "restore": "Khôi pháģĨc", "restore_all": "Khôi pháģĨc táēĨt cáēŖ", + "restore_trash_action_prompt": "{count} Ä‘ÃŖ khôi pháģĨc táģĢ thÚng rÃĄc", "restore_user": "Khôi pháģĨc ngưáģi dÚng", - "restored_asset": "Tài nguyÃĒn Ä‘ÃŖ đưáģŖc khôi pháģĨc", + "restored_asset": "Táģ‡p Ä‘ÃŖ đưáģŖc khôi pháģĨc", "resume": "Tiáēŋp táģĨc", "resume_paused_jobs": "Tiáēŋp táģĨc {count, plural, one {# tÃĄc váģĨ Ä‘ÃŖ dáģĢng} other {# tÃĄc váģĨ Ä‘ÃŖ dáģĢng}}", + "retry_upload": "Tháģ­ táēŖi lÃĒn láēĄi", "review_duplicates": "Xem láēĄi cÃĄc máģĨc trÚng láēˇp", - "review_large_files": "Xem láēĄi cÃĄc táģ‡p dung lưáģŖng láģ›n", + "review_large_files": "Xem láēĄi táģ‡p dung lưáģŖng láģ›n", "role": "Vai trÃ˛", "role_editor": "Ngưáģi cháģ‰nh sáģ­a", "role_viewer": "Ngưáģi xem", "running": "Đang cháēĄy", "save": "Lưu", + "save_to_gallery": "Lưu vào thư viáģ‡n", "saved": "ÄÃŖ lưu", - "saved_api_key": "ÄÃŖ lưu KhÃŗa API", - "saved_profile": "ÄÃŖ lưu háģ“ sÆĄ", - "saved_settings": "ÄÃŖ lưu cài đáēˇt", + "saved_api_key": "KhÃŗa API Ä‘ÃŖ lưu", + "saved_profile": "Háģ“ sÆĄ Ä‘ÃŖ lưu", + "saved_settings": "Cài đáēˇt Ä‘ÃŖ lưu", "say_something": "NÃŗi điáģu gÃŦ Ä‘Ãŗ", "scaffold_body_error_occurred": "XáēŖy ra láģ—i", - "scaffold_body_error_unrecoverable": "ÄÃŖ xáēŖy ra láģ—i không tháģƒ kháē¯c pháģĨc. Vui lÃ˛ng chia sáēģ láģ—i và dáēĨu váēŋt ngăn xáēŋp trÃĒn Discord hoáēˇc GitHub đáģƒ chÃēng tôi cÃŗ tháģƒ tráģŖ giÃēp. Náēŋu đưáģŖc yÃĒu cáē§u, báēĄn cÃŗ tháģƒ xÃŗa dáģ¯ liáģ‡u áģŠng dáģĨng bÃĒn dưáģ›i.", "scan": "QuÊt", "scan_all_libraries": "QuÊt táēĨt cáēŖ thư viáģ‡n", "scan_library": "QuÊt", + "scan_settings": "Cài đáēˇt quÊt", "scanning": "Đang quÊt", "scanning_for_album": "Đang quÊt album...", - "screencast_mode_description": "Hiáģƒn tháģ‹ cháģ‰ bÃĄo sáģą kiáģ‡n bàn phím và chuáģ™t trÃĒn màn hÃŦnh", - "screencast_mode_title": "Báē­t/táē¯t cháēŋ đáģ™ quay màn hÃŦnh", "search": "TÃŦm kiáēŋm", "search_albums": "TÃŦm album", "search_by_context": "TÃŦm theo ngáģ¯ cáēŖnh", "search_by_description": "TÃŦm theo mô táēŖ", - "search_by_description_example": "Leo nÃēi áģŸ Sa Pa", + "search_by_description_example": "DáēĄo chÆĄi Sa Pa", "search_by_filename": "TÃŦm theo tÃĒn hoáēˇc đáģ‹nh dáēĄng táģ‡p", "search_by_filename_example": "Ví dáģĨ: IMG_1234.JPG hoáēˇc PNG", - "search_by_full_path": "TÃŦm kiáēŋm theo đưáģng dáēĢn đáē§y đáģ§ hoáēˇc thư máģĨc", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - báēĄn cÃŗ tháģƒ tÃŦm Projects, 3D, Printing, 2026...", - "search_by_ocr": "TÃŦm theo OCR", + "search_by_ocr": "TÃŦm báēąng OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "TÃŦm dÃ˛ng lens...", "search_camera_make": "TÃŦm thÆ°ÆĄng hiáģ‡u mÃĄy áēŖnh...", @@ -1838,23 +1941,36 @@ "search_filter_location_title": "Cháģn váģ‹ trí", "search_filter_media_type": "PhÆ°ÆĄng tiáģ‡n", "search_filter_media_type_title": "Cháģn loáēĄi phÆ°ÆĄng tiáģ‡n", + "search_filter_ocr": "TÃŦm báēąng OCR", "search_filter_people_title": "Cháģn ngưáģi", "search_filter_star_rating": "Xáēŋp háēĄng sao", "search_filter_tags_title": "Cháģn cÃĄc tháēģ", "search_for": "TÃŦm kiáēŋm", "search_for_existing_person": "TÃŦm ngưáģi hiáģ‡n cÃŗ", - "search_no_more_result": "Không cÃ˛n káēŋt quáēŖ nào", + "search_no_more_result": "Không cÃ˛n káēŋt quáēŖ", "search_no_people": "Không cÃŗ ngưáģi", "search_no_people_named": "Không cÃŗ ngưáģi tÃĒn \"{name}\"", "search_no_result": "Không tÃŦm tháēĨy káēŋt quáēŖ, hÃŖy tháģ­ máģ™t cáģĨm táģĢ hoáēˇc káēŋt háģŖp váģ›i tÃŦm kiáēŋm khÃĄc", "search_options": "TÚy cháģn tÃŦm kiáēŋm", + "search_page_categories": "Danh máģĨc", + "search_page_motion_photos": "áēĸnh đáģ™ng", + "search_page_no_objects": "Không cÃŗ thông tin đáģ‘i tưáģŖng", + "search_page_no_places": "Không cÃŗ thông tin đáģ‹a điáģƒm nào", + "search_page_screenshots": "áēĸnh cháģĨp màn hÃŦnh", "search_page_search_photos_videos": "TÃŦm áēŖnh và video cáģ§a báēĄn", + "search_page_selfies": "áēĸnh selfie", + "search_page_things": "Đáģ‘i tưáģŖng", "search_page_view_all_button": "Xem táēĨt cáēŖ", + "search_page_your_activity": "HoáēĄt đáģ™ng cáģ§a báēĄn", + "search_page_your_map": "BáēŖn đáģ“ cáģ§a báēĄn", "search_people": "TÃŦm ngưáģi", "search_places": "TÃŦm kiáēŋm đáģ‹a điáģƒm", "search_rating": "TÃŦm kiáēŋm theo xáēŋp háēĄngâ€Ļ", + "search_result_page_new_search_hint": "TÃŦm kiáēŋm máģ›i", "search_settings": "TÃŦm kiáēŋm cài đáēˇt", - "search_state": "TÃŦm tiáģƒu bang...", + "search_state": "TÃŦm táģ‰nh...", + "search_suggestion_list_smart_search_hint_1": "TÃŦm kiáēŋm thông minh đưáģŖc báē­t máēˇc đáģ‹nh, đáģƒ tÃŦm kiáēŋm metadata hÃŖy sáģ­ dáģĨng cÃē phÃĄp ", + "search_suggestion_list_smart_search_hint_2": "m:cáģĨm-táģĢ-tÃŦm-kiáēŋm-cáģ§a-báēĄn", "search_tags": "TÃŦm tháēģ...", "search_timezone": "TÃŦm kiáēŋm mÃēi giáģ...", "search_type": "Kiáģƒu tÃŦm kiáēŋm", @@ -1868,27 +1984,29 @@ "select_albums": "Cháģn cÃĄc album", "select_all": "Cháģn táēĨt cáēŖ", "select_all_duplicates": "Cháģn táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp", + "select_all_in": "Cháģn táēĨt cáēŖ trong {group}", "select_avatar_color": "Cháģn màu áēŖnh đáēĄi diáģ‡n", "select_count": "{count, plural, one {Cháģn #} other {Cháģn #}}", - "select_cutoff_date": "Cháģn máģ‘c tháģi gian", + "select_cutoff_date": "Cháģn giáģ›i háēĄn tháģi gian", "select_face": "Cháģn khuôn máēˇt", "select_featured_photo": "Cháģn áēŖnh náģ•i báē­t", "select_from_computer": "Cháģn táģĢ mÃĄy tính", "select_keep_all": "Cháģn giáģ¯ táēĨt cáēŖ", + "select_library_owner": "Cháģn cháģ§ sáģŸ háģ¯u thư viáģ‡n", "select_new_face": "Cháģn khuôn máēˇt máģ›i", "select_people": "Cháģn ngưáģi", "select_person": "Cháģn ngưáģi", "select_person_to_tag": "Cháģn ngưáģi đáģƒ gáē¯n tháēģ", "select_photos": "Cháģn áēŖnh", - "select_quality": "Cháģn cháēĨt lưáģŖng", "select_trash_all": "Cháģn xÃŗa táēĨt cáēŖ", + "select_user_for_sharing_page_err_album": "TáēĄo album tháēĨt báēĄi", "selected": "ÄÃŖ cháģn", "selected_count": "{count, plural, other {ÄÃŖ cháģn # máģĨc}}", "selected_gps_coordinates": "Táģa đáģ™ GPS Ä‘ÃŖ cháģn", "send_message": "Gáģ­i tin nháē¯n", "send_welcome_email": "Gáģ­i email chào máģĢng", "server_endpoint": "Đáģ‹a cháģ‰ mÃĄy cháģ§", - "server_info_box_app_version": "PhiÃĒn báēŖn áģŠng dáģĨng", + "server_info_box_app_version": "PhiÃĒn báēŖn app", "server_info_box_server_url": "URL mÃĄy cháģ§", "server_offline": "MÃĄy cháģ§ ngoáēĄi tuyáēŋn", "server_online": "PhiÃĒn báēŖn", @@ -1905,52 +2023,62 @@ "set_date_of_birth": "Đáēˇt sinh nháē­t", "set_profile_picture": "Đáēˇt áēŖnh đáēĄi diáģ‡n", "set_slideshow_to_fullscreen": "Đáēˇt trÃŦnh chiáēŋu áģŸ cháēŋ đáģ™ toàn màn hÃŦnh", - "set_stack_primary_asset": "Đáēˇt làm tài nguyÃĒn chính", - "setting_image_navigation_enable_subtitle": "Báē­t đáģƒ chuyáģƒn sang áēŖnh trưáģ›c/áēŖnh sau báēąng cÃĄch nháēĨn mÊp trÃĄi/pháēŖi màn hÃŦnh.", + "set_stack_primary_asset": "Đáēˇt làm tài sáēŖn chính", + "setting_image_navigation_enable_subtitle": "Náēŋu đưáģŖc báē­t, báēĄn cÃŗ tháģƒ Ä‘iáģu hưáģ›ng đáēŋn áēŖnh phía trưáģ›c/káēŋ tiáēŋp báēąng cÃĄch cháēĄm vào pháē§n tư bÃĒn trÃĄi/pháēŖi màn hÃŦnh.", "setting_image_navigation_enable_title": "CháēĄm đáģƒ Ä‘iáģu hưáģ›ng", "setting_image_navigation_title": "Điáģu hưáģ›ng hÃŦnh áēŖnh", - "setting_image_viewer_help": "Khi xem chi tiáēŋt, tháģŠ táģą náēĄp sáēŊ là: áēŖnh thu nháģ (cáģĄ nháģ) → áēŖnh xem trưáģ›c (cáģĄ trung bÃŦnh) (náēŋu báē­t) → áēŖnh gáģ‘c (náēŋu báē­t).", + "setting_image_viewer_help": "TrÃŦnh xem áēŖnh táēŖi áēŖnh thu nháģ cáģĄ nháģ trưáģ›c, sau Ä‘Ãŗ táēŖi cáģĄ trung bÃŦnh (náēŋu đưáģŖc báē­t), cuáģ‘i cÚng táēŖi báēŖn gáģ‘c (náēŋu đưáģŖc báē­t).", "setting_image_viewer_original_subtitle": "Báē­t đáģƒ táēŖi áēŖnh gáģ‘c áģŸ Ä‘áģ™ phÃĸn giáēŖi đáē§y đáģ§ (dung lưáģŖng láģ›n). Táē¯t đáģƒ giáēŖm máģŠc sáģ­ dáģĨng dáģ¯ liáģ‡u (máģŠc sáģ­ dáģĨng máēĄng và báģ™ nháģ› Ä‘áģ‡m trÃĒn thiáēŋt báģ‹).", - "setting_image_viewer_original_title": "Ưu tiÃĒn cháēĨt lưáģŖng gáģ‘c", - "setting_image_viewer_preview_subtitle": "Báē­t đáģƒ náēĄp áēŖnh cháēĨt lưáģŖng trung bÃŦnh. Táē¯t đáģƒ náēĄp tháēŗng áēŖnh gáģ‘c hoáēˇc cháģ‰ xem áēŖnh thu nháģ.", - "setting_image_viewer_preview_title": "NáēĄp áēŖnh xem trưáģ›c", + "setting_image_viewer_original_title": "TáēŖi áēŖnh gáģ‘c", + "setting_image_viewer_preview_subtitle": "Báē­t đáģƒ táēŖi áēŖnh đáģ™ phÃĸn giáēŖi trung bÃŦnh. Táē¯t đáģƒ táēŖi tráģąc tiáēŋp áēŖnh gáģ‘c hoáēˇc cháģ‰ sáģ­ dáģĨng áēŖnh thu nháģ.", + "setting_image_viewer_preview_title": "TáēŖi áēŖnh xem trưáģ›c", + "setting_image_viewer_title": "áēĸnh", "setting_languages_apply": "Áp dáģĨng", "setting_languages_subtitle": "Thay đáģ•i ngôn ngáģ¯ áģŠng dáģĨng", + "setting_notifications_notify_failures_grace_period": "Thông bÃĄo láģ—i sao lưu náģn: {duration}", + "setting_notifications_notify_hours": "{count} giáģ", + "setting_notifications_notify_immediately": "ngay láē­p táģŠc", "setting_notifications_notify_minutes": "{count} phÃēt", + "setting_notifications_notify_never": "không bao giáģ", "setting_notifications_notify_seconds": "{count} giÃĸy", - "setting_notifications_subtitle": "Thiáēŋt láē­p thông bÃĄo", + "setting_notifications_single_progress_subtitle": "Thông tin chi tiáēŋt cáģ§a táģĢng áēŖnh đang táēŖi lÃĒn", + "setting_notifications_single_progress_title": "Hiáģ‡n chi tiáēŋt sao lưu náģn đang tháģąc hiáģ‡n", + "setting_notifications_subtitle": "CÃĄc loáēĄi thông bÃĄo", + "setting_notifications_total_progress_subtitle": "Toàn báģ™ quÃĄ trÃŦnh táēŖi lÃĒn (hoàn thành/táģ•ng sáģ‘ táģ‡p)", + "setting_notifications_total_progress_title": "Hiáģ‡n quÃĄ trÃŦnh sao lưu náģn đang tháģąc hiáģ‡n", "setting_video_viewer_auto_play_subtitle": "Khi máģŸ video", "setting_video_viewer_auto_play_title": "Táģą Ä‘áģ™ng phÃĄt video", "setting_video_viewer_looping_title": "Láēˇp láēĄi", - "setting_video_viewer_original_video_subtitle": "Khi phÃĄt tráģąc tuyáēŋn máģ™t video táģĢ mÃĄy cháģ§, háģ‡ tháģ‘ng sáēŊ luôn dÚng video gáģ‘c thay vÃŦ báēŖn chuyáģƒn mÃŖ (cÃŗ tháģƒ gÃĸy hiáģ‡n tưáģŖng giáē­t/cháģ táēŖi). Video lưu trÃĒn thiáēŋt báģ‹ luôn đưáģŖc phÃĄt áģŸ cháēĨt lưáģŖng gáģ‘c.", - "setting_video_viewer_original_video_title": "Ưu tiÃĒn cháēĨt lưáģŖng gáģ‘c", + "setting_video_viewer_original_video_subtitle": "Khi phÃĄt tráģąc tuyáēŋn video táģĢ mÃĄy cháģ§, hÃŖy phÃĄt video gáģ‘c ngay cáēŖ khi cÃŗ báēŖn chuyáģƒn mÃŖ. CÃŗ tháģƒ dáēĢn đáēŋn tÃŦnh tráēĄng cháģ. Video cÃŗ sáēĩn trÃĒn mÃĄy đưáģŖc phÃĄt áģŸ cháēĨt lưáģŖng gáģ‘c báēĨt káģƒ cài đáēˇt này.", + "setting_video_viewer_original_video_title": "DÚng video gáģ‘c", "settings": "Cài đáēˇt", + "settings_require_restart": "Vui lÃ˛ng kháģŸi đáģ™ng láēĄi Immich đáģƒ ÃĄp dáģĨng cài đáēˇt này", "settings_saved": "ÄÃŖ lưu cài đáēˇt", "setup_pin_code": "Thiáēŋt láē­p mÃŖ PIN", "share": "Chia sáēģ", + "share_action_prompt": "ÄÃŖ chia sáēģ {count} táģ‡p", + "share_add_photos": "ThÃĒm áēŖnh", + "share_assets_selected": "{count} Ä‘ÃŖ cháģn", "share_dialog_preparing": "Đang xáģ­ lÃŊ...", - "share_link": "Chia sáēģ link", - "share_original": "CháēĨt lưáģŖng gáģ‘c (láģ›n)", - "share_preview": "DÚng áēŖnh thu nháģ (nháģ)", - "share_quality_body": "NháēĨn giáģ¯ nÃēt chia sáēģ đáģƒ cháģn cháēĨt lưáģŖng hÃŦnh áēŖnh trưáģ›c khi chia sáēģ.", - "share_quality_title": "Cháģn cháēĨt lưáģŖng chia sáēģ", + "share_link": "LiÃĒn káēŋt chia sáēģ", "shared": "ÄÃŖ chia sáēģ", "shared_album_activities_input_disable": "Nháē­n xÊt hiáģ‡n Ä‘ÃŖ táē¯t", "shared_album_activity_remove_content": "BáēĄn cÃŗ muáģ‘n xÃŗa hoáēĄt đáģ™ng này?", "shared_album_activity_remove_title": "XÃŗa hoáēĄt đáģ™ng", "shared_album_section_people_action_error": "Láģ—i khi xÃŗa kháģi album", + "shared_album_section_people_action_leave": "XÃŗa ngưáģi dÚng kháģi album", + "shared_album_section_people_action_remove_user": "XÃŗa ngưáģi dÚng kháģi album", "shared_album_section_people_title": "MáģŒI NGƯáģœI", "shared_by": "ĐưáģŖc chia sáēģ báģŸi", "shared_by_user": "ĐưáģŖc chia sáēģ báģŸi {user}", "shared_by_you": "ĐưáģŖc chia sáēģ báģŸi báēĄn", "shared_from_partner": "áēĸnh táģĢ {partner}", "shared_intent_upload_button_progress_text": "{current} / {total} ÄÃŖ táēŖi lÃĒn", - "shared_link_app_bar_title": "Nháģ¯ng link Ä‘ÃŖ chia sáēģ", - "shared_link_clipboard_copied_massage": "ÄÃŖ sao chÊp vào clipboard", + "shared_link_app_bar_title": "LiÃĒn káēŋt Ä‘ÃŖ chia sáēģ", + "shared_link_clipboard_copied_massage": "ÄÃŖ sao chÊp vào báģ™ nháģ› táēĄm", + "shared_link_clipboard_text": "LiÃĒn káēŋt: {link}\nMáē­t kháēŠu: {password}", "shared_link_create_error": "TáēĄo liÃĒn káēŋt chia sáēģ không thành công", - "shared_link_custom_url_description": "Truy cáē­p link Ä‘ÃŖ chia sáēģ váģ›i máģ™t URL tÚy cháģ‰nh", - "shared_link_custom_url_title": "URL tÚy cháģ‰nh", - "shared_link_custom_url_warning": "CáēŖnh bÃĄo: dÚng dáēĨu gáēĄch chÊo trong URL tÚy cháģ‰nh cÃŗ tháģƒ gÃĸy ra láģ—i.", + "shared_link_custom_url_description": "Truy cáē­p liÃĒn káēŋt chia sáēģ váģ›i máģ™t URL tÚy cháģ‰nh", "shared_link_edit_description_hint": "Nháē­p mô táēŖ chia sáēģ", "shared_link_edit_expire_after_option_day": "1 ngày", "shared_link_edit_expire_after_option_days": "{count} ngày", @@ -1961,7 +2089,7 @@ "shared_link_edit_expire_after_option_months": "{count} thÃĄng", "shared_link_edit_expire_after_option_year": "{count} năm", "shared_link_edit_password_hint": "Nháē­p máē­t kháēŠu chia sáēģ", - "shared_link_edit_submit_button": "Cáē­p nháē­t link", + "shared_link_edit_submit_button": "Cáē­p nháē­t liÃĒn káēŋt", "shared_link_error_server_url_fetch": "Không tháģƒ káēŋt náģ‘i đáģ‹a cháģ‰ mÃĄy cháģ§", "shared_link_expires_day": "Háēŋt háēĄn sau {count} ngày", "shared_link_expires_days": "Háēŋt háēĄn sau {count} ngày", @@ -1974,38 +2102,41 @@ "shared_link_expires_seconds": "Háēŋt háēĄn sau {count} giÃĸy", "shared_link_individual_shared": "Chia sáēģ riÃĒng tư", "shared_link_info_chip_metadata": "Dáģ¯ liáģ‡u EXIF", - "shared_link_manage_links": "QuáēŖn lÃŊ cÃĄc link Ä‘ÃŖ chia sáēģ", - "shared_link_options": "TÚy cháģn chia sáēģ link", - "shared_link_password_description": "Báē¯t buáģ™c đáģƒ truy cáē­p link chia sáēģ này", - "shared_links": "ÄÃŖ chia sáēģ", - "shared_links_description": "Chia sáēģ áēŖnh và video báēąng link", + "shared_link_manage_links": "QuáēŖn lÃŊ liÃĒn káēŋt Ä‘ÃŖ chia sáēģ", + "shared_link_options": "TÚy cháģn liÃĒn káēŋt chia sáēģ", + "shared_link_password_description": "Báē¯t buáģ™c đáģƒ truy cáē­p liÃĒn káēŋt chia sáēģ này", + "shared_links": "LiÃĒn káēŋt chia sáēģ", + "shared_links_description": "Chia sáēģ áēŖnh và video báēąng liÃĒn káēŋt", "shared_photos_and_videos_count": "{assetCount, plural, other {# áēŖnh & video Ä‘ÃŖ chia sáēģ.}}", "shared_with_me": "Chia sáēģ váģ›i tôi", "shared_with_partner": "ÄÃŖ chia sáēģ váģ›i {partner}", "sharing": "Chia sáēģ", "sharing_enter_password": "Vui lÃ˛ng nháē­p máē­t kháēŠu đáģƒ xem trang này.", - "shift_to_permanent_delete": "nháēĨn ⇧ đáģƒ xÃŗa vÄŠnh viáģ…n tài nguyÃĒn", + "sharing_page_album": "Album chia sáēģ", + "sharing_page_description": "TáēĄo album chia sáēģ đáģƒ chia sáēģ áēŖnh và video váģ›i nháģ¯ng ngưáģi trong máēĄng cáģ§a báēĄn.", + "sharing_page_empty_list": "DANH SÁCH TRáģNG", + "sharing_sidebar_description": "Hiáģƒn tháģ‹ máģĨc Chia sáēģ trong thanh bÃĒn", + "sharing_silver_appbar_create_shared_album": "TáēĄo album chia sáēģ", + "sharing_silver_appbar_share_partner": "Chia sáēģ váģ›i ngưáģi thÃĸn", + "shift_to_permanent_delete": "nháēĨn ⇧ đáģƒ xÃŗa vÄŠnh viáģ…n táģ‡p", "show_album_options": "Hiáģ‡n tÚy cháģn album", "show_albums": "Hiáģ‡n album", "show_all_people": "Hiáģƒn tháģ‹ táēĨt cáēŖ máģi ngưáģi", "show_and_hide_people": "Hiáģƒn tháģ‹ & áēŠn ngưáģi", - "show_file_location": "Hiáģƒn tháģ‹ váģ‹ trí táģ‡p", + "show_file_location": "Hiáģƒn tháģ‹ váģ‹ trí táē­p tin", "show_gallery": "Hiáģƒn tháģ‹ thư viáģ‡n áēŖnh", "show_hidden_people": "Hiáģƒn tháģ‹ ngưáģi báģ‹ áēŠn", "show_in_timeline": "Hiáģƒn tháģ‹ trÃĒn dÃ˛ng tháģi gian", - "show_in_timeline_setting_description": "Hiáģƒn tháģ‹ áēŖnh và video táģĢ ngưáģi dÚng này trÃĒn dÃ˛ng tháģi gian cáģ§a báēĄn", + "show_in_timeline_setting_description": "Hiáģƒn tháģ‹ áēŖnh và video táģĢ ngưáģi dÚng này trong dÃ˛ng tháģi gian cáģ§a báēĄn", "show_keyboard_shortcuts": "Hiáģ‡n phím táē¯t", - "show_less": "Hiáģ‡n ít hÆĄn", "show_metadata": "Hiáģƒn tháģ‹ metadata", - "show_more_fields": "{count, plural, one {Hiáģ‡n thÃĒm # trưáģng} other {Hiáģ‡n thÃĒm # trưáģng}}", "show_or_hide_info": "Hiáģƒn tháģ‹ hoáēˇc áēŠn thông tin", "show_password": "Hiáģƒn tháģ‹ máē­t kháēŠu", "show_person_options": "Hiáģ‡n tÚy cháģn ngưáģi", "show_progress_bar": "Hiáģƒn tháģ‹ thanh tiáēŋn trÃŦnh", "show_schema": "Hiáģ‡n lưáģŖc đáģ“", "show_search_options": "Hiáģ‡n tÚy cháģn tÃŦm kiáēŋm", - "show_shared_links": "Hiáģƒn tháģ‹ cÃĄc link đưáģŖc chia sáēģ", - "show_slideshow_metadata_overlay": "Hiáģƒn tháģ‹ láģ›p pháģ§ thông tin hÃŦnh áēŖnh", + "show_shared_links": "Hiáģƒn tháģ‹ cÃĄc liÃĒn káēŋt đưáģŖc chia sáēģ", "show_slideshow_transition": "Hiáģƒn tháģ‹ hiáģ‡u áģŠng chuyáģƒn tiáēŋp", "show_supporter_badge": "Huy hiáģ‡u ngưáģi áģ§ng háģ™", "show_supporter_badge_description": "Hiáģƒn tháģ‹ huy hiáģ‡u ngưáģi áģ§ng háģ™", @@ -2013,49 +2144,39 @@ "show_text_search_menu": "Hiáģƒn tháģ‹ menu tÃŦm kiáēŋm văn báēŖn", "shuffle": "NgáēĢu nhiÃĒn", "sidebar": "Thanh bÃĒn", - "sidebar_display_description": "Hiáģƒn tháģ‹ link đáēŋn cháēŋ đáģ™ xem trong thanh bÃĒn", + "sidebar_display_description": "Hiáģƒn tháģ‹ liÃĒn káēŋt đáēŋn cháēŋ đáģ™ xem trong thanh bÃĒn", "sign_out": "Đăng xuáēĨt", "sign_up": "Đăng kÃŊ", "size": "Kích thưáģ›c", - "skip": "Báģ qua", "skip_to_content": "Chuyáģƒn đáēŋn náģ™i dung", "skip_to_folders": "Chuyáģƒn đáēŋn thư máģĨc", "skip_to_tags": "Chuyáģƒn đáēŋn tháēģ", "slideshow": "TrÃŦnh chiáēŋu", - "slideshow_body": "HÃŖy thư giÃŖn và xem cÃĄc báģŠc áēŖnh cáģ§a báēĄn trÃŦnh chiáēŋu áģŸ cháēŋ đáģ™ toàn màn hÃŦnh.", - "slideshow_metadata_overlay_mode": "Láģ›p pháģ§ náģ™i dung", - "slideshow_metadata_overlay_mode_description_only": "Cháģ‰ mô táēŖ", - "slideshow_metadata_overlay_mode_full": "Đáē§y đáģ§", - "slideshow_repeat": "Láēˇp láēĄi trÃŦnh chiáēŋu", + "slideshow_repeat": "TrÃŦnh chiáēŋu láēĄi", "slideshow_repeat_description": "PhÃĄt láēĄi táģĢ Ä‘áē§u khi trÃŦnh chiáēŋu káēŋt thÃēc", "slideshow_settings": "Cài đáēˇt trÃŦnh chiáēŋu", - "slideshow_title": "Slideshow", - "smart_album": "Album thông minh", - "some_assets_already_have_a_location_warning": "Máģ™t sáģ‘ tài nguyÃĒn đưáģŖc láģąa cháģn Ä‘ÃŖ cÃŗ váģ‹ trí", "sort_albums_by": "Sáē¯p xáēŋp album theo...", "sort_created": "Ngày táēĄo", "sort_items": "Sáģ‘ lưáģŖng máģĨc", "sort_modified": "Ngày sáģ­a đáģ•i", + "sort_newest": "áēĸnh máģ›i nháēĨt", "sort_oldest": "áēĸnh cÅŠ nháēĨt", "sort_people_by_similarity": "Sáē¯p xáēŋp ngưáģi theo đáģ™ tÆ°ÆĄng đáģ“ng", "sort_recent": "áēĸnh gáē§n đÃĸy nháēĨt", "sort_title": "TiÃĒu đáģ", - "source": "MÃŖ nguáģ“n", + "source": "Nguáģ“n", "stack": "NhÃŗm áēŖnh", - "stack_action_prompt": "ÄÃŖ nhÃŗm ({count}) tÆ°ÆĄng táģą", + "stack_action_prompt": "{count} Ä‘ÃŖ đưáģŖc nhÃŗm", "stack_duplicates": "NhÃŗm máģĨc trÚng láēˇp", + "stack_select_one_photo": "Cháģn áēŖnh chính cho nhÃŗm áēŖnh", "stack_selected_photos": "NhÃŗm cÃĄc áēŖnh Ä‘ÃŖ cháģn", - "stacked_assets_count": "ÄÃŖ nhÃŗm {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "stacked_assets_count": "ÄÃŖ nhÃŗm {count, plural, one {# táģ‡p} other {# táģ‡p}}", "stacktrace": "Thông tin chi tiáēŋt láģ—i", "start": "Báē¯t đáē§u", "start_date": "Ngày báē¯t đáē§u", "start_date_before_end_date": "Ngày báē¯t đáē§u pháēŖi trưáģ›c ngày káēŋt thÃēc", - "state": "Tiáģƒu bang", + "state": "Táģ‰nh", "status": "TráēĄng thÃĄi", - "step_delete": "XÃŗa bưáģ›c", - "step_delete_confirm": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa bưáģ›c này?", - "step_details": "Chi tiáēŋt bưáģ›c", - "steps": "CÃĄc bưáģ›c", "stop_casting": "DáģĢng chiáēŋu", "stop_motion_photo": "DáģĢng áēŖnh chuyáģƒn đáģ™ng", "stop_photo_sharing": "DáģĢng chia sáēģ áēŖnh cáģ§a báēĄn?", @@ -2064,7 +2185,7 @@ "storage": "Báģ™ nháģ›", "storage_label": "NhÃŖn lưu tráģ¯", "storage_quota": "HáēĄn máģŠc dung lưáģŖng", - "storage_usage": "{used} trong {available}", + "storage_usage": "ÄÃŖ dÚng {used} cáģ§a {available}", "submit": "Gáģ­i", "success": "Thành công", "suggestions": "GáģŖi ÃŊ", @@ -2076,22 +2197,21 @@ "swap_merge_direction": "Đáģ•i hưáģ›ng háģŖp nháēĨt", "sync": "Đáģ“ng báģ™", "sync_albums": "Đáģ“ng báģ™ album", + "sync_albums_manual_subtitle": "Đáģ“ng báģ™ hÃŗa táēĨt cáēŖ video và áēŖnh Ä‘ÃŖ táēŖi lÃĒn vào album sao lưu Ä‘ÃŖ cháģn", "sync_local": "Đáģ“ng báģ™ hÃŗa thiáēŋt báģ‹", "sync_remote": "Đáģ“ng báģ™ hÃŗa trÃĒn mÃĸy", "sync_status": "TráēĄng thÃĄi đáģ“ng báģ™", "sync_status_subtitle": "Tháģ‘ng kÃĒ váģ viáģ‡c đáģ“ng báģ™", "sync_upload_album_setting_subtitle": "TáēĄo và táēŖi lÃĒn áēŖnh và video cáģ§a báēĄn vào album Ä‘ÃŖ cháģn trÃĒn Immich", - "system_theme": "Cháģ§ Ä‘áģ háģ‡ tháģ‘ng", - "system_theme_command_description": "Giáģ‘ng cháģ§ Ä‘áģ háģ‡ tháģ‘ng ({value})", "tag": "Tháēģ", - "tag_assets": "Gáē¯n tháēģ tài nguyÃĒn", + "tag_assets": "Gáē¯n tháēģ", "tag_created": "ÄÃŖ táēĄo tháēģ: {tag}", - "tag_face": "Gáē¯n tháēģ khuôn máēˇt", - "tag_feature_description": "Duyáģ‡t áēŖnh và video đưáģŖc phÃĸn loáēĄi theo cháģ§ Ä‘áģ tháēģ", + "tag_feature_description": "Duyáģ‡t áēŖnh và video đưáģŖc nhÃŗm theo cháģ§ Ä‘áģ tháēģ háģŖp lÃŊ", + "tag_not_found_question": "Không tÃŦm tháēĨy tháēģ? Create a new tag. (TáēĄo máģ™t tháēģ máģ›i)", "tag_people": "Gáē¯n tháēģ Máģi ngưáģi", "tag_updated": "ÄÃŖ cáē­p nháē­t tháēģ: {tag}", - "tagged_assets": "ÄÃŖ gáē¯n tháēģ {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "tags": "Gáē¯n tháēģ", + "tagged_assets": "ÄÃŖ gáē¯n tháēģ {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "tags": "Tháēģ", "tap_to_run_job": "NháēĨn đáģƒ cháēĄy tÃĄc váģĨ", "template": "MáēĢu", "text_recognition": "Nháē­n dáēĄng văn báēŖn", @@ -2099,13 +2219,18 @@ "theme_selection": "Cháģ§ Ä‘áģ", "theme_selection_description": "Dáģąa theo trÃŦnh duyáģ‡t cáģ§a báēĄn", "theme_setting_asset_list_storage_indicator_title": "Hiáģ‡n tráēĄng thÃĄi sao lưu trÃĒn áēŖnh thu nháģ", - "theme_setting_asset_list_tiles_per_row_title": "Sáģ‘ lưáģŖng tài nguyÃĒn trÃĒn máģ—i hàng ({count})", - "theme_setting_colorful_interface_subtitle": "Áp dáģĨng màu cháģ§ Ä‘áģ cho náģn app.", + "theme_setting_asset_list_tiles_per_row_title": "Sáģ‘ lưáģŖng táģ‡p trÃĒn máģ—i hàng ({count})", + "theme_setting_colorful_interface_subtitle": "Áp dáģĨng màu cháģ§ Ä‘áēĄo cho náģn app.", "theme_setting_colorful_interface_title": "Giao diáģ‡n sinh đáģ™ng", + "theme_setting_image_viewer_quality_subtitle": "Điáģu cháģ‰nh cháēĨt lưáģŖng cáģ§a trÃŦnh xem áēŖnh", + "theme_setting_image_viewer_quality_title": "CháēĨt lưáģŖng trÃŦnh xem áēŖnh", "theme_setting_primary_color_subtitle": "Cháģn màu cho cÃĄc hành đáģ™ng chính và điáģƒm nháēĨn.", - "theme_setting_primary_color_title": "Màu cháģ§ Ä‘áģ", + "theme_setting_primary_color_title": "Màu cháģ§ Ä‘áēĄo", "theme_setting_system_primary_color_title": "DÚng màu háģ‡ tháģ‘ng", "theme_setting_system_theme_switch": "Táģą Ä‘áģ™ng (Giáģ‘ng thiáēŋt báģ‹)", + "theme_setting_theme_subtitle": "Cháģn cài đáēˇt giao diáģ‡n app", + "theme_setting_three_stage_loading_subtitle": "TáēŖi ba giai đoáēĄn cÃŗ tháģƒ tăng táģ‘c đáģ™ táēŖi áēŖnh nhưng sáēŊ táģ‘n dáģ¯ liáģ‡u máēĄng Ä‘ÃĄng káģƒ", + "theme_setting_three_stage_loading_title": "Báē­t táēŖi ba giai đoáēĄn", "then": "Tiáēŋp theo", "they_will_be_merged_together": "ChÃēng sáēŊ đưáģŖc háģŖp nháēĨt váģ›i nhau", "third_party_resources": "Tài nguyÃĒn bÃĒn tháģŠ ba", @@ -2118,46 +2243,51 @@ "to_change_password": "Đáģ•i máē­t kháēŠu", "to_favorite": "Thích", "to_login": "Đăng nháē­p", - "to_multi_select": "cháģn nhiáģu máģĨc", + "to_multi_select": "đáģƒ cháģn-nhiáģu", "to_parent": "Váģ thư máģĨc gáģ‘c", - "to_select": "cháģn", + "to_select": "đáģƒ cháģn", "to_trash": "XÃŗa", - "toggle_settings": "Đáģ•i cài đáēˇt", + "toggle_settings": "Chuyáģƒn đáģ•i cài đáēˇt", "toggle_theme_description": "Đáģ•i cháģ§ Ä‘áģ", "total": "Táģ•ng cáģ™ng", "total_usage": "Dung lưáģŖng Ä‘ÃŖ dÚng", "trash": "ThÚng rÃĄc", - "trash_action_prompt": "ÄÃŖ chuyáģƒn ({count}) vào thÚng rÃĄc", + "trash_action_prompt": "{count} Ä‘ÃŖ chuyáģƒn vào thÚng rÃĄc", "trash_all": "XÃŗa háēŋt", "trash_count": "XÃŗa {count, number} máģĨc", - "trash_delete_asset": "Chuyáģƒn vào thÚng rÃĄc/XÃŗa tài nguyÃĒn", + "trash_delete_asset": "Chuyáģƒn vào thÚng rÃĄc/XÃŗa vÄŠnh viáģ…n", + "trash_emptied": "ÄÃŖ dáģn sáēĄch thÚng rÃĄc", "trash_no_results_message": "áēĸnh và video Ä‘ÃŖ báģ‹ xÃŗa sáēŊ hiáģƒn tháģ‹ áģŸ Ä‘Ãĸy.", "trash_page_delete_all": "XÃŗa táēĨt cáēŖ", + "trash_page_empty_trash_dialog_content": "BáēĄn cÃŗ muáģ‘n dáģn sáēĄch thÚng rÃĄc cáģ§a mÃŦnh không? Nháģ¯ng máģĨc này sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n kháģi Immich", "trash_page_info": "Nháģ¯ng máģĨc này sáēŊ báģ‹ xÃŗa sau {days} ngày", + "trash_page_no_assets": "Không cÃŗ táģ‡p nào", + "trash_page_restore_all": "Khôi pháģĨc táēĨt cáēŖ", + "trash_page_select_assets_btn": "Cháģn táģ‡p", + "trash_page_title": "ThÚng rÃĄc ({count})", "trashed_items_will_be_permanently_deleted_after": "CÃĄc máģĨc Ä‘ÃŖ xÃŗa sáēŊ báģ‹ xÃŗa vÄŠnh viáģ…n sau {days, plural, one {# ngày} other {# ngày}}.", "trigger": "Kích hoáēĄt", - "trigger_asset_metadata_extraction": "Trích xuáēĨt metadata tài nguyÃĒn", - "trigger_asset_metadata_extraction_description": "Sáģą kiáģ‡n này đưáģŖc kích hoáēĄt khi metadata EXIF cáģ§a máģ™t tài nguyÃĒn đưáģŖc trích xuáēĨt", - "trigger_asset_uploaded": "ÄÃŖ táēŖi lÃĒn tài nguyÃĒn", - "trigger_asset_uploaded_description": "ĐưáģŖc kích hoáēĄt khi máģ™t tài nguyÃĒn máģ›i đưáģŖc táēŖi lÃĒn", - "trigger_person_recognized": "ÄÃŖ nháē­n diáģ‡n ngưáģi", + "trigger_asset_uploaded": "Táģ‡p Ä‘ÃŖ đưáģŖc táēŖi lÃĒn", + "trigger_asset_uploaded_description": "Sáģą kiáģ‡n này đưáģŖc kích hoáēĄt khi máģ™t táģ‡p máģ›i đưáģŖc táēŖi lÃĒn", + "trigger_description": "Máģ™t sáģą kiáģ‡n kháģŸi đáē§u workflow", + "trigger_person_recognized": "Ngưáģi đưáģŖc nháē­n diáģ‡n", "trigger_person_recognized_description": "ĐưáģŖc kích hoáēĄt khi phÃĄt hiáģ‡n tháēĨy máģ™t ngưáģi", + "trigger_type": "Kiáģƒu kích hoáēĄt", "troubleshoot": "Kháē¯c pháģĨc sáģą cáģ‘", "type": "LoáēĄi", "unable_to_change_pin_code": "Thay đáģ•i mÃŖ PIN tháēĨt báēĄi", - "unable_to_check_version": "Không tháģƒ kiáģƒm tra phiÃĒn báēŖn áģŠng dáģĨng hoáēˇc mÃĄy cháģ§", + "unable_to_check_version": "Không tháģƒ kiáģƒm tra phiÃĒn báēŖn app hoáēˇc mÃĄy cháģ§", "unable_to_setup_pin_code": "Thiáēŋt láē­p mÃŖ PIN tháēĨt báēĄi", "unarchive": "Báģ lưu tráģ¯", - "unarchive_action_prompt": "ÄÃŖ báģ lưu tráģ¯ ({count})", + "unarchive_action_prompt": "{count} Ä‘ÃŖ báģ kháģi Lưu tráģ¯", "unarchived_count": "{count, plural, other {ÄÃŖ báģ lưu tráģ¯ # máģĨc}}", "undo": "Hoàn tÃĄc", "unfavorite": "Báģ thích", - "unfavorite_action_prompt": "ÄÃŖ báģ yÃĒu thích ({count})", + "unfavorite_action_prompt": "{count} Ä‘ÃŖ báģ kháģi ÄÃŖ thích", "unhide_person": "Hiáģ‡n ngưáģi", "unknown": "Không xÃĄc đáģ‹nh", "unknown_country": "Quáģ‘c gia chưa rÃĩ", "unknown_date": "Dáģ¯ liáģ‡u không xÃĄc đáģ‹nh", - "unknown_schema": "LưáģŖc đáģ“ không xÃĄc đáģ‹nh", "unknown_year": "Năm không rÃĩ", "unlimited": "Không giáģ›i háēĄn", "unlink_motion_video": "Háģ§y liÃĒn káēŋt video chuyáģƒn đáģ™ng", @@ -2170,53 +2300,52 @@ "unsaved_change": "Chưa lưu thay đáģ•i", "unselect_all": "Báģ cháģn táēĨt cáēŖ", "unselect_all_duplicates": "Báģ cháģn táēĨt cáēŖ cÃĄc báēŖn trÚng láēˇp", + "unselect_all_in": "Báģ cháģn táēĨt cáēŖ trong {group}", "unstack": "Háģ§y xáēŋp nhÃŗm", - "unstack_action_prompt": "ÄÃŖ báģ tÆ°ÆĄng táģą ({count})", - "unstacked_assets_count": "ÄÃŖ háģ§y xáēŋp nhÃŗm {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", + "unstack_action_prompt": "{count} Ä‘ÃŖ báģ nhÃŗm", + "unstacked_assets_count": "ÄÃŖ háģ§y xáēŋp nhÃŗm {count, plural, one {# táģ‡p} other {# táģ‡p}}", "unsupported_field_type": "LoáēĄi trưáģng không đưáģŖc háģ— tráģŖ", "unsupported_file_type": "Táģ‡p {file} không tháģƒ Ä‘Æ°áģŖc táēŖi lÃĒn vÃŦ loáēĄi táģ‡p {type} không đưáģŖc háģ— tráģŖ.", "untagged": "Chưa gáē¯n tháēģ", + "untitled_workflow": "Workflow chưa đáēˇt tÃĒn", "up_next": "Tiáēŋp theo", - "update_location_action_prompt": "Cáē­p nháē­t váģ‹ trí cáģ§a {count} tài nguyÃĒn Ä‘ÃŖ cháģn váģ›i:", + "update_location_action_prompt": "Cáē­p nháē­t đáģ‹a điáģƒm cáģ§a {count} táģ‡p Ä‘ÃŖ cháģn váģ›i:", "updated_at": "ÄÃŖ cáē­p nháē­t", "updated_password": "ÄÃŖ cáē­p nháē­t máē­t kháēŠu", "upload": "TáēŖi lÃĒn", "upload_concurrency": "TáēŖi lÃĒn đáģ“ng tháģi", - "upload_day_count": "{date}: {count, plural, one {# táēŖi lÃĒn} other {# táēŖi lÃĒn}}", "upload_details": "Chi tiáēŋt táēŖi lÃĒn", - "upload_error_with_count": "Không tháģƒ táēŖi lÃĒn {count, plural, one {# tài nguyÃĒn} other {# tài nguyÃĒn}}", - "upload_errors": "ÄÃŖ hoàn táēĨt táēŖi lÃĒn váģ›i {count, plural, one {# láģ—i} other {# láģ—i}}, làm máģ›i trang đáģƒ xem cÃĄc tài nguyÃĒn máģ›i táēŖi lÃĒn.", + "upload_dialog_info": "BáēĄn cÃŗ muáģ‘n sao lưu nháģ¯ng táģ‡p Ä‘ÃŖ cháģn lÃĒn mÃĄy cháģ§ không?", + "upload_dialog_title": "TáēŖi lÃĒn táģ‡p", + "upload_error_with_count": "Không tháģƒ táēŖi lÃĒn {count, plural, one {# táģ‡p} other {# táģ‡p}}", + "upload_errors": "ÄÃŖ hoàn táēĨt táēŖi lÃĒn váģ›i {count, plural, one {# láģ—i} other {# láģ—i}}, làm máģ›i trang đáģƒ xem cÃĄc táģ‡p váģĢa táēŖi lÃĒn.", "upload_finished": "ÄÃŖ hoàn táēĨt táēŖi lÃĒn", - "upload_progress": "CÃ˛n {remaining, number} - ÄÃŖ xáģ­ lÃŊ {processed, number}/{total, number}", - "upload_skipped_duplicates": "ÄÃŖ báģ qua {count, plural, one {# tài nguyÃĒn trÚng láēˇp} other {# tài nguyÃĒn trÚng láēˇp}}", - "upload_status_duplicates": "TrÚng láēˇp", + "upload_progress": "CÃ˛n láēĄi {remaining, number} - ÄÃŖ xáģ­ lÃŊ {processed, number}/{total, number}", + "upload_skipped_duplicates": "ÄÃŖ báģ qua {count, plural, one {# táģ‡p trÚng láēˇp} other {# táģ‡p trÚng láēˇp}}", + "upload_status_duplicates": "Táģ‡p trÚng láēˇp", "upload_status_errors": "Láģ—i", "upload_status_uploaded": "ÄÃŖ táēŖi lÃĒn", - "upload_success": "TáēŖi lÃĒn thành công, làm máģ›i trang đáģƒ xem cÃĄc tài nguyÃĒn máģ›i táēŖi lÃĒn.", - "upload_to_album_body": "Đáģ‘i váģ›i nháģ¯ng ngưáģi dÚng không sáģ­ dáģĨng tính năng táēŖi lÃĒn tháģ§ công, giáģ Ä‘Ãĸy báēĄn cÃŗ tháģƒ cháģn thÃĒm áēŖnh táģĢ thiáēŋt báģ‹ tráģąc tiáēŋp vào album ngay khi táēŖi lÃĒn, thay vÃŦ pháēŖi táēŖi lÃĒn ráģ“i máģ›i thÃĒm vào album sau như trưáģ›c đÃĸy.", - "upload_to_album_title": "TáēŖi lÃĒn tráģąc tiáēŋp vào album", + "upload_success": "TáēŖi lÃĒn thành công, làm máģ›i trang đáģƒ xem cÃĄc táģ‡p váģĢa táēŖi lÃĒn.", "upload_to_immich": "TáēŖi lÃĒn Immich ({count})", "uploading": "Đang táēŖi lÃĒn", "uploading_media": "Đang táēŖi lÃĒn phÆ°ÆĄng tiáģ‡n", - "uploads": "TáēŖi lÃĒn", - "uploads_count": "{count, plural, one {# táēŖi lÃĒn} other {# táēŖi lÃĒn}}", "url": "URL", "usage": "Sáģ­ dáģĨng", - "use_biometric": "DÚng vÃĸn tay", + "use_biometric": "DÚng sinh tráē¯c háģc", "use_browser_locale": "DÚng ngôn ngáģ¯ trÃŦnh duyáģ‡t", "use_browser_locale_description": "Đáģ‹nh dáēĄng ngày, tháģi gian và sáģ‘ dáģąa trÃĒn ngôn ngáģ¯ trÃŦnh duyáģ‡t", "use_current_connection": "DÚng káēŋt náģ‘i hiáģ‡n táēĄi", "use_custom_date_range": "Cháģn khoáēŖng tháģi gian tÚy cháģ‰nh", - "use_template": "DÚng máēĢu cÃŗ sáēĩn", "user": "Ngưáģi dÚng", "user_has_been_deleted": "Ngưáģi dÚng này Ä‘ÃŖ báģ‹ xÃŗa.", "user_id": "ID ngưáģi dÚng", - "user_liked": "{user} Ä‘ÃŖ thích {type, select, photo {áēŖnh này} video {video này} asset {tài nguyÃĒn này} other {nÃŗ}}", + "user_liked": "{user} Ä‘ÃŖ thích {type, select, photo {áēŖnh này} video {video này} asset {táģ‡p này} other {nÃŗ}}", "user_pin_code_settings": "MÃŖ PIN", "user_pin_code_settings_description": "QuáēŖn lÃŊ mÃŖ PIN cáģ§a báēĄn", "user_privacy": "Quyáģn riÃĒng tư ngưáģi dÚng", "user_purchase_settings": "Mua", "user_purchase_settings_description": "QuáēŖn lÃŊ Ä‘ÆĄn mua cáģ§a báēĄn", + "user_role_set": "Cháģn {user} làm {role}", "user_usage_detail": "Chi tiáēŋt sáģ­ dáģĨng cáģ§a ngưáģi dÚng", "user_usage_stats": "Tháģ‘ng kÃĒ", "user_usage_stats_description": "Xem tháģ‘ng kÃĒ sáģ­ dáģĨng cáģ§a tài khoáēŖn", @@ -2226,49 +2355,49 @@ "utilities": "Tiáģ‡n ích", "validate": "XÃĄc minh", "validate_endpoint_error": "Vui lÃ˛ng nháē­p URL háģŖp láģ‡", + "validation_error": "Láģ—i xÃĄc tháģąc", "variables": "CÃĄc tham sáģ‘", "version": "PhiÃĒn báēŖn", "version_announcement_closing": "BáēĄn cáģ§a báēĄn, Alex", - "version_announcement_message": "Chào báēĄn! Immich Ä‘ÃŖ ra máē¯t phiÃĒn báēŖn máģ›i. Vui lÃ˛ng dành tháģi gian đáģƒ xem ghi chÃē phÃĄt hành đáģƒ Ä‘áēŖm báēŖo cáēĨu hÃŦnh cáģ§a báēĄn đưáģŖc cáē­p nháē­t nháēąm trÃĄnh cáēĨu hÃŦnh sai, đáēˇc biáģ‡t náēŋu báēĄn sáģ­ dáģĨng WatchTower hoáēˇc báēĨt káģŗ cÆĄ cháēŋ táģą Ä‘áģ™ng cáē­p nháē­t Immich nào.", + "version_announcement_message": "Chào báēĄn! Máģ™t phiÃĒn báēŖn máģ›i cáģ§a Immich Ä‘ÃŖ ra máē¯t. Vui lÃ˛ng dành tháģi gian đáģƒ xem danh sÃĄch thay đáģ•i đáģƒ Ä‘áēŖm báēŖo cáēĨu hÃŦnh cáģ§a báēĄn đưáģŖc cáē­p nháē­t đáģƒ trÃĄnh láģ—i cáēĨu hÃŦnh sai, đáēˇc biáģ‡t náēŋu báēĄn sáģ­ dáģĨng WatchTower hoáēˇc báēĨt káģŗ cÆĄ cháēŋ táģą Ä‘áģ™ng cáē­p nháē­t Immich cáģ§a báēĄn.", "version_history": "Láģ‹ch sáģ­ phiÃĒn báēŖn", "version_history_item": "ÄÃŖ cài đáēˇt {version} vào {date}", "video": "Video", "video_hover_setting": "Xem trưáģ›c video khi di chuáģ™t lÃĒn", - "video_hover_setting_description": "PhÃĄt đoáēĄn video xem trưáģ›c khi di chuáģ™t qua máģĨc. Náēŋu táē¯t, váēĢn cÃŗ tháģƒ báē¯t đáē§u phÃĄt video báēąng cÃĄch di chuáģ™t qua biáģƒu tưáģŖng phÃĄt.", - "video_quality": "CháēĨt lưáģŖng video", + "video_hover_setting_description": "PhÃĄt đoáēĄn video xem trưáģ›c khi di chuáģ™t qua máģĨc. Ngay cáēŖ khi táē¯t cháģŠc năng này, váēĢn cÃŗ tháģƒ báē¯t đáē§u phÃĄt video báēąng cÃĄch di chuáģ™t qua biáģƒu tưáģŖng phÃĄt.", "videos": "Video", "videos_count": "{count, plural, one {# Video} other {# Video}}", + "videos_only": "Cháģ‰ video", "view": "Xem", "view_album": "Xem Album", "view_all": "Xem táēĨt cáēŖ", "view_all_users": "Xem táēĨt cáēŖ ngưáģi dÚng", - "view_asset_owners": "Xem cháģ§ sáģŸ háģ¯u tài nguyÃĒn", + "view_asset_owners": "Xem cháģ§ sáģŸ háģ¯u táģ‡p", "view_details": "Xem thông tin chi tiáēŋt", - "view_in_timeline": "Xem trÃĒn dÃ˛ng tháģi gian", + "view_in_timeline": "Xem trong dÃ˛ng tháģi gian", "view_link": "Xem liÃĒn káēŋt", + "view_links": "Xem cÃĄc liÃĒn káēŋt", "view_name": "Giao diáģ‡n", - "view_next_asset": "Xem tài nguyÃĒn tiáēŋp theo", - "view_previous_asset": "Xem tài nguyÃĒn trưáģ›c Ä‘Ãŗ", + "view_next_asset": "Xem táģ‡p tiáēŋp theo", + "view_previous_asset": "Xem táģ‡p trưáģ›c Ä‘Ãŗ", "view_qr_code": "Xem mÃŖ QR", "view_similar_photos": "Xem áēŖnh tÆ°ÆĄng táģą", "view_stack": "Xem nhÃŗm áēŖnh", + "view_user": "Xem Ngưáģi dÚng", "viewer_remove_from_stack": "XÃŗa kháģi nhÃŗm", - "visibility": "KháēŖ năng hiáģƒn tháģ‹", + "viewer_stack_use_as_main_asset": "Đáēˇt làm áēŖnh náģ•i báē­t", + "viewer_unstack": "Háģ§y xáēŋp nhÃŗm", "visibility_changed": "ÄÃŖ thay đáģ•i tráēĄng thÃĄi hiáģƒn tháģ‹ cho {count, plural, one {# ngưáģi} other {# ngưáģi}}", "visual": "Tráģąc quan", + "visual_builder": "TáēĄo tráģąc quan", "waiting": "Đang cháģ", "waiting_count": "Đang cháģ: {count}", "warning": "CáēŖnh bÃĄo", "week": "Tuáē§n", "welcome": "Chào máģĢng", "welcome_to_immich": "Chào máģĢng đáēŋn váģ›i Immich", - "whats_new": "Nháģ¯ng điáģu máģ›i", - "whats_new_settings_subtitle": "Xem Immich cÃŗ gÃŦ máģ›i", - "whats_new_version": "PhiÃĒn báēŖn {version}", - "when": "Khi nào", "width": "Chiáģu ráģ™ng", "wifi_name": "TÃĒn Wi-Fi", - "workflow": "Workflow", "workflow_delete_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n xÃŗa luáģ“ng công viáģ‡c này?", "workflow_deleted": "ÄÃŖ xÃŗa luáģ“ng công viáģ‡c", "workflow_description": "Mô táēŖ luáģ“ng công viáģ‡c", @@ -2278,17 +2407,17 @@ "workflow_name": "TÃĒn luáģ“ng công viáģ‡c", "workflow_navigation_prompt": "BáēĄn cÃŗ cháē¯c muáģ‘n ráģi đi mà không lưu láēĄi cÃĄc thay đáģ•i cáģ§a mÃŦnh?", "workflow_summary": "Mô táēŖ luáģ“ng công viáģ‡c", - "workflow_templates": "MáēĢu workflow cÃŗ sáēĩn", "workflow_update_success": "ÄÃŖ cáē­p nháē­t luáģ“ng công viáģ‡c thành công", + "workflow_updated": "ÄÃŖ cáē­p nháē­t Luáģ“ng công viáģ‡c", "workflows": "Luáģ“ng công viáģ‡c", - "workflows_help_text": "Luáģ“ng công viáģ‡c táģą Ä‘áģ™ng hÃŗa cÃĄc hành đáģ™ng trÃĒn tài nguyÃĒn cáģ§a báēĄn dáģąa trÃĒn cÃĄc trÃŦnh kích hoáēĄt và báģ™ láģc", + "workflows_help_text": "Luáģ“ng công viáģ‡c táģą Ä‘áģ™ng hÃŗa cÃĄc hành đáģ™ng trÃĒn táē­p tin cáģ§a báēĄn dáģąa trÃĒn cÃĄc trÃŦnh kích hoáēĄt và báģ™ láģc", "wrong_pin_code": "MÃŖ PIN không đÃēng", - "x_of_total": "{x}/{total}", "year": "Năm", "years_ago": "{years, plural, one {# năm} other {# năm}} trưáģ›c", "yes": "Đáģ“ng ÃŊ", "you_dont_have_any_shared_links": "BáēĄn không cÃŗ liÃĒn káēŋt chia sáēģ nào", "your_wifi_name": "TÃĒn Wi-Fi cáģ§a báēĄn", - "zero_to_clear_rating": "nháēĨn 0 đáģƒ xÃŗa xáēŋp háēĄng tài nguyÃĒn", - "zoom_image": "Thu phÃŗng áēŖnh" + "zero_to_clear_rating": "nháēĨn 0 đáģƒ xÃŗa Ä‘ÃĄnh giÃĄ áēŖnh", + "zoom_image": "Thu phÃŗng áēŖnh", + "zoom_to_bounds": "Thu phÃŗng váģĢa khung" } diff --git a/i18n/yue_Hant.json b/i18n/yue_Hant.json index 0d79c8a89a..e0cf403712 100644 --- a/i18n/yue_Hant.json +++ b/i18n/yue_Hant.json @@ -7,37 +7,45 @@ "action_common_update": "更新", "action_description": "é‡å°į¯Šé¸åžŒå˜…čŗ‡æēåŸˇčĄŒå˜…一įŗģ列動äŊœ", "actions": "動äŊœ", - "active": "č™•į†įˇŠ", - "active_count": "č™•į†įˇŠīŧš{count}", + "active": "æ­Ŗåœ¨č™•į†", + "active_count": "æ­Ŗåœ¨č™•į†īŧš{count}", "activity": "æ´ģ動", + "activity_changed": "æ´ģ動厞{enabled, select, true {啟動} other {停æ­ĸ}}", "add": "加", "add_a_description": "加一個描čŋ°", "add_a_location": "加一個äŊįŊŽ", "add_a_name": "加一個姓名", "add_a_title": "åŠ ä¸€å€‹æ¨™éĄŒ", "add_action": "加動äŊœ", + "add_action_description": "éģžæ“ŠäģĨ加動äŊœ", "add_assets": "åŠ čŗ‡æē", "add_birthday": "åŠ ä¸€å€‹į”Ÿæ—Ĩ", "add_endpoint": "加į̝éģž", "add_exclusion_pattern": "加å…Ĩį¯Šé¸æĸäģļ", + "add_filter": "加過æŋžå™¨", + "add_filter_description": "éģžæ“ŠäģĨ加一個過æŋžæĸäģļ", "add_location": "加äŊįŊŽ", + "add_more_users": "åŠ æ›´å¤šį”¨æˆļ", "add_partner": "加äŧ™äŧ´", + "add_path": "åŠ čˇ¯åž‘", "add_photos": "加多åŧĩᛏቇ", - "add_step": "加æ­Ĩ驟", "add_tag": "åŠ æ¨™įą¤", - "add_to": "加到â€Ļ", - "add_to_album": "åŠ åˆ°į›¸į°ŋ", - "add_to_album_bottom_sheet_added": "厞加到{album}", - "add_to_album_bottom_sheet_already_exists": "厞įļ“å–ē {album} 中", - "add_to_album_bottom_sheet_some_local_assets": "åŠ å””åˆ°ä¸€å•˛æœŦæŠŸčŗ‡æēåˆ°į›¸į°ŋ", - "add_to_albums": "åŠ åˆ°į›¸į°ŋ", - "add_to_albums_count": "加 ({count}) å€‹é …į›Žåˆ°į›¸į°ŋ", - "add_to_bottom_bar": "加到", + "add_to": "åŠ č‡ŗâ€Ļ", + "add_to_album": "åŠ č‡ŗį›¸į°ŋ", + "add_to_album_bottom_sheet_added": "åˇ˛åŠ č‡ŗ{album}", + "add_to_album_bottom_sheet_already_exists": "厞圍 {album} 中", + "add_to_album_bottom_sheet_some_local_assets": "į„Ąæŗ•åŠ éƒ¨åˆ†æœŦæŠŸčŗ‡æēč‡ŗį›¸į°ŋ", + "add_to_album_toggle": "選擇{album}ᛏį°ŋ", + "add_to_albums": "åŠ č‡ŗį›¸į°ŋ", + "add_to_albums_count": "加 ({count}) å€‹é …į›Žč‡ŗį›¸į°ŋ", + "add_to_bottom_bar": "åŠ č‡ŗ", + "add_to_shared_album": "åŠ č‡ŗå…ąäēĢᛏį°ŋ", "add_upload_to_stack": "新åĸžä¸Šå‚ŗåˆ°å †į–Š", "add_url": "加įļ˛å€", - "added_to_archive": "åˇ˛åŠ å’—å…Ĩ封存", - "added_to_favorites": "åˇ˛åŠ å’—åˆ°æœ€æ„›", - "added_to_favorites_count": "åˇ˛åŠ å’—{count, number} å€‹é …į›Žåˆ°æœ€æ„›", + "add_workflow_step": "åĸžåŠ åˇĨäŊœæ­Ĩ驟", + "added_to_archive": "厞æˇģč‡ŗå°å­˜", + "added_to_favorites": "åˇ˛åŠ č‡ŗæœ€æ„›", + "added_to_favorites_count": "厞加{count, number} å€‹é …į›Žč‡ŗæœ€æ„›", "admin": { "add_exclusion_pattern_description": "æˇģåŠ æŽ’é™¤æ¨Ąåŧã€‚支持äŊŋᔍ *、** 同? 嘅čŦį”¨å­—å…ƒæ¯”å° (Globbing)。䞋åĻ‚īŧŒäŊŋį”¨ã€Œ**/Raw/**」åŋŊį•ĨäģģäŊ•rawæĒ”æĄˆīŧŒæˆ–č€…åˇ˛ã€Œ**/*.tif」åŋŊį•Ĩ全部äģĨ「.tif」įĩå°žįš„æĒ”æĄˆīŧ›åĻ‚æžœčρåŋŊį•Ĩį‰šåŽšå˜…įĩ•å°čˇ¯åž‘čĢ‹äŊŋį”¨ã€Œ/path/to/ignore/**」。", "admin_user": "įŽĄį†å“Ąį”¨æˆļ", @@ -45,7 +53,7 @@ "authentication_settings": "éŠ—č­‰č¨­åŽš", "authentication_settings_description": "įŽĄį†å¯†įĸŧ、OAuth 同å…ļäģ–éŠ—č­‰č¨­åŽš", "authentication_settings_disable_all": "äŊ įĸē厚äŊ čĻåœį”¨æ‰€æœ‰į™ģå…Ĩ嘅斚åŧå—ŽīŧŸäŊ æœƒåŽŒå…¨å†‡čžĻæŗ•į™ģå…Ĩ。", - "authentication_settings_reenable": "åĻ‚æžœäŊ čĻé‡æ–°å•Ÿį”¨å˜…čŠąīŧŒčĢ‹äŊŋᔍ äŧ翜å™¨æŒ‡äģ¤ã€‚", + "authentication_settings_reenable": "åĻ‚æžœäŊ čĻé‡æ–°å•Ÿį”¨å˜…čŠąīŧŒčĢ‹äŊŋį”¨å˜…äŧ翜å™¨æŒ‡äģ¤ã€‚", "background_task_job": "čƒŒæ™¯æ“äŊœ", "backup_database": "åģēįĢ‹čŗ‡æ–™åēĢ備äģŊ", "backup_database_enable_description": "å•Ÿį”¨čŗ‡æ–™åēĢ備äģŊ", @@ -53,8 +61,8 @@ "backup_onboarding_1_description": "äŋ‚雲įĢ¯æˆ–č€…å…ļäģ–å¯ĻéĢ”åœ°æ–šåģēįĢ‹å˜…å‚™äģŊ副æœŦ。", "backup_onboarding_2_description": "å„˛å­˜äŋ‚å””åŒčŖįŊŽå˜…æœŦ地副æœŦ。包åĢä¸ģčĻå˜…æĒ”æĄˆåŒåŸ‹å–ēæœŦ抟嘅備äģŊ。", "backup_onboarding_3_description": "čŗ‡æ–™åŒ…åĢ原始文äģļīŧŒį¸Ŋå…ąå‚™äģŊ嘅æŦĄæ•¸ã€‚å‘ĸ個包æ‹Ŧ1äģŊį•°åœ°å˜…å‚™äģŊ同埋2äģŊæœŦ抟副æœŦ。", - "backup_onboarding_description": "åģēč­°äŊ äŊŋᔍ 3-2-1備äģŊ原則 嚟äŋč­ˇäŊ å˜…數據。äŊ æ‡‰čО備äģŊ厞įļ“ä¸Šå‚ŗå˜…į›¸īŧŒåŊąį‰‡åŒåŸ‹ Immich 嘅數據åēĢäģ¤åˆ°äŊ å˜…čŗ‡æ–™æ›´åŠ čĸĢäŋéšœã€‚", - "backup_onboarding_footer": "有關å…ļäģ–Immich備äģŊå˜…čŗ‡æ–™īŧŒčĢ‹åƒč€ƒ čĒĒæ˜Žæ–‡äģļ。", + "backup_onboarding_description": "我哋åģēč­°äŊ äŊŋį”¨å˜…3-2-1備äģŊ嚟äŋéšœäŊ å˜…čŗ‡æ–™ã€‚į‚ē咗可äģĨ全斚äŊåœ°å‚™äģŊäŊ å˜…čŗ‡æ–™īŧŒäŊ æ‡‰čОäŋį•™åˇ˛įļ“ä¸Šå‚ŗå˜…į›¸/åŊąį‰‡å˜…副æœŦīŧŒåŒåŸ‹Immichå˜…čŗ‡æ–™åēĢīŧŒã€‚", + "backup_onboarding_footer": "有關å…ļäģ–Immich備äģŊå˜…čŗ‡æ–™īŧŒčĢ‹åƒč€ƒã€‚", "backup_onboarding_parts_title": "一個3-2-1備äģŊ包æ‹Ŧīŧš", "backup_onboarding_title": "備äģŊ", "backup_settings": "čŗ‡æ–™åēĢ備äģŊå˜…č¨­åŽš", @@ -73,14 +81,12 @@ "cron_expression_description": "äŊŋᔍ Cron æ ŧåŧåšŸč¨­åŽšæŽƒæå˜…é–“éš”ã€‚æ›´å¤šå˜…čŗ‡č¨ŠčĢ‹æŸĨ閱 Crontab Guru", "cron_expression_presets": "Cron 表達åŧé č¨­é¸é …", "disable_login": "停æ­ĸäŊŋᔍį™ģå…Ĩ", - "download_csv": "下čŧ‰CSVæĒ”æĄˆ", "duplicate_detection_job_description": "é‹į”¨æŠŸæĸ°å­¸įŋ’īŧŒäžé æ™ē慧搜尋嚟åĩæ¸Ŧᛏäŧŧå˜…į›¸į‰‡", "exclusion_pattern_description": "äŊ å¯äģĨį”¨æŽ’é™¤æ¨Ąåŧå–ē掃描åĒ’éĢ”åēĢæ™‚åŋŊį•Ĩį‰šåŽšå˜…æĒ”æĄˆåŒčŗ‡æ–™å¤žã€‚å–ēæŸå•˛čŗ‡æ–™å¤žæœ‰äŊ å””æƒŗåŠ å…Ĩ嘅æĒ”æĄˆīŧˆäž‹åĻ‚RAWᛏīŧ‰å˜…æƒ…æŗä¸‹å‘ĸ個功čƒŊ將會åĨŊæœ‰į”¨ã€‚", "export_config_as_json_description": "å°‡į›Žå‰å˜…įŗģįĩąč¨­åŽšä¸‹čŧ‰į‚ē JSON æĒ”æĄˆ", "external_libraries_page_description": "įŽĄį†å¤–éƒ¨åĒ’éĢ”åēĢ嘅頁éĸ", "face_detection": "äēēéĸåĩæ¸Ŧ", "face_detection_description": "äŊŋį”¨æŠŸå™¨å­¸įŋ’åĩæ¸Ŧé …į›Žä¸­å˜…č‡‰å­”ã€‚å°æ–ŧåŊąį‰‡īŧŒåĒäŋ‚æœƒåˆ†æžį¸Žåœ–ã€‚ã€Œé‡æ–°æ•´į†ã€æœƒé‡æ–°č™•į†æ‰€æœ‰å˜…é …į›Žīŧ›ã€Œé‡č¨­ã€å°ąæœƒéĄå¤–æ¸…é™¤į›Žå‰å˜…č‡‰å­”čŗ‡æ–™īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€æœƒå°‡å°šæœĒč™•į†å˜…é …į›ŽåŠ å…Ĩåēåˆ—ã€‚åŽŒæˆã€Œč‡‰å­”åĩæ¸Ŧ」垌īŧŒåĩæ¸Ŧåˆ°å˜…č‡‰å­”å°‡æœƒåŠ å…Ĩã€Œč‡‰å­”čž¨č­˜ã€æŽ’į¨‹īŧŒä¸Ļæ­¸éĄžåˆ°č€ŒåŽļæˆ–č€…æ–°å˜…äēēį‰Šįž¤įĩ„。", - "facial_recognition_job_description": "將åĩæ¸Ŧåˆ°å˜…č‡‰å­”æ­¸éĄžį‚ēäēēį‰Šã€‚æ­¤æ­ĨéŠŸæœƒåœ¨č‡‰å­”åĩæ¸ŦåŽŒæˆåžŒåŸˇčĄŒã€‚ã€Œé‡č¨­ã€æœƒé‡æ–°å°æ‰€æœ‰å˜…č‡‰å­”é€˛čĄŒåˆ†įž¤īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€å°ąæœƒå°‡æœĒ指洞äēēį‰Šå˜…č‡‰å­”åŠ å…Ĩåēåˆ—。", "failed_job_command": "åŸˇčĄŒ{job}äģģ務嘅{command}指äģ¤å¤ąæ•—", "force_delete_user_warning": "č­Ļ告īŧšå‘ĸ個會įĢ‹åŗåˆĒ除ᔍæˆļ同埋äŊĸ所有嘅æĒ”æĄˆã€‚å‘ĸ個äŋ‚į„Ąæŗ•æ’¤éŠˇå˜…å‹•äŊœīŧŒč€Œä¸”åˆĒ除嘅æĒ”æĄˆå°‡å†‡čžĻæŗ•åžŠåŽŸã€‚", "image_format": "æ ŧåŧ", @@ -93,9 +99,7 @@ "image_prefer_embedded_preview": "偏向åĩŒå…Ĩ預čĻŊ", "image_prefer_embedded_preview_setting_description": "å–ēå¯į”¨å˜…æ™‚å€™å°‡ RAW į›¸į‰‡ä¸­įš„å…§åĩŒé čĻŊäŊœį‚ēåŊąåƒč™•į†å˜…čŧ¸å…Ĩ來æēã€‚é›–į„ļå‘ĸå€‹č¨­åŽšå¯äģĨäģ¤åˆ°éƒ¨åˆ†į›¸į‰‡å˜…色åŊŠæ›´åŠ æē–įĸēīŧŒäŊ†é čĻŊ品čŗĒ取æąēæ–ŧį›¸æŠŸīŧŒä¸”åŊąåƒå¯čƒŊ會å‡ēįžčŧƒå¤šåŖ“į¸Žį‘•į–ĩ。", "image_prefer_wide_gamut": "傞向åģŖč‰˛åŸŸ", - "image_prefer_wide_gamut_setting_description": "äŊŋᔍ Display P3 čŖŊäŊœį¸Žåœ–īŧšå¯äģĨ更åĨŊ地äŋį•™åģŖč‰˛åŸŸåŊąåƒå˜…鎎蹔åēĻīŧŒäŊ†äŋ‚å–ēčˆŠčŖįŊŽåŒčˆŠį‰ˆį€čĻŊ器上īŧŒåŊąåƒå‘ˆįžå˜…效果可čƒŊ會有所唔同。sRGB åŊąåƒæœƒäŋį•™į‚ē sRGBīŧŒäģĨéŋå…č‰˛åŊŠåį§ģ。", "image_preview_description": "䏭ᭉå°ēå¯¸å˜…åœ–į‰‡īŧŒį”¨åšŸæĒĸčĻ–å–Žä¸€åŊąåƒåŒåŸ‹æŠŸå™¨å­¸įŋ’", - "image_preview_quality_description": "åžž1-100揀預čĻŊ嘅čŗĒį´ ã€‚æ•¸å€ŧčļŠé̘čļŠå“čŗĒåĨŊīŧŒäŊ†æœƒį”ĸį”Ÿčŧƒå¤§å˜…æĒ”æĄˆåŒåŊąéŸŋapp嘅反應速åēĻã€‚č¨­åŽšäŊŽå˜…數å€ŧ有抟會åŊąéŸŋ抟器學įŋ’嘅品čŗĒ。", "image_preview_title": "預čĻŊč¨­åŽš", "image_progressive": "逐æ­Ĩ", "image_progressive_description": "對 JPEG åœ–į‰‡æŽĄį”¨æŧ¸é€˛åŧįˇ¨įĸŧīŧŒäģĨå¯Ļįžæŧ¸é€˛åŧčŧ‰å…ĨéĄ¯į¤ē。å‘ĸ個選項唔會åŊąéŸŋ WebP åœ–į‰‡ã€‚", @@ -108,31 +112,16 @@ "image_thumbnail_quality_description": "į¸Žåœ–å“čŗĒīŧˆ1-100īŧ‰ã€‚數å€ŧčļŠé̘į•ĢčŗĒčļŠåĨŊīŧŒäŊ†äŋ‚æĒ”æĄˆæ›´åŠ å¤§īŧŒč€Œä¸”可čƒŊ降äŊŽæ‡‰į”¨å˜…回應速åēĻ。", "image_thumbnail_title": "į¸Žåœ–č¨­åŽš", "import_config_from_json_description": "é€éŽä¸Šå‚ŗ JSON č¨­åŽšåšŸå°Žå…Ĩįŗģįĩąč¨­åޚ", - "integrity_checks_checksum_files": "æ ĄéŠ—æĒ”æĄˆ", - "integrity_checks_checksum_files_description": "č¨­åŽšæ ĄéŠ—åŒæĒĸæŸĨ", - "integrity_checks_checksum_files_enable_description": "å•Ÿį”¨æ ĄéŠ—åŒåŸ‹æĒĸæŸĨ", - "integrity_checks_checksum_files_percentage_limit": "į™žåˆ†æ¯”é™åˆļ", - "integrity_checks_checksum_files_percentage_limit_description": "č¨­åŽšæ¯å€‹æĒĸæŸĨæĒĸéŠ—æ‰€åŸˇčĄŒīŧŒåžž 0.01 到 1 äš‹é–“å˜…æœ€å¤§į™žåˆ†æ¯”ã€‚", - "integrity_checks_checksum_files_time_limit": "時間限åˆļ", - "integrity_checks_checksum_files_time_limit_description": "č¨­åŽšæ¯å€‹æĒĸæŸĨ中īŧŒæĒĸæŸĨå˜…æœ€é•ˇåŸˇčĄŒæ™‚é–“ã€‚īŧˆæ¯Ģį§’īŧ‰", - "integrity_checks_missing_files": "éēå¤ąå˜…æĒ”æĄˆ", - "integrity_checks_missing_files_description": "č¨­åŽšåŸˇčĄŒé ģįŽ‡åŒåŸ‹å•Ÿį”¨æˆ–č€…åœį”¨éēå¤ąæĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_missing_files_enable_description": "å•Ÿį”¨éēå¤ąæĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_settings": "厌整性æĒĸæŸĨ", - "integrity_checks_settings_description": "įŽĄį†åŽŒæ•´æ€§æĒĸæŸĨ嘅間隔", - "integrity_checks_untracked_files": "æœĒčŋŊčš¤å˜…æĒ”æĄˆ", - "integrity_checks_untracked_files_description": "č¨­åŽšé ģįŽ‡īŧŒä¸Ļä¸”å•Ÿį”¨æˆ–č€…åœį”¨æœĒčŋŊčš¤å˜…æĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_untracked_files_enable_description": "å•Ÿį”¨æœĒčŋŊ蚤æĒ”æĄˆå˜…æĒĸæŸĨ", "job_concurrency": "{job} ä¸ĻčĄŒæ•¸", - "job_created": "åģēį̋咗äģģ務", + "job_created": "厞åģēįĢ‹äģģ務", "job_not_concurrency_safe": "å‘ĸ個äģģ務唔支援ä¸ĻčĄŒåŸˇčĄŒã€‚", "job_settings": "äģģå‹™č¨­åŽš", "job_settings_description": "įŽĄį†äģģ務ä¸ĻčĄŒæ•¸", "jobs_delayed": "{jobCount, plural, other {# 個äģģå‹™æŽ¨é˛}}", "jobs_failed": "{jobCount, plural, other {# 個äģģå‹™å¤ąæ•—}}", "jobs_over_time": "äģģ務數量čļ¨å‹ĸ", - "library_created": "åģēį̋咗åĒ’éĢ”åēĢīŧš{library}", - "library_deleted": "åĒ’éĢ”åēĢåˆĒ除咗", + "library_created": "厞åģēįĢ‹åĒ’éĢ”åēĢīŧš{library}", + "library_deleted": "åĒ’éĢ”åēĢ厞åˆĒ除", "library_details": "åĒ’éĢ”åēĢčŠŗį´°čŗ‡č¨Š", "library_folder_description": "指厚čĻå°Žå…Ĩ嘅文äģļ夞。įŗģįĩąå°‡æœƒæŽƒæå‘ĸ個文äģļ夞īŧˆåŒ…åĢ子文äģļ夞īŧ‰ä¸­æ‰€æœ‰å˜…į›¸åŒåŸ‹åŊąį‰‡ã€‚", "library_remove_exclusion_pattern_prompt": "äŋ‚å””äŋ‚įĸē厚čρį§ģ除å‘ĸå€‹æŽ’é™¤æ¨ĄåŧīŧŸ", @@ -143,7 +132,7 @@ "library_settings": "外部åĒ’éĢ”åēĢ", "library_settings_description": "įŽĄį†å¤–éƒ¨åĒ’éĢ”åēĢč¨­åŽš", "library_tasks_description": "掃描外部åĒ’éĢ”åēĢäģĨæĩå‡ē新åĸžåŒåŸ‹čŽŠæ›´å˜…æ–‡äģļ", - "library_updated": "更新咗åĒ’éĢ”åēĢ", + "library_updated": "åˇ˛æ›´æ–°åĒ’éĢ”åēĢ", "library_watching_enable_description": "ᛪčĻ–å¤–éƒ¨åĒ’éĢ”åēĢ嘅æĒ”æĄˆčŽŠåŒ–", "library_watching_settings": "åĒ’éĢ”åēĢį›ŖæŽ§ [å¯Ļ銗性]", "library_watching_settings_description": "č‡Ēå‹•į›ŖæŽ§æĒ”æĄˆå˜…čŽŠåŒ–", @@ -163,34 +152,6 @@ "machine_learning_duplicate_detection_enabled": "å•Ÿį”¨é‡č¤‡é …į›Žåĩæ¸Ŧ", "machine_learning_duplicate_detection_enabled_description": "å°ąįŽ—åœį”¨å‘ĸ個功čƒŊīŧŒåŽŒå…¨į›¸åŒå˜…é‡č¤‡é …į›Žäģį„ļ會čĸĢåˆĒ除。", "machine_learning_duplicate_detection_setting_description": "åˆŠį”¨ CLIP 向量比對尋扞可čƒŊå˜…é‡č¤‡é …į›Ž", - "machine_learning_enabled": "å•“į”¨æŠŸå™¨å­¸įŋ’", - "machine_learning_enabled_description": "åĻ‚æžœåœį”¨å‘ĸ個功čƒŊīŧŒį„ĄčĢ–äģĨä¸‹č¨­åŽšåĻ‚äŊ•所有抟器學įŋ’嘅功čƒŊéƒŊå°‡æœƒåœį”¨ã€‚", - "machine_learning_facial_recognition": "äēē臉識åˆĨ", - "machine_learning_facial_recognition_description": "åĩæ¸Ŧã€čž¨čĒäģĨåŠé›†åˆį›¸į‰‡ä¸­å˜…äēē臉", - "machine_learning_facial_recognition_model": "äēē臉識åˆĨæ¨Ąåž‹", - "machine_learning_facial_recognition_model_description": "æ¨Ąåž‹æŒ‰å¤§å°į”ąå¤§č‡ŗį´°æŽ’åˆ—ã€‚å¤§å•˛å˜…æ¨Ąåž‹é‹čĄŒé€ŸåēĻčŧƒæ…ĸīŧŒäŊ”į”¨æ›´å¤šč¨˜æ†ļéĢ”īŧŒäŊ†æ•ˆæžœæœƒæ›´åĨŊ。čĢ‹æŗ¨æ„īŧŒæ¯ä¸€æŦĄæ›´æ›æ¨Ąåž‹åžŒéƒŊåŋ…須對所有åŊąåƒé‡æ–°åŸˇčĄŒäēē臉åĩæ¸Ŧ。", - "machine_learning_facial_recognition_setting": "啓ᔍäēē臉識åˆĨ", - "machine_learning_facial_recognition_setting_description": "åĻ‚æžœåœį”¨å‘ĸ個功čƒŊīŧŒåŊąåƒå””æœƒé€˛čĄŒäēēč‡‰čž¨č­˜įˇ¨įĸŧīŧŒäēĻéƒŊå””æœƒéĄ¯į¤ēå–ē「æŽĸį´ĸ」頁éĸįš„ã€Œäēēį‰Šã€éƒ¨åˆ†ã€‚", - "machine_learning_max_detection_distance": "最大åĩæ¸Ŧ距é›ĸ", - "machine_learning_max_detection_distance_description": "åˆ¤æ–ˇå…ŠåŧĩåŊąåƒæ˜¯åĻé‡č¤‡å˜…æœ€å¤§čˇé›ĸīŧŒį¯„圍į‚ē 0.001-0.1。數å€ŧčļŠé̘īŧŒåĩæ¸Ŧåˆ°é‡č¤‡å˜…åŊąåƒæœƒčļŠå¤šīŧŒäŊ†äēĻæœ‰å¯čƒŊå°Žč‡´čĒ¤å ąã€‚", - "machine_learning_max_recognition_distance": "æœ€å¤§č­˜åˆĨ距é›ĸ", - "machine_learning_max_recognition_distance_description": "åˆ¤æ–ˇå…Šå€‹éĸ孔是åĻåąŦæ–ŧ同一äēēå˜…æœ€å¤§čˇé›ĸīŧŒį¯„åœį”ą 0 到 2。降äŊŽå‘ĸ個數å€ŧ可äģĨéŋ免將兊個äēēčǤ判į‚ē同一äēēīŧŒč€ŒæéĢ˜æ­¤æ•¸å€ŧ可äģĨéŋ免將同一個äēēčǤ判į‚ē兊個äēē。čĢ‹æŗ¨æ„īŧŒåˆäŊĩ兊個äē翝”將一個äēēæ‹†åˆ†æˆå…Šå€‹äēēæ›´åŽšæ˜“īŧŒå› æ­¤åģēč­°į›Ąå¯čƒŊ選擇čŧƒäŊŽå˜…數å€ŧ。", - "machine_learning_min_detection_score": "最äŊŽåĩæ¸Ŧ分數", - "machine_learning_min_detection_score_description": "臉孔åĩæ¸Ŧ最äŊŽäŋĄåŋƒå˜…分數īŧŒį¯„圍äŋ‚ 0 到 1。äŊŽå•˛å˜…數å€ŧ會åĩæ¸Ŧåˆ°å¤šå•˛éĸ孔īŧŒäŊ†å¯čƒŊæœƒå””å•ąã€‚", - "machine_learning_min_recognized_faces": "最äŊŽå˜…éĸå­”čž¨č­˜æ•¸é‡", - "machine_learning_min_recognized_faces_description": "åģēįĢ‹æ–°äēēį‰Šéœ€čĻå˜…æœ€äŊŽåˇ˛čž¨č­˜éĸ孔數量。提é̘å‘ĸ個數å€ŧ會äģ¤éĸå­”čž¨č­˜æ›´į˛žįĸēīŧŒäŊ†åŒæ™‚æœƒå¤§å•˛æŠŸæœƒæŒ‡æ´žå””åˆ°éĸ孔äŋžäģģäŊ•äēēį‰Šã€‚", - "machine_learning_ocr": "æ–‡å­—čž¨č­˜īŧˆOCRīŧ‰", - "machine_learning_ocr_description": "į”¨æŠŸæĸ°å­¸įŋ’čž¨č­˜į›¸čŖéĸ嘅文字", - "machine_learning_ocr_enabled": "į”¨æ–‡å­—č­˜åˆĨīŧˆOCRīŧ‰", - "machine_learning_ocr_enabled_description": "åĻ‚æžœå””į”¨īŧŒį›¸å””æœƒé€˛čĄŒæ–‡å­—čž¨č­˜ã€‚", - "machine_learning_ocr_max_resolution": "æœ€å¤§č§ŖæžåēĻ", - "machine_learning_ocr_max_resolution_description": "č§ŖæžåēĻéĢ˜éŽæ­¤å€ŧ嘅預čĻŊį›¸į‰‡æœƒå–ēäŋæŒé•ˇå¯Ŧæ¯”äž‹å˜…æƒ…æŗä¸‹čĒŋæ•´å¤§į´°ã€‚æ•¸å€ŧčļŠé̘čē–įĸēīŧŒäŊ†äŋ‚č™•į†æ™‚é–“æœƒæ›´é•ˇīŧŒåŒåŸ‹æœƒį”¨æ›´å¤šč¨˜æ†ļéĢ”ã€‚", - "machine_learning_ocr_min_detection_score": "最äŊŽæĒĸæ¸Ŧ分數", - "machine_learning_ocr_min_detection_score_description": "文字åĩæ¸Ŧ嘅最äŊŽäŋĄåŋƒåˆ†æ•¸īŧŒį¯„圍äŋ‚ 0 臺 1。čŧƒäŊŽå˜…數å€ŧ會åĩæ¸Ŧåˆ°å¤šå•˛æ–‡å­—īŧŒäŊ†å¯čƒŊæœƒå””å•ąã€‚", - "machine_learning_ocr_min_recognition_score": "最äŊŽčž¨č­˜åˆ†æ•¸", - "machine_learning_ocr_min_score_recognition_description": "厞åĩæ¸Ŧ到文字åĩæ¸Ŧ嘅最äŊŽäŋĄåŋƒåˆ†æ•¸īŧŒį¯„圍äŋ‚ 0 臺 1。čŧƒäŊŽå˜…數å€ŧ會åĩæ¸Ŧåˆ°å¤šå•˛å­—īŧŒäŊ†å¯čƒŊæœƒå””å•ąã€‚", - "machine_learning_ocr_model": "OCR æ¨Ąåž‹", - "machine_learning_ocr_model_description": "äŧ翜å™¨å˜…æ¨Ąåž‹æ¯”čĄŒå‹•čŖįŊŽå˜…æ¨Ąåž‹æē–įĸē啞īŧŒäŊ†äŋ‚č™•į†æ™‚é–“é•ˇå•˛īŧŒåŒåŸ‹æœƒį”¨å¤šå•˛č¨˜æ†ļéĢ”ã€‚", "machine_learning_settings": "抟器學įŋ’設åޚ", "machine_learning_settings_description": "įŽĄį†æŠŸå™¨å­¸įŋ’嘅功čƒŊåŒåŸ‹č¨­åŽš", "machine_learning_smart_search": "æ™ēčƒŊ搜尋", @@ -198,29 +159,13 @@ "machine_learning_smart_search_enabled": "å•Ÿį”¨æ™ē慧搜尋", "machine_learning_smart_search_enabled_description": "åĻ‚æžœåœį”¨īŧŒį›¸į‰‡å””æœƒé€˛čĄŒæ™ēčƒŊæœå°‹įˇ¨įĸŧ。", "machine_learning_url_description": "抟器學įŋ’äŧ翜å™¨å˜… URL。åĻ‚æžœäŊ æäž›å¤šå€‹ URLīŧŒįŗģįĩ࿜ƒé€ä¸€å˜—čŠĻīŧŒį›´åˆ°æœ‰ä¸€å€‹æˆåŠŸå›žæ‡‰į‚ēæ­ĸīŧˆį”ąå‰åˆ°åžŒīŧ‰ã€‚æœĒ回應嘅äŧ翜å™¨å°‡čĸĢæšĢ時åŋŊį•ĨīŧŒį›´åˆ°äŊĸé‡æ–°ä¸Šįˇšį‚ēæ­ĸ。", - "maintenance_backup_management": "įŽĄį†å‚™äģŊ", "maintenance_delete_backup": "åˆĒ除備äģŊ", "maintenance_delete_backup_description": "æ­¤æĒ”æĄˆå°‡čĸĢæ°¸äš…åˆĒ除。", "maintenance_delete_error": "åˆĒ除備äģŊå¤ąæ•—ã€‚", - "maintenance_integrity_check": "æĒĸæŸĨ", - "maintenance_integrity_check_all": "全部æĒĸæŸĨ", - "maintenance_integrity_checksum_mismatch": "æĒĸæŸĨįĸŧ唔對", - "maintenance_integrity_checksum_mismatch_description": "æĒ”æĄˆå˜…į¸Ŋ和æĒĸæŸĨįĸŧåŒčŗ‡æ–™åēĢ嘅唔對。", - "maintenance_integrity_checksum_mismatch_job": "æĒĸæŸĨ有冇æĒĸæŸĨįĸŧ唔對", - "maintenance_integrity_checksum_mismatch_refresh_job": "åˆˇæ–°æĒĸæŸĨįĸŧå””å°å ąå‘Š", - "maintenance_integrity_missing_file": "æĩ唔到æĒ”æĄˆ", - "maintenance_integrity_missing_file_description": "å–ēčŗ‡æ–™åēĢčŋŊčš¤įˇŠīŧŒäŊ†å–ēæĒ”æĄˆįŗģįĩąå””čĻ‹å˜…æĒ”æĄˆã€‚", - "maintenance_integrity_missing_file_job": "æĒĸæŸĨ唔čĻ‹å˜…æĒ”æĄˆ", - "maintenance_integrity_missing_file_refresh_job": "åˆˇæ–°å””čĻ‹æĒ”æĄˆå ąå‘Š", - "maintenance_integrity_report": "åŽŒæ•´æ€§å ąå‘Š", - "maintenance_integrity_untracked_file": "æœĒčŋŊčš¤å˜…æĒ”æĄˆ", - "maintenance_integrity_untracked_file_description": "å–ē Immich į›ŽéŒ„čŖéĸīŧŒäŊ†å†‡äģģäŊ•į´€éŒ„å˜…æĒ”æĄˆã€‚", - "maintenance_integrity_untracked_file_job": "æĒĸæŸĨæœĒčŋŊčš¤å˜…æĒ”æĄˆ", - "maintenance_integrity_untracked_file_refresh_job": "åˆˇæ–°æœĒčŋŊ蚤æĒ”æĄˆå ąå‘Š", "maintenance_restore_backup": "還原備äģŊ", "maintenance_restore_backup_description": "Immich įš„čŗ‡æ–™å°‡æœƒčĸĢæ¸…除īŧŒä¸Ļ還原äŊ æ€å˜…å‚™äģŊ。å–ēįšŧį猿“äŊœå‰īŧŒįŗģįĩ࿜ƒå…ˆåģēįĢ‹į›Žå‰å˜…čŗ‡æ–™å‚™äģŊ。", "maintenance_restore_backup_different_version": "å‘ĸ個備äģŊäŋ‚į”ąä¸åŒį‰ˆæœŦ嘅 Immich 所åģēįĢ‹īŧ", - "maintenance_restore_backup_unknown_version": "įĸē厚唔到備äģŊį‰ˆæœŦ。", + "maintenance_restore_backup_unknown_version": "į„Ąæŗ•įĸē厚備äģŊį‰ˆæœŦ。", "maintenance_restore_database_backup": "é‚„åŽŸčŗ‡æ–™åēĢ備äģŊ", "maintenance_restore_database_backup_description": "äŊŋᔍ備äģŊ文äģļ將數據åēĢ還原到čŧƒæ—Šå˜…į‹€æ…‹", "maintenance_settings": "įļ­äŋŽ", @@ -240,7 +185,6 @@ "map_light_style": "æˇē色éĸ¨æ ŧ", "map_manage_reverse_geocoding_settings": "įŽĄį† é€†å‘åœ°į†įˇ¨įĸŧ å˜…č¨­åŽš", "map_reverse_geocoding": "åå‘åœ°į†įˇ¨įĸŧ", - "map_reverse_geocoding_enable_description": "į”¨åå‘åœ°į†įˇ¨įĸŧ", "map_reverse_geocoding_settings": "åå‘åœ°į†įˇ¨įĸŧč¨­åŽš", "map_settings": "地圖", "map_settings_description": "įŽĄį†åœ°åœ–č¨­åŽš", @@ -248,1402 +192,35 @@ "memory_cleanup_job": "回æ†ļæ¸…į†", "memory_generate_job": "į”ĸį”Ÿå›žæ†ļ", "metadata_extraction_job": "æŠŊ取元數據", - "metadata_extraction_job_description": "å–翝å€‹é …į›Žä¸­æŠŊå–å…ƒæ•¸æ“ščŗ‡č¨ŠīŧŒäž‹åĻ‚ GPSã€č‡‰å­”åŒåŸ‹č§ŖæžåēĻ", - "metadata_faces_import_setting": "å•Ÿį”¨éĸ孔匯å…Ĩ", - "metadata_faces_import_setting_description": "į”ąį›¸į‰‡ EXIF čŗ‡æ–™åŒé™„åąŦæĒ”æĄˆåŒ¯å…Ĩéĸ孔", - "metadata_settings": "čŠŽé‡‹čŗ‡æ–™č¨­åŽš", - "metadata_settings_description": "įŽĄį†čŠŽé‡‹čŗ‡æ–™č¨­åŽš", - "migration_job": "遡į§ģ", - "migration_job_description": "å°‡é …į›ŽåŒč‡‰å­”é čĻŊį¸Žåœ–éˇį§ģåˆ°æœ€æ–°å˜…čŗ‡æ–™å¤žįĩæ§‹", - "nightly_tasks_cluster_faces_setting_description": "čž¨č­˜æ–°åĩæ¸Ŧ到嘅éĸ孔", - "nightly_tasks_cluster_new_faces_setting": "åˆ†įž¤æ–°éĸ孔", - "nightly_tasks_database_cleanup_setting": "čŗ‡æ–™åēĢæ¸…ᐆäģģ務", - "nightly_tasks_database_cleanup_setting_description": "æ¸…é™¤čŗ‡æ–™åēĢčˆŠåŒéŽå’—æœŸå˜…čŗ‡æ–™", - "nightly_tasks_generate_memories_setting": "į”ĸį”Ÿå›žæ†ļ", - "nightly_tasks_generate_memories_setting_description": "į”ąé …į›ŽåģēįĢ‹æ–°å›žæ†ļ", - "nightly_tasks_missing_thumbnails_setting": "į”ĸį”Ÿå””čĻ‹å˜…é čĻŊį¸Žåœ–", - "nightly_tasks_missing_thumbnails_setting_description": "將唔čĻ‹å˜…é čĻŊį¸Žåœ–åŠ å…Ĩ到į”ĸį”ŸæŽ’į¨‹", - "nightly_tasks_settings": "夜間äģģå‹™č¨­åŽš", - "nightly_tasks_settings_description": "įŽĄį†å¤œé–“äģģ務", - "nightly_tasks_start_time_setting": "開始時間", - "nightly_tasks_start_time_setting_description": "äŧ翜å™¨é–‹å§‹åŸˇčĄŒå¤œé–“äģģ務嘅時間", - "nightly_tasks_sync_quota_usage_setting": "同æ­Ĩ限額äŊŋį”¨æƒ…æŗ", - "nightly_tasks_sync_quota_usage_setting_description": "æ šæ“šį›Žå‰å˜…äŊŋį”¨é‡æ›´æ–°äŊŋį”¨č€…å˜…å„˛å­˜é™éĄ", - "no_paths_added": "冇新åĸžå’—å˜…čˇ¯åž‘", - "no_pattern_added": "æœĒ有排除čĻå‰‡", - "note_apply_storage_label_previous_assets": "提į¤ēīŧšåĻ‚æžœæ‚¨æƒŗå°‡å„˛å­˜æ¨™įą¤åĨ—į”¨åˆ°ä¸Šå‚ŗå’—å˜…é …į›ŽīŧŒčĢ‹åŸˇčĄŒ", - "note_cannot_be_changed_later": "æŗ¨æ„īŧšå‘ĸå€‹č¨­åŽšäš‹åžŒæ”šå””åˆ°īŧ", - "notification_email_from_address": "寄äģļ地址", - "notification_email_from_address_description": "寄äģļ者é›ģéƒĩīŧŒåĨŊäŧŧīŧš\"Immich Photo Server \"。čĢ‹įĸēäŋäŋ‚您有æŦŠé™å¯„送到éƒĩäģļ嘅地址。", - "notification_email_host_description": "é›ģ子éƒĩäģļäŧ翜å™¨ä¸ģ抟īŧˆäž‹åĻ‚īŧšsmtp.immich.appīŧ‰", - "notification_email_ignore_certificate_errors": "åŋŊį•Ĩå””å•ąå˜…æ†‘č­‰", - "notification_email_ignore_certificate_errors_description": "åŋŊį•Ĩ TLS éŒ¯å˜…æ†‘č­‰éŠ—č­‰īŧˆå””åģēč­°īŧ‰", - "notification_email_password_description": "į”¨åšŸéŠ—č­‰é›ģéƒĩäŧ翜å™¨å˜…密įĸŧ", - "notification_email_port_description": "é›ģéƒĩäŧ翜å™¨å˜…逪æŽĨ埠īŧˆäž‹åĻ‚ 25、465 或 587īŧ‰", - "notification_email_secure": "SMTPS", - "notification_email_secure_description": "ᔍ SMTPSīŧˆåŸēæ–ŧ TLS 嘅 SMTPīŧ‰", - "notification_email_sent_test_email_button": "傺送æ¸ŦčŠĻé›ģéƒĩīŧŒčˇŸäŊå„˛å­˜", - "notification_email_setting_description": "寄送é›ģéƒĩ通įŸĨå˜…č¨­åŽš", - "notification_email_test_email": "傺送æ¸ŦčŠĻé›ģéƒĩ", - "notification_email_test_email_failed": "å‚ŗé€å””åˆ°æ¸ŦčŠĻé›ģéƒĩīŧŒčĢ‹æĒĸæŸĨæ‚¨å˜…č¨­åŽš", - "notification_email_test_email_sent": "æ¸ŦčŠĻé›ģéƒĩåˇ˛å‚ŗé€å’—åˆ° {email}。čĢ‹æĒĸæŸĨ您嘅æ”ļäģļåŒŖã€‚", - "notification_email_username_description": "į”¨åšŸéŠ—č­‰é›ģéƒĩäŧ翜å™¨å˜…äŊŋį”¨č€…åį¨ą", - "notification_enable_email_notifications": "ᔍé›ģéƒĩ通įŸĨ", - "notification_settings": "通įŸĨč¨­åŽš", - "notification_settings_description": "įŽĄį†é€šįŸĨč¨­åŽšīŧŒåŒ…æ‹Ŧé›ģéƒĩč¨­åŽš", - "oauth_allow_insecure_requests": "į•€į”¨å””å¯é å˜…čĢ‹æą‚", - "oauth_allow_insecure_requests_description": "č­Ļ告īŧšå‘ĸå€‹é¸é …æœƒåœį”¨ OAuth 嘅 TLS æ†‘č­‰éŠ—č­‰čĢ‹æą‚īŧŒå¯čƒŊ會ä줿‚¨æš´éœ˛æ–ŧ MITM æ”ģ擊。", - "oauth_auto_launch": "č‡Ē動啟動", - "oauth_auto_launch_description": "進å…Ĩį™ģå…Ĩ頁éĸ時īŧŒč‡Ē動啟動 OAuth į™ģå…Ĩæĩį¨‹", - "oauth_auto_register": "č‡Ē動č¨ģ冊", - "oauth_auto_register_description": "äŊŋᔍ OAuth į™ģå…Ĩ䚋垌č‡Ē動č¨ģ冊新äŊŋᔍ者", - "oauth_button_text": "按鈕文字", - "oauth_client_secret_description": "æŠŸå¯†į”¨æˆļįĢ¯å˜…åŋ…åĄĢé …į›Žīŧ›åĻ‚æžœå…Ŧ開ᔍæˆļįĢ¯å””æ”¯æ´ PKCE (äģŖįĸŧä礿›įš„éЗ證金鑰)īŧŒåŋ…é ˆåĄĢå¯Ģ。", - "oauth_enable_description": "ᔍ OAuth į™ģå…Ĩ", - "oauth_end_session_url_description": "ᔍæˆļį™ģå‡ē時將äŊĸ導向到å‘ĸ個URL。", - "oauth_mobile_redirect_uri": "æĩå‹•čŖįŊŽé‡æ–°å°Žå‘ URI", - "oauth_mobile_redirect_uri_override": "čφå¯Ģæĩå‹•čŖįŊŽé‡æ–°å°Žå‘ URI", - "oauth_mobile_redirect_uri_override_description": "į•ļ OAuth æäž›č€…å””äŋžį”¨æĩå‹•čŖįŊŽ URIīŧˆäž‹åĻ‚ ''{callback}''īŧ‰å˜…æ™‚å€™å•Ÿį”¨", - "oauth_prompt_description": "Prompt 參數īŧˆäž‹åĻ‚ select_account、login、consentīŧ‰", - "oauth_role_claim": "č§’č‰˛åŽŖå‘Š", - "oauth_role_claim_description": "栚據å‘ĸå€‹åŽŖå‘Šå˜…å­˜åœ¨īŧŒč‡Ē動授æŦŠįŽĄį†å“ĄæŦŠé™ã€‚å‘ĸå•˛åŽŖå‘Šå€ŧ可äģĨäŋ‚ 'user' 或 'admin'。", - "oauth_settings": "OAuth", - "oauth_settings_description": "įŽĄį† OAuth į™ģå…Ĩč¨­åŽš", - "oauth_settings_more_details": "åĻ‚æžœæƒŗäē†č§Ŗå‘ĸ個功čƒŊå˜…čŠŗį´°čŗ‡č¨ŠīŧŒčĢ‹åƒé–ą čĒĒæ˜Žæ–‡äģļ。", - "oauth_storage_label_claim": "å„˛å­˜æ¨™įą¤åŽŖå‘Š", - "oauth_storage_label_claim_description": "č‡Ē動將äŊŋį”¨č€…å˜…å„˛å­˜æ¨™įą¤č¨­åŽšį‚ēå‘ĸå€‹åŽŖå‘Šå€ŧ。", - "oauth_storage_quota_claim": "å„˛å­˜é™éĄåŽŖå‘Š", - "oauth_storage_quota_claim_description": "č‡Ē動將äŊŋį”¨č€…å˜…å„˛å­˜é™éĄč¨­åŽšį‚ēå‘ĸå€‹åŽŖå‘Šäš‹å€ŧ。", - "oauth_storage_quota_default": "é č¨­å„˛å­˜é™éĄīŧˆGiBīŧ‰", - "oauth_storage_quota_default_description": "æœĒæäž›åŽŖå‘Šæ™‚æ‰€äŊŋį”¨å˜…é™éĄīŧˆGiBīŧ‰ã€‚", - "oauth_timeout": "čĢ‹æą‚čļ…æ™‚", - "oauth_timeout_description": "čĢ‹æą‚čļ…æ™‚īŧˆæ¯Ģį§’īŧ‰", - "ocr_job_description": "į”¨æŠŸæĸ°å­¸įŋ’čž¨č­˜åŊąåƒä¸­å˜…文字", - "password_enable_description": "ᔍé›ģ子éƒĩäģļ同埋密įĸŧį™ģå…Ĩ", - "password_settings": "密įĸŧį™ģå…Ĩ", - "password_settings_description": "įŽĄį†å¯†įĸŧį™ģå…Ĩč¨­åŽš", - "paths_validated_successfully": "éŠ—č­‰æ™’æ‰€æœ‰å˜…čˇ¯åž‘", - "person_cleanup_job": "æ¸…į†äēēį‰Š", - "queue_details": "éšŠåˆ—čŗ‡č¨Š", - "queues": "äģģ務隊列", - "queues_page_description": "įŽĄį†å“Ąäģģ務隊列頁éĸ", - "quota_size_gib": "é…éĄå¤§į´°īŧˆGiBīŧ‰", - "refreshing_all_libraries": "é‡æ–°æ•´į†įˇŠæ‰€æœ‰åĒ’éĢ”åēĢ", - "registration": "įŽĄį†č€…č¨ģ冊", - "registration_description": "į”ąæ–ŧ您äŋ‚įŗģįĩąä¸ŠįŦŦ一個äŊŋᔍ者īŧŒæ‚¨æœƒčĸĢæŒ‡æ´žį‚ēįŗģįĩąįŽĄį†å“ĄīŧŒä¸Ļ負č˛ŦįŽĄį†į›¸é—œäē‹å‹™īŧŒäš‹åžŒå…ļ餘嘅äŊŋᔍ者éƒŊį”ąæ‚¨åģēįĢ‹ã€‚", - "release_channel_release_candidate": "候選į™ŧå¸ƒį‰ˆ", - "release_channel_stable": "į¨ŗåŽšį‰ˆ", - "remove_failed_jobs": "į§ģé™¤å¤ąæ•—äģģ務", - "require_password_change_on_login": "čĻæą‚äŊŋᔍ者å–ēįŦŦ一æŦĄį™ģå…Ĩ時攚密įĸŧ", - "reset_settings_to_default": "å°‡č¨­åŽšé‡č¨­åˆ°é č¨­å€ŧ", - "reset_settings_to_recent_saved": "å°‡č¨­åŽšé‡č¨­åˆ°æœ€čŋ‘å„˛å­˜å˜…č¨­åޚ", - "scanning_library": "æŽƒæįˇŠåĒ’éĢ”åēĢ", - "search_jobs": "æĩᎊäģģ務â€Ļ", - "send_welcome_email": "å‚ŗé€æ­ĄčŋŽé›ģéƒĩ", - "server_external_domain_settings": "外部įļ˛åŸŸ", - "server_external_domain_settings_description": "外部čļ…逪įĩįš„įļ˛åŸŸ", - "server_public_users": "å…Ŧ開äŊŋᔍ者", - "server_public_users_description": "將äŊŋį”¨č€…åŠ åˆ°å…ąäēĢᛏį°ŋ時īŧŒæœƒåˆ—å‡ē所有äŊŋᔍ者īŧˆå§“名同é›ģéƒĩīŧ‰ã€‚åĻ‚æžœå””į”¨īŧŒæˇ¨äŋ‚įŽĄį†å“Ąå…ˆå¯äģĨį‡åˆ°äŊŋį”¨č€…æ¸…å–Žã€‚", - "server_settings": "äŧ翜å™¨č¨­åޚ", - "server_settings_description": "įŽĄį†äŧ翜å™¨č¨­åޚ", - "server_stats_page_description": "įŽĄį†äŧ翜å™¨įĩąč¨ˆé éĸ", - "server_welcome_message": "æ­ĄčŋŽč¨Šæ¯", - "server_welcome_message_description": "å–ēį™ģå…Ĩ頁éĸéĄ¯į¤ēå˜…č¨Šæ¯ã€‚", - "settings_page_description": "įŽĄį†č¨­åŽšé éĸ", - "sidecar_job": "附åąŦæĒ”æĄˆčŠŽé‡‹čŗ‡æ–™", - "sidecar_job_description": "į”ąæĒ”æĄˆįŗģįĩąåĩæ¸Ŧæˆ–č€…åŒæ­Ĩ附åąŦæĒ”æĄˆčŠŽé‡‹čŗ‡æ–™", - "slideshow_duration_description": "每åŧĩį›¸åą•į¤ēå˜…į§’æ•¸", - "smart_search_job_description": "å°é …į›ŽåšæŠŸæĸ°å­¸įŋ’īŧŒäš‹åžŒå¯äģĨᔍæ™ē慧搜尋", - "storage_template_date_time_description": "æĒ”æĄˆįš„åģēįĢ‹æ™‚é–“æœƒį”¨å–ēæ—ĨæœŸåŒæ™‚é–“čŗ‡č¨Š", - "storage_template_date_time_sample": "æŠŊæ¨Ŗæ™‚é–“ {date}", - "storage_template_enable_description": "å•Ÿį”¨å„˛å­˜į¯„æœŦåŧ•擎", - "storage_template_hash_verification_enabled": "å•Ÿį”¨é›œæšŠå‡ŊåŧéŠ—č­‰", - "storage_template_hash_verification_enabled_description": "å•Ÿį”¨é›œæšŠå‡ŊåŧéŠ—č­‰īŧŒé™¤éžæ‚¨įœžäŋ‚非常地清æĨšæ˜Žį™Ŋå‘ĸå€‹é¸é …å˜…åŠŸį”¨īŧŒåĻ則č̋唔åĨŊæ‰‹å¤šå¤šåœį”¨", - "storage_template_migration": "å„˛å­˜į¯„æœŦ遡į§ģ", - "storage_template_migration_description": "å°‡į›Žå‰å‘ĸ個 {template} åĨ—ᔍčŊå…ˆå‰ä¸Šå‚ŗå˜…æĒ”æĄˆåēĻ", - "storage_template_migration_info": "å„˛å­˜į¯„æœŦ會將所有副æĒ”名čŊ‰åšį´°æĨˇã€‚į•™æ„čŋ”ᝄæœŦčŽŠæ›´æˇ¨äŋ‚會åĨ—čŊ新嘅æĒ”æĄˆåēĻ。åĻ‚æžœæƒŗčρčŋŊæē¯åĨ—ᔍᝄæœŦč‡ŗå…ˆå‰ä¸Šå‚ŗå’—å˜…æĒ”æĄˆīŧŒčĢ‹åĻå¤–åŸˇčĄŒ {job}。", - "storage_template_migration_job": "å„˛å­˜į¯„æœŦ遡į§ģäŊœæĨ­", - "storage_template_more_details": "關æ–ŧå‘ĸ項功čƒŊå˜…æ›´å¤ščŠŗį´°čŗ‡č¨ŠīŧŒčĢ‹åƒé–ą å„˛å­˜į¯„æœŦ 同埋äŊĸ嘅 åŊąéŸŋ", - "storage_template_onboarding_description_v2": "å•Ÿį”¨äš‹åžŒīŧŒå‘ĸåšŋ功čƒŊ將會栚據äŊŋᔍ者č‡Ē荂ᝄæœŦ嚟åŽģč‡Ēå‹•æ•´į†æĒ”æĄˆã€‚æƒŗįž­č§Ŗæ›´å¤ščŗ‡č¨Šå˜…čŠąīŧŒčĢ‹åƒé–ą čĒĒæ˜Žæ–‡äģļ。", - "storage_template_path_length": "預äŧ°čˇ¯åž‘镡åēĻ上限īŧš{length, number}/{limit, number}", - "storage_template_settings": "å„˛å­˜į¯„æœŦ", - "storage_template_settings_description": "įŽĄį†åˇ˛įļ“ä¸Šå‚ŗå˜…æĒ”æĄˆå€‹čŗ‡æ–™å¤žįĩæ§‹åŒæĒ”æĄˆå", - "storage_template_user_label": "{label} å‘ĸ個äŋ‚äŊŋį”¨č€…å˜…å„˛å­˜æ¨™įą¤", - "system_settings": "įŗģįĩąč¨­åޚ", - "tag_cleanup_job": "æ¸…į†æ¨™įą¤", - "template_email_available_tags": "您可äģĨå–ēᝄæœŦå…ĨéĸäŊŋᔍäģĨä¸‹čŽŠæ•¸īŧš{tags}", - "template_email_if_empty": "č‹ĨᝄæœŦ內厚äŋ‚å‰å˜…čŠąīŧŒå°ąæœƒį”¨čŋ”å€‹é č¨­å˜…éƒĩäģļᝄæœŦ。", - "template_email_invite_album": "ᛏį°ŋ邀č̋ᝄæœŦ", - "template_email_preview": "預čĻŊ", - "template_email_settings": "éƒĩäģļᝄæœŦ", - "template_email_update_album": "æ›´æ–°į›¸į°ŋᝄæœŦ", - "template_email_welcome": "æ­ĄčŋŽéƒĩäģļᝄæœŦ", - "template_settings": "通įŸĨᝄæœŦ", - "template_settings_description": "įŽĄį†é€šįŸĨ個č‡Ē荂ᝄæœŦ", - "theme_custom_css_settings": "č‡Ē訂 CSS", - "theme_custom_css_settings_description": "é€éŽéšŽåą¤åŧæ¨ŖåŧčĄ¨ () 可äģĨč‡Ē訂 Immich å˜…å¤–č§€č¨­č¨ˆã€‚", - "theme_settings": "ä¸ģéĄŒč¨­åŽš", - "theme_settings_description": "č‡Ē訂 Immich įļ˛é äģ‹éĸ", - "thumbnail_generation_job": "į”ĸį”Ÿį¸Žåœ–", - "thumbnail_generation_job_description": "į‚ē每個æĒ”æĄˆį”ĸį”Ÿå¤§ã€å°äģĨåŠæ¨ĄįŗŠį¸Žåœ–īŧŒåŒæ™‚äēĻéƒŊåšĢ每äŊäēēį‰Šį”ĸį”Ÿį¸Žåœ–", - "transcoding_acceleration_api": "加速 API", - "transcoding_acceleration_api_description": "å‘ĸ個 API äŋ‚æœƒåŒæ‚¨å˜…č¨­å‚™čŖįŊŽäē’å‹•į”¨åšŸåščŊ‰įĸŧåŠ é€Ÿå˜…ã€‚į•™æ„čŋ”å‘ĸå€‹č¨­åŽšäŋ‚čĄŒã€Œį›Ąåšã€æ¨Ąåŧīŧšč‹Ĩ果唔垗å‘ĸäŋ‚ᔍčŋ”čģŸäģļčŊ‰įĸŧå˜…ã€‚č‡ŗæ–ŧį”¨å””į”¨åˆ° VP9 å°ąå–æąēæ–ŧįĄŦäģļæĸäģļå•Ļ。", - "transcoding_acceleration_nvenc": "NVENCīŧˆéœ€čρ NVIDIA GPUīŧ‰", - "transcoding_acceleration_qsv": "Quick Syncīŧˆéœ€čρįŦŦ 7 äģŖæˆ–打垌嘅 Intel 處ᐆ噍īŧ‰", - "transcoding_acceleration_rkmpp": "RKMPPīŧˆįĨ—éŠį”¨æ–ŧ Rockchip SOCsīŧ‰", - "transcoding_acceleration_vaapi": "VAAPI", - "transcoding_accepted_audio_codecs": "æŽĨå—å˜…éŸŗč¨Šįˇ¨č§Ŗįĸŧ器", - "transcoding_accepted_audio_codecs_description": "選擇čŋ”é‚Šå•˛éŸŗč¨Šįˇ¨č§Ŗįĸŧ器唔需čρčŊ‰įĸŧ。å‘ĸå€‹č¨­åŽšæˇ¨äŋ‚éŠį”¨æ–ŧį‰šåŽšå˜…čŊ‰įĸŧį­–į•Ĩ。", - "transcoding_accepted_containers": "æŽĨå—å˜…å°čŖæ ŧåŧ", - "transcoding_accepted_containers_description": "選擇čŋ”é‚Šå•˛å°čŖæ ŧåŧå””äŊŋé‡æ–°å°čŖīŧˆremuxīŧ‰åš MP4。å‘ĸå€‹č¨­åŽšæˇ¨äŋ‚éŠį”¨æ–ŧį‰šåŽšå˜…čŊ‰įĸŧį­–į•Ĩ。", - "transcoding_accepted_video_codecs": "æŽĨ受嘅åŊąį‰‡įˇ¨č§Ŗįĸŧ器", - "transcoding_accepted_video_codecs_description": "選擇毋須čŊ‰įĸŧ嘅åŊąį‰‡įˇ¨č§Ŗįĸŧ器æ ŧåŧã€‚å‘ĸå€‹č¨­åŽšæˇ¨äŋ‚éŠį”¨æ–ŧį‰šåŽšå˜…čŊ‰įĸŧį­–į•Ĩ。", - "transcoding_advanced_options_description": "大多數äŊŋį”¨č€…å””æœƒéƒåˆ°å˜…é¸é …", - "transcoding_audio_codec": "韺荊ᎍ觪įĸŧ器", - "transcoding_audio_codec_description": "Opus 選項雖į„ļäŋ‚æœ€éšéŸŗčŗĒīŧŒäŊ†äŋ‚å°čˆŠčŖįŊŽæˆ–čˆŠį‰ˆčģŸäģļå˜…į›¸åŽšæ€§æœƒåˇŽå°‘å°‘ã€‚", - "transcoding_bitrate_description": "åŊąį‰‡äŊå…ƒįއ (Bitrate) éĢ˜éŽæœ€å¤§å€ŧåˆæˆ–č€…į”¨å’—å””æŽĨ受嘅åŊąį‰‡æ ŧåŧ", - "transcoding_codecs_learn_more": "åĻ‚æžœčρäē†č§Ŗå‘ĸåēĻå˜…čĄ“čĒžīŧŒčĢ‹åƒč€ƒ FFmpeg čĒĒæ˜Žæ›¸å…Ĩéĸ關æ–ŧ H.264 ᎍįĸŧ、HEVC č§Ŗįĸŧ器 同埋 VP9 č§Ŗįĸŧ器 嘅čĒĒæ˜Žã€‚", - "transcoding_constant_quality_mode": "恆厚品čŗĒæ¨Ąåŧ", - "transcoding_constant_quality_mode_description": "ICQ 嘅效果會åĨŊ過 CQPīŧŒäŊ†äŋ‚部分įĄŦéĢ”åŠ é€Ÿæ¨Ąåŧå””會支持å‘ĸå€‹č¨­åŽšã€‚å–ēč¨­åŽšå‘ĸ個選項嘅時候å–ēäŊŋᔍäģĨ品čŗĒį‚ēåŸēæē–å˜…įˇ¨įĸŧ會å„Ē先äŊŋᔍäŊ æŒ‡åŽšå˜…æ¨Ąåŧã€‚ICQ唔čĸĢNVIDIAéĄ¯åĄå˜… NVENC ᎍįĸŧ支援īŧŒæ‰€äģĨå–ēNVENC下å‘ĸå€‹č¨­åŽšæœƒčĸĢåŋŊį•Ĩ。", - "transcoding_constant_rate_factor": "æ†åŽšé€ŸįŽ‡å› å­īŧˆ-crfīŧ‰", - "transcoding_constant_rate_factor_description": "åŊąį‰‡å“čŗĒå˜…į­‰į´šã€‚å…¸åž‹å€ŧį‚ē H.264 嘅 23、HEVC 嘅 28、VP9 嘅 31 和 AV1 嘅 35。數å€ŧčļŠäŊŽīŧŒå“čŗĒčļŠåĨŊīŧŒäŊ†å–ē會į”ĸį”Ÿčŧƒå¤§įš„æĒ”æĄˆã€‚", - "transcoding_disabled_description": "唔對äģģäŊ•åŊąį‰‡é€˛čĄŒčŊ‰įĸŧīŧŒå¯čƒŊæœƒå°Žč‡´éƒ¨åˆ†į”¨æˆļį„Ąæŗ•æ­Ŗå¸¸æ’­æ”ž", - "transcoding_encoding_options": "ᎍįĸŧ選項", - "transcoding_encoding_options_description": "č¨­åŽšįˇ¨įĸŧåŊąį‰‡å˜…ᎍ觪įĸŧå™¨ã€č§ŖæžåēĻ、品čŗĒ同埋å…ļäģ–選項", - "transcoding_hardware_acceleration": "įĄŦéĢ”åŠ é€Ÿ", - "transcoding_hardware_acceleration_description": "å¯Ļ銗性功čƒŊīŧšæœƒåŠ åŋĢčŊ‰įĸŧ嘅速åēĻīŧŒäŊ†å–ēį›¸åŒäŊå…ƒé€Ÿįއ䏋品čŗĒ可čƒŊ會äŊŽå•˛", - "transcoding_hardware_decoding": "įĄŦé̔觪įĸŧ", - "transcoding_hardware_decoding_setting_description": "å•Ÿį”¨į̝到įĢ¯åŠ é€ŸīŧŒč€Œå””äŋ‚æˇ¨äŋ‚åŠ é€Ÿįˇ¨įĸŧ。å‘ĸ個功čƒŊ可čƒŊä¸éŠį”¨æ–ŧ所有嘅åŊąį‰‡ã€‚", - "transcoding_max_b_frames": "最大 B 嚀數", - "transcoding_max_b_frames_description": "čŧƒéĢ˜įš„æ•¸å€ŧ可äģĨæå‡åŖ“į¸Žå˜…æ•ˆįŽ‡īŧŒäŊ†äŋ‚會降äŊŽįˇ¨įĸŧ嘅速åēĻ。å–ēčŧƒčˆŠįš„čŖįŊŽä¸Šå¯čƒŊ同įĄŦéĢ”åŠ é€Ÿä¸į›¸åŽšã€‚0 äģŖčĄ¨åœį”¨ B 嚀īŧŒč€Œ -1 å°ąæœƒč‡Ēå‹•č¨­åŽšæ•¸å€ŧ。", - "transcoding_max_bitrate": "最大äŊå…ƒé€Ÿįއ", - "transcoding_max_bitrate_description": "č¨­åŽšæœ€å¤§äŊå…ƒįŽ‡å¯äģĨäģ¤æĒ”æĄˆå˜…å¤§į´°æ›´åŠ įŠŠåŽšīŧŒäŊ†äŋ‚æœƒį¨åžŽįŠ§į‰˛į•ĢčŗĒ。720p ä¸‹įš„å…¸åž‹å€ŧį‚ēīŧšVP9 æˆ–č€… HEVC äŋ‚ 2600 kbit/sīŧŒH.264 į‚ē 4500 kbit/sã€‚č¨­į‚ē 0 å‰‡åœį”¨ã€‚åĻ‚æžœæœĒ指厚喎äŊīŧŒįŗģįĩąå°‡é č¨­į‚ē k (åŗ kbit/s)īŧ›æ‰€äģĨ 5000、5000k 同埋 5M (åŗ Mbit/s) å–ēä¸€æ¨Ŗå˜…ã€‚", - "transcoding_max_keyframe_interval": "最大關éĩ嚀嘅間隔", - "transcoding_max_keyframe_interval_description": "č¨­åŽšé—œéĩåš€äš‹é–“å˜…æœ€å¤§åš€čˇã€‚čŧƒäŊŽå˜…數å€ŧ會降äŊŽåŖ“į¸Žå˜…æ•ˆįŽ‡īŧŒäŊ†äŋ‚æœƒæ”šå–„čˇŗčŊ‰æœå°‹å˜…時間ä¸Ļ提升éĢ˜å‹•æ…‹å ´æ™¯į•ĢčŗĒ。0 į‚ēč‡Ēå‹•č¨­åŽšã€‚", - "transcoding_optimal_description": "é̘æ–ŧį›Žæ¨™č§ŖæžåēĻæˆ–者æ ŧåŧå””äŋ‚可æŽĨå—į¯„åœå˜…åŊąį‰‡", - "transcoding_policy": "čŊ‰įĸŧį­–į•Ĩ", - "transcoding_policy_description": "č¨­åŽšåŊąį‰‡é€˛čĄŒčŊ‰įĸŧ嘅æĸäģļ", - "transcoding_preferred_hardware_device": "éϖ遏įĄŦéĢ”čŖįŊŽ", - "transcoding_preferred_hardware_device_description": "åĒäŋ‚éŠį”¨æ–ŧ VAAPI 同埋 QSVã€‚č¨­åŽšį”¨æ–ŧįĄŦéĢ”čŊ‰įĸŧ嘅 dri ᝀéģžã€‚", - "transcoding_preset_preset": "預設å€ŧīŧˆ-presetīŧ‰", - "transcoding_preset_preset_description": "åŖ“į¸Žé€ŸåēĻ。čŧƒæ…ĸå˜…é č¨­å€ŧ可äģĨį”ĸį”ŸéĢ”įŠčŧƒį´°å˜…æĒ”æĄˆīŧŒä¸Ļäŋ‚指åޚäŊå…ƒįŽ‡å˜…æ™‚å€™æå‡å“į•ĢčŗĒ。VP9 會åŋŊį•Ĩé̘æ–ŧ「fasterã€å˜…č¨­åŽšã€‚", - "transcoding_realtime": "åŗæ™‚čŊ‰įĸŧ [å¯Ļ銗性功čƒŊ]", - "transcoding_realtime_description": "å…č¨ąå–ēåŊąį‰‡ä¸˛æĩæ’­æ”žå˜…åŒæ™‚åŗæ™‚é€˛čĄŒčŊ‰įĸŧ。å‘ĸ個功čƒŊ支援動態切換į•ĢčŗĒīŧŒäŊ†äŋ‚可čƒŊ會因į‚ēäŧ翜å™¨å˜…效čƒŊč€Œå°Žč‡´čŧƒéĢ˜å˜…æ’­æ”žåģļé˛æˆ–æ–ˇæ–ˇįēŒįēŒå˜…į•Ģéĸ。", - "transcoding_realtime_enabled": "å•Ÿį”¨åŗæ™‚čŊ‰įĸŧ", - "transcoding_realtime_enabled_description": "åĻ‚æžœåœį”¨īŧŒäŧ翜å™¨å°‡æœƒæ‹’įĩ•å•Ÿå‹•æ–°å˜…åŗæ™‚čŊ‰įĸŧåˇĨäŊœéšŽæŽĩ。", - "transcoding_realtime_resolutions": "č§ŖåƒåēĻ", - "transcoding_realtime_resolutions_description": "å¯Ļ時čŊ‰įĸŧæ‰€æäž›å˜…č§ŖåƒåēĻ選項。更加éĢ˜å˜…č§ŖåƒåēĻå–ēäŧ翜å™¨čŊ‰įĸŧčŊ‰å””åˆ‡å˜…æƒ…æŗä¸‹å¯čƒŊæœƒå°Žč‡´æ’­æ”žå•éĄŒã€‚", - "transcoding_realtime_video_codecs": "åŊąį‰‡įˇ¨č§Ŗįĸŧ器", - "transcoding_realtime_video_codecs_description": "å‘ĸ個äŋ‚ᔍå–ēå¯Ļ時čŊ‰įĸŧ嘅åŊąį‰‡č§Ŗįĸŧå™¨é¸é …ã€‚į”¨æˆļįĢ¯æœƒå–ē播攞嘅時候選擇äŊĸ支持嘅最äŊŗé¸é …。AV1 å˜…æ•ˆįŽ‡æœƒéĢ˜éŽ HEVCīŧŒč€Œ HEVC å˜…æ•ˆįŽ‡åˆæœƒéĢ˜éŽ H.264。 å–ēäŊŋᔍįĄŦéĢ”åŠ é€Ÿå˜…æ™‚å€™æˇ¨äŋ‚čĻé¸æ“‡åŠ é€Ÿå™¨æ”¯æ´å˜…č§Ŗįĸŧ器。äŊŋᔍčģŸäģļč§Ŗįĸŧ嘅時候īŧŒH.264 嘅速åēĻæœƒåŋĢ過 AV1īŧŒ 而 AV1 嘅速åēĻæœƒåŋĢ過 HEVC。", - "transcoding_reference_frames": "åƒč€ƒåš€", - "transcoding_reference_frames_description": "å–ēåŖ“į¸Žį‰šåŽšåš€å˜…æ™‚å€™æ‰€åƒč€ƒå˜…åš€æ•¸é‡ã€‚æ•¸å€ŧčļŠéĢ˜å¯äģĨæå‡åŖ“į¸Žå˜…æ•ˆįŽ‡īŧŒäŊ†äŋ‚會降äŊŽįˇ¨įĸŧ嘅速åēĻã€‚č¨­į‚ē 0 則č‡Ē動æąē厚此數å€ŧ。", - "transcoding_required_description": "åĒäŋ‚限æ–ŧæ ŧåŧå””čĸĢæŽĨ受嘅åŊąį‰‡", - "transcoding_settings": "åŊąį‰‡čŊ‰įĸŧč¨­åŽš", - "transcoding_settings_description": "įŽĄį†éœ€čρčŊ‰įĸŧ嘅åŊąį‰‡åŒåŸ‹äŊĸč™•į†å˜…æ–šåŧ", - "transcoding_target_resolution": "į›Žæ¨™č§ŖåƒåēĻ", - "transcoding_target_resolution_description": "čŧƒéĢ˜å˜…č§ŖåƒåēĻ可äģĨäŋį•™æ›´å¤šå˜…ᴰᝀīŧŒäŊ†äŋ‚ᎍįĸŧ時間會čĸĢčŧƒé•ˇīŧŒæĒ”æĄˆéƒŊ會čŧƒå¤§īŧŒč€Œä¸”可čƒŊäģ¤åˆ°æ‡‰į”¨į¨‹åŧå˜…反應čފæ…ĸ。", - "transcoding_temporal_aq": "時間č‡Ē遊應量化īŧˆTemporal AQīŧ‰", - "transcoding_temporal_aq_description": "åĒäŋ‚éŠį”¨æ–ŧ NVENCīŧŒæ™‚域č‡Ē我čĒŋ整量化可äģĨ提升éĢ˜į´°į¯€ã€äŊŽå‹•態場景嘅į•ĢčŗĒ。可čƒŊ同čŧƒčˆŠå˜…čŖįŊŽå””į›¸åŽšã€‚", - "transcoding_threads": "įˇšį¨‹", - "transcoding_threads_description": "čŧƒéĢ˜å˜…æ•¸å€ŧ會加åŋĢᎍįĸŧ嘅速åēĻīŧŒäŊ†é‹čĄŒå˜…時候會äŊ”į”¨æ›´åŠ å¤šäŧ翜å™¨č™•ᐆå…ļäģ–äģģ務嘅效čƒŊ。å‘ĸ個數å€ŧå””æ‡‰čŠ˛čļ…過 CPU 嘅核åŋƒæ•¸ã€‚設į‚ē 0 可äģĨæœ€å¤§åŒ–čŗ‡æēå˜…åˆŠį”¨įŽ‡ã€‚", - "transcoding_tone_mapping": "色čĒŋ對映", - "transcoding_tone_mapping_description": "將 HDR åŊąį‰‡čŊ‰æ›į‚ē SDR å˜…æ™‚į›Ąé‡įļ­æŒåŽŸå§‹å˜…čĻ–čĻēæ•ˆæžœã€‚æ¯į¨Žæŧ”įŽ—æŗ•äŋ‚色åŊŠã€į´°į¯€åŒåŸ‹äēŽåēĻ嘅斚éĸéƒŊæœ‰å””åŒå˜…č€ƒé‡ã€‚Hable įŽ—æŗ•č‘—é‡äŋį•™į´°į¯€īŧŒMobius įŽ—æŗ•č‘—é‡åŽŸå§‹č‰˛åŊŠīŧŒč€Œ Reinhard įŽ—æŗ•æœƒåå‘äŋį•™äēŽåēĻ。", - "transcoding_transcode_policy": "čŊ‰įĸŧį­–į•Ĩ", - "transcoding_transcode_policy_description": "åŊąį‰‡čŊ‰įĸŧå˜…į­–į•Ĩ。HDR åŊąį‰‡æˆ–č€…åƒį´ æ ŧåŧå””äŋ‚ YUV 4:2:0 嘅åŊąį‰‡ä¸€åž‹æœƒé€˛čĄŒčŊ‰įĸŧīŧˆé™¤éžåœį”¨čŊ‰įĸŧ功čƒŊīŧ‰ã€‚", - "transcoding_two_pass_encoding": "兊階æŽĩᎍįĸŧ", - "transcoding_two_pass_encoding_setting_description": "åŸˇčĄŒå…ŠæŦĄįˇ¨įĸŧäģĨį”ĸį”Ÿå“čŗĒčŧƒäŊŗå˜…åŊąį‰‡ã€‚å–ēå•Ÿį”¨æœ€å¤§äŊå…ƒé€ŸįŽ‡å˜…æ™‚å€™īŧˆH.264 與 HEVC åŋ…é ˆå•Ÿį”¨īŧ‰å‘ĸå€‹æ¨Ąåŧæœƒæ šæ“šæœ€å¤§äŊå…ƒé€ŸįއčĒŋæ•´į¯„åœä¸ĻåŋŊį•Ĩ CRF。äŊŋᔍ VP9 ᎍįĸŧå˜…æƒ…æŗä¸‹å¯äģĨå–ēåœį”¨æœ€å¤§äŊå…ƒé€ŸįŽ‡å˜…æ™‚å€™äŊŋᔍ CRF。", - "transcoding_video_codec": "åŊąį‰‡įˇ¨č§Ŗįĸŧ器", - "transcoding_video_codec_description": "VP9 å…ˇæœ‰éĢ˜åŖ“į¸Žæ•ˆįŽ‡åŒåŸ‹č‰¯åĨŊ嘅įļ˛é į›¸åŽšæ€§īŧŒäŊ†äŋ‚čŊ‰įĸŧ速åēĻæœƒčŧƒæ…ĸ。HEVC 嘅效čƒŊ類äŧŧīŧŒäŊ†äŋ‚įļ˛é į›¸åŽšæ€§æœƒæ¯”čŧƒåˇŽã€‚H.264 兎備åģŖæŗ›å˜…į›¸åŽšæ€§č€Œä¸”čŊ‰įĸŧ速åēĻåŋĢīŧŒäŊ†äŋ‚į”ĸį”Ÿå˜…æĒ”æĄˆéĢ”įŠæœƒæ¯”čŧƒå¤§ã€‚AV1 äŋ‚æ•ˆįŽ‡æœ€éĢ˜å˜…įˇ¨č§Ŗįĸŧ器īŧŒäŊ†äŋ‚å–ē比čŧƒčˆŠå˜…čŖįŊŽä¸Šæœƒįŧē䚏支援。", - "trash_enabled_description": "å•Ÿį”¨åžƒåœžæĄļ功čƒŊ", - "trash_number_of_days": "æ—Ĩ數", - "trash_number_of_days_description": "文äģļå–ē垃圞æĄļå…Ĩéĸ永䚅åˆĒ除䚋前äŋį•™å˜…æ—Ĩ數", - "trash_settings": "垃圞æĄļč¨­åŽš", - "trash_settings_description": "įŽĄį†åžƒåœžæĄļč¨­åŽš", - "unlink_all_oauth_accounts": "č§Ŗé™¤æ‰€æœ‰ OAuth å¸ŗč™Ÿå˜…é€Ŗįĩ", - "unlink_all_oauth_accounts_description": "å–翐Ŧåˆ°æ–°å˜…æœå‹™æäž›č€…äš‹å‰īŧŒč̋唔åĨŊå””č¨˜åž—čĻč§Ŗé™¤æ‰€æœ‰åŒ OAuth 叺æˆļå˜…é€Ŗįĩã€‚", - "unlink_all_oauth_accounts_prompt": "äŊ äŋ‚å””äŋ‚įĸē厚čĻč§Ŗé™¤æ‰€æœ‰åŒ OAuth å¸ŗč™Ÿå˜…é€ŖįĩīŧŸå‘ĸ個動äŊœæœƒä¸å¯é€†čŊ‰åœ°é‡č¨­æ¯äŊäŊŋį”¨č€…įš„å˜… OAuth ID。", - "user_cleanup_job": "æ¸…į†į”¨æˆļ", - "user_delete_delay": "{user} å˜…å¸ŗč™ŸåŒåŸ‹äŊĸæ‰€æœ‰čŗ‡æ–™æœƒå–ē {delay, plural, one {# æ—Ĩ} other {# 🌞}} 垌永䚅åˆĒ除。", - "user_delete_delay_settings": "åģļ遲åˆĒ除", - "user_delete_delay_settings_description": "į§ģ除垌永䚅åˆĒ除ᔍæˆļå¸ŗč™ŸåŒåŸ‹æ–‡äģļ嘅æ—Ĩæ•¸ã€‚į”¨æˆļåˆĒ除嘅åˇĨäŊœæœƒå–ē午夜æĒĸæŸĨ需čρåˆĒé™¤å˜…å¸ŗč™Ÿã€‚å‘ĸå€‹č¨­åŽšå˜…äģģäŊ•更攚會å–ē下æŦĄåŸˇčĄŒå˜…æ™‚å€™į”Ÿæ•ˆã€‚", - "user_delete_immediately": "{user} å˜…å¸ŗč™ŸåŒåŸ‹čŗ‡æ–™å°‡æœƒ įĢ‹åŗ 永䚅åˆĒ除。", - "user_delete_immediately_checkbox": "įĢ‹åŗå°‡į”¨æˆļ同埋äŊĸ嘅文äģļ加å…ĨįĢ‹åŗåˆĒ除嘅隊äŧ", - "user_details": "ᔍæˆļčŠŗæƒ…", - "user_management": "į”¨æˆˇįŽĄį†", - "user_password_has_been_reset": "ᔍæˆļ嘅密įĸŧåˇ˛é‡č¨­īŧš", - "user_password_reset_description": "čĢ‹å°‡č‡¨æ™‚å¯†įĸŧ比嗰個äŊŋᔍ者īŧŒåŒåŸ‹åŒäŊĸ哋čŦ›å–ē下æŦĄį™ģå…Ĩ嘅時候需čĻæ›´æ”šå¯†įĸŧ。", - "user_restore_description": "{user} å˜…å¸ŗč™Ÿå°‡æœƒčĸĢ還原。", - "user_restore_scheduled_removal": "還原äŊŋᔍ者 - 取æļˆé åޚå–ē {date, date, long} 嘅į§ģ除計į•Ģ", - "user_settings": "į”¨æˆˇč¨­åŽš", - "user_settings_description": "įŽĄį†į”¨æˆˇč¨­åŽš", - "user_successfully_removed": "ᔍæˆļ {email}åˇ˛æˆåŠŸåˆĒ除。", - "users_page_description": "įŽĄį†į”¨æˆˇé éĸ", - "version_check_channel": "更新į™ŧäŊˆæ¸ é“", - "version_check_channel_description": "揀選äŊ æŽĨæ”ļį‰ˆæœŦ更新å…Ŧ告嘅į™ŧ布渠道", - "version_check_enabled_description": "å•Ÿį”¨į‰ˆæœŦæĒĸæŸĨ", - "version_check_implications": "į‰ˆæœŦæĒĸæŸĨ功čƒŊäžčŗ´åŒ {server} å˜…åŽšæœŸé€šč¨Š", - "version_check_settings": "į‰ˆæœŦæĒĸæŸĨ", - "version_check_settings_description": "å•Ÿį”¨ / åœį”¨æ–°į‰ˆæœŦ通įŸĨ", - "video_conversion_job": "åŊąį‰‡čŊ‰įĸŧ", - "video_conversion_job_description": "將åŊąį‰‡čŊ‰įĸŧäģĨæå‡åŒį€čĻŊå™¨åŒåŸ‹čŖįŊŽå˜…į›¸åŽšæ€§" + "metadata_extraction_job_description": "å–翝å€‹é …į›Žä¸­æŠŊå–å…ƒæ•¸æ“ščŗ‡č¨ŠīŧŒäž‹åĻ‚ GPSã€č‡‰å­”åŒåŸ‹č§ŖæžåēĻ" }, - "admin_email": "įŽĄį†å“Ąé›ģéƒĩ", - "admin_password": "įŽĄį†å“Ąå¯†įĸŧ", - "administration": "įŗģįĩąįŽĄį†", - "advanced": "進階", - "advanced_settings_clear_image_cache": "æ¸…é™¤åœ–į‰‡åŋĢ取", - "advanced_settings_clear_image_cache_error": "æ¸…é™¤åœ–į‰‡åŋĢå–å¤ąæ•—", - "advanced_settings_clear_image_cache_success": "成功清除{size}", - "advanced_settings_log_level_title": "į´€éŒ„į­‰į´šīŧš{level}", - "advanced_settings_prefer_remote_subtitle": "éƒ¨åˆ†čŖįŊŽåžžæœŦæŠŸé …į›Žčŧ‰å…Ĩį¸Žåœ–å˜…é€ŸåēĻ非常æ…ĸã€‚å•Ÿį”¨å‘ĸå€‹č¨­åŽšå¯äģĨ攚į‚ēčŧ‰å…Ĩ遠įĢ¯åœ–į‰‡ã€‚", - "advanced_settings_prefer_remote_title": "å„Ē先äŊŋᔍ遠į̝ᛏቇ", - "advanced_settings_proxy_headers_subtitle": "åŽšįžŠ Immich å–ē每æŦĄįļ˛čˇ¯čĢ‹æą‚å˜…æ™‚å€™æ‡‰čŠ˛å‚ŗé€å˜…äģŖį†æ¨™é ­", - "advanced_settings_proxy_headers_title": "č‡Ē訂äģŖį†æ¨™é ­[å¯Ļ銗性]", - "advanced_settings_readonly_mode_subtitle": "å•Ÿį”¨å”¯čŽ€æ¨ĄåŧåžŒæˇ¨äŋ‚可äģĨį€čĻŊᛏቇīŧŒå””æœƒį”¨åˆ°å¤šé¸ã€åˆ†äēĢ、投攞同埋åˆĒé™¤į­‰å˜…åŠŸčƒŊ。äŊ å¯äģĨ透過ä¸ģį•Ģéĸ上嘅äŊŋᔍ者個äēē圖į¤ēå•Ÿį”¨æˆ–č€…åœį”¨å”¯čŽ€æ¨Ąåŧ", - "advanced_settings_readonly_mode_title": "å”¯čŽ€æ¨Ąåŧ", - "advanced_settings_sync_remote_deletions_subtitle": "å–ēįļ˛é į̝åˆĒé™¤æˆ–é‚„åŽŸé …į›Žå˜…æ™‚äž¯īŧŒč‡Ē動å–ēæ­¤čŖįŊŽä¸ŠåˆĒé™¤æˆ–é‚„åŽŸå—°å€‹é …į›Ž", - "advanced_settings_sync_remote_deletions_title": "同æ­Ĩ遙距åˆĒ除 [å¯Ļ銗性]", - "advanced_settings_tile_subtitle": "é€˛éšŽį”¨æˆļč¨­åŽš", - "advanced_settings_troubleshooting_subtitle": "å•Ÿį”¨éĄå¤–åŠŸčƒŊäģĨé€˛čĄŒį–‘é›ŖæŽ’č§Ŗ", - "advanced_settings_troubleshooting_title": "į–‘é›ŖæŽ’č§Ŗ", - "age_months": "{months, plural, one {# 個月} other {# 個月}}", - "age_year_months": "1 æ­˛ {months, plural, one {# 個月} other {# 個月}}", - "age_years": "{years, plural, other {# æ­˛}}", - "album": "ᛏį°ŋ", - "album_added": "厞加å…Ĩᛏį°ŋ", - "album_added_notification_setting_description": "å–ē我čĸĢ加å…Ĩå…ąäēĢᛏį°ŋ嘅時候īŧŒé€éŽé›ģ子éƒĩäģļ通įŸĨ我", - "album_cover_updated": "åˇ˛æ›´æ–°į›¸į°ŋ封éĸ", - "album_delete_confirmation": "äŊ äŋ‚å””äŋ‚įĸē厚čρåˆĒ除ᛏį°ŋ {album} īŧŸ", - "album_delete_confirmation_description": "åĻ‚æžœå‘ĸ個ᛏį°ŋ厞įļ“čĸĢå…ąäēĢīŧŒå’å…ļäģ–äŊŋį”¨č€…å°‡į„Ąæŗ•å†å­˜å–ã€‚", - "album_deleted": "ᛏį°ŋ厞åˆĒ除", - "album_info_updated": "åˇ˛æ›´æ–°į›¸į°ŋčŗ‡č¨Š", - "album_name": "ᛏį°ŋ名", - "album_options": "ᛏį°ŋ選項", - "album_remove_user": "į§ģ除ᔍæˆļīŧŸ", - "album_remove_user_confirmation": "įĸē厚čρį§ģ除 {user} 嗎īŧŸ", - "album_search_not_found": "æĩ唔到įŦĻ合搜尋æĸäģļå˜…į›¸į°ŋ", - "album_share_no_users": "į‡åšŸäŊ åˇ˛įļ“å°‡å‘ĸ個ᛏį°ŋ分äēĢæ‰€æœ‰å˜…ᔍæˆļīŧŒæˆ–者冇å…ļäģ–å˜…į”¨æˆļ可äģĨ分äēĢ。", - "album_summary": "ᛏį°ŋæĻ‚čρ", - "album_updated": "ᛏį°ŋåˇ˛æ›´æ–°", - "album_updated_setting_description": "å–ēå…ąäēĢᛏį°ŋæœ‰æ–°é …į›Žå˜…æ™‚å€™į”¨é›ģéƒĩ通įŸĨ我", - "album_upload_assets": "å–ēäŊ å˜…é›ģč…Ļ上čŧ‰æĒ”æĄˆä¸Ļ加å…Ĩᛏį°ŋ", - "album_viewer_appbar_share_err_delete": "åˆĒ除ᛏį°ŋå¤ąæ•—", - "album_viewer_page_share_add_users": "邀čĢ‹å…ļäģ–äēē", - "album_with_link_access": "äģģäŊ•æ“æœ‰é€Ŗįĩįš„äēēéƒŊ可äģĨį‡åˆ°å‘ĸ個ᛏį°ŋå…Ĩéĸå˜…į›¸į‰‡åŒåŸ‹äēēį‰Šã€‚", - "albums": "ᛏį°ŋ", - "albums_count": "{count, plural, one {{count, number} æœŦᛏį°ŋ} other {{count, number} æœŦᛏį°ŋ}}", - "albums_default_sort_order": "預荭ᛏį°ŋ排åē", - "albums_default_sort_order_description": "åģēįĢ‹æ–°į›¸į°ŋå˜…åˆå§‹é …į›ŽæŽ’åēæ–šåŧã€‚", - "albums_feature_description": "可äģĨå…ąäēĢįĩĻå…ļäģ–äŊŋį”¨č€…å˜…é …į›Žé›†ã€‚", - "albums_on_device_count": "å‘ĸå€‹čŖįŊŽä¸Šæœ‰ ({count}) æœŦᛏį°ŋ", - "all": "全部", - "all_albums": "æ‰€æœ‰į›¸į°ŋ", - "all_people": "所有äēēį‰Š", - "all_photos": "æ‰€æœ‰åœ–į‰‡", - "all_videos": "所有åŊąį‰‡", - "allow_dark_mode": "å…č¨ąæˇąč‰˛æ¨Ąåŧ", - "allow_public_user_to_download": "å…č¨ąå…Ŧ開äŊŋᔍ者䏋čŧ‰", - "allow_public_user_to_upload": "å…č¨ąå…Ŧ開äŊŋį”¨č€…ä¸Šå‚ŗ", - "allowed": "å…č¨ą", - "alt_text_qr_code": "QR code åœ–į‰‡", - "always_keep": "一型äŋį•™", - "always_keep_photos_hint": "「釋攞įŠē間」功čƒŊæœƒå°‡æ‰€æœ‰į›¸į‰‡äŋį•™å–ēå‘ĸå€‹čŖįŊŽä¸Šã€‚", - "always_keep_videos_hint": "「釋攞įŠē間」功čƒŊ會將所有åŊąį‰‡äŋį•™å–ēå‘ĸå€‹čŖįŊŽä¸Šéĸ。", - "api_key": "API 密匙", - "api_key_description": "å‘ĸ個密匙åĒäŋ‚æœƒéĄ¯į¤ē一æŦĄã€‚關閉čĻ–įĒ—å‰å””čŠ˛ä¸€åŽščĻå…ˆč¤‡čŖŊ密匙。", - "api_key_empty": "API 密匙個名唔可äģĨįŠēį™Ŋ", - "api_keys": "API 密匙", - "app_architecture_variant": "變éĢ”īŧˆæžļ構īŧ‰", - "app_bar_signout_dialog_content": "äŊ äŋ‚å””äŋ‚įĸē厚čρį™ģå‡ēīŧŸ", - "app_bar_signout_dialog_title": "į™ģå‡ē", - "app_download_links": "App 下čŧ‰é€Ŗįĩ", - "app_settings": "æ‡‰į”¨į¨‹åŧč¨­åޚ", - "app_stores": "æ‡‰į”¨į¨‹åŧå•†åē—", - "app_update_available": "åˇ˛æœ‰æ‡‰į”¨į¨‹åŧæ›´æ–°", - "appears_in": "å‡ēįžå–ē", - "apply_count": "åĨ—ᔍ ({count, number})", - "archive": "封存", - "archive_action_prompt": "厞將 {count} å€‹é …į›Žå°å­˜", - "archive_or_unarchive_photo": "å°å­˜æˆ–č€…å–æļˆå°å­˜į›¸į‰‡", - "archive_size": "封存大ᴰ", - "archive_size_description": "č¨­åŽšå°å­˜ä¸‹čŧ‰å˜…æĒ”æĄˆå¤§į´°īŧˆGiBīŧ‰", - "archived": "厞封存", - "archived_count": "{count, plural, other {封存咗#äģļ}}", - "are_these_the_same_person": "å‘ĸ嚞個äŋ‚å””äŋ‚同一個äēēīŧŸ", - "are_you_sure_to_do_this": "äŊ äŋ‚å””äŋ‚įĸē厚čĻå’åšīŧŸ", - "asset_added_to_album": "厞įļ“加咗čŊᛏį°ŋåēĻ", - "asset_adding_to_album": "加ᎊčŊᛏį°ŋåēĻâ€Ļ", - "asset_created": "厞å‰ĩįĢ‹é …į›Ž", - "asset_day_count": "{date}īŧš {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", - "asset_description_updated": "é …į›Žå˜…æčŋ°æ›´æ–°å’—å•Ļ", - "asset_hashing": "æ­Ŗåœ¨č¨ˆįŽ—é›œæšŠâ€Ļ", - "asset_list_group_by_sub_title": "éģžæ¨Ŗåˆ†éĄž", - "asset_list_layout_settings_group_by": "é …į›Žéģžæ¨Ŗåˆ†éĄž", - "asset_list_layout_settings_group_by_month_day": "月äģŊ + æ—Ĩ期", - "asset_list_layout_sub_title": "į‰ˆéĸ", - "asset_list_settings_subtitle": "ᛏቇį ŧį‰ˆéĸč¨­åŽš", - "asset_list_settings_title": "ᛏቇį ŧ", - "asset_not_found_on_device_android": "čŖįŊŽä¸Šéĸæĩå””åˆ°é …į›Ž", - "asset_not_found_on_device_ios": "čŖįŊŽä¸Šéĸæĩ唔到å‘ĸå€‹é …į›Žã€‚åĻ‚æžœäŊ į”¨įˇŠ iCloudīŧŒå¯čƒŊå–ē因į‚ē iCloud å…Ĩéĸæ“ē咗損毀咗嘅文äģļč€Œå°Žč‡´é …į›Žå­˜å–å””åˆ°", - "asset_not_found_on_icloud": "iCloudå…Ĩéĸæĩ唔到å‘ĸå€‹é …į›Žã€‚å¯čƒŊå–ē因į‚ēiCloudå…Ĩéĸæ“ē咗損毀嘅文äģļå°Žč‡´é …į›Žå†‡čžĻæŗ•å­˜å–", - "asset_offline": "é …į›Žå””åœ¨įˇš", - "asset_offline_description": "įĄŦįĸŸä¸Šéĸæĩ唔到å‘ĸ個外部文äģļã€‚å””čŠ˛č¯įĩĄäŊ å€‹ Immich įŽĄį†å“ĄåĢäŊĸåšĢ手。", - "asset_skipped": "åˇ˛čˇŗéŽ", - "asset_skipped_in_trash": "å–ē垃圞æĄļå…Ĩéĸ", - "asset_troubleshoot": "文äģļč¨ēæ–ˇ", - "asset_uploaded": "åˇ˛ä¸Šå‚ŗ", - "asset_uploading": "ä¸Šå‚ŗä¸­â€Ļ", - "asset_viewer_settings_subtitle": "įŽĄį†æ‚¨å˜…åĒ’éĢ”åēĢæĒĸčĻ–å™¨č¨­åŽš", - "asset_viewer_settings_title": "文äģ￟Ĩé–ąå™¨", - "assets": "é …į›Ž", - "assets_added_to_album_count": "加咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}} å…ĨåŽģå‘ĸ個ᛏį°ŋå…Ĩéĸ", - "assets_added_to_album_partial_count": "厞įˆåŠŸå°‡ {totalCount}{totalCount, plural, one {äģļé …į›Ž} other {äģļé …į›Ž}}å…Ĩéĸ嘅 {successCount} äģļ加å…Ĩᛏį°ŋ", - "assets_added_to_albums_count": "加咗 {assetTotal, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}} å…Ĩ {albumTotal, plural, one {# 個ᛏį°ŋ} other {# 個ᛏį°ŋ}} å…Ĩéĸ", - "assets_cannot_be_added_to_album_count": "加唔到 {count, plural, one {äģļé …į›Ž} other {äģļé …į›Ž}} å…Ĩᛏį°ŋå…Ĩéĸ", - "assets_cannot_be_added_to_albums": "冇čžĻæŗ•åŠ  {count, plural, one {äģļé …į›Ž} other {äģļé …į›Ž}} å…ĨåŽģäģģäŊ•å˜…į›¸į°ŋå…Ĩéĸ", - "assets_count": "{count, plural, one {#äģļé …į›Ž} other {#äģļé …į›Ž}}", - "assets_moved_to_trash_count": "æŧ咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}} å…Ĩ垃圞æĄļå…Ĩéĸ", - "assets_permanently_deleted_count": "永䚅åˆĒ除咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}}", - "assets_removed_count": "į§ģ除咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}}", - "assets_restore_confirmation": "äŊ äŋ‚å””äŋ‚čρæĸ垊垃圞æĄļå…Ĩéĸå˜…æ‰€æœ‰é …į›ŽīŧŸå‘ĸ個動äŊœå–ē冇垗æ”ļčŋ”嘅īŧäģģäŊ•é›ĸįˇšå˜…é …į›Žå””å¯äģĨå’æ¨Ŗæĸ垊。", - "assets_restored_count": "æĸ垊咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}}", - "assets_trashed": "{count}äģļé …į›Žæŧ咗čŊ垃圞æĄļ", - "assets_trashed_count": "æŧ咗 {count, plural, one {# äģļé …į›Ž} other {# äģļé …į›Ž}} å…Ĩ垃圞æĄļå…Ĩéĸ", - "assets_were_part_of_album_count": "{count, plural, one {äģļé …į›Ž} other {äģļé …į›Ž}}厞įļ“å–ē嗰個ᛏį°ŋå…Ĩéĸ", - "assets_were_part_of_albums_count": "{count, plural, one {äģļé …į›Ž} other {äģļé …į›Ž}} 厞įļ“å–ēᛏį°ŋå…Ĩéĸ", - "authorized_devices": "åˇ˛æŽˆæŦŠå˜…čŖįŊŽ", - "automatic_endpoint_switching_subtitle": "å–ēį”¨åˆ°å˜…æ™‚å€™é€éŽæŒ‡åŽšå˜… Wi-Fi æœŦåœ°é€ŖæŽĨīŧŒå–ēå…ļäģ–åœ°æ–šį”¨å…ļäģ–æ›ŋäģŖå˜…įļ˛įĩĄ", - "automatic_endpoint_switching_title": "č‡Ē動切換 URL", - "autoplay_slideshow": "č‡Ēå‹•æ’­æ”žåœ–į‰‡", - "back": "上一頁", - "back_close_deselect": "čŋ”回īŧŒé—œé–‰æˆ–č€…å–æļˆé¸æ“‡", - "background_location_permission": "垌台厚äŊå˜…æŦŠé™", - "background_location_permission_content": "į‚ē咗可äģĨå–ēåžŒå°čĄŒįˇŠå—°é™Ŗæ™‚æ›įļ˛įĩĄīŧŒImmichå–ēäģģäŊ•時間éƒŊ一厚čĻæœ‰į˛žįĸēäŊįŊŽå˜…æŦŠé™īŧŒå’å€‹į¨‹åŧå…ˆå¯äģĨčŽ€å–äŊ į”¨įˇŠ Wi-Fi 個名", - "background_options": "垌台選項", - "backup": "備äģŊ", - "backup_album_selection_page_albums_tap": "ä¸€æ’ŗåŒ…æ‹ŦīŧŒé›™æ’ŗæŽ’除", - "backup_album_selection_page_assets_scatter": "é …į›Žå¯äģĨåˆ†æ•Ŗå–ēåšžå€‹į›¸į°ŋå…Ĩéĸ。所äģĨå–ē備äģŊå€‹éŽį¨‹å…Ĩéĸ可äģĨ包åĢæˆ–č€…æŽ’é™¤æŸå•˛į›¸į°ŋ。", - "backup_album_selection_page_select_albums": "æ€į›¸į°ŋ", - "backup_album_selection_page_selection_info": "é¸æ“‡čŗ‡æ–™", - "backup_albums_sync": "備äģŊᛏį°ŋ同æ­Ĩ", - "backup_background_service_complete_notification": "é …į›Žå‚™äģŊ厌成", - "backup_background_service_default_notification": "æĩįˇŠæ–°é …į›Žâ€Ļ", - "backup_background_service_in_progress_notification": "備äģŊįˇŠæ‚¨å˜…é …į›Žâ€Ļ", - "backup_controller_page_albums": "備äģŊᛏį°ŋ", - "backup_controller_page_background_battery_info_link": "čŠąæˆ‘įŸĨéģžåš", - "backup_controller_page_background_battery_info_message": "į‚ēį˛åž—æœ€åĨŊå˜…čƒŒæ™¯å‚™äģŊéĢ”éŠ—īŧŒčĢ‹åœį”¨äģģäŊ•會限åˆļ Immich čƒŒæ™¯æ´ģ動嘅é›ģæą æœ€äŊŗåŒ–č¨­åŽšã€‚\n\nį”ąæ–ŧäŊĸåŒčŖįŊŽåž‹č™Ÿį›¸é—œīŧŒčĢ‹æŸĨčŠĸæ‚¨čŖįŊŽčŖŊé€ å•†å˜…į›¸é—œčĒĒæ˜Žã€‚", - "backup_controller_page_background_battery_info_ok": "OK", - "backup_controller_page_background_battery_info_title": "é›ģæą æœ€äŊŗåŒ–", - "backup_controller_page_background_delay": "æ–°é …į›Žå‚™äģŊåģļ遲īŧš{duration}", - "backup_controller_page_backup": "備äģŊ", - "backup_controller_page_backup_selected": "åˇ˛é¸å–īŧš ", - "backup_controller_page_backup_sub": "備äģŊå’—å˜…į›¸į‰‡åŒåŊąį‰‡", - "backup_controller_page_excluded": "åˇ˛æŽ’é™¤īŧš ", - "backup_controller_page_none_selected": "æœĒ選取äģģäŊ•é …į›Ž", - "backup_controller_page_remainder": "削餘", - "backup_controller_page_remainder_sub": "é¸å–é …į›Žä¸­æœĒ備äģŊå˜…į›¸į‰‡åŒåŊąį‰‡", - "backup_controller_page_server_storage": "äŧ翜å™¨å„˛å­˜įŠē間", - "backup_controller_page_storage_format": "ᔍ咗 {used} / {total}", - "backup_controller_page_to_backup": "čρ備äģŊå˜…į›¸į°ŋ", - "backup_controller_page_total_sub": "äŊ æ€å˜…ᛏį°ŋå…Ĩéĸæ‰€æœ‰į¨į‰šå˜…į›¸į‰‡åŒåŸ‹åŊąį‰‡", - "backup_error_sync_failed": "同æ­Ĩå¤ąæ•—ã€‚č™•į†å””åˆ°å‚™äģŊ。", - "backup_info_card_assets": "å€‹é …į›Ž", - "backup_options": "備äģŊ選項", - "backup_settings_subtitle": "įŽĄį†ä¸Šå‚ŗå˜…č¨­åŽš", - "backward": "į”ąčˆŠč‡ŗæ–°", - "battery_optimization_backup_reliability": "閂咗é›ģæą æœ€äŊŗåŒ–可äģĨ提éĢ˜åžŒå°å‚™äģŊ嘅可靠性", - "biometric_auth_enabled": "į”Ÿį‰Šč­˜åˆĨåˇ˛å•Ÿį”¨", - "biometric_locked_out": "äŊ čĸĢéŽ–åŽšį„Ąæŗ•äŊŋį”¨į”Ÿį‰Šč­˜åˆĨ", - "biometric_no_options": "į„Ąå¯äģĨį”¨åˆ°å˜…į”Ÿį‰Šč­˜åˆĨ選項", - "biometric_not_available": "å‘ĸå€‹čŖįŊŽå””æ”¯æŒį”Ÿį‰Šč­˜åˆĨ", - "birthdate_set_description": "å‡ēį”Ÿæ—Ĩ期å–ēæ”žåšŸč¨ˆå–ēåŊąå‘ĸåŧĩį›¸å—°é™Ŗæ™‚å‘ĸ個äēēå€‹æ­˛æ•¸ã€‚", - "blurred_background": "čƒŒæ™¯æ¨ĄįŗŠåŒ–", - "browse_templates": "į€čĻŊæ¨Ŗæŋ", - "bugs_and_feature_requests": "å•éĄŒåŒåŠŸčƒŊåģēč­°", - "build": "åģēįŊŽįˇ¨č™Ÿ", - "build_image": "æ˜ åƒį‰ˆæœŦ", - "bulk_delete_duplicates_confirmation": "äŊ äŋ‚å””äŋ‚įĸē厚čρ䏀æŦĄéŽåˆĒ除å‘ĸ{count, plural, one {#個} other {#個}}é‡č¤‡å˜…é …į›ŽīŧŸå‘ĸ個會äŋį•™æ¯įĩ„å…ĨéĸéĢ”įŠæœ€å¤§å˜…é …į›ŽīŧŒåŒåŸ‹æ°¸äš…åˆĒ除å…ļäģ–æ‰€æœ‰é‡č¤‡å˜…é …į›Žã€‚åˆĒ除咗䚋垌å–ē冇垗撤回嘅īŧ", - "bulk_keep_duplicates_confirmation": "äŊ äŋ‚å””äŋ‚įĸē厚čρäŋį•™{count}å€‹é‡č¤‡é …į›ŽīŧŸå‘ĸ個動äŊœå°‡æœƒæ¨™č¨˜æ‰€æœ‰å˜…é‡č¤‡é …į›Žį‚ē厞觪æąēīŧŒč€Œä¸”唔會åˆĒ除äģģäŊ•å˜…é …į›Žã€‚", - "bulk_trash_duplicates_confirmation": "äŊ äŋ‚å””äŋ‚įĸē厚čρ將å‘ĸ一扚{count}å€‹é‡č¤‡å˜…é …į›Žį§ģ到垃圞æĄļīŧŸå‘ĸ個動äŊœæœƒäŋį•™æ¯įĩ„å…ĨéĸéĢ”įŠæœ€å¤§å˜…é …į›ŽīŧŒä¸Ļ且將å…ļäģ–æ‰€æœ‰é‡č¤‡å˜…é …į›ŽæŦ到垃圞æĄļ。", - "buy": "čŗŧ財 Immich", - "camera": "į›¸æŠŸ", - "camera_brand": "į›¸æŠŸå“į‰Œ", - "camera_model": "į›¸æŠŸåž‹č™Ÿ", - "cancel": "取æļˆ", - "cancel_search": "取æļˆæœå°‹", - "canceled": "åˇ˛å–æļˆ", - "canceling": "取æļˆä¸­", - "cannot_merge_people": "合äŊĩ唔到äēēį‰Š", - "cannot_undo_this_action": "å‘ĸ個動äŊœå†‡čžĻæŗ•æ’¤å›žīŧ", - "cannot_update_the_description": "更新唔到描čŋ°", - "cast": "投攞", - "cast_description": "č¨­åŽšå¯į”¨å˜…æŠ•åŊąčŖįŊŽ", - "change": "更攚", - "change_date": "更攚æ—Ĩ期", - "change_description": "更攚描čŋ°", - "change_display_order": "æ›´æ”šéĄ¯į¤ēæŦĄåē", - "change_location": "更攚äŊįŊŽ", - "change_name": "æ›´æ”šåį¨ą", - "change_name_successfully": "æˆåŠŸæ›´æ”šåį¨ą", - "change_password": "更攚密įĸŧ", - "change_password_description": "å‘ĸ個一å–ēäŊ įŦŦ一æŦĄį™ģ錄å‘ĸ個įŗģįĩąīŧŒä¸€å–ēæ”ļ到更攚密įĸŧ嘅čĻæą‚ã€‚čĢ‹å–ē下éĸčŧ¸å…ĨäŊ å˜…新密įĸŧ。", - "change_password_form_confirm_password": "įĸēčĒå¯†įĸŧ", - "change_password_form_description": "äŊ åĨŊ {name}īŧš\n\nå‘ĸ個å–ēäŊ įŦŦ一æŦĄį™ģ錄įŗģįĩąīŧŒæˆ–者æ”ļ到更攚密įĸŧ嘅čĻæą‚ã€‚čĢ‹äŋ‚下éĸčŧ¸å…Ĩ新密įĸŧ。", - "change_password_form_log_out": "將所有å…ļäģ–čŖįŊŽį™ģå‡ē", - "change_password_form_log_out_description": "åģēč­°į™ģå‡ē所有å…ļäģ–čŖįŊŽ", - "change_password_form_new_password": "新密įĸŧ", - "change_password_form_password_mismatch": "密įĸŧ唔對", - "change_password_form_reenter_new_password": "再čŧ¸å…Ĩ新密įĸŧ", - "change_pin_code": "攚 PIN įĸŧ", - "change_your_password": "更攚您嘅密įĸŧ", - "changed_visibility_successfully": "åˇ˛æˆåŠŸčŽŠæ›´å’—å¯čĻ‹æ€§", - "charging": "å‰įˇŠé›ģ", - "charging_requirement_mobile_backup": "čƒŒæ™¯å‚™äģŊ需čĻčŖįŊŽå……č‘—é›ģ", - "check_logs": "æĒĸæŸĨį´€éŒ„", - "checksum": "æ ĄéŠ—įĸŧ", - "choose": "揀", - "choose_matching_people_to_merge": "選擇čρ合äŊĩå˜…į›¸įŦĻäēēį‰Š", - "city": "城市", - "cleanup_confirm_description": "Immich į™ŧįžæœ‰ {count} å€‹é …į›ŽīŧˆåģēįĢ‹æ–ŧ {date} 䚋前īŧ‰åˇ˛åމ免備äģŊ咗臺äŧ翜å™¨ã€‚äŋ‚å’ĒčĻåžžæ­¤čŖįŊŽä¸­åˆĒ除æœŦ抟副æœŦīŧŸ", - "cleanup_confirm_prompt_title": "åžžæ­¤čŖįŊŽåˆĒ除īŧŸ", - "cleanup_deleted_assets": "厞將 {count} å€‹é …į›Žį§ģåˆ°čŖįŊŽå˜…垃圞æĄļ", - "cleanup_deleting": "æ­Ŗåœ¨į§ģ動到垃圞æĄļ...", - "cleanup_found_assets": "扞到 {count} äģļåˇ˛ä¸Šå‚ŗå˜…é …į›Ž", - "cleanup_found_assets_with_size": "扞到 {count} äģļīŧŒį¸Ŋå…ą ({size}) åˇ˛ä¸Šå‚ŗå˜…é …į›Ž", - "cleanup_icloud_shared_albums_excluded": "iCloud å…ąäēĢᛏį°ŋčĸ̿ޒ除æ–ŧ搜尋䚋外", - "cleanup_no_assets_found": "æĩ唔到įŦĻ合上čŋ°æĸäģļå˜…é …į›Žã€‚é‡‹æ”žįŠē間功čƒŊåĒčƒŊį§ģ除厞備äģŊ臺äŧ翜å™¨å˜…é …į›Ž", - "cleanup_preview_title": "{count} 項需čρį§ģé™¤å˜…é …į›Ž", - "cleanup_step3_description": "掃描įŦĻ合æ—ĨæœŸčˆ‡å„˛å­˜č¨­åŽšå˜…åˇ˛å‚™äģŊé …į›Žã€‚", - "cleanup_step4_summary": "å°‡åžžæ­¤čŖįŊŽį§ģ除 {count} 個åģēįĢ‹æ–ŧ {date} äš‹å‰å˜…é …į›Žã€‚æ‚¨äģå¯é€éŽ Immich æ‡‰į”¨į¨‹åŧå­˜å–å‘ĸå•˛į›¸į‰‡ã€‚", - "cleanup_trash_hint": "č‹ĨčĻåžšåē•é‡‹æ”žå„˛å­˜įŠē間īŧŒčĢ‹é–‹å•Ÿįŗģįĩąį›¸į°ŋ App 清įŠē垃圞æĄļ", - "clear": "清įŠē", - "clear_all": "全部清除", - "clear_all_recent_searches": "清除所有最čŋ‘嘅搜尋", - "clear_failed_count": "æ¸…é™¤å¤ąæ•— ({count})", - "clear_file_cache": "清除æĒ”æĄˆåŋĢ取", - "clear_message": "æ¸…é™¤č¨Šæ¯", - "clear_value": "清除å€ŧ", - "client_cert_import": "導å…Ĩ", - "client_cert_import_success_msg": "匯å…Ĩ咗ᔍæˆļįĢ¯æ†‘č­‰", - "client_cert_invalid_msg": "į„Ąæ•ˆå˜…æ†‘č­‰æĒ”æĄˆæˆ–å¯†įĸŧ錯čǤ", - "client_cert_password_message": "čĢ‹čŧ¸å…Ĩæ­¤æ†‘č­‰å˜…å¯†įĸŧ", - "client_cert_password_title": "æ†‘č­‰å¯†įĸŧ", - "client_cert_remove_msg": "į§ģ除咗ᔍæˆļįĢ¯æ†‘č­‰", - "client_cert_subtitle": "æˇ¨äŋ‚支援 PKCS12 (.p12, .pfx) æ ŧåŧã€‚可äģĨäŋ‚į™ģå…Ĩ前匯å…Ĩ或į§ģé™¤æ†‘č­‰", - "client_cert_title": "SSL ᔍæˆļįĢ¯æ†‘č­‰ [å¯Ļ銗性]", - "close": "閂", - "collapse": "æ”ļ埋", - "collapse_all": "å…¨éƒ¨æŠ˜į–Š", - "color": "顏色", - "command": "å‘Ŋäģ¤", - "command_palette_prompt": "åŋĢ速æĩ頁éĸ、動äŊœæˆ–指äģ¤", - "command_palette_to_close": "關閉", - "command_palette_to_navigate": "čŧ¸å…Ĩ", - "command_palette_to_select": "選擇", - "command_palette_to_show_all": "å…¨éƒ¨éĄ¯į¤ē", - "comment_deleted": "ᕙ荀厞åˆĒ除", - "comment_options": "ᕙ荀遏項", - "comments_and_likes": "į•™č¨€åŠå–œæ­Ą", - "comments_are_disabled": "į•™č¨€åˇ˛åœį”¨", - "common_create_new_album": "åģēįĢ‹æ–°į›¸į°ŋ", - "completed": "搞掂", - "configuration": "č¨­åŽš", - "confirm": "įĸēčĒ", - "confirm_admin_password": "įĸēčĒįŽĄį†å“Ąå¯†įĸŧ", - "confirm_delete_face": "您äŋ‚å’Ēįĸē厚čĻåžžčŠ˛é …į›Žä¸­åˆĒ除 {name} 嘅éĸ孔īŧŸ", - "confirm_delete_shared_link": "您äŋ‚å’Ēįĸē厚čρåˆĒ除此分äēĢ逪įĩīŧŸ", - "confirm_keep_this_delete_others": "é™¤æ­¤é …į›Žå¤–īŧŒå †į–Šä¸­å˜…å…ļäģ–é …į›ŽéƒŊ將會čĸĢåˆĒ除。您äŋ‚å’Ēįĸē厚čρįšŧįēŒīŧŸ", - "confirm_new_pin_code": "įĸēčĒæ–° PIN įĸŧ", - "confirm_password": "įĸēčĒå¯†įĸŧ", - "confirm_tag_face": "您äŋ‚å’Ēæƒŗå°‡å‘ĸåĄŠéĸå­”æ¨™įą¤į‚ē {name} īŧŸ", - "confirm_tag_face_unnamed": "您äŋ‚å’Ēæƒŗæ¨™įą¤å‘ĸåĄŠéĸ孔īŧŸ", - "connected_device": "åˇ˛é€ŖįˇščŖįŊŽ", - "connected_to": "åˇ˛é€Ŗįˇšč‡ŗ", - "contain": "į­‰æ¯”å…§į¸Ž", - "context": "脈įĩĄ", - "continue": "įšŧįēŒ", - "control_bottom_app_bar_add_tags": "新åĸžæ¨™įą¤", - "control_bottom_app_bar_delete_from_local": "åžžčŖįŊŽä¸­åˆĒ除", - "control_bottom_app_bar_edit_location": "ᎍčŧ¯äŊįŊŽčŗ‡æ–™", - "control_bottom_app_bar_edit_time": "ᎍčŧ¯æ—ĨæœŸčˆ‡æ™‚é–“", - "control_bottom_app_bar_trash_from_immich": "į§ģ到垃圞æĄļ", - "copied_image_to_clipboard": "åˇ˛å°‡åœ–į‰‡č¤‡čŖŊ到å‰Ēč˛ŧį°ŋ。", - "copied_to_clipboard": "厞複čŖŊ到å‰Ēč˛ŧį°ŋīŧ", - "copy_error": "複čŖŊ錯čǤ", - "copy_image": "複čŖŊåœ–į‰‡", - "copy_json": "複čŖŊ JSON", - "copy_link": "複čŖŊ逪įĩ", - "copy_link_to_clipboard": "複čŖŊ逪įĩåˆ°å‰Ēč˛ŧį°ŋ", - "copy_password": "複čŖŊ密įĸŧ", - "copy_to_clipboard": "複čŖŊ到å‰Ēč˛ŧį°ŋ", - "country": "國åŽļ", - "cover": "åĄĢæģŋ", - "covers": "封éĸ", - "create": "åģēįĢ‹", - "create_album": "åģēį̋ᛏį°ŋ", - "create_album_page_untitled": "æœĒå‘Ŋ名", - "create_api_key": "åģēįĢ‹ API 金鑰", - "create_first_workflow": "åģēįĢ‹įŦŦ一個åˇĨäŊœæĩį¨‹", - "create_library": "åģēįĢ‹åĒ’éĢ”åēĢ", - "create_link": "åģēį̋逪įĩ", - "create_link_to_share": "åģēįĢ‹å…ąäēĢ逪įĩ", - "create_link_to_share_description": "比äģģäŊ•æœ‰é€Ŗįĩå˜…äē翟Ĩé–ąæ‰€æ€å˜…į›¸", - "create_new": "新åĸž", - "create_new_person": "åģēįĢ‹æ–°å˜…äēēį‰Š", - "create_new_person_hint": "å°‡é¸å–å˜…é …į›ŽæŒ‡æ´žäŋžæ–°å˜…äēēį‰Š", - "create_new_user": "åģēįĢ‹æ–°äŊŋᔍ者", - "create_person": "åģēįĢ‹äēēį‰Š", - "create_person_subtitle": "į‚ē所選嘅éĸ孔å‘Ŋ名äģĨ新åĸžåŒæ¨™č¨˜æ–°äēēį‰Š", - "create_shared_album_page_share_add_assets": "新åĸžé …į›Ž", - "create_shared_album_page_share_select_photos": "é¸å–į›¸į‰‡", - "create_shared_link": "åģēįĢ‹åˆ†äēĢ逪įĩ", - "create_tag": "åģēįĢ‹æ¨™įą¤", - "create_tag_description": "åģēįĢ‹æ–°æ¨™įą¤ã€‚åĻ‚æžœčρåģēįĢ‹åˇĸį‹€æ¨™įą¤īŧŒčĢ‹čŧ¸å…Ĩ包åĢæ­Ŗæ–œįˇšå˜…åŽŒæ•´æ¨™įą¤čˇ¯åž‘ã€‚", - "create_user": "åģēįĢ‹äŊŋᔍ者", - "create_workflow": "åģēįĢ‹åˇĨäŊœæĩį¨‹", - "created": "åģēįĢ‹æ–ŧ", - "created_at": "åģēįĢ‹æ–ŧ", - "creating_linked_albums": "åģēį̋逪įĩį›¸į°ŋ ...", - "crop": "誁å‰Ē", - "crop_aspect_ratio_free": "č‡Ēį”ąæ¯”äž‹", - "crop_aspect_ratio_original": "原æĒ”", - "crop_aspect_ratio_square": "æ–šåŊĸ", - "current_device": "į›Žå‰čŖįŊŽ", - "current_pin_code": "į›Žå‰ PIN įĸŧ", - "current_server_address": "į›Žå‰å˜…äŧ翜å™¨äŊå€", - "custom_date": "åĻ揀æ—Ĩ期", - "custom_locale": "č‡Ēč¨‚åœ°å€č¨­åŽš", - "custom_locale_description": "栚據選厚čĒžč¨€åŒåœ°å€æ ŧåŧåŒ–æ—ĨæœŸã€æ™‚é–“čˆ‡æ•¸å­—", - "cutoff_date_description": "äŋį•™æœ€čŋ‘嚞多æ—Ĩå˜…į›¸į‰‡â€Ļ", - "cutoff_day": "{count, plural, one {æ—Ĩ} other {æ—Ĩ}}", - "cutoff_year": "{count, plural, one {åš´} other {åš´}}", - "dark": "æˇąč‰˛", - "dark_theme": "åˆ‡æ›č‡ŗæˇąč‰˛ä¸ģ題", - "date": "æ—Ĩ期", - "date_after": "開始æ—Ĩ期", - "date_and_time": "æ—ĨæœŸčˆ‡æ™‚é–“", - "date_before": "įĩæŸæ—Ĩ期", - "date_of_birth": "å‡ēį”Ÿæ—Ĩ期", - "date_of_birth_saved": "å‡ēį”Ÿæ—Ĩ期成功äŋå­˜", - "date_range": "æ—ĨæœŸį¯„åœ", - "date_time_original": "原始æ—Ĩ期/時間", - "day": "æ—Ĩ", - "days": "æ—Ĩ", - "deduplicate_all": "åˆĒé™¤æ‰€æœ‰å˜…é‡č¤‡é …į›Ž", - "default_quality_subtitle": "分äēĢå—°é™Ŗį”¨å˜…į•ĢčŗĒã€‚é•ˇãŠ’åˆ†äēĢæŽŖå¯äģĨ每æŦĄæ‰‹å‹•č‡Ēåˇąæ€ã€‚", - "default_share_quality": "é č¨­åˆ†äēĢį•ĢčŗĒ", - "delete": "åˆĒ除", - "delete_action_confirmation_message": "äŊ äŋ‚å””äŋ‚įĸē厚čρåˆĒ除å‘ĸå€‹é …į›ŽīŧŸå‘ĸ個動äŊœæœƒå°‡å‘ĸå€‹é …į›ŽæŧčŊäŧ翜å™¨å˜…垃圞æĄļīŧŒä¸Ļ且會問äŊ äŋ‚å””äŋ‚čρäŋ‚æœŦåœ°čŖįŊŽä¸ŠéĸåˆĒ除", - "delete_action_prompt": "厞įļ“åˆĒ除 {count} 項", - "delete_album": "åˆĒ除ᛏį°ŋ", - "delete_api_key_prompt": "äŊ äŋ‚å””äŋ‚įĸē厚čρį§ģ除å‘ĸ個 API 密匙īŧŸ", - "delete_dialog_alert": "å‘ĸå•˛é …į›Žæœƒå–ē Immich äŧ翜å™¨åŒåŸ‹äŊ å˜…čŖįŊŽä¸Šéĸ永䚅åˆĒ除", - "delete_dialog_alert_local": "å‘ĸå•˛é …į›Žå°‡æœƒå–ēäŊ å˜…čŖįŊŽä¸Šéĸ永䚅咁åˆĒ除īŧŒäŊ†äŋ‚䞝į„ļ會äŋį•™å–ē Immich äŧ翜å™¨ä¸Šéĸ", - "delete_dialog_alert_local_non_backed_up": "éƒ¨åˆ†å˜…é …į›Žä잿œĒ備äģŊ到 Immich 嗰åēĻīŧŒå–ēčŖįŊŽä¸ŠéĸåˆĒ除垌將會永䚅æļˆå¤ą", - "delete_dialog_title": "永䚅åˆĒ除", - "delete_duplicates_confirmation": "äŊ äŋ‚å””äŋ‚įĸē厚čĻæ°¸äš…åˆĒ除å‘ĸå•˛é‡č¤‡å˜…é …į›Ž?", - "delete_face": "åˆĒé™¤å—°åĄŠéĸ", - "delete_key": "åˆĒ除密匙", - "delete_library": "åˆĒé™¤čŗ‡æ–™åēĢ", - "delete_link": "åˆĒ除逪įĩ", - "delete_local_action_prompt": "厞å–ēæœŦ抟åˆĒ除咗 {count} å€‹é …į›Ž", - "delete_local_dialog_ok_backed_up_only": "åˆĒé™¤æˇ¨äŋ‚å‚™äģŊå’—å˜…é …į›Ž", - "delete_local_dialog_ok_force": "įĸēčĒåˆĒ除", - "delete_others": "åˆĒ除å…ļäģ–", - "delete_permanently": "永䚅åˆĒ除", - "delete_permanently_action_prompt": "永䚅åˆĒ除咗 {count} å€‹é …į›Ž", - "delete_shared_link": "åˆĒ除分äēĢ逪įĩ", - "delete_shared_link_dialog_title": "åˆĒ除分äēĢ逪įĩ", - "delete_tag": "åˆĒé™¤æ¨™įą¤", - "delete_tag_confirmation_prompt": "您äŋ‚å’Ēįĸē厚čρåˆĒ除「{tagName}ã€æ¨™įą¤īŧŸ", - "delete_user": "åˆĒ除äŊŋᔍ者", - "deleted_shared_link": "厞åˆĒé™¤å’—å…ąäēĢ逪įĩ", - "description": "描čŋ°", - "deselect_all": "取æļˆå…¨é¸", - "details": "čŠŗį´°čŗ‡æ–™", - "direction": "斚向", - "disable": "åœį”¨", - "disabled": "åœį”¨å’—", - "discord": "Discord", - "discover": "æŽĸį´ĸ", - "discovered_devices": "厞į™ŧįžå˜…čŖįŊŽ", - "dismiss_all_errors": "åŋŊį•Ĩ所有錯čǤ", - "display_options": "éĄ¯į¤ē選項", - "display_order": "éĄ¯į¤ē順åē", - "display_original_photos": "éĄ¯į¤ēåŽŸå§‹į›¸į‰‡", - "display_original_photos_setting_description": "æĒĸčĻ–æ™‚åĻ‚æžœåŽŸå§‹æĒ”æĄˆæ”¯æ´įļ˛é æ ŧåŧīŧŒå„Ēå…ˆéĄ¯į¤ēåŽŸåœ–č€Œéžį¸Žåœ–ã€‚å‘ĸ個選項可čƒŊ會降äŊŽčŧ‰å…Ĩ嘅速åēĻ。", - "do_not_show_again": "唔åĨŊå†éĄ¯į¤ēå‘ĸå€‹č¨Šæ¯", - "documentation": "čĒĒæ˜Žæ–‡äģļ", - "done": "厌成", - "download": "下čŧ‰", - "download_canceled": "下čŧ‰å–æļˆå’—", - "download_complete": "下čŧ‰å’—", - "download_enqueue": "厞加å…Ĩ下čŧ‰", - "download_failed": "下čŧ‰å””到", - "download_finished": "下čŧ‰å’—", - "download_include_embedded_motion_videos": "åĩŒå…ĨåŊąį‰‡", - "download_include_embedded_motion_videos_description": "å°‡å‹•æ…‹į›¸į‰‡čŖéĸ內åĩŒå˜…åŊąį‰‡å„˛å­˜į‚ēį¨įĢ‹æĒ”æĄˆ", - "download_notfound": "æĩ唔到下čŧ‰é …į›Ž", - "download_original": "下čŧ‰åŽŸå§‹é …į›Ž", - "download_paused": "下čŧ‰åˇ˛įšĢ停", - "download_settings": "下čŧ‰", - "download_settings_description": "įŽĄį†åŒé …į›Žä¸‹čŧ‰į›¸é—œå˜…設åޚ", - "download_waiting_to_retry": "į­‰åž…é‡čŠĻ", - "downloading": "下čŧ‰įˇŠ", - "downloading_asset_filename": "下čŧ‰įˇŠ {filename}", - "downloading_from_icloud": "åžž iCloud 嗰åēĻ下čŧ‰", - "downloading_media": "下čŧ‰įˇŠåĒ’éĢ”", - "drag_to_reorder": "拖動嚟重新排列", - "drop_files_to_upload": "æą‚å…ļ拖攞文äģļåšŸä¸Šå‚ŗ", - "duplicate": "複čŖŊ", - "duplicate_workflow": "複čŖŊåˇĨäŊœæĩį¨‹", - "duplicates": "é‡č¤‡é …į›Ž", - "duplicates_description": "čĢ‹é€ä¸€æ¨™į¤ē每įĩ„å…Ĩéĸå˜…é‡č¤‡é …į›Žã€‚", - "duration": "æ™‚é•ˇ", - "edit": "ᎍčŧ¯", - "edit_album": "ᎍčŧ¯į›¸į°ŋ", - "edit_avatar": "ᎍčŧ¯é ­åƒ", - "edit_birthday": "ᎍčŧ¯į”Ÿæ—Ĩ", - "edit_date": "ᎍčŧ¯æ—Ĩ期", - "edit_date_and_time": "ᎍčŧ¯æ—Ĩ期同埋時間", - "edit_date_and_time_action_prompt": "厞ᎍčŧ¯ {count} æ¨Ŗæ—Ĩ期時間", - "edit_date_and_time_by_offset": "按偏į§ģ嚟čĒŋ整æ—Ĩ期", - "edit_description": "äŋŽæ”šæčŋ°", - "edit_exclusion_pattern": "ᎍčŧ¯æŽ’é™¤æ¨Ąåŧ", - "edit_faces": "ᎍčŧ¯äēēéĸ", - "edit_key": "ᎍčŧ¯ API 密匙", - "edit_link": "ᎍčŧ¯é€Ŗįĩ", - "edit_location": "ᎍčŧ¯äŊįŊŽ", - "edit_location_action_prompt": "厞ᎍčŧ¯ {count} 個äŊįŊŽ", - "edit_location_dialog_title": "äŊįŊŽ", - "edit_name": "ᎍčŧ¯å", - "edit_people": "ᎍčŧ¯äēēį‰Š", - "edit_tag": "ᎍčŧ¯æ¨™įą¤", - "edit_title": "ᎍčŧ¯æ¨™éĄŒ", - "edit_user": "ᎍčŧ¯į”¨æˆļ", - "edit_workflow": "ᎍčŧ¯åˇĨäŊœæĩį¨‹", - "editor": "ᎍčŧ¯å™¨", - "editor_discard_edits_confirm": "æ”žæŖ„įˇ¨čŧ¯", - "editor_discard_edits_prompt": "äŊ æœ‰æœĒäŋå­˜å˜…äŋŽæ”šīŧŒäŊ äŋ‚å””äŋ‚įĸē厚čρæŧ咗äŊĸīŧŸ", - "editor_discard_edits_title": "äŋ‚å””äŋ‚įĸēåŽšæ”žæŖ„äŋŽæ”šīŧŸ", - "editor_edits_applied_error": "加唔到äŊ å˜…äŋŽæ”š", - "editor_edits_applied_success": "åˇ˛æˆåŠŸäŋŽæ”š", - "editor_flip_horizontal": "æ°´åšŗįŋģčŊ‰", - "editor_flip_vertical": "åž‚į›´įŋģčŊ‰", - "editor_handle_corner": "{corner, select, top_left {åˇĻ上角} top_right {åŗä¸Šč§’} bottom_left {åˇĻ下角} bottom_right {åŗä¸‹č§’} other {å…ļ中一個}}角čŊįš„æŽ§åˆļ手柄", - "editor_handle_edge": "{edge, select, top {頂部} bottom {åē•部} left {åˇĻ側} right {åŗå´} other {å…ļ中一個}} é‚ŠįˇŖįš„æŽ§åˆļ手柄", - "editor_orientation": "斚向", - "editor_reset_all_changes": "é‡č¨­čŽŠæ›´", - "editor_rotate_left": "逆時針旋čŊ‰ 90 åēĻ", - "editor_rotate_right": "順時針旋čŊ‰ 90 åēĻ", - "email": "é›ģéƒĩ", - "email_notifications": "é›ģéƒĩ通įŸĨ", - "empty_folder": "å‘ĸ個文äģļ夞äŋ‚įŠē嘅", - "empty_trash": "清įŠē垃圞æĄļ", - "empty_trash_confirmation": "您äŋ‚å’Ēįĸē厚čĻæ¸…įŠē垃圞æĄļīŧŸå°‡æœƒåžž Immich 永䚅į§ģ除垃圞æĄļčŖĄéĸæ‰€æœ‰å˜…é …į›Žã€‚\nį„Ąåž—é‚„åŽŸå‘ĸ個動äŊœīŧ", - "enable": "å•Ÿį”¨", - "enable_backup": "å•Ÿį”¨å‚™äģŊ", - "enable_biometric_auth_description": "čŧ¸å…Ĩæ‚¨įš„ PIN įĸŧäģĨå•Ÿį”¨į”Ÿį‰Ščž¨č­˜éŠ—č­‰", - "enabled": "åˇ˛å•Ÿį”¨", - "end_date": "įĩæŸæ—Ĩ期", - "enqueued": "åˇ˛æŽ’å…Ĩ隊列", - "enter_wifi_name": "čŧ¸å…Ĩ Wi-Fi 名", - "enter_your_pin_code": "čŧ¸å…Ĩ您嘅 PIN įĸŧ", - "enter_your_pin_code_subtitle": "čŧ¸å…Ĩ您嘅 PIN įĸŧå­˜å–åˇ˛ä¸ŠéŽ–å˜…čŗ‡æ–™å¤ž", - "error": "錯čǤ", - "error_delete_face": "åžžé …į›ŽåˆĒ除éĸ孔時å‡ē錯", - "error_loading_albums": "čŧ‰å…Ĩᛏį°ŋ時å‡ē錯", - "error_loading_image": "åœ–į‰‡čŧ‰å…Ĩå‡ē錯", - "error_loading_partners": "čŧ‰å…ĨčĻĒ友時å‡ē錯īŧš{error}", - "error_retrieving_asset_information": "æ”žå””åˆ°é …į›Žčŗ‡æ–™", - "error_saving_image": "錯čǤīŧš{error}", - "error_tag_face_bounding_box": "æ¨™č¨˜éĸ孔å‡ē錯 - į„Ąæŗ•å–åž—é‚Šį•ŒæĄ†åæ¨™", - "error_title": "å‡ē錯 - æœ‰å•éĄŒ", - "error_while_navigating": "į€čĻŊ唔到åŽģé …į›Ž", - "errors": { - "cannot_navigate_next_asset": "į€čĻŊå””åˆ°č‡ŗä¸‹ä¸€å€‹é …į›Ž", - "cannot_navigate_previous_asset": "į€čĻŊ唔到åŽģä¸Šå€‹é …į›Ž", - "cant_apply_changes": "åĨ—į”¨å””åˆ°čŽŠæ›´", - "cant_change_activity": "{enabled, select, true {åœį”¨} other {å•Ÿį”¨}}唔到æ´ģ動", - "cant_change_asset_favorite": "æ”šå””åˆ°é …į›Žå˜…æ”ļč—į‹€æ…‹", - "cant_change_metadata_assets_count": "攚唔到 {count, plural, other {# å€‹é …į›Ž}} å˜…čŠŽé‡‹čŗ‡æ–™", - "cant_get_faces": "攞唔到éĸ孔", - "cant_get_number_of_comments": "æ”žå””åˆ°į•™č¨€æ•¸é‡", - "cant_search_people": "æĩ唔到äēēį‰Š", - "cant_search_places": "æĩ唔到地斚", - "error_adding_assets_to_album": "å°‡é …į›ŽåŠ å…Ĩᛏį°ŋ時å‡ē錯", - "error_adding_users_to_album": "將äŊŋį”¨č€…åŠ å…Ĩᛏį°ŋ時å‡ē錯", - "error_deleting_shared_user": "åˆĒé™¤å…ąäēĢäŊŋį”¨č€…æ™‚å‡ē錯", - "error_downloading": "下čŧ‰ {filename} 時å‡ē錯", - "error_hiding_buy_button": "隱藏čŗŧč˛ˇæŒ‰éˆ•æ™‚å‡ē錯", - "error_removing_assets_from_album": "å–ēᛏį°ŋį§ģé™¤é …į›Žæ™‚å‡ē錯īŧŒčĢ‹æĒĸæŸĨä¸ģ控台åŽģäē†č§ŖčŠŗį´°čŗ‡č¨Š", - "error_selecting_all_assets": "選取所有æĒ”æĄˆæ™‚å‡ē錯", - "exclusion_pattern_already_exists": "å‘ĸå€‹æŽ’é™¤æ¨Ąåŧåˇ˛å­˜åœ¨ã€‚", - "failed_to_create_album": "åģēįĢ‹å””åˆ°į›¸į°ŋ", - "failed_to_create_shared_link": "åģēįĢ‹å””åˆ°åˆ†äēĢ逪įĩ", - "failed_to_edit_shared_link": "ᎍčŧ¯å””到分äēĢ逪įĩ", - "failed_to_get_people": "攞唔到äēēį‰Š", - "failed_to_keep_this_delete_others": "äŋį•™å””到å‘ĸå€‹é …į›ŽåŒåˆĒ除唔到å…ļäģ–é …į›Ž", - "failed_to_load_asset": "čŧ‰å…Ĩå””åˆ°é …į›Ž", - "failed_to_load_assets": "čŧ‰å…Ĩå””åˆ°é …į›Ž", - "failed_to_load_notifications": "čŧ‰å…Ĩ唔到通įŸĨ", - "failed_to_load_people": "čŧ‰å…Ĩ唔到äēēį‰Š", - "failed_to_remove_product_key": "į§ģ除唔到į”ĸ品金鑰", - "failed_to_reset_pin_code": "é‡č¨­å””åˆ° PIN įĸŧ", - "failed_to_stack_assets": "å †į–Šå””åˆ°é …į›Ž", - "failed_to_tag_assets": "æ¨™č¨˜å””åˆ°čŗ‡æē", - "failed_to_unstack_assets": "č§Ŗé™¤å””åˆ°é …į›Žå †į–Š", - "failed_to_update_notification_status": "更新唔到通įŸĨį‹€æ…‹", - "incorrect_email_or_password": "é›ģ子éƒĩäģ￈–密įĸŧå””å˛Š", - "library_folder_already_exists": "å‘ĸ個匯å…Ĩčˇ¯åž‘åˇ˛å­˜åœ¨ã€‚", - "page_not_found": "æĩ唔到到頁éĸ", - "paths_validation_failed": "éŠ—č­‰å””åˆ° {paths, plural, one {# å€‹čˇ¯åž‘} other {# å€‹čˇ¯åž‘}}", - "profile_picture_transparent_pixels": "個äēēčŗ‡æ–™åœ–į‰‡å””å¯äģĨ有透明į•Ģį´ ã€‚čĢ‹æ”žå¤§åŒ/或į§ģå‹•åœ–į‰‡ã€‚", - "quota_higher_than_disk_size": "æ‚¨č¨­åŽšå˜…é…éĄå¤§éŽįŖįĸŸåŽšé‡", - "something_went_wrong": "å‡ē錯", - "unable_to_add_album_users": "將äŊŋį”¨č€…åŠ å””åˆ°å…Ĩᛏį°ŋ", - "unable_to_add_assets_to_shared_link": "å°‡é …į›ŽåŠ å””åˆ°å…Ĩč‡ŗåˆ†äēĢ逪įĩ", - "unable_to_add_comment": "新åĸžå””åˆ°į•™č¨€", - "unable_to_add_exclusion_pattern": "新åĸžå””åˆ°æŽ’é™¤æ¨Ąåŧ", - "unable_to_add_partners": "新åĸžå””到čĻĒ友", - "unable_to_add_remove_archive": "é …į›Ž{archived, select, true {į§ģ唔čĩ°} other {加唔到å…Ĩ}}封存", - "unable_to_add_remove_favorites": "å–ēæ”ļ藏å…Ĩéĸ{favorite, select, true {加唔到} other {åˆĒ除唔到}}å€‹é …į›Ž", - "unable_to_archive_unarchive": "{archived, select, true {封存} other {封存取æļˆ}}唔到", - "unable_to_change_album_user_role": "æ”šå””åˆ°į›¸į°ŋᔍæˆļå˜…č§’č‰˛", - "unable_to_change_date": "攚唔到æ—Ĩ期", - "unable_to_change_description": "攚唔到描čŋ°", - "unable_to_change_favorite": "æ”šå””åˆ°é …į›Žå˜…æ”ļč—į‹€æ…‹", - "unable_to_change_location": "攚唔到äŊįŊŽ", - "unable_to_change_password": "攚唔到密įĸŧ", - "unable_to_change_visibility": "攚唔到 {count} 個äēē嘅可čĻ‹æ€§", - "unable_to_complete_oauth_login": "厌成唔到 OAuth į™ģå…Ĩ", - "unable_to_connect": "į„Ąæŗ•é€Ŗįˇš", - "unable_to_delete_user": "åˆĒé™¤å””åˆ°į”¨æˆļ", - "unable_to_delete_workflow": "åˆĒ除唔到åˇĨäŊœæĩį¨‹", - "unable_to_download_files": "下čŧ‰å””到文äģļ" - }, - "errors_text": "錯čǤ", - "exclusion_pattern": "æŽ’é™¤æ¨Ąåŧ", - "exif": "Exif", - "exif_bottom_sheet_description": "加描čŋ°...", - "exif_bottom_sheet_no_description": "冇描čŋ°", - "exit_slideshow": "é›ĸ開åšģį‡ˆį‰‡", - "expand": "åą•é–‹", - "expand_all": "åą•é–‹æ™’", - "expire_after": "åšžæ™‚å¤ąæ•ˆ", - "expired": "過咗期", - "expires_date": "å–ē {date} 過期", - "explore": "æŽĸį´ĸ", - "explorer": "į¸ŊčĻŊ", - "export": "匯å‡ē", - "export_database": "匯å‡ēčŗ‡æ–™åēĢ", - "exposure_time": "曝光時間", - "extension": "副æĒ”名", - "external": "外部", - "external_libraries": "外部åĒ’éĢ”åēĢ", - "external_network": "外部įļ˛įĩĄ", - "f_number": "光圈", - "face_unassigned": "æœĒ指洞", - "failed": "å¤ąæ•—", - "failed_count": "å¤ąæ•—å’— {count} æŦĄ", - "favorite": "æ”ļ藏", - "favorites": "æ”ļč—å¤ž", - "features": "功čƒŊ", - "file_name_text": "æĒ”æĄˆå", - "file_size": "æĒ”æĄˆå¤§į´°", - "filename": "æĒ”æĄˆå", - "filetype": "æĒ”æĄˆéĄžåž‹", - "filter": "į¯Šé¸", - "filter_people": "į¯Šé¸äēēį‰Š", - "filter_places": "į¯Šé¸åœ°éģž", - "filter_tags": "į¯Šé¸æ¨™įą¤", - "filters": "į¯Šé¸å™¨", - "first": "įŦŦ一個", - "focal_length": "į„Ļ距", - "folder": "čŗ‡æ–™å¤ž", - "folders": "čŗ‡æ–™å¤ž", - "forward": "į”ąæ–°åˆ°čˆŠ", - "gcast_enabled": "Google Cast", - "general": "一čˆŦ", - "get_help": "æĩåšĢ手", - "getting_started": "開始äŊŋᔍ", - "go_back": "čŋ”åŽģ", - "gps": "GPS", - "gps_missing": "冇 GPS", - "grant_permission": "授æŦŠ", - "group_no": "唔分įĩ„", - "haptic_feedback_title": "震動回應", - "has_quota": "č¨­åŽšé…éĄ", - "hash_asset": "é›œæšŠé …į›Ž", - "hashed_assets": "é›œæšŠå’—å˜…é …į›Ž", - "hashing": "č¨ˆįˇŠé›œæšŠå€ŧ", - "header_settings_add_header_tip": "加標頭", - "header_settings_header_name_input": "標頭名", - "header_settings_header_value_input": "標頭å€ŧ", - "height": "é̘åēĻ", - "hide_gallery": "éšąč—į›¸į°ŋ", - "hide_password": "過äŊå¯†įĸŧ", - "hide_person": "隱藏äēēį‰Š", - "hide_schema": "隱藏æžļ構", - "host": "ä¸ģ抟", - "hour": "鐘頭", - "hours": "鐘頭", - "id": "ID", - "idle": "閒įŊŽ", - "image": "ᛏ", - "immich_logo": "Immich Logo", - "import_path": "導å…Ĩčˇ¯åž‘", - "in_archive": "封存咗", - "in_year": "{year}åš´", - "in_year_selector": "å–ē", - "include_archived": "包åĢ封存咗嘅", - "individual_share": "å–Žį¨åˆ†äēĢ", - "individual_shares": "å–Žį¨åˆ†äēĢ", - "info": "čŗ‡æ–™", - "integrity_checks": "厌整性æĒĸæŸĨ", - "invalid_date": "į„Ąæ•ˆå˜…æ—Ĩ期", - "invite_people": "邀čĢ‹äēē", - "iso": "ISO", - "jobs": "äģģ務", - "keep": "äŋį•™", - "keep_albums": "äŋį•™į›¸į°ŋ", - "keep_all": "į•™æ›Ŧ", - "keep_favorites": "äŋį•™æ”ļ藏", - "keeping": "äŋį•™īŧš{items}", - "keyboard_shortcuts": "éĩį›¤å˜…åŋĢæˇéĩ", - "language": "čĒžč¨€", - "language_search_hint": "æĩčĒžč¨€...", - "large_files": "大äģļæĒ”æĄˆ", - "last": "最垌一個", - "last_seen": "æœ€åžŒåœ¨įˇš", - "latest_version": "æœ€æ–°į‰ˆæœŦ", - "latitude": "᎝åēĻ", - "leave": "é›ĸ開", - "leave_album": "é›ĸ開ᛏį°ŋ", - "lens_model": "éĄé ­åž‹č™Ÿ", - "less": "æ”ļ埋", - "level": "į­‰į´š", - "library": "åĒ’éĢ”åēĢ", - "library_add_folder": "æ–°åŠ å€‹čŗ‡æ–™å¤ž", - "library_edit_folder": "ᎍčŧ¯čŗ‡æ–™å¤ž", - "library_page_sort_created": "åģēįĢ‹å˜…æ—Ĩ期", - "library_page_sort_last_modified": "上æŦĄäŋŽæ”š", - "library_page_sort_title": "ᛏį°ŋæ¨™éĄŒ", - "licenses": "授æŦŠ", - "light": "æˇē色", - "like": "鍞意", - "like_deleted": "取æļˆä¸­æ„", - "link": "逪įĩ", - "list": "清喎", - "live": "圍᎚", - "load_more": "čŧ‰å…Ĩ更多", - "loading": "čŧ‰å…Ĩᎊ", - "local": "æœŦ抟", - "local_assets": "æœŦæŠŸé …į›Ž", - "local_id": "æœŦ地 ID", - "local_network": "æœŦ抟įļ˛įĩĄ", - "location": "äŊįŊŽ", - "location_permission": "äŊįŊŽæŦŠé™", - "lock": "鎖厚", - "locked_folder": "éŽ–åŽšå’—čŗ‡æ–™å¤ž", - "log_detail_title": "č¨˜éŒ„čŠŗį´°čŗ‡æ–™", - "log_out": "į™ģå‡ē", - "login": "į™ģå…Ĩ", - "login_form_email_hint": "é›ģéƒĩ地址", - "login_form_endpoint_hint": "http://äŊ å˜…äŧ翜å™¨åœ°å€īŧšé€ŖæŽĨ埠", - "login_form_err_invalid_email": "é›ģéƒĩį„Ąæ•ˆ", - "login_form_err_invalid_url": "į„Ąæ•ˆå˜… URL", - "login_form_err_leading_whitespace": "開頭唔可äģĨ有įŠēæ ŧ", - "login_form_err_trailing_whitespace": "įĩå°žå””可äģĨ有įŠēæ ŧ", - "login_form_password_hint": "密įĸŧ", - "logs": "į´€éŒ„", - "longitude": "įļ“åēĻ", - "look": "æ¨Ŗč˛Œ", - "loop_videos": "åžĒį’°æ’­æ”žæĸį‰‡", "main_menu": "ä¸ģ選喎", "maintenance_action_restore": "é‚„åŽŸįˇŠæ•¸æ“šåēĢ", - "maintenance_restore_library_description": "é‚„åŽŸįˇŠčŗ‡æ–™åēĢ", - "maintenance_restore_library_folder_read_fail": "čŽ€å–å””åˆ°", - "maintenance_restore_library_folder_write_fail": "å¯Ģ唔到", - "maintenance_title": "æšĢæ™‚į”¨å””åˆ°", - "make": "į‰Œå­", - "manage_geolocation": "įŽĄį†äŊįŊŽ", - "manage_media_access_settings": "æ‰“é–‹č¨­åŽš", - "map": "地圖", - "map_settings": "åœ°åœ–č¨­åŽš", - "map_settings_dark_mode": "æˇąč‰˛æ¨Ąåŧ", - "map_settings_date_range_option_year": "1嚴前", - "map_settings_include_show_archived": "包åĢå°å­˜å’—å˜…é …į›Ž", - "map_settings_include_show_partners": "包åĢčĻĒ友", - "map_settings_theme_settings": "地圖ä¸ģ題", - "matches": "ᛏįŦĻ", - "matching_assets": "įŦĻåˆå˜…é …į›Ž", - "media_chrome": { - "auto": "č‡Ē動", - "captions": "å­—åš•", - "captions_off": "閂咗", - "closed_captions": "éšąč—å­—åš•", - "disable_captions": "é–‚å­—åš•", - "enable_captions": "é–‹å­—åš•", - "loop": "é‡č¤‡æ’­æ”ž", - "media_loading": "åĒ’éĢ”čŧ‰å…Ĩᎊ", - "mute": "éœéŸŗ", - "network_error": "įļ˛įĩĄéŒ¯čǤ", - "playback_rate": "播攞速åēĻ", - "playback_time": "播攞時間", - "quality": "į•ĢčŗĒ", - "second": "į§’", - "seconds": "į§’", - "time_value_remaining": "削čŋ” {time}", - "unmute": "開čŋ”聲", - "video_player": "åŊąį‰‡æ’­æ”žå™¨", - "volume": "éŸŗé‡" - }, - "media_type": "åĒ’éĢ”éĄžåž‹", - "memories": "回æ†ļ", - "memories_start_over": "重新開始", - "memory": "回æ†ļ", - "menu": "選喎", - "merge": "合äŊĩ", - "merge_people": "合äŊĩäēēį‰Š", - "minFaces": "最少äēēéĸ數量", - "minimize": "į¸Žį´°", - "minute": "分鐘", - "minutes": "分鐘", - "missing": "įŧēå¤ą", - "mobile_app": "æ‰‹æŠŸį¨‹åŧ", - "model": "åž‹č™Ÿ", - "modify_date": "äŋŽæ”šæ—Ĩ期", - "month": "月", - "more": "更多", - "motion": "動äŊœ", - "move": "į§ģ動", - "move_to": "æŦåŽģ", - "mute_memories": "回æ†ļéœéŸŗ", - "my_albums": "æˆ‘å˜…į›¸į°ŋ", - "name": "名", - "navigate": "導čˆĒ", - "network_requirements": "įļ˛įĩĄčĻæą‚", - "networking_settings": "įļ˛įĩĄč¨­åޚ", - "never": "æ°¸å””å¤ąæ•ˆ", - "new_album": "æ–°į›¸į°ŋ", - "new_feature": "新功čƒŊ", - "new_password": "新密įĸŧ", - "new_person": "新嘅äēēį‰Š", - "new_update": "新更新", - "newest_first": "į”ąæ–°åˆ°čˆŠ", - "next": "下一æ­Ĩ", - "next_memory": "ä¸‹å€‹å›žéĄ§", - "no": "åĻ", - "no_name": "冇名", - "no_notifications": "冇通įŸĨ", - "no_places": "冇地斚", - "no_results": "冇įĩæžœ", - "none": "冇", - "not_allowed": "唔垗", - "not_available": "å””éŠį”¨", - "not_selected": "æœĒ揀", - "notes": "提į¤ē", - "notification_backup_reliability": "啟動通įŸĨ嚟提升垌台備äģŊ嘅可靠性", - "notification_enabled_list_tile_open_button": "æ‰“é–‹č¨­åŽš", - "notification_enabled_list_tile_title": "å•Ÿį”¨å’—é€šįŸĨ", - "notification_permission_list_tile_enable_button": "å•“į”¨é€šįŸĨ", - "notification_permission_list_tile_title": "通įŸĨæŦŠé™", - "notifications": "通įŸĨ", - "notifications_setting_description": "įŽĄį†é€šįŸĨ", - "oauth": "OAuth", - "obtainium_configurator": "Obtainium č¨­åŽšåˇĨå…ˇ", - "ocr": "OCR", - "offline": "é›ĸ᎚", - "offset": "偏į§ģ", - "ok": "įĸē厚", - "oldest_first": "į”ąčˆŠč‡ŗæ–°", - "onboarding": "新手指南", "onboarding_user_welcome_description": "æˆ‘å“‹č€ŒåŽļ開始喇īŧ", "onboarding_welcome_user": "æ­ĄčŋŽīŧŒ{user}", "online": "厞䏊᎚", - "only_favorites": "æˇ¨äŋ‚éĄ¯į¤ē最愛", + "only_favorites": "åĒéĄ¯į¤ē最愛", "open": "開", - "open_calendar": "打開æ—Ĩ曆", "open_in_map_view": "į”¨åœ°åœ–é–‹", "open_in_openstreetmap": "ᔍ OpenStreetMap 開", "open_the_search_filters": "開搜尋過æŋžå™¨", "options": "選項", "or": "æˆ–č€…", "organize_into_albums": "åŸˇæˆį›¸į°ŋ", - "orientation": "斚向", - "original": "原圖", - "other": "å…ļäģ–", - "other_devices": "å…ļäģ–čŖįŊŽ", - "other_variables": "å…ļäģ–čŽŠæ•¸", - "owned": "擁有", - "owner": "æ“æœ‰č€…", - "page": "頁", - "partner": "čĻĒ友", - "partner_list_user_photos": "{user} 啞ᛏ", - "partner_page_select_partner": "選擇čĻĒ友", - "partner_sharing": "čĻĒå‹å…ąäēĢ", - "partners": "čĻĒ友", - "password": "密įĸŧ", - "password_required": "čρ坆įĸŧ", - "path": "čˇ¯åž‘", - "pattern": "æ¨Ąåŧ", - "pause": "停咗", - "pause_memories": "æšĢ停回æ†ļ", - "paused": "停咗", - "pending": "åž…č™•į†", - "people": "äēēį‰Š", - "permanently_delete": "永䚅åˆĒ除", - "permission": "æŦŠé™", - "person": "äēēį‰Š", - "person_recognized": "識åˆĨ到äēēį‰Š", - "photos": "ᛏ", - "pick_custom_range": "č‡Ēåˇąæ€į¯„åœ", - "place": "地éģž", - "places": "地éģž", - "play": "播攞", - "play_memories": "播攞回æ†ļ", - "plugin_method_filter_type": "į¯Šé¸", - "port": "逪æŽĨ埠", - "preferences_settings_title": "偏åĨŊ", - "preparing": "æē–å‚™įˇŠ", - "preset": "預設", - "preview": "預čĻŊ", - "previous": "上一åŧĩ", - "previous_memory": "上一åŧĩ回æ†ļ", - "previous_or_next_day": "ᐴæ—Ĩ/čŊæ—Ĩ", - "previous_or_next_month": "上個月/下個月", - "previous_or_next_photo": "上åŧĩᛏ/下åŧĩᛏ", - "previous_or_next_year": "上嚴/下嚴", - "primary": "éĻ–čρ", - "privacy": "į§éšą", - "profile": "ᔍæˆļčŠŗæƒ…", - "profile_drawer_app_logs": "į´€éŒ„", - "profile_drawer_github": "GitHub", - "public_album": "å…Ŧ開ᛏį°ŋ", - "public_share": "å…Ŧ開分äēĢ", - "purchase_account_info": "æ”¯æŒč€…", - "purchase_button_activate": "å•Ÿį”¨", - "purchase_button_buy": "財", - "purchase_button_buy_immich": "財 Immich", - "purchase_button_remove_key": "į§ģ除密匙", - "purchase_button_select": "揀", - "purchase_individual_description_2": "æ”¯æŒč€…į‹€æ…‹", - "purchase_individual_title": "個äēē", - "purchase_lifetime_description": "įĩ‚čēĢæŽˆæŦŠ", - "purchase_option_title": "čŗŧ買選項", - "purchase_panel_info_1": "Immich 嘅持įēŒé–‹į™ŧ需čρåĨŊå¤šå˜…æ™‚é–“åŒåŸ‹į˛žåŠ›īŧŒčƒŒåžŒäēĻéƒŊæœ‰å…¨čˇåˇĨፋå¸Ģį˛žį›Šæą‚į˛žå˜…æ”šå–„åŒį™ŧåą•ã€‚æˆ‘å“‹å˜…äŊŋå‘Ŋå–ē推動開æēčģŸäģļåŒč‰¯åŋƒäŧæĨ­å¯Ļ踐īŧŒį‚ē開į™ŧč€…æäž›å¯æŒįēŒå˜…æ”ļå…ĨīŧŒåģēįĢ‹ä¸€å€‹å°Šé‡į§éšąå˜…į”Ÿæ…‹į’°åĸƒīŧŒæäž›å¯äģĨįœŸæ­ŖäģŖæ›ŋå‰å‰Šæ€§é›˛įĢ¯æœå‹™å˜…é¸æ“‡ã€‚", - "purchase_per_server": "每部äŧ翜å™¨", - "purchase_per_user": "æ¯å€‹į”¨æˆļ", - "purchase_server_description_2": "æ”¯æŒč€…į‹€æ…‹", - "purchase_server_title": "äŧ翜å™¨", - "queue_status": "č™•į†įˇŠ {count}/{total}", - "rate_asset": "é …į›ŽčŠ•åˆ†", - "rating": "æ˜Ÿį´š", - "rating_clear": "æ¸…é™¤čŠ•åˆ†", - "reaction_options": "反映選項", - "read_changelog": "į‡æ›´æ–°į´€éŒ„", - "reassign": "é‡æ–°įˇ¨é…", - "recent": "最čŋ‘", - "recent_searches": "最čŋ‘æĩįˇŠå˜…é …į›Ž", - "recently_added": "čŋ‘期加å…Ĩ嘅", - "recently_added_page_title": "čŋ‘期加å…Ĩ嘅", - "recently_added_title": "最čŋ‘新加嘅", - "recently_taken": "čŋ‘期åŊąå˜…", - "refresh": "é‡æ–°æ•´į†", - "refresh_faces": "é‡æ–°æ•´į†äēēéĸ", - "refresh_metadata": "é‡æ–°æ•´į†å…ƒæ•¸æ“š", - "refresh_thumbnails": "é‡æ–°æ•´į†į¸Žåœ–", - "refreshed": "æ•´į†åŽŒ", - "refreshing_faces": "æ•´į†įˇŠäēēéĸ", - "refreshing_metadata": "æ•´į†įˇŠå…ƒæ•¸æ“š", - "regenerating_thumbnails": "é‡æ–°į”ŸæˆįˇŠį¸Žåœ–", - "remote": "遙距", - "remote_assets": "遠įĢ¯é …į›Ž", - "remove": "į§ģ除", - "remove_assets_title": "į§ģé™¤é …į›ŽīŧŸ", - "remove_filter": "į§ģé™¤į¯Šé¸", - "remove_memory": "į§ģ除記æ†ļ", - "remove_tag": "į§ģé™¤æ¨™įą¤", - "remove_user": "į§ģé™¤į”¨æˆˇ", - "removed_memory": "į§ģé™¤å’—č¨˜æ†ļ", - "rename": "重新å‘Ŋ名", - "repository": "å„˛å­˜åēĢ", - "require_password": "čρ坆įĸŧ", - "rescan": "重新掃描", - "reset": "重設", - "reset_password": "é‡č¨­å¯†įĸŧ", - "reset_sqlite_clear_app_data": "æ¸…é™¤čŗ‡æ–™", - "resolution": "č§ŖåƒåēĻ", - "resolve_duplicates": "č§Ŗæąē重複項", - "restore": "還原", - "restore_all": "全部垊原", - "restore_user": "é‚„åŽŸį”¨æˆļ", - "restored_asset": "還原咗æĒ”æĄˆ", - "resume": "įšŧįēŒ", - "review_duplicates": "æĒĸæŸĨé‡č¤‡é …į›Ž", - "role": "角色", - "role_editor": "ᎍčŧ¯", - "role_viewer": "éŊ‹į‡", - "running": "é‹čĄŒįˇŠ", - "save": "å„˛å­˜", - "saved": "åˇ˛å„˛å­˜", - "saved_profile": "å„˛å­˜å’—å€‹äēēčŗ‡æ–™", - "saved_settings": "äŋå­˜æ›Ŧč¨­åŽš", - "say_something": "čŦ›ä¸‹å˜ĸ", - "scaffold_body_error_occurred": "į™ŧį”ŸéŒ¯čǤ", - "scan": "掃描", - "scan_library": "掃描", - "scanning": "æŽƒæįˇŠ", - "search": "搜尋", - "search_albums": "æĩᛏį°ŋ", - "search_by_ocr_example": "å’–å•Ą", - "search_city": "æĩ城市...", - "search_country": "æĩ國åŽļ...", - "search_filter_apply": "äŊŋį”¨į¯Šé¸", - "search_filter_date": "æ—Ĩ期", - "search_filter_display_options": "éĄ¯į¤ē選項", - "search_filter_location": "äŊįŊŽ", - "search_filter_location_title": "選擇äŊįŊŽ", - "search_filter_media_type": "åĒ’éĢ”éĄžåž‹", - "search_filter_people_title": "選擇äēēį‰Š", - "search_filter_star_rating": "čŠ•åˆ†", - "search_filter_tags_title": "æ€æ¨™įą¤", - "search_for": "搜尋", - "search_no_people": "æĩ唔到äēē", - "search_options": "搜尋選項", - "search_page_view_all_button": "į‡æ›Ŧ", - "search_people": "æĩäēēį‰Š", - "search_places": "æĩ地éģž", - "search_settings": "æœå°‹č¨­åŽš", - "search_state": "搜尋地區...", - "search_tags": "æĩæ¨™įą¤...", - "search_timezone": "æĩ時區...", - "search_type": "æĩéĄžåž‹", - "searching_locales": "æĩ地區...", - "second": "į§’", - "select": "選擇", - "select_album": "æ€į›¸į°ŋ", - "select_albums": "æ€į›¸į°ŋ", - "select_all": "揀æ›Ŧ", - "select_face": "揀äēēéĸ", - "select_people": "揀äēē", - "select_photos": "æ€į›¸", - "select_quality": "揀į•ĢčŗĒ", - "selected": "揀咗", - "send_message": "å‚ŗč¨Šæ¯", - "server_endpoint": "äŧ翜å™¨į̝éģž", - "server_info_box_app_version": "App į‰ˆæœŦ", - "server_info_box_server_url": "äŧ翜å™¨įļ˛å€", - "server_offline": "äŧ翜å™¨é›ĸå’—įˇš", - "server_online": "äŧ翜å™¨åœ¨įˇš", - "server_privacy": "äŧ翜å™¨į§éšą", - "server_stats": "äŧ翜å™¨įĩąč¨ˆ", - "server_version": "厜åŽļå˜…į‰ˆæœŦ", - "set": "č¨­åŽš", - "setting_image_navigation_title": "åœ–į‰‡åŧ•å°Ž", - "setting_languages_apply": "åĨ—ᔍ", - "setting_notifications_notify_minutes": "{count} 分鐘", "setting_notifications_notify_seconds": "{count} į§’", - "setting_video_viewer_looping_title": "é‡č¤‡æ’­æ”ž", - "settings": "č¨­åŽš", - "settings_saved": "äŋå­˜æ›Ŧč¨­åŽš", - "share": "分äēĢ", - "share_dialog_preparing": "æē–å‚™įˇŠ...", - "share_link": "分äēĢ逪įĩ", - "shared": "å…ąäēĢ", - "shared_album_activity_remove_title": "åˆĒ除æ´ģ動", - "shared_album_section_people_title": "äēēį‰Š", - "shared_by": "å…ąäēĢč‡Ē", - "shared_link_app_bar_title": "分äēĢ逪įĩ", - "shared_link_custom_url_title": "č‡Ē厚 URL", - "shared_link_edit_expire_after_option_day": "1æ—Ĩ", - "shared_link_edit_expire_after_option_days": "{count}æ—Ĩ", - "shared_link_edit_expire_after_option_hour": "1 個鐘", - "shared_link_edit_expire_after_option_hours": "{count} į˛’é˜", - "shared_link_edit_expire_after_option_minute": "1 分鐘", - "shared_link_edit_expire_after_option_minutes": "{count} 分鐘", - "shared_link_edit_expire_after_option_months": "{count} 個月", - "shared_link_edit_expire_after_option_year": "{count} åš´", - "shared_link_edit_submit_button": "æ›´æ–°é€Ŗįĩ", - "shared_link_expires_never": "永不過期", - "shared_link_individual_shared": "個äēēå…ąäēĢ", - "shared_link_info_chip_metadata": "Exif", - "shared_links": "分äēĢ逪įĩ", - "sharing": "å…ąäēĢ", - "show_albums": "éĄ¯į¤ēᛏį°ŋ", - "show_gallery": "éĄ¯į¤ēåĒ’éĢ”åēĢ", - "show_metadata": "éĄ¯į¤ē元數據", - "show_password": "éĄ¯į¤ē密įĸŧ", - "show_schema": "éĄ¯į¤ēæžļ構", - "show_supporter_badge": "æ”¯æŒč€…åžŊįĢ ", - "show_supporter_badge_description": "åą•į¤ēæ”¯æŒč€…åžŊįĢ ", - "shuffle": "隨抟排列", - "sidebar": "側æŦ„", - "sign_out": "į™ģå‡ē", - "sign_up": "č¨ģ冊", - "size": "大ᴰ", - "skip": "čˇŗéŽ", - "slideshow": "åšģį‡ˆį‰‡", - "slideshow_metadata_overlay_mode": "čĻ†č“‹å…§åŽš", - "slideshow_metadata_overlay_mode_description_only": "æˇ¨äŋ‚描čŋ°", - "slideshow_metadata_overlay_mode_full": "厌整", - "slideshow_repeat": "é‡č¤‡æŠ•åŊąį‰‡", - "slideshow_settings": "åšģį‡ˆį‰‡č¨­åŽš", - "slideshow_title": "åšģį‡ˆį‰‡", - "smart_album": "æ™ēčƒŊᛏį°ŋ", - "sort_created": "åģēįĢ‹æ—Ĩ期", - "sort_modified": "äŋŽæ”šå˜…æ—Ĩ期", - "sort_oldest": "æœ€čˆŠå˜…į›¸į‰‡", - "sort_title": "æ¨™éĄŒ", - "source": "來æē", - "stack": "å †į–Š", - "stack_action_prompt": "å †į–Šå’— {count} å€‹é …į›Ž", - "stack_duplicates": "å †į–Šé‡č¤‡é …į›Ž", - "stacktrace": "å †į–ŠčŋŊ蚤", - "start": "開始", - "start_date": "開始æ—Ĩ期", - "state": "地區", - "status": "į‹€æ…‹", - "step_delete": "åˆĒ除æ­Ĩ驟", - "step_details": "æ­ĨéŠŸčŠŗæƒ…", - "steps": "æ­Ĩ驟", - "stop_casting": "停æ­ĸ投攞", - "storage": "å„˛å­˜įŠē間", - "storage_label": "å„˛å­˜æ¨™įą¤", - "storage_quota": "å„˛å­˜įŠē間配額", - "submit": "提äē¤", - "success": "成功", - "suggestions": "åģēč­°", - "support": "支持", - "supporter": "æ”¯æŒč€…", - "sync": "同æ­Ĩ", - "sync_albums": "同æ­Ĩᛏį°ŋ", - "sync_local": "同æ­ĨæœŦ抟", - "sync_remote": "同æ­Ĩ遠į̝", - "sync_status": "同æ­Ĩį‹€æ…‹", - "tag": "æ¨™įą¤", - "tag_assets": "æ¨™č¨˜é …į›Ž", - "tag_face": "æ¨™č¨˜č‡‰å­”", - "tag_people": "æ¨™įą¤äēēį‰Š", - "tags": "æ¨™įą¤", - "template": "æ¨Ąæŋ", - "text_recognition": "æ–‡å­—č­˜åˆĨ", - "theme": "ä¸ģ題", - "theme_selection": "ä¸ģ題選項", - "theme_setting_colorful_interface_title": "åŊŠč‰˛į•Œéĸ", - "theme_setting_primary_color_title": "ä¸ģ色čĒŋ", - "then": "䚋垌", - "third_party_resources": "įŦŦä¸‰æ–ščŗ‡æē", - "time": "時間", - "time_based_memories": "䞝時間回æ†ļ", - "timeline": "æ™‚é–“įˇš", - "timezone": "時區", - "to_archive": "封存", - "to_change_password": "攚密įĸŧ", - "to_favorite": "最愛", - "to_login": "į™ģå…Ĩ", - "to_multi_select": "多選", - "to_select": "揀", - "to_trash": "垃圞æĄļ", - "toggle_settings": "åˆ‡æ›č¨­åŽš", - "toggle_theme_description": "切換ä¸ģ題", - "total": "į¸Ŋå…ą", - "total_usage": "į¸Ŋį”¨é‡", - "trash": "垃圞æĄļ", - "trash_all": "掉æ›Ŧ", - "trash_delete_asset": "æŧå€‹é …į›ŽčŊ垃圞æĄļ/åˆĒ除咗äŊĸ", - "trash_page_delete_all": "åˆĒ除æ›Ŧ", - "trigger": "觸į™ŧæĸäģļ", - "trigger_person_recognized": "čž¨č­˜åˆ°äēēį‰Š", - "troubleshoot": "į–‘é›ŖæŽ’č§Ŗ", - "type": "éĄžåž‹", - "unarchive": "取æļˆå°å­˜", - "undo": "還原", - "unfavorite": "取æļˆæ”ļ藏", - "unhide_person": "取æļˆéšąč—äēēį‰Š", - "unknown": "唔įŸĨ", - "unknown_country": "æœĒįŸĨ國åŽļ", - "unknown_date": "æœĒįŸĨ嘅æ—Ĩ期", - "unknown_schema": "æœĒįŸĨ嘅æžļ構", - "unknown_year": "æœĒįŸĨ嘅嚴äģŊ", - "unlimited": "į„Ąé™åˆļ", - "unlink_oauth": "č§Ŗé™¤åŒ OAuth å˜…é€Ŗįĩ", - "unmute_memories": "取æļˆéœéŸŗå›žæ†ļ", - "unnamed_album": "æœĒå‘Ŋåį›¸į°ŋ", - "unnamed_share": "æœĒå‘Ŋ名分äēĢ", - "unsaved_change": "čŽŠæ›´æœĒå„˛å­˜", - "unselect_all": "取æļˆå…¨é¸", - "unstack": "取æļˆå †į–Š", - "unstack_action_prompt": "{count} 個取æļˆå †į–Š", - "untagged": "æœĒæ¨™įą¤", - "up_next": "下個", - "updated_at": "最垌更新嘅時間", - "updated_password": "更新咗密įĸŧ", - "upload": "ä¸Šå‚ŗ", - "upload_concurrency": "åŒæ™‚é–“å˜…ä¸Šå‚ŗæ•¸", - "upload_details": "ä¸Šå‚ŗčŠŗį´°čŗ‡č¨Š", - "upload_finished": "ä¸Šå‚ŗå’—", - "upload_status_duplicates": "é‡č¤‡é …į›Ž", - "upload_status_errors": "錯čǤ", - "upload_status_uploaded": "ä¸Šå‚ŗå’—", - "uploading": "ä¸Šå‚ŗįˇŠ", - "uploading_media": "é …į›Žä¸Šå‚ŗįˇŠ", - "uploads": "ä¸Šå‚ŗ", - "url": "įļ˛å€", - "usage": "į”¨é‡", - "use_biometric": "äŊŋį”¨į”Ÿį‰Šč­˜åˆĨ", - "use_template": "äŊŋį”¨æ¨Ąæŋ", - "user": "ᔍæˆļ", - "user_id": "ᔍæˆļ ID", - "user_pin_code_settings": "PIN", - "user_privacy": "ᔍæˆļį§éšą", - "user_purchase_settings": "財", - "username": "ᔍæˆļåį¨ą", - "users": "ᔍæˆļ", - "utilities": "åˇĨå…ˇ", - "validate": "驗證", - "variables": "čŽŠæ•¸", - "version": "į‰ˆæœŦ", - "version_announcement_message": "旊晨īŧImmich æœ‰æ–°į‰ˆæœŦå–‡ã€‚å””čŠ˛čŽ€ä¸€čŽ€ į™ŧ襨čĒĒæ˜Ž ä¸ĻįĸēäŋäŊ å˜…設åޚå–ē更新åĨŊ嘅īŧŒäģĨéŋ免äģģäŊ•å˜…č¨­åޚ錝čǤīŧŒå°¤å…ļå–ēäŊ į”¨įˇŠ WatchTower æˆ–č€…äģģäŊ•å…ļäģ–č‡Ē動更新 Immich 嘅čģŸäģļ。", - "version_history": "į‰ˆæœŦį´€éŒ„", - "video": "åŊąį‰‡", - "video_quality": "åŊąį‰‡į•ĢčŗĒ", - "videos": "åŊąį‰‡", - "view": "æĒĸčĻ–", - "view_album": "į‡į›¸į°ŋ", - "view_all": "į‡æ›Ŧ", - "view_details": "æĒĸčĻ–čŠŗį´°čŗ‡č¨Š", - "view_link": "æĒĸčϖ逪įĩ", - "view_name": "į‡", - "view_stack": "į‡ä¸‹å †į–Š", - "visibility": "čƒŊčĻ‹åēĻ", - "visual": "čĻ–čĻē化", - "waiting": "į­‰įˇŠč™•į†", - "waiting_count": "į­‰įˇŠ {count}", "warning": "č­Ļ告", "week": "星期", "welcome": "æ­ĄčŋŽ", "welcome_to_immich": "æ­ĄčŋŽäŊŋᔍ Immich", - "whats_new": "有咊新野", - "whats_new_version": "{version} į‰ˆ", - "when": "å–ē", "width": "å¯Ŧ", "wifi_name": "Wi-Fi 名", - "workflow": "åˇĨäŊœæĩį¨‹", - "workflow_info": "åˇĨäŊœæĩį¨‹čŠŗį´°čŗ‡č¨Š", - "workflow_json": "åˇĨäŊœæĩį¨‹ JSON", - "workflow_name": "åˇĨäŊœæĩį¨‹å", - "workflow_summary": "åˇĨäŊœæĩį¨‹į°Ąčρ", - "workflow_templates": "åˇĨäŊœæĩį¨‹į¯„æœŦ", - "workflows": "åˇĨäŊœæĩį¨‹", "wrong_pin_code": "PIN įĸŧå””å•ą", - "x_of_total": "{x} / {total}", "year": "åš´", "years_ago": "{years, plural, one {#åš´} other {#åš´}}前", - "yes": "äŋ‚", - "you_dont_have_any_shared_links": "您冇äģģäŊ•å˜…å…ąäēĢ逪įĩ", - "your_wifi_name": "您嘅 Wi-Fi åį¨ą", - "zero_to_clear_rating": "æ’ŗ 0 æ¸…é™¤čŗ‡æēå˜…čŠ•į´š", - "zoom_image": "į¸Žæ”žį›¸į‰‡" + "yes": "是", + "you_dont_have_any_shared_links": "äŊ į„Ąå…ąäēĢ逪įĩ", + "your_wifi_name": "äŊ å˜… Wi-Fi åį¨ą", + "zero_to_clear_rating": "按0äģĨæ¸…é™¤čŗ‡æēčŠ•į´š", + "zoom_image": "į¸Žæ”žį›¸į‰‡", + "zoom_to_bounds": "į¸Žæ”žč‡ŗé‚Šį•Œ" } diff --git a/i18n/zh_Hans.json b/i18n/zh_Hans.json index fbbbc497f9..a59ec9cb9f 100644 --- a/i18n/zh_Hans.json +++ b/i18n/zh_Hans.json @@ -1,40 +1,48 @@ { "about": "å…ŗäēŽ", - "account": "č´Ļæˆˇ", - "account_settings": "č´ĻæˆˇčŽžįŊŽ", - "acknowledge": "åĨŊ", + "account": "č´Ļåˇ", + "account_settings": "č´ĻåˇčŽžįŊŽ", + "acknowledge": "厞įŸĨ悉", "action": "操äŊœ", "action_common_update": "更新", - "action_description": "å¯šį­›é€‰å‡ēįš„éĄšį›Žæ‰§čĄŒįš„ä¸€į섿“äŊœ", + "action_description": "å¯šį­›é€‰å‡ēįš„į…§į‰‡/视éĸ‘æ‰§čĄŒįš„ä¸€į섿“äŊœ", "actions": "操äŊœ", "active": "čŋ›čĄŒä¸­", - "active_count": "čŋ›čĄŒä¸­īŧš{count}", + "active_count": "æ´ģ动: {count}", "activity": "æ´ģ动", + "activity_changed": "æ´ģ动įŠļ态{enabled, select, true {厞吝ᔍ} other {厞įρᔍ}}", "add": "æˇģ加", "add_a_description": "æˇģ加描čŋ°", "add_a_location": "æˇģ加äŊįŊŽ", "add_a_name": "æˇģ加äēē名", "add_a_title": "æˇģ加标éĸ˜", "add_action": "æˇģ加操äŊœ", + "add_action_description": "į‚šå‡ģäģĨæˇģ加čĻæ‰§čĄŒįš„æ“äŊœ", "add_assets": "æˇģåŠ éĄšį›Ž", "add_birthday": "æˇģåŠ į”Ÿæ—Ĩ", "add_endpoint": "æˇģ加įĢ¯į‚š", "add_exclusion_pattern": "æˇģåŠ æŽ’é™¤č§„åˆ™", + "add_filter": "æˇģåŠ į­›é€‰æĄäģļ", + "add_filter_description": "į‚šå‡ģæˇģåŠ į­›é€‰æĄäģļ", "add_location": "æˇģ加äŊįŊŽ", + "add_more_users": "æˇģåŠ æ›´å¤šį”¨æˆˇ", "add_partner": "æˇģ加协äŊœč€…", + "add_path": "æˇģåŠ čˇ¯åž„", "add_photos": "æˇģåŠ į…§į‰‡", - "add_step": "æˇģ加æ­ĨéǤ", "add_tag": "æˇģåŠ æ ‡į­ž", "add_to": "æˇģ加到â€Ļ", "add_to_album": "æˇģåŠ åˆ°į›¸å†Œ", "add_to_album_bottom_sheet_added": "厞æˇģåŠ č‡ŗ {album}", "add_to_album_bottom_sheet_already_exists": "厞圍 {album} 中", "add_to_album_bottom_sheet_some_local_assets": "部分æœŦ地čĩ„äē§æ— æŗ•æˇģåŠ č‡ŗį›¸å†Œ", + "add_to_album_toggle": "切æĸ {album} įš„é€‰ä¸­įŠļ态", "add_to_albums": "æˇģåŠ åˆ°į›¸å†Œ", "add_to_albums_count": "æˇģåŠ åˆ°į›¸å†Œ ({count})", "add_to_bottom_bar": "æˇģ加到", + "add_to_shared_album": "æˇģåŠ åˆ°å…ąäēĢį›¸å†Œ", "add_upload_to_stack": "æˇģ加上äŧ č‡ŗå †å ", "add_url": "æˇģ加 URL", + "add_workflow_step": "æˇģ加åˇĨäŊœæĩæ­ĨéǤ", "added_to_archive": "æˇģåŠ č‡ŗå­˜æĄŖ", "added_to_favorites": "厞æˇģ加到æ”ļ藏", "added_to_favorites_count": "厞将 {count, number} 饚æˇģ加到æ”ļ藏", @@ -43,112 +51,96 @@ "admin_user": "įŽĄį†å‘˜į”¨æˆˇ", "asset_offline_description": "æœĒ扞到č¯Ĩ外部čĩ„æēå瓿–‡äģļīŧŒåˇ˛å°†å…ļį§ģč‡ŗå›žæ”ļįĢ™ã€‚åĻ‚æžœæ–‡äģ￘¯åœ¨čĩ„æēåē“内čĸĢį§ģ动īŧŒč¯ˇåœ¨æ—ļ间įēŋ中æŸĨ扞寚åē”įš„æ–°æ–‡äģļ。åĻ‚éœ€æĸ复此文äģļīŧŒč¯ˇįĄŽäŋ Immich 可čŽŋé—Žä¸‹æ–šįš„æ–‡äģļčˇ¯åž„īŧŒåšļ重新æ‰Ģ描č¯Ĩčĩ„æēåē“。", "authentication_settings": "čŽ¤č¯čŽžįŊŽ", - "authentication_settings_description": "įŽĄį†å¯†į ã€OAuth与å…ļäģ–čŽ¤č¯čŽžįŊŽ", - "authentication_settings_disable_all": "įĄŽåŽščρįĻį”¨æ‰€æœ‰į™ģåŊ•æ–šåŧå—īŧŸį™ģåŊ•功čƒŊå°†åŽŒå…¨å¤ąæ•ˆã€‚", - "authentication_settings_reenable": "åĻ‚æžœčĻé‡æ–°å¯į”¨īŧŒč¯ˇäŊŋᔍ æœåŠĄå™¨å‘Ŋäģ¤ã€‚", + "authentication_settings_description": "įŽĄį†å¯†į ã€OAuth 和å…ļäģ–čŽ¤č¯čŽžįŊŽ", + "authentication_settings_disable_all": "æ‚¨įĄŽåŽščρįĻį”¨æ‰€æœ‰į™ģåŊ•æ–šåŧå—īŧŸį™ģåŊ•功čƒŊå°†åŽŒå…¨å¤ąæ•ˆã€‚", + "authentication_settings_reenable": "åĻ‚éœ€é‡æ–°å¯į”¨īŧŒč¯ˇäŊŋᔍ æœåŠĄå™¨å‘Ŋäģ¤ã€‚", "background_task_job": "后台äģģåŠĄ", "backup_database": "创åģēæ•°æŽåē“备äģŊ", "backup_database_enable_description": "å¯į”¨æ•°æŽåē“备äģŊ", - "backup_keep_last_amount": "äŋį•™å¤‡äģŊ数量", - "backup_onboarding_1_description": "备äģŊ到äē‘įĢ¯æˆ–å…ļäģ–į‰Šį†äŊįŊŽã€‚", - "backup_onboarding_2_description": "ä¸åŒčŽžå¤‡ä¸Šįš„æœŦ地备äģŊ。包åĢä¸ģ文äģļ与æœŦ地文äģļįš„å¤‡äģŊ。", - "backup_onboarding_3_description": "所有数捎īŧˆåŒ…åĢ1äģŊåŧ‚地备äģŊ和2äģŊæœŦ地备äģŊīŧ‰įš„备äģŊ。", - "backup_onboarding_description": "åģē莎éĩåžĒ 3-2-1备äģŊ原则 æĨäŋæŠ¤äŊ įš„æ•°æŽã€‚通čŋ‡å¤‡äģŊᅧቇ/视éĸ‘与Immich数捎åē“äŊŋäŊ įš„æ•°æŽæ›´åŠ åŽ‰å…¨ã€‚", - "backup_onboarding_footer": "æœ‰å…ŗå¤‡äģŊImmichįš„æ›´å¤šäŋĄæ¯īŧŒč¯ˇå‚阅 æ–‡æĄŖã€‚", - "backup_onboarding_parts_title": "3-2-1备äģŊ原则īŧš", + "backup_keep_last_amount": "äŋį•™įš„åŽ†å˛å¤‡äģŊ数量", + "backup_onboarding_1_description": "åŧ‚地备äģŊīŧŒäž‹åĻ‚å­˜å‚¨åœ¨äē‘įĢ¯æˆ–åĻ一ä¸Ēį‰Šį†äŊįŊŽã€‚", + "backup_onboarding_2_description": "æœŦåœ°å¤ščŽžå¤‡å‰¯æœŦã€‚åŗåœ¨ä¸åŒčŽžå¤‡ä¸Šäŋå­˜ä¸ģ文äģļ及å…ļæœŦ地备äģŊ。", + "backup_onboarding_3_description": "æ•°æŽįš„æ€ģ副æœŦ数īŧŒåŒ…åĢ原始文äģļ。䞋åĻ‚īŧš1 äģŊåŧ‚地备äģŊ和 2 äģŊæœŦ地副æœŦ。", + "backup_onboarding_description": "åģēčŽŽé‡‡į”¨ 3-2-1 备äģŊį­–į•Ĩ æĨäŋæŠ¤äŊ įš„æ•°æŽã€‚ä¸ēäē†åŽžįŽ°å…¨éĸįš„å¤‡äģŊæ–šæĄˆīŧŒäŊ åē”č¯Ĩ同æ—ļäŋå­˜åˇ˛ä¸Šäŧ įš„ᅧቇ/视éĸ‘副æœŦäģĨ及 Immich įš„æ•°æŽåē“。", + "backup_onboarding_footer": "æœ‰å…ŗå¤‡äģŊ Immich įš„æ›´å¤šäŋĄæ¯īŧŒč¯ˇå‚阅 æ–‡æĄŖã€‚", + "backup_onboarding_parts_title": "3-2-1备äģŊ原则包æ‹Ŧīŧš", "backup_onboarding_title": "备äģŊ", "backup_settings": "数捎åē“备äģŊ莞įŊŽ", "backup_settings_description": "įŽĄį†æ•°æŽåē“备äģŊ莞įŊŽã€‚", - "cleared_jobs": "厞停æ­ĸäģģåŠĄīŧš{job}", + "cleared_jobs": "åˇ˛æ¸…é™¤ {job} įš„äģģåŠĄ", "config_set_by_file": "åŊ“前配įŊŽį”ąé…įŊŽæ–‡äģļčŽžåŽš", - "confirm_delete_library": "įĄŽåŽščĻåˆ é™¤čĩ„æēåē““{library}”吗īŧŸ", - "confirm_delete_library_assets": "įĄŽåŽščĻåˆ é™¤æ­¤čĩ„æēåē“吗īŧŸæ­¤æ“äŊœå°†äģŽImmich中删除{count, plural, one {# ä¸Ēå…ŗč”éĄšį›Ž} other {# ä¸Ēå…ŗč”éĄšį›Ž}}īŧŒä¸”æ— æŗ•æ’¤é”€ã€‚æ–‡äģļäģå°†äŋį•™åœ¨įŖį›˜ä¸Šã€‚", - "confirm_email_below": "č¯ˇčž“å…Ĩ“{email}”æĨįĄŽčŽ¤æ“äŊœ", - "confirm_reprocess_all_faces": "įĄŽåŽščĻé‡æ–°č¯†åˆĢ所有äēē脏吗īŧŸæ­¤æ“äŊœå°†æ¸…除厞å‘Ŋåįš„äēēį‰Šã€‚", - "confirm_user_password_reset": "įĄŽåŽščĻé‡įŊŽ{user}įš„å¯†į å—īŧŸ", - "confirm_user_pin_code_reset": "įĄŽåŽščĻé‡įŊŽ{user}įš„PIN᠁吗īŧŸ", - "copy_config_to_clipboard_description": "将åŊ“前įŗģįģŸé…įŊŽæ‹ˇč´ä¸ēJSONå¯ščąĄåˆ°å‰Ēč´´æŋ", + "confirm_delete_library": "įĄŽåŽščĻåˆ é™¤čĩ„æēåē“ \"{library}\" 吗īŧŸ", + "confirm_delete_library_assets": "įĄŽåŽščĻåˆ é™¤æ­¤čĩ„æēåē“吗īŧŸæ­¤æ“äŊœå°†äģŽ Immich 中删除 {count, plural, one {# ä¸Ēå…ŗč”éĄšį›Ž} other {å…ą # ä¸Ēå…ŗč”éĄšį›Ž}}īŧŒä¸”æ— æŗ•æ’¤é”€ã€‚æŗ¨æ„īŧšæ–‡äģļäģå°†äŋį•™åœ¨įŖį›˜ä¸Šã€‚", + "confirm_email_below": "ä¸ēįĄŽčŽ¤æ“äŊœīŧŒč¯ˇåœ¨ä¸‹æ–ščž“å…Ĩ“{email}”", + "confirm_reprocess_all_faces": "įĄŽåŽščĻé‡æ–°å¤„į†æ‰€æœ‰äēē脏吗īŧŸæ­¤æ“äŊœå°†æ¸…除厞å‘Ŋåįš„äēēį‰Šã€‚", + "confirm_user_password_reset": "įĄŽåŽščĻé‡įŊŽ {user} įš„å¯†į å—īŧŸ", + "confirm_user_pin_code_reset": "įĄŽåŽščĻé‡įŊŽ {user} įš„ PIN ᠁吗īŧŸ", + "copy_config_to_clipboard_description": "将åŊ“前įŗģįģŸé…įŊŽäŊœä¸ē JSON å¯ščąĄå¤åˆļ到å‰Ēč´´æŋ", "create_job": "创åģēäģģåŠĄ", "cron_expression": "Cron襨螞åŧ", - "cron_expression_description": "äŊŋᔍCronæ ŧåŧčŽžįŊŽæ‰Ģ描间隔。更多äŋĄæ¯č¯ˇå‚č€ƒCrontab Guruį­‰įŊ‘įĢ™", + "cron_expression_description": "äŊŋᔍCronæ ŧåŧčŽžįŊŽæ‰Ģ描间隔。更多äŋĄæ¯č¯ˇå‚č€ƒ Crontab Guru į­‰įŊ‘įĢ™", "cron_expression_presets": "Cron襨螞åŧéĸ„莞", "disable_login": "įρᔍį™ģåŊ•", - "download_csv": "下čŊŊCSV文äģļ", - "duplicate_detection_job_description": "通čŋ‡æœē器å­Ļäš æĨæŖ€æĩ‹į›¸äŧŧᅧቇ/视éĸ‘。īŧˆåŠŸčƒŊ䞝čĩ–äēŽæ™ēčƒŊ搜į´ĸīŧ‰", - "exclusion_pattern_description": "æŽ’é™¤č§„åˆ™å¯äģĨ在æ‰Ģ描čĩ„æēå瓿—ļåŋŊį•ĨæŒ‡åŽšįš„æ–‡äģļ与文äģļ多。åĻ‚æžœäŊ æœ‰ä¸€äē›ä¸å¸Œæœ›å¯ŧå…Ĩįš„æ–‡äģļīŧˆåĻ‚RAWæ ŧåŧæ–‡äģļīŧ‰æˆ–æ–‡äģļ多īŧŒæ­¤åŠŸčƒŊå°†éžå¸¸æœ‰į”¨ã€‚", - "export_config_as_json_description": "将åŊ“前įŗģįģŸé…įŊŽä¸‹čŊŊä¸ēJSON文äģļ", + "duplicate_detection_job_description": "čŋčĄŒæœē器å­Ļäš æĨæŖ€æĩ‹į›¸äŧŧ回像īŧŒæ­¤åŠŸčƒŊ䞝čĩ–äēŽæ™ēčƒŊ搜į´ĸ", + "exclusion_pattern_description": "æŽ’é™¤č§„åˆ™å…čŽ¸æ‚¨åœ¨æ‰Ģ描čĩ„æēå瓿—ļåŋŊį•Ĩį‰šåŽšįš„æ–‡äģļ和文äģļ多。åĻ‚æžœæ‚¨æœ‰æŸäē›åŒ…åĢ不希望å¯ŧå…Ĩįš„æ–‡äģļīŧˆäž‹åĻ‚ RAW æ ŧåŧæ–‡äģļīŧ‰įš„æ–‡äģļ多īŧŒæ­¤åŠŸčƒŊå°†éžå¸¸æœ‰į”¨ã€‚", + "export_config_as_json_description": "将åŊ“前įŗģįģŸé…įŊŽä¸‹čŊŊä¸ē JSON 文äģļ", "external_libraries_page_description": "įŽĄį†å¤–éƒ¨čĩ„æēåē“", "face_detection": "äēēč„¸æŖ€æĩ‹", - "face_detection_description": "通čŋ‡æœē器å­Ļäš č¯†åˆĢᅧቇ/视éĸ‘ä¸­įš„äēē脸īŧŒå¯šäēŽč§†éĸ‘äģ…处ᐆå…ļįŧŠį•Ĩå›žã€‚â€œåˆˇæ–°â€äŧšé‡æ–°č¯†åˆĢæ‰€æœ‰į…§į‰‡/视éĸ‘īŧ›â€œé‡įŊŽâ€äŧ𿏅除åŊ“前所有äēēč„¸æ•°æŽīŧ›â€œįŧēå¤ąâ€äŧšå°†æœĒæ›žå¤„į†čŋ‡įš„ᅧቇ/视éĸ‘加å…Ĩ队列。åŊ““äēēč„¸æŖ€æĩ‹â€åŽŒæˆåŽīŧŒįŗģįģŸäŧšå°†æ–°č¯†åˆĢåˆ°įš„äēēč„¸æ”žå…Ĩ“äēē脏蝆åˆĢ”队列īŧŒäģĨ将å…ļåŊ’įąģåˆ°įŽ°æœ‰æˆ–æ–°åģēįš„äēēį‰Šåˆ†įģ„中。", - "facial_recognition_job_description": "å°†æŖ€æĩ‹åˆ°įš„äēē脸åŊ’įąģä¸ēä¸åŒįš„äēēį‰ŠīŧŒæ­¤æ­ĨéĒ¤åœ¨â€œäēēč„¸æŖ€æĩ‹â€åŽŒæˆåŽčŋ›čĄŒã€‚“重įŊŽâ€äŧšīŧˆé‡æ–°īŧ‰čšįąģ所有äēē脸īŧ›â€œįŧēå¤ąâ€äŧšå°†å°šæœĒåŊ’įąģįš„äēēč„¸åŠ å…Ĩ队列。", - "failed_job_command": "æ‰§čĄŒå‘Ŋäģ¤{command}å¤ąč´ĨīŧŒäģģåŠĄīŧš{job}", - "force_delete_user_warning": "č­Ļ告īŧšæ­¤æ“äŊœå°†įĢ‹åŗåˆ é™¤æ­¤į”¨æˆˇä¸Žå…￉€æœ‰éĄšį›Žã€‚此操äŊœä¸å¯æ’¤é”€īŧŒä¸”æ–‡äģļæ— æŗ•æĸ复。", + "face_detection_description": "äŊŋᔍæœē器å­Ļäš æŖ€æĩ‹åŊąåƒä¸­įš„äēē脸īŧŒå¯šäēŽč§†éĸ‘äģ…处ᐆå…ļįŧŠį•Ĩå›žã€‚â€œåˆˇæ–°â€äŧšé‡æ–°å¤„į†æ‰€æœ‰åŊąåƒīŧ›â€œé‡įŊŽâ€äŧ𿏅除åŊ“前所有äēēč„¸æ•°æŽīŧ›â€œįŧēå¤ąâ€åˆ™äģ…å°†æœĒæ›žå¤„į†čŋ‡įš„åŊąåƒåŠ å…Ĩ队列。åŊ““äēēč„¸æŖ€æĩ‹â€åŽŒæˆåŽīŧŒįŗģįģŸäŧšå°†æ–°æŖ€æĩ‹åˆ°įš„äēēč„¸æ”žå…Ĩ“äēē脏蝆åˆĢ”队列īŧŒäģĨ将å…ļåŊ’įąģåˆ°įŽ°æœ‰æˆ–æ–°åģēįš„äēēį‰Šåˆ†įģ„中。", + "facial_recognition_job_description": "å°†æŖ€æĩ‹åˆ°įš„äēē脸åŊ’įąģä¸ēä¸åŒįš„äēēį‰ŠīŧŒæ­¤æ­ĨéĒ¤éœ€åœ¨â€œäēēč„¸æŖ€æĩ‹â€åŽŒæˆåŽčŋčĄŒã€‚“重įŊŽâ€äŧšīŧˆé‡æ–°īŧ‰čšįąģ所有äēēč„¸ã€‚â€œįŧēå¤ąâ€åˆ™å°†å°šæœĒįĄŽåŽšæ˜¯č°įš„äēēč„¸åŠ å…ĨåŊ’įąģ队列。", + "failed_job_command": "å‘Ŋäģ¤ {command} åœ¨æ‰§čĄŒäģģåŠĄ {job} æ—ļå¤ąč´Ĩ", + "force_delete_user_warning": "č­Ļ告īŧšæ­¤æ“äŊœå°†įĢ‹åŗåˆ é™¤č¯Ĩį”¨æˆˇåŠå…ļ所有文äģļ。此操äŊœä¸å¯æ’¤é”€īŧŒä¸”æ–‡äģļæ— æŗ•æĸ复。", "image_format": "æ ŧåŧ", "image_format_description": "WebPæ ŧåŧįš„æ–‡äģļäŊ“į§¯æ¯”JPEG更小īŧŒäŊ†įŧ–į é€ŸåēĻ螃æ…ĸ。", "image_fullsize_description": "厞å‰ĨįĻģå…ƒæ•°æŽįš„å…¨å°ē寸回像īŧŒæ”žå¤§æŸĨįœ‹æ—ļäŊŋᔍ", "image_fullsize_enabled": "吝ᔍ免å°ēå¯¸å›žåƒį”Ÿæˆ", - "image_fullsize_enabled_description": "ä¸ē非įŊ‘éĄĩ友åĨŊæ ŧåŧį”Ÿæˆå…¨å°ēå¯¸å›žåƒã€‚å¯į”¨â€œäŧ˜å…ˆäŊŋᔍåĩŒå…Ĩåŧéĸ„č§ˆâ€åˆ™ä¸čŋ›čĄŒčŊŦæĸã€‚æ­¤čŽžįŊŽä¸åŊąå“JPEGį­‰įŊ‘éĄĩ友åĨŊæ ŧåŧã€‚", - "image_fullsize_quality_description": "全å°ēå¯¸å›žåƒč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠéĢ˜č´¨é‡čļŠåĨŊīŧŒäŊ†æ–‡äģļäŊ“į§¯äšŸčļŠå¤§ã€‚", + "image_fullsize_enabled_description": "ä¸ē非įŊ‘éĄĩ友åĨŊæ ŧåŧį”Ÿæˆå…¨å°ēå¯¸å›žåƒã€‚å¯į”¨â€œäŧ˜å…ˆäŊŋᔍåĩŒå…Ĩåŧéĸ„č§ˆâ€åŽīŧŒå°†į›´æŽĨäŊŋᔍåĩŒå…Ĩåŧéĸ„č§ˆč€Œæ— éœ€čŊŦæĸã€‚æ­¤čŽžįŊŽä¸åŊąå“JPEGį­‰įŊ‘éĄĩ友åĨŊæ ŧåŧã€‚", + "image_fullsize_quality_description": "全å°ēå¯¸å›žåƒč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠé̘į”ģč´¨čļŠåĨŊīŧŒäŊ†į”Ÿæˆįš„æ–‡äģļ也čļŠå¤§ã€‚", "image_fullsize_title": "全å°ēå¯¸å›žåƒčŽžįŊŽ", "image_prefer_embedded_preview": "äŧ˜å…ˆäŊŋᔍåĩŒå…Ĩåŧéĸ„č§ˆ", - "image_prefer_embedded_preview_setting_description": "äŊŋᔍRAWᅧቇ内åĩŒįš„éĸ„č§ˆå›žåƒīŧˆåĻ‚æœ‰īŧ‰äŊœä¸ē后įģ­å¤„į†įš„åŸēįĄ€ã€‚čŋ™čƒŊäŊŋéƒ¨åˆ†å›žåƒį”Ÿæˆįš„č‰˛åŊŠæ›´å‡†įĄŽīŧŒäŊ†åĩŒå…Ĩåŧéĸ„č§ˆå›žåƒįš„č´¨é‡å°†å–å†ŗäēŽį›¸æœēåž‹åˇīŧŒä¸”可čƒŊ因有损压įŧŠč€ŒåŒ…åĢæ›´å¤šįš„äŧĒåŊąã€‚", + "image_prefer_embedded_preview_setting_description": "äŊŋᔍ RAW 文äģļ内åĩŒå…Ĩįš„éĸ„č§ˆå›žåƒīŧˆåĻ‚æœ‰īŧ‰äŊœä¸ē后įģ­å¤„į†įš„åŸēįĄ€ã€‚čŋ™čƒŊä¸ēéƒ¨åˆ†å›žåƒį”Ÿæˆæ›´å‡†įĄŽįš„č‰˛åŊŠīŧŒäŊ†åĩŒå…Ĩåŧéĸ„č§ˆå›žåƒįš„č´¨é‡å–å†ŗäēŽį›¸æœēåž‹åˇīŧŒä¸”可čƒŊ因有损压įŧŠåŒ…åĢæ›´å¤šįš„äŧĒåŊąã€‚", "image_prefer_wide_gamut": "äŧ˜å…ˆäŊŋᔍåšŋč‰˛åŸŸ", - "image_prefer_wide_gamut_setting_description": "įŧŠį•Ĩ回äŊŋᔍDisplay P3色åŊŠįŠē间。čŋ™čƒŊ更åĨŊ地äŋį•™åšŋč‰˛åŸŸå›žåƒįš„č‰˛åŊŠé˛œč‰ŗåēĻīŧŒäŊ†åœ¨äŊŋį”¨æ—§į‰ˆæĩč§ˆå™¨įš„č€æ—§čŽžå¤‡ä¸Šå¯čƒŊäē§į”Ÿč‰˛åˇŽã€‚sRGB回像将äŋæŒsRGB色åŊŠįŠē间äģĨéŋå…č‰˛åŊŠåį§ģ。", - "image_preview_description": "厞å‰ĨįĻģå…ƒæ•°æŽįš„ä¸­į­‰å°ē寸回像īŧŒį”¨äēŽå•åš…åŊąåƒåą•į¤ē与æœē器å­Ļäš ", - "image_preview_quality_description": "éĸ„č§ˆå›žč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠéĢ˜č´¨é‡čļŠåĨŊīŧŒäŊ†æ–‡äģļäŊ“į§¯äšŸčļŠå¤§īŧŒä¸”可čƒŊ降äŊŽApp响åē”速åēĻ。čŋ‡äŊŽįš„æ•°å€ŧ可čƒŊåŊąå“æœē器å­Ļäš įš„å‡†įĄŽåēĻ。", + "image_prefer_wide_gamut_setting_description": "įŧŠį•Ĩ回äŊŋᔍ Display P3 色åŊŠįŠē间。čŋ™čƒŊ更åĨŊ地äŋį•™åšŋč‰˛åŸŸå›žåƒįš„č‰˛åŊŠé˛œč‰ŗåēĻīŧŒäŊ†åœ¨äŊŋį”¨æ—§į‰ˆæĩč§ˆå™¨įš„č€æ—§čŽžå¤‡ä¸ŠīŧŒå¯čƒŊå¯ŧč‡´č‰˛åˇŽã€‚sRGB 回像将äŋæŒä¸ē sRGBīŧŒäģĨéŋå…č‰˛åŊŠåį§ģ。", + "image_preview_description": "厞å‰ĨįĻģå…ƒæ•°æŽįš„ä¸­į­‰å°ē寸回像īŧŒį”¨äēŽå•åš…åŊąåƒåą•į¤ē及æœē器å­Ļäš ", + "image_preview_quality_description": "éĸ„č§ˆå›žč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠé̘į”ģč´¨čļŠåĨŊīŧŒäŊ†į”Ÿæˆįš„æ–‡äģļčļŠå¤§īŧŒä¸”可čƒŊ降äŊŽåē”į”¨å“åē”速åēĻã€‚čŽžįŊŽčŋ‡äŊŽįš„æ•°å€ŧ可čƒŊåŊąå“æœē器å­Ļäš īŧˆč¯†åˆĢīŧ‰įš„å‡†įĄŽåēĻ。", "image_preview_title": "éĸ„č§ˆå›žčŽžįŊŽ", "image_progressive": "渐čŋ›åŧįŧ–᠁", - "image_progressive_description": "寚JPEG回像čŋ›čĄŒæ¸čŋ›åŧįŧ–᠁äģĨæå‡å›žį‰‡åŠ čŊŊäŊ“éĒŒã€‚æ­¤čŽžįŊŽä¸åŊąå“WebP回像。", + "image_progressive_description": "寚 JPEG 回像čŋ›čĄŒæ¸čŋ›åŧįŧ–᠁īŧŒäģĨæå‡å›žį‰‡åŠ čŊŊäŊ“éĒŒã€‚æ­¤åŧ€å…ŗå¯š WebP 无效。", "image_quality": "质量", "image_resolution": "åˆ†čž¨įŽ‡", - "image_resolution_description": "螃éĢ˜įš„åˆ†čž¨įŽ‡čƒŊäŋį•™æ›´å¤šįģ†čŠ‚īŧŒäŊ†įŧ–᠁æ—ļ间更é•ŋ、文äģļäŊ“į§¯æ›´å¤§īŧŒä¸”可čƒŊ降äŊŽApp响åē”速åēĻ。", + "image_resolution_description": "螃éĢ˜įš„åˆ†čž¨įŽ‡čƒŊäŋį•™æ›´å¤šå›žåƒįģ†čŠ‚īŧŒäŊ†įŧ–᠁æ—ļ间更é•ŋã€į”Ÿæˆįš„æ–‡äģļæ›´å¤§īŧŒä¸”可čƒŊå¯ŧ致åē”į”¨å“åē”变æ…ĸ。", "image_settings": "å›žåƒčŽžįŊŽ", "image_settings_description": "įŽĄį†į”Ÿæˆå›žåƒįš„č´¨é‡å’Œåˆ†čž¨įŽ‡", - "image_thumbnail_description": "厞å‰ĨįĻģå…ƒæ•°æŽįš„å°åž‹įŧŠį•Ĩ回īŧŒį”¨äēŽåˆ—čĄ¨åą•į¤ē多åŧ į…§į‰‡īŧŒåĻ‚ä¸ģæ—ļ间įēŋ", - "image_thumbnail_quality_description": "įŧŠį•Ĩå›žč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠé̘į”ģč´¨čļŠåĨŊīŧŒäŊ†æ–‡äģļäŊ“į§¯äšŸčļŠå¤§īŧŒä¸”可čƒŊ降äŊŽApp响åē”速åēĻ。", + "image_thumbnail_description": "厞å‰ĨįĻģå…ƒæ•°æŽįš„å°åž‹įŧŠį•Ĩ回īŧŒį”¨äēŽåˆ—čĄ¨åą•į¤ē多åŧ į…§į‰‡īŧŒäž‹åĻ‚ä¸ģæ—ļ间įēŋ", + "image_thumbnail_quality_description": "įŧŠį•Ĩå›žč´¨é‡īŧˆ1-100īŧ‰ã€‚æ•°å€ŧčļŠé̘į”ģč´¨čļŠåĨŊīŧŒäŊ†į”Ÿæˆįš„æ–‡äģļčļŠå¤§īŧŒä¸”可čƒŊ降äŊŽåē”į”¨å“åē”速åēĻ。", "image_thumbnail_title": "įŧŠį•Ĩå›žčŽžįŊŽ", - "import_config_from_json_description": "通čŋ‡ä¸Šäŧ JSON配įŊŽæ–‡äģļå¯ŧå…ĨįŗģįģŸé…įŊŽ", - "integrity_checks_checksum_files": "文äģļæ ĄéĒŒå’Œ", - "integrity_checks_checksum_files_description": "配įŊŽæ ĄéĒŒä¸ŽæŖ€æŸĨ", - "integrity_checks_checksum_files_enable_description": "å¯į”¨æ ĄéĒŒä¸ŽæŖ€æŸĨ", - "integrity_checks_checksum_files_percentage_limit": "į™žåˆ†æ¯”é™åˆļ", - "integrity_checks_checksum_files_percentage_limit_description": "莞įŊŽæ¯ä¸ĒæŖ€æŸĨå‘¨æœŸæ‰§čĄŒæ ĄéĒŒå’ŒæŖ€æŸĨįš„æœ€å¤§æ¯”äž‹īŧˆ0.01–1īŧ‰ã€‚", - "integrity_checks_checksum_files_time_limit": "æ—ļ间限åˆļ", - "integrity_checks_checksum_files_time_limit_description": "莞įŊŽæ¯ä¸ĒæŖ€æŸĨ周期内īŧŒæ ĄéĒŒå’ŒīŧˆChecksumīŧ‰æŖ€æŸĨå…čŽ¸čŋčĄŒįš„æœ€é•ŋæ—ļ间īŧˆæ¯Ģį§’īŧ‰ã€‚", - "integrity_checks_missing_files": "įŧēå¤ąæ–‡äģļ", - "integrity_checks_missing_files_description": "莞įŊŽæ‰§čĄŒéĸ‘įŽ‡åšļå¯į”¨æˆ–įρᔍįŧēå¤ąæ–‡äģļæŖ€æŸĨ", - "integrity_checks_missing_files_enable_description": "吝ᔍįŧēå¤ąæ–‡äģļæŖ€æŸĨ", - "integrity_checks_settings": "åŽŒæ•´æ€§æŖ€æŸĨ", - "integrity_checks_settings_description": "įŽĄį†åŽŒæ•´æ€§æŖ€æŸĨéĸ‘įŽ‡", - "integrity_checks_untracked_files": "æœĒčŋŊč¸Ēįš„æ–‡äģļ", - "integrity_checks_untracked_files_description": "莞įŊŽæ‰§čĄŒéĸ‘įŽ‡åšļå¯į”¨æˆ–įρᔍæœĒčŋŊč¸Ē文äģļæŖ€æŸĨ。", - "integrity_checks_untracked_files_enable_description": "吝ᔍæœĒčŋŊč¸Ē文äģļæŖ€æŸĨ", + "import_config_from_json_description": "通čŋ‡ä¸Šäŧ  JSON 配įŊŽæ–‡äģļå¯ŧå…ĨįŗģįģŸé…įŊŽ", "job_concurrency": "{job}åšļ发数", "job_created": "äģģåŠĄåˇ˛åˆ›åģē", - "job_not_concurrency_safe": "æ­¤äģģåŠĄä¸æ”¯æŒåšļ发操äŊœã€‚", + "job_not_concurrency_safe": "č¯ĨäģģåŠĄä¸æ”¯æŒåšļ发操äŊœã€‚", "job_settings": "äģģåŠĄčŽžįŊŽ", "job_settings_description": "įŽĄį†äģģåŠĄåšļ发数", "jobs_delayed": "{jobCount, plural, other {#ä¸ĒåģļčŋŸ}}", - "jobs_failed": "{jobCount, plural, other {#ä¸Ēå¤ąč´Ĩ}}", + "jobs_failed": "{jobCount, plural, other {# ä¸Ēå¤ąč´Ĩ}}", "jobs_over_time": "äģģåŠĄåŠ¨æ€", "library_created": "åˇ˛åˆ›åģēčĩ„æēåē“īŧš{library}", "library_deleted": "čĩ„æēåē“åˇ˛åˆ é™¤", "library_details": "čĩ„æēåē“č¯Ļ情", - "library_folder_description": "指厚čρå¯ŧå…Ĩįš„æ–‡äģļ多。įŗģįģŸå°†æ‰Ģ描此文äģļ多īŧˆåŒ…æ‹Ŧ子文äģļ多īŧ‰ä¸­įš„į…§į‰‡ä¸Žč§†éĸ‘。", + "library_folder_description": "指厚一ä¸Ēå¯ŧå…Ĩ文äģļ多。įŗģįģŸå°†æ‰Ģ描č¯Ĩ文äģļ多及å…ļ所有子文äģļå¤šä¸­įš„å›žį‰‡å’Œč§†éĸ‘。", "library_remove_exclusion_pattern_prompt": "įĄŽåŽščρį§ģé™¤æ­¤æŽ’é™¤č§„åˆ™å—īŧŸ", "library_remove_folder_prompt": "įĄŽåŽščρį§ģ除此å¯ŧå…Ĩ文äģļ多吗īŧŸ", "library_scanning": "厚期æ‰Ģ描", - "library_scanning_description": "莞įŊŽåŽšæœŸæ‰Ģ描", - "library_scanning_enable_description": "å¯į”¨åŽšæœŸæ‰Ģ描", + "library_scanning_description": "配įŊŽåŽšæœŸæ‰Ģ描", + "library_scanning_enable_description": "åŧ€å¯åŽšæœŸæ‰Ģ描", "library_settings": "外部čĩ„æēåē“", "library_settings_description": "įŽĄį†å¤–éƒ¨čĩ„æēåē“莞įŊŽ", - "library_tasks_description": "æ‰Ģ描åšļæŸĨ扞外部čĩ„æēåē“中新åĸžæˆ–å˜æ›´įš„éĄšį›Ž", + "library_tasks_description": "æ‰Ģ描外部čĩ„æēåē“äģĨæŸĨ扞新åĸžå’Œå˜æ›´įš„æ–‡äģļ", "library_updated": "čĩ„æēåē“åˇ˛æ›´æ–°", "library_watching_enable_description": "į›‘æŽ§å¤–éƒ¨čĩ„æēåē“įš„æ–‡äģļ变更", "library_watching_settings": "čĩ„æēåē“į›‘æŽ§ [厞éĒŒæ€§åŠŸčƒŊ]", "library_watching_settings_description": "č‡ĒåŠ¨į›‘æŽ§æ–‡äģļ变更", - "logging_enable_description": "吝ᔍæ—Ĩåŋ—", - "logging_level_description": "吝ᔍæ—ļäŊŋį”¨įš„æ—Ĩåŋ—įē§åˆĢ。", + "logging_enable_description": "吝ᔍæ—Ĩåŋ—čްåŊ•", + "logging_level_description": "å¯į”¨åŽīŧŒæ‰€é‡‡į”¨įš„æ—Ĩåŋ—įē§åˆĢ。", "logging_settings": "æ—Ĩåŋ—", "machine_learning_availability_checks": "å¯į”¨æ€§æŖ€æŸĨ", "machine_learning_availability_checks_description": "č‡ĒåŠ¨æŖ€æĩ‹åšļäŧ˜å…ˆé€‰æ‹Šå¯į”¨įš„æœē器å­Ļäš æœåŠĄå™¨", @@ -158,65 +150,49 @@ "machine_learning_availability_checks_timeout": "č¯ˇæą‚čļ…æ—ļæ—ļ间", "machine_learning_availability_checks_timeout_description": "å¯į”¨æ€§æŖ€æŸĨįš„č¯ˇæą‚čļ…æ—ļæ—ļ间īŧˆæ¯Ģį§’īŧ‰", "machine_learning_clip_model": "CLIPæ¨Ąåž‹", - "machine_learning_clip_model_description": "在此处列å‡ēįš„CLIPæ¨Ąåž‹åį§°ã€‚æŗ¨æ„īŧšæ›´æ”šæ¨Ąåž‹åŽåŋ…éĄģå¯šæ‰€æœ‰į…§į‰‡é‡æ–°æ‰§čĄŒâ€œæ™ēčƒŊ搜į´ĸ”äģģåŠĄã€‚", - "machine_learning_duplicate_detection": "é‡å¤éĄšį›ŽæŖ€æĩ‹", - "machine_learning_duplicate_detection_enabled": "å¯į”¨é‡å¤éĄšį›ŽæŖ€æĩ‹", - "machine_learning_duplicate_detection_enabled_description": "įĻį”¨åŽåŽŒå…¨į›¸åŒįš„éĄšį›Žäģäŧščŋ›čĄŒåŽģé‡å¤„į†ã€‚", - "machine_learning_duplicate_detection_setting_description": "通čŋ‡CLIPåĩŒå…Ĩå‘é‡č¯†åˆĢæŊœåœ¨įš„é‡å¤éĄšį›Ž", + "machine_learning_clip_model_description": "在 此处 列å‡ēįš„ CLIP æ¨Ąåž‹åį§°ã€‚č¯ˇæŗ¨æ„īŧŒæ›´æ”šæ¨Ąåž‹åŽīŧŒåŋ…éĄģ重新čŋčĄŒæ‰€æœ‰å›žį‰‡įš„“æ™ēčƒŊ搜į´ĸ”äģģåŠĄã€‚", + "machine_learning_duplicate_detection": "é‡å¤éĄšæŖ€æĩ‹", + "machine_learning_duplicate_detection_enabled": "å¯į”¨é‡å¤éĄšæŖ€æĩ‹", + "machine_learning_duplicate_detection_enabled_description": "č‹Ĩå…ŗé—­æ­¤åŠŸčƒŊīŧŒåŽŒå…¨į›¸åŒįš„æ–‡äģļäģäŧščĸĢåŽģé‡å¤„į†ã€‚", + "machine_learning_duplicate_detection_setting_description": "åˆŠį”¨ CLIP åĩŒå…Ĩå‘é‡č¯†åˆĢæŊœåœ¨įš„é‡å¤éĄš", "machine_learning_enabled": "吝ᔍæœē器å­Ļäš ", - "machine_learning_enabled_description": "įĻį”¨åŽä¸Žæœē器å­Ļäš įš„į›¸å…ŗčŽžįŊŽå°†å¤ąæ•ˆã€‚", + "machine_learning_enabled_description": "č‹Ĩå…ŗé—­æ­¤å¤„æ€ģåŧ€å…ŗīŧŒæ‰€æœ‰æœē器å­Ļäš į›¸å…ŗį‰šæ€§å°†å…¨éƒ¨åœį”¨īŧŒä¸‹æ–šå…ˇäŊ“莞įŊŽæ— æ•ˆã€‚", "machine_learning_facial_recognition": "äēē脏蝆åˆĢ", "machine_learning_facial_recognition_description": "æŖ€æĩ‹ã€č¯†åˆĢåšļč‡Ē动åŊ’įąģå›žį‰‡ä¸­įš„äēē脸", "machine_learning_facial_recognition_model": "äēē脏蝆åˆĢæ¨Ąåž‹", - "machine_learning_facial_recognition_model_description": "æ¨Ąåž‹æŒ‰æ–‡äģļäŊ“į§¯é™åēæŽ’åˆ—ã€‚æ›´å¤§įš„æ¨Ąåž‹čŋčĄŒé€ŸåēĻæ›´æ…ĸä¸”å į”¨æ›´å¤šå†…å­˜īŧŒäŊ†æ•ˆæžœæ›´åĨŊã€‚æŗ¨æ„īŧšæ›´æĸæ¨Ąåž‹åŽīŧŒåŋ…éĄģå¯šæ‰€æœ‰į…§į‰‡é‡æ–°æ‰§čĄŒâ€œäēēč„¸æŖ€æĩ‹â€äģģåŠĄã€‚", + "machine_learning_facial_recognition_model_description": "æ¨Ąåž‹æŒ‰å°ē寸降åēæŽ’åˆ—ã€‚čžƒå¤§įš„æ¨Ąåž‹čŋčĄŒé€ŸåēĻ螃æ…ĸä¸”å į”¨æ›´å¤šå†…å­˜īŧŒäŊ†æ•ˆæžœæ›´åĨŊã€‚č¯ˇæŗ¨æ„īŧŒæ›´æĸæ¨Ąåž‹åŽīŧŒåŋ…éĄģ重新čŋčĄŒæ‰€æœ‰å›žį‰‡įš„“äēēč„¸æŖ€æĩ‹â€äģģåŠĄã€‚", "machine_learning_facial_recognition_setting": "吝ᔍäēē脏蝆åˆĢ", - "machine_learning_facial_recognition_setting_description": "įĻį”¨åŽå°†ä¸äŧšå¯šį…§į‰‡čŋ›čĄŒäēē脏蝆åˆĢįŧ–᠁īŧŒä¸”“æŽĸį´ĸ”éĄĩéĸįš„â€œäēēį‰Šâ€éƒ¨åˆ†å°†ä¸äŧšæ˜žį¤ē内厚。", - "machine_learning_max_detection_distance": "æœ€å¤§æŖ€æĩ‹é˜ˆå€ŧ", - "machine_learning_max_detection_distance_description": "两åŧ å›žį‰‡čĸĢ视ä¸ēé‡å¤éĄšį›Žįš„é˜ˆå€ŧīŧŒčŒƒå›´ä¸ē0.001īŊž0.1。更éĢ˜įš„é˜ˆå€ŧčƒŊæŖ€æĩ‹å‡ēæ›´å¤šé‡å¤éĄšį›ŽīŧŒäŊ†č¯¯åˆ¤įš„可čƒŊ性䚟čļŠéĢ˜ã€‚", - "machine_learning_max_recognition_distance": "æœ€å¤§č¯†åˆĢ阈å€ŧ", - "machine_learning_max_recognition_distance_description": "两åŧ äēē脸čĸĢ视ä¸ē同一ä¸Ēäēēįš„é˜ˆå€ŧīŧŒčŒƒå›´ä¸ē0īŊž2。更äŊŽįš„阈å€ŧčƒŊéŋ免将两ä¸Ēäēē蝝判ä¸ē同一äēēīŧ›æ›´éĢ˜įš„é˜ˆå€ŧčƒŊéŋ免将同一ä¸Ēäēē蝝判ä¸ē两ä¸Ēäēēã€‚æŗ¨æ„īŧšåˆåšļ两ä¸Ēäēēį‰Šæ¯”æ‹†åˆ†ä¸€ä¸Ēäēēį‰Šæ›´åŽšæ˜“īŧŒåģē莎å°Ŋ可čƒŊäŧ˜å…ˆčŽžįŊŽčžƒäŊŽįš„阈å€ŧ。", - "machine_learning_min_detection_score": "æœ€å°æŖ€æĩ‹é˜ˆå€ŧ", - "machine_learning_min_detection_score_description": "äēēč„¸æŖ€æĩ‹įš„æœ€äŊŽįŊŽäŋĄåēĻ分数īŧŒčŒƒå›´ä¸ē0īŊž1。更äŊŽįš„æ•°å€ŧčƒŊæŖ€æĩ‹åˆ°æ›´å¤šäēē脸īŧŒäŊ†č¯¯åˆ¤įš„可čƒŊ性䚟čļŠéĢ˜ã€‚", + "machine_learning_facial_recognition_setting_description": "č‹Ĩå…ŗé—­æ­¤åŠŸčƒŊīŧŒå›žį‰‡å°†ä¸äŧščŋ›čĄŒäēē脏蝆åˆĢįŧ–᠁īŧŒä¸”“æŽĸį´ĸ”éĄĩéĸįš„â€œäēēį‰Šâ€æŋå—å°†æ— æŗ•æ˜žį¤ē内厚。", + "machine_learning_max_detection_distance": "æœ€å¤§æŖ€æĩ‹čˇįĻģ", + "machine_learning_max_detection_distance_description": "两åŧ å›žį‰‡čĸĢ视ä¸ēé‡å¤éĄšįš„æœ€å¤§čˇįĻģīŧŒå–å€ŧčŒƒå›´ä¸ē 0.001 - 0.1。数å€ŧčļŠé̘īŧŒæŖ€æĩ‹å‡ēįš„é‡å¤éĄščļŠå¤šīŧŒäŊ†å¯čƒŊå‡ēįŽ°č¯¯åˆ¤īŧˆäž‹åĻ‚å°†ä¸åŒįš„äēē蝆åˆĢä¸ē同一äēēīŧ‰ã€‚", + "machine_learning_max_recognition_distance": "æœ€å¤§č¯†åˆĢ距įĻģ", + "machine_learning_max_recognition_distance_description": "两åŧ äēē脸čĸĢ视ä¸ē同一ä¸Ēäēēįš„æœ€å¤§čˇįĻģīŧŒå–å€ŧčŒƒå›´ä¸ē 0 - 2ã€‚č°ƒäŊŽč¯Ĩå€ŧ可éŋ免将两ä¸Ēäēēč¯¯æ ‡ä¸ē同一äēēīŧŒč°ƒéĢ˜åˆ™å¯éŋ免将同一ä¸Ēäēēč¯¯æ ‡ä¸ē两ä¸Ēäēēã€‚č¯ˇæŗ¨æ„īŧŒäē‹åŽåˆåšļ两ä¸Ēäēēį‰Šæ¯”æ‹†åˆ†ä¸€ä¸Ēäēēį‰Šæ›´åŽšæ˜“īŧŒå› æ­¤åœ¨å¯čƒŊįš„æƒ…å†ĩ下īŧŒåģē莎äŧ˜å…ˆčŽžįŊŽčžƒäŊŽįš„阈å€ŧ。", + "machine_learning_min_detection_score": "最äŊŽæŖ€æĩ‹é˜ˆå€ŧ", + "machine_learning_min_detection_score_description": "äēēč„¸æŖ€æĩ‹įš„æœ€äŊŽįŊŽäŋĄåēĻ分数īŧŒå–å€ŧčŒƒå›´ä¸ē 0-1。数å€ŧčļŠäŊŽīŧŒæŖ€æĩ‹åˆ°įš„äēē脸čļŠå¤šīŧŒäŊ†å¯čƒŊå‡ēįŽ°č¯¯åˆ¤īŧˆäž‹åĻ‚å°†éžäēē脸åŒēåŸŸč¯†åˆĢä¸ēäēē脸īŧ‰ã€‚", "machine_learning_min_recognized_faces": "æœ€å°č¯†åˆĢ数量", - "machine_learning_min_recognized_faces_description": "创åģēäēēį‰Šæ‰€éœ€įš„æœ€å°äēēč„¸æ•°é‡ã€‚æ›´éĢ˜įš„æ•°å€ŧčƒŊ提升äēē脏蝆åˆĢįš„į˛žįĄŽåēĻīŧŒäŊ†äŧšåĸžåŠ äēēč„¸æ— æŗ•åŊ’įąģä¸ēäģģäŊ•äēēį‰Šįš„éŖŽé™Šã€‚", + "machine_learning_min_recognized_faces_description": "创åģēäēēį‰Šæ‰€éœ€įš„æœ€å°‘äēēč„¸æ•°é‡ã€‚č°ƒéĢ˜æ­¤å€ŧ可提升äēē脏蝆åˆĢįš„į˛žå‡†åēĻīŧŒäŊ†äŧšåĸžåŠ äēēč„¸æ— æŗ•čĸĢ分配įģ™äēēį‰Šįš„éŖŽé™Šã€‚", "machine_learning_ocr": "æ–‡å­—č¯†åˆĢīŧˆOCRīŧ‰", - "machine_learning_ocr_description": "通čŋ‡æœē器å­Ļäš č¯†åˆĢį…§į‰‡ä¸­įš„æ–‡æœŦ内厚", + "machine_learning_ocr_description": "åˆŠį”¨æœē器å­Ļäš æŠ€æœ¯č¯†åˆĢå›žį‰‡ä¸­įš„æ–‡æœŦ内厚", "machine_learning_ocr_enabled": "å¯į”¨æ–‡å­—č¯†åˆĢīŧˆOCRīŧ‰", - "machine_learning_ocr_enabled_description": "įĻį”¨åŽå°†ä¸äŧšæ‰§čĄŒæ–‡å­—č¯†åˆĢ。", + "machine_learning_ocr_enabled_description": "č‹ĨįρᔍīŧŒå°†ä¸äŧšå°č¯•蝆åˆĢå›žį‰‡ä¸­įš„æ–‡å­—ã€‚", "machine_learning_ocr_max_resolution": "æœ€å¤§åˆ†čž¨įŽ‡", - "machine_learning_ocr_max_resolution_description": "čļ…čŋ‡æ­¤åˆ†čž¨įŽ‡įš„éĸ„č§ˆå›žå°†æŒ‰æ¯”äž‹č°ƒæ•´å¤§å°ã€‚æ›´éĢ˜įš„æ•°å€ŧčƒŊäŊŋæ•ˆæžœæ›´į˛žįĄŽīŧŒäŊ†å¤„ᐆæ—ļ间更é•ŋ且需čĻå į”¨æ›´å¤šå†…å­˜ã€‚", - "machine_learning_ocr_min_detection_score": "æœ€å°æŖ€æĩ‹é˜ˆå€ŧ", - "machine_learning_ocr_min_detection_score_description": "文æœŦæŖ€æĩ‹įš„æœ€å°įŊŽäŋĄåēĻ分数īŧŒčŒƒå›´ä¸ē0īŊž1。更äŊŽįš„æ•°å€ŧčƒŊæŖ€æĩ‹å‡ē更多文æœŦ内厚īŧŒäŊ†č¯¯åˆ¤įš„可čƒŊ性䚟更éĢ˜ã€‚", - "machine_learning_ocr_min_recognition_score": "æœ€å°č¯†åˆĢ阈å€ŧ", - "machine_learning_ocr_min_score_recognition_description": "æ–‡å­—č¯†åˆĢįš„æœ€å°įŊŽäŋĄåēĻ分数īŧŒčŒƒå›´ä¸ē0īŊž1。更äŊŽįš„æ•°å€ŧčƒŊ蝆åˆĢå‡ē更多文æœŦ内厚īŧŒäŊ†é”™č¯¯č¯†åˆĢįš„å¯čƒŊ性䚟更éĢ˜ã€‚", - "machine_learning_ocr_model": "æ–‡å­—č¯†åˆĢīŧˆOCRīŧ‰æ¨Ąåž‹", + "machine_learning_ocr_max_resolution_description": "čļ…čŋ‡æ­¤åˆ†čž¨įŽ‡įš„éĸ„č§ˆå›žå°†æŒ‰æ¯”äž‹č°ƒæ•´å¤§å°ã€‚æ•°å€ŧčļŠé̘īŧŒæ•ˆæžœčļŠį˛žå‡†īŧŒäŊ†å¤„ᐆæ—ļ间更é•ŋä¸”æ›´å į”¨å†…å­˜ã€‚", + "machine_learning_ocr_min_detection_score": "最äŊŽæŖ€æĩ‹é˜ˆå€ŧ", + "machine_learning_ocr_min_detection_score_description": "文æœŦæŖ€æĩ‹įš„æœ€äŊŽįŊŽäŋĄåēĻ分数īŧˆ0-1īŧ‰ã€‚æ•°å€ŧčļŠäŊŽīŧŒæŖ€æĩ‹åˆ°įš„æ–‡æœŦčļŠå¤šīŧŒäŊ†å¯čƒŊå‡ēįŽ°č¯¯åˆ¤ã€‚", + "machine_learning_ocr_min_recognition_score": "最äŊŽč¯†åˆĢ阈å€ŧ", + "machine_learning_ocr_min_score_recognition_description": "åˇ˛æŖ€æĩ‹æ–‡æœŦįš„æœ€äŊŽįŊŽäŋĄåēĻ分数īŧˆ0-1īŧ‰ã€‚æ•°å€ŧčļŠäŊŽīŧŒč¯†åˆĢå‡ēįš„æ–‡æœŦčļŠå¤šīŧŒäŊ†å¯čƒŊå‡ēįŽ°č¯¯åˆ¤ã€‚", + "machine_learning_ocr_model": "OCRæ¨Ąåž‹", "machine_learning_ocr_model_description": "æœåŠĄå™¨įĢ¯æ¨Ąåž‹æ¯”į§ģ动įĢ¯æ¨Ąåž‹æ›´į˛žå‡†īŧŒäŊ†å¤„ᐆ耗æ—ļ更é•ŋä¸”æ›´å į”¨å†…å­˜ã€‚", "machine_learning_settings": "æœē器å­Ļ䚠莞įŊŽ", "machine_learning_settings_description": "įŽĄį†æœē器å­Ļ䚠功čƒŊåŠį›¸å…ŗčŽžįŊŽ", "machine_learning_smart_search": "æ™ēčƒŊ搜į´ĸ", "machine_learning_smart_search_description": "äŊŋᔍCLIPåĩŒå…Ĩ向量čŋ›čĄŒč¯­äš‰åŒ–å›žį‰‡æœį´ĸ", "machine_learning_smart_search_enabled": "吝ᔍæ™ēčƒŊ搜į´ĸ", - "machine_learning_smart_search_enabled_description": "įĻį”¨åŽį…§į‰‡å°†ä¸äŧšæ‰§čĄŒæ™ēčƒŊ搜į´ĸįŧ–į ã€‚", + "machine_learning_smart_search_enabled_description": "č‹ĨįρᔍīŧŒå›žį‰‡å°†ä¸äŧščĸĢįŧ–᠁äģĨᔍäēŽæ™ēčƒŊ搜į´ĸ。", "machine_learning_url_description": "æœē器å­Ļäš æœåŠĄå™¨įš„ URL。č‹Ĩ提䞛多ä¸Ē URLīŧŒįŗģįģŸå°†æŒ‰äģŽå‰åž€åŽįš„éĄēåēé€ä¸Ēå°č¯•čŋžæŽĨīŧŒį›´č‡ŗæœ‰æœåŠĄå™¨æˆåŠŸå“åē”ä¸ēæ­ĸ。æœĒčƒŊ响åē”įš„æœåŠĄå™¨å°†čĸĢæš‚æ—ļåŋŊį•ĨīŧŒį›´č‡ŗå…￁ĸ复在įēŋ。", - "maintenance_backup_management": "备äģŊįŽĄį†", "maintenance_delete_backup": "删除备äģŊ", "maintenance_delete_backup_description": "此文äģļ将čĸĢæ°¸äš…删除。", "maintenance_delete_error": "删除备äģŊå¤ąč´Ĩ。", - "maintenance_integrity_check": "æŖ€æŸĨ", - "maintenance_integrity_check_all": "æŖ€æŸĨ全部", - "maintenance_integrity_checksum_mismatch": "æ ĄéĒŒå’Œä¸åŒšé…", - "maintenance_integrity_checksum_mismatch_description": "文äģļįš„æ ĄéĒŒä¸Žæ•°æŽåē“įš„ä¸åŒšé…ã€‚", - "maintenance_integrity_checksum_mismatch_job": "æ ĄéĒŒå’Œä¸åŒšé…æŖ€æŸĨ", - "maintenance_integrity_checksum_mismatch_refresh_job": "åˆˇæ–°æ ĄéĒŒå’Œä¸åŒšé…æŠĨ告", - "maintenance_integrity_missing_file": "įŧēå¤ąæ–‡äģļ", - "maintenance_integrity_missing_file_description": "åˇ˛åœ¨æ•°æŽåē“中čŋŊč¸ĒīŧŒäŊ†æĄŖæĄˆįŗģįģŸä¸­åšļä¸å­˜åœ¨įš„æ–‡äģļ。", - "maintenance_integrity_missing_file_job": "æŖ€æŸĨįŧēå¤ąæ–‡äģļ", - "maintenance_integrity_missing_file_refresh_job": "åˆˇæ–°įŧēå¤ąæ–‡äģᅧĨ告", - "maintenance_integrity_report": "厌整性æŠĨ告", - "maintenance_integrity_untracked_file": "æœĒ莟č¸Ē文äģļ", - "maintenance_integrity_untracked_file_description": "在 Immich įš„į›ŽåŊ•中īŧŒäŊ†æ˛Ąæœ‰äģģäŊ•įēĒåŊ•įš„æ–‡äģļ。", - "maintenance_integrity_untracked_file_job": "æŖ€æŸĨæœĒ莟č¸Ē文äģļ", - "maintenance_integrity_untracked_file_refresh_job": "åˆˇæ–°æœĒ莟č¸Ē文äģᅧĨ告", "maintenance_restore_backup": "æĸ复备äģŊ", "maintenance_restore_backup_description": "Immich数捎将čĸĢæ¸…除īŧŒåšļäģŽé€‰åŽšįš„å¤‡äģŊ中æĸ复。在įģ§įģ­äš‹å‰īŧŒå°†å…ˆåˆ›åģē一ä¸ĒåŊ“å‰æ•°æŽįš„å¤‡äģŊ。", "maintenance_restore_backup_different_version": "此备äģŊæ˜¯į”ąä¸åŒį‰ˆæœŦįš„ Immich 创åģēįš„īŧ", @@ -291,8 +267,6 @@ "notification_enable_email_notifications": "å¯į”¨é‚Žäģļ通įŸĨ", "notification_settings": "通įŸĨ莞įŊŽ", "notification_settings_description": "įŽĄį†é€šįŸĨ莞įŊŽīŧŒåŒ…æ‹Ŧ邮äģļ通įŸĨ", - "oauth_allow_insecure_requests": "å…čŽ¸ä¸åŽ‰å…¨įš„č¯ˇæą‚", - "oauth_allow_insecure_requests_description": "č­Ļ告īŧščŋ™å°†įρᔍOAuthč¯ˇæą‚įš„TLS蝁äšĻénj蝁īŧŒåšļ可čƒŊäŊŋ您éĸ临MITMæ”ģå‡ģ。", "oauth_auto_launch": "č‡Ē动启动", "oauth_auto_launch_description": "čŋ›å…Ĩį™ģåŊ•éĄĩéĸæ—ļīŧŒč‡Ē动åŧ€å§‹ OAuth į™ģåŊ•æĩį¨‹", "oauth_auto_register": "č‡ĒåŠ¨æŗ¨å†Œ", @@ -300,11 +274,9 @@ "oauth_button_text": "按钎文字", "oauth_client_secret_description": "æœē密åŽĸæˆˇį̝åŋ…åĄĢīŧŒæˆ–å…Ŧå…ąåŽĸæˆˇį̝č‹Ĩ不支持 PKCEīŧˆäģŖį ä礿ĸč¯æ˜Žå¯†é’Ĩīŧ‰æ—ļåŋ…åĄĢ。", "oauth_enable_description": "äŊŋᔍOAuthį™ģåŊ•", - "oauth_end_session_url_description": "åŊ“į”¨æˆˇæŗ¨é”€æ—ļīŧŒå°†å…ļ重厚向到此URI。", "oauth_mobile_redirect_uri": "į§ģ动įĢ¯é‡åŽšå‘ URI", "oauth_mobile_redirect_uri_override": "į§ģ动įĢ¯é‡åŽšå‘ URI čφᛖ", "oauth_mobile_redirect_uri_override_description": "åŊ“ OAuth æäž›å•†ä¸å…čŽ¸äŊŋᔍį§ģ动į̝ URIīŧˆäž‹åĻ‚ “{callback}”īŧ‰æ—ļ吝ᔍ", - "oauth_prompt_description": "提į¤ē参数īŧˆäž‹åĻ‚select_account, login, consentīŧ‰", "oauth_role_claim": "č§’č‰˛åŖ°æ˜Ž", "oauth_role_claim_description": "æ šæŽæ­¤åŖ°æ˜Žįš„å­˜åœ¨č‡Ē动授äēˆįŽĄį†å‘˜čŽŋé—Žæƒé™ã€‚åŖ°æ˜Žå¯äģĨ是“user”īŧˆį”¨æˆˇīŧ‰æˆ–“admin”īŧˆįŽĄį†å‘˜īŧ‰ã€‚", "oauth_settings": "OAuth", @@ -331,8 +303,6 @@ "refreshing_all_libraries": "åˆˇæ–°æ‰€æœ‰åē“", "registration": "įŽĄį†å‘˜æŗ¨å†Œ", "registration_description": "į”ąäēŽæ‚¨æ˜¯įŗģįģŸįš„įŦŦ一äŊį”¨æˆˇīŧŒįŗģįģŸå°†č‡Ē动ä¸ēæ‚¨åˆ†é…įŽĄį†å‘˜æƒé™ã€‚æ‚¨éœ€čĻč´Ÿč´Ŗį›¸å…ŗįš„įŽĄį†äģģåŠĄīŧŒåŽįģ­įš„å…ļäģ–į”¨æˆˇäšŸå°†į”ąæ‚¨æĨ创åģē。", - "release_channel_release_candidate": "å€™é€‰å‘å¸ƒį‰ˆ", - "release_channel_stable": "į¨ŗåŽšį‰ˆ", "remove_failed_jobs": "į§ģé™¤å¤ąč´ĨäģģåŠĄ", "require_password_change_on_login": "åŧēåˆļį”¨æˆˇéĻ–æŦĄį™ģåŊ•æ—ļäŋŽæ”šå¯†į ", "reset_settings_to_default": "å°†čŽžįŊŽé‡įŊŽä¸ēéģ˜čޤå€ŧ", @@ -343,7 +313,7 @@ "server_external_domain_settings": "外部域名", "server_external_domain_settings_description": "å…Ŧåŧ€åˆ†äēĢ链æŽĨįš„åŸŸåīŧŒéœ€åŒ…åĢ http(s)://", "server_public_users": "į”¨æˆˇå…Ŧåŧ€", - "server_public_users_description": "åœ¨å°†į”¨æˆˇæˇģåŠ č‡ŗå…ąäēĢį›¸å†Œæ—ļīŧŒäŧšåˆ—å‡ēæ‰€æœ‰į”¨æˆˇīŧˆåŒ…æ‹Ŧå§“åå’Œé‚ŽįŽąīŧ‰ã€‚įĻį”¨åŽäģ…įŽĄį†å‘˜å¯č§į”¨æˆˇåˆ—čĄ¨ã€‚", + "server_public_users_description": "åœ¨å°†į”¨æˆˇæˇģåŠ č‡ŗå…ąäēĢį›¸å†Œæ—ļīŧŒäŧšåˆ—å‡ēæ‰€æœ‰į”¨æˆˇīŧˆåŒ…æ‹Ŧå§“åå’Œé‚ŽįŽąīŧ‰ã€‚č‹ĨįĻį”¨æ­¤é€‰éĄšīŧŒåˆ™äģ…įŽĄį†å‘˜å¯č§į”¨æˆˇåˆ—čĄ¨ã€‚", "server_settings": "æœåŠĄå™¨čŽžįŊŽ", "server_settings_description": "įŽĄį†æœåŠĄå™¨čŽžįŊŽ", "server_stats_page_description": "įŽĄį†æœåŠĄå™¨įģŸčŽĄéĄĩéĸ", @@ -368,7 +338,7 @@ "storage_template_path_length": "čŋ‘äŧŧčˇ¯åž„é•ŋåēĻ限åˆļīŧš{length, number}/{limit, number}", "storage_template_settings": "å­˜å‚¨æ¨Ąæŋ", "storage_template_settings_description": "įŽĄį†å­˜æ”žåˇ˛ä¸Šäŧ į…§į‰‡/视éĸ‘įš„æ–‡äģļ多į쓿ž„和文äģļ名", - "storage_template_user_label": "č¯Ĩį”¨æˆˇįš„ã€Œå­˜å‚¨æ ‡į­žã€ä¸ē{label}", + "storage_template_user_label": "{label}ä¸ēč¯Ĩį”¨æˆˇįš„å­˜å‚¨æ ‡į­ž", "system_settings": "įŗģįģŸčŽžįŊŽ", "tag_cleanup_job": "æ ‡į­žæ¸…į†", "template_email_available_tags": "您可äģĨåœ¨æ¨Ąæŋ中äŊŋᔍäģĨ下变量īŧš{tags}", @@ -381,7 +351,7 @@ "template_settings": "通įŸĨæ¨Ąæŋ", "template_settings_description": "įŽĄį†é€šįŸĨįš„č‡ĒåŽšäš‰æ¨Ąæŋ", "theme_custom_css_settings": "č‡Ē厚䚉 CSS", - "theme_custom_css_settings_description": "支持通čŋ‡ CSS č‡Ē厚䚉 Immich į•ŒéĸčŽžčŽĄã€‚", + "theme_custom_css_settings_description": "äŊŋᔍCSSč‡Ē厚䚉Immichį•ŒéĸčŽžčŽĄã€‚", "theme_settings": "ä¸ģéĸ˜čŽžįŊŽ", "theme_settings_description": "č‡Ē厚䚉 Immich Web į•Œéĸ", "thumbnail_generation_job": "į”ŸæˆįŧŠį•Ĩ回", @@ -427,14 +397,6 @@ "transcoding_preferred_hardware_device_description": "äģ…适ᔍäēŽVAAPI和QSVã€‚čŽžįŊŽį”¨äēŽįĄŦäģļčŊŦį įš„DRIčŽžå¤‡čŠ‚į‚šã€‚", "transcoding_preset_preset": "éĸ„莞īŧˆ-presetīŧ‰", "transcoding_preset_preset_description": "压įŧŠé€ŸåēĻ。éĸ„čŽžé€ŸåēĻč…ĸīŧŒį”Ÿæˆįš„æ–‡äģļčļŠå°īŧ›åœ¨čŽžåŽšį‰šåŽšį įŽ‡æ—ļīŧŒčŋ˜čƒŊ提升į”ģč´¨ã€‚VP9 įŧ–᠁噍äŧšåŋŊį•Ĩīŧˆä¸æ”¯æŒīŧ‰é̘äēŽâ€œfaster”速åēĻįš„é€‰éĄšã€‚", - "transcoding_realtime": "厞æ—ļčŊŦ᠁ [厞éĒŒæ€§åŠŸčƒŊ]", - "transcoding_realtime_description": "å…čŽ¸åœ¨č§†éĸ‘æĩåŧäŧ čž“īŧˆæ’­æ”žīŧ‰įš„同æ—ļ厞æ—ļčŋ›čĄŒčŊŦį ã€‚č¯Ĩ功čƒŊ支持动态切æĸį”ģč´¨īŧŒäŊ†æ šæŽæœåŠĄå™¨æ€§čƒŊįš„ä¸åŒīŧŒå¯čƒŊäŧšå¯ŧ致较éĢ˜įš„æ’­æ”žåģļčŋŸæˆ–į”ģéĸåĄéĄŋ。", - "transcoding_realtime_enabled": "å¯į”¨åŽžæ—ļčŊŦ᠁", - "transcoding_realtime_enabled_description": "åĻ‚æžœįρᔍīŧŒæœåŠĄå™¨å°†æ‹’įģå¯åŠ¨æ–°įš„åŽžæ—ļčŊŦ᠁äŧšč¯ã€‚", - "transcoding_realtime_resolutions": "åˆ†čž¨įŽ‡", - "transcoding_realtime_resolutions_description": "čŋ™æ˜¯åŽžæ—ļčŊŦį æ‰€æäž›įš„åˆ†čž¨įŽ‡é€‰éĄšã€‚åĻ‚æžœæœåŠĄå™¨æ— æŗ•äģĨčļŗå¤ŸåŋĢįš„é€ŸåēĻčŋ›čĄŒčŊŦ᠁īŧŒäŊŋį”¨æ›´éĢ˜åˆ†čž¨įŽ‡å¯čƒŊäŧšå¯ŧč‡´æ’­æ”žé—Žéĸ˜ã€‚", - "transcoding_realtime_video_codecs": "视éĸ‘įŧ–觪᠁噍", - "transcoding_realtime_video_codecs_description": "čŋ™æ˜¯į”¨äēŽåŽžæ—ļčŊŦį įš„č§†éĸ‘įŧ–č§Ŗį å™¨é€‰éĄšã€‚åŽĸæˆˇį̝äŧšåœ¨æ’­æ”žæ—ļ选拊å…ļæ”¯æŒįš„æœ€äŊŗæ–šæĄˆã€‚AV1 įš„æ•ˆįŽ‡é̘äēŽ HEVCīŧŒč€Œ HEVC įš„æ•ˆįŽ‡åˆé̘äēŽ H.264。äŊŋᔍįĄŦäģļ加速æ—ļīŧŒč¯ˇäģ…选拊加速器支持įŧ–į įš„įŧ–č§Ŗį å™¨ã€‚äŊŋᔍčŊ¯äģļčŊŦ᠁æ—ļīŧŒč¯ˇæŗ¨æ„ H.264 įš„é€ŸåēĻåŋĢäēŽ AV1īŧŒč€Œ AV1 įš„é€ŸåēĻåŋĢäēŽ HEVC。", "transcoding_reference_frames": "å‚č€ƒå¸§", "transcoding_reference_frames_description": "在压įŧŠæŒ‡åޚ叧æ—ļīŧŒæ‰€å‚č€ƒįš„å¸§æ•°é‡ã€‚æ•°å€ŧčļŠé̘īŧŒåŽ‹įŧŠæ•ˆįއčļŠé̘īŧŒäŊ†äŧšé™äŊŽįŧ–į é€ŸåēĻ。0 襨į¤ēį”ąįŗģįģŸč‡ĒåŠ¨čŽžįŊŽã€‚", "transcoding_required_description": "äģ…非标准æ ŧåŧįš„视éĸ‘", @@ -478,8 +440,6 @@ "user_settings_description": "įŽĄį†į”¨æˆˇčŽžįŊŽ", "user_successfully_removed": "į”¨æˆˇ {email} åˇ˛æˆåŠŸåˆ é™¤ã€‚", "users_page_description": "įŽĄį†į”¨æˆˇéĄĩéĸ", - "version_check_channel": "更新通道", - "version_check_channel_description": "é€‰æ‹Šæ‚¨æƒŗčρæŽĨæ”ļį‰ˆæœŦ更新å…Ŧå‘Šįš„å‘å¸ƒæ¸ é“", "version_check_enabled_description": "æŖ€æŸĨčŊ¯äģļæ–°į‰ˆæœŦ", "version_check_implications": "į‰ˆæœŦæŖ€æŸĨ功čƒŊ䞝čĩ–äēŽä¸Ž {server} įš„åŽšæœŸé€šäŋĄ", "version_check_settings": "æ–°į‰ˆæœŦæŖ€æŸĨ", @@ -494,6 +454,8 @@ "advanced_settings_clear_image_cache": "清įŠē回像įŧ“å­˜", "advanced_settings_clear_image_cache_error": "æ— æŗ•æ¸…įŠē回像įŧ“å­˜", "advanced_settings_clear_image_cache_success": "æˆåŠŸæ¸…į† {size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "äŊŋį”¨æ­¤é€‰éĄšå¯æ šæŽæ›ŋäģŖæ ‡å‡†åœ¨åŒæ­Ĩ期间čŋ‡æģ¤åĒ’äŊ“。äģ…åŊ“åē”į”¨æ— æŗ•æŖ€æĩ‹æ‰€æœ‰į›¸å†Œæ—ļīŧŒæ‰å°č¯•äŊŋį”¨æ­¤é€‰éĄšã€‚", + "advanced_settings_enable_alternate_media_filter_title": "[厞éĒŒæ€§] äŊŋį”¨å¤‡į”¨čŽžå¤‡į›¸å†Œį­›é€‰æ–šåŧ", "advanced_settings_log_level_title": "æ—Ĩåŋ—į­‰įē§: {level}", "advanced_settings_prefer_remote_subtitle": "éƒ¨åˆ†čŽžå¤‡č¯ģ取æœŦ地文äģļįŧŠį•Ĩå›žįš„é€ŸåēĻæžæ…ĸ。åŧ€å¯æ­¤čŽžįŊŽå¯æ”šä¸ē加čŊŊčŋœį¨‹å›žį‰‡ã€‚", "advanced_settings_prefer_remote_title": "äŧ˜å…ˆäŊŋᔍčŋœį¨‹å›žį‰‡", @@ -501,6 +463,8 @@ "advanced_settings_proxy_headers_title": "č‡Ē厚䚉äģŖį†å¤´äŋĄæ¯ [厞éĒŒæ€§]", "advanced_settings_readonly_mode_subtitle": "吝ᔍåĒč¯ģæ¨ĄåŧīŧŒåœ¨æ­¤æ¨Ąåŧä¸‹äģ…可æŸĨįœ‹į…§į‰‡īŧŒå¤šé€‰ã€åˆ†äēĢã€æŠ•åąã€åˆ é™¤į­‰åŠŸčƒŊ将全部įĻį”¨ã€‚å¯é€ščŋ‡ä¸ģåąåš•ä¸Šįš„į”¨æˆˇå¤´åƒåŧ€å¯/å…ŗé—­åĒč¯ģæ¨Ąåŧ", "advanced_settings_readonly_mode_title": "åĒč¯ģæ¨Ąåŧ", + "advanced_settings_self_signed_ssl_subtitle": "莺čŋ‡æœåŠĄå™¨įĢ¯į‚šįš„ SSL 蝁äšĻéĒŒč¯ã€‚č‡Ēį­žåč¯äšĻ情å†ĩ下需čρåŧ€å¯æ­¤é€‰éĄšã€‚", + "advanced_settings_self_signed_ssl_title": "å…čŽ¸äŊŋᔍč‡Ēį­žå SSL 蝁äšĻ[厞éĒŒæ€§]", "advanced_settings_sync_remote_deletions_subtitle": "åŊ“在įŊ‘éĄĩįĢ¯æ‰§čĄŒåˆ é™¤æˆ–æĸ复操äŊœæ—ļīŧŒč‡Ē动在æœŦčŽžå¤‡ä¸ŠåŒæ­Ĩæ‰§čĄŒč¯Ĩ操äŊœ", "advanced_settings_sync_remote_deletions_title": "同æ­Ĩčŋœį¨‹åˆ é™¤æ“äŊœ [厞éĒŒæ€§]", "advanced_settings_tile_subtitle": "é̘įē§į”¨æˆˇčŽžįŊŽ", @@ -516,18 +480,31 @@ "album_delete_confirmation": "įĄŽåŽščĻåˆ é™¤į›¸å†Œ “{album}” 吗īŧŸ", "album_delete_confirmation_description": "åĻ‚æžœæ­¤į›¸å†Œåˇ˛čĸĢå…ąäēĢīŧŒå…ļäģ–į”¨æˆˇäšŸå°†æ— æŗ•å†čŽŋ闎厃。", "album_deleted": "į›¸å†Œåˇ˛åˆ é™¤", + "album_info_card_backup_album_excluded": "åˇ˛æŽ’é™¤", + "album_info_card_backup_album_included": "åˇ˛åŒ…åĢ", "album_info_updated": "į›¸å†ŒäŋĄæ¯åˇ˛æ›´æ–°", + "album_leave": "退å‡ēį›¸å†ŒīŧŸ", + "album_leave_confirmation": "įĄŽåŽščρ退å‡ēį›¸å†Œ “{album}” 吗īŧŸ", "album_name": "į›¸å†Œåį§°", "album_options": "į›¸å†Œé€‰éĄš", "album_remove_user": "į§ģé™¤į”¨æˆˇīŧŸ", "album_remove_user_confirmation": "įĄŽåŽščρį§ģ除 “{user}” 吗īŧŸ", "album_search_not_found": "æœĒ扞到与搜į´ĸæĄäģļåŒšé…įš„į›¸å†Œ", + "album_selected": "į›¸å†Œåˇ˛é€‰ä¸­", "album_share_no_users": "æ‚¨åˇ˛å°†æ­¤į›¸å†Œå…ąäēĢį왿‰€æœ‰į”¨æˆˇīŧŒæˆ–æ˛Ąæœ‰å¯å…ąäēĢįš„į”¨æˆˇã€‚", "album_summary": "į›¸å†ŒæĻ‚č§ˆ", "album_updated": "į›¸å†Œåˇ˛æ›´æ–°", "album_updated_setting_description": "åŊ“å…ąäēĢį›¸å†Œæœ‰æ–°å†…åŽšæ—ļīŧŒé€ščŋ‡é‚Žäģļ通įŸĨ", "album_upload_assets": "äģŽæ‚¨įš„į”ĩ脑上äŧ æ–‡äģļåšļæˇģåŠ åˆ°į›¸å†Œ", + "album_user_left": "厞退å‡ē “{album}”", + "album_user_removed": "厞į§ģ除 “{user}”", + "album_viewer_appbar_delete_confirm": "įĄŽåŽščρäģŽč´Ļæˆˇä¸­åˆ é™¤æ­¤į›¸å†Œå—īŧŸ", "album_viewer_appbar_share_err_delete": "åˆ é™¤į›¸å†Œå¤ąč´Ĩ", + "album_viewer_appbar_share_err_leave": "退å‡ēå…ąäēĢå¤ąč´Ĩ", + "album_viewer_appbar_share_err_remove": "äģŽį›¸å†Œį§ģ除内厚æ—ļå‡ēįŽ°é—Žéĸ˜", + "album_viewer_appbar_share_err_title": "äŋŽæ”šį›¸å†Œæ ‡éĸ˜å¤ąč´Ĩ", + "album_viewer_appbar_share_leave": "退å‡ēį›¸å†Œ", + "album_viewer_appbar_share_to": "分äēĢįģ™", "album_viewer_page_share_add_users": "邀蝎äģ–äēē", "album_with_link_access": "å…čŽ¸äģģäŊ•æ‹Ĩ有č¯Ĩ链æŽĨįš„äē翟Ĩįœ‹æ­¤į›¸å†Œä¸­įš„į…§į‰‡å’Œäēēį‰Šã€‚", "albums": "į›¸å†Œ", @@ -536,12 +513,14 @@ "albums_default_sort_order_description": "创åģēæ–°į›¸å†Œæ—ļīŧŒčĩ„æēįš„初始排åēæ–šåŧã€‚", "albums_feature_description": "可与å…ļäģ–į”¨æˆˇå…ąäēĢįš„į…§į‰‡/内厚合集。", "albums_on_device_count": "čŽžå¤‡ä¸Šįš„į›¸å†Œīŧˆ{count} ä¸Ēīŧ‰", + "albums_selected": "{count, plural, one {厞选䏭#ä¸Ēį›¸å†Œ} other {厞选䏭#ä¸Ēį›¸å†Œ}}", "all": "全部", "all_albums": "æ‰€æœ‰į›¸å†Œ", "all_people": "全部äēēį‰Š", "all_photos": "æ‰€æœ‰į…§į‰‡", "all_videos": "æ‰€æœ‰č§†éĸ‘", "allow_dark_mode": "å…čŽ¸æˇąč‰˛æ¨Ąåŧ", + "allow_edits": "å…čŽ¸įŧ–čž‘", "allow_public_user_to_download": "å…čŽ¸æ‰€æœ‰į”¨æˆˇä¸‹čŊŊ", "allow_public_user_to_upload": "å…čŽ¸æ‰€æœ‰į”¨æˆˇä¸Šäŧ ", "allowed": "å…čŽ¸", @@ -549,12 +528,14 @@ "always_keep": "始įģˆäŋį•™", "always_keep_photos_hint": "åŧ€å¯â€œé‡Šæ”žįŠē间”后īŧŒäģäŧšäŋį•™æ‰€æœ‰į…§į‰‡åœ¨æœŦčŽžå¤‡ä¸Šã€‚", "always_keep_videos_hint": "åŧ€å¯â€œé‡Šæ”žįŠē间”后īŧŒäģäŧšäŋį•™æ‰€æœ‰č§†éĸ‘在æœŦčŽžå¤‡ä¸Šã€‚", + "anti_clockwise": "逆æ—ļ针", "api_key": "API密é’Ĩ", "api_key_description": "č¯Ĩåē”ᔍ坆é’ĨåĒäŧšæ˜žį¤ē一æŦĄã€‚č¯ˇįĄŽäŋåœ¨å…ŗé—­įĒ—åŖå‰å¤åˆļ下æĨ。", "api_key_empty": "API密é’Ĩåį§°ä¸å¯ä¸ēįŠē", "api_keys": "API密é’Ĩ", "app_architecture_variant": "变äŊ“īŧˆæžļ构īŧ‰", "app_bar_signout_dialog_content": "æ‚¨įĄŽåŽščρ退å‡ē吗īŧŸ", + "app_bar_signout_dialog_ok": "是", "app_bar_signout_dialog_title": "退å‡ēį™ģåŊ•", "app_download_links": "åē”ᔍ䏋čŊŊ链æŽĨ", "app_settings": "åē”į”¨čŽžįŊŽ", @@ -565,19 +546,27 @@ "archive": "åŊ’æĄŖ", "archive_action_prompt": "厞将 {count} 饚æˇģ加到åŊ’æĄŖ", "archive_or_unarchive_photo": "åŊ’æĄŖæˆ–å–æļˆåŊ’æĄŖį…§į‰‡", + "archive_page_no_archived_assets": "æœĒæ‰žåˆ°åˇ˛åŊ’æĄŖįš„åŊąåƒ", + "archive_page_title": "åŊ’æĄŖīŧˆ{count}īŧ‰", "archive_size": "åŊ’æĄŖå¤§å°", "archive_size_description": "配įŊŽä¸‹čŊŊįš„åŊ’æĄŖå¤§å°īŧˆGiBīŧ‰", "archived": "厞åŊ’æĄŖ", "archived_count": "{count, plural, other {厞åŊ’æĄŖ#饚}}", "are_these_the_same_person": "čŋ™æ˜¯åŒä¸€ä¸Ēäēē吗īŧŸ", "are_you_sure_to_do_this": "įĄŽåŽščĻæ‰§čĄŒæ­¤æ“äŊœīŧŸ", + "array_field_not_fully_supported": "数įģ„å­—æŽĩ需čĻæ‰‹åŠ¨čŋ›čĄŒ JSON įŧ–čž‘", + "asset_action_delete_err_read_only": "æ— æŗ•åˆ é™¤åĒč¯ģéĄšį›ŽīŧŒåˇ˛čˇŗčŋ‡", + "asset_action_share_err_offline": "æ— æŗ•čŽˇå–įĻģįēŋéĄšį›ŽīŧŒåˇ˛čˇŗčŋ‡", "asset_added_to_album": "厞æˇģåŠ č‡ŗį›¸å†Œ", "asset_adding_to_album": "æ­Ŗåœ¨æˇģåŠ č‡ŗį›¸å†Œâ€Ļ", "asset_created": "éĄšį›Žåˇ˛åˆ›åģē", - "asset_day_count": "{date}: {count, plural, one {# ä¸Ēčĩ„æē} other {# ä¸Ēčĩ„æē}}", "asset_description_updated": "éĄšį›Žæčŋ°åˇ˛æ›´æ–°", + "asset_filename_is_offline": "éĄšį›Ž{filename}厞įĻģįēŋ", + "asset_has_unassigned_faces": "éĄšį›ŽåŒ…åĢæœĒåˆ†é…įš„äēē脸", "asset_hashing": "æ­Ŗåœ¨čŽĄįŽ—å“ˆå¸Œå€ŧâ€Ļ", "asset_list_group_by_sub_title": "分įģ„䞝捎", + "asset_list_layout_settings_dynamic_layout_title": "åŠ¨æ€å¸ƒåą€", + "asset_list_layout_settings_group_automatically": "č‡Ē动", "asset_list_layout_settings_group_by": "ᅧቇ/视éĸ‘分įģ„䞝捎", "asset_list_layout_settings_group_by_month_day": "月äģŊ + æ—Ĩ期", "asset_list_layout_sub_title": "å¸ƒåą€", @@ -588,77 +577,125 @@ "asset_not_found_on_icloud": "iCloud中æœĒ扞到č¯Ĩᅧቇ/视éĸ‘。可čƒŊæ˜¯į”ąäēŽiCloud中存储äē†é”™č¯¯įš„æ–‡äģļå¯ŧ致čĩ„æēæ— æŗ•čŽŋ问", "asset_offline": "éĄšį›ŽįĻģįēŋ", "asset_offline_description": "įŖį›˜ä¸ŠæœĒ扞到此外部文äģļã€‚č¯ˇč”įŗģæ‚¨įš„ Immich įŽĄį†å‘˜å¯ģæą‚å¸ŽåŠŠã€‚", + "asset_restored_successfully": "文äģ￁ĸ复成功", "asset_skipped": "厞莺čŋ‡", "asset_skipped_in_trash": "在回æ”ļį̙䏭", + "asset_trashed": "文äģļ厞į§ģč‡ŗå›žæ”ļįĢ™", "asset_troubleshoot": "文äģļč¯Šæ–­", "asset_uploaded": "厞䏊äŧ ", "asset_uploading": "上äŧ ä¸­â€Ļ", "asset_viewer_settings_subtitle": "įŽĄį†į”ģå슿ŸĨįœ‹å™¨čŽžįŊŽ", "asset_viewer_settings_title": "文äģ￟Ĩįœ‹å™¨", "assets": "čĩ„æē", + "assets_added_count": "厞æˇģ加{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}", "assets_added_to_album_count": "åˇ˛å‘į›¸å†Œæˇģ加{count, plural, one {#ä¸Ēčĩ„æē} other {#ä¸Ēčĩ„æē}}", - "assets_added_to_album_partial_count": "åˇ˛æˆåŠŸå°† {totalCount}{totalCount, plural, one {asset} other {assets}}ä¸ĒéĄšį›Žä¸­įš„ {successCount} ä¸ĒæˇģåŠ åˆ°į›¸å†Œ", - "assets_added_to_albums_count": "厞向 {albumTotal, plural, one {# ä¸Ēį›¸å†Œ} other {# ä¸Ēį›¸å†Œ}}æˇģ加 {assetTotal, plural, one {# ä¸Ēčĩ„æē} other {# ä¸Ēčĩ„æē}}", + "assets_added_to_albums_count": "厞向 {albumTotal, plural, one {# ä¸Ēį›¸å†Œ} other {# ä¸Ēį›¸å†Œ}}æˇģ加 {assetTotal, plural, one {# ä¸Ēčĩ„æē} other {# ä¸ĒåŒģé™ĸ}}", "assets_cannot_be_added_to_album_count": "æ— æŗ•å‘į›¸å†Œæˇģ加{count, plural, one {ä¸Ēčĩ„æē} other {ä¸Ēčĩ„æē}}", "assets_cannot_be_added_to_albums": "æ— æŗ•å‘äģģäŊ•一ä¸Ēį›¸å†Œæˇģ加 {count, plural, one {ä¸Ēčĩ„æē} other {ä¸Ēčĩ„æē}}", "assets_count": "{count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}", + "assets_deleted_permanently": "åˇ˛æ°¸äš…åˆ é™¤ {count} ä¸Ē文äģļ", + "assets_deleted_permanently_from_server": "åˇ˛æ°¸äš…į§ģ除 {count} ä¸Ē文äģļ", + "assets_downloaded_failed": "{count, plural, one {厞䏋čŊŊ#ä¸Ē文äģļ - {error}ä¸Ē文äģļ下čŊŊå¤ąč´Ĩ} other {厞䏋čŊŊ#ä¸Ē文äģļ - {error}ä¸Ē文äģļ下čŊŊå¤ąč´Ĩ}}", + "assets_downloaded_successfully": "{count, plural, one {åˇ˛æˆåŠŸä¸‹čŊŊ#ä¸Ē文äģļ} other {åˇ˛æˆåŠŸä¸‹čŊŊ#ä¸Ē文äģļ}}", "assets_moved_to_trash_count": "厞将{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}į§ģ动到回æ”ļįĢ™", "assets_permanently_deleted_count": "åˇ˛æ°¸äš…åˆ é™¤{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}", "assets_removed_count": "厞į§ģ除{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}", + "assets_removed_permanently_from_device": "厞äģŽæ‚¨įš„čŽžå¤‡ä¸­æ°¸äš…åˆ é™¤{count}ä¸Ē文äģļ", "assets_restore_confirmation": "æ‚¨įĄŽåŽščρæĸ复回æ”ļįĢ™ä¸­įš„æ‰€æœ‰æ–‡äģļ吗īŧŸæ­¤æ“äŊœæ— æŗ•撤销īŧč¯ˇæŗ¨æ„īŧŒįĻģįēŋ文äģļæ— æŗ•通čŋ‡æ­¤æ–šåŧæĸ复。", "assets_restored_count": "厞æĸ复{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}", + "assets_restored_successfully": "åˇ˛æˆåŠŸæĸ复{count}ä¸Ē文äģļ", "assets_trashed": "{count}ä¸Ē文äģļį§ģč‡ŗå›žæ”ļįĢ™", "assets_trashed_count": "厞将{count, plural, one {#ä¸Ē文äģļ} other {#ä¸Ē文äģļ}}į§ģč‡ŗå›žæ”ļįĢ™", - "assets_were_part_of_album_count": "{count, plural, one {ä¸Ēčĩ„æē} other {ä¸Ēčĩ„æē}}厞圍č¯Ĩį›¸å†Œä¸­", - "assets_were_part_of_albums_count": "{count, plural, one {ä¸Ēčĩ„æē} other {ä¸Ēčĩ„æē}} 厞存圍äēŽčŋ™äē›į›¸å†Œä¸­", + "assets_trashed_from_server": "厞äģŽImmichæœåŠĄå™¨ä¸Šį§ģ除{count}ä¸Ē文äģļ", + "assets_were_part_of_album_count": "{count, plural, one {ä¸ĒåŒģé™ĸ} other {ä¸Ēčĩ„æē}}厞圍č¯Ĩį›¸å†Œä¸­", + "assets_were_part_of_albums_count": "{count, plural, one {ä¸ĒéĄšį›Ž} other {ä¸ĒéĄšį›Ž}}厞存圍äēŽčŋ™äē›į›¸į°ŋ中", "authorized_devices": "åˇ˛æŽˆæƒčŽžå¤‡", "automatic_endpoint_switching_subtitle": "åœ¨å¯į”¨æ—ļ通čŋ‡æŒ‡åŽšįš„ Wi-Fi čŋ›čĄŒæœŦ地čŋžæŽĨīŧŒå…ļäģ–äŊįŊŽåˆ™äŊŋᔍæ›ŋäģŖįŊ‘įģœčŋžæŽĨ", "automatic_endpoint_switching_title": "č‡Ē动切æĸ URL", "autoplay_slideshow": "č‡Ē动播攞åšģၝቇ", "back": "čŋ”回", "back_close_deselect": "čŋ”å›žã€å…ŗé—­æˆ–å–æļˆé€‰æ‹Š", + "background_backup_running_error": "后台备äģŊæ­Ŗåœ¨čŋčĄŒä¸­īŧŒæ— æŗ•启动手动备äģŊ", "background_location_permission": "后台厚äŊæƒé™", "background_location_permission_content": "ä¸ēäē†åœ¨åŽå°čŋčĄŒæ—ļåŽžįŽ°įŊ‘įģœåˆ‡æĸīŧŒImmichåŋ…éĄģ始į숿‹Ĩæœ‰į˛žįĄŽäŊįŊŽčŽŋ闎权限īŧŒäģĨäžŋåē”ᔍčƒŊ够č¯ģ取 Wi-Fi įŊ‘įģœįš„åį§°", "background_options": "åŽå°é€‰éĄš", "backup": "备äģŊ", + "backup_album_selection_page_albums_device": "čŽžå¤‡ä¸Šįš„į›¸į°ŋīŧˆ{count}īŧ‰", "backup_album_selection_page_albums_tap": "单å‡ģ包åĢīŧŒåŒå‡ģ排除", "backup_album_selection_page_assets_scatter": "čĩ„æēå¯äģĨåˆ†æ•Ŗåœ¨å¤šä¸Ēį›¸å†Œä¸­ã€‚å› æ­¤īŧŒåœ¨å¤‡äģŊčŋ‡į¨‹ä¸­īŧŒå¯äģĨ包åĢ或排除某äē›į›¸å†Œã€‚", - "backup_album_selection_page_select_albums": "é€‰æ‹Šį›¸å†Œ", + "backup_album_selection_page_select_albums": "é€‰æ‹Šį›¸į°ŋ", "backup_album_selection_page_selection_info": "选拊äŋĄæ¯", - "backup_albums_sync": "备äģŊį›¸å†ŒåŒæ­Ĩ", + "backup_album_selection_page_total_assets": "é€‰ä¸­įš„į…§į‰‡æˆ–č§†éĸ‘æ€ģ数", + "backup_albums_sync": "备äģŊᛏį°ŋ同æ­Ĩ", + "backup_all": "全部", + "backup_background_service_backup_failed_message": "文äģļ备äģŊå¤ąč´Ĩã€‚æ­Ŗåœ¨é‡č¯•â€Ļ", "backup_background_service_complete_notification": "文äģļ备äģŊ厌成", + "backup_background_service_connection_failed_message": "æ— æŗ•čŋžæŽĨåˆ°æœåŠĄå™¨ã€‚æ­Ŗåœ¨é‡č¯•â€Ļ", + "backup_background_service_current_upload_notification": "æ­Ŗåœ¨ä¸Šäŧ  “{filename}”", "backup_background_service_default_notification": "æ­Ŗåœ¨æŖ€æŸĨ新文äģļâ€Ļ", + "backup_background_service_error_title": "备äģŊ错蝝", "backup_background_service_in_progress_notification": "æ­Ŗåœ¨å¤‡äģŊæ‚¨įš„æ–‡äģļâ€Ļ", - "backup_controller_page_albums": "备äģŊį›¸å†Œ", + "backup_background_service_upload_failure_notification": "“{filename}”上äŧ å¤ąč´Ĩ", + "backup_controller_page_albums": "备äģŊᛏį°ŋ", + "backup_controller_page_background_app_refresh_disabled_content": "åœ¨â€œčŽžįŊŽâ€>â€œé€šį”¨â€>“后台 App åˆˇæ–°â€ä¸­å¯į”¨æ­¤åŠŸčƒŊīŧŒäģĨäŊŋį”¨åŽå°å¤‡äģŊ。", + "backup_controller_page_background_app_refresh_disabled_title": "后台 App åˆˇæ–°åˇ˛å…ŗé—­", + "backup_controller_page_background_app_refresh_enable_button_text": "å‰åž€čŽžįŊŽ", "backup_controller_page_background_battery_info_link": "åą•į¤ē操äŊœæ­ĨéǤ", "backup_controller_page_background_battery_info_message": "ä¸ēčŽˇåž—æœ€äŊŗįš„后台备äģŊäŊ“énjīŧŒč¯ˇåœ¨įŗģįģŸčŽžįŊŽä¸­įĻį”¨é’ˆå¯šImmichįš„äģģäŊ•į”ĩæą äŧ˜åŒ–限åˆļ。\n\nį”ąäēŽč¯Ĩ莞įŊŽå› čŽžå¤‡č€Œåŧ‚īŧŒč¯ˇæŸĨč¯ĸæ‚¨čŽžå¤‡åˆļé€ å•†įš„å…ˇäŊ“čĻæą‚ã€‚", "backup_controller_page_background_battery_info_ok": "我įŸĨ道äē†", "backup_controller_page_background_battery_info_title": "į”ĩæą äŧ˜åŒ–", + "backup_controller_page_background_charging": "äģ…在充į”ĩæ—ļ", + "backup_controller_page_background_configure_error": "åŽå°æœåŠĄé…įŊŽå¤ąč´Ĩ", "backup_controller_page_background_delay": "åģļčŋŸæ–°æ–‡äģļ备äģŊīŧš{duration}", + "backup_controller_page_background_description": "åŧ€å¯åŽå°æœåŠĄīŧŒåŗå¯åœ¨æ— éœ€æ‰“åŧ€ App įš„æƒ…å†ĩ下īŧŒč‡Ē动备äģŊ所有新文äģļ", + "backup_controller_page_background_is_off": "后台č‡Ē动备äģŊæœĒåŧ€å¯", + "backup_controller_page_background_is_on": "后台č‡Ē动备äģŊ厞åŧ€å¯", + "backup_controller_page_background_turn_off": "å…ŗé—­åŽå°æœåŠĄ", + "backup_controller_page_background_turn_on": "åŧ€å¯åŽå°æœåŠĄ", + "backup_controller_page_background_wifi": "äģ…在Wi-Fi下", "backup_controller_page_backup": "备äģŊ", "backup_controller_page_backup_selected": "厞选īŧš ", "backup_controller_page_backup_sub": "厞备äģŊįš„į…§į‰‡å’Œč§†éĸ‘", + "backup_controller_page_created": "创åģēæ—ļ间īŧš{date}", + "backup_controller_page_desc_backup": "åŧ€å¯å‰å°å¤‡äģŊīŧŒæ‰“åŧ€ App åŗč‡Ē动上äŧ æ–°æ–‡äģļ。", "backup_controller_page_excluded": "åˇ˛æŽ’é™¤īŧš ", + "backup_controller_page_failed": "å¤ąč´Ĩīŧˆ{count}īŧ‰", + "backup_controller_page_filename": "文äģļ名īŧš{filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "备äģŊäŋĄæ¯", "backup_controller_page_none_selected": "暂æœĒ选拊", "backup_controller_page_remainder": "削äŊ™", "backup_controller_page_remainder_sub": "åˇ˛é€‰éĄšä¸­å°šæœĒ备äģŊįš„į…§į‰‡å’Œč§†éĸ‘", "backup_controller_page_server_storage": "æœåŠĄå™¨å­˜å‚¨", + "backup_controller_page_start_backup": "åŧ€å§‹å¤‡äģŊ", + "backup_controller_page_status_off": "æœĒåŧ€å¯å‰å°č‡Ē动备äģŊ", + "backup_controller_page_status_on": "前台č‡Ē动备äģŊåˇ˛æ‰“åŧ€", "backup_controller_page_storage_format": "厞ᔍ {used}īŧˆå…ą {total}īŧ‰", - "backup_controller_page_to_backup": "垅备äģŊįš„į›¸å†Œ", - "backup_controller_page_total_sub": "包åĢæ‰€é€‰į›¸å†Œå†…å…¨éƒ¨å”¯ä¸€įš„į…§į‰‡å’Œč§†éĸ‘", + "backup_controller_page_to_backup": "垅备äģŊįš„į›¸į°ŋ", + "backup_controller_page_total_sub": "包åĢæ‰€é€‰į›¸į°ŋå†…å…¨éƒ¨å”¯ä¸€įš„į…§į‰‡å’Œč§†éĸ‘", + "backup_controller_page_turn_off": "å…ŗé—­å‰å°å¤‡äģŊ", + "backup_controller_page_turn_on": "åŧ€å¯å‰å°å¤‡äģŊ", + "backup_controller_page_uploading_file_info": "æ­Ŗåœ¨ä¸Šäŧ æ–‡äģļäŋĄæ¯", + "backup_err_only_album": "æ— æŗ•åˆ é™¤å”¯ä¸€įš„į›¸į°ŋ", "backup_error_sync_failed": "同æ­Ĩå¤ąč´Ĩã€‚æ— æŗ•å¤„į†å¤‡äģŊ。", "backup_info_card_assets": "į…§į‰‡å’Œč§†éĸ‘", + "backup_manual_cancelled": "åˇ˛å–æļˆ", + "backup_manual_in_progress": "上äŧ æ­Ŗåœ¨čŋ›čĄŒä¸­īŧŒč¯ˇį¨åŽå†č¯•", + "backup_manual_success": "成功", + "backup_manual_title": "上äŧ įŠļ态", "backup_options": "备äģŊ选项", + "backup_options_page_title": "备äģŊ选项", + "backup_setting_subtitle": "įŽĄį†åŽå°ä¸Žå‰å°ä¸Šäŧ čŽžįŊŽ", "backup_settings_subtitle": "įŽĄį†ä¸Šäŧ čŽžįŊŽ", + "backup_upload_details_page_more_details": "į‚šå‡ģæŸĨįœ‹č¯Ļ情", "backward": "后退", - "battery_optimization_backup_reliability": "å…ŗé—­äŧ˜åŒ–į”ĩæą å……į”ĩ可äģĨ提éĢ˜åŽå°å¤‡äģŊįš„å¯é æ€§", "biometric_auth_enabled": "į”Ÿį‰Šč¯†åˆĢčŽ¤č¯åˇ˛å¯į”¨", "biometric_locked_out": "æ‚¨åˇ˛čĸĢ锁厚īŧŒæ— æŗ•äŊŋį”¨į”Ÿį‰Šč¯†åˆĢčŽ¤č¯", "biometric_no_options": "æ— å¯į”¨įš„į”Ÿį‰Šč¯†åˆĢ选项", "biometric_not_available": "æœŦčŽžå¤‡ä¸æ”¯æŒį”Ÿį‰Šč¯†åˆĢčŽ¤č¯", + "birthdate_saved": "å‡ēį”Ÿæ—Ĩ期äŋå­˜æˆåŠŸ", "birthdate_set_description": "å‡ēį”Ÿæ—ĨæœŸį”¨äēŽčŽĄįŽ—æ‹æ‘„æ­¤į…§į‰‡æ—ļæ­¤äēēįš„åš´éž„ã€‚", "blurred_background": "čƒŒæ™¯č™šåŒ–", - "browse_templates": "æĩč§ˆæ¨Ąæŋ", "bugs_and_feature_requests": "问éĸ˜ä¸ŽåŠŸčƒŊåģē莎", "build": "构åģēį‰ˆæœŦ", "build_image": "é•œåƒį‰ˆæœŦ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "æ‚¨įĄŽåŽščρäŋį•™{count, plural, one {#ä¸Ēé‡å¤éĄš} other {#ä¸Ēé‡å¤éĄš}}吗īŧŸčŋ™å°†æ ‡čŽ°æ‰€æœ‰é‡å¤įģ„ä¸ē厞觪冺īŧŒä¸”不äŧšåˆ é™¤äģģäŊ•æ–‡äģļ。", "bulk_trash_duplicates_confirmation": "æ‚¨įĄŽåŽščĻæ‰šé‡å°†{count, plural, one {#ä¸Ēé‡å¤éĄš} other {#ä¸Ēé‡å¤éĄš}}į§ģč‡ŗå›žæ”ļį̙吗īŧŸčŋ™å°†äŋį•™æ¯įģ„中äŊ“į§¯æœ€å¤§įš„æ–‡äģļīŧŒåšļ将å…ļäŊ™æ‰€æœ‰é‡å¤éĄšį§ģč‡ŗå›žæ”ļįĢ™ã€‚", "buy": "č´­äš° Immich", + "cache_settings_clear_cache_button": "清除įŧ“å­˜", + "cache_settings_clear_cache_button_title": "æ¸…į†åē”ᔍįŧ“存。在įŧ“存重åģ翜Ÿé—´īŧŒåē”į”¨įš„čŋčĄŒé€ŸåēĻäŧšæ˜Žæ˜žå˜æ…ĸ。", + "cache_settings_duplicated_assets_clear_button": "清除", + "cache_settings_duplicated_assets_subtitle": "åŋŊį•Ĩåˆ—čĄ¨ä¸­įš„åĒ’äŊ“æ–‡äģļ", + "cache_settings_duplicated_assets_title": "重复文äģļīŧˆ{count}īŧ‰", + "cache_settings_statistics_album": "回åē“įŧŠį•Ĩ回", + "cache_settings_statistics_full": "原回", + "cache_settings_statistics_shared": "å…ąäēĢᛏį°ŋįŧŠį•Ĩ回", + "cache_settings_statistics_thumbnail": "įŧŠį•Ĩ回", + "cache_settings_statistics_title": "įŧ“å­˜å į”¨æƒ…å†ĩ", + "cache_settings_subtitle": "įŽĄį† Immich 手æœēįĢ¯įš„įŧ“å­˜", + "cache_settings_tile_subtitle": "įŽĄį†æœŦåœ°å­˜å‚¨į›¸å…ŗčĄŒä¸ē", + "cache_settings_tile_title": "æœŦ地存储", + "cache_settings_title": "įŧ“å­˜čŽžįŊŽ", "camera": "ᛏæœē", "camera_brand": "ᛏæœēå“į‰Œ", "camera_model": "ᛏæœēåž‹åˇ", @@ -678,10 +729,10 @@ "cannot_update_the_description": "æ— æŗ•æ›´æ–°æčŋ°", "cast": "æŠ•åą", "cast_description": "配įŊŽå¯į”¨įš„æŠ•åąčŽžå¤‡", - "change": "更攚", "change_date": "更攚æ—Ĩ期", "change_description": "äŋŽæ”šæčŋ°", "change_display_order": "更攚昞į¤ēéĄēåē", + "change_expiration_time": "更攚čŋ‡æœŸæ—ļ间", "change_location": "更攚äŊįŊŽ", "change_name": "æ›´æ”šåį§°", "change_name_successfully": "åį§°äŋŽæ”šæˆåŠŸ", @@ -695,13 +746,17 @@ "change_password_form_password_mismatch": "å¯†į ä¸åŒšé…", "change_password_form_reenter_new_password": "再æŦĄčž“å…Ĩæ–°å¯†į ", "change_pin_code": "äŋŽæ”šPIN᠁", + "change_trigger": "更攚č§Ļå‘æĄäģļ", + "change_trigger_prompt": "įĄŽåŽščĻæ›´æ”šč§Ļå‘æĄäģļ吗īŧŸæ­¤æ“äŊœå°†åˆ é™¤æ‰€æœ‰įŽ°æœ‰įš„æ“äŊœå’Œį­›é€‰å™¨ã€‚", "change_your_password": "äŋŽæ”šå¯†į ", "changed_visibility_successfully": "å¯č§įŠļ态更新成功", "charging": "充į”ĩ中", "charging_requirement_mobile_backup": "后台备äģŊ需čĻčŽžå¤‡å¤„äēŽå……į”ĩįŠļ态", + "check_corrupt_asset_backup": "æŖ€æŸĨ文äģļ备äģŊ是åĻ损坏", + "check_corrupt_asset_backup_button": "æ‰§čĄŒæŖ€æŸĨ", + "check_corrupt_asset_backup_description": "äģ…在Wi-FiįŽ¯åĸƒä¸‹čŋčĄŒæ­¤æŖ€æŸĨīŧŒåšļįĄŽäŋæ‰€æœ‰æ–‡äģļå‡åˇ˛å¤‡äģŊ。č¯Ĩčŋ‡į¨‹å¯čƒŊ需čĻå‡ åˆ†é’Ÿæ—ļ间。", "check_logs": "æŖ€æŸĨæ—Ĩåŋ—", "checksum": "æ ĄéĒŒå’Œ", - "choose": "选拊", "choose_matching_people_to_merge": "选拊čρ合åšļįš„äēēį‰Š", "city": "城市", "cleanup_confirm_description": "Immichåˇ˛æ‰žåˆ°{count}ä¸Ē厉全备äģŊč‡ŗæœåŠĄå™¨įš„æ–‡äģļīŧˆåˆ›åģēäēŽ{date}䚋前īŧ‰ã€‚是åĻäģŽæ­¤čŽžå¤‡į§ģ除æœŦ地副æœŦīŧŸ", @@ -710,7 +765,7 @@ "cleanup_deleting": "æ­Ŗåœ¨į§ģč‡ŗå›žæ”ļįĢ™...", "cleanup_found_assets": "åˇ˛æ‰žåˆ°{count}ä¸Ē厞备äģŊįš„æ–‡äģļ", "cleanup_found_assets_with_size": "åˇ˛æ‰žåˆ°{count}ä¸Ē厞备äģŊįš„æ–‡äģļīŧˆ{size}īŧ‰", - "cleanup_icloud_shared_albums_excluded": "iCloud å…ąäēĢį›¸å†Œåˇ˛æŽ’é™¤åœ¨æ‰ĢæčŒƒå›´äš‹å¤–", + "cleanup_icloud_shared_albums_excluded": "iCloudå…ąäēĢᛏį°ŋåˇ˛æŽ’é™¤åœ¨æ‰ĢæčŒƒå›´äš‹å¤–", "cleanup_no_assets_found": "æœĒ扞到įŦĻ合上čŋ°æĄäģļįš„æ–‡äģļ。“释攞įŠē间”äģ…čƒŊį§ģ除厞备äģŊč‡ŗæœåŠĄå™¨įš„æ–‡äģļ", "cleanup_preview_title": "åž…į§ģé™¤įš„į…§į‰‡/视éĸ‘īŧˆ{count}īŧ‰", "cleanup_step3_description": "æ‰Ģ描įŦĻ合æ—Ĩ期及äŋį•™čŽžįŊŽįš„厞备äģŊᅧቇ/视éĸ‘。", @@ -719,10 +774,11 @@ "clear": "清įŠē", "clear_all": "全部清除", "clear_all_recent_searches": "清除全部最čŋ‘搜į´ĸ莰åŊ•", - "clear_failed_count": "æ¸…é™¤å¤ąč´Ĩ ({count})", "clear_file_cache": "清įŠē文äģļįŧ“å­˜", "clear_message": "清įŠē通įŸĨ", "clear_value": "清除", + "client_cert_dialog_msg_confirm": "įĄŽåŽš", + "client_cert_enter_password": "输å…Ĩ坆᠁", "client_cert_import": "å¯ŧå…Ĩ", "client_cert_import_success_msg": "蝁äšĻå¯ŧå…Ĩ成功", "client_cert_invalid_msg": "蝁äšĻ文äģļæ— æ•ˆæˆ–坆᠁错蝝", @@ -731,10 +787,12 @@ "client_cert_remove_msg": "åŽĸæˆˇį̝蝁äšĻ厞į§ģ除", "client_cert_subtitle": "äģ…æ”¯æŒ PKCS12 æ ŧåŧ (.p12, .pfx)。į™ģåŊ•åŽå°†æ— æŗ•å¯ŧå…Ĩ或į§ģ除蝁äšĻ", "client_cert_title": "SSLåŽĸæˆˇį̝蝁äšĻ [厞éĒŒæ€§åŠŸčƒŊ]", + "clockwise": "éĄēæ—ļ针", "close": "å…ŗé—­", "collapse": "æ”ļčĩˇ", "collapse_all": "全部æ”ļčĩˇ", "color": "éĸœč‰˛", + "color_theme": "色åŊŠä¸ģéĸ˜", "command": "å‘Ŋäģ¤", "command_palette_prompt": "åŋĢ速æŸĨ扞éĄĩéĸ、操äŊœæˆ–å‘Ŋäģ¤", "command_palette_to_close": "å…ŗé—­", @@ -745,9 +803,8 @@ "comment_options": "更多", "comments_and_likes": "蝄čŽē & į‚ščĩž", "comments_are_disabled": "蝄čŽē厞兺闭", - "common_create_new_album": "创åģēæ–°į›¸å†Œ", + "common_create_new_album": "创åģēæ–°į›¸į°ŋ", "completed": "åˇ˛åŽŒæˆ", - "configuration": "配įŊŽ", "confirm": "įĄŽčŽ¤", "confirm_admin_password": "įĄŽčŽ¤įŽĄį†å‘˜å¯†į ", "confirm_delete_face": "įĄŽåŽščρäģŽæ­¤æ–‡äģļ中删除 {name} įš„éĸ部äŋĄæ¯å—īŧŸ", @@ -762,16 +819,19 @@ "contain": "适åē”", "context": "äģĨ文搜回", "continue": "įģ§įģ­", - "control_bottom_app_bar_add_tags": "æˇģåŠ æ ‡į­ž", + "control_bottom_app_bar_create_new_album": "新åģēᛏį°ŋ", + "control_bottom_app_bar_delete_from_immich": "äģŽImmichæœåŠĄå™¨ä¸­åˆ é™¤", "control_bottom_app_bar_delete_from_local": "äģŽčŽžå¤‡ä¸­åˆ é™¤", "control_bottom_app_bar_edit_location": "įŧ–čž‘äŊįŊŽ", "control_bottom_app_bar_edit_time": "įŧ–čž‘æ—Ĩ期和æ—ļ间", + "control_bottom_app_bar_share_link": "å…ąäēĢ链æŽĨ", + "control_bottom_app_bar_share_to": "分äēĢ臺", "control_bottom_app_bar_trash_from_immich": "į§ģ到回æ”ļįĢ™", "copied_image_to_clipboard": "å›žį‰‡åˇ˛å¤åˆļ到å‰Ēč´´æŋ。", "copied_to_clipboard": "åˇ˛å¤åˆļ到å‰Ēč´´æŋīŧ", "copy_error": "复åˆļå‡ē错", + "copy_file_path": "复åˆļ文äģļčˇ¯åž„", "copy_image": "复åˆļå›žį‰‡", - "copy_json": "复åˆļJSON", "copy_link": "复åˆļ链æŽĨ", "copy_link_to_clipboard": "复åˆļ链æŽĨ到å‰Ēč´´æŋ", "copy_password": "复åˆļ坆᠁", @@ -780,7 +840,7 @@ "cover": "åĄĢ充", "covers": "封éĸ", "create": "创åģē", - "create_album": "创åģēį›¸å†Œ", + "create_album": "创åģēᛏį°ŋ", "create_album_page_untitled": "æœĒå‘Ŋ名", "create_api_key": "创åģē API 密é’Ĩ", "create_first_workflow": "创åģēéĻ–ä¸ĒåˇĨäŊœæĩ", @@ -789,6 +849,7 @@ "create_link_to_share": "创åģēå…ąäēĢ链æŽĨ", "create_link_to_share_description": "å…čŽ¸äģģäŊ•æ‹Ĩ有铞æŽĨįš„äē翟Ĩįœ‹æ‰€é€‰į…§į‰‡", "create_new": "新åģē", + "create_new_face": "创åģēæ–°äēē脸", "create_new_person": "创åģēæ–°äēēį‰Š", "create_new_person_hint": "å°†æ‰€é€‰į…§į‰‡/视éĸ‘分配į왿–°äēēį‰Š", "create_new_user": "新åģēį”¨æˆˇ", @@ -803,43 +864,49 @@ "create_workflow": "新åģēåˇĨäŊœæĩ", "created": "åˇ˛åˆ›åģē", "created_at": "创åģēæ—ļ间", - "creating_linked_albums": "æ­Ŗåœ¨åˆ›åģēį›¸å†Œé“žæŽĨâ€Ļ", + "creating_linked_albums": "æ­Ŗåœ¨åˆ›åģēᛏį°ŋ链æŽĨâ€Ļ", "crop": "誁å‰Ē", + "crop_aspect_ratio_fixed": "å›ē厚比䞋", "crop_aspect_ratio_free": "č‡Ēį”ąæ¯”äž‹", "crop_aspect_ratio_original": "原始比䞋", "crop_aspect_ratio_square": "æ–šåŊĸ", + "curated_object_page_title": "į˛žé€‰é›†", "current_device": "åŊ“å‰čŽžå¤‡", "current_pin_code": "åŊ“前 PIN ᠁", "current_server_address": "åŊ“å‰æœåŠĄå™¨åœ°å€", "custom_date": "č‡Ē厚䚉æ—Ĩ期", "custom_locale": "č‡Ē厚䚉åŒē域", "custom_locale_description": "æ šæŽæ‰€é€‰įš„č¯­č¨€å’Œåœ°åŒēæĨæ ŧåŧåŒ–æ—Ĩ期、æ—ļ间和数字", + "custom_url": "č‡Ē厚䚉 URL", "cutoff_date_description": "äŋį•™æœ€čŋ‘įš„į…§į‰‡â€Ļ", "cutoff_day": "{count, plural, one {夊} other {夊}}", "cutoff_year": "{count, plural, one {åš´} other {åš´}}", + "daily_title_text_date": "MMM dd (E)", + "daily_title_text_date_year": "YYYYåš´M月dæ—Ĩ (E)", "dark": "æˇąč‰˛", "dark_theme": "切æĸåˆ°æˇąč‰˛ä¸ģéĸ˜", "date": "æ—Ĩ期", "date_after": "åŧ€å§‹æ—Ĩ期", "date_and_time": "æ—Ĩ期与æ—ļ间", "date_before": "į쓿Ÿæ—Ĩ期", - "date_of_birth": "å‡ēį”Ÿæ—Ĩ期", + "date_format": "yåš´M月dæ—Ĩ (E) h:mm a", "date_of_birth_saved": "å‡ēį”Ÿæ—Ĩ期äŋå­˜æˆåŠŸ", "date_range": "æ—ĨæœŸčŒƒå›´", - "date_time_original": "原始æ—Ĩ期/æ—ļ间", "day": "æ—Ĩ", "days": "夊", "deduplicate_all": "åˆ é™¤æ‰€æœ‰é‡å¤éĄš", - "default_quality_subtitle": "į‚šå‡ģ分äēĢæ—ļäŊŋį”¨įš„į”ģč´¨ã€‚é•ŋ按分äēĢæŒ‰é’Žå¯æ¯æŦĄæ‰‹åŠ¨é€‰æ‹Šã€‚", - "default_share_quality": "éģ˜čŽ¤åˆ†äēĢį”ģč´¨", + "default_locale": "éģ˜čޤ蝭荀", + "default_locale_description": "æ šæŽæ‚¨įš„æĩč§ˆå™¨åŒē域æ ŧåŧåŒ–æ—Ĩ期和数字", "delete": "删除", "delete_action_confirmation_message": "æ‚¨įĄŽåŽščĻåˆ é™¤æ­¤į´ æå—īŧŸæ­¤æ“äŊœäŧšå°†č¯Ĩį´ æį§ģč‡ŗæœåŠĄå™¨įš„å›žæ”ļįĢ™īŧŒåšļ提į¤ē您是åĻ将å…ļ在æœŦåœ°čŽžå¤‡ä¸Šåˆ é™¤", "delete_action_prompt": "åˇ˛åˆ é™¤ {count} 饚", - "delete_album": "åˆ é™¤į›¸å†Œ", + "delete_album": "åˆ é™¤į›¸į°ŋ", "delete_api_key_prompt": "æ‚¨įĄŽåŽščĻåˆ é™¤æ­¤ API 密é’Ĩ吗īŧŸ", "delete_dialog_alert": "čŋ™äē›éĄšį›Žå°†äģŽ Immich æœåŠĄå™¨äģĨ及äŊ įš„čŽžå¤‡ä¸ŠčĸĢæ°¸äš…删除", "delete_dialog_alert_local": "čŋ™äē›éĄšį›Žå°†äģŽäŊ įš„čŽžå¤‡ä¸ŠčĸĢæ°¸äš…į§ģ除īŧŒäŊ†äžį„ļäŧšäŋį•™åœ¨ Immich æœåŠĄå™¨ä¸Š", "delete_dialog_alert_local_non_backed_up": "éƒ¨åˆ†éĄšį›Žå°šæœĒ备äģŊ到 ImmichīŧŒäģŽčŽžå¤‡ä¸Šåˆ é™¤åŽå°†æ°¸äš…ä¸ĸå¤ą", + "delete_dialog_alert_remote": "čŋ™äē›éĄšį›Žå°†äģŽ Immich æœåŠĄå™¨ä¸ŠčĸĢæ°¸äš…删除", + "delete_dialog_ok_force": "įģ§įģ­åˆ é™¤", "delete_dialog_title": "永䚅删除", "delete_duplicates_confirmation": "æ‚¨įĄŽåŽščĻæ°¸äš…åˆ é™¤čŋ™äē›é‡å¤éĄšå—īŧŸ", "delete_face": "删除č¯Ĩäēē脸", @@ -858,16 +925,21 @@ "delete_tag_confirmation_prompt": "æ‚¨įĄŽåŽščĻåˆ é™¤â€œ{tagName}”čŋ™ä¸Ēæ ‡į­žå—īŧŸ", "delete_user": "åˆ é™¤į”¨æˆˇ", "deleted_shared_link": "å…ąäēĢ链æŽĨåˇ˛åˆ é™¤", + "deletes_missing_assets": "æ¸…į†ä¸ĸå¤ąįš„åĒ’äŊ“å瓿Ąį›Ž", "description": "描čŋ°", + "description_input_hint_text": "æˇģ加描čŋ°...", + "description_input_submit_error": "更新描čŋ°äŋĄæ¯æ—ļå‡ē错īŧŒč¯ˇæŖ€æŸĨæ—Ĩåŋ—äģĨčŽˇå–æ›´å¤šč¯Ļ情", "deselect_all": "取æļˆå…¨é€‰", "details": "č¯Ļ情", "direction": "斚向", "disable": "įρᔍ", "disabled": "įρᔍ", + "disallow_edits": "įρæ­ĸįŧ–čž‘", "discord": "Discord", "discover": "å‘įŽ°", "discovered_devices": "åˇ˛å‘įŽ°įš„čŽžå¤‡", "dismiss_all_errors": "åŋŊį•Ĩæ‰€æœ‰é”™č¯¯", + "dismiss_error": "åŋŊį•Ĩæ­¤é”™č¯¯", "display_options": "昞į¤ē选项", "display_order": "排åēæ–šåŧ", "display_original_photos": "昞į¤ē原回", @@ -876,9 +948,11 @@ "documentation": "æ–‡æĄŖ", "done": "厌成", "download": "下čŊŊ", + "download_action_prompt": "æ­Ŗåœ¨ä¸‹čŊŊ {count} éĄšå†…åŽš", "download_canceled": "下čŊŊåˇ˛å–æļˆ", "download_complete": "下čŊŊ厌成", "download_enqueue": "厞加å…Ĩ下čŊŊ队列", + "download_error": "下čŊŊå‡ē错", "download_failed": "下čŊŊå¤ąč´Ĩ", "download_finished": "下čŊŊ厌成", "download_include_embedded_motion_videos": "内åĩŒč§†éĸ‘", @@ -888,30 +962,32 @@ "download_paused": "下čŊŊåˇ˛æš‚åœ", "download_settings": "下čŊŊ", "download_settings_description": "įŽĄį†ä¸Žæ–‡äģļ下čŊŊį›¸å…ŗįš„čŽžįŊŽ", + "download_started": "åŧ€å§‹ä¸‹čŊŊ", + "download_sucess": "下čŊŊ成功", + "download_sucess_android": "厞äŋå­˜åˆ° DCIM/Immich 文äģļ多", "download_waiting_to_retry": "į­‰åž…é‡č¯•", "downloading": "下čŊŊ中", "downloading_asset_filename": "æ­Ŗåœ¨ä¸‹čŊŊ文äģļīŧš{filename}", "downloading_from_icloud": "äģŽiCloud下čŊŊ", "downloading_media": "æ­Ŗåœ¨ä¸‹čŊŊåĒ’äŊ“æ–‡äģļ", - "drag_to_reorder": "拖动äģĨ重新排åē", "drop_files_to_upload": "随意拖攞文äģļäģĨ上äŧ ", - "duplicate": "复åˆļ", - "duplicate_workflow": "复åˆļåˇĨäŊœæĩ", "duplicates": "é‡å¤éĄš", "duplicates_description": "č¯ˇé€ä¸€æ ‡čŽ°æ¯įģ„ä¸­įš„é‡å¤æ–‡äģļ。", "duration": "æ—ļé•ŋ", "edit": "įŧ–čž‘", - "edit_album": "įŧ–čž‘į›¸å†Œ", + "edit_album": "įŧ–čž‘į›¸į°ŋ", "edit_avatar": "įŧ–čž‘å¤´åƒ", "edit_birthday": "įŧ–čž‘į”Ÿæ—Ĩ", "edit_date": "įŧ–čž‘æ—Ĩ期", "edit_date_and_time": "įŧ–čž‘æ—Ĩ期和æ—ļ间", "edit_date_and_time_action_prompt": "厞įŧ–čž‘ {count} 饚æ—Ĩ期æ—ļ间", "edit_date_and_time_by_offset": "按偏į§ģé‡č°ƒæ•´æ—Ĩ期", + "edit_date_and_time_by_offset_interval": "新æ—ĨæœŸčŒƒå›´īŧš{from} - {to}", "edit_description": "äŋŽæ”šæčŋ°", + "edit_description_prompt": "č¯ˇé€‰æ‹Šæ–°įš„æčŋ°īŧš", "edit_exclusion_pattern": "įŧ–čž‘æŽ’é™¤č§„åˆ™", "edit_faces": "įŧ–čž‘äēē脸", - "edit_key": "įŧ–čž‘API密é’Ĩ", + "edit_key": "įŧ–čž‘ API 密é’Ĩ", "edit_link": "įŧ–螑铞æŽĨ", "edit_location": "įŧ–čž‘äŊįŊŽ", "edit_location_action_prompt": "厞įŧ–čž‘ {count} ä¸ĒäŊįŊŽ", @@ -923,6 +999,9 @@ "edit_user": "įŧ–čž‘į”¨æˆˇ", "edit_workflow": "įŧ–čž‘åˇĨäŊœæĩ", "editor": "įŧ–čž‘å™¨", + "editor_close_without_save_prompt": "更攚将不äŧščĸĢäŋå­˜", + "editor_close_without_save_title": "å…ŗé—­įŧ–čž‘å™¨īŧŸ", + "editor_confirm_reset_all_changes": "æ‚¨įĄŽåŽščĻé‡įŊŽæ‰€æœ‰æ›´æ”šå—īŧŸ", "editor_discard_edits_confirm": "攞åŧƒįŧ–čž‘", "editor_discard_edits_prompt": "您有æœĒäŋå­˜įš„äŋŽæ”šīŧŒįĄŽåޚčρä¸ĸåŧƒå—īŧŸ", "editor_discard_edits_title": "įĄŽåŽšæ”žåŧƒįŧ–čž‘å—īŧŸ", @@ -951,8 +1030,10 @@ "enter_your_pin_code": "输å…Ĩæ‚¨įš„ PIN ᠁", "enter_your_pin_code_subtitle": "输å…ĨäŊ įš„ PIN ᠁äģĨčŽŋé—Žé”åŽšįš„æ–‡äģļ多", "error": "错蝝", + "error_change_sort_album": "æ›´æ”šį›¸į°ŋ排åēéĄēåēå¤ąč´Ĩ", "error_delete_face": "删除č¯Ĩᅧቇ/视éĸ‘ä¸­įš„äēē脸æ—ļå‡ē错", - "error_loading_albums": "加čŊŊį›¸å†Œæ—ļå‡ē错", + "error_getting_places": "čŽˇå–åœ°į‚šäŋĄæ¯æ—ļå‡ē错", + "error_loading_albums": "加čŊŊᛏį°ŋæ—ļå‡ē错", "error_loading_image": "加čŊŊå›žį‰‡æ—ļå‡ē错", "error_loading_partners": "加čŊŊ协äŊœč€…æ—ļå‡ē错īŧš{error}", "error_retrieving_asset_information": "čŽˇå–éĄšį›ŽäŋĄæ¯æ—ļå‡ē错", @@ -971,15 +1052,15 @@ "cant_get_number_of_comments": "æ— æŗ•čŽˇå–č¯„čŽē数量", "cant_search_people": "æ— æŗ•æœį´ĸäēēį‰Š", "cant_search_places": "æ— æŗ•æœį´ĸåœ°į‚š", - "error_adding_assets_to_album": "æˇģ加čĩ„æēåˆ°į›¸å†Œæ—ļå‡ē错", - "error_adding_users_to_album": "æˇģåŠ į”¨æˆˇåˆ°į›¸å†Œæ—ļå‡ē错", + "error_adding_assets_to_album": "æˇģåŠ į…§į‰‡/视éĸ‘åˆ°į›¸į°ŋæ—ļå‡ē错", + "error_adding_users_to_album": "æˇģåŠ į”¨æˆˇåˆ°į›¸į°ŋæ—ļå‡ē错", "error_deleting_shared_user": "åˆ é™¤å…ąäēĢį”¨æˆˇæ—ļå‡ē错", "error_downloading": "下čŊŊ{filename}æ—ļå‡ē错", "error_hiding_buy_button": "éšč—č´­äš°æŒ‰é’Žæ—ļå‡ē错", - "error_removing_assets_from_album": "į§ģé™¤į›¸å†Œčĩ„æēæ—ļå‡ē错īŧŒč¯ˇæŖ€æŸĨ控åˆļ台äģĨčŽˇå–æ›´å¤šč¯Ļ情", + "error_removing_assets_from_album": "äģŽį›¸į°ŋ中į§ģ除ᅧቇ/视éĸ‘æ—ļå‡ē错īŧŒč¯ˇæŖ€æŸĨ控åˆļ台äģĨčŽˇå–æ›´å¤šč¯Ļ情", "error_selecting_all_assets": "免选ᅧቇ/视éĸ‘æ—ļå‡ē错", "exclusion_pattern_already_exists": "æ­¤æŽ’é™¤æ¨Ąåŧåˇ˛å­˜åœ¨ã€‚", - "failed_to_create_album": "创åģēį›¸å†Œå¤ąč´Ĩ", + "failed_to_create_album": "创åģēᛏį°ŋå¤ąč´Ĩ", "failed_to_create_shared_link": "创åģēå…ąäēĢ链æŽĨå¤ąč´Ĩ", "failed_to_edit_shared_link": "įŧ–čž‘å…ąäēĢ链æŽĨå¤ąč´Ĩ", "failed_to_get_people": "čŽˇå–äēēį‰Šåˆ—čĄ¨å¤ąč´Ĩ", @@ -991,7 +1072,6 @@ "failed_to_remove_product_key": "į§ģ除äē§å“å¯†é’Ĩå¤ąč´Ĩ", "failed_to_reset_pin_code": "重įŊŽ PIN į å¤ąč´Ĩ", "failed_to_stack_assets": "å †å į…§į‰‡/视éĸ‘å¤ąč´Ĩ", - "failed_to_tag_assets": "æ ‡čŽ°čĩ„æēå¤ąč´Ĩ", "failed_to_unstack_assets": "取æļˆå †å į…§į‰‡/视éĸ‘å¤ąč´Ĩ", "failed_to_update_notification_status": "更新通įŸĨįŠļæ€å¤ąč´Ĩ", "incorrect_email_or_password": "é‚ŽįŽąæˆ–å¯†į é”™č¯¯", @@ -1001,7 +1081,7 @@ "profile_picture_transparent_pixels": "å¤´åƒä¸æ”¯æŒé€æ˜ŽčƒŒæ™¯īŧŒč¯ˇæ”žå¤§æˆ–į§ģåŠ¨å›žį‰‡ã€‚", "quota_higher_than_disk_size": "äŊ čŽžįŊŽįš„配éĸčļ…čŋ‡äē†įŖį›˜æ€ģ大小", "something_went_wrong": "å‡ē错äē†", - "unable_to_add_album_users": "æ— æŗ•å‘į›¸å†ŒæˇģåŠ į”¨æˆˇ", + "unable_to_add_album_users": "æ— æŗ•å‘į›¸į°ŋæˇģåŠ į”¨æˆˇ", "unable_to_add_assets_to_shared_link": "æ— æŗ•å‘åˆ†äēĢ链æŽĨæˇģåŠ į…§į‰‡/视éĸ‘", "unable_to_add_comment": "æ— æŗ•æˇģåŠ č¯„čŽē", "unable_to_add_exclusion_pattern": "æ— æŗ•æˇģåŠ æŽ’é™¤č§„åˆ™", @@ -1009,7 +1089,7 @@ "unable_to_add_remove_archive": "æ— æŗ•{archived, select, true {äģŽåŊ’æĄŖä¸­į§ģ除} other {æˇģåŠ į…§į‰‡/视éĸ‘到åŊ’æĄŖ}}", "unable_to_add_remove_favorites": "æ— æŗ•{favorite, select, true {æˇģåŠ į…§į‰‡/视éĸ‘到æ”ļ藏} other {äģŽæ”ļ藏中į§ģ除}}", "unable_to_archive_unarchive": "æ— æŗ•{archived, select, true {åŊ’æĄŖ} other {取æļˆåŊ’æĄŖ}}", - "unable_to_change_album_user_role": "æ— æŗ•æ›´æ”šį›¸å†Œį”¨æˆˇįš„č§’č‰˛", + "unable_to_change_album_user_role": "æ— æŗ•æ›´æ”šį›¸į°ŋį”¨æˆˇįš„č§’č‰˛", "unable_to_change_date": "æ— æŗ•æ›´æ”šæ—Ĩ期", "unable_to_change_description": "æ— æŗ•äŋŽæ”šæčŋ°", "unable_to_change_favorite": "æ— æŗ•æ›´æ”šį…§į‰‡/视éĸ‘įš„æ”ļ藏įŠļ态", @@ -1018,13 +1098,13 @@ "unable_to_change_visibility": "æ— æŗ•äŋŽæ”š{count, plural, one {#ä¸Ēäēē} other {#ä¸Ēäēē}}įš„å¯č§æ€§čŽžįŊŽ", "unable_to_complete_oauth_login": "æ— æŗ•åŽŒæˆ OAuth į™ģåŊ•", "unable_to_connect": "æ— æŗ•čŋžæŽĨ", - "unable_to_copy_to_clipboard": "复åˆļå¤ąč´ĨīŧŒč¯ˇé€ščŋ‡HTTPSčŽŋ问éĄĩéĸåŽå†č¯•", + "unable_to_copy_to_clipboard": "æ— æŗ•å¤åˆļ到å‰Ēč´´æŋīŧŒč¯ˇįĄŽäŋäŊ æ˜¯é€ščŋ‡ HTTPS čŽŋ问č¯ĨéĄĩéĸįš„", "unable_to_create": "æ— æŗ•åˆ›åģēåˇĨäŊœæĩ", "unable_to_create_admin_account": "æ— æŗ•åˆ›åģēįŽĄį†å‘˜č´Ļæˆˇ", "unable_to_create_api_key": "æ— æŗ•åˆ›åģēæ–°įš„ API 密é’Ĩ", "unable_to_create_library": "æ— æŗ•åˆ›åģēåē“", "unable_to_create_user": "æ— æŗ•åˆ›åģēį”¨æˆˇ", - "unable_to_delete_album": "æ— æŗ•åˆ é™¤į›¸å†Œ", + "unable_to_delete_album": "æ— æŗ•åˆ é™¤į›¸į°ŋ", "unable_to_delete_asset": "æ— æŗ•åˆ é™¤į…§į‰‡/视éĸ‘", "unable_to_delete_assets": "åˆ é™¤į…§į‰‡/视éĸ‘æ—ļå‡ē错", "unable_to_delete_exclusion_pattern": "æ— æŗ•åˆ é™¤æŽ’é™¤č§„åˆ™", @@ -1048,7 +1128,7 @@ "unable_to_reassign_assets_existing_person": "æ— æŗ•å°†éĄšį›Žé‡æ–°åˆ†é…įģ™{name, select, null {åˇ˛å­˜åœ¨įš„äēēį‰Š} other {{name}}}", "unable_to_reassign_assets_new_person": "æ— æŗ•é‡æ–°åˆ†é…į…§į‰‡/视éĸ‘į왿–°įš„äēēį‰Š", "unable_to_refresh_user": "æ— æŗ•åˆˇæ–°į”¨æˆˇ", - "unable_to_remove_album_users": "æ— æŗ•äģŽį›¸å†Œä¸­į§ģé™¤į”¨æˆˇ", + "unable_to_remove_album_users": "æ— æŗ•äģŽį›¸į°ŋ中į§ģé™¤į”¨æˆˇ", "unable_to_remove_api_key": "æ— æŗ•į§ģ除 API 密é’Ĩ", "unable_to_remove_assets_from_shared_link": "æ— æŗ•äģŽå…ąäēĢ链æŽĨ中į§ģ除ᅧቇ/视éĸ‘", "unable_to_remove_library": "æ— æŗ•į§ģ除åē“", @@ -1060,7 +1140,7 @@ "unable_to_restore_assets": "æ— æŗ•æĸå¤į…§į‰‡/视éĸ‘", "unable_to_restore_trash": "æ— æŗ•čŋ˜åŽŸå›žæ”ļįĢ™", "unable_to_restore_user": "æ— æŗ•æĸå¤į”¨æˆˇ", - "unable_to_save_album": "æ— æŗ•äŋå­˜į›¸å†Œ", + "unable_to_save_album": "æ— æŗ•äŋå­˜į›¸į°ŋ", "unable_to_save_api_key": "æ— æŗ•äŋå­˜ API 密é’Ĩ", "unable_to_save_date_of_birth": "æ— æŗ•äŋå­˜å‡ēį”Ÿæ—Ĩ期", "unable_to_save_name": "æ— æŗ•æ›´æ–°äēēį‰Šåį§°", @@ -1075,8 +1155,8 @@ "unable_to_trash_asset": "æ— æŗ•į§ģč‡ŗå›žæ”ļįĢ™", "unable_to_unlink_account": "æ— æŗ•č§Ŗé™¤č´Ļåˇå…ŗč”", "unable_to_unlink_motion_video": "æ— æŗ•č§Ŗé™¤åŽžå†ĩ视éĸ‘兺联", - "unable_to_update_album_cover": "æ— æŗ•æ›´æ–°į›¸å†Œå°éĸ", - "unable_to_update_album_info": "æ— æŗ•æ›´æ–°į›¸å†ŒäŋĄæ¯", + "unable_to_update_album_cover": "æ— æŗ•æ›´æ”šį›¸į°ŋ封éĸ", + "unable_to_update_album_info": "æ— æŗ•æ›´æ”šį›¸į°ŋäŋĄæ¯", "unable_to_update_library": "æ— æŗ•æ›´æ–°å›žåē“", "unable_to_update_location": "æ— æŗ•æ›´æ–°äŊįŊŽäŋĄæ¯", "unable_to_update_settings": "æ— æŗ•æ›´æ–°čŽžįŊŽ", @@ -1090,10 +1170,18 @@ "exif": "Exif äŋĄæ¯", "exif_bottom_sheet_description": "æˇģ加描čŋ°...", "exif_bottom_sheet_description_error": "更新描čŋ°æ—ļå‡ē错", + "exif_bottom_sheet_details": "č¯Ļ情", + "exif_bottom_sheet_location": "äŊįŊŽ", "exif_bottom_sheet_no_description": "无描čŋ°", + "exif_bottom_sheet_people": "äēēį‰Š", + "exif_bottom_sheet_person_add_person": "æˇģ加姓名", "exit_slideshow": "退å‡ēåšģį¯į‰‡æ”žæ˜ ", "expand": "åą•åŧ€", "expand_all": "å…¨éƒ¨åą•åŧ€", + "experimental_settings_new_asset_list_subtitle": "æ­Ŗåœ¨å¤„į†", + "experimental_settings_new_asset_list_title": "å¯į”¨åŽžéĒŒæ€§į…§į‰‡įŊ‘æ ŧ", + "experimental_settings_subtitle": "äŊŋį”¨éŖŽé™Šč‡Ē负īŧ", + "experimental_settings_title": "厞éĒŒæ€§åŠŸčƒŊ", "expire_after": "čŋ‡æœŸæ—ļ间", "expired": "厞čŋ‡æœŸ", "expires_date": "将äēŽ {date} čŋ‡æœŸ", @@ -1103,40 +1191,41 @@ "export_as_json": "å¯ŧå‡ēä¸ē JSON", "export_database": "å¯ŧå‡ē数捎åē“", "export_database_description": "å¯ŧå‡ē SQLite 数捎åē“", - "exposure_time": "曝光æ—ļ间", "extension": "æ‰Šåą•å", "external": "外部", "external_libraries": "外部åē“", "external_network": "外部įŊ‘įģœ", "external_network_sheet_info": "åŊ“不在éĻ–é€‰įš„ Wi-Fi įŊ‘įģœä¸­æ—ļīŧŒåē”ᔍ将äģŽä¸Šåˆ°ä¸‹äžæŦĄå°č¯•īŧŒé€ščŋ‡ä¸‹æ–šįŦŦ一ä¸ĒčƒŊ够成功čŽŋé—Žįš„ URL čŋžæŽĨåˆ°æœåŠĄå™¨", - "f_number": "光圈å€ŧ", "face_unassigned": "æœĒ分配", "failed": "å¤ąč´Ĩ", "failed_count": "å¤ąč´Ĩ: {count}æŦĄ", "failed_to_authenticate": "čēĢäģŊéĒŒč¯å¤ąč´Ĩ", - "failed_to_delete_file": "文äģļåˆ é™¤å¤ąč´Ĩ", "failed_to_load_assets": "ᅧቇ/视éĸ‘加čŊŊå¤ąč´Ĩ", "failed_to_load_folder": "文äģļ多加čŊŊå¤ąč´Ĩ", "favorite": "æ”ļ藏", "favorite_action_prompt": "厞æˇģ加 {count} ä¸Ē到æ”ļč—å¤š", "favorite_or_unfavorite_photo": "æ”ļč—æˆ–å–æļˆæ”ļč—į…§į‰‡", "favorites": "æ”ļč—å¤š", + "favorites_page_no_favorites": "æœĒ扞到æ”ļč—įš„į…§į‰‡/视éĸ‘", "feature_photo_updated": "更æĸäēēį‰Šå°éĸį…§į‰‡æˆåŠŸ", "features": "功čƒŊ", + "features_in_development": "åŧ€å‘ä¸­įš„åŠŸčƒŊ", "features_setting_description": "įŽĄį†åē”į”¨åŠŸčƒŊ", "file_name_or_extension": "文äģļåæˆ–æ‰Šåą•å", "file_name_text": "文äģļ名", + "file_name_with_value": "文äģļ名īŧš{file_name}", "file_size": "大小", "filename": "文äģļ名", "filetype": "文äģļįąģ型", "filter": "᭛选", + "filter_description": "į­›é€‰į›Žæ ‡æ–‡äģļįš„æĄäģļ", "filter_people": "᭛选äēēį‰Š", "filter_places": "į­›é€‰åœ°į‚š", "filter_tags": "į­›é€‰æ ‡į­ž", "filters": "᭛选噍", + "find_them_fast": "æŒ‰åį§°åŋĢ速搜į´ĸ", "first": "įŦŦ一ä¸Ē", "fix_incorrect_match": "äŋŽå¤é”™č¯¯įš„匚配", - "focal_length": "į„Ļ距", "folder": "文äģļ多", "folder_not_found": "æœĒ扞到文äģļ多", "folders": "文äģļ多", @@ -1147,7 +1236,6 @@ "free_up_space_description": "将厞备äģŊįš„į…§į‰‡å’Œč§†éĸ‘į§ģå…ĨčŽžå¤‡įš„å›žæ”ļįĢ™īŧŒäģĨ释攞įŠēé—´ã€‚æ‚¨åœ¨æœåŠĄå™¨ä¸Šįš„å‰¯æœŦäģį„ļ厉全。", "free_up_space_settings_subtitle": "é‡Šæ”žčŽžå¤‡å­˜å‚¨įŠē间", "full_path": "åŽŒæ•´čˇ¯åž„īŧš{path}", - "full_path_or_folder": "åŽŒæ•´čˇ¯åž„æˆ–æ–‡äģļ多", "gcast_enabled": "æŠ•åą", "gcast_enabled_description": "此功čƒŊ需čρäģŽ Google 加čŊŊ外部čĩ„æēæ‰čƒŊčŋčĄŒã€‚", "general": "叏规", @@ -1162,12 +1250,13 @@ "gps": "有GPSäŋĄæ¯", "gps_missing": "无GPSäŋĄæ¯", "grant_permission": "授权权限", - "group_albums_by": "į›¸å†Œåˆ†įģ„䞝捎...", + "group_albums_by": "ᛏį°ŋ分įģ„䞝捎...", "group_country": "按å›ŊåŽļ分įģ„", "group_no": "不分įģ„", "group_owner": "æŒ‰æ‰€æœ‰č€…åˆ†įģ„", "group_places_by": "åœ°į‚šåˆ†įģ„䞝捎...", "group_year": "按嚴分įģ„", + "haptic_feedback_switch": "å¯į”¨æŒ¯åŠ¨åéψ", "haptic_feedback_title": "振动反éψ", "has_quota": "配éĸå¤§å°", "hash_asset": "čŽĄįŽ—æ–‡äģļ哈希å€ŧ", @@ -1181,19 +1270,36 @@ "height": "é̘åēĻ", "hi_user": "您åĨŊīŧŒ{name}īŧˆ{email}īŧ‰", "hide_all_people": "éšč—æ‰€æœ‰äēēį‰Š", - "hide_gallery": "éšč—į›¸å†Œ", + "hide_gallery": "éšč—į›¸į°ŋ", "hide_named_person": "隐藏äēēį‰Šīŧš{name}", "hide_password": "éšč—å¯†į ", "hide_person": "隐藏äēēį‰Š", "hide_schema": "éšč—æ¨Ąåŧ", "hide_text_recognition": "éšč—æ–‡æœŦ蝆åˆĢį쓿žœ", "hide_unnamed_people": "隐藏æœĒå‘Ŋåįš„äēēį‰Š", + "home_page_add_to_album_conflicts": "厞将 {added} ä¸Ē文äģᅫģåŠ åˆ°į›¸į°ŋ“{album}”。{failed} ä¸Ē文äģļåˇ˛å­˜åœ¨ã€‚", + "home_page_add_to_album_err_local": "暂æ—ļæ— æŗ•å°†æœŦ地文äģᅫģåŠ åˆ°į›¸į°ŋīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_add_to_album_success": "åˇ˛æˆåŠŸå°† {added} ä¸Ē文äģᅫģåŠ åˆ°į›¸į°ŋ“{album}”中。", + "home_page_album_err_partner": "暂æ—ļæ— æŗ•å°†â€œå¯šæ–šâ€įš„į…§į‰‡/视éĸ‘æˇģåŠ åˆ°į›¸į°ŋ中īŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_archive_err_local": "暂æ—ļæ— æŗ•åŊ’æĄŖæœŦ地文äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_archive_err_partner": "æ— æŗ•åŊ’æĄŖåäŊœč€…įš„æ–‡äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", "home_page_building_timeline": "æ­Ŗåœ¨æž„åģēæ—ļ间įēŋ", + "home_page_delete_err_partner": "æ— æŗ•åˆ é™¤åäŊœč€…įš„æ–‡äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_delete_remote_err_local": "æŖ€æĩ‹åˆ°åž…åˆ é™¤åˆ—čĄ¨ä¸­åŒ…åĢä熿œŦ地文äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_favorite_err_local": "暂不支持æ”ļ藏æœŦ地文äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_favorite_err_partner": "暂不支持æ”ļč—åäŊœč€…įš„æ–‡äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_first_time_notice": "åĻ‚æžœæ‚¨æ˜¯éĻ–æŦĄäŊŋᔍæœŦåē”ᔍīŧŒč¯ˇåŠĄåŋ…选拊一ä¸Ē备äģŊᛏį°ŋīŧŒäģĨäžŋæ—ļ间įēŋčƒŊäģŽä¸­čŽˇå–åšļåą•į¤ēį…§į‰‡å’Œč§†éĸ‘", + "home_page_locked_error_local": "æ— æŗ•å°†æœŦ地文äģļį§ģå…Ĩé”åŽšįš„æ–‡äģļ多īŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_locked_error_partner": "æ— æŗ•å°†åäŊœč€…įš„æ–‡äģļį§ģå…Ĩé”åŽšįš„æ–‡äģļ多īŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_share_err_local": "æ— æŗ•é€ščŋ‡é“žæŽĨ分äēĢæœŦ地文äģļīŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "home_page_upload_err_limit": "每æŦĄæœ€å¤šåĒčƒŊ上äŧ  30 ä¸Ē文äģļīŧŒčļ…å‡ēéƒ¨åˆ†åˇ˛čˇŗčŋ‡", "host": "æœåŠĄå™¨", "hour": "æ—ļ", "hours": "小æ—ļ", "id": "ID", "idle": "įŠē闲", + "ignore_icloud_photos": "åŋŊį•Ĩ iCloud ᅧቇ", + "ignore_icloud_photos_description": "邪äē›äģ…存储在 iCloud 中īŧˆæœĒ下čŊŊ到æœŦ地īŧ‰įš„ᅧቇīŧŒå°†ä¸äŧščĸĢ上äŧ åˆ° Immich æœåŠĄå™¨", "image": "å›žį‰‡", "image_alt_text_date": "在{date}æ‹æ‘„įš„{isVideo, select, true {视éĸ‘} other {ᅧቇ}}", "image_alt_text_date_1_person": "{date}æ‹æ‘„įš„åŒ…åĢ{person1}įš„{isVideo, select, true {视éĸ‘} other {ᅧቇ}}", @@ -1205,21 +1311,24 @@ "image_alt_text_date_place_2_people": "{date}在{country}{city}æ‹æ‘„įš„åŒ…åĢ{person1}和{person2}įš„{isVideo, select, true {视éĸ‘} other {ᅧቇ}}", "image_alt_text_date_place_3_people": "{date}在{country}{city}æ‹æ‘„įš„åŒ…åĢ{person1}、{person2}和{person3}įš„{isVideo, select, true {视éĸ‘} other {ᅧቇ}}", "image_alt_text_date_place_4_or_more_people": "{date}在{country}{city}æ‹æ‘„įš„åŒ…åĢ{person1}、{person2}及å…ļäģ–{additionalCount, number}ä¸Ēäēēį‰Šįš„{isVideo, select, true {视éĸ‘} other {ᅧቇ}}", + "image_saved_successfully": "å›žį‰‡åˇ˛äŋå­˜", + "image_viewer_page_state_provider_download_started": "åŧ€å§‹ä¸‹čŊŊ", + "image_viewer_page_state_provider_download_success": "下čŊŊ成功", + "image_viewer_page_state_provider_share_error": "分äēĢå‡ē错", "immich_logo": "Immich Logo", "immich_web_interface": "ImmichįŊ‘éĄĩį•Œéĸ", "import_from_json": "äģŽJSONå¯ŧå…Ĩ", "import_path": "å¯ŧå…Ĩčˇ¯åž„", - "in_albums": "在{count, plural, one {# ä¸Ēį›¸å†Œ} other {# ä¸Ēį›¸å†Œ}}中", + "in_albums": "在{count, plural, one {# ä¸Ēᛏį°ŋ} other {# ä¸Ēᛏį°ŋ}}中", "in_archive": "厞åŊ’æĄŖ", "in_year": "{year}åš´", "in_year_selector": "在", "include_archived": "包æ‹Ŧ厞åŊ’æĄŖ", - "include_shared_albums": "包æ‹Ŧå…ąäēĢį›¸å†Œ", + "include_shared_albums": "包æ‹Ŧå…ąäēĢᛏį°ŋ", "include_shared_partner_assets": "包æ‹Ŧ协äŊœč€…å…ąäēĢᅧቇ/视éĸ‘", "individual_share": "单į‹Ŧ分äēĢ", "individual_shares": "单į‹Ŧ分äēĢ", "info": "äŋĄæ¯", - "integrity_checks": "åŽŒæ•´æ€§æŖ€æŸĨ", "interval": { "day_at_onepm": "每夊下午 1 į‚š", "hours": "每隔 {hours, plural, one {小æ—ļ} other {{hours, number} 小æ—ļ}}", @@ -1229,13 +1338,20 @@ "invalid_date": "æ— æ•ˆįš„æ—Ĩ期", "invalid_date_format": "æ— æ•ˆįš„æ—Ĩ期æ ŧåŧ", "invite_people": "邀蝎äēē员", - "invite_to_album": "é‚€č¯ˇåŠ å…Ĩį›¸å†Œ", - "iso": "ISO", + "invite_to_album": "é‚€č¯ˇåŠ å…Ĩᛏį°ŋ", + "ios_debug_info_fetch_ran_at": "čŋčĄŒæ‹‰å– {dateTime}", + "ios_debug_info_last_sync_at": "上æŦĄåŒæ­ĨäēŽ {dateTime}", + "ios_debug_info_no_processes_queued": "æ— åž…å¤„į†įš„åŽå°čŋ›į¨‹", + "ios_debug_info_no_sync_yet": "尚æœĒæ‰§čĄŒåŽå°åŒæ­ĨäģģåŠĄ", + "ios_debug_info_processes_queued": "{count, plural, one {{count}ä¸Ē后台äģģåŠĄåœ¨æŽ’é˜Ÿ} other {{count}ä¸Ē后台äģģåŠĄåœ¨æŽ’é˜Ÿ}}", + "ios_debug_info_processing_ran_at": "处ᐆäēŽ {dateTime}", "items_count": "{count, plural, one {#ä¸Ē} other {#ä¸Ē}}éĄšį›Ž", "jobs": "äģģåŠĄ", + "json_editor": "JSONįŧ–čž‘å™¨", + "json_error": "JSON错蝝", "keep": "äŋį•™", - "keep_albums": "äŋį•™į›¸å†Œ", - "keep_albums_count": "äŋį•™ {count} {count, plural, one {ä¸Ēį›¸å†Œ} other {ä¸Ēį›¸å†Œ}}", + "keep_albums": "äŋį•™į›¸į°ŋ", + "keep_albums_count": "äŋį•™ {count} {count, plural, one {ä¸Ēᛏį°ŋ} other {ä¸Ēᛏį°ŋ}}", "keep_all": "全部äŋį•™", "keep_description": "选拊释攞įŠē间æ—ļäŋį•™åœ¨čŽžå¤‡ä¸Šįš„å†…åŽšã€‚", "keep_favorites": "äŋį•™æ”ļ藏", @@ -1257,31 +1373,30 @@ "latest_version": "æœ€æ–°į‰ˆæœŦ", "latitude": "įēŦåēĻ", "leave": "įĻģåŧ€", - "leave_album": "įĻģåŧ€į›¸å†Œ", + "leave_album": "įĻģåŧ€į›¸į°ŋ", "lens_model": "é•œå¤´åž‹åˇ", - "less": "更少", "let_others_respond": "å…čŽ¸äģ–äēē回复", "level": "į­‰įē§", "library": "čĩ„æēåē“", "library_add_folder": "æˇģ加文äģļ多", "library_edit_folder": "įŧ–čž‘æ–‡äģļ多", + "library_options": "čĩ„æēåē“选饚", + "library_page_device_albums": "čŽžå¤‡ä¸Šįš„į›¸į°ŋ", + "library_page_new_album": "新åģēᛏį°ŋ", "library_page_sort_asset_count": "éĄšį›Žæ•°é‡", "library_page_sort_created": "创åģēæ—Ĩ期", "library_page_sort_last_modified": "上æŦĄäŋŽæ”š", - "library_page_sort_title": "į›¸å†Œæ ‡éĸ˜", + "library_page_sort_title": "ᛏį°ŋ标éĸ˜", "licenses": "čŽ¸å¯č¯", "light": "æĩ…色", "light_theme": "切æĸ到æĩ…色ä¸ģéĸ˜", "like": "į‚ščĩž", "like_deleted": "取æļˆį‚ščĩž", - "link": "链æŽĨ", "link_motion_video": "链æŽĨåŠ¨æ€č§†éĸ‘", "link_to_docs": "更多äŋĄæ¯, č¯ˇå‚č§ æ–‡æĄŖ.", "link_to_oauth": "įģ‘åޚ OAuth", "linked_oauth_account": "厞įģ‘åŽšįš„ OAuth č´Ļæˆˇ", "list": "åˆ—čĄ¨", - "live": "在įēŋ", - "load_more": "加čŊŊ更多", "loading": "加čŊŊ中", "loading_search_results_failed": "加čŊŊ搜į´ĸį쓿žœå¤ąč´Ĩ", "local": "æœŦ地", @@ -1310,9 +1425,11 @@ "login": "į™ģåŊ•", "login_disabled": "į™ģåŊ•功čƒŊ厞įρᔍ", "login_form_api_exception": "APIåŧ‚å¸¸ã€‚č¯ˇæŖ€æŸĨæœåŠĄå™¨URLåšļé‡č¯•ã€‚", + "login_form_back_button_text": "čŋ”回", "login_form_email_hint": "youremail@email.com", "login_form_endpoint_hint": "http://æ‚¨įš„æœåŠĄå™¨åœ°å€:įĢ¯åŖ", "login_form_endpoint_url": "æœåŠĄå™¨įĢ¯į‚šURL", + "login_form_err_http": "č¯ˇæ˜ŽįĄŽæŒ‡åŽš http:// 或 https://", "login_form_err_invalid_email": "æ— æ•ˆįš„į”ĩå­é‚ŽįŽą", "login_form_err_invalid_url": "æ— æ•ˆįš„URL", "login_form_err_leading_whitespace": "ä¸å…čŽ¸å‰å¯ŧįŠēæ ŧ", @@ -1322,9 +1439,10 @@ "login_form_failed_login": "į™ģåŊ•å¤ąč´ĨīŧŒč¯ˇæŖ€æŸĨæœåŠĄå™¨åœ°å€ã€é‚ŽįŽąå’Œå¯†į ", "login_form_handshake_exception": "ä¸ŽæœåŠĄå™¨įš„æĄæ‰‹åŧ‚常。åĻ‚æžœæ‚¨äŊŋį”¨įš„æ˜¯č‡Ēį­žåč¯äšĻīŧŒč¯ˇåœ¨čŽžįŊŽä¸­åŧ€å¯å¯šč‡Ēį­žåč¯äšĻįš„æ”¯æŒã€‚", "login_form_password_hint": "坆᠁", - "login_form_server_empty": "č¯ˇčž“å…ĨæœåŠĄå™¨URL", - "login_form_server_error": "æ— æŗ•čŋžæŽĨåˆ°æœåŠĄå™¨", - "login_has_been_disabled": "į™ģåŊ•功čƒŊ厞įρᔍ", + "login_form_save_login": "äŋæŒį™ģåŊ•įŠļ态", + "login_form_server_empty": "č¯ˇčž“å…ĨæœåŠĄå™¨ URL。", + "login_form_server_error": "æ— æŗ•čŋžæŽĨåˆ°æœåŠĄå™¨ã€‚", + "login_has_been_disabled": "į™ģåŊ•功čƒŊ厞įĻį”¨ã€‚", "login_password_changed_error": "æ›´æ–°å¯†į æ—ļå‡ē错", "login_password_changed_success": "å¯†į æ›´æ–°æˆåŠŸ", "logout_all_device_confirmation": "įĄŽåŽščρ退å‡ēæ‰€æœ‰čŽžå¤‡įš„į™ģåŊ•吗īŧŸ", @@ -1339,6 +1457,7 @@ "maintenance_action_restore": "æ­Ŗåœ¨æĸ复数捎åē“", "maintenance_description": "Immich厞čŋ›å…Ĩįģ´æŠ¤æ¨Ąåŧã€‚", "maintenance_end": "退å‡ēįģ´æŠ¤æ¨Ąåŧ", + "maintenance_end_error": "退å‡ēįģ´æŠ¤æ¨Ąåŧå¤ąč´Ĩ。", "maintenance_logged_in_as": "åŊ“前į™ģåŊ•į”¨æˆˇīŧš{user}", "maintenance_restore_from_backup": "äģŽå¤‡äģŊæĸ复", "maintenance_restore_library": "æĸå¤æ‚¨įš„å›žåē“", @@ -1364,6 +1483,7 @@ "manage_media_access_settings": "打åŧ€čŽžįŊŽ", "manage_media_access_subtitle": "å…čŽ¸ Immich åē”į”¨įŽĄį†å’Œį§ģ动åĒ’äŊ“æ–‡äģļ。", "manage_media_access_title": "åĒ’äŊ“įŽĄį†čŽŋ闎权限", + "manage_shared_links": "įŽĄį†å…ąäēĢ链æŽĨ", "manage_sharing_with_partners": "įŽĄį†ä¸ŽåäŊœč€…įš„å…ąäēĢ莞įŊŽ", "manage_the_app_settings": "įŽĄį†åē”į”¨čŽžįŊŽ", "manage_your_account": "įŽĄį†æ‚¨įš„č´Ļæˆˇ", @@ -1371,11 +1491,13 @@ "manage_your_devices": "įŽĄį†æ‚¨åˇ˛į™ģåŊ•įš„čŽžå¤‡", "manage_your_oauth_connection": "įŽĄį†æ‚¨įš„ OAuth čŋžæŽĨ", "map": "地回", + "map_assets_in_bounds": "{count, plural, =0 {čŋ™ä¸ĒåŒēåŸŸæ— į…§į‰‡} one {# åŧ į…§į‰‡} other {# åŧ į…§į‰‡}}", "map_cannot_get_user_location": "æ— æŗ•čŽˇå–į”¨æˆˇäŊįŊŽ", + "map_location_dialog_yes": "是", "map_location_picker_page_use_location": "äŊŋį”¨æ­¤äŊįŊŽ", "map_location_service_disabled_content": "需čĻå¯į”¨åŽšäŊæœåŠĄæ‰čƒŊ昞į¤ē您åŊ“前äŊįŊŽįš„åĒ’äŊ“æ–‡äģļ。是åĻįŽ°åœ¨å¯į”¨åŽƒīŧŸ", "map_location_service_disabled_title": "厚äŊæœåŠĄåˇ˛įρᔍ", - "map_marker_for_image": "在 {city}, {country} æ‹æ‘„įš„åœ°å›žæ ‡čŽ°", + "map_marker_for_images": "æ ‡čŽ°{city}、{country}æ‹æ‘„į…§į‰‡įš„åœ°å›žå›žæ ‡", "map_marker_with_image": "å¸Ļéĸ„č§ˆå›žįš„åœ°å›žæ ‡čް", "map_no_location_permission_content": "需čρäŊįŊŽæƒé™æ‰čƒŊ昞į¤ē您åŊ“前äŊįŊŽįš„ᅧቇ/视éĸ‘ã€‚įŽ°åœ¨čĻå…čŽ¸å—īŧŸ", "map_no_location_permission_title": "äŊįŊŽæƒé™čĸĢæ‹’įģ", @@ -1385,46 +1507,17 @@ "map_settings_date_range_option_days": "{days}夊前", "map_settings_date_range_option_year": "1嚴前", "map_settings_date_range_option_years": "{years}嚴前", + "map_settings_dialog_title": "åœ°å›žčŽžįŊŽ", "map_settings_include_show_archived": "包åĢ厞åŊ’æĄŖįš„å†…åŽš", "map_settings_include_show_partners": "包åĢ协äŊœč€…", "map_settings_only_show_favorites": "äģ…æ˜žį¤ēæ”ļ藏项", "map_settings_theme_settings": "地回ä¸ģéĸ˜", + "map_zoom_to_see_photos": "蝎įŧŠå°åœ°å›žäģĨæŸĨįœ‹į…§į‰‡", "mark_all_as_read": "å…¨éƒ¨æ ‡čŽ°ä¸ē厞č¯ģ", + "mark_as_read": "æ ‡čŽ°ä¸ē厞č¯ģ", "marked_all_as_read": "åˇ˛å…¨éƒ¨æ ‡čŽ°ä¸ē厞č¯ģ", "matches": "åŒšé…éĄš", "matching_assets": "åŒšé…įš„éĄšį›Ž", - "media_chrome": { - "auto": "č‡Ē动", - "captions": "å­—åš•", - "captions_off": "å…ŗé—­", - "closed_captions": "隐藏åŧå­—åš•", - "decode_error": "觪᠁错蝝", - "disable_captions": "å…ŗé—­å­—åš•", - "enable_captions": "åŧ€å¯å­—åš•", - "enter_fullscreen_mode": "čŋ›å…Ĩå…¨åąæ¨Ąåŧ", - "exit_fullscreen_mode": "退å‡ēå…¨åąæ¨Ąåŧ", - "loop": "åžĒįŽ¯", - "media_error_description": "åĒ’äŊ“错蝝å¯ŧč‡´æ’­æ”žä¸­æ­ĸ。åĒ’äŊ“æ–‡äģļ可čƒŊåˇ˛æŸåīŧŒæˆ–č€…æ‚¨įš„æĩč§ˆå™¨ä¸æ”¯æŒæ­¤æ ŧåŧã€‚", - "media_loading": "åĒ’äŊ“加čŊŊ中", - "mute": "é™éŸŗ", - "network_error": "įŊ‘įģœé”™č¯¯", - "network_error_description": "įŊ‘įģœé”™č¯¯å¯ŧ致åĒ’äŊ“下čŊŊå¤ąč´Ĩ。", - "not_supported_error": "ä¸æ”¯æŒįš„æē", - "playback_rate": "播攞速åēĻ", - "playback_rate_current": "åŊ“前播攞速åēĻ", - "playback_rate_value": "播攞速åēĻ {playbackRate}", - "playback_time": "播攞æ—ļ间", - "quality": "į”ģč´¨", - "second": "į§’", - "seconds": "į§’", - "time_value_of_total_time": "{currentTime} / {totalTime}", - "time_value_remaining": "削äŊ™ {time}", - "unmute": "取æļˆé™éŸŗ", - "unsupported_error_description": "å‘į”Ÿäē†ä¸æ”¯æŒįš„é”™č¯¯ã€‚æœåŠĄå™¨æˆ–įŊ‘įģœå‡ēįŽ°æ•…éšœīŧŒæˆ–č€…æ‚¨įš„æĩč§ˆå™¨ä¸æ”¯æŒæ­¤æ ŧåŧã€‚", - "video_not_loaded_unknown_time": "视éĸ‘æœĒ加čŊŊīŧŒæ—ļ间æœĒįŸĨ。", - "video_player": "视éĸ‘播攞器", - "volume": "éŸŗé‡" - }, "media_type": "åĒ’äŊ“įąģ型", "memories": "é‚Ŗåš´ä슿—Ĩ", "memories_all_caught_up": "åˇ˛å¤„į†åŽŒæ¯•", @@ -1441,32 +1534,35 @@ "merge_people_prompt": "是åĻ合åšļčŋ™äē›äēē选īŧŸæ­¤æ“äŊœä¸å¯æ’¤é”€ã€‚", "merge_people_successfully": "äēēį‰Šåˆåšļ成功", "merged_people_count": "厞合åšļ{count, plural, one {# äēē} other {# äēē}}", - "minFaces": "最少äēēč„¸æ•°", - "minFaces_description": "一ä¸ĒäēēčĸĢæ˜žį¤ēå‡ēæĨæ‰€éœ€č¯†åˆĢåˆ°įš„æœ€å°‘äēēč„¸æ•°é‡", "minimize": "最小化", "minute": "分", "minutes": "分钟", + "mirror_horizontal": "æ°´åšŗįŋģčŊŦ", + "mirror_vertical": "åž‚į›´įŋģčŊŦ", "missing": "įŧēå¤ą", "mobile_app": "į§ģ动į̝APP", "mobile_app_download_onboarding_note": "äŊŋᔍäģĨ下选项下čŊŊ配åĨ—手æœēåē”ᔍ", "model": "åž‹åˇ", - "modify_date": "äŋŽæ”šæ—Ĩ期", "month": "月", + "monthly_title_text_date_format": "yyyyåš´M月", "more": "更多", - "motion": "动äŊœ", "move": "į§ģ动", + "move_down": "向下į§ģ动", "move_off_locked_folder": "į§ģå‡ē锁厚文äģļ多", "move_to": "į§ģ动到", "move_to_device_trash": "į§ģč‡ŗčŽžå¤‡å›žæ”ļįĢ™", "move_to_lock_folder_action_prompt": "厞将 {count} 饚æˇģ加到锁厚文äģļ多", "move_to_locked_folder": "į§ģč‡ŗé”åŽšæ–‡äģļ多", - "move_to_locked_folder_confirmation": "čŋ™äē›į…§į‰‡å’Œč§†éĸ‘å°†äģŽæ‰€æœ‰į›¸å†Œä¸­į§ģ除īŧŒäģ…可在锁厚文äģļ多内æŸĨįœ‹", + "move_to_locked_folder_confirmation": "čŋ™äē›į…§į‰‡å’Œč§†éĸ‘å°†äģŽæ‰€æœ‰į›¸į°ŋ中į§ģ除īŧŒäģ…可在锁厚文äģļ多内æŸĨįœ‹", + "move_up": "向上į§ģ动", + "moved_to_archive": "厞将{count, plural, one {#ä¸Ē} other {#ä¸Ē}}ᅧቇ/视éĸ‘į§ģ臺åŊ’æĄŖ", + "moved_to_library": "厞将{count, plural, one {#ä¸Ē} other {#ä¸Ē}}ᅧቇ/视éĸ‘į§ģ臺čĩ„æēåē“", "moved_to_trash": "厞į§ģč‡ŗå›žæ”ļįĢ™", + "multiselect_grid_edit_date_time_err_read_only": "æ— æŗ•įŧ–čž‘åĒč¯ģéĄšį›Žįš„æ—Ĩ期īŧŒæ­Ŗåœ¨čˇŗčŋ‡", + "multiselect_grid_edit_gps_err_read_only": "æ— æŗ•įŧ–čž‘åĒč¯ģéĄšį›Žįš„äŊįŊŽäŋĄæ¯īŧŒæ­Ŗåœ¨čˇŗčŋ‡", "mute_memories": "é™éŸŗå›žåŋ†", - "my_albums": "æˆ‘įš„į›¸å†Œ", - "my_immich_description": "将åŊ“前éĄĩéĸ复åˆļä¸ēæˆ‘įš„Immich链æŽĨ", - "my_immich_title": "æˆ‘įš„Immich链æŽĨ", - "name": "名字", + "my_albums": "æˆ‘įš„į›¸į°ŋ", + "name": "åį§°", "name_or_nickname": "åį§°æˆ–æ˜ĩį§°", "name_required": "åį§°æ˜¯åŋ…åĄĢ饚", "navigate": "å¯ŧčˆĒ", @@ -1478,13 +1574,14 @@ "networking_settings": "įŊ‘įģœčŽžįŊŽ", "networking_subtitle": "įŽĄį†æœåŠĄå™¨įĢ¯į‚ščŽžįŊŽ", "never": "永不čŋ‡æœŸ", - "new_album": "新åģēį›¸å†Œ", + "new_album": "新åģēᛏį°ŋ", "new_api_key": "新åĸž API 密é’Ĩ", - "new_feature": "新功čƒŊ", + "new_date_range": "æ–°įš„æ—ĨæœŸčŒƒå›´", "new_password": "æ–°å¯†į ", "new_person": "新äēēį‰Š", "new_pin_code": "æ–°įš„PIN᠁", "new_pin_code_subtitle": "čŋ™æ˜¯æ‚¨įŦŦ一æŦĄčŽŋ闎此锁厚文äģļ多。创åģē一ä¸ĒPIN᠁äģĨ厉全čŽŋé—Žæ­¤éĄĩéĸ", + "new_timeline": "切æĸåˆ°æ–°į‰ˆæ—ļ间įēŋ", "new_update": "æ–°į‰ˆæœŦ", "new_user_created": "åˇ˛åˆ›åģēæ–°į”¨æˆˇ", "new_version_available": "æœ‰æ–°į‰ˆæœŦ发布å•Ļ", @@ -1492,23 +1589,27 @@ "next": "下一ä¸Ē", "next_memory": "下一ä¸Ē回åŋ†", "no": "åĻ", - "no_albums_found": "æœĒæ‰žåˆ°į›¸å†Œ", - "no_albums_message": "创åģēį›¸å†ŒäģĨæ•´į†æ‚¨įš„į…§į‰‡å’Œč§†éĸ‘", - "no_albums_with_name_yet": "æ˛Ąæœ‰åŒåįš„į›¸å†Œã€‚", - "no_albums_yet": "您čŋ˜æ˛Ąæœ‰åˆ›åģēäģģäŊ•į›¸å†Œã€‚", + "no_actions_added": "尚æœĒæˇģ加动äŊœ", + "no_albums_found": "æœĒæ‰žåˆ°į›¸į°ŋ", + "no_albums_message": "创åģēᛏį°ŋäģĨæ•´į†æ‚¨įš„į…§į‰‡å’Œč§†éĸ‘", + "no_albums_with_name_yet": "æ˛Ąæœ‰åŒåįš„į›¸į°ŋ。", + "no_albums_yet": "您čŋ˜æ˛Ąæœ‰åˆ›åģēäģģäŊ•ᛏį°ŋ。", "no_archived_assets_message": "åŊ’æĄŖį…§į‰‡å’Œč§†éĸ‘īŧŒå°†å…ļäģŽâ€œį…§į‰‡â€č§†å›žä¸­éšč—", "no_assets_message": "į‚šå‡ģ上äŧ äŊ įš„įŦŦ一åŧ į…§į‰‡", "no_assets_to_show": "暂无内厚可昞į¤ē", "no_cast_devices_found": "æœĒæ‰žåˆ°å¯į”¨įš„æŠ•åąčŽžå¤‡", "no_checksum_local": "æ— å¯į”¨įš„æ ĄéĒŒå’Œ — æ— æŗ•čŽˇå–æœŦåœ°éĄšį›Ž", "no_checksum_remote": "æ— å¯į”¨įš„æ ĄéĒŒå’Œ — æ— æŗ•čŽˇå–čŋœį¨‹éĄšį›Ž", + "no_configuration_needed": "无需配įŊŽ", "no_devices": "æš‚æ— åˇ˛æŽˆæƒįš„čŽžå¤‡", "no_duplicates_found": "æœĒå‘įŽ°é‡å¤å†…åŽšã€‚", - "no_exif_info_available": "æ— å¯į”¨įš„ Exif äŋĄæ¯", + "no_exif_info_available": "æ— å¯į”¨įš„ EXIF äŋĄæ¯", "no_explore_results_message": "上äŧ æ›´å¤šį…§į‰‡īŧŒæĨæŽĸį´ĸäŊ įš„回åē“吧。", "no_favorites_message": "æˇģ加æ”ļ藏īŧŒäģĨäžŋåŋĢ速扞到äŊ æœ€į˛žåŊŠįš„į…§į‰‡å’Œč§†éĸ‘", + "no_filters_added": "尚æœĒæˇģåŠ į­›é€‰æĄäģļ", "no_libraries_message": "创åģē外部回åē“īŧŒäģĨæĩč§ˆäŊ įš„į…§į‰‡å’Œč§†éĸ‘", "no_local_assets_found": "æœĒæ‰žåˆ°å…ˇæœ‰æ­¤æ ĄéĒŒå’Œįš„æœŦåœ°éĄšį›Ž", + "no_location_set": "æœĒ莞įŊŽäŊįŊŽ", "no_locked_photos_message": "锁厚文äģļå¤šä¸­įš„į…§į‰‡å’Œč§†éĸ‘äŧščĸĢ隐藏īŧŒåœ¨æĩč§ˆæˆ–搜į´ĸ回å瓿—ļ不äŧšæ˜žį¤ē。", "no_name": "æœĒå‘Ŋ名äēēį‰Š", "no_notifications": "暂无通įŸĨ", @@ -1517,20 +1618,15 @@ "no_remote_assets_found": "æœĒæ‰žåˆ°å…ˇæœ‰æ­¤æ ĄéĒŒå’Œįš„čŋœį¨‹éĄšį›Ž", "no_results": "æœĒ扞到äģģäŊ•åŒšé…éĄš", "no_results_description": "č¯ˇå°č¯•äŊŋį”¨åŒäš‰č¯æˆ–æ›´åŽŊæŗ›įš„å…ŗé”Žč¯", - "no_shared_albums_message": "创åģēį›¸å†ŒīŧŒä¸Žį¤žäē¤åœˆå†…įš„åĨŊå‹å…ąäēĢį…§į‰‡å’Œč§†éĸ‘", - "no_steps": "尚æœĒæˇģ加äģģäŊ•æ­ĨéǤ", + "no_shared_albums_message": "创åģēᛏį°ŋīŧŒä¸Žį¤žäē¤åœˆå†…įš„åĨŊå‹å…ąäēĢį…§į‰‡å’Œč§†éĸ‘", + "no_uploads_in_progress": "暂无上äŧ äģģåŠĄ", "none": "无", "not_allowed": "ä¸å…čŽ¸", "not_available": "ä¸é€‚į”¨", - "not_in_any_album": "æœĒæ”ļåŊ•äēŽäģģäŊ•į›¸å†Œ", + "not_in_any_album": "æœĒæ”ļåŊ•äēŽäģģäŊ•ᛏį°ŋ", "not_selected": "æœĒ选拊", - "not_set": "æœĒ莞įŊŽ", "notes": "å¤‡æŗ¨", "nothing_here_yet": "暂无内厚", - "notification_backup_reliability": "å¯į”¨é€šįŸĨäģĨ提éĢ˜åŽå°å¤‡äģŊįš„å¯é æ€§", - "notification_enabled_list_tile_content": "Immich äŊŋį”¨é€šįŸĨ功čƒŊčŋ›čĄŒåŽå°å¤‡äģŊ。您可äģĨåœ¨čŽžå¤‡čŽžįŊŽä¸­įŽĄį†čŋ™äē›é€šįŸĨ。", - "notification_enabled_list_tile_open_button": "打åŧ€čŽžįŊŽ", - "notification_enabled_list_tile_title": "åˇ˛å¯į”¨é€šįŸĨ", "notification_permission_dialog_content": "čĻæƒŗåŧ€å¯é€šįŸĨ功čƒŊīŧŒéœ€čρåŽģ“设įŊŽâ€é‡Œæ‰‹åŠ¨å…čŽ¸ã€‚", "notification_permission_list_tile_content": "č¯ˇæŽˆäēˆīŧˆį›¸å…ŗīŧ‰æƒé™īŧŒäģĨåŧ€å¯é€šįŸĨ功čƒŊ。", "notification_permission_list_tile_enable_button": "å¯į”¨é€šįŸĨ", @@ -1539,11 +1635,9 @@ "notifications": "通įŸĨ", "notifications_setting_description": "įŽĄį†é€šįŸĨ", "oauth": "OAuth", - "obtainium_configurator": "Obtainium 配įŊŽå™¨", + "obtainium_configurator": "Obtainium配įŊŽå™¨", "obtainium_configurator_instructions": "äŊŋᔍ Obtainium į›´æŽĨäģŽ Immich įš„ GitHub 发布éĄĩåŽ‰čŖ…å’Œæ›´æ–° Android åē”į”¨ã€‚č¯ˇåˆ›åģē一ä¸Ē API 密é’Ĩåšļ选拊一ä¸Ēį‰ˆæœŦīŧŒäģĨį”ŸæˆäŊ įš„ Obtainium 配įŊŽé“žæŽĨ", "ocr": "OCR", - "ocr_body": "Immich įŽ°åœ¨å¯äģĨč¯ģå–į…§į‰‡ä¸­įš„æ–‡å­—īŧŒå› æ­¤äŊ å¯äģĨæ šæŽį…§į‰‡é‡Œįš„å†…åŽšæĨ搜į´ĸ厃äģŦ。", - "ocr_title": "搜į´ĸį…§į‰‡ä¸­įš„æ–‡å­—", "official_immich_resources": "Immich厘斚čĩ„æē", "offline": "įĻģįēŋ", "offset": "偏į§ģ量", @@ -1562,20 +1656,18 @@ "open": "打åŧ€", "open_calendar": "打åŧ€æ—Ĩ历", "open_in_browser": "在æĩč§ˆå™¨ä¸­æ‰“åŧ€", - "open_in_immich_body": "在 Android 上将 Immich 莞ä¸ē回åē“åē”ᔍīŧŒåŗå¯į›´æŽĨäģŽå…ļäģ–åē”į”¨æ‰“åŧ€į…§į‰‡ã€‚", - "open_in_immich_title": "在 Immich 中打åŧ€į…§į‰‡", "open_in_map_view": "åœ¨åœ°å›žč§†å›žä¸­æ‰“åŧ€", "open_in_openstreetmap": "在 OpenStreetMap 中打åŧ€", "open_the_search_filters": "打åŧ€æœį´ĸ᭛选", "options": "选项", "or": "或", - "organize_into_albums": "æ•´į†åˆ°į›¸å†Œä¸­", - "organize_into_albums_description": "äŊŋᔍåŊ“å‰įš„åŒæ­Ĩ莞įŊŽīŧŒå°†įŽ°æœ‰į…§į‰‡åŊ’å…Ĩį›¸å†Œ", + "organize_into_albums": "æ•´į†åˆ°į›¸į°ŋ中", + "organize_into_albums_description": "äŊŋᔍåŊ“å‰įš„åŒæ­Ĩ莞įŊŽīŧŒå°†įŽ°æœ‰į…§į‰‡åŊ’å…Ĩᛏį°ŋ", "organize_your_library": "æ•´į†æ‚¨įš„čĩ„æēåē“", - "orientation": "斚向", "original": "åŽŸå§‹įš„", "other": "å…ļäģ–", "other_devices": "å…ļäģ–čŽžå¤‡", + "other_entities": "å…ļäģ–厞äŊ“", "other_variables": "å…ļäģ–变量", "owned": "æˆ‘įš„", "owner": "æ‰€æœ‰č€…", @@ -1585,9 +1677,12 @@ "partner_can_access_assets": "æ‰€æœ‰į…§į‰‡å’Œč§†éĸ‘īŧŒé™¤äē†åˇ˛åŊ’æĄŖå’Œåˇ˛åˆ é™¤įš„äš‹å¤–", "partner_can_access_location": "æ‹æ‘„į…§į‰‡įš„åœ°į‚š", "partner_list_user_photos": "{user}įš„į…§į‰‡", + "partner_list_view_all": "æŸĨįœ‹å…¨éƒ¨", "partner_page_empty_message": "æ‚¨įš„į…§į‰‡å°šæœĒ与äģģäŊ•协äŊœč€…å…ąäēĢ。", "partner_page_no_more_users": "į”¨æˆˇæ•°é‡åˇ˛čžžä¸Šé™", + "partner_page_partner_add_failed": "æˇģ加åĨŊå‹å¤ąč´Ĩ", "partner_page_select_partner": "é€‰æ‹Šį›Žæ ‡", + "partner_page_shared_to_title": "分äēĢįģ™", "partner_page_stop_sharing_content": "{partner}å°†æ— æŗ•å†čŽŋé—Žæ‚¨įš„į…§į‰‡ã€‚", "partner_sharing": "åĨŊå‹å…ąäēĢ", "partners": "åĨŊ友", @@ -1609,26 +1704,39 @@ "people": "äēēį‰Š", "people_edits_count": "{count, plural, one {#ä¸Ēäēēį‰Š} other {#ä¸Ēäēēį‰Š}}厞įŧ–čž‘", "people_feature_description": "按äēēį‰Šåˆ†į섿ĩč§ˆį…§į‰‡å’Œč§†éĸ‘", + "people_selected": "{count, plural, one {厞选䏭#äēē} other {厞选䏭#äēē}}", + "people_sidebar_description": "åœ¨äž§čžšæ æ˜žį¤ē“äēēį‰Šâ€é“žæŽĨ", "permanent_deletion_warning": "永䚅删除č­Ļ告", "permanent_deletion_warning_setting_description": "æ°¸äš…åˆ é™¤į…§į‰‡/视éĸ‘æ—ļ昞į¤ēč­Ļ告", "permanently_delete": "永䚅删除", "permanently_delete_assets_count": "永䚅删除{count, plural, one {ä¸ĒéĄšį›Ž} other {ä¸ĒéĄšį›Ž}}", - "permanently_delete_assets_prompt": "įĄŽåŽščĻæ°¸äš…åˆ é™¤ {count, plural, one {æ­¤éĄšį›Žå—īŧŸ} other {čŋ™ # ä¸ĒéĄšį›Žå—īŧŸ}}čŋ™äšŸäŧšå°†{count, plural, one {å…ļ} other {厃äģŦ}}äģŽæ‰€åąžį›¸å†Œä¸­į§ģ除。", + "permanently_delete_assets_prompt": "įĄŽåŽščĻæ°¸äš…åˆ é™¤ {count, plural, one {æ­¤éĄšį›Žå—īŧŸ} other {čŋ™#ä¸ĒéĄšį›Žå—īŧŸ}}čŋ™äšŸäŧšå°†{count, plural, one {å…ļ} other {厃äģŦ}}äģŽæ‰€åąžį›¸į°ŋ中į§ģ除。", "permanently_deleted_asset": "æ°¸äš…åˆ é™¤įš„éĄšį›Ž", "permanently_deleted_assets_count": "åˇ˛æ°¸äš…åˆ é™¤{count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}", "permission": "权限", "permission_empty": "权限不čƒŊä¸ēįŠē", + "permission_onboarding_back": "čŋ”回", + "permission_onboarding_continue_anyway": "äģį„ļįģ§įģ­", + "permission_onboarding_get_started": "åŧ€å§‹äŊŋᔍ", + "permission_onboarding_go_to_settings": "čŊŦåˆ°čŽžįŊŽ", + "permission_onboarding_permission_denied": "权限čĸĢæ‹’įģã€‚čρäŊŋᔍ ImmichīŧŒč¯ˇåœ¨â€œčŽžįŊŽâ€ä¸­æŽˆäēˆį…§į‰‡å’Œč§†éĸ‘权限。", + "permission_onboarding_permission_granted": "æƒé™åˇ˛æŽˆäēˆīŧä¸€åˆ‡å‡†å¤‡å°ąįģĒ。", + "permission_onboarding_permission_limited": "权限受限。čĻčŽŠ Immich 备äģŊåšļįŽĄį†äŊ įš„æ•´ä¸Ē回åē“īŧŒč¯ˇåœ¨â€œčŽžįŊŽâ€ä¸­æŽˆäēˆį…§į‰‡å’Œč§†éĸ‘权限。", + "permission_onboarding_request": "Immich需čĻæƒé™æ‰čƒŊæŸĨįœ‹äŊ įš„į…§į‰‡å’Œč§†éĸ‘。", "person": "äēēį‰Š", "person_age_months": "{months, plural, one {#ä¸Ē月} other {#ä¸Ē月}}大", "person_age_year_months": "1 垁{months, plural, one {# ä¸Ē月} other {# ä¸Ē月}}大", - "person_age_years": "{years, plural, one {# } other {# }} 垁", + "person_age_years": "{years, plural, other {#垁}}", "person_birthdate": "å‡ēį”ŸäēŽ{date}", "person_hidden": "{name}{hidden, select, true {īŧˆéšč—īŧ‰} other {}}", "person_recognized": "厞蝆åˆĢäēēį‰Š", + "person_selected": "åˇ˛é€‰æ‹Šäēēį‰Š", "photo_shared_all_users": "įœ‹čĩˇæĨäŊ åˇ˛įģä¸Žæ‰€æœ‰į”¨æˆˇå…ąäēĢäē†äŊ įš„ᅧቇīŧŒæˆ–者äŊ æ˛Ąæœ‰äģģäŊ•可äģĨå…ąäēĢįš„į”¨æˆˇã€‚", "photos": "ᅧቇ", "photos_and_videos": "ᅧቇ & 视éĸ‘", + "photos_count": "{count, plural, one {{count, number}åŧ į…§į‰‡} other {{count, number}åŧ į…§į‰‡}}", "photos_from_previous_years": "é‚Ŗåš´ä슿—Ĩ", + "photos_only": "äģ…ᅧቇ", "pick_a_location": "选拊äŊįŊŽ", "pick_custom_range": "č‡ĒåŽšäš‰čŒƒå›´", "pick_date_range": "选拊æ—ĨæœŸčŒƒå›´", @@ -1647,8 +1755,6 @@ "play_original_video_setting_description": "äŧ˜å…ˆæ’­æ”žåŽŸå§‹č§†éĸ‘īŧŒč€ŒéžčŊŦ᠁视éĸ‘。åĻ‚æžœåŽŸå§‹č§†éĸ‘不å…ŧ厚īŧŒå¯čƒŊæ— æŗ•æ­Ŗå¸¸æ’­æ”žã€‚", "play_transcoded_video": "播攞čŊŦ᠁视éĸ‘", "please_auth_to_access": "蝎čŋ›čĄŒčēĢäģŊénj蝁äģĨčŽŋ问", - "plugin_method_filter_type": "᭛选", - "plugin_method_filter_type_description": "æ­¤æ–šæŗ•å¯äģĨ᭛选äē‹äģļīŧŒåšļæœ‰æĄäģļ地é˜ģæ­ĸ后įģ­æ­ĨéǤčŋčĄŒ", "port": "įĢ¯åŖ", "preferences_settings_subtitle": "įŽĄį†åē”į”¨įš„ååĨŊ莞įŊŽ", "preferences_settings_title": "偏åĨŊ莞įŊŽ", @@ -1665,12 +1771,12 @@ "privacy": "隐ᧁ", "profile": "č¯Ļ情", "profile_drawer_app_logs": "æ—Ĩåŋ—", + "profile_drawer_client_server_up_to_date": "åŽĸæˆˇįĢ¯å’ŒæœåŠĄį̝éƒŊæ˜¯æœ€æ–°įš„", "profile_drawer_github": "GitHub", "profile_drawer_readonly_mode": "åĒč¯ģæ¨Ąåŧåˇ˛å¯į”¨ã€‚é•ŋæŒ‰į”¨æˆˇå¤´åƒå›žæ ‡é€€å‡ē。", "profile_image_of_user": "{user}įš„ä¸Ēäēēčĩ„æ–™å›žį‰‡", "profile_picture_set": "ä¸Ēäēēčĩ„æ–™å›žį‰‡åˇ˛čŽžįŊŽã€‚", - "projection_type": "投åŊąįąģ型", - "public_album": "å…Ŧåŧ€į›¸å†Œ", + "public_album": "å…Ŧåŧ€į›¸į°ŋ", "public_share": "å…Ŧåŧ€å…ąäēĢ", "purchase_account_info": "æ”¯æŒč€…", "purchase_activated_subtitle": "感č°ĸ您寚 Immich 和åŧ€æēčŊ¯äģļįš„æ”¯æŒ", @@ -1688,6 +1794,7 @@ "purchase_individual_description_2": "æ”¯æŒč€…įŠļ态", "purchase_individual_title": "ä¸Ēäēē", "purchase_input_suggestion": "åˇ˛æœ‰ä¸€ä¸Ēäē§å“å¯†é’ĨīŧŸč¯ˇåœ¨ä¸‹æ–ščž“å…Ĩ密é’Ĩ", + "purchase_license_subtitle": "č´­äš° Immich äģĨæ”¯æŒæ­¤æœåŠĄįš„æŒįģ­å‘åą•", "purchase_lifetime_description": "įģˆčēĢčŽ¸å¯", "purchase_option_title": "购买选项", "purchase_panel_info_1": "åŧ€å‘ Immich 需čĻå¤§é‡įš„æ—ļé—´å’Œį˛žåŠ›īŧŒæˆ‘äģŦæœ‰å…¨čŒåˇĨį¨‹å¸ˆåœ¨åŠĒ力将å…ļ做到最åĨŊ。我äģŦįš„äŊŋå‘Ŋ是通čŋ‡åŧ€æēčŊ¯äģļå’Œé“åžˇå•†ä¸šåŽžčˇĩīŧŒä¸ēåŧ€å‘č€…æäž›å¯æŒįģ­įš„æ”ļå…ĨæĨæēīŧŒåšļ创åģē一ä¸Ēå°Šé‡éšį§įš„į”Ÿæ€įŗģįģŸīŧŒæäž›ä¸€ä¸Ē可äģĨįœŸæ­Ŗæ›ŋäģŖįŽ°æœ‰å‰Ĩ削性ä瑿œåŠĄįš„é€‰æ‹Šã€‚", @@ -1720,19 +1827,19 @@ "reassigned_assets_to_new_person": "重新指洞{count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}åˆ°æ–°įš„äēēį‰Š", "reassing_hint": "æŒ‡æ´žé€‰æ‹Šįš„éĄšį›Žåˆ°åˇ˛å­˜åœ¨įš„äēēį‰Š", "recent": "最čŋ‘", + "recent_albums": "最čŋ‘įš„į›¸į°ŋ", "recent_searches": "最čŋ‘搜į´ĸ", "recently_added": "čŋ‘期æˇģ加", - "recently_added_body": "åœ¨ä¸“åąžéĄĩéĸ上īŧŒį›´æŽĨæŸĨįœ‹æ‚¨čŋ‘期æˇģåŠ įš„æ‰€æœ‰å†…åŽšã€‚", - "recently_added_description": "æĩč§ˆæ‚¨įš„åĒ’äŊ“īŧŒåšļ按上äŧ åˆ° Immich įš„æ—ļ间排åēã€‚", "recently_added_page_title": "最čŋ‘æˇģ加", - "recently_added_title": "最čŋ‘æˇģ加", "recently_taken": "čŋ‘期拍摄", + "recently_taken_page_title": "最čŋ‘拍摄", "refresh": "åˆˇæ–°", "refresh_encoded_videos": "åˆˇæ–°åˇ˛įŧ–į įš„č§†éĸ‘", "refresh_faces": "åˆˇæ–°äēē脸", "refresh_metadata": "åˆˇæ–°å…ƒæ•°æŽ", "refresh_thumbnails": "åˆˇæ–°įŧŠį•Ĩ回", "refreshed": "åˇ˛åˆˇæ–°", + "refreshes_every_file": "重新æ‰Ģææ‰€æœ‰įŽ°æœ‰æ–‡äģļ和新文äģļ", "refreshing_encoded_video": "åˆˇæ–°åˇ˛įŧ–᠁视éĸ‘", "refreshing_faces": "åˆˇæ–°éĸéƒ¨č¯†åˆĢ", "refreshing_metadata": "åˆˇæ–°å…ƒæ•°æŽ", @@ -1745,33 +1852,40 @@ "remove_assets_shared_link_confirmation": "įĄŽåŽščρäģŽå…ąäēĢ链æŽĨ中į§ģ除{count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}īŧŸ", "remove_assets_title": "į§ģé™¤éĄšį›ŽīŧŸ", "remove_custom_date_range": "取æļˆč‡Ē厚䚉æ—ĨæœŸčŒƒå›´", - "remove_filter": "į§ģ除čŋ‡æģ¤", - "remove_from_album": "äģŽį›¸å†Œä¸­į§ģ除", - "remove_from_album_action_prompt": "äģŽį›¸å†Œä¸­į§ģ除äē† {count} 饚", - "remove_from_favorites": "äģŽæ”ļ藏į§ģ除", + "remove_deleted_assets": "åŊģåē•删除文äģļ", + "remove_from_album": "äģŽį›¸į°ŋ中į§ģ除", + "remove_from_album_action_prompt": "äģŽį›¸į°ŋ中į§ģ除äē† {count} 饚", + "remove_from_favorites": "į§ģå‡ēæ”ļ藏", "remove_from_lock_folder_action_prompt": "厞äģŽé”åŽšįš„æ–‡äģļ多中į§ģ除 {count} 饚", "remove_from_locked_folder": "äģŽé”åŽšæ–‡äģļ多中į§ģ除", "remove_from_locked_folder_confirmation": "æ‚¨įĄŽåŽščρ将čŋ™äē›į…§į‰‡å’Œč§†éĸ‘į§ģå‡ē锁厚文äģļ多吗īŧŸį§ģå‡ē后厃äģŦ将äŧšåœ¨æ‚¨įš„回åē“ä¸­å¯č§ã€‚", "remove_from_shared_link": "äģŽå…ąäēĢ链æŽĨ中į§ģ除", - "remove_memory": "į§ģ除回åŋ†åŒē", + "remove_memory": "į§ģå‡ē回åŋ†åŒē", "remove_photo_from_memory": "äģŽåŊ“前回åŋ†åŒēį§ģ除ᅧቇ", "remove_tag": "į§ģé™¤æ ‡į­ž", + "remove_url": "į§ģ除 URL", "remove_user": "į§ģé™¤į”¨æˆˇ", "removed_api_key": "厞į§ģ除 API 密é’Ĩīŧš{name}", "removed_from_archive": "äģŽåŊ’æĄŖä¸­į§ģ除", "removed_from_favorites": "äģŽæ”ļ藏中į§ģ除", "removed_from_favorites_count": "äģŽæ”ļ藏中į§ģ除{count, plural, other {#饚}}", "removed_memory": "åˇ˛åˆ é™¤įš„å›žåŋ†", + "removed_photo_from_memory": "äģŽå›žåŋ†åŒēä¸­åˆ é™¤įš„į…§į‰‡", "removed_tagged_assets": "äģŽ {count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}ä¸­åˆ é™¤æ ‡į­ž", "rename": "重å‘Ŋ名", + "repair": "äŋŽå¤", + "repair_no_results_message": "æœĒ莟č¸Ē和įŧēå¤ąįš„æ–‡äģļ将在此处昞į¤ē", + "replace_with_upload": "上äŧ åšļæ›ŋæĸ", "repository": "åē“", "require_password": "需čρ坆᠁", + "require_user_to_change_password_on_first_login": "į”¨æˆˇåœ¨éĻ–æŦĄį™ģåŊ•æ—ļåŋ…éĄģæ›´æ”šå¯†į ", "rescan": "重新æ‰Ģ描", "reset": "重įŊŽ", "reset_password": "重įŊŽå¯†į ", "reset_people_visibility": "重įŊŽäēēį‰Šč¯†åˆĢ", "reset_pin_code": "重įŊŽPIN᠁", "reset_pin_code_description": "åĻ‚æžœæ‚¨åŋ˜čްä熿‚¨įš„PIN᠁īŧŒæ‚¨å¯äģĨ联įŗģæœåŠĄå™¨įŽĄį†å‘˜čŋ›čĄŒé‡įŊŽ", + "reset_pin_code_success": "成功重įŊŽPIN᠁", "reset_pin_code_with_password": "您始įģˆå¯äģĨäŊŋį”¨æ‚¨įš„å¯†į æĨ重įŊŽæ‚¨įš„PIN᠁", "reset_sqlite": "重įŊŽ SQLite 数捎åē“", "reset_sqlite_clear_app_data": "清除数捎", @@ -1785,17 +1899,20 @@ "resolved_all_duplicates": "å¤„į†æ‰€æœ‰é‡å¤éĄš", "restore": "æĸ复", "restore_all": "æĸ复全部", + "restore_trash_action_prompt": "äģŽå›žæ”ļį̙䏭æĸ复äē†{count}饚", "restore_user": "æĸå¤į”¨æˆˇ", "restored_asset": "厞æĸå¤éĄšį›Ž", "resume": "įģ§įģ­", "resume_paused_jobs": "įģ§įģ­ {count, plural, one {# åˇ˛æš‚åœįš„äģģåŠĄ} other {# åˇ˛æš‚åœįš„äģģåŠĄ}}", + "retry_upload": "重新上äŧ ", "review_duplicates": "æŖ€æŸĨé‡å¤éĄš", - "review_large_files": "æŖ€æŸĨ大文äģļ", + "review_large_files": "æŸĨįœ‹å¤§æ–‡äģļ", "role": "é€‰æ‹Šį”¨æˆˇæƒé™", "role_editor": "可įŧ–čž‘", "role_viewer": "äģ…æŸĨįœ‹", "running": "æ­Ŗåœ¨čŋčĄŒ", "save": "äŋå­˜", + "save_to_gallery": "äŋå­˜åˆ°å›žåē“", "saved": "厞äŋå­˜", "saved_api_key": "厞äŋå­˜įš„ API 密é’Ĩ", "saved_profile": "厞äŋå­˜čĩ„æ–™", @@ -1806,19 +1923,16 @@ "scan": "æ‰Ģ描", "scan_all_libraries": "æ‰Ģ描所有回åē“", "scan_library": "æ‰Ģ描", + "scan_settings": "æ‰ĢæčŽžįŊŽ", "scanning": "æ‰Ģ描中", - "scanning_for_album": "æ‰Ģæį›¸å†Œä¸­...", - "screencast_mode_description": "åœ¨åąåš•ä¸Šæ˜žį¤ēé”Žį›˜å’Œéŧ æ ‡äē‹äģᅩ‡į¤ē器", - "screencast_mode_title": "切æĸåąåš•åŊ•åˆļæ¨Ąåŧ", + "scanning_for_album": "æ‰Ģæį›¸į°ŋ中...", "search": "搜į´ĸ", - "search_albums": "搜į´ĸį›¸å†Œ", + "search_albums": "搜į´ĸᛏį°ŋ", "search_by_context": "通čŋ‡æčŋ°įš„åœē景æŸĨ扞", "search_by_description": "通čŋ‡æčŋ°æŸĨ扞", "search_by_description_example": "åœ¨æ˛™åˇ´åž’æ­Ĩįš„æ—Ĩ子", "search_by_filename": "通čŋ‡æ–‡äģļåæˆ–æ‰Šåą•åæŸĨ扞", "search_by_filename_example": "åĻ‚ IMG_1234.JPG 或 PNG", - "search_by_full_path": "æŒ‰åŽŒæ•´čˇ¯åž„æˆ–æ–‡äģļ多čŋ›čĄŒæœį´ĸ", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - äŊ å¯äģĨ搜į´ĸ Projects, 3D, Printing, 2026 į­‰å…ŗé”Žč¯ã€‚", "search_by_ocr": "通čŋ‡æ–‡æœŦ蝆åˆĢæŸĨ扞", "search_by_ocr_example": "æ‹ŋé“å’–å•Ą", "search_camera_lens_model": "æŒ‰é•œå¤´åž‹åˇæŸĨ扞...", @@ -1831,13 +1945,14 @@ "search_filter_date": "æ—Ĩ期", "search_filter_date_interval": "äģŽ{start}到{end}", "search_filter_date_title": "选拊æ—ĨæœŸčŒƒå›´", - "search_filter_display_option_not_in_album": "ä¸åœ¨į›¸å†Œä¸­", + "search_filter_display_option_not_in_album": "ä¸åœ¨į›¸į°ŋ中", "search_filter_display_options": "昞į¤ē选项", "search_filter_filename": "通čŋ‡æ–‡äģļ名搜į´ĸ", "search_filter_location": "äŊįŊŽ", "search_filter_location_title": "选拊äŊįŊŽ", "search_filter_media_type": "åĒ’äŊ“įąģ型", "search_filter_media_type_title": "选拊åĒ’äŊ“įąģ型", + "search_filter_ocr": "通čŋ‡æ–‡æœŦ蝆åˆĢ搜į´ĸ", "search_filter_people_title": "选拊äēēį‰Š", "search_filter_star_rating": "星įē§č¯„分", "search_filter_tags_title": "é€‰æ‹Šæ ‡į­ž", @@ -1848,13 +1963,25 @@ "search_no_people_named": "äēēį‰Šâ€œ{name}”不存在", "search_no_result": "æœĒ扞到į쓿žœīŧŒč¯ˇå°č¯•ä¸åŒįš„æœį´ĸč¯æˆ–æœį´ĸįģ„合", "search_options": "搜į´ĸ选项", + "search_page_categories": "įąģåˆĢ", + "search_page_motion_photos": "动回", + "search_page_no_objects": "æ˛Ąæœ‰äē‹į‰ŠäŋĄæ¯", + "search_page_no_places": "åœ°į‚šäŋĄæ¯ä¸å­˜åœ¨", + "search_page_screenshots": "åąåš•æˆĒ回", "search_page_search_photos_videos": "搜į´ĸæ‚¨įš„į…§į‰‡å’Œč§†éĸ‘", + "search_page_selfies": "č‡Ē拍", + "search_page_things": "äē‹į‰Š", "search_page_view_all_button": "æŸĨįœ‹å…¨éƒ¨", + "search_page_your_activity": "æ‚¨įš„æ´ģ动", + "search_page_your_map": "čļŗčŋš", "search_people": "按äēēį‰ŠæŸĨ扞", "search_places": "æŒ‰åœ°į‚šæŸĨ扞", "search_rating": "按星įē§æŸĨ扞...", + "search_result_page_new_search_hint": "搜į´ĸæ–°įš„", "search_settings": "搜į´ĸ莞įŊŽ", "search_state": "æŒ‰įœäģŊæŸĨ扞...", + "search_suggestion_list_smart_search_hint_1": "éģ˜čŽ¤æƒ…å†ĩ䏋吝ᔍæ™ēčƒŊ搜į´ĸīŧŒčĻæœį´ĸ元数捎īŧŒč¯ˇäŊŋį”¨į›¸å…ŗč¯­æŗ• ", + "search_suggestion_list_smart_search_hint_2": "m:æ‚¨įš„æœį´ĸå…ŗé”Žč¯", "search_tags": "æŒ‰æ ‡į­žæŸĨ扞â€Ļ", "search_timezone": "按æ—ļåŒēæŸĨ扞...", "search_type": "按įąģ型æŸĨ扞", @@ -1863,11 +1990,12 @@ "second": "į§’", "see_all_people": "æŸĨįœ‹æ‰€æœ‰äēēį‰Š", "select": "选拊", - "select_album": "é€‰æ‹Šį›¸å†Œ", - "select_album_cover": "é€‰æ‹Šį›¸å†Œå°éĸ", - "select_albums": "é€‰æ‹Šį›¸å†Œ", + "select_album": "é€‰æ‹Šį›¸į°ŋ", + "select_album_cover": "é€‰æ‹Šį›¸į°ŋ封éĸ", + "select_albums": "é€‰æ‹Šį›¸į°ŋ", "select_all": "全选", "select_all_duplicates": "é€‰æ‹Šæ‰€æœ‰é‡å¤éĄš", + "select_all_in": "选拊 {group} ä¸­įš„æ‰€æœ‰å†…åŽš", "select_avatar_color": "选拊头像éĸœč‰˛", "select_count": "{count, plural, one {厞选䏭#饚} other {厞选䏭#饚}}", "select_cutoff_date": "选拊æˆĒæ­ĸæ—Ĩ期", @@ -1875,13 +2003,14 @@ "select_featured_photo": "选拊ä¸Ē性头像", "select_from_computer": "äģŽčŽĄįŽ—æœē中选拊", "select_keep_all": "全部äŋį•™", + "select_library_owner": "选拊回å瓿‰€æœ‰č€…", "select_new_face": "选拊新äēē脸", "select_people": "选拊äēēį‰Š", "select_person": "选拊äēēį‰Š", "select_person_to_tag": "选拊čĻæ ‡čŽ°įš„äēēį‰Š", "select_photos": "é€‰æ‹Šį…§į‰‡", - "select_quality": "选拊į”ģč´¨", "select_trash_all": "全部删除", + "select_user_for_sharing_page_err_album": "创åģēᛏį°ŋå¤ąč´Ĩ", "selected": "åˇ˛é€‰æ‹Š", "selected_count": "{count, plural, other {厞选䏭#饚}}", "selected_gps_coordinates": "åˇ˛é€‰åŽšįš„GPS坐标", @@ -1899,7 +2028,7 @@ "server_update_available": "æœåŠĄå™¨æ›´æ–°å¯į”¨", "server_version": "æœåŠĄå™¨į‰ˆæœŦ", "set": "莞įŊŽ", - "set_as_album_cover": "莞ä¸ēį›¸å†Œå°éĸ", + "set_as_album_cover": "莞ä¸ēᛏį°ŋ封éĸ", "set_as_featured_photo": "莞įŊŽä¸ēį‰šč‰˛į…§į‰‡", "set_as_profile_picture": "莞ä¸ēä¸Ēäēēčĩ„æ–™å›žį‰‡", "set_date_of_birth": "莞įŊŽå‡ēį”Ÿæ—Ĩ期", @@ -1914,31 +2043,42 @@ "setting_image_viewer_original_title": "加čŊŊ原回", "setting_image_viewer_preview_subtitle": "吝ᔍäģĨ加čŊŊä¸­į­‰č´¨é‡įš„å›žåƒīŧŒįρᔍäģĨ加čŊŊ原回或įŧŠį•Ĩ回。", "setting_image_viewer_preview_title": "加čŊŊéĸ„č§ˆå›ž", + "setting_image_viewer_title": "å›žį‰‡", "setting_languages_apply": "åē”ᔍ", "setting_languages_subtitle": "更攚åē”ᔍ蝭荀", + "setting_notifications_notify_failures_grace_period": "后台备äģŊå¤ąč´Ĩ通įŸĨīŧš{duration}", + "setting_notifications_notify_hours": "{count}小æ—ļ", + "setting_notifications_notify_immediately": "įĢ‹åŗ", "setting_notifications_notify_minutes": "{count}分钟", + "setting_notifications_notify_never": "äģŽä¸", "setting_notifications_notify_seconds": "{count}į§’", + "setting_notifications_single_progress_subtitle": "æ¯éĄšįš„č¯Ļįģ†ä¸Šäŧ čŋ›åēĻäŋĄæ¯", + "setting_notifications_single_progress_title": "昞į¤ē后台备äģŊč¯Ļįģ†čŋ›åēĻ", "setting_notifications_subtitle": "č°ƒæ•´é€šįŸĨéĻ–é€‰éĄš", + "setting_notifications_total_progress_subtitle": "æ€ģäŊ“上äŧ čŋ›åēĻ(åˇ˛åŽŒæˆ/æ€ģ莥)", + "setting_notifications_total_progress_title": "昞į¤ē后台备äģŊæ€ģčŋ›åēĻ", "setting_video_viewer_auto_play_subtitle": "打åŧ€č§†éĸ‘æ—ļč‡Ē动åŧ€å§‹æ’­æ”ž", "setting_video_viewer_auto_play_title": "č‡ĒåŠ¨æ’­æ”žč§†éĸ‘", "setting_video_viewer_looping_title": "åžĒįŽ¯æ’­æ”ž", "setting_video_viewer_original_video_subtitle": "äģŽæœåŠĄå™¨æĩåŧäŧ čž“视éĸ‘æ—ļīŧŒåŗäŊŋ有čŊŦ᠁īŧŒäšŸæ’­æ”žåŽŸå§‹č§†éĸ‘。可čƒŊäŧšå¯ŧ致įŧ“å†˛ã€‚æœŦåœ°č§†éĸ‘则äģĨåŽŸå§‹č´¨é‡æ’­æ”žīŧŒä¸Žæ­¤čŽžįŊŽæ— å…ŗã€‚", "setting_video_viewer_original_video_title": "åŧēåˆļæ’­æ”žåŽŸå§‹č§†éĸ‘", "settings": "莞įŊŽ", + "settings_require_restart": "č¯ˇé‡å¯ Immich äģĨäŊŋ莞įŊŽį”Ÿæ•ˆ", "settings_saved": "莞įŊŽåˇ˛äŋå­˜", "setup_pin_code": "莞įŊŽPIN᠁", "share": "分äēĢ", + "share_action_prompt": "åˇ˛å…ąäēĢ {count} éĄšį›Ž", + "share_add_photos": "æˇģåŠ éĄšį›Ž", + "share_assets_selected": "{count}åˇ˛é€‰æ‹Š", "share_dialog_preparing": "准备中...", "share_link": "分äēĢ链æŽĨ", - "share_original": "äŊŋį”¨åŽŸå›žīŧˆå¤§īŧ‰", - "share_preview": "äŊŋᔍįŧŠį•Ĩ回īŧˆå°īŧ‰", - "share_quality_body": "在分äēĢ䚋前īŧŒé•ŋ按分äēĢæŒ‰é’ŽäģĨé€‰æ‹Šå›žåƒč´¨é‡ã€‚", - "share_quality_title": "选拊分äēĢį”ģč´¨", "shared": "å…ąäēĢ", "shared_album_activities_input_disable": "蝄čŽē厞įρᔍ", "shared_album_activity_remove_content": "是åĻ删除此æ´ģ动īŧŸ", "shared_album_activity_remove_title": "删除æ´ģ动", - "shared_album_section_people_action_error": "退å‡ē/åˆ é™¤į›¸å†Œå¤ąč´Ĩ", + "shared_album_section_people_action_error": "退å‡ē/åˆ é™¤į›¸į°ŋå¤ąč´Ĩ", + "shared_album_section_people_action_leave": "äģŽį›¸į°ŋä¸­åˆ é™¤į”¨æˆˇ", + "shared_album_section_people_action_remove_user": "äģŽį›¸į°ŋä¸­åˆ é™¤į”¨æˆˇ", "shared_album_section_people_title": "äēēį‰Š", "shared_by": "å…ąäēĢč‡Ē", "shared_by_user": "į”ąâ€œ{user}â€å…ąäēĢ", @@ -1947,10 +2087,9 @@ "shared_intent_upload_button_progress_text": "厞䏊äŧ {current} / {total}", "shared_link_app_bar_title": "å…ąäēĢ链æŽĨ", "shared_link_clipboard_copied_massage": "复åˆļ到å‰Ēč´´æŋ", + "shared_link_clipboard_text": "链æŽĨīŧš{link}\n坆᠁īŧš{password}", "shared_link_create_error": "创åģēå…ąäēĢ链æŽĨå‡ē错", "shared_link_custom_url_description": "äŊŋᔍč‡Ē厚䚉URLčŽŋé—Žæ­¤å…ąäēĢ链æŽĨ", - "shared_link_custom_url_title": "č‡Ē厚䚉 URL", - "shared_link_custom_url_warning": "æŗ¨æ„:å¸Ļæœ‰æ­Ŗæ–œæ įš„č‡Ē厚䚉URLåį§°å¯čƒŊ不äŧšåƒæ‚¨éĸ„æœŸįš„é‚Ŗæ ˇčŋčĄŒã€‚", "shared_link_edit_description_hint": "įŧ–čž‘å…ąäēĢæčŋ°", "shared_link_edit_expire_after_option_day": "1夊", "shared_link_edit_expire_after_option_days": "{count}夊", @@ -1984,9 +2123,15 @@ "shared_with_partner": "与{partner}å…ąäēĢ", "sharing": "å…ąäēĢ", "sharing_enter_password": "č¯ˇčž“å…Ĩå¯†į åŽæŸĨįœ‹æ­¤éĄĩéĸ。", + "sharing_page_album": "å…ąäēĢᛏį°ŋ", + "sharing_page_description": "创åģēå…ąäēĢᛏį°ŋäģĨ与įŊ‘įģœä¸­įš„äēēå…ąäēĢį…§į‰‡å’Œč§†éĸ‘。", + "sharing_page_empty_list": "įŠē", + "sharing_sidebar_description": "åœ¨äž§čžšæ ä¸­æ˜žį¤ēâ€œå…ąäēĢ”链æŽĨ", + "sharing_silver_appbar_create_shared_album": "创åģēå…ąäēĢᛏį°ŋ", + "sharing_silver_appbar_share_partner": "å…ąäēĢįģ™åäŊœč€…", "shift_to_permanent_delete": "按äŊ ⇧ Shift é”Žæ°¸äš…åˆ é™¤éĄšį›Ž", - "show_album_options": "昞į¤ēį›¸å†Œé€‰éĄš", - "show_albums": "昞į¤ēį›¸å†Œ", + "show_album_options": "昞į¤ēᛏį°ŋ选项", + "show_albums": "昞į¤ēᛏį°ŋ", "show_all_people": "昞į¤ē所有äēēį‰Š", "show_and_hide_people": "昞į¤ēå’Œéšč—äēēį‰Š", "show_file_location": "昞į¤ē文äģļäŊįŊŽ", @@ -1995,9 +2140,7 @@ "show_in_timeline": "在æ—ļ间čŊ´ä¸­æ˜žį¤ē", "show_in_timeline_setting_description": "在æ—ļ间čŊ´ä¸­æ˜žį¤ēæ­¤į”¨æˆˇįš„į…§į‰‡å’Œč§†éĸ‘", "show_keyboard_shortcuts": "昞į¤ēé”Žį›˜åŋĢæˇé”Ž", - "show_less": "æ”ļčĩˇ", "show_metadata": "昞į¤ē元数捎", - "show_more_fields": "{count, plural, one {昞į¤ē # ä¸Ē更多字æŽĩ} other {昞į¤ē # ä¸Ē更多字æŽĩ}}", "show_or_hide_info": "昞į¤ēæˆ–éšč—äŋĄæ¯", "show_password": "昞į¤ē坆᠁", "show_person_options": "昞į¤ēäēēį‰Šé€‰éĄš", @@ -2005,7 +2148,6 @@ "show_schema": "昞į¤ēæžļ构", "show_search_options": "昞į¤ē搜į´ĸ选项", "show_shared_links": "昞į¤ēå…ąäēĢ链æŽĨ", - "show_slideshow_metadata_overlay": "昞į¤ē元数捎", "show_slideshow_transition": "昞į¤ēåšģၝቇčŋ‡æ¸Ąæ•ˆæžœ", "show_supporter_badge": "æ”¯æŒč€…åžŊįĢ ", "show_supporter_badge_description": "åą•į¤ēæ”¯æŒč€…åžŊįĢ ", @@ -2017,25 +2159,18 @@ "sign_out": "退å‡ēį™ģåŊ•", "sign_up": "æŗ¨å†Œ", "size": "大小", - "skip": "莺čŋ‡", "skip_to_content": "莺čŊŦ到内厚", "skip_to_folders": "莺čŊŦ到文äģļ多", "skip_to_tags": "莺čŊŦåˆ°æ ‡į­ž", "slideshow": "åšģį¯į‰‡æ”žæ˜ ", - "slideshow_body": "坐下æĨīŧŒäģĨå…¨åąåšģၝቇåŊĸåŧæŦŖčĩæ‚¨įš„į…§į‰‡ã€‚", - "slideshow_metadata_overlay_mode": "叠加内厚昞į¤ē", - "slideshow_metadata_overlay_mode_description_only": "äģ…æčŋ°", - "slideshow_metadata_overlay_mode_full": "厌整äŋĄæ¯", "slideshow_repeat": "重复åšģၝቇ", "slideshow_repeat_description": "åšģၝቇį쓿ŸåŽåžĒįŽ¯æ’­æ”ž", "slideshow_settings": "æ”žæ˜ čŽžįŊŽ", - "slideshow_title": "åšģį¯į‰‡æ”žæ˜ ", - "smart_album": "æ™ēčƒŊį›¸å†Œ", - "some_assets_already_have_a_location_warning": "éƒ¨åˆ†æ‰€é€‰éĄšį›Žåˇ˛æœ‰äŊįŊŽäŋĄæ¯", - "sort_albums_by": "į›¸å†ŒæŽ’åē...", + "sort_albums_by": "ᛏį°ŋ排åēäžæŽ...", "sort_created": "创åģēæ—Ĩ期", "sort_items": "éĄšį›Žæ•°é‡", "sort_modified": "äŋŽæ”šæ—Ĩ期", + "sort_newest": "æœ€æ–°į…§į‰‡", "sort_oldest": "æœ€æ—Šįš„į…§į‰‡", "sort_people_by_similarity": "æŒ‰į›¸äŧŧ性寚äēēį‰Ščŋ›čĄŒæŽ’åē", "sort_recent": "æœ€æ–°įš„į…§į‰‡", @@ -2044,6 +2179,7 @@ "stack": "堆叠", "stack_action_prompt": "{count}ä¸Ēåˇ˛å †å ", "stack_duplicates": "å †å é‡å¤éĄšį›Ž", + "stack_select_one_photo": "ä¸ē堆叠选拊一åŧ åą•į¤ē回", "stack_selected_photos": "å †å é€‰åŽšįš„į…§į‰‡", "stacked_assets_count": "åˇ˛å †å {count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}", "stacktrace": "å †æ ˆčˇŸč¸Ē", @@ -2052,10 +2188,6 @@ "start_date_before_end_date": "åŧ€å§‹æ—Ĩ期åŋ…éĄģ在į쓿Ÿæ—Ĩ期䚋前", "state": "ᜁäģŊ", "status": "įŠļ态", - "step_delete": "删除æ­ĨéǤ", - "step_delete_confirm": "įĄŽåŽščĻåˆ é™¤æ­¤æ­ĨéǤ吗īŧŸ", - "step_details": "æ­ĨéǤč¯Ļ情", - "steps": "æ­ĨéǤ", "stop_casting": "停æ­ĸ投攞", "stop_motion_photo": "厚æ ŧᅧቇ", "stop_photo_sharing": "停æ­ĸå…ąäēĢᅧቇīŧŸ", @@ -2075,19 +2207,19 @@ "supporter": "čĩžåŠŠč€…", "swap_merge_direction": "äē’æĸ合åšļ斚向", "sync": "同æ­Ĩ", - "sync_albums": "同æ­Ĩį›¸å†Œ", + "sync_albums": "同æ­Ĩᛏį°ŋ", + "sync_albums_manual_subtitle": "将所有上äŧ įš„视éĸ‘å’Œį…§į‰‡åŒæ­Ĩåˆ°é€‰åŽšįš„å¤‡äģŊᛏį°ŋ", "sync_local": "同æ­ĨæœŦ地", "sync_remote": "同æ­Ĩčŋœį¨‹", "sync_status": "同æ­ĨįŠļ态", "sync_status_subtitle": "æŸĨįœ‹å’ŒįŽĄį†åŒæ­ĨįŗģįģŸ", - "sync_upload_album_setting_subtitle": "创åģēį…§į‰‡å’Œč§†éĸ‘åšļ上äŧ åˆ° Immich ä¸Šé€‰åŽšįš„į›¸å†Œ", - "system_theme": "įŗģįģŸä¸ģéĸ˜", - "system_theme_command_description": "äŊŋᔍįŗģįģŸä¸ģéĸ˜īŧˆ{value}īŧ‰", + "sync_upload_album_setting_subtitle": "创åģēį…§į‰‡å’Œč§†éĸ‘åšļ上äŧ åˆ° Immich ä¸Šé€‰åŽšįš„į›¸į°ŋ", "tag": "æ ‡į­ž", "tag_assets": "æ ‡čŽ°éĄšį›Ž", "tag_created": "åˇ˛åˆ›åģēæ ‡į­žīŧš{tag}", "tag_face": "æ ‡čŽ°äēē脸", "tag_feature_description": "按é€ģčž‘æ ‡į­žåˆ†įģ„åšļæĩč§ˆį…§į‰‡å’Œč§†éĸ‘", + "tag_not_found_question": "æ‰žä¸åˆ°æ ‡į­žå—īŧŸåˆ›åģēæ–°æ ‡į­žã€‚", "tag_people": "å‘Ŋ名äēēį‰Š", "tag_updated": "åˇ˛æ›´æ–°æ ‡į­žīŧš{tag}", "tagged_assets": "{count, plural, one {# ä¸ĒéĄšį›Ž} other {# ä¸ĒéĄšį›Ž}}čĸĢåŠ ä¸Šæ ‡į­ž", @@ -2102,10 +2234,15 @@ "theme_setting_asset_list_tiles_per_row_title": "æ¯čĄŒåą•į¤ē {count} 饚", "theme_setting_colorful_interface_subtitle": "åē”ᔍä¸ģč‰˛č°ƒåˆ°čƒŒæ™¯ã€‚", "theme_setting_colorful_interface_title": "åŊŠč‰˛į•Œéĸ", + "theme_setting_image_viewer_quality_subtitle": "č°ƒæ•´æŸĨįœ‹å¤§å›žæ—ļįš„å›žåƒč´¨é‡", + "theme_setting_image_viewer_quality_title": "å›žåƒč´¨é‡", "theme_setting_primary_color_subtitle": "选拊éĸœč‰˛äŊœä¸ēä¸ģč‰˛č°ƒã€‚", "theme_setting_primary_color_title": "ä¸ģ色调", "theme_setting_system_primary_color_title": "äŊŋᔍįŗģįģŸéĸœč‰˛", "theme_setting_system_theme_switch": "č‡Ē动īŧˆčˇŸéšįŗģįģŸčŽžįŊŽīŧ‰", + "theme_setting_theme_subtitle": "选拊åē”ᔍä¸ģéĸ˜", + "theme_setting_three_stage_loading_subtitle": "三æŽĩåŧåŠ čŊŊ可čƒŊäŧšæå‡åŠ čŊŊ性čƒŊīŧŒäŊ†å¯čƒŊäŧšå¯ŧč‡´æ›´éĢ˜įš„įŊ‘įģœč´ŸčŊŊ", + "theme_setting_three_stage_loading_title": "吝ᔍ䏉æŽĩåŧåŠ čŊŊ", "then": "į„ļ后", "they_will_be_merged_together": "éĄšį›Žå°†äŧšåˆåšļ到一čĩˇ", "third_party_resources": "įŦŦ三斚čĩ„æē", @@ -2131,17 +2268,23 @@ "trash_all": "全部删除", "trash_count": "删除{count, number}饚", "trash_delete_asset": "å°†éĄšį›Žæ”žå…Ĩ回æ”ļįĢ™/删除", + "trash_emptied": "įŠē回æ”ļįĢ™", "trash_no_results_message": "åˆ é™¤įš„į…§į‰‡å’Œč§†éĸ‘å°†åœ¨æ­¤å¤„åą•į¤ē。", "trash_page_delete_all": "删除全部", + "trash_page_empty_trash_dialog_content": "是åĻ清įŠē回æ”ļįĢ™īŧŸčŋ™äē›éĄšį›Žå°†äģŽ Immich 中永䚅删除", "trash_page_info": "回æ”ļįĢ™ä¸­éĄšį›Žå°†åœ¨ {days} 夊后永䚅删除", + "trash_page_no_assets": "æš‚æ— åˇ˛åˆ é™¤éĄšį›Ž", + "trash_page_restore_all": "æĸ复全部", + "trash_page_select_assets_btn": "é€‰æ‹ŠéĄšį›Ž", + "trash_page_title": "回æ”ļįĢ™ ({count})", "trashed_items_will_be_permanently_deleted_after": "回æ”ļįĢ™ä¸­įš„éĄšį›Žå°†åœ¨{days, plural, one {#夊} other {#夊}}后čĸĢæ°¸äš…删除。", "trigger": "č§Ļå‘æĄäģļ", - "trigger_asset_metadata_extraction": "čĩ„æēå…ƒæ•°æŽæå–", - "trigger_asset_metadata_extraction_description": "åŊ“čĩ„æēįš„EXIF元数捎čĸĢæå–æ—ļč§Ļ发", - "trigger_asset_uploaded": "čĩ„äē§ä¸Šäŧ ", + "trigger_asset_uploaded": "éĄšį›Žåˇ˛ä¸Šäŧ ", "trigger_asset_uploaded_description": "åŊ“上äŧ æ–°éĄšį›Žæ—ļč§Ļ发", + "trigger_description": "启动åˇĨäŊœæĩįš„äē‹äģļ", "trigger_person_recognized": "äēēį‰Šåˇ˛č¯†åˆĢ", - "trigger_person_recognized_description": "åŊ“蝆åˆĢ到äēēį‰Šæ—ļč§Ļ发", + "trigger_person_recognized_description": "åŊ“æŖ€æĩ‹åˆ°äēēį‰Šæ—ļč§Ļ发", + "trigger_type": "č§Ļ发įąģ型", "troubleshoot": "故障排除", "type": "įąģ型", "unable_to_change_pin_code": "æ— æŗ•äŋŽæ”šPIN᠁", @@ -2157,33 +2300,35 @@ "unknown": "æœĒįŸĨ", "unknown_country": "æœĒįŸĨįš„å›ŊåŽļ", "unknown_date": "æœĒįŸĨæ—Ĩ期", - "unknown_schema": "æœĒįŸĨæžļ构", "unknown_year": "æœĒįŸĨåš´äģŊ", "unlimited": "无限åˆļ", "unlink_motion_video": "取æļˆé“žæŽĨåŠ¨æ€č§†éĸ‘", "unlink_oauth": "č§Ŗįģ‘ OAuth", "unlinked_oauth_account": "č§Ŗįģ‘ OAuth č´Ļæˆˇ", "unmute_memories": "取æļˆé™éŸŗå›žåŋ†", - "unnamed_album": "æœĒå‘Ŋåį›¸å†Œ", - "unnamed_album_delete_confirmation": "æ‚¨įĄŽåŽščĻåˆ é™¤č¯Ĩį›¸å†Œå—īŧŸ", + "unnamed_album": "æœĒå‘Ŋåį›¸į°ŋ", + "unnamed_album_delete_confirmation": "æ‚¨įĄŽåŽščĻåˆ é™¤č¯Ĩᛏį°ŋ吗īŧŸ", "unnamed_share": "æœĒå‘Ŋåå…ąäēĢ", "unsaved_change": "äŋŽæ”šæœĒäŋå­˜", "unselect_all": "取æļˆå…¨é€‰", "unselect_all_duplicates": "取æļˆé€‰æ‹Šæ‰€æœ‰é‡å¤éĄš", + "unselect_all_in": "取æļˆé€‰æ‹Š {group} ä¸­įš„æ‰€æœ‰å†…åŽš", "unstack": "取æļˆå †å ", "unstack_action_prompt": "{count}ä¸ĒæœĒ堆叠", "unstacked_assets_count": "{count, plural, one {#ä¸ĒéĄšį›Ž} other {#ä¸ĒéĄšį›Ž}}åˇ˛å–æļˆå †å ", "unsupported_field_type": "ä¸æ”¯æŒįš„å­—æŽĩįąģ型", "unsupported_file_type": "不支持上äŧ æ–‡äģļ {file}īŧŒåŊ“前不支持 {type} įąģåž‹įš„æ–‡äģļ。", "untagged": "æ— æ ‡į­ž", + "untitled_workflow": "无标éĸ˜åˇĨäŊœæĩ", "up_next": "下一ä¸Ē", "update_location_action_prompt": "更新{count}ä¸Ēæ‰€é€‰éĄšį›Žįš„äŊįŊŽīŧš", "updated_at": "最后更新æ—ļ间", "updated_password": "æ›´æ–°å¯†į ", "upload": "上äŧ ", "upload_concurrency": "上äŧ åšļ发", - "upload_day_count": "{date}: {count, plural, one {# ä¸Ē上äŧ } other {# ä¸Ē上äŧ }}", "upload_details": "上äŧ č¯Ļ情", + "upload_dialog_info": "是åĻčĻå°†æ‰€é€‰éĄšį›Žå¤‡äģŊåˆ°æœåŠĄå™¨īŧŸ", + "upload_dialog_title": "上äŧ éĄšį›Ž", "upload_error_with_count": "{count, plural, one {# ä¸ĒéĄšį›Ž} other {# ä¸ĒéĄšį›Ž}}上äŧ é”™č¯¯", "upload_errors": "上äŧ åŽŒæˆīŧŒå‡ēįŽ°{count, plural, one {#ä¸Ē错蝝} other {#ä¸Ē错蝝}}īŧŒåˆˇæ–°éĄĩéĸäģĨæŸĨįœ‹æ–°ä¸Šäŧ įš„éĄšį›Žã€‚", "upload_finished": "上äŧ åŽŒæˆ", @@ -2193,13 +2338,9 @@ "upload_status_errors": "错蝝", "upload_status_uploaded": "厞䏊äŧ ", "upload_success": "上äŧ æˆåŠŸīŧŒåˆˇæ–°éĄĩéĸæŸĨįœ‹æ–°ä¸Šäŧ įš„éĄšį›Žã€‚", - "upload_to_album_body": "寚äēŽä¸äŊŋį”¨æ‰‹åŠ¨ä¸Šäŧ åŠŸčƒŊįš„į”¨æˆˇīŧŒįŽ°åœ¨æ‚¨å¯äģĨ选拊在上äŧ æœŦåœ°į…§į‰‡įš„åŒæ—ļį›´æŽĨ将å…ᅫģåŠ åˆ°į›¸å†Œä¸­īŧŒæ— éœ€å†å…ˆä¸Šäŧ åŽæˇģ加。", - "upload_to_album_title": "į›´æŽĨ上äŧ č‡ŗį›¸å†Œ", "upload_to_immich": "上äŧ č‡ŗImmichīŧˆ{count}īŧ‰", "uploading": "æ­Ŗåœ¨ä¸Šäŧ ", "uploading_media": "文äģļ上äŧ ä¸­", - "uploads": "上äŧ ", - "uploads_count": "{count, plural, one {# ä¸Ē上äŧ } other {# ä¸Ē上äŧ }}", "url": "URL", "usage": "į”¨é‡", "use_biometric": "äŊŋį”¨į”Ÿį‰Šč¯†åˆĢ", @@ -2207,7 +2348,6 @@ "use_browser_locale_description": "栚捎äŊ įš„æĩč§ˆå™¨č¯­č¨€å’Œåœ°åŒē莞įŊŽæĨæ ŧåŧåŒ–æ—Ĩ期、æ—ļ间和数字", "use_current_connection": "äŊŋᔍåŊ“前čŋžæŽĨ", "use_custom_date_range": "č‡Ē厚䚉æ—ĨæœŸčŒƒå›´", - "use_template": "äŊŋį”¨æ¨Ąæŋ", "user": "į”¨æˆˇ", "user_has_been_deleted": "æ­¤į”¨æˆˇåˇ˛čĸĢ删除。", "user_id": "į”¨æˆˇ ID", @@ -2217,58 +2357,60 @@ "user_privacy": "į”¨æˆˇéšį§", "user_purchase_settings": "č´­äš°", "user_purchase_settings_description": "įŽĄį†č´­äš°čŽĸ单", + "user_role_set": "莞įŊŽâ€œ{user}”ä¸ē“{role}”", "user_usage_detail": "į”¨æˆˇį”¨é‡č¯Ļ情", "user_usage_stats": "å¸æˆˇäŊŋᔍįģŸčŽĄ", "user_usage_stats_description": "æŸĨįœ‹å¸æˆˇäŊŋᔍįģŸčŽĄäŋĄæ¯", "username": "į”¨æˆˇå", "users": "į”¨æˆˇ", - "users_added_to_album_count": "厞将 {count, plural, one {# ä¸Ēį”¨æˆˇ} other {# ä¸Ēį”¨æˆˇ}} æˇģåŠ åˆ°į›¸å†Œ", + "users_added_to_album_count": "厞将 {count, plural, one {# ä¸Ēį”¨æˆˇ} other {# ä¸Ēį”¨æˆˇ}} æˇģåŠ åˆ°į›¸į°ŋ", "utilities": "åŽžį”¨åˇĨå…ˇ", "validate": "énj蝁", "validate_endpoint_error": "č¯ˇčž“å…Ĩæœ‰æ•ˆįš„ URL", + "validation_error": "éĒŒč¯é”™č¯¯", "variables": "变量", "version": "į‰ˆæœŦ", "version_announcement_closing": "æ‚¨įš„æœ‹å‹īŧŒAlex", - "version_announcement_message": "您åĨŊīŧImmich æœ‰æ–°į‰ˆæœŦå¯į”¨ã€‚č¯ˇčŠąä盿—ļ间阅č¯ģ å‘čĄŒč¯´æ˜ŽīŧŒįĄŽäŋæ‚¨įš„莞įŊŽåˇ˛æ›´æ–°č‡ŗæœ€æ–°į‰ˆæœŦīŧŒäģĨéŋ免å‡ēįŽ°é…įŊŽé”™č¯¯īŧŒå°¤å…ļ是您äŊŋᔍ WatchTower 或å…ļäģ–č‡Ē动更新 Immich åŽžäž‹įš„æœēåˆļæ—ļ。", + "version_announcement_message": "äŊ åĨŊīŧImmich įš„æ–°į‰ˆæœŦ厞įģå‘å¸ƒã€‚č¯ˇčŠąį‚šæ—ļ间阅č¯ģå‘čĄŒč¯´æ˜ŽīŧŒäģĨįĄŽäŋäŊ įš„部įŊ˛įޝåĸƒäŋæŒæœ€æ–°īŧŒäģŽč€Œéŋ免配įŊŽé”™č¯¯ã€‚į‰šåˆĢ是åĻ‚æžœäŊ äŊŋᔍäē† WatchTower 或å…ļäģ–č‡Ē动更新æœēåˆļīŧŒčŋ™ä¸€į‚šå°¤ä¸ē重čĻã€‚", "version_history": "į‰ˆæœŦæ›´æ–°åŽ†å˛čŽ°åŊ•", "version_history_item": "在 {date} åŽ‰čŖ… {version} į‰ˆæœŦ", "video": "视éĸ‘", "video_hover_setting": "éŧ æ ‡æ‚Ŧ停æ—ļæ’­æ”žč§†éĸ‘įŧŠį•Ĩ回", "video_hover_setting_description": "åŊ“éŧ æ ‡æ‚Ŧåœåœ¨éĄšį›Žä¸Šæ—ļæ’­æ”žč§†éĸ‘įŧŠį•Ĩå›žã€‚åŗäŊŋįρᔍä熿­¤åŠŸčƒŊīŧŒäšŸå¯äģĨ通čŋ‡å°†éŧ æ ‡æ‚Ŧ停在播攞回标上æĨåŧ€å§‹æ’­æ”žã€‚", - "video_quality": "视éĸ‘č´¨é‡", "videos": "视éĸ‘", "videos_count": "{count, plural, one {#ä¸Ē视éĸ‘} other {#ä¸Ē视éĸ‘}}", + "videos_only": "äģ…视éĸ‘", "view": "æŸĨįœ‹", - "view_album": "æŸĨįœ‹į›¸å†Œ", + "view_album": "æŸĨįœ‹į›¸į°ŋ", "view_all": "æŸĨįœ‹å…¨éƒ¨", "view_all_users": "æŸĨįœ‹å…¨éƒ¨į”¨æˆˇ", "view_asset_owners": "æŸĨįœ‹éĄšį›Žæ‰€æœ‰č€…", "view_details": "æŸĨįœ‹č¯Ļ情", "view_in_timeline": "在æ—ļ间čŊ´ä¸­æŸĨįœ‹", "view_link": "æŸĨįœ‹é“žæŽĨ", + "view_links": "æŸĨįœ‹é“žæŽĨ", "view_name": "æŸĨįœ‹", "view_next_asset": "æŸĨįœ‹ä¸‹ä¸€éĄš", "view_previous_asset": "æŸĨįœ‹ä¸Šä¸€éĄš", "view_qr_code": "æŸĨįœ‹äēŒįģ´į ", "view_similar_photos": "æŸĨįœ‹į›¸äŧŧᅧቇ", "view_stack": "æŸĨįœ‹å †å éĄšį›Ž", + "view_user": "æŸĨįœ‹į”¨æˆˇ", "viewer_remove_from_stack": "äģŽå †å ä¸­į§ģ除", + "viewer_stack_use_as_main_asset": "äŊœä¸ēä¸ģį”ģ像äŊŋᔍ", + "viewer_unstack": "取æļˆå †å ", "visibility": "å¯č§æ€§", "visibility_changed": "{count, plural, one {#ä¸Ēäēēį‰Š} other {#ä¸Ēäēēį‰Š}}įš„å¯č§æ€§åˇ˛äŋŽæ”š", "visual": "å¯č§†åŒ–", + "visual_builder": "å¯č§†åŒ–į”Ÿæˆå™¨", "waiting": "į­‰åž…å¤„į†", "waiting_count": "į­‰åž…: {count}", "warning": "č­Ļ告", "week": "周", "welcome": "æŦĸčŋŽ", "welcome_to_immich": "æŦĸčŋŽäŊŋᔍ Immich", - "whats_new": "新功čƒŊ", - "whats_new_settings_subtitle": "įœ‹įœ‹Immich有äģ€äšˆæ–°å˜åŒ–", - "whats_new_version": "į‰ˆæœŦīŧš{version}", - "when": "åŊ“", "width": "åŽŊåēĻ", "wifi_name": "Wi-Fiåį§°", - "workflow": "åˇĨäŊœæĩ", "workflow_delete_prompt": "æ‚¨įĄŽåŽščĻåˆ é™¤æ­¤åˇĨäŊœæĩå—īŧŸ", "workflow_deleted": "åˇĨäŊœæĩåˇ˛åˆ é™¤", "workflow_description": "åˇĨäŊœæĩæčŋ°", @@ -2278,17 +2420,17 @@ "workflow_name": "åˇĨäŊœæĩåį§°", "workflow_navigation_prompt": "äŊ įĄŽåŽšä¸äŋå­˜č€Œé€€å‡ēīŧŸ", "workflow_summary": "åˇĨäŊœæĩæ‘˜čρ", - "workflow_templates": "åˇĨäŊœæĩæ¨Ąæŋ", "workflow_update_success": "åˇĨäŊœæĩæˆåŠŸæ›´æ–°", + "workflow_updated": "åˇĨäŊœæĩåˇ˛æ›´æ–°", "workflows": "åˇĨäŊœæĩ", "workflows_help_text": "åˇĨäŊœæĩå¯æ šæŽč§Ļå‘å’Œį­›é€‰æĄäģļč‡ĒåŠ¨æ‰§čĄŒéĄšį›Žæ“äŊœ", "wrong_pin_code": "é”™č¯¯įš„PIN᠁", - "x_of_total": "{x}/{total}", "year": "åš´", "years_ago": "{years, plural, one {#åš´} other {#åš´}}前", "yes": "是", "you_dont_have_any_shared_links": "æ‚¨æ˛Ąæœ‰äģģäŊ•å…ąäēĢ链æŽĨ", "your_wifi_name": "æ‚¨įš„ Wi-Fi åį§°", "zero_to_clear_rating": "按0æ¸…é™¤éĄšį›Žæ˜Ÿįē§", - "zoom_image": "įŧŠæ”žå›žåƒ" + "zoom_image": "įŧŠæ”žå›žåƒ", + "zoom_to_bounds": "įŧŠæ”žåˆ°čžšį•Œ" } diff --git a/i18n/zh_Hant.json b/i18n/zh_Hant.json index ec133e09a4..0140fe8bb8 100644 --- a/i18n/zh_Hant.json +++ b/i18n/zh_Hant.json @@ -10,31 +10,39 @@ "active": "處ᐆ䏭", "active_count": "處ᐆ䏭īŧš{count}", "activity": "動態", + "activity_changed": "å‹•æ…‹åˇ˛{enabled, select, true {å•Ÿį”¨} other {åœį”¨}}", "add": "加å…Ĩ", "add_a_description": "新åĸžčĒĒæ˜Ž", "add_a_location": "新åĸžåœ°éģž", "add_a_name": "新åĸžå§“名", "add_a_title": "新åĸžæ¨™éĄŒ", "add_action": "新åĸžå‹•äŊœ", + "add_action_description": "按一下äģĨ新åĸžčĻåŸˇčĄŒįš„å‹•äŊœ", "add_assets": "新åĸžé …į›Ž", "add_birthday": "新åĸžį”Ÿæ—Ĩ", "add_endpoint": "新åĸžį̝éģž", "add_exclusion_pattern": "新åĸžæŽ’é™¤æ¨Ąåŧ", + "add_filter": "新åĸžį¯Šé¸å™¨", + "add_filter_description": "按一下äģĨ新åĸžį¯Šé¸æĸäģļ", "add_location": "新åĸžåœ°éģž", + "add_more_users": "新åĸžå…ļäģ–äŊŋᔍ者", "add_partner": "新åĸžčĻĒ友", + "add_path": "新åĸžčˇ¯åž‘", "add_photos": "加å…Ĩᛏቇ", - "add_step": "新åĸžæ­Ĩ驟", - "add_tag": "新åĸžæ¨™įą¤", + "add_tag": "加å…Ĩæ¨™įą¤", "add_to": "加å…Ĩ臺â€Ļ", "add_to_album": "加å…Ĩåˆ°į›¸į°ŋ", "add_to_album_bottom_sheet_added": "åˇ˛æ–°åĸžč‡ŗ {album}", "add_to_album_bottom_sheet_already_exists": "厞圍 {album} 中", "add_to_album_bottom_sheet_some_local_assets": "į„Ąæŗ•å°‡éƒ¨åˆ†æœŦæŠŸé …į›Žæ–°åĸžč‡ŗį›¸į°ŋ", + "add_to_album_toggle": "é¸å–į›¸į°ŋ {album}", "add_to_albums": "加å…Ĩᛏį°ŋ", "add_to_albums_count": "將 ({count}) å€‹é …į›ŽåŠ å…Ĩᛏį°ŋ", "add_to_bottom_bar": "新åĸžåˆ°", + "add_to_shared_album": "新åĸžč‡ŗå…ąäēĢᛏį°ŋ", "add_upload_to_stack": "新åĸžä¸Šå‚ŗåˆ°å †į–Š", "add_url": "新åĸž URL", + "add_workflow_step": "新åĸžåˇĨäŊœæĩį¨‹æ­Ĩ驟", "added_to_archive": "į§ģč‡ŗå°å­˜", "added_to_favorites": "加å…Ĩæ”ļ藏", "added_to_favorites_count": "厞將 {count, number} å€‹é …į›ŽåŠ å…Ĩæ”ļ藏", @@ -46,7 +54,7 @@ "authentication_settings_description": "įŽĄį†å¯†įĸŧ、OAuth 與å…ļäģ–éŠ—č­‰č¨­åŽš", "authentication_settings_disable_all": "您įĸē厚čĻåœį”¨æ‰€æœ‰į™ģå…Ĩæ–šåŧå—ŽīŧŸé€™å°‡å°Žč‡´åŽŒå…¨į„Ąæŗ•į™ģå…Ĩ。", "authentication_settings_reenable": "åĻ‚éœ€é‡æ–°å•Ÿį”¨īŧŒčĢ‹äŊŋᔍ äŧ翜å™¨æŒ‡äģ¤ã€‚", - "background_task_job": "čƒŒæ™¯äģģ務", + "background_task_job": "čƒŒæ™¯åˇĨäŊœ", "backup_database": "åģēįĢ‹čŗ‡æ–™åēĢ備äģŊ", "backup_database_enable_description": "å•Ÿį”¨čŗ‡æ–™åēĢ備äģŊ", "backup_keep_last_amount": "äŋį•™å…ˆå‰å‚™äģŊįš„æ•¸é‡", @@ -55,7 +63,7 @@ "backup_onboarding_3_description": "æ‚¨čŗ‡æ–™įš„į¸Ŋ備äģŊäģŊ數īŧŒåŒ…æ‹Ŧ原始æĒ”æĄˆåœ¨å…§ã€‚é€™åŒ…æ‹Ŧ 1 äģŊį•°åœ°å‚™äģŊ與 2 äģŊæœŦ抟副æœŦ。", "backup_onboarding_description": "åģēč­°æŽĄį”¨ 3-2-1 備äģŊį­–į•Ĩ 來äŋč­ˇæ‚¨įš„čŗ‡æ–™ã€‚æ‚¨æ‡‰äŋį•™åˇ˛ä¸Šå‚ŗįš„ᛏቇ/åŊąį‰‡å‰¯æœŦīŧŒäģĨ及 Immich čŗ‡æ–™åēĢīŧŒäģĨåģēįĢ‹åŽŒæ•´įš„å‚™äģŊæ–šæĄˆã€‚", "backup_onboarding_footer": "更多備äģŊ Immich čŗ‡č¨ŠīŧŒčĢ‹åƒč€ƒ čĒĒæ˜Žæ–‡äģļ。", - "backup_onboarding_parts_title": "3-2-1 備äģŊ包åĢīŧš", + "backup_onboarding_parts_title": "éĩåžžå‚™äģŊ原則 3-2-1īŧš", "backup_onboarding_title": "備äģŊ", "backup_settings": "čŗ‡æ–™åēĢ備äģŊč¨­åŽš", "backup_settings_description": "įŽĄį†čŗ‡æ–™åēĢ備äģŊč¨­åŽšã€‚", @@ -73,56 +81,40 @@ "cron_expression_description": "äŊŋᔍ Cron æ ŧåŧč¨­åŽšæŽƒæé–“éš”ã€‚æ›´å¤ščŗ‡č¨ŠčĢ‹åƒé–ą Crontab Guru", "cron_expression_presets": "Cron 表達åŧé č¨­å€ŧ", "disable_login": "åœį”¨į™ģå…Ĩ", - "download_csv": "下čŧ‰ CSV æĒ”æĄˆ", - "duplicate_detection_job_description": "é‡å°čŗ‡į”ĸåŸˇčĄŒæŠŸå™¨å­¸įŋ’äģĨåĩæ¸Ŧᛏäŧŧåœ–į‰‡ã€‚éœ€äžčŗ´ã€Œæ™ē慧搜尋」功čƒŊ", + "duplicate_detection_job_description": "䞝靠æ™ēæ…§æœå°‹ã€‚å°é …į›ŽåŸˇčĄŒæŠŸå™¨å­¸įŋ’䞆åĩæ¸Ŧᛏäŧŧåœ–į‰‡", "exclusion_pattern_description": "æŽ’é™¤æ¨Ąåŧå¯čŽ“æ‚¨åœ¨æŽƒæåĒ’éĢ”åēĢæ™‚åŋŊį•Ĩį‰šåŽšæĒ”æĄˆčˆ‡čŗ‡æ–™å¤žã€‚č‹Ĩ某äē›čŗ‡æ–™å¤žåŒ…åĢæ‚¨ä¸æƒŗåŒ¯å…Ĩįš„æĒ”æĄˆīŧˆäž‹åĻ‚ RAW æĒ”īŧ‰īŧŒæ­¤åŠŸčƒŊå°‡éžå¸¸æœ‰į”¨ã€‚", "export_config_as_json_description": "å°‡į›Žå‰įŗģįĩąč¨­åŽšä¸‹čŧ‰į‚ē JSON æĒ”æĄˆ", "external_libraries_page_description": "įŽĄį†å¤–éƒ¨åĒ’éĢ”åēĢ頁éĸ", "face_detection": "臉孔åĩæ¸Ŧ", - "face_detection_description": "äŊŋį”¨æŠŸå™¨å­¸įŋ’åĩæ¸Ŧé …į›Žä¸­įš„č‡‰å­”ã€‚å°æ–ŧåŊąį‰‡īŧŒåƒ…æœƒåˆ†æžį¸Žåœ–ã€‚ã€Œé‡æ–°æ•´į†ã€æœƒé‡æ–°č™•į†æ‰€æœ‰é …į›Žīŧ›ã€Œé‡č¨­ã€å‰‡æœƒéĄå¤–æ¸…é™¤į›Žå‰įš„č‡‰å­”čŗ‡æ–™īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€æœƒå°‡å°šæœĒč™•į†įš„é …į›ŽåŠ å…ĨäŊ‡åˆ—ã€‚åŽŒæˆã€Œč‡‰å­”åĩæ¸Ŧ」垌īŧŒåĩæ¸Ŧåˆ°įš„č‡‰å­”å°‡åŠ å…Ĩã€Œč‡‰å­”čž¨č­˜ã€æŽ’į¨‹īŧŒä¸Ļæ­¸éĄžč‡ŗįžæœ‰æˆ–æ–°įš„äēēį‰Šįž¤įĩ„。", - "facial_recognition_job_description": "將åĩæ¸Ŧåˆ°įš„č‡‰å­”æ­¸éĄžį‚ēäēēį‰Šã€‚æ­¤æ­ĨéŠŸæœƒåœ¨č‡‰å­”åĩæ¸ŦåŽŒæˆåžŒåŸˇčĄŒã€‚ã€Œé‡č¨­ã€æœƒé‡æ–°å°æ‰€æœ‰č‡‰å­”é€˛čĄŒåˆ†įž¤īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€å‰‡æœƒå°‡å°šæœĒ指洞äēēį‰Šįš„č‡‰å­”åŠ å…ĨäŊ‡åˆ—。", + "face_detection_description": "äŊŋį”¨æŠŸå™¨å­¸įŋ’åĩæ¸Ŧé …į›Žä¸­įš„č‡‰å­”ã€‚å°æ–ŧåŊąį‰‡īŧŒåƒ…æœƒåˆ†æžį¸Žåœ–ã€‚ã€Œé‡æ–°æ•´į†ã€æœƒé‡æ–°č™•į†æ‰€æœ‰é …į›Žīŧ›ã€Œé‡č¨­ã€å‰‡æœƒéĄå¤–æ¸…é™¤į›Žå‰įš„č‡‰å­”čŗ‡æ–™īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€æœƒå°‡å°šæœĒč™•į†įš„é …į›ŽåŠ å…Ĩåēåˆ—ã€‚åŽŒæˆã€Œč‡‰å­”åĩæ¸Ŧ」垌īŧŒåĩæ¸Ŧåˆ°įš„č‡‰å­”å°‡åŠ å…Ĩã€Œč‡‰å­”čž¨č­˜ã€æŽ’į¨‹īŧŒä¸Ļæ­¸éĄžč‡ŗįžæœ‰æˆ–æ–°įš„äēēį‰Šįž¤įĩ„。", + "facial_recognition_job_description": "將åĩæ¸Ŧåˆ°įš„č‡‰å­”æ­¸éĄžį‚ēäēēį‰Šã€‚æ­¤æ­ĨéŠŸæœƒåœ¨č‡‰å­”åĩæ¸ŦåŽŒæˆåžŒåŸˇčĄŒã€‚ã€Œé‡č¨­ã€æœƒé‡æ–°å°æ‰€æœ‰č‡‰å­”é€˛čĄŒåˆ†įž¤īŧ›ã€ŒåŠ å…ĨæŽ’į¨‹ã€å‰‡æœƒå°‡å°šæœĒ指洞äēēį‰Šįš„č‡‰å­”åŠ å…Ĩåēåˆ—。", "failed_job_command": "{job} äģģå‹™įš„ {command} 指äģ¤åŸˇčĄŒå¤ąæ•—", "force_delete_user_warning": "č­Ļ告īŧšé€™å°‡įĢ‹åŗåˆĒ除äŊŋį”¨č€…åŠå…￉€æœ‰é …į›Žã€‚æ­¤å‹•äŊœį„Ąæŗ•垊原īŧŒä¸”į„Ąæŗ•æ‰žå›žåˇ˛åˆĒé™¤įš„æĒ”æĄˆã€‚", "image_format": "æ ŧåŧ", "image_format_description": "WebP čƒŊį”ĸį”Ÿį›¸å°æ–ŧ JPEG æ›´å°įš„æĒ”æĄˆīŧŒäŊ†įˇ¨įĸŧ速åēĻčŧƒæ…ĸ。", - "image_fullsize_description": "į§ģé™¤čŠŽé‡‹čŗ‡æ–™įš„å¤§å°ē寸åŊąåƒīŧŒåœ¨æ”žå¤§åœ–į‰‡æ™‚äŊŋᔍ", + "image_fullsize_description": "į§ģ除中įšŧčŗ‡æ–™įš„å¤§å°ē寸åŊąåƒīŧŒåœ¨æ”žå¤§åœ–į‰‡æ™‚äŊŋᔍ", "image_fullsize_enabled": "å•Ÿį”¨å¤§å°ē寸åŊąåƒį”ĸį”Ÿ", - "image_fullsize_enabled_description": "į‚ē非įļ˛é į›¸åŽšæ ŧåŧį”ĸį”Ÿå¤§å°ēå¯¸į›¸į‰‡ã€‚å•Ÿį”¨ã€ŒååĨŊ內åĩŒé čĻŊ」時īŧŒįŗģįĩąå°‡į›´æŽĨäŊŋᔍ內åĩŒé čĻŊ而不進行čŊ‰įĸŧīŧŒä¸åŊąéŸŋ JPEG į­‰įļ˛é į›¸åŽšæ ŧåŧã€‚", + "image_fullsize_enabled_description": "į‚ē非įļ˛é å‹å–„æ ŧåŧį”ĸį”Ÿå¤§å°ēå¯¸į›¸į‰‡ã€‚å•Ÿį”¨ã€ŒååĨŊ內åĩŒé čĻŊ」時īŧŒįŗģįĩąå°‡į›´æŽĨäŊŋᔍ內åĩŒé čĻŊ而不進行čŊ‰įĸŧīŧŒä¸åŊąéŸŋ JPEG į­‰įļ˛é å‹å–„æ ŧåŧã€‚", "image_fullsize_quality_description": "大å°ē寸åŊąåƒå“čŗĒīŧŒį¯„圍į‚ē 1 到 100。數å€ŧčļŠéĢ˜å“čŗĒčļŠåĨŊīŧŒäŊ†æĒ”æĄˆä🿜ƒčļŠå¤§ã€‚", "image_fullsize_title": "大å°ē寸åŊąåƒč¨­åޚ", "image_prefer_embedded_preview": "偏åĨŊ內åĩŒé čĻŊ", "image_prefer_embedded_preview_setting_description": "åœ¨å¯į”¨æ™‚īŧŒå°‡ RAW į›¸į‰‡ä¸­įš„å…§åĩŒé čĻŊäŊœį‚ēåŊąåƒč™•į†įš„čŧ¸å…Ĩ來æēã€‚é›–į„ļ這čƒŊčŽ“éƒ¨åˆ†į›¸į‰‡č‰˛åŊŠæ›´æē–įĸēīŧŒäŊ†é čĻŊ品čŗĒ取æąēæ–ŧį›¸æŠŸīŧŒä¸”åŊąåƒå¯čƒŊ會å‡ēįžčŧƒå¤šåŖ“į¸Žį‘•į–ĩ。", "image_prefer_wide_gamut": "偏åĨŊåģŖč‰˛åŸŸ", "image_prefer_wide_gamut_setting_description": "äŊŋᔍ Display P3 čŖŊäŊœį¸Žåœ–。這čƒŊ更åĨŊ地äŋį•™åģŖč‰˛åŸŸåŊąåƒįš„鎎蹔åēĻīŧŒäŊ†åœ¨čˆŠčŖįŊŽčˆ‡čˆŠį‰ˆį€čĻŊ器上īŧŒåŊąåƒå‘ˆįžįš„æ•ˆæžœå¯čƒŊ會有所不同。sRGB åŊąåƒå°‡äŋæŒį‚ē sRGBīŧŒäģĨéŋå…č‰˛åŊŠåį§ģ。", - "image_preview_description": "䏭ᭉå°ē寸åŊąåƒīŧˆä¸åĢčŠŽé‡‹čŗ‡æ–™īŧ‰īŧŒį”¨æ–ŧæĒĸčĻ–å–Žä¸€é …į›Žčˆ‡æŠŸå™¨å­¸įŋ’", + "image_preview_description": "䏭ᭉå°ē寸åŊąåƒīŧˆä¸åĢ中įšŧčŗ‡æ–™īŧ‰īŧŒį”¨æ–ŧæĒĸčĻ–å–Žä¸€é …į›Žčˆ‡æŠŸå™¨å­¸įŋ’", "image_preview_quality_description": "預čĻŊ品čŗĒį¯„åœį‚ē 1 到 100。數å€ŧčļŠéĢ˜å“čŗĒčļŠåĨŊīŧŒäŊ†æĒ”æĄˆä🿜ƒæ›´å¤§īŧŒä¸Ļ可čƒŊ降äŊŽæ‡‰į”¨į¨‹åŧįš„回應速åēĻã€‚č¨­åŽšéŽäŊŽįš„æ•¸å€ŧ可čƒŊ會åŊąéŸŋ抟器學įŋ’įš„å“čŗĒ。", "image_preview_title": "預čĻŊč¨­åŽš", - "image_progressive": "æŧ¸é€˛åŧ", - "image_progressive_description": "對 JPEG åŊąåƒé€˛čĄŒæŧ¸é€˛åŧįˇ¨įĸŧīŧŒäģĨ達成æŧ¸é€˛åŧčŧ‰å…ĨéĄ¯į¤ē。這不會åŊąéŸŋ WebP åŊąåƒã€‚", + "image_progressive": "逐æ­Ĩ", + "image_progressive_description": "對 JPEG åŊąåƒé€˛čĄŒæŧ¸é€˛åŧįˇ¨įĸŧīŧŒäģĨå¯Ļįžæŧ¸é€˛åŧčŧ‰å…ĨéĄ¯į¤ē。這不會åŊąéŸŋ WebP åŊąåƒã€‚", "image_quality": "品čŗĒ", "image_resolution": "č§ŖæžåēĻ", "image_resolution_description": "čŧƒéĢ˜įš„č§ŖæžåēĻčƒŊäŋį•™æ›´å¤šį´°į¯€īŧŒäŊ†įˇ¨įĸŧæ™‚é–“æœƒæ›´é•ˇã€æĒ”æĄˆå¤§å°æœƒæ›´å¤§īŧŒä¸Ļ可čƒŊ降äŊŽæ‡‰į”¨į¨‹åŧįš„回應速åēĻ。", "image_settings": "åœ–į‰‡č¨­åŽš", "image_settings_description": "įŽĄį†į”ĸį”Ÿįš„åŊąåƒå“čŗĒčˆ‡č§ŖæžåēĻ", - "image_thumbnail_description": "厞į§ģé™¤čŠŽé‡‹čŗ‡æ–™įš„å°åž‹į¸Žåœ–īŧŒį”¨æ–ŧæĒĸčĻ–å¤šåŧĩᛏቇīŧˆåĻ‚ä¸ģ時間čģ¸īŧ‰", + "image_thumbnail_description": "į§ģ除中įšŧčŗ‡æ–™įš„å°åž‹į¸Žåœ–īŧŒäģĨᔍæ–ŧæĒĸčĻ–å¤§é‡į›¸į‰‡æ™‚äŊŋᔍīŧŒäž‹åĻ‚ä¸ģ時間čģ¸", "image_thumbnail_quality_description": "į¸Žåœ–å“čŗĒį¯„åœį‚ē 1 到 100。數å€ŧčļŠéĢ˜å“čŗĒčļŠåĨŊīŧŒäŊ†æĒ”æĄˆä🿜ƒæ›´å¤§īŧŒä¸Ļ可čƒŊ降äŊŽæ‡‰į”¨į¨‹åŧįš„回應速åēĻ。", "image_thumbnail_title": "į¸Žåœ–č¨­åŽš", "import_config_from_json_description": "é€éŽä¸Šå‚ŗ JSON č¨­åŽšæĒ”匯å…Ĩįŗģįĩąč¨­åޚ", - "integrity_checks_checksum_files": "æ ĄéŠ—æĒ”æĄˆ", - "integrity_checks_checksum_files_description": "č¨­åŽšæ ĄéŠ—å’ŒæĒĸæŸĨ", - "integrity_checks_checksum_files_enable_description": "å•Ÿį”¨æ ĄéŠ—å’ŒæĒĸæŸĨ", - "integrity_checks_checksum_files_percentage_limit": "į™žåˆ†æ¯”é™åˆļ", - "integrity_checks_checksum_files_percentage_limit_description": "č¨­åŽšæ¯å€‹å€é–“å…§īŧŒæ ĄéŠ—å’ŒæĒĸ銗所čƒŊåŸˇčĄŒįš„æœ€å¤§į™žåˆ†æ¯”īŧŒæ•¸å€ŧį¯„åœä싿–ŧ 0.01 到 1 䚋間。", - "integrity_checks_checksum_files_time_limit": "時間限åˆļ", - "integrity_checks_checksum_files_time_limit_description": "č¨­åŽšæ¯å€‹é€ąæœŸä¸­īŧŒæ ĄéŠ—å’ŒæĒĸæŸĨįš„æœ€é•ˇåŸˇčĄŒæ™‚é–“ã€‚īŧˆæ¯Ģį§’īŧ‰", - "integrity_checks_missing_files": "éēå¤ąįš„æĒ”æĄˆ", - "integrity_checks_missing_files_description": "č¨­åŽšåŸˇčĄŒé ģįŽ‡īŧŒä¸Ļå•Ÿį”¨æˆ–åœį”¨éēå¤ąæĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_missing_files_enable_description": "å•Ÿį”¨éēå¤ąæĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_settings": "厌整性æĒĸæŸĨ", - "integrity_checks_settings_description": "įŽĄį†åŽŒæ•´æ€§æĒĸæŸĨ間隔", - "integrity_checks_untracked_files": "æœĒčŋŊčš¤įš„æĒ”æĄˆ", - "integrity_checks_untracked_files_description": "č¨­åŽšé ģįŽ‡īŧŒä¸Ļå•Ÿį”¨æˆ–åœį”¨æœĒčŋŊ蚤æĒ”æĄˆæĒĸæŸĨ", - "integrity_checks_untracked_files_enable_description": "å•Ÿį”¨æœĒčŋŊ蚤æĒ”æĄˆæĒĸæŸĨ", "job_concurrency": "{job} ä¸ĻčĄŒæ•¸", "job_created": "厞åģēįĢ‹äģģ務", "job_not_concurrency_safe": "æ­¤äģģ務不支援ä¸ĻčĄŒåŸˇčĄŒã€‚", @@ -145,7 +137,7 @@ "library_tasks_description": "掃描外部åĒ’éĢ”åēĢäģĨ尋扞新åĸžæˆ–čŽŠæ›´įš„é …į›Ž", "library_updated": "更新åĒ’éĢ”åēĢ", "library_watching_enable_description": "į›ŖæŽ§å¤–éƒ¨åĒ’éĢ”åēĢįš„æĒ”æĄˆčŽŠåŒ–", - "library_watching_settings": "åĒ’éĢ”åēĢį›ŖæŽ§ [å¯Ļ銗性]", + "library_watching_settings": "åĒ’éĢ”åēĢį›ŖæŽ§[å¯Ļ銗性]", "library_watching_settings_description": "č‡Ēå‹•į›ŖæŽ§æĒ”æĄˆįš„čŽŠåŒ–", "logging_enable_description": "å•Ÿį”¨į´€éŒ„åŠŸčƒŊ", "logging_level_description": "å•Ÿį”¨æ™‚įš„į´€éŒ„åą¤į´šã€‚", @@ -168,20 +160,20 @@ "machine_learning_facial_recognition": "äēē臉辨識", "machine_learning_facial_recognition_description": "åĩæ¸Ŧã€čž¨č­˜ä¸Ļå°åœ–į‰‡ä¸­įš„č‡‰å­”åˆ†éĄž", "machine_learning_facial_recognition_model": "äēēč‡‰čž¨č­˜æ¨Ąåž‹", - "machine_learning_facial_recognition_model_description": "æ¨Ąåž‹é †åēį”ąå¤§č‡ŗå°æŽ’列。čŧƒå¤§įš„æ¨Ąåž‹é€ŸåēĻčŧƒæ…ĸ且äŊ”ᔍčŧƒå¤šč¨˜æ†ļéĢ”īŧŒäŊ†įĩæžœčŧƒäŊŗã€‚čĢ‹æŗ¨æ„īŧŒæ›´æ›æ¨Ąåž‹åžŒåŋ…須對所有åŊąåƒé‡æ–°åŸˇčĄŒã€Œč‡‰å­”åĩæ¸Ŧ」äģģ務。", + "machine_learning_facial_recognition_model_description": "æ¨Ąåž‹é †åēį”ąå¤§č‡ŗå°æŽ’列。čŧƒå¤§įš„æ¨Ąåž‹é€ŸåēĻčŧƒæ…ĸ且äŊ”ᔍčŧƒå¤šč¨˜æ†ļéĢ”īŧŒäŊ†æ•ˆæžœčŧƒäŊŗã€‚čĢ‹æŗ¨æ„īŧŒæ›´æ›æ¨Ąåž‹åžŒåŋ…須對所有åŊąåƒé‡æ–°åŸˇčĄŒã€Œč‡‰å­”åĩæ¸Ŧ」äģģ務。", "machine_learning_facial_recognition_setting": "å•Ÿį”¨äēē臉辨識", "machine_learning_facial_recognition_setting_description": "č‹Ĩåœį”¨īŧŒåŊąåƒå°‡ä¸æœƒé€˛čĄŒäēēč‡‰čž¨č­˜įˇ¨įĸŧīŧŒä¸”「æŽĸį´ĸ」頁éĸįš„ã€Œäēēį‰Šã€å€åĄŠå°‡ä¸æœƒéĄ¯į¤ēäģģäŊ•內厚。", "machine_learning_max_detection_distance": "åĩæ¸Ŧ距é›ĸ上限", "machine_learning_max_detection_distance_description": "č‹Ĩå…ŠåŧĩåŊąåƒé–“įš„čˇé›ĸ小æ–ŧ此將čĸĢåˆ¤æ–ˇį‚ēį›¸åŒīŧŒį¯„圍į‚ē 0.001-0.1。數å€ŧčļŠé̘čƒŊåĩæ¸Ŧ到čļŠå¤šé‡č¤‡īŧŒäŊ†äšŸæ›´æœ‰å¯čƒŊčĒ¤åˆ¤ã€‚", "machine_learning_max_recognition_distance": "辨識距é›ĸ上限", - "machine_learning_max_recognition_distance_description": "å…Šåŧĩ臉孔čĸĢčĻ–į‚ē同一äēēį‰Šįš„æœ€å¤§čˇé›ĸīŧŒį¯„圍į‚ē 0 臺 2。降äŊŽæ­¤æ•¸å€ŧ可éŋ免將不同äēēį‰Šæ¨™č¨˜į‚ē同一äēēīŧ›æéĢ˜æ­¤æ•¸å€ŧ則可éŋ免將同一äēēį‰Šæ¨™č¨˜į‚ēå…Šå€‹ä¸åŒįš„äēē。čĢ‹æŗ¨æ„īŧŒåˆäŊĩäēēį‰Šæ¯”æ‹†é–‹äēēį‰Šæ›´åŽšæ˜“īŧŒå› æ­¤åģēč­°åœ¨å¯čƒŊįš„æƒ…æŗä¸‹å°‡æ­¤é–€æĒģå€ŧč¨­åŽšåž—čŧƒäŊŽã€‚", + "machine_learning_max_recognition_distance_description": "å…Šåŧĩ臉孔čĸĢčĻ–į‚ē同一äēēį‰Šįš„æœ€å¤§čˇé›ĸīŧŒį¯„圍į‚ē 0 臺 2。降äŊŽæ­¤æ•¸å€ŧ可éŋ免將不同äēēį‰Šæ¨™č¨˜į‚ē同一äēēīŧ›æéĢ˜æ­¤æ•¸å€ŧ則可éŋ免將同一äēēį‰Šæ¨™č¨˜į‚ēå…Šå€‹ä¸åŒįš„äēē。čĢ‹æŗ¨æ„īŧŒåˆäŊĩäēēį‰Šæ¯”æ‹†åˆ†äēēį‰Šæ›´åŽšæ˜“īŧŒå› æ­¤åģēč­°åœ¨å¯čƒŊįš„æƒ…æŗä¸‹å°‡æ­¤é–€æĒģå€ŧč¨­åŽšåž—čŧƒäŊŽã€‚", "machine_learning_min_detection_score": "最äŊŽåĩæ¸Ŧ分數", "machine_learning_min_detection_score_description": "臉孔åĩæ¸Ŧįš„æœ€äŊŽäŋĄåŋƒåˆ†æ•¸īŧŒį¯„圍į‚ē 0 - 1。čŧƒäŊŽįš„æ•¸å€ŧ會åĩæ¸Ŧåˆ°æ›´å¤šč‡‰å­”īŧŒäŊ†å¯čƒŊå°Žč‡´čĒ¤åˆ¤ã€‚", "machine_learning_min_recognized_faces": "最äŊŽč‡‰éƒ¨čž¨č­˜æ•¸é‡", "machine_learning_min_recognized_faces_description": "åģēįĢ‹æ–°äēēį‰Šæ‰€éœ€įš„æœ€äŊŽåˇ˛čž¨č­˜č‡‰å­”數量。提éĢ˜æ­¤æ•¸å€ŧå¯čŽ“č‡‰å­”čž¨č­˜æ›´į˛žįĸēīŧŒäŊ†åŒæ™‚會åĸžåР臉孔æœĒčĸĢæŒ‡æ´žįĩĻäģģäŊ•äēēį‰Šįš„å¯čƒŊ性。", "machine_learning_ocr": "æ–‡å­—čž¨č­˜(OCR)", "machine_learning_ocr_description": "äŊŋį”¨æŠŸå™¨å­¸įŋ’čž¨č­˜åŊąåƒä¸­įš„æ–‡å­—", - "machine_learning_ocr_enabled": "å•Ÿį”¨ OCR", + "machine_learning_ocr_enabled": "å•Ÿį”¨OCR", "machine_learning_ocr_enabled_description": "č‹Ĩåœį”¨īŧŒåŊąåƒå°‡ä¸æœƒé€˛čĄŒæ–‡å­—čž¨č­˜ã€‚", "machine_learning_ocr_max_resolution": "æœ€å¤§č§ŖæžåēĻ", "machine_learning_ocr_max_resolution_description": "č§ŖæžåēĻé̘æ–ŧæ­¤å€ŧįš„é čĻŊåŊąåƒå°‡åœ¨äŋæŒé•ˇå¯Ŧæ¯”įš„æƒ…æŗä¸‹čĒŋ整大小。數å€ŧčļŠé̘čē–įĸēīŧŒäŊ†č™•į†æ™‚é–“æ›´é•ˇä¸”æœƒäŊ”į”¨æ›´å¤šč¨˜æ†ļéĢ”ã€‚", @@ -189,7 +181,7 @@ "machine_learning_ocr_min_detection_score_description": "文字åĩæ¸Ŧįš„æœ€äŊŽäŋĄåŋƒåˆ†æ•¸īŧŒį¯„圍į‚ē 0 - 1。čŧƒäŊŽįš„æ•¸å€ŧ會åĩæ¸Ŧ到更多文字īŧŒäŊ†å¯čƒŊå°Žč‡´čĒ¤åˆ¤ã€‚", "machine_learning_ocr_min_recognition_score": "最äŊŽčž¨č­˜åˆ†æ•¸", "machine_learning_ocr_min_score_recognition_description": "厞åĩæ¸Ŧæ–‡å­—įš„æœ€äŊŽčž¨č­˜äŋĄåŋƒåˆ†æ•¸īŧŒį¯„圍į‚ē 0 - 1。čŧƒäŊŽįš„æ•¸å€ŧæœƒčž¨č­˜å‡ē更多文字īŧŒäŊ†å¯čƒŊå°Žč‡´čĒ¤åˆ¤ã€‚", - "machine_learning_ocr_model": "OCR æ¨Ąåž‹", + "machine_learning_ocr_model": "OCRæ¨Ąåž‹", "machine_learning_ocr_model_description": "äŧ翜å™¨æ¨Ąåž‹æ¯”čĄŒå‹•čŖįŊŽæ¨Ąåž‹æ›´æē–įĸēīŧŒäŊ†č™•į†æ™‚é–“čŧƒé•ˇä¸”會äŊ”į”¨æ›´å¤šč¨˜æ†ļéĢ”ã€‚", "machine_learning_settings": "抟器學įŋ’設åޚ", "machine_learning_settings_description": "įŽĄį†æŠŸå™¨å­¸įŋ’įš„åŠŸčƒŊå’Œč¨­åŽš", @@ -198,25 +190,9 @@ "machine_learning_smart_search_enabled": "å•Ÿį”¨æ™ē慧搜尋", "machine_learning_smart_search_enabled_description": "č‹Ĩåœį”¨īŧŒåŊąåƒå°‡ä¸æœƒé€˛čĄŒæ™ēæ…§æœå°‹įˇ¨įĸŧ。", "machine_learning_url_description": "抟器學įŋ’äŧ翜å™¨įš„ URL。č‹Ĩ提䞛多個 URLīŧŒįŗģįĩ࿜ƒäžåēé€ä¸€å˜—čŠĻīŧŒį›´åˆ°å…ļ中一č‡ē成功回應į‚ēæ­ĸīŧˆį”ąå‰åˆ°åžŒīŧ‰ã€‚æœĒå›žæ‡‰įš„äŧ翜å™¨å°‡čĸĢæšĢ時åŋŊį•ĨīŧŒį›´åˆ°å…ļé‡æ–°ä¸Šįˇšã€‚", - "maintenance_backup_management": "įŽĄį†å‚™äģŊ", "maintenance_delete_backup": "åˆĒ除備äģŊ", "maintenance_delete_backup_description": "æ­¤æĒ”æĄˆå°‡čĸĢæ°¸äš…åˆĒé™¤ä¸”į„Ąæŗ•åžŠåŽŸã€‚", "maintenance_delete_error": "åˆĒ除備äģŊå¤ąæ•—ã€‚", - "maintenance_integrity_check": "æĒĸæŸĨ", - "maintenance_integrity_check_all": "全部æĒĸæŸĨ", - "maintenance_integrity_checksum_mismatch": "æĒĸæŸĨįĸŧ不įŦĻ", - "maintenance_integrity_checksum_mismatch_description": "æĒ”æĄˆįš„į¸Ŋ和æĒĸæŸĨįĸŧčˆ‡čŗ‡æ–™åēĢįš„ä¸įŦĻ。", - "maintenance_integrity_checksum_mismatch_job": "æĒĸæŸĨ有åĻæĒĸæŸĨįĸŧ不įŦĻ", - "maintenance_integrity_checksum_mismatch_refresh_job": "é‡æ–°æ•´į†æĒĸæŸĨįĸŧ不įŦĻå ąå‘Š", - "maintenance_integrity_missing_file": "įŧēå¤ąæĒ”æĄˆ", - "maintenance_integrity_missing_file_description": "åˇ˛åœ¨čŗ‡æ–™åēĢ中čŋŊ蚤īŧŒäŊ†æĒ”æĄˆįŗģįĩąä¸­ä¸Ļä¸å­˜åœ¨įš„æĒ”æĄˆã€‚", - "maintenance_integrity_missing_file_job": "æĒĸæŸĨįŧēå¤ąæĒ”æĄˆ", - "maintenance_integrity_missing_file_refresh_job": "é‡æ–°æ•´į†įŧēå¤ąæĒ”æĄˆå ąå‘Š", - "maintenance_integrity_report": "åŽŒæ•´æ€§å ąå‘Š", - "maintenance_integrity_untracked_file": "æœĒčŋŊ蚤æĒ”æĄˆ", - "maintenance_integrity_untracked_file_description": "在 Immich įš„į›ŽéŒ„ä¸­īŧŒäŊ†æ˛’有äģģäŊ•į´€éŒ„įš„æĒ”æĄˆã€‚", - "maintenance_integrity_untracked_file_job": "æĒĸæŸĨæœĒčŋŊ蚤æĒ”æĄˆ", - "maintenance_integrity_untracked_file_refresh_job": "é‡æ–°æ•´į†æœĒčŋŊ蚤æĒ”æĄˆå ąå‘Š", "maintenance_restore_backup": "還原備äģŊ", "maintenance_restore_backup_description": "Immich įš„čŗ‡æ–™å°‡čĸĢæ¸…除īŧŒä¸Ļåžžé¸å–įš„å‚™äģŊ還原。在įšŧį猿“äŊœå‰īŧŒįŗģįĩ࿜ƒå…ˆåģēįĢ‹į›Žå‰įš„čŗ‡æ–™å‚™äģŊ。", "maintenance_restore_backup_different_version": "此備äģŊæ˜¯į”ąä¸åŒį‰ˆæœŦįš„ Immich 所åģēįĢ‹īŧ", @@ -247,12 +223,12 @@ "map_style_description": "地圖ä¸ģ題īŧˆstyle.jsonīŧ‰įš„įļ˛å€", "memory_cleanup_job": "回æ†ļæ¸…į†", "memory_generate_job": "į”ĸį”Ÿå›žæ†ļ", - "metadata_extraction_job": "æ“ˇå–čŠŽé‡‹čŗ‡æ–™", - "metadata_extraction_job_description": "åžžæ¯å€‹é …į›Žä¸­æ“ˇå–čŠŽé‡‹čŗ‡æ–™čŗ‡č¨ŠīŧŒäž‹åĻ‚ GPSã€č‡‰å­”čˆ‡č§ŖæžåēĻ", + "metadata_extraction_job": "æ“ˇå–ä¸­įšŧčŗ‡æ–™", + "metadata_extraction_job_description": "åžžæ¯å€‹é …į›Žä¸­æ“ˇå–ä¸­įšŧčŗ‡æ–™čŗ‡č¨ŠīŧŒäž‹åĻ‚ GPSã€č‡‰å­”čˆ‡č§ŖæžåēĻ", "metadata_faces_import_setting": "å•Ÿį”¨č‡‰å­”åŒ¯å…Ĩ", - "metadata_faces_import_setting_description": "åžžåŊąåƒ EXIF čŗ‡æ–™čˆ‡é™„åąŦæĒ”æĄˆåŒ¯å…Ĩ臉孔", - "metadata_settings": "čŠŽé‡‹čŗ‡æ–™č¨­åŽš", - "metadata_settings_description": "įŽĄį†čŠŽé‡‹čŗ‡æ–™č¨­åŽš", + "metadata_faces_import_setting_description": "åžžåŊąåƒ EXIF čŗ‡æ–™čˆ‡ Sidecar æĒ”æĄˆåŒ¯å…Ĩ臉孔", + "metadata_settings": "中įšŧčŗ‡æ–™č¨­åŽš", + "metadata_settings_description": "įŽĄį†ä¸­įšŧčŗ‡æ–™č¨­åŽš", "migration_job": "遡į§ģ", "migration_job_description": "å°‡é …į›Žčˆ‡č‡‰å­”į¸Žåœ–éˇį§ģč‡ŗæœ€æ–°įš„čŗ‡æ–™å¤žįĩæ§‹", "nightly_tasks_cluster_faces_setting_description": "對新åĩæ¸Ŧåˆ°įš„č‡‰å­”åŸˇčĄŒč‡‰å­”čž¨č­˜", @@ -281,7 +257,7 @@ "notification_email_password_description": "ᔍæ–ŧ與é›ģ子éƒĩäģļäŧ翜å™¨éŠ—č­‰įš„å¯†įĸŧ", "notification_email_port_description": "é›ģ子éƒĩäģļäŧ翜å™¨įš„逪æŽĨ埠īŧˆäž‹åĻ‚ 25、465 或 587īŧ‰", "notification_email_secure": "SMTPS", - "notification_email_secure_description": "äŊŋᔍ SMTPSīŧˆåŸēæ–ŧ TLS įš„ SMTPīŧ‰", + "notification_email_secure_description": "äŊŋᔍSMTPSīŧˆåŸēæ–ŧTLSįš„SMTPīŧ‰", "notification_email_sent_test_email_button": "傺送æ¸ŦčŠĻé›ģ子éƒĩäģļä¸Ļå„˛å­˜", "notification_email_setting_description": "寄送é›ģ子éƒĩäģļ通įŸĨįš„č¨­åŽš", "notification_email_test_email": "傺送æ¸ŦčŠĻé›ģ子éƒĩäģļ", @@ -291,33 +267,29 @@ "notification_enable_email_notifications": "å•Ÿį”¨é›ģ子éƒĩäģļ通įŸĨ", "notification_settings": "通įŸĨč¨­åŽš", "notification_settings_description": "įŽĄį†é€šįŸĨč¨­åŽšīŧŒåŒ…æ‹Ŧé›ģ子éƒĩäģļ", - "oauth_allow_insecure_requests": "å…č¨ąä¸å¯é įš„čĢ‹æą‚", - "oauth_allow_insecure_requests_description": "č­Ļ告īŧšé€™å€‹é¸é …æœƒåœį”¨ OAuth įš„ TLS æ†‘č­‰éŠ—č­‰čĢ‹æą‚īŧŒå¯čƒŊ會äŊŋäŊ æš´éœ˛æ–ŧ MITM æ”ģ擊。", "oauth_auto_launch": "č‡Ē動啟動", "oauth_auto_launch_description": "進å…Ĩį™ģå…Ĩ頁éĸ時īŧŒč‡Ē動啟動 OAuth į™ģå…Ĩæĩį¨‹", "oauth_auto_register": "č‡Ē動č¨ģ冊", "oauth_auto_register_description": "äŊŋᔍ OAuth į™ģå…Ĩ垌č‡Ē動č¨ģ冊新äŊŋᔍ者", "oauth_button_text": "按鈕文字", - "oauth_client_secret_description": "æŠŸå¯†į”¨æˆļįĢ¯įš„åŋ…åĄĢé …į›Žīŧ›č‹Ĩå…Ŧ開ᔍæˆļįĢ¯ä¸æ”¯æ´ PKCEīŧˆäģŖįĸŧä礿›įš„éЗ證金鑰īŧ‰īŧŒäēĻ須åĄĢå¯Ģ。", + "oauth_client_secret_description": "æŠŸå¯†į”¨æˆļįĢ¯įš„åŋ…åĄĢé …į›Žīŧ›č‹Ĩå…Ŧ開ᔍæˆļįĢ¯ä¸æ”¯æ´ PKCE (äģŖįĸŧä礿›įš„éЗ證金鑰)īŧŒäēĻ須åĄĢå¯Ģ。", "oauth_enable_description": "äŊŋᔍ OAuth į™ģå…Ĩ", - "oauth_end_session_url_description": "äŊŋᔍ者į™ģå‡ē時將å…ļ重新導向到此 URL。", "oauth_mobile_redirect_uri": "čĄŒå‹•įĢ¯é‡æ–°å°Žå‘ URI", - "oauth_mobile_redirect_uri_override": "čφå¯ĢčĄŒå‹•įĢ¯é‡æ–°å°Žå‘ URI", + "oauth_mobile_redirect_uri_override": "čĻ†č“‹čĄŒå‹•įĢ¯é‡æ–°å°Žå‘ URI", "oauth_mobile_redirect_uri_override_description": "į•ļ OAuth æäž›č€…ä¸å…č¨ąäŊŋį”¨čĄŒå‹•į̝ URIīŧˆäž‹åĻ‚ ''{callback}''īŧ‰æ™‚å•Ÿį”¨", - "oauth_prompt_description": "Prompt 參數īŧˆäž‹åĻ‚ select_account、login、consentīŧ‰", "oauth_role_claim": "č§’č‰˛åŽŖå‘Š", "oauth_role_claim_description": "æ šæ“šæ­¤åŽŖå‘Šįš„å­˜åœ¨īŧŒč‡Ē動授äēˆįŽĄį†å“ĄæŦŠé™ã€‚čŠ˛åŽŖå‘Šįš„å€ŧ可äģĨ是 'user' 或 'admin'。", "oauth_settings": "OAuth", "oauth_settings_description": "įŽĄį† OAuth į™ģå…Ĩč¨­åŽš", - "oauth_settings_more_details": "č‹ĨčĻįž­č§Ŗæ­¤åŠŸčƒŊįš„čŠŗį´°čŗ‡č¨ŠīŧŒčĢ‹åƒé–ą čĒĒæ˜Žæ–‡äģļ。", + "oauth_settings_more_details": "æŦ˛įž­č§Ŗæ­¤åŠŸčƒŊīŧŒčĢ‹åƒé–ą čĒĒæ˜Žæ›¸ã€‚", "oauth_storage_label_claim": "å„˛å­˜æ¨™įą¤åŽŖå‘Š", - "oauth_storage_label_claim_description": "č‡Ē動將äŊŋį”¨č€…įš„å„˛å­˜æ¨™įą¤č¨­åŽšį‚ēæ­¤åŽŖå‘Šäš‹å€ŧ。", + "oauth_storage_label_claim_description": "č‡Ē動將äŊŋį”¨č€…įš„å„˛å­˜æ¨™įą¤åŽšį‚ēæ­¤åŽŖå‘Šäš‹å€ŧ。", "oauth_storage_quota_claim": "å„˛å­˜é…éĄåŽŖå‘Š", - "oauth_storage_quota_claim_description": "č‡Ē動將äŊŋį”¨č€…įš„å„˛å­˜é…éĄč¨­åŽšį‚ēæ­¤åŽŖå‘Šäš‹å€ŧ。", + "oauth_storage_quota_claim_description": "č‡Ē動將äŊŋį”¨č€…įš„å„˛å­˜é…éĄåŽšį‚ēæ­¤åŽŖå‘Šäš‹å€ŧ。", "oauth_storage_quota_default": "é č¨­å„˛å­˜é…éĄīŧˆGiBīŧ‰", "oauth_storage_quota_default_description": "æœĒæäž›åŽŖå‘Šæ™‚æ‰€äŊŋį”¨įš„é…éĄīŧˆGiBīŧ‰ã€‚", "oauth_timeout": "čĢ‹æą‚é€žæ™‚", - "oauth_timeout_description": "čĢ‹æą‚é€žæ™‚īŧˆæ¯Ģį§’īŧ‰", + "oauth_timeout_description": "čĢ‹æą‚įš„é€žæ™‚æ™‚é–“īŧˆæ¯Ģį§’īŧ‰", "ocr_job_description": "äŊŋį”¨æŠŸå™¨å­¸įŋ’čž¨č­˜åŊąåƒä¸­įš„æ–‡å­—", "password_enable_description": "äŊŋᔍé›ģ子éƒĩäģļ和密įĸŧį™ģå…Ĩ", "password_settings": "密įĸŧį™ģå…Ĩ", @@ -325,14 +297,12 @@ "paths_validated_successfully": "æ‰€æœ‰čˇ¯åž‘éŠ—č­‰æˆåŠŸ", "person_cleanup_job": "æ¸…į†äēēį‰Š", "queue_details": "äŊ‡åˆ—čŗ‡č¨Š", - "queues": "äģģ務äŊ‡åˆ—", - "queues_page_description": "įŽĄį†å“Ąäģģ務åēåˆ—頁éĸ", + "queues": "äģģå‹™æŽ’į¨‹", + "queues_page_description": "åēåˆ—æŽ’į¨‹įŽĄį†į•Œéĸ", "quota_size_gib": "é…éĄå¤§å°īŧˆGiBīŧ‰", "refreshing_all_libraries": "æ­Ŗåœ¨é‡æ–°æ•´į†æ‰€æœ‰åĒ’éĢ”åēĢ", "registration": "įŽĄį†č€…č¨ģ冊", "registration_description": "į”ąæ–ŧ您是įŗģįĩąä¸Šįš„įŦŦ一äŊäŊŋᔍ者īŧŒæ‚¨å°‡čĸĢæŒ‡æ´žį‚ēįŗģįĩąįŽĄį†å“ĄīŧŒä¸Ļ負č˛ŦįŽĄį†į›¸é—œäē‹å‹™īŧŒåžŒįēŒįš„å…ļäģ–äŊŋį”¨č€…äšŸå°‡į”ąæ‚¨åģēįĢ‹ã€‚", - "release_channel_release_candidate": "候選į™ŧå¸ƒį‰ˆ", - "release_channel_stable": "įŠŠåŽšį‰ˆ", "remove_failed_jobs": "į§ģé™¤å¤ąæ•—äģģ務", "require_password_change_on_login": "čĻæą‚äŊŋį”¨č€…åœ¨éĻ–æŦĄį™ģå…Ĩæ™‚čŽŠæ›´å¯†įĸŧ", "reset_settings_to_default": "å°‡č¨­åŽšé‡č¨­į‚ē預設å€ŧ", @@ -341,7 +311,7 @@ "search_jobs": "搜尋äģģ務â€Ļ", "send_welcome_email": "å‚ŗé€æ­ĄčŋŽé›ģ子éƒĩäģļ", "server_external_domain_settings": "外部įļ˛åŸŸ", - "server_external_domain_settings_description": "外部čļ…逪įĩįš„įļ˛åŸŸ", + "server_external_domain_settings_description": "å…Ŧ開分äēĢ逪įĩįš„įļ˛åŸŸ", "server_public_users": "å…Ŧ開äŊŋᔍ者", "server_public_users_description": "將äŊŋį”¨č€…æ–°åĸžč‡ŗå…ąäēĢᛏį°ŋ時īŧŒæœƒåˆ—å‡ē所有äŊŋᔍ者īŧˆå§“åčˆ‡é›ģ子éƒĩäģļīŧ‰ã€‚č‹Ĩåœį”¨īŧŒäŊŋį”¨č€…æ¸…å–Žå°‡åƒ…äž›įŽĄį†å“ĄæŸĨįœ‹ã€‚", "server_settings": "äŧ翜å™¨č¨­åޚ", @@ -350,8 +320,8 @@ "server_welcome_message": "æ­ĄčŋŽč¨Šæ¯", "server_welcome_message_description": "在į™ģå…Ĩ頁éĸéĄ¯į¤ēįš„č¨Šæ¯ã€‚", "settings_page_description": "įŽĄį†č¨­åŽšé éĸ", - "sidecar_job": "附åąŦæĒ”æĄˆčŠŽé‡‹čŗ‡æ–™", - "sidecar_job_description": "åžžæĒ”æĄˆįŗģįĩąåĩæ¸Ŧ或同æ­Ĩ附åąŦæĒ”æĄˆčŠŽé‡‹čŗ‡æ–™", + "sidecar_job": "側æŽĨæĒ”æĄˆä¸­įšŧčŗ‡æ–™", + "sidecar_job_description": "åžžæĒ”æĄˆįŗģįĩąåĩæ¸Ŧ或同æ­Ĩ側æŽĨæĒ”æĄˆä¸­įšŧčŗ‡æ–™", "slideshow_duration_description": "每åŧĩåœ–į‰‡æ”žæ˜ įš„į§’æ•¸", "smart_search_job_description": "å°é …į›ŽåŸˇčĄŒæŠŸå™¨å­¸įŋ’äģĨ支援æ™ē慧搜尋", "storage_template_date_time_description": "æĒ”æĄˆįš„åģēįĢ‹æ™‚é–“æˆŗæœƒį”¨æ–ŧæ—ĨæœŸčˆ‡æ™‚é–“čŗ‡č¨Š", @@ -417,24 +387,16 @@ "transcoding_max_b_frames": "最大 B 嚀數", "transcoding_max_b_frames_description": "čŧƒéĢ˜įš„æ•¸å€ŧå¯æå‡åŖ“į¸Žæ•ˆįŽ‡īŧŒäŊ†æœƒé™äŊŽįˇ¨įĸŧ速åēĻ。在čŧƒčˆŠįš„čŖįŊŽä¸ŠīŧŒå¯čƒŊ與įĄŦéĢ”åŠ é€Ÿä¸į›¸åŽšã€‚0 äģŖčĄ¨åœį”¨ B 嚀īŧŒč€Œ -1 則會č‡Ēå‹•č¨­åŽšæ­¤æ•¸å€ŧ。", "transcoding_max_bitrate": "最大äŊå…ƒé€Ÿįއ", - "transcoding_max_bitrate_description": "č¨­åŽšæœ€å¤§äŊå…ƒįއčƒŊ讓æĒ”æĄˆå¤§å°æ›´įŠŠåŽšīŧŒäŊ†æœƒį¨åžŽįЧ቞品čŗĒ。720p ä¸‹įš„å…¸åž‹å€ŧį‚ēīŧšVP9 或 HEVC į‚ē 2600 kbit/sīŧŒH.264 į‚ē 4500 kbit/sã€‚č¨­į‚ē 0 å‰‡åœį”¨ã€‚č‹ĨæœĒ指厚喎äŊīŧŒįŗģįĩąå°‡é č¨­į‚ē kīŧˆåŗ kbit/sīŧ‰īŧ›å› æ­¤ 5000、5000k 與 5Mīŧˆåŗ Mbit/sīŧ‰æ˜¯į­‰æ•ˆįš„。", + "transcoding_max_bitrate_description": "č¨­åŽšæœ€å¤§äŊå…ƒįއčƒŊ讓æĒ”æĄˆå¤§å°æ›´įŠŠåŽšīŧŒäŊ†æœƒį¨åžŽįЧ቞品čŗĒ。720p ä¸‹įš„å…¸åž‹å€ŧį‚ēīŧšVP9 或 HEVC į‚ē 2600 kbit/sīŧŒH.264 į‚ē 4500 kbit/sã€‚č¨­į‚ē 0 å‰‡åœį”¨ã€‚č‹ĨæœĒ指厚喎äŊīŧŒįŗģįĩąå°‡é č¨­į‚ē k (åŗ kbit/s)īŧ›å› æ­¤ 5000、5000k 與 5M (åŗ Mbit/s) æ˜¯į­‰æ•ˆįš„ã€‚", "transcoding_max_keyframe_interval": "最大關éĩ嚀間隔", "transcoding_max_keyframe_interval_description": "č¨­åŽšé—œéĩåš€äš‹é–“įš„æœ€å¤§åš€čˇã€‚čŧƒäŊŽįš„æ•¸å€ŧ會降äŊŽåŖ“į¸Žæ•ˆįŽ‡īŧŒäŊ†å¯æ”šå–„莺čŊ‰æœå°‹æ™‚é–“ä¸Ļ提升éĢ˜å‹•æ…‹å ´æ™¯å“čŗĒ。0 į‚ēč‡Ēå‹•č¨­åŽšã€‚", "transcoding_optimal_description": "é̘æ–ŧį›Žæ¨™č§ŖæžåēĻæˆ–æ ŧåŧä¸åœ¨å¯æŽĨå—į¯„åœįš„åŊąį‰‡", "transcoding_policy": "čŊ‰įĸŧį­–į•Ĩ", "transcoding_policy_description": "č¨­åŽšåŊąį‰‡é€˛čĄŒčŊ‰įĸŧįš„æĸäģļ", - "transcoding_preferred_hardware_device": "偏åĨŊįš„įĄŦéĢ”čŖįŊŽ", + "transcoding_preferred_hardware_device": "éϖ遏įĄŦéĢ”čŖįŊŽ", "transcoding_preferred_hardware_device_description": "åƒ…éŠį”¨æ–ŧ VAAPI 和 QSVã€‚č¨­åŽšį”¨æ–ŧįĄŦéĢ”čŊ‰įĸŧįš„ dri ᝀéģžã€‚", "transcoding_preset_preset": "預設å€ŧīŧˆ-presetīŧ‰", "transcoding_preset_preset_description": "åŖ“į¸Žé€ŸåēĻ。čŧƒæ…ĸįš„é č¨­å€ŧ可į”ĸį”ŸéĢ”įŠčŧƒå°įš„æĒ”æĄˆīŧŒä¸Ļ在指厚äŊå…ƒįŽ‡æ™‚æå‡å“čŗĒ。VP9 會åŋŊį•Ĩé̘æ–ŧ「fasterã€įš„č¨­åŽšã€‚", - "transcoding_realtime": "åŗæ™‚čŊ‰įĸŧ [å¯Ļ銗性]", - "transcoding_realtime_description": "å…č¨ąåœ¨åŊąį‰‡ä¸˛æĩå‚ŗčŧ¸īŧˆæ’­æ”žīŧ‰įš„åŒæ™‚åŗæ™‚é€˛čĄŒčŊ‰įĸŧ。此功čƒŊ支援動態切換į•ĢčŗĒīŧŒäŊ†æ šæ“šäŧ翜å™¨æ•ˆčƒŊįš„ä¸åŒīŧŒå¯čƒŊæœƒå°Žč‡´čŧƒéĢ˜įš„æ’­æ”žåģļé˛æˆ–į•ĢéĸåĄé “ã€‚", - "transcoding_realtime_enabled": "å•Ÿį”¨åŗæ™‚čŊ‰įĸŧ", - "transcoding_realtime_enabled_description": "åĻ‚æžœåœį”¨īŧŒäŧ翜å™¨å°‡æ‹’įĩ•å•Ÿå‹•æ–°įš„åŗæ™‚čŊ‰įĸŧåˇĨäŊœéšŽæŽĩ。", - "transcoding_realtime_resolutions": "č§ŖæžåēĻ", - "transcoding_realtime_resolutions_description": "åŗæ™‚čŊ‰įĸŧæ‰€æäž›įš„č§ŖæžåēĻ。åĻ‚æžœäŧ翜å™¨į„Ąæŗ•äģĨčļŗå¤ åŋĢįš„é€ŸåēĻ進行čŊ‰įĸŧīŧŒäŊŋį”¨æ›´éĢ˜įš„č§ŖæžåēĻ可čƒŊæœƒå°Žč‡´æ’­æ”žå•éĄŒã€‚", - "transcoding_realtime_video_codecs": "åŊąį‰‡įˇ¨įĸŧ器", - "transcoding_realtime_video_codecs_description": "ᔍæ–ŧåŗæ™‚čŊ‰įĸŧįš„åŊąį‰‡įˇ¨įĸŧå™¨é¸é …ã€‚į”¨æˆļįĢ¯æœƒåœ¨æ’­æ”žįš„æ™‚å€™é¸æ“‡æ”¯æ´įš„æœ€äŊŗæ–šæĄˆã€‚AV1 įš„æ•ˆįŽ‡é̘æ–ŧ HEVCīŧŒč€Œ HEVC įš„æ•ˆįŽ‡åˆé̘æ–ŧ H.264。äŊŋᔍįĄŦéĢ”åŠ é€Ÿæ™‚īŧŒčĢ‹åĒé¸æ“‡åŠ é€Ÿå™¨æ”¯æ´įˇ¨įĸŧįš„įˇ¨įĸŧ器。äŊŋᔍčģŸéĢ”čŊ‰įĸŧ時īŧŒH.264 įš„é€ŸåēĻåŋĢæ–ŧ AV1īŧŒč€Œ AV1 įš„é€ŸåēĻåŋĢæ–ŧ HEVC。", "transcoding_reference_frames": "åƒč€ƒåš€", "transcoding_reference_frames_description": "åœ¨åŖ“į¸Žį‰šåŽšåš€æ™‚æ‰€åƒč€ƒįš„åš€æ•¸é‡ã€‚æ•¸å€ŧčļŠéĢ˜å¯æå‡åŖ“į¸Žæ•ˆįŽ‡īŧŒäŊ†æœƒé™äŊŽįˇ¨įĸŧ速åēĻã€‚č¨­į‚ē 0 則č‡Ē動æąē厚此數å€ŧ。", "transcoding_required_description": "僅限æ ŧåŧä¸čĸĢæŽĨå—įš„åŊąį‰‡", @@ -466,8 +428,8 @@ "user_delete_delay": "{user} įš„å¸ŗč™Ÿå’Œé …į›Žæœƒåœ¨ {delay, plural, one {# 夊} other {# 夊}} 垌永䚅åˆĒ除。", "user_delete_delay_settings": "åģļ垌åˆĒ除", "user_delete_delay_settings_description": "č‡Ēį§ģ除垌čĩˇįŽ—įš„å¤Šæ•¸īŧŒé€žæœŸåžŒå°‡æ°¸äš…åˆĒ除äŊŋį”¨č€…å¸ŗč™Ÿčˆ‡é …į›Žã€‚äŊŋᔍ者åˆĒ除äŊœæĨ­æœƒåœ¨æ¯æ—Ĩåˆå¤œåŸˇčĄŒīŧŒäģĨæĒĸæŸĨįŦĻ合åˆĒ除æĸäģļįš„å¸ŗč™Ÿã€‚æ­¤č¨­åŽšįš„čŽŠæ›´å°‡åœ¨ä¸‹ä¸€æŦĄåŸˇčĄŒæ™‚į”Ÿæ•ˆã€‚", - "user_delete_immediately": "{user} įš„å¸ŗč™Ÿčˆ‡é …į›Žå°‡ įĢ‹åŗ 排å…Ĩ永䚅åˆĒ除äŊ‡åˆ—。", - "user_delete_immediately_checkbox": "įĢ‹åŗå°‡äŊŋį”¨č€…čˆ‡é …į›ŽæŽ’å…Ĩ永䚅åˆĒ除äŊ‡åˆ—", + "user_delete_immediately": "{user} įš„å¸ŗč™Ÿčˆ‡é …į›Žå°‡ įĢ‹åŗ 排å…Ĩ永䚅åˆĒ除åēåˆ—。", + "user_delete_immediately_checkbox": "įĢ‹åŗå°‡äŊŋį”¨č€…čˆ‡é …į›ŽæŽ’å…Ĩ永䚅åˆĒ除åēåˆ—", "user_details": "äŊŋį”¨č€…čŠŗį´°čŗ‡č¨Š", "user_management": "äŊŋį”¨č€…įŽĄį†", "user_password_has_been_reset": "äŊŋᔍ者坆įĸŧåˇ˛é‡č¨­īŧš", @@ -478,8 +440,6 @@ "user_settings_description": "įŽĄį†äŊŋį”¨č€…č¨­åŽš", "user_successfully_removed": "åˇ˛æˆåŠŸåˆĒ除äŊŋᔍ者 {email}。", "users_page_description": "įŽĄį†äŊŋᔍ者頁éĸ", - "version_check_channel": "į™ŧäŊˆæ¸ é“", - "version_check_channel_description": "é¸æ“‡æ‚¨æƒŗčρæŽĨæ”ļį‰ˆæœŦ更新å…Ŧå‘Šįš„į™ŧ布渠道", "version_check_enabled_description": "å•Ÿį”¨į‰ˆæœŦæĒĸæŸĨ", "version_check_implications": "į‰ˆæœŦæĒĸæŸĨ功čƒŊäģ°čŗ´čˆ‡ {server} įš„åŽšæœŸé€šč¨Š", "version_check_settings": "į‰ˆæœŦæĒĸæŸĨ", @@ -493,14 +453,18 @@ "advanced": "進階", "advanced_settings_clear_image_cache": "æ¸…é™¤åœ–į‰‡åŋĢ取", "advanced_settings_clear_image_cache_error": "æ¸…é™¤åœ–į‰‡åŋĢå–å¤ąæ•—", - "advanced_settings_clear_image_cache_success": "成功清除 {size}", + "advanced_settings_clear_image_cache_success": "成功清除{size}", + "advanced_settings_enable_alternate_media_filter_subtitle": "äŊŋį”¨æ­¤é¸é …å¯åœ¨åŒæ­Ĩ時䞝å…ļäģ–æĸäģļį¯Šé¸åĒ’éĢ”ã€‚åƒ…åœ¨æ‡‰į”¨į¨‹åŧį„Ąæŗ•åĩæ¸Ŧåˆ°æ‰€æœ‰į›¸į°ŋ時再嘗čŠĻäŊŋį”¨ã€‚", + "advanced_settings_enable_alternate_media_filter_title": "[å¯Ļ銗性] äŊŋᔍæ›ŋäģŖįš„čŖįŊŽį›¸į°ŋ同æ­Ĩį¯Šé¸å™¨", "advanced_settings_log_level_title": "į´€éŒ„į­‰į´šīŧš{level}", "advanced_settings_prefer_remote_subtitle": "éƒ¨åˆ†čŖįŊŽåžžæœŦæŠŸé …į›Žčŧ‰å…Ĩį¸Žåœ–įš„é€ŸåēĻ非常æ…ĸã€‚å•Ÿį”¨æ­¤č¨­åŽšå¯æ”šį‚ēčŧ‰å…Ĩ遠įĢ¯åœ–į‰‡ã€‚", "advanced_settings_prefer_remote_title": "偏åĨŊ遠į̝åŊąåƒ", "advanced_settings_proxy_headers_subtitle": "åŽšįžŠ Immich 在每æŦĄįļ˛čˇ¯čĢ‹æą‚æ™‚æ‡‰čŠ˛å‚ŗé€įš„äģŖį†æ¨™é ­", - "advanced_settings_proxy_headers_title": "č‡Ē訂äģŖį†æ¨™é ­ [å¯Ļ銗性]", + "advanced_settings_proxy_headers_title": "č‡ĒåŽšįžŠäģŖį†æ¨™é ­[å¯Ļ銗性]", "advanced_settings_readonly_mode_subtitle": "å•Ÿį”¨å”¯čŽ€æ¨ĄåŧåžŒåƒ…čƒŊį€čĻŊᛏቇīŧŒå°‡åœį”¨å¤šé¸ã€åˆ†äēĢ、投攞及åˆĒé™¤į­‰åŠŸčƒŊ。可透過ä¸ģį•Ģéĸä¸Šįš„äŊŋᔍ者個äēē圖į¤ēå•Ÿį”¨æˆ–åœį”¨å”¯čŽ€æ¨Ąåŧ", "advanced_settings_readonly_mode_title": "å”¯čŽ€æ¨Ąåŧ", + "advanced_settings_self_signed_ssl_subtitle": "į•Ĩ過äŧ翜å™¨į̝éģžįš„ SSL æ†‘č­‰éŠ—č­‰ã€‚č‡Ēį°Ŋæ†‘č­‰æ™‚åŋ…é ˆå•Ÿį”¨æ­¤č¨­åŽšã€‚", + "advanced_settings_self_signed_ssl_title": "å…č¨ąč‡Ēį°Ŋįš„ SSL æ†‘č­‰[å¯Ļ銗性]", "advanced_settings_sync_remote_deletions_subtitle": "į•ļ在įļ˛é įĢ¯åŸˇčĄŒåˆĒ除或還原操äŊœæ™‚īŧŒč‡Ēå‹•åœ¨æ­¤čŖįŊŽä¸ŠåˆĒé™¤æˆ–é‚„åŽŸčŠ˛é …į›Ž", "advanced_settings_sync_remote_deletions_title": "同æ­Ĩ遠į̝åˆĒ除 [å¯Ļ銗性]", "advanced_settings_tile_subtitle": "進階äŊŋį”¨č€…č¨­åŽš", @@ -516,32 +480,47 @@ "album_delete_confirmation": "äŊ įĸē厚čρåˆĒ除ᛏį°ŋ {album} 嗎īŧŸ", "album_delete_confirmation_description": "åĻ‚æžœæ­¤į›¸į°ŋ厞čĸĢå…ąäēĢīŧŒå…ļäģ–äŊŋį”¨č€…å°‡į„Ąæŗ•å†å­˜å–ã€‚", "album_deleted": "ᛏį°ŋ厞åˆĒ除", + "album_info_card_backup_album_excluded": "åˇ˛æŽ’é™¤", + "album_info_card_backup_album_included": "åˇ˛åŒ…åĢ", "album_info_updated": "åˇ˛æ›´æ–°į›¸į°ŋčŗ‡č¨Š", + "album_leave": "é›ĸ開ᛏį°ŋīŧŸ", + "album_leave_confirmation": "您įĸē厚čρé›ĸ開 {album} 嗎īŧŸ", "album_name": "ᛏį°ŋåį¨ą", "album_options": "ᛏį°ŋ選項", "album_remove_user": "į§ģ除äŊŋᔍ者īŧŸ", "album_remove_user_confirmation": "įĸē厚čρį§ģ除 {user} 嗎īŧŸ", "album_search_not_found": "扞不到įŦĻ合搜尋æĸäģļįš„į›¸į°ŋ", + "album_selected": "åˇ˛é¸å–į›¸į°ŋ", "album_share_no_users": "įœ‹äž†æ‚¨čˆ‡æ‰€æœ‰äŊŋį”¨č€…å…ąäēĢäē†é€™æœŦᛏį°ŋīŧŒæˆ–æ˛’æœ‰å…ļäģ–äŊŋį”¨č€…å¯äž›åˆ†äēĢ。", "album_summary": "ᛏį°ŋ摘čρ", - "album_updated": "ᛏį°ŋåˇ˛æ›´æ–°", + "album_updated": "æ›´æ–°į›¸į°ŋ時", "album_updated_setting_description": "į•ļå…ąäēĢᛏį°ŋæœ‰æ–°é …į›Žæ™‚į”¨é›ģ子éƒĩäģļ通įŸĨ我", "album_upload_assets": "åžžæ‚¨įš„é›ģč…Ļä¸Šå‚ŗæĒ”æĄˆä¸Ļ加å…Ĩᛏį°ŋ", + "album_user_left": "é›ĸ開 {album}", + "album_user_removed": "į§ģ除 {user}", + "album_viewer_appbar_delete_confirm": "您įĸē厚čĻåžžå¸ŗč™Ÿä¸­åˆĒé™¤æ­¤į›¸į°ŋ嗎īŧŸ", "album_viewer_appbar_share_err_delete": "åˆĒ除ᛏį°ŋå¤ąæ•—", + "album_viewer_appbar_share_err_leave": "é›ĸ開ᛏį°ŋå¤ąæ•—", + "album_viewer_appbar_share_err_remove": "åžžį›¸į°ŋ中į§ģé™¤é …į›Žæ™‚å‡ēįžéŒ¯čǤ", + "album_viewer_appbar_share_err_title": "ᎍčŧ¯į›¸į°ŋæ¨™éĄŒå¤ąæ•—", + "album_viewer_appbar_share_leave": "é›ĸ開ᛏį°ŋ", + "album_viewer_appbar_share_to": "分äēĢįĩĻ", "album_viewer_page_share_add_users": "邀čĢ‹å…ļäģ–äēē", "album_with_link_access": "äģģäŊ•æ“æœ‰é€Ŗįĩįš„äēēįš†å¯æĒĸčĻ–æ­¤į›¸į°ŋä¸­įš„į›¸į‰‡čˆ‡äēēį‰Šã€‚", "albums": "ᛏį°ŋ", - "albums_count": "{count, plural, one {{count, number} æœŦᛏį°ŋ} other {{count, number} æœŦᛏį°ŋ}}", + "albums_count": "{count, plural, one {{count, number} 個ᛏį°ŋ} other {{count, number} 個ᛏį°ŋ}}", "albums_default_sort_order": "預荭ᛏį°ŋ排åē", "albums_default_sort_order_description": "åģēįĢ‹æ–°į›¸į°ŋ時čĻåˆå§‹åŒ–é …į›ŽæŽ’åēæ–šåŧã€‚", "albums_feature_description": "å¯å…ąäēĢįĩĻå…ļäģ–äŊŋį”¨č€…įš„é …į›Žé›†åˆã€‚", - "albums_on_device_count": "æ­¤čŖįŊŽæœ‰ ({count}) æœŦᛏį°ŋ", + "albums_on_device_count": "æ­¤čŖįŊŽæœ‰ ({count}) 個ᛏį°ŋ", + "albums_selected": "{count, plural, one {åˇ˛é¸å– # æœŦᛏį°ŋ} other {åˇ˛é¸å– # æœŦᛏį°ŋ}}", "all": "全部", "all_albums": "æ‰€æœ‰į›¸į°ŋ", "all_people": "所有äēēį‰Š", "all_photos": "æ‰€æœ‰į›¸į‰‡", "all_videos": "所有åŊąį‰‡", "allow_dark_mode": "å…č¨ąæˇąč‰˛æ¨Ąåŧ", + "allow_edits": "å…č¨ąįˇ¨čŧ¯", "allow_public_user_to_download": "å…č¨ąå…Ŧ開äŊŋᔍ者䏋čŧ‰", "allow_public_user_to_upload": "å…č¨ąå…Ŧ開äŊŋį”¨č€…ä¸Šå‚ŗ", "allowed": "å…č¨ą", @@ -549,12 +528,14 @@ "always_keep": "一型äŋį•™", "always_keep_photos_hint": "「釋攞įŠē間」功čƒŊæœƒå°‡æ‰€æœ‰į›¸į‰‡äŋį•™åœ¨æ­¤čŖįŊŽä¸Šã€‚", "always_keep_videos_hint": "「釋攞įŠē間」功čƒŊ會將所有åŊąį‰‡äŋį•™åœ¨æ­¤čŖįŊŽä¸Šã€‚", + "anti_clockwise": "逆時針", "api_key": "API 金鑰", "api_key_description": "æ­¤é‡‘é‘°åƒ…æœƒéĄ¯į¤ē一æŦĄã€‚關閉čĻ–įĒ—å‰č̋務åŋ…å…ˆč¤‡čŖŊ金鑰。", "api_key_empty": "API é‡‘é‘°åį¨ąä¸åž—į‚ēįŠēį™Ŋ", "api_keys": "API 金鑰", - "app_architecture_variant": "變éĢ”īŧˆæžļ構īŧ‰", + "app_architecture_variant": "čŽŠåŒ–į‰ˆæœŦīŧˆæžļ構īŧ‰", "app_bar_signout_dialog_content": "您įĸē厚čρį™ģå‡ē嗎īŧŸ", + "app_bar_signout_dialog_ok": "是", "app_bar_signout_dialog_title": "į™ģå‡ē", "app_download_links": "App 下čŧ‰é€Ŗįĩ", "app_settings": "æ‡‰į”¨į¨‹åŧč¨­åޚ", @@ -565,19 +546,27 @@ "archive": "封存", "archive_action_prompt": "厞將 {count} å€‹é …į›ŽåŠ å…Ĩ封存", "archive_or_unarchive_photo": "封存或取æļˆå°å­˜į›¸į‰‡", + "archive_page_no_archived_assets": "æ‰žä¸åˆ°å°å­˜é …į›Ž", + "archive_page_title": "封存 ({count})", "archive_size": "封存大小", - "archive_size_description": "č¨­åŽščρ䏋čŧ‰įš„封存æĒ”æĄˆå¤§å°īŧˆå–ŽäŊīŧšGiBīŧ‰", + "archive_size_description": "č¨­åŽščρ䏋čŧ‰įš„封存æĒ”æĄˆå¤§å° (å–ŽäŊīŧšGiB)", "archived": "厞封存", "archived_count": "{count, plural, other {厞封存 # å€‹é …į›Ž}}", "are_these_the_same_person": "同一äŊäēēį‰ŠīŧŸ", "are_you_sure_to_do_this": "您įĸē厚嗎īŧŸ", + "array_field_not_fully_supported": "é™Ŗåˆ—æŦ„äŊéœ€čĻæ‰‹å‹•įˇ¨čŧ¯ JSON", + "asset_action_delete_err_read_only": "å”¯čŽ€é …į›Žį„Ąæŗ•åˆĒ除īŧŒåˇ˛į•Ĩ過", + "asset_action_share_err_offline": "į„Ąæŗ•å–åž—é›ĸįˇšé …į›ŽīŧŒåˇ˛į•Ĩ過", "asset_added_to_album": "åˇ˛æ–°åĸžč‡ŗį›¸į°ŋ", "asset_adding_to_album": "新åĸžåˆ°į›¸į°ŋâ€Ļ", "asset_created": "é …į›Žåˇ˛åģēįĢ‹", - "asset_day_count": "{date}: {count, plural, one {# 個躇æē} other {# 個躇æē}}", "asset_description_updated": "é …į›ŽčĒĒæ˜Žåˇ˛æ›´æ–°", + "asset_filename_is_offline": "é …į›Ž {filename} 厞é›ĸ᎚", + "asset_has_unassigned_faces": "é …į›Žæœ‰æœĒæŒ‡æ´žįš„č‡‰å­”", "asset_hashing": "æ­Ŗåœ¨č¨ˆįŽ—é›œæšŠâ€Ļ", "asset_list_group_by_sub_title": "åˆ†éĄžæ–šåŧ", + "asset_list_layout_settings_dynamic_layout_title": "å‹•æ…‹į‰ˆéĸ", + "asset_list_layout_settings_group_automatically": "č‡Ē動", "asset_list_layout_settings_group_by": "é …į›Žåˆ†éĄžæ–šåŧ", "asset_list_layout_settings_group_by_month_day": "月äģŊ和æ—Ĩ期", "asset_list_layout_sub_title": "į‰ˆéĸ", @@ -588,77 +577,125 @@ "asset_not_found_on_icloud": "iCloud ä¸Šæ‰žä¸åˆ°é …į›Žã€‚čŠ˛é …į›Žå¯čƒŊ因æĒ”æĄˆæ¯€æč€Œį„Ąæŗ•å­˜å–", "asset_offline": "é …į›Žé›ĸ᎚", "asset_offline_description": "æ­¤å¤–éƒ¨é …į›Žåˇ˛į„Ąæŗ•åœ¨įŖįĸŸä¸Šæ‰žåˆ°ã€‚č̋聝įĩĄæ‚¨įš„ Immich įŽĄį†å“ĄäģĨ取垗協劊。", + "asset_restored_successfully": "é …į›Žé‚„åŽŸæˆåŠŸ", "asset_skipped": "åˇ˛čˇŗéŽ", "asset_skipped_in_trash": "åˇ˛åœ¨åžƒåœžæĄļ", + "asset_trashed": "é …į›Žåˇ˛į§ģč‡ŗåžƒåœžæĄļ", "asset_troubleshoot": "é …į›Žæ•…éšœæŽ’é™¤", "asset_uploaded": "åˇ˛ä¸Šå‚ŗ", "asset_uploading": "ä¸Šå‚ŗä¸­â€Ļ", "asset_viewer_settings_subtitle": "įŽĄį†æ‚¨įš„åĒ’éĢ”åēĢæĒĸčĻ–å™¨č¨­åŽš", "asset_viewer_settings_title": "é …į›ŽæĒĸčϖ噍", "assets": "é …į›Ž", + "assets_added_count": "åˇ˛æ–°åĸž {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", "assets_added_to_album_count": "厞將 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}加å…Ĩ臺ᛏį°ŋ", - "assets_added_to_album_partial_count": "åˇ˛åžž {totalCount} {totalCount, plural, one {äģļ} other {äģļ}} é …į›Žä¸­æˆåŠŸæ–°åĸž {successCount} äģļé …į›Žåˆ°į›¸į°ŋ中", "assets_added_to_albums_count": "厞將 {assetTotal, plural, other {# å€‹é …į›Ž}} 新åĸžč‡ŗ {albumTotal, plural, other {# æœŦᛏį°ŋ}}", "assets_cannot_be_added_to_album_count": "į„Ąæŗ•å°‡ {count, plural, one {é …į›Ž} other {é …į›Ž}} 加å…Ĩ臺ᛏį°ŋ", - "assets_cannot_be_added_to_albums": "į„Ąæŗ•å°‡ {count, plural, one {é …į›Ž} other {é …į›Ž}} 加å…ĨäģģäŊ•ᛏį°ŋ", + "assets_cannot_be_added_to_albums": "į„Ąæŗ•å°‡ {count, plural, other {# å€‹é …į›Ž}} 加å…ĨäģģäŊ•ᛏį°ŋ", "assets_count": "{count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", + "assets_deleted_permanently": "åˇ˛æ°¸äš…åˆĒ除 {count} å€‹é …į›Ž", + "assets_deleted_permanently_from_server": "åˇ˛åžž Immich äŧ翜å™¨ä¸­æ°¸äš…į§ģ除 {count} å€‹é …į›Ž", + "assets_downloaded_failed": "{count, plural, one {厞䏋čŧ‰ # 個æĒ”æĄˆ - {error} 個æĒ”æĄˆå¤ąæ•—} other {厞䏋čŧ‰ # 個æĒ”æĄˆ - {error} 個æĒ”æĄˆå¤ąæ•—}}", + "assets_downloaded_successfully": "{count, plural, one {åˇ˛æˆåŠŸä¸‹čŧ‰ # 個æĒ”æĄˆ} other {åˇ˛æˆåŠŸä¸‹čŧ‰ # 個æĒ”æĄˆ}}", "assets_moved_to_trash_count": "厞將 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}į§ģč‡ŗåžƒåœžæĄļ", "assets_permanently_deleted_count": "åˇ˛æ°¸äš…åˆĒ除 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", "assets_removed_count": "厞į§ģ除 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", + "assets_removed_permanently_from_device": "åˇ˛åžžæ‚¨įš„čŖįŊŽæ°¸äš…į§ģ除 {count} å€‹é …į›Ž", "assets_restore_confirmation": "您įĸē厚čĻé‚„åŽŸæ‰€æœ‰åžƒåœžæĄļä¸­įš„é …į›Žå—ŽīŧŸæ­¤æ“äŊœį„Ąæŗ•垊原īŧčĢ‹æŗ¨æ„īŧŒäģģäŊ•é›ĸįˇšé …į›ŽéƒŊį„Ąæŗ•é€éŽæ­¤æ–šåŧé‚„原。", "assets_restored_count": "åˇ˛é‚„åŽŸ {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", + "assets_restored_successfully": "åˇ˛æˆåŠŸé‚„åŽŸ {count} å€‹é …į›Ž", "assets_trashed": "厞將 {count} å€‹é …į›Žį§ģč‡ŗåžƒåœžæĄļ", "assets_trashed_count": "厞將 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}į§ģč‡ŗåžƒåœžæĄļ", + "assets_trashed_from_server": "åˇ˛åžž Immich äŧ翜å™¨å°‡ {count} å€‹é …į›Žį§ģč‡ŗåžƒåœžæĄļ", "assets_were_part_of_album_count": "{count, plural, one {čŠ˛é …į›Žåˇ˛} other {這äē›é …į›Žåˇ˛}}åœ¨į›¸į°ŋ中", - "assets_were_part_of_albums_count": "{count, plural, one {čŠ˛é …į›Žåˇ˛} other {這äē›é …į›Žåˇ˛}}存在æ–ŧᛏį°ŋ中", + "assets_were_part_of_albums_count": "{count, plural, one {個} other {個}}é …į›Žåˇ˛čĸĢå„˛å­˜åœ¨į›¸į°ŋ中", "authorized_devices": "åˇ˛æŽˆæŦŠčŖįŊŽ", "automatic_endpoint_switching_subtitle": "į•ļå¯į”¨æ™‚īŧŒé€éŽæŒ‡åŽšįš„ Wi-Fi 在æœŦæŠŸé€ŖįˇšīŧŒå…ļäģ–æƒ…æŗå‰‡äŊŋᔍæ›ŋäģŖé€Ŗįˇš", "automatic_endpoint_switching_title": "č‡Ē動 URL 切換", "autoplay_slideshow": "č‡Ē動播攞åšģį‡ˆį‰‡", "back": "上一頁", "back_close_deselect": "回上一頁、關閉ä¸Ļ取æļˆé¸å–", + "background_backup_running_error": "čƒŒæ™¯å‚™äģŊį›Žå‰æ­Ŗåœ¨åŸˇčĄŒīŧŒį„Ąæŗ•啟動手動備äģŊ", "background_location_permission": "čƒŒæ™¯å­˜å–äŊįŊŽæŦŠé™", "background_location_permission_content": "į‚ēäē†åœ¨čƒŒæ™¯åŸˇčĄŒæ™‚切換įļ˛čˇ¯īŧŒImmich åŋ…須始įĩ‚å…ˇæœ‰į˛žįĸēäŊįŊŽå­˜å–æŦŠé™īŧŒæ‰čƒŊčŽ€å– Wi-Fi įļ˛čˇ¯åį¨ą", "background_options": "čƒŒæ™¯é¸é …", "backup": "備äģŊ", + "backup_album_selection_page_albums_device": "čŖįŊŽä¸Šįš„ᛏį°ŋīŧˆ{count}īŧ‰", "backup_album_selection_page_albums_tap": "éģžä¸€ä¸‹äģĨ選取īŧŒéģžå…Šä¸‹äģĨ排除", - "backup_album_selection_page_assets_scatter": "é …į›Žå¯äģĨåˆ†æ•Ŗåœ¨å¤šæœŦᛏį°ŋ中īŧŒå› æ­¤åœ¨å‚™äģŊéŽį¨‹ä¸­å¯äģĨé¸æ“‡į´å…Ĩæˆ–æŽ’é™¤į›¸į°ŋ。", + "backup_album_selection_page_assets_scatter": "é …į›Žå¯äģĨåˆ†æ•Ŗåœ¨å¤šå€‹į›¸į°ŋ中īŧŒå› æ­¤åœ¨å‚™äģŊéŽį¨‹ä¸­å¯äģĨé¸æ“‡į´å…Ĩæˆ–æŽ’é™¤į›¸į°ŋ。", "backup_album_selection_page_select_albums": "é¸å–į›¸į°ŋ", "backup_album_selection_page_selection_info": "é¸å–čŗ‡č¨Š", + "backup_album_selection_page_total_assets": "į¸Ŋä¸é‡č¤‡é …į›Žæ•¸", "backup_albums_sync": "備äģŊᛏį°ŋ同æ­Ĩ", + "backup_all": "全部", + "backup_background_service_backup_failed_message": "備äģŊé …į›Žå¤ąæ•—ã€‚æ­Ŗåœ¨é‡čŠĻâ€Ļ", "backup_background_service_complete_notification": "é …į›Žå‚™äģŊ厌成", + "backup_background_service_connection_failed_message": "é€Ŗįˇšč‡ŗäŧ翜å™¨å¤ąæ•—ã€‚æ­Ŗåœ¨é‡čŠĻâ€Ļ", + "backup_background_service_current_upload_notification": "æ­Ŗåœ¨ä¸Šå‚ŗ {filename}", "backup_background_service_default_notification": "æ­Ŗåœ¨æĒĸæŸĨæ–°é …į›Žâ€Ļ", + "backup_background_service_error_title": "備äģŊ錯čǤ", "backup_background_service_in_progress_notification": "æ­Ŗåœ¨å‚™äģŊæ‚¨įš„é …į›Žâ€Ļ", + "backup_background_service_upload_failure_notification": "{filename} ä¸Šå‚ŗå¤ąæ•—", "backup_controller_page_albums": "備äģŊᛏį°ŋ", + "backup_controller_page_background_app_refresh_disabled_content": "čĢ‹åœ¨ã€Œč¨­åŽšã€>「一čˆŦ」>ã€ŒčƒŒæ™¯ App é‡æ–°æ•´į†ã€ä¸­å•Ÿį”¨īŧŒäģĨäŊŋį”¨čƒŒæ™¯å‚™äģŊ功čƒŊ。", + "backup_controller_page_background_app_refresh_disabled_title": "čƒŒæ™¯ App é‡æ–°æ•´į†åˇ˛åœį”¨", + "backup_controller_page_background_app_refresh_enable_button_text": "å‰åž€č¨­åŽš", "backup_controller_page_background_battery_info_link": "å‘Šč¨´æˆ‘æ€Žéēŧ做", "backup_controller_page_background_battery_info_message": "į‚ēį˛åž—æœ€äŊŗįš„čƒŒæ™¯å‚™äģŊéĢ”éŠ—īŧŒčĢ‹åœį”¨äģģäŊ•會限åˆļ Immich čƒŒæ™¯æ´ģå‹•įš„é›ģæą æœ€äŊŗåŒ–č¨­åŽšã€‚\n\nį”ąæ–ŧé€™čˆ‡čŖįŊŽåž‹č™Ÿį›¸é—œīŧŒčĢ‹æŸĨčŠĸæ‚¨čŖįŊŽčŖŊé€ å•†įš„į›¸é—œčĒĒæ˜Žã€‚", "backup_controller_page_background_battery_info_ok": "我įŸĨ道äē†", "backup_controller_page_background_battery_info_title": "é›ģæą æœ€äŊŗåŒ–", + "backup_controller_page_background_charging": "僅在充é›ģ時", + "backup_controller_page_background_configure_error": "čƒŒæ™¯æœå‹™č¨­åŽšå¤ąæ•—", "backup_controller_page_background_delay": "æ–°é …į›Žå‚™äģŊåģļ遲īŧš{duration}", + "backup_controller_page_background_description": "é–‹å•ŸčƒŒæ™¯æœå‹™īŧŒåŗå¯åœ¨ä¸éœ€é–‹å•Ÿ App įš„æƒ…æŗä¸‹īŧŒč‡Ē動備äģŊæ‰€æœ‰æ–°é …į›Ž", + "backup_controller_page_background_is_off": "čƒŒæ™¯č‡Ē動備äģŊåˇ˛é—œé–‰", + "backup_controller_page_background_is_on": "čƒŒæ™¯č‡Ē動備äģŊåˇ˛é–‹å•Ÿ", + "backup_controller_page_background_turn_off": "é—œé–‰čƒŒæ™¯æœå‹™", + "backup_controller_page_background_turn_on": "é–‹å•ŸčƒŒæ™¯æœå‹™", + "backup_controller_page_background_wifi": "僅äŊŋᔍ Wi-Fi", "backup_controller_page_backup": "備äģŊ", "backup_controller_page_backup_selected": "åˇ˛é¸å–īŧš ", "backup_controller_page_backup_sub": "厞備äģŊįš„į›¸į‰‡čˆ‡åŊąį‰‡", + "backup_controller_page_created": "åģēįĢ‹æ™‚é–“īŧš{date}", + "backup_controller_page_desc_backup": "開啟前景備äģŊīŧŒåœ¨é–‹å•Ÿ App 時č‡Ēå‹•å°‡æ–°é …į›Žä¸Šå‚ŗč‡ŗäŧ翜å™¨ã€‚", "backup_controller_page_excluded": "åˇ˛æŽ’é™¤īŧš ", + "backup_controller_page_failed": "å¤ąæ•—īŧˆ{count}īŧ‰", + "backup_controller_page_filename": "æĒ”æĄˆåį¨ąīŧš{filename} [{size}]", + "backup_controller_page_id": "ID: {id}", + "backup_controller_page_info": "備äģŊčŗ‡č¨Š", "backup_controller_page_none_selected": "æœĒ選取äģģäŊ•é …į›Ž", "backup_controller_page_remainder": "削餘", "backup_controller_page_remainder_sub": "é¸å–é …į›Žä¸­å°šæœĒ備äģŊįš„į›¸į‰‡čˆ‡åŊąį‰‡", "backup_controller_page_server_storage": "äŧ翜å™¨å„˛å­˜įŠē間", + "backup_controller_page_start_backup": "開始備äģŊ", + "backup_controller_page_status_off": "前景č‡Ē動備äģŊåˇ˛é—œé–‰", + "backup_controller_page_status_on": "前景č‡Ē動備äģŊåˇ˛é–‹å•Ÿ", "backup_controller_page_storage_format": "{used} / {total} 厞äŊŋᔍ", "backup_controller_page_to_backup": "čρ備äģŊįš„į›¸į°ŋ", "backup_controller_page_total_sub": "åˇ˛é¸å–į›¸į°ŋä¸­įš„æ‰€æœ‰ä¸é‡č¤‡įš„į›¸į‰‡čˆ‡åŊąį‰‡", + "backup_controller_page_turn_off": "關閉前景備äģŊ", + "backup_controller_page_turn_on": "開啟前景備äģŊ", + "backup_controller_page_uploading_file_info": "ä¸Šå‚ŗä¸­įš„æĒ”æĄˆčŗ‡č¨Š", + "backup_err_only_album": "不čƒŊį§ģé™¤å”¯ä¸€įš„į›¸į°ŋ", "backup_error_sync_failed": "同æ­Ĩå¤ąæ•—īŧŒį„Ąæŗ•處ᐆ備äģŊ。", "backup_info_card_assets": "å€‹é …į›Ž", + "backup_manual_cancelled": "åˇ˛å–æļˆ", + "backup_manual_in_progress": "ä¸Šå‚ŗæ­Ŗåœ¨é€˛čĄŒä¸­īŧŒčĢ‹į¨åžŒå†čŠĻ", + "backup_manual_success": "成功", + "backup_manual_title": "ä¸Šå‚ŗį‹€æ…‹", "backup_options": "備äģŊ選項", + "backup_options_page_title": "備äģŊ選項", + "backup_setting_subtitle": "įŽĄį†čƒŒæ™¯čˆ‡å‰æ™¯ä¸Šå‚ŗč¨­åŽš", "backup_settings_subtitle": "įŽĄį†ä¸Šå‚ŗč¨­åŽš", + "backup_upload_details_page_more_details": "éģžæ“ŠæŸĨįœ‹æ›´å¤ščŠŗį´°čŗ‡č¨Š", "backward": "į”ąčˆŠč‡ŗæ–°", - "battery_optimization_backup_reliability": "åœį”¨æœ€äŊŗåŒ–é›ģæą å……é›ģ可提éĢ˜čƒŒæ™¯å‚™äģŊįš„å¯é æ€§", "biometric_auth_enabled": "į”Ÿį‰Ščž¨č­˜éŠ—č­‰åˇ˛å•Ÿį”¨", "biometric_locked_out": "į”Ÿį‰Ščž¨č­˜éŠ—č­‰åˇ˛čĸĢ鎖厚", "biometric_no_options": "æ˛’æœ‰į”Ÿį‰Ščž¨č­˜é¸é …å¯į”¨", "biometric_not_available": "æ­¤čŖįŊŽä¸æ”¯æ´į”Ÿį‰Ščž¨č­˜éŠ—č­‰", + "birthdate_saved": "å‡ēį”Ÿæ—ĨæœŸå„˛å­˜æˆåŠŸ", "birthdate_set_description": "å‡ēį”Ÿæ—ĨæœŸį”¨æ–ŧč¨ˆįŽ—æ­¤äēēåœ¨į›¸į‰‡æ‹æ”æ™‚įš„åš´éŊĄã€‚", "blurred_background": "čƒŒæ™¯æ¨ĄįŗŠ", - "browse_templates": "į€čĻŊᝄæœŦ", "bugs_and_feature_requests": "錯čĒ¤åŠåŠŸčƒŊčĢ‹æą‚", "build": "åģēįŊŽįˇ¨č™Ÿ", "build_image": "åģēįŊŽæ˜ åƒ", @@ -666,6 +703,20 @@ "bulk_keep_duplicates_confirmation": "您įĸē厚čρäŋį•™ {count, plural, one {# å€‹é‡č¤‡é …į›Ž} other {# å€‹é‡č¤‡é …į›Ž}} 嗎īŧŸé€™å°‡åœ¨ä¸åˆĒ除äģģäŊ•å…§åŽšįš„æƒ…æŗä¸‹č§Ŗæąēæ‰€æœ‰é‡č¤‡įž¤įĩ„。", "bulk_trash_duplicates_confirmation": "您įĸē厚čĻæ‰šæŦĄå°‡ {count, plural, one {# å€‹é‡č¤‡é …į›Ž} other {# å€‹é‡č¤‡é …į›Ž}}į§ģč‡ŗåžƒåœžæĄļ嗎īŧŸįŗģįĩąå°‡äŋį•™æ¯įĩ„ä¸­åŽšé‡æœ€å¤§įš„é …į›ŽīŧŒä¸Ļ將所有å…ļäģ–é‡č¤‡é …į›Žį§ģč‡ŗåžƒåœžæĄļ。", "buy": "čŗŧ財 Immich", + "cache_settings_clear_cache_button": "清除åŋĢ取", + "cache_settings_clear_cache_button_title": "清除 App įš„åŋĢ取。此動äŊœæœƒåœ¨åŋĢ取重新åģēįĢ‹å‰īŧŒéĄ¯č‘—åŊąéŸŋ App įš„æ•ˆčƒŊ。", + "cache_settings_duplicated_assets_clear_button": "清除", + "cache_settings_duplicated_assets_subtitle": "čĸĢæ‡‰į”¨į¨‹åŧåŋŊį•Ĩæ¸…å–Žä¸­įš„į›¸į‰‡čˆ‡åŊąį‰‡", + "cache_settings_duplicated_assets_title": "é‡č¤‡é …į›Žīŧˆ{count}īŧ‰", + "cache_settings_statistics_album": "åĒ’éĢ”åēĢį¸Žåœ–", + "cache_settings_statistics_full": "åŽŒæ•´åœ–į‰‡", + "cache_settings_statistics_shared": "å…ąäēĢᛏį°ŋį¸Žåœ–", + "cache_settings_statistics_thumbnail": "į¸Žåœ–", + "cache_settings_statistics_title": "åŋĢ取äŊŋį”¨æƒ…æŗ", + "cache_settings_subtitle": "控åˆļ Immich čĄŒå‹•æ‡‰į”¨į¨‹åŧįš„åŋĢå–čĄŒį‚ē", + "cache_settings_tile_subtitle": "č¨­åŽšæœŦæŠŸå„˛å­˜čĄŒį‚ē", + "cache_settings_tile_title": "æœŦæŠŸå„˛å­˜įŠē間", + "cache_settings_title": "åŋĢå–č¨­åŽš", "camera": "į›¸æŠŸ", "camera_brand": "į›¸æŠŸå“į‰Œ", "camera_model": "į›¸æŠŸåž‹č™Ÿ", @@ -678,10 +729,10 @@ "cannot_update_the_description": "į„Ąæŗ•æ›´æ–°æčŋ°", "cast": "投攞", "cast_description": "č¨­åŽšå¯į”¨įš„æŠ•æ”žčŖįŊŽ", - "change": "更攚", "change_date": "čŽŠæ›´æ—Ĩ期", "change_description": "čŽŠæ›´æčŋ°", "change_display_order": "čŽŠæ›´éĄ¯į¤ē順åē", + "change_expiration_time": "čŽŠæ›´åˆ°æœŸæ™‚é–“", "change_location": "čŽŠæ›´äŊįŊŽ", "change_name": "čŽŠæ›´åį¨ą", "change_name_successfully": "åį¨ąčŽŠæ›´æˆåŠŸ", @@ -690,27 +741,31 @@ "change_password_form_confirm_password": "įĸēčĒå¯†įĸŧ", "change_password_form_description": "您åĨŊ {name}īŧŒ\n\n這是您éĻ–æŦĄį™ģå…ĨįŗģįĩąīŧŒæˆ–æ˜¯åˇ˛æ”ļåˆ°čŽŠæ›´å¯†įĸŧįš„čĢ‹æą‚ã€‚čĢ‹åœ¨ä¸‹æ–ščŧ¸å…Ĩ新密įĸŧ。", "change_password_form_log_out": "į™ģå‡ē所有å…ļäģ–čŖįŊŽ", - "change_password_form_log_out_description": "åģēč­°į™ģå‡ē所有å…ļäģ–čŖįŊŽ", + "change_password_form_log_out_description": "åģēč­°åžžæ‰€æœ‰å…ļäģ–čŖįŊŽį™ģå‡ē", "change_password_form_new_password": "新密įĸŧ", "change_password_form_password_mismatch": "密įĸŧ不一致", "change_password_form_reenter_new_password": "再æŦĄčŧ¸å…Ĩ新密įĸŧ", "change_pin_code": "čŽŠæ›´ PIN įĸŧ", + "change_trigger": "æ›´æ”šč§¸į™ŧ器", + "change_trigger_prompt": "įĸē厚čĻčŽŠæ›´č§¸į™ŧæĸäģļ嗎īŧŸé€™å°‡į§ģé™¤æ‰€æœ‰įžæœ‰įš„å‹•äŊœčˆ‡į¯Šé¸å™¨ã€‚", "change_your_password": "čŽŠæ›´æ‚¨įš„å¯†įĸŧ", "changed_visibility_successfully": "åˇ˛æˆåŠŸčŽŠæ›´å¯čĻ‹æ€§", "charging": "充é›ģ", "charging_requirement_mobile_backup": "čƒŒæ™¯å‚™äģŊčĻæą‚čŖįŊŽæ­Ŗåœ¨å……é›ģ", + "check_corrupt_asset_backup": "æĒĸæŸĨææ¯€įš„å‚™äģŊé …į›Ž", + "check_corrupt_asset_backup_button": "åŸˇčĄŒæĒĸæŸĨ", + "check_corrupt_asset_backup_description": "åƒ…åœ¨åˇ˛é€Ŗįˇšč‡ŗ Wi-Fi ä¸”æ‰€æœ‰é …į›Žåˇ˛åŽŒæˆå‚™äģŊåžŒåŸˇčĄŒæ­¤æĒĸæŸĨã€‚æ­¤į¨‹åŧå¯čƒŊ需čĻæ•¸åˆ†é˜ã€‚", "check_logs": "æĒĸæŸĨį´€éŒ„", "checksum": "æ ĄéŠ—įĸŧ", - "choose": "選擇", "choose_matching_people_to_merge": "選擇čρ合äŊĩįš„į›¸įŦĻäēēį‰Š", "city": "城市", "cleanup_confirm_description": "Immich į™ŧįžæœ‰ {count} å€‹é …į›ŽīŧˆåģēįĢ‹æ–ŧ {date} 䚋前īŧ‰åˇ˛åމ免備äģŊ臺äŧ翜å™¨ã€‚是åĻčĻåžžæ­¤čŖįŊŽä¸­åˆĒ除æœŦ抟副æœŦīŧŸ", "cleanup_confirm_prompt_title": "åžžæ­¤čŖįŊŽåˆĒ除īŧŸ", - "cleanup_deleted_assets": "厞將 {count} å€‹é …į›Žį§ģåˆ°čŖįŊŽįš„垃圞æĄļčŖĄ", + "cleanup_deleted_assets": "厞將{count}é …į›Žį§ģåˆ°čŖįŊŽįš„垃圞æĄļčŖĄ", "cleanup_deleting": "æ­Ŗåœ¨į§ģ動到垃圞æĄļ...", - "cleanup_found_assets": "扞到 {count} äģļåˇ˛ä¸Šå‚ŗįš„é …į›Ž", - "cleanup_found_assets_with_size": "扞到 {count} äģļīŧŒį¸Ŋå…ą ({size}) åˇ˛ä¸Šå‚ŗįš„é …į›Ž", - "cleanup_icloud_shared_albums_excluded": "iCloud å…ąäēĢᛏį°ŋčĸ̿ޒ除æ–ŧ搜尋䚋外", + "cleanup_found_assets": "扞到{count}äģļåˇ˛ä¸Šå‚ŗįš„é …į›Ž", + "cleanup_found_assets_with_size": "扞到{count}äģļīŧŒį¸Ŋå…ą({size})åˇ˛ä¸Šå‚ŗįš„é …į›Ž", + "cleanup_icloud_shared_albums_excluded": "iCloudå…ąäēĢᛏį°ŋčĸ̿ޒ除æ–ŧ搜尋䚋外", "cleanup_no_assets_found": "扞不到įŦĻ合上čŋ°æĸäģļįš„é …į›Žã€‚é‡‹æ”žįŠē間功čƒŊ僅čƒŊį§ģ除厞備äģŊ臺äŧ翜å™¨įš„é …į›Ž", "cleanup_preview_title": "{count} 項需čρį§ģé™¤įš„é …į›Ž", "cleanup_step3_description": "掃描įŦĻ合æ—ĨæœŸčˆ‡å„˛å­˜č¨­åŽšįš„åˇ˛å‚™äģŊé …į›Žã€‚", @@ -719,24 +774,27 @@ "clear": "清įŠē", "clear_all": "全部清除", "clear_all_recent_searches": "清除所有最čŋ‘įš„æœå°‹", - "clear_failed_count": "æ¸…é™¤å¤ąæ•— ({count})", "clear_file_cache": "清除æĒ”æĄˆåŋĢ取", "clear_message": "æ¸…é™¤č¨Šæ¯", "clear_value": "清除å€ŧ", + "client_cert_dialog_msg_confirm": "įĸē厚", + "client_cert_enter_password": "čŧ¸å…Ĩ密įĸŧ", "client_cert_import": "匯å…Ĩ", "client_cert_import_success_msg": "厞匝å…ĨᔍæˆļįĢ¯æ†‘č­‰", "client_cert_invalid_msg": "į„Ąæ•ˆįš„æ†‘č­‰æĒ”æĄˆæˆ–å¯†įĸŧ錯čǤ", - "client_cert_password_message": "čĢ‹čŧ¸å…Ĩæ­¤æ†‘č­‰įš„å¯†įĸŧ", - "client_cert_password_title": "æ†‘č­‰å¯†įĸŧ", + "client_cert_password_message": "čĢ‹čŧ¸å…Ĩæ­¤č­‰æ›¸įš„å¯†įĸŧ", + "client_cert_password_title": "č­‰æ›¸å¯†įĸŧ", "client_cert_remove_msg": "ᔍæˆļįĢ¯æ†‘č­‰åˇ˛į§ģ除", "client_cert_subtitle": "僅支援 PKCS12 (.p12, .pfx) æ ŧåŧã€‚æ†‘č­‰åŒ¯å…Ĩ與į§ģ除僅可在į™ģå…Ĩå‰é€˛čĄŒ", "client_cert_title": "SSL ᔍæˆļįĢ¯æ†‘č­‰ [å¯Ļ銗性]", + "clockwise": "順時針", "close": "關閉", "collapse": "æŠ˜į–Š", "collapse_all": "å…¨éƒ¨æŠ˜į–Š", "color": "顏色", - "command": "指äģ¤", - "command_palette_prompt": "åŋĢ速搜尋頁éĸ、動äŊœæˆ–指äģ¤", + "color_theme": "色åŊŠä¸ģ題", + "command": "å‘Ŋäģ¤", + "command_palette_prompt": "åŋĢ速尋扞頁éĸīŧŒå‹•äŊœæˆ–č€…æŒ‡äģ¤", "command_palette_to_close": "關閉", "command_palette_to_navigate": "čŧ¸å…Ĩ", "command_palette_to_select": "選擇", @@ -747,7 +805,6 @@ "comments_are_disabled": "į•™č¨€åˇ˛åœį”¨", "common_create_new_album": "åģēįĢ‹æ–°į›¸į°ŋ", "completed": "åˇ˛åŽŒæˆ", - "configuration": "č¨­åŽš", "confirm": "įĸēčĒ", "confirm_admin_password": "įĸēčĒįŽĄį†å“Ąå¯†įĸŧ", "confirm_delete_face": "您įĸē厚čĻåžžčŠ˛é …į›Žä¸­åˆĒ除 {name} įš„č‡‰å­”å—ŽīŧŸ", @@ -762,22 +819,25 @@ "contain": "į­‰æ¯”å…§į¸Ž", "context": "脈įĩĄ", "continue": "įšŧįēŒ", - "control_bottom_app_bar_add_tags": "新åĸžæ¨™įą¤", + "control_bottom_app_bar_create_new_album": "åģēįĢ‹æ–°į›¸į°ŋ", + "control_bottom_app_bar_delete_from_immich": "åžž Immich äŧ翜å™¨ä¸­åˆĒ除", "control_bottom_app_bar_delete_from_local": "åžžčŖįŊŽä¸­åˆĒ除", "control_bottom_app_bar_edit_location": "ᎍčŧ¯äŊįŊŽčŗ‡č¨Š", "control_bottom_app_bar_edit_time": "ᎍčŧ¯æ—ĨæœŸčˆ‡æ™‚é–“", + "control_bottom_app_bar_share_link": "分äēĢ逪įĩ", + "control_bottom_app_bar_share_to": "分äēĢįĩĻ", "control_bottom_app_bar_trash_from_immich": "į§ģå…Ĩ垃圞æĄļ", "copied_image_to_clipboard": "åˇ˛å°‡åœ–į‰‡č¤‡čŖŊ到å‰Ēč˛ŧį°ŋ。", "copied_to_clipboard": "厞複čŖŊ到å‰Ēč˛ŧį°ŋīŧ", "copy_error": "複čŖŊ錯čǤ", + "copy_file_path": "複čŖŊæĒ”æĄˆčˇ¯åž‘", "copy_image": "複čŖŊåœ–į‰‡", - "copy_json": "複čŖŊ JSON", "copy_link": "複čŖŊ逪įĩ", "copy_link_to_clipboard": "複čŖŊ逪įĩåˆ°å‰Ēč˛ŧį°ŋ", "copy_password": "複čŖŊ密įĸŧ", "copy_to_clipboard": "複čŖŊ到å‰Ēč˛ŧį°ŋ", "country": "國åŽļ", - "cover": "åĄĢæģŋ", + "cover": "封éĸ", "covers": "封éĸ", "create": "åģēįĢ‹", "create_album": "åģēį̋ᛏį°ŋ", @@ -792,8 +852,6 @@ "create_new_person": "åģēįĢ‹æ–°äēēį‰Š", "create_new_person_hint": "å°‡é¸å–įš„é …į›ŽæŒ‡æ´žįĩĻæ–°įš„äēēį‰Š", "create_new_user": "åģēįĢ‹æ–°äŊŋᔍ者", - "create_person": "åģēįĢ‹äēēį‰Š", - "create_person_subtitle": "į‚翉€é¸č‡‰å­”æ–°åĸžåå­—äģĨåģēįĢ‹å’Œæ¨™č¨˜æ–°äēēį‰Š", "create_shared_album_page_share_add_assets": "新åĸžé …į›Ž", "create_shared_album_page_share_select_photos": "é¸å–į›¸į‰‡", "create_shared_link": "åģēįĢ‹åˆ†äēĢ逪įĩ", @@ -805,33 +863,37 @@ "created_at": "åģēįĢ‹æ–ŧ", "creating_linked_albums": "åģēį̋逪įĩį›¸į°ŋ ...", "crop": "誁å‰Ē", - "crop_aspect_ratio_free": "č‡Ēį”ąæ¯”äž‹", + "crop_aspect_ratio_fixed": "厞äŋŽåžŠ", + "crop_aspect_ratio_free": "į„Ąé™åˆļ", "crop_aspect_ratio_original": "原æĒ”", "crop_aspect_ratio_square": "æ–šåŊĸ", + "curated_object_page_title": "äē‹į‰Š", "current_device": "į›Žå‰čŖįŊŽ", "current_pin_code": "į›Žå‰ PIN įĸŧ", "current_server_address": "į›Žå‰įš„äŧ翜å™¨äŊå€", "custom_date": "åĻ選æ—Ĩ期", "custom_locale": "č‡Ēč¨‚åœ°å€č¨­åŽš", "custom_locale_description": "栚據選厚čĒžč¨€čˆ‡åœ°å€æ ŧåŧåŒ–æ—ĨæœŸã€æ™‚é–“čˆ‡æ•¸å­—", + "custom_url": "č‡Ē訂 URL", "cutoff_date_description": "äŋį•™æœ€čŋ‘å¤šå°‘å¤Šįš„į›¸į‰‡â€Ļ", "cutoff_day": "{count, plural, one {夊} other {夊}}", "cutoff_year": "{count, plural, one {åš´} other {åš´}}", + "daily_title_text_date": "E, MMM dd", + "daily_title_text_date_year": "YYYY åš´ M 月 D æ—Ĩ (E)", "dark": "æˇąč‰˛", "dark_theme": "åˆ‡æ›č‡ŗæˇąč‰˛ä¸ģ題", "date": "æ—Ĩ期", "date_after": "čĩˇå§‹æ—Ĩ期", "date_and_time": "æ—ĨæœŸčˆ‡æ™‚é–“", "date_before": "įĩæŸæ—Ĩ期", - "date_of_birth": "å‡ēį”Ÿæ—Ĩ期", + "date_format": "y åš´ M 月 d æ—Ĩ (E) h:mm a", "date_of_birth_saved": "å‡ēį”Ÿæ—ĨæœŸå„˛å­˜æˆåŠŸ", "date_range": "æ—ĨæœŸį¯„åœ", - "date_time_original": "原始æ—Ĩ期/時間", "day": "æ—Ĩ", "days": "æ—Ĩ", "deduplicate_all": "åˆĒé™¤æ‰€æœ‰é‡č¤‡é …į›Ž", - "default_quality_subtitle": "éģžæ“Šåˆ†äēĢæ™‚äŊŋį”¨įš„į•ĢčŗĒã€‚é•ˇæŒ‰åˆ†äēĢæŒ‰éˆ•可每æŦĄæ‰‹å‹•選擇。", - "default_share_quality": "é č¨­åˆ†äēĢį•ĢčŗĒ", + "default_locale": "éģ˜čĒčĒžč¨€", + "default_locale_description": "äŊŋᔍäŊ įš„į€čĻŊ器區域äģĨæ ŧåŧæ—Ĩ期和數字", "delete": "åˆĒ除", "delete_action_confirmation_message": "您įĸē厚čρåˆĒé™¤æ­¤é …į›Žå—ŽīŧŸæ­¤å‹•äŊœæœƒå°‡čŠ˛é …į›Žį§ģ臺äŧ翜å™¨įš„垃圞æĄļīŧŒä¸ĻčŠĸ問您是åĻčρ圍æœŦ抟同æ­ĨåˆĒ除", "delete_action_prompt": "{count} 個厞åˆĒ除", @@ -840,6 +902,8 @@ "delete_dialog_alert": "這äē›é …į›Žå°‡åžž Immich äģĨåŠæ‚¨įš„čŖįŊŽä¸­æ°¸äš…åˆĒ除", "delete_dialog_alert_local": "這äē›é …į›Žå°‡æœƒåžžæ‚¨įš„čŖįŊŽä¸­æ°¸äš…į§ģ除īŧŒäŊ†äģå¯åœ¨ Immich äŧ翜å™¨ä¸Šå­˜å–", "delete_dialog_alert_local_non_backed_up": "éƒ¨åˆ†é …į›Žå°šæœĒ備äģŊ臺 ImmichīŧŒä¸”å°‡æœƒåžžæ‚¨įš„čŖįŊŽä¸­æ°¸äš…į§ģ除", + "delete_dialog_alert_remote": "這äē›é …į›Žå°‡åžž Immich äŧ翜å™¨ä¸­æ°¸äš…åˆĒ除", + "delete_dialog_ok_force": "įĸēčĒåˆĒ除", "delete_dialog_title": "永䚅åˆĒ除", "delete_duplicates_confirmation": "您įĸē厚čĻæ°¸äš…åˆĒ除這äē›é‡č¤‡é …į›Žå—ŽīŧŸ", "delete_face": "åˆĒ除臉孔", @@ -858,27 +922,34 @@ "delete_tag_confirmation_prompt": "您įĸē厚čρåˆĒ除「{tagName}ã€æ¨™įą¤å—ŽīŧŸ", "delete_user": "åˆĒ除äŊŋᔍ者", "deleted_shared_link": "å…ąäēĢ逪įĩåˇ˛åˆĒ除", + "deletes_missing_assets": "åˆĒ除᪁įĸŸä¸­éēå¤ąįš„é …į›Ž", "description": "描čŋ°", + "description_input_hint_text": "新åĸžæčŋ°...", + "description_input_submit_error": "更新čĒĒæ˜Žæ™‚į™ŧį”ŸéŒ¯čǤīŧŒčĢ‹æĒĸæŸĨį´€éŒ„äģĨå–åž—æ›´å¤ščŠŗį´°čŗ‡č¨Š", "deselect_all": "取æļˆå…¨é¸", "details": "čŠŗį´°čŗ‡č¨Š", "direction": "斚向", "disable": "åœį”¨", "disabled": "åˇ˛åœį”¨", + "disallow_edits": "ä¸å…č¨ąįˇ¨čŧ¯", "discord": "Discord", "discover": "æŽĸį´ĸ", "discovered_devices": "厞į™ŧįžįš„čŖįŊŽ", "dismiss_all_errors": "åŋŊį•Ĩ所有錯čǤ", + "dismiss_error": "åŋŊį•Ĩ錯čǤ", "display_options": "éĄ¯į¤ē選項", "display_order": "éĄ¯į¤ē順åē", "display_original_photos": "éĄ¯į¤ēåŽŸå§‹į›¸į‰‡", - "display_original_photos_setting_description": "æĒĸčĻ–æ™‚č‹Ĩ原始æĒ”æĄˆæ”¯æ´įļ˛é æ ŧåŧīŧŒå‰‡å„Ēå…ˆéĄ¯į¤ēåŽŸåœ–č€Œéžį¸Žåœ–ã€‚æ­¤é¸é …å¯čƒŊ會降äŊŽčŧ‰å…Ĩ速åēĻ。", + "display_original_photos_setting_description": "在æĒĸčĻ–é …į›Žæ™‚īŧŒč‹ĨåŽŸå§‹é …į›Žčˆ‡įļ˛é į›¸åŽšīŧŒå‰‡å„Ēå…ˆéĄ¯į¤ēåŽŸå§‹į›¸į‰‡č€Œéžį¸Žåœ–ã€‚é€™å¯čƒŊæœƒå°Žč‡´į›¸į‰‡čŧ‰å…Ĩ速åēĻ變æ…ĸ。", "do_not_show_again": "ä¸å†éĄ¯į¤ēæ­¤č¨Šæ¯", "documentation": "čĒĒæ˜Žæ–‡äģļ", "done": "厌成", "download": "下čŧ‰", + "download_action_prompt": "æ­Ŗåœ¨ä¸‹čŧ‰ {count} å€‹é …į›Ž", "download_canceled": "下čŧ‰åˇ˛å–æļˆ", "download_complete": "下čŧ‰åŽŒæˆ", "download_enqueue": "厞加å…Ĩ下čŧ‰", + "download_error": "下čŧ‰æ™‚į™ŧį”ŸéŒ¯čǤ", "download_failed": "下čŧ‰å¤ąæ•—", "download_finished": "下čŧ‰åŽŒæˆ", "download_include_embedded_motion_videos": "åĩŒå…ĨåŊąį‰‡", @@ -887,16 +958,16 @@ "download_original": "下čŧ‰åŽŸå§‹æĒ”æĄˆ", "download_paused": "下čŧ‰åˇ˛æšĢ停", "download_settings": "下čŧ‰", - "download_settings_description": "įŽĄį†ä¸‹čŧ‰č¨­åޚ", + "download_settings_description": "įŽĄį†čˆ‡é …į›Žä¸‹čŧ‰į›¸é—œįš„設åޚ", + "download_started": "厞開始䏋čŧ‰", + "download_sucess": "下čŧ‰æˆåŠŸ", + "download_sucess_android": "åĒ’é̔厞䏋čŧ‰č‡ŗ DCIM/Immich", "download_waiting_to_retry": "į­‰åž…é‡čŠĻ", "downloading": "下čŧ‰ä¸­", "downloading_asset_filename": "æ­Ŗåœ¨ä¸‹čŧ‰é …į›Ž {filename}", - "downloading_from_icloud": "æ­Ŗåžž iCloud 下čŧ‰", + "downloading_from_icloud": "æ­ŖåžžiCloud下čŧ‰", "downloading_media": "æ­Ŗåœ¨ä¸‹čŧ‰åĒ’éĢ”", - "drag_to_reorder": "æ‹–æ›ŗäģĨ重新排åē", "drop_files_to_upload": "將æĒ”æĄˆæ‹–æ”žåˆ°äģģäŊ•äŊįŊŽäģĨä¸Šå‚ŗ", - "duplicate": "複čŖŊ", - "duplicate_workflow": "複čŖŊåˇĨäŊœæĩį¨‹", "duplicates": "é‡č¤‡é …į›Ž", "duplicates_description": "逐一æĒĸæŸĨæ¯å€‹įž¤įĩ„īŧŒä¸Ļ標į¤ēå…ļ中是åĻæœ‰é‡č¤‡é …į›Žã€‚", "duration": "éĄ¯į¤ēæ™‚é•ˇ", @@ -908,7 +979,9 @@ "edit_date_and_time": "ᎍčŧ¯æ—ĨæœŸčˆ‡æ™‚é–“", "edit_date_and_time_action_prompt": "厞ᎍčŧ¯äē† {count} 個æ—ĨæœŸčˆ‡æ™‚é–“", "edit_date_and_time_by_offset": "䞝偏į§ģé‡čŽŠæ›´æ—Ĩ期", + "edit_date_and_time_by_offset_interval": "æ–°įš„æ—ĨæœŸį¯„åœīŧš{from} 臺 {to}", "edit_description": "ᎍčŧ¯æčŋ°", + "edit_description_prompt": "čĢ‹é¸æ“‡æ–°įš„æčŋ°īŧš", "edit_exclusion_pattern": "ᎍčŧ¯æŽ’除æĸäģļ", "edit_faces": "ᎍčŧ¯č‡‰å­”", "edit_key": "ᎍčŧ¯é‡‘é‘°", @@ -923,9 +996,12 @@ "edit_user": "ᎍčŧ¯äŊŋᔍ者", "edit_workflow": "ᎍčŧ¯åˇĨäŊœæĩį¨‹", "editor": "ᎍčŧ¯å™¨", + "editor_close_without_save_prompt": "čŽŠæ›´å°‡ä¸æœƒčĸĢå„˛å­˜", + "editor_close_without_save_title": "čĻé—œé–‰įˇ¨čŧ¯å™¨å—ŽīŧŸ", + "editor_confirm_reset_all_changes": "äŊ įĸē厚čĻé‡č¨­æ‰€æœ‰čŽŠæ›´å—ŽīŧŸ", "editor_discard_edits_confirm": "æ”žæŖ„įˇ¨čŧ¯", "editor_discard_edits_prompt": "您有尚æœĒå„˛å­˜įš„įˇ¨čŧ¯å…§åŽšã€‚įĸē厚čĻæ¨æŖ„å—ŽīŧŸ", - "editor_discard_edits_title": "čĻæ¨æŖ„įˇ¨čŧ¯å—ŽīŧŸ", + "editor_discard_edits_title": "įĸēčĒæ”žæŖ„įˇ¨čŧ¯å—ŽīŧŸ", "editor_edits_applied_error": "į„Ąæŗ•åĨ—ᔍᎍčŧ¯", "editor_edits_applied_success": "åˇ˛æˆåŠŸåĨ—ᔍᎍčŧ¯", "editor_flip_horizontal": "æ°´åšŗįŋģčŊ‰", @@ -934,8 +1010,8 @@ "editor_handle_edge": "{edge, select, top {頂部} bottom {åē•部} left {åˇĻ側} right {åŗå´} other {某個}} é‚ŠįˇŖįš„æŽ§åˆļ手柄", "editor_orientation": "斚向", "editor_reset_all_changes": "é‡č¨­čŽŠæ›´", - "editor_rotate_left": "逆時針旋čŊ‰ 90 åēĻ", - "editor_rotate_right": "順時針旋čŊ‰ 90 åēĻ", + "editor_rotate_left": "逆時針旋čŊ‰90åēĻ", + "editor_rotate_right": "順時針旋čŊ‰90åēĻ", "email": "é›ģ子éƒĩäģļ", "email_notifications": "é›ģ子éƒĩäģļ通įŸĨ", "empty_folder": "é€™å€‹čŗ‡æ–™å¤žæ˜¯įŠēįš„", @@ -946,12 +1022,14 @@ "enable_biometric_auth_description": "čŧ¸å…Ĩæ‚¨įš„ PIN įĸŧäģĨå•Ÿį”¨į”Ÿį‰Ščž¨č­˜éŠ—č­‰", "enabled": "åˇ˛å•Ÿį”¨", "end_date": "įĩæŸæ—Ĩ期", - "enqueued": "åˇ˛æŽ’å…ĨäŊ‡åˆ—", + "enqueued": "åˇ˛æŽ’å…Ĩåēåˆ—", "enter_wifi_name": "čŧ¸å…Ĩ Wi-Fi åį¨ą", "enter_your_pin_code": "čŧ¸å…Ĩæ‚¨įš„ PIN įĸŧ", - "enter_your_pin_code_subtitle": "čŧ¸å…Ĩæ‚¨įš„ PIN įĸŧäģĨå­˜å–ã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€", + "enter_your_pin_code_subtitle": "čŧ¸å…Ĩæ‚¨įš„ PIN įĸŧäģĨå­˜å–ã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤ž", "error": "錯čǤ", + "error_change_sort_album": "čŽŠæ›´į›¸į°ŋ排åēå¤ąæ•—", "error_delete_face": "åžžé …į›ŽåˆĒé™¤č‡‰å­”æ™‚į™ŧį”ŸéŒ¯čǤ", + "error_getting_places": "取垗äŊįŊŽæ™‚å‡ē錯", "error_loading_albums": "čŧ‰å…Ĩᛏį°ŋ時į™ŧį”ŸéŒ¯čǤ", "error_loading_image": "åœ–į‰‡čŧ‰å…Ĩ錯čǤ", "error_loading_partners": "čŧ‰å…ĨčĻĒ友時į™ŧį”ŸéŒ¯čǤīŧš{error}", @@ -966,7 +1044,7 @@ "cant_apply_changes": "į„Ąæŗ•åĨ—į”¨čŽŠæ›´", "cant_change_activity": "į„Ąæŗ•{enabled, select, true {åœį”¨} other {å•Ÿį”¨}}æ´ģ動", "cant_change_asset_favorite": "į„Ąæŗ•čŽŠæ›´é …į›Žįš„æ”ļč—į‹€æ…‹", - "cant_change_metadata_assets_count": "į„Ąæŗ•čŽŠæ›´ {count, plural, other {# å€‹é …į›Ž}} įš„čŠŽé‡‹čŗ‡æ–™", + "cant_change_metadata_assets_count": "į„Ąæŗ•čŽŠæ›´ {count, plural, other {# å€‹é …į›Ž}} įš„ä¸­įšŧčŗ‡æ–™", "cant_get_faces": "į„Ąæŗ•å–åž—č‡‰å­”", "cant_get_number_of_comments": "į„Ąæŗ•å–åž—į•™č¨€æ•¸é‡", "cant_search_people": "į„Ąæŗ•æœå°‹äēēį‰Š", @@ -991,7 +1069,6 @@ "failed_to_remove_product_key": "į§ģ除į”ĸå“é‡‘é‘°å¤ąæ•—", "failed_to_reset_pin_code": "重設 PIN įĸŧå¤ąæ•—", "failed_to_stack_assets": "é …į›Žå †į–Šå¤ąæ•—", - "failed_to_tag_assets": "æ¨™č¨˜čŗ‡æēå¤ąæ•—", "failed_to_unstack_assets": "č§Ŗé™¤é …į›Žå †į–Šå¤ąæ•—", "failed_to_update_notification_status": "į„Ąæŗ•æ›´æ–°é€šįŸĨį‹€æ…‹", "incorrect_email_or_password": "é›ģ子éƒĩäģ￈–密įĸŧ錯čǤ", @@ -1087,13 +1164,21 @@ }, "errors_text": "錯čǤ", "exclusion_pattern": "æŽ’é™¤æ¨Ąåŧ", - "exif": "Exif čŗ‡č¨Š", + "exif": "EXIF", "exif_bottom_sheet_description": "新åĸžæčŋ°...", "exif_bottom_sheet_description_error": "更新描čŋ°æ™‚į™ŧį”ŸéŒ¯čǤ", + "exif_bottom_sheet_details": "čŠŗį´°čŗ‡æ–™", + "exif_bottom_sheet_location": "äŊįŊŽ", "exif_bottom_sheet_no_description": "į„Ąæčŋ°", + "exif_bottom_sheet_people": "äēēį‰Š", + "exif_bottom_sheet_person_add_person": "新åĸžå§“名", "exit_slideshow": "įĩæŸåšģį‡ˆį‰‡", "expand": "åą•é–‹", "expand_all": "åą•é–‹å…¨éƒ¨", + "experimental_settings_new_asset_list_subtitle": "æ­Ŗåœ¨č™•į†", + "experimental_settings_new_asset_list_title": "å•Ÿį”¨å¯ĻéŠ—æ€§į›¸į‰‡æ ŧį‹€į‰ˆéĸ", + "experimental_settings_subtitle": "äŊŋᔍéĸ¨éšĒč‡Ē負īŧ", + "experimental_settings_title": "å¯Ļ銗性功čƒŊ", "expire_after": "å¤ąæ•ˆæ™‚é–“", "expired": "åˇ˛éŽæœŸ", "expires_date": "å¤ąæ•ˆæ–ŧ {date}", @@ -1103,40 +1188,41 @@ "export_as_json": "匯å‡ēį‚ē JSON", "export_database": "匯å‡ēčŗ‡æ–™åēĢ", "export_database_description": "匯å‡ē SQLite čŗ‡æ–™åēĢ", - "exposure_time": "曝光時間", "extension": "副æĒ”名", "external": "外部", "external_libraries": "外部åĒ’éĢ”åēĢ", "external_network": "外部įļ˛čˇ¯", "external_network_sheet_info": "č‹ĨæœĒ逪įļ˛č‡ŗååĨŊįš„ Wi-FiīŧŒå°‡äžæ¸…å–Žåžžä¸Šåˆ°ä¸‹é¸æ“‡å¯é€Ŗįˇšįš„äŧ翜å™¨įļ˛å€", - "f_number": "光圈å€ŧ", "face_unassigned": "æœĒ指洞", "failed": "å¤ąæ•—", "failed_count": "å¤ąæ•—īŧš{count}", - "failed_to_authenticate": "čēĢåˆ†éŠ—č­‰å¤ąæ•—", - "failed_to_delete_file": "į„Ąæŗ•åˆĒ除æĒ”æĄˆ", + "failed_to_authenticate": "čēĢäģŊéŠ—č­‰å¤ąæ•—", "failed_to_load_assets": "é …į›Žčŧ‰å…Ĩå¤ąæ•—", "failed_to_load_folder": "į„Ąæŗ•čŧ‰å…Ĩčŗ‡æ–™å¤ž", "favorite": "æ”ļ藏", "favorite_action_prompt": "厞將 {count} å€‹é …į›ŽåŠ å…Ĩæ”ļ藏", "favorite_or_unfavorite_photo": "æ”ļč—æˆ–å–æļˆæ”ļč—į›¸į‰‡", "favorites": "æ”ļ藏", + "favorites_page_no_favorites": "æœĒ扞到æ”ļč—é …į›Ž", "feature_photo_updated": "į˛žé¸į›¸į‰‡åˇ˛æ›´æ–°", "features": "功čƒŊ", + "features_in_development": "į™ŧåą•ä¸­įš„į‰šéģž", "features_setting_description": "įŽĄį†æ‡‰į”¨į¨‹åŧåŠŸčƒŊ", "file_name_or_extension": "æĒ”æĄˆåį¨ąæˆ–å‰¯æĒ”名", "file_name_text": "æĒ”æĄˆåį¨ą", + "file_name_with_value": "æĒ”æĄˆåį¨ą: {file_name}", "file_size": "æĒ”æĄˆå¤§å°", "filename": "æĒ”æĄˆåį¨ą", "filetype": "æĒ”æĄˆéĄžåž‹", "filter": "æŋžéĄ", + "filter_description": "į¯Šé¸į›Žæ¨™é …į›Žįš„æĸäģļ", "filter_people": "į¯Šé¸äēēį‰Š", "filter_places": "į¯Šé¸åœ°éģž", "filter_tags": "į¯Šé¸æ¨™įą¤", "filters": "į¯Šé¸å™¨", + "find_them_fast": "透過搜尋姓名åŋĢ速扞到äģ–們", "first": "įŦŦ一個", "fix_incorrect_match": "äŋŽåžŠä¸į›¸įŦĻįš„", - "focal_length": "į„Ļ距", "folder": "čŗ‡æ–™å¤ž", "folder_not_found": "æ‰žä¸åˆ°čŗ‡æ–™å¤ž", "folders": "čŗ‡æ–™å¤ž", @@ -1147,7 +1233,6 @@ "free_up_space_description": "將厞備äģŊįš„į›¸į‰‡čˆ‡åŊąį‰‡į§ģč‡ŗčŖįŊŽåžƒåœžæĄļäģĨ釋攞įŠē間。äŧ翜å™¨ä¸Šįš„å‚™äģŊ將äŋæŒåŽ‰å…¨ã€‚", "free_up_space_settings_subtitle": "é‡‹æ”žčŖįŊŽå„˛å­˜įŠē間", "full_path": "åŽŒæ•´čˇ¯åž‘īŧš{path}", - "full_path_or_folder": "åŽŒæ•´čˇ¯åž‘æˆ–čŗ‡æ–™å¤ž", "gcast_enabled": "Google Cast", "gcast_enabled_description": "此功čƒŊ需čĻåžž Google čŧ‰å…Ĩå¤–éƒ¨čŗ‡æēæ‰čƒŊæ­Ŗå¸¸é‹äŊœã€‚", "general": "一čˆŦ", @@ -1168,6 +1253,7 @@ "group_owner": "æŒ‰æ“æœ‰č€…åˆ†éĄž", "group_places_by": "åˆ†éĄžåœ°éģžįš„æ–šåŧ...", "group_year": "按嚴äģŊåˆ†éĄž", + "haptic_feedback_switch": "å•Ÿį”¨éœ‡å‹•å›žéĨ‹", "haptic_feedback_title": "震動回éĨ‹", "has_quota": "åˇ˛č¨­åŽšé…éĄ", "hash_asset": "é›œæšŠé …į›Ž", @@ -1188,12 +1274,29 @@ "hide_schema": "隱藏æžļ構", "hide_text_recognition": "éšąč—æ–‡å­—čž¨č­˜", "hide_unnamed_people": "隱藏æœĒå‘Ŋåįš„äēēį‰Š", + "home_page_add_to_album_conflicts": "厞將 {added} å€‹é …į›Žæ–°åĸžč‡ŗį›¸į°ŋ {album}。{failed} å€‹é …į›Žåˇ˛åœ¨čŠ˛į›¸į°ŋ中。", + "home_page_add_to_album_err_local": "į›Žå‰į„Ąæŗ•å°‡æœŦæŠŸé …į›Žæ–°åĸžč‡ŗį›¸į°ŋīŧŒåˇ˛į•Ĩ過", + "home_page_add_to_album_success": "厞將 {added} å€‹é …į›Žæ–°åĸžč‡ŗį›¸į°ŋ {album}。", + "home_page_album_err_partner": "į›Žå‰į„Ąæŗ•å°‡čĻĒå‹å…ąäēĢé …į›Žæ–°åĸžč‡ŗį›¸į°ŋīŧŒåˇ˛į•Ĩ過", + "home_page_archive_err_local": "į›Žå‰į„Ąæŗ•å°å­˜æœŦæŠŸé …į›ŽīŧŒåˇ˛į•Ĩ過", + "home_page_archive_err_partner": "į„Ąæŗ•å°å­˜čĻĒå‹å…ąäēĢé …į›ŽīŧŒåˇ˛į•Ĩ過", "home_page_building_timeline": "æ­Ŗåœ¨åģēįĢ‹æ™‚é–“čģ¸", + "home_page_delete_err_partner": "į„Ąæŗ•åˆĒ除čĻĒå‹å…ąäēĢé …į›ŽīŧŒåˇ˛į•Ĩ過", + "home_page_delete_remote_err_local": "é¸å–įš„é į̝åˆĒ除清喎包åĢæœŦæŠŸé …į›ŽīŧŒåˇ˛į•Ĩ過", + "home_page_favorite_err_local": "æšĢæ™‚į„Ąæŗ•å°‡æœŦæŠŸé …į›Žč¨­į‚ēæ”ļ藏īŧŒåˇ˛į•Ĩ過", + "home_page_favorite_err_partner": "æšĢæ™‚į„Ąæŗ•å°‡čĻĒå‹å…ąäēĢé …į›Žč¨­į‚ēæ”ļ藏īŧŒåˇ˛į•Ĩ過", + "home_page_first_time_notice": "åĻ‚æžœé€™æ˜¯æ‚¨įŦŦ一æŦĄäŊŋᔍæœŦፋåŧīŧŒčĢ‹įĸēäŋé¸æ“‡ä¸€å€‹čρ備äģŊįš„į›¸į°ŋīŧŒäģĨå°‡į›¸į‰‡čˆ‡åŊąį‰‡åŠ å…Ĩ時間čģ¸", + "home_page_locked_error_local": "į„Ąæŗ•å°‡æœŦæŠŸé …į›Žį§ģå‹•č‡ŗã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žīŧŒåˇ˛į•Ĩ過", + "home_page_locked_error_partner": "į„Ąæŗ•å°‡čĻĒå‹å…ąäēĢé …į›Žį§ģå‹•č‡ŗã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žīŧŒåˇ˛į•Ĩ過", + "home_page_share_err_local": "į„Ąæŗ•é€éŽé€Ŗįĩåˆ†äēĢæœŦæŠŸé …į›ŽīŧŒåˇ˛į•Ĩ過", + "home_page_upload_err_limit": "一æŦĄæœ€å¤šåĒčƒŊä¸Šå‚ŗ 30 å€‹é …į›ŽīŧŒåˇ˛į•Ĩ過", "host": "ä¸ģ抟", "hour": "小時", "hours": "小時", "id": "ID", "idle": "閒įŊŽ", + "ignore_icloud_photos": "åŋŊį•Ĩ iCloud ᛏቇ", + "ignore_icloud_photos_description": "å„˛å­˜åœ¨ iCloud ä¸­įš„į›¸į‰‡ä¸æœƒä¸Šå‚ŗč‡ŗ Immich äŧ翜å™¨", "image": "åœ–į‰‡", "image_alt_text_date": "{isVideo, select, true {åŊąį‰‡} other {åœ–į‰‡}}拍攝æ–ŧ {date}", "image_alt_text_date_1_person": "{isVideo, select, true {åŊąį‰‡} other {ᛏቇ}}īŧšæ–ŧ {date} 與 {person1} 一同拍攝", @@ -1205,13 +1308,17 @@ "image_alt_text_date_place_2_people": "{isVideo, select, true {åŊąį‰‡} other {ᛏቇ}}īŧšæ–ŧ {date} 在 {country}{city} 與 {person1} 及 {person2} 一同拍攝", "image_alt_text_date_place_3_people": "{isVideo, select, true {åŊąį‰‡} other {ᛏቇ}}īŧšæ–ŧ {date} 在 {country}{city} 與 {person1}、{person2} 及 {person3} 一同拍攝", "image_alt_text_date_place_4_or_more_people": "{isVideo, select, true {åŊąį‰‡} other {ᛏቇ}}īŧšæ–ŧ {date} 在 {country}{city} 與 {person1}、{person2} 及å…ļäģ– {additionalCount, number} äēē一同拍攝", + "image_saved_successfully": "åˇ˛å„˛å­˜åœ–į‰‡", + "image_viewer_page_state_provider_download_started": "下čŧ‰åˇ˛å•Ÿå‹•", + "image_viewer_page_state_provider_download_success": "下čŧ‰æˆåŠŸ", + "image_viewer_page_state_provider_share_error": "分äēĢæ™‚į™ŧį”ŸéŒ¯čǤ", "immich_logo": "Immich Logo", "immich_web_interface": "Immich įļ˛é äģ‹éĸ", "import_from_json": "åžž JSON 匯å…Ĩ", "import_path": "匯å…Ĩčˇ¯åž‘", "in_albums": "在 {count, plural, one {# æœŦᛏį°ŋ} other {# æœŦᛏį°ŋ}}中", "in_archive": "在封存中", - "in_year": "{year} åš´", + "in_year": "{year}åš´", "in_year_selector": "在", "include_archived": "包åĢ厞封存", "include_shared_albums": "包åĢå…ąäēĢᛏį°ŋ", @@ -1219,37 +1326,43 @@ "individual_share": "個åˆĨ分äēĢ", "individual_shares": "個åˆĨ分äēĢ", "info": "čŗ‡č¨Š", - "integrity_checks": "厌整性æĒĸæŸĨ", "interval": { "day_at_onepm": "每夊下午 1 éģž", "hours": "每 {hours, plural, one {小時} other {{hours, number} 小時}}", "night_at_midnight": "每晚午夜", - "night_at_twoam": "每æ—Ĩ凌晨 2 éģž" + "night_at_twoam": "每晚凌晨 2 éģž" }, "invalid_date": "į„Ąæ•ˆįš„æ—Ĩ期", "invalid_date_format": "į„Ąæ•ˆįš„æ—Ĩ期æ ŧåŧ", "invite_people": "邀čĢ‹æˆå“Ą", "invite_to_album": "邀č̋臺ᛏį°ŋ", - "iso": "ISO", + "ios_debug_info_fetch_ran_at": "æŠ“å–åˇ˛æ–ŧ {dateTime} åŸˇčĄŒ", + "ios_debug_info_last_sync_at": "上æŦĄåŒæ­Ĩæ–ŧ {dateTime}", + "ios_debug_info_no_processes_queued": "į„ĄæŽ’į¨‹ä¸­įš„čƒŒæ™¯į¨‹åŧ", + "ios_debug_info_no_sync_yet": "尚æœĒåŸˇčĄŒäģģäŊ•čƒŒæ™¯åŒæ­Ĩäģģ務", + "ios_debug_info_processes_queued": "{count, plural, one {{count} å€‹čƒŒæ™¯į¨‹åŧåˇ˛æŽ’ፋ} other {{count} å€‹čƒŒæ™¯į¨‹åŧåˇ˛æŽ’ፋ}}", + "ios_debug_info_processing_ran_at": "æ–ŧ {dateTime} åŸˇčĄŒč™•į†", "items_count": "{count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", "jobs": "äģģ務", + "json_editor": "JSONᎍčŧ¯å™¨", + "json_error": "JSON錯čǤ", "keep": "äŋį•™", "keep_albums": "äŋį•™į›¸į°ŋ", - "keep_albums_count": "äŋį•™ {count} {count, plural, one {æœŦᛏį°ŋ} other {æœŦᛏį°ŋ}}", + "keep_albums_count": "äŋį•™{count} {count, plural, one {個ᛏį°ŋ} other {個ᛏį°ŋ}}", "keep_all": "全部äŋį•™", "keep_description": "é¸æ“‡åŸˇčĄŒé‡‹æ”žįŠē間時čρäŋį•™åœ¨čŖįŊŽä¸Šįš„é …į›Žã€‚", "keep_favorites": "äŋį•™æœ€æ„›įš„ᛏቇ", "keep_on_device": "äŋį•™åœ¨čŖįŊŽä¸Š", "keep_on_device_hint": "選擇äŋį•™åœ¨čŖįŊŽä¸Šįš„ᛏቇ", "keep_this_delete_others": "äŋį•™é€™å€‹īŧŒåˆĒ除å…ļäģ–", - "keeping": "äŋį•™īŧš{items}", - "kept_this_deleted_others": "äŋį•™é€™å€‹é …į›Žä¸ĻåˆĒ除{count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", + "keeping": "äŋį•™:{items}", + "kept_this_deleted_others": "äŋį•™é€™å€‹é …į›Žä¸ĻåˆĒ除{count, plural, one {# asset} other {# assets}}", "keyboard_shortcuts": "éĩᛤåŋĢæˇéĩ", "language": "čĒžč¨€", "language_no_results_subtitle": "čŠĻ著čĒŋæ•´æ‚¨įš„æœå°‹čŠžåŊ™", "language_no_results_title": "æœĒ扞到čĒžč¨€", "language_search_hint": "搜尋čĒžč¨€...", - "language_setting_description": "選取您偏åĨŊįš„čĒžč¨€", + "language_setting_description": "é¸æ“‡æ‚¨įš„éϖ遏čĒžč¨€", "large_files": "大型æĒ”æĄˆ", "last": "最垌一個", "last_months": "{count, plural, one {上個月} other {最čŋ‘ # 個月}}", @@ -1259,12 +1372,14 @@ "leave": "é›ĸ開", "leave_album": "é›ĸ開ᛏį°ŋ", "lens_model": "éĄé ­åž‹č™Ÿ", - "less": "æ”ļčĩˇ", "let_others_respond": "å…č¨ąäģ–äēē回čφ", "level": "į­‰į´š", "library": "åĒ’éĢ”åēĢ", "library_add_folder": "新åĸžčŗ‡æ–™å¤ž", "library_edit_folder": "ᎍčŧ¯čŗ‡æ–™å¤ž", + "library_options": "åĒ’éĢ”åēĢ選項", + "library_page_device_albums": "čŖįŊŽä¸Šįš„ᛏį°ŋ", + "library_page_new_album": "新åĸžį›¸į°ŋ", "library_page_sort_asset_count": "é …į›Žæ•¸é‡", "library_page_sort_created": "åģēįĢ‹æ—Ĩ期", "library_page_sort_last_modified": "上æŦĄäŋŽæ”š", @@ -1274,20 +1389,17 @@ "light_theme": "åˆ‡æ›č‡ŗæˇē色ä¸ģ題", "like": "å–œæ­Ą", "like_deleted": "åˇ˛å–æļˆå–œæ­Ą", - "link": "逪įĩ", "link_motion_video": "逪įĩå‹•æ…‹åŊąį‰‡", - "link_to_docs": "čĢ‹åƒé–ą čĒĒæ˜Žæ–‡äģļ äģĨį˛å–æ›´å¤ščŗ‡č¨Šã€‚", + "link_to_docs": "čĢ‹åƒé–ą æ–‡æĄˆ äģĨį˛å–æ›´å¤šäŋĄæ¯ã€‚", "link_to_oauth": "逪įĩ OAuth", "linked_oauth_account": "厞逪įĩ OAuth å¸ŗč™Ÿ", "list": "清喎", - "live": "᎚䏊", - "load_more": "čŧ‰å…Ĩ更多", "loading": "čŧ‰å…Ĩ中", "loading_search_results_failed": "čŧ‰å…Ĩ搜尋įĩæžœå¤ąæ•—", "local": "æœŦ抟", "local_asset_cast_failed": "į„Ąæŗ•æŠ•æ”žæœĒä¸Šå‚ŗč‡ŗäŧ翜å™¨įš„é …į›Ž", "local_assets": "æœŦæŠŸé …į›Ž", - "local_id": "æœŦ抟 ID", + "local_id": "æœŦ地ID", "local_media_summary": "æœŦ抟åĒ’éĢ”æ‘˜čρ", "local_network": "æœŦ抟įļ˛čˇ¯", "local_network_sheet_info": "į•ļäŊŋį”¨æŒ‡åŽšįš„ Wi-Fi įļ˛čˇ¯æ™‚īŧŒæ‡‰į”¨į¨‹åŧå°‡é€éŽæ­¤įļ˛å€é€Ŗįˇšč‡ŗäŧ翜å™¨", @@ -1300,19 +1412,21 @@ "location_picker_longitude_error": "čŧ¸å…Ĩæœ‰æ•ˆįš„įļ“åēĻå€ŧ", "location_picker_longitude_hint": "čĢ‹åœ¨æ­¤č™•čŧ¸å…Ĩæ‚¨įš„įļ“åēĻå€ŧ", "lock": "鎖厚", - "locked_folder": "åˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤ž", + "locked_folder": "åˇ˛éŽ–åŽščŗ‡æ–™å¤ž", "log_detail_title": "į´€éŒ„čŠŗį´°čŗ‡č¨Š", "log_out": "į™ģå‡ē", "log_out_all_devices": "į™ģå‡ēæ‰€æœ‰čŖįŊŽ", - "logged_in_as": "äģĨ {user} čēĢ分į™ģå…Ĩ", + "logged_in_as": "äģĨ{user}čēĢ分į™ģå…Ĩ", "logged_out_all_devices": "厞į™ģå‡ēæ‰€æœ‰čŖįŊŽ", "logged_out_device": "厞į™ģå‡ēčŖįŊŽ", "login": "į™ģå…Ĩ", "login_disabled": "åˇ˛åœį”¨į™ģå…Ĩ", "login_form_api_exception": "API į™ŧį”Ÿäž‹å¤–īŧŒčĢ‹æĒĸæŸĨäŧ翜å™¨äŊå€åžŒå†čŠĻ。", + "login_form_back_button_text": "上一頁", "login_form_email_hint": "é›ģ子éƒĩäģļ地址", "login_form_endpoint_hint": "http://æ‚¨įš„äŧ翜å™¨äŊå€:逪æŽĨ埠", "login_form_endpoint_url": "äŧ翜å™¨į̝éģž URL", + "login_form_err_http": "čĢ‹č¨ģ明 http:// 或 https://", "login_form_err_invalid_email": "é›ģ子éƒĩäģļåœ°å€į„Ąæ•ˆ", "login_form_err_invalid_url": "į„Ąæ•ˆįš„ URL", "login_form_err_leading_whitespace": "開頭包åĢįŠēį™Ŋ字元", @@ -1322,9 +1436,10 @@ "login_form_failed_login": "į™ģå…Ĩå¤ąæ•—īŧŒčĢ‹æĒĸæŸĨäŧ翜å™¨äŊå€ã€é›ģ子éƒĩäģļåœ°å€čˆ‡å¯†įĸŧ", "login_form_handshake_exception": "與äŧ翜å™¨é€šč¨Šæ™‚å‡ēįžä礿Ąį•°å¸¸ã€‚č‹ĨäŊŋᔍč‡Ēį°Ŋæ†‘č­‰īŧŒčĢ‹åœ¨č¨­åŽšä¸­å•Ÿį”¨č‡Ēį°Ŋæ†‘č­‰æ”¯æ´ã€‚", "login_form_password_hint": "密įĸŧ", - "login_form_server_empty": "čĢ‹čŧ¸å…Ĩäŧ翜å™¨įļ˛å€", - "login_form_server_error": "į„Ąæŗ•é€Ŗįˇšč‡ŗäŧ翜å™¨", - "login_has_been_disabled": "åˇ˛åœį”¨į™ģå…Ĩ功čƒŊ", + "login_form_save_login": "äŋæŒį™ģå…Ĩ", + "login_form_server_empty": "čĢ‹čŧ¸å…Ĩäŧ翜å™¨įļ˛å€ã€‚", + "login_form_server_error": "į„Ąæŗ•é€Ŗįˇšč‡ŗäŧ翜å™¨ã€‚", + "login_has_been_disabled": "åˇ˛åœį”¨į™ģå…Ĩ功čƒŊ。", "login_password_changed_error": "密įĸŧæ›´æ–°å¤ąæ•—", "login_password_changed_success": "密įĸŧ更新成功", "logout_all_device_confirmation": "您įĸē厚čρį™ģå‡ēæ‰€æœ‰čŖįŊŽå—ŽīŧŸ", @@ -1332,13 +1447,14 @@ "logs": "į´€éŒ„", "longitude": "įļ“åēĻ", "look": "æ¨Ŗč˛Œ", - "loop_videos": "åžĒį’°æ’­æ”žåŊąį‰‡", - "loop_videos_description": "åœ¨čŠŗį´°æĒĸčϖ噍䏭č‡Ē動åžĒį’°æ’­æ”žåŊąį‰‡ã€‚", + "loop_videos": "重播åŊąį‰‡", + "loop_videos_description": "å•Ÿį”¨åžŒīŧŒåŊąį‰‡įĩæŸæœƒč‡Ē動重播。", "main_branch_warning": "æ‚¨æ­ŖäŊŋᔍ開į™ŧį‰ˆæœŦīŧ›åŧˇįƒˆåģēč­°äŊŋį”¨æ­Ŗåŧį‰ˆæœŦīŧ", "main_menu": "ä¸ģ選喎", "maintenance_action_restore": "é‚„åŽŸčŗ‡æ–™åēĢ", "maintenance_description": "Immich 厞逞å…Ĩ įļ­č­ˇæ¨Ąåŧã€‚", "maintenance_end": "įĩæŸįļ­č­ˇæ¨Ąåŧ", + "maintenance_end_error": "æœĒčƒŊįĩæŸįļ­č­ˇæ¨Ąåŧã€‚", "maintenance_logged_in_as": "į›Žå‰äģĨ {user} čēĢ分į™ģå…Ĩ", "maintenance_restore_from_backup": "åžžå‚™äģŊ還原", "maintenance_restore_library": "é‚„åŽŸæ‚¨įš„åĒ’éĢ”åēĢ", @@ -1359,11 +1475,12 @@ "maintenance_task_rollback": "é‚„åŽŸå¤ąæ•—īŧŒæ­Ŗåœ¨å›žæē¯č‡ŗé‚„原éģžâ€Ļ", "maintenance_title": "æšĢæ™‚ä¸å¯į”¨", "make": "čŖŊ造商", - "manage_geolocation": "įŽĄį†äŊįŊŽčŗ‡č¨Š", + "manage_geolocation": "įŽĄį†äŊįŊŽ", "manage_media_access_rationale": "需čĻæ­¤æŦŠé™æ‰čƒŊč™•į†é …į›Žį§ģč‡ŗåžƒåœžæĄļčˆ‡é‚„åŽŸįš„æ“äŊœã€‚", - "manage_media_access_settings": "é–‹å•Ÿč¨­åŽš", + "manage_media_access_settings": "æ‰“é–‹č¨­åŽš", "manage_media_access_subtitle": "å…č¨ą Immich App įŽĄį†čˆ‡į§ģ動åĒ’éĢ”æĒ”æĄˆã€‚", "manage_media_access_title": "åĒ’éĢ”įŽĄį†å­˜å–æŦŠé™", + "manage_shared_links": "įŽĄį†åˆ†äēĢ逪įĩ", "manage_sharing_with_partners": "įŽĄį†čĻĒå‹å…ąäēĢč¨­åŽš", "manage_the_app_settings": "įŽĄį†æ‡‰į”¨į¨‹åŧč¨­åޚ", "manage_your_account": "įŽĄį†æ‚¨įš„å¸ŗč™Ÿ", @@ -1371,12 +1488,14 @@ "manage_your_devices": "įŽĄį†åˇ˛į™ģå…Ĩįš„čŖįŊŽ", "manage_your_oauth_connection": "įŽĄį†æ‚¨įš„ OAuth 逪įĩ", "map": "地圖", + "map_assets_in_bounds": "{count, plural, one {# åŧĩᛏቇ} other {# åŧĩᛏቇ}}", "map_cannot_get_user_location": "į„Ąæŗ•å–åž—äŊŋᔍ者äŊįŊŽ", + "map_location_dialog_yes": "įĸē厚", "map_location_picker_page_use_location": "äŊŋį”¨æ­¤äŊįŊŽ", "map_location_service_disabled_content": "需čĻå•Ÿį”¨åŽšäŊæœå‹™æ‰čƒŊéĄ¯į¤ēæ‚¨į›Žå‰äŊįŊŽį›¸é—œįš„é …į›Žã€‚čĻįžåœ¨å•Ÿį”¨å—ŽīŧŸ", "map_location_service_disabled_title": "厚äŊæœå‹™åˇ˛åœį”¨", - "map_marker_for_image": "在 {city}īŧŒ{country} æ‹æ”åœ–į‰‡įš„åœ°åœ–æ¨™č¨˜", - "map_marker_with_image": "å¸ļ有åŊąåƒįš„åœ°åœ–æ¨™č¨˜", + "map_marker_for_images": "在 {city}、{country} 拍攝åŊąåƒįš„地圖į¤ē記", + "map_marker_with_image": "å¸ļ有åŊąåƒįš„地圖į¤ē記", "map_no_location_permission_content": "需čρäŊįŊŽæŦŠé™æ‰čƒŊéĄ¯į¤ēčˆ‡æ‚¨į›Žå‰äŊįŊŽį›¸é—œįš„é …į›Žã€‚čĻįžåœ¨å°ąæŽˆäēˆäŊįŊŽæŦŠé™å—ŽīŧŸ", "map_no_location_permission_title": "æ˛’æœ‰äŊįŊŽæŦŠé™", "map_settings": "åœ°åœ–č¨­åŽš", @@ -1385,46 +1504,17 @@ "map_settings_date_range_option_days": "{days} 夊前", "map_settings_date_range_option_year": "1 嚴前", "map_settings_date_range_option_years": "{years} 嚴前", + "map_settings_dialog_title": "åœ°åœ–č¨­åŽš", "map_settings_include_show_archived": "包æ‹Ŧåˇ˛å°å­˜é …į›Ž", "map_settings_include_show_partners": "包åĢčĻĒ友", "map_settings_only_show_favorites": "åƒ…éĄ¯į¤ēæ”ļč—įš„é …į›Ž", "map_settings_theme_settings": "地圖ä¸ģ題", + "map_zoom_to_see_photos": "į¸Žå°äģĨæĒĸčĻ–é …į›Ž", "mark_all_as_read": "å…¨éƒ¨æ¨™č¨˜į‚ēåˇ˛čŽ€", + "mark_as_read": "æ¨™č¨˜į‚ēåˇ˛čŽ€", "marked_all_as_read": "åˇ˛å…¨éƒ¨æ¨™č¨˜į‚ēåˇ˛čŽ€", "matches": "ᛏįŦĻ", "matching_assets": "įŦĻåˆįš„é …į›Ž", - "media_chrome": { - "auto": "č‡Ē動", - "captions": "å­—åš•", - "captions_off": "關閉", - "closed_captions": "éšąč—å­—åš•", - "decode_error": "č§Ŗįĸŧ錯čǤ", - "disable_captions": "åœį”¨å­—åš•", - "enable_captions": "å•Ÿį”¨å­—åš•", - "enter_fullscreen_mode": "進å…Ĩ全čžĸåš•", - "exit_fullscreen_mode": "退å‡ē全čžĸåš•", - "loop": "åžĒį’°æ’­æ”ž", - "media_error_description": "åĒ’éĢ”éŒ¯čĒ¤å°Žč‡´æ’­æ”žä¸­æ–ˇã€‚åĒ’éĢ”å¯čƒŊåˇ˛ææ¯€æˆ–æ‚¨įš„į€čĻŊ器不支援此æ ŧåŧã€‚", - "media_loading": "åĒ’éĢ”čŧ‰å…Ĩ中", - "mute": "éœéŸŗ", - "network_error": "įļ˛čˇ¯éŒ¯čǤ", - "network_error_description": "įļ˛čˇ¯éŒ¯čĒ¤å°Žč‡´åĒ’é̔䏋čŧ‰å¤ąæ•—。", - "not_supported_error": "ä¸æ”¯æ´įš„äž†æē", - "playback_rate": "播攞速åēĻ", - "playback_rate_current": "į›Žå‰æ’­æ”žé€ŸåēĻ", - "playback_rate_value": "播攞速åēĻ {playbackRate}", - "playback_time": "播攞時間", - "quality": "į•ĢčŗĒ", - "second": "į§’", - "seconds": "į§’", - "time_value_of_total_time": "{currentTime} / {totalTime}", - "time_value_remaining": "削餘 {time}", - "unmute": "取æļˆéœéŸŗ", - "unsupported_error_description": "į™ŧį”Ÿä¸æ”¯æ´įš„éŒ¯čĒ¤ã€‚äŧ翜å™¨æˆ–įļ˛čˇ¯å¤ąæ•—īŧŒæˆ–æ‚¨įš„į€čĻŊ器不支援此æ ŧåŧã€‚", - "video_not_loaded_unknown_time": "åŊąį‰‡æœĒčŧ‰å…ĨīŧŒæ™‚é–“æœĒįŸĨ。", - "video_player": "åŊąį‰‡æ’­æ”žå™¨", - "volume": "éŸŗé‡" - }, "media_type": "åĒ’éĢ”éĄžåž‹", "memories": "回æ†ļ", "memories_all_caught_up": "åˇ˛å…¨éƒ¨įœ‹åŽŒ", @@ -1441,75 +1531,83 @@ "merge_people_prompt": "您čρ合äŊĩ這äē›äēēį‰Šå—ŽīŧŸæ­¤æ“äŊœį„Ąæŗ•æ’¤éŠˇã€‚", "merge_people_successfully": "成功合äŊĩäēēį‰Š", "merged_people_count": "厞合äŊĩ {count, plural, other {# äŊäēēį‰Š}}", - "minFaces": "最少äēēč‡‰æ•¸", - "minFaces_description": "éĄ¯į¤ē芲äēēį‰Šæ‰€éœ€įš„æœ€å°‘åˇ˛č­˜åˆĨäēēč‡‰æ•¸é‡", "minimize": "最小化", "minute": "分", "minutes": "分鐘", + "mirror_horizontal": "æ°´åšŗ", + "mirror_vertical": "åž‚į›´", "missing": "排å…ĨæœĒ處ᐆ", "mobile_app": "čĄŒå‹•æ‡‰į”¨į¨‹åŧ", "mobile_app_download_onboarding_note": "čĢ‹äŊŋᔍäģĨ下選項下čŧ‰éš¨é™„įš„čĄŒå‹•æ‡‰į”¨į¨‹åŧ", "model": "åž‹č™Ÿ", - "modify_date": "äŋŽæ”šæ—Ĩ期", "month": "月", + "monthly_title_text_date_format": "y MMMM", "more": "更多", - "motion": "動äŊœ", "move": "į§ģ動", - "move_off_locked_folder": "į§ģå‡ēã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€", + "move_down": "向下į§ģ動", + "move_off_locked_folder": "į§ģå‡ēéŽ–åŽšįš„čŗ‡æ–™å¤ž", "move_to": "į§ģ動到", "move_to_device_trash": "į§ģå‹•åˆ°čŖįŊŽįš„垃圞æĄļ", - "move_to_lock_folder_action_prompt": "厞將 {count} å€‹é …į›Žæ–°åĸžč‡ŗã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€", - "move_to_locked_folder": "į§ģč‡ŗã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€", - "move_to_locked_folder_confirmation": "這äē›į›¸į‰‡čˆ‡åŊąį‰‡å°‡åžžæ‰€æœ‰į›¸į°ŋ中į§ģ除īŧŒä¸”僅čƒŊåžžã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€ä¸­æĒĸčĻ–", + "move_to_lock_folder_action_prompt": "厞將 {count} å€‹é …į›Žæ–°åĸžč‡ŗã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤ž", + "move_to_locked_folder": "į§ģč‡ŗã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤ž", + "move_to_locked_folder_confirmation": "這äē›į›¸į‰‡čˆ‡åŊąį‰‡å°‡åžžæ‰€æœ‰į›¸į°ŋ中į§ģ除īŧŒä¸”僅čƒŊåžžã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žä¸­æĒĸčĻ–", + "move_up": "向上į§ģ動", + "moved_to_archive": "厞封存 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}", + "moved_to_library": "厞į§ģ動 {count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}} 臺ᛏį°ŋ", "moved_to_trash": "åˇ˛ä¸Ÿé€˛åžƒåœžæĄļ", + "multiselect_grid_edit_date_time_err_read_only": "å”¯čŽ€é …į›Žįš„æ—ĨæœŸį„Ąæŗ•įˇ¨čŧ¯īŧŒåˇ˛į•Ĩ過", + "multiselect_grid_edit_gps_err_read_only": "å”¯čŽ€é …į›Žįš„äŊįŊŽčŗ‡č¨Šį„Ąæŗ•ᎍčŧ¯īŧŒåˇ˛į•Ĩ過", "mute_memories": "éœéŸŗå›žæ†ļ", "my_albums": "æˆ‘įš„į›¸į°ŋ", - "my_immich_description": "複čŖŊį›Žå‰é éĸį‚ē My Immich 逪įĩ", - "my_immich_title": "My Immich 逪įĩ", "name": "åį¨ą", "name_or_nickname": "åį¨ąæˆ–æšąį¨ą", "name_required": "åį¨ąæ˜¯åŋ…åĄĢ項", - "navigate": "導čĻŊ", + "navigate": "導čˆĒ", "navigate_to_time": "莺čŊ‰č‡ŗæŒ‡åŽšæ™‚é–“", "network_requirement_photos_upload": "äŊŋį”¨čĄŒå‹•įļ˛čˇ¯æĩé‡å‚™äģŊᛏቇ", "network_requirement_videos_upload": "äŊŋį”¨čĄŒå‹•įļ˛čˇ¯æĩé‡å‚™äģŊåŊąį‰‡", "network_requirements": "įļ˛čˇ¯čĻæą‚", - "network_requirements_updated": "įļ˛čˇ¯éœ€æą‚åˇ˛čŽŠæ›´īŧŒæ­Ŗåœ¨é‡č¨­å‚™äģŊäŊ‡åˆ—", + "network_requirements_updated": "įļ˛čˇ¯éœ€æą‚åˇ˛čŽŠæ›´īŧŒæ­Ŗåœ¨é‡č¨­å‚™äģŊåēåˆ—", "networking_settings": "įļ˛čˇ¯", "networking_subtitle": "įŽĄį†äŧ翜å™¨į̝éģžč¨­åޚ", "never": "æ°¸ä¸å¤ąæ•ˆ", "new_album": "æ–°į›¸į°ŋ", "new_api_key": "æ–°įš„ API 金鑰", - "new_feature": "新功čƒŊ", + "new_date_range": "新æ—ĨæœŸį¯„åœ", "new_password": "新密įĸŧ", "new_person": "æ–°įš„äēēį‰Š", "new_pin_code": "新 PIN įĸŧ", - "new_pin_code_subtitle": "這是您įŦŦ一æŦĄå­˜å–ã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€ã€‚čĢ‹åģēįĢ‹ PIN įĸŧäģĨ厉全存取此頁éĸ", + "new_pin_code_subtitle": "這是您įŦŦ一æŦĄå­˜å–ã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žã€‚čĢ‹åģēįĢ‹ PIN įĸŧäģĨ厉全存取此頁éĸ", + "new_timeline": "新時間čģ¸", "new_update": "新更新", "new_user_created": "厞åģēįĢ‹æ–°äŊŋᔍ者", "new_version_available": "æ–°į‰ˆæœŦ厞į™ŧ布", - "newest_first": "į”ąæ–°åˆ°čˆŠ", + "newest_first": "最新å„Ē先", "next": "下一æ­Ĩ", "next_memory": "下一åŧĩ回æ†ļ", "no": "åĻ", + "no_actions_added": "尚æœĒ新åĸžäģģäŊ•å‹•äŊœ", "no_albums_found": "į„Ąį›¸į°ŋ", "no_albums_message": "åģēį̋ᛏį°ŋäž†æ•´į†į›¸į‰‡å’ŒåŊąį‰‡", "no_albums_with_name_yet": "įœ‹äž†é‚„æ˛’æœ‰é€™å€‹åå­—įš„į›¸į°ŋ。", "no_albums_yet": "įœ‹äž†æ‚¨é‚„æ˛’æœ‰äģģäŊ•ᛏį°ŋ。", - "no_archived_assets_message": "å°‡į›¸į‰‡čˆ‡åŊąį‰‡å°å­˜åžŒīŧŒå°ąä¸æœƒéĄ¯į¤ēåœ¨ã€Œį›¸į‰‡ã€é éĸ中", + "no_archived_assets_message": "å°‡į›¸į‰‡čˆ‡åŊąį‰‡å°å­˜åžŒīŧŒå°ąä¸æœƒéĄ¯į¤ēåœ¨ã€Œį›¸į‰‡ã€čĻ–åœ–ä¸­", "no_assets_message": "æŒ‰é€™čŖĄä¸Šå‚ŗæ‚¨įš„įŦŦ一åŧĩᛏቇ", "no_assets_to_show": "į„Ąé …į›Žåą•į¤ē", "no_cast_devices_found": "扞不到 Google Cast čŖįŊŽ", "no_checksum_local": "į„Ąå¯į”¨æ ĄéŠ—įĸŧ - į„Ąæŗ•å–åž—æœŦæŠŸé …į›Ž", "no_checksum_remote": "į„Ąå¯į”¨æ ĄéŠ—įĸŧ - į„Ąæŗ•å–åž—é›˛įĢ¯é …į›Ž", + "no_configuration_needed": "į„Ąéœ€č¨­åŽš", "no_devices": "į„ĄæŽˆæŦŠčŖįŊŽ", "no_duplicates_found": "æœĒį™ŧįžäģģäŊ•é‡č¤‡é …į›Žã€‚", "no_exif_info_available": "æ˛’æœ‰å¯į”¨įš„ Exif čŗ‡č¨Š", "no_explore_results_message": "ä¸Šå‚ŗæ›´å¤šį›¸į‰‡äž†æŽĸį´ĸæ‚¨įš„įč—ã€‚", - "no_favorites_message": "加å…Ĩ最愛īŧŒæ›´åŋĢæ‰žåˆ°æ‚¨æœ€æŖ’įš„į›¸į‰‡čˆ‡åŊąį‰‡", + "no_favorites_message": "加å…Ĩæ”ļ藏īŧŒåŠ é€Ÿå°‹æ‰žåŊąåƒ", + "no_filters_added": "尚æœĒ新åĸžäģģäŊ•į¯Šé¸å™¨", "no_libraries_message": "åģēįĢ‹å¤–éƒ¨åĒ’éĢ”åēĢäģĨæĒĸčĻ–æ‚¨įš„į›¸į‰‡å’ŒåŊąį‰‡", "no_local_assets_found": "æ‰žä¸åˆ°å…ˇæœ‰æ­¤æ ĄéŠ—įĸŧįš„æœŦæŠŸé …į›Ž", - "no_locked_photos_message": "ã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€ä¸­įš„į›¸į‰‡čˆ‡åŊąį‰‡æœƒčĸĢ隱藏īŧŒä¸”不會å‡ēįžåœ¨į€čĻŊ或搜尋įĩæžœä¸­ã€‚", + "no_location_set": "æœĒč¨­åŽšäŊįŊŽ", + "no_locked_photos_message": "ã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žä¸­įš„į›¸į‰‡čˆ‡åŊąį‰‡æœƒčĸĢ隱藏īŧŒä¸”不會å‡ēįžåœ¨į€čĻŊ或搜尋įĩæžœä¸­ã€‚", "no_name": "į„Ąå", "no_notifications": "æ˛’æœ‰é€šįŸĨ", "no_people_found": "扞不到įŦĻåˆįš„äēēį‰Š", @@ -1518,19 +1616,14 @@ "no_results": "æ˛’æœ‰įĩæžœ", "no_results_description": "čŠĻčŠĻåŒįžŠčŠžæˆ–æ›´é€šį”¨įš„é—œéĩ字吧", "no_shared_albums_message": "åģēįĢ‹å…ąäēĢᛏį°ŋäģĨ分äēĢį›¸į‰‡čˆ‡åŊąį‰‡", - "no_steps": "æœĒ新åĸžäģģäŊ•æ­Ĩ驟", + "no_uploads_in_progress": "æ˛’æœ‰æ­Ŗåœ¨ä¸Šå‚ŗįš„é …į›Ž", "none": "į„Ą", "not_allowed": "ä¸å…č¨ą", "not_available": "ä¸éŠį”¨", "not_in_any_album": "不在äģģäŊ•ᛏį°ŋ中", "not_selected": "æœĒ選取", - "not_set": "æœĒč¨­åŽš", "notes": "提į¤ē", "nothing_here_yet": "æšĢį„Ąč¨Šæ¯", - "notification_backup_reliability": "å•Ÿį”¨é€šįŸĨ可提éĢ˜čƒŒæ™¯å‚™äģŊįš„å¯é æ€§", - "notification_enabled_list_tile_content": "Immich 會äŊŋį”¨é€šįŸĨäģĨåŽŒæˆčƒŒæ™¯å‚™äģŊ。čĢ‹åœ¨įŗģįĩąč¨­åŽšä¸­įŽĄį†é€šįŸĨ。", - "notification_enabled_list_tile_open_button": "é–‹å•Ÿč¨­åŽš", - "notification_enabled_list_tile_title": "å•Ÿį”¨é€šįŸĨ", "notification_permission_dialog_content": "開啟通įŸĨīŧŒčĢ‹å‰åž€ã€Œč¨­åŽšã€īŧŒä¸Ļé¸æ“‡ã€Œå…č¨ąã€ã€‚", "notification_permission_list_tile_content": "開啟通įŸĨæŦŠé™ã€‚", "notification_permission_list_tile_enable_button": "開啟通įŸĨ", @@ -1539,11 +1632,9 @@ "notifications": "通įŸĨ", "notifications_setting_description": "įŽĄį†é€šįŸĨ", "oauth": "OAuth", - "obtainium_configurator": "Obtainium č¨­åŽšåˇĨå…ˇ", + "obtainium_configurator": "Obtainium č¨­åŽšå™¨", "obtainium_configurator_instructions": "äŊŋᔍ Obtainium į›´æŽĨåžž Immich GitHub įš„į™ŧčĄŒį‰ˆæœŦåŽ‰čŖä¸Ļ更新 Android App。åģēįĢ‹ API 金鑰ä¸Ļé¸å–į‰ˆæœŦéĄžåž‹äģĨį”ĸį”Ÿæ‚¨įš„ Obtainium č¨­åŽšé€Ŗįĩ", "ocr": "OCR", - "ocr_body": "Immich 可äģĨ螨識äŊ į…§į‰‡čŖĄįš„æ–‡å­—īŧŒæ‰€äģĨäŊ å¯äģĨé€éŽį…§į‰‡čŖĄįš„æ–‡å­—äž†æœå°‹į…§į‰‡ã€‚", - "ocr_title": "åœ¨æ‚¨įš„į…§į‰‡ä¸­æœå°‹æ–‡å­—", "official_immich_resources": "厘斚 Immich čŗ‡æē", "offline": "é›ĸ᎚", "offset": "į§ģ動", @@ -1560,10 +1651,8 @@ "online": "᎚䏊", "only_favorites": "åƒ…éĄ¯į¤ēåˇąæ”ļ藏", "open": "開啟", - "open_calendar": "開啟æ—Ĩ曆", + "open_calendar": "打開æ—Ĩ曆", "open_in_browser": "åœ¨į€čĻŊ器中開啟", - "open_in_immich_body": "在 Android čŖįŊŽä¸Šå°‡ Immich č¨­åŽšį‚ēåĒ’éĢ”åēĢīŧŒåŗå¯į›´æŽĨåžžå…ļäģ–æ‡‰į”¨į¨‹åŧé–‹å•Ÿį…§į‰‡ã€‚", - "open_in_immich_title": "在 Immich čŖĄéĸé–‹å•Ÿį…§į‰‡", "open_in_map_view": "開啟地圖æĒĸčĻ–", "open_in_openstreetmap": "ᔍ OpenStreetMap 開啟", "open_the_search_filters": "é–‹å•Ÿæœå°‹į¯Šé¸å™¨", @@ -1572,10 +1661,10 @@ "organize_into_albums": "æ•´į†æˆį›¸į°ŋ", "organize_into_albums_description": "äŊŋį”¨į›Žå‰åŒæ­Ĩč¨­åŽšå°‡įžæœ‰į›¸į‰‡æ”žå…Ĩᛏį°ŋ", "organize_your_library": "æ•´į†æ‚¨įš„į›¸į°ŋ", - "orientation": "斚向", "original": "原圖", "other": "å…ļäģ–", - "other_devices": "å…ļäģ–čŖįŊŽ", + "other_devices": "å…ļåŽƒčŖįŊŽ", + "other_entities": "å…ļäģ–é …į›Ž", "other_variables": "å…ļäģ–čŽŠæ•¸", "owned": "æˆ‘įš„", "owner": "æ“æœ‰č€…", @@ -1585,9 +1674,12 @@ "partner_can_access_assets": "除äē†ã€Œåˇ˛å°å­˜ã€čˆ‡ã€Œåˇ˛åˆĒ除」外īŧŒæ‚¨æ‰€æœ‰įš„į›¸į‰‡čˆ‡åŊąį‰‡", "partner_can_access_location": "į›¸į‰‡įš„æ‹æ”äŊįŊŽ", "partner_list_user_photos": "{user} įš„į›¸į‰‡", + "partner_list_view_all": "æŸĨįœ‹å…¨éƒ¨", "partner_page_empty_message": "æ‚¨įš„į›¸į‰‡å°šæœĒ與äģģäŊ•čĻĒå‹å…ąäēĢ。", "partner_page_no_more_users": "åˇ˛į„Ąå¯æ–°åĸžįš„äŊŋᔍ者", + "partner_page_partner_add_failed": "čĻĒ友新åĸžå¤ąæ•—", "partner_page_select_partner": "選擇čĻĒ友", + "partner_page_shared_to_title": "å…ąäēĢįĩĻ", "partner_page_stop_sharing_content": "{partner} å°‡į„Ąæŗ•å†å­˜å–æ‚¨įš„į›¸į‰‡ã€‚", "partner_sharing": "čĻĒå‹å…ąäēĢ", "partners": "čĻĒ友", @@ -1609,28 +1701,41 @@ "people": "äēēį‰Š", "people_edits_count": "厞ᎍčŧ¯ {count, plural, one {# äŊäēēį‰Š} other {# äŊäēēį‰Š}}", "people_feature_description": "äģĨäēēį‰Šåˆ†éĄžį€čĻŊį›¸į‰‡å’ŒåŊąį‰‡", + "people_selected": "{count, plural, one {åˇ˛é¸å– # äŊäēēį‰Š} other {åˇ˛é¸å– # äŊäēēį‰Š}}", + "people_sidebar_description": "在側邊æŦ„éĄ¯į¤ē「äēēį‰Šã€įš„é€Ŗįĩ", "permanent_deletion_warning": "永䚅åˆĒ除č­Ļ告", "permanent_deletion_warning_setting_description": "在永䚅åˆĒ除æĒ”æĄˆæ™‚éĄ¯į¤ēč­Ļ告", "permanently_delete": "永䚅åˆĒ除", "permanently_delete_assets_count": "永䚅åˆĒ除 {count, plural, one {æĒ”æĄˆ} other {æĒ”æĄˆ}}", - "permanently_delete_assets_prompt": "įĸē厚čĻæ°¸äš…åˆĒ除 {count, plural, one {這個æĒ”æĄˆīŧŸ} other {這 # 個æĒ”æĄˆīŧŸ}}é€™æ¨Ŗ{count, plural, one {厃} other {厃們}}䚟會垞č‡Ēåˇąæ‰€åœ¨įš„į›¸į°ŋ中æļˆå¤ąã€‚", + "permanently_delete_assets_prompt": "įĸē厚čĻæ°¸äš…åˆĒ除 {count, plural, other {這 # 個æĒ”æĄˆīŧŸ}}é€™æ¨Ŗ{count, plural, one {厃} other {厃們}}䚟會垞č‡Ēåˇąæ‰€åœ¨įš„į›¸į°ŋ中æļˆå¤ąã€‚", "permanently_deleted_asset": "永䚅åˆĒé™¤įš„æĒ”æĄˆ", "permanently_deleted_assets_count": "永䚅åˆĒé™¤įš„ {count, plural, one {# 個æĒ”æĄˆ} other {# 個æĒ”æĄˆ}}", "permission": "æŦŠé™", "permission_empty": "æŦŠé™ä¸čƒŊį‚ēįŠē", + "permission_onboarding_back": "上一頁", + "permission_onboarding_continue_anyway": "įĸēčĒįšŧįēŒ", + "permission_onboarding_get_started": "開始äŊŋᔍ", + "permission_onboarding_go_to_settings": "å‰åž€č¨­åŽš", + "permission_onboarding_permission_denied": "åĻ‚čρįšŧįēŒīŧŒčĢ‹å…č¨ą Immich å­˜å–į›¸į‰‡å’ŒåŊąį‰‡æŦŠé™ã€‚", + "permission_onboarding_permission_granted": "åˇ˛å…č¨ąīŧä¸€åˆ‡å°ąįˇ’。", + "permission_onboarding_permission_limited": "åĻ‚čρįšŧįēŒīŧŒčĢ‹å…č¨ą Immich 備äģŊå’ŒįŽĄį†æ‚¨įš„į›¸į°ŋæ”ļ藏īŧŒåœ¨č¨­åŽšä¸­æŽˆäēˆį›¸į‰‡å’ŒåŊąį‰‡æŦŠé™ã€‚", + "permission_onboarding_request": "Immich 需čρæŦŠé™æ‰čƒŊæĒĸčĻ–æ‚¨įš„į›¸į‰‡čˆ‡åŊąį‰‡ã€‚", "person": "äēēį‰Š", "person_age_months": "{months, plural, one {# 個月} other {# 個月}}", "person_age_year_months": "1 åš´ {months, plural, one {# 個月} other {# 個月}}", - "person_age_years": "{years, plural, one {# æ­˛} other {# æ­˛}}", + "person_age_years": "{years, plural, other {# æ­˛}}", "person_birthdate": "å‡ēį”Ÿæ–ŧ {date}", "person_hidden": "{name}{hidden, select, true {īŧˆéšąč—īŧ‰} other {}}", "person_recognized": "åˇ˛čž¨č­˜äēēį‰Š", + "person_selected": "åˇ˛é¸å–äēēį‰Š", "photo_shared_all_users": "įœ‹äž†æ‚¨čˆ‡æ‰€æœ‰äŊŋį”¨č€…åˆ†äēĢäē†į›¸į‰‡īŧŒæˆ–æ˛’æœ‰å…ļäģ–äŊŋį”¨č€…å¯äž›åˆ†äēĢ。", "photos": "ᛏቇ", "photos_and_videos": "į›¸į‰‡åŠåŊąį‰‡", + "photos_count": "{count, plural, other {{count, number} åŧĩᛏቇ}}", "photos_from_previous_years": "åž€åš´įš„į›¸į‰‡", + "photos_only": "åƒ…é™į›¸į‰‡", "pick_a_location": "選擇äŊįŊŽ", - "pick_custom_range": "č‡Ēč¨‚į¯„åœ", + "pick_custom_range": "č‡ĒåŽšįžŠį¯„åœ", "pick_date_range": "選擇æ—ĨæœŸį¯„åœ", "pin_code_changed_successfully": "PIN įĸŧčŽŠæ›´æˆåŠŸ", "pin_code_reset_successfully": "PIN įĸŧé‡č¨­æˆåŠŸ", @@ -1646,9 +1751,7 @@ "play_original_video": "播攞原始åŊąį‰‡", "play_original_video_setting_description": "å„Ē先播攞原始åŊąį‰‡č€ŒéžčŊ‰įĸŧåžŒįš„åŊąį‰‡ã€‚č‹ĨåŽŸå§‹é …į›Žä¸į›¸åŽšīŧŒå¯čƒŊį„Ąæŗ•æ­Ŗå¸¸æ’­æ”žã€‚", "play_transcoded_video": "播攞čŊ‰įĸŧåŊąį‰‡", - "please_auth_to_access": "čĢ‹é€˛čĄŒčēĢåˆ†éŠ—č­‰æ‰čƒŊ存取", - "plugin_method_filter_type": "į¯Šé¸", - "plugin_method_filter_type_description": "æ­¤æ–šæŗ•å¯į¯Šé¸äē‹äģļīŧŒä¸Ļ有æĸäģļ地é˜ģæ­ĸ垌į猿­ĨéŠŸåŸˇčĄŒ", + "please_auth_to_access": "čĢ‹é€˛čĄŒčēĢäģŊéŠ—č­‰æ‰čƒŊ存取", "port": "逪æŽĨ埠", "preferences_settings_subtitle": "įŽĄį†æ‡‰į”¨į¨‹åŧååĨŊč¨­åŽš", "preferences_settings_title": "偏åĨŊč¨­åŽš", @@ -1665,11 +1768,11 @@ "privacy": "éšąį§", "profile": "叺æˆļč¨­åŽš", "profile_drawer_app_logs": "į´€éŒ„", + "profile_drawer_client_server_up_to_date": "ᔍæˆļįĢ¯čˆ‡äŧ翜å™¨į‰ˆæœŦįš†į‚ē最新", "profile_drawer_github": "GitHub", - "profile_drawer_readonly_mode": "å”¯čŽ€æ¨Ąåŧåˇ˛å•Ÿį”¨ã€‚é•ˇæŒ‰äŊŋᔍ者個äēē圖į¤ēåŗå¯é—œé–‰ã€‚", + "profile_drawer_readonly_mode": "å”¯čŽ€æ¨Ąåŧåˇ˛å•Ÿį”¨ã€‚é•ˇæŒ‰äŊŋᔍ者個äēē圖į¤ēåŗå¯é€€å‡ē。", "profile_image_of_user": "{user} įš„å€‹äēēčŗ‡æ–™åœ–į‰‡", "profile_picture_set": "åˇ˛č¨­åŽšå€‹äēēčŗ‡æ–™åœ–į‰‡ã€‚", - "projection_type": "投åŊąéĄžåž‹", "public_album": "å…Ŧ開ᛏį°ŋ", "public_share": "å…Ŧ開分äēĢ", "purchase_account_info": "æ”¯æŒč€…", @@ -1688,9 +1791,10 @@ "purchase_individual_description_2": "æ”¯æŒč€…į‹€æ…‹", "purchase_individual_title": "個äēē", "purchase_input_suggestion": "åˇ˛æœ‰į”ĸ品金鑰īŧŸčĢ‹åœ¨ä¸‹æ–ščŧ¸å…Ĩ", + "purchase_license_subtitle": "čŗŧ財 Immich äģĨ支持čģŸéĢ”įš„æŒįēŒé–‹į™ŧ", "purchase_lifetime_description": "įĩ‚čēĢæŽˆæŦŠ", "purchase_option_title": "čŗŧ買選項", - "purchase_panel_info_1": "打造 Immich 需耗č˛ģ大量åŋƒåŠ›īŧŒæˆ‘們投å…Ĩäē†å…¨čˇåˇĨፋå¸Ģį˛žį›Šæą‚į˛žīŧŒč‡´åŠ›æ–ŧ提䞛最卓čļŠįš„čģŸéĢ”éĢ”éŠ—ã€‚æˆ‘å€‘įš„äŊŋå‘Ŋ是推動開æēčģŸéĢ”čˆ‡įŦĻ合å€Ģį†įš„å•†æĨ­å¯Ļ踐īŧŒäŊŋå…ļ成į‚ē開į™ŧč€…įŠŠåŽšä¸”æ°¸įēŒįš„æ”ļį›Šäž†æēīŧŒä¸ĻåģēįĢ‹ä¸€å€‹å°Šé‡éšąį§įš„į”Ÿæ…‹éĢ”įŗģīŧŒæäž›čƒŊįœŸæ­Ŗå–ä쪿ޠåĨĒæ€§é›˛įĢ¯æœå‹™įš„å„ĒčŗĒ選擇。", + "purchase_panel_info_1": "開į™ŧ Immich 需čĻæŠ•å…Ĩå¤§é‡æ™‚é–“čˆ‡į˛žåŠ›īŧŒæˆ‘å€‘æœ‰å…¨čˇåˇĨፋå¸Ģč‡´åŠ›æ–ŧ將å…￉“é€ åž—į›Ąå–„į›ĄįžŽã€‚æˆ‘å€‘įš„äŊŋå‘Ŋæ˜¯čŽ“é–‹æēčģŸéĢ”čˆ‡åˆäšŽé“åžˇįš„å•†æĨ­æ¨Ąåŧæˆį‚ē開į™ŧč€…æ°¸įēŒįš„æ”ļå…Ĩ來æēīŧŒä¸ĻåģēįĢ‹ä¸€å€‹é‡čĻ–éšąį§įš„į”Ÿæ…‹įŗģįĩąīŧŒæäž›å–äģŖå‰å‰Šæ€§é›˛įĢ¯æœå‹™įš„įœŸå¯Ļ選擇。", "purchase_panel_info_2": "我們æ‰ŋčĢžä¸č¨­äģ˜č˛ģቆīŧŒæ‰€äģĨčŗŧ財 Immich ä¸Ļä¸æœƒčŽ“æ‚¨į˛åž—éĄå¤–įš„åŠŸčƒŊ。我們äģ°čŗ´åƒæ‚¨é€™æ¨Ŗįš„äŊŋį”¨č€…äž†æ”¯æŒ Immich įš„æŒįēŒé–‹į™ŧ。", "purchase_panel_title": "æ”¯æŒæ­¤å°ˆæĄˆ", "purchase_per_server": "每č‡ēäŧ翜å™¨", @@ -1708,7 +1812,7 @@ "rate_asset": "é …į›ŽčŠ•åˆ†", "rating": "čŠ•æ˜Ÿ", "rating_clear": "æ¸…é™¤čŠ•į­‰", - "rating_count": "{count, plural, =0 {æœĒčŠ•åˆ†} one {# 星} other {# 星}}", + "rating_count": "{count, plural, =0 {Unrated} other {# 星}}", "rating_description": "åœ¨čŗ‡č¨Šéĸæŋä¸­éĄ¯į¤ē EXIF čŠ•į­‰", "reaction_options": "反應選項", "read_changelog": "閱čĻŊæ›´æ–°į´€éŒ„", @@ -1720,23 +1824,23 @@ "reassigned_assets_to_new_person": "厞將 {count, plural, other {# å€‹é …į›Ž}} 重新指洞įĩĻæ–°įš„äēēį‰Š", "reassing_hint": "å°‡é¸å–įš„é …į›ŽæŒ‡æ´žįĩĻįžæœ‰äēēį‰Š", "recent": "最čŋ‘", + "recent_albums": "最čŋ‘ᛏį°ŋ", "recent_searches": "最čŋ‘æœå°‹é …į›Ž", "recently_added": "最čŋ‘æ–°åĸž", - "recently_added_body": "前垀專åąŦ頁éĸīŧŒį›´æŽĨæŸĨįœ‹æ‚¨æœ€čŋ‘æ–°åĸžįš„æ‰€æœ‰å…§åŽšã€‚", - "recently_added_description": "į€čĻŊæ‚¨įš„é …į›ŽīŧŒä¸ĻæŒ‰ä¸Šå‚ŗåˆ° Immich įš„æ™‚é–“æŽ’åē", "recently_added_page_title": "最čŋ‘æ–°åĸž", - "recently_added_title": "最čŋ‘æ–°åĸž", "recently_taken": "最čŋ‘拍攝", + "recently_taken_page_title": "最čŋ‘拍攝", "refresh": "é‡æ–°æ•´į†", "refresh_encoded_videos": "é‡æ–°æ•´į†åˇ˛įˇ¨įĸŧįš„åŊąį‰‡", - "refresh_faces": "é‡æ–°æ•´į†č‡‰å­”čŗ‡æ–™", - "refresh_metadata": "é‡æ–°æ•´į†čŠŽé‡‹čŗ‡æ–™", + "refresh_faces": "重整éĸéƒ¨čŗ‡æ–™", + "refresh_metadata": "é‡æ–°æ•´į†ä¸­įšŧčŗ‡æ–™", "refresh_thumbnails": "é‡æ–°æ•´į†į¸Žåœ–", "refreshed": "é‡æ–°æ•´į†åŽŒį•ĸ", + "refreshes_every_file": "é‡æ–°čŽ€å–æ‰€æœ‰įžæœ‰čˆ‡æ–°åĸžæĒ”æĄˆ", "refreshing_encoded_video": "æ­Ŗåœ¨é‡æ–°æ•´į†åˇ˛įˇ¨įĸŧįš„åŊąį‰‡", - "refreshing_faces": "æ­Ŗåœ¨é‡æ–°æ•´į†č‡‰å­”čŗ‡æ–™", - "refreshing_metadata": "æ­Ŗåœ¨é‡æ–°æ•´į†čŠŽé‡‹čŗ‡æ–™", - "regenerating_thumbnails": "æ­Ŗåœ¨é‡æ–°į”ĸį”Ÿį¸Žåœ–", + "refreshing_faces": "重整éĸéƒ¨čŗ‡æ–™ä¸­", + "refreshing_metadata": "æ­Ŗåœ¨é‡æ–°æ•´į†ä¸­įšŧčŗ‡æ–™", + "regenerating_thumbnails": "重新į”ĸį”Ÿį¸Žåœ–ä¸­", "remote": "遠į̝", "remote_assets": "遠įĢ¯é …į›Ž", "remote_media_summary": "遠į̝åĒ’éĢ”æ‘˜čρ", @@ -1745,39 +1849,46 @@ "remove_assets_shared_link_confirmation": "įĸē厚čĻåžžæ­¤åˆ†äēĢ逪įĩä¸­į§ģ除 {count, plural, other {# å€‹é …į›Ž}} 嗎īŧŸ", "remove_assets_title": "į§ģ除æĒ”æĄˆīŧŸ", "remove_custom_date_range": "į§ģ除č‡Ē訂æ—ĨæœŸį¯„åœ", - "remove_filter": "į§ģé™¤į¯Šé¸å™¨", + "remove_deleted_assets": "į§ģ除é›ĸ᎚æĒ”æĄˆ", "remove_from_album": "åžžį›¸į°ŋ中į§ģ除", "remove_from_album_action_prompt": "åˇ˛åžžį›¸į°ŋ中į§ģ除äē† {count} å€‹é …į›Ž", "remove_from_favorites": "åžžæ”ļ藏中į§ģ除", - "remove_from_lock_folder_action_prompt": "åˇ˛åžžã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€į§ģ除 {count} å€‹é …į›Ž", - "remove_from_locked_folder": "åžžã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€ä¸­į§ģ除", - "remove_from_locked_folder_confirmation": "您įĸē厚čρ將這äē›į›¸į‰‡čˆ‡åŊąį‰‡į§ģå‡ēã€Œåˇ˛ä¸ŠéŽ–įš„čŗ‡æ–™å¤žã€å—ŽīŧŸį§ģå‡ē垌將會å‡ēįžåœ¨æ‚¨įš„åĒ’éĢ”åēĢ中。", + "remove_from_lock_folder_action_prompt": "åˇ˛åžžã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žä¸­į§ģ除 {count} å€‹é …į›Ž", + "remove_from_locked_folder": "åžžã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žä¸­į§ģ除", + "remove_from_locked_folder_confirmation": "您įĸē厚čρ將這äē›į›¸į‰‡čˆ‡åŊąį‰‡į§ģå‡ēã€Œåˇ˛éŽ–åŽšã€čŗ‡æ–™å¤žå—ŽīŧŸį§ģå‡ē垌將會å‡ēįžåœ¨æ‚¨įš„åĒ’éĢ”åēĢ中。", "remove_from_shared_link": "åžžå…ąäēĢ逪įĩä¸­į§ģ除", "remove_memory": "į§ģ除記æ†ļ", "remove_photo_from_memory": "å°‡åœ–į‰‡åžžæ­¤č¨˜æ†ļ中į§ģ除", "remove_tag": "į§ģé™¤æ¨™įą¤", + "remove_url": "į§ģ除 URL", "remove_user": "į§ģ除äŊŋᔍ者", "removed_api_key": "厞į§ģ除 API 金鑰īŧš{name}", "removed_from_archive": "垞封存中į§ģ除", "removed_from_favorites": "åˇ˛åžžæ”ļ藏中į§ģ除", "removed_from_favorites_count": "厞į§ģ除æ”ļč—įš„ {count, plural, other {# å€‹é …į›Ž}}", "removed_memory": "厞į§ģ除記æ†ļ", + "removed_photo_from_memory": "åˇ˛åžžč¨˜æ†ļ中į§ģ除ᛏቇ", "removed_tagged_assets": "厞į§ģ除 {count, plural, one {# 個æĒ”æĄˆ} other {# 個æĒ”æĄˆ}}įš„æ¨™įą¤", - "rename": "重新å‘Ŋ名", + "rename": "攚名", + "repair": "įŗžæ­Ŗ", + "repair_no_results_message": "æœĒčĸĢčŋŊčš¤åŠéēå¤ąįš„æĒ”æĄˆæœƒéĄ¯į¤ēåœ¨é€™čŖĄ", + "replace_with_upload": "į”¨ä¸Šå‚ŗįš„æĒ”æĄˆå–äģŖ", "repository": "å„˛å­˜åēĢ", "require_password": "需čρ坆įĸŧ", + "require_user_to_change_password_on_first_login": "čĻæą‚äŊŋį”¨č€…åœ¨éĻ–æŦĄį™ģå…Ĩæ™‚čŽŠæ›´å¯†įĸŧ", "rescan": "重新掃描", "reset": "重設", "reset_password": "é‡č¨­å¯†įĸŧ", "reset_people_visibility": "重設äēēį‰Šå¯čĻ‹æ€§", "reset_pin_code": "重設 PIN įĸŧ", "reset_pin_code_description": "č‹Ĩåŋ˜č¨˜ PIN įĸŧīŧŒæ‚¨å¯äģĨ聝įĩĄäŧ翜å™¨įŽĄį†å“Ąé€˛čĄŒé‡č¨­", + "reset_pin_code_success": "PIN įĸŧåˇ˛æˆåŠŸé‡č¨­", "reset_pin_code_with_password": "您可隨時äŊŋį”¨æ‚¨įš„å¯†įĸŧ來重設 PIN įĸŧ", "reset_sqlite": "重設 SQLite čŗ‡æ–™åēĢ", "reset_sqlite_clear_app_data": "æ¸…é™¤čŗ‡æ–™", - "reset_sqlite_confirmation": "įĸē厚čĻé‡č¨­æ‰€æœ‰čŗ‡æ–™å—ŽīŧŸäŊ įš„æ‰€æœ‰č¨­åŽšå°‡čĸĢ重設īŧŒä¸”äŊ æœƒčĸĢį™ģå‡ē。", - "reset_sqlite_confirmation_note": "æŗ¨æ„īŧšäŊ éœ€čĻåœ¨æ¸…é™¤čŗ‡æ–™åžŒé‡æ–°é–‹å•Ÿ App。", - "reset_sqlite_done": "čŗ‡æ–™åˇ˛æ¸…é™¤ã€‚čĢ‹é‡å•Ÿ Immich 及重新į™ģå…Ĩ。", + "reset_sqlite_confirmation": "įĸē厚čĻé‡č¨­æ‰€æœ‰čŗ‡æ–™å—ŽīŧŸäŊ įš„æ‰€æœ‰č¨­įŊŽå°‡čĸĢ重設īŧŒä¸”äŊ æœƒčĸĢį™ģå‡ē。", + "reset_sqlite_confirmation_note": "æŗ¨æ„īŧšäŊ éœ€čĻåœ¨æ¸…é™¤čŗ‡æ–™åžŒé‡æ–°é–‹å•Ÿæ‡‰į”¨ã€‚", + "reset_sqlite_done": "čŗ‡æ–™åˇ˛æ¸…é™¤ã€‚čĢ‹é‡å•ŸImmich及重新į™ģå…Ĩ。", "reset_sqlite_success": "åˇ˛æˆåŠŸé‡č¨­ SQLite čŗ‡æ–™åēĢ", "reset_to_default": "重設į‚ē預設å€ŧ", "resolution": "č§ŖæžåēĻ", @@ -1785,31 +1896,33 @@ "resolved_all_duplicates": "厞觪æąēæ‰€æœ‰é‡č¤‡é …į›Ž", "restore": "還原", "restore_all": "全部還原", + "restore_trash_action_prompt": "åˇ˛åžžåžƒåœžæĄļ還原 {count} å€‹é …į›Ž", "restore_user": "還原äŊŋᔍ者", "restored_asset": "åˇ˛é‚„åŽŸæĒ”æĄˆ", "resume": "įšŧįēŒ", - "resume_paused_jobs": "垊原 {count, plural, one {# æšĢåœįš„äģģ務} other {# æšĢåœįš„äģģ務}}", - "review_duplicates": "æĒĸæŸĨé‡č¤‡å…§åŽš", - "review_large_files": "æĒĸæŸĨ大型æĒ”æĄˆ", + "resume_paused_jobs": "æĸ垊 {count, plural, one {# æšĢåœįš„äģģ務} other {# æšĢåœįš„äģģ務}}", + "retry_upload": "é‡æ–°ä¸Šå‚ŗ", + "review_duplicates": "æĒĸčĻ–é‡č¤‡é …į›Ž", + "review_large_files": "æĒĸčĻ–å¤§åž‹æĒ”æĄˆ", "role": "角色", "role_editor": "ᎍčŧ¯č€…", "role_viewer": "æĒĸčϖ者", "running": "åŸˇčĄŒä¸­", "save": "å„˛å­˜", - "saved": "åˇ˛å„˛å­˜", + "save_to_gallery": "å„˛å­˜åˆ°į›¸į°ŋ", + "saved": "厞äŋå­˜", "saved_api_key": "åˇ˛å„˛å­˜ API 金鑰", "saved_profile": "åˇ˛å„˛å­˜å€‹äēēčŗ‡æ–™", "saved_settings": "åˇ˛å„˛å­˜č¨­åŽš", "say_something": "čĒĒčĒĒæ‚¨įš„æƒŗæŗ•吧", "scaffold_body_error_occurred": "į™ŧį”ŸéŒ¯čǤ", - "scaffold_body_error_unrecoverable": "į™ŧį”Ÿį„Ąæŗ•åžŠåŽŸįš„éŒ¯čĒ¤ã€‚čĢ‹åœ¨ Discord 或 Github 上分äēĢ錯čĒ¤čŗ‡č¨ŠåŠå †į–ŠčŋŊ蚤īŧŒäģĨäžŋ我們提䞛協劊。在čĸĢåģēč­°įš„æƒ…æŗä¸‹äŊ å¯äģĨ在下斚嘗čŠĻæ¸…é™¤į¨‹åŧčŗ‡æ–™ã€‚", + "scaffold_body_error_unrecoverable": "į™ŧį”Ÿį„Ąæŗ•æĸåžŠįš„éŒ¯čĒ¤ã€‚čĢ‹åœ¨ Discord 或 Github 上分äēĢ錯čĒ¤čŗ‡č¨ŠåŠå †į–ŠčŋŊ蚤īŧŒäģĨäžŋ我們提䞛協劊。在čĸĢåģēč­°įš„æƒ…æŗä¸‹äŊ å¯äģĨ在下斚嘗čŠĻæ¸…é™¤į¨‹åŧčŗ‡æ–™ã€‚", "scan": "掃描", "scan_all_libraries": "æŽƒææ‰€æœ‰į›¸į°ŋ", "scan_library": "掃描", + "scan_settings": "æŽƒæč¨­åŽš", "scanning": "æ­Ŗåœ¨æŽƒæ", "scanning_for_album": "æŽƒæį›¸į°ŋ中â€Ļâ€Ļ", - "screencast_mode_description": "在čžĸåš•ä¸ŠéĄ¯į¤ēéĩį›¤å’Œæģ‘éŧ æ“äŊœ", - "screencast_mode_title": "å•Ÿį”¨čžĸ嚕投åŊ࿍Ąåŧ", "search": "搜尋", "search_albums": "æœå°‹į›¸į°ŋ", "search_by_context": "äģĨ情åĸƒæœå°‹", @@ -1817,11 +1930,9 @@ "search_by_description_example": "åœ¨æ˛™åŖŠįš„åĨ行之æ—Ĩ", "search_by_filename": "䞝æĒ”名或副æĒ”名搜尋", "search_by_filename_example": "åĻ‚ IMG_1234.JPG 或 PNG", - "search_by_full_path": "äžåŽŒæ•´čˇ¯åž‘æˆ–čŗ‡æ–™å¤žæœå°‹", - "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - 您可äģĨ搜尋 Projects、3D、Printing、2026 į­‰ã€‚", - "search_by_ocr": "透過 OCR 搜尋", + "search_by_ocr": "透過OCR搜尋", "search_by_ocr_example": "æ‹ŋéĩ", - "search_camera_lens_model": "æœå°‹éĄé ­åž‹č™Ÿ...", + "search_camera_lens_model": "蒐į´ĸéĄé ­åž‹č™Ÿ...", "search_camera_make": "æœå°‹į›¸æŠŸčŖŊ造商â€Ļ", "search_camera_model": "æœå°‹į›¸æŠŸåž‹č™Ÿâ€Ļ", "search_city": "搜尋城市â€Ļ", @@ -1838,6 +1949,7 @@ "search_filter_location_title": "選擇äŊįŊŽ", "search_filter_media_type": "åĒ’éĢ”éĄžåž‹", "search_filter_media_type_title": "選擇åĒ’éĢ”éĄžåž‹", + "search_filter_ocr": "透過OCR搜尋", "search_filter_people_title": "選擇äēēį‰Š", "search_filter_star_rating": "čŠ•åˆ†", "search_filter_tags_title": "é¸æ“‡æ¨™įą¤", @@ -1848,13 +1960,25 @@ "search_no_people_named": "æ˛’æœ‰åį‚ē「{name}ã€įš„äēēį‰Š", "search_no_result": "扞不到įĩæžœīŧŒčĢ‹å˜—čŠĻå…ļäģ–æœå°‹å­—čŠžæˆ–įĩ„合", "search_options": "搜尋選項", + "search_page_categories": "類åˆĨ", + "search_page_motion_photos": "å‹•æ…‹į›¸į‰‡", + "search_page_no_objects": "æ‰žä¸åˆ°į‰Šäģļčŗ‡č¨Š", + "search_page_no_places": "扞不到地éģžčŗ‡č¨Š", + "search_page_screenshots": "čžĸåš•æˆĒ圖", "search_page_search_photos_videos": "æœå°‹æ‚¨įš„į›¸į‰‡čˆ‡åŊąį‰‡", + "search_page_selfies": "č‡Ē拍", + "search_page_things": "äē‹į‰Š", "search_page_view_all_button": "æĒĸčĻ–å…¨éƒ¨", + "search_page_your_activity": "æ‚¨įš„æ´ģ動", + "search_page_your_map": "æ‚¨įš„čļŗčˇĄ", "search_people": "搜尋äēēį‰Š", "search_places": "搜尋地éģž", "search_rating": "æŒ‰čŠ•åˆ†æœå°‹...", + "search_result_page_new_search_hint": "æœå°‹æ–°įš„", "search_settings": "æœå°‹č¨­åŽš", "search_state": "搜尋地區â€Ļ", + "search_suggestion_list_smart_search_hint_1": "æ™ē慧搜尋功čƒŊé č¨­åˇ˛å•Ÿį”¨īŧŒåĻ‚čĻæœå°‹ä¸­įšŧčŗ‡æ–™īŧŒčĢ‹äŊŋᔍčĒžæŗ• ", + "search_suggestion_list_smart_search_hint_2": "m:æ‚¨įš„æœå°‹é—œéĩ詞", "search_tags": "æœå°‹æ¨™įą¤...", "search_timezone": "搜尋時區â€Ļ", "search_type": "æœå°‹éĄžåž‹", @@ -1868,6 +1992,7 @@ "select_albums": "é¸æ“‡į›¸į°ŋ", "select_all": "選擇全部", "select_all_duplicates": "äŋį•™æ‰€æœ‰é‡č¤‡é …", + "select_all_in": "選擇在 {group} ä¸­įš„æ‰€æœ‰é …į›Ž", "select_avatar_color": "選擇個äēē圖į¤ē顏色", "select_count": "{count, plural, one {選擇 #} other {選擇 #}}", "select_cutoff_date": "選擇æˆĒæ­ĸæ—Ĩ期", @@ -1875,13 +2000,14 @@ "select_featured_photo": "é¸å–į˛žé¸į›¸į‰‡", "select_from_computer": "åžžé›ģč…Ļ中選取", "select_keep_all": "全部äŋį•™", + "select_library_owner": "é¸æ“‡į›¸į°ŋæ“æœ‰č€…", "select_new_face": "é¸æ“‡æ–°č‡‰å­”", "select_people": "選擇äēēį‰Š", "select_person": "選取äēēį‰Š", "select_person_to_tag": "選擇čĻæ¨™č¨˜įš„äēēį‰Š", - "select_photos": "é¸å–į›¸į‰‡", - "select_quality": "選擇į•ĢčŗĒ", + "select_photos": "遏ᛏቇ", "select_trash_all": "全部åˆĒ除", + "select_user_for_sharing_page_err_album": "åģēį̋ᛏį°ŋå¤ąæ•—", "selected": "åˇ˛é¸å–", "selected_count": "{count, plural, other {åˇ˛é¸å– # 項}}", "selected_gps_coordinates": "é¸å–įš„ GPS åē§æ¨™", @@ -1906,7 +2032,7 @@ "set_profile_picture": "č¨­åŽšå€‹äēēčŗ‡æ–™åœ–į‰‡", "set_slideshow_to_fullscreen": "äģĨ全čžĸ嚕攞映åšģį‡ˆį‰‡", "set_stack_primary_asset": "č¨­åŽšå †į–Šįš„éĻ–čĻé …į›Ž", - "setting_image_navigation_enable_subtitle": "開啟垌äģĨ觸įĸ°čžĸåš•åˇĻ/åŗé‚ŠįˇŖå€åŸŸįš„æ–šåŧåˆ‡æ›ä¸Š/ä¸‹åœ–į‰‡ã€‚", + "setting_image_navigation_enable_subtitle": "開啟垌äģĨ觸įĸ°åąåš•åˇĻ/åŗé‚ŠįˇŖå€åŸŸįš„æ–šåŧåˆ‡æ›ä¸Š/ä¸‹åœ–į‰‡ã€‚", "setting_image_navigation_enable_title": "éģžæ“Šåˆ‡æ›", "setting_image_navigation_title": "åœ–į‰‡å°Žåŧ•", "setting_image_viewer_help": "čŠŗį´°čŗ‡č¨ŠæĒĸčĻ–å™¨æœƒäžåēčŧ‰å…Ĩå°åž‹į¸Žåœ–ã€ä¸­į­‰å°ē寸預čĻŊ圖īŧˆč‹Ĩå•Ÿį”¨īŧ‰īŧŒæœ€åžŒčŧ‰å…ĨåŽŸå§‹į›¸į‰‡ã€‚", @@ -1914,31 +2040,42 @@ "setting_image_viewer_original_title": "čŧ‰å…Ĩ原圖", "setting_image_viewer_preview_subtitle": "å•Ÿį”¨äģĨčŧ‰å…Ĩä¸­į­‰č§ŖæžåēĻåœ–į‰‡ã€‚åœį”¨å‰‡į›´æŽĨčŧ‰å…Ĩ原圖或僅äŊŋį”¨į¸Žåœ–ã€‚", "setting_image_viewer_preview_title": "čŧ‰å…Ĩ預čĻŊ圖", + "setting_image_viewer_title": "åœ–į‰‡", "setting_languages_apply": "åĨ—ᔍ", "setting_languages_subtitle": "čŽŠæ›´æ‡‰į”¨į¨‹åŧįš„čĒžč¨€", + "setting_notifications_notify_failures_grace_period": "čƒŒæ™¯å‚™äģŊå¤ąæ•—é€šįŸĨīŧš{duration}", + "setting_notifications_notify_hours": "{count} 小時", + "setting_notifications_notify_immediately": "įĢ‹åŗ", "setting_notifications_notify_minutes": "{count} 分鐘", + "setting_notifications_notify_never": "垞不", "setting_notifications_notify_seconds": "{count} į§’", + "setting_notifications_single_progress_subtitle": "æ¯é …įš„čŠŗį´°ä¸Šå‚ŗé€˛åēĻčŗ‡č¨Š", + "setting_notifications_single_progress_title": "éĄ¯į¤ēčƒŒæ™¯å‚™äģŊčŠŗį´°é€˛åēĻ", "setting_notifications_subtitle": "čĒŋ整通įŸĨ選項", + "setting_notifications_total_progress_subtitle": "į¸ŊéĢ”ä¸Šå‚ŗé€˛åēĻ (åˇ˛åŽŒæˆ/į¸Ŋ計)", + "setting_notifications_total_progress_title": "éĄ¯į¤ēčƒŒæ™¯å‚™äģŊį¸Ŋ進åēĻ", "setting_video_viewer_auto_play_subtitle": "開啟åŊąį‰‡æ™‚č‡Ē動開始播攞", "setting_video_viewer_auto_play_title": "č‡Ē動播攞åŊąį‰‡", "setting_video_viewer_looping_title": "åžĒį’°æ’­æ”ž", "setting_video_viewer_original_video_subtitle": "åžžäŧ翜å™¨ä¸˛æĩåŊąį‰‡æ™‚īŧŒåŗäŊŋåˇ˛æœ‰čŊ‰įĸŧį‰ˆæœŦīŧŒäģå„Ē先播攞原始į•ĢčŗĒ。這可čƒŊæœƒå°Žč‡´įˇŠčĄã€‚å„˛å­˜æ–ŧæœŦæŠŸįš„åŊąį‰‡å‰‡ä¸€åž‹äģĨ原始į•ĢčŗĒ播攞īŧŒä¸å—æ­¤č¨­åޚåŊąéŸŋ。", "setting_video_viewer_original_video_title": "一型播攞原始åŊąį‰‡", "settings": "č¨­åŽš", + "settings_require_restart": "čĢ‹é‡å•Ÿ Immich äģĨäŊŋč¨­åŽšį”Ÿæ•ˆ", "settings_saved": "č¨­åŽšåˇ˛å„˛å­˜", "setup_pin_code": "č¨­åŽš PIN įĸŧ", "share": "分äēĢ", + "share_action_prompt": "åˇ˛åˆ†äēĢäē† {count} å€‹é …į›Ž", + "share_add_photos": "新åĸžé …į›Ž", + "share_assets_selected": "åˇ˛é¸å– {count} 項", "share_dialog_preparing": "æ­Ŗåœ¨æē–å‚™...", "share_link": "分äēĢ逪įĩ", - "share_original": "äŊŋį”¨åŽŸåœ–īŧˆå¤§īŧ‰", - "share_preview": "äŊŋį”¨į¸Žåœ–īŧˆå°īŧ‰", - "share_quality_body": "é•ˇæŒ‰åˆ†äēĢæŒ‰éˆ•åŗå¯åœ¨åˆ†äēĢäš‹å‰é¸æ“‡åœ–į‰‡å“čŗĒ。", - "share_quality_title": "é¸æ“‡æ‚¨įš„åˆ†äēĢ品čŗĒ", "shared": "å…ąäēĢ", "shared_album_activities_input_disable": "į•™č¨€åŠŸčƒŊåˇ˛åœį”¨", "shared_album_activity_remove_content": "您įĸē厚čρåˆĒ除此æ´ģ動嗎īŧŸ", "shared_album_activity_remove_title": "åˆĒ除æ´ģ動", "shared_album_section_people_action_error": "įĩæŸ/åˆĒ除ᛏį°ŋå¤ąæ•—", + "shared_album_section_people_action_leave": "åžžį›¸į°ŋ中åˆĒ除äŊŋᔍ者", + "shared_album_section_people_action_remove_user": "åžžį›¸į°ŋ中åˆĒ除äŊŋᔍ者", "shared_album_section_people_title": "äēēį‰Š", "shared_by": "å…ąäēĢč‡Ē", "shared_by_user": "į”ą {user} å…ąäēĢ", @@ -1947,10 +2084,9 @@ "shared_intent_upload_button_progress_text": "{current} / {total} åˇ˛ä¸Šå‚ŗ", "shared_link_app_bar_title": "分äēĢ逪įĩ", "shared_link_clipboard_copied_massage": "厞複čŖŊ到å‰Ēč˛ŧį°ŋ", + "shared_link_clipboard_text": "逪įĩīŧš {link}\n密įĸŧīŧš {password}", "shared_link_create_error": "åģēįĢ‹åˆ†äēĢ逪įĩæ™‚į™ŧį”ŸéŒ¯čǤ", - "shared_link_custom_url_description": "äŊŋᔍč‡Ē訂 URL åį¨ąäž†äŊŋį”¨å…ąäēĢ逪įĩ", - "shared_link_custom_url_title": "č‡Ē訂 URL", - "shared_link_custom_url_warning": "č­Ļ告īŧšäŊŋį”¨æ­Ŗæ–œæ įš„č‡Ē訂 URL åį¨ąæœĒåŋ…æœƒčˆ‡äŊ æ‰€é æœŸįš„ä¸€æ¨Ŗé‹čĄŒã€‚", + "shared_link_custom_url_description": "äŊŋᔍč‡Ē訂 URL", "shared_link_edit_description_hint": "ᎍčŧ¯å…ąäēĢæčŋ°", "shared_link_edit_expire_after_option_day": "1 夊", "shared_link_edit_expire_after_option_days": "{count} 夊", @@ -1984,20 +2120,24 @@ "shared_with_partner": "與 {partner} å…ąäēĢ", "sharing": "å…ąäēĢ", "sharing_enter_password": "čρæĒĸčĻ–æ­¤é éĸčĢ‹čŧ¸å…Ĩ密įĸŧ。", + "sharing_page_album": "å…ąäēĢᛏį°ŋ", + "sharing_page_description": "åģēįĢ‹å…ąäēĢᛏį°ŋīŧŒčˆ‡æ‚¨įļ˛čˇ¯ä¸­įš„æˆå“Ąåˆ†äēĢį›¸į‰‡čˆ‡åŊąį‰‡ã€‚", + "sharing_page_empty_list": "įŠēį™Ŋ清喎", + "sharing_sidebar_description": "在側邊æŦ„éĄ¯į¤ēå…ąäēĢ逪įĩ", + "sharing_silver_appbar_create_shared_album": "åģēįĢ‹å…ąäēĢᛏį°ŋ", + "sharing_silver_appbar_share_partner": "與čĻĒå‹å…ąäēĢ", "shift_to_permanent_delete": "按 ⇧ 永䚅åˆĒ除æĒ”æĄˆ", "show_album_options": "éĄ¯į¤ēᛏį°ŋ選項", "show_albums": "éĄ¯į¤ēᛏį°ŋ", "show_all_people": "éĄ¯į¤ē所有äēēį‰Š", "show_and_hide_people": "éĄ¯į¤ē與隱藏äēēį‰Š", "show_file_location": "éĄ¯į¤ēæĒ”æĄˆäŊįŊŽ", - "show_gallery": "éĄ¯į¤ēåĒ’éĢ”åēĢ", + "show_gallery": "éĄ¯į¤ēį•ĢåģŠ", "show_hidden_people": "éĄ¯į¤ēéšąč—įš„äēēį‰Š", "show_in_timeline": "在時間čģ¸ä¸­éĄ¯į¤ē", "show_in_timeline_setting_description": "åœ¨æ‚¨įš„æ™‚é–“čģ¸ä¸­éĄ¯į¤ē這äŊäŊŋį”¨č€…įš„į›¸į‰‡å’ŒåŊąį‰‡", "show_keyboard_shortcuts": "éĄ¯į¤ēéĩᛤåŋĢæˇéĩ", - "show_less": "æ”ļčĩˇ", - "show_metadata": "éĄ¯į¤ēčŠŽé‡‹čŗ‡æ–™", - "show_more_fields": "{count, plural, one {éĄ¯į¤ē # 個更多字æŽĩ} other {éĄ¯į¤ē # 個更多字æŽĩ}}", + "show_metadata": "éĄ¯į¤ē中įšŧčŗ‡æ–™", "show_or_hide_info": "éĄ¯į¤ēæˆ–éšąč—čŗ‡č¨Š", "show_password": "éĄ¯į¤ē密įĸŧ", "show_person_options": "éĄ¯į¤ēäēēį‰Šé¸é …", @@ -2005,37 +2145,29 @@ "show_schema": "éĄ¯į¤ēæžļ構", "show_search_options": "éĄ¯į¤ē搜尋選項", "show_shared_links": "éĄ¯į¤ē分äēĢ逪įĩ", - "show_slideshow_metadata_overlay": "éĄ¯į¤ēåŊąåƒčŗ‡č¨ŠčĻ†č“‹åą¤", "show_slideshow_transition": "éĄ¯į¤ēåšģį‡ˆį‰‡čŊ‰å ´", "show_supporter_badge": "æ”¯æŒč€…åžŊįĢ ", "show_supporter_badge_description": "éĄ¯į¤ēæ”¯æŒč€…åžŊįĢ ", "show_text_recognition": "éĄ¯į¤ēæ–‡å­—čž¨č­˜", - "show_text_search_menu": "éĄ¯į¤ē文字搜尋選喎", + "show_text_search_menu": "éĄ¯į¤ēæ–‡å­—č’į´ĸ選喎", "shuffle": "隨抟排åē", "sidebar": "側邊æŦ„", "sidebar_display_description": "在側邊æŦ„ä¸­éĄ¯į¤ē逪įĩ", "sign_out": "į™ģå‡ē", "sign_up": "č¨ģ冊", "size": "į”¨é‡", - "skip": "čˇŗéŽ", "skip_to_content": "čˇŗč‡ŗå…§åŽš", "skip_to_folders": "čˇŗåˆ°čŗ‡æ–™å¤ž", "skip_to_tags": "莺čŊ‰åˆ°æ¨™įą¤", "slideshow": "åšģį‡ˆį‰‡", - "slideshow_body": "čĢ‹æ”žéŦ†č§€įœ‹īŧŒæ‚¨įš„į…§į‰‡æœƒäģĨ全čžĸåš•åšģį‡ˆį‰‡åŊĸåŧæ’­æ”žã€‚", - "slideshow_metadata_overlay_mode": "čĻ†č“‹å…§åŽš", - "slideshow_metadata_overlay_mode_description_only": "僅描čŋ°", - "slideshow_metadata_overlay_mode_full": "厌整", "slideshow_repeat": "é‡č¤‡æŠ•åŊąį‰‡", "slideshow_repeat_description": "åžĒį’°æ’­æ”ž", "slideshow_settings": "åšģį‡ˆį‰‡č¨­åŽš", - "slideshow_title": "åšģį‡ˆį‰‡", - "smart_album": "æ™ēæ…§į›¸į°ŋ", - "some_assets_already_have_a_location_warning": "éƒ¨åˆ†æ‰€é¸é …į›Žåˇ˛æœ‰äŊįŊŽčŗ‡č¨Š", "sort_albums_by": "ᛏį°ŋ排åēäžæ“š...", "sort_created": "åģēįĢ‹æ—Ĩ期", "sort_items": "é …į›Žæ•¸é‡", - "sort_modified": "äŋŽæ”šæ—Ĩ期", + "sort_modified": "æ—ĨæœŸåˇ˛äŋŽæ”š", + "sort_newest": "æœ€æ–°įš„į›¸į‰‡", "sort_oldest": "æœ€čˆŠįš„į›¸į‰‡", "sort_people_by_similarity": "äžį›¸äŧŧåēϿޒåēäēēį‰Š", "sort_recent": "æœ€æ–°įš„į›¸į‰‡", @@ -2044,6 +2176,7 @@ "stack": "å †į–Š", "stack_action_prompt": "åˇ˛å †į–Šäē†{count} å€‹é …į›Ž", "stack_duplicates": "å †į–Šé‡č¤‡é …į›Ž", + "stack_select_one_photo": "į‚ēå †į–Šé¸ä¸€åŧĩä¸ģčρᛏቇ", "stack_selected_photos": "å †į–Šé¸å–įš„į›¸į‰‡", "stacked_assets_count": "åˇ˛å †į–Š {count, plural, one {# 個æĒ”æĄˆ} other {# 個æĒ”æĄˆ}}", "stacktrace": "å †į–ŠčŋŊ蚤", @@ -2052,10 +2185,6 @@ "start_date_before_end_date": "開始æ—Ĩ期åŋ…須旊æ–ŧįĩæŸæ—Ĩ期", "state": "地區", "status": "į‹€æ…‹", - "step_delete": "åˆĒ除æ­Ĩ驟", - "step_delete_confirm": "įĸē厚čρåˆĒ除此æ­Ĩ銟嗎īŧŸ", - "step_details": "æ­ĨéŠŸčŠŗæƒ…", - "steps": "æ­Ĩ驟", "stop_casting": "停æ­ĸ投攞", "stop_motion_photo": "停æ­ĸå‹•æ…‹į›¸į‰‡", "stop_photo_sharing": "čρ停æ­ĸ分äēĢæ‚¨įš„į›¸į‰‡å—ŽīŧŸ", @@ -2064,7 +2193,7 @@ "storage": "å„˛å­˜įŠē間", "storage_label": "å„˛å­˜æ¨™įą¤", "storage_quota": "å„˛å­˜įŠē間", - "storage_usage": "厞äŊŋᔍ {used}īŧŒå…ą {available}", + "storage_usage": "ᔍäē† {used} / å…ą {available}", "submit": "提äē¤", "success": "成功", "suggestions": "åģēč­°", @@ -2076,18 +2205,17 @@ "swap_merge_direction": "ä礿›åˆäŊĩ斚向", "sync": "同æ­Ĩ", "sync_albums": "同æ­Ĩᛏį°ŋ", + "sync_albums_manual_subtitle": "å°‡æ‰€æœ‰ä¸Šå‚ŗįš„åŊąį‰‡čˆ‡į›¸į‰‡åŒæ­Ĩč‡ŗé¸å–įš„å‚™äģŊᛏį°ŋ", "sync_local": "同æ­ĨæœŦ抟", "sync_remote": "同æ­Ĩ遠į̝", "sync_status": "同æ­Ĩį‹€æ…‹", "sync_status_subtitle": "æĒĸčĻ–å’ŒįŽĄį†åŒæ­Ĩįŗģįĩą", "sync_upload_album_setting_subtitle": "åģēįĢ‹ä¸Ļä¸Šå‚ŗį›¸į‰‡čˆ‡åŊąį‰‡č‡ŗ Immich ä¸Šé¸å–įš„į›¸į°ŋ", - "system_theme": "įŗģįĩąä¸ģ題", - "system_theme_command_description": "äŊŋᔍ {value} įŗģįĩąä¸ģ題", "tag": "æ¨™įą¤", "tag_assets": "æ¨™č¨˜æĒ”æĄˆ", "tag_created": "厞åģēįĢ‹æ¨™įą¤īŧš{tag}", - "tag_face": "æ¨™č¨˜č‡‰å­”", "tag_feature_description": "äģĨ邏čŧ¯æ¨™č¨˜čĻæ—¨åˆ†éĄžį€čĻŊį›¸į‰‡å’ŒåŊąį‰‡", + "tag_not_found_question": "æ‰žä¸åˆ°æ¨™įą¤īŧŸåģēįĢ‹æ–°æ¨™įą¤ã€‚", "tag_people": "æ¨™įą¤äēēį‰Š", "tag_updated": "åˇ˛æ›´æ–°æ¨™įą¤īŧš{tag}", "tagged_assets": "åˇ˛æ¨™įą¤ {count, plural, one {# 個æĒ”æĄˆ} other {# 個æĒ”æĄˆ}}", @@ -2097,15 +2225,20 @@ "text_recognition": "æ–‡å­—čž¨č­˜", "theme": "ä¸ģ題", "theme_selection": "ä¸ģ題選項", - "theme_selection_description": "éš¨į€čĻŊ器或įŗģįĩąč¨­åޚīŧŒč‡Ēå‹•åˆ‡æ›æˇąč‰˛čˆ‡æˇēč‰˛æ¨Ąåŧ", + "theme_selection_description": "äžį€čĻŊ器įŗģįĩąååĨŊč‡Ēå‹•č¨­åŽšæˇąã€æˇē色ä¸ģ題", "theme_setting_asset_list_storage_indicator_title": "åœ¨é …į›Žæ¨™éĄŒä¸ŠéĄ¯į¤ēäŊŋį”¨äš‹å„˛å­˜įŠē間", "theme_setting_asset_list_tiles_per_row_title": "æ¯čĄŒåą•į¤ē {count} 項", "theme_setting_colorful_interface_subtitle": "åĨ—ᔍä¸ģ色čĒŋåˆ°čƒŒæ™¯ã€‚", "theme_setting_colorful_interface_title": "åŊŠč‰˛äģ‹éĸ", + "theme_setting_image_viewer_quality_subtitle": "čĒŋ整æĒĸčĻ–å¤§åœ–æ™‚įš„åœ–į‰‡å“čŗĒ", + "theme_setting_image_viewer_quality_title": "åœ–į‰‡å“čŗĒ", "theme_setting_primary_color_subtitle": "é¸æ“‡éĄč‰˛äŊœį‚ēä¸ģ色čĒŋ。", "theme_setting_primary_color_title": "ä¸ģ色čĒŋ", "theme_setting_system_primary_color_title": "äŊŋᔍįŗģįĩąéĄč‰˛", "theme_setting_system_theme_switch": "č‡Ē動īŧˆčˇŸéš¨įŗģįĩąč¨­åޚīŧ‰", + "theme_setting_theme_subtitle": "選擇åĨ—ᔍä¸ģ題", + "theme_setting_three_stage_loading_subtitle": "三æŽĩåŧčŧ‰å…Ĩ可čƒŊ提升čŧ‰å…Ĩ效čƒŊīŧŒäŊ†æœƒå¤§åš…åĸžåŠ įļ˛čˇ¯č˛ čŧ‰", + "theme_setting_three_stage_loading_title": "å•Ÿį”¨ä¸‰æŽĩåŧčŧ‰å…Ĩ", "then": "į„ļ垌", "they_will_be_merged_together": "厃們將會čĸĢ合äŊĩ在一čĩˇ", "third_party_resources": "įŦŦä¸‰æ–ščŗ‡æē", @@ -2131,18 +2264,24 @@ "trash_all": "全部丟掉", "trash_count": "丟掉 {count, number} 個æĒ”æĄˆ", "trash_delete_asset": "將æĒ”æĄˆä¸Ÿé€˛åžƒåœžæĄļ / åˆĒ除", + "trash_emptied": "åˇ˛æ¸…įŠē回æ”ļæĄļ", "trash_no_results_message": "垃圞æĄļä¸­įš„į›¸į‰‡å’ŒåŊąį‰‡å°‡éĄ¯į¤ēåœ¨é€™čŖĄã€‚", "trash_page_delete_all": "åˆĒ除全部", - "trash_page_info": "垃圞æĄļä¸­é …į›Žå°‡åœ¨ {days} 夊垌永䚅åˆĒ除", + "trash_page_empty_trash_dialog_content": "是åĻ清įŠē回æ”ļæĄļīŧŸé€™äē›é …į›Žå°‡čĸĢåžž Immich 中永䚅åˆĒ除", + "trash_page_info": "回æ”ļæĄļä¸­é …į›Žå°‡åœ¨ {days} 夊垌永䚅åˆĒ除", + "trash_page_no_assets": "æšĢį„Ąåˇ˛åˆĒé™¤é …į›Ž", + "trash_page_restore_all": "全部還原", + "trash_page_select_assets_btn": "é¸æ“‡é …į›Ž", + "trash_page_title": "垃圞æĄļ ({count})", "trashed_items_will_be_permanently_deleted_after": "垃圞æĄļä¸­įš„é …į›Žæœƒåœ¨ {days, plural, other {# 夊}}垌永䚅åˆĒ除。", "trigger": "觸į™ŧ", - "trigger_asset_metadata_extraction": "čŗ‡æēåžŒč¨­čŗ‡æ–™æå–", - "trigger_asset_metadata_extraction_description": "į•ļčŗ‡æēįš„EXIFåžŒč¨­čŗ‡æ–™čĸĢæå–æ™‚č§¸į™ŧ", - "trigger_asset_uploaded": "ä¸Šå‚ŗé …į›Ž", + "trigger_asset_uploaded": "é …į›Žåˇ˛ä¸Šå‚ŗ", "trigger_asset_uploaded_description": "åœ¨ä¸Šå‚ŗæ–°é …į›Žæ™‚č§¸į™ŧ", + "trigger_description": "觸į™ŧåˇĨäŊœæĩį¨‹įš„äē‹äģļ", "trigger_person_recognized": "åˇ˛čž¨č­˜äēēį‰Š", - "trigger_person_recognized_description": "識åˆĨ到äēēį‰Šæ™‚č§¸į™ŧ", - "troubleshoot": "į–‘é›ŖæŽ’č§Ŗ", + "trigger_person_recognized_description": "åĩæ¸Ŧ到äēēį‰Šæ™‚č§¸į™ŧ", + "trigger_type": "觸į™ŧéĄžåž‹", + "troubleshoot": "ᖑ雪觪᭔", "type": "éĄžåž‹", "unable_to_change_pin_code": "į„Ąæŗ•čŽŠæ›´ PIN įĸŧ", "unable_to_check_version": "į„Ąæŗ•æĒĸæŸĨæ‡‰į”¨į¨‹åŧæˆ–äŧ翜å™¨į‰ˆæœŦ", @@ -2157,7 +2296,6 @@ "unknown": "æœĒįŸĨ", "unknown_country": "æœĒįŸĨ國åŽļ", "unknown_date": "æœĒįŸĨįš„æ—Ĩ期", - "unknown_schema": "æœĒįŸĨįš„æžļ構", "unknown_year": "æœĒįŸĨåš´äģŊ", "unlimited": "不限åˆļ", "unlink_motion_video": "觪除逪įĩå‹•æ…‹åŊąį‰‡", @@ -2170,20 +2308,23 @@ "unsaved_change": "čŽŠæ›´æœĒå„˛å­˜", "unselect_all": "取æļˆå…¨é¸", "unselect_all_duplicates": "取æļˆé¸å–æ‰€æœ‰įš„é‡č¤‡é …į›Ž", + "unselect_all_in": "{group} 全不選", "unstack": "取æļˆå †į–Š", "unstack_action_prompt": "{count} 個取æļˆå †į–Š", "unstacked_assets_count": "åˇ˛č§Ŗé™¤å †į–Š {count, plural, other {# 個æĒ”æĄˆ}}", "unsupported_field_type": "ä¸æ”¯æ´įš„æŦ„äŊéĄžåž‹", - "unsupported_file_type": "不支援 {type} éĄžåž‹įš„æĒ”æĄˆīŧŒį„Ąæŗ•ä¸Šå‚ŗ {file} æĒ”æĄˆã€‚", + "unsupported_file_type": "不支持 {type} éĄžåž‹įš„æĒ”æĄˆīŧŒį„Ąæŗ•ä¸Šå‚ŗ {file} 文äģļ。", "untagged": "į„Ąæ¨™įą¤", + "untitled_workflow": "æœĒå‘Ŋ名åˇĨäŊœæĩį¨‹", "up_next": "下一個", "update_location_action_prompt": "更新 {count} å€‹æ‰€é¸é …į›Žįš„äŊįŊŽīŧš", "updated_at": "更新æ–ŧ", "updated_password": "åˇ˛æ›´æ–°å¯†įĸŧ", "upload": "ä¸Šå‚ŗ", "upload_concurrency": "ä¸Šå‚ŗä¸ĻčĄŒæ•¸", - "upload_day_count": "{date}: {count, plural, one {# å€‹ä¸Šå‚ŗ} other {# å€‹ä¸Šå‚ŗ}}", "upload_details": "ä¸Šå‚ŗčŠŗį´°čŗ‡č¨Š", + "upload_dialog_info": "是åĻčĻå°‡æ‰€é¸é …į›Žå‚™äģŊ到äŧ翜å™¨īŧŸ", + "upload_dialog_title": "ä¸Šå‚ŗé …į›Ž", "upload_error_with_count": "{count, plural, one {# å€‹é …į›Ž} other {# å€‹é …į›Ž}}ä¸Šå‚ŗéŒ¯čǤ", "upload_errors": "ä¸Šå‚ŗåŽŒæˆīŧŒäŊ†æœ‰ {count, plural, other {# 個錯čǤ}}。čĢ‹é‡æ–°æ•´į†é éĸäģĨæŸĨįœ‹æ–°ä¸Šå‚ŗįš„é …į›Žã€‚", "upload_finished": "ä¸Šå‚ŗåŽŒæˆ", @@ -2193,13 +2334,9 @@ "upload_status_errors": "錯čǤ", "upload_status_uploaded": "åˇ˛ä¸Šå‚ŗ", "upload_success": "ä¸Šå‚ŗæˆåŠŸīŧŒčρæĒĸčĻ–æ–°ä¸Šå‚ŗįš„æĒ”æĄˆčĢ‹é‡æ–°æ•´į†é éĸ。", - "upload_to_album_body": "對æ–ŧ不äŊŋį”¨æ‰‹å‹•ä¸Šå‚ŗåŠŸčƒŊįš„äŊŋᔍ者īŧŒįžåœ¨å¯äģĨé¸æ“‡åœ¨ä¸Šå‚ŗį…§į‰‡įš„åŒæ™‚į›´æŽĨ將å…ļ新åĸžč‡ŗį›¸į°ŋīŧŒį„Ąéœ€å…ˆä¸Šå‚ŗåžŒæ‰æ–°åĸžč‡ŗį›¸į°ŋ。", - "upload_to_album_title": "į›´æŽĨä¸Šå‚ŗåˆ°į›¸į°ŋ", "upload_to_immich": "ä¸Šå‚ŗč‡ŗ Immich ({count})", "uploading": "ä¸Šå‚ŗä¸­", "uploading_media": "é …į›Žä¸Šå‚ŗä¸­", - "uploads": "ä¸Šå‚ŗ", - "uploads_count": "{count, plural, one {# å€‹ä¸Šå‚ŗ} other {# å€‹ä¸Šå‚ŗ}}", "url": "įļ˛å€", "usage": "į”¨é‡", "use_biometric": "äŊŋį”¨į”Ÿį‰Ščž¨č­˜", @@ -2207,7 +2344,6 @@ "use_browser_locale_description": "栚據äŊ į€čĻŊå™¨įš„čĒžč¨€å’Œåœ°å€č¨­åŽšäž†æ›´æ”šæ—Ĩ期īŧŒæ™‚é–“å’Œæ•¸å­—įš„æ ŧåŧ", "use_current_connection": "äŊŋį”¨į›Žå‰įš„é€Ŗįˇš", "use_custom_date_range": "æ”šį”¨č‡Ē訂æ—ĨæœŸį¯„åœ", - "use_template": "äŊŋᔍᝄæœŦ", "user": "äŊŋᔍ者", "user_has_been_deleted": "æ­¤äŊŋᔍ者厞čĸĢåˆĒ除。", "user_id": "äŊŋᔍ者 ID", @@ -2217,6 +2353,7 @@ "user_privacy": "äŊŋį”¨č€…éšąį§", "user_purchase_settings": "čŗŧ財", "user_purchase_settings_description": "įŽĄį†æ‚¨įš„čŗŧ財", + "user_role_set": "設 {user} į‚ē{role}", "user_usage_detail": "äŊŋį”¨č€…į”¨é‡čŠŗį´°čŗ‡č¨Š", "user_usage_stats": "å¸ŗč™ŸäŊŋį”¨é‡įĩąč¨ˆ", "user_usage_stats_description": "æĒĸčĻ–å¸ŗč™ŸäŊŋį”¨é‡", @@ -2226,6 +2363,7 @@ "utilities": "åˇĨå…ˇ", "validate": "驗證", "validate_endpoint_error": "čĢ‹čŧ¸å…Ĩæœ‰æ•ˆįš„ URL", + "validation_error": "éŠ—č­‰éŒ¯čǤ", "variables": "čŽŠæ•¸", "version": "į‰ˆæœŦ", "version_announcement_closing": "æ•ŦįĨé †åŋƒīŧŒAlex", @@ -2233,11 +2371,11 @@ "version_history": "į‰ˆæœŦį´€éŒ„", "version_history_item": "{date} åŽ‰čŖäē† {version}", "video": "åŊąį‰‡", - "video_hover_setting": "æģ‘éŧ æ‡¸åœæ™‚播攞åŊąį‰‡é čĻŊ", + "video_hover_setting": "éŠæ¨™åœį•™æ™‚æ’­æ”žåŊąį‰‡į¸Žåœ–", "video_hover_setting_description": "į•ļæģ‘éŧ åœåœ¨é …į›Žä¸Šæ™‚æ’­æ”žåŊąį‰‡į¸Žåœ–ã€‚åŗäŊŋåœį”¨æ­¤åŠŸčƒŊīŧŒäģå¯é€éŽå°‡æģ‘éŧ åœåœ¨æ’­æ”žåœ–į¤ē上䞆開始播攞。", - "video_quality": "åŊąį‰‡į•ĢčŗĒ", "videos": "åŊąį‰‡", "videos_count": "{count, plural, other {# 部åŊąį‰‡}}", + "videos_only": "åĒå…č¨ąåŊąį‰‡", "view": "æĒĸčĻ–", "view_album": "æĒĸčϖᛏį°ŋ", "view_all": "į€čĻŊ全部", @@ -2246,29 +2384,29 @@ "view_details": "æĒĸčĻ–čŠŗį´°čŗ‡č¨Š", "view_in_timeline": "在時間čģ¸ä¸­æĒĸčĻ–", "view_link": "æĒĸčϖ逪įĩ", + "view_links": "æĒĸčϖ逪įĩ", "view_name": "æĒĸčĻ–åˆ†éĄž", "view_next_asset": "æĒĸčϖ䏋䏀項", "view_previous_asset": "æĒĸčĻ–ä¸Šä¸€é …", "view_qr_code": "æĒĸčĻ– QR code", "view_similar_photos": "æĒĸčϖᛏäŧŧᛏቇ", "view_stack": "æĒĸčĻ–å †į–Š", + "view_user": "éĄ¯į¤ēäŊŋᔍ者", "viewer_remove_from_stack": "åžžå †į–Šä¸­į§ģ除", + "viewer_stack_use_as_main_asset": "äŊœį‚ēä¸ģé …į›ŽäŊŋᔍ", + "viewer_unstack": "取æļˆå †į–Š", "visibility": "可čĻ–æ€§", "visibility_changed": "åˇ˛čŽŠæ›´ {count, plural, other {# äŊäēēį‰Š}}įš„å¯čĻ‹æ€§", "visual": "čĻ–čĻēįš„", + "visual_builder": "čĻ–čĻēæ§‹åģē器", "waiting": "åž…č™•į†", "waiting_count": "åž…č™•į†īŧš{count}", "warning": "č­Ļ告", "week": "週", "welcome": "æ­ĄčŋŽ", "welcome_to_immich": "æ­ĄčŋŽäŊŋᔍ Immich", - "whats_new": "有äģ€éēŧæ–°æąčĨŋ", - "whats_new_settings_subtitle": "įœ‹įœ‹ Immich čŖĄæœ‰äģ€éēŧæ–°æąčĨŋ", - "whats_new_version": "į‰ˆæœŦīŧš{version}", - "when": "į•ļ", "width": "å¯ŦåēĻ", "wifi_name": "Wi-Fi åį¨ą", - "workflow": "åˇĨäŊœæĩį¨‹", "workflow_delete_prompt": "įĸē厚čρåˆĒ除此åˇĨäŊœæĩį¨‹å—ŽīŧŸ", "workflow_deleted": "åˇĨäŊœæĩį¨‹åˇ˛åˆĒ除", "workflow_description": "åˇĨäŊœæĩį¨‹čĒĒæ˜Ž", @@ -2278,17 +2416,17 @@ "workflow_name": "åˇĨäŊœæĩį¨‹åį¨ą", "workflow_navigation_prompt": "您įĸē厚čĻä¸å„˛å­˜čŽŠæ›´å°ąé›ĸ開嗎īŧŸ", "workflow_summary": "åˇĨäŊœæĩį¨‹æ‘˜čρ", - "workflow_templates": "åˇĨäŊœæĩį¨‹į¯„æœŦ", "workflow_update_success": "åˇ˛æˆåŠŸæ›´æ–°åˇĨäŊœæĩį¨‹", + "workflow_updated": "åˇĨäŊœæĩį¨‹åˇ˛æ›´æ–°", "workflows": "åˇĨäŊœæĩį¨‹", "workflows_help_text": "æ šæ“šč§¸į™ŧæĸäģļčˆ‡į¯Šé¸å™¨č‡Ēå‹•åŸˇčĄŒå‹•äŊœ", "wrong_pin_code": "PIN įĸŧ錯čǤ", - "x_of_total": "{x} / {total}", "year": "åš´", "years_ago": "{years, plural, other {# åš´}}前", "yes": "是", "you_dont_have_any_shared_links": "æ‚¨æ˛’æœ‰äģģäŊ•分äēĢ逪įĩ", "your_wifi_name": "æ‚¨įš„ Wi-Fi åį¨ą", "zero_to_clear_rating": "按 0 äģĨæ¸…é™¤é …į›ŽčŠ•åˆ†", - "zoom_image": "į¸Žæ”žåœ–į‰‡" + "zoom_image": "į¸Žæ”žåœ–į‰‡", + "zoom_to_bounds": "į¸Žæ”žåˆ°é‚Šį•Œ" } diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile index c8191e307b..89480a8cb8 100644 --- a/machine-learning/Dockerfile +++ b/machine-learning/Dockerfile @@ -1,8 +1,8 @@ ARG DEVICE=cpu -FROM python:3.11-bookworm@sha256:20ec607c68642c64c73269ce245aa0f060913f4129d15d9687850aa158e6269c AS builder-cpu +FROM python:3.11-bookworm@sha256:aa23850b91cb4c7faedac8ca9aa74ddc6eb03529a519145a589a7f35df4c5927 AS builder-cpu -FROM python:3.13-slim-trixie@sha256:f82c96458eedc847b233e582eb31336f4954b39cae020b6dcf5b3ed0e5cbcd74 AS builder-openvino +FROM python:3.13-slim-trixie@sha256:3de9a8d7aedbb7984dc18f2dff178a7850f16c1ae7c34ba9d7ecc23d0755e35f AS builder-openvino FROM builder-cpu AS builder-cuda @@ -39,23 +39,23 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --extra ${DEVICE} --no-dev --no-editable --no-install-project --compile-bytecode --no-progress --active --link-mode copy -FROM python:3.11-slim-bookworm@sha256:e2d3af735aff6eeee600b1933bedd99da6645fedf572cc12ef4cc1331f2ceebe AS prod-cpu +FROM python:3.11-slim-bookworm@sha256:04cd27899595a99dfe77709d96f08876bf2ee99139ee2f0fe9ac948005034e5b AS prod-cpu ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ MACHINE_LEARNING_MODEL_ARENA=false -FROM python:3.13-slim-trixie@sha256:f82c96458eedc847b233e582eb31336f4954b39cae020b6dcf5b3ed0e5cbcd74 AS prod-openvino +FROM python:3.13-slim-trixie@sha256:3de9a8d7aedbb7984dc18f2dff178a7850f16c1ae7c34ba9d7ecc23d0755e35f AS prod-openvino RUN apt-get update && \ apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.36.3/intel-igc-core-2_2.36.3+21719_amd64.deb && \ - wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.36.3/intel-igc-opencl-2_2.36.3+21719_amd64.deb && \ - wget -nv https://github.com/intel/compute-runtime/releases/download/26.22.38646.4/intel-opencl-icd_26.22.38646.4-0_amd64.deb && \ + wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-core-2_2.28.4+20760_amd64.deb && \ + wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.28.4/intel-igc-opencl-2_2.28.4+20760_amd64.deb && \ + wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/intel-opencl-icd_26.05.37020.3-0_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \ wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \ # TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file - wget -nv https://github.com/intel/compute-runtime/releases/download/26.22.38646.4/libigdgmm12_22.10.0_amd64.deb && \ + wget -nv https://github.com/intel/compute-runtime/releases/download/26.05.37020.3/libigdgmm12_22.9.0_amd64.deb && \ dpkg -i *.deb && \ rm *.deb && \ apt-get remove wget -yqq && \ @@ -68,7 +68,7 @@ ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ RUN apt-get update && \ # Pascal support was dropped in 9.11 - apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 tzdata && \ + apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -112,7 +112,7 @@ ARG RKNN_TOOLKIT_VERSION="v2.3.0" ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \ MACHINE_LEARNING_MODEL_ARENA=false -ADD --chmod=644 --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/ +ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/ FROM prod-${DEVICE} AS prod @@ -160,9 +160,6 @@ ENV IMMICH_SOURCE_REF=${BUILD_SOURCE_REF} ENV IMMICH_SOURCE_COMMIT=${BUILD_SOURCE_COMMIT} ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE_COMMIT} -# store huggingface in /cache subfolder to enable rootless deployment with a single volume -ENV HF_HOME=/cache/hf-cache - ENTRYPOINT ["tini", "--"] CMD ["python", "-m", "immich_ml"] diff --git a/machine-learning/conftest.py b/machine-learning/conftest.py index a5acc059d1..57741f3807 100644 --- a/machine-learning/conftest.py +++ b/machine-learning/conftest.py @@ -1,10 +1,11 @@ import json -from types import SimpleNamespace -from typing import Any, Callable, Iterator +from typing import Any, Iterator from unittest import mock +import numpy as np import pytest from fastapi.testclient import TestClient +from numpy.typing import NDArray from PIL import Image from immich_ml.config import log @@ -16,6 +17,11 @@ def pil_image() -> Image.Image: return Image.new("RGB", (600, 800)) +@pytest.fixture +def cv_image(pil_image: Image.Image) -> NDArray[np.float32]: + return np.asarray(pil_image)[:, :, ::-1] # PIL uses RGB while cv2 uses BGR + + @pytest.fixture def mock_get_model() -> Iterator[mock.Mock]: with mock.patch("immich_ml.models.cache.from_model_type", autospec=True) as mocked: @@ -123,18 +129,6 @@ def ort_session() -> Iterator[mock.Mock]: yield mocked -@pytest.fixture(scope="function") -def stub_session() -> Callable[..., mock.Mock]: - def _make(shape: tuple[Any, ...], outputs: Any = None, name: str = "input.1") -> mock.Mock: - session = mock.Mock() - session.get_inputs.return_value = [SimpleNamespace(name=name, shape=shape)] - if outputs is not None: - session.run.return_value = outputs - return session - - return _make - - @pytest.fixture(scope="function") def ann_session() -> Iterator[mock.Mock]: with mock.patch("immich_ml.sessions.ann.Ann") as mocked: diff --git a/machine-learning/immich_ml/__main__.py b/machine-learning/immich_ml/__main__.py index f305c77657..8d575a58d5 100644 --- a/machine-learning/immich_ml/__main__.py +++ b/machine-learning/immich_ml/__main__.py @@ -49,7 +49,6 @@ try: str(settings.http_keepalive_timeout_s), "--graceful-timeout", "10", - "--no-control-socket", ], ) as cmd: cmd.wait() diff --git a/machine-learning/immich_ml/config.py b/machine-learning/immich_ml/config.py index d1e44fd70a..8b383f5419 100644 --- a/machine-learning/immich_ml/config.py +++ b/machine-learning/immich_ml/config.py @@ -6,7 +6,7 @@ from pathlib import Path from socket import socket from gunicorn.arbiter import Arbiter -from pydantic import BaseModel, Field +from pydantic import BaseModel from pydantic_settings import BaseSettings, SettingsConfigDict from rich.console import Console from rich.logging import RichHandler @@ -32,20 +32,29 @@ class OcrSettings(BaseModel): class PreloadModelData(BaseModel): + clip_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None) + facial_recognition_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None) + if clip_fallback is not None: + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = clip_fallback + os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = clip_fallback + del os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] + if facial_recognition_fallback is not None: + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = facial_recognition_fallback + os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = facial_recognition_fallback + del os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] clip: ClipSettings = ClipSettings() facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings() ocr: OcrSettings = OcrSettings() class MaxBatchSize(BaseModel): + ocr_fallback: str | None = os.getenv("MACHINE_LEARNING_MAX_BATCH_SIZE__TEXT_RECOGNITION", None) + if ocr_fallback is not None: + os.environ["MACHINE_LEARNING_MAX_BATCH_SIZE__OCR"] = ocr_fallback facial_recognition: int | None = None ocr: int | None = None -def default_worker_timeout() -> int: - return 900 if os.environ.get("DEVICE") == "rocm" else 300 - - class Settings(BaseSettings): model_config = SettingsConfigDict( env_prefix="MACHINE_LEARNING_", @@ -58,7 +67,7 @@ class Settings(BaseSettings): model_ttl: int = 300 model_ttl_poll_s: int = 10 workers: int = 1 - worker_timeout: int = Field(default_factory=default_worker_timeout) + worker_timeout: int = 300 http_keepalive_timeout_s: int = 2 test_full: bool = False request_threads: int = os.cpu_count() or 4 diff --git a/machine-learning/immich_ml/main.py b/machine-learning/immich_ml/main.py index 4ac5b020ae..e7e3a719bb 100644 --- a/machine-learning/immich_ml/main.py +++ b/machine-learning/immich_ml/main.py @@ -12,7 +12,7 @@ from zipfile import BadZipFile import orjson from fastapi import Depends, FastAPI, File, Form, HTTPException -from fastapi.responses import PlainTextResponse +from fastapi.responses import ORJSONResponse, PlainTextResponse from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile from PIL.Image import Image from pydantic import ValidationError @@ -32,7 +32,6 @@ from .schemas import ( ModelIdentity, ModelTask, ModelType, - ORJSONResponse, PipelineRequest, T, ) @@ -118,6 +117,20 @@ async def preload_models(preload: PreloadModelData) -> None: ModelTask.OCR, ) + if preload.clip_fallback is not None: + log.warning( + "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. " + "Use 'MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL' and " + "'MACHINE_LEARNING_PRELOAD__CLIP__VISUAL' instead." + ) + + if preload.facial_recognition_fallback is not None: + log.warning( + "Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION'. " + "Use 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION' and " + "'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION' instead." + ) + def update_state() -> Iterator[None]: global active_requests, last_called @@ -170,10 +183,7 @@ async def predict( text: str | None = Form(default=None), ) -> Any: if image is not None: - decoded = await run(lambda: decode_pil(image)) - if decoded.width == 0 or decoded.height == 0: - raise HTTPException(400, "Image has zero width or height") - inputs: Image | str = decoded + inputs: Image | str = await run(lambda: decode_pil(image)) elif text is not None: inputs = text else: diff --git a/machine-learning/immich_ml/models/base.py b/machine-learning/immich_ml/models/base.py index 5c40b21f32..00790ce744 100644 --- a/machine-learning/immich_ml/models/base.py +++ b/machine-learning/immich_ml/models/base.py @@ -57,7 +57,7 @@ class InferenceModel(ABC): self.load() if model_kwargs: self.configure(**model_kwargs) - return self._predict(*inputs, **model_kwargs) + return self._predict(*inputs) @abstractmethod def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ... diff --git a/machine-learning/immich_ml/models/clip/textual.py b/machine-learning/immich_ml/models/clip/textual.py index 989f46a2af..7e1908e120 100644 --- a/machine-learning/immich_ml/models/clip/textual.py +++ b/machine-learning/immich_ml/models/clip/textual.py @@ -89,9 +89,7 @@ class OpenClipTextualEncoder(BaseCLIPTextualEncoder): tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix()) - pad_id = tokenizer.token_to_id(pad_token) - if pad_id is None: - raise ValueError(f"Pad token '{pad_token}' not found in tokenizer vocab") + pad_id: int = tokenizer.token_to_id(pad_token) tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id) tokenizer.enable_truncation(max_length=context_length) diff --git a/machine-learning/immich_ml/models/facial_recognition/_ops.py b/machine-learning/immich_ml/models/facial_recognition/_ops.py deleted file mode 100644 index 6bdf5f5c3c..0000000000 --- a/machine-learning/immich_ml/models/facial_recognition/_ops.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Host-side geometry for the fused face models. - -The decode, NMS and Umeyama similarity are ports of insightface (Apache-2.0, https://github.com/deepinsight/insightface) -""" - -from functools import lru_cache - -import cv2 -import numpy as np -from numpy.typing import NDArray - -from immich_ml.models.transforms import ensure_dims - -DET_SIZE = 640 -ALIGNED_SIZE = 112 - -# the FPN levels for which the fused detector emits a (scores, boxes, kps) triple, -# and the anchors each feature-map cell carries, laid out anchor-major -DET_STRIDES = (8, 16, 32) -ANCHORS_PER_CELL = 2 - -# canonical ArcFace 5-point template for a 112x112 crop -ARCFACE_DST = np.array( - [[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041]], - dtype=np.float32, -) - - -@lru_cache(maxsize=len(DET_STRIDES)) -def _anchor_centers(size: int, stride: int) -> NDArray[np.float32]: - ys, xs = np.mgrid[: size // stride, : size // stride] - centers = np.stack([xs, ys], axis=-1, dtype=np.float32).reshape(-1, 2) * stride - return np.repeat(centers, ANCHORS_PER_CELL, axis=0) - - -def decode_scrfd( - heads: list[NDArray[np.float32]], size: int = DET_SIZE -) -> tuple[NDArray[np.float32], NDArray[np.float32], NDArray[np.float32]]: - heads = [ensure_dims(head, 4) for head in heads] - scores, boxes, kps = [], [], [] - for level, stride in enumerate(DET_STRIDES): - centers = _anchor_centers(size, stride) - distance = heads[level + len(DET_STRIDES)][0] * stride - offsets = heads[level + 2 * len(DET_STRIDES)][0] * stride - scores.append(heads[level][0].squeeze(-1)) - boxes.append(np.concatenate([centers[None] - distance[:, :, :2], centers[None] + distance[:, :, 2:]], axis=2)) - kps.append(np.tile(centers, offsets.shape[2] // 2) + offsets) - - return np.concatenate(scores, axis=1), np.concatenate(boxes, axis=1), np.concatenate(kps, axis=1) - - -def nms(boxes: NDArray[np.float32], scores: NDArray[np.float32], threshold: float = 0.4) -> NDArray[np.intp]: - wh = np.column_stack([boxes[:, 0], boxes[:, 1], boxes[:, 2] - boxes[:, 0], boxes[:, 3] - boxes[:, 1]]) - keep = cv2.dnn.NMSBoxes(wh.tolist(), scores.tolist(), 0.0, threshold) # NMSBoxes treats the inputs as Sequences - return np.asarray(keep, dtype=np.intp).reshape(-1) - - -def umeyama(src: NDArray[np.float32], dst: NDArray[np.float32]) -> NDArray[np.float32]: - src_mean, dst_mean = src.mean(0), dst.mean(0) - src_c, dst_c = src - src_mean, dst - dst_mean - cov = dst_c.T @ src_c / len(src) - u, s, vt = np.linalg.svd(cov) - d = np.sign(np.linalg.det(u @ vt)) - diag = np.diag([1.0, d]) - rotation = u @ diag @ vt - scale = np.trace(np.diag(s) @ diag) / (src_c**2).sum() * len(src) - translation = dst_mean - scale * rotation @ src_mean - return np.hstack([scale * rotation, translation[:, None]], dtype=np.float32) - - -def align_face(image: NDArray[np.uint8], kps: NDArray[np.float32]) -> NDArray[np.float32]: - matrix = umeyama(kps, ARCFACE_DST) - return cv2.warpAffine(image, matrix, (ALIGNED_SIZE, ALIGNED_SIZE)).astype(np.float32) diff --git a/machine-learning/immich_ml/models/facial_recognition/detection.py b/machine-learning/immich_ml/models/facial_recognition/detection.py index a113755201..26c9208e48 100644 --- a/machine-learning/immich_ml/models/facial_recognition/detection.py +++ b/machine-learning/immich_ml/models/facial_recognition/detection.py @@ -1,31 +1,41 @@ +from typing import Any + import numpy as np +from insightface.model_zoo import RetinaFace from numpy.typing import NDArray from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import decode_pil, letterbox, normalize -from immich_ml.schemas import FaceDetectionOutput, ModelTask, ModelType - -from ._ops import DET_SIZE, decode_scrfd, nms +from immich_ml.models.transforms import decode_cv2 +from immich_ml.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType class FaceDetector(InferenceModel): depends = [] identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION) - def _predict(self, inputs: NDArray[np.uint8] | bytes, minScore: float) -> FaceDetectionOutput: - canvas, scale = letterbox(decode_pil(inputs), DET_SIZE) - blob = normalize(canvas.astype(np.float32), mean=127.5, std=128).transpose(2, 0, 1)[None] + def __init__(self, model_name: str, min_score: float = 0.7, **model_kwargs: Any) -> None: + self.min_score = model_kwargs.pop("minScore", min_score) + super().__init__(model_name, **model_kwargs) - input_name = self.session.get_inputs()[0].name - heads = self.session.run(None, {input_name: blob}) - scores, boxes, kps = decode_scrfd(heads, DET_SIZE) + def _load(self) -> ModelSession: + session = self._make_session(self.model_path) + self.model = RetinaFace(session=session) + self.model.prepare(ctx_id=0, det_thresh=self.min_score, input_size=(640, 640)) - candidates = scores >= minScore - scores, boxes, kps = scores[candidates], boxes[candidates] / scale, kps[candidates] / scale - keep = nms(boxes, scores) + return session + def _predict(self, inputs: NDArray[np.uint8] | bytes) -> FaceDetectionOutput: + inputs = decode_cv2(inputs) + + bboxes, landmarks = self._detect(inputs) return { - "boxes": boxes[keep].round(), - "scores": scores[keep], - "landmarks": kps[keep].reshape(-1, 5, 2), + "boxes": bboxes[:, :4].round(), + "scores": bboxes[:, 4], + "landmarks": landmarks, } + + def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]: + return self.model.detect(inputs) # type: ignore + + def configure(self, **kwargs: Any) -> None: + self.model.det_thresh = kwargs.pop("minScore", self.model.det_thresh) diff --git a/machine-learning/immich_ml/models/facial_recognition/recognition.py b/machine-learning/immich_ml/models/facial_recognition/recognition.py index 2b0575ec38..ed1897c9f9 100644 --- a/machine-learning/immich_ml/models/facial_recognition/recognition.py +++ b/machine-learning/immich_ml/models/facial_recognition/recognition.py @@ -4,13 +4,15 @@ from typing import Any import numpy as np import onnx import onnxruntime as ort +from insightface.model_zoo import ArcFaceONNX +from insightface.utils.face_align import norm_crop from numpy.typing import NDArray from onnx.tools.update_model_dims import update_inputs_outputs_dims from PIL import Image from immich_ml.config import log, settings from immich_ml.models.base import InferenceModel -from immich_ml.models.transforms import decode_pil, normalize, serialize_np_array +from immich_ml.models.transforms import decode_cv2, serialize_np_array from immich_ml.schemas import ( FaceDetectionOutput, FacialRecognitionOutput, @@ -20,8 +22,6 @@ from immich_ml.schemas import ( ModelType, ) -from ._ops import align_face - class FaceRecognizer(InferenceModel): depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)] @@ -37,6 +37,10 @@ class FaceRecognizer(InferenceModel): if (not self.batch_size or self.batch_size > 1) and str(session.get_inputs()[0].shape[0]) != "batch": self._add_batch_axis(self.model_path) session = self._make_session(self.model_path) + self.model = ArcFaceONNX( + self.model_path_for_format(ModelFormat.ONNX).as_posix(), + session=session, + ) return session def _predict( @@ -44,21 +48,20 @@ class FaceRecognizer(InferenceModel): ) -> FacialRecognitionOutput: if faces["boxes"].shape[0] == 0: return [] - image = np.asarray(decode_pil(inputs), dtype=np.uint8) - crops = np.stack([align_face(image, kps).transpose(2, 0, 1) for kps in faces["landmarks"]]) - embeddings = self._predict_batch(normalize(crops, mean=127.5, std=127.5)) + inputs = decode_cv2(inputs) + cropped_faces = self._crop(inputs, faces) + embeddings = self._predict_batch(cropped_faces) return self.postprocess(faces, embeddings) - def _predict_batch(self, crops: NDArray[np.float32]) -> NDArray[np.float32]: - input_name = self.session.get_inputs()[0].name - if not self.batch_size or crops.shape[0] <= self.batch_size: - return self.session.run(None, {input_name: crops})[0] + def _predict_batch(self, cropped_faces: list[NDArray[np.uint8]]) -> NDArray[np.float32]: + if not self.batch_size or len(cropped_faces) <= self.batch_size: + embeddings: NDArray[np.float32] = self.model.get_feat(cropped_faces) + return embeddings - batches = [ - self.session.run(None, {input_name: crops[i : i + self.batch_size]})[0] - for i in range(0, crops.shape[0], self.batch_size) - ] - return np.concatenate(batches, axis=0) + batch_embeddings: list[NDArray[np.float32]] = [] + for i in range(0, len(cropped_faces), self.batch_size): + batch_embeddings.append(self.model.get_feat(cropped_faces[i : i + self.batch_size])) + return np.concatenate(batch_embeddings, axis=0) def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput: return [ @@ -70,6 +73,9 @@ class FaceRecognizer(InferenceModel): for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"]) ] + def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]: + return [norm_crop(image, landmark) for landmark in faces["landmarks"]] + def _add_batch_axis(self, model_path: Path) -> None: log.debug(f"Adding batch axis to model {model_path}") proto = onnx.load(model_path) @@ -83,10 +89,4 @@ class FaceRecognizer(InferenceModel): @property def _batch_size_default(self) -> int | None: providers = ort.get_available_providers() - if ( - self.model_format == ModelFormat.ONNX - and "MIGraphXExecutionProvider" not in providers - and "OpenVINOExecutionProvider" not in providers - ): - return None - return 1 + return None if self.model_format == ModelFormat.ONNX and "OpenVINOExecutionProvider" not in providers else 1 diff --git a/machine-learning/immich_ml/models/ocr/detection.py b/machine-learning/immich_ml/models/ocr/detection.py index 48dd87854b..0a2cb8ad91 100644 --- a/machine-learning/immich_ml/models/ocr/detection.py +++ b/machine-learning/immich_ml/models/ocr/detection.py @@ -63,8 +63,7 @@ class TextDetector(InferenceModel): return OrtSession(self.model_path) # partly adapted from RapidOCR - # TODO: take the options as parameters instead of applying them through `configure` - def _predict(self, inputs: Image.Image, **kwargs: Any) -> TextDetectionOutput: + def _predict(self, inputs: Image.Image) -> TextDetectionOutput: w, h = inputs.size if w < 32 or h < 32: return self._empty diff --git a/machine-learning/immich_ml/models/ocr/recognition.py b/machine-learning/immich_ml/models/ocr/recognition.py index ea7ff565d1..6408e4818f 100644 --- a/machine-learning/immich_ml/models/ocr/recognition.py +++ b/machine-learning/immich_ml/models/ocr/recognition.py @@ -64,13 +64,11 @@ class TextRecognizer(InferenceModel): rec_batch_num=max_batch_size if max_batch_size else 6, rec_img_shape=(3, 48, 320), lang_type=self.language, - model_root_dir=self.cache_dir, ) ) return session - # TODO: take the options as parameters instead of applying them through `configure` - def _predict(self, img: Image.Image, texts: TextDetectionOutput, **kwargs: Any) -> TextRecognitionOutput: + def _predict(self, img: Image.Image, texts: TextDetectionOutput) -> TextRecognitionOutput: boxes, box_scores = texts["boxes"], texts["scores"] if boxes.shape[0] == 0: return self._empty diff --git a/machine-learning/immich_ml/models/transforms.py b/machine-learning/immich_ml/models/transforms.py index ac220a3812..3f9d93bed3 100644 --- a/machine-learning/immich_ml/models/transforms.py +++ b/machine-learning/immich_ml/models/transforms.py @@ -1,5 +1,6 @@ import string from io import BytesIO +from typing import IO import cv2 import numpy as np @@ -11,10 +12,11 @@ _PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in _PUNCTUATION_TRANS = str.maketrans("", "", string.punctuation) -def resize_pil(img: Image.Image, size: int, resample: Image.Resampling = Image.Resampling.BICUBIC) -> Image.Image: +def resize_pil(img: Image.Image, size: int) -> Image.Image: if img.width < img.height: - return img.resize((size, int((img.height / img.width) * size)), resample=resample) - return img.resize((int((img.width / img.height) * size), size), resample=resample) + return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC) + else: + return img.resize((int((img.width / img.height) * size), size), resample=Image.Resampling.BICUBIC) # https://stackoverflow.com/a/60883103 @@ -34,9 +36,7 @@ def to_numpy(img: Image.Image) -> NDArray[np.float32]: def normalize( img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32] ) -> NDArray[np.float32]: - img *= 1.0 / std - img -= mean / std - return img + return (img - mean) / std def get_pil_resampling(resample: str) -> Image.Resampling: @@ -47,21 +47,26 @@ def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]: return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore -def decode_pil(image_bytes: bytes | Image.Image | NDArray[np.uint8]) -> Image.Image: - image: Image.Image - match image_bytes: - case Image.Image(): - image = image_bytes - case np.ndarray(): - image = Image.fromarray(image_bytes) - case bytes(): - image = Image.open(BytesIO(image_bytes)) +def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image: + if isinstance(image_bytes, Image.Image): + return image_bytes + image: Image.Image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes) image.load() if not image.mode == "RGB": image = image.convert("RGB") return image +def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]: + match image_bytes: + case bytes() | memoryview() | bytearray(): + return pil_to_cv2(decode_pil(image_bytes)) # pillow is much faster than cv2 + case Image.Image(): + return pil_to_cv2(image_bytes) + case _: + return image_bytes + + def clean_text(text: str, canonicalize: bool = False) -> str: text = " ".join(text.split()) if canonicalize: @@ -73,21 +78,3 @@ def clean_text(text: str, canonicalize: bool = False) -> str: # TODO: use this in a less invasive way def serialize_np_array(arr: NDArray[np.float32]) -> str: return orjson.dumps(arr, option=orjson.OPT_SERIALIZE_NUMPY).decode() - - -def letterbox(image: NDArray[np.uint8] | Image.Image, size: int) -> tuple[NDArray[np.uint8], float]: - if isinstance(image, Image.Image): - image = np.asarray(image) - height, width = image.shape[:2] - if height > width: - new_height, new_width = size, int(size * width / height) - else: - new_width, new_height = size, int(size * height / width) - - canvas = np.zeros((size, size, 3), dtype=np.uint8) - cv2.resize(image, (new_width, new_height), dst=canvas[:new_height, :new_width]) - return canvas, new_height / height - - -def ensure_dims(array: NDArray[np.float32], ndim: int) -> NDArray[np.float32]: - return array if array.ndim >= ndim else np.expand_dims(array, axis=tuple(range(ndim - array.ndim))) diff --git a/machine-learning/immich_ml/schemas.py b/machine-learning/immich_ml/schemas.py index d5695f5eb3..41706180de 100644 --- a/machine-learning/immich_ml/schemas.py +++ b/machine-learning/immich_ml/schemas.py @@ -3,18 +3,14 @@ from typing import Any, Literal, Protocol, TypeGuard, TypeVar import numpy as np import numpy.typing as npt -import orjson -from fastapi.responses import JSONResponse from typing_extensions import TypedDict -class ORJSONResponse(JSONResponse): - def render(self, content: Any) -> bytes: - return orjson.dumps(content, option=orjson.OPT_SERIALIZE_NUMPY) - - class StrEnum(str, Enum): - __str__ = str.__str__ + value: str + + def __str__(self) -> str: + return self.value class BoundingBox(TypedDict): @@ -60,7 +56,7 @@ ModelIdentity = tuple[ModelType, ModelTask] class SessionNode(Protocol): @property - def name(self) -> str: ... + def name(self) -> str | None: ... @property def shape(self) -> tuple[int, ...]: ... diff --git a/machine-learning/immich_ml/sessions/ann/__init__.py b/machine-learning/immich_ml/sessions/ann/__init__.py index d3279f9a17..6f36f675f6 100644 --- a/machine-learning/immich_ml/sessions/ann/__init__.py +++ b/machine-learning/immich_ml/sessions/ann/__init__.py @@ -37,11 +37,11 @@ class AnnSession: def get_inputs(self) -> list[SessionNode]: shapes = self.ann.input_shapes[self.model] - return [AnnNode(f"input.{i + 1}", s) for i, s in enumerate(shapes)] + return [AnnNode(None, s) for s in shapes] def get_outputs(self) -> list[SessionNode]: shapes = self.ann.output_shapes[self.model] - return [AnnNode(f"output.{i + 1}", s) for i, s in enumerate(shapes)] + return [AnnNode(None, s) for s in shapes] def run( self, @@ -54,5 +54,5 @@ class AnnSession: class AnnNode(NamedTuple): - name: str + name: str | None shape: tuple[int, ...] diff --git a/machine-learning/immich_ml/sessions/ort.py b/machine-learning/immich_ml/sessions/ort.py index 579cafc1cd..bebd235970 100644 --- a/machine-learning/immich_ml/sessions/ort.py +++ b/machine-learning/immich_ml/sessions/ort.py @@ -1,7 +1,6 @@ from __future__ import annotations from pathlib import Path -from threading import Lock from typing import Any import numpy as np @@ -13,37 +12,6 @@ from immich_ml.schemas import ModelPrecision, SessionNode from ..config import log, settings -MigraphxInputSignature = tuple[tuple[str, str, tuple[int, ...]], ...] - -_migraphx_registry_lock = Lock() -_migraphx_model_locks: dict[str, Lock] = {} -_migraphx_compiled_inputs: set[tuple[str, MigraphxInputSignature]] = set() - - -def _migraphx_get_model_lock(model_key: str) -> Lock: - with _migraphx_registry_lock: - lock = _migraphx_model_locks.get(model_key) - if lock is None: - lock = Lock() - _migraphx_model_locks[model_key] = lock - return lock - - -def _migraphx_has_compiled_input(key: tuple[str, MigraphxInputSignature]) -> bool: - with _migraphx_registry_lock: - return key in _migraphx_compiled_inputs - - -def _migraphx_mark_compiled_input(key: tuple[str, MigraphxInputSignature]) -> None: - with _migraphx_registry_lock: - _migraphx_compiled_inputs.add(key) - - -def _migraphx_input_signature( - input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], -) -> MigraphxInputSignature: - return tuple((name, str(value.dtype), tuple(value.shape)) for name, value in sorted(input_feed.items())) - class OrtSession: session: ort.InferenceSession @@ -80,21 +48,7 @@ class OrtSession: input_feed: dict[str, NDArray[np.float32]] | dict[str, NDArray[np.int32]], run_options: Any = None, ) -> list[NDArray[np.float32]]: - if "MIGraphXExecutionProvider" in self.providers: - model_key = self.model_path.resolve().as_posix() - input_key = (model_key, _migraphx_input_signature(input_feed)) - if not _migraphx_has_compiled_input(input_key): - model_lock = _migraphx_get_model_lock(model_key) - with model_lock: - if not _migraphx_has_compiled_input(input_key): - outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options) - _migraphx_mark_compiled_input(input_key) - return outputs - - outputs = self.session.run(output_names, input_feed, run_options) - return outputs - - outputs = self.session.run(output_names, input_feed, run_options) + outputs: list[NDArray[np.float32]] = self.session.run(output_names, input_feed, run_options) return outputs @property diff --git a/machine-learning/immich_ml/sessions/rknn/__init__.py b/machine-learning/immich_ml/sessions/rknn/__init__.py index 90f2e63ed3..e388e4febc 100644 --- a/machine-learning/immich_ml/sessions/rknn/__init__.py +++ b/machine-learning/immich_ml/sessions/rknn/__init__.py @@ -69,7 +69,7 @@ class RknnSession: class RknnNode(NamedTuple): - name: str + name: str | None shape: tuple[int, ...] diff --git a/machine-learning/mise.lock b/machine-learning/mise.lock deleted file mode 100644 index 560a5bcbd3..0000000000 --- a/machine-learning/mise.lock +++ /dev/null @@ -1,79 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html - -[[tools.python]] -version = "3.11.15" -backend = "core:python" - -[tools.python."platforms.linux-arm64"] -checksum = "sha256:9ac18c9a761e91e6c6452bc0ef0082922a00a3fdec734555635d57c3169309b7" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.linux-arm64-musl"] -checksum = "sha256:bcd414f2e9d8cc5d4ac7fab2c8f912e30379c150fa6b94da19d64daf85cfc5d4" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-unknown-linux-musl-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.linux-x64"] -checksum = "sha256:0604cd029b142dc223e131f17f5941c0c8d2d5074997c8178b515b19eea2a6c2" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.linux-x64-musl"] -checksum = "sha256:45bffdb320cf4a2c2dbf183b634aeb7e28f9cb0646f715b2f4117c11084d839a" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-unknown-linux-musl-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.macos-arm64"] -checksum = "sha256:2318799eaf104f8a29bc09a93b0851b05dbbcb4ce9a5f045ddea169c0c7ff3a5" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-aarch64-apple-darwin-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.macos-x64"] -checksum = "sha256:4925e5aaa9bc77c85302d350b36c1d9def2002996a6bcfa55c88ba6eb318de29" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-apple-darwin-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[tools.python."platforms.windows-x64"] -checksum = "sha256:6589ca6d63f520bec4096d62b3ab91da3d0a80b16b594c99a6b677e335814683" -url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260623/cpython-3.11.15+20260623-x86_64-pc-windows-msvc-install_only_stripped.tar.gz" -provenance = "github-attestations" - -[[tools.uv]] -version = "0.8.15" -backend = "aqua:astral-sh/uv" - -[tools.uv."platforms.linux-arm64"] -checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419" - -[tools.uv."platforms.linux-arm64-musl"] -checksum = "sha256:23ea21a05c62c4c307ce691f29bff2f15c94c4f07f2b83d9b356f0664bc8b3a2" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-unknown-linux-musl.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235419" - -[tools.uv."platforms.linux-x64"] -checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475" - -[tools.uv."platforms.linux-x64-musl"] -checksum = "sha256:d0fec58f3124e05e0a1af0f6541abfce4333253cdaf23c7b6bb2e6128bf138ea" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-unknown-linux-musl.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235475" - -[tools.uv."platforms.macos-arm64"] -checksum = "sha256:103367962c5cb00bf7370d84cbaa3fec5a9807be9cc833ea9d8eea400c119fa2" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-aarch64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235412" - -[tools.uv."platforms.macos-x64"] -checksum = "sha256:2bbef70982e97dfc36454de173f35ec1a5e83ae11e3885df6a50db3fd76171cb" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-apple-darwin.tar.gz" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235464" - -[tools.uv."platforms.windows-x64"] -checksum = "sha256:459d95892a5cc5c21779532f4f41b9238594b79e312a5142da2148ecfa10e705" -url = "https://github.com/astral-sh/uv/releases/download/0.8.15/uv-x86_64-pc-windows-msvc.zip" -url_api = "https://api.github.com/repos/astral-sh/uv/releases/assets/289235467" diff --git a/machine-learning/mise.toml b/machine-learning/mise.toml deleted file mode 100644 index e5e30c4fc2..0000000000 --- a/machine-learning/mise.toml +++ /dev/null @@ -1,36 +0,0 @@ -[tools] -python = "3.11" -uv = "0.8.15" - -[tasks.install] -run = "uv sync --locked" - -[tasks.lint] -run = "uv run ruff check immich_ml" - -[tasks.test] -run = "uv run pytest --cov=immich_ml --cov-report term-missing" - -[tasks.format] -run = "uv run ruff format immich_ml" - -[tasks.check] -run = "uv run mypy --strict immich_ml/" - -[tasks.ci-unit] -run = [ - { task = ":install --extra cpu" }, - { task = ":format" }, - { task = ":lint --output-format=github" }, - { task = ":check" }, - { task = ":test" }, -] - -[tasks.checklist] -run = [ - { task = ":install" }, - { task = ":format" }, - { task = ":lint" }, - { task = ":check" }, - { task = ":test" }, -] diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index e95ef7afef..7f0de412c5 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "immich-ml" -version = "3.1.0" +version = "2.7.3" description = "" authors = [{ name = "Hau Tran", email = "alex.tran1502@gmail.com" }] requires-python = ">=3.11,<4.0" @@ -9,12 +9,12 @@ dependencies = [ "aiocache>=0.12.1,<1.0", "fastapi>=0.95.2,<1.0", "gunicorn>=21.1.0", - "huggingface-hub>=1.0,<2.0", - "numpy>=2.4.0,<3.0", - "onnx>=1.22.0", + "huggingface-hub>=0.20.1,<1.0", + "insightface>=0.7.3,<1.0", + "numpy<2.4.0", "opencv-python-headless>=4.7.0.72,<5.0", "orjson>=3.9.5", - "pillow>=12.2,<13", + "pillow>=12.1.1,<12.2", "pydantic>=2.0.0,<3", "pydantic-settings>=2.5.2,<3", "python-multipart>=0.0.6,<1.0", diff --git a/machine-learning/test_main.py b/machine-learning/test_main.py index 567c6ab176..0182c57c67 100644 --- a/machine-learning/test_main.py +++ b/machine-learning/test_main.py @@ -7,6 +7,7 @@ from types import SimpleNamespace from typing import Any, Callable from unittest import mock +import cv2 import numpy as np import onnxruntime as ort import orjson @@ -34,37 +35,7 @@ from immich_ml.sessions.ort import OrtSession from immich_ml.sessions.rknn import RknnSession, run_inference -class FakeLock: - def __init__(self) -> None: - self.enter = mock.Mock() - self.exit = mock.Mock() - - def __enter__(self) -> None: - self.enter() - - def __exit__(self, *args: object) -> None: - self.exit(*args) - - class TestBase: - def test_sets_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.delenv("DEVICE", raising=False) - monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False) - - assert Settings().worker_timeout == 300 - - def test_sets_rocm_default_worker_timeout(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setenv("DEVICE", "rocm") - monkeypatch.delenv("MACHINE_LEARNING_WORKER_TIMEOUT", raising=False) - - assert Settings().worker_timeout == 900 - - def test_worker_timeout_env_override(self, monkeypatch: MonkeyPatch) -> None: - monkeypatch.setenv("DEVICE", "rocm") - monkeypatch.setenv("MACHINE_LEARNING_WORKER_TIMEOUT", "1200") - - assert Settings().worker_timeout == 1200 - def test_sets_default_cache_dir(self) -> None: encoder = OpenClipTextualEncoder("ViT-B-32__openai") @@ -442,52 +413,6 @@ class TestOrtSession: assert sess_options is session.sess_options - def test_serializes_rocm_first_run_for_new_input_signature(self, mocker: MockerFixture) -> None: - lock = FakeLock() - get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) - mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set()) - mocker.patch("immich_ml.sessions.ort.Path.mkdir") - session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"]) - input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} - - session.run(None, input_feed) - session.run(None, input_feed) - - lock.enter.assert_called_once() - lock.exit.assert_called_once() - get_model_lock.assert_called_once() - session.session.run.assert_has_calls([mock.call(None, input_feed, None), mock.call(None, input_feed, None)]) - - def test_serializes_rocm_run_for_each_new_input_signature(self, mocker: MockerFixture) -> None: - lock = FakeLock() - mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) - mocker.patch("immich_ml.sessions.ort._migraphx_compiled_inputs", set()) - mocker.patch("immich_ml.sessions.ort.Path.mkdir") - session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["MIGraphXExecutionProvider"]) - input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} - new_shape_input_feed = {"input": np.random.rand(2, 3, 224, 224).astype(np.float32)} - - session.run(None, input_feed) - session.run(None, new_shape_input_feed) - - assert lock.enter.call_count == 2 - assert lock.exit.call_count == 2 - session.session.run.assert_has_calls( - [mock.call(None, input_feed, None), mock.call(None, new_shape_input_feed, None)] - ) - - def test_does_not_serialize_non_rocm_run(self, mocker: MockerFixture) -> None: - lock = FakeLock() - get_model_lock = mocker.patch("immich_ml.sessions.ort._migraphx_get_model_lock", return_value=lock) - session = OrtSession("/cache/ViT-B-32__openai/model.onnx", providers=["CPUExecutionProvider"]) - input_feed = {"input": np.random.rand(1, 3, 224, 224).astype(np.float32)} - - session.run(None, input_feed) - - get_model_lock.assert_not_called() - lock.enter.assert_not_called() - session.session.run.assert_called_once_with(None, input_feed, None) - class TestAnnSession: def test_creates_ann_session(self, ann_session: mock.Mock, info: mock.Mock) -> None: @@ -515,7 +440,7 @@ class TestAnnSession: inputs = session.get_inputs() assert len(inputs) == 1 - assert inputs[0].name == "input.1" + assert inputs[0].name is None assert inputs[0].shape == (1, 3, 224, 224) def test_get_outputs(self, ann_session: mock.Mock) -> None: @@ -526,7 +451,7 @@ class TestAnnSession: outputs = session.get_outputs() assert len(outputs) == 1 - assert outputs[0].name == "output.1" + assert outputs[0].name is None assert outputs[0].shape == (1, 3, 224, 224) def test_run(self, ann_session: mock.Mock, mocker: MockerFixture) -> None: @@ -780,193 +705,85 @@ class TestCLIP: assert np.allclose(tokens["attention_mask"], np.array([mock_attention_mask], dtype=np.int32), atol=0) -def make_scrfd_heads(detections: list[tuple[int, int, float]]) -> list[np.ndarray]: - """Build the 9 head tensors a SCRFD keypoint model emits at 640x640. - - `detections` is a list of (cell_x, cell_y, score) placed on the stride-8 level. - Distances and keypoint offsets are fixed so the decoded geometry is known by - hand rather than derived from the code under test: - box = [cx - 1*8, cy - 2*8, cx + 3*8, cy + 4*8] - kps = [cx, cy] + [0, 1, 2, ... 9] * 8, reshaped to 5 points - """ - heads: list[np.ndarray] = [] - counts = [(640 // stride) ** 2 * 2 for stride in (8, 16, 32)] - for channels in (1, 4, 10): - for n in counts: - heads.append(np.zeros((n, channels), dtype=np.float32)) - for cell_x, cell_y, score in detections: - i = 2 * (cell_y * 80 + cell_x) # anchor-major, 2 anchors per cell - heads[0][i] = score - heads[3][i] = [1, 2, 3, 4] - heads[6][i] = np.arange(10) - return heads - - -def expected_box(cell_x: int, cell_y: int) -> list[float]: - cx, cy = cell_x * 8, cell_y * 8 - return [cx - 8, cy - 16, cx + 24, cy + 32] - - -def expected_landmarks(cell_x: int, cell_y: int) -> np.ndarray: - cx, cy = cell_x * 8, cell_y * 8 - return (np.tile([cx, cy], 5) + np.arange(10) * 8).reshape(5, 2).astype(np.float32) - - -@pytest.fixture -def batch_axis(mocker: MockerFixture) -> SimpleNamespace: - """Patches for the `_add_batch_axis` path: the onnx module it rewrites the model - through, and the download it would otherwise trigger on load.""" - mocker.patch("immich_ml.models.base.InferenceModel.download") - return SimpleNamespace( - onnx=mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True), - update_dims=mocker.patch( - "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True - ), - ) - - class TestFaceRecognition: - def test_detection(self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture) -> None: + def test_set_min_score(self, snapshot_download: mock.Mock, ort_session: mock.Mock, path: mock.Mock) -> None: + path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" + + face_detector = FaceDetector("buffalo_s", min_score=0.5, cache_dir="test_cache") + face_detector.load() + + assert face_detector.min_score == 0.5 + assert face_detector.model.det_thresh == 0.5 + + def test_detection(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: mocker.patch.object(FaceDetector, "load") - face_detector = FaceDetector("buffalo_s", cache_dir="test_cache") + face_detector = FaceDetector("buffalo_s", min_score=0.0, cache_dir="test_cache") - session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9), (50, 50, 0.8)])) - face_detector.session = session + det_model = mock.Mock() + num_faces = 2 + bbox = np.random.rand(num_faces, 4).astype(np.float32) + scores = np.array([[0.67]] * num_faces).astype(np.float32) + kpss = np.random.rand(num_faces, 5, 2).astype(np.float32) + det_model.detect.return_value = (np.concatenate([bbox, scores], axis=-1), kpss) + face_detector.model = det_model - faces = face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.7) + faces = face_detector.predict(cv_image) assert isinstance(faces, dict) - assert set(faces) == {"boxes", "scores", "landmarks"} - # NMS returns highest score first - assert faces["boxes"].tolist() == [expected_box(10, 10), expected_box(50, 50)] - assert np.allclose(faces["scores"], [0.9, 0.8]) - assert faces["landmarks"].shape == (2, 5, 2) - assert np.allclose(faces["landmarks"][0], expected_landmarks(10, 10)) - assert np.allclose(faces["landmarks"][1], expected_landmarks(50, 50)) + assert isinstance(faces.get("boxes", None), np.ndarray) + assert isinstance(faces.get("landmarks", None), np.ndarray) + assert isinstance(faces.get("scores", None), np.ndarray) + assert np.equal(faces["boxes"], bbox.round()).all() + assert np.equal(faces["landmarks"], kpss).all() + assert np.equal(faces["scores"], scores).all() + det_model.detect.assert_called_once() - def test_detection_applies_min_score_per_request( - self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture - ) -> None: - mocker.patch.object(FaceDetector, "load") - face_detector = FaceDetector("buffalo_s", cache_dir="test_cache") - - session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9), (50, 50, 0.5)])) - face_detector.session = session - - # the threshold is a request parameter, so the same loaded model must honour both - assert face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.7)["boxes"].shape[0] == 1 - assert face_detector.predict(Image.new("RGB", (640, 640)), minScore=0.4)["boxes"].shape[0] == 2 - - def test_detection_scales_boxes_back_to_the_original_image( - self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture - ) -> None: - mocker.patch.object(FaceDetector, "load") - face_detector = FaceDetector("buffalo_s", cache_dir="test_cache") - - session = stub_session((1, 3, 640, 640), outputs=make_scrfd_heads([(10, 10, 0.9)])) - face_detector.session = session - - # a 320x320 image is letterboxed up to 640, so coordinates come back halved - faces = face_detector.predict(Image.new("RGB", (320, 320)), minScore=0.7) - - assert faces["boxes"].tolist() == [[v / 2 for v in expected_box(10, 10)]] - assert np.allclose(faces["landmarks"][0], expected_landmarks(10, 10) / 2) - - def test_recognition(self, stub_session: Callable[..., mock.Mock], mocker: MockerFixture) -> None: + def test_recognition(self, cv_image: cv2.Mat, mocker: MockerFixture) -> None: mocker.patch.object(FaceRecognizer, "load") - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], - ) - face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache") + face_recognizer = FaceRecognizer("buffalo_s", min_score=0.0, cache_dir="test_cache") - # a uniform grey image whose crops land wholly inside it, so every sampled - # pixel is 128 and the normalised value the session receives is exact - image = Image.new("RGB", (600, 800), (128, 128, 128)) num_faces = 2 - arcface_dst = np.array( - [[38.2946, 51.6963], [73.5318, 51.5014], [56.0252, 71.7366], [41.5493, 92.3655], [70.7299, 92.2041]], - dtype=np.float32, - ) - kpss = np.stack([arcface_dst * 2 + [200, 300], arcface_dst * 2 + [220, 320]]).astype(np.float32) bbox = np.random.rand(num_faces, 4).astype(np.float32) scores = np.array([0.67] * num_faces).astype(np.float32) - embeddings = np.random.rand(num_faces, 512).astype(np.float32) + kpss = np.random.rand(num_faces, 5, 2).astype(np.float32) + faces = {"boxes": bbox, "landmarks": kpss, "scores": scores} - session = stub_session(("batch", 3, 112, 112), outputs=[embeddings]) - face_recognizer.session = session + rec_model = mock.Mock() + embedding = np.random.rand(num_faces, 512).astype(np.float32) + rec_model.get_feat.return_value = embedding + face_recognizer.model = rec_model - faces = face_recognizer.predict(image, {"boxes": bbox, "landmarks": kpss, "scores": scores}) + faces = face_recognizer.predict(cv_image, faces) assert isinstance(faces, list) assert len(faces) == num_faces for face in faces: + assert isinstance(face.get("boundingBox"), dict) assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"} assert all(isinstance(val, np.float32) for val in face["boundingBox"].values()) - embedding = orjson.loads(face["embedding"]) + embedding_str = face.get("embedding") + assert isinstance(embedding_str, str) + embedding = orjson.loads(embedding_str) assert isinstance(embedding, list) assert len(embedding) == 512 assert isinstance(face.get("score", None), np.float32) - session.run.assert_called_once() - crops = session.run.call_args.args[1]["input.1"] - assert crops.shape == (num_faces, 3, 112, 112) - assert crops.dtype == np.float32 - # mean/std 127.5, not raw 0-255. atol is loose enough for the float32 cancellation - # in normalize's scale-then-subtract, but still rejects a wrong mean or std - assert np.allclose(crops, (128 - 127.5) / 127.5, atol=1e-6) - - def test_recognition_returns_early_without_faces(self, pil_image: Image.Image, mocker: MockerFixture) -> None: - mocker.patch.object(FaceRecognizer, "load") - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], - ) - face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache") - session = mock.Mock() - face_recognizer.session = session - - empty = { - "boxes": np.empty((0, 4), dtype=np.float32), - "landmarks": np.empty((0, 5, 2), dtype=np.float32), - "scores": np.empty(0, dtype=np.float32), - } - - assert face_recognizer.predict(pil_image, empty) == [] - session.run.assert_not_called() - - def test_recognition_batches_when_batch_size_is_set( - self, pil_image: Image.Image, stub_session: Callable[..., mock.Mock], mocker: MockerFixture - ) -> None: - mocker.patch.object(FaceRecognizer, "load") - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], - ) - face_recognizer = FaceRecognizer("buffalo_s", cache_dir="test_cache") - face_recognizer.batch_size = 2 - - num_faces = 5 - session = stub_session((1, 3, 112, 112)) - session.run.side_effect = lambda _, feed: [np.zeros((feed["input.1"].shape[0], 512), dtype=np.float32)] - face_recognizer.session = session - - faces = { - "boxes": np.random.rand(num_faces, 4).astype(np.float32), - "landmarks": (np.random.rand(num_faces, 5, 2) * 100).astype(np.float32), - "scores": np.array([0.67] * num_faces, dtype=np.float32), - } - assert len(face_recognizer.predict(pil_image, faces)) == num_faces - assert session.run.call_count == 3 # 2 + 2 + 1 - assert [c.args[1]["input.1"].shape[0] for c in session.run.call_args_list] == [2, 2, 1] + rec_model.get_feat.assert_called_once() + call_args = rec_model.get_feat.call_args_list[0].args + assert len(call_args) == 1 + assert isinstance(call_args[0], list) + assert isinstance(call_args[0][0], np.ndarray) + assert call_args[0][0].shape == (112, 112, 3) def test_recognition_adds_batch_axis_for_ort( - self, batch_axis: SimpleNamespace, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture + self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture ) -> None: - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], + onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) + update_dims = mocker.patch( + "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True ) + mocker.patch("immich_ml.models.base.InferenceModel.download") + mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") ort_session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] ort_session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" @@ -983,24 +800,24 @@ class TestFaceRecognition: output_dims.type.tensor_type.shape.dim = [SimpleNamespace(dim_value=size) for size in [1, 800]] proto.graph.output = [output_dims] - batch_axis.onnx.load.return_value = proto + onnx.load.return_value = proto face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path) face_recognizer.load() assert face_recognizer.batch_size is None - batch_axis.update_dims.assert_called_once_with( - proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]} - ) - batch_axis.onnx.save.assert_called_once_with(batch_axis.update_dims.return_value, face_recognizer.model_path) + update_dims.assert_called_once_with(proto, {"input.1": ["batch", 3, 224, 224]}, {"output.1": ["batch", 800]}) + onnx.save.assert_called_once_with(update_dims.return_value, face_recognizer.model_path) def test_recognition_does_not_add_batch_axis_if_exists( - self, batch_axis: SimpleNamespace, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture + self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture ) -> None: - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], + onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) + update_dims = mocker.patch( + "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True ) + mocker.patch("immich_ml.models.base.InferenceModel.download") + mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] @@ -1011,53 +828,60 @@ class TestFaceRecognition: face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path) face_recognizer.load() - # batching is available here, so the axis is only skipped because it already exists assert face_recognizer.batch_size is None - batch_axis.update_dims.assert_not_called() - batch_axis.onnx.load.assert_not_called() - batch_axis.onnx.save.assert_not_called() + update_dims.assert_not_called() + onnx.load.assert_not_called() + onnx.save.assert_not_called() - @pytest.mark.parametrize( - ("session_fixture", "suffix", "model_kwargs", "available_providers"), - [ - pytest.param("ann_session", ".armnn", {"model_format": ModelFormat.ARMNN}, None, id="armnn"), - pytest.param( - "ort_session", ".onnx", {}, ["OpenVINOExecutionProvider", "CPUExecutionProvider"], id="openvino" - ), - pytest.param( - "ort_session", ".onnx", {}, ["MIGraphXExecutionProvider", "CPUExecutionProvider"], id="migraphx" - ), - ], - ) - def test_recognition_does_not_add_batch_axis_when_batching_is_unsupported( - self, - request: pytest.FixtureRequest, - batch_axis: SimpleNamespace, - path: mock.Mock, - ort_pybind: mock.Mock, - mocker: MockerFixture, - session_fixture: str, - suffix: str, - model_kwargs: dict[str, Any], - available_providers: list[str] | None, + def test_recognition_does_not_add_batch_axis_for_armnn( + self, ann_session: mock.Mock, path: mock.Mock, mocker: MockerFixture ) -> None: - session = request.getfixturevalue(session_fixture) - ort_pybind.get_available_openvino_device_ids.return_value = ["CPU"] - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=available_providers, + onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) + update_dims = mocker.patch( + "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True ) - path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = suffix - session.return_value.get_inputs.return_value = [SimpleNamespace(name="input.1", shape=(1, 3, 224, 224))] - session.return_value.get_outputs.return_value = [SimpleNamespace(name="output.1", shape=(1, 800))] + mocker.patch("immich_ml.models.base.InferenceModel.download") + mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".armnn" - face_recognizer = FaceRecognizer("buffalo_s", cache_dir=path, **model_kwargs) + inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] + outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))] + ann_session.return_value.get_inputs.return_value = inputs + ann_session.return_value.get_outputs.return_value = outputs + + face_recognizer = FaceRecognizer("buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path) face_recognizer.load() assert face_recognizer.batch_size == 1 - batch_axis.update_dims.assert_not_called() - batch_axis.onnx.load.assert_not_called() - batch_axis.onnx.save.assert_not_called() + update_dims.assert_not_called() + onnx.load.assert_not_called() + onnx.save.assert_not_called() + + def test_recognition_does_not_add_batch_axis_for_openvino( + self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture + ) -> None: + onnx = mocker.patch("immich_ml.models.facial_recognition.recognition.onnx", autospec=True) + update_dims = mocker.patch( + "immich_ml.models.facial_recognition.recognition.update_inputs_outputs_dims", autospec=True + ) + mocker.patch("immich_ml.models.base.InferenceModel.download") + mocker.patch("immich_ml.models.facial_recognition.recognition.ArcFaceONNX") + path.return_value.__truediv__.return_value.__truediv__.return_value.suffix = ".onnx" + + inputs = [SimpleNamespace(name="input.1", shape=("batch", 3, 224, 224))] + outputs = [SimpleNamespace(name="output.1", shape=("batch", 800))] + ort_session.return_value.get_inputs.return_value = inputs + ort_session.return_value.get_outputs.return_value = outputs + + face_recognizer = FaceRecognizer( + "buffalo_s", model_format=ModelFormat.ARMNN, cache_dir=path, providers=["OpenVINOExecutionProvider"] + ) + face_recognizer.load() + + assert face_recognizer.batch_size == 1 + update_dims.assert_not_called() + onnx.load.assert_not_called() + onnx.save.assert_not_called() def test_set_custom_max_batch_size(self, mocker: MockerFixture) -> None: mocker.patch.object(settings, "max_batch_size", MaxBatchSize(facial_recognition=2)) @@ -1068,10 +892,6 @@ class TestFaceRecognition: def test_ignore_other_custom_max_batch_size(self, mocker: MockerFixture) -> None: mocker.patch.object(settings, "max_batch_size", MaxBatchSize(ocr=2)) - mocker.patch( - "immich_ml.models.facial_recognition.recognition.ort.get_available_providers", - return_value=["CPUExecutionProvider"], - ) recognizer = FaceRecognizer("buffalo_l", cache_dir="test_cache") @@ -1104,12 +924,7 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions( - session=ort_session.return_value, - rec_batch_num=6, - rec_img_shape=(3, 48, 320), - model_root_dir=text_recognizer.cache_dir, - ) + OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320)) ) def test_set_custom_max_batch_size(self, ort_session: mock.Mock, path: mock.Mock, mocker: MockerFixture) -> None: @@ -1122,12 +937,7 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions( - session=ort_session.return_value, - rec_batch_num=4, - rec_img_shape=(3, 48, 320), - model_root_dir=text_recognizer.cache_dir, - ) + OcrOptions(session=ort_session.return_value, rec_batch_num=4, rec_img_shape=(3, 48, 320)) ) def test_ignore_other_custom_max_batch_size( @@ -1142,12 +952,7 @@ class TestOcr: text_recognizer.load() rapid_recognizer.assert_called_once_with( - OcrOptions( - session=ort_session.return_value, - rec_batch_num=6, - rec_img_shape=(3, 48, 320), - model_root_dir=text_recognizer.cache_dir, - ) + OcrOptions(session=ort_session.return_value, rec_batch_num=6, rec_img_shape=(3, 48, 320)) ) @@ -1393,19 +1198,6 @@ class TestLoad: mock_model.model_format = ModelFormat.ONNX -@pytest.mark.parametrize("size", [(0, 100), (100, 0), (0, 0)]) -def test_predict_rejects_empty_image(size: tuple[int, int], deployed_app: TestClient) -> None: - with mock.patch("immich_ml.main.decode_pil", return_value=Image.new("RGB", size)): - response = deployed_app.post( - "http://localhost:3003/predict", - data={"entries": json.dumps({"clip": {"visual": {"modelName": "ViT-B-32__openai"}}})}, - files={"image": b"fake image bytes"}, - ) - - assert response.status_code == 400 - assert "zero" in response.json()["detail"].lower() - - def test_root_endpoint(deployed_app: TestClient) -> None: response = deployed_app.get("http://localhost:3003") diff --git a/machine-learning/uv.lock b/machine-learning/uv.lock index fa92147445..ea8d6dd551 100644 --- a/machine-learning/uv.lock +++ b/machine-learning/uv.lock @@ -2,14 +2,11 @@ version = 1 revision = 3 requires-python = ">=3.11, <4.0" resolution-markers = [ - "python_full_version >= '3.15' and sys_platform == 'darwin'", - "python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'darwin'", + "python_full_version >= '3.13' and sys_platform == 'darwin'", "python_full_version == '3.12.*' and sys_platform == 'darwin'", - "python_full_version >= '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.13' and python_full_version < '3.15' and platform_machine == 'aarch64' and sys_platform == 'linux'", + "python_full_version >= '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.12.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.15' and sys_platform != 'darwin' and sys_platform != 'linux')", - "(python_full_version >= '3.13' and python_full_version < '3.15' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform != 'darwin' and sys_platform != 'linux')", + "(python_full_version >= '3.13' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform != 'darwin' and sys_platform != 'linux')", "(python_full_version == '3.12.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and sys_platform != 'linux')", "python_full_version < '3.12' and sys_platform == 'darwin'", "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", @@ -25,6 +22,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/d7/15d67e05b235d1ed8c3ce61688fe4d84130e72af1657acadfaac3479f4cf/aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d", size = 28199, upload-time = "2024-09-25T13:20:22.688Z" }, ] +[[package]] +name = "albumentations" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pyyaml" }, + { name = "qudida" }, + { name = "scikit-image" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/d6/8dd5b690d28a332a0b2c3179a345808b5d4c7ad5ddc079b7e116098dff35/albumentations-1.3.1.tar.gz", hash = "sha256:a6a38388fe546c568071e8c82f414498e86c9ed03c08b58e7a88b31cf7a244c6", size = 176371, upload-time = "2023-06-10T07:44:32.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f6/c486cedb4f75147232f32ec4c97026714cfef7c7e247a1f0427bc5489f66/albumentations-1.3.1-py3-none-any.whl", hash = "sha256:6b641d13733181d9ecdc29550e6ad580d1bfa9d25e2213a66940062f25e291bd", size = 125706, upload-time = "2023-06-10T07:44:30.373Z" }, +] + [[package]] name = "annotated-doc" version = "0.0.4" @@ -62,46 +76,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/cd/d6d9bb1dadf73e7af02d18225cbd2c93f8552e13130484f1c8dcfece292b/anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee", size = 85481, upload-time = "2023-12-16T17:06:55.989Z" }, ] -[[package]] -name = "ast-serialize" -version = "0.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/9d/09e27731bd5864a9ce04e3244074e674bb8936bf62b45e0357248717adac/ast_serialize-0.5.0.tar.gz", hash = "sha256:5880091bfe6f4f986f22866375c2e884843e7a0b6343ae41aeea659613d879b6", size = 61157, upload-time = "2026-05-17T17:48:29.429Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/9a/13dde51ba9e15f8b97957ab7cb0120d0e381524d651c6bd630b9c359227f/ast_serialize-0.5.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8f5c14f169eb0972c0c21bada5358b23d6047c76583b005234f865b11f1fa00a", size = 1183520, upload-time = "2026-05-17T17:47:30.831Z" }, - { url = "https://files.pythonhosted.org/packages/37/de/5a7f0a9fe68944f536632a5af84676739c7d2582be42deb082634bf3a754/ast_serialize-0.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7d1a2de9de5be04652f0ed60738356ef94f66db37924a9499fffe98dc491aa0b", size = 1175779, upload-time = "2026-05-17T17:47:32.551Z" }, - { url = "https://files.pythonhosted.org/packages/9c/81/0bb853e76e4f6e9a1855d569003c59e19ffac45f7079d91505d1bb212f92/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be5173fb66f9b49026d9d5a2ff0fc7c7009077107c0eb285b2d60fdf1fe10bd1", size = 1233750, upload-time = "2026-05-17T17:47:34.731Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/4cf705beeccc08754d0bbda99aefff26110e209b9a07ac8a6b60eec48531/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8015cd071ac1339924ee2b8098c93e00e155f30a16f40ec9816fcf84f4753f6", size = 1235942, upload-time = "2026-05-17T17:47:36.287Z" }, - { url = "https://files.pythonhosted.org/packages/26/c8/ee097e437ea27dd2b8b227865c875492b585650a5802a22d82b304c8201b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5499e8797edff2a9186aa313ed382c6b422e798e9332d9953badcee6e69a88f2", size = 1442517, upload-time = "2026-05-17T17:47:38.17Z" }, - { url = "https://files.pythonhosted.org/packages/ff/bd/68063442838f1ba68ec72b5436430bc75b3bb17a1a3c3063f09b0c05ae2b/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6848f2a093fb5548751a9a09bff8fcd229e2bbeb0e3331f391b6ae6d26cd9903", size = 1254081, upload-time = "2026-05-17T17:47:39.826Z" }, - { url = "https://files.pythonhosted.org/packages/50/e2/1e520793bc6a4e4524a6ab022391e827825eaa0c3811828bfdc6852eca26/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:832d4c998e0b091fd60a6d6bceee535483c4d490de9ba85003af835225719261", size = 1259910, upload-time = "2026-05-17T17:47:41.369Z" }, - { url = "https://files.pythonhosted.org/packages/4e/e1/49b60f467979979cfe6913b43948ff25bca971ad0591d181812f163a988e/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:16db7c62ec0b8efe1d7afd283a388d8f74f2605d56032e5a37747d2de8dba027", size = 1250678, upload-time = "2026-05-17T17:47:43.702Z" }, - { url = "https://files.pythonhosted.org/packages/74/ba/66ab9555de6275677566f6574e5ef6c29cb185ea866f643bc06f8280a8ee/ast_serialize-0.5.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:baf5eb061eb5bccade4128ad42da33787d72f6013809cd1b590376ece8b3c937", size = 1301603, upload-time = "2026-05-17T17:47:46.256Z" }, - { url = "https://files.pythonhosted.org/packages/66/42/6aca9b9abc710014b2be9059689e5dd1679339e78f567ffb4d255a9e2050/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:104e4a35bd7c124173c41760ef9aaea17ddb3f86c65cb643671d59afbe3ee94c", size = 1410332, upload-time = "2026-05-17T17:47:47.899Z" }, - { url = "https://files.pythonhosted.org/packages/47/68/2f76594432a22581ecf878b5e75a9b8601c24b2241cf0bbeb1e21fcf370c/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:36be371028fc1675acb38a331bde160dbab7ff907fdf00b67eb6911aa106951b", size = 1509979, upload-time = "2026-05-17T17:47:50.942Z" }, - { url = "https://files.pythonhosted.org/packages/40/ac/a93c9b58292653f6c595752f677a08e608f903b710594909e9231a389b3b/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:061ee58bdb52341c8201a6df41182a977736bae3b7ded87ca7176ca25a8a47ab", size = 1505002, upload-time = "2026-05-17T17:47:54.093Z" }, - { url = "https://files.pythonhosted.org/packages/14/2e/b278f68c497ee2f1d1576cbbef8db5281cd4a5f2db040537592ac9c8862e/ast_serialize-0.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b15219e9cdc9f53f6f4cb51c009203507228226148c05c5e8fe451c28b435eb3", size = 1456231, upload-time = "2026-05-17T17:47:56.311Z" }, - { url = "https://files.pythonhosted.org/packages/0b/43/419be1c566a4c504cd8fd60ce2f84e790f295495c0f327cfaeadf3d51012/ast_serialize-0.5.0-cp314-cp314t-win32.whl", hash = "sha256:842d1c004bb466c7df036f95fabef789570541922b10976b12f5592a69cf0b38", size = 1058668, upload-time = "2026-05-17T17:47:58.305Z" }, - { url = "https://files.pythonhosted.org/packages/03/6f/c9d4d549295ed05111aeb8853232d1afd9d0a179fddb01eeffbb3a4a6842/ast_serialize-0.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0c06d760909b095cc466356dfccd05a1c7233a6ca191c020dca2c6a6f16c24c", size = 1101075, upload-time = "2026-05-17T17:48:00.35Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8e/d00c5ab30c58222e07d62956fca86c59d91b9ad32997e633c38b526623a3/ast_serialize-0.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:787baedb0262cc49e8ce37cc15c00ae818e46a165a3b36f5e21ed174998104cb", size = 1075347, upload-time = "2026-05-17T17:48:01.753Z" }, - { url = "https://files.pythonhosted.org/packages/e0/9e/dc2530acb3a60dc6e46d65abf27d1d9f86721694757906a148d90a6860de/ast_serialize-0.5.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:0668aa9459cfa8c9c49ddd2163ebcf43088ba045ef7492af6fe22e0098303101", size = 1191380, upload-time = "2026-05-17T17:48:03.738Z" }, - { url = "https://files.pythonhosted.org/packages/26/0a/bd3d18a582f273d6c843d16bb9e22e9e16365ff7991e92f18f798e9f1224/ast_serialize-0.5.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:bf683d6363edf2b39eed6b6d4fe22d34b6203867a67e27134d9e2a2680c4bc4a", size = 1183879, upload-time = "2026-05-17T17:48:05.463Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/1f919100f8620887af58fcc381c61a1f218cdf89c6e155f87b213e61010a/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc22cf0c9be65e71cf88fda130af60d61eb4a79370ad4cfe7900d48a4aa2211", size = 1244529, upload-time = "2026-05-17T17:48:07.008Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ca/6376559dcce707cdbc1d0d9a13c8d3baaaa501e949ce0ebdc4230cd881aa/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f66173891548c9f2726bf27957b41cabce12fa679dc6da505ddbde4d4b3b31cf", size = 1240560, upload-time = "2026-05-17T17:48:08.46Z" }, - { url = "https://files.pythonhosted.org/packages/35/b2/a620e206b5aeb7efbf2710336df57d457cffbb3991076bbcc1147ef9abd4/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e42d729ef2be96a14efbad355093284739e3670ece3e534f82cc8832790911d9", size = 1451172, upload-time = "2026-05-17T17:48:09.922Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e0/4ad5c04c24a40481b2935ce9a0ccdb6023dc8b667167d06ae530cc3512f2/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b725026bafa801dbd7310eb13a75f0a2e370e7e51b2cb225f9d21fcfadf919ee", size = 1265072, upload-time = "2026-05-17T17:48:11.469Z" }, - { url = "https://files.pythonhosted.org/packages/b2/71/4d1d479aa56d0101c40e17720c3d6ac2af7269ea0487a80b18e7bfd1a5b7/ast_serialize-0.5.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b54f60c1d78767a53b67eaa663f0dfac3afe606aa07f1301572f588b73d64809", size = 1270488, upload-time = "2026-05-17T17:48:13.575Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4f/0de1bbe06f6edef9fde4ed12ca8e7b3ec7e6e2bd4e672c5af487f7957665/ast_serialize-0.5.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:27d51654fc240a1e87e742d353d98eb45b75f62f129086b3596ab53df2ac2a43", size = 1260702, upload-time = "2026-05-17T17:48:15.141Z" }, - { url = "https://files.pythonhosted.org/packages/75/61/e00872439cfdddcc3c1b6cdaa6e5d904ba8e26a18807c67c4e14409d0ca8/ast_serialize-0.5.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c36237c46dd1674542f2109740ea5ea485a169bf1431939ada0434e17934", size = 1311182, upload-time = "2026-05-17T17:48:16.779Z" }, - { url = "https://files.pythonhosted.org/packages/76/8e/699a5b955f7926956c95e9e1d74132acad73c2fe7a426f94da89123c20aa/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1943db345233cc7194a470f13afa9c59772c0b123dea0c9414c4d4ca54369759", size = 1421410, upload-time = "2026-05-17T17:48:18.527Z" }, - { url = "https://files.pythonhosted.org/packages/a9/ae/d5b7626874478997adc7a29ab28accf21e596fb590c944290401dfd0b29e/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df1c00022cbbcb064bfaa505aa9c9295362443ce5dacb459d1331d3da353f887", size = 1516587, upload-time = "2026-05-17T17:48:20.133Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ce/b59e02a82d9c4244d64cde502e0b00e83e38816abe19155ceb5437402c7f/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:cae65289fc456fde04af979a2be09302ef5d8ab92ef23e596d6746dc267ada27", size = 1515171, upload-time = "2026-05-17T17:48:21.921Z" }, - { url = "https://files.pythonhosted.org/packages/8b/38/d8d90042747d05aa08d4efcf1c99035a5f670a6bf4c214d31644392afbca/ast_serialize-0.5.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:239a4c354e8d676e9d94631d1d4a64edc6b266f86ff3a5a80aedd344f342c01d", size = 1464668, upload-time = "2026-05-17T17:48:23.544Z" }, - { url = "https://files.pythonhosted.org/packages/dd/51/5b840c4df7334104cecffa28f23904fe81ca89ca223d2450e288de39fd3c/ast_serialize-0.5.0-cp39-abi3-win32.whl", hash = "sha256:143a4ef63285a075871908fda3672dc21864b83a8ec3ee12304aa3e4c5387b9a", size = 1068311, upload-time = "2026-05-17T17:48:25.027Z" }, - { url = "https://files.pythonhosted.org/packages/41/11/ca5672c7d491825bc4cd6702dea106a6b60d928707712ec257c7833ae476/ast_serialize-0.5.0-cp39-abi3-win_amd64.whl", hash = "sha256:cf25572c526add400f26a4750dc6ce0c3bb93fc1f75e7ae0cad4ce4f2cd5c590", size = 1108931, upload-time = "2026-05-17T17:48:26.591Z" }, - { url = "https://files.pythonhosted.org/packages/45/19/cc8bd127d28a43da249aa955cfd164cf8fd534e79e42cea96c4854d72fd0/ast_serialize-0.5.0-cp39-abi3-win_arm64.whl", hash = "sha256:92a31c9c20d25a076edaeec76b128a3535d74a24f340b9a8a7e96c9b86dc9642", size = 1081181, upload-time = "2026-05-17T17:48:28.122Z" }, -] - [[package]] name = "bidict" version = "0.23.1" @@ -269,14 +243,14 @@ wheels = [ [[package]] name = "click" -version = "8.4.1" +version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" }, + { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, ] [[package]] @@ -309,6 +283,88 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/28/d28211d29bcc3620b1fece85a65ce5bb22f18670a03cd28ea4b75ede270c/configargparse-1.7.1-py3-none-any.whl", hash = "sha256:8b586a31f9d873abd1ca527ffbe58863c99f36d896e2829779803125e83be4b6", size = 25607, upload-time = "2025-05-23T14:26:15.923Z" }, ] +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + [[package]] name = "coverage" version = "7.13.4" @@ -413,9 +469,49 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "cython" +version = "3.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/09/ffb61f29b8e3d207c444032b21328327d753e274ea081bc74e009827cc81/Cython-3.0.8.tar.gz", hash = "sha256:8333423d8fd5765e7cceea3a9985dd1e0a5dfeb2734629e1a2ed2d6233d39de6", size = 2744096, upload-time = "2024-01-10T11:01:02.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/a7/f4a0bc9a80e23b380daa2ebb4879bf434aaa0b3b91f7ad8a7f9762b4bd1b/Cython-3.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aae26f9663e50caf9657148403d9874eea41770ecdd6caf381d177c2b1bb82ba", size = 3113615, upload-time = "2024-01-10T11:34:05.899Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/e9295df74246c165b91253a473bfa179debf739c9bee961cbb3ae56c2b79/Cython-3.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:547eb3cdb2f8c6f48e6865d5a741d9dd051c25b3ce076fbca571727977b28ac3", size = 3436320, upload-time = "2024-01-10T11:02:08.689Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/6a887c957aa53e44f928119dea628a5dfacc8e875424034f5fecac9daba4/Cython-3.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a567d4b9ba70b26db89d75b243529de9e649a2f56384287533cf91512705bee", size = 3591755, upload-time = "2024-01-10T11:02:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b8/f9c97bae6281da50b3ecb1f7fef0f7f7851eae084609b364717a2b366bf1/Cython-3.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51d1426263b0e82fb22bda8ea60dc77a428581cc19e97741011b938445d383f1", size = 3636099, upload-time = "2024-01-10T11:02:15.191Z" }, + { url = "https://files.pythonhosted.org/packages/17/ae/cd055c2c081c67a6fcad1d8d17d82bd6395b14c6741e3a938f40318c8bc5/Cython-3.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c26daaeccda072459b48d211415fd1e5507c06bcd976fa0d5b8b9f1063467d7b", size = 3458119, upload-time = "2024-01-10T11:02:19.103Z" }, + { url = "https://files.pythonhosted.org/packages/72/ab/ac6f5548d6194f4bb2fc8c6c996aa7369f0fa1403e4d4de787d9e9309b27/Cython-3.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:289ce7838208211cd166e975865fd73b0649bf118170b6cebaedfbdaf4a37795", size = 3614418, upload-time = "2024-01-10T11:02:22.732Z" }, + { url = "https://files.pythonhosted.org/packages/70/e2/3e3e448b7a94887bec3235bcb71957b6681dc42b4536459f8f54d46fa936/Cython-3.0.8-cp311-cp311-win32.whl", hash = "sha256:c8aa05f5e17f8042a3be052c24f2edc013fb8af874b0bf76907d16c51b4e7871", size = 2572819, upload-time = "2024-01-10T11:02:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/85/7d/58635941dfbb5b4e197adb88080b9cbfb230dc3b75683698a530a1989bdb/Cython-3.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:000dc9e135d0eec6ecb2b40a5b02d0868a2f8d2e027a41b0fe16a908a9e6de02", size = 2784167, upload-time = "2024-01-10T11:02:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8e/28f8c6109990eef7317ab7e43644092b49a88a39f9373dcd19318946df09/Cython-3.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:90d3fe31db55685d8cb97d43b0ec39ef614fcf660f83c77ed06aa670cb0e164f", size = 3135638, upload-time = "2024-01-10T11:34:22.889Z" }, + { url = "https://files.pythonhosted.org/packages/83/1f/4720cb682b8ed1ab9749dea35351a66dd29b6a022628cce038415660c384/Cython-3.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e24791ddae2324e88e3c902a765595c738f19ae34ee66bfb1a6dac54b1833419", size = 3340052, upload-time = "2024-01-10T11:02:32.471Z" }, + { url = "https://files.pythonhosted.org/packages/8a/47/ec3fceb9e8f7d6fa130216b8740038e1df7c8e5f215bba363fcf1272a6c1/Cython-3.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f020fa1c0552052e0660790b8153b79e3fc9a15dbd8f1d0b841fe5d204a6ae6", size = 3510079, upload-time = "2024-01-10T11:02:35.312Z" }, + { url = "https://files.pythonhosted.org/packages/71/31/b458127851e248effb909e2791b55870914863cde7c60b94db5ee65d7867/Cython-3.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18bfa387d7a7f77d7b2526af69a65dbd0b731b8d941aaff5becff8e21f6d7717", size = 3573972, upload-time = "2024-01-10T11:02:39.044Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d5/ca6513844d0634abd05ba12304053a454bb70441a9520afa9897d4300156/Cython-3.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fe81b339cffd87c0069c6049b4d33e28bdd1874625ee515785bf42c9fdff3658", size = 3356158, upload-time = "2024-01-10T11:02:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/33/59/98a87b6264f4ad45c820db13c4ec657567476efde020c49443cc842a86af/Cython-3.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80fd94c076e1e1b1ee40a309be03080b75f413e8997cddcf401a118879863388", size = 3522312, upload-time = "2024-01-10T11:02:45.056Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cb/132115d07a0b9d4f075e0741db70a5416b424dcd875b2bb0dd805e818222/Cython-3.0.8-cp312-cp312-win32.whl", hash = "sha256:85077915a93e359a9b920280d214dc0cf8a62773e1f3d7d30fab8ea4daed670c", size = 2602579, upload-time = "2024-01-10T11:02:48.368Z" }, + { url = "https://files.pythonhosted.org/packages/b4/69/cb4620287cd9ef461103e122c0a2ae7f7ecf183e02510676fb5a15c95b05/Cython-3.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:0cb2dcc565c7851f75d496f724a384a790fab12d1b82461b663e66605bec429a", size = 2791268, upload-time = "2024-01-10T11:02:51.483Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7f/f584f5d15323feb897d42ef0e9d910649e2150d7a30cf7e7a8cc1d236e6f/Cython-3.0.8-py2.py3-none-any.whl", hash = "sha256:171b27051253d3f9108e9759e504ba59ff06e7f7ba944457f94deaf9c21bf0b6", size = 1168213, upload-time = "2024-01-10T11:00:56.857Z" }, +] + +[[package]] +name = "easydict" +version = "1.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d2/deb3296d08097fedd622d423c0ec8b68b78c1704b3f1545326f6ce05c75c/easydict-1.11.tar.gz", hash = "sha256:dcb1d2ed28eb300c8e46cd371340373abc62f7c14d6dea74fdfc6f1069061c78", size = 6644, upload-time = "2023-10-23T23:01:37.686Z" } + [[package]] name = "fastapi" -version = "0.136.3" +version = "0.128.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -424,9 +520,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/72/0df5c58c954742f31a7054e2dd1143bae0b408b7f36b59b85f928f9b456c/fastapi-0.128.8.tar.gz", hash = "sha256:3171f9f328c4a218f0a8d2ba8310ac3a55d1ee12c28c949650288aee25966007", size = 375523, upload-time = "2026-02-11T15:19:36.69Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/9f/37/37b07e276f8923c69a5df266bfcb5bac4ba8b55dfe4a126720f8c48681d1/fastapi-0.128.8-py3-none-any.whl", hash = "sha256:5618f492d0fe973a778f8fec97723f598aa9deee495040a8d51aaf3cf123ecf1", size = 103630, upload-time = "2026-02-11T15:19:35.209Z" }, ] [[package]] @@ -488,6 +584,55 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/d5c79ee252793ffe845d58a913197bfa02ae9a0b5c9bc3dc4b58d477b9e7/flatbuffers-23.5.26-py2.py3-none-any.whl", hash = "sha256:c0ff356da363087b915fde4b8b45bdda73432fc17cddb3c8157472eab1422ad1", size = 26744, upload-time = "2023-05-26T17:35:14.269Z" }, ] +[[package]] +name = "fonttools" +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cf/00ba28b0990982530addb8dc3e9e6f2fa9cb5c20df2abdda7baa755e8fe1/fonttools-4.61.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c56c488ab471628ff3bfa80964372fc13504ece601e0d97a78ee74126b2045c", size = 2846454, upload-time = "2025-12-12T17:30:24.938Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ca/468c9a8446a2103ae645d14fee3f610567b7042aba85031c1c65e3ef7471/fonttools-4.61.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc492779501fa723b04d0ab1f5be046797fee17d27700476edc7ee9ae535a61e", size = 2398191, upload-time = "2025-12-12T17:30:27.343Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/d67eedaed19def5967fade3297fed8161b25ba94699efc124b14fb68cdbc/fonttools-4.61.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:64102ca87e84261419c3747a0d20f396eb024bdbeb04c2bfb37e2891f5fadcb5", size = 4928410, upload-time = "2025-12-12T17:30:29.771Z" }, + { url = "https://files.pythonhosted.org/packages/b0/8d/6fb3494dfe61a46258cd93d979cf4725ded4eb46c2a4ca35e4490d84daea/fonttools-4.61.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c1b526c8d3f615a7b1867f38a9410849c8f4aef078535742198e942fba0e9bd", size = 4984460, upload-time = "2025-12-12T17:30:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/f7/f1/a47f1d30b3dc00d75e7af762652d4cbc3dff5c2697a0dbd5203c81afd9c3/fonttools-4.61.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:41ed4b5ec103bd306bb68f81dc166e77409e5209443e5773cb4ed837bcc9b0d3", size = 4925800, upload-time = "2025-12-12T17:30:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/a7/01/e6ae64a0981076e8a66906fab01539799546181e32a37a0257b77e4aa88b/fonttools-4.61.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b501c862d4901792adaec7c25b1ecc749e2662543f68bb194c42ba18d6eec98d", size = 5067859, upload-time = "2025-12-12T17:30:36.593Z" }, + { url = "https://files.pythonhosted.org/packages/73/aa/28e40b8d6809a9b5075350a86779163f074d2b617c15d22343fce81918db/fonttools-4.61.1-cp313-cp313-win32.whl", hash = "sha256:4d7092bb38c53bbc78e9255a59158b150bcdc115a1e3b3ce0b5f267dc35dd63c", size = 2267821, upload-time = "2025-12-12T17:30:38.478Z" }, + { url = "https://files.pythonhosted.org/packages/1a/59/453c06d1d83dc0951b69ef692d6b9f1846680342927df54e9a1ca91c6f90/fonttools-4.61.1-cp313-cp313-win_amd64.whl", hash = "sha256:21e7c8d76f62ab13c9472ccf74515ca5b9a761d1bde3265152a6dc58700d895b", size = 2318169, upload-time = "2025-12-12T17:30:40.951Z" }, + { url = "https://files.pythonhosted.org/packages/32/8f/4e7bf82c0cbb738d3c2206c920ca34ca74ef9dabde779030145d28665104/fonttools-4.61.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fff4f534200a04b4a36e7ae3cb74493afe807b517a09e99cb4faa89a34ed6ecd", size = 2846094, upload-time = "2025-12-12T17:30:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/71/09/d44e45d0a4f3a651f23a1e9d42de43bc643cce2971b19e784cc67d823676/fonttools-4.61.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d9203500f7c63545b4ce3799319fe4d9feb1a1b89b28d3cb5abd11b9dd64147e", size = 2396589, upload-time = "2025-12-12T17:30:45.681Z" }, + { url = "https://files.pythonhosted.org/packages/89/18/58c64cafcf8eb677a99ef593121f719e6dcbdb7d1c594ae5a10d4997ca8a/fonttools-4.61.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa646ecec9528bef693415c79a86e733c70a4965dd938e9a226b0fc64c9d2e6c", size = 4877892, upload-time = "2025-12-12T17:30:47.709Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ec/9e6b38c7ba1e09eb51db849d5450f4c05b7e78481f662c3b79dbde6f3d04/fonttools-4.61.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:11f35ad7805edba3aac1a3710d104592df59f4b957e30108ae0ba6c10b11dd75", size = 4972884, upload-time = "2025-12-12T17:30:49.656Z" }, + { url = "https://files.pythonhosted.org/packages/5e/87/b5339da8e0256734ba0dbbf5b6cdebb1dd79b01dc8c270989b7bcd465541/fonttools-4.61.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b931ae8f62db78861b0ff1ac017851764602288575d65b8e8ff1963fed419063", size = 4924405, upload-time = "2025-12-12T17:30:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/0b/47/e3409f1e1e69c073a3a6fd8cb886eb18c0bae0ee13db2c8d5e7f8495e8b7/fonttools-4.61.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b148b56f5de675ee16d45e769e69f87623a4944f7443850bf9a9376e628a89d2", size = 5035553, upload-time = "2025-12-12T17:30:54.823Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b6/1f6600161b1073a984294c6c031e1a56ebf95b6164249eecf30012bb2e38/fonttools-4.61.1-cp314-cp314-win32.whl", hash = "sha256:9b666a475a65f4e839d3d10473fad6d47e0a9db14a2f4a224029c5bfde58ad2c", size = 2271915, upload-time = "2025-12-12T17:30:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/91e7b01e37cc8eb0e1f770d08305b3655e4f002fc160fb82b3390eabacf5/fonttools-4.61.1-cp314-cp314-win_amd64.whl", hash = "sha256:4f5686e1fe5fce75d82d93c47a438a25bf0d1319d2843a926f741140b2b16e0c", size = 2323487, upload-time = "2025-12-12T17:30:59.804Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/908ad78e46c61c3e3ed70c3b58ff82ab48437faf84ec84f109592cabbd9f/fonttools-4.61.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e76ce097e3c57c4bcb67c5aa24a0ecdbd9f74ea9219997a707a4061fbe2707aa", size = 2929571, upload-time = "2025-12-12T17:31:02.574Z" }, + { url = "https://files.pythonhosted.org/packages/bd/41/975804132c6dea64cdbfbaa59f3518a21c137a10cccf962805b301ac6ab2/fonttools-4.61.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9cfef3ab326780c04d6646f68d4b4742aae222e8b8ea1d627c74e38afcbc9d91", size = 2435317, upload-time = "2025-12-12T17:31:04.974Z" }, + { url = "https://files.pythonhosted.org/packages/b0/5a/aef2a0a8daf1ebaae4cfd83f84186d4a72ee08fd6a8451289fcd03ffa8a4/fonttools-4.61.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a75c301f96db737e1c5ed5fd7d77d9c34466de16095a266509e13da09751bd19", size = 4882124, upload-time = "2025-12-12T17:31:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/d6db3485b645b81cea538c9d1c9219d5805f0877fda18777add4671c5240/fonttools-4.61.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91669ccac46bbc1d09e9273546181919064e8df73488ea087dcac3e2968df9ba", size = 5100391, upload-time = "2025-12-12T17:31:09.732Z" }, + { url = "https://files.pythonhosted.org/packages/6c/d6/675ba631454043c75fcf76f0ca5463eac8eb0666ea1d7badae5fea001155/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c33ab3ca9d3ccd581d58e989d67554e42d8d4ded94ab3ade3508455fe70e65f7", size = 4978800, upload-time = "2025-12-12T17:31:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/7f/33/d3ec753d547a8d2bdaedd390d4a814e8d5b45a093d558f025c6b990b554c/fonttools-4.61.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:664c5a68ec406f6b1547946683008576ef8b38275608e1cee6c061828171c118", size = 5006426, upload-time = "2025-12-12T17:31:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/b4/40/cc11f378b561a67bea850ab50063366a0d1dd3f6d0a30ce0f874b0ad5664/fonttools-4.61.1-cp314-cp314t-win32.whl", hash = "sha256:aed04cabe26f30c1647ef0e8fbb207516fd40fe9472e9439695f5c6998e60ac5", size = 2335377, upload-time = "2025-12-12T17:31:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ff/c9a2b66b39f8628531ea58b320d66d951267c98c6a38684daa8f50fb02f8/fonttools-4.61.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2180f14c141d2f0f3da43f3a81bc8aa4684860f6b0e6f9e165a4831f24e6a23b", size = 2400613, upload-time = "2025-12-12T17:31:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, +] + [[package]] name = "fsspec" version = "2023.12.2" @@ -619,14 +764,14 @@ wheels = [ [[package]] name = "gunicorn" -version = "25.3.0" +version = "25.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/f4/e78fa054248fab913e2eab0332c6c2cb07421fca1ce56d8fe43b6aef57a4/gunicorn-25.3.0.tar.gz", hash = "sha256:f74e1b2f9f76f6cd1ca01198968bd2dd65830edc24b6e8e4d78de8320e2fe889", size = 634883, upload-time = "2026-03-27T00:00:26.092Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/c8/8aaf447698c4d59aa853fd318eed300b5c9e44459f242ab8ead6c9c09792/gunicorn-25.3.0-py3-none-any.whl", hash = "sha256:cacea387dab08cd6776501621c295a904fe8e3b7aae9a1a3cbb26f4e7ed54660", size = 208403, upload-time = "2026-03-27T00:00:27.386Z" }, + { url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" }, ] [[package]] @@ -640,34 +785,17 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.5.1" +version = "1.1.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/0a/a0f56735940fde6dd627602fec9ab3bad23f66a272397560abd65aba416e/hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80", size = 477719, upload-time = "2025-08-06T00:30:55.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ee/dd9ba7beae1005e54131b7d45263cc74c8a066d47d354e6d58ae9445a388/hf_xet-1.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", size = 4069485, upload-time = "2026-06-08T23:02:13.193Z" }, - { url = "https://files.pythonhosted.org/packages/b6/bc/9cae6cfeb4e03070874e73e5c97c66eb90369d3206b6a2b1ef5f96520888/hf_xet-1.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", size = 3838493, upload-time = "2026-06-08T23:02:15.282Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b4/d5c01e0eb6d9f2ca2dacd84d0d1b71e6cfbb2ef3208c968528e010e9b3d7/hf_xet-1.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", size = 4505658, upload-time = "2026-06-08T23:02:17.196Z" }, - { url = "https://files.pythonhosted.org/packages/76/c5/29a7598c0c6383c523dc22186d577f4e04267a626cd95ae60f67c00bfe66/hf_xet-1.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", size = 4292822, upload-time = "2026-06-08T23:02:18.608Z" }, - { url = "https://files.pythonhosted.org/packages/04/9a/dceaf6ca69390126b86ea825fb354b93d01163199070b7bd849225de9468/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", size = 4491255, upload-time = "2026-06-08T23:02:20.124Z" }, - { url = "https://files.pythonhosted.org/packages/48/a7/e5a7afaacf6c1791fdbeeac42951fb81c3d2bc482992b115dedcc86d963e/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", size = 4711062, upload-time = "2026-06-08T23:02:21.863Z" }, - { url = "https://files.pythonhosted.org/packages/53/49/2802f8433c9742ce281bddc1e65c02c32268ca3098d66828b05e12e45ee2/hf_xet-1.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", size = 4017205, upload-time = "2026-06-08T23:02:23.495Z" }, - { url = "https://files.pythonhosted.org/packages/9e/5a/50c71195b9fb883659f596e7252faf4c18c58e753a9013bdbf9bac5d2250/hf_xet-1.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", size = 3845426, upload-time = "2026-06-08T23:02:25.124Z" }, - { url = "https://files.pythonhosted.org/packages/05/24/5e0c28f80371c17d49fed004597d9d132cb75c1f6f53db2cb95f459d2312/hf_xet-1.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", size = 4069676, upload-time = "2026-06-08T23:02:26.759Z" }, - { url = "https://files.pythonhosted.org/packages/d2/17/261ba565b6a4d960fb478f61fdf919c0be5824645aaf1c319eca660c1611/hf_xet-1.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", size = 3838509, upload-time = "2026-06-08T23:02:28.573Z" }, - { url = "https://files.pythonhosted.org/packages/4e/44/7ffdc2e184b0d41fc0f683ba3936ef669ab63cf242cf36ef50e57d683668/hf_xet-1.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", size = 4505881, upload-time = "2026-06-08T23:02:30.257Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/788060d5aa4d5e671f1a31bf69624c314eb2d8babab3aa562f9e5d53444e/hf_xet-1.5.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", size = 4292995, upload-time = "2026-06-08T23:02:31.993Z" }, - { url = "https://files.pythonhosted.org/packages/22/93/c5540cbd6b55529b7dc42f6734e88cebee21aefbea34128b66229df56c57/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", size = 4491570, upload-time = "2026-06-08T23:02:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/03/f3/9d8ceab30f44f36c1679b1b8683054c71a0dadc787dbf07421891742d3ca/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", size = 4711565, upload-time = "2026-06-08T23:02:35.454Z" }, - { url = "https://files.pythonhosted.org/packages/cd/54/27ed9a5e2cc583b4df82f75a03a4df8dbf55f5a9fa1f47f1fadfb20dbeac/hf_xet-1.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", size = 4017343, upload-time = "2026-06-08T23:02:37.14Z" }, - { url = "https://files.pythonhosted.org/packages/ae/12/ecb2fc8d45e767580e3a37faa97cb895608b614965567efb4f18cff67e27/hf_xet-1.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", size = 3845716, upload-time = "2026-06-08T23:02:39.073Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" }, - { url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" }, - { url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" }, - { url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" }, - { url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" }, + { url = "https://files.pythonhosted.org/packages/b1/7c/8d7803995caf14e7d19a392a486a040f923e2cfeff824e9b800b92072f76/hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde", size = 2761743, upload-time = "2025-08-06T00:30:50.634Z" }, + { url = "https://files.pythonhosted.org/packages/51/a3/fa5897099454aa287022a34a30e68dbff0e617760f774f8bd1db17f06bd4/hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e", size = 2624331, upload-time = "2025-08-06T00:30:49.212Z" }, + { url = "https://files.pythonhosted.org/packages/86/50/2446a132267e60b8a48b2e5835d6e24fd988000d0f5b9b15ebd6d64ef769/hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f", size = 3183844, upload-time = "2025-08-06T00:30:47.582Z" }, + { url = "https://files.pythonhosted.org/packages/20/8f/ccc670616bb9beee867c6bb7139f7eab2b1370fe426503c25f5cbb27b148/hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513", size = 3074209, upload-time = "2025-08-06T00:30:45.509Z" }, + { url = "https://files.pythonhosted.org/packages/21/0a/4c30e1eb77205565b854f5e4a82cf1f056214e4dc87f2918ebf83d47ae14/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8", size = 3239602, upload-time = "2025-08-06T00:30:52.41Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1e/fc7e9baf14152662ef0b35fa52a6e889f770a7ed14ac239de3c829ecb47e/hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604", size = 3348184, upload-time = "2025-08-06T00:30:54.105Z" }, + { url = "https://files.pythonhosted.org/packages/a3/73/e354eae84ceff117ec3560141224724794828927fcc013c5b449bf0b8745/hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34", size = 2820008, upload-time = "2025-08-06T00:30:57.056Z" }, ] [[package]] @@ -685,45 +813,31 @@ wheels = [ [[package]] name = "httptools" -version = "0.8.0" +version = "0.6.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, - { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, - { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, - { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, - { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, - { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, - { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, - { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, - { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, - { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, - { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, - { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, - { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, - { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, - { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, - { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, - { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, - { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, - { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, - { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, - { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, + { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, + { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, + { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, + { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, + { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, + { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, + { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, ] [[package]] @@ -743,23 +857,21 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "1.19.0" +version = "0.36.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, { name = "filelock" }, { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "packaging" }, { name = "pyyaml" }, + { name = "requests" }, { name = "tqdm" }, - { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/27/629cfe58c582f92ded066c4a07d1a057ff617118ab7973200f770bd853cb/huggingface_hub-1.19.0.tar.gz", hash = "sha256:fd771622182d40977272a923953ee3b1b13538f9f8a7f5d78398f10af0f1c0bd", size = 824721, upload-time = "2026-06-11T12:33:18.665Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/b7/8cb61d2eece5fb05a83271da168186721c450eb74e3c31f7ef3169fa475b/huggingface_hub-0.36.2.tar.gz", hash = "sha256:1934304d2fb224f8afa3b87007d58501acfda9215b334eed53072dd5e815ff7a", size = 649782, upload-time = "2026-02-06T09:24:13.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/a5/558da89f66464d8d0229ff497e8b8666977de2d8cf48c28a2862ecf1250f/huggingface_hub-1.19.0-py3-none-any.whl", hash = "sha256:1dc72e1f6b4d6df6b30eb72e57d00514ef453d660f04af2b87f0e67267f31ee0", size = 693398, upload-time = "2026-06-11T12:33:16.695Z" }, + { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, ] [[package]] @@ -771,17 +883,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "imageio" +version = "2.33.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/38/f4c568318c656352d211eec6954460dc3af0b7583a6682308f8a66e4c19b/imageio-2.33.1.tar.gz", hash = "sha256:78722d40b137bd98f5ec7312119f8aea9ad2049f76f434748eb306b6937cc1ce", size = 387374, upload-time = "2023-12-11T02:26:44.715Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/69/3aaa69cb0748e33e644fda114c9abd3186ce369edd4fca11107e9f39c6a7/imageio-2.33.1-py3-none-any.whl", hash = "sha256:c5094c48ccf6b2e6da8b4061cd95e1209380afafcbeae4a4e280938cce227e1d", size = 313345, upload-time = "2023-12-11T02:26:42.724Z" }, +] + [[package]] name = "immich-ml" -version = "3.1.0" +version = "2.7.3" source = { editable = "." } dependencies = [ { name = "aiocache" }, { name = "fastapi" }, { name = "gunicorn" }, { name = "huggingface-hub" }, + { name = "insightface" }, { name = "numpy" }, - { name = "onnx" }, { name = "opencv-python-headless" }, { name = "orjson" }, { name = "pillow" }, @@ -860,9 +985,9 @@ requires-dist = [ { name = "aiocache", specifier = ">=0.12.1,<1.0" }, { name = "fastapi", specifier = ">=0.95.2,<1.0" }, { name = "gunicorn", specifier = ">=21.1.0" }, - { name = "huggingface-hub", specifier = ">=1.0,<2.0" }, - { name = "numpy", specifier = ">=2.4.0,<3.0" }, - { name = "onnx", specifier = ">=1.22.0" }, + { name = "huggingface-hub", specifier = ">=0.20.1,<1.0" }, + { name = "insightface", specifier = ">=0.7.3,<1.0" }, + { name = "numpy", specifier = "<2.4.0" }, { name = "onnxruntime", marker = "extra == 'armnn'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'cpu'", specifier = ">=1.23.2,<2" }, { name = "onnxruntime", marker = "extra == 'rknn'", specifier = ">=1.23.2,<2" }, @@ -871,7 +996,7 @@ requires-dist = [ { name = "onnxruntime-openvino", marker = "extra == 'openvino'", specifier = ">=1.24.1,<2" }, { name = "opencv-python-headless", specifier = ">=4.7.0.72,<5.0" }, { name = "orjson", specifier = ">=3.9.5" }, - { name = "pillow", specifier = ">=12.2,<13" }, + { name = "pillow", specifier = ">=12.1.1,<12.2" }, { name = "pydantic", specifier = ">=2.0.0,<3" }, { name = "pydantic-settings", specifier = ">=2.5.2,<3" }, { name = "python-multipart", specifier = ">=0.0.6,<1.0" }, @@ -932,6 +1057,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] +[[package]] +name = "insightface" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "albumentations" }, + { name = "cython" }, + { name = "easydict" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "onnx" }, + { name = "pillow" }, + { name = "prettytable" }, + { name = "requests" }, + { name = "scikit-image" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/8d/0f4af90999ca96cf8cb846eb5ae27c5ef5b390f9c090dd19e4fa76364c13/insightface-0.7.3.tar.gz", hash = "sha256:f191f719612ebb37018f41936814500544cd0f86e6fcd676c023f354c668ddf7", size = 439490, upload-time = "2023-04-02T08:01:54.541Z" } + [[package]] name = "itsdangerous" version = "2.1.2" @@ -954,81 +1100,130 @@ wheels = [ ] [[package]] -name = "librt" -version = "0.11.0" +name = "joblib" +version = "1.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/08/9e7f6b5d2b5bed6ad055cdd5925f192bb403a51280f86b56554d9d0699a2/librt-0.11.0.tar.gz", hash = "sha256:075dc3ef4458a278e0195cbf6ac9d38808d9b906c5a6c7f7f79c3888276a3fb1", size = 200139, upload-time = "2026-05-10T18:17:25.138Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/0f/d3b33b9f106dddef461f6df1872b7881321b247f3d255b87f61a7636f7fe/joblib-1.3.2.tar.gz", hash = "sha256:92f865e621e17784e7955080b6d042489e3b8e294949cc44c6eac304f59772b1", size = 1987720, upload-time = "2023-08-09T09:23:40.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/87/2bf31fe17587b29e3f93ec31421e2b1e1c3e349b8bf6c7c313dbad1d5340/librt-0.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:93d95bd45b7d58343d8b90d904450a545144eec19a002511163426f8ab1fae29", size = 141092, upload-time = "2026-05-10T18:15:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/cf/08/5c5bf772920b7ebac6e32bc91a643e0ab3870199c0b542356d3baa83970a/librt-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ee278c769a713638cdacd4c0436d72156e75df3ebc0166ab2b9dc43acc386c9", size = 142035, upload-time = "2026-05-10T18:15:36.242Z" }, - { url = "https://files.pythonhosted.org/packages/06/20/662a03d254e5b000d838e8b345d83303ddb768c080fd488e40634c0fa66b/librt-0.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f230cb1cbc9faaa616f9a678f530ebcf186e414b6bcbd88b960e4ba1b92428d5", size = 475022, upload-time = "2026-05-10T18:15:37.56Z" }, - { url = "https://files.pythonhosted.org/packages/de/f3/aa81523e45184c6ec23dc7f63263362ec55f80a09d424c012359ecbe7e35/librt-0.11.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5d63c855d86938d9de93e265c9bd8c705b51ec494de5738340ee93767a686e4b", size = 467273, upload-time = "2026-05-10T18:15:39.182Z" }, - { url = "https://files.pythonhosted.org/packages/6b/6f/59c74b560ca8853834d5501d589c8a2519f4184f273a085ffd0f37a1cc47/librt-0.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f028be9e96a08d31df3479ac80d99be374d17f3b78e4796b3fd3c913d4e89", size = 497083, upload-time = "2026-05-10T18:15:40.634Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7b/5aa4d2c9600a719401160bf7055417df0b2a47439b9d88286ce45e56b65f/librt-0.11.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:258d73a0aa66a055e65b2e4d1b8cdb23b9d132c5bb915d9547d804fcaed116cc", size = 489139, upload-time = "2026-05-10T18:15:41.934Z" }, - { url = "https://files.pythonhosted.org/packages/d6/31/9143803d7da6856a69153785768c4936864430eec0fd9461c3ea527d9922/librt-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0827efe7854718f04aaddf6496e96960a956e676fe1d0f04eb41511fd8ad06d5", size = 508442, upload-time = "2026-05-10T18:15:43.206Z" }, - { url = "https://files.pythonhosted.org/packages/2f/5a/bce08184488426bda4ccc2c4964ac048c8f68ae89bd7120082eef4233cfd/librt-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7753e57d6e12d019c0d8786f1c09c709f4c3fcc57c3887b24e36e6c06ec938b7", size = 514230, upload-time = "2026-05-10T18:15:44.761Z" }, - { url = "https://files.pythonhosted.org/packages/89/8c/bb5e213d254b7505a0e658da199d8ab719086632ce09eef311ab27976523/librt-0.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:11bd19822431cc21af9f27374e7ae2e58103c7d98bda823536a6c47f6bb2bb3d", size = 494231, upload-time = "2026-05-10T18:15:46.308Z" }, - { url = "https://files.pythonhosted.org/packages/9d/fb/541cdad5b1ab1300398c74c4c9a497b88e5074c21b1244c8f49731d3a284/librt-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:22bdf239b219d3993761a148ffa134b19e52e9989c84f845d5d7b71d70a17412", size = 537585, upload-time = "2026-05-10T18:15:47.629Z" }, - { url = "https://files.pythonhosted.org/packages/8f/f2/464bb69295c320cb06bddb4f14a4ec67934ee14b2bffb12b19fb7ab287ba/librt-0.11.0-cp311-cp311-win32.whl", hash = "sha256:46c60b61e308eb535fbd6fa622b1ee1bb2815691c1ad9c98bf7b84952ec3bc8d", size = 100509, upload-time = "2026-05-10T18:15:49.157Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e7/a17ee1788f9e4fbf548c19f4afa07c92089b9e24fef6cb2410863781ef4c/librt-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:902e546ff044f579ff1c953ff5fce97b636fe9e3943996b2177710c6ef076f73", size = 118628, upload-time = "2026-05-10T18:15:50.345Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c7/6c766214f9f9903bcfcfbef97d807af8d8f5aa3502d247858ab17582d212/librt-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:65ac3bc20f78aa0ee5ae84baa68917f89fef4af63e941084dd019a0d0e749f0c", size = 103122, upload-time = "2026-05-10T18:15:52.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/d0/07c77e067f0838949b43bd89232c29d72efebb9d2801a9750184eb706b71/librt-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b87504f1690a23b9a2cca841191a04f83895d4fc2dd04df91d82b1a04ca2ad46", size = 144147, upload-time = "2026-05-10T18:15:53.227Z" }, - { url = "https://files.pythonhosted.org/packages/7a/24/8493538fa4f62f982686398a5b8f68008138a75086abdea19ade64bf4255/librt-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40071fc5fe0ce8daa6de616702314a01e1250711682b0523d6ab8d4525910cb3", size = 143614, upload-time = "2026-05-10T18:15:54.657Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1e/f8bad050810d9171f34a1648ed910e56814c2ba61639f2bd53c6377ae24b/librt-0.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:137e79445c896a0ea7b265f52d23954e05b64222ee1af69e2cb34219067cbb67", size = 485538, upload-time = "2026-05-10T18:15:56.117Z" }, - { url = "https://files.pythonhosted.org/packages/c0/fe/3594ebfbaf03084ba4b120c9ba5c3183fd938a48725e9bbe6ff0a5159ad8/librt-0.11.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:cca6644054e78746d8d4ef238681f9c34ff8b584fe6b988ecebb8db3b15e622a", size = 479623, upload-time = "2026-05-10T18:15:57.544Z" }, - { url = "https://files.pythonhosted.org/packages/b0/da/5d1876984b3746c85dbd219dbfcb73c85f54ee263fd32e5b2a632ec14571/librt-0.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5b0eea49f5562861ee8d757a32ef7d559c1d35be2aaaa1ec28941d74c9ffc8a", size = 513082, upload-time = "2026-05-10T18:15:58.805Z" }, - { url = "https://files.pythonhosted.org/packages/19/6e/55bdf5d5ca00c3e18430690bf2c953d8d3ffd3c337418173d33dec985dc9/librt-0.11.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0d1029d7e1ae1a7e647ed6fb5df8c4ce2dffefb7a9f5fd1376a4554d96dac09f", size = 508105, upload-time = "2026-05-10T18:16:00.2Z" }, - { url = "https://files.pythonhosted.org/packages/07/10/f1f23a7c595ee90ece4d35c851e5d104b1311a887ed1b4ac4c35bbd13da8/librt-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bc3ce6b33c5828d9e80592011a5c584cb2ce86edbc4088405f70da47dc1d1b3b", size = 522268, upload-time = "2026-05-10T18:16:01.708Z" }, - { url = "https://files.pythonhosted.org/packages/b6/02/5720f5697a7f54b78b3aefbe20df3a48cedcff1276618c4aa481177942ed/librt-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:936c5995f3514a42111f20099397d8177c79b4d7e70961e396c6f5a0a3566766", size = 527348, upload-time = "2026-05-10T18:16:03.496Z" }, - { url = "https://files.pythonhosted.org/packages/50/db/b4a47c6f91db4ff76348a0b3dd0cc65e090a078b765a810a62ff9434c3d3/librt-0.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9bc0ca6ad9381cbe8e4aa6e5726e4c80c78115a6e9723c599ed1d73e092bc49d", size = 516294, upload-time = "2026-05-10T18:16:05.173Z" }, - { url = "https://files.pythonhosted.org/packages/9e/58/9384b2f4eb1ed1d273d40948a7c5c4b2360213b402ef3be4641c06299f9c/librt-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:070aa8c26c0a74774317a72df8851facc7f0f012a5b406557ac56992d92e1ec8", size = 553608, upload-time = "2026-05-10T18:16:06.839Z" }, - { url = "https://files.pythonhosted.org/packages/21/7b/5aa8848a7c6a9278c79375146da1812e695754ceec5f005e6043461a7315/librt-0.11.0-cp312-cp312-win32.whl", hash = "sha256:6bf14feb84b05ae945277395451998c89c54d0def4070eb5c08de544930b245a", size = 101879, upload-time = "2026-05-10T18:16:08.103Z" }, - { url = "https://files.pythonhosted.org/packages/37/33/8a745436944947575b584231750a41417de1a38cf6a2e9251d1065651c09/librt-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:75672f0bc524ede266287d532d7923dbce94c7514ad07627bac3d0c6d92cc4d9", size = 119831, upload-time = "2026-05-10T18:16:09.174Z" }, - { url = "https://files.pythonhosted.org/packages/59/67/a6739ac96e28b7855808bdb0370e250606104a859750d209e5a0716fe7ab/librt-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f10cf143e4a9bb0f4f5af568a00df94a2d69ef41c2579584454bb0fe5cc642c", size = 103470, upload-time = "2026-05-10T18:16:10.369Z" }, - { url = "https://files.pythonhosted.org/packages/82/61/e59168d4d0bf2bf90f4f0caf7a001bfc60254c3af4586013b04dc3ef517b/librt-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:78dc31f7fdfe9c9d0eb0e8f42d139db230e826415bbcabd9f0e9faaaee909894", size = 144119, upload-time = "2026-05-10T18:16:11.771Z" }, - { url = "https://files.pythonhosted.org/packages/61/fd/caa1d60b12f7dd79ccea23054e06eeaebe266a5f52c40a6b651069200ce5/librt-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fa475675db22290c3158e1d42326d0f5a65f04f44a0e68c3630a25b53560fb9c", size = 143565, upload-time = "2026-05-10T18:16:13.334Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/dc744f5c2b4978d48db970be29f22716d3413d28b14ad99740817315cf2c/librt-0.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:621db29691044bdeda22e789e482e1b0f3a985d90e3426c9c6d17606416205ea", size = 485395, upload-time = "2026-05-10T18:16:14.729Z" }, - { url = "https://files.pythonhosted.org/packages/8f/21/7f8e97a1e4dae952a5a95948f6f8507a173bc1e669f54340bba6ca1ca31b/librt-0.11.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a9010e2ed5b3a9e158c5fd966b3ab7e834bb3d3aacc8f66c91dd4b57a3799230", size = 479383, upload-time = "2026-05-10T18:16:16.321Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6d/d8ee9c114bebf2c50e29ec2aa940826fccb62a645c3e4c18760987d0e16d/librt-0.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c39513d8b7477a2e1ed8c43fc21c524e8d5a0f8d4e8b7b074dbdbe7820a08e2", size = 513010, upload-time = "2026-05-10T18:16:17.647Z" }, - { url = "https://files.pythonhosted.org/packages/f0/43/0b5708af2bd30a46400e72ba6bdaa8f066f15fb9a688527e34220e8d6c06/librt-0.11.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7aef3cf1d5af86e770ab04bfd993dfc4ae8b8c17f66fb77dd4a7d50de7bbb1a3", size = 508433, upload-time = "2026-05-10T18:16:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/4a/50/356187247d09013490481033183b3532b58acf8028bcb34b2b56a375c9b2/librt-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:557183ddc36babe46b27dd60facbd5adb4492181a5be887587d57cda6e092f21", size = 522595, upload-time = "2026-05-10T18:16:20.642Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/c6ac4240899c7f3248079d5a9900debe0dadb3fdeaf856684c987105ba47/librt-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83d3e1f72bd42f6c5c0b7daec530c3f829bd02db42c70b8ddf0c2d90a2459930", size = 527255, upload-time = "2026-05-10T18:16:22.352Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b5/a81322dbeedeeaf9c1ee6f001734d28a09d8383ac9e6779bc24bbd0743c6/librt-0.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4ce1f21fbe589bc1afd7872dece84fb0e1144f794a288e58a10d2c54a55c43be", size = 516847, upload-time = "2026-05-10T18:16:23.627Z" }, - { url = "https://files.pythonhosted.org/packages/ae/66/6e6323787d592b55204a42595ff1102da5115601b53a7e9ddebc889a6da5/librt-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b09f7044ea2b64c9da42fd3d335666518cfd1c6e8a182c95da73d0214b41e", size = 553920, upload-time = "2026-05-10T18:16:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/9c/21/623f8ca230857102066d9ca8c6c1734995908c4d0d1bee7bb2ef0021cb33/librt-0.11.0-cp313-cp313-win32.whl", hash = "sha256:78fddc31cd4d3caa897ad5d31f856b1faadc9474021ad6cb182b9018793e254e", size = 101898, upload-time = "2026-05-10T18:16:26.649Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1d/b4ebd44dd723f768469007515cb92251e0ae286c94c140f374801140fa74/librt-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ca8aa88751a775870b764e93bad5135385f563cb8dcee399abf034ea4d3cb47", size = 119812, upload-time = "2026-05-10T18:16:27.859Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e4/b2f4ca7965ca373b491cdb4bc25cdb30c1649ca81a8782056a83850292a9/librt-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:96f044bb325fd9cf1a723015638c219e9143f0dfbc0ca54c565df2b7fc748b44", size = 103448, upload-time = "2026-05-10T18:16:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/29/eb/dbce197da4e227779e56b5735f2decc3eb36e55a1cdbf1bd65d6639d76c1/librt-0.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4a017a95e5837dc15a8c5661d60e05daa96b90908b1aa6b7acdf443cd25c8ebd", size = 143345, upload-time = "2026-05-10T18:16:30.674Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/254bebd0c11c8ba684018efb8006ff22e466abce445215cca6c778e7d9de/librt-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ecbd9819deccc39b7542bf4d2a740d8a620694d39989e58661d3763458f8d4", size = 143131, upload-time = "2026-05-10T18:16:32.037Z" }, - { url = "https://files.pythonhosted.org/packages/f1/3f/f77d6122d21ac7bf6ae8a7dfced1bd2a7ac545d3273ebdcaf8042f6d619f/librt-0.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7da327dacd7be8f8ec36547373550744a3cc0e536d54665cd83f8bcd961200e8", size = 477024, upload-time = "2026-05-10T18:16:33.493Z" }, - { url = "https://files.pythonhosted.org/packages/ac/0a/2c996dadebaa7d9bbbd43ef2d4f3e66b6da545f838a41694ef6172cebec8/librt-0.11.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0dc56b1f8d06e60db362cc3fdae206681817f86ce4725d34511473487f12a34b", size = 474221, upload-time = "2026-05-10T18:16:34.864Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7e/f5d92af8486b8272c23b3e686b46ff72d89c8169585eb61eef01a2ac7147/librt-0.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05fb8fb2ab90e21c8d12ea240d744ad514da9baf381ebfa70d91d20d21713175", size = 505174, upload-time = "2026-05-10T18:16:36.705Z" }, - { url = "https://files.pythonhosted.org/packages/af/1a/cb0734fe86398eb33193ab753b7326255c74cac5eb09e76b9b16536e7adb/librt-0.11.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cae74872be221df4374d10fec61f93ed1513b9546ea84f2c0bf73ab3e9bd0b03", size = 497216, upload-time = "2026-05-10T18:16:38.418Z" }, - { url = "https://files.pythonhosted.org/packages/18/06/094820f91558b66e29943c0ec41c9914f460f48dd51fc503c3101e10842d/librt-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32bcc918c0148eb7e3d57385125bac7e5f9e4359d05f07448b09f6f778c2f31c", size = 513921, upload-time = "2026-05-10T18:16:39.848Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c2/00de9018871a282f530cacb457d5ec0428f6ac7e6fedde9aff7468d9fb04/librt-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f9743fc99135d5f78d2454435615f6dec0473ca507c26ce9d92b10b562a280d3", size = 520850, upload-time = "2026-05-10T18:16:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/51/9d/64631832348fd1834fb3a61b996434edddaaf25a31d03b0a76273159d2cf/librt-0.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5ba067f4aadae8fda802d91d2124c90c42195ff32d9161d3549e6d05cfe26f96", size = 504237, upload-time = "2026-05-10T18:16:43.15Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ec/ae5525eb16edc827a044e7bb8777a455ff95d4bca9379e7e6bddd7383647/librt-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:de3bf945454d032f9e390b85c4072e0a0570bf825421c8be0e71209fa65e1abe", size = 546261, upload-time = "2026-05-10T18:16:44.408Z" }, - { url = "https://files.pythonhosted.org/packages/5a/09/adce371f27ca039411da9659f7430fcc2ba6cd0c7b3e4467a0f091be7fa9/librt-0.11.0-cp314-cp314-win32.whl", hash = "sha256:d2277a05f6dcb9fd13db9566aac4fabd68c3ea1ea46ee5567d4eef8efa495a2f", size = 96965, upload-time = "2026-05-10T18:16:46.039Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ee/8ac720d98548f173c7ce2e632a7ca94673f74cacd5c8162a84af5b35958a/librt-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:ab73e8db5e3f564d812c1f5c3a175930a5f9bc96ccb5e3b22a34d7858b401cf7", size = 115151, upload-time = "2026-05-10T18:16:47.133Z" }, - { url = "https://files.pythonhosted.org/packages/94/20/c900cf14efeb09b6bef2b2dff20779f73464b97fd58d1c6bccc379588ae3/librt-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aea3caa317752e3a466fa8af45d91ee0ea8c7fdd96e42b0a8dd9b76a7931eba1", size = 98850, upload-time = "2026-05-10T18:16:48.597Z" }, - { url = "https://files.pythonhosted.org/packages/0c/71/944bfe4b64e12abffcd3c15e1cce07f72f3d55655083786285f4dedeb532/librt-0.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d1b36540d7aaf9b9101b3a6f376c8d8e9f7a9aec93ed05918f2c69d493ffef72", size = 151138, upload-time = "2026-05-10T18:16:49.839Z" }, - { url = "https://files.pythonhosted.org/packages/b6/10/99e64a5c86989357fda078c8143c533389585f6473b7439172dd8f3b3b2d/librt-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:efbb343ab2ce3540f4ecbe6315d677ed70f37cd9a72b1e58066c918ca83acbaa", size = 151976, upload-time = "2026-05-10T18:16:51.062Z" }, - { url = "https://files.pythonhosted.org/packages/21/31/5072ad880946d83e5ea4147d6d018c78eefce85b77819b19bdd0ee229435/librt-0.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0dd688aab3f7914d3e6e5e3554978e0383312fb8e771d84be008a35b9ee548", size = 557927, upload-time = "2026-05-10T18:16:52.632Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8d/70b5fb7cfbab60edbe7381614ab985da58e144fbf465c86d44c95f43cdca/librt-0.11.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f5fb36b8c6c63fdcbb1d526d94c0d1331610d43f4118cc1beb4efef4f3faacb2", size = 539698, upload-time = "2026-05-10T18:16:53.934Z" }, - { url = "https://files.pythonhosted.org/packages/fa/a3/ba3495a0b3edbd24a4cae0d1d3c64f39a9fc45d06e812101289b50c1a619/librt-0.11.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9a237d13addb93715b6fee74023d5ee3469b53fce527626c0e088aa585805f", size = 577162, upload-time = "2026-05-10T18:16:55.589Z" }, - { url = "https://files.pythonhosted.org/packages/f7/db/36e25fb81f99937ff1b96612a1dc9fd66f039cb9cc3aee12c01fac31aab9/librt-0.11.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5ddd17bd87b2c56ddd60e546a7984a2e64c4e8eab92fb4cf3830a48ad5469d51", size = 566494, upload-time = "2026-05-10T18:16:56.975Z" }, - { url = "https://files.pythonhosted.org/packages/33/0d/3f622b47f0b013eeb9cf4cc07ae9bfe378d832a4eec998b2b209fe84244d/librt-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd43992b4473d42f12ff9e68326079f0696d9d4e6000e8f39a0238d482ba6ee2", size = 596858, upload-time = "2026-05-10T18:16:58.374Z" }, - { url = "https://files.pythonhosted.org/packages/a9/02/71b90bc93039c46a2000651f6ad60122b114c8f54c4ad306e0e96f5b75ad/librt-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:f8e3e8056dd674e279741485e2e512d6e9a751c7455809d0114e6ebf8d781085", size = 590318, upload-time = "2026-05-10T18:16:59.676Z" }, - { url = "https://files.pythonhosted.org/packages/04/04/418cb3f75621e2b761fb1ab0f017f4d70a1a72a6e7c74ee4f7e8d198c2f3/librt-0.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c1f708d8ae9c56cf38a903c44297243d2ec83fd82b396b977e0144a3e76217e3", size = 575115, upload-time = "2026-05-10T18:17:01.007Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2c/5a2183ac58dd911f26b5d7e7d7d8f1d87fcecdddd99d6c12169a258ff62c/librt-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0add982e0e7b9fc14cf4b33789d5f13f66581889b88c2f58099f6ce8f92617bd", size = 617918, upload-time = "2026-05-10T18:17:02.682Z" }, - { url = "https://files.pythonhosted.org/packages/15/1f/dc6771a52592a4451be6effa200cbfc9cec61e4393d3033d81a9d307961d/librt-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:2b481d846ac894c4e8403c5fd0e87c5d11d6499e404b474602508a224ff531c8", size = 103562, upload-time = "2026-05-10T18:17:03.99Z" }, - { url = "https://files.pythonhosted.org/packages/62/4a/7d1415567027286a75ba1093ec4aca11f073e0f559c530cf3e0a757ad55c/librt-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:28edb433edde181112a908c78907af28f964eabc15f4dd16c9d66c834302677c", size = 124327, upload-time = "2026-05-10T18:17:05.465Z" }, - { url = "https://files.pythonhosted.org/packages/ce/62/b40b382fa0c66fee1478073eb8db352a4a6beda4a1adccf1df911d8c289c/librt-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dee008f20b542e3cd162ba338a7f9ec0f6d23d395f66fe8aeeec3c9d067ea253", size = 102572, upload-time = "2026-05-10T18:17:06.809Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/d551139c85db202f1f384ba8bcf96aca2f329440a844f924c8a0040b6d02/joblib-1.3.2-py3-none-any.whl", hash = "sha256:ef4331c65f239985f3f2220ecc87db222f08fd22097a3dd5698f693875f8cbb9", size = 302207, upload-time = "2023-08-09T09:23:34.583Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/2d/226779e405724344fc678fcc025b812587617ea1a48b9442628b688e85ea/kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec", size = 97552, upload-time = "2023-08-24T09:30:39.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/eb/9e099ad7c47c279995d2d20474e1821100a5f10f847739bd65b1c1f02442/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5", size = 127403, upload-time = "2023-08-24T09:28:39.3Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/695922e71288855fc7cace3bdb52edda9d7e50edba77abb0c9d7abb51e96/kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90", size = 68156, upload-time = "2023-08-24T09:28:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fe/23d7fa78f7c66086d196406beb1fb2eaf629dd7adc01c3453033303d17fa/kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797", size = 66166, upload-time = "2023-08-24T09:28:41.235Z" }, + { url = "https://files.pythonhosted.org/packages/f1/68/f472bf16c9141bb1bea5c0b8c66c68fc1ccb048efdbd8f0872b92125724e/kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9", size = 1334300, upload-time = "2023-08-24T09:28:42.409Z" }, + { url = "https://files.pythonhosted.org/packages/8d/26/b4569d1f29751fca22ee915b4ebfef5974f4ef239b3335fc072882bd62d9/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437", size = 1426579, upload-time = "2023-08-24T09:28:43.677Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/804fc7c8bf233806ec0321c9da35971578620f2ab4fafe67d76100b3ce52/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9", size = 1541360, upload-time = "2023-08-24T09:28:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/07/ef/286e1d26524854f6fbd6540e8364d67a8857d61038ac743e11edc42fe217/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da", size = 1470091, upload-time = "2023-08-24T09:28:47.959Z" }, + { url = "https://files.pythonhosted.org/packages/17/ba/17a706b232308e65f57deeccae503c268292e6a091313f6ce833a23093ea/kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e", size = 1426259, upload-time = "2023-08-24T09:28:49.224Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f3/a0925611c9d6c2f37c5935a39203cadec6883aa914e013b46c84c4c2e641/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8", size = 1847516, upload-time = "2023-08-24T09:28:50.979Z" }, + { url = "https://files.pythonhosted.org/packages/da/85/82d59bb8f7c4c9bb2785138b72462cb1b161668f8230c58bbb28c0403cd5/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d", size = 1946228, upload-time = "2023-08-24T09:28:52.812Z" }, + { url = "https://files.pythonhosted.org/packages/34/3c/6a37f444c0233993881e5db3a6a1775925d4d9d2f2609bb325bb1348ed94/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0", size = 1901716, upload-time = "2023-08-24T09:28:54.115Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7e/180425790efc00adfd47db14e1e341cb4826516982334129012b971121a6/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f", size = 1852871, upload-time = "2023-08-24T09:28:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9a/13c68b2edb1fa74321e60893a9a5829788e135138e68060cf44e2d92d2c3/kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f", size = 1870265, upload-time = "2023-08-24T09:28:56.855Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0a/fa56a0fdee5da2b4c79899c0f6bd1aefb29d9438c2d66430e78793571c6b/kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac", size = 46649, upload-time = "2023-08-24T09:28:58.021Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/d3c2d4ba2719059a0f12730947bbe1ad5ee8bff89e8c35319dcb2c9ddb4c/kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355", size = 56116, upload-time = "2023-08-24T09:28:58.994Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7a/debbce859be1a2711eb8437818107137192007b88d17b5cfdb556f457b42/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a", size = 125484, upload-time = "2023-08-24T09:28:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/bf8df75ba93b9e035cc6757dd5dcaf63084fdc1c846ae134e818bd7e0f03/kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192", size = 67332, upload-time = "2023-08-24T09:29:01.733Z" }, + { url = "https://files.pythonhosted.org/packages/26/61/58bb691f6880588be3a4801d199bd776032ece07203faf3e4a8b377f7d9b/kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45", size = 64987, upload-time = "2023-08-24T09:29:02.789Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a3/96ac5413068b237c006f54dd8d70114e8756d70e3da7613c5aef20627e22/kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7", size = 1370613, upload-time = "2023-08-24T09:29:03.912Z" }, + { url = "https://files.pythonhosted.org/packages/4d/12/f48539e6e17068b59c7f12f4d6214b973431b8e3ac83af525cafd27cebec/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db", size = 1463183, upload-time = "2023-08-24T09:29:05.244Z" }, + { url = "https://files.pythonhosted.org/packages/f3/70/26c99be8eb034cc8e3f62e0760af1fbdc97a842a7cbc252f7978507d41c2/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff", size = 1581248, upload-time = "2023-08-24T09:29:06.531Z" }, + { url = "https://files.pythonhosted.org/packages/17/f6/f75f20e543639b09b2de7fc864274a5a9b96cda167a6210a1d9d19306b9d/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228", size = 1508815, upload-time = "2023-08-24T09:29:07.867Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/bc0f22ac108743062ab703f8d6d71c9c7b077b8839fa358700bfb81770b8/kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16", size = 1466042, upload-time = "2023-08-24T09:29:09.403Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/98142500f21d6838bcab49ec919414a1f0c6d049d21ddadf139124db6a70/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9", size = 1885159, upload-time = "2023-08-24T09:29:10.66Z" }, + { url = "https://files.pythonhosted.org/packages/21/49/a241eff9e0ee013368c1d17957f9d345b0957493c3a43d82ebb558c90b0a/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162", size = 1981694, upload-time = "2023-08-24T09:29:12.469Z" }, + { url = "https://files.pythonhosted.org/packages/90/90/9490c3de4788123041b1d600d64434f1eed809a2ce9f688075a22166b289/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4", size = 1941579, upload-time = "2023-08-24T09:29:13.743Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bb/a0cc488ef2aa92d7d304318c8549d3ec8dfe6dd3c2c67a44e3922b77bc4f/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3", size = 1888168, upload-time = "2023-08-24T09:29:15.097Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e9/9c0de8e45fef3d63f85eed3b1757f9aa511065942866331ef8b99421f433/kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a", size = 1908464, upload-time = "2023-08-24T09:29:16.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/60/4f0fd50b08f5be536ea0cef518ac7255d9dab43ca40f3b93b60e3ddf80dd/kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20", size = 46473, upload-time = "2023-08-24T09:29:17.956Z" }, + { url = "https://files.pythonhosted.org/packages/63/50/2746566bdf4a6a842d117367d05c90cfb87ac04e9e2845aa1fa21f071362/kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9", size = 56004, upload-time = "2023-08-24T09:29:19.329Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + +[[package]] +name = "librt" +version = "0.7.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/e4/b59bdf1197fdf9888452ea4d2048cdad61aef85eb83e99dc52551d7fdc04/librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba", size = 145862, upload-time = "2025-12-15T16:52:43.862Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/64/44089b12d8b4714a7f0e2f33fb19285ba87702d4be0829f20b36ebeeee07/librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a", size = 54709, upload-time = "2025-12-15T16:51:16.699Z" }, + { url = "https://files.pythonhosted.org/packages/26/ef/6fa39fb5f37002f7d25e0da4f24d41b457582beea9369eeb7e9e73db5508/librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729", size = 56663, upload-time = "2025-12-15T16:51:17.856Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e4/cbaca170a13bee2469c90df9e47108610b4422c453aea1aec1779ac36c24/librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed", size = 161703, upload-time = "2025-12-15T16:51:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/d0/32/0b2296f9cc7e693ab0d0835e355863512e5eac90450c412777bd699c76ae/librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6", size = 171027, upload-time = "2025-12-15T16:51:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/c70b6d40f7342716e5f1353c8da92d9e32708a18cbfa44897a93ec2bf879/librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82", size = 184700, upload-time = "2025-12-15T16:51:22.272Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c8/555c405155da210e4c4113a879d378f54f850dbc7b794e847750a8fadd43/librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727", size = 180719, upload-time = "2025-12-15T16:51:23.561Z" }, + { url = "https://files.pythonhosted.org/packages/6b/88/34dc1f1461c5613d1b73f0ecafc5316cc50adcc1b334435985b752ed53e5/librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11", size = 174535, upload-time = "2025-12-15T16:51:25.031Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/f3fafe80a221626bcedfa9fe5abbf5f04070989d44782f579b2d5920d6d0/librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c", size = 195236, upload-time = "2025-12-15T16:51:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/d8/77/5c048d471ce17f4c3a6e08419be19add4d291e2f7067b877437d482622ac/librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2", size = 42930, upload-time = "2025-12-15T16:51:27.853Z" }, + { url = "https://files.pythonhosted.org/packages/fb/3b/514a86305a12c3d9eac03e424b07cd312c7343a9f8a52719aa079590a552/librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e", size = 49240, upload-time = "2025-12-15T16:51:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/3b7b1914f565926b780a734fac6e9a4d2c7aefe41f4e89357d73697a9457/librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170", size = 42613, upload-time = "2025-12-15T16:51:30.194Z" }, + { url = "https://files.pythonhosted.org/packages/f3/e7/b805d868d21f425b7e76a0ea71a2700290f2266a4f3c8357fcf73efc36aa/librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92", size = 55688, upload-time = "2025-12-15T16:51:31.571Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/69a2b02e62a14cfd5bfd9f1e9adea294d5bcfeea219c7555730e5d068ee4/librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108", size = 57141, upload-time = "2025-12-15T16:51:32.714Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6b/05dba608aae1272b8ea5ff8ef12c47a4a099a04d1e00e28a94687261d403/librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94", size = 165322, upload-time = "2025-12-15T16:51:33.986Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bc/199533d3fc04a4cda8d7776ee0d79955ab0c64c79ca079366fbc2617e680/librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab", size = 174216, upload-time = "2025-12-15T16:51:35.384Z" }, + { url = "https://files.pythonhosted.org/packages/62/ec/09239b912a45a8ed117cb4a6616d9ff508f5d3131bd84329bf2f8d6564f1/librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba", size = 189005, upload-time = "2025-12-15T16:51:36.687Z" }, + { url = "https://files.pythonhosted.org/packages/46/2e/e188313d54c02f5b0580dd31476bb4b0177514ff8d2be9f58d4a6dc3a7ba/librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9", size = 183960, upload-time = "2025-12-15T16:51:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/eb/84/f1d568d254518463d879161d3737b784137d236075215e56c7c9be191cee/librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74", size = 177609, upload-time = "2025-12-15T16:51:40.584Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/060bbc1c002f0d757c33a1afe6bf6a565f947a04841139508fc7cef6c08b/librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f", size = 199269, upload-time = "2025-12-15T16:51:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/ff/7f/708f8f02d8012ee9f366c07ea6a92882f48bd06cc1ff16a35e13d0fbfb08/librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286", size = 43186, upload-time = "2025-12-15T16:51:43.149Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a5/4e051b061c8b2509be31b2c7ad4682090502c0a8b6406edcf8c6b4fe1ef7/librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20", size = 49455, upload-time = "2025-12-15T16:51:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d2/90d84e9f919224a3c1f393af1636d8638f54925fdc6cd5ee47f1548461e5/librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a", size = 42828, upload-time = "2025-12-15T16:51:45.498Z" }, + { url = "https://files.pythonhosted.org/packages/fe/4d/46a53ccfbb39fd0b493fd4496eb76f3ebc15bb3e45d8c2e695a27587edf5/librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b", size = 55745, upload-time = "2025-12-15T16:51:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2b/3ac7f5212b1828bf4f979cf87f547db948d3e28421d7a430d4db23346ce4/librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32", size = 57166, upload-time = "2025-12-15T16:51:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/e8/99/6523509097cbe25f363795f0c0d1c6a3746e30c2994e25b5aefdab119b21/librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67", size = 165833, upload-time = "2025-12-15T16:51:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/fe/35/323611e59f8fe032649b4fb7e77f746f96eb7588fcbb31af26bae9630571/librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20", size = 174818, upload-time = "2025-12-15T16:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/40fb2bb21616c6e06b6a64022802228066e9a31618f493e03f6b9661548a/librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74", size = 189607, upload-time = "2025-12-15T16:51:52.671Z" }, + { url = "https://files.pythonhosted.org/packages/32/48/1b47c7d5d28b775941e739ed2bfe564b091c49201b9503514d69e4ed96d7/librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee", size = 184585, upload-time = "2025-12-15T16:51:54.027Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/ee135dfb5d3b54d5d9001dbe483806229c6beac3ee2ba1092582b7efeb1b/librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681", size = 178249, upload-time = "2025-12-15T16:51:55.248Z" }, + { url = "https://files.pythonhosted.org/packages/04/87/d5b84ec997338be26af982bcd6679be0c1db9a32faadab1cf4bb24f9e992/librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c", size = 199851, upload-time = "2025-12-15T16:51:56.933Z" }, + { url = "https://files.pythonhosted.org/packages/86/63/ba1333bf48306fe398e3392a7427ce527f81b0b79d0d91618c4610ce9d15/librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2", size = 43249, upload-time = "2025-12-15T16:51:58.498Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8a/de2c6df06cdfa9308c080e6b060fe192790b6a48a47320b215e860f0e98c/librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e", size = 49417, upload-time = "2025-12-15T16:51:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/31/66/8ee0949efc389691381ed686185e43536c20e7ad880c122dd1f31e65c658/librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788", size = 42824, upload-time = "2025-12-15T16:52:00.784Z" }, + { url = "https://files.pythonhosted.org/packages/74/81/6921e65c8708eb6636bbf383aa77e6c7dad33a598ed3b50c313306a2da9d/librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d", size = 55191, upload-time = "2025-12-15T16:52:01.97Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d6/3eb864af8a8de8b39cc8dd2e9ded1823979a27795d72c4eea0afa8c26c9f/librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23", size = 56898, upload-time = "2025-12-15T16:52:03.356Z" }, + { url = "https://files.pythonhosted.org/packages/49/bc/b1d4c0711fdf79646225d576faee8747b8528a6ec1ceb6accfd89ade7102/librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063", size = 163725, upload-time = "2025-12-15T16:52:04.572Z" }, + { url = "https://files.pythonhosted.org/packages/2c/08/61c41cd8f0a6a41fc99ea78a2205b88187e45ba9800792410ed62f033584/librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848", size = 172469, upload-time = "2025-12-15T16:52:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/4ee18b4d57f01444230bc18cf59103aeab8f8c0f45e84e0e540094df1df1/librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843", size = 186804, upload-time = "2025-12-15T16:52:07.192Z" }, + { url = "https://files.pythonhosted.org/packages/a1/af/009e8ba3fbf830c936842da048eda1b34b99329f402e49d88fafff6525d1/librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a", size = 181807, upload-time = "2025-12-15T16:52:08.554Z" }, + { url = "https://files.pythonhosted.org/packages/85/26/51ae25f813656a8b117c27a974f25e8c1e90abcd5a791ac685bf5b489a1b/librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16", size = 175595, upload-time = "2025-12-15T16:52:10.186Z" }, + { url = "https://files.pythonhosted.org/packages/48/93/36d6c71f830305f88996b15c8e017aa8d1e03e2e947b40b55bbf1a34cf24/librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce", size = 196504, upload-time = "2025-12-15T16:52:11.472Z" }, + { url = "https://files.pythonhosted.org/packages/08/11/8299e70862bb9d704735bf132c6be09c17b00fbc7cda0429a9df222fdc1b/librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f", size = 39738, upload-time = "2025-12-15T16:52:12.962Z" }, + { url = "https://files.pythonhosted.org/packages/54/d5/656b0126e4e0f8e2725cd2d2a1ec40f71f37f6f03f135a26b663c0e1a737/librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a", size = 45976, upload-time = "2025-12-15T16:52:14.441Z" }, + { url = "https://files.pythonhosted.org/packages/60/86/465ff07b75c1067da8fa7f02913c4ead096ef106cfac97a977f763783bfb/librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44", size = 39073, upload-time = "2025-12-15T16:52:15.621Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a0/24941f85960774a80d4b3c2aec651d7d980466da8101cae89e8b032a3e21/librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105", size = 57369, upload-time = "2025-12-15T16:52:16.782Z" }, + { url = "https://files.pythonhosted.org/packages/77/a0/ddb259cae86ab415786c1547d0fe1b40f04a7b089f564fd5c0242a3fafb2/librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4", size = 59230, upload-time = "2025-12-15T16:52:18.259Z" }, + { url = "https://files.pythonhosted.org/packages/31/11/77823cb530ab8a0c6fac848ac65b745be446f6f301753b8990e8809080c9/librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a", size = 183869, upload-time = "2025-12-15T16:52:19.457Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ce/157db3614cf3034b3f702ae5ba4fefda4686f11eea4b7b96542324a7a0e7/librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95", size = 194606, upload-time = "2025-12-15T16:52:20.795Z" }, + { url = "https://files.pythonhosted.org/packages/30/ef/6ec4c7e3d6490f69a4fd2803516fa5334a848a4173eac26d8ee6507bff6e/librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906", size = 206776, upload-time = "2025-12-15T16:52:22.229Z" }, + { url = "https://files.pythonhosted.org/packages/ad/22/750b37bf549f60a4782ab80e9d1e9c44981374ab79a7ea68670159905918/librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf", size = 203205, upload-time = "2025-12-15T16:52:23.603Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/2e8a0f584412a93df5faad46c5fa0a6825fdb5eba2ce482074b114877f44/librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f", size = 196696, upload-time = "2025-12-15T16:52:24.951Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ca/7bf78fa950e43b564b7de52ceeb477fb211a11f5733227efa1591d05a307/librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5", size = 217191, upload-time = "2025-12-15T16:52:26.194Z" }, + { url = "https://files.pythonhosted.org/packages/d6/49/3732b0e8424ae35ad5c3166d9dd5bcdae43ce98775e0867a716ff5868064/librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb", size = 40276, upload-time = "2025-12-15T16:52:27.54Z" }, + { url = "https://files.pythonhosted.org/packages/35/d6/d8823e01bd069934525fddb343189c008b39828a429b473fb20d67d5cd36/librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481", size = 46772, upload-time = "2025-12-15T16:52:28.653Z" }, + { url = "https://files.pythonhosted.org/packages/36/e9/a0aa60f5322814dd084a89614e9e31139702e342f8459ad8af1984a18168/librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f", size = 39724, upload-time = "2025-12-15T16:52:29.836Z" }, ] [[package]] name = "locust" -version = "2.44.1" +version = "2.43.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "configargparse" }, @@ -1048,9 +1243,9 @@ dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, { name = "werkzeug" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/98/1501e70415157ec47c9020a682a0119c8ead46ae9f1aa79e007f779fda14/locust-2.44.1.tar.gz", hash = "sha256:962e6498431f152eca26d9cb158c23e3f61bf26e026f4f4171bed3f713379820", size = 1437151, upload-time = "2026-06-01T22:41:58.604Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c5/7d7bd50ac744bc209a4bcbeb74660d7ae450a44441737efe92ee9d8ea6a7/locust-2.43.3.tar.gz", hash = "sha256:b5d2c48f8f7d443e3abdfdd6ec2f7aebff5cd74fab986bcf1e95b375b5c5a54b", size = 1445349, upload-time = "2026-02-12T09:55:34.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/f9/45b4c7849051deb080ba90c86bbe7d96969ba8858894eebcfdd921a173ac/locust-2.44.1-py3-none-any.whl", hash = "sha256:dba774c609fd56402ddf1ca07e3040ae7b436b4c2027393288e83167d24ac397", size = 1458058, upload-time = "2026-06-01T22:41:57.063Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d2/dc5379876d3a481720803653ea4d219f0c26f2d2b37c9243baaa16d0bc79/locust-2.43.3-py3-none-any.whl", hash = "sha256:e032c119b54a9d984cb74a936ee83cfd7d68b3c76c8f308af63d04f11396b553", size = 1463473, upload-time = "2026-02-12T09:55:31.727Z" }, ] [[package]] @@ -1093,6 +1288,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/44/44/dbaf65876e258facd65f586dde158387ab89963e7f2235551afc9c2e24c2/MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", size = 16979, upload-time = "2023-09-07T16:00:57.77Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/91/f2939bb60b7ebf12478b030e0d7f340247390f402b3b189616aad790c366/matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076", size = 34804044, upload-time = "2025-07-31T18:09:33.805Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/c7/1f2db90a1d43710478bb1e9b57b162852f79234d28e4f48a28cc415aa583/matplotlib-3.10.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dcfc39c452c6a9f9028d3e44d2d721484f665304857188124b505b2c95e1eecf", size = 8239216, upload-time = "2025-07-31T18:07:51.947Z" }, + { url = "https://files.pythonhosted.org/packages/82/6d/ca6844c77a4f89b1c9e4d481c412e1d1dbabf2aae2cbc5aa2da4a1d6683e/matplotlib-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:903352681b59f3efbf4546985142a9686ea1d616bb054b09a537a06e4b892ccf", size = 8102130, upload-time = "2025-07-31T18:07:53.65Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1e/5e187a30cc673a3e384f3723e5f3c416033c1d8d5da414f82e4e731128ea/matplotlib-3.10.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:080c3676a56b8ee1c762bcf8fca3fe709daa1ee23e6ef06ad9f3fc17332f2d2a", size = 8666471, upload-time = "2025-07-31T18:07:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/03/c0/95540d584d7d645324db99a845ac194e915ef75011a0d5e19e1b5cee7e69/matplotlib-3.10.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4b4984d5064a35b6f66d2c11d668565f4389b1119cc64db7a4c1725bc11adffc", size = 9500518, upload-time = "2025-07-31T18:07:57.199Z" }, + { url = "https://files.pythonhosted.org/packages/ba/2e/e019352099ea58b4169adb9c6e1a2ad0c568c6377c2b677ee1f06de2adc7/matplotlib-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3967424121d3a46705c9fa9bdb0931de3228f13f73d7bb03c999c88343a89d89", size = 9552372, upload-time = "2025-07-31T18:07:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b7/81/3200b792a5e8b354f31f4101ad7834743ad07b6d620259f2059317b25e4d/matplotlib-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:33775bbeb75528555a15ac29396940128ef5613cf9a2d31fb1bfd18b3c0c0903", size = 8100634, upload-time = "2025-07-31T18:08:01.801Z" }, + { url = "https://files.pythonhosted.org/packages/52/46/a944f6f0c1f5476a0adfa501969d229ce5ae60cf9a663be0e70361381f89/matplotlib-3.10.5-cp311-cp311-win_arm64.whl", hash = "sha256:c61333a8e5e6240e73769d5826b9a31d8b22df76c0778f8480baf1b4b01c9420", size = 7978880, upload-time = "2025-07-31T18:08:03.407Z" }, + { url = "https://files.pythonhosted.org/packages/66/1e/c6f6bcd882d589410b475ca1fc22e34e34c82adff519caf18f3e6dd9d682/matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2", size = 8253056, upload-time = "2025-07-31T18:08:05.385Z" }, + { url = "https://files.pythonhosted.org/packages/53/e6/d6f7d1b59413f233793dda14419776f5f443bcccb2dfc84b09f09fe05dbe/matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389", size = 8110131, upload-time = "2025-07-31T18:08:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/66/2b/bed8a45e74957549197a2ac2e1259671cd80b55ed9e1fe2b5c94d88a9202/matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea", size = 8669603, upload-time = "2025-07-31T18:08:09.064Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a7/315e9435b10d057f5e52dfc603cd353167ae28bb1a4e033d41540c0067a4/matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468", size = 9508127, upload-time = "2025-07-31T18:08:10.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d9/edcbb1f02ca99165365d2768d517898c22c6040187e2ae2ce7294437c413/matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369", size = 9566926, upload-time = "2025-07-31T18:08:13.186Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d9/6dd924ad5616c97b7308e6320cf392c466237a82a2040381163b7500510a/matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b", size = 8107599, upload-time = "2025-07-31T18:08:15.116Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f3/522dc319a50f7b0279fbe74f86f7a3506ce414bc23172098e8d2bdf21894/matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2", size = 7978173, upload-time = "2025-07-31T18:08:21.518Z" }, + { url = "https://files.pythonhosted.org/packages/8d/05/4f3c1f396075f108515e45cb8d334aff011a922350e502a7472e24c52d77/matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed", size = 8253586, upload-time = "2025-07-31T18:08:23.107Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2c/e084415775aac7016c3719fe7006cdb462582c6c99ac142f27303c56e243/matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1", size = 8110715, upload-time = "2025-07-31T18:08:24.675Z" }, + { url = "https://files.pythonhosted.org/packages/52/1b/233e3094b749df16e3e6cd5a44849fd33852e692ad009cf7de00cf58ddf6/matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7", size = 8669397, upload-time = "2025-07-31T18:08:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ec/03f9e003a798f907d9f772eed9b7c6a9775d5bd00648b643ebfb88e25414/matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f", size = 9508646, upload-time = "2025-07-31T18:08:28.848Z" }, + { url = "https://files.pythonhosted.org/packages/91/e7/c051a7a386680c28487bca27d23b02d84f63e3d2a9b4d2fc478e6a42e37e/matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4", size = 9567424, upload-time = "2025-07-31T18:08:30.726Z" }, + { url = "https://files.pythonhosted.org/packages/36/c2/24302e93ff431b8f4173ee1dd88976c8d80483cadbc5d3d777cef47b3a1c/matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe", size = 8107809, upload-time = "2025-07-31T18:08:33.928Z" }, + { url = "https://files.pythonhosted.org/packages/0b/33/423ec6a668d375dad825197557ed8fbdb74d62b432c1ed8235465945475f/matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674", size = 7978078, upload-time = "2025-07-31T18:08:36.764Z" }, + { url = "https://files.pythonhosted.org/packages/51/17/521fc16ec766455c7bb52cc046550cf7652f6765ca8650ff120aa2d197b6/matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c", size = 8295590, upload-time = "2025-07-31T18:08:38.521Z" }, + { url = "https://files.pythonhosted.org/packages/f8/12/23c28b2c21114c63999bae129fce7fd34515641c517ae48ce7b7dcd33458/matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e", size = 8158518, upload-time = "2025-07-31T18:08:40.195Z" }, + { url = "https://files.pythonhosted.org/packages/81/f8/aae4eb25e8e7190759f3cb91cbeaa344128159ac92bb6b409e24f8711f78/matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b", size = 8691815, upload-time = "2025-07-31T18:08:42.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ba/450c39ebdd486bd33a359fc17365ade46c6a96bf637bbb0df7824de2886c/matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea", size = 9522814, upload-time = "2025-07-31T18:08:44.914Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/9c66f6a990e27bb9aa023f7988d2d5809cb98aa39c09cbf20fba75a542ef/matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124", size = 9573917, upload-time = "2025-07-31T18:08:47.038Z" }, + { url = "https://files.pythonhosted.org/packages/b3/69/8b49394de92569419e5e05e82e83df9b749a0ff550d07631ea96ed2eb35a/matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce", size = 8181034, upload-time = "2025-07-31T18:08:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/47/23/82dc435bb98a2fc5c20dffcac8f0b083935ac28286413ed8835df40d0baa/matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154", size = 8023337, upload-time = "2025-07-31T18:08:50.791Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/26b6cfde31f5383503ee45dcb7e691d45dadf0b3f54639332b59316a97f8/matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715", size = 8253591, upload-time = "2025-07-31T18:08:53.254Z" }, + { url = "https://files.pythonhosted.org/packages/c1/89/98488c7ef7ea20ea659af7499628c240a608b337af4be2066d644cfd0a0f/matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837", size = 8112566, upload-time = "2025-07-31T18:08:55.116Z" }, + { url = "https://files.pythonhosted.org/packages/52/67/42294dfedc82aea55e1a767daf3263aacfb5a125f44ba189e685bab41b6f/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202", size = 9513281, upload-time = "2025-07-31T18:08:56.885Z" }, + { url = "https://files.pythonhosted.org/packages/e7/68/f258239e0cf34c2cbc816781c7ab6fca768452e6bf1119aedd2bd4a882a3/matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb", size = 9780873, upload-time = "2025-07-31T18:08:59.241Z" }, + { url = "https://files.pythonhosted.org/packages/89/64/f4881554006bd12e4558bd66778bdd15d47b00a1f6c6e8b50f6208eda4b3/matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975", size = 9568954, upload-time = "2025-07-31T18:09:01.244Z" }, + { url = "https://files.pythonhosted.org/packages/06/f8/42779d39c3f757e1f012f2dda3319a89fb602bd2ef98ce8faf0281f4febd/matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667", size = 8237465, upload-time = "2025-07-31T18:09:03.206Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f8/153fd06b5160f0cd27c8b9dd797fcc9fb56ac6a0ebf3c1f765b6b68d3c8a/matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516", size = 8108898, upload-time = "2025-07-31T18:09:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/c4b082a382a225fe0d2a73f1f57cf6f6f132308805b493a54c8641006238/matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e", size = 8295636, upload-time = "2025-07-31T18:09:07.306Z" }, + { url = "https://files.pythonhosted.org/packages/30/73/2195fa2099718b21a20da82dfc753bf2af58d596b51aefe93e359dd5915a/matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a", size = 8158575, upload-time = "2025-07-31T18:09:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e9/a08cdb34618a91fa08f75e6738541da5cacde7c307cea18ff10f0d03fcff/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569", size = 9522815, upload-time = "2025-07-31T18:09:11.191Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/34d8b7e0d1bb6d06ef45db01dfa560d5a67b1c40c0b998ce9ccde934bb09/matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012", size = 9783514, upload-time = "2025-07-31T18:09:13.307Z" }, + { url = "https://files.pythonhosted.org/packages/12/09/d330d1e55dcca2e11b4d304cc5227f52e2512e46828d6249b88e0694176e/matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592", size = 9573932, upload-time = "2025-07-31T18:09:15.335Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3b/f70258ac729aa004aca673800a53a2b0a26d49ca1df2eaa03289a1c40f81/matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959", size = 8322003, upload-time = "2025-07-31T18:09:17.416Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/3601f8ce6d76a7c81c7f25a0e15fde0d6b66226dd187aa6d2838e6374161/matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b", size = 8153849, upload-time = "2025-07-31T18:09:19.673Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d6/e921be4e1a5f7aca5194e1f016cb67ec294548e530013251f630713e456d/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:160e125da27a749481eaddc0627962990f6029811dbeae23881833a011a0907f", size = 8233224, upload-time = "2025-07-31T18:09:27.512Z" }, + { url = "https://files.pythonhosted.org/packages/ec/74/a2b9b04824b9c349c8f1b2d21d5af43fa7010039427f2b133a034cb09e59/matplotlib-3.10.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac3d50760394d78a3c9be6b28318fe22b494c4fcf6407e8fd4794b538251899b", size = 8098539, upload-time = "2025-07-31T18:09:29.629Z" }, + { url = "https://files.pythonhosted.org/packages/fc/66/cd29ebc7f6c0d2a15d216fb572573e8fc38bd5d6dec3bd9d7d904c0949f7/matplotlib-3.10.5-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c49465bf689c4d59d174d0c7795fb42a21d4244d11d70e52b8011987367ac61", size = 8672192, upload-time = "2025-07-31T18:09:31.407Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1104,43 +1363,39 @@ wheels = [ [[package]] name = "ml-dtypes" -version = "0.5.4" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, - { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, - { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, - { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, - { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, - { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, - { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, - { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, - { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, - { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, - { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, - { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, - { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, - { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, - { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, - { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, - { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, - { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, - { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, - { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/af/f1/720cb1409b5d0c05cff9040c0e9fba73fa4c67897d33babf905d5d46a070/ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458", size = 667412, upload-time = "2025-07-29T18:38:25.275Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d5/05861ede5d299f6599f86e6bc1291714e2116d96df003cfe23cc54bcc568/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2", size = 4964606, upload-time = "2025-07-29T18:38:27.045Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/72992b68de367741bfab8df3b3fe7c29f982b7279d341aa5bf3e7ef737ea/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee", size = 4938435, upload-time = "2025-07-29T18:38:29.193Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/d27a930bca31fb07d975a2d7eaf3404f9388114463b9f15032813c98f893/ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46", size = 206334, upload-time = "2025-07-29T18:38:30.687Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d8/6922499effa616012cb8dc445280f66d100a7ff39b35c864cfca019b3f89/ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184", size = 157584, upload-time = "2025-07-29T18:38:32.187Z" }, + { url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" }, + { url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" }, + { url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" }, + { url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" }, + { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" }, + { url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" }, + { url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" }, + { url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" }, ] [[package]] @@ -1207,53 +1462,41 @@ wheels = [ [[package]] name = "mypy" -version = "2.1.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ast-serialize" }, { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, - { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, - { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, - { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, - { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, - { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, - { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, - { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, - { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, - { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, - { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, - { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, - { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, - { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, - { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, - { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, - { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, - { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, - { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, - { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, - { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, - { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, - { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, - { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] @@ -1266,82 +1509,93 @@ wheels = [ ] [[package]] -name = "numpy" -version = "2.4.6" +name = "networkx" +version = "3.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/80/a84676339aaae2f1cfdf9f418701dd634aef9cc76f708ef55c36ff39c3ca/networkx-3.2.1.tar.gz", hash = "sha256:9f1bb5cf3409bf324e0a722c20bdb4c20ee39bf1c30ce8ae499c8502b0b5e0c6", size = 2073928, upload-time = "2023-10-28T08:41:39.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, - { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, - { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, - { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, - { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, - { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, - { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, - { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, - { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, - { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, - { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, - { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f0/8fbc882ca80cf077f1b246c0e3c3465f7f415439bdea6b899f6b19f61f70/networkx-3.2.1-py3-none-any.whl", hash = "sha256:f18c69adc97877c42332c170849c96cefa91881c99a7cb3e95b7c659ebdc1ec2", size = 1647772, upload-time = "2023-10-28T08:41:36.945Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, + { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, + { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, + { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, + { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, + { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, + { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, + { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, + { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, + { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, + { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, + { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, + { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, + { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, + { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, + { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, + { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, + { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, + { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, + { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, + { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, + { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, + { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, + { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, + { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, + { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, + { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, + { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, + { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, + { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, + { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, + { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, + { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, + { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, + { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, + { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, + { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, + { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, ] [[package]] @@ -1359,7 +1613,7 @@ wheels = [ [[package]] name = "onnx" -version = "1.22.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, @@ -1367,103 +1621,107 @@ dependencies = [ { name = "protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/19/8ea73a64b368b75fe339771a20a02bc61ea1f551484c9e3d9d0bfbd0450f/onnx-1.22.0.tar.gz", hash = "sha256:ef40c0aaf0b643857ea9306fc7eddce17eaf9fb0407e4801f1fc5758443a38e0", size = 12024721, upload-time = "2026-06-15T12:50:05.354Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/2f/c619eb65769357e9b6de9212c9a821ab39cd484448e5d6b3fb5fb0a64c6d/onnx-1.19.1.tar.gz", hash = "sha256:737524d6eb3907d3499ea459c6f01c5a96278bb3a0f2ff8ae04786fb5d7f1ed5", size = 12033525, upload-time = "2025-10-10T04:01:34.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/55/30825c02c92a0380ce84c3feeeec95d329fa77548ba58cb10ad4bbfd83c6/onnx-1.22.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:2d8f229a553fa440fe623ed7b36fca5e7762da3af871c3f8f8ce451df73e2914", size = 20167891, upload-time = "2026-06-15T12:49:14.212Z" }, - { url = "https://files.pythonhosted.org/packages/4b/24/cd4ab52ecaf41c3fbed674772ccbfe39041cb257b8471a47a37e48bff3f8/onnx-1.22.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a89a7cb9ba13d78f009bdec448ec82a98972589734f157022a2bff7a5973a6", size = 18892720, upload-time = "2026-06-15T12:49:16.904Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a0/c9d9d56ceadb1c0a90a7cbec5a0510520ab6538938944fa84548e4b5b054/onnx-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d0a2bdb15eb2b3cb65c438f3423d9620d14fdce32f92380e6bb1b2e09568ef5", size = 19110720, upload-time = "2026-06-15T12:49:19.812Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/e43e5a68d9cadde55df75310027f87127333a77e5ddcea14c73e96a10cac/onnx-1.22.0-cp311-cp311-win32.whl", hash = "sha256:239958534464612fbcb6ed23d5228aaa925b39b8773f58726809ffdccb4edd1c", size = 17083746, upload-time = "2026-06-15T12:49:22.935Z" }, - { url = "https://files.pythonhosted.org/packages/54/57/cc0a9f2cf4522e42829d089927b4b75924d32f50dca237482e7b741df003/onnx-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:8561a2c00041c07e08db0c228593b5b4694100398685f348532af7dbb84189da", size = 17215684, upload-time = "2026-06-15T12:49:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/c9/99/0f049f9eaa06c8383060c5f0a338e3a6caac8822e6e326c9162f05abf95a/onnx-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:8907b9b9389893bc0dc6314cc00ee1e3a69844e48d689eacc6a0340411a7da58", size = 17210398, upload-time = "2026-06-15T12:49:29.091Z" }, - { url = "https://files.pythonhosted.org/packages/ee/6a/481561f1093834376ed493e4ca42a73e5be0d50031f2969c86593bdc7c96/onnx-1.22.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:596fbf0490947533c1c1045ba860851dc9fb77471023dac9a71ba5b42ceab103", size = 20167081, upload-time = "2026-06-15T12:49:32.078Z" }, - { url = "https://files.pythonhosted.org/packages/84/55/b34fc2aa30aa54b4a775402d24c4082242c720283a274fe976ac8eb94480/onnx-1.22.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae5a563f281cd9d2845622cecf6c092a57e4ee1b138f66fdbbdd4200567a5e16", size = 18889249, upload-time = "2026-06-15T12:49:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/09/a6/bd32357e6cc1ecb473afd78193d7231724f284435d2db25696ecfaaa1503/onnx-1.22.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:955e02e1f6d385b53d52f9cd7b9cdf5caf417c300bcfe3c64c6d542be763845b", size = 19106514, upload-time = "2026-06-15T12:49:37.424Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9d/3af461ac6c714b8b369cb71499659932f4f12cfb066250b62f7567c3d530/onnx-1.22.0-cp312-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:82e9f27fc1223cb06d68a56bed6f9d3caf3d0dad1b61bce45006d529b15bd94c", size = 16966387, upload-time = "2026-06-15T12:49:40.918Z" }, - { url = "https://files.pythonhosted.org/packages/d0/f0/68195b5e5a53e333faf2660f5352ee43738d0e42fc5216cc6b1871a9fbfb/onnx-1.22.0-cp312-abi3-win32.whl", hash = "sha256:cc8b66b312f8f03a53e268afb67180a2d97dd12cc79e2b61361c6c0073448016", size = 17081568, upload-time = "2026-06-15T12:49:43.398Z" }, - { url = "https://files.pythonhosted.org/packages/13/a8/734725bb703c5fabb687f79c79e51249475212b3eb37771ac4a4ac9b487f/onnx-1.22.0-cp312-abi3-win_amd64.whl", hash = "sha256:72ccebab3bac07215c204ce8848d42e78eaaa666badbf72d25cd359b9f269e3a", size = 17213290, upload-time = "2026-06-15T12:49:45.933Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2a/8ce48d8ae26a8761ad4e5dc771961b155c5c3c7c8540ec7f2f2d71b69af0/onnx-1.22.0-cp312-abi3-win_arm64.whl", hash = "sha256:f3c120dcdb70ad738f3c061b32798f408ea299eb69f84dd69ab4a6bf3c2ec01f", size = 17207030, upload-time = "2026-06-15T12:49:48.635Z" }, - { url = "https://files.pythonhosted.org/packages/f3/13/47323b97846387848efb1044ded11bb94b83526f3d1fbdb37c6480d4520f/onnx-1.22.0-cp314-cp314t-macosx_12_0_universal2.whl", hash = "sha256:19e45e4af88e3fe3261458d4b8cc461957ae2782a358a3560503569bf3b23b72", size = 20176465, upload-time = "2026-06-15T12:49:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/13/0c/d3b8a7e7eee123938586c608bb9894b5723f2342b9450c0eec59fbec7099/onnx-1.22.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c21a0e59fd967a95b358e4a6e756d1f1eec2d304a83480f329f66e30d2bf0223", size = 18894028, upload-time = "2026-06-15T12:49:54.451Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8a/da2a97ab46fe6e0cd9beb3ac14603a22f5be492f9ca347faf8233a07bb33/onnx-1.22.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2632406b8f523ef2e2873c363f90b20a3d88c0fbcfac757d3addffccf8f452c2", size = 19110420, upload-time = "2026-06-15T12:49:57.665Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a3/ce984063017518307ebfaa545782fc400e593dc2d7fdf4f23ce4be1ed197/onnx-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a3a39fc4643867aecb33417fdddb11e308ee79d2d4a584b9d50cc7aec2091b13", size = 17237547, upload-time = "2026-06-15T12:50:00.382Z" }, - { url = "https://files.pythonhosted.org/packages/00/50/257a880384a1dd502d543b0067945074d63cd17d0840e958355bc8197da8/onnx-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:8e268cdc0547e3949799ffd4a44451dc2b9080b57d0824a2db680b6ec65506f0", size = 17231391, upload-time = "2026-06-15T12:50:03.047Z" }, + { url = "https://files.pythonhosted.org/packages/36/07/0019c72924909e4f64b9199770630ab7b8d7914b912b03230e68f5eda7ae/onnx-1.19.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:17aaf5832126de0a5197a5864e4f09a764dd7681d3035135547959b4b6b77a09", size = 18320936, upload-time = "2025-10-10T04:00:04.235Z" }, + { url = "https://files.pythonhosted.org/packages/af/2f/5c47acf740dc35f0decc640844260fbbdc0efa0565657c93fd7ff30f13f3/onnx-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01b292a4d0b197c45d8184545bbc8ae1df83466341b604187c1b05902cb9c920", size = 18044269, upload-time = "2025-10-10T04:00:07.449Z" }, + { url = "https://files.pythonhosted.org/packages/d5/61/6c457ee8c3a62a3cad0a4bfa4c5436bb3ac4df90c3551d40bee1224b5b51/onnx-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1839af08ab4a909e4af936b8149c27f8c64b96138981024e251906e0539d8bf9", size = 18218092, upload-time = "2025-10-10T04:00:11.135Z" }, + { url = "https://files.pythonhosted.org/packages/54/d5/ab832e1369505e67926a70e9a102061f89ad01f91aa296c4b1277cb81b25/onnx-1.19.1-cp311-cp311-win32.whl", hash = "sha256:0bdbb676e3722bd32f9227c465d552689f49086f986a696419d865cb4e70b989", size = 16344809, upload-time = "2025-10-10T04:00:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/8b/b5/6eb4611d24b85002f878ba8476b4cecbe6f9784c0236a3c5eff85236cc0a/onnx-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:1346853df5c1e3ebedb2e794cf2a51e0f33759affd655524864ccbcddad7035b", size = 16464319, upload-time = "2025-10-10T04:00:18.235Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ff/f0e1f06420c70e20d497fec7c94a864d069943b6312bedd4224c0ab946f8/onnx-1.19.1-cp311-cp311-win_arm64.whl", hash = "sha256:2d69c280c0e665b7f923f499243b9bb84fe97970b7a4668afa0032045de602c8", size = 16437503, upload-time = "2025-10-10T04:00:21.247Z" }, + { url = "https://files.pythonhosted.org/packages/50/07/f6c5b2cffef8c29e739616d1415aea22f7b7ef1f19c17f02b7cff71f5498/onnx-1.19.1-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:3612193a89ddbce5c4e86150869b9258780a82fb8c4ca197723a4460178a6ce9", size = 18327840, upload-time = "2025-10-10T04:00:24.259Z" }, + { url = "https://files.pythonhosted.org/packages/93/20/0568ebd52730287ae80cac8ac893a7301c793ea1630984e2519ee92b02a9/onnx-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c2fd2f744e7a3880ad0c262efa2edf6d965d0bd02b8f327ec516ad4cb0f2f15", size = 18042539, upload-time = "2025-10-10T04:00:27.693Z" }, + { url = "https://files.pythonhosted.org/packages/14/fd/cd7a0fd10a04f8cc5ae436b63e0022e236fe51b9dbb8ee6317fd48568c72/onnx-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:485d3674d50d789e0ee72fa6f6e174ab81cb14c772d594f992141bd744729d8a", size = 18218271, upload-time = "2025-10-10T04:00:30.495Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/cc8b8c05469fe08384b446304ad7e6256131ca0463bf6962366eebec98c0/onnx-1.19.1-cp312-cp312-win32.whl", hash = "sha256:638bc56ff1a5718f7441e887aeb4e450f37a81c6eac482040381b140bd9ba601", size = 16345111, upload-time = "2025-10-10T04:00:34.982Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5e/d1cb16693598a512c2cf9ffe0841d8d8fd2c83ae8e889efd554f5aa427cf/onnx-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:bc7e2e4e163e679721e547958b5a7db875bf822cad371b7c1304aa4401a7c7a4", size = 16465621, upload-time = "2025-10-10T04:00:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/90/32/da116cc61fdef334782aa7f87a1738431dd1af1a5d1a44bd95d6d51ad260/onnx-1.19.1-cp312-cp312-win_arm64.whl", hash = "sha256:17c215b1c0f20fe93b4cbe62668247c1d2294b9bc7f6be0ca9ced28e980c07b7", size = 16437505, upload-time = "2025-10-10T04:00:42.255Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b8/ab1fdfe2e8502f4dc4289fc893db35816bd20d080d8370f86e74dda5f598/onnx-1.19.1-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:4e5f938c68c4dffd3e19e4fd76eb98d298174eb5ebc09319cdd0ec5fe50050dc", size = 18327815, upload-time = "2025-10-10T04:00:45.682Z" }, + { url = "https://files.pythonhosted.org/packages/04/40/eb875745a4b92aea10e5e32aa2830f409c4d7b6f7b48ca1c4eaad96636c5/onnx-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:86e20a5984b017feeef2dbf4ceff1c7c161ab9423254968dd77d3696c38691d0", size = 18041464, upload-time = "2025-10-10T04:00:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/8586135f40dbe4989cec4d413164bc8fc5c73d37c566f33f5ea3a7f2b6f6/onnx-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d9c467f0f29993c12f330736af87972f30adb8329b515f39d63a0db929cb2c", size = 18218244, upload-time = "2025-10-10T04:00:51.891Z" }, + { url = "https://files.pythonhosted.org/packages/51/b5/4201254b8683129db5da3fb55aa1f7e56d0a8d45c66ce875dec21ca1ff25/onnx-1.19.1-cp313-cp313-win32.whl", hash = "sha256:65eee353a51b4e4ca3e797784661e5376e2b209f17557e04921eac9166a8752e", size = 16345330, upload-time = "2025-10-10T04:00:54.858Z" }, + { url = "https://files.pythonhosted.org/packages/69/67/c6d239afbcdbeb6805432969b908b5c9f700c96d332b34e3f99518d76caf/onnx-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:c3bc87e38b53554b1fc9ef7b275c81c6f5c93c90a91935bb0aa8d4d498a6d48e", size = 16465567, upload-time = "2025-10-10T04:00:57.893Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/89f1e40f5bc54595ff0dcf5391ce19e578b528973ccc74dd99800196d30d/onnx-1.19.1-cp313-cp313-win_arm64.whl", hash = "sha256:e41496f400afb980ec643d80d5164753a88a85234fa5c06afdeebc8b7d1ec252", size = 16437562, upload-time = "2025-10-10T04:01:00.703Z" }, + { url = "https://files.pythonhosted.org/packages/86/43/b186ccbc8fe7e93643a6a6d40bbf2bb6ce4fb9469bbd3453c77e270c50ad/onnx-1.19.1-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:5f6274abf0fd74e80e78ecbb44bd44509409634525c89a9b38276c8af47dc0a2", size = 18355703, upload-time = "2025-10-10T04:01:03.735Z" }, + { url = "https://files.pythonhosted.org/packages/60/f1/22ee4d8b8f9fa4cb1d1b9579da3b4b5187ddab33846ec5ac744af02c0e2b/onnx-1.19.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07dcd4d83584eb4bf8f21ac04c82643712e5e93ac2a0ed10121ec123cb127e1e", size = 18047830, upload-time = "2025-10-10T04:01:06.552Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a4/8f3d51e3a095d42cdf2039a590cff06d024f2a10efbd0b1a2a6b3825f019/onnx-1.19.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1975860c3e720db25d37f1619976582828264bdcc64fa7511c321ac4fc01add3", size = 18221126, upload-time = "2025-10-10T04:01:09.77Z" }, + { url = "https://files.pythonhosted.org/packages/4f/0d/f9d6c2237083f1aac14b37f0b03b0d81f1147a8e2af0c3828165e0a6a67b/onnx-1.19.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9807d0e181f6070ee3a6276166acdc571575d1bd522fc7e89dba16fd6e7ffed9", size = 16465560, upload-time = "2025-10-10T04:01:13.212Z" }, + { url = "https://files.pythonhosted.org/packages/36/70/8418a58faa7d606d6a92cab69ae8d361b3b3969bf7e7e9a65a86d5d1b674/onnx-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6ee83e6929d75005482d9f304c502ac7c9b8d6db153aa6b484dae74d0f28570", size = 18042812, upload-time = "2025-10-10T04:01:15.919Z" }, ] [[package]] name = "onnxruntime" -version = "1.26.0" +version = "1.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, + { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/81/29a9eb470994a75eb7b3ccf32be314d7c66675a00ac7b50294816cc2db27/onnxruntime-1.26.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ee1109ef4ef27cad90e823399e61e03b3c6c7bfe0fb820b4baf3678c15be8b3c", size = 18005108, upload-time = "2026-05-08T19:08:11.728Z" }, - { url = "https://files.pythonhosted.org/packages/66/c7/73efa6c8a4000c38fcc14947d84f234a17e5d66f203b37b7f1ad4a7b46eb/onnxruntime-1.26.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35c7c7b0ac2e02001d28fab6c9fc24e9abc5e6faa35e6e19c63cecf1406ba89f", size = 16043752, upload-time = "2026-05-08T19:07:10.707Z" }, - { url = "https://files.pythonhosted.org/packages/b6/3f/8de630f595daf6ce884d4dd95afd2a60e70ec6572e52bfee3aa2229befab/onnxruntime-1.26.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11a8df4dcfe9ad5ff0bd71a7571dbed019fabc7594676c89fe8b86ea029c246f", size = 18176043, upload-time = "2026-05-08T19:07:33.735Z" }, - { url = "https://files.pythonhosted.org/packages/9c/21/9f041de20787cd85498bd48e0ec4d098bf2a6c486e25b24b8dae1bf492b2/onnxruntime-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:e6456718125fd777c673f3b78d4a9ab58d6adea641e9afae85ee6444f0e0e9a9", size = 13023165, upload-time = "2026-05-08T19:08:00.633Z" }, - { url = "https://files.pythonhosted.org/packages/0e/82/3b9fe0ead2557cc3adf74c74c141bd1c7c4c6a9548c610af37df199f4512/onnxruntime-1.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:cd920e45b730e4a87833e2910d8ca375aaca9da6ccc09e24bce463b3356d637f", size = 12789514, upload-time = "2026-05-08T19:07:49.433Z" }, - { url = "https://files.pythonhosted.org/packages/81/b1/d111b1df656761f980d9e298a60039a9cb66036b1d039e777537743d0ac3/onnxruntime-1.26.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05b028781b322ad74b57ce5b50aa5280bb1fe96ceec334628ade681e0b24c1ac", size = 18016624, upload-time = "2026-05-12T00:41:01.735Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a0/3f9d896a0385a36bd04345d6d0b802821a5782adde562e7e135f6bb71c73/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91f2bb870a4b9224eba0a6728c1fa7a9e552b8e59e1083c51fbbc3d013f2b5c0", size = 16052692, upload-time = "2026-05-08T19:07:13.829Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/2a4e04f8dbeffad19bbcced4bcd4289bf478921518437404d6b92bdf213b/onnxruntime-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b6dd70599005bd1bf29779f04a91978b92b5e719c11a20068a8f8e535f725b6", size = 18185439, upload-time = "2026-05-08T19:07:36.299Z" }, - { url = "https://files.pythonhosted.org/packages/44/fc/026d0a7162b9c2153dac292baea9e027c42304dc1d9dc6f8ff5b4cfbaedd/onnxruntime-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:a26374dc7fbcaae593601086b242120e13f2310558df0991da6dd8b8fac00414", size = 13026427, upload-time = "2026-05-08T19:08:03.503Z" }, - { url = "https://files.pythonhosted.org/packages/3e/27/1dcf88e45e4c69db5f7b106f2dacc3801ba98994e082ca03e1dfdf7bfe57/onnxruntime-1.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:54a8053410fd31fd66469bd754fcfe8a4df9f7eb44756b4b5479bf50c842d948", size = 12796647, upload-time = "2026-05-08T19:07:52.108Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a2/c801242685e0ce48a4ca51dfafbb588765e0446397e123be53ba5598f3f5/onnxruntime-1.26.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccce19c5f771b8268902f77d9fed9e88f9499465d6780808faa6611a789d33f0", size = 18016563, upload-time = "2026-05-08T19:07:28.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/64/0492c0b1db04e29b2630c87cfa36f9d6872b1ca8614b90c5cad58fac7d76/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdbed8cf3b672b66acb032f33a253bc27f42bce6ece48ae3fab4fa483a5e96e0", size = 16052634, upload-time = "2026-05-08T19:07:16.885Z" }, - { url = "https://files.pythonhosted.org/packages/3d/26/4d09ddc755a84fc8d5e192991626b0e0680e8f6c5d58f4f1d05c42bc48cf/onnxruntime-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c07af6fc6d5557835f2b6ee7a96d8b3235d0c57a8e230efdedaee106a8a3cbc6", size = 18185632, upload-time = "2026-05-08T19:07:38.756Z" }, - { url = "https://files.pythonhosted.org/packages/77/89/3e52249aa08fa301e217ecba07b5246a8338fa2b401e109326e3fc5be0f9/onnxruntime-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:61bec80655efa460591c2bc655392d57d2650ce85533a6b9b3b7a790d7ea7916", size = 13026751, upload-time = "2026-05-08T19:08:06.2Z" }, - { url = "https://files.pythonhosted.org/packages/06/b3/c1c8782b14af6797c303de132d6eef26a9fb80dfacd3750ce57911d11c6b/onnxruntime-1.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a6677545ff451e3539a02746d2f207d8c5baa4a0a818886bb9d6a6eb9511ee89", size = 12796807, upload-time = "2026-05-08T19:07:54.879Z" }, - { url = "https://files.pythonhosted.org/packages/c3/f5/47b0676408abec652c14b84d7173e389837832d850c24f87184277313e8d/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e016edc15d3c19f36807e1c6b10be5b27807688c32720f91b5ae480a95215d0", size = 16057265, upload-time = "2026-05-08T19:07:19.603Z" }, - { url = "https://files.pythonhosted.org/packages/3b/45/33ab6deeef010ca844c877dd618cebc079590bbe52d2a3678e7223b1b908/onnxruntime-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5fc48a91a046a6a5c9b147f83fb41d65d24d24923373b222cdd248f0f4f4aac", size = 18197590, upload-time = "2026-05-08T19:07:41.422Z" }, - { url = "https://files.pythonhosted.org/packages/40/89/17546c1c20f6bfc3ae41c22152378a26edfea918af3129e2139dcd7c99f3/onnxruntime-1.26.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:33a791f31432a3af1a96db5e54818b37aba5e5eefc2e6af5794c10a9118a9993", size = 18019724, upload-time = "2026-05-08T19:07:30.723Z" }, - { url = "https://files.pythonhosted.org/packages/bb/24/89457a35f6af29538a76647f2c18c3a28277e6c19234c847e7b4b7c19860/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e90c00732c4553618103149d93f688e8c3063017938f8983e21a71d9f3b6d22e", size = 16054821, upload-time = "2026-05-08T19:07:22.348Z" }, - { url = "https://files.pythonhosted.org/packages/12/f9/15b2e1815cf570d238e0135529f80d2dce64e8e8818a1489cae83823c5c6/onnxruntime-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01498e80ba8988428d08c2d51b1338f89e3de2a93e6ffe555f79c68f26a5c06b", size = 18185815, upload-time = "2026-05-08T19:07:44.179Z" }, - { url = "https://files.pythonhosted.org/packages/d7/65/2e11055faf015e4b07f45b513fa49b391baf2e19d92d77d73ebee13c1004/onnxruntime-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:7ead61450d8405167c87dd3a31d8da1d576b490a57dab1aa8b82a7da6825f5aa", size = 13349887, upload-time = "2026-05-08T19:08:08.671Z" }, - { url = "https://files.pythonhosted.org/packages/19/e4/0f9d1a5718b1781c610c1e354765a3820597081754277a6a9a2b50705702/onnxruntime-1.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:31d71a53490e46910877d0902b5ad99c69a5955e5c7ea6c82863519410e1ba7c", size = 13140121, upload-time = "2026-05-08T19:07:57.804Z" }, - { url = "https://files.pythonhosted.org/packages/1c/42/3b8e635f067d06d9f45bede470b8d539d101a4166c272213158dfd08b6ce/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b6d258fb78fdfcf049795bcfaa74dcb90ae7baa277afd21e6fd28b83f2c496", size = 16057240, upload-time = "2026-05-08T19:07:25.163Z" }, - { url = "https://files.pythonhosted.org/packages/93/99/f2be40a31b908d96b861ae0ce98582fa376c18a7f816b9d5eb4cd6aa0a4c/onnxruntime-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4eefd386a45202aefb7a5132b94f32df9d506c9edcc7faf2fc60d65183f4b183", size = 18197382, upload-time = "2026-05-08T19:07:46.965Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d9757c62a0f96b5193f8d447a141eefd14498c404cc5caf1a6f3233cf102/onnxruntime-1.24.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:79b3119ab9f4f3817062e6dbe7f4a44937de93905e3a31ba34313d18cb49e7be", size = 17212018, upload-time = "2026-02-05T17:32:13.986Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/b3305c39144e19dbe8791802076b29b4b592b09de03d0e340c1314bfd408/onnxruntime-1.24.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:86bc43e922b1f581b3de26a3dc402149c70e5542fceb5bec6b3a85542dbeb164", size = 15018703, upload-time = "2026-02-05T17:30:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/94/d6/d273b75fe7825ea3feed321dd540aef33d8a1380ddd8ac3bb70a8ed000fe/onnxruntime-1.24.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1cabe71ca14dcfbf812d312aab0a704507ac909c137ee6e89e4908755d0fc60e", size = 17096352, upload-time = "2026-02-05T17:31:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/0616101a3938bfe2918ea60b581a9bbba61ffc255c63388abb0885f7ce18/onnxruntime-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:3273c330f5802b64b4103e87b5bbc334c0355fff1b8935d8910b0004ce2f20c8", size = 12493235, upload-time = "2026-02-05T17:32:04.451Z" }, + { url = "https://files.pythonhosted.org/packages/c8/30/437de870e4e1c6d237a2ca5e11f54153531270cb5c745c475d6e3d5c5dcf/onnxruntime-1.24.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:7307aab9e2e879c0171f37e0eb2808a5b4aec7ba899bb17c5f0cedfc301a8ac2", size = 17211043, upload-time = "2026-02-05T17:32:16.909Z" }, + { url = "https://files.pythonhosted.org/packages/21/60/004401cd86525101ad8aa9eec301327426555d7a77fac89fd991c3c7aae6/onnxruntime-1.24.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780add442ce2d4175fafb6f3102cdc94243acffa3ab16eacc03dd627cc7b1b54", size = 15016224, upload-time = "2026-02-05T17:30:56.791Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a1/43ad01b806a1821d1d6f98725edffcdbad54856775643718e9124a09bfbe/onnxruntime-1.24.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34b6119526eda12613f0d0498e2ae59563c247c370c9cef74c2fc93133dde157", size = 17098191, upload-time = "2026-02-05T17:31:31.87Z" }, + { url = "https://files.pythonhosted.org/packages/ff/37/5beb65270864037d5c8fb25cfe6b23c48b618d1f4d06022d425cbf29bd9c/onnxruntime-1.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:df0af2f1cfcfff9094971c7eb1d1dfae7ccf81af197493c4dc4643e4342c0946", size = 12493108, upload-time = "2026-02-05T17:32:07.076Z" }, + { url = "https://files.pythonhosted.org/packages/95/77/7172ecfcbdabd92f338e694f38c325f6fab29a38fa0a8c3d1c85b9f4617c/onnxruntime-1.24.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:82e367770e8fba8a87ba9f4c04bb527e6d4d7204540f1390f202c27a3b759fb4", size = 17211381, upload-time = "2026-02-05T17:31:09.601Z" }, + { url = "https://files.pythonhosted.org/packages/79/5b/532a0d75b93bbd0da0e108b986097ebe164b84fbecfdf2ddbf7c8a3a2e83/onnxruntime-1.24.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1099f3629832580fedf415cfce2462a56cc9ca2b560d6300c24558e2ac049134", size = 15016000, upload-time = "2026-02-05T17:31:00.116Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b5/40606c7bce0702975a077bc6668cd072cd77695fc5c0b3fcf59bdb1fe65e/onnxruntime-1.24.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6361dda4270f3939a625670bd67ae0982a49b7f923207450e28433abc9c3a83b", size = 17097637, upload-time = "2026-02-05T17:31:34.787Z" }, + { url = "https://files.pythonhosted.org/packages/d5/a0/9e8f7933796b466241b934585723c700d8fb6bde2de856e65335193d7c93/onnxruntime-1.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:bd1e4aefe73b6b99aa303cd72562ab6de3cccb09088100f8ad1c974be13079c7", size = 12492467, upload-time = "2026-02-05T17:32:09.834Z" }, + { url = "https://files.pythonhosted.org/packages/fb/8a/ee07d86e35035f9fed42497af76435f5a613d4e8b6c537ea0f8ef9fa85da/onnxruntime-1.24.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88a2b54dca00c90fca6303eedf13d49b5b4191d031372c2e85f5cffe4d86b79e", size = 15025407, upload-time = "2026-02-05T17:31:02.251Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9e/ab3e1dda4b126313d240e1aaa87792ddb1f5ba6d03ca2f093a7c4af8c323/onnxruntime-1.24.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dfbba602da840615ed5b431facda4b3a43b5d8276cf9e0dbf13d842df105838", size = 17099810, upload-time = "2026-02-05T17:31:37.537Z" }, + { url = "https://files.pythonhosted.org/packages/87/23/167d964414cee2af9c72af323b28d2c4cb35beed855c830a23f198265c79/onnxruntime-1.24.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:890c503ca187bc883c3aa72c53f2a604ec8e8444bdd1bf6ac243ec6d5e085202", size = 17214004, upload-time = "2026-02-05T17:31:11.917Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/6e5558fdd51027d6830cf411bc003ae12c64054826382e2fab89e99486a0/onnxruntime-1.24.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da1b84b3bdeec543120df169e5e62a1445bf732fc2c7fb036c2f8a4090455e8", size = 15017034, upload-time = "2026-02-05T17:31:04.331Z" }, + { url = "https://files.pythonhosted.org/packages/91/d4/3cb1c9eaae1103265ed7eb00a3eaeb0d9ba51dc88edc398b7071c9553bed/onnxruntime-1.24.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:557753ec345efa227c6a65139f3d29c76330fcbd54cc10dd1b64232ebb939c13", size = 17097531, upload-time = "2026-02-05T17:31:40.303Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/4522b199c12db7c5b46aaf265ee0d741abe65ea912f6c0aaa2cc18a4654d/onnxruntime-1.24.1-cp314-cp314-win_amd64.whl", hash = "sha256:ea4942104805e868f3ddddfa1fbb58b04503a534d489ab2d1452bbfa345c78c2", size = 12795556, upload-time = "2026-02-05T17:32:11.886Z" }, + { url = "https://files.pythonhosted.org/packages/a1/53/3b8969417276b061ff04502ccdca9db4652d397abbeb06c9f6ae05cec9ca/onnxruntime-1.24.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea8963a99e0f10489acdf00ef3383c3232b7e44aa497b063c63be140530d9f85", size = 15025434, upload-time = "2026-02-05T17:31:06.942Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a2/cfcf009eb38d90cc628c087b6506b3dfe1263387f3cbbf8d272af4fef957/onnxruntime-1.24.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34488aa760fb5c2e6d06a7ca9241124eb914a6a06f70936a14c669d1b3df9598", size = 17099815, upload-time = "2026-02-05T17:31:43.092Z" }, ] [[package]] name = "onnxruntime-gpu" -version = "1.26.0" +version = "1.24.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, + { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/0f/696b4f94a282952239ffed39db78cb17a00ad993acd929cfac010a09759b/onnxruntime_gpu-1.26.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fa231294c2643911d2a7d16469c4808b0bdcdc4b5f4063d3a53744ce25b683a", size = 276956564, upload-time = "2026-05-08T19:15:35.133Z" }, - { url = "https://files.pythonhosted.org/packages/ef/26/a417b7a1cdbbf56a389bfcd399255be23f30e5721e3e519472fe8dde9c99/onnxruntime_gpu-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc5329aad02d9745cc3ae9cdb185bfa1aad242a7bf89b8c471280002ec40f98a", size = 226539455, upload-time = "2026-05-08T19:09:24.631Z" }, - { url = "https://files.pythonhosted.org/packages/94/fd/59bee7cffaa435da44fefdeb63e29c61de4dbfa4b279852f59cd02c042ae/onnxruntime_gpu-1.26.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c01119ed4d9449d60367fa8ccffcd02bd3fe736754284e4b198d131f54edad6", size = 276971796, upload-time = "2026-05-08T19:15:46.192Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e4/9b378a5466ea0bed65e5beb8e09254973c580a6522810a38afbcc45e5105/onnxruntime_gpu-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:5f49c44689894650990e4c8a857d2edafc276fbd79bba57ceb224bd18d25d491", size = 226548963, upload-time = "2026-05-08T19:09:34.925Z" }, - { url = "https://files.pythonhosted.org/packages/dd/97/fe8979f44b9275654b42f7bb556e30789b71a1b22998c83b540df2b1b774/onnxruntime_gpu-1.26.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfda2fad535595bfc3e570eb588092717711dcb2957656d814695e0c9ceb1508", size = 276974871, upload-time = "2026-05-08T19:15:58.052Z" }, - { url = "https://files.pythonhosted.org/packages/67/3f/59f1777a394625ecc9a85636de57dc47c25dbb5f888da050f1463955a0ce/onnxruntime_gpu-1.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:6ab9f9c741d2e239b2e321ab0d389c04329d4ab7f11e3b92dd3aa7db1c59dee4", size = 226548083, upload-time = "2026-05-08T19:09:44.408Z" }, - { url = "https://files.pythonhosted.org/packages/89/96/360328e3c463f7ea08e853c4239c397e83363dd0204de71a710dc1a544bd/onnxruntime_gpu-1.26.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcf6f347cad9f88a9a625c2b352cf9de927528aedb627ebbc089a201f1990b94", size = 276992052, upload-time = "2026-05-08T19:16:09.892Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c8/aa2dc0e79bba577f37d5448bcb32fea79977e07506684d8138c19a0f1077/onnxruntime_gpu-1.26.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e6e4fb1ec9ae1cf456534d9115f106ab2a1ae96fa513b4ed0f4795302b4a2c6", size = 276978254, upload-time = "2026-05-08T19:16:22.096Z" }, - { url = "https://files.pythonhosted.org/packages/41/e7/923298431e669567d7ccc2a4c898b6534a47641a051569fd97165fe6d9b8/onnxruntime_gpu-1.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e592439b0183d303c2374517b5b392599a3d50b2dc9de949b9b15731ac921c9", size = 229142768, upload-time = "2026-05-08T19:09:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/97/91/93ffe5431d154989f5e04864a25a97eea480997d771232bcbbc538188241/onnxruntime_gpu-1.26.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56dc7b73954ff4bdc71f5b8ab306b6f61be5d007881b6ef423a609e2b9cd088b", size = 276991545, upload-time = "2026-05-08T19:16:33.347Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c7/07d06175f1124fc89e8b7da30d70eb8e0e1400d90961ae1cbea9da69e69b/onnxruntime_gpu-1.24.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac4bfc90c376516b13d709764ab257e4e3d78639bf6a2ccfc826e9db4a5c7ddf", size = 252616647, upload-time = "2026-02-05T17:24:02.993Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/47c2a873bf5fc307cda696e8a8cb54b7c709f5a4b3f9e2b4a636066a63c2/onnxruntime_gpu-1.24.1-cp311-cp311-win_amd64.whl", hash = "sha256:ccd800875cb6c04ce623154c7fa312da21631ef89a9543c9a21593817cfa3473", size = 207089749, upload-time = "2026-02-05T17:23:59.5Z" }, + { url = "https://files.pythonhosted.org/packages/db/a8/fb1a36a052321a839cc9973f6cfd630709412a24afff2d7315feb3efc4b8/onnxruntime_gpu-1.24.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:710bf83751e6761584ad071102af3cbffd4b42bb77b2e3caacfb54ffbaa0666b", size = 252628733, upload-time = "2026-02-05T17:24:12.926Z" }, + { url = "https://files.pythonhosted.org/packages/52/65/48f694b81a963f3ee575041d5f2879b15268f5e7e14d90c3e671836c9646/onnxruntime_gpu-1.24.1-cp312-cp312-win_amd64.whl", hash = "sha256:b128a42b3fa098647765ba60c2af9d4bf839181307cfac27da649364feb37f7b", size = 207089008, upload-time = "2026-02-05T17:24:07.126Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e7/4e19062e95d3701c0d32c228aa848ba4a1cc97651e53628d978dba8e1267/onnxruntime_gpu-1.24.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:db9acb0d0e59d93b4fa6b7fd44284ece4408d0acee73235d43ed343f8cee7ee5", size = 252629216, upload-time = "2026-02-05T17:24:24.604Z" }, + { url = "https://files.pythonhosted.org/packages/c4/82/223d7120d8a98b07c104ddecfb0cc2536188e566a4e9c2dee7572453f89c/onnxruntime_gpu-1.24.1-cp313-cp313-win_amd64.whl", hash = "sha256:59fdb40743f0722f3b859209f649ea160ca6bb42799e43f49b70a3ec5fc8c4ad", size = 207089285, upload-time = "2026-02-05T17:24:18.497Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/3159e57f09d7e6c8ad47d8ba8d5bd7494f383bc1071481cf38c9c8142bf9/onnxruntime_gpu-1.24.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88ca04e1dffea2d4c3c79cf4de7f429e99059d085f21b3e775a8d36380cd5186", size = 252633977, upload-time = "2026-02-05T17:24:33.568Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b4/51ad0ab878ff1456a831a0566b4db982a904e22f138e4b2c5f021bac517f/onnxruntime_gpu-1.24.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ced66900b1f48bddb62b5233925c3b56f8e008e2c34ebf8c060b20cae5842bcf", size = 252629039, upload-time = "2026-02-05T17:24:43.551Z" }, + { url = "https://files.pythonhosted.org/packages/9c/46/336d4e09a6af66532eedde5c8f03a73eaa91a046b408522259ab6a604363/onnxruntime_gpu-1.24.1-cp314-cp314-win_amd64.whl", hash = "sha256:129f6ae8b331a6507759597cd317b23e94aed6ead1da951f803c3328f2990b0c", size = 209487551, upload-time = "2026-02-05T17:24:26.373Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/a3b20276261f5e64dbd72bda656af988282cff01f18c2685953600e2f810/onnxruntime_gpu-1.24.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2cee7e12b0f4813c62f9a48df83fd01d066cc970400c832252cf3c155a6957", size = 252633096, upload-time = "2026-02-05T17:24:53.248Z" }, ] [[package]] name = "onnxruntime-migraphx" -version = "1.25.0" +version = "1.24.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "flatbuffers" }, { name = "numpy" }, { name = "packaging" }, { name = "protobuf" }, + { name = "sympy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/3c/8c142017df701c38c4f5d6cae0294a2891bdd0f719eaff42e87564ac1480/onnxruntime_migraphx-1.25.0-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:c7f28ddb8eef13fd5e9002d5162bfe848abfd02dcf13ab6a6b8c00380c014f33", size = 21467402, upload-time = "2026-04-21T05:23:52.026Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0d/21730262547fae9557cb3fc85d891d600158c92399f68e4a760aed59bffc/onnxruntime_migraphx-1.25.0-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:5c1dac156f98a9a20556bbc1e1c69d5ca1f78bad14d4953aa2b3cb2711507166", size = 21477899, upload-time = "2026-04-21T05:24:31.072Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/39764a6c87e9f97c0891f194100883192a0ce88725aba6215d499cd714b8/onnxruntime_migraphx-1.25.0-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:d778d88bfdd8818817618ff3e4d973ed861428f61ba7c8b841690c699cb29ed4", size = 21477896, upload-time = "2026-04-21T05:25:11.969Z" }, - { url = "https://files.pythonhosted.org/packages/35/97/f8d8f3141c606e14a23e52a035c123524f5b714e15a1f9425a940906dfce/onnxruntime_migraphx-1.25.0-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:5558a05f4cc1975c9974090337bbc460aae72b29ae20afb42bec93b74df0eae8", size = 21478521, upload-time = "2026-04-21T05:25:52.121Z" }, + { url = "https://files.pythonhosted.org/packages/dd/da/ca7ebc1a8d1193c97ceb9a05fad50f675eb955dc51beb7eb9ba89c8e7db0/onnxruntime_migraphx-1.24.2-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:a2b434fb8880cac2b268950bdf279f33741d29c1f1c5461d27af835e8e288043", size = 20339710, upload-time = "2026-02-21T07:25:13.17Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/8c83ec45a9365b4256495ca55eea30da7f03b02177b6da423c7da1ff5f6a/onnxruntime_migraphx-1.24.2-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:ec814818da952bda3062e26f56c88bb713c00491ef91f86716c8d7346f9bc31b", size = 20341883, upload-time = "2026-02-21T07:25:17.86Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/4776ac68dbc46ca02c9a14cc9e5c496017f47a18cedf606cc38f4911b96a/onnxruntime_migraphx-1.24.2-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:20e497538362170af639b03a40249d7ed61b873ac354f20d732b90252206e320", size = 20342422, upload-time = "2026-02-21T07:25:22.526Z" }, + { url = "https://files.pythonhosted.org/packages/76/44/db9035204a3363f9c0a4822c68e9a7520c13ef8d261f96b89b1375106dab/onnxruntime_migraphx-1.24.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:9d7f1b1a2b9651143a2080b4f42ee99eead02023de1855d1b8a02199a9c179aa", size = 20343783, upload-time = "2026-02-21T07:25:29.155Z" }, ] [[package]] @@ -1523,70 +1781,70 @@ wheels = [ [[package]] name = "orjson" -version = "3.11.9" +version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/51/3fb9e65ae76ee97bd611869a503fa3fc0a6e81dd8b737cf3003f682df7ff/orjson-3.11.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f01c4818b3fc9b0da8e096722a84318071eaa118df35f6ed2344da0e73a5444f", size = 228522, upload-time = "2026-05-06T15:09:35.362Z" }, - { url = "https://files.pythonhosted.org/packages/16/fa/9d54b07cb3f3b0bfd57841478e42d7a0ece4a9f49f9907eecf5a45461687/orjson-3.11.9-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:3ebca4179031ee716ed076ffadc29428e900512f6fccee8614c9983157fcf19c", size = 128463, upload-time = "2026-05-06T15:09:37.063Z" }, - { url = "https://files.pythonhosted.org/packages/88/b1/6ceafc2eefd0a553e3be77ce6c49d107e772485d9568629376171c50e634/orjson-3.11.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48ee05097750de0ff69ed5b7bbcf0732182fd57a24043dcc2a1da780a5ead3a5", size = 132306, upload-time = "2026-05-06T15:09:38.299Z" }, - { url = "https://files.pythonhosted.org/packages/ea/76/f11311285324a40aab1e3031385c50b635a7cd0734fdaf60c7e89a696f60/orjson-3.11.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6082706765a95a6680d812e1daf1c0cfe8adec7831b3ff3b625693f3b461b1c", size = 127988, upload-time = "2026-05-06T15:09:39.597Z" }, - { url = "https://files.pythonhosted.org/packages/9e/85/0ef63bcf1337f44031ce9b91b1919563f62a37527b3ea4368bb15a22e5d7/orjson-3.11.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:277fefe9d76ee17eb14debf399e3533d4d63b5f677a4d3719eb763536af1f4bd", size = 135188, upload-time = "2026-05-06T15:09:40.957Z" }, - { url = "https://files.pythonhosted.org/packages/05/94/b0d27090ea8a2095db3c2bd1b1c96f96f19bbb494d7fef33130e846e613d/orjson-3.11.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03db380e3780fa0015ed776a90f20e8e20bb11dde13b216ce19e5718e3dfba62", size = 145937, upload-time = "2026-05-06T15:09:42.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/eb/75d50c29c05b8054013e221e598820a365c8e64065312e75e202ed880709/orjson-3.11.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33d7d766701847dc6729846362dc27895d2f2d2251264f9d10e7cb9878194877", size = 132758, upload-time = "2026-05-06T15:09:43.945Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/360686f39348aa88827cb6fbf7dc606fd41c831a35235e1abf1db8e3a9e6/orjson-3.11.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:147302878da387104b66bb4a8b0227d1d487e976ce41a8501916161072ed87b1", size = 133971, upload-time = "2026-05-06T15:09:45.239Z" }, - { url = "https://files.pythonhosted.org/packages/0e/30/3178eb16f3221aeef068b6f1f1ebe05f656ea5c6dffe9f6c917329fe17a3/orjson-3.11.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3513550321f8c8c811a7c3297b8a630e82dc08e4c10216d07703c997776236cd", size = 141685, upload-time = "2026-05-06T15:09:46.858Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f1/ff2f19ed0225f9680fafa42febca3570dd59444ebf190980738d376214c2/orjson-3.11.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c5d001196b89fa9cf0a4ab79766cd835b991a166e4b621ba95089edc50c429ff", size = 415167, upload-time = "2026-05-06T15:09:48.312Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/863bddf0da6e9e586765414debd54b4e58db05f560902b6d00658cb88636/orjson-3.11.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:16969c9d369c98eb084889c6e4d2d39b77c7eb38ceccf8da2a9fff62ae908980", size = 147913, upload-time = "2026-05-06T15:09:49.733Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4081492586d75b073d60c5271a8d0f05a0955cabf1e34c8473f6fcd84235/orjson-3.11.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:63e0efbc991250c0b3143488fa57d95affcabbfc63c99c48d625dd37779aafe2", size = 136959, upload-time = "2026-05-06T15:09:51.311Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bd/70b6ab193594d7abb875320c0a7c8335e846f28968c432c31042409c3c8d/orjson-3.11.9-cp311-cp311-win32.whl", hash = "sha256:14ed654580c1ed2bc217352ec82f91b047aef82951aa71c7f64e0dcb03c0e180", size = 131533, upload-time = "2026-05-06T15:09:52.637Z" }, - { url = "https://files.pythonhosted.org/packages/3f/17/1a1a228183d62d1b77e2c30d210f47dd4768b310ebe1607c63e3c0e3a71e/orjson-3.11.9-cp311-cp311-win_amd64.whl", hash = "sha256:57ea77fb70a448ce87d18fca050193202a3da5e54598f6501ca5476fb66cfe02", size = 127106, upload-time = "2026-05-06T15:09:54.204Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/285de5fa296d09681ee9c546cd4a8aeb773b701cf343dc125994f4d52953/orjson-3.11.9-cp311-cp311-win_arm64.whl", hash = "sha256:19b72ed11572a2ee51a67a903afbe5af504f84ed6f529c0fe44b0ab3fb5cc697", size = 126848, upload-time = "2026-05-06T15:09:55.551Z" }, - { url = "https://files.pythonhosted.org/packages/16/6d/11867a3ffa3a3608d84a4de51ef4dd0896d6b5cc9132fbe1daf593e677bc/orjson-3.11.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9ef6fe90aadef185c7b128859f40beb24720b4ecea95379fc9000931179c3a49", size = 228515, upload-time = "2026-05-06T15:09:57.265Z" }, - { url = "https://files.pythonhosted.org/packages/24/75/05912954c8b288f34fcf5cd4b9b071cb4f6e77b9961e175e56ebb258089f/orjson-3.11.9-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e5c9b8f28e726e97d97696c826bc7bea5d71cecd63576dba92924a32c1961291", size = 128409, upload-time = "2026-05-06T15:09:59.063Z" }, - { url = "https://files.pythonhosted.org/packages/ab/86/1c3a47df3bc8191ea9ac51603bbb872a95167a364320c269f2557911f406/orjson-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26a473dbb4162108b27901492546f83c76fdcea3d0eadff00ae7a07e18dcce09", size = 132106, upload-time = "2026-05-06T15:10:00.798Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cf/b33b5f3e695ae7d63feef9d915c37cc3b8f465493dcd4f8e0b4c697a2366/orjson-3.11.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:011382e2a60fda9d46f1cdee31068cfc52ffe952b587d683ec0463002802a0f4", size = 127864, upload-time = "2026-05-06T15:10:02.15Z" }, - { url = "https://files.pythonhosted.org/packages/31/6a/6cf69385a58208024fcb8c014e2141b8ce838aba6492b589f8acfff97fab/orjson-3.11.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2d3dc759490128c5c1711a53eeaa8ee1d437fd0038ffd2b6008abf46db3f882", size = 135213, upload-time = "2026-05-06T15:10:03.515Z" }, - { url = "https://files.pythonhosted.org/packages/e8/f8/0b1bd3e8f2efcdd376af5c8cfd79eaf13f018080c0089c80ebd724e3c7fb/orjson-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8ea516b3726d190e1b4297e6f4e7a8650347ae053868a18163b4dd3641d1fff", size = 145994, upload-time = "2026-05-06T15:10:05.083Z" }, - { url = "https://files.pythonhosted.org/packages/f3/59/dab79f61044c529d2c81aecdc589b1f833a1c8dec11ba3b1c2498a02ca7e/orjson-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380cdce7ba24989af81d0a7013d0aaec5d0e2a21734c0e2681b1bc4f141957fe", size = 132744, upload-time = "2026-05-06T15:10:06.853Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a4/82b7a2fe5d8a67a59ed831b24d59a3d46ea7d207b66e1602d376541d94a6/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be4fa4f0af7fa18951f7ab3fc2148e223af211bf03f59e1c6034ec3f97f21d61", size = 134014, upload-time = "2026-05-06T15:10:08.213Z" }, - { url = "https://files.pythonhosted.org/packages/50/c7/375e83a76851b73b2e39f3bcf0e5a19e2b89bad13e5bca97d0b293d27f24/orjson-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a8f5f8bc7ce7d59f08d9f99fa510c06496164a24cb5f3d34537dbd9ca30132e2", size = 141509, upload-time = "2026-05-06T15:10:09.595Z" }, - { url = "https://files.pythonhosted.org/packages/7f/7c/49d5d82a3d3097f641f094f552131f1e2723b0b8cb0fa2874ab65ecfffa6/orjson-3.11.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4d7fde5501b944f83b3e665e1b31343ff6e154b15560a16b7130ea1e594a4206", size = 415127, upload-time = "2026-05-06T15:10:11.049Z" }, - { url = "https://files.pythonhosted.org/packages/3a/dc/7446c538590d55f455647e5f3c61fc33f7108714e7afcffa6a2a033f8350/orjson-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cde1a448023ba7d5bb4c01c5afb48894380b5e4956e0627266526587ef4e535f", size = 148025, upload-time = "2026-05-06T15:10:12.842Z" }, - { url = "https://files.pythonhosted.org/packages/df/e5/4d2d8af06f788329b4f78f8cc3679bb395392fcaa1e4d8d3c33e85308fa4/orjson-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:71e63adb0e1f1ed5d9e168f50a91ceb93ae6420731d222dc7da5c69409aa47aa", size = 136943, upload-time = "2026-05-06T15:10:14.405Z" }, - { url = "https://files.pythonhosted.org/packages/06/69/850264ccf6d80f6b174620d30a87f65c9b1490aba33fe6b62798e618cad3/orjson-3.11.9-cp312-cp312-win32.whl", hash = "sha256:2d057a602cdd19a0ad680417527c45b6961a095081c0f46fe0e03e304aac6470", size = 131606, upload-time = "2026-05-06T15:10:15.791Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d5/973a43fc9c55e20f2051e9830997649f669be0cb3ca52192087c0143f118/orjson-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:59e403b1cc5a676da8eaf31f6254801b7341b3e29efa85f92b48d272637e77be", size = 127101, upload-time = "2026-05-06T15:10:17.129Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/495470f0e4a18f73fa10b7f6b84b464ec4cc5291c4e0c7c2a6c400bef006/orjson-3.11.9-cp312-cp312-win_arm64.whl", hash = "sha256:9af678d6488357948f1f84c6cd1c1d397c014e1ae2f98ae082a44eb48f602624", size = 126736, upload-time = "2026-05-06T15:10:18.645Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, - { url = "https://files.pythonhosted.org/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, - { url = "https://files.pythonhosted.org/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, - { url = "https://files.pythonhosted.org/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, - { url = "https://files.pythonhosted.org/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, - { url = "https://files.pythonhosted.org/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, - { url = "https://files.pythonhosted.org/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, - { url = "https://files.pythonhosted.org/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, - { url = "https://files.pythonhosted.org/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, - { url = "https://files.pythonhosted.org/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, - { url = "https://files.pythonhosted.org/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, - { url = "https://files.pythonhosted.org/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, - { url = "https://files.pythonhosted.org/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, - { url = "https://files.pythonhosted.org/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, - { url = "https://files.pythonhosted.org/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, - { url = "https://files.pythonhosted.org/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, - { url = "https://files.pythonhosted.org/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, - { url = "https://files.pythonhosted.org/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, - { url = "https://files.pythonhosted.org/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, - { url = "https://files.pythonhosted.org/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, - { url = "https://files.pythonhosted.org/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, + { url = "https://files.pythonhosted.org/packages/37/02/da6cb01fc6087048d7f61522c327edf4250f1683a58a839fdcc435746dd5/orjson-3.11.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9487abc2c2086e7c8eb9a211d2ce8855bae0e92586279d0d27b341d5ad76c85c", size = 228664, upload-time = "2026-02-02T15:37:25.542Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c2/5885e7a5881dba9a9af51bc564e8967225a642b3e03d089289a35054e749/orjson-3.11.7-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:79cacb0b52f6004caf92405a7e1f11e6e2de8bdf9019e4f76b44ba045125cd6b", size = 125344, upload-time = "2026-02-02T15:37:26.92Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1d/4e7688de0a92d1caf600dfd5fb70b4c5bfff51dfa61ac555072ef2d0d32a/orjson-3.11.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2e85fe4698b6a56d5e2ebf7ae87544d668eb6bde1ad1226c13f44663f20ec9e", size = 128404, upload-time = "2026-02-02T15:37:28.108Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b2/ec04b74ae03a125db7bd69cffd014b227b7f341e3261bf75b5eb88a1aa92/orjson-3.11.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8d14b71c0b12963fe8a62aac87119f1afdf4cb88a400f61ca5ae581449efcb5", size = 123677, upload-time = "2026-02-02T15:37:30.287Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/f95bdf960605f08f827f6e3291fe243d8aa9c5c9ff017a8d7232209184c3/orjson-3.11.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91c81ef070c8f3220054115e1ef468b1c9ce8497b4e526cb9f68ab4dc0a7ac62", size = 128950, upload-time = "2026-02-02T15:37:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1b/de59c57bae1d148ef298852abd31909ac3089cff370dfd4cd84cc99cbc42/orjson-3.11.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:411ebaf34d735e25e358a6d9e7978954a9c9d58cfb47bc6683cdc3964cd2f910", size = 141756, upload-time = "2026-02-02T15:37:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/ee/9e/9decc59f4499f695f65c650f6cfa6cd4c37a3fbe8fa235a0a3614cb54386/orjson-3.11.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a16bcd08ab0bcdfc7e8801d9c4a9cc17e58418e4d48ddc6ded4e9e4b1a94062b", size = 130812, upload-time = "2026-02-02T15:37:34.204Z" }, + { url = "https://files.pythonhosted.org/packages/28/e6/59f932bcabd1eac44e334fe8e3281a92eacfcb450586e1f4bde0423728d8/orjson-3.11.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c0b51672e466fd7e56230ffbae7f1639e18d0ce023351fb75da21b71bc2c960", size = 133444, upload-time = "2026-02-02T15:37:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/f1/36/b0f05c0eaa7ca30bc965e37e6a2956b0d67adb87a9872942d3568da846ae/orjson-3.11.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:136dcd6a2e796dfd9ffca9fc027d778567b0b7c9968d092842d3c323cef88aa8", size = 138609, upload-time = "2026-02-02T15:37:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/b8/03/58ec7d302b8d86944c60c7b4b82975d5161fcce4c9bc8c6cb1d6741b6115/orjson-3.11.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:7ba61079379b0ae29e117db13bda5f28d939766e410d321ec1624afc6a0b0504", size = 408918, upload-time = "2026-02-02T15:37:38.076Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/868d65ef9a8b99be723bd510de491349618abd9f62c826cf206d962db295/orjson-3.11.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0527a4510c300e3b406591b0ba69b5dc50031895b0a93743526a3fc45f59d26e", size = 143998, upload-time = "2026-02-02T15:37:39.706Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/1e18e1c83afe3349f4f6dc9e14910f0ae5f82eac756d1412ea4018938535/orjson-3.11.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a709e881723c9b18acddcfb8ba357322491ad553e277cf467e1e7e20e2d90561", size = 134802, upload-time = "2026-02-02T15:37:41.002Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0b/ccb7ee1a65b37e8eeb8b267dc953561d72370e85185e459616d4345bab34/orjson-3.11.7-cp311-cp311-win32.whl", hash = "sha256:c43b8b5bab288b6b90dac410cca7e986a4fa747a2e8f94615aea407da706980d", size = 127828, upload-time = "2026-02-02T15:37:42.241Z" }, + { url = "https://files.pythonhosted.org/packages/af/9e/55c776dffda3f381e0f07d010a4f5f3902bf48eaba1bb7684d301acd4924/orjson-3.11.7-cp311-cp311-win_amd64.whl", hash = "sha256:6543001328aa857187f905308a028935864aefe9968af3848401b6fe80dbb471", size = 124941, upload-time = "2026-02-02T15:37:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8e/424a620fa7d263b880162505fb107ef5e0afaa765b5b06a88312ac291560/orjson-3.11.7-cp311-cp311-win_arm64.whl", hash = "sha256:1ee5cc7160a821dfe14f130bc8e63e7611051f964b463d9e2a3a573204446a4d", size = 126245, upload-time = "2026-02-02T15:37:45.18Z" }, + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, ] [[package]] @@ -1600,98 +1858,98 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.4" +version = "0.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] [[package]] name = "pillow" -version = "12.2.0" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, + { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" }, + { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" }, + { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" }, + { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" }, + { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" }, + { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" }, + { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" }, + { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" }, + { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" }, + { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" }, + { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" }, + { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" }, + { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" }, + { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" }, + { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" }, + { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" }, + { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" }, + { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" }, + { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" }, + { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" }, + { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" }, + { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" }, + { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" }, + { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" }, + { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" }, ] [[package]] @@ -1703,6 +1961,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, ] +[[package]] +name = "prettytable" +version = "3.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/c0/5e9c4d2a643a00a6f67578ef35485173de273a4567279e4f0c200c01386b/prettytable-3.9.0.tar.gz", hash = "sha256:f4ed94803c23073a90620b201965e5dc0bccf1760b7a7eaf3158cab8aaffdf34", size = 47874, upload-time = "2023-09-11T14:04:14.548Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/81/316b6a55a0d1f327d04cc7b0ba9d04058cb62de6c3a4d4b0df280cbe3b0b/prettytable-3.9.0-py3-none-any.whl", hash = "sha256:a71292ab7769a5de274b146b276ce938786f56c31cf7cea88b6f3775d82fe8c8", size = 27772, upload-time = "2023-09-11T14:03:45.582Z" }, +] + [[package]] name = "protobuf" version = "6.33.2" @@ -1769,7 +2039,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.13.4" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1777,125 +2047,120 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [[package]] name = "pydantic-core" -version = "2.46.4" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, - { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, - { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, - { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, - { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, - { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, - { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, - { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, - { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, - { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, - { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, - { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, - { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, - { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, - { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, - { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, - { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, - { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, - { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, - { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, - { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, - { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, - { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, - { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, - { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, - { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, - { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, - { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, - { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, - { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, - { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, - { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, - { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, - { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, - { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, - { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, - { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, - { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, - { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, - { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, - { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, - { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, - { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, - { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, - { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, - { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, - { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, - { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, - { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, - { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, - { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, - { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, - { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, - { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, - { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, - { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, - { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, - { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, - { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, - { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, - { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, - { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, - { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, - { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, - { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, - { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, - { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, - { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, - { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] [[package]] name = "pydantic-settings" -version = "2.14.1" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/60/1d1e59c9c90d54591469ada7d268251f71c24bdb765f1a8a832cee8c6653/pydantic_settings-2.14.1.tar.gz", hash = "sha256:e874d3bec7e787b0c9958277956ed9b4dd5de6a80e162188fdaff7c5e26fd5fa", size = 235551, upload-time = "2026-05-08T13:40:06.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/8d/f1af3832f5e6eb13ba94ee809e72b8ecb5eef226d27ee0bef7d963d943c7/pydantic_settings-2.14.1-py3-none-any.whl", hash = "sha256:6e3c7edfd8277687cdc598f56e5cff0e9bfff0910a3749deaa8d4401c3a2b9de", size = 60964, upload-time = "2026-05-08T13:40:04.958Z" }, + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] [[package]] @@ -1907,9 +2172,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/9c/372fef8377a6e340b1704768d20daaded98bf13282b5327beb2e2fe2c7ef/pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", size = 1179756, upload-time = "2023-11-21T20:43:49.423Z" }, ] +[[package]] +name = "pyparsing" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/fe/65c989f70bd630b589adfbbcd6ed238af22319e90f059946c26b4835e44b/pyparsing-3.1.1.tar.gz", hash = "sha256:ede28a1a32462f5a9705e07aea48001a08f7cf81a021585011deba701581a0db", size = 884814, upload-time = "2023-07-30T15:07:02.617Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/92/8486ede85fcc088f1b3dba4ce92dd29d126fd96b0008ea213167940a2475/pyparsing-3.1.1-py3-none-any.whl", hash = "sha256:32c7c0b711493c72ff18a981d24f28aaf9c1fb7ed5e9667c9e84e3db623bdbfb", size = 103139, upload-time = "2023-07-30T15:06:59.829Z" }, +] + [[package]] name = "pytest" -version = "9.0.3" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1918,36 +2192,36 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.4.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] name = "pytest-cov" -version = "7.1.0" +version = "7.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "coverage", extra = ["toml"] }, { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] [[package]] @@ -1962,6 +2236,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/c4/13b4776ea2d76c115c1d1b84579f3764ee6d57204f6be27119f13a61d0a9/python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", size = 357324, upload-time = "2021-07-14T08:19:19.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/7a/87837f39d0296e723bb9b62bbb257d0355c7f6128853c78955f57342a56d/python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9", size = 247702, upload-time = "2021-07-14T08:19:18.161Z" }, +] + [[package]] name = "python-dotenv" version = "1.0.0" @@ -1985,11 +2271,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.32" +version = "0.0.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, ] [[package]] @@ -2123,9 +2409,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] +[[package]] +name = "qudida" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "scikit-learn" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/2d/bab8babd9dc9a9e4df6eb115540cee4322c1a74078fb6f3b3ebc452a22b3/qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8", size = 3100, upload-time = "2021-08-09T16:47:55.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/a1/a5f4bebaa31d109003909809d88aeb0d4b201463a9ea29308d9e4f9e7655/qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6", size = 3478, upload-time = "2021-08-09T16:47:54.637Z" }, +] + [[package]] name = "rapidocr" -version = "3.8.1" +version = "3.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorlog" }, @@ -2141,7 +2442,7 @@ dependencies = [ { name = "tqdm" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/4a/fa521d947f0fc7bb304bf11bec4cb66266bd81494588b4cb48dc01001719/rapidocr-3.8.1-py3-none-any.whl", hash = "sha256:650044b1fbce9e6bae5cae462dcf8be754cde11e2f23fc51f65dcc08deae2c46", size = 15080319, upload-time = "2026-04-11T07:13:22.56Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/0d025466f0f84552634f2a94c018df34568fe55cc97184a6bb2c719c5b3a/rapidocr-3.6.0-py3-none-any.whl", hash = "sha256:d16b43872fc4dfa1e60996334dcd0dc3e3f1f64161e2332bc1873b9f65754e6b", size = 15067340, upload-time = "2026-01-28T14:45:04.271Z" }, ] [[package]] @@ -2161,15 +2462,15 @@ wheels = [ [[package]] name = "rich" -version = "15.0.0" +version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] @@ -2235,27 +2536,160 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.16" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/5f7ec371001337d8fa61701c186ff8b613ecac1651848c5950f4c4d5f2e9/ruff-0.15.16.tar.gz", hash = "sha256:d05e78d38c78caf020b03789e25106c93017db5a0cb6e2819885018c61343b78", size = 4714267, upload-time = "2026-06-04T16:33:09.974Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/42/53ef1c3953f157956db9bf7861e3bc50b9b887ce93300aa48cdba8336fe6/ruff-0.15.16-py3-none-linux_armv6l.whl", hash = "sha256:6ac3c0b3969cc6cf6b158c4e2f8f682acb58e7d700d8a44b65ecdc72d66ab0b2", size = 10709025, upload-time = "2026-06-04T16:32:51.935Z" }, - { url = "https://files.pythonhosted.org/packages/93/9a/a79159346f19134a956607754e57d8d128f7a4c00f4ad2f7514d224c172c/ruff-0.15.16-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:197c207ed75ffba54a0dec23db4aa939a27a3053073e085e0042433cbdc58e4a", size = 11063550, upload-time = "2026-06-04T16:32:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/bc/72/3ce2ac000a5299ec238e01f51397b3b653c93b077d9b1bfe8715bb895f20/ruff-0.15.16-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a39fec45ab316cc23e7558f23fea4a70403ddb5648ea9a4a3854a16973d0071", size = 10421345, upload-time = "2026-06-04T16:32:37.251Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c2/cc7fad3ec9169373f5b6a18f1917b91080feec40c3f9658334a1d28e2f03/ruff-0.15.16-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba93191d79003116b95128c9d306e045200fdbd0bccb782b110f3cd1d4abc5cf", size = 10757217, upload-time = "2026-06-04T16:32:54.722Z" }, - { url = "https://files.pythonhosted.org/packages/69/d2/3474009eaa0a65b31fa7152a2fad5e2f050c640ceb1e6b02ee6922e94c82/ruff-0.15.16-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6ee4b90520630120ef032aa5cc10db483852dff950e78b1d717e2993a61ac8d", size = 10507035, upload-time = "2026-06-04T16:33:05.343Z" }, - { url = "https://files.pythonhosted.org/packages/ca/81/b7ae6ccbd11f0c8dc3d5d67fc4be9b57ff57ca86ba56152021378e1277f2/ruff-0.15.16-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e4215bc938bc3c8215c1472c1aa437e310fee20cd427335fec9d7e609563628", size = 11255291, upload-time = "2026-06-04T16:32:49.49Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e1/46e526f1a7cc90857ce6ddf25fbb77eb6568651ac38d71b033af07076dd5/ruff-0.15.16-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c8d26be963b090f10e29abc8b3e74a2a321f6fa34e02424e30b5af89350ecbb", size = 12124922, upload-time = "2026-06-04T16:33:07.821Z" }, - { url = "https://files.pythonhosted.org/packages/1a/da/5c791b088b596b24d0deb967fa28ae02ad751a140c0b9ea81c5ab915d6c0/ruff-0.15.16-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f198cf4123602a2280ed46c307bcbafe41758d6fee5b456b6b6058ca1514b3b4", size = 11332186, upload-time = "2026-06-04T16:33:02.971Z" }, - { url = "https://files.pythonhosted.org/packages/72/11/5da87abe20047c8962361473923ebb2f62b595250126aadfad8c20649c1e/ruff-0.15.16-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb27515fa6240fb586ae82b901a59e67d24acff86f2190b433dc542fe0435aeb", size = 11373541, upload-time = "2026-06-04T16:32:47.007Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2a/8554754c23a854ae3fd6b507e36ad61ddb121e298c6d5d617dec94ed0f14/ruff-0.15.16-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a267c46ba1593fc26b8eecbea050b39d40c0b6bb7781ee11c90a02cd10032951", size = 11353014, upload-time = "2026-06-04T16:32:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/62/25/62ea41529ec89f742ea3fed9cb1059c72877ec7cf9b9e99ac9cf3294d1d9/ruff-0.15.16-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:528c68f39a91498a8d50e91ff5985df3d105782bab49cc378e73ac26bff083e8", size = 10737467, upload-time = "2026-06-04T16:32:26.348Z" }, - { url = "https://files.pythonhosted.org/packages/90/17/334d3ad9de4d40f9dd58fdd09e35ce64553bb501e2f19a839e2fb6be14fc/ruff-0.15.16-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7ed55c58950df60589a9a7a5d2f8fa5f54ebd287163be805adfe6ee95a9de123", size = 10521910, upload-time = "2026-06-04T16:32:32.54Z" }, - { url = "https://files.pythonhosted.org/packages/4d/bd/3ac7c6ae77a885c1004b3dda2446ea401768d24f851c14b4ad4b24f6639c/ruff-0.15.16-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d482feaf51512b50f9790ceb417a56a61dd1e9d9bf967662b9ed27c01b34f53a", size = 10979190, upload-time = "2026-06-04T16:32:57.492Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/609546e6a413c3f216fbf2a50c928f97c80939154f6a0503114094a86191/ruff-0.15.16-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1e15bc8c94513dae2a40cc9ef07c94fdd4ecc9e29dabebeebe170f952322c9e3", size = 11477014, upload-time = "2026-06-04T16:32:44.687Z" }, - { url = "https://files.pythonhosted.org/packages/74/0d/f2cd247ad32633a5c36e97141a2c21b11c6279f7957bc2ff360b1e08fddd/ruff-0.15.16-py3-none-win32.whl", hash = "sha256:580378f7bd4aa25f72e74aa54948a9622f142b1e509521dd10902e886681cc1e", size = 10735541, upload-time = "2026-06-04T16:32:30.145Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9e/02e845ef151b1dee585e55c4739f8e1734ae1d9f1221dff65761c162208b/ruff-0.15.16-py3-none-win_amd64.whl", hash = "sha256:408256017284eddf98fff77b29aa4fb30f586042d535b2d9befc6512f400aaec", size = 11843403, upload-time = "2026-06-04T16:32:39.76Z" }, - { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/84/5f4af978fff619706b8961accac84780a6d298d82a8873446f72edb4ead0/scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802", size = 7190445, upload-time = "2025-07-18T08:01:54.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/bd/a23177930abd81b96daffa30ef9c54ddbf544d3226b8788ce4c3ef1067b4/scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b", size = 9334838, upload-time = "2025-07-18T08:01:11.239Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a1/d3a7628630a711e2ac0d1a482910da174b629f44e7dd8cfcd6924a4ef81a/scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518", size = 8651241, upload-time = "2025-07-18T08:01:13.234Z" }, + { url = "https://files.pythonhosted.org/packages/26/92/85ec172418f39474c1cd0221d611345d4f433fc4ee2fc68e01f524ccc4e4/scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8", size = 9718677, upload-time = "2025-07-18T08:01:15.649Z" }, + { url = "https://files.pythonhosted.org/packages/df/ce/abdb1dcbb1d2b66168ec43b23ee0cee356b4cc4100ddee3943934ebf1480/scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7", size = 9511189, upload-time = "2025-07-18T08:01:18.013Z" }, + { url = "https://files.pythonhosted.org/packages/b2/3b/47b5eaee01ef2b5a80ba3f7f6ecf79587cb458690857d4777bfd77371c6f/scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650", size = 8914794, upload-time = "2025-07-18T08:01:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/57f176585b35ed865f51b04117947fe20f130f78940c6477b6d66279c9c2/scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087", size = 9260431, upload-time = "2025-07-18T08:01:22.77Z" }, + { url = "https://files.pythonhosted.org/packages/67/4e/899317092f5efcab0e9bc929e3391341cec8fb0e816c4789686770024580/scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f", size = 8637191, upload-time = "2025-07-18T08:01:24.731Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/998312db6d361ded1dd56b457ada371a8d8d77ca2195a7d18fd8a1736f21/scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87", size = 9486346, upload-time = "2025-07-18T08:01:26.713Z" }, + { url = "https://files.pythonhosted.org/packages/ad/09/a2aa0b4e644e5c4ede7006748f24e72863ba2ae71897fecfd832afea01b4/scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7", size = 9290988, upload-time = "2025-07-18T08:01:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/15/fa/c61a787e35f05f17fc10523f567677ec4eeee5f95aa4798dbbbcd9625617/scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88", size = 8735568, upload-time = "2025-07-18T08:01:30.936Z" }, + { url = "https://files.pythonhosted.org/packages/52/f8/e0533303f318a0f37b88300d21f79b6ac067188d4824f1047a37214ab718/scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae", size = 9213143, upload-time = "2025-07-18T08:01:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/71/f3/f1df377d1bdfc3e3e2adc9c119c238b182293e6740df4cbeac6de2cc3e23/scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10", size = 8591977, upload-time = "2025-07-18T08:01:34.967Z" }, + { url = "https://files.pythonhosted.org/packages/99/72/c86a4cd867816350fe8dee13f30222340b9cd6b96173955819a5561810c5/scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309", size = 9436142, upload-time = "2025-07-18T08:01:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/e8/66/277967b29bd297538dc7a6ecfb1a7dce751beabd0d7f7a2233be7a4f7832/scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43", size = 9282996, upload-time = "2025-07-18T08:01:39.721Z" }, + { url = "https://files.pythonhosted.org/packages/e2/47/9291cfa1db1dae9880420d1e07dbc7e8dd4a7cdbc42eaba22512e6bde958/scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11", size = 8707418, upload-time = "2025-07-18T08:01:42.124Z" }, + { url = "https://files.pythonhosted.org/packages/61/95/45726819beccdaa34d3362ea9b2ff9f2b5d3b8bf721bd632675870308ceb/scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae", size = 9561466, upload-time = "2025-07-18T08:01:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1c/6f4b3344805de783d20a51eb24d4c9ad4b11a7f75c1801e6ec6d777361fd/scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c", size = 9040467, upload-time = "2025-07-18T08:01:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/6f/80/abe18fe471af9f1d181904203d62697998b27d9b62124cd281d740ded2f9/scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e", size = 9532052, upload-time = "2025-07-18T08:01:48.676Z" }, + { url = "https://files.pythonhosted.org/packages/14/82/b21aa1e0c4cee7e74864d3a5a721ab8fcae5ca55033cb6263dca297ed35b/scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7", size = 9361575, upload-time = "2025-07-18T08:01:50.639Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/f4777fcd5627dc6695fa6b92179d0edb7a3ac1b91bcd9a1c7f64fa7ade23/scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5", size = 9277310, upload-time = "2025-07-18T08:01:52.547Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, + { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, + { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, + { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ec4864f5896232133f51382b54a08de91a9d1af7a76dfa372894026dfee2/scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c", size = 36575194, upload-time = "2025-07-27T16:27:41.321Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6d/40e81ecfb688e9d25d34a847dca361982a6addf8e31f0957b1a54fbfa994/scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04", size = 28594590, upload-time = "2025-07-27T16:27:49.204Z" }, + { url = "https://files.pythonhosted.org/packages/0e/37/9f65178edfcc629377ce9a64fc09baebea18c80a9e57ae09a52edf84880b/scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919", size = 20866458, upload-time = "2025-07-27T16:27:54.98Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7b/749a66766871ea4cb1d1ea10f27004db63023074c22abed51f22f09770e0/scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921", size = 23539318, upload-time = "2025-07-27T16:28:01.604Z" }, + { url = "https://files.pythonhosted.org/packages/c4/db/8d4afec60eb833a666434d4541a3151eedbf2494ea6d4d468cbe877f00cd/scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725", size = 33292899, upload-time = "2025-07-27T16:28:09.147Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/79023ca3bbb13a015d7d2757ecca3b81293c663694c35d6541b4dca53e98/scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618", size = 35162637, upload-time = "2025-07-27T16:28:17.535Z" }, + { url = "https://files.pythonhosted.org/packages/b6/49/0648665f9c29fdaca4c679182eb972935b3b4f5ace41d323c32352f29816/scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d", size = 35490507, upload-time = "2025-07-27T16:28:25.705Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/66cbb9d6bbb18d8c658f774904f42a92078707a7c71e5347e8bf2f52bb89/scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119", size = 37923998, upload-time = "2025-07-27T16:28:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/14/c3/61f273ae550fbf1667675701112e380881905e28448c080b23b5a181df7c/scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a", size = 38508060, upload-time = "2025-07-27T16:28:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/93/0b/b5c99382b839854a71ca9482c684e3472badc62620287cbbdab499b75ce6/scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f", size = 36533717, upload-time = "2025-07-27T16:28:51.706Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e5/69ab2771062c91e23e07c12e7d5033a6b9b80b0903ee709c3c36b3eb520c/scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb", size = 28570009, upload-time = "2025-07-27T16:28:57.017Z" }, + { url = "https://files.pythonhosted.org/packages/f4/69/bd75dbfdd3cf524f4d753484d723594aed62cfaac510123e91a6686d520b/scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c", size = 20841942, upload-time = "2025-07-27T16:29:01.152Z" }, + { url = "https://files.pythonhosted.org/packages/ea/74/add181c87663f178ba7d6144b370243a87af8476664d5435e57d599e6874/scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608", size = 23498507, upload-time = "2025-07-27T16:29:05.202Z" }, + { url = "https://files.pythonhosted.org/packages/1d/74/ece2e582a0d9550cee33e2e416cc96737dce423a994d12bbe59716f47ff1/scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f", size = 33286040, upload-time = "2025-07-27T16:29:10.201Z" }, + { url = "https://files.pythonhosted.org/packages/e4/82/08e4076df538fb56caa1d489588d880ec7c52d8273a606bb54d660528f7c/scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b", size = 35176096, upload-time = "2025-07-27T16:29:17.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/cd710aab8c921375711a8321c6be696e705a120e3011a643efbbcdeeabcc/scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45", size = 35490328, upload-time = "2025-07-27T16:29:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/e9cc3d35ee4526d784520d4494a3e1ca969b071fb5ae5910c036a375ceec/scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65", size = 37939921, upload-time = "2025-07-27T16:29:29.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/12/c0efd2941f01940119b5305c375ae5c0fcb7ec193f806bd8f158b73a1782/scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab", size = 38479462, upload-time = "2025-07-27T16:30:24.078Z" }, + { url = "https://files.pythonhosted.org/packages/7a/19/c3d08b675260046a991040e1ea5d65f91f40c7df1045fffff412dcfc6765/scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6", size = 36938832, upload-time = "2025-07-27T16:29:35.057Z" }, + { url = "https://files.pythonhosted.org/packages/81/f2/ce53db652c033a414a5b34598dba6b95f3d38153a2417c5a3883da429029/scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27", size = 29093084, upload-time = "2025-07-27T16:29:40.201Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ae/7a10ff04a7dc15f9057d05b33737ade244e4bd195caa3f7cc04d77b9e214/scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7", size = 21365098, upload-time = "2025-07-27T16:29:44.295Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/029ff710959932ad3c2a98721b20b405f05f752f07344622fd61a47c5197/scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6", size = 23896858, upload-time = "2025-07-27T16:29:48.784Z" }, + { url = "https://files.pythonhosted.org/packages/71/13/d1ef77b6bd7898720e1f0b6b3743cb945f6c3cafa7718eaac8841035ab60/scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4", size = 33438311, upload-time = "2025-07-27T16:29:54.164Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e0/e64a6821ffbb00b4c5b05169f1c1fddb4800e9307efe3db3788995a82a2c/scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3", size = 35279542, upload-time = "2025-07-27T16:30:00.249Z" }, + { url = "https://files.pythonhosted.org/packages/57/59/0dc3c8b43e118f1e4ee2b798dcc96ac21bb20014e5f1f7a8e85cc0653bdb/scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7", size = 35667665, upload-time = "2025-07-27T16:30:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/45/5f/844ee26e34e2f3f9f8febb9343748e72daeaec64fe0c70e9bf1ff84ec955/scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc", size = 38045210, upload-time = "2025-07-27T16:30:11.655Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d7/210f2b45290f444f1de64bc7353aa598ece9f0e90c384b4a156f9b1a5063/scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39", size = 38593661, upload-time = "2025-07-27T16:30:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/81/ea/84d481a5237ed223bd3d32d6e82d7a6a96e34756492666c260cef16011d1/scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318", size = 36525921, upload-time = "2025-07-27T16:30:30.081Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9f/d9edbdeff9f3a664807ae3aea383e10afaa247e8e6255e6d2aa4515e8863/scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc", size = 28564152, upload-time = "2025-07-27T16:30:35.336Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/8125bcb1fe04bc267d103e76516243e8d5e11229e6b306bda1024a5423d1/scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8", size = 20836028, upload-time = "2025-07-27T16:30:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/77/9c/bf92e215701fc70bbcd3d14d86337cf56a9b912a804b9c776a269524a9e9/scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e", size = 23489666, upload-time = "2025-07-27T16:30:43.663Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/5e941d397d9adac41b02839011594620d54d99488d1be5be755c00cde9ee/scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0", size = 33358318, upload-time = "2025-07-27T16:30:48.982Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/8db3aa10dde6e3e8e7eb0133f24baa011377d543f5b19c71469cf2648026/scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b", size = 35185724, upload-time = "2025-07-27T16:30:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/89/b4/6ab9ae443216807622bcff02690262d8184078ea467efee2f8c93288a3b1/scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731", size = 35554335, upload-time = "2025-07-27T16:30:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9a/d0e9dc03c5269a1afb60661118296a32ed5d2c24298af61b676c11e05e56/scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3", size = 37960310, upload-time = "2025-07-27T16:31:06.151Z" }, + { url = "https://files.pythonhosted.org/packages/5e/00/c8f3130a50521a7977874817ca89e0599b1b4ee8e938bad8ae798a0e1f0d/scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19", size = 39319239, upload-time = "2025-07-27T16:31:59.942Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f2/1ca3eda54c3a7e4c92f6acef7db7b3a057deb135540d23aa6343ef8ad333/scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65", size = 36939460, upload-time = "2025-07-27T16:31:11.865Z" }, + { url = "https://files.pythonhosted.org/packages/80/30/98c2840b293a132400c0940bb9e140171dcb8189588619048f42b2ce7b4f/scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2", size = 29093322, upload-time = "2025-07-27T16:31:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e6/1e6e006e850622cf2a039b62d1a6ddc4497d4851e58b68008526f04a9a00/scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d", size = 21365329, upload-time = "2025-07-27T16:31:21.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/72a5aa5b820589dda9a25e329ca752842bfbbaf635e36bc7065a9b42216e/scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695", size = 23897544, upload-time = "2025-07-27T16:31:25.408Z" }, + { url = "https://files.pythonhosted.org/packages/2b/dc/7122d806a6f9eb8a33532982234bed91f90272e990f414f2830cfe656e0b/scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86", size = 33442112, upload-time = "2025-07-27T16:31:30.62Z" }, + { url = "https://files.pythonhosted.org/packages/24/39/e383af23564daa1021a5b3afbe0d8d6a68ec639b943661841f44ac92de85/scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff", size = 35286594, upload-time = "2025-07-27T16:31:36.112Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/1a0b0aff40c3056d955f38b0df5d178350c3d74734ec54f9c68d23910be5/scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4", size = 35665080, upload-time = "2025-07-27T16:31:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/ce88803e9ed6e27fe9b9abefa157cf2c80e4fa527cf17ee14be41f790ad4/scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3", size = 38050306, upload-time = "2025-07-27T16:31:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6c/a76329897a7cae4937d403e623aa6aaea616a0bb5b36588f0b9d1c9a3739/scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998", size = 39427705, upload-time = "2025-07-27T16:31:53.96Z" }, ] [[package]] @@ -2310,15 +2744,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ea/f1/5e9b3ba5c7aa7ebfaf269657e728067d16a7c99401c7973ddf5f0cf121bd/shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7", size = 1723061, upload-time = "2025-05-19T11:04:40.082Z" }, ] -[[package]] -name = "shellingham" -version = "1.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, -] - [[package]] name = "simple-websocket" version = "1.1.0" @@ -2374,31 +2799,51 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/05/e6600db80270777c4a64238a98d442f0fd07cc8915be2a1c16da7f2b9e74/sympy-1.12-py3-none-any.whl", hash = "sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5", size = 5742435, upload-time = "2023-05-10T18:22:14.76Z" }, ] +[[package]] +name = "threadpoolctl" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/8a/c05f7831beb32aff70f808766224f11c650f7edfd49b27a8fc6666107006/threadpoolctl-3.2.0.tar.gz", hash = "sha256:c96a0ba3bdddeaca37dc4cc7344aafad41cdb8c313f74fdfe387a867bba93355", size = 36266, upload-time = "2023-07-13T14:53:53.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/12/fd4dea011af9d69e1cad05c75f3f7202cdcbeac9b712eea58ca779a72865/threadpoolctl-3.2.0-py3-none-any.whl", hash = "sha256:2b7818516e423bdaebb97c723f86a7c6b0a83d3f3b0970328d66f4d9104dc032", size = 15539, upload-time = "2023-07-13T14:53:39.336Z" }, +] + +[[package]] +name = "tifffile" +version = "2023.12.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/a4/6c0eadea1ccfcda27e6cce400c366098b5b082138a073f4252fe399f4148/tifffile-2023.12.9.tar.gz", hash = "sha256:9dd1da91180a6453018a241ff219e1905f169384355cd89c9ef4034c1b46cdb8", size = 353467, upload-time = "2023-12-09T20:46:29.203Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/a4/569fc717831969cf48bced350bdaf070cdeab06918d179429899e144358d/tifffile-2023.12.9-py3-none-any.whl", hash = "sha256:9b066e4b1a900891ea42ffd33dab8ba34c537935618b9893ddef42d7d422692f", size = 223627, upload-time = "2023-12-09T20:46:26.569Z" }, +] + [[package]] name = "tokenizers" -version = "0.23.1" +version = "0.22.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, - { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, - { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, - { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, - { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, - { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, - { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, - { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, - { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, - { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, - { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, ] [[package]] @@ -2462,58 +2907,43 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/ad/7d47bbf2cae78ff79f29db0bed5016ec9c56b212a93fca624bb88b551a7c/tqdm-4.66.3-py3-none-any.whl", hash = "sha256:4f41d54107ff9a223dca80b53efe4fb654c67efaba7f47bada3ee9d50e05bd53", size = 78374, upload-time = "2024-05-02T21:44:01.541Z" }, ] -[[package]] -name = "typer" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-doc" }, - { name = "click" }, - { name = "rich" }, - { name = "shellingham" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" }, -] - [[package]] name = "types-pyyaml" -version = "6.0.12.20260518" +version = "6.0.12.20250915" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/83/4a1afc3fbfcf5b8d46fc390cd95ed6b0dc9010a265f4e9f46314efffa37a/types_pyyaml-6.0.12.20260518.tar.gz", hash = "sha256:d917f83fb38462550338c1297faedd860b3ec83912b96b1e3d73255f7473e466", size = 17850, upload-time = "2026-05-18T06:01:58.675Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/a2/c01db32be2ae7d6a1689972f3c492b149ee4e164b12fdfd9f64b50888215/types_pyyaml-6.0.12.20260518-py3-none-any.whl", hash = "sha256:d2150f75a231c9fe9c7463bd29487d93e60bac90400287351384bc2284eba7cd", size = 20312, upload-time = "2026-05-18T06:01:57.368Z" }, + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, ] [[package]] name = "types-requests" -version = "2.33.0.20260518" +version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, ] [[package]] name = "types-setuptools" -version = "82.0.0.20260518" +version = "82.0.0.20260210" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/bc/73c2c27e047e42f114ac50fb3bdef986c56cbdb68096f8690eeafb839a93/types_setuptools-82.0.0.20260518.tar.gz", hash = "sha256:3b743cfe63d0981ea4c15b90710fc1ed41e3464a537d51e705be514e891c1d07", size = 44999, upload-time = "2026-05-18T06:02:55.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/90/796ac8c774a7f535084aacbaa6b7053d16fff5c630eff87c3ecff7896c37/types_setuptools-82.0.0.20260210.tar.gz", hash = "sha256:d9719fbbeb185254480ade1f25327c4654f8c00efda3fec36823379cebcdee58", size = 44768, upload-time = "2026-02-10T04:22:02.107Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/8f/d5e2d493f09a7a98c95619edda1cb37cee377626c0a869d53274c26f2858/types_setuptools-82.0.0.20260518-py3-none-any.whl", hash = "sha256:31c04a62b57a653a5021caf191be0f10f70df890f813b51f02bab3969d300f20", size = 68444, upload-time = "2026-05-18T06:02:54.582Z" }, + { url = "https://files.pythonhosted.org/packages/3e/54/3489432b1d9bc713c9d8aa810296b8f5b0088403662959fb63a8acdbd4fc/types_setuptools-82.0.0.20260210-py3-none-any.whl", hash = "sha256:5124a7daf67f195c6054e0f00f1d97c69caad12fdcf9113eba33eff0bce8cd2b", size = 68433, upload-time = "2026-02-10T04:22:00.876Z" }, ] [[package]] name = "types-simplejson" -version = "3.20.0.20260518" +version = "3.20.0.20250822" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/e4/f8ecdc3529688ad058b0496f67a1bdfbd2576c3669e1c478b6347b12cb7e/types_simplejson-3.20.0.20260518.tar.gz", hash = "sha256:298c5b2fc2df3eb128d2077abd5bd1f5b0419dcf72fdf0551b2af2f03b1ff2b3", size = 10777, upload-time = "2026-05-18T06:04:39.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/6b/96d43a90cd202bd552cdd871858a11c138fe5ef11aeb4ed8e8dc51389257/types_simplejson-3.20.0.20250822.tar.gz", hash = "sha256:2b0bfd57a6beed3b932fd2c3c7f8e2f48a7df3978c9bba43023a32b3741a95b0", size = 10608, upload-time = "2025-08-22T03:03:35.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/41/16937326596bec6283a51cc901c701a964019569b861ac5f88128bc98b43/types_simplejson-3.20.0.20260518-py3-none-any.whl", hash = "sha256:f4792dbc1ab049fcaee5d99ca950bd6468ea462fe79af25fe8ca08b6bee313ac", size = 10421, upload-time = "2026-05-18T06:04:38.706Z" }, + { url = "https://files.pythonhosted.org/packages/3c/9f/8e2c9e6aee9a2ff34f2ffce6ccd9c26edeef6dfd366fde611dc2e2c00ab9/types_simplejson-3.20.0.20250822-py3-none-any.whl", hash = "sha256:b5e63ae220ac7a1b0bb9af43b9cb8652237c947981b2708b0c776d3b5d8fa169", size = 10417, upload-time = "2025-08-22T03:03:34.485Z" }, ] [[package]] @@ -2557,15 +2987,15 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.49.0" +version = "0.40.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, ] [package.optional-dependencies] @@ -2654,6 +3084,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/a5/7aba9435beb863c2490bae3173a45f42044ac7a48155d3dd42ab49cfae45/watchfiles-0.21.0-cp312-none-win_arm64.whl", hash = "sha256:a3b9bec9579a15fb3ca2d9878deae789df72f2b0fdaf90ad49ee389cad5edab6", size = 268026, upload-time = "2023-10-13T13:05:38.591Z" }, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + [[package]] name = "websocket-client" version = "1.8.0" diff --git a/misc/release/archive-version.js b/misc/release/archive-version.js new file mode 100755 index 0000000000..5c0ed9f22f --- /dev/null +++ b/misc/release/archive-version.js @@ -0,0 +1,45 @@ +#! /usr/bin/env node +const { readFileSync, writeFileSync } = require('node:fs'); + +const asVersion = (item) => { + const { label, url } = item; + const [major, minor, patch] = label.substring(1).split('.').map(Number); + return { major, minor, patch, label, url }; +}; + +const nextVersion = process.argv[2]; +if (!nextVersion) { + console.log('Usage: archive-version.js '); + process.exit(1); +} + +const filename = './docs/static/archived-versions.json'; +let versions = JSON.parse(readFileSync(filename)); +const newVersion = { + label: `v${nextVersion}`, + url: `https://docs.v${nextVersion}.archive.immich.app`, +}; + +let lastVersion = asVersion(newVersion); +for (const item of versions) { + const version = asVersion(item); + // only keep the latest patch version for each minor release + if ( + lastVersion.major === version.major && + lastVersion.minor === version.minor && + lastVersion.patch >= version.patch + ) { + versions = versions.filter((item) => item.label !== version.label); + console.log( + `Removed ${version.label} (replaced with ${lastVersion.label})` + ); + continue; + } + + lastVersion = version; +} + +writeFileSync( + filename, + JSON.stringify([newVersion, ...versions], null, 2) + '\n' +); diff --git a/misc/release/pump-version.sh b/misc/release/pump-version.sh new file mode 100755 index 0000000000..6be0ddebb9 --- /dev/null +++ b/misc/release/pump-version.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +# +# Pump one or both of the server/mobile versions in appropriate files +# +# usage: './scripts/pump-version.sh -s <-m> +# +# examples: +# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50 +# ./scripts/pump-version.sh -s minor -m true # 1.0.0+50 => 1.1.0+51 +# ./scripts/pump-version.sh -m true # 1.0.0+50 => 1.0.0+51 +# + +SERVER_PUMP="false" +MOBILE_PUMP="false" + +while getopts 's:m:' flag; do + case "${flag}" in + s) SERVER_PUMP=${OPTARG} ;; + m) MOBILE_PUMP=${OPTARG} ;; + *) + echo "Invalid args" + exit 1 + ;; + esac +done + +CURRENT_SERVER=$(jq -r '.version' server/package.json) +MAJOR=$(echo "$CURRENT_SERVER" | cut -d '.' -f1) +MINOR=$(echo "$CURRENT_SERVER" | cut -d '.' -f2) +PATCH=$(echo "$CURRENT_SERVER" | cut -d '.' -f3) + +if [[ $SERVER_PUMP == "major" ]]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 +elif [[ $SERVER_PUMP == "minor" ]]; then + MINOR=$((MINOR + 1)) + PATCH=0 +elif [[ $SERVER_PUMP == "patch" ]]; then + PATCH=$((PATCH + 1)) +elif [[ $SERVER_PUMP == "false" ]]; then + echo 'Skipping Server Pump' +else + echo 'Expected for the server argument' + exit 1 +fi + +NEXT_SERVER=$MAJOR.$MINOR.$PATCH + +CURRENT_MOBILE=$(grep "^version: .*+[0-9]\+$" mobile/pubspec.yaml | cut -d "+" -f2) +NEXT_MOBILE=$CURRENT_MOBILE +if [[ $MOBILE_PUMP == "true" ]]; then + set $((NEXT_MOBILE++)) +elif [[ $MOBILE_PUMP == "false" ]]; then + echo 'Skipping Mobile Pump' +else + echo "Fatal: MOBILE_PUMP value $MOBILE_PUMP is invalid" + exit 1 +fi + +if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then + echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER" + + pnpm version "$NEXT_SERVER" --no-git-tag-version + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix server + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix i18n + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix cli + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix web + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix e2e + pnpm version "$NEXT_SERVER" --no-git-tag-version --prefix open-api/typescript-sdk + + # copy version to open-api spec + pnpm install --frozen-lockfile --prefix server + pnpm --prefix server run build + ( cd ./open-api && bash ./bin/generate-open-api.sh ) + + uv version --directory machine-learning "$NEXT_SERVER" + + ./misc/release/archive-version.js "$NEXT_SERVER" +fi + +if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then + echo "Pumping Mobile: $CURRENT_MOBILE => $NEXT_MOBILE" +fi + +sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile +sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.injected\.version\.code\" => $NEXT_MOBILE,/" mobile/android/fastlane/Fastfile +sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml +perl -i -p0e "s/(CFBundleShortVersionString<\/key>\s*)$CURRENT_SERVER(<\/string>)/\${1}$NEXT_SERVER\${2}/s" mobile/ios/Runner/Info.plist + + +echo "IMMICH_VERSION=v$NEXT_SERVER" >>"$GITHUB_ENV" diff --git a/mise.lock b/mise.lock deleted file mode 100644 index 137b1f1c06..0000000000 --- a/mise.lock +++ /dev/null @@ -1,370 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html - -[[tools."github:extism/cli"]] -version = "1.6.3" -backend = "github:extism/cli" - -[tools."github:extism/cli"."platforms.linux-arm64"] -checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" - -[tools."github:extism/cli"."platforms.linux-arm64-musl"] -checksum = "sha256:d92f830c9be39637569feacb04e9750c28848df6d9a219db94152a9b4eb9452b" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-arm64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694030" - -[tools."github:extism/cli"."platforms.linux-x64"] -checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" - -[tools."github:extism/cli"."platforms.linux-x64-musl"] -checksum = "sha256:34e7ae9bfded6e2c32dee83f70a4e50d34f9d3e80d1762b09625fe82e214d02d" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-linux-amd64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694025" - -[tools."github:extism/cli"."platforms.macos-arm64"] -checksum = "sha256:b4ddbc575b5ac000115247f781723f9b9f284ed87b29c600539d72161b5b29fc" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-arm64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694029" - -[tools."github:extism/cli"."platforms.macos-x64"] -checksum = "sha256:9a2f71b6e6009685a622cc3084e52d2a1a8e23c98d29ffa72e666e9dc699855f" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-darwin-amd64.tar.gz" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694026" - -[tools."github:extism/cli"."platforms.windows-x64"] -checksum = "sha256:47e4ed2782445b2b08a4d1ac127211588f8b4d1fc25fd6481d4cb65151b5213c" -url = "https://github.com/extism/cli/releases/download/v1.6.3/extism-v1.6.3-windows-amd64.zip" -url_api = "https://api.github.com/repos/extism/cli/releases/assets/275694035" - -[[tools."github:extism/js-pdk"]] -version = "1.6.0" -backend = "github:extism/js-pdk" - -[tools."github:extism/js-pdk"."platforms.linux-arm64"] -checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" - -[tools."github:extism/js-pdk"."platforms.linux-arm64-musl"] -checksum = "sha256:15a186250e68d6bff4ec839fff275d45a90e383a69209dcc1239eb9e3aee6e1b" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-linux-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223214" - -[tools."github:extism/js-pdk"."platforms.linux-x64"] -checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" - -[tools."github:extism/js-pdk"."platforms.linux-x64-musl"] -checksum = "sha256:4ded271ccf465031ccd0dc35e7a140e134d7f30721671cc4a8e1ff805d4aad68" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-linux-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223119" - -[tools."github:extism/js-pdk"."platforms.macos-arm64"] -checksum = "sha256:548e25bda3971a07c32d78a249135cf8cb7b3eede101e878e06e53e01ac2e0ce" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-aarch64-macos-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223215" - -[tools."github:extism/js-pdk"."platforms.macos-x64"] -checksum = "sha256:d85a875c2a071f0c29fe572764c52c3a499f157ab7f9efac8939a4364390e29b" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-macos-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353223239" - -[tools."github:extism/js-pdk"."platforms.windows-x64"] -checksum = "sha256:97b7b746141e4777e1ca2b76febdeb16dc9d314ff6a4257df05a476b67228acc" -url = "https://github.com/extism/js-pdk/releases/download/v1.6.0/extism-js-x86_64-windows-v1.6.0.gz" -url_api = "https://api.github.com/repos/extism/js-pdk/releases/assets/353224133" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.windows-x64"] -checksum = "sha256:7b7168149689610296f3a187c717056ce0786cc125a31caf28056737e9ba1cc1" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_win64-clang-gpl.zip" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409036094" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - -[tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-arm64"] -checksum = "sha256:e024d5e78d5414e75f0181036cd21373fafb9270c72894dfd7dbda2572439820" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_macarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995838" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - -[tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64"] -checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-x64-musl"] -checksum = "sha256:39e99a7927468a6abec5f65d00f55010e8ff2ae3c2605294f179c94f6ae21af2" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linux64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048879" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - -[tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.macos-x64"] -checksum = "sha256:066ede9774aaae97a18098aaeea8b7e0d286653eb8618f640476e99c59a536c2" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_mac64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/408995889" - -[[tools."github:jellyfin/jellyfin-ffmpeg"]] -version = "7.1.3-6" -backend = "github:jellyfin/jellyfin-ffmpeg" - -[tools."github:jellyfin/jellyfin-ffmpeg".options] -asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64"] -checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" - -[tools."github:jellyfin/jellyfin-ffmpeg"."platforms.linux-arm64-musl"] -checksum = "sha256:bea03c670e8cc5bfe9edc0c5d624d4735421610cef5e808db93e7d8596952886" -url = "https://github.com/jellyfin/jellyfin-ffmpeg/releases/download/v7.1.3-6/jellyfin-ffmpeg_7.1.3-6_portable_linuxarm64-gpl.tar.xz" -url_api = "https://api.github.com/repos/jellyfin/jellyfin-ffmpeg/releases/assets/409048876" - -[[tools."github:webassembly/binaryen"]] -version = "version_124" -backend = "github:webassembly/binaryen" - -[tools."github:webassembly/binaryen"."platforms.linux-arm64"] -checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" - -[tools."github:webassembly/binaryen"."platforms.linux-arm64-musl"] -checksum = "sha256:6291bd9a57d8e046f3bc099a4db386c147433a87f71c783a901c5b1792e38de3" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-aarch64-linux.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288927659" - -[tools."github:webassembly/binaryen"."platforms.linux-x64"] -checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" - -[tools."github:webassembly/binaryen"."platforms.linux-x64-musl"] -checksum = "sha256:0290c3779fedf592b8da0ded3032ff55c41a2b7bfa2d6bf7b7bac6f0e6e28963" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-linux.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926769" - -[tools."github:webassembly/binaryen"."platforms.macos-arm64"] -checksum = "sha256:86a2c960ff62c6d2ea6009d1f89745c22c70100d394a095eab45eb941bdaa24c" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-arm64-macos.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926134" - -[tools."github:webassembly/binaryen"."platforms.macos-x64"] -checksum = "sha256:b389bb0731758d86c3cb266d01d28a12725c23bd3cabc3df34faa162af0887e9" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-macos.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288926135" - -[tools."github:webassembly/binaryen"."platforms.windows-x64"] -checksum = "sha256:b5e1d2a1ad3c03229ddc89823848f4a1c11f9c6402a51fa26f0aaa5f1d7a2203" -url = "https://github.com/WebAssembly/binaryen/releases/download/version_124/binaryen-version_124-x86_64-windows.tar.gz" -url_api = "https://api.github.com/repos/WebAssembly/binaryen/releases/assets/288925833" - -[[tools.java]] -version = "21.0.2" -backend = "core:java" - -[tools.java.options] -shorthand_vendor = "openjdk" - -[tools.java."platforms.linux-arm64"] -checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" - -[tools.java."platforms.linux-x64"] -checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" - -[tools.java."platforms.macos-arm64"] -checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" - -[tools.java."platforms.macos-x64"] -checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" - -[tools.java."platforms.windows-x64"] -checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" - -[[tools.node]] -version = "24.15.0" -backend = "core:node" - -[tools.node."platforms.linux-arm64"] -checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz" - -[tools.node."platforms.linux-arm64-musl"] -checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a" -url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz" - -[tools.node."platforms.linux-x64"] -checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz" - -[tools.node."platforms.linux-x64-musl"] -checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3" -url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz" - -[tools.node."platforms.macos-arm64"] -checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz" - -[tools.node."platforms.macos-x64"] -checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz" - -[tools.node."platforms.windows-x64"] -checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62" -url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip" - -[[tools."npm:@openapitools/openapi-generator-cli"]] -version = "2.40.1" -backend = "npm:@openapitools/openapi-generator-cli" - -[[tools."npm:oazapfts"]] -version = "7.5.0" -backend = "npm:oazapfts" - -[[tools.opentofu]] -version = "1.12.5" -backend = "aqua:opentofu/opentofu" - -[tools.opentofu."platforms.linux-arm64"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545" - -[tools.opentofu."platforms.linux-arm64-musl"] -checksum = "sha256:e67e9da2b1ddf5050ebee62a584cb826eafe1dfd3827d7ec20899ac62791ed1a" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602545" - -[tools.opentofu."platforms.linux-x64"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624" - -[tools.opentofu."platforms.linux-x64-musl"] -checksum = "sha256:a6894d45ae7a17ce83189cce8fe04b5a65f68cefceb62455b5a6a89fa53ab38f" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602624" - -[tools.opentofu."platforms.macos-arm64"] -checksum = "sha256:2ae38150a667f5c0bd57b318d18ad8091d08f93fcca40345f3d88998661de5a9" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602544" - -[tools.opentofu."platforms.macos-x64"] -checksum = "sha256:1012d8f3d4567bcbcd1f2c7d766feca39a30bced32fb8be47e1887fbbee2456d" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602621" - -[tools.opentofu."platforms.windows-x64"] -checksum = "sha256:af11850b496f3720e0184084c56d8b43aa74ea92d2338978bf368d70c96473f1" -url = "https://github.com/opentofu/opentofu/releases/download/v1.12.5/tofu_1.12.5_windows_amd64.tar.gz" -url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/484602547" - -[[tools.pnpm]] -version = "11.21.0" -backend = "aqua:pnpm/pnpm" - -[tools.pnpm."platforms.linux-arm64"] -checksum = "sha256:64eb219b008f7a4c176d81fbce919c20b0b7e093e815cbb669d46e681451f43b" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-linux-arm64.tar.gz" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585528" -provenance = "github-attestations" - -[tools.pnpm."platforms.linux-arm64-musl"] -checksum = "sha256:43587a1f3d26ee009c640378ef377cac3531990579acf0f35646531b7832831d" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-linux-arm64-musl.tar.gz" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585511" -provenance = "github-attestations" - -[tools.pnpm."platforms.linux-x64"] -checksum = "sha256:aadc489ce4473c2af0fec06a5c19e113b5793d404eac49853c8153bf4b0d8263" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-linux-x64.tar.gz" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585503" -provenance = "github-attestations" - -[tools.pnpm."platforms.linux-x64-musl"] -checksum = "sha256:8cad0a4d20318c0445d992630ace51edc0853fd259573f50ee5d0216dce9b420" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-linux-x64-musl.tar.gz" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585524" -provenance = "github-attestations" - -[tools.pnpm."platforms.macos-arm64"] -checksum = "sha256:860a96978a79ca5132bdc247375f94150a3c98edbecbee36e9f8e0c9e2f7f056" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-darwin-arm64.tar.gz" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585526" -provenance = "github-attestations" - -[tools.pnpm."platforms.windows-x64"] -checksum = "sha256:6643043caa3b01c65c431039926a2af954326d5cefbdb4da7baceceb17f5051f" -url = "https://github.com/pnpm/pnpm/releases/download/v11.21.0/pnpm-win32-x64.zip" -url_api = "https://api.github.com/repos/pnpm/pnpm/releases/assets/507585516" -provenance = "github-attestations" - -[[tools.terragrunt]] -version = "1.1.1" -backend = "aqua:gruntwork-io/terragrunt" - -[tools.terragrunt."platforms.linux-arm64"] -checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148" - -[tools.terragrunt."platforms.linux-arm64-musl"] -checksum = "sha256:a374a7993ff3d99665a7e014007d3647ec7f0465c9d55c85e9f94c932e73cea2" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786148" - -[tools.terragrunt."platforms.linux-x64"] -checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968" - -[tools.terragrunt."platforms.linux-x64-musl"] -checksum = "sha256:ce90077ac31ef17a2ba10d11d45f36c6501997a8f4f79d703bb7daba37032f53" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_linux_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785968" - -[tools.terragrunt."platforms.macos-arm64"] -checksum = "sha256:9ec8f678b9ae6c81d5d9d77b94cbf6349ce639d5938694b4adc9dea73e416794" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_arm64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785628" - -[tools.terragrunt."platforms.macos-x64"] -checksum = "sha256:73e768a69fa44a60f9d9174f54aaf179327e08706f633c9c79d5f5c9622c91c2" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_darwin_amd64.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476785520" - -[tools.terragrunt."platforms.windows-x64"] -checksum = "sha256:dd50a324691e072a3ac879e9ec43d6310e2936c929fda658eb2811fefcc75115" -url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.1.1/terragrunt_windows_amd64.exe.tar.gz" -url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/476786421" diff --git a/mise.toml b/mise.toml index 3ff8c22035..ab44ea8652 100644 --- a/mise.toml +++ b/mise.toml @@ -1,180 +1,49 @@ -monorepo_root = true +experimental_monorepo_root = true [monorepo] config_roots = [ - "packages/plugin-core", + "plugins", "server", - "packages/cli", + "cli", "deployment", "mobile", "e2e", "web", "docs", ".github", - "machine-learning", ] [tools] -node = "24.15.0" -pnpm = "11.21.0" -terragrunt = "1.1.1" -opentofu = "1.12.5" -"npm:@openapitools/openapi-generator-cli" = "2.40.1" -"npm:oazapfts" = "7.5.0" -"github:extism/cli" = "1.6.3" -"github:webassembly/binaryen" = "version_124" -"github:extism/js-pdk" = "1.6.0" +node = "24.14.1" +flutter = "3.35.7" +pnpm = "10.32.1" +terragrunt = "0.99.5" +opentofu = "1.11.5" java = "21.0.2" -[tools."github:jellyfin/jellyfin-ffmpeg"] -version = "7.1.3-6" - -[tools."github:jellyfin/jellyfin-ffmpeg".platforms] -linux-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" } -linux-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" } -macos-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" } -macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" } +[tools."github:CQLabs/homebrew-dcm"] +version = "1.35.1" +bin = "dcm" +postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" [settings] +experimental = true pin = true -lockfile = true - -[tasks.release] -run = [ - "pnpm --filter @immich/scripts install --frozen-lockfile", - "pnpm --filter @immich/scripts build", - "pnpm --filter @immich/scripts cli release {{arg(name='args', var=true)}}", - { task = "//:open-api" }, -] - -[tasks.plugins] -run = [ - "pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core install --frozen-lockfile", - "pnpm --filter @immich/sdk --filter @immich/plugin-sdk --filter @immich/plugin-core build", -] - -[tasks.open-api-typescript] -run = [ - "oazapfts --optimistic --argumentStyle=object --useEnumType --allSchemas open-api/immich-openapi-specs.json packages/sdk/src/fetch-client.ts", - { task = "//:sdk:install" }, - { task = "//:sdk:build" }, -] - -[tasks.open-api-dart] -dir = "open-api" -sources = [ - "immich-openapi-specs.json", - "openapitools.json", - "templates/**/*", - "patch/*", - "bin/generate-dart-sdk.sh", -] -outputs = ["../mobile/generated/openapi/"] -run = "bash ./bin/generate-dart-sdk.sh" - -[tasks.open-api] -run = [ - { task = "//:plugins" }, - { task = "//server:install" }, - { task = "//server:build" }, - { task = "//server:sync-open-api" }, - { task = ":open-api-typescript" }, - { task = ":open-api-dart" }, -] - -[tasks.sql] -dir = "server" -run = "node ./dist/bin/sync-sql.js" - -# TODO dev, prod, and e2e should be de-duplicated by using env but for some reason I ran into issues -[tasks.dev] -depends = "//:plugins" -dir = "docker" -interactive = true -env = { COMPOSE_BAKE = true } -run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans" -depends_post = "//:dev-down" - -[tasks.dev-update] -run = { task = "//:dev", args = ["--build", "-V"] } - -[tasks.dev-scale] -run = { task = "//:dev", args = ["--build", "-V", "--scale immich-server=3"] } - -[tasks.dev-down] -dir = "docker" -run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans" - -[tasks.prod] -depends = "//:plugins" -dir = "docker" -interactive = true -env = { COMPOSE_BAKE = true } -run = "docker compose -f ./docker-compose.prod.yml up --build --remove-orphans" -depends_post = "//:prod-down" - -[tasks.prod-scale] -run = { task = "//:prod", args = [ - "--build", - "-V", - "--scale immich-server=3", - "--scale immich-microservices", -] } - -[tasks.prod-down] -dir = "docker" -run = "docker compose -f ./docker-compose.prod.yml down --remove-orphans" - -[tasks.e2e] -depends = "//:plugins" -dir = "e2e" -interactive = true -env = { COMPOSE_BAKE = true } -run = "docker compose -f ./docker-compose.yml up --remove-orphans" -depends_post = "//:e2e-down" - -[tasks.e2e-dev] -depends = "//:plugins" -dir = "e2e" -interactive = true -env = { COMPOSE_BAKE = true } -run = "docker compose -f ./docker-compose.dev.yml up --remove-orphans" -depends_post = "//:e2e-dev-down" - -[tasks.e2e-update] -run = { task = "//:e2e", args = ["--build", '-V'] } - -[tasks.e2e-down] -dir = "e2e" -run = "docker compose -f ./docker-compose.yml down --remove-orphans" - -[tasks.e2e-dev-down] -dir = "e2e" -run = "docker compose -f ./docker-compose.dev.yml down --remove-orphans" # SDK tasks [tasks."sdk:install"] -dir = "packages/sdk" -run = "pnpm --filter @immich/sdk install --frozen-lockfile" +dir = "open-api/typescript-sdk" +run = "pnpm install --filter @immich/sdk --frozen-lockfile" [tasks."sdk:build"] -dir = "packages/sdk" -run = "pnpm build" +dir = "open-api/typescript-sdk" +run = "pnpm run build" # i18n tasks [tasks."i18n:format"] -run = "pnpm format" +dir = "i18n" +run = "pnpm run format" [tasks."i18n:format-fix"] -run = "pnpm format:fix" - -[tasks.clean] -run = [ - "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", - "find . -name 'dist' -type d -prune -exec rm -rf '{}' +", - "find . -name 'build' -type d -prune -exec rm -rf '{}' +", - "find . -name '.svelte-kit' -type d -prune -exec rm -rf '{}' +", - "find . -name 'coverage' -type d -prune -exec rm -rf '{}' +", - "find . -name '.pnpm-store' -type d -prune -exec rm -rf '{}' +", - { task = "//:*-down" }, -] +dir = "i18n" +run = "pnpm run format:fix" diff --git a/mobile/.gitignore b/mobile/.gitignore index 3cc72276ab..04eb74fddd 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -31,16 +31,6 @@ .pub-cache/ .pub/ /build/ -lib/**/*.drift.dart -lib/**/*.steps.dart -lib/**/*.freezed.dart -lib/routing/router.gr.dart -test/drift/main/generated/ - -# Pigeon related -/lib/platform/*.g.dart -/ios/**/*.g.swift -/android/**/*.g.kt # Web related lib/generated_plugin_registrant.dart diff --git a/mobile/.isar b/mobile/.isar new file mode 160000 index 0000000000..6643d064ab --- /dev/null +++ b/mobile/.isar @@ -0,0 +1 @@ +Subproject commit 6643d064abf22606b6c6a741ea873e4781115ef4 diff --git a/mobile/.isar-cargo.lock b/mobile/.isar-cargo.lock new file mode 100644 index 0000000000..a7b1dd37b9 --- /dev/null +++ b/mobile/.isar-cargo.lock @@ -0,0 +1,859 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" + +[[package]] +name = "cc" +version = "1.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "float_next_after" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc612c5837986b7104a87a0df74a5460931f1c5274be12f8d0f40aa2f30d632" +dependencies = [ + "num-traits", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "intmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee87fd093563344074bacf24faa0bb0227fb6969fb223e922db798516de924d6" + +[[package]] +name = "isar" +version = "0.0.0" +dependencies = [ + "dirs", + "intmap", + "isar-core", + "itertools", + "jni", + "ndk-context", + "objc", + "objc-foundation", + "once_cell", + "paste", + "serde_json", + "threadpool", + "unicode-segmentation", +] + +[[package]] +name = "isar-core" +version = "0.0.0" +dependencies = [ + "byteorder", + "cfg-if", + "crossbeam-channel", + "enum_dispatch", + "float_next_after", + "intmap", + "itertools", + "libc", + "mdbx-sys", + "once_cell", + "paste", + "rand", + "serde", + "serde_json", + "snafu", + "widestring", + "xxhash-rust", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "mdbx-sys" +version = "0.0.0" +dependencies = [ + "bindgen", + "cc", + "cmake", + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "widestring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/mobile/.vscode/settings.json b/mobile/.vscode/settings.json index 781acecc6d..eafbef8102 100644 --- a/mobile/.vscode/settings.json +++ b/mobile/.vscode/settings.json @@ -1,4 +1,5 @@ { + "dart.flutterSdkPath": ".fvm/versions/3.35.7", "dart.lineLength": 120, "[dart]": { "editor.rulers": [ diff --git a/mobile/README.md b/mobile/README.md index 4115891e99..1f0860ced6 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -4,7 +4,20 @@ The Immich mobile app is a Flutter-based solution leveraging the Isar Database f ## Setup -See [setup](https://docs.immich.app/developer/setup) for how to set up the mobile build environment. +1. [Install mise](https://mise.jdx.dev/installing-mise.html). +2. Change to the immich directory and trust the mise config with `mise trust`. +3. Install tools with mise: `mise install`. +4. Run `flutter pub get` to install the dependencies. +5. Run `make translation` to generate the translation file. +6. Run `flutter run` to start the app. + +## Translation + +To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then, from the `mobile/` directory, run + +```bash +make translation +``` ## Static Analysis diff --git a/mobile/analysis_options.yaml b/mobile/analysis_options.yaml index 065b3d56d8..895203fb98 100644 --- a/mobile/analysis_options.yaml +++ b/mobile/analysis_options.yaml @@ -26,74 +26,116 @@ linter: # producing the lint. rules: - # Correctness - always_declare_return_types: true - avoid_type_to_string: true - avoid_unused_constructor_parameters: true - avoid_void_async: true - cancel_subscriptions: true - cast_nullable_to_non_nullable: true - close_sinks: true - collection_methods_unrelated_type: true - deprecated_consistency: true - discarded_futures: true - no_adjacent_strings_in_list: true - no_self_assignments: true - noop_primitive_operations: true - only_throw_errors: true - parameter_assignments: true - throw_in_finally: true - tighten_type_of_initializing_formals: true + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule unawaited_futures: true - unnecessary_ignore: true - unnecessary_null_checks: true - unnecessary_statements: true - unrelated_type_equality_checks: true - - # Performance - avoid_slow_async_io: true - prefer_const_constructors: true - prefer_const_declarations: true - prefer_const_literals_to_create_immutables: true - - # Flutter specific - avoid_unnecessary_containers: true - sized_box_for_whitespace: true - use_build_context_synchronously: true - use_colored_box: true - use_decorated_box: true - use_full_hex_values_for_flutter_colors: true - - # Style - always_put_control_body_on_new_line: true - always_use_package_imports: true - avoid_multiple_declarations_per_line: true - avoid_print: true - combinators_ordering: true - directives_ordering: true - no_leading_underscores_for_local_identifiers: true - prefer_final_locals: true + use_build_context_synchronously: false require_trailing_commas: true - unnecessary_breaks: true - unnecessary_parenthesis: true - use_named_constants: true - use_super_parameters: true + unrelated_type_equality_checks: true + prefer_const_constructors: true + always_use_package_imports: true # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options analyzer: exclude: - - generated/openapi/** + - openapi/** - build/** - lib/generated_plugin_registrant.dart - lib/**/*.g.dart - lib/**/*.drift.dart - # NOTE: We explicitly do not use riverpod_lint as there are analyzer version conflicts between - # our Flutter version and the required old riverpod_lint 2.x + # TODO: Re-enable after upgrading custom_lint + # plugins: + # - custom_lint errors: unawaited_futures: warning - always_put_control_body_on_new_line: warning + +custom_lint: + debug: true + rules: + - avoid_build_context_in_providers: false + - avoid_public_notifier_properties: false + - avoid_manual_providers_as_generated_provider_dependency: false + - unsupported_provider_value: false + - import_rule_photo_manager: + message: photo_manager must only be used in MediaRepositories + restrict: package:photo_manager + allowed: + # required / wanted + - 'lib/infrastructure/repositories/album_media.repository.dart' + - 'lib/infrastructure/repositories/{storage,asset_media}.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 + - lib/providers/image/immich_local_{image,thumbnail}_provider.dart # accesses thumbnails via PhotoManager + # refactor to make the providers and services testable + - lib/providers/backup/{backup,manual_upload}.provider.dart # uses only PMProgressHandler + - lib/services/{background,backup}.service.dart # uses only PMProgressHandler + - test/**.dart + - import_rule_isar: + message: isar must only be used in entities and repositories + restrict: package:isar + allowed: + # required / wanted + - lib/entities/*.entity.dart + - lib/repositories/{album,asset,backup,database,etag,exif_info,user,timeline,partner}.repository.dart + - lib/infrastructure/entities/*.entity.dart + - lib/infrastructure/repositories/*.repository.dart + - lib/providers/infrastructure/db.provider.dart + # acceptable exceptions for the time being (until Isar is fully replaced) + - lib/providers/app_life_cycle.provider.dart + - integration_test/test_utils/general_helper.dart + - lib/domain/services/background_worker.service.dart + - lib/main.dart + - lib/pages/album/album_asset_selection.page.dart + - lib/routing/router.dart + - lib/services/immich_logger.service.dart # not really a service... more a util + - lib/utils/{db,migration}.dart + - lib/utils/bootstrap.dart + - lib/widgets/asset_grid/asset_grid_data_structure.dart + - test/**.dart + # refactor the remaining providers + - lib/providers/db.provider.dart + + - import_rule_openapi: + message: openapi must only be used through ApiRepositories + restrict: package:openapi + allowed: + # required / wanted + - lib/repositories/*_api.repository.dart + - lib/domain/models/sync_event.model.dart + - lib/{domain,infrastructure}/**/sync_stream.* + - lib/{domain,infrastructure}/**/sync_api.* + - lib/infrastructure/repositories/*_api.repository.dart + - lib/infrastructure/utils/*.converter.dart + # acceptable exceptions for the time being + - lib/entities/{album,asset,exif_info,user}.entity.dart # to convert DTOs to entities + - lib/infrastructure/utils/*.converter.dart + - lib/utils/{image_url_builder,openapi_patching}.dart # utils are fine + - test/modules/utils/openapi_patching_test.dart # filename is self-explanatory... + - lib/domain/services/sync_stream.service.dart # Making sure to comply with the type from database + - lib/domain/services/search.service.dart + + # refactor + - lib/models/map/map_marker.model.dart + - lib/models/server_info/server_{config,disk_info,features,version}.model.dart + - lib/models/shared_link/shared_link.model.dart + - lib/providers/asset_viewer/asset_people.provider.dart + - lib/providers/auth.provider.dart + - lib/providers/image/immich_remote_{image,thumbnail}_provider.dart + - lib/providers/map/map_state.provider.dart + - lib/providers/search/{search,search_filter}.provider.dart + - lib/providers/websocket.provider.dart + - lib/routing/auth_guard.dart + - lib/services/{api,asset,backup,memory,oauth,search,shared_link,stack,trash}.service.dart + - lib/widgets/album/album_thumbnail_listtile.dart + - lib/widgets/forms/login/login_form.dart + - lib/widgets/search/search_filter/{camera_picker,location_picker,people_picker}.dart + - lib/services/auth.service.dart # on ApiException + - test/services/auth.service_test.dart # on ApiException + # allow import from test + - test/**.dart dart_code_metrics: rules: @@ -104,18 +146,6 @@ dart_code_metrics: exclude-paths: - 'lib/utils/debug_print.dart' severity: perf - - name: tr - description: 'Use context.t. or StaticTranslations.instance. instead of tr().' - - type: String - entries: - - name: tr - description: 'Use context.t. or StaticTranslations.instance. instead of String.tr().' - - name: plural - description: 'Use the generated plural accessor instead of String.plural().' - - type: Text - entries: - - name: tr - description: 'Use Text(context.t.) instead of Text().tr().' # All rules from "recommended" preset # Show potential errors # - avoid-cascade-after-if-null @@ -129,17 +159,3 @@ dart_code_metrics: - prefer-const-border-radius - prefer-declaring-const-constructor - prefer-switch-expression - - # Riverpod - - avoid-calling-notifier-members-inside-build - - avoid-notifier-constructors - - avoid-nullable-async-value-pattern - - avoid-public-notifier-properties - - avoid-ref-inside-state-dispose - - avoid-ref-read-inside-build - - avoid-ref-watch-outside-build - - avoid-unnecessary-consumer-widgets - - dispose-provided-instances - - prefer-immutable-provider-arguments - - use-ref-and-state-synchronously - - use-ref-read-synchronously diff --git a/mobile/android/app/CMakeLists.txt b/mobile/android/app/CMakeLists.txt index 9cad7b915a..133bde4fc0 100644 --- a/mobile/android/app/CMakeLists.txt +++ b/mobile/android/app/CMakeLists.txt @@ -7,7 +7,6 @@ project(native_buffer LANGUAGES C) add_library(native_buffer SHARED src/main/cpp/native_buffer.c - src/main/cpp/native_image.c ) target_link_libraries(native_buffer jnigraphics) diff --git a/mobile/android/app/build.gradle b/mobile/android/app/build.gradle index 7e3d67fa81..103cf79e4e 100644 --- a/mobile/android/app/build.gradle +++ b/mobile/android/app/build.gradle @@ -1,10 +1,27 @@ plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) + id "com.android.application" + id "kotlin-android" id "dev.flutter.flutter-gradle-plugin" - alias(libs.plugins.ksp) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.kotlin.compose) + id 'com.google.devtools.ksp' + id 'org.jetbrains.kotlin.plugin.serialization' + id 'org.jetbrains.kotlin.plugin.compose' version '2.0.20' // this version matches your Kotlin version + +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withInputStream { localProperties.load(it) } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' } def keystoreProperties = new Properties() @@ -14,8 +31,8 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + compileSdkVersion 35 + ndkVersion = "28.2.13676358" compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -38,10 +55,10 @@ android { defaultConfig { applicationId "app.alextran.immich" - minSdk = 26 - targetSdk = flutter.targetSdkVersion - versionCode flutter.versionCode - versionName flutter.versionName + minSdkVersion 26 + targetSdkVersion 35 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName } signingConfigs { @@ -50,10 +67,10 @@ android { def keyPasswordVal = System.getenv("ANDROID_KEY_PASSWORD") def storePasswordVal = System.getenv("ANDROID_STORE_PASSWORD") - keyAlias keyAliasVal ?: keystoreProperties['keyAlias'] - keyPassword keyPasswordVal ?: keystoreProperties['keyPassword'] - storeFile file("../key.jks").exists() ? file("../key.jks") : file(keystoreProperties['storeFile'] ?: '../key.jks') - storePassword storePasswordVal ?: keystoreProperties['storePassword'] + keyAlias keyAliasVal ? keyAliasVal : keystoreProperties['keyAlias'] + keyPassword keyPasswordVal ? keyPasswordVal : keystoreProperties['keyPassword'] + storeFile file("../key.jks") ? file("../key.jks") : file(keystoreProperties['storeFile']) + storePassword storePasswordVal ? storePasswordVal : keystoreProperties['storePassword'] } } @@ -64,15 +81,8 @@ android { } release { - def hasKeystore = file("../key.jks").exists() && file("../key.jks").length() > 0 - signingConfig hasKeystore ? signingConfigs.release : signingConfigs.debug + signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - - def prNumber = System.getenv("PR_NUMBER") - if (prNumber) { - applicationIdSuffix ".pr${prNumber}" - versionNameSuffix "-pr${prNumber}" - } } } namespace 'app.alextran.immich' @@ -89,31 +99,43 @@ flutter { } dependencies { - implementation libs.okhttp - implementation libs.cronet.embedded - implementation libs.media3.datasource.okhttp - implementation libs.media3.datasource.cronet - implementation libs.kotlinx.coroutines.android - implementation libs.work.runtime.ktx - implementation libs.concurrent.futures - implementation libs.guava - implementation libs.glide - implementation libs.kotlinx.serialization.json + def kotlin_version = '2.0.20' + def kotlin_coroutines_version = '1.9.0' + def work_version = '2.9.1' + def concurrent_version = '1.2.0' + def guava_version = '33.3.1-android' + def glide_version = '4.16.0' + def serialization_version = '1.8.1' + def compose_version = '1.1.1' + def gson_version = '2.10.1' + def okhttp_version = '4.12.0' - ksp libs.glide.ksp - coreLibraryDesugaring libs.desugar.jdk.libs + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "com.squareup.okhttp3:okhttp:$okhttp_version" + implementation 'org.chromium.net:cronet-embedded:143.7445.0' + implementation("androidx.media3:media3-datasource-okhttp:1.9.2") + implementation("androidx.media3:media3-datasource-cronet:1.9.2") + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" + implementation "androidx.work:work-runtime-ktx:$work_version" + implementation "androidx.concurrent:concurrent-futures:$concurrent_version" + implementation "com.google.guava:guava:$guava_version" + implementation "com.github.bumptech.glide:glide:$glide_version" + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version" + + ksp "com.github.bumptech.glide:ksp:$glide_version" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2' //Glance Widget - implementation libs.glance.appwidget - implementation libs.gson + implementation "androidx.glance:glance-appwidget:$compose_version" + implementation "com.google.code.gson:gson:$gson_version" // Glance Configure - implementation libs.activity.compose - implementation libs.compose.ui - implementation libs.compose.ui.tooling - implementation libs.compose.material3 - implementation libs.lifecycle.runtime.ktx - implementation libs.material + implementation "androidx.activity:activity-compose:1.8.2" + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.ui:ui-tooling:$compose_version" + implementation "androidx.compose.material3:material3:1.2.1" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" + implementation "com.google.android.material:material:1.12.0" } // This is uncommented in F-Droid build script diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/android/app/src/main/AndroidManifest.xml index 5f4de8284a..db3859ab6e 100644 --- a/mobile/android/app/src/main/AndroidManifest.xml +++ b/mobile/android/app/src/main/AndroidManifest.xml @@ -39,6 +39,10 @@ android:exported="false" android:foregroundServiceType="dataSync|shortService" /> + + @@ -89,20 +93,6 @@ - - - - - - - - - - - - - - @@ -152,6 +142,12 @@ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> + + + -#include -#include -#include - -// Cache-friendly block size for the tiled rotation (in pixels). 32x32 uint32 = 4KB, fits L1. -#define TILE 32 - -// EXIF orientation values (androidx.exifinterface.media.ExifInterface.ORIENTATION_*). -enum { - ORIENTATION_FLIP_HORIZONTAL = 2, - ORIENTATION_ROTATE_180 = 3, - ORIENTATION_FLIP_VERTICAL = 4, - ORIENTATION_TRANSPOSE = 5, - ORIENTATION_ROTATE_90 = 6, - ORIENTATION_TRANSVERSE = 7, - ORIENTATION_ROTATE_270 = 8, -}; - -// The orientations that swap width and height. Must stay in sync with affine_for's dim usage. -static int swaps_dims(int o) { - return o == ORIENTATION_ROTATE_90 || o == ORIENTATION_ROTATE_270 || - o == ORIENTATION_TRANSPOSE || o == ORIENTATION_TRANSVERSE; -} - -// A source pixel (sx, sy) maps to destination index base + sx*stepX + sy*stepY, where dw is the -// destination width. This affine form covers all 8 EXIF orientations and matches the pixel layout -// of Bitmap.createBitmap(src, matrixForExifOrientation(o)). int64_t so it stays correct on -// armeabi-v7a (32-bit long) regardless of how large MAX_RAW_DECODE_PIXELS grows. -static void affine_for(int o, int sw, int sh, int dw, int64_t *base, int64_t *stepX, int64_t *stepY) { - switch (o) { - case ORIENTATION_ROTATE_90: *base = sh - 1; *stepX = dw; *stepY = -1; break; - case ORIENTATION_ROTATE_270: *base = (int64_t) (sw - 1) * dw; *stepX = -dw; *stepY = 1; break; - case ORIENTATION_ROTATE_180: *base = (int64_t) (sh - 1) * dw + (sw - 1); *stepX = -1; *stepY = -dw; break; - case ORIENTATION_FLIP_HORIZONTAL: *base = sw - 1; *stepX = -1; *stepY = dw; break; - case ORIENTATION_FLIP_VERTICAL: *base = (int64_t) (sh - 1) * dw; *stepX = 1; *stepY = -dw; break; - case ORIENTATION_TRANSPOSE: *base = 0; *stepX = dw; *stepY = 1; break; - case ORIENTATION_TRANSVERSE: *base = (int64_t) (sw - 1) * dw + (sh - 1); *stepX = -dw; *stepY = -1; break; - default: *base = 0; *stepX = 1; *stepY = dw; break; - } -} - -// Copy each source pixel (whole uint32, so channel order/premult is irrelevant) to its rotated -// destination, walking TILE x TILE blocks so the scattered writes of a 90/270 transpose stay -// cache-resident. dst is densely packed (rowBytes == dw*4, no padding), which the affine math relies on. -static void rotate_tiled(const uint8_t *src, int srcStride, uint32_t *dst, - int sw, int sh, int64_t base, int64_t stepX, int64_t stepY) { - for (int ty = 0; ty < sh; ty += TILE) { - int yEnd = ty + TILE < sh ? ty + TILE : sh; - for (int tx = 0; tx < sw; tx += TILE) { - int xEnd = tx + TILE < sw ? tx + TILE : sw; - for (int sy = ty; sy < yEnd; sy++) { - const uint32_t *srcRow = (const uint32_t *) (src + (size_t) sy * srcStride); - int64_t idx = base + (int64_t) sy * stepY + (int64_t) tx * stepX; - for (int sx = tx; sx < xEnd; sx++) { - dst[idx] = srcRow[sx]; - idx += stepX; - } - } - } - } -} - -// Rotates an RGBA_8888 bitmap to the given EXIF orientation into a freshly malloc'd buffer (free it -// via NativeBuffer.free). Fills outInfo with {width, height, rowBytes} and returns the buffer -// address, or 0 if the bitmap can't be handled (e.g. a non-8888 format) so the caller can fall back. -JNIEXPORT jlong JNICALL -Java_app_alextran_immich_NativeImage_rotate( - JNIEnv *env, jclass clazz, jobject bitmap, jint orientation, jintArray outInfo) { - AndroidBitmapInfo info; - if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) { - return 0; - } - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { - return 0; - } - - int sw = (int) info.width; - int sh = (int) info.height; - int dw = swaps_dims(orientation) ? sh : sw; - int dh = swaps_dims(orientation) ? sw : sh; - - uint32_t *dst = (uint32_t *) malloc((size_t) dw * dh * 4); - if (dst == NULL) { - return 0; - } - - void *srcPixels = NULL; - if (AndroidBitmap_lockPixels(env, bitmap, &srcPixels) != ANDROID_BITMAP_RESULT_SUCCESS) { - free(dst); - return 0; - } - - int64_t base, stepX, stepY; - affine_for(orientation, sw, sh, dw, &base, &stepX, &stepY); - rotate_tiled((const uint8_t *) srcPixels, (int) info.stride, dst, sw, sh, base, stepX, stepY); - - AndroidBitmap_unlockPixels(env, bitmap); - - jint dims[3] = {dw, dh, dw * 4}; - (*env)->SetIntArrayRegion(env, outInfo, 0, 3, dims); - // Keep ownership in C until the buffer is safely handed back: if outInfo was somehow too small, - // SetIntArrayRegion left a pending exception and Kotlin will never receive (or free) dst. - if ((*env)->ExceptionCheck(env)) { - free(dst); - return 0; - } - return (jlong) dst; -} - -// Convert an RGBA_1010102 buffer to densely-packed RGBA_8888, matching Skia's -// Bitmap.copy(ARGB_8888) byte-for-byte so it's a drop-in for the intermediate 8888 bitmap. -// Each source pixel is a native (little-endian on every Android ABI) u32 with R in bits 0-9, -// G in 10-19, B in 20-29, A in 30-31 (standard RGB10_A2). Each 10-bit channel maps to 8-bit via -// round(v*255/1023). The 2-bit alpha maps to a*85. Both are kept as plain arithmetic as the whole -// loop auto-vectorizes to NEON and measures faster than a LUT on-device. Output is R,G,B,A bytes -// per pixel, i.e. Android ARGB_8888 memory == Dart PixelFormat.rgba8888. -static void convert_1010102(const uint8_t *src, int srcStride, uint32_t *dst, int w, int h) { - for (int y = 0; y < h; y++) { - const uint32_t *srcRow = (const uint32_t *) (src + (size_t) y * srcStride); - uint32_t *dstRow = dst + (size_t) y * w; - for (int x = 0; x < w; x++) { - uint32_t px = srcRow[x]; - uint32_t r = ((px & 0x3FF) * 16336u + 32768u) >> 16; - uint32_t g = (((px >> 10) & 0x3FF) * 16336u + 32768u) >> 16; - uint32_t b = (((px >> 20) & 0x3FF) * 16336u + 32768u) >> 16; - uint32_t a = ((px >> 30) & 0x3) * 85u; - dstRow[x] = r | (g << 8) | (b << 16) | (a << 24); - } - } -} - -// Converts an RGBA_1010102 bitmap (what a 10-bit HEIC/AVIF decodes to on API 33+) into a freshly -// malloc'd RGBA_8888 buffer. Fills outInfo with {width, height, rowBytes} and returns the buffer -// address, or 0 (so the caller falls back to a Skia copy) if the bitmap isn't 1010102 or can't be -// locked. Same ownership contract as rotate: free the returned buffer via NativeBuffer.free. -JNIEXPORT jlong JNICALL -Java_app_alextran_immich_NativeImage_convert1010102( - JNIEnv *env, jclass clazz, jobject bitmap, jintArray outInfo) { - AndroidBitmapInfo info; - if (AndroidBitmap_getInfo(env, bitmap, &info) != ANDROID_BITMAP_RESULT_SUCCESS) { - return 0; - } - if (info.format != ANDROID_BITMAP_FORMAT_RGBA_1010102) { - return 0; - } - - int w = (int) info.width; - int h = (int) info.height; - - uint32_t *dst = (uint32_t *) malloc((size_t) w * h * 4); - if (dst == NULL) { - return 0; - } - - void *srcPixels = NULL; - if (AndroidBitmap_lockPixels(env, bitmap, &srcPixels) != ANDROID_BITMAP_RESULT_SUCCESS) { - free(dst); - return 0; - } - - convert_1010102((const uint8_t *) srcPixels, (int) info.stride, dst, w, h); - - AndroidBitmap_unlockPixels(env, bitmap); - - jint dims[3] = {w, h, w * 4}; - (*env)->SetIntArrayRegion(env, outInfo, 0, 3, dims); - if ((*env)->ExceptionCheck(env)) { - free(dst); - return 0; - } - return (jlong) dst; -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt new file mode 100644 index 0000000000..f62f25558d --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackgroundServicePlugin.kt @@ -0,0 +1,389 @@ +package app.alextran.immich + +import android.app.Activity +import android.content.ContentResolver +import android.content.ContentUris +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.provider.Settings +import android.util.Log +import androidx.annotation.RequiresApi +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry +import java.security.MessageDigest +import java.io.FileInputStream +import kotlinx.coroutines.* +import androidx.core.net.toUri + +/** + * Android plugin for Dart `BackgroundService` and file trash operations + */ +class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener { + + private var methodChannel: MethodChannel? = null + private var fileTrashChannel: MethodChannel? = null + private var context: Context? = null + private var pendingResult: Result? = null + private val permissionRequestCode = 1001 + private val trashRequestCode = 1002 + private var activityBinding: ActivityPluginBinding? = null + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + onAttachedToEngine(binding.applicationContext, binding.binaryMessenger) + } + + private fun onAttachedToEngine(ctx: Context, messenger: BinaryMessenger) { + context = ctx + methodChannel = MethodChannel(messenger, "immich/foregroundChannel") + methodChannel?.setMethodCallHandler(this) + + // Add file trash channel + fileTrashChannel = MethodChannel(messenger, "file_trash") + fileTrashChannel?.setMethodCallHandler(this) + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + onDetachedFromEngine() + } + + private fun onDetachedFromEngine() { + methodChannel?.setMethodCallHandler(null) + methodChannel = null + fileTrashChannel?.setMethodCallHandler(null) + fileTrashChannel = null + } + + override fun onMethodCall(call: MethodCall, result: Result) { + val ctx = context!! + when (call.method) { + // Existing BackgroundService methods + "enable" -> { + val args = call.arguments>()!! + ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putBoolean(ContentObserverWorker.SHARED_PREF_SERVICE_ENABLED, true) + .putLong(BackupWorker.SHARED_PREF_CALLBACK_KEY, args[0] as Long) + .putString(BackupWorker.SHARED_PREF_NOTIFICATION_TITLE, args[1] as String) + .apply() + ContentObserverWorker.enable(ctx, immediate = args[2] as Boolean) + result.success(true) + } + + "configure" -> { + val args = call.arguments>()!! + val requireUnmeteredNetwork = args[0] as Boolean + val requireCharging = args[1] as Boolean + val triggerUpdateDelay = (args[2] as Number).toLong() + val triggerMaxDelay = (args[3] as Number).toLong() + ContentObserverWorker.configureWork( + ctx, + requireUnmeteredNetwork, + requireCharging, + triggerUpdateDelay, + triggerMaxDelay + ) + result.success(true) + } + + "disable" -> { + ContentObserverWorker.disable(ctx) + BackupWorker.stopWork(ctx) + result.success(true) + } + + "isEnabled" -> { + result.success(ContentObserverWorker.isEnabled(ctx)) + } + + "isIgnoringBatteryOptimizations" -> { + result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx)) + } + + "digestFiles" -> { + val args = call.arguments>()!! + GlobalScope.launch(Dispatchers.IO) { + val buf = ByteArray(BUFFER_SIZE) + val digest: MessageDigest = MessageDigest.getInstance("SHA-1") + val hashes = arrayOfNulls(args.size) + for (i in args.indices) { + val path = args[i] + var len = 0 + try { + val file = FileInputStream(path) + file.use { assetFile -> + while (true) { + len = assetFile.read(buf) + if (len != BUFFER_SIZE) break + digest.update(buf) + } + } + digest.update(buf, 0, len) + hashes[i] = digest.digest() + } catch (e: Exception) { + // skip this file + Log.w(TAG, "Failed to hash file ${args[i]}: $e") + } + } + result.success(hashes.asList()) + } + } + + // File Trash methods moved from MainActivity + "moveToTrash" -> { + val mediaUrls = call.argument>("mediaUrls") + if (mediaUrls != null) { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { + moveToTrash(mediaUrls, result) + } else { + result.error("PERMISSION_DENIED", "Media permission required", null) + } + } else { + result.error("INVALID_NAME", "The mediaUrls is not specified.", null) + } + } + + "restoreFromTrash" -> { + val fileName = call.argument("fileName") + val type = call.argument("type") + val mediaId = call.argument("mediaId") + if (fileName != null && type != null) { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { + restoreFromTrash(fileName, type, result) + } else { + result.error("PERMISSION_DENIED", "Media permission required", null) + } + } else + if (mediaId != null && type != null) { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && hasManageMediaPermission()) { + restoreFromTrashById(mediaId, type, result) + } else { + result.error("PERMISSION_DENIED", "Media permission required", null) + } + } else { + result.error("INVALID_PARAMS", "Required params are not specified.", null) + } + } + + "requestManageMediaPermission" -> { + if (!hasManageMediaPermission()) { + requestManageMediaPermission(result) + } else { + Log.e("Manage storage permission", "Permission already granted") + result.success(true) + } + } + + "hasManageMediaPermission" -> { + if (hasManageMediaPermission()) { + Log.i("Manage storage permission", "Permission already granted") + result.success(true) + } else { + result.success(false) + } + } + + "manageMediaPermission" -> requestManageMediaPermission(result) + + else -> result.notImplemented() + } + } + + private fun hasManageMediaPermission(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + MediaStore.canManageMedia(context!!); + } else { + false + } + } + + private fun requestManageMediaPermission(result: Result) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + pendingResult = result // Store the result callback + val activity = activityBinding?.activity ?: return + + val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA) + intent.data = "package:${activity.packageName}".toUri() + activity.startActivityForResult(intent, permissionRequestCode) + } else { + result.success(false) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun moveToTrash(mediaUrls: List, result: Result) { + val urisToTrash = mediaUrls.map { it.toUri() } + if (urisToTrash.isEmpty()) { + result.error("INVALID_ARGS", "No valid URIs provided", null) + return + } + + toggleTrash(urisToTrash, true, result); + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreFromTrash(name: String, type: Int, result: Result) { + val uri = getTrashedFileUri(name, type) + if (uri == null) { + Log.e("TrashError", "Asset Uri cannot be found obtained") + result.error("TrashError", "Asset Uri cannot be found obtained", null) + return + } + Log.e("FILE_URI", uri.toString()) + uri.let { toggleTrash(listOf(it), false, result) } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreFromTrashById(mediaId: String, type: Int, result: Result) { + val id = mediaId.toLongOrNull() + if (id == null) { + result.error("INVALID_ID", "The file id is not a valid number: $mediaId", null) + return + } + if (!isInTrash(id)) { + result.error("TrashNotFound", "Item with id=$id not found in trash", null) + return + } + + val uri = ContentUris.withAppendedId(contentUriForType(type), id) + + try { + Log.i(TAG, "restoreFromTrashById: uri=$uri (type=$type,id=$id)") + restoreUris(listOf(uri), result) + } catch (e: Exception) { + Log.w(TAG, "restoreFromTrashById failed", e) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun toggleTrash(contentUris: List, isTrashed: Boolean, result: Result) { + val activity = activityBinding?.activity + val contentResolver = context?.contentResolver + if (activity == null || contentResolver == null) { + result.error("TrashError", "Activity or ContentResolver not available", null) + return + } + + try { + val pendingIntent = MediaStore.createTrashRequest(contentResolver, contentUris, isTrashed) + pendingResult = result // Store for onActivityResult + activity.startIntentSenderForResult( + pendingIntent.intentSender, + trashRequestCode, + null, 0, 0, 0 + ) + } catch (e: Exception) { + Log.e("TrashError", "Error creating or starting trash request", e) + result.error("TrashError", "Error creating or starting trash request", null) + } + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun getTrashedFileUri(fileName: String, type: Int): Uri? { + val contentResolver = context?.contentResolver ?: return null + val queryUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + val projection = arrayOf(MediaStore.Files.FileColumns._ID) + + val queryArgs = Bundle().apply { + putString( + ContentResolver.QUERY_ARG_SQL_SELECTION, + "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ?" + ) + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(fileName)) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + } + + contentResolver.query(queryUri, projection, queryArgs, null)?.use { cursor -> + if (cursor.moveToFirst()) { + val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)) + return ContentUris.withAppendedId(contentUriForType(type), id) + } + } + return null + } + + // ActivityAware implementation + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + override fun onDetachedFromActivityForConfigChanges() { + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + activityBinding = binding + binding.addActivityResultListener(this) + } + + override fun onDetachedFromActivity() { + activityBinding?.removeActivityResultListener(this) + activityBinding = null + } + + // ActivityResultListener implementation + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + if (requestCode == permissionRequestCode) { + val granted = hasManageMediaPermission() + pendingResult?.success(granted) + pendingResult = null + return true + } + + if (requestCode == trashRequestCode) { + val approved = resultCode == Activity.RESULT_OK + pendingResult?.success(approved) + pendingResult = null + return true + } + return false + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun isInTrash(id: Long): Boolean { + val contentResolver = context?.contentResolver ?: return false + val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + val args = Bundle().apply { + putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?") + putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString())) + putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) + putInt(ContentResolver.QUERY_ARG_LIMIT, 1) + } + return contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) + ?.use { it.moveToFirst() } == true + } + + @RequiresApi(Build.VERSION_CODES.R) + private fun restoreUris(uris: List, result: Result) { + if (uris.isEmpty()) { + result.error("TrashError", "No URIs to restore", null) + return + } + Log.i(TAG, "restoreUris: count=${uris.size}, first=${uris.first()}") + toggleTrash(uris, false, result) + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun contentUriForType(type: Int): Uri = + when (type) { + // same order as AssetType from dart + 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI + 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI + 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) + } +} + +private const val TAG = "BackgroundServicePlugin" +private const val BUFFER_SIZE = 2 * 1024 * 1024 diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt new file mode 100644 index 0000000000..9c90528dc9 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/BackupWorker.kt @@ -0,0 +1,394 @@ +package app.alextran.immich + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.os.PowerManager +import android.os.SystemClock +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.app.NotificationCompat +import androidx.concurrent.futures.ResolvableFuture +import androidx.work.BackoffPolicy +import androidx.work.Constraints +import androidx.work.ForegroundInfo +import androidx.work.ListenableWorker +import androidx.work.NetworkType +import androidx.work.WorkerParameters +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import androidx.work.WorkInfo +import com.google.common.util.concurrent.ListenableFuture +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.embedding.engine.loader.FlutterLoader +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.view.FlutterCallbackInformation +import java.util.concurrent.TimeUnit + +/** + * Worker executed by Android WorkManager to perform backup in background + * + * Starts the Dart runtime/engine and calls `_nativeEntry` function in + * `background.service.dart` to run the actual backup logic. + * Called by Android WorkManager when all constraints for the work are met, + * i.e. battery is not low and optionally Wifi and charging are active. + */ +class BackupWorker(ctx: Context, params: WorkerParameters) : ListenableWorker(ctx, params), + MethodChannel.MethodCallHandler { + + private val resolvableFuture = ResolvableFuture.create() + private var engine: FlutterEngine? = null + private lateinit var backgroundChannel: MethodChannel + private val notificationManager = + ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private val isIgnoringBatteryOptimizations = isIgnoringBatteryOptimizations(applicationContext) + private var timeBackupStarted: Long = 0L + private var notificationBuilder: NotificationCompat.Builder? = null + private var notificationDetailBuilder: NotificationCompat.Builder? = null + private var fgFuture: ListenableFuture? = null + + override fun startWork(): ListenableFuture { + + Log.d(TAG, "startWork") + + val ctx = applicationContext + + if (!flutterLoader.initialized()) { + flutterLoader.startInitialization(ctx) + } + + // Create a Notification channel + createChannel() + + Log.d(TAG, "isIgnoringBatteryOptimizations $isIgnoringBatteryOptimizations") + if (isIgnoringBatteryOptimizations) { + // normal background services can only up to 10 minutes + // foreground services are allowed to run indefinitely + // requires battery optimizations to be disabled (either manually by the user + // or by the system learning that immich is important to the user) + val title = ctx.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getString(SHARED_PREF_NOTIFICATION_TITLE, NOTIFICATION_DEFAULT_TITLE)!! + showInfo(getInfoBuilder(title, indeterminate = true).build()) + } + + engine = FlutterEngine(ctx) + + flutterLoader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { + runDart() + + } + + return resolvableFuture + } + + /** + * Starts the Dart runtime/engine and calls `_nativeEntry` function in + * `background.service.dart` to run the actual backup logic. + */ + private fun runDart() { + val callbackDispatcherHandle = applicationContext.getSharedPreferences( + SHARED_PREF_NAME, Context.MODE_PRIVATE + ).getLong(SHARED_PREF_CALLBACK_KEY, 0L) + val callbackInformation = + FlutterCallbackInformation.lookupCallbackInformation(callbackDispatcherHandle) + val appBundlePath = flutterLoader.findAppBundlePath() + + engine?.let { engine -> + backgroundChannel = MethodChannel(engine.dartExecutor, "immich/backgroundChannel") + backgroundChannel.setMethodCallHandler(this@BackupWorker) + engine.dartExecutor.executeDartCallback( + DartExecutor.DartCallback( + applicationContext.assets, + appBundlePath, + callbackInformation + ) + ) + } + } + + override fun onStopped() { + Log.d(TAG, "onStopped") + // called when the system has to stop this worker because constraints are + // no longer met or the system needs resources for more important tasks + Handler(Looper.getMainLooper()).postAtFrontOfQueue { + if (::backgroundChannel.isInitialized) { + backgroundChannel.invokeMethod("systemStop", null) + } + } + waitOnSetForegroundAsync() + // cannot await/get(block) on resolvableFuture as its already cancelled (would throw CancellationException) + // instead, wait for 5 seconds until forcefully stopping backup work + Handler(Looper.getMainLooper()).postDelayed({ + stopEngine(null) + }, 5000) + } + + private fun waitOnSetForegroundAsync() { + val fgFuture = this.fgFuture + if (fgFuture != null && !fgFuture.isCancelled && !fgFuture.isDone) { + try { + fgFuture.get(500, TimeUnit.MILLISECONDS) + } catch (e: Exception) { + // ignored, there is nothing to be done + } + } + } + + private fun stopEngine(result: Result?) { + clearBackgroundNotification() + engine?.destroy() + engine = null + if (result != null) { + Log.d(TAG, "stopEngine result=${result}") + resolvableFuture.set(result) + } + waitOnSetForegroundAsync() + } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + override fun onMethodCall(call: MethodCall, r: MethodChannel.Result) { + when (call.method) { + "initialized" -> { + timeBackupStarted = SystemClock.uptimeMillis() + backgroundChannel.invokeMethod( + "onAssetsChanged", + null, + object : MethodChannel.Result { + override fun notImplemented() { + stopEngine(Result.failure()) + } + + override fun error(errorCode: String, errorMessage: String?, errorDetails: Any?) { + stopEngine(Result.failure()) + } + + override fun success(receivedResult: Any?) { + val success = receivedResult as Boolean + stopEngine(if (success) Result.success() else Result.retry()) + } + } + ) + } + + "updateNotification" -> { + val args = call.arguments>()!! + val title = args[0] as String? + val content = args[1] as String? + val progress = args[2] as Int + val max = args[3] as Int + val indeterminate = args[4] as Boolean + val isDetail = args[5] as Boolean + val onlyIfFG = args[6] as Boolean + if (!onlyIfFG || isIgnoringBatteryOptimizations) { + showInfo( + getInfoBuilder(title, content, isDetail, progress, max, indeterminate).build(), + isDetail + ) + } + } + + "showError" -> { + val args = call.arguments>()!! + val title = args[0] as String + val content = args[1] as String? + val individualTag = args[2] as String? + showError(title, content, individualTag) + } + + "clearErrorNotifications" -> clearErrorNotifications() + "hasContentChanged" -> { + val lastChange = applicationContext + .getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getLong(SHARED_PREF_LAST_CHANGE, timeBackupStarted) + val hasContentChanged = lastChange > timeBackupStarted; + timeBackupStarted = SystemClock.uptimeMillis() + r.success(hasContentChanged) + } + + else -> r.notImplemented() + } + } + + private fun showError(title: String, content: String?, individualTag: String?) { + val notification = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ERROR_ID) + .setContentTitle(title) + .setTicker(title) + .setContentText(content) + .setSmallIcon(R.drawable.notification_icon) + .build() + notificationManager.notify(individualTag, NOTIFICATION_ERROR_ID, notification) + } + + private fun clearErrorNotifications() { + notificationManager.cancel(NOTIFICATION_ERROR_ID) + } + + private fun clearBackgroundNotification() { + notificationManager.cancel(NOTIFICATION_ID) + notificationManager.cancel(NOTIFICATION_DETAIL_ID) + } + + private fun showInfo(notification: Notification, isDetail: Boolean = false) { + val id = if (isDetail) NOTIFICATION_DETAIL_ID else NOTIFICATION_ID + + if (isIgnoringBatteryOptimizations && !isDetail) { + fgFuture = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + setForegroundAsync(ForegroundInfo(id, notification, FOREGROUND_SERVICE_TYPE_SHORT_SERVICE)) + } else { + setForegroundAsync(ForegroundInfo(id, notification)) + } + } else { + notificationManager.notify(id, notification) + } + } + + private fun getInfoBuilder( + title: String? = null, + content: String? = null, + isDetail: Boolean = false, + progress: Int = 0, + max: Int = 0, + indeterminate: Boolean = false, + ): NotificationCompat.Builder { + var builder = if (isDetail) notificationDetailBuilder else notificationBuilder + if (builder == null) { + builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.notification_icon) + .setOnlyAlertOnce(true) + .setOngoing(true) + if (isDetail) { + notificationDetailBuilder = builder + } else { + notificationBuilder = builder + } + } + if (title != null) { + builder.setTicker(title).setContentTitle(title) + } + if (content != null) { + builder.setContentText(content) + } + return builder.setProgress(max, progress, indeterminate) + } + + private fun createChannel() { + val foreground = NotificationChannel( + NOTIFICATION_CHANNEL_ID, + NOTIFICATION_CHANNEL_ID, + NotificationManager.IMPORTANCE_LOW + ) + notificationManager.createNotificationChannel(foreground) + val error = NotificationChannel( + NOTIFICATION_CHANNEL_ERROR_ID, + NOTIFICATION_CHANNEL_ERROR_ID, + NotificationManager.IMPORTANCE_HIGH + ) + notificationManager.createNotificationChannel(error) + } + + companion object { + const val SHARED_PREF_NAME = "immichBackgroundService" + const val SHARED_PREF_CALLBACK_KEY = "callbackDispatcherHandle" + const val SHARED_PREF_NOTIFICATION_TITLE = "notificationTitle" + const val SHARED_PREF_LAST_CHANGE = "lastChange" + + private const val TASK_NAME_BACKUP = "immich/BackupWorker" + private const val NOTIFICATION_CHANNEL_ID = "immich/backgroundService" + private const val NOTIFICATION_CHANNEL_ERROR_ID = "immich/backgroundServiceError" + private const val NOTIFICATION_DEFAULT_TITLE = "Immich" + private const val NOTIFICATION_ID = 1 + private const val NOTIFICATION_ERROR_ID = 2 + private const val NOTIFICATION_DETAIL_ID = 3 + private const val ONE_MINUTE = 60000L + + /** + * Enqueues the BackupWorker to run once the constraints are met + */ + fun enqueueBackupWorker( + context: Context, + requireWifi: Boolean = false, + requireCharging: Boolean = false, + delayMilliseconds: Long = 0L + ) { + val workRequest = buildWorkRequest(requireWifi, requireCharging, delayMilliseconds) + WorkManager.getInstance(context) + .enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.KEEP, workRequest) + Log.d(TAG, "enqueueBackupWorker: BackupWorker enqueued") + } + + /** + * Updates the constraints of an already enqueued BackupWorker + */ + fun updateBackupWorker( + context: Context, + requireWifi: Boolean = false, + requireCharging: Boolean = false + ) { + try { + val wm = WorkManager.getInstance(context) + val workInfoFuture = wm.getWorkInfosForUniqueWork(TASK_NAME_BACKUP) + val workInfoList = workInfoFuture.get(1000, TimeUnit.MILLISECONDS) + if (workInfoList != null) { + for (workInfo in workInfoList) { + if (workInfo.state == WorkInfo.State.ENQUEUED) { + val workRequest = buildWorkRequest(requireWifi, requireCharging) + wm.enqueueUniqueWork(TASK_NAME_BACKUP, ExistingWorkPolicy.REPLACE, workRequest) + Log.d(TAG, "updateBackupWorker updated BackupWorker constraints") + return + } + } + } + Log.d(TAG, "updateBackupWorker: BackupWorker not enqueued") + } catch (e: Exception) { + Log.d(TAG, "updateBackupWorker failed: $e") + } + } + + /** + * Stops the currently running worker (if any) and removes it from the work queue + */ + fun stopWork(context: Context) { + WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_BACKUP) + Log.d(TAG, "stopWork: BackupWorker cancelled") + } + + /** + * Returns `true` if the app is ignoring battery optimizations + */ + fun isIgnoringBatteryOptimizations(ctx: Context): Boolean { + val powerManager = ctx.getSystemService(Context.POWER_SERVICE) as PowerManager + return powerManager.isIgnoringBatteryOptimizations(ctx.packageName) + } + + private fun buildWorkRequest( + requireWifi: Boolean = false, + requireCharging: Boolean = false, + delayMilliseconds: Long = 0L + ): OneTimeWorkRequest { + val constraints = Constraints.Builder() + .setRequiredNetworkType(if (requireWifi) NetworkType.UNMETERED else NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true) + .setRequiresCharging(requireCharging) + .build(); + + val work = OneTimeWorkRequest.Builder(BackupWorker::class.java) + .setConstraints(constraints) + .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, ONE_MINUTE, TimeUnit.MILLISECONDS) + .setInitialDelay(delayMilliseconds, TimeUnit.MILLISECONDS) + .build() + return work + } + + private val flutterLoader = FlutterLoader() + } +} + +private const val TAG = "BackupWorker" diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/ContentObserverWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/ContentObserverWorker.kt new file mode 100644 index 0000000000..9cb2ec7779 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/ContentObserverWorker.kt @@ -0,0 +1,144 @@ +package app.alextran.immich + +import android.content.Context +import android.os.SystemClock +import android.provider.MediaStore +import android.util.Log +import androidx.work.Constraints +import androidx.work.Worker +import androidx.work.WorkerParameters +import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest +import androidx.work.WorkManager +import androidx.work.Operation +import java.util.concurrent.TimeUnit + +/** + * Worker executed by Android WorkManager observing content changes (new photos/videos) + * + * Immediately enqueues the BackupWorker when running. + * As this work is not triggered periodically, but on content change, the + * worker enqueues itself again after each run. + */ +class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { + + override fun doWork(): Result { + if (!isEnabled(applicationContext)) { + return Result.failure() + } + if (triggeredContentUris.size > 0) { + startBackupWorker(applicationContext, delayMilliseconds = 0) + } + enqueueObserverWorker(applicationContext, ExistingWorkPolicy.REPLACE) + return Result.success() + } + + companion object { + const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled" + private const val SHARED_PREF_REQUIRE_WIFI = "requireWifi" + private const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging" + private const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay" + private const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay" + + private const val TASK_NAME_OBSERVER = "immich/ContentObserver" + + /** + * Enqueues the `ContentObserverWorker`. + * + * @param context Android Context + */ + fun enable(context: Context, immediate: Boolean = false) { + enqueueObserverWorker(context, ExistingWorkPolicy.KEEP) + Log.d(TAG, "enabled ContentObserverWorker") + if (immediate) { + startBackupWorker(context, delayMilliseconds = 5000) + } + } + + /** + * Configures the `BackupWorker` to run when all constraints are met. + * + * @param context Android Context + * @param requireWifi if true, task only runs if connected to wifi + * @param requireCharging if true, task only runs if device is charging + */ + fun configureWork(context: Context, + requireWifi: Boolean = false, + requireCharging: Boolean = false, + triggerUpdateDelay: Long = 5000, + triggerMaxDelay: Long = 50000) { + context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit() + .putBoolean(SHARED_PREF_SERVICE_ENABLED, true) + .putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi) + .putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging) + .putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay) + .putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay) + .apply() + BackupWorker.updateBackupWorker(context, requireWifi, requireCharging) + } + + /** + * Stops the currently running worker (if any) and removes it from the work queue + */ + fun disable(context: Context) { + context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + .edit().putBoolean(SHARED_PREF_SERVICE_ENABLED, false).apply() + WorkManager.getInstance(context).cancelUniqueWork(TASK_NAME_OBSERVER) + Log.d(TAG, "disabled ContentObserverWorker") + } + + /** + * Return true if the user has enabled the background backup service + */ + fun isEnabled(ctx: Context): Boolean { + return ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + .getBoolean(SHARED_PREF_SERVICE_ENABLED, false) + } + + /** + * Enqueue and replace the worker without the content trigger but with a short delay + */ + fun workManagerAppClearedWorkaround(context: Context) { + val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java) + .setInitialDelay(500, TimeUnit.MILLISECONDS) + .build() + WorkManager + .getInstance(context) + .enqueueUniqueWork(TASK_NAME_OBSERVER, ExistingWorkPolicy.REPLACE, work) + .result + .get() + Log.d(TAG, "workManagerAppClearedWorkaround") + } + + private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) { + val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + val constraints = Constraints.Builder() + .addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true) + .addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true) + .addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true) + .addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true) + .setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS) + .setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS) + .build() + + val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java) + .setConstraints(constraints) + .build() + WorkManager.getInstance(context).enqueueUniqueWork(TASK_NAME_OBSERVER, policy, work) + } + + fun startBackupWorker(context: Context, delayMilliseconds: Long) { + val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE) + if (!sp.getBoolean(SHARED_PREF_SERVICE_ENABLED, false)) + return + val requireWifi = sp.getBoolean(SHARED_PREF_REQUIRE_WIFI, true) + val requireCharging = sp.getBoolean(SHARED_PREF_REQUIRE_CHARGING, false) + BackupWorker.enqueueBackupWorker(context, requireWifi, requireCharging, delayMilliseconds) + sp.edit().putLong(BackupWorker.SHARED_PREF_LAST_CHANGE, SystemClock.uptimeMillis()).apply() + } + + } +} + +private const val TAG = "ContentObserverWorker" diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt index 65249ec901..4474c63e09 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/ImmichApp.kt @@ -3,21 +3,28 @@ package app.alextran.immich import android.app.Application import android.os.Handler import android.os.Looper +import androidx.work.Configuration +import androidx.work.WorkManager import app.alextran.immich.background.BackgroundEngineLock import app.alextran.immich.background.BackgroundWorkerApiImpl class ImmichApp : Application() { override fun onCreate() { super.onCreate() + val config = Configuration.Builder().build() + WorkManager.initialize(this, config) + // always start BackupWorker after WorkManager init; this fixes the following bug: // After the process is killed (by user or system), the first trigger (taking a new picture) is lost. // Thus, the BackupWorker is not started. If the system kills the process after each initialization // (because of low memory etc.), the backup is never performed. // As a workaround, we also run a backup check when initializing the application + + ContentObserverWorker.startBackupWorker(context = this, delayMilliseconds = 0) Handler(Looper.getMainLooper()).postDelayed({ // We can only check the engine count and not the status of the lock here, // as the previous start might have been killed without unlocking. if (BackgroundEngineLock.connectEngines > 0) return@postDelayed BackgroundWorkerApiImpl.enqueueBackgroundWorker(this) - }, 15000) + }, 5000) } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt index fc9ab28fa2..06649de8f0 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/MainActivity.kt @@ -1,7 +1,6 @@ package app.alextran.immich import android.content.Context -import android.content.Intent import android.os.Build import android.os.ext.SdkExtensions import app.alextran.immich.background.BackgroundEngineLock @@ -18,12 +17,9 @@ import app.alextran.immich.images.LocalImageApi import app.alextran.immich.images.LocalImagesImpl import app.alextran.immich.images.RemoteImageApi import app.alextran.immich.images.RemoteImagesImpl -import app.alextran.immich.permission.PermissionApi -import app.alextran.immich.permission.PermissionApiImpl import app.alextran.immich.sync.NativeSyncApi import app.alextran.immich.sync.NativeSyncApiImpl26 import app.alextran.immich.sync.NativeSyncApiImpl30 -import app.alextran.immich.viewintent.ViewIntentPlugin import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine @@ -33,11 +29,6 @@ class MainActivity : FlutterFragmentActivity() { registerPlugins(this, flutterEngine) } - override fun onNewIntent(intent: Intent) { - super.onNewIntent(intent) - setIntent(intent) - } - companion object { fun registerPlugins(ctx: Context, flutterEngine: FlutterEngine) { HttpClientManager.initialize(ctx) @@ -53,19 +44,16 @@ class MainActivity : FlutterFragmentActivity() { } else { NativeSyncApiImpl30(ctx) } - val permissionApiImpl = PermissionApiImpl(ctx) NativeSyncApi.setUp(messenger, nativeSyncApiImpl) - PermissionApi.setUp(messenger, permissionApiImpl) LocalImageApi.setUp(messenger, LocalImagesImpl(ctx)) RemoteImageApi.setUp(messenger, RemoteImagesImpl(ctx)) BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx)) ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx)) - flutterEngine.plugins.add(ViewIntentPlugin()) + flutterEngine.plugins.add(BackgroundServicePlugin()) flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(nativeSyncApiImpl) - flutterEngine.plugins.add(permissionApiImpl) } fun cancelPlugins(flutterEngine: FlutterEngine) { @@ -73,8 +61,6 @@ class MainActivity : FlutterFragmentActivity() { flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin? ?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin? nativeApi?.detachFromEngine() - val permissionApi = flutterEngine.plugins.get(PermissionApiImpl::class.java) as ImmichPlugin? - permissionApi?.detachFromEngine() } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt deleted file mode 100644 index 68feaa2d14..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/NativeImage.kt +++ /dev/null @@ -1,29 +0,0 @@ -package app.alextran.immich - -import android.graphics.Bitmap - -object NativeImage { - init { - // rotate() is compiled into the native_buffer shared lib (which already links jnigraphics). - System.loadLibrary("native_buffer") - } - - /** - * Rotates an RGBA_8888 [bitmap] to the given EXIF [orientation], writing the result into a freshly - * malloc'd native buffer. Returns the buffer address (free it with [NativeBuffer.free]) and fills - * [outInfo] with {width, height, rowBytes}. Returns 0 when the bitmap can't be handled (e.g. a - * non-8888 config) so the caller can fall back. - */ - @JvmStatic - external fun rotate(bitmap: Bitmap, orientation: Int, outInfo: IntArray): Long - - /** - * Converts an RGBA_1010102 [bitmap] (what a 10-bit HEIC/AVIF decodes to on API 33+) to RGBA_8888, - * writing the result into a freshly malloc'd native buffer in one pass, with no intermediate - * ARGB_8888 bitmap. Returns the buffer address (free it with [NativeBuffer.free]) and fills - * [outInfo] with {width, height, rowBytes}. Returns 0 when the bitmap isn't RGBA_1010102 so the - * caller can fall back to a Skia copy. - */ - @JvmStatic - external fun convert1010102(bitmap: Bitmap, outInfo: IntArray): Long -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt index bcd7eeee18..b11b53bcde 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundEngineLock.kt @@ -43,8 +43,8 @@ class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, ImmichPl override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { super.onAttachedToEngine(binding) - engineCount.incrementAndGet() checkAndEnforceBackgroundLock(binding.applicationContext) + engineCount.incrementAndGet() Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount") } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt new file mode 100644 index 0000000000..b6b387db03 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt @@ -0,0 +1,332 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.background + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object BackgroundWorkerPigeonUtils { + + fun createConnectionError(channelName: String): FlutterError { + return FlutterError("channel-error", "Unable to establish connection on channel: '$channelName'.", "") } + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class BackgroundWorkerSettings ( + val requiresCharging: Boolean, + val minimumDelaySeconds: Long +) + { + companion object { + fun fromList(pigeonVar_list: List): BackgroundWorkerSettings { + val requiresCharging = pigeonVar_list[0] as Boolean + val minimumDelaySeconds = pigeonVar_list[1] as Long + return BackgroundWorkerSettings(requiresCharging, minimumDelaySeconds) + } + } + fun toList(): List { + return listOf( + requiresCharging, + minimumDelaySeconds, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is BackgroundWorkerSettings) { + return false + } + if (this === other) { + return true + } + return BackgroundWorkerPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class BackgroundWorkerPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + BackgroundWorkerSettings.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is BackgroundWorkerSettings -> { + stream.write(129) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface BackgroundWorkerFgHostApi { + fun enable() + fun saveNotificationMessage(title: String, body: String) + fun configure(settings: BackgroundWorkerSettings) + fun disable() + + companion object { + /** The codec used by BackgroundWorkerFgHostApi. */ + val codec: MessageCodec by lazy { + BackgroundWorkerPigeonCodec() + } + /** Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.enable() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val titleArg = args[0] as String + val bodyArg = args[1] as String + val wrapped: List = try { + api.saveNotificationMessage(titleArg, bodyArg) + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val settingsArg = args[0] as BackgroundWorkerSettings + val wrapped: List = try { + api.configure(settingsArg) + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.disable() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface BackgroundWorkerBgHostApi { + fun onInitialized() + fun close() + + companion object { + /** The codec used by BackgroundWorkerBgHostApi. */ + val codec: MessageCodec by lazy { + BackgroundWorkerPigeonCodec() + } + /** Sets up an instance of `BackgroundWorkerBgHostApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerBgHostApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.onInitialized$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.onInitialized() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.close() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} +/** Generated class from Pigeon that represents Flutter messages that can be called from Kotlin. */ +class BackgroundWorkerFlutterApi(private val binaryMessenger: BinaryMessenger, private val messageChannelSuffix: String = "") { + companion object { + /** The codec used by BackgroundWorkerFlutterApi. */ + val codec: MessageCodec by lazy { + BackgroundWorkerPigeonCodec() + } + } + fun onIosUpload(isRefreshArg: Boolean, maxSecondsArg: Long?, callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(listOf(isRefreshArg, maxSecondsArg)) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName))) + } + } + } + fun onAndroidUpload(callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(null) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName))) + } + } + } + fun cancel(callback: (Result) -> Unit) +{ + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val channelName = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.cancel$separatedMessageChannelSuffix" + val channel = BasicMessageChannel(binaryMessenger, channelName, codec) + channel.send(null) { + if (it is List<*>) { + if (it.size > 1) { + callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?))) + } else { + callback(Result.success(Unit)) + } + } else { + callback(Result.failure(BackgroundWorkerPigeonUtils.createConnectionError(channelName))) + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt index 60387f434a..7dce1f6edf 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.kt @@ -15,7 +15,6 @@ import androidx.work.ListenableWorker import androidx.work.WorkerParameters import app.alextran.immich.MainActivity import app.alextran.immich.R -import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture import io.flutter.FlutterInjector @@ -62,11 +61,6 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : } override fun startWork(): ListenableFuture { - if (BackgroundWorkerPreferences(ctx).isLocked() && BackgroundEngineLock.connectEngines > 0) { - Log.i(TAG, "Foreground engine active, skipping background worker") - return Futures.immediateFuture(Result.success()) - } - Log.i(TAG, "Starting background upload worker") if (!loader.initialized()) { @@ -75,7 +69,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : val notificationChannel = NotificationChannel( NOTIFICATION_CHANNEL_ID, - ctx.getString(R.string.background_worker_notification_channel_name), + NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW ) notificationManager.createNotificationChannel(notificationChannel) @@ -83,10 +77,6 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : showNotification(notificationConfig.first, notificationConfig.second) loader.ensureInitializationCompleteAsync(ctx, null, Handler(Looper.getMainLooper())) { - if (isStopped || isComplete) { - return@ensureInitializationCompleteAsync - } - engine = FlutterEngine(ctx) FlutterEngineCache.getInstance().put(BackgroundWorkerApiImpl.ENGINE_CACHE_KEY, engine!!) @@ -117,7 +107,7 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : * This method acts as a bridge between the native Android background task system and Flutter. */ override fun onInitialized() { - flutterApi?.onAndroidUpload(maxMinutesArg = 20) { handleHostResult(it) } + flutterApi?.onAndroidUpload { handleHostResult(it) } } // TODO: Move this to a separate NotificationManager class @@ -153,17 +143,11 @@ class BackgroundWorker(context: Context, params: WorkerParameters) : return } - val api = flutterApi - if (api == null) { - Handler(Looper.getMainLooper()).postAtFrontOfQueue { - complete(Result.failure()) - } - return - } - Handler(Looper.getMainLooper()).postAtFrontOfQueue { - api.cancel { - complete(Result.failure()) + if (flutterApi != null) { + flutterApi?.cancel { + complete(Result.failure()) + } } } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt index bc0766bee5..a78db3c5ea 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerApiImpl.kt @@ -5,10 +5,8 @@ import android.provider.MediaStore import android.util.Log import androidx.work.BackoffPolicy import androidx.work.Constraints -import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy -import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.PeriodicWorkRequestBuilder +import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import io.flutter.embedding.engine.FlutterEngineCache import java.util.concurrent.TimeUnit @@ -20,7 +18,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { override fun enable() { enqueueMediaObserver(ctx) - enqueuePeriodicWorker(ctx) } override fun saveNotificationMessage(title: String, body: String) { @@ -30,14 +27,12 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { override fun configure(settings: BackgroundWorkerSettings) { BackgroundWorkerPreferences(ctx).updateSettings(settings) enqueueMediaObserver(ctx) - enqueuePeriodicWorker(ctx) } override fun disable() { WorkManager.getInstance(ctx).apply { cancelUniqueWork(OBSERVER_WORKER_NAME) cancelUniqueWork(BACKGROUND_WORKER_NAME) - cancelUniqueWork(PERIODIC_WORKER_NAME) } Log.i(TAG, "Cancelled background upload tasks") } @@ -45,7 +40,6 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { companion object { private const val BACKGROUND_WORKER_NAME = "immich/BackgroundWorkerV1" private const val OBSERVER_WORKER_NAME = "immich/MediaObserverV1" - private const val PERIODIC_WORKER_NAME = "immich/PeriodicBackgroundWorkerV1" const val ENGINE_CACHE_KEY = "immich::background_worker::engine" @@ -61,7 +55,7 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { setRequiresCharging(settings.requiresCharging) }.build() - val work = OneTimeWorkRequestBuilder() + val work = OneTimeWorkRequest.Builder(MediaObserver::class.java) .setConstraints(constraints) .build() WorkManager.getInstance(ctx) @@ -73,30 +67,10 @@ class BackgroundWorkerApiImpl(context: Context) : BackgroundWorkerFgHostApi { ) } - fun enqueuePeriodicWorker(ctx: Context) { - val settings = BackgroundWorkerPreferences(ctx).getSettings() - val constraints = Constraints.Builder().apply { - setRequiresCharging(settings.requiresCharging) - }.build() - - val work = - PeriodicWorkRequestBuilder( - 1, - TimeUnit.HOURS, - 15, - TimeUnit.MINUTES - ).setConstraints(constraints) - .build() - - WorkManager.getInstance(ctx) - .enqueueUniquePeriodicWork(PERIODIC_WORKER_NAME, ExistingPeriodicWorkPolicy.UPDATE, work) - - Log.i(TAG, "Enqueued periodic background worker with name: $PERIODIC_WORKER_NAME") - } - fun enqueueBackgroundWorker(ctx: Context) { val constraints = Constraints.Builder().setRequiresBatteryNotLow(true).build() - val work = OneTimeWorkRequestBuilder() + + val work = OneTimeWorkRequest.Builder(BackgroundWorker::class.java) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES) .build() diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt new file mode 100644 index 0000000000..d7353f0462 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt @@ -0,0 +1,95 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.background + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object BackgroundWorkerLockPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} +private open class BackgroundWorkerLockPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface BackgroundWorkerLockApi { + fun lock() + fun unlock() + + companion object { + /** The codec used by BackgroundWorkerLockApi. */ + val codec: MessageCodec by lazy { + BackgroundWorkerLockPigeonCodec() + } + /** Sets up an instance of `BackgroundWorkerLockApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: BackgroundWorkerLockApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.lock$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.lock() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerLockPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.unlock$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.unlock() + listOf(null) + } catch (exception: Throwable) { + BackgroundWorkerLockPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/PeriodicWorker.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/background/PeriodicWorker.kt deleted file mode 100644 index d4ecde9bbb..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/background/PeriodicWorker.kt +++ /dev/null @@ -1,16 +0,0 @@ -package app.alextran.immich.background - -import android.content.Context -import android.util.Log -import androidx.work.Worker -import androidx.work.WorkerParameters - -class PeriodicWorker(context: Context, params: WorkerParameters) : Worker(context, params) { - private val ctx: Context = context.applicationContext - - override fun doWork(): Result { - Log.i("PeriodicWorker", "Periodic worker triggered, starting background worker") - BackgroundWorkerApiImpl.enqueueBackgroundWorker(ctx) - return Result.success() - } -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt new file mode 100644 index 0000000000..629071382a --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt @@ -0,0 +1,116 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.connectivity + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object ConnectivityPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +enum class NetworkCapability(val raw: Int) { + CELLULAR(0), + WIFI(1), + VPN(2), + UNMETERED(3); + + companion object { + fun ofRaw(raw: Int): NetworkCapability? { + return values().firstOrNull { it.raw == raw } + } + } +} +private open class ConnectivityPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + NetworkCapability.ofRaw(it.toInt()) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is NetworkCapability -> { + stream.write(129) + writeValue(stream, value.raw) + } + else -> super.writeValue(stream, value) + } + } +} + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface ConnectivityApi { + fun getCapabilities(): List + + companion object { + /** The codec used by ConnectivityApi. */ + val codec: MessageCodec by lazy { + ConnectivityPigeonCodec() + } + /** Sets up an instance of `ConnectivityApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: ConnectivityApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getCapabilities()) + } catch (exception: Throwable) { + ConnectivityPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt new file mode 100644 index 0000000000..869e312515 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/Network.g.kt @@ -0,0 +1,305 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.core + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object NetworkPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ClientCertData ( + val data: ByteArray, + val password: String +) + { + companion object { + fun fromList(pigeonVar_list: List): ClientCertData { + val data = pigeonVar_list[0] as ByteArray + val password = pigeonVar_list[1] as String + return ClientCertData(data, password) + } + } + fun toList(): List { + return listOf( + data, + password, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is ClientCertData) { + return false + } + if (this === other) { + return true + } + return NetworkPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class ClientCertPrompt ( + val title: String, + val message: String, + val cancel: String, + val confirm: String +) + { + companion object { + fun fromList(pigeonVar_list: List): ClientCertPrompt { + val title = pigeonVar_list[0] as String + val message = pigeonVar_list[1] as String + val cancel = pigeonVar_list[2] as String + val confirm = pigeonVar_list[3] as String + return ClientCertPrompt(title, message, cancel, confirm) + } + } + fun toList(): List { + return listOf( + title, + message, + cancel, + confirm, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is ClientCertPrompt) { + return false + } + if (this === other) { + return true + } + return NetworkPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class NetworkPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as? List)?.let { + ClientCertData.fromList(it) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + ClientCertPrompt.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is ClientCertData -> { + stream.write(129) + writeValue(stream, value.toList()) + } + is ClientCertPrompt -> { + stream.write(130) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface NetworkApi { + fun addCertificate(clientData: ClientCertData, callback: (Result) -> Unit) + fun selectCertificate(promptText: ClientCertPrompt, callback: (Result) -> Unit) + fun removeCertificate(callback: (Result) -> Unit) + fun hasCertificate(): Boolean + fun getClientPointer(): Long + fun setRequestHeaders(headers: Map, serverUrls: List, token: String?) + + companion object { + /** The codec used by NetworkApi. */ + val codec: MessageCodec by lazy { + NetworkPigeonCodec() + } + /** Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val clientDataArg = args[0] as ClientCertData + api.addCertificate(clientDataArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(NetworkPigeonUtils.wrapError(error)) + } else { + reply.reply(NetworkPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val promptTextArg = args[0] as ClientCertPrompt + api.selectCertificate(promptTextArg) { result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(NetworkPigeonUtils.wrapError(error)) + } else { + reply.reply(NetworkPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.removeCertificate{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(NetworkPigeonUtils.wrapError(error)) + } else { + reply.reply(NetworkPigeonUtils.wrapResult(null)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.hasCertificate$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.hasCertificate()) + } catch (exception: Throwable) { + NetworkPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.getClientPointer$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getClientPointer()) + } catch (exception: Throwable) { + NetworkPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val headersArg = args[0] as Map + val serverUrlsArg = args[1] as List + val tokenArg = args[2] as String? + val wrapped: List = try { + api.setRequestHeaders(headersArg, serverUrlsArg, tokenArg) + listOf(null) + } catch (exception: Throwable) { + NetworkPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt index 4479ec7701..85b7a6c730 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/core/NetworkApiPlugin.kt @@ -13,7 +13,7 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { private var networkApi: NetworkApiImpl? = null override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - networkApi = NetworkApiImpl(binding.applicationContext) + networkApi = NetworkApiImpl() NetworkApi.setUp(binding.binaryMessenger, networkApi) } @@ -39,11 +39,9 @@ class NetworkApiPlugin : FlutterPlugin, ActivityAware { } } -private class NetworkApiImpl(private val context: Context) : NetworkApi { +private class NetworkApiImpl : NetworkApi { var activity: Activity? = null - override fun getAppGroupId(): String = context.packageName - override fun addCertificate(clientData: ClientCertData, callback: (Result) -> Unit) { try { HttpClientManager.setKeyEntry(clientData.data, clientData.password.toCharArray()) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt new file mode 100644 index 0000000000..7d998c2f48 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt @@ -0,0 +1,140 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.images + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object LocalImagesPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() +private open class LocalImagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface LocalImageApi { + fun requestImage(assetId: String, requestId: Long, width: Long, height: Long, isVideo: Boolean, preferEncoded: Boolean, callback: (Result?>) -> Unit) + fun cancelRequest(requestId: Long) + fun getThumbhash(thumbhash: String, callback: (Result>) -> Unit) + + companion object { + /** The codec used by LocalImageApi. */ + val codec: MessageCodec by lazy { + LocalImagesPigeonCodec() + } + /** Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val assetIdArg = args[0] as String + val requestIdArg = args[1] as Long + val widthArg = args[2] as Long + val heightArg = args[3] as Long + val isVideoArg = args[4] as Boolean + val preferEncodedArg = args[5] as Boolean + api.requestImage(assetIdArg, requestIdArg, widthArg, heightArg, isVideoArg, preferEncodedArg) { result: Result?> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(LocalImagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(LocalImagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val requestIdArg = args[0] as Long + val wrapped: List = try { + api.cancelRequest(requestIdArg) + listOf(null) + } catch (exception: Throwable) { + LocalImagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val thumbhashArg = args[0] as String + api.getThumbhash(thumbhashArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(LocalImagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(LocalImagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt index af953612cf..3babad2e37 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/LocalImagesImpl.kt @@ -12,9 +12,7 @@ import android.provider.MediaStore.Images import android.provider.MediaStore.Video import android.util.Size import androidx.annotation.RequiresApi -import androidx.exifinterface.media.ExifInterface import app.alextran.immich.NativeBuffer -import app.alextran.immich.NativeImage import kotlin.math.* import java.io.IOException import java.util.concurrent.Executors @@ -33,28 +31,12 @@ data class Request( val callback: (Result?>) -> Unit ) -/** Set [exactSize] to decode at the smallest size that covers [target] without upscaling. */ @RequiresApi(Build.VERSION_CODES.Q) -inline fun ImageDecoder.Source.decodeBitmap(target: Size = Size(0, 0), exactSize: Boolean = false): Bitmap { +inline fun ImageDecoder.Source.decodeBitmap(target: Size = Size(0, 0)): Bitmap { return ImageDecoder.decodeBitmap(this) { decoder, info, _ -> if (target.width > 0 && target.height > 0) { - if (exactSize) { - val fillScale = max( - target.width.toDouble() / info.size.width, - target.height.toDouble() / info.size.height - ) - val scale = min(1.0, fillScale) - if (scale < 1) { - val width = ceil(info.size.width * scale).toInt() - val height = ceil(info.size.height * scale).toInt() - if (width > 0 && height > 0) { - decoder.setTargetSize(width, height) - } - } - } else { - val sample = max(1, min(info.size.width / target.width, info.size.height / target.height)) - decoder.setTargetSampleSize(sample) - } + val sample = max(1, min(info.size.width / target.width, info.size.height / target.height)) + decoder.setTargetSampleSize(sample) } decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE decoder.setTargetColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)) @@ -62,41 +44,22 @@ inline fun ImageDecoder.Source.decodeBitmap(target: Size = Size(0, 0), exactSize } fun Bitmap.toNativeBuffer(): Map { - // Dart reads the buffer as rgba8888, but 10-bit sources decode to RGBA_1010102, which garbles - // colors when copied verbatim. Convert those straight into the output buffer in native code - - // one pass, no intermediate ARGB_8888 bitmap. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && config == Bitmap.Config.RGBA_1010102) { - val info = IntArray(3) - val pointer = NativeImage.convert1010102(this, info) - if (pointer != 0L) { - recycle() - return mapOf( - "pointer" to pointer, - "width" to info[0].toLong(), - "height" to info[1].toLong(), - "rowBytes" to info[2].toLong() - ) - } - // native convert declined (OOM/lock) -> fall through to the Skia copy path below. - } - // Other non-8888 configs (e.g. HDR F16) still need Skia's convert; 8-bit is copied as-is. - val bitmap = toArgb8888() - val size = bitmap.width * bitmap.height * 4 + val size = width * height * 4 val pointer = NativeBuffer.allocate(size) try { val buffer = NativeBuffer.wrap(pointer, size) - bitmap.copyPixelsToBuffer(buffer) + copyPixelsToBuffer(buffer) return mapOf( "pointer" to pointer, - "width" to bitmap.width.toLong(), - "height" to bitmap.height.toLong(), - "rowBytes" to (bitmap.width * 4).toLong() + "width" to width.toLong(), + "height" to height.toLong(), + "rowBytes" to (width * 4).toLong() ) - } catch (e: Throwable) { + } catch (e: Exception) { NativeBuffer.free(pointer) throw e } finally { - bitmap.recycle() + recycle() } } @@ -218,56 +181,35 @@ class LocalImagesImpl(context: Context) : LocalImageApi { val id = assetId.toLong() signal.throwIfCanceled() + val bitmap = if (isVideo) { + decodeVideoThumbnail(id, size, signal) + } else { + decodeImage(id, size, signal) + } + try { - val res = if (isVideo) { - decodeVideoThumbnail(id, size, signal).toNativeBuffer() - } else { - val (bitmap, orientation) = decodeImage(id, size, signal) - signal.throwIfCanceled() - if (orientation == ExifInterface.ORIENTATION_NORMAL || orientation == ExifInterface.ORIENTATION_UNDEFINED) { - bitmap.toNativeBuffer() - } else { - rotateToNativeBuffer(bitmap, orientation) - } - } - // Don't re-check cancellation here: res owns a malloc'd buffer, and bailing to CANCELLED would - // orphan it. Deliver it; Dart frees the buffer itself if the request was cancelled meanwhile. + signal.throwIfCanceled() + val res = bitmap.toNativeBuffer() + signal.throwIfCanceled() callback(Result.success(res)) } catch (e: Exception) { callback(if (e is OperationCanceledException) CANCELLED else Result.failure(e)) } } - // Returns the decoded bitmap plus the EXIF orientation that still needs applying. Only Q+ raw - // decodes come back unrotated (ImageDecoder / loadThumbnail skip EXIF for raw like DNG); every - // other path already orients itself, so it reports ORIENTATION_NORMAL. - private fun decodeImage(id: Long, size: Size, signal: CancellationSignal): Pair { + private fun decodeImage(id: Long, size: Size, signal: CancellationSignal): Bitmap { signal.throwIfCanceled() val uri = ContentUris.withAppendedId(Images.Media.EXTERNAL_CONTENT_URI, id) - val handleRaw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isRawMime(uri) - val orientation = if (handleRaw) rawOrientation(uri) else ExifInterface.ORIENTATION_NORMAL - if (size.width <= 0 || size.height <= 0 || size.width > 768 || size.height > 768) { - // A "load original" request is unsized -> a full-res decode (a sized > 768 just samples to target). - return decodeSource(uri, size, signal) to orientation + return decodeSource(uri, size, signal) } - val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { resolver.loadThumbnail(uri, size, signal) } else { signal.setOnCancelListener { Images.Thumbnails.cancelThumbnailRequest(resolver, id) } Images.Thumbnails.getThumbnail(resolver, id, Images.Thumbnails.MINI_KIND, OPTIONS) } - return bitmap to orientation - } - - private fun isRawMime(uri: Uri): Boolean { - val mime = resolver.getType(uri) ?: return false - return mime.startsWith("image/x-") || mime == "image/dng" - } - - private fun rawOrientation(uri: Uri): Int { - return resolver.openInputStream(uri)?.use { readOrientation(it) } ?: ExifInterface.ORIENTATION_NORMAL } private fun decodeVideoThumbnail(id: Long, target: Size, signal: CancellationSignal): Bitmap { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt deleted file mode 100644 index e91ff871b3..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt +++ /dev/null @@ -1,68 +0,0 @@ -package app.alextran.immich.images - -import android.graphics.Bitmap -import androidx.exifinterface.media.ExifInterface -import app.alextran.immich.NativeImage -import java.io.IOException -import java.io.InputStream -import java.nio.ByteBuffer -import java.nio.ByteOrder - -fun readOrientation(input: InputStream): Int = - ExifInterface(input).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - -// Reads the EXIF orientation (TIFF tag 0x0112) from a raw file's IFD0. -fun readRawOrientation(buffer: ByteBuffer, len: Int): Int { - try { - if (len < 8) return ExifInterface.ORIENTATION_NORMAL - buffer.order( - when (buffer.get(0).toInt() and 0xFF) { - 0x49 -> ByteOrder.LITTLE_ENDIAN - 0x4D -> ByteOrder.BIG_ENDIAN - else -> return ExifInterface.ORIENTATION_NORMAL - } - ) - val ifd0 = buffer.getInt(4).toLong() and 0xFFFFFFFFL - if (ifd0 < 8 || ifd0 + 2 > len) return ExifInterface.ORIENTATION_NORMAL - val start = ifd0.toInt() - val entries = buffer.getShort(start).toInt() and 0xFFFF - if (entries <= 0 || entries > 512) return ExifInterface.ORIENTATION_NORMAL - // Each IFD entry is 12 bytes: tag(2) type(2) count(4) value(4); orientation is a SHORT in value. - for (i in 0 until entries) { - val p = start + 2 + i * 12 - if (p + 12 > len) return ExifInterface.ORIENTATION_NORMAL - if ((buffer.getShort(p).toInt() and 0xFFFF) == 0x0112) { - val value = buffer.getShort(p + 8).toInt() and 0xFFFF - return if (value in 1..8) value else ExifInterface.ORIENTATION_NORMAL - } - } - return ExifInterface.ORIENTATION_NORMAL - } catch (_: Exception) { - return ExifInterface.ORIENTATION_NORMAL - } -} - -fun Bitmap.toArgb8888(): Bitmap { - if (config == Bitmap.Config.ARGB_8888) return this - val converted = copy(Bitmap.Config.ARGB_8888, false) - recycle() - return converted ?: throw IOException("could not convert bitmap to ARGB_8888") -} - -// Force ARGB_8888 first: the native rotate needs a lockable 8888 buffer and allocates w*h*4. -fun rotateToNativeBuffer(bitmap: Bitmap, orientation: Int): Map { - val src = bitmap.toArgb8888() - try { - val info = IntArray(3) - val pointer = NativeImage.rotate(src, orientation, info) - if (pointer == 0L) throw IOException("native rotate failed for orientation $orientation") - return mapOf( - "pointer" to pointer, - "width" to info[0].toLong(), - "height" to info[1].toLong(), - "rowBytes" to info[2].toLong() - ) - } finally { - if (!src.isRecycled) src.recycle() - } -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt new file mode 100644 index 0000000000..bef6418904 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImages.g.kt @@ -0,0 +1,123 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.images + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object RemoteImagesPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } +} +private open class RemoteImagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return super.readValueOfType(type, buffer) + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + super.writeValue(stream, value) + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface RemoteImageApi { + fun requestImage(url: String, requestId: Long, preferEncoded: Boolean, callback: (Result?>) -> Unit) + fun cancelRequest(requestId: Long) + fun clearCache(callback: (Result) -> Unit) + + companion object { + /** The codec used by RemoteImageApi. */ + val codec: MessageCodec by lazy { + RemoteImagesPigeonCodec() + } + /** Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val urlArg = args[0] as String + val requestIdArg = args[1] as Long + val preferEncodedArg = args[2] as Boolean + api.requestImage(urlArg, requestIdArg, preferEncodedArg) { result: Result?> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(RemoteImagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(RemoteImagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val requestIdArg = args[0] as Long + val wrapped: List = try { + api.cancelRequest(requestIdArg) + listOf(null) + } catch (exception: Throwable) { + RemoteImagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.RemoteImageApi.clearCache$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + api.clearCache{ result: Result -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(RemoteImagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(RemoteImagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt index cdc82c871c..9255eff44b 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RemoteImagesImpl.kt @@ -1,12 +1,8 @@ package app.alextran.immich.images import android.content.Context -import android.graphics.ImageDecoder -import android.os.Build import android.os.CancellationSignal import android.os.OperationCanceledException -import android.util.Size -import androidx.exifinterface.media.ExifInterface import app.alextran.immich.INITIAL_BUFFER_SIZE import app.alextran.immich.NativeBuffer import app.alextran.immich.NativeByteBuffer @@ -26,41 +22,9 @@ import java.io.File import java.io.IOException import java.nio.ByteBuffer import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.Executors - -private const val MAX_PREALLOC_BYTES = 128 * 1024 * 1024 private class RemoteRequest(val cancellationSignal: CancellationSignal) -// The full mimeTypes.raw set the server can serve (short + vendor forms). -private val RAW_MIME_TYPES = setOf( - "image/3fr", "image/ari", "image/arw", - "image/cap", "image/cin", "image/cr2", - "image/cr3", "image/crw", "image/dcr", - "image/dng", "image/erf", "image/fff", - "image/iiq", "image/k25", "image/kdc", - "image/mrw", "image/nef", "image/nrw", - "image/orf", "image/ori", "image/pef", - "image/psd", "image/raf", "image/raw", - "image/rw2", "image/rwl", "image/sr2", - "image/srf", "image/srw", "image/vnd.adobe.photoshop", - "image/x-adobe-dng", "image/x-arriflex-ari", "image/x-canon-cr2", - "image/x-canon-cr3", "image/x-canon-crw", "image/x-epson-erf", - "image/x-fuji-raf", "image/x-hasselblad-3fr", "image/x-hasselblad-fff", - "image/x-kodak-dcr", "image/x-kodak-k25", "image/x-kodak-kdc", - "image/x-leica-rwl", "image/x-minolta-mrw", "image/x-nikon-nef", - "image/x-nikon-nrw", "image/x-olympus-orf", "image/x-olympus-ori", - "image/x-panasonic-raw", "image/x-panasonic-rw2", "image/x-pentax-pef", - "image/x-phantom-cin", "image/x-phaseone-cap", "image/x-phaseone-iiq", - "image/x-samsung-srw", "image/x-sigma-x3f", "image/x-sony-arw", - "image/x-sony-sr2", "image/x-sony-srf", "image/x3f", -) - -private fun isRawMime(contentType: String?): Boolean { - val mime = contentType?.substringBefore(';')?.trim()?.lowercase() ?: return false - return mime in RAW_MIME_TYPES -} - class RemoteImagesImpl(context: Context) : RemoteImageApi { private val requestMap = ConcurrentHashMap() @@ -70,18 +34,12 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi { companion object { val CANCELLED = Result.success?>(null) - - // Shared, process-lifetime pool: RemoteImagesImpl is re-created per FlutterEngine, so a - // per-instance pool would leak threads across engine restarts. - private val decodeExecutor = Executors.newFixedThreadPool(2) } override fun requestImage( url: String, requestId: Long, - preferEncoded: Boolean, - width: Long?, - height: Long?, + @Suppress("UNUSED_PARAMETER") preferEncoded: Boolean, // always returns encoded; setting has no effect on Android callback: (Result?>) -> Unit ) { val signal = CancellationSignal() @@ -90,70 +48,13 @@ class RemoteImagesImpl(context: Context) : RemoteImageApi { ImageFetcherManager.fetch( url, signal, - onSuccess = { buffer, contentType -> + onSuccess = { buffer -> + requestMap.remove(requestId) if (signal.isCanceled) { - requestMap.remove(requestId) - buffer.free() + NativeBuffer.free(buffer.pointer) return@fetch callback(CANCELLED) } - // Decode natively when the caller wants pixels: Flutter's fallback decoder copies - // 10-bit bitmaps (RGBA_1010102) as if they were rgba8888, garbling colors. Decode on a - // dedicated pool - the fetch callback threads are shared with video streaming. On any - // decode failure (including OOM on huge originals), hand Flutter the encoded bytes as - // before. - if (!preferEncoded && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - decodeExecutor.execute { - val res = if (signal.isCanceled) null else try { - // The embedded preview a raw decodes to has no orientation, so read the container's. - val orientation = if (isRawMime(contentType)) { - readRawOrientation(NativeBuffer.wrap(buffer.pointer, buffer.offset), buffer.offset) - } else { - ExifInterface.ORIENTATION_NORMAL - } - val target = when (orientation) { - ExifInterface.ORIENTATION_TRANSPOSE, - ExifInterface.ORIENTATION_ROTATE_90, - ExifInterface.ORIENTATION_TRANSVERSE, - ExifInterface.ORIENTATION_ROTATE_270 -> Size(height?.toInt() ?: 0, width?.toInt() ?: 0) - else -> Size(width?.toInt() ?: 0, height?.toInt() ?: 0) - } - val source = ImageDecoder.createSource(NativeBuffer.wrap(buffer.pointer, buffer.offset)) - val bitmap = source.decodeBitmap(target, exactSize = true) - if (orientation == ExifInterface.ORIENTATION_NORMAL || orientation == ExifInterface.ORIENTATION_UNDEFINED) { - bitmap.toNativeBuffer() - } else { - rotateToNativeBuffer(bitmap, orientation) - } - } catch (_: Throwable) { - null - } - requestMap.remove(requestId) - when { - // Deliver even if the request was cancelled meanwhile: re-checking here would orphan - // res's malloc, and Dart frees the buffer itself when it sees the cancel. - res != null -> { - buffer.free() - callback(Result.success(res)) - } - signal.isCanceled -> { - buffer.free() - callback(CANCELLED) - } - else -> callback( - Result.success( - mapOf( - "pointer" to buffer.pointer, - "length" to buffer.offset.toLong() - ) - ) - ) - } - } - return@fetch - } - - requestMap.remove(requestId) callback( Result.success( mapOf( @@ -205,7 +106,7 @@ private object ImageFetcherManager { fun fetch( url: String, signal: CancellationSignal, - onSuccess: (NativeByteBuffer, String?) -> Unit, + onSuccess: (NativeByteBuffer) -> Unit, onFailure: (Exception) -> Unit, ) { fetcher.fetch(url, signal, onSuccess, onFailure) @@ -236,7 +137,7 @@ private sealed interface ImageFetcher { fun fetch( url: String, signal: CancellationSignal, - onSuccess: (NativeByteBuffer, String?) -> Unit, + onSuccess: (NativeByteBuffer) -> Unit, onFailure: (Exception) -> Unit, ) @@ -254,7 +155,7 @@ private class CronetImageFetcher : ImageFetcher { override fun fetch( url: String, signal: CancellationSignal, - onSuccess: (NativeByteBuffer, String?) -> Unit, + onSuccess: (NativeByteBuffer) -> Unit, onFailure: (Exception) -> Unit, ) { synchronized(stateLock) { @@ -322,11 +223,12 @@ private class CronetImageFetcher : ImageFetcher { } private class FetchCallback( - private val onSuccess: (NativeByteBuffer, String?) -> Unit, + private val onSuccess: (NativeByteBuffer) -> Unit, private val onFailure: (Exception) -> Unit, private val onComplete: () -> Unit, ) : UrlRequest.Callback() { private var buffer: NativeByteBuffer? = null + private var wrapped: ByteBuffer? = null private var error: Exception? = null override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newUrl: String) { @@ -340,16 +242,15 @@ private class CronetImageFetcher : ImageFetcher { } try { - // Content-Length is a size hint only. With Content-Encoding (gzip/br/...), - // Cronet auto-decompresses and writes decompressed bytes to our buffer, which - // may exceed the wire/compressed Content-Length. Always use the growable - // buffer path so we can't overflow. val contentLength = info.allHeaders["content-length"]?.firstOrNull()?.toIntOrNull() ?: 0 - // Cap the up-front alloc: Content-Length is untrusted and can be huge or near - // Int.MAX_VALUE (overflowing `+1`). For larger responses the grow path takes over. - val initialSize = if (contentLength in 1..MAX_PREALLOC_BYTES) contentLength + 1 else INITIAL_BUFFER_SIZE - buffer = NativeByteBuffer(initialSize) - request.read(buffer!!.wrapRemaining()) + if (contentLength > 0) { + buffer = NativeByteBuffer(contentLength + 1) + wrapped = NativeBuffer.wrap(buffer!!.pointer, contentLength + 1) + request.read(wrapped) + } else { + buffer = NativeByteBuffer(INITIAL_BUFFER_SIZE) + request.read(buffer!!.wrapRemaining()) + } } catch (e: Exception) { error = e return request.cancel() @@ -362,14 +263,14 @@ private class CronetImageFetcher : ImageFetcher { byteBuffer: ByteBuffer ) { try { - // Always pass a fresh wrap so byteBuffer.position() represents only the - // bytes Cronet wrote in this iteration. Reusing the caller-supplied - // ByteBuffer breaks advance(): Cronet's position keeps accumulating - // across reads, which would double-count previous iterations' bytes. - val buf = buffer!!.run { - advance(byteBuffer.position()) - ensureHeadroom() - wrapRemaining() + val buf = if (wrapped == null) { + buffer!!.run { + advance(byteBuffer.position()) + ensureHeadroom() + wrapRemaining() + } + } else { + wrapped } request.read(buf) } catch (e: Exception) { @@ -379,9 +280,8 @@ private class CronetImageFetcher : ImageFetcher { } override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) { - val contentType = info.allHeaders.entries - .firstOrNull { it.key.equals("content-type", ignoreCase = true) }?.value?.firstOrNull() - onSuccess(buffer!!, contentType) + wrapped?.let { buffer!!.advance(it.position()) } + onSuccess(buffer!!) onComplete() } @@ -432,7 +332,7 @@ private class OkHttpImageFetcher private constructor( override fun fetch( url: String, signal: CancellationSignal, - onSuccess: (NativeByteBuffer, String?) -> Unit, + onSuccess: (NativeByteBuffer) -> Unit, onFailure: (Exception) -> Unit, ) { synchronized(stateLock) { @@ -486,7 +386,7 @@ private class OkHttpImageFetcher private constructor( buffer.ensureHeadroom() } } - onSuccess(buffer, response.header("Content-Type")) + onSuccess(buffer) } catch (e: Exception) { buffer.free() onFailure(e) diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt deleted file mode 100644 index ddabfbabd8..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/ManageMediaPermissionDelegate.kt +++ /dev/null @@ -1,96 +0,0 @@ -package app.alextran.immich.permission - -import android.content.Context -import android.content.Intent -import android.os.Build -import android.provider.MediaStore -import android.provider.Settings -import androidx.core.net.toUri -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.PluginRegistry - -class ManageMediaPermissionDelegate( - context: Context, - private val requestCode: Int = 1003, -) : PluginRegistry.ActivityResultListener { - private val ctx = context.applicationContext - private var activityBinding: ActivityPluginBinding? = null - private var pendingResult: ((Result) -> Unit)? = null - - fun hasManageMediaPermission(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaStore.canManageMedia(ctx) - } else { - false - } - } - - fun requestManageMediaPermission(callback: (Result) -> Unit) { - if (hasManageMediaPermission()) { - callback(Result.success(true)) - return - } - - openManageMediaPermissionSettings(callback) - } - - fun manageMediaPermission(callback: (Result) -> Unit) { - openManageMediaPermissionSettings(callback) - } - - private fun openManageMediaPermissionSettings(callback: (Result) -> Unit) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { - callback(Result.success(false)) - return - } - - val activity = activityBinding?.activity - if (activity == null) { - callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) - return - } - - pendingResult = callback - val intent = Intent(Settings.ACTION_REQUEST_MANAGE_MEDIA).apply { - data = "package:${activity.packageName}".toUri() - } - try { - activity.startActivityForResult(intent, requestCode) - } catch (e: Exception) { - pendingResult = null - callback( - Result.failure( - FlutterError("ACTIVITY_LAUNCH_FAILED", "Failed to launch MANAGE_MEDIA settings", e.toString()) - ) - ) - } - } - - fun onAttachedToActivity(binding: ActivityPluginBinding) { - activityBinding = binding - binding.addActivityResultListener(this) - } - - fun onDetachedFromActivity() { - failPending("ACTIVITY_DETACHED", "Activity detached before MANAGE_MEDIA result") - activityBinding?.removeActivityResultListener(this) - activityBinding = null - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { - if (requestCode == this.requestCode) { - val callback = pendingResult - pendingResult = null - callback?.invoke(Result.success(hasManageMediaPermission())) - return true - } - - return false - } - - private fun failPending(code: String, message: String) { - val callback = pendingResult ?: return - pendingResult = null - callback(Result.failure(FlutterError(code, message, null))) - } -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt deleted file mode 100644 index 4e4ce7b424..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt +++ /dev/null @@ -1,50 +0,0 @@ -package app.alextran.immich.permission - -import android.content.Context -import android.os.PowerManager -import app.alextran.immich.core.ImmichPlugin -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding - -class PermissionApiImpl(context: Context) : ImmichPlugin(), PermissionApi, ActivityAware { - private val ctx: Context = context.applicationContext - private val manageMediaPermissionDelegate = ManageMediaPermissionDelegate(context) - - private val powerManager = - ctx.getSystemService(Context.POWER_SERVICE) as PowerManager - - - override fun isIgnoringBatteryOptimizations(): PermissionStatus { - if (powerManager.isIgnoringBatteryOptimizations(ctx.packageName)) { - return PermissionStatus.GRANTED - } - return PermissionStatus.DENIED - } - - override fun hasManageMediaPermission(): Boolean = - manageMediaPermissionDelegate.hasManageMediaPermission() - - override fun requestManageMediaPermission(callback: (Result) -> Unit) { - manageMediaPermissionDelegate.requestManageMediaPermission { completeWhenActive(callback, it) } - } - - override fun manageMediaPermission(callback: (Result) -> Unit) { - manageMediaPermissionDelegate.manageMediaPermission { completeWhenActive(callback, it) } - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - manageMediaPermissionDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivityForConfigChanges() { - manageMediaPermissionDelegate.onDetachedFromActivity() - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - manageMediaPermissionDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivity() { - manageMediaPermissionDelegate.onDetachedFromActivity() - } -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt deleted file mode 100644 index fbf4d7e919..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MediaTrashDelegate.kt +++ /dev/null @@ -1,133 +0,0 @@ -package app.alextran.immich.sync - -import android.app.Activity -import android.content.ContentResolver -import android.content.ContentUris -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.provider.MediaStore -import androidx.annotation.RequiresApi -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.PluginRegistry - -class MediaTrashDelegate( - context: Context, - private val trashRequestCode: Int = 1002, -) : PluginRegistry.ActivityResultListener { - private val ctx = context.applicationContext - private var activityBinding: ActivityPluginBinding? = null - private var pendingResult: ((Result) -> Unit)? = null - - private fun hasManageMediaPermission(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - MediaStore.canManageMedia(ctx) - } else { - false - } - } - - fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || !hasManageMediaPermission()) { - callback(Result.failure(FlutterError("PERMISSION_DENIED", "Media permission required", null))) - return - } - - val id = mediaId.toLongOrNull() - if (id == null) { - callback(Result.failure(FlutterError("INVALID_ID", "The file id is not a valid number: $mediaId", null))) - return - } - - if (!isInTrash(id)) { - callback(Result.failure(FlutterError("TRASH_NOT_FOUND", "Item with id=$id not found in trash", null))) - return - } - - restoreUri(ContentUris.withAppendedId(contentUriForType(type.toInt()), id), callback) - } - - @RequiresApi(Build.VERSION_CODES.R) - private fun restoreUri( - contentUri: Uri, - callback: (Result) -> Unit, - ) { - val activity = activityBinding?.activity - if (activity == null) { - callback(Result.failure(FlutterError("NO_ACTIVITY", "Activity not available", null))) - return - } - - try { - val pendingIntent = MediaStore.createTrashRequest(ctx.contentResolver, listOf(contentUri), false) - pendingResult = callback - activity.startIntentSenderForResult( - pendingIntent.intentSender, - trashRequestCode, - null, - 0, - 0, - 0, - ) - } catch (e: Exception) { - pendingResult = null - callback( - Result.failure( - FlutterError("TRASH_ERROR", "Error creating or starting trash request", e.toString()) - ) - ) - } - } - - @RequiresApi(Build.VERSION_CODES.R) - private fun isInTrash(id: Long): Boolean { - val filesUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) - val args = Bundle().apply { - putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns._ID}=?") - putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(id.toString())) - putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY) - putInt(ContentResolver.QUERY_ARG_LIMIT, 1) - } - return ctx.contentResolver.query(filesUri, arrayOf(MediaStore.Files.FileColumns._ID), args, null) - ?.use { it.moveToFirst() } == true - } - - private fun contentUriForType(type: Int): Uri = - when (type) { - // Same order as AssetType from Dart. - 1 -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - 2 -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - 3 -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - else -> MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL) - } - - fun onAttachedToActivity(binding: ActivityPluginBinding) { - activityBinding = binding - binding.addActivityResultListener(this) - } - - fun onDetachedFromActivity() { - failPending("ACTIVITY_DETACHED", "Activity detached before trash result") - activityBinding?.removeActivityResultListener(this) - activityBinding = null - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { - if (requestCode == trashRequestCode) { - val callback = pendingResult - pendingResult = null - callback?.invoke(Result.success(resultCode == Activity.RESULT_OK)) - return true - } - - return false - } - - private fun failPending(code: String, message: String) { - val callback = pendingResult ?: return - pendingResult = null - callback(Result.failure(FlutterError(code, message, null))) - } -} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt new file mode 100644 index 0000000000..29c197c2b6 --- /dev/null +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt @@ -0,0 +1,601 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass") + +package app.alextran.immich.sync + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.EventChannel +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMethodCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer +private object MessagesPigeonUtils { + + fun wrapResult(result: Any?): List { + return listOf(result) + } + + fun wrapError(exception: Throwable): List { + return if (exception is FlutterError) { + listOf( + exception.code, + exception.message, + exception.details + ) + } else { + listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } + } + fun deepEquals(a: Any?, b: Any?): Boolean { + if (a is ByteArray && b is ByteArray) { + return a.contentEquals(b) + } + if (a is IntArray && b is IntArray) { + return a.contentEquals(b) + } + if (a is LongArray && b is LongArray) { + return a.contentEquals(b) + } + if (a is DoubleArray && b is DoubleArray) { + return a.contentEquals(b) + } + if (a is Array<*> && b is Array<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is List<*> && b is List<*>) { + return a.size == b.size && + a.indices.all{ deepEquals(a[it], b[it]) } + } + if (a is Map<*, *> && b is Map<*, *>) { + return a.size == b.size && a.all { + (b as Map).containsKey(it.key) && + deepEquals(it.value, b[it.key]) + } + } + return a == b + } + +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +enum class PlatformAssetPlaybackStyle(val raw: Int) { + UNKNOWN(0), + IMAGE(1), + VIDEO(2), + IMAGE_ANIMATED(3), + LIVE_PHOTO(4), + VIDEO_LOOPING(5); + + companion object { + fun ofRaw(raw: Int): PlatformAssetPlaybackStyle? { + return values().firstOrNull { it.raw == raw } + } + } +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PlatformAsset ( + val id: String, + val name: String, + val type: Long, + val createdAt: Long? = null, + val updatedAt: Long? = null, + val width: Long? = null, + val height: Long? = null, + val durationInSeconds: Long, + val orientation: Long, + val isFavorite: Boolean, + val adjustmentTime: Long? = null, + val latitude: Double? = null, + val longitude: Double? = null, + val playbackStyle: PlatformAssetPlaybackStyle +) + { + companion object { + fun fromList(pigeonVar_list: List): PlatformAsset { + val id = pigeonVar_list[0] as String + val name = pigeonVar_list[1] as String + val type = pigeonVar_list[2] as Long + val createdAt = pigeonVar_list[3] as Long? + val updatedAt = pigeonVar_list[4] as Long? + val width = pigeonVar_list[5] as Long? + val height = pigeonVar_list[6] as Long? + val durationInSeconds = pigeonVar_list[7] as Long + val orientation = pigeonVar_list[8] as Long + val isFavorite = pigeonVar_list[9] as Boolean + val adjustmentTime = pigeonVar_list[10] as Long? + val latitude = pigeonVar_list[11] as Double? + val longitude = pigeonVar_list[12] as Double? + val playbackStyle = pigeonVar_list[13] as PlatformAssetPlaybackStyle + return PlatformAsset(id, name, type, createdAt, updatedAt, width, height, durationInSeconds, orientation, isFavorite, adjustmentTime, latitude, longitude, playbackStyle) + } + } + fun toList(): List { + return listOf( + id, + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + orientation, + isFavorite, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is PlatformAsset) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class PlatformAlbum ( + val id: String, + val name: String, + val updatedAt: Long? = null, + val isCloud: Boolean, + val assetCount: Long +) + { + companion object { + fun fromList(pigeonVar_list: List): PlatformAlbum { + val id = pigeonVar_list[0] as String + val name = pigeonVar_list[1] as String + val updatedAt = pigeonVar_list[2] as Long? + val isCloud = pigeonVar_list[3] as Boolean + val assetCount = pigeonVar_list[4] as Long + return PlatformAlbum(id, name, updatedAt, isCloud, assetCount) + } + } + fun toList(): List { + return listOf( + id, + name, + updatedAt, + isCloud, + assetCount, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is PlatformAlbum) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class SyncDelta ( + val hasChanges: Boolean, + val updates: List, + val deletes: List, + val assetAlbums: Map> +) + { + companion object { + fun fromList(pigeonVar_list: List): SyncDelta { + val hasChanges = pigeonVar_list[0] as Boolean + val updates = pigeonVar_list[1] as List + val deletes = pigeonVar_list[2] as List + val assetAlbums = pigeonVar_list[3] as Map> + return SyncDelta(hasChanges, updates, deletes, assetAlbums) + } + } + fun toList(): List { + return listOf( + hasChanges, + updates, + deletes, + assetAlbums, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is SyncDelta) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class HashResult ( + val assetId: String, + val error: String? = null, + val hash: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): HashResult { + val assetId = pigeonVar_list[0] as String + val error = pigeonVar_list[1] as String? + val hash = pigeonVar_list[2] as String? + return HashResult(assetId, error, hash) + } + } + fun toList(): List { + return listOf( + assetId, + error, + hash, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is HashResult) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} + +/** Generated class from Pigeon that represents data sent in messages. */ +data class CloudIdResult ( + val assetId: String, + val error: String? = null, + val cloudId: String? = null +) + { + companion object { + fun fromList(pigeonVar_list: List): CloudIdResult { + val assetId = pigeonVar_list[0] as String + val error = pigeonVar_list[1] as String? + val cloudId = pigeonVar_list[2] as String? + return CloudIdResult(assetId, error, cloudId) + } + } + fun toList(): List { + return listOf( + assetId, + error, + cloudId, + ) + } + override fun equals(other: Any?): Boolean { + if (other !is CloudIdResult) { + return false + } + if (this === other) { + return true + } + return MessagesPigeonUtils.deepEquals(toList(), other.toList()) } + + override fun hashCode(): Int = toList().hashCode() +} +private open class MessagesPigeonCodec : StandardMessageCodec() { + override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + PlatformAssetPlaybackStyle.ofRaw(it.toInt()) + } + } + 130.toByte() -> { + return (readValue(buffer) as? List)?.let { + PlatformAsset.fromList(it) + } + } + 131.toByte() -> { + return (readValue(buffer) as? List)?.let { + PlatformAlbum.fromList(it) + } + } + 132.toByte() -> { + return (readValue(buffer) as? List)?.let { + SyncDelta.fromList(it) + } + } + 133.toByte() -> { + return (readValue(buffer) as? List)?.let { + HashResult.fromList(it) + } + } + 134.toByte() -> { + return (readValue(buffer) as? List)?.let { + CloudIdResult.fromList(it) + } + } + else -> super.readValueOfType(type, buffer) + } + } + override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { + when (value) { + is PlatformAssetPlaybackStyle -> { + stream.write(129) + writeValue(stream, value.raw) + } + is PlatformAsset -> { + stream.write(130) + writeValue(stream, value.toList()) + } + is PlatformAlbum -> { + stream.write(131) + writeValue(stream, value.toList()) + } + is SyncDelta -> { + stream.write(132) + writeValue(stream, value.toList()) + } + is HashResult -> { + stream.write(133) + writeValue(stream, value.toList()) + } + is CloudIdResult -> { + stream.write(134) + writeValue(stream, value.toList()) + } + else -> super.writeValue(stream, value) + } + } +} + + +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface NativeSyncApi { + fun shouldFullSync(): Boolean + fun getMediaChanges(): SyncDelta + fun checkpointSync() + fun clearSyncCheckpoint() + fun getAssetIdsForAlbum(albumId: String): List + fun getAlbums(): List + fun getAssetsCountSince(albumId: String, timestamp: Long): Long + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List + fun hashAssets(assetIds: List, allowNetworkAccess: Boolean, callback: (Result>) -> Unit) + fun cancelHashing() + fun getTrashedAssets(): Map> + fun getCloudIdForAssetIds(assetIds: List): List + + companion object { + /** The codec used by NativeSyncApi. */ + val codec: MessageCodec by lazy { + MessagesPigeonCodec() + } + /** Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`. */ + @JvmOverloads + fun setUp(binaryMessenger: BinaryMessenger, api: NativeSyncApi?, messageChannelSuffix: String = "") { + val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + val taskQueue = binaryMessenger.makeBackgroundTaskQueue() + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.shouldFullSync()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getMediaChanges()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.checkpointSync() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.clearSyncCheckpoint() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val albumIdArg = args[0] as String + val wrapped: List = try { + listOf(api.getAssetIdsForAlbum(albumIdArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getAlbums()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val albumIdArg = args[0] as String + val timestampArg = args[1] as Long + val wrapped: List = try { + listOf(api.getAssetsCountSince(albumIdArg, timestampArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val albumIdArg = args[0] as String + val updatedTimeCondArg = args[1] as Long? + val wrapped: List = try { + listOf(api.getAssetsForAlbum(albumIdArg, updatedTimeCondArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val assetIdsArg = args[0] as List + val allowNetworkAccessArg = args[1] as Boolean + api.hashAssets(assetIdsArg, allowNetworkAccessArg) { result: Result> -> + val error = result.exceptionOrNull() + if (error != null) { + reply.reply(MessagesPigeonUtils.wrapError(error)) + } else { + val data = result.getOrNull() + reply.reply(MessagesPigeonUtils.wrapResult(data)) + } + } + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + api.cancelHashing() + listOf(null) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.getTrashedAssets()) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val assetIdsArg = args[0] as List + val wrapped: List = try { + listOf(api.getCloudIdForAssetIds(assetIdsArg)) + } catch (exception: Throwable) { + MessagesPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt index 180e23286c..6d2c35d78f 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl26.kt @@ -4,11 +4,7 @@ import android.content.Context class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), NativeSyncApi { - override fun shouldFullSync(callback: (Result) -> Unit) { - runSync(callback) { shouldFullSync() } - } - - private fun shouldFullSync(): Boolean { + override fun shouldFullSync(): Boolean { return true } @@ -22,11 +18,7 @@ class NativeSyncApiImpl26(context: Context) : NativeSyncApiImplBase(context), Na // No-op for Android 10 and below } - override fun getMediaChanges(callback: (Result) -> Unit) { - runSync(callback) { getMediaChanges() } - } - - private fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(): SyncDelta { throw IllegalStateException("Method not supported on this Android version.") } diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt index 4785b751c0..ca54c9f823 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImpl30.kt @@ -7,8 +7,6 @@ import android.os.Bundle import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.annotation.RequiresExtension -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.ensureActive import kotlinx.serialization.json.Json @RequiresApi(Build.VERSION_CODES.Q) @@ -37,11 +35,7 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun shouldFullSync(callback: (Result) -> Unit) { - runSync(callback) { shouldFullSync() } - } - - private fun shouldFullSync(): Boolean = + override fun shouldFullSync(): Boolean = MediaStore.getVersion(ctx) != prefs.getString(SHARED_PREF_MEDIA_STORE_VERSION_KEY, null) override fun checkpointSync() { @@ -55,11 +49,7 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na } } - override fun getMediaChanges(callback: (Result) -> Unit) { - runSync(callback) { getMediaChanges() } - } - - private suspend fun getMediaChanges(): SyncDelta { + override fun getMediaChanges(): SyncDelta { val genMap = getSavedGenerationMap() val currentVolumes = MediaStore.getExternalVolumeNames(ctx) val changed = mutableListOf() @@ -68,7 +58,6 @@ class NativeSyncApiImpl30(context: Context) : NativeSyncApiImplBase(context), Na var hasChanges = genMap.keys != currentVolumes for (volume in currentVolumes) { - currentCoroutineContext().ensureActive() val currentGen = MediaStore.getGeneration(ctx, volume) val storedGen = genMap[volume] ?: 0 if (currentGen <= storedGen) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt index 25a849ad3a..05671579ae 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/sync/MessagesImplBase.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.ContentUris import android.content.Context import android.database.Cursor +import androidx.exifinterface.media.ExifInterface import android.os.Build import android.os.Bundle import android.os.ext.SdkExtensions @@ -16,8 +17,6 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.ImageHeaderParser import com.bumptech.glide.load.ImageHeaderParserUtils import com.bumptech.glide.load.resource.bitmap.DefaultImageHeaderParser -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -29,8 +28,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import java.io.File -import java.io.IOException -import java.io.InputStream import java.security.MessageDigest import kotlin.coroutines.cancellation.CancellationException @@ -42,18 +39,15 @@ sealed class AssetResult { private const val TAG = "NativeSyncApiImplBase" @SuppressLint("InlinedApi") -open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAware { +open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() { private val ctx: Context = context.applicationContext private var hashTask: Job? = null - private var syncJob: Job? = null - private val mediaTrashDelegate = MediaTrashDelegate(ctx) companion object { private const val MAX_CONCURRENT_HASH_OPERATIONS = 16 private val hashSemaphore = Semaphore(MAX_CONCURRENT_HASH_OPERATIONS) private const val HASHING_CANCELLED_CODE = "HASH_CANCELLED" - private const val SYNC_CANCELLED_CODE = "SYNC_CANCELLED" // MediaStore.Files.FileColumns.SPECIAL_FORMAT — S Extensions 21+ // https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT @@ -100,12 +94,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa const val HASH_BUFFER_SIZE = 2 * 1024 * 1024 - // _special_format: added in API level 37, also in S Extensions 21+ + // _special_format requires S Extensions 21+ // https://developer.android.com/reference/android/provider/MediaStore.Files.FileColumns#SPECIAL_FORMAT private fun hasSpecialFormatColumn(): Boolean = - Build.VERSION.SDK_INT >= 37 || - (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && - SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 21) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && + SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 21 } protected fun getCursor( @@ -184,7 +177,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa val height = c.getInt(heightColumn).toLong() // Duration is milliseconds val duration = if (rawMediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE) 0L - else c.getLong(durationColumn) + else c.getLong(durationColumn) / 1000 val orientation = c.getInt(orientationColumn) val isFavorite = if (favoriteColumn == -1) false else c.getInt(favoriteColumn) != 0 @@ -298,11 +291,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa return PlatformAssetPlaybackStyle.IMAGE } - fun getAlbums(callback: (Result>) -> Unit) { - runSync(callback) { getAlbums() } - } - - private suspend fun getAlbums(): List { + fun getAlbums(): List { val albums = mutableListOf() val albumsCount = mutableMapOf() @@ -329,7 +318,6 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED) while (cursor.moveToNext()) { - currentCoroutineContext().ensureActive() val id = cursor.getString(bucketIdColumn) val count = albumsCount.getOrDefault(id, 0) @@ -350,11 +338,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa .sortedBy { it.id } } - fun getAssetIdsForAlbum(albumId: String, callback: (Result>) -> Unit) { - runSync(callback) { getAssetIdsForAlbum(albumId) } - } - - private fun getAssetIdsForAlbum(albumId: String): List { + fun getAssetIdsForAlbum(albumId: String): List { val projection = arrayOf(MediaStore.MediaColumns._ID) return getCursor( @@ -378,15 +362,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa )?.use { cursor -> cursor.count.toLong() } ?: 0L - fun getAssetsForAlbum( - albumId: String, - updatedTimeCond: Long?, - callback: (Result>) -> Unit - ) { - runSync(callback) { getAssetsForAlbum(albumId, updatedTimeCond) } - } - - private fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { + fun getAssetsForAlbum(albumId: String, updatedTimeCond: Long?): List { var selection = "$BUCKET_SELECTION AND $MEDIA_SELECTION" val selectionArgs = mutableListOf(albumId, *MEDIA_SELECTION_ARGS) @@ -424,7 +400,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa }.awaitAll() completeWhenActive(callback, Result.success(results)) - } catch (_: CancellationException) { + } catch (e: CancellationException) { completeWhenActive( callback, Result.failure( FlutterError( @@ -442,20 +418,23 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa private suspend fun hashAsset(assetId: String): HashResult { return try { + val assetUri = ContentUris.withAppendedId( + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), + assetId.toLong() + ) + val digest = MessageDigest.getInstance("SHA-1") - openOriginalStream(assetId).use { inputStream -> + ctx.contentResolver.openInputStream(assetUri)?.use { inputStream -> + var bytesRead: Int val buffer = ByteArray(HASH_BUFFER_SIZE) - while (true) { - val bytesRead = inputStream.read(buffer) - if (bytesRead == -1) break + while (inputStream.read(buffer).also { bytesRead = it } > 0) { currentCoroutineContext().ensureActive() digest.update(buffer, 0, bytesRead) } - } + } ?: return HashResult(assetId, "Cannot open input stream for asset", null) - HashResult(assetId, null, Base64.encodeToString(digest.digest(), Base64.NO_WRAP)) - } catch (e: CancellationException) { - throw e + val hashString = Base64.encodeToString(digest.digest(), Base64.NO_WRAP) + HashResult(assetId, null, hashString) } catch (e: SecurityException) { HashResult(assetId, "Permission denied accessing asset: ${e.message}", null) } catch (e: Exception) { @@ -463,83 +442,11 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin(), ActivityAwa } } - private fun openOriginalStream(assetId: String): InputStream { - val id = assetId.toLong() - val collection = when (getMediaType(id)) { - MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - } - val uri = ContentUris.withAppendedId(collection, id) - val original = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - MediaStore.setRequireOriginal(uri) - } else { - uri - } - - return ctx.contentResolver.openInputStream(original) - ?: throw IOException("Cannot open original stream for asset $assetId") - } - - private fun getMediaType(id: Long): Int = - getCursor( - MediaStore.VOLUME_EXTERNAL, - "${MediaStore.MediaColumns._ID} = ?", - arrayOf(id.toString()), - arrayOf(MediaStore.Files.FileColumns.MEDIA_TYPE) - )?.use { cursor -> - if (cursor.moveToFirst()) { - cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MEDIA_TYPE)) - } else { - MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE - } - } ?: MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE - fun cancelHashing() { hashTask?.cancel() hashTask = null } - fun cancelSync() { - syncJob?.cancel() - syncJob = null - } - - protected fun runSync(callback: (Result) -> Unit, work: suspend () -> T) { - syncJob?.cancel() - syncJob = CoroutineScope(Dispatchers.IO).launch { - try { - completeWhenActive(callback, Result.success(work())) - } catch (_: CancellationException) { - completeWhenActive( - callback, - Result.failure(FlutterError(SYNC_CANCELLED_CODE, "Sync cancelled", null)) - ) - } catch (e: Exception) { - completeWhenActive(callback, Result.failure(e)) - } - } - } - - fun restoreFromTrashById(mediaId: String, type: Long, callback: (Result) -> Unit) { - mediaTrashDelegate.restoreFromTrashById(mediaId, type) { completeWhenActive(callback, it) } - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - mediaTrashDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivityForConfigChanges() { - mediaTrashDelegate.onDetachedFromActivity() - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - mediaTrashDelegate.onAttachedToActivity(binding) - } - - override fun onDetachedFromActivity() { - mediaTrashDelegate.onDetachedFromActivity() - } - // This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs @Suppress("unused", "UNUSED_PARAMETER") fun getCloudIdForAssetIds(assetIds: List): List { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt deleted file mode 100644 index a1e1fea3dd..0000000000 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntentPlugin.kt +++ /dev/null @@ -1,201 +0,0 @@ -package app.alextran.immich.viewintent - -import android.app.Activity -import android.content.Context -import android.content.Intent -import android.net.Uri -import android.provider.DocumentsContract -import android.provider.MediaStore -import android.provider.OpenableColumns -import android.util.Log -import android.webkit.MimeTypeMap -import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.embedding.engine.plugins.activity.ActivityAware -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding -import io.flutter.plugin.common.PluginRegistry -import java.io.File -import java.io.FileOutputStream -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch - -private const val TAG = "ViewIntentPlugin" - -class ViewIntentPlugin : FlutterPlugin, ActivityAware, PluginRegistry.NewIntentListener, ViewIntentHostApi { - private var context: Context? = null - private var activity: Activity? = null - private var unconsumedIntent: Intent? = null - private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - - override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - context = binding.applicationContext - ViewIntentHostApi.setUp(binding.binaryMessenger, this) - } - - override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - ViewIntentHostApi.setUp(binding.binaryMessenger, null) - ioScope.cancel() - context = null - } - - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activity = binding.activity - unconsumedIntent = binding.activity.intent - binding.addOnNewIntentListener(this) - } - - override fun onDetachedFromActivityForConfigChanges() { - activity = null - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - onAttachedToActivity(binding) - } - - override fun onDetachedFromActivity() { - activity = null - } - - override fun onNewIntent(intent: Intent): Boolean { - unconsumedIntent = intent - return false - } - - override fun consumeViewIntent(callback: (Result) -> Unit) { - val context = context ?: run { - callback(Result.success(null)) - return - } - val intent = unconsumedIntent ?: activity?.intent - - if (intent?.action != Intent.ACTION_VIEW) { - callback(Result.success(null)) - return - } - - val uri = intent.data - if (uri == null) { - callback(Result.success(null)) - return - } - - ioScope.launch { - try { - val mimeType = context.contentResolver.getType(uri) ?: intent.type - if (mimeType == null || (!mimeType.startsWith("image/") && !mimeType.startsWith("video/"))) { - callback(Result.success(null)) - return@launch - } - - val localAssetId = extractLocalAssetId(context, uri, mimeType) - val tempFilePath = if (localAssetId == null) { - copyUriToTempFile(context, uri, mimeType)?.absolutePath ?: run { - callback(Result.success(null)) - return@launch - } - } else { - null - } - val payload = ViewIntentPayload( - path = tempFilePath, - mimeType = mimeType, - localAssetId = localAssetId, - ) - consumeViewIntent(intent) - callback(Result.success(payload)) - } catch (e: Exception) { - callback(Result.failure(e)) - } - } - } - - private fun consumeViewIntent(currentIntent: Intent) { - unconsumedIntent = Intent(currentIntent).apply { - action = null - data = null - type = null - } - activity?.intent = unconsumedIntent - } - - private fun extractLocalAssetId(context: Context, uri: Uri, mimeType: String): String? { - return tryExtractDocumentLocalAssetId(context, uri) - ?: tryParseContentUriId(uri) - ?: resolveLocalIdByNameAndSize(context, uri, mimeType) - } - - private fun tryExtractDocumentLocalAssetId(context: Context, uri: Uri): String? { - return try { - if (!DocumentsContract.isDocumentUri(context, uri)) return null - val docId = DocumentsContract.getDocumentId(uri) - if (docId.isBlank() || docId.startsWith("raw:")) return null - docId.substringAfter(':', docId).toLongOrNull()?.toString() - } catch (e: Exception) { - Log.w(TAG, "Failed to resolve local asset id from document URI: $uri", e) - null - } - } - - private fun tryParseContentUriId(uri: Uri): String? { - val id = uri.lastPathSegment?.toLongOrNull() ?: return null - return if (id >= 0) id.toString() else null - } - - private fun copyUriToTempFile(context: Context, uri: Uri, mimeType: String): File? { - return try { - val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" } - val tempFile = File.createTempFile("view_intent_", extension, context.cacheDir) - context.contentResolver.openInputStream(uri)?.use { inputStream -> - FileOutputStream(tempFile).use { outputStream -> - inputStream.copyTo(outputStream) - } - } ?: return null - tempFile - } catch (_: Exception) { - null - } - } - - private fun resolveLocalIdByNameAndSize(context: Context, uri: Uri, mimeType: String): String? { - val metaProjection = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE) - val (displayName, size) = - try { - context.contentResolver.query(uri, metaProjection, null, null, null)?.use { cursor -> - if (!cursor.moveToFirst()) return null - val nameIdx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - val sizeIdx = cursor.getColumnIndex(OpenableColumns.SIZE) - val name = if (nameIdx >= 0) cursor.getString(nameIdx) else null - val bytes = if (sizeIdx >= 0) cursor.getLong(sizeIdx) else -1L - if (name.isNullOrBlank() || bytes < 0) return null - name to bytes - } ?: return null - } catch (_: Exception) { - return null - } - - val tableUri = when { - mimeType.startsWith("image/") -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - mimeType.startsWith("video/") -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI - else -> return null - } - return try { - context.contentResolver - .query( - tableUri, - arrayOf(MediaStore.MediaColumns._ID), - "${MediaStore.MediaColumns.DISPLAY_NAME}=? AND ${MediaStore.MediaColumns.SIZE}=?", - arrayOf(displayName, size.toString()), - "${MediaStore.MediaColumns.DATE_MODIFIED} DESC", - )?.use { cursor -> - if (!cursor.moveToFirst()) return null - val idIndex = cursor.getColumnIndex(MediaStore.MediaColumns._ID) - if (idIndex < 0) return null - cursor.getLong(idIndex).toString() - } - } catch (_: Exception) { - null - } - } -} diff --git a/mobile/android/app/src/main/res/values/strings.xml b/mobile/android/app/src/main/res/values/strings.xml index d543e33116..5ac495ebb5 100644 --- a/mobile/android/app/src/main/res/values/strings.xml +++ b/mobile/android/app/src/main/res/values/strings.xml @@ -5,9 +5,4 @@ See memories from Immich. View a random image from your library or a specific album. - - Uploads and downloads - Progress updates for uploads and downloads - - Background backup diff --git a/mobile/android/build.gradle b/mobile/android/build.gradle index 2663154d9d..719c946bd6 100644 --- a/mobile/android/build.gradle +++ b/mobile/android/build.gradle @@ -1,4 +1,6 @@ allprojects { + ext.kotlin_version = '2.2.20' + repositories { google() mavenCentral() @@ -8,7 +10,22 @@ allprojects { rootProject.buildDir = '../build' subprojects { + // fix for verifyReleaseResources + // ============ + afterEvaluate { project -> + if (project.plugins.hasPlugin("com.android.application") || + project.plugins.hasPlugin("com.android.library")) { + project.android { + compileSdkVersion 36 + buildToolsVersion "36.0.0" + } + } + } + // ============ project.buildDir = "${rootProject.buildDir}/${project.name}" +} + +subprojects { project.evaluationDependsOn(':app') } @@ -19,3 +36,4 @@ tasks.register("clean", Delete) { tasks.named('wrapper') { distributionType = Wrapper.DistributionType.ALL } + diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index d41a0c8d89..a8237b9482 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -22,8 +22,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 3057, - "android.injected.version.name" => "3.1.0", + "android.injected.version.code" => 47, + "android.injected.version.name" => "1.30.2", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab', track: 'beta') @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 3057, - "android.injected.version.name" => "3.1.0", + "android.injected.version.code" => 3044, + "android.injected.version.name" => "2.7.3", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/android/gradle.properties b/mobile/android/gradle.properties index 99f62dc8c2..f63dcd33e1 100644 --- a/mobile/android/gradle.properties +++ b/mobile/android/gradle.properties @@ -5,7 +5,3 @@ android.nonTransitiveRClass=false android.nonFinalResIds=false org.gradle.caching=true org.gradle.parallel=true -# This builtInKotlin flag was added automatically by Flutter migrator -android.builtInKotlin=false -# This newDsl flag was added automatically by Flutter migrator -android.newDsl=false diff --git a/mobile/android/gradle/libs.versions.toml b/mobile/android/gradle/libs.versions.toml deleted file mode 100644 index fa4ba34a2d..0000000000 --- a/mobile/android/gradle/libs.versions.toml +++ /dev/null @@ -1,51 +0,0 @@ -[versions] -agp = "8.11.2" -kotlin = "2.2.20" -ksp = "2.2.20-2.0.3" -coroutines = "1.9.0" -work = "2.9.1" -concurrent = "1.2.0" -guava = "33.3.1-android" -glide = "4.16.0" -serialization-json = "1.8.1" -glance = "1.1.1" -gson = "2.10.1" -okhttp = "4.12.0" -cronet = "143.7445.0" -media3 = "1.10.0" -desugar = "2.1.2" -activity-compose = "1.8.2" -compose-ui = "1.1.1" -material3 = "1.2.1" -lifecycle = "2.6.2" -material = "1.12.0" - -[libraries] -okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } -cronet-embedded = { module = "org.chromium.net:cronet-embedded", version.ref = "cronet" } -media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" } -media3-datasource-cronet = { module = "androidx.media3:media3-datasource-cronet", version.ref = "media3" } -kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } -work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } -concurrent-futures = { module = "androidx.concurrent:concurrent-futures", version.ref = "concurrent" } -guava = { module = "com.google.guava:guava", version.ref = "guava" } -glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } -glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" } -kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization-json" } -desugar-jdk-libs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "desugar" } -glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "glance" } -gson = { module = "com.google.code.gson:gson", version.ref = "gson" } -activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" } -compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose-ui" } -compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose-ui" } -compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } -lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } -material = { module = "com.google.android.material:material", version.ref = "material" } - -[plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -# TODO: update to version.ref = "kotlin" when background_downloader is removed -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version = "2.1.0" } -kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/android/gradle/wrapper/gradle-wrapper.properties index 6514f919fd..ed4c299adb 100644 --- a/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ b/mobile/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/mobile/android/settings.gradle b/mobile/android/settings.gradle index d6555f43b1..fbed55a3e3 100644 --- a/mobile/android/settings.gradle +++ b/mobile/android/settings.gradle @@ -18,11 +18,10 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.11.2" apply false + id "com.android.application" version '8.11.2' apply false id "org.jetbrains.kotlin.android" version "2.2.20" apply false - // TODO: update to match kotlin version when background_downloader is removed - id "org.jetbrains.kotlin.plugin.serialization" version "2.1.0" apply false - id "com.google.devtools.ksp" version "2.2.20-2.0.3" apply false + id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22' apply false + id 'com.google.devtools.ksp' version '2.2.20-2.0.3' apply false } include ":app" diff --git a/mobile/assets/feature_message/ocr.webp b/mobile/assets/feature_message/ocr.webp deleted file mode 100644 index 5465c4e06a..0000000000 Binary files a/mobile/assets/feature_message/ocr.webp and /dev/null differ diff --git a/mobile/assets/feature_message/open_in_immich.webp b/mobile/assets/feature_message/open_in_immich.webp deleted file mode 100644 index 910bb9e397..0000000000 Binary files a/mobile/assets/feature_message/open_in_immich.webp and /dev/null differ diff --git a/mobile/assets/feature_message/recently_added.webp b/mobile/assets/feature_message/recently_added.webp deleted file mode 100644 index f530e42802..0000000000 Binary files a/mobile/assets/feature_message/recently_added.webp and /dev/null differ diff --git a/mobile/assets/feature_message/share_quality.webp b/mobile/assets/feature_message/share_quality.webp deleted file mode 100644 index 8e78cfcbfe..0000000000 Binary files a/mobile/assets/feature_message/share_quality.webp and /dev/null differ diff --git a/mobile/assets/feature_message/slideshow.webp b/mobile/assets/feature_message/slideshow.webp deleted file mode 100644 index 91a9a29667..0000000000 Binary files a/mobile/assets/feature_message/slideshow.webp and /dev/null differ diff --git a/mobile/bin/generate_keys.dart b/mobile/bin/generate_keys.dart index 160f2c5b5c..3c5c284c3e 100644 --- a/mobile/bin/generate_keys.dart +++ b/mobile/bin/generate_keys.dart @@ -1,4 +1,4 @@ -// ignore_for_file: avoid_slow_async_io, avoid_print +// ignore_for_file: avoid_print import 'dart:convert'; import 'dart:io'; @@ -97,15 +97,6 @@ const _kIntParamNames = [ 'quantity', ]; -const _kParamTypeOverrides = { - 'advanced_settings_clear_image_cache_success.size': 'String', - 'backup_controller_page_storage_format.total': 'String', - 'backup_controller_page_storage_format.used': 'String', - 'cleanup_found_assets_with_size.size': 'String', -}; - -final _usedParamTypeOverrides = {}; - void main() async { final sourceFile = File('../i18n/en.json'); if (!await sourceFile.exists()) { @@ -152,11 +143,6 @@ class TranslationParam { Future _generateTranslations(Map translations, File output) async { final root = _buildTranslationTree('', translations); - final staleOverrides = _kParamTypeOverrides.keys.toSet().difference(_usedParamTypeOverrides); - if (staleOverrides.isNotEmpty) { - throw StateError('_kParamTypeOverrides has entries matching no plain {placeholder}: ${staleOverrides.join(', ')}'); - } - final buffer = StringBuffer(''' // DO NOT EDIT. This is code generated via generate_keys.dart @@ -208,22 +194,21 @@ class Translations extends _BaseTranslations { await output.writeAsString(buffer.toString()); } -TranslationNode _buildTranslationTree(String key, dynamic value, [String parentKey = '']) { - final fullKey = parentKey.isEmpty ? key : '$parentKey.$key'; +TranslationNode _buildTranslationTree(String key, dynamic value) { if (value is Map) { final children = {}; for (final entry in value.entries) { - children[entry.key] = _buildTranslationTree(entry.key, entry.value, fullKey); + children[entry.key] = _buildTranslationTree(entry.key, entry.value); } return TranslationNode(key: key, children: children); } else { final stringValue = value.toString(); - final params = _extractParams(stringValue, fullKey); + final params = _extractParams(stringValue); return TranslationNode(key: key, value: stringValue, params: params); } } -List _extractParams(String value, String fullKey) { +List _extractParams(String value) { final params = {}; final icuRegex = RegExp(r'\{(\w+),\s*(plural|select|number|date|time)([^}]*(?:\{[^}]*\}[^}]*)*)\}'); @@ -232,9 +217,7 @@ List _extractParams(String value, String fullKey) { final icuType = match.group(2)!; final icuContent = match.group(3) ?? ''; - if (params.containsKey(name)) { - continue; - } + if (params.containsKey(name)) continue; String type; if (icuType == 'plural' || icuType == 'number') { @@ -255,9 +238,7 @@ List _extractParams(String value, String fullKey) { for (var i = 0; i < value.length; i++) { if (value[i] == '{') { - if (depth == 0) { - icuStart = i; - } + if (depth == 0) icuStart = i; depth++; } else if (value[i] == '}') { depth--; @@ -275,15 +256,10 @@ List _extractParams(String value, String fullKey) { for (final match in simpleRegex.allMatches(cleanedValue)) { final name = match.group(1)!; - if (params.containsKey(name)) { - continue; - } + if (params.containsKey(name)) continue; - final String type; - if (_kParamTypeOverrides['$fullKey.$name'] case final override?) { - type = override; - _usedParamTypeOverrides.add('$fullKey.$name'); - } else if (_kIntParamNames.contains(name.toLowerCase())) { + String type; + if (_kIntParamNames.contains(name.toLowerCase())) { type = 'int'; } else { type = 'Object'; diff --git a/mobile/build.yaml b/mobile/build.yaml index 32cd96c5d3..cb718d9d3e 100644 --- a/mobile/build.yaml +++ b/mobile/build.yaml @@ -18,7 +18,8 @@ targets: generate_for: &drift_generate_for - lib/infrastructure/entities/*.dart - lib/infrastructure/entities/*.drift - - lib/infrastructure/repositories/*.repository.dart + - lib/infrastructure/repositories/db.repository.dart + - lib/infrastructure/repositories/logger_db.repository.dart drift_dev:modular: enabled: true options: *drift_options diff --git a/mobile/dart_test.yaml b/mobile/dart_test.yaml new file mode 100644 index 0000000000..fa54954090 --- /dev/null +++ b/mobile/dart_test.yaml @@ -0,0 +1,3 @@ +# Used to filter out tags from test runs +tags: + widget: diff --git a/mobile/dcm_global.yaml b/mobile/dcm_global.yaml index 0518849062..ffe77eede8 100644 --- a/mobile/dcm_global.yaml +++ b/mobile/dcm_global.yaml @@ -1 +1 @@ -version: '>=1.29.0 <=1.37.0' +version: '>=1.29.0 <=1.36.0' diff --git a/mobile/drift_schemas/main/drift_schema_v23.json b/mobile/drift_schemas/main/drift_schema_v23.json deleted file mode 100644 index 490484f7e8..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v23.json +++ /dev/null @@ -1,3351 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 0, - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 4 - ], - "type": "index", - "data": { - "on": 4, - "name": "idx_remote_album_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_day", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 18, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_month", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 19, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 20, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 21, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 22, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 23, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 24, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 25, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 26, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [ - 1, - 26 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 28, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [ - 1, - 28 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 31, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 32, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 33, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 23 - ], - "type": "index", - "data": { - "on": 23, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 25 - ], - "type": "index", - "data": { - "on": 25, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 31 - ], - "type": "index", - "data": { - "on": 31, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 31 - ], - "type": "index", - "data": { - "on": 31, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 32 - ], - "type": "index", - "data": { - "on": 32, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_album_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_local_date_time_day", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))" - } - ] - }, - { - "name": "idx_remote_asset_local_date_time_month", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v24.json b/mobile/drift_schemas/main/drift_schema_v24.json deleted file mode 100644 index 94c65bd09a..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v24.json +++ /dev/null @@ -1,3301 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_day", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_local_date_time_month", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", - "unique": false, - "columns": [] - } - }, - { - "id": 18, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 19, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 20, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 21, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 22, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 23, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 24, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 25, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 26, - "references": [ - 1, - 25 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 27, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [ - 1, - 27 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 31, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 32, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 33, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 24 - ], - "type": "index", - "data": { - "on": 24, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 31 - ], - "type": "index", - "data": { - "on": 31, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_local_date_time_day", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))" - } - ] - }, - { - "name": "idx_remote_asset_local_date_time_month", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v25.json b/mobile/drift_schemas/main/drift_schema_v25.json deleted file mode 100644 index 5a3f78aae7..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v25.json +++ /dev/null @@ -1,3358 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 17, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 19, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 20, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 21, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 22, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 23, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 24, - "references": [ - 1, - 23 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 25, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 26, - "references": [ - 1, - 25 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 29, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "metadata", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 31, - "references": [ - 18 - ], - "type": "index", - "data": { - "on": 18, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 32, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 33, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 25 - ], - "type": "index", - "data": { - "on": 25, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "metadata", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"metadata\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v26.json b/mobile/drift_schemas/main/drift_schema_v26.json deleted file mode 100644 index b958bcca43..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v26.json +++ /dev/null @@ -1,3368 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 17, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 19, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 20, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 21, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 22, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 23, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 24, - "references": [ - 1, - 23 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 25, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 26, - "references": [ - 1, - 25 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 29, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "metadata", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 31, - "references": [ - 18 - ], - "type": "index", - "data": { - "on": 18, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 32, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 33, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 25 - ], - "type": "index", - "data": { - "on": 25, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "metadata", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"metadata\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v27.json b/mobile/drift_schemas/main/drift_schema_v27.json deleted file mode 100644 index 4df6ef8389..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v27.json +++ /dev/null @@ -1,3368 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 17, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 19, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 20, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 21, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 22, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 23, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 24, - "references": [ - 1, - 23 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 25, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 26, - "references": [ - 1, - 25 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 29, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "settings", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 31, - "references": [ - 18 - ], - "type": "index", - "data": { - "on": 18, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 32, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 33, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 25 - ], - "type": "index", - "data": { - "on": 25, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "settings", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v28.json b/mobile/drift_schemas/main/drift_schema_v28.json deleted file mode 100644 index c635d0a6a2..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v28.json +++ /dev/null @@ -1,3391 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_created_at", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 19, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 20, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 21, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 22, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 23, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 24, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 25, - "references": [ - 1, - 24 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 26, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [ - 1, - 26 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 30, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 31, - "references": [], - "type": "table", - "data": { - "name": "settings", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 32, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 33, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 23 - ], - "type": "index", - "data": { - "on": 23, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 43, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_local_asset_created_at", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "settings", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v29.json b/mobile/drift_schemas/main/drift_schema_v29.json deleted file mode 100644 index d40878efc2..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v29.json +++ /dev/null @@ -1,3603 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_created_at", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 19, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 20, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 21, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 22, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 23, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 24, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 25, - "references": [ - 1, - 24 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 26, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [ - 1, - 26 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 30, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 31, - "references": [], - "type": "table", - "data": { - "name": "settings", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 32, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_ocr_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "x1", - "getter_name": "x1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y1", - "getter_name": "y1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x2", - "getter_name": "x2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y2", - "getter_name": "y2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x3", - "getter_name": "x3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y3", - "getter_name": "y3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x4", - "getter_name": "x4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y4", - "getter_name": "y4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "box_score", - "getter_name": "boxScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "text_score", - "getter_name": "textScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "recognized_text", - "getter_name": "recognizedText", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 33, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 23 - ], - "type": "index", - "data": { - "on": 23, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 43, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 44, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 45, - "references": [ - 32 - ], - "type": "index", - "data": { - "on": 32, - "name": "idx_asset_ocr_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_local_asset_created_at", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "settings", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_ocr_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_ocr_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v30.json b/mobile/drift_schemas/main/drift_schema_v30.json deleted file mode 100644 index 60a6327a73..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v30.json +++ /dev/null @@ -1,3603 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_created_at", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 18, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 19, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 20, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 21, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 22, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 23, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 24, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 25, - "references": [ - 1, - 24 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 26, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 27, - "references": [ - 1, - 26 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 30, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 31, - "references": [], - "type": "table", - "data": { - "name": "settings", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 32, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_ocr_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "x1", - "getter_name": "x1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y1", - "getter_name": "y1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x2", - "getter_name": "x2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y2", - "getter_name": "y2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x3", - "getter_name": "x3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y3", - "getter_name": "y3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x4", - "getter_name": "x4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y4", - "getter_name": "y4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "box_score", - "getter_name": "boxScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "text_score", - "getter_name": "textScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "recognized_text", - "getter_name": "recognizedText", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 33, - "references": [ - 19 - ], - "type": "index", - "data": { - "on": 19, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 23 - ], - "type": "index", - "data": { - "on": 23, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 26 - ], - "type": "index", - "data": { - "on": 26, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 43, - "references": [ - 29 - ], - "type": "index", - "data": { - "on": 29, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 44, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 45, - "references": [ - 32 - ], - "type": "index", - "data": { - "on": 32, - "name": "idx_asset_ocr_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_local_asset_created_at", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "settings", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_ocr_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_ocr_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/drift_schemas/main/drift_schema_v31.json b/mobile/drift_schemas/main/drift_schema_v31.json deleted file mode 100644 index aff31af3f5..0000000000 --- a/mobile/drift_schemas/main/drift_schema_v31.json +++ /dev/null @@ -1,3626 +0,0 @@ -{ - "_meta": { - "description": "This file contains a serialized version of schema entities for drift.", - "version": "1.3.0" - }, - "options": { - "store_date_time_values_as_text": true - }, - "entities": [ - { - "id": 0, - "references": [], - "type": "table", - "data": { - "name": "user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 1, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "remote_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "local_date_time", - "getter_name": "localDateTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumb_hash", - "getter_name": "thumbHash", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "uploaded_at", - "getter_name": "uploadedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "live_photo_video_id", - "getter_name": "livePhotoVideoId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "visibility", - "getter_name": "visibility", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetVisibility.values)", - "dart_type_name": "AssetVisibility" - } - }, - { - "name": "stack_id", - "getter_name": "stackId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "library_id", - "getter_name": "libraryId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_edited", - "getter_name": "isEdited", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_edited\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_edited\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 2, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "stack_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "primary_asset_id", - "getter_name": "primaryAssetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 3, - "references": [], - "type": "table", - "data": { - "name": "local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "i_cloud_id", - "getter_name": "iCloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 4, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('\\'\\'')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "thumbnail_asset_id", - "getter_name": "thumbnailAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "is_activity_enabled", - "getter_name": "isActivityEnabled", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_activity_enabled\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_activity_enabled\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "order", - "getter_name": "order", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumAssetOrder.values)", - "dart_type_name": "AlbumAssetOrder" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 5, - "references": [ - 4 - ], - "type": "table", - "data": { - "name": "local_album_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "backup_selection", - "getter_name": "backupSelection", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(BackupSelection.values)", - "dart_type_name": "BackupSelection" - } - }, - { - "name": "is_ios_shared_album", - "getter_name": "isIosSharedAlbum", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_ios_shared_album\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_ios_shared_album\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "linked_remote_album_id", - "getter_name": "linkedRemoteAlbumId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 6, - "references": [ - 3, - 5 - ], - "type": "table", - "data": { - "name": "local_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES local_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES local_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "local_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "marker", - "getter_name": "marker_", - "moor_type": "bool", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "CHECK (\"marker\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"marker\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 7, - "references": [ - 6 - ], - "type": "index", - "data": { - "on": 6, - "name": "idx_local_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 8, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 9, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 10, - "references": [ - 3 - ], - "type": "index", - "data": { - "on": 3, - "name": "idx_local_asset_created_at", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)", - "unique": false, - "columns": [] - } - }, - { - "id": 11, - "references": [ - 2 - ], - "type": "index", - "data": { - "on": 2, - "name": "idx_stack_primary_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 12, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum\nON remote_asset_entity (owner_id, checksum)\nWHERE (library_id IS NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 13, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "UQ_remote_assets_owner_library_checksum", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum\nON remote_asset_entity (owner_id, library_id, checksum)\nWHERE (library_id IS NOT NULL);\n", - "unique": true, - "columns": [] - } - }, - { - "id": 14, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 15, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_stack_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 16, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created\nON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)\n", - "unique": false, - "columns": [] - } - }, - { - "id": 17, - "references": [ - 1 - ], - "type": "index", - "data": { - "on": 1, - "name": "idx_remote_asset_uploaded", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)", - "unique": false, - "columns": [] - } - }, - { - "id": 18, - "references": [], - "type": "table", - "data": { - "name": "auth_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "email", - "getter_name": "email", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_admin", - "getter_name": "isAdmin", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_admin\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_admin\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "has_profile_image", - "getter_name": "hasProfileImage", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"has_profile_image\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"has_profile_image\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "profile_changed_at", - "getter_name": "profileChangedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "avatar_color", - "getter_name": "avatarColor", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AvatarColor.values)", - "dart_type_name": "AvatarColor" - } - }, - { - "name": "quota_size_in_bytes", - "getter_name": "quotaSizeInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "quota_usage_in_bytes", - "getter_name": "quotaUsageInBytes", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "pin_code", - "getter_name": "pinCode", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 19, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "user_metadata_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "key", - "getter_name": "key", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(UserMetadataKey.values)", - "dart_type_name": "UserMetadataKey" - } - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "userMetadataConverter", - "dart_type_name": "Map" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "user_id", - "key" - ] - } - }, - { - "id": 20, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "partner_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "shared_by_id", - "getter_name": "sharedById", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "shared_with_id", - "getter_name": "sharedWithId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "in_timeline", - "getter_name": "inTimeline", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"in_timeline\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"in_timeline\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "shared_by_id", - "shared_with_id" - ] - } - }, - { - "id": 21, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_exif_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "city", - "getter_name": "city", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "state", - "getter_name": "state", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "country", - "getter_name": "country", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "date_time_original", - "getter_name": "dateTimeOriginal", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "description", - "getter_name": "description", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "exposure_time", - "getter_name": "exposureTime", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "f_number", - "getter_name": "fNumber", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "file_size", - "getter_name": "fileSize", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "focal_length", - "getter_name": "focalLength", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "iso", - "getter_name": "iso", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "make", - "getter_name": "make", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "model", - "getter_name": "model", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "lens", - "getter_name": "lens", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "time_zone", - "getter_name": "timeZone", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "rating", - "getter_name": "rating", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "projection_type", - "getter_name": "projectionType", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 22, - "references": [ - 1, - 4 - ], - "type": "table", - "data": { - "name": "remote_album_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "album_id" - ] - } - }, - { - "id": 23, - "references": [ - 4, - 0 - ], - "type": "table", - "data": { - "name": "remote_album_user_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_album_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_album_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_album_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "user_id", - "getter_name": "userId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "role", - "getter_name": "role", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AlbumUserRole.values)", - "dart_type_name": "AlbumUserRole" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "album_id", - "user_id" - ] - } - }, - { - "id": 24, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "remote_asset_cloud_id_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "cloud_id", - "getter_name": "cloudId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "adjustment_time", - "getter_name": "adjustmentTime", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "latitude", - "getter_name": "latitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "longitude", - "getter_name": "longitude", - "moor_type": "double", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id" - ] - } - }, - { - "id": 25, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "memory_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(MemoryTypeEnum.values)", - "dart_type_name": "MemoryTypeEnum" - } - }, - { - "name": "data", - "getter_name": "data", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_saved", - "getter_name": "isSaved", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_saved\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_saved\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "memory_at", - "getter_name": "memoryAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "seen_at", - "getter_name": "seenAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "show_at", - "getter_name": "showAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "hide_at", - "getter_name": "hideAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 26, - "references": [ - 1, - 25 - ], - "type": "table", - "data": { - "name": "memory_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "memory_id", - "getter_name": "memoryId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES memory_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES memory_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "memory_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "asset_id", - "memory_id" - ] - } - }, - { - "id": 27, - "references": [ - 0 - ], - "type": "table", - "data": { - "name": "person_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "owner_id", - "getter_name": "ownerId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES user_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES user_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "user_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "face_asset_id", - "getter_name": "faceAssetId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_hidden", - "getter_name": "isHidden", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_hidden\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_hidden\" IN (0, 1))" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "color", - "getter_name": "color", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "birth_date", - "getter_name": "birthDate", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 28, - "references": [ - 1, - 27 - ], - "type": "table", - "data": { - "name": "asset_face_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "person_id", - "getter_name": "personId", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "defaultConstraints": "REFERENCES person_entity (id) ON DELETE SET NULL", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES person_entity (id) ON DELETE SET NULL" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "person_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "setNull" - } - } - ] - }, - { - "name": "image_width", - "getter_name": "imageWidth", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "image_height", - "getter_name": "imageHeight", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x1", - "getter_name": "boundingBoxX1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y1", - "getter_name": "boundingBoxY1", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_x2", - "getter_name": "boundingBoxX2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "bounding_box_y2", - "getter_name": "boundingBoxY2", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source_type", - "getter_name": "sourceType", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "deleted_at", - "getter_name": "deletedAt", - "moor_type": "dateTime", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 29, - "references": [], - "type": "table", - "data": { - "name": "store_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "string_value", - "getter_name": "stringValue", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "int_value", - "getter_name": "intValue", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 30, - "references": [], - "type": "table", - "data": { - "name": "trashed_local_asset_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "name", - "getter_name": "name", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "type", - "getter_name": "type", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetType.values)", - "dart_type_name": "AssetType" - } - }, - { - "name": "created_at", - "getter_name": "createdAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "width", - "getter_name": "width", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "height", - "getter_name": "height", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "duration_ms", - "getter_name": "durationMs", - "moor_type": "int", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "album_id", - "getter_name": "albumId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "checksum", - "getter_name": "checksum", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_favorite", - "getter_name": "isFavorite", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_favorite\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_favorite\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "orientation", - "getter_name": "orientation", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "source", - "getter_name": "source", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(TrashOrigin.values)", - "dart_type_name": "TrashOrigin" - } - }, - { - "name": "playback_style", - "getter_name": "playbackStyle", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('0')", - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetPlaybackStyle.values)", - "dart_type_name": "AssetPlaybackStyle" - } - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id", - "album_id" - ] - } - }, - { - "id": 31, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_edit_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "action", - "getter_name": "action", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "const EnumIndexConverter(AssetEditAction.values)", - "dart_type_name": "AssetEditAction" - } - }, - { - "name": "parameters", - "getter_name": "parameters", - "moor_type": "blob", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [], - "type_converter": { - "dart_expr": "editParameterConverter", - "dart_type_name": "Map" - } - }, - { - "name": "sequence", - "getter_name": "sequence", - "moor_type": "int", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 32, - "references": [], - "type": "table", - "data": { - "name": "settings", - "was_declared_in_moor": false, - "columns": [ - { - "name": "key", - "getter_name": "key", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "value", - "getter_name": "value", - "moor_type": "string", - "nullable": true, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "updated_at", - "getter_name": "updatedAt", - "moor_type": "dateTime", - "nullable": false, - "customConstraints": null, - "default_dart": "const CustomExpression('CURRENT_TIMESTAMP')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "key" - ] - } - }, - { - "id": 33, - "references": [ - 1 - ], - "type": "table", - "data": { - "name": "asset_ocr_entity", - "was_declared_in_moor": false, - "columns": [ - { - "name": "id", - "getter_name": "id", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "asset_id", - "getter_name": "assetId", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE", - "dialectAwareDefaultConstraints": { - "sqlite": "REFERENCES remote_asset_entity (id) ON DELETE CASCADE" - }, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [ - { - "foreign_key": { - "to": { - "table": "remote_asset_entity", - "column": "id" - }, - "initially_deferred": false, - "on_update": null, - "on_delete": "cascade" - } - } - ] - }, - { - "name": "x1", - "getter_name": "x1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y1", - "getter_name": "y1", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x2", - "getter_name": "x2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y2", - "getter_name": "y2", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x3", - "getter_name": "x3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y3", - "getter_name": "y3", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "x4", - "getter_name": "x4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "y4", - "getter_name": "y4", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "box_score", - "getter_name": "boxScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "text_score", - "getter_name": "textScore", - "moor_type": "double", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "recognized_text", - "getter_name": "recognizedText", - "moor_type": "string", - "nullable": false, - "customConstraints": null, - "default_dart": null, - "default_client_dart": null, - "dsl_features": [] - }, - { - "name": "is_visible", - "getter_name": "isVisible", - "moor_type": "bool", - "nullable": false, - "customConstraints": null, - "defaultConstraints": "CHECK (\"is_visible\" IN (0, 1))", - "dialectAwareDefaultConstraints": { - "sqlite": "CHECK (\"is_visible\" IN (0, 1))" - }, - "default_dart": "const CustomExpression('1')", - "default_client_dart": null, - "dsl_features": [] - } - ], - "is_virtual": false, - "without_rowid": true, - "constraints": [], - "strict": true, - "explicit_pk": [ - "id" - ] - } - }, - { - "id": 34, - "references": [ - 20 - ], - "type": "index", - "data": { - "on": 20, - "name": "idx_partner_shared_with_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 35, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_lat_lng", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)", - "unique": false, - "columns": [] - } - }, - { - "id": 36, - "references": [ - 21 - ], - "type": "index", - "data": { - "on": 21, - "name": "idx_remote_exif_city", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city\nON remote_exif_entity (city) WHERE city IS NOT NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 37, - "references": [ - 22 - ], - "type": "index", - "data": { - "on": 22, - "name": "idx_remote_album_asset_album_asset", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 38, - "references": [ - 24 - ], - "type": "index", - "data": { - "on": 24, - "name": "idx_remote_asset_cloud_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 39, - "references": [ - 27 - ], - "type": "index", - "data": { - "on": 27, - "name": "idx_person_owner_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 40, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_asset_face_person_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 41, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_asset_face_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 42, - "references": [ - 28 - ], - "type": "index", - "data": { - "on": 28, - "name": "idx_asset_face_visible_person", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person\nON asset_face_entity (person_id, asset_id)\nWHERE is_visible = 1 AND deleted_at IS NULL\n", - "unique": false, - "columns": [] - } - }, - { - "id": 43, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_trashed_local_asset_checksum", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)", - "unique": false, - "columns": [] - } - }, - { - "id": 44, - "references": [ - 30 - ], - "type": "index", - "data": { - "on": 30, - "name": "idx_trashed_local_asset_album", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 45, - "references": [ - 31 - ], - "type": "index", - "data": { - "on": 31, - "name": "idx_asset_edit_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)", - "unique": false, - "columns": [] - } - }, - { - "id": 46, - "references": [ - 33 - ], - "type": "index", - "data": { - "on": 33, - "name": "idx_asset_ocr_asset_id", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)", - "unique": false, - "columns": [] - } - } - ], - "fixed_sql": [ - { - "name": "user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NOT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"local_date_time\" TEXT NULL, \"thumb_hash\" TEXT NULL, \"deleted_at\" TEXT NULL, \"uploaded_at\" TEXT NULL, \"live_photo_video_id\" TEXT NULL, \"visibility\" INTEGER NOT NULL, \"stack_id\" TEXT NULL, \"library_id\" TEXT NULL, \"is_edited\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_edited\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "stack_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"stack_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"primary_asset_id\" TEXT NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"i_cloud_id\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"description\" TEXT NOT NULL DEFAULT '', \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"thumbnail_asset_id\" TEXT NULL REFERENCES remote_asset_entity (id) ON DELETE SET NULL, \"is_activity_enabled\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_activity_enabled\" IN (0, 1)), \"order\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"backup_selection\" INTEGER NOT NULL, \"is_ios_shared_album\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_ios_shared_album\" IN (0, 1)), \"linked_remote_album_id\" TEXT NULL REFERENCES remote_album_entity (id) ON DELETE SET NULL, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "local_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"local_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES local_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES local_album_entity (id) ON DELETE CASCADE, \"marker\" INTEGER NULL CHECK (\"marker\" IN (0, 1)), PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_local_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_local_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)" - } - ] - }, - { - "name": "idx_local_asset_created_at", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)" - } - ] - }, - { - "name": "idx_stack_primary_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)" - } - ] - }, - { - "name": "UQ_remote_assets_owner_library_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)" - } - ] - }, - { - "name": "idx_remote_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_remote_asset_stack_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)" - } - ] - }, - { - "name": "idx_remote_asset_owner_visibility_deleted_created", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)" - } - ] - }, - { - "name": "idx_remote_asset_uploaded", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)" - } - ] - }, - { - "name": "auth_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"auth_user_entity\" (\"id\" TEXT NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT NOT NULL, \"is_admin\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_admin\" IN (0, 1)), \"has_profile_image\" INTEGER NOT NULL DEFAULT 0 CHECK (\"has_profile_image\" IN (0, 1)), \"profile_changed_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"avatar_color\" INTEGER NOT NULL, \"quota_size_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"quota_usage_in_bytes\" INTEGER NOT NULL DEFAULT 0, \"pin_code\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "user_metadata_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"user_metadata_entity\" (\"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"key\" INTEGER NOT NULL, \"value\" BLOB NOT NULL, PRIMARY KEY (\"user_id\", \"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "partner_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"partner_entity\" (\"shared_by_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"shared_with_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"in_timeline\" INTEGER NOT NULL DEFAULT 0 CHECK (\"in_timeline\" IN (0, 1)), PRIMARY KEY (\"shared_by_id\", \"shared_with_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_exif_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_exif_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"city\" TEXT NULL, \"state\" TEXT NULL, \"country\" TEXT NULL, \"date_time_original\" TEXT NULL, \"description\" TEXT NULL, \"height\" INTEGER NULL, \"width\" INTEGER NULL, \"exposure_time\" TEXT NULL, \"f_number\" REAL NULL, \"file_size\" INTEGER NULL, \"focal_length\" REAL NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, \"iso\" INTEGER NULL, \"make\" TEXT NULL, \"model\" TEXT NULL, \"lens\" TEXT NULL, \"orientation\" TEXT NULL, \"time_zone\" TEXT NULL, \"rating\" INTEGER NULL, \"projection_type\" TEXT NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_album_user_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_album_user_entity\" (\"album_id\" TEXT NOT NULL REFERENCES remote_album_entity (id) ON DELETE CASCADE, \"user_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"role\" INTEGER NOT NULL, PRIMARY KEY (\"album_id\", \"user_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "remote_asset_cloud_id_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"remote_asset_cloud_id_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"cloud_id\" TEXT NULL, \"created_at\" TEXT NULL, \"adjustment_time\" TEXT NULL, \"latitude\" REAL NULL, \"longitude\" REAL NULL, PRIMARY KEY (\"asset_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"deleted_at\" TEXT NULL, \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"type\" INTEGER NOT NULL, \"data\" TEXT NOT NULL, \"is_saved\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_saved\" IN (0, 1)), \"memory_at\" TEXT NOT NULL, \"seen_at\" TEXT NULL, \"show_at\" TEXT NULL, \"hide_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "memory_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"memory_asset_entity\" (\"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"memory_id\" TEXT NOT NULL REFERENCES memory_entity (id) ON DELETE CASCADE, PRIMARY KEY (\"asset_id\", \"memory_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "person_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"person_entity\" (\"id\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"owner_id\" TEXT NOT NULL REFERENCES user_entity (id) ON DELETE CASCADE, \"name\" TEXT NOT NULL, \"face_asset_id\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL CHECK (\"is_favorite\" IN (0, 1)), \"is_hidden\" INTEGER NOT NULL CHECK (\"is_hidden\" IN (0, 1)), \"color\" TEXT NULL, \"birth_date\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_face_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_face_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"person_id\" TEXT NULL REFERENCES person_entity (id) ON DELETE SET NULL, \"image_width\" INTEGER NOT NULL, \"image_height\" INTEGER NOT NULL, \"bounding_box_x1\" INTEGER NOT NULL, \"bounding_box_y1\" INTEGER NOT NULL, \"bounding_box_x2\" INTEGER NOT NULL, \"bounding_box_y2\" INTEGER NOT NULL, \"source_type\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), \"deleted_at\" TEXT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "store_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"store_entity\" (\"id\" INTEGER NOT NULL, \"string_value\" TEXT NULL, \"int_value\" INTEGER NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "trashed_local_asset_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"trashed_local_asset_entity\" (\"name\" TEXT NOT NULL, \"type\" INTEGER NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), \"width\" INTEGER NULL, \"height\" INTEGER NULL, \"duration_ms\" INTEGER NULL, \"id\" TEXT NOT NULL, \"album_id\" TEXT NOT NULL, \"checksum\" TEXT NULL, \"is_favorite\" INTEGER NOT NULL DEFAULT 0 CHECK (\"is_favorite\" IN (0, 1)), \"orientation\" INTEGER NOT NULL DEFAULT 0, \"source\" INTEGER NOT NULL, \"playback_style\" INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (\"id\", \"album_id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_edit_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_edit_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"action\" INTEGER NOT NULL, \"parameters\" BLOB NOT NULL, \"sequence\" INTEGER NOT NULL, PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "settings", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"settings\" (\"key\" TEXT NOT NULL, \"value\" TEXT NULL, \"updated_at\" TEXT NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (\"key\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "asset_ocr_entity", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE TABLE IF NOT EXISTS \"asset_ocr_entity\" (\"id\" TEXT NOT NULL, \"asset_id\" TEXT NOT NULL REFERENCES remote_asset_entity (id) ON DELETE CASCADE, \"x1\" REAL NOT NULL, \"y1\" REAL NOT NULL, \"x2\" REAL NOT NULL, \"y2\" REAL NOT NULL, \"x3\" REAL NOT NULL, \"y3\" REAL NOT NULL, \"x4\" REAL NOT NULL, \"y4\" REAL NOT NULL, \"box_score\" REAL NOT NULL, \"text_score\" REAL NOT NULL, \"recognized_text\" TEXT NOT NULL, \"is_visible\" INTEGER NOT NULL DEFAULT 1 CHECK (\"is_visible\" IN (0, 1)), PRIMARY KEY (\"id\")) WITHOUT ROWID, STRICT;" - } - ] - }, - { - "name": "idx_partner_shared_with_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)" - } - ] - }, - { - "name": "idx_lat_lng", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)" - } - ] - }, - { - "name": "idx_remote_exif_city", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL" - } - ] - }, - { - "name": "idx_remote_album_asset_album_asset", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)" - } - ] - }, - { - "name": "idx_remote_asset_cloud_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)" - } - ] - }, - { - "name": "idx_person_owner_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)" - } - ] - }, - { - "name": "idx_asset_face_person_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)" - } - ] - }, - { - "name": "idx_asset_face_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_face_visible_person", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL" - } - ] - }, - { - "name": "idx_trashed_local_asset_checksum", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)" - } - ] - }, - { - "name": "idx_trashed_local_asset_album", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)" - } - ] - }, - { - "name": "idx_asset_edit_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)" - } - ] - }, - { - "name": "idx_asset_ocr_asset_id", - "sql": [ - { - "dialect": "sqlite", - "sql": "CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)" - } - ] - } - ] -} \ No newline at end of file diff --git a/mobile/immich_lint/analysis_options.yaml b/mobile/immich_lint/analysis_options.yaml new file mode 100644 index 0000000000..572dd239d0 --- /dev/null +++ b/mobile/immich_lint/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml diff --git a/mobile/immich_lint/lib/immich_mobile_immich_lint.dart b/mobile/immich_lint/lib/immich_mobile_immich_lint.dart new file mode 100644 index 0000000000..7d3ed4757e --- /dev/null +++ b/mobile/immich_lint/lib/immich_mobile_immich_lint.dart @@ -0,0 +1,89 @@ +import 'package:analyzer/error/error.dart' show ErrorSeverity; +import 'package:analyzer/error/listener.dart'; +import 'package:custom_lint_builder/custom_lint_builder.dart'; +// ignore: depend_on_referenced_packages +import 'package:glob/glob.dart'; + +PluginBase createPlugin() => ImmichLinter(); + +class ImmichLinter extends PluginBase { + @override + List getLintRules(CustomLintConfigs configs) { + final List rules = []; + for (final entry in configs.rules.entries) { + if (entry.value.enabled && entry.key.startsWith("import_rule_")) { + final code = makeCode(entry.key, entry.value); + final allowedPaths = getStrings(entry.value, "allowed"); + final forbiddenPaths = getStrings(entry.value, "forbidden"); + final restrict = getStrings(entry.value, "restrict"); + rules.add(ImportRule(code, buildGlob(allowedPaths), + buildGlob(forbiddenPaths), restrict)); + } + } + return rules; + } + + static LintCode makeCode(String name, LintOptions options) => LintCode( + name: name, + problemMessage: options.json["message"] as String, + errorSeverity: ErrorSeverity.WARNING, + ); + + static List getStrings(LintOptions options, String field) { + final List result = []; + final excludeOption = options.json[field]; + if (excludeOption is String) { + result.add(excludeOption); + } else if (excludeOption is List) { + result.addAll(excludeOption.map((option) => option)); + } + return result; + } + + Glob? buildGlob(List globs) { + if (globs.isEmpty) return null; + if (globs.length == 1) return Glob(globs[0], caseSensitive: true); + return Glob("{${globs.join(",")}}", caseSensitive: true); + } +} + +// ignore: must_be_immutable +class ImportRule extends DartLintRule { + ImportRule(LintCode code, this._allowed, this._forbidden, this._restrict) + : super(code: code); + + final Glob? _allowed; + final Glob? _forbidden; + final List _restrict; + int _rootOffset = -1; + + @override + void run( + CustomLintResolver resolver, + ErrorReporter reporter, + CustomLintContext context, + ) { + if (_rootOffset == -1) { + const project = "/immich/mobile/"; + _rootOffset = + resolver.path.toLowerCase().indexOf(project) + project.length; + } + final path = resolver.path.substring(_rootOffset); + + if ((_allowed != null && _allowed!.matches(path)) && + (_forbidden == null || !_forbidden!.matches(path))) { + return; + } + + context.registry.addImportDirective((node) { + final uri = node.uri.stringValue; + if (uri == null) return; + for (final restricted in _restrict) { + if (uri.startsWith(restricted) == true) { + reporter.atNode(node, code); + return; + } + } + }); + } +} diff --git a/mobile/immich_lint/pubspec.lock b/mobile/immich_lint/pubspec.lock new file mode 100644 index 0000000000..0e4b08be87 --- /dev/null +++ b/mobile/immich_lint/pubspec.lock @@ -0,0 +1,365 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f + url: "https://pub.dev" + source: hosted + version: "82.0.0" + analyzer: + dependency: "direct main" + description: + name: analyzer + sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0" + url: "https://pub.dev" + source: hosted + version: "7.4.5" + analyzer_plugin: + dependency: "direct main" + description: + name: analyzer_plugin + sha256: ee188b6df6c85f1441497c7171c84f1392affadc0384f71089cb10a3bc508cef + url: "https://pub.dev" + source: hosted + version: "0.13.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + custom_lint: + dependency: transitive + description: + name: custom_lint + sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_builder: + dependency: "direct main" + description: + name: custom_lint_builder + sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: cba5b6d7a6217312472bf4468cdf68c949488aed7ffb0eab792cd0b6c435054d + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.4.5" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + glob: + dependency: "direct main" + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + url: "https://pub.dev" + source: hosted + version: "4.3.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + lints: + dependency: "direct dev" + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + watcher: + dependency: transitive + description: + name: watcher + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.8.0 <4.0.0" diff --git a/mobile/immich_lint/pubspec.yaml b/mobile/immich_lint/pubspec.yaml new file mode 100644 index 0000000000..e49e9c5010 --- /dev/null +++ b/mobile/immich_lint/pubspec.yaml @@ -0,0 +1,14 @@ +name: immich_mobile_immich_lint +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + analyzer: ^7.0.0 + analyzer_plugin: ^0.13.0 + custom_lint_builder: ^0.7.5 + glob: ^2.1.2 + +dev_dependencies: + lints: ^6.0.0 diff --git a/mobile/integration_test/background_sync_teardown_test.dart b/mobile/integration_test/background_sync_teardown_test.dart deleted file mode 100644 index 3e88109c82..0000000000 --- a/mobile/integration_test/background_sync_teardown_test.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'dart:async'; - -import 'package:drift/drift.dart' show Value; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/utils/background_sync.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/main.dart' as app; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/bootstrap.dart'; -import 'package:immich_mobile/wm_executor.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:openapi/api.dart'; - -import 'test_utils/fake_immich_server.dart'; - -void main() { - final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - // These tests do real I/O without pumping a widget tree, so disable the fake async clock - binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; - - late Drift drift; - late FakeImmichServer server; - - setUpAll(() async { - await app.initApp(); - (drift, _) = await Bootstrap.initDomain(); - }); - - setUp(() async { - await workerManagerPatch.init(dynamicSpawning: true); - server = await FakeImmichServer.start(); - await ApiService().resolveAndSetEndpoint(server.endpoint); - await drift.delete(drift.userEntity).go(); - await Store.delete(StoreKey.syncMigrationStatus); - }); - - tearDown(() async { - await workerManagerPatch.dispose(); - await server.close(); - await Store.delete(StoreKey.serverEndpoint); - await Store.delete(StoreKey.syncMigrationStatus); - }); - - void sendUser(SyncStream stream, String id, String name) { - stream.send( - type: SyncEntityType.userV1.toString(), - data: SyncUserV1( - id: id, - name: name, - email: '$id@test.com', - hasProfileImage: false, - deletedAt: null, - profileChangedAt: DateTime.utc(2025), - ).toJson(), - ack: id, - ); - } - - Future dbReadable() async { - try { - await drift.customSelect('SELECT 1').get().timeout(const Duration(seconds: 5)); - return true; - } catch (_) { - return false; - } - } - - Future userCount() async => (await drift.select(drift.userEntity).get()).length; - - // Starts a remote sync and resolves once its /sync/stream request is open. - Future<(Future, SyncStream)> startSync() async { - final sync = BackgroundSyncManager().syncRemote(); - final stream = await server.streamOpened.timeout( - const Duration(seconds: 30), - onTimeout: () => fail('sync isolate never opened /sync/stream'), - ); - return (sync, stream); - } - - testWidgets('a full sync ingests streamed events into the shared DB', (tester) async { - expect(await userCount(), 0); - - final (sync, stream) = await startSync(); - - sendUser(stream, 'u1', 'Alice'); - sendUser(stream, 'u2', 'Bob'); - await stream.close(); - - final result = await sync.timeout( - const Duration(seconds: 30), - onTimeout: () => fail('sync did not complete after the stream ended'), - ); - expect(result, isTrue); - expect(await userCount(), 2); - expect(server.ackRequests, greaterThan(0)); - }); - - testWidgets('disposing the pool during an in-flight sync drains promptly', (tester) async { - final (sync, _) = await startSync(); - - final sw = Stopwatch()..start(); - await workerManagerPatch.dispose().timeout( - const Duration(seconds: 15), - onTimeout: () => fail('dispose() hung — worker did not drain and exit'), - ); - expect(sw.elapsed, lessThan(const Duration(seconds: 10)), reason: 'abort-driven, not socket-timeout bound'); - - expect(await sync.timeout(const Duration(seconds: 5), onTimeout: () => false), isFalse); - }); - - testWidgets('tearing down a worker blocked mid-write leaves the DB usable', (tester) async { - final (sync, stream) = await startSync(); - - // Hold an exclusive write transaction so the worker's write is blocked. The lock is taken only - // after the stream opens to avoid blocking the worker's own startup DB reads. - final releaseTxn = Completer(); - final txnHeld = Completer(); - final txn = drift.transaction(() async { - await drift.into(drift.userEntity).insert( - UserEntityCompanion.insert( - id: 'holder', - name: 'holder', - email: 'holder@test.com', - hasProfileImage: const Value(false), - profileChangedAt: Value(DateTime.utc(2025)), - ), - ); - txnHeld.complete(); - await releaseTxn.future; - }); - await txnHeld.future; - - sendUser(stream, 'u1', 'Alice'); - await stream.close(); - - // dispose() can only finish once the worker unwinds, which is blocked on the - // lock — so start it, release the lock, then await completion. - final disposed = workerManagerPatch.dispose(); - releaseTxn.complete(); - await txn; - await disposed.timeout( - const Duration(seconds: 15), - onTimeout: () => fail('dispose() hung after releasing the write lock'), - ); - await sync.timeout(const Duration(seconds: 5), onTimeout: () => false); - - expect(await dbReadable(), isTrue); - final users = await drift.select(drift.userEntity).get(); - expect(users.map((u) => u.id), contains('holder')); - }); -} diff --git a/mobile/integration_test/failed_sync_resume_e2e_test.dart b/mobile/integration_test/failed_sync_resume_e2e_test.dart deleted file mode 100644 index 255074df93..0000000000 --- a/mobile/integration_test/failed_sync_resume_e2e_test.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/services/background_worker.service.dart'; -import 'package:immich_mobile/domain/utils/background_sync.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/main.dart' as app; -import 'package:immich_mobile/platform/background_worker_api.g.dart'; -import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; -import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/bootstrap.dart'; -import 'package:immich_mobile/wm_executor.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'test_utils/fake_immich_server.dart'; - -// Issue #28082 end-to-end: a resume after a sync froze mid-flight starts a fresh sync. -// Kept in its own file on purpose: run after the failed_sync_resume tests in one process, -// the resume sync starves past its window - a cross-test interaction that survived pool, -// sqlite and bg-worker audits unnamed. One file per process is device-proven green. -void main() { - final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; - - late Drift drift; - late FakeImmichServer server; - - // main()'s formula with a higher floor: a wedged frozen worker plus a full local - // sync must not starve the fresh resume sync out of its 25s window on 4 cores. - final poolSize = max(Platform.numberOfProcessors - 1, 8); - - setUpAll(() async { - await app.initApp(); - (drift, _) = await Bootstrap.initDomain(); - // A background-worker schedule persisted by real app use on this device can - // launch a second engine mid-file (own isolate pool + full sync) and starve - // these tests on a small device. Unregister it for the whole run. - await BackgroundWorkerFgService(BackgroundWorkerFgHostApi()).disable(); - }); - - setUp(() async { - // A task completing while dispose tears the pool down re-warms it (_schedule - // on the cleared pool re-creates workers), and init on a warm pool is silently - // ignored - poolSize would never apply. Reset first, then verify it took. - await workerManagerPatch.dispose(); - await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: poolSize); - expect( - workerManagerPatch.pool.length, - poolSize, - reason: 'init was ignored: a straggler from the previous test re-warmed the pool', - ); - server = await FakeImmichServer.start(); - await ApiService().resolveAndSetEndpoint(server.endpoint); - await drift.delete(drift.userEntity).go(); - }); - - tearDown(() async { - // Close the server first so any held-open sync stream ends and its isolate unwinds, - // then drain the pool - otherwise dispose waits on the frozen read. - await server.close(); - await workerManagerPatch.dispose(); - }); - - testWidgets('a resume after a sync froze mid-flight starts a fresh sync', (tester) async { - final manager = BackgroundSyncManager(); - // Not disposed on purpose: driftOverride closes the drift on dispose, and that - // drift belongs to setUpAll. The container only holds the shared drift and a - // fire-and-forget resume; the frozen isolates + server are drained by tearDown. - final container = ProviderContainer( - overrides: [driftProvider.overrideWith(driftOverride(drift)), backgroundSyncProvider.overrideWithValue(manager)], - ); - - // A first sync opens /sync/stream and never finishes - the frozen state a - // suspended sync isolate is left in. Holding the stream open keeps - // _syncTask non-null, exactly as it is across an iOS process suspension. - unawaited(manager.syncRemote()); - await server - .streamOpenedNth(1) - .timeout(const Duration(seconds: 30), onTimeout: () => fail('first sync isolate never opened /sync/stream')); - - // The lifecycle then goes background -> foreground. handleAppResume runs the - // resume sync exactly once. On the buggy build it hangs on the stale task's - // future, so it is not awaited here. - final notifier = container.read(appStateProvider.notifier); - await notifier.handleAppPause(); - unawaited(notifier.handleAppResume()); - - await server - .streamOpenedNth(2) - .timeout( - const Duration(seconds: 25), - onTimeout: () => fail('resume did not start a fresh remote sync - the stale frozen sync blocked it (#28082)'), - ); - expect(server.streamOpenCount, greaterThanOrEqualTo(2)); - }); -} diff --git a/mobile/integration_test/failed_sync_resume_test.dart b/mobile/integration_test/failed_sync_resume_test.dart deleted file mode 100644 index c23319dffe..0000000000 --- a/mobile/integration_test/failed_sync_resume_test.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/services/background_worker.service.dart'; -import 'package:immich_mobile/domain/utils/background_sync.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/main.dart' as app; -import 'package:immich_mobile/platform/background_worker_api.g.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/bootstrap.dart'; -import 'package:immich_mobile/wm_executor.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'test_utils/fake_immich_server.dart'; - -// Issue #28082: a remote sync in-flight when the app is backgrounded stays referenced -// but frozen across the suspension. On resume the app drops the stale task -// (cancelResumeSyncs) and starts a fresh sync. -// -// Device/emulator tests: real worker isolates + a real drift db + a loopback fake server -// (same pattern as background_sync_teardown_test). The mobile integration-test CI job is -// disabled in test.yml, so like Mert's teardown test this is a local/on-device guard. -// -// The end-to-end resume test lives in failed_sync_resume_e2e_test.dart - one process -// per file keeps it clear of the cross-test interaction described there. -void main() { - final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - binding.framePolicy = LiveTestWidgetsFlutterBindingFramePolicy.fullyLive; - - late Drift drift; - late FakeImmichServer server; - - // main()'s formula with a higher floor: a wedged frozen worker plus a full local - // sync must not starve the fresh resume sync out of its 25s window on 4 cores. - final poolSize = max(Platform.numberOfProcessors - 1, 8); - - setUpAll(() async { - await app.initApp(); - (drift, _) = await Bootstrap.initDomain(); - // A background-worker schedule persisted by real app use on this device can - // launch a second engine mid-file (own isolate pool + full sync) and starve - // these tests on a small device. Unregister it for the whole run. - await BackgroundWorkerFgService(BackgroundWorkerFgHostApi()).disable(); - }); - - setUp(() async { - // A task completing while dispose tears the pool down re-warms it (_schedule - // on the cleared pool re-creates workers), and init on a warm pool is silently - // ignored - poolSize would never apply. Reset first, then verify it took. - await workerManagerPatch.dispose(); - await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: poolSize); - expect( - workerManagerPatch.pool.length, - poolSize, - reason: 'init was ignored: a straggler from the previous test re-warmed the pool', - ); - server = await FakeImmichServer.start(); - await ApiService().resolveAndSetEndpoint(server.endpoint); - await drift.delete(drift.userEntity).go(); - }); - - tearDown(() async { - // Close the server first so any held-open sync stream ends and its isolate unwinds, - // then drain the pool - otherwise dispose waits on the frozen read. - await server.close(); - await workerManagerPatch.dispose(); - }); - - // Self-contained (bare manager, no fire-and-forget resume), so it runs first: its - // frozen syncs are fully drained by tearDown. - testWidgets('a cancelled sync task does not clear the slot of the fresh task that superseded it', (tester) async { - final manager = BackgroundSyncManager(); - - // First sync opens /sync/stream and is held open - the frozen suspended state. - unawaited(manager.syncRemote()); - await server - .streamOpenedNth(1) - .timeout(const Duration(seconds: 30), onTimeout: () => fail('first sync isolate never opened /sync/stream')); - - // Resume drops the stale task then immediately starts a fresh one, exactly as - // _handleBetaTimelineResume does. cancelResumeSyncs cancels the first task; its - // completion chain then fires and must NOT null the fresh task's slot. - unawaited(manager.cancelResumeSyncs()); - unawaited(manager.syncRemote()); - await server - .streamOpenedNth(2) - .timeout(const Duration(seconds: 30), onTimeout: () => fail('fresh sync isolate never opened /sync/stream')); - - // A third sync stream only opens if the cancelled task cleared the fresh slot, - // letting the dedupe guard start a redundant sync. - var thirdOpened = false; - unawaited(server.streamOpenedNth(3).then((_) => thirdOpened = true)); - - // Let the cancelled task's completion chain settle, then ask to sync again. - await Future.delayed(const Duration(milliseconds: 200)); - unawaited(manager.syncRemote()); - await Future.delayed(const Duration(seconds: 3)); - - expect( - thirdOpened, - isFalse, - reason: 'the cancelled task cleared the fresh task slot, so a redundant third sync started (#28082 clobber)', - ); - expect(server.streamOpenCount, 2); - }); - - // The false-error-flash fix: a task cancelled by cancelResumeSyncs completes with a - // CanceledError, which is not a sync failure and must not reach onRemoteSyncError. - // Before the filter the stale task reported an error right after the fresh sync - // started, so the status UI showed a failure for the whole healthy run. - testWidgets('a cancelled sync does not report a false error to the status callbacks', (tester) async { - final errors = []; - final manager = BackgroundSyncManager(onRemoteSyncError: errors.add); - - // Hold the stream open so the task is genuinely in-flight when it is cancelled. - unawaited(manager.syncRemote()); - await server - .streamOpenedNth(1) - .timeout(const Duration(seconds: 30), onTimeout: () => fail('sync isolate never opened /sync/stream')); - - await manager.cancelResumeSyncs(); - // Let the cancelled task's completion chain run before checking the callbacks. - await Future.delayed(const Duration(milliseconds: 100)); - - expect( - errors, - isEmpty, - reason: 'a cancelled task is not a real error; its CanceledError must not fire onRemoteSyncError', - ); - }); -} diff --git a/mobile/integration_test/module_login/login_input_validation_test.dart b/mobile/integration_test/module_login/login_input_validation_test.dart index 504940f643..1b47b6d8cc 100644 --- a/mobile/integration_test/module_login/login_input_validation_test.dart +++ b/mobile/integration_test/module_login/login_input_validation_test.dart @@ -1,5 +1,5 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import '../test_utils/general_helper.dart'; @@ -7,32 +7,45 @@ void main() async { await ImmichTestHelper.initialize(); group("Login input validation test", () { - immichWidgetTest("Test leading/trailing whitespace", (tester, helper) async { + immichWidgetTest("Test leading/trailing whitespace", + (tester, helper) async { await helper.loginHelper.waitForLoginScreen(); await helper.loginHelper.acknowledgeNewServerVersion(); - await helper.loginHelper.enterCredentials(email: " demo@immich.app"); + await helper.loginHelper.enterCredentials( + email: " demo@immich.app", + ); await tester.pump(const Duration(milliseconds: 300)); - expect(find.text(StaticTranslations.instance.login_form_err_leading_whitespace), findsOneWidget); + expect( + find.text("login_form_err_leading_whitespace".tr()), + findsOneWidget, + ); - await helper.loginHelper.enterCredentials(email: "demo@immich.app "); + await helper.loginHelper.enterCredentials( + email: "demo@immich.app ", + ); await tester.pump(const Duration(milliseconds: 300)); - expect(find.text(StaticTranslations.instance.login_form_err_trailing_whitespace), findsOneWidget); + expect( + find.text("login_form_err_trailing_whitespace".tr()), + findsOneWidget, + ); }); immichWidgetTest("Test invalid email", (tester, helper) async { await helper.loginHelper.waitForLoginScreen(); await helper.loginHelper.acknowledgeNewServerVersion(); - await helper.loginHelper.enterCredentials(email: "demo.immich.app"); + await helper.loginHelper.enterCredentials( + email: "demo.immich.app", + ); await tester.pump(const Duration(milliseconds: 300)); - expect(find.text(StaticTranslations.instance.login_form_err_invalid_email), findsOneWidget); + expect(find.text("login_form_err_invalid_email".tr()), findsOneWidget); }); }); } diff --git a/mobile/integration_test/test_utils/fake_immich_server.dart b/mobile/integration_test/test_utils/fake_immich_server.dart deleted file mode 100644 index 7c63098c52..0000000000 --- a/mobile/integration_test/test_utils/fake_immich_server.dart +++ /dev/null @@ -1,132 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -/// A dummy localhost server that implements only the endpoints that remote-sync touches. -class FakeImmichServer { - FakeImmichServer._(this._server, this.version); - - final HttpServer _server; - final (int, int, int) version; - - final Completer _streamOpened = Completer(); - final List _streamOpens = []; - final Map> _openWaiters = {}; - - int ackRequests = 0; - - String get endpoint => 'http://${_server.address.host}:${_server.port}/api'; - - /// Resolves when the sync isolate opens `POST /sync/stream`. - Future get streamOpened => _streamOpened.future; - - /// How many `/sync/stream` requests have opened so far. - int get streamOpenCount => _streamOpens.length; - - /// Resolves when the [n]-th (1-indexed) `/sync/stream` opens. - Future streamOpenedNth(int n) { - if (_streamOpens.length >= n) { - return Future.value(_streamOpens[n - 1]); - } - return (_openWaiters[n] ??= Completer()).future; - } - - static Future start({(int, int, int) version = (3, 0, 0)}) async { - final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); - final fake = FakeImmichServer._(server, version); - fake._listen(); - return fake; - } - - void _listen() { - // A connection torn down mid-write during teardown is expected - _server.listen((request) => unawaited(_route(request).catchError((_) {}))); - } - - Future _route(HttpRequest request) async { - final method = request.method; - final path = request.uri.path; - - if (method == 'GET' && path == '/api/server/ping') { - return _respondJson(request, {'res': 'pong'}); - } - if (method == 'GET' && path == '/api/server/version') { - final (major, minor, patch) = version; - return _respondJson(request, {'major': major, 'minor': minor, 'patch': patch}); - } - if (path == '/api/sync/ack') { - if (method != 'DELETE') { - ackRequests++; - } - return _respondEmpty(request); - } - if (method == 'POST' && path == '/api/sync/stream') { - return _openSyncStream(request); - } - return _respondEmpty(request, status: HttpStatus.notFound); - } - - Future _openSyncStream(HttpRequest request) async { - await request.drain(); - request.response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType('application', 'jsonlines+json') - ..contentLength = - -1 // chunked: stays open to stream incrementally - ..bufferOutput = false; - // Flush headers so the client's send() resolves and enters its read loop. - await request.response.flush(); - final stream = SyncStream._(request.response); - _streamOpens.add(stream); - if (!_streamOpened.isCompleted) { - _streamOpened.complete(stream); - } - _openWaiters.remove(_streamOpens.length)?.complete(stream); - } - - Future _respondJson(HttpRequest request, Object body) async { - await request.drain(); - request.response - ..statusCode = HttpStatus.ok - ..headers.contentType = ContentType.json - ..write(jsonEncode(body)); - await request.response.close(); - } - - Future _respondEmpty(HttpRequest request, {int status = HttpStatus.ok}) async { - await request.drain(); - request.response.statusCode = status; - await request.response.close(); - } - - Future close() async { - for (final stream in _streamOpens) { - await stream.close(); - } - await _server.close(force: true); - } -} - -/// Handle to the open `/sync/stream` response: push jsonlines events, then end. -class SyncStream { - SyncStream._(this._response); - - final HttpResponse _response; - bool _closed = false; - - /// [data] should be a Sync*V1 DTO's `toJson()` so the parser's `fromJson` round-trips it. - void send({required String type, required Object data, required String ack}) { - if (_closed) { - return; - } - _response.write('${jsonEncode({'type': type, 'data': data, 'ack': ack})}\n'); - } - - Future close() async { - if (_closed) { - return; - } - _closed = true; - await _response.close(); - } -} diff --git a/mobile/integration_test/test_utils/general_helper.dart b/mobile/integration_test/test_utils/general_helper.dart index 66955364f3..d6065170ef 100644 --- a/mobile/integration_test/test_utils/general_helper.dart +++ b/mobile/integration_test/test_utils/general_helper.dart @@ -5,6 +5,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/main.dart' as app; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:integration_test/integration_test.dart'; @@ -38,11 +39,20 @@ class ImmichTestHelper { static Future loadApp(WidgetTester tester) async { await EasyLocalization.ensureInitialized(); // Clear all data from Isar (reuse existing instance if available) - final (drift, _) = await Bootstrap.initDomain(); + final (isar, drift, logDb) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDb); await Store.clear(); + await isar.writeTxn(() => isar.clear()); // Load main Widget await tester.pumpWidget( - ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const app.MainWidget()), + ProviderScope( + overrides: [ + dbProvider.overrideWithValue(isar), + isarProvider.overrideWithValue(isar), + driftProvider.overrideWith(driftOverride(drift)), + ], + child: const app.MainWidget(), + ), ); // Post run tasks await EasyLocalization.ensureInitialized(); diff --git a/mobile/integration_test/test_utils/login_helper.dart b/mobile/integration_test/test_utils/login_helper.dart index ad61b77a53..cfbc5a9214 100644 --- a/mobile/integration_test/test_utils/login_helper.dart +++ b/mobile/integration_test/test_utils/login_helper.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'general_helper.dart'; @@ -23,7 +23,11 @@ class ImmichTestLoginHelper { return true; } - Future enterCredentials({String server = "", String email = "", String password = ""}) async { + Future enterCredentials({ + String server = "", + String email = "", + String password = "", + }) async { final loginForms = find.byType(TextFormField); await tester.enterText(loginForms.at(0), email); @@ -40,30 +44,49 @@ class ImmichTestLoginHelper { } Future enterCredentialsOf(LoginCredentials credentials) async { - await enterCredentials(server: credentials.server, email: credentials.email, password: credentials.password); + await enterCredentials( + server: credentials.server, + email: credentials.email, + password: credentials.password, + ); } Future pressLoginButton() async { - await pumpUntilFound(tester, find.textContaining(StaticTranslations.instance.login)); - final button = find.textContaining(StaticTranslations.instance.login); + await pumpUntilFound( + tester, + find.textContaining("login_form_button_text".tr()), + ); + final button = find.textContaining("login_form_button_text".tr()); await tester.tap(button); } Future assertLoginSuccess() async { - await pumpUntilFound(tester, find.text(StaticTranslations.instance.home_page_building_timeline)); + await pumpUntilFound(tester, find.text("home_page_building_timeline".tr())); } Future assertLoginFailed() async { - await pumpUntilFound(tester, find.text(StaticTranslations.instance.login_form_failed_login)); + await pumpUntilFound(tester, find.text("login_form_failed_login".tr())); } } enum LoginCredentials { - testInstance("https://flutter-int-test.preview.immich.app", "demo@immich.app", "demo"), + testInstance( + "https://flutter-int-test.preview.immich.app", + "demo@immich.app", + "demo", + ), - testInstanceButWithWrongPassword("https://flutter-int-test.preview.immich.app", "demo@immich.app", "wrong"), + testInstanceButWithWrongPassword( + "https://flutter-int-test.preview.immich.app", + "demo@immich.app", + "wrong", + ), - wrongInstanceUrl("https://does-not-exist.preview.immich.app", "demo@immich.app", "demo"); + wrongInstanceUrl( + "https://does-not-exist.preview.immich.app", + "demo@immich.app", + "demo", + ); const LoginCredentials(this.server, this.email, this.password); diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore index 8c827d90e9..63e84080df 100644 --- a/mobile/ios/.gitignore +++ b/mobile/ios/.gitignore @@ -26,9 +26,6 @@ Flutter/flutter_export_environment.sh ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* -# Local signing overrides. -Signing.local.xcconfig - # Exceptions to above rules. !default.mode1v3 !default.mode2v3 diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/ios/Flutter/AppFrameworkInfo.plist index 391a902b2b..1dc6cf7652 100644 --- a/mobile/ios/Flutter/AppFrameworkInfo.plist +++ b/mobile/ios/Flutter/AppFrameworkInfo.plist @@ -20,5 +20,7 @@ ???? CFBundleVersion 1.0 + MinimumOSVersion + 13.0 diff --git a/mobile/ios/Gemfile b/mobile/ios/Gemfile index 180c2a00c2..3b6771ad35 100644 --- a/mobile/ios/Gemfile +++ b/mobile/ios/Gemfile @@ -2,5 +2,4 @@ source "https://rubygems.org" gem "fastlane" gem "cocoapods" -gem "abbrev" # Required for Ruby 3.4+ -gem "multi_json" \ No newline at end of file +gem "abbrev" # Required for Ruby 3.4+ \ No newline at end of file diff --git a/mobile/ios/Podfile b/mobile/ios/Podfile index f697d17411..a236b027f5 100644 --- a/mobile/ios/Podfile +++ b/mobile/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '15.0' +platform :ios, '14.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -45,7 +45,7 @@ post_install do |installer| installer.generated_projects.each do |project| project.targets.each do |target| target.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' end end end diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 75eda67ce1..e1ec4aff07 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -1,19 +1,64 @@ PODS: + - background_downloader (0.0.1): + - Flutter - bonsoir_darwin (0.0.1): - Flutter - FlutterMacOS + - connectivity_plus (0.0.1): + - Flutter - cupertino_http (0.0.1): - Flutter - FlutterMacOS + - device_info_plus (0.0.1): + - Flutter - Flutter (1.0.0) - flutter_local_notifications (0.0.1): - Flutter + - flutter_native_splash (2.4.3): + - Flutter - flutter_secure_storage (6.0.0): - Flutter + - flutter_udid (0.0.1): + - Flutter + - SAMKeychain + - flutter_web_auth_2 (3.0.0): + - Flutter - fluttertoast (0.0.2): - Flutter + - geolocator_apple (1.2.0): + - Flutter + - home_widget (0.0.1): + - Flutter + - image_picker_ios (0.0.1): + - Flutter + - integration_test (0.0.1): + - Flutter + - isar_community_flutter_libs (1.0.0): + - Flutter + - local_auth_darwin (0.0.1): + - Flutter + - FlutterMacOS + - MapLibre (6.14.0) + - maplibre_gl (0.0.1): + - Flutter + - MapLibre (= 6.14.0) - native_video_player (1.0.0): - Flutter + - network_info_plus (0.0.1): + - Flutter + - objective_c (0.0.1): + - Flutter + - package_info_plus (0.4.5): + - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - permission_handler_apple (9.3.0): + - Flutter + - photo_manager (3.7.1): + - Flutter + - FlutterMacOS + - SAMKeychain (1.5.3) - share_handler_ios (0.0.14): - Flutter - share_handler_ios/share_handler_ios_models (= 0.0.14) @@ -22,49 +67,189 @@ PODS: - Flutter - share_handler_ios_models - share_handler_ios_models (0.0.9) + - share_plus (0.0.1): + - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - sqlite3 (3.49.1): + - sqlite3/common (= 3.49.1) + - sqlite3/common (3.49.1) + - sqlite3/dbstatvtab (3.49.1): + - sqlite3/common + - sqlite3/fts5 (3.49.1): + - sqlite3/common + - sqlite3/perf-threadsafe (3.49.1): + - sqlite3/common + - sqlite3/rtree (3.49.1): + - sqlite3/common + - sqlite3_flutter_libs (0.0.1): + - Flutter + - FlutterMacOS + - sqlite3 (~> 3.49.1) + - sqlite3/dbstatvtab + - sqlite3/fts5 + - sqlite3/perf-threadsafe + - sqlite3/rtree + - url_launcher_ios (0.0.1): + - Flutter + - wakelock_plus (0.0.1): + - Flutter DEPENDENCIES: + - background_downloader (from `.symlinks/plugins/background_downloader/ios`) - bonsoir_darwin (from `.symlinks/plugins/bonsoir_darwin/darwin`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - flutter_udid (from `.symlinks/plugins/flutter_udid/ios`) + - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) + - geolocator_apple (from `.symlinks/plugins/geolocator_apple/ios`) + - home_widget (from `.symlinks/plugins/home_widget/ios`) + - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - isar_community_flutter_libs (from `.symlinks/plugins/isar_community_flutter_libs/ios`) + - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) + - maplibre_gl (from `.symlinks/plugins/maplibre_gl/ios`) - native_video_player (from `.symlinks/plugins/native_video_player/ios`) + - network_info_plus (from `.symlinks/plugins/network_info_plus/ios`) + - objective_c (from `.symlinks/plugins/objective_c/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`) - share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`) + - share_plus (from `.symlinks/plugins/share_plus/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) + +SPEC REPOS: + trunk: + - MapLibre + - SAMKeychain + - sqlite3 EXTERNAL SOURCES: + background_downloader: + :path: ".symlinks/plugins/background_downloader/ios" bonsoir_darwin: :path: ".symlinks/plugins/bonsoir_darwin/darwin" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" cupertino_http: :path: ".symlinks/plugins/cupertino_http/darwin" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" Flutter: :path: Flutter flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_native_splash: + :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + flutter_udid: + :path: ".symlinks/plugins/flutter_udid/ios" + flutter_web_auth_2: + :path: ".symlinks/plugins/flutter_web_auth_2/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" + geolocator_apple: + :path: ".symlinks/plugins/geolocator_apple/ios" + home_widget: + :path: ".symlinks/plugins/home_widget/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + isar_community_flutter_libs: + :path: ".symlinks/plugins/isar_community_flutter_libs/ios" + local_auth_darwin: + :path: ".symlinks/plugins/local_auth_darwin/darwin" + maplibre_gl: + :path: ".symlinks/plugins/maplibre_gl/ios" native_video_player: :path: ".symlinks/plugins/native_video_player/ios" + network_info_plus: + :path: ".symlinks/plugins/network_info_plus/ios" + objective_c: + :path: ".symlinks/plugins/objective_c/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" + permission_handler_apple: + :path: ".symlinks/plugins/permission_handler_apple/ios" + photo_manager: + :path: ".symlinks/plugins/photo_manager/ios" share_handler_ios: :path: ".symlinks/plugins/share_handler_ios/ios" share_handler_ios_models: :path: ".symlinks/plugins/share_handler_ios/ios/Models" + share_plus: + :path: ".symlinks/plugins/share_plus/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + sqlite3_flutter_libs: + :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" + wakelock_plus: + :path: ".symlinks/plugins/wakelock_plus/ios" SPEC CHECKSUMS: + background_downloader: 50e91d979067b82081aba359d7d916b3ba5fadad bonsoir_darwin: 29c7ccf356646118844721f36e1de4b61f6cbd0e + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100 + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9 + flutter_web_auth_2: 5c8d9dcd7848b5a9efb086d24e7a9adcae979c80 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 + geolocator_apple: 1560c3c875af2a412242c7a923e15d0d401966ff + home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + isar_community_flutter_libs: bede843185a61a05ff364a05c9b23209523f7e0d + local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391 + MapLibre: 69e572367f4ef6287e18246cfafc39c80cdcabcd + maplibre_gl: 3c924e44725147b03dda33430ad216005b40555f native_video_player: b65c58951ede2f93d103a25366bdebca95081265 + network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc + objective_c: 89e720c30d716b036faf9c9684022048eee1eee2 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62 + SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c share_handler_ios: e2244e990f826b2c8eaa291ac3831569438ba0fb share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983 + sqlite3_flutter_libs: f8fc13346870e73fe35ebf6dbb997fbcd156b241 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 -PODFILE CHECKSUM: 3c43a700a4bffb4120bf696cad263aefd4bb3c8c +PODFILE CHECKSUM: 938abbae4114b9c2140c550a2a0d8f7c674f5dfe COCOAPODS: 1.16.2 diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index b569693971..178454f381 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -3,15 +3,16 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 77; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 467DA6EAF83F3481F8BD94AB /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */; }; + 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */; }; + 65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */; }; + 65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -21,9 +22,7 @@ B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */; }; B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */; }; B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */; }; - B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */; }; - B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */; }; - D3BED739C0BC29BB32E18EB2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */; }; + D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; }; F02538E92DFBCBDD008C3FA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; F0B57D3A2DF764BD00DC5BCC /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */; }; F0B57D3C2DF764BD00DC5BCC /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */; }; @@ -85,20 +84,18 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 51516E0F2A0000000000C0F1 /* Signing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Signing.xcconfig; sourceTree = ""; }; - 614A7F5DC5DB09E89E4FCBE8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 681FBA560D5D2ADDE4F0B59E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 6D160F04A389B9FFBC557803 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = ""; }; + 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundServicePlugin.swift; sourceTree = ""; }; + 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundSyncWorker.swift; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; + 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Immich-Debug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Immich-Debug.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -107,18 +104,18 @@ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; A01DD6982F7F43B40049AB63 /* ImageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRequest.swift; sourceTree = ""; }; + B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; B21E34A92E5AFD210031FDB9 /* BackgroundWorkerApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorkerApiImpl.swift; sourceTree = ""; }; B21E34AB2E5B09100031FDB9 /* BackgroundWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.swift; sourceTree = ""; }; B25D37782E72CA15008B6CA7 /* Connectivity.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Connectivity.g.swift; sourceTree = ""; }; B25D377B2E72CA20008B6CA7 /* ConnectivityApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectivityApiImpl.swift; sourceTree = ""; }; B2BE315E2E5E5229006EEF88 /* BackgroundWorker.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundWorker.g.swift; sourceTree = ""; }; - B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApi.g.swift; sourceTree = ""; }; - B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionApiImpl.swift; sourceTree = ""; }; - C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = ""; }; - CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F0B57D382DF764BD00DC5BCC /* WidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; + F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = ""; }; FA9973382CF6DF4B000EF859 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; FAC6F8902D287C890078CB2F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; FAC6F8B12D287F120078CB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -154,15 +151,11 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ B231F52D2E93A44A00BC45D1 /* Core */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Core; sourceTree = ""; }; B2CF7F8C2DDE4EBB00744BF6 /* Sync */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Sync; sourceTree = ""; }; @@ -184,8 +177,6 @@ }; FEE084F22EC172080045228E /* Schemas */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - ); path = Schemas; sourceTree = ""; }; @@ -196,11 +187,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, FEE084F82EC172460045228E /* SQLiteData in Frameworks */, FEE084FB2EC1725A0045228E /* RawStructuredFieldValues in Frameworks */, FEE084FD2EC1725A0045228E /* StructuredFieldValues in Frameworks */, - D3BED739C0BC29BB32E18EB2 /* Pods_Runner.framework in Frameworks */, + D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -217,7 +207,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 467DA6EAF83F3481F8BD94AB /* Pods_ShareExtension.framework in Frameworks */, + 3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -227,12 +217,12 @@ 0FB772A5B9601143383626CA /* Pods */ = { isa = PBXGroup; children = ( - 614A7F5DC5DB09E89E4FCBE8 /* Pods-Runner.debug.xcconfig */, - 6D160F04A389B9FFBC557803 /* Pods-Runner.release.xcconfig */, - 681FBA560D5D2ADDE4F0B59E /* Pods-Runner.profile.xcconfig */, - 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */, - 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */, - C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */, + 2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */, + E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */, + F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */, + F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */, + 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */, + B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -240,18 +230,26 @@ 1754452DD81DA6620E279E51 /* Frameworks */ = { isa = PBXGroup; children = ( + 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */, + 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */, F0B57D392DF764BD00DC5BCC /* WidgetKit.framework */, F0B57D3B2DF764BD00DC5BCC /* SwiftUI.framework */, - CC499FBCE6B29B2DAFED7130 /* Pods_Runner.framework */, - 8AB817AA297EDEC88B23F3F6 /* Pods_ShareExtension.framework */, ); name = Frameworks; sourceTree = ""; }; + 65DD438629917FAD0047FFA8 /* BackgroundSync */ = { + isa = PBXGroup; + children = ( + 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */, + 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */, + ); + path = BackgroundSync; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, @@ -263,7 +261,6 @@ 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( - 51516E0F2A0000000000C0F1 /* Signing.xcconfig */, 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, FAC6F8B62D287F120078CB2F /* ShareExtension */, @@ -293,8 +290,8 @@ B25D37792E72CA15008B6CA7 /* Connectivity */, B21E34A62E5AF9760031FDB9 /* Background */, B2CF7F8C2DDE4EBB00744BF6 /* Sync */, - B2EE00052E72CA15008B6CA7 /* Permission */, FA9973382CF6DF4B000EF859 /* Runner.entitlements */, + 65DD438629917FAD0047FFA8 /* BackgroundSync */, FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -328,15 +325,6 @@ path = Connectivity; sourceTree = ""; }; - B2EE00052E72CA15008B6CA7 /* Permission */ = { - isa = PBXGroup; - children = ( - B2EE00032E72CA15008B6CA7 /* PermissionApiImpl.swift */, - B2EE00012E72CA15008B6CA7 /* PermissionApi.g.swift */, - ); - path = Permission; - sourceTree = ""; - }; FAC6F8B62D287F120078CB2F /* ShareExtension */ = { isa = PBXGroup; children = ( @@ -369,7 +357,7 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */, + 4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -377,7 +365,8 @@ 9705A1C41CF9048500538489 /* Embed Frameworks */, FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */, + D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */, + 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -391,9 +380,6 @@ FEE084F22EC172080045228E /* Schemas */, ); name = Runner; - packageProductDependencies = ( - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */, - ); productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */; productType = "com.apple.product-type.application"; @@ -422,7 +408,7 @@ isa = PBXNativeTarget; buildConfigurationList = FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */; buildPhases = ( - 8EC9CF3E20AF32BF24D4F3E1 /* [CP] Check Pods Manifest.lock */, + 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */, FAC6F88C2D287C890078CB2F /* Sources */, FAC6F88D2D287C890078CB2F /* Frameworks */, FAC6F88E2D287C890078CB2F /* Resources */, @@ -471,7 +457,6 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */, FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */, ); @@ -534,24 +519,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 513DA7292DED6106813332F4 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - 8EC9CF3E20AF32BF24D4F3E1 /* [CP] Check Pods Manifest.lock */ = { + 3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -573,22 +541,7 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/bash \"$SRCROOT/scripts/xcode_flutter_build.sh\"\n"; - }; - BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */ = { + 4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -610,6 +563,63 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -617,6 +627,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */, 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, A01DD69B2F7F43B40049AB63 /* ImageRequest.swift in Sources */, B21E34AC2E5B09190031FDB9 /* BackgroundWorker.swift in Sources */, @@ -625,14 +636,13 @@ FE5499F42F1197D8006016CB /* RemoteImages.g.swift in Sources */, FE5FE4AE2F30FBC000A71243 /* ImageProcessing.swift in Sources */, B25D377A2E72CA15008B6CA7 /* Connectivity.g.swift in Sources */, - B2EE00022E72CA15008B6CA7 /* PermissionApi.g.swift in Sources */, - B2EE00042E72CA15008B6CA7 /* PermissionApiImpl.swift in Sources */, FE5499F82F1198E2006016CB /* RemoteImagesImpl.swift in Sources */, FEAFA8732E4D42F4001E47FE /* Thumbhash.swift in Sources */, B25D377C2E72CA26008B6CA7 /* ConnectivityApiImpl.swift in Sources */, B21E34AA2E5AFD2B0031FDB9 /* BackgroundWorkerApiImpl.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, B2BE315F2E5E5229006EEF88 /* BackgroundWorker.g.swift in Sources */, + 65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -696,7 +706,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51516E0F2A0000000000C0F1 /* Signing.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -727,7 +736,6 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CUSTOM_GROUP_ID = "$(IMMICH_GROUP_ID).profile"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -740,7 +748,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; NEW_SETTING = ""; SDKROOT = iphoneos; @@ -759,17 +767,18 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CURRENT_PROJECT_VERSION = 240; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).profile"; + MARKETING_VERSION = 1.121.0; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile; PRODUCT_NAME = "Immich-Profile"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -780,7 +789,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51516E0F2A0000000000C0F1 /* Signing.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; @@ -811,7 +819,6 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CUSTOM_GROUP_ID = "$(IMMICH_GROUP_ID).debug"; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -830,7 +837,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; NEW_SETTING = ""; ONLY_ACTIVE_ARCH = YES; @@ -841,7 +848,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 51516E0F2A0000000000C0F1 /* Signing.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; @@ -872,7 +878,6 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CUSTOM_GROUP_ID = "$(IMMICH_GROUP_ID)"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -885,7 +890,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; NEW_SETTING = ""; SDKROOT = iphoneos; @@ -906,17 +911,18 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CURRENT_PROJECT_VERSION = 240; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).debug"; + MARKETING_VERSION = 1.121.0; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug; PRODUCT_NAME = "Immich-Debug"; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -935,17 +941,18 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CURRENT_PROJECT_VERSION = 240; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 3.0.0; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_PROD)"; + MARKETING_VERSION = 1.121.0; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich; PRODUCT_NAME = Immich; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -969,7 +976,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -986,7 +993,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).debug.Widget"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; @@ -1012,7 +1019,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1028,7 +1035,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_PROD).Widget"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1052,7 +1059,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1068,7 +1075,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).profile.Widget"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.Widget; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1079,7 +1086,7 @@ }; FAC6F89C2D287C890078CB2F /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 937632897A02DE9C249F20A6 /* Pods-ShareExtension.debug.xcconfig */; + baseConfigurationReference = F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1091,7 +1098,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1108,7 +1116,7 @@ MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).debug.ShareExtension"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1122,7 +1130,7 @@ }; FAC6F89D2D287C890078CB2F /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 10B378D23F917891A0F23E33 /* Pods-ShareExtension.release.xcconfig */; + baseConfigurationReference = 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1134,7 +1142,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1150,7 +1159,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_PROD).ShareExtension"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1162,7 +1171,7 @@ }; FAC6F89E2D287C890078CB2F /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C4A6A71F33CE37B3C913115C /* Pods-ShareExtension.profile.xcconfig */; + baseConfigurationReference = B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1174,7 +1183,8 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 240; - DEVELOPMENT_TEAM = "$(IMMICH_TEAM_ID)"; + CUSTOM_GROUP_ID = group.app.immich.share; + DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -1190,7 +1200,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = "$(IMMICH_BUNDLE_ID_DEV).profile.ShareExtension"; + PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1245,20 +1255,13 @@ }; /* End XCConfigurationList section */ -/* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { - isa = XCLocalSwiftPackageReference; - relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; - }; -/* End XCLocalSwiftPackageReference section */ - /* Begin XCRemoteSwiftPackageReference section */ FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/pointfreeco/sqlite-data"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.6.1; + minimumVersion = 1.3.0; }; }; FEE084F92EC1725A0045228E /* XCRemoteSwiftPackageReference "swift-http-structured-headers" */ = { @@ -1266,16 +1269,12 @@ repositoryURL = "https://github.com/apple/swift-http-structured-headers.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 1.6.0; + minimumVersion = 1.5.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { - isa = XCSwiftPackageProductDependency; - productName = FlutterGeneratedPluginSwiftPackage; - }; FEE084F72EC172460045228E /* SQLiteData */ = { isa = XCSwiftPackageProductDependency; package = FEE084F62EC172460045228E /* XCRemoteSwiftPackageReference "sqlite-data" */; diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e1c39435fa..432e81234d 100644 --- a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "9be33bfaa68721646604aefff3cabbdaf9a193da192aae024c265065671f6c49", "pins" : [ { "identity" : "combine-schedulers", @@ -19,21 +20,12 @@ } }, { - "identity" : "keychainaccess", + "identity" : "opencombine", "kind" : "remoteSourceControl", - "location" : "https://github.com/kishikawakatsumi/KeychainAccess", + "location" : "https://github.com/OpenCombine/OpenCombine.git", "state" : { - "revision" : "84e546727d66f1adc5439debad16270d0fdd04e7", - "version" : "4.2.2" - } - }, - { - "identity" : "maplibre-gl-native-distribution", - "kind" : "remoteSourceControl", - "location" : "https://github.com/maplibre/maplibre-gl-native-distribution.git", - "state" : { - "revision" : "84a79bc375a301169390ac110c868f06c857b83f", - "version" : "6.27.0" + "revision" : "8576f0d579b27020beccbccc3ea6844f3ddfc2c2", + "version" : "0.14.0" } }, { @@ -41,8 +33,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/sqlite-data", "state" : { - "revision" : "da3a94ed49c7a30d82853de551c07a93196e8cab", - "version" : "1.6.1" + "revision" : "b66b894b9a5710f1072c8eb6448a7edfc2d743d9", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-case-paths", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "6989976265be3f8d2b5802c722f9ba168e227c71", + "version" : "1.7.2" } }, { @@ -95,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-http-structured-headers.git", "state" : { - "revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b", - "version" : "1.6.0" + "revision" : "a9f3c352f4d46afd155e00b3c6e85decae6bcbeb", + "version" : "1.5.0" } }, { @@ -140,8 +141,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "8da8818fccd9959bd683934ddc62cf45bb65b3c8", - "version" : "0.31.1" + "revision" : "9c84335373bae5f5c9f7b5f0adf3ae10f2cab5b9", + "version" : "0.25.2" } }, { @@ -153,6 +154,15 @@ "version" : "602.0.0" } }, + { + "identity" : "swift-tagged", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-tagged", + "state" : { + "revision" : "3907a9438f5b57d317001dc99f3f11b46882272b", + "version" : "0.10.0" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", @@ -163,5 +173,5 @@ } } ], - "version" : 2 + "version" : 3 } diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 72c577d41e..2367c52b97 100644 --- a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -5,24 +5,6 @@ - - - - - - - - - - + allowLocationSimulation = "YES"> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func createConnectionError(withChannelName channelName: String) -> PigeonError { + return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +func deepEqualsBackgroundWorker(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case is (Void, Void): + return true + + case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): + return cleanLhsHashable == cleanRhsHashable + + case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): + guard cleanLhsArray.count == cleanRhsArray.count else { return false } + for (index, element) in cleanLhsArray.enumerated() { + if !deepEqualsBackgroundWorker(element, cleanRhsArray[index]) { + return false + } + } + return true + + case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } + for (key, cleanLhsValue) in cleanLhsDictionary { + guard cleanRhsDictionary.index(forKey: key) != nil else { return false } + if !deepEqualsBackgroundWorker(cleanLhsValue, cleanRhsDictionary[key]!) { + return false + } + } + return true + + default: + // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. + return false + } +} + +func deepHashBackgroundWorker(value: Any?, hasher: inout Hasher) { + if let valueList = value as? [AnyHashable] { + for item in valueList { deepHashBackgroundWorker(value: item, hasher: &hasher) } + return + } + + if let valueDict = value as? [AnyHashable: AnyHashable] { + for key in valueDict.keys { + hasher.combine(key) + deepHashBackgroundWorker(value: valueDict[key]!, hasher: &hasher) + } + return + } + + if let hashableValue = value as? AnyHashable { + hasher.combine(hashableValue.hashValue) + } + + return hasher.combine(String(describing: value)) +} + + + +/// Generated class from Pigeon that represents data sent in messages. +struct BackgroundWorkerSettings: Hashable { + var requiresCharging: Bool + var minimumDelaySeconds: Int64 + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> BackgroundWorkerSettings? { + let requiresCharging = pigeonVar_list[0] as! Bool + let minimumDelaySeconds = pigeonVar_list[1] as! Int64 + + return BackgroundWorkerSettings( + requiresCharging: requiresCharging, + minimumDelaySeconds: minimumDelaySeconds + ) + } + func toList() -> [Any?] { + return [ + requiresCharging, + minimumDelaySeconds, + ] + } + static func == (lhs: BackgroundWorkerSettings, rhs: BackgroundWorkerSettings) -> Bool { + return deepEqualsBackgroundWorker(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashBackgroundWorker(value: toList(), hasher: &hasher) + } +} + +private class BackgroundWorkerPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return BackgroundWorkerSettings.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class BackgroundWorkerPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? BackgroundWorkerSettings { + super.writeByte(129) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class BackgroundWorkerPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return BackgroundWorkerPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return BackgroundWorkerPigeonCodecWriter(data: data) + } +} + +class BackgroundWorkerPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = BackgroundWorkerPigeonCodec(readerWriter: BackgroundWorkerPigeonCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol BackgroundWorkerFgHostApi { + func enable() throws + func saveNotificationMessage(title: String, body: String) throws + func configure(settings: BackgroundWorkerSettings) throws + func disable() throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class BackgroundWorkerFgHostApiSetup { + static var codec: FlutterStandardMessageCodec { BackgroundWorkerPigeonCodec.shared } + /// Sets up an instance of `BackgroundWorkerFgHostApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerFgHostApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let enableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + enableChannel.setMessageHandler { _, reply in + do { + try api.enable() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + enableChannel.setMessageHandler(nil) + } + let saveNotificationMessageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + saveNotificationMessageChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let titleArg = args[0] as! String + let bodyArg = args[1] as! String + do { + try api.saveNotificationMessage(title: titleArg, body: bodyArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + saveNotificationMessageChannel.setMessageHandler(nil) + } + let configureChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + configureChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let settingsArg = args[0] as! BackgroundWorkerSettings + do { + try api.configure(settings: settingsArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + configureChannel.setMessageHandler(nil) + } + let disableChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + disableChannel.setMessageHandler { _, reply in + do { + try api.disable() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + disableChannel.setMessageHandler(nil) + } + } +} +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol BackgroundWorkerBgHostApi { + func onInitialized() throws + func close() throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class BackgroundWorkerBgHostApiSetup { + static var codec: FlutterStandardMessageCodec { BackgroundWorkerPigeonCodec.shared } + /// Sets up an instance of `BackgroundWorkerBgHostApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: BackgroundWorkerBgHostApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let onInitializedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.onInitialized\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + onInitializedChannel.setMessageHandler { _, reply in + do { + try api.onInitialized() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + onInitializedChannel.setMessageHandler(nil) + } + let closeChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + closeChannel.setMessageHandler { _, reply in + do { + try api.close() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + closeChannel.setMessageHandler(nil) + } + } +} +/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. +protocol BackgroundWorkerFlutterApiProtocol { + func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result) -> Void) + func onAndroidUpload(completion: @escaping (Result) -> Void) + func cancel(completion: @escaping (Result) -> Void) +} +class BackgroundWorkerFlutterApi: BackgroundWorkerFlutterApiProtocol { + private let binaryMessenger: FlutterBinaryMessenger + private let messageChannelSuffix: String + init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") { + self.binaryMessenger = binaryMessenger + self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + } + var codec: BackgroundWorkerPigeonCodec { + return BackgroundWorkerPigeonCodec.shared + } + func onIosUpload(isRefresh isRefreshArg: Bool, maxSeconds maxSecondsArg: Int64?, completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage([isRefreshArg, maxSecondsArg] as [Any?]) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func onAndroidUpload(completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage(nil) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } + func cancel(completion: @escaping (Result) -> Void) { + let channelName: String = "dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.cancel\(messageChannelSuffix)" + let channel = FlutterBasicMessageChannel(name: channelName, binaryMessenger: binaryMessenger, codec: codec) + channel.sendMessage(nil) { response in + guard let listResponse = response as? [Any?] else { + completion(.failure(createConnectionError(withChannelName: channelName))) + return + } + if listResponse.count > 1 { + let code: String = listResponse[0] as! String + let message: String? = nilOrValue(listResponse[1]) + let details: String? = nilOrValue(listResponse[2]) + completion(.failure(PigeonError(code: code, message: message, details: details))) + } else { + completion(.success(())) + } + } + } +} diff --git a/mobile/ios/Runner/Background/BackgroundWorker.swift b/mobile/ios/Runner/Background/BackgroundWorker.swift index ad583065f0..85e1a55d3d 100644 --- a/mobile/ios/Runner/Background/BackgroundWorker.swift +++ b/mobile/ios/Runner/Background/BackgroundWorker.swift @@ -95,7 +95,7 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { // Register plugins in the new engine GeneratedPluginRegistrant.register(with: engine) // Register custom plugins - AppDelegate.registerPlugins(with: engine, messenger: engine.binaryMessenger) + AppDelegate.registerPlugins(with: engine, controller: nil) flutterApi = BackgroundWorkerFlutterApi(binaryMessenger: engine.binaryMessenger) BackgroundWorkerBgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: self) @@ -121,8 +121,8 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { /** * Cancels the currently running background task, either due to timeout or external request. - * Only tears down the engine after Dart confirms it's drained. If Dart overruns iOS's grace window, - * the expiration handler still calls setTaskCompleted and iOS suspends us. + * Sends a cancel signal to the Flutter side and sets up a fallback timer to ensure + * the completion handler is eventually called even if Flutter doesn't respond. */ func close() { if isComplete { @@ -132,6 +132,12 @@ class BackgroundWorker: BackgroundWorkerBgHostApi { flutterApi?.cancel { result in self.complete(success: false) } + + // Fallback safety mechanism: ensure completion is called within 2 seconds + // This prevents the background task from hanging indefinitely if Flutter doesn't respond + Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { _ in + self.complete(success: false) + } } diff --git a/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift b/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift index 0eee970e71..a7bbc31ceb 100644 --- a/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift +++ b/mobile/ios/Runner/Background/BackgroundWorkerApiImpl.swift @@ -22,9 +22,8 @@ class BackgroundWorkerApiImpl: BackgroundWorkerFgHostApi { print("BackgroundWorkerApiImpl:disableUploadWorker Disabled background workers") } - private static let taskIDs = Bundle.main.object(forInfoDictionaryKey: "BGTaskSchedulerPermittedIdentifiers") as! [String] - private static let refreshTaskID = taskIDs.first { $0.hasSuffix(".refreshUpload") }! - private static let processingTaskID = taskIDs.first { $0.hasSuffix(".processingUpload") }! + private static let refreshTaskID = "app.alextran.immich.background.refreshUpload" + private static let processingTaskID = "app.alextran.immich.background.processingUpload" private static let taskSemaphore = DispatchSemaphore(value: 1) public static func registerBackgroundWorkers() { diff --git a/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift b/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift new file mode 100644 index 0000000000..cac9faab01 --- /dev/null +++ b/mobile/ios/Runner/BackgroundSync/BackgroundServicePlugin.swift @@ -0,0 +1,408 @@ +// +// BackgroundServicePlugin.swift +// Runner +// +// Created by Marty Fuhry on 2/14/23. +// + +import Flutter +import BackgroundTasks +import path_provider_foundation +import CryptoKit +import Network + +class BackgroundServicePlugin: NSObject, FlutterPlugin { + + public static var flutterPluginRegistrantCallback: FlutterPluginRegistrantCallback? + + public static func setPluginRegistrantCallback(_ callback: FlutterPluginRegistrantCallback) { + flutterPluginRegistrantCallback = callback + } + + // Pause the application in XCode, then enter + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"app.alextran.immich.backgroundFetch"] + // or + // e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"app.alextran.immich.backgroundProcessing"] + // Then resume the application see the background code run + // Tested on a physical device, not a simulator + // This will submit either the Fetch or Processing command to the BGTaskScheduler for immediate processing. + // In my tests, I can only get app.alextran.immich.backgroundProcessing simulated by running the above command + + // This is the task ID in Info.plist to register as our background task ID + public static let backgroundFetchTaskID = "app.alextran.immich.backgroundFetch" + public static let backgroundProcessingTaskID = "app.alextran.immich.backgroundProcessing" + + // Establish communication with the main isolate and set up the channel call + // to this BackgroundServicePlugion() + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel( + name: "immich/foregroundChannel", + binaryMessenger: registrar.messenger() + ) + + let instance = BackgroundServicePlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + registrar.addApplicationDelegate(instance) + } + + // Registers the Flutter engine with the plugins, used by the other Background Flutter engine + public static func register(engine: FlutterEngine) { + GeneratedPluginRegistrant.register(with: engine) + } + + // Registers the task IDs from the system so that we can process them here in this class + public static func registerBackgroundProcessing() { + + let processingRegisterd = BGTaskScheduler.shared.register( + forTaskWithIdentifier: backgroundProcessingTaskID, + using: nil) { task in + if task is BGProcessingTask { + handleBackgroundProcessing(task: task as! BGProcessingTask) + } + } + + let fetchRegisterd = BGTaskScheduler.shared.register( + forTaskWithIdentifier: backgroundFetchTaskID, + using: nil) { task in + if task is BGAppRefreshTask { + handleBackgroundFetch(task: task as! BGAppRefreshTask) + } + } + } + + // Handles the channel methods from Flutter + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "enable": + handleBackgroundEnable(call: call, result: result) + break + case "configure": + handleConfigure(call: call, result: result) + break + case "disable": + handleDisable(call: call, result: result) + break + case "isEnabled": + handleIsEnabled(call: call, result: result) + break + case "isIgnoringBatteryOptimizations": + result(FlutterMethodNotImplemented) + break + case "lastBackgroundFetchTime": + let defaults = UserDefaults.standard + let lastRunTime = defaults.value(forKey: "last_background_fetch_run_time") + result(lastRunTime) + break + case "lastBackgroundProcessingTime": + let defaults = UserDefaults.standard + let lastRunTime = defaults.value(forKey: "last_background_processing_run_time") + result(lastRunTime) + break + case "numberOfBackgroundProcesses": + handleNumberOfProcesses(call: call, result: result) + break + case "backgroundAppRefreshEnabled": + handleBackgroundRefreshStatus(call: call, result: result) + break + case "digestFiles": + handleDigestFiles(call: call, result: result) + break + default: + result(FlutterMethodNotImplemented) + break + } + } + + // Calculates the SHA-1 hash of each file from the list of paths provided + func handleDigestFiles(call: FlutterMethodCall, result: @escaping FlutterResult) { + + let bufsize = 2 * 1024 * 1024 + // Private error to throw if file cannot be read + enum DigestError: String, LocalizedError { + case NoFileHandle = "Cannot Open File Handle" + + public var errorDescription: String? { self.rawValue } + } + + // Parse the arguments or else fail + guard let args = call.arguments as? Array else { + print("Cannot parse args as array: \(String(describing: call.arguments))") + result(FlutterError(code: "Malformed", + message: "Received args is not an Array", + details: nil)) + return + } + + // Compute hash in background thread + DispatchQueue.global(qos: .background).async { + var hashes: [FlutterStandardTypedData?] = Array(repeating: nil, count: args.count) + for i in (0 ..< args.count) { + do { + guard let file = FileHandle(forReadingAtPath: args[i]) else { throw DigestError.NoFileHandle } + var hasher = Insecure.SHA1.init(); + while autoreleasepool(invoking: { + let chunk = file.readData(ofLength: bufsize) + guard !chunk.isEmpty else { return false } // EOF + hasher.update(data: chunk) + return true // continue + }) { } + let digest = hasher.finalize() + hashes[i] = FlutterStandardTypedData(bytes: Data(Array(digest.makeIterator()))) + } catch { + print("Cannot calculate the digest of the file \(args[i]) due to \(error.localizedDescription)") + } + } + + // Return result in main thread + DispatchQueue.main.async { + result(Array(hashes)) + } + } + } + + // Called by the flutter code when enabled so that we can turn on the background services + // and save the callback information to communicate on this method channel + public func handleBackgroundEnable(call: FlutterMethodCall, result: FlutterResult) { + + // Needs to parse the arguments from the method call + guard let args = call.arguments as? Array else { + print("Cannot parse args as array: \(call.arguments)") + result(FlutterMethodNotImplemented) + return + } + + // Requires 3 arguments in the array + guard args.count == 3 else { + print("Requires 3 arguments and received \(args.count)") + result(FlutterMethodNotImplemented) + return + } + + // Parses the arguments + let callbackHandle = args[0] as? Int64 + let notificationTitle = args[1] as? String + let instant = args[2] as? Bool + + // Write enabled to settings + let defaults = UserDefaults.standard + + // We are now enabled, so store this + defaults.set(true, forKey: "background_service_enabled") + + // The callback handle is an int64 address to communicate with the main isolate's + // entry function + defaults.set(callbackHandle, forKey: "callback_handle") + + // This is not used yet and will need to be implemented + defaults.set(notificationTitle, forKey: "notification_title") + + // Schedule the background services + BackgroundServicePlugin.scheduleBackgroundSync() + BackgroundServicePlugin.scheduleBackgroundFetch() + + result(true) + } + + // Called by the flutter code at launch to see if the background service is enabled or not + func handleIsEnabled(call: FlutterMethodCall, result: FlutterResult) { + let defaults = UserDefaults.standard + let enabled = defaults.value(forKey: "background_service_enabled") as? Bool + + // False by default + result(enabled ?? false) + } + + // Called by the Flutter code whenever a change in configuration is set + func handleConfigure(call: FlutterMethodCall, result: FlutterResult) { + + // Needs to be able to parse the arguments or else fail + guard let args = call.arguments as? Array else { + print("Cannot parse args as array: \(call.arguments)") + result(FlutterError()) + return + } + + // Needs to have 4 arguments in the call or else fail + guard args.count == 4 else { + print("Not enough arguments, 4 required: \(args.count) given") + result(FlutterError()) + return + } + + // Parse the arguments from the method call + let requireUnmeteredNetwork = args[0] as? Bool + let requireCharging = args[1] as? Bool + let triggerUpdateDelay = args[2] as? Int + let triggerMaxDelay = args[3] as? Int + + // Store the values from the call in the defaults + let defaults = UserDefaults.standard + defaults.set(requireUnmeteredNetwork, forKey: "require_unmetered_network") + defaults.set(requireCharging, forKey: "require_charging") + defaults.set(triggerUpdateDelay, forKey: "trigger_update_delay") + defaults.set(triggerMaxDelay, forKey: "trigger_max_delay") + + // Cancel the background services and reschedule them + BGTaskScheduler.shared.cancelAllTaskRequests() + BackgroundServicePlugin.scheduleBackgroundSync() + BackgroundServicePlugin.scheduleBackgroundFetch() + result(true) + } + + // Returns the number of currently scheduled background processes to Flutter, strictly + // for debugging + func handleNumberOfProcesses(call: FlutterMethodCall, result: @escaping FlutterResult) { + BGTaskScheduler.shared.getPendingTaskRequests { requests in + result(requests.count) + } + } + + // Disables the service, cancels all the task requests + func handleDisable(call: FlutterMethodCall, result: FlutterResult) { + let defaults = UserDefaults.standard + defaults.set(false, forKey: "background_service_enabled") + + BGTaskScheduler.shared.cancelAllTaskRequests() + result(true) + } + + // Checks the status of the Background App Refresh from the system + // Returns true if the service is enabled for Immich, and false otherwise + func handleBackgroundRefreshStatus(call: FlutterMethodCall, result: FlutterResult) { + switch UIApplication.shared.backgroundRefreshStatus { + case .available: + result(true) + break + case .denied: + result(false) + break + case .restricted: + result(false) + break + default: + result(false) + break + } + } + + + // Schedules a short-running background sync to sync only a few photos + static func scheduleBackgroundFetch() { + // We will schedule this task to run no matter the charging or wifi requirents from the end user + // 1. They can set Background App Refresh to Off / Wi-Fi / Wi-Fi & Cellular Data from Settings + // 2. We will check the battery connectivity when we begin running the background activity + let backgroundFetch = BGAppRefreshTaskRequest(identifier: BackgroundServicePlugin.backgroundFetchTaskID) + + // Use 5 minutes from now as earliest begin date + backgroundFetch.earliestBeginDate = Date(timeIntervalSinceNow: 5 * 60) + + do { + try BGTaskScheduler.shared.submit(backgroundFetch) + } catch { + print("Could not schedule the background task \(error.localizedDescription)") + } + } + + // Schedules a long-running background sync for syncing all of the photos + static func scheduleBackgroundSync() { + let backgroundProcessing = BGProcessingTaskRequest(identifier: BackgroundServicePlugin.backgroundProcessingTaskID) + + // We need the values for requiring charging + let defaults = UserDefaults.standard + let requireCharging = defaults.value(forKey: "require_charging") as? Bool + + // Always require network connectivity, and set the require charging from the above + backgroundProcessing.requiresNetworkConnectivity = true + backgroundProcessing.requiresExternalPower = requireCharging ?? true + + // Use 15 minutes from now as earliest begin date + backgroundProcessing.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60) + + do { + // Submit the task to the scheduler + try BGTaskScheduler.shared.submit(backgroundProcessing) + } catch { + print("Could not schedule the background task \(error.localizedDescription)") + } + } + + // This function runs when the system kicks off the BGAppRefreshTask from the Background Task Scheduler + static func handleBackgroundFetch(task: BGAppRefreshTask) { + // Schedule the next sync task so we can run this again later + scheduleBackgroundFetch() + + // Log the time of last background processing to now + let defaults = UserDefaults.standard + defaults.set(Date().timeIntervalSince1970, forKey: "last_background_fetch_run_time") + + // If we have required charging, we should check the charging status + let requireCharging = defaults.value(forKey: "require_charging") as? Bool ?? false + if (requireCharging) { + UIDevice.current.isBatteryMonitoringEnabled = true + if (UIDevice.current.batteryState == .unplugged) { + // The device is unplugged and we have required charging + // Therefore, we will simply complete the task without + // running it. + task.setTaskCompleted(success: true) + return + } + } + + // If we have required Wi-Fi, we can check the isExpensive property + let requireWifi = defaults.value(forKey: "require_wifi") as? Bool ?? false + if (requireWifi) { + let wifiMonitor = NWPathMonitor(requiredInterfaceType: .wifi) + let isExpensive = wifiMonitor.currentPath.isExpensive + if (isExpensive) { + // The network is expensive and we have required Wi-Fi + // Therefore, we will simply complete the task without + // running it + task.setTaskCompleted(success: true) + return + } + } + + // Schedule the next sync task so we can run this again later + scheduleBackgroundFetch() + + // The background sync task should only run for 20 seconds at most + BackgroundServicePlugin.runBackgroundSync(task, maxSeconds: 20) + } + + // This function runs when the system kicks off the BGProcessingTask from the Background Task Scheduler + static func handleBackgroundProcessing(task: BGProcessingTask) { + // Schedule the next sync task so we run this again later + scheduleBackgroundSync() + + // Log the time of last background processing to now + let defaults = UserDefaults.standard + defaults.set(Date().timeIntervalSince1970, forKey: "last_background_processing_run_time") + + // We won't specify a max time for the background sync service, so this can run for longer + BackgroundServicePlugin.runBackgroundSync(task, maxSeconds: nil) + } + + // This is a synchronous function which uses a semaphore to run the background sync worker's run + // function, which will create a background Isolate and communicate with the Flutter code to back + // up the assets. When it completes, we signal the semaphore and complete the execution allowing the + // control to pass back to the caller synchronously + static func runBackgroundSync(_ task: BGTask, maxSeconds: Int?) { + + let semaphore = DispatchSemaphore(value: 0) + DispatchQueue.main.async { + let backgroundWorker = BackgroundSyncWorker { _ in + semaphore.signal() + } + task.expirationHandler = { + backgroundWorker.cancel() + task.setTaskCompleted(success: true) + } + + backgroundWorker.run(maxSeconds: maxSeconds) + task.setTaskCompleted(success: true) + } + semaphore.wait() + } + + +} diff --git a/mobile/ios/Runner/BackgroundSync/BackgroundSyncWorker.swift b/mobile/ios/Runner/BackgroundSync/BackgroundSyncWorker.swift new file mode 100644 index 0000000000..88d9368308 --- /dev/null +++ b/mobile/ios/Runner/BackgroundSync/BackgroundSyncWorker.swift @@ -0,0 +1,271 @@ +// +// BackgroundSyncProcessing.swift +// Runner +// +// Created by Marty Fuhry on 2/6/23. +// +// Credit to https://github.com/fluttercommunity/flutter_workmanager/blob/main/ios/Classes/BackgroundWorker.swift + +import Foundation +import Flutter +import BackgroundTasks + +// The background worker which creates a new Flutter VM, communicates with it +// to run the backup job, and then finishes execution and calls back to its callback +// handler +class BackgroundSyncWorker { + + // The Flutter engine we create for background execution. + // This is not the main Flutter engine which shows the UI, + // this is a brand new isolate created and managed in this code + // here. It does not share memory with the main + // Flutter engine which shows the UI. + // It needs to be started up, registered, and torn down here + let engine: FlutterEngine? = FlutterEngine( + name: "BackgroundImmich" + ) + + let notificationId = "com.alextran.immich/backgroundNotifications" + // The background message passing channel + var channel: FlutterMethodChannel? + + var completionHandler: (UIBackgroundFetchResult) -> Void + let taskSessionStart = Date() + + // We need the completion handler to tell the system when we are done running + init(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + + // This is the background message passing channel to be used with the background engine + // created here in this platform code + self.channel = FlutterMethodChannel( + name: "immich/backgroundChannel", + binaryMessenger: engine!.binaryMessenger + ) + self.completionHandler = completionHandler + } + + // Handles all of the messages from the Flutter VM called into this platform code + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "initialized": + // Initialize tells us that we can now call into the Flutter VM to tell it to begin the update + self.channel?.invokeMethod( + "backgroundProcessing", + arguments: nil, + result: { flutterResult in + + // This is the result we send back to the BGTaskScheduler to let it know whether we'll need more time later or + // if this execution failed + let result: UIBackgroundFetchResult = (flutterResult as? Bool ?? false) ? .newData : .failed + + // Show the task duration + let taskSessionCompleter = Date() + let taskDuration = taskSessionCompleter.timeIntervalSince(self.taskSessionStart) + print("[\(String(describing: self))] \(#function) -> performBackgroundRequest.\(result) (finished in \(taskDuration) seconds)") + + // Complete the execution + self.complete(result) + }) + break + case "updateNotification": + let handled = self.handleNotification(call) + result(handled) + break + case "showError": + let handled = self.handleError(call) + result(handled) + break + case "clearErrorNotifications": + self.handleClearErrorNotifications() + result(true) + break + case "hasContentChanged": + // This is only called for Android, but we provide an implementation here + // telling Flutter that we don't have any information about whether the gallery + // contents have changed or not, so we can just say "no, they've not changed" + result(false) + break + default: + result(FlutterError()) + self.complete(UIBackgroundFetchResult.failed) + } + } + + // Runs the background sync by starting up a new isolate and handling the calls + // until it completes + public func run(maxSeconds: Int?) { + // We need the callback handle to start up the Flutter VM from the entry point + let defaults = UserDefaults.standard + guard let callbackHandle = defaults.value(forKey: "callback_handle") as? Int64 else { + // Can't find the callback handle, this is fatal + complete(UIBackgroundFetchResult.failed) + return + + } + + // Use the provided callbackHandle to get the callback function + guard let callback = FlutterCallbackCache.lookupCallbackInformation(callbackHandle) else { + // We need this callback or else this is fatal + complete(UIBackgroundFetchResult.failed) + return + } + + // Sanity check for the engine existing + if engine == nil { + complete(UIBackgroundFetchResult.failed) + return + } + + // Run the engine + let isRunning = engine!.run( + withEntrypoint: callback.callbackName, + libraryURI: callback.callbackLibraryPath + ) + + // If this engine isn't running, this is fatal + if !isRunning { + complete(UIBackgroundFetchResult.failed) + return + } + + // If we have a timer, we need to start the timer to cancel ourselves + // so that we don't run longer than the provided maxSeconds + // After maxSeconds has elapsed, we will invoke "systemStop" + if maxSeconds != nil { + // Schedule a non-repeating timer to run after maxSeconds + let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(maxSeconds!), + repeats: false) { timer in + // The callback invalidates the timer and stops execution + timer.invalidate() + + // If the channel is already deallocated, we don't need to do anything + if self.channel == nil { + return + } + + // Tell the Flutter VM to stop backing up now + self.channel?.invokeMethod( + "systemStop", + arguments: nil, + result: nil) + + // Complete the execution + self.complete(UIBackgroundFetchResult.newData) + } + } + + // Set the handle function to the channel message handler + self.channel?.setMethodCallHandler(handle) + + // Register this to get access to the plugins on the platform channel + BackgroundServicePlugin.flutterPluginRegistrantCallback?(engine!) + } + + // Cancels execution of this task, used by the system's task expiration handler + // which is called shortly before execution is about to expire + public func cancel() { + // If the channel is already deallocated, we don't need to do anything + if self.channel == nil { + return + } + + // Tell the Flutter VM to stop backing up now + self.channel?.invokeMethod( + "systemStop", + arguments: nil, + result: nil) + + // Complete the execution + self.complete(UIBackgroundFetchResult.newData) + } + + // Completes the execution, destroys the engine, and sends a completion to our callback completionHandler + private func complete(_ fetchResult: UIBackgroundFetchResult) { + engine?.destroyContext() + channel = nil + completionHandler(fetchResult) + } + + private func handleNotification(_ call: FlutterMethodCall) -> Bool { + + // Parse the arguments as an array list + guard let args = call.arguments as? Array else { + print("Failed to parse \(call.arguments) as array") + return false; + } + + // Requires 7 arguments passed or else fail + guard args.count == 7 else { + print("Needs 7 arguments, but was only passed \(args.count)") + return false + } + + // Parse the arguments to send the notification update + let title = args[0] as? String + let content = args[1] as? String + let progress = args[2] as? Int + let maximum = args[3] as? Int + let indeterminate = args[4] as? Bool + let isDetail = args[5] as? Bool + let onlyIfForeground = args[6] as? Bool + + // Build the notification + let notificationContent = UNMutableNotificationContent() + notificationContent.body = content ?? "Uploading..." + notificationContent.title = title ?? "Immich" + + // Add it to the Notification center + let notification = UNNotificationRequest( + identifier: notificationId, + content: notificationContent, + trigger: nil + ) + let center = UNUserNotificationCenter.current() + center.add(notification) { (error: Error?) in + if let theError = error { + print("Error showing notifications: \(theError)") + } + } + + return true + } + + private func handleError(_ call: FlutterMethodCall) -> Bool { + // Parse the arguments as an array list + guard let args = call.arguments as? Array else { + return false; + } + + // Requires 7 arguments passed or else fail + guard args.count == 3 else { + return false + } + + let title = args[0] as? String + let content = args[1] as? String + let individualTag = args[2] as? String + + // Build the notification + let notificationContent = UNMutableNotificationContent() + notificationContent.body = content ?? "Error running the backup job." + notificationContent.title = title ?? "Immich" + + // Add it to the Notification center + let notification = UNNotificationRequest( + identifier: notificationId, + content: notificationContent, + trigger: nil + ) + let center = UNUserNotificationCenter.current() + center.add(notification) + + return true + } + + private func handleClearErrorNotifications() { + let center = UNUserNotificationCenter.current() + center.removeDeliveredNotifications(withIdentifiers: [notificationId]) + center.removePendingNotificationRequests(withIdentifiers: [notificationId]) + } +} + diff --git a/mobile/ios/Runner/Connectivity/Connectivity.g.swift b/mobile/ios/Runner/Connectivity/Connectivity.g.swift new file mode 100644 index 0000000000..f8d85a2edf --- /dev/null +++ b/mobile/ios/Runner/Connectivity/Connectivity.g.swift @@ -0,0 +1,129 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + + +enum NetworkCapability: Int { + case cellular = 0 + case wifi = 1 + case vpn = 2 + case unmetered = 3 +} + +private class ConnectivityPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return NetworkCapability(rawValue: enumResultAsInt) + } + return nil + default: + return super.readValue(ofType: type) + } + } +} + +private class ConnectivityPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? NetworkCapability { + super.writeByte(129) + super.writeValue(value.rawValue) + } else { + super.writeValue(value) + } + } +} + +private class ConnectivityPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return ConnectivityPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return ConnectivityPigeonCodecWriter(data: data) + } +} + +class ConnectivityPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = ConnectivityPigeonCodec(readerWriter: ConnectivityPigeonCodecReaderWriter()) +} + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol ConnectivityApi { + func getCapabilities() throws -> [NetworkCapability] +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class ConnectivityApiSetup { + static var codec: FlutterStandardMessageCodec { ConnectivityPigeonCodec.shared } + /// Sets up an instance of `ConnectivityApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ConnectivityApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + #if os(iOS) + let taskQueue = binaryMessenger.makeBackgroundTaskQueue?() + #else + let taskQueue: FlutterTaskQueue? = nil + #endif + let getCapabilitiesChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getCapabilitiesChannel.setMessageHandler { _, reply in + do { + let result = try api.getCapabilities() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getCapabilitiesChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Connectivity/ConnectivityApiImpl.swift b/mobile/ios/Runner/Connectivity/ConnectivityApiImpl.swift index e88b7a0fd6..f104314fae 100644 --- a/mobile/ios/Runner/Connectivity/ConnectivityApiImpl.swift +++ b/mobile/ios/Runner/Connectivity/ConnectivityApiImpl.swift @@ -43,16 +43,15 @@ class ConnectivityApiImpl: ConnectivityApi { capabilities.append(.vpn) } - // Determine if connection is unmetered from the OS metered flags rather than - // the interface type, so wired ethernet (iPhone USB adapters, Apple Silicon - // Macs) is treated as unmetered like Wi-Fi: - // - Not on cellular - // - Not expensive (also rules out cellular and personal hotspot) - // - Not constrained (Low Data Mode) - // Note: VPN over cellular stays metered because the path is still expensive. + // Determine if connection is unmetered: + // - Must be on WiFi (not cellular) + // - Must not be expensive (rules out personal hotspot) + // - Must not be constrained (Low Data Mode) + // Note: VPN over cellular should still be considered metered let isOnCellular = path.usesInterfaceType(.cellular) - - if !isOnCellular && !path.isExpensive && !path.isConstrained { + let isOnWifi = path.usesInterfaceType(.wifi) + + if isOnWifi && !isOnCellular && !path.isExpensive && !path.isConstrained { capabilities.append(.unmetered) } diff --git a/mobile/ios/Runner/Core/Network.g.swift b/mobile/ios/Runner/Core/Network.g.swift new file mode 100644 index 0000000000..5a8075f91a --- /dev/null +++ b/mobile/ios/Runner/Core/Network.g.swift @@ -0,0 +1,330 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +func deepEqualsNetwork(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case is (Void, Void): + return true + + case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): + return cleanLhsHashable == cleanRhsHashable + + case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): + guard cleanLhsArray.count == cleanRhsArray.count else { return false } + for (index, element) in cleanLhsArray.enumerated() { + if !deepEqualsNetwork(element, cleanRhsArray[index]) { + return false + } + } + return true + + case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } + for (key, cleanLhsValue) in cleanLhsDictionary { + guard cleanRhsDictionary.index(forKey: key) != nil else { return false } + if !deepEqualsNetwork(cleanLhsValue, cleanRhsDictionary[key]!) { + return false + } + } + return true + + default: + // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. + return false + } +} + +func deepHashNetwork(value: Any?, hasher: inout Hasher) { + if let valueList = value as? [AnyHashable] { + for item in valueList { deepHashNetwork(value: item, hasher: &hasher) } + return + } + + if let valueDict = value as? [AnyHashable: AnyHashable] { + for key in valueDict.keys { + hasher.combine(key) + deepHashNetwork(value: valueDict[key]!, hasher: &hasher) + } + return + } + + if let hashableValue = value as? AnyHashable { + hasher.combine(hashableValue.hashValue) + } + + return hasher.combine(String(describing: value)) +} + + + +/// Generated class from Pigeon that represents data sent in messages. +struct ClientCertData: Hashable { + var data: FlutterStandardTypedData + var password: String + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertData? { + let data = pigeonVar_list[0] as! FlutterStandardTypedData + let password = pigeonVar_list[1] as! String + + return ClientCertData( + data: data, + password: password + ) + } + func toList() -> [Any?] { + return [ + data, + password, + ] + } + static func == (lhs: ClientCertData, rhs: ClientCertData) -> Bool { + return deepEqualsNetwork(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashNetwork(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct ClientCertPrompt: Hashable { + var title: String + var message: String + var cancel: String + var confirm: String + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> ClientCertPrompt? { + let title = pigeonVar_list[0] as! String + let message = pigeonVar_list[1] as! String + let cancel = pigeonVar_list[2] as! String + let confirm = pigeonVar_list[3] as! String + + return ClientCertPrompt( + title: title, + message: message, + cancel: cancel, + confirm: confirm + ) + } + func toList() -> [Any?] { + return [ + title, + message, + cancel, + confirm, + ] + } + static func == (lhs: ClientCertPrompt, rhs: ClientCertPrompt) -> Bool { + return deepEqualsNetwork(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashNetwork(value: toList(), hasher: &hasher) + } +} + +private class NetworkPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + return ClientCertData.fromList(self.readValue() as! [Any?]) + case 130: + return ClientCertPrompt.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class NetworkPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? ClientCertData { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? ClientCertPrompt { + super.writeByte(130) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class NetworkPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return NetworkPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return NetworkPigeonCodecWriter(data: data) + } +} + +class NetworkPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = NetworkPigeonCodec(readerWriter: NetworkPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol NetworkApi { + func addCertificate(clientData: ClientCertData, completion: @escaping (Result) -> Void) + func selectCertificate(promptText: ClientCertPrompt, completion: @escaping (Result) -> Void) + func removeCertificate(completion: @escaping (Result) -> Void) + func hasCertificate() throws -> Bool + func getClientPointer() throws -> Int64 + func setRequestHeaders(headers: [String: String], serverUrls: [String], token: String?) throws +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class NetworkApiSetup { + static var codec: FlutterStandardMessageCodec { NetworkPigeonCodec.shared } + /// Sets up an instance of `NetworkApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NetworkApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let addCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + addCertificateChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let clientDataArg = args[0] as! ClientCertData + api.addCertificate(clientData: clientDataArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + addCertificateChannel.setMessageHandler(nil) + } + let selectCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + selectCertificateChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let promptTextArg = args[0] as! ClientCertPrompt + api.selectCertificate(promptText: promptTextArg) { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + selectCertificateChannel.setMessageHandler(nil) + } + let removeCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + removeCertificateChannel.setMessageHandler { _, reply in + api.removeCertificate { result in + switch result { + case .success: + reply(wrapResult(nil)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + removeCertificateChannel.setMessageHandler(nil) + } + let hasCertificateChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.hasCertificate\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + hasCertificateChannel.setMessageHandler { _, reply in + do { + let result = try api.hasCertificate() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + hasCertificateChannel.setMessageHandler(nil) + } + let getClientPointerChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.getClientPointer\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getClientPointerChannel.setMessageHandler { _, reply in + do { + let result = try api.getClientPointer() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getClientPointerChannel.setMessageHandler(nil) + } + let setRequestHeadersChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + setRequestHeadersChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let headersArg = args[0] as! [String: String] + let serverUrlsArg = args[1] as! [String] + let tokenArg: String? = nilOrValue(args[2]) + do { + try api.setRequestHeaders(headers: headersArg, serverUrls: serverUrlsArg, token: tokenArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + setRequestHeadersChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Core/NetworkApiImpl.swift b/mobile/ios/Runner/Core/NetworkApiImpl.swift index 288b8e9539..3c4be8e718 100644 --- a/mobile/ios/Runner/Core/NetworkApiImpl.swift +++ b/mobile/ios/Runner/Core/NetworkApiImpl.swift @@ -10,14 +10,11 @@ enum ImportError: Error { } class NetworkApiImpl: NetworkApi { + weak var viewController: UIViewController? private var activeImporter: CertImporter? - - private var viewController: UIViewController? { - UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .flatMap { $0.windows } - .first { $0.isKeyWindow }? - .rootViewController + + init(viewController: UIViewController?) { + self.viewController = viewController } func selectCertificate(promptText: ClientCertPrompt, completion: @escaping (Result) -> Void) { @@ -61,10 +58,6 @@ class NetworkApiImpl: NetworkApi { return Int64(Int(bitPattern: pointer)) } - func getAppGroupId() throws -> String { - return Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String - } - func setRequestHeaders(headers: [String : String], serverUrls: [String], token: String?) throws { URLSessionManager.setServerUrls(serverUrls) diff --git a/mobile/ios/Runner/Core/URLSessionManager.swift b/mobile/ios/Runner/Core/URLSessionManager.swift index 48963aa577..e9d65d3113 100644 --- a/mobile/ios/Runner/Core/URLSessionManager.swift +++ b/mobile/ios/Runner/Core/URLSessionManager.swift @@ -4,7 +4,7 @@ import native_video_player let CLIENT_CERT_LABEL = "app.alextran.immich.client_identity" let HEADERS_KEY = "immich.request_headers" let SERVER_URLS_KEY = "immich.server_urls" -let APP_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String +let APP_GROUP = "group.app.immich.share" let COOKIE_EXPIRY_DAYS: TimeInterval = 400 enum AuthCookie: CaseIterable { diff --git a/mobile/ios/Runner/Images/ImageRequest.swift b/mobile/ios/Runner/Images/ImageRequest.swift index 6c8bb04c70..5c05bafb83 100644 --- a/mobile/ios/Runner/Images/ImageRequest.swift +++ b/mobile/ios/Runner/Images/ImageRequest.swift @@ -1,32 +1,5 @@ import Foundation -class ImageRequest: @unchecked Sendable { - private struct State: Sendable { - var isCancelled = false - } - - let completion: @Sendable (Result<[String: Int64]?, any Error>) -> Void - private let state: Mutex - - var isCancelled: Bool { - get { - state.withLock { $0.isCancelled } - } - set { - state.withLock { $0.isCancelled = newValue } - } - } - - init(completion: @escaping @Sendable (Result<[String: Int64]?, any Error>) -> Void) { - self.state = Mutex(State()) - self.completion = completion - } - - func cancel() { - isCancelled = true - } -} - struct RequestRegistry: ~Copyable, Sendable { private let requests = Mutex<[Int64: T]>([:]) diff --git a/mobile/ios/Runner/Images/LocalImages.g.swift b/mobile/ios/Runner/Images/LocalImages.g.swift new file mode 100644 index 0000000000..146950cd51 --- /dev/null +++ b/mobile/ios/Runner/Images/LocalImages.g.swift @@ -0,0 +1,139 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + + +private class LocalImagesPigeonCodecReader: FlutterStandardReader { +} + +private class LocalImagesPigeonCodecWriter: FlutterStandardWriter { +} + +private class LocalImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return LocalImagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return LocalImagesPigeonCodecWriter(data: data) + } +} + +class LocalImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = LocalImagesPigeonCodec(readerWriter: LocalImagesPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol LocalImageApi { + func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void) + func cancelRequest(requestId: Int64) throws + func getThumbhash(thumbhash: String, completion: @escaping (Result<[String: Int64], Error>) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class LocalImageApiSetup { + static var codec: FlutterStandardMessageCodec { LocalImagesPigeonCodec.shared } + /// Sets up an instance of `LocalImageApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: LocalImageApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + requestImageChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let assetIdArg = args[0] as! String + let requestIdArg = args[1] as! Int64 + let widthArg = args[2] as! Int64 + let heightArg = args[3] as! Int64 + let isVideoArg = args[4] as! Bool + let preferEncodedArg = args[5] as! Bool + api.requestImage(assetId: assetIdArg, requestId: requestIdArg, width: widthArg, height: heightArg, isVideo: isVideoArg, preferEncoded: preferEncodedArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + requestImageChannel.setMessageHandler(nil) + } + let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelRequestChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let requestIdArg = args[0] as! Int64 + do { + try api.cancelRequest(requestId: requestIdArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cancelRequestChannel.setMessageHandler(nil) + } + let getThumbhashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + getThumbhashChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let thumbhashArg = args[0] as! String + api.getThumbhash(thumbhash: thumbhashArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + getThumbhashChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Images/LocalImagesImpl.swift b/mobile/ios/Runner/Images/LocalImagesImpl.swift index 0d023ad466..713a24a2dd 100644 --- a/mobile/ios/Runner/Images/LocalImagesImpl.swift +++ b/mobile/ios/Runner/Images/LocalImagesImpl.swift @@ -3,6 +3,21 @@ import Flutter import MobileCoreServices import Photos +class LocalImageRequest { + weak var operation: Operation? + var isCancelled = false + let callback: (Result<[String: Int64]?, any Error>) -> Void + + init(callback: @escaping (Result<[String: Int64]?, any Error>) -> Void) { + self.callback = callback + } + + func cancel() { + isCancelled = true + operation?.cancel() + } +} + class LocalImageApiImpl: LocalImageApi { private static let imageManager = PHImageManager.default() private static let fetchOptions = { @@ -20,11 +35,10 @@ class LocalImageApiImpl: LocalImageApi { requestOptions.version = .current return requestOptions }() - private static let maxPixelSize: CGFloat = 16384 - private static let registry = RequestRegistry() + private static let registry = RequestRegistry() - private static let rgbaFormat = vImage_CGImageFormat( + private static var rgbaFormat = vImage_CGImageFormat( bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: CGColorSpaceCreateDeviceRGB(), @@ -53,20 +67,21 @@ class LocalImageApiImpl: LocalImageApi { } func requestImage(assetId: String, requestId: Int64, width: Int64, height: Int64, isVideo: Bool, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) { - let request = ImageRequest(completion: completion) + let request = LocalImageRequest(callback: completion) let operation = BlockOperation { if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } guard let asset = Self.requestAsset(assetId: assetId) else { Self.registry.remove(requestId: requestId) - return request.completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil))) + completion(.failure(PigeonError(code: "", message: "Could not get asset data for \(assetId)", details: nil))) + return } if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } if preferEncoded { @@ -85,12 +100,12 @@ class LocalImageApiImpl: LocalImageApi { ) if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } guard let data = imageData else { Self.registry.remove(requestId: requestId) - return request.completion(.failure(PigeonError(code: "", message: "Could not get image data for \(assetId)", details: nil))) + return completion(.failure(PigeonError(code: "", message: "Could not get image data for \(assetId)", details: nil))) } let length = data.count @@ -99,26 +114,22 @@ class LocalImageApiImpl: LocalImageApi { if request.isCancelled { free(pointer) - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } - Self.registry.remove(requestId: requestId) - return request.completion(.success([ + request.callback(.success([ "pointer": Int64(Int(bitPattern: pointer)), "length": Int64(length), ])) + Self.registry.remove(requestId: requestId) + return } - let isOriginal = !(width > 0 && height > 0) - let targetSize = isOriginal - ? CGSize(width: Self.maxPixelSize, height: Self.maxPixelSize) - : CGSize(width: Double(width), height: Double(height)) - let contentMode: PHImageContentMode = isOriginal ? .aspectFit : .aspectFill var image: UIImage? Self.imageManager.requestImage( for: asset, - targetSize: targetSize, - contentMode: contentMode, + targetSize: width > 0 && height > 0 ? CGSize(width: Double(width), height: Double(height)) : PHImageManagerMaximumSize, + contentMode: .aspectFill, options: Self.requestOptions, resultHandler: { (_image, info) -> Void in image = _image @@ -126,43 +137,17 @@ class LocalImageApiImpl: LocalImageApi { ) if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } - guard let fastImage = image, - var cgImage = fastImage.cgImage else { + guard let image = image, + let cgImage = image.cgImage else { Self.registry.remove(requestId: requestId) - return request.completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))) - } - - // .fast can return larger than the target, so retry with .exact to guarantee the bound. - if max(cgImage.width, cgImage.height) > Int(Self.maxPixelSize) { - let exactOptions = Self.requestOptions.copy() as! PHImageRequestOptions - exactOptions.resizeMode = .exact - image = nil - Self.imageManager.requestImage( - for: asset, - targetSize: targetSize, - contentMode: contentMode, - options: exactOptions, - resultHandler: { (_image, info) -> Void in - image = _image - } - ) - - if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) - } - - guard let exactImage = image?.cgImage else { - Self.registry.remove(requestId: requestId) - return request.completion(.failure(PigeonError(code: "", message: "Could not resize image for \(assetId)", details: nil))) - } - cgImage = exactImage + return completion(.failure(PigeonError(code: "", message: "Could not get pixel data for \(assetId)", details: nil))) } if request.isCancelled { - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } do { @@ -170,22 +155,23 @@ class LocalImageApiImpl: LocalImageApi { if request.isCancelled { buffer.free() - return request.completion(ImageProcessing.cancelledResult) + return completion(ImageProcessing.cancelledResult) } - Self.registry.remove(requestId: requestId) - return request.completion(.success([ + request.callback(.success([ "pointer": Int64(Int(bitPattern: buffer.data)), "width": Int64(buffer.width), "height": Int64(buffer.height), "rowBytes": Int64(buffer.rowBytes), ])) + Self.registry.remove(requestId: requestId) } catch { Self.registry.remove(requestId: requestId) - return request.completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil))) + return completion(.failure(PigeonError(code: "", message: "Failed to convert image for \(assetId): \(error)", details: nil))) } } + request.operation = operation Self.registry.add(requestId: requestId, request: request) ImageProcessing.queue.addOperation(operation) } diff --git a/mobile/ios/Runner/Images/RemoteImages.g.swift b/mobile/ios/Runner/Images/RemoteImages.g.swift new file mode 100644 index 0000000000..9fcffd4233 --- /dev/null +++ b/mobile/ios/Runner/Images/RemoteImages.g.swift @@ -0,0 +1,134 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + + +private class RemoteImagesPigeonCodecReader: FlutterStandardReader { +} + +private class RemoteImagesPigeonCodecWriter: FlutterStandardWriter { +} + +private class RemoteImagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return RemoteImagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return RemoteImagesPigeonCodecWriter(data: data) + } +} + +class RemoteImagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = RemoteImagesPigeonCodec(readerWriter: RemoteImagesPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol RemoteImageApi { + func requestImage(url: String, requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String: Int64]?, Error>) -> Void) + func cancelRequest(requestId: Int64) throws + func clearCache(completion: @escaping (Result) -> Void) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class RemoteImageApiSetup { + static var codec: FlutterStandardMessageCodec { RemoteImagesPigeonCodec.shared } + /// Sets up an instance of `RemoteImageApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: RemoteImageApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let requestImageChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + requestImageChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let urlArg = args[0] as! String + let requestIdArg = args[1] as! Int64 + let preferEncodedArg = args[2] as! Bool + api.requestImage(url: urlArg, requestId: requestIdArg, preferEncoded: preferEncodedArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + requestImageChannel.setMessageHandler(nil) + } + let cancelRequestChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelRequestChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let requestIdArg = args[0] as! Int64 + do { + try api.cancelRequest(requestId: requestIdArg) + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cancelRequestChannel.setMessageHandler(nil) + } + let clearCacheChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.RemoteImageApi.clearCache\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + clearCacheChannel.setMessageHandler { _, reply in + api.clearCache { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + clearCacheChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Images/RemoteImagesImpl.swift b/mobile/ios/Runner/Images/RemoteImagesImpl.swift index b4b70f0b30..37d37f597b 100644 --- a/mobile/ios/Runner/Images/RemoteImagesImpl.swift +++ b/mobile/ios/Runner/Images/RemoteImagesImpl.swift @@ -1,105 +1,102 @@ import Accelerate import Flutter -import ImageIO import MobileCoreServices import Photos -final class RemoteImageRequest: ImageRequest { - var task: URLSessionDataTask? +class RemoteImageRequest { + weak var task: URLSessionDataTask? let id: Int64 + var isCancelled = false + let completion: (Result<[String: Int64]?, any Error>) -> Void - init(id: Int64, completion: @escaping @Sendable (Result<[String: Int64]?, any Error>) -> Void) { + init(id: Int64, task: URLSessionDataTask, completion: @escaping (Result<[String: Int64]?, any Error>) -> Void) { self.id = id - super.init(completion: completion) + self.task = task + self.completion = completion } - override func cancel() { - super.cancel() + func cancel() { + isCancelled = true task?.cancel() } } class RemoteImageApiImpl: NSObject, RemoteImageApi { private static let registry = RequestRegistry() - private static let rgbaFormat = vImage_CGImageFormat( + private static var rgbaFormat = vImage_CGImageFormat( bitsPerComponent: 8, bitsPerPixel: 32, colorSpace: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue), renderingIntent: .perceptual )! - private static let decodeOptions: [NSString: Bool] = [ + private static let decodeOptions = [ kCGImageSourceShouldCache: false, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceCreateThumbnailFromImageAlways: true - ] + ] as CFDictionary - func requestImage(url: String, requestId: Int64, preferEncoded: Bool, width: Int64?, height: Int64?, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) { + func requestImage(url: String, requestId: Int64, preferEncoded: Bool, completion: @escaping (Result<[String : Int64]?, any Error>) -> Void) { var urlRequest = URLRequest(url: URL(string: url)!) urlRequest.cachePolicy = .returnCacheDataElseLoad - let request = RemoteImageRequest(id: requestId, completion: completion) - let task = URLSessionManager.shared.session.dataTask(with: urlRequest) { data, response, error in - Self.handleCompletion(request: request, encoded: preferEncoded, width: width, height: height, data: data, response: response, error: error) + Self.handleCompletion(requestId: requestId, encoded: preferEncoded, data: data, response: response, error: error) } - request.task = task + let request = RemoteImageRequest(id: requestId, task: task, completion: completion) + Self.registry.add(requestId: requestId, request: request) + task.resume() } - private static func handleCompletion(request: RemoteImageRequest, encoded: Bool, width: Int64?, height: Int64?, data: Data?, response: URLResponse?, error: Error?) { + private static func handleCompletion(requestId: Int64, encoded: Bool, data: Data?, response: URLResponse?, error: Error?) { + guard let request = registry.remove(requestId: requestId) else { + return + } + + if let error = error { + if request.isCancelled || (error as NSError).code == NSURLErrorCancelled { + return request.completion(ImageProcessing.cancelledResult) + } + return request.completion(.failure(error)) + } + if request.isCancelled { return request.completion(ImageProcessing.cancelledResult) } - if let error = error { - registry.remove(requestId: request.id) - return request.completion(.failure(error)) - } - guard let data = data else { - registry.remove(requestId: request.id) return request.completion(.failure(PigeonError(code: "", message: "No data received", details: nil))) } - if encoded { - let length = data.count - let pointer = malloc(length)! - data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length) - - if request.isCancelled { - free(pointer) - return request.completion(ImageProcessing.cancelledResult) - } - - registry.remove(requestId: request.id) - return request.completion( - .success([ - "pointer": Int64(Int(bitPattern: pointer)), - "length": Int64(length), - ])) - } - ImageProcessing.queue.addOperation { if request.isCancelled { return request.completion(ImageProcessing.cancelledResult) } - guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else { - registry.remove(requestId: request.id) - return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil))) + // Return raw encoded bytes when requested (for animated images) + if encoded { + let length = data.count + let pointer = malloc(length)! + data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: length) + + if request.isCancelled { + free(pointer) + return request.completion(ImageProcessing.cancelledResult) + } + + return request.completion( + .success([ + "pointer": Int64(Int(bitPattern: pointer)), + "length": Int64(length), + ])) } - var options: [NSString: Any] = decodeOptions - if let maxPixelSize = targetThumbnailRenderSize(imageSource: imageSource, width: width, height: height) { - options[kCGImageSourceThumbnailMaxPixelSize] = maxPixelSize - } - - guard let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { - registry.remove(requestId: request.id) + guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil), + let cgImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, decodeOptions) else { return request.completion(.failure(PigeonError(code: "", message: "Failed to decode image for request", details: nil))) } @@ -115,49 +112,19 @@ class RemoteImageApiImpl: NSObject, RemoteImageApi { return request.completion(ImageProcessing.cancelledResult) } - registry.remove(requestId: request.id) - return request.completion( - .success([ - "pointer": Int64(Int(bitPattern: buffer.data)), - "width": Int64(buffer.width), - "height": Int64(buffer.height), - "rowBytes": Int64(buffer.rowBytes), - ])) + request.completion( + .success([ + "pointer": Int64(Int(bitPattern: buffer.data)), + "width": Int64(buffer.width), + "height": Int64(buffer.height), + "rowBytes": Int64(buffer.rowBytes), + ])) } catch { - registry.remove(requestId: request.id) return request.completion(.failure(PigeonError(code: "", message: "Failed to convert image for request: \(error)", details: nil))) } } } - /// Returns the longest rendered edge needed to cover the requested size. - private static func targetThumbnailRenderSize(imageSource: CGImageSource, width: Int64?, height: Int64?) -> Int? { - guard let width, - let height, - width > 0, - height > 0, - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any], - let pixelWidth = properties[kCGImagePropertyPixelWidth] as? NSNumber, - let pixelHeight = properties[kCGImagePropertyPixelHeight] as? NSNumber else { - return nil - } - - let orientation = (properties[kCGImagePropertyOrientation] as? NSNumber) - .flatMap { CGImagePropertyOrientation(rawValue: $0.uint32Value) } ?? .up - let swapsDimensions: Bool - switch orientation { - case .leftMirrored, .right, .rightMirrored, .left: - swapsDimensions = true - default: - swapsDimensions = false - } - let sourceWidth = swapsDimensions ? pixelHeight.doubleValue : pixelWidth.doubleValue - let sourceHeight = swapsDimensions ? pixelWidth.doubleValue : pixelHeight.doubleValue - let fillScale = max(Double(width) / sourceWidth, Double(height) / sourceHeight) - let scale = min(1, fillScale) - return scale < 1 ? Int(ceil(max(sourceWidth * scale, sourceHeight * scale))) : nil - } - func cancelRequest(requestId: Int64) { Self.registry.remove(requestId: requestId)?.cancel() } diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index 31feda2e11..67b206750b 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -1,206 +1,188 @@ - - AppGroupId - $(CUSTOM_GROUP_ID) - BGTaskSchedulerPermittedIdentifiers - - app.alextran.immich.background.refreshUpload - app.alextran.immich.background.processingUpload - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - ${PRODUCT_NAME} - CFBundleDocumentTypes - - - CFBundleTypeName - ShareHandler - LSHandlerRank - Alternate - LSItemContentTypes - - public.file-url - public.image - public.text - public.movie - public.url - public.data - - - - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLocalizations - - en - ar - ca - cs - da - de - es - fi - fr - he - hi - hu - it - ja - ko - lv - mn - nb - nl - pl - pt - ro - ru - sk - sl - sr - sv - th - uk - vi - zh - - CFBundleName - immich_mobile - CFBundlePackageType - APPL - CFBundleShortVersionString - 3.1.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - Share Extension - CFBundleURLSchemes - - ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) - - - - CFBundleTypeRole - Editor - CFBundleURLName - Deep Link - CFBundleURLSchemes - - immich - - - - CFBundleVersion - 4 - ITSAppUsesNonExemptEncryption - - LSApplicationQueriesSchemes - - https - - LSRequiresIPhoneOS - - LSSupportsOpeningDocumentsInPlace - No - MGLMapboxMetricsEnabledSettingShownInApp - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - NSBonjourServices - - _googlecast._tcp - _CC1AD845._googlecast._tcp - - NSCameraUsageDescription - We need to access the camera to let you take beautiful video using this app - NSFaceIDUsageDescription - We need to use FaceID to allow access to your locked folder - NSLocalNetworkUsageDescription - We need local network permission to connect to the local server using IP address and - allow the casting feature to work - NSLocationAlwaysAndWhenInUseUsageDescription - We require this permission to access the local WiFi name for background upload mechanism - NSLocationUsageDescription - We require this permission to access the local WiFi name - NSLocationWhenInUseUsageDescription - We require this permission to access the local WiFi name - NSMicrophoneUsageDescription - We need to access the microphone to let you take beautiful video using this app - NSPhotoLibraryAddUsageDescription - We need to manage backup your photos album - NSPhotoLibraryUsageDescription - We need to manage backup your photos album - NSUserActivityTypes - - INSendMessageIntent - - UIApplicationSceneManifest - - UIApplicationSupportsMultipleScenes - - UISceneConfigurations - - UIWindowSceneSessionRoleApplication - - - UISceneClassName - UIWindowScene - UISceneConfigurationName - flutter - UISceneDelegateClassName - FlutterSceneDelegate - UISceneStoryboardFile - Main - - - - - UIApplicationSupportsIndirectInputEvents - - UIBackgroundModes - - fetch - processing - - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UIStatusBarHidden - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIViewControllerBasedStatusBarAppearance - - io.flutter.embedded_views_preview - - - \ No newline at end of file + + AppGroupId + $(CUSTOM_GROUP_ID) + BGTaskSchedulerPermittedIdentifiers + + app.alextran.immich.background.refreshUpload + app.alextran.immich.background.processingUpload + app.alextran.immich.backgroundFetch + app.alextran.immich.backgroundProcessing + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ${PRODUCT_NAME} + CFBundleDocumentTypes + + + CFBundleTypeName + ShareHandler + LSHandlerRank + Alternate + LSItemContentTypes + + public.file-url + public.image + public.text + public.movie + public.url + public.data + + + + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLocalizations + + en + ar + ca + cs + da + de + es + fi + fr + he + hi + hu + it + ja + ko + lv + mn + nb + nl + pl + pt + ro + ru + sk + sl + sr + sv + th + uk + vi + zh + + CFBundleName + immich_mobile + CFBundlePackageType + APPL + CFBundleShortVersionString + 2.7.3 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + Share Extension + CFBundleURLSchemes + + ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER) + + + + CFBundleTypeRole + Editor + CFBundleURLName + Deep Link + CFBundleURLSchemes + + immich + + + + CFBundleVersion + 240 + FLTEnableImpeller + + ITSAppUsesNonExemptEncryption + + LSApplicationQueriesSchemes + + https + + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + No + MGLMapboxMetricsEnabledSettingShownInApp + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSBonjourServices + + _googlecast._tcp + _CC1AD845._googlecast._tcp + + NSCameraUsageDescription + We need to access the camera to let you take beautiful video using this app + NSFaceIDUsageDescription + We need to use FaceID to allow access to your locked folder + NSLocalNetworkUsageDescription + We need local network permission to connect to the local server using IP address and allow the casting feature to work + NSLocationAlwaysAndWhenInUseUsageDescription + We require this permission to access the local WiFi name for background upload mechanism + NSLocationUsageDescription + We require this permission to access the local WiFi name + NSLocationWhenInUseUsageDescription + We require this permission to access the local WiFi name + NSMicrophoneUsageDescription + We need to access the microphone to let you take beautiful video using this app + NSPhotoLibraryAddUsageDescription + We need to manage backup your photos album + NSPhotoLibraryUsageDescription + We need to manage backup your photos album + NSUserActivityTypes + + INSendMessageIntent + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + fetch + processing + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + io.flutter.embedded_views_preview + + + diff --git a/mobile/ios/Runner/Permission/PermissionApiImpl.swift b/mobile/ios/Runner/Permission/PermissionApiImpl.swift deleted file mode 100644 index 3d8e89486d..0000000000 --- a/mobile/ios/Runner/Permission/PermissionApiImpl.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Foundation - -class PermissionApiImpl: PermissionApi { - func isIgnoringBatteryOptimizations() throws -> PermissionStatus { - return PermissionStatus.granted; - } - - func hasManageMediaPermission() throws -> Bool { - return false - } - - func requestManageMediaPermission(completion: @escaping (Result) -> Void) { - completion(.success(false)) - } - - func manageMediaPermission(completion: @escaping (Result) -> Void) { - completion(.success(false)) - } -} diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/ios/Runner/Runner.entitlements index c8a6be9cbb..e5862cb213 100644 --- a/mobile/ios/Runner/Runner.entitlements +++ b/mobile/ios/Runner/Runner.entitlements @@ -10,7 +10,7 @@ com.apple.security.application-groups - $(CUSTOM_GROUP_ID) + group.app.immich.share diff --git a/mobile/ios/Runner/RunnerProfile.entitlements b/mobile/ios/Runner/RunnerProfile.entitlements index 93a4aab552..6a5c086baf 100644 --- a/mobile/ios/Runner/RunnerProfile.entitlements +++ b/mobile/ios/Runner/RunnerProfile.entitlements @@ -12,7 +12,7 @@ com.apple.security.application-groups - $(CUSTOM_GROUP_ID) + group.app.immich.share diff --git a/mobile/ios/Runner/Sync/Messages.g.swift b/mobile/ios/Runner/Sync/Messages.g.swift new file mode 100644 index 0000000000..6bba25d94b --- /dev/null +++ b/mobile/ios/Runner/Sync/Messages.g.swift @@ -0,0 +1,642 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +func deepEqualsMessages(_ lhs: Any?, _ rhs: Any?) -> Bool { + let cleanLhs = nilOrValue(lhs) as Any? + let cleanRhs = nilOrValue(rhs) as Any? + switch (cleanLhs, cleanRhs) { + case (nil, nil): + return true + + case (nil, _), (_, nil): + return false + + case is (Void, Void): + return true + + case let (cleanLhsHashable, cleanRhsHashable) as (AnyHashable, AnyHashable): + return cleanLhsHashable == cleanRhsHashable + + case let (cleanLhsArray, cleanRhsArray) as ([Any?], [Any?]): + guard cleanLhsArray.count == cleanRhsArray.count else { return false } + for (index, element) in cleanLhsArray.enumerated() { + if !deepEqualsMessages(element, cleanRhsArray[index]) { + return false + } + } + return true + + case let (cleanLhsDictionary, cleanRhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]): + guard cleanLhsDictionary.count == cleanRhsDictionary.count else { return false } + for (key, cleanLhsValue) in cleanLhsDictionary { + guard cleanRhsDictionary.index(forKey: key) != nil else { return false } + if !deepEqualsMessages(cleanLhsValue, cleanRhsDictionary[key]!) { + return false + } + } + return true + + default: + // Any other type shouldn't be able to be used with pigeon. File an issue if you find this to be untrue. + return false + } +} + +func deepHashMessages(value: Any?, hasher: inout Hasher) { + if let valueList = value as? [AnyHashable] { + for item in valueList { deepHashMessages(value: item, hasher: &hasher) } + return + } + + if let valueDict = value as? [AnyHashable: AnyHashable] { + for key in valueDict.keys { + hasher.combine(key) + deepHashMessages(value: valueDict[key]!, hasher: &hasher) + } + return + } + + if let hashableValue = value as? AnyHashable { + hasher.combine(hashableValue.hashValue) + } + + return hasher.combine(String(describing: value)) +} + + + +enum PlatformAssetPlaybackStyle: Int { + case unknown = 0 + case image = 1 + case video = 2 + case imageAnimated = 3 + case livePhoto = 4 + case videoLooping = 5 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PlatformAsset: Hashable { + var id: String + var name: String + var type: Int64 + var createdAt: Int64? = nil + var updatedAt: Int64? = nil + var width: Int64? = nil + var height: Int64? = nil + var durationInSeconds: Int64 + var orientation: Int64 + var isFavorite: Bool + var adjustmentTime: Int64? = nil + var latitude: Double? = nil + var longitude: Double? = nil + var playbackStyle: PlatformAssetPlaybackStyle + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PlatformAsset? { + let id = pigeonVar_list[0] as! String + let name = pigeonVar_list[1] as! String + let type = pigeonVar_list[2] as! Int64 + let createdAt: Int64? = nilOrValue(pigeonVar_list[3]) + let updatedAt: Int64? = nilOrValue(pigeonVar_list[4]) + let width: Int64? = nilOrValue(pigeonVar_list[5]) + let height: Int64? = nilOrValue(pigeonVar_list[6]) + let durationInSeconds = pigeonVar_list[7] as! Int64 + let orientation = pigeonVar_list[8] as! Int64 + let isFavorite = pigeonVar_list[9] as! Bool + let adjustmentTime: Int64? = nilOrValue(pigeonVar_list[10]) + let latitude: Double? = nilOrValue(pigeonVar_list[11]) + let longitude: Double? = nilOrValue(pigeonVar_list[12]) + let playbackStyle = pigeonVar_list[13] as! PlatformAssetPlaybackStyle + + return PlatformAsset( + id: id, + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + orientation: orientation, + isFavorite: isFavorite, + adjustmentTime: adjustmentTime, + latitude: latitude, + longitude: longitude, + playbackStyle: playbackStyle + ) + } + func toList() -> [Any?] { + return [ + id, + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + orientation, + isFavorite, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ] + } + static func == (lhs: PlatformAsset, rhs: PlatformAsset) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct PlatformAlbum: Hashable { + var id: String + var name: String + var updatedAt: Int64? = nil + var isCloud: Bool + var assetCount: Int64 + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> PlatformAlbum? { + let id = pigeonVar_list[0] as! String + let name = pigeonVar_list[1] as! String + let updatedAt: Int64? = nilOrValue(pigeonVar_list[2]) + let isCloud = pigeonVar_list[3] as! Bool + let assetCount = pigeonVar_list[4] as! Int64 + + return PlatformAlbum( + id: id, + name: name, + updatedAt: updatedAt, + isCloud: isCloud, + assetCount: assetCount + ) + } + func toList() -> [Any?] { + return [ + id, + name, + updatedAt, + isCloud, + assetCount, + ] + } + static func == (lhs: PlatformAlbum, rhs: PlatformAlbum) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SyncDelta: Hashable { + var hasChanges: Bool + var updates: [PlatformAsset] + var deletes: [String] + var assetAlbums: [String: [String]] + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> SyncDelta? { + let hasChanges = pigeonVar_list[0] as! Bool + let updates = pigeonVar_list[1] as! [PlatformAsset] + let deletes = pigeonVar_list[2] as! [String] + let assetAlbums = pigeonVar_list[3] as! [String: [String]] + + return SyncDelta( + hasChanges: hasChanges, + updates: updates, + deletes: deletes, + assetAlbums: assetAlbums + ) + } + func toList() -> [Any?] { + return [ + hasChanges, + updates, + deletes, + assetAlbums, + ] + } + static func == (lhs: SyncDelta, rhs: SyncDelta) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct HashResult: Hashable { + var assetId: String + var error: String? = nil + var hash: String? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> HashResult? { + let assetId = pigeonVar_list[0] as! String + let error: String? = nilOrValue(pigeonVar_list[1]) + let hash: String? = nilOrValue(pigeonVar_list[2]) + + return HashResult( + assetId: assetId, + error: error, + hash: hash + ) + } + func toList() -> [Any?] { + return [ + assetId, + error, + hash, + ] + } + static func == (lhs: HashResult, rhs: HashResult) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct CloudIdResult: Hashable { + var assetId: String + var error: String? = nil + var cloudId: String? = nil + + + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> CloudIdResult? { + let assetId = pigeonVar_list[0] as! String + let error: String? = nilOrValue(pigeonVar_list[1]) + let cloudId: String? = nilOrValue(pigeonVar_list[2]) + + return CloudIdResult( + assetId: assetId, + error: error, + cloudId: cloudId + ) + } + func toList() -> [Any?] { + return [ + assetId, + error, + cloudId, + ] + } + static func == (lhs: CloudIdResult, rhs: CloudIdResult) -> Bool { + return deepEqualsMessages(lhs.toList(), rhs.toList()) } + func hash(into hasher: inout Hasher) { + deepHashMessages(value: toList(), hasher: &hasher) + } +} + +private class MessagesPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return PlatformAssetPlaybackStyle(rawValue: enumResultAsInt) + } + return nil + case 130: + return PlatformAsset.fromList(self.readValue() as! [Any?]) + case 131: + return PlatformAlbum.fromList(self.readValue() as! [Any?]) + case 132: + return SyncDelta.fromList(self.readValue() as! [Any?]) + case 133: + return HashResult.fromList(self.readValue() as! [Any?]) + case 134: + return CloudIdResult.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class MessagesPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? PlatformAssetPlaybackStyle { + super.writeByte(129) + super.writeValue(value.rawValue) + } else if let value = value as? PlatformAsset { + super.writeByte(130) + super.writeValue(value.toList()) + } else if let value = value as? PlatformAlbum { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? SyncDelta { + super.writeByte(132) + super.writeValue(value.toList()) + } else if let value = value as? HashResult { + super.writeByte(133) + super.writeValue(value.toList()) + } else if let value = value as? CloudIdResult { + super.writeByte(134) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class MessagesPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return MessagesPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return MessagesPigeonCodecWriter(data: data) + } +} + +class MessagesPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = MessagesPigeonCodec(readerWriter: MessagesPigeonCodecReaderWriter()) +} + + +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol NativeSyncApi { + func shouldFullSync() throws -> Bool + func getMediaChanges() throws -> SyncDelta + func checkpointSync() throws + func clearSyncCheckpoint() throws + func getAssetIdsForAlbum(albumId: String) throws -> [String] + func getAlbums() throws -> [PlatformAlbum] + func getAssetsCountSince(albumId: String, timestamp: Int64) throws -> Int64 + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] + func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void) + func cancelHashing() throws + func getTrashedAssets() throws -> [String: [PlatformAsset]] + func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class NativeSyncApiSetup { + static var codec: FlutterStandardMessageCodec { MessagesPigeonCodec.shared } + /// Sets up an instance of `NativeSyncApi` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NativeSyncApi?, messageChannelSuffix: String = "") { + let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + #if os(iOS) + let taskQueue = binaryMessenger.makeBackgroundTaskQueue?() + #else + let taskQueue: FlutterTaskQueue? = nil + #endif + let shouldFullSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + shouldFullSyncChannel.setMessageHandler { _, reply in + do { + let result = try api.shouldFullSync() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + shouldFullSyncChannel.setMessageHandler(nil) + } + let getMediaChangesChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getMediaChangesChannel.setMessageHandler { _, reply in + do { + let result = try api.getMediaChanges() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getMediaChangesChannel.setMessageHandler(nil) + } + let checkpointSyncChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + checkpointSyncChannel.setMessageHandler { _, reply in + do { + try api.checkpointSync() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + checkpointSyncChannel.setMessageHandler(nil) + } + let clearSyncCheckpointChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + clearSyncCheckpointChannel.setMessageHandler { _, reply in + do { + try api.clearSyncCheckpoint() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + clearSyncCheckpointChannel.setMessageHandler(nil) + } + let getAssetIdsForAlbumChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getAssetIdsForAlbumChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let albumIdArg = args[0] as! String + do { + let result = try api.getAssetIdsForAlbum(albumId: albumIdArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAssetIdsForAlbumChannel.setMessageHandler(nil) + } + let getAlbumsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getAlbumsChannel.setMessageHandler { _, reply in + do { + let result = try api.getAlbums() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAlbumsChannel.setMessageHandler(nil) + } + let getAssetsCountSinceChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getAssetsCountSinceChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let albumIdArg = args[0] as! String + let timestampArg = args[1] as! Int64 + do { + let result = try api.getAssetsCountSince(albumId: albumIdArg, timestamp: timestampArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAssetsCountSinceChannel.setMessageHandler(nil) + } + let getAssetsForAlbumChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getAssetsForAlbumChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let albumIdArg = args[0] as! String + let updatedTimeCondArg: Int64? = nilOrValue(args[1]) + do { + let result = try api.getAssetsForAlbum(albumId: albumIdArg, updatedTimeCond: updatedTimeCondArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getAssetsForAlbumChannel.setMessageHandler(nil) + } + let hashAssetsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + hashAssetsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let assetIdsArg = args[0] as! [String] + let allowNetworkAccessArg = args[1] as! Bool + api.hashAssets(assetIds: assetIdsArg, allowNetworkAccess: allowNetworkAccessArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + hashAssetsChannel.setMessageHandler(nil) + } + let cancelHashingChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + cancelHashingChannel.setMessageHandler { _, reply in + do { + try api.cancelHashing() + reply(wrapResult(nil)) + } catch { + reply(wrapError(error)) + } + } + } else { + cancelHashingChannel.setMessageHandler(nil) + } + let getTrashedAssetsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getTrashedAssetsChannel.setMessageHandler { _, reply in + do { + let result = try api.getTrashedAssets() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getTrashedAssetsChannel.setMessageHandler(nil) + } + let getCloudIdForAssetIdsChannel = taskQueue == nil + ? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + : FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue) + if let api = api { + getCloudIdForAssetIdsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let assetIdsArg = args[0] as! [String] + do { + let result = try api.getCloudIdForAssetIds(assetIds: assetIdsArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + getCloudIdForAssetIdsChannel.setMessageHandler(nil) + } + } +} diff --git a/mobile/ios/Runner/Sync/MessagesImpl.swift b/mobile/ios/Runner/Sync/MessagesImpl.swift index ddfd023690..8022fb06d2 100644 --- a/mobile/ios/Runner/Sync/MessagesImpl.swift +++ b/mobile/ios/Runner/Sync/MessagesImpl.swift @@ -39,9 +39,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { private static let hashCancelledCode = "HASH_CANCELLED" private static let hashCancelled = Result<[HashResult], Error>.failure(PigeonError(code: hashCancelledCode, message: "Hashing cancelled", details: nil)) - private var syncTask: Task? - private static let syncCancelledCode = "SYNC_CANCELLED" - private static let syncCancelled = PigeonError(code: syncCancelledCode, message: "Sync cancelled", details: nil) init(with defaults: UserDefaults = .standard) { self.defaults = defaults @@ -74,11 +71,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { saveChangeToken(token: PHPhotoLibrary.shared().currentChangeToken) } - func shouldFullSync(completion: @escaping (Result) -> Void) { - runSync(completion) { $0.shouldFullSync() } - } - - private func shouldFullSync() -> Bool { + func shouldFullSync() -> Bool { guard #available(iOS 16, *), PHPhotoLibrary.authorizationStatus(for: .readWrite) == .authorized, let storedToken = getChangeToken() else { @@ -94,17 +87,12 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return false } - func getAlbums(completion: @escaping (Result<[PlatformAlbum], Error>) -> Void) { - runSync(completion) { try $0.getAlbums() } - } - - private func getAlbums() throws -> [PlatformAlbum] { + func getAlbums() throws -> [PlatformAlbum] { var albums: [PlatformAlbum] = [] - for type in albumTypes { + albumTypes.forEach { type in let collections = PHAssetCollection.fetchAssetCollections(with: type, subtype: .any, options: nil) for i in 0..) -> Void) { - runSync(completion) { try $0.getMediaChanges() } - } - - private func getMediaChanges() throws -> SyncDelta { + func getMediaChanges() throws -> SyncDelta { guard #available(iOS 16, *) else { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature requires iOS 16 or later.", details: nil) } @@ -162,49 +146,51 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return SyncDelta(hasChanges: false, updates: [], deletes: [], assetAlbums: [:]) } - let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) - - var updatedAssets: Set = [] - var deletedAssets: Set = [] - - for change in changes { - try Task.checkCancellation() - guard let details = try? change.changeDetails(for: PHObjectType.asset) else { continue } + do { + let changes = try PHPhotoLibrary.shared().fetchPersistentChanges(since: storedToken) - let updated = details.updatedLocalIdentifiers.union(details.insertedLocalIdentifiers) - deletedAssets.formUnion(details.deletedLocalIdentifiers) + var updatedAssets: Set = [] + var deletedAssets: Set = [] - if (updated.isEmpty) { continue } - - let options = PHFetchOptions() - options.includeHiddenAssets = false - let result = PHAsset.fetchAssets(withLocalIdentifiers: Array(updated), options: options) - for i in 0..) -> [String: [String]] { guard !assets.isEmpty else { return [:] @@ -227,11 +213,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return albumAssets } - func getAssetIdsForAlbum(albumId: String, completion: @escaping (Result<[String], Error>) -> Void) { - runSync(completion) { try $0.getAssetIdsForAlbum(albumId: albumId) } - } - - private func getAssetIdsForAlbum(albumId: String) throws -> [String] { + func getAssetIdsForAlbum(albumId: String) throws -> [String] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -241,14 +223,9 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { let options = PHFetchOptions() options.includeHiddenAssets = false let assets = getAssetsFromAlbum(in: album, options: options) - assets.enumerateObjects { (asset, _, stop) in - if Task.isCancelled { - stop.pointee = true - return - } + assets.enumerateObjects { (asset, _, _) in ids.append(asset.localIdentifier) } - try Task.checkCancellation() return ids } @@ -266,11 +243,7 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { return Int64(assets.count) } - func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?, completion: @escaping (Result<[PlatformAsset], Error>) -> Void) { - runSync(completion) { try $0.getAssetsForAlbum(albumId: albumId, updatedTimeCond: updatedTimeCond) } - } - - private func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { + func getAssetsForAlbum(albumId: String, updatedTimeCond: Int64?) throws -> [PlatformAsset] { let collections = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [albumId], options: nil) guard let album = collections.firstObject else { return [] @@ -289,14 +262,9 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { } var assets: [PlatformAsset] = [] - result.enumerateObjects { (asset, _, stop) in - if Task.isCancelled { - stop.pointee = true - return - } + result.enumerateObjects { (asset, _, _) in assets.append(asset.toPlatformAsset()) } - try Task.checkCancellation() return assets } @@ -356,31 +324,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { hashTask = nil } - func cancelSync() { - syncTask?.cancel() - syncTask = nil - } - - private func runSync( - _ completion: @escaping (Result) -> Void, - _ work: @escaping (NativeSyncApiImpl) throws -> T - ) { - syncTask?.cancel() - syncTask = Task { [weak self] in - guard let self else { return nil } - let result: Result - do { - result = .success(try work(self)) - } catch is CancellationError { - result = .failure(Self.syncCancelled) - } catch { - result = .failure(error) - } - self.completeWhenActive(for: completion, with: result) - return nil - } - } - private func hashAsset(_ asset: PHAsset, allowNetworkAccess: Bool) async -> HashResult? { class RequestRef { var id: PHAssetResourceDataRequestID? @@ -439,10 +382,6 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin { func getTrashedAssets() throws -> [String: [PlatformAsset]] { throw PigeonError(code: "UNSUPPORTED_OS", message: "This feature not supported on iOS.", details: nil) } - - func restoreFromTrashById(mediaId: String, type: Int64, completion: @escaping (Result) -> Void) { - completion(.success(false)) - } private func getAssetsFromAlbum(in album: PHAssetCollection, options: PHFetchOptions) -> PHFetchResult { // Ensure to actually getting all assets for the Recents album diff --git a/mobile/ios/Runner/Sync/PHAssetExtensions.swift b/mobile/ios/Runner/Sync/PHAssetExtensions.swift index d4d947d5bb..0fc1dfc701 100644 --- a/mobile/ios/Runner/Sync/PHAssetExtensions.swift +++ b/mobile/ios/Runner/Sync/PHAssetExtensions.swift @@ -21,7 +21,7 @@ extension PHAsset { updatedAt: modificationDate.map { Int64($0.timeIntervalSince1970) }, width: Int64(pixelWidth), height: Int64(pixelHeight), - durationMs: Int64(duration * 1000), + durationInSeconds: Int64(duration), orientation: 0, isFavorite: isFavorite, adjustmentTime: adjustmentTimestamp, diff --git a/mobile/ios/ShareExtension/Info.plist b/mobile/ios/ShareExtension/Info.plist index dbed75e380..0f52fbffdf 100644 --- a/mobile/ios/ShareExtension/Info.plist +++ b/mobile/ios/ShareExtension/Info.plist @@ -1,35 +1,35 @@ - - AppGroupId - $(CUSTOM_GROUP_ID) - NSExtension - - NSExtensionAttributes - - IntentsSupported - - INSendMessageIntent - - NSExtensionActivationRule - SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, + + AppGroupId + $(CUSTOM_GROUP_ID) + NSExtension + + NSExtensionAttributes + + IntentsSupported + + INSendMessageIntent + + NSExtensionActivationRule + SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments, $attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0 ).@count > 0 - PHSupportedMediaTypes - - Video - Image - - - NSExtensionMainStoryboard - MainInterface - NSExtensionPointIdentifier - com.apple.share-services - - - + PHSupportedMediaTypes + + Video + Image + + + NSExtensionMainStoryboard + MainInterface + NSExtensionPointIdentifier + com.apple.share-services + + + \ No newline at end of file diff --git a/mobile/ios/ShareExtension/ShareExtension.entitlements b/mobile/ios/ShareExtension/ShareExtension.entitlements index 1328658832..4ad1a257d8 100644 --- a/mobile/ios/ShareExtension/ShareExtension.entitlements +++ b/mobile/ios/ShareExtension/ShareExtension.entitlements @@ -1,10 +1,10 @@ - - com.apple.security.application-groups - - $(CUSTOM_GROUP_ID) - - - \ No newline at end of file + + com.apple.security.application-groups + + group.app.immich.share + + + diff --git a/mobile/ios/Signing.xcconfig b/mobile/ios/Signing.xcconfig deleted file mode 100644 index bd6b270772..0000000000 --- a/mobile/ios/Signing.xcconfig +++ /dev/null @@ -1,29 +0,0 @@ -// Xcode Signing Constants -// -// These are integrated into the Xcode project at build time to allow for dynamic signing configs -// across our many targets. -// -// Create `Signing.local.xcconfig` overriding each variable to provide your own personal signing -// overrides during local development: -// IMMICH_TEAM_ID = ABCDE12345 -// IMMICH_BUNDLE_ID_PROD = com.customuniqueid.immich -// IMMICH_BUNDLE_ID_DEV = com.customuniqueid.immichdev -// IMMICH_GROUP_ID = group.com.customuniqueid.immich - -// Apple Developer Team ID used for automatically provisioning signing credentials -IMMICH_TEAM_ID = 2W7AC6T8T5 - -// Root bundle identifier for the Release configuration. -// Extension suffixes (`.ShareExtension`/`.Widget`) will be appended -IMMICH_BUNDLE_ID_PROD = app.alextran.immich - -// Root bundle identifier for the Debug and Profile configurations. -// Build scheme suffixes (`.debug`/`.profile`) will be appended -IMMICH_BUNDLE_ID_DEV = app.futo.immich - -// Root App Group identifier. -// Build scheme suffixes (`.debug`/`.profile`) will be appended -IMMICH_GROUP_ID = group.app.immich.share - -// Optional per-developer configuration override -#include? "Signing.local.xcconfig" diff --git a/mobile/ios/WidgetExtension/ImageEntry.swift b/mobile/ios/WidgetExtension/ImageEntry.swift index 58f89339ac..ee371703a8 100644 --- a/mobile/ios/WidgetExtension/ImageEntry.swift +++ b/mobile/ios/WidgetExtension/ImageEntry.swift @@ -12,15 +12,13 @@ struct ImageEntry: TimelineEntry { var subtitle: String? = nil var error: WidgetError? = nil var deepLink: URL? = nil - var forceFullColor: Bool = true } static func build( api: ImmichAPI, asset: Asset, dateOffset: Int, - subtitle: String? = nil, - forceFullColor: Bool = true + subtitle: String? = nil ) async throws -> Self { @@ -36,8 +34,7 @@ struct ImageEntry: TimelineEntry { image: image, metadata: EntryMetadata( subtitle: subtitle, - deepLink: asset.deepLink, - forceFullColor: forceFullColor + deepLink: asset.deepLink ) ) } @@ -114,26 +111,12 @@ struct ImageEntry: TimelineEntry { } -// Required so that we can decode older versions of the metadata -extension ImageEntry.Metadata { - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - subtitle = try container.decodeIfPresent(String.self, forKey: .subtitle) - error = try container.decodeIfPresent(WidgetError.self, forKey: .error) - deepLink = try container.decodeIfPresent(URL.self, forKey: .deepLink) - - forceFullColor = - try container.decodeIfPresent(Bool.self, forKey: .forceFullColor) ?? true - } -} - func generateRandomEntries( api: ImmichAPI, now: Date, count: Int, filter: SearchFilter = Album.NONE.filter, - subtitle: String? = nil, - forceFullColor: Bool = true + subtitle: String? = nil ) async throws -> [ImageEntry] { @@ -149,8 +132,7 @@ func generateRandomEntries( api: api, asset: asset, dateOffset: dateOffset, - subtitle: subtitle, - forceFullColor: forceFullColor + subtitle: subtitle ) } } diff --git a/mobile/ios/WidgetExtension/ImageWidgetView.swift b/mobile/ios/WidgetExtension/ImageWidgetView.swift index 2e3a816d92..8e810b051e 100644 --- a/mobile/ios/WidgetExtension/ImageWidgetView.swift +++ b/mobile/ios/WidgetExtension/ImageWidgetView.swift @@ -3,10 +3,10 @@ import WidgetKit extension Image { @ViewBuilder - func tintedWidgetImageModifier(forceFullColor: Bool) -> some View { + func tintedWidgetImageModifier() -> some View { if #available(iOS 18.0, *) { self - .widgetAccentedRenderingMode(forceFullColor ? .accentedDesaturated : .fullColor) + .widgetAccentedRenderingMode(.accentedDesaturated) } else { self } @@ -17,77 +17,44 @@ struct ImmichWidgetView: View { var entry: ImageEntry var body: some View { - if let image = entry.image { - ImmichWidgetContentView( - image: image, - subtitle: entry.metadata.subtitle, - deepLink: entry.metadata.deepLink, - forceFullColor: entry.metadata.forceFullColor - ) - } else { - ImmichWidgetLoadingView( - message: entry.metadata.error?.errorDescription, - forceFullColor: entry.metadata.forceFullColor - ) - } - } -} - -private struct ImmichWidgetLoadingView: View { - let message: String? - let forceFullColor: Bool - - var body: some View { - let messageText = Text(message ?? "") - .minimumScaleFactor(0.25) - .multilineTextAlignment(.center) - .foregroundStyle(.secondary) - - VStack(spacing: 8) { - // This is used as a nicer way to center the image, rather than using offsets - messageText.hidden() - - Image("LaunchImage") - .tintedWidgetImageModifier(forceFullColor: forceFullColor) - - messageText - } - } -} - -private struct ImmichWidgetContentView: View { - let image: UIImage - let subtitle: String? - let deepLink: URL? - let forceFullColor: Bool - - var body: some View { - ZStack(alignment: .leading) { - Color.clear.overlay( - Image(uiImage: image) - .resizable() - .tintedWidgetImageModifier(forceFullColor: forceFullColor) - .scaledToFill() - ) - + if entry.image == nil { VStack { - Spacer() - if let subtitle { - Text(subtitle) - .foregroundColor(.white) - .padding(6) - .background(ContainerRelativeShape().fill(Color.black.opacity(0.6))) - .font(.system(size: 16)) - } + Image("LaunchImage") + .tintedWidgetImageModifier() + Text(entry.metadata.error?.errorDescription ?? "") + .minimumScaleFactor(0.25) + .multilineTextAlignment(.center) + .foregroundStyle(.secondary) } .padding(16) + } else { + ZStack(alignment: .leading) { + Color.clear.overlay( + Image(uiImage: entry.image!) + .resizable() + .tintedWidgetImageModifier() + .scaledToFill() + + ) + VStack { + Spacer() + if let subtitle = entry.metadata.subtitle { + Text(subtitle) + .foregroundColor(.white) + .padding(8) + .background(Color.black.opacity(0.6)) + .cornerRadius(8) + .font(.system(size: 16)) + } + } + .padding(16) + } + .widgetURL(entry.metadata.deepLink) } - .widgetURL(deepLink) } } #Preview( - "Medium", as: .systemMedium, widget: { ImmichRandomWidget() @@ -96,93 +63,10 @@ private struct ImmichWidgetContentView: View { let date = Date() ImageEntry( date: date, - image: UIImage(named: "LaunchImage"), + image: UIImage(named: "ImmichLogo"), metadata: EntryMetadata( subtitle: "1 year ago" ) ) } ) - -#Preview( - "Medium No Data", - as: .systemMedium, - widget: { - ImmichRandomWidget() - }, - timeline: { - let date = Date() - ImageEntry( - date: date, - image: nil - ) - } -) - -#Preview( - "Medium No Data Error", - as: .systemMedium, - widget: { - ImmichRandomWidget() - }, - timeline: { - let date = Date() - ImageEntry( - date: date, - image: nil, - metadata: EntryMetadata(error: WidgetError.fetchFailed) - ) - } -) - -#Preview( - "Small", - as: .systemSmall, - widget: { - ImmichRandomWidget() - }, - timeline: { - let date = Date() - ImageEntry( - date: date, - image: UIImage(named: "LaunchImage"), - metadata: EntryMetadata( - subtitle: "Yesterday" - ) - ) - } -) - -#Preview( - "Small No Data Error", - as: .systemSmall, - widget: { - ImmichRandomWidget() - }, - timeline: { - let date = Date() - ImageEntry( - date: date, - image: nil, - metadata: EntryMetadata(error: WidgetError.fetchFailed) - ) - } -) - -#Preview( - "Large", - as: .systemLarge, - widget: { - ImmichRandomWidget() - }, - timeline: { - let date = Date() - ImageEntry( - date: date, - image: UIImage(named: "LaunchImage"), - metadata: EntryMetadata( - subtitle: "2000 seconds ago" - ) - ) - } -) diff --git a/mobile/ios/WidgetExtension/ImmichAPI.swift b/mobile/ios/WidgetExtension/ImmichAPI.swift index 7e9dcd904a..6ae2d502f8 100644 --- a/mobile/ios/WidgetExtension/ImmichAPI.swift +++ b/mobile/ios/WidgetExtension/ImmichAPI.swift @@ -2,7 +2,7 @@ import Foundation import SwiftUI import WidgetKit -let IMMICH_SHARE_GROUP = Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as! String +let IMMICH_SHARE_GROUP = "group.app.immich.share" enum WidgetError: Error, Codable { case noLogin @@ -25,7 +25,7 @@ extension WidgetError: LocalizedError { return "Login to Immich" case .fetchFailed: - return "Unable to connect to Immich" + return "Unable to connect to your Immich instance" case .albumNotFound: return "Album not found" @@ -203,10 +203,7 @@ class ImmichAPI { func fetchMemory(for date: Date) async throws -> [MemoryResult] { // get URL - let localDay = date.formatted( - Date.ISO8601FormatStyle(timeZone: .current).year().month().day().dateSeparator(.dash) - ) - let memoryParams = [URLQueryItem(name: "for", value: localDay)] + let memoryParams = [URLQueryItem(name: "for", value: date.ISO8601Format())] guard let searchURL = buildRequestURL( serverConfig: serverConfig, diff --git a/mobile/ios/WidgetExtension/Info.plist b/mobile/ios/WidgetExtension/Info.plist index 15b00baa20..d4e598ee31 100644 --- a/mobile/ios/WidgetExtension/Info.plist +++ b/mobile/ios/WidgetExtension/Info.plist @@ -2,8 +2,6 @@ - AppGroupId - $(CUSTOM_GROUP_ID) NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/mobile/ios/WidgetExtension/WidgetExtension.entitlements b/mobile/ios/WidgetExtension/WidgetExtension.entitlements index 1328658832..4ad1a257d8 100644 --- a/mobile/ios/WidgetExtension/WidgetExtension.entitlements +++ b/mobile/ios/WidgetExtension/WidgetExtension.entitlements @@ -1,10 +1,10 @@ - - com.apple.security.application-groups - - $(CUSTOM_GROUP_ID) - - - \ No newline at end of file + + com.apple.security.application-groups + + group.app.immich.share + + + diff --git a/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift b/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift index dee5a32159..22414fbec4 100644 --- a/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift +++ b/mobile/ios/WidgetExtension/widgets/MemoryWidget.swift @@ -2,21 +2,7 @@ import AppIntents import SwiftUI import WidgetKit -// MARK: Widget Configuration - -struct MemoryConfigurationAppIntent: WidgetConfigurationIntent { - static var title: LocalizedStringResource { "Memories" } - static var description: IntentDescription { - "See memories from Immich." - } - - @Parameter(title: "Always Display in Full Color", default: false) - var forceFullColor: Bool -} - -// MARK: Provider - -struct ImmichMemoryProvider: AppIntentTimelineProvider { +struct ImmichMemoryProvider: TimelineProvider { func getYearDifferenceSubtitle(assetYear: Int) -> String { let currentYear = Calendar.current.component(.year, from: Date.now) // construct a "X years ago" subtitle @@ -29,69 +15,75 @@ struct ImmichMemoryProvider: AppIntentTimelineProvider { ImageEntry(date: Date(), image: nil) } - func snapshot( - for configuration: MemoryConfigurationAppIntent, - in context: Context - ) async -> ImageEntry { + func getSnapshot( + in context: Context, + completion: @escaping @Sendable (ImageEntry) -> Void + ) { let cacheKey = "memory_\(context.family.rawValue)" - guard let api = try? await ImmichAPI() else { - return ImageEntry.handleError(for: cacheKey, error: .noLogin).entries - .first! - } - - guard let memories = try? await api.fetchMemory(for: Date.now) else { - return ImageEntry.handleError(for: cacheKey).entries.first! - } - - for memory in memories { - if let asset = memory.assets.first(where: { $0.type == .image }), - let entry = try? await ImageEntry.build( - api: api, - asset: asset, - dateOffset: 0, - subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year), - forceFullColor: configuration.forceFullColor + Task { + guard let api = try? await ImmichAPI() else { + completion( + ImageEntry.handleError(for: cacheKey, error: .noLogin).entries.first! ) - { - return entry + return } - } - // fallback to random image - guard - let randomImage = try? await api.fetchSearchResults().first, - let imageEntry = try? await ImageEntry.build( - api: api, - asset: randomImage, - dateOffset: 0, - forceFullColor: configuration.forceFullColor - ) - else { - return ImageEntry.handleError(for: cacheKey).entries.first! - } + guard let memories = try? await api.fetchMemory(for: Date.now) + else { + completion(ImageEntry.handleError(for: cacheKey).entries.first!) + return + } - return imageEntry + for memory in memories { + if let asset = memory.assets.first(where: { $0.type == .image }), + let entry = try? await ImageEntry.build( + api: api, + asset: asset, + dateOffset: 0, + subtitle: getYearDifferenceSubtitle(assetYear: memory.data.year) + ) + { + completion(entry) + return + } + } + + // fallback to random image + guard + let randomImage = try? await api.fetchSearchResults().first, + let imageEntry = try? await ImageEntry.build( + api: api, + asset: randomImage, + dateOffset: 0 + ) + else { + completion(ImageEntry.handleError(for: cacheKey).entries.first!) + return + } + + completion(imageEntry) + } } - func timeline( - for configuration: MemoryConfigurationAppIntent, - in context: Context - ) async - -> Timeline - { - var entries: [ImageEntry] = [] - let now = Date() + func getTimeline( + in context: Context, + completion: @escaping @Sendable (Timeline) -> Void + ) { + Task { + var entries: [ImageEntry] = [] + let now = Date() - let cacheKey = "memory_\(context.family.rawValue)" + let cacheKey = "memory_\(context.family.rawValue)" - guard let api = try? await ImmichAPI() else { - return ImageEntry.handleError(for: cacheKey, error: .noLogin) - } + guard let api = try? await ImmichAPI() else { + completion( + ImageEntry.handleError(for: cacheKey, error: .noLogin) + ) + return + } - let memories: [MemoryResult] - do { - memories = try await api.fetchMemory(for: Date.now) + let memories = try await api.fetchMemory(for: Date.now) await withTaskGroup(of: ImageEntry?.self) { group in var totalAssets = 0 @@ -106,8 +98,7 @@ struct ImmichMemoryProvider: AppIntentTimelineProvider { dateOffset: totalAssets, subtitle: getYearDifferenceSubtitle( assetYear: memory.data.year - ), - forceFullColor: configuration.forceFullColor + ) ) } @@ -123,38 +114,38 @@ struct ImmichMemoryProvider: AppIntentTimelineProvider { } } - } catch { - return ImageEntry.handleError(for: cacheKey) - } + // If we didn't add any memory images (some failure occurred or no images in memory), + // default to 12 hours of random photos + if entries.count == 0 { + // this must be a do/catch since we need to + // distinguish between a network fail and an empty search + do { + let search = try await generateRandomEntries( + api: api, + now: now, + count: 12 + ) - // If we didn't add any memory images (some failure occurred or no images in memory), - // default to 12 hours of random photos - if entries.count == 0 { - // this must be a do/catch since we need to - // distinguish between a network fail and an empty search - do { - let search = try await generateRandomEntries( - api: api, - now: now, - count: 12, - forceFullColor: configuration.forceFullColor - ) + // Load or save a cached asset for when network conditions are bad + if search.count == 0 { + completion( + ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable) + ) + return + } - // Load or save a cached asset for when network conditions are bad - if search.count == 0 { - return ImageEntry.handleError(for: cacheKey, error: .noAssetsAvailable) + entries.append(contentsOf: search) + } catch { + completion(ImageEntry.handleError(for: cacheKey)) + return } - - entries.append(contentsOf: search) - } catch { - return ImageEntry.handleError(for: cacheKey) } + + // cache the last image + try? entries.last!.cache(for: cacheKey) + + completion(Timeline(entries: entries, policy: .atEnd)) } - - // cache the last image - try? entries.last!.cache(for: cacheKey) - - return Timeline(entries: entries, policy: .atEnd) } } @@ -162,9 +153,8 @@ struct ImmichMemoryWidget: Widget { let kind: String = "com.immich.widget.memory" var body: some WidgetConfiguration { - AppIntentConfiguration( + StaticConfiguration( kind: kind, - intent: MemoryConfigurationAppIntent.self, provider: ImmichMemoryProvider() ) { entry in ImmichWidgetView(entry: entry) diff --git a/mobile/ios/WidgetExtension/widgets/RandomWidget.swift b/mobile/ios/WidgetExtension/widgets/RandomWidget.swift index d987464152..37f3c5e596 100644 --- a/mobile/ios/WidgetExtension/widgets/RandomWidget.swift +++ b/mobile/ios/WidgetExtension/widgets/RandomWidget.swift @@ -47,9 +47,6 @@ struct RandomConfigurationAppIntent: WidgetConfigurationIntent { @Parameter(title: "Show Album Name", default: false) var showAlbumName: Bool - - @Parameter(title: "Always Display in Full Color", default: false) - var forceFullColor: Bool } // MARK: Provider @@ -79,8 +76,7 @@ struct ImmichRandomProvider: AppIntentTimelineProvider { let entry = try? await ImageEntry.build( api: api, asset: randomImage, - dateOffset: 0, - forceFullColor: configuration.forceFullColor + dateOffset: 0 ) else { return ImageEntry.handleError(for: cacheKey).entries.first! @@ -118,8 +114,7 @@ struct ImmichRandomProvider: AppIntentTimelineProvider { now: now, count: 12, filter: album.filter, - subtitle: configuration.showAlbumName ? albumName : nil, - forceFullColor: configuration.forceFullColor + subtitle: configuration.showAlbumName ? albumName : nil ) // Load or save a cached asset for when network conditions are bad diff --git a/mobile/ios/fastlane/Appfile b/mobile/ios/fastlane/Appfile index 77318e3603..e233ba2dcc 100644 --- a/mobile/ios/fastlane/Appfile +++ b/mobile/ios/fastlane/Appfile @@ -1,5 +1,5 @@ app_identifier "app.alextran.immich" # The bundle identifier of your app -apple_id "altran@futo.org" # Your Apple email address +apple_id "alex.tran1502@gmail.com" # Your Apple email address # For more information about the Appfile, see: diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 17fa91786e..9c31ced00d 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -17,12 +17,10 @@ default_platform(:ios) platform :ios do # Constants - TEAM_ID = "2W7AC6T8T5" - CODE_SIGN_IDENTITY = "Apple Distribution: FUTO Holdings, Inc. (#{TEAM_ID})" + TEAM_ID = "2F67MQ8R79" + CODE_SIGN_IDENTITY = "Apple Distribution: Hau Tran (#{TEAM_ID})" BASE_BUNDLE_ID = "app.alextran.immich" - DEV_BUNDLE_ID = "tech.futo.immich.testflight" - DEV_GROUP_ID = "group.app.immich.share.testflight" - + # Helper method to get App Store Connect API key def get_api_key app_store_connect_api_key( @@ -34,13 +32,6 @@ platform :ios do ) end - # Helper method to assemble xcargs with optional CUSTOM_GROUP_ID override - def build_xcargs(group_id: nil) - args = "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual" - args += " CUSTOM_GROUP_ID='#{group_id}'" if group_id - args - end - # Helper method to get version from pubspec.yaml def get_version_from_pubspec require 'yaml' @@ -49,64 +40,66 @@ def get_version_from_pubspec pubspec = YAML.load_file(pubspec_path) version_string = pubspec['version'] - version_string ? version_string.split('+').first.split('-').first : nil + version_string ? version_string.split('+').first : nil end # Helper method to configure code signing for all targets - def configure_code_signing(base_bundle_id:, profile_name_main:, profile_name_share:, profile_name_widget:) + def configure_code_signing(bundle_id_suffix: "", profile_name_main:, profile_name_share:, profile_name_widget:) + bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" + # Runner (main app) update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: base_bundle_id, + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}", profile_name: profile_name_main, targets: ["Runner"] ) - + # ShareExtension update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: "#{base_bundle_id}.ShareExtension", + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.ShareExtension", profile_name: profile_name_share, targets: ["ShareExtension"] ) - + # WidgetExtension update_code_signing_settings( use_automatic_signing: false, path: "./Runner.xcodeproj", team_id: ENV["FASTLANE_TEAM_ID"] || TEAM_ID, code_sign_identity: CODE_SIGN_IDENTITY, - bundle_identifier: "#{base_bundle_id}.Widget", + bundle_identifier: "#{BASE_BUNDLE_ID}#{bundle_suffix}.Widget", profile_name: profile_name_widget, targets: ["WidgetExtension"] ) end - + # Helper method to build and upload to TestFlight def build_and_upload( api_key:, - base_bundle_id:, + bundle_id_suffix: "", configuration: "Release", distribute_external: true, version_number: nil, profile_name_main:, profile_name_share:, - profile_name_widget:, - group_id: nil + profile_name_widget: ) - app_identifier = base_bundle_id - + bundle_suffix = bundle_id_suffix.empty? ? "" : ".#{bundle_id_suffix}" + app_identifier = "#{BASE_BUNDLE_ID}#{bundle_suffix}" + # Set version number if provided if version_number increment_version_number(version_number: version_number) end - + # Increment build number increment_build_number( build_number: latest_testflight_build_number( @@ -115,14 +108,14 @@ end ) + 1, xcodeproj: "./Runner.xcodeproj" ) - + # Build the app build_app( scheme: "Runner", workspace: "Runner.xcworkspace", configuration: configuration, export_method: "app-store", - xcargs: build_xcargs(group_id: group_id), + xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", export_options: { provisioningProfiles: { "#{app_identifier}" => profile_name_main, @@ -145,37 +138,36 @@ end desc "iOS Development Build to TestFlight (requires separate bundle ID)" lane :gha_testflight_dev do api_key = get_api_key - + # Download and install provisioning profiles from App Store Connect # Certificate is imported by GHA workflow into build.keychain # Capture profile names after each sigh call - sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true) main_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) + + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true) share_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) + + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true) widget_profile_name = lane_context[SharedValues::SIGH_NAME] - + # Configure code signing for dev bundle IDs using the downloaded profile names configure_code_signing( - base_bundle_id: DEV_BUNDLE_ID, + bundle_id_suffix: "development", profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name ) - + # Build and upload build_and_upload( api_key: api_key, - base_bundle_id: DEV_BUNDLE_ID, + bundle_id_suffix: "development", configuration: "Profile", distribute_external: false, profile_name_main: main_profile_name, profile_name_share: share_profile_name, - profile_name_widget: widget_profile_name, - group_id: DEV_GROUP_ID + profile_name_widget: widget_profile_name ) end @@ -197,7 +189,6 @@ end # Configure code signing for production bundle IDs configure_code_signing( - base_bundle_id: BASE_BUNDLE_ID, profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name @@ -206,7 +197,6 @@ end # Build and upload with version number build_and_upload( api_key: api_key, - base_bundle_id: BASE_BUNDLE_ID, version_number: get_version_from_pubspec, distribute_external: false, profile_name_main: main_profile_name, @@ -253,30 +243,30 @@ end desc "iOS Build Only (no TestFlight upload)" lane :gha_build_only do - # Use the same build process as the dev TestFlight lane, just skip the upload - # This ensures PR builds validate the same way as dev TestFlight builds - + # Use the same build process as production, just skip the upload + # This ensures PR builds validate the same way as production builds + api_key = get_api_key - + # Download and install provisioning profiles from App Store Connect # Certificate is imported by GHA workflow into build.keychain - sigh(api_key: api_key, app_identifier: DEV_BUNDLE_ID, force: true) + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development", force: true) main_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.ShareExtension", force: true) + + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.ShareExtension", force: true) share_profile_name = lane_context[SharedValues::SIGH_NAME] - - sigh(api_key: api_key, app_identifier: "#{DEV_BUNDLE_ID}.Widget", force: true) + + sigh(api_key: api_key, app_identifier: "#{BASE_BUNDLE_ID}.development.Widget", force: true) widget_profile_name = lane_context[SharedValues::SIGH_NAME] - + # Configure code signing for dev bundle IDs configure_code_signing( - base_bundle_id: DEV_BUNDLE_ID, + bundle_id_suffix: "development", profile_name_main: main_profile_name, profile_name_share: share_profile_name, profile_name_widget: widget_profile_name ) - + # Build the app (same as gha_testflight_dev but without upload) build_app( scheme: "Runner", @@ -284,12 +274,12 @@ end configuration: "Release", export_method: "app-store", skip_package_ipa: true, - xcargs: build_xcargs(group_id: DEV_GROUP_ID), + xcargs: "-skipMacroValidation CODE_SIGN_IDENTITY='#{CODE_SIGN_IDENTITY}' CODE_SIGN_STYLE=Manual", export_options: { provisioningProfiles: { - DEV_BUNDLE_ID => main_profile_name, - "#{DEV_BUNDLE_ID}.ShareExtension" => share_profile_name, - "#{DEV_BUNDLE_ID}.Widget" => widget_profile_name + "#{BASE_BUNDLE_ID}.development" => main_profile_name, + "#{BASE_BUNDLE_ID}.development.ShareExtension" => share_profile_name, + "#{BASE_BUNDLE_ID}.development.Widget" => widget_profile_name }, signingStyle: "manual", signingCertificate: CODE_SIGN_IDENTITY diff --git a/mobile/ios/scripts/xcode_flutter_build.sh b/mobile/ios/scripts/xcode_flutter_build.sh deleted file mode 100755 index 43977d3861..0000000000 --- a/mobile/ios/scripts/xcode_flutter_build.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Makes Flutter's builds through the Xcode GUI properly display errors and warnings -# in the Issue navigator -# -# Flutter's `xcode_backend.dart` runs `flutter assemble` with `allowFail: true`, -# which intentionally does not prefix output with `error:`. Unsure why they do this, -# but this script rebuilds the expected output so Xcode can parse and display the errors - -set -o pipefail - -# The Immich mobile root (containing the Dart `lib` directory). This is used to make -# absolute paths for Xcode linking -app_root="${FLUTTER_APPLICATION_PATH:-$SRCROOT/..}" - -/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build 2>&1 \ - | awk -v app_root="$app_root" ' - # Match Dart CFE diagnostics: .dart::: : - # Written for macOS/POSIX/BSD awk - { - # Always pass the original line through to preserve the original build log - print - - if ($0 ~ /^.*\.dart:[0-9]+:[0-9]+: (Error|Warning|Context|Info):/) { - # Locate the ": Kind:" separator to split location from message. - rest = $0 - if (match(rest, /: Error:/)) { kind = "Error"; keyword = "error" } - else if (match(rest, /: Warning:/)) { kind = "Warning"; keyword = "warning" } - else if (match(rest, /: Context:/)) { kind = "Context"; keyword = "note" } - else if (match(rest, /: Info:/)) { kind = "Info"; keyword = "note" } - - # location = everything before ": Kind:" (e.g. "lib/foo.dart:12:5") - location = substr(rest, 1, RSTART - 1) - # message = everything after ": Kind:" (leading space preserved) - message = substr(rest, RSTART + length(": " kind ":")) - - # Make the path absolute so Xcode links to it - if (location !~ /^\//) - location = app_root "/" location - - printf "%s: %s:%s\n", location, keyword, message - } - } - ' - -exit "${PIPESTATUS[0]}" diff --git a/mobile/ios/scripts/xcode_flutter_patch.sh b/mobile/ios/scripts/xcode_flutter_patch.sh deleted file mode 100755 index 95c92d0b49..0000000000 --- a/mobile/ios/scripts/xcode_flutter_patch.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env bash -# Makes Flutter not incorrectly append `-sdk` arguments to simulator builds -# This breaks macros that must build for the host's platform -# -# Flutter was informed of this (https://github.com/flutter/flutter/issues/146122) but has not fixed it in 2 years - -set -eu -sdk="${MISE_TOOL_INSTALL_PATH:-$(mise where aqua:flutter/flutter)}/flutter" -mac_dart="$sdk/packages/flutter_tools/lib/src/ios/mac.dart" - -pattern="buildCommands.addAll(['-sdk', XcodeSdk.IPhoneSimulator.platformName]);" - -# Filter out the sdk arg pattern -if awk -v pat="$pattern" 'index($0, pat) { found=1; next } { print } END { exit !found }' "$mac_dart" > "$mac_dart.tmp"; then - # If it was filtered out, apply it - mv "$mac_dart.tmp" "$mac_dart" - - # Force flutter itself to rebuild - rm -f "$sdk/bin/cache/flutter_tools.snapshot" "$sdk/bin/cache/flutter_tools.stamp" - - echo "flutter postinstall: removed simulator -sdk flag from xcodebuild invocation" -else - rm -f "$mac_dart.tmp" -fi diff --git a/mobile/lib/constants/aspect_ratios.dart b/mobile/lib/constants/aspect_ratios.dart deleted file mode 100644 index 49165e0913..0000000000 --- a/mobile/lib/constants/aspect_ratios.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'aspect_ratios.freezed.dart'; - -@freezed -abstract class CropAspectRatio with _$CropAspectRatio { - const CropAspectRatio._(); - - const factory CropAspectRatio({int? numerator, int? denominator, String? customLabel, IconData? icon}) = - _CropAspectRatio; - - static const free = CropAspectRatio(customLabel: "Free", icon: Icons.crop_free); - static const original = CropAspectRatio(customLabel: "Original", icon: Icons.crop_original); - - String get label { - return customLabel ?? (numerator != null && denominator != null ? '$numerator:$denominator' : 'Free'); - } - - bool get hasFlippedVariant => numerator != denominator; - double? get ratio => (numerator != null && denominator != null) ? numerator! / denominator! : null; - - CropAspectRatio get flipped { - return CropAspectRatio(numerator: denominator, denominator: numerator, customLabel: customLabel, icon: icon); - } -} - -const aspectRatioFree = CropAspectRatio.free; -const aspectRatioOriginal = CropAspectRatio.original; - -final aspectRatioPresets = [ - CropAspectRatio.free, - CropAspectRatio.original, - - const CropAspectRatio(numerator: 1, denominator: 1), - - // lanscape - const CropAspectRatio(numerator: 16, denominator: 9), - const CropAspectRatio(numerator: 3, denominator: 2), - const CropAspectRatio(numerator: 7, denominator: 5), - const CropAspectRatio(numerator: 4, denominator: 3), - - // portrait - const CropAspectRatio(numerator: 16, denominator: 9).flipped, - const CropAspectRatio(numerator: 3, denominator: 2).flipped, - const CropAspectRatio(numerator: 7, denominator: 5).flipped, - const CropAspectRatio(numerator: 4, denominator: 3).flipped, -]; diff --git a/mobile/lib/constants/colors.dart b/mobile/lib/constants/colors.dart index 655d2d9c09..e39480de32 100644 --- a/mobile/lib/constants/colors.dart +++ b/mobile/lib/constants/colors.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray } +const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo; +const String defaultColorPresetName = "indigo"; + const Color immichBrandColorLight = Color(0xFF4150AF); const Color immichBrandColorDark = Color(0xFFACCBFA); const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75); diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index c2a13c3ec2..9d28941b8f 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -1,5 +1,9 @@ import 'dart:io'; +const int noDbId = -9223372036854775808; // from Isar +const double downloadCompleted = -1; +const double downloadFailed = -2; + const String kMobileMetadataKey = "mobile-app"; // Number of log entries to retain on app start @@ -23,13 +27,14 @@ const String kBackupLivePhotoGroup = 'backup_live_photo_group'; const String kDownloadGroupImage = 'group_image'; const String kDownloadGroupVideo = 'group_video'; const String kDownloadGroupLivePhoto = 'group_livephoto'; -const String kShareDownloadGroup = 'group_share'; // Timeline constants +const int kTimelineNoneSegmentSize = 120; const int kTimelineAssetLoadBatchSize = 1024; const int kTimelineAssetLoadOppositeSize = 64; // Widget keys +const String appShareGroupId = "group.app.immich.share"; const String kWidgetAuthToken = "widget_auth_token"; const String kWidgetServerEndpoint = "widget_server_url"; const String kWidgetCustomHeaders = "widget_custom_headers"; @@ -42,6 +47,9 @@ const List<(String, String)> kWidgetNames = [ ('com.immich.widget.memory', 'app.alextran.immich.widget.MemoryReceiver'), ]; +const double kUploadStatusFailed = -1.0; +const double kUploadStatusCanceled = -2.0; + const int kMinMonthsToEnableScrubberSnap = 12; const String kImmichAppStoreLink = "https://apps.apple.com/app/immich/id1613945652"; diff --git a/mobile/lib/constants/enums.dart b/mobile/lib/constants/enums.dart index d3f85ceb2e..32ef9bbbed 100644 --- a/mobile/lib/constants/enums.dart +++ b/mobile/lib/constants/enums.dart @@ -9,29 +9,14 @@ enum SortOrder { enum TextSearchType { context, filename, description, ocr } -enum ActionSource { timeline, viewer } +enum AssetVisibilityEnum { timeline, hidden, archive, locked } -enum ShareAssetType { original, preview } +enum SortUserBy { id } + +enum ActionSource { timeline, viewer } enum CleanupStep { selectDate, scan, delete } enum AssetKeepType { none, photosOnly, videosOnly } enum AssetDateAggregation { start, end } - -enum SlideshowLook { contain, cover, blurredBackground } - -enum SlideshowDirection { forward, backward, shuffle } - -enum PartnerDirection { sharedBy, sharedWith } - -enum DevicePermission { photos, videos, storage, mediaLocation } - -enum DevicePermissionStatus { - denied, - granted, - limited, - permanentlyDenied; - - bool get hasAccess => this == granted || this == limited; -} diff --git a/mobile/lib/constants/locales.dart b/mobile/lib/constants/locales.dart index 429a3718da..f44aa5cc3e 100644 --- a/mobile/lib/constants/locales.dart +++ b/mobile/lib/constants/locales.dart @@ -5,23 +5,15 @@ const Map locales = { 'English (en)': Locale('en'), // Additional locales 'Arabic (ar)': Locale('ar'), - 'Basque (eu)': Locale('eu'), - 'Belarusian (be)': Locale('be'), - 'Bengali (bn)': Locale('bn'), - 'Brazilian Portuguese (pt_BR)': Locale('pt', 'BR'), 'Bulgarian (bg)': Locale('bg'), 'Catalan (ca)': Locale('ca'), - 'Cantonese (Traditional)': Locale.fromSubtags(languageCode: 'yue', scriptCode: 'Hant'), 'Chinese Simplified (zh_CN)': Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hans'), 'Chinese Traditional (zh_TW)': Locale.fromSubtags(languageCode: 'zh', scriptCode: 'Hant'), 'Croatian (hr)': Locale('hr'), 'Czech (cs)': Locale('cs'), 'Danish (da)': Locale('da'), 'Dutch (nl)': Locale('nl'), - 'English (United Kingdom) (en_GB)': Locale('en', 'GB'), 'Estonian (et)': Locale('et'), - 'Filipino (fil)': Locale('fil'), - 'Filipino (Tagalog) (tl)': Locale('tl'), 'Finnish (fi)': Locale('fi'), 'French (fr)': Locale('fr'), 'Galician (gl)': Locale('gl'), @@ -33,17 +25,13 @@ const Map locales = { 'Indonesian (id)': Locale('id'), 'Italian (it)': Locale('it'), 'Japanese (ja)': Locale('ja'), - 'Kabyle (kab)': Locale('kab'), - 'Khmer (Northern) (kxm)': Locale('kxm'), 'Korean (ko)': Locale('ko'), 'Latvian (lv)': Locale('lv'), 'Lithuanian (lt)': Locale('lt'), - 'Lombard (lmo)': Locale('lmo'), 'Mongolian (mn)': Locale('mn'), - 'Māori (mi)': Locale('mi'), - 'Nepali (ne)': Locale('ne'), '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'), @@ -52,8 +40,6 @@ const Map locales = { 'Slovak (sk)': Locale('sk'), 'Slovenian (sl)': Locale('sl'), 'Spanish (es)': Locale('es'), - 'Swabian (swg)': Locale('swg'), - 'Swahili (sw)': Locale('sw'), 'Swedish (sv)': Locale('sv'), 'Tamil (ta)': Locale('ta'), 'Telugu (te)': Locale('te'), diff --git a/mobile/lib/domain/interfaces/db.interface.dart b/mobile/lib/domain/interfaces/db.interface.dart new file mode 100644 index 0000000000..5645d15c47 --- /dev/null +++ b/mobile/lib/domain/interfaces/db.interface.dart @@ -0,0 +1,3 @@ +abstract interface class IDatabaseRepository { + Future transaction(Future Function() callback); +} diff --git a/mobile/lib/domain/models/album/album.model.dart b/mobile/lib/domain/models/album/album.model.dart index e63a3774e1..4558e1d5be 100644 --- a/mobile/lib/domain/models/album/album.model.dart +++ b/mobile/lib/domain/models/album/album.model.dart @@ -1,8 +1,3 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'album.model.freezed.dart'; - enum AlbumAssetOrder { // do not change this order! asc, @@ -13,24 +8,117 @@ enum AlbumUserRole { // do not change this order! editor, viewer, - owner, } // Model for an album stored in the server -@freezed -abstract class RemoteAlbum with _$RemoteAlbum { - const factory RemoteAlbum({ - required String id, - required String name, - required String ownerId, - required String description, - required DateTime createdAt, - required DateTime updatedAt, +class RemoteAlbum { + final String id; + final String name; + final String ownerId; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final AlbumAssetOrder order; + final int assetCount; + final String ownerName; + final bool isShared; + + const RemoteAlbum({ + required this.id, + required this.name, + required this.ownerId, + required this.description, + required this.createdAt, + required this.updatedAt, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + required this.assetCount, + required this.ownerName, + required this.isShared, + }); + + @override + String toString() { + return '''Album { + id: $id, + name: $name, + ownerId: $ownerId, + description: $description, + createdAt: $createdAt, + updatedAt: $updatedAt, + isActivityEnabled: $isActivityEnabled, + order: $order, + thumbnailAssetId: ${thumbnailAssetId ?? ""} + assetCount: $assetCount + ownerName: $ownerName + isShared: $isShared + }'''; + } + + @override + bool operator ==(Object other) { + if (other is! RemoteAlbum) return false; + if (identical(this, other)) return true; + return id == other.id && + name == other.name && + ownerId == other.ownerId && + description == other.description && + createdAt == other.createdAt && + updatedAt == other.updatedAt && + thumbnailAssetId == other.thumbnailAssetId && + isActivityEnabled == other.isActivityEnabled && + order == other.order && + assetCount == other.assetCount && + ownerName == other.ownerName && + isShared == other.isShared; + } + + @override + int get hashCode { + return id.hashCode ^ + name.hashCode ^ + ownerId.hashCode ^ + description.hashCode ^ + createdAt.hashCode ^ + updatedAt.hashCode ^ + thumbnailAssetId.hashCode ^ + isActivityEnabled.hashCode ^ + order.hashCode ^ + assetCount.hashCode ^ + ownerName.hashCode ^ + isShared.hashCode; + } + + RemoteAlbum copyWith({ + String? id, + String? name, + String? ownerId, + String? description, + DateTime? createdAt, + DateTime? updatedAt, String? thumbnailAssetId, - required bool isActivityEnabled, - required AlbumAssetOrder order, - required int assetCount, - required String ownerName, - required bool isShared, - }) = _RemoteAlbum; + bool? isActivityEnabled, + AlbumAssetOrder? order, + int? assetCount, + String? ownerName, + bool? isShared, + }) { + return RemoteAlbum( + id: id ?? this.id, + name: name ?? this.name, + ownerId: ownerId ?? this.ownerId, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + assetCount: assetCount ?? this.assetCount, + ownerName: ownerName ?? this.ownerName, + isShared: isShared ?? this.isShared, + ); + } } diff --git a/mobile/lib/domain/models/album/local_album.model.dart b/mobile/lib/domain/models/album/local_album.model.dart index cddc1d1953..ea06118aa1 100644 --- a/mobile/lib/domain/models/album/local_album.model.dart +++ b/mobile/lib/domain/models/album/local_album.model.dart @@ -1,8 +1,3 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'local_album.model.freezed.dart'; - enum BackupSelection { // Used to sort albums based on the backupSelection // selected -> none -> excluded @@ -12,15 +7,81 @@ enum BackupSelection { excluded, } -@freezed -abstract class LocalAlbum with _$LocalAlbum { - const factory LocalAlbum({ - required String id, - required String name, - required DateTime updatedAt, - @Default(0) int assetCount, - @Default(BackupSelection.none) BackupSelection backupSelection, - @Default(false) bool isIosSharedAlbum, +class LocalAlbum { + final String id; + final String name; + final DateTime updatedAt; + final bool isIosSharedAlbum; + + final int assetCount; + final BackupSelection backupSelection; + final String? linkedRemoteAlbumId; + + const LocalAlbum({ + required this.id, + required this.name, + required this.updatedAt, + this.assetCount = 0, + this.backupSelection = BackupSelection.none, + this.isIosSharedAlbum = false, + this.linkedRemoteAlbumId, + }); + + LocalAlbum copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? assetCount, + BackupSelection? backupSelection, + bool? isIosSharedAlbum, String? linkedRemoteAlbumId, - }) = _LocalAlbum; + }) { + return LocalAlbum( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + assetCount: assetCount ?? this.assetCount, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + ); + } + + @override + bool operator ==(Object other) { + if (other is! LocalAlbum) return false; + if (identical(this, other)) return true; + + return other.id == id && + other.name == name && + other.updatedAt == updatedAt && + other.assetCount == assetCount && + other.backupSelection == backupSelection && + other.isIosSharedAlbum == isIosSharedAlbum && + other.linkedRemoteAlbumId == linkedRemoteAlbumId; + } + + @override + int get hashCode { + return id.hashCode ^ + name.hashCode ^ + updatedAt.hashCode ^ + assetCount.hashCode ^ + backupSelection.hashCode ^ + isIosSharedAlbum.hashCode ^ + linkedRemoteAlbumId.hashCode; + } + + @override + String toString() { + return '''LocalAlbum: { +id: $id, +name: $name, +updatedAt: $updatedAt, +assetCount: $assetCount, +backupSelection: $backupSelection, +isIosSharedAlbum: $isIosSharedAlbum +linkedRemoteAlbumId: $linkedRemoteAlbumId, +}'''; + } } diff --git a/mobile/lib/domain/models/asset/base_asset.model.dart b/mobile/lib/domain/models/asset/base_asset.model.dart index ac89fe9a0a..cb40c8f76a 100644 --- a/mobile/lib/domain/models/asset/base_asset.model.dart +++ b/mobile/lib/domain/models/asset/base_asset.model.dart @@ -1,5 +1,3 @@ -import 'package:immich_mobile/domain/models/exif.model.dart'; - part 'local_asset.model.dart'; part 'remote_asset.model.dart'; @@ -25,8 +23,9 @@ sealed class BaseAsset { final DateTime updatedAt; final int? width; final int? height; - final int? durationMs; + final int? durationInSeconds; final bool isFavorite; + final String? livePhotoVideoId; final bool isEdited; const BaseAsset({ @@ -37,51 +36,44 @@ sealed class BaseAsset { required this.updatedAt, this.width, this.height, - this.durationMs, + this.durationInSeconds, this.isFavorite = false, + this.livePhotoVideoId, required this.isEdited, }); bool get isImage => type == AssetType.image; bool get isVideo => type == AssetType.video; - bool get isMotionPhoto => playbackStyle == AssetPlaybackStyle.livePhoto; + bool get isMotionPhoto => livePhotoVideoId != null; bool get isAnimatedImage => playbackStyle == AssetPlaybackStyle.imageAnimated; + AssetPlaybackStyle get playbackStyle { + if (isVideo) return AssetPlaybackStyle.video; + if (isMotionPhoto) return AssetPlaybackStyle.livePhoto; + if (isImage && durationInSeconds != null && durationInSeconds! > 0) return AssetPlaybackStyle.imageAnimated; + if (isImage) return AssetPlaybackStyle.image; + return AssetPlaybackStyle.unknown; + } + Duration get duration { - final durationMs = this.durationMs; - if (durationMs != null) { - return Duration(milliseconds: durationMs); + final durationInSeconds = this.durationInSeconds; + if (durationInSeconds != null) { + return Duration(seconds: durationInSeconds); } - return Duration.zero; + return const Duration(); } bool get hasRemote => storage == AssetState.remote || storage == AssetState.merged; bool get hasLocal => storage == AssetState.local || storage == AssetState.merged; bool get isLocalOnly => storage == AssetState.local; bool get isRemoteOnly => storage == AssetState.remote; - bool get isMerged => storage == .merged; - - // Same asset even if localId is known on one side but not the other (heroTag isn't stable then) - bool refersToSameAsset(BaseAsset other) { - if (remoteId != null && other.remoteId != null) { - return remoteId == other.remoteId; - } - if (localId != null && other.localId != null) { - return localId == other.localId; - } - return checksum != null && checksum == other.checksum; - } - - bool get isEditable => false; // Overridden in subclasses AssetState get storage; - String get id; String? get localId; String? get remoteId; String get heroTag; - AssetPlaybackStyle get playbackStyle; @override String toString() { @@ -92,7 +84,7 @@ sealed class BaseAsset { updatedAt: $updatedAt, width: ${width ?? ""}, height: ${height ?? ""}, - durationMs: ${durationMs ?? ""}, + durationInSeconds: ${durationInSeconds ?? ""}, isFavorite: $isFavorite, isEdited: $isEdited, }'''; @@ -100,9 +92,7 @@ sealed class BaseAsset { @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; if (other is BaseAsset) { return name == other.name && type == other.type && @@ -110,7 +100,7 @@ sealed class BaseAsset { updatedAt == other.updatedAt && width == other.width && height == other.height && - durationMs == other.durationMs && + durationInSeconds == other.durationInSeconds && isFavorite == other.isFavorite && isEdited == other.isEdited; } @@ -125,7 +115,7 @@ sealed class BaseAsset { updatedAt.hashCode ^ width.hashCode ^ height.hashCode ^ - durationMs.hashCode ^ + durationInSeconds.hashCode ^ isFavorite.hashCode ^ isEdited.hashCode; } diff --git a/mobile/lib/domain/models/asset/local_asset.model.dart b/mobile/lib/domain/models/asset/local_asset.model.dart index 15039761a8..6766f4c3a2 100644 --- a/mobile/lib/domain/models/asset/local_asset.model.dart +++ b/mobile/lib/domain/models/asset/local_asset.model.dart @@ -1,7 +1,6 @@ part of 'base_asset.model.dart'; class LocalAsset extends BaseAsset { - @override final String id; final String? remoteAssetId; final String? cloudId; @@ -24,8 +23,9 @@ class LocalAsset extends BaseAsset { required super.updatedAt, super.width, super.height, - super.durationMs, + super.durationInSeconds, super.isFavorite = false, + super.livePhotoVideoId, this.orientation = 0, required this.playbackStyle, this.adjustmentTime, @@ -58,7 +58,7 @@ class LocalAsset extends BaseAsset { updatedAt: $updatedAt, width: ${width ?? ""}, height: ${height ?? ""}, - durationMs: ${durationMs ?? ""}, + durationInSeconds: ${durationInSeconds ?? ""}, playbackStyle: $playbackStyle, remoteId: ${remoteId ?? ""}, cloudId: ${cloudId ?? ""}, @@ -74,12 +74,8 @@ class LocalAsset extends BaseAsset { // Not checking for remoteId here @override bool operator ==(Object other) { - if (other is! LocalAsset) { - return false; - } - if (identical(this, other)) { - return true; - } + if (other is! LocalAsset) return false; + if (identical(this, other)) return true; return super == other && id == other.id && cloudId == other.cloudId && @@ -112,7 +108,7 @@ class LocalAsset extends BaseAsset { DateTime? updatedAt, int? width, int? height, - int? durationMs, + int? durationInSeconds, bool? isFavorite, int? orientation, AssetPlaybackStyle? playbackStyle, @@ -132,7 +128,7 @@ class LocalAsset extends BaseAsset { updatedAt: updatedAt ?? this.updatedAt, width: width ?? this.width, height: height ?? this.height, - durationMs: durationMs ?? this.durationMs, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, isFavorite: isFavorite ?? this.isFavorite, orientation: orientation ?? this.orientation, playbackStyle: playbackStyle ?? this.playbackStyle, diff --git a/mobile/lib/domain/models/asset/remote_asset.model.dart b/mobile/lib/domain/models/asset/remote_asset.model.dart index 5707329ba3..43d49506e3 100644 --- a/mobile/lib/domain/models/asset/remote_asset.model.dart +++ b/mobile/lib/domain/models/asset/remote_asset.model.dart @@ -4,16 +4,12 @@ enum AssetVisibility { timeline, hidden, archive, locked } // Model for an asset stored in the server class RemoteAsset extends BaseAsset { - @override final String id; final String? localAssetId; final String? thumbHash; final AssetVisibility visibility; final String ownerId; final String? stackId; - final String? livePhotoVideoId; - final DateTime? uploadedAt; - final DateTime? deletedAt; const RemoteAsset({ required this.id, @@ -24,39 +20,20 @@ class RemoteAsset extends BaseAsset { required super.type, required super.createdAt, required super.updatedAt, - this.uploadedAt, super.width, super.height, - super.durationMs, + super.durationInSeconds, super.isFavorite = false, this.thumbHash, this.visibility = AssetVisibility.timeline, - this.livePhotoVideoId, + super.livePhotoVideoId, this.stackId, required super.isEdited, - this.deletedAt, }) : localAssetId = localId; @override String? get localId => localAssetId; - @override - AssetPlaybackStyle get playbackStyle { - if (isVideo) { - return AssetPlaybackStyle.video; - } - if (livePhotoVideoId != null) { - return AssetPlaybackStyle.livePhoto; - } - if (isImage && durationMs != null && durationMs! > 0) { - return AssetPlaybackStyle.imageAnimated; - } - if (isImage) { - return AssetPlaybackStyle.image; - } - return AssetPlaybackStyle.unknown; - } - @override String? get remoteId => id; @@ -66,17 +43,6 @@ class RemoteAsset extends BaseAsset { @override String get heroTag => '${localId ?? checksum}_$id'; - @override - bool get isEditable => isImage && !isMotionPhoto && !isAnimatedImage; - - bool get isTrashed => deletedAt != null; - - bool get isStacked => stackId != null; - - bool get isArchived => visibility == .archive; - - bool get isLocked => visibility == .locked; - @override String toString() { return '''Asset { @@ -86,10 +52,9 @@ class RemoteAsset extends BaseAsset { type: $type, createdAt: $createdAt, updatedAt: $updatedAt, - uploadedAt: ${uploadedAt ?? ""}, width: ${width ?? ""}, height: ${height ?? ""}, - durationMs: ${durationMs ?? ""}, + durationInSeconds: ${durationInSeconds ?? ""}, localId: ${localId ?? ""}, isFavorite: $isFavorite, thumbHash: ${thumbHash ?? ""}, @@ -103,21 +68,14 @@ class RemoteAsset extends BaseAsset { // Not checking for localId here @override bool operator ==(Object other) { - if (other is! RemoteAsset) { - return false; - } - if (identical(this, other)) { - return true; - } + if (other is! RemoteAsset) return false; + if (identical(this, other)) return true; return super == other && id == other.id && ownerId == other.ownerId && thumbHash == other.thumbHash && visibility == other.visibility && - stackId == other.stackId && - livePhotoVideoId == other.livePhotoVideoId && - uploadedAt == other.uploadedAt && - deletedAt == other.deletedAt; + stackId == other.stackId; } @override @@ -128,10 +86,7 @@ class RemoteAsset extends BaseAsset { localId.hashCode ^ thumbHash.hashCode ^ visibility.hashCode ^ - stackId.hashCode ^ - livePhotoVideoId.hashCode ^ - uploadedAt.hashCode ^ - deletedAt.hashCode; + stackId.hashCode; RemoteAsset copyWith({ String? id, @@ -142,17 +97,15 @@ class RemoteAsset extends BaseAsset { AssetType? type, DateTime? createdAt, DateTime? updatedAt, - DateTime? uploadedAt, int? width, int? height, - int? durationMs, + int? durationInSeconds, bool? isFavorite, String? thumbHash, AssetVisibility? visibility, String? livePhotoVideoId, String? stackId, bool? isEdited, - DateTime? deletedAt, }) { return RemoteAsset( id: id ?? this.id, @@ -163,105 +116,15 @@ class RemoteAsset extends BaseAsset { type: type ?? this.type, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, - uploadedAt: uploadedAt ?? this.uploadedAt, width: width ?? this.width, height: height ?? this.height, - durationMs: durationMs ?? this.durationMs, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, isFavorite: isFavorite ?? this.isFavorite, thumbHash: thumbHash ?? this.thumbHash, visibility: visibility ?? this.visibility, livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, stackId: stackId ?? this.stackId, isEdited: isEdited ?? this.isEdited, - deletedAt: deletedAt ?? this.deletedAt, - ); - } -} - -class RemoteAssetExif extends RemoteAsset { - final ExifInfo exifInfo; - - const RemoteAssetExif({ - required super.id, - super.localId, - required super.name, - required super.ownerId, - required super.checksum, - required super.type, - required super.createdAt, - required super.updatedAt, - super.uploadedAt, - super.deletedAt, - super.width, - super.height, - super.durationMs, - super.isFavorite = false, - super.thumbHash, - super.visibility = AssetVisibility.timeline, - super.livePhotoVideoId, - super.stackId, - super.isEdited = false, - this.exifInfo = const ExifInfo(), - }); - - @override - bool operator ==(Object other) { - if (other is! RemoteAssetExif) { - return false; - } - if (identical(this, other)) { - return true; - } - return super == other && exifInfo == other.exifInfo; - } - - @override - int get hashCode => super.hashCode ^ exifInfo.hashCode; - - @override - RemoteAssetExif copyWith({ - String? id, - String? localId, - String? name, - String? ownerId, - String? checksum, - AssetType? type, - DateTime? createdAt, - DateTime? updatedAt, - DateTime? uploadedAt, - DateTime? deletedAt, - int? width, - int? height, - int? durationMs, - bool? isFavorite, - String? thumbHash, - AssetVisibility? visibility, - String? livePhotoVideoId, - String? stackId, - bool? isEdited, - ExifInfo? exifInfo, - }) { - return RemoteAssetExif( - id: id ?? this.id, - localId: localId ?? this.localId, - name: name ?? this.name, - ownerId: ownerId ?? this.ownerId, - checksum: checksum ?? this.checksum, - type: type ?? this.type, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, - uploadedAt: uploadedAt ?? this.uploadedAt, - deletedAt: deletedAt ?? this.deletedAt, - width: width ?? this.width, - height: height ?? this.height, - durationMs: durationMs ?? this.durationMs, - isFavorite: isFavorite ?? this.isFavorite, - thumbHash: thumbHash ?? this.thumbHash, - visibility: visibility ?? this.visibility, - livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, - stackId: stackId ?? this.stackId, - isEdited: isEdited ?? this.isEdited, - exifInfo: exifInfo ?? this.exifInfo, // Use the new parameter ); } } diff --git a/mobile/lib/domain/models/asset_edit.model.dart b/mobile/lib/domain/models/asset_edit.model.dart index c7a09dbae3..b3266dba46 100644 --- a/mobile/lib/domain/models/asset_edit.model.dart +++ b/mobile/lib/domain/models/asset_edit.model.dart @@ -1,25 +1,21 @@ -import "package:openapi/api.dart" show CropParameters, MirrorParameters, RotateParameters; +import "package:openapi/api.dart" as api show AssetEditAction; enum AssetEditAction { rotate, crop, mirror, other } -sealed class AssetEdit { - const AssetEdit(); +extension AssetEditActionExtension on AssetEditAction { + api.AssetEditAction? toDto() { + return switch (this) { + AssetEditAction.rotate => api.AssetEditAction.rotate, + AssetEditAction.crop => api.AssetEditAction.crop, + AssetEditAction.mirror => api.AssetEditAction.mirror, + AssetEditAction.other => null, + }; + } } -class CropEdit extends AssetEdit { - final CropParameters parameters; +class AssetEdit { + final AssetEditAction action; + final Map parameters; - const CropEdit(this.parameters); -} - -class RotateEdit extends AssetEdit { - final RotateParameters parameters; - - const RotateEdit(this.parameters); -} - -class MirrorEdit extends AssetEdit { - final MirrorParameters parameters; - - const MirrorEdit(this.parameters); + const AssetEdit({required this.action, required this.parameters}); } diff --git a/mobile/lib/domain/models/asset_face.model.dart b/mobile/lib/domain/models/asset_face.model.dart index c65f159fe7..f432b923e3 100644 --- a/mobile/lib/domain/models/asset_face.model.dart +++ b/mobile/lib/domain/models/asset_face.model.dart @@ -1,21 +1,98 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'asset_face.model.freezed.dart'; - // Model for an asset face stored in the server -@freezed -abstract class AssetFace with _$AssetFace { - const factory AssetFace({ - required String id, - required String assetId, +class AssetFace { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + + const AssetFace({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + + AssetFace copyWith({ + String? id, + String? assetId, String? personId, - required int imageWidth, - required int imageHeight, - required int boundingBoxX1, - required int boundingBoxY1, - required int boundingBoxX2, - required int boundingBoxY2, - required String sourceType, - }) = _AssetFace; + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) { + return AssetFace( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + String toString() { + return '''AssetFace { + id: $id, + assetId: $assetId, + personId: ${personId ?? ""}, + imageWidth: $imageWidth, + imageHeight: $imageHeight, + boundingBoxX1: $boundingBoxX1, + boundingBoxY1: $boundingBoxY1, + boundingBoxX2: $boundingBoxX2, + boundingBoxY2: $boundingBoxY2, + sourceType: $sourceType, +}'''; + } + + @override + bool operator ==(covariant AssetFace other) { + if (identical(this, other)) return true; + + return other.id == id && + other.assetId == assetId && + other.personId == personId && + other.imageWidth == imageWidth && + other.imageHeight == imageHeight && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY2 == boundingBoxY2 && + other.sourceType == sourceType; + } + + @override + int get hashCode { + return id.hashCode ^ + assetId.hashCode ^ + personId.hashCode ^ + imageWidth.hashCode ^ + imageHeight.hashCode ^ + boundingBoxX1.hashCode ^ + boundingBoxY1.hashCode ^ + boundingBoxX2.hashCode ^ + boundingBoxY2.hashCode ^ + sourceType.hashCode; + } } diff --git a/mobile/lib/domain/models/config/album_config.dart b/mobile/lib/domain/models/config/album_config.dart deleted file mode 100644 index 16deca1b8f..0000000000 --- a/mobile/lib/domain/models/config/album_config.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; - -part 'album_config.freezed.dart'; - -@freezed -abstract class AlbumConfig with _$AlbumConfig { - const factory AlbumConfig({ - @Default(AlbumSortMode.mostRecent) AlbumSortMode sortMode, - @Default(true) bool isReverse, - @Default(false) bool isGrid, - }) = _AlbumConfig; -} diff --git a/mobile/lib/domain/models/config/app_config.dart b/mobile/lib/domain/models/config/app_config.dart deleted file mode 100644 index bc089e621e..0000000000 --- a/mobile/lib/domain/models/config/app_config.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/constants/colors.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/config/album_config.dart'; -import 'package:immich_mobile/domain/models/config/backup_config.dart'; -import 'package:immich_mobile/domain/models/config/cleanup_config.dart'; -import 'package:immich_mobile/domain/models/config/feature_message_config.dart'; -import 'package:immich_mobile/domain/models/config/image_config.dart'; -import 'package:immich_mobile/domain/models/config/map_config.dart'; -import 'package:immich_mobile/domain/models/config/network_config.dart'; -import 'package:immich_mobile/domain/models/config/share_config.dart'; -import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; -import 'package:immich_mobile/domain/models/config/theme_config.dart'; -import 'package:immich_mobile/domain/models/config/timeline_config.dart'; -import 'package:immich_mobile/domain/models/config/viewer_config.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/utils/semver.dart'; - -part 'app_config.freezed.dart'; - -const defaultConfig = AppConfig(); - -@freezed -abstract class AppConfig with _$AppConfig { - const AppConfig._(); - - const factory AppConfig({ - @Default(LogLevel.info) LogLevel logLevel, - @Default(ThemeConfig()) ThemeConfig theme, - @Default(CleanupConfig()) CleanupConfig cleanup, - @Default(MapConfig()) MapConfig map, - @Default(TimelineConfig()) TimelineConfig timeline, - @Default(ImageConfig()) ImageConfig image, - @Default(ViewerConfig()) ViewerConfig viewer, - @Default(SlideshowConfig()) SlideshowConfig slideshow, - @Default(AlbumConfig()) AlbumConfig album, - @Default(BackupConfig()) BackupConfig backup, - @Default(NetworkConfig()) NetworkConfig network, - @Default(ShareConfig()) ShareConfig share, - @Default(FeatureMessageConfig()) FeatureMessageConfig featureMessage, - }) = _AppConfig; - - T read(SettingsKey key) => - (switch (key) { - .logLevel => logLevel, - .themePrimaryColor => theme.primaryColor, - .themeMode => theme.mode, - .themeDynamic => theme.dynamicTheme, - .themeColorfulInterface => theme.colorfulInterface, - .imagePreferRemote => image.preferRemote, - .imageLoadOriginal => image.loadOriginal, - .viewerLoopVideo => viewer.loopVideo, - .viewerLoadOriginalVideo => viewer.loadOriginalVideo, - .viewerAutoPlayVideo => viewer.autoPlayVideo, - .viewerTapToNavigate => viewer.tapToNavigate, - .networkAutoEndpointSwitching => network.autoEndpointSwitching, - .networkPreferredWifiName => network.preferredWifiName, - .networkLocalEndpoint => network.localEndpoint, - .networkExternalEndpointList => network.externalEndpointList, - .networkCustomHeaders => network.customHeaders, - .albumSortMode => album.sortMode, - .albumIsReverse => album.isReverse, - .albumIsGrid => album.isGrid, - .backupEnabled => backup.enabled, - .backupUseCellularForVideos => backup.useCellularForVideos, - .backupUseCellularForPhotos => backup.useCellularForPhotos, - .backupRequireCharging => backup.requireCharging, - .backupTriggerDelay => backup.triggerDelay, - .backupSyncAlbums => backup.syncAlbums, - .timelineTilesPerRow => timeline.tilesPerRow, - .timelineGroupAssetsBy => timeline.groupAssetsBy, - .timelineStorageIndicator => timeline.storageIndicator, - .mapShowFavoriteOnly => map.favoritesOnly, - .mapRelativeDate => map.relativeDays, - .mapCustomFrom => map.customFrom, - .mapCustomTo => map.customTo, - .mapIncludeArchived => map.includeArchived, - .mapThemeMode => map.themeMode, - .mapWithPartners => map.withPartners, - .cleanupKeepFavorites => cleanup.keepFavorites, - .cleanupKeepMediaType => cleanup.keepMediaType, - .cleanupKeepAlbumIds => cleanup.keepAlbumIds, - .cleanupCutoffDaysAgo => cleanup.cutoffDaysAgo, - .cleanupDefaultsInitialized => cleanup.defaultsInitialized, - .shareFileType => share.fileType, - .slideshowRepeat => slideshow.repeat, - .slideshowDuration => slideshow.duration, - .slideshowLook => slideshow.look, - .slideshowDirection => slideshow.direction, - .featureMessageSeenRelease => featureMessage.seenRelease, - }) - as T; - - factory AppConfig.fromEntries(Map overrides) => - overrides.entries.fold(const AppConfig(), (config, entry) => config.write(entry.key, entry.value)); - - AppConfig write(SettingsKey key, U value) { - return switch (key) { - .logLevel => copyWith(logLevel: value as LogLevel), - .themePrimaryColor => copyWith(theme: theme.copyWith(primaryColor: value as ImmichColorPreset)), - .themeMode => copyWith(theme: theme.copyWith(mode: value as ThemeMode)), - .themeDynamic => copyWith(theme: theme.copyWith(dynamicTheme: value as bool)), - .themeColorfulInterface => copyWith(theme: theme.copyWith(colorfulInterface: value as bool)), - .imagePreferRemote => copyWith(image: image.copyWith(preferRemote: value as bool)), - .imageLoadOriginal => copyWith(image: image.copyWith(loadOriginal: value as bool)), - .viewerLoopVideo => copyWith(viewer: viewer.copyWith(loopVideo: value as bool)), - .viewerLoadOriginalVideo => copyWith(viewer: viewer.copyWith(loadOriginalVideo: value as bool)), - .viewerAutoPlayVideo => copyWith(viewer: viewer.copyWith(autoPlayVideo: value as bool)), - .viewerTapToNavigate => copyWith(viewer: viewer.copyWith(tapToNavigate: value as bool)), - .networkAutoEndpointSwitching => copyWith(network: network.copyWith(autoEndpointSwitching: value as bool)), - .networkPreferredWifiName => copyWith( - network: network.copyWith(preferredWifiName: .fromNullable(value as String?)), - ), - .networkLocalEndpoint => copyWith(network: network.copyWith(localEndpoint: .fromNullable(value as String?))), - .networkExternalEndpointList => copyWith(network: network.copyWith(externalEndpointList: value as List)), - .networkCustomHeaders => copyWith(network: network.copyWith(customHeaders: value as Map)), - .albumSortMode => copyWith(album: album.copyWith(sortMode: value as AlbumSortMode)), - .albumIsReverse => copyWith(album: album.copyWith(isReverse: value as bool)), - .albumIsGrid => copyWith(album: album.copyWith(isGrid: value as bool)), - .backupEnabled => copyWith(backup: backup.copyWith(enabled: value as bool)), - .backupUseCellularForVideos => copyWith(backup: backup.copyWith(useCellularForVideos: value as bool)), - .backupUseCellularForPhotos => copyWith(backup: backup.copyWith(useCellularForPhotos: value as bool)), - .backupRequireCharging => copyWith(backup: backup.copyWith(requireCharging: value as bool)), - .backupTriggerDelay => copyWith(backup: backup.copyWith(triggerDelay: value as int)), - .backupSyncAlbums => copyWith(backup: backup.copyWith(syncAlbums: value as bool)), - .timelineTilesPerRow => copyWith(timeline: timeline.copyWith(tilesPerRow: value as int)), - .timelineGroupAssetsBy => copyWith(timeline: timeline.copyWith(groupAssetsBy: value as GroupAssetsBy)), - .timelineStorageIndicator => copyWith(timeline: timeline.copyWith(storageIndicator: value as bool)), - .mapShowFavoriteOnly => copyWith(map: map.copyWith(favoritesOnly: value as bool)), - .mapRelativeDate => copyWith(map: map.copyWith(relativeDays: value as int)), - .mapCustomFrom => copyWith(map: map.copyWith(customFrom: .fromNullable(value as DateTime?))), - .mapCustomTo => copyWith(map: map.copyWith(customTo: .fromNullable(value as DateTime?))), - .mapIncludeArchived => copyWith(map: map.copyWith(includeArchived: value as bool)), - .mapThemeMode => copyWith(map: map.copyWith(themeMode: value as ThemeMode)), - .mapWithPartners => copyWith(map: map.copyWith(withPartners: value as bool)), - .cleanupKeepFavorites => copyWith(cleanup: cleanup.copyWith(keepFavorites: value as bool)), - .cleanupKeepMediaType => copyWith(cleanup: cleanup.copyWith(keepMediaType: value as AssetKeepType)), - .cleanupKeepAlbumIds => copyWith(cleanup: cleanup.copyWith(keepAlbumIds: value as List)), - .cleanupCutoffDaysAgo => copyWith(cleanup: cleanup.copyWith(cutoffDaysAgo: value as int)), - .cleanupDefaultsInitialized => copyWith(cleanup: cleanup.copyWith(defaultsInitialized: value as bool)), - .shareFileType => copyWith(share: share.copyWith(fileType: value as ShareAssetType)), - .slideshowRepeat => copyWith(slideshow: slideshow.copyWith(repeat: value as bool)), - .slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)), - .slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)), - .slideshowDirection => copyWith(slideshow: slideshow.copyWith(direction: value as SlideshowDirection)), - .featureMessageSeenRelease => copyWith(featureMessage: featureMessage.copyWith(seenRelease: value as SemVer)), - }; - } -} diff --git a/mobile/lib/domain/models/config/backup_config.dart b/mobile/lib/domain/models/config/backup_config.dart deleted file mode 100644 index 4177408fcc..0000000000 --- a/mobile/lib/domain/models/config/backup_config.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'backup_config.freezed.dart'; - -@freezed -abstract class BackupConfig with _$BackupConfig { - const factory BackupConfig({ - @Default(false) bool enabled, - @Default(false) bool useCellularForVideos, - @Default(false) bool useCellularForPhotos, - @Default(false) bool requireCharging, - @Default(30) int triggerDelay, - @Default(false) bool syncAlbums, - }) = _BackupConfig; -} diff --git a/mobile/lib/domain/models/config/cleanup_config.dart b/mobile/lib/domain/models/config/cleanup_config.dart deleted file mode 100644 index e87494e9a7..0000000000 --- a/mobile/lib/domain/models/config/cleanup_config.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/constants/enums.dart'; - -part 'cleanup_config.freezed.dart'; - -@freezed -abstract class CleanupConfig with _$CleanupConfig { - const factory CleanupConfig({ - @Default(true) bool keepFavorites, - @Default(AssetKeepType.none) AssetKeepType keepMediaType, - @Default([]) List keepAlbumIds, - @Default(-1) int cutoffDaysAgo, - @Default(false) bool defaultsInitialized, - }) = _CleanupConfig; -} diff --git a/mobile/lib/domain/models/config/feature_message_config.dart b/mobile/lib/domain/models/config/feature_message_config.dart deleted file mode 100644 index 0fc13fd51f..0000000000 --- a/mobile/lib/domain/models/config/feature_message_config.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/utils/semver.dart'; - -part 'feature_message_config.freezed.dart'; - -@freezed -abstract class FeatureMessageConfig with _$FeatureMessageConfig { - const factory FeatureMessageConfig({@Default(SemVer(major: 0, minor: 0, patch: 0)) SemVer seenRelease}) = - _FeatureMessageConfig; -} diff --git a/mobile/lib/domain/models/config/image_config.dart b/mobile/lib/domain/models/config/image_config.dart deleted file mode 100644 index a51193d1d3..0000000000 --- a/mobile/lib/domain/models/config/image_config.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'image_config.freezed.dart'; - -@freezed -abstract class ImageConfig with _$ImageConfig { - const factory ImageConfig({@Default(false) bool preferRemote, @Default(false) bool loadOriginal}) = _ImageConfig; -} diff --git a/mobile/lib/domain/models/config/map_config.dart b/mobile/lib/domain/models/config/map_config.dart deleted file mode 100644 index bf82b88933..0000000000 --- a/mobile/lib/domain/models/config/map_config.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/utils/option.dart'; - -part 'map_config.freezed.dart'; - -@Freezed(copyWith: false) -abstract class MapConfig with _$MapConfig { - const MapConfig._(); - - const factory MapConfig({ - @Default(0) int relativeDays, - @Default(false) bool favoritesOnly, - @Default(false) bool includeArchived, - @Default(ThemeMode.system) ThemeMode themeMode, - @Default(false) bool withPartners, - DateTime? customFrom, - DateTime? customTo, - }) = _MapConfig; - - // We patch `customFrom` and `customTo`, which prevents us from using Freezed `copyWith` - MapConfig copyWith({ - int? relativeDays, - bool? favoritesOnly, - bool? includeArchived, - ThemeMode? themeMode, - bool? withPartners, - Option? customFrom, - Option? customTo, - }) => MapConfig( - relativeDays: relativeDays ?? this.relativeDays, - favoritesOnly: favoritesOnly ?? this.favoritesOnly, - includeArchived: includeArchived ?? this.includeArchived, - themeMode: themeMode ?? this.themeMode, - withPartners: withPartners ?? this.withPartners, - customFrom: customFrom.patch(this.customFrom), - customTo: customTo.patch(this.customTo), - ); -} diff --git a/mobile/lib/domain/models/config/network_config.dart b/mobile/lib/domain/models/config/network_config.dart deleted file mode 100644 index 97e0167a94..0000000000 --- a/mobile/lib/domain/models/config/network_config.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/utils/option.dart'; - -part 'network_config.freezed.dart'; - -@Freezed(copyWith: false) -abstract class NetworkConfig with _$NetworkConfig { - const NetworkConfig._(); - - const factory NetworkConfig({ - @Default(false) bool autoEndpointSwitching, - String? preferredWifiName, - String? localEndpoint, - @Default([]) List externalEndpointList, - @Default({}) Map customHeaders, - }) = _NetworkConfig; - - // We patch `preferredWifiName` and `localEndpoint`, which prevents us from using Freezed `copyWith` - NetworkConfig copyWith({ - bool? autoEndpointSwitching, - Option? preferredWifiName, - Option? localEndpoint, - List? externalEndpointList, - Map? customHeaders, - }) => NetworkConfig( - autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching, - preferredWifiName: preferredWifiName.patch(this.preferredWifiName), - localEndpoint: localEndpoint.patch(this.localEndpoint), - externalEndpointList: externalEndpointList ?? this.externalEndpointList, - customHeaders: customHeaders ?? this.customHeaders, - ); -} diff --git a/mobile/lib/domain/models/config/share_config.dart b/mobile/lib/domain/models/config/share_config.dart deleted file mode 100644 index 49530aca30..0000000000 --- a/mobile/lib/domain/models/config/share_config.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/constants/enums.dart'; - -part 'share_config.freezed.dart'; - -@freezed -abstract class ShareConfig with _$ShareConfig { - const factory ShareConfig({@Default(ShareAssetType.original) ShareAssetType fileType}) = _ShareConfig; -} diff --git a/mobile/lib/domain/models/config/slideshow_config.dart b/mobile/lib/domain/models/config/slideshow_config.dart deleted file mode 100644 index cacba1a89e..0000000000 --- a/mobile/lib/domain/models/config/slideshow_config.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/constants/enums.dart'; - -part 'slideshow_config.freezed.dart'; - -@freezed -abstract class SlideshowConfig with _$SlideshowConfig { - const factory SlideshowConfig({ - @Default(true) bool repeat, - @Default(5) int duration, - @Default(SlideshowLook.blurredBackground) SlideshowLook look, - @Default(SlideshowDirection.forward) SlideshowDirection direction, - }) = _SlideshowConfig; -} diff --git a/mobile/lib/domain/models/config/theme_config.dart b/mobile/lib/domain/models/config/theme_config.dart deleted file mode 100644 index ac651a8062..0000000000 --- a/mobile/lib/domain/models/config/theme_config.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/constants/colors.dart'; - -part 'theme_config.freezed.dart'; - -@freezed -abstract class ThemeConfig with _$ThemeConfig { - const factory ThemeConfig({ - @Default(ThemeMode.system) ThemeMode mode, - @Default(ImmichColorPreset.indigo) ImmichColorPreset primaryColor, - @Default(false) bool dynamicTheme, - @Default(true) bool colorfulInterface, - }) = _ThemeConfig; -} diff --git a/mobile/lib/domain/models/config/timeline_config.dart b/mobile/lib/domain/models/config/timeline_config.dart deleted file mode 100644 index c4681b0a62..0000000000 --- a/mobile/lib/domain/models/config/timeline_config.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; - -part 'timeline_config.freezed.dart'; - -@freezed -abstract class TimelineConfig with _$TimelineConfig { - const factory TimelineConfig({ - @Default(4) int tilesPerRow, - @Default(GroupAssetsBy.day) GroupAssetsBy groupAssetsBy, - @Default(true) bool storageIndicator, - }) = _TimelineConfig; -} diff --git a/mobile/lib/domain/models/config/viewer_config.dart b/mobile/lib/domain/models/config/viewer_config.dart deleted file mode 100644 index e799843034..0000000000 --- a/mobile/lib/domain/models/config/viewer_config.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'viewer_config.freezed.dart'; - -@freezed -abstract class ViewerConfig with _$ViewerConfig { - const factory ViewerConfig({ - @Default(true) bool loopVideo, - @Default(false) bool loadOriginalVideo, - @Default(true) bool autoPlayVideo, - @Default(false) bool tapToNavigate, - }) = _ViewerConfig; -} diff --git a/mobile/lib/domain/models/device_asset.model.dart b/mobile/lib/domain/models/device_asset.model.dart new file mode 100644 index 0000000000..a404f5a9e2 --- /dev/null +++ b/mobile/lib/domain/models/device_asset.model.dart @@ -0,0 +1,34 @@ +import 'dart:typed_data'; + +class DeviceAsset { + final String assetId; + final Uint8List hash; + final DateTime modifiedTime; + + const DeviceAsset({required this.assetId, required this.hash, required this.modifiedTime}); + + @override + bool operator ==(covariant DeviceAsset other) { + if (identical(this, other)) return true; + + return other.assetId == assetId && other.hash == hash && other.modifiedTime == modifiedTime; + } + + @override + int get hashCode { + return assetId.hashCode ^ hash.hashCode ^ modifiedTime.hashCode; + } + + @override + String toString() { + return 'DeviceAsset(assetId: $assetId, hash: $hash, modifiedTime: $modifiedTime)'; + } + + DeviceAsset copyWith({String? assetId, Uint8List? hash, DateTime? modifiedTime}) { + return DeviceAsset( + assetId: assetId ?? this.assetId, + hash: hash ?? this.hash, + modifiedTime: modifiedTime ?? this.modifiedTime, + ); + } +} diff --git a/mobile/lib/domain/models/events.model.dart b/mobile/lib/domain/models/events.model.dart index ef034a0d03..9bbe00852e 100644 --- a/mobile/lib/domain/models/events.model.dart +++ b/mobile/lib/domain/models/events.model.dart @@ -24,12 +24,6 @@ class ViewerReloadAssetEvent extends Event { const ViewerReloadAssetEvent(); } -class ViewerStackAssetDeletedEvent extends Event { - final int stackIndex; - - const ViewerStackAssetDeletedEvent({required this.stackIndex}); -} - // Multi-Select Events class MultiSelectToggleEvent extends Event { final bool isEnabled; diff --git a/mobile/lib/domain/models/exif.model.dart b/mobile/lib/domain/models/exif.model.dart index 3536048ecc..d0f78b59de 100644 --- a/mobile/lib/domain/models/exif.model.dart +++ b/mobile/lib/domain/models/exif.model.dart @@ -1,45 +1,33 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +class ExifInfo { + final int? assetId; + final int? fileSize; + final String? description; + final bool isFlipped; + final String? orientation; + final String? timeZone; + final DateTime? dateTimeOriginal; + final int? rating; -part 'exif.model.freezed.dart'; + // GPS + final double? latitude; + final double? longitude; + final String? city; + final String? state; + final String? country; -@freezed -abstract class ExifInfo with _$ExifInfo { - const ExifInfo._(); - - const factory ExifInfo({ - int? assetId, - int? fileSize, - String? description, - @Default(false) bool isFlipped, - String? orientation, - String? timeZone, - DateTime? dateTimeOriginal, - int? rating, - int? width, - int? height, - - // GPS - double? latitude, - double? longitude, - String? city, - String? state, - String? country, - - // Camera related - String? make, - String? model, - String? lens, - double? f, - double? mm, - int? iso, - double? exposureSeconds, - }) = _ExifInfo; + // Camera related + final String? make; + final String? model; + final String? lens; + final double? f; + final double? mm; + final int? iso; + final double? exposureSeconds; bool get hasCoordinates => latitude != null && longitude != null && latitude != 0 && longitude != 0; String get exposureTime { - if (exposureSeconds == null || exposureSeconds! <= 0 || exposureSeconds!.isNaN) { + if (exposureSeconds == null) { return ""; } if (exposureSeconds! < 1) { @@ -51,4 +39,148 @@ abstract class ExifInfo with _$ExifInfo { String get fNumber => f == null ? "" : f!.toStringAsFixed(1); String get focalLength => mm == null ? "" : mm!.toStringAsFixed(3); + + const ExifInfo({ + this.assetId, + this.fileSize, + this.description, + this.orientation, + this.timeZone, + this.dateTimeOriginal, + this.rating, + this.isFlipped = false, + this.latitude, + this.longitude, + this.city, + this.state, + this.country, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + }); + + @override + bool operator ==(covariant ExifInfo other) { + if (identical(this, other)) return true; + + return other.fileSize == fileSize && + other.description == description && + other.isFlipped == isFlipped && + other.orientation == orientation && + other.timeZone == timeZone && + other.dateTimeOriginal == dateTimeOriginal && + other.rating == rating && + other.latitude == latitude && + other.longitude == longitude && + other.city == city && + other.state == state && + other.country == country && + other.make == make && + other.model == model && + other.lens == lens && + other.f == f && + other.mm == mm && + other.iso == iso && + other.exposureSeconds == exposureSeconds && + other.assetId == assetId; + } + + @override + int get hashCode { + return fileSize.hashCode ^ + description.hashCode ^ + orientation.hashCode ^ + isFlipped.hashCode ^ + timeZone.hashCode ^ + dateTimeOriginal.hashCode ^ + rating.hashCode ^ + latitude.hashCode ^ + longitude.hashCode ^ + city.hashCode ^ + state.hashCode ^ + country.hashCode ^ + make.hashCode ^ + model.hashCode ^ + lens.hashCode ^ + f.hashCode ^ + mm.hashCode ^ + iso.hashCode ^ + exposureSeconds.hashCode ^ + assetId.hashCode; + } + + @override + String toString() { + return '''{ +fileSize: ${fileSize ?? 'NA'}, +description: ${description ?? 'NA'}, +orientation: ${orientation ?? 'NA'}, +isFlipped: $isFlipped, +timeZone: ${timeZone ?? 'NA'}, +dateTimeOriginal: ${dateTimeOriginal ?? 'NA'}, +rating: ${rating ?? 'NA'}, +latitude: ${latitude ?? 'NA'}, +longitude: ${longitude ?? 'NA'}, +city: ${city ?? 'NA'}, +state: ${state ?? 'NA'}, +country: ${country ?? ''}, +make: ${make ?? 'NA'}, +model: ${model ?? 'NA'}, +lens: ${lens ?? 'NA'}, +f: ${f ?? 'NA'}, +mm: ${mm ?? ''}, +iso: ${iso ?? 'NA'}, +exposureSeconds: ${exposureSeconds ?? 'NA'}, +}'''; + } + + ExifInfo copyWith({ + int? assetId, + int? fileSize, + String? description, + String? orientation, + String? timeZone, + DateTime? dateTimeOriginal, + int? rating, + double? latitude, + double? longitude, + String? city, + String? state, + String? country, + bool? isFlipped, + String? make, + String? model, + String? lens, + double? f, + double? mm, + int? iso, + double? exposureSeconds, + }) { + return ExifInfo( + assetId: assetId ?? this.assetId, + fileSize: fileSize ?? this.fileSize, + description: description ?? this.description, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + rating: rating ?? this.rating, + isFlipped: isFlipped ?? this.isFlipped, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + f: f ?? this.f, + mm: mm ?? this.mm, + iso: iso ?? this.iso, + exposureSeconds: exposureSeconds ?? this.exposureSeconds, + ); + } } diff --git a/mobile/lib/domain/models/feature_message.model.dart b/mobile/lib/domain/models/feature_message.model.dart deleted file mode 100644 index 817076c0e9..0000000000 --- a/mobile/lib/domain/models/feature_message.model.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/utils/semver.dart'; - -enum FeatureHighlight { - shareQuality(image: 'assets/feature_message/share_quality.webp'), - slideshow(image: 'assets/feature_message/slideshow.webp'), - recentlyAdded(image: 'assets/feature_message/recently_added.webp'), - ocr(image: 'assets/feature_message/ocr.webp'), - openInImmich(image: 'assets/feature_message/open_in_immich.webp', platform: [.android]), - uploadToAlbum(); - - /// Asset path of the feature screenshot, or null to show a placeholder. - final String? image; - final List platform; - - const FeatureHighlight({this.image, this.platform = const [.iOS, .android]}); - - bool get isVisibleOnCurrentPlatform => platform.contains(defaultTargetPlatform); - - String title(Translations t) => switch (this) { - FeatureHighlight.shareQuality => t.share_quality_title, - FeatureHighlight.slideshow => t.slideshow_title, - FeatureHighlight.recentlyAdded => t.recently_added_title, - FeatureHighlight.ocr => t.ocr_title, - FeatureHighlight.openInImmich => t.open_in_immich_title, - FeatureHighlight.uploadToAlbum => t.upload_to_album_title, - }; - - String body(Translations t) => switch (this) { - FeatureHighlight.shareQuality => t.share_quality_body, - FeatureHighlight.slideshow => t.slideshow_body, - FeatureHighlight.recentlyAdded => t.recently_added_body, - FeatureHighlight.ocr => t.ocr_body, - FeatureHighlight.openInImmich => t.open_in_immich_body, - FeatureHighlight.uploadToAlbum => t.upload_to_album_body, - }; -} - -/// The release this batch of highlights was authored for. Content-defined: -/// bump it only when publishing a new batch, never from the running app version. -const featureMessageRelease = SemVer(major: 3, minor: 0, patch: 0); - -/// Highlights relevant to the current platform. -List get visibleFeatureMessageHighlights => - FeatureHighlight.values.where((h) => h.isVisibleOnCurrentPlatform).toList(); diff --git a/mobile/lib/domain/models/log.model.dart b/mobile/lib/domain/models/log.model.dart index 72267bcb5c..9902ca04ca 100644 --- a/mobile/lib/domain/models/log.model.dart +++ b/mobile/lib/domain/models/log.model.dart @@ -1,18 +1,49 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'log.model.freezed.dart'; - /// Log levels according to dart logging [Level] enum LogLevel { all, finest, finer, fine, config, info, warning, severe, shout, off } -@freezed -abstract class LogMessage with _$LogMessage { - const factory LogMessage({ - required String message, - required LogLevel level, - required DateTime createdAt, - String? logger, - String? error, - String? stack, - }) = _LogMessage; +class LogMessage { + final String message; + final LogLevel level; + final DateTime createdAt; + final String? logger; + final String? error; + final String? stack; + + const LogMessage({ + required this.message, + required this.level, + required this.createdAt, + this.logger, + this.error, + this.stack, + }); + + @override + bool operator ==(covariant LogMessage other) { + if (identical(this, other)) return true; + + return other.message == message && + other.level == level && + other.createdAt == createdAt && + other.logger == logger && + other.error == error && + other.stack == stack; + } + + @override + int get hashCode { + return message.hashCode ^ level.hashCode ^ createdAt.hashCode ^ logger.hashCode ^ error.hashCode ^ stack.hashCode; + } + + @override + String toString() { + return '''LogMessage: { +message: $message, +level: $level, +createdAt: $createdAt, +logger: ${logger ?? ''}, +error: ${error ?? ''}, +stack: ${stack ?? ''}, +}'''; + } } diff --git a/mobile/lib/domain/models/map.model.dart b/mobile/lib/domain/models/map.model.dart index 52f6fe8afd..ce0834f0cb 100644 --- a/mobile/lib/domain/models/map.model.dart +++ b/mobile/lib/domain/models/map.model.dart @@ -1,9 +1,18 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -part 'map.model.freezed.dart'; +class Marker { + final LatLng location; + final String assetId; -@freezed -abstract class Marker with _$Marker { - const factory Marker({required LatLng location, required String assetId}) = _Marker; + const Marker({required this.location, required this.assetId}); + + @override + bool operator ==(covariant Marker other) { + if (identical(this, other)) return true; + + return other.location == location && other.assetId == assetId; + } + + @override + int get hashCode => location.hashCode ^ assetId.hashCode; } diff --git a/mobile/lib/domain/models/memory.model.dart b/mobile/lib/domain/models/memory.model.dart index 1c556f382f..40117c5ac6 100644 --- a/mobile/lib/domain/models/memory.model.dart +++ b/mobile/lib/domain/models/memory.model.dart @@ -1,21 +1,23 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; + import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -part 'memory.model.freezed.dart'; - -// TODO(agg23): Remove enum suffix enum MemoryTypeEnum { // do not change this order! onThisDay, } -@Freezed(fromJson: false, toJson: false) -abstract class MemoryData with _$MemoryData { - const MemoryData._(); +class MemoryData { + final int year; - const factory MemoryData({required int year}) = _MemoryData; + const MemoryData({required this.year}); + + MemoryData copyWith({int? year}) { + return MemoryData(year: year ?? this.year); + } Map toMap() { return {'year': year}; @@ -28,25 +30,140 @@ abstract class MemoryData with _$MemoryData { String toJson() => json.encode(toMap()); factory MemoryData.fromJson(String source) => MemoryData.fromMap(json.decode(source) as Map); + + @override + String toString() => 'MemoryData(year: $year)'; + + @override + bool operator ==(covariant MemoryData other) { + if (identical(this, other)) return true; + + return other.year == year; + } + + @override + int get hashCode => year.hashCode; } // Model for a memory stored in the server -// TODO(agg23): DriftMemoryRepository currently mutates `assets` -@Freezed(makeCollectionsUnmodifiable: false) -abstract class DriftMemory with _$DriftMemory { - const factory DriftMemory({ - required String id, - required DateTime createdAt, - required DateTime updatedAt, +class DriftMemory { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + + // enum + final MemoryTypeEnum type; + final MemoryData data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + final List assets; + + const DriftMemory({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + required this.assets, + }); + + DriftMemory copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, DateTime? deletedAt, - required String ownerId, - required MemoryTypeEnum type, - required MemoryData data, - required bool isSaved, - required DateTime memoryAt, + String? ownerId, + MemoryTypeEnum? type, + MemoryData? data, + bool? isSaved, + DateTime? memoryAt, DateTime? seenAt, DateTime? showAt, DateTime? hideAt, - required List assets, - }) = _DriftMemory; + List? assets, + }) { + return DriftMemory( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + assets: assets ?? this.assets, + ); + } + + @override + String toString() { + return '''Memory { + id: $id, + createdAt: $createdAt, + updatedAt: $updatedAt, + deletedAt: ${deletedAt ?? ""}, + ownerId: $ownerId, + type: $type, + data: $data, + isSaved: $isSaved, + memoryAt: $memoryAt, + seenAt: ${seenAt ?? ""}, + showAt: ${showAt ?? ""}, + hideAt: ${hideAt ?? ""}, + assets: $assets +}'''; + } + + @override + bool operator ==(covariant DriftMemory other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.deletedAt == deletedAt && + other.ownerId == ownerId && + other.type == type && + other.data == data && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.seenAt == seenAt && + other.showAt == showAt && + other.hideAt == hideAt && + listEquals(other.assets, assets); + } + + @override + int get hashCode { + return id.hashCode ^ + createdAt.hashCode ^ + updatedAt.hashCode ^ + deletedAt.hashCode ^ + ownerId.hashCode ^ + type.hashCode ^ + data.hashCode ^ + isSaved.hashCode ^ + memoryAt.hashCode ^ + seenAt.hashCode ^ + showAt.hashCode ^ + hideAt.hashCode ^ + assets.hashCode; + } } diff --git a/mobile/lib/domain/models/ocr.model.dart b/mobile/lib/domain/models/ocr.model.dart deleted file mode 100644 index bfb398bc79..0000000000 --- a/mobile/lib/domain/models/ocr.model.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'ocr.model.freezed.dart'; - -@freezed -abstract class Ocr with _$Ocr { - const factory Ocr({ - required String id, - required String assetId, - required double x1, - required double y1, - required double x2, - required double y2, - required double x3, - required double y3, - required double x4, - required double y4, - required double boxScore, - required double textScore, - required String text, - required bool isVisible, - }) = _Ocr; -} diff --git a/mobile/lib/domain/models/person.model.dart b/mobile/lib/domain/models/person.model.dart index 94ead6a2e9..7559720c45 100644 --- a/mobile/lib/domain/models/person.model.dart +++ b/mobile/lib/domain/models/person.model.dart @@ -1,9 +1,190 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:convert'; -part 'person.model.freezed.dart'; +// TODO: Remove PersonDto once Isar is removed +class PersonDto { + const PersonDto({ + required this.id, + this.birthDate, + required this.isHidden, + required this.name, + required this.thumbnailPath, + this.updatedAt, + }); -@freezed -abstract class Person with _$Person { - const factory Person({required String id, required String name, DateTime? updatedAt, DateTime? birthDate}) = _Person; + final String id; + final DateTime? birthDate; + final bool isHidden; + final String name; + final String thumbnailPath; + final DateTime? updatedAt; + + @override + String toString() { + return 'Person(id: $id, birthDate: $birthDate, isHidden: $isHidden, name: $name, thumbnailPath: $thumbnailPath, updatedAt: $updatedAt)'; + } + + PersonDto copyWith({ + String? id, + DateTime? birthDate, + bool? isHidden, + String? name, + String? thumbnailPath, + DateTime? updatedAt, + }) { + return PersonDto( + id: id ?? this.id, + birthDate: birthDate ?? this.birthDate, + isHidden: isHidden ?? this.isHidden, + name: name ?? this.name, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + Map toMap() { + return { + 'id': id, + 'birthDate': birthDate?.millisecondsSinceEpoch, + 'isHidden': isHidden, + 'name': name, + 'thumbnailPath': thumbnailPath, + 'updatedAt': updatedAt?.millisecondsSinceEpoch, + }; + } + + factory PersonDto.fromMap(Map map) { + return PersonDto( + id: map['id'] as String, + birthDate: map['birthDate'] != null ? DateTime.fromMillisecondsSinceEpoch(map['birthDate'] as int) : null, + isHidden: map['isHidden'] as bool, + name: map['name'] as String, + thumbnailPath: map['thumbnailPath'] as String, + updatedAt: map['updatedAt'] != null ? DateTime.fromMillisecondsSinceEpoch(map['updatedAt'] as int) : null, + ); + } + + String toJson() => json.encode(toMap()); + + factory PersonDto.fromJson(String source) => PersonDto.fromMap(json.decode(source) as Map); + + @override + bool operator ==(covariant PersonDto other) { + if (identical(this, other)) return true; + + return other.id == id && + other.birthDate == birthDate && + other.isHidden == isHidden && + other.name == name && + other.thumbnailPath == thumbnailPath && + other.updatedAt == updatedAt; + } + + @override + int get hashCode { + return id.hashCode ^ + birthDate.hashCode ^ + isHidden.hashCode ^ + name.hashCode ^ + thumbnailPath.hashCode ^ + updatedAt.hashCode; + } +} + +// Model for a person stored in the server +class DriftPerson { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + + const DriftPerson({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + required this.color, + this.birthDate, + }); + + DriftPerson copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + String? faceAssetId, + bool? isFavorite, + bool? isHidden, + String? color, + DateTime? birthDate, + }) { + return DriftPerson( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + String toString() { + return '''Person { + id: $id, + createdAt: $createdAt, + updatedAt: $updatedAt, + ownerId: $ownerId, + name: $name, + faceAssetId: ${faceAssetId ?? ""}, + isFavorite: $isFavorite, + isHidden: $isHidden, + color: ${color ?? ""}, + birthDate: ${birthDate ?? ""} +}'''; + } + + @override + bool operator ==(covariant DriftPerson other) { + if (identical(this, other)) return true; + + return other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.ownerId == ownerId && + other.name == name && + other.faceAssetId == faceAssetId && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.color == color && + other.birthDate == birthDate; + } + + @override + int get hashCode { + return id.hashCode ^ + createdAt.hashCode ^ + updatedAt.hashCode ^ + ownerId.hashCode ^ + name.hashCode ^ + faceAssetId.hashCode ^ + isFavorite.hashCode ^ + isHidden.hashCode ^ + color.hashCode ^ + birthDate.hashCode; + } } diff --git a/mobile/lib/domain/models/search_result.model.dart b/mobile/lib/domain/models/search_result.model.dart index 11409e64a2..21134b73d8 100644 --- a/mobile/lib/domain/models/search_result.model.dart +++ b/mobile/lib/domain/models/search_result.model.dart @@ -1,15 +1,23 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -part 'search_result.model.freezed.dart'; +class SearchResult { + final List assets; + final int? nextPage; -@Freezed(toStringOverride: false) -abstract class SearchResult with _$SearchResult { - const SearchResult._(); + const SearchResult({required this.assets, this.nextPage}); - const factory SearchResult({required List assets, int? nextPage}) = _SearchResult; - - // Explicitly don't log results, only attributes @override String toString() => 'SearchResult(assets: ${assets.length}, nextPage: $nextPage)'; + + @override + bool operator ==(covariant SearchResult other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.assets, assets) && other.nextPage == nextPage; + } + + @override + int get hashCode => assets.hashCode ^ nextPage.hashCode; } diff --git a/mobile/lib/domain/models/server_capability.model.dart b/mobile/lib/domain/models/server_capability.model.dart deleted file mode 100644 index ece9850594..0000000000 --- a/mobile/lib/domain/models/server_capability.model.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:immich_mobile/utils/semver.dart'; - -enum ServerCapability { - // Feature Support - cloudIdMetadata(SemVer(major: 2, minor: 4, patch: 0)), - bulkCloudIdMetadata(SemVer(major: 2, minor: 5, patch: 0)), - assetEdits(SemVer(major: 2, minor: 6, patch: 0)), - assetFacesV2(SemVer(major: 2, minor: 6, patch: 0)), - syncV2(SemVer(major: 3, minor: 0, patch: 0)), - assetOcr(SemVer(major: 3, minor: 0, patch: 0)), - - // Migrations - assetPayloadChange20260128(SemVer(major: 2, minor: 5, patch: 0)), - assetPayloadChange20260597(SemVer(major: 2, minor: 7, patch: 6)); - - const ServerCapability(this.minVersion); - - final SemVer minVersion; -} - -extension ServerCapabilitySupport on SemVer { - bool supports(ServerCapability capability) => this >= capability.minVersion; -} diff --git a/mobile/lib/domain/models/setting.model.dart b/mobile/lib/domain/models/setting.model.dart index d6d9e2902b..2c46507331 100644 --- a/mobile/lib/domain/models/setting.model.dart +++ b/mobile/lib/domain/models/setting.model.dart @@ -1,7 +1,15 @@ import 'package:immich_mobile/domain/models/store.model.dart'; enum Setting { - advancedTroubleshooting(StoreKey.advancedTroubleshooting, false); + tilesPerRow(StoreKey.tilesPerRow, 4), + groupAssetsBy(StoreKey.groupAssetsBy, 0), + showStorageIndicator(StoreKey.storageIndicator, true), + loadOriginal(StoreKey.loadOriginal, false), + loadOriginalVideo(StoreKey.loadOriginalVideo, false), + autoPlayVideo(StoreKey.autoPlayVideo, true), + preferRemoteImage(StoreKey.preferRemoteImage, false), + advancedTroubleshooting(StoreKey.advancedTroubleshooting, false), + enableBackup(StoreKey.enableBackup, false); const Setting(this.storeKey, this.defaultValue); diff --git a/mobile/lib/domain/models/settings_key.dart b/mobile/lib/domain/models/settings_key.dart deleted file mode 100644 index 85e58ffcf1..0000000000 --- a/mobile/lib/domain/models/settings_key.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_mobile/constants/colors.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/models/value_codec.dart'; -import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/utils/semver.dart'; - -enum SettingsKey { - // Theme - themePrimaryColor(codec: EnumCodec(ImmichColorPreset.values)), - themeMode(codec: EnumCodec(ThemeMode.values)), - themeDynamic(), - themeColorfulInterface(), - - // Image - imagePreferRemote(), - imageLoadOriginal(), - - // Viewer - viewerLoopVideo(), - viewerLoadOriginalVideo(), - viewerAutoPlayVideo(), - viewerTapToNavigate(), - - // Network - networkAutoEndpointSwitching(), - networkExternalEndpointList>(codec: ListCodec(PrimitiveCodec.string)), - networkCustomHeaders>(codec: MapCodec(PrimitiveCodec.string, PrimitiveCodec.string)), - networkPreferredWifiName(), - networkLocalEndpoint(), - - // Album - albumSortMode(codec: EnumCodec(AlbumSortMode.values)), - albumIsReverse(), - albumIsGrid(), - - // Backup - backupEnabled(), - backupUseCellularForVideos(), - backupUseCellularForPhotos(), - backupRequireCharging(), - backupTriggerDelay(), - backupSyncAlbums(), - - // Timeline - timelineTilesPerRow(), - timelineGroupAssetsBy(codec: EnumCodec(GroupAssetsBy.values)), - timelineStorageIndicator(), - - // Log - logLevel(codec: EnumCodec(LogLevel.values)), - - // Map - mapShowFavoriteOnly(), - mapRelativeDate(), - mapCustomFrom(), - mapCustomTo(), - mapIncludeArchived(), - mapThemeMode(codec: EnumCodec(ThemeMode.values)), - mapWithPartners(), - - // Cleanup - cleanupKeepFavorites(), - cleanupKeepMediaType(codec: EnumCodec(AssetKeepType.values)), - cleanupKeepAlbumIds>(codec: ListCodec(PrimitiveCodec.string)), - cleanupCutoffDaysAgo(), - cleanupDefaultsInitialized(), - - // Share - shareFileType(codec: EnumCodec(ShareAssetType.values)), - - // Slideshow - slideshowRepeat(), - slideshowDuration(), - slideshowLook(codec: EnumCodec(SlideshowLook.values)), - slideshowDirection(codec: EnumCodec(SlideshowDirection.values)), - - // Feature message - featureMessageSeenRelease(codec: SemVerCodec()); - - final ValueCodec? _codecOverride; - - const SettingsKey({ValueCodec? codec}) : _codecOverride = codec; - - ValueCodec get _codec => _codecOverride ?? ValueCodec.forType(T); - - String encode(T value) => _codec.encode(value); - - T decode(String raw) => _codec.decode(raw); -} diff --git a/mobile/lib/domain/models/stack.model.dart b/mobile/lib/domain/models/stack.model.dart index a016d6cd3d..d5ccf5558d 100644 --- a/mobile/lib/domain/models/stack.model.dart +++ b/mobile/lib/domain/models/stack.model.dart @@ -1,22 +1,71 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'stack.model.freezed.dart'; - // Model for a stack stored in the server -@freezed -abstract class Stack with _$Stack { - const factory Stack({ - required String id, - required DateTime createdAt, - required DateTime updatedAt, - required String ownerId, - required String primaryAssetId, - }) = _Stack; +class Stack { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + + const Stack({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + + Stack copyWith({String? id, DateTime? createdAt, DateTime? updatedAt, String? ownerId, String? primaryAssetId}) { + return Stack( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + String toString() { + return '''Stack { + id: $id, + createdAt: $createdAt, + updatedAt: $updatedAt, + ownerId: $ownerId, + primaryAssetId: $primaryAssetId +}'''; + } + + @override + bool operator ==(covariant Stack other) { + if (identical(this, other)) return true; + + return other.id == id && + other.createdAt == createdAt && + other.updatedAt == updatedAt && + other.ownerId == ownerId && + other.primaryAssetId == primaryAssetId; + } + + @override + int get hashCode { + return id.hashCode ^ createdAt.hashCode ^ updatedAt.hashCode ^ ownerId.hashCode ^ primaryAssetId.hashCode; + } } -@freezed -abstract class StackResponse with _$StackResponse { - const factory StackResponse({required String id, required String primaryAssetId, required List assetIds}) = - _StackResponse; +class StackResponse { + final String id; + final String primaryAssetId; + final List assetIds; + + const StackResponse({required this.id, required this.primaryAssetId, required this.assetIds}); + + @override + bool operator ==(covariant StackResponse other) { + if (identical(this, other)) return true; + + return other.id == id && other.primaryAssetId == primaryAssetId && other.assetIds == assetIds; + } + + @override + int get hashCode => id.hashCode ^ primaryAssetId.hashCode ^ assetIds.hashCode; } diff --git a/mobile/lib/domain/models/store.model.dart b/mobile/lib/domain/models/store.model.dart index d662dd3425..00545aa01a 100644 --- a/mobile/lib/domain/models/store.model.dart +++ b/mobile/lib/domain/models/store.model.dart @@ -1,72 +1,128 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -part 'store.model.freezed.dart'; - /// Key for each possible value in the `Store`. /// Defines the data type for each value enum StoreKey { version._(0), + assetETag._(1), currentUser._(2), + deviceIdHash._(3), deviceId._(4), + backupFailedSince._(5), + backupRequireWifi._(6), + backupRequireCharging._(7), + backupTriggerDelay._(8), serverUrl._(10), accessToken._(11), serverEndpoint._(12), + autoBackup._(13), + backgroundBackup._(14), + sslClientCertData._(15), + sslClientPasswd._(16), + // user settings from [AppSettingsEnum] below: + loadPreview._(100), + loadOriginal._(101), + themeMode._(102), + tilesPerRow._(103), + dynamicLayout._(104), + groupAssetsBy._(105), + uploadErrorNotificationGracePeriod._(106), + backgroundBackupTotalProgress._(107), + backgroundBackupSingleProgress._(108), + storageIndicator._(109), + thumbnailCacheSize._(110), + imageCacheSize._(111), + albumThumbnailCacheSize._(112), + selectedAlbumSortOrder._(113), advancedTroubleshooting._(114), + logLevel._(115), + preferRemoteImage._(116), + loopVideo._(117), + // map related settings + mapShowFavoriteOnly._(118), + mapRelativeDate._(119), + selfSignedCert._(120), + mapIncludeArchived._(121), + ignoreIcloudAssets._(122), + selectedAlbumSortReverse._(123), + mapThemeMode._(124), + mapwithPartners._(125), enableHapticFeedback._(126), + customHeaders._(127), + // theme settings + primaryColor._(128), + dynamicTheme._(129), + colorfulInterface._(130), + + syncAlbums._(131), + + // Auto endpoint switching + autoEndpointSwitching._(132), + preferredWifiName._(133), + localEndpoint._(134), + externalEndpointList._(135), + + // Video settings + loadOriginalVideo._(136), manageLocalMediaAndroid._(137), + // Read-only Mode settings readonlyModeEnabled._(138), - syncMigrationStatus._(1013), + autoPlayVideo._(139), + albumGridView._(140), - // Legacy keys that have been migrated to the new metadata store - legacyBackupRequireCharging._(7), - legacyBackupTriggerDelay._(8), - legacySyncAlbums._(131), - legacyEnableBackup._(1003), - legacyUseWifiForUploadVideos._(1004), - legacyUseWifiForUploadPhotos._(1005), - legacySelectedAlbumSortOrder._(113), - legacySelectedAlbumSortReverse._(123), - legacyAlbumGridView._(140), - legacyAutoEndpointSwitching._(132), - legacyPreferredWifiName._(133), - legacyLocalEndpoint._(134), - legacyExternalEndpointList._(135), - legacyCustomHeaders._(127), - legacyLoopVideo._(117), - legacyLoadOriginalVideo._(136), - legacyAutoPlayVideo._(139), - legacyTapToNavigate._(141), - legacyPreferRemoteImage._(116), - legacyLoadOriginal._(101), - legacyPrimaryColor._(128), - legacyDynamicTheme._(129), - legacyColorfulInterface._(130), - legacyThemeMode._(102), - legacyCleanupKeepFavorites._(1008), - legacyCleanupKeepMediaType._(1009), - legacyCleanupKeepAlbumIds._(1010), - legacyCleanupCutoffDaysAgo._(1011), - legacyCleanupDefaultsInitialized._(1012), - legacyTilesPerRow._(103), - legacyGroupAssetsBy._(105), - legacyStorageIndicator._(109), - legacyMapRelativeDate._(119), - legacyMapShowFavoriteOnly._(118), - legacyMapIncludeArchived._(121), - legacyMapThemeMode._(124), - legacyMapwithPartners._(125), - legacyLogLevel._(115); + // Image viewer navigation settings + tapToNavigate._(141), + + // Experimental stuff + photoManagerCustomFilter._(1000), + betaPromptShown._(1001), + betaTimeline._(1002), + enableBackup._(1003), + useWifiForUploadVideos._(1004), + useWifiForUploadPhotos._(1005), + needBetaMigration._(1006), + // TODO: Remove this after patching open-api + shouldResetSync._(1007), + + // Free up space + cleanupKeepFavorites._(1008), + cleanupKeepMediaType._(1009), + cleanupKeepAlbumIds._(1010), + cleanupCutoffDaysAgo._(1011), + cleanupDefaultsInitialized._(1012), + + syncMigrationStatus._(1013); const StoreKey._(this.id); final int id; Type get type => T; } -@freezed -abstract class StoreDto with _$StoreDto { - const factory StoreDto(StoreKey key, T? value) = _StoreDto; +class StoreDto { + final StoreKey key; + final T? value; + + const StoreDto(this.key, this.value); + + @override + String toString() { + return ''' +StoreDto: { + key: $key, + value: ${value ?? ''}, +}'''; + } + + @override + bool operator ==(covariant StoreDto other) { + if (identical(this, other)) return true; + + return other.key == key && other.value == value; + } + + @override + int get hashCode => key.hashCode ^ value.hashCode; } diff --git a/mobile/lib/domain/models/tag.model.dart b/mobile/lib/domain/models/tag.model.dart index d38931a285..357367b13e 100644 --- a/mobile/lib/domain/models/tag.model.dart +++ b/mobile/lib/domain/models/tag.model.dart @@ -1,11 +1,27 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -part 'tag.model.freezed.dart'; +class Tag { + final String id; + final String value; -@freezed -abstract class Tag with _$Tag { - const factory Tag({required String id, required String value}) = _Tag; + const Tag({required this.id, required this.value}); + + @override + String toString() { + return 'Tag(id: $id, value: $value)'; + } + + @override + bool operator ==(covariant Tag other) { + if (identical(this, other)) return true; + + return other.id == id && other.value == value; + } + + @override + int get hashCode { + return id.hashCode ^ value.hashCode; + } static Tag fromDto(TagResponseDto dto) { return Tag(id: dto.id, value: dto.value); diff --git a/mobile/lib/domain/models/time_range.model.dart b/mobile/lib/domain/models/time_range.model.dart deleted file mode 100644 index b0b990d6db..0000000000 --- a/mobile/lib/domain/models/time_range.model.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/utils/option.dart'; - -part 'time_range.model.freezed.dart'; - -@Freezed(copyWith: false) -abstract class TimeRange with _$TimeRange { - const TimeRange._(); - - const factory TimeRange({DateTime? from, DateTime? to}) = _TimeRange; - - // Patching is custom, which prevents using Freezed `copyWith` - TimeRange copyWith({Option? from, Option? to}) => - TimeRange(from: from.patch(this.from), to: to.patch(this.to)); - - TimeRange clearFrom() => TimeRange(to: to); - TimeRange clearTo() => TimeRange(from: from); -} diff --git a/mobile/lib/domain/models/timeline.model.dart b/mobile/lib/domain/models/timeline.model.dart index 86f6f112fb..c531fa4a94 100644 --- a/mobile/lib/domain/models/timeline.model.dart +++ b/mobile/lib/domain/models/timeline.model.dart @@ -2,8 +2,6 @@ enum GroupAssetsBy { day, month, auto, none } enum HeaderType { none, month, day, monthAndDay } -enum SortAssetsBy { taken, uploaded } - class Bucket { final int assetCount; diff --git a/mobile/lib/domain/models/user.model.dart b/mobile/lib/domain/models/user.model.dart index e48992980b..380295b4b3 100644 --- a/mobile/lib/domain/models/user.model.dart +++ b/mobile/lib/domain/models/user.model.dart @@ -1,9 +1,7 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; import 'dart:ui'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'user.model.freezed.dart'; - enum AvatarColor { // do not change this order or reuse indices for other purposes, adding is OK primary("primary"), @@ -35,36 +33,99 @@ enum AvatarColor { } // TODO: Rename to User once Isar is removed -@Freezed(equal: false) -abstract class UserDto with _$UserDto { - const UserDto._(); +class UserDto { + final String id; + final String email; + final String name; + final bool isAdmin; + final DateTime? updatedAt; - const factory UserDto({ - required String id, - required String email, - required String name, - @Default(false) bool isAdmin, - DateTime? updatedAt, - required DateTime profileChangedAt, - @Default(AvatarColor.primary) AvatarColor avatarColor, - @Default(true) bool memoryEnabled, - @Default(false) bool inTimeline, - @Default(false) bool isPartnerSharedBy, - @Default(false) bool isPartnerSharedWith, - @Default(false) bool hasProfileImage, - @Default(0) int quotaUsageInBytes, - @Default(0) int quotaSizeInBytes, - }) = _UserDto; + final AvatarColor avatarColor; + + final bool memoryEnabled; + final bool inTimeline; + + final bool isPartnerSharedBy; + final bool isPartnerSharedWith; + + final int quotaUsageInBytes; + final int quotaSizeInBytes; bool get hasQuota => quotaSizeInBytes > 0; - // We use [DateTime.isAtSameMomentAs] for comparing across timezones. As Freezed doesn't support custom equality, we need to have our own `==` for now - // TODO(agg23): Switch to newtypes to fix equality + final bool hasProfileImage; + final DateTime profileChangedAt; + + const UserDto({ + required this.id, + required this.email, + required this.name, + this.isAdmin = false, + this.updatedAt, + required this.profileChangedAt, + this.avatarColor = AvatarColor.primary, + this.memoryEnabled = true, + this.inTimeline = false, + this.isPartnerSharedBy = false, + this.isPartnerSharedWith = false, + this.hasProfileImage = false, + this.quotaUsageInBytes = 0, + this.quotaSizeInBytes = 0, + }); + + @override + String toString() { + return '''User: { +id: $id, +email: $email, +name: $name, +isAdmin: $isAdmin, +updatedAt: $updatedAt, +avatarColor: $avatarColor, +memoryEnabled: $memoryEnabled, +inTimeline: $inTimeline, +isPartnerSharedBy: $isPartnerSharedBy, +isPartnerSharedWith: $isPartnerSharedWith, +hasProfileImage: $hasProfileImage +profileChangedAt: $profileChangedAt +}'''; + } + + UserDto copyWith({ + String? id, + String? email, + String? name, + bool? isAdmin, + DateTime? updatedAt, + AvatarColor? avatarColor, + bool? memoryEnabled, + bool? inTimeline, + bool? isPartnerSharedBy, + bool? isPartnerSharedWith, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + }) => UserDto( + id: id ?? this.id, + email: email ?? this.email, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + updatedAt: updatedAt ?? this.updatedAt, + avatarColor: avatarColor ?? this.avatarColor, + memoryEnabled: memoryEnabled ?? this.memoryEnabled, + inTimeline: inTimeline ?? this.inTimeline, + isPartnerSharedBy: isPartnerSharedBy ?? this.isPartnerSharedBy, + isPartnerSharedWith: isPartnerSharedWith ?? this.isPartnerSharedWith, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + @override bool operator ==(covariant UserDto other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; return other.id == id && ((updatedAt == null && other.updatedAt == null) || @@ -101,135 +162,74 @@ abstract class UserDto with _$UserDto { quotaUsageInBytes.hashCode; } -@freezed -abstract class PartnerUserDto with _$PartnerUserDto { - const factory PartnerUserDto({ - required String id, - required String email, - required String name, - required bool inTimeline, - String? profileImagePath, - }) = _PartnerUserDto; -} - -class User { +class PartnerUserDto { final String id; - final String name; final String email; - final DateTime profileChangedAt; - final bool hasProfileImage; - final AvatarColor? avatarColor; - - const User({ - required this.id, - required this.name, - required this.email, - required this.profileChangedAt, - required this.hasProfileImage, - this.avatarColor = AvatarColor.primary, - }); - - @override - String toString() { - return 'User(id: $id, name: $name, email: $email, profileChangedAt: $profileChangedAt, hasProfileImage: $hasProfileImage, avatarColor: $avatarColor)'; - } - - @override - bool operator ==(covariant User other) { - if (identical(this, other)) { - return true; - } - - return other.id == id && - other.name == name && - other.email == email && - other.profileChangedAt == profileChangedAt && - other.hasProfileImage == hasProfileImage && - other.avatarColor == avatarColor; - } - - @override - int get hashCode => Object.hash(id, name, email, profileChangedAt, hasProfileImage, avatarColor); -} - -class AuthUser extends User { - final bool isAdmin; - final String? pinCode; - final int? quotaSizeInBytes; - final int quotaUsageInBytes; - - const AuthUser({ - required super.id, - required super.name, - required super.email, - required super.profileChangedAt, - required super.hasProfileImage, - super.avatarColor, - this.isAdmin = false, - this.pinCode, - this.quotaSizeInBytes = 0, - this.quotaUsageInBytes = 0, - }); - - @override - String toString() { - return 'AuthUser(user: ${super.toString()}, isAdmin: $isAdmin, pinCode: $pinCode, quotaSizeInBytes: $quotaSizeInBytes, quotaUsageInBytes: $quotaUsageInBytes)'; - } - - @override - bool operator ==(covariant AuthUser other) { - if (identical(this, other)) { - return true; - } - - return super == other && - other.isAdmin == isAdmin && - other.pinCode == pinCode && - other.quotaSizeInBytes == quotaSizeInBytes && - other.quotaUsageInBytes == quotaUsageInBytes; - } - - @override - int get hashCode => Object.hash(super.hashCode, isAdmin, pinCode, quotaSizeInBytes, quotaUsageInBytes); -} - -class Partner extends User { + final String name; final bool inTimeline; - const Partner({ - required super.id, - required super.name, - required super.email, - required super.profileChangedAt, - required super.hasProfileImage, - super.avatarColor, - this.inTimeline = false, + final String? profileImagePath; + + const PartnerUserDto({ + required this.id, + required this.email, + required this.name, + required this.inTimeline, + this.profileImagePath, }); - Partner.fromUser(User user, {this.inTimeline = false}) - : super( - id: user.id, - name: user.name, - email: user.email, - profileChangedAt: user.profileChangedAt, - hasProfileImage: user.hasProfileImage, - avatarColor: user.avatarColor, - ); + PartnerUserDto copyWith({String? id, String? email, String? name, bool? inTimeline, String? profileImagePath}) { + return PartnerUserDto( + id: id ?? this.id, + email: email ?? this.email, + name: name ?? this.name, + inTimeline: inTimeline ?? this.inTimeline, + profileImagePath: profileImagePath ?? this.profileImagePath, + ); + } + + Map toMap() { + return { + 'id': id, + 'email': email, + 'name': name, + 'inTimeline': inTimeline, + 'profileImagePath': profileImagePath, + }; + } + + factory PartnerUserDto.fromMap(Map map) { + return PartnerUserDto( + id: map['id'] as String, + email: map['email'] as String, + name: map['name'] as String, + inTimeline: map['inTimeline'] as bool, + profileImagePath: map['profileImagePath'] != null ? map['profileImagePath'] as String : null, + ); + } + + String toJson() => json.encode(toMap()); + + factory PartnerUserDto.fromJson(String source) => PartnerUserDto.fromMap(json.decode(source) as Map); @override String toString() { - return 'Partner(user: ${super.toString()}, inTimeline: $inTimeline)'; + return 'PartnerUserDto(id: $id, email: $email, name: $name, inTimeline: $inTimeline, profileImagePath: $profileImagePath)'; } @override - bool operator ==(covariant Partner other) { - if (identical(this, other)) { - return true; - } + bool operator ==(covariant PartnerUserDto other) { + if (identical(this, other)) return true; - return super == other && other.inTimeline == inTimeline; + return other.id == id && + other.email == email && + other.name == name && + other.inTimeline == inTimeline && + other.profileImagePath == profileImagePath; } @override - int get hashCode => Object.hash(super.hashCode, inTimeline); + int get hashCode { + return id.hashCode ^ email.hashCode ^ name.hashCode ^ inTimeline.hashCode ^ profileImagePath.hashCode; + } } diff --git a/mobile/lib/domain/models/user_metadata.model.dart b/mobile/lib/domain/models/user_metadata.model.dart index 2b1da0a809..af404051a7 100644 --- a/mobile/lib/domain/models/user_metadata.model.dart +++ b/mobile/lib/domain/models/user_metadata.model.dart @@ -1,9 +1,5 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -part 'user_metadata.model.freezed.dart'; - enum UserMetadataKey { // do not change this order! onboarding, @@ -11,32 +7,98 @@ enum UserMetadataKey { license, } -@freezed -abstract class Onboarding with _$Onboarding { - const Onboarding._(); +class Onboarding { + final bool isOnboarded; - const factory Onboarding({required bool isOnboarded}) = _Onboarding; + const Onboarding({required this.isOnboarded}); + + Onboarding copyWith({bool? isOnboarded}) { + return Onboarding(isOnboarded: isOnboarded ?? this.isOnboarded); + } + + Map toMap() { + final onboarding = {}; + onboarding["isOnboarded"] = isOnboarded; + return onboarding; + } factory Onboarding.fromMap(Map map) { - return Onboarding(isOnboarded: map["isOnboarded"]! as bool); + return Onboarding(isOnboarded: map["isOnboarded"] as bool); } + + @override + String toString() { + return '''Onboarding { +isOnboarded: $isOnboarded, +}'''; + } + + @override + bool operator ==(covariant Onboarding other) { + if (identical(this, other)) return true; + + return isOnboarded == other.isOnboarded; + } + + @override + int get hashCode => isOnboarded.hashCode; } -@freezed -abstract class Preferences with _$Preferences { - const Preferences._(); +class Preferences { + final bool foldersEnabled; + final bool memoriesEnabled; + final bool peopleEnabled; + final bool ratingsEnabled; + final bool sharedLinksEnabled; + final bool tagsEnabled; + final AvatarColor userAvatarColor; + final bool showSupportBadge; - const factory Preferences({ - @Default(false) bool foldersEnabled, - @Default(true) bool memoriesEnabled, - @Default(true) bool peopleEnabled, - @Default(false) bool ratingsEnabled, - @Default(true) bool sharedLinksEnabled, - @Default(false) bool tagsEnabled, - @Default(AvatarColor.primary) AvatarColor userAvatarColor, - @Default(true) bool showSupportBadge, - @Default(3) int minimumFaces, - }) = _Preferences; + const Preferences({ + this.foldersEnabled = false, + this.memoriesEnabled = true, + this.peopleEnabled = true, + this.ratingsEnabled = false, + this.sharedLinksEnabled = true, + this.tagsEnabled = false, + this.userAvatarColor = AvatarColor.primary, + this.showSupportBadge = true, + }); + + Preferences copyWith({ + bool? foldersEnabled, + bool? memoriesEnabled, + bool? peopleEnabled, + bool? ratingsEnabled, + bool? sharedLinksEnabled, + bool? tagsEnabled, + AvatarColor? userAvatarColor, + bool? showSupportBadge, + }) { + return Preferences( + foldersEnabled: foldersEnabled ?? this.foldersEnabled, + memoriesEnabled: memoriesEnabled ?? this.memoriesEnabled, + peopleEnabled: peopleEnabled ?? this.peopleEnabled, + ratingsEnabled: ratingsEnabled ?? this.ratingsEnabled, + sharedLinksEnabled: sharedLinksEnabled ?? this.sharedLinksEnabled, + tagsEnabled: tagsEnabled ?? this.tagsEnabled, + userAvatarColor: userAvatarColor ?? this.userAvatarColor, + showSupportBadge: showSupportBadge ?? this.showSupportBadge, + ); + } + + Map toMap() { + final preferences = {}; + preferences["folders-Enabled"] = foldersEnabled; + preferences["memories-Enabled"] = memoriesEnabled; + preferences["people-Enabled"] = peopleEnabled; + preferences["ratings-Enabled"] = ratingsEnabled; + preferences["sharedLinks-Enabled"] = sharedLinksEnabled; + preferences["tags-Enabled"] = tagsEnabled; + preferences["avatar-Color"] = userAvatarColor.value; + preferences["purchase-ShowSupportBadge"] = showSupportBadge; + return preferences; + } factory Preferences.fromMap(Map map) { return Preferences( @@ -51,39 +113,155 @@ abstract class Preferences with _$Preferences { orElse: () => AvatarColor.primary, ), showSupportBadge: (map["purchase"] as Map?)?["showSupportBadge"] as bool? ?? true, - minimumFaces: (map["people"] as Map?)?["minimumFaces"] as int? ?? 3, ); } + + @override + String toString() { + return '''Preferences: { +foldersEnabled: $foldersEnabled, +memoriesEnabled: $memoriesEnabled, +peopleEnabled: $peopleEnabled, +ratingsEnabled: $ratingsEnabled, +sharedLinksEnabled: $sharedLinksEnabled, +tagsEnabled: $tagsEnabled, +userAvatarColor: $userAvatarColor, +showSupportBadge: $showSupportBadge, +}'''; + } + + @override + bool operator ==(covariant Preferences other) { + if (identical(this, other)) return true; + + return other.foldersEnabled == foldersEnabled && + other.memoriesEnabled == memoriesEnabled && + other.peopleEnabled == peopleEnabled && + other.ratingsEnabled == ratingsEnabled && + other.sharedLinksEnabled == sharedLinksEnabled && + other.tagsEnabled == tagsEnabled && + other.userAvatarColor == userAvatarColor && + other.showSupportBadge == showSupportBadge; + } + + @override + int get hashCode { + return foldersEnabled.hashCode ^ + memoriesEnabled.hashCode ^ + peopleEnabled.hashCode ^ + ratingsEnabled.hashCode ^ + sharedLinksEnabled.hashCode ^ + tagsEnabled.hashCode ^ + userAvatarColor.hashCode ^ + showSupportBadge.hashCode; + } } -@freezed -abstract class License with _$License { - const License._(); +class License { + final DateTime activatedAt; + final String activationKey; + final String licenseKey; - const factory License({required DateTime activatedAt, required String activationKey, required String licenseKey}) = - _License; + const License({required this.activatedAt, required this.activationKey, required this.licenseKey}); + + License copyWith({DateTime? activatedAt, String? activationKey, String? licenseKey}) { + return License( + activatedAt: activatedAt ?? this.activatedAt, + activationKey: activationKey ?? this.activationKey, + licenseKey: licenseKey ?? this.licenseKey, + ); + } + + Map toMap() { + final license = {}; + license["activatedAt"] = activatedAt; + license["activationKey"] = activationKey; + license["licenseKey"] = licenseKey; + return license; + } factory License.fromMap(Map map) { return License( - activatedAt: DateTime.parse(map["activatedAt"]! as String), - activationKey: map["activationKey"]! as String, - licenseKey: map["licenseKey"]! as String, + activatedAt: DateTime.parse(map["activatedAt"] as String), + activationKey: map["activationKey"] as String, + licenseKey: map["licenseKey"] as String, ); } + + @override + String toString() { + return '''License { +activatedAt: $activatedAt, +activationKey: $activationKey, +licenseKey: $licenseKey, +}'''; + } + + @override + bool operator ==(covariant License other) { + if (identical(this, other)) return true; + + return activatedAt == other.activatedAt && activationKey == other.activationKey && licenseKey == other.licenseKey; + } + + @override + int get hashCode => activatedAt.hashCode ^ activationKey.hashCode ^ licenseKey.hashCode; } // Model for a user metadata stored in the server -@freezed -abstract class UserMetadata with _$UserMetadata { - @Assert( - 'onboarding != null || preferences != null || license != null', - 'One of onboarding, preferences and license must be provided', - ) - const factory UserMetadata({ - required String userId, - required UserMetadataKey key, +class UserMetadata { + final String userId; + final UserMetadataKey key; + final Onboarding? onboarding; + final Preferences? preferences; + final License? license; + + const UserMetadata({required this.userId, required this.key, this.onboarding, this.preferences, this.license}) + : assert( + onboarding != null || preferences != null || license != null, + 'One of onboarding, preferences and license must be provided', + ); + + UserMetadata copyWith({ + String? userId, + UserMetadataKey? key, Onboarding? onboarding, Preferences? preferences, License? license, - }) = _UserMetadata; + }) { + return UserMetadata( + userId: userId ?? this.userId, + key: key ?? this.key, + onboarding: onboarding ?? this.onboarding, + preferences: preferences ?? this.preferences, + license: license ?? this.license, + ); + } + + @override + String toString() { + return '''UserMetadata: { +userId: $userId, +key: $key, +onboarding: ${onboarding ?? ""}, +preferences: ${preferences ?? ""}, +license: ${license ?? ""}, +}'''; + } + + @override + bool operator ==(covariant UserMetadata other) { + if (identical(this, other)) return true; + + return other.userId == userId && + other.key == key && + other.onboarding == onboarding && + other.preferences == preferences && + other.license == license; + } + + @override + int get hashCode { + return userId.hashCode ^ key.hashCode ^ onboarding.hashCode ^ preferences.hashCode ^ license.hashCode; + } } diff --git a/mobile/lib/domain/models/value_codec.dart b/mobile/lib/domain/models/value_codec.dart deleted file mode 100644 index 814ef1b926..0000000000 --- a/mobile/lib/domain/models/value_codec.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'dart:convert'; - -import 'package:immich_mobile/utils/semver.dart'; - -sealed class ValueCodec { - const ValueCodec(); - - String encode(T value); - T decode(String raw); - - static final Map> _primitives = { - ..._register(PrimitiveCodec.integer), - ..._register(PrimitiveCodec.real), - ..._register(PrimitiveCodec.boolean), - ..._register(PrimitiveCodec.string), - ..._register(const DateTimeCodec()), - }; - - static Map> _register(ValueCodec codec) => { - T: codec, - // Reifies the nullable type T so it can be used as a key in the _primitives map - _typeOf(): codec, - }; - - static Type _typeOf() => T; - - static ValueCodec forType(Type runtimeType) { - final codec = _primitives[runtimeType]; - if (codec == null) { - throw StateError('No primitive codec for $runtimeType. Provide an explicit codec when defining the key.'); - } - return codec as ValueCodec; - } -} - -final class EnumCodec extends ValueCodec { - final List values; - - const EnumCodec(this.values); - - @override - String encode(T value) => value.name; - - @override - T decode(String raw) => values.firstWhere((v) => v.name == raw); -} - -final class DateTimeCodec extends ValueCodec { - const DateTimeCodec(); - - @override - String encode(DateTime value) => value.toIso8601String(); - - @override - DateTime decode(String raw) => DateTime.parse(raw); -} - -final class SemVerCodec extends ValueCodec { - const SemVerCodec(); - - @override - String encode(SemVer value) => value.toString(); - - @override - SemVer decode(String raw) => SemVer.fromString(raw); -} - -final class MapCodec extends ValueCodec> { - final ValueCodec _keyCodec; - final ValueCodec _valueCodec; - - const MapCodec(this._keyCodec, this._valueCodec); - - @override - String encode(Map value) { - final entries = {}; - value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v)); - return jsonEncode(entries); - } - - @override - Map decode(String raw) { - try { - final decoded = jsonDecode(raw); - if (decoded is! Map) { - return {}; - } - final result = {}; - for (final entry in decoded.entries) { - final rawKey = entry.key; - final rawValue = entry.value; - if (rawKey is! String || rawValue is! String) { - continue; - } - final k = _keyCodec.decode(rawKey); - final v = _valueCodec.decode(rawValue); - result[k] = v; - } - return result; - } on FormatException { - return {}; - } - } -} - -final class ListCodec extends ValueCodec> { - final ValueCodec _elementCodec; - - const ListCodec(this._elementCodec); - - @override - String encode(List value) => jsonEncode(value.map(_elementCodec.encode).toList()); - - @override - List decode(String raw) { - try { - final decoded = jsonDecode(raw); - if (decoded is! List) { - return []; - } - final result = []; - for (final item in decoded) { - if (item is! String) { - return []; - } - final element = _elementCodec.decode(item); - result.add(element); - } - return result; - } on FormatException { - return []; - } - } -} - -final class PrimitiveCodec extends ValueCodec { - final T Function(String) _parse; - - const PrimitiveCodec._(this._parse); - - @override - String encode(T value) => value.toString(); - - @override - T decode(String raw) => _parse(raw); - - static const integer = PrimitiveCodec._(int.parse); - static const real = PrimitiveCodec._(double.parse); - static const boolean = PrimitiveCodec._(bool.parse); - static const string = PrimitiveCodec._(_identity); - - static String _identity(String s) => s; -} diff --git a/mobile/lib/domain/services/asset.service.dart b/mobile/lib/domain/services/asset.service.dart index 1693d74990..198733b3c8 100644 --- a/mobile/lib/domain/services/asset.service.dart +++ b/mobile/lib/domain/services/asset.service.dart @@ -1,56 +1,42 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/asset_api.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:maplibre_gl/maplibre_gl.dart'; + +typedef _AssetVideoDimension = ({double? width, double? height, bool isFlipped}); class AssetService { - final RemoteAssetRepository _remoteRepository; - final RemoteExifRepository _exifRepository; - final LocalAssetRepository _localRepository; - final AssetApiRepository _apiRepository; - final AssetMediaRepository _mediaRepository; - final TrashedLocalAssetRepository _trashedLocalRepository; + final RemoteAssetRepository _remoteAssetRepository; + final DriftLocalAssetRepository _localAssetRepository; const AssetService({ - required this._remoteRepository, - required this._exifRepository, - required this._localRepository, - required this._apiRepository, - required this._mediaRepository, - required this._trashedLocalRepository, - }); + required RemoteAssetRepository remoteAssetRepository, + required DriftLocalAssetRepository localAssetRepository, + }) : _remoteAssetRepository = remoteAssetRepository, + _localAssetRepository = localAssetRepository; Future getAsset(BaseAsset asset) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id; - return asset is LocalAsset ? _localRepository.get(id) : _remoteRepository.get(id); + return asset is LocalAsset ? _localAssetRepository.get(id) : _remoteAssetRepository.get(id); } Stream watchAsset(BaseAsset asset) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).id; - return asset is LocalAsset ? _localRepository.watch(id) : _remoteRepository.watch(id); + return asset is LocalAsset ? _localAssetRepository.watch(id) : _remoteAssetRepository.watch(id); } Future> getLocalAssetsByChecksum(String checksum) { - return _localRepository.getByChecksum(checksum); + return _localAssetRepository.getByChecksum(checksum); } - Future> getAllRemoteAssetDebugByChecksum(String checksum) { - return _remoteRepository.getAllDebugForChecksum(checksum); + Future getRemoteAssetByChecksum(String checksum) { + return _remoteAssetRepository.getByChecksum(checksum); } Future getRemoteAsset(String id) { - return _remoteRepository.get(id); + return _remoteAssetRepository.get(id); } Future> getStack(RemoteAsset asset) async { @@ -58,7 +44,7 @@ class AssetService { return const []; } - final stack = await _remoteRepository.getStackChildren(asset); + final stack = await _remoteAssetRepository.getStackChildren(asset); // Include the primary asset in the stack as the first item return [asset, ...stack]; } @@ -69,132 +55,65 @@ class AssetService { } final id = asset is LocalAsset ? asset.remoteId! : (asset as RemoteAsset).id; - return _remoteRepository.getExif(id); + return _remoteAssetRepository.getExif(id); + } + + Future getAspectRatio(BaseAsset asset) async { + final dimension = asset is LocalAsset + ? await _getLocalAssetDimensions(asset) + : await _getRemoteAssetDimensions(asset as RemoteAsset); + + if (dimension.width == null || dimension.height == null || dimension.height == 0) { + return 1.0; + } + + return dimension.isFlipped ? dimension.height! / dimension.width! : dimension.width! / dimension.height!; + } + + Future<_AssetVideoDimension> _getLocalAssetDimensions(LocalAsset asset) async { + double? width = asset.width?.toDouble(); + double? height = asset.height?.toDouble(); + int orientation = asset.orientation; + + if (width == null || height == null) { + final fetched = await _localAssetRepository.get(asset.id); + width = fetched?.width?.toDouble(); + height = fetched?.height?.toDouble(); + orientation = fetched?.orientation ?? 0; + } + + // On Android, local assets need orientation correction for 90°/270° rotations + // On iOS, the Photos framework pre-corrects dimensions + final isFlipped = CurrentPlatform.isAndroid && (orientation == 90 || orientation == 270); + return (width: width, height: height, isFlipped: isFlipped); + } + + Future<_AssetVideoDimension> _getRemoteAssetDimensions(RemoteAsset asset) async { + double? width = asset.width?.toDouble(); + double? height = asset.height?.toDouble(); + + if (width == null || height == null) { + final fetched = await _remoteAssetRepository.get(asset.id); + width = fetched?.width?.toDouble(); + height = fetched?.height?.toDouble(); + } + + return (width: width, height: height, isFlipped: false); } Future> getPlaces(String userId) { - return _remoteRepository.getPlaces(userId); + return _remoteAssetRepository.getPlaces(userId); } Future<(int local, int remote)> getAssetCounts() async { - return (await _localRepository.getCount(), await _remoteRepository.getCount()); + return (await _localAssetRepository.getCount(), await _remoteAssetRepository.getCount()); } Future getLocalHashedCount() { - return _localRepository.getHashedCount(); + return _localAssetRepository.getHashedCount(); } Future> getSourceAlbums(String localAssetId, {BackupSelection? backupSelection}) { - return _localRepository.getSourceAlbums(localAssetId, backupSelection: backupSelection); - } - - Future restoreTrash(List remoteIds) async { - if (remoteIds.isEmpty) { - return; - } - - await _apiRepository.restoreTrash(remoteIds); - await _remoteRepository.restoreTrash(remoteIds); - } - - Future stack(String userId, List remoteIds) async { - if (remoteIds.isEmpty) { - return; - } - - final stack = await _apiRepository.stack(remoteIds); - await _remoteRepository.stack(userId, stack); - } - - Future unstack(List stackIds) async { - if (stackIds.isEmpty) { - return; - } - - await _remoteRepository.unStack(stackIds); - await _apiRepository.unStack(stackIds); - } - - Future update( - List remoteIds, { - Option isFavorite = const .none(), - Option visibility = const .none(), - Option location = const .none(), - Option dateTime = const .none(), - }) async { - if (remoteIds.isEmpty) { - return; - } - - final parsedDateTime = dateTime.map((dt) => DateTime.parse(dt)); - final offset = RegExp(r'[+-]\d{2}:\d{2}$').firstMatch(dateTime.unwrapOrNull ?? '')?.group(0); - - await _apiRepository.update( - remoteIds, - isFavorite: isFavorite, - visibility: visibility, - location: location, - dateTimeOriginal: dateTime, - ); - await _remoteRepository.updateAssets( - remoteIds, - isFavorite: isFavorite, - visibility: visibility, - createdAt: parsedDateTime, - ); - await _exifRepository.updateExif( - remoteIds, - location: location, - dateTimeOriginal: parsedDateTime, - timeZone: .fromNullable(offset).map((o) => 'UTC$o'), - ); - } - - Future trash(List remoteIds) async { - if (remoteIds.isEmpty) { - return; - } - - await _apiRepository.delete(remoteIds, false); - await _remoteRepository.trash(remoteIds); - } - - Future delete(List remoteIds) async { - if (remoteIds.isEmpty) { - return; - } - - await _apiRepository.delete(remoteIds, true); - await _remoteRepository.deleteAssets(remoteIds); - } - - Future applyEdits(String remoteId, List edits) async { - if (edits.isEmpty) { - await _apiRepository.removeEdits(remoteId); - } else { - await _apiRepository.editAsset(remoteId, edits); - } - } - - Future deleteLocal(List localIds, {bool trash = true}) async { - if (localIds.isEmpty) { - return 0; - } - - final deletedIds = await _mediaRepository.deleteAll(localIds, trash: trash); - if (deletedIds.isEmpty) { - return 0; - } - - if (trash && CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _trashedLocalRepository.applyTrashedAssets(deletedIds); - } else { - await _localRepository.deleteAssets(deletedIds); - } - return deletedIds.length; - } - - Future getLocalAsset(String id) { - return _localRepository.get(id); + return _localAssetRepository.getSourceAlbums(localAssetId, backupSelection: backupSelection); } } diff --git a/mobile/lib/domain/services/background_worker.service.dart b/mobile/lib/domain/services/background_worker.service.dart index 817cc33380..93a2a14127 100644 --- a/mobile/lib/domain/services/background_worker.service.dart +++ b/mobile/lib/domain/services/background_worker.service.dart @@ -6,31 +6,29 @@ import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/services/hash.service.dart'; -import 'package:immich_mobile/domain/services/local_sync.service.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; -import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/localization.service.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/wm_executor.dart'; +import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; class BackgroundWorkerFgService { @@ -44,68 +42,47 @@ class BackgroundWorkerFgService { Future saveNotificationMessage(String title, String body) => _foregroundHostApi.saveNotificationMessage(title, body); - Future configure({int? minimumDelaySeconds, bool? requireCharging}) { - final backup = SettingsRepository.instance.appConfig.backup; - return _foregroundHostApi.configure( - BackgroundWorkerSettings( - minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay, - requiresCharging: requireCharging ?? backup.requireCharging, - ), - ); - } + Future configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure( + BackgroundWorkerSettings( + minimumDelaySeconds: + minimumDelaySeconds ?? + Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue), + requiresCharging: + requireCharging ?? + Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue), + ), + ); Future disable() => _foregroundHostApi.disable(); } class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { ProviderContainer? _ref; + final Isar _isar; final Drift _drift; final DriftLogger _driftLogger; final BackgroundWorkerBgHostApi _backgroundHostApi; final _cancellationToken = Completer(); final Logger _logger = Logger('BackgroundWorkerBgService'); - late LocalSyncService _localSyncService; - late SyncStreamService _remoteSyncService; - late HashService _hashService; bool _isCleanedUp = false; - BackgroundWorkerBgService({required this._drift, required this._driftLogger}) - : _backgroundHostApi = BackgroundWorkerBgHostApi() { - final ref = ProviderContainer(overrides: [driftProvider.overrideWith(driftOverride(_drift))]); - _ref = ref; - final db = ref.read(driftProvider); - _localSyncService = LocalSyncService( - localAlbumRepository: db.localAlbumRepository, - localAssetRepository: db.localAssetRepository, - nativeSyncApi: ref.read(nativeSyncApiProvider), - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - assetMediaRepository: ref.read(assetMediaRepositoryProvider), - permissionRepository: ref.read(permissionRepositoryProvider), - cancellation: _cancellationToken, - ); - _remoteSyncService = SyncStreamService( - syncApiRepository: ref.read(syncApiRepositoryProvider), - syncStreamRepository: db.syncStreamRepository, - localAssetRepository: db.localAssetRepository, - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - assetMediaRepository: ref.read(assetMediaRepositoryProvider), - permissionRepository: ref.read(permissionRepositoryProvider), - syncMigrationRepository: db.syncMigrationRepository, - api: ref.read(apiServiceProvider), - cancellation: _cancellationToken, - ); - _hashService = HashService( - localAlbumRepository: db.localAlbumRepository, - localAssetRepository: db.localAssetRepository, - nativeSyncApi: ref.read(nativeSyncApiProvider), - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - cancellation: _cancellationToken, + BackgroundWorkerBgService({required Isar isar, required Drift drift, required DriftLogger driftLogger}) + : _isar = isar, + _drift = drift, + _driftLogger = driftLogger, + _backgroundHostApi = BackgroundWorkerBgHostApi() { + _ref = ProviderContainer( + overrides: [ + dbProvider.overrideWithValue(isar), + isarProvider.overrideWithValue(isar), + driftProvider.overrideWith(driftOverride(drift)), + ], ); BackgroundWorkerFlutterApi.setUp(this); } - bool get _isBackupEnabled => SettingsRepository.instance.appConfig.backup.enabled; + bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false; Future init() async { try { @@ -125,6 +102,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { ), FileDownloader().trackTasksInGroup(kDownloadGroupLivePhoto, markDownloadedComplete: false), FileDownloader().trackTasks(), + _ref?.read(fileMediaRepositoryProvider).enableBackgroundAccess(), ].nonNulls, ); @@ -139,15 +117,22 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } @override - Future onAndroidUpload(int? maxMinutes) async { - final hashTimeout = Duration(minutes: _isBackupEnabled ? 3 : 6); - final backupTimeout = maxMinutes != null ? Duration(minutes: maxMinutes - 1) : null; - await _optimizeDB(); - return _backgroundLoop( - hashTimeout: hashTimeout, - backupTimeout: backupTimeout, - debugLabel: 'Android background upload', - ); + Future onAndroidUpload() async { + _logger.info('Android background processing started'); + final sw = Stopwatch()..start(); + try { + if (!await _syncAssets(hashTimeout: Duration(minutes: _isBackupEnabled ? 3 : 6))) { + _logger.warning("Remote sync did not complete successfully, skipping backup"); + return; + } + await _handleBackup(); + } catch (error, stack) { + _logger.severe("Failed to complete Android background processing", error, stack); + } finally { + sw.stop(); + _logger.info("Android background processing completed in ${sw.elapsed.inSeconds}s"); + await _cleanup(); + } } @override @@ -155,38 +140,17 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { _logger.info('iOS background upload started with maxSeconds: ${maxSeconds}s'); final sw = Stopwatch()..start(); try { - final budget = maxSeconds != null ? Duration(seconds: maxSeconds - 1) : null; - - // Only for Background Processing tasks - if (maxSeconds == null) { - await _optimizeDB(); + final timeout = isRefresh ? const Duration(seconds: 5) : Duration(minutes: _isBackupEnabled ? 3 : 6); + if (!await _syncAssets(hashTimeout: timeout)) { + _logger.warning("Remote sync did not complete successfully, skipping backup"); + return; } - // Run sync local, sync remote, hash and backup concurrently so the bg - // refresh task (20s budget) can make progress on all four instead of - // racing them sequentially. Phases are independent at the data layer: - // hash and handle_backup read drift state and tolerate stale reads - // (server-side dedup catches the rare race). The single budget caps the - // whole batch; no phase needs its own timeout. - final all = Future.wait([ - _localSyncService.sync(), - _remoteSyncService.sync(), - _hashService.hashAssets(), - _handleBackup(), - ]); - if (budget != null) { - await all.timeout( - budget, - onTimeout: () { - if (!_cancellationToken.isCompleted) { - _logger.warning("iOS background upload timed out after ${budget.inSeconds}s, cancelling tasks"); - _cancellationToken.complete(); - } - return []; - }, - ); + final backupFuture = _handleBackup(); + if (maxSeconds != null) { + await backupFuture.timeout(Duration(seconds: maxSeconds - 1), onTimeout: () {}); } else { - await all; + await backupFuture; } } catch (error, stack) { _logger.severe("Failed to complete iOS background upload", error, stack); @@ -197,45 +161,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } } - Future _backgroundLoop({ - required Duration hashTimeout, - required Duration? backupTimeout, - required String debugLabel, - }) async { - _logger.info( - '$debugLabel started hashTimeout: ${hashTimeout.inSeconds}s, backupTimeout: ${backupTimeout?.inMinutes ?? '~'}m', - ); - final sw = Stopwatch()..start(); - try { - if (!await _syncAssets(hashTimeout: hashTimeout)) { - _logger.warning("Remote sync did not complete successfully, skipping backup"); - return; - } - - final backupFuture = _handleBackup(); - Timer? cancelTimer; - if (backupTimeout != null) { - cancelTimer = Timer(backupTimeout, () { - if (!_cancellationToken.isCompleted) { - _logger.warning("$debugLabel timed out after ${backupTimeout.inMinutes}m, cancelling backup"); - _cancellationToken.complete(); - } - }); - } - try { - await backupFuture; - } finally { - cancelTimer?.cancel(); - } - } catch (error, stack) { - _logger.severe("Failed to complete $debugLabel", error, stack); - } finally { - sw.stop(); - _logger.info("$debugLabel completed in ${sw.elapsed.inSeconds}s"); - await _cleanup(); - } - } - @override Future cancel() async { _logger.warning("Background worker cancelled"); @@ -246,14 +171,6 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } } - Future _optimizeDB() async { - try { - await (_drift.optimize(allTables: true), _driftLogger.optimize()).wait; - } catch (error, stack) { - dPrint(() => "Error during background worker optimize: $error, $stack"); - } - } - Future _cleanup() async { await runZonedGuarded(_handleCleanup, (error, stack) { dPrint(() => "Error during background worker cleanup: $error, $stack"); @@ -268,22 +185,35 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { try { _isCleanedUp = true; + final backgroundSyncManager = _ref?.read(backgroundSyncProvider); final nativeSyncApi = _ref?.read(nativeSyncApiProvider); - _logger.info("Cleaning up background worker"); - if (!_cancellationToken.isCompleted) { - _cancellationToken.complete(); - } - - // Workers share one sqlite connection, so DB teardown must wait until every worker has stopped using it. - await Future.wait([if (nativeSyncApi != null) nativeSyncApi.cancelHashing()]); - await workerManagerPatch.dispose().catchError((_) async {}); - await Future.wait([LogService.I.dispose(), Store.dispose()]); await _drift.close(); await _driftLogger.close(); _ref?.dispose(); _ref = null; + + _cancellationToken.complete(); + _logger.info("Cleaning up background worker"); + + final cleanupFutures = [ + nativeSyncApi?.cancelHashing(), + workerManagerPatch.dispose().catchError((_) async { + // Discard any errors on the dispose call + return; + }), + LogService.I.dispose(), + Store.dispose(), + + backgroundSyncManager?.cancel(), + ]; + + if (_isar.isOpen) { + cleanupFutures.add(_isar.close()); + } + await Future.wait(cleanupFutures.nonNulls); + _logger.info("Background worker resources cleaned up"); } catch (error, stack) { dPrint(() => 'Failed to cleanup background worker: $error with stack: $stack'); } @@ -322,18 +252,18 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi { } Future _syncAssets({Duration? hashTimeout}) async { - await _localSyncService.sync(); + await _ref?.read(backgroundSyncProvider).syncLocal(); if (_isCleanedUp) { return false; } - final isSuccess = await _remoteSyncService.sync(); + final isSuccess = await _ref?.read(backgroundSyncProvider).syncRemote() ?? false; if (_isCleanedUp) { return isSuccess; } - var hashFuture = _hashService.hashAssets(); - if (hashTimeout != null) { + var hashFuture = _ref?.read(backgroundSyncProvider).hashAssets(); + if (hashTimeout != null && hashFuture != null) { hashFuture = hashFuture.timeout( hashTimeout, onTimeout: () { @@ -371,6 +301,7 @@ Future backgroundSyncNativeEntrypoint() async { WidgetsFlutterBinding.ensureInitialized(); DartPluginRegistrant.ensureInitialized(); - final (drift, logDB) = await Bootstrap.initDomain(shouldBufferLogs: false, listenStoreUpdates: false); - await BackgroundWorkerBgService(drift: drift, driftLogger: logDB).init(); + final (isar, drift, logDB) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDB, shouldBufferLogs: false, listenStoreUpdates: false); + await BackgroundWorkerBgService(isar: isar, drift: drift, driftLogger: logDB).init(); } diff --git a/mobile/lib/domain/services/device_permission.service.dart b/mobile/lib/domain/services/device_permission.service.dart deleted file mode 100644 index d6ce128b8a..0000000000 --- a/mobile/lib/domain/services/device_permission.service.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; - -typedef _Handler = Future Function(DevicePermission); - -class DevicePermissionService { - const DevicePermissionService(this._permissionRepository); - - final DevicePermissionRepository _permissionRepository; - - Future galleryStatus() => _gallery(_permissionRepository.getStatus); - - Future requestGallery() => _gallery(_permissionRepository.request); - - Future _gallery(_Handler handler) async { - if (CurrentPlatform.isIOS) { - return handler(.photos); - } - - final sdkVersion = await _permissionRepository.getAndroidSdkVersion(); - const maxExternalStorageSdk = 32; // READ/WRITE_EXTERNAL_STORAGE - Android 12.1 - final status = sdkVersion <= maxExternalStorageSdk ? await handler(.storage) : await _photosAndVideos(handler); - - const minMediaLocationSdk = 29; // ACCESS_MEDIA_LOCATION - Android 10 - if (status.hasAccess && sdkVersion >= minMediaLocationSdk) { - final mediaLocation = await handler(.mediaLocation); - return mediaLocation.hasAccess ? status : mediaLocation; - } - - return status; - } - - Future _photosAndVideos(_Handler handler) async { - final photos = await handler(.photos); - if (!photos.hasAccess) { - return photos; - } - - final videos = await handler(.videos); - if (!videos.hasAccess) { - return videos; - } - - return photos == .granted && videos == .granted ? .granted : .limited; - } -} diff --git a/mobile/lib/domain/services/feature_message.service.dart b/mobile/lib/domain/services/feature_message.service.dart deleted file mode 100644 index 5eb58f0823..0000000000 --- a/mobile/lib/domain/services/feature_message.service.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:immich_mobile/domain/models/feature_message.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; - -class FeatureMessageService { - final SettingsRepository _settingsRepository; - - const FeatureMessageService(this._settingsRepository); - - bool shouldShow() { - final seen = _settingsRepository.appConfig.featureMessage.seenRelease; - return visibleFeatureMessageHighlights.isNotEmpty && featureMessageRelease > seen; - } - - Future markSeen() => _settingsRepository.write(SettingsKey.featureMessageSeenRelease, featureMessageRelease); -} diff --git a/mobile/lib/domain/services/hash.service.dart b/mobile/lib/domain/services/hash.service.dart index 64595aff2b..6781507566 100644 --- a/mobile/lib/domain/services/hash.service.dart +++ b/mobile/lib/domain/services/hash.service.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/services.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; @@ -15,27 +13,28 @@ const String _kHashCancelledCode = "HASH_CANCELLED"; class HashService { final int _batchSize; - final LocalAlbumRepository _localAlbumRepository; - final LocalAssetRepository _localAssetRepository; - final TrashedLocalAssetRepository _trashedLocalAssetRepository; + final DriftLocalAlbumRepository _localAlbumRepository; + final DriftLocalAssetRepository _localAssetRepository; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; final NativeSyncApi _nativeSyncApi; - final Completer? _cancellation; + final bool Function()? _cancelChecker; final _log = Logger('HashService'); HashService({ - required this._localAlbumRepository, - required this._localAssetRepository, - required this._trashedLocalAssetRepository, - required this._nativeSyncApi, - this._cancellation, + required DriftLocalAlbumRepository localAlbumRepository, + required DriftLocalAssetRepository localAssetRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, + required NativeSyncApi nativeSyncApi, + bool Function()? cancelChecker, int? batchSize, - }) : _batchSize = batchSize ?? kBatchHashFileLimit { - // Stop the in-flight native hash call promptly on cancellation; the loops - // below also observe [isCancelled] to bail between batches. - unawaited(_cancellation?.future.then((_) => _nativeSyncApi.cancelHashing().onError(_log.warning))); - } + }) : _localAlbumRepository = localAlbumRepository, + _localAssetRepository = localAssetRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, + _cancelChecker = cancelChecker, + _nativeSyncApi = nativeSyncApi, + _batchSize = batchSize ?? kBatchHashFileLimit; - bool get isCancelled => _cancellation?.isCompleted ?? false; + bool get isCancelled => _cancelChecker?.call() ?? false; Future hashAssets() async { _log.info("Starting hashing of assets"); @@ -66,12 +65,11 @@ class HashService { await _hashAssets(pseudoAlbum, trashedToHash, isTrashed: true); } } - } on PlatformException catch (e, s) { + } on PlatformException catch (e) { if (e.code == _kHashCancelledCode) { _log.warning("Hashing cancelled by platform"); return; } - _log.severe("Native hashing failed: ${e.code}", e, s); } catch (e, s) { _log.severe("Error during hashing", e, s); } diff --git a/mobile/lib/domain/services/local_album.service.dart b/mobile/lib/domain/services/local_album.service.dart index 8661a8d10a..e3d888f063 100644 --- a/mobile/lib/domain/services/local_album.service.dart +++ b/mobile/lib/domain/services/local_album.service.dart @@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; class LocalAlbumService { - final LocalAlbumRepository _repository; + final DriftLocalAlbumRepository _repository; const LocalAlbumService(this._repository); diff --git a/mobile/lib/domain/services/local_sync.service.dart b/mobile/lib/domain/services/local_sync.service.dart index 0793d191b0..029482978a 100644 --- a/mobile/lib/domain/services/local_sync.service.dart +++ b/mobile/lib/domain/services/local_sync.service.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; @@ -10,46 +9,43 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/utils/datetime_helpers.dart'; import 'package:immich_mobile/utils/diff.dart'; import 'package:logging/logging.dart'; -const String _kSyncCancelledCode = "SYNC_CANCELLED"; - class LocalSyncService { - final LocalAlbumRepository _localAlbumRepository; + final DriftLocalAlbumRepository _localAlbumRepository; // ignore: unused_field - final LocalAssetRepository _localAssetRepository; + final DriftLocalAssetRepository _localAssetRepository; final NativeSyncApi _nativeSyncApi; - final TrashedLocalAssetRepository _trashedLocalAssetRepository; - final AssetMediaRepository _assetMediaRepository; - final DevicePermissionRepository _permissionRepository; - final Completer? _cancellation; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; + final LocalFilesManagerRepository _localFilesManager; + final StorageRepository _storageRepository; final Logger _log = Logger("DeviceSyncService"); LocalSyncService({ - required this._localAlbumRepository, - required this._localAssetRepository, - required this._nativeSyncApi, - required this._trashedLocalAssetRepository, - required this._assetMediaRepository, - required this._permissionRepository, - this._cancellation, - }) { - unawaited(_cancellation?.future.then((_) => _nativeSyncApi.cancelSync().onError(_log.warning))); - } - - bool get _isCancelled => _cancellation?.isCompleted ?? false; + required DriftLocalAlbumRepository localAlbumRepository, + required DriftLocalAssetRepository localAssetRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, + required LocalFilesManagerRepository localFilesManager, + required StorageRepository storageRepository, + required NativeSyncApi nativeSyncApi, + }) : _localAlbumRepository = localAlbumRepository, + _localAssetRepository = localAssetRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, + _localFilesManager = localFilesManager, + _storageRepository = storageRepository, + _nativeSyncApi = nativeSyncApi; Future sync({bool full = false}) async { final Stopwatch stopwatch = Stopwatch()..start(); try { if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - final hasPermission = await _permissionRepository.hasManageMediaPermission(); + final hasPermission = await _localFilesManager.hasManageMediaPermission(); if (hasPermission) { await _syncTrashedAssets(); } else { @@ -90,10 +86,6 @@ class LocalSyncService { // detect album deletions from the native side if (CurrentPlatform.isAndroid) { for (final album in dbAlbums) { - if (_isCancelled) { - _log.warning("Local sync cancelled. Stopped processing albums."); - return; - } final deviceIds = await _nativeSyncApi.getAssetIdsForAlbum(album.id); await _localAlbumRepository.syncDeletes(album.id, deviceIds); } @@ -101,13 +93,10 @@ class LocalSyncService { if (CurrentPlatform.isIOS) { // On iOS, we need to full sync albums that are marked as cloud as the delta sync - // does not include changes for cloud albums. + // does not include changes for cloud albums. If ignoreIcloudAssets is enabled, + // remove the albums from the local database from the previous sync final cloudAlbums = deviceAlbums.where((a) => a.isCloud).toLocalAlbums(); for (final album in cloudAlbums) { - if (_isCancelled) { - _log.warning("Local sync cancelled. Stopped processing cloud albums."); - return; - } final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id); if (dbAlbum == null) { _log.warning("Cloud album ${album.name} not found in local database. Skipping sync."); @@ -119,12 +108,6 @@ class LocalSyncService { await _mapIosCloudIds(newAssets); } await _nativeSyncApi.checkpointSync(); - } on PlatformException catch (e, s) { - if (e.code == _kSyncCancelledCode) { - _log.warning("Local sync cancelled"); - } else { - _log.severe("Error performing device sync", e, s); - } } catch (e, s) { _log.severe("Error performing device sync", e, s); } finally { @@ -152,21 +135,12 @@ class LocalSyncService { await _nativeSyncApi.checkpointSync(); stopwatch.stop(); _log.info("Full device sync took - ${stopwatch.elapsedMilliseconds}ms"); - } on PlatformException catch (e, s) { - if (e.code == _kSyncCancelledCode) { - _log.warning("Full device sync cancelled"); - } else { - _log.severe("Error performing full device sync", e, s); - } } catch (e, s) { _log.severe("Error performing full device sync", e, s); } } Future addAlbum(LocalAlbum album) async { - if (_isCancelled) { - return; - } try { _log.fine("Adding device album ${album.name}"); @@ -186,7 +160,7 @@ class LocalSyncService { _log.fine("Removing device album ${a.name}"); try { // Asset deletion is handled in the repository - await _localAlbumRepository.deleteAlbum(a.id); + await _localAlbumRepository.delete(a.id); } catch (e, s) { _log.warning("Error while removing device album", e, s); } @@ -194,9 +168,6 @@ class LocalSyncService { // The deviceAlbum is ignored since we are going to refresh it anyways FutureOr updateAlbum(LocalAlbum dbAlbum, LocalAlbum deviceAlbum) async { - if (_isCancelled) { - return false; - } try { _log.fine("Syncing device album ${dbAlbum.name}"); @@ -366,7 +337,7 @@ class LocalSyncService { a.createdAt.isAtSameMomentAs(b.createdAt) && a.width == b.width && a.height == b.height && - a.durationMs == b.durationMs; + a.durationInSeconds == b.durationInSeconds; } final firstAdjustment = a.adjustmentTime?.millisecondsSinceEpoch ?? 0; @@ -375,7 +346,7 @@ class LocalSyncService { a.createdAt.isAtSameMomentAs(b.createdAt) && a.width == b.width && a.height == b.height && - a.durationMs == b.durationMs && + a.durationInSeconds == b.durationInSeconds && a.latitude == b.latitude && a.longitude == b.longitude; } @@ -403,7 +374,7 @@ class LocalSyncService { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _log.info("syncTrashedAssets, No remote assets found for restoration"); @@ -411,15 +382,15 @@ class LocalSyncService { final localAssetsToTrash = await _trashedLocalAssetRepository.getToTrash(); if (localAssetsToTrash.isNotEmpty) { - final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); - _log.info("Moving to trash ${localIds.join(", ")} assets"); - final movedIds = await _assetMediaRepository.deleteAll(localIds); - if (movedIds.isNotEmpty) { - final movedAssetsByAlbum = localAssetsToTrash.map( - (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), - )..removeWhere((_, assets) => assets.isEmpty); - - await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); + final mediaUrls = await Future.wait( + localAssetsToTrash.values + .expand((e) => e) + .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), + ); + _log.info("Moving to trash ${mediaUrls.join(", ")} assets"); + final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + if (result) { + await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); } } else { _log.info("syncTrashedAssets, No assets found in backup-enabled albums for move to trash"); @@ -461,7 +432,7 @@ extension PlatformToLocalAsset on PlatformAsset { updatedAt: tryFromSecondsSinceEpoch(updatedAt, isUtc: true) ?? DateTime.timestamp(), width: width, height: height, - durationMs: durationMs, + durationInSeconds: durationInSeconds, isFavorite: isFavorite, orientation: orientation, playbackStyle: _toPlaybackStyle(playbackStyle), diff --git a/mobile/lib/domain/services/log.service.dart b/mobile/lib/domain/services/log.service.dart index b612b3ce91..64010b9220 100644 --- a/mobile/lib/domain/services/log.service.dart +++ b/mobile/lib/domain/services/log.service.dart @@ -2,20 +2,20 @@ import 'dart:async'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; /// Service responsible for handling application logging. /// /// It listens to Dart's [Logger.root], buffers logs in memory (optionally), -/// writes them to a persistent [LogRepository], and manages log levels via -/// [SettingsRepository]. +/// writes them to a persistent [ILogRepository], and manages log levels +/// via [IStoreRepository] class LogService { final LogRepository _logRepository; - final SettingsRepository _settingsRepository; + final IStoreRepository _storeRepository; final List _msgBuffer = []; @@ -38,12 +38,12 @@ class LogService { static Future init({ required LogRepository logRepository, - required SettingsRepository settingsRepository, + required IStoreRepository storeRepository, bool shouldBuffer = true, }) async { _instance ??= await create( logRepository: logRepository, - settingsRepository: settingsRepository, + storeRepository: storeRepository, shouldBuffer: shouldBuffer, ); return _instance!; @@ -51,17 +51,17 @@ class LogService { static Future create({ required LogRepository logRepository, - required SettingsRepository settingsRepository, + required IStoreRepository storeRepository, bool shouldBuffer = true, }) async { - final instance = LogService._(logRepository, settingsRepository, shouldBuffer); + final instance = LogService._(logRepository, storeRepository, shouldBuffer); await logRepository.truncate(limit: kLogTruncateLimit); - final level = instance._settingsRepository.appConfig.logLevel; - Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO; + final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index; + Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO; return instance; } - LogService._(this._logRepository, this._settingsRepository, this._shouldBuffer) { + LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) { _logSubscription = Logger.root.onRecord.listen(_handleLogRecord); } @@ -91,7 +91,7 @@ class LogService { } Future setLogLevel(LogLevel level) async { - await _settingsRepository.write(SettingsKey.logLevel, level); + await _storeRepository.upsert(StoreKey.logLevel, level.index); Logger.root.level = level.toLevel(); } @@ -112,16 +112,10 @@ class LogService { return _flushBuffer(); } - Future dispose() async { + Future dispose() { _flushTimer?.cancel(); - _flushTimer = null; - await _logSubscription.cancel(); - await _flushBuffer(); - // Allow a subsequent init() (e.g. when a worker isolate is reused) to - // create a fresh instance instead of returning this disposed one. - if (identical(_instance, this)) { - _instance = null; - } + _logSubscription.cancel(); + return _flushBuffer(); } Future _flushBuffer() async { diff --git a/mobile/lib/domain/services/map.service.dart b/mobile/lib/domain/services/map.service.dart index 5e0a3bd857..6c64e2817e 100644 --- a/mobile/lib/domain/services/map.service.dart +++ b/mobile/lib/domain/services/map.service.dart @@ -8,9 +8,9 @@ typedef MapMarkerSource = Future> Function(LatLngBounds? bounds); typedef MapQuery = ({MapMarkerSource markerSource}); class MapFactory { - final MapRepository _mapRepository; + final DriftMapRepository _mapRepository; - const MapFactory({required this._mapRepository}); + const MapFactory({required DriftMapRepository mapRepository}) : _mapRepository = mapRepository; MapService remote(List ownerIds, TimelineMapOptions options) => MapService(_mapRepository.remote(ownerIds, options)); diff --git a/mobile/lib/domain/services/memory.service.dart b/mobile/lib/domain/services/memory.service.dart index ccb865fb64..ead613370f 100644 --- a/mobile/lib/domain/services/memory.service.dart +++ b/mobile/lib/domain/services/memory.service.dart @@ -5,7 +5,7 @@ import 'package:logging/logging.dart'; class DriftMemoryService { final log = Logger("DriftMemoryService"); - final MemoryRepository _repository; + final DriftMemoryRepository _repository; DriftMemoryService(this._repository); diff --git a/mobile/lib/domain/services/ocr.service.dart b/mobile/lib/domain/services/ocr.service.dart deleted file mode 100644 index 2786b1ce22..0000000000 --- a/mobile/lib/domain/services/ocr.service.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:immich_mobile/domain/models/ocr.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart'; - -class OcrService { - final OcrRepository _repository; - - const OcrService(this._repository); - - Future?> get(String assetId) { - return _repository.get(assetId); - } -} diff --git a/mobile/lib/domain/services/partner.service.dart b/mobile/lib/domain/services/partner.service.dart index 3fd0d32683..ce1bd9557b 100644 --- a/mobile/lib/domain/services/partner.service.dart +++ b/mobile/lib/domain/services/partner.service.dart @@ -1,42 +1,51 @@ -import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; -import 'package:stream_transform/stream_transform.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; -class PartnerService { - final UserRepository _userRepository; - final PartnerRepository _partnerRepository; +class DriftPartnerService { + final DriftPartnerRepository _driftPartnerRepository; final PartnerApiRepository _partnerApiRepository; - const PartnerService(this._userRepository, this._partnerRepository, this._partnerApiRepository); + const DriftPartnerService(this._driftPartnerRepository, this._partnerApiRepository); - Stream> getCandidates(String userId) { - final userStream = _userRepository.getAll(); - final partnerStream = _partnerRepository.search(userId, .sharedBy); - - return userStream.combineLatest(partnerStream, (users, partners) { - final partnersSet = partners.map((partner) => partner.id).toSet(); - return users.where((user) => user.id != userId && !partnersSet.contains(user.id)); - }); + Future> getSharedWith(String userId) { + return _driftPartnerRepository.getSharedWith(userId); } - Stream> search(String userId, PartnerDirection direction) => - _partnerRepository.search(userId, direction); - - Future update({required String sharedById, required String sharedWithId, required bool inTimeline}) async { - await _partnerApiRepository.update(sharedById, inTimeline: inTimeline); - await _partnerRepository.updatePartner(sharedById: sharedById, sharedWithId: sharedWithId, inTimeline: inTimeline); + Future> getSharedBy(String userId) { + return _driftPartnerRepository.getSharedBy(userId); } - Future create({required String sharedById, required String sharedWithId, bool inTimeline = false}) async { - await _partnerApiRepository.create(sharedWithId); - await _partnerRepository.create(sharedById: sharedById, sharedWithId: sharedWithId, inTimeline: inTimeline); + Future> getAvailablePartners(String currentUserId) async { + final otherUsers = await _driftPartnerRepository.getAvailablePartners(currentUserId); + final currentPartners = await _driftPartnerRepository.getSharedBy(currentUserId); + final available = otherUsers.where((user) { + return !currentPartners.any((partner) => partner.id == user.id); + }).toList(); + + return available; } - Future delete({required String sharedById, required String sharedWithId}) async { - await _partnerApiRepository.delete(sharedWithId); - await _partnerRepository.deletePartner(sharedById: sharedById, sharedWithId: sharedWithId); + Future toggleShowInTimeline(String partnerId, String userId) async { + final partner = await _driftPartnerRepository.getPartner(partnerId, userId); + if (partner == null) { + dPrint(() => "Partner not found: $partnerId for user: $userId"); + return; + } + + await _partnerApiRepository.update(partnerId, inTimeline: !partner.inTimeline); + + await _driftPartnerRepository.toggleShowInTimeline(partner, userId); + } + + Future addPartner(String partnerId, String userId) async { + await _partnerApiRepository.create(partnerId); + await _driftPartnerRepository.create(partnerId, userId); + } + + Future removePartner(String partnerId, String userId) async { + await _partnerApiRepository.delete(partnerId); + await _driftPartnerRepository.delete(partnerId, userId); } } diff --git a/mobile/lib/domain/services/people.service.dart b/mobile/lib/domain/services/people.service.dart index e0323246b0..ecfe83e5cb 100644 --- a/mobile/lib/domain/services/people.service.dart +++ b/mobile/lib/domain/services/people.service.dart @@ -5,21 +5,21 @@ import 'package:immich_mobile/infrastructure/repositories/people.repository.dart import 'package:immich_mobile/repositories/person_api.repository.dart'; class DriftPeopleService { - final PeopleRepository _repository; + final DriftPeopleRepository _repository; final PersonApiRepository _personApiRepository; const DriftPeopleService(this._repository, this._personApiRepository); - Future get(String personId) { + Future get(String personId) { return _repository.get(personId); } - Future> getAssetPeople(String assetId) { + Future> getAssetPeople(String assetId) { return _repository.getAssetPeople(assetId); } - Stream> watch({int minFaces = 3}) { - return _repository.watch(minFaces: minFaces); + Future> getAllPeople() { + return _repository.getAllPeople(); } Future updateName(String personId, String name) async { @@ -27,7 +27,7 @@ class DriftPeopleService { return _repository.updateName(personId, name); } - Future updateBirthday(String personId, DateTime birthday) async { + Future updateBrithday(String personId, DateTime birthday) async { await _personApiRepository.update(personId, birthday: birthday); return _repository.updateBirthday(personId, birthday); } diff --git a/mobile/lib/domain/services/remote_album.service.dart b/mobile/lib/domain/services/remote_album.service.dart index 8e6a6c8aae..945ba8eb3f 100644 --- a/mobile/lib/domain/services/remote_album.service.dart +++ b/mobile/lib/domain/services/remote_album.service.dart @@ -7,49 +7,14 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; -import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:logging/logging.dart'; - -/// Categorizes a heterogeneous asset selection into the candidates that can -/// be added to an album immediately (already on the server) and the local-only -/// candidates that must be uploaded first. -class AlbumAssetCandidates { - final List remoteAssetIds; - final List localAssetsToUpload; - - const AlbumAssetCandidates({required this.remoteAssetIds, required this.localAssetsToUpload}); -} +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; class RemoteAlbumService { - static final _logger = Logger('RemoteAlbumService'); - - final RemoteAlbumRepository _repository; + final DriftRemoteAlbumRepository _repository; final DriftAlbumApiRepository _albumApiRepository; - final ForegroundUploadService _uploadService; - const RemoteAlbumService(this._repository, this._albumApiRepository, this._uploadService); - - /// Categorizes a heterogeneous asset selection into already-on-server IDs - /// and local assets that still need to be uploaded. - static AlbumAssetCandidates categorizeCandidates(Iterable assets) { - final remoteIds = []; - final localToUpload = []; - for (final asset in assets) { - if (asset is RemoteAsset) { - remoteIds.add(asset.id); - } else if (asset is LocalAsset) { - final remoteId = asset.remoteId; - if (remoteId != null) { - remoteIds.add(remoteId); - } else { - localToUpload.add(asset); - } - } - } - return AlbumAssetCandidates(remoteAssetIds: remoteIds, localAssetsToUpload: localToUpload); - } + const RemoteAlbumService(this._repository, this._albumApiRepository); Stream watchAlbum(String albumId) { return _repository.watchAlbum(albumId); @@ -63,6 +28,10 @@ class RemoteAlbumService { return _repository.get(albumId); } + Future getByName(String albumName, String ownerId) { + return _repository.getByName(albumName, ownerId); + } + Future> sortAlbums( List albums, AlbumSortMode sortMode, { @@ -105,8 +74,10 @@ class RemoteAlbumService { switch (filterMode) { case QuickFilterMode.myAlbums: filtered = filtered.where((album) => album.ownerId == userId).toList(); + break; case QuickFilterMode.sharedWithMe: filtered = filtered.where((album) => album.ownerId != userId).toList(); + break; case QuickFilterMode.all: break; } @@ -115,18 +86,8 @@ class RemoteAlbumService { return filtered; } - Future createAlbum({ - required String title, - required UserDto owner, - required List assetIds, - String? description, - }) async { - final album = await _albumApiRepository.createDriftAlbum( - title, - owner, - description: description, - assetIds: assetIds, - ); + Future createAlbum({required String title, required List assetIds, String? description}) async { + final album = await _albumApiRepository.createDriftAlbum(title, description: description, assetIds: assetIds); await _repository.create(album, assetIds); return album; @@ -140,10 +101,8 @@ class RemoteAlbumService { bool? isActivityEnabled, AlbumAssetOrder? order, }) async { - final owner = await _repository.getOwner(albumId); final updatedAlbum = await _albumApiRepository.updateAlbum( albumId, - owner, name: name, description: description, thumbnailAssetId: thumbnailAssetId, @@ -152,13 +111,13 @@ class RemoteAlbumService { ); // Update the local database - await _repository.updateAlbum(updatedAlbum); + await _repository.update(updatedAlbum); return updatedAlbum; } - Stream<(DateTime, DateTime)> watchDateRange(String albumId) { - return _repository.watchDateRange(albumId); + FutureOr<(DateTime, DateTime)> getDateRange(String albumId) { + return _repository.getDateRange(albumId); } Future> getSharedUsers(String albumId) { @@ -173,115 +132,12 @@ class RemoteAlbumService { return _repository.getAssets(albumId); } - Future<({int added, int failed})> addAssets({required String albumId, required List assetIds}) async { + Future addAssets({required String albumId, required List assetIds}) async { final album = await _albumApiRepository.addAssets(albumId, assetIds); await _repository.addAssets(albumId, album.added); - return (added: album.added.length, failed: album.failed.length); - } - - /// !TODO The name here is not clear as we have addAssets method above, - /// which is only add remote assets to album, for the next PR, we will allow - /// adding local assets from album from the timeline as well with this flow. - /// So saving that for the next refactor - Future addAssetsToAlbum({ - required String albumId, - required UserDto uploader, - required AlbumAssetCandidates candidates, - UploadCallbacks uploadCallbacks = const UploadCallbacks(), - Completer? cancelToken, - }) async { - int addedCount = 0; - if (candidates.remoteAssetIds.isNotEmpty) { - addedCount += (await addAssets(albumId: albumId, assetIds: candidates.remoteAssetIds)).added; - } - if (candidates.localAssetsToUpload.isNotEmpty) { - addedCount += await _uploadAndAddLocals( - albumId, - uploader, - candidates.localAssetsToUpload, - uploadCallbacks, - cancelToken, - ); - } - return addedCount; - } - - Future _uploadAndAddLocals( - String albumId, - UserDto uploader, - List localAssets, - UploadCallbacks userCallbacks, - Completer? cancelToken, - ) async { - int addedCount = 0; - final pendingAdds = >[]; - final localById = {for (final a in localAssets) a.id: a}; - - final wrappedCallbacks = UploadCallbacks( - onProgress: (localId, filename, bytes, totalBytes) => _runUploadCallback( - 'Upload progress callback failed for $localId', - () => userCallbacks.onProgress?.call(localId, filename, bytes, totalBytes), - ), - onICloudProgress: (localId, progress) => _runUploadCallback( - 'iCloud progress callback failed for $localId', - () => userCallbacks.onICloudProgress?.call(localId, progress), - ), - onError: (localId, errorMessage) => _runUploadCallback( - 'Upload error callback failed for $localId', - () => userCallbacks.onError?.call(localId, errorMessage), - ), - onSuccess: (localId, remoteId) { - _runUploadCallback( - 'Upload success callback failed for $localId', - () => userCallbacks.onSuccess?.call(localId, remoteId), - ); - final source = localById[localId]; - if (source == null) { - _logger.warning('Upload success for $localId but source LocalAsset missing; skipping album link'); - return; - } - pendingAdds.add( - linkUploadedAssetToAlbum(albumId, remoteId, uploader, source) - .then((added) { - addedCount += added; - }) - .catchError((Object error, StackTrace stack) { - _logger.warning('Failed to add uploaded asset $remoteId to album $albumId', error, stack); - }), - ); - }, - ); - - await _uploadService.uploadManual(localAssets, callbacks: wrappedCallbacks, cancelToken: cancelToken); - await Future.wait(pendingAdds); - return addedCount; - } - - void _runUploadCallback(String message, void Function() callback) { - try { - callback(); - } catch (error, stack) { - _logger.warning(message, error, stack); - } - } - - /// Links a freshly-uploaded asset to an album, ensuring the local DB - /// reflects the change without waiting for the next sync. We call the API - /// (server is the source of truth), then upsert a placeholder - /// `remote_asset_entity` row from the local source so the FK-protected - /// junction insert succeeds. Sync overwrites the placeholder later with - /// the authoritative server data. - Future linkUploadedAssetToAlbum(String albumId, String remoteId, UserDto uploader, LocalAsset source) async { - final result = await _albumApiRepository.addAssets(albumId, [remoteId]); - if (result.added.isEmpty) { - return 0; - } - - await _repository.upsertRemoteAssetStub(remoteId: remoteId, ownerId: uploader.id, source: source); - await _repository.addAssets(albumId, result.added); - return result.added.length; + return album.added.length; } Future deleteAlbum(String albumId) async { @@ -320,9 +176,7 @@ class RemoteAlbumService { List albums, { required AssetDateAggregation aggregation, }) async { - if (albums.isEmpty) { - return []; - } + if (albums.isEmpty) return []; final albumIds = albums.map((e) => e.id).toList(); final sortedIds = await _repository.getSortedAlbumIds(albumIds, aggregation: aggregation); @@ -339,10 +193,4 @@ class RemoteAlbumService { return sortedAlbums; } - - Future removeAssets({required String albumId, required List assetIds}) async { - final result = await _albumApiRepository.removeAssets(albumId, assetIds); - await _repository.removeAssets(albumId, result.removed); - return result.removed.length; - } } diff --git a/mobile/lib/domain/services/search.service.dart b/mobile/lib/domain/services/search.service.dart index 8b93e9c8cc..004ad06b1b 100644 --- a/mobile/lib/domain/services/search.service.dart +++ b/mobile/lib/domain/services/search.service.dart @@ -1,9 +1,10 @@ +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/search_result.model.dart'; -import 'package:immich_mobile/extensions/asset_extensions.dart'; import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/search_api.repository.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:logging/logging.dart'; +import 'package:openapi/api.dart' as api show AssetVisibility; import 'package:openapi/api.dart' hide AssetVisibility; class SearchService { @@ -51,3 +52,43 @@ class SearchService { return null; } } + +extension on AssetResponseDto { + RemoteAsset toDto() { + return RemoteAsset( + id: id, + name: originalFileName, + checksum: checksum, + createdAt: fileCreatedAt, + updatedAt: updatedAt, + ownerId: ownerId, + visibility: switch (visibility) { + api.AssetVisibility.timeline => AssetVisibility.timeline, + api.AssetVisibility.hidden => AssetVisibility.hidden, + api.AssetVisibility.archive => AssetVisibility.archive, + api.AssetVisibility.locked => AssetVisibility.locked, + _ => AssetVisibility.timeline, + }, + durationInSeconds: duration.toDuration()?.inSeconds ?? 0, + height: height?.toInt(), + width: width?.toInt(), + isFavorite: isFavorite, + livePhotoVideoId: livePhotoVideoId, + thumbHash: thumbhash, + localId: null, + type: type.toAssetType(), + stackId: stack?.id, + isEdited: isEdited, + ); + } +} + +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'), + }; +} diff --git a/mobile/lib/domain/services/setting.service.dart b/mobile/lib/domain/services/setting.service.dart index 1823e46cf9..99e07a2872 100644 --- a/mobile/lib/domain/services/setting.service.dart +++ b/mobile/lib/domain/services/setting.service.dart @@ -9,7 +9,7 @@ final AppSetting = SettingsService(storeService: StoreService.I); class SettingsService { final StoreService _storeService; - const SettingsService({required this._storeService}); + const SettingsService({required StoreService storeService}) : _storeService = storeService; T get(Setting setting) => _storeService.get(setting.storeKey, setting.defaultValue); diff --git a/mobile/lib/domain/services/store.service.dart b/mobile/lib/domain/services/store.service.dart index ea830dc322..0098c3d262 100644 --- a/mobile/lib/domain/services/store.service.dart +++ b/mobile/lib/domain/services/store.service.dart @@ -6,13 +6,13 @@ import 'package:immich_mobile/infrastructure/repositories/store.repository.dart' /// Provides access to a persistent key-value store with an in-memory cache. /// Listens for repository changes to keep the cache updated. class StoreService { - final StoreRepository _storeRepository; + final IStoreRepository _storeRepository; /// In-memory cache. Keys are [StoreKey.id] final Map _cache = {}; StreamSubscription>? _storeUpdateSubscription; - StoreService._({required StoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository; + StoreService._({required IStoreRepository isarStoreRepository}) : _storeRepository = isarStoreRepository; // TODO: Temporary typedef to make minimal changes. Remove this and make the presentation layer access store through a provider static StoreService? _instance; @@ -24,12 +24,12 @@ class StoreService { } // TODO: Replace the implementation with the one from create after removing the typedef - static Future init({required StoreRepository storeRepository, bool listenUpdates = true}) async { + static Future init({required IStoreRepository storeRepository, bool listenUpdates = true}) async { _instance ??= await create(storeRepository: storeRepository, listenUpdates: listenUpdates); return _instance!; } - static Future create({required StoreRepository storeRepository, bool listenUpdates = true}) async { + static Future create({required IStoreRepository storeRepository, bool listenUpdates = true}) async { final instance = StoreService._(isarStoreRepository: storeRepository); await instance.populateCache(); if (listenUpdates) { @@ -54,13 +54,7 @@ class StoreService { /// Disposes the store and cancels the subscription. To reuse the store call init() again Future dispose() async { await _storeUpdateSubscription?.cancel(); - _storeUpdateSubscription = null; _cache.clear(); - // Allow a subsequent init() (e.g. when a worker isolate is reused) to - // create a fresh instance instead of returning this disposed one. - if (identical(_instance, this)) { - _instance = null; - } } /// Returns the cached value for [key], or `null` @@ -78,9 +72,7 @@ class StoreService { /// Stores the [value] for the [key]. Skips write if value hasn't changed. Future put, T>(U key, T value) async { - if (_cache[key.id] == value) { - return; - } + if (_cache[key.id] == value) return; await _storeRepository.upsert(key, value); _cache[key.id] = value; } @@ -90,7 +82,7 @@ class StoreService { /// Removes the value for [key] Future delete(StoreKey key) async { - await _storeRepository.deleteValue(key); + await _storeRepository.delete(key); _cache.remove(key.id); } @@ -99,6 +91,8 @@ class StoreService { await _storeRepository.deleteAll(); _cache.clear(); } + + bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? true; } class StoreKeyNotFoundException implements Exception { diff --git a/mobile/lib/domain/services/sync_linked_album.service.dart b/mobile/lib/domain/services/sync_linked_album.service.dart index 80a1fbaf9e..b61ca1c965 100644 --- a/mobile/lib/domain/services/sync_linked_album.service.dart +++ b/mobile/lib/domain/services/sync_linked_album.service.dart @@ -1,43 +1,30 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; -final syncLinkedAlbumServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return SyncLinkedAlbumService( - db.localAlbumRepository, - db.remoteAlbumRepository, +final syncLinkedAlbumServiceProvider = Provider( + (ref) => SyncLinkedAlbumService( + ref.watch(localAlbumRepository), + ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider), - ref.watch(storeServiceProvider), - ); -}); + ), +); class SyncLinkedAlbumService { - final LocalAlbumRepository _localAlbumRepository; - final RemoteAlbumRepository _remoteAlbumRepository; + final DriftLocalAlbumRepository _localAlbumRepository; + final DriftRemoteAlbumRepository _remoteAlbumRepository; final DriftAlbumApiRepository _albumApiRepository; - final StoreService _storeService; - SyncLinkedAlbumService( - this._localAlbumRepository, - this._remoteAlbumRepository, - this._albumApiRepository, - this._storeService, - ); + SyncLinkedAlbumService(this._localAlbumRepository, this._remoteAlbumRepository, this._albumApiRepository); final _log = Logger("SyncLinkedAlbumService"); - Future syncLinkedAlbums(String userId, {Completer? cancellation}) async { + Future syncLinkedAlbums(String userId) async { final selectedAlbums = await _localAlbumRepository.getBackupAlbums(); await Future.wait( @@ -58,11 +45,7 @@ class SyncLinkedAlbumService { final assetIds = await _remoteAlbumRepository.getLinkedAssetIds(userId, localAlbum.id, linkedRemoteAlbumId); _log.fine("Syncing ${assetIds.length} assets to remote album: ${remoteAlbum.name}"); if (assetIds.isNotEmpty) { - final album = await _albumApiRepository.addAssets( - remoteAlbum.id, - assetIds, - abortTrigger: cancellation?.future, - ); + final album = await _albumApiRepository.addAssets(remoteAlbum.id, assetIds); await _remoteAlbumRepository.addAssets(remoteAlbum.id, album.added); } }), @@ -120,11 +103,7 @@ class SyncLinkedAlbumService { /// Creates a new remote album and links it to the local album Future _createAndLinkNewRemoteAlbum(LocalAlbum localAlbum) async { dPrint(() => "Creating new remote album for local album: ${localAlbum.name}"); - final newRemoteAlbum = await _albumApiRepository.createDriftAlbum( - localAlbum.name, - _storeService.get(StoreKey.currentUser), - assetIds: [], - ); + final newRemoteAlbum = await _albumApiRepository.createDriftAlbum(localAlbum.name, assetIds: []); await _remoteAlbumRepository.create(newRemoteAlbum, []); return _localAlbumRepository.linkRemoteAlbum(localAlbum.id, newRemoteAlbum.id); } diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index 2f6ad3f045..9769c2eeec 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -3,19 +3,17 @@ import 'dart:async'; import 'dart:convert'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:logging/logging.dart'; @@ -25,8 +23,6 @@ enum SyncMigrationTask { v20260128_ResetExifV1, // EXIF table has incorrect width and height information. v20260128_CopyExifWidthHeightToAsset, // Asset table has incorrect width and height for video ratio calculations. v20260128_ResetAssetV1, // Asset v2.5.0 has width and height information that were edited assets. - v20260597_ResetAssetV1AssetV2, // Assets didn't include the uploadedAt column. - v20260701_ResetAlbumsV1, // Album user migration dropped the owner. Sync fresh albums from the server to re-populate them. } class SyncStreamService { @@ -34,27 +30,35 @@ class SyncStreamService { final SyncApiRepository _syncApiRepository; final SyncStreamRepository _syncStreamRepository; - final LocalAssetRepository _localAssetRepository; - final TrashedLocalAssetRepository _trashedLocalAssetRepository; - final AssetMediaRepository _assetMediaRepository; - final DevicePermissionRepository _permissionRepository; + final DriftLocalAssetRepository _localAssetRepository; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; + final LocalFilesManagerRepository _localFilesManager; + final StorageRepository _storageRepository; final SyncMigrationRepository _syncMigrationRepository; final ApiService _api; - final Completer? _cancellation; + final bool Function()? _cancelChecker; SyncStreamService({ - required this._syncApiRepository, - required this._syncStreamRepository, - required this._localAssetRepository, - required this._trashedLocalAssetRepository, - required this._assetMediaRepository, - required this._permissionRepository, - required this._syncMigrationRepository, - required this._api, - this._cancellation, - }); + required SyncApiRepository syncApiRepository, + required SyncStreamRepository syncStreamRepository, + required DriftLocalAssetRepository localAssetRepository, + required DriftTrashedLocalAssetRepository trashedLocalAssetRepository, + required LocalFilesManagerRepository localFilesManager, + required StorageRepository storageRepository, + required SyncMigrationRepository syncMigrationRepository, + required ApiService api, + bool Function()? cancelChecker, + }) : _syncApiRepository = syncApiRepository, + _syncStreamRepository = syncStreamRepository, + _localAssetRepository = localAssetRepository, + _trashedLocalAssetRepository = trashedLocalAssetRepository, + _localFilesManager = localFilesManager, + _storageRepository = storageRepository, + _syncMigrationRepository = syncMigrationRepository, + _api = api, + _cancelChecker = cancelChecker; - bool get isCancelled => _cancellation?.isCompleted ?? false; + bool get isCancelled => _cancelChecker?.call() ?? false; Future sync() async { _logger.info("Remote sync request for user"); @@ -82,15 +86,10 @@ class SyncStreamService { _handleEvents, serverVersion: serverSemVer, onReset: () => shouldReset = true, - abortSignal: _cancellation?.future, ); if (shouldReset) { _logger.info("Resetting sync state as requested by server"); - await _syncApiRepository.streamChanges( - _handleEvents, - serverVersion: serverSemVer, - abortSignal: _cancellation?.future, - ); + await _syncApiRepository.streamChanges(_handleEvents, serverVersion: serverSemVer); } previousLength = migrations.length; @@ -105,12 +104,6 @@ class SyncStreamService { } Future _runPreSyncTasks(List migrations, SemVer semVer) async { - if (!migrations.contains(SyncMigrationTask.v20260701_ResetAlbumsV1.name)) { - _logger.info("Running pre-sync task: v20260701_ResetAlbumsV1"); - await _syncApiRepository.deleteSyncAck([SyncEntityType.albumV1]); - migrations.add(SyncMigrationTask.v20260701_ResetAlbumsV1.name); - } - if (!migrations.contains(SyncMigrationTask.v20260128_ResetExifV1.name)) { _logger.info("Running pre-sync task: v20260128_ResetExifV1"); await _syncApiRepository.deleteSyncAck([ @@ -123,7 +116,7 @@ class SyncStreamService { } if (!migrations.contains(SyncMigrationTask.v20260128_ResetAssetV1.name) && - semVer.supports(.assetPayloadChange20260128)) { + semVer >= const SemVer(major: 2, minor: 5, patch: 0)) { _logger.info("Running pre-sync task: v20260128_ResetAssetV1"); await _syncApiRepository.deleteSyncAck([ SyncEntityType.assetV1, @@ -138,13 +131,6 @@ class SyncStreamService { migrations.add(SyncMigrationTask.v20260128_CopyExifWidthHeightToAsset.name); } } - - if (!migrations.contains(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name) && - semVer.supports(.assetPayloadChange20260597)) { - _logger.info("Running pre-sync task: v20260597_ResetAssetV1AssetV2"); - await _syncApiRepository.deleteSyncAck([SyncEntityType.assetV1, SyncEntityType.assetV2]); - migrations.add(SyncMigrationTask.v20260597_ResetAssetV1AssetV2.name); - } } Future _runPostSyncTasks(List migrations) async { @@ -156,7 +142,7 @@ class SyncStreamService { } Future _handleEvents(List events, Function() abort, Function() reset) async { - final List items = []; + List items = []; for (final event in events) { if (isCancelled) { _logger.warning("Sync stream cancelled"); @@ -206,22 +192,17 @@ class SyncStreamService { final remoteSyncAssets = data.cast(); await _syncStreamRepository.updateAssetsV1(remoteSyncAssets); if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); - } - return; - case SyncEntityType.assetV2: - final remoteSyncAssets = data.cast(); - await _syncStreamRepository.updateAssetsV2(remoteSyncAssets); - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList()); + final hasPermission = await _localFilesManager.hasManageMediaPermission(); + if (hasPermission) { + await _handleRemoteTrashed(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.checksum)); + await _applyRemoteRestoreToLocal(); + } else { + _logger.warning("sync Trashed Assets cannot proceed because MANAGE_MEDIA permission is missing"); + } } return; case SyncEntityType.assetDeleteV1: - final remoteSyncAssets = data.cast(); - if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { - await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList()); - } - return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets); + return _syncStreamRepository.deleteAssetsV1(data.cast()); case SyncEntityType.assetExifV1: return _syncStreamRepository.updateAssetsExifV1(data.cast()); case SyncEntityType.assetEditV1: @@ -234,12 +215,8 @@ class SyncStreamService { return _syncStreamRepository.deleteAssetsMetadataV1(data.cast()); case SyncEntityType.partnerAssetV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner'); - case SyncEntityType.partnerAssetV2: - return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner'); case SyncEntityType.partnerAssetBackfillV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill'); - case SyncEntityType.partnerAssetBackfillV2: - return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill'); case SyncEntityType.partnerAssetDeleteV1: return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner"); case SyncEntityType.partnerAssetExifV1: @@ -248,8 +225,6 @@ class SyncStreamService { return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'partner backfill'); case SyncEntityType.albumV1: return _syncStreamRepository.updateAlbumsV1(data.cast()); - case SyncEntityType.albumV2: - return _syncStreamRepository.updateAlbumsV2(data.cast()); case SyncEntityType.albumDeleteV1: return _syncStreamRepository.deleteAlbumsV1(data.cast()); case SyncEntityType.albumUserV1: @@ -260,16 +235,10 @@ class SyncStreamService { return _syncStreamRepository.deleteAlbumUsersV1(data.cast()); case SyncEntityType.albumAssetCreateV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create'); - case SyncEntityType.albumAssetCreateV2: - return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create'); case SyncEntityType.albumAssetUpdateV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update'); - case SyncEntityType.albumAssetUpdateV2: - return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update'); case SyncEntityType.albumAssetBackfillV1: return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill'); - case SyncEntityType.albumAssetBackfillV2: - return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill'); case SyncEntityType.albumAssetExifCreateV1: return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create'); case SyncEntityType.albumAssetExifUpdateV1: @@ -325,17 +294,13 @@ class SyncStreamService { return _syncStreamRepository.updateAssetFacesV2(data.cast()); case SyncEntityType.assetFaceDeleteV1: return _syncStreamRepository.deleteAssetFacesV1(data.cast()); - case SyncEntityType.assetOcrV1: - return _syncStreamRepository.updateAssetOcrV1(data.cast()); - case SyncEntityType.assetOcrDeleteV1: - return _syncStreamRepository.deleteAssetOcrV1(data.cast()); + default: + _logger.warning("Unknown sync data type: $type"); } } Future handleWsAssetUploadReadyV1Batch(List batchData) async { - if (batchData.isEmpty || isCancelled) { - return; - } + if (batchData.isEmpty) return; _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV1 events'); @@ -375,53 +340,7 @@ class SyncStreamService { } } - Future handleWsAssetUploadReadyV2Batch(List batchData) async { - if (batchData.isEmpty || isCancelled) { - return; - } - - _logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events'); - - final List assets = []; - final List exifs = []; - - try { - for (final data in batchData) { - if (data is! Map) { - continue; - } - - final payload = data; - final assetData = payload['asset']; - final exifData = payload['exif']; - - if (assetData == null || exifData == null) { - continue; - } - - final asset = SyncAssetV2.fromJson(assetData); - final exif = SyncAssetExifV1.fromJson(exifData); - - if (asset != null && exif != null) { - assets.add(asset); - exifs.add(exif); - } - } - - if (assets.isNotEmpty && exifs.isNotEmpty) { - await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch'); - await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch'); - _logger.info('Successfully processed ${assets.length} assets in batch'); - } - } catch (error, stackTrace) { - _logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace); - } - } - Future handleWsAssetEditReadyV1(dynamic data) async { - if (isCancelled) { - return; - } _logger.info('Processing AssetEditReadyV1 event'); try { @@ -461,96 +380,35 @@ class SyncStreamService { } } - Future handleWsAssetEditReadyV2(dynamic data) async { - if (isCancelled) { - return; - } - _logger.info('Processing AssetEditReadyV2 event'); - - try { - if (data is! Map) { - throw ArgumentError("Invalid data format for AssetEditReadyV2 event"); - } - - final payload = data; - - if (payload['asset'] == null) { - throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data"); - } - - final asset = SyncAssetV2.fromJson(payload['asset']); - if (asset == null) { - throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data"); - } - - final assetEdits = (payload['edit'] as List) - .map((e) => SyncAssetEditV1.fromJson(e)) - .whereType() - .toList(); - - await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit'); - await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit'); - - _logger.info( - 'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits', - ); - } catch (error, stackTrace) { - _logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace); - } - } - - Future _handleRemoteDeleted(Iterable remoteIds) async { - if (remoteIds.isEmpty) { + Future _handleRemoteTrashed(Iterable checksums) async { + if (checksums.isEmpty) { return Future.value(); } else { - final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(remoteIds); + final localAssetsToTrash = await _localAssetRepository.getAssetsFromBackupAlbums(checksums); if (localAssetsToTrash.isNotEmpty) { - await _trashLocalAssets(localAssetsToTrash); + final mediaUrls = await Future.wait( + localAssetsToTrash.values + .expand((e) => e) + .map((localAsset) => _storageRepository.getAssetEntityForAsset(localAsset).then((e) => e?.getMediaUrl())), + ); + _logger.info("Moving to trash ${mediaUrls.join(", ")} assets"); + final result = await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + if (result) { + await _trashedLocalAssetRepository.trashLocalAsset(localAssetsToTrash); + } } else { - _logger.info("No assets found in backup-enabled albums for remote assets: $remoteIds"); + _logger.info("No assets found in backup-enabled albums for assets: $checksums"); } } } - Future _trashLocalAssets(Map> localAssetsToTrash) async { - final localIds = localAssetsToTrash.values.expand((assets) => assets).map((asset) => asset.id).toList(); - _logger.info("Moving to trash ${localIds.join(", ")} assets"); - final movedIds = await _assetMediaRepository.deleteAll(localIds); - if (movedIds.isNotEmpty) { - final movedAssetsByAlbum = localAssetsToTrash.map( - (albumId, assets) => MapEntry(albumId, assets.where((asset) => movedIds.contains(asset.id)).toList()), - )..removeWhere((_, assets) => assets.isEmpty); - - await _trashedLocalAssetRepository.trashLocalAsset(movedAssetsByAlbum); - } - } - Future _applyRemoteRestoreToLocal() async { final assetsToRestore = await _trashedLocalAssetRepository.getToRestore(); if (assetsToRestore.isNotEmpty) { - final restoredIds = await _assetMediaRepository.restoreAssetsFromTrash(assetsToRestore); + final restoredIds = await _localFilesManager.restoreAssetsFromTrash(assetsToRestore); await _trashedLocalAssetRepository.applyRestoredAssets(restoredIds); } else { _logger.info("No remote assets found for restoration"); } } - - Future _syncAssetTrashStatus(List remoteIds) async { - if (!(await _permissionRepository.hasManageMediaPermission())) { - _logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing"); - return; - } - - await _handleRemoteDeleted(remoteIds); - await _applyRemoteRestoreToLocal(); - } - - Future _syncAssetDeletion(List remoteIds) async { - if (!(await _permissionRepository.hasManageMediaPermission())) { - _logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing"); - return; - } - - await _handleRemoteDeleted(remoteIds); - } } diff --git a/mobile/lib/domain/services/tag.service.dart b/mobile/lib/domain/services/tag.service.dart deleted file mode 100644 index 6eeb83d4cb..0000000000 --- a/mobile/lib/domain/services/tag.service.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; - -final tagServiceProvider = Provider((ref) => TagService(ref.watch(tagsApiRepositoryProvider))); - -class TagService { - final TagsApiRepository _repository; - - const TagService(this._repository); - - Future bulkTagAssets(List assetIds, List tagIds) async { - return _repository.bulkTagAssets(assetIds, tagIds); - } - - Future> getAllTags() async { - final dtos = await _repository.getAllTags(); - if (dtos == null) { - return {}; - } - return dtos.map((dto) => Tag.fromDto(dto)).toSet(); - } - - Future> upsertTags(List tags) async { - final dtos = await _repository.upsertTags(tags); - if (dtos == null) { - return []; - } - return dtos.map((dto) => Tag.fromDto(dto)).toList(); - } -} diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 84895b6056..b33940eacd 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -5,9 +5,10 @@ import 'package:collection/collection.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; @@ -33,18 +34,18 @@ enum TimelineOrigin { search, deepLink, albumActivities, - folder, - recentlyAdded, } class TimelineFactory { - final TimelineRepository _timelineRepository; - final SettingsRepository _settingsRepository; + final DriftTimelineRepository _timelineRepository; + final SettingsService _settingsService; - const TimelineFactory({required this._timelineRepository, required this._settingsRepository}); + const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService}) + : _timelineRepository = timelineRepository, + _settingsService = settingsService; GroupAssetsBy get groupBy { - final group = _settingsRepository.appConfig.timeline.groupAssetsBy; + final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)]; // We do not support auto grouping in the new timeline yet, fallback to day grouping return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group; } @@ -59,8 +60,6 @@ class TimelineFactory { TimelineService remoteAssets(String userId) => TimelineService(_timelineRepository.remote(userId, groupBy)); - TimelineService recentlyAdded(String userId) => TimelineService(_timelineRepository.recentlyAdded(userId, groupBy)); - TimelineService favorite(String userId) => TimelineService(_timelineRepository.favorite(userId, groupBy)); TimelineService trash(String userId) => TimelineService(_timelineRepository.trash(userId, groupBy)); @@ -104,36 +103,39 @@ class TimelineService { TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin); - TimelineService._({required this._assetSource, required this._bucketSource, required this.origin}) { + TimelineService._({ + required TimelineAssetSource assetSource, + required TimelineBucketSource bucketSource, + required this.origin, + }) : _assetSource = assetSource, + _bucketSource = bucketSource { _bucketSubscription = _bucketSource().listen((buckets) { - unawaited( - _mutex.run(() async { - final totalAssets = buckets.fold(0, (acc, bucket) => acc + bucket.assetCount); + _mutex.run(() async { + final totalAssets = buckets.fold(0, (acc, bucket) => acc + bucket.assetCount); - if (totalAssets == 0) { - _bufferOffset = 0; - _buffer = []; + if (totalAssets == 0) { + _bufferOffset = 0; + _buffer = []; + } else { + final int offset; + final int count; + // When the buffer is empty or the old bufferOffset is greater than the new total assets, + // we need to reset the buffer and load the first batch of assets. + if (_bufferOffset >= totalAssets || _buffer.isEmpty) { + offset = 0; + count = kTimelineAssetLoadBatchSize; } else { - final int offset; - final int count; - // When the buffer is empty or the old bufferOffset is greater than the new total assets, - // we need to reset the buffer and load the first batch of assets. - if (_bufferOffset >= totalAssets || _buffer.isEmpty) { - offset = 0; - count = kTimelineAssetLoadBatchSize; - } else { - offset = _bufferOffset; - count = math.min(_buffer.length, totalAssets - _bufferOffset); - } - _buffer = await _assetSource(offset, count); - _bufferOffset = offset; + offset = _bufferOffset; + count = math.min(_buffer.length, totalAssets - _bufferOffset); } + _buffer = await _assetSource(offset, count); + _bufferOffset = offset; + } - // change the state's total assets count only after the buffer is reloaded - _totalAssets = totalAssets; - EventStream.shared.emit(const TimelineReloadEvent()); - }), - ); + // change the state's total assets count only after the buffer is reloaded + _totalAssets = totalAssets; + EventStream.shared.emit(const TimelineReloadEvent()); + }); }); } @@ -180,7 +182,7 @@ class TimelineService { if (!hasRange(index, count)) { throw RangeError('TimelineService::getAssets Index out of range'); } - final int start = index - _bufferOffset; + int start = index - _bufferOffset; return _buffer.slice(start, start + count); } diff --git a/mobile/lib/domain/services/user.service.dart b/mobile/lib/domain/services/user.service.dart index d5c1a6c4f7..d347d8aa4f 100644 --- a/mobile/lib/domain/services/user.service.dart +++ b/mobile/lib/domain/services/user.service.dart @@ -4,15 +4,23 @@ import 'dart:typed_data'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:logging/logging.dart'; class UserService { final Logger _log = Logger("UserService"); + final IsarUserRepository _isarUserRepository; final UserApiRepository _userApiRepository; final StoreService _storeService; - UserService({required this._userApiRepository, required this._storeService}); + UserService({ + required IsarUserRepository isarUserRepository, + required UserApiRepository userApiRepository, + required StoreService storeService, + }) : _isarUserRepository = isarUserRepository, + _userApiRepository = userApiRepository, + _storeService = storeService; UserDto getMyUser() { return _storeService.get(StoreKey.currentUser); @@ -28,10 +36,9 @@ class UserService { Future refreshMyUser() async { final user = await _userApiRepository.getMyUser(); - if (user == null) { - return null; - } + if (user == null) return null; await _storeService.put(StoreKey.currentUser, user); + await _isarUserRepository.update(user); return user; } @@ -40,10 +47,19 @@ class UserService { final path = await _userApiRepository.createProfileImage(name: name, data: image); final updatedUser = getMyUser(); await _storeService.put(StoreKey.currentUser, updatedUser); + await _isarUserRepository.update(updatedUser); return path; } catch (e) { _log.warning("Failed to upload profile image", e); return null; } } + + Future> getAll() async { + return await _isarUserRepository.getAll(); + } + + Future deleteAll() { + return _isarUserRepository.deleteAll(); + } } diff --git a/mobile/lib/domain/utils/background_sync.dart b/mobile/lib/domain/utils/background_sync.dart index f1e0328165..7c9b6ae061 100644 --- a/mobile/lib/domain/utils/background_sync.dart +++ b/mobile/lib/domain/utils/background_sync.dart @@ -28,7 +28,6 @@ class BackgroundSyncManager { final SyncErrorCallback? onCloudIdSyncError; Cancelable? _syncTask; - bool _syncQueued = false; Cancelable? _syncWebsocketTask; Cancelable? _cloudIdSyncTask; Cancelable? _deviceAlbumSyncTask; @@ -50,46 +49,55 @@ class BackgroundSyncManager { this.onCloudIdSyncError, }); - // The tasks the app-resume path re-runs. One in-flight when the app was suspended - // stays referenced but frozen, so on resume the dedupe guards would hand back the - // stale task instead of syncing (#28082). Websocket and cloud-id are excluded - the - // resume path never restarts them. [_allTasks] builds on this so the lists can't drift. - List get _resumeSyncTasks => [_syncTask, _deviceAlbumSyncTask, _hashTask, _linkedAlbumSyncTask]; - - List get _allTasks => [_syncWebsocketTask, _cloudIdSyncTask, ..._resumeSyncTasks]; - Future cancel() async { - _syncQueued = false; - final tasks = _allTasks; - _syncTask = null; - _syncWebsocketTask = null; - _cloudIdSyncTask = null; - _linkedAlbumSyncTask = null; - _deviceAlbumSyncTask = null; - _hashTask = null; - await _cancelAll(tasks); - } + final futures = []; - Future cancelResumeSyncs() async { - _syncQueued = false; - final tasks = _resumeSyncTasks; - _syncTask = null; - _deviceAlbumSyncTask = null; - _hashTask = null; - _linkedAlbumSyncTask = null; - await _cancelAll(tasks); - } - - // Cancels every task in [tasks] and waits for them to unwind. Callers null out - // their own fields first, so the sync guards see a clean slate immediately. - Future _cancelAll(List tasks) async { - final futures = [ - for (final task in tasks) - if (task != null) task.future, - ]; - for (final task in tasks) { - task?.cancel(); + if (_syncTask != null) { + futures.add(_syncTask!.future); } + _syncTask?.cancel(); + _syncTask = null; + + if (_syncWebsocketTask != null) { + futures.add(_syncWebsocketTask!.future); + } + _syncWebsocketTask?.cancel(); + _syncWebsocketTask = null; + + if (_cloudIdSyncTask != null) { + futures.add(_cloudIdSyncTask!.future); + } + _cloudIdSyncTask?.cancel(); + _cloudIdSyncTask = null; + + if (_linkedAlbumSyncTask != null) { + futures.add(_linkedAlbumSyncTask!.future); + } + _linkedAlbumSyncTask?.cancel(); + _linkedAlbumSyncTask = null; + + try { + await Future.wait(futures); + } on CanceledError { + // Ignore cancellation errors + } + } + + Future cancelLocal() async { + final futures = []; + + if (_hashTask != null) { + futures.add(_hashTask!.future); + } + _hashTask?.cancel(); + _hashTask = null; + + if (_deviceAlbumSyncTask != null) { + futures.add(_deviceAlbumSyncTask!.future); + } + _deviceAlbumSyncTask?.cancel(); + _deviceAlbumSyncTask = null; + try { await Future.wait(futures); } on CanceledError { @@ -100,14 +108,14 @@ class BackgroundSyncManager { // No need to cancel the task, as it can also be run when the user logs out Future syncLocal({bool full = false}) { if (_deviceAlbumSyncTask != null) { - return _deviceAlbumSyncTask!.future.catchError((_) {}, test: (error) => error is CanceledError); + return _deviceAlbumSyncTask!.future; } onLocalSyncStart?.call(); // We use a ternary operator to avoid [_deviceAlbumSyncTask] from being // captured by the closure passed to [runInIsolateGentle]. - final task = _deviceAlbumSyncTask = full + _deviceAlbumSyncTask = full ? runInIsolateGentle( computation: (ref) => ref.read(localSyncServiceProvider).sync(full: true), debugLabel: 'local-sync-full-true', @@ -117,86 +125,68 @@ class BackgroundSyncManager { debugLabel: 'local-sync-full-false', ); - return task + return _deviceAlbumSyncTask! .whenComplete(() { - if (identical(_deviceAlbumSyncTask, task)) { - _deviceAlbumSyncTask = null; - } + _deviceAlbumSyncTask = null; onLocalSyncComplete?.call(); }) .catchError((error) { - if (error is! CanceledError) { - onLocalSyncError?.call(error.toString()); - } + onLocalSyncError?.call(error.toString()); + _deviceAlbumSyncTask = null; }); } Future hashAssets() { if (_hashTask != null) { - return _hashTask!.future.catchError((_) {}, test: (error) => error is CanceledError); + return _hashTask!.future; } onHashingStart?.call(); - final task = _hashTask = runInIsolateGentle( + _hashTask = runInIsolateGentle( computation: (ref) => ref.read(hashServiceProvider).hashAssets(), debugLabel: 'hash-assets', ); - return task + return _hashTask! .whenComplete(() { onHashingComplete?.call(); - if (identical(_hashTask, task)) { - _hashTask = null; - } + _hashTask = null; }) .catchError((error) { - if (error is! CanceledError) { - onHashingError?.call(error.toString()); - } + onHashingError?.call(error.toString()); + _hashTask = null; }); } - Future syncRemote({bool enqueue = false}) { + Future syncRemote() { if (_syncTask != null) { - _syncQueued |= enqueue; return _syncTask!.future.then((result) => result ?? false).catchError((_) => false); } onRemoteSyncStart?.call(); - final task = _syncTask = runInIsolateGentle( + _syncTask = runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).sync(), debugLabel: 'remote-sync', ); - return task + return _syncTask! .then((result) { final success = result ?? false; onRemoteSyncComplete?.call(success); - _syncQueued &= success; return success; }) .catchError((error) { - if (error is! CanceledError) { - onRemoteSyncError?.call(error.toString()); - } - _syncQueued = false; + onRemoteSyncError?.call(error.toString()); + _syncTask = null; return false; }) - // A task clears only its own slot: one that was cancelled and superseded by a - // fresh task (see cancelResumeSyncs) must not null the new task's slot. .whenComplete(() { - if (identical(_syncTask, task)) { - _syncTask = null; - if (_syncQueued) { - _syncQueued = false; - unawaited(syncRemote()); - } - } + _syncTask = null; }); } - Future syncWebsocketBatchV1(List batchData) { + Future syncWebsocketBatch(List batchData) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; } @@ -206,17 +196,7 @@ class BackgroundSyncManager { }); } - Future syncWebsocketBatchV2(List batchData) { - if (_syncWebsocketTask != null) { - return _syncWebsocketTask!.future; - } - _syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData); - return _syncWebsocketTask!.whenComplete(() { - _syncWebsocketTask = null; - }); - } - - Future syncWebsocketEditV1(dynamic data) { + Future syncWebsocketEdit(dynamic data) { if (_syncWebsocketTask != null) { return _syncWebsocketTask!.future; } @@ -226,33 +206,15 @@ class BackgroundSyncManager { }); } - Future syncWebsocketEditV2(dynamic data) { - if (_syncWebsocketTask != null) { - return _syncWebsocketTask!.future; - } - _syncWebsocketTask = _handleWsAssetEditReadyV2(data); - return _syncWebsocketTask!.whenComplete(() { - _syncWebsocketTask = null; - }); - } - Future syncLinkedAlbum() { if (_linkedAlbumSyncTask != null) { - return _linkedAlbumSyncTask!.future.catchError((_) {}, test: (error) => error is CanceledError); + return _linkedAlbumSyncTask!.future; } - final task = _linkedAlbumSyncTask = runInIsolateGentle( - computation: syncLinkedAlbumsIsolated, - debugLabel: 'linked-album-sync', - ); - return task - .whenComplete(() { - if (identical(_linkedAlbumSyncTask, task)) { - _linkedAlbumSyncTask = null; - } - }) - // a cancelled resume sync is not a failure; absorb it so the websocket callers don't get an uncaught error - .catchError((_) {}, test: (error) => error is CanceledError); + _linkedAlbumSyncTask = runInIsolateGentle(computation: syncLinkedAlbumsIsolated, debugLabel: 'linked-album-sync'); + return _linkedAlbumSyncTask!.whenComplete(() { + _linkedAlbumSyncTask = null; + }); } Future syncCloudIds() { @@ -280,17 +242,7 @@ Cancelable _handleWsAssetUploadReadyV1Batch(List batchData) => ru debugLabel: 'websocket-batch', ); -Cancelable _handleWsAssetUploadReadyV2Batch(List batchData) => runInIsolateGentle( - computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData), - debugLabel: 'websocket-batch', -); - Cancelable _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle( computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data), debugLabel: 'websocket-edit', ); - -Cancelable _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle( - computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data), - debugLabel: 'websocket-edit', -); diff --git a/mobile/lib/domain/utils/event_stream.dart b/mobile/lib/domain/utils/event_stream.dart index 0069c75f86..5967fdca50 100644 --- a/mobile/lib/domain/utils/event_stream.dart +++ b/mobile/lib/domain/utils/event_stream.dart @@ -32,7 +32,7 @@ class EventStream { } /// Closes the stream controller - Future dispose() { - return _controller.close(); + void dispose() { + _controller.close(); } } diff --git a/mobile/lib/domain/utils/migrate_cloud_ids.dart b/mobile/lib/domain/utils/migrate_cloud_ids.dart index ecdf5fc9a0..33a8eca94d 100644 --- a/mobile/lib/domain/utils/migrate_cloud_ids.dart +++ b/mobile/lib/domain/utils/migrate_cloud_ids.dart @@ -1,18 +1,14 @@ -import 'dart:async'; - import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/server_capability.model.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/providers/api.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/sync.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -32,12 +28,12 @@ Future syncCloudIds(ProviderContainer ref) async { await _populateCloudIds(db); final serverInfo = await ref.read(serverInfoProvider.notifier).getServerInfo(); - final canUpdateMetadata = serverInfo.serverVersion.supports(.cloudIdMetadata); + final canUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 4); if (!canUpdateMetadata) { logger.fine('Server version does not support asset metadata updates. Skipping cloudId migration.'); return; } - final canBulkUpdateMetadata = serverInfo.serverVersion.supports(.bulkCloudIdMetadata); + final canBulkUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 5); // Wait for remote sync to complete, so we have up-to-date asset metadata entries try { @@ -55,10 +51,9 @@ Future syncCloudIds(ProviderContainer ref) async { } final assetApi = ref.read(apiServiceProvider).assetsApi; - final cancellation = ref.read(cancellationProvider); // Process cloud IDs in paginated batches - await _processCloudIdMappingsInBatches(db, currentUser.id, assetApi, canBulkUpdateMetadata, logger, cancellation); + await _processCloudIdMappingsInBatches(db, currentUser.id, assetApi, canBulkUpdateMetadata, logger); } Future _processCloudIdMappingsInBatches( @@ -67,17 +62,12 @@ Future _processCloudIdMappingsInBatches( AssetsApi assetsApi, bool canBulkUpdate, Logger logger, - Completer cancellation, ) async { const pageSize = 20000; String? lastLocalId; final seenRemoteAssetIds = {}; while (true) { - if (cancellation.isCompleted) { - logger.warning('Cloud ID migration cancelled. Stopping batch processing.'); - break; - } final mappings = await _fetchCloudIdMappings(drift, userId, pageSize, lastLocalId); if (mappings.isEmpty) { break; @@ -90,14 +80,12 @@ Future _processCloudIdMappingsInBatches( AssetMetadataBulkUpsertItemDto( assetId: mapping.remoteAssetId, key: kMobileMetadataKey, - value: Map.from( - RemoteAssetMobileAppMetadata( - cloudId: mapping.localAsset.cloudId, - createdAt: mapping.localAsset.createdAt.toIso8601String(), - adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(), - latitude: mapping.localAsset.latitude?.toString(), - longitude: mapping.localAsset.longitude?.toString(), - ).toJson(), + value: RemoteAssetMobileAppMetadata( + cloudId: mapping.localAsset.cloudId, + createdAt: mapping.localAsset.createdAt.toIso8601String(), + adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(), + latitude: mapping.localAsset.latitude?.toString(), + longitude: mapping.localAsset.longitude?.toString(), ), ), ); @@ -108,9 +96,9 @@ Future _processCloudIdMappingsInBatches( if (items.isNotEmpty) { if (canBulkUpdate) { - await _bulkUpdateCloudIds(assetsApi, items, cancellation.future); + await _bulkUpdateCloudIds(assetsApi, items); } else { - await _sequentialUpdateCloudIds(assetsApi, items, cancellation); + await _sequentialUpdateCloudIds(assetsApi, items); } } @@ -121,35 +109,20 @@ Future _processCloudIdMappingsInBatches( } } -Future _sequentialUpdateCloudIds( - AssetsApi assetsApi, - List items, - Completer cancellation, -) async { +Future _sequentialUpdateCloudIds(AssetsApi assetsApi, List items) async { for (final item in items) { - if (cancellation.isCompleted) { - break; - } final upsertItem = AssetMetadataUpsertItemDto(key: item.key, value: item.value); try { - await assetsApi.updateAssetMetadata( - item.assetId, - AssetMetadataUpsertDto(items: [upsertItem]), - abortTrigger: cancellation.future, - ); + await assetsApi.updateAssetMetadata(item.assetId, AssetMetadataUpsertDto(items: [upsertItem])); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to update metadata for asset ${item.assetId}', error, stack); } } } -Future _bulkUpdateCloudIds( - AssetsApi assetsApi, - List items, - Future abortTrigger, -) async { +Future _bulkUpdateCloudIds(AssetsApi assetsApi, List items) async { try { - await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items), abortTrigger: abortTrigger); + await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items)); } catch (error, stack) { Logger('migrateCloudIds').warning('Failed to bulk update metadata', error, stack); } @@ -172,7 +145,7 @@ Future _populateCloudIds(Drift drift) async { ); } } - await LocalAlbumRepository(drift).updateCloudMapping(cloudMapping); + await DriftLocalAlbumRepository(drift).updateCloudMapping(cloudMapping); } typedef _CloudIdMapping = ({String remoteAssetId, LocalAsset localAsset}); diff --git a/mobile/lib/domain/utils/sync_linked_album.dart b/mobile/lib/domain/utils/sync_linked_album.dart index bdf35d3e2a..7bfadc96e7 100644 --- a/mobile/lib/domain/utils/sync_linked_album.dart +++ b/mobile/lib/domain/utils/sync_linked_album.dart @@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:logging/logging.dart'; Future syncLinkedAlbumsIsolated(ProviderContainer ref) { @@ -11,7 +10,5 @@ Future syncLinkedAlbumsIsolated(ProviderContainer ref) { Logger("SyncLinkedAlbum").warning("No user logged in, skipping linked album sync"); return Future.value(); } - return ref - .read(syncLinkedAlbumServiceProvider) - .syncLinkedAlbums(user.id, cancellation: ref.read(cancellationProvider)); + return ref.read(syncLinkedAlbumServiceProvider).syncLinkedAlbums(user.id); } diff --git a/mobile/lib/entities/README.md b/mobile/lib/entities/README.md new file mode 100644 index 0000000000..c2ad4876e3 --- /dev/null +++ b/mobile/lib/entities/README.md @@ -0,0 +1 @@ +This directory contains entity that is stored in the local storage. \ No newline at end of file diff --git a/mobile/lib/entities/album.entity.dart b/mobile/lib/entities/album.entity.dart new file mode 100644 index 0000000000..2ca0d50dcc --- /dev/null +++ b/mobile/lib/entities/album.entity.dart @@ -0,0 +1,192 @@ +import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/utils/datetime_comparison.dart'; +import 'package:isar/isar.dart'; +// ignore: implementation_imports +import 'package:isar/src/common/isar_links_common.dart'; +import 'package:openapi/api.dart'; + +part 'album.entity.g.dart'; + +@Collection(inheritance: false) +class Album { + @protected + Album({ + this.remoteId, + this.localId, + required this.name, + required this.createdAt, + required this.modifiedAt, + this.description, + this.startDate, + this.endDate, + this.lastModifiedAssetTimestamp, + required this.shared, + required this.activityEnabled, + this.sortOrder = SortOrder.desc, + }); + + // fields stored in DB + Id id = Isar.autoIncrement; + @Index(unique: false, replace: false, type: IndexType.hash) + String? remoteId; + @Index(unique: false, replace: false, type: IndexType.hash) + String? localId; + String name; + String? description; + DateTime createdAt; + DateTime modifiedAt; + DateTime? startDate; + DateTime? endDate; + DateTime? lastModifiedAssetTimestamp; + bool shared; + bool activityEnabled; + @enumerated + SortOrder sortOrder; + final IsarLink owner = IsarLink(); + final IsarLink thumbnail = IsarLink(); + final IsarLinks sharedUsers = IsarLinks(); + final IsarLinks assets = IsarLinks(); + + // transient fields + @ignore + bool isAll = false; + + @ignore + String? remoteThumbnailAssetId; + + @ignore + int remoteAssetCount = 0; + + // getters + @ignore + bool get isRemote => remoteId != null; + + @ignore + bool get isLocal => localId != null; + + @ignore + int get assetCount => assets.length; + + @ignore + String? get ownerId => owner.value?.id; + + @ignore + String? get ownerName { + // Guard null owner + if (owner.value == null) { + return null; + } + + final name = []; + if (owner.value?.name != null) { + name.add(owner.value!.name); + } + + return name.join(' '); + } + + @ignore + String get eTagKeyAssetCount => "device-album-$localId-asset-count"; + + // the following getter are needed because Isar links do not make data + // accessible in an object freshly created (not loaded from DB) + + @ignore + Iterable get remoteUsers => + sharedUsers.isEmpty ? (sharedUsers as IsarLinksCommon).addedObjects : sharedUsers; + + @ignore + Iterable get remoteAssets => assets.isEmpty ? (assets as IsarLinksCommon).addedObjects : assets; + + @override + bool operator ==(other) { + if (other is! Album) return false; + return id == other.id && + remoteId == other.remoteId && + localId == other.localId && + name == other.name && + description == other.description && + createdAt.isAtSameMomentAs(other.createdAt) && + modifiedAt.isAtSameMomentAs(other.modifiedAt) && + isAtSameMomentAs(startDate, other.startDate) && + isAtSameMomentAs(endDate, other.endDate) && + isAtSameMomentAs(lastModifiedAssetTimestamp, other.lastModifiedAssetTimestamp) && + shared == other.shared && + activityEnabled == other.activityEnabled && + owner.value == other.owner.value && + thumbnail.value == other.thumbnail.value && + sharedUsers.length == other.sharedUsers.length && + assets.length == other.assets.length; + } + + @override + @ignore + int get hashCode => + id.hashCode ^ + remoteId.hashCode ^ + localId.hashCode ^ + name.hashCode ^ + createdAt.hashCode ^ + modifiedAt.hashCode ^ + startDate.hashCode ^ + endDate.hashCode ^ + description.hashCode ^ + lastModifiedAssetTimestamp.hashCode ^ + shared.hashCode ^ + activityEnabled.hashCode ^ + owner.value.hashCode ^ + thumbnail.value.hashCode ^ + sharedUsers.length.hashCode ^ + assets.length.hashCode; + + static Future remote(AlbumResponseDto dto) async { + final Isar db = Isar.getInstance()!; + final Album a = Album( + remoteId: dto.id, + name: dto.albumName, + createdAt: dto.createdAt, + modifiedAt: dto.updatedAt, + description: dto.description, + lastModifiedAssetTimestamp: dto.lastModifiedAssetTimestamp, + shared: dto.shared, + startDate: dto.startDate, + endDate: dto.endDate, + activityEnabled: dto.isActivityEnabled, + ); + a.remoteAssetCount = dto.assetCount; + a.owner.value = await db.users.getById(dto.ownerId); + if (dto.order != null) { + a.sortOrder = dto.order == AssetOrder.asc ? SortOrder.asc : SortOrder.desc; + } + + if (dto.albumThumbnailAssetId != null) { + a.thumbnail.value = await db.assets.where().remoteIdEqualTo(dto.albumThumbnailAssetId).findFirst(); + } + if (dto.albumUsers.isNotEmpty) { + final users = await db.users.getAllById(dto.albumUsers.map((e) => e.user.id).toList(growable: false)); + a.sharedUsers.addAll(users.cast()); + } + if (dto.assets.isNotEmpty) { + final assets = await db.assets.getAllByRemoteId(dto.assets.map((e) => e.id)); + a.assets.addAll(assets); + } + return a; + } + + @override + String toString() => 'remoteId: $remoteId name: $name description: $description'; +} + +extension AssetsHelper on IsarCollection { + Future store(Album a) async { + await put(a); + await a.owner.save(); + await a.thumbnail.save(); + await a.sharedUsers.save(); + await a.assets.save(); + return a; + } +} diff --git a/mobile/lib/entities/album.entity.g.dart b/mobile/lib/entities/album.entity.g.dart new file mode 100644 index 0000000000..ecbbab48c2 --- /dev/null +++ b/mobile/lib/entities/album.entity.g.dart @@ -0,0 +1,2240 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'album.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetAlbumCollection on Isar { + IsarCollection get albums => this.collection(); +} + +const AlbumSchema = CollectionSchema( + name: r'Album', + id: -1355968412107120937, + properties: { + r'activityEnabled': PropertySchema( + id: 0, + name: r'activityEnabled', + type: IsarType.bool, + ), + r'createdAt': PropertySchema( + id: 1, + name: r'createdAt', + type: IsarType.dateTime, + ), + r'description': PropertySchema( + id: 2, + name: r'description', + type: IsarType.string, + ), + r'endDate': PropertySchema( + id: 3, + name: r'endDate', + type: IsarType.dateTime, + ), + r'lastModifiedAssetTimestamp': PropertySchema( + id: 4, + name: r'lastModifiedAssetTimestamp', + type: IsarType.dateTime, + ), + r'localId': PropertySchema(id: 5, name: r'localId', type: IsarType.string), + r'modifiedAt': PropertySchema( + id: 6, + name: r'modifiedAt', + type: IsarType.dateTime, + ), + r'name': PropertySchema(id: 7, name: r'name', type: IsarType.string), + r'remoteId': PropertySchema( + id: 8, + name: r'remoteId', + type: IsarType.string, + ), + r'shared': PropertySchema(id: 9, name: r'shared', type: IsarType.bool), + r'sortOrder': PropertySchema( + id: 10, + name: r'sortOrder', + type: IsarType.byte, + enumMap: _AlbumsortOrderEnumValueMap, + ), + r'startDate': PropertySchema( + id: 11, + name: r'startDate', + type: IsarType.dateTime, + ), + }, + + estimateSize: _albumEstimateSize, + serialize: _albumSerialize, + deserialize: _albumDeserialize, + deserializeProp: _albumDeserializeProp, + idName: r'id', + indexes: { + r'remoteId': IndexSchema( + id: 6301175856541681032, + name: r'remoteId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'remoteId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + r'localId': IndexSchema( + id: 1199848425898359622, + name: r'localId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'localId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + }, + links: { + r'owner': LinkSchema( + id: 8272576585804958029, + name: r'owner', + target: r'User', + single: true, + ), + r'thumbnail': LinkSchema( + id: 4055421409629988258, + name: r'thumbnail', + target: r'Asset', + single: true, + ), + r'sharedUsers': LinkSchema( + id: 8972835302564625434, + name: r'sharedUsers', + target: r'User', + single: false, + ), + r'assets': LinkSchema( + id: 1059358332698388152, + name: r'assets', + target: r'Asset', + single: false, + ), + }, + embeddedSchemas: {}, + + getId: _albumGetId, + getLinks: _albumGetLinks, + attach: _albumAttach, + version: '3.3.0-dev.3', +); + +int _albumEstimateSize( + Album object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.description; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.localId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.name.length * 3; + { + final value = object.remoteId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _albumSerialize( + Album object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + 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); +} + +Album _albumDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + 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]), + ); + object.id = id; + return object; +} + +P _albumDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readBool(offset)) as P; + case 1: + return (reader.readDateTime(offset)) as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readDateTimeOrNull(offset)) as P; + case 4: + return (reader.readDateTimeOrNull(offset)) as P; + case 5: + return (reader.readStringOrNull(offset)) as P; + case 6: + return (reader.readDateTime(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readBool(offset)) as P; + case 10: + return (_AlbumsortOrderValueEnumMap[reader.readByteOrNull(offset)] ?? + SortOrder.desc) + as P; + case 11: + return (reader.readDateTimeOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _AlbumsortOrderEnumValueMap = {'asc': 0, 'desc': 1}; +const _AlbumsortOrderValueEnumMap = {0: SortOrder.asc, 1: SortOrder.desc}; + +Id _albumGetId(Album object) { + return object.id; +} + +List> _albumGetLinks(Album object) { + return [object.owner, object.thumbnail, object.sharedUsers, object.assets]; +} + +void _albumAttach(IsarCollection col, Id id, Album object) { + object.id = id; + object.owner.attach(col, col.isar.collection(), r'owner', id); + object.thumbnail.attach(col, col.isar.collection(), r'thumbnail', id); + object.sharedUsers.attach( + col, + col.isar.collection(), + r'sharedUsers', + id, + ); + object.assets.attach(col, col.isar.collection(), r'assets', id); +} + +extension AlbumQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension AlbumQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder remoteIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'remoteId', value: [null]), + ); + }); + } + + QueryBuilder remoteIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [null], + includeLower: false, + upper: [], + ), + ); + }); + } + + QueryBuilder remoteIdEqualTo( + String? remoteId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'remoteId', value: [remoteId]), + ); + }); + } + + QueryBuilder remoteIdNotEqualTo( + String? remoteId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [], + upper: [remoteId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [remoteId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [remoteId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [], + upper: [remoteId], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder localIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'localId', value: [null]), + ); + }); + } + + QueryBuilder localIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [null], + includeLower: false, + upper: [], + ), + ); + }); + } + + QueryBuilder localIdEqualTo( + String? localId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'localId', value: [localId]), + ); + }); + } + + QueryBuilder localIdNotEqualTo( + String? localId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [], + upper: [localId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [localId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [localId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [], + upper: [localId], + includeUpper: false, + ), + ); + } + }); + } +} + +extension AlbumQueryFilter on QueryBuilder { + QueryBuilder activityEnabledEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'activityEnabled', value: value), + ); + }); + } + + QueryBuilder createdAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'createdAt', value: value), + ); + }); + } + + QueryBuilder createdAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'createdAt', + value: value, + ), + ); + }); + } + + QueryBuilder createdAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'createdAt', + value: value, + ), + ); + }); + } + + QueryBuilder createdAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'createdAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder descriptionIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'description'), + ); + }); + } + + QueryBuilder descriptionIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'description'), + ); + }); + } + + QueryBuilder descriptionEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder 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 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 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 descriptionStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'description', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'description', value: ''), + ); + }); + } + + QueryBuilder descriptionIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'description', value: ''), + ); + }); + } + + QueryBuilder endDateIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'endDate'), + ); + }); + } + + QueryBuilder endDateIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'endDate'), + ); + }); + } + + QueryBuilder endDateEqualTo( + DateTime? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'endDate', value: value), + ); + }); + } + + QueryBuilder endDateGreaterThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'endDate', + value: value, + ), + ); + }); + } + + QueryBuilder endDateLessThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'endDate', + value: value, + ), + ); + }); + } + + QueryBuilder endDateBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'endDate', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'lastModifiedAssetTimestamp'), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull( + property: r'lastModifiedAssetTimestamp', + ), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampEqualTo(DateTime? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'lastModifiedAssetTimestamp', + value: value, + ), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampGreaterThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'lastModifiedAssetTimestamp', + value: value, + ), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampLessThan(DateTime? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'lastModifiedAssetTimestamp', + value: value, + ), + ); + }); + } + + QueryBuilder + lastModifiedAssetTimestampBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'lastModifiedAssetTimestamp', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder localIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'localId'), + ); + }); + } + + QueryBuilder localIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'localId'), + ); + }); + } + + QueryBuilder localIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdBetween( + 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'localId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'localId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'localId', value: ''), + ); + }); + } + + QueryBuilder localIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'localId', value: ''), + ); + }); + } + + QueryBuilder modifiedAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'modifiedAt', value: value), + ); + }); + } + + QueryBuilder modifiedAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'modifiedAt', + value: value, + ), + ); + }); + } + + QueryBuilder modifiedAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'modifiedAt', + value: value, + ), + ); + }); + } + + QueryBuilder modifiedAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'modifiedAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameBetween( + 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'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'name', value: ''), + ); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'name', value: ''), + ); + }); + } + + QueryBuilder remoteIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'remoteId'), + ); + }); + } + + QueryBuilder remoteIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'remoteId'), + ); + }); + } + + QueryBuilder remoteIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdBetween( + 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'remoteId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'remoteId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'remoteId', value: ''), + ); + }); + } + + QueryBuilder remoteIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'remoteId', value: ''), + ); + }); + } + + QueryBuilder sharedEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shared', value: value), + ); + }); + } + + QueryBuilder sortOrderEqualTo( + SortOrder value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'sortOrder', value: value), + ); + }); + } + + QueryBuilder sortOrderGreaterThan( + SortOrder value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'sortOrder', + value: value, + ), + ); + }); + } + + QueryBuilder sortOrderLessThan( + SortOrder value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'sortOrder', + value: value, + ), + ); + }); + } + + QueryBuilder sortOrderBetween( + SortOrder lower, + SortOrder upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'sortOrder', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder startDateIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'startDate'), + ); + }); + } + + QueryBuilder startDateIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'startDate'), + ); + }); + } + + QueryBuilder startDateEqualTo( + DateTime? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'startDate', value: value), + ); + }); + } + + QueryBuilder startDateGreaterThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'startDate', + value: value, + ), + ); + }); + } + + QueryBuilder startDateLessThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'startDate', + value: value, + ), + ); + }); + } + + QueryBuilder startDateBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'startDate', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension AlbumQueryObject on QueryBuilder {} + +extension AlbumQueryLinks on QueryBuilder { + QueryBuilder owner(FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'owner'); + }); + } + + QueryBuilder ownerIsNull() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'owner', 0, true, 0, true); + }); + } + + QueryBuilder thumbnail( + FilterQuery q, + ) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'thumbnail'); + }); + } + + QueryBuilder thumbnailIsNull() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'thumbnail', 0, true, 0, true); + }); + } + + QueryBuilder sharedUsers( + FilterQuery q, + ) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'sharedUsers'); + }); + } + + QueryBuilder sharedUsersLengthEqualTo( + int length, + ) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'sharedUsers', length, true, length, true); + }); + } + + QueryBuilder sharedUsersIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'sharedUsers', 0, true, 0, true); + }); + } + + QueryBuilder sharedUsersIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'sharedUsers', 0, false, 999999, true); + }); + } + + QueryBuilder sharedUsersLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'sharedUsers', 0, true, length, include); + }); + } + + QueryBuilder + sharedUsersLengthGreaterThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'sharedUsers', length, include, 999999, true); + }); + } + + QueryBuilder sharedUsersLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength( + r'sharedUsers', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder assets( + FilterQuery q, + ) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'assets'); + }); + } + + QueryBuilder assetsLengthEqualTo( + int length, + ) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'assets', length, true, length, true); + }); + } + + QueryBuilder assetsIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'assets', 0, true, 0, true); + }); + } + + QueryBuilder assetsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'assets', 0, false, 999999, true); + }); + } + + QueryBuilder assetsLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'assets', 0, true, length, include); + }); + } + + QueryBuilder assetsLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'assets', length, include, 999999, true); + }); + } + + QueryBuilder assetsLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength( + r'assets', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } +} + +extension AlbumQuerySortBy on QueryBuilder { + QueryBuilder sortByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.asc); + }); + } + + QueryBuilder sortByActivityEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.desc); + }); + } + + QueryBuilder sortByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.asc); + }); + } + + QueryBuilder sortByCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.desc); + }); + } + + QueryBuilder sortByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder sortByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder sortByEndDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'endDate', Sort.asc); + }); + } + + QueryBuilder sortByEndDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'endDate', Sort.desc); + }); + } + + QueryBuilder sortByLastModifiedAssetTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastModifiedAssetTimestamp', Sort.asc); + }); + } + + QueryBuilder + sortByLastModifiedAssetTimestampDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastModifiedAssetTimestamp', Sort.desc); + }); + } + + QueryBuilder sortByLocalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.asc); + }); + } + + QueryBuilder sortByLocalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.desc); + }); + } + + QueryBuilder sortByModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedAt', Sort.asc); + }); + } + + QueryBuilder sortByModifiedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedAt', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder sortByRemoteId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.asc); + }); + } + + QueryBuilder sortByRemoteIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.desc); + }); + } + + QueryBuilder sortByShared() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shared', Sort.asc); + }); + } + + QueryBuilder sortBySharedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shared', Sort.desc); + }); + } + + QueryBuilder sortBySortOrder() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'sortOrder', Sort.asc); + }); + } + + QueryBuilder sortBySortOrderDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'sortOrder', Sort.desc); + }); + } + + QueryBuilder sortByStartDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startDate', Sort.asc); + }); + } + + QueryBuilder sortByStartDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startDate', Sort.desc); + }); + } +} + +extension AlbumQuerySortThenBy on QueryBuilder { + QueryBuilder thenByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.asc); + }); + } + + QueryBuilder thenByActivityEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'activityEnabled', Sort.desc); + }); + } + + QueryBuilder thenByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.asc); + }); + } + + QueryBuilder thenByCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.desc); + }); + } + + QueryBuilder thenByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder thenByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder thenByEndDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'endDate', Sort.asc); + }); + } + + QueryBuilder thenByEndDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'endDate', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByLastModifiedAssetTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastModifiedAssetTimestamp', Sort.asc); + }); + } + + QueryBuilder + thenByLastModifiedAssetTimestampDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastModifiedAssetTimestamp', Sort.desc); + }); + } + + QueryBuilder thenByLocalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.asc); + }); + } + + QueryBuilder thenByLocalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.desc); + }); + } + + QueryBuilder thenByModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedAt', Sort.asc); + }); + } + + QueryBuilder thenByModifiedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedAt', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder thenByRemoteId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.asc); + }); + } + + QueryBuilder thenByRemoteIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.desc); + }); + } + + QueryBuilder thenByShared() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shared', Sort.asc); + }); + } + + QueryBuilder thenBySharedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shared', Sort.desc); + }); + } + + QueryBuilder thenBySortOrder() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'sortOrder', Sort.asc); + }); + } + + QueryBuilder thenBySortOrderDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'sortOrder', Sort.desc); + }); + } + + QueryBuilder thenByStartDate() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startDate', Sort.asc); + }); + } + + QueryBuilder thenByStartDateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'startDate', Sort.desc); + }); + } +} + +extension AlbumQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByActivityEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'activityEnabled'); + }); + } + + QueryBuilder distinctByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'createdAt'); + }); + } + + QueryBuilder distinctByDescription({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'description', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByEndDate() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'endDate'); + }); + } + + QueryBuilder distinctByLastModifiedAssetTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lastModifiedAssetTimestamp'); + }); + } + + QueryBuilder distinctByLocalId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'localId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'modifiedAt'); + }); + } + + QueryBuilder distinctByName({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByRemoteId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'remoteId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByShared() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'shared'); + }); + } + + QueryBuilder distinctBySortOrder() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'sortOrder'); + }); + } + + QueryBuilder distinctByStartDate() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'startDate'); + }); + } +} + +extension AlbumQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder activityEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'activityEnabled'); + }); + } + + QueryBuilder createdAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'createdAt'); + }); + } + + QueryBuilder descriptionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'description'); + }); + } + + QueryBuilder endDateProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'endDate'); + }); + } + + QueryBuilder + lastModifiedAssetTimestampProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lastModifiedAssetTimestamp'); + }); + } + + QueryBuilder localIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'localId'); + }); + } + + QueryBuilder modifiedAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'modifiedAt'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder remoteIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'remoteId'); + }); + } + + QueryBuilder sharedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shared'); + }); + } + + QueryBuilder sortOrderProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'sortOrder'); + }); + } + + QueryBuilder startDateProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'startDate'); + }); + } +} diff --git a/mobile/lib/entities/android_device_asset.entity.dart b/mobile/lib/entities/android_device_asset.entity.dart new file mode 100644 index 0000000000..792de346b9 --- /dev/null +++ b/mobile/lib/entities/android_device_asset.entity.dart @@ -0,0 +1,10 @@ +import 'package:immich_mobile/entities/device_asset.entity.dart'; +import 'package:isar/isar.dart'; + +part 'android_device_asset.entity.g.dart'; + +@Collection() +class AndroidDeviceAsset extends DeviceAsset { + AndroidDeviceAsset({required this.id, required super.hash}); + Id id; +} diff --git a/mobile/lib/entities/android_device_asset.entity.g.dart b/mobile/lib/entities/android_device_asset.entity.g.dart new file mode 100644 index 0000000000..f8b1e32c72 --- /dev/null +++ b/mobile/lib/entities/android_device_asset.entity.g.dart @@ -0,0 +1,463 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'android_device_asset.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetAndroidDeviceAssetCollection on Isar { + IsarCollection get androidDeviceAssets => + this.collection(); +} + +const AndroidDeviceAssetSchema = CollectionSchema( + name: r'AndroidDeviceAsset', + id: -6758387181232899335, + properties: { + r'hash': PropertySchema(id: 0, name: r'hash', type: IsarType.byteList), + }, + + estimateSize: _androidDeviceAssetEstimateSize, + serialize: _androidDeviceAssetSerialize, + deserialize: _androidDeviceAssetDeserialize, + deserializeProp: _androidDeviceAssetDeserializeProp, + idName: r'id', + indexes: { + r'hash': IndexSchema( + id: -7973251393006690288, + name: r'hash', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'hash', + type: IndexType.hash, + caseSensitive: false, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _androidDeviceAssetGetId, + getLinks: _androidDeviceAssetGetLinks, + attach: _androidDeviceAssetAttach, + version: '3.3.0-dev.3', +); + +int _androidDeviceAssetEstimateSize( + AndroidDeviceAsset object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.hash.length; + return bytesCount; +} + +void _androidDeviceAssetSerialize( + AndroidDeviceAsset object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeByteList(offsets[0], object.hash); +} + +AndroidDeviceAsset _androidDeviceAssetDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = AndroidDeviceAsset( + hash: reader.readByteList(offsets[0]) ?? [], + id: id, + ); + return object; +} + +P _androidDeviceAssetDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readByteList(offset) ?? []) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _androidDeviceAssetGetId(AndroidDeviceAsset object) { + return object.id; +} + +List> _androidDeviceAssetGetLinks( + AndroidDeviceAsset object, +) { + return []; +} + +void _androidDeviceAssetAttach( + IsarCollection col, + Id id, + AndroidDeviceAsset object, +) { + object.id = id; +} + +extension AndroidDeviceAssetQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension AndroidDeviceAssetQueryWhere + on QueryBuilder { + QueryBuilder + idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder + idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder + idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder + idLessThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder + idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + hashEqualTo(List hash) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'hash', value: [hash]), + ); + }); + } + + QueryBuilder + hashNotEqualTo(List hash) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ); + } + }); + } +} + +extension AndroidDeviceAssetQueryFilter + on QueryBuilder { + QueryBuilder + hashElementEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'hash', value: value), + ); + }); + } + + QueryBuilder + hashElementGreaterThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementLessThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'hash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + hashLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, true, length, true); + }); + } + + QueryBuilder + hashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, 0, true); + }); + } + + QueryBuilder + hashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, false, 999999, true); + }); + } + + QueryBuilder + hashLengthLessThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, length, include); + }); + } + + QueryBuilder + hashLengthGreaterThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, include, 999999, true); + }); + } + + QueryBuilder + hashLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'hash', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder + idGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idLessThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension AndroidDeviceAssetQueryObject + on QueryBuilder {} + +extension AndroidDeviceAssetQueryLinks + on QueryBuilder {} + +extension AndroidDeviceAssetQuerySortBy + on QueryBuilder {} + +extension AndroidDeviceAssetQuerySortThenBy + on QueryBuilder { + QueryBuilder + thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } +} + +extension AndroidDeviceAssetQueryWhereDistinct + on QueryBuilder { + QueryBuilder + distinctByHash() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'hash'); + }); + } +} + +extension AndroidDeviceAssetQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder, QQueryOperations> hashProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'hash'); + }); + } +} diff --git a/mobile/lib/entities/asset.entity.dart b/mobile/lib/entities/asset.entity.dart new file mode 100644 index 0000000000..0d549457a1 --- /dev/null +++ b/mobile/lib/entities/asset.entity.dart @@ -0,0 +1,575 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' as entity; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; +import 'package:immich_mobile/utils/diff.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; +import 'package:openapi/api.dart'; +import 'package:path/path.dart' as p; +import 'package:photo_manager/photo_manager.dart' show AssetEntity; + +part 'asset.entity.g.dart'; + +/// Asset (online or local) +@Collection(inheritance: false) +class Asset { + Asset.remote(AssetResponseDto remote) + : remoteId = remote.id, + checksum = remote.checksum, + fileCreatedAt = remote.fileCreatedAt, + fileModifiedAt = remote.fileModifiedAt, + updatedAt = remote.updatedAt, + durationInSeconds = remote.duration.toDuration()?.inSeconds ?? 0, + type = remote.type.toAssetType(), + fileName = remote.originalFileName, + height = remote.exifInfo?.exifImageHeight?.toInt(), + width = remote.exifInfo?.exifImageWidth?.toInt(), + livePhotoVideoId = remote.livePhotoVideoId, + ownerId = fastHash(remote.ownerId), + exifInfo = remote.exifInfo == null ? null : ExifDtoConverter.fromDto(remote.exifInfo!), + isFavorite = remote.isFavorite, + isArchived = remote.isArchived, + isTrashed = remote.isTrashed, + isOffline = remote.isOffline, + // workaround to nullify stackPrimaryAssetId for the parent asset until we refactor the mobile app + // stack handling to properly handle it + stackPrimaryAssetId = remote.stack?.primaryAssetId == remote.id ? null : remote.stack?.primaryAssetId, + stackCount = remote.stack?.assetCount ?? 0, + stackId = remote.stack?.id, + thumbhash = remote.thumbhash, + visibility = getVisibility(remote.visibility); + + Asset({ + this.id = Isar.autoIncrement, + required this.checksum, + this.remoteId, + required this.localId, + required this.ownerId, + required this.fileCreatedAt, + required this.fileModifiedAt, + required this.updatedAt, + required this.durationInSeconds, + required this.type, + this.width, + this.height, + required this.fileName, + this.livePhotoVideoId, + this.exifInfo, + this.isFavorite = false, + this.isArchived = false, + this.isTrashed = false, + this.stackId, + this.stackPrimaryAssetId, + this.stackCount = 0, + this.isOffline = false, + this.thumbhash, + this.visibility = AssetVisibilityEnum.timeline, + }); + + @ignore + AssetEntity? _local; + + @ignore + AssetEntity? get local { + if (isLocal && _local == null) { + _local = AssetEntity( + id: localId!, + typeInt: isImage ? 1 : 2, + width: width ?? 0, + height: height ?? 0, + duration: durationInSeconds, + createDateSecond: fileCreatedAt.millisecondsSinceEpoch ~/ 1000, + modifiedDateSecond: fileModifiedAt.millisecondsSinceEpoch ~/ 1000, + title: fileName, + ); + } + return _local; + } + + set local(AssetEntity? assetEntity) => _local = assetEntity; + + @ignore + bool _didUpdateLocal = false; + + @ignore + Future get localAsync async { + final local = this.local; + if (local == null) { + throw Exception('Asset $fileName has no local data'); + } + + final updatedLocal = _didUpdateLocal ? local : await local.obtainForNewProperties(); + if (updatedLocal == null) { + throw Exception('Could not fetch local data for $fileName'); + } + + this.local = updatedLocal; + _didUpdateLocal = true; + return updatedLocal; + } + + Id id = Isar.autoIncrement; + + /// stores the raw SHA1 bytes as a base64 String + /// because Isar cannot sort lists of byte arrays + String checksum; + + String? thumbhash; + + @Index(unique: false, replace: false, type: IndexType.hash) + String? remoteId; + + @Index(unique: false, replace: false, type: IndexType.hash) + String? localId; + + @Index(unique: true, replace: false, composite: [CompositeIndex("checksum", type: IndexType.hash)]) + int ownerId; + + DateTime fileCreatedAt; + + DateTime fileModifiedAt; + + DateTime updatedAt; + + int durationInSeconds; + + @Enumerated(EnumType.ordinal) + AssetType type; + + short? width; + + short? height; + + String fileName; + + String? livePhotoVideoId; + + bool isFavorite; + + bool isArchived; + + bool isTrashed; + + bool isOffline; + + @ignore + ExifInfo? exifInfo; + + String? stackId; + + String? stackPrimaryAssetId; + + int stackCount; + + @Enumerated(EnumType.ordinal) + AssetVisibilityEnum visibility; + + /// Returns null if the asset has no sync access to the exif info + @ignore + double? get aspectRatio { + final orientatedWidth = this.orientatedWidth; + final orientatedHeight = this.orientatedHeight; + + if (orientatedWidth != null && orientatedHeight != null && orientatedWidth > 0 && orientatedHeight > 0) { + return orientatedWidth.toDouble() / orientatedHeight.toDouble(); + } + + return null; + } + + /// `true` if this [Asset] is present on the device + @ignore + bool get isLocal => localId != null; + + @ignore + bool get isInDb => id != Isar.autoIncrement; + + @ignore + String get name => p.withoutExtension(fileName); + + /// `true` if this [Asset] is present on the server + @ignore + bool get isRemote => remoteId != null; + + @ignore + bool get isImage => type == AssetType.image; + + @ignore + bool get isVideo => type == AssetType.video; + + @ignore + bool get isMotionPhoto => livePhotoVideoId != null; + + @ignore + AssetState get storage { + if (isRemote && isLocal) { + return AssetState.merged; + } else if (isRemote) { + return AssetState.remote; + } else if (isLocal) { + return AssetState.local; + } else { + throw Exception("Asset has illegal state: $this"); + } + } + + @ignore + Duration get duration => Duration(seconds: durationInSeconds); + + // ignore: invalid_annotation_target + @ignore + set byteHash(List hash) => checksum = base64.encode(hash); + + /// Returns null if the asset has no sync access to the exif info + @ignore + @pragma('vm:prefer-inline') + bool? get isFlipped { + final exifInfo = this.exifInfo; + if (exifInfo != null) { + return exifInfo.isFlipped; + } + + if (_didUpdateLocal && Platform.isAndroid) { + final local = this.local; + if (local == null) { + throw Exception('Asset $fileName has no local data'); + } + return local.orientation == 90 || local.orientation == 270; + } + + return null; + } + + /// Returns null if the asset has no sync access to the exif info + @ignore + @pragma('vm:prefer-inline') + int? get orientatedHeight { + final isFlipped = this.isFlipped; + if (isFlipped == null) { + return null; + } + + return isFlipped ? width : height; + } + + /// Returns null if the asset has no sync access to the exif info + @ignore + @pragma('vm:prefer-inline') + int? get orientatedWidth { + final isFlipped = this.isFlipped; + if (isFlipped == null) { + return null; + } + + return isFlipped ? height : width; + } + + @override + bool operator ==(other) { + if (other is! Asset) return false; + if (identical(this, other)) return true; + return id == other.id && + checksum == other.checksum && + remoteId == other.remoteId && + localId == other.localId && + ownerId == other.ownerId && + fileCreatedAt.isAtSameMomentAs(other.fileCreatedAt) && + fileModifiedAt.isAtSameMomentAs(other.fileModifiedAt) && + updatedAt.isAtSameMomentAs(other.updatedAt) && + durationInSeconds == other.durationInSeconds && + type == other.type && + width == other.width && + height == other.height && + fileName == other.fileName && + livePhotoVideoId == other.livePhotoVideoId && + isFavorite == other.isFavorite && + isLocal == other.isLocal && + isArchived == other.isArchived && + isTrashed == other.isTrashed && + stackCount == other.stackCount && + stackPrimaryAssetId == other.stackPrimaryAssetId && + stackId == other.stackId; + } + + @override + @ignore + int get hashCode => + id.hashCode ^ + checksum.hashCode ^ + remoteId.hashCode ^ + localId.hashCode ^ + ownerId.hashCode ^ + fileCreatedAt.hashCode ^ + fileModifiedAt.hashCode ^ + updatedAt.hashCode ^ + durationInSeconds.hashCode ^ + type.hashCode ^ + width.hashCode ^ + height.hashCode ^ + fileName.hashCode ^ + livePhotoVideoId.hashCode ^ + isFavorite.hashCode ^ + isLocal.hashCode ^ + isArchived.hashCode ^ + isTrashed.hashCode ^ + stackCount.hashCode ^ + stackPrimaryAssetId.hashCode ^ + stackId.hashCode; + + /// Returns `true` if this [Asset] can updated with values from parameter [a] + bool canUpdate(Asset a) { + assert(isInDb); + assert(checksum == a.checksum); + assert(a.storage != AssetState.merged); + return a.updatedAt.isAfter(updatedAt) || + a.isRemote && !isRemote || + a.isLocal && !isLocal || + width == null && a.width != null || + height == null && a.height != null || + livePhotoVideoId == null && a.livePhotoVideoId != null || + isFavorite != a.isFavorite || + isArchived != a.isArchived || + isTrashed != a.isTrashed || + isOffline != a.isOffline || + a.exifInfo?.latitude != exifInfo?.latitude || + a.exifInfo?.longitude != exifInfo?.longitude || + // no local stack count or different count from remote + a.thumbhash != thumbhash || + stackId != a.stackId || + stackCount != a.stackCount || + stackPrimaryAssetId == null && a.stackPrimaryAssetId != null || + visibility != a.visibility; + } + + /// Returns a new [Asset] with values from this and merged & updated with [a] + Asset updatedCopy(Asset a) { + assert(canUpdate(a)); + if (a.updatedAt.isAfter(updatedAt)) { + // take most values from newer asset + // keep vales that can never be set by the asset not in DB + if (a.isRemote) { + return a.copyWith( + id: id, + localId: localId, + width: a.width ?? width, + height: a.height ?? height, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, + ); + } else if (isRemote) { + return copyWith( + localId: localId ?? a.localId, + width: width ?? a.width, + height: height ?? a.height, + exifInfo: exifInfo ?? a.exifInfo?.copyWith(assetId: id), + ); + } else { + // TODO: Revisit this and remove all bool field assignments + return a.copyWith( + id: id, + remoteId: remoteId, + livePhotoVideoId: livePhotoVideoId, + // workaround to nullify stackPrimaryAssetId for the parent asset until we refactor the mobile app + // stack handling to properly handle it + stackId: stackId, + stackPrimaryAssetId: stackPrimaryAssetId == remoteId ? null : stackPrimaryAssetId, + stackCount: stackCount, + isFavorite: isFavorite, + isArchived: isArchived, + isTrashed: isTrashed, + isOffline: isOffline, + ); + } + } else { + // fill in potentially missing values, i.e. merge assets + if (a.isRemote) { + // values from remote take precedence + return copyWith( + remoteId: a.remoteId, + width: a.width, + height: a.height, + livePhotoVideoId: a.livePhotoVideoId, + // workaround to nullify stackPrimaryAssetId for the parent asset until we refactor the mobile app + // stack handling to properly handle it + stackId: a.stackId, + stackPrimaryAssetId: a.stackPrimaryAssetId == a.remoteId ? null : a.stackPrimaryAssetId, + stackCount: a.stackCount, + // isFavorite + isArchived are not set by device-only assets + isFavorite: a.isFavorite, + isArchived: a.isArchived, + isTrashed: a.isTrashed, + isOffline: a.isOffline, + exifInfo: a.exifInfo?.copyWith(assetId: id) ?? exifInfo, + thumbhash: a.thumbhash, + ); + } else { + // add only missing values (and set isLocal to true) + return copyWith( + localId: localId ?? a.localId, + width: width ?? a.width, + height: height ?? a.height, + exifInfo: exifInfo ?? a.exifInfo?.copyWith(assetId: id), // updated to use assetId + ); + } + } + } + + Asset copyWith({ + Id? id, + String? checksum, + String? remoteId, + String? localId, + int? ownerId, + DateTime? fileCreatedAt, + DateTime? fileModifiedAt, + DateTime? updatedAt, + int? durationInSeconds, + AssetType? type, + short? width, + short? height, + String? fileName, + String? livePhotoVideoId, + bool? isFavorite, + bool? isArchived, + bool? isTrashed, + bool? isOffline, + ExifInfo? exifInfo, + String? stackId, + String? stackPrimaryAssetId, + int? stackCount, + String? thumbhash, + AssetVisibilityEnum? visibility, + }) => Asset( + id: id ?? this.id, + checksum: checksum ?? this.checksum, + remoteId: remoteId ?? this.remoteId, + localId: localId ?? this.localId, + ownerId: ownerId ?? this.ownerId, + fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, + fileModifiedAt: fileModifiedAt ?? this.fileModifiedAt, + updatedAt: updatedAt ?? this.updatedAt, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + type: type ?? this.type, + width: width ?? this.width, + height: height ?? this.height, + fileName: fileName ?? this.fileName, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + isFavorite: isFavorite ?? this.isFavorite, + isArchived: isArchived ?? this.isArchived, + isTrashed: isTrashed ?? this.isTrashed, + isOffline: isOffline ?? this.isOffline, + exifInfo: exifInfo ?? this.exifInfo, + stackId: stackId ?? this.stackId, + stackPrimaryAssetId: stackPrimaryAssetId ?? this.stackPrimaryAssetId, + stackCount: stackCount ?? this.stackCount, + thumbhash: thumbhash ?? this.thumbhash, + visibility: visibility ?? this.visibility, + ); + + Future put(Isar db) async { + await db.assets.put(this); + if (exifInfo != null) { + await db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo!.copyWith(assetId: id))); + } + } + + static int compareById(Asset a, Asset b) => a.id.compareTo(b.id); + + static int compareByLocalId(Asset a, Asset b) => compareToNullable(a.localId, b.localId); + + static int compareByChecksum(Asset a, Asset b) => a.checksum.compareTo(b.checksum); + + static int compareByOwnerChecksum(Asset a, Asset b) { + final int ownerIdOrder = a.ownerId.compareTo(b.ownerId); + if (ownerIdOrder != 0) return ownerIdOrder; + return compareByChecksum(a, b); + } + + static int compareByOwnerChecksumCreatedModified(Asset a, Asset b) { + final int ownerIdOrder = a.ownerId.compareTo(b.ownerId); + if (ownerIdOrder != 0) return ownerIdOrder; + final int checksumOrder = compareByChecksum(a, b); + if (checksumOrder != 0) return checksumOrder; + final int createdOrder = a.fileCreatedAt.compareTo(b.fileCreatedAt); + if (createdOrder != 0) return createdOrder; + return a.fileModifiedAt.compareTo(b.fileModifiedAt); + } + + @override + String toString() { + return """ +{ + "id": ${id == Isar.autoIncrement ? '"N/A"' : id}, + "remoteId": "${remoteId ?? "N/A"}", + "localId": "${localId ?? "N/A"}", + "checksum": "$checksum", + "ownerId": $ownerId, + "livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}", + "stackId": "${stackId ?? "N/A"}", + "stackPrimaryAssetId": "${stackPrimaryAssetId ?? "N/A"}", + "stackCount": "$stackCount", + "fileCreatedAt": "$fileCreatedAt", + "fileModifiedAt": "$fileModifiedAt", + "updatedAt": "$updatedAt", + "durationInSeconds": $durationInSeconds, + "type": "$type", + "fileName": "$fileName", + "isFavorite": $isFavorite, + "isRemote": $isRemote, + "storage": "$storage", + "width": ${width ?? "N/A"}, + "height": ${height ?? "N/A"}, + "isArchived": $isArchived, + "isTrashed": $isTrashed, + "isOffline": $isOffline, + "visibility": "$visibility", +}"""; + } + + static getVisibility(AssetVisibility visibility) => switch (visibility) { + AssetVisibility.archive => AssetVisibilityEnum.archive, + AssetVisibility.hidden => AssetVisibilityEnum.hidden, + AssetVisibility.locked => AssetVisibilityEnum.locked, + AssetVisibility.timeline || _ => AssetVisibilityEnum.timeline, + }; +} + +enum AssetType { + // do not change this order! + other, + image, + video, + audio, +} + +extension AssetTypeEnumHelper on AssetTypeEnum { + AssetType toAssetType() => switch (this) { + AssetTypeEnum.IMAGE => AssetType.image, + AssetTypeEnum.VIDEO => AssetType.video, + AssetTypeEnum.AUDIO => AssetType.audio, + AssetTypeEnum.OTHER => AssetType.other, + _ => throw Exception(), + }; +} + +/// Describes where the information of this asset came from: +/// only from the local device, only from the remote server or merged from both +enum AssetState { local, remote, merged } + +extension AssetsHelper on IsarCollection { + Future deleteAllByRemoteId(Iterable ids) => ids.isEmpty ? Future.value(0) : remote(ids).deleteAll(); + Future deleteAllByLocalId(Iterable ids) => ids.isEmpty ? Future.value(0) : local(ids).deleteAll(); + Future> getAllByRemoteId(Iterable ids) => ids.isEmpty ? Future.value([]) : remote(ids).findAll(); + Future> getAllByLocalId(Iterable ids) => ids.isEmpty ? Future.value([]) : local(ids).findAll(); + Future getByRemoteId(String id) => where().remoteIdEqualTo(id).findFirst(); + + QueryBuilder remote(Iterable ids) => + where().anyOf(ids, (q, String e) => q.remoteIdEqualTo(e)); + QueryBuilder local(Iterable ids) { + return where().anyOf(ids, (q, String e) => q.localIdEqualTo(e)); + } +} diff --git a/mobile/lib/entities/asset.entity.g.dart b/mobile/lib/entities/asset.entity.g.dart new file mode 100644 index 0000000000..db6bc72331 --- /dev/null +++ b/mobile/lib/entities/asset.entity.g.dart @@ -0,0 +1,3711 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetAssetCollection on Isar { + IsarCollection get assets => this.collection(); +} + +const AssetSchema = CollectionSchema( + name: r'Asset', + id: -2933289051367723566, + properties: { + r'checksum': PropertySchema( + id: 0, + name: r'checksum', + type: IsarType.string, + ), + r'durationInSeconds': PropertySchema( + id: 1, + name: r'durationInSeconds', + type: IsarType.long, + ), + r'fileCreatedAt': PropertySchema( + id: 2, + name: r'fileCreatedAt', + type: IsarType.dateTime, + ), + r'fileModifiedAt': PropertySchema( + id: 3, + name: r'fileModifiedAt', + type: IsarType.dateTime, + ), + r'fileName': PropertySchema( + id: 4, + name: r'fileName', + type: IsarType.string, + ), + r'height': PropertySchema(id: 5, name: r'height', type: IsarType.int), + r'isArchived': PropertySchema( + id: 6, + name: r'isArchived', + type: IsarType.bool, + ), + r'isFavorite': PropertySchema( + id: 7, + name: r'isFavorite', + type: IsarType.bool, + ), + r'isOffline': PropertySchema( + id: 8, + name: r'isOffline', + type: IsarType.bool, + ), + r'isTrashed': PropertySchema( + id: 9, + name: r'isTrashed', + type: IsarType.bool, + ), + r'livePhotoVideoId': PropertySchema( + id: 10, + name: r'livePhotoVideoId', + type: IsarType.string, + ), + r'localId': PropertySchema(id: 11, name: r'localId', type: IsarType.string), + r'ownerId': PropertySchema(id: 12, name: r'ownerId', type: IsarType.long), + r'remoteId': PropertySchema( + id: 13, + name: r'remoteId', + type: IsarType.string, + ), + r'stackCount': PropertySchema( + id: 14, + name: r'stackCount', + type: IsarType.long, + ), + r'stackId': PropertySchema(id: 15, name: r'stackId', type: IsarType.string), + r'stackPrimaryAssetId': PropertySchema( + id: 16, + name: r'stackPrimaryAssetId', + type: IsarType.string, + ), + r'thumbhash': PropertySchema( + id: 17, + name: r'thumbhash', + type: IsarType.string, + ), + r'type': PropertySchema( + id: 18, + name: r'type', + type: IsarType.byte, + enumMap: _AssettypeEnumValueMap, + ), + r'updatedAt': PropertySchema( + id: 19, + name: r'updatedAt', + type: IsarType.dateTime, + ), + r'visibility': PropertySchema( + id: 20, + name: r'visibility', + type: IsarType.byte, + enumMap: _AssetvisibilityEnumValueMap, + ), + r'width': PropertySchema(id: 21, name: r'width', type: IsarType.int), + }, + + estimateSize: _assetEstimateSize, + serialize: _assetSerialize, + deserialize: _assetDeserialize, + deserializeProp: _assetDeserializeProp, + idName: r'id', + indexes: { + r'remoteId': IndexSchema( + id: 6301175856541681032, + name: r'remoteId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'remoteId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + r'localId': IndexSchema( + id: 1199848425898359622, + name: r'localId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'localId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + r'ownerId_checksum': IndexSchema( + id: -3295822444433175883, + name: r'ownerId_checksum', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'ownerId', + type: IndexType.value, + caseSensitive: false, + ), + IndexPropertySchema( + name: r'checksum', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _assetGetId, + getLinks: _assetGetLinks, + attach: _assetAttach, + version: '3.3.0-dev.3', +); + +int _assetEstimateSize( + Asset object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.checksum.length * 3; + bytesCount += 3 + object.fileName.length * 3; + { + final value = object.livePhotoVideoId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.localId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.remoteId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.stackId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.stackPrimaryAssetId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.thumbhash; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _assetSerialize( + Asset object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.checksum); + writer.writeLong(offsets[1], object.durationInSeconds); + writer.writeDateTime(offsets[2], object.fileCreatedAt); + writer.writeDateTime(offsets[3], object.fileModifiedAt); + writer.writeString(offsets[4], object.fileName); + writer.writeInt(offsets[5], object.height); + writer.writeBool(offsets[6], object.isArchived); + writer.writeBool(offsets[7], object.isFavorite); + writer.writeBool(offsets[8], object.isOffline); + writer.writeBool(offsets[9], object.isTrashed); + writer.writeString(offsets[10], object.livePhotoVideoId); + writer.writeString(offsets[11], object.localId); + writer.writeLong(offsets[12], object.ownerId); + writer.writeString(offsets[13], object.remoteId); + writer.writeLong(offsets[14], object.stackCount); + writer.writeString(offsets[15], object.stackId); + writer.writeString(offsets[16], object.stackPrimaryAssetId); + writer.writeString(offsets[17], object.thumbhash); + writer.writeByte(offsets[18], object.type.index); + writer.writeDateTime(offsets[19], object.updatedAt); + writer.writeByte(offsets[20], object.visibility.index); + writer.writeInt(offsets[21], object.width); +} + +Asset _assetDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Asset( + checksum: reader.readString(offsets[0]), + durationInSeconds: reader.readLong(offsets[1]), + fileCreatedAt: reader.readDateTime(offsets[2]), + fileModifiedAt: reader.readDateTime(offsets[3]), + fileName: reader.readString(offsets[4]), + height: reader.readIntOrNull(offsets[5]), + id: id, + isArchived: reader.readBoolOrNull(offsets[6]) ?? false, + isFavorite: reader.readBoolOrNull(offsets[7]) ?? false, + isOffline: reader.readBoolOrNull(offsets[8]) ?? false, + isTrashed: reader.readBoolOrNull(offsets[9]) ?? false, + livePhotoVideoId: reader.readStringOrNull(offsets[10]), + localId: reader.readStringOrNull(offsets[11]), + ownerId: reader.readLong(offsets[12]), + remoteId: reader.readStringOrNull(offsets[13]), + stackCount: reader.readLongOrNull(offsets[14]) ?? 0, + stackId: reader.readStringOrNull(offsets[15]), + stackPrimaryAssetId: reader.readStringOrNull(offsets[16]), + thumbhash: reader.readStringOrNull(offsets[17]), + type: + _AssettypeValueEnumMap[reader.readByteOrNull(offsets[18])] ?? + AssetType.other, + updatedAt: reader.readDateTime(offsets[19]), + visibility: + _AssetvisibilityValueEnumMap[reader.readByteOrNull(offsets[20])] ?? + AssetVisibilityEnum.timeline, + width: reader.readIntOrNull(offsets[21]), + ); + return object; +} + +P _assetDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readLong(offset)) as P; + case 2: + return (reader.readDateTime(offset)) as P; + case 3: + return (reader.readDateTime(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (reader.readIntOrNull(offset)) as P; + case 6: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 7: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 8: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 9: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 10: + return (reader.readStringOrNull(offset)) as P; + case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: + return (reader.readLong(offset)) as P; + case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: + return (reader.readLongOrNull(offset) ?? 0) as P; + case 15: + return (reader.readStringOrNull(offset)) as P; + case 16: + return (reader.readStringOrNull(offset)) as P; + case 17: + return (reader.readStringOrNull(offset)) as P; + case 18: + return (_AssettypeValueEnumMap[reader.readByteOrNull(offset)] ?? + AssetType.other) + as P; + case 19: + return (reader.readDateTime(offset)) as P; + case 20: + return (_AssetvisibilityValueEnumMap[reader.readByteOrNull(offset)] ?? + AssetVisibilityEnum.timeline) + as P; + case 21: + return (reader.readIntOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _AssettypeEnumValueMap = {'other': 0, 'image': 1, 'video': 2, 'audio': 3}; +const _AssettypeValueEnumMap = { + 0: AssetType.other, + 1: AssetType.image, + 2: AssetType.video, + 3: AssetType.audio, +}; +const _AssetvisibilityEnumValueMap = { + 'timeline': 0, + 'hidden': 1, + 'archive': 2, + 'locked': 3, +}; +const _AssetvisibilityValueEnumMap = { + 0: AssetVisibilityEnum.timeline, + 1: AssetVisibilityEnum.hidden, + 2: AssetVisibilityEnum.archive, + 3: AssetVisibilityEnum.locked, +}; + +Id _assetGetId(Asset object) { + return object.id; +} + +List> _assetGetLinks(Asset object) { + return []; +} + +void _assetAttach(IsarCollection col, Id id, Asset object) { + object.id = id; +} + +extension AssetByIndex on IsarCollection { + Future getByOwnerIdChecksum(int ownerId, String checksum) { + return getByIndex(r'ownerId_checksum', [ownerId, checksum]); + } + + Asset? getByOwnerIdChecksumSync(int ownerId, String checksum) { + return getByIndexSync(r'ownerId_checksum', [ownerId, checksum]); + } + + Future deleteByOwnerIdChecksum(int ownerId, String checksum) { + return deleteByIndex(r'ownerId_checksum', [ownerId, checksum]); + } + + bool deleteByOwnerIdChecksumSync(int ownerId, String checksum) { + return deleteByIndexSync(r'ownerId_checksum', [ownerId, checksum]); + } + + Future> getAllByOwnerIdChecksum( + List ownerIdValues, + List checksumValues, + ) { + final len = ownerIdValues.length; + assert( + checksumValues.length == len, + 'All index values must have the same length', + ); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([ownerIdValues[i], checksumValues[i]]); + } + + return getAllByIndex(r'ownerId_checksum', values); + } + + List getAllByOwnerIdChecksumSync( + List ownerIdValues, + List checksumValues, + ) { + final len = ownerIdValues.length; + assert( + checksumValues.length == len, + 'All index values must have the same length', + ); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([ownerIdValues[i], checksumValues[i]]); + } + + return getAllByIndexSync(r'ownerId_checksum', values); + } + + Future deleteAllByOwnerIdChecksum( + List ownerIdValues, + List checksumValues, + ) { + final len = ownerIdValues.length; + assert( + checksumValues.length == len, + 'All index values must have the same length', + ); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([ownerIdValues[i], checksumValues[i]]); + } + + return deleteAllByIndex(r'ownerId_checksum', values); + } + + int deleteAllByOwnerIdChecksumSync( + List ownerIdValues, + List checksumValues, + ) { + final len = ownerIdValues.length; + assert( + checksumValues.length == len, + 'All index values must have the same length', + ); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([ownerIdValues[i], checksumValues[i]]); + } + + return deleteAllByIndexSync(r'ownerId_checksum', values); + } + + Future putByOwnerIdChecksum(Asset object) { + return putByIndex(r'ownerId_checksum', object); + } + + Id putByOwnerIdChecksumSync(Asset object, {bool saveLinks = true}) { + return putByIndexSync(r'ownerId_checksum', object, saveLinks: saveLinks); + } + + Future> putAllByOwnerIdChecksum(List objects) { + return putAllByIndex(r'ownerId_checksum', objects); + } + + List putAllByOwnerIdChecksumSync( + List objects, { + bool saveLinks = true, + }) { + return putAllByIndexSync( + r'ownerId_checksum', + objects, + saveLinks: saveLinks, + ); + } +} + +extension AssetQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension AssetQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder remoteIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'remoteId', value: [null]), + ); + }); + } + + QueryBuilder remoteIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [null], + includeLower: false, + upper: [], + ), + ); + }); + } + + QueryBuilder remoteIdEqualTo( + String? remoteId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'remoteId', value: [remoteId]), + ); + }); + } + + QueryBuilder remoteIdNotEqualTo( + String? remoteId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [], + upper: [remoteId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [remoteId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [remoteId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'remoteId', + lower: [], + upper: [remoteId], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder localIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'localId', value: [null]), + ); + }); + } + + QueryBuilder localIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [null], + includeLower: false, + upper: [], + ), + ); + }); + } + + QueryBuilder localIdEqualTo( + String? localId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'localId', value: [localId]), + ); + }); + } + + QueryBuilder localIdNotEqualTo( + String? localId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [], + upper: [localId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [localId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [localId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'localId', + lower: [], + upper: [localId], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder ownerIdEqualToAnyChecksum( + int ownerId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo( + indexName: r'ownerId_checksum', + value: [ownerId], + ), + ); + }); + } + + QueryBuilder ownerIdNotEqualToAnyChecksum( + int ownerId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [], + upper: [ownerId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [], + upper: [ownerId], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder ownerIdGreaterThanAnyChecksum( + int ownerId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId], + includeLower: include, + upper: [], + ), + ); + }); + } + + QueryBuilder ownerIdLessThanAnyChecksum( + int ownerId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [], + upper: [ownerId], + includeUpper: include, + ), + ); + }); + } + + QueryBuilder ownerIdBetweenAnyChecksum( + int lowerOwnerId, + int upperOwnerId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [lowerOwnerId], + includeLower: includeLower, + upper: [upperOwnerId], + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder ownerIdChecksumEqualTo( + int ownerId, + String checksum, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo( + indexName: r'ownerId_checksum', + value: [ownerId, checksum], + ), + ); + }); + } + + QueryBuilder + ownerIdEqualToChecksumNotEqualTo(int ownerId, String checksum) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId], + upper: [ownerId, checksum], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId, checksum], + includeLower: false, + upper: [ownerId], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId, checksum], + includeLower: false, + upper: [ownerId], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ownerId_checksum', + lower: [ownerId], + upper: [ownerId, checksum], + includeUpper: false, + ), + ); + } + }); + } +} + +extension AssetQueryFilter on QueryBuilder { + QueryBuilder checksumEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumBetween( + 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'checksum', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'checksum', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'checksum', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder checksumIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'checksum', value: ''), + ); + }); + } + + QueryBuilder checksumIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'checksum', value: ''), + ); + }); + } + + QueryBuilder durationInSecondsEqualTo( + int value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'durationInSeconds', value: value), + ); + }); + } + + QueryBuilder + durationInSecondsGreaterThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'durationInSeconds', + value: value, + ), + ); + }); + } + + QueryBuilder durationInSecondsLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'durationInSeconds', + value: value, + ), + ); + }); + } + + QueryBuilder durationInSecondsBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'durationInSeconds', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder fileCreatedAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'fileCreatedAt', value: value), + ); + }); + } + + QueryBuilder fileCreatedAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'fileCreatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder fileCreatedAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'fileCreatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder fileCreatedAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'fileCreatedAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder fileModifiedAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'fileModifiedAt', value: value), + ); + }); + } + + QueryBuilder fileModifiedAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'fileModifiedAt', + value: value, + ), + ); + }); + } + + QueryBuilder fileModifiedAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'fileModifiedAt', + value: value, + ), + ); + }); + } + + QueryBuilder fileModifiedAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'fileModifiedAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder fileNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameBetween( + 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'fileName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'fileName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'fileName', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder fileNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'fileName', value: ''), + ); + }); + } + + QueryBuilder fileNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'fileName', value: ''), + ); + }); + } + + QueryBuilder heightIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'height'), + ); + }); + } + + QueryBuilder heightIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'height'), + ); + }); + } + + QueryBuilder heightEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'height', value: value), + ); + }); + } + + QueryBuilder heightGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'height', + value: value, + ), + ); + }); + } + + QueryBuilder heightLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'height', + value: value, + ), + ); + }); + } + + QueryBuilder heightBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'height', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder isArchivedEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isArchived', value: value), + ); + }); + } + + QueryBuilder isFavoriteEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isFavorite', value: value), + ); + }); + } + + QueryBuilder isOfflineEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isOffline', value: value), + ); + }); + } + + QueryBuilder isTrashedEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isTrashed', value: value), + ); + }); + } + + QueryBuilder livePhotoVideoIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'livePhotoVideoId'), + ); + }); + } + + QueryBuilder + livePhotoVideoIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'livePhotoVideoId'), + ); + }); + } + + QueryBuilder livePhotoVideoIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdBetween( + 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'livePhotoVideoId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'livePhotoVideoId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'livePhotoVideoId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder livePhotoVideoIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'livePhotoVideoId', value: ''), + ); + }); + } + + QueryBuilder + livePhotoVideoIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'livePhotoVideoId', value: ''), + ); + }); + } + + QueryBuilder localIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'localId'), + ); + }); + } + + QueryBuilder localIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'localId'), + ); + }); + } + + QueryBuilder localIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdBetween( + 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'localId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'localId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'localId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder localIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'localId', value: ''), + ); + }); + } + + QueryBuilder localIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'localId', value: ''), + ); + }); + } + + QueryBuilder ownerIdEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'ownerId', value: value), + ); + }); + } + + QueryBuilder ownerIdGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'ownerId', + value: value, + ), + ); + }); + } + + QueryBuilder ownerIdLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'ownerId', + value: value, + ), + ); + }); + } + + QueryBuilder ownerIdBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'ownerId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder remoteIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'remoteId'), + ); + }); + } + + QueryBuilder remoteIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'remoteId'), + ); + }); + } + + QueryBuilder remoteIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdBetween( + 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'remoteId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'remoteId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'remoteId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder remoteIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'remoteId', value: ''), + ); + }); + } + + QueryBuilder remoteIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'remoteId', value: ''), + ); + }); + } + + QueryBuilder stackCountEqualTo( + int value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'stackCount', value: value), + ); + }); + } + + QueryBuilder stackCountGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'stackCount', + value: value, + ), + ); + }); + } + + QueryBuilder stackCountLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'stackCount', + value: value, + ), + ); + }); + } + + QueryBuilder stackCountBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'stackCount', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder stackIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'stackId'), + ); + }); + } + + QueryBuilder stackIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'stackId'), + ); + }); + } + + QueryBuilder stackIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdBetween( + 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'stackId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'stackId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'stackId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'stackId', value: ''), + ); + }); + } + + QueryBuilder stackIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'stackId', value: ''), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'stackPrimaryAssetId'), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'stackPrimaryAssetId'), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdBetween( + 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'stackPrimaryAssetId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'stackPrimaryAssetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stackPrimaryAssetIdMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'stackPrimaryAssetId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'stackPrimaryAssetId', value: ''), + ); + }); + } + + QueryBuilder + stackPrimaryAssetIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + property: r'stackPrimaryAssetId', + value: '', + ), + ); + }); + } + + QueryBuilder thumbhashIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'thumbhash'), + ); + }); + } + + QueryBuilder thumbhashIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'thumbhash'), + ); + }); + } + + QueryBuilder thumbhashEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashBetween( + 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'thumbhash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'thumbhash', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'thumbhash', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder thumbhashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'thumbhash', value: ''), + ); + }); + } + + QueryBuilder thumbhashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'thumbhash', value: ''), + ); + }); + } + + QueryBuilder typeEqualTo( + AssetType value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'type', value: value), + ); + }); + } + + QueryBuilder typeGreaterThan( + AssetType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'type', + value: value, + ), + ); + }); + } + + QueryBuilder typeLessThan( + AssetType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'type', + value: value, + ), + ); + }); + } + + QueryBuilder typeBetween( + AssetType lower, + AssetType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'type', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder updatedAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'updatedAt', value: value), + ); + }); + } + + QueryBuilder updatedAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'updatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder updatedAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'updatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder updatedAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'updatedAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder visibilityEqualTo( + AssetVisibilityEnum value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'visibility', value: value), + ); + }); + } + + QueryBuilder visibilityGreaterThan( + AssetVisibilityEnum value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'visibility', + value: value, + ), + ); + }); + } + + QueryBuilder visibilityLessThan( + AssetVisibilityEnum value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'visibility', + value: value, + ), + ); + }); + } + + QueryBuilder visibilityBetween( + AssetVisibilityEnum lower, + AssetVisibilityEnum upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'visibility', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder widthIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'width'), + ); + }); + } + + QueryBuilder widthIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'width'), + ); + }); + } + + QueryBuilder widthEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'width', value: value), + ); + }); + } + + QueryBuilder widthGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'width', + value: value, + ), + ); + }); + } + + QueryBuilder widthLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'width', + value: value, + ), + ); + }); + } + + QueryBuilder widthBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'width', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension AssetQueryObject on QueryBuilder {} + +extension AssetQueryLinks on QueryBuilder {} + +extension AssetQuerySortBy on QueryBuilder { + QueryBuilder sortByChecksum() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checksum', Sort.asc); + }); + } + + QueryBuilder sortByChecksumDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checksum', Sort.desc); + }); + } + + QueryBuilder sortByDurationInSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'durationInSeconds', Sort.asc); + }); + } + + QueryBuilder sortByDurationInSecondsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'durationInSeconds', Sort.desc); + }); + } + + QueryBuilder sortByFileCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileCreatedAt', Sort.asc); + }); + } + + QueryBuilder sortByFileCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileCreatedAt', Sort.desc); + }); + } + + QueryBuilder sortByFileModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileModifiedAt', Sort.asc); + }); + } + + QueryBuilder sortByFileModifiedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileModifiedAt', Sort.desc); + }); + } + + QueryBuilder sortByFileName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileName', Sort.asc); + }); + } + + QueryBuilder sortByFileNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileName', Sort.desc); + }); + } + + QueryBuilder sortByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.asc); + }); + } + + QueryBuilder sortByHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.desc); + }); + } + + QueryBuilder sortByIsArchived() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isArchived', Sort.asc); + }); + } + + QueryBuilder sortByIsArchivedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isArchived', Sort.desc); + }); + } + + QueryBuilder sortByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.asc); + }); + } + + QueryBuilder sortByIsFavoriteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.desc); + }); + } + + QueryBuilder sortByIsOffline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isOffline', Sort.asc); + }); + } + + QueryBuilder sortByIsOfflineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isOffline', Sort.desc); + }); + } + + QueryBuilder sortByIsTrashed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isTrashed', Sort.asc); + }); + } + + QueryBuilder sortByIsTrashedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isTrashed', Sort.desc); + }); + } + + QueryBuilder sortByLivePhotoVideoId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'livePhotoVideoId', Sort.asc); + }); + } + + QueryBuilder sortByLivePhotoVideoIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'livePhotoVideoId', Sort.desc); + }); + } + + QueryBuilder sortByLocalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.asc); + }); + } + + QueryBuilder sortByLocalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.desc); + }); + } + + QueryBuilder sortByOwnerId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ownerId', Sort.asc); + }); + } + + QueryBuilder sortByOwnerIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ownerId', Sort.desc); + }); + } + + QueryBuilder sortByRemoteId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.asc); + }); + } + + QueryBuilder sortByRemoteIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.desc); + }); + } + + QueryBuilder sortByStackCount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackCount', Sort.asc); + }); + } + + QueryBuilder sortByStackCountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackCount', Sort.desc); + }); + } + + QueryBuilder sortByStackId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackId', Sort.asc); + }); + } + + QueryBuilder sortByStackIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackId', Sort.desc); + }); + } + + QueryBuilder sortByStackPrimaryAssetId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackPrimaryAssetId', Sort.asc); + }); + } + + QueryBuilder sortByStackPrimaryAssetIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackPrimaryAssetId', Sort.desc); + }); + } + + QueryBuilder sortByThumbhash() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbhash', Sort.asc); + }); + } + + QueryBuilder sortByThumbhashDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbhash', Sort.desc); + }); + } + + QueryBuilder sortByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder sortByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder sortByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.asc); + }); + } + + QueryBuilder sortByUpdatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.desc); + }); + } + + QueryBuilder sortByVisibility() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'visibility', Sort.asc); + }); + } + + QueryBuilder sortByVisibilityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'visibility', Sort.desc); + }); + } + + QueryBuilder sortByWidth() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'width', Sort.asc); + }); + } + + QueryBuilder sortByWidthDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'width', Sort.desc); + }); + } +} + +extension AssetQuerySortThenBy on QueryBuilder { + QueryBuilder thenByChecksum() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checksum', Sort.asc); + }); + } + + QueryBuilder thenByChecksumDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checksum', Sort.desc); + }); + } + + QueryBuilder thenByDurationInSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'durationInSeconds', Sort.asc); + }); + } + + QueryBuilder thenByDurationInSecondsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'durationInSeconds', Sort.desc); + }); + } + + QueryBuilder thenByFileCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileCreatedAt', Sort.asc); + }); + } + + QueryBuilder thenByFileCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileCreatedAt', Sort.desc); + }); + } + + QueryBuilder thenByFileModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileModifiedAt', Sort.asc); + }); + } + + QueryBuilder thenByFileModifiedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileModifiedAt', Sort.desc); + }); + } + + QueryBuilder thenByFileName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileName', Sort.asc); + }); + } + + QueryBuilder thenByFileNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileName', Sort.desc); + }); + } + + QueryBuilder thenByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.asc); + }); + } + + QueryBuilder thenByHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsArchived() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isArchived', Sort.asc); + }); + } + + QueryBuilder thenByIsArchivedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isArchived', Sort.desc); + }); + } + + QueryBuilder thenByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.asc); + }); + } + + QueryBuilder thenByIsFavoriteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.desc); + }); + } + + QueryBuilder thenByIsOffline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isOffline', Sort.asc); + }); + } + + QueryBuilder thenByIsOfflineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isOffline', Sort.desc); + }); + } + + QueryBuilder thenByIsTrashed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isTrashed', Sort.asc); + }); + } + + QueryBuilder thenByIsTrashedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isTrashed', Sort.desc); + }); + } + + QueryBuilder thenByLivePhotoVideoId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'livePhotoVideoId', Sort.asc); + }); + } + + QueryBuilder thenByLivePhotoVideoIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'livePhotoVideoId', Sort.desc); + }); + } + + QueryBuilder thenByLocalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.asc); + }); + } + + QueryBuilder thenByLocalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'localId', Sort.desc); + }); + } + + QueryBuilder thenByOwnerId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ownerId', Sort.asc); + }); + } + + QueryBuilder thenByOwnerIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ownerId', Sort.desc); + }); + } + + QueryBuilder thenByRemoteId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.asc); + }); + } + + QueryBuilder thenByRemoteIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'remoteId', Sort.desc); + }); + } + + QueryBuilder thenByStackCount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackCount', Sort.asc); + }); + } + + QueryBuilder thenByStackCountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackCount', Sort.desc); + }); + } + + QueryBuilder thenByStackId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackId', Sort.asc); + }); + } + + QueryBuilder thenByStackIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackId', Sort.desc); + }); + } + + QueryBuilder thenByStackPrimaryAssetId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackPrimaryAssetId', Sort.asc); + }); + } + + QueryBuilder thenByStackPrimaryAssetIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackPrimaryAssetId', Sort.desc); + }); + } + + QueryBuilder thenByThumbhash() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbhash', Sort.asc); + }); + } + + QueryBuilder thenByThumbhashDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'thumbhash', Sort.desc); + }); + } + + QueryBuilder thenByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder thenByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder thenByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.asc); + }); + } + + QueryBuilder thenByUpdatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.desc); + }); + } + + QueryBuilder thenByVisibility() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'visibility', Sort.asc); + }); + } + + QueryBuilder thenByVisibilityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'visibility', Sort.desc); + }); + } + + QueryBuilder thenByWidth() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'width', Sort.asc); + }); + } + + QueryBuilder thenByWidthDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'width', Sort.desc); + }); + } +} + +extension AssetQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByChecksum({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checksum', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByDurationInSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'durationInSeconds'); + }); + } + + QueryBuilder distinctByFileCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fileCreatedAt'); + }); + } + + QueryBuilder distinctByFileModifiedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fileModifiedAt'); + }); + } + + QueryBuilder distinctByFileName({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fileName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'height'); + }); + } + + QueryBuilder distinctByIsArchived() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isArchived'); + }); + } + + QueryBuilder distinctByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isFavorite'); + }); + } + + QueryBuilder distinctByIsOffline() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isOffline'); + }); + } + + QueryBuilder distinctByIsTrashed() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isTrashed'); + }); + } + + QueryBuilder distinctByLivePhotoVideoId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'livePhotoVideoId', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder distinctByLocalId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'localId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByOwnerId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ownerId'); + }); + } + + QueryBuilder distinctByRemoteId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'remoteId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByStackCount() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stackCount'); + }); + } + + QueryBuilder distinctByStackId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stackId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByStackPrimaryAssetId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'stackPrimaryAssetId', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder distinctByThumbhash({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'thumbhash', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'type'); + }); + } + + QueryBuilder distinctByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'updatedAt'); + }); + } + + QueryBuilder distinctByVisibility() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'visibility'); + }); + } + + QueryBuilder distinctByWidth() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'width'); + }); + } +} + +extension AssetQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder checksumProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checksum'); + }); + } + + QueryBuilder durationInSecondsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'durationInSeconds'); + }); + } + + QueryBuilder fileCreatedAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fileCreatedAt'); + }); + } + + QueryBuilder fileModifiedAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fileModifiedAt'); + }); + } + + QueryBuilder fileNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fileName'); + }); + } + + QueryBuilder heightProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'height'); + }); + } + + QueryBuilder isArchivedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isArchived'); + }); + } + + QueryBuilder isFavoriteProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isFavorite'); + }); + } + + QueryBuilder isOfflineProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isOffline'); + }); + } + + QueryBuilder isTrashedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isTrashed'); + }); + } + + QueryBuilder livePhotoVideoIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'livePhotoVideoId'); + }); + } + + QueryBuilder localIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'localId'); + }); + } + + QueryBuilder ownerIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ownerId'); + }); + } + + QueryBuilder remoteIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'remoteId'); + }); + } + + QueryBuilder stackCountProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackCount'); + }); + } + + QueryBuilder stackIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackId'); + }); + } + + QueryBuilder stackPrimaryAssetIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackPrimaryAssetId'); + }); + } + + QueryBuilder thumbhashProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'thumbhash'); + }); + } + + QueryBuilder typeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'type'); + }); + } + + QueryBuilder updatedAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'updatedAt'); + }); + } + + QueryBuilder + visibilityProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'visibility'); + }); + } + + QueryBuilder widthProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'width'); + }); + } +} diff --git a/mobile/lib/entities/backup_album.entity.dart b/mobile/lib/entities/backup_album.entity.dart new file mode 100644 index 0000000000..ad2a5d6718 --- /dev/null +++ b/mobile/lib/entities/backup_album.entity.dart @@ -0,0 +1,22 @@ +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'backup_album.entity.g.dart'; + +@Collection(inheritance: false) +class BackupAlbum { + String id; + DateTime lastBackup; + @Enumerated(EnumType.ordinal) + BackupSelection selection; + + BackupAlbum(this.id, this.lastBackup, this.selection); + + Id get isarId => fastHash(id); + + BackupAlbum copyWith({String? id, DateTime? lastBackup, BackupSelection? selection}) { + return BackupAlbum(id ?? this.id, lastBackup ?? this.lastBackup, selection ?? this.selection); + } +} + +enum BackupSelection { none, select, exclude } diff --git a/mobile/lib/entities/backup_album.entity.g.dart b/mobile/lib/entities/backup_album.entity.g.dart new file mode 100644 index 0000000000..583aa55c4d --- /dev/null +++ b/mobile/lib/entities/backup_album.entity.g.dart @@ -0,0 +1,679 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'backup_album.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetBackupAlbumCollection on Isar { + IsarCollection get backupAlbums => this.collection(); +} + +const BackupAlbumSchema = CollectionSchema( + name: r'BackupAlbum', + id: 8308487201128361847, + properties: { + r'id': PropertySchema(id: 0, name: r'id', type: IsarType.string), + r'lastBackup': PropertySchema( + id: 1, + name: r'lastBackup', + type: IsarType.dateTime, + ), + r'selection': PropertySchema( + id: 2, + name: r'selection', + type: IsarType.byte, + enumMap: _BackupAlbumselectionEnumValueMap, + ), + }, + + estimateSize: _backupAlbumEstimateSize, + serialize: _backupAlbumSerialize, + deserialize: _backupAlbumDeserialize, + deserializeProp: _backupAlbumDeserializeProp, + idName: r'isarId', + indexes: {}, + links: {}, + embeddedSchemas: {}, + + getId: _backupAlbumGetId, + getLinks: _backupAlbumGetLinks, + attach: _backupAlbumAttach, + version: '3.3.0-dev.3', +); + +int _backupAlbumEstimateSize( + BackupAlbum object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.id.length * 3; + return bytesCount; +} + +void _backupAlbumSerialize( + BackupAlbum object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.id); + writer.writeDateTime(offsets[1], object.lastBackup); + writer.writeByte(offsets[2], object.selection.index); +} + +BackupAlbum _backupAlbumDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = BackupAlbum( + reader.readString(offsets[0]), + reader.readDateTime(offsets[1]), + _BackupAlbumselectionValueEnumMap[reader.readByteOrNull(offsets[2])] ?? + BackupSelection.none, + ); + return object; +} + +P _backupAlbumDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readDateTime(offset)) as P; + case 2: + return (_BackupAlbumselectionValueEnumMap[reader.readByteOrNull( + offset, + )] ?? + BackupSelection.none) + as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _BackupAlbumselectionEnumValueMap = { + 'none': 0, + 'select': 1, + 'exclude': 2, +}; +const _BackupAlbumselectionValueEnumMap = { + 0: BackupSelection.none, + 1: BackupSelection.select, + 2: BackupSelection.exclude, +}; + +Id _backupAlbumGetId(BackupAlbum object) { + return object.isarId; +} + +List> _backupAlbumGetLinks(BackupAlbum object) { + return []; +} + +void _backupAlbumAttach( + IsarCollection col, + Id id, + BackupAlbum object, +) {} + +extension BackupAlbumQueryWhereSort + on QueryBuilder { + QueryBuilder anyIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension BackupAlbumQueryWhere + on QueryBuilder { + QueryBuilder isarIdEqualTo( + Id isarId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between(lower: isarId, upper: isarId), + ); + }); + } + + QueryBuilder isarIdNotEqualTo( + Id isarId, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ); + } + }); + } + + QueryBuilder isarIdGreaterThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: include), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: include), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lowerIsarId, + Id upperIsarId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerIsarId, + includeLower: includeLower, + upper: upperIsarId, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension BackupAlbumQueryFilter + on QueryBuilder { + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + 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'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'id', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: ''), + ); + }); + } + + QueryBuilder idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'id', value: ''), + ); + }); + } + + QueryBuilder isarIdEqualTo( + Id value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isarId', value: value), + ); + }); + } + + QueryBuilder + isarIdGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'isarId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + lastBackupEqualTo(DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'lastBackup', value: value), + ); + }); + } + + QueryBuilder + lastBackupGreaterThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'lastBackup', + value: value, + ), + ); + }); + } + + QueryBuilder + lastBackupLessThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'lastBackup', + value: value, + ), + ); + }); + } + + QueryBuilder + lastBackupBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'lastBackup', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + selectionEqualTo(BackupSelection value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'selection', value: value), + ); + }); + } + + QueryBuilder + selectionGreaterThan(BackupSelection value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'selection', + value: value, + ), + ); + }); + } + + QueryBuilder + selectionLessThan(BackupSelection value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'selection', + value: value, + ), + ); + }); + } + + QueryBuilder + selectionBetween( + BackupSelection lower, + BackupSelection upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'selection', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension BackupAlbumQueryObject + on QueryBuilder {} + +extension BackupAlbumQueryLinks + on QueryBuilder {} + +extension BackupAlbumQuerySortBy + on QueryBuilder { + QueryBuilder sortById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder sortByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder sortByLastBackup() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastBackup', Sort.asc); + }); + } + + QueryBuilder sortByLastBackupDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastBackup', Sort.desc); + }); + } + + QueryBuilder sortBySelection() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'selection', Sort.asc); + }); + } + + QueryBuilder sortBySelectionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'selection', Sort.desc); + }); + } +} + +extension BackupAlbumQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.asc); + }); + } + + QueryBuilder thenByIsarIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.desc); + }); + } + + QueryBuilder thenByLastBackup() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastBackup', Sort.asc); + }); + } + + QueryBuilder thenByLastBackupDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lastBackup', Sort.desc); + }); + } + + QueryBuilder thenBySelection() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'selection', Sort.asc); + }); + } + + QueryBuilder thenBySelectionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'selection', Sort.desc); + }); + } +} + +extension BackupAlbumQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctById({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'id', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByLastBackup() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lastBackup'); + }); + } + + QueryBuilder distinctBySelection() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'selection'); + }); + } +} + +extension BackupAlbumQueryProperty + on QueryBuilder { + QueryBuilder isarIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isarId'); + }); + } + + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder lastBackupProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lastBackup'); + }); + } + + QueryBuilder + selectionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'selection'); + }); + } +} diff --git a/mobile/lib/entities/device_asset.entity.dart b/mobile/lib/entities/device_asset.entity.dart new file mode 100644 index 0000000000..0973dd4ff8 --- /dev/null +++ b/mobile/lib/entities/device_asset.entity.dart @@ -0,0 +1,8 @@ +import 'package:isar/isar.dart'; + +class DeviceAsset { + DeviceAsset({required this.hash}); + + @Index(unique: false, type: IndexType.hash) + List hash; +} diff --git a/mobile/lib/entities/duplicated_asset.entity.dart b/mobile/lib/entities/duplicated_asset.entity.dart new file mode 100644 index 0000000000..9368dc1a52 --- /dev/null +++ b/mobile/lib/entities/duplicated_asset.entity.dart @@ -0,0 +1,11 @@ +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'duplicated_asset.entity.g.dart'; + +@Collection(inheritance: false) +class DuplicatedAsset { + String id; + DuplicatedAsset(this.id); + Id get isarId => fastHash(id); +} diff --git a/mobile/lib/entities/duplicated_asset.entity.g.dart b/mobile/lib/entities/duplicated_asset.entity.g.dart new file mode 100644 index 0000000000..80d2f344e6 --- /dev/null +++ b/mobile/lib/entities/duplicated_asset.entity.g.dart @@ -0,0 +1,444 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'duplicated_asset.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetDuplicatedAssetCollection on Isar { + IsarCollection get duplicatedAssets => this.collection(); +} + +const DuplicatedAssetSchema = CollectionSchema( + name: r'DuplicatedAsset', + id: -2679334728174694496, + properties: { + r'id': PropertySchema(id: 0, name: r'id', type: IsarType.string), + }, + + estimateSize: _duplicatedAssetEstimateSize, + serialize: _duplicatedAssetSerialize, + deserialize: _duplicatedAssetDeserialize, + deserializeProp: _duplicatedAssetDeserializeProp, + idName: r'isarId', + indexes: {}, + links: {}, + embeddedSchemas: {}, + + getId: _duplicatedAssetGetId, + getLinks: _duplicatedAssetGetLinks, + attach: _duplicatedAssetAttach, + version: '3.3.0-dev.3', +); + +int _duplicatedAssetEstimateSize( + DuplicatedAsset object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.id.length * 3; + return bytesCount; +} + +void _duplicatedAssetSerialize( + DuplicatedAsset object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.id); +} + +DuplicatedAsset _duplicatedAssetDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = DuplicatedAsset(reader.readString(offsets[0])); + return object; +} + +P _duplicatedAssetDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _duplicatedAssetGetId(DuplicatedAsset object) { + return object.isarId; +} + +List> _duplicatedAssetGetLinks(DuplicatedAsset object) { + return []; +} + +void _duplicatedAssetAttach( + IsarCollection col, + Id id, + DuplicatedAsset object, +) {} + +extension DuplicatedAssetQueryWhereSort + on QueryBuilder { + QueryBuilder anyIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension DuplicatedAssetQueryWhere + on QueryBuilder { + QueryBuilder + isarIdEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between(lower: isarId, upper: isarId), + ); + }); + } + + QueryBuilder + isarIdNotEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ); + } + }); + } + + QueryBuilder + isarIdGreaterThan(Id isarId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: include), + ); + }); + } + + QueryBuilder + isarIdLessThan(Id isarId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: include), + ); + }); + } + + QueryBuilder + isarIdBetween( + Id lowerIsarId, + Id upperIsarId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerIsarId, + includeLower: includeLower, + upper: upperIsarId, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension DuplicatedAssetQueryFilter + on QueryBuilder { + QueryBuilder + idEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idLessThan(String value, {bool include = false, bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idBetween( + 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'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'id', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: ''), + ); + }); + } + + QueryBuilder + idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'id', value: ''), + ); + }); + } + + QueryBuilder + isarIdEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isarId', value: value), + ); + }); + } + + QueryBuilder + isarIdGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder + isarIdLessThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder + isarIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'isarId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension DuplicatedAssetQueryObject + on QueryBuilder {} + +extension DuplicatedAssetQueryLinks + on QueryBuilder {} + +extension DuplicatedAssetQuerySortBy + on QueryBuilder { + QueryBuilder sortById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder sortByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } +} + +extension DuplicatedAssetQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.asc); + }); + } + + QueryBuilder + thenByIsarIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.desc); + }); + } +} + +extension DuplicatedAssetQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctById({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'id', caseSensitive: caseSensitive); + }); + } +} + +extension DuplicatedAssetQueryProperty + on QueryBuilder { + QueryBuilder isarIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isarId'); + }); + } + + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } +} diff --git a/mobile/lib/entities/etag.entity.dart b/mobile/lib/entities/etag.entity.dart new file mode 100644 index 0000000000..3b8ef39c61 --- /dev/null +++ b/mobile/lib/entities/etag.entity.dart @@ -0,0 +1,14 @@ +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'etag.entity.g.dart'; + +@Collection(inheritance: false) +class ETag { + ETag({required this.id, this.assetCount, this.time}); + Id get isarId => fastHash(id); + @Index(unique: true, replace: true, type: IndexType.hash) + String id; + int? assetCount; + DateTime? time; +} diff --git a/mobile/lib/entities/etag.entity.g.dart b/mobile/lib/entities/etag.entity.g.dart new file mode 100644 index 0000000000..03b4ea9918 --- /dev/null +++ b/mobile/lib/entities/etag.entity.g.dart @@ -0,0 +1,796 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'etag.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetETagCollection on Isar { + IsarCollection get eTags => this.collection(); +} + +const ETagSchema = CollectionSchema( + name: r'ETag', + id: -644290296585643859, + properties: { + r'assetCount': PropertySchema( + id: 0, + name: r'assetCount', + type: IsarType.long, + ), + r'id': PropertySchema(id: 1, name: r'id', type: IsarType.string), + r'time': PropertySchema(id: 2, name: r'time', type: IsarType.dateTime), + }, + + estimateSize: _eTagEstimateSize, + serialize: _eTagSerialize, + deserialize: _eTagDeserialize, + deserializeProp: _eTagDeserializeProp, + idName: r'isarId', + indexes: { + r'id': IndexSchema( + id: -3268401673993471357, + name: r'id', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'id', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _eTagGetId, + getLinks: _eTagGetLinks, + attach: _eTagAttach, + version: '3.3.0-dev.3', +); + +int _eTagEstimateSize( + ETag object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.id.length * 3; + return bytesCount; +} + +void _eTagSerialize( + ETag object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.assetCount); + writer.writeString(offsets[1], object.id); + writer.writeDateTime(offsets[2], object.time); +} + +ETag _eTagDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ETag( + assetCount: reader.readLongOrNull(offsets[0]), + id: reader.readString(offsets[1]), + time: reader.readDateTimeOrNull(offsets[2]), + ); + return object; +} + +P _eTagDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readDateTimeOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _eTagGetId(ETag object) { + return object.isarId; +} + +List> _eTagGetLinks(ETag object) { + return []; +} + +void _eTagAttach(IsarCollection col, Id id, ETag object) {} + +extension ETagByIndex on IsarCollection { + Future getById(String id) { + return getByIndex(r'id', [id]); + } + + ETag? getByIdSync(String id) { + return getByIndexSync(r'id', [id]); + } + + Future deleteById(String id) { + return deleteByIndex(r'id', [id]); + } + + bool deleteByIdSync(String id) { + return deleteByIndexSync(r'id', [id]); + } + + Future> getAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndex(r'id', values); + } + + List getAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'id', values); + } + + Future deleteAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'id', values); + } + + int deleteAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'id', values); + } + + Future putById(ETag object) { + return putByIndex(r'id', object); + } + + Id putByIdSync(ETag object, {bool saveLinks = true}) { + return putByIndexSync(r'id', object, saveLinks: saveLinks); + } + + Future> putAllById(List objects) { + return putAllByIndex(r'id', objects); + } + + List putAllByIdSync(List objects, {bool saveLinks = true}) { + return putAllByIndexSync(r'id', objects, saveLinks: saveLinks); + } +} + +extension ETagQueryWhereSort on QueryBuilder { + QueryBuilder anyIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension ETagQueryWhere on QueryBuilder { + QueryBuilder isarIdEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between(lower: isarId, upper: isarId), + ); + }); + } + + QueryBuilder isarIdNotEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ); + } + }); + } + + QueryBuilder isarIdGreaterThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: include), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: include), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lowerIsarId, + Id upperIsarId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerIsarId, + includeLower: includeLower, + upper: upperIsarId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo(String id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'id', value: [id]), + ); + }); + } + + QueryBuilder idNotEqualTo(String id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ); + } + }); + } +} + +extension ETagQueryFilter on QueryBuilder { + QueryBuilder assetCountIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'assetCount'), + ); + }); + } + + QueryBuilder assetCountIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'assetCount'), + ); + }); + } + + QueryBuilder assetCountEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'assetCount', value: value), + ); + }); + } + + QueryBuilder assetCountGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'assetCount', + value: value, + ), + ); + }); + } + + QueryBuilder assetCountLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'assetCount', + value: value, + ), + ); + }); + } + + QueryBuilder assetCountBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'assetCount', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + 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'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'id', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: ''), + ); + }); + } + + QueryBuilder idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'id', value: ''), + ); + }); + } + + QueryBuilder isarIdEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isarId', value: value), + ); + }); + } + + QueryBuilder isarIdGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'isarId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder timeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'time'), + ); + }); + } + + QueryBuilder timeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'time'), + ); + }); + } + + QueryBuilder timeEqualTo(DateTime? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'time', value: value), + ); + }); + } + + QueryBuilder timeGreaterThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'time', + value: value, + ), + ); + }); + } + + QueryBuilder timeLessThan( + DateTime? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'time', + value: value, + ), + ); + }); + } + + QueryBuilder timeBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'time', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension ETagQueryObject on QueryBuilder {} + +extension ETagQueryLinks on QueryBuilder {} + +extension ETagQuerySortBy on QueryBuilder { + QueryBuilder sortByAssetCount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetCount', Sort.asc); + }); + } + + QueryBuilder sortByAssetCountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetCount', Sort.desc); + }); + } + + QueryBuilder sortById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder sortByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder sortByTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'time', Sort.asc); + }); + } + + QueryBuilder sortByTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'time', Sort.desc); + }); + } +} + +extension ETagQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAssetCount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetCount', Sort.asc); + }); + } + + QueryBuilder thenByAssetCountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetCount', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.asc); + }); + } + + QueryBuilder thenByIsarIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.desc); + }); + } + + QueryBuilder thenByTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'time', Sort.asc); + }); + } + + QueryBuilder thenByTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'time', Sort.desc); + }); + } +} + +extension ETagQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAssetCount() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'assetCount'); + }); + } + + QueryBuilder distinctById({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'id', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'time'); + }); + } +} + +extension ETagQueryProperty on QueryBuilder { + QueryBuilder isarIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isarId'); + }); + } + + QueryBuilder assetCountProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'assetCount'); + }); + } + + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder timeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'time'); + }); + } +} diff --git a/mobile/lib/entities/ios_device_asset.entity.dart b/mobile/lib/entities/ios_device_asset.entity.dart new file mode 100644 index 0000000000..dfd0a660f8 --- /dev/null +++ b/mobile/lib/entities/ios_device_asset.entity.dart @@ -0,0 +1,14 @@ +import 'package:immich_mobile/entities/device_asset.entity.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'ios_device_asset.entity.g.dart'; + +@Collection() +class IOSDeviceAsset extends DeviceAsset { + IOSDeviceAsset({required this.id, required super.hash}); + + @Index(replace: true, unique: true, type: IndexType.hash) + String id; + Id get isarId => fastHash(id); +} diff --git a/mobile/lib/entities/ios_device_asset.entity.g.dart b/mobile/lib/entities/ios_device_asset.entity.g.dart new file mode 100644 index 0000000000..252fe127bb --- /dev/null +++ b/mobile/lib/entities/ios_device_asset.entity.g.dart @@ -0,0 +1,766 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'ios_device_asset.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetIOSDeviceAssetCollection on Isar { + IsarCollection get iOSDeviceAssets => this.collection(); +} + +const IOSDeviceAssetSchema = CollectionSchema( + name: r'IOSDeviceAsset', + id: -1671546753821948030, + properties: { + r'hash': PropertySchema(id: 0, name: r'hash', type: IsarType.byteList), + r'id': PropertySchema(id: 1, name: r'id', type: IsarType.string), + }, + + estimateSize: _iOSDeviceAssetEstimateSize, + serialize: _iOSDeviceAssetSerialize, + deserialize: _iOSDeviceAssetDeserialize, + deserializeProp: _iOSDeviceAssetDeserializeProp, + idName: r'isarId', + indexes: { + r'id': IndexSchema( + id: -3268401673993471357, + name: r'id', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'id', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + r'hash': IndexSchema( + id: -7973251393006690288, + name: r'hash', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'hash', + type: IndexType.hash, + caseSensitive: false, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _iOSDeviceAssetGetId, + getLinks: _iOSDeviceAssetGetLinks, + attach: _iOSDeviceAssetAttach, + version: '3.3.0-dev.3', +); + +int _iOSDeviceAssetEstimateSize( + IOSDeviceAsset object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.hash.length; + bytesCount += 3 + object.id.length * 3; + return bytesCount; +} + +void _iOSDeviceAssetSerialize( + IOSDeviceAsset object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeByteList(offsets[0], object.hash); + writer.writeString(offsets[1], object.id); +} + +IOSDeviceAsset _iOSDeviceAssetDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = IOSDeviceAsset( + hash: reader.readByteList(offsets[0]) ?? [], + id: reader.readString(offsets[1]), + ); + return object; +} + +P _iOSDeviceAssetDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readByteList(offset) ?? []) as P; + case 1: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _iOSDeviceAssetGetId(IOSDeviceAsset object) { + return object.isarId; +} + +List> _iOSDeviceAssetGetLinks(IOSDeviceAsset object) { + return []; +} + +void _iOSDeviceAssetAttach( + IsarCollection col, + Id id, + IOSDeviceAsset object, +) {} + +extension IOSDeviceAssetByIndex on IsarCollection { + Future getById(String id) { + return getByIndex(r'id', [id]); + } + + IOSDeviceAsset? getByIdSync(String id) { + return getByIndexSync(r'id', [id]); + } + + Future deleteById(String id) { + return deleteByIndex(r'id', [id]); + } + + bool deleteByIdSync(String id) { + return deleteByIndexSync(r'id', [id]); + } + + Future> getAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndex(r'id', values); + } + + List getAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'id', values); + } + + Future deleteAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'id', values); + } + + int deleteAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'id', values); + } + + Future putById(IOSDeviceAsset object) { + return putByIndex(r'id', object); + } + + Id putByIdSync(IOSDeviceAsset object, {bool saveLinks = true}) { + return putByIndexSync(r'id', object, saveLinks: saveLinks); + } + + Future> putAllById(List objects) { + return putAllByIndex(r'id', objects); + } + + List putAllByIdSync( + List objects, { + bool saveLinks = true, + }) { + return putAllByIndexSync(r'id', objects, saveLinks: saveLinks); + } +} + +extension IOSDeviceAssetQueryWhereSort + on QueryBuilder { + QueryBuilder anyIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension IOSDeviceAssetQueryWhere + on QueryBuilder { + QueryBuilder isarIdEqualTo( + Id isarId, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between(lower: isarId, upper: isarId), + ); + }); + } + + QueryBuilder + isarIdNotEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ); + } + }); + } + + QueryBuilder + isarIdGreaterThan(Id isarId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: include), + ); + }); + } + + QueryBuilder + isarIdLessThan(Id isarId, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: include), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lowerIsarId, + Id upperIsarId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerIsarId, + includeLower: includeLower, + upper: upperIsarId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo( + String id, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'id', value: [id]), + ); + }); + } + + QueryBuilder idNotEqualTo( + String id, + ) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder hashEqualTo( + List hash, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'hash', value: [hash]), + ); + }); + } + + QueryBuilder + hashNotEqualTo(List hash) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ); + } + }); + } +} + +extension IOSDeviceAssetQueryFilter + on QueryBuilder { + QueryBuilder + hashElementEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'hash', value: value), + ); + }); + } + + QueryBuilder + hashElementGreaterThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementLessThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'hash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + hashLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, true, length, true); + }); + } + + QueryBuilder + hashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, 0, true); + }); + } + + QueryBuilder + hashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, false, 999999, true); + }); + } + + QueryBuilder + hashLengthLessThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, length, include); + }); + } + + QueryBuilder + hashLengthGreaterThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, include, 999999, true); + }); + } + + QueryBuilder + hashLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'hash', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idLessThan(String value, {bool include = false, bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + 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'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'id', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: ''), + ); + }); + } + + QueryBuilder + idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'id', value: ''), + ); + }); + } + + QueryBuilder + isarIdEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isarId', value: value), + ); + }); + } + + QueryBuilder + isarIdGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder + isarIdLessThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder + isarIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'isarId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension IOSDeviceAssetQueryObject + on QueryBuilder {} + +extension IOSDeviceAssetQueryLinks + on QueryBuilder {} + +extension IOSDeviceAssetQuerySortBy + on QueryBuilder { + QueryBuilder sortById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder sortByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } +} + +extension IOSDeviceAssetQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.asc); + }); + } + + QueryBuilder + thenByIsarIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.desc); + }); + } +} + +extension IOSDeviceAssetQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByHash() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'hash'); + }); + } + + QueryBuilder distinctById({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'id', caseSensitive: caseSensitive); + }); + } +} + +extension IOSDeviceAssetQueryProperty + on QueryBuilder { + QueryBuilder isarIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isarId'); + }); + } + + QueryBuilder, QQueryOperations> hashProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'hash'); + }); + } + + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } +} diff --git a/mobile/lib/entities/store.entity.dart b/mobile/lib/entities/store.entity.dart index 17ad88cee9..7b59e119d6 100644 --- a/mobile/lib/entities/store.entity.dart +++ b/mobile/lib/entities/store.entity.dart @@ -1,4 +1,38 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; // ignore: non_constant_identifier_names final Store = StoreService.I; + +class SSLClientCertStoreVal { + final Uint8List data; + final String? password; + + const SSLClientCertStoreVal(this.data, this.password); + + Future save() async { + final b64Str = base64Encode(data); + await Store.put(StoreKey.sslClientCertData, b64Str); + if (password != null) { + await Store.put(StoreKey.sslClientPasswd, password!); + } + } + + static SSLClientCertStoreVal? load() { + final b64Str = Store.tryGet(StoreKey.sslClientCertData); + if (b64Str == null) { + return null; + } + final Uint8List certData = base64Decode(b64Str); + final passwd = Store.tryGet(StoreKey.sslClientPasswd); + return SSLClientCertStoreVal(certData, passwd); + } + + static Future delete() async { + await Store.delete(StoreKey.sslClientCertData); + await Store.delete(StoreKey.sslClientPasswd); + } +} diff --git a/mobile/lib/extensions/asset_extensions.dart b/mobile/lib/extensions/asset_extensions.dart index ec1aa80ceb..a8ca7ef2aa 100644 --- a/mobile/lib/extensions/asset_extensions.dart +++ b/mobile/lib/extensions/asset_extensions.dart @@ -1,71 +1,17 @@ -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/exif.model.dart'; -import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; -import 'package:openapi/api.dart' as api; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/timezone.dart'; -extension DTOToAsset on api.AssetResponseDto { - RemoteAsset toDto() { - return RemoteAsset( - id: id, - name: originalFileName, - checksum: checksum, - createdAt: fileCreatedAt, - updatedAt: updatedAt, - uploadedAt: createdAt, - ownerId: ownerId, - visibility: visibility.toAssetVisibility(), - durationMs: duration, - height: height, - width: width, - isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId.orElse(null), - thumbHash: thumbhash, - localId: null, - type: type.toAssetType(), - stackId: stack.orElse(null)?.id, - isEdited: isEdited, - ); - } +extension TZExtension on Asset { + /// Returns the created time of the asset from the exif info (if available) or from + /// the fileCreatedAt field, adjusted to the timezone value from the exif info along with + /// the timezone offset in [Duration] + (DateTime, Duration) getTZAdjustedTimeAndOffset() { + DateTime dt = fileCreatedAt.toLocal(); - RemoteAssetExif toDtoWithExif() { - return RemoteAssetExif( - id: id, - name: originalFileName, - checksum: checksum, - createdAt: fileCreatedAt, - updatedAt: updatedAt, - uploadedAt: createdAt, - ownerId: ownerId, - visibility: visibility.toAssetVisibility(), - durationMs: duration, - height: height, - width: width, - isFavorite: isFavorite, - livePhotoVideoId: livePhotoVideoId.orElse(null), - thumbHash: thumbhash, - localId: null, - type: type.toAssetType(), - stackId: stack.orElse(null)?.id, - isEdited: isEdited, - exifInfo: exifInfo.orElse(null) != null ? ExifDtoConverter.fromDto(exifInfo.orElse(null)!) : const ExifInfo(), - ); + if (exifInfo?.dateTimeOriginal != null) { + return applyTimezoneOffset(dateTime: exifInfo!.dateTimeOriginal!, timeZone: exifInfo?.timeZone); + } + + return (dt, dt.timeZoneOffset); } } - -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, - }; -} - -extension on api.AssetTypeEnum { - AssetType toAssetType() => switch (this) { - api.AssetTypeEnum.IMAGE => AssetType.image, - api.AssetTypeEnum.VIDEO => AssetType.video, - api.AssetTypeEnum.AUDIO => AssetType.audio, - api.AssetTypeEnum.OTHER => AssetType.other, - }; -} diff --git a/mobile/lib/extensions/collection_extensions.dart b/mobile/lib/extensions/collection_extensions.dart index 53e8e73fd1..541db7ccaf 100644 --- a/mobile/lib/extensions/collection_extensions.dart +++ b/mobile/lib/extensions/collection_extensions.dart @@ -1,12 +1,14 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/hash.dart'; extension ListExtension on List { List uniqueConsecutive({int Function(E a, E b)? compare, void Function(E a, E b)? onDuplicate}) { compare ??= (E a, E b) => a == b ? 0 : 1; - int i = 1; - int j = 1; + int i = 1, j = 1; for (; i < length; i++) { if (compare(this[i - 1], this[i]) != 0) { if (i != j) { @@ -38,6 +40,31 @@ extension IntListExtension on Iterable { } } +extension AssetListExtension on Iterable { + /// Returns the assets that are already available in the Immich server + Iterable remoteOnly({void Function()? errorCallback}) { + final bool onlyRemote = every((e) => e.isRemote); + if (!onlyRemote) { + if (errorCallback != null) errorCallback(); + return where((a) => a.isRemote); + } + return this; + } + + /// Returns the assets that are owned by the user passed to the [owner] param + /// If [owner] is null, an empty list is returned + Iterable ownedOnly(UserDto? owner, {void Function()? errorCallback}) { + if (owner == null) return []; + final isarUserId = fastHash(owner.id); + final bool onlyOwned = every((e) => e.ownerId == isarUserId); + if (!onlyOwned) { + if (errorCallback != null) errorCallback(); + return where((a) => a.ownerId == isarUserId); + } + return this; + } +} + extension SortedByProperty on Iterable { Iterable sortedByField(Comparable Function(T e) key) { return sorted((a, b) => key(a).compareTo(key(b))); diff --git a/mobile/lib/extensions/datetime_extensions.dart b/mobile/lib/extensions/datetime_extensions.dart index 6a51d15df0..0bc95565a6 100644 --- a/mobile/lib/extensions/datetime_extensions.dart +++ b/mobile/lib/extensions/datetime_extensions.dart @@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; extension TimeAgoExtension on DateTime { /// Displays the time difference of this [DateTime] object to the current time as a [String] String timeAgo({bool numericDates = true}) { - final DateTime date = toLocal(); + DateTime date = toLocal(); final now = DateTime.now().toLocal(); final difference = now.difference(date); @@ -13,27 +13,27 @@ extension TimeAgoExtension on DateTime { } else if (difference.inSeconds < 60) { return '${difference.inSeconds} seconds ago'; } else if (difference.inMinutes <= 1) { - return numericDates ? '1 minute ago' : 'A minute ago'; + return (numericDates) ? '1 minute ago' : 'A minute ago'; } else if (difference.inMinutes < 60) { return '${difference.inMinutes} minutes ago'; } else if (difference.inHours <= 1) { - return numericDates ? '1 hour ago' : 'An hour ago'; + return (numericDates) ? '1 hour ago' : 'An hour ago'; } else if (difference.inHours < 60) { return '${difference.inHours} hours ago'; } else if (difference.inDays <= 1) { - return numericDates ? '1 day ago' : 'Yesterday'; + return (numericDates) ? '1 day ago' : 'Yesterday'; } else if (difference.inDays < 6) { return '${difference.inDays} days ago'; } else if ((difference.inDays / 7).ceil() <= 1) { - return numericDates ? '1 week ago' : 'Last week'; + return (numericDates) ? '1 week ago' : 'Last week'; } else if ((difference.inDays / 7).ceil() < 4) { return '${(difference.inDays / 7).ceil()} weeks ago'; } else if ((difference.inDays / 30).ceil() <= 1) { - return numericDates ? '1 month ago' : 'Last month'; + return (numericDates) ? '1 month ago' : 'Last month'; } else if ((difference.inDays / 30).ceil() < 30) { return '${(difference.inDays / 30).ceil()} months ago'; } else if ((difference.inDays / 365).ceil() <= 1) { - return numericDates ? '1 year ago' : 'Last year'; + return (numericDates) ? '1 year ago' : 'Last year'; } return '${(difference.inDays / 365).floor()} years ago'; } diff --git a/mobile/lib/extensions/object_extensions.dart b/mobile/lib/extensions/object_extensions.dart deleted file mode 100644 index 4e76532137..0000000000 --- a/mobile/lib/extensions/object_extensions.dart +++ /dev/null @@ -1,3 +0,0 @@ -extension Let on T { - R let(R Function(T) transform) => transform(this); -} diff --git a/mobile/lib/extensions/scroll_extensions.dart b/mobile/lib/extensions/scroll_extensions.dart index eb11734c67..5b8f9e2a13 100644 --- a/mobile/lib/extensions/scroll_extensions.dart +++ b/mobile/lib/extensions/scroll_extensions.dart @@ -94,12 +94,8 @@ class SnapScrollPhysics extends ScrollPhysics { bool get allowUserScrolling => false; static double target(ScrollMetrics position, double velocity, double snapOffset) { - if (velocity > _minFlingVelocity) { - return snapOffset; - } - if (velocity < -_minFlingVelocity) { - return position.pixels < snapOffset ? 0.0 : snapOffset; - } + if (velocity > _minFlingVelocity) return snapOffset; + if (velocity < -_minFlingVelocity) return position.pixels < snapOffset ? 0.0 : snapOffset; return position.pixels < minSnapDistance ? 0.0 : snapOffset; } } diff --git a/mobile/lib/extensions/string_extensions.dart b/mobile/lib/extensions/string_extensions.dart index a4308c4ec4..ae31565044 100644 --- a/mobile/lib/extensions/string_extensions.dart +++ b/mobile/lib/extensions/string_extensions.dart @@ -1,28 +1,17 @@ import 'dart:convert'; -import 'package:diacritic/diacritic.dart' as diacritic; - extension StringExtension on String { String capitalize() { return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" "); } - - String? get nullIfEmpty => isEmpty ? null : this; - - String removeDiacritics() => diacritic.removeDiacritics(this); } extension DurationExtension on String { - /// Parses and returns the string of format HH:MM:SS.ffffff as a duration object else null + /// Parses and returns the string of format HH:MM:SS as a duration object else null Duration? toDuration() { try { - final parts = split(':'); - final hours = double.parse(parts[0]).toInt(); - final minutes = double.parse(parts[1]).toInt(); - final secondsParts = parts[2].split('.'); - final seconds = int.parse(secondsParts[0]); - final milliseconds = secondsParts.length > 1 ? (double.parse('0.${secondsParts[1]}') * 1000).round() : 0; - return Duration(hours: hours, minutes: minutes, seconds: seconds, milliseconds: milliseconds); + final parts = split(':').map((e) => double.parse(e).toInt()).toList(growable: false); + return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]); } catch (e) { return null; } diff --git a/mobile/lib/extensions/translate_extensions.dart b/mobile/lib/extensions/translate_extensions.dart new file mode 100644 index 0000000000..7677f3cbd8 --- /dev/null +++ b/mobile/lib/extensions/translate_extensions.dart @@ -0,0 +1,46 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:intl/message_format.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +extension StringTranslateExtension on String { + String t({BuildContext? context, Map? args}) { + return _translateHelper(context, this, args); + } +} + +extension TextTranslateExtension on Text { + Text t({BuildContext? context, Map? args}) { + return Text( + _translateHelper(context, data ?? '', args), + key: key, + style: style, + strutStyle: strutStyle, + textAlign: textAlign, + textDirection: textDirection, + locale: locale, + softWrap: softWrap, + overflow: overflow, + textScaler: textScaler, + maxLines: maxLines, + semanticsLabel: semanticsLabel, + textWidthBasis: textWidthBasis, + textHeightBehavior: textHeightBehavior, + ); + } +} + +String _translateHelper(BuildContext? context, String key, [Map? args]) { + if (key.isEmpty) { + return ''; + } + try { + final translatedMessage = key.tr(context: context); + return args != null + ? MessageFormat(translatedMessage, locale: Intl.defaultLocale ?? 'en').format(args) + : translatedMessage; + } catch (e) { + dPrint(() => 'Translation failed for key "$key". Error: $e'); + return key; + } +} diff --git a/mobile/lib/infrastructure/entities/asset_edit.entity.dart b/mobile/lib/infrastructure/entities/asset_edit.entity.dart index 58e1a91d33..22d059bdb4 100644 --- a/mobile/lib/infrastructure/entities/asset_edit.entity.dart +++ b/mobile/lib/infrastructure/entities/asset_edit.entity.dart @@ -1,10 +1,8 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/extensions/object_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; -import 'package:openapi/api.dart' hide AssetEditAction; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)') class AssetEditEntity extends Table with DriftDefaultsMixin { @@ -25,16 +23,11 @@ class AssetEditEntity extends Table with DriftDefaultsMixin { } final JsonTypeConverter2, Uint8List, Object?> editParameterConverter = TypeConverter.jsonb( - fromJson: (json) => json! as Map, + fromJson: (json) => json as Map, ); extension AssetEditEntityDataDomainEx on AssetEditEntityData { - AssetEdit? toDto() { - return switch (action) { - AssetEditAction.crop => CropParameters.fromJson(parameters)?.let(CropEdit.new), - AssetEditAction.rotate => RotateParameters.fromJson(parameters)?.let(RotateEdit.new), - AssetEditAction.mirror => MirrorParameters.fromJson(parameters)?.let(MirrorEdit.new), - AssetEditAction.other => null, - }; + AssetEdit toDto() { + return AssetEdit(action: action, parameters: parameters); } } diff --git a/mobile/lib/infrastructure/entities/asset_edit.entity.drift.dart b/mobile/lib/infrastructure/entities/asset_edit.entity.drift.dart new file mode 100644 index 0000000000..fc40bf9030 --- /dev/null +++ b/mobile/lib/infrastructure/entities/asset_edit.entity.drift.dart @@ -0,0 +1,752 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/asset_edit.model.dart' as i2; +import 'dart:typed_data' as i3; +import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart' + as i4; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i5; +import 'package:drift/internal/modular.dart' as i6; + +typedef $$AssetEditEntityTableCreateCompanionBuilder = + i1.AssetEditEntityCompanion Function({ + required String id, + required String assetId, + required i2.AssetEditAction action, + required Map parameters, + required int sequence, + }); +typedef $$AssetEditEntityTableUpdateCompanionBuilder = + i1.AssetEditEntityCompanion Function({ + i0.Value id, + i0.Value assetId, + i0.Value action, + i0.Value> parameters, + i0.Value sequence, + }); + +final class $$AssetEditEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$AssetEditEntityTable, + i1.AssetEditEntityData + > { + $$AssetEditEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i5.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i6.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet('asset_edit_entity') + .assetId, + i6.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i5.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i5 + .$$RemoteAssetEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$AssetEditEntityTableFilterComposer + extends i0.Composer { + $$AssetEditEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get action => $composableBuilder( + column: $table.action, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnWithTypeConverterFilters< + Map, + Map, + i3.Uint8List + > + get parameters => $composableBuilder( + column: $table.parameters, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get sequence => $composableBuilder( + column: $table.sequence, + builder: (column) => i0.ColumnFilters(column), + ); + + i5.$$RemoteAssetEntityTableFilterComposer get assetId { + final i5.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetEditEntityTableOrderingComposer + extends i0.Composer { + $$AssetEditEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get action => $composableBuilder( + column: $table.action, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get parameters => $composableBuilder( + column: $table.parameters, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get sequence => $composableBuilder( + column: $table.sequence, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i5.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetEditEntityTableAnnotationComposer + extends i0.Composer { + $$AssetEditEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get action => + $composableBuilder(column: $table.action, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter, i3.Uint8List> + get parameters => $composableBuilder( + column: $table.parameters, + builder: (column) => column, + ); + + i0.GeneratedColumn get sequence => + $composableBuilder(column: $table.sequence, builder: (column) => column); + + i5.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i5.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetEditEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$AssetEditEntityTable, + i1.AssetEditEntityData, + i1.$$AssetEditEntityTableFilterComposer, + i1.$$AssetEditEntityTableOrderingComposer, + i1.$$AssetEditEntityTableAnnotationComposer, + $$AssetEditEntityTableCreateCompanionBuilder, + $$AssetEditEntityTableUpdateCompanionBuilder, + (i1.AssetEditEntityData, i1.$$AssetEditEntityTableReferences), + i1.AssetEditEntityData, + i0.PrefetchHooks Function({bool assetId}) + > { + $$AssetEditEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$AssetEditEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$AssetEditEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$AssetEditEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$AssetEditEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value assetId = const i0.Value.absent(), + i0.Value action = const i0.Value.absent(), + i0.Value> parameters = + const i0.Value.absent(), + i0.Value sequence = const i0.Value.absent(), + }) => i1.AssetEditEntityCompanion( + id: id, + assetId: assetId, + action: action, + parameters: parameters, + sequence: sequence, + ), + createCompanionCallback: + ({ + required String id, + required String assetId, + required i2.AssetEditAction action, + required Map parameters, + required int sequence, + }) => i1.AssetEditEntityCompanion.insert( + id: id, + assetId: assetId, + action: action, + parameters: parameters, + sequence: sequence, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$AssetEditEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$AssetEditEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$AssetEditEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$AssetEditEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$AssetEditEntityTable, + i1.AssetEditEntityData, + i1.$$AssetEditEntityTableFilterComposer, + i1.$$AssetEditEntityTableOrderingComposer, + i1.$$AssetEditEntityTableAnnotationComposer, + $$AssetEditEntityTableCreateCompanionBuilder, + $$AssetEditEntityTableUpdateCompanionBuilder, + (i1.AssetEditEntityData, i1.$$AssetEditEntityTableReferences), + i1.AssetEditEntityData, + i0.PrefetchHooks Function({bool assetId}) + >; +i0.Index get idxAssetEditAssetId => i0.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', +); + +class $AssetEditEntityTable extends i4.AssetEditEntity + with i0.TableInfo<$AssetEditEntityTable, i1.AssetEditEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $AssetEditEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + @override + late final i0.GeneratedColumnWithTypeConverter + action = + i0.GeneratedColumn( + 'action', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$AssetEditEntityTable.$converteraction, + ); + @override + late final i0.GeneratedColumnWithTypeConverter< + Map, + i3.Uint8List + > + parameters = + i0.GeneratedColumn( + 'parameters', + aliasedName, + false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + ).withConverter>( + i1.$AssetEditEntityTable.$converterparameters, + ); + static const i0.VerificationMeta _sequenceMeta = const i0.VerificationMeta( + 'sequence', + ); + @override + late final i0.GeneratedColumn sequence = i0.GeneratedColumn( + 'sequence', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('sequence')) { + context.handle( + _sequenceMeta, + sequence.isAcceptableOrUnknown(data['sequence']!, _sequenceMeta), + ); + } else if (isInserting) { + context.missing(_sequenceMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: i1.$AssetEditEntityTable.$converteraction.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + ), + parameters: i1.$AssetEditEntityTable.$converterparameters.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + ), + sequence: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + $AssetEditEntityTable createAlias(String alias) { + return $AssetEditEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converteraction = + const i0.EnumIndexConverter( + i2.AssetEditAction.values, + ); + static i0.JsonTypeConverter2, i3.Uint8List, Object?> + $converterparameters = i4.editParameterConverter; + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetEditEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String assetId; + final i2.AssetEditAction action; + final Map parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['asset_id'] = i0.Variable(assetId); + { + map['action'] = i0.Variable( + i1.$AssetEditEntityTable.$converteraction.toSql(action), + ); + } + { + map['parameters'] = i0.Variable( + i1.$AssetEditEntityTable.$converterparameters.toSql(parameters), + ); + } + map['sequence'] = i0.Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: i1.$AssetEditEntityTable.$converteraction.fromJson( + serializer.fromJson(json['action']), + ), + parameters: i1.$AssetEditEntityTable.$converterparameters.fromJson( + serializer.fromJson(json['parameters']), + ), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson( + i1.$AssetEditEntityTable.$converteraction.toJson(action), + ), + 'parameters': serializer.toJson( + i1.$AssetEditEntityTable.$converterparameters.toJson(parameters), + ), + 'sequence': serializer.toJson(sequence), + }; + } + + i1.AssetEditEntityData copyWith({ + String? id, + String? assetId, + i2.AssetEditAction? action, + Map? parameters, + int? sequence, + }) => i1.AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(i1.AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, assetId, action, parameters, sequence); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + other.parameters == this.parameters && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value assetId; + final i0.Value action; + final i0.Value> parameters; + final i0.Value sequence; + const AssetEditEntityCompanion({ + this.id = const i0.Value.absent(), + this.assetId = const i0.Value.absent(), + this.action = const i0.Value.absent(), + this.parameters = const i0.Value.absent(), + this.sequence = const i0.Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required i2.AssetEditAction action, + required Map parameters, + required int sequence, + }) : id = i0.Value(id), + assetId = i0.Value(assetId), + action = i0.Value(action), + parameters = i0.Value(parameters), + sequence = i0.Value(sequence); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? assetId, + i0.Expression? action, + i0.Expression? parameters, + i0.Expression? sequence, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + i1.AssetEditEntityCompanion copyWith({ + i0.Value? id, + i0.Value? assetId, + i0.Value? action, + i0.Value>? parameters, + i0.Value? sequence, + }) { + return i1.AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (action.present) { + map['action'] = i0.Variable( + i1.$AssetEditEntityTable.$converteraction.toSql(action.value), + ); + } + if (parameters.present) { + map['parameters'] = i0.Variable( + i1.$AssetEditEntityTable.$converterparameters.toSql(parameters.value), + ); + } + if (sequence.present) { + map['sequence'] = i0.Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.dart b/mobile/lib/infrastructure/entities/asset_face.entity.dart index b94a0cf094..40fe9ab1c1 100644 --- a/mobile/lib/infrastructure/entities/asset_face.entity.dart +++ b/mobile/lib/infrastructure/entities/asset_face.entity.dart @@ -5,11 +5,6 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)') -@TableIndex.sql(''' -CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person -ON asset_face_entity (person_id, asset_id) -WHERE is_visible = 1 AND deleted_at IS NULL -''') class AssetFaceEntity extends Table with DriftDefaultsMixin { const AssetFaceEntity(); diff --git a/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart b/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart new file mode 100644 index 0000000000..c97dd545a8 --- /dev/null +++ b/mobile/lib/infrastructure/entities/asset_face.entity.drift.dart @@ -0,0 +1,1352 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; +import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' + as i6; + +typedef $$AssetFaceEntityTableCreateCompanionBuilder = + i1.AssetFaceEntityCompanion Function({ + required String id, + required String assetId, + i0.Value personId, + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + i0.Value isVisible, + i0.Value deletedAt, + }); +typedef $$AssetFaceEntityTableUpdateCompanionBuilder = + i1.AssetFaceEntityCompanion Function({ + i0.Value id, + i0.Value assetId, + i0.Value personId, + i0.Value imageWidth, + i0.Value imageHeight, + i0.Value boundingBoxX1, + i0.Value boundingBoxY1, + i0.Value boundingBoxX2, + i0.Value boundingBoxY2, + i0.Value sourceType, + i0.Value isVisible, + i0.Value deletedAt, + }); + +final class $$AssetFaceEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$AssetFaceEntityTable, + i1.AssetFaceEntityData + > { + $$AssetFaceEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i4.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet('asset_face_entity') + .assetId, + i5.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i4.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i4 + .$$RemoteAssetEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i6.$PersonEntityTable _personIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('person_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet('asset_face_entity') + .personId, + i5.ReadDatabaseContainer( + db, + ).resultSet('person_entity').id, + ), + ); + + i6.$$PersonEntityTableProcessedTableManager? get personId { + final $_column = $_itemColumn('person_id'); + if ($_column == null) return null; + final manager = i6 + .$$PersonEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('person_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_personIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$AssetFaceEntityTableFilterComposer + extends i0.Composer { + $$AssetFaceEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get imageWidth => $composableBuilder( + column: $table.imageWidth, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get imageHeight => $composableBuilder( + column: $table.imageHeight, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get boundingBoxX1 => $composableBuilder( + column: $table.boundingBoxX1, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get boundingBoxY1 => $composableBuilder( + column: $table.boundingBoxY1, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get boundingBoxX2 => $composableBuilder( + column: $table.boundingBoxX2, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get boundingBoxY2 => $composableBuilder( + column: $table.boundingBoxY2, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get sourceType => $composableBuilder( + column: $table.sourceType, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isVisible => $composableBuilder( + column: $table.isVisible, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i4.$$RemoteAssetEntityTableFilterComposer get assetId { + final i4.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$PersonEntityTableFilterComposer get personId { + final i6.$$PersonEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.personId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$PersonEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetFaceEntityTableOrderingComposer + extends i0.Composer { + $$AssetFaceEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get imageWidth => $composableBuilder( + column: $table.imageWidth, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get imageHeight => $composableBuilder( + column: $table.imageHeight, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get boundingBoxX1 => $composableBuilder( + column: $table.boundingBoxX1, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get boundingBoxY1 => $composableBuilder( + column: $table.boundingBoxY1, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get boundingBoxX2 => $composableBuilder( + column: $table.boundingBoxX2, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get boundingBoxY2 => $composableBuilder( + column: $table.boundingBoxY2, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get sourceType => $composableBuilder( + column: $table.sourceType, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isVisible => $composableBuilder( + column: $table.isVisible, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i4.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i4.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$PersonEntityTableOrderingComposer get personId { + final i6.$$PersonEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.personId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$PersonEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetFaceEntityTableAnnotationComposer + extends i0.Composer { + $$AssetFaceEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get imageWidth => $composableBuilder( + column: $table.imageWidth, + builder: (column) => column, + ); + + i0.GeneratedColumn get imageHeight => $composableBuilder( + column: $table.imageHeight, + builder: (column) => column, + ); + + i0.GeneratedColumn get boundingBoxX1 => $composableBuilder( + column: $table.boundingBoxX1, + builder: (column) => column, + ); + + i0.GeneratedColumn get boundingBoxY1 => $composableBuilder( + column: $table.boundingBoxY1, + builder: (column) => column, + ); + + i0.GeneratedColumn get boundingBoxX2 => $composableBuilder( + column: $table.boundingBoxX2, + builder: (column) => column, + ); + + i0.GeneratedColumn get boundingBoxY2 => $composableBuilder( + column: $table.boundingBoxY2, + builder: (column) => column, + ); + + i0.GeneratedColumn get sourceType => $composableBuilder( + column: $table.sourceType, + builder: (column) => column, + ); + + i0.GeneratedColumn get isVisible => + $composableBuilder(column: $table.isVisible, builder: (column) => column); + + i0.GeneratedColumn get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => column); + + i4.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i4.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$PersonEntityTableAnnotationComposer get personId { + final i6.$$PersonEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.personId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$PersonEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('person_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$AssetFaceEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$AssetFaceEntityTable, + i1.AssetFaceEntityData, + i1.$$AssetFaceEntityTableFilterComposer, + i1.$$AssetFaceEntityTableOrderingComposer, + i1.$$AssetFaceEntityTableAnnotationComposer, + $$AssetFaceEntityTableCreateCompanionBuilder, + $$AssetFaceEntityTableUpdateCompanionBuilder, + (i1.AssetFaceEntityData, i1.$$AssetFaceEntityTableReferences), + i1.AssetFaceEntityData, + i0.PrefetchHooks Function({bool assetId, bool personId}) + > { + $$AssetFaceEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$AssetFaceEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$AssetFaceEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$AssetFaceEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$AssetFaceEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value assetId = const i0.Value.absent(), + i0.Value personId = const i0.Value.absent(), + i0.Value imageWidth = const i0.Value.absent(), + i0.Value imageHeight = const i0.Value.absent(), + i0.Value boundingBoxX1 = const i0.Value.absent(), + i0.Value boundingBoxY1 = const i0.Value.absent(), + i0.Value boundingBoxX2 = const i0.Value.absent(), + i0.Value boundingBoxY2 = const i0.Value.absent(), + i0.Value sourceType = const i0.Value.absent(), + i0.Value isVisible = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + }) => i1.AssetFaceEntityCompanion( + id: id, + assetId: assetId, + personId: personId, + imageWidth: imageWidth, + imageHeight: imageHeight, + boundingBoxX1: boundingBoxX1, + boundingBoxY1: boundingBoxY1, + boundingBoxX2: boundingBoxX2, + boundingBoxY2: boundingBoxY2, + sourceType: sourceType, + isVisible: isVisible, + deletedAt: deletedAt, + ), + createCompanionCallback: + ({ + required String id, + required String assetId, + i0.Value personId = const i0.Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + i0.Value isVisible = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + }) => i1.AssetFaceEntityCompanion.insert( + id: id, + assetId: assetId, + personId: personId, + imageWidth: imageWidth, + imageHeight: imageHeight, + boundingBoxX1: boundingBoxX1, + boundingBoxY1: boundingBoxY1, + boundingBoxX2: boundingBoxX2, + boundingBoxY2: boundingBoxY2, + sourceType: sourceType, + isVisible: isVisible, + deletedAt: deletedAt, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$AssetFaceEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false, personId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$AssetFaceEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$AssetFaceEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + if (personId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.personId, + referencedTable: i1 + .$$AssetFaceEntityTableReferences + ._personIdTable(db), + referencedColumn: i1 + .$$AssetFaceEntityTableReferences + ._personIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$AssetFaceEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$AssetFaceEntityTable, + i1.AssetFaceEntityData, + i1.$$AssetFaceEntityTableFilterComposer, + i1.$$AssetFaceEntityTableOrderingComposer, + i1.$$AssetFaceEntityTableAnnotationComposer, + $$AssetFaceEntityTableCreateCompanionBuilder, + $$AssetFaceEntityTableUpdateCompanionBuilder, + (i1.AssetFaceEntityData, i1.$$AssetFaceEntityTableReferences), + i1.AssetFaceEntityData, + i0.PrefetchHooks Function({bool assetId, bool personId}) + >; +i0.Index get idxAssetFacePersonId => i0.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', +); + +class $AssetFaceEntityTable extends i2.AssetFaceEntity + with i0.TableInfo<$AssetFaceEntityTable, i1.AssetFaceEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $AssetFaceEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _personIdMeta = const i0.VerificationMeta( + 'personId', + ); + @override + late final i0.GeneratedColumn personId = i0.GeneratedColumn( + 'person_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + static const i0.VerificationMeta _imageWidthMeta = const i0.VerificationMeta( + 'imageWidth', + ); + @override + late final i0.GeneratedColumn imageWidth = i0.GeneratedColumn( + 'image_width', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _imageHeightMeta = const i0.VerificationMeta( + 'imageHeight', + ); + @override + late final i0.GeneratedColumn imageHeight = i0.GeneratedColumn( + 'image_height', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _boundingBoxX1Meta = + const i0.VerificationMeta('boundingBoxX1'); + @override + late final i0.GeneratedColumn boundingBoxX1 = i0.GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _boundingBoxY1Meta = + const i0.VerificationMeta('boundingBoxY1'); + @override + late final i0.GeneratedColumn boundingBoxY1 = i0.GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _boundingBoxX2Meta = + const i0.VerificationMeta('boundingBoxX2'); + @override + late final i0.GeneratedColumn boundingBoxX2 = i0.GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _boundingBoxY2Meta = + const i0.VerificationMeta('boundingBoxY2'); + @override + late final i0.GeneratedColumn boundingBoxY2 = i0.GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _sourceTypeMeta = const i0.VerificationMeta( + 'sourceType', + ); + @override + late final i0.GeneratedColumn sourceType = i0.GeneratedColumn( + 'source_type', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _isVisibleMeta = const i0.VerificationMeta( + 'isVisible', + ); + @override + late final i0.GeneratedColumn isVisible = i0.GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_visible" IN (0, 1))', + ), + defaultValue: const i3.Constant(true), + ); + static const i0.VerificationMeta _deletedAtMeta = const i0.VerificationMeta( + 'deletedAt', + ); + @override + late final i0.GeneratedColumn deletedAt = + i0.GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('person_id')) { + context.handle( + _personIdMeta, + personId.isAcceptableOrUnknown(data['person_id']!, _personIdMeta), + ); + } + if (data.containsKey('image_width')) { + context.handle( + _imageWidthMeta, + imageWidth.isAcceptableOrUnknown(data['image_width']!, _imageWidthMeta), + ); + } else if (isInserting) { + context.missing(_imageWidthMeta); + } + if (data.containsKey('image_height')) { + context.handle( + _imageHeightMeta, + imageHeight.isAcceptableOrUnknown( + data['image_height']!, + _imageHeightMeta, + ), + ); + } else if (isInserting) { + context.missing(_imageHeightMeta); + } + if (data.containsKey('bounding_box_x1')) { + context.handle( + _boundingBoxX1Meta, + boundingBoxX1.isAcceptableOrUnknown( + data['bounding_box_x1']!, + _boundingBoxX1Meta, + ), + ); + } else if (isInserting) { + context.missing(_boundingBoxX1Meta); + } + if (data.containsKey('bounding_box_y1')) { + context.handle( + _boundingBoxY1Meta, + boundingBoxY1.isAcceptableOrUnknown( + data['bounding_box_y1']!, + _boundingBoxY1Meta, + ), + ); + } else if (isInserting) { + context.missing(_boundingBoxY1Meta); + } + if (data.containsKey('bounding_box_x2')) { + context.handle( + _boundingBoxX2Meta, + boundingBoxX2.isAcceptableOrUnknown( + data['bounding_box_x2']!, + _boundingBoxX2Meta, + ), + ); + } else if (isInserting) { + context.missing(_boundingBoxX2Meta); + } + if (data.containsKey('bounding_box_y2')) { + context.handle( + _boundingBoxY2Meta, + boundingBoxY2.isAcceptableOrUnknown( + data['bounding_box_y2']!, + _boundingBoxY2Meta, + ), + ); + } else if (isInserting) { + context.missing(_boundingBoxY2Meta); + } + if (data.containsKey('source_type')) { + context.handle( + _sourceTypeMeta, + sourceType.isAcceptableOrUnknown(data['source_type']!, _sourceTypeMeta), + ); + } else if (isInserting) { + context.missing(_sourceTypeMeta); + } + if (data.containsKey('is_visible')) { + context.handle( + _isVisibleMeta, + isVisible.isAcceptableOrUnknown(data['is_visible']!, _isVisibleMeta), + ); + } + if (data.containsKey('deleted_at')) { + context.handle( + _deletedAtMeta, + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + $AssetFaceEntityTable createAlias(String alias) { + return $AssetFaceEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final bool isVisible; + final DateTime? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['asset_id'] = i0.Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = i0.Variable(personId); + } + map['image_width'] = i0.Variable(imageWidth); + map['image_height'] = i0.Variable(imageHeight); + map['bounding_box_x1'] = i0.Variable(boundingBoxX1); + map['bounding_box_y1'] = i0.Variable(boundingBoxY1); + map['bounding_box_x2'] = i0.Variable(boundingBoxX2); + map['bounding_box_y2'] = i0.Variable(boundingBoxY2); + map['source_type'] = i0.Variable(sourceType); + map['is_visible'] = i0.Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = i0.Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + i1.AssetFaceEntityData copyWith({ + String? id, + String? assetId, + i0.Value personId = const i0.Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + bool? isVisible, + i0.Value deletedAt = const i0.Value.absent(), + }) => i1.AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(i1.AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value assetId; + final i0.Value personId; + final i0.Value imageWidth; + final i0.Value imageHeight; + final i0.Value boundingBoxX1; + final i0.Value boundingBoxY1; + final i0.Value boundingBoxX2; + final i0.Value boundingBoxY2; + final i0.Value sourceType; + final i0.Value isVisible; + final i0.Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const i0.Value.absent(), + this.assetId = const i0.Value.absent(), + this.personId = const i0.Value.absent(), + this.imageWidth = const i0.Value.absent(), + this.imageHeight = const i0.Value.absent(), + this.boundingBoxX1 = const i0.Value.absent(), + this.boundingBoxY1 = const i0.Value.absent(), + this.boundingBoxX2 = const i0.Value.absent(), + this.boundingBoxY2 = const i0.Value.absent(), + this.sourceType = const i0.Value.absent(), + this.isVisible = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const i0.Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + }) : id = i0.Value(id), + assetId = i0.Value(assetId), + imageWidth = i0.Value(imageWidth), + imageHeight = i0.Value(imageHeight), + boundingBoxX1 = i0.Value(boundingBoxX1), + boundingBoxY1 = i0.Value(boundingBoxY1), + boundingBoxX2 = i0.Value(boundingBoxX2), + boundingBoxY2 = i0.Value(boundingBoxY2), + sourceType = i0.Value(sourceType); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? assetId, + i0.Expression? personId, + i0.Expression? imageWidth, + i0.Expression? imageHeight, + i0.Expression? boundingBoxX1, + i0.Expression? boundingBoxY1, + i0.Expression? boundingBoxX2, + i0.Expression? boundingBoxY2, + i0.Expression? sourceType, + i0.Expression? isVisible, + i0.Expression? deletedAt, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + i1.AssetFaceEntityCompanion copyWith({ + i0.Value? id, + i0.Value? assetId, + i0.Value? personId, + i0.Value? imageWidth, + i0.Value? imageHeight, + i0.Value? boundingBoxX1, + i0.Value? boundingBoxY1, + i0.Value? boundingBoxX2, + i0.Value? boundingBoxY2, + i0.Value? sourceType, + i0.Value? isVisible, + i0.Value? deletedAt, + }) { + return i1.AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = i0.Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = i0.Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = i0.Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = i0.Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = i0.Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = i0.Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = i0.Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = i0.Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = i0.Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = i0.Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +i0.Index get idxAssetFaceAssetId => i0.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', +); diff --git a/mobile/lib/infrastructure/entities/asset_ocr.entity.dart b/mobile/lib/infrastructure/entities/asset_ocr.entity.dart deleted file mode 100644 index 40438ca1ed..0000000000 --- a/mobile/lib/infrastructure/entities/asset_ocr.entity.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; - -@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)') -class AssetOcrEntity extends Table with DriftDefaultsMixin { - const AssetOcrEntity(); - - TextColumn get id => text()(); - - TextColumn get assetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)(); - - RealColumn get x1 => real()(); - RealColumn get y1 => real()(); - - RealColumn get x2 => real()(); - RealColumn get y2 => real()(); - - RealColumn get x3 => real()(); - RealColumn get y3 => real()(); - - RealColumn get x4 => real()(); - RealColumn get y4 => real()(); - - RealColumn get boxScore => real()(); - RealColumn get textScore => real()(); - - TextColumn get recognizedText => text()(); - - BoolColumn get isVisible => boolean().withDefault(const Constant(true))(); - - @override - Set get primaryKey => {id}; -} diff --git a/mobile/lib/infrastructure/entities/auth_user.entity.drift.dart b/mobile/lib/infrastructure/entities/auth_user.entity.drift.dart new file mode 100644 index 0000000000..4dba1c42fb --- /dev/null +++ b/mobile/lib/infrastructure/entities/auth_user.entity.drift.dart @@ -0,0 +1,933 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/user.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$AuthUserEntityTableCreateCompanionBuilder = + i1.AuthUserEntityCompanion Function({ + required String id, + required String name, + required String email, + i0.Value isAdmin, + i0.Value hasProfileImage, + i0.Value profileChangedAt, + required i2.AvatarColor avatarColor, + i0.Value quotaSizeInBytes, + i0.Value quotaUsageInBytes, + i0.Value pinCode, + }); +typedef $$AuthUserEntityTableUpdateCompanionBuilder = + i1.AuthUserEntityCompanion Function({ + i0.Value id, + i0.Value name, + i0.Value email, + i0.Value isAdmin, + i0.Value hasProfileImage, + i0.Value profileChangedAt, + i0.Value avatarColor, + i0.Value quotaSizeInBytes, + i0.Value quotaUsageInBytes, + i0.Value pinCode, + }); + +class $$AuthUserEntityTableFilterComposer + extends i0.Composer { + $$AuthUserEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get email => $composableBuilder( + column: $table.email, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isAdmin => $composableBuilder( + column: $table.isAdmin, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get avatarColor => $composableBuilder( + column: $table.avatarColor, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get quotaSizeInBytes => $composableBuilder( + column: $table.quotaSizeInBytes, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get quotaUsageInBytes => $composableBuilder( + column: $table.quotaUsageInBytes, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get pinCode => $composableBuilder( + column: $table.pinCode, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$AuthUserEntityTableOrderingComposer + extends i0.Composer { + $$AuthUserEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get email => $composableBuilder( + column: $table.email, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isAdmin => $composableBuilder( + column: $table.isAdmin, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get avatarColor => $composableBuilder( + column: $table.avatarColor, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get quotaSizeInBytes => $composableBuilder( + column: $table.quotaSizeInBytes, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get quotaUsageInBytes => $composableBuilder( + column: $table.quotaUsageInBytes, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get pinCode => $composableBuilder( + column: $table.pinCode, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$AuthUserEntityTableAnnotationComposer + extends i0.Composer { + $$AuthUserEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get email => + $composableBuilder(column: $table.email, builder: (column) => column); + + i0.GeneratedColumn get isAdmin => + $composableBuilder(column: $table.isAdmin, builder: (column) => column); + + i0.GeneratedColumn get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => column, + ); + + i0.GeneratedColumn get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => column, + ); + + i0.GeneratedColumnWithTypeConverter get avatarColor => + $composableBuilder( + column: $table.avatarColor, + builder: (column) => column, + ); + + i0.GeneratedColumn get quotaSizeInBytes => $composableBuilder( + column: $table.quotaSizeInBytes, + builder: (column) => column, + ); + + i0.GeneratedColumn get quotaUsageInBytes => $composableBuilder( + column: $table.quotaUsageInBytes, + builder: (column) => column, + ); + + i0.GeneratedColumn get pinCode => + $composableBuilder(column: $table.pinCode, builder: (column) => column); +} + +class $$AuthUserEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$AuthUserEntityTable, + i1.AuthUserEntityData, + i1.$$AuthUserEntityTableFilterComposer, + i1.$$AuthUserEntityTableOrderingComposer, + i1.$$AuthUserEntityTableAnnotationComposer, + $$AuthUserEntityTableCreateCompanionBuilder, + $$AuthUserEntityTableUpdateCompanionBuilder, + ( + i1.AuthUserEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$AuthUserEntityTable, + i1.AuthUserEntityData + >, + ), + i1.AuthUserEntityData, + i0.PrefetchHooks Function() + > { + $$AuthUserEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$AuthUserEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$AuthUserEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$AuthUserEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => i1 + .$$AuthUserEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + i0.Value email = const i0.Value.absent(), + i0.Value isAdmin = const i0.Value.absent(), + i0.Value hasProfileImage = const i0.Value.absent(), + i0.Value profileChangedAt = const i0.Value.absent(), + i0.Value avatarColor = const i0.Value.absent(), + i0.Value quotaSizeInBytes = const i0.Value.absent(), + i0.Value quotaUsageInBytes = const i0.Value.absent(), + i0.Value pinCode = const i0.Value.absent(), + }) => i1.AuthUserEntityCompanion( + id: id, + name: name, + email: email, + isAdmin: isAdmin, + hasProfileImage: hasProfileImage, + profileChangedAt: profileChangedAt, + avatarColor: avatarColor, + quotaSizeInBytes: quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes, + pinCode: pinCode, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String email, + i0.Value isAdmin = const i0.Value.absent(), + i0.Value hasProfileImage = const i0.Value.absent(), + i0.Value profileChangedAt = const i0.Value.absent(), + required i2.AvatarColor avatarColor, + i0.Value quotaSizeInBytes = const i0.Value.absent(), + i0.Value quotaUsageInBytes = const i0.Value.absent(), + i0.Value pinCode = const i0.Value.absent(), + }) => i1.AuthUserEntityCompanion.insert( + id: id, + name: name, + email: email, + isAdmin: isAdmin, + hasProfileImage: hasProfileImage, + profileChangedAt: profileChangedAt, + avatarColor: avatarColor, + quotaSizeInBytes: quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes, + pinCode: pinCode, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$AuthUserEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$AuthUserEntityTable, + i1.AuthUserEntityData, + i1.$$AuthUserEntityTableFilterComposer, + i1.$$AuthUserEntityTableOrderingComposer, + i1.$$AuthUserEntityTableAnnotationComposer, + $$AuthUserEntityTableCreateCompanionBuilder, + $$AuthUserEntityTableUpdateCompanionBuilder, + ( + i1.AuthUserEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$AuthUserEntityTable, + i1.AuthUserEntityData + >, + ), + i1.AuthUserEntityData, + i0.PrefetchHooks Function() + >; + +class $AuthUserEntityTable extends i3.AuthUserEntity + with i0.TableInfo<$AuthUserEntityTable, i1.AuthUserEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $AuthUserEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _emailMeta = const i0.VerificationMeta( + 'email', + ); + @override + late final i0.GeneratedColumn email = i0.GeneratedColumn( + 'email', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _isAdminMeta = const i0.VerificationMeta( + 'isAdmin', + ); + @override + late final i0.GeneratedColumn isAdmin = i0.GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _hasProfileImageMeta = + const i0.VerificationMeta('hasProfileImage'); + @override + late final i0.GeneratedColumn hasProfileImage = + i0.GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _profileChangedAtMeta = + const i0.VerificationMeta('profileChangedAt'); + @override + late final i0.GeneratedColumn profileChangedAt = + i0.GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + avatarColor = + i0.GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$AuthUserEntityTable.$converteravatarColor, + ); + static const i0.VerificationMeta _quotaSizeInBytesMeta = + const i0.VerificationMeta('quotaSizeInBytes'); + @override + late final i0.GeneratedColumn quotaSizeInBytes = i0.GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ); + static const i0.VerificationMeta _quotaUsageInBytesMeta = + const i0.VerificationMeta('quotaUsageInBytes'); + @override + late final i0.GeneratedColumn quotaUsageInBytes = + i0.GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ); + static const i0.VerificationMeta _pinCodeMeta = const i0.VerificationMeta( + 'pinCode', + ); + @override + late final i0.GeneratedColumn pinCode = i0.GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, + email.isAcceptableOrUnknown(data['email']!, _emailMeta), + ); + } else if (isInserting) { + context.missing(_emailMeta); + } + if (data.containsKey('is_admin')) { + context.handle( + _isAdminMeta, + isAdmin.isAcceptableOrUnknown(data['is_admin']!, _isAdminMeta), + ); + } + if (data.containsKey('has_profile_image')) { + context.handle( + _hasProfileImageMeta, + hasProfileImage.isAcceptableOrUnknown( + data['has_profile_image']!, + _hasProfileImageMeta, + ), + ); + } + if (data.containsKey('profile_changed_at')) { + context.handle( + _profileChangedAtMeta, + profileChangedAt.isAcceptableOrUnknown( + data['profile_changed_at']!, + _profileChangedAtMeta, + ), + ); + } + if (data.containsKey('quota_size_in_bytes')) { + context.handle( + _quotaSizeInBytesMeta, + quotaSizeInBytes.isAcceptableOrUnknown( + data['quota_size_in_bytes']!, + _quotaSizeInBytesMeta, + ), + ); + } + if (data.containsKey('quota_usage_in_bytes')) { + context.handle( + _quotaUsageInBytesMeta, + quotaUsageInBytes.isAcceptableOrUnknown( + data['quota_usage_in_bytes']!, + _quotaUsageInBytesMeta, + ), + ); + } + if (data.containsKey('pin_code')) { + context.handle( + _pinCodeMeta, + pinCode.isAcceptableOrUnknown(data['pin_code']!, _pinCodeMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: i1.$AuthUserEntityTable.$converteravatarColor.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ), + quotaSizeInBytes: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + $AuthUserEntityTable createAlias(String alias) { + return $AuthUserEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converteravatarColor = + const i0.EnumIndexConverter(i2.AvatarColor.values); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final i2.AvatarColor avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['name'] = i0.Variable(name); + map['email'] = i0.Variable(email); + map['is_admin'] = i0.Variable(isAdmin); + map['has_profile_image'] = i0.Variable(hasProfileImage); + map['profile_changed_at'] = i0.Variable(profileChangedAt); + { + map['avatar_color'] = i0.Variable( + i1.$AuthUserEntityTable.$converteravatarColor.toSql(avatarColor), + ); + } + map['quota_size_in_bytes'] = i0.Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = i0.Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = i0.Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: i1.$AuthUserEntityTable.$converteravatarColor.fromJson( + serializer.fromJson(json['avatarColor']), + ), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson( + i1.$AuthUserEntityTable.$converteravatarColor.toJson(avatarColor), + ), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + i1.AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + i2.AvatarColor? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + i0.Value pinCode = const i0.Value.absent(), + }) => i1.AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(i1.AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + final i0.Value email; + final i0.Value isAdmin; + final i0.Value hasProfileImage; + final i0.Value profileChangedAt; + final i0.Value avatarColor; + final i0.Value quotaSizeInBytes; + final i0.Value quotaUsageInBytes; + final i0.Value pinCode; + const AuthUserEntityCompanion({ + this.id = const i0.Value.absent(), + this.name = const i0.Value.absent(), + this.email = const i0.Value.absent(), + this.isAdmin = const i0.Value.absent(), + this.hasProfileImage = const i0.Value.absent(), + this.profileChangedAt = const i0.Value.absent(), + this.avatarColor = const i0.Value.absent(), + this.quotaSizeInBytes = const i0.Value.absent(), + this.quotaUsageInBytes = const i0.Value.absent(), + this.pinCode = const i0.Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const i0.Value.absent(), + this.hasProfileImage = const i0.Value.absent(), + this.profileChangedAt = const i0.Value.absent(), + required i2.AvatarColor avatarColor, + this.quotaSizeInBytes = const i0.Value.absent(), + this.quotaUsageInBytes = const i0.Value.absent(), + this.pinCode = const i0.Value.absent(), + }) : id = i0.Value(id), + name = i0.Value(name), + email = i0.Value(email), + avatarColor = i0.Value(avatarColor); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + i0.Expression? email, + i0.Expression? isAdmin, + i0.Expression? hasProfileImage, + i0.Expression? profileChangedAt, + i0.Expression? avatarColor, + i0.Expression? quotaSizeInBytes, + i0.Expression? quotaUsageInBytes, + i0.Expression? pinCode, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + i1.AuthUserEntityCompanion copyWith({ + i0.Value? id, + i0.Value? name, + i0.Value? email, + i0.Value? isAdmin, + i0.Value? hasProfileImage, + i0.Value? profileChangedAt, + i0.Value? avatarColor, + i0.Value? quotaSizeInBytes, + i0.Value? quotaUsageInBytes, + i0.Value? pinCode, + }) { + return i1.AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (email.present) { + map['email'] = i0.Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = i0.Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = i0.Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = i0.Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = i0.Variable( + i1.$AuthUserEntityTable.$converteravatarColor.toSql(avatarColor.value), + ); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = i0.Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = i0.Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = i0.Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/device_asset.entity.dart b/mobile/lib/infrastructure/entities/device_asset.entity.dart new file mode 100644 index 0000000000..e3e4a0d4f4 --- /dev/null +++ b/mobile/lib/infrastructure/entities/device_asset.entity.dart @@ -0,0 +1,25 @@ +import 'dart:typed_data'; + +import 'package:immich_mobile/domain/models/device_asset.model.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'device_asset.entity.g.dart'; + +@Collection(inheritance: false) +class DeviceAssetEntity { + Id get id => fastHash(assetId); + + @Index(replace: true, unique: true, type: IndexType.hash) + final String assetId; + @Index(unique: false, type: IndexType.hash) + final List hash; + final DateTime modifiedTime; + + const DeviceAssetEntity({required this.assetId, required this.hash, required this.modifiedTime}); + + DeviceAsset toModel() => DeviceAsset(assetId: assetId, hash: Uint8List.fromList(hash), modifiedTime: modifiedTime); + + static DeviceAssetEntity fromDto(DeviceAsset dto) => + DeviceAssetEntity(assetId: dto.assetId, hash: dto.hash, modifiedTime: dto.modifiedTime); +} diff --git a/mobile/lib/infrastructure/entities/device_asset.entity.g.dart b/mobile/lib/infrastructure/entities/device_asset.entity.g.dart new file mode 100644 index 0000000000..b6c30aca6f --- /dev/null +++ b/mobile/lib/infrastructure/entities/device_asset.entity.g.dart @@ -0,0 +1,874 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'device_asset.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetDeviceAssetEntityCollection on Isar { + IsarCollection get deviceAssetEntitys => this.collection(); +} + +const DeviceAssetEntitySchema = CollectionSchema( + name: r'DeviceAssetEntity', + id: 6967030785073446271, + properties: { + r'assetId': PropertySchema(id: 0, name: r'assetId', type: IsarType.string), + r'hash': PropertySchema(id: 1, name: r'hash', type: IsarType.byteList), + r'modifiedTime': PropertySchema( + id: 2, + name: r'modifiedTime', + type: IsarType.dateTime, + ), + }, + + estimateSize: _deviceAssetEntityEstimateSize, + serialize: _deviceAssetEntitySerialize, + deserialize: _deviceAssetEntityDeserialize, + deserializeProp: _deviceAssetEntityDeserializeProp, + idName: r'id', + indexes: { + r'assetId': IndexSchema( + id: 174362542210192109, + name: r'assetId', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'assetId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + r'hash': IndexSchema( + id: -7973251393006690288, + name: r'hash', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'hash', + type: IndexType.hash, + caseSensitive: false, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _deviceAssetEntityGetId, + getLinks: _deviceAssetEntityGetLinks, + attach: _deviceAssetEntityAttach, + version: '3.3.0-dev.3', +); + +int _deviceAssetEntityEstimateSize( + DeviceAssetEntity object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.assetId.length * 3; + bytesCount += 3 + object.hash.length; + return bytesCount; +} + +void _deviceAssetEntitySerialize( + DeviceAssetEntity object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.assetId); + writer.writeByteList(offsets[1], object.hash); + writer.writeDateTime(offsets[2], object.modifiedTime); +} + +DeviceAssetEntity _deviceAssetEntityDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = DeviceAssetEntity( + assetId: reader.readString(offsets[0]), + hash: reader.readByteList(offsets[1]) ?? [], + modifiedTime: reader.readDateTime(offsets[2]), + ); + return object; +} + +P _deviceAssetEntityDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readByteList(offset) ?? []) as P; + case 2: + return (reader.readDateTime(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _deviceAssetEntityGetId(DeviceAssetEntity object) { + return object.id; +} + +List> _deviceAssetEntityGetLinks( + DeviceAssetEntity object, +) { + return []; +} + +void _deviceAssetEntityAttach( + IsarCollection col, + Id id, + DeviceAssetEntity object, +) {} + +extension DeviceAssetEntityByIndex on IsarCollection { + Future getByAssetId(String assetId) { + return getByIndex(r'assetId', [assetId]); + } + + DeviceAssetEntity? getByAssetIdSync(String assetId) { + return getByIndexSync(r'assetId', [assetId]); + } + + Future deleteByAssetId(String assetId) { + return deleteByIndex(r'assetId', [assetId]); + } + + bool deleteByAssetIdSync(String assetId) { + return deleteByIndexSync(r'assetId', [assetId]); + } + + Future> getAllByAssetId(List assetIdValues) { + final values = assetIdValues.map((e) => [e]).toList(); + return getAllByIndex(r'assetId', values); + } + + List getAllByAssetIdSync(List assetIdValues) { + final values = assetIdValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'assetId', values); + } + + Future deleteAllByAssetId(List assetIdValues) { + final values = assetIdValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'assetId', values); + } + + int deleteAllByAssetIdSync(List assetIdValues) { + final values = assetIdValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'assetId', values); + } + + Future putByAssetId(DeviceAssetEntity object) { + return putByIndex(r'assetId', object); + } + + Id putByAssetIdSync(DeviceAssetEntity object, {bool saveLinks = true}) { + return putByIndexSync(r'assetId', object, saveLinks: saveLinks); + } + + Future> putAllByAssetId(List objects) { + return putAllByIndex(r'assetId', objects); + } + + List putAllByAssetIdSync( + List objects, { + bool saveLinks = true, + }) { + return putAllByIndexSync(r'assetId', objects, saveLinks: saveLinks); + } +} + +extension DeviceAssetEntityQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension DeviceAssetEntityQueryWhere + on QueryBuilder { + QueryBuilder + idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder + idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder + idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder + idLessThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder + idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + assetIdEqualTo(String assetId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'assetId', value: [assetId]), + ); + }); + } + + QueryBuilder + assetIdNotEqualTo(String assetId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'assetId', + lower: [], + upper: [assetId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'assetId', + lower: [assetId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'assetId', + lower: [assetId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'assetId', + lower: [], + upper: [assetId], + includeUpper: false, + ), + ); + } + }); + } + + QueryBuilder + hashEqualTo(List hash) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'hash', value: [hash]), + ); + }); + } + + QueryBuilder + hashNotEqualTo(List hash) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [hash], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'hash', + lower: [], + upper: [hash], + includeUpper: false, + ), + ); + } + }); + } +} + +extension DeviceAssetEntityQueryFilter + on QueryBuilder { + QueryBuilder + assetIdEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdBetween( + 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'assetId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'assetId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'assetId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + assetIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'assetId', value: ''), + ); + }); + } + + QueryBuilder + assetIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'assetId', value: ''), + ); + }); + } + + QueryBuilder + hashElementEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'hash', value: value), + ); + }); + } + + QueryBuilder + hashElementGreaterThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementLessThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'hash', + value: value, + ), + ); + }); + } + + QueryBuilder + hashElementBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'hash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + hashLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, true, length, true); + }); + } + + QueryBuilder + hashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, 0, true); + }); + } + + QueryBuilder + hashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, false, 999999, true); + }); + } + + QueryBuilder + hashLengthLessThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', 0, true, length, include); + }); + } + + QueryBuilder + hashLengthGreaterThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'hash', length, include, 999999, true); + }); + } + + QueryBuilder + hashLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'hash', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder + idGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idLessThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + modifiedTimeEqualTo(DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'modifiedTime', value: value), + ); + }); + } + + QueryBuilder + modifiedTimeGreaterThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'modifiedTime', + value: value, + ), + ); + }); + } + + QueryBuilder + modifiedTimeLessThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'modifiedTime', + value: value, + ), + ); + }); + } + + QueryBuilder + modifiedTimeBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'modifiedTime', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension DeviceAssetEntityQueryObject + on QueryBuilder {} + +extension DeviceAssetEntityQueryLinks + on QueryBuilder {} + +extension DeviceAssetEntityQuerySortBy + on QueryBuilder { + QueryBuilder + sortByAssetId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetId', Sort.asc); + }); + } + + QueryBuilder + sortByAssetIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetId', Sort.desc); + }); + } + + QueryBuilder + sortByModifiedTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedTime', Sort.asc); + }); + } + + QueryBuilder + sortByModifiedTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedTime', Sort.desc); + }); + } +} + +extension DeviceAssetEntityQuerySortThenBy + on QueryBuilder { + QueryBuilder + thenByAssetId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetId', Sort.asc); + }); + } + + QueryBuilder + thenByAssetIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'assetId', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder + thenByModifiedTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedTime', Sort.asc); + }); + } + + QueryBuilder + thenByModifiedTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'modifiedTime', Sort.desc); + }); + } +} + +extension DeviceAssetEntityQueryWhereDistinct + on QueryBuilder { + QueryBuilder + distinctByAssetId({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'assetId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByHash() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'hash'); + }); + } + + QueryBuilder + distinctByModifiedTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'modifiedTime'); + }); + } +} + +extension DeviceAssetEntityQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder assetIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'assetId'); + }); + } + + QueryBuilder, QQueryOperations> hashProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'hash'); + }); + } + + QueryBuilder + modifiedTimeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'modifiedTime'); + }); + } +} diff --git a/mobile/lib/infrastructure/entities/exif.entity.dart b/mobile/lib/infrastructure/entities/exif.entity.dart index 3328b38f31..77cae5dbbe 100644 --- a/mobile/lib/infrastructure/entities/exif.entity.dart +++ b/mobile/lib/infrastructure/entities/exif.entity.dart @@ -4,12 +4,98 @@ import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; 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'; + +part 'exif.entity.g.dart'; + +/// Exif information 1:1 relation with Asset +@Collection(inheritance: false) +class ExifInfo { + final Id? id; + final int? fileSize; + final DateTime? dateTimeOriginal; + final String? timeZone; + final String? make; + final String? model; + final String? lens; + final float? f; + final float? mm; + final short? iso; + final float? exposureSeconds; + final float? lat; + final float? long; + final String? city; + final String? state; + final String? country; + final String? description; + final String? orientation; + + const ExifInfo({ + this.id, + this.fileSize, + this.dateTimeOriginal, + this.timeZone, + this.make, + this.model, + this.lens, + this.f, + this.mm, + this.iso, + this.exposureSeconds, + this.lat, + this.long, + this.city, + this.state, + this.country, + this.description, + this.orientation, + }); + + static ExifInfo fromDto(domain.ExifInfo dto) => ExifInfo( + id: dto.assetId, + fileSize: dto.fileSize, + dateTimeOriginal: dto.dateTimeOriginal, + timeZone: dto.timeZone, + make: dto.make, + model: dto.model, + lens: dto.lens, + f: dto.f, + mm: dto.mm, + iso: dto.iso?.toInt(), + exposureSeconds: dto.exposureSeconds, + lat: dto.latitude, + long: dto.longitude, + city: dto.city, + state: dto.state, + country: dto.country, + description: dto.description, + orientation: dto.orientation, + ); + + domain.ExifInfo toDto() => domain.ExifInfo( + assetId: id, + fileSize: fileSize, + description: description, + orientation: orientation, + timeZone: timeZone, + dateTimeOriginal: dateTimeOriginal, + isFlipped: ExifDtoConverter.isOrientationFlipped(orientation), + latitude: lat, + longitude: long, + city: city, + state: state, + country: country, + make: make, + model: model, + lens: lens, + f: f, + mm: mm, + iso: iso?.toInt(), + exposureSeconds: exposureSeconds, + ); +} @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)') -@TableIndex.sql(''' -CREATE INDEX IF NOT EXISTS idx_remote_exif_city -ON remote_exif_entity (city) WHERE city IS NOT NULL -''') class RemoteExifEntity extends Table with DriftDefaultsMixin { const RemoteExifEntity(); @@ -66,8 +152,6 @@ extension RemoteExifEntityDataDomainEx on RemoteExifEntityData { fileSize: fileSize, dateTimeOriginal: dateTimeOriginal, rating: rating, - width: width, - height: height, timeZone: timeZone, make: make, model: model, @@ -79,8 +163,8 @@ extension RemoteExifEntityDataDomainEx on RemoteExifEntityData { orientation: orientation, latitude: latitude, longitude: longitude, - f: fNumber, - mm: focalLength, + f: fNumber?.toDouble(), + mm: focalLength?.toDouble(), lens: lens, isFlipped: ExifDtoConverter.isOrientationFlipped(orientation), exposureSeconds: ExifDtoConverter.exposureTimeToSeconds(exposureTime), diff --git a/mobile/lib/infrastructure/entities/exif.entity.drift.dart b/mobile/lib/infrastructure/entities/exif.entity.drift.dart new file mode 100644 index 0000000000..8695e2004b --- /dev/null +++ b/mobile/lib/infrastructure/entities/exif.entity.drift.dart @@ -0,0 +1,1885 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; + +typedef $$RemoteExifEntityTableCreateCompanionBuilder = + i1.RemoteExifEntityCompanion Function({ + required String assetId, + i0.Value city, + i0.Value state, + i0.Value country, + i0.Value dateTimeOriginal, + i0.Value description, + i0.Value height, + i0.Value width, + i0.Value exposureTime, + i0.Value fNumber, + i0.Value fileSize, + i0.Value focalLength, + i0.Value latitude, + i0.Value longitude, + i0.Value iso, + i0.Value make, + i0.Value model, + i0.Value lens, + i0.Value orientation, + i0.Value timeZone, + i0.Value rating, + i0.Value projectionType, + }); +typedef $$RemoteExifEntityTableUpdateCompanionBuilder = + i1.RemoteExifEntityCompanion Function({ + i0.Value assetId, + i0.Value city, + i0.Value state, + i0.Value country, + i0.Value dateTimeOriginal, + i0.Value description, + i0.Value height, + i0.Value width, + i0.Value exposureTime, + i0.Value fNumber, + i0.Value fileSize, + i0.Value focalLength, + i0.Value latitude, + i0.Value longitude, + i0.Value iso, + i0.Value make, + i0.Value model, + i0.Value lens, + i0.Value orientation, + i0.Value timeZone, + i0.Value rating, + i0.Value projectionType, + }); + +final class $$RemoteExifEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteExifEntityTable, + i1.RemoteExifEntityData + > { + $$RemoteExifEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet('remote_exif_entity') + .assetId, + i4.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i3.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i3 + .$$RemoteAssetEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteExifEntityTableFilterComposer + extends i0.Composer { + $$RemoteExifEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get city => $composableBuilder( + column: $table.city, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get state => $composableBuilder( + column: $table.state, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get country => $composableBuilder( + column: $table.country, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get dateTimeOriginal => $composableBuilder( + column: $table.dateTimeOriginal, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get exposureTime => $composableBuilder( + column: $table.exposureTime, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get fNumber => $composableBuilder( + column: $table.fNumber, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get fileSize => $composableBuilder( + column: $table.fileSize, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get focalLength => $composableBuilder( + column: $table.focalLength, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get iso => $composableBuilder( + column: $table.iso, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get make => $composableBuilder( + column: $table.make, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get model => $composableBuilder( + column: $table.model, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get lens => $composableBuilder( + column: $table.lens, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get timeZone => $composableBuilder( + column: $table.timeZone, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get rating => $composableBuilder( + column: $table.rating, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get projectionType => $composableBuilder( + column: $table.projectionType, + builder: (column) => i0.ColumnFilters(column), + ); + + i3.$$RemoteAssetEntityTableFilterComposer get assetId { + final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteExifEntityTableOrderingComposer + extends i0.Composer { + $$RemoteExifEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get city => $composableBuilder( + column: $table.city, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get state => $composableBuilder( + column: $table.state, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get country => $composableBuilder( + column: $table.country, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get dateTimeOriginal => $composableBuilder( + column: $table.dateTimeOriginal, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get exposureTime => $composableBuilder( + column: $table.exposureTime, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get fNumber => $composableBuilder( + column: $table.fNumber, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get fileSize => $composableBuilder( + column: $table.fileSize, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get focalLength => $composableBuilder( + column: $table.focalLength, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get iso => $composableBuilder( + column: $table.iso, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get make => $composableBuilder( + column: $table.make, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get model => $composableBuilder( + column: $table.model, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get lens => $composableBuilder( + column: $table.lens, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get timeZone => $composableBuilder( + column: $table.timeZone, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get rating => $composableBuilder( + column: $table.rating, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get projectionType => $composableBuilder( + column: $table.projectionType, + builder: (column) => i0.ColumnOrderings(column), + ); + + i3.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i3.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteExifEntityTableAnnotationComposer + extends i0.Composer { + $$RemoteExifEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get city => + $composableBuilder(column: $table.city, builder: (column) => column); + + i0.GeneratedColumn get state => + $composableBuilder(column: $table.state, builder: (column) => column); + + i0.GeneratedColumn get country => + $composableBuilder(column: $table.country, builder: (column) => column); + + i0.GeneratedColumn get dateTimeOriginal => $composableBuilder( + column: $table.dateTimeOriginal, + builder: (column) => column, + ); + + i0.GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + i0.GeneratedColumn get height => + $composableBuilder(column: $table.height, builder: (column) => column); + + i0.GeneratedColumn get width => + $composableBuilder(column: $table.width, builder: (column) => column); + + i0.GeneratedColumn get exposureTime => $composableBuilder( + column: $table.exposureTime, + builder: (column) => column, + ); + + i0.GeneratedColumn get fNumber => + $composableBuilder(column: $table.fNumber, builder: (column) => column); + + i0.GeneratedColumn get fileSize => + $composableBuilder(column: $table.fileSize, builder: (column) => column); + + i0.GeneratedColumn get focalLength => $composableBuilder( + column: $table.focalLength, + builder: (column) => column, + ); + + i0.GeneratedColumn get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => column); + + i0.GeneratedColumn get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => column); + + i0.GeneratedColumn get iso => + $composableBuilder(column: $table.iso, builder: (column) => column); + + i0.GeneratedColumn get make => + $composableBuilder(column: $table.make, builder: (column) => column); + + i0.GeneratedColumn get model => + $composableBuilder(column: $table.model, builder: (column) => column); + + i0.GeneratedColumn get lens => + $composableBuilder(column: $table.lens, builder: (column) => column); + + i0.GeneratedColumn get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => column, + ); + + i0.GeneratedColumn get timeZone => + $composableBuilder(column: $table.timeZone, builder: (column) => column); + + i0.GeneratedColumn get rating => + $composableBuilder(column: $table.rating, builder: (column) => column); + + i0.GeneratedColumn get projectionType => $composableBuilder( + column: $table.projectionType, + builder: (column) => column, + ); + + i3.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i3.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteExifEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteExifEntityTable, + i1.RemoteExifEntityData, + i1.$$RemoteExifEntityTableFilterComposer, + i1.$$RemoteExifEntityTableOrderingComposer, + i1.$$RemoteExifEntityTableAnnotationComposer, + $$RemoteExifEntityTableCreateCompanionBuilder, + $$RemoteExifEntityTableUpdateCompanionBuilder, + (i1.RemoteExifEntityData, i1.$$RemoteExifEntityTableReferences), + i1.RemoteExifEntityData, + i0.PrefetchHooks Function({bool assetId}) + > { + $$RemoteExifEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteExifEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteExifEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$RemoteExifEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$RemoteExifEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value city = const i0.Value.absent(), + i0.Value state = const i0.Value.absent(), + i0.Value country = const i0.Value.absent(), + i0.Value dateTimeOriginal = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value exposureTime = const i0.Value.absent(), + i0.Value fNumber = const i0.Value.absent(), + i0.Value fileSize = const i0.Value.absent(), + i0.Value focalLength = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i0.Value iso = const i0.Value.absent(), + i0.Value make = const i0.Value.absent(), + i0.Value model = const i0.Value.absent(), + i0.Value lens = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value timeZone = const i0.Value.absent(), + i0.Value rating = const i0.Value.absent(), + i0.Value projectionType = const i0.Value.absent(), + }) => i1.RemoteExifEntityCompanion( + assetId: assetId, + city: city, + state: state, + country: country, + dateTimeOriginal: dateTimeOriginal, + description: description, + height: height, + width: width, + exposureTime: exposureTime, + fNumber: fNumber, + fileSize: fileSize, + focalLength: focalLength, + latitude: latitude, + longitude: longitude, + iso: iso, + make: make, + model: model, + lens: lens, + orientation: orientation, + timeZone: timeZone, + rating: rating, + projectionType: projectionType, + ), + createCompanionCallback: + ({ + required String assetId, + i0.Value city = const i0.Value.absent(), + i0.Value state = const i0.Value.absent(), + i0.Value country = const i0.Value.absent(), + i0.Value dateTimeOriginal = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value exposureTime = const i0.Value.absent(), + i0.Value fNumber = const i0.Value.absent(), + i0.Value fileSize = const i0.Value.absent(), + i0.Value focalLength = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i0.Value iso = const i0.Value.absent(), + i0.Value make = const i0.Value.absent(), + i0.Value model = const i0.Value.absent(), + i0.Value lens = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value timeZone = const i0.Value.absent(), + i0.Value rating = const i0.Value.absent(), + i0.Value projectionType = const i0.Value.absent(), + }) => i1.RemoteExifEntityCompanion.insert( + assetId: assetId, + city: city, + state: state, + country: country, + dateTimeOriginal: dateTimeOriginal, + description: description, + height: height, + width: width, + exposureTime: exposureTime, + fNumber: fNumber, + fileSize: fileSize, + focalLength: focalLength, + latitude: latitude, + longitude: longitude, + iso: iso, + make: make, + model: model, + lens: lens, + orientation: orientation, + timeZone: timeZone, + rating: rating, + projectionType: projectionType, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteExifEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$RemoteExifEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$RemoteExifEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteExifEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteExifEntityTable, + i1.RemoteExifEntityData, + i1.$$RemoteExifEntityTableFilterComposer, + i1.$$RemoteExifEntityTableOrderingComposer, + i1.$$RemoteExifEntityTableAnnotationComposer, + $$RemoteExifEntityTableCreateCompanionBuilder, + $$RemoteExifEntityTableUpdateCompanionBuilder, + (i1.RemoteExifEntityData, i1.$$RemoteExifEntityTableReferences), + i1.RemoteExifEntityData, + i0.PrefetchHooks Function({bool assetId}) + >; +i0.Index get idxLatLng => i0.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', +); + +class $RemoteExifEntityTable extends i2.RemoteExifEntity + with i0.TableInfo<$RemoteExifEntityTable, i1.RemoteExifEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteExifEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _cityMeta = const i0.VerificationMeta( + 'city', + ); + @override + late final i0.GeneratedColumn city = i0.GeneratedColumn( + 'city', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _stateMeta = const i0.VerificationMeta( + 'state', + ); + @override + late final i0.GeneratedColumn state = i0.GeneratedColumn( + 'state', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _countryMeta = const i0.VerificationMeta( + 'country', + ); + @override + late final i0.GeneratedColumn country = i0.GeneratedColumn( + 'country', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _dateTimeOriginalMeta = + const i0.VerificationMeta('dateTimeOriginal'); + @override + late final i0.GeneratedColumn dateTimeOriginal = + i0.GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _descriptionMeta = const i0.VerificationMeta( + 'description', + ); + @override + late final i0.GeneratedColumn description = + i0.GeneratedColumn( + 'description', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta( + 'height', + ); + @override + late final i0.GeneratedColumn height = i0.GeneratedColumn( + 'height', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta( + 'width', + ); + @override + late final i0.GeneratedColumn width = i0.GeneratedColumn( + 'width', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _exposureTimeMeta = + const i0.VerificationMeta('exposureTime'); + @override + late final i0.GeneratedColumn exposureTime = + i0.GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _fNumberMeta = const i0.VerificationMeta( + 'fNumber', + ); + @override + late final i0.GeneratedColumn fNumber = i0.GeneratedColumn( + 'f_number', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _fileSizeMeta = const i0.VerificationMeta( + 'fileSize', + ); + @override + late final i0.GeneratedColumn fileSize = i0.GeneratedColumn( + 'file_size', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _focalLengthMeta = const i0.VerificationMeta( + 'focalLength', + ); + @override + late final i0.GeneratedColumn focalLength = + i0.GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _latitudeMeta = const i0.VerificationMeta( + 'latitude', + ); + @override + late final i0.GeneratedColumn latitude = i0.GeneratedColumn( + 'latitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _longitudeMeta = const i0.VerificationMeta( + 'longitude', + ); + @override + late final i0.GeneratedColumn longitude = i0.GeneratedColumn( + 'longitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isoMeta = const i0.VerificationMeta('iso'); + @override + late final i0.GeneratedColumn iso = i0.GeneratedColumn( + 'iso', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _makeMeta = const i0.VerificationMeta( + 'make', + ); + @override + late final i0.GeneratedColumn make = i0.GeneratedColumn( + 'make', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _modelMeta = const i0.VerificationMeta( + 'model', + ); + @override + late final i0.GeneratedColumn model = i0.GeneratedColumn( + 'model', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _lensMeta = const i0.VerificationMeta( + 'lens', + ); + @override + late final i0.GeneratedColumn lens = i0.GeneratedColumn( + 'lens', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta( + 'orientation', + ); + @override + late final i0.GeneratedColumn orientation = + i0.GeneratedColumn( + 'orientation', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _timeZoneMeta = const i0.VerificationMeta( + 'timeZone', + ); + @override + late final i0.GeneratedColumn timeZone = i0.GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _ratingMeta = const i0.VerificationMeta( + 'rating', + ); + @override + late final i0.GeneratedColumn rating = i0.GeneratedColumn( + 'rating', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _projectionTypeMeta = + const i0.VerificationMeta('projectionType'); + @override + late final i0.GeneratedColumn projectionType = + i0.GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('city')) { + context.handle( + _cityMeta, + city.isAcceptableOrUnknown(data['city']!, _cityMeta), + ); + } + if (data.containsKey('state')) { + context.handle( + _stateMeta, + state.isAcceptableOrUnknown(data['state']!, _stateMeta), + ); + } + if (data.containsKey('country')) { + context.handle( + _countryMeta, + country.isAcceptableOrUnknown(data['country']!, _countryMeta), + ); + } + if (data.containsKey('date_time_original')) { + context.handle( + _dateTimeOriginalMeta, + dateTimeOriginal.isAcceptableOrUnknown( + data['date_time_original']!, + _dateTimeOriginalMeta, + ), + ); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('height')) { + context.handle( + _heightMeta, + height.isAcceptableOrUnknown(data['height']!, _heightMeta), + ); + } + if (data.containsKey('width')) { + context.handle( + _widthMeta, + width.isAcceptableOrUnknown(data['width']!, _widthMeta), + ); + } + if (data.containsKey('exposure_time')) { + context.handle( + _exposureTimeMeta, + exposureTime.isAcceptableOrUnknown( + data['exposure_time']!, + _exposureTimeMeta, + ), + ); + } + if (data.containsKey('f_number')) { + context.handle( + _fNumberMeta, + fNumber.isAcceptableOrUnknown(data['f_number']!, _fNumberMeta), + ); + } + if (data.containsKey('file_size')) { + context.handle( + _fileSizeMeta, + fileSize.isAcceptableOrUnknown(data['file_size']!, _fileSizeMeta), + ); + } + if (data.containsKey('focal_length')) { + context.handle( + _focalLengthMeta, + focalLength.isAcceptableOrUnknown( + data['focal_length']!, + _focalLengthMeta, + ), + ); + } + if (data.containsKey('latitude')) { + context.handle( + _latitudeMeta, + latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta), + ); + } + if (data.containsKey('longitude')) { + context.handle( + _longitudeMeta, + longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta), + ); + } + if (data.containsKey('iso')) { + context.handle( + _isoMeta, + iso.isAcceptableOrUnknown(data['iso']!, _isoMeta), + ); + } + if (data.containsKey('make')) { + context.handle( + _makeMeta, + make.isAcceptableOrUnknown(data['make']!, _makeMeta), + ); + } + if (data.containsKey('model')) { + context.handle( + _modelMeta, + model.isAcceptableOrUnknown(data['model']!, _modelMeta), + ); + } + if (data.containsKey('lens')) { + context.handle( + _lensMeta, + lens.isAcceptableOrUnknown(data['lens']!, _lensMeta), + ); + } + if (data.containsKey('orientation')) { + context.handle( + _orientationMeta, + orientation.isAcceptableOrUnknown( + data['orientation']!, + _orientationMeta, + ), + ); + } + if (data.containsKey('time_zone')) { + context.handle( + _timeZoneMeta, + timeZone.isAcceptableOrUnknown(data['time_zone']!, _timeZoneMeta), + ); + } + if (data.containsKey('rating')) { + context.handle( + _ratingMeta, + rating.isAcceptableOrUnknown(data['rating']!, _ratingMeta), + ); + } + if (data.containsKey('projection_type')) { + context.handle( + _projectionTypeMeta, + projectionType.isAcceptableOrUnknown( + data['projection_type']!, + _projectionTypeMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {assetId}; + @override + i1.RemoteExifEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + $RemoteExifEntityTable createAlias(String alias) { + return $RemoteExifEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = i0.Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = i0.Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = i0.Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = i0.Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = i0.Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = i0.Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = i0.Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = i0.Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = i0.Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = i0.Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = i0.Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = i0.Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = i0.Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = i0.Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = i0.Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = i0.Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = i0.Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = i0.Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = i0.Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = i0.Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = i0.Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + i1.RemoteExifEntityData copyWith({ + String? assetId, + i0.Value city = const i0.Value.absent(), + i0.Value state = const i0.Value.absent(), + i0.Value country = const i0.Value.absent(), + i0.Value dateTimeOriginal = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value exposureTime = const i0.Value.absent(), + i0.Value fNumber = const i0.Value.absent(), + i0.Value fileSize = const i0.Value.absent(), + i0.Value focalLength = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i0.Value iso = const i0.Value.absent(), + i0.Value make = const i0.Value.absent(), + i0.Value model = const i0.Value.absent(), + i0.Value lens = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value timeZone = const i0.Value.absent(), + i0.Value rating = const i0.Value.absent(), + i0.Value projectionType = const i0.Value.absent(), + }) => i1.RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(i1.RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value city; + final i0.Value state; + final i0.Value country; + final i0.Value dateTimeOriginal; + final i0.Value description; + final i0.Value height; + final i0.Value width; + final i0.Value exposureTime; + final i0.Value fNumber; + final i0.Value fileSize; + final i0.Value focalLength; + final i0.Value latitude; + final i0.Value longitude; + final i0.Value iso; + final i0.Value make; + final i0.Value model; + final i0.Value lens; + final i0.Value orientation; + final i0.Value timeZone; + final i0.Value rating; + final i0.Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.city = const i0.Value.absent(), + this.state = const i0.Value.absent(), + this.country = const i0.Value.absent(), + this.dateTimeOriginal = const i0.Value.absent(), + this.description = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.exposureTime = const i0.Value.absent(), + this.fNumber = const i0.Value.absent(), + this.fileSize = const i0.Value.absent(), + this.focalLength = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + this.iso = const i0.Value.absent(), + this.make = const i0.Value.absent(), + this.model = const i0.Value.absent(), + this.lens = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + this.timeZone = const i0.Value.absent(), + this.rating = const i0.Value.absent(), + this.projectionType = const i0.Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const i0.Value.absent(), + this.state = const i0.Value.absent(), + this.country = const i0.Value.absent(), + this.dateTimeOriginal = const i0.Value.absent(), + this.description = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.exposureTime = const i0.Value.absent(), + this.fNumber = const i0.Value.absent(), + this.fileSize = const i0.Value.absent(), + this.focalLength = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + this.iso = const i0.Value.absent(), + this.make = const i0.Value.absent(), + this.model = const i0.Value.absent(), + this.lens = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + this.timeZone = const i0.Value.absent(), + this.rating = const i0.Value.absent(), + this.projectionType = const i0.Value.absent(), + }) : assetId = i0.Value(assetId); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? city, + i0.Expression? state, + i0.Expression? country, + i0.Expression? dateTimeOriginal, + i0.Expression? description, + i0.Expression? height, + i0.Expression? width, + i0.Expression? exposureTime, + i0.Expression? fNumber, + i0.Expression? fileSize, + i0.Expression? focalLength, + i0.Expression? latitude, + i0.Expression? longitude, + i0.Expression? iso, + i0.Expression? make, + i0.Expression? model, + i0.Expression? lens, + i0.Expression? orientation, + i0.Expression? timeZone, + i0.Expression? rating, + i0.Expression? projectionType, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + i1.RemoteExifEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? city, + i0.Value? state, + i0.Value? country, + i0.Value? dateTimeOriginal, + i0.Value? description, + i0.Value? height, + i0.Value? width, + i0.Value? exposureTime, + i0.Value? fNumber, + i0.Value? fileSize, + i0.Value? focalLength, + i0.Value? latitude, + i0.Value? longitude, + i0.Value? iso, + i0.Value? make, + i0.Value? model, + i0.Value? lens, + i0.Value? orientation, + i0.Value? timeZone, + i0.Value? rating, + i0.Value? projectionType, + }) { + return i1.RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (city.present) { + map['city'] = i0.Variable(city.value); + } + if (state.present) { + map['state'] = i0.Variable(state.value); + } + if (country.present) { + map['country'] = i0.Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = i0.Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = i0.Variable(description.value); + } + if (height.present) { + map['height'] = i0.Variable(height.value); + } + if (width.present) { + map['width'] = i0.Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = i0.Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = i0.Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = i0.Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = i0.Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = i0.Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = i0.Variable(longitude.value); + } + if (iso.present) { + map['iso'] = i0.Variable(iso.value); + } + if (make.present) { + map['make'] = i0.Variable(make.value); + } + if (model.present) { + map['model'] = i0.Variable(model.value); + } + if (lens.present) { + map['lens'] = i0.Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = i0.Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = i0.Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = i0.Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = i0.Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/exif.entity.g.dart b/mobile/lib/infrastructure/entities/exif.entity.g.dart new file mode 100644 index 0000000000..ffbfd0d8f0 --- /dev/null +++ b/mobile/lib/infrastructure/entities/exif.entity.g.dart @@ -0,0 +1,3200 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetExifInfoCollection on Isar { + IsarCollection get exifInfos => this.collection(); +} + +const ExifInfoSchema = CollectionSchema( + name: r'ExifInfo', + id: -2409260054350835217, + properties: { + r'city': PropertySchema(id: 0, name: r'city', type: IsarType.string), + r'country': PropertySchema(id: 1, name: r'country', type: IsarType.string), + r'dateTimeOriginal': PropertySchema( + id: 2, + name: r'dateTimeOriginal', + type: IsarType.dateTime, + ), + r'description': PropertySchema( + id: 3, + name: r'description', + type: IsarType.string, + ), + r'exposureSeconds': PropertySchema( + id: 4, + name: r'exposureSeconds', + type: IsarType.float, + ), + r'f': PropertySchema(id: 5, name: r'f', type: IsarType.float), + r'fileSize': PropertySchema(id: 6, name: r'fileSize', type: IsarType.long), + r'iso': PropertySchema(id: 7, name: r'iso', type: IsarType.int), + r'lat': PropertySchema(id: 8, name: r'lat', type: IsarType.float), + r'lens': PropertySchema(id: 9, name: r'lens', type: IsarType.string), + r'long': PropertySchema(id: 10, name: r'long', type: IsarType.float), + r'make': PropertySchema(id: 11, name: r'make', type: IsarType.string), + r'mm': PropertySchema(id: 12, name: r'mm', type: IsarType.float), + r'model': PropertySchema(id: 13, name: r'model', type: IsarType.string), + r'orientation': PropertySchema( + id: 14, + name: r'orientation', + type: IsarType.string, + ), + r'state': PropertySchema(id: 15, name: r'state', type: IsarType.string), + r'timeZone': PropertySchema( + id: 16, + name: r'timeZone', + type: IsarType.string, + ), + }, + + estimateSize: _exifInfoEstimateSize, + serialize: _exifInfoSerialize, + deserialize: _exifInfoDeserialize, + deserializeProp: _exifInfoDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + + getId: _exifInfoGetId, + getLinks: _exifInfoGetLinks, + attach: _exifInfoAttach, + version: '3.3.0-dev.3', +); + +int _exifInfoEstimateSize( + ExifInfo object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.city; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.country; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.description; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.lens; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.make; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.model; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.orientation; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.state; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.timeZone; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _exifInfoSerialize( + ExifInfo object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.city); + writer.writeString(offsets[1], object.country); + writer.writeDateTime(offsets[2], object.dateTimeOriginal); + writer.writeString(offsets[3], object.description); + writer.writeFloat(offsets[4], object.exposureSeconds); + writer.writeFloat(offsets[5], object.f); + writer.writeLong(offsets[6], object.fileSize); + writer.writeInt(offsets[7], object.iso); + writer.writeFloat(offsets[8], object.lat); + writer.writeString(offsets[9], object.lens); + writer.writeFloat(offsets[10], object.long); + writer.writeString(offsets[11], object.make); + writer.writeFloat(offsets[12], object.mm); + writer.writeString(offsets[13], object.model); + writer.writeString(offsets[14], object.orientation); + writer.writeString(offsets[15], object.state); + writer.writeString(offsets[16], object.timeZone); +} + +ExifInfo _exifInfoDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ExifInfo( + city: reader.readStringOrNull(offsets[0]), + country: reader.readStringOrNull(offsets[1]), + dateTimeOriginal: reader.readDateTimeOrNull(offsets[2]), + description: reader.readStringOrNull(offsets[3]), + exposureSeconds: reader.readFloatOrNull(offsets[4]), + f: reader.readFloatOrNull(offsets[5]), + fileSize: reader.readLongOrNull(offsets[6]), + id: id, + iso: reader.readIntOrNull(offsets[7]), + lat: reader.readFloatOrNull(offsets[8]), + lens: reader.readStringOrNull(offsets[9]), + long: reader.readFloatOrNull(offsets[10]), + make: reader.readStringOrNull(offsets[11]), + mm: reader.readFloatOrNull(offsets[12]), + model: reader.readStringOrNull(offsets[13]), + orientation: reader.readStringOrNull(offsets[14]), + state: reader.readStringOrNull(offsets[15]), + timeZone: reader.readStringOrNull(offsets[16]), + ); + return object; +} + +P _exifInfoDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + case 2: + return (reader.readDateTimeOrNull(offset)) as P; + case 3: + return (reader.readStringOrNull(offset)) as P; + case 4: + return (reader.readFloatOrNull(offset)) as P; + case 5: + return (reader.readFloatOrNull(offset)) as P; + case 6: + return (reader.readLongOrNull(offset)) as P; + case 7: + return (reader.readIntOrNull(offset)) as P; + case 8: + return (reader.readFloatOrNull(offset)) as P; + case 9: + return (reader.readStringOrNull(offset)) as P; + case 10: + return (reader.readFloatOrNull(offset)) as P; + case 11: + return (reader.readStringOrNull(offset)) as P; + case 12: + return (reader.readFloatOrNull(offset)) as P; + case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: + return (reader.readStringOrNull(offset)) as P; + case 15: + return (reader.readStringOrNull(offset)) as P; + case 16: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _exifInfoGetId(ExifInfo object) { + return object.id ?? Isar.autoIncrement; +} + +List> _exifInfoGetLinks(ExifInfo object) { + return []; +} + +void _exifInfoAttach(IsarCollection col, Id id, ExifInfo object) {} + +extension ExifInfoQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension ExifInfoQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension ExifInfoQueryFilter + on QueryBuilder { + QueryBuilder cityIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'city'), + ); + }); + } + + QueryBuilder cityIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'city'), + ); + }); + } + + QueryBuilder cityEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityBetween( + 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'city', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'city', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'city', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder cityIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'city', value: ''), + ); + }); + } + + QueryBuilder cityIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'city', value: ''), + ); + }); + } + + QueryBuilder countryIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'country'), + ); + }); + } + + QueryBuilder countryIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'country'), + ); + }); + } + + QueryBuilder countryEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryBetween( + 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'country', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'country', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'country', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder countryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'country', value: ''), + ); + }); + } + + QueryBuilder countryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'country', value: ''), + ); + }); + } + + QueryBuilder + dateTimeOriginalIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'dateTimeOriginal'), + ); + }); + } + + QueryBuilder + dateTimeOriginalIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'dateTimeOriginal'), + ); + }); + } + + QueryBuilder + dateTimeOriginalEqualTo(DateTime? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'dateTimeOriginal', value: value), + ); + }); + } + + QueryBuilder + dateTimeOriginalGreaterThan(DateTime? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'dateTimeOriginal', + value: value, + ), + ); + }); + } + + QueryBuilder + dateTimeOriginalLessThan(DateTime? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'dateTimeOriginal', + value: value, + ), + ); + }); + } + + QueryBuilder + dateTimeOriginalBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'dateTimeOriginal', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder descriptionIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'description'), + ); + }); + } + + QueryBuilder + descriptionIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'description'), + ); + }); + } + + QueryBuilder descriptionEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + 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 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 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 descriptionStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'description', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'description', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder descriptionIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'description', value: ''), + ); + }); + } + + QueryBuilder + descriptionIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'description', value: ''), + ); + }); + } + + QueryBuilder + exposureSecondsIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'exposureSeconds'), + ); + }); + } + + QueryBuilder + exposureSecondsIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'exposureSeconds'), + ); + }); + } + + QueryBuilder + exposureSecondsEqualTo(double? value, {double epsilon = Query.epsilon}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'exposureSeconds', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder + exposureSecondsGreaterThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'exposureSeconds', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder + exposureSecondsLessThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'exposureSeconds', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder + exposureSecondsBetween( + double? lower, + double? upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'exposureSeconds', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder fIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'f'), + ); + }); + } + + QueryBuilder fIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'f'), + ); + }); + } + + QueryBuilder fEqualTo( + double? value, { + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'f', value: value, epsilon: epsilon), + ); + }); + } + + QueryBuilder fGreaterThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'f', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder fLessThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'f', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder fBetween( + double? lower, + double? upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'f', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder fileSizeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'fileSize'), + ); + }); + } + + QueryBuilder fileSizeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'fileSize'), + ); + }); + } + + QueryBuilder fileSizeEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'fileSize', value: value), + ); + }); + } + + QueryBuilder fileSizeGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'fileSize', + value: value, + ), + ); + }); + } + + QueryBuilder fileSizeLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'fileSize', + value: value, + ), + ); + }); + } + + QueryBuilder fileSizeBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'fileSize', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'id'), + ); + }); + } + + QueryBuilder idIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'id'), + ); + }); + } + + QueryBuilder idEqualTo(Id? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder idGreaterThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idLessThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idBetween( + Id? lower, + Id? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder isoIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'iso'), + ); + }); + } + + QueryBuilder isoIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'iso'), + ); + }); + } + + QueryBuilder isoEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'iso', value: value), + ); + }); + } + + QueryBuilder isoGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'iso', + value: value, + ), + ); + }); + } + + QueryBuilder isoLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'iso', + value: value, + ), + ); + }); + } + + QueryBuilder isoBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'iso', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder latIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'lat'), + ); + }); + } + + QueryBuilder latIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'lat'), + ); + }); + } + + QueryBuilder latEqualTo( + double? value, { + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'lat', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder latGreaterThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'lat', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder latLessThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'lat', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder latBetween( + double? lower, + double? upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'lat', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder lensIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'lens'), + ); + }); + } + + QueryBuilder lensIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'lens'), + ); + }); + } + + QueryBuilder lensEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensBetween( + 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'lens', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'lens', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'lens', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder lensIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'lens', value: ''), + ); + }); + } + + QueryBuilder lensIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'lens', value: ''), + ); + }); + } + + QueryBuilder longIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'long'), + ); + }); + } + + QueryBuilder longIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'long'), + ); + }); + } + + QueryBuilder longEqualTo( + double? value, { + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'long', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder longGreaterThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'long', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder longLessThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'long', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder longBetween( + double? lower, + double? upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'long', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder makeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'make'), + ); + }); + } + + QueryBuilder makeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'make'), + ); + }); + } + + QueryBuilder makeEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeBetween( + 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'make', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'make', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'make', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder makeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'make', value: ''), + ); + }); + } + + QueryBuilder makeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'make', value: ''), + ); + }); + } + + QueryBuilder mmIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'mm'), + ); + }); + } + + QueryBuilder mmIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'mm'), + ); + }); + } + + QueryBuilder mmEqualTo( + double? value, { + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'mm', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder mmGreaterThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'mm', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder mmLessThan( + double? value, { + bool include = false, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'mm', + value: value, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder mmBetween( + double? lower, + double? upper, { + bool includeLower = true, + bool includeUpper = true, + double epsilon = Query.epsilon, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'mm', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + + epsilon: epsilon, + ), + ); + }); + } + + QueryBuilder modelIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'model'), + ); + }); + } + + QueryBuilder modelIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'model'), + ); + }); + } + + QueryBuilder modelEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelBetween( + 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'model', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'model', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'model', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder modelIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'model', value: ''), + ); + }); + } + + QueryBuilder modelIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'model', value: ''), + ); + }); + } + + QueryBuilder orientationIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'orientation'), + ); + }); + } + + QueryBuilder + orientationIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'orientation'), + ); + }); + } + + QueryBuilder orientationEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + orientationGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationBetween( + 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'orientation', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'orientation', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'orientation', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder orientationIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'orientation', value: ''), + ); + }); + } + + QueryBuilder + orientationIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'orientation', value: ''), + ); + }); + } + + QueryBuilder stateIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'state'), + ); + }); + } + + QueryBuilder stateIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'state'), + ); + }); + } + + QueryBuilder stateEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateBetween( + 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'state', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'state', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'state', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder stateIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'state', value: ''), + ); + }); + } + + QueryBuilder stateIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'state', value: ''), + ); + }); + } + + QueryBuilder timeZoneIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'timeZone'), + ); + }); + } + + QueryBuilder timeZoneIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'timeZone'), + ); + }); + } + + QueryBuilder timeZoneEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneBetween( + 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'timeZone', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'timeZone', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'timeZone', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder timeZoneIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'timeZone', value: ''), + ); + }); + } + + QueryBuilder timeZoneIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'timeZone', value: ''), + ); + }); + } +} + +extension ExifInfoQueryObject + on QueryBuilder {} + +extension ExifInfoQueryLinks + on QueryBuilder {} + +extension ExifInfoQuerySortBy on QueryBuilder { + QueryBuilder sortByCity() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'city', Sort.asc); + }); + } + + QueryBuilder sortByCityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'city', Sort.desc); + }); + } + + QueryBuilder sortByCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'country', Sort.asc); + }); + } + + QueryBuilder sortByCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'country', Sort.desc); + }); + } + + QueryBuilder sortByDateTimeOriginal() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'dateTimeOriginal', Sort.asc); + }); + } + + QueryBuilder sortByDateTimeOriginalDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'dateTimeOriginal', Sort.desc); + }); + } + + QueryBuilder sortByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder sortByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder sortByExposureSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exposureSeconds', Sort.asc); + }); + } + + QueryBuilder sortByExposureSecondsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exposureSeconds', Sort.desc); + }); + } + + QueryBuilder sortByF() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'f', Sort.asc); + }); + } + + QueryBuilder sortByFDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'f', Sort.desc); + }); + } + + QueryBuilder sortByFileSize() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileSize', Sort.asc); + }); + } + + QueryBuilder sortByFileSizeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileSize', Sort.desc); + }); + } + + QueryBuilder sortByIso() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'iso', Sort.asc); + }); + } + + QueryBuilder sortByIsoDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'iso', Sort.desc); + }); + } + + QueryBuilder sortByLat() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lat', Sort.asc); + }); + } + + QueryBuilder sortByLatDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lat', Sort.desc); + }); + } + + QueryBuilder sortByLens() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lens', Sort.asc); + }); + } + + QueryBuilder sortByLensDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lens', Sort.desc); + }); + } + + QueryBuilder sortByLong() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'long', Sort.asc); + }); + } + + QueryBuilder sortByLongDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'long', Sort.desc); + }); + } + + QueryBuilder sortByMake() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'make', Sort.asc); + }); + } + + QueryBuilder sortByMakeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'make', Sort.desc); + }); + } + + QueryBuilder sortByMm() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mm', Sort.asc); + }); + } + + QueryBuilder sortByMmDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mm', Sort.desc); + }); + } + + QueryBuilder sortByModel() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.asc); + }); + } + + QueryBuilder sortByModelDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.desc); + }); + } + + QueryBuilder sortByOrientation() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'orientation', Sort.asc); + }); + } + + QueryBuilder sortByOrientationDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'orientation', Sort.desc); + }); + } + + QueryBuilder sortByState() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'state', Sort.asc); + }); + } + + QueryBuilder sortByStateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'state', Sort.desc); + }); + } + + QueryBuilder sortByTimeZone() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeZone', Sort.asc); + }); + } + + QueryBuilder sortByTimeZoneDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeZone', Sort.desc); + }); + } +} + +extension ExifInfoQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByCity() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'city', Sort.asc); + }); + } + + QueryBuilder thenByCityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'city', Sort.desc); + }); + } + + QueryBuilder thenByCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'country', Sort.asc); + }); + } + + QueryBuilder thenByCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'country', Sort.desc); + }); + } + + QueryBuilder thenByDateTimeOriginal() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'dateTimeOriginal', Sort.asc); + }); + } + + QueryBuilder thenByDateTimeOriginalDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'dateTimeOriginal', Sort.desc); + }); + } + + QueryBuilder thenByDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.asc); + }); + } + + QueryBuilder thenByDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'description', Sort.desc); + }); + } + + QueryBuilder thenByExposureSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exposureSeconds', Sort.asc); + }); + } + + QueryBuilder thenByExposureSecondsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exposureSeconds', Sort.desc); + }); + } + + QueryBuilder thenByF() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'f', Sort.asc); + }); + } + + QueryBuilder thenByFDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'f', Sort.desc); + }); + } + + QueryBuilder thenByFileSize() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileSize', Sort.asc); + }); + } + + QueryBuilder thenByFileSizeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fileSize', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIso() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'iso', Sort.asc); + }); + } + + QueryBuilder thenByIsoDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'iso', Sort.desc); + }); + } + + QueryBuilder thenByLat() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lat', Sort.asc); + }); + } + + QueryBuilder thenByLatDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lat', Sort.desc); + }); + } + + QueryBuilder thenByLens() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lens', Sort.asc); + }); + } + + QueryBuilder thenByLensDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'lens', Sort.desc); + }); + } + + QueryBuilder thenByLong() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'long', Sort.asc); + }); + } + + QueryBuilder thenByLongDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'long', Sort.desc); + }); + } + + QueryBuilder thenByMake() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'make', Sort.asc); + }); + } + + QueryBuilder thenByMakeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'make', Sort.desc); + }); + } + + QueryBuilder thenByMm() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mm', Sort.asc); + }); + } + + QueryBuilder thenByMmDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'mm', Sort.desc); + }); + } + + QueryBuilder thenByModel() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.asc); + }); + } + + QueryBuilder thenByModelDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'model', Sort.desc); + }); + } + + QueryBuilder thenByOrientation() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'orientation', Sort.asc); + }); + } + + QueryBuilder thenByOrientationDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'orientation', Sort.desc); + }); + } + + QueryBuilder thenByState() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'state', Sort.asc); + }); + } + + QueryBuilder thenByStateDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'state', Sort.desc); + }); + } + + QueryBuilder thenByTimeZone() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeZone', Sort.asc); + }); + } + + QueryBuilder thenByTimeZoneDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timeZone', Sort.desc); + }); + } +} + +extension ExifInfoQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByCity({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'city', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByCountry({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'country', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByDateTimeOriginal() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'dateTimeOriginal'); + }); + } + + QueryBuilder distinctByDescription({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'description', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByExposureSeconds() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'exposureSeconds'); + }); + } + + QueryBuilder distinctByF() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'f'); + }); + } + + QueryBuilder distinctByFileSize() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fileSize'); + }); + } + + QueryBuilder distinctByIso() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'iso'); + }); + } + + QueryBuilder distinctByLat() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lat'); + }); + } + + QueryBuilder distinctByLens({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'lens', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByLong() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'long'); + }); + } + + QueryBuilder distinctByMake({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'make', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByMm() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'mm'); + }); + } + + QueryBuilder distinctByModel({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'model', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByOrientation({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'orientation', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByState({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'state', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTimeZone({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'timeZone', caseSensitive: caseSensitive); + }); + } +} + +extension ExifInfoQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder cityProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'city'); + }); + } + + QueryBuilder countryProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'country'); + }); + } + + QueryBuilder + dateTimeOriginalProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'dateTimeOriginal'); + }); + } + + QueryBuilder descriptionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'description'); + }); + } + + QueryBuilder exposureSecondsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'exposureSeconds'); + }); + } + + QueryBuilder fProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'f'); + }); + } + + QueryBuilder fileSizeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fileSize'); + }); + } + + QueryBuilder isoProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'iso'); + }); + } + + QueryBuilder latProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lat'); + }); + } + + QueryBuilder lensProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'lens'); + }); + } + + QueryBuilder longProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'long'); + }); + } + + QueryBuilder makeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'make'); + }); + } + + QueryBuilder mmProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'mm'); + }); + } + + QueryBuilder modelProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'model'); + }); + } + + QueryBuilder orientationProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'orientation'); + }); + } + + QueryBuilder stateProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'state'); + }); + } + + QueryBuilder timeZoneProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timeZone'); + }); + } +} diff --git a/mobile/lib/infrastructure/entities/local_album.entity.drift.dart b/mobile/lib/infrastructure/entities/local_album.entity.drift.dart new file mode 100644 index 0000000000..0703844291 --- /dev/null +++ b/mobile/lib/infrastructure/entities/local_album.entity.drift.dart @@ -0,0 +1,904 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/album/local_album.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' + as i5; +import 'package:drift/internal/modular.dart' as i6; + +typedef $$LocalAlbumEntityTableCreateCompanionBuilder = + i1.LocalAlbumEntityCompanion Function({ + required String id, + required String name, + i0.Value updatedAt, + required i2.BackupSelection backupSelection, + i0.Value isIosSharedAlbum, + i0.Value linkedRemoteAlbumId, + i0.Value marker_, + }); +typedef $$LocalAlbumEntityTableUpdateCompanionBuilder = + i1.LocalAlbumEntityCompanion Function({ + i0.Value id, + i0.Value name, + i0.Value updatedAt, + i0.Value backupSelection, + i0.Value isIosSharedAlbum, + i0.Value linkedRemoteAlbumId, + i0.Value marker_, + }); + +final class $$LocalAlbumEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LocalAlbumEntityTable, + i1.LocalAlbumEntityData + > { + $$LocalAlbumEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i5.$RemoteAlbumEntityTable _linkedRemoteAlbumIdTable( + i0.GeneratedDatabase db, + ) => i6.ReadDatabaseContainer(db) + .resultSet('remote_album_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet('local_album_entity') + .linkedRemoteAlbumId, + i6.ReadDatabaseContainer( + db, + ).resultSet('remote_album_entity').id, + ), + ); + + i5.$$RemoteAlbumEntityTableProcessedTableManager? get linkedRemoteAlbumId { + final $_column = $_itemColumn('linked_remote_album_id'); + if ($_column == null) return null; + final manager = i5 + .$$RemoteAlbumEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('remote_album_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_linkedRemoteAlbumIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$LocalAlbumEntityTableFilterComposer + extends i0.Composer { + $$LocalAlbumEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get backupSelection => $composableBuilder( + column: $table.backupSelection, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get isIosSharedAlbum => $composableBuilder( + column: $table.isIosSharedAlbum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get marker_ => $composableBuilder( + column: $table.marker_, + builder: (column) => i0.ColumnFilters(column), + ); + + i5.$$RemoteAlbumEntityTableFilterComposer get linkedRemoteAlbumId { + final i5.$$RemoteAlbumEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.linkedRemoteAlbumId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumEntityTableOrderingComposer + extends i0.Composer { + $$LocalAlbumEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get backupSelection => $composableBuilder( + column: $table.backupSelection, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isIosSharedAlbum => $composableBuilder( + column: $table.isIosSharedAlbum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get marker_ => $composableBuilder( + column: $table.marker_, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$RemoteAlbumEntityTableOrderingComposer get linkedRemoteAlbumId { + final i5.$$RemoteAlbumEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.linkedRemoteAlbumId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumEntityTableAnnotationComposer + extends i0.Composer { + $$LocalAlbumEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter + get backupSelection => $composableBuilder( + column: $table.backupSelection, + builder: (column) => column, + ); + + i0.GeneratedColumn get isIosSharedAlbum => $composableBuilder( + column: $table.isIosSharedAlbum, + builder: (column) => column, + ); + + i0.GeneratedColumn get marker_ => + $composableBuilder(column: $table.marker_, builder: (column) => column); + + i5.$$RemoteAlbumEntityTableAnnotationComposer get linkedRemoteAlbumId { + final i5.$$RemoteAlbumEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.linkedRemoteAlbumId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$LocalAlbumEntityTable, + i1.LocalAlbumEntityData, + i1.$$LocalAlbumEntityTableFilterComposer, + i1.$$LocalAlbumEntityTableOrderingComposer, + i1.$$LocalAlbumEntityTableAnnotationComposer, + $$LocalAlbumEntityTableCreateCompanionBuilder, + $$LocalAlbumEntityTableUpdateCompanionBuilder, + (i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences), + i1.LocalAlbumEntityData, + i0.PrefetchHooks Function({bool linkedRemoteAlbumId}) + > { + $$LocalAlbumEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$LocalAlbumEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$LocalAlbumEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$LocalAlbumEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$LocalAlbumEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value backupSelection = + const i0.Value.absent(), + i0.Value isIosSharedAlbum = const i0.Value.absent(), + i0.Value linkedRemoteAlbumId = const i0.Value.absent(), + i0.Value marker_ = const i0.Value.absent(), + }) => i1.LocalAlbumEntityCompanion( + id: id, + name: name, + updatedAt: updatedAt, + backupSelection: backupSelection, + isIosSharedAlbum: isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId, + marker_: marker_, + ), + createCompanionCallback: + ({ + required String id, + required String name, + i0.Value updatedAt = const i0.Value.absent(), + required i2.BackupSelection backupSelection, + i0.Value isIosSharedAlbum = const i0.Value.absent(), + i0.Value linkedRemoteAlbumId = const i0.Value.absent(), + i0.Value marker_ = const i0.Value.absent(), + }) => i1.LocalAlbumEntityCompanion.insert( + id: id, + name: name, + updatedAt: updatedAt, + backupSelection: backupSelection, + isIosSharedAlbum: isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId, + marker_: marker_, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$LocalAlbumEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({linkedRemoteAlbumId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (linkedRemoteAlbumId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.linkedRemoteAlbumId, + referencedTable: i1 + .$$LocalAlbumEntityTableReferences + ._linkedRemoteAlbumIdTable(db), + referencedColumn: i1 + .$$LocalAlbumEntityTableReferences + ._linkedRemoteAlbumIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$LocalAlbumEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$LocalAlbumEntityTable, + i1.LocalAlbumEntityData, + i1.$$LocalAlbumEntityTableFilterComposer, + i1.$$LocalAlbumEntityTableOrderingComposer, + i1.$$LocalAlbumEntityTableAnnotationComposer, + $$LocalAlbumEntityTableCreateCompanionBuilder, + $$LocalAlbumEntityTableUpdateCompanionBuilder, + (i1.LocalAlbumEntityData, i1.$$LocalAlbumEntityTableReferences), + i1.LocalAlbumEntityData, + i0.PrefetchHooks Function({bool linkedRemoteAlbumId}) + >; + +class $LocalAlbumEntityTable extends i3.LocalAlbumEntity + with i0.TableInfo<$LocalAlbumEntityTable, i1.LocalAlbumEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $LocalAlbumEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + backupSelection = + i0.GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$LocalAlbumEntityTable.$converterbackupSelection, + ); + static const i0.VerificationMeta _isIosSharedAlbumMeta = + const i0.VerificationMeta('isIosSharedAlbum'); + @override + late final i0.GeneratedColumn isIosSharedAlbum = + i0.GeneratedColumn( + '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 _linkedRemoteAlbumIdMeta = + const i0.VerificationMeta('linkedRemoteAlbumId'); + @override + late final i0.GeneratedColumn linkedRemoteAlbumId = + i0.GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + static const i0.VerificationMeta _marker_Meta = const i0.VerificationMeta( + 'marker_', + ); + @override + late final i0.GeneratedColumn marker_ = i0.GeneratedColumn( + 'marker', + aliasedName, + true, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('updated_at')) { + 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('linked_remote_album_id')) { + context.handle( + _linkedRemoteAlbumIdMeta, + linkedRemoteAlbumId.isAcceptableOrUnknown( + data['linked_remote_album_id']!, + _linkedRemoteAlbumIdMeta, + ), + ); + } + if (data.containsKey('marker')) { + context.handle( + _marker_Meta, + marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.LocalAlbumEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + 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'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + $LocalAlbumEntityTable createAlias(String alias) { + return $LocalAlbumEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 + $converterbackupSelection = const i0.EnumIndexConverter( + i2.BackupSelection.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String name; + final DateTime updatedAt; + final i2.BackupSelection backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['name'] = i0.Variable(name); + map['updated_at'] = i0.Variable(updatedAt); + { + map['backup_selection'] = i0.Variable( + i1.$LocalAlbumEntityTable.$converterbackupSelection.toSql( + backupSelection, + ), + ); + } + map['is_ios_shared_album'] = i0.Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = i0.Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = i0.Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: i1.$LocalAlbumEntityTable.$converterbackupSelection + .fromJson(serializer.fromJson(json['backupSelection'])), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson( + i1.$LocalAlbumEntityTable.$converterbackupSelection.toJson( + backupSelection, + ), + ), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + i1.LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + i2.BackupSelection? backupSelection, + bool? isIosSharedAlbum, + i0.Value linkedRemoteAlbumId = const i0.Value.absent(), + i0.Value 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, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(i1.LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + final i0.Value updatedAt; + final i0.Value backupSelection; + final i0.Value isIosSharedAlbum; + final i0.Value linkedRemoteAlbumId; + final i0.Value 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.linkedRemoteAlbumId = const i0.Value.absent(), + this.marker_ = const i0.Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const i0.Value.absent(), + required i2.BackupSelection backupSelection, + this.isIosSharedAlbum = const i0.Value.absent(), + this.linkedRemoteAlbumId = const i0.Value.absent(), + this.marker_ = const i0.Value.absent(), + }) : id = i0.Value(id), + name = i0.Value(name), + backupSelection = i0.Value(backupSelection); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + i0.Expression? updatedAt, + i0.Expression? backupSelection, + i0.Expression? isIosSharedAlbum, + i0.Expression? linkedRemoteAlbumId, + i0.Expression? marker_, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + i1.LocalAlbumEntityCompanion copyWith({ + i0.Value? id, + i0.Value? name, + i0.Value? updatedAt, + i0.Value? backupSelection, + i0.Value? isIosSharedAlbum, + i0.Value? linkedRemoteAlbumId, + i0.Value? marker_, + }) { + return i1.LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = i0.Variable( + i1.$LocalAlbumEntityTable.$converterbackupSelection.toSql( + backupSelection.value, + ), + ); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = i0.Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = i0.Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = i0.Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart new file mode 100644 index 0000000000..77b2798afb --- /dev/null +++ b/mobile/lib/infrastructure/entities/local_album_asset.entity.drift.dart @@ -0,0 +1,739 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart' + as i2; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' + as i5; + +typedef $$LocalAlbumAssetEntityTableCreateCompanionBuilder = + i1.LocalAlbumAssetEntityCompanion Function({ + required String assetId, + required String albumId, + i0.Value marker_, + }); +typedef $$LocalAlbumAssetEntityTableUpdateCompanionBuilder = + i1.LocalAlbumAssetEntityCompanion Function({ + i0.Value assetId, + i0.Value albumId, + i0.Value marker_, + }); + +final class $$LocalAlbumAssetEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LocalAlbumAssetEntityTable, + i1.LocalAlbumAssetEntityData + > { + $$LocalAlbumAssetEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i3.$LocalAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('local_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet( + 'local_album_asset_entity', + ) + .assetId, + i4.ReadDatabaseContainer( + db, + ).resultSet('local_asset_entity').id, + ), + ); + + i3.$$LocalAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i3 + .$$LocalAssetEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('local_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i5.$LocalAlbumEntityTable _albumIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('local_album_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet( + 'local_album_asset_entity', + ) + .albumId, + i4.ReadDatabaseContainer( + db, + ).resultSet('local_album_entity').id, + ), + ); + + i5.$$LocalAlbumEntityTableProcessedTableManager get albumId { + final $_column = $_itemColumn('album_id')!; + + final manager = i5 + .$$LocalAlbumEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('local_album_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_albumIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$LocalAlbumAssetEntityTableFilterComposer + extends i0.Composer { + $$LocalAlbumAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get marker_ => $composableBuilder( + column: $table.marker_, + builder: (column) => i0.ColumnFilters(column), + ); + + i3.$$LocalAssetEntityTableFilterComposer get assetId { + final i3.$$LocalAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$LocalAssetEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$LocalAlbumEntityTableFilterComposer get albumId { + final i5.$$LocalAlbumEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$LocalAlbumEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumAssetEntityTableOrderingComposer + extends i0.Composer { + $$LocalAlbumAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get marker_ => $composableBuilder( + column: $table.marker_, + builder: (column) => i0.ColumnOrderings(column), + ); + + i3.$$LocalAssetEntityTableOrderingComposer get assetId { + final i3.$$LocalAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$LocalAssetEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$LocalAlbumEntityTableOrderingComposer get albumId { + final i5.$$LocalAlbumEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$LocalAlbumEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumAssetEntityTableAnnotationComposer + extends i0.Composer { + $$LocalAlbumAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get marker_ => + $composableBuilder(column: $table.marker_, builder: (column) => column); + + i3.$$LocalAssetEntityTableAnnotationComposer get assetId { + final i3.$$LocalAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$LocalAssetEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$LocalAlbumEntityTableAnnotationComposer get albumId { + final i5.$$LocalAlbumEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$LocalAlbumEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('local_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$LocalAlbumAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$LocalAlbumAssetEntityTable, + i1.LocalAlbumAssetEntityData, + i1.$$LocalAlbumAssetEntityTableFilterComposer, + i1.$$LocalAlbumAssetEntityTableOrderingComposer, + i1.$$LocalAlbumAssetEntityTableAnnotationComposer, + $$LocalAlbumAssetEntityTableCreateCompanionBuilder, + $$LocalAlbumAssetEntityTableUpdateCompanionBuilder, + ( + i1.LocalAlbumAssetEntityData, + i1.$$LocalAlbumAssetEntityTableReferences, + ), + i1.LocalAlbumAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool albumId}) + > { + $$LocalAlbumAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$LocalAlbumAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$LocalAlbumAssetEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$LocalAlbumAssetEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$LocalAlbumAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value albumId = const i0.Value.absent(), + i0.Value marker_ = const i0.Value.absent(), + }) => i1.LocalAlbumAssetEntityCompanion( + assetId: assetId, + albumId: albumId, + marker_: marker_, + ), + createCompanionCallback: + ({ + required String assetId, + required String albumId, + i0.Value marker_ = const i0.Value.absent(), + }) => i1.LocalAlbumAssetEntityCompanion.insert( + assetId: assetId, + albumId: albumId, + marker_: marker_, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$LocalAlbumAssetEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false, albumId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$LocalAlbumAssetEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$LocalAlbumAssetEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + if (albumId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.albumId, + referencedTable: i1 + .$$LocalAlbumAssetEntityTableReferences + ._albumIdTable(db), + referencedColumn: i1 + .$$LocalAlbumAssetEntityTableReferences + ._albumIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$LocalAlbumAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$LocalAlbumAssetEntityTable, + i1.LocalAlbumAssetEntityData, + i1.$$LocalAlbumAssetEntityTableFilterComposer, + i1.$$LocalAlbumAssetEntityTableOrderingComposer, + i1.$$LocalAlbumAssetEntityTableAnnotationComposer, + $$LocalAlbumAssetEntityTableCreateCompanionBuilder, + $$LocalAlbumAssetEntityTableUpdateCompanionBuilder, + (i1.LocalAlbumAssetEntityData, i1.$$LocalAlbumAssetEntityTableReferences), + i1.LocalAlbumAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool albumId}) + >; +i0.Index get idxLocalAlbumAssetAlbumAsset => i0.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', +); + +class $LocalAlbumAssetEntityTable extends i2.LocalAlbumAssetEntity + with + i0.TableInfo< + $LocalAlbumAssetEntityTable, + i1.LocalAlbumAssetEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $LocalAlbumAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta( + 'albumId', + ); + @override + late final i0.GeneratedColumn albumId = i0.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _marker_Meta = const i0.VerificationMeta( + 'marker_', + ); + @override + late final i0.GeneratedColumn marker_ = i0.GeneratedColumn( + 'marker', + aliasedName, + true, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('album_id')) { + context.handle( + _albumIdMeta, + albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta), + ); + } else if (isInserting) { + context.missing(_albumIdMeta); + } + if (data.containsKey('marker')) { + context.handle( + _marker_Meta, + marker_.isAcceptableOrUnknown(data['marker']!, _marker_Meta), + ); + } + return context; + } + + @override + Set get $primaryKey => {assetId, albumId}; + @override + i1.LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + $LocalAlbumAssetEntityTable createAlias(String alias) { + return $LocalAlbumAssetEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + map['album_id'] = i0.Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = i0.Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + i1.LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + i0.Value marker_ = const i0.Value.absent(), + }) => i1.LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + i1.LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value albumId; + final i0.Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.albumId = const i0.Value.absent(), + this.marker_ = const i0.Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const i0.Value.absent(), + }) : assetId = i0.Value(assetId), + albumId = i0.Value(albumId); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? albumId, + i0.Expression? marker_, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + i1.LocalAlbumAssetEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? albumId, + i0.Value? marker_, + }) { + return i1.LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = i0.Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = i0.Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.dart b/mobile/lib/infrastructure/entities/local_asset.entity.dart index a19c1aa540..e1cb5f5597 100644 --- a/mobile/lib/infrastructure/entities/local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/local_asset.entity.dart @@ -6,7 +6,6 @@ import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)') -@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)') class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { const LocalAssetEntity(); @@ -40,7 +39,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData { type: type, createdAt: createdAt, updatedAt: updatedAt, - durationMs: durationMs, + durationInSeconds: durationInSeconds, isFavorite: isFavorite, height: height, width: width, diff --git a/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart new file mode 100644 index 0000000000..92ac3d2e35 --- /dev/null +++ b/mobile/lib/infrastructure/entities/local_asset.entity.drift.dart @@ -0,0 +1,1355 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$LocalAssetEntityTableCreateCompanionBuilder = + i1.LocalAssetEntityCompanion Function({ + required String name, + required i2.AssetType type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + required String id, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + i0.Value iCloudId, + i0.Value adjustmentTime, + i0.Value latitude, + i0.Value longitude, + i0.Value playbackStyle, + }); +typedef $$LocalAssetEntityTableUpdateCompanionBuilder = + i1.LocalAssetEntityCompanion Function({ + i0.Value name, + i0.Value type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + i0.Value id, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + i0.Value iCloudId, + i0.Value adjustmentTime, + i0.Value latitude, + i0.Value longitude, + i0.Value playbackStyle, + }); + +class $$LocalAssetEntityTableFilterComposer + extends i0.Composer { + $$LocalAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters get type => + $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get iCloudId => $composableBuilder( + column: $table.iCloudId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters< + i2.AssetPlaybackStyle, + i2.AssetPlaybackStyle, + int + > + get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); +} + +class $$LocalAssetEntityTableOrderingComposer + extends i0.Composer { + $$LocalAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get iCloudId => $composableBuilder( + column: $table.iCloudId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$LocalAssetEntityTableAnnotationComposer + extends i0.Composer { + $$LocalAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get width => + $composableBuilder(column: $table.width, builder: (column) => column); + + i0.GeneratedColumn get height => + $composableBuilder(column: $table.height, builder: (column) => column); + + i0.GeneratedColumn get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => column, + ); + + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); + + i0.GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => column, + ); + + i0.GeneratedColumn get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => column, + ); + + i0.GeneratedColumn get iCloudId => + $composableBuilder(column: $table.iCloudId, builder: (column) => column); + + i0.GeneratedColumn get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => column, + ); + + i0.GeneratedColumn get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => column); + + i0.GeneratedColumn get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter + get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => column, + ); +} + +class $$LocalAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$LocalAssetEntityTable, + i1.LocalAssetEntityData, + i1.$$LocalAssetEntityTableFilterComposer, + i1.$$LocalAssetEntityTableOrderingComposer, + i1.$$LocalAssetEntityTableAnnotationComposer, + $$LocalAssetEntityTableCreateCompanionBuilder, + $$LocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.LocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LocalAssetEntityTable, + i1.LocalAssetEntityData + >, + ), + i1.LocalAssetEntityData, + i0.PrefetchHooks Function() + > { + $$LocalAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$LocalAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$LocalAssetEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$LocalAssetEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$LocalAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value name = const i0.Value.absent(), + i0.Value type = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + i0.Value id = const i0.Value.absent(), + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value iCloudId = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i0.Value playbackStyle = + const i0.Value.absent(), + }) => i1.LocalAssetEntityCompanion( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + iCloudId: iCloudId, + adjustmentTime: adjustmentTime, + latitude: latitude, + longitude: longitude, + playbackStyle: playbackStyle, + ), + createCompanionCallback: + ({ + required String name, + required i2.AssetType type, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + required String id, + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value iCloudId = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i0.Value playbackStyle = + const i0.Value.absent(), + }) => i1.LocalAssetEntityCompanion.insert( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + iCloudId: iCloudId, + adjustmentTime: adjustmentTime, + latitude: latitude, + longitude: longitude, + playbackStyle: playbackStyle, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$LocalAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$LocalAssetEntityTable, + i1.LocalAssetEntityData, + i1.$$LocalAssetEntityTableFilterComposer, + i1.$$LocalAssetEntityTableOrderingComposer, + i1.$$LocalAssetEntityTableAnnotationComposer, + $$LocalAssetEntityTableCreateCompanionBuilder, + $$LocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.LocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LocalAssetEntityTable, + i1.LocalAssetEntityData + >, + ), + i1.LocalAssetEntityData, + i0.PrefetchHooks Function() + >; +i0.Index get idxLocalAssetChecksum => i0.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', +); + +class $LocalAssetEntityTable extends i3.LocalAssetEntity + with i0.TableInfo<$LocalAssetEntityTable, i1.LocalAssetEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $LocalAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final i0.GeneratedColumnWithTypeConverter type = + i0.GeneratedColumn( + 'type', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter(i1.$LocalAssetEntityTable.$convertertype); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta( + 'width', + ); + @override + late final i0.GeneratedColumn width = i0.GeneratedColumn( + 'width', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta( + 'height', + ); + @override + late final i0.GeneratedColumn height = i0.GeneratedColumn( + 'height', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _durationInSecondsMeta = + const i0.VerificationMeta('durationInSeconds'); + @override + late final i0.GeneratedColumn durationInSeconds = + i0.GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta( + 'checksum', + ); + @override + late final i0.GeneratedColumn checksum = i0.GeneratedColumn( + 'checksum', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta( + 'isFavorite', + ); + @override + late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta( + 'orientation', + ); + @override + late final i0.GeneratedColumn orientation = i0.GeneratedColumn( + 'orientation', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ); + static const i0.VerificationMeta _iCloudIdMeta = const i0.VerificationMeta( + 'iCloudId', + ); + @override + late final i0.GeneratedColumn iCloudId = i0.GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _adjustmentTimeMeta = + const i0.VerificationMeta('adjustmentTime'); + @override + late final i0.GeneratedColumn adjustmentTime = + i0.GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _latitudeMeta = const i0.VerificationMeta( + 'latitude', + ); + @override + late final i0.GeneratedColumn latitude = i0.GeneratedColumn( + 'latitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _longitudeMeta = const i0.VerificationMeta( + 'longitude', + ); + @override + late final i0.GeneratedColumn longitude = i0.GeneratedColumn( + 'longitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + playbackStyle = + i0.GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ).withConverter( + i1.$LocalAssetEntityTable.$converterplaybackStyle, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('width')) { + context.handle( + _widthMeta, + width.isAcceptableOrUnknown(data['width']!, _widthMeta), + ); + } + if (data.containsKey('height')) { + context.handle( + _heightMeta, + height.isAcceptableOrUnknown(data['height']!, _heightMeta), + ); + } + if (data.containsKey('duration_in_seconds')) { + context.handle( + _durationInSecondsMeta, + durationInSeconds.isAcceptableOrUnknown( + data['duration_in_seconds']!, + _durationInSecondsMeta, + ), + ); + } + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta), + ); + } + if (data.containsKey('orientation')) { + context.handle( + _orientationMeta, + orientation.isAcceptableOrUnknown( + data['orientation']!, + _orientationMeta, + ), + ); + } + if (data.containsKey('i_cloud_id')) { + context.handle( + _iCloudIdMeta, + iCloudId.isAcceptableOrUnknown(data['i_cloud_id']!, _iCloudIdMeta), + ); + } + if (data.containsKey('adjustment_time')) { + context.handle( + _adjustmentTimeMeta, + adjustmentTime.isAcceptableOrUnknown( + data['adjustment_time']!, + _adjustmentTimeMeta, + ), + ); + } + if (data.containsKey('latitude')) { + context.handle( + _latitudeMeta, + latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta), + ); + } + if (data.containsKey('longitude')) { + context.handle( + _longitudeMeta, + longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.LocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: i1.$LocalAssetEntityTable.$convertertype.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ), + ); + } + + @override + $LocalAssetEntityTable createAlias(String alias) { + return $LocalAssetEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $convertertype = + const i0.EnumIndexConverter(i2.AssetType.values); + static i0.JsonTypeConverter2 + $converterplaybackStyle = const i0.EnumIndexConverter( + i2.AssetPlaybackStyle.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String name; + final i2.AssetType type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + final i2.AssetPlaybackStyle playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = i0.Variable(name); + { + map['type'] = i0.Variable( + i1.$LocalAssetEntityTable.$convertertype.toSql(type), + ); + } + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = i0.Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = i0.Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds); + } + map['id'] = i0.Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = i0.Variable(checksum); + } + map['is_favorite'] = i0.Variable(isFavorite); + map['orientation'] = i0.Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = i0.Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = i0.Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = i0.Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = i0.Variable(longitude); + } + { + map['playback_style'] = i0.Variable( + i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql(playbackStyle), + ); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: i1.$LocalAssetEntityTable.$convertertype.fromJson( + serializer.fromJson(json['type']), + ), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: i1.$LocalAssetEntityTable.$converterplaybackStyle.fromJson( + serializer.fromJson(json['playbackStyle']), + ), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson( + i1.$LocalAssetEntityTable.$convertertype.toJson(type), + ), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson( + i1.$LocalAssetEntityTable.$converterplaybackStyle.toJson(playbackStyle), + ), + }; + } + + i1.LocalAssetEntityData copyWith({ + String? name, + i2.AssetType? type, + DateTime? createdAt, + DateTime? updatedAt, + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + String? id, + i0.Value checksum = const i0.Value.absent(), + bool? isFavorite, + int? orientation, + i0.Value iCloudId = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + i2.AssetPlaybackStyle? playbackStyle, + }) => i1.LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(i1.LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value name; + final i0.Value type; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value width; + final i0.Value height; + final i0.Value durationInSeconds; + final i0.Value id; + final i0.Value checksum; + final i0.Value isFavorite; + final i0.Value orientation; + final i0.Value iCloudId; + final i0.Value adjustmentTime; + final i0.Value latitude; + final i0.Value longitude; + final i0.Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const i0.Value.absent(), + this.type = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + this.id = const i0.Value.absent(), + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + this.iCloudId = const i0.Value.absent(), + this.adjustmentTime = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + this.playbackStyle = const i0.Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required i2.AssetType type, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + required String id, + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + this.iCloudId = const i0.Value.absent(), + this.adjustmentTime = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + this.playbackStyle = const i0.Value.absent(), + }) : name = i0.Value(name), + type = i0.Value(type), + id = i0.Value(id); + static i0.Insertable custom({ + i0.Expression? name, + i0.Expression? type, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? width, + i0.Expression? height, + i0.Expression? durationInSeconds, + i0.Expression? id, + i0.Expression? checksum, + i0.Expression? isFavorite, + i0.Expression? orientation, + i0.Expression? iCloudId, + i0.Expression? adjustmentTime, + i0.Expression? latitude, + i0.Expression? longitude, + i0.Expression? playbackStyle, + }) { + return i0.RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + i1.LocalAssetEntityCompanion copyWith({ + i0.Value? name, + i0.Value? type, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? width, + i0.Value? height, + i0.Value? durationInSeconds, + i0.Value? id, + i0.Value? checksum, + i0.Value? isFavorite, + i0.Value? orientation, + i0.Value? iCloudId, + i0.Value? adjustmentTime, + i0.Value? latitude, + i0.Value? longitude, + i0.Value? playbackStyle, + }) { + return i1.LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (type.present) { + map['type'] = i0.Variable( + i1.$LocalAssetEntityTable.$convertertype.toSql(type.value), + ); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (width.present) { + map['width'] = i0.Variable(width.value); + } + if (height.present) { + map['height'] = i0.Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = i0.Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = i0.Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = i0.Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = i0.Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = i0.Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = i0.Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = i0.Variable( + i1.$LocalAssetEntityTable.$converterplaybackStyle.toSql( + playbackStyle.value, + ), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +i0.Index get idxLocalAssetCloudId => i0.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', +); diff --git a/mobile/lib/infrastructure/entities/log.entity.dart b/mobile/lib/infrastructure/entities/log.entity.dart index 264bd3331f..e578459827 100644 --- a/mobile/lib/infrastructure/entities/log.entity.dart +++ b/mobile/lib/infrastructure/entities/log.entity.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; -import 'package:immich_mobile/domain/models/log.model.dart' as domain; import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart'; +import 'package:immich_mobile/domain/models/log.model.dart' as domain; class LogMessageEntity extends Table { const LogMessageEntity(); diff --git a/mobile/lib/infrastructure/entities/log.entity.drift.dart b/mobile/lib/infrastructure/entities/log.entity.drift.dart new file mode 100644 index 0000000000..d04cd5b7a2 --- /dev/null +++ b/mobile/lib/infrastructure/entities/log.entity.drift.dart @@ -0,0 +1,697 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/log.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/log.entity.dart' as i3; + +typedef $$LogMessageEntityTableCreateCompanionBuilder = + i1.LogMessageEntityCompanion Function({ + i0.Value id, + required String message, + i0.Value details, + required i2.LogLevel level, + required DateTime createdAt, + i0.Value logger, + i0.Value stack, + }); +typedef $$LogMessageEntityTableUpdateCompanionBuilder = + i1.LogMessageEntityCompanion Function({ + i0.Value id, + i0.Value message, + i0.Value details, + i0.Value level, + i0.Value createdAt, + i0.Value logger, + i0.Value stack, + }); + +class $$LogMessageEntityTableFilterComposer + extends i0.Composer { + $$LogMessageEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get message => $composableBuilder( + column: $table.message, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get details => $composableBuilder( + column: $table.details, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters get level => + $composableBuilder( + column: $table.level, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get logger => $composableBuilder( + column: $table.logger, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get stack => $composableBuilder( + column: $table.stack, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$LogMessageEntityTableOrderingComposer + extends i0.Composer { + $$LogMessageEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get message => $composableBuilder( + column: $table.message, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get details => $composableBuilder( + column: $table.details, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get level => $composableBuilder( + column: $table.level, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get logger => $composableBuilder( + column: $table.logger, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get stack => $composableBuilder( + column: $table.stack, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$LogMessageEntityTableAnnotationComposer + extends i0.Composer { + $$LogMessageEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get message => + $composableBuilder(column: $table.message, builder: (column) => column); + + i0.GeneratedColumn get details => + $composableBuilder(column: $table.details, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get level => + $composableBuilder(column: $table.level, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get logger => + $composableBuilder(column: $table.logger, builder: (column) => column); + + i0.GeneratedColumn get stack => + $composableBuilder(column: $table.stack, builder: (column) => column); +} + +class $$LogMessageEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$LogMessageEntityTable, + i1.LogMessageEntityData, + i1.$$LogMessageEntityTableFilterComposer, + i1.$$LogMessageEntityTableOrderingComposer, + i1.$$LogMessageEntityTableAnnotationComposer, + $$LogMessageEntityTableCreateCompanionBuilder, + $$LogMessageEntityTableUpdateCompanionBuilder, + ( + i1.LogMessageEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LogMessageEntityTable, + i1.LogMessageEntityData + >, + ), + i1.LogMessageEntityData, + i0.PrefetchHooks Function() + > { + $$LogMessageEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$LogMessageEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$LogMessageEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$LogMessageEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$LogMessageEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value message = const i0.Value.absent(), + i0.Value details = const i0.Value.absent(), + i0.Value level = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value logger = const i0.Value.absent(), + i0.Value stack = const i0.Value.absent(), + }) => i1.LogMessageEntityCompanion( + id: id, + message: message, + details: details, + level: level, + createdAt: createdAt, + logger: logger, + stack: stack, + ), + createCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + required String message, + i0.Value details = const i0.Value.absent(), + required i2.LogLevel level, + required DateTime createdAt, + i0.Value logger = const i0.Value.absent(), + i0.Value stack = const i0.Value.absent(), + }) => i1.LogMessageEntityCompanion.insert( + id: id, + message: message, + details: details, + level: level, + createdAt: createdAt, + logger: logger, + stack: stack, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$LogMessageEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$LogMessageEntityTable, + i1.LogMessageEntityData, + i1.$$LogMessageEntityTableFilterComposer, + i1.$$LogMessageEntityTableOrderingComposer, + i1.$$LogMessageEntityTableAnnotationComposer, + $$LogMessageEntityTableCreateCompanionBuilder, + $$LogMessageEntityTableUpdateCompanionBuilder, + ( + i1.LogMessageEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$LogMessageEntityTable, + i1.LogMessageEntityData + >, + ), + i1.LogMessageEntityData, + i0.PrefetchHooks Function() + >; + +class $LogMessageEntityTable extends i3.LogMessageEntity + with i0.TableInfo<$LogMessageEntityTable, i1.LogMessageEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $LogMessageEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + static const i0.VerificationMeta _messageMeta = const i0.VerificationMeta( + 'message', + ); + @override + late final i0.GeneratedColumn message = i0.GeneratedColumn( + 'message', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _detailsMeta = const i0.VerificationMeta( + 'details', + ); + @override + late final i0.GeneratedColumn details = i0.GeneratedColumn( + 'details', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final i0.GeneratedColumnWithTypeConverter level = + i0.GeneratedColumn( + 'level', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter(i1.$LogMessageEntityTable.$converterlevel); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _loggerMeta = const i0.VerificationMeta( + 'logger', + ); + @override + late final i0.GeneratedColumn logger = i0.GeneratedColumn( + 'logger', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _stackMeta = const i0.VerificationMeta( + 'stack', + ); + @override + late final i0.GeneratedColumn stack = i0.GeneratedColumn( + 'stack', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + message, + details, + level, + createdAt, + logger, + stack, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'logger_messages'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('message')) { + context.handle( + _messageMeta, + message.isAcceptableOrUnknown(data['message']!, _messageMeta), + ); + } else if (isInserting) { + context.missing(_messageMeta); + } + if (data.containsKey('details')) { + context.handle( + _detailsMeta, + details.isAcceptableOrUnknown(data['details']!, _detailsMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + if (data.containsKey('logger')) { + context.handle( + _loggerMeta, + logger.isAcceptableOrUnknown(data['logger']!, _loggerMeta), + ); + } + if (data.containsKey('stack')) { + context.handle( + _stackMeta, + stack.isAcceptableOrUnknown(data['stack']!, _stackMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.LogMessageEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.LogMessageEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + message: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}message'], + )!, + details: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}details'], + ), + level: i1.$LogMessageEntityTable.$converterlevel.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}level'], + )!, + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + logger: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}logger'], + ), + stack: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}stack'], + ), + ); + } + + @override + $LogMessageEntityTable createAlias(String alias) { + return $LogMessageEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converterlevel = + const i0.EnumIndexConverter(i2.LogLevel.values); +} + +class LogMessageEntityData extends i0.DataClass + implements i0.Insertable { + final int id; + final String message; + final String? details; + final i2.LogLevel level; + final DateTime createdAt; + final String? logger; + final String? stack; + const LogMessageEntityData({ + required this.id, + required this.message, + this.details, + required this.level, + required this.createdAt, + this.logger, + this.stack, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['message'] = i0.Variable(message); + if (!nullToAbsent || details != null) { + map['details'] = i0.Variable(details); + } + { + map['level'] = i0.Variable( + i1.$LogMessageEntityTable.$converterlevel.toSql(level), + ); + } + map['created_at'] = i0.Variable(createdAt); + if (!nullToAbsent || logger != null) { + map['logger'] = i0.Variable(logger); + } + if (!nullToAbsent || stack != null) { + map['stack'] = i0.Variable(stack); + } + return map; + } + + factory LogMessageEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return LogMessageEntityData( + id: serializer.fromJson(json['id']), + message: serializer.fromJson(json['message']), + details: serializer.fromJson(json['details']), + level: i1.$LogMessageEntityTable.$converterlevel.fromJson( + serializer.fromJson(json['level']), + ), + createdAt: serializer.fromJson(json['createdAt']), + logger: serializer.fromJson(json['logger']), + stack: serializer.fromJson(json['stack']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'message': serializer.toJson(message), + 'details': serializer.toJson(details), + 'level': serializer.toJson( + i1.$LogMessageEntityTable.$converterlevel.toJson(level), + ), + 'createdAt': serializer.toJson(createdAt), + 'logger': serializer.toJson(logger), + 'stack': serializer.toJson(stack), + }; + } + + i1.LogMessageEntityData copyWith({ + int? id, + String? message, + i0.Value details = const i0.Value.absent(), + i2.LogLevel? level, + DateTime? createdAt, + i0.Value logger = const i0.Value.absent(), + i0.Value stack = const i0.Value.absent(), + }) => i1.LogMessageEntityData( + id: id ?? this.id, + message: message ?? this.message, + details: details.present ? details.value : this.details, + level: level ?? this.level, + createdAt: createdAt ?? this.createdAt, + logger: logger.present ? logger.value : this.logger, + stack: stack.present ? stack.value : this.stack, + ); + LogMessageEntityData copyWithCompanion(i1.LogMessageEntityCompanion data) { + return LogMessageEntityData( + id: data.id.present ? data.id.value : this.id, + message: data.message.present ? data.message.value : this.message, + details: data.details.present ? data.details.value : this.details, + level: data.level.present ? data.level.value : this.level, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + logger: data.logger.present ? data.logger.value : this.logger, + stack: data.stack.present ? data.stack.value : this.stack, + ); + } + + @override + String toString() { + return (StringBuffer('LogMessageEntityData(') + ..write('id: $id, ') + ..write('message: $message, ') + ..write('details: $details, ') + ..write('level: $level, ') + ..write('createdAt: $createdAt, ') + ..write('logger: $logger, ') + ..write('stack: $stack') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, message, details, level, createdAt, logger, stack); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.LogMessageEntityData && + other.id == this.id && + other.message == this.message && + other.details == this.details && + other.level == this.level && + other.createdAt == this.createdAt && + other.logger == this.logger && + other.stack == this.stack); +} + +class LogMessageEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value message; + final i0.Value details; + final i0.Value level; + final i0.Value createdAt; + final i0.Value logger; + final i0.Value stack; + const LogMessageEntityCompanion({ + this.id = const i0.Value.absent(), + this.message = const i0.Value.absent(), + this.details = const i0.Value.absent(), + this.level = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.logger = const i0.Value.absent(), + this.stack = const i0.Value.absent(), + }); + LogMessageEntityCompanion.insert({ + this.id = const i0.Value.absent(), + required String message, + this.details = const i0.Value.absent(), + required i2.LogLevel level, + required DateTime createdAt, + this.logger = const i0.Value.absent(), + this.stack = const i0.Value.absent(), + }) : message = i0.Value(message), + level = i0.Value(level), + createdAt = i0.Value(createdAt); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? message, + i0.Expression? details, + i0.Expression? level, + i0.Expression? createdAt, + i0.Expression? logger, + i0.Expression? stack, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (message != null) 'message': message, + if (details != null) 'details': details, + if (level != null) 'level': level, + if (createdAt != null) 'created_at': createdAt, + if (logger != null) 'logger': logger, + if (stack != null) 'stack': stack, + }); + } + + i1.LogMessageEntityCompanion copyWith({ + i0.Value? id, + i0.Value? message, + i0.Value? details, + i0.Value? level, + i0.Value? createdAt, + i0.Value? logger, + i0.Value? stack, + }) { + return i1.LogMessageEntityCompanion( + id: id ?? this.id, + message: message ?? this.message, + details: details ?? this.details, + level: level ?? this.level, + createdAt: createdAt ?? this.createdAt, + logger: logger ?? this.logger, + stack: stack ?? this.stack, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (message.present) { + map['message'] = i0.Variable(message.value); + } + if (details.present) { + map['details'] = i0.Variable(details.value); + } + if (level.present) { + map['level'] = i0.Variable( + i1.$LogMessageEntityTable.$converterlevel.toSql(level.value), + ); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (logger.present) { + map['logger'] = i0.Variable(logger.value); + } + if (stack.present) { + map['stack'] = i0.Variable(stack.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LogMessageEntityCompanion(') + ..write('id: $id, ') + ..write('message: $message, ') + ..write('details: $details, ') + ..write('level: $level, ') + ..write('createdAt: $createdAt, ') + ..write('logger: $logger, ') + ..write('stack: $stack') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/memory.entity.drift.dart b/mobile/lib/infrastructure/entities/memory.entity.drift.dart new file mode 100644 index 0000000000..f5f18695a5 --- /dev/null +++ b/mobile/lib/infrastructure/entities/memory.entity.drift.dart @@ -0,0 +1,1179 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/memory.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/memory.entity.dart' 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 i5; +import 'package:drift/internal/modular.dart' as i6; + +typedef $$MemoryEntityTableCreateCompanionBuilder = + i1.MemoryEntityCompanion Function({ + required String id, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value deletedAt, + required String ownerId, + required i2.MemoryTypeEnum type, + required String data, + i0.Value isSaved, + required DateTime memoryAt, + i0.Value seenAt, + i0.Value showAt, + i0.Value hideAt, + }); +typedef $$MemoryEntityTableUpdateCompanionBuilder = + i1.MemoryEntityCompanion Function({ + i0.Value id, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value deletedAt, + i0.Value ownerId, + i0.Value type, + i0.Value data, + i0.Value isSaved, + i0.Value memoryAt, + i0.Value seenAt, + i0.Value showAt, + i0.Value hideAt, + }); + +final class $$MemoryEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$MemoryEntityTable, + i1.MemoryEntityData + > { + $$MemoryEntityTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => + i6.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer( + db, + ).resultSet('memory_entity').ownerId, + i6.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i5.$$UserEntityTableProcessedTableManager get ownerId { + final $_column = $_itemColumn('owner_id')!; + + final manager = i5 + .$$UserEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$MemoryEntityTableFilterComposer + extends i0.Composer { + $$MemoryEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get data => $composableBuilder( + column: $table.data, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isSaved => $composableBuilder( + column: $table.isSaved, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get memoryAt => $composableBuilder( + column: $table.memoryAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get seenAt => $composableBuilder( + column: $table.seenAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get showAt => $composableBuilder( + column: $table.showAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get hideAt => $composableBuilder( + column: $table.hideAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i5.$$UserEntityTableFilterComposer get ownerId { + final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryEntityTableOrderingComposer + extends i0.Composer { + $$MemoryEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get data => $composableBuilder( + column: $table.data, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isSaved => $composableBuilder( + column: $table.isSaved, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get memoryAt => $composableBuilder( + column: $table.memoryAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get seenAt => $composableBuilder( + column: $table.seenAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get showAt => $composableBuilder( + column: $table.showAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get hideAt => $composableBuilder( + column: $table.hideAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$UserEntityTableOrderingComposer get ownerId { + final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryEntityTableAnnotationComposer + extends i0.Composer { + $$MemoryEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + i0.GeneratedColumn get data => + $composableBuilder(column: $table.data, builder: (column) => column); + + i0.GeneratedColumn get isSaved => + $composableBuilder(column: $table.isSaved, builder: (column) => column); + + i0.GeneratedColumn get memoryAt => + $composableBuilder(column: $table.memoryAt, builder: (column) => column); + + i0.GeneratedColumn get seenAt => + $composableBuilder(column: $table.seenAt, builder: (column) => column); + + i0.GeneratedColumn get showAt => + $composableBuilder(column: $table.showAt, builder: (column) => column); + + i0.GeneratedColumn get hideAt => + $composableBuilder(column: $table.hideAt, builder: (column) => column); + + i5.$$UserEntityTableAnnotationComposer get ownerId { + final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$MemoryEntityTable, + i1.MemoryEntityData, + i1.$$MemoryEntityTableFilterComposer, + i1.$$MemoryEntityTableOrderingComposer, + i1.$$MemoryEntityTableAnnotationComposer, + $$MemoryEntityTableCreateCompanionBuilder, + $$MemoryEntityTableUpdateCompanionBuilder, + (i1.MemoryEntityData, i1.$$MemoryEntityTableReferences), + i1.MemoryEntityData, + i0.PrefetchHooks Function({bool ownerId}) + > { + $$MemoryEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$MemoryEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$MemoryEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$MemoryEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$MemoryEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + i0.Value ownerId = const i0.Value.absent(), + i0.Value type = const i0.Value.absent(), + i0.Value data = const i0.Value.absent(), + i0.Value isSaved = const i0.Value.absent(), + i0.Value memoryAt = const i0.Value.absent(), + i0.Value seenAt = const i0.Value.absent(), + i0.Value showAt = const i0.Value.absent(), + i0.Value hideAt = const i0.Value.absent(), + }) => i1.MemoryEntityCompanion( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + ownerId: ownerId, + type: type, + data: data, + isSaved: isSaved, + memoryAt: memoryAt, + seenAt: seenAt, + showAt: showAt, + hideAt: hideAt, + ), + createCompanionCallback: + ({ + required String id, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + required String ownerId, + required i2.MemoryTypeEnum type, + required String data, + i0.Value isSaved = const i0.Value.absent(), + required DateTime memoryAt, + i0.Value seenAt = const i0.Value.absent(), + i0.Value showAt = const i0.Value.absent(), + i0.Value hideAt = const i0.Value.absent(), + }) => i1.MemoryEntityCompanion.insert( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + deletedAt: deletedAt, + ownerId: ownerId, + type: type, + data: data, + isSaved: isSaved, + memoryAt: memoryAt, + seenAt: seenAt, + showAt: showAt, + hideAt: hideAt, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$MemoryEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({ownerId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (ownerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.ownerId, + referencedTable: i1 + .$$MemoryEntityTableReferences + ._ownerIdTable(db), + referencedColumn: i1 + .$$MemoryEntityTableReferences + ._ownerIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$MemoryEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$MemoryEntityTable, + i1.MemoryEntityData, + i1.$$MemoryEntityTableFilterComposer, + i1.$$MemoryEntityTableOrderingComposer, + i1.$$MemoryEntityTableAnnotationComposer, + $$MemoryEntityTableCreateCompanionBuilder, + $$MemoryEntityTableUpdateCompanionBuilder, + (i1.MemoryEntityData, i1.$$MemoryEntityTableReferences), + i1.MemoryEntityData, + i0.PrefetchHooks Function({bool ownerId}) + >; + +class $MemoryEntityTable extends i3.MemoryEntity + with i0.TableInfo<$MemoryEntityTable, i1.MemoryEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $MemoryEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _deletedAtMeta = const i0.VerificationMeta( + 'deletedAt', + ); + @override + late final i0.GeneratedColumn deletedAt = + i0.GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( + 'ownerId', + ); + @override + late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + @override + late final i0.GeneratedColumnWithTypeConverter type = + i0.GeneratedColumn( + 'type', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter(i1.$MemoryEntityTable.$convertertype); + static const i0.VerificationMeta _dataMeta = const i0.VerificationMeta( + 'data', + ); + @override + late final i0.GeneratedColumn data = i0.GeneratedColumn( + 'data', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _isSavedMeta = const i0.VerificationMeta( + 'isSaved', + ); + @override + late final i0.GeneratedColumn isSaved = i0.GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _memoryAtMeta = const i0.VerificationMeta( + 'memoryAt', + ); + @override + late final i0.GeneratedColumn memoryAt = + i0.GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _seenAtMeta = const i0.VerificationMeta( + 'seenAt', + ); + @override + late final i0.GeneratedColumn seenAt = i0.GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _showAtMeta = const i0.VerificationMeta( + 'showAt', + ); + @override + late final i0.GeneratedColumn showAt = i0.GeneratedColumn( + 'show_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _hideAtMeta = const i0.VerificationMeta( + 'hideAt', + ); + @override + late final i0.GeneratedColumn hideAt = i0.GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('deleted_at')) { + context.handle( + _deletedAtMeta, + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta), + ); + } + if (data.containsKey('owner_id')) { + context.handle( + _ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), + ); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('data')) { + context.handle( + _dataMeta, + this.data.isAcceptableOrUnknown(data['data']!, _dataMeta), + ); + } else if (isInserting) { + context.missing(_dataMeta); + } + if (data.containsKey('is_saved')) { + context.handle( + _isSavedMeta, + isSaved.isAcceptableOrUnknown(data['is_saved']!, _isSavedMeta), + ); + } + if (data.containsKey('memory_at')) { + context.handle( + _memoryAtMeta, + memoryAt.isAcceptableOrUnknown(data['memory_at']!, _memoryAtMeta), + ); + } else if (isInserting) { + context.missing(_memoryAtMeta); + } + if (data.containsKey('seen_at')) { + context.handle( + _seenAtMeta, + seenAt.isAcceptableOrUnknown(data['seen_at']!, _seenAtMeta), + ); + } + if (data.containsKey('show_at')) { + context.handle( + _showAtMeta, + showAt.isAcceptableOrUnknown(data['show_at']!, _showAtMeta), + ); + } + if (data.containsKey('hide_at')) { + context.handle( + _hideAtMeta, + hideAt.isAcceptableOrUnknown(data['hide_at']!, _hideAtMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.MemoryEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: i1.$MemoryEntityTable.$convertertype.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + ), + data: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + $MemoryEntityTable createAlias(String alias) { + return $MemoryEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $convertertype = + const i0.EnumIndexConverter(i2.MemoryTypeEnum.values); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final i2.MemoryTypeEnum type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = i0.Variable(deletedAt); + } + map['owner_id'] = i0.Variable(ownerId); + { + map['type'] = i0.Variable( + i1.$MemoryEntityTable.$convertertype.toSql(type), + ); + } + map['data'] = i0.Variable(data); + map['is_saved'] = i0.Variable(isSaved); + map['memory_at'] = i0.Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = i0.Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = i0.Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = i0.Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: i1.$MemoryEntityTable.$convertertype.fromJson( + serializer.fromJson(json['type']), + ), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson( + i1.$MemoryEntityTable.$convertertype.toJson(type), + ), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + i1.MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + i0.Value deletedAt = const i0.Value.absent(), + String? ownerId, + i2.MemoryTypeEnum? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + i0.Value seenAt = const i0.Value.absent(), + i0.Value showAt = const i0.Value.absent(), + i0.Value hideAt = const i0.Value.absent(), + }) => i1.MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(i1.MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value deletedAt; + final i0.Value ownerId; + final i0.Value type; + final i0.Value data; + final i0.Value isSaved; + final i0.Value memoryAt; + final i0.Value seenAt; + final i0.Value showAt; + final i0.Value hideAt; + const MemoryEntityCompanion({ + this.id = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + this.ownerId = const i0.Value.absent(), + this.type = const i0.Value.absent(), + this.data = const i0.Value.absent(), + this.isSaved = const i0.Value.absent(), + this.memoryAt = const i0.Value.absent(), + this.seenAt = const i0.Value.absent(), + this.showAt = const i0.Value.absent(), + this.hideAt = const i0.Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + required String ownerId, + required i2.MemoryTypeEnum type, + required String data, + this.isSaved = const i0.Value.absent(), + required DateTime memoryAt, + this.seenAt = const i0.Value.absent(), + this.showAt = const i0.Value.absent(), + this.hideAt = const i0.Value.absent(), + }) : id = i0.Value(id), + ownerId = i0.Value(ownerId), + type = i0.Value(type), + data = i0.Value(data), + memoryAt = i0.Value(memoryAt); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? deletedAt, + i0.Expression? ownerId, + i0.Expression? type, + i0.Expression? data, + i0.Expression? isSaved, + i0.Expression? memoryAt, + i0.Expression? seenAt, + i0.Expression? showAt, + i0.Expression? hideAt, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + i1.MemoryEntityCompanion copyWith({ + i0.Value? id, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? deletedAt, + i0.Value? ownerId, + i0.Value? type, + i0.Value? data, + i0.Value? isSaved, + i0.Value? memoryAt, + i0.Value? seenAt, + i0.Value? showAt, + i0.Value? hideAt, + }) { + return i1.MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = i0.Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = i0.Variable(ownerId.value); + } + if (type.present) { + map['type'] = i0.Variable( + i1.$MemoryEntityTable.$convertertype.toSql(type.value), + ); + } + if (data.present) { + map['data'] = i0.Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = i0.Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = i0.Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = i0.Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = i0.Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = i0.Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/memory_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/memory_asset.entity.drift.dart new file mode 100644 index 0000000000..eedeb85e5c --- /dev/null +++ b/mobile/lib/infrastructure/entities/memory_asset.entity.drift.dart @@ -0,0 +1,641 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart' + as i2; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart' + as i5; + +typedef $$MemoryAssetEntityTableCreateCompanionBuilder = + i1.MemoryAssetEntityCompanion Function({ + required String assetId, + required String memoryId, + }); +typedef $$MemoryAssetEntityTableUpdateCompanionBuilder = + i1.MemoryAssetEntityCompanion Function({ + i0.Value assetId, + i0.Value memoryId, + }); + +final class $$MemoryAssetEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$MemoryAssetEntityTable, + i1.MemoryAssetEntityData + > { + $$MemoryAssetEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet('memory_asset_entity') + .assetId, + i4.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i3.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i3 + .$$RemoteAssetEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i5.$MemoryEntityTable _memoryIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('memory_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet('memory_asset_entity') + .memoryId, + i4.ReadDatabaseContainer( + db, + ).resultSet('memory_entity').id, + ), + ); + + i5.$$MemoryEntityTableProcessedTableManager get memoryId { + final $_column = $_itemColumn('memory_id')!; + + final manager = i5 + .$$MemoryEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('memory_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_memoryIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$MemoryAssetEntityTableFilterComposer + extends i0.Composer { + $$MemoryAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableFilterComposer get assetId { + final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$MemoryEntityTableFilterComposer get memoryId { + final i5.$$MemoryEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.memoryId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$MemoryEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryAssetEntityTableOrderingComposer + extends i0.Composer { + $$MemoryAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i3.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$MemoryEntityTableOrderingComposer get memoryId { + final i5.$$MemoryEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.memoryId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$MemoryEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryAssetEntityTableAnnotationComposer + extends i0.Composer { + $$MemoryAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i3.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$MemoryEntityTableAnnotationComposer get memoryId { + final i5.$$MemoryEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.memoryId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$MemoryEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('memory_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$MemoryAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$MemoryAssetEntityTable, + i1.MemoryAssetEntityData, + i1.$$MemoryAssetEntityTableFilterComposer, + i1.$$MemoryAssetEntityTableOrderingComposer, + i1.$$MemoryAssetEntityTableAnnotationComposer, + $$MemoryAssetEntityTableCreateCompanionBuilder, + $$MemoryAssetEntityTableUpdateCompanionBuilder, + (i1.MemoryAssetEntityData, i1.$$MemoryAssetEntityTableReferences), + i1.MemoryAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool memoryId}) + > { + $$MemoryAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$MemoryAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$MemoryAssetEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$MemoryAssetEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$MemoryAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value memoryId = const i0.Value.absent(), + }) => i1.MemoryAssetEntityCompanion( + assetId: assetId, + memoryId: memoryId, + ), + createCompanionCallback: + ({required String assetId, required String memoryId}) => + i1.MemoryAssetEntityCompanion.insert( + assetId: assetId, + memoryId: memoryId, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$MemoryAssetEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false, memoryId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$MemoryAssetEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$MemoryAssetEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + if (memoryId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.memoryId, + referencedTable: i1 + .$$MemoryAssetEntityTableReferences + ._memoryIdTable(db), + referencedColumn: i1 + .$$MemoryAssetEntityTableReferences + ._memoryIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$MemoryAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$MemoryAssetEntityTable, + i1.MemoryAssetEntityData, + i1.$$MemoryAssetEntityTableFilterComposer, + i1.$$MemoryAssetEntityTableOrderingComposer, + i1.$$MemoryAssetEntityTableAnnotationComposer, + $$MemoryAssetEntityTableCreateCompanionBuilder, + $$MemoryAssetEntityTableUpdateCompanionBuilder, + (i1.MemoryAssetEntityData, i1.$$MemoryAssetEntityTableReferences), + i1.MemoryAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool memoryId}) + >; + +class $MemoryAssetEntityTable extends i2.MemoryAssetEntity + with i0.TableInfo<$MemoryAssetEntityTable, i1.MemoryAssetEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $MemoryAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _memoryIdMeta = const i0.VerificationMeta( + 'memoryId', + ); + @override + late final i0.GeneratedColumn memoryId = i0.GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('memory_id')) { + context.handle( + _memoryIdMeta, + memoryId.isAcceptableOrUnknown(data['memory_id']!, _memoryIdMeta), + ); + } else if (isInserting) { + context.missing(_memoryIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {assetId, memoryId}; + @override + i1.MemoryAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + $MemoryAssetEntityTable createAlias(String alias) { + return $MemoryAssetEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + map['memory_id'] = i0.Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + i1.MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + i1.MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(i1.MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.memoryId = const i0.Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = i0.Value(assetId), + memoryId = i0.Value(memoryId); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? memoryId, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + i1.MemoryAssetEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? memoryId, + }) { + return i1.MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = i0.Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift b/mobile/lib/infrastructure/entities/merged_asset.drift index d0321ab1ef..73276d1756 100644 --- a/mobile/lib/infrastructure/entities/merged_asset.drift +++ b/mobile/lib/infrastructure/entities/merged_asset.drift @@ -4,7 +4,7 @@ import 'local_asset.entity.dart'; import 'local_album.entity.dart'; import 'local_album_asset.entity.dart'; -mergedAsset: +mergedAsset: SELECT rae.id as remote_id, (SELECT lae.id FROM local_asset_entity lae WHERE lae.checksum = rae.checksum LIMIT 1) as local_id, @@ -14,7 +14,7 @@ SELECT rae.updated_at, rae.width, rae.height, - rae.duration_ms, + rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, @@ -27,8 +27,7 @@ SELECT NULL as longitude, NULL as adjustmentTime, rae.is_edited, - 0 as playback_style, - rae.uploaded_at + 0 as playback_style FROM remote_asset_entity rae LEFT JOIN @@ -53,7 +52,7 @@ SELECT lae.updated_at, lae.width, lae.height, - lae.duration_ms, + lae.duration_in_seconds, lae.is_favorite, NULL as thumb_hash, lae.checksum, @@ -66,8 +65,7 @@ SELECT lae.longitude, lae.adjustment_time, 0 as is_edited, - lae.playback_style, - NULL as uploaded_at + lae.playback_style FROM local_asset_entity lae WHERE NOT EXISTS ( diff --git a/mobile/lib/infrastructure/entities/merged_asset.drift.dart b/mobile/lib/infrastructure/entities/merged_asset.drift.dart new file mode 100644 index 0000000000..c6004eb10d --- /dev/null +++ b/mobile/lib/infrastructure/entities/merged_asset.drift.dart @@ -0,0 +1,176 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift/internal/modular.dart' as i1; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' + as i3; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i4; +import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' + as i5; +import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' + as i6; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' + as i7; + +class MergedAssetDrift extends i1.ModularAccessor { + MergedAssetDrift(i0.GeneratedDatabase db) : super(db); + i0.Selectable mergedAsset({ + required List userIds, + required MergedAsset$limit limit, + }) { + var $arrayStartIndex = 1; + final expandeduserIds = $expandVar($arrayStartIndex, userIds.length); + $arrayStartIndex += userIds.length; + final generatedlimit = $write( + limit(alias(this.localAssetEntity, 'lae')), + startIndex: $arrayStartIndex, + ); + $arrayStartIndex += generatedlimit.amountOfVariables; + return customSelect( + 'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited, 0 AS playback_style FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited, lae.playback_style FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}', + variables: [ + for (var $ in userIds) i0.Variable($), + ...generatedlimit.introducedVariables, + ], + readsFrom: { + remoteAssetEntity, + localAssetEntity, + stackEntity, + localAlbumAssetEntity, + localAlbumEntity, + ...generatedlimit.watchedTables, + }, + ).map( + (i0.QueryRow row) => MergedAssetResult( + remoteId: row.readNullable('remote_id'), + localId: row.readNullable('local_id'), + name: row.read('name'), + type: i4.$RemoteAssetEntityTable.$convertertype.fromSql( + row.read('type'), + ), + createdAt: row.read('created_at'), + updatedAt: row.read('updated_at'), + width: row.readNullable('width'), + height: row.readNullable('height'), + durationInSeconds: row.readNullable('duration_in_seconds'), + isFavorite: row.read('is_favorite'), + thumbHash: row.readNullable('thumb_hash'), + checksum: row.readNullable('checksum'), + ownerId: row.readNullable('owner_id'), + livePhotoVideoId: row.readNullable('live_photo_video_id'), + orientation: row.read('orientation'), + stackId: row.readNullable('stack_id'), + iCloudId: row.readNullable('i_cloud_id'), + latitude: row.readNullable('latitude'), + longitude: row.readNullable('longitude'), + adjustmentTime: row.readNullable('adjustmentTime'), + isEdited: row.read('is_edited'), + playbackStyle: row.read('playback_style'), + ), + ); + } + + i0.Selectable mergedBucket({ + required int groupBy, + required List userIds, + }) { + var $arrayStartIndex = 2; + final expandeduserIds = $expandVar($arrayStartIndex, userIds.length); + $arrayStartIndex += userIds.length; + return customSelect( + 'SELECT COUNT(*) AS asset_count, bucket_date FROM (SELECT CASE WHEN ?1 = 0 THEN COALESCE(STRFTIME(\'%Y-%m-%d\', rae.local_date_time), STRFTIME(\'%Y-%m-%d\', rae.created_at, \'localtime\')) WHEN ?1 = 1 THEN COALESCE(STRFTIME(\'%Y-%m\', rae.local_date_time), STRFTIME(\'%Y-%m\', rae.created_at, \'localtime\')) END AS bucket_date FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT CASE WHEN ?1 = 0 THEN STRFTIME(\'%Y-%m-%d\', lae.created_at, \'localtime\') WHEN ?1 = 1 THEN STRFTIME(\'%Y-%m\', lae.created_at, \'localtime\') END AS bucket_date FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2)) GROUP BY bucket_date ORDER BY bucket_date DESC', + variables: [ + i0.Variable(groupBy), + for (var $ in userIds) i0.Variable($), + ], + readsFrom: { + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumAssetEntity, + localAlbumEntity, + }, + ).map( + (i0.QueryRow row) => MergedBucketResult( + assetCount: row.read('asset_count'), + bucketDate: row.read('bucket_date'), + ), + ); + } + + i4.$RemoteAssetEntityTable get remoteAssetEntity => i1.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('remote_asset_entity'); + i5.$StackEntityTable get stackEntity => i1.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('stack_entity'); + i3.$LocalAssetEntityTable get localAssetEntity => i1.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('local_asset_entity'); + i6.$LocalAlbumAssetEntityTable get localAlbumAssetEntity => + i1.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('local_album_asset_entity'); + i7.$LocalAlbumEntityTable get localAlbumEntity => i1.ReadDatabaseContainer( + attachedDatabase, + ).resultSet('local_album_entity'); +} + +class MergedAssetResult { + final String? remoteId; + final String? localId; + final String name; + final i2.AssetType type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final bool isFavorite; + final String? thumbHash; + final String? checksum; + final String? ownerId; + final String? livePhotoVideoId; + final int orientation; + final String? stackId; + final String? iCloudId; + final double? latitude; + final double? longitude; + final DateTime? adjustmentTime; + final bool isEdited; + final int playbackStyle; + MergedAssetResult({ + this.remoteId, + this.localId, + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.isFavorite, + this.thumbHash, + this.checksum, + this.ownerId, + this.livePhotoVideoId, + required this.orientation, + this.stackId, + this.iCloudId, + this.latitude, + this.longitude, + this.adjustmentTime, + required this.isEdited, + required this.playbackStyle, + }); +} + +typedef MergedAsset$limit = i0.Limit Function(i3.$LocalAssetEntityTable lae); + +class MergedBucketResult { + final int assetCount; + final String bucketDate; + MergedBucketResult({required this.assetCount, required this.bucketDate}); +} diff --git a/mobile/lib/infrastructure/entities/partner.entity.drift.dart b/mobile/lib/infrastructure/entities/partner.entity.drift.dart new file mode 100644 index 0000000000..76a91f27bf --- /dev/null +++ b/mobile/lib/infrastructure/entities/partner.entity.drift.dart @@ -0,0 +1,725 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/partner.entity.dart' + as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; + +typedef $$PartnerEntityTableCreateCompanionBuilder = + i1.PartnerEntityCompanion Function({ + required String sharedById, + required String sharedWithId, + i0.Value inTimeline, + }); +typedef $$PartnerEntityTableUpdateCompanionBuilder = + i1.PartnerEntityCompanion Function({ + i0.Value sharedById, + i0.Value sharedWithId, + i0.Value inTimeline, + }); + +final class $$PartnerEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$PartnerEntityTable, + i1.PartnerEntityData + > { + $$PartnerEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i4.$UserEntityTable _sharedByIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer( + db, + ).resultSet('partner_entity').sharedById, + i5.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i4.$$UserEntityTableProcessedTableManager get sharedById { + final $_column = $_itemColumn('shared_by_id')!; + + final manager = i4 + .$$UserEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sharedByIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i4.$UserEntityTable _sharedWithIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet('partner_entity') + .sharedWithId, + i5.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i4.$$UserEntityTableProcessedTableManager get sharedWithId { + final $_column = $_itemColumn('shared_with_id')!; + + final manager = i4 + .$$UserEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_sharedWithIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$PartnerEntityTableFilterComposer + extends i0.Composer { + $$PartnerEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get inTimeline => $composableBuilder( + column: $table.inTimeline, + builder: (column) => i0.ColumnFilters(column), + ); + + i4.$$UserEntityTableFilterComposer get sharedById { + final i4.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedById, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i4.$$UserEntityTableFilterComposer get sharedWithId { + final i4.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedWithId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PartnerEntityTableOrderingComposer + extends i0.Composer { + $$PartnerEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get inTimeline => $composableBuilder( + column: $table.inTimeline, + builder: (column) => i0.ColumnOrderings(column), + ); + + i4.$$UserEntityTableOrderingComposer get sharedById { + final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedById, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i4.$$UserEntityTableOrderingComposer get sharedWithId { + final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedWithId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PartnerEntityTableAnnotationComposer + extends i0.Composer { + $$PartnerEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get inTimeline => $composableBuilder( + column: $table.inTimeline, + builder: (column) => column, + ); + + i4.$$UserEntityTableAnnotationComposer get sharedById { + final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedById, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i4.$$UserEntityTableAnnotationComposer get sharedWithId { + final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.sharedWithId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PartnerEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$PartnerEntityTable, + i1.PartnerEntityData, + i1.$$PartnerEntityTableFilterComposer, + i1.$$PartnerEntityTableOrderingComposer, + i1.$$PartnerEntityTableAnnotationComposer, + $$PartnerEntityTableCreateCompanionBuilder, + $$PartnerEntityTableUpdateCompanionBuilder, + (i1.PartnerEntityData, i1.$$PartnerEntityTableReferences), + i1.PartnerEntityData, + i0.PrefetchHooks Function({bool sharedById, bool sharedWithId}) + > { + $$PartnerEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$PartnerEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$PartnerEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$PartnerEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$PartnerEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value sharedById = const i0.Value.absent(), + i0.Value sharedWithId = const i0.Value.absent(), + i0.Value inTimeline = const i0.Value.absent(), + }) => i1.PartnerEntityCompanion( + sharedById: sharedById, + sharedWithId: sharedWithId, + inTimeline: inTimeline, + ), + createCompanionCallback: + ({ + required String sharedById, + required String sharedWithId, + i0.Value inTimeline = const i0.Value.absent(), + }) => i1.PartnerEntityCompanion.insert( + sharedById: sharedById, + sharedWithId: sharedWithId, + inTimeline: inTimeline, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$PartnerEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({sharedById = false, sharedWithId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (sharedById) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sharedById, + referencedTable: i1 + .$$PartnerEntityTableReferences + ._sharedByIdTable(db), + referencedColumn: i1 + .$$PartnerEntityTableReferences + ._sharedByIdTable(db) + .id, + ) + as T; + } + if (sharedWithId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.sharedWithId, + referencedTable: i1 + .$$PartnerEntityTableReferences + ._sharedWithIdTable(db), + referencedColumn: i1 + .$$PartnerEntityTableReferences + ._sharedWithIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$PartnerEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$PartnerEntityTable, + i1.PartnerEntityData, + i1.$$PartnerEntityTableFilterComposer, + i1.$$PartnerEntityTableOrderingComposer, + i1.$$PartnerEntityTableAnnotationComposer, + $$PartnerEntityTableCreateCompanionBuilder, + $$PartnerEntityTableUpdateCompanionBuilder, + (i1.PartnerEntityData, i1.$$PartnerEntityTableReferences), + i1.PartnerEntityData, + i0.PrefetchHooks Function({bool sharedById, bool sharedWithId}) + >; +i0.Index get idxPartnerSharedWithId => i0.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', +); + +class $PartnerEntityTable extends i2.PartnerEntity + with i0.TableInfo<$PartnerEntityTable, i1.PartnerEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $PartnerEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _sharedByIdMeta = const i0.VerificationMeta( + 'sharedById', + ); + @override + late final i0.GeneratedColumn sharedById = i0.GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + 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 sharedWithId = + i0.GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _inTimelineMeta = const i0.VerificationMeta( + 'inTimeline', + ); + @override + late final i0.GeneratedColumn inTimeline = i0.GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const i3.Constant(false), + ); + @override + List get $columns => [ + sharedById, + sharedWithId, + inTimeline, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('shared_by_id')) { + context.handle( + _sharedByIdMeta, + sharedById.isAcceptableOrUnknown( + data['shared_by_id']!, + _sharedByIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_sharedByIdMeta); + } + if (data.containsKey('shared_with_id')) { + context.handle( + _sharedWithIdMeta, + sharedWithId.isAcceptableOrUnknown( + data['shared_with_id']!, + _sharedWithIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_sharedWithIdMeta); + } + if (data.containsKey('in_timeline')) { + context.handle( + _inTimelineMeta, + inTimeline.isAcceptableOrUnknown(data['in_timeline']!, _inTimelineMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + i1.PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + $PartnerEntityTable createAlias(String alias) { + return $PartnerEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends i0.DataClass + implements i0.Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = i0.Variable(sharedById); + map['shared_with_id'] = i0.Variable(sharedWithId); + map['in_timeline'] = i0.Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + i1.PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => i1.PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(i1.PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends i0.UpdateCompanion { + final i0.Value sharedById; + final i0.Value sharedWithId; + final i0.Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const i0.Value.absent(), + this.sharedWithId = const i0.Value.absent(), + this.inTimeline = const i0.Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const i0.Value.absent(), + }) : sharedById = i0.Value(sharedById), + sharedWithId = i0.Value(sharedWithId); + static i0.Insertable custom({ + i0.Expression? sharedById, + i0.Expression? sharedWithId, + i0.Expression? inTimeline, + }) { + return i0.RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + i1.PartnerEntityCompanion copyWith({ + i0.Value? sharedById, + i0.Value? sharedWithId, + i0.Value? inTimeline, + }) { + return i1.PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = i0.Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = i0.Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = i0.Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/person.entity.drift.dart b/mobile/lib/infrastructure/entities/person.entity.drift.dart new file mode 100644 index 0000000000..02ea48c846 --- /dev/null +++ b/mobile/lib/infrastructure/entities/person.entity.drift.dart @@ -0,0 +1,1062 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/person.entity.dart' as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; + +typedef $$PersonEntityTableCreateCompanionBuilder = + i1.PersonEntityCompanion Function({ + required String id, + i0.Value createdAt, + i0.Value updatedAt, + required String ownerId, + required String name, + i0.Value faceAssetId, + required bool isFavorite, + required bool isHidden, + i0.Value color, + i0.Value birthDate, + }); +typedef $$PersonEntityTableUpdateCompanionBuilder = + i1.PersonEntityCompanion Function({ + i0.Value id, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value ownerId, + i0.Value name, + i0.Value faceAssetId, + i0.Value isFavorite, + i0.Value isHidden, + i0.Value color, + i0.Value birthDate, + }); + +final class $$PersonEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$PersonEntityTable, + i1.PersonEntityData + > { + $$PersonEntityTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer( + db, + ).resultSet('person_entity').ownerId, + i5.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i4.$$UserEntityTableProcessedTableManager get ownerId { + final $_column = $_itemColumn('owner_id')!; + + final manager = i4 + .$$UserEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$PersonEntityTableFilterComposer + extends i0.Composer { + $$PersonEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get faceAssetId => $composableBuilder( + column: $table.faceAssetId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isHidden => $composableBuilder( + column: $table.isHidden, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get color => $composableBuilder( + column: $table.color, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get birthDate => $composableBuilder( + column: $table.birthDate, + builder: (column) => i0.ColumnFilters(column), + ); + + i4.$$UserEntityTableFilterComposer get ownerId { + final i4.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PersonEntityTableOrderingComposer + extends i0.Composer { + $$PersonEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get faceAssetId => $composableBuilder( + column: $table.faceAssetId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isHidden => $composableBuilder( + column: $table.isHidden, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get color => $composableBuilder( + column: $table.color, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get birthDate => $composableBuilder( + column: $table.birthDate, + builder: (column) => i0.ColumnOrderings(column), + ); + + i4.$$UserEntityTableOrderingComposer get ownerId { + final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PersonEntityTableAnnotationComposer + extends i0.Composer { + $$PersonEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get faceAssetId => $composableBuilder( + column: $table.faceAssetId, + builder: (column) => column, + ); + + i0.GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => column, + ); + + i0.GeneratedColumn get isHidden => + $composableBuilder(column: $table.isHidden, builder: (column) => column); + + i0.GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + i0.GeneratedColumn get birthDate => + $composableBuilder(column: $table.birthDate, builder: (column) => column); + + i4.$$UserEntityTableAnnotationComposer get ownerId { + final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PersonEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$PersonEntityTable, + i1.PersonEntityData, + i1.$$PersonEntityTableFilterComposer, + i1.$$PersonEntityTableOrderingComposer, + i1.$$PersonEntityTableAnnotationComposer, + $$PersonEntityTableCreateCompanionBuilder, + $$PersonEntityTableUpdateCompanionBuilder, + (i1.PersonEntityData, i1.$$PersonEntityTableReferences), + i1.PersonEntityData, + i0.PrefetchHooks Function({bool ownerId}) + > { + $$PersonEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$PersonEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$PersonEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$PersonEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$PersonEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value ownerId = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + i0.Value faceAssetId = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value isHidden = const i0.Value.absent(), + i0.Value color = const i0.Value.absent(), + i0.Value birthDate = const i0.Value.absent(), + }) => i1.PersonEntityCompanion( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + name: name, + faceAssetId: faceAssetId, + isFavorite: isFavorite, + isHidden: isHidden, + color: color, + birthDate: birthDate, + ), + createCompanionCallback: + ({ + required String id, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + required String ownerId, + required String name, + i0.Value faceAssetId = const i0.Value.absent(), + required bool isFavorite, + required bool isHidden, + i0.Value color = const i0.Value.absent(), + i0.Value birthDate = const i0.Value.absent(), + }) => i1.PersonEntityCompanion.insert( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + name: name, + faceAssetId: faceAssetId, + isFavorite: isFavorite, + isHidden: isHidden, + color: color, + birthDate: birthDate, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$PersonEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({ownerId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (ownerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.ownerId, + referencedTable: i1 + .$$PersonEntityTableReferences + ._ownerIdTable(db), + referencedColumn: i1 + .$$PersonEntityTableReferences + ._ownerIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$PersonEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$PersonEntityTable, + i1.PersonEntityData, + i1.$$PersonEntityTableFilterComposer, + i1.$$PersonEntityTableOrderingComposer, + i1.$$PersonEntityTableAnnotationComposer, + $$PersonEntityTableCreateCompanionBuilder, + $$PersonEntityTableUpdateCompanionBuilder, + (i1.PersonEntityData, i1.$$PersonEntityTableReferences), + i1.PersonEntityData, + i0.PrefetchHooks Function({bool ownerId}) + >; +i0.Index get idxPersonOwnerId => i0.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', +); + +class $PersonEntityTable extends i2.PersonEntity + with i0.TableInfo<$PersonEntityTable, i1.PersonEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $PersonEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime, + ); + static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( + 'ownerId', + ); + @override + late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _faceAssetIdMeta = const i0.VerificationMeta( + 'faceAssetId', + ); + @override + late final i0.GeneratedColumn faceAssetId = + i0.GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta( + 'isFavorite', + ); + @override + late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + static const i0.VerificationMeta _isHiddenMeta = const i0.VerificationMeta( + 'isHidden', + ); + @override + late final i0.GeneratedColumn isHidden = i0.GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + static const i0.VerificationMeta _colorMeta = const i0.VerificationMeta( + 'color', + ); + @override + late final i0.GeneratedColumn color = i0.GeneratedColumn( + 'color', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _birthDateMeta = const i0.VerificationMeta( + 'birthDate', + ); + @override + late final i0.GeneratedColumn birthDate = + i0.GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('owner_id')) { + context.handle( + _ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), + ); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('face_asset_id')) { + context.handle( + _faceAssetIdMeta, + faceAssetId.isAcceptableOrUnknown( + data['face_asset_id']!, + _faceAssetIdMeta, + ), + ); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta), + ); + } else if (isInserting) { + context.missing(_isFavoriteMeta); + } + if (data.containsKey('is_hidden')) { + context.handle( + _isHiddenMeta, + isHidden.isAcceptableOrUnknown(data['is_hidden']!, _isHiddenMeta), + ); + } else if (isInserting) { + context.missing(_isHiddenMeta); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, + color.isAcceptableOrUnknown(data['color']!, _colorMeta), + ); + } + if (data.containsKey('birth_date')) { + context.handle( + _birthDateMeta, + birthDate.isAcceptableOrUnknown(data['birth_date']!, _birthDateMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.PersonEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + $PersonEntityTable createAlias(String alias) { + return $PersonEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + map['owner_id'] = i0.Variable(ownerId); + map['name'] = i0.Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = i0.Variable(faceAssetId); + } + map['is_favorite'] = i0.Variable(isFavorite); + map['is_hidden'] = i0.Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = i0.Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = i0.Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + i1.PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + i0.Value faceAssetId = const i0.Value.absent(), + bool? isFavorite, + bool? isHidden, + i0.Value color = const i0.Value.absent(), + i0.Value birthDate = const i0.Value.absent(), + }) => i1.PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(i1.PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value ownerId; + final i0.Value name; + final i0.Value faceAssetId; + final i0.Value isFavorite; + final i0.Value isHidden; + final i0.Value color; + final i0.Value birthDate; + const PersonEntityCompanion({ + this.id = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.ownerId = const i0.Value.absent(), + this.name = const i0.Value.absent(), + this.faceAssetId = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.isHidden = const i0.Value.absent(), + this.color = const i0.Value.absent(), + this.birthDate = const i0.Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const i0.Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const i0.Value.absent(), + this.birthDate = const i0.Value.absent(), + }) : id = i0.Value(id), + ownerId = i0.Value(ownerId), + name = i0.Value(name), + isFavorite = i0.Value(isFavorite), + isHidden = i0.Value(isHidden); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? ownerId, + i0.Expression? name, + i0.Expression? faceAssetId, + i0.Expression? isFavorite, + i0.Expression? isHidden, + i0.Expression? color, + i0.Expression? birthDate, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + i1.PersonEntityCompanion copyWith({ + i0.Value? id, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? ownerId, + i0.Value? name, + i0.Value? faceAssetId, + i0.Value? isFavorite, + i0.Value? isHidden, + i0.Value? color, + i0.Value? birthDate, + }) { + return i1.PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = i0.Variable(ownerId.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = i0.Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = i0.Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = i0.Variable(isHidden.value); + } + if (color.present) { + map['color'] = i0.Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = i0.Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/remote_album.entity.dart b/mobile/lib/infrastructure/entities/remote_album.entity.dart index d12d511148..30e13853d8 100644 --- a/mobile/lib/infrastructure/entities/remote_album.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_album.entity.dart @@ -1,8 +1,10 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; +@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)') class RemoteAlbumEntity extends Table with DriftDefaultsMixin { const RemoteAlbumEntity(); @@ -16,6 +18,8 @@ class RemoteAlbumEntity extends Table with DriftDefaultsMixin { DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + TextColumn get ownerId => text().references(UserEntity, #id, onDelete: KeyAction.cascade)(); + TextColumn get thumbnailAssetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.setNull).nullable()(); diff --git a/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart new file mode 100644 index 0000000000..7dc864b978 --- /dev/null +++ b/mobile/lib/infrastructure/entities/remote_album.entity.drift.dart @@ -0,0 +1,1146 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/album/album.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart' + 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 i5; +import 'package:drift/internal/modular.dart' as i6; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i7; + +typedef $$RemoteAlbumEntityTableCreateCompanionBuilder = + i1.RemoteAlbumEntityCompanion Function({ + required String id, + required String name, + i0.Value description, + i0.Value createdAt, + i0.Value updatedAt, + required String ownerId, + i0.Value thumbnailAssetId, + i0.Value isActivityEnabled, + required i2.AlbumAssetOrder order, + }); +typedef $$RemoteAlbumEntityTableUpdateCompanionBuilder = + i1.RemoteAlbumEntityCompanion Function({ + i0.Value id, + i0.Value name, + i0.Value description, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value ownerId, + i0.Value thumbnailAssetId, + i0.Value isActivityEnabled, + i0.Value order, + }); + +final class $$RemoteAlbumEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteAlbumEntityTable, + i1.RemoteAlbumEntityData + > { + $$RemoteAlbumEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => + i6.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet('remote_album_entity') + .ownerId, + i6.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i5.$$UserEntityTableProcessedTableManager get ownerId { + final $_column = $_itemColumn('owner_id')!; + + final manager = i5 + .$$UserEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i7.$RemoteAssetEntityTable _thumbnailAssetIdTable( + i0.GeneratedDatabase db, + ) => i6.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet('remote_album_entity') + .thumbnailAssetId, + i6.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i7.$$RemoteAssetEntityTableProcessedTableManager? get thumbnailAssetId { + final $_column = $_itemColumn('thumbnail_asset_id'); + if ($_column == null) return null; + final manager = i7 + .$$RemoteAssetEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_thumbnailAssetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteAlbumEntityTableFilterComposer + extends i0.Composer { + $$RemoteAlbumEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isActivityEnabled => $composableBuilder( + column: $table.isActivityEnabled, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get order => $composableBuilder( + column: $table.order, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i5.$$UserEntityTableFilterComposer get ownerId { + final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i7.$$RemoteAssetEntityTableFilterComposer get thumbnailAssetId { + final i7.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.thumbnailAssetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i7.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumEntityTableOrderingComposer + extends i0.Composer { + $$RemoteAlbumEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isActivityEnabled => $composableBuilder( + column: $table.isActivityEnabled, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get order => $composableBuilder( + column: $table.order, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$UserEntityTableOrderingComposer get ownerId { + final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i7.$$RemoteAssetEntityTableOrderingComposer get thumbnailAssetId { + final i7.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.thumbnailAssetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i7.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumEntityTableAnnotationComposer + extends i0.Composer { + $$RemoteAlbumEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get isActivityEnabled => $composableBuilder( + column: $table.isActivityEnabled, + builder: (column) => column, + ); + + i0.GeneratedColumnWithTypeConverter get order => + $composableBuilder(column: $table.order, builder: (column) => column); + + i5.$$UserEntityTableAnnotationComposer get ownerId { + final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i7.$$RemoteAssetEntityTableAnnotationComposer get thumbnailAssetId { + final i7.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.thumbnailAssetId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i7.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumEntityTable, + i1.RemoteAlbumEntityData, + i1.$$RemoteAlbumEntityTableFilterComposer, + i1.$$RemoteAlbumEntityTableOrderingComposer, + i1.$$RemoteAlbumEntityTableAnnotationComposer, + $$RemoteAlbumEntityTableCreateCompanionBuilder, + $$RemoteAlbumEntityTableUpdateCompanionBuilder, + (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), + i1.RemoteAlbumEntityData, + i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId}) + > { + $$RemoteAlbumEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteAlbumEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteAlbumEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$RemoteAlbumEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$RemoteAlbumEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + i0.Value description = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value ownerId = const i0.Value.absent(), + i0.Value thumbnailAssetId = const i0.Value.absent(), + i0.Value isActivityEnabled = const i0.Value.absent(), + i0.Value order = const i0.Value.absent(), + }) => i1.RemoteAlbumEntityCompanion( + id: id, + name: name, + description: description, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + thumbnailAssetId: thumbnailAssetId, + isActivityEnabled: isActivityEnabled, + order: order, + ), + createCompanionCallback: + ({ + required String id, + required String name, + i0.Value description = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + required String ownerId, + i0.Value thumbnailAssetId = const i0.Value.absent(), + i0.Value isActivityEnabled = const i0.Value.absent(), + required i2.AlbumAssetOrder order, + }) => i1.RemoteAlbumEntityCompanion.insert( + id: id, + name: name, + description: description, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + thumbnailAssetId: thumbnailAssetId, + isActivityEnabled: isActivityEnabled, + order: order, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteAlbumEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({ownerId = false, thumbnailAssetId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (ownerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.ownerId, + referencedTable: i1 + .$$RemoteAlbumEntityTableReferences + ._ownerIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumEntityTableReferences + ._ownerIdTable(db) + .id, + ) + as T; + } + if (thumbnailAssetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.thumbnailAssetId, + referencedTable: i1 + .$$RemoteAlbumEntityTableReferences + ._thumbnailAssetIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumEntityTableReferences + ._thumbnailAssetIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteAlbumEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumEntityTable, + i1.RemoteAlbumEntityData, + i1.$$RemoteAlbumEntityTableFilterComposer, + i1.$$RemoteAlbumEntityTableOrderingComposer, + i1.$$RemoteAlbumEntityTableAnnotationComposer, + $$RemoteAlbumEntityTableCreateCompanionBuilder, + $$RemoteAlbumEntityTableUpdateCompanionBuilder, + (i1.RemoteAlbumEntityData, i1.$$RemoteAlbumEntityTableReferences), + i1.RemoteAlbumEntityData, + i0.PrefetchHooks Function({bool ownerId, bool thumbnailAssetId}) + >; +i0.Index get idxRemoteAlbumOwnerId => i0.Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', +); + +class $RemoteAlbumEntityTable extends i3.RemoteAlbumEntity + with i0.TableInfo<$RemoteAlbumEntityTable, i1.RemoteAlbumEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteAlbumEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _descriptionMeta = const i0.VerificationMeta( + 'description', + ); + @override + late final i0.GeneratedColumn description = + i0.GeneratedColumn( + 'description', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const i4.Constant(''), + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( + 'ownerId', + ); + @override + late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _thumbnailAssetIdMeta = + const i0.VerificationMeta('thumbnailAssetId'); + @override + late final i0.GeneratedColumn thumbnailAssetId = + i0.GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + static const i0.VerificationMeta _isActivityEnabledMeta = + const i0.VerificationMeta('isActivityEnabled'); + @override + late final i0.GeneratedColumn isActivityEnabled = + i0.GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const i4.Constant(true), + ); + @override + late final i0.GeneratedColumnWithTypeConverter + order = + i0.GeneratedColumn( + 'order', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$RemoteAlbumEntityTable.$converterorder, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, + _descriptionMeta, + ), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('owner_id')) { + context.handle( + _ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), + ); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('thumbnail_asset_id')) { + context.handle( + _thumbnailAssetIdMeta, + thumbnailAssetId.isAcceptableOrUnknown( + data['thumbnail_asset_id']!, + _thumbnailAssetIdMeta, + ), + ); + } + if (data.containsKey('is_activity_enabled')) { + context.handle( + _isActivityEnabledMeta, + isActivityEnabled.isAcceptableOrUnknown( + data['is_activity_enabled']!, + _isActivityEnabledMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.RemoteAlbumEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: i1.$RemoteAlbumEntityTable.$converterorder.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ), + ); + } + + @override + $RemoteAlbumEntityTable createAlias(String alias) { + return $RemoteAlbumEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converterorder = + const i0.EnumIndexConverter( + i2.AlbumAssetOrder.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final i2.AlbumAssetOrder order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['name'] = i0.Variable(name); + map['description'] = i0.Variable(description); + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + map['owner_id'] = i0.Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = i0.Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = i0.Variable(isActivityEnabled); + { + map['order'] = i0.Variable( + i1.$RemoteAlbumEntityTable.$converterorder.toSql(order), + ); + } + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: i1.$RemoteAlbumEntityTable.$converterorder.fromJson( + serializer.fromJson(json['order']), + ), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson( + i1.$RemoteAlbumEntityTable.$converterorder.toJson(order), + ), + }; + } + + i1.RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + i0.Value thumbnailAssetId = const i0.Value.absent(), + bool? isActivityEnabled, + i2.AlbumAssetOrder? order, + }) => i1.RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(i1.RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + final i0.Value description; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value ownerId; + final i0.Value thumbnailAssetId; + final i0.Value isActivityEnabled; + final i0.Value order; + const RemoteAlbumEntityCompanion({ + this.id = const i0.Value.absent(), + this.name = const i0.Value.absent(), + this.description = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.ownerId = const i0.Value.absent(), + this.thumbnailAssetId = const i0.Value.absent(), + this.isActivityEnabled = const i0.Value.absent(), + this.order = const i0.Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + required String ownerId, + this.thumbnailAssetId = const i0.Value.absent(), + this.isActivityEnabled = const i0.Value.absent(), + required i2.AlbumAssetOrder order, + }) : id = i0.Value(id), + name = i0.Value(name), + ownerId = i0.Value(ownerId), + order = i0.Value(order); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + i0.Expression? description, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? ownerId, + i0.Expression? thumbnailAssetId, + i0.Expression? isActivityEnabled, + i0.Expression? order, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + i1.RemoteAlbumEntityCompanion copyWith({ + i0.Value? id, + i0.Value? name, + i0.Value? description, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? ownerId, + i0.Value? thumbnailAssetId, + i0.Value? isActivityEnabled, + i0.Value? order, + }) { + return i1.RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (description.present) { + map['description'] = i0.Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = i0.Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = i0.Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = i0.Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = i0.Variable( + i1.$RemoteAlbumEntityTable.$converterorder.toSql(order.value), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/remote_album_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_album_asset.entity.drift.dart new file mode 100644 index 0000000000..a03c4d7e96 --- /dev/null +++ b/mobile/lib/infrastructure/entities/remote_album_asset.entity.drift.dart @@ -0,0 +1,672 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart' + as i2; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' + as i5; + +typedef $$RemoteAlbumAssetEntityTableCreateCompanionBuilder = + i1.RemoteAlbumAssetEntityCompanion Function({ + required String assetId, + required String albumId, + }); +typedef $$RemoteAlbumAssetEntityTableUpdateCompanionBuilder = + i1.RemoteAlbumAssetEntityCompanion Function({ + i0.Value assetId, + i0.Value albumId, + }); + +final class $$RemoteAlbumAssetEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteAlbumAssetEntityTable, + i1.RemoteAlbumAssetEntityData + > { + $$RemoteAlbumAssetEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet( + 'remote_album_asset_entity', + ) + .assetId, + i4.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i3.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i3 + .$$RemoteAssetEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i5.$RemoteAlbumEntityTable _albumIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('remote_album_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet( + 'remote_album_asset_entity', + ) + .albumId, + i4.ReadDatabaseContainer( + db, + ).resultSet('remote_album_entity').id, + ), + ); + + i5.$$RemoteAlbumEntityTableProcessedTableManager get albumId { + final $_column = $_itemColumn('album_id')!; + + final manager = i5 + .$$RemoteAlbumEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('remote_album_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_albumIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteAlbumAssetEntityTableFilterComposer + extends i0.Composer { + $$RemoteAlbumAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableFilterComposer get assetId { + final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$RemoteAlbumEntityTableFilterComposer get albumId { + final i5.$$RemoteAlbumEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumAssetEntityTableOrderingComposer + extends i0.Composer { + $$RemoteAlbumAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i3.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$RemoteAlbumEntityTableOrderingComposer get albumId { + final i5.$$RemoteAlbumEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumAssetEntityTableAnnotationComposer + extends i0.Composer { + $$RemoteAlbumAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i3.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i3.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i5.$$RemoteAlbumEntityTableAnnotationComposer get albumId { + final i5.$$RemoteAlbumEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$RemoteAlbumEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumAssetEntityTable, + i1.RemoteAlbumAssetEntityData, + i1.$$RemoteAlbumAssetEntityTableFilterComposer, + i1.$$RemoteAlbumAssetEntityTableOrderingComposer, + i1.$$RemoteAlbumAssetEntityTableAnnotationComposer, + $$RemoteAlbumAssetEntityTableCreateCompanionBuilder, + $$RemoteAlbumAssetEntityTableUpdateCompanionBuilder, + ( + i1.RemoteAlbumAssetEntityData, + i1.$$RemoteAlbumAssetEntityTableReferences, + ), + i1.RemoteAlbumAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool albumId}) + > { + $$RemoteAlbumAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteAlbumAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteAlbumAssetEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$RemoteAlbumAssetEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$RemoteAlbumAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value albumId = const i0.Value.absent(), + }) => i1.RemoteAlbumAssetEntityCompanion( + assetId: assetId, + albumId: albumId, + ), + createCompanionCallback: + ({required String assetId, required String albumId}) => + i1.RemoteAlbumAssetEntityCompanion.insert( + assetId: assetId, + albumId: albumId, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteAlbumAssetEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false, albumId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$RemoteAlbumAssetEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumAssetEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + if (albumId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.albumId, + referencedTable: i1 + .$$RemoteAlbumAssetEntityTableReferences + ._albumIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumAssetEntityTableReferences + ._albumIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteAlbumAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumAssetEntityTable, + i1.RemoteAlbumAssetEntityData, + i1.$$RemoteAlbumAssetEntityTableFilterComposer, + i1.$$RemoteAlbumAssetEntityTableOrderingComposer, + i1.$$RemoteAlbumAssetEntityTableAnnotationComposer, + $$RemoteAlbumAssetEntityTableCreateCompanionBuilder, + $$RemoteAlbumAssetEntityTableUpdateCompanionBuilder, + ( + i1.RemoteAlbumAssetEntityData, + i1.$$RemoteAlbumAssetEntityTableReferences, + ), + i1.RemoteAlbumAssetEntityData, + i0.PrefetchHooks Function({bool assetId, bool albumId}) + >; +i0.Index get idxRemoteAlbumAssetAlbumAsset => i0.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', +); + +class $RemoteAlbumAssetEntityTable extends i2.RemoteAlbumAssetEntity + with + i0.TableInfo< + $RemoteAlbumAssetEntityTable, + i1.RemoteAlbumAssetEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteAlbumAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta( + 'albumId', + ); + @override + late final i0.GeneratedColumn albumId = i0.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('album_id')) { + context.handle( + _albumIdMeta, + albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta), + ); + } else if (isInserting) { + context.missing(_albumIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {assetId, albumId}; + @override + i1.RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + $RemoteAlbumAssetEntityTable createAlias(String alias) { + return $RemoteAlbumAssetEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + map['album_id'] = i0.Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + i1.RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + i1.RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + i1.RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.albumId = const i0.Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = i0.Value(assetId), + albumId = i0.Value(albumId); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? albumId, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + i1.RemoteAlbumAssetEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? albumId, + }) { + return i1.RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = i0.Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/remote_album_user.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_album_user.entity.drift.dart new file mode 100644 index 0000000000..f8167ddf94 --- /dev/null +++ b/mobile/lib/infrastructure/entities/remote_album_user.entity.drift.dart @@ -0,0 +1,739 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/album/album.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart' + as i3; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' + as i6; + +typedef $$RemoteAlbumUserEntityTableCreateCompanionBuilder = + i1.RemoteAlbumUserEntityCompanion Function({ + required String albumId, + required String userId, + required i2.AlbumUserRole role, + }); +typedef $$RemoteAlbumUserEntityTableUpdateCompanionBuilder = + i1.RemoteAlbumUserEntityCompanion Function({ + i0.Value albumId, + i0.Value userId, + i0.Value role, + }); + +final class $$RemoteAlbumUserEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteAlbumUserEntityTable, + i1.RemoteAlbumUserEntityData + > { + $$RemoteAlbumUserEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i4.$RemoteAlbumEntityTable _albumIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('remote_album_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet( + 'remote_album_user_entity', + ) + .albumId, + i5.ReadDatabaseContainer( + db, + ).resultSet('remote_album_entity').id, + ), + ); + + i4.$$RemoteAlbumEntityTableProcessedTableManager get albumId { + final $_column = $_itemColumn('album_id')!; + + final manager = i4 + .$$RemoteAlbumEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('remote_album_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_albumIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static i6.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer(db) + .resultSet( + 'remote_album_user_entity', + ) + .userId, + i5.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i6.$$UserEntityTableProcessedTableManager get userId { + final $_column = $_itemColumn('user_id')!; + + final manager = i6 + .$$UserEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteAlbumUserEntityTableFilterComposer + extends i0.Composer { + $$RemoteAlbumUserEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnWithTypeConverterFilters + get role => $composableBuilder( + column: $table.role, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i4.$$RemoteAlbumEntityTableFilterComposer get albumId { + final i4.$$RemoteAlbumEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAlbumEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$UserEntityTableFilterComposer get userId { + final i6.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$UserEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumUserEntityTableOrderingComposer + extends i0.Composer { + $$RemoteAlbumUserEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get role => $composableBuilder( + column: $table.role, + builder: (column) => i0.ColumnOrderings(column), + ); + + i4.$$RemoteAlbumEntityTableOrderingComposer get albumId { + final i4.$$RemoteAlbumEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAlbumEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$UserEntityTableOrderingComposer get userId { + final i6.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumUserEntityTableAnnotationComposer + extends i0.Composer { + $$RemoteAlbumUserEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumnWithTypeConverter get role => + $composableBuilder(column: $table.role, builder: (column) => column); + + i4.$$RemoteAlbumEntityTableAnnotationComposer get albumId { + final i4.$$RemoteAlbumEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.albumId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$RemoteAlbumEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('remote_album_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + i6.$$UserEntityTableAnnotationComposer get userId { + final i6.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i6.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAlbumUserEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumUserEntityTable, + i1.RemoteAlbumUserEntityData, + i1.$$RemoteAlbumUserEntityTableFilterComposer, + i1.$$RemoteAlbumUserEntityTableOrderingComposer, + i1.$$RemoteAlbumUserEntityTableAnnotationComposer, + $$RemoteAlbumUserEntityTableCreateCompanionBuilder, + $$RemoteAlbumUserEntityTableUpdateCompanionBuilder, + ( + i1.RemoteAlbumUserEntityData, + i1.$$RemoteAlbumUserEntityTableReferences, + ), + i1.RemoteAlbumUserEntityData, + i0.PrefetchHooks Function({bool albumId, bool userId}) + > { + $$RemoteAlbumUserEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteAlbumUserEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteAlbumUserEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$RemoteAlbumUserEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$RemoteAlbumUserEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value albumId = const i0.Value.absent(), + i0.Value userId = const i0.Value.absent(), + i0.Value role = const i0.Value.absent(), + }) => i1.RemoteAlbumUserEntityCompanion( + albumId: albumId, + userId: userId, + role: role, + ), + createCompanionCallback: + ({ + required String albumId, + required String userId, + required i2.AlbumUserRole role, + }) => i1.RemoteAlbumUserEntityCompanion.insert( + albumId: albumId, + userId: userId, + role: role, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteAlbumUserEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({albumId = false, userId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (albumId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.albumId, + referencedTable: i1 + .$$RemoteAlbumUserEntityTableReferences + ._albumIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumUserEntityTableReferences + ._albumIdTable(db) + .id, + ) + as T; + } + if (userId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: i1 + .$$RemoteAlbumUserEntityTableReferences + ._userIdTable(db), + referencedColumn: i1 + .$$RemoteAlbumUserEntityTableReferences + ._userIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteAlbumUserEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteAlbumUserEntityTable, + i1.RemoteAlbumUserEntityData, + i1.$$RemoteAlbumUserEntityTableFilterComposer, + i1.$$RemoteAlbumUserEntityTableOrderingComposer, + i1.$$RemoteAlbumUserEntityTableAnnotationComposer, + $$RemoteAlbumUserEntityTableCreateCompanionBuilder, + $$RemoteAlbumUserEntityTableUpdateCompanionBuilder, + (i1.RemoteAlbumUserEntityData, i1.$$RemoteAlbumUserEntityTableReferences), + i1.RemoteAlbumUserEntityData, + i0.PrefetchHooks Function({bool albumId, bool userId}) + >; + +class $RemoteAlbumUserEntityTable extends i3.RemoteAlbumUserEntity + with + i0.TableInfo< + $RemoteAlbumUserEntityTable, + i1.RemoteAlbumUserEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteAlbumUserEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta( + 'albumId', + ); + @override + late final i0.GeneratedColumn albumId = i0.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _userIdMeta = const i0.VerificationMeta( + 'userId', + ); + @override + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + @override + late final i0.GeneratedColumnWithTypeConverter role = + i0.GeneratedColumn( + 'role', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$RemoteAlbumUserEntityTable.$converterrole, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('album_id')) { + context.handle( + _albumIdMeta, + albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta), + ); + } else if (isInserting) { + context.missing(_albumIdMeta); + } + if (data.containsKey('user_id')) { + context.handle( + _userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + ); + } else if (isInserting) { + context.missing(_userIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {albumId, userId}; + @override + i1.RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: i1.$RemoteAlbumUserEntityTable.$converterrole.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ), + ); + } + + @override + $RemoteAlbumUserEntityTable createAlias(String alias) { + return $RemoteAlbumUserEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converterrole = + const i0.EnumIndexConverter(i2.AlbumUserRole.values); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends i0.DataClass + implements i0.Insertable { + final String albumId; + final String userId; + final i2.AlbumUserRole role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = i0.Variable(albumId); + map['user_id'] = i0.Variable(userId); + { + map['role'] = i0.Variable( + i1.$RemoteAlbumUserEntityTable.$converterrole.toSql(role), + ); + } + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: i1.$RemoteAlbumUserEntityTable.$converterrole.fromJson( + serializer.fromJson(json['role']), + ), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson( + i1.$RemoteAlbumUserEntityTable.$converterrole.toJson(role), + ), + }; + } + + i1.RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + i2.AlbumUserRole? role, + }) => i1.RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + i1.RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends i0.UpdateCompanion { + final i0.Value albumId; + final i0.Value userId; + final i0.Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const i0.Value.absent(), + this.userId = const i0.Value.absent(), + this.role = const i0.Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required i2.AlbumUserRole role, + }) : albumId = i0.Value(albumId), + userId = i0.Value(userId), + role = i0.Value(role); + static i0.Insertable custom({ + i0.Expression? albumId, + i0.Expression? userId, + i0.Expression? role, + }) { + return i0.RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + i1.RemoteAlbumUserEntityCompanion copyWith({ + i0.Value? albumId, + i0.Value? userId, + i0.Value? role, + }) { + return i1.RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = i0.Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (role.present) { + map['role'] = i0.Variable( + i1.$RemoteAlbumUserEntityTable.$converterrole.toSql(role.value), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.dart index 3fe56ae431..4c8b563616 100644 --- a/mobile/lib/infrastructure/entities/remote_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.dart @@ -5,6 +5,9 @@ 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.sql( + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', +) @TableIndex.sql(''' CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) @@ -17,11 +20,12 @@ WHERE (library_id IS NOT NULL); ''') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)') @TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)') -@TableIndex.sql(''' -CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created -ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC) -''') -@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_remote_asset_uploaded ON remote_asset_entity (uploaded_at)') +@TableIndex.sql( + "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME('%Y-%m-%d', local_date_time))", +) +@TableIndex.sql( + "CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME('%Y-%m', local_date_time))", +) class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin { const RemoteAssetEntity(); @@ -39,8 +43,6 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin DateTimeColumn get deletedAt => dateTime().nullable()(); - DateTimeColumn get uploadedAt => dateTime().nullable()(); - TextColumn get livePhotoVideoId => text().nullable()(); IntColumn get visibility => intEnum()(); @@ -64,8 +66,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { type: type, createdAt: createdAt, updatedAt: updatedAt, - uploadedAt: uploadedAt, - durationMs: durationMs, + durationInSeconds: durationInSeconds, isFavorite: isFavorite, height: height, width: width, @@ -75,6 +76,5 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData { localId: localId, stackId: stackId, isEdited: isEdited, - deletedAt: deletedAt, ); } diff --git a/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart new file mode 100644 index 0000000000..8231cfcd8a --- /dev/null +++ b/mobile/lib/infrastructure/entities/remote_asset.entity.drift.dart @@ -0,0 +1,1724 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart' + 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 i5; +import 'package:drift/internal/modular.dart' as i6; + +typedef $$RemoteAssetEntityTableCreateCompanionBuilder = + i1.RemoteAssetEntityCompanion Function({ + required String name, + required i2.AssetType type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + required String id, + required String checksum, + i0.Value isFavorite, + required String ownerId, + i0.Value localDateTime, + i0.Value thumbHash, + i0.Value deletedAt, + i0.Value livePhotoVideoId, + required i2.AssetVisibility visibility, + i0.Value stackId, + i0.Value libraryId, + i0.Value isEdited, + }); +typedef $$RemoteAssetEntityTableUpdateCompanionBuilder = + i1.RemoteAssetEntityCompanion Function({ + i0.Value name, + i0.Value type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + i0.Value id, + i0.Value checksum, + i0.Value isFavorite, + i0.Value ownerId, + i0.Value localDateTime, + i0.Value thumbHash, + i0.Value deletedAt, + i0.Value livePhotoVideoId, + i0.Value visibility, + i0.Value stackId, + i0.Value libraryId, + i0.Value isEdited, + }); + +final class $$RemoteAssetEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteAssetEntityTable, + i1.RemoteAssetEntityData + > { + $$RemoteAssetEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i5.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => + i6.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .ownerId, + i6.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i5.$$UserEntityTableProcessedTableManager get ownerId { + final $_column = $_itemColumn('owner_id')!; + + final manager = i5 + .$$UserEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteAssetEntityTableFilterComposer + extends i0.Composer { + $$RemoteAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters get type => + $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get localDateTime => $composableBuilder( + column: $table.localDateTime, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get thumbHash => $composableBuilder( + column: $table.thumbHash, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get visibility => $composableBuilder( + column: $table.visibility, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get stackId => $composableBuilder( + column: $table.stackId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get libraryId => $composableBuilder( + column: $table.libraryId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isEdited => $composableBuilder( + column: $table.isEdited, + builder: (column) => i0.ColumnFilters(column), + ); + + i5.$$UserEntityTableFilterComposer get ownerId { + final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetEntityTableOrderingComposer + extends i0.Composer { + $$RemoteAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get localDateTime => $composableBuilder( + column: $table.localDateTime, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get thumbHash => $composableBuilder( + column: $table.thumbHash, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get deletedAt => $composableBuilder( + column: $table.deletedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get visibility => $composableBuilder( + column: $table.visibility, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get stackId => $composableBuilder( + column: $table.stackId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get libraryId => $composableBuilder( + column: $table.libraryId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isEdited => $composableBuilder( + column: $table.isEdited, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$UserEntityTableOrderingComposer get ownerId { + final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetEntityTableAnnotationComposer + extends i0.Composer { + $$RemoteAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get width => + $composableBuilder(column: $table.width, builder: (column) => column); + + i0.GeneratedColumn get height => + $composableBuilder(column: $table.height, builder: (column) => column); + + i0.GeneratedColumn get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => column, + ); + + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); + + i0.GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => column, + ); + + i0.GeneratedColumn get localDateTime => $composableBuilder( + column: $table.localDateTime, + builder: (column) => column, + ); + + i0.GeneratedColumn get thumbHash => + $composableBuilder(column: $table.thumbHash, builder: (column) => column); + + i0.GeneratedColumn get deletedAt => + $composableBuilder(column: $table.deletedAt, builder: (column) => column); + + i0.GeneratedColumn get livePhotoVideoId => $composableBuilder( + column: $table.livePhotoVideoId, + builder: (column) => column, + ); + + i0.GeneratedColumnWithTypeConverter get visibility => + $composableBuilder( + column: $table.visibility, + builder: (column) => column, + ); + + i0.GeneratedColumn get stackId => + $composableBuilder(column: $table.stackId, builder: (column) => column); + + i0.GeneratedColumn get libraryId => + $composableBuilder(column: $table.libraryId, builder: (column) => column); + + i0.GeneratedColumn get isEdited => + $composableBuilder(column: $table.isEdited, builder: (column) => column); + + i5.$$UserEntityTableAnnotationComposer get ownerId { + final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteAssetEntityTable, + i1.RemoteAssetEntityData, + i1.$$RemoteAssetEntityTableFilterComposer, + i1.$$RemoteAssetEntityTableOrderingComposer, + i1.$$RemoteAssetEntityTableAnnotationComposer, + $$RemoteAssetEntityTableCreateCompanionBuilder, + $$RemoteAssetEntityTableUpdateCompanionBuilder, + (i1.RemoteAssetEntityData, i1.$$RemoteAssetEntityTableReferences), + i1.RemoteAssetEntityData, + i0.PrefetchHooks Function({bool ownerId}) + > { + $$RemoteAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteAssetEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => i1 + .$$RemoteAssetEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$RemoteAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value name = const i0.Value.absent(), + i0.Value type = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + i0.Value id = const i0.Value.absent(), + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value ownerId = const i0.Value.absent(), + i0.Value localDateTime = const i0.Value.absent(), + i0.Value thumbHash = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + i0.Value visibility = + const i0.Value.absent(), + i0.Value stackId = const i0.Value.absent(), + i0.Value libraryId = const i0.Value.absent(), + i0.Value isEdited = const i0.Value.absent(), + }) => i1.RemoteAssetEntityCompanion( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + checksum: checksum, + isFavorite: isFavorite, + ownerId: ownerId, + localDateTime: localDateTime, + thumbHash: thumbHash, + deletedAt: deletedAt, + livePhotoVideoId: livePhotoVideoId, + visibility: visibility, + stackId: stackId, + libraryId: libraryId, + isEdited: isEdited, + ), + createCompanionCallback: + ({ + required String name, + required i2.AssetType type, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + required String id, + required String checksum, + i0.Value isFavorite = const i0.Value.absent(), + required String ownerId, + i0.Value localDateTime = const i0.Value.absent(), + i0.Value thumbHash = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + required i2.AssetVisibility visibility, + i0.Value stackId = const i0.Value.absent(), + i0.Value libraryId = const i0.Value.absent(), + i0.Value isEdited = const i0.Value.absent(), + }) => i1.RemoteAssetEntityCompanion.insert( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + checksum: checksum, + isFavorite: isFavorite, + ownerId: ownerId, + localDateTime: localDateTime, + thumbHash: thumbHash, + deletedAt: deletedAt, + livePhotoVideoId: livePhotoVideoId, + visibility: visibility, + stackId: stackId, + libraryId: libraryId, + isEdited: isEdited, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteAssetEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({ownerId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (ownerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.ownerId, + referencedTable: i1 + .$$RemoteAssetEntityTableReferences + ._ownerIdTable(db), + referencedColumn: i1 + .$$RemoteAssetEntityTableReferences + ._ownerIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteAssetEntityTable, + i1.RemoteAssetEntityData, + i1.$$RemoteAssetEntityTableFilterComposer, + i1.$$RemoteAssetEntityTableOrderingComposer, + i1.$$RemoteAssetEntityTableAnnotationComposer, + $$RemoteAssetEntityTableCreateCompanionBuilder, + $$RemoteAssetEntityTableUpdateCompanionBuilder, + (i1.RemoteAssetEntityData, i1.$$RemoteAssetEntityTableReferences), + i1.RemoteAssetEntityData, + i0.PrefetchHooks Function({bool ownerId}) + >; +i0.Index get idxRemoteAssetOwnerChecksum => i0.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', +); + +class $RemoteAssetEntityTable extends i3.RemoteAssetEntity + with i0.TableInfo<$RemoteAssetEntityTable, i1.RemoteAssetEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final i0.GeneratedColumnWithTypeConverter type = + i0.GeneratedColumn( + 'type', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter(i1.$RemoteAssetEntityTable.$convertertype); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta( + 'width', + ); + @override + late final i0.GeneratedColumn width = i0.GeneratedColumn( + 'width', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta( + 'height', + ); + @override + late final i0.GeneratedColumn height = i0.GeneratedColumn( + 'height', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _durationInSecondsMeta = + const i0.VerificationMeta('durationInSeconds'); + @override + late final i0.GeneratedColumn durationInSeconds = + i0.GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta( + 'checksum', + ); + @override + late final i0.GeneratedColumn checksum = i0.GeneratedColumn( + 'checksum', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta( + 'isFavorite', + ); + @override + late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( + 'ownerId', + ); + @override + late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _localDateTimeMeta = + const i0.VerificationMeta('localDateTime'); + @override + late final i0.GeneratedColumn localDateTime = + i0.GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _thumbHashMeta = const i0.VerificationMeta( + 'thumbHash', + ); + @override + late final i0.GeneratedColumn thumbHash = i0.GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _deletedAtMeta = const i0.VerificationMeta( + 'deletedAt', + ); + @override + late final i0.GeneratedColumn deletedAt = + i0.GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _livePhotoVideoIdMeta = + const i0.VerificationMeta('livePhotoVideoId'); + @override + late final i0.GeneratedColumn livePhotoVideoId = + i0.GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + visibility = + i0.GeneratedColumn( + 'visibility', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$RemoteAssetEntityTable.$convertervisibility, + ); + static const i0.VerificationMeta _stackIdMeta = const i0.VerificationMeta( + 'stackId', + ); + @override + late final i0.GeneratedColumn stackId = i0.GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _libraryIdMeta = const i0.VerificationMeta( + 'libraryId', + ); + @override + late final i0.GeneratedColumn libraryId = i0.GeneratedColumn( + 'library_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isEditedMeta = const i0.VerificationMeta( + 'isEdited', + ); + @override + late final i0.GeneratedColumn isEdited = i0.GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('width')) { + context.handle( + _widthMeta, + width.isAcceptableOrUnknown(data['width']!, _widthMeta), + ); + } + if (data.containsKey('height')) { + context.handle( + _heightMeta, + height.isAcceptableOrUnknown(data['height']!, _heightMeta), + ); + } + if (data.containsKey('duration_in_seconds')) { + context.handle( + _durationInSecondsMeta, + durationInSeconds.isAcceptableOrUnknown( + data['duration_in_seconds']!, + _durationInSecondsMeta, + ), + ); + } + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } else if (isInserting) { + context.missing(_checksumMeta); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta), + ); + } + if (data.containsKey('owner_id')) { + context.handle( + _ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), + ); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('local_date_time')) { + context.handle( + _localDateTimeMeta, + localDateTime.isAcceptableOrUnknown( + data['local_date_time']!, + _localDateTimeMeta, + ), + ); + } + if (data.containsKey('thumb_hash')) { + context.handle( + _thumbHashMeta, + thumbHash.isAcceptableOrUnknown(data['thumb_hash']!, _thumbHashMeta), + ); + } + if (data.containsKey('deleted_at')) { + context.handle( + _deletedAtMeta, + deletedAt.isAcceptableOrUnknown(data['deleted_at']!, _deletedAtMeta), + ); + } + if (data.containsKey('live_photo_video_id')) { + context.handle( + _livePhotoVideoIdMeta, + livePhotoVideoId.isAcceptableOrUnknown( + data['live_photo_video_id']!, + _livePhotoVideoIdMeta, + ), + ); + } + if (data.containsKey('stack_id')) { + context.handle( + _stackIdMeta, + stackId.isAcceptableOrUnknown(data['stack_id']!, _stackIdMeta), + ); + } + if (data.containsKey('library_id')) { + context.handle( + _libraryIdMeta, + libraryId.isAcceptableOrUnknown(data['library_id']!, _libraryIdMeta), + ); + } + if (data.containsKey('is_edited')) { + context.handle( + _isEditedMeta, + isEdited.isAcceptableOrUnknown(data['is_edited']!, _isEditedMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.RemoteAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: i1.$RemoteAssetEntityTable.$convertertype.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: i1.$RemoteAssetEntityTable.$convertervisibility.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + ), + stackId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + $RemoteAssetEntityTable createAlias(String alias) { + return $RemoteAssetEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $convertertype = + const i0.EnumIndexConverter(i2.AssetType.values); + static i0.JsonTypeConverter2 + $convertervisibility = const i0.EnumIndexConverter( + i2.AssetVisibility.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String name; + final i2.AssetType type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final i2.AssetVisibility visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = i0.Variable(name); + { + map['type'] = i0.Variable( + i1.$RemoteAssetEntityTable.$convertertype.toSql(type), + ); + } + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = i0.Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = i0.Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds); + } + map['id'] = i0.Variable(id); + map['checksum'] = i0.Variable(checksum); + map['is_favorite'] = i0.Variable(isFavorite); + map['owner_id'] = i0.Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = i0.Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = i0.Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = i0.Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId); + } + { + map['visibility'] = i0.Variable( + i1.$RemoteAssetEntityTable.$convertervisibility.toSql(visibility), + ); + } + if (!nullToAbsent || stackId != null) { + map['stack_id'] = i0.Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = i0.Variable(libraryId); + } + map['is_edited'] = i0.Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: i1.$RemoteAssetEntityTable.$convertertype.fromJson( + serializer.fromJson(json['type']), + ), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: i1.$RemoteAssetEntityTable.$convertervisibility.fromJson( + serializer.fromJson(json['visibility']), + ), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson( + i1.$RemoteAssetEntityTable.$convertertype.toJson(type), + ), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson( + i1.$RemoteAssetEntityTable.$convertervisibility.toJson(visibility), + ), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + i1.RemoteAssetEntityData copyWith({ + String? name, + i2.AssetType? type, + DateTime? createdAt, + DateTime? updatedAt, + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + i0.Value localDateTime = const i0.Value.absent(), + i0.Value thumbHash = const i0.Value.absent(), + i0.Value deletedAt = const i0.Value.absent(), + i0.Value livePhotoVideoId = const i0.Value.absent(), + i2.AssetVisibility? visibility, + i0.Value stackId = const i0.Value.absent(), + i0.Value libraryId = const i0.Value.absent(), + bool? isEdited, + }) => i1.RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(i1.RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value name; + final i0.Value type; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value width; + final i0.Value height; + final i0.Value durationInSeconds; + final i0.Value id; + final i0.Value checksum; + final i0.Value isFavorite; + final i0.Value ownerId; + final i0.Value localDateTime; + final i0.Value thumbHash; + final i0.Value deletedAt; + final i0.Value livePhotoVideoId; + final i0.Value visibility; + final i0.Value stackId; + final i0.Value libraryId; + final i0.Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const i0.Value.absent(), + this.type = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + this.id = const i0.Value.absent(), + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.ownerId = const i0.Value.absent(), + this.localDateTime = const i0.Value.absent(), + this.thumbHash = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + this.visibility = const i0.Value.absent(), + this.stackId = const i0.Value.absent(), + this.libraryId = const i0.Value.absent(), + this.isEdited = const i0.Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required i2.AssetType type, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + required String id, + required String checksum, + this.isFavorite = const i0.Value.absent(), + required String ownerId, + this.localDateTime = const i0.Value.absent(), + this.thumbHash = const i0.Value.absent(), + this.deletedAt = const i0.Value.absent(), + this.livePhotoVideoId = const i0.Value.absent(), + required i2.AssetVisibility visibility, + this.stackId = const i0.Value.absent(), + this.libraryId = const i0.Value.absent(), + this.isEdited = const i0.Value.absent(), + }) : name = i0.Value(name), + type = i0.Value(type), + id = i0.Value(id), + checksum = i0.Value(checksum), + ownerId = i0.Value(ownerId), + visibility = i0.Value(visibility); + static i0.Insertable custom({ + i0.Expression? name, + i0.Expression? type, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? width, + i0.Expression? height, + i0.Expression? durationInSeconds, + i0.Expression? id, + i0.Expression? checksum, + i0.Expression? isFavorite, + i0.Expression? ownerId, + i0.Expression? localDateTime, + i0.Expression? thumbHash, + i0.Expression? deletedAt, + i0.Expression? livePhotoVideoId, + i0.Expression? visibility, + i0.Expression? stackId, + i0.Expression? libraryId, + i0.Expression? isEdited, + }) { + return i0.RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + i1.RemoteAssetEntityCompanion copyWith({ + i0.Value? name, + i0.Value? type, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? width, + i0.Value? height, + i0.Value? durationInSeconds, + i0.Value? id, + i0.Value? checksum, + i0.Value? isFavorite, + i0.Value? ownerId, + i0.Value? localDateTime, + i0.Value? thumbHash, + i0.Value? deletedAt, + i0.Value? livePhotoVideoId, + i0.Value? visibility, + i0.Value? stackId, + i0.Value? libraryId, + i0.Value? isEdited, + }) { + return i1.RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (type.present) { + map['type'] = i0.Variable( + i1.$RemoteAssetEntityTable.$convertertype.toSql(type.value), + ); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (width.present) { + map['width'] = i0.Variable(width.value); + } + if (height.present) { + map['height'] = i0.Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = i0.Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = i0.Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = i0.Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = i0.Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = i0.Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = i0.Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = i0.Variable( + i1.$RemoteAssetEntityTable.$convertervisibility.toSql(visibility.value), + ); + } + if (stackId.present) { + map['stack_id'] = i0.Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = i0.Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = i0.Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +i0.Index get uQRemoteAssetsOwnerChecksum => i0.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', +); +i0.Index get uQRemoteAssetsOwnerLibraryChecksum => i0.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', +); +i0.Index get idxRemoteAssetChecksum => i0.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', +); +i0.Index get idxRemoteAssetStackId => i0.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', +); +i0.Index get idxRemoteAssetLocalDateTimeDay => i0.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', +); +i0.Index get idxRemoteAssetLocalDateTimeMonth => i0.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', +); diff --git a/mobile/lib/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart b/mobile/lib/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart new file mode 100644 index 0000000000..f86528ee64 --- /dev/null +++ b/mobile/lib/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart @@ -0,0 +1,830 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart' + as i2; +import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart' + as i3; +import 'package:drift/internal/modular.dart' as i4; + +typedef $$RemoteAssetCloudIdEntityTableCreateCompanionBuilder = + i1.RemoteAssetCloudIdEntityCompanion Function({ + required String assetId, + i0.Value cloudId, + i0.Value createdAt, + i0.Value adjustmentTime, + i0.Value latitude, + i0.Value longitude, + }); +typedef $$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder = + i1.RemoteAssetCloudIdEntityCompanion Function({ + i0.Value assetId, + i0.Value cloudId, + i0.Value createdAt, + i0.Value adjustmentTime, + i0.Value latitude, + i0.Value longitude, + }); + +final class $$RemoteAssetCloudIdEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$RemoteAssetCloudIdEntityTable, + i1.RemoteAssetCloudIdEntityData + > { + $$RemoteAssetCloudIdEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) => + i4.ReadDatabaseContainer(db) + .resultSet('remote_asset_entity') + .createAlias( + i0.$_aliasNameGenerator( + i4.ReadDatabaseContainer(db) + .resultSet( + 'remote_asset_cloud_id_entity', + ) + .assetId, + i4.ReadDatabaseContainer( + db, + ).resultSet('remote_asset_entity').id, + ), + ); + + i3.$$RemoteAssetEntityTableProcessedTableManager get assetId { + final $_column = $_itemColumn('asset_id')!; + + final manager = i3 + .$$RemoteAssetEntityTableTableManager( + $_db, + i4.ReadDatabaseContainer( + $_db, + ).resultSet('remote_asset_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_assetIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$RemoteAssetCloudIdEntityTableFilterComposer + extends + i0.Composer { + $$RemoteAssetCloudIdEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get cloudId => $composableBuilder( + column: $table.cloudId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnFilters(column), + ); + + i3.$$RemoteAssetEntityTableFilterComposer get assetId { + final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableFilterComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetCloudIdEntityTableOrderingComposer + extends + i0.Composer { + $$RemoteAssetCloudIdEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get cloudId => $composableBuilder( + column: $table.cloudId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get latitude => $composableBuilder( + column: $table.latitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get longitude => $composableBuilder( + column: $table.longitude, + builder: (column) => i0.ColumnOrderings(column), + ); + + i3.$$RemoteAssetEntityTableOrderingComposer get assetId { + final i3.$$RemoteAssetEntityTableOrderingComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableOrderingComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetCloudIdEntityTableAnnotationComposer + extends + i0.Composer { + $$RemoteAssetCloudIdEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get cloudId => + $composableBuilder(column: $table.cloudId, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get adjustmentTime => $composableBuilder( + column: $table.adjustmentTime, + builder: (column) => column, + ); + + i0.GeneratedColumn get latitude => + $composableBuilder(column: $table.latitude, builder: (column) => column); + + i0.GeneratedColumn get longitude => + $composableBuilder(column: $table.longitude, builder: (column) => column); + + i3.$$RemoteAssetEntityTableAnnotationComposer get assetId { + final i3.$$RemoteAssetEntityTableAnnotationComposer composer = + $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.assetId, + referencedTable: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i3.$$RemoteAssetEntityTableAnnotationComposer( + $db: $db, + $table: i4.ReadDatabaseContainer( + $db, + ).resultSet('remote_asset_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$RemoteAssetCloudIdEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$RemoteAssetCloudIdEntityTable, + i1.RemoteAssetCloudIdEntityData, + i1.$$RemoteAssetCloudIdEntityTableFilterComposer, + i1.$$RemoteAssetCloudIdEntityTableOrderingComposer, + i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer, + $$RemoteAssetCloudIdEntityTableCreateCompanionBuilder, + $$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder, + ( + i1.RemoteAssetCloudIdEntityData, + i1.$$RemoteAssetCloudIdEntityTableReferences, + ), + i1.RemoteAssetCloudIdEntityData, + i0.PrefetchHooks Function({bool assetId}) + > { + $$RemoteAssetCloudIdEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$RemoteAssetCloudIdEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$RemoteAssetCloudIdEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$RemoteAssetCloudIdEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value assetId = const i0.Value.absent(), + i0.Value cloudId = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + }) => i1.RemoteAssetCloudIdEntityCompanion( + assetId: assetId, + cloudId: cloudId, + createdAt: createdAt, + adjustmentTime: adjustmentTime, + latitude: latitude, + longitude: longitude, + ), + createCompanionCallback: + ({ + required String assetId, + i0.Value cloudId = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + }) => i1.RemoteAssetCloudIdEntityCompanion.insert( + assetId: assetId, + cloudId: cloudId, + createdAt: createdAt, + adjustmentTime: adjustmentTime, + latitude: latitude, + longitude: longitude, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$RemoteAssetCloudIdEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({assetId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (assetId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.assetId, + referencedTable: i1 + .$$RemoteAssetCloudIdEntityTableReferences + ._assetIdTable(db), + referencedColumn: i1 + .$$RemoteAssetCloudIdEntityTableReferences + ._assetIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$RemoteAssetCloudIdEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$RemoteAssetCloudIdEntityTable, + i1.RemoteAssetCloudIdEntityData, + i1.$$RemoteAssetCloudIdEntityTableFilterComposer, + i1.$$RemoteAssetCloudIdEntityTableOrderingComposer, + i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer, + $$RemoteAssetCloudIdEntityTableCreateCompanionBuilder, + $$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder, + ( + i1.RemoteAssetCloudIdEntityData, + i1.$$RemoteAssetCloudIdEntityTableReferences, + ), + i1.RemoteAssetCloudIdEntityData, + i0.PrefetchHooks Function({bool assetId}) + >; +i0.Index get idxRemoteAssetCloudId => i0.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', +); + +class $RemoteAssetCloudIdEntityTable extends i2.RemoteAssetCloudIdEntity + with + i0.TableInfo< + $RemoteAssetCloudIdEntityTable, + i1.RemoteAssetCloudIdEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $RemoteAssetCloudIdEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta( + 'assetId', + ); + @override + late final i0.GeneratedColumn assetId = i0.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _cloudIdMeta = const i0.VerificationMeta( + 'cloudId', + ); + @override + late final i0.GeneratedColumn cloudId = i0.GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _adjustmentTimeMeta = + const i0.VerificationMeta('adjustmentTime'); + @override + late final i0.GeneratedColumn adjustmentTime = + i0.GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _latitudeMeta = const i0.VerificationMeta( + 'latitude', + ); + @override + late final i0.GeneratedColumn latitude = i0.GeneratedColumn( + 'latitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _longitudeMeta = const i0.VerificationMeta( + 'longitude', + ); + @override + late final i0.GeneratedColumn longitude = i0.GeneratedColumn( + 'longitude', + aliasedName, + true, + type: i0.DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('asset_id')) { + context.handle( + _assetIdMeta, + assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta), + ); + } else if (isInserting) { + context.missing(_assetIdMeta); + } + if (data.containsKey('cloud_id')) { + context.handle( + _cloudIdMeta, + cloudId.isAcceptableOrUnknown(data['cloud_id']!, _cloudIdMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('adjustment_time')) { + context.handle( + _adjustmentTimeMeta, + adjustmentTime.isAcceptableOrUnknown( + data['adjustment_time']!, + _adjustmentTimeMeta, + ), + ); + } + if (data.containsKey('latitude')) { + context.handle( + _latitudeMeta, + latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta), + ); + } + if (data.containsKey('longitude')) { + context.handle( + _longitudeMeta, + longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {assetId}; + @override + i1.RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + i0.DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + $RemoteAssetCloudIdEntityTable createAlias(String alias) { + return $RemoteAssetCloudIdEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends i0.DataClass + implements i0.Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = i0.Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = i0.Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = i0.Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = i0.Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = i0.Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = i0.Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + i1.RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + i0.Value cloudId = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value adjustmentTime = const i0.Value.absent(), + i0.Value latitude = const i0.Value.absent(), + i0.Value longitude = const i0.Value.absent(), + }) => i1.RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + i1.RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends i0.UpdateCompanion { + final i0.Value assetId; + final i0.Value cloudId; + final i0.Value createdAt; + final i0.Value adjustmentTime; + final i0.Value latitude; + final i0.Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const i0.Value.absent(), + this.cloudId = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.adjustmentTime = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.adjustmentTime = const i0.Value.absent(), + this.latitude = const i0.Value.absent(), + this.longitude = const i0.Value.absent(), + }) : assetId = i0.Value(assetId); + static i0.Insertable custom({ + i0.Expression? assetId, + i0.Expression? cloudId, + i0.Expression? createdAt, + i0.Expression? adjustmentTime, + i0.Expression? latitude, + i0.Expression? longitude, + }) { + return i0.RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + i1.RemoteAssetCloudIdEntityCompanion copyWith({ + i0.Value? assetId, + i0.Value? cloudId, + i0.Value? createdAt, + i0.Value? adjustmentTime, + i0.Value? latitude, + i0.Value? longitude, + }) { + return i1.RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = i0.Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = i0.Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = i0.Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = i0.Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = i0.Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/settings.entity.dart b/mobile/lib/infrastructure/entities/settings.entity.dart deleted file mode 100644 index a246c03f5d..0000000000 --- a/mobile/lib/infrastructure/entities/settings.entity.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; - -class SettingsEntity extends Table with DriftDefaultsMixin { - const SettingsEntity(); - - TextColumn get key => text()(); - - TextColumn get value => text().nullable()(); - - DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); - - @override - Set get primaryKey => {key}; - - @override - String get tableName => "settings"; -} diff --git a/mobile/lib/infrastructure/entities/stack.entity.drift.dart b/mobile/lib/infrastructure/entities/stack.entity.drift.dart new file mode 100644 index 0000000000..55017f8344 --- /dev/null +++ b/mobile/lib/infrastructure/entities/stack.entity.drift.dart @@ -0,0 +1,717 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/stack.entity.dart' as i2; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' + as i4; +import 'package:drift/internal/modular.dart' as i5; + +typedef $$StackEntityTableCreateCompanionBuilder = + i1.StackEntityCompanion Function({ + required String id, + i0.Value createdAt, + i0.Value updatedAt, + required String ownerId, + required String primaryAssetId, + }); +typedef $$StackEntityTableUpdateCompanionBuilder = + i1.StackEntityCompanion Function({ + i0.Value id, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value ownerId, + i0.Value primaryAssetId, + }); + +final class $$StackEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$StackEntityTable, + i1.StackEntityData + > { + $$StackEntityTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static i4.$UserEntityTable _ownerIdTable(i0.GeneratedDatabase db) => + i5.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i5.ReadDatabaseContainer( + db, + ).resultSet('stack_entity').ownerId, + i5.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i4.$$UserEntityTableProcessedTableManager get ownerId { + final $_column = $_itemColumn('owner_id')!; + + final manager = i4 + .$$UserEntityTableTableManager( + $_db, + i5.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_ownerIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$StackEntityTableFilterComposer + extends i0.Composer { + $$StackEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get primaryAssetId => $composableBuilder( + column: $table.primaryAssetId, + builder: (column) => i0.ColumnFilters(column), + ); + + i4.$$UserEntityTableFilterComposer get ownerId { + final i4.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableFilterComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StackEntityTableOrderingComposer + extends i0.Composer { + $$StackEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get primaryAssetId => $composableBuilder( + column: $table.primaryAssetId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i4.$$UserEntityTableOrderingComposer get ownerId { + final i4.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StackEntityTableAnnotationComposer + extends i0.Composer { + $$StackEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get primaryAssetId => $composableBuilder( + column: $table.primaryAssetId, + builder: (column) => column, + ); + + i4.$$UserEntityTableAnnotationComposer get ownerId { + final i4.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.ownerId, + referencedTable: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i4.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i5.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$StackEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$StackEntityTable, + i1.StackEntityData, + i1.$$StackEntityTableFilterComposer, + i1.$$StackEntityTableOrderingComposer, + i1.$$StackEntityTableAnnotationComposer, + $$StackEntityTableCreateCompanionBuilder, + $$StackEntityTableUpdateCompanionBuilder, + (i1.StackEntityData, i1.$$StackEntityTableReferences), + i1.StackEntityData, + i0.PrefetchHooks Function({bool ownerId}) + > { + $$StackEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$StackEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$StackEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$StackEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$StackEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value ownerId = const i0.Value.absent(), + i0.Value primaryAssetId = const i0.Value.absent(), + }) => i1.StackEntityCompanion( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + primaryAssetId: primaryAssetId, + ), + createCompanionCallback: + ({ + required String id, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + required String ownerId, + required String primaryAssetId, + }) => i1.StackEntityCompanion.insert( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + primaryAssetId: primaryAssetId, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$StackEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({ownerId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (ownerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.ownerId, + referencedTable: i1.$$StackEntityTableReferences + ._ownerIdTable(db), + referencedColumn: i1 + .$$StackEntityTableReferences + ._ownerIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$StackEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$StackEntityTable, + i1.StackEntityData, + i1.$$StackEntityTableFilterComposer, + i1.$$StackEntityTableOrderingComposer, + i1.$$StackEntityTableAnnotationComposer, + $$StackEntityTableCreateCompanionBuilder, + $$StackEntityTableUpdateCompanionBuilder, + (i1.StackEntityData, i1.$$StackEntityTableReferences), + i1.StackEntityData, + i0.PrefetchHooks Function({bool ownerId}) + >; +i0.Index get idxStackPrimaryAssetId => i0.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', +); + +class $StackEntityTable extends i2.StackEntity + with i0.TableInfo<$StackEntityTable, i1.StackEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $StackEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i3.currentDateAndTime, + ); + static const i0.VerificationMeta _ownerIdMeta = const i0.VerificationMeta( + 'ownerId', + ); + @override + late final i0.GeneratedColumn ownerId = i0.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + static const i0.VerificationMeta _primaryAssetIdMeta = + const i0.VerificationMeta('primaryAssetId'); + @override + late final i0.GeneratedColumn primaryAssetId = + i0.GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('owner_id')) { + context.handle( + _ownerIdMeta, + ownerId.isAcceptableOrUnknown(data['owner_id']!, _ownerIdMeta), + ); + } else if (isInserting) { + context.missing(_ownerIdMeta); + } + if (data.containsKey('primary_asset_id')) { + context.handle( + _primaryAssetIdMeta, + primaryAssetId.isAcceptableOrUnknown( + data['primary_asset_id']!, + _primaryAssetIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_primaryAssetIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.StackEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + $StackEntityTable createAlias(String alias) { + return $StackEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + map['owner_id'] = i0.Variable(ownerId); + map['primary_asset_id'] = i0.Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + i1.StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => i1.StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(i1.StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value ownerId; + final i0.Value primaryAssetId; + const StackEntityCompanion({ + this.id = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.ownerId = const i0.Value.absent(), + this.primaryAssetId = const i0.Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = i0.Value(id), + ownerId = i0.Value(ownerId), + primaryAssetId = i0.Value(primaryAssetId); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? ownerId, + i0.Expression? primaryAssetId, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + i1.StackEntityCompanion copyWith({ + i0.Value? id, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? ownerId, + i0.Value? primaryAssetId, + }) { + return i1.StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = i0.Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = i0.Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/store.entity.dart b/mobile/lib/infrastructure/entities/store.entity.dart index 2de8eb713e..d4b3eec84f 100644 --- a/mobile/lib/infrastructure/entities/store.entity.dart +++ b/mobile/lib/infrastructure/entities/store.entity.dart @@ -1,5 +1,18 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; +import 'package:isar/isar.dart'; + +part 'store.entity.g.dart'; + +/// Internal class for `Store`, do not use elsewhere. +@Collection(inheritance: false) +class StoreValue { + final Id id; + final int? intValue; + final String? strValue; + + const StoreValue(this.id, {this.intValue, this.strValue}); +} class StoreEntity extends Table with DriftDefaultsMixin { IntColumn get id => integer()(); diff --git a/mobile/lib/infrastructure/entities/store.entity.drift.dart b/mobile/lib/infrastructure/entities/store.entity.drift.dart new file mode 100644 index 0000000000..327b0e95d9 --- /dev/null +++ b/mobile/lib/infrastructure/entities/store.entity.drift.dart @@ -0,0 +1,426 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' + as i1; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart' as i2; + +typedef $$StoreEntityTableCreateCompanionBuilder = + i1.StoreEntityCompanion Function({ + required int id, + i0.Value stringValue, + i0.Value intValue, + }); +typedef $$StoreEntityTableUpdateCompanionBuilder = + i1.StoreEntityCompanion Function({ + i0.Value id, + i0.Value stringValue, + i0.Value intValue, + }); + +class $$StoreEntityTableFilterComposer + extends i0.Composer { + $$StoreEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get stringValue => $composableBuilder( + column: $table.stringValue, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get intValue => $composableBuilder( + column: $table.intValue, + builder: (column) => i0.ColumnFilters(column), + ); +} + +class $$StoreEntityTableOrderingComposer + extends i0.Composer { + $$StoreEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get stringValue => $composableBuilder( + column: $table.stringValue, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get intValue => $composableBuilder( + column: $table.intValue, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$StoreEntityTableAnnotationComposer + extends i0.Composer { + $$StoreEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get stringValue => $composableBuilder( + column: $table.stringValue, + builder: (column) => column, + ); + + i0.GeneratedColumn get intValue => + $composableBuilder(column: $table.intValue, builder: (column) => column); +} + +class $$StoreEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$StoreEntityTable, + i1.StoreEntityData, + i1.$$StoreEntityTableFilterComposer, + i1.$$StoreEntityTableOrderingComposer, + i1.$$StoreEntityTableAnnotationComposer, + $$StoreEntityTableCreateCompanionBuilder, + $$StoreEntityTableUpdateCompanionBuilder, + ( + i1.StoreEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$StoreEntityTable, + i1.StoreEntityData + >, + ), + i1.StoreEntityData, + i0.PrefetchHooks Function() + > { + $$StoreEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$StoreEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$StoreEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$StoreEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$StoreEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value stringValue = const i0.Value.absent(), + i0.Value intValue = const i0.Value.absent(), + }) => i1.StoreEntityCompanion( + id: id, + stringValue: stringValue, + intValue: intValue, + ), + createCompanionCallback: + ({ + required int id, + i0.Value stringValue = const i0.Value.absent(), + i0.Value intValue = const i0.Value.absent(), + }) => i1.StoreEntityCompanion.insert( + id: id, + stringValue: stringValue, + intValue: intValue, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$StoreEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$StoreEntityTable, + i1.StoreEntityData, + i1.$$StoreEntityTableFilterComposer, + i1.$$StoreEntityTableOrderingComposer, + i1.$$StoreEntityTableAnnotationComposer, + $$StoreEntityTableCreateCompanionBuilder, + $$StoreEntityTableUpdateCompanionBuilder, + ( + i1.StoreEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$StoreEntityTable, + i1.StoreEntityData + >, + ), + i1.StoreEntityData, + i0.PrefetchHooks Function() + >; + +class $StoreEntityTable extends i2.StoreEntity + with i0.TableInfo<$StoreEntityTable, i1.StoreEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $StoreEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _stringValueMeta = const i0.VerificationMeta( + 'stringValue', + ); + @override + late final i0.GeneratedColumn stringValue = + i0.GeneratedColumn( + 'string_value', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _intValueMeta = const i0.VerificationMeta( + 'intValue', + ); + @override + late final i0.GeneratedColumn intValue = i0.GeneratedColumn( + 'int_value', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('string_value')) { + context.handle( + _stringValueMeta, + stringValue.isAcceptableOrUnknown( + data['string_value']!, + _stringValueMeta, + ), + ); + } + if (data.containsKey('int_value')) { + context.handle( + _intValueMeta, + intValue.isAcceptableOrUnknown(data['int_value']!, _intValueMeta), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.StoreEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + $StoreEntityTable createAlias(String alias) { + return $StoreEntityTable(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends i0.DataClass + implements i0.Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = i0.Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = i0.Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + i1.StoreEntityData copyWith({ + int? id, + i0.Value stringValue = const i0.Value.absent(), + i0.Value intValue = const i0.Value.absent(), + }) => i1.StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(i1.StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value stringValue; + final i0.Value intValue; + const StoreEntityCompanion({ + this.id = const i0.Value.absent(), + this.stringValue = const i0.Value.absent(), + this.intValue = const i0.Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const i0.Value.absent(), + this.intValue = const i0.Value.absent(), + }) : id = i0.Value(id); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? stringValue, + i0.Expression? intValue, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + i1.StoreEntityCompanion copyWith({ + i0.Value? id, + i0.Value? stringValue, + i0.Value? intValue, + }) { + return i1.StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = i0.Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = i0.Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/store.entity.g.dart b/mobile/lib/infrastructure/entities/store.entity.g.dart new file mode 100644 index 0000000000..626c3084fe --- /dev/null +++ b/mobile/lib/infrastructure/entities/store.entity.g.dart @@ -0,0 +1,596 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'store.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetStoreValueCollection on Isar { + IsarCollection get storeValues => this.collection(); +} + +const StoreValueSchema = CollectionSchema( + name: r'StoreValue', + id: 902899285492123510, + properties: { + r'intValue': PropertySchema(id: 0, name: r'intValue', type: IsarType.long), + r'strValue': PropertySchema( + id: 1, + name: r'strValue', + type: IsarType.string, + ), + }, + + estimateSize: _storeValueEstimateSize, + serialize: _storeValueSerialize, + deserialize: _storeValueDeserialize, + deserializeProp: _storeValueDeserializeProp, + idName: r'id', + indexes: {}, + links: {}, + embeddedSchemas: {}, + + getId: _storeValueGetId, + getLinks: _storeValueGetLinks, + attach: _storeValueAttach, + version: '3.3.0-dev.3', +); + +int _storeValueEstimateSize( + StoreValue object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.strValue; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _storeValueSerialize( + StoreValue object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.intValue); + writer.writeString(offsets[1], object.strValue); +} + +StoreValue _storeValueDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = StoreValue( + id, + intValue: reader.readLongOrNull(offsets[0]), + strValue: reader.readStringOrNull(offsets[1]), + ); + return object; +} + +P _storeValueDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLongOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _storeValueGetId(StoreValue object) { + return object.id; +} + +List> _storeValueGetLinks(StoreValue object) { + return []; +} + +void _storeValueAttach(IsarCollection col, Id id, StoreValue object) {} + +extension StoreValueQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension StoreValueQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension StoreValueQueryFilter + on QueryBuilder { + QueryBuilder idEqualTo( + Id value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder intValueIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'intValue'), + ); + }); + } + + QueryBuilder + intValueIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'intValue'), + ); + }); + } + + QueryBuilder intValueEqualTo( + int? value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'intValue', value: value), + ); + }); + } + + QueryBuilder + intValueGreaterThan(int? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'intValue', + value: value, + ), + ); + }); + } + + QueryBuilder intValueLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'intValue', + value: value, + ), + ); + }); + } + + QueryBuilder intValueBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'intValue', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder strValueIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'strValue'), + ); + }); + } + + QueryBuilder + strValueIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'strValue'), + ); + }); + } + + QueryBuilder strValueEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + strValueGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder strValueLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder strValueBetween( + 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'strValue', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + strValueStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder strValueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder strValueContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'strValue', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder strValueMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'strValue', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + strValueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'strValue', value: ''), + ); + }); + } + + QueryBuilder + strValueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'strValue', value: ''), + ); + }); + } +} + +extension StoreValueQueryObject + on QueryBuilder {} + +extension StoreValueQueryLinks + on QueryBuilder {} + +extension StoreValueQuerySortBy + on QueryBuilder { + QueryBuilder sortByIntValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'intValue', Sort.asc); + }); + } + + QueryBuilder sortByIntValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'intValue', Sort.desc); + }); + } + + QueryBuilder sortByStrValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'strValue', Sort.asc); + }); + } + + QueryBuilder sortByStrValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'strValue', Sort.desc); + }); + } +} + +extension StoreValueQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIntValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'intValue', Sort.asc); + }); + } + + QueryBuilder thenByIntValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'intValue', Sort.desc); + }); + } + + QueryBuilder thenByStrValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'strValue', Sort.asc); + }); + } + + QueryBuilder thenByStrValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'strValue', Sort.desc); + }); + } +} + +extension StoreValueQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByIntValue() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'intValue'); + }); + } + + QueryBuilder distinctByStrValue({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'strValue', caseSensitive: caseSensitive); + }); + } +} + +extension StoreValueQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder intValueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'intValue'); + }); + } + + QueryBuilder strValueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'strValue'); + }); + } +} diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart index 6bcff5d591..4a8a374f20 100644 --- a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart +++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.dart @@ -42,7 +42,7 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD type: type, createdAt: createdAt, updatedAt: updatedAt, - durationMs: durationMs, + durationInSeconds: durationInSeconds, isFavorite: isFavorite, height: height, width: width, diff --git a/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart new file mode 100644 index 0000000000..84be6289b8 --- /dev/null +++ b/mobile/lib/infrastructure/entities/trashed_local_asset.entity.drift.dart @@ -0,0 +1,1246 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart' + as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder = + i1.TrashedLocalAssetEntityCompanion Function({ + required String name, + required i2.AssetType type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + required String id, + required String albumId, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + required i3.TrashOrigin source, + i0.Value playbackStyle, + }); +typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder = + i1.TrashedLocalAssetEntityCompanion Function({ + i0.Value name, + i0.Value type, + i0.Value createdAt, + i0.Value updatedAt, + i0.Value width, + i0.Value height, + i0.Value durationInSeconds, + i0.Value id, + i0.Value albumId, + i0.Value checksum, + i0.Value isFavorite, + i0.Value orientation, + i0.Value source, + i0.Value playbackStyle, + }); + +class $$TrashedLocalAssetEntityTableFilterComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters get type => + $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get albumId => $composableBuilder( + column: $table.albumId, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get source => $composableBuilder( + column: $table.source, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnWithTypeConverterFilters< + i2.AssetPlaybackStyle, + i2.AssetPlaybackStyle, + int + > + get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); +} + +class $$TrashedLocalAssetEntityTableOrderingComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get type => $composableBuilder( + column: $table.type, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get updatedAt => $composableBuilder( + column: $table.updatedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get width => $composableBuilder( + column: $table.width, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get height => $composableBuilder( + column: $table.height, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get albumId => $composableBuilder( + column: $table.albumId, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get checksum => $composableBuilder( + column: $table.checksum, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get source => $composableBuilder( + column: $table.source, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$TrashedLocalAssetEntityTableAnnotationComposer + extends + i0.Composer { + $$TrashedLocalAssetEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter get type => + $composableBuilder(column: $table.type, builder: (column) => column); + + i0.GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + i0.GeneratedColumn get updatedAt => + $composableBuilder(column: $table.updatedAt, builder: (column) => column); + + i0.GeneratedColumn get width => + $composableBuilder(column: $table.width, builder: (column) => column); + + i0.GeneratedColumn get height => + $composableBuilder(column: $table.height, builder: (column) => column); + + i0.GeneratedColumn get durationInSeconds => $composableBuilder( + column: $table.durationInSeconds, + builder: (column) => column, + ); + + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get albumId => + $composableBuilder(column: $table.albumId, builder: (column) => column); + + i0.GeneratedColumn get checksum => + $composableBuilder(column: $table.checksum, builder: (column) => column); + + i0.GeneratedColumn get isFavorite => $composableBuilder( + column: $table.isFavorite, + builder: (column) => column, + ); + + i0.GeneratedColumn get orientation => $composableBuilder( + column: $table.orientation, + builder: (column) => column, + ); + + i0.GeneratedColumnWithTypeConverter get source => + $composableBuilder(column: $table.source, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter + get playbackStyle => $composableBuilder( + column: $table.playbackStyle, + builder: (column) => column, + ); +} + +class $$TrashedLocalAssetEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData, + i1.$$TrashedLocalAssetEntityTableFilterComposer, + i1.$$TrashedLocalAssetEntityTableOrderingComposer, + i1.$$TrashedLocalAssetEntityTableAnnotationComposer, + $$TrashedLocalAssetEntityTableCreateCompanionBuilder, + $$TrashedLocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.TrashedLocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + >, + ), + i1.TrashedLocalAssetEntityData, + i0.PrefetchHooks Function() + > { + $$TrashedLocalAssetEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$TrashedLocalAssetEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$TrashedLocalAssetEntityTableFilterComposer( + $db: db, + $table: table, + ), + createOrderingComposer: () => + i1.$$TrashedLocalAssetEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$TrashedLocalAssetEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value name = const i0.Value.absent(), + i0.Value type = const i0.Value.absent(), + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + i0.Value id = const i0.Value.absent(), + i0.Value albumId = const i0.Value.absent(), + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + i0.Value source = const i0.Value.absent(), + i0.Value playbackStyle = + const i0.Value.absent(), + }) => i1.TrashedLocalAssetEntityCompanion( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + albumId: albumId, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + source: source, + playbackStyle: playbackStyle, + ), + createCompanionCallback: + ({ + required String name, + required i2.AssetType type, + i0.Value createdAt = const i0.Value.absent(), + i0.Value updatedAt = const i0.Value.absent(), + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + required String id, + required String albumId, + i0.Value checksum = const i0.Value.absent(), + i0.Value isFavorite = const i0.Value.absent(), + i0.Value orientation = const i0.Value.absent(), + required i3.TrashOrigin source, + i0.Value playbackStyle = + const i0.Value.absent(), + }) => i1.TrashedLocalAssetEntityCompanion.insert( + name: name, + type: type, + createdAt: createdAt, + updatedAt: updatedAt, + width: width, + height: height, + durationInSeconds: durationInSeconds, + id: id, + albumId: albumId, + checksum: checksum, + isFavorite: isFavorite, + orientation: orientation, + source: source, + playbackStyle: playbackStyle, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$TrashedLocalAssetEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData, + i1.$$TrashedLocalAssetEntityTableFilterComposer, + i1.$$TrashedLocalAssetEntityTableOrderingComposer, + i1.$$TrashedLocalAssetEntityTableAnnotationComposer, + $$TrashedLocalAssetEntityTableCreateCompanionBuilder, + $$TrashedLocalAssetEntityTableUpdateCompanionBuilder, + ( + i1.TrashedLocalAssetEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + >, + ), + i1.TrashedLocalAssetEntityData, + i0.PrefetchHooks Function() + >; +i0.Index get idxTrashedLocalAssetChecksum => i0.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', +); + +class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity + with + i0.TableInfo< + $TrashedLocalAssetEntityTable, + i1.TrashedLocalAssetEntityData + > { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $TrashedLocalAssetEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + late final i0.GeneratedColumnWithTypeConverter type = + i0.GeneratedColumn( + 'type', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$TrashedLocalAssetEntityTable.$convertertype, + ); + static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta( + 'createdAt', + ); + @override + late final i0.GeneratedColumn createdAt = + i0.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta( + 'updatedAt', + ); + @override + late final i0.GeneratedColumn updatedAt = + i0.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + static const i0.VerificationMeta _widthMeta = const i0.VerificationMeta( + 'width', + ); + @override + late final i0.GeneratedColumn width = i0.GeneratedColumn( + 'width', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _heightMeta = const i0.VerificationMeta( + 'height', + ); + @override + late final i0.GeneratedColumn height = i0.GeneratedColumn( + 'height', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _durationInSecondsMeta = + const i0.VerificationMeta('durationInSeconds'); + @override + late final i0.GeneratedColumn durationInSeconds = + i0.GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _albumIdMeta = const i0.VerificationMeta( + 'albumId', + ); + @override + late final i0.GeneratedColumn albumId = i0.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _checksumMeta = const i0.VerificationMeta( + 'checksum', + ); + @override + late final i0.GeneratedColumn checksum = i0.GeneratedColumn( + 'checksum', + aliasedName, + true, + type: i0.DriftSqlType.string, + requiredDuringInsert: false, + ); + static const i0.VerificationMeta _isFavoriteMeta = const i0.VerificationMeta( + 'isFavorite', + ); + @override + late final i0.GeneratedColumn isFavorite = i0.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _orientationMeta = const i0.VerificationMeta( + 'orientation', + ); + @override + late final i0.GeneratedColumn orientation = i0.GeneratedColumn( + 'orientation', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ); + @override + late final i0.GeneratedColumnWithTypeConverter source = + i0.GeneratedColumn( + 'source', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$TrashedLocalAssetEntityTable.$convertersource, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + playbackStyle = + i0.GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ).withConverter( + i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } + if (data.containsKey('updated_at')) { + context.handle( + _updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta), + ); + } + if (data.containsKey('width')) { + context.handle( + _widthMeta, + width.isAcceptableOrUnknown(data['width']!, _widthMeta), + ); + } + if (data.containsKey('height')) { + context.handle( + _heightMeta, + height.isAcceptableOrUnknown(data['height']!, _heightMeta), + ); + } + if (data.containsKey('duration_in_seconds')) { + context.handle( + _durationInSecondsMeta, + durationInSeconds.isAcceptableOrUnknown( + data['duration_in_seconds']!, + _durationInSecondsMeta, + ), + ); + } + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('album_id')) { + context.handle( + _albumIdMeta, + albumId.isAcceptableOrUnknown(data['album_id']!, _albumIdMeta), + ); + } else if (isInserting) { + context.missing(_albumIdMeta); + } + if (data.containsKey('checksum')) { + context.handle( + _checksumMeta, + checksum.isAcceptableOrUnknown(data['checksum']!, _checksumMeta), + ); + } + if (data.containsKey('is_favorite')) { + context.handle( + _isFavoriteMeta, + isFavorite.isAcceptableOrUnknown(data['is_favorite']!, _isFavoriteMeta), + ); + } + if (data.containsKey('orientation')) { + context.handle( + _orientationMeta, + orientation.isAcceptableOrUnknown( + data['orientation']!, + _orientationMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id, albumId}; + @override + i1.TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + ), + createdAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ), + playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle + .fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ), + ); + } + + @override + $TrashedLocalAssetEntityTable createAlias(String alias) { + return $TrashedLocalAssetEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $convertertype = + const i0.EnumIndexConverter(i2.AssetType.values); + static i0.JsonTypeConverter2 $convertersource = + const i0.EnumIndexConverter(i3.TrashOrigin.values); + static i0.JsonTypeConverter2 + $converterplaybackStyle = const i0.EnumIndexConverter( + i2.AssetPlaybackStyle.values, + ); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends i0.DataClass + implements i0.Insertable { + final String name; + final i2.AssetType type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final i3.TrashOrigin source; + final i2.AssetPlaybackStyle playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = i0.Variable(name); + { + map['type'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type), + ); + } + map['created_at'] = i0.Variable(createdAt); + map['updated_at'] = i0.Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = i0.Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = i0.Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds); + } + map['id'] = i0.Variable(id); + map['album_id'] = i0.Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = i0.Variable(checksum); + } + map['is_favorite'] = i0.Variable(isFavorite); + map['orientation'] = i0.Variable(orientation); + { + map['source'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source), + ); + } + { + map['playback_style'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql( + playbackStyle, + ), + ); + } + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: i1.$TrashedLocalAssetEntityTable.$convertertype.fromJson( + serializer.fromJson(json['type']), + ), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson( + serializer.fromJson(json['source']), + ), + playbackStyle: i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle + .fromJson(serializer.fromJson(json['playbackStyle'])), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson( + i1.$TrashedLocalAssetEntityTable.$convertertype.toJson(type), + ), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson( + i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source), + ), + 'playbackStyle': serializer.toJson( + i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toJson( + playbackStyle, + ), + ), + }; + } + + i1.TrashedLocalAssetEntityData copyWith({ + String? name, + i2.AssetType? type, + DateTime? createdAt, + DateTime? updatedAt, + i0.Value width = const i0.Value.absent(), + i0.Value height = const i0.Value.absent(), + i0.Value durationInSeconds = const i0.Value.absent(), + String? id, + String? albumId, + i0.Value checksum = const i0.Value.absent(), + bool? isFavorite, + int? orientation, + i3.TrashOrigin? source, + i2.AssetPlaybackStyle? playbackStyle, + }) => i1.TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + i1.TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends i0.UpdateCompanion { + final i0.Value name; + final i0.Value type; + final i0.Value createdAt; + final i0.Value updatedAt; + final i0.Value width; + final i0.Value height; + final i0.Value durationInSeconds; + final i0.Value id; + final i0.Value albumId; + final i0.Value checksum; + final i0.Value isFavorite; + final i0.Value orientation; + final i0.Value source; + final i0.Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const i0.Value.absent(), + this.type = const i0.Value.absent(), + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + this.id = const i0.Value.absent(), + this.albumId = const i0.Value.absent(), + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + this.source = const i0.Value.absent(), + this.playbackStyle = const i0.Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required i2.AssetType type, + this.createdAt = const i0.Value.absent(), + this.updatedAt = const i0.Value.absent(), + this.width = const i0.Value.absent(), + this.height = const i0.Value.absent(), + this.durationInSeconds = const i0.Value.absent(), + required String id, + required String albumId, + this.checksum = const i0.Value.absent(), + this.isFavorite = const i0.Value.absent(), + this.orientation = const i0.Value.absent(), + required i3.TrashOrigin source, + this.playbackStyle = const i0.Value.absent(), + }) : name = i0.Value(name), + type = i0.Value(type), + id = i0.Value(id), + albumId = i0.Value(albumId), + source = i0.Value(source); + static i0.Insertable custom({ + i0.Expression? name, + i0.Expression? type, + i0.Expression? createdAt, + i0.Expression? updatedAt, + i0.Expression? width, + i0.Expression? height, + i0.Expression? durationInSeconds, + i0.Expression? id, + i0.Expression? albumId, + i0.Expression? checksum, + i0.Expression? isFavorite, + i0.Expression? orientation, + i0.Expression? source, + i0.Expression? playbackStyle, + }) { + return i0.RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + i1.TrashedLocalAssetEntityCompanion copyWith({ + i0.Value? name, + i0.Value? type, + i0.Value? createdAt, + i0.Value? updatedAt, + i0.Value? width, + i0.Value? height, + i0.Value? durationInSeconds, + i0.Value? id, + i0.Value? albumId, + i0.Value? checksum, + i0.Value? isFavorite, + i0.Value? orientation, + i0.Value? source, + i0.Value? playbackStyle, + }) { + return i1.TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (type.present) { + map['type'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertertype.toSql(type.value), + ); + } + if (createdAt.present) { + map['created_at'] = i0.Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = i0.Variable(updatedAt.value); + } + if (width.present) { + map['width'] = i0.Variable(width.value); + } + if (height.present) { + map['height'] = i0.Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = i0.Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (albumId.present) { + map['album_id'] = i0.Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = i0.Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = i0.Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = i0.Variable(orientation.value); + } + if (source.present) { + map['source'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value), + ); + } + if (playbackStyle.present) { + map['playback_style'] = i0.Variable( + i1.$TrashedLocalAssetEntityTable.$converterplaybackStyle.toSql( + playbackStyle.value, + ), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +i0.Index get idxTrashedLocalAssetAlbum => i0.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', +); diff --git a/mobile/lib/infrastructure/entities/user.entity.dart b/mobile/lib/infrastructure/entities/user.entity.dart index 8d4371672c..667a9d6a59 100644 --- a/mobile/lib/infrastructure/entities/user.entity.dart +++ b/mobile/lib/infrastructure/entities/user.entity.dart @@ -1,6 +1,79 @@ import 'package:drift/drift.dart' hide Index; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +part 'user.entity.g.dart'; + +@Collection(inheritance: false) +class User { + Id get isarId => fastHash(id); + @Index(unique: true, replace: false, type: IndexType.hash) + final String id; + final DateTime updatedAt; + final String email; + final String name; + final bool isPartnerSharedBy; + final bool isPartnerSharedWith; + final bool isAdmin; + final String profileImagePath; + @Enumerated(EnumType.ordinal) + final AvatarColor avatarColor; + final bool memoryEnabled; + final bool inTimeline; + final int quotaUsageInBytes; + final int quotaSizeInBytes; + + const User({ + required this.id, + required this.updatedAt, + required this.email, + required this.name, + required this.isAdmin, + this.isPartnerSharedBy = false, + this.isPartnerSharedWith = false, + this.profileImagePath = '', + this.avatarColor = AvatarColor.primary, + this.memoryEnabled = true, + this.inTimeline = false, + this.quotaUsageInBytes = 0, + this.quotaSizeInBytes = 0, + }); + + static User fromDto(UserDto dto) => User( + id: dto.id, + updatedAt: dto.updatedAt ?? DateTime(2025), + email: dto.email, + name: dto.name, + isAdmin: dto.isAdmin, + isPartnerSharedBy: dto.isPartnerSharedBy, + isPartnerSharedWith: dto.isPartnerSharedWith, + profileImagePath: dto.hasProfileImage ? "HAS_PROFILE_IMAGE" : "", + avatarColor: dto.avatarColor, + memoryEnabled: dto.memoryEnabled, + inTimeline: dto.inTimeline, + quotaUsageInBytes: dto.quotaUsageInBytes, + quotaSizeInBytes: dto.quotaSizeInBytes, + ); + + UserDto toDto() => UserDto( + id: id, + email: email, + name: name, + isAdmin: isAdmin, + updatedAt: updatedAt, + avatarColor: avatarColor, + memoryEnabled: memoryEnabled, + inTimeline: inTimeline, + isPartnerSharedBy: isPartnerSharedBy, + isPartnerSharedWith: isPartnerSharedWith, + hasProfileImage: profileImagePath.isNotEmpty, + profileChangedAt: updatedAt, + quotaUsageInBytes: quotaUsageInBytes, + quotaSizeInBytes: quotaSizeInBytes, + ); +} class UserEntity extends Table with DriftDefaultsMixin { const UserEntity(); diff --git a/mobile/lib/infrastructure/entities/user.entity.drift.dart b/mobile/lib/infrastructure/entities/user.entity.drift.dart new file mode 100644 index 0000000000..083c14a095 --- /dev/null +++ b/mobile/lib/infrastructure/entities/user.entity.drift.dart @@ -0,0 +1,655 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart' + as i1; +import 'package:immich_mobile/domain/models/user.model.dart' as i2; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as i3; +import 'package:drift/src/runtime/query_builder/query_builder.dart' as i4; + +typedef $$UserEntityTableCreateCompanionBuilder = + i1.UserEntityCompanion Function({ + required String id, + required String name, + required String email, + i0.Value hasProfileImage, + i0.Value profileChangedAt, + i0.Value avatarColor, + }); +typedef $$UserEntityTableUpdateCompanionBuilder = + i1.UserEntityCompanion Function({ + i0.Value id, + i0.Value name, + i0.Value email, + i0.Value hasProfileImage, + i0.Value profileChangedAt, + i0.Value avatarColor, + }); + +class $$UserEntityTableFilterComposer + extends i0.Composer { + $$UserEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get email => $composableBuilder( + column: $table.email, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnFilters get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => i0.ColumnFilters(column), + ); + + i0.ColumnWithTypeConverterFilters + get avatarColor => $composableBuilder( + column: $table.avatarColor, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); +} + +class $$UserEntityTableOrderingComposer + extends i0.Composer { + $$UserEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get email => $composableBuilder( + column: $table.email, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get avatarColor => $composableBuilder( + column: $table.avatarColor, + builder: (column) => i0.ColumnOrderings(column), + ); +} + +class $$UserEntityTableAnnotationComposer + extends i0.Composer { + $$UserEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + i0.GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + i0.GeneratedColumn get email => + $composableBuilder(column: $table.email, builder: (column) => column); + + i0.GeneratedColumn get hasProfileImage => $composableBuilder( + column: $table.hasProfileImage, + builder: (column) => column, + ); + + i0.GeneratedColumn get profileChangedAt => $composableBuilder( + column: $table.profileChangedAt, + builder: (column) => column, + ); + + i0.GeneratedColumnWithTypeConverter get avatarColor => + $composableBuilder( + column: $table.avatarColor, + builder: (column) => column, + ); +} + +class $$UserEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$UserEntityTable, + i1.UserEntityData, + i1.$$UserEntityTableFilterComposer, + i1.$$UserEntityTableOrderingComposer, + i1.$$UserEntityTableAnnotationComposer, + $$UserEntityTableCreateCompanionBuilder, + $$UserEntityTableUpdateCompanionBuilder, + ( + i1.UserEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$UserEntityTable, + i1.UserEntityData + >, + ), + i1.UserEntityData, + i0.PrefetchHooks Function() + > { + $$UserEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$UserEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + i1.$$UserEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$UserEntityTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + i1.$$UserEntityTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + i0.Value id = const i0.Value.absent(), + i0.Value name = const i0.Value.absent(), + i0.Value email = const i0.Value.absent(), + i0.Value hasProfileImage = const i0.Value.absent(), + i0.Value profileChangedAt = const i0.Value.absent(), + i0.Value avatarColor = const i0.Value.absent(), + }) => i1.UserEntityCompanion( + id: id, + name: name, + email: email, + hasProfileImage: hasProfileImage, + profileChangedAt: profileChangedAt, + avatarColor: avatarColor, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String email, + i0.Value hasProfileImage = const i0.Value.absent(), + i0.Value profileChangedAt = const i0.Value.absent(), + i0.Value avatarColor = const i0.Value.absent(), + }) => i1.UserEntityCompanion.insert( + id: id, + name: name, + email: email, + hasProfileImage: hasProfileImage, + profileChangedAt: profileChangedAt, + avatarColor: avatarColor, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), i0.BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + ), + ); +} + +typedef $$UserEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$UserEntityTable, + i1.UserEntityData, + i1.$$UserEntityTableFilterComposer, + i1.$$UserEntityTableOrderingComposer, + i1.$$UserEntityTableAnnotationComposer, + $$UserEntityTableCreateCompanionBuilder, + $$UserEntityTableUpdateCompanionBuilder, + ( + i1.UserEntityData, + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$UserEntityTable, + i1.UserEntityData + >, + ), + i1.UserEntityData, + i0.PrefetchHooks Function() + >; + +class $UserEntityTable extends i3.UserEntity + with i0.TableInfo<$UserEntityTable, i1.UserEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $UserEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + @override + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _nameMeta = const i0.VerificationMeta( + 'name', + ); + @override + late final i0.GeneratedColumn name = i0.GeneratedColumn( + 'name', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _emailMeta = const i0.VerificationMeta( + 'email', + ); + @override + late final i0.GeneratedColumn email = i0.GeneratedColumn( + 'email', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + ); + static const i0.VerificationMeta _hasProfileImageMeta = + const i0.VerificationMeta('hasProfileImage'); + @override + late final i0.GeneratedColumn hasProfileImage = + i0.GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: i0.DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const i4.Constant(false), + ); + static const i0.VerificationMeta _profileChangedAtMeta = + const i0.VerificationMeta('profileChangedAt'); + @override + late final i0.GeneratedColumn profileChangedAt = + i0.GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: i0.DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: i4.currentDateAndTime, + ); + @override + late final i0.GeneratedColumnWithTypeConverter + avatarColor = i0.GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const i4.Constant(0), + ).withConverter(i1.$UserEntityTable.$converteravatarColor); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, + email.isAcceptableOrUnknown(data['email']!, _emailMeta), + ); + } else if (isInserting) { + context.missing(_emailMeta); + } + if (data.containsKey('has_profile_image')) { + context.handle( + _hasProfileImageMeta, + hasProfileImage.isAcceptableOrUnknown( + data['has_profile_image']!, + _hasProfileImageMeta, + ), + ); + } + if (data.containsKey('profile_changed_at')) { + context.handle( + _profileChangedAtMeta, + profileChangedAt.isAcceptableOrUnknown( + data['profile_changed_at']!, + _profileChangedAtMeta, + ), + ); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.UserEntityData( + id: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + i0.DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + i0.DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: i1.$UserEntityTable.$converteravatarColor.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ), + ); + } + + @override + $UserEntityTable createAlias(String alias) { + return $UserEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converteravatarColor = + const i0.EnumIndexConverter(i2.AvatarColor.values); + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends i0.DataClass + implements i0.Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final i2.AvatarColor avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['name'] = i0.Variable(name); + map['email'] = i0.Variable(email); + map['has_profile_image'] = i0.Variable(hasProfileImage); + map['profile_changed_at'] = i0.Variable(profileChangedAt); + { + map['avatar_color'] = i0.Variable( + i1.$UserEntityTable.$converteravatarColor.toSql(avatarColor), + ); + } + return map; + } + + factory UserEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: i1.$UserEntityTable.$converteravatarColor.fromJson( + serializer.fromJson(json['avatarColor']), + ), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson( + i1.$UserEntityTable.$converteravatarColor.toJson(avatarColor), + ), + }; + } + + i1.UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + i2.AvatarColor? avatarColor, + }) => i1.UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(i1.UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value name; + final i0.Value email; + final i0.Value hasProfileImage; + final i0.Value profileChangedAt; + final i0.Value avatarColor; + const UserEntityCompanion({ + this.id = const i0.Value.absent(), + this.name = const i0.Value.absent(), + this.email = const i0.Value.absent(), + this.hasProfileImage = const i0.Value.absent(), + this.profileChangedAt = const i0.Value.absent(), + this.avatarColor = const i0.Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const i0.Value.absent(), + this.profileChangedAt = const i0.Value.absent(), + this.avatarColor = const i0.Value.absent(), + }) : id = i0.Value(id), + name = i0.Value(name), + email = i0.Value(email); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? name, + i0.Expression? email, + i0.Expression? hasProfileImage, + i0.Expression? profileChangedAt, + i0.Expression? avatarColor, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + i1.UserEntityCompanion copyWith({ + i0.Value? id, + i0.Value? name, + i0.Value? email, + i0.Value? hasProfileImage, + i0.Value? profileChangedAt, + i0.Value? avatarColor, + }) { + return i1.UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (name.present) { + map['name'] = i0.Variable(name.value); + } + if (email.present) { + map['email'] = i0.Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = i0.Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = i0.Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = i0.Variable( + i1.$UserEntityTable.$converteravatarColor.toSql(avatarColor.value), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/entities/user.entity.g.dart b/mobile/lib/infrastructure/entities/user.entity.g.dart new file mode 100644 index 0000000000..7e0af41b77 --- /dev/null +++ b/mobile/lib/infrastructure/entities/user.entity.g.dart @@ -0,0 +1,1854 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.entity.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetUserCollection on Isar { + IsarCollection get users => this.collection(); +} + +const UserSchema = CollectionSchema( + name: r'User', + id: -7838171048429979076, + properties: { + r'avatarColor': PropertySchema( + id: 0, + name: r'avatarColor', + type: IsarType.byte, + enumMap: _UseravatarColorEnumValueMap, + ), + r'email': PropertySchema(id: 1, name: r'email', type: IsarType.string), + r'id': PropertySchema(id: 2, name: r'id', type: IsarType.string), + r'inTimeline': PropertySchema( + id: 3, + name: r'inTimeline', + type: IsarType.bool, + ), + r'isAdmin': PropertySchema(id: 4, name: r'isAdmin', type: IsarType.bool), + r'isPartnerSharedBy': PropertySchema( + id: 5, + name: r'isPartnerSharedBy', + type: IsarType.bool, + ), + r'isPartnerSharedWith': PropertySchema( + id: 6, + name: r'isPartnerSharedWith', + type: IsarType.bool, + ), + r'memoryEnabled': PropertySchema( + id: 7, + name: r'memoryEnabled', + type: IsarType.bool, + ), + r'name': PropertySchema(id: 8, name: r'name', type: IsarType.string), + r'profileImagePath': PropertySchema( + id: 9, + name: r'profileImagePath', + type: IsarType.string, + ), + r'quotaSizeInBytes': PropertySchema( + id: 10, + name: r'quotaSizeInBytes', + type: IsarType.long, + ), + r'quotaUsageInBytes': PropertySchema( + id: 11, + name: r'quotaUsageInBytes', + type: IsarType.long, + ), + r'updatedAt': PropertySchema( + id: 12, + name: r'updatedAt', + type: IsarType.dateTime, + ), + }, + + estimateSize: _userEstimateSize, + serialize: _userSerialize, + deserialize: _userDeserialize, + deserializeProp: _userDeserializeProp, + idName: r'isarId', + indexes: { + r'id': IndexSchema( + id: -3268401673993471357, + name: r'id', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'id', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {}, + + getId: _userGetId, + getLinks: _userGetLinks, + attach: _userAttach, + version: '3.3.0-dev.3', +); + +int _userEstimateSize( + User object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.email.length * 3; + bytesCount += 3 + object.id.length * 3; + bytesCount += 3 + object.name.length * 3; + bytesCount += 3 + object.profileImagePath.length * 3; + return bytesCount; +} + +void _userSerialize( + User object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeByte(offsets[0], object.avatarColor.index); + writer.writeString(offsets[1], object.email); + writer.writeString(offsets[2], object.id); + writer.writeBool(offsets[3], object.inTimeline); + writer.writeBool(offsets[4], object.isAdmin); + writer.writeBool(offsets[5], object.isPartnerSharedBy); + writer.writeBool(offsets[6], object.isPartnerSharedWith); + writer.writeBool(offsets[7], object.memoryEnabled); + writer.writeString(offsets[8], object.name); + writer.writeString(offsets[9], object.profileImagePath); + writer.writeLong(offsets[10], object.quotaSizeInBytes); + writer.writeLong(offsets[11], object.quotaUsageInBytes); + writer.writeDateTime(offsets[12], object.updatedAt); +} + +User _userDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = User( + avatarColor: + _UseravatarColorValueEnumMap[reader.readByteOrNull(offsets[0])] ?? + AvatarColor.primary, + email: reader.readString(offsets[1]), + id: reader.readString(offsets[2]), + inTimeline: reader.readBoolOrNull(offsets[3]) ?? false, + isAdmin: reader.readBool(offsets[4]), + isPartnerSharedBy: reader.readBoolOrNull(offsets[5]) ?? false, + isPartnerSharedWith: reader.readBoolOrNull(offsets[6]) ?? false, + memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true, + name: reader.readString(offsets[8]), + profileImagePath: reader.readStringOrNull(offsets[9]) ?? '', + quotaSizeInBytes: reader.readLongOrNull(offsets[10]) ?? 0, + quotaUsageInBytes: reader.readLongOrNull(offsets[11]) ?? 0, + updatedAt: reader.readDateTime(offsets[12]), + ); + return object; +} + +P _userDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (_UseravatarColorValueEnumMap[reader.readByteOrNull(offset)] ?? + AvatarColor.primary) + as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 4: + return (reader.readBool(offset)) as P; + case 5: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 6: + return (reader.readBoolOrNull(offset) ?? false) as P; + case 7: + return (reader.readBoolOrNull(offset) ?? true) as P; + case 8: + return (reader.readString(offset)) as P; + case 9: + return (reader.readStringOrNull(offset) ?? '') as P; + case 10: + return (reader.readLongOrNull(offset) ?? 0) as P; + case 11: + return (reader.readLongOrNull(offset) ?? 0) as P; + case 12: + return (reader.readDateTime(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _UseravatarColorEnumValueMap = { + 'primary': 0, + 'pink': 1, + 'red': 2, + 'yellow': 3, + 'blue': 4, + 'green': 5, + 'purple': 6, + 'orange': 7, + 'gray': 8, + 'amber': 9, +}; +const _UseravatarColorValueEnumMap = { + 0: AvatarColor.primary, + 1: AvatarColor.pink, + 2: AvatarColor.red, + 3: AvatarColor.yellow, + 4: AvatarColor.blue, + 5: AvatarColor.green, + 6: AvatarColor.purple, + 7: AvatarColor.orange, + 8: AvatarColor.gray, + 9: AvatarColor.amber, +}; + +Id _userGetId(User object) { + return object.isarId; +} + +List> _userGetLinks(User object) { + return []; +} + +void _userAttach(IsarCollection col, Id id, User object) {} + +extension UserByIndex on IsarCollection { + Future getById(String id) { + return getByIndex(r'id', [id]); + } + + User? getByIdSync(String id) { + return getByIndexSync(r'id', [id]); + } + + Future deleteById(String id) { + return deleteByIndex(r'id', [id]); + } + + bool deleteByIdSync(String id) { + return deleteByIndexSync(r'id', [id]); + } + + Future> getAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndex(r'id', values); + } + + List getAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'id', values); + } + + Future deleteAllById(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'id', values); + } + + int deleteAllByIdSync(List idValues) { + final values = idValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'id', values); + } + + Future putById(User object) { + return putByIndex(r'id', object); + } + + Id putByIdSync(User object, {bool saveLinks = true}) { + return putByIndexSync(r'id', object, saveLinks: saveLinks); + } + + Future> putAllById(List objects) { + return putAllByIndex(r'id', objects); + } + + List putAllByIdSync(List objects, {bool saveLinks = true}) { + return putAllByIndexSync(r'id', objects, saveLinks: saveLinks); + } +} + +extension UserQueryWhereSort on QueryBuilder { + QueryBuilder anyIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension UserQueryWhere on QueryBuilder { + QueryBuilder isarIdEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between(lower: isarId, upper: isarId), + ); + }); + } + + QueryBuilder isarIdNotEqualTo(Id isarId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: false), + ); + } + }); + } + + QueryBuilder isarIdGreaterThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: isarId, includeLower: include), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id isarId, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: isarId, includeUpper: include), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lowerIsarId, + Id upperIsarId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerIsarId, + includeLower: includeLower, + upper: upperIsarId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder idEqualTo(String id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'id', value: [id]), + ); + }); + } + + QueryBuilder idNotEqualTo(String id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [id], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'id', + lower: [], + upper: [id], + includeUpper: false, + ), + ); + } + }); + } +} + +extension UserQueryFilter on QueryBuilder { + QueryBuilder avatarColorEqualTo( + AvatarColor value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'avatarColor', value: value), + ); + }); + } + + QueryBuilder avatarColorGreaterThan( + AvatarColor value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'avatarColor', + value: value, + ), + ); + }); + } + + QueryBuilder avatarColorLessThan( + AvatarColor value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'avatarColor', + value: value, + ), + ); + }); + } + + QueryBuilder avatarColorBetween( + AvatarColor lower, + AvatarColor upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'avatarColor', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder emailEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailBetween( + 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'email', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'email', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'email', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder emailIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'email', value: ''), + ); + }); + } + + QueryBuilder emailIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'email', value: ''), + ); + }); + } + + QueryBuilder idEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idBetween( + 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'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'id', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'id', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder idIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: ''), + ); + }); + } + + QueryBuilder idIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'id', value: ''), + ); + }); + } + + QueryBuilder inTimelineEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'inTimeline', value: value), + ); + }); + } + + QueryBuilder isAdminEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isAdmin', value: value), + ); + }); + } + + QueryBuilder isPartnerSharedByEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isPartnerSharedBy', value: value), + ); + }); + } + + QueryBuilder isPartnerSharedWithEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isPartnerSharedWith', value: value), + ); + }); + } + + QueryBuilder isarIdEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isarId', value: value), + ); + }); + } + + QueryBuilder isarIdGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'isarId', + value: value, + ), + ); + }); + } + + QueryBuilder isarIdBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'isarId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder memoryEnabledEqualTo( + bool value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'memoryEnabled', value: value), + ); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameBetween( + 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'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'name', value: ''), + ); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'name', value: ''), + ); + }); + } + + QueryBuilder profileImagePathEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathBetween( + 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'profileImagePath', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathContains( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'profileImagePath', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathMatches( + String pattern, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'profileImagePath', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder profileImagePathIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'profileImagePath', value: ''), + ); + }); + } + + QueryBuilder profileImagePathIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'profileImagePath', value: ''), + ); + }); + } + + QueryBuilder quotaSizeInBytesEqualTo( + int value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'quotaSizeInBytes', value: value), + ); + }); + } + + QueryBuilder quotaSizeInBytesGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'quotaSizeInBytes', + value: value, + ), + ); + }); + } + + QueryBuilder quotaSizeInBytesLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'quotaSizeInBytes', + value: value, + ), + ); + }); + } + + QueryBuilder quotaSizeInBytesBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'quotaSizeInBytes', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder quotaUsageInBytesEqualTo( + int value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'quotaUsageInBytes', value: value), + ); + }); + } + + QueryBuilder quotaUsageInBytesGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'quotaUsageInBytes', + value: value, + ), + ); + }); + } + + QueryBuilder quotaUsageInBytesLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'quotaUsageInBytes', + value: value, + ), + ); + }); + } + + QueryBuilder quotaUsageInBytesBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'quotaUsageInBytes', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder updatedAtEqualTo( + DateTime value, + ) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'updatedAt', value: value), + ); + }); + } + + QueryBuilder updatedAtGreaterThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'updatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder updatedAtLessThan( + DateTime value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'updatedAt', + value: value, + ), + ); + }); + } + + QueryBuilder updatedAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'updatedAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension UserQueryObject on QueryBuilder {} + +extension UserQueryLinks on QueryBuilder {} + +extension UserQuerySortBy on QueryBuilder { + QueryBuilder sortByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.asc); + }); + } + + QueryBuilder sortByAvatarColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.desc); + }); + } + + QueryBuilder sortByEmail() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'email', Sort.asc); + }); + } + + QueryBuilder sortByEmailDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'email', Sort.desc); + }); + } + + QueryBuilder sortById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder sortByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder sortByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder sortByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + + QueryBuilder sortByIsAdmin() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAdmin', Sort.asc); + }); + } + + QueryBuilder sortByIsAdminDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAdmin', Sort.desc); + }); + } + + QueryBuilder sortByIsPartnerSharedBy() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedBy', Sort.asc); + }); + } + + QueryBuilder sortByIsPartnerSharedByDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedBy', Sort.desc); + }); + } + + QueryBuilder sortByIsPartnerSharedWith() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedWith', Sort.asc); + }); + } + + QueryBuilder sortByIsPartnerSharedWithDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedWith', Sort.desc); + }); + } + + QueryBuilder sortByMemoryEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'memoryEnabled', Sort.asc); + }); + } + + QueryBuilder sortByMemoryEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'memoryEnabled', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder sortByProfileImagePath() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'profileImagePath', Sort.asc); + }); + } + + QueryBuilder sortByProfileImagePathDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'profileImagePath', Sort.desc); + }); + } + + QueryBuilder sortByQuotaSizeInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaSizeInBytes', Sort.asc); + }); + } + + QueryBuilder sortByQuotaSizeInBytesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaSizeInBytes', Sort.desc); + }); + } + + QueryBuilder sortByQuotaUsageInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaUsageInBytes', Sort.asc); + }); + } + + QueryBuilder sortByQuotaUsageInBytesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaUsageInBytes', Sort.desc); + }); + } + + QueryBuilder sortByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.asc); + }); + } + + QueryBuilder sortByUpdatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.desc); + }); + } +} + +extension UserQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.asc); + }); + } + + QueryBuilder thenByAvatarColorDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'avatarColor', Sort.desc); + }); + } + + QueryBuilder thenByEmail() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'email', Sort.asc); + }); + } + + QueryBuilder thenByEmailDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'email', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.asc); + }); + } + + QueryBuilder thenByInTimelineDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'inTimeline', Sort.desc); + }); + } + + QueryBuilder thenByIsAdmin() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAdmin', Sort.asc); + }); + } + + QueryBuilder thenByIsAdminDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAdmin', Sort.desc); + }); + } + + QueryBuilder thenByIsPartnerSharedBy() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedBy', Sort.asc); + }); + } + + QueryBuilder thenByIsPartnerSharedByDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedBy', Sort.desc); + }); + } + + QueryBuilder thenByIsPartnerSharedWith() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedWith', Sort.asc); + }); + } + + QueryBuilder thenByIsPartnerSharedWithDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPartnerSharedWith', Sort.desc); + }); + } + + QueryBuilder thenByIsarId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.asc); + }); + } + + QueryBuilder thenByIsarIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isarId', Sort.desc); + }); + } + + QueryBuilder thenByMemoryEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'memoryEnabled', Sort.asc); + }); + } + + QueryBuilder thenByMemoryEnabledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'memoryEnabled', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder thenByProfileImagePath() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'profileImagePath', Sort.asc); + }); + } + + QueryBuilder thenByProfileImagePathDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'profileImagePath', Sort.desc); + }); + } + + QueryBuilder thenByQuotaSizeInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaSizeInBytes', Sort.asc); + }); + } + + QueryBuilder thenByQuotaSizeInBytesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaSizeInBytes', Sort.desc); + }); + } + + QueryBuilder thenByQuotaUsageInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaUsageInBytes', Sort.asc); + }); + } + + QueryBuilder thenByQuotaUsageInBytesDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'quotaUsageInBytes', Sort.desc); + }); + } + + QueryBuilder thenByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.asc); + }); + } + + QueryBuilder thenByUpdatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'updatedAt', Sort.desc); + }); + } +} + +extension UserQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAvatarColor() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'avatarColor'); + }); + } + + QueryBuilder distinctByEmail({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'email', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctById({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'id', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByInTimeline() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'inTimeline'); + }); + } + + QueryBuilder distinctByIsAdmin() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isAdmin'); + }); + } + + QueryBuilder distinctByIsPartnerSharedBy() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isPartnerSharedBy'); + }); + } + + QueryBuilder distinctByIsPartnerSharedWith() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isPartnerSharedWith'); + }); + } + + QueryBuilder distinctByMemoryEnabled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'memoryEnabled'); + }); + } + + QueryBuilder distinctByName({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByProfileImagePath({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'profileImagePath', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder distinctByQuotaSizeInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'quotaSizeInBytes'); + }); + } + + QueryBuilder distinctByQuotaUsageInBytes() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'quotaUsageInBytes'); + }); + } + + QueryBuilder distinctByUpdatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'updatedAt'); + }); + } +} + +extension UserQueryProperty on QueryBuilder { + QueryBuilder isarIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isarId'); + }); + } + + QueryBuilder avatarColorProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'avatarColor'); + }); + } + + QueryBuilder emailProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'email'); + }); + } + + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder inTimelineProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'inTimeline'); + }); + } + + QueryBuilder isAdminProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isAdmin'); + }); + } + + QueryBuilder isPartnerSharedByProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isPartnerSharedBy'); + }); + } + + QueryBuilder isPartnerSharedWithProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isPartnerSharedWith'); + }); + } + + QueryBuilder memoryEnabledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'memoryEnabled'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder profileImagePathProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'profileImagePath'); + }); + } + + QueryBuilder quotaSizeInBytesProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'quotaSizeInBytes'); + }); + } + + QueryBuilder quotaUsageInBytesProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'quotaUsageInBytes'); + }); + } + + QueryBuilder updatedAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'updatedAt'); + }); + } +} diff --git a/mobile/lib/infrastructure/entities/user_metadata.entity.dart b/mobile/lib/infrastructure/entities/user_metadata.entity.dart index da2070fd71..ede3de3966 100644 --- a/mobile/lib/infrastructure/entities/user_metadata.entity.dart +++ b/mobile/lib/infrastructure/entities/user_metadata.entity.dart @@ -17,5 +17,5 @@ class UserMetadataEntity extends Table with DriftDefaultsMixin { } final JsonTypeConverter2, Uint8List, Object?> userMetadataConverter = TypeConverter.jsonb( - fromJson: (json) => json! as Map, + fromJson: (json) => json as Map, ); diff --git a/mobile/lib/infrastructure/entities/user_metadata.entity.drift.dart b/mobile/lib/infrastructure/entities/user_metadata.entity.drift.dart new file mode 100644 index 0000000000..1e9dc8a890 --- /dev/null +++ b/mobile/lib/infrastructure/entities/user_metadata.entity.drift.dart @@ -0,0 +1,624 @@ +// dart format width=80 +// ignore_for_file: type=lint +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 i3; +import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart' + as i4; +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.UserMetadataKey key, + required Map value, + }); +typedef $$UserMetadataEntityTableUpdateCompanionBuilder = + i1.UserMetadataEntityCompanion Function({ + i0.Value userId, + i0.Value key, + i0.Value> value, + }); + +final class $$UserMetadataEntityTableReferences + extends + i0.BaseReferences< + i0.GeneratedDatabase, + i1.$UserMetadataEntityTable, + i1.UserMetadataEntityData + > { + $$UserMetadataEntityTableReferences( + super.$_db, + super.$_table, + super.$_typedResult, + ); + + static i5.$UserEntityTable _userIdTable(i0.GeneratedDatabase db) => + i6.ReadDatabaseContainer(db) + .resultSet('user_entity') + .createAlias( + i0.$_aliasNameGenerator( + i6.ReadDatabaseContainer(db) + .resultSet( + 'user_metadata_entity', + ) + .userId, + i6.ReadDatabaseContainer( + db, + ).resultSet('user_entity').id, + ), + ); + + i5.$$UserEntityTableProcessedTableManager get userId { + final $_column = $_itemColumn('user_id')!; + + final manager = i5 + .$$UserEntityTableTableManager( + $_db, + i6.ReadDatabaseContainer( + $_db, + ).resultSet('user_entity'), + ) + .filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_userIdTable($_db)); + if (item == null) return manager; + return i0.ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$UserMetadataEntityTableFilterComposer + extends i0.Composer { + $$UserMetadataEntityTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnWithTypeConverterFilters + get key => $composableBuilder( + column: $table.key, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i0.ColumnWithTypeConverterFilters< + Map, + Map, + i3.Uint8List + > + get value => $composableBuilder( + column: $table.value, + builder: (column) => i0.ColumnWithTypeConverterFilters(column), + ); + + i5.$$UserEntityTableFilterComposer get userId { + final i5.$$UserEntityTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableFilterComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserMetadataEntityTableOrderingComposer + extends i0.Composer { + $$UserMetadataEntityTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.ColumnOrderings get key => $composableBuilder( + column: $table.key, + builder: (column) => i0.ColumnOrderings(column), + ); + + i0.ColumnOrderings get value => $composableBuilder( + column: $table.value, + builder: (column) => i0.ColumnOrderings(column), + ); + + i5.$$UserEntityTableOrderingComposer get userId { + final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableOrderingComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserMetadataEntityTableAnnotationComposer + extends i0.Composer { + $$UserMetadataEntityTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + i0.GeneratedColumnWithTypeConverter get key => + $composableBuilder(column: $table.key, builder: (column) => column); + + i0.GeneratedColumnWithTypeConverter, i3.Uint8List> + get value => + $composableBuilder(column: $table.value, builder: (column) => column); + + i5.$$UserEntityTableAnnotationComposer get userId { + final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.userId, + referencedTable: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => i5.$$UserEntityTableAnnotationComposer( + $db: $db, + $table: i6.ReadDatabaseContainer( + $db, + ).resultSet('user_entity'), + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$UserMetadataEntityTableTableManager + extends + i0.RootTableManager< + i0.GeneratedDatabase, + i1.$UserMetadataEntityTable, + i1.UserMetadataEntityData, + i1.$$UserMetadataEntityTableFilterComposer, + i1.$$UserMetadataEntityTableOrderingComposer, + i1.$$UserMetadataEntityTableAnnotationComposer, + $$UserMetadataEntityTableCreateCompanionBuilder, + $$UserMetadataEntityTableUpdateCompanionBuilder, + (i1.UserMetadataEntityData, i1.$$UserMetadataEntityTableReferences), + i1.UserMetadataEntityData, + i0.PrefetchHooks Function({bool userId}) + > { + $$UserMetadataEntityTableTableManager( + i0.GeneratedDatabase db, + i1.$UserMetadataEntityTable table, + ) : super( + i0.TableManagerState( + db: db, + table: table, + createFilteringComposer: () => i1 + .$$UserMetadataEntityTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + i1.$$UserMetadataEntityTableOrderingComposer( + $db: db, + $table: table, + ), + createComputedFieldComposer: () => + i1.$$UserMetadataEntityTableAnnotationComposer( + $db: db, + $table: table, + ), + updateCompanionCallback: + ({ + i0.Value userId = const i0.Value.absent(), + i0.Value key = const i0.Value.absent(), + i0.Value> value = const i0.Value.absent(), + }) => i1.UserMetadataEntityCompanion( + userId: userId, + key: key, + value: value, + ), + createCompanionCallback: + ({ + required String userId, + required i2.UserMetadataKey key, + required Map value, + }) => i1.UserMetadataEntityCompanion.insert( + userId: userId, + key: key, + value: value, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + i1.$$UserMetadataEntityTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({userId = false}) { + return i0.PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends i0.TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (userId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.userId, + referencedTable: i1 + .$$UserMetadataEntityTableReferences + ._userIdTable(db), + referencedColumn: i1 + .$$UserMetadataEntityTableReferences + ._userIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$UserMetadataEntityTableProcessedTableManager = + i0.ProcessedTableManager< + i0.GeneratedDatabase, + i1.$UserMetadataEntityTable, + i1.UserMetadataEntityData, + i1.$$UserMetadataEntityTableFilterComposer, + i1.$$UserMetadataEntityTableOrderingComposer, + i1.$$UserMetadataEntityTableAnnotationComposer, + $$UserMetadataEntityTableCreateCompanionBuilder, + $$UserMetadataEntityTableUpdateCompanionBuilder, + (i1.UserMetadataEntityData, i1.$$UserMetadataEntityTableReferences), + i1.UserMetadataEntityData, + i0.PrefetchHooks Function({bool userId}) + >; + +class $UserMetadataEntityTable extends i4.UserMetadataEntity + with i0.TableInfo<$UserMetadataEntityTable, i1.UserMetadataEntityData> { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + $UserMetadataEntityTable(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _userIdMeta = const i0.VerificationMeta( + 'userId', + ); + @override + late final i0.GeneratedColumn userId = i0.GeneratedColumn( + 'user_id', + aliasedName, + false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: i0.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + @override + late final i0.GeneratedColumnWithTypeConverter key = + i0.GeneratedColumn( + 'key', + aliasedName, + false, + type: i0.DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + i1.$UserMetadataEntityTable.$converterkey, + ); + @override + late final i0.GeneratedColumnWithTypeConverter< + Map, + i3.Uint8List + > + value = + i0.GeneratedColumn( + 'value', + aliasedName, + false, + type: i0.DriftSqlType.blob, + requiredDuringInsert: true, + ).withConverter>( + i1.$UserMetadataEntityTable.$convertervalue, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + i0.VerificationContext validateIntegrity( + i0.Insertable instance, { + bool isInserting = false, + }) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle( + _userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta), + ); + } else if (isInserting) { + context.missing(_userIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {userId, key}; + @override + i1.UserMetadataEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + i0.DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: i1.$UserMetadataEntityTable.$converterkey.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + ), + value: i1.$UserMetadataEntityTable.$convertervalue.fromSql( + attachedDatabase.typeMapping.read( + i0.DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ), + ); + } + + @override + $UserMetadataEntityTable createAlias(String alias) { + return $UserMetadataEntityTable(attachedDatabase, alias); + } + + static i0.JsonTypeConverter2 $converterkey = + const i0.EnumIndexConverter( + i2.UserMetadataKey.values, + ); + static i0.JsonTypeConverter2, i3.Uint8List, Object?> + $convertervalue = i4.userMetadataConverter; + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends i0.DataClass + implements i0.Insertable { + final String userId; + final i2.UserMetadataKey key; + final Map value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = i0.Variable(userId); + { + map['key'] = i0.Variable( + i1.$UserMetadataEntityTable.$converterkey.toSql(key), + ); + } + { + map['value'] = i0.Variable( + i1.$UserMetadataEntityTable.$convertervalue.toSql(value), + ); + } + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + i0.ValueSerializer? serializer, + }) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: i1.$UserMetadataEntityTable.$converterkey.fromJson( + serializer.fromJson(json['key']), + ), + value: i1.$UserMetadataEntityTable.$convertervalue.fromJson( + serializer.fromJson(json['value']), + ), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson( + i1.$UserMetadataEntityTable.$converterkey.toJson(key), + ), + 'value': serializer.toJson( + i1.$UserMetadataEntityTable.$convertervalue.toJson(value), + ), + }; + } + + i1.UserMetadataEntityData copyWith({ + String? userId, + i2.UserMetadataKey? key, + Map? value, + }) => i1.UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion( + i1.UserMetadataEntityCompanion data, + ) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, value); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + other.value == this.value); +} + +class UserMetadataEntityCompanion + extends i0.UpdateCompanion { + final i0.Value userId; + final i0.Value key; + final i0.Value> value; + const UserMetadataEntityCompanion({ + this.userId = const i0.Value.absent(), + this.key = const i0.Value.absent(), + this.value = const i0.Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required i2.UserMetadataKey key, + required Map value, + }) : userId = i0.Value(userId), + key = i0.Value(key), + value = i0.Value(value); + static i0.Insertable custom({ + i0.Expression? userId, + i0.Expression? key, + i0.Expression? value, + }) { + return i0.RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + i1.UserMetadataEntityCompanion copyWith({ + i0.Value? userId, + i0.Value? key, + i0.Value>? value, + }) { + return i1.UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = i0.Variable(userId.value); + } + if (key.present) { + map['key'] = i0.Variable( + i1.$UserMetadataEntityTable.$converterkey.toSql(key.value), + ); + } + if (value.present) { + map['value'] = i0.Variable( + i1.$UserMetadataEntityTable.$convertervalue.toSql(value.value), + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} diff --git a/mobile/lib/infrastructure/loaders/image_request.dart b/mobile/lib/infrastructure/loaders/image_request.dart index 6b2be218dc..4df470277e 100644 --- a/mobile/lib/infrastructure/loaders/image_request.dart +++ b/mobile/lib/infrastructure/loaders/image_request.dart @@ -1,17 +1,16 @@ import 'dart:async'; import 'dart:ffi'; -import 'dart:math' as math; import 'dart:ui' as ui; -import 'package:ffi/ffi.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:ffi/ffi.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; part 'local_image_request.dart'; -part 'remote_image_request.dart'; part 'thumbhash_image_request.dart'; +part 'remote_image_request.dart'; abstract class ImageRequest { static int _nextRequestId = 0; @@ -19,7 +18,7 @@ abstract class ImageRequest { final int requestId = _nextRequestId++; bool _isCancelled = false; - bool get isCancelled => _isCancelled; + get isCancelled => _isCancelled; ImageRequest(); @@ -37,11 +36,7 @@ abstract class ImageRequest { void _onCancelled(); - Future<(ui.Codec, ui.ImageDescriptor)?> _codecFromEncodedPlatformImage( - int address, - int length, { - ui.Size? decodeSize, - }) async { + Future<(ui.Codec, ui.ImageDescriptor)?> _codecFromEncodedPlatformImage(int address, int length) async { final pointer = Pointer.fromAddress(address); if (_isCancelled) { malloc.free(pointer); @@ -67,8 +62,7 @@ abstract class ImageRequest { return null; } - final target = _targetSize(descriptor.width, descriptor.height, decodeSize); - final codec = await descriptor.instantiateCodec(targetWidth: target?.$1, targetHeight: target?.$2); + final codec = await descriptor.instantiateCodec(); if (_isCancelled) { descriptor.dispose(); codec.dispose(); @@ -78,11 +72,9 @@ abstract class ImageRequest { return (codec, descriptor); } - Future _fromEncodedPlatformImage(int address, int length, {ui.Size? decodeSize}) async { - final result = await _codecFromEncodedPlatformImage(address, length, decodeSize: decodeSize); - if (result == null) { - return null; - } + Future _fromEncodedPlatformImage(int address, int length) async { + final result = await _codecFromEncodedPlatformImage(address, length); + if (result == null) return null; final (codec, descriptor) = result; if (_isCancelled) { @@ -102,19 +94,6 @@ abstract class ImageRequest { return frame; } - (int, int)? _targetSize(int width, int height, ui.Size? decodeSize) { - if (width <= 0 || height <= 0 || decodeSize == null || decodeSize.width <= 0 || decodeSize.height <= 0) { - return null; - } - - final scale = math.max(decodeSize.width / width, decodeSize.height / height); - if (scale >= 1) { - return null; - } - - return ((width * scale).ceil(), (height * scale).ceil()); - } - Future _fromDecodedPlatformImage(int address, int width, int height, int rowBytes) async { final pointer = Pointer.fromAddress(address); if (_isCancelled) { diff --git a/mobile/lib/infrastructure/loaders/local_image_request.dart b/mobile/lib/infrastructure/loaders/local_image_request.dart index 403e5d34cb..a6c9fa2989 100644 --- a/mobile/lib/infrastructure/loaders/local_image_request.dart +++ b/mobile/lib/infrastructure/loaders/local_image_request.dart @@ -46,9 +46,7 @@ class LocalImageRequest extends ImageRequest { isVideo: assetType == AssetType.video, preferEncoded: true, ); - if (info == null) { - return null; - } + if (info == null) return null; final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null); return codec; diff --git a/mobile/lib/infrastructure/loaders/remote_image_request.dart b/mobile/lib/infrastructure/loaders/remote_image_request.dart index 7b504697ac..bcfa9a93c7 100644 --- a/mobile/lib/infrastructure/loaders/remote_image_request.dart +++ b/mobile/lib/infrastructure/loaders/remote_image_request.dart @@ -3,10 +3,7 @@ part of 'image_request.dart'; class RemoteImageRequest extends ImageRequest { final String uri; - /// Physical size to decode, or null for the source size. - final ui.Size? decodeSize; - - RemoteImageRequest({required this.uri, this.decodeSize}); + RemoteImageRequest({required this.uri}); @override Future load(ImageDecoderCallback decode, {double scale = 1.0}) async { @@ -14,26 +11,11 @@ class RemoteImageRequest extends ImageRequest { return null; } - final info = await remoteImageApi.requestImage( - uri, - requestId: requestId, - preferEncoded: false, - width: decodeSize?.width.ceil(), - height: decodeSize?.height.ceil(), - ); - // Android falls back to encoded data if native decoding fails, so check for both shapes of the response. + final info = await remoteImageApi.requestImage(uri, requestId: requestId, preferEncoded: false); + // Android always returns encoded data, so we need to check for both shapes of the response. final frame = switch (info) { - {'pointer': final int pointer, 'length': final int length} => await _fromEncodedPlatformImage( - pointer, - length, - decodeSize: decodeSize, - ), - { - 'pointer': final int pointer, - 'width': final int width, - 'height': final int height, - 'rowBytes': final int rowBytes, - } => + {'pointer': int pointer, 'length': int length} => await _fromEncodedPlatformImage(pointer, length), + {'pointer': int pointer, 'width': int width, 'height': int height, 'rowBytes': int rowBytes} => await _fromDecodedPlatformImage(pointer, width, height, rowBytes), _ => null, }; @@ -46,16 +28,8 @@ class RemoteImageRequest extends ImageRequest { return null; } - final info = await remoteImageApi.requestImage( - uri, - requestId: requestId, - preferEncoded: true, - width: null, - height: null, - ); - if (info == null) { - return null; - } + final info = await remoteImageApi.requestImage(uri, requestId: requestId, preferEncoded: true); + if (info == null) return null; final (codec, _) = await _codecFromEncodedPlatformImage(info['pointer']!, info['length']!) ?? (null, null); return codec; diff --git a/mobile/lib/infrastructure/mapper.dart b/mobile/lib/infrastructure/mapper.dart deleted file mode 100644 index a53aa2419a..0000000000 --- a/mobile/lib/infrastructure/mapper.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; - -User mapToUser(UserEntityData data) => User( - id: data.id, - name: data.name, - email: data.email, - hasProfileImage: data.hasProfileImage, - profileChangedAt: data.profileChangedAt, - avatarColor: data.avatarColor, -); - -Partner mapToPartner(UserEntityData user, PartnerEntityData partner) => - Partner.fromUser(mapToUser(user), inTimeline: partner.inTimeline); diff --git a/mobile/lib/infrastructure/repositories/api.repository.dart b/mobile/lib/infrastructure/repositories/api.repository.dart index 9e11024539..15696c65b7 100644 --- a/mobile/lib/infrastructure/repositories/api.repository.dart +++ b/mobile/lib/infrastructure/repositories/api.repository.dart @@ -5,9 +5,7 @@ class ApiRepository { Future checkNull(Future future) async { final response = await future; - if (response == null) { - throw const NoResponseDtoError(); - } + if (response == null) throw const NoResponseDtoError(); return response; } } diff --git a/mobile/lib/infrastructure/repositories/backup.repository.dart b/mobile/lib/infrastructure/repositories/backup.repository.dart index c4c07118d6..0241711d4b 100644 --- a/mobile/lib/infrastructure/repositories/backup.repository.dart +++ b/mobile/lib/infrastructure/repositories/backup.repository.dart @@ -1,20 +1,22 @@ import 'dart:async'; import 'package:drift/drift.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/backup.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -@DriftAccessor() -class BackupRepository extends DatabaseAccessor with $BackupRepositoryMixin { - BackupRepository(super.attachedDatabase); +final backupRepositoryProvider = Provider( + (ref) => DriftBackupRepository(ref.watch(driftProvider)), +); - Drift get _db => attachedDatabase; +class DriftBackupRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftBackupRepository(this._db) : super(_db); - JoinedSelectStatement<$LocalAlbumAssetEntityTable, LocalAlbumAssetEntityData> _getExcludedSubquery() { + _getExcludedSubquery() { return _db.localAlbumAssetEntity.selectOnly() ..addColumns([_db.localAlbumAssetEntity.assetId]) ..join([ diff --git a/mobile/lib/infrastructure/repositories/cached_key_value_repository.dart b/mobile/lib/infrastructure/repositories/cached_key_value_repository.dart deleted file mode 100644 index afeb31fa27..0000000000 --- a/mobile/lib/infrastructure/repositories/cached_key_value_repository.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:drift/drift.dart'; -// ignore: depend_on_referenced_packages -import 'package:meta/meta.dart'; - -abstract class CachedKeyValueRepository { - CachedKeyValueRepository(this._snapshot); - - S _snapshot; - S get snapshot => _snapshot; - @protected - set snapshot(S value) => _snapshot = value; - - List get keys; - - Object decodeValue(K key, String raw); - - S buildSnapshot(Map overrides); - - Selectable<({String key, String? value})> selectable(); - - Future refresh() async => _snapshot = _build(await selectable().get()); - - Stream watchSnapshot() => selectable().watch().map((rows) => _snapshot = _build(rows)); - - S _build(List<({String key, String? value})> rows) => buildSnapshot( - rows.fold({}, (overrides, row) { - final key = keys.firstWhereOrNull((key) => key.name == row.key); - if (key == null) { - return overrides; - } - - Object? decodedValue; - if (row.value != null) { - decodedValue = decodeValue(key, row.value!); - } - - return {...overrides, key: decodedValue}; - }), - ); -} diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index 195f1455d2..d41891e2ea 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -1,20 +1,16 @@ import 'dart:async'; -import 'dart:io'; import 'package:drift/drift.dart'; -// ignore: implementation_imports, invalid_use_of_internal_member -import 'package:drift/src/runtime/executor/stream_queries.dart' show StreamQueryStore; -import 'package:drift_sqlite_async/drift_sqlite_async.dart'; +import 'package:drift_flutter/drift_flutter.dart'; import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart'; import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; @@ -23,43 +19,30 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/map.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/people.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/stack.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart'; -import 'package:logging/logging.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; -import 'package:sqlite3/sqlite3.dart'; -import 'package:sqlite3_connection_pool/sqlite3_connection_pool.dart'; -import 'package:sqlite_async/native.dart'; -import 'package:sqlite_async/sqlite_async.dart'; +import 'package:isar/isar.dart' hide Index; + +// #zoneTxn is the symbol used by Isar to mark a transaction within the current zone +// ref: isar/isar_common.dart +const Symbol _kzoneTxn = #zoneTxn; + +class IsarDatabaseRepository implements IDatabaseRepository { + final Isar _db; + const IsarDatabaseRepository(Isar db) : _db = db; + + // Isar do not support nested transactions. This is a workaround to prevent us from making nested transactions + // Reuse the current transaction if it is already active, else start a new transaction + @override + Future transaction(Future Function() callback) => + Zone.current[_kzoneTxn] == null ? _db.writeTxn(callback) : callback(); +} @DriftDatabase( tables: [ @@ -84,47 +67,12 @@ import 'package:sqlite_async/sqlite_async.dart'; StoreEntity, TrashedLocalAssetEntity, AssetEditEntity, - SettingsEntity, - AssetOcrEntity, ], include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, - daos: [ - AuthUserRepository, - BackupRepository, - LocalAlbumRepository, - LocalAssetRepository, - MapRepository, - MemoryRepository, - OcrRepository, - PartnerRepository, - PeopleRepository, - RemoteAlbumRepository, - RemoteAssetRepository, - RemoteExifRepository, - StackRepository, - StoreRepository, - SyncMigrationRepository, - SyncStreamRepository, - TimelineRepository, - TrashedLocalAssetRepository, - UserMetadataRepository, - UserRepository, - ], ) -class Drift extends $Drift { - final SqliteConnectionPool? _updatePool; - - Drift(super.executor) : _updatePool = null; - - Drift.sqlite(SqliteConnection db, SqliteConnectionPool updatePool) - : _updatePool = updatePool, - super(DatabaseConnection(SqliteAsyncQueryExecutor(db), streamQueries: _DriftPoolStreamQueries(updatePool))); - - @override - Future close() async { - await super.close(); - _updatePool?.close(); - } +class Drift extends $Drift implements IDatabaseRepository { + Drift([QueryExecutor? executor]) + : super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true))); Future reset() async { // https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94 @@ -149,19 +97,8 @@ class Drift extends $Drift { }); } - Future optimize({bool allTables = false}) async { - try { - if (allTables) { - await customStatement('PRAGMA optimize=0x10002'); - } - await customStatement('PRAGMA optimize'); - } catch (error) { - Logger('Drift').fine('Failed to optimize database', error); - } - } - @override - int get schemaVersion => 31; + int get schemaVersion => 22; @override MigrationStrategy get migration => MigrationStrategy( @@ -169,210 +106,149 @@ class Drift extends $Drift { // Run migration steps without foreign keys and re-enable them later await customStatement('PRAGMA foreign_keys = OFF'); - try { - await transaction( - () => m.runMigrationSteps( - from: from, - to: to, - steps: migrationSteps( - from1To2: (m, v2) async { - for (final entity in v2.entities) { - await m.drop(entity); - await m.create(entity); - } - }, - from2To3: (m, v3) async { - // Removed foreign key constraint on stack.primaryAssetId - await m.alterTable(TableMigration(v3.stackEntity)); - }, - from3To4: (m, v4) async { - // Thumbnail path column got removed from person_entity - await m.alterTable(TableMigration(v4.personEntity)); - // asset_face_entity is added - await m.create(v4.assetFaceEntity); - }, - from4To5: (m, v5) async { - await m.alterTable( - TableMigration( - v5.userEntity, - newColumns: [v5.userEntity.hasProfileImage, v5.userEntity.profileChangedAt], - columnTransformer: {v5.userEntity.profileChangedAt: currentDateAndTime}, - ), - ); - }, - from5To6: (m, v6) async { - // Drops the (checksum, ownerId) and adds it back as (ownerId, checksum) - await customStatement('DROP INDEX IF EXISTS UQ_remote_asset_owner_checksum'); - await m.drop(v6.idxRemoteAssetOwnerChecksum); - await m.create(v6.idxRemoteAssetOwnerChecksum); - // Adds libraryId to remote_asset_entity - await m.addColumn(v6.remoteAssetEntity, v6.remoteAssetEntity.libraryId); - await m.drop(v6.uQRemoteAssetsOwnerChecksum); - await m.create(v6.uQRemoteAssetsOwnerChecksum); - await m.drop(v6.uQRemoteAssetsOwnerLibraryChecksum); - await m.create(v6.uQRemoteAssetsOwnerLibraryChecksum); - }, - from6To7: (m, v7) async { - await m.createIndex(v7.idxLatLng); - }, - from7To8: (m, v8) async { - await m.create(v8.storeEntity); - }, - from8To9: (m, v9) async { - await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId); - }, - from9To10: (m, v10) async { - await m.createTable(v10.authUserEntity); - await m.addColumn(v10.userEntity, v10.userEntity.avatarColor); - await m.alterTable(TableMigration(v10.userEntity)); - }, - from10To11: (m, v11) async { - await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_); - }, - from11To12: (m, v12) async { - final localToUTCMapping = { - v12.localAssetEntity: [v12.localAssetEntity.createdAt, v12.localAssetEntity.updatedAt], - v12.localAlbumEntity: [v12.localAlbumEntity.updatedAt], - }; + await m.runMigrationSteps( + from: from, + to: to, + steps: migrationSteps( + from1To2: (m, v2) async { + for (final entity in v2.entities) { + await m.drop(entity); + await m.create(entity); + } + }, + from2To3: (m, v3) async { + // Removed foreign key constraint on stack.primaryAssetId + await m.alterTable(TableMigration(v3.stackEntity)); + }, + from3To4: (m, v4) async { + // Thumbnail path column got removed from person_entity + await m.alterTable(TableMigration(v4.personEntity)); + // asset_face_entity is added + await m.create(v4.assetFaceEntity); + }, + from4To5: (m, v5) async { + await m.alterTable( + TableMigration( + v5.userEntity, + newColumns: [v5.userEntity.hasProfileImage, v5.userEntity.profileChangedAt], + columnTransformer: {v5.userEntity.profileChangedAt: currentDateAndTime}, + ), + ); + }, + from5To6: (m, v6) async { + // Drops the (checksum, ownerId) and adds it back as (ownerId, checksum) + await customStatement('DROP INDEX IF EXISTS UQ_remote_asset_owner_checksum'); + await m.drop(v6.idxRemoteAssetOwnerChecksum); + await m.create(v6.idxRemoteAssetOwnerChecksum); + // Adds libraryId to remote_asset_entity + await m.addColumn(v6.remoteAssetEntity, v6.remoteAssetEntity.libraryId); + await m.drop(v6.uQRemoteAssetsOwnerChecksum); + await m.create(v6.uQRemoteAssetsOwnerChecksum); + await m.drop(v6.uQRemoteAssetsOwnerLibraryChecksum); + await m.create(v6.uQRemoteAssetsOwnerLibraryChecksum); + }, + from6To7: (m, v7) async { + await m.createIndex(v7.idxLatLng); + }, + from7To8: (m, v8) async { + await m.create(v8.storeEntity); + }, + from8To9: (m, v9) async { + await m.addColumn(v9.localAlbumEntity, v9.localAlbumEntity.linkedRemoteAlbumId); + }, + from9To10: (m, v10) async { + await m.createTable(v10.authUserEntity); + await m.addColumn(v10.userEntity, v10.userEntity.avatarColor); + await m.alterTable(TableMigration(v10.userEntity)); + }, + from10To11: (m, v11) async { + await m.addColumn(v11.localAlbumAssetEntity, v11.localAlbumAssetEntity.marker_); + }, + from11To12: (m, v12) async { + final localToUTCMapping = { + v12.localAssetEntity: [v12.localAssetEntity.createdAt, v12.localAssetEntity.updatedAt], + v12.localAlbumEntity: [v12.localAlbumEntity.updatedAt], + }; - for (final entry in localToUTCMapping.entries) { - final table = entry.key; - await m.alterTable( - TableMigration( - table, - columnTransformer: { - for (final column in entry.value) - column: column.modify(const DateTimeModifier.utc()).strftime('%Y-%m-%dT%H:%M:%fZ'), - }, - ), - ); - } - }, - from12To13: (m, v13) async { - await m.create(v13.trashedLocalAssetEntity); - await m.createIndex(v13.idxTrashedLocalAssetChecksum); - await m.createIndex(v13.idxTrashedLocalAssetAlbum); - }, - from13To14: (m, v14) async { - await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.adjustmentTime); - await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude); - await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude); - }, - from14To15: (m, v15) async { - await m.alterTable( - TableMigration( - v15.trashedLocalAssetEntity, - columnTransformer: {v15.trashedLocalAssetEntity.source: Constant(TrashOrigin.localSync.index)}, - newColumns: [v15.trashedLocalAssetEntity.source], - ), - ); - }, - from15To16: (m, v16) async { - // Add i_cloud_id to local and remote asset tables - await m.addColumn(v16.localAssetEntity, v16.localAssetEntity.iCloudId); - await m.createIndex(v16.idxLocalAssetCloudId); - await m.createTable(v16.remoteAssetCloudIdEntity); - }, - from16To17: (m, v17) async { - await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited); - }, - from17To18: (m, v18) async { - await m.createIndex(v18.idxRemoteAssetCloudId); - }, - from18To19: (m, v19) async { - await m.createIndex(v19.idxAssetFacePersonId); - await m.createIndex(v19.idxAssetFaceAssetId); - await m.createIndex(v19.idxLocalAlbumAssetAlbumAsset); - await m.createIndex(v19.idxPartnerSharedWithId); - await m.createIndex(v19.idxPersonOwnerId); - await m.createIndex(v19.idxRemoteAlbumOwnerId); - await m.createIndex(v19.idxRemoteAlbumAssetAlbumAsset); - await m.createIndex(v19.idxRemoteAssetStackId); - await m.createIndex(v19.idxRemoteAssetLocalDateTimeDay); - await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth); - await m.createIndex(v19.idxStackPrimaryAssetId); - }, - from19To20: (m, v20) async { - await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible); - await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt); - }, - from20To21: (m, v21) async { - await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle); - await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle); - }, - from21To22: (m, v22) async { - await m.createTable(v22.assetEditEntity); - await m.createIndex(v22.idxAssetEditAssetId); - }, - from22To23: (m, v23) async { - await m.renameColumn(v23.localAssetEntity, 'duration_in_seconds', v23.localAssetEntity.durationMs); - await m.renameColumn(v23.remoteAssetEntity, 'duration_in_seconds', v23.remoteAssetEntity.durationMs); - await m.renameColumn( - v23.trashedLocalAssetEntity, - 'duration_in_seconds', - v23.trashedLocalAssetEntity.durationMs, - ); + for (final entry in localToUTCMapping.entries) { + final table = entry.key; + await m.alterTable( + TableMigration( + table, + columnTransformer: { + for (final column in entry.value) + column: column.modify(const DateTimeModifier.utc()).strftime('%Y-%m-%dT%H:%M:%fZ'), + }, + ), + ); + } + }, + from12To13: (m, v13) async { + await m.create(v13.trashedLocalAssetEntity); + await m.createIndex(v13.idxTrashedLocalAssetChecksum); + await m.createIndex(v13.idxTrashedLocalAssetAlbum); + }, + from13To14: (m, v14) async { + await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.adjustmentTime); + await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude); + await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude); + }, + from14To15: (m, v15) async { + await m.alterTable( + TableMigration( + v15.trashedLocalAssetEntity, + columnTransformer: {v15.trashedLocalAssetEntity.source: Constant(TrashOrigin.localSync.index)}, + newColumns: [v15.trashedLocalAssetEntity.source], + ), + ); + }, + from15To16: (m, v16) async { + // Add i_cloud_id to local and remote asset tables + await m.addColumn(v16.localAssetEntity, v16.localAssetEntity.iCloudId); + await m.createIndex(v16.idxLocalAssetCloudId); + await m.createTable(v16.remoteAssetCloudIdEntity); + }, + from16To17: (m, v17) async { + await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited); + }, + from17To18: (m, v18) async { + await m.createIndex(v18.idxRemoteAssetCloudId); + }, + from18To19: (m, v19) async { + await m.createIndex(v19.idxAssetFacePersonId); + await m.createIndex(v19.idxAssetFaceAssetId); + await m.createIndex(v19.idxLocalAlbumAssetAlbumAsset); + await m.createIndex(v19.idxPartnerSharedWithId); + await m.createIndex(v19.idxPersonOwnerId); + await m.createIndex(v19.idxRemoteAlbumOwnerId); + await m.createIndex(v19.idxRemoteAlbumAssetAlbumAsset); + await m.createIndex(v19.idxRemoteAssetStackId); + await m.createIndex(v19.idxRemoteAssetLocalDateTimeDay); + await m.createIndex(v19.idxRemoteAssetLocalDateTimeMonth); + await m.createIndex(v19.idxStackPrimaryAssetId); + }, + from19To20: (m, v20) async { + await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.isVisible); + await m.addColumn(v20.assetFaceEntity, v20.assetFaceEntity.deletedAt); + }, + from20To21: (m, v21) async { + await m.addColumn(v21.localAssetEntity, v21.localAssetEntity.playbackStyle); + await m.addColumn(v21.trashedLocalAssetEntity, v21.trashedLocalAssetEntity.playbackStyle); + }, + from21To22: (m, v22) async { + await m.createTable(v22.assetEditEntity); + await m.createIndex(v22.idxAssetEditAssetId); + }, + ), + ); - await localAssetEntity.update().write( - LocalAssetEntityCompanion.custom(durationMs: v23.localAssetEntity.durationMs * const Constant(1000)), - ); - await remoteAssetEntity.update().write( - RemoteAssetEntityCompanion.custom( - durationMs: v23.remoteAssetEntity.durationMs * const Constant(1000), - ), - ); - await trashedLocalAssetEntity.update().write( - TrashedLocalAssetEntityCompanion.custom( - durationMs: v23.trashedLocalAssetEntity.durationMs * const Constant(1000), - ), - ); - }, - from23To24: (m, v24) async { - await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id'); - await m.alterTable(TableMigration(v24.remoteAlbumEntity)); - }, - from24To25: (m, v25) async { - await m.createTable(v25.metadata); - await customStatement('DROP INDEX IF EXISTS idx_remote_asset_owner_checksum'); - await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_day'); - await customStatement('DROP INDEX IF EXISTS idx_remote_asset_local_date_time_month'); - await m.createIndex(v25.idxRemoteAssetOwnerVisibilityDeletedCreated); - await m.createIndex(v25.idxRemoteExifCity); - await m.createIndex(v25.idxAssetFaceVisiblePerson); - }, - from25To26: (m, v26) async { - await m.addColumn(v26.remoteAssetEntity, v26.remoteAssetEntity.uploadedAt); - }, - from26To27: (m, v27) async { - await customStatement('ALTER TABLE metadata RENAME TO settings'); - }, - from27To28: (m, v28) async { - await m.createIndex(v28.idxLocalAssetCreatedAt); - }, - from28To29: (m, v29) async { - await m.createTable(v29.assetOcrEntity); - await m.createIndex(v29.idxAssetOcrAssetId); - }, - from29To30: (m, v30) async { - await m.alterTable(TableMigration(v30.settings)); - }, - from30To31: (m, v31) async { - await m.createIndex(v31.idxRemoteAssetUploaded); - }, - ), - ), - ); - - if (kDebugMode) { - // Fail if the migration broke foreign keys - final wrongFKs = await customSelect('PRAGMA foreign_key_check').get(); - assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}'); - } - } finally { - await customStatement('PRAGMA foreign_keys = ON;'); + if (kDebugMode) { + // Fail if the migration broke foreign keys + final wrongFKs = await customSelect('PRAGMA foreign_key_check').get(); + assert(wrongFKs.isEmpty, '${wrongFKs.map((e) => e.data)}'); } - await optimize(); + await customStatement('PRAGMA foreign_keys = ON;'); }, beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); @@ -385,90 +261,10 @@ class Drift extends $Drift { ); } -// ignore: invalid_use_of_internal_member -final class _DriftPoolStreamQueries extends StreamQueryStore { - _DriftPoolStreamQueries(this._pool); - - final SqliteConnectionPool _pool; +class DriftDatabaseRepository implements IDatabaseRepository { + final Drift _db; + const DriftDatabaseRepository(this._db); @override - void handleTableUpdates(Set updates) { - if (updates.isEmpty) { - return; - } - _pool.dispatchUpdateNotification([for (final update in updates) update.table]); - } - - @override - Stream> updatesForSync(TableUpdateQuery query) { - return _pool.updatedTables - .map((tables) => {for (final table in tables) TableUpdate(table)}) - .where((updates) => updates.any(query.matches)); - } -} - -Future deleteSqliteDatabase({required String name}) async { - final file = await _databaseFile(name); - await [ - file.path, - '${file.path}-wal', - '${file.path}-shm', - ].map((path) => File(path).delete().catchError((_) => File(path), test: (e) => e is FileSystemException)).wait; -} - -Future openSqliteConnection({required String name}) async { - return _openImmichDatabase(await _databaseFile(name)); -} - -Future<(SqliteConnection, SqliteConnectionPool)> openSqliteConnectionWithUpdatePool({required String name}) async { - final file = await _databaseFile(name); - final db = _openImmichDatabase(file); - await db.initialize(); - final updatePool = SqliteConnectionPool.open( - name: file.path, - openConnections: () => throw StateError('Pool for "$name" should already be open via sqlite_async'), - ); - return (db, updatePool); -} - -Future _databaseFile(String name) async { - final dbFolder = await getApplicationDocumentsDirectory(); - return File(p.join(dbFolder.path, '$name.sqlite')); -} - -SqliteDatabase _openImmichDatabase(File file) { - return SqliteDatabase.withFactory( - ImmichSqliteOpenFactory( - path: file.path, - sqliteOptions: const SqliteOptions( - journalMode: SqliteJournalMode.wal, // PRAGMA journal_mode (writer only) - synchronous: SqliteSynchronous.normal, // PRAGMA synchronous - lockTimeout: Duration(seconds: 30), // -> PRAGMA busy_timeout = 30000 - ), - ), - ); -} - -@visibleForTesting -final class ImmichSqliteOpenFactory extends NativeSqliteOpenFactory { - ImmichSqliteOpenFactory({required super.path, super.sqliteOptions}); - - @override - List pragmaStatements(SqliteOpenOptions options) { - return [ - ...super.pragmaStatements(options), - 'PRAGMA cache_size = -32000', // 32MB - 'PRAGMA temp_store = MEMORY', - 'PRAGMA foreign_keys = ON', - ]; - } -} - -Future configureSqliteCache() async { - // Make sqlite3 pick a more suitable location for temporary files - the - // one from the system may be inaccessible due to sand-boxing. - final cacheBase = (await getTemporaryDirectory()).path; - // We can't access /tmp on Android, which sqlite3 would try by default. - // Explicitly tell it about the correct temporary directory. - sqlite3.tempDirectory = cacheBase; + Future transaction(Future Function() callback) => _db.transaction(callback); } diff --git a/mobile/lib/infrastructure/repositories/db.repository.drift.dart b/mobile/lib/infrastructure/repositories/db.repository.drift.dart new file mode 100644 index 0000000000..c898b7ce65 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/db.repository.drift.dart @@ -0,0 +1,402 @@ +// dart format width=80 +// ignore_for_file: type=lint +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/remote_asset.entity.drift.dart' + as i2; +import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart' + as i3; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart' + as i4; +import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart' + as i5; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart' + as i6; +import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart' + as i7; +import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart' + as i8; +import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart' + as i9; +import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart' + as i10; +import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart' + as i11; +import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart' + as i12; +import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart' + as i13; +import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart' + as i14; +import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart' + as i15; +import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart' + as i16; +import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart' + as i17; +import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart' + as i18; +import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart' + as i19; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart' + as i20; +import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart' + as i21; +import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart' + as i22; +import 'package:drift/internal/modular.dart' as i23; + +abstract class $Drift extends i0.GeneratedDatabase { + $Drift(i0.QueryExecutor e) : super(e); + $DriftManager get managers => $DriftManager(this); + late final i1.$UserEntityTable userEntity = i1.$UserEntityTable(this); + late final i2.$RemoteAssetEntityTable remoteAssetEntity = i2 + .$RemoteAssetEntityTable(this); + late final i3.$StackEntityTable stackEntity = i3.$StackEntityTable(this); + late final i4.$LocalAssetEntityTable localAssetEntity = i4 + .$LocalAssetEntityTable(this); + late final i5.$RemoteAlbumEntityTable remoteAlbumEntity = i5 + .$RemoteAlbumEntityTable(this); + late final i6.$LocalAlbumEntityTable localAlbumEntity = i6 + .$LocalAlbumEntityTable(this); + late final i7.$LocalAlbumAssetEntityTable localAlbumAssetEntity = i7 + .$LocalAlbumAssetEntityTable(this); + late final i8.$AuthUserEntityTable authUserEntity = i8.$AuthUserEntityTable( + this, + ); + late final i9.$UserMetadataEntityTable userMetadataEntity = i9 + .$UserMetadataEntityTable(this); + late final i10.$PartnerEntityTable partnerEntity = i10.$PartnerEntityTable( + this, + ); + late final i11.$RemoteExifEntityTable remoteExifEntity = i11 + .$RemoteExifEntityTable(this); + late final i12.$RemoteAlbumAssetEntityTable remoteAlbumAssetEntity = i12 + .$RemoteAlbumAssetEntityTable(this); + late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13 + .$RemoteAlbumUserEntityTable(this); + late final i14.$RemoteAssetCloudIdEntityTable remoteAssetCloudIdEntity = i14 + .$RemoteAssetCloudIdEntityTable(this); + late final i15.$MemoryEntityTable memoryEntity = i15.$MemoryEntityTable(this); + late final i16.$MemoryAssetEntityTable memoryAssetEntity = i16 + .$MemoryAssetEntityTable(this); + late final i17.$PersonEntityTable personEntity = i17.$PersonEntityTable(this); + late final i18.$AssetFaceEntityTable assetFaceEntity = i18 + .$AssetFaceEntityTable(this); + late final i19.$StoreEntityTable storeEntity = i19.$StoreEntityTable(this); + late final i20.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i20 + .$TrashedLocalAssetEntityTable(this); + late final i21.$AssetEditEntityTable assetEditEntity = i21 + .$AssetEditEntityTable(this); + i22.MergedAssetDrift get mergedAssetDrift => i23.ReadDatabaseContainer( + this, + ).accessor(i22.MergedAssetDrift.new); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + i7.idxLocalAlbumAssetAlbumAsset, + i5.idxRemoteAlbumOwnerId, + i4.idxLocalAssetChecksum, + i4.idxLocalAssetCloudId, + i3.idxStackPrimaryAssetId, + i2.idxRemoteAssetOwnerChecksum, + i2.uQRemoteAssetsOwnerChecksum, + i2.uQRemoteAssetsOwnerLibraryChecksum, + i2.idxRemoteAssetChecksum, + i2.idxRemoteAssetStackId, + i2.idxRemoteAssetLocalDateTimeDay, + i2.idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + i10.idxPartnerSharedWithId, + i11.idxLatLng, + i12.idxRemoteAlbumAssetAlbumAsset, + i14.idxRemoteAssetCloudId, + i17.idxPersonOwnerId, + i18.idxAssetFacePersonId, + i18.idxAssetFaceAssetId, + i20.idxTrashedLocalAssetChecksum, + i20.idxTrashedLocalAssetAlbum, + i21.idxAssetEditAssetId, + ]; + @override + i0.StreamQueryUpdateRules + get streamUpdateRules => const i0.StreamQueryUpdateRules([ + 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( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('stack_entity', kind: i0.UpdateKind.delete)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_entity', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('local_album_entity', kind: i0.UpdateKind.update), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'local_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('local_album_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'local_album_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('local_album_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('user_metadata_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('partner_entity', kind: i0.UpdateKind.delete)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('partner_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), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_album_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_user_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('remote_album_user_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate( + 'remote_asset_cloud_id_entity', + kind: i0.UpdateKind.delete, + ), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('memory_entity', kind: i0.UpdateKind.delete)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('memory_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'memory_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [ + i0.TableUpdate('memory_asset_entity', kind: i0.UpdateKind.delete), + ], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'user_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('person_entity', kind: i0.UpdateKind.delete)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.delete)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'person_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('asset_face_entity', kind: i0.UpdateKind.update)], + ), + i0.WritePropagation( + on: i0.TableUpdateQuery.onTableName( + 'remote_asset_entity', + limitUpdateKind: i0.UpdateKind.delete, + ), + result: [i0.TableUpdate('asset_edit_entity', kind: i0.UpdateKind.delete)], + ), + ]); + @override + i0.DriftDatabaseOptions get options => + const i0.DriftDatabaseOptions(storeDateTimeAsText: true); +} + +class $DriftManager { + final $Drift _db; + $DriftManager(this._db); + i1.$$UserEntityTableTableManager get userEntity => + i1.$$UserEntityTableTableManager(_db, _db.userEntity); + i2.$$RemoteAssetEntityTableTableManager get remoteAssetEntity => + i2.$$RemoteAssetEntityTableTableManager(_db, _db.remoteAssetEntity); + i3.$$StackEntityTableTableManager get stackEntity => + i3.$$StackEntityTableTableManager(_db, _db.stackEntity); + i4.$$LocalAssetEntityTableTableManager get localAssetEntity => + i4.$$LocalAssetEntityTableTableManager(_db, _db.localAssetEntity); + i5.$$RemoteAlbumEntityTableTableManager get remoteAlbumEntity => + i5.$$RemoteAlbumEntityTableTableManager(_db, _db.remoteAlbumEntity); + i6.$$LocalAlbumEntityTableTableManager get localAlbumEntity => + i6.$$LocalAlbumEntityTableTableManager(_db, _db.localAlbumEntity); + i7.$$LocalAlbumAssetEntityTableTableManager get localAlbumAssetEntity => i7 + .$$LocalAlbumAssetEntityTableTableManager(_db, _db.localAlbumAssetEntity); + i8.$$AuthUserEntityTableTableManager get authUserEntity => + i8.$$AuthUserEntityTableTableManager(_db, _db.authUserEntity); + i9.$$UserMetadataEntityTableTableManager get userMetadataEntity => + i9.$$UserMetadataEntityTableTableManager(_db, _db.userMetadataEntity); + i10.$$PartnerEntityTableTableManager get partnerEntity => + i10.$$PartnerEntityTableTableManager(_db, _db.partnerEntity); + i11.$$RemoteExifEntityTableTableManager get remoteExifEntity => + i11.$$RemoteExifEntityTableTableManager(_db, _db.remoteExifEntity); + i12.$$RemoteAlbumAssetEntityTableTableManager get remoteAlbumAssetEntity => + i12.$$RemoteAlbumAssetEntityTableTableManager( + _db, + _db.remoteAlbumAssetEntity, + ); + i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13 + .$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity); + i14.$$RemoteAssetCloudIdEntityTableTableManager + get remoteAssetCloudIdEntity => + i14.$$RemoteAssetCloudIdEntityTableTableManager( + _db, + _db.remoteAssetCloudIdEntity, + ); + i15.$$MemoryEntityTableTableManager get memoryEntity => + i15.$$MemoryEntityTableTableManager(_db, _db.memoryEntity); + i16.$$MemoryAssetEntityTableTableManager get memoryAssetEntity => + i16.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity); + i17.$$PersonEntityTableTableManager get personEntity => + i17.$$PersonEntityTableTableManager(_db, _db.personEntity); + i18.$$AssetFaceEntityTableTableManager get assetFaceEntity => + i18.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity); + i19.$$StoreEntityTableTableManager get storeEntity => + i19.$$StoreEntityTableTableManager(_db, _db.storeEntity); + i20.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity => + i20.$$TrashedLocalAssetEntityTableTableManager( + _db, + _db.trashedLocalAssetEntity, + ); + i21.$$AssetEditEntityTableTableManager get assetEditEntity => + i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity); +} diff --git a/mobile/lib/infrastructure/repositories/db.repository.steps.dart b/mobile/lib/infrastructure/repositories/db.repository.steps.dart new file mode 100644 index 0000000000..379f37169d --- /dev/null +++ b/mobile/lib/infrastructure/repositories/db.repository.steps.dart @@ -0,0 +1,10233 @@ +// dart format width=80 +import 'package:drift/internal/versioned_schema.dart' as i0; +import 'package:drift/drift.dart' as i1; +import 'dart:typed_data' as i2; +import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import + +// GENERATED BY drift_dev, DO NOT MODIFY. +final class Schema2 extends i0.VersionedSchema { + Schema2({required super.database}) : super(version: 2); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + localAssetEntity, + stackEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + localAlbumEntity, + localAlbumAssetEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + ]; + late final Shape0 userEntity = Shape0( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 remoteAssetEntity = Shape1( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_24], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index uQRemoteAssetOwnerChecksum = i1.Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape13 personEntity = Shape13( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_70, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape0 extends i0.VersionedTable { + Shape0({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get isAdmin => + columnsByName['is_admin']! as i1.GeneratedColumn; + i1.GeneratedColumn get email => + columnsByName['email']! as i1.GeneratedColumn; + i1.GeneratedColumn get profileImagePath => + columnsByName['profile_image_path']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get quotaSizeInBytes => + columnsByName['quota_size_in_bytes']! as i1.GeneratedColumn; + i1.GeneratedColumn get quotaUsageInBytes => + columnsByName['quota_usage_in_bytes']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_0(String aliasedName) => + i1.GeneratedColumn( + 'id', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_1(String aliasedName) => + i1.GeneratedColumn( + 'name', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_2(String aliasedName) => + i1.GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_3(String aliasedName) => + i1.GeneratedColumn( + 'email', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_4(String aliasedName) => + i1.GeneratedColumn( + 'profile_image_path', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_5(String aliasedName) => + i1.GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: i1.DriftSqlType.dateTime, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); +i1.GeneratedColumn _column_6(String aliasedName) => + i1.GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_7(String aliasedName) => + i1.GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); + +class Shape1 extends i0.VersionedTable { + Shape1({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get localDateTime => + columnsByName['local_date_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbHash => + columnsByName['thumb_hash']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get livePhotoVideoId => + columnsByName['live_photo_video_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get visibility => + columnsByName['visibility']! as i1.GeneratedColumn; + i1.GeneratedColumn get stackId => + columnsByName['stack_id']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_8(String aliasedName) => + i1.GeneratedColumn( + 'type', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_9(String aliasedName) => + i1.GeneratedColumn( + 'created_at', + aliasedName, + false, + type: i1.DriftSqlType.dateTime, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); +i1.GeneratedColumn _column_10(String aliasedName) => + i1.GeneratedColumn( + 'width', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_11(String aliasedName) => + i1.GeneratedColumn( + 'height', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_12(String aliasedName) => + i1.GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_13(String aliasedName) => + i1.GeneratedColumn( + 'checksum', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_14(String aliasedName) => + i1.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_15(String aliasedName) => + i1.GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_16(String aliasedName) => + i1.GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_17(String aliasedName) => + i1.GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_18(String aliasedName) => + i1.GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_19(String aliasedName) => + i1.GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_20(String aliasedName) => + i1.GeneratedColumn( + 'visibility', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_21(String aliasedName) => + i1.GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); + +class Shape2 extends i0.VersionedTable { + Shape2({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_22(String aliasedName) => + i1.GeneratedColumn( + 'checksum', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_23(String aliasedName) => + i1.GeneratedColumn( + 'orientation', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); + +class Shape3 extends i0.VersionedTable { + Shape3({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get primaryAssetId => + columnsByName['primary_asset_id']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_24(String aliasedName) => + i1.GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id)', + ), + ); + +class Shape4 extends i0.VersionedTable { + Shape4({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get userId => + columnsByName['user_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get key => + columnsByName['key']! as i1.GeneratedColumn; + i1.GeneratedColumn get value => + columnsByName['value']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_25(String aliasedName) => + i1.GeneratedColumn( + 'user_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_26(String aliasedName) => + i1.GeneratedColumn( + 'key', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_27(String aliasedName) => + i1.GeneratedColumn( + 'value', + aliasedName, + false, + type: i1.DriftSqlType.blob, + ); + +class Shape5 extends i0.VersionedTable { + Shape5({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get sharedById => + columnsByName['shared_by_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get sharedWithId => + columnsByName['shared_with_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get inTimeline => + columnsByName['in_timeline']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_28(String aliasedName) => + i1.GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_29(String aliasedName) => + i1.GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_30(String aliasedName) => + i1.GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + +class Shape6 extends i0.VersionedTable { + Shape6({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get backupSelection => + columnsByName['backup_selection']! as i1.GeneratedColumn; + i1.GeneratedColumn get isIosSharedAlbum => + columnsByName['is_ios_shared_album']! as i1.GeneratedColumn; + i1.GeneratedColumn get marker_ => + columnsByName['marker']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_31(String aliasedName) => + i1.GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_32(String aliasedName) => + i1.GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_33(String aliasedName) => + i1.GeneratedColumn( + 'marker', + aliasedName, + true, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + +class Shape7 extends i0.VersionedTable { + Shape7({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_34(String aliasedName) => + i1.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_35(String aliasedName) => + i1.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + +class Shape8 extends i0.VersionedTable { + Shape8({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get city => + columnsByName['city']! as i1.GeneratedColumn; + i1.GeneratedColumn get state => + columnsByName['state']! as i1.GeneratedColumn; + i1.GeneratedColumn get country => + columnsByName['country']! as i1.GeneratedColumn; + i1.GeneratedColumn get dateTimeOriginal => + columnsByName['date_time_original']! as i1.GeneratedColumn; + i1.GeneratedColumn get description => + columnsByName['description']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get exposureTime => + columnsByName['exposure_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get fNumber => + columnsByName['f_number']! as i1.GeneratedColumn; + i1.GeneratedColumn get fileSize => + columnsByName['file_size']! as i1.GeneratedColumn; + i1.GeneratedColumn get focalLength => + columnsByName['focal_length']! as i1.GeneratedColumn; + i1.GeneratedColumn get latitude => + columnsByName['latitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get longitude => + columnsByName['longitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get iso => + columnsByName['iso']! as i1.GeneratedColumn; + i1.GeneratedColumn get make => + columnsByName['make']! as i1.GeneratedColumn; + i1.GeneratedColumn get model => + columnsByName['model']! as i1.GeneratedColumn; + i1.GeneratedColumn get lens => + columnsByName['lens']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get timeZone => + columnsByName['time_zone']! as i1.GeneratedColumn; + i1.GeneratedColumn get rating => + columnsByName['rating']! as i1.GeneratedColumn; + i1.GeneratedColumn get projectionType => + columnsByName['projection_type']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_36(String aliasedName) => + i1.GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_37(String aliasedName) => + i1.GeneratedColumn( + 'city', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_38(String aliasedName) => + i1.GeneratedColumn( + 'state', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_39(String aliasedName) => + i1.GeneratedColumn( + 'country', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_40(String aliasedName) => + i1.GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_41(String aliasedName) => + i1.GeneratedColumn( + 'description', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_42(String aliasedName) => + i1.GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_43(String aliasedName) => + i1.GeneratedColumn( + 'f_number', + aliasedName, + true, + type: i1.DriftSqlType.double, + ); +i1.GeneratedColumn _column_44(String aliasedName) => + i1.GeneratedColumn( + 'file_size', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_45(String aliasedName) => + i1.GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: i1.DriftSqlType.double, + ); +i1.GeneratedColumn _column_46(String aliasedName) => + i1.GeneratedColumn( + 'latitude', + aliasedName, + true, + type: i1.DriftSqlType.double, + ); +i1.GeneratedColumn _column_47(String aliasedName) => + i1.GeneratedColumn( + 'longitude', + aliasedName, + true, + type: i1.DriftSqlType.double, + ); +i1.GeneratedColumn _column_48(String aliasedName) => + i1.GeneratedColumn( + 'iso', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_49(String aliasedName) => + i1.GeneratedColumn( + 'make', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_50(String aliasedName) => + i1.GeneratedColumn( + 'model', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_51(String aliasedName) => + i1.GeneratedColumn( + 'lens', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_52(String aliasedName) => + i1.GeneratedColumn( + 'orientation', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_53(String aliasedName) => + i1.GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_54(String aliasedName) => + i1.GeneratedColumn( + 'rating', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_55(String aliasedName) => + i1.GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); + +class Shape9 extends i0.VersionedTable { + Shape9({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get description => + columnsByName['description']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbnailAssetId => + columnsByName['thumbnail_asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isActivityEnabled => + columnsByName['is_activity_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get order => + columnsByName['order']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_56(String aliasedName) => + i1.GeneratedColumn( + 'description', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultValue: const CustomExpression('\'\''), + ); +i1.GeneratedColumn _column_57(String aliasedName) => + i1.GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); +i1.GeneratedColumn _column_58(String aliasedName) => + i1.GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); +i1.GeneratedColumn _column_59(String aliasedName) => + i1.GeneratedColumn( + 'order', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_60(String aliasedName) => + i1.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + +class Shape10 extends i0.VersionedTable { + Shape10({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get userId => + columnsByName['user_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get role => + columnsByName['role']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_61(String aliasedName) => + i1.GeneratedColumn( + 'role', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); + +class Shape11 extends i0.VersionedTable { + Shape11({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get data => + columnsByName['data']! as i1.GeneratedColumn; + i1.GeneratedColumn get isSaved => + columnsByName['is_saved']! as i1.GeneratedColumn; + i1.GeneratedColumn get memoryAt => + columnsByName['memory_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get seenAt => + columnsByName['seen_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get showAt => + columnsByName['show_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get hideAt => + columnsByName['hide_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_62(String aliasedName) => + i1.GeneratedColumn( + 'data', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_63(String aliasedName) => + i1.GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_64(String aliasedName) => + i1.GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_65(String aliasedName) => + i1.GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_66(String aliasedName) => + i1.GeneratedColumn( + 'show_at', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); +i1.GeneratedColumn _column_67(String aliasedName) => + i1.GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); + +class Shape12 extends i0.VersionedTable { + Shape12({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get memoryId => + columnsByName['memory_id']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_68(String aliasedName) => + i1.GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + +class Shape13 extends i0.VersionedTable { + Shape13({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get faceAssetId => + columnsByName['face_asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbnailPath => + columnsByName['thumbnail_path']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get isHidden => + columnsByName['is_hidden']! as i1.GeneratedColumn; + i1.GeneratedColumn get color => + columnsByName['color']! as i1.GeneratedColumn; + i1.GeneratedColumn get birthDate => + columnsByName['birth_date']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_69(String aliasedName) => + i1.GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_70(String aliasedName) => + i1.GeneratedColumn( + 'thumbnail_path', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_71(String aliasedName) => + i1.GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_72(String aliasedName) => + i1.GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_73(String aliasedName) => + i1.GeneratedColumn( + 'color', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_74(String aliasedName) => + i1.GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); + +final class Schema3 extends i0.VersionedSchema { + Schema3({required super.database}) : super(version: 3); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + localAssetEntity, + stackEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + localAlbumEntity, + localAlbumAssetEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + ]; + late final Shape0 userEntity = Shape0( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 remoteAssetEntity = Shape1( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index uQRemoteAssetOwnerChecksum = i1.Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape13 personEntity = Shape13( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_70, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +i1.GeneratedColumn _column_75(String aliasedName) => + i1.GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); + +final class Schema4 extends i0.VersionedSchema { + Schema4({required super.database}) : super(version: 4); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + late final Shape0 userEntity = Shape0( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 remoteAssetEntity = Shape1( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index uQRemoteAssetOwnerChecksum = i1.Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape14 extends i0.VersionedTable { + Shape14({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get faceAssetId => + columnsByName['face_asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get isHidden => + columnsByName['is_hidden']! as i1.GeneratedColumn; + i1.GeneratedColumn get color => + columnsByName['color']! as i1.GeneratedColumn; + i1.GeneratedColumn get birthDate => + columnsByName['birth_date']! as i1.GeneratedColumn; +} + +class Shape15 extends i0.VersionedTable { + Shape15({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get personId => + columnsByName['person_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get imageWidth => + columnsByName['image_width']! as i1.GeneratedColumn; + i1.GeneratedColumn get imageHeight => + columnsByName['image_height']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxX1 => + columnsByName['bounding_box_x1']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxY1 => + columnsByName['bounding_box_y1']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxX2 => + columnsByName['bounding_box_x2']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxY2 => + columnsByName['bounding_box_y2']! as i1.GeneratedColumn; + i1.GeneratedColumn get sourceType => + columnsByName['source_type']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_76(String aliasedName) => + i1.GeneratedColumn( + 'person_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); +i1.GeneratedColumn _column_77(String aliasedName) => + i1.GeneratedColumn( + 'image_width', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_78(String aliasedName) => + i1.GeneratedColumn( + 'image_height', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_79(String aliasedName) => + i1.GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_80(String aliasedName) => + i1.GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_81(String aliasedName) => + i1.GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_82(String aliasedName) => + i1.GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_83(String aliasedName) => + i1.GeneratedColumn( + 'source_type', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); + +final class Schema5 extends i0.VersionedSchema { + Schema5({required super.database}) : super(version: 5); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + late final Shape16 userEntity = Shape16( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_84, + _column_85, + _column_5, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 remoteAssetEntity = Shape1( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index uQRemoteAssetOwnerChecksum = i1.Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape16 extends i0.VersionedTable { + Shape16({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get isAdmin => + columnsByName['is_admin']! as i1.GeneratedColumn; + i1.GeneratedColumn get email => + columnsByName['email']! as i1.GeneratedColumn; + i1.GeneratedColumn get hasProfileImage => + columnsByName['has_profile_image']! as i1.GeneratedColumn; + i1.GeneratedColumn get profileChangedAt => + columnsByName['profile_changed_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_84(String aliasedName) => + i1.GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_85(String aliasedName) => + i1.GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: i1.DriftSqlType.dateTime, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + +final class Schema6 extends i0.VersionedSchema { + Schema6({required super.database}) : super(version: 6); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + late final Shape16 userEntity = Shape16( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_84, + _column_85, + _column_5, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape17 extends i0.VersionedTable { + Shape17({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get localDateTime => + columnsByName['local_date_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbHash => + columnsByName['thumb_hash']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get livePhotoVideoId => + columnsByName['live_photo_video_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get visibility => + columnsByName['visibility']! as i1.GeneratedColumn; + i1.GeneratedColumn get stackId => + columnsByName['stack_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get libraryId => + columnsByName['library_id']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_86(String aliasedName) => + i1.GeneratedColumn( + 'library_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); + +final class Schema7 extends i0.VersionedSchema { + Schema7({required super.database}) : super(version: 7); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + idxLatLng, + ]; + late final Shape16 userEntity = Shape16( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_84, + _column_85, + _column_5, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +final class Schema8 extends i0.VersionedSchema { + Schema8({required super.database}) : super(version: 8); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape16 userEntity = Shape16( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_84, + _column_85, + _column_5, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape6 localAlbumEntity = Shape6( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +class Shape18 extends i0.VersionedTable { + Shape18({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get stringValue => + columnsByName['string_value']! as i1.GeneratedColumn; + i1.GeneratedColumn get intValue => + columnsByName['int_value']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_87(String aliasedName) => + i1.GeneratedColumn( + 'id', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_88(String aliasedName) => + i1.GeneratedColumn( + 'string_value', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_89(String aliasedName) => + i1.GeneratedColumn( + 'int_value', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); + +final class Schema9 extends i0.VersionedSchema { + Schema9({required super.database}) : super(version: 9); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape16 userEntity = Shape16( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_84, + _column_85, + _column_5, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +class Shape19 extends i0.VersionedTable { + Shape19({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get backupSelection => + columnsByName['backup_selection']! as i1.GeneratedColumn; + i1.GeneratedColumn get isIosSharedAlbum => + columnsByName['is_ios_shared_album']! as i1.GeneratedColumn; + i1.GeneratedColumn get linkedRemoteAlbumId => + columnsByName['linked_remote_album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get marker_ => + columnsByName['marker']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_90(String aliasedName) => + i1.GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + +final class Schema10 extends i0.VersionedSchema { + Schema10({required super.database}) : super(version: 10); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 localAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +class Shape20 extends i0.VersionedTable { + Shape20({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get email => + columnsByName['email']! as i1.GeneratedColumn; + i1.GeneratedColumn get hasProfileImage => + columnsByName['has_profile_image']! as i1.GeneratedColumn; + i1.GeneratedColumn get profileChangedAt => + columnsByName['profile_changed_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get avatarColor => + columnsByName['avatar_color']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_91(String aliasedName) => + i1.GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); + +class Shape21 extends i0.VersionedTable { + Shape21({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get email => + columnsByName['email']! as i1.GeneratedColumn; + i1.GeneratedColumn get isAdmin => + columnsByName['is_admin']! as i1.GeneratedColumn; + i1.GeneratedColumn get hasProfileImage => + columnsByName['has_profile_image']! as i1.GeneratedColumn; + i1.GeneratedColumn get profileChangedAt => + columnsByName['profile_changed_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get avatarColor => + columnsByName['avatar_color']! as i1.GeneratedColumn; + i1.GeneratedColumn get quotaSizeInBytes => + columnsByName['quota_size_in_bytes']! as i1.GeneratedColumn; + i1.GeneratedColumn get quotaUsageInBytes => + columnsByName['quota_usage_in_bytes']! as i1.GeneratedColumn; + i1.GeneratedColumn get pinCode => + columnsByName['pin_code']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_92(String aliasedName) => + i1.GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_93(String aliasedName) => + i1.GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_94(String aliasedName) => + i1.GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); + +final class Schema11 extends i0.VersionedSchema { + Schema11({required super.database}) : super(version: 11); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +class Shape22 extends i0.VersionedTable { + Shape22({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get marker_ => + columnsByName['marker']! as i1.GeneratedColumn; +} + +final class Schema12 extends i0.VersionedSchema { + Schema12({required super.database}) : super(version: 12); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); +} + +final class Schema13 extends i0.VersionedSchema { + Schema13({required super.database}) : super(version: 13); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape2 localAssetEntity = Shape2( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape23 trashedLocalAssetEntity = Shape23( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape23 extends i0.VersionedTable { + Shape23({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_95(String aliasedName) => + i1.GeneratedColumn( + 'album_id', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); + +final class Schema14 extends i0.VersionedSchema { + Schema14({required super.database}) : super(version: 14); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape24 localAssetEntity = Shape24( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape23 trashedLocalAssetEntity = Shape23( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape24 extends i0.VersionedTable { + Shape24({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get adjustmentTime => + columnsByName['adjustment_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get latitude => + columnsByName['latitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get longitude => + columnsByName['longitude']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_96(String aliasedName) => + i1.GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); + +final class Schema15 extends i0.VersionedSchema { + Schema15({required super.database}) : super(version: 15); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape24 localAssetEntity = Shape24( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape25 extends i0.VersionedTable { + Shape25({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get source => + columnsByName['source']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_97(String aliasedName) => + i1.GeneratedColumn( + 'source', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); + +final class Schema16 extends i0.VersionedSchema { + Schema16({required super.database}) : super(version: 16); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape17 remoteAssetEntity = Shape17( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape26 localAssetEntity = Shape26( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape26 extends i0.VersionedTable { + Shape26({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get iCloudId => + columnsByName['i_cloud_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get adjustmentTime => + columnsByName['adjustment_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get latitude => + columnsByName['latitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get longitude => + columnsByName['longitude']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_98(String aliasedName) => + i1.GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); + +class Shape27 extends i0.VersionedTable { + Shape27({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get cloudId => + columnsByName['cloud_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get adjustmentTime => + columnsByName['adjustment_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get latitude => + columnsByName['latitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get longitude => + columnsByName['longitude']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_99(String aliasedName) => + i1.GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_100(String aliasedName) => + i1.GeneratedColumn( + 'created_at', + aliasedName, + true, + type: i1.DriftSqlType.dateTime, + ); + +final class Schema17 extends i0.VersionedSchema { + Schema17({required super.database}) : super(version: 17); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape26 localAssetEntity = Shape26( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape28 extends i0.VersionedTable { + Shape28({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get ownerId => + columnsByName['owner_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get localDateTime => + columnsByName['local_date_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get thumbHash => + columnsByName['thumb_hash']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get livePhotoVideoId => + columnsByName['live_photo_video_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get visibility => + columnsByName['visibility']! as i1.GeneratedColumn; + i1.GeneratedColumn get stackId => + columnsByName['stack_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get libraryId => + columnsByName['library_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get isEdited => + columnsByName['is_edited']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_101(String aliasedName) => + i1.GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + +final class Schema18 extends i0.VersionedSchema { + Schema18({required super.database}) : super(version: 18); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxRemoteAssetCloudId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape26 localAssetEntity = Shape26( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +final class Schema19 extends i0.VersionedSchema { + Schema19({required super.database}) : super(version: 19); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape26 localAssetEntity = Shape26( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAlbumOwnerId = i1.Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape15 assetFaceEntity = Shape15( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +final class Schema20 extends i0.VersionedSchema { + Schema20({required super.database}) : super(version: 20); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape26 localAssetEntity = Shape26( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAlbumOwnerId = i1.Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape29 assetFaceEntity = Shape29( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + _column_102, + _column_18, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape25 trashedLocalAssetEntity = Shape25( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape29 extends i0.VersionedTable { + Shape29({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get personId => + columnsByName['person_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get imageWidth => + columnsByName['image_width']! as i1.GeneratedColumn; + i1.GeneratedColumn get imageHeight => + columnsByName['image_height']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxX1 => + columnsByName['bounding_box_x1']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxY1 => + columnsByName['bounding_box_y1']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxX2 => + columnsByName['bounding_box_x2']! as i1.GeneratedColumn; + i1.GeneratedColumn get boundingBoxY2 => + columnsByName['bounding_box_y2']! as i1.GeneratedColumn; + i1.GeneratedColumn get sourceType => + columnsByName['source_type']! as i1.GeneratedColumn; + i1.GeneratedColumn get isVisible => + columnsByName['is_visible']! as i1.GeneratedColumn; + i1.GeneratedColumn get deletedAt => + columnsByName['deleted_at']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_102(String aliasedName) => + i1.GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("is_visible" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + +final class Schema21 extends i0.VersionedSchema { + Schema21({required super.database}) : super(version: 21); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape30 localAssetEntity = Shape30( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + _column_103, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAlbumOwnerId = i1.Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape29 assetFaceEntity = Shape29( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + _column_102, + _column_18, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape31 trashedLocalAssetEntity = Shape31( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + _column_103, + ], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); +} + +class Shape30 extends i0.VersionedTable { + Shape30({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get iCloudId => + columnsByName['i_cloud_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get adjustmentTime => + columnsByName['adjustment_time']! as i1.GeneratedColumn; + i1.GeneratedColumn get latitude => + columnsByName['latitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get longitude => + columnsByName['longitude']! as i1.GeneratedColumn; + i1.GeneratedColumn get playbackStyle => + columnsByName['playback_style']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_103(String aliasedName) => + i1.GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); + +class Shape31 extends i0.VersionedTable { + Shape31({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get type => + columnsByName['type']! as i1.GeneratedColumn; + i1.GeneratedColumn get createdAt => + columnsByName['created_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get updatedAt => + columnsByName['updated_at']! as i1.GeneratedColumn; + i1.GeneratedColumn get width => + columnsByName['width']! as i1.GeneratedColumn; + i1.GeneratedColumn get height => + columnsByName['height']! as i1.GeneratedColumn; + i1.GeneratedColumn get durationInSeconds => + columnsByName['duration_in_seconds']! as i1.GeneratedColumn; + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get albumId => + columnsByName['album_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get checksum => + columnsByName['checksum']! as i1.GeneratedColumn; + i1.GeneratedColumn get isFavorite => + columnsByName['is_favorite']! as i1.GeneratedColumn; + i1.GeneratedColumn get orientation => + columnsByName['orientation']! as i1.GeneratedColumn; + i1.GeneratedColumn get source => + columnsByName['source']! as i1.GeneratedColumn; + i1.GeneratedColumn get playbackStyle => + columnsByName['playback_style']! as i1.GeneratedColumn; +} + +final class Schema22 extends i0.VersionedSchema { + Schema22({required super.database}) : super(version: 22); + @override + late final List entities = [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + late final Shape20 userEntity = Shape20( + source: i0.VersionedTable( + entityName: 'user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_84, + _column_85, + _column_91, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape28 remoteAssetEntity = Shape28( + source: i0.VersionedTable( + entityName: 'remote_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_13, + _column_14, + _column_15, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_86, + _column_101, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 stackEntity = Shape3( + source: i0.VersionedTable( + entityName: 'stack_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_9, _column_5, _column_15, _column_75], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape30 localAssetEntity = Shape30( + source: i0.VersionedTable( + entityName: 'local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_22, + _column_14, + _column_23, + _column_98, + _column_96, + _column_46, + _column_47, + _column_103, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape9 remoteAlbumEntity = Shape9( + source: i0.VersionedTable( + entityName: 'remote_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_56, + _column_9, + _column_5, + _column_15, + _column_57, + _column_58, + _column_59, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape19 localAlbumEntity = Shape19( + source: i0.VersionedTable( + entityName: 'local_album_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_5, + _column_31, + _column_32, + _column_90, + _column_33, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape22 localAlbumAssetEntity = Shape22( + source: i0.VersionedTable( + entityName: 'local_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_34, _column_35, _column_33], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAlbumOwnerId = i1.Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + final i1.Index idxLocalAssetChecksum = i1.Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + final i1.Index idxLocalAssetCloudId = i1.Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + final i1.Index idxStackPrimaryAssetId = i1.Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + final i1.Index idxRemoteAssetOwnerChecksum = i1.Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + final i1.Index idxRemoteAssetChecksum = i1.Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + final i1.Index idxRemoteAssetStackId = i1.Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final Shape21 authUserEntity = Shape21( + source: i0.VersionedTable( + entityName: 'auth_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_1, + _column_3, + _column_2, + _column_84, + _column_85, + _column_92, + _column_93, + _column_7, + _column_94, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape4 userMetadataEntity = Shape4( + source: i0.VersionedTable( + entityName: 'user_metadata_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(user_id, "key")'], + columns: [_column_25, _column_26, _column_27], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape5 partnerEntity = Shape5( + source: i0.VersionedTable( + entityName: 'partner_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'], + columns: [_column_28, _column_29, _column_30], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape8 remoteExifEntity = Shape8( + source: i0.VersionedTable( + entityName: 'remote_exif_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_37, + _column_38, + _column_39, + _column_40, + _column_41, + _column_11, + _column_10, + _column_42, + _column_43, + _column_44, + _column_45, + _column_46, + _column_47, + _column_48, + _column_49, + _column_50, + _column_51, + _column_52, + _column_53, + _column_54, + _column_55, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape7 remoteAlbumAssetEntity = Shape7( + source: i0.VersionedTable( + entityName: 'remote_album_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, album_id)'], + columns: [_column_36, _column_60], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape10 remoteAlbumUserEntity = Shape10( + source: i0.VersionedTable( + entityName: 'remote_album_user_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(album_id, user_id)'], + columns: [_column_60, _column_25, _column_61], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape27 remoteAssetCloudIdEntity = Shape27( + source: i0.VersionedTable( + entityName: 'remote_asset_cloud_id_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id)'], + columns: [ + _column_36, + _column_99, + _column_100, + _column_96, + _column_46, + _column_47, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape11 memoryEntity = Shape11( + source: i0.VersionedTable( + entityName: 'memory_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_18, + _column_15, + _column_8, + _column_62, + _column_63, + _column_64, + _column_65, + _column_66, + _column_67, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape12 memoryAssetEntity = Shape12( + source: i0.VersionedTable( + entityName: 'memory_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'], + columns: [_column_36, _column_68], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape14 personEntity = Shape14( + source: i0.VersionedTable( + entityName: 'person_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_9, + _column_5, + _column_15, + _column_1, + _column_69, + _column_71, + _column_72, + _column_73, + _column_74, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape29 assetFaceEntity = Shape29( + source: i0.VersionedTable( + entityName: 'asset_face_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [ + _column_0, + _column_36, + _column_76, + _column_77, + _column_78, + _column_79, + _column_80, + _column_81, + _column_82, + _column_83, + _column_102, + _column_18, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape18 storeEntity = Shape18( + source: i0.VersionedTable( + entityName: 'store_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_87, _column_88, _column_89], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape31 trashedLocalAssetEntity = Shape31( + source: i0.VersionedTable( + entityName: 'trashed_local_asset_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id, album_id)'], + columns: [ + _column_1, + _column_8, + _column_9, + _column_5, + _column_10, + _column_11, + _column_12, + _column_0, + _column_95, + _column_22, + _column_14, + _column_23, + _column_97, + _column_103, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape32 assetEditEntity = Shape32( + source: i0.VersionedTable( + entityName: 'asset_edit_entity', + withoutRowId: true, + isStrict: true, + tableConstraints: ['PRIMARY KEY(id)'], + columns: [_column_0, _column_36, _column_104, _column_105, _column_106], + attachedDatabase: database, + ), + alias: null, + ); + final i1.Index idxPartnerSharedWithId = i1.Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + final i1.Index idxLatLng = i1.Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + final i1.Index idxRemoteAssetCloudId = i1.Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + final i1.Index idxPersonOwnerId = i1.Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + final i1.Index idxAssetFacePersonId = i1.Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + final i1.Index idxAssetFaceAssetId = i1.Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + final i1.Index idxTrashedLocalAssetChecksum = i1.Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + final i1.Index idxTrashedLocalAssetAlbum = i1.Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + final i1.Index idxAssetEditAssetId = i1.Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); +} + +class Shape32 extends i0.VersionedTable { + Shape32({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get assetId => + columnsByName['asset_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get action => + columnsByName['action']! as i1.GeneratedColumn; + i1.GeneratedColumn get parameters => + columnsByName['parameters']! as i1.GeneratedColumn; + i1.GeneratedColumn get sequence => + columnsByName['sequence']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_104(String aliasedName) => + i1.GeneratedColumn( + 'action', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_105(String aliasedName) => + i1.GeneratedColumn( + 'parameters', + aliasedName, + false, + type: i1.DriftSqlType.blob, + ); +i1.GeneratedColumn _column_106(String aliasedName) => + i1.GeneratedColumn( + 'sequence', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i0.MigrationStepWithVersion migrationSteps({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, + required Future Function(i1.Migrator m, Schema5 schema) from4To5, + required Future Function(i1.Migrator m, Schema6 schema) from5To6, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, + required Future Function(i1.Migrator m, Schema8 schema) from7To8, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, + required Future Function(i1.Migrator m, Schema10 schema) from9To10, + required Future Function(i1.Migrator m, Schema11 schema) from10To11, + required Future Function(i1.Migrator m, Schema12 schema) from11To12, + required Future Function(i1.Migrator m, Schema13 schema) from12To13, + required Future Function(i1.Migrator m, Schema14 schema) from13To14, + required Future Function(i1.Migrator m, Schema15 schema) from14To15, + required Future Function(i1.Migrator m, Schema16 schema) from15To16, + required Future Function(i1.Migrator m, Schema17 schema) from16To17, + required Future Function(i1.Migrator m, Schema18 schema) from17To18, + required Future Function(i1.Migrator m, Schema19 schema) from18To19, + required Future Function(i1.Migrator m, Schema20 schema) from19To20, + required Future Function(i1.Migrator m, Schema21 schema) from20To21, + required Future Function(i1.Migrator m, Schema22 schema) from21To22, +}) { + return (currentVersion, database) async { + switch (currentVersion) { + case 1: + final schema = Schema2(database: database); + final migrator = i1.Migrator(database, schema); + await from1To2(migrator, schema); + return 2; + case 2: + final schema = Schema3(database: database); + final migrator = i1.Migrator(database, schema); + await from2To3(migrator, schema); + return 3; + case 3: + final schema = Schema4(database: database); + final migrator = i1.Migrator(database, schema); + await from3To4(migrator, schema); + return 4; + case 4: + final schema = Schema5(database: database); + final migrator = i1.Migrator(database, schema); + await from4To5(migrator, schema); + return 5; + case 5: + final schema = Schema6(database: database); + final migrator = i1.Migrator(database, schema); + await from5To6(migrator, schema); + return 6; + case 6: + final schema = Schema7(database: database); + final migrator = i1.Migrator(database, schema); + await from6To7(migrator, schema); + return 7; + case 7: + final schema = Schema8(database: database); + final migrator = i1.Migrator(database, schema); + await from7To8(migrator, schema); + return 8; + case 8: + final schema = Schema9(database: database); + final migrator = i1.Migrator(database, schema); + await from8To9(migrator, schema); + return 9; + case 9: + final schema = Schema10(database: database); + final migrator = i1.Migrator(database, schema); + await from9To10(migrator, schema); + return 10; + case 10: + final schema = Schema11(database: database); + final migrator = i1.Migrator(database, schema); + await from10To11(migrator, schema); + return 11; + case 11: + final schema = Schema12(database: database); + final migrator = i1.Migrator(database, schema); + await from11To12(migrator, schema); + return 12; + case 12: + final schema = Schema13(database: database); + final migrator = i1.Migrator(database, schema); + await from12To13(migrator, schema); + return 13; + case 13: + final schema = Schema14(database: database); + final migrator = i1.Migrator(database, schema); + await from13To14(migrator, schema); + return 14; + case 14: + final schema = Schema15(database: database); + final migrator = i1.Migrator(database, schema); + await from14To15(migrator, schema); + return 15; + case 15: + final schema = Schema16(database: database); + final migrator = i1.Migrator(database, schema); + await from15To16(migrator, schema); + return 16; + case 16: + final schema = Schema17(database: database); + final migrator = i1.Migrator(database, schema); + await from16To17(migrator, schema); + return 17; + case 17: + final schema = Schema18(database: database); + final migrator = i1.Migrator(database, schema); + await from17To18(migrator, schema); + return 18; + case 18: + final schema = Schema19(database: database); + final migrator = i1.Migrator(database, schema); + await from18To19(migrator, schema); + return 19; + case 19: + final schema = Schema20(database: database); + final migrator = i1.Migrator(database, schema); + await from19To20(migrator, schema); + return 20; + case 20: + final schema = Schema21(database: database); + final migrator = i1.Migrator(database, schema); + await from20To21(migrator, schema); + return 21; + case 21: + final schema = Schema22(database: database); + final migrator = i1.Migrator(database, schema); + await from21To22(migrator, schema); + return 22; + default: + throw ArgumentError.value('Unknown migration from $currentVersion'); + } + }; +} + +i1.OnUpgrade stepByStep({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, + required Future Function(i1.Migrator m, Schema5 schema) from4To5, + required Future Function(i1.Migrator m, Schema6 schema) from5To6, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, + required Future Function(i1.Migrator m, Schema8 schema) from7To8, + required Future Function(i1.Migrator m, Schema9 schema) from8To9, + required Future Function(i1.Migrator m, Schema10 schema) from9To10, + required Future Function(i1.Migrator m, Schema11 schema) from10To11, + required Future Function(i1.Migrator m, Schema12 schema) from11To12, + required Future Function(i1.Migrator m, Schema13 schema) from12To13, + required Future Function(i1.Migrator m, Schema14 schema) from13To14, + required Future Function(i1.Migrator m, Schema15 schema) from14To15, + required Future Function(i1.Migrator m, Schema16 schema) from15To16, + required Future Function(i1.Migrator m, Schema17 schema) from16To17, + required Future Function(i1.Migrator m, Schema18 schema) from17To18, + required Future Function(i1.Migrator m, Schema19 schema) from18To19, + required Future Function(i1.Migrator m, Schema20 schema) from19To20, + required Future Function(i1.Migrator m, Schema21 schema) from20To21, + required Future Function(i1.Migrator m, Schema22 schema) from21To22, +}) => i0.VersionedSchema.stepByStepHelper( + step: migrationSteps( + from1To2: from1To2, + from2To3: from2To3, + from3To4: from3To4, + from4To5: from4To5, + from5To6: from5To6, + from6To7: from6To7, + from7To8: from7To8, + from8To9: from8To9, + from9To10: from9To10, + from10To11: from10To11, + from11To12: from11To12, + from12To13: from12To13, + from13To14: from13To14, + from14To15: from14To15, + from15To16: from15To16, + from16To17: from16To17, + from17To18: from17To18, + from18To19: from18To19, + from19To20: from19To20, + from20To21: from20To21, + from21To22: from21To22, + ), +); diff --git a/mobile/lib/infrastructure/repositories/device_asset.repository.dart b/mobile/lib/infrastructure/repositories/device_asset.repository.dart new file mode 100644 index 0000000000..73ee148ab3 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/device_asset.repository.dart @@ -0,0 +1,31 @@ +import 'package:immich_mobile/domain/models/device_asset.model.dart'; +import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; + +class IsarDeviceAssetRepository extends IsarDatabaseRepository { + final Isar _db; + + const IsarDeviceAssetRepository(this._db) : super(_db); + + Future deleteIds(List ids) { + return transaction(() async { + await _db.deviceAssetEntitys.deleteAllByAssetId(ids.toList()); + }); + } + + Future> getByIds(List localIds) { + return _db.deviceAssetEntitys + .where() + .anyOf(localIds, (query, id) => query.assetIdEqualTo(id)) + .findAll() + .then((value) => value.map((e) => e.toModel()).toList()); + } + + Future updateAll(List assetHash) { + return transaction(() async { + await _db.deviceAssetEntitys.putAll(assetHash.map(DeviceAssetEntity.fromDto).toList()); + return true; + }); + } +} diff --git a/mobile/lib/infrastructure/repositories/exif.repository.dart b/mobile/lib/infrastructure/repositories/exif.repository.dart new file mode 100644 index 0000000000..0ede30680e --- /dev/null +++ b/mobile/lib/infrastructure/repositories/exif.repository.dart @@ -0,0 +1,40 @@ +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' as entity; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; + +class IsarExifRepository extends IsarDatabaseRepository { + final Isar _db; + + const IsarExifRepository(this._db) : super(_db); + + Future delete(int assetId) async { + await transaction(() async { + await _db.exifInfos.delete(assetId); + }); + } + + Future deleteAll() async { + await transaction(() async { + await _db.exifInfos.clear(); + }); + } + + Future get(int assetId) async { + return (await _db.exifInfos.get(assetId))?.toDto(); + } + + Future update(ExifInfo exifInfo) { + return transaction(() async { + await _db.exifInfos.put(entity.ExifInfo.fromDto(exifInfo)); + return exifInfo; + }); + } + + Future> updateAll(List exifInfos) { + return transaction(() async { + await _db.exifInfos.putAll(exifInfos.map(entity.ExifInfo.fromDto).toList()); + return exifInfos; + }); + } +} diff --git a/mobile/lib/infrastructure/repositories/local_album.repository.dart b/mobile/lib/infrastructure/repositories/local_album.repository.dart index 780b4f5ef9..87a72f02be 100644 --- a/mobile/lib/infrastructure/repositories/local_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_album.repository.dart @@ -10,15 +10,13 @@ import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.d import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_album.repository.drift.dart'; enum SortLocalAlbumsBy { id, backupSelection, isIosSharedAlbum, name, assetCount, newestAsset } -@DriftAccessor() -class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepositoryMixin { - LocalAlbumRepository(super.attachedDatabase); +class DriftLocalAlbumRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const DriftLocalAlbumRepository(this._db) : super(_db); Future> getAll({Set sortBy = const {}}) { final assetCount = _db.localAlbumAssetEntity.assetId.count(); @@ -59,7 +57,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos return query.map((row) => row.toDto()).get(); } - Future deleteAlbum(String albumId) => transaction(() async { + Future delete(String albumId) => transaction(() async { // Remove all assets that are only in this particular album // We cannot remove all assets in the album because they might be in other albums in iOS // That is not the case on Android since asset <-> album has one:one mapping @@ -212,19 +210,17 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos await _deleteAssets(deletes); await _upsertAssets(updates); - // Drop every existing album link for each changed asset before re-adding the - // ones the native side reports. A moved asset only reports its new album here, - // so leaving the old link around makes the per-album delete sweep wipe the - // asset entirely (it is still linked to a bucket it no longer lives in). - await _db.batch((batch) async { - for (final assetId in assetAlbums.keys) { - batch.deleteWhere(_db.localAlbumAssetEntity, (f) => f.assetId.equals(assetId)); - } - }); // The ugly casting below is required for now because the generated code // casts the returned values from the platform during decoding them // and iterating over them causes the type to be List instead of // List + await _db.batch((batch) async { + assetAlbums.cast>().forEach((assetId, albumIds) { + for (final albumId in albumIds.cast().nonNulls) { + batch.deleteWhere(_db.localAlbumAssetEntity, (f) => f.albumId.equals(albumId) & f.assetId.equals(assetId)); + } + }); + }); await _db.batch((batch) async { assetAlbums.cast>().forEach((assetId, albumIds) { batch.insertAll( @@ -245,7 +241,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), ]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId) & _db.localAssetEntity.checksum.isNull()) - ..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)]); + ..orderBy([OrderingTerm.asc(_db.localAssetEntity.id)]); return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get(); } @@ -301,7 +297,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos updatedAt: Value(asset.updatedAt), width: Value(asset.width), height: Value(asset.height), - durationMs: Value(asset.durationMs), + durationInSeconds: Value(asset.durationInSeconds), id: asset.id, orientation: Value(asset.orientation), isFavorite: Value(asset.isFavorite), @@ -333,7 +329,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos updatedAt: Value(asset.updatedAt), width: Value(asset.width), height: Value(asset.height), - durationMs: Value(asset.durationMs), + durationInSeconds: Value(asset.durationInSeconds), id: asset.id, checksum: const Value(null), orientation: Value(asset.orientation), @@ -358,7 +354,7 @@ class LocalAlbumRepository extends DatabaseAccessor with $LocalAlbumRepos return _deleteAssets(assetIds); } - final List assetsToDelete = []; + List assetsToDelete = []; List assetsToUnLink = []; final uniqueAssets = await _getUniqueAssetsInAlbum(albumId); diff --git a/mobile/lib/infrastructure/repositories/local_asset.repository.dart b/mobile/lib/infrastructure/repositories/local_asset.repository.dart index e035bddea4..6f6ef20aeb 100644 --- a/mobile/lib/infrastructure/repositories/local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/local_asset.repository.dart @@ -10,7 +10,6 @@ import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.drift.dart'; class RemovalCandidatesResult { final List assets; @@ -19,28 +18,19 @@ class RemovalCandidatesResult { const RemovalCandidatesResult({required this.assets, required this.totalBytes}); } -@DriftAccessor() -class LocalAssetRepository extends DatabaseAccessor with $LocalAssetRepositoryMixin { - LocalAssetRepository(super.attachedDatabase); +class DriftLocalAssetRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const DriftLocalAssetRepository(this._db) : super(_db); SingleOrNullSelectable _assetSelectable(String id) { - final query = - _db.localAssetEntity.select().addColumns([_db.remoteAssetEntity.id]).join([ - leftOuterJoin( - _db.remoteAssetEntity, - _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum) & - _db.remoteAssetEntity.ownerId.isInQuery( - _db.selectOnly(_db.authUserEntity) - ..addColumns([_db.authUserEntity.id]) - ..limit(1), - ), - useColumns: false, - ), - ]) - ..where(_db.localAssetEntity.id.equals(id)) - ..limit(1); + final query = _db.localAssetEntity.select().addColumns([_db.remoteAssetEntity.id]).join([ + leftOuterJoin( + _db.remoteAssetEntity, + _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), + useColumns: false, + ), + ])..where(_db.localAssetEntity.id.equals(id)); return query.map((row) { final asset = row.readTable(_db.localAssetEntity).toDto(); @@ -74,7 +64,7 @@ class LocalAssetRepository extends DatabaseAccessor with $LocalAssetRepos }); } - Future deleteAssets(List ids) { + Future delete(List ids) { if (ids.isEmpty) { return Future.value(); } @@ -119,40 +109,31 @@ class LocalAssetRepository extends DatabaseAccessor with $LocalAssetRepos return query.map((localAlbum) => localAlbum.toDto()).get(); } - Future>> getAssetsFromBackupAlbums(Iterable remoteIds) async { - if (remoteIds.isEmpty) { + Future>> getAssetsFromBackupAlbums(Iterable checksums) async { + if (checksums.isEmpty) { return {}; } final result = >{}; - for (final slice in remoteIds.toSet().slices(kDriftMaxChunk)) { + for (final slice in checksums.toSet().slices(kDriftMaxChunk)) { final rows = await (_db.select(_db.localAlbumAssetEntity).join([ - innerJoin( - _db.localAlbumEntity, - _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id), - useColumns: false, - ), + innerJoin(_db.localAlbumEntity, _db.localAlbumAssetEntity.albumId.equalsExp(_db.localAlbumEntity.id)), innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), - innerJoin( - _db.remoteAssetEntity, - _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), - useColumns: false, - ), ])..where( _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & - _db.remoteAssetEntity.id.isIn(slice), + _db.localAssetEntity.checksum.isIn(slice), )) .get(); for (final row in rows) { final albumId = row.readTable(_db.localAlbumAssetEntity).albumId; - final asset = row.readTable(_db.localAssetEntity).toDto(); + final assetData = row.readTable(_db.localAssetEntity); + final asset = assetData.toDto(); (result[albumId] ??= []).add(asset); } } - return result; } diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.dart index 8d9794ac8b..e494782fa6 100644 --- a/mobile/lib/infrastructure/repositories/logger_db.repository.dart +++ b/mobile/lib/infrastructure/repositories/logger_db.repository.dart @@ -1,35 +1,26 @@ import 'package:drift/drift.dart'; -import 'package:drift_sqlite_async/drift_sqlite_async.dart'; +import 'package:drift_flutter/drift_flutter.dart'; +import 'package:immich_mobile/domain/interfaces/db.interface.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.drift.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; -import 'package:sqlite_async/sqlite_async.dart'; @DriftDatabase(tables: [LogMessageEntity]) -class DriftLogger extends $DriftLogger { - DriftLogger.fromExecutor(super.executor); - - DriftLogger.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db)); +class DriftLogger extends $DriftLogger implements IDatabaseRepository { + DriftLogger([QueryExecutor? executor]) + : super( + executor ?? driftDatabase(name: 'immich_logs', native: const DriftNativeOptions(shareAcrossIsolates: true)), + ); @override int get schemaVersion => 1; - Future optimize() async { - try { - await customStatement('PRAGMA optimize=0x10002'); - } catch (error) { - dPrint(() => 'Failed to optimize logger database: $error'); - } - } - @override MigrationStrategy get migration => MigrationStrategy( beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); await customStatement('PRAGMA synchronous = NORMAL'); await customStatement('PRAGMA journal_mode = WAL'); - await customStatement('PRAGMA busy_timeout = 30000'); // 30s - await customStatement('PRAGMA cache_size = -32000'); // 32MB + await customStatement('PRAGMA busy_timeout = 500'); await customStatement('PRAGMA temp_store = MEMORY'); }, ); diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart new file mode 100644 index 0000000000..8389d3a827 --- /dev/null +++ b/mobile/lib/infrastructure/repositories/logger_db.repository.drift.dart @@ -0,0 +1,27 @@ +// dart format width=80 +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:immich_mobile/infrastructure/entities/log.entity.drift.dart' + as i1; + +abstract class $DriftLogger extends i0.GeneratedDatabase { + $DriftLogger(i0.QueryExecutor e) : super(e); + $DriftLoggerManager get managers => $DriftLoggerManager(this); + late final i1.$LogMessageEntityTable logMessageEntity = i1 + .$LogMessageEntityTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [logMessageEntity]; + @override + i0.DriftDatabaseOptions get options => + const i0.DriftDatabaseOptions(storeDateTimeAsText: true); +} + +class $DriftLoggerManager { + final $DriftLogger _db; + $DriftLoggerManager(this._db); + i1.$$LogMessageEntityTableTableManager get logMessageEntity => + i1.$$LogMessageEntityTableTableManager(_db, _db.logMessageEntity); +} diff --git a/mobile/lib/infrastructure/repositories/map.repository.dart b/mobile/lib/infrastructure/repositories/map.repository.dart index bd24985fbb..95e42337fc 100644 --- a/mobile/lib/infrastructure/repositories/map.repository.dart +++ b/mobile/lib/infrastructure/repositories/map.repository.dart @@ -5,15 +5,13 @@ import 'package:immich_mobile/domain/services/map.service.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/map.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -@DriftAccessor() -class MapRepository extends DatabaseAccessor with $MapRepositoryMixin { - MapRepository(super.attachedDatabase); +class DriftMapRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const DriftMapRepository(super._db) : _db = _db; MapQuery remote(List ownerIds, TimelineMapOptions options) => _mapQueryBuilder( assetFilter: (row) { @@ -29,18 +27,7 @@ class MapRepository extends DatabaseAccessor with $MapRepositoryMixin { condition = condition & _db.remoteAssetEntity.isFavorite.equals(true); } - final timeRange = options.timeRange; - final hasCustomRange = timeRange.from != null || timeRange.to != null; - - if (hasCustomRange) { - if (timeRange.from != null) { - condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(timeRange.from!); - } - - if (timeRange.to != null) { - condition = condition & _db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(timeRange.to!); - } - } else if (options.relativeDays > 0) { + if (options.relativeDays != 0) { final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays)); condition = condition & _db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate); } diff --git a/mobile/lib/infrastructure/repositories/memory.repository.dart b/mobile/lib/infrastructure/repositories/memory.repository.dart index 11bb99ba7b..0dcf7200cc 100644 --- a/mobile/lib/infrastructure/repositories/memory.repository.dart +++ b/mobile/lib/infrastructure/repositories/memory.repository.dart @@ -4,13 +4,10 @@ import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/memory.repository.drift.dart'; -@DriftAccessor() -class MemoryRepository extends DatabaseAccessor with $MemoryRepositoryMixin { - MemoryRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; +class DriftMemoryRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftMemoryRepository(this._db) : super(_db); Future> getAll(String ownerId) async { final now = DateTime.now(); @@ -28,8 +25,8 @@ class MemoryRepository extends DatabaseAccessor with $MemoryRepositoryMix ]) ..where(_db.memoryEntity.ownerId.equals(ownerId)) ..where(_db.memoryEntity.deletedAt.isNull()) - ..where(_db.memoryEntity.showAt.isNull() | _db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc)) - ..where(_db.memoryEntity.hideAt.isNull() | _db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc)) + ..where(_db.memoryEntity.showAt.isSmallerOrEqualValue(localUtc)) + ..where(_db.memoryEntity.hideAt.isBiggerOrEqualValue(localUtc)) ..orderBy([OrderingTerm.desc(_db.memoryEntity.memoryAt), OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); final rows = await query.get(); diff --git a/mobile/lib/infrastructure/repositories/ocr.repository.dart b/mobile/lib/infrastructure/repositories/ocr.repository.dart deleted file mode 100644 index f374937b4a..0000000000 --- a/mobile/lib/infrastructure/repositories/ocr.repository.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:immich_mobile/domain/models/ocr.model.dart'; -import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/ocr.repository.drift.dart'; - -@DriftAccessor() -class OcrRepository extends DatabaseAccessor with $OcrRepositoryMixin { - OcrRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; - - Future> get(String assetId) async { - final query = _db.select(_db.assetOcrEntity) - ..where((row) => row.assetId.equals(assetId) & row.isVisible.equals(true)); - - final result = await query.get(); - return result.map((e) => e.toDto()).toList(); - } -} - -extension on AssetOcrEntityData { - Ocr toDto() { - return Ocr( - id: id, - assetId: assetId, - x1: x1, - y1: y1, - x2: x2, - y2: y2, - x3: x3, - y3: y3, - x4: x4, - y4: y4, - boxScore: boxScore, - textScore: textScore, - text: recognizedText, - isVisible: isVisible, - ); - } -} diff --git a/mobile/lib/infrastructure/repositories/partner.repository.dart b/mobile/lib/infrastructure/repositories/partner.repository.dart index fd38f12686..b12061ad24 100644 --- a/mobile/lib/infrastructure/repositories/partner.repository.dart +++ b/mobile/lib/infrastructure/repositories/partner.repository.dart @@ -1,65 +1,106 @@ import 'package:drift/drift.dart'; -import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/mapper.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/partner.repository.drift.dart'; -@DriftAccessor() -class PartnerRepository extends DatabaseAccessor with $PartnerRepositoryMixin { - PartnerRepository(super.attachedDatabase); +class DriftPartnerRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftPartnerRepository(this._db) : super(_db); - Drift get _db => attachedDatabase; + Future> getPartners(String userId) { + final query = _db.select(_db.partnerEntity).join([ + innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)), + ])..where(_db.partnerEntity.sharedWithId.equals(userId)); - Future get({required String sharedById, required String sharedWithId}) => - (_db.select(_db.partnerEntity).join([ - innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)), - ])..where( - _db.partnerEntity.sharedById.equals(sharedById) & _db.partnerEntity.sharedWithId.equals(sharedWithId), - )) - .map(_resultToPartner) - .getSingle(); + return query.map((row) { + final user = row.readTable(_db.userEntity); + final partner = row.readTable(_db.partnerEntity); + return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline); + }).get(); + } - Stream> search(String userId, PartnerDirection direction) => - (_db.select(_db.partnerEntity).join([ - innerJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(switch (direction) { - .sharedBy => _db.partnerEntity.sharedWithId, - .sharedWith => _db.partnerEntity.sharedById, - }), - ), - ])..where( - switch (direction) { - .sharedBy => _db.partnerEntity.sharedById, - .sharedWith => _db.partnerEntity.sharedWithId, - }.equals(userId) & - _db.userEntity.id.equals(userId).not(), - )) - .map(_resultToPartner) - .watch(); + // Get users who we can share our library with + Future> getAvailablePartners(String currentUserId) { + final query = _db.select(_db.userEntity)..where((row) => row.id.equals(currentUserId).not()); - Future create({required String sharedById, required String sharedWithId, bool inTimeline = false}) => - _db.partnerEntity.insertOnConflictUpdate( - PartnerEntityCompanion( - sharedById: Value(sharedById), - sharedWithId: Value(sharedWithId), - inTimeline: Value(inTimeline), - ), - ); + return query.map((user) { + return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: false); + }).get(); + } - Future updatePartner({required String sharedById, required String sharedWithId, required bool inTimeline}) => - (_db.partnerEntity.update()..where((t) => t.sharedById.equals(sharedById) & t.sharedWithId.equals(sharedWithId))) - .write(PartnerEntityCompanion(inTimeline: Value(inTimeline))); + // Get users who are sharing their photos WITH the current user + Future> getSharedWith(String partnerId) { + final query = _db.select(_db.partnerEntity).join([ + innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)), + ])..where(_db.partnerEntity.sharedWithId.equals(partnerId)); - Future deletePartner({required String sharedById, required String sharedWithId}) => - (_db.partnerEntity.delete()..where((t) => t.sharedById.equals(sharedById) & t.sharedWithId.equals(sharedWithId))) - .go(); + return query.map((row) { + final user = row.readTable(_db.userEntity); + final partner = row.readTable(_db.partnerEntity); + return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline); + }).get(); + } - Partner _resultToPartner(TypedResult result) { - final user = result.readTable(_db.userEntity); - final partner = result.readTable(_db.partnerEntity); - return mapToPartner(user, partner); + // Get users who the current user is sharing their photos TO + Future> getSharedBy(String userId) { + final query = _db.select(_db.partnerEntity).join([ + innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedWithId)), + ])..where(_db.partnerEntity.sharedById.equals(userId)); + + return query.map((row) { + final user = row.readTable(_db.userEntity); + final partner = row.readTable(_db.partnerEntity); + return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline); + }).get(); + } + + Future> getAllPartnerIds(String userId) async { + // Get users who are sharing with me (sharedWithId = userId) + final sharingWithMeQuery = _db.select(_db.partnerEntity)..where((tbl) => tbl.sharedWithId.equals(userId)); + final sharingWithMe = await sharingWithMeQuery.map((row) => row.sharedById).get(); + + // Get users who I am sharing with (sharedById = userId) + final sharingWithThemQuery = _db.select(_db.partnerEntity)..where((tbl) => tbl.sharedById.equals(userId)); + final sharingWithThem = await sharingWithThemQuery.map((row) => row.sharedWithId).get(); + + // Combine both lists and remove duplicates + final allPartnerIds = {...sharingWithMe, ...sharingWithThem}.toList(); + return allPartnerIds; + } + + Future getPartner(String partnerId, String userId) { + final query = _db.select(_db.partnerEntity).join([ + innerJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.partnerEntity.sharedById)), + ])..where(_db.partnerEntity.sharedById.equals(partnerId) & _db.partnerEntity.sharedWithId.equals(userId)); + + return query.map((row) { + final user = row.readTable(_db.userEntity); + final partner = row.readTable(_db.partnerEntity); + return PartnerUserDto(id: user.id, email: user.email, name: user.name, inTimeline: partner.inTimeline); + }).getSingleOrNull(); + } + + Future toggleShowInTimeline(PartnerUserDto partner, String userId) { + return _db.partnerEntity.update().replace( + PartnerEntityCompanion( + sharedById: Value(partner.id), + sharedWithId: Value(userId), + inTimeline: Value(!partner.inTimeline), + ), + ); + } + + Future create(String partnerId, String userId) { + final entity = PartnerEntityCompanion( + sharedById: Value(userId), + sharedWithId: Value(partnerId), + inTimeline: const Value(false), + ); + + return _db.partnerEntity.insertOne(entity); + } + + Future delete(String partnerId, String userId) { + return _db.partnerEntity.deleteWhere((t) => t.sharedById.equals(userId) & t.sharedWithId.equals(partnerId)); } } diff --git a/mobile/lib/infrastructure/repositories/people.repository.dart b/mobile/lib/infrastructure/repositories/people.repository.dart index 52ba362332..9e55d44867 100644 --- a/mobile/lib/infrastructure/repositories/people.repository.dart +++ b/mobile/lib/infrastructure/repositories/people.repository.dart @@ -3,40 +3,36 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/people.repository.drift.dart'; -@DriftAccessor() -class PeopleRepository extends DatabaseAccessor with $PeopleRepositoryMixin { - PeopleRepository(super.attachedDatabase); +class DriftPeopleRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftPeopleRepository(this._db) : super(_db); - Drift get _db => attachedDatabase; - - Future get(String personId) async { + Future get(String personId) async { final query = _db.select(_db.personEntity)..where((row) => row.id.equals(personId)); final result = await query.getSingleOrNull(); return result?.toDto(); } - Future> getAssetPeople(String assetId) async { - // An asset can have multiple face records for the same person (e.g., metadata - // imports alongside ML detections). Use a subquery instead of a join so each - // person is returned once, regardless of how many of their faces are on the asset - final faceQuery = _db.assetFaceEntity.selectOnly() - ..addColumns([_db.assetFaceEntity.personId]) - ..where( - _db.assetFaceEntity.assetId.equals(assetId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), - ); + Future> getAssetPeople(String assetId) async { + final query = + _db.select(_db.assetFaceEntity).join([ + innerJoin(_db.personEntity, _db.personEntity.id.equalsExp(_db.assetFaceEntity.personId)), + ])..where( + _db.assetFaceEntity.assetId.equals(assetId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull() & + _db.personEntity.isHidden.equals(false), + ); - final query = _db.select(_db.personEntity) - ..where((row) => row.id.isInQuery(faceQuery) & row.isHidden.equals(false)); - - return query.map((row) => row.toDto()).get(); + return query.map((row) { + final person = row.readTable(_db.personEntity); + return person.toDto(); + }).get(); } - Stream> watch({int minFaces = 3}) { + Future> getAllPeople() async { final people = _db.personEntity; final faces = _db.assetFaceEntity; final assets = _db.remoteAssetEntity; @@ -53,7 +49,7 @@ class PeopleRepository extends DatabaseAccessor with $PeopleRepositoryMix faces.isVisible.equals(true) & faces.deletedAt.isNull(), ) - ..groupBy([people.id], having: faces.id.count().isBiggerOrEqualValue(minFaces) | people.name.equals('').not()) + ..groupBy([people.id], having: faces.id.count().isBiggerOrEqualValue(3) | people.name.equals('').not()) ..orderBy([ OrderingTerm(expression: people.name.equals('').not(), mode: OrderingMode.desc), OrderingTerm(expression: faces.id.count(), mode: OrderingMode.desc), @@ -62,7 +58,7 @@ class PeopleRepository extends DatabaseAccessor with $PeopleRepositoryMix return query.map((row) { final person = row.readTable(people); return person.toDto(); - }).watch(); + }).get(); } Future updateName(String personId, String name) { @@ -79,5 +75,18 @@ class PeopleRepository extends DatabaseAccessor with $PeopleRepositoryMix } extension on PersonEntityData { - Person toDto() => Person(id: id, updatedAt: updatedAt, name: name, birthDate: birthDate); + DriftPerson toDto() { + return DriftPerson( + id: id, + createdAt: createdAt, + updatedAt: updatedAt, + ownerId: ownerId, + name: name, + faceAssetId: faceAssetId, + isFavorite: isFavorite, + isHidden: isHidden, + color: color, + birthDate: birthDate, + ); + } } diff --git a/mobile/lib/infrastructure/repositories/remote_album.repository.dart b/mobile/lib/infrastructure/repositories/remote_album.repository.dart index 2f7106c270..a594647f19 100644 --- a/mobile/lib/infrastructure/repositories/remote_album.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_album.repository.dart @@ -10,23 +10,16 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift. import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.drift.dart'; enum SortRemoteAlbumsBy { id, updatedAt } -@DriftAccessor() -class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRepositoryMixin { - RemoteAlbumRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; +class DriftRemoteAlbumRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftRemoteAlbumRepository(this._db) : super(_db); Future> getAll({Set sortBy = const {SortRemoteAlbumsBy.updatedAt}}) { - // Count non-trashed assets via the joined asset table. Filtering trashed assets in the - // join condition (instead of the where clause) keeps albums whose assets are all trashed - // in the result, the same way truly empty albums are kept - final assetCount = _db.remoteAssetEntity.id.count(distinct: true); + final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true); final query = _db.remoteAlbumEntity.select().join([ leftOuterJoin( @@ -36,26 +29,20 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep ), leftOuterJoin( _db.remoteAssetEntity, - _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) & - _db.remoteAssetEntity.deletedAt.isNull(), + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), + leftOuterJoin(_db.userEntity, _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), - innerJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & - _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - useColumns: false, - ), ]); query + ..where(_db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) - ..addColumns([_db.userEntity.name, _db.userEntity.id]) + ..addColumns([_db.userEntity.name]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -76,7 +63,6 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep .readTable(_db.remoteAlbumEntity) .toDto( assetCount: row.read(assetCount) ?? 0, - ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), @@ -85,7 +71,7 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep } Future get(String albumId) { - final assetCount = _db.remoteAssetEntity.id.count(distinct: true); + final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true); final query = _db.remoteAlbumEntity.select().join([ @@ -96,8 +82,12 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep ), leftOuterJoin( _db.remoteAssetEntity, - _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) & - _db.remoteAssetEntity.deletedAt.isNull(), + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), + useColumns: false, + ), + leftOuterJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false, ), leftOuterJoin( @@ -105,17 +95,10 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), - innerJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & - _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - useColumns: false, - ), ]) - ..where(_db.remoteAlbumEntity.id.equals(albumId)) + ..where(_db.remoteAlbumEntity.id.equals(albumId) & _db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) - ..addColumns([_db.userEntity.name, _db.userEntity.id]) + ..addColumns([_db.userEntity.name]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -125,7 +108,6 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep .readTable(_db.remoteAlbumEntity) .toDto( assetCount: row.read(assetCount) ?? 0, - ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ), @@ -134,29 +116,12 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep } Future getByName(String albumName, String ownerId) { - final query = _db.remoteAlbumEntity.select().join([ - innerJoin( - _db.remoteAlbumUserEntity, - _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & - _db.remoteAlbumUserEntity.userId.equals(ownerId) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - useColumns: false, - ), - ]); - - query - ..addColumns([_db.remoteAlbumUserEntity.userId]) - ..where(_db.remoteAlbumEntity.name.equals(albumName)) - ..orderBy([OrderingTerm.desc(_db.remoteAlbumEntity.createdAt)]) + final query = _db.remoteAlbumEntity.select() + ..where((row) => row.name.equals(albumName) & row.ownerId.equals(ownerId)) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) ..limit(1); - return query - .map( - (row) => row - .readTable(_db.remoteAlbumEntity) - .toDto(ownerId: row.read(_db.remoteAlbumUserEntity.userId)!, ownerName: '', isShared: false), - ) - .getSingleOrNull(); + return query.map((row) => row.toDto(ownerName: '', isShared: false)).getSingleOrNull(); } Future create(RemoteAlbum album, List assetIds) async { @@ -164,24 +129,17 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep final entity = RemoteAlbumEntityCompanion( id: Value(album.id), name: Value(album.name), + ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), - thumbnailAssetId: Value(album.thumbnailAssetId ?? (assetIds.isNotEmpty ? assetIds.first : null)), + thumbnailAssetId: Value(album.thumbnailAssetId), isActivityEnabled: Value(album.isActivityEnabled), order: Value(album.order), ); await _db.remoteAlbumEntity.insertOne(entity); - await _db.remoteAlbumUserEntity.insertOne( - RemoteAlbumUserEntityCompanion( - albumId: Value(album.id), - userId: Value(album.ownerId), - role: const Value(AlbumUserRole.owner), - ), - ); - if (assetIds.isNotEmpty) { final albumAssets = assetIds.map( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(album.id), assetId: Value(assetId)), @@ -194,11 +152,12 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep }); } - Future updateAlbum(RemoteAlbum album) async { + Future update(RemoteAlbum album) async { await _db.remoteAlbumEntity.update().replace( RemoteAlbumEntityCompanion( id: Value(album.id), name: Value(album.name), + ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), description: Value(album.description), @@ -220,7 +179,7 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep }); } - Stream<(DateTime, DateTime)> watchDateRange(String albumId) { + FutureOr<(DateTime, DateTime)> getDateRange(String albumId) { final query = _db.remoteAlbumAssetEntity.selectOnly() ..where(_db.remoteAlbumAssetEntity.albumId.equals(albumId)) ..addColumns([_db.remoteAssetEntity.createdAt.min(), _db.remoteAssetEntity.createdAt.max()]) @@ -232,13 +191,13 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep final minDate = row.read(_db.remoteAssetEntity.createdAt.min()); final maxDate = row.read(_db.remoteAssetEntity.createdAt.max()); return (minDate ?? DateTime.now(), maxDate ?? DateTime.now()); - }).watchSingle(); + }).getSingle(); } Future> getSharedUsers(String albumId) async { final albumUserRows = await (_db.select( _db.remoteAlbumUserEntity, - )..where((row) => row.albumId.equals(albumId) & row.role.isNotValue(AlbumUserRole.owner.index))).get(); + )..where((row) => row.albumId.equals(albumId))).get(); if (albumUserRows.isEmpty) { return []; @@ -282,59 +241,17 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep } Future addAssets(String albumId, List assetIds) async { - if (assetIds.isEmpty) { - return 0; - } - final albumAssets = assetIds.map( (assetId) => RemoteAlbumAssetEntityCompanion(albumId: Value(albumId), assetId: Value(assetId)), ); - await _db.transaction(() async { - await _db.batch((batch) { - batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); - }); - - final album = _db.update(_db.remoteAlbumEntity) - ..where((row) => row.id.equals(albumId) & row.thumbnailAssetId.isNull()); - - await album.write(RemoteAlbumEntityCompanion(thumbnailAssetId: Value(assetIds.first))); + await _db.batch((batch) { + batch.insertAll(_db.remoteAlbumAssetEntity, albumAssets); }); return assetIds.length; } - /// Inserts a placeholder `remote_asset_entity` row from a freshly-uploaded - /// local asset. Skips silently if a row with the same id or - /// (owner_id, checksum) already exists — sync will overwrite with the - /// authoritative server data once the AssetUploadReadyV1 event is processed. - Future upsertRemoteAssetStub({ - required String remoteId, - required String ownerId, - required LocalAsset source, - }) async { - await _db - .into(_db.remoteAssetEntity) - .insert( - RemoteAssetEntityCompanion( - id: Value(remoteId), - ownerId: Value(ownerId), - checksum: Value(source.checksum ?? remoteId), - name: Value(source.name), - type: Value(source.type), - createdAt: Value(source.createdAt), - updatedAt: Value(source.updatedAt), - width: Value(source.width), - height: Value(source.height), - durationMs: Value(source.durationMs), - isFavorite: Value(source.isFavorite), - visibility: const Value(AssetVisibility.timeline), - isEdited: Value(source.isEdited), - ), - mode: InsertMode.insertOrIgnore, - ); - } - Future addUsers(String albumId, List userIds) { final albumUsers = userIds.map( (assetId) => RemoteAlbumUserEntityCompanion( @@ -378,21 +295,19 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), useColumns: false, ), + leftOuterJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), + useColumns: false, + ), leftOuterJoin( _db.remoteAlbumUserEntity, _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), - innerJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & - _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - useColumns: false, - ), ]) ..where(_db.remoteAlbumEntity.id.equals(albumId)) - ..addColumns([_db.userEntity.name, _db.userEntity.id]) + ..addColumns([_db.userEntity.name]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) ..groupBy([_db.remoteAlbumEntity.id]); @@ -400,7 +315,6 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep final album = row .readTable(_db.remoteAlbumEntity) .toDto( - ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name)!, isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, ); @@ -410,9 +324,7 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep } Future> getSortedAlbumIds(List albumIds, {required AssetDateAggregation aggregation}) async { - if (albumIds.isEmpty) { - return []; - } + if (albumIds.isEmpty) return []; final jsonIds = jsonEncode(albumIds); final sqlAgg = aggregation == AssetDateAggregation.start ? 'MIN' : 'MAX'; @@ -443,37 +355,6 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep return _db.managers.remoteAlbumEntity.count(); } - Future getOwner(String albumId) { - final query = - _db.userEntity.select().join([ - innerJoin( - _db.remoteAlbumUserEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId), - useColumns: false, - ), - ])..where( - _db.remoteAlbumUserEntity.albumId.equals(albumId) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - ); - - return query - .map( - (row) => UserDto( - id: row.read(_db.userEntity.id)!, - email: row.read(_db.userEntity.email)!, - name: row.read(_db.userEntity.name)!, - memoryEnabled: true, - inTimeline: false, - isPartnerSharedBy: false, - isPartnerSharedWith: false, - profileChangedAt: row.read(_db.userEntity.profileChangedAt)!, - hasProfileImage: row.read(_db.userEntity.hasProfileImage)!, - avatarColor: AvatarColor.values[row.read(_db.userEntity.avatarColor)!], - ), - ) - .getSingle(); - } - Future> getLinkedAssetIds(String userId, String localAlbumId, String remoteAlbumId) async { // Find remote asset ids that: // 1. Belong to the provided local album (via local_album_asset_entity) @@ -522,7 +403,7 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep return []; } - final assetCount = _db.remoteAssetEntity.id.count(distinct: true); + final assetCount = _db.remoteAlbumAssetEntity.assetId.count(distinct: true); final query = _db.remoteAlbumEntity.select().join([ leftOuterJoin( @@ -532,8 +413,12 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep ), leftOuterJoin( _db.remoteAssetEntity, - _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId) & - _db.remoteAssetEntity.deletedAt.isNull(), + _db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId), + useColumns: false, + ), + leftOuterJoin( + _db.userEntity, + _db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId), useColumns: false, ), leftOuterJoin( @@ -541,18 +426,11 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id), useColumns: false, ), - innerJoin( - _db.userEntity, - _db.userEntity.id.equalsExp(_db.remoteAlbumUserEntity.userId) & - _db.remoteAlbumUserEntity.albumId.equalsExp(_db.remoteAlbumEntity.id) & - _db.remoteAlbumUserEntity.role.equalsValue(AlbumUserRole.owner), - useColumns: false, - ), ]) - ..where(_db.remoteAlbumEntity.id.isIn(albumIds)) + ..where(_db.remoteAlbumEntity.id.isIn(albumIds) & _db.remoteAssetEntity.deletedAt.isNull()) ..addColumns([assetCount]) ..addColumns([_db.remoteAlbumUserEntity.userId.count(distinct: true)]) - ..addColumns([_db.userEntity.name, _db.userEntity.id]) + ..addColumns([_db.userEntity.name]) ..groupBy([_db.remoteAlbumEntity.id]); return query @@ -560,7 +438,6 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep (row) => row .readTable(_db.remoteAlbumEntity) .toDto( - ownerId: row.read(_db.userEntity.id)!, ownerName: row.read(_db.userEntity.name) ?? '', isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0, assetCount: row.read(assetCount) ?? 0, @@ -571,7 +448,7 @@ class RemoteAlbumRepository extends DatabaseAccessor with $RemoteAlbumRep } extension on RemoteAlbumEntityData { - RemoteAlbum toDto({int assetCount = 0, required String ownerName, required String ownerId, required bool isShared}) { + RemoteAlbum toDto({int assetCount = 0, required String ownerName, required bool isShared}) { return RemoteAlbum( id: id, name: name, diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index e6fd0e9c89..df4172df99 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -1,24 +1,19 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/stack.model.dart'; -import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart' hide ExifInfo; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.drift.dart'; -import 'package:immich_mobile/utils/option.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -@DriftAccessor() -class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRepositoryMixin { - RemoteAssetRepository(super.attachedDatabase); +class RemoteAssetRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const RemoteAssetRepository(this._db) : super(_db); /// For testing purposes Future> getSome(String userId) { @@ -61,10 +56,10 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep return _assetSelectable(id).getSingleOrNull(); } - Future> getAllDebugForChecksum(String checksum) { + Future getByChecksum(String checksum) { final query = _db.remoteAssetEntity.select()..where((row) => row.checksum.equals(checksum)); - return query.map((row) => row.toDto()).get(); + return query.map((row) => row.toDto()).getSingleOrNull(); } Future> getStackChildren(RemoteAsset asset) { @@ -119,6 +114,18 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep }).get(); } + Future updateFavorite(List ids, bool isFavorite) { + return _db.batch((batch) async { + for (final id in ids) { + batch.update( + _db.remoteAssetEntity, + RemoteAssetEntityCompanion(isFavorite: Value(isFavorite)), + where: (e) => e.id.equals(id), + ); + } + }); + } + Future updateVisibility(List ids, AssetVisibility visibility) { return _db.batch((batch) async { for (final id in ids) { @@ -155,17 +162,7 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep }); } - Future emptyTrash(String ownerId) async { - await _db.remoteAssetEntity.deleteWhere((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId)); - } - - Future restoreAllTrash(String ownerId) async { - await (_db.remoteAssetEntity.update()..where((t) => t.deletedAt.isNotNull() & t.ownerId.equals(ownerId))).write( - const RemoteAssetEntityCompanion(deletedAt: Value(null)), - ); - } - - Future deleteAssets(List ids) { + Future delete(List ids) { return _db.batch((batch) { for (final id in ids) { batch.deleteWhere(_db.remoteAssetEntity, (row) => row.id.equals(id)); @@ -173,6 +170,35 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep }); } + Future updateLocation(List ids, LatLng location) { + return _db.batch((batch) async { + for (final id in ids) { + batch.update( + _db.remoteExifEntity, + RemoteExifEntityCompanion(latitude: Value(location.latitude), longitude: Value(location.longitude)), + where: (e) => e.assetId.equals(id), + ); + } + }); + } + + Future updateDateTime(List ids, DateTime dateTime) { + return _db.batch((batch) async { + for (final id in ids) { + batch.update( + _db.remoteExifEntity, + RemoteExifEntityCompanion(dateTimeOriginal: Value(dateTime)), + where: (e) => e.assetId.equals(id), + ); + batch.update( + _db.remoteAssetEntity, + RemoteAssetEntityCompanion(createdAt: Value(dateTime)), + where: (e) => e.id.equals(id), + ); + } + }); + } + Future stack(String userId, StackResponse stack) { return _db.transaction(() async { final stackIds = await _db.managers.stackEntity @@ -229,7 +255,7 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep ); } - Future updateRating(String assetId, int? rating) async { + Future updateRating(String assetId, int rating) async { await (_db.remoteExifEntity.update()..where((row) => row.assetId.equals(assetId))).write( RemoteExifEntityCompanion(rating: Value(rating)), ); @@ -238,66 +264,4 @@ class RemoteAssetRepository extends DatabaseAccessor with $RemoteAssetRep Future getCount() { return _db.managers.remoteAssetEntity.count(); } - - Future> getAssetEdits(String assetId) { - final query = _db.assetEditEntity.select() - ..where((row) => row.assetId.equals(assetId) & row.action.equals(AssetEditAction.other.index).not()) - ..orderBy([(row) => OrderingTerm.asc(row.sequence)]); - return query.map((row) => row.toDto()!).get(); - } - - Future updateAssets( - List remoteIds, { - Option isFavorite = const .none(), - Option visibility = const .none(), - Option createdAt = const .none(), - }) async { - if ([isFavorite, visibility, createdAt].every((option) => option.isNone)) { - return; - } - - final companion = RemoteAssetEntityCompanion( - visibility: visibility.toDriftValue(), - isFavorite: isFavorite.toDriftValue(), - createdAt: createdAt.toDriftValue(), - ); - return _db.batch((batch) { - for (final remoteId in remoteIds) { - batch.update(_db.remoteAssetEntity, companion, where: (e) => e.id.equals(remoteId)); - } - }); - } - - // TODO(shenlong): remove after action migration - Future updateLocation(List ids, LatLng location) { - return _db.batch((batch) async { - for (final id in ids) { - batch.update( - _db.remoteExifEntity, - RemoteExifEntityCompanion(latitude: Value(location.latitude), longitude: Value(location.longitude)), - where: (e) => e.assetId.equals(id), - ); - } - }); - } - - Future updateDateTime(List ids, DateTime dateTime, {String? timeZone}) { - return _db.batch((batch) async { - for (final id in ids) { - batch.update( - _db.remoteExifEntity, - RemoteExifEntityCompanion( - dateTimeOriginal: Value(dateTime), - timeZone: timeZone == null ? const Value.absent() : Value(timeZone), - ), - where: (e) => e.assetId.equals(id), - ); - batch.update( - _db.remoteAssetEntity, - RemoteAssetEntityCompanion(createdAt: Value(dateTime)), - where: (e) => e.id.equals(id), - ); - } - }); - } } diff --git a/mobile/lib/infrastructure/repositories/remote_exif.repository.dart b/mobile/lib/infrastructure/repositories/remote_exif.repository.dart deleted file mode 100644 index f3064104f2..0000000000 --- a/mobile/lib/infrastructure/repositories/remote_exif.repository.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.drift.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:maplibre_gl/maplibre_gl.dart'; - -@DriftAccessor() -class RemoteExifRepository extends DatabaseAccessor with $RemoteExifRepositoryMixin { - RemoteExifRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; - - Future updateExif( - List ids, { - Option dateTimeOriginal = const .none(), - Option timeZone = const .none(), - Option location = const .none(), - }) async { - if ([dateTimeOriginal, timeZone, location].every((option) => option.isNone)) { - return; - } - - final companion = RemoteExifEntityCompanion( - dateTimeOriginal: dateTimeOriginal.toDriftValue(), - timeZone: timeZone.toDriftValue(), - latitude: location.map((loc) => loc.latitude).toDriftValue(), - longitude: location.map((loc) => loc.longitude).toDriftValue(), - ); - - return _db.batch((batch) { - for (final id in ids) { - batch.update(_db.remoteExifEntity, companion, where: (a) => a.assetId.equals(id)); - } - }); - } -} diff --git a/mobile/lib/infrastructure/repositories/search_api.repository.dart b/mobile/lib/infrastructure/repositories/search_api.repository.dart index dec6e1d167..bcfddfce6e 100644 --- a/mobile/lib/infrastructure/repositories/search_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/search_api.repository.dart @@ -1,7 +1,6 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' hide AssetVisibility; import 'package:immich_mobile/infrastructure/repositories/api.repository.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; -import 'package:immich_mobile/utils/option.dart'; import 'package:openapi/api.dart'; class SearchApiRepository extends ApiRepository { @@ -21,62 +20,50 @@ class SearchApiRepository extends ApiRepository { (filter.assetId != null && filter.assetId!.isNotEmpty)) { return _api.searchSmart( SmartSearchDto( - query: filter.context == null ? const Optional.absent() : Optional.present(filter.context), - queryAssetId: filter.assetId == null ? const Optional.absent() : Optional.present(filter.assetId), - language: filter.language == null ? const Optional.absent() : Optional.present(filter.language), - country: filter.location.country == null - ? const Optional.absent() - : Optional.present(filter.location.country), - state: filter.location.state == null ? const Optional.absent() : Optional.present(filter.location.state), - city: filter.location.city == null ? const Optional.absent() : Optional.present(filter.location.city), - make: filter.camera.make == null ? const Optional.absent() : Optional.present(filter.camera.make), - model: filter.camera.model == null ? const Optional.absent() : Optional.present(filter.camera.model), - takenAfter: filter.date.takenAfter == null - ? const Optional.absent() - : Optional.present(filter.date.takenAfter), - takenBefore: filter.date.takenBefore == null - ? const Optional.absent() - : Optional.present(filter.date.takenBefore), - visibility: Optional.present(filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline), - rating: filter.rating.rating.toOptional(), - isFavorite: filter.display.isFavorite ? const Optional.present(true) : const Optional.absent(), - isNotInAlbum: filter.display.isNotInAlbum ? const Optional.present(true) : const Optional.absent(), - personIds: Optional.present(filter.people.map((e) => e.id).toList()), - tagIds: filter.tagIds == null ? const Optional.absent() : Optional.present(filter.tagIds), - type: type == null ? const Optional.absent() : Optional.present(type), - page: Optional.present(page), - size: const Optional.present(100), + query: filter.context, + queryAssetId: filter.assetId, + language: filter.language, + country: filter.location.country, + state: filter.location.state, + city: filter.location.city, + make: filter.camera.make, + model: filter.camera.model, + takenAfter: filter.date.takenAfter, + takenBefore: filter.date.takenBefore, + visibility: filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline, + rating: filter.rating.rating, + isFavorite: filter.display.isFavorite ? true : null, + isNotInAlbum: filter.display.isNotInAlbum ? true : null, + personIds: filter.people.map((e) => e.id).toList(), + tagIds: filter.tagIds, + type: type, + page: page, + size: 100, ), ); } return _api.searchAssets( MetadataSearchDto( - originalFileName: filter.filename != null && filter.filename!.isNotEmpty - ? Optional.present(filter.filename) - : const Optional.absent(), - country: filter.location.country == null ? const Optional.absent() : Optional.present(filter.location.country), - description: filter.description != null && filter.description!.isNotEmpty - ? Optional.present(filter.description) - : const Optional.absent(), - ocr: filter.ocr != null && filter.ocr!.isNotEmpty ? Optional.present(filter.ocr) : const Optional.absent(), - state: filter.location.state == null ? const Optional.absent() : Optional.present(filter.location.state), - city: filter.location.city == null ? const Optional.absent() : Optional.present(filter.location.city), - make: filter.camera.make == null ? const Optional.absent() : Optional.present(filter.camera.make), - model: filter.camera.model == null ? const Optional.absent() : Optional.present(filter.camera.model), - takenAfter: filter.date.takenAfter == null ? const Optional.absent() : Optional.present(filter.date.takenAfter), - takenBefore: filter.date.takenBefore == null - ? const Optional.absent() - : Optional.present(filter.date.takenBefore), - visibility: Optional.present(filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline), - rating: filter.rating.rating.toOptional(), - isFavorite: filter.display.isFavorite ? const Optional.present(true) : const Optional.absent(), - isNotInAlbum: filter.display.isNotInAlbum ? const Optional.present(true) : const Optional.absent(), - personIds: Optional.present(filter.people.map((e) => e.id).toList()), - tagIds: filter.tagIds == null ? const Optional.absent() : Optional.present(filter.tagIds), - type: type == null ? const Optional.absent() : Optional.present(type), - page: Optional.present(page), - size: const Optional.present(1000), + originalFileName: filter.filename != null && filter.filename!.isNotEmpty ? filter.filename : null, + country: filter.location.country, + description: filter.description != null && filter.description!.isNotEmpty ? filter.description : null, + ocr: filter.ocr != null && filter.ocr!.isNotEmpty ? filter.ocr : null, + state: filter.location.state, + city: filter.location.city, + make: filter.camera.make, + model: filter.camera.model, + takenAfter: filter.date.takenAfter, + takenBefore: filter.date.takenBefore, + visibility: filter.display.isArchive ? AssetVisibility.archive : AssetVisibility.timeline, + rating: filter.rating.rating, + isFavorite: filter.display.isFavorite ? true : null, + isNotInAlbum: filter.display.isNotInAlbum ? true : null, + personIds: filter.people.map((e) => e.id).toList(), + tagIds: filter.tagIds, + type: type, + page: page, + size: 1000, ), ); } diff --git a/mobile/lib/infrastructure/repositories/settings.repository.dart b/mobile/lib/infrastructure/repositories/settings.repository.dart deleted file mode 100644 index c5f8fb48d8..0000000000 --- a/mobile/lib/infrastructure/repositories/settings.repository.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:flutter/foundation.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/cached_key_value_repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; - -class SettingsRepository extends CachedKeyValueRepository { - final Drift _db; - - SettingsRepository._(this._db) : super(const .new()); - - static SettingsRepository? _instance; - - static SettingsRepository get instance { - final instance = _instance; - if (instance == null) { - throw StateError('SettingsRepository not initialized. Call ensureInitialized() first'); - } - return instance; - } - - static Future ensureInitialized(Drift db) async { - if (_instance == null) { - final instance = SettingsRepository._(db); - await instance.refresh(); - _instance = instance; - } - return _instance!; - } - - @visibleForTesting - static Future reset() async { - final instance = _instance; - if (instance != null) { - await instance.clear(SettingsKey.values); - _instance = null; - } - } - - @override - List get keys => SettingsKey.values; - - @override - Object decodeValue(SettingsKey key, String raw) => key.decode(raw); - - @override - AppConfig buildSnapshot(Map overrides) => AppConfig.fromEntries(overrides); - - @override - Selectable<({String key, String? value})> selectable() => - _db.select(_db.settingsEntity).map((row) => (key: row.key, value: row.value)); - - AppConfig get appConfig => snapshot; - - Future clear(Iterable keys) async { - if (keys.isEmpty) { - return; - } - - final names = keys.map((key) => key.name).toList(); - await (_db.delete(_db.settingsEntity)..where((row) => row.key.isIn(names))).go(); - - var config = snapshot; - for (final key in keys) { - config = config.write(key, defaultConfig.read(key)); - } - snapshot = config; - } - - Future write(SettingsKey key, U value) async { - if (value == snapshot.read(key)) { - return; - } - - if (value == defaultConfig.read(key)) { - return clear([key]); - } - - String? resolvedValue; - if (value != null) { - resolvedValue = key.encode(value); - } - - await _db - .into(_db.settingsEntity) - .insertOnConflictUpdate( - SettingsEntityCompanion.insert(key: key.name, value: .new(resolvedValue), updatedAt: .new(DateTime.now())), - ); - snapshot = snapshot.write(key, value); - } - - Stream watchConfig() => watchSnapshot(); -} diff --git a/mobile/lib/infrastructure/repositories/stack.repository.dart b/mobile/lib/infrastructure/repositories/stack.repository.dart index eecf7b9147..28f7496f97 100644 --- a/mobile/lib/infrastructure/repositories/stack.repository.dart +++ b/mobile/lib/infrastructure/repositories/stack.repository.dart @@ -2,13 +2,10 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/stack.model.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/stack.repository.drift.dart'; -@DriftAccessor() -class StackRepository extends DatabaseAccessor with $StackRepositoryMixin { - StackRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; +class DriftStackRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftStackRepository(this._db) : super(_db); Future> getAll(String userId) { final query = _db.stackEntity.select()..where((e) => e.ownerId.equals(userId)); diff --git a/mobile/lib/infrastructure/repositories/storage.repository.dart b/mobile/lib/infrastructure/repositories/storage.repository.dart index 9500190abc..eaa6ce79f7 100644 --- a/mobile/lib/infrastructure/repositories/storage.repository.dart +++ b/mobile/lib/infrastructure/repositories/storage.repository.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_slow_async_io - import 'dart:io'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -35,7 +33,6 @@ class StorageRepository { return file; } - // TODO(agg23): Unify these methods Future getMotionFileForAsset(LocalAsset asset) async { File? file; final log = Logger('StorageRepository'); diff --git a/mobile/lib/infrastructure/repositories/store.repository.dart b/mobile/lib/infrastructure/repositories/store.repository.dart index 62cf38c209..d4e34a02f5 100644 --- a/mobile/lib/infrastructure/repositories/store.repository.dart +++ b/mobile/lib/infrastructure/repositories/store.repository.dart @@ -1,45 +1,150 @@ import 'package:drift/drift.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:isar/isar.dart'; -@DriftAccessor() -class StoreRepository extends DatabaseAccessor with $StoreRepositoryMixin { +// Temporary interface until Isar is removed to make the service work with both Isar and Sqlite +abstract class IStoreRepository { + Future deleteAll(); + Stream>> watchAll(); + Future delete(StoreKey key); + Future upsert(StoreKey key, T value); + Future tryGet(StoreKey key); + Stream watch(StoreKey key); + Future>> getAll(); +} + +class IsarStoreRepository extends IsarDatabaseRepository implements IStoreRepository { + final Isar _db; final validStoreKeys = StoreKey.values.map((e) => e.id).toSet(); - StoreRepository(super.attachedDatabase); + IsarStoreRepository(super.db) : _db = db; - Drift get _db => attachedDatabase; + @override + Future deleteAll() async { + return await transaction(() async { + await _db.storeValues.clear(); + return true; + }); + } + @override + Stream>> watchAll() { + return _db.storeValues + .filter() + .anyOf(validStoreKeys, (query, id) => query.idEqualTo(id)) + .watch(fireImmediately: true) + .asyncMap((entities) => Future.wait(entities.map((entity) => _toUpdateEvent(entity)))); + } + + @override + Future delete(StoreKey key) async { + return await transaction(() async => await _db.storeValues.delete(key.id)); + } + + @override + Future upsert(StoreKey key, T value) async { + return await transaction(() async { + await _db.storeValues.put(await _fromValue(key, value)); + return true; + }); + } + + @override + Future tryGet(StoreKey key) async { + final entity = (await _db.storeValues.get(key.id)); + if (entity == null) { + return null; + } + return await _toValue(key, entity); + } + + @override + Stream watch(StoreKey key) async* { + yield* _db.storeValues + .watchObject(key.id, fireImmediately: true) + .asyncMap((e) async => e == null ? null : await _toValue(key, e)); + } + + Future> _toUpdateEvent(StoreValue entity) async { + final key = StoreKey.values.firstWhere((e) => e.id == entity.id) as StoreKey; + final value = await _toValue(key, entity); + return StoreDto(key, value); + } + + Future _toValue(StoreKey key, StoreValue entity) async => + switch (key.type) { + const (int) => entity.intValue, + const (String) => entity.strValue, + const (bool) => entity.intValue == 1, + const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), + const (UserDto) => + entity.strValue == null ? null : await IsarUserRepository(_db).getByUserId(entity.strValue!), + _ => null, + } + as T?; + + Future _fromValue(StoreKey key, T value) async { + final (int? intValue, String? strValue) = switch (key.type) { + const (int) => (value as int, null), + const (String) => (null, value as String), + const (bool) => ((value as bool) ? 1 : 0, null), + const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), + const (UserDto) => (null, (await IsarUserRepository(_db).update(value as UserDto)).id), + _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"), + }; + return StoreValue(key.id, intValue: intValue, strValue: strValue); + } + + @override + Future>> getAll() async { + final entities = await _db.storeValues.filter().anyOf(validStoreKeys, (query, id) => query.idEqualTo(id)).findAll(); + return Future.wait(entities.map((e) => _toUpdateEvent(e)).toList()); + } +} + +class DriftStoreRepository extends DriftDatabaseRepository implements IStoreRepository { + final Drift _db; + final validStoreKeys = StoreKey.values.map((e) => e.id).toSet(); + + DriftStoreRepository(super.db) : _db = db; + + @override Future deleteAll() async { await _db.storeEntity.deleteAll(); return true; } + @override Future>> getAll() async { final query = _db.storeEntity.select()..where((entity) => entity.id.isIn(validStoreKeys)); return query.asyncMap((entity) => _toUpdateEvent(entity)).get(); } + @override Stream>> watchAll() { final query = _db.storeEntity.select()..where((entity) => entity.id.isIn(validStoreKeys)); return query.asyncMap((entity) => _toUpdateEvent(entity)).watch(); } - Future deleteValue(StoreKey key) async { + @override + Future delete(StoreKey key) async { await _db.storeEntity.deleteWhere((entity) => entity.id.equals(key.id)); return; } + @override Future upsert(StoreKey key, T value) async { await _db.storeEntity.insertOnConflictUpdate(await _fromValue(key, value)); return true; } + @override Future tryGet(StoreKey key) async { final entity = await _db.managers.storeEntity.filter((entity) => entity.id.equals(key.id)).getSingleOrNull(); if (entity == null) { @@ -48,6 +153,7 @@ class StoreRepository extends DatabaseAccessor with $StoreRepositoryMixin return await _toValue(key, entity); } + @override Stream watch(StoreKey key) async* { final query = _db.storeEntity.select()..where((entity) => entity.id.equals(key.id)); @@ -67,7 +173,7 @@ class StoreRepository extends DatabaseAccessor with $StoreRepositoryMixin const (bool) => entity.intValue == 1, const (DateTime) => entity.intValue == null ? null : DateTime.fromMillisecondsSinceEpoch(entity.intValue!), const (UserDto) => - entity.stringValue == null ? null : await AuthUserRepository(_db).get(entity.stringValue!), + entity.stringValue == null ? null : await DriftAuthUserRepository(_db).get(entity.stringValue!), _ => null, } as T?; @@ -78,7 +184,7 @@ class StoreRepository extends DatabaseAccessor with $StoreRepositoryMixin const (String) => (null, value as String), const (bool) => ((value as bool) ? 1 : 0, null), const (DateTime) => ((value as DateTime).millisecondsSinceEpoch, null), - const (UserDto) => (null, (await AuthUserRepository(_db).upsert(value as UserDto)).id), + const (UserDto) => (null, (await DriftAuthUserRepository(_db).upsert(value as UserDto)).id), _ => throw UnsupportedError("Unsupported primitive type: ${key.type} for key: ${key.name}"), }; return StoreEntityCompanion(id: Value(key.id), intValue: Value(intValue), stringValue: Value(strValue)); diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 4e27da1a3d..624759b2e6 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -3,8 +3,9 @@ import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/server_capability.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/sync_event.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/semver.dart'; @@ -21,7 +22,7 @@ class SyncApiRepository { } Future deleteSyncAck(List types) { - return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: Optional.present(types))); + return _api.syncApi.deleteSyncAck(SyncAckDeleteDto(types: types)); } Future streamChanges( @@ -30,7 +31,6 @@ class SyncApiRepository { Function()? onReset, int batchSize = kSyncEventBatchSize, http.Client? httpClient, - Future? abortSignal, }) async { final stopwatch = Stopwatch()..start(); final client = httpClient ?? NetworkRepository.client; @@ -38,23 +38,24 @@ class SyncApiRepository { final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'}; - final request = http.AbortableRequest('POST', Uri.parse(endpoint), abortTrigger: abortSignal); + final shouldReset = Store.get(StoreKey.shouldResetSync, false); + final request = http.Request('POST', Uri.parse(endpoint)); request.headers.addAll(headers); request.body = jsonEncode( SyncStreamDto( types: [ SyncRequestType.authUsersV1, SyncRequestType.usersV1, - serverVersion.supports(.syncV2) ? SyncRequestType.assetsV2 : SyncRequestType.assetsV1, + SyncRequestType.assetsV1, SyncRequestType.assetExifsV1, - if (serverVersion.supports(.assetEdits)) SyncRequestType.assetEditsV1, + if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1, SyncRequestType.assetMetadataV1, SyncRequestType.partnersV1, - serverVersion.supports(.syncV2) ? SyncRequestType.partnerAssetsV2 : SyncRequestType.partnerAssetsV1, + SyncRequestType.partnerAssetsV1, SyncRequestType.partnerAssetExifsV1, - serverVersion.supports(.syncV2) ? SyncRequestType.albumsV2 : SyncRequestType.albumsV1, + SyncRequestType.albumsV1, SyncRequestType.albumUsersV1, - serverVersion.supports(.syncV2) ? SyncRequestType.albumAssetsV2 : SyncRequestType.albumAssetsV1, + SyncRequestType.albumAssetsV1, SyncRequestType.albumAssetExifsV1, SyncRequestType.albumToAssetsV1, SyncRequestType.memoriesV1, @@ -63,14 +64,15 @@ class SyncApiRepository { SyncRequestType.partnerStacksV1, SyncRequestType.userMetadataV1, SyncRequestType.peopleV1, - serverVersion.supports(.assetFacesV2) ? SyncRequestType.assetFacesV2 : SyncRequestType.assetFacesV1, - if (serverVersion.supports(.assetOcr)) SyncRequestType.assetOcrV1, + if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1, + if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2, ], + reset: shouldReset, ).toJson(), ); String previousChunk = ''; - final List lines = []; + List lines = []; bool shouldAbort = false; @@ -89,13 +91,16 @@ class SyncApiRepository { throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody'); } + // Reset after successful stream start + await Store.put(StoreKey.shouldResetSync, false); + await for (final chunk in response.stream.transform(utf8.decoder)) { if (shouldAbort) { break; } previousChunk += chunk; - final parts = previousChunk.split('\n'); + final parts = previousChunk.toString().split('\n'); previousChunk = parts.removeLast(); lines.addAll(parts); @@ -145,7 +150,6 @@ const _kResponseMap = { SyncEntityType.partnerV1: SyncPartnerV1.fromJson, SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson, SyncEntityType.assetV1: SyncAssetV1.fromJson, - SyncEntityType.assetV2: SyncAssetV2.fromJson, SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson, @@ -153,24 +157,18 @@ const _kResponseMap = { SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson, SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson, SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson, - SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson, SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson, - SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson, SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson, SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson, SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson, SyncEntityType.albumV1: SyncAlbumV1.fromJson, - SyncEntityType.albumV2: SyncAlbumV2.fromJson, SyncEntityType.albumDeleteV1: SyncAlbumDeleteV1.fromJson, SyncEntityType.albumUserV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson, SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson, SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson, - SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson, - SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson, - SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson, SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson, SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson, @@ -195,8 +193,6 @@ const _kResponseMap = { SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson, SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson, SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson, - SyncEntityType.assetOcrV1: SyncAssetOcrV1.fromJson, - SyncEntityType.assetOcrDeleteV1: SyncAssetOcrDeleteV1.fromJson, SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson, }; diff --git a/mobile/lib/infrastructure/repositories/sync_migration.repository.dart b/mobile/lib/infrastructure/repositories/sync_migration.repository.dart index eca451dd16..814c8780ad 100644 --- a/mobile/lib/infrastructure/repositories/sync_migration.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_migration.repository.dart @@ -1,12 +1,9 @@ -import 'package:drift/drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/sync_migration.repository.drift.dart'; -@DriftAccessor() -class SyncMigrationRepository extends DatabaseAccessor with $SyncMigrationRepositoryMixin { - SyncMigrationRepository(super.attachedDatabase); +class SyncMigrationRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const SyncMigrationRepository(super.db) : _db = db; Future v20260128CopyExifWidthHeightToAsset() async { await _db.customStatement(''' diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index ce246b97ab..4319ee63cf 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -9,13 +9,10 @@ import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user_metadata.model.dart'; -import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; @@ -29,19 +26,16 @@ import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.drift.dart'; import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; import 'package:logging/logging.dart'; -import 'package:openapi/api.dart' as api show AlbumUserRole, AssetEditAction, AssetVisibility, UserMetadataKey; -import 'package:openapi/api.dart' hide AlbumUserRole, AssetEditAction, AssetVisibility, UserMetadataKey; +import 'package:openapi/api.dart' as api show AssetVisibility, AlbumUserRole, UserMetadataKey, AssetEditAction; +import 'package:openapi/api.dart' hide AlbumUserRole, UserMetadataKey, AssetEditAction, AssetVisibility; -@DriftAccessor() -class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepositoryMixin { - final Logger _logger = Logger('SyncStreamRepository'); +class SyncStreamRepository extends DriftDatabaseRepository { + final Logger _logger = Logger('DriftSyncStreamRepository'); + final Drift _db; - SyncStreamRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; + SyncStreamRepository(super.db) : _db = db; Future reset() async { _logger.fine("SyncResetV1 received. Resetting remote entities"); @@ -50,36 +44,25 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos // foreign_keys PRAGMA is no-op within transactions // https://www.sqlite.org/pragma.html#pragma_foreign_keys await _db.customStatement('PRAGMA foreign_keys = OFF'); - try { - await transaction(() async { - // FK cascade (ON DELETE SET NULL) does not fire while foreign_keys = OFF, - // so null linkedRemoteAlbumId manually to avoid dangling pointers in local_album_entity. - await _db.localAlbumEntity.update().write( - const LocalAlbumEntityCompanion(linkedRemoteAlbumId: Value(null)), - ); - await _db.assetFaceEntity.deleteAll(); - await _db.memoryAssetEntity.deleteAll(); - await _db.memoryEntity.deleteAll(); - await _db.partnerEntity.deleteAll(); - await _db.personEntity.deleteAll(); - await _db.remoteAlbumAssetEntity.deleteAll(); - await _db.remoteAlbumEntity.deleteAll(); - await _db.remoteAlbumUserEntity.deleteAll(); - await _db.remoteAssetEntity.deleteAll(); - await _db.remoteExifEntity.deleteAll(); - await _db.stackEntity.deleteAll(); - await _db.authUserEntity.deleteAll(); - await _db.userEntity.deleteAll(); - await _db.userMetadataEntity.deleteAll(); - await _db.remoteAssetCloudIdEntity.deleteAll(); - await _db.assetEditEntity.deleteAll(); - await _db.assetOcrEntity.deleteAll(); - }); - } finally { - // re-enable FK even if the transaction throws, otherwise the connection - // would be left with foreign_keys = OFF, silently disabling cascades. - await _db.customStatement('PRAGMA foreign_keys = ON'); - } + await transaction(() async { + await _db.assetFaceEntity.deleteAll(); + await _db.memoryAssetEntity.deleteAll(); + await _db.memoryEntity.deleteAll(); + await _db.partnerEntity.deleteAll(); + await _db.personEntity.deleteAll(); + await _db.remoteAlbumAssetEntity.deleteAll(); + await _db.remoteAlbumEntity.deleteAll(); + await _db.remoteAlbumUserEntity.deleteAll(); + await _db.remoteAssetEntity.deleteAll(); + await _db.remoteExifEntity.deleteAll(); + await _db.stackEntity.deleteAll(); + await _db.authUserEntity.deleteAll(); + await _db.userEntity.deleteAll(); + await _db.userMetadataEntity.deleteAll(); + await _db.remoteAssetCloudIdEntity.deleteAll(); + await _db.assetEditEntity.deleteAll(); + }); + await _db.customStatement('PRAGMA foreign_keys = ON'); }); } catch (error, stack) { _logger.severe('Error: SyncResetV1', error, stack); @@ -96,7 +79,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), isAdmin: Value(user.isAdmin), pinCode: Value(user.pinCode), quotaSizeInBytes: Value(user.quotaSizeInBytes ?? 0), @@ -138,7 +121,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos email: Value(user.email), hasProfileImage: Value(user.hasProfileImage), profileChangedAt: Value(user.profileChangedAt), - avatarColor: Value(user.avatarColor.orElse(null)?.toAvatarColor() ?? AvatarColor.primary), + avatarColor: Value(user.avatarColor?.toAvatarColor() ?? AvatarColor.primary), ); batch.insert(_db.userEntity, companion.copyWith(id: Value(user.id)), onConflict: DoUpdate((_) => companion)); @@ -207,8 +190,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos type: Value(asset.type.toAssetType()), createdAt: Value.absentIfNull(asset.fileCreatedAt), updatedAt: Value.absentIfNull(asset.fileModifiedAt), - uploadedAt: Value(asset.createdAt), - durationMs: Value(asset.duration?.toDuration()?.inMilliseconds ?? 0), + durationInSeconds: Value(asset.duration?.toDuration()?.inSeconds ?? 0), checksum: Value(asset.checksum), isFavorite: Value(asset.isFavorite), ownerId: Value(asset.ownerId), @@ -227,7 +209,6 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos batch.insert( _db.remoteAssetEntity, companion.copyWith(id: Value(asset.id)), - mode: InsertMode.insertOrReplace, onConflict: DoUpdate((_) => companion), ); } @@ -238,46 +219,6 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } } - Future updateAssetsV2(Iterable data, {String debugLabel = 'user'}) async { - try { - await _db.batch((batch) { - for (final asset in data) { - final companion = RemoteAssetEntityCompanion( - name: Value(asset.originalFileName), - type: Value(asset.type.toAssetType()), - createdAt: Value.absentIfNull(asset.fileCreatedAt), - updatedAt: Value.absentIfNull(asset.fileModifiedAt), - uploadedAt: Value(asset.createdAt), - durationMs: Value(asset.duration), - checksum: Value(asset.checksum), - isFavorite: Value(asset.isFavorite), - ownerId: Value(asset.ownerId), - localDateTime: Value(asset.localDateTime), - thumbHash: Value(asset.thumbhash), - deletedAt: Value(asset.deletedAt), - visibility: Value(asset.visibility.toAssetVisibility()), - livePhotoVideoId: Value(asset.livePhotoVideoId), - stackId: Value(asset.stackId), - libraryId: Value(asset.libraryId), - width: Value(asset.width), - height: Value(asset.height), - isEdited: Value(asset.isEdited), - ); - - batch.insert( - _db.remoteAssetEntity, - companion.copyWith(id: Value(asset.id)), - mode: InsertMode.insertOrReplace, - onConflict: DoUpdate((_) => companion), - ); - } - }); - } catch (error, stack) { - _logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack); - rethrow; - } - } - Future updateAssetsExifV1(Iterable data, {String debugLabel = 'user'}) async { try { await _db.batch((batch) { @@ -292,8 +233,8 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos fNumber: Value(exif.fNumber), fileSize: Value(exif.fileSizeInByte), focalLength: Value(exif.focalLength), - latitude: Value(exif.latitude), - longitude: Value(exif.longitude), + latitude: Value(exif.latitude?.toDouble()), + longitude: Value(exif.longitude?.toDouble()), iso: Value(exif.iso), make: Value(exif.make), model: Value(exif.model), @@ -363,12 +304,12 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos final map = metadata.value as Map; final companion = RemoteAssetCloudIdEntityCompanion( cloudId: Value(map['iCloudId']?.toString()), - createdAt: Value(map['createdAt'] != null ? DateTime.parse(map['createdAt']! as String) : null), + createdAt: Value(map['createdAt'] != null ? DateTime.parse(map['createdAt'] as String) : null), adjustmentTime: Value( - map['adjustmentTime'] != null ? DateTime.parse(map['adjustmentTime']! as String) : null, + map['adjustmentTime'] != null ? DateTime.parse(map['adjustmentTime'] as String) : null, ), - latitude: Value(map['latitude'] != null ? (double.tryParse(map['latitude']! as String)) : null), - longitude: Value(map['longitude'] != null ? (double.tryParse(map['longitude']! as String)) : null), + latitude: Value(map['latitude'] != null ? (double.tryParse(map['latitude'] as String)) : null), + longitude: Value(map['longitude'] != null ? (double.tryParse(map['longitude'] as String)) : null), ); batch.insert( _db.remoteAssetCloudIdEntity, @@ -455,47 +396,6 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } Future updateAlbumsV1(Iterable data) async { - try { - await _db.transaction(() async { - await _db.batch((batch) { - for (final album in data) { - final companion = RemoteAlbumEntityCompanion( - name: Value(album.name), - description: Value(album.description), - isActivityEnabled: Value(album.isActivityEnabled), - order: Value(album.order.toAlbumAssetOrder()), - thumbnailAssetId: Value(album.thumbnailAssetId), - createdAt: Value(album.createdAt), - updatedAt: Value(album.updatedAt), - ); - - batch.insert( - _db.remoteAlbumEntity, - companion.copyWith(id: Value(album.id)), - onConflict: DoUpdate((_) => companion), - ); - } - }); - - await _db.batch((batch) { - for (final album in data) { - final companion = RemoteAlbumUserEntityCompanion( - albumId: Value(album.id), - userId: Value(album.ownerId), - role: const Value(AlbumUserRole.owner), - ); - - batch.insert(_db.remoteAlbumUserEntity, companion, onConflict: DoUpdate((_) => companion)); - } - }); - }); - } catch (error, stack) { - _logger.severe('Error: updateAlbumsV1', error, stack); - rethrow; - } - } - - Future updateAlbumsV2(Iterable data) async { try { await _db.batch((batch) { for (final album in data) { @@ -505,6 +405,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos isActivityEnabled: Value(album.isActivityEnabled), order: Value(album.order.toAlbumAssetOrder()), thumbnailAssetId: Value(album.thumbnailAssetId), + ownerId: Value(album.ownerId), createdAt: Value(album.createdAt), updatedAt: Value(album.updatedAt), ); @@ -517,7 +418,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } }); } catch (error, stack) { - _logger.severe('Error: updateAlbumsV2', error, stack); + _logger.severe('Error: updateAlbumsV1', error, stack); rethrow; } } @@ -855,52 +756,6 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } } - Future updateAssetOcrV1(Iterable data) async { - try { - await _db.batch((batch) { - for (final assetOcr in data) { - final companion = AssetOcrEntityCompanion( - assetId: Value(assetOcr.assetId), - recognizedText: Value(assetOcr.text), - x1: Value(assetOcr.x1), - y1: Value(assetOcr.y1), - x2: Value(assetOcr.x2), - y2: Value(assetOcr.y2), - x3: Value(assetOcr.x3), - y3: Value(assetOcr.y3), - x4: Value(assetOcr.x4), - y4: Value(assetOcr.y4), - boxScore: Value(assetOcr.boxScore), - textScore: Value(assetOcr.textScore), - isVisible: Value(assetOcr.isVisible), - ); - - batch.insert( - _db.assetOcrEntity, - companion.copyWith(id: Value(assetOcr.id)), - onConflict: DoUpdate((_) => companion), - ); - } - }); - } catch (error, stack) { - _logger.severe('Error: updateAssetOcrV1', error, stack); - rethrow; - } - } - - Future deleteAssetOcrV1(Iterable data) async { - try { - await _db.batch((batch) { - for (final assetOcr in data) { - batch.deleteWhere(_db.assetOcrEntity, (row) => row.id.equals(assetOcr.id)); - } - }); - } catch (error, stack) { - _logger.severe('Error: deleteAssetOcrV1', error, stack); - rethrow; - } - } - Future pruneAssets() async { try { await _db.transaction(() async { @@ -942,6 +797,7 @@ extension on AssetTypeEnum { AssetTypeEnum.VIDEO => AssetType.video, AssetTypeEnum.AUDIO => AssetType.audio, AssetTypeEnum.OTHER => AssetType.other, + _ => throw Exception('Unknown AssetType value: $this'), }; } @@ -949,12 +805,14 @@ extension on AssetOrder { AlbumAssetOrder toAlbumAssetOrder() => switch (this) { AssetOrder.asc => AlbumAssetOrder.asc, AssetOrder.desc => AlbumAssetOrder.desc, + _ => throw Exception('Unknown AssetOrder value: $this'), }; } extension on MemoryType { MemoryTypeEnum toMemoryType() => switch (this) { MemoryType.onThisDay => MemoryTypeEnum.onThisDay, + _ => throw Exception('Unknown MemoryType value: $this'), }; } @@ -962,7 +820,7 @@ extension on api.AlbumUserRole { AlbumUserRole toAlbumUserRole() => switch (this) { api.AlbumUserRole.editor => AlbumUserRole.editor, api.AlbumUserRole.viewer => AlbumUserRole.viewer, - api.AlbumUserRole.owner => AlbumUserRole.owner, + _ => throw Exception('Unknown AlbumUserRole value: $this'), }; } @@ -972,6 +830,7 @@ extension on api.AssetVisibility { api.AssetVisibility.hidden => AssetVisibility.hidden, api.AssetVisibility.archive => AssetVisibility.archive, api.AssetVisibility.locked => AssetVisibility.locked, + _ => throw Exception('Unknown AssetVisibility value: $this'), }; } @@ -980,11 +839,24 @@ extension on api.UserMetadataKey { api.UserMetadataKey.onboarding => UserMetadataKey.onboarding, api.UserMetadataKey.preferences => UserMetadataKey.preferences, api.UserMetadataKey.license => UserMetadataKey.license, + _ => throw Exception('Unknown UserMetadataKey value: $this'), }; } +extension on String { + Duration? toDuration() { + try { + final parts = split(':').map((e) => double.parse(e).toInt()).toList(growable: false); + + return Duration(hours: parts[0], minutes: parts[1], seconds: parts[2]); + } catch (_) { + return null; + } + } +} + extension on UserAvatarColor { - AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == toString()); + AvatarColor? toAvatarColor() => AvatarColor.values.firstWhereOrNull((c) => c.name == value); } extension on api.AssetEditAction { @@ -992,5 +864,6 @@ extension on api.AssetEditAction { api.AssetEditAction.crop => AssetEditAction.crop, api.AssetEditAction.rotate => AssetEditAction.rotate, api.AssetEditAction.mirror => AssetEditAction.mirror, + _ => AssetEditAction.other, }; } diff --git a/mobile/lib/infrastructure/repositories/tags_api.repository.dart b/mobile/lib/infrastructure/repositories/tags_api.repository.dart index 5963fc2f23..e81b79c459 100644 --- a/mobile/lib/infrastructure/repositories/tags_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/tags_api.repository.dart @@ -14,13 +14,4 @@ class TagsApiRepository extends ApiRepository { Future?> getAllTags() async { return await _api.getAllTags(); } - - Future bulkTagAssets(List assetIds, List tagIds) async { - final response = await _api.bulkTagAssets(TagBulkAssetsDto(assetIds: assetIds, tagIds: tagIds)); - return response?.count ?? 0; - } - - Future?> upsertTags(List tags) async { - return _api.upsertTags(TagUpsertDto(tags: tags)); - } } diff --git a/mobile/lib/infrastructure/repositories/timeline.repository.dart b/mobile/lib/infrastructure/repositories/timeline.repository.dart index 824449e939..74af6dc3f0 100644 --- a/mobile/lib/infrastructure/repositories/timeline.repository.dart +++ b/mobile/lib/infrastructure/repositories/timeline.repository.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:drift/drift.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/time_range.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; @@ -12,7 +12,6 @@ import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/map.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/timeline.repository.drift.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -22,7 +21,6 @@ class TimelineMapOptions { final bool includeArchived; final bool withPartners; final int relativeDays; - final TimeRange timeRange; const TimelineMapOptions({ required this.bounds, @@ -30,15 +28,13 @@ class TimelineMapOptions { this.includeArchived = false, this.withPartners = false, this.relativeDays = 0, - this.timeRange = const TimeRange(), }); } -@DriftAccessor() -class TimelineRepository extends DatabaseAccessor with $TimelineRepositoryMixin { - TimelineRepository(super.attachedDatabase); +class DriftTimelineRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; + const DriftTimelineRepository(super._db) : _db = _db; Stream> watchTimelineUserIds(String userId) { final query = _db.partnerEntity.selectOnly() @@ -83,12 +79,11 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor type: row.type, createdAt: row.createdAt, updatedAt: row.updatedAt, - uploadedAt: row.uploadedAt, thumbHash: row.thumbHash, width: row.width, height: row.height, isFavorite: row.isFavorite, - durationMs: row.durationMs, + durationInSeconds: row.durationInSeconds, livePhotoVideoId: row.livePhotoVideoId, stackId: row.stackId, isEdited: row.isEdited, @@ -104,7 +99,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor width: row.width, height: row.height, isFavorite: row.isFavorite, - durationMs: row.durationMs, + durationInSeconds: row.durationInSeconds, orientation: row.orientation, playbackStyle: AssetPlaybackStyle.values[row.playbackStyle], cloudId: row.iCloudId, @@ -141,6 +136,11 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id), useColumns: false, ), + leftOuterJoin( + _db.remoteAssetEntity, + _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), + useColumns: false, + ), ]) ..addColumns([assetCountExp, dateExp]) ..where(_db.localAlbumAssetEntity.albumId.equals(albumId)) @@ -164,12 +164,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor ), leftOuterJoin( _db.remoteAssetEntity, - _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum) & - _db.remoteAssetEntity.ownerId.isInQuery( - _db.selectOnly(_db.authUserEntity) - ..addColumns([_db.authUserEntity.id]) - ..limit(1), - ), + _db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum), useColumns: false, ), ]) @@ -185,8 +180,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => ( bucketSource: () => _watchRemoteAlbumBucket(albumId, groupBy: groupBy), - assetSource: (offset, count) => - _getRemoteAlbumBucketAssets(albumId, groupBy: groupBy, offset: offset, count: count), + assetSource: (offset, count) => _getRemoteAlbumBucketAssets(albumId, offset: offset, count: count), origin: TimelineOrigin.remoteAlbum, ); @@ -240,12 +234,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor .handleError((error) => const []); } - Future> _getRemoteAlbumBucketAssets( - String albumId, { - required int offset, - required int count, - GroupAssetsBy groupBy = GroupAssetsBy.day, - }) async { + Future> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async { final albumData = await (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId))).getSingleOrNull(); // If album doesn't exist (was deleted), return empty list @@ -255,30 +244,30 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor final isAscending = albumData.order == AlbumAssetOrder.asc; - // Correlated subquery picks the first matching local asset by checksum, - // avoiding fan-out when the same photo exists in multiple device albums (#23273). - final localId = subqueryExpression( - _db.localAssetEntity.selectOnly() - ..addColumns([_db.localAssetEntity.id]) - ..where(_db.localAssetEntity.checksum.equalsExp(_db.remoteAssetEntity.checksum)) - ..limit(1), - ); - - final query = _db.remoteAssetEntity.select().addColumns([localId]).join([ + final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([ innerJoin( _db.remoteAlbumAssetEntity, _db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id), useColumns: false, ), + leftOuterJoin( + _db.localAssetEntity, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), + useColumns: false, + ), ])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId)); - query.orderBy( - _assetDateOrder(groupBy, ascending: isAscending).map((order) => order(_db.remoteAssetEntity)).toList(), - ); + if (isAscending) { + query.orderBy([OrderingTerm.asc(_db.remoteAssetEntity.createdAt)]); + } else { + query.orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]); + } query.limit(count, offset: offset); - return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(localId))).get(); + return query + .map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id))) + .get(); } TimelineQuery fromAssets(List assets, TimelineOrigin origin) => ( @@ -326,17 +315,6 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor origin: TimelineOrigin.remoteAssets, ); - TimelineQuery recentlyAdded(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( - filter: (row) => - row.uploadedAt.isNotNull() & - row.deletedAt.isNull() & - row.ownerId.equals(userId) & - (row.visibility.equalsValue(AssetVisibility.timeline) | row.visibility.equalsValue(AssetVisibility.archive)), - origin: TimelineOrigin.recentlyAdded, - groupBy: groupBy, - sortBy: SortAssetsBy.uploaded, - ); - TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder( filter: (row) => row.deletedAt.isNull() & @@ -381,14 +359,13 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor TimelineQuery place(String place, GroupAssetsBy groupBy) => ( bucketSource: () => _watchPlaceBucket(place, groupBy: groupBy), - assetSource: (offset, count) => _getPlaceBucketAssets(place, groupBy: groupBy, offset: offset, count: count), + assetSource: (offset, count) => _getPlaceBucketAssets(place, offset: offset, count: count), origin: TimelineOrigin.place, ); TimelineQuery person(String userId, String personId, GroupAssetsBy groupBy) => ( bucketSource: () => _watchPersonBucket(userId, personId, groupBy: groupBy), - assetSource: (offset, count) => - _getPersonBucketAssets(userId, personId, groupBy: groupBy, offset: offset, count: count), + assetSource: (offset, count) => _getPersonBucketAssets(userId, personId, offset: offset, count: count), origin: TimelineOrigin.person, ); @@ -425,12 +402,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor }).watch(); } - Future> _getPlaceBucketAssets( - String place, { - required int offset, - required int count, - GroupAssetsBy groupBy = GroupAssetsBy.day, - }) { + Future> _getPlaceBucketAssets(String place, {required int offset, required int count}) { final query = _db.remoteAssetEntity.select().join([ innerJoin( @@ -444,28 +416,29 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & _db.remoteExifEntity.city.equals(place), ) - ..orderBy(_assetDateOrder(groupBy).map((order) => order(_db.remoteAssetEntity)).toList()) + ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..limit(count, offset: offset); return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); } Stream> _watchPersonBucket(String userId, String personId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) { - final idQuery = _db.assetFaceEntity.selectOnly() - ..addColumns([_db.assetFaceEntity.assetId]) - ..where( - _db.assetFaceEntity.personId.equals(personId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), - ); - if (groupBy == GroupAssetsBy.none) { final query = _db.remoteAssetEntity.selectOnly() ..addColumns([_db.remoteAssetEntity.id.count()]) + ..join([ + innerJoin( + _db.assetFaceEntity, + _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), + useColumns: false, + ), + ]) ..where( - _db.remoteAssetEntity.id.isInQuery(idQuery) & - _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.ownerId.equals(userId) & - _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline), + _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & + _db.assetFaceEntity.personId.equals(personId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull(), ); return query.map((row) { @@ -479,11 +452,20 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) + ..join([ + innerJoin( + _db.assetFaceEntity, + _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), + useColumns: false, + ), + ]) ..where( - _db.remoteAssetEntity.id.isInQuery(idQuery) & + _db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAssetEntity.ownerId.equals(userId) & _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & - _db.remoteAssetEntity.deletedAt.isNull(), + _db.assetFaceEntity.personId.equals(personId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull(), ) ..groupBy([dateExp]) ..orderBy([OrderingTerm.desc(dateExp)]); @@ -500,28 +482,27 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor String personId, { required int offset, required int count, - GroupAssetsBy groupBy = GroupAssetsBy.day, }) { - final idQuery = _db.assetFaceEntity.selectOnly() - ..addColumns([_db.assetFaceEntity.assetId]) - ..where( - _db.assetFaceEntity.personId.equals(personId) & - _db.assetFaceEntity.isVisible.equals(true) & - _db.assetFaceEntity.deletedAt.isNull(), - ); + final query = + _db.remoteAssetEntity.select().join([ + innerJoin( + _db.assetFaceEntity, + _db.assetFaceEntity.assetId.equalsExp(_db.remoteAssetEntity.id), + useColumns: false, + ), + ]) + ..where( + _db.remoteAssetEntity.deletedAt.isNull() & + _db.remoteAssetEntity.ownerId.equals(userId) & + _db.remoteAssetEntity.visibility.equalsValue(AssetVisibility.timeline) & + _db.assetFaceEntity.personId.equals(personId) & + _db.assetFaceEntity.isVisible.equals(true) & + _db.assetFaceEntity.deletedAt.isNull(), + ) + ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) + ..limit(count, offset: offset); - final query = _db.remoteAssetEntity.select() - ..where( - (row) => - row.id.isInQuery(idQuery) & - row.deletedAt.isNull() & - row.ownerId.equals(userId) & - row.visibility.equalsValue(AssetVisibility.timeline), - ) - ..orderBy(_assetDateOrder(groupBy)) - ..limit(count, offset: offset); - - return query.map((row) => row.toDto()).get(); + return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get(); } TimelineQuery map(List userIds, TimelineMapOptions options, GroupAssetsBy groupBy) => ( @@ -568,21 +549,8 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor query.where(_db.remoteAssetEntity.isFavorite.equals(true)); } - final timeRange = options.timeRange; - - final hasCustomRange = timeRange.from != null || timeRange.to != null; - - if (hasCustomRange) { - if (timeRange.from != null) { - query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(timeRange.from!)); - } - - if (timeRange.to != null) { - query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(timeRange.to!)); - } - } else if (options.relativeDays > 0) { + if (options.relativeDays != 0) { final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays)); - query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate)); } @@ -623,21 +591,8 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor query.where(_db.remoteAssetEntity.isFavorite.equals(true)); } - final timeRange = options.timeRange; - - final hasCustomRange = timeRange.from != null || timeRange.to != null; - - if (hasCustomRange) { - if (timeRange.from != null) { - query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(timeRange.from!)); - } - - if (timeRange.to != null) { - query.where(_db.remoteAssetEntity.createdAt.isSmallerOrEqualValue(timeRange.to!)); - } - } else if (options.relativeDays > 0) { + if (options.relativeDays != 0) { final cutoffDate = DateTime.now().toUtc().subtract(Duration(days: options.relativeDays)); - query.where(_db.remoteAssetEntity.createdAt.isBiggerOrEqualValue(cutoffDate)); } @@ -650,12 +605,11 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor required TimelineOrigin origin, GroupAssetsBy groupBy = GroupAssetsBy.day, bool joinLocal = false, - SortAssetsBy sortBy = SortAssetsBy.taken, }) { return ( - bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy, sortBy: sortBy), + bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy), assetSource: (offset, count) => - _getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal, sortBy: sortBy), + _getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal), origin: origin, ); } @@ -663,7 +617,6 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor Stream> _watchRemoteBucket({ required Expression Function($RemoteAssetEntityTable row) filter, GroupAssetsBy groupBy = GroupAssetsBy.day, - SortAssetsBy sortBy = SortAssetsBy.taken, }) { if (groupBy == GroupAssetsBy.none) { final query = _db.remoteAssetEntity.count(where: filter); @@ -671,7 +624,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor } final assetCountExp = _db.remoteAssetEntity.id.count(); - final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy, sortBy: sortBy); + final dateExp = _db.remoteAssetEntity.effectiveCreatedAt(groupBy); final query = _db.remoteAssetEntity.selectOnly() ..addColumns([assetCountExp, dateExp]) @@ -692,7 +645,6 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor required int offset, required int count, bool joinLocal = false, - SortAssetsBy sortBy = SortAssetsBy.taken, }) { if (joinLocal) { final query = @@ -705,11 +657,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor ]) ..addColumns([_db.localAssetEntity.id]) ..where(filter(_db.remoteAssetEntity)) - ..orderBy([ - OrderingTerm.desc( - sortBy == SortAssetsBy.uploaded ? _db.remoteAssetEntity.uploadedAt : _db.remoteAssetEntity.createdAt, - ), - ]) + ..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)]) ..limit(count, offset: offset); return query @@ -718,7 +666,7 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor } else { final query = _db.remoteAssetEntity.select() ..where(filter) - ..orderBy([(row) => OrderingTerm.desc(sortBy == SortAssetsBy.uploaded ? row.uploadedAt : row.createdAt)]) + ..orderBy([(row) => OrderingTerm.desc(row.createdAt)]) ..limit(count, offset: offset); return query.map((row) => row.toDto()).get(); @@ -726,14 +674,15 @@ class TimelineRepository extends DatabaseAccessor with $TimelineRepositor } } -List _generateBuckets(int count) => count == 0 ? const [] : [Bucket(assetCount: count)]; - -List _assetDateOrder(GroupAssetsBy groupBy, {bool ascending = false}) { - OrderingTerm order(Expression exp) => ascending ? OrderingTerm.asc(exp) : OrderingTerm.desc(exp); - return [ - if (groupBy != GroupAssetsBy.none) (row) => order(row.effectiveCreatedAt(groupBy)), - (row) => order(row.createdAt), - ]; +List _generateBuckets(int count) { + final buckets = List.filled( + (count / kTimelineNoneSegmentSize).ceil(), + const Bucket(assetCount: kTimelineNoneSegmentSize), + ); + if (count % kTimelineNoneSegmentSize != 0) { + buckets[buckets.length - 1] = Bucket(assetCount: count % kTimelineNoneSegmentSize); + } + return buckets; } extension on Expression { @@ -751,13 +700,8 @@ extension on Expression { } extension on $RemoteAssetEntityTable { - Expression effectiveCreatedAt(GroupAssetsBy groupBy, {SortAssetsBy sortBy = SortAssetsBy.taken}) { - if (sortBy == SortAssetsBy.uploaded) { - return uploadedAt.dateFmt(groupBy, toLocal: true); - } - - return coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]); - } + Expression effectiveCreatedAt(GroupAssetsBy groupBy) => + coalesce([localDateTime.dateFmt(groupBy), createdAt.dateFmt(groupBy, toLocal: true)]); } extension on String { diff --git a/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart b/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart index ee762fd330..1195256f5e 100644 --- a/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/trashed_local_asset.repository.dart @@ -8,20 +8,13 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.d import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.drift.dart'; typedef TrashedAsset = ({String albumId, LocalAsset asset}); -@DriftAccessor() -class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedLocalAssetRepositoryMixin { - TrashedLocalAssetRepository(super.attachedDatabase); +class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository { + final Drift _db; - Drift get _db => attachedDatabase; - - /// Matches remote_asset_entity rows owned by the current user. The asset is unique over (owner, checksum), - /// so partners can have a duplicate checksum - Expression get _ownedByCurrentUser => - _db.remoteAssetEntity.ownerId.isInQuery(_db.selectOnly(_db.authUserEntity)..addColumns([_db.authUserEntity.id])); + const DriftTrashedLocalAssetRepository(this._db) : super(_db); Future updateHashes(Map hashes) { if (hashes.isEmpty) { @@ -52,7 +45,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL await (_db.select(_db.trashedLocalAssetEntity).join([ innerJoin( _db.remoteAssetEntity, - _db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum) & _ownedByCurrentUser, + _db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum), ), ])..where( _db.trashedLocalAssetEntity.source.equalsValue(TrashOrigin.remoteSync) & @@ -73,7 +66,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL return; } final assetIds = trashedAssets.map((e) => e.asset.id).toSet(); - final Map localChecksumById = await _getCachedChecksums(assetIds); + Map localChecksumById = await _getCachedChecksums(assetIds); return _db.transaction(() async { await _db.batch((batch) { @@ -89,7 +82,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL updatedAt: Value(item.asset.updatedAt), width: Value(item.asset.width), height: Value(item.asset.height), - durationMs: Value(item.asset.durationMs), + durationInSeconds: Value(item.asset.durationInSeconds), isFavorite: Value(item.asset.isFavorite), orientation: Value(item.asset.orientation), playbackStyle: Value(item.asset.playbackStyle), @@ -152,7 +145,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL type: Value(asset.type), width: Value(asset.width), height: Value(asset.height), - durationMs: Value(asset.durationMs), + durationInSeconds: Value(asset.durationInSeconds), isFavorite: Value(asset.isFavorite), orientation: Value(asset.orientation), playbackStyle: Value(asset.playbackStyle), @@ -200,7 +193,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL updatedAt: Value(e.updatedAt), width: Value(e.width), height: Value(e.height), - durationMs: Value(e.durationMs), + durationInSeconds: Value(e.durationInSeconds), checksum: Value(e.checksum), isFavorite: Value(e.isFavorite), orientation: Value(e.orientation), @@ -251,7 +244,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL updatedAt: Value(e.asset.updatedAt), width: Value(e.asset.width), height: Value(e.asset.height), - durationMs: Value(e.asset.durationMs), + durationInSeconds: Value(e.asset.durationInSeconds), checksum: Value(e.asset.checksum), isFavorite: Value(e.asset.isFavorite), orientation: Value(e.asset.orientation), @@ -280,7 +273,7 @@ class TrashedLocalAssetRepository extends DatabaseAccessor with $TrashedL innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)), leftOuterJoin( _db.remoteAssetEntity, - _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum) & _ownedByCurrentUser, + _db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum), ), ])..where( _db.localAlbumEntity.backupSelection.equalsValue(BackupSelection.selected) & diff --git a/mobile/lib/infrastructure/repositories/user.repository.dart b/mobile/lib/infrastructure/repositories/user.repository.dart index 157851ab58..d4eb1ceed6 100644 --- a/mobile/lib/infrastructure/repositories/user.repository.dart +++ b/mobile/lib/infrastructure/repositories/user.repository.dart @@ -1,33 +1,81 @@ import 'package:drift/drift.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user_metadata.model.dart'; import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/mapper.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart'; +import 'package:isar/isar.dart'; -@DriftAccessor() -class UserRepository extends DatabaseAccessor with $UserRepositoryMixin { - UserRepository(super.attachedDatabase); +class IsarUserRepository extends IsarDatabaseRepository { + final Isar _db; + const IsarUserRepository(super.db) : _db = db; - Drift get _db => attachedDatabase; + Future delete(List ids) async { + await transaction(() async { + await _db.users.deleteAllById(ids); + }); + } - Stream> getAll() => _db.select(_db.userEntity).map(mapToUser).watch(); + Future deleteAll() async { + await transaction(() async { + await _db.users.clear(); + }); + } + + Future> getAll({SortUserBy? sortBy}) async { + return (await _db.users + .where() + .optional( + sortBy != null, + (query) => switch (sortBy!) { + SortUserBy.id => query.sortById(), + }, + ) + .findAll()) + .map((u) => u.toDto()) + .toList(); + } + + Future getByUserId(String id) async { + return (await _db.users.getById(id))?.toDto(); + } + + Future> getByUserIds(List ids) async { + return (await _db.users.getAllById(ids)).map((u) => u?.toDto()).toList(); + } + + Future insert(UserDto user) async { + await transaction(() async { + await _db.users.put(entity.User.fromDto(user)); + }); + return true; + } + + Future update(UserDto user) async { + await transaction(() async { + await _db.users.put(entity.User.fromDto(user)); + }); + return user; + } + + Future updateAll(List users) async { + await transaction(() async { + await _db.users.putAll(users.map(entity.User.fromDto).toList()); + }); + return true; + } } -@DriftAccessor() -class AuthUserRepository extends DatabaseAccessor with $AuthUserRepositoryMixin { - AuthUserRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; +class DriftAuthUserRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftAuthUserRepository(super.db) : _db = db; Future get(String id) async { final user = await _db.managers.authUserEntity.filter((user) => user.id.equals(id)).getSingleOrNull(); - if (user == null) { - return null; - } + if (user == null) return null; final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(id)); final metadata = await query.map((row) => row.toDto()).get(); @@ -69,7 +117,6 @@ extension on AuthUserEntityData { id: id, email: email, name: name, - updatedAt: profileChangedAt, profileChangedAt: profileChangedAt, hasProfileImage: hasProfileImage, avatarColor: avatarColor, diff --git a/mobile/lib/infrastructure/repositories/user_api.repository.dart b/mobile/lib/infrastructure/repositories/user_api.repository.dart index aa645f2a4a..d21a1b71a6 100644 --- a/mobile/lib/infrastructure/repositories/user_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/user_api.repository.dart @@ -12,9 +12,7 @@ class UserApiRepository extends ApiRepository { Future getMyUser() async { final (adminDto, preferenceDto) = await (_api.getMyUser(), _api.getMyPreferences()).wait; - if (adminDto == null) { - return null; - } + if (adminDto == null) return null; return UserConverter.fromAdminDto(adminDto, preferenceDto); } diff --git a/mobile/lib/infrastructure/repositories/user_metadata.repository.dart b/mobile/lib/infrastructure/repositories/user_metadata.repository.dart index 001e482f5c..173ec10b97 100644 --- a/mobile/lib/infrastructure/repositories/user_metadata.repository.dart +++ b/mobile/lib/infrastructure/repositories/user_metadata.repository.dart @@ -3,11 +3,9 @@ import 'package:immich_mobile/domain/models/user_metadata.model.dart'; import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -@DriftAccessor() -class UserMetadataRepository extends DatabaseAccessor { - UserMetadataRepository(super.attachedDatabase); - - Drift get _db => attachedDatabase; +class DriftUserMetadataRepository extends DriftDatabaseRepository { + final Drift _db; + const DriftUserMetadataRepository(this._db) : super(_db); Future> getUserMetadata(String userId) { final query = _db.userMetadataEntity.select()..where((e) => e.userId.equals(userId)); diff --git a/mobile/lib/infrastructure/utils/asset.mixin.dart b/mobile/lib/infrastructure/utils/asset.mixin.dart index 17e7e5d707..4e14a2919f 100644 --- a/mobile/lib/infrastructure/utils/asset.mixin.dart +++ b/mobile/lib/infrastructure/utils/asset.mixin.dart @@ -8,5 +8,5 @@ mixin AssetEntityMixin on Table { DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); IntColumn get width => integer().nullable()(); IntColumn get height => integer().nullable()(); - IntColumn get durationMs => integer().nullable()(); + IntColumn get durationInSeconds => integer().nullable()(); } diff --git a/mobile/lib/infrastructure/utils/exif.converter.dart b/mobile/lib/infrastructure/utils/exif.converter.dart index d47c9cc97a..50639e8e42 100644 --- a/mobile/lib/infrastructure/utils/exif.converter.dart +++ b/mobile/lib/infrastructure/utils/exif.converter.dart @@ -5,24 +5,24 @@ import 'package:openapi/api.dart'; abstract final class ExifDtoConverter { static ExifInfo fromDto(ExifResponseDto dto) { return ExifInfo( - fileSize: dto.fileSizeInByte.orElse(null), - description: dto.description.orElse(null), - orientation: dto.orientation.orElse(null), - timeZone: dto.timeZone.orElse(null), - dateTimeOriginal: dto.dateTimeOriginal.orElse(null), - isFlipped: isOrientationFlipped(dto.orientation.orElse(null)), - latitude: dto.latitude.orElse(null)?.toDouble(), - longitude: dto.longitude.orElse(null)?.toDouble(), - city: dto.city.orElse(null), - state: dto.state.orElse(null), - country: dto.country.orElse(null), - make: dto.make.orElse(null), - model: dto.model.orElse(null), - lens: dto.lensModel.orElse(null), - f: dto.fNumber.orElse(null)?.toDouble(), - mm: dto.focalLength.orElse(null)?.toDouble(), - iso: dto.iso.orElse(null), - exposureSeconds: exposureTimeToSeconds(dto.exposureTime.orElse(null)), + fileSize: dto.fileSizeInByte, + description: dto.description, + orientation: dto.orientation, + timeZone: dto.timeZone, + dateTimeOriginal: dto.dateTimeOriginal, + isFlipped: isOrientationFlipped(dto.orientation), + latitude: dto.latitude?.toDouble(), + longitude: dto.longitude?.toDouble(), + city: dto.city, + state: dto.state, + country: dto.country, + make: dto.make, + model: dto.model, + lens: dto.lensModel, + f: dto.fNumber?.toDouble(), + mm: dto.focalLength?.toDouble(), + iso: dto.iso?.toInt(), + exposureSeconds: exposureTimeToSeconds(dto.exposureTime), ); } @@ -40,7 +40,7 @@ abstract final class ExifDtoConverter { if (second == null) { return null; } - final double? value = double.tryParse(second); + double? value = double.tryParse(second); if (value != null) { return value; } diff --git a/mobile/lib/infrastructure/utils/user.converter.dart b/mobile/lib/infrastructure/utils/user.converter.dart index 7e8a2c6fcc..826649b247 100644 --- a/mobile/lib/infrastructure/utils/user.converter.dart +++ b/mobile/lib/infrastructure/utils/user.converter.dart @@ -40,7 +40,7 @@ abstract final class UserConverter { updatedAt: DateTime.now(), avatarColor: dto.avatarColor.toAvatarColor(), memoryEnabled: false, - inTimeline: dto.inTimeline.orElse(null) ?? false, + inTimeline: dto.inTimeline ?? false, isPartnerSharedBy: false, isPartnerSharedWith: false, profileChangedAt: dto.profileChangedAt, diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index d94306475f..7e7c709eeb 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -10,12 +10,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/constants/locales.dart'; import 'package:immich_mobile/domain/services/background_worker.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; @@ -23,16 +24,17 @@ import 'package:immich_mobile/pages/common/splash_screen.page.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/theme.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/routing/app_navigation_observer.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/deep_link.service.dart'; +import 'package:immich_mobile/services/local_notification.service.dart'; import 'package:immich_mobile/theme/dynamic_theme.dart'; import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; @@ -51,13 +53,23 @@ void main() async { ImmichWidgetsBinding(); unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock()); await EasyLocalization.ensureInitialized(); - final (drift, _) = await Bootstrap.initDomain(); + final (isar, drift, logDb) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDb); await initApp(); // Warm-up isolate pool for worker manager await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5)); - await migrateDatabaseIfNeeded(drift); + await migrateDatabaseIfNeeded(isar, drift); - runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget())); + runApp( + ProviderScope( + overrides: [ + dbProvider.overrideWithValue(isar), + isarProvider.overrideWithValue(isar), + driftProvider.overrideWith(driftOverride(drift)), + ], + child: const MainWidget(), + ), + ); } catch (error, stack) { runApp(BootstrapErrorWidget(error: error.toString(), stack: stack.toString())); } @@ -83,7 +95,7 @@ Future initApp() async { FlutterError.presentError(details); log.severe( 'FlutterError - Catch all', - "$details\nException: ${details.exception}\nLibrary: ${details.library}\nContext: ${details.context}", + "${details.toString()}\nException: ${details.exception}\nLibrary: ${details.library}\nContext: ${details.context}", details.stack, ); }; @@ -127,51 +139,44 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve switch (state) { case AppLifecycleState.resumed: dPrint(() => "[APP STATE] resumed"); - unawaited(ref.read(appStateProvider.notifier).handleAppResume()); - unawaited(ref.read(viewIntentHandlerProvider).onAppResumed()); + ref.read(appStateProvider.notifier).handleAppResume(); + break; case AppLifecycleState.inactive: dPrint(() => "[APP STATE] inactive"); ref.read(appStateProvider.notifier).handleAppInactivity(); + break; case AppLifecycleState.paused: dPrint(() => "[APP STATE] paused"); - unawaited(ref.read(appStateProvider.notifier).handleAppPause()); + ref.read(appStateProvider.notifier).handleAppPause(); + break; case AppLifecycleState.detached: dPrint(() => "[APP STATE] detached"); - unawaited(ref.read(appStateProvider.notifier).handleAppDetached()); + ref.read(appStateProvider.notifier).handleAppDetached(); + break; case AppLifecycleState.hidden: dPrint(() => "[APP STATE] hidden"); ref.read(appStateProvider.notifier).handleAppHidden(); + break; } } Future initApp() async { WidgetsBinding.instance.addObserver(this); + // Draw the app from edge to edge unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); - await _setNavigationBarColor(); - await FlutterLocalNotificationsPlugin().initialize( - const InitializationSettings( - android: AndroidInitializationSettings('@drawable/notification_icon'), - iOS: DarwinInitializationSettings(), - ), - ); - } - - Future _setNavigationBarColor() async { + // Sets the navigation bar color SystemUiOverlayStyle overlayStyle = const SystemUiOverlayStyle(systemNavigationBarColor: Colors.transparent); if (Platform.isAndroid) { // Android 8 does not support transparent app bars final info = await DeviceInfoPlugin().androidInfo; - if (!mounted) { - return; - } - if (info.version.sdkInt <= 26) { overlayStyle = context.isDarkTheme ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light; } } SystemChrome.setSystemUIOverlayStyle(overlayStyle); + await ref.read(localNotificationService).setup(); } Future _deepLinkBuilder(PlatformDeepLink deepLink) async { @@ -180,32 +185,19 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve final isColdStart = currentRouteName == null || currentRouteName == SplashScreenRoute.name; - PageRouteInfo? route; if (deepLink.uri.scheme == "immich") { - route = await deepLinkHandler.handleScheme(deepLink, ref); - } else if (deepLink.uri.host == "my.immich.app") { - route = await deepLinkHandler.handleMyImmichApp(deepLink, ref); - } else { - return DeepLink.path(deepLink.path); + final proposedRoute = await deepLinkHandler.handleScheme(deepLink, ref, isColdStart); + + return proposedRoute; } - if (route == null) { - return isColdStart ? DeepLink.defaultPath : DeepLink.none; + if (deepLink.uri.host == "my.immich.app") { + final proposedRoute = await deepLinkHandler.handleMyImmichApp(deepLink, ref, isColdStart); + + return proposedRoute; } - // We need to replace the route if the destination is the current route - if (!isColdStart) { - unawaited( - ref.read(appRouterProvider).pushAndPopUntil(route, predicate: (r) => r.settings.name != route!.routeName), - ); - return DeepLink.none; - } - - return DeepLink([ - // we need something to segue back to if the app was cold started - if (isColdStart) const TabShellRoute(children: [MainTimelineRoute()]), - route, - ]); + return DeepLink.path(deepLink.path); } @override @@ -218,25 +210,28 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve } @override - void initState() { + initState() { super.initState(); - unawaited(initApp().then((_) => dPrint(() => "App Init Completed"))); + initApp().then((_) => dPrint(() => "App Init Completed")); WidgetsBinding.instance.addPostFrameCallback((_) { // needs to be delayed so that EasyLocalization is working - unawaited(ref.read(backgroundWorkerFgServiceProvider).enable()); - if (Platform.isAndroid) { - unawaited( + if (Store.isBetaTimelineEnabled) { + ref.read(backgroundServiceProvider).disableService(); + ref.read(backgroundWorkerFgServiceProvider).enable(); + if (Platform.isAndroid) { ref .read(backgroundWorkerFgServiceProvider) .saveNotificationMessage( StaticTranslations.instance.uploading_media, StaticTranslations.instance.backup_background_service_default_notification, - ), - ); + ); + } + } else { + ref.read(backgroundWorkerFgServiceProvider).disable(); + ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); } }); - ref.read(viewIntentHandlerProvider).init(); ref.read(shareIntentUploadProvider.notifier).init(); } @@ -249,7 +244,7 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve @override void reassemble() { if (kDebugMode) { - unawaited(NetworkRepository.init()); + NetworkRepository.init(); } super.reassemble(); } @@ -264,24 +259,22 @@ class ImmichAppState extends ConsumerState with WidgetsBindingObserve child: MaterialApp.router( title: 'Immich', debugShowCheckedModeBanner: true, - scaffoldMessengerKey: scaffoldMessengerKey, localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - themeMode: ref.watch(appConfigProvider.select((config) => config.theme.mode)), + themeMode: ref.watch(immichThemeModeProvider), darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale), theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale), builder: (context, child) => ImmichTranslationProvider( translations: ImmichTranslations( - submit: context.t.submit, - password: context.t.password, - undo: context.t.undo, + submit: "submit".t(context: context), + password: "password".t(context: context), ), child: ImmichThemeProvider(colorScheme: context.colorScheme, child: child!), ), routerConfig: router.config( deepLinkBuilder: _deepLinkBuilder, - navigatorObservers: () => [AppNavigationObserver(ref: ref), TransitioningRouteObserver()], + navigatorObservers: () => [AppNavigationObserver(ref: ref)], ), ), ); diff --git a/mobile/lib/models/activities/activity.model.dart b/mobile/lib/models/activities/activity.model.dart index 9a589ef729..38c2bef77a 100644 --- a/mobile/lib/models/activities/activity.model.dart +++ b/mobile/lib/models/activities/activity.model.dart @@ -1,21 +1,63 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -part 'activity.model.freezed.dart'; - enum ActivityType { comment, like } -@freezed -abstract class Activity with _$Activity { - const factory Activity({ - required String id, +class Activity { + final String id; + final String? assetId; + final String? comment; + final DateTime createdAt; + final ActivityType type; + final UserDto user; + + const Activity({ + required this.id, + this.assetId, + this.comment, + required this.createdAt, + required this.type, + required this.user, + }); + + Activity copyWith({ + String? id, String? assetId, String? comment, - required DateTime createdAt, - required ActivityType type, - required UserDto user, - }) = _Activity; + DateTime? createdAt, + ActivityType? type, + UserDto? user, + }) { + return Activity( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + comment: comment ?? this.comment, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + user: user ?? this.user, + ); + } + + @override + String toString() { + return 'Activity(id: $id, assetId: $assetId, comment: $comment, createdAt: $createdAt, type: $type, user: $user)'; + } + + @override + bool operator ==(covariant Activity other) { + if (identical(this, other)) return true; + + return other.id == id && + other.assetId == assetId && + other.comment == comment && + other.createdAt == createdAt && + other.type == type && + other.user == user; + } + + @override + int get hashCode { + return id.hashCode ^ assetId.hashCode ^ comment.hashCode ^ createdAt.hashCode ^ type.hashCode ^ user.hashCode; + } } class ActivityStats { diff --git a/mobile/lib/models/albums/album_add_asset_response.model.dart b/mobile/lib/models/albums/album_add_asset_response.model.dart new file mode 100644 index 0000000000..38dd989af5 --- /dev/null +++ b/mobile/lib/models/albums/album_add_asset_response.model.dart @@ -0,0 +1,38 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; + +import 'package:collection/collection.dart'; + +class AlbumAddAssetsResponse { + List alreadyInAlbum; + int successfullyAdded; + + AlbumAddAssetsResponse({required this.alreadyInAlbum, required this.successfullyAdded}); + + AlbumAddAssetsResponse copyWith({List? alreadyInAlbum, int? successfullyAdded}) { + return AlbumAddAssetsResponse( + alreadyInAlbum: alreadyInAlbum ?? this.alreadyInAlbum, + successfullyAdded: successfullyAdded ?? this.successfullyAdded, + ); + } + + Map toMap() { + return {'alreadyInAlbum': alreadyInAlbum, 'successfullyAdded': successfullyAdded}; + } + + String toJson() => json.encode(toMap()); + + @override + String toString() => 'AddAssetsResponse(alreadyInAlbum: $alreadyInAlbum, successfullyAdded: $successfullyAdded)'; + + @override + bool operator ==(covariant AlbumAddAssetsResponse other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.alreadyInAlbum, alreadyInAlbum) && other.successfullyAdded == successfullyAdded; + } + + @override + int get hashCode => alreadyInAlbum.hashCode ^ successfullyAdded.hashCode; +} diff --git a/mobile/lib/models/albums/album_viewer_page_state.model.dart b/mobile/lib/models/albums/album_viewer_page_state.model.dart new file mode 100644 index 0000000000..70427899ae --- /dev/null +++ b/mobile/lib/models/albums/album_viewer_page_state.model.dart @@ -0,0 +1,60 @@ +import 'dart:convert'; + +class AlbumViewerPageState { + final bool isEditAlbum; + final String editTitleText; + final String editDescriptionText; + + const 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, + ); + } + + Map toMap() { + final result = {}; + + result.addAll({'isEditAlbum': isEditAlbum}); + result.addAll({'editTitleText': editTitleText}); + result.addAll({'editDescriptionText': editDescriptionText}); + + return result; + } + + factory AlbumViewerPageState.fromMap(Map map) { + return AlbumViewerPageState( + isEditAlbum: map['isEditAlbum'] ?? false, + editTitleText: map['editTitleText'] ?? '', + editDescriptionText: map['editDescriptionText'] ?? '', + ); + } + + String toJson() => json.encode(toMap()); + + factory AlbumViewerPageState.fromJson(String source) => AlbumViewerPageState.fromMap(json.decode(source)); + + @override + String toString() => + 'AlbumViewerPageState(isEditAlbum: $isEditAlbum, editTitleText: $editTitleText, editDescriptionText: $editDescriptionText)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AlbumViewerPageState && + other.isEditAlbum == isEditAlbum && + other.editTitleText == editTitleText && + other.editDescriptionText == editDescriptionText; + } + + @override + int get hashCode => isEditAlbum.hashCode ^ editTitleText.hashCode ^ editDescriptionText.hashCode; +} diff --git a/mobile/lib/models/albums/asset_selection_page_result.model.dart b/mobile/lib/models/albums/asset_selection_page_result.model.dart new file mode 100644 index 0000000000..cc750f397f --- /dev/null +++ b/mobile/lib/models/albums/asset_selection_page_result.model.dart @@ -0,0 +1,18 @@ +import 'package:collection/collection.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; + +class AssetSelectionPageResult { + final Set selectedAssets; + + const AssetSelectionPageResult({required this.selectedAssets}); + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final setEquals = const DeepCollectionEquality().equals; + + return other is AssetSelectionPageResult && setEquals(other.selectedAssets, selectedAssets); + } + + @override + int get hashCode => selectedAssets.hashCode; +} diff --git a/mobile/lib/models/asset_selection_state.dart b/mobile/lib/models/asset_selection_state.dart new file mode 100644 index 0000000000..aded3064ce --- /dev/null +++ b/mobile/lib/models/asset_selection_state.dart @@ -0,0 +1,47 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; + +class AssetSelectionState { + final bool hasRemote; + final bool hasLocal; + final bool hasMerged; + final int selectedCount; + + const AssetSelectionState({ + this.hasRemote = false, + this.hasLocal = false, + this.hasMerged = false, + this.selectedCount = 0, + }); + + AssetSelectionState copyWith({bool? hasRemote, bool? hasLocal, bool? hasMerged, int? selectedCount}) { + return AssetSelectionState( + hasRemote: hasRemote ?? this.hasRemote, + hasLocal: hasLocal ?? this.hasLocal, + hasMerged: hasMerged ?? this.hasMerged, + selectedCount: selectedCount ?? this.selectedCount, + ); + } + + AssetSelectionState.fromSelection(Set selection) + : hasLocal = selection.any((e) => e.storage == AssetState.local), + hasMerged = selection.any((e) => e.storage == AssetState.merged), + hasRemote = selection.any((e) => e.storage == AssetState.remote), + selectedCount = selection.length; + + @override + String toString() => + 'SelectionAssetState(hasRemote: $hasRemote, hasLocal: $hasLocal, hasMerged: $hasMerged, selectedCount: $selectedCount)'; + + @override + bool operator ==(covariant AssetSelectionState other) { + if (identical(this, other)) return true; + + return other.hasRemote == hasRemote && + other.hasLocal == hasLocal && + other.hasMerged == hasMerged && + other.selectedCount == selectedCount; + } + + @override + int get hashCode => hasRemote.hashCode ^ hasLocal.hashCode ^ hasMerged.hashCode ^ selectedCount.hashCode; +} diff --git a/mobile/lib/models/auth/auth_state.model.dart b/mobile/lib/models/auth/auth_state.model.dart index 0a3bf983b2..0d8357d66d 100644 --- a/mobile/lib/models/auth/auth_state.model.dart +++ b/mobile/lib/models/auth/auth_state.model.dart @@ -1,17 +1,69 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +class AuthState { + final String deviceId; + final String userId; + final String userEmail; + final bool isAuthenticated; + final String name; + final bool isAdmin; + final String profileImagePath; -part 'auth_state.model.freezed.dart'; + const AuthState({ + required this.deviceId, + required this.userId, + required this.userEmail, + required this.isAuthenticated, + required this.name, + required this.isAdmin, + required this.profileImagePath, + }); -@freezed -abstract class AuthState with _$AuthState { - const factory AuthState({ - required String deviceId, - required String userId, - required String userEmail, - required bool isAuthenticated, - required String name, - required bool isAdmin, - required String profileImagePath, - }) = _AuthState; + AuthState copyWith({ + String? deviceId, + String? userId, + String? userEmail, + bool? isAuthenticated, + String? name, + bool? isAdmin, + String? profileImagePath, + }) { + return AuthState( + deviceId: deviceId ?? this.deviceId, + userId: userId ?? this.userId, + userEmail: userEmail ?? this.userEmail, + isAuthenticated: isAuthenticated ?? this.isAuthenticated, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + profileImagePath: profileImagePath ?? this.profileImagePath, + ); + } + + @override + String toString() { + return 'AuthenticationState(deviceId: $deviceId, userId: $userId, userEmail: $userEmail, isAuthenticated: $isAuthenticated, name: $name, isAdmin: $isAdmin, profileImagePath: $profileImagePath)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AuthState && + other.deviceId == deviceId && + other.userId == userId && + other.userEmail == userEmail && + other.isAuthenticated == isAuthenticated && + other.name == name && + other.isAdmin == isAdmin && + other.profileImagePath == profileImagePath; + } + + @override + int get hashCode { + return deviceId.hashCode ^ + userId.hashCode ^ + userEmail.hashCode ^ + isAuthenticated.hashCode ^ + name.hashCode ^ + isAdmin.hashCode ^ + profileImagePath.hashCode; + } } diff --git a/mobile/lib/models/auth/auxilary_endpoint.model.dart b/mobile/lib/models/auth/auxilary_endpoint.model.dart index 8686ae9461..c7f472e111 100644 --- a/mobile/lib/models/auth/auxilary_endpoint.model.dart +++ b/mobile/lib/models/auth/auxilary_endpoint.model.dart @@ -1,14 +1,32 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:freezed_annotation/freezed_annotation.dart'; +class AuxilaryEndpoint { + final String url; + final AuxCheckStatus status; -part 'auxilary_endpoint.model.freezed.dart'; + const AuxilaryEndpoint({required this.url, required this.status}); -@Freezed(fromJson: false, toJson: false) -abstract class AuxilaryEndpoint with _$AuxilaryEndpoint { - const AuxilaryEndpoint._(); + AuxilaryEndpoint copyWith({String? url, AuxCheckStatus? status}) { + return AuxilaryEndpoint(url: url ?? this.url, status: status ?? this.status); + } - const factory AuxilaryEndpoint({required String url, required AuxCheckStatus status}) = _AuxilaryEndpoint; + @override + String toString() => 'AuxilaryEndpoint(url: $url, status: $status)'; + + @override + bool operator ==(covariant AuxilaryEndpoint other) { + if (identical(this, other)) return true; + + return other.url == url && other.status == status; + } + + @override + int get hashCode => url.hashCode ^ status.hashCode; + + Map toMap() { + return {'url': url, 'status': status.toMap()}; + } factory AuxilaryEndpoint.fromMap(Map map) { return AuxilaryEndpoint( @@ -17,23 +35,48 @@ abstract class AuxilaryEndpoint with _$AuxilaryEndpoint { ); } + String toJson() => json.encode(toMap()); + factory AuxilaryEndpoint.fromJson(String source) => AuxilaryEndpoint.fromMap(json.decode(source) as Map); } -// TODO(agg23): Should be an enum -@freezed -abstract class AuxCheckStatus with _$AuxCheckStatus { - const AuxCheckStatus._(); +class AuxCheckStatus { + final String name; + const AuxCheckStatus({required this.name}); + const AuxCheckStatus._(this.name); - const factory AuxCheckStatus({required String name}) = _AuxCheckStatus; + static const loading = AuxCheckStatus._('loading'); + static const valid = AuxCheckStatus._('valid'); + static const error = AuxCheckStatus._('error'); + static const unknown = AuxCheckStatus._('unknown'); - static const loading = AuxCheckStatus(name: 'loading'); - static const valid = AuxCheckStatus(name: 'valid'); - static const error = AuxCheckStatus(name: 'error'); - static const unknown = AuxCheckStatus(name: 'unknown'); + @override + bool operator ==(covariant AuxCheckStatus other) { + if (identical(this, other)) return true; + + return other.name == name; + } + + @override + int get hashCode => name.hashCode; + + AuxCheckStatus copyWith({String? name}) { + return AuxCheckStatus(name: name ?? this.name); + } + + Map toMap() { + return {'name': name}; + } factory AuxCheckStatus.fromMap(Map map) { return AuxCheckStatus(name: map['name'] as String); } + + String toJson() => json.encode(toMap()); + + factory AuxCheckStatus.fromJson(String source) => AuxCheckStatus.fromMap(json.decode(source) as Map); + + @override + String toString() => 'AuxCheckStatus(name: $name)'; } diff --git a/mobile/lib/models/auth/biometric_status.model.dart b/mobile/lib/models/auth/biometric_status.model.dart index 691fee3468..ad2b06be04 100644 --- a/mobile/lib/models/auth/biometric_status.model.dart +++ b/mobile/lib/models/auth/biometric_status.model.dart @@ -1,10 +1,30 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:local_auth/local_auth.dart'; -part 'biometric_status.model.freezed.dart'; +class BiometricStatus { + final List availableBiometrics; + final bool canAuthenticate; -@freezed -abstract class BiometricStatus with _$BiometricStatus { - const factory BiometricStatus({required List availableBiometrics, required bool canAuthenticate}) = - _BiometricStatus; + const BiometricStatus({required this.availableBiometrics, required this.canAuthenticate}); + + @override + String toString() => 'BiometricStatus(availableBiometrics: $availableBiometrics, canAuthenticate: $canAuthenticate)'; + + BiometricStatus copyWith({List? availableBiometrics, bool? canAuthenticate}) { + return BiometricStatus( + availableBiometrics: availableBiometrics ?? this.availableBiometrics, + canAuthenticate: canAuthenticate ?? this.canAuthenticate, + ); + } + + @override + bool operator ==(covariant BiometricStatus other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.availableBiometrics, availableBiometrics) && other.canAuthenticate == canAuthenticate; + } + + @override + int get hashCode => availableBiometrics.hashCode ^ canAuthenticate.hashCode; } diff --git a/mobile/lib/models/backup/available_album.model.dart b/mobile/lib/models/backup/available_album.model.dart new file mode 100644 index 0000000000..502d0b66be --- /dev/null +++ b/mobile/lib/models/backup/available_album.model.dart @@ -0,0 +1,35 @@ +import 'package:immich_mobile/entities/album.entity.dart'; + +class AvailableAlbum { + final Album album; + final int assetCount; + final DateTime? lastBackup; + const AvailableAlbum({required this.album, required this.assetCount, this.lastBackup}); + + AvailableAlbum copyWith({Album? album, int? assetCount, DateTime? lastBackup}) { + return AvailableAlbum( + album: album ?? this.album, + assetCount: assetCount ?? this.assetCount, + lastBackup: lastBackup ?? this.lastBackup, + ); + } + + String get name => album.name; + + String get id => album.localId!; + + bool get isAll => album.isAll; + + @override + String toString() => 'AvailableAlbum(albumEntity: $album, lastBackup: $lastBackup)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is AvailableAlbum && other.album == album; + } + + @override + int get hashCode => album.hashCode; +} diff --git a/mobile/lib/models/backup/backup_candidate.model.dart b/mobile/lib/models/backup/backup_candidate.model.dart new file mode 100644 index 0000000000..01c257dc05 --- /dev/null +++ b/mobile/lib/models/backup/backup_candidate.model.dart @@ -0,0 +1,19 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; + +class BackupCandidate { + BackupCandidate({required this.asset, required this.albumNames}); + + Asset asset; + List albumNames; + + @override + int get hashCode => asset.hashCode; + + @override + bool operator ==(Object other) { + if (other is! BackupCandidate) { + return false; + } + return asset == other.asset; + } +} diff --git a/mobile/lib/models/backup/backup_state.model.dart b/mobile/lib/models/backup/backup_state.model.dart new file mode 100644 index 0000000000..51a17de4fc --- /dev/null +++ b/mobile/lib/models/backup/backup_state.model.dart @@ -0,0 +1,173 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first + +import 'package:collection/collection.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; + +import 'package:immich_mobile/models/backup/available_album.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; + +enum BackUpProgressEnum { idle, inProgress, manualInProgress, inBackground, done } + +class BackUpState { + // enum + final BackUpProgressEnum backupProgress; + final List allAssetsInDatabase; + final double progressInPercentage; + final String progressInFileSize; + final double progressInFileSpeed; + final List progressInFileSpeeds; + final DateTime progressInFileSpeedUpdateTime; + final int progressInFileSpeedUpdateSentBytes; + final double iCloudDownloadProgress; + final ServerDiskInfo serverInfo; + final bool autoBackup; + final bool backgroundBackup; + final bool backupRequireWifi; + final bool backupRequireCharging; + final int backupTriggerDelay; + + /// All available albums on the device + final List availableAlbums; + final Set selectedBackupAlbums; + final Set excludedBackupAlbums; + + /// Assets that are not overlapping in selected backup albums and excluded backup albums + final Set allUniqueAssets; + + /// All assets from the selected albums that have been backup + final Set selectedAlbumsBackupAssetsIds; + + // Current Backup Asset + final CurrentUploadAsset currentUploadAsset; + + const BackUpState({ + required this.backupProgress, + required this.allAssetsInDatabase, + required this.progressInPercentage, + required this.progressInFileSize, + required this.progressInFileSpeed, + required this.progressInFileSpeeds, + required this.progressInFileSpeedUpdateTime, + required this.progressInFileSpeedUpdateSentBytes, + required this.iCloudDownloadProgress, + required this.serverInfo, + required this.autoBackup, + required this.backgroundBackup, + required this.backupRequireWifi, + required this.backupRequireCharging, + required this.backupTriggerDelay, + required this.availableAlbums, + required this.selectedBackupAlbums, + required this.excludedBackupAlbums, + required this.allUniqueAssets, + required this.selectedAlbumsBackupAssetsIds, + required this.currentUploadAsset, + }); + + BackUpState copyWith({ + BackUpProgressEnum? backupProgress, + List? allAssetsInDatabase, + double? progressInPercentage, + String? progressInFileSize, + double? progressInFileSpeed, + List? progressInFileSpeeds, + DateTime? progressInFileSpeedUpdateTime, + int? progressInFileSpeedUpdateSentBytes, + double? iCloudDownloadProgress, + ServerDiskInfo? serverInfo, + bool? autoBackup, + bool? backgroundBackup, + bool? backupRequireWifi, + bool? backupRequireCharging, + int? backupTriggerDelay, + List? availableAlbums, + Set? selectedBackupAlbums, + Set? excludedBackupAlbums, + Set? allUniqueAssets, + Set? selectedAlbumsBackupAssetsIds, + CurrentUploadAsset? currentUploadAsset, + }) { + return BackUpState( + backupProgress: backupProgress ?? this.backupProgress, + allAssetsInDatabase: allAssetsInDatabase ?? this.allAssetsInDatabase, + progressInPercentage: progressInPercentage ?? this.progressInPercentage, + progressInFileSize: progressInFileSize ?? this.progressInFileSize, + progressInFileSpeed: progressInFileSpeed ?? this.progressInFileSpeed, + progressInFileSpeeds: progressInFileSpeeds ?? this.progressInFileSpeeds, + progressInFileSpeedUpdateTime: progressInFileSpeedUpdateTime ?? this.progressInFileSpeedUpdateTime, + progressInFileSpeedUpdateSentBytes: progressInFileSpeedUpdateSentBytes ?? this.progressInFileSpeedUpdateSentBytes, + iCloudDownloadProgress: iCloudDownloadProgress ?? this.iCloudDownloadProgress, + serverInfo: serverInfo ?? this.serverInfo, + autoBackup: autoBackup ?? this.autoBackup, + backgroundBackup: backgroundBackup ?? this.backgroundBackup, + backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi, + backupRequireCharging: backupRequireCharging ?? this.backupRequireCharging, + backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay, + availableAlbums: availableAlbums ?? this.availableAlbums, + selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums, + excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums, + allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets, + selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds, + currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset, + ); + } + + @override + String toString() { + return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, progressInFileSize: $progressInFileSize, progressInFileSpeed: $progressInFileSpeed, progressInFileSpeeds: $progressInFileSpeeds, progressInFileSpeedUpdateTime: $progressInFileSpeedUpdateTime, progressInFileSpeedUpdateSentBytes: $progressInFileSpeedUpdateSentBytes, iCloudDownloadProgress: $iCloudDownloadProgress, serverInfo: $serverInfo, autoBackup: $autoBackup, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)'; + } + + @override + bool operator ==(covariant BackUpState other) { + if (identical(this, other)) return true; + final collectionEquals = const DeepCollectionEquality().equals; + + return other.backupProgress == backupProgress && + collectionEquals(other.allAssetsInDatabase, allAssetsInDatabase) && + other.progressInPercentage == progressInPercentage && + other.progressInFileSize == progressInFileSize && + other.progressInFileSpeed == progressInFileSpeed && + collectionEquals(other.progressInFileSpeeds, progressInFileSpeeds) && + other.progressInFileSpeedUpdateTime == progressInFileSpeedUpdateTime && + other.progressInFileSpeedUpdateSentBytes == progressInFileSpeedUpdateSentBytes && + other.iCloudDownloadProgress == iCloudDownloadProgress && + other.serverInfo == serverInfo && + other.autoBackup == autoBackup && + other.backgroundBackup == backgroundBackup && + other.backupRequireWifi == backupRequireWifi && + other.backupRequireCharging == backupRequireCharging && + other.backupTriggerDelay == backupTriggerDelay && + collectionEquals(other.availableAlbums, availableAlbums) && + collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) && + collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) && + collectionEquals(other.allUniqueAssets, allUniqueAssets) && + collectionEquals(other.selectedAlbumsBackupAssetsIds, selectedAlbumsBackupAssetsIds) && + other.currentUploadAsset == currentUploadAsset; + } + + @override + int get hashCode { + return backupProgress.hashCode ^ + allAssetsInDatabase.hashCode ^ + progressInPercentage.hashCode ^ + progressInFileSize.hashCode ^ + progressInFileSpeed.hashCode ^ + progressInFileSpeeds.hashCode ^ + progressInFileSpeedUpdateTime.hashCode ^ + progressInFileSpeedUpdateSentBytes.hashCode ^ + iCloudDownloadProgress.hashCode ^ + serverInfo.hashCode ^ + autoBackup.hashCode ^ + backgroundBackup.hashCode ^ + backupRequireWifi.hashCode ^ + backupRequireCharging.hashCode ^ + backupTriggerDelay.hashCode ^ + availableAlbums.hashCode ^ + selectedBackupAlbums.hashCode ^ + excludedBackupAlbums.hashCode ^ + allUniqueAssets.hashCode ^ + selectedAlbumsBackupAssetsIds.hashCode ^ + currentUploadAsset.hashCode; + } +} diff --git a/mobile/lib/models/backup/current_upload_asset.model.dart b/mobile/lib/models/backup/current_upload_asset.model.dart new file mode 100644 index 0000000000..2214897357 --- /dev/null +++ b/mobile/lib/models/backup/current_upload_asset.model.dart @@ -0,0 +1,95 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; + +class CurrentUploadAsset { + final String id; + final DateTime fileCreatedAt; + final String fileName; + final String fileType; + final int? fileSize; + final bool? iCloudAsset; + + const CurrentUploadAsset({ + required this.id, + required this.fileCreatedAt, + required this.fileName, + required this.fileType, + this.fileSize, + this.iCloudAsset, + }); + + @pragma('vm:prefer-inline') + bool get isIcloudAsset => iCloudAsset != null && iCloudAsset!; + + CurrentUploadAsset copyWith({ + String? id, + DateTime? fileCreatedAt, + String? fileName, + String? fileType, + int? fileSize, + bool? iCloudAsset, + }) { + return CurrentUploadAsset( + id: id ?? this.id, + fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, + fileName: fileName ?? this.fileName, + fileType: fileType ?? this.fileType, + fileSize: fileSize ?? this.fileSize, + iCloudAsset: iCloudAsset ?? this.iCloudAsset, + ); + } + + Map toMap() { + return { + 'id': id, + 'fileCreatedAt': fileCreatedAt.millisecondsSinceEpoch, + 'fileName': fileName, + 'fileType': fileType, + 'fileSize': fileSize, + 'iCloudAsset': iCloudAsset, + }; + } + + factory CurrentUploadAsset.fromMap(Map map) { + return CurrentUploadAsset( + id: map['id'] as String, + fileCreatedAt: DateTime.fromMillisecondsSinceEpoch(map['fileCreatedAt'] as int), + fileName: map['fileName'] as String, + fileType: map['fileType'] as String, + fileSize: map['fileSize'] as int, + iCloudAsset: map['iCloudAsset'] != null ? map['iCloudAsset'] as bool : null, + ); + } + + String toJson() => json.encode(toMap()); + + factory CurrentUploadAsset.fromJson(String source) => + CurrentUploadAsset.fromMap(json.decode(source) as Map); + + @override + String toString() { + return 'CurrentUploadAsset(id: $id, fileCreatedAt: $fileCreatedAt, fileName: $fileName, fileType: $fileType, fileSize: $fileSize, iCloudAsset: $iCloudAsset)'; + } + + @override + bool operator ==(covariant CurrentUploadAsset other) { + if (identical(this, other)) return true; + + return other.id == id && + other.fileCreatedAt == fileCreatedAt && + other.fileName == fileName && + other.fileType == fileType && + other.fileSize == fileSize && + other.iCloudAsset == iCloudAsset; + } + + @override + int get hashCode { + return id.hashCode ^ + fileCreatedAt.hashCode ^ + fileName.hashCode ^ + fileType.hashCode ^ + fileSize.hashCode ^ + iCloudAsset.hashCode; + } +} diff --git a/mobile/lib/models/backup/error_upload_asset.model.dart b/mobile/lib/models/backup/error_upload_asset.model.dart new file mode 100644 index 0000000000..38f241e748 --- /dev/null +++ b/mobile/lib/models/backup/error_upload_asset.model.dart @@ -0,0 +1,65 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; + +class ErrorUploadAsset { + final String id; + final DateTime fileCreatedAt; + final String fileName; + final String fileType; + final Asset asset; + final String errorMessage; + + const ErrorUploadAsset({ + required this.id, + required this.fileCreatedAt, + required this.fileName, + required this.fileType, + required this.asset, + required this.errorMessage, + }); + + ErrorUploadAsset copyWith({ + String? id, + DateTime? fileCreatedAt, + String? fileName, + String? fileType, + Asset? asset, + String? errorMessage, + }) { + return ErrorUploadAsset( + id: id ?? this.id, + fileCreatedAt: fileCreatedAt ?? this.fileCreatedAt, + fileName: fileName ?? this.fileName, + fileType: fileType ?? this.fileType, + asset: asset ?? this.asset, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + String toString() { + return 'ErrorUploadAsset(id: $id, fileCreatedAt: $fileCreatedAt, fileName: $fileName, fileType: $fileType, asset: $asset, errorMessage: $errorMessage)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ErrorUploadAsset && + other.id == id && + other.fileCreatedAt == fileCreatedAt && + other.fileName == fileName && + other.fileType == fileType && + other.asset == asset && + other.errorMessage == errorMessage; + } + + @override + int get hashCode { + return id.hashCode ^ + fileCreatedAt.hashCode ^ + fileName.hashCode ^ + fileType.hashCode ^ + asset.hashCode ^ + errorMessage.hashCode; + } +} diff --git a/mobile/lib/models/backup/manual_upload_state.model.dart b/mobile/lib/models/backup/manual_upload_state.model.dart new file mode 100644 index 0000000000..120327c611 --- /dev/null +++ b/mobile/lib/models/backup/manual_upload_state.model.dart @@ -0,0 +1,102 @@ +import 'package:collection/collection.dart'; + +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; + +class ManualUploadState { + // Current Backup Asset + final CurrentUploadAsset currentUploadAsset; + final int currentAssetIndex; + + final bool showDetailedNotification; + + /// Manual Upload Stats + final int totalAssetsToUpload; + final int successfulUploads; + final double progressInPercentage; + final String progressInFileSize; + final double progressInFileSpeed; + final List progressInFileSpeeds; + final DateTime progressInFileSpeedUpdateTime; + final int progressInFileSpeedUpdateSentBytes; + + const ManualUploadState({ + required this.progressInPercentage, + required this.progressInFileSize, + required this.progressInFileSpeed, + required this.progressInFileSpeeds, + required this.progressInFileSpeedUpdateTime, + required this.progressInFileSpeedUpdateSentBytes, + required this.currentUploadAsset, + required this.totalAssetsToUpload, + required this.currentAssetIndex, + required this.successfulUploads, + required this.showDetailedNotification, + }); + + ManualUploadState copyWith({ + double? progressInPercentage, + String? progressInFileSize, + double? progressInFileSpeed, + List? progressInFileSpeeds, + DateTime? progressInFileSpeedUpdateTime, + int? progressInFileSpeedUpdateSentBytes, + CurrentUploadAsset? currentUploadAsset, + int? totalAssetsToUpload, + int? successfulUploads, + int? currentAssetIndex, + bool? showDetailedNotification, + }) { + return ManualUploadState( + progressInPercentage: progressInPercentage ?? this.progressInPercentage, + progressInFileSize: progressInFileSize ?? this.progressInFileSize, + progressInFileSpeed: progressInFileSpeed ?? this.progressInFileSpeed, + progressInFileSpeeds: progressInFileSpeeds ?? this.progressInFileSpeeds, + progressInFileSpeedUpdateTime: progressInFileSpeedUpdateTime ?? this.progressInFileSpeedUpdateTime, + progressInFileSpeedUpdateSentBytes: progressInFileSpeedUpdateSentBytes ?? this.progressInFileSpeedUpdateSentBytes, + currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset, + totalAssetsToUpload: totalAssetsToUpload ?? this.totalAssetsToUpload, + currentAssetIndex: currentAssetIndex ?? this.currentAssetIndex, + successfulUploads: successfulUploads ?? this.successfulUploads, + showDetailedNotification: showDetailedNotification ?? this.showDetailedNotification, + ); + } + + @override + String toString() { + return 'ManualUploadState(progressInPercentage: $progressInPercentage, progressInFileSize: $progressInFileSize, progressInFileSpeed: $progressInFileSpeed, progressInFileSpeeds: $progressInFileSpeeds, progressInFileSpeedUpdateTime: $progressInFileSpeedUpdateTime, progressInFileSpeedUpdateSentBytes: $progressInFileSpeedUpdateSentBytes, currentUploadAsset: $currentUploadAsset, totalAssetsToUpload: $totalAssetsToUpload, successfulUploads: $successfulUploads, currentAssetIndex: $currentAssetIndex, showDetailedNotification: $showDetailedNotification)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final collectionEquals = const DeepCollectionEquality().equals; + + return other is ManualUploadState && + other.progressInPercentage == progressInPercentage && + other.progressInFileSize == progressInFileSize && + other.progressInFileSpeed == progressInFileSpeed && + collectionEquals(other.progressInFileSpeeds, progressInFileSpeeds) && + other.progressInFileSpeedUpdateTime == progressInFileSpeedUpdateTime && + other.progressInFileSpeedUpdateSentBytes == progressInFileSpeedUpdateSentBytes && + other.currentUploadAsset == currentUploadAsset && + other.totalAssetsToUpload == totalAssetsToUpload && + other.currentAssetIndex == currentAssetIndex && + other.successfulUploads == successfulUploads && + other.showDetailedNotification == showDetailedNotification; + } + + @override + int get hashCode { + return progressInPercentage.hashCode ^ + progressInFileSize.hashCode ^ + progressInFileSpeed.hashCode ^ + progressInFileSpeeds.hashCode ^ + progressInFileSpeedUpdateTime.hashCode ^ + progressInFileSpeedUpdateSentBytes.hashCode ^ + currentUploadAsset.hashCode ^ + totalAssetsToUpload.hashCode ^ + currentAssetIndex.hashCode ^ + successfulUploads.hashCode ^ + showDetailedNotification.hashCode; + } +} diff --git a/mobile/lib/models/backup/success_upload_asset.model.dart b/mobile/lib/models/backup/success_upload_asset.model.dart new file mode 100644 index 0000000000..da1e104ba3 --- /dev/null +++ b/mobile/lib/models/backup/success_upload_asset.model.dart @@ -0,0 +1,31 @@ +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; + +class SuccessUploadAsset { + final BackupCandidate candidate; + final String remoteAssetId; + final bool isDuplicate; + + const SuccessUploadAsset({required this.candidate, required this.remoteAssetId, required this.isDuplicate}); + + SuccessUploadAsset copyWith({BackupCandidate? candidate, String? remoteAssetId, bool? isDuplicate}) { + return SuccessUploadAsset( + candidate: candidate ?? this.candidate, + remoteAssetId: remoteAssetId ?? this.remoteAssetId, + isDuplicate: isDuplicate ?? this.isDuplicate, + ); + } + + @override + String toString() => + 'SuccessUploadAsset(asset: $candidate, remoteAssetId: $remoteAssetId, isDuplicate: $isDuplicate)'; + + @override + bool operator ==(covariant SuccessUploadAsset other) { + if (identical(this, other)) return true; + + return other.candidate == candidate && other.remoteAssetId == remoteAssetId && other.isDuplicate == isDuplicate; + } + + @override + int get hashCode => candidate.hashCode ^ remoteAssetId.hashCode ^ isDuplicate.hashCode; +} diff --git a/mobile/lib/models/cast/cast_manager_state.dart b/mobile/lib/models/cast/cast_manager_state.dart index 0fd3dc877b..c948921792 100644 --- a/mobile/lib/models/cast/cast_manager_state.dart +++ b/mobile/lib/models/cast/cast_manager_state.dart @@ -1,18 +1,83 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'cast_manager_state.freezed.dart'; +import 'dart:convert'; enum CastDestinationType { googleCast } enum CastState { idle, playing, paused, buffering } -@freezed -abstract class CastManagerState with _$CastManagerState { - const factory CastManagerState({ - required bool isCasting, - required String receiverName, - required CastState castState, - required Duration currentTime, - required Duration duration, - }) = _CastManagerState; +class CastManagerState { + final bool isCasting; + final String receiverName; + final CastState castState; + final Duration currentTime; + final Duration duration; + + const CastManagerState({ + required this.isCasting, + required this.receiverName, + required this.castState, + required this.currentTime, + required this.duration, + }); + + CastManagerState copyWith({ + bool? isCasting, + String? receiverName, + CastState? castState, + Duration? currentTime, + Duration? duration, + }) { + return CastManagerState( + isCasting: isCasting ?? this.isCasting, + receiverName: receiverName ?? this.receiverName, + castState: castState ?? this.castState, + currentTime: currentTime ?? this.currentTime, + duration: duration ?? this.duration, + ); + } + + Map toMap() { + final result = {}; + + result.addAll({'isCasting': isCasting}); + result.addAll({'receiverName': receiverName}); + result.addAll({'castState': castState}); + result.addAll({'currentTime': currentTime.inSeconds}); + result.addAll({'duration': duration.inSeconds}); + + return result; + } + + factory CastManagerState.fromMap(Map map) { + return CastManagerState( + isCasting: map['isCasting'] ?? false, + receiverName: map['receiverName'] ?? '', + castState: map['castState'] ?? CastState.idle, + currentTime: Duration(seconds: map['currentTime']?.toInt() ?? 0), + duration: Duration(seconds: map['duration']?.toInt() ?? 0), + ); + } + + String toJson() => json.encode(toMap()); + + factory CastManagerState.fromJson(String source) => CastManagerState.fromMap(json.decode(source)); + + @override + String toString() => + 'CastManagerState(isCasting: $isCasting, receiverName: $receiverName, castState: $castState, currentTime: $currentTime, duration: $duration)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CastManagerState && + other.isCasting == isCasting && + other.receiverName == receiverName && + other.castState == castState && + other.currentTime == currentTime && + other.duration == duration; + } + + @override + int get hashCode => + isCasting.hashCode ^ receiverName.hashCode ^ castState.hashCode ^ currentTime.hashCode ^ duration.hashCode; } diff --git a/mobile/lib/models/download/download_state.model.dart b/mobile/lib/models/download/download_state.model.dart index 5f04fcdfd4..82d4e31253 100644 --- a/mobile/lib/models/download/download_state.model.dart +++ b/mobile/lib/models/download/download_state.model.dart @@ -1,19 +1,84 @@ -import 'package:background_downloader/background_downloader.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'download_state.model.freezed.dart'; - -@freezed -abstract class DownloadInfo with _$DownloadInfo { - const factory DownloadInfo({required String fileName, required double progress, required TaskStatus status}) = - _DownloadInfo; -} - -@freezed -abstract class DownloadState with _$DownloadState { - const factory DownloadState({ - required TaskStatus downloadStatus, - required Map taskProgress, - required bool showProgress, - }) = _DownloadState; -} +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; + +import 'package:background_downloader/background_downloader.dart'; +import 'package:collection/collection.dart'; + +class DownloadInfo { + final String fileName; + final double progress; + // enum + final TaskStatus status; + + const DownloadInfo({required this.fileName, required this.progress, required this.status}); + + DownloadInfo copyWith({String? fileName, double? progress, TaskStatus? status}) { + return DownloadInfo( + fileName: fileName ?? this.fileName, + progress: progress ?? this.progress, + status: status ?? this.status, + ); + } + + Map toMap() { + return {'fileName': fileName, 'progress': progress, 'status': status.index}; + } + + factory DownloadInfo.fromMap(Map map) { + return DownloadInfo( + fileName: map['fileName'] as String, + progress: map['progress'] as double, + status: TaskStatus.values[map['status'] as int], + ); + } + + String toJson() => json.encode(toMap()); + + factory DownloadInfo.fromJson(String source) => DownloadInfo.fromMap(json.decode(source) as Map); + + @override + String toString() => 'DownloadInfo(fileName: $fileName, progress: $progress, status: $status)'; + + @override + bool operator ==(covariant DownloadInfo other) { + if (identical(this, other)) return true; + + return other.fileName == fileName && other.progress == progress && other.status == status; + } + + @override + int get hashCode => fileName.hashCode ^ progress.hashCode ^ status.hashCode; +} + +class DownloadState { + // enum + final TaskStatus downloadStatus; + final Map taskProgress; + final bool showProgress; + const DownloadState({required this.downloadStatus, required this.taskProgress, required this.showProgress}); + + DownloadState copyWith({TaskStatus? downloadStatus, Map? taskProgress, bool? showProgress}) { + return DownloadState( + downloadStatus: downloadStatus ?? this.downloadStatus, + taskProgress: taskProgress ?? this.taskProgress, + showProgress: showProgress ?? this.showProgress, + ); + } + + @override + String toString() => + 'DownloadState(downloadStatus: $downloadStatus, taskProgress: $taskProgress, showProgress: $showProgress)'; + + @override + bool operator ==(covariant DownloadState other) { + if (identical(this, other)) return true; + final mapEquals = const DeepCollectionEquality().equals; + + return other.downloadStatus == downloadStatus && + mapEquals(other.taskProgress, taskProgress) && + other.showProgress == showProgress; + } + + @override + int get hashCode => downloadStatus.hashCode ^ taskProgress.hashCode ^ showProgress.hashCode; +} diff --git a/mobile/lib/models/download/livephotos_medatada.model.dart b/mobile/lib/models/download/livephotos_medatada.model.dart index 40795142bc..f77a1514ac 100644 --- a/mobile/lib/models/download/livephotos_medatada.model.dart +++ b/mobile/lib/models/download/livephotos_medatada.model.dart @@ -1,16 +1,18 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'livephotos_medatada.model.freezed.dart'; - enum LivePhotosPart { video, image } -@Freezed(fromJson: false, toJson: false) -abstract class LivePhotosMetadata with _$LivePhotosMetadata { - const LivePhotosMetadata._(); +class LivePhotosMetadata { + // enum + LivePhotosPart part; - const factory LivePhotosMetadata({required LivePhotosPart part, required String id}) = _LivePhotosMetadata; + String id; + LivePhotosMetadata({required this.part, required this.id}); + + LivePhotosMetadata copyWith({LivePhotosPart? part, String? id}) { + return LivePhotosMetadata(part: part ?? this.part, id: id ?? this.id); + } Map toMap() { return {'part': part.index, 'id': id}; @@ -24,4 +26,17 @@ abstract class LivePhotosMetadata with _$LivePhotosMetadata { factory LivePhotosMetadata.fromJson(String source) => LivePhotosMetadata.fromMap(json.decode(source) as Map); + + @override + String toString() => 'LivePhotosMetadata(part: $part, id: $id)'; + + @override + bool operator ==(covariant LivePhotosMetadata other) { + if (identical(this, other)) return true; + + return other.part == part && other.id == id; + } + + @override + int get hashCode => part.hashCode ^ id.hashCode; } diff --git a/mobile/lib/models/map/map_marker.model.dart b/mobile/lib/models/map/map_marker.model.dart index 4093747e84..0f425306ff 100644 --- a/mobile/lib/models/map/map_marker.model.dart +++ b/mobile/lib/models/map/map_marker.model.dart @@ -1,14 +1,27 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; import 'package:openapi/api.dart'; -part 'map_marker.model.freezed.dart'; +class MapMarker { + final LatLng latLng; + final String assetRemoteId; + const MapMarker({required this.latLng, required this.assetRemoteId}); -@freezed -abstract class MapMarker with _$MapMarker { - const factory MapMarker({required LatLng latLng, required String assetRemoteId}) = _MapMarker; + MapMarker copyWith({LatLng? latLng, String? assetRemoteId}) { + return MapMarker(latLng: latLng ?? this.latLng, assetRemoteId: assetRemoteId ?? this.assetRemoteId); + } - factory MapMarker.fromDto(MapMarkerResponseDto dto) => - MapMarker(latLng: LatLng(dto.lat, dto.lon), assetRemoteId: dto.id); + MapMarker.fromDto(MapMarkerResponseDto dto) : latLng = LatLng(dto.lat, dto.lon), assetRemoteId = dto.id; + + @override + String toString() => 'MapMarker(latLng: $latLng, assetRemoteId: $assetRemoteId)'; + + @override + bool operator ==(covariant MapMarker other) { + if (identical(this, other)) return true; + + return other.latLng == latLng && other.assetRemoteId == assetRemoteId; + } + + @override + int get hashCode => latLng.hashCode ^ assetRemoteId.hashCode; } diff --git a/mobile/lib/models/map/map_state.model.dart b/mobile/lib/models/map/map_state.model.dart index f7bd435073..78747e770d 100644 --- a/mobile/lib/models/map/map_state.model.dart +++ b/mobile/lib/models/map/map_state.model.dart @@ -1,20 +1,77 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -part 'map_state.model.freezed.dart'; +class MapState { + final ThemeMode themeMode; + final bool showFavoriteOnly; + final bool includeArchived; + final bool withPartners; + final int relativeTime; + final bool shouldRefetchMarkers; + final AsyncValue lightStyleFetched; + final AsyncValue darkStyleFetched; -@freezed -abstract class MapState with _$MapState { - const factory MapState({ - @Default(ThemeMode.system) ThemeMode themeMode, - @Default(false) bool showFavoriteOnly, - @Default(false) bool includeArchived, - @Default(false) bool withPartners, - @Default(0) int relativeTime, - @Default(false) bool shouldRefetchMarkers, - @Default(AsyncLoading()) AsyncValue lightStyleFetched, - @Default(AsyncLoading()) AsyncValue darkStyleFetched, - }) = _MapState; + const MapState({ + this.themeMode = ThemeMode.system, + this.showFavoriteOnly = false, + this.includeArchived = false, + this.withPartners = false, + this.relativeTime = 0, + this.shouldRefetchMarkers = false, + this.lightStyleFetched = const AsyncLoading(), + this.darkStyleFetched = const AsyncLoading(), + }); + + MapState copyWith({ + ThemeMode? themeMode, + bool? showFavoriteOnly, + bool? includeArchived, + bool? withPartners, + int? relativeTime, + bool? shouldRefetchMarkers, + AsyncValue? lightStyleFetched, + AsyncValue? darkStyleFetched, + }) { + return MapState( + themeMode: themeMode ?? this.themeMode, + showFavoriteOnly: showFavoriteOnly ?? this.showFavoriteOnly, + includeArchived: includeArchived ?? this.includeArchived, + withPartners: withPartners ?? this.withPartners, + relativeTime: relativeTime ?? this.relativeTime, + shouldRefetchMarkers: shouldRefetchMarkers ?? this.shouldRefetchMarkers, + lightStyleFetched: lightStyleFetched ?? this.lightStyleFetched, + darkStyleFetched: darkStyleFetched ?? this.darkStyleFetched, + ); + } + + @override + String toString() { + return 'MapState(themeMode: $themeMode, showFavoriteOnly: $showFavoriteOnly, includeArchived: $includeArchived, withPartners: $withPartners, relativeTime: $relativeTime, shouldRefetchMarkers: $shouldRefetchMarkers, lightStyleFetched: $lightStyleFetched, darkStyleFetched: $darkStyleFetched)'; + } + + @override + bool operator ==(covariant MapState other) { + if (identical(this, other)) return true; + + return other.themeMode == themeMode && + other.showFavoriteOnly == showFavoriteOnly && + other.includeArchived == includeArchived && + other.withPartners == withPartners && + other.relativeTime == relativeTime && + other.shouldRefetchMarkers == shouldRefetchMarkers && + other.lightStyleFetched == lightStyleFetched && + other.darkStyleFetched == darkStyleFetched; + } + + @override + int get hashCode { + return themeMode.hashCode ^ + showFavoriteOnly.hashCode ^ + includeArchived.hashCode ^ + withPartners.hashCode ^ + relativeTime.hashCode ^ + shouldRefetchMarkers.hashCode ^ + lightStyleFetched.hashCode ^ + darkStyleFetched.hashCode; + } } diff --git a/mobile/lib/models/memories/memory.model.dart b/mobile/lib/models/memories/memory.model.dart new file mode 100644 index 0000000000..8a9db5d51b --- /dev/null +++ b/mobile/lib/models/memories/memory.model.dart @@ -0,0 +1,29 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first + +import 'package:collection/collection.dart'; + +import 'package:immich_mobile/entities/asset.entity.dart'; + +class Memory { + final String title; + final List assets; + const Memory({required this.title, required this.assets}); + + Memory copyWith({String? title, List? assets}) { + return Memory(title: title ?? this.title, assets: assets ?? this.assets); + } + + @override + String toString() => 'Memory(title: $title, assets: $assets)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is Memory && other.title == title && listEquals(other.assets, assets); + } + + @override + int get hashCode => title.hashCode ^ assets.hashCode; +} diff --git a/mobile/lib/models/search/search_curated_content.model.dart b/mobile/lib/models/search/search_curated_content.model.dart index 8778c9b407..6e4a083876 100644 --- a/mobile/lib/models/search/search_curated_content.model.dart +++ b/mobile/lib/models/search/search_curated_content.model.dart @@ -1,20 +1,52 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'search_curated_content.model.freezed.dart'; - -/// A wrapper for [CuratedLocationsResponseDto] objects -/// and [CuratedObjectsResponseDto] to be displayed in -/// a view -@freezed -abstract class SearchCuratedContent with _$SearchCuratedContent { - const factory SearchCuratedContent({ - /// The label to show associated with this curated object - required String label, - - /// The id to lookup the asset from the server - required String id, - - /// The subtitle to show below the label - String? subtitle, - }) = _SearchCuratedContent; -} +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:convert'; + +/// A wrapper for [CuratedLocationsResponseDto] objects +/// and [CuratedObjectsResponseDto] to be displayed in +/// a view +class SearchCuratedContent { + /// The label to show associated with this curated object + final String label; + + /// The subtitle to show below the label + final String? subtitle; + + /// The id to lookup the asset from the server + final String id; + + const SearchCuratedContent({required this.label, required this.id, this.subtitle}); + + SearchCuratedContent copyWith({String? label, String? subtitle, String? id}) { + return SearchCuratedContent(label: label ?? this.label, subtitle: subtitle ?? this.subtitle, id: id ?? this.id); + } + + Map toMap() { + return {'label': label, 'subtitle': subtitle, 'id': id}; + } + + factory SearchCuratedContent.fromMap(Map map) { + return SearchCuratedContent( + label: map['label'] as String, + subtitle: map['subtitle'] as String?, + id: map['id'] as String, + ); + } + + String toJson() => json.encode(toMap()); + + factory SearchCuratedContent.fromJson(String source) => + SearchCuratedContent.fromMap(json.decode(source) as Map); + + @override + String toString() => 'CuratedContent(label: $label, subtitle: $subtitle, id: $id)'; + + @override + bool operator ==(covariant SearchCuratedContent other) { + if (identical(this, other)) return true; + + return other.label == label && other.subtitle == subtitle && other.id == id; + } + + @override + int get hashCode => label.hashCode ^ id.hashCode; +} diff --git a/mobile/lib/models/search/search_filter.model.dart b/mobile/lib/models/search/search_filter.model.dart index 4c996da12d..1b730e0c68 100644 --- a/mobile/lib/models/search/search_filter.model.dart +++ b/mobile/lib/models/search/search_filter.model.dart @@ -1,17 +1,18 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; -import 'package:immich_mobile/utils/option.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; -part 'search_filter.model.freezed.dart'; +class SearchLocationFilter { + String? country; + String? state; + String? city; + SearchLocationFilter({this.country, this.state, this.city}); -@Freezed(fromJson: false, toJson: false) -abstract class SearchLocationFilter with _$SearchLocationFilter { - const SearchLocationFilter._(); - - const factory SearchLocationFilter({String? country, String? state, String? city}) = _SearchLocationFilter; + SearchLocationFilter copyWith({String? country, String? state, String? city}) { + return SearchLocationFilter(country: country ?? this.country, state: state ?? this.state, city: city ?? this.city); + } Map toMap() { return {'country': country, 'state': state, 'city': city}; @@ -29,13 +30,29 @@ abstract class SearchLocationFilter with _$SearchLocationFilter { factory SearchLocationFilter.fromJson(String source) => SearchLocationFilter.fromMap(json.decode(source) as Map); + + @override + String toString() => 'SearchLocationFilter(country: $country, state: $state, city: $city)'; + + @override + bool operator ==(covariant SearchLocationFilter other) { + if (identical(this, other)) return true; + + return other.country == country && other.state == state && other.city == city; + } + + @override + int get hashCode => country.hashCode ^ state.hashCode ^ city.hashCode; } -@Freezed(fromJson: false, toJson: false) -abstract class SearchCameraFilter with _$SearchCameraFilter { - const SearchCameraFilter._(); +class SearchCameraFilter { + String? make; + String? model; + SearchCameraFilter({this.make, this.model}); - const factory SearchCameraFilter({String? make, String? model}) = _SearchCameraFilter; + SearchCameraFilter copyWith({String? make, String? model}) { + return SearchCameraFilter(make: make ?? this.make, model: model ?? this.model); + } Map toMap() { return {'make': make, 'model': model}; @@ -52,13 +69,29 @@ abstract class SearchCameraFilter with _$SearchCameraFilter { factory SearchCameraFilter.fromJson(String source) => SearchCameraFilter.fromMap(json.decode(source) as Map); + + @override + String toString() => 'SearchCameraFilter(make: $make, model: $model)'; + + @override + bool operator ==(covariant SearchCameraFilter other) { + if (identical(this, other)) return true; + + return other.make == make && other.model == model; + } + + @override + int get hashCode => make.hashCode ^ model.hashCode; } -@Freezed(fromJson: false, toJson: false) -abstract class SearchDateFilter with _$SearchDateFilter { - const SearchDateFilter._(); +class SearchDateFilter { + DateTime? takenBefore; + DateTime? takenAfter; + SearchDateFilter({this.takenBefore, this.takenAfter}); - const factory SearchDateFilter({DateTime? takenBefore, DateTime? takenAfter}) = _SearchDateFilter; + SearchDateFilter copyWith({DateTime? takenBefore, DateTime? takenAfter}) { + return SearchDateFilter(takenBefore: takenBefore ?? this.takenBefore, takenAfter: takenAfter ?? this.takenAfter); + } Map toMap() { return { @@ -78,62 +111,136 @@ abstract class SearchDateFilter with _$SearchDateFilter { factory SearchDateFilter.fromJson(String source) => SearchDateFilter.fromMap(json.decode(source) as Map); + + @override + String toString() => 'SearchDateFilter(takenBefore: $takenBefore, takenAfter: $takenAfter)'; + + @override + bool operator ==(covariant SearchDateFilter other) { + if (identical(this, other)) return true; + + return other.takenBefore == takenBefore && other.takenAfter == takenAfter; + } + + @override + int get hashCode => takenBefore.hashCode ^ takenAfter.hashCode; } -@Freezed(fromJson: false, toJson: false) -abstract class SearchRatingFilter with _$SearchRatingFilter { - const SearchRatingFilter._(); +class SearchRatingFilter { + int? rating; + SearchRatingFilter({this.rating}); - /// [rating]: none = no filter; some(null) = filter for unrated; some(1-5) = filter for that rating - // TODO(agg23): Switch to enum - const factory SearchRatingFilter({@Default(Option.none()) Option rating}) = _SearchRatingFilter; + SearchRatingFilter copyWith({int? rating}) { + return SearchRatingFilter(rating: rating ?? this.rating); + } Map toMap() { - if (rating.isNone) { - return {'active': false}; - } - return {'active': true, 'value': rating.unwrapOrNull}; + return {'rating': rating}; } factory SearchRatingFilter.fromMap(Map map) { - if (!(map['active'] as bool? ?? false)) { - return const SearchRatingFilter(); - } - return SearchRatingFilter(rating: Option.some(map['value'] as int?)); + return SearchRatingFilter(rating: map['rating'] != null ? map['rating'] as int : null); } String toJson() => json.encode(toMap()); factory SearchRatingFilter.fromJson(String source) => SearchRatingFilter.fromMap(json.decode(source) as Map); + + @override + String toString() => 'SearchRatingFilter(rating: $rating)'; + + @override + bool operator ==(covariant SearchRatingFilter other) { + if (identical(this, other)) return true; + + return other.rating == rating; + } + + @override + int get hashCode => rating.hashCode; } -@freezed -abstract class SearchDisplayFilters with _$SearchDisplayFilters { - const factory SearchDisplayFilters({required bool isNotInAlbum, required bool isArchive, required bool isFavorite}) = - _SearchDisplayFilters; +class SearchDisplayFilters { + bool isNotInAlbum = false; + bool isArchive = false; + bool isFavorite = false; + SearchDisplayFilters({required this.isNotInAlbum, required this.isArchive, required this.isFavorite}); + + SearchDisplayFilters copyWith({bool? isNotInAlbum, bool? isArchive, bool? isFavorite}) { + return SearchDisplayFilters( + isNotInAlbum: isNotInAlbum ?? this.isNotInAlbum, + isArchive: isArchive ?? this.isArchive, + isFavorite: isFavorite ?? this.isFavorite, + ); + } + + Map toMap() { + return {'isNotInAlbum': isNotInAlbum, 'isArchive': isArchive, 'isFavorite': isFavorite}; + } + + factory SearchDisplayFilters.fromMap(Map map) { + return SearchDisplayFilters( + isNotInAlbum: map['isNotInAlbum'] as bool, + isArchive: map['isArchive'] as bool, + isFavorite: map['isFavorite'] as bool, + ); + } + + String toJson() => json.encode(toMap()); + + factory SearchDisplayFilters.fromJson(String source) => + SearchDisplayFilters.fromMap(json.decode(source) as Map); + + @override + String toString() => + 'SearchDisplayFilters(isNotInAlbum: $isNotInAlbum, isArchive: $isArchive, isFavorite: $isFavorite)'; + + @override + bool operator ==(covariant SearchDisplayFilters other) { + if (identical(this, other)) return true; + + return other.isNotInAlbum == isNotInAlbum && other.isArchive == isArchive && other.isFavorite == isFavorite; + } + + @override + int get hashCode => isNotInAlbum.hashCode ^ isArchive.hashCode ^ isFavorite.hashCode; } -@freezed -abstract class SearchFilter with _$SearchFilter { - const SearchFilter._(); +class SearchFilter { + String? context; + String? filename; + String? description; + String? ocr; + String? language; + String? assetId; + List? tagIds; + Set people; + SearchLocationFilter location; + SearchCameraFilter camera; + SearchDateFilter date; + SearchRatingFilter rating; + SearchDisplayFilters display; - const factory SearchFilter({ - String? context, - String? filename, - String? description, - String? ocr, - String? language, - String? assetId, - List? tagIds, - required Set people, - required SearchLocationFilter location, - required SearchCameraFilter camera, - required SearchDateFilter date, - required SearchRatingFilter rating, - required SearchDisplayFilters display, - required AssetType mediaType, - }) = _SearchFilter; + // Enum + AssetType mediaType; + + SearchFilter({ + this.context, + this.filename, + this.description, + this.ocr, + this.language, + this.assetId, + this.tagIds, + required this.people, + required this.location, + required this.camera, + required this.date, + required this.display, + required this.rating, + required this.mediaType, + }); bool get isEmpty { return (context == null || (context != null && context!.isEmpty)) && @@ -153,7 +260,84 @@ abstract class SearchFilter with _$SearchFilter { display.isNotInAlbum == false && display.isArchive == false && display.isFavorite == false && - rating.rating.isNone && + rating.rating == null && mediaType == AssetType.other; } + + SearchFilter copyWith({ + String? context, + String? filename, + String? description, + String? language, + String? ocr, + String? assetId, + Set? people, + List? tagIds, + SearchLocationFilter? location, + SearchCameraFilter? camera, + SearchDateFilter? date, + SearchDisplayFilters? display, + SearchRatingFilter? rating, + AssetType? mediaType, + }) { + return SearchFilter( + context: context ?? this.context, + filename: filename ?? this.filename, + description: description ?? this.description, + language: language ?? this.language, + ocr: ocr ?? this.ocr, + assetId: assetId ?? this.assetId, + people: people ?? this.people, + location: location ?? this.location, + camera: camera ?? this.camera, + date: date ?? this.date, + display: display ?? this.display, + rating: rating ?? this.rating, + mediaType: mediaType ?? this.mediaType, + tagIds: tagIds ?? this.tagIds, + ); + } + + @override + String toString() { + return 'SearchFilter(context: $context, filename: $filename, description: $description, language: $language, ocr: $ocr, people: $people, location: $location, tagIds: $tagIds, camera: $camera, date: $date, display: $display, rating: $rating, mediaType: $mediaType, assetId: $assetId)'; + } + + @override + bool operator ==(covariant SearchFilter other) { + if (identical(this, other)) return true; + + return other.context == context && + other.filename == filename && + other.description == description && + other.language == language && + other.ocr == ocr && + other.assetId == assetId && + other.people == people && + other.tagIds == tagIds && + other.location == location && + other.camera == camera && + other.date == date && + other.display == display && + other.rating == rating && + other.mediaType == mediaType; + } + + @override + int get hashCode { + return context.hashCode ^ + filename.hashCode ^ + description.hashCode ^ + language.hashCode ^ + ocr.hashCode ^ + assetId.hashCode ^ + people.hashCode ^ + tagIds.hashCode ^ + location.hashCode ^ + camera.hashCode ^ + date.hashCode ^ + display.hashCode ^ + rating.hashCode ^ + mediaType.hashCode; + } } diff --git a/mobile/lib/models/search/search_result.model.dart b/mobile/lib/models/search/search_result.model.dart new file mode 100644 index 0000000000..02553869bf --- /dev/null +++ b/mobile/lib/models/search/search_result.model.dart @@ -0,0 +1,28 @@ +import 'package:collection/collection.dart'; + +import 'package:immich_mobile/entities/asset.entity.dart'; + +class SearchResult { + final List assets; + final int? nextPage; + + const SearchResult({required this.assets, this.nextPage}); + + SearchResult copyWith({List? assets, int? nextPage}) { + return SearchResult(assets: assets ?? this.assets, nextPage: nextPage ?? this.nextPage); + } + + @override + String toString() => 'SearchResult(assets: $assets, nextPage: $nextPage)'; + + @override + bool operator ==(covariant SearchResult other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.assets, assets) && other.nextPage == nextPage; + } + + @override + int get hashCode => assets.hashCode ^ nextPage.hashCode; +} diff --git a/mobile/lib/models/search/search_result_page_state.model.dart b/mobile/lib/models/search/search_result_page_state.model.dart new file mode 100644 index 0000000000..7c8a27b50c --- /dev/null +++ b/mobile/lib/models/search/search_result_page_state.model.dart @@ -0,0 +1,57 @@ +import 'package:collection/collection.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; + +class SearchResultPageState { + final bool isLoading; + final bool isSuccess; + final bool isError; + final bool isSmart; + final List searchResult; + + const SearchResultPageState({ + required this.isLoading, + required this.isSuccess, + required this.isError, + required this.isSmart, + required this.searchResult, + }); + + SearchResultPageState copyWith({ + bool? isLoading, + bool? isSuccess, + bool? isError, + bool? isSmart, + List? searchResult, + }) { + return SearchResultPageState( + isLoading: isLoading ?? this.isLoading, + isSuccess: isSuccess ?? this.isSuccess, + isError: isError ?? this.isError, + isSmart: isSmart ?? this.isSmart, + searchResult: searchResult ?? this.searchResult, + ); + } + + @override + String toString() { + return 'SearchresultPageState(isLoading: $isLoading, isSuccess: $isSuccess, isError: $isError, isSmart: $isSmart, searchResult: $searchResult)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return other is SearchResultPageState && + other.isLoading == isLoading && + other.isSuccess == isSuccess && + other.isError == isError && + other.isSmart == isSmart && + listEquals(other.searchResult, searchResult); + } + + @override + int get hashCode { + return isLoading.hashCode ^ isSuccess.hashCode ^ isError.hashCode ^ isSmart.hashCode ^ searchResult.hashCode; + } +} diff --git a/mobile/lib/models/server_info/server_config.model.dart b/mobile/lib/models/server_info/server_config.model.dart index 4293d098b9..37b98afadb 100644 --- a/mobile/lib/models/server_info/server_config.model.dart +++ b/mobile/lib/models/server_info/server_config.model.dart @@ -1,23 +1,50 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -part 'server_config.model.freezed.dart'; +class ServerConfig { + final int trashDays; + final String oauthButtonText; + final String externalDomain; + final String mapDarkStyleUrl; + final String mapLightStyleUrl; -@freezed -abstract class ServerConfig with _$ServerConfig { - const factory ServerConfig({ - required int trashDays, - required String oauthButtonText, - required String externalDomain, - required String mapDarkStyleUrl, - required String mapLightStyleUrl, - }) = _ServerConfig; + const ServerConfig({ + required this.trashDays, + required this.oauthButtonText, + required this.externalDomain, + required this.mapDarkStyleUrl, + required this.mapLightStyleUrl, + }); - factory ServerConfig.fromDto(ServerConfigDto dto) => ServerConfig( - trashDays: dto.trashDays, - oauthButtonText: dto.oauthButtonText, - externalDomain: dto.externalDomain, - mapDarkStyleUrl: dto.mapDarkStyleUrl, - mapLightStyleUrl: dto.mapLightStyleUrl, - ); + ServerConfig copyWith({int? trashDays, String? oauthButtonText, String? externalDomain}) { + return ServerConfig( + trashDays: trashDays ?? this.trashDays, + oauthButtonText: oauthButtonText ?? this.oauthButtonText, + externalDomain: externalDomain ?? this.externalDomain, + mapDarkStyleUrl: mapDarkStyleUrl, + mapLightStyleUrl: mapLightStyleUrl, + ); + } + + @override + String toString() => + 'ServerConfig(trashDays: $trashDays, oauthButtonText: $oauthButtonText, externalDomain: $externalDomain)'; + + ServerConfig.fromDto(ServerConfigDto dto) + : trashDays = dto.trashDays, + oauthButtonText = dto.oauthButtonText, + externalDomain = dto.externalDomain, + mapDarkStyleUrl = dto.mapDarkStyleUrl, + mapLightStyleUrl = dto.mapLightStyleUrl; + + @override + bool operator ==(covariant ServerConfig other) { + if (identical(this, other)) return true; + + return other.trashDays == trashDays && + other.oauthButtonText == oauthButtonText && + other.externalDomain == externalDomain; + } + + @override + int get hashCode => trashDays.hashCode ^ oauthButtonText.hashCode ^ externalDomain.hashCode; } diff --git a/mobile/lib/models/server_info/server_disk_info.model.dart b/mobile/lib/models/server_info/server_disk_info.model.dart index 910f06b065..01042b9f6d 100644 --- a/mobile/lib/models/server_info/server_disk_info.model.dart +++ b/mobile/lib/models/server_info/server_disk_info.model.dart @@ -1,22 +1,51 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -part 'server_disk_info.model.freezed.dart'; +class ServerDiskInfo { + final String diskAvailable; + final String diskSize; + final String diskUse; + final double diskUsagePercentage; -@freezed -abstract class ServerDiskInfo with _$ServerDiskInfo { - const factory ServerDiskInfo({ - required String diskAvailable, - required String diskSize, - required String diskUse, - required double diskUsagePercentage, - }) = _ServerDiskInfo; + const ServerDiskInfo({ + required this.diskAvailable, + required this.diskSize, + required this.diskUse, + required this.diskUsagePercentage, + }); - factory ServerDiskInfo.fromDto(ServerStorageResponseDto dto) => ServerDiskInfo( - diskAvailable: dto.diskAvailable, - diskSize: dto.diskSize, - diskUse: dto.diskUse, - diskUsagePercentage: dto.diskUsagePercentage, - ); + ServerDiskInfo copyWith({String? diskAvailable, String? diskSize, String? diskUse, double? diskUsagePercentage}) { + return ServerDiskInfo( + diskAvailable: diskAvailable ?? this.diskAvailable, + diskSize: diskSize ?? this.diskSize, + diskUse: diskUse ?? this.diskUse, + diskUsagePercentage: diskUsagePercentage ?? this.diskUsagePercentage, + ); + } + + @override + String toString() { + return 'ServerDiskInfo(diskAvailable: $diskAvailable, diskSize: $diskSize, diskUse: $diskUse, diskUsagePercentage: $diskUsagePercentage)'; + } + + ServerDiskInfo.fromDto(ServerStorageResponseDto dto) + : diskAvailable = dto.diskAvailable, + diskSize = dto.diskSize, + diskUse = dto.diskUse, + diskUsagePercentage = dto.diskUsagePercentage; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ServerDiskInfo && + other.diskAvailable == diskAvailable && + other.diskSize == diskSize && + other.diskUse == diskUse && + other.diskUsagePercentage == diskUsagePercentage; + } + + @override + int get hashCode { + return diskAvailable.hashCode ^ diskSize.hashCode ^ diskUse.hashCode ^ diskUsagePercentage.hashCode; + } } diff --git a/mobile/lib/models/server_info/server_features.model.dart b/mobile/lib/models/server_info/server_features.model.dart index 4de7e2e3cd..78a80c9013 100644 --- a/mobile/lib/models/server_info/server_features.model.dart +++ b/mobile/lib/models/server_info/server_features.model.dart @@ -1,26 +1,72 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -part 'server_features.model.freezed.dart'; +class ServerFeatures { + final bool trash; + final bool map; + final bool oauthEnabled; + final bool passwordLogin; + final bool ocr; + final bool smartSearch; -@freezed -abstract class ServerFeatures with _$ServerFeatures { - const factory ServerFeatures({ - required bool trash, - required bool map, - required bool oauthEnabled, - required bool passwordLogin, - @Default(false) bool ocr, - @Default(false) bool smartSearch, - }) = _ServerFeatures; + const ServerFeatures({ + required this.trash, + required this.map, + required this.oauthEnabled, + required this.passwordLogin, + this.ocr = false, + this.smartSearch = false, + }); - factory ServerFeatures.fromDto(ServerFeaturesDto dto) => ServerFeatures( - trash: dto.trash, - map: dto.map, - oauthEnabled: dto.oauth, - passwordLogin: dto.passwordLogin, - ocr: dto.ocr, - smartSearch: dto.smartSearch, - ); + ServerFeatures copyWith({ + bool? trash, + bool? map, + bool? oauthEnabled, + bool? passwordLogin, + bool? ocr, + bool? smartSearch, + }) { + return ServerFeatures( + trash: trash ?? this.trash, + map: map ?? this.map, + oauthEnabled: oauthEnabled ?? this.oauthEnabled, + passwordLogin: passwordLogin ?? this.passwordLogin, + ocr: ocr ?? this.ocr, + smartSearch: smartSearch ?? this.smartSearch, + ); + } + + @override + String toString() { + return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin, ocr: $ocr, smartSearch: $smartSearch)'; + } + + ServerFeatures.fromDto(ServerFeaturesDto dto) + : trash = dto.trash, + map = dto.map, + oauthEnabled = dto.oauth, + passwordLogin = dto.passwordLogin, + ocr = dto.ocr, + smartSearch = dto.smartSearch; + + @override + bool operator ==(covariant ServerFeatures other) { + if (identical(this, other)) return true; + + return other.trash == trash && + other.map == map && + other.oauthEnabled == oauthEnabled && + other.passwordLogin == passwordLogin && + other.ocr == ocr && + other.smartSearch == smartSearch; + } + + @override + int get hashCode { + return trash.hashCode ^ + map.hashCode ^ + oauthEnabled.hashCode ^ + passwordLogin.hashCode ^ + ocr.hashCode ^ + smartSearch.hashCode; + } } diff --git a/mobile/lib/models/server_info/server_info.model.dart b/mobile/lib/models/server_info/server_info.model.dart index 1ed7dd6f8b..a039bb70eb 100644 --- a/mobile/lib/models/server_info/server_info.model.dart +++ b/mobile/lib/models/server_info/server_info.model.dart @@ -1,13 +1,9 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/models/server_info/server_features.model.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; -part 'server_info.model.freezed.dart'; - enum VersionStatus { upToDate, clientOutOfDate, @@ -16,20 +12,72 @@ enum VersionStatus { String get message => switch (this) { VersionStatus.upToDate => "", - VersionStatus.clientOutOfDate => StaticTranslations.instance.app_update_available, - VersionStatus.serverOutOfDate => StaticTranslations.instance.server_update_available, - VersionStatus.error => StaticTranslations.instance.unable_to_check_version, + VersionStatus.clientOutOfDate => "app_update_available".tr(), + VersionStatus.serverOutOfDate => "server_update_available".tr(), + VersionStatus.error => "unable_to_check_version".tr(), }; } -@freezed -abstract class ServerInfo with _$ServerInfo { - const factory ServerInfo({ - required ServerVersion serverVersion, +class ServerInfo { + final ServerVersion serverVersion; + final ServerVersion? latestVersion; + final ServerFeatures serverFeatures; + final ServerConfig serverConfig; + final ServerDiskInfo serverDiskInfo; + final VersionStatus versionStatus; + + const ServerInfo({ + required this.serverVersion, + this.latestVersion, + required this.serverFeatures, + required this.serverConfig, + required this.serverDiskInfo, + required this.versionStatus, + }); + + ServerInfo copyWith({ + ServerVersion? serverVersion, ServerVersion? latestVersion, - required ServerFeatures serverFeatures, - required ServerConfig serverConfig, - required ServerDiskInfo serverDiskInfo, - required VersionStatus versionStatus, - }) = _ServerInfo; + ServerFeatures? serverFeatures, + ServerConfig? serverConfig, + ServerDiskInfo? serverDiskInfo, + VersionStatus? versionStatus, + }) { + return ServerInfo( + serverVersion: serverVersion ?? this.serverVersion, + latestVersion: latestVersion ?? this.latestVersion, + serverFeatures: serverFeatures ?? this.serverFeatures, + serverConfig: serverConfig ?? this.serverConfig, + serverDiskInfo: serverDiskInfo ?? this.serverDiskInfo, + versionStatus: versionStatus ?? this.versionStatus, + ); + } + + @override + String toString() { + return 'ServerInfo(serverVersion: $serverVersion, latestVersion: $latestVersion, serverFeatures: $serverFeatures, serverConfig: $serverConfig, serverDiskInfo: $serverDiskInfo, versionStatus: $versionStatus)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is ServerInfo && + other.serverVersion == serverVersion && + other.latestVersion == latestVersion && + other.serverFeatures == serverFeatures && + other.serverConfig == serverConfig && + other.serverDiskInfo == serverDiskInfo && + other.versionStatus == versionStatus; + } + + @override + int get hashCode { + return serverVersion.hashCode ^ + latestVersion.hashCode ^ + serverFeatures.hashCode ^ + serverConfig.hashCode ^ + serverDiskInfo.hashCode ^ + versionStatus.hashCode; + } } diff --git a/mobile/lib/models/server_info/server_version.model.dart b/mobile/lib/models/server_info/server_version.model.dart index cf16d4db0b..c8bf73db81 100644 --- a/mobile/lib/models/server_info/server_version.model.dart +++ b/mobile/lib/models/server_info/server_version.model.dart @@ -2,8 +2,16 @@ import 'package:immich_mobile/utils/semver.dart'; import 'package:openapi/api.dart'; class ServerVersion extends SemVer { - const ServerVersion({required super.major, required super.minor, required super.patch, super.prerelease}); + const ServerVersion({required super.major, required super.minor, required super.patch}); - ServerVersion.fromDto(ServerVersionResponseDto dto) - : super(major: dto.major, minor: dto.minor, patch: dto.patch_, prerelease: dto.prerelease); + @override + String toString() { + return 'ServerVersion(major: $major, minor: $minor, patch: $patch)'; + } + + ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_); + + bool isAtLeast({int major = 0, int minor = 0, int patch = 0}) { + return this >= SemVer(major: major, minor: minor, patch: patch); + } } diff --git a/mobile/lib/models/shared_link/shared_link.model.dart b/mobile/lib/models/shared_link/shared_link.model.dart index cf96609d3c..4315cf616a 100644 --- a/mobile/lib/models/shared_link/shared_link.model.dart +++ b/mobile/lib/models/shared_link/shared_link.model.dart @@ -1,47 +1,119 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:openapi/api.dart'; -part 'shared_link.model.freezed.dart'; - enum SharedLinkSource { album, individual } -@freezed -abstract class SharedLink with _$SharedLink { - const factory SharedLink({ - required String id, - required String title, - required bool allowDownload, - required bool allowUpload, - required String? thumbAssetId, - required String? description, - required String? password, - required DateTime? expiresAt, - required String key, - required bool showMetadata, - required SharedLinkSource type, - required String? slug, - }) = _SharedLink; +class SharedLink { + final String id; + final String title; + final bool allowDownload; + final bool allowUpload; + final String? thumbAssetId; + final String? description; + final String? password; + final DateTime? expiresAt; + final String key; + final bool showMetadata; + final SharedLinkSource type; + final String? slug; - factory SharedLink.fromDto(SharedLinkResponseDto dto) { - final isAlbum = dto.type == SharedLinkType.ALBUM; + const SharedLink({ + required this.id, + required this.title, + required this.allowDownload, + required this.allowUpload, + required this.thumbAssetId, + required this.description, + required this.password, + required this.expiresAt, + required this.key, + required this.showMetadata, + required this.type, + required this.slug, + }); + + SharedLink copyWith({ + String? id, + String? title, + String? thumbAssetId, + bool? allowDownload, + bool? allowUpload, + String? description, + String? password, + DateTime? expiresAt, + String? key, + bool? showMetadata, + SharedLinkSource? type, + String? slug, + }) { return SharedLink( - id: dto.id, - allowDownload: dto.allowDownload, - allowUpload: dto.allowUpload, - description: dto.description, - password: dto.password, - expiresAt: dto.expiresAt, - key: dto.key, - showMetadata: dto.showMetadata, - slug: dto.slug, - type: isAlbum ? SharedLinkSource.album : SharedLinkSource.individual, - title: isAlbum ? dto.album.orElse(null)?.albumName.toUpperCase() ?? "UNKNOWN SHARE" : "INDIVIDUAL SHARE", - thumbAssetId: isAlbum - ? dto.album.orElse(null)?.albumThumbnailAssetId - : dto.assets.isNotEmpty - ? dto.assets[0].id - : null, + id: id ?? this.id, + title: title ?? this.title, + thumbAssetId: thumbAssetId ?? this.thumbAssetId, + allowDownload: allowDownload ?? this.allowDownload, + allowUpload: allowUpload ?? this.allowUpload, + description: description ?? this.description, + password: password ?? this.password, + expiresAt: expiresAt ?? this.expiresAt, + key: key ?? this.key, + showMetadata: showMetadata ?? this.showMetadata, + type: type ?? this.type, + slug: slug ?? this.slug, ); } + + SharedLink.fromDto(SharedLinkResponseDto dto) + : id = dto.id, + allowDownload = dto.allowDownload, + allowUpload = dto.allowUpload, + description = dto.description, + password = dto.password, + expiresAt = dto.expiresAt, + key = dto.key, + showMetadata = dto.showMetadata, + slug = dto.slug, + type = dto.type == SharedLinkType.ALBUM ? SharedLinkSource.album : SharedLinkSource.individual, + title = dto.type == SharedLinkType.ALBUM + ? dto.album?.albumName.toUpperCase() ?? "UNKNOWN SHARE" + : "INDIVIDUAL SHARE", + thumbAssetId = dto.type == SharedLinkType.ALBUM + ? dto.album?.albumThumbnailAssetId + : dto.assets.isNotEmpty + ? dto.assets[0].id + : null; + + @override + String toString() => + 'SharedLink(id=$id, title=$title, thumbAssetId=$thumbAssetId, allowDownload=$allowDownload, allowUpload=$allowUpload, description=$description, password=$password, expiresAt=$expiresAt, key=$key, showMetadata=$showMetadata, type=$type, slug=$slug)'; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is SharedLink && + other.id == id && + other.title == title && + other.thumbAssetId == thumbAssetId && + other.allowDownload == allowDownload && + other.allowUpload == allowUpload && + other.description == description && + other.password == password && + other.expiresAt == expiresAt && + other.key == key && + other.showMetadata == showMetadata && + other.type == type && + other.slug == slug; + + @override + int get hashCode => + id.hashCode ^ + title.hashCode ^ + thumbAssetId.hashCode ^ + allowDownload.hashCode ^ + allowUpload.hashCode ^ + description.hashCode ^ + password.hashCode ^ + expiresAt.hashCode ^ + key.hashCode ^ + showMetadata.hashCode ^ + type.hashCode ^ + slug.hashCode; } diff --git a/mobile/lib/models/upload/share_intent_attachment.model.dart b/mobile/lib/models/upload/share_intent_attachment.model.dart index 2629bf3a0d..e5388fce2c 100644 --- a/mobile/lib/models/upload/share_intent_attachment.model.dart +++ b/mobile/lib/models/upload/share_intent_attachment.model.dart @@ -1,27 +1,34 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; import 'dart:io'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:path/path.dart'; -part 'share_intent_attachment.model.freezed.dart'; - enum ShareIntentAttachmentType { image, video } enum UploadStatus { enqueued, running, complete, failed } -@Freezed(fromJson: false, toJson: false, equal: false) -abstract class ShareIntentAttachment with _$ShareIntentAttachment { - const ShareIntentAttachment._(); +class ShareIntentAttachment { + final String path; - const factory ShareIntentAttachment({ - required String path, - required ShareIntentAttachmentType type, - required UploadStatus status, - @Default(0.0) double uploadProgress, - @Default(0) int fileLength, - }) = _ShareIntentAttachment; + // enum + final ShareIntentAttachmentType type; + + // enum + final UploadStatus status; + + final double uploadProgress; + + final int fileLength; + + ShareIntentAttachment({ + required this.path, + required this.type, + required this.status, + this.uploadProgress = 0, + this.fileLength = 0, + }); int get id => hash(path); @@ -33,7 +40,23 @@ abstract class ShareIntentAttachment with _$ShareIntentAttachment { bool get isVideo => type == ShareIntentAttachmentType.video; - String get fileSize => formatHumanReadableBytes(fileLength, 2); + String? _fileSize; + + String get fileSize => _fileSize ??= formatHumanReadableBytes(fileLength, 2); + + ShareIntentAttachment copyWith({ + String? path, + ShareIntentAttachmentType? type, + UploadStatus? status, + double? uploadProgress, + }) { + return ShareIntentAttachment( + path: path ?? this.path, + type: type ?? this.type, + status: status ?? this.status, + uploadProgress: uploadProgress ?? this.uploadProgress, + ); + } Map toMap() { return { @@ -58,14 +81,16 @@ abstract class ShareIntentAttachment with _$ShareIntentAttachment { factory ShareIntentAttachment.fromJson(String source) => ShareIntentAttachment.fromMap(json.decode(source) as Map); - // Identity is sourced from the backing file, not from upload progress @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + String toString() { + return 'ShareIntentAttachment(path: $path, type: $type, status: $status, uploadProgress: $uploadProgress)'; + } - return other is ShareIntentAttachment && other.path == path && other.type == type; + @override + bool operator ==(covariant ShareIntentAttachment other) { + if (identical(this, other)) return true; + + return other.path == path && other.type == type; } @override diff --git a/mobile/lib/models/view_intent/view_intent_payload.extension.dart b/mobile/lib/models/view_intent/view_intent_payload.extension.dart deleted file mode 100644 index ca66e6a163..0000000000 --- a/mobile/lib/models/view_intent/view_intent_payload.extension.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:path/path.dart'; - -extension ViewIntentPayloadX on ViewIntentPayload { - String get fileName { - final resolvedPath = path; - if (resolvedPath != null && resolvedPath.isNotEmpty) { - return basename(resolvedPath); - } - return localAssetId ?? 'view_intent_asset'; - } - - bool get isImage => mimeType.toLowerCase().startsWith('image/'); - - bool get isVideo => mimeType.toLowerCase().startsWith('video/'); - - AssetPlaybackStyle get playbackStyle { - if (isVideo) { - return AssetPlaybackStyle.video; - } - - final normalizedMimeType = mimeType.toLowerCase(); - if (normalizedMimeType == 'image/gif' || normalizedMimeType == 'image/webp') { - return AssetPlaybackStyle.imageAnimated; - } - - final normalizedPath = path?.toLowerCase(); - if (normalizedPath != null && (normalizedPath.endsWith('.gif') || normalizedPath.endsWith('.webp'))) { - return AssetPlaybackStyle.imageAnimated; - } - - return AssetPlaybackStyle.image; - } -} diff --git a/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart new file mode 100644 index 0000000000..f40ac9ccae --- /dev/null +++ b/mobile/lib/pages/album/album_additional_shared_user_selection.page.dart @@ -0,0 +1,115 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +@RoutePage() +class AlbumAdditionalSharedUserSelectionPage extends HookConsumerWidget { + final Album album; + + const AlbumAdditionalSharedUserSelectionPage({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final AsyncValue> suggestedShareUsers = ref.watch(otherUsersProvider); + final sharedUsersList = useState>({}); + + addNewUsersHandler() { + context.maybePop(sharedUsersList.value.map((e) => e.id).toList()); + } + + buildTileIcon(UserDto user) { + if (sharedUsersList.value.contains(user)) { + return CircleAvatar(backgroundColor: context.primaryColor, child: const Icon(Icons.check_rounded, size: 25)); + } else { + return UserCircleAvatar(user: user); + } + } + + buildUserList(List users) { + List usersChip = []; + + for (var user in sharedUsersList.value) { + usersChip.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Chip( + backgroundColor: context.primaryColor.withValues(alpha: 0.15), + label: Text(user.name, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold)), + ), + ), + ); + } + return ListView( + children: [ + Wrap(children: [...usersChip]), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'suggestions'.tr(), + style: const TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), + ), + ), + ListView.builder( + primary: false, + shrinkWrap: true, + itemBuilder: ((context, index) { + return ListTile( + leading: buildTileIcon(users[index]), + dense: true, + title: Text(users[index].name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + subtitle: Text(users[index].email, style: const TextStyle(fontSize: 12)), + onTap: () { + if (sharedUsersList.value.contains(users[index])) { + sharedUsersList.value = sharedUsersList.value + .where((selectedUser) => selectedUser.id != users[index].id) + .toSet(); + } else { + sharedUsersList.value = {...sharedUsersList.value, users[index]}; + } + }, + ); + }), + itemCount: users.length, + ), + ], + ); + } + + return Scaffold( + appBar: AppBar( + title: const Text('invite_to_album').tr(), + elevation: 0, + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close_rounded), + onPressed: () { + context.maybePop(null); + }, + ), + actions: [ + TextButton( + onPressed: sharedUsersList.value.isEmpty ? null : addNewUsersHandler, + child: const Text("add", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), + ), + ], + ), + body: suggestedShareUsers.widgetWhen( + onData: (users) { + for (var sharedUsers in album.sharedUsers) { + users.removeWhere((u) => u.id == sharedUsers.id || u.id == album.ownerId); + } + + return buildUserList(users); + }, + ), + ); + } +} diff --git a/mobile/lib/pages/album/album_asset_selection.page.dart b/mobile/lib/pages/album/album_asset_selection.page.dart new file mode 100644 index 0000000000..ccc4c44d43 --- /dev/null +++ b/mobile/lib/pages/album/album_asset_selection.page.dart @@ -0,0 +1,74 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +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/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; + +@RoutePage() +class AlbumAssetSelectionPage extends HookConsumerWidget { + const AlbumAssetSelectionPage({super.key, required this.existingAssets, this.canDeselect = false}); + + final Set existingAssets; + final bool canDeselect; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assetSelectionRenderList = ref.watch(assetSelectionTimelineProvider); + final selected = useState>(existingAssets); + final selectionEnabledHook = useState(true); + + Widget buildBody(RenderList renderList) { + return ImmichAssetGrid( + renderList: renderList, + listener: (active, assets) { + selectionEnabledHook.value = active; + selected.value = assets; + }, + selectionActive: true, + preselectedAssets: existingAssets, + canDeselect: canDeselect, + showMultiSelectIndicator: false, + ); + } + + return Scaffold( + appBar: AppBar( + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.close_rounded), + onPressed: () { + AutoRouter.of(context).popForced(null); + }, + ), + title: selected.value.isEmpty + ? const Text('add_photos', style: TextStyle(fontSize: 18)).tr() + : const Text( + 'share_assets_selected', + style: TextStyle(fontSize: 18), + ).tr(namedArgs: {'count': selected.value.length.toString()}), + centerTitle: false, + actions: [ + if (selected.value.isNotEmpty || canDeselect) + TextButton( + onPressed: () { + var payload = AssetSelectionPageResult(selectedAssets: selected.value); + AutoRouter.of(context).popForced(payload); + }, + child: Text( + canDeselect ? "done" : "add", + style: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor), + ).tr(), + ), + ], + ), + body: assetSelectionRenderList.widgetWhen(onData: (data) => buildBody(data)), + ); + } +} diff --git a/mobile/lib/pages/album/album_control_button.dart b/mobile/lib/pages/album/album_control_button.dart new file mode 100644 index 0000000000..578eb839a0 --- /dev/null +++ b/mobile/lib/pages/album/album_control_button.dart @@ -0,0 +1,37 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/widgets/album/album_action_filled_button.dart'; + +class AlbumControlButton extends ConsumerWidget { + final void Function()? onAddPhotosPressed; + final void Function()? onAddUsersPressed; + + const AlbumControlButton({super.key, this.onAddPhotosPressed, this.onAddUsersPressed}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SizedBox( + height: 36, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + if (onAddPhotosPressed != null) + AlbumActionFilledButton( + key: const ValueKey('add_photos_button'), + iconData: Icons.add_photo_alternate_outlined, + onPressed: onAddPhotosPressed, + labelText: "add_photos".tr(), + ), + if (onAddUsersPressed != null) + AlbumActionFilledButton( + key: const ValueKey('add_users_button'), + iconData: Icons.person_add_alt_rounded, + onPressed: onAddUsersPressed, + labelText: "album_viewer_page_share_add_users".tr(), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/pages/album/album_date_range.dart b/mobile/lib/pages/album/album_date_range.dart new file mode 100644 index 0000000000..dbfd9214f1 --- /dev/null +++ b/mobile/lib/pages/album/album_date_range.dart @@ -0,0 +1,53 @@ +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'; + +class AlbumDateRange extends ConsumerWidget { + const AlbumDateRange({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final data = ref.watch( + currentAlbumProvider.select((album) { + if (album == null || album.assets.isEmpty) { + return null; + } + + final startDate = album.startDate; + final endDate = album.endDate; + if (startDate == null || endDate == null) { + return null; + } + return (startDate, endDate, album.shared); + }), + ); + + if (data == null) { + return const SizedBox(); + } + final (startDate, endDate, shared) = data; + + return Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + _getDateRangeText(startDate, endDate), + style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant), + ), + ); + } + + @pragma('vm:prefer-inline') + String _getDateRangeText(DateTime startDate, DateTime endDate) { + if (startDate.day == endDate.day && startDate.month == endDate.month && startDate.year == endDate.year) { + return DateFormat.yMMMd().format(startDate); + } + + final String startDateText = (startDate.year == endDate.year ? DateFormat.MMMd() : DateFormat.yMMMd()).format( + startDate, + ); + final String endDateText = DateFormat.yMMMd().format(endDate); + return "$startDateText - $endDateText"; + } +} diff --git a/mobile/lib/pages/album/album_description.dart b/mobile/lib/pages/album/album_description.dart new file mode 100644 index 0000000000..383367e8b7 --- /dev/null +++ b/mobile/lib/pages/album/album_description.dart @@ -0,0 +1,42 @@ +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), + ); + } +} diff --git a/mobile/lib/pages/album/album_options.page.dart b/mobile/lib/pages/album/album_options.page.dart new file mode 100644 index 0000000000..ca65a92a79 --- /dev/null +++ b/mobile/lib/pages/album/album_options.page.dart @@ -0,0 +1,192 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +@RoutePage() +class AlbumOptionsPage extends HookConsumerWidget { + const AlbumOptionsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final album = ref.watch(currentAlbumProvider); + if (album == null) { + return const SizedBox(); + } + + final sharedUsers = useState(album.sharedUsers.map((u) => u.toDto()).toList()); + final owner = album.owner.value; + final userId = ref.watch(authProvider).userId; + final activityEnabled = useState(album.activityEnabled); + final isProcessing = useProcessingOverlay(); + final isOwner = owner?.id == userId; + + void showErrorMessage() { + context.pop(); + ImmichToast.show( + context: context, + msg: "shared_album_section_people_action_error".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + + void leaveAlbum() async { + isProcessing.value = true; + + try { + final isSuccess = await ref.read(albumProvider.notifier).leaveAlbum(album); + + if (isSuccess) { + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); + } else { + showErrorMessage(); + } + } catch (_) { + showErrorMessage(); + } + + isProcessing.value = false; + } + + void removeUserFromAlbum(UserDto user) async { + isProcessing.value = true; + + try { + await ref.read(albumProvider.notifier).removeUser(album, user); + album.sharedUsers.remove(entity.User.fromDto(user)); + sharedUsers.value = album.sharedUsers.map((u) => u.toDto()).toList(); + } catch (error) { + showErrorMessage(); + } + + context.pop(); + isProcessing.value = false; + } + + void handleUserClick(UserDto user) { + var actions = []; + + if (user.id == userId) { + actions = [ + ListTile( + leading: const Icon(Icons.exit_to_app_rounded), + title: const Text("shared_album_section_people_action_leave").tr(), + onTap: leaveAlbum, + ), + ]; + } + + if (isOwner) { + actions = [ + ListTile( + leading: const Icon(Icons.person_remove_rounded), + title: const Text("shared_album_section_people_action_remove_user").tr(), + onTap: () => removeUserFromAlbum(user), + ), + ]; + } + + showModalBottomSheet( + backgroundColor: context.colorScheme.surfaceContainer, + isScrollControlled: false, + context: context, + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column(mainAxisSize: MainAxisSize.min, children: [...actions]), + ), + ); + }, + ); + } + + buildOwnerInfo() { + return ListTile( + leading: owner != null ? UserCircleAvatar(user: owner.toDto()) : const SizedBox(), + title: Text(album.owner.value?.name ?? "", style: const TextStyle(fontWeight: FontWeight.w500)), + subtitle: Text(album.owner.value?.email ?? "", style: TextStyle(color: context.colorScheme.onSurfaceSecondary)), + trailing: Text("owner", style: context.textTheme.labelLarge).tr(), + ); + } + + buildSharedUsersList() { + return ListView.builder( + primary: false, + shrinkWrap: true, + itemCount: sharedUsers.value.length, + itemBuilder: (context, index) { + final user = sharedUsers.value[index]; + return ListTile( + leading: UserCircleAvatar(user: user), + title: Text(user.name, style: const TextStyle(fontWeight: FontWeight.w500)), + subtitle: Text(user.email, style: TextStyle(color: context.colorScheme.onSurfaceSecondary)), + trailing: userId == user.id || isOwner ? const Icon(Icons.more_horiz_rounded) : const SizedBox(), + onTap: userId == user.id || isOwner ? () => handleUserClick(user) : null, + ); + }, + ); + } + + buildSectionTitle(String text) { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Text(text, style: context.textTheme.bodySmall), + ); + } + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios_new_rounded), + onPressed: () => context.maybePop(null), + ), + centerTitle: true, + title: Text("options".tr()), + ), + body: ListView( + children: [ + if (isOwner && album.shared) + SwitchListTile.adaptive( + value: activityEnabled.value, + onChanged: (bool value) async { + activityEnabled.value = value; + if (await ref.read(albumProvider.notifier).setActivitystatus(album, value)) { + album.activityEnabled = value; + } + }, + activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor, + dense: true, + title: Text( + "comments_and_likes", + style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), + ).tr(), + subtitle: Text( + "let_others_respond", + style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ).tr(), + ), + buildSectionTitle("shared_album_section_people_title".tr()), + buildOwnerInfo(), + buildSharedUsersList(), + ], + ), + ); + } +} diff --git a/mobile/lib/pages/album/album_shared_user_icons.dart b/mobile/lib/pages/album/album_shared_user_icons.dart new file mode 100644 index 0000000000..7cf6f387ae --- /dev/null +++ b/mobile/lib/pages/album/album_shared_user_icons.dart @@ -0,0 +1,52 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +class AlbumSharedUserIcons extends HookConsumerWidget { + const AlbumSharedUserIcons({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sharedUsers = useRef>(const []); + sharedUsers.value = ref.watch( + currentAlbumProvider.select((album) { + if (album == null) { + return const []; + } + + if (album.sharedUsers.length == sharedUsers.value.length) { + return sharedUsers.value; + } + + return album.sharedUsers.map((u) => u.toDto()).toList(growable: false); + }), + ); + + if (sharedUsers.value.isEmpty) { + return const SizedBox(); + } + + return GestureDetector( + onTap: () => context.pushRoute(const AlbumOptionsRoute()), + child: SizedBox( + height: 50, + child: ListView.builder( + padding: const EdgeInsets.only(left: 16, bottom: 8), + scrollDirection: Axis.horizontal, + itemBuilder: ((context, index) { + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: UserCircleAvatar(user: sharedUsers.value[index], size: 36), + ); + }), + itemCount: sharedUsers.value.length, + ), + ), + ); + } +} diff --git a/mobile/lib/pages/album/album_shared_user_selection.page.dart b/mobile/lib/pages/album/album_shared_user_selection.page.dart new file mode 100644 index 0000000000..ec084b1859 --- /dev/null +++ b/mobile/lib/pages/album/album_shared_user_selection.page.dart @@ -0,0 +1,140 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.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/suggested_shared_users.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +@RoutePage() +class AlbumSharedUserSelectionPage extends HookConsumerWidget { + const AlbumSharedUserSelectionPage({super.key, required this.assets}); + + final Set assets; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final sharedUsersList = useState>({}); + final suggestedShareUsers = ref.watch(otherUsersProvider); + + createSharedAlbum() async { + var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(ref.watch(albumTitleProvider), assets); + + if (newAlbum != null) { + ref.watch(albumTitleProvider.notifier).clearAlbumTitle(); + unawaited(context.maybePop(true)); + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); + } + + ScaffoldMessenger( + child: SnackBar( + content: Text( + 'select_user_for_sharing_page_err_album', + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ).tr(), + ), + ); + } + + buildTileIcon(UserDto user) { + if (sharedUsersList.value.contains(user)) { + return CircleAvatar(backgroundColor: context.primaryColor, child: const Icon(Icons.check_rounded, size: 25)); + } else { + return UserCircleAvatar(user: user); + } + } + + buildUserList(List users) { + List usersChip = []; + + for (var user in sharedUsersList.value) { + usersChip.add( + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Chip( + backgroundColor: context.primaryColor.withValues(alpha: 0.15), + label: Text( + user.email, + style: const TextStyle(fontSize: 12, color: Colors.black87, fontWeight: FontWeight.bold), + ), + ), + ), + ); + } + return ListView( + children: [ + Wrap(children: [...usersChip]), + Padding( + padding: const EdgeInsets.all(16.0), + child: const Text( + 'suggestions', + style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), + ).tr(), + ), + ListView.builder( + primary: false, + shrinkWrap: true, + itemBuilder: ((context, index) { + return ListTile( + leading: buildTileIcon(users[index]), + title: Text(users[index].email, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + onTap: () { + if (sharedUsersList.value.contains(users[index])) { + sharedUsersList.value = sharedUsersList.value + .where((selectedUser) => selectedUser.id != users[index].id) + .toSet(); + } else { + sharedUsersList.value = {...sharedUsersList.value, users[index]}; + } + }, + ); + }), + itemCount: users.length, + ), + ], + ); + } + + return Scaffold( + appBar: AppBar( + title: Text('invite_to_album', style: TextStyle(color: context.primaryColor)).tr(), + elevation: 0, + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close_rounded), + onPressed: () { + unawaited(context.maybePop()); + }, + ), + actions: [ + TextButton( + style: TextButton.styleFrom(foregroundColor: context.primaryColor), + onPressed: sharedUsersList.value.isEmpty ? null : createSharedAlbum, + child: const Text( + "create_album", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + // color: context.primaryColor, + ), + ).tr(), + ), + ], + ), + body: suggestedShareUsers.widgetWhen( + onData: (users) { + return buildUserList(users); + }, + ), + ); + } +} diff --git a/mobile/lib/pages/album/album_title.dart b/mobile/lib/pages/album/album_title.dart new file mode 100644 index 0000000000..6c7fc3faaa --- /dev/null +++ b/mobile/lib/pages/album/album_title.dart @@ -0,0 +1,38 @@ +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_title.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; + +class AlbumTitle extends ConsumerWidget { + const AlbumTitle({super.key, required this.titleFocusNode}); + + final FocusNode titleFocusNode; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userId = ref.watch(authProvider).userId; + final (isOwner, isRemote, albumName) = ref.watch( + currentAlbumProvider.select((album) { + if (album == null) { + return const (false, false, ''); + } + + return (album.ownerId == userId, album.isRemote, album.name); + }), + ); + + if (isOwner && isRemote) { + return Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: AlbumViewerEditableTitle(albumName: albumName, titleFocusNode: titleFocusNode), + ); + } + + return Padding( + padding: const EdgeInsets.only(left: 16, right: 8), + child: Text(albumName, style: context.textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.w700)), + ); + } +} diff --git a/mobile/lib/pages/album/album_viewer.dart b/mobile/lib/pages/album/album_viewer.dart new file mode 100644 index 0000000000..97853fb96a --- /dev/null +++ b/mobile/lib/pages/album/album_viewer.dart @@ -0,0 +1,165 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +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'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/widgets/album/album_viewer_appbar.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class AlbumViewer extends HookConsumerWidget { + const AlbumViewer({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final album = ref.watch(currentAlbumProvider); + if (album == null) { + return const SizedBox(); + } + + final titleFocusNode = useFocusNode(); + final descriptionFocusNode = useFocusNode(); + final userId = ref.watch(authProvider).userId; + final isMultiselecting = ref.watch(multiselectProvider); + final isProcessing = useProcessingOverlay(); + final isOwner = ref.watch( + currentAlbumProvider.select((album) { + return album?.ownerId == userId; + }), + ); + + Future onRemoveFromAlbumPressed(Iterable assets) async { + final bool isSuccess = await ref.read(albumProvider.notifier).removeAsset(album, assets); + + if (!isSuccess) { + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_remove".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + return isSuccess; + } + + /// Find out if the assets in album exist on the device + /// If they exist, add to selected asset state to show they are already selected. + void onAddPhotosPressed() async { + AssetSelectionPageResult? returnPayload = await context.pushRoute( + AlbumAssetSelectionRoute(existingAssets: album.assets, canDeselect: false), + ); + + if (returnPayload != null && returnPayload.selectedAssets.isNotEmpty) { + // Check if there is new assets add + isProcessing.value = true; + + await ref.watch(albumProvider.notifier).addAssets(album, returnPayload.selectedAssets); + + isProcessing.value = false; + } + } + + void onAddUsersPressed() async { + List? sharedUserIds = await context.pushRoute?>( + AlbumAdditionalSharedUserSelectionRoute(album: album), + ); + + if (sharedUserIds != null) { + isProcessing.value = true; + + await ref.watch(albumProvider.notifier).addUsers(album, sharedUserIds); + + isProcessing.value = false; + } + } + + onActivitiesPressed() { + if (album.remoteId != null) { + ref.read(currentAssetProvider.notifier).set(null); + context.pushRoute(const ActivitiesRoute()); + } + } + + return Stack( + children: [ + 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], + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 32), + const AlbumDateRange(), + AlbumTitle(key: const ValueKey("albumTitle"), titleFocusNode: titleFocusNode), + AlbumDescription(key: const ValueKey("albumDescription"), descriptionFocusNode: descriptionFocusNode), + const AlbumSharedUserIcons(), + if (album.isRemote) + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: AlbumControlButton( + key: const ValueKey("albumControlButton"), + onAddPhotosPressed: onAddPhotosPressed, + onAddUsersPressed: isOwner ? onAddUsersPressed : null, + ), + ), + const SizedBox(height: 8), + ], + ), + ), + onRemoveFromAlbum: onRemoveFromAlbumPressed, + editEnabled: album.ownerId == userId, + ), + AnimatedPositioned( + key: const ValueKey("albumViewerAppbarPositioned"), + duration: const Duration(milliseconds: 300), + top: isMultiselecting ? -(kToolbarHeight + context.padding.top) : 0, + left: 0, + right: 0, + child: AlbumViewerAppbar( + key: const ValueKey("albumViewerAppbar"), + titleFocusNode: titleFocusNode, + descriptionFocusNode: descriptionFocusNode, + userId: userId, + onAddPhotos: onAddPhotosPressed, + onAddUsers: onAddUsersPressed, + onActivities: onActivitiesPressed, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/pages/album/album_viewer.page.dart b/mobile/lib/pages/album/album_viewer.page.dart new file mode 100644 index 0000000000..c99dacd9b7 --- /dev/null +++ b/mobile/lib/pages/album/album_viewer.page.dart @@ -0,0 +1,29 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/pages/album/album_viewer.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/timeline.provider.dart'; + +@RoutePage() +class AlbumViewerPage extends HookConsumerWidget { + final int albumId; + + const AlbumViewerPage({super.key, required this.albumId}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Listen provider to prevent autoDispose when navigating to other routes from within the viewer page + ref.listen(currentAlbumProvider, (_, __) {}); + + // This call helps rendering the asset selection instantly + ref.listen(assetSelectionTimelineProvider, (_, __) {}); + + ref.listen(albumWatcher(albumId), (_, albumFuture) { + albumFuture.whenData((value) => ref.read(currentAlbumProvider.notifier).set(value)); + }); + + return const Scaffold(body: AlbumViewer()); + } +} diff --git a/mobile/lib/pages/albums/albums.page.dart b/mobile/lib/pages/albums/albums.page.dart new file mode 100644 index 0000000000..5f155c2f0d --- /dev/null +++ b/mobile/lib/pages/albums/albums.page.dart @@ -0,0 +1,359 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +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/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'; +import 'package:immich_mobile/widgets/common/search_field.dart'; + +@RoutePage() +class AlbumsPage extends HookConsumerWidget { + const AlbumsPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(albumProvider).where((album) => album.isRemote).toList(); + final albumSortOption = ref.watch(albumSortByOptionsProvider); + final albumSortIsReverse = ref.watch(albumSortOrderProvider); + final sorted = albumSortOption.sortFn(albums, albumSortIsReverse); + final isGrid = useState(false); + final searchController = useTextEditingController(); + final debounceTimer = useRef(null); + final filterMode = useState(QuickFilterMode.all); + final userId = ref.watch(currentUserProvider)?.id; + final searchFocusNode = useFocusNode(); + + toggleViewMode() { + isGrid.value = !isGrid.value; + } + + onSearch(String searchTerm, QuickFilterMode mode) { + debounceTimer.value?.cancel(); + debounceTimer.value = Timer(const Duration(milliseconds: 300), () { + ref.read(albumProvider.notifier).searchAlbums(searchTerm, mode); + }); + } + + changeFilter(QuickFilterMode mode) { + filterMode.value = mode; + } + + useEffect(() { + searchController.addListener(() { + onSearch(searchController.text, filterMode.value); + }); + + return () { + searchController.removeListener(() { + onSearch(searchController.text, filterMode.value); + }); + debounceTimer.value?.cancel(); + }; + }, []); + + clearSearch() { + filterMode.value = QuickFilterMode.all; + searchController.clear(); + onSearch('', QuickFilterMode.all); + } + + return Scaffold( + appBar: ImmichAppBar( + showUploadButton: false, + actions: [ + IconButton( + icon: const Icon(Icons.add_rounded, size: 28), + onPressed: () => context.pushRoute(CreateAlbumRoute()), + ), + ], + ), + body: RefreshIndicator( + displacement: 70, + onRefresh: () async { + await ref.read(albumProvider.notifier).refreshRemoteAlbums(); + }, + child: ListView( + shrinkWrap: true, + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12), + children: [ + Container( + decoration: BoxDecoration( + border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0), + borderRadius: const BorderRadius.all(Radius.circular(24)), + gradient: LinearGradient( + colors: [ + context.colorScheme.primary.withValues(alpha: 0.075), + context.colorScheme.primary.withValues(alpha: 0.09), + context.colorScheme.primary.withValues(alpha: 0.075), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + transform: const GradientRotation(0.5 * pi), + ), + ), + child: SearchField( + autofocus: false, + contentPadding: const EdgeInsets.all(16), + hintText: 'search_albums'.tr(), + prefixIcon: const Icon(Icons.search_rounded), + suffixIcon: searchController.text.isNotEmpty + ? IconButton(icon: const Icon(Icons.clear_rounded), onPressed: clearSearch) + : null, + controller: searchController, + onChanged: (_) => onSearch(searchController.text, filterMode.value), + focusNode: searchFocusNode, + onTapOutside: (_) => searchFocusNode.unfocus(), + ), + ), + const SizedBox(height: 8), + Wrap( + spacing: 4, + runSpacing: 4, + children: [ + QuickFilterButton( + label: 'all'.tr(), + isSelected: filterMode.value == QuickFilterMode.all, + onTap: () { + changeFilter(QuickFilterMode.all); + onSearch(searchController.text, QuickFilterMode.all); + }, + ), + QuickFilterButton( + label: 'shared_with_me'.tr(), + isSelected: filterMode.value == QuickFilterMode.sharedWithMe, + onTap: () { + changeFilter(QuickFilterMode.sharedWithMe); + onSearch(searchController.text, QuickFilterMode.sharedWithMe); + }, + ), + QuickFilterButton( + label: 'my_albums'.tr(), + isSelected: filterMode.value == QuickFilterMode.myAlbums, + onTap: () { + changeFilter(QuickFilterMode.myAlbums); + onSearch(searchController.text, QuickFilterMode.myAlbums); + }, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SortButton(), + IconButton( + icon: Icon(isGrid.value ? Icons.view_list_outlined : Icons.grid_view_outlined, size: 24), + onPressed: toggleViewMode, + ), + ], + ), + const SizedBox(height: 5), + AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: isGrid.value + ? GridView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 250, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + childAspectRatio: .7, + ), + itemBuilder: (context, index) { + return AlbumThumbnailCard( + album: sorted[index], + onTap: () => context.pushRoute(AlbumViewerRoute(albumId: sorted[index].id)), + showOwner: true, + ); + }, + itemCount: sorted.length, + ) + : ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: sorted.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: LargeLeadingTile( + title: Text( + sorted[index].name, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), + ), + subtitle: sorted[index].ownerId != null + ? Text( + '${'items_count'.t(context: context, args: {'count': sorted[index].assetCount})} â€ĸ ${sorted[index].ownerId != userId ? 'shared_by_user'.t(context: context, args: {'user': sorted[index].ownerName!}) : 'owned'.t(context: context)}', + overflow: TextOverflow.ellipsis, + style: context.textTheme.bodyMedium?.copyWith( + color: context.colorScheme.onSurfaceSecondary, + ), + ) + : null, + onTap: () => context.pushRoute(AlbumViewerRoute(albumId: sorted[index].id)), + leadingPadding: const EdgeInsets.only(right: 16), + leading: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15)), + child: ImmichThumbnail(asset: sorted[index].thumbnail.value, width: 80, height: 80), + ), + // minVerticalPadding: 1, + ), + ); + }, + ), + ), + ], + ), + ), + resizeToAvoidBottomInset: false, + ); + } +} + +class QuickFilterButton extends StatelessWidget { + const QuickFilterButton({super.key, required this.isSelected, required this.onTap, required this.label}); + + final bool isSelected; + final VoidCallback onTap; + final String label; + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: onTap, + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all(isSelected ? context.colorScheme.primary : Colors.transparent), + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(20)), + side: BorderSide(color: context.colorScheme.onSurface.withAlpha(25), width: 1), + ), + ), + ), + child: Text( + label, + style: TextStyle( + color: isSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + fontSize: 14, + ), + ), + ); + } +} + +class SortButton extends ConsumerWidget { + const SortButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumSortOption = ref.watch(albumSortByOptionsProvider); + final albumSortIsReverse = ref.watch(albumSortOrderProvider); + + return MenuAnchor( + style: MenuStyle( + elevation: const WidgetStatePropertyAll(1), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), + ), + padding: const WidgetStatePropertyAll(EdgeInsets.all(4)), + ), + consumeOutsideTap: true, + menuChildren: AlbumSortMode.values + .map( + (mode) => MenuItemButton( + leadingIcon: albumSortOption == mode + ? albumSortIsReverse + ? Icon( + Icons.keyboard_arrow_down, + color: albumSortOption == mode + ? context.colorScheme.onPrimary + : context.colorScheme.onSurface, + ) + : Icon( + Icons.keyboard_arrow_up_rounded, + color: albumSortOption == mode + ? context.colorScheme.onPrimary + : context.colorScheme.onSurface, + ) + : const Icon(Icons.abc, color: Colors.transparent), + onPressed: () { + final selected = albumSortOption == mode; + // Switch direction + if (selected) { + ref.read(albumSortOrderProvider.notifier).changeSortDirection(!albumSortIsReverse); + } else { + ref.read(albumSortByOptionsProvider.notifier).changeSortMode(mode); + } + }, + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(16, 16, 32, 16)), + backgroundColor: WidgetStateProperty.all( + albumSortOption == mode ? context.colorScheme.primary : Colors.transparent, + ), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), + ), + ), + child: Text( + mode.label.tr(), + style: context.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w600, + color: albumSortOption == mode + ? context.colorScheme.onPrimary + : context.colorScheme.onSurface.withAlpha(185), + ), + ), + ), + ) + .toList(), + builder: (context, controller, child) { + return GestureDetector( + onTap: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 5), + child: Transform.rotate( + angle: 90 * pi / 180, + child: Icon( + Icons.compare_arrows_rounded, + size: 18, + color: context.colorScheme.onSurface.withAlpha(225), + ), + ), + ), + Text( + albumSortOption.label.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: context.colorScheme.onSurface.withAlpha(225), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/mobile/lib/pages/backup/album_preview.page.dart b/mobile/lib/pages/backup/album_preview.page.dart new file mode 100644 index 0000000000..def31afcd4 --- /dev/null +++ b/mobile/lib/pages/backup/album_preview.page.dart @@ -0,0 +1,64 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; + +@RoutePage() +class AlbumPreviewPage extends HookConsumerWidget { + final Album album; + const AlbumPreviewPage({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assets = useState>([]); + + getAssetsInAlbum() async { + assets.value = await ref.read(albumMediaRepositoryProvider).getAssets(album.localId!); + } + + useEffect(() { + getAssetsInAlbum(); + return null; + }, []); + + return Scaffold( + appBar: AppBar( + elevation: 0, + title: Column( + children: [ + Text(album.name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + Padding( + padding: const EdgeInsets.only(top: 4.0), + child: Text( + "ID ${album.id}", + style: TextStyle( + fontSize: 10, + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_new_rounded)), + ), + body: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 5, + crossAxisSpacing: 2, + mainAxisSpacing: 2, + ), + itemCount: assets.value.length, + itemBuilder: (context, index) { + return ImmichThumbnail(asset: assets.value[index], width: 100, height: 100); + }, + ), + ); + } +} diff --git a/mobile/lib/pages/backup/backup_album_selection.page.dart b/mobile/lib/pages/backup/backup_album_selection.page.dart new file mode 100644 index 0000000000..d222211577 --- /dev/null +++ b/mobile/lib/pages/backup/backup_album_selection.page.dart @@ -0,0 +1,225 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/widgets/backup/album_info_card.dart'; +import 'package:immich_mobile/widgets/backup/album_info_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; + +@RoutePage() +class BackupAlbumSelectionPage extends HookConsumerWidget { + const BackupAlbumSelectionPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums; + final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums; + final enableSyncUploadAlbum = useAppSettingsState(AppSettingsEnum.syncAlbums); + final isDarkTheme = context.isDarkTheme; + final albums = ref.watch(backupProvider).availableAlbums; + + useEffect(() { + ref.watch(backupProvider.notifier).getBackupInfo(); + return null; + }, []); + + buildAlbumSelectionList() { + if (albums.isEmpty) { + return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); + } + + return SliverPadding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + sliver: SliverList( + delegate: SliverChildBuilderDelegate(((context, index) { + return AlbumInfoListTile(album: albums[index]); + }), childCount: albums.length), + ), + ); + } + + buildAlbumSelectionGrid() { + if (albums.isEmpty) { + return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); + } + + return SliverPadding( + padding: const EdgeInsets.all(12.0), + sliver: SliverGrid.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 300, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + ), + itemCount: albums.length, + itemBuilder: ((context, index) { + return AlbumInfoCard(album: albums[index]); + }), + ), + ); + } + + buildSelectedAlbumNameChip() { + return selectedBackupAlbums.map((album) { + void removeSelection() => ref.read(backupProvider.notifier).removeAlbumForBackup(album); + + return Padding( + padding: const EdgeInsets.only(right: 8.0), + child: GestureDetector( + onTap: removeSelection, + child: Chip( + label: Text( + album.name, + style: TextStyle( + fontSize: 12, + color: isDarkTheme ? Colors.black : Colors.white, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: context.primaryColor, + deleteIconColor: isDarkTheme ? Colors.black : Colors.white, + deleteIcon: const Icon(Icons.cancel_rounded, size: 15), + onDeleted: removeSelection, + ), + ), + ); + }).toSet(); + } + + buildExcludedAlbumNameChip() { + return excludedBackupAlbums.map((album) { + void removeSelection() { + ref.watch(backupProvider.notifier).removeExcludedAlbumForBackup(album); + } + + return GestureDetector( + onTap: removeSelection, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Chip( + label: Text( + album.name, + style: TextStyle(fontSize: 12, color: context.scaffoldBackgroundColor, fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.red[300], + deleteIconColor: context.scaffoldBackgroundColor, + deleteIcon: const Icon(Icons.cancel_rounded, size: 15), + onDeleted: removeSelection, + ), + ), + ); + }).toSet(); + } + + handleSyncAlbumToggle(bool isEnable) async { + if (isEnable) { + await ref.read(albumProvider.notifier).refreshRemoteAlbums(); + for (final album in selectedBackupAlbums) { + await ref.read(albumProvider.notifier).createSyncAlbum(album.name); + } + } + } + + return Scaffold( + appBar: AppBar( + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + title: const Text("backup_album_selection_page_select_albums").tr(), + elevation: 0, + ), + body: CustomScrollView( + physics: const ClampingScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Text("backup_album_selection_page_selection_info", style: context.textTheme.titleSmall).tr(), + ), + + // Selected Album Chips + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Wrap(children: [...buildSelectedAlbumNameChip(), ...buildExcludedAlbumNameChip()]), + ), + + SettingsSwitchListTile( + valueNotifier: enableSyncUploadAlbum, + title: "sync_albums".tr(), + subtitle: "sync_upload_album_setting_subtitle".tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + titleStyle: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.bold), + subtitleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary), + onChanged: handleSyncAlbumToggle, + ), + + ListTile( + title: Text( + "backup_album_selection_page_albums_device".tr( + namedArgs: {'count': ref.watch(backupProvider).availableAlbums.length.toString()}, + ), + style: context.textTheme.titleSmall, + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + "backup_album_selection_page_albums_tap", + style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + ).tr(), + ), + trailing: IconButton( + splashRadius: 16, + icon: Icon(Icons.info, size: 20, color: context.primaryColor), + onPressed: () { + // show the dialog + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + elevation: 5, + title: Text( + 'backup_album_selection_page_selection_info', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor), + ).tr(), + content: SingleChildScrollView( + child: ListBody( + children: [ + const Text( + 'backup_album_selection_page_assets_scatter', + style: TextStyle(fontSize: 14), + ).tr(), + ], + ), + ), + ); + }, + ); + }, + ), + ), + + // buildSearchBar(), + ], + ), + ), + SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent > 600) { + return buildAlbumSelectionGrid(); + } else { + return buildAlbumSelectionList(); + } + }, + ), + ], + ), + ); + } +} diff --git a/mobile/lib/pages/backup/backup_controller.page.dart b/mobile/lib/pages/backup/backup_controller.page.dart new file mode 100644 index 0000000000..1e008be1bb --- /dev/null +++ b/mobile/lib/pages/backup/backup_controller.page.dart @@ -0,0 +1,286 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; +import 'package:immich_mobile/widgets/backup/current_backup_asset_info_box.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +@RoutePage() +class BackupControllerPage extends HookConsumerWidget { + const BackupControllerPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + BackUpState backupState = ref.watch(backupProvider); + final hasAnyAlbum = backupState.selectedBackupAlbums.isNotEmpty; + final didGetBackupInfo = useState(false); + + bool hasExclusiveAccess = backupState.backupProgress != BackUpProgressEnum.inBackground; + bool shouldBackup = + backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length == 0 || + !hasExclusiveAccess + ? false + : true; + + useEffect(() { + // Update the background settings information just to make sure we + // have the latest, since the platform channel will not update + // automatically + if (Platform.isIOS) { + ref.watch(iOSBackgroundSettingsProvider.notifier).refresh(); + } + + ref.watch(websocketProvider.notifier).stopListenToEvent('on_upload_success'); + + return () { + WakelockPlus.disable(); + }; + }, []); + + useEffect(() { + if (backupState.backupProgress == BackUpProgressEnum.idle && !didGetBackupInfo.value) { + ref.watch(backupProvider.notifier).getBackupInfo(); + didGetBackupInfo.value = true; + } + return null; + }, [backupState.backupProgress]); + + useEffect(() { + if (backupState.backupProgress == BackUpProgressEnum.inProgress) { + WakelockPlus.enable(); + } else { + WakelockPlus.disable(); + } + + return null; + }, [backupState.backupProgress]); + + Widget buildSelectedAlbumName() { + var text = "backup_controller_page_backup_selected".tr(); + var albums = ref.watch(backupProvider).selectedBackupAlbums; + + if (albums.isNotEmpty) { + for (var album in albums) { + if (album.name == "Recent" || album.name == "Recents") { + text += "${album.name} (${'all'.tr()}), "; + } else { + text += "${album.name}, "; + } + } + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + text.trim().substring(0, text.length - 2), + style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + "backup_controller_page_none_selected".tr(), + style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + ), + ); + } + } + + Widget buildExcludedAlbumName() { + var text = "backup_controller_page_excluded".tr(); + var albums = ref.watch(backupProvider).excludedBackupAlbums; + + if (albums.isNotEmpty) { + for (var album in albums) { + text += "${album.name}, "; + } + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + text.trim().substring(0, text.length - 2), + style: context.textTheme.labelLarge?.copyWith(color: Colors.red[300]), + ), + ); + } else { + return const SizedBox(); + } + } + + buildFolderSelectionTile() { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Card( + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(20)), + side: BorderSide(color: context.colorScheme.outlineVariant, width: 1), + ), + elevation: 0, + borderOnForeground: false, + child: ListTile( + minVerticalPadding: 18, + title: Text("backup_controller_page_albums", style: context.textTheme.titleMedium).tr(), + subtitle: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "backup_controller_page_to_backup", + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ).tr(), + buildSelectedAlbumName(), + buildExcludedAlbumName(), + ], + ), + ), + trailing: ElevatedButton( + onPressed: () async { + await context.pushRoute(const BackupAlbumSelectionRoute()); + // waited until returning from selection + await ref.read(backupProvider.notifier).backupAlbumSelectionDone(); + // waited until backup albums are stored in DB + await ref.read(albumProvider.notifier).refreshDeviceAlbums(); + }, + child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), + ), + ), + ); + } + + void startBackup() { + ref.watch(errorBackupListProvider.notifier).empty(); + if (ref.watch(backupProvider).backupProgress != BackUpProgressEnum.inBackground) { + ref.watch(backupProvider.notifier).startBackupProcess(); + } + } + + Widget buildBackupButton() { + return Padding( + padding: const EdgeInsets.only(top: 24), + child: Container( + child: + backupState.backupProgress == BackUpProgressEnum.inProgress || + backupState.backupProgress == BackUpProgressEnum.manualInProgress + ? ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.grey[50], + backgroundColor: Colors.red[300], + // padding: const EdgeInsets.all(14), + ), + onPressed: () { + if (backupState.backupProgress == BackUpProgressEnum.manualInProgress) { + ref.read(manualUploadProvider.notifier).cancelBackup(); + } else { + ref.read(backupProvider.notifier).cancelBackup(); + } + }, + child: const Text("cancel", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), + ) + : ElevatedButton( + onPressed: shouldBackup ? startBackup : null, + child: const Text( + "backup_controller_page_start_backup", + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ).tr(), + ), + ), + ); + } + + buildBackgroundBackupInfo() { + return ListTile( + leading: const Icon(Icons.info_outline_rounded), + title: Text('background_backup_running_error'.tr()), + ); + } + + buildLoadingIndicator() { + return const Padding( + padding: EdgeInsets.only(top: 42.0), + child: Center(child: CircularProgressIndicator()), + ); + } + + return Scaffold( + appBar: AppBar( + elevation: 0, + title: const Text("backup_controller_page_backup").tr(), + leading: IconButton( + onPressed: () { + ref.watch(websocketProvider.notifier).listenUploadEvent(); + context.maybePop(true); + }, + splashRadius: 24, + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: IconButton( + onPressed: () => context.pushRoute(const BackupOptionsRoute()), + splashRadius: 24, + icon: const Icon(Icons.settings_outlined), + ), + ), + ], + ), + body: Stack( + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 32), + child: ListView( + // crossAxisAlignment: CrossAxisAlignment.start, + children: hasAnyAlbum + ? [ + buildFolderSelectionTile(), + BackupInfoCard( + title: "total".tr(), + subtitle: "backup_controller_page_total_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.allUniqueAssets.length}", + ), + BackupInfoCard( + title: "backup_controller_page_backup".tr(), + subtitle: "backup_controller_page_backup_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${backupState.selectedAlbumsBackupAssetsIds.length}", + ), + BackupInfoCard( + title: "backup_controller_page_remainder".tr(), + subtitle: "backup_controller_page_remainder_sub".tr(), + info: ref.watch(backupProvider).availableAlbums.isEmpty + ? "..." + : "${max(0, backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length)}", + ), + const Divider(), + const CurrentUploadingAssetInfoBox(), + if (!hasExclusiveAccess) buildBackgroundBackupInfo(), + buildBackupButton(), + ] + : [buildFolderSelectionTile(), if (!didGetBackupInfo.value) buildLoadingIndicator()], + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/pages/backup/backup_options.page.dart b/mobile/lib/pages/backup/backup_options.page.dart new file mode 100644 index 0000000000..846a32a742 --- /dev/null +++ b/mobile/lib/pages/backup/backup_options.page.dart @@ -0,0 +1,24 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart'; + +@RoutePage() +class BackupOptionsPage extends StatelessWidget { + const BackupOptionsPage({super.key}); + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + title: const Text("backup_options_page_title").tr(), + leading: IconButton( + onPressed: () => context.maybePop(true), + splashRadius: 24, + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + ), + body: const BackupSettings(), + ); + } +} diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index a0e55e58de..6bdb8dd552 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -1,29 +1,25 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.widget.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; -import 'package:immich_ui/immich_ui.dart'; import 'package:logging/logging.dart'; -import 'package:permission_handler/permission_handler.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @RoutePage() @@ -41,7 +37,7 @@ class _DriftBackupPageState extends ConsumerState { void initState() { super.initState(); - unawaited(WakelockPlus.enable()); + WakelockPlus.enable(); final currentUser = ref.read(currentUserProvider); if (currentUser == null) { @@ -65,9 +61,9 @@ class _DriftBackupPageState extends ConsumerState { } @override - void dispose() { + dispose() { super.dispose(); - unawaited(WakelockPlus.disable()); + WakelockPlus.disable(); } @override @@ -79,8 +75,8 @@ class _DriftBackupPageState extends ConsumerState { final error = ref.watch(driftBackupProvider.select((p) => p.error)); - final backupNotifier = ref.watch(driftBackupProvider.notifier); - final backupSyncManager = ref.watch(backgroundSyncProvider); + final backupNotifier = ref.read(driftBackupProvider.notifier); + final backupSyncManager = ref.read(backgroundSyncProvider); Future startBackup() async { final currentUser = Store.tryGet(StoreKey.currentUser); @@ -106,10 +102,10 @@ class _DriftBackupPageState extends ConsumerState { return Scaffold( appBar: AppBar( elevation: 0, - title: Text(context.t.backup_controller_page_backup), + title: Text("backup_controller_page_backup".t()), leading: IconButton( onPressed: () { - unawaited(context.maybePop(true)); + context.maybePop(true); }, splashRadius: 24, icon: const Icon(Icons.arrow_back_ios_rounded), @@ -117,10 +113,10 @@ class _DriftBackupPageState extends ConsumerState { actions: [ IconButton( onPressed: () { - unawaited(context.pushRoute(const DriftBackupOptionsRoute())); + context.pushRoute(const DriftBackupOptionsRoute()); }, icon: const Icon(Icons.settings_outlined), - tooltip: context.t.backup_options, + tooltip: "backup_options".t(context: context), ), ], ), @@ -141,7 +137,7 @@ class _DriftBackupPageState extends ConsumerState { onStart: () async => await startBackup(), onStop: () { syncSuccess = null; - backupNotifier.stopForegroundBackup(reason: "backup button toggled off"); + backupNotifier.stopForegroundBackup(); }, ), switch (error) { @@ -166,7 +162,11 @@ class _DriftBackupPageState extends ConsumerState { ), ), }, - const _BackupFooter(), + TextButton.icon( + icon: const Icon(Icons.info_outline_rounded), + onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), + label: Text("view_details".t(context: context)), + ), ], ], ), @@ -177,155 +177,22 @@ class _DriftBackupPageState extends ConsumerState { } } -class _BackupFooter extends ConsumerStatefulWidget { - const _BackupFooter(); - - @override - ConsumerState<_BackupFooter> createState() => _BackupFooterState(); -} - -class _BackupFooterState extends ConsumerState<_BackupFooter> with WidgetsBindingObserver { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (CurrentPlatform.isAndroid && state == AppLifecycleState.resumed && mounted) { - unawaited(ref.read(notificationPermissionProvider.notifier).getNotificationPermission()); - unawaited(ref.read(batteryOptimizationProvider.notifier).getBatteryOptimizationPermission()); - } - } - - Future showPermissionsDialog() { - return showDialog( - context: context, - builder: (ctx) => AlertDialog( - content: Text(context.t.notification_permission_dialog_content), - actions: [ - ImmichTextButton( - labelText: context.t.cancel, - variant: .ghost, - expanded: false, - onPressed: () => ContextHelper(ctx).pop(), - ), - ImmichTextButton( - labelText: context.t.settings, - variant: .ghost, - expanded: false, - onPressed: () { - ContextHelper(context).pop(); - unawaited(openAppSettings()); - }, - ), - ], - ), - ); - } - - Future showBatteryOptimizationInfo() { - return showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext ctx) { - return AlertDialog( - title: Text(context.t.backup_controller_page_background_battery_info_title), - content: SingleChildScrollView(child: Text(context.t.backup_controller_page_background_battery_info_message)), - actions: [ - ImmichTextButton( - labelText: context.t.backup_controller_page_background_battery_info_link, - variant: .ghost, - expanded: false, - onPressed: () => - unawaited(launchUrl(Uri.parse('https://dontkillmyapp.com'), mode: LaunchMode.externalApplication)), - ), - ImmichTextButton( - labelText: context.t.backup_controller_page_background_battery_info_ok, - variant: .ghost, - expanded: false, - onPressed: () => ContextHelper(ctx).pop(), - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - final isBackupEnabled = ref.watch(appConfigProvider.select((config) => config.backup.enabled)); - final notificationStatus = ref.watch(notificationPermissionProvider); - final batteryOptimizationStatus = ref.watch(batteryOptimizationProvider).valueOrNull; - - return Column( - children: [ - if (CurrentPlatform.isAndroid && isBackupEnabled) ...[ - if (notificationStatus != PermissionStatus.granted) - TextButton.icon( - iconAlignment: .end, - icon: Icon(Icons.open_in_new_outlined, color: context.colorScheme.onSurfaceSecondary), - label: Text( - context.t.notification_backup_reliability, - textAlign: TextAlign.left, - style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), - onPressed: () { - unawaited( - ref.read(notificationPermissionProvider.notifier).requestNotificationPermission().then((p) { - if (p == PermissionStatus.permanentlyDenied) { - unawaited(showPermissionsDialog()); - } - }), - ); - }, - ), - if (notificationStatus != PermissionStatus.granted && batteryOptimizationStatus != PermissionStatus.granted) - const Divider(indent: 32, endIndent: 32), - if (batteryOptimizationStatus != PermissionStatus.granted) - TextButton.icon( - iconAlignment: .end, - icon: Icon(Icons.open_in_new_outlined, color: context.colorScheme.onSurfaceSecondary), - label: Text( - context.t.battery_optimization_backup_reliability, - textAlign: TextAlign.left, - style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), - onPressed: () => unawaited(showBatteryOptimizationInfo()), - ), - ], - TextButton.icon( - icon: const Icon(Icons.info_outline_rounded), - onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), - label: Text(context.t.view_details), - ), - ], - ); - } -} - class _BackupAlbumSelectionCard extends ConsumerWidget { const _BackupAlbumSelectionCard(); @override Widget build(BuildContext context, WidgetRef ref) { - final backupAlbums = ref.watch(backupAlbumProvider); - Widget buildSelectedAlbumName() { - String text = context.t.backup_controller_page_backup_selected; - final albums = backupAlbums.where((album) => album.backupSelection == BackupSelection.selected).toList(); + String text = "backup_controller_page_backup_selected".tr(); + final albums = ref + .watch(backupAlbumProvider) + .where((album) => album.backupSelection == BackupSelection.selected) + .toList(); if (albums.isNotEmpty) { for (var album in albums) { if (album.name == "Recent" || album.name == "Recents") { - text += "${album.name} (${context.t.all}), "; + text += "${album.name} (${'all'.tr()}), "; } else { text += "${album.name}, "; } @@ -342,7 +209,7 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - context.t.backup_controller_page_none_selected, + "backup_controller_page_none_selected".tr(), style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), ), ); @@ -350,8 +217,11 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { } Widget buildExcludedAlbumName() { - String text = context.t.backup_controller_page_excluded; - final albums = backupAlbums.where((album) => album.backupSelection == BackupSelection.excluded).toList(); + String text = "backup_controller_page_excluded".tr(); + final albums = ref + .watch(backupAlbumProvider) + .where((album) => album.backupSelection == BackupSelection.excluded) + .toList(); if (albums.isNotEmpty) { for (var album in albums) { @@ -379,16 +249,16 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { borderOnForeground: false, child: ListTile( minVerticalPadding: 18, - title: Text(context.t.backup_controller_page_albums, style: context.textTheme.titleMedium), + title: Text("backup_controller_page_albums", style: context.textTheme.titleMedium).tr(), subtitle: Padding( padding: const EdgeInsets.only(top: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.backup_controller_page_to_backup, + "backup_controller_page_to_backup", style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), + ).tr(), buildSelectedAlbumName(), buildExcludedAlbumName(), ], @@ -397,17 +267,13 @@ class _BackupAlbumSelectionCard extends ConsumerWidget { trailing: ElevatedButton( onPressed: () async { await context.pushRoute(const DriftBackupAlbumSelectionRoute()); - if (!context.mounted) { - return; - } - final currentUser = ref.read(currentUserProvider); if (currentUser == null) { return; } unawaited(ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id)); }, - child: Text(context.t.select, style: const TextStyle(fontWeight: FontWeight.bold)), + child: const Text("select", style: TextStyle(fontWeight: FontWeight.bold)).tr(), ), ), ); @@ -422,8 +288,8 @@ class _TotalCard extends ConsumerWidget { final totalCount = ref.watch(driftBackupProvider.select((p) => p.totalCount)); return BackupInfoCard( - title: context.t.total, - subtitle: context.t.backup_controller_page_total_sub, + title: "total".tr(), + subtitle: "backup_controller_page_total_sub".tr(), info: totalCount.toString(), ); } @@ -438,8 +304,8 @@ class _BackupCard extends ConsumerWidget { final syncStatus = ref.watch(syncStatusProvider); return BackupInfoCard( - title: context.t.backup_controller_page_backup, - subtitle: context.t.backup_controller_page_backup_sub, + title: "backup_controller_page_backup".tr(), + subtitle: "backup_controller_page_backup_sub".tr(), info: backupCount.toString(), isLoading: syncStatus.isRemoteSyncing, ); @@ -466,11 +332,11 @@ class _RemainderCard extends ConsumerWidget { ListTile( minVerticalPadding: 18, isThreeLine: true, - title: Text(context.t.backup_controller_page_remainder, style: context.textTheme.titleMedium), + title: Text("backup_controller_page_remainder".t(context: context), style: context.textTheme.titleMedium), subtitle: Padding( padding: const EdgeInsets.only(top: 4.0, right: 18.0), child: Text( - context.t.backup_controller_page_remainder_sub, + "backup_controller_page_remainder_sub".t(context: context), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), ), @@ -503,11 +369,11 @@ class _RemainderCard extends ConsumerWidget { ], ), Text( - context.t.backup_info_card_assets, + "backup_info_card_assets", style: context.textTheme.labelLarge?.copyWith( color: context.colorScheme.onSurface.withAlpha(syncStatus.isRemoteSyncing ? 50 : 255), ), - ), + ).tr(), ], ), ), @@ -524,7 +390,7 @@ class _RemainderCard extends ConsumerWidget { ), onTap: () => context.pushRoute(const DriftBackupAssetDetailRoute()), title: Text( - context.t.view_details, + "view_details".t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)), ), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: context.colorScheme.onSurfaceVariant), @@ -552,19 +418,12 @@ class _PreparingStatusState extends ConsumerState { } void _startPollingIfNeeded() { - if (_pollingTimer != null) { - return; - } + if (_pollingTimer != null) return; _pollingTimer = Timer.periodic(const Duration(seconds: 3), (timer) async { final currentUser = ref.read(currentUserProvider); if (currentUser != null && mounted) { await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); - if (!context.mounted) { - timer.cancel(); - _pollingTimer = null; - return; - } // Stop polling if processing count reaches 0 final updatedProcessingCount = ref.read(driftBackupProvider.select((p) => p.processingCount)); @@ -616,7 +475,7 @@ class _PreparingStatusState extends ConsumerState { Row( children: [ Text( - context.t.preparing, + "preparing".t(context: context), style: context.textTheme.labelLarge?.copyWith( color: context.colorScheme.onSurface.withAlpha(200), ), @@ -646,7 +505,7 @@ class _PreparingStatusState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - context.t.ready_for_upload, + "ready_for_upload".t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)), ), const SizedBox(height: 2), diff --git a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart index 26719c2884..1732385675 100644 --- a/mobile/lib/pages/backup/drift_backup_album_selection.page.dart +++ b/mobile/lib/pages/backup/drift_backup_album_selection.page.dart @@ -1,28 +1,24 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:logging/logging.dart'; -final backupAlbumCountProvider = FutureProvider.autoDispose((ref) async { - await ref.read(backupAlbumProvider.notifier).getAll(); - return ref.read(backupAlbumProvider).length; -}); - @RoutePage() class DriftBackupAlbumSelectionPage extends ConsumerStatefulWidget { const DriftBackupAlbumSelectionPage({super.key}); @@ -47,7 +43,8 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); } @@ -58,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState a.backupSelection == BackupSelection.selected) @@ -82,14 +79,11 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState p.totalCount)); final totalChanged = currentTotalAssetCount != _initialTotalAssetCount; final backupNotifier = ref.read(driftBackupProvider.notifier); @@ -125,7 +112,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState backgroundSync.hashAssets())); if (isBackupEnabled) { - backupNotifier.stopForegroundBackup(reason: "backup albums updated"); + backupNotifier.stopForegroundBackup(); unawaited( backgroundSync.syncRemote().then((success) { if (success) { @@ -138,10 +125,6 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState setState(() => _searchQuery = value.trim()), ) - : Text(context.t.backup_album_selection_page_select_albums), + : const Text("backup_album_selection_page_select_albums").t(context: context), actions: [ if (!_isSearchMode) IconButton( @@ -193,9 +176,9 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState 600) { - return _AlbumSelectionGrid(filteredAlbums: filteredAlbums); - } else { - return _AlbumSelectionList(filteredAlbums: filteredAlbums); - } - }, - ), + SliverLayoutBuilder( + builder: (context, constraints) { + if (constraints.crossAxisExtent > 600) { + return _AlbumSelectionGrid(filteredAlbums: filteredAlbums, searchQuery: _searchQuery); + } else { + return _AlbumSelectionList(filteredAlbums: filteredAlbums, searchQuery: _searchQuery); + } + }, + ), ], ), if (_handleLinkedAlbumFuture != null) @@ -292,7 +262,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState filteredAlbums; + final String searchQuery; - const _AlbumSelectionList({required this.filteredAlbums}); + const _AlbumSelectionList({required this.filteredAlbums, required this.searchQuery}); @override Widget build(BuildContext context) { + if (filteredAlbums.isEmpty && searchQuery.isNotEmpty) { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Text('album_search_not_found'.t(context: context)), + ), + ), + ); + } + + if (filteredAlbums.isEmpty) { + return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); + } + return SliverPadding( padding: const EdgeInsets.symmetric(vertical: 12.0), sliver: SliverList( - delegate: SliverChildBuilderDelegate((context, index) { + delegate: SliverChildBuilderDelegate(((context, index) { return DriftAlbumInfoListTile(album: filteredAlbums[index]); - }, childCount: filteredAlbums.length), + }), childCount: filteredAlbums.length), ), ); } @@ -337,11 +323,27 @@ class _AlbumSelectionList extends StatelessWidget { class _AlbumSelectionGrid extends StatelessWidget { final List filteredAlbums; + final String searchQuery; - const _AlbumSelectionGrid({required this.filteredAlbums}); + const _AlbumSelectionGrid({required this.filteredAlbums, required this.searchQuery}); @override Widget build(BuildContext context) { + if (filteredAlbums.isEmpty && searchQuery.isNotEmpty) { + return SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const EdgeInsets.all(24.0), + child: Text('album_search_not_found'.t(context: context)), + ), + ), + ); + } + + if (filteredAlbums.isEmpty) { + return const SliverToBoxAdapter(child: Center(child: CircularProgressIndicator())); + } + return SliverPadding( padding: const EdgeInsets.all(12.0), sliver: SliverGrid.builder( @@ -351,9 +353,9 @@ class _AlbumSelectionGrid extends StatelessWidget { crossAxisSpacing: 12, ), itemCount: filteredAlbums.length, - itemBuilder: (context, index) { + itemBuilder: ((context, index) { return DriftAlbumInfoListTile(album: filteredAlbums[index]); - }, + }), ), ); } @@ -370,14 +372,14 @@ class _SelectedAlbumNameChips extends ConsumerWidget { children: selectedBackupAlbums.asMap().entries.map((entry) { final album = entry.value; - Future removeSelection() { - return ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + void removeSelection() { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); } return Padding( padding: const EdgeInsets.only(right: 8.0), child: GestureDetector( - onTap: () => unawaited(removeSelection()), + onTap: removeSelection, child: AnimatedContainer( duration: const Duration(milliseconds: 200), curve: Curves.easeInOut, @@ -393,7 +395,7 @@ class _SelectedAlbumNameChips extends ConsumerWidget { backgroundColor: context.primaryColor, deleteIconColor: context.isDarkTheme ? Colors.black : Colors.white, deleteIcon: const Icon(Icons.cancel_rounded, size: 15), - onDeleted: () => unawaited(removeSelection()), + onDeleted: removeSelection, ), ), ), @@ -414,12 +416,12 @@ class _ExcludedAlbumNameChips extends ConsumerWidget { children: excludedBackupAlbums.asMap().entries.map((entry) { final album = entry.value; - Future removeSelection() { - return ref.read(backupAlbumProvider.notifier).deselectAlbum(album); + void removeSelection() { + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); } return GestureDetector( - onTap: () => unawaited(removeSelection()), + onTap: removeSelection, child: Padding( padding: const EdgeInsets.only(right: 8.0), child: AnimatedContainer( @@ -433,7 +435,7 @@ class _ExcludedAlbumNameChips extends ConsumerWidget { backgroundColor: Colors.red[300], deleteIconColor: context.scaffoldBackgroundColor, deleteIcon: const Icon(Icons.cancel_rounded, size: 15), - onDeleted: () => unawaited(removeSelection()), + onDeleted: removeSelection, ), ), ), @@ -463,13 +465,16 @@ class _SelectAllButton extends ConsumerWidget { ? () { for (final album in filteredAlbums) { if (album.backupSelection != BackupSelection.selected) { - unawaited(ref.read(backupAlbumProvider.notifier).selectAlbum(album)); + ref.read(backupAlbumProvider.notifier).selectAlbum(album); } } } : null, icon: const Icon(Icons.select_all), - label: AnimatedSwitcher(duration: const Duration(milliseconds: 200), child: Text(context.t.select_all)), + label: AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: Text("select_all".t(context: context)), + ), style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12.0)), ), ), @@ -480,13 +485,13 @@ class _SelectAllButton extends ConsumerWidget { ? () { for (final album in filteredAlbums) { if (album.backupSelection == BackupSelection.selected) { - unawaited(ref.read(backupAlbumProvider.notifier).deselectAlbum(album)); + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); } } } : null, icon: const Icon(Icons.deselect), - label: Text(context.t.deselect_all), + label: Text('deselect_all'.t(context: context)), style: OutlinedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12.0)), ), ), diff --git a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart index 18ad9fbd3d..36d51c5624 100644 --- a/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart +++ b/mobile/lib/pages/backup/drift_backup_asset_detail.page.dart @@ -1,4 +1,5 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -6,7 +7,7 @@ import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; @@ -19,9 +20,9 @@ class DriftBackupAssetDetailPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final AsyncValue> result = ref.watch(driftBackupCandidateProvider); + AsyncValue> result = ref.watch(driftBackupCandidateProvider); return Scaffold( - appBar: AppBar(title: Text(context.t.backup_controller_page_remainder)), + appBar: AppBar(title: Text('backup_controller_page_remainder'.t(context: context))), body: result.when( data: (List candidates) { return ListView.separated( @@ -65,7 +66,7 @@ class DriftBackupAssetDetailPage extends ConsumerWidget { ); }, error: (error, stackTrace) => Text( - context.t.error_saving_image(error: error.toString()), + 'error_saving_image'.tr(args: [error.toString()]), style: TextStyle(color: context.colorScheme.error), ), loading: () => @@ -87,10 +88,6 @@ class DriftBackupAssetDetailPage extends ConsumerWidget { ), onTap: () async { await context.maybePop(); - if (!context.mounted) { - return; - } - await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); EventStream.shared.emit(ScrollToDateEvent(asset.createdAt)); }, @@ -101,7 +98,7 @@ class DriftBackupAssetDetailPage extends ConsumerWidget { ); }, error: (Object error, StackTrace stackTrace) { - return Center(child: Text(context.t.error_saving_image(error: error.toString()))); + return Center(child: Text('error_saving_image'.tr(args: [error.toString()]))); }, loading: () { return const SizedBox(height: 48, width: 48, child: Center(child: CircularProgressIndicator.adaptive())); diff --git a/mobile/lib/pages/backup/drift_backup_options.page.dart b/mobile/lib/pages/backup/drift_backup_options.page.dart index 994f09766c..79891d7002 100644 --- a/mobile/lib/pages/backup/drift_backup_options.page.dart +++ b/mobile/lib/pages/backup/drift_backup_options.page.dart @@ -3,12 +3,14 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart'; import 'package:logging/logging.dart'; @@ -19,20 +21,18 @@ class DriftBackupOptionsPage extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { bool hasPopped = false; - final previousBackup = ref.watch(appConfigProvider.select((s) => s.backup)); - final previousCellularForVideos = previousBackup.useCellularForVideos; - final previousCellularForPhotos = previousBackup.useCellularForPhotos; + final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; + final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; return PopScope( onPopInvokedWithResult: (didPop, result) async { // There is an issue with Flutter where the pop event // can be triggered multiple times, so we guard it with _hasPopped - final currentBackup = ref.read(appConfigProvider).backup; - final currentCellularForVideos = currentBackup.useCellularForVideos; - final currentCellularForPhotos = currentBackup.useCellularForPhotos; + final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false; + final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false; - if (currentCellularForVideos == previousCellularForVideos && - currentCellularForPhotos == previousCellularForPhotos) { + if (currentWifiReqForVideos == previousWifiReqForVideos && + currentWifiReqForPhotos == previousWifiReqForPhotos) { return; } @@ -45,18 +45,21 @@ class DriftBackupOptionsPage extends ConsumerWidget { } await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id); - final isBackupEnabled = SettingsRepository.instance.appConfig.backup.enabled; - if (!isBackupEnabled || !context.mounted) { + final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); + if (!isBackupEnabled) { return; } ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(context.t.network_requirements_updated), duration: const Duration(seconds: 4)), + SnackBar( + content: Text("network_requirements_updated".t(context: context)), + duration: const Duration(seconds: 4), + ), ); final backupNotifier = ref.read(driftBackupProvider.notifier); final backgroundSync = ref.read(backgroundSyncProvider); - backupNotifier.stopForegroundBackup(reason: "backup settings updated"); + backupNotifier.stopForegroundBackup(); unawaited( backgroundSync.syncRemote().then((success) { if (success) { @@ -69,7 +72,7 @@ class DriftBackupOptionsPage extends ConsumerWidget { } }, child: Scaffold( - appBar: AppBar(title: Text(context.t.backup_options)), + appBar: AppBar(title: Text("backup_options".t(context: context))), body: const DriftBackupSettings(), ), ); diff --git a/mobile/lib/pages/backup/drift_upload_detail.page.dart b/mobile/lib/pages/backup/drift_upload_detail.page.dart index 764cb5dcf1..71249d1c4b 100644 --- a/mobile/lib/pages/backup/drift_upload_detail.page.dart +++ b/mobile/lib/pages/backup/drift_upload_detail.page.dart @@ -3,10 +3,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:path/path.dart' as path; @@ -40,9 +40,7 @@ class _DriftUploadDetailPageState extends ConsumerState { } for (final item in uploadingItems) { - if (_taskSlotAssignments.containsKey(item.taskId)) { - continue; - } + if (_taskSlotAssignments.containsKey(item.taskId)) continue; for (int i = 0; i < _maxSlots; i++) { if (slots[i] == null) { @@ -80,7 +78,7 @@ class _DriftUploadDetailPageState extends ConsumerState { return Scaffold( appBar: AppBar( - title: Text(context.t.upload_details), + title: Text("upload_details".t(context: context)), backgroundColor: context.colorScheme.surface, elevation: 0, scrolledUnderElevation: 1, @@ -125,7 +123,7 @@ class _DriftUploadDetailPageState extends ConsumerState { SliverToBoxAdapter( child: _buildSectionHeader( context, - title: context.t.uploading, + title: "uploading".t(context: context), count: uploadingItems.length, color: context.colorScheme.primary, ), @@ -151,7 +149,7 @@ class _DriftUploadDetailPageState extends ConsumerState { SliverToBoxAdapter( child: _buildSectionHeader( context, - title: context.t.errors_text, + title: "errors_text".t(context: context), count: failedItems.length, color: context.colorScheme.error, ), @@ -231,7 +229,7 @@ class _DriftUploadDetailPageState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.downloading_from_icloud, + "downloading_from_icloud".t(context: context), style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -318,7 +316,7 @@ class _DriftUploadDetailPageState extends ConsumerState { const SizedBox(height: 2), Text( isFailed - ? item.error ?? context.t.errors.unable_to_upload_file + ? item.error ?? "unable_to_upload_file".t(context: context) : "${formatHumanReadableBytes(item.fileSize, 1)} â€ĸ ${item.networkSpeedAsString}", style: context.textTheme.labelLarge?.copyWith( color: isFailed @@ -395,7 +393,7 @@ class _DriftUploadDetailPageState extends ConsumerState { ), const SizedBox(height: 4), Text( - item.error ?? context.t.errors.unable_to_upload_file, + item.error ?? "unable_to_upload_file".t(context: context), style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.error), maxLines: 4, overflow: TextOverflow.ellipsis, @@ -429,7 +427,7 @@ class _DriftUploadDetailPageState extends ConsumerState { SizedBox( width: 48, height: 48, - child: DecoratedBox( + child: Container( decoration: BoxDecoration( color: context.colorScheme.outline.withValues(alpha: 0.1), borderRadius: const BorderRadius.all(Radius.circular(8)), @@ -520,7 +518,7 @@ class _CurrentUploadThumbnail extends ConsumerWidget { ), clipBehavior: Clip.antiAlias, child: snapshot.data != null - ? Thumbnail.fromAsset(asset: snapshot.data, size: const Size(48, 48), fit: BoxFit.cover) + ? Thumbnail.fromAsset(asset: snapshot.data!, size: const Size(48, 48), fit: BoxFit.cover) : Icon(Icons.image, size: 24, color: context.colorScheme.primary), ), ); @@ -530,7 +528,7 @@ class _CurrentUploadThumbnail extends ConsumerWidget { Future _getAsset(WidgetRef ref) async { try { - return await ref.read(driftProvider).localAssetRepository.getById(taskId); + return await ref.read(localAssetRepository).getById(taskId); } catch (e) { return null; } @@ -556,7 +554,7 @@ class FileDetailDialog extends ConsumerWidget { const SizedBox(width: 8), Expanded( child: Text( - context.t.details, + "details".t(context: context), style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), ), ), @@ -595,14 +593,20 @@ class FileDetailDialog extends ConsumerWidget { const SizedBox(height: 24), if (asset != null) _buildInfoSection(context, [ - _buildInfoRow(context, context.t.filename, path.basename(uploadStatus.filename)), - _buildInfoRow(context, context.t.local_id, asset.id), - _buildInfoRow(context, context.t.file_size, formatHumanReadableBytes(uploadStatus.fileSize, 2)), - if (asset.width != null) _buildInfoRow(context, context.t.width, "${asset.width}px"), - if (asset.height != null) _buildInfoRow(context, context.t.height, "${asset.height}px"), - _buildInfoRow(context, context.t.created_at, asset.createdAt.toString()), - _buildInfoRow(context, context.t.updated_at, asset.updatedAt.toString()), - if (asset.checksum != null) _buildInfoRow(context, context.t.checksum, asset.checksum!), + _buildInfoRow(context, "filename".t(context: context), path.basename(uploadStatus.filename)), + _buildInfoRow(context, "local_id".t(context: context), asset.id), + _buildInfoRow( + context, + "file_size".t(context: context), + formatHumanReadableBytes(uploadStatus.fileSize, 2), + ), + if (asset.width != null) _buildInfoRow(context, "width".t(context: context), "${asset.width}px"), + if (asset.height != null) + _buildInfoRow(context, "height".t(context: context), "${asset.height}px"), + _buildInfoRow(context, "created_at".t(context: context), asset.createdAt.toString()), + _buildInfoRow(context, "updated_at".t(context: context), asset.updatedAt.toString()), + if (asset.checksum != null) + _buildInfoRow(context, "checksum".t(context: context), asset.checksum!), ]), ], ), @@ -614,7 +618,7 @@ class FileDetailDialog extends ConsumerWidget { TextButton( onPressed: () => Navigator.of(context).pop(), child: Text( - context.t.close, + "close".t(), style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), ), ), @@ -661,7 +665,7 @@ class FileDetailDialog extends ConsumerWidget { Future _getAssetDetails(WidgetRef ref, String localAssetId) async { try { - return await ref.read(driftProvider).localAssetRepository.getById(localAssetId); + return await ref.read(localAssetRepository).getById(localAssetId); } catch (e) { return null; } diff --git a/mobile/lib/pages/backup/failed_backup_status.page.dart b/mobile/lib/pages/backup/failed_backup_status.page.dart new file mode 100644 index 0000000000..a97a133b89 --- /dev/null +++ b/mobile/lib/pages/backup/failed_backup_status.page.dart @@ -0,0 +1,116 @@ +import 'package:auto_route/auto_route.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/presentation/widgets/images/local_image_provider.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:intl/intl.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; + +@RoutePage() +class FailedBackupStatusPage extends HookConsumerWidget { + const FailedBackupStatusPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final errorBackupList = ref.watch(errorBackupListProvider); + + return Scaffold( + appBar: AppBar( + elevation: 0, + title: Text( + "Failed Backup (${errorBackupList.length})", + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + leading: IconButton( + onPressed: () { + context.maybePop(true); + }, + splashRadius: 24, + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + ), + body: ListView.builder( + shrinkWrap: true, + itemCount: errorBackupList.length, + itemBuilder: ((context, index) { + var errorAsset = errorBackupList.elementAt(index); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4), + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(15), // if you need this + ), + side: BorderSide(color: Colors.black12, width: 1), + ), + elevation: 0, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ConstrainedBox( + constraints: const BoxConstraints(minWidth: 100, minHeight: 100, maxWidth: 100, maxHeight: 150), + child: ClipRRect( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(15), + topLeft: Radius.circular(15), + ), + clipBehavior: Clip.hardEdge, + child: Image( + fit: BoxFit.cover, + image: LocalThumbProvider(id: errorAsset.asset.localId!, assetType: base_asset.AssetType.video), + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat.yMMMMd().format( + DateTime.parse(errorAsset.fileCreatedAt.toString()).toLocal(), + ), + style: TextStyle( + fontWeight: FontWeight.w600, + color: context.isDarkTheme ? Colors.white70 : Colors.grey[800], + ), + ), + Icon(Icons.error, color: Colors.red.withAlpha(200), size: 18), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + errorAsset.fileName, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor), + ), + ), + Text( + errorAsset.errorMessage, + style: TextStyle( + fontWeight: FontWeight.w500, + color: context.isDarkTheme ? Colors.white70 : Colors.grey[800], + ), + ), + ], + ), + ), + ), + ], + ), + ), + ); + }), + ), + ); + } +} diff --git a/mobile/lib/pages/common/activities.page.dart b/mobile/lib/pages/common/activities.page.dart new file mode 100644 index 0000000000..9d1123dbca --- /dev/null +++ b/mobile/lib/pages/common/activities.page.dart @@ -0,0 +1,97 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity.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/user.provider.dart'; +import 'package:immich_mobile/widgets/activities/activity_text_field.dart'; +import 'package:immich_mobile/widgets/activities/activity_tile.dart'; +import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; + +@RoutePage() +class ActivitiesPage extends HookConsumerWidget { + const ActivitiesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // Album has to be set in the provider before reaching this page + final album = ref.watch(currentAlbumProvider)!; + final asset = ref.watch(currentAssetProvider); + final user = ref.watch(currentUserProvider); + + final activityNotifier = ref.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier); + final activities = ref.watch(albumActivityProvider(album.remoteId!, asset?.remoteId)); + + final listViewScrollController = useScrollController(); + + Future onAddComment(String comment) async { + await activityNotifier.addComment(comment); + // Scroll to the end of the list to show the newly added activity + await listViewScrollController.animateTo( + listViewScrollController.position.maxScrollExtent + 200, + duration: const Duration(milliseconds: 600), + curve: Curves.fastOutSlowIn, + ); + } + + return Scaffold( + appBar: AppBar(title: asset == null ? Text(album.name) : null), + body: activities.widgetWhen( + onData: (data) { + final liked = data.firstWhereOrNull( + (a) => a.type == ActivityType.like && a.user.id == user?.id && a.assetId == asset?.remoteId, + ); + + return SafeArea( + child: Stack( + children: [ + ListView.builder( + controller: listViewScrollController, + // +1 to display an additional over-scroll space after the last element + itemCount: data.length + 1, + itemBuilder: (context, index) { + // Additional vertical gap after the last element + if (index == data.length) { + return const SizedBox(height: 80); + } + + final activity = data[index]; + final canDelete = activity.user.id == user?.id || album.ownerId == user?.id; + + return Padding( + padding: const EdgeInsets.all(5), + child: DismissibleActivity( + activity.id, + ActivityTile(activity), + onDismiss: canDelete + ? (activityId) async => await activityNotifier.removeActivity(activity.id) + : null, + ), + ); + }, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: context.scaffoldBackgroundColor, + child: ActivityTextField( + isEnabled: album.activityEnabled, + likeId: liked?.id, + onSubmit: onAddComment, + ), + ), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/mobile/lib/pages/common/app_log.page.dart b/mobile/lib/pages/common/app_log.page.dart index d0b5096288..336bf0b605 100644 --- a/mobile/lib/pages/common/app_log.page.dart +++ b/mobile/lib/pages/common/app_log.page.dart @@ -1,23 +1,21 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/immich_logger.service.dart'; -import 'package:intl/intl.dart'; @RoutePage() -class AppLogPage extends HookWidget { +class AppLogPage extends HookConsumerWidget { const AppLogPage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final immichLogger = LogService.I; final shouldReload = useState(false); final logMessages = useFuture(useMemoized(() => immichLogger.getMessages(), [shouldReload.value])); @@ -51,7 +49,7 @@ class AppLogPage extends HookWidget { return Scaffold( appBar: AppBar( - title: Text(context.t.logs, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0)), + title: Text('logs'.tr(), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0)), scrolledUnderElevation: 1, elevation: 2, actions: [ @@ -63,7 +61,7 @@ class AppLogPage extends HookWidget { size: 20.0, ), onPressed: () { - unawaited(immichLogger.clearLogs()); + immichLogger.clearLogs(); shouldReload.value = !shouldReload.value; }, ), @@ -72,7 +70,7 @@ class AppLogPage extends HookWidget { return IconButton( icon: Icon(Icons.share_rounded, color: context.primaryColor, semanticLabel: "Share logs", size: 20.0), onPressed: () { - unawaited(ImmichLogger.shareLogs(iconContext)); + ImmichLogger.shareLogs(iconContext); }, ); }, @@ -80,7 +78,7 @@ class AppLogPage extends HookWidget { ], leading: IconButton( onPressed: () { - unawaited(context.maybePop()); + context.maybePop(); }, icon: const Icon(Icons.arrow_back_ios_new_rounded, size: 20.0), ), @@ -92,7 +90,7 @@ class AppLogPage extends HookWidget { }, itemCount: logMessages.data?.length ?? 0, itemBuilder: (context, index) { - final logMessage = logMessages.data![index]; + var logMessage = logMessages.data![index]; return ListTile( onTap: () => context.pushRoute(AppLogDetailRoute(logMessage: logMessage)), trailing: const Icon(Icons.arrow_forward_ios_rounded), @@ -118,7 +116,7 @@ class AppLogPage extends HookWidget { /// Truncate the log message to a certain number of lines /// @param int maxLines - Max number of lines to truncate String truncateLogMessage(String message, int maxLines) { - final List messageLines = message.split("\n"); + List messageLines = message.split("\n"); if (messageLines.length < maxLines) { return message; } diff --git a/mobile/lib/pages/common/app_log_detail.page.dart b/mobile/lib/pages/common/app_log_detail.page.dart index 8e7eaf3ba8..890e46888f 100644 --- a/mobile/lib/pages/common/app_log_detail.page.dart +++ b/mobile/lib/pages/common/app_log_detail.page.dart @@ -1,22 +1,20 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; @RoutePage() -class AppLogDetailPage extends HookWidget { +class AppLogDetailPage extends HookConsumerWidget { const AppLogDetailPage({super.key, required this.logMessage}); final LogMessage logMessage; @override - Widget build(BuildContext context) { - Padding buildTextWithCopyButton(String header, String text) { + Widget build(BuildContext context, WidgetRef ref) { + buildTextWithCopyButton(String header, String text) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( @@ -35,28 +33,22 @@ class AppLogDetailPage extends HookWidget { ), IconButton( onPressed: () { - unawaited( - Clipboard.setData(ClipboardData(text: text)).then((_) { - if (!context.mounted) { - return; - } - - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - context.t.copied_to_clipboard, - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ), + Clipboard.setData(ClipboardData(text: text)).then((_) { + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "copied_to_clipboard".tr(), + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), - ); - }), - ); + ), + ); + }); }, icon: Icon(Icons.copy, size: 16.0, color: context.primaryColor), ), ], ), - DecoratedBox( + Container( decoration: BoxDecoration( color: context.colorScheme.surfaceContainerHigh, borderRadius: const BorderRadius.all(Radius.circular(15.0)), @@ -74,7 +66,7 @@ class AppLogDetailPage extends HookWidget { ); } - Padding buildLogContext(String logger) { + buildLogContext(String logger) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( @@ -87,7 +79,7 @@ class AppLogDetailPage extends HookWidget { style: TextStyle(fontSize: 12.0, color: context.primaryColor, fontWeight: FontWeight.bold), ), ), - DecoratedBox( + Container( decoration: BoxDecoration( color: context.colorScheme.surfaceContainerHigh, borderRadius: const BorderRadius.all(Radius.circular(15.0)), @@ -95,7 +87,7 @@ class AppLogDetailPage extends HookWidget { child: Padding( padding: const EdgeInsets.all(8.0), child: SelectableText( - logger, + logger.toString(), style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "GoogleSansCode"), ), ), @@ -106,7 +98,7 @@ class AppLogDetailPage extends HookWidget { } return Scaffold( - appBar: AppBar(title: Text(context.t.log_detail_title)), + appBar: AppBar(title: Text("log_detail_title".tr())), body: SafeArea( child: ListView( children: [ diff --git a/mobile/lib/pages/common/change_experience.page.dart b/mobile/lib/pages/common/change_experience.page.dart new file mode 100644 index 0000000000..2cc3dede1e --- /dev/null +++ b/mobile/lib/pages/common/change_experience.page.dart @@ -0,0 +1,168 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; +import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:immich_mobile/utils/migration.dart'; +import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; + +@RoutePage() +class ChangeExperiencePage extends ConsumerStatefulWidget { + final bool switchingToBeta; + + const ChangeExperiencePage({super.key, required this.switchingToBeta}); + + @override + ConsumerState createState() => _ChangeExperiencePageState(); +} + +class _ChangeExperiencePageState extends ConsumerState { + AsyncValue hasMigrated = const AsyncValue.loading(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) => _handleMigration()); + } + + Future _handleMigration() async { + try { + await _performMigrationLogic().timeout( + const Duration(minutes: 3), + onTimeout: () async { + await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta); + await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta); + }, + ); + + if (mounted) { + setState(() { + HapticFeedback.heavyImpact(); + hasMigrated = const AsyncValue.data(true); + }); + } + } catch (e, s) { + Logger("ChangeExperiencePage").severe("Error during migration", e, s); + if (mounted) { + setState(() { + hasMigrated = AsyncValue.error(e, s); + }); + } + } + } + + Future _performMigrationLogic() async { + if (widget.switchingToBeta) { + final assetNotifier = ref.read(assetProvider.notifier); + if (assetNotifier.mounted) { + assetNotifier.dispose(); + } + final albumNotifier = ref.read(albumProvider.notifier); + if (albumNotifier.mounted) { + albumNotifier.dispose(); + } + + // Cancel uploads + await Store.put(StoreKey.backgroundBackup, false); + ref + .read(backupProvider.notifier) + .configureBackgroundBackup(enabled: false, onBatteryInfo: () {}, onError: (_) {}); + ref.read(backupProvider.notifier).setAutoBackup(false); + ref.read(backupProvider.notifier).cancelBackup(); + ref.read(manualUploadProvider.notifier).cancelBackup(); + // Start listening to new websocket events + ref.read(websocketProvider.notifier).stopListenToOldEvents(); + ref.read(websocketProvider.notifier).startListeningToBetaEvents(); + + await ref.read(driftProvider).reset(); + await Store.put(StoreKey.shouldResetSync, true); + final delay = Store.get(StoreKey.backupTriggerDelay, AppSettingsEnum.backupTriggerDelay.defaultValue); + if (delay >= 1000) { + await Store.put(StoreKey.backupTriggerDelay, (delay / 1000).toInt()); + } + final permission = await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); + + if (permission.isGranted) { + await ref.read(backgroundSyncProvider).syncLocal(full: true); + await migrateDeviceAssetToSqlite(ref.read(isarProvider), ref.read(driftProvider)); + await migrateBackupAlbumsToSqlite(ref.read(isarProvider), ref.read(driftProvider)); + await migrateStoreToSqlite(ref.read(isarProvider), ref.read(driftProvider)); + await ref.read(backgroundServiceProvider).disableService(); + } + } else { + await ref.read(backgroundSyncProvider).cancel(); + ref.read(websocketProvider.notifier).stopListeningToBetaEvents(); + ref.read(websocketProvider.notifier).startListeningToOldEvents(); + ref.read(readonlyModeProvider.notifier).setReadonlyMode(false); + await migrateStoreToIsar(ref.read(isarProvider), ref.read(driftProvider)); + await ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); + await ref.read(backgroundWorkerFgServiceProvider).disable(); + } + + await IsarStoreRepository(ref.read(isarProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta); + await DriftStoreRepository(ref.read(driftProvider)).upsert(StoreKey.betaTimeline, widget.switchingToBeta); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedSwitcher( + duration: Durations.long4, + child: hasMigrated.when( + data: (data) => const Icon(Icons.check_circle_rounded, color: Colors.green, size: 48.0), + error: (error, stackTrace) => const Icon(Icons.error, color: Colors.red, size: 48.0), + loading: () => const SizedBox(width: 50.0, height: 50.0, child: CircularProgressIndicator()), + ), + ), + const SizedBox(height: 16.0), + SizedBox( + width: 300.0, + child: AnimatedSwitcher( + duration: Durations.long4, + child: hasMigrated.when( + data: (data) => Text( + "Migration success!\nPlease close and reopen the app to apply changes", + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + error: (error, stackTrace) => Text( + "Migration failed!\nError: $error", + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + loading: () => Text( + "Data migration in progress...\nPlease wait and don't close this page", + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/common/create_album.page.dart b/mobile/lib/pages/common/create_album.page.dart new file mode 100644 index 0000000000..0a28dfeb5a --- /dev/null +++ b/mobile/lib/pages/common/create_album.page.dart @@ -0,0 +1,238 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +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() +// ignore: must_be_immutable +class CreateAlbumPage extends HookConsumerWidget { + final List? assets; + + const CreateAlbumPage({super.key, this.assets}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumTitleController = useTextEditingController.fromValue(TextEditingValue.empty); + final albumTitleTextFieldFocusNode = useFocusNode(); + final albumDescriptionTextFieldFocusNode = useFocusNode(); + final isAlbumTitleTextFieldFocus = useState(false); + final isAlbumTitleEmpty = useState(true); + final selectedAssets = useState>(assets != null ? Set.from(assets!) : const {}); + + void onBackgroundTapped() { + albumTitleTextFieldFocusNode.unfocus(); + albumDescriptionTextFieldFocusNode.unfocus(); + isAlbumTitleTextFieldFocus.value = false; + + if (albumTitleController.text.isEmpty) { + albumTitleController.text = 'create_album_page_untitled'.tr(); + isAlbumTitleEmpty.value = false; + ref.watch(albumTitleProvider.notifier).setAlbumTitle('create_album_page_untitled'.tr()); + } + } + + onSelectPhotosButtonPressed() async { + AssetSelectionPageResult? selectedAsset = await context.pushRoute( + AlbumAssetSelectionRoute(existingAssets: selectedAssets.value, canDeselect: true), + ); + if (selectedAsset == null) { + selectedAssets.value = const {}; + } else { + selectedAssets.value = selectedAsset.selectedAssets; + } + } + + buildTitleInputField() { + return Padding( + padding: const EdgeInsets.only(right: 10, left: 10), + child: AlbumTitleTextField( + isAlbumTitleEmpty: isAlbumTitleEmpty, + albumTitleTextFieldFocusNode: albumTitleTextFieldFocusNode, + albumTitleController: albumTitleController, + isAlbumTitleTextFieldFocus: isAlbumTitleTextFieldFocus, + ), + ); + } + + buildDescriptionInputField() { + return Padding( + padding: const EdgeInsets.only(right: 10, left: 10), + child: AlbumViewerEditableDescription( + albumDescription: '', + descriptionFocusNode: albumDescriptionTextFieldFocusNode, + ), + ); + } + + buildTitle() { + if (selectedAssets.value.isEmpty) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 200, left: 18), + child: Text('create_shared_album_page_share_add_assets', style: context.textTheme.labelLarge).tr(), + ), + ); + } + + return const SliverToBoxAdapter(); + } + + buildSelectPhotosButton() { + if (selectedAssets.value.isEmpty) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(top: 16, left: 16, right: 16), + child: FilledButton.icon( + style: FilledButton.styleFrom( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + backgroundColor: context.colorScheme.surfaceContainerHigh, + ), + onPressed: onSelectPhotosButtonPressed, + icon: Icon(Icons.add_rounded, color: context.primaryColor), + label: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: Text( + 'create_shared_album_page_share_select_photos', + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), + ).tr(), + ), + ), + ), + ); + } + + return const SliverToBoxAdapter(); + } + + buildControlButton() { + return Padding( + padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16), + child: SizedBox( + height: 42, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + AlbumActionFilledButton( + iconData: Icons.add_photo_alternate_outlined, + onPressed: onSelectPhotosButtonPressed, + labelText: "add_photos".tr(), + ), + ], + ), + ), + ); + } + + buildSelectedImageGrid() { + if (selectedAssets.value.isNotEmpty) { + return SliverPadding( + padding: const EdgeInsets.only(top: 16), + sliver: SliverGrid( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 5.0, + mainAxisSpacing: 5, + ), + delegate: SliverChildBuilderDelegate((BuildContext context, int index) { + return GestureDetector( + onTap: onBackgroundTapped, + child: SharedAlbumThumbnailImage(asset: selectedAssets.value.elementAt(index)), + ); + }, childCount: selectedAssets.value.length), + ), + ); + } + + return const SliverToBoxAdapter(); + } + + Future createAlbum() async { + onBackgroundTapped(); + var newAlbum = await ref + .watch(albumProvider.notifier) + .createAlbum(ref.read(albumTitleProvider), selectedAssets.value); + + if (newAlbum != null) { + await ref.read(albumProvider.notifier).refreshRemoteAlbums(); + selectedAssets.value = {}; + ref.read(albumTitleProvider.notifier).clearAlbumTitle(); + ref.read(albumViewerProvider.notifier).disableEditAlbum(); + unawaited(context.replaceRoute(AlbumViewerRoute(albumId: newAlbum.id))); + } + } + + return Scaffold( + appBar: AppBar( + elevation: 0, + centerTitle: false, + backgroundColor: context.scaffoldBackgroundColor, + leading: IconButton( + onPressed: () { + selectedAssets.value = {}; + context.maybePop(); + }, + icon: const Icon(Icons.close_rounded), + ), + title: const Text('create_album').tr(), + actions: [ + TextButton( + onPressed: albumTitleController.text.isNotEmpty ? createAlbum : null, + child: Text( + 'create'.tr(), + style: TextStyle( + fontWeight: FontWeight.bold, + color: albumTitleController.text.isNotEmpty ? context.primaryColor : context.themeData.disabledColor, + ), + ), + ), + ], + ), + body: GestureDetector( + onTap: onBackgroundTapped, + child: CustomScrollView( + slivers: [ + SliverAppBar( + backgroundColor: context.scaffoldBackgroundColor, + elevation: 5, + automaticallyImplyLeading: false, + pinned: true, + floating: false, + bottom: PreferredSize( + preferredSize: const Size.fromHeight(125.0), + child: Column( + children: [ + buildTitleInputField(), + buildDescriptionInputField(), + if (selectedAssets.value.isNotEmpty) buildControlButton(), + ], + ), + ), + ), + buildTitle(), + buildSelectPhotosButton(), + buildSelectedImageGrid(), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/common/download_panel.dart b/mobile/lib/pages/common/download_panel.dart index 2cf7d037a3..0775f5b4e4 100644 --- a/mobile/lib/pages/common/download_panel.dart +++ b/mobile/lib/pages/common/download_panel.dart @@ -1,10 +1,8 @@ -import 'dart:async'; - import 'package:background_downloader/background_downloader.dart'; +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/generated/translations.g.dart'; import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; class DownloadPanel extends ConsumerWidget { @@ -16,8 +14,8 @@ class DownloadPanel extends ConsumerWidget { final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList(); - void onCancelDownload(String id) { - unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id)); + onCancelDownload(String id) { + ref.watch(downloadStateProvider.notifier).cancelDownload(id); } return Positioned( @@ -64,18 +62,17 @@ class DownloadTaskTile extends StatelessWidget { @override Widget build(BuildContext context) { - final isComplete = status == TaskStatus.complete; final progressPercent = (progress * 100).round(); String getStatusText() => switch (status) { - TaskStatus.running => context.t.downloading, - TaskStatus.complete => context.t.download_complete, - TaskStatus.failed => context.t.download_failed, - TaskStatus.canceled => context.t.download_canceled, - TaskStatus.paused => context.t.download_paused, - TaskStatus.enqueued => context.t.download_enqueue, - TaskStatus.notFound => context.t.download_notfound, - TaskStatus.waitingToRetry => context.t.download_waiting_to_retry, + TaskStatus.running => 'downloading'.tr(), + TaskStatus.complete => 'download_complete'.tr(), + TaskStatus.failed => 'download_failed'.tr(), + TaskStatus.canceled => 'download_canceled'.tr(), + TaskStatus.paused => 'download_paused'.tr(), + TaskStatus.enqueued => 'download_enqueue'.tr(), + TaskStatus.notFound => 'download_notfound'.tr(), + TaskStatus.waitingToRetry => 'download_waiting_to_retry'.tr(), }; return SizedBox( @@ -89,14 +86,9 @@ class DownloadTaskTile extends StatelessWidget { leading: const Icon(Icons.video_file_outlined), title: Text(getStatusText(), style: context.textTheme.labelLarge), trailing: IconButton( - icon: Icon( - isComplete ? Icons.download_done : Icons.close, - color: isComplete ? context.colorScheme.primary : context.colorScheme.onError, - ), + icon: Icon(Icons.close, color: context.colorScheme.onError), onPressed: onCancelDownload, - style: isComplete - ? null - : ElevatedButton.styleFrom(backgroundColor: context.colorScheme.error.withAlpha(200)), + style: ElevatedButton.styleFrom(backgroundColor: context.colorScheme.error.withAlpha(200)), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/mobile/lib/pages/common/gallery_stacked_children.dart b/mobile/lib/pages/common/gallery_stacked_children.dart new file mode 100644 index 0000000000..68123509ae --- /dev/null +++ b/mobile/lib/pages/common/gallery_stacked_children.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; + +class GalleryStackedChildren extends HookConsumerWidget { + final ValueNotifier stackIndex; + + const GalleryStackedChildren(this.stackIndex, {super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetProvider); + if (asset == null) { + return const SizedBox(); + } + + final stackId = asset.stackId; + if (stackId == null) { + return const SizedBox(); + } + + final stackElements = ref.watch(assetStackStateProvider(stackId)); + final showControls = ref.watch(showControlsProvider); + + return IgnorePointer( + ignoring: !showControls, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: showControls ? 1.0 : 0.0, + child: SizedBox( + height: 80, + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: stackElements.length, + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 30), + itemBuilder: (context, index) { + final currentAsset = stackElements.elementAt(index); + final assetId = currentAsset.remoteId; + if (assetId == null) { + return const SizedBox(); + } + + return Padding( + key: ValueKey(currentAsset.id), + padding: const EdgeInsets.only(right: 5), + child: GestureDetector( + onTap: () { + stackIndex.value = index; + ref.read(currentAssetProvider.notifier).set(currentAsset); + }, + child: Container( + width: 60, + height: 60, + decoration: index == stackIndex.value + ? const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6)), + border: Border.fromBorderSide(BorderSide(color: Colors.white, width: 2)), + ) + : const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(6)), + border: null, + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(4)), + child: Image( + fit: BoxFit.cover, + image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: asset.thumbhash ?? ""), + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart new file mode 100644 index 0000000000..1d43bff167 --- /dev/null +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -0,0 +1,438 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; +import 'dart:ui' as ui; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/scroll_extensions.dart'; +import 'package:immich_mobile/pages/common/download_panel.dart'; +import 'package:immich_mobile/pages/common/gallery_stacked_children.dart'; +import 'package:immich_mobile/pages/common/native_video_viewer.page.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart'; +import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/detail_panel.dart'; +import 'package:immich_mobile/widgets/asset_viewer/gallery_app_bar.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; +import 'package:immich_mobile/widgets/photo_view/photo_view_gallery.dart'; +import 'package:immich_mobile/widgets/photo_view/src/photo_view_computed_scale.dart'; +import 'package:immich_mobile/widgets/photo_view/src/photo_view_scale_state.dart'; +import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_hero_attributes.dart'; + +@RoutePage() +// ignore: must_be_immutable +/// Expects [currentAssetProvider] to be set before navigating to this page +class GalleryViewerPage extends HookConsumerWidget { + final int initialIndex; + final int heroOffset; + final bool showStack; + final RenderList renderList; + + GalleryViewerPage({ + super.key, + required this.renderList, + this.initialIndex = 0, + this.heroOffset = 0, + this.showStack = false, + }) : controller = PageController(initialPage: initialIndex); + + final PageController controller; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final totalAssets = useState(renderList.totalAssets); + final isZoomed = useState(false); + final stackIndex = useState(0); + final localPosition = useRef(null); + final currentIndex = useValueNotifier(initialIndex); + final loadAsset = renderList.loadAsset; + final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + + final videoPlayerKeys = useRef>({}); + + GlobalKey getVideoPlayerKey(int id) { + videoPlayerKeys.value.putIfAbsent(id, () => GlobalKey()); + return videoPlayerKeys.value[id]!; + } + + Future precacheNextImage(int index) async { + if (!context.mounted) { + return; + } + + void onError(Object exception, StackTrace? stackTrace) { + // swallow error silently + log.severe('Error precaching next image: $exception, $stackTrace'); + } + + try { + if (index < totalAssets.value && index >= 0) { + final asset = loadAsset(index); + await precacheImage( + ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height), + context, + onError: onError, + ); + } + } catch (e) { + // swallow error silently + log.severe('Error precaching next image: $e'); + await context.maybePop(); + } + } + + useEffect(() { + if (ref.read(showControlsProvider)) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + } else { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + } + + // Delay this a bit so we can finish loading the page + Timer(const Duration(milliseconds: 400), () { + precacheNextImage(currentIndex.value + 1); + }); + + return null; + }, const []); + + useEffect(() { + final asset = loadAsset(currentIndex.value); + + if (asset.isRemote) { + ref.read(castProvider.notifier).loadMediaOld(asset, false); + } else { + if (isCasting) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + ref.read(castProvider.notifier).stop(); + context.scaffoldMessenger.showSnackBar( + SnackBar( + duration: const Duration(seconds: 1), + content: Text( + "local_asset_cast_failed".tr(), + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), + ), + ); + } + }); + } + } + return null; + }, [ref.watch(castProvider).isCasting]); + + void showInfo() { + final asset = ref.read(currentAssetProvider); + if (asset == null) { + return; + } + showModalBottomSheet( + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))), + barrierColor: Colors.transparent, + isScrollControlled: true, + showDragHandle: true, + enableDrag: true, + context: context, + useSafeArea: true, + builder: (context) { + return DraggableScrollableSheet( + minChildSize: 0.5, + maxChildSize: 1, + initialChildSize: 0.75, + expand: false, + builder: (context, scrollController) { + return Padding( + padding: EdgeInsets.only(bottom: context.viewInsets.bottom), + child: ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.advancedTroubleshooting) + ? AdvancedBottomSheet(assetDetail: asset, scrollController: scrollController) + : DetailPanel(asset: asset, scrollController: scrollController), + ); + }, + ); + }, + ); + } + + void handleSwipeUpDown(DragUpdateDetails details) { + const int sensitivity = 15; + const int dxThreshold = 50; + const double ratioThreshold = 3.0; + + if (isZoomed.value) { + return; + } + + // Guard [localPosition] null + if (localPosition.value == null) { + return; + } + + // Check for delta from initial down point + final d = details.localPosition - localPosition.value!; + // If the magnitude of the dx swipe is large, we probably didn't mean to go down + if (d.dx.abs() > dxThreshold) { + return; + } + + final ratio = d.dy / max(d.dx.abs(), 1); + if (d.dy > sensitivity && ratio > ratioThreshold) { + context.maybePop(); + } else if (d.dy < -sensitivity && ratio < -ratioThreshold) { + showInfo(); + } + } + + ref.listen(showControlsProvider, (_, show) { + if (show || Platform.isIOS) { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + return; + } + + // This prevents the bottom bar from "dropping" while the controls are being hidden + Timer(const Duration(milliseconds: 100), () { + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + }); + }); + + PhotoViewGalleryPageOptions buildImage(Asset asset) { + return PhotoViewGalleryPageOptions( + onDragStart: (_, details, __, ___) { + localPosition.value = details.localPosition; + }, + onDragUpdate: (_, details, __) { + handleSwipeUpDown(details); + }, + onTapDown: (ctx, tapDownDetails, _) { + final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.tapToNavigate); + if (!tapToNavigate) { + ref.read(showControlsProvider.notifier).toggle(); + return; + } + + double tapX = tapDownDetails.globalPosition.dx; + double screenWidth = ctx.width; + + // We want to change images if the user taps in the leftmost or + // rightmost quarter of the screen + bool tappedLeftSide = tapX < screenWidth / 4; + bool tappedRightSide = tapX > screenWidth * (3 / 4); + + int? currentPage = controller.page?.toInt(); + int maxPage = renderList.totalAssets - 1; + + if (tappedLeftSide && currentPage != null) { + // Nested if because we don't want to fallback to show/hide controls + if (currentPage != 0) { + controller.jumpToPage(currentPage - 1); + } + } else if (tappedRightSide && currentPage != null) { + // Nested if because we don't want to fallback to show/hide controls + if (currentPage != maxPage) { + controller.jumpToPage(currentPage + 1); + } + } else { + ref.read(showControlsProvider.notifier).toggle(); + } + }, + onLongPressStart: asset.isMotionPhoto + ? (_, __, ___) { + ref.read(isPlayingMotionVideoProvider.notifier).playing = true; + } + : null, + imageProvider: ImmichImage.imageProvider(asset: asset), + heroAttributes: _getHeroAttributes(asset), + filterQuality: FilterQuality.high, + tightMode: true, + initialScale: PhotoViewComputedScale.contained * 0.99, + minScale: PhotoViewComputedScale.contained * 0.99, + errorBuilder: (context, error, stackTrace) => ImmichImage(asset, fit: BoxFit.contain), + ); + } + + PhotoViewGalleryPageOptions buildVideo(BuildContext context, Asset asset) { + return PhotoViewGalleryPageOptions.customChild( + onDragStart: (_, details, __, ___) => localPosition.value = details.localPosition, + onDragUpdate: (_, details, __) => handleSwipeUpDown(details), + heroAttributes: _getHeroAttributes(asset), + filterQuality: FilterQuality.high, + initialScale: PhotoViewComputedScale.contained * 0.99, + maxScale: 1.0, + minScale: PhotoViewComputedScale.contained * 0.99, + basePosition: Alignment.center, + child: SizedBox( + width: context.width, + height: context.height, + child: NativeVideoViewerPage( + key: getVideoPlayerKey(asset.id), + asset: asset, + image: Image( + key: ValueKey(asset), + image: ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height), + fit: BoxFit.contain, + height: context.height, + width: context.width, + alignment: Alignment.center, + ), + ), + ), + ); + } + + PhotoViewGalleryPageOptions buildAsset(BuildContext context, int index) { + var newAsset = loadAsset(index); + + final stackId = newAsset.stackId; + if (stackId != null && currentIndex.value == index) { + final stackElements = ref.read(assetStackStateProvider(newAsset.stackId!)); + if (stackIndex.value < stackElements.length) { + newAsset = stackElements.elementAt(stackIndex.value); + } + } + + if (newAsset.isImage && !isPlayingMotionVideo) { + return buildImage(newAsset); + } + return buildVideo(context, newAsset); + } + + return PopScope( + // Change immersive mode back to normal "edgeToEdge" mode + onPopInvokedWithResult: (didPop, _) => SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge), + child: Scaffold( + backgroundColor: Colors.black, + body: Stack( + children: [ + PhotoViewGallery.builder( + key: const ValueKey('gallery'), + scaleStateChangedCallback: (state) { + final asset = ref.read(currentAssetProvider); + if (asset == null) { + return; + } + + if (asset.isImage && !ref.read(isPlayingMotionVideoProvider)) { + isZoomed.value = state != PhotoViewScaleState.initial; + ref.read(showControlsProvider.notifier).show = !isZoomed.value; + } + }, + gaplessPlayback: true, + loadingBuilder: (context, event, index) { + final asset = loadAsset(index); + return ClipRect( + child: Stack( + fit: StackFit.expand, + children: [ + BackdropFilter(filter: ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10)), + ImmichThumbnail(key: ValueKey(asset), asset: asset, fit: BoxFit.contain), + ], + ), + ); + }, + 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 + ), + itemCount: totalAssets.value, + scrollDirection: Axis.horizontal, + onPageChanged: (value, _) { + final next = currentIndex.value < value ? value + 1 : value - 1; + + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + + final newAsset = loadAsset(value); + + currentIndex.value = value; + stackIndex.value = 0; + + ref.read(currentAssetProvider.notifier).set(newAsset); + + // Wait for page change animation to finish, then precache the next image + Timer(const Duration(milliseconds: 400), () { + precacheNextImage(next); + }); + + context.scaffoldMessenger.hideCurrentSnackBar(); + + // send image to casting if the server has it + if (newAsset.isRemote) { + ref.read(castProvider.notifier).loadMediaOld(newAsset, false); + } else { + context.scaffoldMessenger.clearSnackBars(); + + if (isCasting) { + ref.read(castProvider.notifier).stop(); + context.scaffoldMessenger.showSnackBar( + SnackBar( + duration: const Duration(seconds: 2), + content: Text( + "local_asset_cast_failed".tr(), + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), + ), + ); + } + } + }, + builder: buildAsset, + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: GalleryAppBar(key: const ValueKey('app-bar'), showInfo: showInfo), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Column( + children: [ + GalleryStackedChildren(stackIndex), + BottomGalleryBar( + key: const ValueKey('bottom-bar'), + renderList: renderList, + totalAssets: totalAssets, + controller: controller, + showStack: showStack, + stackIndex: stackIndex, + assetIndex: currentIndex, + ), + ], + ), + ), + const DownloadPanel(), + ], + ), + ), + ); + } + + @pragma('vm:prefer-inline') + PhotoViewHeroAttributes _getHeroAttributes(Asset asset) { + return PhotoViewHeroAttributes( + tag: asset.isInDb ? asset.id + heroOffset : '${asset.remoteId}-$heroOffset', + transitionOnUserGestures: true, + ); + } +} diff --git a/mobile/lib/pages/common/headers_settings.page.dart b/mobile/lib/pages/common/headers_settings.page.dart index c934ca6a46..6eba49442f 100644 --- a/mobile/lib/pages/common/headers_settings.page.dart +++ b/mobile/lib/pages/common/headers_settings.page.dart @@ -1,10 +1,14 @@ +import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; class SettingsHeader { String key = ""; @@ -20,14 +24,17 @@ class HeaderSettingsPage extends HookConsumerWidget { final headers = useState>([]); final setInitialHeaders = useState(false); - final storedHeaders = ref.watch(appConfigProvider.select((s) => s.network.customHeaders)); + var headersStr = Store.get(StoreKey.customHeaders, ""); if (!setInitialHeaders.value) { - storedHeaders.forEach((k, v) { - final header = SettingsHeader(); - header.key = k; - header.value = v; - headers.value.add(header); - }); + if (headersStr.isNotEmpty) { + var customHeaders = jsonDecode(headersStr) as Map; + customHeaders.forEach((k, v) { + final header = SettingsHeader(); + header.key = k; + header.value = v; + headers.value.add(header); + }); + } // add first one to help the user if (headers.value.isEmpty) { @@ -40,7 +47,7 @@ class HeaderSettingsPage extends HookConsumerWidget { } setInitialHeaders.value = true; - final list = [ + var list = [ ...headers.value.map((headerValue) { return HeaderKeyValueSettings( header: headerValue, @@ -63,7 +70,7 @@ class HeaderSettingsPage extends HookConsumerWidget { headers.value = headers.value.toList(); }, icon: const Icon(Icons.add_outlined), - tooltip: context.t.header_settings_add_header_tip, + tooltip: 'header_settings_add_header_tip'.tr(), ), ], ), @@ -80,21 +87,19 @@ class HeaderSettingsPage extends HookConsumerWidget { ); } - Future saveHeaders(WidgetRef ref, List headers) async { - final headersMap = {}; - for (final header in headers) { + saveHeaders(WidgetRef ref, List headers) async { + final headersMap = {}; + for (var header in headers) { final key = header.key.trim(); final value = header.value.trim(); - if (key.isEmpty || value.isEmpty) { - continue; - } + if (key.isEmpty || value.isEmpty) continue; headersMap[key] = value; } - final apiService = ref.read(apiServiceProvider); - await ref.read(settingsProvider).write(.networkCustomHeaders, headersMap); - await apiService.updateHeaders(); + var encoded = jsonEncode(headersMap); + await Store.put(StoreKey.customHeaders, encoded); + await ref.read(apiServiceProvider).updateHeaders(); } } @@ -110,7 +115,7 @@ class HeaderKeyValueSettings extends StatelessWidget { String? emptyFieldValidator(String? value) { if (value == null || value.isEmpty) { - return StaticTranslations.instance.header_settings_field_validator_msg; + return 'header_settings_field_validator_msg'.tr(); } return null; @@ -128,12 +133,10 @@ class HeaderKeyValueSettings extends StatelessWidget { child: TextFormField( controller: keyController, decoration: InputDecoration( - labelText: context.t.header_settings_header_name_input, + labelText: 'header_settings_header_name_input'.tr(), border: const OutlineInputBorder(), ), autocorrect: false, - smartDashesType: .disabled, - smartQuotesType: .disabled, onChanged: (headerKey) { header.key = headerKey; }, @@ -158,12 +161,10 @@ class HeaderKeyValueSettings extends StatelessWidget { child: TextFormField( controller: valueController, decoration: InputDecoration( - labelText: context.t.header_settings_header_value_input, + labelText: 'header_settings_header_value_input'.tr(), border: const OutlineInputBorder(), ), autocorrect: false, - smartDashesType: .disabled, - smartQuotesType: .disabled, onChanged: (headerValue) { header.value = headerValue; }, diff --git a/mobile/lib/pages/common/large_leading_tile.dart b/mobile/lib/pages/common/large_leading_tile.dart index f8a8c51e17..4563834473 100644 --- a/mobile/lib/pages/common/large_leading_tile.dart +++ b/mobile/lib/pages/common/large_leading_tile.dart @@ -34,7 +34,7 @@ class LargeLeadingTile extends StatelessWidget { return InkWell( borderRadius: BorderRadius.circular(borderRadius), onTap: disabled ? null : onTap, - child: DecoratedBox( + child: Container( decoration: BoxDecoration( color: selected ? selectedTileColor ?? Theme.of(context).primaryColor.withAlpha(30) @@ -54,7 +54,7 @@ class LargeLeadingTile extends StatelessWidget { ], ), ), - ?trailing, + if (trailing != null) trailing!, ], ), ), diff --git a/mobile/lib/pages/common/native_video_viewer.page.dart b/mobile/lib/pages/common/native_video_viewer.page.dart new file mode 100644 index 0000000000..b1eed29c5c --- /dev/null +++ b/mobile/lib/pages/common/native_video_viewer.page.dart @@ -0,0 +1,282 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/widgets/asset_viewer/custom_video_player_controls.dart'; +import 'package:logging/logging.dart'; +import 'package:native_video_player/native_video_player.dart'; + +@RoutePage() +class NativeVideoViewerPage extends HookConsumerWidget { + static final log = Logger('NativeVideoViewer'); + final Asset asset; + final bool showControls; + final int playbackDelayFactor; + final Widget image; + + const NativeVideoViewerPage({ + super.key, + required this.asset, + required this.image, + this.showControls = true, + this.playbackDelayFactor = 1, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final videoId = asset.id.toString(); + final controller = useState(null); + final shouldPlayOnForeground = useRef(true); + + final currentAsset = useState(ref.read(currentAssetProvider)); + final isCurrent = currentAsset.value == asset; + + // Used to show the placeholder during hero animations for remote videos to avoid a stutter + final isVisible = useState(Platform.isIOS && asset.isLocal); + + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + + final isVideoReady = useState(false); + + Future createSource() async { + if (!context.mounted) { + return null; + } + + try { + final local = asset.local; + if (local != null && asset.livePhotoVideoId == null) { + final file = await local.file; + if (file == null) { + throw Exception('No file found for the video'); + } + + final source = await VideoSource.init(path: file.path, type: VideoSourceType.file); + return source; + } + + // Use a network URL for the video player controller + final serverEndpoint = Store.get(StoreKey.serverEndpoint); + final isOriginalVideo = ref + .read(appSettingsServiceProvider) + .getSetting(AppSettingsEnum.loadOriginalVideo); + final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback'; + final String videoUrl = asset.livePhotoVideoId != null + ? '$serverEndpoint/assets/${asset.livePhotoVideoId}/$postfixUrl' + : '$serverEndpoint/assets/${asset.remoteId}/$postfixUrl'; + + final source = await VideoSource.init( + path: videoUrl, + type: VideoSourceType.network, + headers: ApiService.getRequestHeaders(), + ); + return source; + } catch (error) { + log.severe('Error creating video source for asset ${asset.fileName}: $error'); + return null; + } + } + + final videoSource = useMemoized>(() => createSource()); + final aspectRatio = useState(asset.aspectRatio); + useMemoized(() async { + if (!context.mounted || aspectRatio.value != null) { + return null; + } + + try { + aspectRatio.value = await ref.read(assetServiceProvider).getAspectRatio(asset); + } catch (error) { + log.severe('Error getting aspect ratio for asset ${asset.fileName}: $error'); + } + }); + + void onPlaybackReady() async { + final videoController = controller.value; + if (videoController == null || !isCurrent || !context.mounted) { + return; + } + + final notifier = ref.read(videoPlayerProvider(videoId).notifier); + notifier.onNativePlaybackReady(); + + isVideoReady.value = true; + + try { + final autoPlayVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.autoPlayVideo); + if (autoPlayVideo) { + await notifier.play(); + } + await notifier.setVolume(1); + } catch (error) { + log.severe('Error playing video: $error'); + } + } + + void onPlaybackStatusChanged() { + if (!context.mounted) return; + ref.read(videoPlayerProvider(videoId).notifier).onNativeStatusChanged(); + } + + void onPlaybackPositionChanged() { + if (!context.mounted) return; + ref.read(videoPlayerProvider(videoId).notifier).onNativePositionChanged(); + } + + void onPlaybackEnded() { + if (!context.mounted) return; + + ref.read(videoPlayerProvider(videoId).notifier).onNativePlaybackEnded(); + + final videoController = controller.value; + if (videoController?.playbackInfo?.status == PlaybackStatus.stopped && + !ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo)) { + ref.read(isPlayingMotionVideoProvider.notifier).playing = false; + } + } + + void removeListeners(NativeVideoPlayerController controller) { + controller.onPlaybackPositionChanged.removeListener(onPlaybackPositionChanged); + controller.onPlaybackStatusChanged.removeListener(onPlaybackStatusChanged); + controller.onPlaybackReady.removeListener(onPlaybackReady); + controller.onPlaybackEnded.removeListener(onPlaybackEnded); + } + + void initController(NativeVideoPlayerController nc) async { + if (controller.value != null || !context.mounted) { + return; + } + + final source = await videoSource; + if (source == null) { + return; + } + + final notifier = ref.read(videoPlayerProvider(videoId).notifier); + notifier.attachController(nc); + + nc.onPlaybackPositionChanged.addListener(onPlaybackPositionChanged); + nc.onPlaybackStatusChanged.addListener(onPlaybackStatusChanged); + nc.onPlaybackReady.addListener(onPlaybackReady); + nc.onPlaybackEnded.addListener(onPlaybackEnded); + + unawaited( + nc.loadVideoSource(source).catchError((error) { + log.severe('Error loading video source: $error'); + }), + ); + final loopVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo); + await notifier.setLoop(loopVideo); + + controller.value = nc; + } + + ref.listen(currentAssetProvider, (_, value) { + final playerController = controller.value; + if (playerController != null && value != asset) { + removeListeners(playerController); + } + + final curAsset = currentAsset.value; + if (curAsset == asset) { + return; + } + + final imageToVideo = curAsset != null && !curAsset.isVideo; + + // No need to delay video playback when swiping from an image to a video + if (imageToVideo && Platform.isIOS) { + currentAsset.value = value; + onPlaybackReady(); + return; + } + + // Delay the video playback to avoid a stutter in the swipe animation + Timer( + Platform.isIOS + ? Duration(milliseconds: 300 * playbackDelayFactor) + : imageToVideo + ? Duration(milliseconds: 200 * playbackDelayFactor) + : Duration(milliseconds: 400 * playbackDelayFactor), + () { + if (!context.mounted) { + return; + } + + currentAsset.value = value; + if (currentAsset.value == asset) { + onPlaybackReady(); + } + }, + ); + }); + + useEffect(() { + // If opening a remote video from a hero animation, delay visibility to avoid a stutter + final timer = isVisible.value ? null : Timer(const Duration(milliseconds: 300), () => isVisible.value = true); + + return () { + timer?.cancel(); + final playerController = controller.value; + if (playerController == null) { + return; + } + removeListeners(playerController); + playerController.stop().catchError((error) { + log.fine('Error stopping video: $error'); + }); + }; + }, const []); + + useOnAppLifecycleStateChange((_, state) async { + final notifier = ref.read(videoPlayerProvider(videoId).notifier); + if (state == AppLifecycleState.resumed && shouldPlayOnForeground.value) { + await notifier.play(); + } else if (state == AppLifecycleState.paused) { + final videoPlaying = await controller.value?.isPlaying(); + if (videoPlaying ?? true) { + shouldPlayOnForeground.value = true; + await notifier.pause(); + } else { + shouldPlayOnForeground.value = false; + } + } + }); + + return Stack( + children: [ + // This remains under the video to avoid flickering + // For motion videos, this is the image portion of the asset + if (!isVideoReady.value || asset.isMotionPhoto) Center(key: ValueKey(asset.id), child: image), + if (aspectRatio.value != null && !isCasting) + Visibility.maintain( + key: ValueKey(asset), + visible: isVisible.value, + child: Center( + key: ValueKey(asset), + child: AspectRatio( + key: ValueKey(asset), + aspectRatio: aspectRatio.value!, + child: isCurrent ? NativeVideoPlayerView(key: ValueKey(asset), onViewReady: initController) : null, + ), + ), + ), + if (showControls) Center(child: CustomVideoPlayerControls(videoId: videoId)), + ], + ); + } +} diff --git a/mobile/lib/pages/common/settings.page.dart b/mobile/lib/pages/common/settings.page.dart index 2e941a70a3..e8f5eb2ee2 100644 --- a/mobile/lib/pages/common/settings.page.dart +++ b/mobile/lib/pages/common/settings.page.dart @@ -1,12 +1,15 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/settings/advanced_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart'; +import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart'; import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart'; import 'package:immich_mobile/widgets/settings/free_up_space_settings.dart'; @@ -17,49 +20,26 @@ import 'package:immich_mobile/widgets/settings/preference_settings/preference_se import 'package:immich_mobile/widgets/settings/settings_card.dart'; enum SettingSection { - advanced(Icons.build_outlined), - assetViewer(Icons.image_outlined), - backup(Icons.cloud_upload_outlined), - freeUpSpace(Icons.cleaning_services_outlined), - languages(Icons.language), - networking(Icons.wifi), - notifications(Icons.notifications_none_rounded), - preferences(Icons.interests_outlined), - timeline(Icons.auto_awesome_mosaic_outlined), - beta(Icons.sync_outlined); + advanced('advanced', Icons.build_outlined, "advanced_settings_tile_subtitle"), + assetViewer('asset_viewer_settings_title', Icons.image_outlined, "asset_viewer_settings_subtitle"), + backup('backup', Icons.cloud_upload_outlined, "backup_settings_subtitle"), + freeUpSpace('free_up_space', Icons.cleaning_services_outlined, "free_up_space_settings_subtitle"), + languages('language', Icons.language, "setting_languages_subtitle"), + networking('networking_settings', Icons.wifi, "networking_subtitle"), + notifications('notifications', Icons.notifications_none_rounded, "setting_notifications_subtitle"), + preferences('preferences_settings_title', Icons.interests_outlined, "preferences_settings_subtitle"), + timeline('asset_list_settings_title', Icons.auto_awesome_mosaic_outlined, "asset_list_settings_subtitle"), + beta('sync_status', Icons.sync_outlined, "sync_status_subtitle"); + final String title; + final String subtitle; final IconData icon; - String title(Translations t) => switch (this) { - SettingSection.advanced => t.advanced, - SettingSection.assetViewer => t.asset_viewer_settings_title, - SettingSection.backup => t.backup, - SettingSection.freeUpSpace => t.free_up_space, - SettingSection.languages => t.language, - SettingSection.networking => t.networking_settings, - SettingSection.notifications => t.notifications, - SettingSection.preferences => t.preferences_settings_title, - SettingSection.timeline => t.asset_list_settings_title, - SettingSection.beta => t.sync_status, - }; - - String subtitle(Translations t) => switch (this) { - SettingSection.advanced => t.advanced_settings_tile_subtitle, - SettingSection.assetViewer => t.asset_viewer_settings_subtitle, - SettingSection.backup => t.backup_settings_subtitle, - SettingSection.freeUpSpace => t.free_up_space_settings_subtitle, - SettingSection.languages => t.setting_languages_subtitle, - SettingSection.networking => t.networking_subtitle, - SettingSection.notifications => t.setting_notifications_subtitle, - SettingSection.preferences => t.preferences_settings_subtitle, - SettingSection.timeline => t.asset_list_settings_subtitle, - SettingSection.beta => t.sync_status_subtitle, - }; - Widget get widget => switch (this) { SettingSection.advanced => const AdvancedSettings(), SettingSection.assetViewer => const AssetViewerSettings(), - SettingSection.backup => const DriftBackupSettings(), + SettingSection.backup => + Store.tryGet(StoreKey.betaTimeline) ?? false ? const DriftBackupSettings() : const BackupSettings(), SettingSection.freeUpSpace => const FreeUpSpaceSettings(), SettingSection.languages => const LanguageSettings(), SettingSection.networking => const NetworkingSettings(), @@ -69,7 +49,7 @@ enum SettingSection { SettingSection.beta => const SyncStatusAndActions(), }; - const SettingSection(this.icon); + const SettingSection(this.title, this.icon, this.subtitle); } @RoutePage() @@ -78,8 +58,9 @@ class SettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { + context.locale; return Scaffold( - appBar: AppBar(centerTitle: false, title: Text(context.t.settings)), + appBar: AppBar(centerTitle: false, title: const Text('settings').tr()), body: context.isMobile ? const _MobileLayout() : const _TabletLayout(), ); } @@ -93,31 +74,24 @@ class _MobileLayout extends StatelessWidget { .expand( (setting) => setting == SettingSection.beta ? [ - SettingsCard( - icon: Icons.sync_outlined, - title: context.t.sync_status, - subtitle: context.t.sync_status_subtitle, - settingRoute: const SyncStatusRoute(), - ), + if (Store.isBetaTimelineEnabled) + SettingsCard( + icon: Icons.sync_outlined, + title: 'sync_status'.tr(), + subtitle: 'sync_status_subtitle'.tr(), + settingRoute: const SyncStatusRoute(), + ), ] : [ SettingsCard( - title: setting.title(context.t), - subtitle: setting.subtitle(context.t), + title: setting.title.tr(), + subtitle: setting.subtitle.tr(), icon: setting.icon, settingRoute: SettingsSubRoute(section: setting), ), ], ) .toList(); - settings.add( - SettingsCard( - icon: Icons.auto_awesome_outlined, - title: context.t.whats_new, - subtitle: context.t.whats_new_settings_subtitle, - settingRoute: const WhatsNewRoute(), - ), - ); return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 60), children: [...settings]); } } @@ -138,7 +112,7 @@ class _TabletLayout extends HookWidget { ...SettingSection.values.map( (s) => SliverToBoxAdapter( child: ListTile( - title: Text(s.title(context.t)), + title: Text(s.title).tr(), leading: Icon(s.icon), selected: s.index == selectedSection.value.index, selectedColor: context.primaryColor, @@ -147,13 +121,6 @@ class _TabletLayout extends HookWidget { ), ), ), - SliverToBoxAdapter( - child: ListTile( - title: Text(context.t.whats_new), - leading: const Icon(Icons.auto_awesome_outlined), - onTap: () => context.pushRoute(const WhatsNewRoute()), - ), - ), ], ), ), @@ -172,8 +139,9 @@ class SettingsSubPage extends StatelessWidget { @override Widget build(BuildContext context) { + context.locale; return Scaffold( - appBar: AppBar(centerTitle: false, title: Text(section.title(context.t))), + appBar: AppBar(centerTitle: false, title: Text(section.title).tr()), body: section.widget, ); } diff --git a/mobile/lib/pages/common/splash_screen.page.dart b/mobile/lib/pages/common/splash_screen.page.dart index 65a78aed8a..37c6b95806 100644 --- a/mobile/lib/pages/common/splash_screen.page.dart +++ b/mobile/lib/pages/common/splash_screen.page.dart @@ -1,23 +1,25 @@ import 'dart:async'; +import 'dart:io'; import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/generated/codegen_loader.g.dart'; import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; @@ -25,7 +27,7 @@ import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; import 'package:immich_mobile/widgets/common/immich_title_text.dart'; import 'package:logging/logging.dart'; -import 'package:url_launcher/url_launcher.dart' show LaunchMode, launchUrl; +import 'package:url_launcher/url_launcher.dart' show launchUrl, LaunchMode; class BootstrapErrorWidget extends StatelessWidget { final String error; @@ -35,7 +37,7 @@ class BootstrapErrorWidget extends StatelessWidget { @override Widget build(BuildContext _) { - final immichTheme = defaultConfig.theme.primaryColor.themeOfPreset; + final immichTheme = defaultColorPreset.themeOfPreset; return EasyLocalization( supportedLocales: locales.values.toList(), @@ -130,16 +132,20 @@ class _BottomPanelState extends State<_BottomPanel> { } try { - await deleteSqliteDatabase(name: 'immich'); + final dir = await getApplicationDocumentsDirectory(); + for (final suffix in ['', '-wal', '-shm']) { + final file = File(path.join(dir.path, 'immich.sqlite$suffix')); + if (await file.exists()) { + await file.delete(); + } + } } catch (_) { return; } - if (!mounted) { - return; + if (mounted) { + setState(() => _cleared = true); } - - setState(() => _cleared = true); } @override @@ -284,13 +290,11 @@ class SplashScreenPageState extends ConsumerState { @override void initState() { super.initState(); - unawaited( - ref - .read(authProvider.notifier) - .setOpenApiServiceEndpoint() - .then(logConnectionInfo) - .whenComplete(() => resumeSession()), - ); + ref + .read(authProvider.notifier) + .setOpenApiServiceEndpoint() + .then(logConnectionInfo) + .whenComplete(() => resumeSession()); } void logConnectionInfo(String? endpoint) { @@ -301,7 +305,7 @@ class SplashScreenPageState extends ConsumerState { log.info("Resuming session at $endpoint"); } - Future resumeSession() async { + void resumeSession() async { final serverUrl = Store.tryGet(StoreKey.serverUrl); final endpoint = Store.tryGet(StoreKey.serverEndpoint); final accessToken = Store.tryGet(StoreKey.accessToken); @@ -311,73 +315,116 @@ class SplashScreenPageState extends ConsumerState { final wsProvider = ref.read(websocketProvider.notifier); final backgroundManager = ref.read(backgroundSyncProvider); final backupProvider = ref.read(driftBackupProvider.notifier); - final viewIntentHandler = ref.read(viewIntentHandlerProvider); unawaited( - ref - .read(authProvider.notifier) - .saveAuthInfo(accessToken: accessToken) - .then( - (_) async { - try { - wsProvider.connect(); - unawaited(infoProvider.getServerInfo()); + ref.read(authProvider.notifier).saveAuthInfo(accessToken: accessToken).then( + (_) async { + try { + wsProvider.connect(); + unawaited(infoProvider.getServerInfo()); - bool syncSuccess = false; + if (Store.isBetaTimelineEnabled) { + bool syncSuccess = false; + await Future.wait([ + backgroundManager.syncLocal(full: true), + backgroundManager.syncRemote().then((success) => syncSuccess = success), + ]); + + if (syncSuccess) { await Future.wait([ - backgroundManager.syncLocal(full: true), - backgroundManager.syncRemote().then((success) => syncSuccess = success), + backgroundManager.hashAssets().then((_) { + _resumeBackup(backupProvider); + }), + _resumeBackup(backupProvider), + // TODO: Bring back when the soft freeze issue is addressed + // backgroundManager.syncCloudIds(), ]); - - await viewIntentHandler.flushDeferredViewIntent(); - - if (syncSuccess) { - await Future.wait([ - backgroundManager.hashAssets().then((_) { - unawaited(_resumeBackup(backupProvider)); - }), - _resumeBackup(backupProvider), - // TODO: Bring back when the soft freeze issue is addressed - // backgroundManager.syncCloudIds(), - ]); - } else { - await backgroundManager.hashAssets(); - } - - if (SettingsRepository.instance.appConfig.backup.syncAlbums) { - await backgroundManager.syncLinkedAlbum(); - } - } catch (e) { - log.severe('Failed establishing connection to the server: $e'); - } - }, - onError: (exception) { - log.severe('Failed to update auth info with access token: $accessToken'); - if (!mounted) { - return; + } else { + await backgroundManager.hashAssets(); } - unawaited(ref.read(authProvider.notifier).logout()); - unawaited(context.router.replaceAll([const LoginRoute()])); - }, - ), + if (Store.get(StoreKey.syncAlbums, false)) { + await backgroundManager.syncLinkedAlbum(); + } + } + } catch (e) { + log.severe('Failed establishing connection to the server: $e'); + } + }, + onError: (exception) => { + log.severe('Failed to update auth info with access token: $accessToken'), + ref.read(authProvider.notifier).logout(), + context.replaceRoute(const LoginRoute()), + }, + ), ); } else { log.severe('Missing crucial offline login info - Logging out completely'); unawaited(ref.read(authProvider.notifier).logout()); - unawaited(context.router.replaceAll([const LoginRoute()])); + unawaited(context.replaceRoute(const LoginRoute())); return; } // clean install - change the default of the flag // current install not using beta timeline if (context.router.current.name == SplashScreenRoute.name) { - unawaited(context.replaceRoute(const TabShellRoute())); + final needBetaMigration = Store.get(StoreKey.needBetaMigration, false); + if (needBetaMigration) { + bool migrate = + (await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text("New Timeline Experience"), + content: const Text( + "The old timeline has been deprecated and will be removed in an upcoming release. Would you like to switch to the new timeline now?", + ), + actions: [ + TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")), + ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")), + ], + ), + )) ?? + false; + if (migrate != true) { + migrate = + (await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text("Are you sure?"), + content: const Text( + "If you choose to remain on the old timeline, you will be automatically migrated to the new timeline in an upcoming release. Would you like to switch now?", + ), + actions: [ + TextButton(onPressed: () => Navigator.of(ctx).pop(false), child: const Text("No")), + ElevatedButton(onPressed: () => Navigator.of(ctx).pop(true), child: const Text("Yes")), + ], + ), + )) ?? + false; + } + await Store.put(StoreKey.needBetaMigration, false); + if (migrate) { + unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)])); + return; + } + } + + unawaited(context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute())); + } + + if (Store.isBetaTimelineEnabled) { + return; + } + + final hasPermission = await ref.read(galleryPermissionNotifier.notifier).hasPermission; + if (hasPermission) { + // Resume backup (if enable) then navigate + await ref.watch(backupProvider.notifier).resumeBackup(); } } Future _resumeBackup(DriftBackupNotifier notifier) async { - final isEnableBackup = SettingsRepository.instance.appConfig.backup.enabled; + final isEnableBackup = Store.get(StoreKey.enableBackup, false); if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); diff --git a/mobile/lib/pages/common/tab_controller.page.dart b/mobile/lib/pages/common/tab_controller.page.dart new file mode 100644 index 0000000000..ef637ba1c8 --- /dev/null +++ b/mobile/lib/pages/common/tab_controller.page.dart @@ -0,0 +1,142 @@ +import 'package:auto_route/auto_route.dart'; +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/album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +@RoutePage() +class TabControllerPage extends HookConsumerWidget { + const TabControllerPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isRefreshingAssets = ref.watch(assetProvider); + final isRefreshingRemoteAlbums = ref.watch(isRefreshingRemoteAlbumProvider); + final isScreenLandscape = MediaQuery.orientationOf(context) == Orientation.landscape; + + Widget buildIcon({required Widget icon, required bool isProcessing}) { + if (!isProcessing) return icon; + return Stack( + alignment: Alignment.center, + clipBehavior: Clip.none, + children: [ + icon, + Positioned( + right: -18, + child: SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(context.primaryColor), + ), + ), + ), + ], + ); + } + + void onNavigationSelected(TabsRouter router, int index) { + // On Photos page menu tapped + if (router.activeIndex == 0 && index == 0) { + scrollToTopNotifierProvider.scrollToTop(); + } + + // On Search page tapped + if (router.activeIndex == 1 && index == 1) { + ref.read(searchInputFocusProvider).requestFocus(); + } + + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + router.setActiveIndex(index); + ref.read(tabProvider.notifier).state = TabEnum.values[index]; + } + + final navigationDestinations = [ + NavigationDestination( + label: 'photos'.tr(), + icon: const Icon(Icons.photo_library_outlined), + selectedIcon: buildIcon( + isProcessing: isRefreshingAssets, + icon: Icon(Icons.photo_library, color: context.primaryColor), + ), + ), + NavigationDestination( + label: 'search'.tr(), + icon: const Icon(Icons.search_rounded), + selectedIcon: Icon(Icons.search, color: context.primaryColor), + ), + NavigationDestination( + label: 'albums'.tr(), + icon: const Icon(Icons.photo_album_outlined), + selectedIcon: buildIcon( + isProcessing: isRefreshingRemoteAlbums, + icon: Icon(Icons.photo_album_rounded, color: context.primaryColor), + ), + ), + NavigationDestination( + label: 'library'.tr(), + icon: const Icon(Icons.space_dashboard_outlined), + selectedIcon: buildIcon( + isProcessing: isRefreshingAssets, + icon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor), + ), + ), + ]; + + Widget bottomNavigationBar(TabsRouter tabsRouter) { + return NavigationBar( + selectedIndex: tabsRouter.activeIndex, + onDestinationSelected: (index) => onNavigationSelected(tabsRouter, index), + destinations: navigationDestinations, + ); + } + + Widget navigationRail(TabsRouter tabsRouter) { + return NavigationRail( + destinations: navigationDestinations + .map((e) => NavigationRailDestination(icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon)) + .toList(), + onDestinationSelected: (index) => onNavigationSelected(tabsRouter, index), + selectedIndex: tabsRouter.activeIndex, + labelType: NavigationRailLabelType.all, + groupAlignment: 0.0, + ); + } + + final multiselectEnabled = ref.watch(multiselectProvider); + return AutoTabsRouter( + routes: [const PhotosRoute(), SearchRoute(), const AlbumsRoute(), const LibraryRoute()], + duration: const Duration(milliseconds: 600), + transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child), + builder: (context, child) { + final tabsRouter = AutoTabsRouter.of(context); + return PopScope( + canPop: tabsRouter.activeIndex == 0, + onPopInvokedWithResult: (didPop, _) => !didPop ? tabsRouter.setActiveIndex(0) : null, + child: Scaffold( + resizeToAvoidBottomInset: false, + body: isScreenLandscape + ? Row( + children: [ + navigationRail(tabsRouter), + const VerticalDivider(), + Expanded(child: child), + ], + ) + : child, + bottomNavigationBar: multiselectEnabled || isScreenLandscape ? null : bottomNavigationBar(tabsRouter), + ), + ); + }, + ); + } +} diff --git a/mobile/lib/pages/common/tab_shell.page.dart b/mobile/lib/pages/common/tab_shell.page.dart index 240c4abe7a..2fdcec4054 100644 --- a/mobile/lib/pages/common/tab_shell.page.dart +++ b/mobile/lib/pages/common/tab_shell.page.dart @@ -1,17 +1,18 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart'; @@ -33,24 +34,24 @@ class _TabShellPageState extends ConsumerState { final navigationDestinations = [ NavigationDestination( - label: context.t.photos, + label: 'photos'.tr(), icon: const Icon(Icons.photo_library_outlined), selectedIcon: Icon(Icons.photo_library, color: context.primaryColor), ), NavigationDestination( - label: context.t.search, + label: 'search'.tr(), icon: const Icon(Icons.search_rounded), selectedIcon: Icon(Icons.search, color: context.primaryColor), enabled: !isReadonlyModeEnabled, ), NavigationDestination( - label: context.t.albums, + label: 'albums'.tr(), icon: const Icon(Icons.photo_album_outlined), selectedIcon: Icon(Icons.photo_album_rounded, color: context.primaryColor), enabled: !isReadonlyModeEnabled, ), NavigationDestination( - label: context.t.library$, + label: 'library'.tr(), icon: const Icon(Icons.space_dashboard_outlined), selectedIcon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor), enabled: !isReadonlyModeEnabled, @@ -125,12 +126,13 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) { // Album page if (index == kAlbumTabIndex) { - unawaited(ref.read(remoteAlbumProvider.notifier).refresh()); + ref.read(remoteAlbumProvider.notifier).refresh(); } // Library page if (index == kLibraryTabIndex) { ref.invalidate(localAlbumProvider); + ref.invalidate(driftGetAllPeopleProvider); } ref.read(hapticFeedbackProvider.notifier).selectionClick(); @@ -166,7 +168,7 @@ class _BottomNavigationBarState extends ConsumerState<_BottomNavigationBar> { @override void dispose() { - unawaited(_eventSubscription?.cancel()); + _eventSubscription?.cancel(); super.dispose(); } diff --git a/mobile/lib/pages/editing/crop.page.dart b/mobile/lib/pages/editing/crop.page.dart new file mode 100644 index 0000000000..a6a66c1358 --- /dev/null +++ b/mobile/lib/pages/editing/crop.page.dart @@ -0,0 +1,177 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:crop_image/crop_image.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/pages/editing/edit.page.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; + +/// A widget for cropping an image. +/// This widget uses [HookWidget] to manage its lifecycle and state. It allows +/// users to crop an image and then navigate to the [EditImagePage] with the +/// cropped image. + +@RoutePage() +class CropImagePage extends HookWidget { + final Image image; + final Asset asset; + const CropImagePage({super.key, required this.image, required this.asset}); + + @override + Widget build(BuildContext context) { + final cropController = useCropController(); + final aspectRatio = useState(null); + + return Scaffold( + appBar: AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("crop".tr()), + leading: CloseButton(color: context.primaryColor), + actions: [ + IconButton( + icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), + onPressed: () async { + final croppedImage = await cropController.croppedImage(); + unawaited(context.pushRoute(EditImageRoute(asset: asset, image: croppedImage, isEdited: true))); + }, + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: SafeArea( + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20), + width: constraints.maxWidth * 0.9, + height: constraints.maxHeight * 0.6, + child: CropImage(controller: cropController, image: image, gridColor: Colors.white), + ), + Expanded( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: context.scaffoldBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.rotate_left, color: context.themeData.iconTheme.color), + onPressed: () { + cropController.rotateLeft(); + }, + ), + IconButton( + icon: Icon(Icons.rotate_right, color: context.themeData.iconTheme.color), + onPressed: () { + cropController.rotateRight(); + }, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: null, + label: 'Free', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 1.0, + label: '1:1', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 16.0 / 9.0, + label: '16:9', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 3.0 / 2.0, + label: '3:2', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 7.0 / 5.0, + label: '7:5', + ), + ], + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class _AspectRatioButton extends StatelessWidget { + final CropController cropController; + final ValueNotifier aspectRatio; + final double? ratio; + final String label; + + const _AspectRatioButton({ + required this.cropController, + required this.aspectRatio, + required this.ratio, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(switch (label) { + 'Free' => Icons.crop_free_rounded, + '1:1' => Icons.crop_square_rounded, + '16:9' => Icons.crop_16_9_rounded, + '3:2' => Icons.crop_3_2_rounded, + '7:5' => Icons.crop_7_5_rounded, + _ => Icons.crop_free_rounded, + }, color: aspectRatio.value == ratio ? context.primaryColor : context.themeData.iconTheme.color), + onPressed: () { + cropController.crop = const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9); + aspectRatio.value = ratio; + cropController.aspectRatio = ratio; + }, + ), + Text(label, style: context.textTheme.displayMedium), + ], + ); + } +} diff --git a/mobile/lib/pages/editing/edit.page.dart b/mobile/lib/pages/editing/edit.page.dart new file mode 100644 index 0000000000..2889785d0b --- /dev/null +++ b/mobile/lib/pages/editing/edit.page.dart @@ -0,0 +1,131 @@ +import 'dart:typed_data'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/image_converter.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:path/path.dart' as p; + +/// A stateless widget that provides functionality for editing an image. +/// +/// This widget allows users to edit an image provided either as an [Asset] or +/// directly as an [Image]. It ensures that exactly one of these is provided. +/// +/// It also includes a conversion method to convert an [Image] to a [Uint8List] to save the image on the user's phone +/// They automatically navigate to the [HomePage] with the edited image saved and they eventually get backed up to the server. +@immutable +@RoutePage() +class EditImagePage extends ConsumerWidget { + final Asset asset; + final Image image; + final bool isEdited; + + const EditImagePage({super.key, required this.asset, required this.image, required this.isEdited}); + + Future _saveEditedImage(BuildContext context, Asset asset, Image image, WidgetRef ref) async { + try { + final Uint8List imageData = await imageToUint8List(image); + await ref + .read(fileMediaRepositoryProvider) + .saveImage(imageData, title: "${p.withoutExtension(asset.fileName)}_edited.jpg"); + await ref.read(albumProvider.notifier).refreshDeviceAlbums(); + context.navigator.popUntil((route) => route.isFirst); + ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!', gravity: ToastGravity.CENTER); + } catch (e) { + ImmichToast.show( + durationInSecond: 6, + context: context, + msg: "error_saving_image".tr(namedArgs: {'error': e.toString()}), + gravity: ToastGravity.CENTER, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text("edit".tr()), + backgroundColor: context.scaffoldBackgroundColor, + leading: IconButton( + icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24), + onPressed: () => context.navigator.popUntil((route) => route.isFirst), + ), + actions: [ + TextButton( + onPressed: isEdited ? () => _saveEditedImage(context, asset, image, ref) : null, + child: Text("save_to_gallery".tr(), style: TextStyle(color: isEdited ? context.primaryColor : Colors.grey)), + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: context.height * 0.7, maxWidth: context.width * 0.9), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.2), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(7)), + child: Image(image: image.image, fit: BoxFit.contain), + ), + ), + ), + ), + bottomNavigationBar: Container( + height: 70, + margin: const EdgeInsets.only(bottom: 60, right: 10, left: 10, top: 10), + decoration: BoxDecoration( + color: context.scaffoldBackgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(30)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon(Icons.crop_rotate_rounded, color: context.themeData.iconTheme.color, size: 25), + onPressed: () { + context.pushRoute(CropImageRoute(asset: asset, image: image)); + }, + ), + Text("crop".tr(), style: context.textTheme.displayMedium), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon(Icons.filter, color: context.themeData.iconTheme.color, size: 25), + onPressed: () { + context.pushRoute(FilterImageRoute(asset: asset, image: image)); + }, + ), + Text("filter".tr(), style: context.textTheme.displayMedium), + ], + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/editing/filter.page.dart b/mobile/lib/pages/editing/filter.page.dart new file mode 100644 index 0000000000..f8b144bb96 --- /dev/null +++ b/mobile/lib/pages/editing/filter.page.dart @@ -0,0 +1,159 @@ +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/constants/filters.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/routing/router.dart'; + +/// A widget for filtering an image. +/// This widget uses [HookWidget] to manage its lifecycle and state. It allows +/// users to add filters to an image and then navigate to the [EditImagePage] with the +/// final composition.' +@RoutePage() +class FilterImagePage extends HookWidget { + final Image image; + final Asset asset; + + const FilterImagePage({super.key, required this.image, required this.asset}); + + @override + Widget build(BuildContext context) { + final colorFilter = useState(filters[0]); + final selectedFilterIndex = useState(0); + + Future createFilteredImage(ui.Image inputImage, ColorFilter filter) { + final completer = Completer(); + final size = Size(inputImage.width.toDouble(), inputImage.height.toDouble()); + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + + final paint = Paint()..colorFilter = filter; + canvas.drawImage(inputImage, Offset.zero, paint); + + recorder.endRecording().toImage(size.width.round(), size.height.round()).then((image) { + completer.complete(image); + }); + + return completer.future; + } + + void applyFilter(ColorFilter filter, int index) { + colorFilter.value = filter; + selectedFilterIndex.value = index; + } + + Future applyFilterAndConvert(ColorFilter filter) async { + final completer = Completer(); + image.image + .resolve(ImageConfiguration.empty) + .addListener( + ImageStreamListener((ImageInfo info, bool _) { + completer.complete(info.image); + }), + ); + final uiImage = await completer.future; + + final filteredUiImage = await createFilteredImage(uiImage, filter); + final byteData = await filteredUiImage.toByteData(format: ui.ImageByteFormat.png); + final pngBytes = byteData!.buffer.asUint8List(); + + return Image.memory(pngBytes, fit: BoxFit.contain); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("filter".tr()), + leading: CloseButton(color: context.primaryColor), + actions: [ + IconButton( + icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), + onPressed: () async { + final filteredImage = await applyFilterAndConvert(colorFilter.value); + unawaited(context.pushRoute(EditImageRoute(asset: asset, image: filteredImage, isEdited: true))); + }, + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: Column( + children: [ + SizedBox( + height: context.height * 0.7, + child: Center( + child: ColorFiltered(colorFilter: colorFilter.value, child: image), + ), + ), + SizedBox( + height: 120, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: filters.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: _FilterButton( + image: image, + label: filterNames[index], + filter: filters[index], + isSelected: selectedFilterIndex.value == index, + onTap: () => applyFilter(filters[index], index), + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +class _FilterButton extends StatelessWidget { + final Image image; + final String label; + final ColorFilter filter; + final bool isSelected; + final VoidCallback onTap; + + const _FilterButton({ + required this.image, + required this.label, + required this.filter, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + GestureDetector( + onTap: onTap, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: isSelected ? Border.all(color: context.primaryColor, width: 3) : null, + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: ColorFiltered( + colorFilter: filter, + child: FittedBox(fit: BoxFit.cover, child: image), + ), + ), + ), + ), + const SizedBox(height: 10), + Text(label, style: context.themeData.textTheme.bodyMedium), + ], + ); + } +} diff --git a/mobile/lib/pages/library/archive.page.dart b/mobile/lib/pages/library/archive.page.dart new file mode 100644 index 0000000000..8ca1bb9752 --- /dev/null +++ b/mobile/lib/pages/library/archive.page.dart @@ -0,0 +1,37 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; + +@RoutePage() +class ArchivePage extends HookConsumerWidget { + const ArchivePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AppBar buildAppBar() { + final archiveRenderList = ref.watch(archiveTimelineProvider); + final count = archiveRenderList.value?.totalAssets.toString() ?? "?"; + return AppBar( + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + centerTitle: true, + automaticallyImplyLeading: false, + title: const Text('archive_page_title').tr(namedArgs: {'count': count}), + ); + } + + return Scaffold( + appBar: ref.watch(multiselectProvider) ? null : buildAppBar(), + body: MultiselectGrid( + renderListProvider: archiveTimelineProvider, + unarchive: true, + archiveEnabled: true, + deleteEnabled: true, + editEnabled: true, + ), + ); + } +} diff --git a/mobile/lib/pages/library/favorite.page.dart b/mobile/lib/pages/library/favorite.page.dart new file mode 100644 index 0000000000..649d7727d5 --- /dev/null +++ b/mobile/lib/pages/library/favorite.page.dart @@ -0,0 +1,34 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; + +@RoutePage() +class FavoritesPage extends HookConsumerWidget { + const FavoritesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AppBar buildAppBar() { + return AppBar( + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + centerTitle: true, + automaticallyImplyLeading: false, + title: const Text('favorites').tr(), + ); + } + + return Scaffold( + appBar: ref.watch(multiselectProvider) ? null : buildAppBar(), + body: MultiselectGrid( + renderListProvider: favoriteTimelineProvider, + favoriteEnabled: true, + editEnabled: true, + unfavorite: true, + ), + ); + } +} diff --git a/mobile/lib/pages/library/folder/folder.page.dart b/mobile/lib/pages/library/folder/folder.page.dart index 0264ceee16..497d3e5151 100644 --- a/mobile/lib/pages/library/folder/folder.page.dart +++ b/mobile/lib/pages/library/folder/folder.page.dart @@ -1,26 +1,20 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; -import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.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/presentation/widgets/asset_viewer/asset_viewer.page.dart'; -import 'package:immich_mobile/presentation/widgets/images/thumbnail_tile.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/folder.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:intl/intl.dart'; RecursiveFolder? _findFolderInStructure(RootFolder rootFolder, RecursiveFolder targetFolder) { for (final folder in rootFolder.subfolders) { @@ -34,9 +28,7 @@ RecursiveFolder? _findFolderInStructure(RootFolder rootFolder, RecursiveFolder t if (folder.subfolders.isNotEmpty) { final found = _findFolderInStructure(folder, targetFolder); - if (found != null) { - return found; - } + if (found != null) return found; } } return null; @@ -56,7 +48,7 @@ class FolderPage extends HookConsumerWidget { useEffect(() { if (folder == null) { - unawaited(ref.read(folderStructureProvider.notifier).fetchFolders(sortOrder.value)); + ref.read(folderStructureProvider.notifier).fetchFolders(sortOrder.value); } return null; }, []); @@ -75,14 +67,14 @@ class FolderPage extends HookConsumerWidget { void onToggleSortOrder() { final newOrder = sortOrder.value == SortOrder.asc ? SortOrder.desc : SortOrder.asc; - unawaited(ref.read(folderStructureProvider.notifier).fetchFolders(newOrder)); + ref.read(folderStructureProvider.notifier).fetchFolders(newOrder); sortOrder.value = newOrder; } return Scaffold( appBar: AppBar( - title: Text(currentFolder.value?.name ?? context.t.folders), + title: Text(currentFolder.value?.name ?? tr("folders")), elevation: 0, centerTitle: false, actions: [IconButton(icon: const Icon(Icons.swap_vert), onPressed: onToggleSortOrder)], @@ -92,13 +84,13 @@ class FolderPage extends HookConsumerWidget { if (folder == null) { return FolderContent(folder: rootFolder, root: rootFolder, sortOrder: sortOrder.value); } else { - return FolderContent(folder: currentFolder.value, root: rootFolder, sortOrder: sortOrder.value); + return FolderContent(folder: currentFolder.value!, root: rootFolder, sortOrder: sortOrder.value); } }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) { - ImmichToast.show(context: context, msg: context.t.failed_to_load_folder, toastType: ToastType.error); - return Center(child: Text(context.t.failed_to_load_folder)); + ImmichToast.show(context: context, msg: "failed_to_load_folder".tr(), toastType: ToastType.error); + return Center(child: const Text("failed_to_load_folder").tr()); }, ), ); @@ -118,24 +110,22 @@ class FolderContent extends HookConsumerWidget { // Initial asset fetch useEffect(() { - if (folder == null) { - return; - } - unawaited(ref.read(folderRenderListProvider(folder!).notifier).fetchAssets(sortOrder)); + if (folder == null) return; + ref.read(folderRenderListProvider(folder!).notifier).fetchAssets(sortOrder); return null; }, [folder]); if (folder == null) { - return Center(child: Text(context.t.folder_not_found)); + return Center(child: const Text("folder_not_found").tr()); } - String getSubtitle(int subFolderCount) { + getSubtitle(int subFolderCount) { if (subFolderCount > 0) { - return "$subFolderCount ${context.t.folders}".toLowerCase(); + return "$subFolderCount ${tr("folders")}".toLowerCase(); } if (subFolderCount == 1) { - return "1 ${context.t.folder}".toLowerCase(); + return "1 ${tr("folder")}".toLowerCase(); } return ""; @@ -146,9 +136,9 @@ class FolderContent extends HookConsumerWidget { FolderPath(currentFolder: folder!, root: root), Expanded( child: folderRenderlist.when( - data: (folderAssets) { - if (folder!.subfolders.isEmpty && folderAssets.isEmpty) { - return Center(child: Text(context.t.empty_folder)); + data: (list) { + if (folder!.subfolders.isEmpty && list.isEmpty) { + return Center(child: const Text("empty_folder").tr()); } return ListView( @@ -174,35 +164,32 @@ class FolderContent extends HookConsumerWidget { onTap: () => context.pushRoute(FolderRoute(folder: subfolder)), ), ), - if (folderAssets.isNotEmpty) - ...folderAssets.mapIndexed( - (index, asset) => LargeLeadingTile( + if (!list.isEmpty && list.allAssets != null && list.allAssets!.isNotEmpty) + ...list.allAssets!.map( + (asset) => LargeLeadingTile( onTap: () { - AssetViewer.setAsset(ref, asset); - unawaited( - context.pushRoute( - AssetViewerRoute( - initialIndex: index, - timelineService: ref - .read(timelineFactoryProvider) - .fromAssets(folderAssets, TimelineOrigin.folder), - ), - ), + ref.read(currentAssetProvider.notifier).set(asset); + context.pushRoute( + GalleryViewerRoute(renderList: list, initialIndex: list.allAssets!.indexOf(asset)), ); }, leading: ClipRRect( borderRadius: const BorderRadius.all(Radius.circular(15)), - child: SizedBox(width: 80, height: 80, child: ThumbnailTile(asset)), + child: SizedBox( + width: 80, + height: 80, + child: ThumbnailImage(asset: asset, showStorageIndicator: false), + ), ), title: Text( - asset.name, + asset.fileName, maxLines: 2, softWrap: false, overflow: TextOverflow.ellipsis, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), subtitle: Text( - "${asset.exifInfo.fileSize != null ? "${formatBytes(asset.exifInfo.fileSize ?? 0)} â€ĸ " : ""}${DateFormat.yMMMd().format(asset.createdAt.toLocal())}", + "${asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo?.fileSize ?? 0) : ""} â€ĸ ${DateFormat.yMMMd().format(asset.fileCreatedAt)}", style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), ), @@ -212,8 +199,8 @@ class FolderContent extends HookConsumerWidget { }, loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) { - ImmichToast.show(context: context, msg: context.t.failed_to_load_assets, toastType: ToastType.error); - return Center(child: Text(context.t.failed_to_load_assets)); + ImmichToast.show(context: context, msg: "failed_to_load_assets".tr(), toastType: ToastType.error); + return Center(child: const Text("failed_to_load_assets").tr()); }, ), ), diff --git a/mobile/lib/pages/library/library.page.dart b/mobile/lib/pages/library/library.page.dart new file mode 100644 index 0000000000..99a534e9cf --- /dev/null +++ b/mobile/lib/pages/library/library.page.dart @@ -0,0 +1,383 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/partner.provider.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/widgets/album/album_thumbnail_card.dart'; +import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; +import 'package:immich_mobile/widgets/common/user_avatar.dart'; +import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; + +@RoutePage() +class LibraryPage extends ConsumerWidget { + const LibraryPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + context.locale; + final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); + + return Scaffold( + appBar: const ImmichAppBar(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ListView( + shrinkWrap: true, + children: [ + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Row( + children: [ + ActionButton( + onPressed: () => context.pushRoute(const FavoritesRoute()), + icon: Icons.favorite_outline_rounded, + label: context.t.favorites, + ), + const SizedBox(width: 8), + ActionButton( + onPressed: () => context.pushRoute(const ArchiveRoute()), + icon: Icons.archive_outlined, + label: context.t.archived, + ), + ], + ), + ), + const SizedBox(height: 8), + Row( + children: [ + ActionButton( + onPressed: () => context.pushRoute(const SharedLinkRoute()), + icon: Icons.link_outlined, + label: context.t.shared_links, + ), + SizedBox(width: trashEnabled ? 8 : 0), + trashEnabled + ? ActionButton( + onPressed: () => context.pushRoute(const TrashRoute()), + icon: Icons.delete_outline_rounded, + label: context.t.trash, + ) + : const SizedBox.shrink(), + ], + ), + const SizedBox(height: 12), + const Wrap( + spacing: 8, + runSpacing: 8, + children: [PeopleCollectionCard(), PlacesCollectionCard(), LocalAlbumsCollectionCard()], + ), + const SizedBox(height: 12), + const QuickAccessButtons(), + const SizedBox(height: 32), + ], + ), + ), + ); + } +} + +class QuickAccessButtons extends ConsumerWidget { + const QuickAccessButtons({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final partners = ref.watch(partnerSharedWithProvider); + + return Container( + decoration: BoxDecoration( + border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + gradient: LinearGradient( + colors: [ + context.colorScheme.primary.withAlpha(10), + context.colorScheme.primary.withAlpha(15), + context.colorScheme.primary.withAlpha(20), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(20), + topRight: const Radius.circular(20), + bottomLeft: Radius.circular(partners.isEmpty ? 20 : 0), + bottomRight: Radius.circular(partners.isEmpty ? 20 : 0), + ), + ), + leading: const Icon(Icons.folder_outlined, size: 26), + title: Text(context.t.folders, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)), + onTap: () => context.pushRoute(FolderRoute()), + ), + ListTile( + leading: const Icon(Icons.lock_outline_rounded, size: 26), + title: Text( + context.t.locked_folder, + style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), + ), + onTap: () => context.pushRoute(const LockedRoute()), + ), + ListTile( + leading: const Icon(Icons.group_outlined, size: 26), + title: Text(context.t.partners, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)), + onTap: () => context.pushRoute(const PartnerRoute()), + ), + PartnerList(partners: partners), + ], + ), + ); + } +} + +class PartnerList extends ConsumerWidget { + const PartnerList({super.key, required this.partners}); + + final List partners; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: partners.length, + shrinkWrap: true, + itemBuilder: (context, index) { + final partner = partners[index]; + final isLastItem = index == partners.length - 1; + return ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(isLastItem ? 20 : 0), + bottomRight: Radius.circular(isLastItem ? 20 : 0), + ), + ), + contentPadding: const EdgeInsets.only(left: 12.0, right: 18.0), + leading: userAvatar(context, partner, radius: 16), + title: const Text( + "partner_list_user_photos", + style: TextStyle(fontWeight: FontWeight.w500), + ).tr(namedArgs: {'user': partner.name}), + onTap: () => context.pushRoute((PartnerDetailRoute(partner: partner))), + ); + }, + ); + } +} + +class PeopleCollectionCard extends ConsumerWidget { + const PeopleCollectionCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final people = ref.watch(getAllPeopleProvider); + return LayoutBuilder( + builder: (context, constraints) { + final isTablet = constraints.maxWidth > 600; + final widthFactor = isTablet ? 0.25 : 0.5; + final size = context.width * widthFactor - 20.0; + + return GestureDetector( + onTap: () => context.pushRoute(const PeopleCollectionRoute()), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: size, + width: size, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + gradient: LinearGradient( + colors: [context.colorScheme.primary.withAlpha(30), context.colorScheme.primary.withAlpha(25)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: people.widgetWhen( + onLoading: () => const Center(child: CircularProgressIndicator()), + onData: (people) { + return GridView.count( + crossAxisCount: 2, + padding: const EdgeInsets.all(12), + crossAxisSpacing: 8, + mainAxisSpacing: 8, + physics: const NeverScrollableScrollPhysics(), + children: people.take(4).map((person) { + return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id))); + }).toList(), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.t.people, + style: context.textTheme.titleSmall?.copyWith( + color: context.colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + }, + ); + } +} + +class LocalAlbumsCollectionCard extends HookConsumerWidget { + const LocalAlbumsCollectionCard({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(localAlbumsProvider); + + return LayoutBuilder( + builder: (context, constraints) { + final isTablet = constraints.maxWidth > 600; + final widthFactor = isTablet ? 0.25 : 0.5; + final size = context.width * widthFactor - 20.0; + + return GestureDetector( + onTap: () => context.pushRoute(const LocalAlbumsRoute()), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: size, + width: size, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + gradient: LinearGradient( + colors: [context.colorScheme.primary.withAlpha(30), context.colorScheme.primary.withAlpha(25)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: GridView.count( + crossAxisCount: 2, + padding: const EdgeInsets.all(12), + crossAxisSpacing: 8, + mainAxisSpacing: 8, + physics: const NeverScrollableScrollPhysics(), + children: albums.take(4).map((album) { + return AlbumThumbnailCard(album: album, showTitle: false); + }).toList(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.t.on_this_device, + style: context.textTheme.titleSmall?.copyWith( + color: context.colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + }, + ); + } +} + +class PlacesCollectionCard extends StatelessWidget { + const PlacesCollectionCard({super.key}); + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final isTablet = constraints.maxWidth > 600; + final widthFactor = isTablet ? 0.25 : 0.5; + final size = context.width * widthFactor - 20.0; + + return GestureDetector( + onTap: () => context.pushRoute(PlacesCollectionRoute(currentLocation: null)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: size, + width: size, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + color: context.colorScheme.secondaryContainer.withAlpha(100), + ), + child: IgnorePointer( + child: MapThumbnail( + zoom: 8, + centre: const LatLng(21.44950, -157.91959), + showAttribution: false, + themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + context.t.places, + style: context.textTheme.titleSmall?.copyWith( + color: context.colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + }, + ); + } +} + +class ActionButton extends StatelessWidget { + final VoidCallback onPressed; + final IconData icon; + final String label; + + const ActionButton({super.key, required this.onPressed, required this.icon, required this.label}); + + @override + Widget build(BuildContext context) { + return Expanded( + child: FilledButton.icon( + onPressed: onPressed, + label: Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text(label, style: TextStyle(color: context.colorScheme.onSurface, fontSize: 15)), + ), + style: FilledButton.styleFrom( + elevation: 0, + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + backgroundColor: context.colorScheme.surfaceContainerLow, + alignment: Alignment.centerLeft, + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(25)), + side: BorderSide(color: context.colorScheme.onSurface.withAlpha(10), width: 1), + ), + ), + icon: Icon(icon, color: context.primaryColor), + ), + ); + } +} diff --git a/mobile/lib/pages/library/local_albums.page.dart b/mobile/lib/pages/library/local_albums.page.dart new file mode 100644 index 0000000000..e52a8326df --- /dev/null +++ b/mobile/lib/pages/library/local_albums.page.dart @@ -0,0 +1,49 @@ +import 'package:auto_route/auto_route.dart'; +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/extensions/theme_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; + +@RoutePage() +class LocalAlbumsPage extends HookConsumerWidget { + const LocalAlbumsPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(localAlbumsProvider); + + return Scaffold( + appBar: AppBar(title: Text('on_this_device'.tr())), + body: ListView.builder( + padding: const EdgeInsets.all(18.0), + itemCount: albums.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: LargeLeadingTile( + leadingPadding: const EdgeInsets.only(right: 16), + leading: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(15)), + child: ImmichThumbnail(asset: albums[index].thumbnail.value, width: 80, height: 80), + ), + title: Text( + albums[index].name, + style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), + ), + subtitle: Text( + 'items_count'.t(context: context, args: {'count': albums[index].assetCount}), + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ), + onTap: () => context.pushRoute(AlbumViewerRoute(albumId: albums[index].id)), + ), + ); + }, + ), + ); + } +} diff --git a/mobile/lib/pages/library/locked/locked.page.dart b/mobile/lib/pages/library/locked/locked.page.dart new file mode 100644 index 0000000000..aea62e0051 --- /dev/null +++ b/mobile/lib/pages/library/locked/locked.page.dart @@ -0,0 +1,82 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; + +@RoutePage() +class LockedPage extends HookConsumerWidget { + const LockedPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final appLifeCycle = useAppLifecycleState(); + final showOverlay = useState(false); + final authProviderNotifier = ref.read(authProvider.notifier); + // lock the page when it is destroyed + useEffect(() { + return () { + authProviderNotifier.lockPinCode(); + }; + }, []); + + useEffect(() { + if (context.mounted) { + if (appLifeCycle == AppLifecycleState.resumed) { + showOverlay.value = false; + } else { + showOverlay.value = true; + } + } + + return null; + }, [appLifeCycle]); + + return Scaffold( + appBar: ref.watch(multiselectProvider) ? null : const LockPageAppBar(), + body: showOverlay.value + ? const SizedBox() + : MultiselectGrid( + renderListProvider: lockedTimelineProvider, + topWidget: Padding( + padding: const EdgeInsets.all(16.0), + child: Center(child: Text('no_locked_photos_message'.tr(), style: context.textTheme.labelLarge)), + ), + editEnabled: false, + favoriteEnabled: false, + unfavorite: false, + archiveEnabled: false, + stackEnabled: false, + unarchive: false, + ), + ); + } +} + +class LockPageAppBar extends ConsumerWidget implements PreferredSizeWidget { + const LockPageAppBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return AppBar( + leading: IconButton( + onPressed: () { + ref.read(authProvider.notifier).lockPinCode(); + context.maybePop(); + }, + icon: const Icon(Icons.arrow_back_ios_rounded), + ), + centerTitle: true, + automaticallyImplyLeading: false, + title: const Text('locked_folder').tr(), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/mobile/lib/pages/library/locked/pin_auth.page.dart b/mobile/lib/pages/library/locked/pin_auth.page.dart index db0e009d8c..a39c91871b 100644 --- a/mobile/lib/pages/library/locked/pin_auth.page.dart +++ b/mobile/lib/pages/library/locked/pin_auth.page.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' show useState; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/local_auth.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/forms/pin_registration_form.dart'; @@ -21,27 +22,30 @@ class PinAuthPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final localAuthState = ref.watch(localAuthProvider); final showPinRegistrationForm = useState(createPinCode); + final isBetaTimeline = Store.isBetaTimelineEnabled; Future registerBiometric(String pinCode) async { final isRegistered = await ref.read(localAuthProvider.notifier).registerBiometric(context, pinCode); - if (!isRegistered || !context.mounted) { - return; + if (isRegistered) { + context.showSnackBar( + SnackBar( + content: Text('biometric_auth_enabled'.tr(), style: context.textTheme.labelLarge), + duration: const Duration(seconds: 3), + backgroundColor: context.colorScheme.primaryContainer, + ), + ); + + if (isBetaTimeline) { + unawaited(context.replaceRoute(const DriftLockedFolderRoute())); + } else { + unawaited(context.replaceRoute(const LockedRoute())); + } } - - context.showSnackBar( - SnackBar( - content: Text(context.t.biometric_auth_enabled, style: context.textTheme.labelLarge), - duration: const Duration(seconds: 3), - backgroundColor: context.colorScheme.primaryContainer, - ), - ); - - unawaited(context.replaceRoute(const DriftLockedFolderRoute())); } - Future enableBiometricAuth() { - return showDialog( + enableBiometricAuth() { + showDialog( context: context, builder: (buildContext) { return SimpleDialog( @@ -52,10 +56,10 @@ class PinAuthPage extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ PinVerificationForm( - description: context.t.enable_biometric_auth_description, + description: 'enable_biometric_auth_description'.tr(), onSuccess: (pinCode) { Navigator.pop(buildContext); - unawaited(registerBiometric(pinCode)); + registerBiometric(pinCode); }, autoFocus: true, icon: Icons.fingerprint_rounded, @@ -71,7 +75,7 @@ class PinAuthPage extends HookConsumerWidget { } return Scaffold( - appBar: AppBar(title: Text(context.t.locked_folder)), + appBar: AppBar(title: Text('locked_folder'.tr())), body: ListView( shrinkWrap: true, children: [ @@ -85,7 +89,11 @@ class PinAuthPage extends HookConsumerWidget { child: PinVerificationForm( autoFocus: true, onSuccess: (_) { - unawaited(context.replaceRoute(const DriftLockedFolderRoute())); + if (isBetaTimeline) { + context.replaceRoute(const DriftLockedFolderRoute()); + } else { + context.replaceRoute(const LockedRoute()); + } }, ), ), @@ -95,9 +103,9 @@ class PinAuthPage extends HookConsumerWidget { padding: const EdgeInsets.only(right: 16.0), child: TextButton.icon( icon: const Icon(Icons.fingerprint, size: 28), - onPressed: () => unawaited(enableBiometricAuth()), + onPressed: enableBiometricAuth, label: Text( - context.t.use_biometric, + 'use_biometric'.tr(), style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor, fontSize: 18), ), ), diff --git a/mobile/lib/pages/library/partner/drift_partner.page.dart b/mobile/lib/pages/library/partner/drift_partner.page.dart new file mode 100644 index 0000000000..d81cc44c76 --- /dev/null +++ b/mobile/lib/pages/library/partner/drift_partner.page.dart @@ -0,0 +1,140 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/partner.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +@RoutePage() +class DriftPartnerPage extends HookConsumerWidget { + const DriftPartnerPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final potentialPartnersAsync = ref.watch(driftAvailablePartnerProvider); + + addNewUsersHandler() async { + final potentialPartners = potentialPartnersAsync.value; + if (potentialPartners == null || potentialPartners.isEmpty) { + ImmichToast.show(context: context, msg: "partner_page_no_more_users".tr()); + return; + } + + final selectedUser = await showDialog( + context: context, + builder: (context) { + return SimpleDialog( + title: const Text("partner_page_select_partner").tr(), + children: [ + for (PartnerUserDto partner in potentialPartners) + SimpleDialogOption( + onPressed: () => context.pop(partner), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(right: 8), + child: PartnerUserAvatar(partner: partner), + ), + Text(partner.name), + ], + ), + ), + ], + ); + }, + ); + if (selectedUser != null) { + await ref.read(partnerUsersProvider.notifier).addPartner(selectedUser); + } + } + + onDeleteUser(PartnerUserDto partner) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ConfirmDialog( + title: "stop_photo_sharing", + content: "partner_page_stop_sharing_content".tr(namedArgs: {'partner': partner.name}), + onOk: () => ref.read(partnerUsersProvider.notifier).removePartner(partner), + ); + }, + ); + } + + return Scaffold( + appBar: AppBar( + title: const Text("partners").t(context: context), + elevation: 0, + centerTitle: false, + actions: [ + IconButton( + onPressed: potentialPartnersAsync.whenOrNull(data: (data) => addNewUsersHandler), + icon: const Icon(Icons.person_add), + tooltip: "add_partner".tr(), + ), + ], + ), + body: _SharedToPartnerList(onAddPartner: addNewUsersHandler, onDeletePartner: onDeleteUser), + ); + } +} + +class _SharedToPartnerList extends ConsumerWidget { + final VoidCallback onAddPartner; + final Function(PartnerUserDto partner) onDeletePartner; + + const _SharedToPartnerList({required this.onAddPartner, required this.onDeletePartner}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final partnerAsync = ref.watch(driftSharedByPartnerProvider); + + return partnerAsync.when( + data: (partners) { + if (partners.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: const Text("partner_page_empty_message", style: TextStyle(fontSize: 14)).tr(), + ), + Align( + alignment: Alignment.center, + child: ElevatedButton.icon( + onPressed: onAddPartner, + icon: const Icon(Icons.person_add), + label: const Text("add_partner").tr(), + ), + ), + ], + ), + ); + } + + return ListView.builder( + itemCount: partners.length, + itemBuilder: (context, index) { + final partner = partners[index]; + return ListTile( + leading: PartnerUserAvatar(partner: partner), + title: Text(partner.name), + subtitle: Text(partner.email), + trailing: IconButton(icon: const Icon(Icons.person_remove), onPressed: () => onDeletePartner(partner)), + ); + }, + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('error_loading_partners'.tr(args: [error.toString()]))), + ); + } +} diff --git a/mobile/lib/pages/library/partner/partner.page.dart b/mobile/lib/pages/library/partner/partner.page.dart index afe64d24f4..eae4228a2d 100644 --- a/mobile/lib/pages/library/partner/partner.page.dart +++ b/mobile/lib/pages/library/partner/partner.page.dart @@ -1,98 +1,139 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/partner.action.dart'; -import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; - -@visibleForTesting -final partnersStateProvider = StreamProvider.autoDispose>((ref) { - final currentUser = ref.watch(currentUserProvider); - // TODO: Refactor with a route guard to avoid this check in every provider - if (currentUser == null) { - return const Stream.empty(); - } - - return ref.watch(partnerServiceProvider).search(currentUser.id, .sharedBy); -}); +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/partner.provider.dart'; +import 'package:immich_mobile/services/partner.service.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/widgets/common/user_avatar.dart'; @RoutePage() -class PartnerPage extends ConsumerWidget { +class PartnerPage extends HookConsumerWidget { const PartnerPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final sharedByAsync = ref.watch(partnersStateProvider); + final List partners = ref.watch(partnerSharedByProvider); + final availableUsers = ref.watch(partnerAvailableProvider); + + addNewUsersHandler() async { + final users = availableUsers.value; + if (users == null || users.isEmpty) { + ImmichToast.show(context: context, msg: "partner_page_no_more_users".tr()); + return; + } + + final selectedUser = await showDialog( + context: context, + builder: (context) { + return SimpleDialog( + title: const Text("partner_page_select_partner").tr(), + children: [ + for (UserDto u in users) + SimpleDialogOption( + onPressed: () => context.pop(u), + child: Row( + children: [ + Padding(padding: const EdgeInsets.only(right: 8), child: userAvatar(context, u)), + Text(u.name), + ], + ), + ), + ], + ); + }, + ); + if (selectedUser != null) { + final ok = await ref.read(partnerServiceProvider).addPartner(selectedUser); + if (ok) { + ref.invalidate(partnerSharedByProvider); + } else { + ImmichToast.show(context: context, msg: "partner_page_partner_add_failed".tr(), toastType: ToastType.error); + } + } + } + + onDeleteUser(UserDto u) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ConfirmDialog( + title: "stop_photo_sharing", + content: "partner_page_stop_sharing_content".tr(namedArgs: {'partner': u.name}), + onOk: () => ref.read(partnerServiceProvider).removePartner(u), + ); + }, + ); + } + + buildUserList(List users) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0), + child: Text( + "partner_page_shared_to_title", + style: context.textTheme.titleSmall?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)), + ).tr(), + ), + if (users.isNotEmpty) + ListView.builder( + shrinkWrap: true, + itemCount: users.length, + itemBuilder: ((context, index) { + return ListTile( + leading: userAvatar(context, users[index]), + title: Text(users[index].email, style: context.textTheme.bodyLarge), + trailing: IconButton( + icon: const Icon(Icons.person_remove), + onPressed: () => onDeleteUser(users[index]), + ), + ); + }), + ), + if (users.isEmpty) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: const Text("partner_page_empty_message", style: TextStyle(fontSize: 14)).tr(), + ), + Align( + alignment: Alignment.center, + child: ElevatedButton.icon( + onPressed: availableUsers.whenOrNull(data: (data) => addNewUsersHandler), + icon: const Icon(Icons.person_add), + label: const Text("add_partner").tr(), + ), + ), + ], + ), + ), + ], + ); + } return Scaffold( appBar: AppBar( - title: Text(context.t.partners), + title: const Text("partners").tr(), elevation: 0, centerTitle: false, - actions: const [ActionIconButton(action: PartnerAddAction())], - ), - body: sharedByAsync.when( - data: (partners) => PartnerSharedByList(partners: partners.toList(growable: false)), - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, _) => Center(child: Text(context.t.error_loading_partners(error: error))), - ), - ); - } -} - -class _EmptyPartners extends StatelessWidget { - const _EmptyPartners(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const .symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: .start, - children: [ - Padding( - padding: const .symmetric(vertical: 8), - child: Text(context.t.partner_page_empty_message, style: const TextStyle(fontSize: 14)), - ), - const Align( - alignment: .center, - child: ActionButton(action: PartnerAddAction()), + actions: [ + IconButton( + onPressed: availableUsers.whenOrNull(data: (data) => addNewUsersHandler), + icon: const Icon(Icons.person_add), + tooltip: "add_partner".tr(), ), ], ), - ); - } -} - -@visibleForTesting -class PartnerSharedByList extends StatelessWidget { - const PartnerSharedByList({super.key, required this.partners}); - - final List partners; - - @override - Widget build(BuildContext context) { - if (partners.isEmpty) { - return const _EmptyPartners(); - } - - return ListView.builder( - itemCount: partners.length, - itemBuilder: (_, index) { - final partner = partners[index]; - return ListTile( - leading: PartnerUserAvatar(userId: partner.id, name: partner.name), - title: Text(partner.name), - subtitle: Text(partner.email), - trailing: ActionIconButton( - action: PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), - ), - ); - }, + body: buildUserList(partners), ); } } diff --git a/mobile/lib/pages/library/partner/partner_detail.page.dart b/mobile/lib/pages/library/partner/partner_detail.page.dart new file mode 100644 index 0000000000..1f15dab6a3 --- /dev/null +++ b/mobile/lib/pages/library/partner/partner_detail.page.dart @@ -0,0 +1,99 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/partner.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +@RoutePage() +class PartnerDetailPage extends HookConsumerWidget { + const PartnerDetailPage({super.key, required this.partner}); + + final UserDto partner; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final inTimeline = useState(partner.inTimeline); + bool toggleInProcess = false; + + useEffect(() { + Future.microtask(() async => {await ref.read(assetProvider.notifier).getAllAsset()}); + return null; + }, []); + + void toggleInTimeline() async { + if (toggleInProcess) return; + toggleInProcess = true; + try { + final ok = await ref + .read(partnerSharedWithProvider.notifier) + .updatePartner(partner, inTimeline: !inTimeline.value); + if (ok) { + inTimeline.value = !inTimeline.value; + final action = inTimeline.value ? "shown on" : "hidden from"; + ImmichToast.show( + context: context, + toastType: ToastType.success, + durationInSecond: 1, + msg: "${partner.name}'s assets $action your timeline", + ); + } else { + ImmichToast.show( + context: context, + toastType: ToastType.error, + durationInSecond: 1, + msg: "Failed to toggle the timeline setting", + ); + } + } finally { + toggleInProcess = false; + } + } + + return Scaffold( + appBar: ref.watch(multiselectProvider) + ? null + : AppBar(title: Text(partner.name), elevation: 0, centerTitle: false), + body: MultiselectGrid( + topWidget: Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + gradient: LinearGradient( + colors: [context.colorScheme.primary.withAlpha(10), context.colorScheme.primary.withAlpha(15)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ListTile( + title: Text( + "Show in timeline", + style: context.textTheme.titleSmall?.copyWith(color: context.colorScheme.primary), + ), + subtitle: Text( + "Show photos and videos from this user in your timeline", + style: context.textTheme.bodyMedium, + ), + trailing: Switch(value: inTimeline.value, onChanged: (_) => toggleInTimeline()), + ), + ), + ), + ), + renderListProvider: singleUserTimelineProvider(partner.id), + onRefresh: () => ref.read(assetProvider.notifier).getAllAsset(), + deleteEnabled: false, + favoriteEnabled: false, + ), + ); + } +} diff --git a/mobile/lib/pages/library/people/people_collection.page.dart b/mobile/lib/pages/library/people/people_collection.page.dart new file mode 100644 index 0000000000..bff52df6da --- /dev/null +++ b/mobile/lib/pages/library/people/people_collection.page.dart @@ -0,0 +1,127 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/widgets/common/search_field.dart'; +import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; + +@RoutePage() +class PeopleCollectionPage extends HookConsumerWidget { + const PeopleCollectionPage({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final people = ref.watch(getAllPeopleProvider); + final formFocus = useFocusNode(); + final ValueNotifier search = useState(null); + + showNameEditModel(String personId, String personName) { + return showDialog( + context: context, + useRootNavigator: false, + builder: (BuildContext context) { + return PersonNameEditForm(personId: personId, personName: personName); + }, + ); + } + + return LayoutBuilder( + builder: (context, constraints) { + final isTablet = constraints.maxWidth > 600; + final isPortrait = context.orientation == Orientation.portrait; + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: search.value == null, + title: search.value != null + ? SearchField( + focusNode: formFocus, + onTapOutside: (_) => formFocus.unfocus(), + onChanged: (value) => search.value = value, + filled: true, + hintText: 'filter_people'.tr(), + autofocus: true, + ) + : Text('people'.tr()), + actions: [ + IconButton( + icon: Icon(search.value != null ? Icons.close : Icons.search), + onPressed: () { + search.value = search.value == null ? '' : null; + }, + ), + ], + ), + body: SafeArea( + child: people.when( + data: (people) { + if (search.value != null) { + people = people.where((person) { + return person.name.toLowerCase().contains(search.value!.toLowerCase()); + }).toList(); + } + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isTablet ? 6 : 3, + childAspectRatio: 0.85, + mainAxisSpacing: isPortrait && isTablet ? 36 : 0, + ), + padding: const EdgeInsets.symmetric(vertical: 32), + itemCount: people.length, + itemBuilder: (context, index) { + final person = people[index]; + + return Column( + children: [ + GestureDetector( + onTap: () { + context.pushRoute(PersonResultRoute(personId: person.id, personName: person.name)); + }, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: isTablet ? 120 / 2 : 96 / 2, + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), + ), + ), + ), + const SizedBox(height: 12), + GestureDetector( + onTap: () => showNameEditModel(person.id, person.name), + child: person.name.isEmpty + ? Text( + 'add_a_name'.tr(), + style: context.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.w500, + color: context.colorScheme.primary, + ), + ) + : Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + person.name, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), + ), + ), + ), + ], + ); + }, + ); + }, + error: (error, stack) => const Text("error"), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ), + ); + }, + ); + } +} diff --git a/mobile/lib/pages/library/places/places_collection.page.dart b/mobile/lib/pages/library/places/places_collection.page.dart new file mode 100644 index 0000000000..a4a6f66915 --- /dev/null +++ b/mobile/lib/pages/library/places/places_collection.page.dart @@ -0,0 +1,136 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/providers/search/search_page_state.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/search_field.dart'; +import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; + +@RoutePage() +class PlacesCollectionPage extends HookConsumerWidget { + const PlacesCollectionPage({super.key, this.currentLocation}); + final LatLng? currentLocation; + @override + Widget build(BuildContext context, WidgetRef ref) { + final places = ref.watch(getAllPlacesProvider); + final formFocus = useFocusNode(); + final ValueNotifier search = useState(null); + + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: search.value == null, + title: search.value != null + ? SearchField( + autofocus: true, + filled: true, + focusNode: formFocus, + onChanged: (value) => search.value = value, + onTapOutside: (_) => formFocus.unfocus(), + hintText: 'filter_places'.tr(), + ) + : Text('places'.tr()), + actions: [ + IconButton( + icon: Icon(search.value != null ? Icons.close : Icons.search), + onPressed: () { + search.value = search.value == null ? '' : null; + }, + ), + ], + ), + body: ListView( + shrinkWrap: true, + children: [ + if (search.value == null) + Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox( + height: 200, + width: context.width, + child: MapThumbnail( + onTap: (_, __) => context.pushRoute(MapRoute(initialLocation: currentLocation)), + zoom: 8, + centre: currentLocation ?? const LatLng(21.44950, -157.91959), + showAttribution: false, + themeMode: context.isDarkTheme ? ThemeMode.dark : ThemeMode.light, + ), + ), + ), + places.when( + data: (places) { + if (search.value != null) { + places = places.where((place) { + return place.label.toLowerCase().contains(search.value!.toLowerCase()); + }).toList(); + } + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: places.length, + itemBuilder: (context, index) { + final place = places[index]; + + return PlaceTile(id: place.id, name: place.label); + }, + ); + }, + error: (error, stask) => Text('error_getting_places'.tr()), + loading: () => const Center(child: CircularProgressIndicator()), + ), + ], + ), + ); + } +} + +class PlaceTile extends StatelessWidget { + const PlaceTile({super.key, required this.id, required this.name}); + + final String id; + final String name; + + @override + Widget build(BuildContext context) { + final thumbnailUrl = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail'; + + void navigateToPlace() { + context.pushRoute( + SearchRoute( + prefilter: SearchFilter( + people: {}, + location: SearchLocationFilter(city: name), + camera: SearchCameraFilter(), + date: SearchDateFilter(), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + rating: SearchRatingFilter(), + mediaType: AssetType.other, + ), + ), + ); + } + + return LargeLeadingTile( + onTap: () => navigateToPlace(), + title: Text(name, style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500)), + leading: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: SizedBox( + width: 80, + height: 80, + child: Thumbnail(imageProvider: RemoteImageProvider(url: thumbnailUrl)), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/library/shared_link/shared_link.page.dart b/mobile/lib/pages/library/shared_link/shared_link.page.dart index 70cd4e2129..66a77fb761 100644 --- a/mobile/lib/pages/library/shared_link/shared_link.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link.page.dart @@ -1,11 +1,10 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/widgets/shared_link/shared_link_item.dart'; @@ -19,56 +18,84 @@ class SharedLinkPage extends HookConsumerWidget { final sharedLinks = ref.watch(sharedLinksStateProvider); useEffect(() { - unawaited(ref.read(sharedLinksStateProvider.notifier).fetchLinks()); + ref.read(sharedLinksStateProvider.notifier).fetchLinks(); return () { - if (!context.mounted) { - return; - } + if (!context.mounted) return; ref.invalidate(sharedLinksStateProvider); }; }, []); Widget buildNoShares() { - return Center( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.link_off, size: 100, color: Theme.of(context).colorScheme.onSurface.withAlpha(128)), - const SizedBox(height: 20), - Text(context.t.you_dont_have_any_shared_links, style: const TextStyle(fontSize: 14)), - ], - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0), + child: const Text( + "shared_link_manage_links", + style: TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), + ).tr(), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: const Text("you_dont_have_any_shared_links", style: TextStyle(fontSize: 14)).tr(), + ), + ), + Expanded( + child: Center( + child: Icon(Icons.link_off, size: 100, color: context.themeData.iconTheme.color?.withValues(alpha: 0.5)), + ), + ), + ], ); } Widget buildSharesList(List links) { - return LayoutBuilder( - builder: (context, constraints) => constraints.maxWidth > 600 - ? GridView.builder( - key: const PageStorageKey('shared-links-grid'), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - mainAxisExtent: 180, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), - padding: const EdgeInsets.all(12), - itemCount: links.length, - itemBuilder: (context, index) => SharedLinkItem(links[index]), - ) - : ListView.separated( - key: const PageStorageKey('shared-links-list'), - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: links.length, - itemBuilder: (context, index) => SharedLinkItem(links[index]), - separatorBuilder: (context, index) => const Divider(height: 1), - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16.0, top: 16.0, bottom: 30.0), + child: Text( + "shared_link_manage_links", + style: context.textTheme.labelLarge?.copyWith(color: context.textTheme.labelLarge?.color?.withAlpha(200)), + ).tr(), + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth > 600) { + // Two column + return GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + mainAxisExtent: 180, + ), + itemCount: links.length, + itemBuilder: (context, index) { + return SharedLinkItem(links.elementAt(index)); + }, + ); + } + + // Single column + return ListView.builder( + itemCount: links.length, + itemBuilder: (context, index) { + return SharedLinkItem(links.elementAt(index)); + }, + ); + }, + ), + ), + ], ); } return Scaffold( - appBar: AppBar(title: Text(context.t.shared_link_app_bar_title), elevation: 0, centerTitle: false), + appBar: AppBar(title: const Text("shared_link_app_bar_title").tr(), elevation: 0, centerTitle: false), body: SafeArea( child: sharedLinks.widgetWhen( onError: (error, stackTrace) => buildNoShares(), diff --git a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart index 6c90571428..47a3dd853d 100644 --- a/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart +++ b/mobile/lib/pages/library/shared_link/shared_link_edit.page.dart @@ -6,21 +6,15 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/services/shared_link.service.dart'; -import 'package:immich_mobile/utils/option.dart'; import 'package:immich_mobile/utils/url_helper.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:share_plus/share_plus.dart'; @RoutePage() class SharedLinkEditPage extends HookConsumerWidget { - static const int maxFutureDate = 365 * 2; - final SharedLink? existingLink; final List? assetsList; final String? albumId; @@ -29,82 +23,71 @@ class SharedLinkEditPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + const padding = 20.0; final themeData = context.themeData; final colorScheme = context.colorScheme; - final externalDomain = ref.watch(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - final displayServerUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - final expiryPresets = <(Duration, String)>[ - (Duration.zero, context.t.never), - (const Duration(minutes: 30), context.t.shared_link_edit_expire_after_option_minutes(count: 30)), - (const Duration(hours: 1), context.t.shared_link_edit_expire_after_option_hour), - (const Duration(hours: 6), context.t.shared_link_edit_expire_after_option_hours(count: 6)), - (const Duration(days: 1), context.t.shared_link_edit_expire_after_option_day), - (const Duration(days: 7), context.t.shared_link_edit_expire_after_option_days(count: 7)), - (const Duration(days: 30), context.t.shared_link_edit_expire_after_option_days(count: 30)), - (const Duration(days: 90), context.t.shared_link_edit_expire_after_option_months(count: 3)), - (const Duration(days: 365), context.t.shared_link_edit_expire_after_option_year(count: 1)), - ]; final descriptionController = useTextEditingController(text: existingLink?.description ?? ""); final descriptionFocusNode = useFocusNode(); final passwordController = useTextEditingController(text: existingLink?.password ?? ""); final slugController = useTextEditingController(text: existingLink?.slug ?? ""); final slugFocusNode = useFocusNode(); - useListenable(slugController); final showMetadata = useState(existingLink?.showMetadata ?? true); final allowDownload = useState(existingLink?.allowDownload ?? true); final allowUpload = useState(existingLink?.allowUpload ?? false); - final expiryAfter = useState(existingLink?.expiresAt?.toLocal()); - final selectedPresetIndex = useState(existingLink?.expiresAt == null ? 0 : null); + final editExpiry = useState(false); + final expiryAfter = useState(0); final newShareLink = useState(""); - Widget buildSharedLinkRow({required String leading, required String content}) { - return Row( - children: [ - Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Text( - content, - style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), - ), - ), - ), - const SizedBox(width: 8), - Text(leading, style: const TextStyle(fontWeight: FontWeight.bold)), - ], - ); - } - Widget buildLinkTitle() { if (existingLink != null) { if (existingLink!.type == SharedLinkSource.album) { - return buildSharedLinkRow(leading: context.t.public_album, content: existingLink!.title); + return Row( + children: [ + const Text('public_album', style: TextStyle(fontWeight: FontWeight.bold)).tr(), + const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), + Text( + existingLink!.title, + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + ), + ], + ); } if (existingLink!.type == SharedLinkSource.individual) { - return buildSharedLinkRow( - leading: context.t.shared_link_individual_shared, - content: existingLink!.description ?? "--", + return Row( + children: [ + const Text('shared_link_individual_shared', style: TextStyle(fontWeight: FontWeight.bold)).tr(), + const Text(" | ", style: TextStyle(fontWeight: FontWeight.bold)), + Expanded( + child: Text( + existingLink!.description ?? "--", + style: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.bold), + overflow: TextOverflow.ellipsis, + ), + ), + ], ); } } - return Text(context.t.create_link_to_share_description, style: const TextStyle(fontWeight: FontWeight.bold)); + return const Text("create_link_to_share_description", style: TextStyle(fontWeight: FontWeight.bold)).tr(); } Widget buildDescriptionField() { return TextField( controller: descriptionController, + enabled: newShareLink.value.isEmpty, focusNode: descriptionFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: context.t.description, - labelStyle: const TextStyle(fontWeight: FontWeight.bold), + labelText: 'description'.tr(), + labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: context.t.shared_link_edit_description_hint, + hintText: 'shared_link_edit_description_hint'.tr(), hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), + disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), onTapOutside: (_) => descriptionFocusNode.unfocus(), ); @@ -113,14 +96,16 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildPasswordField() { return TextField( controller: passwordController, + enabled: newShareLink.value.isEmpty, autofocus: false, decoration: InputDecoration( - labelText: context.t.password, - labelStyle: const TextStyle(fontWeight: FontWeight.bold), + labelText: 'password'.tr(), + labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: context.t.shared_link_edit_password_hint, + hintText: 'shared_link_edit_password_hint'.tr(), hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), + disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), ); } @@ -128,16 +113,18 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildSlugField() { return TextField( controller: slugController, + enabled: newShareLink.value.isEmpty, focusNode: slugFocusNode, textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: slugController.text.isNotEmpty ? context.t.shared_link_custom_url_title : null, - labelStyle: const TextStyle(fontWeight: FontWeight.bold), + labelText: 'custom_url'.tr(), + labelStyle: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + floatingLabelBehavior: FloatingLabelBehavior.always, border: const OutlineInputBorder(), - hintText: context.t.shared_link_custom_url_title, - prefixText: slugController.text.isNotEmpty ? '/s/' : null, - prefixStyle: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), + hintText: 'custom_url'.tr(), + hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), + disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.grey.withValues(alpha: 0.5))), ), onTapOutside: (_) => slugFocusNode.unfocus(), ); @@ -146,182 +133,145 @@ class SharedLinkEditPage extends HookConsumerWidget { Widget buildShowMetaButton() { return SwitchListTile.adaptive( value: showMetadata.value, - onChanged: (value) => showMetadata.value = value, + onChanged: newShareLink.value.isEmpty ? (value) => showMetadata.value = value : null, + activeThumbColor: colorScheme.primary, dense: true, - title: Text( - context.t.show_metadata, - style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ), + title: Text("show_metadata", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold)).tr(), ); } Widget buildAllowDownloadButton() { return SwitchListTile.adaptive( value: allowDownload.value, - onChanged: (value) => allowDownload.value = value, + onChanged: newShareLink.value.isEmpty ? (value) => allowDownload.value = value : null, + activeThumbColor: colorScheme.primary, dense: true, title: Text( - context.t.allow_public_user_to_download, + "allow_public_user_to_download", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ), + ).tr(), ); } Widget buildAllowUploadButton() { return SwitchListTile.adaptive( value: allowUpload.value, - onChanged: (value) => allowUpload.value = value, + onChanged: newShareLink.value.isEmpty ? (value) => allowUpload.value = value : null, + activeThumbColor: colorScheme.primary, dense: true, title: Text( - context.t.allow_public_user_to_upload, + "allow_public_user_to_upload", style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ), + ).tr(), ); } - String formatDateTime(DateTime dateTime) => DateFormat.yMMMd(context.locale.toString()).add_Hm().format(dateTime); - - DateTime? getExpiresAtFromPreset(Duration preset) => preset == Duration.zero ? null : DateTime.now().add(preset); - - Future selectDate() async { - final today = DateTime.now(); - final safeInitialDate = expiryAfter.value ?? today.add(const Duration(days: 7)); - final initialDate = safeInitialDate.isBefore(today) ? today : safeInitialDate; - - final selectedDate = await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: today, - lastDate: today.add(const Duration(days: maxFutureDate)), + Widget buildEditExpiryButton() { + return SwitchListTile.adaptive( + value: editExpiry.value, + onChanged: newShareLink.value.isEmpty ? (value) => editExpiry.value = value : null, + activeThumbColor: colorScheme.primary, + dense: true, + title: Text( + "change_expiration_time", + style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), + ).tr(), ); - - if (selectedDate != null && context.mounted) { - final isToday = - selectedDate.year == today.year && selectedDate.month == today.month && selectedDate.day == today.day; - final initialTime = isToday ? TimeOfDay.fromDateTime(today) : const TimeOfDay(hour: 12, minute: 0); - - final selectedTime = await showTimePicker(context: context, initialTime: initialTime); - - if (selectedTime != null) { - final now = DateTime.now(); - var finalDateTime = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - - if (finalDateTime.isBefore(now) && isToday) { - finalDateTime = now; - } - - selectedPresetIndex.value = null; - expiryAfter.value = finalDateTime; - } - } } Widget buildExpiryAfterButton() { - return ExpansionTile( - title: Text( - context.t.expire_after, - style: themeData.textTheme.labelLarge?.copyWith(fontWeight: FontWeight.bold), - ), - subtitle: Text( - expiryAfter.value == null ? context.t.shared_link_expires_never : formatDateTime(expiryAfter.value!), - style: TextStyle(color: themeData.colorScheme.primary), - ), + return DropdownMenu( + label: Text( + "expire_after", + style: TextStyle(fontWeight: FontWeight.bold, color: colorScheme.primary), + ).tr(), + enableSearch: false, + enableFilter: false, + width: context.width - 40, + initialSelection: expiryAfter.value, + enabled: newShareLink.value.isEmpty && (existingLink == null || editExpiry.value), + onSelected: (value) { + expiryAfter.value = value!; + }, + dropdownMenuEntries: [ + DropdownMenuEntry(value: 0, label: "never".tr()), + DropdownMenuEntry( + value: 30, + label: "shared_link_edit_expire_after_option_minutes".tr(namedArgs: {'count': "30"}), + ), + DropdownMenuEntry(value: 60, label: "shared_link_edit_expire_after_option_hour".tr()), + DropdownMenuEntry( + value: 60 * 6, + label: "shared_link_edit_expire_after_option_hours".tr(namedArgs: {'count': "6"}), + ), + DropdownMenuEntry(value: 60 * 24, label: "shared_link_edit_expire_after_option_day".tr()), + DropdownMenuEntry( + value: 60 * 24 * 7, + label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "7"}), + ), + DropdownMenuEntry( + value: 60 * 24 * 30, + label: "shared_link_edit_expire_after_option_days".tr(namedArgs: {'count': "30"}), + ), + DropdownMenuEntry( + value: 60 * 24 * 30 * 3, + label: "shared_link_edit_expire_after_option_months".tr(namedArgs: {'count': "3"}), + ), + DropdownMenuEntry( + value: 60 * 24 * 30 * 12, + label: "shared_link_edit_expire_after_option_year".tr(namedArgs: {'count': "1"}), + ), + ], + ); + } + + void copyLinkToClipboard() { + Clipboard.setData(ClipboardData(text: newShareLink.value)).then((_) { + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "shared_link_clipboard_copied_massage", + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ).tr(), + duration: const Duration(seconds: 2), + ), + ); + }); + } + + Widget buildNewLinkField() { + return Column( children: [ + const Padding(padding: EdgeInsets.only(top: 20, bottom: 20), child: Divider()), + TextFormField( + readOnly: true, + initialValue: newShareLink.value, + decoration: InputDecoration( + border: const OutlineInputBorder(), + enabledBorder: themeData.inputDecorationTheme.focusedBorder, + suffixIcon: IconButton(onPressed: copyLinkToClipboard, icon: const Icon(Icons.copy)), + ), + ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Wrap( - spacing: 8, - runSpacing: 8, - children: List.generate(expiryPresets.length, (index) { - final preset = expiryPresets[index]; - return ChoiceChip( - label: Text(preset.$2), - selected: selectedPresetIndex.value == index, - onSelected: (_) { - selectedPresetIndex.value = index; - expiryAfter.value = getExpiresAtFromPreset(preset.$1); - }, - ); - }), - ), - if (expiryAfter.value != null) ...[ - const SizedBox(height: 16), - SizedBox( - width: double.infinity, - child: OutlinedButton.icon( - onPressed: selectDate, - icon: const Icon(Icons.edit_calendar), - label: Text(context.t.edit_date_and_time), - ), - ), - ], - ], + padding: const EdgeInsets.only(top: 16.0), + child: Align( + alignment: Alignment.bottomRight, + child: ElevatedButton( + onPressed: () { + context.maybePop(); + }, + child: const Text("done", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), + ), ), ), ], ); } - Future copyToClipboard(String link) async { - await Clipboard.setData(ClipboardData(text: link)); - if (!context.mounted) { - return; - } - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - context.t.shared_link_clipboard_copied_massage, - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), - ), - duration: const Duration(seconds: 2), - ), - ); + DateTime calculateExpiry() { + return DateTime.now().add(Duration(minutes: expiryAfter.value)); } - Widget buildLinkCopyField(String link) { - return TextFormField( - readOnly: true, - onTap: () => copyToClipboard(link), - initialValue: link, - decoration: InputDecoration( - border: const OutlineInputBorder(), - enabledBorder: themeData.inputDecorationTheme.focusedBorder, - suffixIcon: IconButton(onPressed: () => Share.share(link), icon: const Icon(Icons.share)), - ), - ); - } - - Widget buildNewLinkReadyScreen() { - return Padding( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.add_link, size: 100, color: themeData.colorScheme.primary), - const SizedBox(height: 20), - buildLinkCopyField(newShareLink.value), - const SizedBox(height: 20), - ElevatedButton.icon( - onPressed: () => context.maybePop(), - icon: const Icon(Icons.check), - label: Text(context.t.done, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - ), - ], - ), - ); - } - - DateTime? calculateExpiry() => expiryAfter.value; - Future handleNewLink() async { final newLink = await ref .read(sharedLinkServiceProvider) @@ -334,30 +284,30 @@ class SharedLinkEditPage extends HookConsumerWidget { description: descriptionController.text.isEmpty ? null : descriptionController.text, password: passwordController.text.isEmpty ? null : passwordController.text, slug: slugController.text.isEmpty ? null : slugController.text, - expiresAt: calculateExpiry()?.toUtc(), + expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), ); - if (!context.mounted) { - return; - } ref.invalidate(sharedLinksStateProvider); await ref.read(serverInfoProvider.notifier).getServerConfig(); - if (!context.mounted) { - return; - } final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + if (serverUrl != null && !serverUrl.endsWith('/')) { + serverUrl += '/'; + } - if (newLink != null) { - newShareLink.value = buildSharedLinkUrl(baseUrl: serverUrl, slug: newLink.slug, key: newLink.key) ?? ''; - await copyToClipboard(newShareLink.value); - } else { + if (newLink != null && serverUrl != null) { + final hasSlug = newLink.slug?.isNotEmpty == true; + final urlPath = hasSlug ? newLink.slug : newLink.key; + final basePath = hasSlug ? 's' : 'share'; + newShareLink.value = "$serverUrl$basePath/$urlPath"; + copyLinkToClipboard(); + } else if (newLink == null) { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: context.t.shared_link_create_error, + msg: 'shared_link_create_error'.tr(), ); } } @@ -366,10 +316,11 @@ class SharedLinkEditPage extends HookConsumerWidget { bool? download; bool? upload; bool? meta; - var password = const Option.none(); - var description = const Option.none(); + String? desc; + String? password; String? slug; - var expiry = const Option.none(); + DateTime? expiry; + bool? changeExpiry; if (allowDownload.value != existingLink!.allowDownload) { download = allowDownload.value; @@ -383,14 +334,12 @@ class SharedLinkEditPage extends HookConsumerWidget { meta = showMetadata.value; } - if (descriptionController.text != (existingLink!.description ?? '')) { - description = descriptionController.text.isEmpty - ? const Option.some(null) - : Option.some(descriptionController.text); + if (descriptionController.text != existingLink!.description) { + desc = descriptionController.text; } - if (passwordController.text != (existingLink!.password ?? '')) { - password = passwordController.text.isEmpty ? const Option.some(null) : Option.some(passwordController.text); + if (passwordController.text != existingLink!.password) { + password = passwordController.text; } if (slugController.text != (existingLink!.slug ?? "")) { @@ -399,9 +348,9 @@ class SharedLinkEditPage extends HookConsumerWidget { slug = existingLink!.slug; } - final newExpiry = expiryAfter.value; - if (newExpiry?.toUtc() != existingLink!.expiresAt?.toUtc()) { - expiry = newExpiry == null ? const Option.some(null) : Option.some(newExpiry.toUtc()); + if (editExpiry.value) { + expiry = expiryAfter.value == 0 ? null : calculateExpiry(); + changeExpiry = true; } await ref @@ -411,117 +360,72 @@ class SharedLinkEditPage extends HookConsumerWidget { showMeta: meta, allowDownload: download, allowUpload: upload, - description: description, + description: desc, password: password, slug: slug, expiresAt: expiry, + changeExpiry: changeExpiry, ); - if (!context.mounted) { - return; - } ref.invalidate(sharedLinksStateProvider); await context.maybePop(); } - Future handleDeleteLink() async { - return showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - title: context.t.delete_shared_link_dialog_title, - content: context.t.confirm_delete_shared_link, - onOk: () async { - await ref.read(sharedLinkServiceProvider).deleteSharedLink(existingLink!.id); - ref.invalidate(sharedLinksStateProvider); - if (context.mounted) { - await context.maybePop(); - } - }, - ), - ); - } - return Scaffold( appBar: AppBar( - title: Text(existingLink == null ? context.t.create_link_to_share : context.t.edit_link), + title: Text(existingLink == null ? "create_link_to_share" : "edit_link").tr(), elevation: 0, leading: const CloseButton(), centerTitle: false, ), body: SafeArea( - child: newShareLink.value.isEmpty - ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: ListView( - children: [ - const SizedBox(height: 20), - buildLinkTitle(), - if (existingLink != null) - Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const SizedBox(height: 16), - buildLinkCopyField( - buildSharedLinkUrl( - baseUrl: displayServerUrl, - slug: existingLink!.slug, - key: existingLink!.key, - ) ?? - '', - ), - const SizedBox(height: 24), - const Divider(), - ], - ), - const SizedBox(height: 24), - buildDescriptionField(), - const SizedBox(height: 16), - buildPasswordField(), - const SizedBox(height: 16), - buildSlugField(), - const SizedBox(height: 16), - buildShowMetaButton(), - const SizedBox(height: 16), - buildAllowDownloadButton(), - const SizedBox(height: 16), - buildAllowUploadButton(), - const SizedBox(height: 16), - buildExpiryAfterButton(), - const SizedBox(height: 24), - Align( - alignment: Alignment.centerRight, - child: Row( - mainAxisSize: MainAxisSize.min, - spacing: 8, - children: [ - if (existingLink != null) - OutlinedButton.icon( - style: OutlinedButton.styleFrom( - foregroundColor: themeData.colorScheme.error, - side: BorderSide(color: themeData.colorScheme.error), - ), - onPressed: handleDeleteLink, - icon: const Icon(Icons.delete_outline), - label: Text( - context.t.delete, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ), - ), - ElevatedButton.icon( - icon: const Icon(Icons.check), - onPressed: existingLink != null ? handleEditLink : handleNewLink, - label: Text( - existingLink != null ? context.t.shared_link_edit_submit_button : context.t.create_link, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), - ), - ), - ], - ), - ), - const SizedBox(height: 40), - ], + child: ListView( + children: [ + Padding(padding: const EdgeInsets.all(padding), child: buildLinkTitle()), + Padding(padding: const EdgeInsets.all(padding), child: buildDescriptionField()), + Padding(padding: const EdgeInsets.all(padding), child: buildPasswordField()), + Padding(padding: const EdgeInsets.all(padding), child: buildSlugField()), + Padding( + padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), + child: buildShowMetaButton(), + ), + Padding( + padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), + child: buildAllowDownloadButton(), + ), + Padding( + padding: const EdgeInsets.only(left: padding, right: 20, bottom: 20), + child: buildAllowUploadButton(), + ), + if (existingLink != null) + Padding( + padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), + child: buildEditExpiryButton(), + ), + Padding( + padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), + child: buildExpiryAfterButton(), + ), + if (newShareLink.value.isEmpty) + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(right: padding + 10, bottom: padding), + child: ElevatedButton( + onPressed: existingLink != null ? handleEditLink : handleNewLink, + child: Text( + existingLink != null ? "shared_link_edit_submit_button" : "create_link", + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ).tr(), + ), ), - ) - : Center(child: buildNewLinkReadyScreen()), + ), + if (newShareLink.value.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding), + child: buildNewLinkField(), + ), + ], + ), ), ); } diff --git a/mobile/lib/pages/library/trash.page.dart b/mobile/lib/pages/library/trash.page.dart new file mode 100644 index 0000000000..2279998c2d --- /dev/null +++ b/mobile/lib/pages/library/trash.page.dart @@ -0,0 +1,225 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/providers/trash.provider.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +@RoutePage() +class TrashPage extends HookConsumerWidget { + const TrashPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final trashRenderList = ref.watch(trashTimelineProvider); + final trashDays = ref.watch(serverInfoProvider.select((v) => v.serverConfig.trashDays)); + final selectionEnabledHook = useState(false); + final selection = useState({}); + final processing = useProcessingOverlay(); + + void selectionListener(bool multiselect, Set selectedAssets) { + selectionEnabledHook.value = multiselect; + selection.value = selectedAssets; + } + + onEmptyTrash() async { + processing.value = true; + await ref.read(trashProvider.notifier).emptyTrash(); + processing.value = false; + selectionEnabledHook.value = false; + if (context.mounted) { + ImmichToast.show(context: context, msg: 'trash_emptied'.tr(), gravity: ToastGravity.BOTTOM); + } + } + + handleEmptyTrash() async { + await showDialog( + context: context, + builder: (context) => ConfirmDialog( + onOk: () => onEmptyTrash(), + title: "empty_trash".tr(), + ok: "ok".tr(), + content: "trash_page_empty_trash_dialog_content".tr(), + ), + ); + } + + Future onPermanentlyDelete() async { + processing.value = true; + try { + if (selection.value.isNotEmpty) { + final isRemoved = await ref.read(assetProvider.notifier).deleteAssets(selection.value, force: true); + + if (isRemoved) { + if (context.mounted) { + ImmichToast.show( + context: context, + msg: 'assets_deleted_permanently'.tr(namedArgs: {'count': "${selection.value.length}"}), + gravity: ToastGravity.BOTTOM, + ); + } + } + } + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + handlePermanentDelete() async { + await showDialog( + context: context, + builder: (context) => DeleteDialog(alert: "delete_dialog_alert_remote", onDelete: () => onPermanentlyDelete()), + ); + } + + Future handleRestoreAll() async { + processing.value = true; + await ref.read(trashProvider.notifier).restoreTrash(); + processing.value = false; + selectionEnabledHook.value = false; + } + + Future handleRestore() async { + processing.value = true; + try { + if (selection.value.isNotEmpty) { + final result = await ref.read(trashProvider.notifier).restoreAssets(selection.value); + + if (result && context.mounted) { + ImmichToast.show( + context: context, + msg: 'assets_restored_successfully'.tr(namedArgs: {'count': "${selection.value.length}"}), + gravity: ToastGravity.BOTTOM, + ); + } + } + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + String getAppBarTitle(String count) { + if (selectionEnabledHook.value) { + return selection.value.isNotEmpty ? "${selection.value.length}" : "trash_page_select_assets_btn".tr(); + } + return 'trash_page_title'.tr(namedArgs: {'count': count}); + } + + AppBar buildAppBar(String count) { + return AppBar( + leading: IconButton( + onPressed: !selectionEnabledHook.value + ? () => context.maybePop() + : () { + selectionEnabledHook.value = false; + selection.value = {}; + }, + icon: !selectionEnabledHook.value + ? const Icon(Icons.arrow_back_ios_rounded) + : const Icon(Icons.close_rounded), + ), + centerTitle: !selectionEnabledHook.value, + automaticallyImplyLeading: false, + title: Text(getAppBarTitle(count)), + actions: [ + if (!selectionEnabledHook.value) + PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem(value: () => selectionEnabledHook.value = true, child: const Text('select').tr()), + PopupMenuItem(value: handleEmptyTrash, child: const Text('empty_trash').tr()), + ]; + }, + onSelected: (fn) => fn(), + ), + ], + ); + } + + Widget buildBottomBar() { + return SafeArea( + child: Align( + alignment: Alignment.bottomCenter, + child: SizedBox( + height: 64, + child: Container( + color: context.themeData.canvasColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + TextButton.icon( + icon: Icon(Icons.delete_forever, color: Colors.red[400]), + label: Text( + selection.value.isEmpty ? 'trash_page_delete_all'.tr() : 'delete'.tr(), + style: TextStyle(fontSize: 14, color: Colors.red[400], fontWeight: FontWeight.bold), + ), + onPressed: processing.value + ? null + : selection.value.isEmpty + ? handleEmptyTrash + : handlePermanentDelete, + ), + TextButton.icon( + icon: const Icon(Icons.history_rounded), + label: Text( + selection.value.isEmpty ? 'trash_page_restore_all'.tr() : 'restore'.tr(), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold), + ), + onPressed: processing.value + ? null + : selection.value.isEmpty + ? handleRestoreAll + : handleRestore, + ), + ], + ), + ), + ), + ), + ); + } + + return Scaffold( + appBar: trashRenderList.maybeWhen( + orElse: () => buildAppBar("?"), + data: (data) => buildAppBar(data.totalAssets.toString()), + ), + body: trashRenderList.widgetWhen( + onData: (data) => data.isEmpty + ? Center(child: Text('trash_page_no_assets'.tr())) + : Stack( + children: [ + SafeArea( + child: ImmichAssetGrid( + renderList: data, + listener: selectionListener, + selectionActive: selectionEnabledHook.value, + showMultiSelectIndicator: false, + showStack: true, + topWidget: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 24), + child: const Text("trash_page_info").tr(namedArgs: {"days": "$trashDays"}), + ), + ), + ), + if (selectionEnabledHook.value) buildBottomBar(), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/login/change_password.page.dart b/mobile/lib/pages/login/change_password.page.dart index 051f7f408c..248526df1b 100644 --- a/mobile/lib/pages/login/change_password.page.dart +++ b/mobile/lib/pages/login/change_password.page.dart @@ -1,11 +1,14 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/widgets/forms/change_password_form.dart'; @RoutePage() -class ChangePasswordPage extends StatelessWidget { +class ChangePasswordPage extends HookConsumerWidget { const ChangePasswordPage({super.key}); @override - Widget build(BuildContext context) => const Scaffold(body: ChangePasswordForm()); + Widget build(BuildContext context, WidgetRef ref) { + return const Scaffold(body: ChangePasswordForm()); + } } diff --git a/mobile/lib/pages/login/login.page.dart b/mobile/lib/pages/login/login.page.dart index 228fa1cc30..5f40b32baa 100644 --- a/mobile/lib/pages/login/login.page.dart +++ b/mobile/lib/pages/login/login.page.dart @@ -1,29 +1,28 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/forms/login/login_form.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:package_info_plus/package_info_plus.dart'; @RoutePage() -class LoginPage extends HookWidget { +class LoginPage extends HookConsumerWidget { const LoginPage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final appVersion = useState('0.0.0'); - Future getAppInfo() async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + getAppInfo() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); appVersion.value = packageInfo.version; } useEffect(() { - unawaited(getAppInfo()); + getAppInfo(); return null; }); @@ -56,7 +55,7 @@ class LoginPage extends HookWidget { ), ), onTap: () { - unawaited(context.pushRoute(const AppLogRoute())); + context.pushRoute(const AppLogRoute()); }, ), ], diff --git a/mobile/lib/pages/onboarding/permission_onboarding.page.dart b/mobile/lib/pages/onboarding/permission_onboarding.page.dart new file mode 100644 index 0000000000..52d4ac0125 --- /dev/null +++ b/mobile/lib/pages/onboarding/permission_onboarding.page.dart @@ -0,0 +1,141 @@ +import 'package:auto_route/auto_route.dart'; +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/gallery_permission.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/immich_logo.dart'; +import 'package:immich_mobile/widgets/common/immich_title_text.dart'; +import 'package:permission_handler/permission_handler.dart'; + +@RoutePage() +class PermissionOnboardingPage extends HookConsumerWidget { + const PermissionOnboardingPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final PermissionStatus permission = ref.watch(galleryPermissionNotifier); + + // Navigate to the main Tab Controller when permission is granted + void goToBackup() => context.replaceRoute(const BackupControllerRoute()); + + // When the permission is denied, we show a request permission page + buildRequestPermission() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('permission_onboarding_request', style: context.textTheme.titleMedium, textAlign: TextAlign.center).tr(), + const SizedBox(height: 18), + ElevatedButton( + onPressed: () => + ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission().then((permission) async { + if (permission.isGranted) { + // If permission is limited, we will show the limited + // permission page + goToBackup(); + } + }), + child: const Text('continue').tr(), + ), + ], + ); + } + + // When permission is granted from outside the app, this will show to + // let them continue on to the main timeline + buildPermissionGranted() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'permission_onboarding_permission_granted', + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ).tr(), + const SizedBox(height: 18), + ElevatedButton(onPressed: () => goToBackup(), child: const Text('permission_onboarding_get_started').tr()), + ], + ); + } + + // iOS 14+ has limited permission options, which let someone just share + // a few photos with the app. If someone only has limited permissions, we + // inform that Immich works best when given full permission + buildPermissionLimited() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.warning_outlined, color: Colors.yellow, size: 48), + const SizedBox(height: 8), + Text( + 'permission_onboarding_permission_limited', + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ).tr(), + const SizedBox(height: 18), + ElevatedButton( + onPressed: () => openAppSettings(), + child: const Text('permission_onboarding_go_to_settings').tr(), + ), + const SizedBox(height: 8.0), + TextButton(onPressed: () => goToBackup(), child: const Text('permission_onboarding_continue_anyway').tr()), + ], + ); + } + + buildPermissionDenied() { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.warning_outlined, color: Colors.red, size: 48), + const SizedBox(height: 8), + Text( + 'permission_onboarding_permission_denied', + style: context.textTheme.titleMedium, + textAlign: TextAlign.center, + ).tr(), + const SizedBox(height: 18), + ElevatedButton( + onPressed: () => openAppSettings(), + child: const Text('permission_onboarding_go_to_settings').tr(), + ), + ], + ); + } + + final Widget child = switch (permission) { + PermissionStatus.limited => buildPermissionLimited(), + PermissionStatus.denied => buildRequestPermission(), + PermissionStatus.granted || PermissionStatus.provisional => buildPermissionGranted(), + PermissionStatus.restricted || PermissionStatus.permanentlyDenied => buildPermissionDenied(), + }; + + return Scaffold( + body: SafeArea( + child: Center( + child: SizedBox( + width: 380, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const ImmichLogo(heroTag: 'logo'), + const ImmichTitleText(), + AnimatedSwitcher( + duration: const Duration(milliseconds: 500), + child: Padding(padding: const EdgeInsets.all(18.0), child: child), + ), + TextButton(child: const Text('back').tr(), onPressed: () => context.maybePop()), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/photos/memory.page.dart b/mobile/lib/pages/photos/memory.page.dart new file mode 100644 index 0000000000..bd7973bc21 --- /dev/null +++ b/mobile/lib/pages/photos/memory.page.dart @@ -0,0 +1,324 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; +import 'package:immich_mobile/widgets/memories/memory_bottom_info.dart'; +import 'package:immich_mobile/widgets/memories/memory_card.dart'; +import 'package:immich_mobile/widgets/memories/memory_epilogue.dart'; +import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart'; + +@RoutePage() +/// Expects [currentAssetProvider] to be set before navigating to this page +class MemoryPage extends HookConsumerWidget { + final List memories; + final int memoryIndex; + + const MemoryPage({required this.memories, required this.memoryIndex, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentMemory = useState(memories[memoryIndex]); + final currentAssetPage = useState(0); + final currentMemoryIndex = useState(memoryIndex); + final assetProgress = useState("${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"); + const bgColor = Colors.black; + final currentAsset = useState(null); + + /// The list of all of the asset page controllers + final memoryAssetPageControllers = List.generate(memories.length, (i) => usePageController()); + + /// The main vertically scrolling page controller with each list of memories + final memoryPageController = usePageController(initialPage: memoryIndex); + + useEffect(() { + // Memories is an immersive activity + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); + return () { + // Clean up to normal edge to edge when we are done + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + }; + }); + + toNextMemory() { + memoryPageController.nextPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn); + } + + void toPreviousMemory() { + if (currentMemoryIndex.value > 0) { + // Move to the previous memory page + memoryPageController.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn); + + // Wait for the next frame to ensure the page is built + SchedulerBinding.instance.addPostFrameCallback((_) { + final previousIndex = currentMemoryIndex.value - 1; + final previousMemoryController = memoryAssetPageControllers[previousIndex]; + + // Ensure the controller is attached + if (previousMemoryController.hasClients) { + previousMemoryController.jumpToPage(memories[previousIndex].assets.length - 1); + } else { + // Wait for the next frame until it is attached + SchedulerBinding.instance.addPostFrameCallback((_) { + if (previousMemoryController.hasClients) { + previousMemoryController.jumpToPage(memories[previousIndex].assets.length - 1); + } + }); + } + }); + } + } + + toNextAsset(int currentAssetIndex) { + if (currentAssetIndex + 1 < currentMemory.value.assets.length) { + // Go to the next asset + PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; + + controller.nextPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500)); + } else { + // Go to the next memory since we are at the end of our assets + toNextMemory(); + } + } + + toPreviousAsset(int currentAssetIndex) { + if (currentAssetIndex > 0) { + // Go to the previous asset + PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; + + controller.previousPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500)); + } else { + // Go to the previous memory since we are at the end of our assets + toPreviousMemory(); + } + } + + updateProgressText() { + assetProgress.value = "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"; + } + + /// Downloads and caches the image for the asset at this [currentMemory]'s index + precacheAsset(int index) async { + // Guard index out of range + if (index < 0) { + return; + } + + // Context might be removed due to popping out of Memory Lane during Scroll handling + if (!context.mounted) { + return; + } + + late Asset asset; + if (index < currentMemory.value.assets.length) { + // Uses the next asset in this current memory + asset = currentMemory.value.assets[index]; + } else { + // Precache the first asset in the next memory if available + final currentMemoryIndex = memories.indexOf(currentMemory.value); + + // Guard no memory found + if (currentMemoryIndex == -1) { + return; + } + + final nextMemoryIndex = currentMemoryIndex + 1; + // Guard no next memory + if (nextMemoryIndex >= memories.length) { + return; + } + + // Get the first asset from the next memory + asset = memories[nextMemoryIndex].assets.first; + } + + // Precache the asset + final size = MediaQuery.sizeOf(context); + await precacheImage( + ImmichImage.imageProvider(asset: asset, width: size.width, height: size.height), + context, + size: size, + ); + } + + // Precache the next page right away if we are on the first page + if (currentAssetPage.value == 0) { + Future.delayed(const Duration(milliseconds: 200)).then((_) => precacheAsset(1)); + } + + Future onAssetChanged(int otherIndex) async { + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + currentAssetPage.value = otherIndex; + updateProgressText(); + + // Wait for page change animation to finish + await Future.delayed(const Duration(milliseconds: 400)); + // And then precache the next asset + await precacheAsset(otherIndex + 1); + + final asset = currentMemory.value.assets[otherIndex]; + currentAsset.value = asset; + ref.read(currentAssetProvider.notifier).set(asset); + } + + /* Notification listener is used instead of OnPageChanged callback since OnPageChanged is called + * when the page in the **center** of the viewer changes. We want to reset currentAssetPage only when the final + * page during the end of scroll is different than the current page + */ + return NotificationListener( + onNotification: (ScrollNotification notification) { + // Calculate OverScroll manually using the number of pixels away from maxScrollExtent + // maxScrollExtend contains the sum of horizontal pixels of all assets for depth = 1 + // or sum of vertical pixels of all memories for depth = 0 + if (notification is ScrollUpdateNotification) { + final isEpiloguePage = (memoryPageController.page?.floor() ?? 0) >= memories.length; + + final offset = notification.metrics.pixels; + if (isEpiloguePage && (offset > notification.metrics.maxScrollExtent + 150)) { + context.maybePop(); + return true; + } + } + + return false; + }, + child: Scaffold( + backgroundColor: bgColor, + body: SafeArea( + child: PageView.builder( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + scrollDirection: Axis.vertical, + controller: memoryPageController, + onPageChanged: (pageNumber) { + ref.read(hapticFeedbackProvider.notifier).mediumImpact(); + if (pageNumber < memories.length) { + currentMemoryIndex.value = pageNumber; + currentMemory.value = memories[pageNumber]; + } + + currentAssetPage.value = 0; + + updateProgressText(); + }, + itemCount: memories.length + 1, + itemBuilder: (context, mIndex) { + // Build last page + if (mIndex == memories.length) { + return MemoryEpilogue( + onStartOver: () => memoryPageController.animateToPage( + 0, + duration: const Duration(seconds: 1), + curve: Curves.easeInOut, + ), + ); + } + // Build horizontal page + final assetController = memoryAssetPageControllers[mIndex]; + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 24.0, right: 24.0, top: 8.0, bottom: 2.0), + child: AnimatedBuilder( + animation: assetController, + builder: (context, child) { + double value = 0.0; + if (assetController.hasClients) { + // We can only access [page] if this has clients + value = assetController.page ?? 0; + } + return MemoryProgressIndicator( + ticks: memories[mIndex].assets.length, + value: (value + 1) / memories[mIndex].assets.length, + ); + }, + ), + ), + Expanded( + child: Stack( + children: [ + PageView.builder( + physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()), + controller: assetController, + onPageChanged: onAssetChanged, + scrollDirection: Axis.horizontal, + itemCount: memories[mIndex].assets.length, + itemBuilder: (context, index) { + final asset = memories[mIndex].assets[index]; + return Stack( + children: [ + Container( + color: Colors.black, + child: MemoryCard(asset: asset, title: memories[mIndex].title, showTitle: index == 0), + ), + Positioned.fill( + child: Row( + children: [ + // Left side of the screen + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + toPreviousAsset(index); + }, + ), + ), + + // Right side of the screen + Expanded( + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + toNextAsset(index); + }, + ), + ), + ], + ), + ), + ], + ); + }, + ), + Positioned( + top: 8, + left: 8, + child: MaterialButton( + minWidth: 0, + onPressed: () { + // auto_route doesn't invoke pop scope, so + // turn off full screen mode here + // https://github.com/Milad-Akarie/auto_route_library/issues/1799 + context.maybePop(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + }, + shape: const CircleBorder(), + color: Colors.white.withValues(alpha: 0.2), + elevation: 0, + child: const Icon(Icons.close_rounded, color: Colors.white), + ), + ), + if (currentAsset.value != null && currentAsset.value!.isVideo) + Positioned( + bottom: 24, + right: 32, + child: Icon(Icons.videocam_outlined, color: Colors.grey[200]), + ), + ], + ), + ), + MemoryBottomInfo(memory: memories[mIndex]), + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/photos/photos.page.dart b/mobile/lib/pages/photos/photos.page.dart new file mode 100644 index 0000000000..7f57247ec4 --- /dev/null +++ b/mobile/lib/pages/photos/photos.page.dart @@ -0,0 +1,130 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; +import 'package:immich_mobile/widgets/common/immich_app_bar.dart'; +import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; +import 'package:immich_mobile/widgets/memories/memory_lane.dart'; + +@RoutePage() +class PhotosPage extends HookConsumerWidget { + const PhotosPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentUser = ref.watch(currentUserProvider); + final timelineUsers = ref.watch(timelineUsersIdsProvider); + final tipOneOpacity = useState(0.0); + final refreshCount = useState(0); + + useEffect(() { + ref.read(websocketProvider.notifier).connect(); + Future(() => ref.read(assetProvider.notifier).getAllAsset()); + Future(() => ref.read(albumProvider.notifier).refreshRemoteAlbums()); + ref.read(serverInfoProvider.notifier).getServerInfo(); + + return; + }, []); + + Widget buildLoadingIndicator() { + Timer(const Duration(seconds: 2), () => tipOneOpacity.value = 1); + + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const ImmichLoadingIndicator(), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text( + 'home_page_building_timeline', + style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor), + ).tr(), + ), + const SizedBox(height: 8), + AnimatedOpacity( + duration: const Duration(milliseconds: 1000), + opacity: tipOneOpacity.value, + child: Column( + children: [ + SizedBox( + width: 320, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + 'home_page_first_time_notice', + textAlign: TextAlign.center, + style: context.textTheme.bodyMedium, + ).tr(), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Future refreshAssets() async { + final fullRefresh = refreshCount.value > 0; + + if (fullRefresh) { + unawaited( + Future.wait([ + ref.read(assetProvider.notifier).getAllAsset(clear: true), + ref.read(albumProvider.notifier).refreshRemoteAlbums(), + ]), + ); + + // refresh was forced: user requested another refresh within 2 seconds + refreshCount.value = 0; + } else { + await ref.read(assetProvider.notifier).getAllAsset(clear: false); + + refreshCount.value++; + // set counter back to 0 if user does not request refresh again + Timer(const Duration(seconds: 4), () => refreshCount.value = 0); + } + } + + return Stack( + children: [ + MultiselectGrid( + topWidget: (currentUser != null && currentUser.memoryEnabled) ? const MemoryLane() : const SizedBox(), + renderListProvider: timelineUsers.length > 1 + ? multiUsersTimelineProvider(timelineUsers) + : singleUserTimelineProvider(currentUser?.id), + buildLoadingIndicator: buildLoadingIndicator, + onRefresh: refreshAssets, + stackEnabled: true, + archiveEnabled: true, + editEnabled: true, + ), + AnimatedPositioned( + duration: const Duration(milliseconds: 300), + top: ref.watch(multiselectProvider) ? -(kToolbarHeight + context.padding.top) : 0, + left: 0, + right: 0, + child: Container( + height: kToolbarHeight + context.padding.top, + color: context.themeData.appBarTheme.backgroundColor, + child: const ImmichAppBar(), + ), + ), + ], + ); + } +} diff --git a/mobile/lib/pages/search/all_motion_videos.page.dart b/mobile/lib/pages/search/all_motion_videos.page.dart new file mode 100644 index 0000000000..60bb8a6cff --- /dev/null +++ b/mobile/lib/pages/search/all_motion_videos.page.dart @@ -0,0 +1,25 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/providers/search/all_motion_photos.provider.dart'; + +@RoutePage() +class AllMotionPhotosPage extends HookConsumerWidget { + const AllMotionPhotosPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final motionPhotos = ref.watch(allMotionPhotosProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('search_page_motion_photos').tr(), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + ), + body: motionPhotos.widgetWhen(onData: (assets) => ImmichAssetGrid(assets: assets)), + ); + } +} diff --git a/mobile/lib/pages/search/all_people.page.dart b/mobile/lib/pages/search/all_people.page.dart new file mode 100644 index 0000000000..b2814e6c13 --- /dev/null +++ b/mobile/lib/pages/search/all_people.page.dart @@ -0,0 +1,31 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/widgets/search/explore_grid.dart'; + +@RoutePage() +class AllPeoplePage extends HookConsumerWidget { + const AllPeoplePage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final curatedPeople = ref.watch(getAllPeopleProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('people').tr(), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + ), + body: curatedPeople.widgetWhen( + onData: (people) => ExploreGrid( + isPeople: true, + curatedContent: people.map((e) => SearchCuratedContent(label: e.name, id: e.id)).toList(), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/search/all_places.page.dart b/mobile/lib/pages/search/all_places.page.dart new file mode 100644 index 0000000000..c92f87d3ac --- /dev/null +++ b/mobile/lib/pages/search/all_places.page.dart @@ -0,0 +1,26 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/providers/search/search_page_state.provider.dart'; +import 'package:immich_mobile/widgets/search/explore_grid.dart'; + +@RoutePage() +class AllPlacesPage extends HookConsumerWidget { + const AllPlacesPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AsyncValue> places = ref.watch(getAllPlacesProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('places').tr(), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + ), + body: places.widgetWhen(onData: (data) => ExploreGrid(curatedContent: data)), + ); + } +} diff --git a/mobile/lib/pages/search/all_videos.page.dart b/mobile/lib/pages/search/all_videos.page.dart new file mode 100644 index 0000000000..acad043a58 --- /dev/null +++ b/mobile/lib/pages/search/all_videos.page.dart @@ -0,0 +1,22 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; + +@RoutePage() +class AllVideosPage extends HookConsumerWidget { + const AllVideosPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: const Text('videos').tr(), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + ), + body: MultiselectGrid(renderListProvider: allVideosTimelineProvider), + ); + } +} diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart new file mode 100644 index 0000000000..993b91d8f7 --- /dev/null +++ b/mobile/lib/pages/search/map/map.page.dart @@ -0,0 +1,384 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:geolocator/geolocator.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; +import 'package:immich_mobile/models/map/map_event.model.dart'; +import 'package:immich_mobile/models/map/map_marker.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/providers/map/map_marker.provider.dart'; +import 'package:immich_mobile/providers/map/map_state.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/debounce.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; +import 'package:immich_mobile/utils/map_utils.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/widgets/map/map_app_bar.dart'; +import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; +import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart'; +import 'package:immich_mobile/widgets/map/map_theme_override.dart'; +import 'package:immich_mobile/widgets/map/positioned_asset_marker_icon.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; + +@RoutePage() +class MapPage extends HookConsumerWidget { + const MapPage({super.key, this.initialLocation}); + final LatLng? initialLocation; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mapController = useRef(null); + final markers = useRef>([]); + final markersInBounds = useRef>([]); + final bottomSheetStreamController = useStreamController(); + final selectedMarker = useValueNotifier<_AssetMarkerMeta?>(null); + final assetsDebouncer = useDebouncer(); + final layerDebouncer = useDebouncer(interval: const Duration(seconds: 1)); + final isLoading = useProcessingOverlay(); + final scrollController = useScrollController(); + final markerDebouncer = useDebouncer(interval: const Duration(milliseconds: 800)); + final selectedAssets = useValueNotifier>({}); + const mapZoomToAssetLevel = 12.0; + + // updates the markersInBounds value with the map markers that are visible in the current + // map camera bounds + Future updateAssetsInBounds() async { + // Guard map not created + if (mapController.value == null) { + return; + } + + final bounds = await mapController.value!.getVisibleRegion(); + final inBounds = markers.value + .where((m) => bounds.contains(LatLng(m.latLng.latitude, m.latLng.longitude))) + .toList(); + // Notify bottom sheet to update asset grid only when there are new assets + if (markersInBounds.value.length != inBounds.length) { + bottomSheetStreamController.add(MapAssetsInBoundsUpdated(inBounds.map((e) => e.assetRemoteId).toList())); + } + markersInBounds.value = inBounds; + } + + // removes all sources and layers and re-adds them with the updated markers + Future reloadLayers() async { + if (mapController.value != null) { + layerDebouncer.run(() => mapController.value!.reloadAllLayersForMarkers(markers.value)); + } + } + + Future loadMarkers() async { + try { + isLoading.value = true; + markers.value = await ref.read(mapMarkersProvider.future); + assetsDebouncer.run(updateAssetsInBounds); + await reloadLayers(); + } finally { + isLoading.value = false; + } + } + + useEffect(() { + final currentAssetLink = ref.read(currentAssetProvider.notifier).ref.keepAlive(); + + loadMarkers(); + return currentAssetLink.close; + }, []); + + // Refetch markers when map state is changed + ref.listen(mapStateNotifierProvider, (_, current) { + if (current.shouldRefetchMarkers) { + markerDebouncer.run(() { + ref.invalidate(mapMarkersProvider); + // Reset marker + selectedMarker.value = null; + loadMarkers(); + ref.read(mapStateNotifierProvider.notifier).setRefetchMarkers(false); + }); + } + }); + + // updates the selected markers position based on the current map camera + Future updateAssetMarkerPosition(MapMarker marker, {bool shouldAnimate = true}) async { + final assetPoint = await mapController.value!.toScreenLocation(marker.latLng); + selectedMarker.value = _AssetMarkerMeta(point: assetPoint, marker: marker, shouldAnimate: shouldAnimate); + (assetPoint, marker, shouldAnimate); + } + + // finds the nearest asset marker from the tap point and store it as the selectedMarker + Future onMarkerClicked(Point point, LatLng _) async { + // Guard map not created + if (mapController.value == null) { + return; + } + final latlngBound = await mapController.value!.getBoundsFromPoint(point, 50); + final marker = markersInBounds.value.firstWhereOrNull( + (m) => latlngBound.contains(LatLng(m.latLng.latitude, m.latLng.longitude)), + ); + + if (marker != null) { + await updateAssetMarkerPosition(marker); + } else { + // If no asset was previously selected and no new asset is available, close the bottom sheet + if (selectedMarker.value == null) { + bottomSheetStreamController.add(const MapCloseBottomSheet()); + } + selectedMarker.value = null; + } + } + + void onMapCreated(MapLibreMapController controller) async { + mapController.value = controller; + controller.addListener(() { + if (controller.isCameraMoving && selectedMarker.value != null) { + updateAssetMarkerPosition(selectedMarker.value!.marker, shouldAnimate: false); + } + }); + } + + Future onMarkerTapped() async { + final assetId = selectedMarker.value?.marker.assetRemoteId; + if (assetId == null) { + return; + } + + final asset = await ref.read(dbProvider).assets.getByRemoteId(assetId); + if (asset == null) { + return; + } + + // Since we only have a single asset, we can just show GroupAssetBy.none + final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.none); + + ref.read(currentAssetProvider.notifier).set(asset); + if (asset.isVideo) { + ref.read(showControlsProvider.notifier).show = false; + } + unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); + } + + /// BOTTOM SHEET CALLBACKS + + Future onMapMoved() async { + assetsDebouncer.run(updateAssetsInBounds); + } + + void onBottomSheetScrolled(String assetRemoteId) { + final assetMarker = markersInBounds.value.firstWhereOrNull((m) => m.assetRemoteId == assetRemoteId); + if (assetMarker != null) { + updateAssetMarkerPosition(assetMarker); + } + } + + void onZoomToAsset(String assetRemoteId) { + final assetMarker = markersInBounds.value.firstWhereOrNull((m) => m.assetRemoteId == assetRemoteId); + if (mapController.value != null && assetMarker != null) { + // Offset the latitude a little to show the marker just above the viewports center + final offset = context.isMobile ? 0.02 : 0; + final latlng = LatLng(assetMarker.latLng.latitude - offset, assetMarker.latLng.longitude); + mapController.value!.animateCamera( + CameraUpdate.newLatLngZoom(latlng, mapZoomToAssetLevel), + duration: const Duration(milliseconds: 800), + ); + } + } + + void onZoomToLocation() async { + final (location, error) = await MapUtils.checkPermAndGetLocation(context: context); + if (error != null) { + if (error == LocationPermission.unableToDetermine && context.mounted) { + ImmichToast.show( + context: context, + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + msg: "map_cannot_get_user_location".tr(), + ); + } + return; + } + + if (mapController.value != null && location != null) { + await mapController.value!.animateCamera( + CameraUpdate.newLatLngZoom(LatLng(location.latitude, location.longitude), mapZoomToAssetLevel), + duration: const Duration(milliseconds: 800), + ); + } + } + + void onAssetsSelected(bool selected, Set selection) { + selectedAssets.value = selected ? selection : {}; + } + + return MapThemeOverride( + mapBuilder: (style) => context.isMobile + // Single-column + ? Scaffold( + extendBodyBehindAppBar: true, + appBar: MapAppBar(selectedAssets: selectedAssets), + body: Stack( + children: [ + _MapWithMarker( + initialLocation: initialLocation, + style: style, + selectedMarker: selectedMarker, + onMapCreated: onMapCreated, + onMapMoved: onMapMoved, + onMapClicked: onMarkerClicked, + onStyleLoaded: reloadLayers, + onMarkerTapped: onMarkerTapped, + ), + // Should be a part of the body and not scaffold::bottomsheet for the + // location button to be hit testable + MapBottomSheet( + mapEventStream: bottomSheetStreamController.stream, + onGridAssetChanged: onBottomSheetScrolled, + onZoomToAsset: onZoomToAsset, + onAssetsSelected: onAssetsSelected, + onZoomToLocation: onZoomToLocation, + selectedAssets: selectedAssets, + ), + ], + ), + ) + // Two-pane + : Row( + children: [ + Expanded( + child: Scaffold( + extendBodyBehindAppBar: true, + appBar: MapAppBar(selectedAssets: selectedAssets), + body: Stack( + children: [ + _MapWithMarker( + initialLocation: initialLocation, + style: style, + selectedMarker: selectedMarker, + onMapCreated: onMapCreated, + onMapMoved: onMapMoved, + onMapClicked: onMarkerClicked, + onStyleLoaded: reloadLayers, + onMarkerTapped: onMarkerTapped, + ), + Positioned( + right: 0, + bottom: context.padding.bottom + 16, + child: ElevatedButton( + onPressed: onZoomToLocation, + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.my_location), + ), + ), + ], + ), + ), + ), + Expanded( + child: LayoutBuilder( + builder: (ctx, constraints) => MapAssetGrid( + controller: scrollController, + mapEventStream: bottomSheetStreamController.stream, + onGridAssetChanged: onBottomSheetScrolled, + onZoomToAsset: onZoomToAsset, + onAssetsSelected: onAssetsSelected, + selectedAssets: selectedAssets, + ), + ), + ), + ], + ), + ); + } +} + +class _AssetMarkerMeta { + final Point point; + final MapMarker marker; + final bool shouldAnimate; + + const _AssetMarkerMeta({required this.point, required this.marker, required this.shouldAnimate}); + + @override + String toString() => '_AssetMarkerMeta(point: $point, marker: $marker, shouldAnimate: $shouldAnimate)'; +} + +class _MapWithMarker extends StatelessWidget { + final AsyncValue style; + final MapCreatedCallback onMapCreated; + final OnCameraIdleCallback onMapMoved; + final OnMapClickCallback onMapClicked; + final OnStyleLoadedCallback onStyleLoaded; + final Function()? onMarkerTapped; + final ValueNotifier<_AssetMarkerMeta?> selectedMarker; + final LatLng? initialLocation; + + const _MapWithMarker({ + required this.style, + required this.onMapCreated, + required this.onMapMoved, + required this.onMapClicked, + required this.onStyleLoaded, + required this.selectedMarker, + this.onMarkerTapped, + this.initialLocation, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (ctx, constraints) => SizedBox( + height: constraints.maxHeight, + width: constraints.maxWidth, + child: Stack( + children: [ + style.widgetWhen( + onData: (style) => MapLibreMap( + attributionButtonMargins: const Point(8, kToolbarHeight), + initialCameraPosition: CameraPosition( + target: initialLocation ?? const LatLng(0, 0), + zoom: initialLocation != null ? 12 : 0, + ), + styleString: style, + // This is needed to update the selectedMarker's position on map camera updates + // The changes are notified through the mapController ValueListener which is added in [onMapCreated] + trackCameraPosition: true, + onMapCreated: onMapCreated, + onCameraIdle: onMapMoved, + onMapClick: onMapClicked, + onStyleLoadedCallback: onStyleLoaded, + tiltGesturesEnabled: false, + dragEnabled: false, + myLocationEnabled: false, + attributionButtonPosition: AttributionButtonPosition.topRight, + rotateGesturesEnabled: false, + ), + ), + ValueListenableBuilder( + valueListenable: selectedMarker, + builder: (ctx, value, _) => value != null + ? PositionedAssetMarkerIcon( + point: value.point, + assetRemoteId: value.marker.assetRemoteId, + assetThumbhash: '', + durationInMilliseconds: value.shouldAnimate ? 100 : 0, + onTap: onMarkerTapped, + ) + : const SizedBox.shrink(), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/pages/search/map/map_location_picker.page.dart b/mobile/lib/pages/search/map/map_location_picker.page.dart index 314f2aba7d..3dace15ced 100644 --- a/mobile/lib/pages/search/map/map_location_picker.page.dart +++ b/mobile/lib/pages/search/map/map_location_picker.page.dart @@ -1,25 +1,25 @@ -import 'dart:async'; import 'dart:math'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/utils/map_utils.dart'; import 'package:immich_mobile/widgets/map/map_theme_override.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @RoutePage() -class MapLocationPickerPage extends HookWidget { +class MapLocationPickerPage extends HookConsumerWidget { final LatLng initialLatLng; const MapLocationPickerPage({super.key, this.initialLatLng = const LatLng(0, 0)}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final selectedLatLng = useValueNotifier(initialLatLng); final controller = useRef(null); final marker = useRef(null); @@ -37,17 +37,17 @@ class MapLocationPickerPage extends HookWidget { } void onClose([LatLng? selected]) { - unawaited(context.maybePop(selected)); + context.maybePop(selected); } Future getCurrentLocation() async { - final (currentLocation, _) = await MapUtils.checkPermAndGetLocation(context: context); + var (currentLocation, _) = await MapUtils.checkPermAndGetLocation(context: context); if (currentLocation == null) { return; } - final currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude); + var currentLatLng = LatLng(currentLocation.latitude, currentLocation.longitude); selectedLatLng.value = currentLatLng; await controller.value?.animateCamera(CameraUpdate.newLatLngZoom(currentLatLng, 12)); } @@ -140,7 +140,7 @@ class _BottomBar extends StatelessWidget { const SizedBox(width: 15), ValueListenableBuilder( valueListenable: selectedLatLng, - builder: (_, value, _) => + builder: (_, value, __) => Text("${value.latitude.toStringAsFixed(4)}, ${value.longitude.toStringAsFixed(4)}"), ), ], @@ -148,7 +148,10 @@ class _BottomBar extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - ElevatedButton(onPressed: onUseLocation, child: Text(context.t.map_location_picker_page_use_location)), + ElevatedButton( + onPressed: onUseLocation, + child: const Text("map_location_picker_page_use_location").tr(), + ), ElevatedButton(onPressed: onGetCurrentLocation, child: const Icon(Icons.my_location)), ], ), diff --git a/mobile/lib/pages/search/person_result.page.dart b/mobile/lib/pages/search/person_result.page.dart new file mode 100644 index 0000000000..8375eb14fd --- /dev/null +++ b/mobile/lib/pages/search/person_result.page.dart @@ -0,0 +1,101 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +@RoutePage() +class PersonResultPage extends HookConsumerWidget { + final String personId; + final String personName; + + const PersonResultPage({super.key, required this.personId, required this.personName}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final name = useState(personName); + + showEditNameDialog() { + showDialog( + context: context, + useRootNavigator: false, + builder: (BuildContext context) { + return PersonNameEditForm(personId: personId, personName: name.value); + }, + ).then((result) { + if (result != null && result.success) { + name.value = result.updatedName; + } + }); + } + + void buildBottomSheet() { + showModalBottomSheet( + backgroundColor: context.scaffoldBackgroundColor, + isScrollControlled: false, + context: context, + useSafeArea: true, + builder: (context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.edit_outlined), + title: const Text('edit_name', style: TextStyle(fontWeight: FontWeight.bold)).tr(), + onTap: showEditNameDialog, + ), + ], + ), + ); + }, + ); + } + + buildTitleBlock() { + return GestureDetector( + onTap: showEditNameDialog, + child: name.value.isEmpty + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('add_a_name', style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor)).tr(), + Text('find_them_fast', style: context.textTheme.labelLarge).tr(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [Text(name.value, style: context.textTheme.titleLarge, overflow: TextOverflow.ellipsis)], + ), + ); + } + + return Scaffold( + appBar: AppBar( + title: Text(name.value), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + actions: [IconButton(onPressed: buildBottomSheet, icon: const Icon(Icons.more_vert_rounded))], + ), + body: MultiselectGrid( + renderListProvider: personAssetsProvider(personId), + topWidget: Padding( + padding: const EdgeInsets.only(left: 8.0, top: 24), + child: Row( + children: [ + CircleAvatar(radius: 36, backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(personId))), + Expanded( + child: Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0), child: buildTitleBlock()), + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/pages/search/recently_taken.page.dart b/mobile/lib/pages/search/recently_taken.page.dart new file mode 100644 index 0000000000..988af2faf0 --- /dev/null +++ b/mobile/lib/pages/search/recently_taken.page.dart @@ -0,0 +1,25 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/providers/search/recently_taken_asset.provider.dart'; + +@RoutePage() +class RecentlyTakenPage extends HookConsumerWidget { + const RecentlyTakenPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final recents = ref.watch(recentlyTakenAssetProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('recently_taken_page_title').tr(), + leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.arrow_back_ios_rounded)), + ), + body: recents.widgetWhen(onData: (searchResponse) => ImmichAssetGrid(assets: searchResponse)), + ); + } +} diff --git a/mobile/lib/pages/search/search.page.dart b/mobile/lib/pages/search/search.page.dart new file mode 100644 index 0000000000..dbd32ac94b --- /dev/null +++ b/mobile/lib/pages/search/search.page.dart @@ -0,0 +1,760 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/person.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/providers/search/paginated_search.provider.dart'; +import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/asset_grid/multiselect_grid.dart'; +import 'package:immich_mobile/widgets/common/search_field.dart'; +import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart'; +import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart'; +import 'package:immich_mobile/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart'; +import 'package:immich_mobile/widgets/search/search_filter/location_picker.dart'; +import 'package:immich_mobile/widgets/search/search_filter/media_type_picker.dart'; +import 'package:immich_mobile/widgets/search/search_filter/people_picker.dart'; +import 'package:immich_mobile/widgets/search/search_filter/search_filter_chip.dart'; +import 'package:immich_mobile/widgets/search/search_filter/search_filter_utils.dart'; + +@RoutePage() +class SearchPage extends HookConsumerWidget { + const SearchPage({super.key, this.prefilter}); + + final SearchFilter? prefilter; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textSearchType = useState(TextSearchType.context); + final searchHintText = useState('sunrise_on_the_beach'.tr()); + final textSearchController = useTextEditingController(); + final filter = useState( + SearchFilter( + people: prefilter?.people ?? {}, + location: prefilter?.location ?? SearchLocationFilter(), + camera: prefilter?.camera ?? SearchCameraFilter(), + date: prefilter?.date ?? SearchDateFilter(), + display: prefilter?.display ?? SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + mediaType: prefilter?.mediaType ?? AssetType.other, + rating: prefilter?.rating ?? SearchRatingFilter(), + language: "${context.locale.languageCode}-${context.locale.countryCode}", + ), + ); + + final previousFilter = useState(null); + + final peopleCurrentFilterWidget = useState(null); + final dateRangeCurrentFilterWidget = useState(null); + final cameraCurrentFilterWidget = useState(null); + final locationCurrentFilterWidget = useState(null); + final mediaTypeCurrentFilterWidget = useState(null); + final displayOptionCurrentFilterWidget = useState(null); + + final isSearching = useState(false); + + SnackBar searchInfoSnackBar(String message) { + return SnackBar( + content: Text(message, style: context.textTheme.labelLarge), + showCloseIcon: true, + behavior: SnackBarBehavior.fixed, + closeIconColor: context.colorScheme.onSurface, + ); + } + + search() async { + if (filter.value.isEmpty) { + return; + } + + if (prefilter == null && filter.value == previousFilter.value) { + return; + } + + isSearching.value = true; + ref.watch(paginatedSearchProvider.notifier).clear(); + final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value); + + if (!hasResult) { + context.showSnackBar(searchInfoSnackBar('search_no_result'.tr())); + } + + previousFilter.value = filter.value; + isSearching.value = false; + } + + loadMoreSearchResult() async { + isSearching.value = true; + final hasResult = await ref.watch(paginatedSearchProvider.notifier).search(filter.value); + + if (!hasResult) { + context.showSnackBar(searchInfoSnackBar('search_no_more_result'.tr())); + } + + isSearching.value = false; + } + + searchPrefilter() { + if (prefilter != null) { + Future.delayed(Duration.zero, () { + search(); + + if (prefilter!.location.city != null) { + locationCurrentFilterWidget.value = Text(prefilter!.location.city!, style: context.textTheme.labelLarge); + } + }); + } + } + + useEffect(() { + Future.microtask(() => ref.invalidate(paginatedSearchProvider)); + searchPrefilter(); + + return null; + }, []); + + showPeoplePicker() { + handleOnSelect(Set value) { + filter.value = filter.value.copyWith(people: value); + + peopleCurrentFilterWidget.value = Text( + value.map((e) => e.name != '' ? e.name : 'no_name'.tr()).join(', '), + style: context.textTheme.labelLarge, + ); + } + + handleClear() { + filter.value = filter.value.copyWith(people: {}); + + peopleCurrentFilterWidget.value = null; + search(); + } + + showFilterBottomSheet( + context: context, + isScrollControlled: true, + child: FractionallySizedBox( + heightFactor: 0.8, + child: FilterBottomSheetScaffold( + title: 'search_filter_people_title'.tr(), + expanded: true, + onSearch: search, + onClear: handleClear, + child: PeoplePicker(onSelect: handleOnSelect, filter: filter.value.people), + ), + ), + ); + } + + showLocationPicker() { + handleOnSelect(Map value) { + filter.value = filter.value.copyWith( + location: SearchLocationFilter(country: value['country'], city: value['city'], state: value['state']), + ); + + final locationText = []; + if (value['country'] != null) { + locationText.add(value['country']!); + } + + if (value['state'] != null) { + locationText.add(value['state']!); + } + + if (value['city'] != null) { + locationText.add(value['city']!); + } + + locationCurrentFilterWidget.value = Text(locationText.join(', '), style: context.textTheme.labelLarge); + } + + handleClear() { + filter.value = filter.value.copyWith(location: SearchLocationFilter()); + + locationCurrentFilterWidget.value = null; + search(); + } + + showFilterBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: true, + child: FilterBottomSheetScaffold( + title: 'search_filter_location_title'.tr(), + onSearch: search, + onClear: handleClear, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Container( + padding: EdgeInsets.only(bottom: context.viewInsets.bottom), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: LocationPicker(onSelected: handleOnSelect, filter: filter.value.location), + ), + ), + ), + ), + ); + } + + showCameraPicker() { + handleOnSelect(Map value) { + filter.value = filter.value.copyWith( + camera: SearchCameraFilter(make: value['make'], model: value['model']), + ); + + cameraCurrentFilterWidget.value = Text( + '${value['make'] ?? ''} ${value['model'] ?? ''}', + style: context.textTheme.labelLarge, + ); + } + + handleClear() { + filter.value = filter.value.copyWith(camera: SearchCameraFilter()); + + cameraCurrentFilterWidget.value = null; + search(); + } + + showFilterBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: true, + child: FilterBottomSheetScaffold( + title: 'search_filter_camera_title'.tr(), + onSearch: search, + onClear: handleClear, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: CameraPicker(onSelect: handleOnSelect, filter: filter.value.camera), + ), + ), + ); + } + + showDatePicker() async { + final firstDate = DateTime(1900); + final lastDate = DateTime.now(); + + final date = await showDateRangePicker( + context: context, + firstDate: firstDate, + lastDate: lastDate, + currentDate: DateTime.now(), + initialDateRange: DateTimeRange( + start: filter.value.date.takenAfter ?? lastDate, + end: filter.value.date.takenBefore ?? lastDate, + ), + helpText: 'search_filter_date_title'.tr(), + cancelText: 'cancel'.tr(), + confirmText: 'select'.tr(), + saveText: 'save'.tr(), + errorFormatText: 'invalid_date_format'.tr(), + errorInvalidText: 'invalid_date'.tr(), + fieldStartHintText: 'start_date'.tr(), + fieldEndHintText: 'end_date'.tr(), + initialEntryMode: DatePickerEntryMode.calendar, + keyboardType: TextInputType.text, + ); + + if (date == null) { + filter.value = filter.value.copyWith(date: SearchDateFilter()); + + dateRangeCurrentFilterWidget.value = null; + unawaited(search()); + return; + } + + filter.value = filter.value.copyWith( + date: SearchDateFilter( + takenAfter: date.start, + takenBefore: date.end.add(const Duration(hours: 23, minutes: 59, seconds: 59)), + ), + ); + + // If date range is less than 24 hours, set the end date to the end of the day + if (date.end.difference(date.start).inHours < 24) { + dateRangeCurrentFilterWidget.value = Text( + DateFormat.yMMMd().format(date.start.toLocal()), + style: context.textTheme.labelLarge, + ); + } else { + dateRangeCurrentFilterWidget.value = Text( + 'search_filter_date_interval'.tr( + namedArgs: { + "start": DateFormat.yMMMd().format(date.start.toLocal()), + "end": DateFormat.yMMMd().format(date.end.toLocal()), + }, + ), + style: context.textTheme.labelLarge, + ); + } + + unawaited(search()); + } + + // MEDIA PICKER + showMediaTypePicker() { + handleOnSelected(AssetType assetType) { + filter.value = filter.value.copyWith(mediaType: assetType); + + mediaTypeCurrentFilterWidget.value = Text( + assetType == AssetType.image + ? 'image'.tr() + : assetType == AssetType.video + ? 'video'.tr() + : 'all'.tr(), + style: context.textTheme.labelLarge, + ); + } + + handleClear() { + filter.value = filter.value.copyWith(mediaType: AssetType.other); + + mediaTypeCurrentFilterWidget.value = null; + search(); + } + + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: 'search_filter_media_type_title'.tr(), + onSearch: search, + onClear: handleClear, + child: MediaTypePicker(onSelect: handleOnSelected, filter: filter.value.mediaType), + ), + ); + } + + // DISPLAY OPTION + showDisplayOptionPicker() { + handleOnSelect(Map value) { + final filterText = []; + value.forEach((key, value) { + switch (key) { + case DisplayOption.notInAlbum: + filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isNotInAlbum: value)); + if (value) { + filterText.add('search_filter_display_option_not_in_album'.tr()); + } + break; + case DisplayOption.archive: + filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isArchive: value)); + if (value) { + filterText.add('archive'.tr()); + } + break; + case DisplayOption.favorite: + filter.value = filter.value.copyWith(display: filter.value.display.copyWith(isFavorite: value)); + if (value) { + filterText.add('favorite'.tr()); + } + break; + } + }); + + if (filterText.isEmpty) { + displayOptionCurrentFilterWidget.value = null; + return; + } + + displayOptionCurrentFilterWidget.value = Text(filterText.join(', '), style: context.textTheme.labelLarge); + } + + handleClear() { + filter.value = filter.value.copyWith( + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + ); + + displayOptionCurrentFilterWidget.value = null; + search(); + } + + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: 'display_options'.tr(), + onSearch: search, + onClear: handleClear, + child: DisplayOptionPicker(onSelect: handleOnSelect, filter: filter.value.display), + ), + ); + } + + handleTextSubmitted(String value) { + switch (textSearchType.value) { + case TextSearchType.context: + filter.value = filter.value.copyWith(filename: '', context: value, description: '', ocr: ''); + + break; + case TextSearchType.filename: + filter.value = filter.value.copyWith(filename: value, context: '', description: '', ocr: ''); + + break; + case TextSearchType.description: + filter.value = filter.value.copyWith(filename: '', context: '', description: value, ocr: ''); + break; + case TextSearchType.ocr: + filter.value = filter.value.copyWith(filename: '', context: '', description: '', ocr: value); + break; + } + + search(); + } + + IconData getSearchPrefixIcon() => switch (textSearchType.value) { + TextSearchType.context => Icons.image_search_rounded, + TextSearchType.filename => Icons.abc_rounded, + TextSearchType.description => Icons.text_snippet_outlined, + TextSearchType.ocr => Icons.document_scanner_outlined, + }; + + return Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + automaticallyImplyLeading: true, + actions: [ + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: MenuAnchor( + style: MenuStyle( + elevation: const WidgetStatePropertyAll(1), + shape: WidgetStateProperty.all( + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), + ), + padding: const WidgetStatePropertyAll(EdgeInsets.all(4)), + ), + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert_rounded), + tooltip: 'show_text_search_menu'.tr(), + ); + }, + menuChildren: [ + MenuItemButton( + child: ListTile( + leading: const Icon(Icons.image_search_rounded), + title: Text( + 'search_by_context'.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.context ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.context, + ), + onPressed: () { + textSearchType.value = TextSearchType.context; + searchHintText.value = 'sunrise_on_the_beach'.tr(); + }, + ), + MenuItemButton( + child: ListTile( + leading: const Icon(Icons.abc_rounded), + title: Text( + 'search_filter_filename'.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.filename ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.filename, + ), + onPressed: () { + textSearchType.value = TextSearchType.filename; + searchHintText.value = 'file_name_or_extension'.tr(); + }, + ), + MenuItemButton( + child: ListTile( + leading: const Icon(Icons.text_snippet_outlined), + title: Text( + 'search_by_description'.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.description ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.description, + ), + onPressed: () { + textSearchType.value = TextSearchType.description; + searchHintText.value = 'search_by_description_example'.tr(); + }, + ), + MenuItemButton( + child: ListTile( + leading: const Icon(Icons.document_scanner_outlined), + title: Text( + 'search_filter_ocr'.tr(), + style: context.textTheme.bodyLarge?.copyWith( + fontWeight: FontWeight.w500, + color: textSearchType.value == TextSearchType.ocr ? context.colorScheme.primary : null, + ), + ), + selectedColor: context.colorScheme.primary, + selected: textSearchType.value == TextSearchType.ocr, + ), + onPressed: () { + textSearchType.value = TextSearchType.ocr; + searchHintText.value = 'search_by_ocr_example'.tr(); + }, + ), + ], + ), + ), + ], + title: Container( + decoration: BoxDecoration( + border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0), + borderRadius: const BorderRadius.all(Radius.circular(24)), + gradient: LinearGradient( + colors: [ + context.colorScheme.primary.withValues(alpha: 0.075), + context.colorScheme.primary.withValues(alpha: 0.09), + context.colorScheme.primary.withValues(alpha: 0.075), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: SearchField( + hintText: searchHintText.value, + key: const Key('search_text_field'), + controller: textSearchController, + contentPadding: prefilter != null ? const EdgeInsets.only(left: 24) : const EdgeInsets.all(8), + prefixIcon: prefilter != null ? null : Icon(getSearchPrefixIcon(), color: context.colorScheme.primary), + onSubmitted: handleTextSubmitted, + focusNode: ref.watch(searchInputFocusProvider), + ), + ), + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 12.0), + child: SizedBox( + height: 50, + child: ListView( + key: const Key('search_filter_chip_list'), + shrinkWrap: true, + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + children: [ + SearchFilterChip( + icon: Icons.people_alt_outlined, + onTap: showPeoplePicker, + label: 'people'.tr(), + currentFilter: peopleCurrentFilterWidget.value, + ), + SearchFilterChip( + icon: Icons.location_on_outlined, + onTap: showLocationPicker, + label: 'search_filter_location'.tr(), + currentFilter: locationCurrentFilterWidget.value, + ), + SearchFilterChip( + icon: Icons.camera_alt_outlined, + onTap: showCameraPicker, + label: 'camera'.tr(), + currentFilter: cameraCurrentFilterWidget.value, + ), + SearchFilterChip( + icon: Icons.date_range_outlined, + onTap: showDatePicker, + label: 'search_filter_date'.tr(), + currentFilter: dateRangeCurrentFilterWidget.value, + ), + SearchFilterChip( + key: const Key('media_type_chip'), + icon: Icons.video_collection_outlined, + onTap: showMediaTypePicker, + label: 'search_filter_media_type'.tr(), + currentFilter: mediaTypeCurrentFilterWidget.value, + ), + SearchFilterChip( + icon: Icons.display_settings_outlined, + onTap: showDisplayOptionPicker, + label: 'search_filter_display_options'.tr(), + currentFilter: displayOptionCurrentFilterWidget.value, + ), + ], + ), + ), + ), + if (isSearching.value) + const Expanded(child: Center(child: CircularProgressIndicator())) + else + SearchResultGrid(onScrollEnd: loadMoreSearchResult, isSearching: isSearching.value), + ], + ), + ); + } +} + +class SearchResultGrid extends StatelessWidget { + final VoidCallback onScrollEnd; + final bool isSearching; + + const SearchResultGrid({super.key, required this.onScrollEnd, this.isSearching = false}); + + @override + Widget build(BuildContext context) { + return Expanded( + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: NotificationListener( + onNotification: (notification) { + final isBottomSheetNotification = + notification.context?.findAncestorWidgetOfExactType() != null; + + final metrics = notification.metrics; + final isVerticalScroll = metrics.axis == Axis.vertical; + + if (metrics.pixels >= metrics.maxScrollExtent && isVerticalScroll && !isBottomSheetNotification) { + onScrollEnd(); + } + + return true; + }, + child: MultiselectGrid( + renderListProvider: paginatedSearchRenderListProvider, + archiveEnabled: true, + deleteEnabled: true, + editEnabled: true, + favoriteEnabled: true, + stackEnabled: false, + dragScrollLabelEnabled: false, + emptyIndicator: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: !isSearching ? const SearchEmptyContent() : const SizedBox.shrink(), + ), + ), + ), + ), + ); + } +} + +class SearchEmptyContent extends StatelessWidget { + const SearchEmptyContent({super.key}); + + @override + Widget build(BuildContext context) { + return NotificationListener( + onNotification: (_) => true, + child: ListView( + shrinkWrap: false, + children: [ + const SizedBox(height: 40), + Center( + child: Image.asset( + context.isDarkTheme ? 'assets/polaroid-dark.png' : 'assets/polaroid-light.png', + height: 125, + ), + ), + const SizedBox(height: 16), + Center(child: Text('search_page_search_photos_videos'.tr(), style: context.textTheme.labelLarge)), + const SizedBox(height: 32), + const QuickLinkList(), + ], + ), + ); + } +} + +class QuickLinkList extends StatelessWidget { + const QuickLinkList({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(20)), + border: Border.all(color: context.colorScheme.outline.withAlpha(10), width: 1), + gradient: LinearGradient( + colors: [ + context.colorScheme.primary.withAlpha(10), + context.colorScheme.primary.withAlpha(15), + context.colorScheme.primary.withAlpha(20), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + QuickLink( + title: 'recently_taken'.tr(), + icon: Icons.schedule_outlined, + isTop: true, + onTap: () => context.pushRoute(const RecentlyTakenRoute()), + ), + QuickLink( + title: 'videos'.tr(), + icon: Icons.play_circle_outline_rounded, + onTap: () => context.pushRoute(const AllVideosRoute()), + ), + QuickLink( + title: 'favorites'.tr(), + icon: Icons.favorite_border_rounded, + isBottom: true, + onTap: () => context.pushRoute(const FavoritesRoute()), + ), + ], + ), + ); + } +} + +class QuickLink extends StatelessWidget { + final String title; + final IconData icon; + final VoidCallback onTap; + final bool isTop; + final bool isBottom; + + const QuickLink({ + super.key, + required this.title, + required this.icon, + required this.onTap, + this.isTop = false, + this.isBottom = false, + }); + + @override + Widget build(BuildContext context) { + final borderRadius = BorderRadius.only( + topLeft: Radius.circular(isTop ? 20 : 0), + topRight: Radius.circular(isTop ? 20 : 0), + bottomLeft: Radius.circular(isBottom ? 20 : 0), + bottomRight: Radius.circular(isBottom ? 20 : 0), + ); + + return ListTile( + shape: RoundedRectangleBorder(borderRadius: borderRadius), + leading: Icon(icon, size: 26), + title: Text(title, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500)), + onTap: onTap, + ); + } +} diff --git a/mobile/lib/pages/settings/sync_status.page.dart b/mobile/lib/pages/settings/sync_status.page.dart index 6a0af1230d..58750e9e30 100644 --- a/mobile/lib/pages/settings/sync_status.page.dart +++ b/mobile/lib/pages/settings/sync_status.page.dart @@ -1,6 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart'; @RoutePage() @@ -12,7 +12,7 @@ class SyncStatusPage extends StatelessWidget { return Scaffold( appBar: AppBar( elevation: 0, - title: Text(context.t.sync_status), + title: const Text("sync_status").t(context: context), leading: IconButton( onPressed: () => context.maybePop(true), splashRadius: 24, diff --git a/mobile/lib/pages/share_intent/share_intent.page.dart b/mobile/lib/pages/share_intent/share_intent.page.dart index 5b651fbc8f..2be51fbfc9 100644 --- a/mobile/lib/pages/share_intent/share_intent.page.dart +++ b/mobile/lib/pages/share_intent/share_intent.page.dart @@ -1,10 +1,9 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.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'; @@ -37,7 +36,7 @@ class ShareIntentPage extends ConsumerWidget { ref.read(shareIntentUploadProvider.notifier).addAttachments(attachments); } - Future upload() async { + void upload() async { final files = candidates.map((candidate) => candidate.file).toList(); await ref.read(shareIntentUploadProvider.notifier).uploadAll(files); } @@ -58,7 +57,7 @@ class ShareIntentPage extends ConsumerWidget { appBar: AppBar( title: Column( children: [ - Text(context.t.upload_to_immich(count: candidates.length)), + const Text('upload_to_immich').tr(namedArgs: {'count': candidates.length.toString()}), Text( currentEndpoint, style: context.textTheme.labelMedium?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)), @@ -67,7 +66,7 @@ class ShareIntentPage extends ConsumerWidget { ), leading: IconButton( onPressed: () { - unawaited(context.navigateTo(const TabShellRoute())); + context.navigateTo(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute()); }, icon: const Icon(Icons.arrow_back), ), @@ -104,7 +103,7 @@ class ShareIntentPage extends ConsumerWidget { Icons.image, color: Colors.white, size: 20, - shadows: [Shadow(offset: Offset.zero, blurRadius: 8.0, color: Colors.black45)], + shadows: [Shadow(offset: Offset(0, 0), blurRadius: 8.0, color: Colors.black45)], ), ), ], @@ -130,7 +129,7 @@ class ShareIntentPage extends ConsumerWidget { height: 48, child: ElevatedButton( onPressed: (isUploading || isUploaded) ? null : upload, - child: (isUploading || isUploaded) ? UploadingText(candidates: candidates) : Text(context.t.upload), + child: (isUploading || isUploaded) ? UploadingText(candidates: candidates) : const Text('upload').tr(), ), ), ), @@ -149,7 +148,9 @@ class UploadingText extends StatelessWidget { return element.status == UploadStatus.complete; }).length; - return Text(context.t.shared_intent_upload_button_progress_text(current: uploadedCount, total: candidates.length)); + return const Text( + "shared_intent_upload_button_progress_text", + ).tr(namedArgs: {'current': uploadedCount.toString(), 'total': candidates.length.toString()}); } } @@ -166,7 +167,7 @@ class UploadStatusIcon extends StatelessWidget { return Icon( Icons.check_circle_outline_rounded, color: context.colorScheme.onSurface.withAlpha(100), - semanticLabel: context.t.not_selected, + semanticLabel: 'not_selected'.tr(), ); } @@ -174,7 +175,7 @@ class UploadStatusIcon extends StatelessWidget { UploadStatus.enqueued => Icon( Icons.check_circle_rounded, color: context.primaryColor, - semanticLabel: context.t.enqueued, + semanticLabel: 'enqueued'.tr(), ), UploadStatus.running => Stack( alignment: AlignmentDirectional.center, @@ -189,7 +190,7 @@ class UploadStatusIcon extends StatelessWidget { backgroundColor: context.colorScheme.surfaceContainerLow, strokeWidth: 3, value: value, - semanticsLabel: context.t.uploading, + semanticsLabel: 'uploading'.tr(), ), ), ), @@ -199,12 +200,8 @@ class UploadStatusIcon extends StatelessWidget { ), ], ), - UploadStatus.complete => Icon( - Icons.check_circle_rounded, - color: Colors.green, - semanticLabel: context.t.completed, - ), - UploadStatus.failed => Icon(Icons.error_rounded, color: Colors.red, semanticLabel: context.t.failed), + UploadStatus.complete => Icon(Icons.check_circle_rounded, color: Colors.green, semanticLabel: 'completed'.tr()), + UploadStatus.failed => Icon(Icons.error_rounded, color: Colors.red, semanticLabel: 'failed'.tr()), }; return statusIcon; diff --git a/mobile/lib/platform/background_worker_api.g.dart b/mobile/lib/platform/background_worker_api.g.dart new file mode 100644 index 0000000000..e8c87aa1a4 --- /dev/null +++ b/mobile/lib/platform/background_worker_api.g.dart @@ -0,0 +1,367 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every( + (MapEntry entry) => + (b as Map).containsKey(entry.key) && _deepEquals(entry.value, b[entry.key]), + ); + } + return a == b; +} + +class BackgroundWorkerSettings { + BackgroundWorkerSettings({required this.requiresCharging, required this.minimumDelaySeconds}); + + bool requiresCharging; + + int minimumDelaySeconds; + + List _toList() { + return [requiresCharging, minimumDelaySeconds]; + } + + Object encode() { + return _toList(); + } + + static BackgroundWorkerSettings decode(Object result) { + result as List; + return BackgroundWorkerSettings(requiresCharging: result[0]! as bool, minimumDelaySeconds: result[1]! as int); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! BackgroundWorkerSettings || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is BackgroundWorkerSettings) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return BackgroundWorkerSettings.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class BackgroundWorkerFgHostApi { + /// Constructor for [BackgroundWorkerFgHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + BackgroundWorkerFgHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future enable() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.enable$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future saveNotificationMessage(String title, String body) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.saveNotificationMessage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([title, body]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future configure(BackgroundWorkerSettings settings) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.configure$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([settings]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future disable() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFgHostApi.disable$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } +} + +class BackgroundWorkerBgHostApi { + /// Constructor for [BackgroundWorkerBgHostApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + BackgroundWorkerBgHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future onInitialized() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.onInitialized$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future close() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerBgHostApi.close$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } +} + +abstract class BackgroundWorkerFlutterApi { + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + Future onIosUpload(bool isRefresh, int? maxSeconds); + + Future onAndroidUpload(); + + Future cancel(); + + static void setUp( + BackgroundWorkerFlutterApi? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + assert( + message != null, + 'Argument for dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload was null.', + ); + final List args = (message as List?)!; + final bool? arg_isRefresh = (args[0] as bool?); + assert( + arg_isRefresh != null, + 'Argument for dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onIosUpload was null, expected non-null bool.', + ); + final int? arg_maxSeconds = (args[1] as int?); + try { + await api.onIosUpload(arg_isRefresh!, arg_maxSeconds); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.onAndroidUpload$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + try { + await api.onAndroidUpload(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerFlutterApi.cancel$messageChannelSuffix', + pigeonChannelCodec, + binaryMessenger: binaryMessenger, + ); + if (api == null) { + pigeonVar_channel.setMessageHandler(null); + } else { + pigeonVar_channel.setMessageHandler((Object? message) async { + try { + await api.cancel(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString()), + ); + } + }); + } + } + } +} diff --git a/mobile/lib/platform/background_worker_lock_api.g.dart b/mobile/lib/platform/background_worker_lock_api.g.dart new file mode 100644 index 0000000000..93852d2564 --- /dev/null +++ b/mobile/lib/platform/background_worker_lock_api.g.dart @@ -0,0 +1,97 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class BackgroundWorkerLockApi { + /// Constructor for [BackgroundWorkerLockApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + BackgroundWorkerLockApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future lock() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.lock$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future unlock() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.BackgroundWorkerLockApi.unlock$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } +} diff --git a/mobile/lib/platform/connectivity_api.g.dart b/mobile/lib/platform/connectivity_api.g.dart new file mode 100644 index 0000000000..0422d87438 --- /dev/null +++ b/mobile/lib/platform/connectivity_api.g.dart @@ -0,0 +1,87 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +enum NetworkCapability { cellular, wifi, vpn, unmetered } + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is NetworkCapability) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : NetworkCapability.values[value]; + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ConnectivityApi { + /// Constructor for [ConnectivityApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + ConnectivityApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future> getCapabilities() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ConnectivityApi.getCapabilities$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } +} diff --git a/mobile/lib/platform/local_image_api.g.dart b/mobile/lib/platform/local_image_api.g.dart new file mode 100644 index 0000000000..f23cb86ced --- /dev/null +++ b/mobile/lib/platform/local_image_api.g.dart @@ -0,0 +1,139 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class LocalImageApi { + /// Constructor for [LocalImageApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + LocalImageApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future?> requestImage( + String assetId, { + required int requestId, + required int width, + required int height, + required bool isVideo, + required bool preferEncoded, + }) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.LocalImageApi.requestImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([ + assetId, + requestId, + width, + height, + isVideo, + preferEncoded, + ]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return (pigeonVar_replyList[0] as Map?)?.cast(); + } + } + + Future cancelRequest(int requestId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.LocalImageApi.cancelRequest$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([requestId]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future> getThumbhash(String thumbhash) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.LocalImageApi.getThumbhash$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([thumbhash]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 Map?)!.cast(); + } + } +} diff --git a/mobile/lib/platform/native_sync_api.g.dart b/mobile/lib/platform/native_sync_api.g.dart new file mode 100644 index 0000000000..6681912c2f --- /dev/null +++ b/mobile/lib/platform/native_sync_api.g.dart @@ -0,0 +1,704 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every( + (MapEntry entry) => + (b as Map).containsKey(entry.key) && _deepEquals(entry.value, b[entry.key]), + ); + } + return a == b; +} + +enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping } + +class PlatformAsset { + PlatformAsset({ + required this.id, + required this.name, + required this.type, + this.createdAt, + this.updatedAt, + this.width, + this.height, + required this.durationInSeconds, + required this.orientation, + required this.isFavorite, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + + String id; + + String name; + + int type; + + int? createdAt; + + int? updatedAt; + + int? width; + + int? height; + + int durationInSeconds; + + int orientation; + + bool isFavorite; + + int? adjustmentTime; + + double? latitude; + + double? longitude; + + PlatformAssetPlaybackStyle playbackStyle; + + List _toList() { + return [ + id, + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + orientation, + isFavorite, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + } + + Object encode() { + return _toList(); + } + + static PlatformAsset decode(Object result) { + result as List; + return PlatformAsset( + id: result[0]! as String, + name: result[1]! as String, + type: result[2]! as int, + createdAt: result[3] as int?, + updatedAt: result[4] as int?, + width: result[5] as int?, + height: result[6] as int?, + durationInSeconds: result[7]! as int, + orientation: result[8]! as int, + isFavorite: result[9]! as bool, + adjustmentTime: result[10] as int?, + latitude: result[11] as double?, + longitude: result[12] as double?, + playbackStyle: result[13]! as PlatformAssetPlaybackStyle, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PlatformAsset || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class PlatformAlbum { + PlatformAlbum({ + required this.id, + required this.name, + this.updatedAt, + required this.isCloud, + required this.assetCount, + }); + + String id; + + String name; + + int? updatedAt; + + bool isCloud; + + int assetCount; + + List _toList() { + return [id, name, updatedAt, isCloud, assetCount]; + } + + Object encode() { + return _toList(); + } + + static PlatformAlbum decode(Object result) { + result as List; + return PlatformAlbum( + id: result[0]! as String, + name: result[1]! as String, + updatedAt: result[2] as int?, + isCloud: result[3]! as bool, + assetCount: result[4]! as int, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! PlatformAlbum || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class SyncDelta { + SyncDelta({required this.hasChanges, required this.updates, required this.deletes, required this.assetAlbums}); + + bool hasChanges; + + List updates; + + List deletes; + + Map> assetAlbums; + + List _toList() { + return [hasChanges, updates, deletes, assetAlbums]; + } + + Object encode() { + return _toList(); + } + + static SyncDelta decode(Object result) { + result as List; + return SyncDelta( + hasChanges: result[0]! as bool, + updates: (result[1] as List?)!.cast(), + deletes: (result[2] as List?)!.cast(), + assetAlbums: (result[3] as Map?)!.cast>(), + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! SyncDelta || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class HashResult { + HashResult({required this.assetId, this.error, this.hash}); + + String assetId; + + String? error; + + String? hash; + + List _toList() { + return [assetId, error, hash]; + } + + Object encode() { + return _toList(); + } + + static HashResult decode(Object result) { + result as List; + return HashResult(assetId: result[0]! as String, error: result[1] as String?, hash: result[2] as String?); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! HashResult || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class CloudIdResult { + CloudIdResult({required this.assetId, this.error, this.cloudId}); + + String assetId; + + String? error; + + String? cloudId; + + List _toList() { + return [assetId, error, cloudId]; + } + + Object encode() { + return _toList(); + } + + static CloudIdResult decode(Object result) { + result as List; + return CloudIdResult(assetId: result[0]! as String, error: result[1] as String?, cloudId: result[2] as String?); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! CloudIdResult || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is PlatformAssetPlaybackStyle) { + buffer.putUint8(129); + writeValue(buffer, value.index); + } else if (value is PlatformAsset) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is PlatformAlbum) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is SyncDelta) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else if (value is HashResult) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is CloudIdResult) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformAssetPlaybackStyle.values[value]; + case 130: + return PlatformAsset.decode(readValue(buffer)!); + case 131: + return PlatformAlbum.decode(readValue(buffer)!); + case 132: + return SyncDelta.decode(readValue(buffer)!); + case 133: + return HashResult.decode(readValue(buffer)!); + case 134: + return CloudIdResult.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class NativeSyncApi { + /// Constructor for [NativeSyncApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + NativeSyncApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future shouldFullSync() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.shouldFullSync$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 bool?)!; + } + } + + Future getMediaChanges() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getMediaChanges$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 SyncDelta?)!; + } + } + + Future checkpointSync() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.checkpointSync$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future clearSyncCheckpoint() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.clearSyncCheckpoint$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future> getAssetIdsForAlbum(String albumId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetIdsForAlbum$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([albumId]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } + + Future> getAlbums() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAlbums$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } + + Future getAssetsCountSince(String albumId, int timestamp) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsCountSince$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([albumId, timestamp]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 int?)!; + } + } + + Future> getAssetsForAlbum(String albumId, {int? updatedTimeCond}) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getAssetsForAlbum$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([albumId, updatedTimeCond]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } + + Future> hashAssets(List assetIds, {bool allowNetworkAccess = false}) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.hashAssets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([assetIds, allowNetworkAccess]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } + + Future cancelHashing() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.cancelHashing$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future>> getTrashedAssets() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getTrashedAssets$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 Map?)!.cast>(); + } + } + + Future> getCloudIdForAssetIds(List assetIds) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([assetIds]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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?)!.cast(); + } + } +} diff --git a/mobile/lib/platform/network_api.g.dart b/mobile/lib/platform/network_api.g.dart new file mode 100644 index 0000000000..0ecbb430d3 --- /dev/null +++ b/mobile/lib/platform/network_api.g.dart @@ -0,0 +1,306 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +bool _deepEquals(Object? a, Object? b) { + if (a is List && b is List) { + return a.length == b.length && a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1])); + } + if (a is Map && b is Map) { + return a.length == b.length && + a.entries.every( + (MapEntry entry) => + (b as Map).containsKey(entry.key) && _deepEquals(entry.value, b[entry.key]), + ); + } + return a == b; +} + +class ClientCertData { + ClientCertData({required this.data, required this.password}); + + Uint8List data; + + String password; + + List _toList() { + return [data, password]; + } + + Object encode() { + return _toList(); + } + + static ClientCertData decode(Object result) { + result as List; + return ClientCertData(data: result[0]! as Uint8List, password: result[1]! as String); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! ClientCertData || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class ClientCertPrompt { + ClientCertPrompt({required this.title, required this.message, required this.cancel, required this.confirm}); + + String title; + + String message; + + String cancel; + + String confirm; + + List _toList() { + return [title, message, cancel, confirm]; + } + + Object encode() { + return _toList(); + } + + static ClientCertPrompt decode(Object result) { + result as List; + return ClientCertPrompt( + title: result[0]! as String, + message: result[1]! as String, + cancel: result[2]! as String, + confirm: result[3]! as String, + ); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + bool operator ==(Object other) { + if (other is! ClientCertPrompt || other.runtimeType != runtimeType) { + return false; + } + if (identical(this, other)) { + return true; + } + return _deepEquals(encode(), other.encode()); + } + + @override + // ignore: avoid_equals_and_hash_code_on_mutable_classes + int get hashCode => Object.hashAll(_toList()); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is ClientCertData) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is ClientCertPrompt) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 129: + return ClientCertData.decode(readValue(buffer)!); + case 130: + return ClientCertPrompt.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +class NetworkApi { + /// Constructor for [NetworkApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + NetworkApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future addCertificate(ClientCertData clientData) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.addCertificate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([clientData]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future selectCertificate(ClientCertPrompt promptText) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.selectCertificate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([promptText]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future removeCertificate() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.removeCertificate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future hasCertificate() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.hasCertificate$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 bool?)!; + } + } + + Future getClientPointer() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.getClientPointer$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 int?)!; + } + } + + Future setRequestHeaders(Map headers, List serverUrls, String? token) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.NetworkApi.setRequestHeaders$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([headers, serverUrls, token]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } +} diff --git a/mobile/lib/platform/remote_image_api.g.dart b/mobile/lib/platform/remote_image_api.g.dart new file mode 100644 index 0000000000..474f033f1f --- /dev/null +++ b/mobile/lib/platform/remote_image_api.g.dart @@ -0,0 +1,125 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class RemoteImageApi { + /// Constructor for [RemoteImageApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + RemoteImageApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future?> requestImage(String url, {required int requestId, required bool preferEncoded}) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([url, requestId, preferEncoded]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return (pigeonVar_replyList[0] as Map?)?.cast(); + } + } + + Future cancelRequest(int requestId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.RemoteImageApi.cancelRequest$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([requestId]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future clearCache() async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.RemoteImageApi.clearCache$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 int?)!; + } + } +} diff --git a/mobile/lib/platform/thumbnail_api.g.dart b/mobile/lib/platform/thumbnail_api.g.dart new file mode 100644 index 0000000000..53d7b10fc3 --- /dev/null +++ b/mobile/lib/platform/thumbnail_api.g.dart @@ -0,0 +1,142 @@ +// Autogenerated from Pigeon (v26.0.2), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + default: + return super.readValueOfType(type, buffer); + } + } +} + +class ThumbnailApi { + /// Constructor for [ThumbnailApi]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + ThumbnailApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); + + final String pigeonVar_messageChannelSuffix; + + Future> requestImage( + String assetId, { + required int requestId, + required int width, + required int height, + required bool isVideo, + }) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ThumbnailApi.requestImage$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([ + assetId, + requestId, + width, + height, + isVideo, + ]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 Map?)!.cast(); + } + } + + Future cancelImageRequest(int requestId) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ThumbnailApi.cancelImageRequest$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([requestId]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 { + return; + } + } + + Future> getThumbhash(String thumbhash) async { + final String pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.ThumbnailApi.getThumbhash$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send([thumbhash]); + final List? pigeonVar_replyList = await pigeonVar_sendFuture as List?; + 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 Map?)!.cast(); + } + } +} diff --git a/mobile/lib/presentation/actions/action.dart b/mobile/lib/presentation/actions/action.dart deleted file mode 100644 index d880b7ddf7..0000000000 --- a/mobile/lib/presentation/actions/action.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/utils/asset_filter.dart'; - -class ActionItem { - final IconData icon; - final String label; - final FutureOr Function() onAction; - final FutureOr Function()? onSecondaryAction; - - const ActionItem({required this.icon, required this.label, required this.onAction, this.onSecondaryAction}); -} - -abstract class ActionBuilder { - const ActionBuilder(); - - // null when the action is not applicable for the current context - ActionItem? create(BuildContext context, WidgetRef ref); -} - -final assetsActionProvider = Provider.family.autoDispose, ActionSource>( - (ref, source) => AssetFilter(switch (source) { - .timeline => ref.watch(multiSelectProvider.select((s) => s.selectedAssets)), - .viewer => switch (ref.watch(assetViewerProvider.select((s) => s.currentAsset))) { - final BaseAsset asset => {asset}, - null => const {}, - }, - }), - dependencies: [multiSelectProvider], -); - -final clearSelectionProvider = Provider.family.autoDispose((ref, source) { - if (source == .timeline) { - return ref.read(multiSelectProvider.notifier).reset; - } - - return () {}; -}, dependencies: [multiSelectProvider]); - -final ownedAssetsActionProvider = Provider.family.autoDispose, ActionSource>( - (ref, source) => ref.watch(assetsActionProvider(source)).owned(ref.watch(authUserProvider).id), - dependencies: [assetsActionProvider], -); - -abstract class AssetActionBuilder extends ActionBuilder { - final ActionSource source; - - const AssetActionBuilder({required this.source}); -} diff --git a/mobile/lib/presentation/actions/action.widget.dart b/mobile/lib/presentation/actions/action.widget.dart deleted file mode 100644 index a865528440..0000000000 --- a/mobile/lib/presentation/actions/action.widget.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_ui/immich_ui.dart'; - -abstract class ActionWidget extends ConsumerWidget { - final ActionBuilder action; - - const ActionWidget({super.key, required this.action}); - - Widget builder(BuildContext context, WidgetRef ref, ActionItem action); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final actionItem = action.create(context, ref); - if (actionItem == null) { - return const SizedBox.shrink(); - } - - return builder(context, ref, actionItem); - } -} - -class ActionColumnButton extends ActionWidget { - const ActionColumnButton({super.key, required super.action}); - - @override - Widget builder(BuildContext context, WidgetRef ref, ActionItem action) => ImmichColumnButton( - icon: action.icon, - label: action.label, - onPressed: action.onAction, - onLongPress: action.onSecondaryAction, - ); -} - -class ActionIconButton extends ActionWidget { - final ImmichVariant variant; - - const ActionIconButton({super.key, required super.action, this.variant = .ghost}); - - @override - Widget builder(BuildContext context, WidgetRef ref, ActionItem action) => ImmichIconButton( - icon: action.icon, - onPressed: action.onAction, - onLongPress: action.onSecondaryAction, - variant: variant, - ); -} - -class ActionButton extends ActionWidget { - final ImmichVariant variant; - - const ActionButton({super.key, required super.action, this.variant = .ghost}); - - @override - Widget builder(BuildContext context, WidgetRef ref, ActionItem action) => ImmichTextButton( - labelText: action.label, - icon: action.icon, - onPressed: action.onAction, - onLongPress: action.onSecondaryAction, - variant: variant, - ); -} - -class ActionMenuItem extends ActionWidget { - const ActionMenuItem({super.key, required super.action}); - - @override - Widget builder(BuildContext context, WidgetRef ref, ActionItem action) => - ImmichMenuItem(icon: action.icon, label: action.label, onPressed: action.onAction); -} diff --git a/mobile/lib/presentation/actions/archive.action.dart b/mobile/lib/presentation/actions/archive.action.dart deleted file mode 100644 index 1e41411a3c..0000000000 --- a/mobile/lib/presentation/actions/archive.action.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -typedef _State = ({bool shouldArchive, List assetIds}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - if (ref.watch(inLockedViewProvider)) { - return null; - } - - final assets = ref.watch(ownedAssetsActionProvider(source)); - final shouldArchive = assets.notVisibility(.archive).isNotEmpty; - final assetIds = assets - .visibility(shouldArchive ? .timeline : .archive) - .map((asset) => asset.id) - .toList(growable: false); - return assetIds.isEmpty ? null : (shouldArchive: shouldArchive, assetIds: assetIds); -}, dependencies: [ownedAssetsActionProvider]); - -class ArchiveAction extends AssetActionBuilder { - const ArchiveAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final shouldArchive = ref.watch(_stateProvider(source).select((state) => state?.shouldArchive)); - if (shouldArchive == null) { - return null; - } - - return .new( - icon: shouldArchive ? Icons.archive_outlined : Icons.unarchive_outlined, - label: shouldArchive ? context.t.archive : context.t.unarchive, - onAction: () => _archive(context, ref), - ); - } - - Future _archive(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:shouldArchive, :assetIds) = state; - final message = shouldArchive - ? context.t.archive_action_prompt(count: assetIds.length) - : context.t.unarchive_action_prompt(count: assetIds.length); - final assetService = ref.read(assetServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - await assetService.update(assetIds, visibility: .some(shouldArchive ? .archive : .timeline)); - Future undo() => assetService.update(assetIds, visibility: .some(shouldArchive ? .timeline : .archive)); - toastService.success(message, toast: .new(onUndo: undo)); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the archive status for assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/asset_debug.action.dart b/mobile/lib/presentation/actions/asset_debug.action.dart deleted file mode 100644 index 16ad15700e..0000000000 --- a/mobile/lib/presentation/actions/asset_debug.action.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:async'; - -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/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; - -class AssetDebugAction extends AssetActionBuilder { - const AssetDebugAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final asset = ref.watch(assetsActionProvider(source)).assets.singleOrNull; - final troubleshootEnabled = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting); - if (!troubleshootEnabled || asset == null) { - return null; - } - - return .new( - icon: Icons.help_outline_rounded, - label: context.t.troubleshoot, - onAction: () => unawaited(context.pushRoute(AssetTroubleshootRoute(asset: asset))), - ); - } -} diff --git a/mobile/lib/presentation/actions/cast.action.dart b/mobile/lib/presentation/actions/cast.action.dart deleted file mode 100644 index 5d90479647..0000000000 --- a/mobile/lib/presentation/actions/cast.action.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/cast.provider.dart'; -import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; - -class CastAction extends ActionBuilder { - const CastAction(); - - @override - ActionItem create(BuildContext context, WidgetRef ref) { - final isCasting = ref.watch(castProvider.select((state) => state.isCasting)); - - return .new( - icon: isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded, - label: context.t.cast, - onAction: () async => unawaited(showDialog(context: context, builder: (_) => const CastDialog())), - ); - } -} diff --git a/mobile/lib/presentation/actions/delete.action.dart b/mobile/lib/presentation/actions/delete.action.dart deleted file mode 100644 index 7edac9aa42..0000000000 --- a/mobile/lib/presentation/actions/delete.action.dart +++ /dev/null @@ -1,238 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/cleanup.service.dart'; -import 'package:immich_mobile/services/toast.service.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; - -typedef _State = ({List localIds, List remoteIds, bool trash}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final authUserId = ref.watch(authUserProvider).id; - - final localIds = []; - final ownedRemote = []; - for (final asset in assets) { - if (asset.localId case final localId?) { - localIds.add(localId); - } - if (asset case final RemoteAsset remote when remote.ownerId == authUserId) { - ownedRemote.add(remote); - } - } - - if (localIds.isEmpty && ownedRemote.isEmpty) { - return null; - } - - final trashEnabled = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); - // Assets already in the trash or in the locked folder are deleted outright, irrespective of the server setting. - final trash = - ownedRemote.isEmpty || (trashEnabled && !ownedRemote.every((asset) => asset.isTrashed || asset.isLocked)); - - return (localIds: localIds, remoteIds: ownedRemote.map((asset) => asset.id).toList(growable: false), trash: trash); -}, dependencies: [assetsActionProvider]); - -class DeleteAction extends AssetActionBuilder { - const DeleteAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final trash = ref.watch(_stateProvider(source).select((state) => state?.trash)); - if (trash == null) { - return null; - } - - return .new( - icon: Icons.delete_outline, - label: trash ? context.t.trash : context.t.delete, - onAction: () => _delete(context, ref), - ); - } - - Future _delete(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:localIds, :remoteIds, :trash) = state; - final assetService = ref.read(assetServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - final String? message; - // Only trashing is reversible; a permanent delete and a device cleanup are not. - ToastOption? undo; - if (remoteIds.isEmpty) { - message = await _removeLocalAssets(context, ref, localIds); - } else if (trash) { - message = await _moveToTrash(context, ref, remoteIds, localIds); - undo = .new(onUndo: () => assetService.restoreTrash(remoteIds)); - } else { - message = await _deletePermanently(context, ref, remoteIds, localIds); - } - - if (message == null) { - return; - } - - toastService.success(message, toast: undo); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to delete assets"); - } - } - - Future _removeLocalAssets(BuildContext context, WidgetRef ref, List localIds) async { - final count = await _cleanupLocalAssets(context, ref, localIds); - if (count <= 0 || !context.mounted) { - return null; - } - - return context.t.cleanup_deleted_assets(count: count); - } - - Future _moveToTrash( - BuildContext context, - WidgetRef ref, - List remoteIds, - List localIds, - ) async { - final assetService = ref.read(assetServiceProvider); - if (localIds.isNotEmpty) { - await _cleanupLocalAssets(context, ref, localIds); - if (!context.mounted) { - return null; - } - } - - final message = context.t.trash_action_prompt(count: remoteIds.length); - await assetService.trash(remoteIds); - return message; - } - - Future _deletePermanently( - BuildContext context, - WidgetRef ref, - List remoteIds, - List localIds, - ) async { - final assetService = ref.read(assetServiceProvider); - final confirmed = await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: context.t.delete_dialog_title, - content: context.t.delete_dialog_alert, - ok: context.t.delete_permanently, - ), - ); - if (confirmed != true || !context.mounted) { - return null; - } - - final message = context.t.delete_permanently_action_prompt(count: remoteIds.length); - // Server first, so a failed request will not remove the local copy - await assetService.delete(remoteIds); - if (localIds.isNotEmpty && context.mounted) { - await _cleanupLocalAssets(context, ref, localIds, requestCustomPrompt: false); - } - - return message; - } -} - -final _cleanupStateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final assetIds = assets.backedUp().map((asset) => asset.localId).nonNulls.toList(growable: false); - return assetIds.isEmpty ? null : assetIds; -}, dependencies: [assetsActionProvider]); - -class CleanupLocalAction extends AssetActionBuilder { - const CleanupLocalAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final isVisible = ref.watch(_cleanupStateProvider(source).select((state) => state != null)); - if (!isVisible) { - return null; - } - - return .new( - icon: Icons.no_cell_outlined, - label: context.t.control_bottom_app_bar_delete_from_local, - onAction: () => _cleanup(context, ref), - ); - } - - Future _cleanup(BuildContext context, WidgetRef ref) async { - final assetIds = ref.read(_cleanupStateProvider(source)); - if (assetIds == null) { - return; - } - - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - final count = await _cleanupLocalAssets(context, ref, assetIds); - if (count <= 0 || !context.mounted) { - return; - } - - toastService.success(context.t.cleanup_deleted_assets(count: count)); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to remove the device copies"); - } - } -} - -/// Removes the device copies of [assetIds], returning how many were deleted. -/// -/// iOS and Android without MANAGE_MEDIA prompt the user -/// with MANAGE_MEDIA, we do it ourselves unless [requestCustomPrompt] is false. -Future _cleanupLocalAssets( - BuildContext context, - WidgetRef ref, - List assetIds, { - bool requestCustomPrompt = true, -}) async { - if (assetIds.isEmpty) { - return 0; - } - - final cleanupService = ref.read(cleanupServiceProvider); - final requiresPrompt = - requestCustomPrompt && - CurrentPlatform.isAndroid && - ref.read(storeServiceProvider).get(.manageLocalMediaAndroid, false); - - if (requiresPrompt) { - final confirmed = await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: context.t.move_to_device_trash, - content: context.t.free_up_space_description, - ok: context.t.ok, - ), - ); - if (confirmed != true) { - return 0; - } - } - - return cleanupService.deleteLocalAssets(assetIds); -} diff --git a/mobile/lib/presentation/actions/download.action.dart b/mobile/lib/presentation/actions/download.action.dart deleted file mode 100644 index 60a1395bcd..0000000000 --- a/mobile/lib/presentation/actions/download.action.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/repositories/download.repository.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final remote = assets.remote().toList(growable: false); - return remote.isEmpty ? null : remote; -}, dependencies: [assetsActionProvider]); - -class DownloadAction extends AssetActionBuilder { - const DownloadAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assets = ref.watch(_stateProvider(source)); - if (assets == null) { - return null; - } - - return .new(icon: Icons.download, label: context.t.download, onAction: () => _download(ref, assets)); - } - - Future _download(WidgetRef ref, List assets) async { - final backgroundSync = ref.read(backgroundSyncProvider); - final downloads = ref.read(downloadRepositoryProvider); - - try { - await downloads.downloadAllAssets(assets); - - unawaited( - Future.delayed(const .new(seconds: 1), () async { - await backgroundSync.syncLocal(); - await backgroundSync.hashAssets(); - }), - ); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to download the assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/edit_asset.action.dart b/mobile/lib/presentation/actions/edit_asset.action.dart deleted file mode 100644 index e2b4993c3b..0000000000 --- a/mobile/lib/presentation/actions/edit_asset.action.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:async'; - -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/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/domain/models/server_capability.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/pages/edit/editor.provider.dart'; -import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -final _stateProvider = Provider.family.autoDispose((ref, source) { - final isSupported = ref.watch(serverInfoProvider.select((state) => state.serverVersion.supports(.assetEdits))); - if (!isSupported) { - return null; - } - - final assets = ref.watch(ownedAssetsActionProvider(source)); - return assets.where((asset) => asset.isEditable).singleOrNull; -}, dependencies: [ownedAssetsActionProvider]); - -class EditAssetAction extends AssetActionBuilder { - const EditAssetAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - if (!ref.watch(_stateProvider(source).select((asset) => asset != null))) { - return null; - } - - return .new(icon: Icons.tune, label: context.t.edit, onAction: () => _edit(context, ref)); - } - - Future _edit(BuildContext context, WidgetRef ref) async { - final asset = ref.read(_stateProvider(source)); - if (asset == null) { - return; - } - - try { - // TODO(shenlong): Move all EXIF and Apply Edits logic onto the Route - final repository = ref.read(driftProvider).remoteAssetRepository; - final (edits, exif) = await (repository.getAssetEdits(asset.id), repository.getExif(asset.id)).wait; - if (exif == null || !context.mounted) { - return; - } - - ref.read(editorStateProvider.notifier).init(edits, exif); - unawaited( - context.pushRoute( - DriftEditImageRoute( - image: Image(image: getFullImageProvider(asset, edited: false)), - applyEdits: (newEdits) => applyEdits(ref, asset.id, newEdits), - ), - ), - ); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to open the editor for the asset"); - } - } -} - -@visibleForTesting -Future applyEdits(WidgetRef ref, String remoteId, List edits) async { - final websocket = ref.read(websocketProvider.notifier); - - bool isCurrentId(dynamic data) => data is Map && (data['asset'] as Map?)?['id'] == remoteId; - await ref.read(assetServiceProvider).applyEdits(remoteId, edits); - await Future.any([ - websocket.waitForEvent('AssetEditReadyV1', isCurrentId, const .new(seconds: 10)), - websocket.waitForEvent('AssetEditReadyV2', isCurrentId, const .new(seconds: 10)), - ]).catchError((_) {}); -} diff --git a/mobile/lib/presentation/actions/edit_datetime.action.dart b/mobile/lib/presentation/actions/edit_datetime.action.dart deleted file mode 100644 index 4ca3ddbaf4..0000000000 --- a/mobile/lib/presentation/actions/edit_datetime.action.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/utils/timezone.dart'; -import 'package:immich_mobile/widgets/common/date_time_picker.dart'; - -typedef _State = ({List assetIds, RemoteAsset? origin}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - if (assets.isEmpty) { - return null; - } - - return (assetIds: assets.map((asset) => asset.id).toList(growable: false), origin: assets.singleOrNull); -}, dependencies: [ownedAssetsActionProvider]); - -class EditDateTimeAction extends AssetActionBuilder { - const EditDateTimeAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - if (!ref.watch(_stateProvider(source).select((state) => state != null))) { - return null; - } - - return .new( - icon: Icons.edit_calendar_outlined, - label: context.t.control_bottom_app_bar_edit_time, - onAction: () => _edit(context, ref), - ); - } - - Future _edit(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:assetIds, :origin) = state; - final remoteAssetRepository = ref.read(driftProvider).remoteAssetRepository; - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - DateTime? initialDate; - String? timeZone; - Duration? offset; - - if (origin != null) { - final exif = await remoteAssetRepository.getExif(origin.id); - - // Prefer the EXIF timezone, so the picker opens on what the asset actually shows. - DateTime dateTime = origin.createdAt.toLocal(); - offset = dateTime.timeZoneOffset; - if (exif?.dateTimeOriginal case final original?) { - timeZone = exif!.timeZone; - (dateTime, offset) = applyTimezoneOffset(dateTime: original, timeZone: exif.timeZone); - } - initialDate = dateTime; - - if (!context.mounted) { - return; - } - } - - final picked = await showDateTimePicker( - context: context, - initialDateTime: initialDate, - initialTZ: timeZone, - initialTZOffset: offset, - ); - if (picked == null || !context.mounted) { - return; - } - - await saveDateTime(context, ref, assetIds, picked); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the date and time for assets"); - } - } -} - -@visibleForTesting -Future saveDateTime(BuildContext context, WidgetRef ref, List assetIds, String dateTime) async { - final message = context.t.edit_date_and_time_action_prompt(count: assetIds.length); - final toastService = ref.read(toastServiceProvider); - - await ref.read(assetServiceProvider).update(assetIds, dateTime: .some(dateTime)); - ref.invalidate(assetExifProvider); - toastService.success(message); -} diff --git a/mobile/lib/presentation/actions/edit_location.action.dart b/mobile/lib/presentation/actions/edit_location.action.dart deleted file mode 100644 index 01318de999..0000000000 --- a/mobile/lib/presentation/actions/edit_location.action.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/widgets/common/location_picker.dart'; -import 'package:maplibre_gl/maplibre_gl.dart'; - -typedef _State = ({List assetIds, RemoteAsset? origin}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - if (assets.isEmpty) { - return null; - } - - return (assetIds: assets.map((asset) => asset.id).toList(growable: false), origin: assets.singleOrNull); -}, dependencies: [ownedAssetsActionProvider]); - -class EditLocationAction extends AssetActionBuilder { - const EditLocationAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - if (!ref.watch(_stateProvider(source).select((state) => state != null))) { - return null; - } - - return .new( - icon: Icons.edit_location_alt_outlined, - label: context.t.control_bottom_app_bar_edit_location, - onAction: () => _edit(context, ref), - ); - } - - Future _edit(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:assetIds, :origin) = state; - final remoteAssetRepository = ref.read(driftProvider).remoteAssetRepository; - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - LatLng? initialLatLng; - if (origin != null) { - final exif = await remoteAssetRepository.getExif(origin.id); - if (exif?.latitude != null && exif?.longitude != null) { - initialLatLng = LatLng(exif!.latitude!, exif.longitude!); - } - if (!context.mounted) { - return; - } - } - - final location = await showLocationPicker(context: context, initialLatLng: initialLatLng); - if (location == null || !context.mounted) { - return; - } - - await saveLocation(context, ref, assetIds, location); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the location for assets"); - } - } -} - -@visibleForTesting -Future saveLocation(BuildContext context, WidgetRef ref, List assetIds, LatLng location) async { - final message = context.t.edit_location_action_prompt(count: assetIds.length); - final toastService = ref.read(toastServiceProvider); - - await ref.read(assetServiceProvider).update(assetIds, location: .some(location)); - ref.invalidate(assetExifProvider); - toastService.success(message); -} diff --git a/mobile/lib/presentation/actions/favorite.action.dart b/mobile/lib/presentation/actions/favorite.action.dart deleted file mode 100644 index 402d2f3833..0000000000 --- a/mobile/lib/presentation/actions/favorite.action.dart +++ /dev/null @@ -1,62 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -typedef _State = ({bool shouldFavorite, List assetIds}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - if (assets.isEmpty) { - return null; - } - - final shouldFavorite = assets.favorite(isFavorite: false).isNotEmpty; - final assetIds = assets.favorite(isFavorite: !shouldFavorite).map((asset) => asset.id).toList(growable: false); - return (shouldFavorite: shouldFavorite, assetIds: assetIds); -}, dependencies: [ownedAssetsActionProvider]); - -class FavoriteAction extends AssetActionBuilder { - const FavoriteAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final shouldFavorite = ref.watch(_stateProvider(source).select((state) => state?.shouldFavorite)); - if (shouldFavorite == null) { - return null; - } - - return .new( - icon: shouldFavorite ? Icons.favorite_border_rounded : Icons.favorite_rounded, - label: shouldFavorite ? context.t.favorite : context.t.unfavorite, - onAction: () => _favorite(context, ref), - ); - } - - Future _favorite(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:shouldFavorite, :assetIds) = state; - final message = shouldFavorite - ? context.t.favorite_action_prompt(count: assetIds.length) - : context.t.unfavorite_action_prompt(count: assetIds.length); - final assetService = ref.read(assetServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - await assetService.update(assetIds, isFavorite: .some(shouldFavorite)); - toastService.success(message); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update favorite status for assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/lock.action.dart b/mobile/lib/presentation/actions/lock.action.dart deleted file mode 100644 index f9030917ab..0000000000 --- a/mobile/lib/presentation/actions/lock.action.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/services/toast.service.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; -import 'package:logging/logging.dart'; - -typedef _State = ({bool shouldLock, List assetIds, List localIds}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - if (assets.isEmpty) { - return null; - } - - final shouldLock = assets.locked(isLocked: false).isNotEmpty; - final targets = assets.locked(isLocked: !shouldLock); - return ( - shouldLock: shouldLock, - assetIds: targets.map((asset) => asset.id).toList(growable: false), - // Only locking has an on-device copy to clean up; unlocking leaves the device alone. - localIds: shouldLock ? targets.map((asset) => asset.localId).nonNulls.toList(growable: false) : const [], - ); -}, dependencies: [ownedAssetsActionProvider]); - -class LockAction extends AssetActionBuilder { - const LockAction({required super.source}); - - static final Logger _log = Logger('LockAction'); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final shouldLock = ref.watch(_stateProvider(source).select((state) => state?.shouldLock)); - if (shouldLock == null) { - return null; - } - - return .new( - icon: shouldLock ? Icons.lock_rounded : Icons.lock_open_rounded, - label: shouldLock ? context.t.move_to_locked_folder : context.t.remove_from_locked_folder, - onAction: () => _lock(context, ref), - ); - } - - Future _lock(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:shouldLock, :assetIds, :localIds) = state; - if (shouldLock && localIds.isNotEmpty) { - final confirmed = await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: context.t.move_to_locked_folder, - content: CurrentPlatform.isAndroid - ? context.t.delete_dialog_alert_local - : context.t.delete_dialog_alert_local_ios, - ok: context.t.confirm, - ), - ); - if (confirmed != true || !context.mounted) { - return; - } - } - - final assetService = ref.read(assetServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - await assetService.update(assetIds, visibility: .some(shouldLock ? .locked : .timeline)); - var keptCount = 0; - if (localIds.isNotEmpty) { - // A locked asset still sits in the device gallery, so offer to remove the local copy. - keptCount = localIds.length - await assetService.deleteLocal(localIds, trash: false); - if (keptCount != 0) { - _log.warning('Only deleted ${localIds.length - keptCount} of ${localIds.length} local copies'); - } - } - if (!context.mounted) { - return; - } - final message = shouldLock - ? keptCount > 0 - ? context.t.move_to_lock_folder_partial_prompt(count: assetIds.length) - : context.t.move_to_lock_folder_action_prompt(count: assetIds.length) - : context.t.remove_from_lock_folder_action_prompt(count: assetIds.length); - // Unlocking is a sensitive action and requires an elevated session - final toast = shouldLock - ? null - : ToastOption(onUndo: () => assetService.update(assetIds, visibility: const .some(.locked))); - toastService.success(message, toast: toast); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the locked folder for assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/open_in_browser.action.dart b/mobile/lib/presentation/actions/open_in_browser.action.dart deleted file mode 100644 index 6b05762977..0000000000 --- a/mobile/lib/presentation/actions/open_in_browser.action.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; -import 'package:url_launcher/url_launcher.dart'; - -class OpenInBrowserAction extends ActionBuilder { - final String remoteId; - final TimelineOrigin origin; - - const OpenInBrowserAction({required this.remoteId, required this.origin}); - - @override - ActionItem create(BuildContext context, WidgetRef ref) => - .new(icon: Icons.open_in_browser, label: context.t.open_in_browser, onAction: () => _open(ref)); - - Future _open(WidgetRef ref) async { - final serverEndpoint = ref.read(storeServiceProvider).get(.serverEndpoint).replaceFirst('/api', ''); - final url = Uri.parse('$serverEndpoint${webPathFor(origin)}/photos/$remoteId'); - - if (await canLaunchUrl(url)) { - await launchUrl(url, mode: .externalApplication); - } - } -} - -@visibleForTesting -String webPathFor(TimelineOrigin origin) => switch (origin) { - .favorite => '/favorites', - .trash => '/trash', - .archive => '/archive', - _ => '', -}; diff --git a/mobile/lib/presentation/actions/partner.action.dart b/mobile/lib/presentation/actions/partner.action.dart deleted file mode 100644 index 012340c1d8..0000000000 --- a/mobile/lib/presentation/actions/partner.action.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; - -class PartnerAddAction extends ActionBuilder { - const PartnerAddAction(); - - @override - ActionItem create(BuildContext context, WidgetRef ref) => - ActionItem(icon: Icons.person_add_rounded, label: context.t.add_partner, onAction: () => _add(context, ref)); - - Future _add(BuildContext context, WidgetRef ref) async { - final partnerService = ref.read(partnerServiceProvider); - final authUserId = ref.read(authUserProvider).id; - - final selected = await showDialog(context: context, builder: (_) => const PartnerSelectionDialog()); - if (selected == null) { - return; - } - - try { - await partnerService.create(sharedById: authUserId, sharedWithId: selected.id); - } catch (error, stack) { - handleError(error, stack: stack, description: 'Failed to add partner'); - } - } -} - -class PartnerRemoveAction extends ActionBuilder { - const PartnerRemoveAction({required this.sharedWithId, required this.partnerName}); - - final String sharedWithId; - final String partnerName; - - @override - ActionItem create(BuildContext context, WidgetRef ref) => - ActionItem(icon: Icons.person_remove_rounded, label: context.t.remove, onAction: () => _remove(context, ref)); - - Future _remove(BuildContext context, WidgetRef ref) async { - final partnerService = ref.read(partnerServiceProvider); - final authUserId = ref.read(authUserProvider).id; - - final confirmed = await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: context.t.stop_photo_sharing, - content: context.t.partner_page_stop_sharing_content(partner: partnerName), - ), - ); - if (confirmed != true) { - return; - } - - try { - await partnerService.delete(sharedById: authUserId, sharedWithId: sharedWithId); - } catch (error, stack) { - handleError(error, stack: stack, description: 'Failed to remove partner'); - } - } -} - -@visibleForTesting -final candidatesStateProvider = StreamProvider.autoDispose>( - (ref) => ref.watch(partnerServiceProvider).getCandidates(ref.watch(authUserProvider).id), -); - -@visibleForTesting -class PartnerSelectionDialog extends ConsumerWidget { - const PartnerSelectionDialog({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final candidatesAsync = ref.watch(candidatesStateProvider); - - return SimpleDialog( - title: Text(context.t.partner_page_select_partner), - children: candidatesAsync.when( - data: (candidates) { - final users = candidates.toList(); - if (users.isEmpty) { - return [ - Padding( - padding: const .symmetric(horizontal: 24, vertical: 8), - child: Text(context.t.partner_page_no_more_users), - ), - ]; - } - return [ - for (final candidate in users) - SimpleDialogOption( - onPressed: () => Navigator.of(context).pop(candidate), - child: Row( - children: [ - Padding( - padding: const .only(right: 8), - child: PartnerUserAvatar(userId: candidate.id, name: candidate.name), - ), - Text(candidate.name), - ], - ), - ), - ]; - }, - loading: () => const [ - Padding( - padding: .all(24), - child: Center(child: CircularProgressIndicator()), - ), - ], - error: (error, _) => [ - Padding( - padding: const .symmetric(horizontal: 24, vertical: 8), - child: Text(context.t.error_loading_partners(error: error)), - ), - ], - ), - ); - } -} diff --git a/mobile/lib/presentation/actions/remove_from_album.action.dart b/mobile/lib/presentation/actions/remove_from_album.action.dart deleted file mode 100644 index 9e2d9d582f..0000000000 --- a/mobile/lib/presentation/actions/remove_from_album.action.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final assetIds = assets.remote().map((asset) => asset.id).toList(growable: false); - return assetIds.isEmpty ? null : assetIds; -}, dependencies: [assetsActionProvider]); - -class RemoveFromAlbumAction extends AssetActionBuilder { - final String albumId; - - const RemoveFromAlbumAction({required super.source, required this.albumId}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assetIds = ref.watch(_stateProvider(source)); - if (assetIds == null) { - return null; - } - - return .new( - icon: Icons.remove_circle_outline, - label: context.t.remove_from_album, - onAction: () => _remove(context, ref, assetIds), - ); - } - - Future _remove(BuildContext context, WidgetRef ref, List assetIds) async { - final albumService = ref.read(remoteAlbumServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - final count = await albumService.removeAssets(albumId: albumId, assetIds: assetIds); - if (!context.mounted) { - return; - } - - toastService.success(context.t.remove_from_album_action_prompt(count: count)); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to remove the assets from the album"); - } - } -} diff --git a/mobile/lib/presentation/actions/restore.action.dart b/mobile/lib/presentation/actions/restore.action.dart deleted file mode 100644 index 1ce444e906..0000000000 --- a/mobile/lib/presentation/actions/restore.action.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - final assetIds = assets.trashed().map((asset) => asset.id).toList(growable: false); - return assetIds.isEmpty ? null : assetIds; -}, dependencies: [ownedAssetsActionProvider]); - -class RestoreAction extends AssetActionBuilder { - const RestoreAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - if (!ref.watch(_stateProvider(source).select((state) => state != null))) { - return null; - } - - return .new(icon: Icons.history_rounded, label: context.t.restore, onAction: () => _restore(context, ref)); - } - - Future _restore(BuildContext context, WidgetRef ref) async { - final assetIds = ref.read(_stateProvider(source)); - if (assetIds == null) { - return; - } - - final message = context.t.assets_restored_count(count: assetIds.length); - final assetService = ref.read(assetServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - await assetService.restoreTrash(assetIds); - toastService.success(message, toast: .new(onUndo: () => assetService.trash(assetIds))); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to restore assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/set_album_cover.action.dart b/mobile/lib/presentation/actions/set_album_cover.action.dart deleted file mode 100644 index d5ad367eed..0000000000 --- a/mobile/lib/presentation/actions/set_album_cover.action.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -final _stateProvider = Provider.family.autoDispose((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - return assets.remote().map((asset) => asset.id).singleOrNull; -}, dependencies: [assetsActionProvider]); - -class SetAlbumCoverAction extends AssetActionBuilder { - final String albumId; - - const SetAlbumCoverAction({required super.source, required this.albumId}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assetId = ref.watch(_stateProvider(source)); - if (assetId == null) { - return null; - } - - return .new( - icon: Icons.image_outlined, - label: context.t.set_as_album_cover, - onAction: () => _setCover(context, ref, assetId), - ); - } - - Future _setCover(BuildContext context, WidgetRef ref, String assetId) async { - final message = context.t.album_cover_updated; - final albumService = ref.read(remoteAlbumServiceProvider); - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - await albumService.updateAlbum(albumId, thumbnailAssetId: assetId); - toastService.success(message); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the album cover"); - } - } -} diff --git a/mobile/lib/presentation/actions/set_profile_picture.action.dart b/mobile/lib/presentation/actions/set_profile_picture.action.dart deleted file mode 100644 index c14be1dc87..0000000000 --- a/mobile/lib/presentation/actions/set_profile_picture.action.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/routing/router.dart'; - -class SetProfilePictureAction extends ActionBuilder { - final BaseAsset asset; - - const SetProfilePictureAction({required this.asset}); - - @override - ActionItem create(BuildContext context, WidgetRef ref) => .new( - icon: Icons.account_circle_outlined, - label: context.t.set_as_profile_picture, - onAction: () async => unawaited(context.pushRoute(ProfilePictureCropRoute(asset: asset))), - ); -} diff --git a/mobile/lib/presentation/actions/share.action.dart b/mobile/lib/presentation/actions/share.action.dart deleted file mode 100644 index 3b8a6318de..0000000000 --- a/mobile/lib/presentation/actions/share.action.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final shareable = assets.toList(growable: false); - return shareable.isEmpty ? null : shareable; -}, dependencies: [assetsActionProvider]); - -class ShareAction extends AssetActionBuilder { - const ShareAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assets = ref.watch(_stateProvider(source)); - if (assets == null) { - return null; - } - - return .new( - icon: CurrentPlatform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded, - label: context.t.share, - onAction: () => _share(context, ref, assets, ref.read(appConfigProvider).share.fileType), - onSecondaryAction: () => _promptQualityAndShare(context, ref, assets), - ); - } - - Future _promptQualityAndShare(BuildContext context, WidgetRef ref, List assets) async { - // Only show preview option when at least one of the assets is not a video - final showPreview = assets.any((asset) => !asset.isVideo); - - final fileType = await showDialog( - context: context, - builder: (_) => _ShareFileTypeDialog(showPreview: showPreview), - useRootNavigator: false, - ); - if (fileType == null || !context.mounted) { - return; - } - - await _share(context, ref, assets, fileType); - } - - Future _share(BuildContext context, WidgetRef ref, List assets, ShareAssetType fileType) async { - final cancelCompleter = Completer(); - final progress = ValueNotifier(null); - final mediaRepository = ref.read(assetMediaRepositoryProvider); - final toastService = ref.read(toastServiceProvider); - final errorMessage = context.t.scaffold_body_error_occurred; - - await showDialog( - context: context, - barrierDismissible: false, - useRootNavigator: false, - builder: (dialogContext) { - void finish({required bool failed}) { - if (cancelCompleter.isCompleted || !dialogContext.mounted) { - return; - } - if (failed) { - unawaited(.value(toastService.error(errorMessage))); - } - dialogContext.pop(); - } - - unawaited( - mediaRepository - .shareAssets( - assets, - context, - fileType: fileType, - cancelCompleter: cancelCompleter, - onAssetDownloadProgress: (value) => progress.value = value, - ) - .then((count) => finish(failed: count == 0), onError: (_) => finish(failed: true)), - ); - - return _SharePreparingDialog(progress: progress); - }, - ).then((_) { - if (!cancelCompleter.isCompleted) { - cancelCompleter.complete(); - } - progress.dispose(); - }); - } -} - -class _SharePreparingDialog extends StatelessWidget { - final ValueNotifier progress; - - const _SharePreparingDialog({required this.progress}); - - @override - Widget build(BuildContext context) { - return AlertDialog( - content: Column( - mainAxisSize: .min, - children: [ - Container(margin: const .only(bottom: 12), child: Text(context.t.share_dialog_preparing)), - SizedBox( - width: 240, - child: ValueListenableBuilder( - valueListenable: progress, - builder: (context, value, _) { - final percent = value == null ? null : (value * 100).clamp(0, 100); - return Column( - mainAxisSize: .min, - children: [ - LinearProgressIndicator(value: value, minHeight: 8.0), - if (percent != null) - Container(margin: const .only(top: 8), child: Text('${percent.toStringAsFixed(0)}%')), - ], - ); - }, - ), - ), - ], - ), - ); - } -} - -class _ShareFileTypeDialog extends StatelessWidget { - final bool showPreview; - - const _ShareFileTypeDialog({this.showPreview = true}); - - @override - Widget build(BuildContext context) { - return AlertDialog( - title: Text(context.t.select_quality), - contentPadding: const .symmetric(vertical: 8), - content: Column( - mainAxisSize: .min, - children: [ - ListTile( - leading: const Icon(Icons.high_quality_rounded), - title: Text(context.t.share_original), - onTap: () => context.pop(ShareAssetType.original), - ), - if (showPreview) - ListTile( - leading: const Icon(Icons.photo_size_select_large_rounded), - title: Text(context.t.share_preview), - onTap: () => context.pop(ShareAssetType.preview), - ), - ], - ), - actions: [TextButton(onPressed: () => context.pop(), child: Text(context.t.cancel))], - ); - } -} diff --git a/mobile/lib/presentation/actions/share_link.action.dart b/mobile/lib/presentation/actions/share_link.action.dart deleted file mode 100644 index 4285105ed3..0000000000 --- a/mobile/lib/presentation/actions/share_link.action.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/routing/router.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final remoteIds = assets.remote().map((asset) => asset.id).toList(growable: false); - return remoteIds.isEmpty ? null : remoteIds; -}, dependencies: [assetsActionProvider]); - -class ShareLinkAction extends AssetActionBuilder { - const ShareLinkAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final remoteIds = ref.watch(_stateProvider(source)); - if (remoteIds == null) { - return null; - } - - return .new( - icon: Icons.link_rounded, - label: context.t.share_link, - onAction: () async => unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds))), - ); - } -} diff --git a/mobile/lib/presentation/actions/similar_photos.action.dart b/mobile/lib/presentation/actions/similar_photos.action.dart deleted file mode 100644 index 53bb29f09a..0000000000 --- a/mobile/lib/presentation/actions/similar_photos.action.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; - -class SimilarPhotosAction extends ActionBuilder { - final String assetId; - - const SimilarPhotosAction({required this.assetId}); - - @override - ActionItem create(BuildContext context, WidgetRef ref) => - .new(icon: Icons.compare, label: context.t.view_similar_photos, onAction: () => _search(context, ref)); - - Future _search(BuildContext context, WidgetRef ref) async { - ref.invalidate(assetViewerProvider); - ref.invalidate(paginatedSearchProvider); - - ref.read(searchPreFilterProvider.notifier) - ..clear() - ..setFilter( - .new( - assetId: assetId, - people: {}, - location: const .new(), - camera: const .new(), - date: const .new(), - display: const .new(isNotInAlbum: false, isArchive: false, isFavorite: false), - rating: const .new(), - mediaType: .other, - ), - ); - - unawaited(context.navigateTo(const DriftSearchRoute())); - } -} diff --git a/mobile/lib/presentation/actions/slideshow.action.dart b/mobile/lib/presentation/actions/slideshow.action.dart deleted file mode 100644 index 4b6c90b517..0000000000 --- a/mobile/lib/presentation/actions/slideshow.action.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; - -class SlideshowAction extends ActionBuilder { - const SlideshowAction(); - - @override - ActionItem create(BuildContext context, WidgetRef ref) => .new( - icon: Icons.slideshow, - label: context.t.slideshow, - onAction: () async => - unawaited(context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider)))), - ); -} diff --git a/mobile/lib/presentation/actions/stack.action.dart b/mobile/lib/presentation/actions/stack.action.dart deleted file mode 100644 index 5ab978a7e4..0000000000 --- a/mobile/lib/presentation/actions/stack.action.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; - -typedef _State = ({bool shouldStack, List assetIds, List stackIds}); - -final _stateProvider = Provider.family.autoDispose<_State?, ActionSource>((ref, source) { - final assets = ref.watch(ownedAssetsActionProvider(source)); - final shouldStack = assets.stacked(isStacked: false).isNotEmpty; - // Stacking needs at least two assets; unstacking needs at least one stack. - if (shouldStack ? assets.elementAtOrNull(1) == null : assets.isEmpty) { - return null; - } - - return ( - shouldStack: shouldStack, - assetIds: assets.map((asset) => asset.id).toList(growable: false), - stackIds: assets.map((asset) => asset.stackId).nonNulls.toList(growable: false), - ); -}, dependencies: [ownedAssetsActionProvider]); - -class StackAction extends AssetActionBuilder { - const StackAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final shouldStack = ref.watch(_stateProvider(source).select((state) => state?.shouldStack)); - if (shouldStack == null) { - return null; - } - - return .new( - icon: shouldStack ? Icons.filter_none_rounded : Icons.layers_clear_outlined, - label: shouldStack ? context.t.stack : context.t.unstack, - onAction: () => _stack(context, ref), - ); - } - - Future _stack(BuildContext context, WidgetRef ref) async { - final state = ref.read(_stateProvider(source)); - if (state == null) { - return; - } - - final (:shouldStack, :assetIds, :stackIds) = state; - final message = shouldStack - ? context.t.stacked_assets_count(count: assetIds.length) - : context.t.unstacked_assets_count(count: assetIds.length); - final assetService = ref.read(assetServiceProvider); - final userId = ref.read(authUserProvider).id; - final toastService = ref.read(toastServiceProvider); - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - if (shouldStack) { - await assetService.stack(userId, assetIds); - } else { - await assetService.unstack(stackIds); - } - toastService.success(message); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to update the stack for assets"); - } - } -} diff --git a/mobile/lib/presentation/actions/tag.action.dart b/mobile/lib/presentation/actions/tag.action.dart deleted file mode 100644 index 978648708f..0000000000 --- a/mobile/lib/presentation/actions/tag.action.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/services/tag.service.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_mobile/widgets/common/tag_picker.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final tagsEnabled = ref.watch( - userMetadataPreferencesProvider.select((value) => value.valueOrNull?.tagsEnabled ?? false), - ); - if (!tagsEnabled) { - return null; - } - - final assets = ref.watch(ownedAssetsActionProvider(source)); - final assetIds = assets.map((asset) => asset.id).toList(growable: false); - return assetIds.isEmpty ? null : assetIds; -}, dependencies: [ownedAssetsActionProvider]); - -class TagAction extends AssetActionBuilder { - const TagAction({required super.source}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assetIds = ref.watch(_stateProvider(source)); - if (assetIds == null) { - return null; - } - - return .new( - icon: Icons.sell_outlined, - label: context.t.control_bottom_app_bar_add_tags, - onAction: () => _tag(context, ref, assetIds), - ); - } - - Future _tag(BuildContext context, WidgetRef ref, List assetIds) async { - final clearSelection = ref.read(clearSelectionProvider(source)); - - try { - final results = await showTagPickerModal(context: context); - if (results == null || !context.mounted) { - return; - } - - final (selected, created) = results; - await tagAssets(context, ref, assetIds, selected: selected, created: created); - clearSelection(); - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to tag the assets"); - } - } -} - -@visibleForTesting -Future tagAssets( - BuildContext context, - WidgetRef ref, - List assetIds, { - required Set selected, - required Set created, -}) async { - final tagService = ref.read(tagServiceProvider); - final toastService = ref.read(toastServiceProvider); - final tagIds = {...selected}; - - if (created.isNotEmpty) { - final tags = await tagService.upsertTags(created.toList()); - tagIds.addAll(tags.map((tag) => tag.id)); - } - if (tagIds.isEmpty) { - return; - } - - final count = await tagService.bulkTagAssets(assetIds, tagIds.toList()); - ref.invalidate(tagProvider); - if (context.mounted) { - toastService.success(context.t.tagged_assets(count: count)); - } -} diff --git a/mobile/lib/presentation/actions/upload.action.dart b/mobile/lib/presentation/actions/upload.action.dart deleted file mode 100644 index e52659beb7..0000000000 --- a/mobile/lib/presentation/actions/upload.action.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_mobile/utils/error_handler.dart'; -import 'package:immich_ui/immich_ui.dart'; - -final _stateProvider = Provider.family.autoDispose?, ActionSource>((ref, source) { - final assets = ref.watch(assetsActionProvider(source)); - final local = assets.backedUp(isBackedUp: false).local().toList(growable: false); - return local.isEmpty ? null : local; -}, dependencies: [assetsActionProvider]); - -class UploadAction extends AssetActionBuilder { - final bool showProgress; - - const UploadAction({required super.source, this.showProgress = false}); - - @override - ActionItem? create(BuildContext context, WidgetRef ref) { - final assets = ref.watch(_stateProvider(source)); - if (assets == null) { - return null; - } - - return .new(icon: Icons.backup_outlined, label: context.t.upload, onAction: () => _upload(context, ref, assets)); - } - - Future _upload(BuildContext context, WidgetRef ref, List assets) async { - try { - if (!showProgress) { - await uploadAssets(context, ref, assets); - return; - } - - // The dialog is not awaited: it stays up while the upload runs and is - // dismissed below, unless the user cancelled it themselves first - var isDialogOpen = true; - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => const _UploadProgressDialog(), - ).whenComplete(() => isDialogOpen = false), - ); - - await uploadAssets(context, ref, assets); - - if (isDialogOpen && context.mounted) { - Navigator.of(context, rootNavigator: true).pop(); - } - } catch (error, stack) { - handleError(error, stack: stack, description: "Failed to upload the assets"); - } - } -} - -@visibleForTesting -Future uploadAssets(BuildContext context, WidgetRef ref, List assets) async { - final progress = ref.read(assetUploadProgressProvider.notifier); - final uploads = ref.read(foregroundUploadServiceProvider); - final toastService = ref.read(toastServiceProvider); - final errorMessage = context.t.scaffold_body_error_occurred; - - final cancelToken = Completer(); - ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken; - - final uploaded = {}; - final failed = {}; - for (final asset in assets) { - progress.setProgress(asset.id, 0.0); - } - - try { - await uploads.uploadManual( - assets, - cancelToken: cancelToken, - callbacks: UploadCallbacks( - onProgress: (id, _, bytes, total) => progress.setProgress(id, total > 0 ? bytes / total : 0.0), - onSuccess: (id, _) { - uploaded.add(id); - progress.remove(id); - }, - onError: (id, _) { - failed.add(id); - progress.setError(id); - }, - ), - ); - } finally { - ref.read(manualUploadCancelTokenProvider.notifier).state = null; - } - - final uploadedCount = uploaded.difference(failed).length; - if (!cancelToken.isCompleted && (uploadedCount != assets.length || failed.isNotEmpty)) { - toastService.error(errorMessage); - } - - unawaited(Future.delayed(const Duration(seconds: 2), progress.clear)); -} - -class _UploadProgressDialog extends ConsumerWidget { - const _UploadProgressDialog(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final progressMap = ref.watch(assetUploadProgressProvider); - - final values = progressMap.values.where((value) => value >= 0).toList(growable: false); - final progress = values.isEmpty ? 0.0 : values.reduce((a, b) => a + b) / values.length; - final hasError = progressMap.values.any((value) => value < 0); - - return AlertDialog( - title: Text(context.t.uploading), - content: Column( - mainAxisSize: .min, - children: [ - if (hasError) - const Icon(Icons.error_outline, color: Colors.red, size: 48) - else - CircularProgressIndicator(value: progress > 0 ? progress : null), - const SizedBox(height: 16), - Text(hasError ? context.t.scaffold_body_error_occurred : '${(progress * 100).toInt()}%'), - ], - ), - actions: [ - ImmichTextButton( - onPressed: () { - ref.read(manualUploadCancelTokenProvider)?.complete(); - ref.read(manualUploadCancelTokenProvider.notifier).state = null; - Navigator.of(context, rootNavigator: true).pop(); - }, - labelText: context.t.cancel, - ), - ], - ); - } -} diff --git a/mobile/lib/presentation/pages/cleanup_preview.page.dart b/mobile/lib/presentation/pages/cleanup_preview.page.dart index 45f8c80c91..556ed6412f 100644 --- a/mobile/lib/presentation/pages/cleanup_preview.page.dart +++ b/mobile/lib/presentation/pages/cleanup_preview.page.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -19,7 +19,7 @@ class CleanupPreviewPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(context.t.cleanup_preview_title(count: assets.length)), + title: Text('cleanup_preview_title'.t(context: context, args: {'count': assets.length.toString()})), centerTitle: true, elevation: 0, scrolledUnderElevation: 0, diff --git a/mobile/lib/presentation/pages/dev/main_timeline.page.dart b/mobile/lib/presentation/pages/dev/main_timeline.page.dart index f2215d169c..5ec946858d 100644 --- a/mobile/lib/presentation/pages/dev/main_timeline.page.dart +++ b/mobile/lib/presentation/pages/dev/main_timeline.page.dart @@ -1,44 +1,16 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_dialog.widget.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/feature_message.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; @RoutePage() -class MainTimelinePage extends ConsumerStatefulWidget { +class MainTimelinePage extends ConsumerWidget { const MainTimelinePage({super.key}); @override - ConsumerState createState() => _MainTimelinePageState(); -} - -class _MainTimelinePageState extends ConsumerState { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (!mounted) { - return; - } - final service = ref.read(featureMessageServiceProvider); - if (!service.shouldShow()) { - return; - } - - await service.markSeen(); - if (!mounted) { - return; - } - - await showFeatureMessageDialog(context); - }); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final hasMemories = ref.watch(driftMemoryFutureProvider.select((state) => state.value?.isNotEmpty ?? false)); return Timeline( topSliverWidget: const SliverToBoxAdapter(child: DriftMemoryLane()), diff --git a/mobile/lib/presentation/pages/dev/media_stat.page.dart b/mobile/lib/presentation/pages/dev/media_stat.page.dart index ec6a260c84..4c18a09200 100644 --- a/mobile/lib/presentation/pages/dev/media_stat.page.dart +++ b/mobile/lib/presentation/pages/dev/media_stat.page.dart @@ -1,10 +1,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; +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/generated/translations.g.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'; import 'package:immich_mobile/routing/router.dart'; @@ -55,11 +56,11 @@ class LocalMediaSummaryPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(context.t.local_media_summary)), + appBar: AppBar(title: Text('local_media_summary'.tr())), body: Consumer( - builder: (ctx, ref, _) { + builder: (ctx, ref, __) { final db = ref.watch(driftProvider); - final albumsFuture = db.localAlbumRepository.getAll(); + final albumsFuture = ref.watch(localAlbumRepository).getAll(); return CustomScrollView( slivers: [ @@ -78,7 +79,7 @@ class LocalMediaSummaryPage extends StatelessWidget { const Divider(), Padding( padding: const EdgeInsets.only(left: 15), - child: Text(ctx.t.album_summary, style: ctx.textTheme.titleMedium), + child: Text("album_summary".tr(), style: ctx.textTheme.titleMedium), ), ], ), @@ -135,11 +136,11 @@ class RemoteMediaSummaryPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(context.t.remote_media_summary)), + appBar: AppBar(title: Text('remote_media_summary'.tr())), body: Consumer( - builder: (ctx, ref, _) { + builder: (ctx, ref, __) { final db = ref.watch(driftProvider); - final albumsFuture = db.remoteAlbumRepository.getAll(); + final albumsFuture = ref.watch(remoteAlbumRepository).getAll(); return CustomScrollView( slivers: [ @@ -158,7 +159,7 @@ class RemoteMediaSummaryPage extends StatelessWidget { const Divider(), Padding( padding: const EdgeInsets.only(left: 15), - child: Text(ctx.t.album_summary, style: ctx.textTheme.titleMedium), + child: Text("album_summary".tr(), style: ctx.textTheme.titleMedium), ), ], ), diff --git a/mobile/lib/presentation/pages/download_info.page.dart b/mobile/lib/presentation/pages/download_info.page.dart index 09a65b4586..e805458e76 100644 --- a/mobile/lib/presentation/pages/download_info.page.dart +++ b/mobile/lib/presentation/pages/download_info.page.dart @@ -1,10 +1,8 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.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/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/download_panel.dart'; import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; @@ -16,12 +14,15 @@ class DownloadInfoPage extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final tasks = ref.watch(downloadStateProvider.select((state) => state.taskProgress)).entries.toList(); - void onCancelDownload(String id) { - unawaited(ref.read(downloadStateProvider.notifier).cancelDownload(id)); + onCancelDownload(String id) { + ref.watch(downloadStateProvider.notifier).cancelDownload(id); } return Scaffold( - appBar: AppBar(title: Text(context.t.download), actions: const []), + appBar: AppBar( + title: Text("download".t(context: context)), + actions: [], + ), body: ListView.builder( physics: const ClampingScrollPhysics(), shrinkWrap: true, @@ -46,7 +47,7 @@ class DownloadInfoPage extends ConsumerWidget { }, style: OutlinedButton.styleFrom(side: BorderSide(color: context.colorScheme.primary)), child: Text( - context.t.clear_all, + 'clear_all'.t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.primary), ), ), diff --git a/mobile/lib/presentation/pages/drift_activities.page.dart b/mobile/lib/presentation/pages/drift_activities.page.dart index 2bb3507e6e..fa5737443f 100644 --- a/mobile/lib/presentation/pages/drift_activities.page.dart +++ b/mobile/lib/presentation/pages/drift_activities.page.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; @@ -7,11 +5,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/activities/comment_bubble.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/drift_activity_text_field.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; -import 'package:immich_mobile/widgets/activities/comment_bubble.dart'; @RoutePage() class DriftActivitiesPage extends HookConsumerWidget { @@ -23,21 +21,17 @@ class DriftActivitiesPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final activityNotifier = ref.watch(albumActivityProvider((album.id, assetId)).notifier); - final activities = ref.watch(albumActivityProvider((album.id, assetId))); + final activityNotifier = ref.read(albumActivityProvider(album.id, assetId).notifier); + final activities = ref.watch(albumActivityProvider(album.id, assetId)); final listViewScrollController = useScrollController(); - Future scrollToBottom() { - return listViewScrollController.animateTo( - 0, - duration: const Duration(milliseconds: 300), - curve: Curves.fastOutSlowIn, - ); + void scrollToBottom() { + listViewScrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.fastOutSlowIn); } Future onAddComment(String comment) async { await activityNotifier.addComment(comment); - unawaited(scrollToBottom()); + scrollToBottom(); } return ProviderScope( @@ -51,7 +45,7 @@ class DriftActivitiesPage extends HookConsumerWidget { if (assetName != null) Text(assetName!, style: context.textTheme.bodySmall), ], ), - actions: const [LikeActivityActionButton(iconOnly: true)], + actions: [const LikeActivityActionButton(iconOnly: true)], actionsPadding: const EdgeInsets.only(right: 8), ), body: activities.widgetWhen( @@ -77,7 +71,7 @@ class DriftActivitiesPage extends HookConsumerWidget { ), Align( alignment: Alignment.bottomCenter, - child: DecoratedBox( + child: Container( decoration: BoxDecoration( color: context.scaffoldBackgroundColor, border: Border(top: BorderSide(color: context.colorScheme.secondaryContainer, width: 1)), diff --git a/mobile/lib/presentation/pages/drift_album.page.dart b/mobile/lib/presentation/pages/drift_album.page.dart index ab91b6f3c8..c9fed636b4 100644 --- a/mobile/lib/presentation/pages/drift_album.page.dart +++ b/mobile/lib/presentation/pages/drift_album.page.dart @@ -37,7 +37,6 @@ class _DriftAlbumsPageState extends ConsumerState { final scrollView = CustomScrollView( controller: _scrollController, - physics: const AlwaysScrollableScrollPhysics(), slivers: [ ImmichSliverAppBar( snap: false, @@ -53,7 +52,7 @@ class _DriftAlbumsPageState extends ConsumerState { ), AlbumSelector( onAlbumSelected: (album) { - unawaited(context.router.push(RemoteAlbumRoute(album: album))); + context.router.push(RemoteAlbumRoute(album: album)); }, ), ], diff --git a/mobile/lib/presentation/pages/drift_album_options.page.dart b/mobile/lib/presentation/pages/drift_album_options.page.dart index 078b9cc325..061edbaf26 100644 --- a/mobile/lib/presentation/pages/drift_album_options.page.dart +++ b/mobile/lib/presentation/pages/drift_album_options.page.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; @@ -32,33 +32,27 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { final userId = ref.watch(authProvider).userId; final activityEnabled = useState(album.isActivityEnabled); final isOwner = album.ownerId == userId; - final owner = isOwner ? ref.watch(currentUserProvider) : null; - final allUsers = isOwner ? null : ref.watch(driftUsersProvider); void showErrorMessage() { - ContextHelper(context).pop(); + context.pop(); ImmichToast.show( context: context, - msg: context.t.shared_album_section_people_action_error, + msg: "shared_album_section_people_action_error".t(context: context), toastType: ToastType.error, gravity: ToastGravity.BOTTOM, ); } - Future leaveAlbum() async { + void leaveAlbum() async { try { await ref.read(remoteAlbumProvider.notifier).leaveAlbum(album.id, userId: userId); - if (!context.mounted) { - return; - } - unawaited(context.navigateTo(const DriftAlbumsRoute())); } catch (_) { showErrorMessage(); } } - Future removeUserFromAlbum(UserDto user) async { + void removeUserFromAlbum(UserDto user) async { try { await ref.read(remoteAlbumProvider.notifier).removeUser(album.id, user.id); ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); @@ -66,7 +60,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { showErrorMessage(); } - ContextHelper(context).pop(); + context.pop(); } Future addUsers() async { @@ -77,27 +71,23 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { } try { - if (!context.mounted) { - return; - } - await ref.read(remoteAlbumProvider.notifier).addUsers(album.id, newUsers); - ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); - if (!context.mounted) { - return; + + if (newUsers.isNotEmpty) { + ImmichToast.show( + context: context, + msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), + toastType: ToastType.success, + ); } + ref.invalidate(remoteAlbumSharedUsersProvider(album.id)); + } catch (e) { ImmichToast.show( context: context, - msg: context.t.users_added_to_album_count(count: newUsers.length), - toastType: ToastType.success, + msg: "Failed to add users to album: ${e.toString()}", + toastType: ToastType.error, ); - } catch (e) { - if (!context.mounted) { - return; - } - - ImmichToast.show(context: context, msg: "Failed to add users to album: $e", toastType: ToastType.error); } } @@ -108,7 +98,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { actions = [ ListTile( leading: const Icon(Icons.exit_to_app_rounded), - title: Text(context.t.leave_album), + title: const Text("leave_album").t(context: context), onTap: leaveAlbum, ), ]; @@ -118,43 +108,39 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { actions = [ ListTile( leading: const Icon(Icons.person_remove_rounded), - title: Text(context.t.remove_user), + title: const Text("remove_user").t(context: context), onTap: () => removeUserFromAlbum(user), ), ]; } - unawaited( - showModalBottomSheet( - backgroundColor: context.colorScheme.surfaceContainer, - isScrollControlled: false, - context: context, - builder: (context) { - return SafeArea( - child: Padding( - padding: const EdgeInsets.only(top: 24.0), - child: Column(mainAxisSize: MainAxisSize.min, children: [...actions]), - ), - ); - }, - ), + showModalBottomSheet( + backgroundColor: context.colorScheme.surfaceContainer, + isScrollControlled: false, + context: context, + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column(mainAxisSize: MainAxisSize.min, children: [...actions]), + ), + ); + }, ); } - Widget buildOwnerInfo() { + buildOwnerInfo() { if (isOwner) { + final owner = ref.watch(currentUserProvider); return ListTile( leading: owner != null ? UserCircleAvatar(user: owner) : const SizedBox(), title: Text(album.ownerName, style: const TextStyle(fontWeight: FontWeight.w500)), subtitle: Text(owner?.email ?? "", style: TextStyle(color: context.colorScheme.onSurfaceSecondary)), - trailing: Text(context.t.owner, style: context.textTheme.labelLarge), + trailing: Text("owner", style: context.textTheme.labelLarge).t(context: context), ); } else { - if (allUsers == null) { - return const SizedBox(); - } - - return allUsers.maybeWhen( + final usersProvider = ref.watch(driftUsersProvider); + return usersProvider.maybeWhen( data: (users) { final user = users.firstWhereOrNull((u) => u.id == album.ownerId); @@ -166,7 +152,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { leading: UserCircleAvatar(user: user), title: Text(user.name, style: const TextStyle(fontWeight: FontWeight.w500)), subtitle: Text(user.email, style: TextStyle(color: context.colorScheme.onSurfaceSecondary)), - trailing: Text(context.t.owner, style: context.textTheme.labelLarge), + trailing: Text("owner", style: context.textTheme.labelLarge).t(context: context), ); }, orElse: () => const SizedBox(), @@ -174,7 +160,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { } } - Widget buildSharedUsersList() { + buildSharedUsersList() { return sharedUsersAsync.maybeWhen( data: (sharedUsers) => ListView.builder( primary: false, @@ -195,7 +181,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { ); } - Padding buildSectionTitle(String text) { + buildSectionTitle(String text) { return Padding( padding: const EdgeInsets.all(16.0), child: Text(text, style: context.textTheme.bodySmall), @@ -211,7 +197,7 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { onPressed: () => context.maybePop(null), ), centerTitle: true, - title: Text(context.t.options), + title: Text("options".t(context: context)), ), body: ListView( children: [ @@ -226,19 +212,19 @@ class DriftAlbumOptionsPage extends HookConsumerWidget { activeThumbColor: activityEnabled.value ? context.primaryColor : context.themeData.disabledColor, dense: true, title: Text( - context.t.comments_and_likes, + "comments_and_likes", style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w500), - ), + ).t(context: context), subtitle: Text( - context.t.let_others_respond, + "let_others_respond", style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), + ).t(context: context), ), - buildSectionTitle(context.t.shared_album_section_people_title), + buildSectionTitle("shared_album_section_people_title".t(context: context)), if (isOwner) ...[ ListTile( leading: const Icon(Icons.person_add_rounded), - title: Text(context.t.invite_people), + title: Text("invite_people".t(context: context)), onTap: () async => addUsers(), ), const Divider(indent: 16), diff --git a/mobile/lib/presentation/pages/drift_archive.page.dart b/mobile/lib/presentation/pages/drift_archive.page.dart index 75c05eb070..ee8417bcad 100644 --- a/mobile/lib/presentation/pages/drift_archive.page.dart +++ b/mobile/lib/presentation/pages/drift_archive.page.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -28,7 +28,10 @@ class DriftArchivePage extends StatelessWidget { }), ], child: Timeline( - appBar: MesmerizingSliverAppBar(title: context.t.archive, icon: Icons.archive_outlined), + appBar: MesmerizingSliverAppBar( + title: 'archive'.t(context: context), + icon: Icons.archive_outlined, + ), bottomSheet: const ArchiveBottomSheet(), ), ); diff --git a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart index d256cc2220..19f813cdb5 100644 --- a/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_selection_timeline.page.dart @@ -5,14 +5,15 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; @RoutePage() -class DriftAssetSelectionTimelinePage extends StatelessWidget { +class DriftAssetSelectionTimelinePage extends ConsumerWidget { final Set lockedSelectionAssets; const DriftAssetSelectionTimelinePage({super.key, this.lockedSelectionAssets = const {}}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return ProviderScope( overrides: [ multiSelectProvider.overrideWith( @@ -21,13 +22,17 @@ class DriftAssetSelectionTimelinePage extends StatelessWidget { ), ), timelineServiceProvider.overrideWith((ref) { - final timelineUsers = ref.watch(timelineUsersProvider).valueOrNull ?? []; - final timelineService = ref.watch(timelineFactoryProvider).main(timelineUsers); + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception('User must be logged in to access asset selection timeline'); + } + + final timelineService = ref.watch(timelineFactoryProvider).remoteAssets(user.id); ref.onDispose(timelineService.dispose); return timelineService; }), ], - child: const Timeline(showStorageIndicator: true), + child: const Timeline(), ); } } diff --git a/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart b/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart index 81d765239b..9da21c72ee 100644 --- a/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart +++ b/mobile/lib/presentation/pages/drift_asset_troubleshoot.page.dart @@ -1,24 +1,22 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; @RoutePage() -class AssetTroubleshootPage extends StatelessWidget { +class AssetTroubleshootPage extends ConsumerWidget { final BaseAsset asset; const AssetTroubleshootPage({super.key, required this.asset}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Scaffold( - appBar: AppBar(title: Text(context.t.asset_troubleshoot)), + appBar: AppBar(title: Text('asset_troubleshoot'.tr())), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), @@ -29,20 +27,20 @@ class AssetTroubleshootPage extends StatelessWidget { } } -class _AssetDetailsView extends StatelessWidget { +class _AssetDetailsView extends ConsumerWidget { final BaseAsset asset; const _AssetDetailsView({required this.asset}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _AssetPropertiesSection(asset: asset), const SizedBox(height: 16), Text( - context.t.matching_assets, + 'matching_assets'.tr(), style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), ), if (asset.checksum != null) ...[ @@ -52,12 +50,12 @@ class _AssetDetailsView extends StatelessWidget { ] else ...[ _PropertySectionCard( title: 'Local Assets', - properties: [_PropertyItem(label: 'Status', value: context.t.no_checksum_local)], + properties: [_PropertyItem(label: 'Status', value: 'no_checksum_local'.tr())], ), const SizedBox(height: 16), _PropertySectionCard( title: 'Remote Assets', - properties: [_PropertyItem(label: 'Status', value: context.t.no_checksum_remote)], + properties: [_PropertyItem(label: 'Status', value: 'no_checksum_remote'.tr())], ), ], ], @@ -80,13 +78,11 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection @override void initState() { super.initState(); - unawaited( - _buildAssetProperties(widget.asset).whenComplete(() { - if (mounted) { - setState(() {}); - } - }), - ); + _buildAssetProperties(widget.asset).whenComplete(() { + if (mounted) { + setState(() {}); + } + }); } @override @@ -116,8 +112,12 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection _PropertyItem(label: 'Updated At', value: asset.updatedAt.toString()), _PropertyItem(label: 'Width', value: asset.width?.toString()), _PropertyItem(label: 'Height', value: asset.height?.toString()), - _PropertyItem(label: 'Duration', value: asset.durationMs != null ? '${asset.durationMs} ms' : null), + _PropertyItem( + label: 'Duration', + value: asset.durationInSeconds != null ? '${asset.durationInSeconds} seconds' : null, + ), _PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()), + _PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId), _PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()), ]); } @@ -154,7 +154,6 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection _PropertyItem(label: 'Thumb Hash', value: asset.thumbHash), _PropertyItem(label: 'Visibility', value: asset.visibility.toString()), _PropertyItem(label: 'Stack ID', value: asset.stackId), - _PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId), ]; properties.insertAll(4, additionalProps); @@ -195,12 +194,8 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection } String _getAssetTypeTitle(BaseAsset asset) { - if (asset is LocalAsset) { - return 'Local Asset'; - } - if (asset is RemoteAsset) { - return 'Remote Asset'; - } + if (asset is LocalAsset) return 'Local Asset'; + if (asset is RemoteAsset) return 'Remote Asset'; return 'Base Asset'; } } @@ -243,7 +238,7 @@ class _LocalAssetsSection extends ConsumerWidget { if (localAssets.isEmpty) { return _PropertySectionCard( title: 'Local Assets', - properties: [_PropertyItem(label: 'Status', value: context.t.no_local_assets_found)], + properties: [_PropertyItem(label: 'Status', value: 'no_local_assets_found'.tr())], ); } @@ -280,8 +275,8 @@ class _RemoteAssetSection extends ConsumerWidget { return const SizedBox.shrink(); } - return FutureBuilder>( - future: assetService.getAllRemoteAssetDebugByChecksum(asset.checksum!), + return FutureBuilder( + future: assetService.getRemoteAssetByChecksum(asset.checksum!), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const _PropertySectionCard( @@ -297,12 +292,12 @@ class _RemoteAssetSection extends ConsumerWidget { ); } - final remoteAsset = snapshot.data?.firstOrNull; + final remoteAsset = snapshot.data; if (remoteAsset == null) { return _PropertySectionCard( title: 'Remote Assets', - properties: [_PropertyItem(label: 'Status', value: context.t.no_remote_assets_found)], + properties: [_PropertyItem(label: 'Status', value: 'no_remote_assets_found'.tr())], ); } @@ -356,7 +351,7 @@ class _PropertyItem extends StatelessWidget { ), Expanded( child: Text( - value ?? context.t.not_available, + value ?? 'not_available'.tr(), style: TextStyle(color: Theme.of(context).colorScheme.secondary), ), ), diff --git a/mobile/lib/presentation/pages/drift_create_album.page.dart b/mobile/lib/presentation/pages/drift_create_album.page.dart index f536b5b60f..f1cbdb13ff 100644 --- a/mobile/lib/presentation/pages/drift_create_album.page.dart +++ b/mobile/lib/presentation/pages/drift_create_album.page.dart @@ -5,12 +5,11 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.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/common/immich_toast.dart'; @RoutePage() class DriftCreateAlbumPage extends ConsumerStatefulWidget { @@ -26,7 +25,6 @@ class _DriftCreateAlbumPageState extends ConsumerState { FocusNode albumTitleTextFieldFocusNode = FocusNode(); FocusNode albumDescriptionTextFieldFocusNode = FocusNode(); bool isAlbumTitleTextFieldFocus = false; - bool isCreatingAlbum = false; Set selectedAssets = {}; @override @@ -49,10 +47,12 @@ class _DriftCreateAlbumPageState extends ConsumerState { super.dispose(); } - bool get _canCreateAlbum => albumTitleController.text.trim().isNotEmpty && !isCreatingAlbum; + bool get _canCreateAlbum => albumTitleController.text.isNotEmpty; String _getEffectiveTitle() { - return albumTitleController.text.isNotEmpty ? albumTitleController.text : context.t.create_album_page_untitled; + return albumTitleController.text.isNotEmpty + ? albumTitleController.text + : 'create_album_page_untitled'.t(context: context); } Widget _buildSliverAppBar() { @@ -91,7 +91,7 @@ class _DriftCreateAlbumPageState extends ConsumerState { Widget _buildEmptyState() { return Padding( padding: const EdgeInsets.only(top: 0, left: 18), - child: Text(context.t.create_shared_album_page_share_add_assets, style: context.textTheme.labelLarge), + child: Text('create_shared_album_page_share_add_assets', style: context.textTheme.labelLarge).t(), ); } @@ -110,9 +110,9 @@ class _DriftCreateAlbumPageState extends ConsumerState { label: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text( - context.t.create_shared_album_page_share_select_photos, + 'create_shared_album_page_share_select_photos', style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), - ), + ).t(), ), ), ); @@ -146,7 +146,7 @@ class _DriftCreateAlbumPageState extends ConsumerState { }); if (albumTitleController.text.isEmpty) { - final untitledText = context.t.create_album_page_untitled; + final untitledText = 'create_album_page_untitled'.t(); albumTitleController.text = untitledText; } } @@ -166,39 +166,31 @@ class _DriftCreateAlbumPageState extends ConsumerState { } Future createAlbum() async { - if (isCreatingAlbum) { + onBackgroundTapped(); + + final title = _getEffectiveTitle().trim(); + if (title.isEmpty) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('create_album_title_required'.t()), backgroundColor: context.colorScheme.error), + ); + } return; } - onBackgroundTapped(); - setState(() => isCreatingAlbum = true); - - final title = _getEffectiveTitle().trim(); - - try { - final album = await ref - .read(remoteAlbumProvider.notifier) - .createAlbumWithAssets( - title: title, - description: albumDescriptionController.text.trim(), - assets: selectedAssets, - ); - - if (!mounted || album == null) { - return; - } + final album = await ref + .watch(remoteAlbumProvider.notifier) + .createAlbum( + title: title, + description: albumDescriptionController.text.trim(), + assetIds: selectedAssets.map((asset) { + final remoteAsset = asset as RemoteAsset; + return remoteAsset.id; + }).toList(), + ); + if (album != null) { unawaited(context.replaceRoute(RemoteAlbumRoute(album: album))); - } catch (_) { - if (!mounted) { - return; - } - - ImmichToast.show(context: context, toastType: ToastType.error, msg: context.t.errors.failed_to_create_album); - } finally { - if (mounted) { - setState(() => isCreatingAlbum = false); - } } } @@ -239,7 +231,7 @@ class _DriftCreateAlbumPageState extends ConsumerState { AlbumActionFilledButton( iconData: Icons.add_photo_alternate_outlined, onPressed: onSelectPhotos, - labelText: context.t.add_photos, + labelText: "add_photos".t(), ), ], ), @@ -255,12 +247,12 @@ class _DriftCreateAlbumPageState extends ConsumerState { centerTitle: false, backgroundColor: context.scaffoldBackgroundColor, leading: IconButton(onPressed: () => context.maybePop(), icon: const Icon(Icons.close_rounded)), - title: Text(context.t.create_album), + title: const Text('create_album').t(), actions: [ TextButton( onPressed: _canCreateAlbum ? createAlbum : null, child: Text( - context.t.create, + 'create'.t(), style: TextStyle( fontWeight: FontWeight.bold, color: _canCreateAlbum ? context.primaryColor : context.themeData.disabledColor, @@ -318,7 +310,7 @@ class _AlbumTitleTextFieldState extends State<_AlbumTitleTextField> { style: TextStyle(fontSize: 28.0, color: context.colorScheme.onSurface, fontWeight: FontWeight.bold), controller: widget.textController, onTap: () { - if (widget.textController.text == context.t.create_album_page_untitled) { + if (widget.textController.text == 'create_album_page_untitled'.t(context: context)) { widget.textController.clear(); } }, @@ -341,7 +333,7 @@ class _AlbumTitleTextFieldState extends State<_AlbumTitleTextField> { borderSide: BorderSide(color: context.primaryColor.withValues(alpha: 0.3)), borderRadius: const BorderRadius.all(Radius.circular(16.0)), ), - hintText: context.t.add_a_title, + hintText: 'add_a_title'.t(), hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith( fontSize: 28.0, fontWeight: FontWeight.bold, @@ -428,7 +420,7 @@ class _AlbumViewerEditableDescriptionState extends State<_AlbumViewerEditableDes focusColor: Colors.grey[300], fillColor: context.scaffoldBackgroundColor, filled: widget.focusNode.hasFocus, - hintText: context.t.add_a_description, + hintText: 'add_a_description'.t(), ), ), ); diff --git a/mobile/lib/presentation/pages/drift_favorite.page.dart b/mobile/lib/presentation/pages/drift_favorite.page.dart index dd4384906b..2bc3f26363 100644 --- a/mobile/lib/presentation/pages/drift_favorite.page.dart +++ b/mobile/lib/presentation/pages/drift_favorite.page.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -28,7 +28,10 @@ class DriftFavoritePage extends StatelessWidget { }), ], child: Timeline( - appBar: MesmerizingSliverAppBar(title: context.t.favorites, icon: Icons.favorite_outline), + appBar: MesmerizingSliverAppBar( + title: 'favorites'.t(context: context), + icon: Icons.favorite_outline, + ), bottomSheet: const FavoriteBottomSheet(), ), ); diff --git a/mobile/lib/presentation/pages/drift_library.page.dart b/mobile/lib/presentation/pages/drift_library.page.dart index 137f46659f..4708b5e615 100644 --- a/mobile/lib/presentation/pages/drift_library.page.dart +++ b/mobile/lib/presentation/pages/drift_library.page.dart @@ -1,18 +1,18 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/local_album_thumbnail.widget.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/people/partner_user_avatar.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/partner.provider.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; @@ -20,11 +20,11 @@ import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @RoutePage() -class DriftLibraryPage extends StatelessWidget { +class DriftLibraryPage extends ConsumerWidget { const DriftLibraryPage({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return const Scaffold( body: CustomScrollView( slivers: [ @@ -55,13 +55,13 @@ class _ActionButtonGrid extends ConsumerWidget { _ActionButton( icon: Icons.favorite_outline_rounded, onTap: () => context.pushRoute(const DriftFavoriteRoute()), - label: context.t.favorites, + label: 'favorites'.t(context: context), ), const SizedBox(width: 8), _ActionButton( icon: Icons.archive_outlined, onTap: () => context.pushRoute(const DriftArchiveRoute()), - label: context.t.archived, + label: 'archived'.t(context: context), ), ], ), @@ -71,14 +71,14 @@ class _ActionButtonGrid extends ConsumerWidget { _ActionButton( icon: Icons.link_outlined, onTap: () => context.pushRoute(const SharedLinkRoute()), - label: context.t.shared_links, + label: 'shared_links'.t(context: context), ), isTrashEnable ? const SizedBox(width: 8) : const SizedBox.shrink(), isTrashEnable ? _ActionButton( icon: Icons.delete_outline_rounded, onTap: () => context.pushRoute(const DriftTrashRoute()), - label: context.t.trash, + label: 'trash'.t(context: context), ) : const SizedBox.shrink(), ], @@ -145,7 +145,7 @@ class _PeopleCollectionCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final people = ref.watch(getAllPeopleProvider); + final people = ref.watch(driftGetAllPeopleProvider); return LayoutBuilder( builder: (context, constraints) { @@ -179,11 +179,7 @@ class _PeopleCollectionCard extends ConsumerWidget { mainAxisSpacing: 8, physics: const NeverScrollableScrollPhysics(), children: people.take(4).map((person) { - return CircleAvatar( - backgroundImage: RemoteImageProvider( - url: getFaceThumbnailUrl(person.id, updatedAt: person.updatedAt), - ), - ); + return CircleAvatar(backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id))); }).toList(), ); }, @@ -192,7 +188,7 @@ class _PeopleCollectionCard extends ConsumerWidget { Padding( padding: const EdgeInsets.all(8.0), child: Text( - context.t.people, + 'people'.t(context: context), style: context.textTheme.titleSmall?.copyWith( color: context.colorScheme.onSurface, fontWeight: FontWeight.w500, @@ -244,7 +240,7 @@ class _PlacesCollectionCard extends StatelessWidget { Padding( padding: const EdgeInsets.all(8.0), child: Text( - context.t.places, + 'places'.t(), style: context.textTheme.titleSmall?.copyWith( color: context.colorScheme.onSurface, fontWeight: FontWeight.w500, @@ -302,7 +298,9 @@ class _LocalAlbumsCollectionCard extends ConsumerWidget { }).toList(); }, error: (error, _) { - return [Center(child: Text(context.t.error_saving_image(error: error.toString())))]; + return [ + Center(child: Text('error_saving_image'.tr(args: [error.toString()]))), + ]; }, loading: () { return [const Center(child: CircularProgressIndicator())]; @@ -314,7 +312,7 @@ class _LocalAlbumsCollectionCard extends ConsumerWidget { Padding( padding: const EdgeInsets.all(8.0), child: Text( - context.t.on_this_device, + 'on_this_device'.t(context: context), style: context.textTheme.titleSmall?.copyWith( color: context.colorScheme.onSurface, fontWeight: FontWeight.w500, @@ -329,29 +327,18 @@ class _LocalAlbumsCollectionCard extends ConsumerWidget { } } -@visibleForTesting -final sharedWithPartnerProvider = StreamProvider.autoDispose>((ref) { - final currentUser = ref.watch(currentUserProvider); - if (currentUser == null) { - // TODO: Refactor with a route guard to avoid this check in every provider - return const .empty(); - } - - return ref.watch(partnerServiceProvider).search(currentUser.id, .sharedWith); -}); - class _QuickAccessButtonList extends ConsumerWidget { const _QuickAccessButtonList(); @override Widget build(BuildContext context, WidgetRef ref) { - final partnerSharedWithAsync = ref.watch(sharedWithPartnerProvider); + final partnerSharedWithAsync = ref.watch(driftSharedWithPartnerProvider); final partners = partnerSharedWithAsync.valueOrNull ?? []; return SliverPadding( padding: const EdgeInsets.only(left: 16, top: 12, right: 16, bottom: 32), sliver: SliverToBoxAdapter( - child: DecoratedBox( + child: Container( decoration: BoxDecoration( border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1), borderRadius: const BorderRadius.all(Radius.circular(20)), @@ -367,7 +354,7 @@ class _QuickAccessButtonList extends ConsumerWidget { ), child: ListView( shrinkWrap: true, - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(0), physics: const NeverScrollableScrollPhysics(), children: [ ListTile( @@ -381,7 +368,7 @@ class _QuickAccessButtonList extends ConsumerWidget { ), leading: const Icon(Icons.folder_outlined, size: 26), title: Text( - context.t.folders, + 'folders'.t(context: context), style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), ), onTap: () => context.pushRoute(FolderRoute()), @@ -389,7 +376,7 @@ class _QuickAccessButtonList extends ConsumerWidget { ListTile( leading: const Icon(Icons.lock_outline_rounded, size: 26), title: Text( - context.t.locked_folder, + 'locked_folder'.t(context: context), style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), ), onTap: () => context.pushRoute(const DriftLockedFolderRoute()), @@ -397,12 +384,12 @@ class _QuickAccessButtonList extends ConsumerWidget { ListTile( leading: const Icon(Icons.group_outlined, size: 26), title: Text( - context.t.partners, + 'partners'.t(context: context), style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w500), ), - onTap: () => context.pushRoute(const PartnerRoute()), + onTap: () => context.pushRoute(const DriftPartnerRoute()), ), - _PartnerList(partners: partners.toList()), + _PartnerList(partners: partners), ], ), ), @@ -414,12 +401,12 @@ class _QuickAccessButtonList extends ConsumerWidget { class _PartnerList extends StatelessWidget { const _PartnerList({required this.partners}); - final List partners; + final List partners; @override Widget build(BuildContext context) { return ListView.builder( - padding: EdgeInsets.zero, + padding: const EdgeInsets.all(0), physics: const NeverScrollableScrollPhysics(), itemCount: partners.length, shrinkWrap: true, @@ -434,11 +421,11 @@ class _PartnerList extends StatelessWidget { ), ), contentPadding: const EdgeInsets.only(left: 12.0, right: 18.0), - leading: PartnerUserAvatar(userId: partner.id, name: partner.name), - title: Text( - context.t.partner_list_user_photos(user: partner.name), - style: const TextStyle(fontWeight: FontWeight.w500), - ), + leading: PartnerUserAvatar(partner: partner), + title: const Text( + "partner_list_user_photos", + style: TextStyle(fontWeight: FontWeight.w500), + ).t(context: context, args: {'user': partner.name}), onTap: () => context.pushRoute(DriftPartnerDetailRoute(partner: partner)), ); }, diff --git a/mobile/lib/presentation/pages/drift_local_album.page.dart b/mobile/lib/presentation/pages/drift_local_album.page.dart index 71707688da..6b133f7a6f 100644 --- a/mobile/lib/presentation/pages/drift_local_album.page.dart +++ b/mobile/lib/presentation/pages/drift_local_album.page.dart @@ -1,9 +1,10 @@ import 'package:auto_route/auto_route.dart'; +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/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/local_album_thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; @@ -48,7 +49,7 @@ class _AlbumList extends ConsumerWidget { if (albums.isEmpty) { return SliverToBoxAdapter( child: Center( - child: Padding(padding: const EdgeInsets.all(20.0), child: Text(context.t.no_albums_yet)), + child: Padding(padding: const EdgeInsets.all(20.0), child: Text('no_albums_yet'.tr())), ), ); } @@ -66,7 +67,7 @@ class _AlbumList extends ConsumerWidget { leading: SizedBox(width: 80, height: 80, child: LocalAlbumThumbnail(albumId: album.id)), title: Text(album.name, style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600)), subtitle: Text( - context.t.items_count(count: album.assetCount), + 'items_count'.t(context: context, args: {'count': album.assetCount}), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), onTap: () => context.pushRoute(LocalTimelineRoute(album: album)), diff --git a/mobile/lib/presentation/pages/drift_locked_folder.page.dart b/mobile/lib/presentation/pages/drift_locked_folder.page.dart index 6ebe7283d3..b19e8468ca 100644 --- a/mobile/lib/presentation/pages/drift_locked_folder.page.dart +++ b/mobile/lib/presentation/pages/drift_locked_folder.page.dart @@ -1,15 +1,12 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; @RoutePage() @@ -37,17 +34,11 @@ class _DriftLockedFolderPageState extends ConsumerState w @override void didChangeAppLifecycleState(AppLifecycleState state) { - if (!mounted) { - return; + if (mounted) { + setState(() { + _showOverlay = state != AppLifecycleState.resumed; + }); } - if (state == AppLifecycleState.paused) { - unawaited(ref.read(authProvider.notifier).lockPinCode()); - unawaited(context.navigateTo(const TabShellRoute())); - return; - } - setState(() { - _showOverlay = state != AppLifecycleState.resumed; - }); } @override @@ -70,7 +61,7 @@ class _DriftLockedFolderPageState extends ConsumerState w : PopScope( onPopInvokedWithResult: (didPop, _) => didPop ? ref.read(authProvider.notifier).lockPinCode() : null, child: Timeline( - appBar: MesmerizingSliverAppBar(title: context.t.locked_folder), + appBar: MesmerizingSliverAppBar(title: 'locked_folder'.t(context: context)), bottomSheet: const LockedFolderBottomSheet(), ), ), diff --git a/mobile/lib/presentation/pages/drift_map.page.dart b/mobile/lib/presentation/pages/drift_map.page.dart index d36ccc350c..96384c97e5 100644 --- a/mobile/lib/presentation/pages/drift_map.page.dart +++ b/mobile/lib/presentation/pages/drift_map.page.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; @@ -13,8 +11,8 @@ class DriftMapPage extends StatelessWidget { const DriftMapPage({super.key, this.initialLocation}); - Future onSettingsPressed(BuildContext context) { - return showModalBottomSheet( + void onSettingsPressed(BuildContext context) { + showModalBottomSheet( elevation: 0.0, showDragHandle: true, isScrollControlled: true, @@ -35,7 +33,7 @@ class DriftMapPage extends StatelessWidget { top: 70, child: IconButton.filled( color: Colors.white, - onPressed: () => ContextHelper(context).pop(), + onPressed: () => context.pop(), icon: const Icon(Icons.arrow_back_ios_new_rounded), style: IconButton.styleFrom( padding: const EdgeInsets.all(8), diff --git a/mobile/lib/presentation/pages/drift_memory.page.dart b/mobile/lib/presentation/pages/drift_memory.page.dart index 3ad7d90657..846f062501 100644 --- a/mobile/lib/presentation/pages/drift_memory.page.dart +++ b/mobile/lib/presentation/pages/drift_memory.page.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -8,13 +6,12 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_bottom_info.widget.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_card.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; -import 'package:immich_mobile/utils/system_ui.utils.dart'; import 'package:immich_mobile/widgets/memories/memory_epilogue.dart'; import 'package:immich_mobile/widgets/memories/memory_progress_indicator.dart'; @@ -49,21 +46,21 @@ class DriftMemoryPage extends HookConsumerWidget { useEffect(() { // Memories is an immersive activity - unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive)); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); return () { // Clean up to normal edge to edge when we are done - unawaited(restoreEdgeToEdge()); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); }; }); - void toNextMemory() { - unawaited(memoryPageController.nextPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn)); + toNextMemory() { + memoryPageController.nextPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn); } void toPreviousMemory() { if (currentMemoryIndex.value > 0) { // Move to the previous memory page - unawaited(memoryPageController.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn)); + memoryPageController.previousPage(duration: const Duration(milliseconds: 500), curve: Curves.easeIn); // Wait for the next frame to ensure the page is built SchedulerBinding.instance.addPostFrameCallback((_) { @@ -85,36 +82,36 @@ class DriftMemoryPage extends HookConsumerWidget { } } - void toNextAsset(int currentAssetIndex) { + toNextAsset(int currentAssetIndex) { if (currentAssetIndex + 1 < currentMemory.value.assets.length) { // Go to the next asset - final PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; + PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; - unawaited(controller.nextPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500))); + controller.nextPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500)); } else { // Go to the next memory since we are at the end of our assets toNextMemory(); } } - void toPreviousAsset(int currentAssetIndex) { + toPreviousAsset(int currentAssetIndex) { if (currentAssetIndex > 0) { // Go to the previous asset - final PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; + PageController controller = memoryAssetPageControllers[currentMemoryIndex.value]; - unawaited(controller.previousPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500))); + controller.previousPage(curve: Curves.easeInOut, duration: const Duration(milliseconds: 500)); } else { // Go to the previous memory since we are at the end of our assets toPreviousMemory(); } } - void updateProgressText() { + updateProgressText() { assetProgress.value = "${currentAssetPage.value + 1}|${currentMemory.value.assets.length}"; } /// Downloads and caches the image for the asset at this [currentMemory]'s index - Future precacheAsset(int index) async { + precacheAsset(int index) async { // Guard index out of range if (index < 0) { return; @@ -155,7 +152,7 @@ class DriftMemoryPage extends HookConsumerWidget { // Precache the next page right away if we are on the first page if (currentAssetPage.value == 0) { - unawaited(Future.delayed(const Duration(milliseconds: 200)).then((_) => precacheAsset(1))); + Future.delayed(const Duration(milliseconds: 200)).then((_) => precacheAsset(1)); } Future onAssetChanged(int otherIndex) async { @@ -163,25 +160,12 @@ class DriftMemoryPage extends HookConsumerWidget { currentAssetPage.value = otherIndex; updateProgressText(); - final activeMemory = currentMemory.value; - // Wait for page change animation to finish await Future.delayed(const Duration(milliseconds: 400)); - - // check if memory is still the same and if context is still mounted - if (currentMemory.value != activeMemory || !context.mounted) { - return; - } - // And then precache the next asset await precacheAsset(otherIndex + 1); - // check again as precache involves async operations - if (currentMemory.value != activeMemory || !context.mounted) { - return; - } - - final asset = activeMemory.assets[otherIndex]; + final asset = currentMemory.value.assets[otherIndex]; currentAsset.value = asset; ref.read(assetViewerProvider.notifier).setAsset(asset); } @@ -200,7 +184,7 @@ class DriftMemoryPage extends HookConsumerWidget { final offset = notification.metrics.pixels; if (isEpiloguePage && (offset > notification.metrics.maxScrollExtent + 150)) { - unawaited(context.maybePop()); + context.maybePop(); return true; } } @@ -248,7 +232,7 @@ class DriftMemoryPage extends HookConsumerWidget { } final yearsAgo = DateTime.now().year - memories[mIndex].data.year; - final title = context.t.years_ago(years: yearsAgo); + final title = 'years_ago'.t(context: context, args: {'years': yearsAgo.toString()}); // Build horizontal page final assetController = memoryAssetPageControllers[mIndex]; return Column( @@ -283,7 +267,7 @@ class DriftMemoryPage extends HookConsumerWidget { final asset = memories[mIndex].assets[index]; return Stack( children: [ - ColoredBox( + Container( color: Colors.black, child: DriftMemoryCard( asset: asset, @@ -330,8 +314,8 @@ class DriftMemoryPage extends HookConsumerWidget { // auto_route doesn't invoke pop scope, so // turn off full screen mode here // https://github.com/Milad-Akarie/auto_route_library/issues/1799 - unawaited(context.maybePop()); - unawaited(restoreEdgeToEdge()); + context.maybePop(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); }, shape: const CircleBorder(), color: Colors.white.withValues(alpha: 0.2), diff --git a/mobile/lib/presentation/pages/drift_partner_detail.page.dart b/mobile/lib/presentation/pages/drift_partner_detail.page.dart index 70de434d9f..f8a19b6b70 100644 --- a/mobile/lib/presentation/pages/drift_partner_detail.page.dart +++ b/mobile/lib/presentation/pages/drift_partner_detail.page.dart @@ -8,13 +8,13 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; @RoutePage() class DriftPartnerDetailPage extends StatelessWidget { - final Partner partner; + final PartnerUserDto partner; const DriftPartnerDetailPage({super.key, required this.partner}); @@ -39,7 +39,7 @@ class DriftPartnerDetailPage extends StatelessWidget { } class _InfoBox extends ConsumerStatefulWidget { - final Partner partner; + final PartnerUserDto partner; const _InfoBox({required this.partner}); @@ -56,32 +56,27 @@ class _InfoBoxState extends ConsumerState<_InfoBox> { _inTimeline = widget.partner.inTimeline; } - Future _toggleInTimeline() async { + _toggleInTimeline() async { final user = ref.read(currentUserProvider); if (user == null) { return; } try { - await ref - .read(partnerServiceProvider) - .update(sharedById: widget.partner.id, sharedWithId: user.id, inTimeline: !_inTimeline); + await ref.read(partnerUsersProvider.notifier).toggleShowInTimeline(widget.partner.id, user.id); setState(() { _inTimeline = !_inTimeline; }); } catch (error, stack) { dPrint(() => "Failed to toggle in timeline: $error $stack"); - if (!mounted) { - return; - } - ImmichToast.show( context: context, toastType: ToastType.error, durationInSecond: 1, msg: "Failed to toggle the timeline setting", ); + return; } } @@ -92,7 +87,7 @@ class _InfoBoxState extends ConsumerState<_InfoBox> { height: 110, child: Padding( padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0), - child: DecoratedBox( + child: Container( decoration: BoxDecoration( border: Border.all(color: context.colorScheme.onSurface.withAlpha(10), width: 1), borderRadius: const BorderRadius.all(Radius.circular(20)), diff --git a/mobile/lib/presentation/pages/drift_people_collection.page.dart b/mobile/lib/presentation/pages/drift_people_collection.page.dart index 92f1ff79ca..d34ce3e776 100644 --- a/mobile/lib/presentation/pages/drift_people_collection.page.dart +++ b/mobile/lib/presentation/pages/drift_people_collection.page.dart @@ -1,14 +1,10 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/string_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/people.utils.dart'; @@ -34,7 +30,7 @@ class _DriftPeopleCollectionPageState extends ConsumerState _formFocus.unfocus(), onChanged: (value) => setState(() => _search = value), filled: true, - hintText: context.t.filter_people, + hintText: 'filter_people'.tr(), autofocus: true, ) - : Text(context.t.people), + : Text('people'.tr()), actions: [ IconButton( icon: Icon(_search != null ? Icons.close : Icons.search), @@ -66,15 +62,10 @@ class _DriftPeopleCollectionPageState extends ConsumerState filtered; if (_search != null) { - filtered = people.where((person) { - return person.name.toLowerCase().removeDiacritics().contains( - _search!.toLowerCase().removeDiacritics(), - ); + people = people.where((person) { + return person.name.toLowerCase().contains(_search!.toLowerCase()); }).toList(); - } else { - filtered = people; } return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( @@ -83,26 +74,24 @@ class _DriftPeopleCollectionPageState extends ConsumerState showNameEditModal(context, person), child: person.name.isEmpty ? Text( - context.t.add_a_name, + 'add_a_name'.tr(), style: context.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w500, color: context.colorScheme.primary, diff --git a/mobile/lib/presentation/pages/drift_person.page.dart b/mobile/lib/presentation/pages/drift_person.page.dart index cad3b7b6a6..ebd4954a51 100644 --- a/mobile/lib/presentation/pages/drift_person.page.dart +++ b/mobile/lib/presentation/pages/drift_person.page.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -14,7 +12,7 @@ import 'package:immich_mobile/widgets/common/person_sliver_app_bar.dart'; @RoutePage() class DriftPersonPage extends ConsumerStatefulWidget { - final Person person; + final DriftPerson person; const DriftPersonPage({super.key, required this.person}); @@ -23,10 +21,10 @@ class DriftPersonPage extends ConsumerStatefulWidget { } class _DriftPersonPageState extends ConsumerState { - late Person _person; + late DriftPerson _person; @override - void initState() { + initState() { super.initState(); _person = widget.person; } @@ -51,8 +49,8 @@ class _DriftPersonPageState extends ConsumerState { } } - Future showOptionSheet(BuildContext context) { - return showModalBottomSheet( + void showOptionSheet(BuildContext context) { + showModalBottomSheet( context: context, backgroundColor: context.colorScheme.surface, isScrollControlled: false, @@ -60,11 +58,11 @@ class _DriftPersonPageState extends ConsumerState { return PersonOptionSheet( onEditName: () async { await handleEditName(context); - ContextHelper(context).pop(); + context.pop(); }, onEditBirthday: () async { await handleEditBirthday(context); - ContextHelper(context).pop(); + context.pop(); }, birthdayExists: _person.birthDate != null, ); diff --git a/mobile/lib/presentation/pages/drift_place.page.dart b/mobile/lib/presentation/pages/drift_place.page.dart index 0d33f2ab29..10b9ca7ae4 100644 --- a/mobile/lib/presentation/pages/drift_place.page.dart +++ b/mobile/lib/presentation/pages/drift_place.page.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; @@ -62,10 +62,10 @@ class _PlaceSliverAppBar extends HookWidget { onTapOutside: (_) => searchFocusNode.unfocus(), onChanged: (value) => search.value = value, filled: true, - hintText: context.t.filter_places, + hintText: 'filter_places'.t(context: context), autofocus: true, ) - : Text(context.t.places), + : Text('places'.t(context: context)), actions: [ IconButton( icon: Icon(search.value != null ? Icons.close : Icons.search), @@ -94,7 +94,7 @@ class _Map extends StatelessWidget { height: 200, width: context.width, child: MapThumbnail( - onTap: (_, _) => context.pushRoute(DriftMapRoute(initialLocation: currentLocation)), + onTap: (_, __) => context.pushRoute(DriftMapRoute(initialLocation: currentLocation)), zoom: 8, centre: currentLocation ?? const LatLng(21.44950, -157.91959), showAttribution: false, @@ -134,19 +134,16 @@ class _PlaceList extends ConsumerWidget { ), ), data: (places) { - final List<(String, String)> filtered; if (search.value != null) { - filtered = places.where((place) { + places = places.where((place) { return place.$1.toLowerCase().contains(search.value!.toLowerCase()); }).toList(); - } else { - filtered = places; } return SliverList.builder( - itemCount: filtered.length, + itemCount: places.length, itemBuilder: (context, index) { - final place = filtered[index]; + final place = places[index]; return _PlaceTile(place: place); }, ); diff --git a/mobile/lib/presentation/pages/drift_recently_added.page.dart b/mobile/lib/presentation/pages/drift_recently_added.page.dart deleted file mode 100644 index 841fb9a671..0000000000 --- a/mobile/lib/presentation/pages/drift_recently_added.page.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/widgets.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; - -@RoutePage() -class DriftRecentlyAddedPage extends StatelessWidget { - const DriftRecentlyAddedPage({super.key}); - - @override - Widget build(BuildContext context) { - return ProviderScope( - overrides: [ - timelineServiceProvider.overrideWith((ref) { - final user = ref.watch(currentUserProvider); - if (user == null) { - throw Exception('User must be logged in to access recently taken'); - } - - final timelineService = ref.watch(timelineFactoryProvider).recentlyAdded(user.id); - ref.onDispose(timelineService.dispose); - return timelineService; - }), - ], - child: Timeline(appBar: MesmerizingSliverAppBar(title: context.t.recently_added)), - ); - } -} diff --git a/mobile/lib/presentation/pages/drift_recently_taken.page.dart b/mobile/lib/presentation/pages/drift_recently_taken.page.dart index 22d89770fe..ceb121b124 100644 --- a/mobile/lib/presentation/pages/drift_recently_taken.page.dart +++ b/mobile/lib/presentation/pages/drift_recently_taken.page.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -26,7 +26,7 @@ class DriftRecentlyTakenPage extends StatelessWidget { return timelineService; }), ], - child: Timeline(appBar: MesmerizingSliverAppBar(title: context.t.recently_taken)), + child: Timeline(appBar: MesmerizingSliverAppBar(title: 'recently_taken'.t())), ); } } diff --git a/mobile/lib/presentation/pages/drift_remote_album.page.dart b/mobile/lib/presentation/pages/drift_remote_album.page.dart index 7669b6c765..ba9ccf2ffd 100644 --- a/mobile/lib/presentation/pages/drift_remote_album.page.dart +++ b/mobile/lib/presentation/pages/drift_remote_album.page.dart @@ -7,8 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/album/pending_uploads_banner.widget.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/remote_album/drift_album_option.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; @@ -40,11 +39,7 @@ class _RemoteAlbumPageState extends ConsumerState { } Future addAssets(BuildContext context) async { - final notifier = ref.read(remoteAlbumProvider.notifier); - final albumAssets = await notifier.getAssets(_album.id); - if (!context.mounted) { - return; - } + final albumAssets = await ref.read(remoteAlbumProvider.notifier).getAssets(_album.id); final newAssets = await context.pushRoute>( DriftAssetSelectionTimelineRoute(lockedSelectionAssets: albumAssets.toSet()), @@ -54,15 +49,20 @@ class _RemoteAlbumPageState extends ConsumerState { return; } - final added = await notifier.addAssetsToAlbum(_album.id, newAssets); - if (!context.mounted) { - return; - } + final added = await ref + .read(remoteAlbumProvider.notifier) + .addAssets( + _album.id, + newAssets.map((asset) { + final remoteAsset = asset as RemoteAsset; + return remoteAsset.id; + }).toList(), + ); if (added > 0) { ImmichToast.show( context: context, - msg: context.t.assets_added_to_album_count(count: added), + msg: "assets_added_to_album_count".t(context: context, args: {'count': added.toString()}), toastType: ToastType.success, ); } @@ -76,27 +76,23 @@ class _RemoteAlbumPageState extends ConsumerState { } try { - if (!context.mounted) { - return; - } - await ref.read(remoteAlbumProvider.notifier).addUsers(_album.id, newUsers); - ref.invalidate(remoteAlbumSharedUsersProvider(_album.id)); - if (!context.mounted) { - return; + + if (newUsers.isNotEmpty) { + ImmichToast.show( + context: context, + msg: "users_added_to_album_count".t(context: context, args: {'count': newUsers.length}), + toastType: ToastType.success, + ); } + ref.invalidate(remoteAlbumSharedUsersProvider(_album.id)); + } catch (e) { ImmichToast.show( context: context, - msg: context.t.users_added_to_album_count(count: newUsers.length), - toastType: ToastType.success, + msg: "Failed to add users to album: ${e.toString()}", + toastType: ToastType.error, ); - } catch (e) { - if (!context.mounted) { - return; - } - - ImmichToast.show(context: context, msg: "Failed to add users to album: $e", toastType: ToastType.error); } } @@ -111,21 +107,24 @@ class _RemoteAlbumPageState extends ConsumerState { context: context, builder: (BuildContext context) { return AlertDialog( - title: Text(context.t.delete_album), + title: Text('delete_album'.t(context: context)), content: Column( mainAxisSize: MainAxisSize.min, children: [ - Text(context.t.album_delete_confirmation(album: _album.name)), + Text('album_delete_confirmation'.t(context: context, args: {'album': _album.name})), const SizedBox(height: 8), - Text(context.t.album_delete_confirmation_description), + Text('album_delete_confirmation_description'.t(context: context)), ], ), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(false), child: Text(context.t.cancel)), + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('cancel'.t(context: context)), + ), TextButton( onPressed: () => Navigator.of(context).pop(true), style: TextButton.styleFrom(foregroundColor: Theme.of(context).colorScheme.error), - child: Text(context.t.delete_album), + child: Text('delete_album'.t(context: context)), ), ], ); @@ -134,26 +133,19 @@ class _RemoteAlbumPageState extends ConsumerState { if (confirmed == true) { try { - if (!context.mounted) { - return; - } - await ref.read(remoteAlbumProvider.notifier).deleteAlbum(_album.id); - if (!context.mounted) { - return; - } - - ImmichToast.show(context: context, msg: context.t.album_deleted, toastType: ToastType.success); - - unawaited(context.pushRoute(const DriftAlbumsRoute())); - } catch (e) { - if (!context.mounted) { - return; - } ImmichToast.show( context: context, - msg: context.t.album_viewer_appbar_share_err_delete, + msg: 'album_deleted'.t(context: context), + toastType: ToastType.success, + ); + + unawaited(context.pushRoute(const DriftAlbumsRoute())); + } catch (e) { + ImmichToast.show( + context: context, + msg: 'album_viewer_appbar_share_err_delete'.t(context: context), toastType: ToastType.error, ); } @@ -167,11 +159,7 @@ class _RemoteAlbumPageState extends ConsumerState { builder: (context) => _EditAlbumDialog(album: _album), ); - if (!context.mounted) { - return; - } - - if (result != null) { + if (result != null && context.mounted) { setState(() { _album = _album.copyWith(name: result.name, description: result.description ?? ''); }); @@ -198,7 +186,6 @@ class _RemoteAlbumPageState extends ConsumerState { currentRemoteAlbumScopedProvider.overrideWithValue(_album), ], child: Timeline( - topSliverWidget: PendingUploadsBanner(albumId: _album.id), appBar: RemoteAlbumSliverAppBar( icon: Icons.photo_album_outlined, kebabMenu: _AlbumKebabMenu( @@ -258,9 +245,7 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { } Future _handleSave() async { - if (formKey.currentState?.validate() != true) { - return; - } + if (formKey.currentState?.validate() != true) return; try { final newTitle = titleController.text.trim(); @@ -269,19 +254,20 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { await ref .read(remoteAlbumProvider.notifier) .updateAlbum(widget.album.id, name: newTitle, description: newDescription); - if (!mounted) { - return; - } - Navigator.of( - context, - ).pop(_EditAlbumData(name: newTitle, description: newDescription.isEmpty ? null : newDescription)); + if (mounted) { + Navigator.of( + context, + ).pop(_EditAlbumData(name: newTitle, description: newDescription.isEmpty ? null : newDescription)); + } } catch (e) { - if (!mounted) { - return; + if (mounted) { + ImmichToast.show( + context: context, + msg: 'album_update_error'.t(context: context), + toastType: ToastType.error, + ); } - - ImmichToast.show(context: context, msg: context.t.errors.unable_to_update_album_info, toastType: ToastType.error); } } @@ -304,14 +290,14 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { children: [ Icon(Icons.edit_outlined, color: context.colorScheme.primary, size: 24), const SizedBox(width: 12), - Text(context.t.edit_album, style: context.textTheme.titleMedium), + Text('edit_album'.t(context: context), style: context.textTheme.titleMedium), ], ), const SizedBox(height: 24), // Album Name Text( - context.t.album_name.toUpperCase(), + 'album_name'.t(context: context).toUpperCase(), style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 4), @@ -326,7 +312,7 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { ), validator: (value) { if (value == null || value.trim().isEmpty) { - return context.t.name_required; + return 'album_name_required'.t(context: context); } return null; @@ -336,7 +322,7 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { // Description Text( - context.t.description.toUpperCase(), + 'description'.t(context: context).toUpperCase(), style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.w600), ), const SizedBox(height: 4), @@ -356,9 +342,15 @@ class _EditAlbumDialogState extends ConsumerState<_EditAlbumDialog> { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - TextButton(onPressed: () => Navigator.of(context).pop(null), child: Text(context.t.cancel)), + TextButton( + onPressed: () => Navigator.of(context).pop(null), + child: Text('cancel'.t(context: context)), + ), const SizedBox(width: 12), - FilledButton(onPressed: _handleSave, child: Text(context.t.save)), + FilledButton( + onPressed: _handleSave, + child: Text('save'.t(context: context)), + ), ], ), ], @@ -422,7 +414,7 @@ class _AlbumKebabMenu extends ConsumerWidget { return FutureBuilder( future: ref - .watch(remoteAlbumServiceProvider) + .read(remoteAlbumServiceProvider) .getUserRole(album.id, user?.id ?? '') .then((role) => role == AlbumUserRole.editor), builder: (context, snapshot) { diff --git a/mobile/lib/presentation/pages/drift_slideshow.page.dart b/mobile/lib/presentation/pages/drift_slideshow.page.dart deleted file mode 100644 index 2662c60a30..0000000000 --- a/mobile/lib/presentation/pages/drift_slideshow.page.dart +++ /dev/null @@ -1,593 +0,0 @@ -import 'dart:async'; -import 'dart:math'; -import 'dart:ui'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/config/slideshow_config.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/scroll_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/pages/common/settings.page.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; -import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/utils/system_ui.utils.dart'; -import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; -import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; -import 'package:wakelock_plus/wakelock_plus.dart'; - -@RoutePage() -class DriftSlideshowPage extends ConsumerStatefulWidget { - final TimelineService timeline; - - const DriftSlideshowPage({super.key, required this.timeline}); - - @override - ConsumerState createState() => _DriftSlideshowPageState(); -} - -class _DriftSlideshowPageState extends ConsumerState with SingleTickerProviderStateMixin { - static const double _kenBurnsZoom = 0.1; - - late SlideshowConfig _config; - late final PageController _pageController; - late final Stopwatch _stopwatch; - late Timer _timer; - late int _index; - late int _nextIndex; - bool _paused = false; - bool _showAppBar = false; - - late final AnimationController _crossfadeController; - late final Animation _crossfadeOpacity; - int? _crossfadeFromIndex; - int? _crossfadeToIndex; - int _zoomCycle = 0; - bool _disableAnimations = false; - - @override - void initState() { - super.initState(); - _config = ref.read(appConfigProvider.select((s) => s.slideshow)); - final asset = ref.read(assetViewerProvider).currentAsset; - _index = asset == null ? 0 : widget.timeline.getIndex(asset.heroTag) ?? 0; - _pageController = PageController(initialPage: _index); - _crossfadeController = AnimationController(vsync: this, duration: Durations.extralong2); - _crossfadeOpacity = Tween(begin: 1.0, end: 0.0).animate(_crossfadeController); - _stopwatch = Stopwatch(); - _createTimer(); - _updateNextIndex(); - ref.listenManual(appConfigProvider.select((s) => s.slideshow), _onConfigChanged); - - unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive)); - unawaited(WakelockPlus.enable()); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _disableAnimations = MediaQuery.disableAnimationsOf(context); - } - - @override - void dispose() { - _timer.cancel(); - _stopwatch.stop(); - _pageController.dispose(); - _crossfadeController.dispose(); - unawaited(WakelockPlus.disable()); - unawaited(restoreEdgeToEdge()); - super.dispose(); - } - - void _play() { - final asset = widget.timeline.getAssetSafe(_index)!; - - if (asset.isImage) { - _createTimer(); - } else if (ref.read(videoPlayerProvider(asset.id)).status == VideoPlaybackStatus.paused) { - unawaited(ref.read(videoPlayerProvider(asset.id).notifier).play()); - } else { - unawaited(_nextPage()); - } - - _updateNextIndex(); - - setState(() { - _paused = false; - }); - } - - void _pause() { - _timer.cancel(); - _stopwatch.stop(); - - final asset = widget.timeline.getAssetSafe(_index)!; - - if (!asset.isImage) { - unawaited(ref.read(videoPlayerProvider(asset.id).notifier).pause()); - } - - setState(() { - _paused = true; - }); - } - - void _onConfigChanged(SlideshowConfig? previous, SlideshowConfig next) { - if (_config == next) { - return; - } - - final durationChanged = _config.duration != next.duration; - _config = next; - _updateNextIndex(); - - final asset = widget.timeline.getAssetSafe(_index); - if (durationChanged && !_paused && asset?.isImage == true) { - _timer.cancel(); - _createTimer(); - } - - setState(() {}); - } - - void _updateNextIndex() { - _nextIndex = switch (_config.direction) { - SlideshowDirection.forward => _index + 1, - SlideshowDirection.backward => _index - 1, - SlideshowDirection.shuffle => widget.timeline.getIndex(widget.timeline.getRandomAsset().heroTag)!, - }; - - if (!widget.timeline.hasRange(_nextIndex, 1)) { - unawaited(widget.timeline.preloadAssets(_nextIndex)); - } - } - - Future _nextPage() async { - if (_nextIndex < 0 || _nextIndex >= widget.timeline.totalAssets) { - if (_config.repeat) { - final wrapped = _config.direction == SlideshowDirection.forward ? 0 : widget.timeline.totalAssets - 1; - await widget.timeline.preloadAssets(wrapped); - _pageController.jumpToPage(wrapped); - } else { - setState(() { - _paused = true; - }); - } - return; - } - - if (!widget.timeline.hasRange(_nextIndex, 1)) { - await widget.timeline.preloadAssets(_nextIndex); - } - - _crossFadeToPage(_nextIndex); - } - - void _crossFadeToPage(int page) { - if (_disableAnimations) { - _pageController.jumpToPage(page); - return; - } - - final previousIndex = _index; - _pageController.jumpToPage(page); - setState(() { - _crossfadeFromIndex = previousIndex; - _crossfadeToIndex = page; - }); - unawaited( - _crossfadeController.forward(from: 0.0).whenComplete(() { - if (mounted) { - setState(() { - _crossfadeFromIndex = null; - _crossfadeToIndex = null; - }); - } - }), - ); - } - - Widget _getCrossfadeLayer(BuildContext context, int index, {required bool isIncoming}) { - final asset = widget.timeline.getAssetSafe(index); - - final Widget child; - if (isIncoming && asset?.isImage == true) { - child = _getPhotoView(context, index); - } else { - final zoomOut = isIncoming ? _zoomCycle.isOdd : _zoomCycle.isEven; - final zoom = isIncoming ? (zoomOut ? 1.0 : 0.0) : (zoomOut ? 0.0 : 1.0); - child = _getCrossfadeChild(context, index, zoom); - } - - return Stack( - fit: StackFit.expand, - children: [if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index), child], - ); - } - - Widget _getCrossfadeChild(BuildContext context, int index, double zoom) { - final asset = widget.timeline.getAssetSafe(index); - - if (asset == null) { - return const SizedBox.shrink(); - } - - final scale = _config.look == SlideshowLook.cover - ? PhotoViewComputedScale.covered - : PhotoViewComputedScale.contained; - - return PhotoView( - imageProvider: getFullImageProvider(asset, size: context.sizeData), - index: index, - disableScaleGestures: true, - gaplessPlayback: true, - filterQuality: FilterQuality.high, - initialScale: scale * (1.0 + zoom * _kenBurnsZoom), - controller: PhotoViewController(), - ); - } - - void _createTimer() { - _timer = Timer(Duration(milliseconds: _config.duration * 1000 - _stopwatch.elapsedMilliseconds), () { - _stopwatch.stop(); - _stopwatch.reset(); - unawaited(_nextPage()); - }); - - _stopwatch.start(); - } - - void _pageChanged(int page) { - final asset = widget.timeline.getAssetSafe(page)!; - - setState(() { - _index = page; - _zoomCycle++; - - if (!asset.isImage) { - _paused = false; - } - }); - - _timer.cancel(); - _stopwatch.stop(); - _stopwatch.reset(); - - if (!_paused && asset.isImage) { - _createTimer(); - } - - _updateNextIndex(); - } - - Future _onTapUp() async { - await (_showAppBar ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive) : restoreEdgeToEdge()); - - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - _showAppBar = !_showAppBar; - }); - }); - } - - Widget _getProgressBar(BuildContext context) { - final asset = widget.timeline.getAssetSafe(_index); - - if (asset == null) { - return Container(); - } - - if (asset.isImage) { - return _SlideshowProgressBar( - key: Key(_index.toString()), - durationMs: _config.duration * 1000, - elapsedMs: _stopwatch.elapsedMilliseconds, - paused: _paused, - color: context.colorScheme.primary, - ); - } else { - return _VideoProgressBar(asset: asset); - } - } - - Widget _getBlur(BuildContext context, int index) { - final asset = widget.timeline.getAssetSafe(index); - - if (asset == null) { - return Container(); - } - - return ImageFiltered( - imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), - child: DecoratedBox( - decoration: BoxDecoration( - image: DecorationImage( - image: getFullImageProvider(asset, size: Size(context.width, context.height)), - fit: BoxFit.cover, - ), - ), - child: Container(color: Colors.black.withValues(alpha: 0.2)), - ), - ); - } - - Widget _getPhotoView(BuildContext context, int index) { - final asset = widget.timeline.getAssetSafe(index); - - if (asset == null) { - return const Center(child: ImmichLoadingIndicator()); - } - - final scale = _config.look == SlideshowLook.cover - ? PhotoViewComputedScale.covered - : PhotoViewComputedScale.contained; - final isCurrent = _index == index; - final imageProvider = getFullImageProvider(asset, size: context.sizeData); - - if (asset.isImage) { - PhotoView buildPhotoView(PhotoViewComputedScale initialScale) => PhotoView( - imageProvider: imageProvider, - index: index, - disableScaleGestures: true, - gaplessPlayback: true, - filterQuality: FilterQuality.high, - initialScale: initialScale, - controller: PhotoViewController(), - onTapUp: (_, _, _) => _onTapUp(), - ); - - if (_disableAnimations) { - return buildPhotoView(scale); - } - - final zoomOut = _zoomCycle.isOdd; - final elapsed = _stopwatch.elapsedMilliseconds; - final duration = _config.duration * 1000; - final progress = zoomOut ? 1.0 - elapsed / duration.toDouble() : elapsed / duration.toDouble(); - - return TweenAnimationBuilder( - tween: Tween( - begin: progress, - end: _paused - ? progress - : zoomOut - ? 0.0 - : 1.0, - ), - duration: Duration(milliseconds: _paused ? 1 : max(duration - elapsed, 1)), - builder: (context, value, _) => buildPhotoView(scale * (1.0 + value * _kenBurnsZoom)), - ); - } else { - return _VideoChild( - asset: asset, - isCurrent: isCurrent, - scale: scale, - imageProvider: imageProvider, - onTapUp: _onTapUp, - onCompleted: _nextPage, - ); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: PreferredSize( - preferredSize: Size(AppBar().preferredSize.width, AppBar().preferredSize.height + 5), - child: IgnorePointer( - ignoring: !_showAppBar, - child: AnimatedOpacity( - opacity: _showAppBar ? 1.0 : 0.0, - duration: Durations.short2, - child: Column( - children: [ - AppBar( - backgroundColor: context.scaffoldBackgroundColor, - title: Text(context.t.slideshow), - actions: [ - IconButton( - onPressed: _paused ? _play : _pause, - icon: Icon(_paused ? Icons.play_arrow : Icons.pause), - ), - IconButton( - onPressed: () { - _pause(); - unawaited(context.pushRoute(SettingsSubRoute(section: SettingSection.assetViewer))); - }, - icon: const Icon(Icons.settings), - ), - ], - ), - _getProgressBar(context), - ], - ), - ), - ), - ), - extendBody: true, - extendBodyBehindAppBar: true, - backgroundColor: Colors.black, - body: Stack( - children: [ - PhotoViewGestureDetectorScope( - axis: Axis.horizontal, - child: PageView.builder( - controller: _pageController, - physics: const FastClampingScrollPhysics(), - itemCount: widget.timeline.totalAssets, - onPageChanged: _pageChanged, - itemBuilder: (context, index) => Stack( - children: [ - if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index), - _getPhotoView(context, index), - ], - ), - ), - ), - if (_crossfadeFromIndex != null && _crossfadeToIndex != null) - Positioned.fill( - child: IgnorePointer( - child: Stack( - fit: StackFit.expand, - children: [ - const ColoredBox(color: Colors.black), - FadeTransition( - opacity: _crossfadeController, - child: _getCrossfadeLayer(context, _crossfadeToIndex!, isIncoming: true), - ), - FadeTransition( - opacity: _crossfadeOpacity, - child: _getCrossfadeLayer(context, _crossfadeFromIndex!, isIncoming: false), - ), - ], - ), - ), - ), - ], - ), - ); - } -} - -class _VideoChild extends ConsumerWidget { - final BaseAsset asset; - final bool isCurrent; - final PhotoViewComputedScale scale; - final ImageProvider imageProvider; - final VoidCallback onTapUp; - final VoidCallback onCompleted; - - const _VideoChild({ - required this.asset, - required this.isCurrent, - required this.scale, - required this.imageProvider, - required this.onTapUp, - required this.onCompleted, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - ref.listen(videoPlayerProvider(asset.id).select((s) => s.status), (_, status) { - if (status == VideoPlaybackStatus.completed) { - if (isCurrent && ref.read(videoPlayerProvider(asset.id)).position.inMicroseconds > 0) { - onCompleted(); - } - } else if (status == VideoPlaybackStatus.playing) { - unawaited(ref.read(videoPlayerProvider(asset.id).notifier).setLoop(false)); - } - }); - - return PhotoView.customChild( - onTapUp: (_, _, _) => onTapUp(), - disableScaleGestures: true, - filterQuality: FilterQuality.high, - initialScale: scale, - child: NativeVideoViewer( - asset: asset, - isCurrent: isCurrent, - image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), - ), - ); - } -} - -class _VideoProgressBar extends ConsumerWidget { - final BaseAsset asset; - - const _VideoProgressBar({required this.asset}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final position = ref.watch(videoPlayerProvider(asset.id).select((s) => s.position)); - - return LinearProgressIndicator( - color: context.colorScheme.primary, - borderRadius: BorderRadius.zero, - minHeight: 5, - value: position.inMilliseconds / asset.duration.inMilliseconds, - ); - } -} - -/// Progress bar for image slides, driven by an explicit [AnimationController]. -/// -/// [TweenAnimationBuilder] creates its controller internally with the default -/// [AnimationBehavior.normal], which makes it run ~20x too fast while the system -/// "reduce motion" setting is on (flutter/flutter#164287). This owns its -/// controller so it can use [AnimationBehavior.preserve] and animate at the real -/// slide duration regardless of that setting. -class _SlideshowProgressBar extends StatefulWidget { - final int durationMs; - final int elapsedMs; - final bool paused; - final Color color; - - const _SlideshowProgressBar({ - super.key, - required this.durationMs, - required this.elapsedMs, - required this.paused, - required this.color, - }); - - @override - State<_SlideshowProgressBar> createState() => _SlideshowProgressBarState(); -} - -class _SlideshowProgressBarState extends State<_SlideshowProgressBar> with SingleTickerProviderStateMixin { - late final AnimationController _controller; - - @override - void initState() { - super.initState(); - _controller = AnimationController( - vsync: this, - duration: Duration(milliseconds: widget.durationMs), - animationBehavior: AnimationBehavior.preserve, - )..value = (widget.elapsedMs / widget.durationMs).clamp(0.0, 1.0); - if (!widget.paused) { - unawaited(_controller.forward()); - } - } - - @override - void didUpdateWidget(_SlideshowProgressBar oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.durationMs != oldWidget.durationMs) { - _controller.duration = Duration(milliseconds: widget.durationMs); - } - if (widget.paused != oldWidget.paused) { - widget.paused ? _controller.stop() : _controller.forward(); - } - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return AnimatedBuilder( - animation: _controller, - builder: (context, _) => LinearProgressIndicator( - color: widget.color, - borderRadius: BorderRadius.zero, - minHeight: 5, - value: _controller.value, - ), - ); - } -} diff --git a/mobile/lib/presentation/pages/drift_trash.page.dart b/mobile/lib/presentation/pages/drift_trash.page.dart index 4dd027494d..a85f69a75e 100644 --- a/mobile/lib/presentation/pages/drift_trash.page.dart +++ b/mobile/lib/presentation/pages/drift_trash.page.dart @@ -1,17 +1,13 @@ import 'package:auto_route/auto_route.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/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; @RoutePage() class DriftTrashPage extends StatelessWidget { @@ -34,13 +30,12 @@ class DriftTrashPage extends StatelessWidget { ], child: Timeline( appBar: SliverAppBar( - title: Text(context.t.trash), + title: Text('trash'.t(context: context)), floating: true, snap: true, pinned: true, centerTitle: true, elevation: 0, - actions: const [_TrashKebabMenu()], ), topSliverWidgetHeight: 24, topSliverWidget: Consumer( @@ -58,89 +53,3 @@ class DriftTrashPage extends StatelessWidget { ); } } - -class _TrashKebabMenu extends ConsumerWidget { - const _TrashKebabMenu(); - - Future _confirmAndRun( - BuildContext context, - WidgetRef ref, { - required String title, - required String content, - required Future Function(String userId) action, - required String Function(int count) successMsg, - }) async { - await showDialog( - context: context, - builder: (_) => ConfirmDialog( - title: title, - content: content, - onOk: () async { - final user = ref.read(currentUserProvider); - if (user == null) { - return; - } - final result = await action(user.id); - if (!context.mounted) { - return; - } - ImmichToast.show( - context: context, - msg: result.success ? successMsg(result.count) : context.t.scaffold_body_error_occurred, - toastType: result.success ? ToastType.success : ToastType.error, - ); - }, - ), - ); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return MenuAnchor( - consumeOutsideTap: true, - style: MenuStyle( - backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), - surfaceTintColor: const WidgetStatePropertyAll(Colors.grey), - elevation: const WidgetStatePropertyAll(4), - shape: const WidgetStatePropertyAll( - RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), - ), - padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), - ), - menuChildren: [ - BaseActionButton( - label: context.t.empty_trash, - iconData: Icons.delete_forever_outlined, - onPressed: () => _confirmAndRun( - context, - ref, - title: context.t.empty_trash, - content: context.t.empty_trash_confirmation, - action: ref.read(actionProvider.notifier).emptyTrash, - successMsg: (count) => context.t.assets_permanently_deleted_count(count: count), - ), - menuItem: true, - ), - BaseActionButton( - label: context.t.restore_all, - iconData: Icons.restore_outlined, - onPressed: () => _confirmAndRun( - context, - ref, - title: context.t.restore_all, - content: context.t.assets_restore_confirmation, - action: ref.read(actionProvider.notifier).restoreAllTrash, - successMsg: (count) => context.t.assets_restored_count(count: count), - ), - menuItem: true, - ), - ], - builder: (context, controller, child) { - return IconButton( - icon: const Icon(Icons.more_vert_rounded), - onPressed: () => controller.isOpen ? controller.close() : controller.open(), - ); - }, - ); - } -} diff --git a/mobile/lib/presentation/pages/drift_user_selection.page.dart b/mobile/lib/presentation/pages/drift_user_selection.page.dart index c11d5a1fd6..b73913fd02 100644 --- a/mobile/lib/presentation/pages/drift_user_selection.page.dart +++ b/mobile/lib/presentation/pages/drift_user_selection.page.dart @@ -1,6 +1,5 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -8,7 +7,6 @@ import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -52,14 +50,13 @@ class DriftUserSelectionPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final AsyncValue> suggestedShareUsers = ref.watch(driftUsersProvider); - final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id)); final sharedUsersList = useState>({}); - void addNewUsersHandler() { - unawaited(context.maybePop(sharedUsersList.value.map((e) => e.id).toList())); + addNewUsersHandler() { + context.maybePop(sharedUsersList.value.map((e) => e.id).toList()); } - Widget buildTileIcon(UserDto user) { + buildTileIcon(UserDto user) { if (sharedUsersList.value.contains(user)) { return CircleAvatar(backgroundColor: context.primaryColor, child: const Icon(Icons.check_rounded, size: 25)); } else { @@ -67,8 +64,8 @@ class DriftUserSelectionPage extends HookConsumerWidget { } } - ListView buildUserList(List users) { - final List usersChip = []; + buildUserList(List users) { + List usersChip = []; for (var user in sharedUsersList.value) { usersChip.add( @@ -87,14 +84,14 @@ class DriftUserSelectionPage extends HookConsumerWidget { Padding( padding: const EdgeInsets.all(16.0), child: Text( - context.t.suggestions, + 'suggestions'.tr(), style: const TextStyle(fontSize: 14, color: Colors.grey, fontWeight: FontWeight.bold), ), ), ListView.builder( primary: false, shrinkWrap: true, - itemBuilder: (context, index) { + itemBuilder: ((context, index) { return ListTile( leading: buildTileIcon(users[index]), dense: true, @@ -110,7 +107,7 @@ class DriftUserSelectionPage extends HookConsumerWidget { } }, ); - }, + }), itemCount: users.length, ), ], @@ -119,24 +116,27 @@ class DriftUserSelectionPage extends HookConsumerWidget { return Scaffold( appBar: AppBar( - title: Text(context.t.invite_to_album), + title: const Text('invite_to_album').tr(), elevation: 0, centerTitle: false, leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () { - unawaited(context.maybePop(null)); + context.maybePop(null); }, ), actions: [ TextButton( - onPressed: sharedUsersList.value.isEmpty ? null : () => addNewUsersHandler(), - child: Text(context.t.add, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + onPressed: sharedUsersList.value.isEmpty ? null : addNewUsersHandler, + child: const Text("add", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), ), ], ), body: suggestedShareUsers.widgetWhen( onData: (users) { + // Get shared users for this album from the database + final sharedUsers = ref.watch(remoteAlbumSharedUsersProvider(album.id)); + return sharedUsers.when( data: (albumSharedUsers) { // Filter out users that are already shared with this album and the owner diff --git a/mobile/lib/presentation/pages/drift_video.page.dart b/mobile/lib/presentation/pages/drift_video.page.dart index b51ebc25b8..eef05acdce 100644 --- a/mobile/lib/presentation/pages/drift_video.page.dart +++ b/mobile/lib/presentation/pages/drift_video.page.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -26,7 +26,7 @@ class DriftVideoPage extends StatelessWidget { return timelineService; }), ], - child: Timeline(appBar: MesmerizingSliverAppBar(title: context.t.videos)), + child: Timeline(appBar: MesmerizingSliverAppBar(title: 'videos'.t())), ); } } diff --git a/mobile/lib/presentation/pages/edit/drift_edit.page.dart b/mobile/lib/presentation/pages/edit/drift_edit.page.dart deleted file mode 100644 index 6237169431..0000000000 --- a/mobile/lib/presentation/pages/edit/drift_edit.page.dart +++ /dev/null @@ -1,447 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:auto_route/auto_route.dart'; -import 'package:crop_image/crop_image.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/aspect_ratios.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/pages/edit/editor.provider.dart'; -import 'package:immich_mobile/providers/theme.provider.dart'; -import 'package:immich_mobile/theme/theme_data.dart'; -import 'package:immich_mobile/utils/editor.utils.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:openapi/api.dart' show MirrorAxis, MirrorParameters, RotateParameters; - -@RoutePage() -class DriftEditImagePage extends ConsumerStatefulWidget { - final Image image; - final Future Function(List edits) applyEdits; - - const DriftEditImagePage({super.key, required this.image, required this.applyEdits}); - - @override - ConsumerState createState() => _DriftEditImagePageState(); -} - -class _DriftEditImagePageState extends ConsumerState with TickerProviderStateMixin { - Future _saveEditedImage() async { - ref.read(editorStateProvider.notifier).setIsEditing(true); - - final editorState = ref.read(editorStateProvider); - final cropParameters = convertRectToCropParameters( - editorState.crop, - editorState.originalWidth, - editorState.originalHeight, - ); - final edits = []; - - if (cropParameters.width != editorState.originalWidth || cropParameters.height != editorState.originalHeight) { - edits.add(CropEdit(cropParameters)); - } - - if (editorState.flipHorizontal) { - edits.add(MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal))); - } - - if (editorState.flipVertical) { - edits.add(MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical))); - } - - final normalizedRotation = (editorState.rotationAngle % 360 + 360) % 360; - if (normalizedRotation != 0) { - edits.add(RotateEdit(RotateParameters(angle: normalizedRotation))); - } - - try { - await widget.applyEdits(edits); - if (!mounted) { - return; - } - - ImmichToast.show(context: context, msg: context.t.success, toastType: ToastType.success); - Navigator.of(context).pop(); - } catch (e) { - if (!mounted) { - return; - } - - ImmichToast.show(context: context, msg: context.t.error_title, toastType: ToastType.error); - } finally { - ref.read(editorStateProvider.notifier).setIsEditing(false); - } - } - - Future _showDiscardChangesDialog() { - return showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(context.t.editor_discard_edits_title), - content: Text(context.t.editor_discard_edits_prompt), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - style: ButtonStyle( - foregroundColor: WidgetStateProperty.all(context.themeData.colorScheme.onSurfaceVariant), - ), - child: Text(context.t.cancel), - ), - TextButton(onPressed: () => Navigator.of(context).pop(true), child: Text(context.t.confirm)), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - final hasUnsavedEdits = ref.watch(editorStateProvider.select((state) => state.hasUnsavedEdits)); - - return PopScope( - canPop: !hasUnsavedEdits, - onPopInvokedWithResult: (didPop, result) async { - if (didPop) { - return; - } - final shouldDiscard = await _showDiscardChangesDialog() ?? false; - if (shouldDiscard && context.mounted) { - Navigator.of(context).pop(); - } - }, - child: Theme( - data: getThemeData(colorScheme: ref.watch(immichThemeProvider).dark, locale: context.locale), - child: Scaffold( - appBar: AppBar( - backgroundColor: Colors.black, - title: Text(context.t.edit), - leading: ImmichCloseButton(onPressed: () => Navigator.of(context).maybePop()), - actions: [_SaveEditsButton(onSave: _saveEditedImage)], - ), - backgroundColor: Colors.black, - body: SafeArea( - bottom: false, - child: Column( - children: [ - Expanded(child: _EditorPreview(image: widget.image)), - AnimatedSize( - duration: const Duration(milliseconds: 250), - curve: Curves.easeInOut, - alignment: Alignment.bottomCenter, - clipBehavior: Clip.none, - child: Container( - width: double.infinity, - decoration: BoxDecoration( - color: ref.watch(immichThemeProvider).dark.surface, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: const Column( - mainAxisSize: MainAxisSize.min, - children: [ - _TransformControls(), - Padding( - padding: EdgeInsets.only(bottom: 36, left: 24, right: 24), - child: Row(children: [Spacer(), _ResetEditsButton()]), - ), - ], - ), - ), - ), - ], - ), - ), - ), - ), - ); - } -} - -class _AspectRatioButton extends StatelessWidget { - final CropAspectRatio ratio; - final bool isSelected; - final VoidCallback onPressed; - - const _AspectRatioButton({required this.ratio, required this.isSelected, required this.onPressed}); - - @override - Widget build(BuildContext context) { - final color = isSelected ? context.primaryColor : context.themeData.iconTheme.color; - - return Column( - mainAxisSize: MainAxisSize.max, - children: [ - IconButton( - iconSize: 36, - icon: ratio.ratio != null - ? _AspectRatioRect(ratio: ratio.ratio!, color: color) - : Icon(ratio.icon, color: color), - onPressed: onPressed, - ), - Text(ratio.label, style: context.textTheme.displayMedium), - ], - ); - } -} - -class _AspectRatioRect extends StatelessWidget { - final double ratio; - final Color? color; - - const _AspectRatioRect({required this.ratio, required this.color}); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 28, - height: 28, - child: Center( - child: AspectRatio( - aspectRatio: ratio, - child: Container( - decoration: BoxDecoration( - border: Border.all(color: color ?? Colors.transparent, width: 3), - borderRadius: const BorderRadius.all(Radius.circular(4)), - ), - ), - ), - ), - ); - } -} - -class _AspectRatioSelector extends ConsumerWidget { - const _AspectRatioSelector(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final editorState = ref.watch(editorStateProvider); - final editorNotifier = ref.watch(editorStateProvider.notifier); - - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: aspectRatioPresets.map((entry) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: _AspectRatioButton( - ratio: entry, - isSelected: editorState.aspectRatio == entry, - onPressed: () => editorNotifier.setAspectRatio(entry), - ), - ); - }).toList(), - ), - ); - } -} - -class _TransformControls extends ConsumerWidget { - const _TransformControls(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final editorNotifier = ref.watch(editorStateProvider.notifier); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 20, right: 20, top: 20, bottom: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - ImmichIconButton( - icon: Icons.rotate_left, - variant: ImmichVariant.ghost, - color: ImmichColor.secondary, - onPressed: editorNotifier.rotateCCW, - ), - const SizedBox(width: 8), - ImmichIconButton( - icon: Icons.rotate_right, - variant: ImmichVariant.ghost, - color: ImmichColor.secondary, - onPressed: editorNotifier.rotateCW, - ), - ], - ), - Row( - children: [ - ImmichIconButton( - icon: Icons.flip, - variant: ImmichVariant.ghost, - color: ImmichColor.secondary, - onPressed: editorNotifier.flipHorizontally, - ), - const SizedBox(width: 8), - Transform.rotate( - angle: pi / 2, - child: ImmichIconButton( - icon: Icons.flip, - variant: ImmichVariant.ghost, - color: ImmichColor.secondary, - onPressed: editorNotifier.flipVertically, - ), - ), - ], - ), - ], - ), - ), - const _AspectRatioSelector(), - const SizedBox(height: 32), - ], - ); - } -} - -class _SaveEditsButton extends ConsumerWidget { - final VoidCallback onSave; - - const _SaveEditsButton({required this.onSave}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final isApplyingEdits = ref.watch(editorStateProvider.select((state) => state.isApplyingEdits)); - final hasUnsavedEdits = ref.watch(editorStateProvider.select((state) => state.hasUnsavedEdits)); - - return isApplyingEdits - ? const Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox(width: 28, height: 28, child: CircularProgressIndicator(strokeWidth: 2.5)), - ) - : ImmichIconButton( - icon: Icons.done_rounded, - color: ImmichColor.primary, - variant: ImmichVariant.ghost, - disabled: !hasUnsavedEdits, - onPressed: onSave, - ); - } -} - -class _ResetEditsButton extends ConsumerWidget { - const _ResetEditsButton(); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final editorState = ref.watch(editorStateProvider); - final editorNotifier = ref.watch(editorStateProvider.notifier); - - return ImmichTextButton( - labelText: context.t.reset, - onPressed: editorNotifier.resetEdits, - variant: ImmichVariant.ghost, - expanded: false, - disabled: !editorState.hasEdits || editorState.isApplyingEdits, - ); - } -} - -class _EditorPreview extends ConsumerStatefulWidget { - final Image image; - - const _EditorPreview({required this.image}); - - @override - ConsumerState<_EditorPreview> createState() => _EditorPreviewState(); -} - -class _EditorPreviewState extends ConsumerState<_EditorPreview> with TickerProviderStateMixin { - late final CropController cropController; - - @override - void initState() { - super.initState(); - - cropController = CropController(); - cropController.crop = ref.read(editorStateProvider.select((state) => state.crop)); - cropController.addListener(onCrop); - } - - void onCrop() { - if (!mounted || cropController.crop == ref.read(editorStateProvider).crop) { - return; - } - - ref.read(editorStateProvider.notifier).setCrop(cropController.crop); - } - - @override - void dispose() { - cropController.removeListener(onCrop); - cropController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final editorState = ref.watch(editorStateProvider); - final editorNotifier = ref.watch(editorStateProvider.notifier); - - ref.listen(editorStateProvider, (previous, current) { - // Only re-apply the aspect ratio when it changes, otherwise the crop rect will shrink on every rotation - if (previous?.aspectRatio != current.aspectRatio) { - double? ratio; - - ratio = switch (current.aspectRatio) { - CropAspectRatio.original => current.originalWidth / current.originalHeight, - _ => current.aspectRatio.ratio, - }; - - if (current.rotationAngle % 180 != 0) { - ratio = ratio != null ? 1 / ratio : null; - } - - cropController.aspectRatio = ratio; - } - - if (cropController.crop != current.crop) { - cropController.crop = current.crop; - } - }); - - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // Calculate the bounding box size needed for the rotated container - final baseWidth = constraints.maxWidth * 0.9; - final baseHeight = constraints.maxHeight * 0.95; - - return Center( - child: AnimatedRotation( - turns: editorState.rotationAngle / 360, - duration: editorState.animationDuration, - curve: Curves.easeInOut, - onEnd: editorNotifier.normalizeRotation, - child: Transform( - alignment: Alignment.center, - transform: Matrix4.identity() - ..scaleByDouble( - editorState.flipHorizontal ? -1.0 : 1.0, - editorState.flipVertical ? -1.0 : 1.0, - 1.0, - 1.0, - ), - child: AnimatedContainer( - duration: editorState.animationDuration, - curve: Curves.easeInOut, - padding: const EdgeInsets.all(10), - width: (editorState.rotationAngle % 180 == 0) ? baseWidth : baseHeight, - height: (editorState.rotationAngle % 180 == 0) ? baseHeight : baseWidth, - child: CropImage(controller: cropController, image: widget.image, gridColor: Colors.white), - ), - ), - ), - ); - }, - ); - } -} diff --git a/mobile/lib/presentation/pages/edit/editor.provider.dart b/mobile/lib/presentation/pages/edit/editor.provider.dart deleted file mode 100644 index 39cf4cc3ff..0000000000 --- a/mobile/lib/presentation/pages/edit/editor.provider.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/aspect_ratios.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/domain/models/exif.model.dart'; -import 'package:immich_mobile/utils/editor.utils.dart'; - -part 'editor.provider.freezed.dart'; - -final editorStateProvider = NotifierProvider(EditorProvider.new); - -class EditorProvider extends Notifier { - @override - EditorState build() { - return const EditorState(); - } - - void clear() { - state = const EditorState(); - } - - void init(List edits, ExifInfo exifInfo) { - clear(); - - final existingCrop = edits.whereType().firstOrNull; - - final originalWidth = exifInfo.isFlipped ? exifInfo.height : exifInfo.width; - final originalHeight = exifInfo.isFlipped ? exifInfo.width : exifInfo.height; - - final Rect crop = existingCrop != null && originalWidth != null && originalHeight != null - ? convertCropParametersToRect(existingCrop.parameters, originalWidth, originalHeight) - : const Rect.fromLTRB(0, 0, 1, 1); - - final transform = normalizeTransformEdits(edits); - - state = state.copyWith( - originalWidth: originalWidth ?? state.originalWidth, - originalHeight: originalHeight ?? state.originalHeight, - crop: crop, - flipHorizontal: transform.mirrorHorizontal, - flipVertical: transform.mirrorVertical, - ); - - _animateRotation(transform.rotation.toInt(), duration: Duration.zero); - } - - void _animateRotation(int angle, {Duration duration = const Duration(milliseconds: 300)}) { - state = state.copyWith(rotationAngle: angle, animationDuration: duration); - } - - void normalizeRotation() { - final normalizedAngle = ((state.rotationAngle % 360) + 360) % 360; - if (normalizedAngle != state.rotationAngle) { - state = state.copyWith(rotationAngle: normalizedAngle, animationDuration: Duration.zero); - } - } - - void setIsEditing(bool isApplyingEdits) { - state = state.copyWith(isApplyingEdits: isApplyingEdits); - } - - void setCrop(Rect crop) { - state = state.copyWith(crop: crop, hasUnsavedEdits: true); - } - - void setAspectRatio(CropAspectRatio preset) { - state = state.copyWith(aspectRatio: preset, hasUnsavedEdits: true); - } - - void resetEdits() { - _animateRotation(0); - - state = state.copyWith( - flipHorizontal: false, - flipVertical: false, - crop: const Rect.fromLTRB(0, 0, 1, 1), - aspectRatio: CropAspectRatio.free, - hasUnsavedEdits: true, - ); - } - - void rotateCCW() { - _animateRotation(state.rotationAngle - 90); - state = state.copyWith(aspectRatio: state.aspectRatio.flipped, hasUnsavedEdits: true); - } - - void rotateCW() { - _animateRotation(state.rotationAngle + 90); - state = state.copyWith(aspectRatio: state.aspectRatio.flipped, hasUnsavedEdits: true); - } - - void flipHorizontally() { - if (state.rotationAngle % 180 != 0) { - // When rotated 90 or 270 degrees, flipping horizontally is equivalent to flipping vertically - state = state.copyWith(flipVertical: !state.flipVertical, hasUnsavedEdits: true); - } else { - state = state.copyWith(flipHorizontal: !state.flipHorizontal, hasUnsavedEdits: true); - } - } - - void flipVertically() { - if (state.rotationAngle % 180 != 0) { - // When rotated 90 or 270 degrees, flipping vertically is equivalent to flipping horizontally - state = state.copyWith(flipHorizontal: !state.flipHorizontal, hasUnsavedEdits: true); - } else { - state = state.copyWith(flipVertical: !state.flipVertical, hasUnsavedEdits: true); - } - } -} - -@freezed -abstract class EditorState with _$EditorState { - const EditorState._(); - - const factory EditorState({ - @Default(false) bool isApplyingEdits, - @Default(0) int rotationAngle, - @Default(false) bool flipHorizontal, - @Default(false) bool flipVertical, - @Default(Rect.fromLTRB(0, 0, 1, 1)) Rect crop, - @Default(CropAspectRatio.free) CropAspectRatio aspectRatio, - @Default(0) int originalWidth, - @Default(0) int originalHeight, - @Default(Duration.zero) Duration animationDuration, - @Default(false) bool hasUnsavedEdits, - }) = _EditorState; - - bool get hasEdits { - return rotationAngle != 0 || flipHorizontal || flipVertical || crop != const Rect.fromLTRB(0, 0, 1, 1); - } -} diff --git a/mobile/lib/presentation/pages/editing/drift_crop.page.dart b/mobile/lib/presentation/pages/editing/drift_crop.page.dart new file mode 100644 index 0000000000..a213e4c640 --- /dev/null +++ b/mobile/lib/presentation/pages/editing/drift_crop.page.dart @@ -0,0 +1,179 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:crop_image/crop_image.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart'; +import 'package:immich_ui/immich_ui.dart'; + +/// A widget for cropping an image. +/// This widget uses [HookWidget] to manage its lifecycle and state. It allows +/// users to crop an image and then navigate to the [EditImagePage] with the +/// cropped image. + +@RoutePage() +class DriftCropImagePage extends HookWidget { + final Image image; + final BaseAsset asset; + const DriftCropImagePage({super.key, required this.image, required this.asset}); + + @override + Widget build(BuildContext context) { + final cropController = useCropController(); + final aspectRatio = useState(null); + + return Scaffold( + appBar: AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("crop".tr()), + leading: const ImmichCloseButton(), + actions: [ + ImmichIconButton( + icon: Icons.done_rounded, + color: ImmichColor.primary, + variant: ImmichVariant.ghost, + onPressed: () async { + final croppedImage = await cropController.croppedImage(); + unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true))); + }, + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: SafeArea( + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 20), + width: constraints.maxWidth * 0.9, + height: constraints.maxHeight * 0.6, + child: CropImage(controller: cropController, image: image, gridColor: Colors.white), + ), + Expanded( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: context.scaffoldBackgroundColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImmichIconButton( + icon: Icons.rotate_left, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onPressed: () => cropController.rotateLeft(), + ), + ImmichIconButton( + icon: Icons.rotate_right, + variant: ImmichVariant.ghost, + color: ImmichColor.secondary, + onPressed: () => cropController.rotateRight(), + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: null, + label: 'Free', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 1.0, + label: '1:1', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 16.0 / 9.0, + label: '16:9', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 3.0 / 2.0, + label: '3:2', + ), + _AspectRatioButton( + cropController: cropController, + aspectRatio: aspectRatio, + ratio: 7.0 / 5.0, + label: '7:5', + ), + ], + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ), + ); + } +} + +class _AspectRatioButton extends StatelessWidget { + final CropController cropController; + final ValueNotifier aspectRatio; + final double? ratio; + final String label; + + const _AspectRatioButton({ + required this.cropController, + required this.aspectRatio, + required this.ratio, + required this.label, + }); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(switch (label) { + 'Free' => Icons.crop_free_rounded, + '1:1' => Icons.crop_square_rounded, + '16:9' => Icons.crop_16_9_rounded, + '3:2' => Icons.crop_3_2_rounded, + '7:5' => Icons.crop_7_5_rounded, + _ => Icons.crop_free_rounded, + }, color: aspectRatio.value == ratio ? context.primaryColor : context.themeData.iconTheme.color), + onPressed: () { + cropController.crop = const Rect.fromLTRB(0.1, 0.1, 0.9, 0.9); + aspectRatio.value = ratio; + cropController.aspectRatio = ratio; + }, + ), + Text(label, style: context.textTheme.displayMedium), + ], + ); + } +} diff --git a/mobile/lib/presentation/pages/editing/drift_edit.page.dart b/mobile/lib/presentation/pages/editing/drift_edit.page.dart new file mode 100644 index 0000000000..6d4ea4d3a6 --- /dev/null +++ b/mobile/lib/presentation/pages/editing/drift_edit.page.dart @@ -0,0 +1,153 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/utils/image_converter.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; + +/// A stateless widget that provides functionality for editing an image. +/// +/// This widget allows users to edit an image provided either as an [Asset] or +/// directly as an [Image]. It ensures that exactly one of these is provided. +/// +/// It also includes a conversion method to convert an [Image] to a [Uint8List] to save the image on the user's phone +/// They automatically navigate to the [HomePage] with the edited image saved and they eventually get backed up to the server. +@immutable +@RoutePage() +class DriftEditImagePage extends ConsumerWidget { + final BaseAsset asset; + final Image image; + final bool isEdited; + + const DriftEditImagePage({super.key, required this.asset, required this.image, required this.isEdited}); + + void _exitEditing(BuildContext context) { + // this assumes that the only way to get to this page is from the AssetViewerRoute + context.navigator.popUntil((route) => route.data?.name == AssetViewerRoute.name); + } + + Future _saveEditedImage(BuildContext context, BaseAsset asset, Image image, WidgetRef ref) async { + try { + final Uint8List imageData = await imageToUint8List(image); + LocalAsset? localAsset; + + try { + localAsset = await ref + .read(fileMediaRepositoryProvider) + .saveLocalAsset(imageData, title: "${p.withoutExtension(asset.name)}_edited.jpg"); + } on PlatformException catch (e) { + // OS might not return the saved image back, so we handle that gracefully + // This can happen if app does not have full library access + Logger("SaveEditedImage").warning("Failed to retrieve the saved image back from OS", e); + } + + unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true)); + _exitEditing(context); + ImmichToast.show(durationInSecond: 3, context: context, msg: 'Image Saved!'); + + if (localAsset == null) { + return; + } + + await ref.read(foregroundUploadServiceProvider).uploadManual([localAsset]); + } catch (e) { + ImmichToast.show( + durationInSecond: 6, + context: context, + msg: "error_saving_image".tr(namedArgs: {'error': e.toString()}), + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + title: Text("edit".tr()), + backgroundColor: context.scaffoldBackgroundColor, + leading: IconButton( + icon: Icon(Icons.close_rounded, color: context.primaryColor, size: 24), + onPressed: () => _exitEditing(context), + ), + actions: [ + TextButton( + onPressed: isEdited ? () => _saveEditedImage(context, asset, image, ref) : null, + child: Text("save_to_gallery".tr(), style: TextStyle(color: isEdited ? context.primaryColor : Colors.grey)), + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: Center( + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: context.height * 0.7, maxWidth: context.width * 0.9), + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.2), + spreadRadius: 2, + blurRadius: 10, + offset: const Offset(0, 3), + ), + ], + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(7)), + child: Image(image: image.image, fit: BoxFit.contain), + ), + ), + ), + ), + bottomNavigationBar: Container( + height: 70, + margin: const EdgeInsets.only(bottom: 60, right: 10, left: 10, top: 10), + decoration: BoxDecoration( + color: context.scaffoldBackgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(30)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon(Icons.crop_rotate_rounded, color: context.themeData.iconTheme.color, size: 25), + onPressed: () { + context.pushRoute(DriftCropImageRoute(asset: asset, image: image)); + }, + ), + Text("crop".tr(), style: context.textTheme.displayMedium), + ], + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon(Icons.filter, color: context.themeData.iconTheme.color, size: 25), + onPressed: () { + context.pushRoute(DriftFilterImageRoute(asset: asset, image: image)); + }, + ), + Text("filter".tr(), style: context.textTheme.displayMedium), + ], + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/presentation/pages/editing/drift_filter.page.dart b/mobile/lib/presentation/pages/editing/drift_filter.page.dart new file mode 100644 index 0000000000..8198a41bbe --- /dev/null +++ b/mobile/lib/presentation/pages/editing/drift_filter.page.dart @@ -0,0 +1,159 @@ +import 'dart:async'; +import 'dart:ui' as ui; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/constants/filters.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/routing/router.dart'; + +/// A widget for filtering an image. +/// This widget uses [HookWidget] to manage its lifecycle and state. It allows +/// users to add filters to an image and then navigate to the [EditImagePage] with the +/// final composition.' +@RoutePage() +class DriftFilterImagePage extends HookWidget { + final Image image; + final BaseAsset asset; + + const DriftFilterImagePage({super.key, required this.image, required this.asset}); + + @override + Widget build(BuildContext context) { + final colorFilter = useState(filters[0]); + final selectedFilterIndex = useState(0); + + Future createFilteredImage(ui.Image inputImage, ColorFilter filter) { + final completer = Completer(); + final size = Size(inputImage.width.toDouble(), inputImage.height.toDouble()); + final recorder = ui.PictureRecorder(); + final canvas = Canvas(recorder); + + final paint = Paint()..colorFilter = filter; + canvas.drawImage(inputImage, Offset.zero, paint); + + recorder.endRecording().toImage(size.width.round(), size.height.round()).then((image) { + completer.complete(image); + }); + + return completer.future; + } + + void applyFilter(ColorFilter filter, int index) { + colorFilter.value = filter; + selectedFilterIndex.value = index; + } + + Future applyFilterAndConvert(ColorFilter filter) async { + final completer = Completer(); + image.image + .resolve(ImageConfiguration.empty) + .addListener( + ImageStreamListener((ImageInfo info, bool _) { + completer.complete(info.image); + }), + ); + final uiImage = await completer.future; + + final filteredUiImage = await createFilteredImage(uiImage, filter); + final byteData = await filteredUiImage.toByteData(format: ui.ImageByteFormat.png); + final pngBytes = byteData!.buffer.asUint8List(); + + return Image.memory(pngBytes, fit: BoxFit.contain); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: context.scaffoldBackgroundColor, + title: Text("filter".tr()), + leading: CloseButton(color: context.primaryColor), + actions: [ + IconButton( + icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24), + onPressed: () async { + final filteredImage = await applyFilterAndConvert(colorFilter.value); + unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: filteredImage, isEdited: true))); + }, + ), + ], + ), + backgroundColor: context.scaffoldBackgroundColor, + body: Column( + children: [ + SizedBox( + height: context.height * 0.7, + child: Center( + child: ColorFiltered(colorFilter: colorFilter.value, child: image), + ), + ), + SizedBox( + height: 120, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: filters.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: _FilterButton( + image: image, + label: filterNames[index], + filter: filters[index], + isSelected: selectedFilterIndex.value == index, + onTap: () => applyFilter(filters[index], index), + ), + ); + }, + ), + ), + ], + ), + ); + } +} + +class _FilterButton extends StatelessWidget { + final Image image; + final String label; + final ColorFilter filter; + final bool isSelected; + final VoidCallback onTap; + + const _FilterButton({ + required this.image, + required this.label, + required this.filter, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + GestureDetector( + onTap: onTap, + child: Container( + width: 80, + height: 80, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(10)), + border: isSelected ? Border.all(color: context.primaryColor, width: 3) : null, + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(10)), + child: ColorFiltered( + colorFilter: filter, + child: FittedBox(fit: BoxFit.cover, child: image), + ), + ), + ), + ), + const SizedBox(height: 10), + Text(label, style: context.themeData.textTheme.bodyMedium), + ], + ); + } +} diff --git a/mobile/lib/presentation/pages/feature_message/whats_new.page.dart b/mobile/lib/presentation/pages/feature_message/whats_new.page.dart deleted file mode 100644 index d9e4e5c6eb..0000000000 --- a/mobile/lib/presentation/pages/feature_message/whats_new.page.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:immich_mobile/domain/models/feature_message.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_placeholder.widget.dart'; - -@RoutePage() -class WhatsNewPage extends StatelessWidget { - const WhatsNewPage({super.key}); - - @override - Widget build(BuildContext context) { - final highlights = visibleFeatureMessageHighlights; - return Scaffold( - appBar: AppBar(centerTitle: false, title: Text(context.t.whats_new)), - body: ListView.separated( - padding: const EdgeInsets.only(top: 16, bottom: 64), - itemCount: highlights.length, - separatorBuilder: (_, _) => const SizedBox(height: 24), - itemBuilder: (_, index) => _HighlightCard(highlight: highlights[index]), - ), - ); - } -} - -class _HighlightCard extends StatelessWidget { - final FeatureHighlight highlight; - - const _HighlightCard({required this.highlight}); - - @override - Widget build(BuildContext context) { - final scheme = context.colorScheme; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DecoratedBox( - decoration: BoxDecoration( - color: scheme.surfaceContainerHighest, - borderRadius: const BorderRadius.all(Radius.circular(18)), - border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.5)), - ), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(18)), - child: SizedBox( - width: double.infinity, - height: 256, - child: highlight.image == null - ? const FeatureMessagePlaceholder() - : Image.asset( - highlight.image!, - fit: BoxFit.contain, - errorBuilder: (context, _, _) => const FeatureMessagePlaceholder(), - ), - ), - ), - ), - const SizedBox(height: 12), - Text(highlight.title(context.t), style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600)), - const SizedBox(height: 4), - Text( - highlight.body(context.t), - style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceVariant), - ), - ], - ), - ); - } -} diff --git a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart index a5c069671c..f460633cbb 100644 --- a/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart +++ b/mobile/lib/presentation/pages/profile/profile_picture_crop.page.dart @@ -2,13 +2,13 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:crop_image/crop_image.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; @@ -58,9 +58,7 @@ class _ProfilePictureCropPageState extends ConsumerState } Future _handleDone() async { - if (_isLoading) { - return; - } + if (_isLoading) return; setState(() { _isLoading = true; @@ -70,17 +68,11 @@ class _ProfilePictureCropPageState extends ConsumerState final croppedImage = await _cropController.croppedImage(); final pngBytes = await imageToUint8List(croppedImage); final xFile = XFile.fromData(pngBytes, mimeType: 'image/png'); - if (!context.mounted) { - return; - } - final success = await ref .read(uploadProfileImageProvider.notifier) .upload(xFile, fileName: 'profile-picture.png'); - if (!mounted) { - return; - } + if (!context.mounted) return; if (success) { final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath; @@ -93,28 +85,28 @@ class _ProfilePictureCropPageState extends ConsumerState ImmichToast.show( context: context, - msg: context.t.profile_picture_set, + msg: 'profile_picture_set'.tr(), gravity: ToastGravity.BOTTOM, toastType: ToastType.success, ); - unawaited(context.maybePop()); + if (context.mounted) { + unawaited(context.maybePop()); + } } else { ImmichToast.show( context: context, - msg: context.t.errors.unable_to_set_profile_picture, + msg: 'errors.unable_to_set_profile_picture'.tr(), toastType: ToastType.error, gravity: ToastGravity.BOTTOM, ); } } catch (e) { - if (!mounted) { - return; - } + if (!context.mounted) return; ImmichToast.show( context: context, - msg: context.t.errors.unable_to_set_profile_picture, + msg: 'errors.unable_to_set_profile_picture'.tr(), toastType: ToastType.error, gravity: ToastGravity.BOTTOM, ); @@ -135,7 +127,7 @@ class _ProfilePictureCropPageState extends ConsumerState return Scaffold( appBar: AppBar( backgroundColor: context.scaffoldBackgroundColor, - title: Text(context.t.set_profile_picture), + title: Text("set_profile_picture".tr()), leading: _isLoading ? null : const ImmichCloseButton(), actions: [ if (_isLoading) @@ -159,7 +151,7 @@ class _ProfilePictureCropPageState extends ConsumerState return Center( child: ConstrainedBox( constraints: BoxConstraints(maxHeight: context.height * 0.7, maxWidth: context.width * 0.9), - child: DecoratedBox( + child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(7)), boxShadow: [ diff --git a/mobile/lib/presentation/pages/search/drift_search.page.dart b/mobile/lib/presentation/pages/search/drift_search.page.dart index 1f2fe506b2..7e47a742ae 100644 --- a/mobile/lib/presentation/pages/search/drift_search.page.dart +++ b/mobile/lib/presentation/pages/search/drift_search.page.dart @@ -6,13 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; @@ -47,17 +47,19 @@ class DriftSearchPage extends HookConsumerWidget { serverFeatures.smartSearch ? TextSearchType.context : TextSearchType.filename, ); final searchHintText = useState( - serverFeatures.smartSearch ? context.t.sunrise_on_the_beach : context.t.file_name_or_extension, + serverFeatures.smartSearch + ? 'sunrise_on_the_beach'.t(context: context) + : 'file_name_or_extension'.t(context: context), ); final textSearchController = useTextEditingController(); final filter = useState( SearchFilter( people: {}, - location: const SearchLocationFilter(), - camera: const SearchCameraFilter(), - date: const SearchDateFilter(), - display: const SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), - rating: const SearchRatingFilter(), + location: SearchLocationFilter(), + camera: SearchCameraFilter(), + date: SearchDateFilter(), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + rating: SearchRatingFilter(), mediaType: AssetType.other, language: "${context.locale.languageCode}-${context.locale.countryCode}", tagIds: [], @@ -77,7 +79,7 @@ class DriftSearchPage extends HookConsumerWidget { final userPreferences = ref.watch(userMetadataPreferencesProvider); - void search(SearchFilter f) { + search(SearchFilter f) { if (f == filter.value) { return; } @@ -91,7 +93,7 @@ class DriftSearchPage extends HookConsumerWidget { } } - void loadMoreSearchResults() { + loadMoreSearchResults() { unawaited(ref.read(paginatedSearchProvider.notifier).search(filter.value)); } @@ -102,116 +104,99 @@ class DriftSearchPage extends HookConsumerWidget { return null; } - unawaited( - Future.microtask(() { - if (!context.mounted) { - return; - } - - textSearchController.clear(); - peopleCurrentFilterWidget.value = null; - dateRangeCurrentFilterWidget.value = null; - cameraCurrentFilterWidget.value = null; - tagCurrentFilterWidget.value = null; - mediaTypeCurrentFilterWidget.value = null; - ratingCurrentFilterWidget.value = null; - displayOptionCurrentFilterWidget.value = null; - locationCurrentFilterWidget.value = preFilter.location.city != null - ? Text(preFilter.location.city!, style: context.textTheme.labelLarge) - : null; - search(preFilter); - }), - ); + Future.microtask(() { + textSearchController.clear(); + search(preFilter); + if (preFilter.location.city != null) { + locationCurrentFilterWidget.value = Text(preFilter.location.city!, style: context.textTheme.labelLarge); + } + }); return null; }, [preFilter]); - void showPeoplePicker() { + showPeoplePicker() { var people = filter.value.people; - void handleOnSelect(Set value) { + handleOnSelect(Set value) { people = value; } - void handleClear() { + handleClear() { peopleCurrentFilterWidget.value = null; search(filter.value.copyWith(people: {})); } - void handleApply() { - final label = people.map((e) => e.name != '' ? e.name : context.t.no_name).join(', '); + handleApply() { + final label = people.map((e) => e.name != '' ? e.name : 'no_name'.t(context: context)).join(', '); peopleCurrentFilterWidget.value = label.isNotEmpty ? Text(label, style: context.textTheme.labelLarge) : null; search(filter.value.copyWith(people: people)); } - unawaited( - showFilterBottomSheet( - context: context, - isScrollControlled: true, - child: FractionallySizedBox( - heightFactor: 0.8, - child: FilterBottomSheetScaffold( - title: context.t.search_filter_people_title, - expanded: true, - onSearch: handleApply, - onClear: handleClear, - child: PeoplePicker(onSelect: handleOnSelect, filter: filter.value.people), - ), + showFilterBottomSheet( + context: context, + isScrollControlled: true, + child: FractionallySizedBox( + heightFactor: 0.8, + child: FilterBottomSheetScaffold( + title: 'search_filter_people_title'.t(context: context), + expanded: true, + onSearch: handleApply, + onClear: handleClear, + child: PeoplePicker(onSelect: handleOnSelect, filter: filter.value.people), ), ), ); } - void showTagPicker() { + showTagPicker() { var tagIds = filter.value.tagIds ?? []; String tagLabel = ''; - void handleOnSelect(Iterable tags) { + handleOnSelect(Iterable tags) { tagIds = tags.map((t) => t.id).toList(); tagLabel = tags.map((t) => t.value).join(', '); } - void handleClear() { + handleClear() { tagCurrentFilterWidget.value = null; search(filter.value.copyWith(tagIds: [])); } - void handleApply() { + handleApply() { tagCurrentFilterWidget.value = tagLabel.isNotEmpty ? Text(tagLabel, style: context.textTheme.labelLarge) : null; search(filter.value.copyWith(tagIds: tagIds)); } - unawaited( - showFilterBottomSheet( - context: context, - isScrollControlled: true, - child: FractionallySizedBox( - heightFactor: 0.8, - child: FilterBottomSheetScaffold( - title: context.t.search_filter_tags_title, - expanded: true, - onSearch: handleApply, - onClear: handleClear, - child: TagPicker(onSelectExistingTag: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), - ), + showFilterBottomSheet( + context: context, + isScrollControlled: true, + child: FractionallySizedBox( + heightFactor: 0.8, + child: FilterBottomSheetScaffold( + title: 'search_filter_tags_title'.t(context: context), + expanded: true, + onSearch: handleApply, + onClear: handleClear, + child: TagPicker(onSelect: handleOnSelect, filter: (filter.value.tagIds ?? []).toSet()), ), ), ); } - void showLocationPicker() { + showLocationPicker() { var location = filter.value.location; - void handleOnSelect(Map value) { + handleOnSelect(Map value) { location = SearchLocationFilter(country: value['country'], city: value['city'], state: value['state']); } - void handleClear() { + handleClear() { locationCurrentFilterWidget.value = null; - search(filter.value.copyWith(location: const SearchLocationFilter())); + search(filter.value.copyWith(location: SearchLocationFilter())); } - void handleApply() { + handleApply() { final locationText = [ if (location.country != null) location.country!, if (location.state != null) location.state!, @@ -223,23 +208,21 @@ class DriftSearchPage extends HookConsumerWidget { search(filter.value.copyWith(location: location)); } - unawaited( - showFilterBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: true, - child: FilterBottomSheetScaffold( - title: context.t.search_filter_location_title, - onSearch: handleApply, - onClear: handleClear, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Container( - padding: EdgeInsets.only(bottom: context.viewInsets.bottom), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: LocationPicker(onSelected: handleOnSelect, filter: filter.value.location), - ), + showFilterBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: true, + child: FilterBottomSheetScaffold( + title: 'search_filter_location_title'.t(context: context), + onSearch: handleApply, + onClear: handleClear, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Container( + padding: EdgeInsets.only(bottom: context.viewInsets.bottom), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: LocationPicker(onSelected: handleOnSelect, filter: filter.value.location), ), ), ), @@ -247,19 +230,19 @@ class DriftSearchPage extends HookConsumerWidget { ); } - void showCameraPicker() { + showCameraPicker() { var camera = filter.value.camera; - void handleOnSelect(Map value) { + handleOnSelect(Map value) { camera = SearchCameraFilter(make: value['make'], model: value['model']); } - void handleClear() { + handleClear() { cameraCurrentFilterWidget.value = null; - search(filter.value.copyWith(camera: const SearchCameraFilter())); + search(filter.value.copyWith(camera: SearchCameraFilter())); } - void handleApply() { + handleApply() { final make = camera.make ?? ''; final model = camera.model ?? ''; cameraCurrentFilterWidget.value = (make.isNotEmpty || model.isNotEmpty) @@ -268,29 +251,27 @@ class DriftSearchPage extends HookConsumerWidget { search(filter.value.copyWith(camera: camera)); } - unawaited( - showFilterBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: true, - child: FilterBottomSheetScaffold( - title: context.t.search_filter_camera_title, - onSearch: handleApply, - onClear: handleClear, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: CameraPicker(onSelect: handleOnSelect, filter: filter.value.camera), - ), + showFilterBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: true, + child: FilterBottomSheetScaffold( + title: 'search_filter_camera_title'.t(context: context), + onSearch: handleApply, + onClear: handleClear, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: CameraPicker(onSelect: handleOnSelect, filter: filter.value.camera), ), ), ); } - void datePicked(DateFilterInputModel? selectedDate) { + datePicked(DateFilterInputModel? selectedDate) { dateInputFilter.value = selectedDate; if (selectedDate == null) { dateRangeCurrentFilterWidget.value = null; - search(filter.value.copyWith(date: const SearchDateFilter())); + search(filter.value.copyWith(date: SearchDateFilter())); return; } @@ -309,7 +290,7 @@ class DriftSearchPage extends HookConsumerWidget { ); } - Future showDatePicker() async { + showDatePicker() async { final firstDate = DateTime(1900); final lastDate = DateTime.now(); @@ -330,14 +311,14 @@ class DriftSearchPage extends HookConsumerWidget { lastDate: lastDate, currentDate: DateTime.now(), initialDateRange: dateRange, - helpText: context.t.search_filter_date_title, - cancelText: context.t.cancel, - confirmText: context.t.select, - saveText: context.t.save, - errorFormatText: context.t.invalid_date_format, - errorInvalidText: context.t.invalid_date, - fieldStartHintText: context.t.start_date, - fieldEndHintText: context.t.end_date, + helpText: 'search_filter_date_title'.t(context: context), + cancelText: 'cancel'.t(context: context), + confirmText: 'select'.t(context: context), + saveText: 'save'.t(context: context), + errorFormatText: 'invalid_date_format'.t(context: context), + errorInvalidText: 'invalid_date'.t(context: context), + fieldStartHintText: 'start_date'.t(context: context), + fieldEndHintText: 'end_date'.t(context: context), initialEntryMode: DatePickerEntryMode.calendar, keyboardType: TextInputType.text, ); @@ -349,126 +330,120 @@ class DriftSearchPage extends HookConsumerWidget { } } - void showQuickDatePicker() { - unawaited( - showFilterBottomSheet( - context: context, - child: FilterBottomSheetScaffold( - title: context.t.pick_date_range, - expanded: true, - onClear: () => datePicked(null), - child: QuickDatePicker( - currentInput: dateInputFilter.value, - onRequestPicker: () { - ContextHelper(context).pop(); - unawaited(showDatePicker()); - }, - onSelect: (date) { - ContextHelper(context).pop(); - datePicked(date); - }, - ), + showQuickDatePicker() { + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: "pick_date_range".tr(), + expanded: true, + onClear: () => datePicked(null), + child: QuickDatePicker( + currentInput: dateInputFilter.value, + onRequestPicker: () { + context.pop(); + showDatePicker(); + }, + onSelect: (date) { + context.pop(); + datePicked(date); + }, ), ), ); } // MEDIA PICKER - void showMediaTypePicker() { + showMediaTypePicker() { var mediaType = filter.value.mediaType; - void handleOnSelected(AssetType assetType) { + handleOnSelected(AssetType assetType) { mediaType = assetType; } - void handleClear() { + handleClear() { mediaTypeCurrentFilterWidget.value = null; search(filter.value.copyWith(mediaType: AssetType.other)); } - void handleApply() { + handleApply() { mediaTypeCurrentFilterWidget.value = mediaType != AssetType.other ? Text( - mediaType == AssetType.image ? context.t.image : context.t.video, + mediaType == AssetType.image ? 'image'.t(context: context) : 'video'.t(context: context), style: context.textTheme.labelLarge, ) : null; search(filter.value.copyWith(mediaType: mediaType)); } - unawaited( - showFilterBottomSheet( - context: context, - child: FilterBottomSheetScaffold( - title: context.t.search_filter_media_type_title, - onSearch: handleApply, - onClear: handleClear, - child: MediaTypePicker(onSelect: handleOnSelected, filter: filter.value.mediaType), - ), + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: 'search_filter_media_type_title'.t(context: context), + onSearch: handleApply, + onClear: handleClear, + child: MediaTypePicker(onSelect: handleOnSelected, filter: filter.value.mediaType), ), ); } // STAR RATING PICKER - void showStarRatingPicker() { + showStarRatingPicker() { var rating = filter.value.rating; - void handleOnSelected(SearchRatingFilter value) { + handleOnSelected(SearchRatingFilter value) { rating = value; } - void handleClear() { + handleClear() { ratingCurrentFilterWidget.value = null; - search(filter.value.copyWith(rating: const SearchRatingFilter())); + search(filter.value.copyWith(rating: SearchRatingFilter(rating: null))); } - void handleApply() { - ratingCurrentFilterWidget.value = rating.rating.isSome - ? Text(context.t.rating_count(count: rating.rating.unwrapOrNull ?? 0), style: context.textTheme.labelLarge) + handleApply() { + ratingCurrentFilterWidget.value = rating.rating != null + ? Text('rating_count'.t(args: {'count': rating.rating!}), style: context.textTheme.labelLarge) : null; search(filter.value.copyWith(rating: rating)); } - unawaited( - showFilterBottomSheet( - context: context, - isScrollControlled: true, - child: FilterBottomSheetScaffold( - title: context.t.rating, - onSearch: handleApply, - onClear: handleClear, - child: StarRatingPicker(onSelect: handleOnSelected, filter: filter.value.rating), - ), + showFilterBottomSheet( + context: context, + isScrollControlled: true, + child: FilterBottomSheetScaffold( + title: 'rating'.t(context: context), + onSearch: handleApply, + onClear: handleClear, + child: StarRatingPicker(onSelect: handleOnSelected, filter: filter.value.rating), ), ); } // DISPLAY OPTION - void showDisplayOptionPicker() { + showDisplayOptionPicker() { var display = filter.value.display; - void handleOnSelect(Map value) { + handleOnSelect(Map value) { display = display.copyWith( - isNotInAlbum: value[DisplayOption.notInAlbum] ?? display.isNotInAlbum, - isArchive: value[DisplayOption.archive] ?? display.isArchive, - isFavorite: value[DisplayOption.favorite] ?? display.isFavorite, + isNotInAlbum: value[DisplayOption.notInAlbum], + isArchive: value[DisplayOption.archive], + isFavorite: value[DisplayOption.favorite], ); } - void handleClear() { + handleClear() { displayOptionCurrentFilterWidget.value = null; search( filter.value.copyWith( - display: const SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), ), ); } - void handleApply() { + handleApply() { final filterText = [ - if (display.isNotInAlbum) context.t.search_filter_display_option_not_in_album, - if (display.isArchive) context.t.archive, - if (display.isFavorite) context.t.favorite, + if (display.isNotInAlbum) 'search_filter_display_option_not_in_album'.t(context: context), + if (display.isArchive) 'archive'.t(context: context), + if (display.isFavorite) 'favorite'.t(context: context), ]; displayOptionCurrentFilterWidget.value = filterText.isNotEmpty ? Text(filterText.join(', '), style: context.textTheme.labelLarge) @@ -476,20 +451,18 @@ class DriftSearchPage extends HookConsumerWidget { search(filter.value.copyWith(display: display)); } - unawaited( - showFilterBottomSheet( - context: context, - child: FilterBottomSheetScaffold( - title: context.t.display_options, - onSearch: handleApply, - onClear: handleClear, - child: DisplayOptionPicker(onSelect: handleOnSelect, filter: filter.value.display), - ), + showFilterBottomSheet( + context: context, + child: FilterBottomSheetScaffold( + title: 'display_options'.t(context: context), + onSearch: handleApply, + onClear: handleClear, + child: DisplayOptionPicker(onSelect: handleOnSelect, filter: filter.value.display), ), ); } - void handleTextSubmitted(String value) => search(switch (textSearchType.value) { + handleTextSubmitted(String value) => search(switch (textSearchType.value) { TextSearchType.context => filter.value.copyWith(filename: '', context: value, description: '', ocr: ''), TextSearchType.filename => filter.value.copyWith(filename: value, context: '', description: '', ocr: ''), TextSearchType.description => filter.value.copyWith(filename: '', context: '', description: value, ocr: ''), @@ -528,7 +501,7 @@ class DriftSearchPage extends HookConsumerWidget { } }, icon: const Icon(Icons.more_vert_rounded), - tooltip: context.t.show_text_search_menu, + tooltip: 'show_text_search_menu'.tr(), ); }, menuChildren: [ @@ -538,7 +511,7 @@ class DriftSearchPage extends HookConsumerWidget { child: ListTile( leading: const Icon(Icons.image_search_rounded), title: Text( - context.t.search_by_context, + 'search_by_context'.t(context: context), style: context.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, color: textSearchType.value == TextSearchType.context ? context.colorScheme.primary : null, @@ -549,7 +522,7 @@ class DriftSearchPage extends HookConsumerWidget { ), onPressed: () { textSearchType.value = TextSearchType.context; - searchHintText.value = context.t.sunrise_on_the_beach; + searchHintText.value = 'sunrise_on_the_beach'.t(context: context); }, ), ), @@ -557,7 +530,7 @@ class DriftSearchPage extends HookConsumerWidget { child: ListTile( leading: const Icon(Icons.abc_rounded), title: Text( - context.t.search_filter_filename, + 'search_filter_filename'.t(context: context), style: context.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, color: textSearchType.value == TextSearchType.filename ? context.colorScheme.primary : null, @@ -568,14 +541,14 @@ class DriftSearchPage extends HookConsumerWidget { ), onPressed: () { textSearchType.value = TextSearchType.filename; - searchHintText.value = context.t.file_name_or_extension; + searchHintText.value = 'file_name_or_extension'.t(context: context); }, ), MenuItemButton( child: ListTile( leading: const Icon(Icons.text_snippet_outlined), title: Text( - context.t.search_by_description, + 'search_by_description'.t(context: context), style: context.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, color: textSearchType.value == TextSearchType.description ? context.colorScheme.primary : null, @@ -586,7 +559,7 @@ class DriftSearchPage extends HookConsumerWidget { ), onPressed: () { textSearchType.value = TextSearchType.description; - searchHintText.value = context.t.search_by_description_example; + searchHintText.value = 'search_by_description_example'.t(context: context); }, ), FeatureCheck( @@ -595,7 +568,7 @@ class DriftSearchPage extends HookConsumerWidget { child: ListTile( leading: const Icon(Icons.document_scanner_outlined), title: Text( - context.t.search_by_ocr, + 'search_by_ocr'.t(context: context), style: context.textTheme.bodyLarge?.copyWith( fontWeight: FontWeight.w500, color: textSearchType.value == TextSearchType.ocr ? context.colorScheme.primary : null, @@ -606,7 +579,7 @@ class DriftSearchPage extends HookConsumerWidget { ), onPressed: () { textSearchType.value = TextSearchType.ocr; - searchHintText.value = context.t.search_by_ocr_example; + searchHintText.value = 'search_by_ocr_example'.t(context: context); }, ), ), @@ -614,7 +587,7 @@ class DriftSearchPage extends HookConsumerWidget { ), ), ], - title: DecoratedBox( + title: Container( decoration: BoxDecoration( border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0), borderRadius: const BorderRadius.all(Radius.circular(24)), @@ -657,52 +630,52 @@ class DriftSearchPage extends HookConsumerWidget { SearchFilterChip( icon: Icons.people_alt_outlined, onTap: showPeoplePicker, - label: context.t.people, + label: 'people'.t(context: context), currentFilter: peopleCurrentFilterWidget.value, ), SearchFilterChip( icon: Icons.location_on_outlined, onTap: showLocationPicker, - label: context.t.search_filter_location, + label: 'search_filter_location'.t(context: context), currentFilter: locationCurrentFilterWidget.value, ), if (userPreferences.valueOrNull?.tagsEnabled ?? false) SearchFilterChip( icon: Icons.sell_outlined, onTap: showTagPicker, - label: context.t.tags, + label: 'tags'.t(context: context), currentFilter: tagCurrentFilterWidget.value, ), SearchFilterChip( icon: Icons.camera_alt_outlined, onTap: showCameraPicker, - label: context.t.camera, + label: 'camera'.t(context: context), currentFilter: cameraCurrentFilterWidget.value, ), SearchFilterChip( icon: Icons.date_range_outlined, onTap: showQuickDatePicker, - label: context.t.search_filter_date, + label: 'search_filter_date'.t(context: context), currentFilter: dateRangeCurrentFilterWidget.value, ), SearchFilterChip( key: const Key('media_type_chip'), icon: Icons.video_collection_outlined, onTap: showMediaTypePicker, - label: context.t.search_filter_media_type, + label: 'search_filter_media_type'.t(context: context), currentFilter: mediaTypeCurrentFilterWidget.value, ), if (userPreferences.valueOrNull?.ratingsEnabled ?? false) SearchFilterChip( icon: Icons.star_outline_rounded, onTap: showStarRatingPicker, - label: context.t.search_filter_star_rating, + label: 'search_filter_star_rating'.t(context: context), currentFilter: ratingCurrentFilterWidget.value, ), SearchFilterChip( icon: Icons.display_settings_outlined, onTap: showDisplayOptionPicker, - label: context.t.search_filter_display_options, + label: 'search_filter_display_options'.t(context: context), currentFilter: displayOptionCurrentFilterWidget.value, ), ], @@ -713,7 +686,7 @@ class DriftSearchPage extends HookConsumerWidget { if (filter.value.isEmpty) const _SearchSuggestions() else - _SearchResultGrid(onScrollEnd: () => loadMoreSearchResults()), + _SearchResultGrid(onScrollEnd: loadMoreSearchResults), ], ), ); @@ -728,9 +701,7 @@ class _SearchResultGrid extends ConsumerWidget { bool _onScrollUpdateNotification(ScrollNotification notification) { final metrics = notification.metrics; - if (metrics.axis != Axis.vertical) { - return false; - } + if (metrics.axis != Axis.vertical) return false; final isBottomSheet = notification.context?.findAncestorWidgetOfExactType() != null; final remaining = metrics.maxScrollExtent - metrics.pixels; @@ -742,7 +713,9 @@ class _SearchResultGrid extends ConsumerWidget { return false; } - Widget? _bottomWidget(BuildContext context, {required bool isLoading, required bool hasMore}) { + Widget? _bottomWidget(BuildContext context, WidgetRef ref) { + final isLoading = ref.watch(paginatedSearchProvider.select((s) => s.isLoading)); + if (isLoading) { return const SliverFillRemaining( hasScrollBody: false, @@ -753,16 +726,16 @@ class _SearchResultGrid extends ConsumerWidget { ); } - if (hasMore) { - return null; - } + final hasMore = ref.watch(paginatedSearchProvider.select((s) => s.nextPage != null)); + + if (hasMore) return null; return SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(vertical: 32), child: Center( child: Text( - context.t.search_no_more_result, + 'search_no_more_result'.t(context: context), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceVariant), ), ), @@ -774,7 +747,6 @@ class _SearchResultGrid extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final hasAssets = ref.watch(paginatedSearchProvider.select((s) => s.assets.isNotEmpty)); final isLoading = ref.watch(paginatedSearchProvider.select((s) => s.isLoading)); - final hasMore = ref.watch(paginatedSearchProvider.select((s) => s.nextPage != null)); if (!hasAssets && !isLoading) { return const _SearchNoResults(); @@ -804,7 +776,7 @@ class _SearchResultGrid extends ConsumerWidget { bottomSheet: const GeneralBottomSheet(minChildSize: 0.20), snapToMonth: false, loadingWidget: const SizedBox.shrink(), - bottomSliverWidget: _bottomWidget(context, isLoading: isLoading, hasMore: hasMore), + bottomSliverWidget: _bottomWidget(context, ref), ), ), ), @@ -828,7 +800,7 @@ class _SearchNoResults extends StatelessWidget { Icon(Icons.search_off_rounded, size: 72, color: context.colorScheme.onSurfaceVariant), const SizedBox(height: 24), Text( - context.t.search_no_result, + 'search_no_result'.t(context: context), textAlign: TextAlign.center, style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurfaceVariant), ), @@ -856,7 +828,9 @@ class _SearchSuggestions extends StatelessWidget { ), ), const SizedBox(height: 16), - Center(child: Text(context.t.search_page_search_photos_videos, style: context.textTheme.labelLarge)), + Center( + child: Text('search_page_search_photos_videos'.t(context: context), style: context.textTheme.labelLarge), + ), const SizedBox(height: 32), const Padding(padding: EdgeInsets.symmetric(horizontal: 16), child: _QuickLinkList()), ], @@ -870,7 +844,7 @@ class _QuickLinkList extends StatelessWidget { @override Widget build(BuildContext context) { - return DecoratedBox( + return Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), border: Border.all(color: context.colorScheme.outline.withAlpha(10), width: 1), @@ -889,23 +863,18 @@ class _QuickLinkList extends StatelessWidget { physics: const NeverScrollableScrollPhysics(), children: [ _QuickLink( - title: context.t.recently_taken, + title: 'recently_taken'.t(context: context), icon: Icons.schedule_outlined, isTop: true, onTap: () => context.pushRoute(const DriftRecentlyTakenRoute()), ), _QuickLink( - title: context.t.recently_added, - icon: Icons.upload_outlined, - onTap: () => context.pushRoute(const DriftRecentlyAddedRoute()), - ), - _QuickLink( - title: context.t.videos, + title: 'videos'.t(context: context), icon: Icons.play_circle_outline_rounded, onTap: () => context.pushRoute(const DriftVideoRoute()), ), _QuickLink( - title: context.t.favorites, + title: 'favorites'.t(context: context), icon: Icons.favorite_border_rounded, isBottom: true, onTap: () => context.pushRoute(const DriftFavoriteRoute()), diff --git a/mobile/lib/presentation/pages/search/paginated_search.provider.dart b/mobile/lib/presentation/pages/search/paginated_search.provider.dart index e9839f31ea..f65ca6b909 100644 --- a/mobile/lib/presentation/pages/search/paginated_search.provider.dart +++ b/mobile/lib/presentation/pages/search/paginated_search.provider.dart @@ -44,9 +44,7 @@ class PaginatedSearchNotifier extends StateNotifier { Stream get assetCount => _assetCountController.stream; Future search(SearchFilter filter) async { - if (state.nextPage == null || state.isLoading) { - return; - } + if (state.nextPage == null || state.isLoading) return; state = SearchState(assets: state.assets, nextPage: state.nextPage, isLoading: true); @@ -70,7 +68,7 @@ class PaginatedSearchNotifier extends StateNotifier { @override void dispose() { - unawaited(_assetCountController.close()); + _assetCountController.close(); super.dispose(); } } diff --git a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart index 2466a52ead..39bdef8b9a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/add_action_button.widget.dart @@ -1,26 +1,25 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; + import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; -import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:immich_ui/immich_ui.dart'; -enum AddToMenuItem { album } +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; + +enum AddToMenuItem { album, archive, unarchive, lockedFolder } class AddActionButton extends ConsumerStatefulWidget { const AddActionButton({super.key, this.originalTheme}); @@ -36,26 +35,39 @@ class _AddActionButtonState extends ConsumerState { switch (selected) { case AddToMenuItem.album: _openAlbumSelector(); + break; + case AddToMenuItem.archive: + performArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.unarchive: + performUnArchiveAction(context, ref, source: ActionSource.viewer); + break; + case AddToMenuItem.lockedFolder: + performMoveToLockFolderAction(context, ref, source: ActionSource.viewer); + break; } } List _buildMenuChildren() { final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) { - return []; - } + if (asset == null) return []; final user = ref.read(currentUserProvider); final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; + final isInLockedView = ref.watch(inLockedViewProvider); + final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; + final hasRemote = asset is RemoteAsset; + final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived; + final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived; return [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text(context.t.add_to_bottom_bar, style: context.textTheme.labelMedium), + child: Text("add_to_bottom_bar".tr(), style: context.textTheme.labelMedium), ), BaseActionButton( iconData: Icons.photo_album_outlined, - label: context.t.album, + label: "album".tr(), menuItem: true, onPressed: () => _handleMenuSelection(AddToMenuItem.album), ), @@ -64,10 +76,28 @@ class _AddActionButtonState extends ConsumerState { const Divider(), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text(context.t.move_to, style: context.textTheme.labelMedium), + child: Text("move_to".tr(), style: context.textTheme.labelMedium), + ), + if (showArchive) + BaseActionButton( + iconData: Icons.archive_outlined, + label: "archive".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.archive), + ), + if (showUnarchive) + BaseActionButton( + iconData: Icons.unarchive_outlined, + label: "unarchive".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.unarchive), + ), + BaseActionButton( + iconData: Icons.lock_outline, + label: "locked_folder".tr(), + menuItem: true, + onPressed: () => _handleMenuSelection(AddToMenuItem.lockedFolder), ), - const ActionMenuItem(action: ArchiveAction(source: .viewer)), - const ActionMenuItem(action: LockAction(source: .viewer)), ], ]; } @@ -84,23 +114,21 @@ class _AddActionButtonState extends ConsumerState { AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(album)), ]; - unawaited( - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (_) { - return BaseBottomSheet( - actions: const [], - slivers: slivers, - initialChildSize: 0.6, - minChildSize: 0.3, - maxChildSize: 0.95, - expand: false, - backgroundColor: context.isDarkTheme ? Colors.black : Colors.white, - ); - }, - ), + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (_) { + return BaseBottomSheet( + actions: const [], + slivers: slivers, + initialChildSize: 0.6, + minChildSize: 0.3, + maxChildSize: 0.95, + expand: false, + backgroundColor: context.isDarkTheme ? Colors.black : Colors.white, + ); + }, ); } @@ -112,40 +140,28 @@ class _AddActionButtonState extends ConsumerState { return; } - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.viewer, album); + final addedCount = await ref.read(remoteAlbumProvider.notifier).addAssets(album.id, [latest.remoteId!]); - if (!mounted) { + if (!context.mounted) { return; } - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; - } - - // Only report the failure when nothing was added; if some succeeded we show "added". - if (result.count > 0) { + if (addedCount == 0) { ImmichToast.show( context: context, - msg: context.t.add_to_album_bottom_sheet_added(album: album.name), - ); - - // Refresh the "Appears in" list on the asset's info panel. - ref.invalidate(albumsContainingAssetProvider(latest.remoteId!)); - } else if (result.failedCount > 0) { - ImmichToast.show( - context: context, - msg: context.t.assets_cannot_be_added_to_album_count(count: result.failedCount), - toastType: ToastType.error, + msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {'album': album.name}), ); } else { ImmichToast.show( context: context, - msg: context.t.add_to_album_bottom_sheet_already_exists(album: album.name), + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), ); + + // Invalidate using the asset's remote ID to refresh the "Appears in" list + ref.invalidate(albumsContainingAssetProvider(latest.remoteId!)); } - if (!mounted) { + if (!context.mounted) { return; } await Navigator.of(context).maybePop(); @@ -160,7 +176,7 @@ class _AddActionButtonState extends ConsumerState { final themeData = widget.originalTheme ?? context.themeData; - return ImmichMenu( + return MenuAnchor( consumeOutsideTap: true, style: MenuStyle( backgroundColor: WidgetStatePropertyAll(themeData.scaffoldBackgroundColor), @@ -171,7 +187,7 @@ class _AddActionButtonState extends ConsumerState { ), padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), ), - children: widget.originalTheme != null + menuChildren: widget.originalTheme != null ? [ Theme( data: widget.originalTheme!, @@ -182,7 +198,7 @@ class _AddActionButtonState extends ConsumerState { builder: (context, controller, child) { return BaseActionButton( iconData: Icons.add, - label: context.t.add_to_bottom_bar, + label: "add_to_bottom_bar".tr(), onPressed: () => controller.isOpen ? controller.close() : controller.open(), ); }, diff --git a/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart new file mode 100644 index 0000000000..b68ab69b26 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; + +class AdvancedInfoActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const AdvancedInfoActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + unawaited(ref.read(actionProvider.notifier).troubleshoot(source, context)); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 115.0, + iconData: Icons.help_outline_rounded, + label: "troubleshoot".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart new file mode 100644 index 0000000000..a673dff1d7 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +// used to allow performing archive action from different sources (without duplicating code) +Future performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).archive(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} + +class ArchiveActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const ArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + Future _onTap(BuildContext context, WidgetRef ref) async { + await performArchiveAction(context, ref, source: source); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.archive_outlined, + label: "to_archive".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart index b64cb20e74..1ca875e483 100644 --- a/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/base_action_button.widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -class BaseActionButton extends StatelessWidget { +class BaseActionButton extends ConsumerWidget { const BaseActionButton({ super.key, required this.label, @@ -30,45 +31,32 @@ class BaseActionButton extends StatelessWidget { final void Function()? onLongPressed; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0); final iconTheme = IconTheme.of(context); final iconSize = iconTheme.size ?? 24.0; + final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; final textColor = context.themeData.textTheme.labelLarge?.color; if (iconOnly) { - final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; - return IconButton( onPressed: onPressed, - onLongPress: onLongPressed, icon: Icon(iconData, size: iconSize, color: iconColor), ); } if (menuItem) { - final iconColor = this.iconColor; - final onPressed = this.onPressed; + final theme = context.themeData; + final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant; return MenuItemButton( - closeOnActivate: false, - style: MenuItemButton.styleFrom( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), - ), - leadingIcon: Icon(iconData, color: iconColor, size: 20), - onPressed: onPressed == null - ? null - : () { - onPressed(); - MenuController.maybeOf(context)?.close(); - }, - child: Text(label, style: TextStyle(fontSize: 15, color: iconColor)), + style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)), + leadingIcon: Icon(iconData, color: effectiveIconColor), + onPressed: onPressed, + child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16, color: iconColor)), ); } - final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color; - return ConstrainedBox( constraints: BoxConstraints(maxWidth: maxWidth), child: MaterialButton( @@ -86,7 +74,7 @@ class BaseActionButton extends StatelessWidget { const SizedBox(height: 8), Text( label, - style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w500), + style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400), maxLines: 3, textAlign: TextAlign.center, softWrap: true, diff --git a/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart new file mode 100644 index 0000000000..7a4f84fb4f --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/cast_action_button.widget.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; + +class CastActionButton extends ConsumerWidget { + const CastActionButton({super.key, this.iconOnly = false, this.menuItem = false}); + + final bool iconOnly; + final bool menuItem; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + + return BaseActionButton( + iconData: isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded, + iconColor: isCasting ? context.primaryColor : null, // null = default color + label: "cast".t(context: context), + onPressed: () { + showDialog(context: context, builder: (context) => const CastDialog()); + }, + iconOnly: iconOnly, + menuItem: menuItem, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart new file mode 100644 index 0000000000..2121ef3159 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_action_button.widget.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +/// This delete action has the following behavior: +/// - Set the deletedAt information, put the asset in the trash in the server +/// which will be permanently deleted after the number of days configure by the admin +/// - Prompt to delete the asset locally +class DeleteActionButton extends ConsumerWidget { + final ActionSource source; + final bool showConfirmation; + final bool iconOnly; + final bool menuItem; + const DeleteActionButton({ + super.key, + required this.source, + this.showConfirmation = false, + this.iconOnly = false, + this.menuItem = false, + }); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + if (showConfirmation) { + final confirm = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('delete'.t(context: context)), + content: Text('delete_action_confirmation_message'.t(context: context)), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('cancel'.t(context: context)), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text( + 'confirm'.t(context: context), + style: TextStyle(color: context.colorScheme.error), + ), + ), + ], + ), + ); + if (confirm != true) return; + } + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'delete_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 110.0, + iconData: Icons.delete_sweep_outlined, + label: "delete".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart new file mode 100644 index 0000000000..2e15de49df --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +/// This delete action has the following behavior: +/// - Prompt to delete the asset locally +class DeleteLocalActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const DeleteLocalActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).deleteLocal(source, context); + if (result == null) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + if (result.count == 0) { + return; + } + + final successMessage = 'delete_local_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 95.0, + iconData: Icons.no_cell_outlined, + label: "control_bottom_app_bar_delete_from_local".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart new file mode 100644 index 0000000000..27a1a4d8af --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/permanent_delete_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +/// This delete action has the following behavior: +/// - Delete permanently on the server +/// - Prompt to delete the asset locally +class DeletePermanentActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const DeletePermanentActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final count = source == ActionSource.viewer ? 1 : ref.read(multiSelectProvider).selectedAssets.length; + final confirm = + await showDialog( + context: context, + builder: (context) => PermanentDeleteDialog(count: count), + ) ?? + false; + if (!confirm) return; + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'delete_permanently_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 110.0, + iconData: Icons.delete_forever, + label: "delete_permanently".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart new file mode 100644 index 0000000000..d19a188561 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/permanent_delete_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +/// This delete action has the following behavior: +/// - Delete permanently on the server +/// - Prompt to delete the asset locally +/// +/// This action is used when the asset is selected in multi-selection mode in the trash page +class DeleteTrashActionButton extends ConsumerWidget { + final ActionSource source; + + const DeleteTrashActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final selectCount = ref.watch(multiSelectProvider.select((s) => s.selectedAssets.length)); + + final confirmDelete = + await showDialog( + context: context, + builder: (context) => PermanentDeleteDialog(count: selectCount), + ) ?? + false; + if (!confirmDelete) { + return; + } + + final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'assets_permanently_deleted_count'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return TextButton.icon( + icon: Icon(Icons.delete_forever, color: Colors.red[400]), + label: Text( + "delete".t(context: context), + style: TextStyle(fontSize: 14, color: Colors.red[400], fontWeight: FontWeight.bold), + ), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart new file mode 100644 index 0000000000..a5129b643a --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/download_action_button.widget.dart @@ -0,0 +1,47 @@ +import 'package:immich_mobile/constants/enums.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/utils/background_sync.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; + +class DownloadActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + const DownloadActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref, BackgroundSyncManager backgroundSyncManager) async { + if (!context.mounted) { + return; + } + + try { + await ref.read(actionProvider.notifier).downloadAll(source); + + Future.delayed(const Duration(seconds: 1), () async { + await backgroundSyncManager.syncLocal(); + await backgroundSyncManager.hashAssets(); + }); + } finally { + ref.read(multiSelectProvider.notifier).reset(); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final backgroundManager = ref.watch(backgroundSyncProvider); + + return BaseActionButton( + iconData: Icons.download, + maxWidth: 95, + label: "download".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref, backgroundManager), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart index 264e489db3..efa7f5c6d0 100644 --- a/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/download_status_floating_button.widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -35,7 +33,7 @@ class DownloadStatusFloatingButton extends ConsumerWidget { : context.colorScheme.surfaceBright, elevation: 2, onPressed: () { - unawaited(context.pushRoute(const DownloadInfoRoute())); + context.pushRoute(const DownloadInfoRoute()); }, child: Stack( alignment: AlignmentDirectional.center, diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart new file mode 100644 index 0000000000..6eeec0658b --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class EditDateTimeActionButton extends ConsumerWidget { + final ActionSource source; + + const EditDateTimeActionButton({super.key, required this.source}); + + _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).editDateTime(source, context); + if (result == null) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'edit_date_and_time_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 95.0, + iconData: Icons.edit_calendar_outlined, + label: "control_bottom_app_bar_edit_time".t(context: context), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart new file mode 100644 index 0000000000..cad74ce658 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/edit_image_action_button.widget.dart @@ -0,0 +1,32 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class EditImageActionButton extends ConsumerWidget { + const EditImageActionButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentAsset = ref.watch(assetViewerProvider.select((s) => s.currentAsset)); + + onPress() { + if (currentAsset == null) { + return; + } + + final image = Image(image: getFullImageProvider(currentAsset)); + context.pushRoute(DriftEditImageRoute(asset: currentAsset, image: image, isEdited: false)); + } + + return BaseActionButton( + iconData: Icons.tune, + label: "edit".t(context: context), + onPressed: onPress, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart new file mode 100644 index 0000000000..1a8a1a5c39 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/edit_location_action_button.widget.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class EditLocationActionButton extends ConsumerWidget { + final ActionSource source; + + const EditLocationActionButton({super.key, required this.source}); + + _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).editLocation(source, context); + if (result == null) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'edit_location_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.edit_location_alt_outlined, + label: "control_bottom_app_bar_edit_location".t(context: context), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart new file mode 100644 index 0000000000..07ace7e631 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/favorite_action_button.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class FavoriteActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const FavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).favorite(source); + + if (source == ActionSource.viewer) { + if (result.success) { + final currentAsset = ref.read(assetViewerProvider).currentAsset; + if (currentAsset is RemoteAsset && !currentAsset.isFavorite) { + ref.read(assetViewerProvider.notifier).setAsset(currentAsset.copyWith(isFavorite: true)); + } + } + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'favorite_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.favorite_border_rounded, + label: "favorite".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart index e39a76ef02..96a7daa327 100644 --- a/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/like_activity_action_button.widget.dart @@ -1,8 +1,9 @@ import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; @@ -22,20 +23,20 @@ class LikeActivityActionButton extends ConsumerWidget { final asset = ref.watch(assetViewerProvider.select((s) => s.currentAsset)) as RemoteAsset?; final user = ref.watch(currentUserProvider); - final activities = ref.watch(albumActivityProvider((album?.id ?? "", asset?.id))); + final activities = ref.watch(albumActivityProvider(album?.id ?? "", asset?.id)); - Future onTap(Activity? liked) async { + onTap(Activity? liked) async { if (user == null) { return; } if (liked != null) { - await ref.read(albumActivityProvider((album?.id ?? "", asset?.id)).notifier).removeActivity(liked.id); + await ref.read(albumActivityProvider(album?.id ?? "", asset?.id).notifier).removeActivity(liked.id); } else { - await ref.read(albumActivityProvider((album?.id ?? "", asset?.id)).notifier).addLike(); + await ref.read(albumActivityProvider(album?.id ?? "", asset?.id).notifier).addLike(); } - ref.invalidate(albumActivityProvider((album?.id ?? "", asset?.id))); + ref.invalidate(albumActivityProvider(album?.id ?? "", asset?.id)); } return activities.when( @@ -47,7 +48,7 @@ class LikeActivityActionButton extends ConsumerWidget { return BaseActionButton( maxWidth: 60, iconData: liked != null ? Icons.thumb_up : Icons.thumb_up_off_alt, - label: context.t.like, + label: "like".t(context: context), onPressed: () => onTap(liked), iconOnly: iconOnly, menuItem: menuItem, @@ -57,11 +58,11 @@ class LikeActivityActionButton extends ConsumerWidget { // default to empty heart during loading loading: () => BaseActionButton( iconData: Icons.thumb_up_off_alt, - label: context.t.like, + label: "like".t(context: context), iconOnly: iconOnly, menuItem: menuItem, ), - error: (error, stack) => Text(context.t.error_saving_image(error: error.toString())), + error: (error, stack) => Text('error_saving_image'.tr(args: [error.toString()])), ); } } diff --git a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart index 6be8a6b023..3bd67978e2 100644 --- a/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; @@ -16,8 +16,8 @@ class MotionPhotoActionButton extends ConsumerWidget { return BaseActionButton( iconData: isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded, - label: context.t.play_motion_photo, - onPressed: ref.watch(isPlayingMotionVideoProvider.notifier).toggle, + label: "play_motion_photo".t(context: context), + onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle, iconOnly: iconOnly, menuItem: menuItem, ); diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart new file mode 100644 index 0000000000..2f7c3899eb --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +// Reusable helper: move to locked folder from any source (e.g called from menu) +Future performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).moveToLockFolder(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'move_to_lock_folder_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} + +class MoveToLockFolderActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const MoveToLockFolderActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + Future _onTap(BuildContext context, WidgetRef ref) async { + await performMoveToLockFolderAction(context, ref, source: source); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 115.0, + iconData: Icons.lock_outline_rounded, + label: "move_to_locked_folder".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart new file mode 100644 index 0000000000..17703d0beb --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class OpenInBrowserActionButton extends ConsumerWidget { + final String remoteId; + final TimelineOrigin origin; + final bool iconOnly; + final bool menuItem; + final Color? iconColor; + + const OpenInBrowserActionButton({ + super.key, + required this.remoteId, + required this.origin, + this.iconOnly = false, + this.menuItem = false, + this.iconColor, + }); + + void _onTap() async { + final serverEndpoint = Store.get(StoreKey.serverEndpoint).replaceFirst('/api', ''); + + String originPath = ''; + switch (origin) { + case TimelineOrigin.favorite: + originPath = '/favorites'; + break; + case TimelineOrigin.trash: + originPath = '/trash'; + break; + case TimelineOrigin.archive: + originPath = '/archive'; + break; + default: + break; + } + + final url = '$serverEndpoint$originPath/photos/$remoteId'; + if (await canLaunchUrl(Uri.parse(url))) { + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + label: 'open_in_browser'.t(context: context), + iconData: Icons.open_in_browser, + iconColor: iconColor, + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: _onTap, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart new file mode 100644 index 0000000000..97a36a56dc --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class RemoveFromAlbumActionButton extends ConsumerWidget { + final String albumId; + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const RemoveFromAlbumActionButton({ + super.key, + required this.albumId, + required this.source, + this.iconOnly = false, + this.menuItem = false, + }); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'remove_from_album_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.remove_circle_outline, + label: "remove_from_album".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart new file mode 100644 index 0000000000..17d2a76af7 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class RemoveFromLockFolderActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const RemoveFromLockFolderActionButton({ + super.key, + required this.source, + this.iconOnly = false, + this.menuItem = false, + }); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).removeFromLockFolder(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'remove_from_lock_folder_action_prompt'.t( + context: context, + args: {'count': result.count.toString()}, + ); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 100.0, + iconData: Icons.lock_open_rounded, + label: "remove_from_locked_folder".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart new file mode 100644 index 0000000000..e7928bd325 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class RestoreTrashActionButton extends ConsumerWidget { + final ActionSource source; + + const RestoreTrashActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).restoreTrash(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'assets_restored_count'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return TextButton.icon( + icon: const Icon(Icons.history_rounded), + label: Text('restore'.t(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart b/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart new file mode 100644 index 0000000000..1d704aafe8 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/set_album_cover.widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class SetAlbumCoverActionButton extends ConsumerWidget { + final String albumId; + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const SetAlbumCoverActionButton({ + super.key, + required this.albumId, + required this.source, + this.iconOnly = false, + this.menuItem = false, + }); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).setAlbumCover(source, albumId); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'album_cover_updated'.t(context: context); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.image_outlined, + label: 'set_as_album_cover'.t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart new file mode 100644 index 0000000000..c8dbb7cb1f --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart @@ -0,0 +1,35 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class SetProfilePictureActionButton extends ConsumerWidget { + final BaseAsset asset; + final bool iconOnly; + final bool menuItem; + + const SetProfilePictureActionButton({super.key, required this.asset, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context) { + if (!context.mounted) { + return; + } + + context.pushRoute(ProfilePictureCropRoute(asset: asset)); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.account_circle_outlined, + label: "set_as_profile_picture".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart new file mode 100644 index 0000000000..6fbd6f7dfa --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/share_action_button.widget.dart @@ -0,0 +1,93 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class _SharePreparingDialog extends StatelessWidget { + const _SharePreparingDialog(); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()), + ], + ), + ); + } +} + +class ShareActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const ShareActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final cancelCompleter = Completer(); + const preparingDialog = _SharePreparingDialog(); + await showDialog( + context: context, + builder: (BuildContext buildContext) { + ref.read(actionProvider.notifier).shareAssets(source, context, cancelCompleter: cancelCompleter).then(( + ActionResult result, + ) { + if (cancelCompleter.isCompleted || !context.mounted) { + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + if (!result.success) { + ImmichToast.show( + context: context, + msg: 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } + + buildContext.pop(); + }); + + // show a loading spinner with a "Preparing" message + return preparingDialog; + }, + barrierDismissible: false, + useRootNavigator: false, + ).then((_) { + if (!cancelCompleter.isCompleted) { + cancelCompleter.complete(); + } + }); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded, + label: 'share'.t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart new file mode 100644 index 0000000000..b8dc69f515 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/share_link_action_button.widget.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; + +class ShareLinkActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const ShareLinkActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + await ref.read(actionProvider.notifier).shareLink(source, context); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.link_rounded, + label: "share_link".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart new file mode 100644 index 0000000000..bb42140d0a --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart @@ -0,0 +1,58 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class SimilarPhotosActionButton extends ConsumerWidget { + final String assetId; + final bool iconOnly; + final bool menuItem; + + const SimilarPhotosActionButton({super.key, required this.assetId, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + ref.invalidate(assetViewerProvider); + ref.invalidate(paginatedSearchProvider); + + ref.read(searchPreFilterProvider.notifier) + ..clear() + ..setFilter( + SearchFilter( + assetId: assetId, + people: {}, + location: SearchLocationFilter(), + camera: SearchCameraFilter(), + date: SearchDateFilter(), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + rating: SearchRatingFilter(), + mediaType: AssetType.image, + ), + ); + + unawaited(context.navigateTo(const DriftSearchRoute())); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.compare, + label: "view_similar_photos".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + maxWidth: 100, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart new file mode 100644 index 0000000000..22fccf5473 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/stack_action_button.widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class StackActionButton extends ConsumerWidget { + final ActionSource source; + + const StackActionButton({super.key, required this.source}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final user = ref.watch(currentUserProvider); + if (user == null) { + throw Exception('User must be logged in to access stack action'); + } + + final result = await ref.read(actionProvider.notifier).stack(user.id, source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'stack_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.filter_none_rounded, + label: "stack".t(context: context), + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart new file mode 100644 index 0000000000..e95569af45 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +/// This delete action has the following behavior: +/// - Set the deletedAt information, put the asset in the trash in the server +/// which will be permanently deleted after the number of days configure by the admin +class TrashActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const TrashActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).trash(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'trash_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + maxWidth: 85.0, + iconData: Icons.delete_outline_rounded, + label: "control_bottom_app_bar_trash_from_immich".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart new file mode 100644 index 0000000000..98e868d953 --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart @@ -0,0 +1,59 @@ +// dart +// File: `lib/presentation/widgets/action_buttons/unarchive_action_button.widget.dart` +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; + +// used to allow performing unarchive action from different sources (without duplicating code) +Future performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async { + if (!context.mounted) return; + + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + + final result = await ref.read(actionProvider.notifier).unArchive(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } +} + +class UnArchiveActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const UnArchiveActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + Future _onTap(BuildContext context, WidgetRef ref) async { + await performUnArchiveAction(context, ref, source: source); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.unarchive_outlined, + label: "unarchive".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart new file mode 100644 index 0000000000..5e88735d9c --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class UnFavoriteActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const UnFavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).unFavorite(source); + + if (source == ActionSource.viewer) { + if (result.success) { + final currentAsset = ref.read(assetViewerProvider).currentAsset; + if (currentAsset is RemoteAsset && currentAsset.isFavorite) { + ref.read(assetViewerProvider.notifier).setAsset(currentAsset.copyWith(isFavorite: false)); + } + } + return; + } + + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'unfavorite_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.favorite_rounded, + label: "unfavorite".t(context: context), + onPressed: () => _onTap(context, ref), + iconOnly: iconOnly, + menuItem: menuItem, + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart new file mode 100644 index 0000000000..e7badf129f --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/unstack_action_button.widget.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class UnStackActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const UnStackActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final result = await ref.read(actionProvider.notifier).unStack(source); + ref.read(multiSelectProvider.notifier).reset(); + + final successMessage = 'unstack_action_prompt'.t(context: context, args: {'count': result.count.toString()}); + + if (context.mounted) { + ImmichToast.show( + context: context, + msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: result.success ? ToastType.success : ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.layers_clear_outlined, + label: "unstack".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} diff --git a/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart new file mode 100644 index 0000000000..98eb09a4aa --- /dev/null +++ b/mobile/lib/presentation/widgets/action_buttons/upload_action_button.widget.dart @@ -0,0 +1,113 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_ui/immich_ui.dart'; + +class UploadActionButton extends ConsumerWidget { + final ActionSource source; + final bool iconOnly; + final bool menuItem; + + const UploadActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false}); + + void _onTap(BuildContext context, WidgetRef ref) async { + if (!context.mounted) { + return; + } + + final isTimeline = source == ActionSource.timeline; + List? assets; + + if (source == ActionSource.timeline) { + assets = ref.read(multiSelectProvider).selectedAssets.whereType().toList(); + if (assets.isEmpty) { + return; + } + ref.read(multiSelectProvider.notifier).reset(); + } else { + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => const _UploadProgressDialog(), + ), + ); + } + + final result = await ref.read(actionProvider.notifier).upload(source, assets: assets); + + if (!isTimeline && context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + } + + if (context.mounted && !result.success) { + ImmichToast.show( + context: context, + msg: 'scaffold_body_error_occurred'.t(context: context), + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return BaseActionButton( + iconData: Icons.backup_outlined, + label: "upload".t(context: context), + iconOnly: iconOnly, + menuItem: menuItem, + onPressed: () => _onTap(context, ref), + ); + } +} + +class _UploadProgressDialog extends ConsumerWidget { + const _UploadProgressDialog(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final progressMap = ref.watch(assetUploadProgressProvider); + + // Calculate overall progress from all assets + final values = progressMap.values.where((v) => v >= 0).toList(); + final progress = values.isEmpty ? 0.0 : values.reduce((a, b) => a + b) / values.length; + final hasError = progressMap.values.any((v) => v < 0); + final percentage = (progress * 100).toInt(); + + return AlertDialog( + title: Text('uploading'.t(context: context)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (hasError) + const Icon(Icons.error_outline, color: Colors.red, size: 48) + else + CircularProgressIndicator(value: progress > 0 ? progress : null), + const SizedBox(height: 16), + Text(hasError ? 'Error' : '$percentage%'), + ], + ), + actions: [ + ImmichTextButton( + onPressed: () { + ref.read(manualUploadCancelTokenProvider)?.complete(); + ref.read(manualUploadCancelTokenProvider.notifier).state = null; + Navigator.of(context).pop(); + }, + labelText: 'cancel'.t(context: context), + ), + ], + ); + } +} diff --git a/mobile/lib/presentation/widgets/album/album_selector.widget.dart b/mobile/lib/presentation/widgets/album/album_selector.widget.dart index b66063f737..0c039847a4 100644 --- a/mobile/lib/presentation/widgets/album/album_selector.widget.dart +++ b/mobile/lib/presentation/widgets/album/album_selector.widget.dart @@ -2,25 +2,28 @@ import 'dart:async'; import 'dart:math'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/app_settings.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/settings.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/album_filter.utils.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -47,22 +50,30 @@ class _AlbumSelectorState extends ConsumerState { List sortedAlbums = []; List shownAlbums = []; - AlbumFilter filter = const AlbumFilter(query: "", mode: QuickFilterMode.all); - AlbumSort sort = const AlbumSort(mode: AlbumSortMode.lastModified, isReverse: true); + AlbumFilter filter = AlbumFilter(query: "", mode: QuickFilterMode.all); + AlbumSort sort = AlbumSort(mode: AlbumSortMode.lastModified, isReverse: true); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { - final albumConfig = ref.read(appConfigProvider).album; + final appSettings = ref.read(appSettingsServiceProvider); + final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder); + final savedIsReverse = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortReverse); + final savedIsGrid = appSettings.getSetting(AppSettingsEnum.albumGridView); + + final albumSortMode = AlbumSortMode.values.firstWhere( + (e) => e.storeIndex == savedSortMode, + orElse: () => AlbumSortMode.lastModified, + ); setState(() { - sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse); - isGrid = albumConfig.isGrid; + sort = AlbumSort(mode: albumSortMode, isReverse: savedIsReverse); + isGrid = savedIsGrid; }); - unawaited(ref.read(remoteAlbumProvider.notifier).refresh()); + ref.read(remoteAlbumProvider.notifier).refresh(); }); searchController.addListener(() { @@ -80,7 +91,7 @@ class _AlbumSelectorState extends ConsumerState { final userId = ref.read(currentUserProvider)?.id; filter = filter.copyWith(query: searchTerm, userId: userId, mode: filterMode); - unawaited(filterAlbums()); + filterAlbums(); } Future onRefresh() async { @@ -91,7 +102,7 @@ class _AlbumSelectorState extends ConsumerState { setState(() { isGrid = !isGrid; }); - unawaited(ref.read(settingsProvider).write(.albumIsGrid, isGrid)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid); } void changeFilter(QuickFilterMode mode) { @@ -99,7 +110,7 @@ class _AlbumSelectorState extends ConsumerState { filter = filter.copyWith(mode: mode); }); - unawaited(filterAlbums()); + filterAlbums(); } Future changeSort(AlbumSort sort) async { @@ -107,9 +118,9 @@ class _AlbumSelectorState extends ConsumerState { this.sort = sort; }); - final metadata = ref.read(settingsProvider); - await metadata.write(.albumSortMode, sort.mode); - await metadata.write(.albumIsReverse, sort.isReverse); + final appSettings = ref.read(appSettingsServiceProvider); + await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex); + await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse); await sortAlbums(); } @@ -120,7 +131,7 @@ class _AlbumSelectorState extends ConsumerState { searchController.clear(); }); - unawaited(filterAlbums()); + filterAlbums(); } Future sortAlbums() async { @@ -325,7 +336,7 @@ class _SortButtonState extends ConsumerState<_SortButton> { ), ), child: Text( - sortMode.label(context.t), + sortMode.label.t(context: context), style: context.textTheme.labelLarge?.copyWith( color: albumSortOption == sortMode ? context.colorScheme.onPrimary @@ -353,7 +364,7 @@ class _SortButtonState extends ConsumerState<_SortButton> { : Icon(Icons.keyboard_arrow_up_rounded, color: context.colorScheme.onSurface), ), Text( - albumSortOption.label(context.t), + albumSortOption.label.t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(225)), ), isSorting @@ -394,7 +405,7 @@ class _SearchBar extends StatelessWidget { return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), sliver: SliverToBoxAdapter( - child: DecoratedBox( + child: Container( decoration: BoxDecoration( border: Border.all(color: context.colorScheme.onSurface.withAlpha(0), width: 0), borderRadius: const BorderRadius.all(Radius.circular(24)), @@ -412,7 +423,7 @@ class _SearchBar extends StatelessWidget { child: SearchField( autofocus: false, contentPadding: const EdgeInsets.all(16), - hintText: context.t.search_albums, + hintText: 'search_albums'.tr(), prefixIcon: const Icon(Icons.search_rounded), suffixIcon: searchController.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear_rounded), onPressed: onClearSearch) @@ -451,7 +462,7 @@ class _QuickFilterButtonRow extends StatelessWidget { runSpacing: 4, children: [ _QuickFilterButton( - label: context.t.all, + label: 'all'.tr(), isSelected: filterMode == QuickFilterMode.all, onTap: () { onChangeFilter(QuickFilterMode.all); @@ -459,7 +470,7 @@ class _QuickFilterButtonRow extends StatelessWidget { }, ), _QuickFilterButton( - label: context.t.shared_with_me, + label: 'shared_with_me'.tr(), isSelected: filterMode == QuickFilterMode.sharedWithMe, onTap: () { onChangeFilter(QuickFilterMode.sharedWithMe); @@ -467,7 +478,7 @@ class _QuickFilterButtonRow extends StatelessWidget { }, ), _QuickFilterButton( - label: context.t.my_albums, + label: 'my_albums'.tr(), isSelected: filterMode == QuickFilterMode.myAlbums, onTap: () { onChangeFilter(QuickFilterMode.myAlbums); @@ -570,7 +581,7 @@ class _AlbumList extends ConsumerWidget { if (albums.isEmpty) { return SliverToBoxAdapter( child: Center( - child: Padding(padding: const EdgeInsets.all(20.0), child: Text(context.t.album_search_not_found)), + child: Padding(padding: const EdgeInsets.all(20.0), child: Text('album_search_not_found'.tr())), ), ); } @@ -599,9 +610,9 @@ class _AlbumList extends ConsumerWidget { context: context, builder: (context) => ConfirmDialog( onOk: () => true, - title: context.t.delete_album, - content: context.t.album_delete_confirmation(album: album.name), - ok: context.t.delete, + title: "delete_album".t(context: context), + content: "album_delete_confirmation".t(context: context, args: {'album': album.name}), + ok: "delete".t(context: context), ), ); }, @@ -636,7 +647,7 @@ class _AlbumGrid extends StatelessWidget { if (albums.isEmpty) { return SliverToBoxAdapter( child: Center( - child: Padding(padding: const EdgeInsets.all(20.0), child: Text(context.t.album_search_not_found)), + child: Padding(padding: const EdgeInsets.all(20.0), child: Text('album_search_not_found'.tr())), ), ); } @@ -668,7 +679,7 @@ class _GridAlbumCard extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final albumThumbnailAsset = ref.watch(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? ""); + final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? ""); return GestureDetector( onTap: () => onAlbumSelected(album), @@ -698,7 +709,7 @@ class _GridAlbumCard extends ConsumerWidget { ); } - return ColoredBox( + return Container( color: context.colorScheme.surfaceContainerHighest, child: const Icon(Icons.photo_album_rounded, size: 40, color: Colors.grey), ); @@ -722,7 +733,7 @@ class _GridAlbumCard extends ConsumerWidget { style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), Text( - '${context.t.items_count(count: album.assetCount)} â€ĸ ${album.ownerId != userId ? context.t.shared_by_user(user: album.ownerName) : context.t.owned}', + '${'items_count'.t(context: context, args: {'count': album.assetCount})} â€ĸ ${album.ownerId != userId ? 'shared_by_user'.t(context: context, args: {'user': album.ownerName}) : 'owned'.t(context: context)}', maxLines: 1, overflow: TextOverflow.ellipsis, style: context.textTheme.labelMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), @@ -744,22 +755,15 @@ class AddToAlbumHeader extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { - final albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); - if (albumName == null || !context.mounted) { - return; - } - - final selectedAssets = ref.read(multiSelectProvider).selectedAssets; final newAlbum = await ref .read(remoteAlbumProvider.notifier) - .createAlbumWithAssets(title: albumName, assets: selectedAssets); - - if (!context.mounted) { - return; - } + .createAlbum( + title: "Untitled Album", + assetIds: ref.read(multiSelectProvider).selectedAssets.map((e) => (e as RemoteAsset).id).toList(), + ); if (newAlbum == null) { - ImmichToast.show(context: context, toastType: ToastType.error, msg: context.t.errors.failed_to_create_album); + ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); return; } @@ -773,19 +777,19 @@ class AddToAlbumHeader extends ConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(context.t.add_to_album, style: context.textTheme.titleSmall), + Text("add_to_album", style: context.textTheme.titleSmall).tr(), TextButton.icon( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), // remove internal padding - minimumSize: Size.zero, // allow shrinking + minimumSize: const Size(0, 0), // allow shrinking tapTargetSize: MaterialTapTargetSize.shrinkWrap, // remove extra height ), onPressed: onCreateAlbum, icon: Icon(Icons.add, color: context.primaryColor), label: Text( - context.t.common_create_new_album, + "common_create_new_album", style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14), - ), + ).tr(), ), ], ), @@ -800,8 +804,8 @@ class CreateAlbumButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { Future onCreateAlbum() async { - final albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); - if (albumName == null || !context.mounted) { + var albumName = await showDialog(context: context, builder: (context) => const NewAlbumNameModal()); + if (albumName == null) { return; } @@ -816,24 +820,20 @@ class CreateAlbumButton extends ConsumerWidget { .read(remoteAlbumProvider.notifier) .createAlbum(title: albumName, assetIds: [asset.remoteId!]); - if (!context.mounted) { - return; - } - if (album == null) { - ImmichToast.show(context: context, toastType: ToastType.error, msg: context.t.errors.failed_to_create_album); + ImmichToast.show(context: context, toastType: ToastType.error, msg: 'errors.failed_to_create_album'.tr()); return; } ImmichToast.show( context: context, - msg: context.t.add_to_album_bottom_sheet_added(album: album.name), + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {'album': album.name}), ); // Invalidate using the asset's remote ID to refresh the "Appears in" list ref.invalidate(albumsContainingAssetProvider(asset.remoteId!)); - ContextHelper(context).pop(); + context.pop(); } return SliverPadding( @@ -842,19 +842,19 @@ class CreateAlbumButton extends ConsumerWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(context.t.add_to_album, style: context.textTheme.titleSmall), + Text("add_to_album", style: context.textTheme.titleSmall).tr(), TextButton.icon( style: TextButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - minimumSize: Size.zero, + minimumSize: const Size(0, 0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), onPressed: onCreateAlbum, icon: Icon(Icons.add, color: context.primaryColor), label: Text( - context.t.common_create_new_album, + "common_create_new_album", style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14), - ), + ).tr(), ), ], ), diff --git a/mobile/lib/presentation/widgets/album/album_tile.dart b/mobile/lib/presentation/widgets/album/album_tile.dart index 54fc27cfd1..1aeadf61bc 100644 --- a/mobile/lib/presentation/widgets/album/album_tile.dart +++ b/mobile/lib/presentation/widgets/album/album_tile.dart @@ -3,7 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/pages/common/large_leading_tile.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; @@ -17,7 +17,7 @@ class AlbumTile extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final albumThumbnailAsset = ref.watch(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? ""); + final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? ""); return LargeLeadingTile( title: Text( @@ -27,7 +27,7 @@ class AlbumTile extends ConsumerWidget { style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600), ), subtitle: Text( - '${context.t.items_count(count: album.assetCount)} â€ĸ ${isOwner ? context.t.owned : context.t.shared_by_user(user: album.ownerName)}', + '${'items_count'.t(context: context, args: {'count': album.assetCount})} â€ĸ ${isOwner ? 'owned'.t(context: context) : 'shared_by_user'.t(context: context, args: {'user': album.ownerName})}', overflow: TextOverflow.ellipsis, style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), @@ -51,7 +51,7 @@ class AlbumTile extends ConsumerWidget { : SizedBox( width: 80, height: 80, - child: DecoratedBox( + child: Container( decoration: BoxDecoration( color: context.colorScheme.surfaceContainer, borderRadius: const BorderRadius.all(Radius.circular(16)), diff --git a/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart b/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart index 55024d9b2b..691b46f80d 100644 --- a/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart +++ b/mobile/lib/presentation/widgets/album/drift_activity_text_field.dart @@ -1,7 +1,7 @@ +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/generated/translations.g.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; @@ -98,7 +98,7 @@ class _DriftActivityTextFieldState extends ConsumerState color: context.primaryColor, disabledColor: context.colorScheme.secondaryContainer, ), - hintText: !widget.isEnabled ? context.t.shared_album_activities_input_disable : context.t.say_something, + hintText: !widget.isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(), hintStyle: TextStyle(fontWeight: FontWeight.normal, fontSize: 14, color: Colors.grey[600]), ), onEditingComplete: onEditingComplete, diff --git a/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart b/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart index 255dbd4a0c..a5e21af489 100644 --- a/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart +++ b/mobile/lib/presentation/widgets/album/new_album_name_modal.widget.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class NewAlbumNameModal extends StatefulWidget { const NewAlbumNameModal({super.key}); @@ -21,31 +21,31 @@ class _NewAlbumNameModalState extends State { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(context.t.album_name, style: const TextStyle(fontWeight: FontWeight.bold)), + title: const Text("album_name", style: TextStyle(fontWeight: FontWeight.bold)).tr(), content: SingleChildScrollView( child: TextFormField( controller: nameController, textCapitalization: TextCapitalization.words, autofocus: true, - decoration: InputDecoration(hintText: context.t.name, border: const OutlineInputBorder()), + decoration: InputDecoration(hintText: 'name'.tr(), border: const OutlineInputBorder()), ), ), actions: [ TextButton( onPressed: () => context.pop(null), child: Text( - context.t.cancel, + "cancel", style: TextStyle(color: Colors.red[300], fontWeight: FontWeight.bold), - ), + ).tr(), ), TextButton( onPressed: () { context.pop(nameController.text.trim()); }, child: Text( - context.t.create_album, + "create_album", style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart b/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart deleted file mode 100644 index 796c4ce43a..0000000000 --- a/mobile/lib/presentation/widgets/album/pending_uploads_banner.widget.dart +++ /dev/null @@ -1,268 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; -import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; -import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; - -/// Pinned banner sliver that surfaces in-flight album uploads directly under -/// the album app bar. Renders nothing while the queue is empty. Tapping the -/// banner opens a bottom sheet with per-asset progress. -class PendingUploadsBanner extends ConsumerWidget { - static const double _height = 52; - - final String albumId; - - const PendingUploadsBanner({super.key, required this.albumId}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); - if (pending.isEmpty) { - return const SliverToBoxAdapter(child: SizedBox.shrink()); - } - - final hasFailures = pending.any((p) => p.failed); - final clamped = pending.map((p) => p.progress.clamp(0.0, 1.0)).toList(growable: false); - final overallProgress = clamped.isEmpty ? 0.0 : clamped.reduce((a, b) => a + b) / clamped.length; - final isIndeterminate = overallProgress <= 0.0; - - return SliverPersistentHeader( - pinned: true, - delegate: _PendingUploadsBannerDelegate( - height: _height, - child: _PendingUploadsBannerContent( - albumId: albumId, - previewAsset: pending.first.asset, - count: pending.length, - overallProgress: overallProgress, - isIndeterminate: isIndeterminate, - hasFailures: hasFailures, - ), - ), - ); - } - - static Future _openSheet(BuildContext context, String albumId) { - return showModalBottomSheet( - context: context, - showDragHandle: true, - builder: (_) => _PendingUploadsSheet(albumId: albumId), - ); - } -} - -class _PendingUploadsBannerDelegate extends SliverPersistentHeaderDelegate { - final double height; - final Widget child; - - const _PendingUploadsBannerDelegate({required this.height, required this.child}); - - @override - double get minExtent => height; - - @override - double get maxExtent => height; - - @override - Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) => child; - - @override - bool shouldRebuild(covariant _PendingUploadsBannerDelegate oldDelegate) => - height != oldDelegate.height || child != oldDelegate.child; -} - -class _PendingUploadsBannerContent extends StatelessWidget { - final String albumId; - final BaseAsset previewAsset; - final int count; - final double overallProgress; - final bool isIndeterminate; - final bool hasFailures; - - const _PendingUploadsBannerContent({ - required this.albumId, - required this.previewAsset, - required this.count, - required this.overallProgress, - required this.isIndeterminate, - required this.hasFailures, - }); - - @override - Widget build(BuildContext context) { - final percentLabel = isIndeterminate ? '' : ' ¡ ${(overallProgress * 100).toInt()}%'; - return Material( - color: hasFailures ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainerHigh, - child: InkWell( - onTap: () => unawaited(PendingUploadsBanner._openSheet(context, albumId)), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(4)), - child: SizedBox(width: 32, height: 32, child: Thumbnail.fromAsset(asset: previewAsset)), - ), - const SizedBox(width: 12), - Expanded( - child: Text( - '${context.t.uploading} $count$percentLabel', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500), - ), - ), - if (hasFailures) - Padding( - padding: const EdgeInsets.only(left: 8), - child: Icon(Icons.error_outline, color: context.colorScheme.error, size: 20), - ), - Icon(Icons.chevron_right_rounded, color: context.colorScheme.onSurfaceVariant), - ], - ), - ), - ), - SizedBox( - height: 3, - child: LinearProgressIndicator( - value: isIndeterminate ? null : overallProgress, - backgroundColor: context.colorScheme.surfaceContainerHighest, - valueColor: AlwaysStoppedAnimation( - hasFailures ? context.colorScheme.error : context.colorScheme.primary, - ), - ), - ), - ], - ), - ), - ); - } -} - -class _PendingUploadsSheet extends ConsumerWidget { - final String albumId; - - const _PendingUploadsSheet({required this.albumId}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final pending = ref.watch(pendingAlbumUploadsProvider(albumId)); - - // Auto-dismiss when the queue empties. - if (pending.isEmpty) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } - }); - return const SizedBox.shrink(); - } - - final failedCount = pending.where((p) => p.failed).length; - final inFlightCount = pending.length - failedCount; - final canAbort = inFlightCount > 0 && ref.watch(manualUploadCancelTokenProvider) != null; - - return SafeArea( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Row( - children: [ - Expanded( - child: Text('${context.t.uploading} (${pending.length})', style: context.textTheme.titleMedium), - ), - if (canAbort) - TextButton.icon( - onPressed: () { - final cancelToken = ref.read(manualUploadCancelTokenProvider); - if (cancelToken != null && !cancelToken.isCompleted) { - cancelToken.complete(); - } - ref.read(manualUploadCancelTokenProvider.notifier).state = null; - ref.read(pendingAlbumUploadsProvider(albumId).notifier).clear(); - }, - icon: const Icon(Icons.stop_circle_outlined, size: 18), - label: Text(context.t.cancel), - style: TextButton.styleFrom(foregroundColor: context.colorScheme.error), - ) - else if (failedCount > 0) - TextButton.icon( - onPressed: () => ref.read(pendingAlbumUploadsProvider(albumId).notifier).clearFailed(), - icon: const Icon(Icons.clear_rounded, size: 18), - label: Text(context.t.clear_failed_count(count: failedCount)), - style: TextButton.styleFrom(foregroundColor: context.colorScheme.error), - ), - ], - ), - ), - SizedBox( - height: 96, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: pending.length, - separatorBuilder: (_, _) => const SizedBox(width: 8), - itemBuilder: (_, index) => _PendingUploadTile(entry: pending[index]), - ), - ), - ], - ), - ), - ); - } -} - -class _PendingUploadTile extends StatelessWidget { - final PendingAlbumUpload entry; - - const _PendingUploadTile({required this.entry}); - - @override - Widget build(BuildContext context) { - return ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: SizedBox( - width: 96, - height: 96, - child: Stack( - fit: StackFit.expand, - children: [ - Thumbnail.fromAsset(asset: entry.asset), - Positioned.fill( - child: ColoredBox( - color: entry.failed ? Colors.red.withValues(alpha: 0.6) : Colors.black54, - child: Center( - child: entry.failed - ? const Icon(Icons.error_outline, color: Colors.white, size: 28) - : SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator( - value: entry.progress > 0 ? entry.progress : null, - strokeWidth: 2.5, - backgroundColor: Colors.white24, - valueColor: const AlwaysStoppedAnimation(Colors.white), - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart index 4b59e1ae60..dd5743a2d0 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details.widget.dart @@ -26,14 +26,6 @@ class AssetDetails extends ConsumerWidget { decoration: BoxDecoration( color: context.colorScheme.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), - boxShadow: [ - BoxShadow( - color: Colors.black.withValues(alpha: 0.2), - offset: const Offset(0, -3), - blurRadius: 10, - spreadRadius: 2, - ), - ], ), child: SafeArea( top: false, diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart index 0cb2ccd254..fc15503a3f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/appears_in_details.widget.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; @@ -7,10 +6,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @@ -22,27 +21,21 @@ class AppearsInDetails extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - if (!asset.hasRemote) { - return const SizedBox.shrink(); - } + if (!asset.hasRemote) return const SizedBox.shrink(); final remoteAssetId = switch (asset) { RemoteAsset(:final id) => id, LocalAsset(:final remoteAssetId) => remoteAssetId, }; - if (remoteAssetId == null) { - return const SizedBox.shrink(); - } + if (remoteAssetId == null) return const SizedBox.shrink(); final userId = ref.watch(currentUserProvider)?.id; final assetAlbums = ref.watch(albumsContainingAssetProvider(remoteAssetId)); return assetAlbums.when( data: (albums) { - if (albums.isEmpty) { - return const SizedBox.shrink(); - } + if (albums.isEmpty) return const SizedBox.shrink(); albums.sortBy((a) => a.name); @@ -52,7 +45,7 @@ class AppearsInDetails extends ConsumerWidget { spacing: 12, children: [ SheetTile( - title: context.t.appears_in, + title: 'appears_in'.t(context: context), titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), Padding( @@ -77,7 +70,7 @@ class AppearsInDetails extends ConsumerWidget { ); }, loading: () => const SizedBox.shrink(), - error: (_, _) => const SizedBox.shrink(), + error: (_, __) => const SizedBox.shrink(), ); } } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart index 9f426f6db7..27bac68310 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/date_time_details.widget.dart @@ -1,5 +1,4 @@ import 'dart:async'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -8,8 +7,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; @@ -29,15 +27,16 @@ class DateTimeDetails extends ConsumerWidget { final asset = this.asset; final exifInfo = this.exifInfo; final isOwner = ref.watch(currentUserProvider)?.id == (asset is RemoteAsset ? asset.ownerId : null); - final editDateTime = const EditDateTimeAction(source: .viewer).create(context, ref); return Column( children: [ SheetTile( title: _getDateTime(context, asset, exifInfo), titleStyle: context.textTheme.labelLarge, - trailing: editDateTime == null ? null : const Icon(Icons.edit, size: 18), - onTap: editDateTime?.onAction, + trailing: asset.hasRemote && isOwner ? const Icon(Icons.edit, size: 18) : null, + onTap: asset.hasRemote && isOwner + ? () async => await ref.read(actionProvider.notifier).editDateTime(ActionSource.viewer, context) + : null, ), if (exifInfo != null) _SheetAssetDescription(exif: exifInfo, isEditable: isOwner), ], @@ -94,13 +93,10 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription> if (!editAction.success) { _controller.text = previousDescription ?? ''; - if (!mounted) { - return; - } ImmichToast.show( context: context, - msg: context.t.exif_bottom_sheet_description_error, + msg: 'exif_bottom_sheet_description_error'.t(context: context), toastType: ToastType.error, ); } @@ -111,9 +107,9 @@ class _SheetAssetDescriptionState extends ConsumerState<_SheetAssetDescription> @override Widget build(BuildContext context) { final currentDescription = widget.exif.description ?? ''; - final hintText = widget.isEditable - ? context.t.exif_bottom_sheet_description - : context.t.exif_bottom_sheet_no_description; + final hintText = (widget.isEditable ? 'exif_bottom_sheet_description' : 'exif_bottom_sheet_no_description').t( + context: context, + ); if (_controller.text != currentDescription && !_descriptionFocus.hasFocus) { _controller.text = currentDescription; } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/location_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/location_details.widget.dart index 47ecb53d8a..8c144a83bd 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/location_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/location_details.widget.dart @@ -1,14 +1,13 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; +import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/detail_panel/exif_map.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -49,11 +48,15 @@ class _LocationDetailsState extends ConsumerState { if (widget.exifInfo != oldWidget.exifInfo) { final exif = widget.exifInfo; if (exif != null && exif.hasCoordinates) { - unawaited(_mapController?.moveCamera(CameraUpdate.newLatLng(LatLng(exif.latitude!, exif.longitude!)))); + _mapController?.moveCamera(CameraUpdate.newLatLng(LatLng(exif.latitude!, exif.longitude!))); } } } + void editLocation() async { + await ref.read(actionProvider.notifier).editLocation(ActionSource.viewer, context); + } + @override Widget build(BuildContext context) { final asset = widget.asset; @@ -65,7 +68,6 @@ class _LocationDetailsState extends ConsumerState { return const SizedBox.shrink(); } - final editLocation = const EditLocationAction(source: .viewer).create(context, ref); final locationName = _getLocationName(exifInfo); final coordinates = "${exifInfo?.latitude?.toStringAsFixed(4)}, ${exifInfo?.longitude?.toStringAsFixed(4)}"; @@ -75,10 +77,10 @@ class _LocationDetailsState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ SheetTile( - title: context.t.location, + title: 'location'.t(context: context), titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), - trailing: hasCoordinates && editLocation != null ? const Icon(Icons.edit_location_alt, size: 20) : null, - onTap: editLocation?.onAction, + trailing: hasCoordinates ? const Icon(Icons.edit_location_alt, size: 20) : null, + onTap: editLocation, ), if (hasCoordinates) Padding( @@ -107,13 +109,13 @@ class _LocationDetailsState extends ConsumerState { ), if (!hasCoordinates) SheetTile( - title: context.t.add_a_location, + title: "add_a_location".t(context: context), titleStyle: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: context.primaryColor, ), leading: const Icon(Icons.location_off), - onTap: editLocation?.onAction, + onTap: editLocation, ), ], ), diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/people_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/people_details.widget.dart index 00c20f35bd..6c6f4a002c 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/people_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/people_details.widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -7,11 +5,11 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/people.utils.dart'; @@ -30,7 +28,7 @@ class PeopleDetails extends ConsumerWidget { final peopleFuture = ref.watch(driftPeopleAssetProvider(asset.id)); - Future showNameEditModal(Person person) async { + Future showNameEditModal(DriftPerson person) async { await showDialog( context: context, useRootNavigator: false, @@ -52,7 +50,7 @@ class PeopleDetails extends ConsumerWidget { Padding( padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16), child: Text( - context.t.people, + "people".t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), ), @@ -75,8 +73,8 @@ class PeopleDetails extends ConsumerWidget { context.back(); return; } - ContextHelper(context).pop(); - unawaited(context.pushRoute(DriftPersonRoute(person: person))); + context.pop(); + context.pushRoute(DriftPersonRoute(person: person)); }, onNameTap: () => showNameEditModal(person), ), @@ -89,14 +87,14 @@ class PeopleDetails extends ConsumerWidget { duration: Durations.short4, ); }, - error: (error, stack) => Text(context.t.errors.failed_to_load_people, style: context.textTheme.bodyMedium), + error: (error, stack) => Text("error_loading_people".t(context: context), style: context.textTheme.bodyMedium), loading: () => const SizedBox.shrink(), ); } } class _Avatar extends StatelessWidget { - final Person person; + final DriftPerson person; final DateTime assetFileCreatedAt; final VoidCallback? onTap; final VoidCallback? onNameTap; @@ -123,9 +121,7 @@ class _Avatar extends StatelessWidget { elevation: 3, child: CircleAvatar( maxRadius: imageSize / 2, - backgroundImage: RemoteImageProvider( - url: getFaceThumbnailUrl(person.id, updatedAt: person.updatedAt), - ), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), ), ), ), @@ -135,7 +131,7 @@ class _Avatar extends StatelessWidget { GestureDetector( onTap: () => onNameTap?.call(), child: Text( - context.t.add_a_name, + "add_a_name".t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), maxLines: 2, overflow: TextOverflow.ellipsis, diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart index f07fb5c112..fb3a9dd8a8 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/rating_details.widget.dart @@ -4,7 +4,7 @@ import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/rating_bar.widget.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; @@ -20,9 +20,7 @@ class RatingDetails extends ConsumerWidget { .watch(userMetadataPreferencesProvider) .maybeWhen(data: (prefs) => prefs?.ratingsEnabled ?? false, orElse: () => false); - if (!isRatingEnabled) { - return const SizedBox.shrink(); - } + if (!isRatingEnabled) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.only(left: 16.0, top: 16.0), @@ -32,7 +30,7 @@ class RatingDetails extends ConsumerWidget { spacing: 8, children: [ Text( - context.t.rating, + 'rating'.t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), RatingBar( @@ -41,10 +39,10 @@ class RatingDetails extends ConsumerWidget { unfilledColor: context.themeData.colorScheme.onSurface.withAlpha(100), itemSize: 40, onRatingUpdate: (rating) async { - await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating); + await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, rating.round()); }, onClearRating: () async { - await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, null); + await ref.read(actionProvider.notifier).updateRating(ActionSource.viewer, 0); }, ), ], diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart index e36cd9967b..52d00828f1 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_details/technical_details.widget.dart @@ -4,7 +4,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; @@ -22,12 +22,11 @@ class TechnicalDetails extends ConsumerWidget { final exifInfo = this.exifInfo; final cameraTitle = _getCameraInfoTitle(exifInfo); final lensTitle = exifInfo?.lens != null && exifInfo!.lens!.isNotEmpty ? exifInfo.lens : null; - final lensSubtitle = _getLensInfoSubtitle(exifInfo); return Column( children: [ SheetTile( - title: context.t.details, + title: 'details'.t(context: context), titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), _buildFileInfoTile(context, ref, asset, exifInfo), @@ -47,16 +46,9 @@ class TechnicalDetails extends ConsumerWidget { title: lensTitle, titleStyle: context.textTheme.labelLarge, leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), - subtitle: lensSubtitle, + subtitle: _getLensInfoSubtitle(exifInfo), subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), - ] else if (lensSubtitle != null) ...[ - const SizedBox(height: 16), - SheetTile( - title: lensSubtitle, - titleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), - leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), - ), ], ], ); @@ -72,7 +64,7 @@ class TechnicalDetails extends ConsumerWidget { final subtitleStyle = context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary); if (asset is LocalAsset) { - final assetMediaRepository = ref.read(assetMediaRepositoryProvider); + final assetMediaRepository = ref.watch(assetMediaRepositoryProvider); return FutureBuilder( future: assetMediaRepository.getOriginalFilename(asset.id), builder: (context, snapshot) { @@ -99,47 +91,38 @@ class TechnicalDetails extends ConsumerWidget { static String _getFileInfo(BaseAsset asset, ExifInfo? exifInfo) { final height = asset.height; final width = asset.width; - final resolution = (width != null && height != null) ? "$width x $height" : null; + final resolution = (width != null && height != null) ? "${width.toInt()} x ${height.toInt()}" : null; final fileSize = exifInfo?.fileSize != null ? formatBytes(exifInfo!.fileSize!) : null; return switch ((fileSize, resolution)) { (null, null) => '', - (final String fileSize, null) => fileSize, - (null, final String resolution) => resolution, - (final String fileSize, final String resolution) => '$fileSize$_kSeparator$resolution', + (String fileSize, null) => fileSize, + (null, String resolution) => resolution, + (String fileSize, String resolution) => '$fileSize$_kSeparator$resolution', }; } static String? _getCameraInfoTitle(ExifInfo? exifInfo) { - if (exifInfo == null) { - return null; - } + if (exifInfo == null) return null; return switch ((exifInfo.make, exifInfo.model)) { (null, null) => null, - (final String make, null) => make, - (null, final String model) => model, - (final String make, final String model) => '$make $model', + (String make, null) => make, + (null, String model) => model, + (String make, String model) => '$make $model', }; } static String? _getCameraInfoSubtitle(ExifInfo? exifInfo) { - if (exifInfo == null) { - return null; - } + if (exifInfo == null) return null; final exposureTime = exifInfo.exposureTime.isNotEmpty ? exifInfo.exposureTime : null; final iso = exifInfo.iso != null ? 'ISO ${exifInfo.iso}' : null; return [exposureTime, iso].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); } static String? _getLensInfoSubtitle(ExifInfo? exifInfo) { - if (exifInfo == null) { - return null; - } + if (exifInfo == null) return null; final fNumber = exifInfo.fNumber.isNotEmpty ? 'ƒ/${exifInfo.fNumber}' : null; final focalLength = exifInfo.focalLength.isNotEmpty ? '${exifInfo.focalLength} mm' : null; - if (fNumber == null && focalLength == null) { - return null; - } return [fNumber, focalLength].where((spec) => spec != null && spec.isNotEmpty).join(_kSeparator); } } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart index 9c17d9a035..0934536471 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_page.widget.dart @@ -7,22 +7,20 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/scroll_extensions.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_overlay.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_file_path.provider.dart'; import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; @@ -58,17 +56,12 @@ class _AssetPageState extends ConsumerState { _DragIntent _dragIntent = _DragIntent.none; Drag? _drag; - BaseAsset? _asset; - @override void initState() { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); - _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || !_scrollController.hasClients) { - return; - } + if (!mounted || !_scrollController.hasClients) return; _scrollController.snapPosition.snapOffset = _snapOffset; if (_showingDetails && _snapOffset > 0) { _scrollController.jumpTo(_snapOffset); @@ -76,19 +69,11 @@ class _AssetPageState extends ConsumerState { }); } - @override - void didUpdateWidget(AssetPage oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.index != widget.index) { - _asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); - } - } - @override void dispose() { _scrollController.dispose(); - unawaited(_scaleBoundarySub?.cancel()); - unawaited(_eventSubscription?.cancel()); + _scaleBoundarySub?.cancel(); + _eventSubscription?.cancel(); super.dispose(); } @@ -96,23 +81,14 @@ class _AssetPageState extends ConsumerState { switch (event) { case ViewerShowDetailsEvent(): _showDetails(); - case TimelineReloadEvent(): - final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); - if (asset != _asset) { - _isZoomed = false; - _viewer.setZoomed(false); - setState(() => _asset = asset); - } default: } } void _showDetails() { - if (!_scrollController.hasClients || _snapOffset <= 0) { - return; - } + if (!_scrollController.hasClients || _snapOffset <= 0) return; _viewer.setShowingDetails(true); - unawaited(_scrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic)); + _scrollController.animateTo(_snapOffset, duration: Durations.medium2, curve: Curves.easeOutCubic); } bool _willClose(double scrollVelocity) => @@ -151,9 +127,7 @@ class _AssetPageState extends ConsumerState { } void _updateDrag(DragUpdateDetails details) { - if (_dragStart == null) { - return; - } + if (_dragStart == null) return; if (_dragIntent == _DragIntent.none) { _dragIntent = switch ((details.globalPosition - _dragStart!.globalPosition).dy) { @@ -166,9 +140,7 @@ class _AssetPageState extends ConsumerState { switch (_dragIntent) { case _DragIntent.none: case _DragIntent.scroll: - if (_drag == null) { - _startProxyDrag(); - } + if (_drag == null) _startProxyDrag(); _drag?.update(details); _syncShowingDetails(); @@ -178,9 +150,7 @@ class _AssetPageState extends ConsumerState { } void _endDrag(DragEndDetails details) { - if (_dragStart == null) { - return; - } + if (_dragStart == null) return; final start = _dragStart; _dragStart = null; @@ -199,7 +169,7 @@ class _AssetPageState extends ConsumerState { case _DragIntent.dismiss: const popThreshold = 75.0; if (details.localPosition.dy - start!.localPosition.dy > popThreshold) { - unawaited(context.maybePop()); + context.maybePop(); return; } _viewController?.animateMultiple( @@ -217,9 +187,7 @@ class _AssetPageState extends ConsumerState { PhotoViewControllerBase controller, PhotoViewScaleStateController scaleStateController, ) { - if (!_showingDetails && _isZoomed) { - return; - } + if (!_showingDetails && _isZoomed) return; _beginDrag(details); } @@ -246,11 +214,9 @@ class _AssetPageState extends ConsumerState { } void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) { - if (_showingDetails || _dragStart != null) { - return; - } + if (_showingDetails || _dragStart != null) return; - final tapToNavigate = ref.read(appConfigProvider).viewer.tapToNavigate; + final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.tapToNavigate); if (!tapToNavigate) { _viewer.toggleControls(); return; @@ -280,43 +246,31 @@ class _AssetPageState extends ConsumerState { _viewer.setZoomed(_isZoomed); if (scaleState != PhotoViewScaleState.initial) { - if (_dragStart == null) { - _viewer.setControls(false); - } + if (_dragStart == null) _viewer.setControls(false); return; } - if (!_showingDetails) { - _viewer.setControls(true); - } + if (!_showingDetails) _viewer.setControls(true); } void _listenForScaleBoundaries(PhotoViewControllerBase? controller) { - unawaited(_scaleBoundarySub?.cancel()); + _scaleBoundarySub?.cancel(); _scaleBoundarySub = null; - if (controller == null || controller.scaleBoundaries != null) { - return; - } + if (controller == null || controller.scaleBoundaries != null) return; _scaleBoundarySub = controller.outputStateStream.listen((_) { if (controller.scaleBoundaries != null) { - unawaited(_scaleBoundarySub?.cancel()); + _scaleBoundarySub?.cancel(); _scaleBoundarySub = null; - if (mounted) { - setState(() {}); - } + if (mounted) setState(() {}); } }); } double _getImageHeight(double maxWidth, double maxHeight, BaseAsset? asset) { final sb = _viewController?.scaleBoundaries; - if (sb != null) { - return sb.childSize.height * sb.initialScale; - } + if (sb != null) return sb.childSize.height * sb.initialScale; - if (asset == null || asset.width == null || asset.height == null) { - return maxHeight; - } + if (asset == null || asset.width == null || asset.height == null) return maxHeight; final r = asset.width! / asset.height!; return math.min(maxWidth / r, maxHeight); @@ -332,22 +286,14 @@ class _AssetPageState extends ConsumerState { required PhotoViewHeroAttributes? heroAttributes, required bool isCurrent, required bool isPlayingMotionVideo, - required String? localFilePath, - required Size? remoteThumbnailSize, }) { final size = context.sizeData; - final imageProvider = getFullImageProvider( - asset, - size: size, - localFilePath: localFilePath, - remoteThumbnailSize: remoteThumbnailSize, - ); if (asset.isImage && !isPlayingMotionVideo) { return PhotoView( key: Key(asset.heroTag), index: widget.index, - imageProvider: imageProvider, + imageProvider: getFullImageProvider(asset, size: size), heroAttributes: heroAttributes, loadingBuilder: (context, progress, index) => const Center(child: ImmichLoadingIndicator()), gaplessPlayback: true, @@ -363,7 +309,7 @@ class _AssetPageState extends ConsumerState { onDragCancel: _onDragCancel, onTapUp: _onTapUp, onLongPressStart: asset.isMotionPhoto ? _onLongPress : null, - errorBuilder: (_, _, _) => SizedBox( + errorBuilder: (_, __, ___) => SizedBox( width: size.width, height: size.height, child: Thumbnail.fromAsset(asset: asset, fit: BoxFit.contain), @@ -394,38 +340,35 @@ class _AssetPageState extends ConsumerState { child: NativeVideoViewer( key: _NativeVideoViewerKey(asset.heroTag), asset: asset, - localFilePath: localFilePath, isCurrent: isCurrent, - image: Image(image: imageProvider, fit: BoxFit.contain, alignment: Alignment.center), + image: Image( + image: getFullImageProvider(asset, size: size), + fit: BoxFit.contain, + alignment: Alignment.center, + ), ), ); } @override Widget build(BuildContext context) { - final (currentAsset, thumbnailSize) = ref.watch( - assetViewerProvider.select((s) => (s.currentAsset, s.thumbnailSize)), - ); + final currentHeroTag = ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag)); _showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex)); final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); - final timelineOrigin = ref.watch(timelineServiceProvider).origin; - final showingOcr = ref.watch(assetViewerProvider.select((s) => s.showingOcr)); - final asset = _asset; + final asset = ref.read(timelineServiceProvider).getAssetSafe(widget.index); if (asset == null) { return const Center(child: ImmichLoadingIndicator()); } BaseAsset displayAsset = asset; - final showAssetStack = ref.watch(timelineServiceProvider.select((s) => s.origin != TimelineOrigin.trash)); - final stackChildren = showAssetStack ? ref.watch(stackChildrenNotifier(asset)).valueOrNull : null; + final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull; if (stackChildren != null && stackChildren.isNotEmpty) { - final safeStackIndex = stackIndex.clamp(0, stackChildren.length - 1); - displayAsset = stackChildren.elementAt(safeStackIndex); + displayAsset = stackChildren.elementAt(stackIndex); } - final isCurrent = currentAsset != null && currentAsset.refersToSameAsset(displayAsset); + final isCurrent = currentHeroTag == displayAsset.heroTag; final viewportWidth = MediaQuery.widthOf(context); final viewportHeight = MediaQuery.heightOf(context); @@ -440,8 +383,6 @@ class _AssetPageState extends ConsumerState { _scrollController.snapPosition.snapOffset = _snapOffset; } - final viewIntentFilePath = timelineOrigin == TimelineOrigin.deepLink ? ref.watch(viewIntentFilePathProvider) : null; - return Stack( children: [ SingleChildScrollView( @@ -461,19 +402,8 @@ class _AssetPageState extends ConsumerState { : null, isCurrent: isCurrent, isPlayingMotionVideo: isPlayingMotionVideo, - localFilePath: viewIntentFilePath, - remoteThumbnailSize: thumbnailSize, ), ), - if (showingOcr && displayAsset.width != null && displayAsset.height != null) - Positioned.fill( - child: OcrOverlay( - asset: displayAsset, - imageSize: Size(displayAsset.width!.toDouble(), displayAsset.height!.toDouble()), - viewportSize: Size(viewportWidth, viewportHeight), - controller: _viewController, - ), - ), IgnorePointer( ignoring: !_showingDetails, child: Column( diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart index 8c153e9995..ca7498a37f 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_preloader.dart @@ -17,31 +17,25 @@ class AssetPreloader { AssetPreloader({required this.timelineService, required this.mounted}); - /// Preloads adjacent images with the current thumbnail size. - void preload(int index, Size size, {Size? thumbnailSize}) { + void preload(int index, Size size) { unawaited(timelineService.preloadAssets(index)); _timer?.cancel(); _timer = Timer(Durations.medium4, () async { - if (!mounted()) { - return; - } + if (!mounted()) return; final (prev, next) = await ( timelineService.getAssetAsync(index - 1), timelineService.getAssetAsync(index + 1), ).wait; - if (!mounted()) { - return; - } + if (!mounted()) return; _prevStream?.removeListener(_dummyListener); _nextStream?.removeListener(_dummyListener); - _prevStream = prev != null ? _resolveImage(prev, size, thumbnailSize) : null; - _nextStream = next != null ? _resolveImage(next, size, thumbnailSize) : null; + _prevStream = prev != null ? _resolveImage(prev, size) : null; + _nextStream = next != null ? _resolveImage(next, size) : null; }); } - ImageStream _resolveImage(BaseAsset asset, Size size, Size? thumbnailSize) { - return getFullImageProvider(asset, size: size, remoteThumbnailSize: thumbnailSize).resolve(ImageConfiguration.empty) - ..addListener(_dummyListener); + ImageStream _resolveImage(BaseAsset asset, Size size) { + return getFullImageProvider(asset, size: size).resolve(ImageConfiguration.empty)..addListener(_dummyListener); } void dispose() { diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.provider.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.provider.dart index c4e06e5ad9..dae6db568c 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.provider.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.provider.dart @@ -11,10 +11,6 @@ class StackChildrenNotifier extends AutoDisposeFamilyAsyncNotifier stack) { - state = AsyncData(stack); - } } final stackChildrenNotifier = AsyncNotifierProvider.autoDispose diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart index 77832c2685..213dc92ef3 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_stack.widget.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; class AssetStackRow extends ConsumerWidget { final List stack; @@ -17,19 +15,8 @@ class AssetStackRow extends ConsumerWidget { return const SizedBox.shrink(); } - final hideAssetStack = ref.watch(timelineServiceProvider).origin == TimelineOrigin.trash; - if (hideAssetStack) { - return const SizedBox.shrink(); - } - - final showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); - if (showingDetails) { - return const SizedBox.shrink(); - } - final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); - final double opacity = - ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); + double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); return IgnorePointer( ignoring: opacity < 1.0, @@ -81,7 +68,7 @@ class _StackItemState extends ConsumerState<_StackItem> { Icons.play_circle_outline_rounded, color: Colors.white, size: 16, - shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset.zero)], + shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))], ), ); const selectedDecoration = BoxDecoration( diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index ad330309ab..3308ae8295 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -1,7 +1,7 @@ import 'dart:async'; -import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -13,18 +13,16 @@ import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/scroll_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/download_status_floating_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_page.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_preloader.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/utils/system_ui.utils.dart'; import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; @RoutePage() @@ -65,26 +63,21 @@ class AssetViewer extends ConsumerStatefulWidget { @override ConsumerState createState() => _AssetViewerState(); - /// Sets the asset and thumbnail size before opening the viewer. - static void setAsset(WidgetRef ref, BaseAsset asset, {Size? thumbnailSize}) { + static void setAsset(WidgetRef ref, BaseAsset asset) { ref.read(assetViewerProvider.notifier).reset(); // Hide controls by default for videos - if (asset.isVideo) { - ref.read(assetViewerProvider.notifier).setControls(false); - } + if (asset.isVideo) ref.read(assetViewerProvider.notifier).setControls(false); - ref.read(assetViewerProvider.notifier).setAsset(asset, thumbnailSize: thumbnailSize); + _setAsset(ref, asset); + } + + static void _setAsset(WidgetRef ref, BaseAsset asset) { + ref.read(assetViewerProvider.notifier).setAsset(asset); } } class _AssetViewerState extends ConsumerState { - static const _viewerOverlayStyle = SystemUiOverlayStyle( - statusBarIconBrightness: Brightness.light, - statusBarBrightness: Brightness.dark, - systemNavigationBarIconBrightness: Brightness.light, - ); - late final _heroOffset = widget.heroOffset ?? TabsRouterScope.of(context)?.controller.activeIndex ?? 0; late final _pageController = PageController(initialPage: widget.initialIndex); late final _preloader = AssetPreloader(timelineService: ref.read(timelineServiceProvider), mounted: () => mounted); @@ -97,14 +90,12 @@ class _AssetViewerState extends ConsumerState { void _onTapNavigate(int direction) { final page = _pageController.page?.toInt(); - if (page == null) { - return; - } + if (page == null) return; final target = page + direction; final maxPage = _totalAssets - 1; if (target >= 0 && target <= maxPage) { _pageController.jumpToPage(target); - unawaited(_onAssetChanged(target)); + _onAssetChanged(target); } } @@ -114,26 +105,24 @@ class _AssetViewerState extends ConsumerState { final asset = ref.read(assetViewerProvider).currentAsset; assert(asset != null, "Current asset should not be null when opening the AssetViewer"); - if (asset != null) { - _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive(); - } + if (asset != null) _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive(); _reloadSubscription = EventStream.shared.listen(_onEvent); WidgetsBinding.instance.addPostFrameCallback(_onAssetInit); final assetViewer = ref.read(assetViewerProvider); - unawaited(_setSystemUIMode(assetViewer.showingControls, assetViewer.showingDetails)); + _setSystemUIMode(assetViewer.showingControls, assetViewer.showingDetails); } @override void dispose() { _pageController.dispose(); _preloader.dispose(); - unawaited(_reloadSubscription?.cancel()); + _reloadSubscription?.cancel(); _stackChildrenKeepAlive?.close(); - unawaited(restoreEdgeToEdge()); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); super.dispose(); } @@ -148,55 +137,37 @@ class _AssetViewerState extends ConsumerState { // playing, and preventing the video on the next page from becoming ready // unnecessarily. bool _onScrollEnd(ScrollEndNotification notification) { - if (notification.depth != 0) { - return false; - } + if (notification.depth != 0) return false; final page = _pageController.page?.round(); if (page != null && page != _currentPage) { - unawaited(_onAssetChanged(page)); + _onAssetChanged(page); } return false; } void _onAssetInit(Duration timeStamp) { - _preloader.preload( - widget.initialIndex, - context.sizeData, - thumbnailSize: ref.read(assetViewerProvider).thumbnailSize, - ); + _preloader.preload(widget.initialIndex, context.sizeData); _handleCasting(); } - Future _onAssetChanged(int index) async { + void _onAssetChanged(int index) async { _currentPage = index; final asset = await ref.read(timelineServiceProvider).getAssetAsync(index); - if (asset == null) { - return; - } + if (asset == null) return; - // The viewer is closing; don't flip the current asset now. Flipping it swaps - // the grid tile hero keys mid pop and animates the close on two tiles (#23779). - if (!mounted || !(ModalRoute.of(context)?.isActive ?? true)) { - return; - } - - ref.read(assetViewerProvider.notifier).setAsset(asset); - _preloader.preload(index, context.sizeData, thumbnailSize: ref.read(assetViewerProvider).thumbnailSize); + AssetViewer._setAsset(ref, asset); + _preloader.preload(index, context.sizeData); _handleCasting(); _stackChildrenKeepAlive?.close(); _stackChildrenKeepAlive = ref.read(stackChildrenNotifier(asset).notifier).ref.keepAlive(); } void _handleCasting() { - if (!ref.read(castProvider).isCasting) { - return; - } + if (!ref.read(castProvider).isCasting) return; final asset = ref.read(assetViewerProvider).currentAsset; - if (asset == null) { - return; - } + if (asset == null) return; if (asset is RemoteAsset) { context.scaffoldMessenger.hideCurrentSnackBar(); @@ -210,7 +181,7 @@ class _AssetViewerState extends ConsumerState { SnackBar( duration: const Duration(seconds: 2), content: Text( - context.t.local_asset_cast_failed, + "local_asset_cast_failed".tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), ), @@ -223,48 +194,17 @@ class _AssetViewerState extends ConsumerState { _onTimelineReloadEvent(); case ViewerReloadAssetEvent(): _onViewerReloadEvent(); - case final ViewerStackAssetDeletedEvent event: - unawaited(_onViewerStackAssetDeletedEvent(event)); default: } } void _onViewerReloadEvent() { - if (_totalAssets <= 1) { - return; - } + if (_totalAssets <= 1) return; final index = _pageController.page?.round() ?? 0; final target = index >= _totalAssets - 1 ? index - 1 : index + 1; - unawaited(_pageController.animateToPage(target, duration: Durations.medium1, curve: Curves.easeInOut)); - unawaited(_onAssetChanged(target)); - } - - Future _onViewerStackAssetDeletedEvent(ViewerStackAssetDeletedEvent event) async { - final timelineAsset = ref.read(timelineServiceProvider).getAssetSafe(_currentPage); - if (timelineAsset == null) { - _onViewerReloadEvent(); - return; - } - - final stackProvider = stackChildrenNotifier(timelineAsset); - - ref.invalidate(stackProvider); - final stack = await ref.read(stackProvider.future); - - if (!mounted) { - return; - } - - if (stack.isEmpty) { - _onViewerReloadEvent(); - return; - } - - final targetIndex = math.min(event.stackIndex, stack.length - 1); - ref.read(assetViewerProvider.notifier) - ..setAsset(stack[targetIndex]) - ..setStackIndex(targetIndex); + _pageController.animateToPage(target, duration: Durations.medium1, curve: Curves.easeInOut); + _onAssetChanged(target); } void _onTimelineReloadEvent() { @@ -272,7 +212,7 @@ class _AssetViewerState extends ConsumerState { final totalAssets = timelineService.totalAssets; if (totalAssets == 0) { - unawaited(context.maybePop()); + context.maybePop(); return; } @@ -282,14 +222,9 @@ class _AssetViewerState extends ConsumerState { if (index != _currentPage) { _pageController.jumpToPage(index); - unawaited(_onAssetChanged(index)); - } else if (currentAsset is RemoteAsset && currentAsset.stackId != null && assetIndex == null) { - final timelineAsset = timelineService.getAssetSafe(index); - if (timelineAsset is! RemoteAsset || currentAsset.stackId != timelineAsset.stackId) { - unawaited(_onAssetChanged(index)); - } + _onAssetChanged(index); } else if (currentAsset != null && assetIndex == null) { - unawaited(_onAssetChanged(index)); + _onAssetChanged(index); } if (_totalAssets != totalAssets) { @@ -299,9 +234,11 @@ class _AssetViewerState extends ConsumerState { } } - Future _setSystemUIMode(bool controls, bool details) { - final immersive = !controls || (CurrentPlatform.isIOS && details); - return immersive ? SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky) : restoreEdgeToEdge(); + void _setSystemUIMode(bool controls, bool details) { + final mode = !controls || (CurrentPlatform.isIOS && details) + ? SystemUiMode.immersiveSticky + : SystemUiMode.edgeToEdge; + unawaited(SystemChrome.setEnabledSystemUIMode(mode)); } @override @@ -315,9 +252,7 @@ class _AssetViewerState extends ConsumerState { // Listen for casting changes and send initial asset to the cast provider ref.listen(castProvider.select((value) => value.isCasting), (_, isCasting) { - if (!isCasting) { - return; - } + if (!isCasting) return; WidgetsBinding.instance.addPostFrameCallback((_) { _handleCasting(); }); @@ -325,55 +260,52 @@ class _AssetViewerState extends ConsumerState { ref.listen(assetViewerProvider.select((value) => (value.showingControls, value.showingDetails)), (_, state) { final (controls, details) = state; - unawaited(_setSystemUIMode(controls, details)); + _setSystemUIMode(controls, details); }); - return AnnotatedRegion( - value: _viewerOverlayStyle, - child: Scaffold( - backgroundColor: backgroundColor, - resizeToAvoidBottomInset: false, - appBar: const ViewerTopAppBar(), - extendBody: true, - extendBodyBehindAppBar: true, - floatingActionButton: IgnorePointer( - ignoring: !showingControls, - child: AnimatedOpacity( - opacity: showingControls ? 1.0 : 0.0, - duration: Durations.short2, - child: const DownloadStatusFloatingButton(), - ), + return Scaffold( + backgroundColor: backgroundColor, + resizeToAvoidBottomInset: false, + appBar: const ViewerTopAppBar(), + extendBody: true, + extendBodyBehindAppBar: true, + floatingActionButton: IgnorePointer( + ignoring: !showingControls, + child: AnimatedOpacity( + opacity: showingControls ? 1.0 : 0.0, + duration: Durations.short2, + child: const DownloadStatusFloatingButton(), ), - bottomNavigationBar: const ViewerBottomAppBar(), - body: Stack( - children: [ - NotificationListener( - onNotification: _onScrollEnd, - child: PhotoViewGestureDetectorScope( - axis: Axis.horizontal, - child: PageView.builder( - controller: _pageController, - physics: isZoomed - ? const NeverScrollableScrollPhysics() - : CurrentPlatform.isIOS - ? const FastScrollPhysics() - : const FastClampingScrollPhysics(), - itemCount: _totalAssets, - itemBuilder: (context, index) => - AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate), - ), + ), + bottomNavigationBar: const ViewerBottomAppBar(), + body: Stack( + children: [ + NotificationListener( + onNotification: _onScrollEnd, + child: PhotoViewGestureDetectorScope( + axis: Axis.horizontal, + child: PageView.builder( + controller: _pageController, + physics: isZoomed + ? const NeverScrollableScrollPhysics() + : CurrentPlatform.isIOS + ? const FastScrollPhysics() + : const FastClampingScrollPhysics(), + itemCount: _totalAssets, + itemBuilder: (context, index) => + AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate), ), ), - if (!CurrentPlatform.isIOS) - IgnorePointer( - child: AnimatedContainer( - duration: Durations.short2, - color: Colors.black.withValues(alpha: showingDetails ? 0.6 : 0.0), - height: context.padding.top, - ), + ), + if (!CurrentPlatform.isIOS) + IgnorePointer( + child: AnimatedContainer( + duration: Durations.short2, + color: Colors.black.withValues(alpha: showingDetails ? 0.6 : 0.0), + height: context.padding.top, ), - ], - ), + ), + ], ), ); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart index d6bf888e09..b51960bb05 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/bottom_bar.widget.dart @@ -1,31 +1,19 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_asset.action.dart'; -import 'package:immich_mobile/presentation/actions/restore.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/upload.action.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_image_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/add_action_button.widget.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart'; -import 'package:immich_ui/immich_ui.dart'; - -// Resolved here rather than as an ActionColumnButton so a hidden restore -// takes no slot in the spaceEvenly row below. -List _actionColumnButtons(BuildContext context, WidgetRef ref, List actions) => actions - .map((a) => a.create(context, ref)) - .nonNulls - .map( - (item) => .new(icon: item.icon, label: item.label, onPressed: item.onAction, onLongPress: item.onSecondaryAction), - ) - .toList(growable: false); class ViewerBottomBar extends ConsumerWidget { const ViewerBottomBar({super.key}); @@ -38,24 +26,26 @@ class ViewerBottomBar extends ConsumerWidget { } final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); + final user = ref.watch(currentUserProvider); + final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; final showingDetails = ref.watch(assetViewerProvider.select((s) => s.showingDetails)); final isInLockedView = ref.watch(inLockedViewProvider); - final isInTrash = ref.watch(timelineServiceProvider).origin == TimelineOrigin.trash; final originalTheme = context.themeData; final actions = [ - ..._actionColumnButtons(context, ref, const [RestoreAction(source: .viewer), ShareAction(source: .viewer)]), + const ShareActionButton(source: ActionSource.viewer), if (!isInLockedView) ...[ - if (!isInTrash) ...[ - ..._actionColumnButtons(context, ref, const [ - UploadAction(source: .viewer, showProgress: true), - EditAssetAction(source: .viewer), - ]), - if (asset.hasRemote) ImmichColorOverride(color: null, child: AddActionButton(originalTheme: originalTheme)), + if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer), + if (asset.type == AssetType.image) const EditImageActionButton(), + if (asset.hasRemote) AddActionButton(originalTheme: originalTheme), + + if (isOwner) ...[ + asset.isLocalOnly + ? const DeleteLocalActionButton(source: ActionSource.viewer) + : const DeleteActionButton(source: ActionSource.viewer, showConfirmation: true), ], - ..._actionColumnButtons(context, ref, const [DeleteAction(source: .viewer)]), ], ]; @@ -65,46 +55,31 @@ class ViewerBottomBar extends ConsumerWidget { ? const SizedBox.shrink() : Theme( data: context.themeData.copyWith( - iconTheme: const IconThemeData(size: ImmichIconSize.md, color: Colors.white), + iconTheme: const IconThemeData(size: 22, color: Colors.white), textTheme: context.themeData.textTheme.copyWith( labelLarge: context.themeData.textTheme.labelLarge?.copyWith(color: Colors.white), ), ), - child: Stack( - children: [ - const Positioned.fill( - child: IgnorePointer( - child: DecoratedBox( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [Colors.black45, Colors.black12, Colors.transparent], - stops: [0.0, 0.7, 1.0], - ), - ), - ), - ), + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [Colors.black45, Colors.black12, Colors.transparent], + stops: [0.0, 0.7, 1.0], ), - SafeArea( - top: false, - child: Padding( - padding: const EdgeInsets.only(top: 16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (asset.isImage) OcrToggleButton(asset: asset), - if (asset.isVideo) VideoControls(videoPlayerName: asset.id), - if (!isReadonlyModeEnabled) - ImmichColorOverride( - color: Colors.white, - child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions), - ), - ], - ), - ), + ), + child: SafeArea( + top: false, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (asset.isVideo) VideoControls(videoPlayerName: asset.heroTag), + if (!isReadonlyModeEnabled) + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: actions), + ], ), - ], + ), ), ), ); diff --git a/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart deleted file mode 100644 index 78f234d39b..0000000000 --- a/mobile/lib/presentation/widgets/asset_viewer/motion_photo_button.widget.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; - -class MotionPhotoPlayButton extends ConsumerWidget { - const MotionPhotoPlayButton({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final asset = ref.watch(assetViewerProvider.select((state) => state.currentAsset)); - final isPlaying = ref.watch(isPlayingMotionVideoProvider); - final showControls = ref.watch(assetViewerProvider.select((state) => state.showingControls)); - final isShowingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails)); - - if (asset == null || !asset.isMotionPhoto || isShowingDetails) { - return const SizedBox.shrink(); - } - - return IgnorePointer( - ignoring: !showControls, - child: AnimatedOpacity( - opacity: showControls ? 1.0 : 0.0, - duration: Durations.short2, - child: SafeArea( - child: Padding( - padding: const EdgeInsets.only(top: 60), - child: Center( - child: _MotionButton( - isPlaying: isPlaying, - onPressed: ref.watch(isPlayingMotionVideoProvider.notifier).toggle, - ), - ), - ), - ), - ), - ); - } -} - -class _MotionButton extends StatelessWidget { - final bool isPlaying; - final VoidCallback onPressed; - - const _MotionButton({required this.isPlaying, required this.onPressed}); - - @override - Widget build(BuildContext context) { - return Material( - color: Colors.grey[900]!.withValues(alpha: 0.4), - borderRadius: const BorderRadius.all(Radius.circular(24)), - child: InkWell( - onTap: onPressed, - borderRadius: const BorderRadius.all(Radius.circular(24)), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded, - color: Colors.white, - size: 16, - ), - const SizedBox(width: 8), - Text( - CurrentPlatform.isAndroid ? context.t.motion : context.t.live, - style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500), - ), - ], - ), - ), - ), - ); - } -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart deleted file mode 100644 index b86d7298d5..0000000000 --- a/mobile/lib/presentation/widgets/asset_viewer/ocr_overlay.widget.dart +++ /dev/null @@ -1,387 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/ocr.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart'; -import 'package:immich_mobile/widgets/photo_view/photo_view.dart'; - -class OcrOverlay extends ConsumerStatefulWidget { - final BaseAsset asset; - final Size imageSize; - final Size viewportSize; - final PhotoViewControllerBase? controller; - - const OcrOverlay({ - super.key, - required this.asset, - required this.imageSize, - required this.viewportSize, - this.controller, - }); - - @override - ConsumerState createState() => _OcrOverlayState(); -} - -class _OcrOverlayState extends ConsumerState { - int? _selectedBoxIndex; - - // Current transform read from the PhotoView controller. - // Null until the controller has emitted at least one real event or until - // we can seed a reliable value from controller.value on init. - PhotoViewControllerValue? _controllerValue; - StreamSubscription? _controllerSub; - - @override - void initState() { - super.initState(); - _attachController(widget.controller); - } - - @override - void didUpdateWidget(OcrOverlay oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - _detachController(); - _attachController(widget.controller); - } - } - - @override - void dispose() { - _detachController(); - super.dispose(); - } - - void _attachController(PhotoViewControllerBase? controller) { - if (controller == null) { - return; - } - - // Seed with the current value only when scaleBoundaries is already set. - // Before the image finishes loading, PhotoView uses childSize = outerSize - // (viewport) as a placeholder, which sets scale = 1.0. That placeholder - // is wrong for any image that doesn't exactly fill the viewport. - // Once scaleBoundaries is set the value is trustworthy (the image has rendered - // at least one frame and setScaleInvisibly has been called with the real - // initial/zoomed scale). - if (controller.scaleBoundaries != null) { - _controllerValue = controller.value; - } - - _controllerSub = controller.outputStateStream.listen((value) { - if (mounted) { - setState(() => _controllerValue = value); - } - }); - } - - void _detachController() { - unawaited(_controllerSub?.cancel()); - _controllerSub = null; - } - - @override - Widget build(BuildContext context) { - if (widget.asset is! RemoteAsset) { - return const SizedBox.shrink(); - } - - final ocrData = ref.watch(ocrAssetProvider((widget.asset as RemoteAsset).id)); - - return ocrData.when( - data: (data) { - if (data == null || data.isEmpty) { - return const SizedBox.shrink(); - } - return _OcrBoxes( - ocrData: data, - controller: widget.controller, - imageSize: widget.imageSize, - viewportSize: widget.viewportSize, - controllerValue: _controllerValue, - selectedBoxIndex: _selectedBoxIndex, - onSelectionChanged: (index) => setState(() => _selectedBoxIndex = index), - ); - }, - loading: () => const SizedBox.shrink(), - error: (_, _) => const SizedBox.shrink(), - ); - } -} - -class _OcrBoxes extends StatelessWidget { - final List ocrData; - final PhotoViewControllerBase? controller; - final Size imageSize; - final Size viewportSize; - final PhotoViewControllerValue? controllerValue; - final int? selectedBoxIndex; - final ValueChanged onSelectionChanged; - - const _OcrBoxes({ - required this.ocrData, - required this.controller, - required this.imageSize, - required this.viewportSize, - required this.controllerValue, - required this.selectedBoxIndex, - required this.onSelectionChanged, - }); - - @override - Widget build(BuildContext context) { - // Use the actual decoded image size from PhotoView's scaleBoundaries when - // available. The image provider may serve a downscaled preview (e.g. Immich - // serves a ~1440px preview for large originals), so the decoded dimensions - // can differ significantly from the stored asset dimensions. Using the wrong - // size would scale every coordinate by the ratio between the two resolutions. - final resolvedImageSize = controller?.scaleBoundaries?.childSize ?? imageSize; - - final scale = - controllerValue?.scale ?? - math.min(viewportSize.width / resolvedImageSize.width, viewportSize.height / resolvedImageSize.height); - final position = controllerValue?.position ?? Offset.zero; - - final imageWidth = resolvedImageSize.width; - final imageHeight = resolvedImageSize.height; - final viewportWidth = viewportSize.width; - final viewportHeight = viewportSize.height; - - // Image center in viewport space, accounting for pan - final cx = viewportWidth / 2 + position.dx; - final cy = viewportHeight / 2 + position.dy; - - final quads = >[]; - final boxes = []; - - for (final entry in ocrData.asMap().entries) { - final index = entry.key; - final ocr = entry.value; - - // Map normalized image coords (0–1) to viewport space - final x1 = cx + (ocr.x1 - 0.5) * imageWidth * scale; - final y1 = cy + (ocr.y1 - 0.5) * imageHeight * scale; - final x2 = cx + (ocr.x2 - 0.5) * imageWidth * scale; - final y2 = cy + (ocr.y2 - 0.5) * imageHeight * scale; - final x3 = cx + (ocr.x3 - 0.5) * imageWidth * scale; - final y3 = cy + (ocr.y3 - 0.5) * imageHeight * scale; - final x4 = cx + (ocr.x4 - 0.5) * imageWidth * scale; - final y4 = cy + (ocr.y4 - 0.5) * imageHeight * scale; - - // Bounding rectangle for hit testing and Positioned placement - final minX = [x1, x2, x3, x4].reduce((a, b) => a < b ? a : b); - final maxX = [x1, x2, x3, x4].reduce((a, b) => a > b ? a : b); - final minY = [y1, y2, y3, y4].reduce((a, b) => a < b ? a : b); - final maxY = [y1, y2, y3, y4].reduce((a, b) => a > b ? a : b); - - quads.add([Offset(x1, y1), Offset(x2, y2), Offset(x3, y3), Offset(x4, y4)]); - - boxes.add( - _OcrBoxItem( - key: ValueKey(index), - ocr: ocr, - index: index, - isSelected: selectedBoxIndex == index, - points: [ - Offset(x1 - minX, y1 - minY), - Offset(x2 - minX, y2 - minY), - Offset(x3 - minX, y3 - minY), - Offset(x4 - minX, y4 - minY), - ], - left: minX, - top: minY, - width: maxX - minX, - height: maxY - minY, - angle: math.atan2(y2 - y1, x2 - x1), - labelDx: (minX + maxX) / 2 - minX, - labelDy: (minY + maxY) / 2 - minY, - onSelectionChanged: onSelectionChanged, - ), - ); - } - - return GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () => onSelectionChanged(null), - child: ClipRect( - child: Stack( - children: [ - // Fills the viewport so taps outside boxes deselect - SizedBox(width: viewportWidth, height: viewportHeight), - // Dark scrim with the text boxes punched out - Positioned.fill( - child: IgnorePointer( - child: CustomPaint(painter: _OcrScrimPainter(quads: quads)), - ), - ), - ...boxes, - ], - ), - ), - ); - } -} - -class _OcrBoxItem extends StatelessWidget { - final Ocr ocr; - final int index; - final bool isSelected; - final List points; - final double left; - final double top; - final double width; - final double height; - final double angle; - final double labelDx; - final double labelDy; - final ValueChanged onSelectionChanged; - - const _OcrBoxItem({ - super.key, - required this.ocr, - required this.index, - required this.isSelected, - required this.points, - required this.left, - required this.top, - required this.width, - required this.height, - required this.angle, - required this.labelDx, - required this.labelDy, - required this.onSelectionChanged, - }); - - @override - Widget build(BuildContext context) { - return Positioned( - left: left, - top: top, - child: GestureDetector( - onTap: () => onSelectionChanged(isSelected ? null : index), - behavior: HitTestBehavior.translucent, - child: SizedBox( - width: width, - height: height, - child: Stack( - children: [ - CustomPaint( - painter: _OcrBoxPainter( - points: points, - isSelected: isSelected, - colorScheme: context.themeData.colorScheme, - ), - size: Size(width, height), - ), - if (isSelected) - Positioned( - left: labelDx, - top: labelDy, - child: FractionalTranslation( - translation: const Offset(-0.5, -0.5), - child: Transform.rotate( - angle: angle, - alignment: Alignment.center, - child: Container( - margin: const EdgeInsets.all(2), - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), - decoration: BoxDecoration( - color: Colors.grey[800]?.withValues(alpha: 0.4), - borderRadius: const BorderRadius.all(Radius.circular(4)), - ), - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: math.max(50, width), maxHeight: math.max(20, height)), - child: FittedBox( - fit: BoxFit.scaleDown, - child: SelectableText( - ocr.text, - style: TextStyle( - color: Colors.white, - fontSize: math.max(12, height * 0.6), - fontWeight: FontWeight.bold, - ), - textAlign: TextAlign.center, - ), - ), - ), - ), - ), - ), - ), - ], - ), - ), - ), - ); - } -} - -class _OcrScrimPainter extends CustomPainter { - final List> quads; - - const _OcrScrimPainter({required this.quads}); - - @override - void paint(Canvas canvas, Size size) { - // Fill the whole viewport, then subtract each text quad using the even-odd - // rule so the original image shows through the boxes. - final path = Path() - ..fillType = PathFillType.evenOdd - ..addRect(Offset.zero & size); - - for (final quad in quads) { - path - ..moveTo(quad[0].dx, quad[0].dy) - ..lineTo(quad[1].dx, quad[1].dy) - ..lineTo(quad[2].dx, quad[2].dy) - ..lineTo(quad[3].dx, quad[3].dy) - ..close(); - } - - canvas.drawPath(path, Paint()..color = Colors.black54); - } - - @override - bool shouldRepaint(_OcrScrimPainter oldDelegate) => true; -} - -class _OcrBoxPainter extends CustomPainter { - final List points; - final bool isSelected; - final ColorScheme colorScheme; - - const _OcrBoxPainter({required this.points, required this.isSelected, required this.colorScheme}); - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = isSelected ? colorScheme.primary : colorScheme.secondary - ..style = PaintingStyle.stroke - ..strokeWidth = 2.0; - - final fillPaint = Paint() - ..color = isSelected ? colorScheme.primary.withValues(alpha: 0.45) : Colors.transparent - ..style = PaintingStyle.fill; - - final path = Path() - ..moveTo(points[0].dx, points[0].dy) - ..lineTo(points[1].dx, points[1].dy) - ..lineTo(points[2].dx, points[2].dy) - ..lineTo(points[3].dx, points[3].dy) - ..close(); - - canvas.drawPath(path, fillPaint); - canvas.drawPath(path, paint); - } - - @override - bool shouldRepaint(_OcrBoxPainter oldDelegate) { - return oldDelegate.isSelected != isSelected || !listEquals(oldDelegate.points, points); - } -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart deleted file mode 100644 index cdff727967..0000000000 --- a/mobile/lib/presentation/widgets/asset_viewer/ocr_toggle_button.widget.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart'; - -class OcrToggleButton extends ConsumerWidget { - final BaseAsset asset; - const OcrToggleButton({super.key, required this.asset}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final asset = this.asset; - final hasOcr = asset is RemoteAsset && ref.watch(ocrAssetProvider(asset.id)).valueOrNull?.isNotEmpty == true; - final showingOcr = ref.watch(assetViewerProvider.select((s) => s.showingOcr)); - - return AnimatedSwitcher( - duration: Durations.short4, - child: !hasOcr - ? const SizedBox.shrink() - : Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 32, bottom: 8), - child: Material( - color: showingOcr ? context.primaryColor : Colors.black.withValues(alpha: 0.4), - shape: const CircleBorder(), - clipBehavior: Clip.antiAlias, - child: InkWell( - onTap: ref.watch(assetViewerProvider.notifier).toggleOcr, - child: const Padding( - padding: EdgeInsets.all(10.0), - child: Icon(Icons.text_fields_rounded, size: 22, color: Colors.white), - ), - ), - ), - ), - ), - ); - } -} diff --git a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart index fb5cfff255..62a439fe39 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/rating_bar.widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; class RatingBar extends StatefulWidget { final double initialRating; @@ -53,9 +53,7 @@ class _RatingBarState extends State { final totalWidth = widget.itemCount * widget.itemSize + (widget.itemCount - 1) * widget.starPadding; double dx = localPosition.dx; - if (isRTL) { - dx = totalWidth - dx; - } + if (isRTL) dx = totalWidth - dx; double newRating; @@ -64,8 +62,8 @@ class _RatingBarState extends State { } else if (dx >= totalWidth) { newRating = widget.itemCount.toDouble(); } else { - final double starWithPadding = widget.itemSize + widget.starPadding; - final int tappedIndex = (dx / starWithPadding).floor().clamp(0, widget.itemCount - 1); + double starWithPadding = widget.itemSize + widget.starPadding; + int tappedIndex = (dx / starWithPadding).floor().clamp(0, widget.itemCount - 1); newRating = tappedIndex + 1.0; if (isTap && newRating == _currentRating && _currentRating != 0) { @@ -77,18 +75,14 @@ class _RatingBarState extends State { setState(() { _currentRating = newRating; }); - if (newRating == 0) { - widget.onClearRating?.call(); - } else { - widget.onRatingUpdate?.call(newRating.round()); - } + widget.onRatingUpdate?.call(newRating.round()); } } @override Widget build(BuildContext context) { final isRTL = Directionality.of(context) == TextDirection.rtl; - const double visualAlignmentOffset = 5.0; + final double visualAlignmentOffset = 5.0; return Column( mainAxisSize: MainAxisSize.min, @@ -107,8 +101,8 @@ class _RatingBarState extends State { if (i.isOdd) { return SizedBox(width: widget.starPadding); } - final int index = i ~/ 2; - final bool filled = _currentRating > index; + int index = i ~/ 2; + bool filled = _currentRating > index; return widget.itemBuilder ?? Icon( Icons.star_rounded, @@ -129,7 +123,10 @@ class _RatingBarState extends State { }); widget.onClearRating?.call(); }, - child: Text(context.t.rating_clear, style: TextStyle(color: context.themeData.colorScheme.primary)), + child: Text( + 'rating_clear'.t(context: context), + style: TextStyle(color: context.themeData.colorScheme.primary), + ), ), ), ], diff --git a/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart index 8ff0befd7b..2af68e1ff0 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/sheet_tile.widget.dart @@ -1,9 +1,7 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -28,8 +26,12 @@ class SheetTile extends ConsumerWidget { }); void copyTitle(BuildContext context, WidgetRef ref) { - unawaited(Clipboard.setData(ClipboardData(text: title))); - ImmichToast.show(context: context, msg: context.t.copied_to_clipboard, toastType: ToastType.info); + Clipboard.setData(ClipboardData(text: title)); + ImmichToast.show( + context: context, + msg: 'copied_to_clipboard'.t(context: context), + toastType: ToastType.info, + ); ref.read(hapticFeedbackProvider.notifier).selectionClick(); } @@ -61,19 +63,16 @@ class SheetTile extends ConsumerWidget { subtitleWidget = null; } - return Material( - type: MaterialType.transparency, - child: ListTile( - dense: true, - visualDensity: VisualDensity.compact, - title: GestureDetector(onLongPress: () => copyTitle(context, ref), child: titleWidget), - titleAlignment: ListTileTitleAlignment.center, - leading: leading, - trailing: trailing, - contentPadding: leading == null ? null : const EdgeInsets.only(left: 25), - subtitle: subtitleWidget, - onTap: onTap, - ), + return ListTile( + dense: true, + visualDensity: VisualDensity.compact, + title: GestureDetector(onLongPress: () => copyTitle(context, ref), child: titleWidget), + titleAlignment: ListTileTitleAlignment.center, + leading: leading, + trailing: trailing, + contentPadding: leading == null ? null : const EdgeInsets.only(left: 25), + subtitle: subtitleWidget, + onTap: onTap, ); } } diff --git a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart index 9011888b54..9285c01c41 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/video_viewer.widget.dart @@ -1,26 +1,28 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; import 'package:native_video_player/native_video_player.dart'; class NativeVideoViewer extends ConsumerStatefulWidget { final BaseAsset asset; - final String? localFilePath; final bool isCurrent; final bool showControls; final Widget image; @@ -28,7 +30,6 @@ class NativeVideoViewer extends ConsumerStatefulWidget { const NativeVideoViewer({ super.key, required this.asset, - this.localFilePath, required this.image, this.isCurrent = false, this.showControls = true, @@ -47,7 +48,7 @@ class _NativeVideoViewerState extends ConsumerState with Widg bool _isVideoReady = false; bool _shouldPlayOnForeground = true; - VideoPlayerNotifier get _notifier => ref.read(videoPlayerProvider(widget.asset.id).notifier); + VideoPlayerNotifier get _notifier => ref.read(videoPlayerProvider(widget.asset.heroTag).notifier); @override void initState() { @@ -60,13 +61,11 @@ class _NativeVideoViewerState extends ConsumerState with Widg void didUpdateWidget(NativeVideoViewer oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.isCurrent == oldWidget.isCurrent || _controller == null) { - return; - } + if (widget.isCurrent == oldWidget.isCurrent || _controller == null) return; if (!widget.isCurrent) { _loadTimer?.cancel(); - unawaited(_notifier.pause()); + _notifier.pause(); return; } @@ -83,57 +82,28 @@ class _NativeVideoViewerState extends ConsumerState with Widg } @override - Future didChangeAppLifecycleState(AppLifecycleState state) async { + void didChangeAppLifecycleState(AppLifecycleState state) async { switch (state) { case AppLifecycleState.resumed: - if (_shouldPlayOnForeground) { - await _notifier.play(); - } + if (_shouldPlayOnForeground) await _notifier.play(); case AppLifecycleState.paused: _shouldPlayOnForeground = await _controller?.isPlaying() ?? true; - if (_shouldPlayOnForeground && mounted) { - await _notifier.pause(); - } + if (_shouldPlayOnForeground) await _notifier.pause(); default: } } Future _createSource() async { - if (!mounted) { - return null; - } + if (!mounted) return null; final videoAsset = await ref.read(assetServiceProvider).getAsset(widget.asset) ?? widget.asset; - if (!mounted) { - return null; - } + if (!mounted) return null; try { - final localFilePath = widget.localFilePath; - if (localFilePath != null) { - final file = File(localFilePath); - // ignore: avoid_slow_async_io - if (!await file.exists()) { - throw Exception('No file found for the video'); - } - - return VideoSource.init( - path: CurrentPlatform.isAndroid ? file.uri.toString() : file.path, - type: VideoSourceType.file, - ); - } - - // Attempt to retrieve LocalAsset, falling back to remote if it cannot be found - final localAsset = await _localPlaybackAsset(videoAsset); - - if (localAsset != null) { - final file = localAsset.isMotionPhoto - ? await StorageRepository().getMotionFileForAsset(localAsset) - : await StorageRepository().getFileForAsset(localAsset.id); - - if (!mounted) { - return null; - } + if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) { + final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!; + final file = await StorageRepository().getFileForAsset(id); + if (!mounted) return null; if (file == null) { throw Exception('No file found for the video'); @@ -147,17 +117,14 @@ class _NativeVideoViewerState extends ConsumerState with Widg ); } - final remoteAsset = videoAsset as RemoteAsset; + final remoteId = (videoAsset as RemoteAsset).id; final serverEndpoint = Store.get(StoreKey.serverEndpoint); - if (!context.mounted) { - return null; - } - - final isOriginalVideo = ref.read(appConfigProvider).viewer.loadOriginalVideo; + final isOriginalVideo = ref.read(settingsProvider).get(Setting.loadOriginalVideo); final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback'; - final String assetId = remoteAsset.livePhotoVideoId ?? remoteAsset.id; - final String videoUrl = '$serverEndpoint/assets/$assetId/$postfixUrl'; + final String videoUrl = videoAsset.livePhotoVideoId != null + ? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl' + : '$serverEndpoint/assets/$remoteId/$postfixUrl'; return VideoSource.init(path: videoUrl, type: VideoSourceType.network, headers: ApiService.getRequestHeaders()); } catch (error) { @@ -166,73 +133,26 @@ class _NativeVideoViewerState extends ConsumerState with Widg } } - Future _localPlaybackAsset(BaseAsset baseAsset) async { - if (!baseAsset.hasLocal) { - return null; - } - - LocalAsset? localAsset; - - if (baseAsset is LocalAsset) { - localAsset = baseAsset; - } else { - final localId = (baseAsset as RemoteAsset).localId; - localAsset = localId != null ? await ref.read(assetServiceProvider).getLocalAsset(localId) : null; - } - - if (localAsset == null) { - _log.severe( - 'Invariant violation: asset ${baseAsset.name} (${baseAsset.localId}) is marked `hasLocal` but local asset could not be retrieved', - ); - - return null; - } - - // Clients (local) may not correctly recognize a given asset as a motion photo. This allows for a scenario where both remote and local - // have the same asset (hash), but only the remote properly recognizes it as a motion asset - // If this scenario occurs, fall back to using the remote asset - if (baseAsset.isMotionPhoto && !localAsset.isMotionPhoto) { - // Platform mismatch for motion photo, use remote instead - _log.warning( - 'Mismatched local and remote motion states on ${baseAsset.name} (${baseAsset.localId}), local = ${localAsset.isMotionPhoto}, remote = ${baseAsset.isMotionPhoto}', - ); - - return null; - } - - return localAsset; - } - - Future _onPlaybackReady() async { - if (!mounted || !widget.isCurrent) { - return; - } + void _onPlaybackReady() async { + if (!mounted || !widget.isCurrent) return; _notifier.onNativePlaybackReady(); // onPlaybackReady may be called multiple times, usually when more data // loads. If this is not the first time that the player has become ready, we // should not autoplay. - if (_isVideoReady) { - return; - } + if (_isVideoReady) return; setState(() => _isVideoReady = true); - if (ref.read(assetViewerProvider).showingDetails) { - return; - } + if (ref.read(assetViewerProvider).showingDetails) return; - final autoPlayVideo = ref.read(appConfigProvider).viewer.autoPlayVideo; - if (autoPlayVideo || widget.asset.isMotionPhoto) { - await _notifier.play(); - } + final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo); + if (autoPlayVideo) await _notifier.play(); } void _onPlaybackEnded() { - if (!mounted) { - return; - } + if (!mounted) return; _notifier.onNativePlaybackEnded(); @@ -242,16 +162,12 @@ class _NativeVideoViewerState extends ConsumerState with Widg } void _onPlaybackPositionChanged() { - if (!mounted) { - return; - } + if (!mounted) return; _notifier.onNativePositionChanged(); } void _onPlaybackStatusChanged() { - if (!mounted) { - return; - } + if (!mounted) return; _notifier.onNativeStatusChanged(); } @@ -262,30 +178,21 @@ class _NativeVideoViewerState extends ConsumerState with Widg _controller?.onPlaybackEnded.removeListener(_onPlaybackEnded); } - Future _loadVideo() async { + void _loadVideo() async { final nc = _controller; - if (nc == null || nc.videoSource != null || !mounted) { - return; - } + if (nc == null || nc.videoSource != null || !mounted) return; final source = await _videoSource; - if (source == null || !mounted) { - return; - } + if (source == null || !mounted) return; - // Grab refs to prevent reading after dispose - final loopVideo = ref.read(appConfigProvider).viewer.loopVideo; - final localNotifier = _notifier; - - await localNotifier.load(source); - await localNotifier.setLoop(!widget.asset.isMotionPhoto && loopVideo); - await localNotifier.setVolume(1); + await _notifier.load(source); + final loopVideo = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.loopVideo); + await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo); + await _notifier.setVolume(1); } void _initController(NativeVideoPlayerController nc) { - if (_controller != null || !mounted) { - return; - } + if (_controller != null || !mounted) return; _notifier.attachController(nc); @@ -296,20 +203,18 @@ class _NativeVideoViewerState extends ConsumerState with Widg _controller = nc; - if (widget.isCurrent) { - unawaited(_loadVideo()); - } + if (widget.isCurrent) _loadVideo(); } @override Widget build(BuildContext context) { final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); - final status = ref.watch(videoPlayerProvider(widget.asset.id).select((v) => v.status)); + final status = ref.watch(videoPlayerProvider(widget.asset.heroTag).select((v) => v.status)); return IgnorePointer( child: Stack( children: [ - if (!_isVideoReady || widget.asset.isMotionPhoto || isCasting) Center(child: widget.image), + Center(child: widget.image), if (!isCasting) ...[ Visibility.maintain( visible: _isVideoReady, diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart index 0e7b5661de..1c0b600843 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_bottom_app_bar.widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart'; class ViewerBottomAppBar extends ConsumerWidget { const ViewerBottomAppBar({super.key}); @@ -9,8 +9,7 @@ class ViewerBottomAppBar extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); - final double opacity = - ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); + double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); return IgnorePointer( ignoring: opacity < 1.0, diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart index cbbc851108..78b2e50da5 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart @@ -2,9 +2,10 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; @@ -12,7 +13,6 @@ import 'package:immich_mobile/providers/routes.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/utils/action_button.utils.dart'; -import 'package:immich_ui/immich_ui.dart'; class ViewerKebabMenu extends ConsumerWidget { const ViewerKebabMenu({super.key, this.originalTheme}); @@ -29,12 +29,12 @@ class ViewerKebabMenu extends ConsumerWidget { final user = ref.watch(currentUserProvider); final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); - final timelineOrigin = ref.watch(timelineServiceProvider).origin; + final timelineOrigin = ref.read(timelineServiceProvider).origin; final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); final isInLockedView = ref.watch(inLockedViewProvider); final currentAlbum = ref.watch(currentRemoteAlbumProvider); final isArchived = asset is RemoteAsset && asset.visibility == AssetVisibility.archive; - final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(.advancedTroubleshooting); + final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); final actionContext = ActionButtonContext( asset: asset, @@ -48,11 +48,12 @@ class ViewerKebabMenu extends ConsumerWidget { source: ActionSource.viewer, isCasting: isCasting, timelineOrigin: timelineOrigin, + originalTheme: originalTheme, ); - final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context); + final menuChildren = ActionButtonBuilder.buildViewerKebabMenu(actionContext, context, ref); - return ImmichMenu( + return MenuAnchor( consumeOutsideTap: true, style: MenuStyle( backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor), @@ -63,16 +64,13 @@ class ViewerKebabMenu extends ConsumerWidget { ), padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)), ), - children: [ + menuChildren: [ ConstrainedBox( constraints: const BoxConstraints(minWidth: 150), - child: Theme( - data: originalTheme ?? context.themeData, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: menuChildren, - ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: menuChildren, ), ), ], diff --git a/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart b/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart index deae9cbd07..ae7dd85396 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart @@ -1,24 +1,20 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart'; import 'package:immich_mobile/providers/activity.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/utils/timezone.dart'; -import 'package:immich_ui/immich_ui.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { const ViewerTopAppBar({super.key}); @@ -32,18 +28,19 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { final album = ref.watch(currentRemoteAlbumProvider); + final user = ref.watch(currentUserProvider); + final isOwner = asset is RemoteAsset && asset.ownerId == user?.id; final isInLockedView = ref.watch(inLockedViewProvider); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails)); if (album != null && album.isActivityEnabled && album.isShared && asset is RemoteAsset) { - ref.watch(albumActivityProvider((album.id, asset.id))); + ref.watch(albumActivityProvider(album.id, asset.id)); } final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls)); - final double opacity = - ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); + double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0); final originalTheme = context.themeData; @@ -53,21 +50,22 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { IconButton( icon: const Icon(Icons.chat_outlined), onPressed: () { - unawaited( - context.router.push( - DriftActivitiesRoute( - album: album, - assetId: asset is RemoteAsset ? asset.id : null, - assetName: asset.name, - ), + context.router.push( + DriftActivitiesRoute( + album: album, + assetId: asset is RemoteAsset ? asset.id : null, + assetName: asset.name, ), ); }, ), - const ActionIconButton(action: FavoriteAction(source: .viewer)), + if (asset.hasRemote && isOwner && !asset.isFavorite) + const FavoriteActionButton(source: ActionSource.viewer, iconOnly: true), + if (asset.hasRemote && isOwner && asset.isFavorite) + const UnFavoriteActionButton(source: ActionSource.viewer, iconOnly: true), - ImmichColorOverride(color: null, child: ViewerKebabMenu(originalTheme: originalTheme)), + ViewerKebabMenu(originalTheme: originalTheme), ]; final lockedViewActions = [ViewerKebabMenu(originalTheme: originalTheme)]; @@ -77,48 +75,29 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget { child: AnimatedOpacity( opacity: opacity, duration: Durations.short2, - child: Stack( - children: [ - Positioned.fill( - child: IgnorePointer( - child: DecoratedBox( - decoration: BoxDecoration( - gradient: showingDetails - ? null - : const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black45, Colors.black12, Colors.transparent], - stops: [0.0, 0.7, 1.0], - ), + child: DecoratedBox( + decoration: BoxDecoration( + gradient: showingDetails + ? null + : const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black45, Colors.black12, Colors.transparent], + stops: [0.0, 0.7, 1.0], ), - ), - ), - ), - SafeArea( - bottom: false, - child: SizedBox( - height: preferredSize.height, - child: Theme( - data: context.themeData.copyWith(iconTheme: const IconThemeData(size: 22, color: Colors.white)), - child: NavigationToolbar( - centerMiddle: true, - leading: const _AppBarBackButton(), - middle: showingDetails ? null : _AssetInfoTitle(asset: asset), - trailing: !showingDetails && !isReadonlyModeEnabled - ? ImmichColorOverride( - color: Colors.white, - child: Row( - mainAxisSize: MainAxisSize.min, - children: isInLockedView ? lockedViewActions : actions, - ), - ) - : null, - ), - ), - ), - ), - ], + ), + child: AppBar( + backgroundColor: Colors.transparent, + leading: const _AppBarBackButton(), + iconTheme: const IconThemeData(size: 22, color: Colors.white), + actionsIconTheme: const IconThemeData(size: 22, color: Colors.white), + shape: const Border(), + actions: showingDetails || isReadonlyModeEnabled + ? null + : isInLockedView + ? lockedViewActions + : actions, + ), ), ), ); @@ -134,46 +113,20 @@ class _AppBarBackButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails)); - return ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: showingDetails ? context.colorScheme.surface : Colors.transparent, - shape: const CircleBorder(), - iconSize: 22, - iconColor: showingDetails ? context.colorScheme.onSurface : Colors.white, - padding: const EdgeInsets.all(10.0), - elevation: showingDetails ? 4 : 0, + return Padding( + padding: const EdgeInsets.only(left: 12.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: showingDetails ? context.colorScheme.surface : Colors.transparent, + shape: const CircleBorder(), + iconSize: 22, + iconColor: showingDetails ? context.colorScheme.onSurface : Colors.white, + padding: EdgeInsets.zero, + elevation: showingDetails ? 4 : 0, + ), + onPressed: context.maybePop, + child: const Icon(Icons.arrow_back_rounded), ), - onPressed: context.maybePop, - child: const Icon(Icons.arrow_back_rounded), - ); - } -} - -class _AssetInfoTitle extends ConsumerWidget { - final BaseAsset asset; - - const _AssetInfoTitle({required this.asset}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - DateTime dateTime = asset.createdAt.toLocal(); - final currentYear = DateTime.now().year; - final exifInfo = ref.watch(assetExifProvider(asset)).valueOrNull; - - if (exifInfo?.dateTimeOriginal != null) { - (dateTime, _) = applyTimezoneOffset(dateTime: exifInfo!.dateTimeOriginal!, timeZone: exifInfo.timeZone); - } - - final isCurrentYear = dateTime.year == currentYear; - final dateFormatted = isCurrentYear ? DateFormat.MMMd().format(dateTime) : DateFormat.yMMMd().format(dateTime); - final timeFormatted = DateFormat.jm().format(dateTime); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text(dateFormatted, style: context.textTheme.labelLarge?.copyWith(color: Colors.white)), - Text(timeFormatted, style: context.textTheme.labelMedium?.copyWith(color: Colors.white70)), - ], ); } } diff --git a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart index 383ec17952..7c92dc01d8 100644 --- a/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart +++ b/mobile/lib/presentation/widgets/backup/backup_toggle_button.widget.dart @@ -1,9 +1,10 @@ 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/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; class BackupToggleButton extends ConsumerStatefulWidget { final VoidCallback onStart; @@ -30,7 +31,7 @@ class BackupToggleButtonState extends ConsumerState with Sin end: 1, ).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut)); - _isEnabled = ref.read(appConfigProvider).backup.enabled; + _isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); } @override @@ -40,7 +41,7 @@ class BackupToggleButtonState extends ConsumerState with Sin } Future _onToggle(bool value) async { - await ref.read(settingsProvider).write(.backupEnabled, value); + await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value); setState(() { _isEnabled = value; @@ -106,57 +107,65 @@ class BackupToggleButtonState extends ConsumerState with Sin borderRadius: const BorderRadius.all(Radius.circular(18.5)), color: context.colorScheme.surfaceContainerLow, ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - shape: BoxShape.circle, - gradient: LinearGradient( - colors: [ - context.primaryColor.withValues(alpha: 0.2), - context.primaryColor.withValues(alpha: 0.1), - ], + child: Material( + color: context.colorScheme.surfaceContainerLow, + borderRadius: const BorderRadius.all(Radius.circular(20.5)), + child: InkWell( + borderRadius: const BorderRadius.all(Radius.circular(20.5)), + onTap: () => _onToggle(!_isEnabled), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + context.primaryColor.withValues(alpha: 0.2), + context.primaryColor.withValues(alpha: 0.1), + ], + ), + ), + child: isProcessing + ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)) + : Icon(Icons.cloud_upload_outlined, color: context.primaryColor, size: 24), ), - ), - child: isProcessing - ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)) - : Icon(Icons.cloud_upload_outlined, color: context.primaryColor, size: 24), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: Text( - context.t.enable_backup, - style: context.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, - color: context.primaryColor, + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Text( + "enable_backup".t(context: context), + style: context.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: context.primaryColor, + ), + ), + ), + ], + ), + if (errorCount > 0) + Padding( + padding: const EdgeInsets.only(top: 2), + child: Text( + "upload_error_with_count".t(context: context, args: {'count': '$errorCount'}), + style: context.textTheme.labelMedium?.copyWith(color: context.colorScheme.error), ), ), - ), ], ), - if (errorCount > 0) - Padding( - padding: const EdgeInsets.only(top: 2), - child: Text( - context.t.upload_error_with_count(count: errorCount), - style: context.textTheme.labelMedium?.copyWith(color: context.colorScheme.error), - ), - ), - ], - ), + ), + Switch.adaptive(value: _isEnabled, onChanged: (value) => _onToggle(value)), + ], ), - Switch.adaptive(value: _isEnabled, onChanged: (value) => _onToggle(value)), - ], + ), ), ), ), diff --git a/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart index c68ce8d9ca..cdff393a3f 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/archive_bottom_sheet.widget.dart @@ -1,95 +1,52 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; -import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -class ArchiveBottomSheet extends ConsumerStatefulWidget { +class ArchiveBottomSheet extends ConsumerWidget { const ArchiveBottomSheet({super.key}); @override - ConsumerState createState() => _ArchiveBottomSheetState(); -} - -class _ArchiveBottomSheetState extends ConsumerState { - late final DraggableScrollableController sheetController; - - @override - void initState() { - super.initState(); - sheetController = DraggableScrollableController(); - } - - @override - void dispose() { - sheetController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Future addToAlbum(RemoteAlbum album) async { - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); - - if (!context.mounted) { - return; - } - - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; - } - - ImmichToast.show( - context: context, - msg: result.count == 0 - ? context.t.add_to_album_bottom_sheet_already_exists(album: album.name) - : context.t.add_to_album_bottom_sheet_added(album: album.name), - ); - } - - Future onKeyboardExpand() { - return sheetController.animateTo(0.85, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); - } + Widget build(BuildContext context, WidgetRef ref) { + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); return BaseBottomSheet( - controller: sheetController, initialChildSize: 0.25, - maxChildSize: 0.85, + maxChildSize: 0.4, shouldCloseOnMinExtent: false, - actions: const [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: ShareLinkAction(source: .timeline)), - .new(action: ArchiveAction(source: .timeline)), - .new(action: FavoriteAction(source: .timeline)), - .new(action: DownloadAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: EditDateTimeAction(source: .timeline)), - .new(action: EditLocationAction(source: .timeline)), - .new(action: LockAction(source: .timeline)), - .new(action: StackAction(source: .timeline)), - .new(action: CleanupLocalAction(source: .timeline)), - ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + actions: [ + const ShareActionButton(source: ActionSource.timeline), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + const UnArchiveActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton(source: ActionSource.timeline), + const EditDateTimeActionButton(source: ActionSource.timeline), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton(source: ActionSource.timeline), + if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), + ], + if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline), ], ); } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart index a066e0167e..0d0e0259fd 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; @@ -55,12 +53,10 @@ class _BaseDraggableScrollableSheetState extends ConsumerState } if (previous?.isInteracting != true && next.isInteracting) { - unawaited( - _controller.animateTo( - widget.minChildSize, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOut, - ), + _controller.animateTo( + widget.minChildSize, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOut, ); } }); @@ -78,7 +74,7 @@ class _BaseDraggableScrollableSheetState extends ConsumerState color: widget.backgroundColor ?? context.colorScheme.surfaceContainer, elevation: 3.0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(18))), - margin: EdgeInsets.zero, + margin: const EdgeInsets.symmetric(horizontal: 0), child: Column( children: [ Expanded( diff --git a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart index 5ac6869c57..1dee0f6456 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/favorite_bottom_sheet.widget.dart @@ -1,23 +1,26 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -27,6 +30,7 @@ class FavoriteBottomSheet extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); Future addAssetsToAlbum(RemoteAlbum album) async { final selectedAssets = multiselect.selectedAssets; @@ -35,33 +39,26 @@ class FavoriteBottomSheet extends ConsumerWidget { } final remoteAssets = selectedAssets.whereType(); - final result = await ref + final addedCount = await ref .read(remoteAlbumProvider.notifier) .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); - if (!context.mounted) { - return; - } if (selectedAssets.length != remoteAssets.length) { - ImmichToast.show(context: context, msg: context.t.add_to_album_bottom_sheet_some_local_assets); + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context), + ); } - // Only report the failure when nothing was added; if some succeeded we show "added". - if (result.added > 0) { + if (addedCount != remoteAssets.length) { ImmichToast.show( context: context, - msg: context.t.add_to_album_bottom_sheet_added(album: album.name), - ); - } else if (result.failed > 0) { - ImmichToast.show( - context: context, - msg: context.t.assets_cannot_be_added_to_album_count(count: result.failed), - toastType: ToastType.error, + msg: 'add_to_album_bottom_sheet_already_exists'.t(args: {"album": album.name}), ); } else { ImmichToast.show( context: context, - msg: context.t.add_to_album_bottom_sheet_already_exists(album: album.name), + msg: 'add_to_album_bottom_sheet_added'.t(args: {"album": album.name}), ); } @@ -72,19 +69,23 @@ class FavoriteBottomSheet extends ConsumerWidget { initialChildSize: 0.4, maxChildSize: 0.7, shouldCloseOnMinExtent: false, - actions: const [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: ShareLinkAction(source: .timeline)), - .new(action: FavoriteAction(source: .timeline)), - .new(action: ArchiveAction(source: .timeline)), - .new(action: DownloadAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: EditDateTimeAction(source: .timeline)), - .new(action: EditLocationAction(source: .timeline)), - .new(action: LockAction(source: .timeline)), - .new(action: StackAction(source: .timeline)), - .new(action: CleanupLocalAction(source: .timeline)), + actions: [ + const ShareActionButton(source: ActionSource.timeline), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + const UnFavoriteActionButton(source: ActionSource.timeline), + const ArchiveActionButton(source: ActionSource.timeline), + if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton(source: ActionSource.timeline), + const EditDateTimeActionButton(source: ActionSource.timeline), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton(source: ActionSource.timeline), + if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), + ], + if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline), ], slivers: multiselect.hasRemote ? [const AddToAlbumHeader(), AlbumSelector(onAlbumSelected: addAssetsToAlbum)] diff --git a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart index 1fb78703a1..8753a9c14f 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart @@ -1,25 +1,33 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; -import 'package:immich_mobile/presentation/actions/tag.action.dart'; -import 'package:immich_mobile/presentation/actions/upload.action.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; class GeneralBottomSheet extends ConsumerStatefulWidget { @@ -46,23 +54,41 @@ class _GeneralBottomSheetState extends ConsumerState { @override Widget build(BuildContext context) { - Future addToAlbum(RemoteAlbum album) async { - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); + final advancedTroubleshooting = ref.watch(settingsProvider.notifier).get(Setting.advancedTroubleshooting); - if (!context.mounted) { + Future addAssetsToAlbum(RemoteAlbum album) async { + final selectedAssets = multiselect.selectedAssets; + if (selectedAssets.isEmpty) { return; } - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; + final remoteAssets = selectedAssets.whereType(); + final addedCount = await ref + .read(remoteAlbumProvider.notifier) + .addAssets(album.id, remoteAssets.map((e) => e.id).toList()); + + if (selectedAssets.length != remoteAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context), + ); } - ImmichToast.show( - context: context, - msg: result.count == 0 - ? context.t.add_to_album_bottom_sheet_already_exists(album: album.name) - : context.t.add_to_album_bottom_sheet_added(album: album.name), - ); + + if (addedCount != remoteAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}), + ); + } else { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {"album": album.name}), + ); + } + + ref.read(multiSelectProvider.notifier).reset(); } Future onKeyboardExpand() { @@ -75,26 +101,36 @@ class _GeneralBottomSheetState extends ConsumerState { minChildSize: widget.minChildSize, maxChildSize: 0.85, shouldCloseOnMinExtent: false, - actions: const [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: ShareLinkAction(source: .timeline)), - .new(action: DownloadAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: FavoriteAction(source: .timeline)), - .new(action: ArchiveAction(source: .timeline)), - .new(action: TagAction(source: .timeline)), - .new(action: EditDateTimeAction(source: .timeline)), - .new(action: EditLocationAction(source: .timeline)), - .new(action: LockAction(source: .timeline)), - .new(action: StackAction(source: .timeline)), - .new(action: CleanupLocalAction(source: .timeline)), - .new(action: UploadAction(source: .timeline)), - ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + actions: [ + if (multiselect.selectedAssets.length == 1 && advancedTroubleshooting) ...[ + const AdvancedInfoActionButton(source: ActionSource.timeline), + ], + const ShareActionButton(source: ActionSource.timeline), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), + if (multiselect.onlyRemote) const DownloadActionButton(source: ActionSource.timeline), + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + const ArchiveActionButton(source: ActionSource.timeline), + const EditDateTimeActionButton(source: ActionSource.timeline), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton(source: ActionSource.timeline), + if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), + if (multiselect.onlyLocal || multiselect.hasMerged) const DeleteActionButton(source: ActionSource.timeline), + ], + if (multiselect.onlyLocal || multiselect.hasMerged) + const DeleteLocalActionButton(source: ActionSource.timeline), + if (multiselect.onlyLocal) const UploadActionButton(source: ActionSource.timeline), ], + slivers: multiselect.hasRemote + ? [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), + ] + : [], ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart index 74d5ec96da..b1e87dfaea 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/local_album_bottom_sheet.widget.dart @@ -1,81 +1,24 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/upload.action.dart'; -import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; -class LocalAlbumBottomSheet extends ConsumerStatefulWidget { +class LocalAlbumBottomSheet extends ConsumerWidget { const LocalAlbumBottomSheet({super.key}); @override - ConsumerState createState() => _LocalAlbumBottomSheetState(); -} - -class _LocalAlbumBottomSheetState extends ConsumerState { - late final DraggableScrollableController sheetController; - - @override - void initState() { - super.initState(); - sheetController = DraggableScrollableController(); - } - - @override - void dispose() { - sheetController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Future addToAlbum(RemoteAlbum album) async { - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); - - if (!context.mounted) { - return; - } - - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; - } - - ImmichToast.show( - context: context, - msg: result.count == 0 - ? context.t.add_to_album_bottom_sheet_already_exists(album: album.name) - : context.t.add_to_album_bottom_sheet_added(album: album.name), - ); - } - - Future onKeyboardExpand() { - return sheetController.animateTo(0.85, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); - } - - return BaseBottomSheet( - controller: sheetController, + Widget build(BuildContext context, WidgetRef ref) { + return const BaseBottomSheet( initialChildSize: 0.25, - maxChildSize: 0.85, + maxChildSize: 0.4, shouldCloseOnMinExtent: false, - actions: const [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: CleanupLocalAction(source: .timeline)), - .new(action: UploadAction(source: .timeline)), - ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + actions: [ + ShareActionButton(source: ActionSource.timeline), + DeleteLocalActionButton(source: ActionSource.timeline), + UploadActionButton(source: ActionSource.timeline), ], ); } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart index 32730b8ac4..a644e6a035 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/locked_folder_bottom_sheet.widget.dart @@ -1,27 +1,26 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -class LockedFolderBottomSheet extends StatelessWidget { +class LockedFolderBottomSheet extends ConsumerWidget { const LockedFolderBottomSheet({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return const BaseBottomSheet( initialChildSize: 0.25, maxChildSize: 0.4, shouldCloseOnMinExtent: false, - actions: [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: DownloadAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: LockAction(source: .timeline)), + actions: [ + ShareActionButton(source: ActionSource.timeline), + DownloadActionButton(source: ActionSource.timeline), + DeletePermanentActionButton(source: ActionSource.timeline), + RemoveFromLockFolderActionButton(source: ActionSource.timeline), ], ); } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart index 06d1ddc99d..d7ef604718 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart @@ -2,30 +2,24 @@ 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/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; class MapBottomSheet extends StatelessWidget { - final Key? sheetKey; - - const MapBottomSheet({super.key, this.sheetKey}); + const MapBottomSheet({super.key}); @override Widget build(BuildContext context) { return BaseBottomSheet( - key: sheetKey, initialChildSize: 0.25, maxChildSize: 0.75, shouldCloseOnMinExtent: false, resizeOnScroll: false, - actions: const [], + actions: [], backgroundColor: context.themeData.colorScheme.surface, - slivers: const [ - SliverFillRemaining(hasScrollBody: false, child: SizedBox(height: 0, child: _ScopedMapTimeline())), - ], + slivers: [const SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())], ); } } @@ -55,7 +49,7 @@ class _ScopedMapTimeline extends StatelessWidget { return timelineService; }), ], - child: const Timeline(appBar: null, bottomSheet: GeneralBottomSheet(minChildSize: 0.23), withScrubber: false), + child: const Timeline(appBar: null, bottomSheet: null, withScrubber: false), ); } } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart index 0c63cde140..5e4dae34bc 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/partner_detail_bottom_sheet.widget.dart @@ -1,78 +1,22 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; -import 'package:immich_mobile/widgets/common/immich_toast.dart'; -class PartnerDetailBottomSheet extends ConsumerStatefulWidget { +class PartnerDetailBottomSheet extends ConsumerWidget { const PartnerDetailBottomSheet({super.key}); @override - ConsumerState createState() => _PartnerDetailBottomSheetState(); -} - -class _PartnerDetailBottomSheetState extends ConsumerState { - late final DraggableScrollableController sheetController; - - @override - void initState() { - super.initState(); - sheetController = DraggableScrollableController(); - } - - @override - void dispose() { - sheetController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Future addToAlbum(RemoteAlbum album) async { - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); - - if (!context.mounted) { - return; - } - - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; - } - - ImmichToast.show( - context: context, - msg: result.count == 0 - ? context.t.add_to_album_bottom_sheet_already_exists(album: album.name) - : context.t.add_to_album_bottom_sheet_added(album: album.name), - ); - } - - Future onKeyboardExpand() { - return sheetController.animateTo(0.85, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut); - } - - return BaseBottomSheet( - controller: sheetController, + Widget build(BuildContext context, WidgetRef ref) { + return const BaseBottomSheet( initialChildSize: 0.25, - maxChildSize: 0.85, + maxChildSize: 0.4, shouldCloseOnMinExtent: false, - actions: const [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: ShareAction(source: .timeline)), - .new(action: DownloadAction(source: .timeline)), - ], - slivers: [ - const AddToAlbumHeader(), - AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand), + actions: [ + ShareActionButton(source: ActionSource.timeline), + DownloadActionButton(source: ActionSource.timeline), ], ); } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart index 9500e6329d..6848a07bb8 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/remote_album_bottom_sheet.widget.dart @@ -2,24 +2,28 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/remove_from_album.action.dart'; -import 'package:immich_mobile/presentation/actions/set_album_cover.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -48,26 +52,33 @@ class _RemoteAlbumBottomSheetState extends ConsumerState @override Widget build(BuildContext context) { + final multiselect = ref.watch(multiSelectProvider); + final isTrashEnable = ref.watch(serverInfoProvider.select((state) => state.serverFeatures.trash)); final ownsAlbum = ref.watch(currentUserProvider)?.id == widget.album.ownerId; - Future addToAlbum(RemoteAlbum album) async { - final result = await ref.read(actionProvider.notifier).addToAlbum(ActionSource.timeline, album); - - if (!context.mounted) { + Future addAssetsToAlbum(RemoteAlbum album) async { + final selectedAssets = multiselect.selectedAssets; + if (selectedAssets.isEmpty) { return; } - if (!result.success) { - ImmichToast.show(context: context, msg: context.t.scaffold_body_error_occurred, toastType: ToastType.error); - return; + final addedCount = await ref + .read(remoteAlbumProvider.notifier) + .addAssets(album.id, selectedAssets.map((e) => (e as RemoteAsset).id).toList()); + + if (addedCount != selectedAssets.length) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_already_exists'.t(context: context, args: {"album": album.name}), + ); + } else { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.t(context: context, args: {"album": album.name}), + ); } - ImmichToast.show( - context: context, - msg: result.count == 0 - ? context.t.add_to_album_bottom_sheet_already_exists(album: album.name) - : context.t.add_to_album_bottom_sheet_added(album: album.name), - ); + ref.read(multiSelectProvider.notifier).reset(); } Future onKeyboardExpand() { @@ -80,35 +91,37 @@ class _RemoteAlbumBottomSheetState extends ConsumerState minChildSize: 0.22, maxChildSize: 0.85, shouldCloseOnMinExtent: false, - actions: [ - const .new(action: AssetDebugAction(source: .timeline)), - const .new(action: ShareAction(source: .timeline)), - const .new(action: ShareLinkAction(source: .timeline)), + actions: [ + const ShareActionButton(source: ActionSource.timeline), + if (multiselect.hasRemote) ...[ + const ShareLinkActionButton(source: ActionSource.timeline), - if (ownsAlbum) ...const [ - .new(action: ArchiveAction(source: .timeline)), - .new(action: FavoriteAction(source: .timeline)), - ], - const .new(action: DownloadAction(source: .timeline)), - if (ownsAlbum) ...const [ - .new(action: DeleteAction(source: .timeline)), - .new(action: EditDateTimeAction(source: .timeline)), - .new(action: EditLocationAction(source: .timeline)), - .new(action: LockAction(source: .timeline)), - .new(action: StackAction(source: .timeline)), - ], - const .new(action: CleanupLocalAction(source: .timeline)), - if (ownsAlbum) ...[ - ActionColumnButton( - action: RemoveFromAlbumAction(source: .timeline, albumId: widget.album.id), - ), - ActionColumnButton( - action: SetAlbumCoverAction(source: .timeline, albumId: widget.album.id), - ), + if (ownsAlbum) ...[ + const ArchiveActionButton(source: ActionSource.timeline), + const FavoriteActionButton(source: ActionSource.timeline), + ], + const DownloadActionButton(source: ActionSource.timeline), + if (ownsAlbum) ...[ + isTrashEnable + ? const TrashActionButton(source: ActionSource.timeline) + : const DeletePermanentActionButton(source: ActionSource.timeline), + const EditDateTimeActionButton(source: ActionSource.timeline), + const EditLocationActionButton(source: ActionSource.timeline), + const MoveToLockFolderActionButton(source: ActionSource.timeline), + if (multiselect.selectedAssets.length > 1) const StackActionButton(source: ActionSource.timeline), + if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline), + ], ], + if (multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline), + if (ownsAlbum) RemoveFromAlbumActionButton(source: ActionSource.timeline, albumId: widget.album.id), + if (ownsAlbum && multiselect.selectedAssets.length == 1) + SetAlbumCoverActionButton(source: ActionSource.timeline, albumId: widget.album.id), ], slivers: ownsAlbum - ? [const AddToAlbumHeader(), AlbumSelector(onAlbumSelected: addToAlbum, onKeyboardExpanded: onKeyboardExpand)] + ? [ + const AddToAlbumHeader(), + AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand), + ] : null, ); } diff --git a/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart b/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart index 32e5b01a99..c96e680966 100644 --- a/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_sheet/trash_bottom_sheet.widget.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/restore.action.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/restore_trash_action_button.widget.dart'; -class TrashBottomBar extends StatelessWidget { +class TrashBottomBar extends ConsumerWidget { const TrashBottomBar({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Align( alignment: Alignment.bottomCenter, child: Container( @@ -19,10 +19,9 @@ class TrashBottomBar extends StatelessWidget { top: false, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - .new(action: AssetDebugAction(source: .timeline)), - .new(action: DeleteAction(source: .timeline)), - .new(action: RestoreAction(source: .timeline)), + children: [ + DeleteTrashActionButton(source: ActionSource.timeline), + RestoreTrashActionButton(source: ActionSource.timeline), ], ), ), diff --git a/mobile/lib/presentation/widgets/feature_message/feature_message_dialog.widget.dart b/mobile/lib/presentation/widgets/feature_message/feature_message_dialog.widget.dart deleted file mode 100644 index dc89c10294..0000000000 --- a/mobile/lib/presentation/widgets/feature_message/feature_message_dialog.widget.dart +++ /dev/null @@ -1,316 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:immich_mobile/domain/models/feature_message.model.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/feature_message/feature_message_placeholder.widget.dart'; - -Future showFeatureMessageDialog(BuildContext context) { - return showGeneralDialog( - context: context, - useRootNavigator: true, - barrierDismissible: true, - barrierLabel: context.t.whats_new, - barrierColor: Colors.black.withValues(alpha: 0.55), - transitionDuration: const Duration(milliseconds: 280), - pageBuilder: (_, _, _) => const _FeatureMessageDialog(), - transitionBuilder: (_, animation, _, child) { - final curved = CurvedAnimation(parent: animation, curve: Curves.easeOutCubic, reverseCurve: Curves.easeInCubic); - return FadeTransition( - opacity: animation, - child: ScaleTransition(scale: Tween(begin: 0.94, end: 1.0).animate(curved), child: child), - ); - }, - ); -} - -class _FeatureMessageDialog extends StatefulWidget { - const _FeatureMessageDialog(); - - @override - State<_FeatureMessageDialog> createState() => _FeatureMessageDialogState(); -} - -class _FeatureMessageDialogState extends State<_FeatureMessageDialog> with SingleTickerProviderStateMixin { - static const double _radius = 24; - - final PageController _controller = PageController(); - late final AnimationController _borderController = AnimationController( - vsync: this, - duration: const Duration(seconds: 7), - )..repeat(); - final List _highlights = visibleFeatureMessageHighlights; - int _index = 0; - - bool get _isLast => _index >= _highlights.length - 1; - - @override - void dispose() { - _controller.dispose(); - _borderController.dispose(); - super.dispose(); - } - - void _advance() { - if (_isLast) { - Navigator.of(context).pop(); - return; - } - unawaited(_controller.nextPage(duration: const Duration(milliseconds: 320), curve: Curves.easeOutCubic)); - } - - List _borderColors(BuildContext context) { - final scheme = context.colorScheme; - // Mute the hues toward the surface and drop opacity in dark mode to keep it gentle. - Color tone(Color c) => context.isDarkTheme ? Color.lerp(c, scheme.surface, 0.45)!.withValues(alpha: 0.6) : c; - return [tone(scheme.primary), tone(scheme.tertiary), tone(scheme.secondary), tone(scheme.primary)]; - } - - @override - Widget build(BuildContext context) { - return Dialog( - insetPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 64), - clipBehavior: Clip.antiAlias, - backgroundColor: context.isDarkTheme ? context.colorScheme.surfaceContainerLow : Colors.white, - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(_radius))), - child: AnimatedBuilder( - animation: _borderController, - builder: (context, child) => CustomPaint( - foregroundPainter: _GradientBorderPainter( - rotation: _borderController.value, - colors: _borderColors(context), - radius: _radius, - strokeWidth: 3, - ), - child: child, - ), - child: ConstrainedBox( - constraints: BoxConstraints(maxHeight: context.height * 0.9, maxWidth: 480), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 20, 24, 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - context.t.whats_new, - style: context.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700), - ), - const SizedBox(height: 2), - Text( - context.t.whats_new_version(version: featureMessageRelease.toString()), - style: context.textTheme.bodyLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary), - ), - ], - ), - ), - const SizedBox(height: 32), - - Expanded( - child: PageView.builder( - controller: _controller, - itemCount: _highlights.length, - onPageChanged: (i) => setState(() => _index = i), - itemBuilder: (_, index) => _FeaturePage(highlight: _highlights[index]), - ), - ), - const SizedBox(height: 8), - _PageDots(controller: _controller, index: _index, count: _highlights.length), - Padding( - padding: const EdgeInsets.fromLTRB(20, 18, 20, 26), - child: Row( - children: [ - TextButton( - onPressed: () => Navigator.of(context).pop(), - style: TextButton.styleFrom(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14)), - child: Text(context.t.skip), - ), - const SizedBox(width: 8), - Expanded( - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(100)), - boxShadow: [ - // Soft wide primary glow. - BoxShadow( - color: context.primaryColor.withValues(alpha: 0.38), - blurRadius: 22, - spreadRadius: -4, - offset: const Offset(0, 10), - ), - // Tight contact shadow for grounding. - BoxShadow( - color: context.primaryColor.withValues(alpha: 0.22), - blurRadius: 6, - offset: const Offset(0, 2), - ), - ], - ), - child: FilledButton( - onPressed: _advance, - style: FilledButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - elevation: 0, - textStyle: context.textTheme.labelLarge?.copyWith( - fontWeight: FontWeight.w700, - fontSize: 16, - ), - ), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - child: Text(_isLast ? context.t.ok : context.t.next, key: ValueKey(_isLast)), - ), - ), - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - } -} - -class _GradientBorderPainter extends CustomPainter { - const _GradientBorderPainter({ - required this.rotation, - required this.colors, - required this.radius, - this.strokeWidth = 3, - }); - - final double rotation; - final List colors; - final double radius; - final double strokeWidth; - - @override - void paint(Canvas canvas, Size size) { - final inset = strokeWidth / 2; - final rect = (Offset.zero & size).deflate(inset); - final rrect = RRect.fromRectAndRadius(rect, Radius.circular(radius - inset)); - - final shader = SweepGradient( - transform: GradientRotation(rotation * 2 * math.pi), - colors: colors, - ).createShader(rect); - - final paint = Paint() - ..shader = shader - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth; - canvas.drawRRect(rrect, paint); - } - - @override - bool shouldRepaint(_GradientBorderPainter oldDelegate) => - oldDelegate.rotation != rotation || !listEquals(oldDelegate.colors, colors); -} - -class _FeaturePage extends StatelessWidget { - final FeatureHighlight highlight; - - const _FeaturePage({required this.highlight}); - - @override - Widget build(BuildContext context) { - final scheme = context.colorScheme; - - return SingleChildScrollView( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(20, 8, 20, 0), - child: DecoratedBox( - decoration: BoxDecoration( - color: scheme.surfaceContainerHighest, - borderRadius: const BorderRadius.all(Radius.circular(18)), - border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.5)), - ), - child: ClipRRect( - borderRadius: const BorderRadius.all(Radius.circular(18)), - child: SizedBox( - width: double.infinity, - height: 256, - child: highlight.image == null - ? const FeatureMessagePlaceholder() - : Image.asset( - highlight.image!, - fit: BoxFit.contain, - errorBuilder: (context, _, _) => const FeatureMessagePlaceholder(), - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 18, 24, 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - highlight.title(context.t), - style: context.textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700, fontSize: 24), - ), - const SizedBox(height: 8), - Text( - highlight.body(context.t), - style: context.textTheme.bodyLarge?.copyWith(color: scheme.onSurfaceVariant, height: 1.4), - ), - ], - ), - ), - ], - ), - ); - } -} - -class _PageDots extends StatelessWidget { - final PageController controller; - final int index; - final int count; - - const _PageDots({required this.controller, required this.index, required this.count}); - - @override - Widget build(BuildContext context) { - final primary = context.primaryColor; - - return AnimatedBuilder( - animation: controller, - builder: (context, _) { - final page = controller.hasClients ? (controller.page ?? index.toDouble()) : index.toDouble(); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(count, (i) { - final activeness = (1 - (page - i).abs()).clamp(0.0, 1.0); - return AnimatedContainer( - duration: const Duration(milliseconds: 150), - margin: const EdgeInsets.symmetric(horizontal: 3), - height: 7, - width: 7 + 16 * activeness, - decoration: BoxDecoration( - color: Color.lerp(context.colorScheme.surfaceContainerHighest, primary, activeness), - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - ); - }), - ); - }, - ); - } -} diff --git a/mobile/lib/presentation/widgets/feature_message/feature_message_placeholder.widget.dart b/mobile/lib/presentation/widgets/feature_message/feature_message_placeholder.widget.dart deleted file mode 100644 index b157d5078b..0000000000 --- a/mobile/lib/presentation/widgets/feature_message/feature_message_placeholder.widget.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/widgets/common/immich_logo.dart'; - -class _SplatColors { - static const primary = Color(0xFF4250AF); - static const info = Color(0xFF3B82F6); - static const success = Color(0xFF2FB457); - static const warning = Color(0xFFF2A73B); - static const danger = Color(0xFFE5484D); -} - -class FeatureMessagePlaceholder extends StatelessWidget { - const FeatureMessagePlaceholder({super.key}); - - @override - Widget build(BuildContext context) { - final dark = Theme.of(context).brightness == Brightness.dark; - - final cardColor = dark ? const Color(0xFF232228) : const Color(0xFFEEEDF4); - final tileColor = dark ? const Color(0xFF2B2A32) : const Color(0xFFFBFAFE); - final inkColor = dark ? const Color(0xFFE7E7EC) : const Color(0xFF1A1A1E); - - return Container( - width: double.infinity, - height: double.infinity, - clipBehavior: Clip.antiAlias, - // Fill a plain rectangle — the parent's ClipRRect handles the corner rounding, - // so the placeholder doesn't round its own corners inside that clip. - decoration: BoxDecoration(color: cardColor), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // ---- confetti motif (168 × 120 region) ---- - SizedBox( - width: 168, - height: 120, - child: Stack( - clipBehavior: Clip.none, - children: [ - // scattered confetti - Positioned(left: 6, top: 24, child: _dot(12, _SplatColors.primary)), - Positioned(left: 80, top: -2, child: _dot(9, _SplatColors.danger)), - Positioned(left: 148, top: 84, child: _dot(11, _SplatColors.success)), - Positioned(left: 140, top: 14, child: _bar(22, 8, 0.49, _SplatColors.danger)), // ~28° - Positioned(left: 2, top: 90, child: _bar(20, 8, -0.31, _SplatColors.info)), // ~-18° - // tilted spark tile - Positioned( - left: 46, - top: 18, - child: Transform.rotate( - angle: -0.105, // ~-6° - child: Container( - width: 84, - height: 84, - decoration: BoxDecoration( - color: tileColor, - borderRadius: const BorderRadius.all(Radius.circular(18)), - boxShadow: [ - BoxShadow( - color: const Color(0xFF0F122D).withValues(alpha: 0.22), - blurRadius: 22, - offset: const Offset(0, 10), - ), - ], - ), - child: Stack( - alignment: Alignment.center, - children: [ - Positioned(left: 12, top: 12, child: _dot(12, _SplatColors.warning)), - const ImmichLogo(size: 40), - ], - ), - ), - ), - ), - ], - ), - ), - const SizedBox(height: 16), - Text( - context.t.new_feature, - style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600, color: inkColor), - ), - ], - ), - ); - } - - static Widget _dot(double d, Color c) => Container( - width: d, - height: d, - decoration: BoxDecoration(color: c, shape: BoxShape.circle), - ); - - static Widget _bar(double w, double h, double angle, Color c) => Transform.rotate( - angle: angle, - child: Container( - width: w, - height: h, - decoration: BoxDecoration(color: c, borderRadius: const BorderRadius.all(Radius.circular(99))), - ), - ); -} diff --git a/mobile/lib/presentation/widgets/images/full_image.widget.dart b/mobile/lib/presentation/widgets/images/full_image.widget.dart index 8c92ac2818..77ea996b89 100644 --- a/mobile/lib/presentation/widgets/images/full_image.widget.dart +++ b/mobile/lib/presentation/widgets/images/full_image.widget.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; @@ -24,7 +22,7 @@ class FullImage extends StatelessWidget { Widget build(BuildContext context) { final provider = getFullImageProvider(asset, size: size); return OctoImage( - fadeInDuration: Duration.zero, + fadeInDuration: const Duration(milliseconds: 0), fadeOutDuration: const Duration(milliseconds: 100), placeholderBuilder: placeholder != null ? (_) => placeholder! : null, image: provider, @@ -32,7 +30,7 @@ class FullImage extends StatelessWidget { height: size.height, fit: fit, errorBuilder: (context, error, stackTrace) { - unawaited(provider.evict()); + provider.evict(); return const Icon(Icons.image_not_supported_outlined, size: 32); }, ); diff --git a/mobile/lib/presentation/widgets/images/image_provider.dart b/mobile/lib/presentation/widgets/images/image_provider.dart index bdedd29d45..1d87fa404e 100644 --- a/mobile/lib/presentation/widgets/images/image_provider.dart +++ b/mobile/lib/presentation/widgets/images/image_provider.dart @@ -1,12 +1,11 @@ -import 'dart:async'; -import 'dart:io'; import 'dart:ui' as ui; import 'package:async/async.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; @@ -26,7 +25,7 @@ mixin CancellableImageProviderMixin on CancellableImageProvide ImageInfo? getInitialImage(CancellableImageProvider provider) { final completer = CancelableCompleter(onCancel: provider.cancel); - final cachedStream = provider.resolve(ImageConfiguration.empty); + final cachedStream = provider.resolve(const ImageConfiguration()); ImageInfo? cachedImage; final listener = ImageStreamListener((image, synchronousCall) { if (synchronousCall) { @@ -44,17 +43,15 @@ mixin CancellableImageProviderMixin on CancellableImageProvide return cachedImage; } - unawaited( - completer.operation.valueOrCancellation().whenComplete(() { - cachedStream.removeListener(listener); - cachedOperation = null; - }), - ); + completer.operation.valueOrCancellation().whenComplete(() { + cachedStream.removeListener(listener); + cachedOperation = null; + }); cachedOperation = completer.operation; return null; } - Stream loadRequest(ImageRequest request, ImageDecoderCallback decode, {required bool isFinal}) async* { + Stream loadRequest(ImageRequest request, ImageDecoderCallback decode, {bool evictOnError = true}) async* { if (isCancelled) { this.request = null; return; @@ -62,18 +59,21 @@ mixin CancellableImageProviderMixin on CancellableImageProvide try { final image = await request.load(decode); - if (isCancelled || image == null) { - image?.dispose(); + if (isCancelled) { + return; + } + if (image == null && evictOnError) { + PaintingBinding.instance.imageCache.evict(this); + return; + } else if (image == null) { return; } - isFinished = isFinal; yield image; } catch (e, stack) { if (isCancelled) { return; } - if (isFinal) { - isFinished = true; + if (evictOnError) { PaintingBinding.instance.imageCache.evict(this); rethrow; } @@ -83,7 +83,7 @@ mixin CancellableImageProviderMixin on CancellableImageProvide } } - Future loadCodecRequest(ImageRequest request, {required bool isFinal}) async { + Future loadCodecRequest(ImageRequest request) async { if (isCancelled) { this.request = null; return null; @@ -91,19 +91,20 @@ mixin CancellableImageProviderMixin on CancellableImageProvide try { final codec = await request.loadCodec(); - if (isCancelled || codec == null) { + if (isCancelled) { codec?.dispose(); return null; } - isFinished = isFinal; + if (codec == null) { + PaintingBinding.instance.imageCache.evict(this); + return null; + } return codec; } catch (e) { - if (isFinal) { - isFinished = true; + if (!isCancelled) { PaintingBinding.instance.imageCache.evict(this); - rethrow; } - return null; + rethrow; } finally { this.request = null; } @@ -141,7 +142,7 @@ mixin CancellableImageProviderMixin on CancellableImageProvide final operation = cachedOperation; if (operation != null) { cachedOperation = null; - unawaited(operation.cancel()); + operation.cancel(); } if (hasActiveWork) { @@ -150,28 +151,12 @@ mixin CancellableImageProviderMixin on CancellableImageProvide } } -ImageProvider getFullImageProvider( - BaseAsset asset, { - Size size = const Size(1080, 1920), - bool edited = true, - String? localFilePath, - Size? remoteThumbnailSize, -}) { +ImageProvider getFullImageProvider(BaseAsset asset, {Size size = const Size(1080, 1920)}) { // Create new provider and cache it final ImageProvider provider; - if (localFilePath != null) { - provider = FileImage(File(localFilePath)); - } else if (_shouldUseLocalAsset(asset)) { + if (_shouldUseLocalAsset(asset)) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!; - provider = LocalFullImageProvider( - id: id, - size: size, - assetType: asset.type, - isAnimated: asset.isAnimatedImage, - width: asset.width, - height: asset.height, - checksum: asset.checksum, - ); + provider = LocalFullImageProvider(id: id, size: size, assetType: asset.type, isAnimated: asset.isAnimatedImage); } else { final String assetId; final String thumbhash; @@ -189,35 +174,22 @@ ImageProvider getFullImageProvider( thumbhash: thumbhash, assetType: asset.type, isAnimated: asset.isAnimatedImage, - edited: edited, - thumbnailSize: remoteThumbnailSize, ); } return provider; } -ImageProvider? getThumbnailImageProvider( - BaseAsset asset, { - Size size = kThumbnailResolution, - - /// Physical size to decode for remote thumbnails, or null for the source size. - Size? remoteSize, - bool edited = true, -}) { +ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnailResolution}) { if (_shouldUseLocalAsset(asset)) { final id = asset is LocalAsset ? asset.id : (asset as RemoteAsset).localId!; - return LocalThumbProvider(id: id, size: size, assetType: asset.type, checksum: asset.checksum); + return LocalThumbProvider(id: id, size: size, assetType: asset.type); } final assetId = asset is RemoteAsset ? asset.id : (asset as LocalAsset).remoteId; final thumbhash = asset is RemoteAsset ? asset.thumbHash ?? "" : ""; - return assetId != null - ? RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: thumbhash, edited: edited, decodeSize: remoteSize) - : null; + return assetId != null ? RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: thumbhash) : null; } bool _shouldUseLocalAsset(BaseAsset asset) => - asset.hasLocal && - (!asset.hasRemote || !SettingsRepository.instance.appConfig.image.preferRemote) && - !asset.isEdited; + asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)) && !asset.isEdited; diff --git a/mobile/lib/presentation/widgets/images/local_album_thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/local_album_thumbnail.widget.dart index 966e070872..b519da33c3 100644 --- a/mobile/lib/presentation/widgets/images/local_album_thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/local_album_thumbnail.widget.dart @@ -14,7 +14,7 @@ class LocalAlbumThumbnail extends ConsumerWidget { return localAlbumThumbnail.when( data: (data) { if (data == null) { - return DecoratedBox( + return Container( decoration: BoxDecoration( color: context.colorScheme.surfaceContainer, borderRadius: const BorderRadius.all(Radius.circular(16)), diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index 46e8eda85b..d4f829bed5 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -1,28 +1,21 @@ -import 'dart:math' as math; - import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; -// iOS GPU textures max out at 16384px; larger images squish. -const _kMaxPixelSize = 16384; - class LocalThumbProvider extends CancellableImageProvider with CancellableImageProviderMixin { final String id; final Size size; final AssetType assetType; - // an edit on the device keeps the id and changes the bytes, so the checksum is what separates two renders - final String? checksum; - - LocalThumbProvider({required this.id, required this.assetType, this.checksum, this.size = kThumbnailResolution}); + LocalThumbProvider({required this.id, required this.assetType, this.size = kThumbnailResolution}); @override Future obtainKey(ImageConfiguration configuration) { @@ -43,22 +36,20 @@ class LocalThumbProvider extends CancellableImageProvider Stream _codec(LocalThumbProvider key, ImageDecoderCallback decode) { final request = this.request = LocalImageRequest(localId: key.id, size: key.size, assetType: key.assetType); - return loadRequest(request, decode, isFinal: true); + return loadRequest(request, decode); } @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; if (other is LocalThumbProvider) { - return id == other.id && checksum == other.checksum; + return id == other.id; } return false; } @override - int get hashCode => Object.hash(id, checksum); + int get hashCode => id.hashCode; } class LocalFullImageProvider extends CancellableImageProvider @@ -67,38 +58,8 @@ class LocalFullImageProvider extends CancellableImageProvider - previewTargetSize(size.width * dpr, size.height * dpr, width, height, previewIsFinal: previewIsFinal); - - // Use an aspect-correct target when aspectFill would exceed the texture limit. - @visibleForTesting - static Size previewTargetSize(double boxW, double boxH, int? width, int? height, {required bool previewIsFinal}) { - if (width == null || height == null || width <= 0 || height <= 0) { - return Size(boxW, boxH); - } - final imgLong = math.max(width, height).toDouble(); - final coverLong = imgLong * math.max(boxW / width, boxH / height); - if (coverLong <= _kMaxPixelSize) { - return Size(boxW, boxH); - } - final bound = previewIsFinal ? _kMaxPixelSize.toDouble() : math.max(boxW, boxH); - final scale = math.min(1.0, bound / imgLong); - return Size(math.max(1.0, width * scale), math.max(1.0, height * scale)); - } + LocalFullImageProvider({required this.id, required this.assetType, required this.size, required this.isAnimated}); @override Future obtainKey(ImageConfiguration configuration) { @@ -111,7 +72,7 @@ class LocalFullImageProvider extends CancellableImageProvider [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Id', key.id), @@ -124,7 +85,7 @@ class LocalFullImageProvider extends CancellableImageProvider [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Id', key.id), @@ -142,16 +103,16 @@ class LocalFullImageProvider extends CancellableImageProvider _animatedCodec(LocalFullImageProvider key, ImageDecoderCallback decode) async* { @@ -174,10 +136,10 @@ class LocalFullImageProvider extends CancellableImageProvider Object.hash(id, size, isAnimated, width, height, checksum); + int get hashCode => id.hashCode ^ size.hashCode ^ isAnimated.hashCode; } diff --git a/mobile/lib/presentation/widgets/images/remote_image_provider.dart b/mobile/lib/presentation/widgets/images/remote_image_provider.dart index b533fd8cf5..314a8d5ff1 100644 --- a/mobile/lib/presentation/widgets/images/remote_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/remote_image_provider.dart @@ -1,8 +1,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; +import 'package:immich_mobile/domain/services/setting.service.dart'; import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart'; @@ -12,19 +13,11 @@ import 'package:openapi/api.dart'; class RemoteImageProvider extends CancellableImageProvider with CancellableImageProviderMixin { final String url; - final bool edited; - /// Physical size to decode, or null for the source size. - final Size? decodeSize; + RemoteImageProvider({required this.url}); - RemoteImageProvider({required this.url, this.edited = true, this.decodeSize}); - - RemoteImageProvider.thumbnail({ - required String assetId, - required String thumbhash, - this.edited = true, - this.decodeSize, - }) : url = getThumbnailUrlForRemoteId(assetId, thumbhash: thumbhash, edited: edited); + RemoteImageProvider.thumbnail({required String assetId, required String thumbhash}) + : url = getThumbnailUrlForRemoteId(assetId, thumbhash: thumbhash); @override Future obtainKey(ImageConfiguration configuration) { @@ -44,23 +37,21 @@ class RemoteImageProvider extends CancellableImageProvider } Stream _codec(RemoteImageProvider key, ImageDecoderCallback decode) { - final request = this.request = RemoteImageRequest(uri: key.url, decodeSize: key.decodeSize); - return loadRequest(request, decode, isFinal: true); + final request = this.request = RemoteImageRequest(uri: key.url); + return loadRequest(request, decode); } @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; if (other is RemoteImageProvider) { - return url == other.url && edited == other.edited && decodeSize == other.decodeSize; + return url == other.url; } return false; } @override - int get hashCode => url.hashCode ^ edited.hashCode ^ decodeSize.hashCode; + int get hashCode => url.hashCode; } class RemoteFullImageProvider extends CancellableImageProvider @@ -69,18 +60,12 @@ class RemoteFullImageProvider extends CancellableImageProvider [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Asset Id', key.assetId), @@ -108,14 +91,7 @@ class RemoteFullImageProvider extends CancellableImageProvider [ DiagnosticsProperty('Image provider', this), DiagnosticsProperty('Asset Id', key.assetId), @@ -133,17 +109,13 @@ class RemoteFullImageProvider extends CancellableImageProvider _animatedCodec(RemoteFullImageProvider key, ImageDecoderCallback decode) async* { @@ -165,24 +136,17 @@ class RemoteFullImageProvider extends CancellableImageProvider assetId.hashCode ^ thumbhash.hashCode ^ isAnimated.hashCode ^ edited.hashCode; + int get hashCode => assetId.hashCode ^ thumbhash.hashCode ^ isAnimated.hashCode; } diff --git a/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart b/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart index 1b40b48f2f..7076febe3b 100644 --- a/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart +++ b/mobile/lib/presentation/widgets/images/thumb_hash_provider.dart @@ -22,14 +22,12 @@ class ThumbHashProvider extends CancellableImageProvider Stream _loadCodec(ThumbHashProvider key, ImageDecoderCallback decode) { final request = this.request = ThumbhashImageRequest(thumbhash: key.thumbHash); - return loadRequest(request, decode, isFinal: true); + return loadRequest(request, decode); } @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; if (other is ThumbHashProvider) { return thumbHash == other.thumbHash; } diff --git a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart index 5bfeb25977..70a9057e12 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail.widget.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; @@ -26,22 +25,17 @@ class Thumbnail extends StatefulWidget { required String remoteId, required String thumbhash, this.fit = BoxFit.cover, - - /// Physical size to decode, or null for the source size. - Size? decodeSize, + Size size = kThumbnailResolution, super.key, - }) : imageProvider = RemoteImageProvider.thumbnail(assetId: remoteId, thumbhash: thumbhash, decodeSize: decodeSize), + }) : imageProvider = RemoteImageProvider.thumbnail(assetId: remoteId, thumbhash: thumbhash), thumbhashProvider = null; Thumbnail.fromAsset({ required BaseAsset? asset, this.fit = BoxFit.cover, - /// Decode size for local thumbnails. This does not affect the widget size. + /// The logical UI size of the thumbnail. This is only used to determine the ideal image resolution and does not affect the widget size. Size size = kThumbnailResolution, - - /// Physical size to decode for remote thumbnails. - Size? remoteSize, super.key, }) : thumbhashProvider = switch (asset) { RemoteAsset() when asset.thumbHash != null && asset.localId == null => ThumbHashProvider( @@ -49,7 +43,7 @@ class Thumbnail extends StatefulWidget { ), _ => null, }, - imageProvider = asset == null ? null : getThumbnailImageProvider(asset, size: size, remoteSize: remoteSize); + imageProvider = asset == null ? null : getThumbnailImageProvider(asset, size: size); @override State createState() => _ThumbnailState(); @@ -88,9 +82,7 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix void _loadFromThumbhashProvider() { _stopListeningToThumbhashStream(); final thumbhashProvider = widget.thumbhashProvider; - if (thumbhashProvider == null || _providerImage != null) { - return; - } + if (thumbhashProvider == null || _providerImage != null) return; final thumbhashStream = _thumbhashStream = thumbhashProvider.resolve(ImageConfiguration.empty); final thumbhashStreamListener = _thumbhashStreamListener = ImageStreamListener( @@ -116,9 +108,7 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix void _loadFromImageProvider() { _stopListeningToImageStream(); final imageProvider = widget.imageProvider; - if (imageProvider == null) { - return; - } + if (imageProvider == null) return; final imageStream = _imageStream = imageProvider.resolve(ImageConfiguration.empty); final imageStreamListener = _imageStreamListener = ImageStreamListener( @@ -136,9 +126,9 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix if ((synchronousCall && _providerImage == null) || !_isVisible()) { _fadeController.value = 1.0; } else if (_fadeController.isAnimating) { - unawaited(_fadeController.forward()); + _fadeController.forward(); } else { - unawaited(_fadeController.forward(from: 0.0)); + _fadeController.forward(from: 0.0); } setState(() { @@ -211,9 +201,7 @@ class _ThumbnailState extends State with SingleTickerProviderStateMix bool _isVisible() { final renderObject = context.findRenderObject() as RenderBox?; - if (renderObject == null || !renderObject.attached) { - return false; - } + if (renderObject == null || !renderObject.attached) return false; final topLeft = renderObject.localToGlobal(Offset.zero); final bottomRight = renderObject.localToGlobal(Offset(renderObject.size.width, renderObject.size.height)); @@ -302,12 +290,16 @@ class _ThumbnailRenderBox extends RenderBox { bool isRepaintBoundary = true; _ThumbnailRenderBox({ - required this._image, - required this._previousImage, - required this._fadeValue, - required this._fit, - required this._placeholderGradient, - }); + required ui.Image? image, + required ui.Image? previousImage, + required double fadeValue, + required BoxFit fit, + required Gradient placeholderGradient, + }) : _image = image, + _previousImage = previousImage, + _fadeValue = fadeValue, + _fit = fit, + _placeholderGradient = placeholderGradient; @override void paint(PaintingContext context, Offset offset) { diff --git a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart index 953efc144d..2ceaf80db0 100644 --- a/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart +++ b/mobile/lib/presentation/widgets/images/thumbnail_tile.widget.dart @@ -2,39 +2,34 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class ThumbnailTile extends ConsumerStatefulWidget { const ThumbnailTile( this.asset, { this.size = kThumbnailResolution, - this.remoteSize, this.fit = BoxFit.cover, this.showStorageIndicator = false, this.lockSelection = false, this.heroOffset, - this.showStackIndicator = false, super.key, }); final BaseAsset? asset; final Size size; - - /// Physical size to decode for remote thumbnails. - final Size? remoteSize; final BoxFit fit; final bool showStorageIndicator; final bool lockSelection; final int? heroOffset; - final bool showStackIndicator; @override ConsumerState createState() => _ThumbnailTileState(); @@ -64,7 +59,7 @@ class _ThumbnailTileState extends ConsumerState { ); final bool storageIndicator = - ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)) && widget.showStorageIndicator; + ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && widget.showStorageIndicator; if (!isCurrentAsset) { _hideIndicators = false; @@ -112,7 +107,7 @@ class _ThumbnailTileState extends ConsumerState { // but other solutions have failed thus far. key: ValueKey(isCurrentAsset), tag: '${asset?.heroTag}_$heroIndex', - child: Thumbnail.fromAsset(asset: asset, size: widget.size, remoteSize: widget.remoteSize), + child: Thumbnail.fromAsset(asset: asset, size: widget.size), // Placeholderbuilder used to hide indicators on first hero animation, since flightShuttleBuilder isn't called until both source and destination hero exist in widget tree. placeholderBuilder: (context, heroSize, child) { if (!_hideIndicators) { @@ -124,9 +119,6 @@ class _ThumbnailTileState extends ConsumerState { }, flightShuttleBuilder: (context, animation, direction, from, to) { void animationStatusListener(AnimationStatus status) { - if (!mounted) { - return; - } final heroInFlight = status == AnimationStatus.forward || status == AnimationStatus.reverse; if (_hideIndicators != heroInFlight) { setState(() => _hideIndicators = heroInFlight); @@ -137,7 +129,7 @@ class _ThumbnailTileState extends ConsumerState { } animation.addStatusListener(animationStatusListener); - return direction == HeroFlightDirection.push ? from.widget : to.widget; + return to.widget; }, ), ), @@ -147,14 +139,7 @@ class _ThumbnailTileState extends ConsumerState { duration: Durations.short4, child: Align( alignment: Alignment.topRight, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - _AssetTypeIcons(asset: asset), - if (widget.showStackIndicator) _StackIndicator(asset: asset), - ], - ), + child: _AssetTypeIcons(asset: asset), ), ), if (storageIndicator && asset != null) @@ -289,7 +274,7 @@ class _TileOverlayIcon extends StatelessWidget { icon, color: Colors.white, size: 16, - shadows: const [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset.zero)], + shadows: [const Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))], ); } } @@ -301,7 +286,8 @@ class _AssetTypeIcons extends StatelessWidget { @override Widget build(BuildContext context) { - final isLivePhoto = asset.isMotionPhoto; + final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null; + final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null; return Column( mainAxisSize: MainAxisSize.min, @@ -309,6 +295,11 @@ class _AssetTypeIcons extends StatelessWidget { children: [ if (asset.isVideo) Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)), + if (hasStack) + const Padding( + padding: EdgeInsets.only(right: 10.0, top: 6.0), + child: _TileOverlayIcon(Icons.burst_mode_rounded), + ), if (isLivePhoto) const Padding( padding: EdgeInsets.only(right: 10.0, top: 6.0), @@ -321,24 +312,6 @@ class _AssetTypeIcons extends StatelessWidget { } } -class _StackIndicator extends StatelessWidget { - final BaseAsset asset; - - const _StackIndicator({required this.asset}); - - @override - Widget build(BuildContext context) { - if (asset is! RemoteAsset || (asset as RemoteAsset).stackId == null) { - return const SizedBox.shrink(); - } - - return const Padding( - padding: EdgeInsets.only(right: 10.0, top: 6.0), - child: _TileOverlayIcon(Icons.burst_mode_rounded), - ); - } -} - class _UploadProgressOverlay extends StatelessWidget { final double progress; @@ -350,7 +323,7 @@ class _UploadProgressOverlay extends StatelessWidget { final percentage = isError ? 0 : (progress * 100).toInt(); return Positioned.fill( - child: ColoredBox( + child: Container( color: isError ? Colors.red.withValues(alpha: 0.6) : Colors.black54, child: Center( child: Column( diff --git a/mobile/lib/presentation/widgets/map/map.state.dart b/mobile/lib/presentation/widgets/map/map.state.dart index bc6436fc34..bfd3011050 100644 --- a/mobile/lib/presentation/widgets/map/map.state.dart +++ b/mobile/lib/presentation/widgets/map/map.state.dart @@ -1,35 +1,31 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/models/time_range.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/map.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; -import 'package:immich_mobile/utils/option.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -part 'map.state.freezed.dart'; +class MapState { + final ThemeMode themeMode; + final LatLngBounds bounds; + final bool onlyFavorites; + final bool includeArchived; + final bool withPartners; + final int relativeDays; -@Freezed(equal: false) -abstract class MapState with _$MapState { - const MapState._(); + const MapState({ + this.themeMode = ThemeMode.system, + required this.bounds, + this.onlyFavorites = false, + this.includeArchived = false, + this.withPartners = false, + this.relativeDays = 0, + }); - const factory MapState({ - @Default(ThemeMode.system) ThemeMode themeMode, - required LatLngBounds bounds, - @Default(false) bool onlyFavorites, - @Default(false) bool includeArchived, - @Default(false) bool withPartners, - @Default(0) int relativeDays, - @Default(TimeRange()) TimeRange timeRange, - }) = _MapState; - - // We only care about bounds changes, overriding Freezed @override bool operator ==(covariant MapState other) { return bounds == other.bounds; @@ -38,13 +34,30 @@ abstract class MapState with _$MapState { @override int get hashCode => bounds.hashCode; + MapState copyWith({ + LatLngBounds? bounds, + ThemeMode? themeMode, + bool? onlyFavorites, + bool? includeArchived, + bool? withPartners, + int? relativeDays, + }) { + return MapState( + bounds: bounds ?? this.bounds, + themeMode: themeMode ?? this.themeMode, + onlyFavorites: onlyFavorites ?? this.onlyFavorites, + includeArchived: includeArchived ?? this.includeArchived, + withPartners: withPartners ?? this.withPartners, + relativeDays: relativeDays ?? this.relativeDays, + ); + } + TimelineMapOptions toOptions() => TimelineMapOptions( bounds: bounds, onlyFavorites: onlyFavorites, includeArchived: includeArchived, withPartners: withPartners, relativeDays: relativeDays, - timeRange: timeRange, ); } @@ -68,58 +81,39 @@ class MapStateNotifier extends Notifier { } void switchFavoriteOnly(bool isFavoriteOnly) { - unawaited(ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); state = state.copyWith(onlyFavorites: isFavoriteOnly); EventStream.shared.emit(const MapMarkerReloadEvent()); } void switchIncludeArchived(bool isIncludeArchived) { - unawaited(ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived); state = state.copyWith(includeArchived: isIncludeArchived); EventStream.shared.emit(const MapMarkerReloadEvent()); } void switchWithPartners(bool isWithPartners) { - unawaited(ref.read(settingsProvider).write(.mapWithPartners, isWithPartners)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners); state = state.copyWith(withPartners: isWithPartners); EventStream.shared.emit(const MapMarkerReloadEvent()); } void setRelativeTime(int relativeDays) { - unawaited(ref.read(settingsProvider).write(.mapRelativeDate, relativeDays)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeDays); state = state.copyWith(relativeDays: relativeDays); EventStream.shared.emit(const MapMarkerReloadEvent()); } - void setCustomTimeRange(TimeRange range) { - unawaited(ref.read(settingsProvider).write(.mapCustomFrom, range.from)); - unawaited(ref.read(settingsProvider).write(.mapCustomTo, range.to)); - state = state.copyWith(timeRange: range); - EventStream.shared.emit(const MapMarkerReloadEvent()); - } - - Option parseDateOption(String s) { - try { - if (s.trim().isEmpty) { - return const Option.none(); - } - return Option.some(DateTime.parse(s)); - } catch (_) { - return const Option.none(); - } - } - @override MapState build() { - final mapConfig = ref.read(appConfigProvider.select((config) => config.map)); + final appSettingsService = ref.read(appSettingsServiceProvider); return MapState( - themeMode: mapConfig.themeMode, - onlyFavorites: mapConfig.favoritesOnly, - includeArchived: mapConfig.includeArchived, - withPartners: mapConfig.withPartners, - relativeDays: mapConfig.relativeDays, + themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)], + onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly), + includeArchived: appSettingsService.getSetting(AppSettingsEnum.mapIncludeArchived), + withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners), + relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate), bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)), - timeRange: TimeRange(from: mapConfig.customFrom, to: mapConfig.customTo), ); } } diff --git a/mobile/lib/presentation/widgets/map/map.widget.dart b/mobile/lib/presentation/widgets/map/map.widget.dart index ab2db23d8b..72f4e8bda6 100644 --- a/mobile/lib/presentation/widgets/map/map.widget.dart +++ b/mobile/lib/presentation/widgets/map/map.widget.dart @@ -10,12 +10,12 @@ import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/presentation/widgets/map/map_utils.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -53,7 +53,6 @@ class _DriftMapState extends ConsumerState { final _reloadMutex = AsyncMutex(); final _debouncer = Debouncer(interval: const Duration(milliseconds: 500), maxWaitTime: const Duration(seconds: 2)); final ValueNotifier bottomSheetOffset = ValueNotifier(0.25); - final GlobalKey _bottomSheetKey = GlobalKey(); StreamSubscription? _eventSubscription; @override @@ -66,7 +65,7 @@ class _DriftMapState extends ConsumerState { void dispose() { _debouncer.dispose(); bottomSheetOffset.dispose(); - unawaited(_eventSubscription?.cancel()); + _eventSubscription?.cancel(); super.dispose(); } @@ -132,7 +131,8 @@ class _DriftMapState extends ConsumerState { // When the AssetViewer is open, the DriftMap route stays alive in the background. // If we continue to update bounds, the map-scoped timeline service gets recreated and the previous one disposed, // which can invalidate the TimelineService instance that was passed into AssetViewerRoute (causing "loading forever"). - if (ref.read(isAssetViewerOpenProvider)) { + final currentRoute = ref.read(currentRouteNameProvider); + if (currentRoute == AssetViewerRoute.name || currentRoute == GalleryViewerRoute.name) { return; } @@ -164,7 +164,7 @@ class _DriftMapState extends ConsumerState { context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: context.t.map_cannot_get_user_location, + msg: "map_cannot_get_user_location".t(context: context), ); } return; @@ -181,15 +181,10 @@ class _DriftMapState extends ConsumerState { @override Widget build(BuildContext context) { - ref.listen(isAssetViewerOpenProvider, (previous, current) { - if (previous == true && !current) { - _debouncer.run(() => setBounds(forceReload: true)); - } - }); return Stack( children: [ _Map(initialLocation: widget.initialLocation, onMapCreated: onMapCreated, onMapReady: onMapReady), - _DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset, sheetKey: _bottomSheetKey), + _DynamicBottomSheet(bottomSheetOffset: bottomSheetOffset), _DynamicMyLocationButton(onZoomToLocation: onZoomToLocation, bottomSheetOffset: bottomSheetOffset), ], ); @@ -229,9 +224,8 @@ class _Map extends StatelessWidget { class _DynamicBottomSheet extends StatefulWidget { final ValueNotifier bottomSheetOffset; - final GlobalKey sheetKey; - const _DynamicBottomSheet({required this.bottomSheetOffset, required this.sheetKey}); + const _DynamicBottomSheet({required this.bottomSheetOffset}); @override State<_DynamicBottomSheet> createState() => _DynamicBottomSheetState(); @@ -242,13 +236,10 @@ class _DynamicBottomSheetState extends State<_DynamicBottomSheet> { Widget build(BuildContext context) { return NotificationListener( onNotification: (notification) { - final sheet = notification.context.findAncestorWidgetOfExactType(); - if (sheet?.key == widget.sheetKey) { - widget.bottomSheetOffset.value = notification.extent; - } - return false; + widget.bottomSheetOffset.value = notification.extent; + return true; }, - child: MapBottomSheet(sheetKey: widget.sheetKey), + child: const MapBottomSheet(), ); } } diff --git a/mobile/lib/presentation/widgets/map/map_settings_sheet.dart b/mobile/lib/presentation/widgets/map/map_settings_sheet.dart index c2a5678ee9..c581dd6292 100644 --- a/mobile/lib/presentation/widgets/map/map_settings_sheet.dart +++ b/mobile/lib/presentation/widgets/map/map_settings_sheet.dart @@ -1,38 +1,21 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/time_range.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; -import 'package:immich_mobile/widgets/map/map_settings/map_custom_time_range.dart'; import 'package:immich_mobile/widgets/map/map_settings/map_settings_list_tile.dart'; import 'package:immich_mobile/widgets/map/map_settings/map_settings_time_dropdown.dart'; import 'package:immich_mobile/widgets/map/map_settings/map_theme_picker.dart'; -class DriftMapSettingsSheet extends ConsumerStatefulWidget { +class DriftMapSettingsSheet extends HookConsumerWidget { const DriftMapSettingsSheet({super.key}); @override - ConsumerState createState() => _DriftMapSettingsSheetState(); -} - -class _DriftMapSettingsSheetState extends ConsumerState { - late bool useCustomRange; - - @override - void initState() { - super.initState(); - final mapState = ref.read(mapStateProvider); - final timeRange = mapState.timeRange; - useCustomRange = timeRange.from != null || timeRange.to != null; - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final mapState = ref.watch(mapStateProvider); return DraggableScrollableSheet( expand: false, - initialChildSize: useCustomRange ? 0.7 : 0.6, + initialChildSize: 0.6, builder: (ctx, scrollController) => SingleChildScrollView( controller: scrollController, child: Card( @@ -49,56 +32,25 @@ class _DriftMapSettingsSheetState extends ConsumerState { ), const Divider(height: 30, thickness: 1), MapSettingsListTile( - title: context.t.map_settings_only_show_favorites, + title: "map_settings_only_show_favorites".t(context: context), selected: mapState.onlyFavorites, onChanged: (favoriteOnly) => ref.read(mapStateProvider.notifier).switchFavoriteOnly(favoriteOnly), ), MapSettingsListTile( - title: context.t.map_settings_include_show_archived, + title: "map_settings_include_show_archived".t(context: context), selected: mapState.includeArchived, onChanged: (includeArchive) => ref.read(mapStateProvider.notifier).switchIncludeArchived(includeArchive), ), MapSettingsListTile( - title: context.t.map_settings_include_show_partners, + title: "map_settings_include_show_partners".t(context: context), selected: mapState.withPartners, onChanged: (withPartners) => ref.read(mapStateProvider.notifier).switchWithPartners(withPartners), ), - if (useCustomRange) ...[ - MapTimeRange( - timeRange: mapState.timeRange, - onChanged: (range) { - ref.read(mapStateProvider.notifier).setCustomTimeRange(range); - }, - ), - Align( - alignment: Alignment.centerLeft, - child: TextButton( - onPressed: () => setState(() { - useCustomRange = false; - ref.read(mapStateProvider.notifier).setRelativeTime(0); - ref.read(mapStateProvider.notifier).setCustomTimeRange(const TimeRange()); - }), - child: Text(context.t.remove_custom_date_range), - ), - ), - ] else ...[ - MapTimeDropDown( - relativeTime: mapState.relativeDays, - onTimeChange: (time) => ref.read(mapStateProvider.notifier).setRelativeTime(time), - ), - Align( - alignment: Alignment.centerLeft, - child: TextButton( - onPressed: () => setState(() { - useCustomRange = true; - ref.read(mapStateProvider.notifier).setRelativeTime(0); - ref.read(mapStateProvider.notifier).setCustomTimeRange(const TimeRange()); - }), - child: Text(context.t.use_custom_date_range), - ), - ), - ], + MapTimeDropDown( + relativeTime: mapState.relativeDays, + onTimeChange: (time) => ref.read(mapStateProvider.notifier).setRelativeTime(time), + ), const SizedBox(height: 20), ], ), diff --git a/mobile/lib/presentation/widgets/map/map_utils.dart b/mobile/lib/presentation/widgets/map/map_utils.dart index 3ed6ceee25..80df5995b6 100644 --- a/mobile/lib/presentation/widgets/map/map_utils.dart +++ b/mobile/lib/presentation/widgets/map/map_utils.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:logging/logging.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -71,11 +71,7 @@ class MapUtils { bool silent = false, }) async { try { - final serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!context.mounted) { - return (null, LocationPermission.unableToDetermine); - } - + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled && !silent) { unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog(context))); return (null, LocationPermission.deniedForever); @@ -85,10 +81,6 @@ class MapUtils { bool shouldRequestPermission = false; if (permission == LocationPermission.denied && !silent) { - if (!context.mounted) { - return (null, LocationPermission.unableToDetermine); - } - shouldRequestPermission = await showDialog( context: context, builder: (context) => _LocationPermissionDisabledDialog(context), @@ -106,7 +98,7 @@ class MapUtils { return (null, LocationPermission.deniedForever); } - final Position currentUserLocation = await Geolocator.getCurrentPosition( + Position currentUserLocation = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 0, @@ -124,10 +116,10 @@ class MapUtils { class _LocationServiceDisabledDialog extends ConfirmDialog { _LocationServiceDisabledDialog(BuildContext context) : super( - title: context.t.map_location_service_disabled_title, - content: context.t.map_location_service_disabled_content, - cancel: context.t.cancel, - ok: context.t.yes, + title: 'map_location_service_disabled_title'.t(context: context), + content: 'map_location_service_disabled_content'.t(context: context), + cancel: 'cancel'.t(context: context), + ok: 'yes'.t(context: context), onOk: () async { await Geolocator.openLocationSettings(); }, @@ -137,10 +129,10 @@ class _LocationServiceDisabledDialog extends ConfirmDialog { class _LocationPermissionDisabledDialog extends ConfirmDialog { _LocationPermissionDisabledDialog(BuildContext context) : super( - title: context.t.map_no_location_permission_title, - content: context.t.map_no_location_permission_content, - cancel: context.t.cancel, - ok: context.t.yes, + title: 'map_no_location_permission_title'.t(context: context), + content: 'map_no_location_permission_content'.t(context: context), + cancel: 'cancel'.t(context: context), + ok: 'yes'.t(context: context), onOk: () {}, ); } diff --git a/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart b/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart index eb8ce1815a..b514f9f0a5 100644 --- a/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_bottom_info.widget.dart @@ -1,11 +1,12 @@ +// ignore_for_file: require_trailing_commas + import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:intl/intl.dart'; class DriftMemoryBottomInfo extends StatelessWidget { final DriftMemory memory; @@ -29,18 +30,19 @@ class DriftMemoryBottomInfo extends StatelessWidget { style: TextStyle(color: Colors.grey[400], fontSize: 13.0, fontWeight: FontWeight.w500), ), Text( - df.format(fileCreatedDate.toLocal()), + df.format(fileCreatedDate), style: const TextStyle(color: Colors.white, fontSize: 15.0, fontWeight: FontWeight.w500), ), ], ), Tooltip( - message: context.t.view_in_timeline, + message: 'view_in_timeline'.tr(), child: MaterialButton( minWidth: 0, onPressed: () async { - await context.router.navigate(const TabShellRoute(children: [MainTimelineRoute()])); - EventStream.shared.emit(ScrollToDateEvent(fileCreatedDate.toLocal())); + await context.maybePop(); + await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); + EventStream.shared.emit(ScrollToDateEvent(fileCreatedDate)); }, shape: const CircleBorder(), color: Colors.white.withValues(alpha: 0.2), diff --git a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart index 7e782a3db4..3df9c8074e 100644 --- a/mobile/lib/presentation/widgets/memory/memory_card.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_card.widget.dart @@ -54,9 +54,7 @@ class DriftMemoryCard extends StatelessWidget { } } - if (asset.isImage) { - return FullImage(asset, fit: fit, size: Size.infinite); - } + if (asset.isImage) return FullImage(asset, fit: fit, size: const Size(double.infinity, double.infinity)); return Center( child: AspectRatio( @@ -97,7 +95,7 @@ class _BlurredBackdrop extends HookWidget { final blurhash = useDriftBlurHashRef(asset).value; if (blurhash != null) { // Use a nice cheap blur hash image decoration - return DecoratedBox( + return Container( decoration: BoxDecoration( image: DecorationImage(image: MemoryImage(blurhash), fit: BoxFit.cover), ), @@ -109,7 +107,7 @@ class _BlurredBackdrop extends HookWidget { // safely use that as the image provider return ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), - child: DecoratedBox( + child: Container( decoration: BoxDecoration( image: DecorationImage( image: getFullImageProvider(asset, size: Size(context.width, context.height)), diff --git a/mobile/lib/presentation/widgets/memory/memory_lane.widget.dart b/mobile/lib/presentation/widgets/memory/memory_lane.widget.dart index 1732249523..62889b10cb 100644 --- a/mobile/lib/presentation/widgets/memory/memory_lane.widget.dart +++ b/mobile/lib/presentation/widgets/memory/memory_lane.widget.dart @@ -1,10 +1,8 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/pages/drift_memory.page.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; @@ -35,7 +33,7 @@ class DriftMemoryLane extends ConsumerWidget { if (memories[index].assets.isNotEmpty) { DriftMemoryPage.setMemory(ref, memories[index]); } - unawaited(context.pushRoute(DriftMemoryRoute(memories: memories, memoryIndex: index))); + context.pushRoute(DriftMemoryRoute(memories: memories, memoryIndex: index)); }, children: memories .map((memory) => DriftMemoryCard(key: Key(memory.id), memory: memory)) @@ -45,15 +43,15 @@ class DriftMemoryLane extends ConsumerWidget { } } -class DriftMemoryCard extends StatelessWidget { +class DriftMemoryCard extends ConsumerWidget { const DriftMemoryCard({super.key, required this.memory}); final DriftMemory memory; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final yearsAgo = DateTime.now().year - memory.data.year; - final title = context.t.years_ago(years: yearsAgo); + final title = 'years_ago'.t(context: context, args: {'years': yearsAgo.toString()}); return Center( child: Stack( children: [ diff --git a/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart b/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart index 8618d78362..8b391d50c6 100644 --- a/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart +++ b/mobile/lib/presentation/widgets/people/partner_user_avatar.widget.dart @@ -1,19 +1,19 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; class PartnerUserAvatar extends StatelessWidget { - const PartnerUserAvatar({super.key, required this.userId, required this.name}); + const PartnerUserAvatar({super.key, required this.partner}); - final String userId; - final String name; + final PartnerUserDto partner; @override Widget build(BuildContext context) { - final url = "${Store.get(StoreKey.serverEndpoint)}/users/$userId/profile-image"; - final nameFirstLetter = name.isNotEmpty ? name[0] : ""; + final url = "${Store.get(StoreKey.serverEndpoint)}/users/${partner.id}/profile-image"; + final nameFirstLetter = partner.name.isNotEmpty ? partner.name[0] : ""; return CircleAvatar( radius: 16, backgroundColor: context.primaryColor.withAlpha(50), diff --git a/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart b/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart index e56a21e021..7ed02af26b 100644 --- a/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart +++ b/mobile/lib/presentation/widgets/people/person_edit_birthday_modal.widget.dart @@ -4,14 +4,14 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:scroll_date_picker/scroll_date_picker.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; class DriftPersonBirthdayEditForm extends ConsumerStatefulWidget { - final Person person; + final DriftPerson person; const DriftPersonBirthdayEditForm({super.key, required this.person}); @@ -28,23 +28,24 @@ class _DriftPersonNameEditFormState extends ConsumerState saveBirthday() async { + void saveBirthday() async { try { - final result = await ref.read(driftPeopleServiceProvider).updateBirthday(widget.person.id, _selectedDate); + final result = await ref.read(driftPeopleServiceProvider).updateBrithday(widget.person.id, _selectedDate); - if (result != 0 && mounted) { + if (result != 0) { + ref.invalidate(driftGetAllPeopleProvider); context.pop(_selectedDate); } } catch (error) { dPrint(() => 'Error updating birthday: $error'); - if (!mounted) { + if (!context.mounted) { return; } ImmichToast.show( context: context, - msg: context.t.scaffold_body_error_occurred, + msg: 'scaffold_body_error_occurred'.t(context: context), gravity: ToastGravity.BOTTOM, toastType: ToastType.error, ); @@ -54,14 +55,16 @@ class _DriftPersonNameEditFormState extends ConsumerState context.pop(null), child: Text( - context.t.cancel, + "cancel", style: TextStyle(color: Colors.red[300], fontWeight: FontWeight.bold), - ), + ).tr(), ), TextButton( onPressed: () => saveBirthday(), child: Text( - context.t.save, + "save", style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), - ), + ).tr(), ), ], ); } } - -List? datePickerColumnOrder(String? pattern) { - if (pattern == null) { - return null; - } - final positions = { - DatePickerViewType.year: pattern.indexOf('y'), - DatePickerViewType.month: pattern.indexOf('M'), - DatePickerViewType.day: pattern.indexOf('d'), - }; - if (positions.values.any((position) => position < 0)) { - return null; - } - return positions.keys.toList()..sort((a, b) => positions[a]!.compareTo(positions[b]!)); -} diff --git a/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart b/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart index 4ae41bc640..6de19000e0 100644 --- a/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart +++ b/mobile/lib/presentation/widgets/people/person_edit_name_modal.widget.dart @@ -1,15 +1,16 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; class DriftPersonNameEditForm extends ConsumerStatefulWidget { - final Person person; + final DriftPerson person; const DriftPersonNameEditForm({super.key, required this.person}); @@ -26,22 +27,23 @@ class _DriftPersonNameEditFormState extends ConsumerState onEdit(String personId, String newName) async { + void onEdit(String personId, String newName) async { try { final result = await ref.read(driftPeopleServiceProvider).updateName(personId, newName); - if (result != 0 && mounted) { + if (result != 0) { + ref.invalidate(driftGetAllPeopleProvider); context.pop(newName); } } catch (error) { dPrint(() => 'Error updating name: $error'); - if (!mounted) { + if (!context.mounted) { return; } ImmichToast.show( context: context, - msg: context.t.scaffold_body_error_occurred, + msg: 'scaffold_body_error_occurred'.t(context: context), gravity: ToastGravity.BOTTOM, toastType: ToastType.error, ); @@ -51,29 +53,29 @@ class _DriftPersonNameEditFormState extends ConsumerState context.pop(null), child: Text( - context.t.cancel, + "cancel", style: TextStyle(color: Colors.red[300], fontWeight: FontWeight.bold), - ), + ).tr(), ), TextButton( onPressed: () => onEdit(widget.person.id, _formController.text), child: Text( - context.t.save, + "save", style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/presentation/widgets/people/person_option_sheet.widget.dart b/mobile/lib/presentation/widgets/people/person_option_sheet.widget.dart index 026a28bd7d..b374d48417 100644 --- a/mobile/lib/presentation/widgets/people/person_option_sheet.widget.dart +++ b/mobile/lib/presentation/widgets/people/person_option_sheet.widget.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; -class PersonOptionSheet extends StatelessWidget { +class PersonOptionSheet extends ConsumerWidget { const PersonOptionSheet({super.key, this.onEditName, this.onEditBirthday, this.birthdayExists = false}); final VoidCallback? onEditName; @@ -9,8 +10,8 @@ class PersonOptionSheet extends StatelessWidget { final bool birthdayExists; @override - Widget build(BuildContext context) { - final TextStyle textStyle = Theme.of(context).textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w600); + Widget build(BuildContext context, WidgetRef ref) { + TextStyle textStyle = Theme.of(context).textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w600); return SafeArea( child: Padding( @@ -20,12 +21,12 @@ class PersonOptionSheet extends StatelessWidget { children: [ ListTile( leading: const Icon(Icons.edit), - title: Text(context.t.edit_name, style: textStyle), + title: Text('edit_name'.t(context: context), style: textStyle), onTap: onEditName, ), ListTile( leading: const Icon(Icons.cake), - title: Text(birthdayExists ? context.t.edit_birthday : context.t.add_birthday, style: textStyle), + title: Text((birthdayExists ? 'edit_birthday' : "add_birthday").t(context: context), style: textStyle), onTap: onEditBirthday, ), ], diff --git a/mobile/lib/presentation/widgets/remote_album/drift_album_option.widget.dart b/mobile/lib/presentation/widgets/remote_album/drift_album_option.widget.dart index dc89a25788..355e1a01a8 100644 --- a/mobile/lib/presentation/widgets/remote_album/drift_album_option.widget.dart +++ b/mobile/lib/presentation/widgets/remote_album/drift_album_option.widget.dart @@ -1,9 +1,10 @@ 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/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; -class DriftRemoteAlbumOption extends StatelessWidget { +class DriftRemoteAlbumOption extends ConsumerWidget { const DriftRemoteAlbumOption({ super.key, this.onAddPhotos, @@ -30,20 +31,25 @@ class DriftRemoteAlbumOption extends StatelessWidget { final List? iconShadows; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final theme = context.themeData; final menuChildren = []; if (onEditAlbum != null) { menuChildren.add( - BaseActionButton(label: context.t.edit_album, iconData: Icons.edit, onPressed: onEditAlbum, menuItem: true), + BaseActionButton( + label: 'edit_album'.t(context: context), + iconData: Icons.edit, + onPressed: onEditAlbum, + menuItem: true, + ), ); } if (onAddPhotos != null) { menuChildren.add( BaseActionButton( - label: context.t.add_photos, + label: 'add_photos'.t(context: context), iconData: Icons.add_a_photo, onPressed: onAddPhotos, menuItem: true, @@ -54,7 +60,7 @@ class DriftRemoteAlbumOption extends StatelessWidget { if (onAddUsers != null) { menuChildren.add( BaseActionButton( - label: context.t.album_viewer_page_share_add_users, + label: 'album_viewer_page_share_add_users'.t(context: context), iconData: Icons.group_add, onPressed: onAddUsers, menuItem: true, @@ -65,7 +71,7 @@ class DriftRemoteAlbumOption extends StatelessWidget { if (onLeaveAlbum != null) { menuChildren.add( BaseActionButton( - label: context.t.leave_album, + label: 'leave_album'.t(context: context), iconData: Icons.person_remove_rounded, onPressed: onLeaveAlbum, menuItem: true, @@ -76,7 +82,7 @@ class DriftRemoteAlbumOption extends StatelessWidget { if (onToggleAlbumOrder != null) { menuChildren.add( BaseActionButton( - label: context.t.change_display_order, + label: 'change_display_order'.t(context: context), iconData: Icons.swap_vert_rounded, onPressed: onToggleAlbumOrder, menuItem: true, @@ -87,7 +93,7 @@ class DriftRemoteAlbumOption extends StatelessWidget { if (onCreateSharedLink != null) { menuChildren.add( BaseActionButton( - label: context.t.create_shared_link, + label: 'create_shared_link'.t(context: context), iconData: Icons.link, onPressed: onCreateSharedLink, menuItem: true, @@ -97,7 +103,12 @@ class DriftRemoteAlbumOption extends StatelessWidget { if (onShowOptions != null) { menuChildren.add( - BaseActionButton(label: context.t.options, iconData: Icons.settings, onPressed: onShowOptions, menuItem: true), + BaseActionButton( + label: 'options'.t(context: context), + iconData: Icons.settings, + onPressed: onShowOptions, + menuItem: true, + ), ); } @@ -105,7 +116,7 @@ class DriftRemoteAlbumOption extends StatelessWidget { menuChildren.add(const Divider(height: 1)); menuChildren.add( BaseActionButton( - label: context.t.delete_album, + label: 'delete_album'.t(context: context), iconData: Icons.delete, iconColor: context.isDarkTheme ? Colors.red[400] : Colors.red[800], onPressed: onDeleteAlbum, diff --git a/mobile/lib/presentation/widgets/search/quick_date_picker.dart b/mobile/lib/presentation/widgets/search/quick_date_picker.dart index 97ea163be8..09b1cee700 100644 --- a/mobile/lib/presentation/widgets/search/quick_date_picker.dart +++ b/mobile/lib/presentation/widgets/search/quick_date_picker.dart @@ -1,7 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:intl/intl.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; sealed class DateFilterInputModel { DateTimeRange asDateTimeRange(); @@ -13,9 +13,12 @@ sealed class DateFilterInputModel { if (date.end.difference(date.start).inHours < 24) { return DateFormat.yMMMd().format(date.start.toLocal()); } else { - return context.t.search_filter_date_interval( - start: DateFormat.yMMMd().format(date.start.toLocal()), - end: DateFormat.yMMMd().format(date.end.toLocal()), + return 'search_filter_date_interval'.t( + context: context, + args: { + "start": DateFormat.yMMMd().format(date.start.toLocal()), + "end": DateFormat.yMMMd().format(date.end.toLocal()), + }, ); } } @@ -35,7 +38,7 @@ class RecentMonthRangeFilter extends DateFilterInputModel { @override String asHumanReadable(BuildContext context) { - return context.t.last_months(count: monthDelta); + return 'last_months'.t(context: context, args: {"count": monthDelta.toString()}); } } @@ -59,7 +62,7 @@ class YearFilter extends DateFilterInputModel { @override String asHumanReadable(BuildContext context) { - return context.t.in_year(year: year); + return 'in_year'.tr(namedArgs: {"year": year.toString()}); } } @@ -118,13 +121,14 @@ class QuickDatePicker extends HookWidget { return null; } - Text _monthLabel(BuildContext context, int monthsFromNow) => Text(context.t.last_months(count: monthsFromNow)); + Text _monthLabel(BuildContext context, int monthsFromNow) => + const Text('last_months').t(context: context, args: {"count": monthsFromNow.toString()}); Widget _yearPicker(BuildContext context) { final size = MediaQuery.of(context).size; return Row( children: [ - Text(context.t.in_year_selector), + const Text("in_year_selector").tr(), const SizedBox(width: 15), Expanded( child: DropdownMenu( @@ -132,9 +136,7 @@ class QuickDatePicker extends HookWidget { menuStyle: MenuStyle(maximumSize: WidgetStateProperty.all(Size(size.width, size.height * 0.5))), dropdownMenuEntries: _recentYears.map((e) => DropdownMenuEntry(value: e, label: e.toString())).toList(), onSelected: (year) { - if (year == null) { - return; - } + if (year == null) return; onSelect(YearFilter(year)); }, ), @@ -154,7 +156,7 @@ class QuickDatePicker extends HookWidget { child: IgnorePointer( ignoring: true, child: RadioListTile( - title: Text(context.t.pick_custom_range), + title: const Text('pick_custom_range').tr(), subtitle: hasPreviousInput ? Text(currentInput!.asHumanReadable(context)) : null, secondary: hasPreviousInput ? const Icon(Icons.edit) : null, value: _QuickPickerType.custom, @@ -177,9 +179,7 @@ class QuickDatePicker extends HookWidget { child: SingleChildScrollView( child: RadioGroup( onChanged: (value) { - if (value == null) { - return; - } + if (value == null) return; final _ = switch (value) { _QuickPickerType.custom => onRequestPicker(), _QuickPickerType.last1Month => onSelect(RecentMonthRangeFilter(1)), diff --git a/mobile/lib/presentation/widgets/timeline/constants.dart b/mobile/lib/presentation/widgets/timeline/constants.dart index 86c44d4a7f..84892c79f7 100644 --- a/mobile/lib/presentation/widgets/timeline/constants.dart +++ b/mobile/lib/presentation/widgets/timeline/constants.dart @@ -9,5 +9,5 @@ const double kScrubberThumbHeight = 48.0; const Duration kTimelineScrubberFadeInDuration = Duration(milliseconds: 300); const Duration kTimelineScrubberFadeOutDuration = Duration(milliseconds: 800); -const Size kThumbnailResolution = Size.square(320); +const Size kThumbnailResolution = Size.square(320); // TODO: make the resolution vary based on actual tile size const kThumbnailDiskCacheSize = 1024 << 20; // 1GiB diff --git a/mobile/lib/presentation/widgets/timeline/fixed/row.dart b/mobile/lib/presentation/widgets/timeline/fixed/row.dart index 851cb5ebae..97067add24 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/row.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/row.dart @@ -62,11 +62,14 @@ class RenderFixedRow extends RenderBox RenderBoxContainerDefaultsMixin { RenderFixedRow({ List? children, - required this._height, - required this._widths, - required this._spacing, - required this._textDirection, - }) { + required double height, + required List widths, + required double spacing, + required TextDirection textDirection, + }) : _height = height, + _widths = widths, + _spacing = spacing, + _textDirection = textDirection { addAll(children); } @@ -74,9 +77,7 @@ class RenderFixedRow extends RenderBox double _height; set height(double value) { - if (_height == value) { - return; - } + if (_height == value) return; _height = value; markNeedsLayout(); } @@ -85,9 +86,7 @@ class RenderFixedRow extends RenderBox List _widths; set widths(List value) { - if (listEquals(_widths, value)) { - return; - } + if (listEquals(_widths, value)) return; _widths = value; markNeedsLayout(); } @@ -96,9 +95,7 @@ class RenderFixedRow extends RenderBox double _spacing; set spacing(double value) { - if (_spacing == value) { - return; - } + if (_spacing == value) return; _spacing = value; markNeedsLayout(); } @@ -107,9 +104,7 @@ class RenderFixedRow extends RenderBox TextDirection _textDirection; set textDirection(TextDirection value) { - if (_textDirection == value) { - return; - } + if (_textDirection == value) return; _textDirection = value; markNeedsLayout(); } diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 32bbffa95e..aa2112b8dd 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -53,18 +53,14 @@ class FixedSegment extends Segment { @override int getMinChildIndexForScrollOffset(double scrollOffset) { final adjustedOffset = scrollOffset - gridOffset; - if (!adjustedOffset.isFinite || adjustedOffset < 0) { - return firstIndex; - } + if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex; return gridIndex + (adjustedOffset / mainAxisExtend).floor(); } @override int getMaxChildIndexForScrollOffset(double scrollOffset) { final adjustedOffset = scrollOffset - gridOffset; - if (!adjustedOffset.isFinite || adjustedOffset < 0) { - return firstIndex; - } + if (!adjustedOffset.isFinite || adjustedOffset < 0) return firstIndex; return gridIndex + (adjustedOffset / mainAxisExtend).ceil() - 1; } @@ -107,9 +103,12 @@ class _FixedSegmentRow extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isScrubbing = ref.watch(timelineStateProvider.select((s) => s.isScrubbing)); - final timelineService = ref.watch(timelineServiceProvider); + final timelineService = ref.read(timelineServiceProvider); final isDynamicLayout = columnCount <= (context.isMobile ? 2 : 3); + if (isScrubbing) { + return _buildPlaceholder(context); + } if (timelineService.hasRange(assetIndex, assetCount)) { return _buildAssetRow( context, @@ -119,10 +118,6 @@ class _FixedSegmentRow extends ConsumerWidget { ); } - if (isScrubbing) { - return _buildPlaceholder(context); - } - return FutureBuilder>( future: timelineService.loadAssets(assetIndex, assetCount), builder: (context, snapshot) { @@ -144,6 +139,19 @@ class _FixedSegmentRow extends ConsumerWidget { TimelineService timelineService, bool isDynamicLayout, ) { + final children = [ + for (int i = 0; i < assets.length; i++) + TimelineAssetIndexWrapper( + assetIndex: assetIndex + i, + segmentIndex: 0, // For simplicity, using 0 for now + child: _AssetTileWidget( + key: ValueKey(Object.hash(assets[i].heroTag, assetIndex + i, timelineService.hashCode)), + asset: assets[i], + assetIndex: assetIndex + i, + ), + ), + ]; + final widths = List.filled(assets.length, tileHeight); if (isDynamicLayout) { @@ -154,12 +162,8 @@ class _FixedSegmentRow extends ConsumerWidget { // 0.5: width < mean - threshold // 1.5: width > mean + threshold final arConfiguration = aspectRatios.map((e) { - if (e - meanAspectRatio > 0.3) { - return 1.5; - } - if (e - meanAspectRatio < -0.3) { - return 0.5; - } + if (e - meanAspectRatio > 0.3) return 1.5; + if (e - meanAspectRatio < -0.3) return 0.5; return 1.0; }); @@ -173,20 +177,6 @@ class _FixedSegmentRow extends ConsumerWidget { } } - final children = [ - for (int i = 0; i < assets.length; i++) - TimelineAssetIndexWrapper( - assetIndex: assetIndex + i, - segmentIndex: 0, // For simplicity, using 0 for now - child: _AssetTileWidget( - key: ValueKey(Object.hash(assets[i].heroTag, assetIndex + i, timelineService.hashCode)), - asset: assets[i], - assetIndex: assetIndex + i, - size: Size(widths[i], tileHeight), - ), - ), - ]; - return TimelineDragRegion( child: TimelineRow( height: tileHeight, @@ -202,30 +192,18 @@ class _FixedSegmentRow extends ConsumerWidget { class _AssetTileWidget extends ConsumerWidget { final BaseAsset asset; final int assetIndex; - final Size size; - const _AssetTileWidget({super.key, required this.asset, required this.assetIndex, required this.size}); + const _AssetTileWidget({super.key, required this.asset, required this.assetIndex}); - Future _handleOnTap( - BuildContext ctx, - WidgetRef ref, - int assetIndex, - BaseAsset asset, - int? heroOffset, - Size remoteSize, - ) async { + Future _handleOnTap(BuildContext ctx, WidgetRef ref, int assetIndex, BaseAsset asset, int? heroOffset) async { final multiSelectState = ref.read(multiSelectProvider); if (multiSelectState.forceEnable || multiSelectState.isEnabled) { ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset); } else { await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); - if (!ctx.mounted) { - return; - } - ref.read(isPlayingMotionVideoProvider.notifier).playing = false; - AssetViewer.setAsset(ref, asset, thumbnailSize: remoteSize); + AssetViewer.setAsset(ref, asset); unawaited( ctx.pushRoute( AssetViewerRoute( @@ -256,34 +234,25 @@ class _AssetTileWidget extends ConsumerWidget { return false; } - // Iterate with `==` instead of `Set.contains` because `RemoteAsset.hashCode` - // includes `localId` while `==` does not — so the same server asset can - // hash to a different bucket when its `localId` differs (e.g., album-fetched - // copy has localId=null, merged-timeline copy has it populated). - return lockSelectionAssets.any((a) => a == asset); + return lockSelectionAssets.contains(asset); } @override Widget build(BuildContext context, WidgetRef ref) { - final remoteSize = size * MediaQuery.devicePixelRatioOf(context); - final heroOffset = TabsRouterScope.of(context)?.controller.activeIndex ?? 0; final lockSelection = _getLockSelectionStatus(ref); final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator)); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); - final showStackIndicator = ref.watch(timelineServiceProvider).origin != TimelineOrigin.trash; return RepaintBoundary( child: GestureDetector( - onTap: () => lockSelection ? null : _handleOnTap(context, ref, assetIndex, asset, heroOffset, remoteSize), + onTap: () => lockSelection ? null : _handleOnTap(context, ref, assetIndex, asset, heroOffset), onLongPress: () => lockSelection || isReadonlyModeEnabled ? null : _handleOnLongPress(ref, asset), child: ThumbnailTile( asset, - remoteSize: remoteSize, lockSelection: lockSelection, showStorageIndicator: showStorageIndicator, - showStackIndicator: showStackIndicator, heroOffset: heroOffset, ), ), diff --git a/mobile/lib/presentation/widgets/timeline/header.widget.dart b/mobile/lib/presentation/widgets/timeline/header.widget.dart index 433118d13c..3eff305251 100644 --- a/mobile/lib/presentation/widgets/timeline/header.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/header.widget.dart @@ -1,19 +1,16 @@ -import 'dart:async'; - import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -class TimelineHeader extends HookWidget { +class TimelineHeader extends HookConsumerWidget { final Bucket bucket; final HeaderType header; final double height; @@ -40,7 +37,7 @@ class TimelineHeader extends HookWidget { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { if (bucket is! TimeBucket || header == HeaderType.none) { return const SizedBox.shrink(); } @@ -108,7 +105,7 @@ class _BulkSelectIconButton extends ConsumerWidget { ? const SizedBox.shrink() : IconButton( onPressed: () { - unawaited(ref.read(multiSelectProvider.notifier).toggleBucketSelection(assetOffset, bucket.assetCount)); + ref.read(multiSelectProvider.notifier).toggleBucketSelection(assetOffset, bucket.assetCount); ref.read(hapticFeedbackProvider.notifier).heavyImpact(); }, icon: isAllSelected diff --git a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart index 635e358755..f0dfef571c 100644 --- a/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/scrubber.widget.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; @@ -15,8 +14,6 @@ import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:intl/intl.dart' hide TextDirection; -part 'scrubber.widget.freezed.dart'; - /// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged /// for quick navigation of the BoxScrollView. class Scrubber extends ConsumerStatefulWidget { @@ -41,6 +38,7 @@ class Scrubber extends ConsumerStatefulWidget { Scrubber({ super.key, + Key? scrollThumbKey, required this.layoutSegments, required this.timelineHeight, this.topPadding = 0, @@ -106,9 +104,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi late ScrollController _scrollController; double get _currentOffset { - if (_scrollController.hasClients != true) { - return 0.0; - } + if (_scrollController.hasClients != true) return 0.0; return _scrollController.offset * _scrubberHeight / _scrollController.position.maxScrollExtent; } @@ -154,7 +150,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi void _resetThumbTimer() { _fadeOutTimer?.cancel(); _fadeOutTimer = Timer(kTimelineScrubberFadeOutDuration, () { - unawaited(_thumbAnimationController.reverse()); + _thumbAnimationController.reverse(); _fadeOutTimer = null; }); } @@ -179,10 +175,10 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi if (notification is ScrollUpdateNotification) { _thumbTopOffset = _currentOffset; if (_labelAnimation.status != AnimationStatus.reverse) { - unawaited(_labelAnimationController.reverse()); + _labelAnimationController.reverse(); } if (_thumbAnimationController.status != AnimationStatus.forward) { - unawaited(_thumbAnimationController.forward()); + _thumbAnimationController.forward(); } } _resetThumbTimer(); @@ -212,7 +208,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi void _onDragStart(DragStartDetails _) { setState(() { _isDragging = true; - unawaited(_labelAnimationController.forward()); + _labelAnimationController.forward(); _fadeOutTimer?.cancel(); _lastLabel = null; }); @@ -223,12 +219,8 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi return; } - if (_scrubberHeight <= 0) { - return; - } - if (_thumbAnimationController.status != AnimationStatus.forward) { - unawaited(_thumbAnimationController.forward()); + _thumbAnimationController.forward(); } final dragPosition = _calculateDragPosition(details); @@ -346,7 +338,7 @@ class ScrubberState extends ConsumerState with TickerProviderStateMixi } void _onDragEnd(DragEndDetails _) { - unawaited(_labelAnimationController.reverse()); + _labelAnimationController.reverse(); setState(() { _isDragging = false; }); @@ -584,7 +576,9 @@ class _SlideFadeTransition extends StatelessWidget { final Animation _animation; final Widget _child; - const _SlideFadeTransition({required this._animation, required this._child}); + const _SlideFadeTransition({required Animation animation, required Widget child}) + : _animation = animation, + _child = child; @override Widget build(BuildContext context) { @@ -592,19 +586,32 @@ class _SlideFadeTransition extends StatelessWidget { animation: _animation, builder: (context, child) => _animation.value == 0.0 ? const SizedBox() : child!, child: SlideTransition( - position: Tween(begin: const Offset(0.3, 0.0), end: Offset.zero).animate(_animation), + position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(_animation), child: FadeTransition(opacity: _animation, child: _child), ), ); } } -@freezed -abstract class _Segment with _$Segment { - const factory _Segment({ - required DateTime date, - required double startOffset, - required String scrollLabel, - @Default(false) bool showSegment, - }) = __Segment; +class _Segment { + final DateTime date; + final double startOffset; + final String scrollLabel; + final bool showSegment; + + const _Segment({required this.date, required this.startOffset, required this.scrollLabel, this.showSegment = false}); + + _Segment copyWith({DateTime? date, double? startOffset, String? scrollLabel, bool? showSegment}) { + return _Segment( + date: date ?? this.date, + startOffset: startOffset ?? this.startOffset, + scrollLabel: scrollLabel ?? this.scrollLabel, + showSegment: showSegment ?? this.showSegment, + ); + } + + @override + String toString() { + return 'Segment(scrollLabel: $scrollLabel, date: $date)'; + } } diff --git a/mobile/lib/presentation/widgets/timeline/segment.model.dart b/mobile/lib/presentation/widgets/timeline/segment.model.dart index 99bf7b0a4d..bc5f974874 100644 --- a/mobile/lib/presentation/widgets/timeline/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/segment.model.dart @@ -52,9 +52,7 @@ abstract class Segment { @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; return other is Segment && other.firstIndex == firstIndex && diff --git a/mobile/lib/presentation/widgets/timeline/timeline.state.dart b/mobile/lib/presentation/widgets/timeline/timeline.state.dart index adad06b987..1e1d4130f7 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.state.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.state.dart @@ -1,37 +1,74 @@ import 'dart:math' as math; -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/constants.dart'; import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart'; import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -part 'timeline.state.freezed.dart'; +class TimelineArgs { + final double maxWidth; + final double maxHeight; + final double spacing; + final int columnCount; + final bool showStorageIndicator; + final bool withStack; + final GroupAssetsBy? groupBy; -@freezed -abstract class TimelineArgs with _$TimelineArgs { - const factory TimelineArgs({ - required double maxWidth, - required double maxHeight, - @Default(kTimelineSpacing) double spacing, - @Default(kTimelineColumnCount) int columnCount, - @Default(false) bool showStorageIndicator, - @Default(false) bool withStack, - GroupAssetsBy? groupBy, - }) = _TimelineArgs; + const TimelineArgs({ + required this.maxWidth, + required this.maxHeight, + this.spacing = kTimelineSpacing, + this.columnCount = kTimelineColumnCount, + this.showStorageIndicator = false, + this.withStack = false, + this.groupBy, + }); + + @override + bool operator ==(covariant TimelineArgs other) { + return spacing == other.spacing && + maxWidth == other.maxWidth && + maxHeight == other.maxHeight && + columnCount == other.columnCount && + showStorageIndicator == other.showStorageIndicator && + withStack == other.withStack && + groupBy == other.groupBy; + } + + @override + int get hashCode => + maxWidth.hashCode ^ + maxHeight.hashCode ^ + spacing.hashCode ^ + columnCount.hashCode ^ + showStorageIndicator.hashCode ^ + withStack.hashCode ^ + groupBy.hashCode; } -@freezed -abstract class TimelineState with _$TimelineState { - const TimelineState._(); +class TimelineState { + final bool isScrubbing; + final bool isScrolling; - const factory TimelineState({@Default(false) bool isScrubbing, @Default(false) bool isScrolling}) = _TimelineState; + const TimelineState({this.isScrubbing = false, this.isScrolling = false}); bool get isInteracting => isScrubbing || isScrolling; + + @override + bool operator ==(covariant TimelineState other) { + return isScrubbing == other.isScrubbing && isScrolling == other.isScrolling; + } + + @override + int get hashCode => isScrubbing.hashCode ^ isScrolling.hashCode; + + TimelineState copyWith({bool? isScrubbing, bool? isScrolling}) { + return TimelineState(isScrubbing: isScrubbing ?? this.isScrubbing, isScrolling: isScrolling ?? this.isScrolling); + } } class TimelineStateNotifier extends Notifier { @@ -50,14 +87,13 @@ class TimelineStateNotifier extends Notifier { // This provider watches the buckets from the timeline service & args and serves the segments. // It should be used only after the timeline service and timeline args provider is overridden final timelineSegmentProvider = StreamProvider.autoDispose>((ref) async* { - // maxHeight is left out on purpose, a height-only change must not restart the bucket stream - final (maxWidth, columnCount, spacing, groupByArg) = ref.watch( - timelineArgsProvider.select((args) => (args.maxWidth, args.columnCount, args.spacing, args.groupBy)), - ); - final availableTileWidth = maxWidth - (spacing * (columnCount - 1)); + final args = ref.watch(timelineArgsProvider); + final columnCount = args.columnCount; + final spacing = args.spacing; + final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1)); final tileExtent = math.max(0, availableTileWidth) / columnCount; - final groupBy = groupByArg ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy)); + final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)]; final timelineService = ref.watch(timelineServiceProvider); yield* timelineService.watchBuckets().map((buckets) { @@ -66,7 +102,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose>((ref) tileHeight: tileExtent, columnCount: columnCount, spacing: spacing, - groupBy: groupBy!, + groupBy: groupBy, ).generate(); }); }, dependencies: [timelineServiceProvider, timelineArgsProvider]); diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index 0387cbf597..8d494a8452 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:collection'; import 'dart:math' as math; -import 'package:auto_route/auto_route.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -11,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; @@ -23,15 +23,14 @@ import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/routing/app_navigation_observer.dart'; import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart'; import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart'; -class Timeline extends ConsumerWidget { +class Timeline extends StatelessWidget { const Timeline({ super.key, this.topSliverWidget, @@ -64,19 +63,18 @@ class Timeline extends ConsumerWidget { final Widget? loadingWidget; @override - Widget build(BuildContext context, WidgetRef ref) { - final columnCount = ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)); - return LayoutBuilder( - builder: (_, constraints) { - return ProviderScope( + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButton: const DownloadStatusFloatingButton(), + body: LayoutBuilder( + builder: (_, constraints) => ProviderScope( overrides: [ - // overrideWithValue keeps the scoped args in sync with the latest constraints on rebuilds, - // a function override would stay locked to the first frame's constraints for the whole session - timelineArgsProvider.overrideWithValue( - TimelineArgs( + timelineArgsProvider.overrideWith( + (ref) => TimelineArgs( maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight, - columnCount: columnCount, + columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))), showStorageIndicator: showStorageIndicator, withStack: withStack, groupBy: groupBy, @@ -96,8 +94,8 @@ class Timeline extends ConsumerWidget { maxWidth: constraints.maxWidth, loadingWidget: loadingWidget, ), - ); - }, + ), + ), ); } } @@ -142,7 +140,7 @@ class _SliverTimeline extends ConsumerStatefulWidget { ConsumerState createState() => _SliverTimelineState(); } -class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBindingObserver { +class _SliverTimelineState extends ConsumerState<_SliverTimeline> { late final ScrollController _scrollController; StreamSubscription? _eventSubscription; @@ -160,11 +158,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); _scrollController = ScrollController(onAttach: _restoreAssetPosition); _eventSubscription = EventStream.shared.listen(_onEvent); - final currentTilesPerRow = ref.read(appConfigProvider.select((config) => config.timeline.tilesPerRow)); + final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow); _perRow = currentTilesPerRow; _scaleFactor = 7.0 - _perRow; _baseScaleFactor = _scaleFactor; @@ -176,38 +173,25 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi void didUpdateWidget(covariant _SliverTimeline oldWidget) { super.didUpdateWidget(oldWidget); if (widget.maxWidth != oldWidget.maxWidth) { - // The updated args already regenerate the segments, only remember the scroll position to restore it afterwards - final segments = ref.read(timelineSegmentProvider).valueOrNull; - if (segments != null && _scrollController.hasClients) { - _restoreAssetIndex = _getCurrentAssetIndex(segments); - } - } - } - - // Capture iOS status bar tap - @override - void handleStatusBarTap() { - // Routes may be pushed non-opaquely on top of the timeline (such as the asset viewer), or the timeline - // may be in a background tab. In either case, `handleStatusBarTap()` still fires - // Make sure the timeline is the primary route before scrolling to the top - final routeData = context.findAncestorWidgetOfExactType()?.routeData; - // The tap is generated async, so it can arrive after a route pop has started (due to a back button or similar) - // Check if route is alive and not exiting before taking action - final observers = Navigator.maybeOf(context)?.widget.observers ?? const []; - final isRouteTransitioning = observers.whereType().any( - (observer) => observer.hasTransitioningRoute, - ); - - if (ModalRoute.of(context)?.isCurrent == true && routeData?.isActive == true && !isRouteTransitioning) { - _scrollToTop(); + final asyncSegments = ref.read(timelineSegmentProvider); + asyncSegments.whenData((segments) { + final index = _getCurrentAssetIndex(segments); + // Refresh to wait for new segments to be generated with the updated width before restoring the scroll position + final _ = ref.refresh(timelineArgsProvider); + _restoreAssetIndex = index; + }); } } void _onEvent(Event event) { switch (event) { case ScrollToTopEvent(): - _scrollToTop(); - case final ScrollToDateEvent scrollToDateEvent: + ref.read(timelineStateProvider.notifier).setScrubbing(true); + _scrollController + .animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut) + .whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false)); + + case ScrollToDateEvent scrollToDateEvent: _scrollToDate(scrollToDateEvent.date); case TimelineReloadEvent(): setState(() {}); @@ -217,9 +201,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi } void _restoreAssetPosition(_) { - if (_restoreAssetIndex == null) { - return; - } + if (_restoreAssetIndex == null) return; final asyncSegments = ref.read(timelineSegmentProvider); asyncSegments.whenData((segments) { @@ -264,28 +246,12 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi @override void dispose() { - WidgetsBinding.instance.removeObserver(this); _scrollController.dispose(); - unawaited(_eventSubscription?.cancel()); + _eventSubscription?.cancel(); super.dispose(); } - void _scrollToTop() { - if (!_scrollController.hasClients) { - return; - } - - final timelineState = ref.read(timelineStateProvider.notifier); - timelineState.setScrubbing(true); - unawaited( - _scrollController - .animateTo(0, duration: const Duration(milliseconds: 250), curve: Curves.easeInOut) - .whenComplete(() => timelineState.setScrubbing(false)), - ); - } - void _scrollToDate(DateTime date) { - final timelineState = ref.read(timelineStateProvider.notifier); final asyncSegments = ref.read(timelineSegmentProvider); asyncSegments.whenData((segments) { // Find the segment that contains assets from the target date @@ -312,18 +278,16 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi if (fallbackSegment != null) { // Scroll to the segment with a small offset to show the header final targetOffset = fallbackSegment.startOffset - 50; - timelineState.setScrubbing(true); - unawaited( - _scrollController - .animateTo( - targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent), - duration: const Duration(milliseconds: 500), - curve: Curves.easeInOut, - ) - .whenComplete(() => timelineState.setScrubbing(false)), - ); + ref.read(timelineStateProvider.notifier).setScrubbing(true); + _scrollController + .animateTo( + targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent), + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, + ) + .whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false)); } else { - timelineState.setScrubbing(false); + ref.read(timelineStateProvider.notifier).setScrubbing(false); } }); } @@ -340,24 +304,24 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi void _stopDrag() { WidgetsBinding.instance.addPostFrameCallback((_) { // Update the physics post frame to prevent sudden change in physics on iOS. - if (mounted) { - setState(() { - _scrollPhysics = null; - }); - } + setState(() { + _scrollPhysics = null; + }); }); setState(() { _dragging = false; _draggedAssets.clear(); }); - final timelineState = ref.read(timelineStateProvider.notifier); + // Reset the scrolling state after a small delay to allow bottom sheet to expand again Future.delayed(const Duration(milliseconds: 300), () { - timelineState.setScrolling(false); + if (mounted) { + ref.read(timelineStateProvider.notifier).setScrolling(false); + } }); } - Future _dragScroll(ScrollDirection direction) { - return _scrollController.animateTo( + void _dragScroll(ScrollDirection direction) { + _scrollController.animateTo( _scrollController.offset + (direction == ScrollDirection.forward ? 175 : -175), duration: const Duration(milliseconds: 125), curve: Curves.easeOut, @@ -365,9 +329,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi } void _handleDragAssetEnter(TimelineAssetIndex index) { - if (_dragAnchorIndex == null || !_dragging) { - return; - } + if (_dragAnchorIndex == null || !_dragging) return; final timelineService = ref.read(timelineServiceProvider); final dragAnchorIndex = _dragAnchorIndex!; @@ -408,134 +370,123 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi return PopScope( canPop: !isMultiSelectEnabled, - onPopInvokedWithResult: (_, _) { + onPopInvokedWithResult: (_, __) { if (isMultiSelectEnabled) { ref.read(multiSelectProvider.notifier).reset(); } }, - child: PrimaryScrollController( - controller: _scrollController, - child: Scaffold( - // This removes the built in Scaffold `handleStatusBarTap` implementation, preventing duplicate - // events when we provide our own - primary: false, - resizeToAvoidBottomInset: false, - floatingActionButton: const DownloadStatusFloatingButton(), - body: asyncSegments.widgetWhen( - onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, - onData: (segments) { - final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; - final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar - ? 200 - : 0; - final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; + child: asyncSegments.widgetWhen( + onLoading: widget.loadingWidget != null ? () => widget.loadingWidget! : null, + onData: (segments) { + final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; + final double appBarExpandedHeight = widget.appBar != null && widget.appBar is MesmerizingSliverAppBar + ? 200 + : 0; + final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10; - const bottomSheetOpenModifier = 120.0; - final contentBottomPadding = - context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); - final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; + const bottomSheetOpenModifier = 120.0; + final contentBottomPadding = context.padding.bottom + (isMultiSelectEnabled ? bottomSheetOpenModifier : 0); + final scrubberBottomPadding = contentBottomPadding + kScrubberThumbHeight; - final grid = CustomScrollView( - primary: true, - physics: _scrollPhysics, - scrollCacheExtent: .pixels(maxHeight * 2), - slivers: [ - if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, - if (widget.topSliverWidget != null) widget.topSliverWidget!, - _SliverSegmentedList( - segments: segments, - delegate: SliverChildBuilderDelegate( - (ctx, index) { - if (index >= childCount) { - return null; - } - final segment = segments.findByIndex(index); - return segment?.builder(ctx, index) ?? const SizedBox.shrink(); - }, - childCount: childCount, - addAutomaticKeepAlives: false, - // We add repaint boundary around tiles, so skip the auto boundaries - addRepaintBoundaries: false, - ), - ), - if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, - SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), - ], - ); - - final Widget timeline; - if (widget.withScrubber) { - timeline = Scrubber( - snapToMonth: widget.snapToMonth, - layoutSegments: segments, - timelineHeight: maxHeight, - topPadding: topPadding, - bottomPadding: scrubberBottomPadding, - monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, - hasAppBar: widget.appBar != null, - child: grid, - ); - } else { - timeline = grid; - } - - return RawGestureDetector( - gestures: { - CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( - () => CustomScaleGestureRecognizer(), - (CustomScaleGestureRecognizer scale) { - scale.onStart = (details) { - _baseScaleFactor = _scaleFactor; - }; - - scale.onUpdate = (details) { - final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); - final newPerRow = 7 - newScaleFactor.toInt(); - - if (newPerRow != _perRow) { - final targetAssetIndex = _getCurrentAssetIndex(segments); - setState(() { - _scaleFactor = newScaleFactor; - _perRow = newPerRow; - _restoreAssetIndex = targetAssetIndex; - }); - - unawaited(ref.read(settingsProvider).write(.timelineTilesPerRow, _perRow)); - } - }; - }, - ), - }, - child: TimelineDragRegion( - onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, - onAssetEnter: _handleDragAssetEnter, - onEnd: !isReadonlyModeEnabled ? _stopDrag : null, - onScroll: (direction) => unawaited(_dragScroll(direction)), - onScrollStart: () { - // Minimize the bottom sheet when drag selection starts - ref.read(timelineStateProvider.notifier).setScrolling(true); + final grid = CustomScrollView( + primary: true, + physics: _scrollPhysics, + cacheExtent: maxHeight * 2, + slivers: [ + if (isSelectionMode) const SelectionSliverAppBar() else if (widget.appBar != null) widget.appBar!, + if (widget.topSliverWidget != null) widget.topSliverWidget!, + _SliverSegmentedList( + segments: segments, + delegate: SliverChildBuilderDelegate( + (ctx, index) { + if (index >= childCount) return null; + final segment = segments.findByIndex(index); + return segment?.builder(ctx, index) ?? const SizedBox.shrink(); }, - child: Stack( - clipBehavior: Clip.none, - children: [ - timeline, - if (isBottomWidgetVisible) - Positioned( - top: MediaQuery.paddingOf(context).top, - left: 25, - child: const SizedBox( - height: kToolbarHeight, - child: Center(child: _MultiSelectStatusButton()), - ), - ), - if (isBottomWidgetVisible) widget.bottomSheet!, - ], - ), + childCount: childCount, + addAutomaticKeepAlives: false, + // We add repaint boundary around tiles, so skip the auto boundaries + addRepaintBoundaries: false, ), - ); - }, - ), - ), + ), + if (widget.bottomSliverWidget != null) widget.bottomSliverWidget!, + SliverPadding(padding: EdgeInsets.only(bottom: contentBottomPadding)), + ], + ); + + final Widget timeline; + if (widget.withScrubber) { + timeline = Scrubber( + snapToMonth: widget.snapToMonth, + layoutSegments: segments, + timelineHeight: maxHeight, + topPadding: topPadding, + bottomPadding: scrubberBottomPadding, + monthSegmentSnappingOffset: widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight, + hasAppBar: widget.appBar != null, + child: grid, + ); + } else { + timeline = grid; + } + + return PrimaryScrollController( + controller: _scrollController, + child: RawGestureDetector( + gestures: { + CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => CustomScaleGestureRecognizer(), + (CustomScaleGestureRecognizer scale) { + scale.onStart = (details) { + _baseScaleFactor = _scaleFactor; + }; + + scale.onUpdate = (details) { + final newScaleFactor = math.max(math.min(5.0, _baseScaleFactor * details.scale), 1.0); + final newPerRow = 7 - newScaleFactor.toInt(); + + if (newPerRow != _perRow) { + final targetAssetIndex = _getCurrentAssetIndex(segments); + setState(() { + _scaleFactor = newScaleFactor; + _perRow = newPerRow; + _restoreAssetIndex = targetAssetIndex; + }); + + ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow); + } + }; + }, + ), + }, + child: TimelineDragRegion( + onStart: !isReadonlyModeEnabled ? _setDragStartIndex : null, + onAssetEnter: _handleDragAssetEnter, + onEnd: !isReadonlyModeEnabled ? _stopDrag : null, + onScroll: _dragScroll, + onScrollStart: () { + // Minimize the bottom sheet when drag selection starts + ref.read(timelineStateProvider.notifier).setScrolling(true); + }, + child: Stack( + children: [ + timeline, + if (isBottomWidgetVisible) + Positioned( + top: MediaQuery.paddingOf(context).top, + left: 25, + child: const SizedBox( + height: kToolbarHeight, + child: Center(child: _MultiSelectStatusButton()), + ), + ), + if (isBottomWidgetVisible) widget.bottomSheet!, + ], + ), + ), + ), + ); + }, ), ); } @@ -544,7 +495,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> with WidgetsBi class _SliverSegmentedList extends SliverMultiBoxAdaptorWidget { final List _segments; - const _SliverSegmentedList({required this._segments, required super.delegate}); + const _SliverSegmentedList({required List segments, required super.delegate}) : _segments = segments; @override _RenderSliverTimelineBoxAdaptor createRenderObject(BuildContext context) => @@ -568,7 +519,8 @@ class _RenderSliverTimelineBoxAdaptor extends RenderSliverMultiBoxAdaptor { markNeedsLayout(); } - _RenderSliverTimelineBoxAdaptor({required super.childManager, required this._segments}); + _RenderSliverTimelineBoxAdaptor({required super.childManager, required List segments}) + : _segments = segments; int getMinChildIndexForScrollOffset(double offset) => _segments.findByOffset(offset)?.getMinChildIndexForScrollOffset(offset) ?? 0; diff --git a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart index 892d0c6102..88d46b143f 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline_drag_region.dart @@ -4,9 +4,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'timeline_drag_region.freezed.dart'; class TimelineDragRegion extends StatefulWidget { final Widget child; @@ -84,15 +81,11 @@ class _TimelineDragRegionState extends State { TimelineAssetIndex? _getValueKeyAtPosition(Offset position) { final box = context.findAncestorRenderObjectOfType(); - if (box == null) { - return null; - } + if (box == null) return null; final hitTestResult = BoxHitTestResult(); final local = box.globalToLocal(position); - if (!box.hitTest(hitTestResult, position: local)) { - return null; - } + if (!box.hitTest(hitTestResult, position: local)) return null; return (hitTestResult.path.firstWhereOrNull((hit) => hit.target is _TimelineAssetIndexProxy)?.target as _TimelineAssetIndexProxy?) @@ -110,9 +103,7 @@ class _TimelineDragRegionState extends State { final initialHit = _getValueKeyAtPosition(event.globalPosition); anchorAsset = initialHit; - if (initialHit == null) { - return; - } + if (initialHit == null) return; if (anchorAsset != null) { widget.onStart?.call(anchorAsset!); @@ -126,12 +117,8 @@ class _TimelineDragRegionState extends State { } void _onLongPressMove(LongPressMoveUpdateDetails event) { - if (anchorAsset == null) { - return; - } - if (topScrollOffset == null || bottomScrollOffset == null) { - return; - } + if (anchorAsset == null) return; + if (topScrollOffset == null || bottomScrollOffset == null) return; final currentDy = event.localPosition.dy; @@ -151,9 +138,7 @@ class _TimelineDragRegionState extends State { } final currentlyTouchingAsset = _getValueKeyAtPosition(event.globalPosition); - if (currentlyTouchingAsset == null) { - return; - } + if (currentlyTouchingAsset == null) return; if (assetUnderPointer != currentlyTouchingAsset) { if (!scrollNotified) { @@ -209,7 +194,19 @@ class _TimelineAssetIndexProxy extends RenderProxyBox { _TimelineAssetIndexProxy({required this.index}); } -@freezed -abstract class TimelineAssetIndex with _$TimelineAssetIndex { - const factory TimelineAssetIndex({required int assetIndex, required int segmentIndex}) = _TimelineAssetIndex; +class TimelineAssetIndex { + final int assetIndex; + final int segmentIndex; + + const TimelineAssetIndex({required this.assetIndex, required this.segmentIndex}); + + @override + bool operator ==(covariant TimelineAssetIndex other) { + if (identical(this, other)) return true; + + return other.assetIndex == assetIndex && other.segmentIndex == segmentIndex; + } + + @override + int get hashCode => assetIndex.hashCode ^ segmentIndex.hashCode; } diff --git a/mobile/lib/providers/activity.provider.dart b/mobile/lib/providers/activity.provider.dart index b2cdbcf18c..5e0e71d85d 100644 --- a/mobile/lib/providers/activity.provider.dart +++ b/mobile/lib/providers/activity.provider.dart @@ -1,22 +1,17 @@ import 'package:collection/collection.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/activities/activity.model.dart'; import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:immich_mobile/providers/activity_statistics.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'activity.provider.g.dart'; // ignore: unintended_html_in_doc_comment /// Maintains the current list of all activities for - -final albumActivityProvider = AsyncNotifierProvider.autoDispose - .family, (String albumId, String? assetId)>(AlbumActivity.new); - -class AlbumActivity extends AutoDisposeFamilyAsyncNotifier, (String albumId, String? assetId)> { - late String albumId; - late String? assetId; - +@riverpod +class AlbumActivity extends _$AlbumActivity { @override - Future> build((String albumId, String? assetId) args) async { - albumId = args.$1; - assetId = args.$2; + Future> build(String albumId, [String? assetId]) async { return ref.watch(activityServiceProvider).getAllActivities(albumId, assetId: assetId); } @@ -28,7 +23,14 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier, (Stri } if (assetId != null) { - ref.read(albumActivityProvider((albumId, assetId)).notifier)._removeFromState(id); + ref.read(albumActivityProvider(albumId).notifier)._removeFromState(id); + } + + if (removedActivity.type == ActivityType.comment) { + ref.watch(activityStatisticsProvider(albumId, assetId).notifier).removeActivity(); + if (assetId != null) { + ref.watch(activityStatisticsProvider(albumId).notifier).removeActivity(); + } } } } @@ -38,7 +40,7 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier, (Stri if (activity.hasValue) { _addToState(activity.requireValue); if (assetId != null) { - ref.read(albumActivityProvider((albumId, assetId)).notifier)._addToState(activity.requireValue); + ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue); } } } @@ -51,7 +53,13 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier, (Stri if (activity.hasValue) { _addToState(activity.requireValue); if (assetId != null) { - ref.read(albumActivityProvider((albumId, assetId)).notifier)._addToState(activity.requireValue); + ref.read(albumActivityProvider(albumId).notifier)._addToState(activity.requireValue); + } + ref.watch(activityStatisticsProvider(albumId, assetId).notifier).addActivity(); + // The previous addActivity call would increase the count of an asset if assetId != null + // To also increase the activity count of the album, calling it once again with assetId set to null + if (assetId != null) { + ref.watch(activityStatisticsProvider(albumId).notifier).addActivity(); } } } @@ -79,3 +87,6 @@ class AlbumActivity extends AutoDisposeFamilyAsyncNotifier, (Stri return activity; } } + +/// Mock class for testing +abstract class AlbumActivityInternal extends _$AlbumActivity {} diff --git a/mobile/lib/providers/activity.provider.g.dart b/mobile/lib/providers/activity.provider.g.dart new file mode 100644 index 0000000000..6ca99e4f72 --- /dev/null +++ b/mobile/lib/providers/activity.provider.g.dart @@ -0,0 +1,194 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$albumActivityHash() => r'154e8ae98da3efc142369eae46d4005468fd67da'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$AlbumActivity + extends BuildlessAutoDisposeAsyncNotifier> { + late final String albumId; + late final String? assetId; + + FutureOr> build(String albumId, [String? assetId]); +} + +/// Maintains the current list of all activities for +/// +/// Copied from [AlbumActivity]. +@ProviderFor(AlbumActivity) +const albumActivityProvider = AlbumActivityFamily(); + +/// Maintains the current list of all activities for +/// +/// Copied from [AlbumActivity]. +class AlbumActivityFamily extends Family>> { + /// Maintains the current list of all activities for + /// + /// Copied from [AlbumActivity]. + const AlbumActivityFamily(); + + /// Maintains the current list of all activities for + /// + /// Copied from [AlbumActivity]. + AlbumActivityProvider call(String albumId, [String? assetId]) { + return AlbumActivityProvider(albumId, assetId); + } + + @override + AlbumActivityProvider getProviderOverride( + covariant AlbumActivityProvider provider, + ) { + return call(provider.albumId, provider.assetId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'albumActivityProvider'; +} + +/// Maintains the current list of all activities for +/// +/// Copied from [AlbumActivity]. +class AlbumActivityProvider + extends + AutoDisposeAsyncNotifierProviderImpl> { + /// Maintains the current list of all activities for + /// + /// Copied from [AlbumActivity]. + AlbumActivityProvider(String albumId, [String? assetId]) + : this._internal( + () => AlbumActivity() + ..albumId = albumId + ..assetId = assetId, + from: albumActivityProvider, + name: r'albumActivityProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$albumActivityHash, + dependencies: AlbumActivityFamily._dependencies, + allTransitiveDependencies: + AlbumActivityFamily._allTransitiveDependencies, + albumId: albumId, + assetId: assetId, + ); + + AlbumActivityProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.albumId, + required this.assetId, + }) : super.internal(); + + final String albumId; + final String? assetId; + + @override + FutureOr> runNotifierBuild(covariant AlbumActivity notifier) { + return notifier.build(albumId, assetId); + } + + @override + Override overrideWith(AlbumActivity Function() create) { + return ProviderOverride( + origin: this, + override: AlbumActivityProvider._internal( + () => create() + ..albumId = albumId + ..assetId = assetId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + albumId: albumId, + assetId: assetId, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement> + createElement() { + return _AlbumActivityProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AlbumActivityProvider && + other.albumId == albumId && + other.assetId == assetId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, albumId.hashCode); + hash = _SystemHash.combine(hash, assetId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin AlbumActivityRef on AutoDisposeAsyncNotifierProviderRef> { + /// The parameter `albumId` of this provider. + String get albumId; + + /// The parameter `assetId` of this provider. + String? get assetId; +} + +class _AlbumActivityProviderElement + extends + AutoDisposeAsyncNotifierProviderElement> + with AlbumActivityRef { + _AlbumActivityProviderElement(super.provider); + + @override + String get albumId => (origin as AlbumActivityProvider).albumId; + @override + String? get assetId => (origin as AlbumActivityProvider).assetId; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/activity_service.provider.dart b/mobile/lib/providers/activity_service.provider.dart index 3be6c6b234..f17617bced 100644 --- a/mobile/lib/providers/activity_service.provider.dart +++ b/mobile/lib/providers/activity_service.provider.dart @@ -3,11 +3,13 @@ import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/services/activity.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final activityServiceProvider = Provider.autoDispose((ref) { - return ActivityService( - ref.watch(activityApiRepositoryProvider), - ref.watch(timelineFactoryProvider), - ref.watch(assetServiceProvider), - ); -}); +part 'activity_service.provider.g.dart'; + +@riverpod +ActivityService activityService(Ref ref) => ActivityService( + ref.watch(activityApiRepositoryProvider), + ref.watch(timelineFactoryProvider), + ref.watch(assetServiceProvider), +); diff --git a/mobile/lib/providers/activity_service.provider.g.dart b/mobile/lib/providers/activity_service.provider.g.dart new file mode 100644 index 0000000000..4641738fc4 --- /dev/null +++ b/mobile/lib/providers/activity_service.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity_service.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityServiceHash() => r'3ce0eb33948138057cc63f07a7598047b99e7599'; + +/// See also [activityService]. +@ProviderFor(activityService) +final activityServiceProvider = AutoDisposeProvider.internal( + activityService, + name: r'activityServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ActivityServiceRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/activity_statistics.provider.dart b/mobile/lib/providers/activity_statistics.provider.dart new file mode 100644 index 0000000000..96d2633d1b --- /dev/null +++ b/mobile/lib/providers/activity_statistics.provider.dart @@ -0,0 +1,22 @@ +import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'activity_statistics.provider.g.dart'; + +// ignore: unintended_html_in_doc_comment +/// Maintains the current number of comments by +@riverpod +class ActivityStatistics extends _$ActivityStatistics { + @override + int build(String albumId, [String? assetId]) { + ref.watch(activityServiceProvider).getStatistics(albumId, assetId: assetId).then((stats) => state = stats.comments); + return 0; + } + + void addActivity() => state = state + 1; + + void removeActivity() => state = state - 1; +} + +/// Mock class for testing +abstract class ActivityStatisticsInternal extends _$ActivityStatistics {} diff --git a/mobile/lib/providers/activity_statistics.provider.g.dart b/mobile/lib/providers/activity_statistics.provider.g.dart new file mode 100644 index 0000000000..83d887f6dc --- /dev/null +++ b/mobile/lib/providers/activity_statistics.provider.g.dart @@ -0,0 +1,191 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity_statistics.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$activityStatisticsHash() => + r'1f43f0bcb11c754ca3cb586a13570db25023b9a8'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$ActivityStatistics extends BuildlessAutoDisposeNotifier { + late final String albumId; + late final String? assetId; + + int build(String albumId, [String? assetId]); +} + +/// Maintains the current number of comments by +/// +/// Copied from [ActivityStatistics]. +@ProviderFor(ActivityStatistics) +const activityStatisticsProvider = ActivityStatisticsFamily(); + +/// Maintains the current number of comments by +/// +/// Copied from [ActivityStatistics]. +class ActivityStatisticsFamily extends Family { + /// Maintains the current number of comments by + /// + /// Copied from [ActivityStatistics]. + const ActivityStatisticsFamily(); + + /// Maintains the current number of comments by + /// + /// Copied from [ActivityStatistics]. + ActivityStatisticsProvider call(String albumId, [String? assetId]) { + return ActivityStatisticsProvider(albumId, assetId); + } + + @override + ActivityStatisticsProvider getProviderOverride( + covariant ActivityStatisticsProvider provider, + ) { + return call(provider.albumId, provider.assetId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'activityStatisticsProvider'; +} + +/// Maintains the current number of comments by +/// +/// Copied from [ActivityStatistics]. +class ActivityStatisticsProvider + extends AutoDisposeNotifierProviderImpl { + /// Maintains the current number of comments by + /// + /// Copied from [ActivityStatistics]. + ActivityStatisticsProvider(String albumId, [String? assetId]) + : this._internal( + () => ActivityStatistics() + ..albumId = albumId + ..assetId = assetId, + from: activityStatisticsProvider, + name: r'activityStatisticsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$activityStatisticsHash, + dependencies: ActivityStatisticsFamily._dependencies, + allTransitiveDependencies: + ActivityStatisticsFamily._allTransitiveDependencies, + albumId: albumId, + assetId: assetId, + ); + + ActivityStatisticsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.albumId, + required this.assetId, + }) : super.internal(); + + final String albumId; + final String? assetId; + + @override + int runNotifierBuild(covariant ActivityStatistics notifier) { + return notifier.build(albumId, assetId); + } + + @override + Override overrideWith(ActivityStatistics Function() create) { + return ProviderOverride( + origin: this, + override: ActivityStatisticsProvider._internal( + () => create() + ..albumId = albumId + ..assetId = assetId, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + albumId: albumId, + assetId: assetId, + ), + ); + } + + @override + AutoDisposeNotifierProviderElement createElement() { + return _ActivityStatisticsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is ActivityStatisticsProvider && + other.albumId == albumId && + other.assetId == assetId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, albumId.hashCode); + hash = _SystemHash.combine(hash, assetId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin ActivityStatisticsRef on AutoDisposeNotifierProviderRef { + /// The parameter `albumId` of this provider. + String get albumId; + + /// The parameter `assetId` of this provider. + String? get assetId; +} + +class _ActivityStatisticsProviderElement + extends AutoDisposeNotifierProviderElement + with ActivityStatisticsRef { + _ActivityStatisticsProviderElement(super.provider); + + @override + String get albumId => (origin as ActivityStatisticsProvider).albumId; + @override + String? get assetId => (origin as ActivityStatisticsProvider).assetId; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/album.provider.dart b/mobile/lib/providers/album/album.provider.dart new file mode 100644 index 0000000000..35634d77c8 --- /dev/null +++ b/mobile/lib/providers/album/album.provider.dart @@ -0,0 +1,151 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/services/album.service.dart'; + +final isRefreshingRemoteAlbumProvider = StateProvider((ref) => false); + +class AlbumNotifier extends StateNotifier> { + AlbumNotifier(this.albumService, this.ref) : super([]) { + albumService.getAllRemoteAlbums().then((value) { + if (mounted) { + state = value; + } + }); + + _streamSub = albumService.watchRemoteAlbums().listen((data) => state = data); + } + + final AlbumService albumService; + final Ref ref; + late final StreamSubscription> _streamSub; + + Future refreshRemoteAlbums() async { + ref.read(isRefreshingRemoteAlbumProvider.notifier).state = true; + await albumService.refreshRemoteAlbums(); + ref.read(isRefreshingRemoteAlbumProvider.notifier).state = false; + } + + Future refreshDeviceAlbums() => albumService.refreshDeviceAlbums(); + + Future deleteAlbum(Album album) => albumService.deleteAlbum(album); + + Future createAlbum(String albumTitle, Set assets) => albumService.createAlbum(albumTitle, assets, []); + + Future getAlbumByName(String albumName, {bool? remote, bool? shared, bool? owner}) => + albumService.getAlbumByName(albumName, remote: remote, shared: shared, owner: owner); + + /// Create an album on the server with the same name as the selected album for backup + /// First this will check if the album already exists on the server with name + /// If it does not exist, it will create the album on the server + Future createSyncAlbum(String albumName) async { + final album = await getAlbumByName(albumName, remote: true, owner: true); + if (album != null) { + return; + } + + await createAlbum(albumName, {}); + } + + Future leaveAlbum(Album album) async { + var res = await albumService.leaveAlbum(album); + + if (res) { + await deleteAlbum(album); + return true; + } else { + return false; + } + } + + void searchAlbums(String searchTerm, QuickFilterMode filterMode) async { + state = await albumService.search(searchTerm, filterMode); + } + + Future addUsers(Album album, List userIds) async { + await albumService.addUsers(album, userIds); + } + + Future removeUser(Album album, UserDto user) async { + final isRemoved = await albumService.removeUser(album, user); + + if (isRemoved && album.sharedUsers.isEmpty) { + state = state.where((element) => element.id != album.id).toList(); + } + + return isRemoved; + } + + Future addAssets(Album album, Iterable assets) async { + await albumService.addAssets(album, assets); + } + + Future removeAsset(Album album, Iterable assets) async { + return await albumService.removeAsset(album, assets); + } + + Future setActivitystatus(Album album, bool enabled) { + return albumService.setActivityStatus(album, enabled); + } + + Future toggleSortOrder(Album album) { + final order = album.sortOrder == SortOrder.asc ? SortOrder.desc : SortOrder.asc; + + return albumService.updateSortOrder(album, order); + } + + @override + void dispose() { + _streamSub.cancel(); + super.dispose(); + } +} + +final albumProvider = StateNotifierProvider.autoDispose>((ref) { + return AlbumNotifier(ref.watch(albumServiceProvider), ref); +}); + +final albumWatcher = StreamProvider.autoDispose.family((ref, id) async* { + final albumService = ref.watch(albumServiceProvider); + + final album = await albumService.getAlbumById(id); + if (album != null) { + yield album; + } + + await for (final album in albumService.watchAlbum(id)) { + if (album != null) { + yield album; + } + } +}); + +class LocalAlbumsNotifier extends StateNotifier> { + LocalAlbumsNotifier(this.albumService) : super([]) { + albumService.getAllLocalAlbums().then((value) { + if (mounted) { + state = value; + } + }); + + _streamSub = albumService.watchLocalAlbums().listen((data) => state = data); + } + + final AlbumService albumService; + late final StreamSubscription> _streamSub; + + @override + void dispose() { + _streamSub.cancel(); + super.dispose(); + } +} + +final localAlbumsProvider = StateNotifierProvider.autoDispose>((ref) { + return LocalAlbumsNotifier(ref.watch(albumServiceProvider)); +}); diff --git a/mobile/lib/providers/album/album_sort_by_options.provider.dart b/mobile/lib/providers/album/album_sort_by_options.provider.dart index 015a846de8..c969dbd37d 100644 --- a/mobile/lib/providers/album/album_sort_by_options.provider.dart +++ b/mobile/lib/providers/album/album_sort_by_options.provider.dart @@ -1,28 +1,119 @@ +import 'package:collection/collection.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'album_sort_by_options.provider.g.dart'; + +typedef AlbumSortFn = List Function(List albums, bool isReverse); + +class _AlbumSortHandlers { + const _AlbumSortHandlers._(); + + static const AlbumSortFn created = _sortByCreated; + static List _sortByCreated(List albums, bool isReverse) { + final sorted = albums.sortedBy((album) => album.createdAt); + return (isReverse ? sorted.reversed : sorted).toList(); + } + + static const AlbumSortFn title = _sortByTitle; + static List _sortByTitle(List albums, bool isReverse) { + final sorted = albums.sortedBy((album) => album.name); + return (isReverse ? sorted.reversed : sorted).toList(); + } + + static const AlbumSortFn lastModified = _sortByLastModified; + static List _sortByLastModified(List albums, bool isReverse) { + final sorted = albums.sortedBy((album) => album.modifiedAt); + return (isReverse ? sorted.reversed : sorted).toList(); + } + + static const AlbumSortFn assetCount = _sortByAssetCount; + static List _sortByAssetCount(List albums, bool isReverse) { + final sorted = albums.sorted((a, b) => a.assetCount.compareTo(b.assetCount)); + return (isReverse ? sorted.reversed : sorted).toList(); + } + + static const AlbumSortFn mostRecent = _sortByMostRecent; + static List _sortByMostRecent(List albums, bool isReverse) { + final sorted = albums.sorted((a, b) { + if (a.endDate == null && b.endDate == null) { + return 0; + } + + if (a.endDate == null) { + // Put nulls at the end for recent sorting + return 1; + } + + if (b.endDate == null) { + return -1; + } + + // Sort by descending recent date + return b.endDate!.compareTo(a.endDate!); + }); + return (isReverse ? sorted.reversed : sorted).toList(); + } + + static const AlbumSortFn mostOldest = _sortByMostOldest; + static List _sortByMostOldest(List albums, bool isReverse) { + final sorted = albums.sorted((a, b) { + if (a.startDate != null && b.startDate != null) { + return a.startDate!.compareTo(b.startDate!); + } + if (a.startDate == null) return 1; + if (b.startDate == null) return -1; + return 0; + }); + return (isReverse ? sorted.reversed : sorted).toList(); + } +} // Store index allows us to re-arrange the values without affecting the saved prefs enum AlbumSortMode { - title(1, SortOrder.asc), - assetCount(4, SortOrder.desc), - lastModified(3, SortOrder.desc), - created(0, SortOrder.desc), - mostRecent(2, SortOrder.desc), - mostOldest(5, SortOrder.asc); + title(1, "library_page_sort_title", _AlbumSortHandlers.title, SortOrder.asc), + assetCount(4, "library_page_sort_asset_count", _AlbumSortHandlers.assetCount, SortOrder.desc), + lastModified(3, "library_page_sort_last_modified", _AlbumSortHandlers.lastModified, SortOrder.desc), + created(0, "library_page_sort_created", _AlbumSortHandlers.created, SortOrder.desc), + mostRecent(2, "sort_recent", _AlbumSortHandlers.mostRecent, SortOrder.desc), + mostOldest(5, "sort_oldest", _AlbumSortHandlers.mostOldest, SortOrder.asc); final int storeIndex; + final String label; + final AlbumSortFn sortFn; final SortOrder defaultOrder; - const AlbumSortMode(this.storeIndex, this.defaultOrder); - - String label(Translations t) => switch (this) { - AlbumSortMode.title => t.library_page_sort_title, - AlbumSortMode.assetCount => t.library_page_sort_asset_count, - AlbumSortMode.lastModified => t.library_page_sort_last_modified, - AlbumSortMode.created => t.library_page_sort_created, - AlbumSortMode.mostRecent => t.sort_recent, - AlbumSortMode.mostOldest => t.sort_oldest, - }; + const AlbumSortMode(this.storeIndex, this.label, this.sortFn, this.defaultOrder); SortOrder effectiveOrder(bool isReverse) => isReverse ? defaultOrder.reverse() : defaultOrder; } + +@riverpod +class AlbumSortByOptions extends _$AlbumSortByOptions { + @override + AlbumSortMode build() { + final sortOpt = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.selectedAlbumSortOrder); + return AlbumSortMode.values.firstWhere((e) => e.storeIndex == sortOpt, orElse: () => AlbumSortMode.title); + } + + void changeSortMode(AlbumSortMode sortOption) { + state = sortOption; + ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.selectedAlbumSortOrder, sortOption.storeIndex); + } +} + +@riverpod +class AlbumSortOrder extends _$AlbumSortOrder { + @override + bool build() { + return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.selectedAlbumSortReverse); + } + + void changeSortDirection(bool isReverse) { + state = isReverse; + ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.selectedAlbumSortReverse, isReverse); + } +} diff --git a/mobile/lib/providers/album/album_sort_by_options.provider.g.dart b/mobile/lib/providers/album/album_sort_by_options.provider.g.dart new file mode 100644 index 0000000000..750329c9d5 --- /dev/null +++ b/mobile/lib/providers/album/album_sort_by_options.provider.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'album_sort_by_options.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$albumSortByOptionsHash() => + r'dd8da5e730af555de1b86c3b157b6c93183523ac'; + +/// See also [AlbumSortByOptions]. +@ProviderFor(AlbumSortByOptions) +final albumSortByOptionsProvider = + AutoDisposeNotifierProvider.internal( + AlbumSortByOptions.new, + name: r'albumSortByOptionsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$albumSortByOptionsHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$AlbumSortByOptions = AutoDisposeNotifier; +String _$albumSortOrderHash() => r'573dea45b4519e69386fc7104c72522e35713440'; + +/// See also [AlbumSortOrder]. +@ProviderFor(AlbumSortOrder) +final albumSortOrderProvider = + AutoDisposeNotifierProvider.internal( + AlbumSortOrder.new, + name: r'albumSortOrderProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$albumSortOrderHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$AlbumSortOrder = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/album_title.provider.dart b/mobile/lib/providers/album/album_title.provider.dart index b38c2929fb..bf812a01d8 100644 --- a/mobile/lib/providers/album/album_title.provider.dart +++ b/mobile/lib/providers/album/album_title.provider.dart @@ -3,11 +3,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; class AlbumTitleNotifier extends StateNotifier { AlbumTitleNotifier() : super(""); - void setAlbumTitle(String title) { + setAlbumTitle(String title) { state = title; } - void clearAlbumTitle() { + clearAlbumTitle() { state = ""; } } diff --git a/mobile/lib/providers/album/album_viewer.provider.dart b/mobile/lib/providers/album/album_viewer.provider.dart new file mode 100644 index 0000000000..f4ce047464 --- /dev/null +++ b/mobile/lib/providers/album/album_viewer.provider.dart @@ -0,0 +1,74 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/models/albums/album_viewer_page_state.model.dart'; +import 'package:immich_mobile/services/album.service.dart'; + +class AlbumViewerNotifier extends StateNotifier { + AlbumViewerNotifier(this.ref) + : super(const AlbumViewerPageState(editTitleText: "", isEditAlbum: false, editDescriptionText: "")); + + final Ref ref; + + void enableEditAlbum() { + state = state.copyWith(isEditAlbum: true); + } + + void disableEditAlbum() { + state = state.copyWith(isEditAlbum: false); + } + + void setEditTitleText(String newTitle) { + 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: ""); + } + + Future changeAlbumTitle(Album album, String newAlbumTitle) async { + AlbumService service = ref.watch(albumServiceProvider); + + bool isSuccess = await service.changeTitleAlbum(album, newAlbumTitle); + + if (isSuccess) { + state = state.copyWith(editTitleText: "", isEditAlbum: false); + + return true; + } + + state = state.copyWith(editTitleText: "", isEditAlbum: false); + return false; + } + + Future 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 = StateNotifierProvider((ref) { + return AlbumViewerNotifier(ref); +}); diff --git a/mobile/lib/providers/album/current_album.provider.dart b/mobile/lib/providers/album/current_album.provider.dart new file mode 100644 index 0000000000..bd22c7a7cd --- /dev/null +++ b/mobile/lib/providers/album/current_album.provider.dart @@ -0,0 +1,15 @@ +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'current_album.provider.g.dart'; + +@riverpod +class CurrentAlbum extends _$CurrentAlbum { + @override + Album? build() => null; + + void set(Album? a) => state = a; +} + +/// Mock class for testing +abstract class CurrentAlbumInternal extends _$CurrentAlbum {} diff --git a/mobile/lib/providers/album/current_album.provider.g.dart b/mobile/lib/providers/album/current_album.provider.g.dart new file mode 100644 index 0000000000..b6d079231f --- /dev/null +++ b/mobile/lib/providers/album/current_album.provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'current_album.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$currentAlbumHash() => r'61f00273d6b69da45add1532cc3d3a076ee55110'; + +/// See also [CurrentAlbum]. +@ProviderFor(CurrentAlbum) +final currentAlbumProvider = + AutoDisposeNotifierProvider.internal( + CurrentAlbum.new, + name: r'currentAlbumProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$currentAlbumHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$CurrentAlbum = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/album/pending_album_uploads.provider.dart b/mobile/lib/providers/album/pending_album_uploads.provider.dart deleted file mode 100644 index 44d279c789..0000000000 --- a/mobile/lib/providers/album/pending_album_uploads.provider.dart +++ /dev/null @@ -1,87 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; - -part 'pending_album_uploads.provider.freezed.dart'; - -@freezed -abstract class PendingAlbumUpload with _$PendingAlbumUpload { - const factory PendingAlbumUpload({ - required LocalAsset asset, - @Default(0.0) double progress, - @Default(false) bool failed, - }) = _PendingAlbumUpload; -} - -class AlbumPendingUploadsNotifier extends AutoDisposeFamilyNotifier, String> { - KeepAliveLink? _keepAliveLink; - - @override - List build(String albumId) { - ref.onDispose(() { - _keepAliveLink?.close(); - _keepAliveLink = null; - }); - - return const []; - } - - void enqueue(Iterable assets) { - if (assets.isEmpty) { - return; - } - - final existingIds = state.map((e) => e.asset.id).toSet(); - final additions = assets.where((a) => !existingIds.contains(a.id)).map((a) => PendingAlbumUpload(asset: a)); - state = [...state, ...additions]; - _syncKeepAlive(); - } - - void updateProgress(String localAssetId, double progress) { - state = [ - for (final entry in state) - if (entry.asset.id == localAssetId) entry.copyWith(progress: progress, failed: false) else entry, - ]; - _syncKeepAlive(); - } - - void markFailed(String localAssetId) { - state = [ - for (final entry in state) - if (entry.asset.id == localAssetId) entry.copyWith(failed: true) else entry, - ]; - _syncKeepAlive(); - } - - void markAllFailed() { - state = [for (final entry in state) entry.copyWith(failed: true)]; - _syncKeepAlive(); - } - - void remove(String localAssetId) { - state = state.where((e) => e.asset.id != localAssetId).toList(); - _syncKeepAlive(); - } - - void clearFailed() { - state = state.where((e) => !e.failed).toList(); - _syncKeepAlive(); - } - - void clear() { - state = const []; - _syncKeepAlive(); - } - - void _syncKeepAlive() { - if (state.isEmpty) { - _keepAliveLink?.close(); - _keepAliveLink = null; - } else { - _keepAliveLink ??= ref.keepAlive(); - } - } -} - -final pendingAlbumUploadsProvider = NotifierProvider.autoDispose - .family, String>(AlbumPendingUploadsNotifier.new); diff --git a/mobile/lib/providers/album/suggested_shared_users.provider.dart b/mobile/lib/providers/album/suggested_shared_users.provider.dart new file mode 100644 index 0000000000..51146748c7 --- /dev/null +++ b/mobile/lib/providers/album/suggested_shared_users.provider.dart @@ -0,0 +1,14 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; + +final otherUsersProvider = FutureProvider.autoDispose>((ref) async { + UserService userService = ref.watch(userServiceProvider); + final currentUser = ref.watch(currentUserProvider); + + final allUsers = await userService.getAll(); + allUsers.removeWhere((u) => currentUser?.id == u.id); + return allUsers; +}); diff --git a/mobile/lib/providers/api.provider.dart b/mobile/lib/providers/api.provider.dart index 4b3209418a..a54496d94c 100644 --- a/mobile/lib/providers/api.provider.dart +++ b/mobile/lib/providers/api.provider.dart @@ -1,4 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/api.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final apiServiceProvider = Provider((_) => ApiService()); +part 'api.provider.g.dart'; + +@Riverpod(keepAlive: true) +ApiService apiService(Ref _) => ApiService(); diff --git a/mobile/lib/providers/api.provider.g.dart b/mobile/lib/providers/api.provider.g.dart new file mode 100644 index 0000000000..ee1781c24c --- /dev/null +++ b/mobile/lib/providers/api.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'api.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$apiServiceHash() => r'187a7de59b064fab1104c23717f18ce0ae3e426c'; + +/// See also [apiService]. +@ProviderFor(apiService) +final apiServiceProvider = Provider.internal( + apiService, + name: r'apiServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$apiServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ApiServiceRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index f369a37142..68007f283a 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -5,17 +5,28 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/permission.provider.dart'; +import 'package:immich_mobile/providers/memory.provider.dart'; +import 'package:immich_mobile/providers/notification_permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden } @@ -35,7 +46,7 @@ class AppLifeCycleNotifier extends StateNotifier { return state; } - Future handleAppResume() async { + void handleAppResume() async { state = AppLifeCycleEnum.resumed; // Prevent overlapping resume operations @@ -65,9 +76,7 @@ class AppLifeCycleNotifier extends StateNotifier { Future _performResume() async { // no need to resume because app was never really paused - if (!_wasPaused) { - return; - } + if (!_wasPaused) return; _wasPaused = false; final isAuthenticated = _ref.read(authProvider).isAuthenticated; @@ -78,72 +87,88 @@ class AppLifeCycleNotifier extends StateNotifier { final endpoint = await _ref.read(authProvider.notifier).setOpenApiServiceEndpoint(); _log.info("Using server URL: $endpoint"); + if (!Store.isBetaTimelineEnabled) { + final permission = _ref.watch(galleryPermissionNotifier); + if (permission.isGranted || permission.isLimited) { + await _ref.read(backupProvider.notifier).resumeBackup(); + await _ref.read(backgroundServiceProvider).resumeServiceIfEnabled(); + } + } + await _ref.read(serverInfoProvider.notifier).getServerVersion(); } - if (!_shouldContinueOperation()) { - _wasPaused = true; - return; + if (!Store.isBetaTimelineEnabled) { + switch (_ref.read(tabProvider)) { + case TabEnum.home: + await _ref.read(assetProvider.notifier).getAllAsset(); + + case TabEnum.albums: + await _ref.read(albumProvider.notifier).refreshRemoteAlbums(); + + case TabEnum.library: + case TabEnum.search: + break; + } + } else { + _ref.read(websocketProvider.notifier).connect(); + await _handleBetaTimelineResume(); } - _ref.read(websocketProvider.notifier).connect(); - await _handleBetaTimelineResume(); await _ref.read(notificationPermissionProvider.notifier).getNotificationPermission(); await _ref.read(galleryPermissionNotifier.notifier).getGalleryPermissionStatus(); + + if (!Store.isBetaTimelineEnabled) { + await _ref.read(iOSBackgroundSettingsProvider.notifier).refresh(); + + _ref.invalidate(memoryFutureProvider); + } } - Future _safeRun(Future Function() action, String debugName) async { + Future _safeRun(Future action, String debugName) async { if (!_shouldContinueOperation()) { return; } try { - await action(); + await action; } catch (e, stackTrace) { _log.warning("Error during $debugName operation", e, stackTrace); } } Future _handleBetaTimelineResume() async { + _ref.read(backupProvider.notifier).cancelBackup(); unawaited(_ref.read(backgroundWorkerLockServiceProvider).lock()); // Give isolates time to complete any ongoing database transactions await Future.delayed(const Duration(milliseconds: 500)); final backgroundManager = _ref.read(backgroundSyncProvider); - - // Drop any sync that froze mid-flight while the app was suspended so resume - // starts fresh instead of awaiting the stale task (#28082). cancelResumeSyncs - // clears the task refs synchronously, so the syncs below see a clean slate. - unawaited(backgroundManager.cancelResumeSyncs()); - - final isAlbumLinkedSyncEnable = _ref.read(appConfigProvider).backup.syncAlbums; + final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); try { bool syncSuccess = false; await Future.wait([ - _safeRun(() => backgroundManager.syncLocal(full: CurrentPlatform.isAndroid), "syncLocal"), - _safeRun(() async { - syncSuccess = await backgroundManager.syncRemote(); - }, "syncRemote"), + _safeRun(backgroundManager.syncLocal(full: CurrentPlatform.isAndroid ? true : false), "syncLocal"), + _safeRun(backgroundManager.syncRemote().then((success) => syncSuccess = success), "syncRemote"), ]); - _ref.invalidate(driftMemoryFutureProvider); if (syncSuccess) { await Future.wait([ - _safeRun(backgroundManager.hashAssets, "hashAssets").then((_) { - unawaited(_resumeBackup()); + _safeRun(backgroundManager.hashAssets(), "hashAssets").then((_) { + _resumeBackup(); }), _resumeBackup(), // TODO: Bring back when the soft freeze issue is addressed // _safeRun(backgroundManager.syncCloudIds(), "syncCloudIds"), ]); } else { - await _safeRun(backgroundManager.hashAssets, "hashAssets"); + await _safeRun(backgroundManager.hashAssets(), "hashAssets"); } if (isAlbumLinkedSyncEnable) { - await _safeRun(backgroundManager.syncLinkedAlbum, "syncLinkedAlbum"); + await _safeRun(backgroundManager.syncLinkedAlbum(), "syncLinkedAlbum"); } } catch (e, stackTrace) { _log.severe("Error during background sync", e, stackTrace); @@ -151,13 +176,13 @@ class AppLifeCycleNotifier extends StateNotifier { } Future _resumeBackup() async { - final isEnableBackup = _ref.read(appConfigProvider).backup.enabled; + final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup); if (isEnableBackup) { final currentUser = Store.tryGet(StoreKey.currentUser); if (currentUser != null) { await _safeRun( - () => _ref.read(driftBackupProvider.notifier).startForegroundBackup(currentUser.id), + _ref.read(driftBackupProvider.notifier).startForegroundBackup(currentUser.id), "handleBackupResume", ); } @@ -193,7 +218,9 @@ class AppLifeCycleNotifier extends StateNotifier { _pauseOperation = Completer(); try { - unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock()); + if (Store.isBetaTimelineEnabled) { + unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock()); + } await _performPause(); } catch (e, stackTrace) { _log.severe("Error during app pause", e, stackTrace); @@ -207,7 +234,14 @@ class AppLifeCycleNotifier extends StateNotifier { Future _performPause() { if (_ref.read(authProvider).isAuthenticated) { - _ref.read(driftBackupProvider.notifier).stopForegroundBackup(reason: "the app being sent to the background"); + if (!Store.isBetaTimelineEnabled) { + // Do not cancel backup if manual upload is in progress + if (_ref.read(backupProvider.notifier).backupProgress != BackUpProgressEnum.manualInProgress) { + _ref.read(backupProvider.notifier).cancelBackup(); + } + } else { + _ref.read(driftBackupProvider.notifier).stopForegroundBackup(); + } _ref.read(websocketProvider.notifier).disconnect(); } @@ -218,12 +252,31 @@ class AppLifeCycleNotifier extends StateNotifier { Future handleAppDetached() async { state = AppLifeCycleEnum.detached; - unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock()); + if (Store.isBetaTimelineEnabled) { + unawaited(_ref.read(backgroundWorkerLockServiceProvider).unlock()); + } // Flush logs before closing database try { await LogService.I.flush(); } catch (_) {} + + // Close Isar database safely + try { + final isar = Isar.getInstance(); + if (isar != null && isar.isOpen) { + await isar.close(); + } + } catch (_) {} + + if (Store.isBetaTimelineEnabled) { + return; + } + + // no guarantee this is called at all + try { + _ref.read(manualUploadProvider.notifier).cancelBackup(); + } catch (_) {} } void handleAppHidden() { diff --git a/mobile/lib/providers/app_settings.provider.dart b/mobile/lib/providers/app_settings.provider.dart index 3d3947a931..109218a07c 100644 --- a/mobile/lib/providers/app_settings.provider.dart +++ b/mobile/lib/providers/app_settings.provider.dart @@ -1,4 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final appSettingsServiceProvider = Provider((_) => const AppSettingsService()); +part 'app_settings.provider.g.dart'; + +@Riverpod(keepAlive: true) +AppSettingsService appSettingsService(Ref _) => const AppSettingsService(); diff --git a/mobile/lib/providers/app_settings.provider.g.dart b/mobile/lib/providers/app_settings.provider.g.dart new file mode 100644 index 0000000000..c959861c04 --- /dev/null +++ b/mobile/lib/providers/app_settings.provider.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_settings.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$appSettingsServiceHash() => + r'89cece3a19e06612f5639ae290120e854a0c5a31'; + +/// See also [appSettingsService]. +@ProviderFor(appSettingsService) +final appSettingsServiceProvider = Provider.internal( + appSettingsService, + name: r'appSettingsServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$appSettingsServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef AppSettingsServiceRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset.provider.dart b/mobile/lib/providers/asset.provider.dart new file mode 100644 index 0000000000..d5a4e42b74 --- /dev/null +++ b/mobile/lib/providers/asset.provider.dart @@ -0,0 +1,182 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/memory.provider.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/services/etag.service.dart'; +import 'package:immich_mobile/services/exif.service.dart'; +import 'package:immich_mobile/services/sync.service.dart'; +import 'package:logging/logging.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +final assetProvider = StateNotifierProvider((ref) { + return AssetNotifier( + ref.watch(assetServiceProvider), + ref.watch(albumServiceProvider), + ref.watch(userServiceProvider), + ref.watch(syncServiceProvider), + ref.watch(etagServiceProvider), + ref.watch(exifServiceProvider), + ref, + ); +}); + +class AssetNotifier extends StateNotifier { + final AssetService _assetService; + final AlbumService _albumService; + final UserService _userService; + final SyncService _syncService; + final ETagService _etagService; + final ExifService _exifService; + final Ref _ref; + final log = Logger('AssetNotifier'); + bool _getAllAssetInProgress = false; + bool _deleteInProgress = false; + + AssetNotifier( + this._assetService, + this._albumService, + this._userService, + this._syncService, + this._etagService, + this._exifService, + this._ref, + ) : super(false); + + Future getAllAsset({bool clear = false}) async { + if (_getAllAssetInProgress || _deleteInProgress) { + // guard against multiple calls to this method while it's still working + return; + } + final stopwatch = Stopwatch()..start(); + try { + _getAllAssetInProgress = true; + state = true; + if (clear) { + await clearAllAssets(); + log.info("Manual refresh requested, cleared assets and albums from db"); + } + final users = await _syncService.getUsersFromServer(); + bool changedUsers = false; + if (users != null) { + changedUsers = await _syncService.syncUsersFromServer(users); + } + final bool newRemote = await _assetService.refreshRemoteAssets(); + final bool newLocal = await _albumService.refreshDeviceAlbums(); + dPrint(() => "changedUsers: $changedUsers, newRemote: $newRemote, newLocal: $newLocal"); + if (newRemote) { + _ref.invalidate(memoryFutureProvider); + } + + log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms"); + } catch (error) { + // If there is error in getting the remote assets, still showing the new local assets + await _albumService.refreshDeviceAlbums(); + } finally { + _getAllAssetInProgress = false; + if (mounted) { + state = false; + } + } + } + + Future clearAllAssets() async { + await Store.delete(StoreKey.assetETag); + await Future.wait([ + _assetService.clearTable(), + _exifService.clearTable(), + _albumService.clearTable(), + _userService.deleteAll(), + _etagService.clearTable(), + ]); + } + + Future onNewAssetUploaded(Asset newAsset) async { + // eTag on device is not valid after partially modifying the assets + await Store.delete(StoreKey.assetETag); + await _syncService.syncNewAssetToDb(newAsset); + } + + Future deleteLocalAssets(List assets) async { + _deleteInProgress = true; + state = true; + try { + await _assetService.deleteLocalAssets(assets); + return true; + } catch (error) { + log.severe("Failed to delete local assets", error); + return false; + } finally { + _deleteInProgress = false; + state = false; + } + } + + /// Delete remote asset only + /// + /// Default behavior is trashing the asset + Future deleteRemoteAssets(Iterable deleteAssets, {bool shouldDeletePermanently = false}) async { + _deleteInProgress = true; + state = true; + try { + await _assetService.deleteRemoteAssets(deleteAssets, shouldDeletePermanently: shouldDeletePermanently); + return true; + } catch (error) { + log.severe("Failed to delete remote assets", error); + return false; + } finally { + _deleteInProgress = false; + state = false; + } + } + + Future deleteAssets(Iterable deleteAssets, {bool force = false}) async { + _deleteInProgress = true; + state = true; + try { + await _assetService.deleteAssets(deleteAssets, shouldDeletePermanently: force); + return true; + } catch (error) { + log.severe("Failed to delete assets", error); + return false; + } finally { + _deleteInProgress = false; + state = false; + } + } + + Future toggleFavorite(List assets, [bool? status]) { + status ??= !assets.every((a) => a.isFavorite); + return _assetService.changeFavoriteStatus(assets, status); + } + + Future toggleArchive(List assets, [bool? status]) { + status ??= !assets.every((a) => a.isArchived); + return _assetService.changeArchiveStatus(assets, status); + } + + Future setLockedView(List selection, AssetVisibilityEnum visibility) { + return _assetService.setVisibility(selection, visibility); + } +} + +final assetDetailProvider = StreamProvider.autoDispose.family((ref, asset) async* { + final assetService = ref.watch(assetServiceProvider); + yield await assetService.loadExif(asset); + + await for (final asset in assetService.watchAsset(asset.id)) { + if (asset != null) { + yield await ref.watch(assetServiceProvider).loadExif(asset); + } + } +}); + +final assetWatcher = StreamProvider.autoDispose.family((ref, asset) { + final assetService = ref.watch(assetServiceProvider); + return assetService.watchAsset(asset.id, fireImmediately: true); +}); diff --git a/mobile/lib/providers/asset_viewer/asset_people.provider.dart b/mobile/lib/providers/asset_viewer/asset_people.provider.dart new file mode 100644 index 0000000000..e2227920c7 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/asset_people.provider.dart @@ -0,0 +1,49 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'asset_people.provider.g.dart'; + +/// Maintains the list of people for an asset. +@riverpod +class AssetPeopleNotifier extends _$AssetPeopleNotifier { + final log = Logger('AssetPeopleNotifier'); + + @override + Future> build(Asset asset) async { + if (!asset.isRemote) { + return []; + } + + final list = await ref.watch(assetServiceProvider).getRemotePeopleOfAsset(asset.remoteId!); + if (list == null) { + return []; + } + + // explicitly a sorted slice to make it deterministic + // named people will be at the beginning, and names are sorted + // ascendingly + list.sort((a, b) { + final aNotEmpty = a.name.isNotEmpty; + final bNotEmpty = b.name.isNotEmpty; + if (aNotEmpty && !bNotEmpty) { + return -1; + } else if (!aNotEmpty && bNotEmpty) { + return 1; + } else if (!aNotEmpty && !bNotEmpty) { + return 0; + } + + return a.name.compareTo(b.name); + }); + return list; + } + + Future refresh() async { + // invalidate the state – this way we don't have to + // duplicate the code from build. + ref.invalidateSelf(); + } +} diff --git a/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart b/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart new file mode 100644 index 0000000000..031a70e0d9 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/asset_people.provider.g.dart @@ -0,0 +1,192 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset_people.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$assetPeopleNotifierHash() => + r'9835b180984a750c91e923e7b64dbda94f6d7574'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +abstract class _$AssetPeopleNotifier + extends + BuildlessAutoDisposeAsyncNotifier> { + late final Asset asset; + + FutureOr> build(Asset asset); +} + +/// Maintains the list of people for an asset. +/// +/// Copied from [AssetPeopleNotifier]. +@ProviderFor(AssetPeopleNotifier) +const assetPeopleNotifierProvider = AssetPeopleNotifierFamily(); + +/// Maintains the list of people for an asset. +/// +/// Copied from [AssetPeopleNotifier]. +class AssetPeopleNotifierFamily + extends Family>> { + /// Maintains the list of people for an asset. + /// + /// Copied from [AssetPeopleNotifier]. + const AssetPeopleNotifierFamily(); + + /// Maintains the list of people for an asset. + /// + /// Copied from [AssetPeopleNotifier]. + AssetPeopleNotifierProvider call(Asset asset) { + return AssetPeopleNotifierProvider(asset); + } + + @override + AssetPeopleNotifierProvider getProviderOverride( + covariant AssetPeopleNotifierProvider provider, + ) { + return call(provider.asset); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'assetPeopleNotifierProvider'; +} + +/// Maintains the list of people for an asset. +/// +/// Copied from [AssetPeopleNotifier]. +class AssetPeopleNotifierProvider + extends + AutoDisposeAsyncNotifierProviderImpl< + AssetPeopleNotifier, + List + > { + /// Maintains the list of people for an asset. + /// + /// Copied from [AssetPeopleNotifier]. + AssetPeopleNotifierProvider(Asset asset) + : this._internal( + () => AssetPeopleNotifier()..asset = asset, + from: assetPeopleNotifierProvider, + name: r'assetPeopleNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$assetPeopleNotifierHash, + dependencies: AssetPeopleNotifierFamily._dependencies, + allTransitiveDependencies: + AssetPeopleNotifierFamily._allTransitiveDependencies, + asset: asset, + ); + + AssetPeopleNotifierProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.asset, + }) : super.internal(); + + final Asset asset; + + @override + FutureOr> runNotifierBuild( + covariant AssetPeopleNotifier notifier, + ) { + return notifier.build(asset); + } + + @override + Override overrideWith(AssetPeopleNotifier Function() create) { + return ProviderOverride( + origin: this, + override: AssetPeopleNotifierProvider._internal( + () => create()..asset = asset, + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + asset: asset, + ), + ); + } + + @override + AutoDisposeAsyncNotifierProviderElement< + AssetPeopleNotifier, + List + > + createElement() { + return _AssetPeopleNotifierProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is AssetPeopleNotifierProvider && other.asset == asset; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, asset.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin AssetPeopleNotifierRef + on AutoDisposeAsyncNotifierProviderRef> { + /// The parameter `asset` of this provider. + Asset get asset; +} + +class _AssetPeopleNotifierProviderElement + extends + AutoDisposeAsyncNotifierProviderElement< + AssetPeopleNotifier, + List + > + with AssetPeopleNotifierRef { + _AssetPeopleNotifierProviderElement(super.provider); + + @override + Asset get asset => (origin as AssetPeopleNotifierProvider).asset; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset_viewer/asset_stack.provider.dart b/mobile/lib/providers/asset_viewer/asset_stack.provider.dart new file mode 100644 index 0000000000..8772e3d0cb --- /dev/null +++ b/mobile/lib/providers/asset_viewer/asset_stack.provider.dart @@ -0,0 +1,42 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'asset_stack.provider.g.dart'; + +class AssetStackNotifier extends StateNotifier> { + final AssetService assetService; + final String _stackId; + + AssetStackNotifier(this.assetService, this._stackId) : super([]) { + _fetchStack(_stackId); + } + + void _fetchStack(String stackId) async { + if (!mounted) { + return; + } + + final stack = await assetService.getStackAssets(stackId); + if (stack.isNotEmpty) { + state = stack; + } + } + + void removeChild(int index) { + if (index < state.length) { + state.removeAt(index); + state = List.from(state); + } + } +} + +final assetStackStateProvider = StateNotifierProvider.autoDispose.family, String>( + (ref, stackId) => AssetStackNotifier(ref.watch(assetServiceProvider), stackId), +); + +@riverpod +int assetStackIndex(Ref _) { + return -1; +} diff --git a/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart b/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart new file mode 100644 index 0000000000..dcf82cdebd --- /dev/null +++ b/mobile/lib/providers/asset_viewer/asset_stack.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'asset_stack.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$assetStackIndexHash() => r'086ddb782e3eb38b80d755666fe35be8fe7322d7'; + +/// See also [assetStackIndex]. +@ProviderFor(assetStackIndex) +final assetStackIndexProvider = AutoDisposeProvider.internal( + assetStackIndex, + name: r'assetStackIndexProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$assetStackIndexHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef AssetStackIndexRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart index fd7fbebe36..19c92e7c96 100644 --- a/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart +++ b/mobile/lib/providers/asset_viewer/asset_viewer.provider.dart @@ -1,67 +1,91 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'asset_viewer.provider.freezed.dart'; +class AssetViewerState { + final double backgroundOpacity; + final bool showingDetails; + final bool showingControls; + final bool isZoomed; + final BaseAsset? currentAsset; + final int stackIndex; -@freezed -abstract class AssetViewerState with _$AssetViewerState { - const factory AssetViewerState({ - @Default(1.0) double backgroundOpacity, - @Default(false) bool showingDetails, - @Default(true) bool showingControls, - @Default(false) bool isZoomed, - @Default(false) bool showingOcr, + const AssetViewerState({ + this.backgroundOpacity = 1.0, + this.showingDetails = false, + this.showingControls = true, + this.isZoomed = false, + this.currentAsset, + this.stackIndex = 0, + }); + + AssetViewerState copyWith({ + double? backgroundOpacity, + bool? showingDetails, + bool? showingControls, + bool? isZoomed, BaseAsset? currentAsset, + int? stackIndex, + }) { + return AssetViewerState( + backgroundOpacity: backgroundOpacity ?? this.backgroundOpacity, + showingDetails: showingDetails ?? this.showingDetails, + showingControls: showingControls ?? this.showingControls, + isZoomed: isZoomed ?? this.isZoomed, + currentAsset: currentAsset ?? this.currentAsset, + stackIndex: stackIndex ?? this.stackIndex, + ); + } - /// Physical thumbnail size retained while paging through the viewer. - Size? thumbnailSize, - @Default(0) int stackIndex, - }) = _AssetViewerState; + @override + String toString() { + return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + if (other.runtimeType != runtimeType) return false; + return other is AssetViewerState && + other.backgroundOpacity == backgroundOpacity && + other.showingDetails == showingDetails && + other.showingControls == showingControls && + other.isZoomed == isZoomed && + other.currentAsset == currentAsset && + other.stackIndex == stackIndex; + } + + @override + int get hashCode => + backgroundOpacity.hashCode ^ + showingDetails.hashCode ^ + showingControls.hashCode ^ + isZoomed.hashCode ^ + currentAsset.hashCode ^ + stackIndex.hashCode; } class AssetViewerStateNotifier extends Notifier { - StreamSubscription? _assetSubscription; - @override AssetViewerState build() { - ref.onDispose(() => _assetSubscription?.cancel()); - return const AssetViewerState(); - } - - void reset() { - unawaited(_assetSubscription?.cancel()); - _assetSubscription = null; - state = const AssetViewerState(); - } - - void setAsset(BaseAsset asset, {Size? thumbnailSize}) { - if (asset == state.currentAsset) { - return; - } - // Swiping to a neighbor passes no size; keep the tapped tile's so neighbors reuse it. - state = state.copyWith( - currentAsset: asset, - thumbnailSize: thumbnailSize ?? state.thumbnailSize, - stackIndex: 0, - showingOcr: false, - ); - _watchCurrentAsset(asset); - } - - void _watchCurrentAsset(BaseAsset asset) { - unawaited(_assetSubscription?.cancel()); - _assetSubscription = ref.read(assetServiceProvider).watchAsset(asset).listen((updated) { + ref.listen(_watchedCurrentAssetProvider, (_, next) { + final updated = next.valueOrNull; if (updated != null) { state = state.copyWith(currentAsset: updated); } }); + return const AssetViewerState(); + } + + void reset() { + state = const AssetViewerState(); + } + + void setAsset(BaseAsset asset) { + if (asset == state.currentAsset) return; + state = state.copyWith(currentAsset: asset, stackIndex: 0); } void setOpacity(double opacity) { @@ -77,9 +101,9 @@ class AssetViewerStateNotifier extends Notifier { } state = state.copyWith(showingDetails: showing, showingControls: showing ? true : state.showingControls); - final id = state.currentAsset?.id; - if (id != null) { - final notifier = ref.read(videoPlayerProvider(id).notifier); + final heroTag = state.currentAsset?.heroTag; + if (heroTag != null) { + final notifier = ref.read(videoPlayerProvider(heroTag).notifier); showing ? notifier.hold() : notifier.release(); } } @@ -108,10 +132,13 @@ class AssetViewerStateNotifier extends Notifier { } state = state.copyWith(stackIndex: index); } - - void toggleOcr() { - state = state.copyWith(showingOcr: !state.showingOcr); - } } final assetViewerProvider = NotifierProvider(AssetViewerStateNotifier.new); + +final _watchedCurrentAssetProvider = StreamProvider((ref) { + ref.watch(assetViewerProvider.select((s) => s.currentAsset?.heroTag)); + final asset = ref.read(assetViewerProvider).currentAsset; + if (asset == null) return const Stream.empty(); + return ref.read(assetServiceProvider).watchAsset(asset); +}); diff --git a/mobile/lib/providers/asset_viewer/current_asset.provider.dart b/mobile/lib/providers/asset_viewer/current_asset.provider.dart new file mode 100644 index 0000000000..0e25660ab0 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/current_asset.provider.dart @@ -0,0 +1,15 @@ +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'current_asset.provider.g.dart'; + +@riverpod +class CurrentAsset extends _$CurrentAsset { + @override + Asset? build() => null; + + void set(Asset? a) => state = a; +} + +/// Mock class for testing +abstract class CurrentAssetInternal extends _$CurrentAsset {} diff --git a/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart b/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart new file mode 100644 index 0000000000..e0d8d47d3a --- /dev/null +++ b/mobile/lib/providers/asset_viewer/current_asset.provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'current_asset.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$currentAssetHash() => r'2def10ea594152c984ae2974d687ab6856d7bdd0'; + +/// See also [CurrentAsset]. +@ProviderFor(CurrentAsset) +final currentAssetProvider = + AutoDisposeNotifierProvider.internal( + CurrentAsset.new, + name: r'currentAssetProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$currentAssetHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$CurrentAsset = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/asset_viewer/download.provider.dart b/mobile/lib/providers/asset_viewer/download.provider.dart index ba72ef60c5..a461d5766a 100644 --- a/mobile/lib/providers/asset_viewer/download.provider.dart +++ b/mobile/lib/providers/asset_viewer/download.provider.dart @@ -1,12 +1,26 @@ +import 'dart:async'; + import 'package:background_downloader/background_downloader.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/download/download_state.model.dart'; +import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; +import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/download.service.dart'; +import 'package:immich_mobile/services/share.service.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/widgets/common/share_dialog.dart'; class DownloadStateNotifier extends StateNotifier { final DownloadService _downloadService; + final ShareService _shareService; + final AlbumService _albumService; - DownloadStateNotifier(this._downloadService) + DownloadStateNotifier(this._downloadService, this._shareService, this._albumService) : super( const DownloadState( downloadStatus: TaskStatus.complete, @@ -14,29 +28,79 @@ class DownloadStateNotifier extends StateNotifier { taskProgress: {}, ), ) { - _downloadService.onImageDownloadStatus = _downloadStatusCallback; - _downloadService.onVideoDownloadStatus = _downloadStatusCallback; - _downloadService.onLivePhotoDownloadStatus = _downloadStatusCallback; + _downloadService.onImageDownloadStatus = _downloadImageCallback; + _downloadService.onVideoDownloadStatus = _downloadVideoCallback; + _downloadService.onLivePhotoDownloadStatus = _downloadLivePhotoCallback; _downloadService.onTaskProgress = _taskProgressCallback; } - void _downloadStatusCallback(TaskStatusUpdate update) { - if (update.status == TaskStatus.canceled) { - return; - } - - final existing = state.taskProgress[update.task.taskId]; - if (existing == null) { + void _updateDownloadStatus(String taskId, TaskStatus status) { + if (status == TaskStatus.canceled) { return; } state = state.copyWith( taskProgress: {} ..addAll(state.taskProgress) - ..addAll({update.task.taskId: existing.copyWith(status: update.status)}), + ..addAll({ + taskId: DownloadInfo( + progress: state.taskProgress[taskId]?.progress ?? 0, + fileName: state.taskProgress[taskId]?.fileName ?? '', + status: status, + ), + }), ); } + // Download live photo callback + void _downloadLivePhotoCallback(TaskStatusUpdate update) { + _updateDownloadStatus(update.task.taskId, update.status); + + switch (update.status) { + case TaskStatus.complete: + if (update.task.metaData.isEmpty) { + return; + } + final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id; + _downloadService.saveLivePhotos(update.task, livePhotosId); + _onDownloadComplete(update.task.taskId); + break; + + default: + break; + } + } + + // Download image callback + void _downloadImageCallback(TaskStatusUpdate update) { + _updateDownloadStatus(update.task.taskId, update.status); + + switch (update.status) { + case TaskStatus.complete: + _downloadService.saveImageWithPath(update.task); + _onDownloadComplete(update.task.taskId); + break; + + default: + break; + } + } + + // Download video callback + void _downloadVideoCallback(TaskStatusUpdate update) { + _updateDownloadStatus(update.task.taskId, update.status); + + switch (update.status) { + case TaskStatus.complete: + _downloadService.saveVideo(update.task); + _onDownloadComplete(update.task.taskId); + break; + + default: + break; + } + } + void _taskProgressCallback(TaskProgressUpdate update) { // Ignore if the task is canceled or completed if (update.progress == -2 || update.progress == -1) { @@ -57,7 +121,30 @@ class DownloadStateNotifier extends StateNotifier { ); } - Future cancelDownload(String id) async { + void _onDownloadComplete(String id) { + Future.delayed(const Duration(seconds: 2), () { + state = state.copyWith( + taskProgress: {} + ..addAll(state.taskProgress) + ..remove(id), + ); + + if (state.taskProgress.isEmpty) { + state = state.copyWith(showProgress: false); + } + _albumService.refreshDeviceAlbums(); + }); + } + + Future> downloadAllAsset(List assets) async { + return await _downloadService.downloadAll(assets); + } + + void downloadAsset(Asset asset) async { + await _downloadService.download(asset); + } + + void cancelDownload(String id) async { final isCanceled = await _downloadService.cancelDownload(id); if (isCanceled) { @@ -72,8 +159,36 @@ class DownloadStateNotifier extends StateNotifier { state = state.copyWith(showProgress: false); } } + + void shareAsset(Asset asset, BuildContext context) async { + unawaited( + showDialog( + context: context, + builder: (BuildContext buildContext) { + _shareService.shareAsset(asset, context).then((bool status) { + if (!status) { + ImmichToast.show( + context: context, + msg: 'image_viewer_page_state_provider_share_error'.tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + buildContext.pop(); + }); + return const ShareDialog(); + }, + barrierDismissible: false, + useRootNavigator: false, + ), + ); + } } final downloadStateProvider = StateNotifierProvider( - (ref) => DownloadStateNotifier(ref.watch(downloadServiceProvider)), + ((ref) => DownloadStateNotifier( + ref.watch(downloadServiceProvider), + ref.watch(shareServiceProvider), + ref.watch(albumServiceProvider), + )), ); diff --git a/mobile/lib/providers/asset_viewer/render_list_status_provider.dart b/mobile/lib/providers/asset_viewer/render_list_status_provider.dart new file mode 100644 index 0000000000..189ac85452 --- /dev/null +++ b/mobile/lib/providers/asset_viewer/render_list_status_provider.dart @@ -0,0 +1,19 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +enum RenderListStatusEnum { complete, empty, error, loading } + +final renderListStatusProvider = StateNotifierProvider((ref) { + return RenderListStatus(ref); +}); + +class RenderListStatus extends StateNotifier { + RenderListStatus(this.ref) : super(RenderListStatusEnum.complete); + + final Ref ref; + + RenderListStatusEnum get status => state; + + set status(RenderListStatusEnum value) { + state = value; + } +} diff --git a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart index 47d67c9674..66a8deb466 100644 --- a/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart +++ b/mobile/lib/providers/asset_viewer/share_intent_upload.provider.dart @@ -1,102 +1,101 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_mobile/services/share_intent_service.dart'; -import 'package:logging/logging.dart'; -import 'package:path/path.dart' as p; - -final shareIntentUploadProvider = StateNotifierProvider>( - (ref) => ShareIntentUploadStateNotifier( - ref.watch(appRouterProvider), - ref.read(foregroundUploadServiceProvider), - ref.read(shareIntentServiceProvider), - ), -); - -class ShareIntentUploadStateNotifier extends StateNotifier> { - final AppRouter router; - final ForegroundUploadService _foregroundUploadService; - final ShareIntentService _shareIntentService; - final Logger _logger = Logger('ShareIntentUploadStateNotifier'); - - ShareIntentUploadStateNotifier(this.router, this._foregroundUploadService, this._shareIntentService) : super([]); - - void init() { - _shareIntentService.onSharedMedia = onSharedMedia; - _shareIntentService.init(); - } - - void onSharedMedia(List attachments) { - router.removeWhere((route) => route.name == "ShareIntentRoute"); - clearAttachments(); - addAttachments(attachments); - unawaited(router.push(ShareIntentRoute(attachments: attachments))); - } - - void addAttachments(List attachments) { - if (attachments.isEmpty) { - return; - } - state = [...state, ...attachments]; - } - - void removeAttachment(ShareIntentAttachment attachment) { - final updatedState = state.where((element) => element != attachment).toList(); - if (updatedState.length != state.length) { - state = updatedState; - } - } - - void clearAttachments() { - if (state.isEmpty) { - return; - } - - state = []; - } - - Future uploadAll(List files) async { - for (final file in files) { - final fileId = p.hash(file.path).toString(); - _updateStatus(fileId, UploadStatus.running); - } - - await _foregroundUploadService.uploadShareIntent( - files, - onProgress: (fileId, bytes, totalBytes) { - final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; - _updateProgress(fileId, progress); - }, - onSuccess: (fileId, _) { - _updateStatus(fileId, UploadStatus.complete, progress: 1.0); - }, - onError: (fileId, errorMessage) { - _logger.warning("Upload failed for file: $fileId, error: $errorMessage"); - _updateStatus(fileId, UploadStatus.failed); - }, - ); - } - - void _updateStatus(String fileId, UploadStatus status, {double? progress}) { - final id = int.parse(fileId); - state = [ - for (final attachment in state) - if (attachment.id == id) - attachment.copyWith(status: status, uploadProgress: progress ?? attachment.uploadProgress) - else - attachment, - ]; - } - - void _updateProgress(String fileId, double progress) { - final id = int.parse(fileId); - state = [ - for (final attachment in state) - if (attachment.id == id) attachment.copyWith(uploadProgress: progress) else attachment, - ]; - } -} +import 'dart:io'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/share_intent_service.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; + +final shareIntentUploadProvider = StateNotifierProvider>( + ((ref) => ShareIntentUploadStateNotifier( + ref.watch(appRouterProvider), + ref.read(foregroundUploadServiceProvider), + ref.read(shareIntentServiceProvider), + )), +); + +class ShareIntentUploadStateNotifier extends StateNotifier> { + final AppRouter router; + final ForegroundUploadService _foregroundUploadService; + final ShareIntentService _shareIntentService; + final Logger _logger = Logger('ShareIntentUploadStateNotifier'); + + ShareIntentUploadStateNotifier(this.router, this._foregroundUploadService, this._shareIntentService) : super([]); + + void init() { + _shareIntentService.onSharedMedia = onSharedMedia; + _shareIntentService.init(); + } + + void onSharedMedia(List attachments) { + router.removeWhere((route) => route.name == "ShareIntentRoute"); + clearAttachments(); + addAttachments(attachments); + router.push(ShareIntentRoute(attachments: attachments)); + } + + void addAttachments(List attachments) { + if (attachments.isEmpty) { + return; + } + state = [...state, ...attachments]; + } + + void removeAttachment(ShareIntentAttachment attachment) { + final updatedState = state.where((element) => element != attachment).toList(); + if (updatedState.length != state.length) { + state = updatedState; + } + } + + void clearAttachments() { + if (state.isEmpty) { + return; + } + + state = []; + } + + Future uploadAll(List files) async { + for (final file in files) { + final fileId = p.hash(file.path).toString(); + _updateStatus(fileId, UploadStatus.running); + } + + await _foregroundUploadService.uploadShareIntent( + files, + onProgress: (fileId, bytes, totalBytes) { + final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; + _updateProgress(fileId, progress); + }, + onSuccess: (fileId) { + _updateStatus(fileId, UploadStatus.complete, progress: 1.0); + }, + onError: (fileId, errorMessage) { + _logger.warning("Upload failed for file: $fileId, error: $errorMessage"); + _updateStatus(fileId, UploadStatus.failed); + }, + ); + } + + void _updateStatus(String fileId, UploadStatus status, {double? progress}) { + final id = int.parse(fileId); + state = [ + for (final attachment in state) + if (attachment.id == id) + attachment.copyWith(status: status, uploadProgress: progress ?? attachment.uploadProgress) + else + attachment, + ]; + } + + void _updateProgress(String fileId, double progress) { + final id = int.parse(fileId); + state = [ + for (final attachment in state) + if (attachment.id == id) attachment.copyWith(uploadProgress: progress) else attachment, + ]; + } +} diff --git a/mobile/lib/providers/asset_viewer/video_player_provider.dart b/mobile/lib/providers/asset_viewer/video_player_provider.dart index 16df19540f..8093926873 100644 --- a/mobile/lib/providers/asset_viewer/video_player_provider.dart +++ b/mobile/lib/providers/asset_viewer/video_player_provider.dart @@ -1,22 +1,26 @@ import 'dart:async'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:logging/logging.dart'; import 'package:native_video_player/native_video_player.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; -part 'video_player_provider.freezed.dart'; - enum VideoPlaybackStatus { paused, playing, buffering, completed } -@freezed -abstract class VideoPlayerState with _$VideoPlayerState { - const factory VideoPlayerState({ - required Duration position, - required Duration duration, - required VideoPlaybackStatus status, - }) = _VideoPlayerState; +class VideoPlayerState { + final Duration position; + final Duration duration; + final VideoPlaybackStatus status; + + const VideoPlayerState({required this.position, required this.duration, required this.status}); + + VideoPlayerState copyWith({Duration? position, Duration? duration, VideoPlaybackStatus? status}) { + return VideoPlayerState( + position: position ?? this.position, + duration: duration ?? this.duration, + status: status ?? this.status, + ); + } } const _defaultState = VideoPlayerState( @@ -46,7 +50,7 @@ class VideoPlayerNotifier extends StateNotifier { void dispose() { _bufferingTimer?.cancel(); _seekTimer?.cancel(); - unawaited(WakelockPlus.disable()); + WakelockPlus.disable(); _controller = null; super.dispose(); @@ -66,9 +70,7 @@ class VideoPlayerNotifier extends StateNotifier { } Future pause() async { - if (_controller == null) { - return; - } + if (_controller == null) return; _bufferingTimer?.cancel(); @@ -81,9 +83,7 @@ class VideoPlayerNotifier extends StateNotifier { } Future play() async { - if (_controller == null) { - return; - } + if (_controller == null) return; try { await _flushSeek(); @@ -97,27 +97,21 @@ class VideoPlayerNotifier extends StateNotifier { Future _flushSeek() async { final timer = _seekTimer; - if (timer == null || !timer.isActive) { - return; - } + if (timer == null || !timer.isActive) return; timer.cancel(); await _controller?.seekTo(state.position.inMilliseconds); } void seekTo(Duration position) { - if (_controller == null || state.position == position) { - return; - } + if (_controller == null || state.position == position) return; state = state.copyWith(position: position); - if (_seekTimer?.isActive ?? false) { - return; - } + if (_seekTimer?.isActive ?? false) return; _seekTimer = Timer(const Duration(milliseconds: 150), () { - unawaited(_controller?.seekTo(state.position.inMilliseconds)); + _controller?.seekTo(state.position.inMilliseconds); }); } @@ -126,22 +120,20 @@ class VideoPlayerNotifier extends StateNotifier { switch (state.status) { case VideoPlaybackStatus.paused: - unawaited(play()); + play(); case VideoPlaybackStatus.playing || VideoPlaybackStatus.buffering: - unawaited(pause()); + pause(); case VideoPlaybackStatus.completed: - unawaited(restart()); + restart(); } } /// Pauses playback and preserves the current status for later restoration. void hold() { - if (_holdStatus != null) { - return; - } + if (_holdStatus != null) return; _holdStatus = state.status; - unawaited(pause()); + pause(); } /// Restores playback to the status before [hold] was called. @@ -151,7 +143,7 @@ class VideoPlayerNotifier extends StateNotifier { switch (status) { case VideoPlaybackStatus.playing || VideoPlaybackStatus.buffering: - unawaited(play()); + play(); default: } } @@ -178,16 +170,12 @@ class VideoPlayerNotifier extends StateNotifier { } void onNativePlaybackReady() { - if (!mounted) { - return; - } + if (!mounted) return; final playbackInfo = _controller?.playbackInfo; final videoInfo = _controller?.videoInfo; - if (playbackInfo == null || videoInfo == null) { - return; - } + if (playbackInfo == null || videoInfo == null) return; state = state.copyWith( position: Duration(milliseconds: playbackInfo.position), @@ -197,56 +185,42 @@ class VideoPlayerNotifier extends StateNotifier { } void onNativePositionChanged() { - if (!mounted || (_seekTimer?.isActive ?? false)) { - return; - } + if (!mounted || (_seekTimer?.isActive ?? false)) return; final playbackInfo = _controller?.playbackInfo; - if (playbackInfo == null) { - return; - } + if (playbackInfo == null) return; final position = Duration(milliseconds: playbackInfo.position); - if (state.position == position) { - return; - } + if (state.position == position) return; - if (state.status == VideoPlaybackStatus.playing) { - _startBufferingTimer(); - } + if (state.status == VideoPlaybackStatus.playing) _startBufferingTimer(); state = state.copyWith( position: position, - status: state.status == VideoPlaybackStatus.buffering ? VideoPlaybackStatus.playing : state.status, + status: state.status == VideoPlaybackStatus.buffering ? VideoPlaybackStatus.playing : null, ); } void onNativeStatusChanged() { - if (!mounted) { - return; - } + if (!mounted) return; final playbackInfo = _controller?.playbackInfo; - if (playbackInfo == null) { - return; - } + if (playbackInfo == null) return; final newStatus = _mapStatus(playbackInfo.status); switch (newStatus) { case VideoPlaybackStatus.playing: - unawaited(WakelockPlus.enable()); + WakelockPlus.enable(); _startBufferingTimer(); default: onNativePlaybackEnded(); } - if (state.status != newStatus) { - state = state.copyWith(status: newStatus); - } + if (state.status != newStatus) state = state.copyWith(status: newStatus); } void onNativePlaybackEnded() { - unawaited(WakelockPlus.disable()); + WakelockPlus.disable(); _bufferingTimer?.cancel(); } diff --git a/mobile/lib/providers/auth.provider.dart b/mobile/lib/providers/auth.provider.dart index 2ed4cbcf00..825d9e7bc8 100644 --- a/mobile/lib/providers/auth.provider.dart +++ b/mobile/lib/providers/auth.provider.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter_udid/flutter_udid.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; @@ -10,15 +8,15 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; -import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/services/secure_storage.service.dart'; +import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:immich_mobile/services/widget.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -129,12 +127,11 @@ class AuthNotifier extends StateNotifier { await _apiService.updateHeaders(); final serverEndpoint = Store.get(StoreKey.serverEndpoint); - final headerMap = _ref.read(appConfigProvider).network.customHeaders; - final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap); + final customHeaders = Store.tryGet(StoreKey.customHeaders); await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders); // Get the deviceid from the store if it exists, otherwise generate a new one - final String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; + String deviceId = Store.tryGet(StoreKey.deviceId) ?? await FlutterUdid.consistentUdid; UserDto? user = _userService.tryGetMyUser(); @@ -147,6 +144,7 @@ class AuthNotifier extends StateNotifier { // Due to the flow of the code, this will always happen on first login user = serverUser; await Store.put(StoreKey.deviceId, deviceId); + await Store.put(StoreKey.deviceIdHash, fastHash(deviceId)); } } on ApiException catch (error, stackTrace) { if (error.code == 401) { @@ -178,20 +176,19 @@ class AuthNotifier extends StateNotifier { } Future saveWifiName(String wifiName) async { - await _ref.read(settingsProvider).write(.networkPreferredWifiName, wifiName); + await Store.put(StoreKey.preferredWifiName, wifiName); } Future saveLocalEndpoint(String url) async { - await _ref.read(settingsProvider).write(.networkLocalEndpoint, url); - await _apiService.updateHeaders(); + await Store.put(StoreKey.localEndpoint, url); } String? getSavedWifiName() { - return _ref.read(appConfigProvider).network.preferredWifiName; + return Store.tryGet(StoreKey.preferredWifiName); } String? getSavedLocalEndpoint() { - return _ref.read(appConfigProvider).network.localEndpoint; + return Store.tryGet(StoreKey.localEndpoint); } /// Returns the current server endpoint (with /api) URL from the store diff --git a/mobile/lib/providers/background_sync.provider.dart b/mobile/lib/providers/background_sync.provider.dart index 5873e992b1..37b3145eb4 100644 --- a/mobile/lib/providers/background_sync.provider.dart +++ b/mobile/lib/providers/background_sync.provider.dart @@ -9,7 +9,6 @@ final backgroundSyncProvider = Provider((ref) { final manager = BackgroundSyncManager( onRemoteSyncStart: () { syncStatusNotifier.startRemoteSync(); - // ignore: dispose-provided-instances final backupProvider = ref.read(driftBackupProvider.notifier); if (backupProvider.mounted) { backupProvider.updateError(BackupError.none); @@ -17,7 +16,6 @@ final backgroundSyncProvider = Provider((ref) { }, onRemoteSyncComplete: (isSuccess) { syncStatusNotifier.completeRemoteSync(); - // ignore: dispose-provided-instances final backupProvider = ref.read(driftBackupProvider.notifier); if (backupProvider.mounted) { backupProvider.updateError(isSuccess == true ? BackupError.none : BackupError.syncFailed); diff --git a/mobile/lib/providers/backup/backup.provider.dart b/mobile/lib/providers/backup/backup.provider.dart index a6dc272313..5f3ad3d058 100644 --- a/mobile/lib/providers/backup/backup.provider.dart +++ b/mobile/lib/providers/backup/backup.provider.dart @@ -1,23 +1,672 @@ import 'dart:async'; +import 'dart:io'; +import 'package:collection/collection.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'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/models/auth/auth_state.model.dart'; +import 'package:immich_mobile/models/backup/available_album.model.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/success_upload_asset.model.dart'; import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; +import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/backup_album.service.dart'; import 'package:immich_mobile/services/server_info.service.dart'; +import 'package:immich_mobile/utils/backup_progress.dart'; +import 'package:immich_mobile/utils/diff.dart'; +import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; +import 'package:immich_mobile/utils/debug_print.dart'; -final backupProvider = StateNotifierProvider((ref) { - return BackupNotifier(ref.watch(serverInfoServiceProvider)); +final backupProvider = StateNotifierProvider((ref) { + return BackupNotifier( + ref.watch(backupServiceProvider), + ref.watch(serverInfoServiceProvider), + ref.watch(authProvider), + ref.watch(backgroundServiceProvider), + ref.watch(galleryPermissionNotifier.notifier), + ref.watch(albumMediaRepositoryProvider), + ref.watch(fileMediaRepositoryProvider), + ref.watch(backupAlbumServiceProvider), + ref, + ); }); -class BackupNotifier extends StateNotifier { - BackupNotifier(this._serverInfoService) - : super(const ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0)); +class BackupNotifier extends StateNotifier { + BackupNotifier( + this._backupService, + this._serverInfoService, + this._authState, + this._backgroundService, + this._galleryPermissionNotifier, + this._albumMediaRepository, + this._fileMediaRepository, + this._backupAlbumService, + this.ref, + ) : super( + BackUpState( + backupProgress: BackUpProgressEnum.idle, + allAssetsInDatabase: const [], + progressInPercentage: 0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + progressInFileSpeeds: const [], + progressInFileSpeedUpdateTime: DateTime.now(), + progressInFileSpeedUpdateSentBytes: 0, + autoBackup: Store.get(StoreKey.autoBackup, false), + backgroundBackup: Store.get(StoreKey.backgroundBackup, false), + backupRequireWifi: Store.get(StoreKey.backupRequireWifi, true), + backupRequireCharging: Store.get(StoreKey.backupRequireCharging, false), + backupTriggerDelay: Store.get(StoreKey.backupTriggerDelay, 5000), + serverInfo: const ServerDiskInfo(diskAvailable: "0", diskSize: "0", diskUse: "0", diskUsagePercentage: 0), + availableAlbums: const [], + selectedBackupAlbums: const {}, + excludedBackupAlbums: const {}, + allUniqueAssets: const {}, + selectedAlbumsBackupAssetsIds: const {}, + currentUploadAsset: CurrentUploadAsset( + id: '...', + fileCreatedAt: DateTime.parse('2020-10-04'), + fileName: '...', + fileType: '...', + fileSize: 0, + iCloudAsset: false, + ), + iCloudDownloadProgress: 0.0, + ), + ); + final log = Logger('BackupNotifier'); + final BackupService _backupService; final ServerInfoService _serverInfoService; + final AuthState _authState; + final BackgroundService _backgroundService; + final GalleryPermissionNotifier _galleryPermissionNotifier; + final AlbumMediaRepository _albumMediaRepository; + final FileMediaRepository _fileMediaRepository; + final BackupAlbumService _backupAlbumService; + final Ref ref; + Completer? _cancelToken; + + /// + /// UI INTERACTION + /// + /// Album selection + /// Due to the overlapping assets across multiple albums on the device + /// We have method to include and exclude albums + /// The total unique assets will be used for backing mechanism + /// + void addAlbumForBackup(AvailableAlbum album) { + if (state.excludedBackupAlbums.contains(album)) { + removeExcludedAlbumForBackup(album); + } + + state = state.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album}); + } + + void addExcludedAlbumForBackup(AvailableAlbum album) { + if (state.selectedBackupAlbums.contains(album)) { + removeAlbumForBackup(album); + } + state = state.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album}); + } + + void removeAlbumForBackup(AvailableAlbum album) { + Set currentSelectedAlbums = state.selectedBackupAlbums; + + currentSelectedAlbums.removeWhere((a) => a == album); + + state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums); + } + + void removeExcludedAlbumForBackup(AvailableAlbum album) { + Set currentExcludedAlbums = state.excludedBackupAlbums; + + currentExcludedAlbums.removeWhere((a) => a == album); + + state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums); + } + + Future backupAlbumSelectionDone() { + if (state.selectedBackupAlbums.isEmpty) { + // disable any backup + cancelBackup(); + setAutoBackup(false); + configureBackgroundBackup(enabled: false, onError: (msg) {}, onBatteryInfo: () {}); + } + return _updateBackupAssetCount(); + } + + void setAutoBackup(bool enabled) { + Store.put(StoreKey.autoBackup, enabled); + state = state.copyWith(autoBackup: enabled); + } + + void configureBackgroundBackup({ + bool? enabled, + bool? requireWifi, + bool? requireCharging, + int? triggerDelay, + required void Function(String msg) onError, + required void Function() onBatteryInfo, + }) async { + assert(enabled != null || requireWifi != null || requireCharging != null || triggerDelay != null); + final bool wasEnabled = state.backgroundBackup; + final bool wasWifi = state.backupRequireWifi; + final bool wasCharging = state.backupRequireCharging; + final int oldTriggerDelay = state.backupTriggerDelay; + state = state.copyWith( + backgroundBackup: enabled, + backupRequireWifi: requireWifi, + backupRequireCharging: requireCharging, + backupTriggerDelay: triggerDelay, + ); + + if (state.backgroundBackup) { + bool success = true; + if (!wasEnabled) { + if (!await _backgroundService.isIgnoringBatteryOptimizations()) { + onBatteryInfo(); + } + success &= await _backgroundService.enableService(immediate: true); + } + success &= + success && + await _backgroundService.configureService( + requireUnmetered: state.backupRequireWifi, + requireCharging: state.backupRequireCharging, + triggerUpdateDelay: state.backupTriggerDelay, + triggerMaxDelay: state.backupTriggerDelay * 10, + ); + if (success) { + await Store.put(StoreKey.backupRequireWifi, state.backupRequireWifi); + await Store.put(StoreKey.backupRequireCharging, state.backupRequireCharging); + await Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay); + await Store.put(StoreKey.backgroundBackup, state.backgroundBackup); + } else { + state = state.copyWith( + backgroundBackup: wasEnabled, + backupRequireWifi: wasWifi, + backupRequireCharging: wasCharging, + backupTriggerDelay: oldTriggerDelay, + ); + onError("backup_controller_page_background_configure_error"); + } + } else { + final bool success = await _backgroundService.disableService(); + if (!success) { + state = state.copyWith(backgroundBackup: wasEnabled); + onError("backup_controller_page_background_configure_error"); + } + } + } + + /// + /// Get all album on the device + /// Get all selected and excluded album from the user's persistent storage + /// If this is the first time performing backup - set the default selected album to be + /// the one that has all assets (`Recent` on Android, `Recents` on iOS) + /// + Future _getBackupAlbumsInfo() async { + Stopwatch stopwatch = Stopwatch()..start(); + // Get all albums on the device + List availableAlbums = []; + List albums = await _albumMediaRepository.getAll(); + + // Map of id -> album for quick album lookup later on. + Map albumMap = {}; + + log.info('Found ${albums.length} local albums'); + + for (Album album in albums) { + AvailableAlbum availableAlbum = AvailableAlbum( + album: album, + assetCount: await ref.read(albumMediaRepositoryProvider).getAssetCount(album.localId!), + ); + + availableAlbums.add(availableAlbum); + + albumMap[album.localId!] = album; + } + state = state.copyWith(availableAlbums: availableAlbums); + + final List excludedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.exclude); + final List selectedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.select); + + final Set selectedAlbums = {}; + for (final BackupAlbum ba in selectedBackupAlbums) { + final albumAsset = albumMap[ba.id]; + + if (albumAsset != null) { + selectedAlbums.add( + AvailableAlbum( + album: albumAsset, + assetCount: await _albumMediaRepository.getAssetCount(albumAsset.localId!), + lastBackup: ba.lastBackup, + ), + ); + } else { + log.severe('Selected album not found'); + } + } + + final Set excludedAlbums = {}; + for (final BackupAlbum ba in excludedBackupAlbums) { + final albumAsset = albumMap[ba.id]; + + if (albumAsset != null) { + excludedAlbums.add( + AvailableAlbum( + album: albumAsset, + assetCount: await ref.read(albumMediaRepositoryProvider).getAssetCount(albumAsset.localId!), + lastBackup: ba.lastBackup, + ), + ); + } else { + log.severe('Excluded album not found'); + } + } + + state = state.copyWith(selectedBackupAlbums: selectedAlbums, excludedBackupAlbums: excludedAlbums); + + log.info("_getBackupAlbumsInfo: Found ${availableAlbums.length} available albums"); + dPrint(() => "_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms"); + } + + /// + /// From all the selected and albums assets + /// Find the assets that are not overlapping between the two sets + /// Those assets are unique and are used as the total assets + /// + Future _updateBackupAssetCount() async { + // Save to persistent storage + await _updatePersistentAlbumsSelection(); + + final duplicatedAssetIds = await _backupService.getDuplicatedAssetIds(); + final Set assetsFromSelectedAlbums = {}; + final Set assetsFromExcludedAlbums = {}; + + for (final album in state.selectedBackupAlbums) { + final assetCount = await ref.read(albumMediaRepositoryProvider).getAssetCount(album.album.localId!); + + if (assetCount == 0) { + continue; + } + + final assets = await ref.read(albumMediaRepositoryProvider).getAssets(album.album.localId!); + + // Add album's name to the asset info + for (final asset in assets) { + List albumNames = [album.name]; + + final existingAsset = assetsFromSelectedAlbums.firstWhereOrNull((a) => a.asset.localId == asset.localId); + + if (existingAsset != null) { + albumNames.addAll(existingAsset.albumNames); + assetsFromSelectedAlbums.remove(existingAsset); + } + + assetsFromSelectedAlbums.add(BackupCandidate(asset: asset, albumNames: albumNames)); + } + } + + for (final album in state.excludedBackupAlbums) { + final assetCount = await ref.read(albumMediaRepositoryProvider).getAssetCount(album.album.localId!); + + if (assetCount == 0) { + continue; + } + + final assets = await ref.read(albumMediaRepositoryProvider).getAssets(album.album.localId!); + + for (final asset in assets) { + assetsFromExcludedAlbums.add(BackupCandidate(asset: asset, albumNames: [album.name])); + } + } + + final Set allUniqueAssets = assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); + + final allAssetsInDatabase = await _backupService.getDeviceBackupAsset(); + + if (allAssetsInDatabase == null) { + return; + } + + // Find asset that were backup from selected albums + final Set selectedAlbumsBackupAssets = Set.from(allUniqueAssets.map((e) => e.asset.localId)); + + selectedAlbumsBackupAssets.removeWhere((assetId) => !allAssetsInDatabase.contains(assetId)); + + // Remove duplicated asset from all unique assets + allUniqueAssets.removeWhere((candidate) => duplicatedAssetIds.contains(candidate.asset.localId)); + + if (allUniqueAssets.isEmpty) { + log.info("No assets are selected for back up"); + state = state.copyWith( + backupProgress: BackUpProgressEnum.idle, + allAssetsInDatabase: allAssetsInDatabase, + allUniqueAssets: {}, + selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets, + ); + } else { + state = state.copyWith( + allAssetsInDatabase: allAssetsInDatabase, + allUniqueAssets: allUniqueAssets, + selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets, + ); + } + } + + /// Get all necessary information for calculating the available albums, + /// which albums are selected or excluded + /// and then update the UI according to those information + Future getBackupInfo() async { + final isEnabled = await _backgroundService.isBackgroundBackupEnabled(); + + state = state.copyWith(backgroundBackup: isEnabled); + if (isEnabled != Store.get(StoreKey.backgroundBackup, !isEnabled)) { + await Store.put(StoreKey.backgroundBackup, isEnabled); + } + + if (state.backupProgress != BackUpProgressEnum.inBackground) { + await _getBackupAlbumsInfo(); + await updateDiskInfo(); + await _updateBackupAssetCount(); + } else { + log.warning("cannot get backup info - background backup is in progress!"); + } + } + + /// Save user selection of selected albums and excluded albums to database + Future _updatePersistentAlbumsSelection() async { + final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + final selected = state.selectedBackupAlbums.map( + (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.select), + ); + final excluded = state.excludedBackupAlbums.map( + (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.exclude), + ); + final candidates = selected.followedBy(excluded).toList(); + candidates.sortBy((e) => e.id); + + final savedBackupAlbums = await _backupAlbumService.getAll(sort: BackupAlbumSort.id); + final List toDelete = []; + final List toUpsert = []; + + diffSortedListsSync( + savedBackupAlbums, + candidates, + compare: (BackupAlbum a, BackupAlbum b) => a.id.compareTo(b.id), + both: (BackupAlbum a, BackupAlbum b) { + b.lastBackup = a.lastBackup.isAfter(b.lastBackup) ? a.lastBackup : b.lastBackup; + toUpsert.add(b); + return true; + }, + onlyFirst: (BackupAlbum a) => toDelete.add(a.isarId), + onlySecond: (BackupAlbum b) => toUpsert.add(b), + ); + + await _backupAlbumService.deleteAll(toDelete); + await _backupAlbumService.updateAll(toUpsert); + } + + /// Invoke backup process + Future startBackupProcess() async { + dPrint(() => "Start backup process"); + assert(state.backupProgress == BackUpProgressEnum.idle); + state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress); + + await getBackupInfo(); + + final hasPermission = _galleryPermissionNotifier.hasPermission; + if (hasPermission) { + await _fileMediaRepository.clearFileCache(); + + if (state.allUniqueAssets.isEmpty) { + log.info("No Asset On Device - Abort Backup Process"); + state = state.copyWith(backupProgress: BackUpProgressEnum.idle); + return; + } + + Set assetsWillBeBackup = Set.from(state.allUniqueAssets); + // Remove item that has already been backed up + for (final assetId in state.allAssetsInDatabase) { + assetsWillBeBackup.removeWhere((e) => e.asset.localId == assetId); + } + + if (assetsWillBeBackup.isEmpty) { + state = state.copyWith(backupProgress: BackUpProgressEnum.idle); + } + + // Perform Backup + _cancelToken?.complete(); + _cancelToken = Completer(); + + final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null; + + pmProgressHandler?.stream.listen((event) { + final double progress = event.progress; + state = state.copyWith(iCloudDownloadProgress: progress); + }); + + await _backupService.backupAsset( + assetsWillBeBackup, + _cancelToken!, + pmProgressHandler: pmProgressHandler, + onSuccess: _onAssetUploaded, + onProgress: _onUploadProgress, + onCurrentAsset: _onSetCurrentBackupAsset, + onError: _onBackupError, + ); + await notifyBackgroundServiceCanRun(); + } else { + await openAppSettings(); + } + } + + void setAvailableAlbums(availableAlbums) { + state = state.copyWith(availableAlbums: availableAlbums); + } + + void _onBackupError(ErrorUploadAsset errorAssetInfo) { + ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo); + } + + void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) { + state = state.copyWith(currentUploadAsset: currentUploadAsset); + } + + void cancelBackup() { + if (state.backupProgress != BackUpProgressEnum.inProgress) { + notifyBackgroundServiceCanRun(); + } + _cancelToken?.complete(); + _cancelToken = null; + state = state.copyWith( + backupProgress: BackUpProgressEnum.idle, + progressInPercentage: 0.0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + progressInFileSpeedUpdateTime: DateTime.now(), + progressInFileSpeedUpdateSentBytes: 0, + ); + } + + void _onAssetUploaded(SuccessUploadAsset result) async { + if (result.isDuplicate) { + state = state.copyWith( + allUniqueAssets: state.allUniqueAssets + .where((candidate) => candidate.asset.localId != result.candidate.asset.localId) + .toSet(), + ); + } else { + state = state.copyWith( + selectedAlbumsBackupAssetsIds: {...state.selectedAlbumsBackupAssetsIds, result.candidate.asset.localId!}, + allAssetsInDatabase: [...state.allAssetsInDatabase, result.candidate.asset.localId!], + ); + } + + if (state.allUniqueAssets.length - state.selectedAlbumsBackupAssetsIds.length == 0) { + final latestAssetBackup = state.allUniqueAssets + .map((candidate) => candidate.asset.fileModifiedAt) + .reduce((v, e) => e.isAfter(v) ? e : v); + state = state.copyWith( + selectedBackupAlbums: state.selectedBackupAlbums.map((e) => e.copyWith(lastBackup: latestAssetBackup)).toSet(), + excludedBackupAlbums: state.excludedBackupAlbums.map((e) => e.copyWith(lastBackup: latestAssetBackup)).toSet(), + backupProgress: BackUpProgressEnum.done, + progressInPercentage: 0.0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + progressInFileSpeedUpdateTime: DateTime.now(), + progressInFileSpeedUpdateSentBytes: 0, + ); + await _updatePersistentAlbumsSelection(); + } + + await updateDiskInfo(); + } + + void _onUploadProgress(int sent, int total) { + double lastUploadSpeed = state.progressInFileSpeed; + List lastUploadSpeeds = state.progressInFileSpeeds.toList(); + DateTime lastUpdateTime = state.progressInFileSpeedUpdateTime; + int lastSentBytes = state.progressInFileSpeedUpdateSentBytes; + + final now = DateTime.now(); + final duration = now.difference(lastUpdateTime); + + // Keep the upload speed average span limited, to keep it somewhat relevant + if (lastUploadSpeeds.length > 10) { + lastUploadSpeeds.removeAt(0); + } + + if (duration.inSeconds > 0) { + lastUploadSpeeds.add(((sent - lastSentBytes) / duration.inSeconds).abs().roundToDouble()); + + lastUploadSpeed = lastUploadSpeeds.average.abs().roundToDouble(); + lastUpdateTime = now; + lastSentBytes = sent; + } + + state = state.copyWith( + progressInPercentage: (sent.toDouble() / total.toDouble() * 100), + progressInFileSize: humanReadableFileBytesProgress(sent, total), + progressInFileSpeed: lastUploadSpeed, + progressInFileSpeeds: lastUploadSpeeds, + progressInFileSpeedUpdateTime: lastUpdateTime, + progressInFileSpeedUpdateSentBytes: lastSentBytes, + ); + } Future updateDiskInfo() async { final diskInfo = await _serverInfoService.getDiskInfo(); + + // Update server info if (diskInfo != null) { - state = diskInfo; + state = state.copyWith(serverInfo: diskInfo); } } + + Future _resumeBackup() async { + // Check if user is login + final accessKey = Store.tryGet(StoreKey.accessToken); + + // User has been logged out return + if (accessKey == null || !_authState.isAuthenticated) { + log.info("[_resumeBackup] not authenticated - abort"); + return; + } + + // Check if this device is enable backup by the user + if (state.autoBackup) { + // check if backup is already in process - then return + if (state.backupProgress == BackUpProgressEnum.inProgress) { + log.info("[_resumeBackup] Auto Backup is already in progress - abort"); + return; + } + + if (state.backupProgress == BackUpProgressEnum.inBackground) { + log.info("[_resumeBackup] Background backup is running - abort"); + return; + } + + if (state.backupProgress == BackUpProgressEnum.manualInProgress) { + log.info("[_resumeBackup] Manual upload is running - abort"); + return; + } + + // Run backup + log.info("[_resumeBackup] Start back up"); + await startBackupProcess(); + } + return; + } + + Future resumeBackup() async { + final List selectedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.select); + final List excludedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.exclude); + Set selectedAlbums = state.selectedBackupAlbums; + Set excludedAlbums = state.excludedBackupAlbums; + if (selectedAlbums.isNotEmpty) { + selectedAlbums = _updateAlbumsBackupTime(selectedAlbums, selectedBackupAlbums); + } + + if (excludedAlbums.isNotEmpty) { + excludedAlbums = _updateAlbumsBackupTime(excludedAlbums, excludedBackupAlbums); + } + final BackUpProgressEnum previous = state.backupProgress; + state = state.copyWith( + backupProgress: BackUpProgressEnum.inBackground, + selectedBackupAlbums: selectedAlbums, + excludedBackupAlbums: excludedAlbums, + ); + // assumes the background service is currently running + // if true, waits until it has stopped to start the backup + final bool hasLock = await _backgroundService.acquireLock(); + if (hasLock) { + state = state.copyWith(backupProgress: previous); + } + return _resumeBackup(); + } + + Set _updateAlbumsBackupTime(Set albums, List backupAlbums) { + Set result = {}; + for (BackupAlbum ba in backupAlbums) { + try { + AvailableAlbum a = albums.firstWhere((e) => e.id == ba.id); + result.add(a.copyWith(lastBackup: ba.lastBackup)); + } on StateError { + log.severe("[_updateAlbumBackupTime] failed to find album in state", "State Error", StackTrace.current); + } + } + return result; + } + + Future notifyBackgroundServiceCanRun() async { + const allowedStates = [AppLifeCycleEnum.inactive, AppLifeCycleEnum.paused, AppLifeCycleEnum.detached]; + if (allowedStates.contains(ref.read(appStateProvider.notifier).state)) { + _backgroundService.releaseLock(); + } + } + + BackUpProgressEnum get backupProgress => state.backupProgress; + + void updateBackupProgress(BackUpProgressEnum backupProgress) { + state = state.copyWith(backupProgress: backupProgress); + } } diff --git a/mobile/lib/providers/backup/backup_album.provider.dart b/mobile/lib/providers/backup/backup_album.provider.dart index 17d275ff51..f81f905c2f 100644 --- a/mobile/lib/providers/backup/backup_album.provider.dart +++ b/mobile/lib/providers/backup/backup_album.provider.dart @@ -1,18 +1,16 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/services/local_album.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; final backupAlbumProvider = StateNotifierProvider>( - (ref) => BackupAlbumNotifier(LocalAlbumService(ref.watch(driftProvider).localAlbumRepository)), + (ref) => BackupAlbumNotifier(ref.watch(localAlbumServiceProvider)), ); class BackupAlbumNotifier extends StateNotifier> { BackupAlbumNotifier(this._localAlbumService) : super([]) { - unawaited(getAll()); + getAll(); } final LocalAlbumService _localAlbumService; @@ -22,25 +20,40 @@ class BackupAlbumNotifier extends StateNotifier> { } Future selectAlbum(LocalAlbum album) async { - final selectedAlbum = album.copyWith(backupSelection: BackupSelection.selected); - await _localAlbumService.update(selectedAlbum); + album = album.copyWith(backupSelection: BackupSelection.selected); + await _localAlbumService.update(album); - state = state.map((currentAlbum) => currentAlbum.id == selectedAlbum.id ? selectedAlbum : currentAlbum).toList(); + state = state + .map( + (currentAlbum) => currentAlbum.id == album.id + ? currentAlbum.copyWith(backupSelection: BackupSelection.selected) + : currentAlbum, + ) + .toList(); } Future deselectAlbum(LocalAlbum album) async { - final deselectedAlbum = album.copyWith(backupSelection: BackupSelection.none); - await _localAlbumService.update(deselectedAlbum); + album = album.copyWith(backupSelection: BackupSelection.none); + await _localAlbumService.update(album); state = state - .map((currentAlbum) => currentAlbum.id == deselectedAlbum.id ? deselectedAlbum : currentAlbum) + .map( + (currentAlbum) => + currentAlbum.id == album.id ? currentAlbum.copyWith(backupSelection: BackupSelection.none) : currentAlbum, + ) .toList(); } Future excludeAlbum(LocalAlbum album) async { - final excludedAlbum = album.copyWith(backupSelection: BackupSelection.excluded); - await _localAlbumService.update(excludedAlbum); + album = album.copyWith(backupSelection: BackupSelection.excluded); + await _localAlbumService.update(album); - state = state.map((currentAlbum) => currentAlbum.id == excludedAlbum.id ? excludedAlbum : currentAlbum).toList(); + state = state + .map( + (currentAlbum) => currentAlbum.id == album.id + ? currentAlbum.copyWith(backupSelection: BackupSelection.excluded) + : currentAlbum, + ) + .toList(); } } diff --git a/mobile/lib/providers/backup/backup_verification.provider.dart b/mobile/lib/providers/backup/backup_verification.provider.dart new file mode 100644 index 0000000000..50270e87ca --- /dev/null +++ b/mobile/lib/providers/backup/backup_verification.provider.dart @@ -0,0 +1,101 @@ +import 'dart:async'; + +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/services/backup_verification.service.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:wakelock_plus/wakelock_plus.dart'; + +part 'backup_verification.provider.g.dart'; + +@riverpod +class BackupVerification extends _$BackupVerification { + @override + bool build() => false; + + void performBackupCheck(BuildContext context) async { + try { + state = true; + final backupState = ref.read(backupProvider); + + if (backupState.allUniqueAssets.length > backupState.selectedAlbumsBackupAssetsIds.length) { + if (context.mounted) { + ImmichToast.show( + context: context, + msg: "Backup all assets before starting this check!", + toastType: ToastType.error, + ); + } + return; + } + final connection = await Connectivity().checkConnectivity(); + if (!connection.contains(ConnectivityResult.wifi)) { + if (context.mounted) { + ImmichToast.show( + context: context, + msg: "Make sure to be connected to unmetered Wi-Fi", + toastType: ToastType.error, + ); + } + return; + } + unawaited(WakelockPlus.enable()); + + const limit = 100; + final toDelete = await ref.read(backupVerificationServiceProvider).findWronglyBackedUpAssets(limit: limit); + if (toDelete.isEmpty) { + if (context.mounted) { + ImmichToast.show( + context: context, + msg: "Did not find any corrupt asset backups!", + toastType: ToastType.success, + ); + } + } else { + if (context.mounted) { + await showDialog( + context: context, + builder: (ctx) => ConfirmDialog( + onOk: () => _performDeletion(context, toDelete), + title: "Corrupt backups!", + ok: "Delete", + content: + "Found ${toDelete.length} (max $limit at once) corrupt asset backups. " + "Run the check again to find more.\n" + "Do you want to delete the corrupt asset backups now?", + ), + ); + } + } + } finally { + unawaited(WakelockPlus.disable()); + state = false; + } + } + + Future _performDeletion(BuildContext context, List assets) async { + try { + state = true; + if (context.mounted) { + ImmichToast.show(context: context, msg: "Deleting ${assets.length} assets on the server..."); + } + await ref.read(assetProvider.notifier).deleteAssets(assets, force: true); + if (context.mounted) { + ImmichToast.show( + context: context, + msg: + "Deleted ${assets.length} assets on the server. " + "You can now start a manual backup", + toastType: ToastType.success, + ); + } + } finally { + state = false; + } + } +} diff --git a/mobile/lib/providers/backup/backup_verification.provider.g.dart b/mobile/lib/providers/backup/backup_verification.provider.g.dart new file mode 100644 index 0000000000..13f6819fa7 --- /dev/null +++ b/mobile/lib/providers/backup/backup_verification.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'backup_verification.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$backupVerificationHash() => + r'b4b34909ed1af3f28877ea457d53a4a18b6417f8'; + +/// See also [BackupVerification]. +@ProviderFor(BackupVerification) +final backupVerificationProvider = + AutoDisposeNotifierProvider.internal( + BackupVerification.new, + name: r'backupVerificationProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$backupVerificationHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$BackupVerification = AutoDisposeNotifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/backup/drift_backup.provider.dart b/mobile/lib/providers/backup/drift_backup.provider.dart index 57482bdd11..4507747c7d 100644 --- a/mobile/lib/providers/backup/drift_backup.provider.dart +++ b/mobile/lib/providers/backup/drift_backup.provider.dart @@ -1,56 +1,182 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:logging/logging.dart'; + import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/background_upload.service.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:immich_mobile/utils/upload_speed_calculator.dart'; -import 'package:logging/logging.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/services/background_upload.service.dart'; -part 'drift_backup.provider.freezed.dart'; +class EnqueueStatus { + final int enqueueCount; + final int totalCount; -@freezed -abstract class EnqueueStatus with _$EnqueueStatus { - const factory EnqueueStatus({required int enqueueCount, required int totalCount}) = _EnqueueStatus; + const EnqueueStatus({required this.enqueueCount, required this.totalCount}); + + EnqueueStatus copyWith({int? enqueueCount, int? totalCount}) { + return EnqueueStatus(enqueueCount: enqueueCount ?? this.enqueueCount, totalCount: totalCount ?? this.totalCount); + } + + @override + String toString() => 'EnqueueStatus(enqueueCount: $enqueueCount, totalCount: $totalCount)'; } -@freezed -abstract class DriftUploadStatus with _$DriftUploadStatus { - const factory DriftUploadStatus({ - required String taskId, - required String filename, - required double progress, - required int fileSize, - required String networkSpeedAsString, +class DriftUploadStatus { + final String taskId; + final String filename; + final double progress; + final int fileSize; + final String networkSpeedAsString; + final bool? isFailed; + final String? error; + + const DriftUploadStatus({ + required this.taskId, + required this.filename, + required this.progress, + required this.fileSize, + required this.networkSpeedAsString, + this.isFailed, + this.error, + }); + + DriftUploadStatus copyWith({ + String? taskId, + String? filename, + double? progress, + int? fileSize, + String? networkSpeedAsString, bool? isFailed, String? error, - }) = _DriftUploadStatus; + }) { + return DriftUploadStatus( + taskId: taskId ?? this.taskId, + filename: filename ?? this.filename, + progress: progress ?? this.progress, + fileSize: fileSize ?? this.fileSize, + networkSpeedAsString: networkSpeedAsString ?? this.networkSpeedAsString, + isFailed: isFailed ?? this.isFailed, + error: error ?? this.error, + ); + } + + @override + String toString() { + return 'DriftUploadStatus(taskId: $taskId, filename: $filename, progress: $progress, fileSize: $fileSize, networkSpeedAsString: $networkSpeedAsString, isFailed: $isFailed, error: $error)'; + } + + @override + bool operator ==(covariant DriftUploadStatus other) { + if (identical(this, other)) return true; + + return other.taskId == taskId && + other.filename == filename && + other.progress == progress && + other.fileSize == fileSize && + other.networkSpeedAsString == networkSpeedAsString && + other.isFailed == isFailed && + other.error == error; + } + + @override + int get hashCode { + return taskId.hashCode ^ + filename.hashCode ^ + progress.hashCode ^ + fileSize.hashCode ^ + networkSpeedAsString.hashCode ^ + isFailed.hashCode ^ + error.hashCode; + } } enum BackupError { none, syncFailed } -@freezed -abstract class DriftBackupState with _$DriftBackupState { - const DriftBackupState._(); +class DriftBackupState { + final int totalCount; + final int backupCount; + final int remainderCount; + final int processingCount; - const factory DriftBackupState({ - required int totalCount, - required int backupCount, - required int remainderCount, - required int processingCount, - required bool isSyncing, - @Default(BackupError.none) BackupError error, - required Map uploadItems, - @Default({}) Map iCloudDownloadProgress, - }) = _DriftBackupState; + final bool isSyncing; + final BackupError error; + + final Map uploadItems; + + final Map iCloudDownloadProgress; + + const DriftBackupState({ + required this.totalCount, + required this.backupCount, + required this.remainderCount, + required this.processingCount, + required this.isSyncing, + this.error = BackupError.none, + required this.uploadItems, + this.iCloudDownloadProgress = const {}, + }); + + DriftBackupState copyWith({ + int? totalCount, + int? backupCount, + int? remainderCount, + int? processingCount, + bool? isSyncing, + BackupError? error, + Map? uploadItems, + Map? iCloudDownloadProgress, + }) { + return DriftBackupState( + totalCount: totalCount ?? this.totalCount, + backupCount: backupCount ?? this.backupCount, + remainderCount: remainderCount ?? this.remainderCount, + processingCount: processingCount ?? this.processingCount, + isSyncing: isSyncing ?? this.isSyncing, + error: error ?? this.error, + uploadItems: uploadItems ?? this.uploadItems, + iCloudDownloadProgress: iCloudDownloadProgress ?? this.iCloudDownloadProgress, + ); + } int get errorCount => uploadItems.values.where((item) => item.isFailed == true).length; + + @override + String toString() { + return 'DriftBackupState(totalCount: $totalCount, backupCount: $backupCount, remainderCount: $remainderCount, processingCount: $processingCount, isSyncing: $isSyncing, error: $error, uploadItems: $uploadItems, iCloudDownloadProgress: $iCloudDownloadProgress)'; + } + + @override + bool operator ==(covariant DriftBackupState other) { + if (identical(this, other)) return true; + final mapEquals = const DeepCollectionEquality().equals; + + return other.totalCount == totalCount && + other.backupCount == backupCount && + other.remainderCount == remainderCount && + other.processingCount == processingCount && + other.isSyncing == isSyncing && + other.error == error && + mapEquals(other.iCloudDownloadProgress, iCloudDownloadProgress) && + mapEquals(other.uploadItems, uploadItems); + } + + @override + int get hashCode { + return totalCount.hashCode ^ + backupCount.hashCode ^ + remainderCount.hashCode ^ + processingCount.hashCode ^ + isSyncing.hashCode ^ + error.hashCode ^ + uploadItems.hashCode ^ + iCloudDownloadProgress.hashCode; + } } final driftBackupProvider = StateNotifierProvider((ref) { @@ -126,25 +252,19 @@ class DriftBackupNotifier extends StateNotifier { state = state.copyWith(isSyncing: isSyncing); } - Future startForegroundBackup(String userId) async { + Future startForegroundBackup(String userId) { // Cancel any existing backup before starting a new one if (_cancelToken != null) { - stopForegroundBackup(reason: "restarting the backup"); + stopForegroundBackup(); } state = state.copyWith(error: BackupError.none); - // A pause during the recount below nulls _cancelToken, so the run keeps its own reference. - final cancelToken = Completer(); - _cancelToken = cancelToken; - - // Re-baseline the counters against the same DB read that feeds this run's candidate list, - // otherwise a resume counts duplicate successes against the old baseline (#26215). - await getBackupStatus(userId); + _cancelToken = Completer(); return _foregroundUploadService.uploadCandidates( userId, - cancelToken, + _cancelToken!, callbacks: UploadCallbacks( onProgress: _handleForegroundBackupProgress, onSuccess: _handleForegroundBackupSuccess, @@ -154,10 +274,7 @@ class DriftBackupNotifier extends StateNotifier { ); } - void stopForegroundBackup({required String reason}) { - if (_cancelToken != null) { - _logger.info("Foreground backup cancelled: $reason"); - } + void stopForegroundBackup() { _cancelToken?.complete(); _cancelToken = null; _uploadSpeedManager.clear(); @@ -213,10 +330,6 @@ class DriftBackupNotifier extends StateNotifier { } void _handleForegroundBackupSuccess(String localAssetId, String remoteAssetId) { - if (!mounted) { - _logger.warning("Skip _handleForegroundBackupSuccess: notifier disposed"); - return; - } state = state.copyWith(backupCount: state.backupCount + 1, remainderCount: state.remainderCount - 1); _uploadSpeedManager.removeTask(localAssetId); @@ -293,8 +406,5 @@ final driftCandidateBackupAlbumInfoProvider = FutureProvider.autoDispose.family< ref, assetId, ) { - return ref - .read(driftProvider) - .localAssetRepository - .getSourceAlbums(assetId, backupSelection: BackupSelection.selected); + return ref.read(localAssetRepository).getSourceAlbums(assetId, backupSelection: BackupSelection.selected); }); diff --git a/mobile/lib/providers/backup/error_backup_list.provider.dart b/mobile/lib/providers/backup/error_backup_list.provider.dart new file mode 100644 index 0000000000..db116e4bb9 --- /dev/null +++ b/mobile/lib/providers/backup/error_backup_list.provider.dart @@ -0,0 +1,22 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; + +class ErrorBackupListNotifier extends StateNotifier> { + ErrorBackupListNotifier() : super({}); + + add(ErrorUploadAsset errorAsset) { + state = state.union({errorAsset}); + } + + remove(ErrorUploadAsset errorAsset) { + state = state.difference({errorAsset}); + } + + empty() { + state = {}; + } +} + +final errorBackupListProvider = StateNotifierProvider>( + (ref) => ErrorBackupListNotifier(), +); diff --git a/mobile/lib/providers/backup/ios_background_settings.provider.dart b/mobile/lib/providers/backup/ios_background_settings.provider.dart new file mode 100644 index 0000000000..98d55882cc --- /dev/null +++ b/mobile/lib/providers/backup/ios_background_settings.provider.dart @@ -0,0 +1,54 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/services/background.service.dart'; + +class IOSBackgroundSettings { + final bool appRefreshEnabled; + final int numberOfBackgroundTasksQueued; + final DateTime? timeOfLastFetch; + final DateTime? timeOfLastProcessing; + + const IOSBackgroundSettings({ + required this.appRefreshEnabled, + required this.numberOfBackgroundTasksQueued, + this.timeOfLastFetch, + this.timeOfLastProcessing, + }); +} + +class IOSBackgroundSettingsNotifier extends StateNotifier { + final BackgroundService _service; + IOSBackgroundSettingsNotifier(this._service) : super(null); + + IOSBackgroundSettings? get settings => state; + + Future refresh() async { + final lastFetchTime = await _service.getIOSBackupLastRun(IosBackgroundTask.fetch); + final lastProcessingTime = await _service.getIOSBackupLastRun(IosBackgroundTask.processing); + int numberOfProcesses = await _service.getIOSBackupNumberOfProcesses(); + final appRefreshEnabled = await _service.getIOSBackgroundAppRefreshEnabled(); + + // If this is enabled and there are no background processes, + // the user just enabled app refresh in Settings. + // But we don't have any background services running, since it was disabled + // before. + if (await _service.isBackgroundBackupEnabled() && numberOfProcesses == 0) { + // We need to restart the background service + await _service.enableService(); + numberOfProcesses = await _service.getIOSBackupNumberOfProcesses(); + } + + final settings = IOSBackgroundSettings( + appRefreshEnabled: appRefreshEnabled, + numberOfBackgroundTasksQueued: numberOfProcesses, + timeOfLastFetch: lastFetchTime, + timeOfLastProcessing: lastProcessingTime, + ); + + state = settings; + return settings; + } +} + +final iOSBackgroundSettingsProvider = StateNotifierProvider( + (ref) => IOSBackgroundSettingsNotifier(ref.watch(backgroundServiceProvider)), +); diff --git a/mobile/lib/providers/backup/manual_upload.provider.dart b/mobile/lib/providers/backup/manual_upload.provider.dart new file mode 100644 index 0000000000..40efcd7422 --- /dev/null +++ b/mobile/lib/providers/backup/manual_upload.provider.dart @@ -0,0 +1,391 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/widgets.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/manual_upload_state.model.dart'; +import 'package:immich_mobile/models/backup/success_upload_asset.model.dart'; +import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/backup_album.service.dart'; +import 'package:immich_mobile/services/local_notification.service.dart'; +import 'package:immich_mobile/utils/backup_progress.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; + +final manualUploadProvider = StateNotifierProvider((ref) { + return ManualUploadNotifier( + ref.watch(localNotificationService), + ref.watch(backupProvider.notifier), + ref.watch(backupServiceProvider), + ref.watch(backupAlbumServiceProvider), + ref, + ); +}); + +class ManualUploadNotifier extends StateNotifier { + final Logger _log = Logger("ManualUploadNotifier"); + final LocalNotificationService _localNotificationService; + final BackupNotifier _backupProvider; + final BackupService _backupService; + final BackupAlbumService _backupAlbumService; + final Ref ref; + Completer? _cancelToken; + + ManualUploadNotifier( + this._localNotificationService, + this._backupProvider, + this._backupService, + this._backupAlbumService, + this.ref, + ) : super( + ManualUploadState( + progressInPercentage: 0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + progressInFileSpeeds: const [], + progressInFileSpeedUpdateTime: DateTime.now(), + progressInFileSpeedUpdateSentBytes: 0, + currentUploadAsset: CurrentUploadAsset( + id: '...', + fileCreatedAt: DateTime.parse('2020-10-04'), + fileName: '...', + fileType: '...', + ), + totalAssetsToUpload: 0, + successfulUploads: 0, + currentAssetIndex: 0, + showDetailedNotification: false, + ), + ); + + String _lastPrintedDetailContent = ''; + String? _lastPrintedDetailTitle; + + static const notifyInterval = Duration(milliseconds: 500); + late final ThrottleProgressUpdate _throttledNotifiy = ThrottleProgressUpdate(_updateProgress, notifyInterval); + late final ThrottleProgressUpdate _throttledDetailNotify = ThrottleProgressUpdate( + _updateDetailProgress, + notifyInterval, + ); + + void _updateProgress(String? title, int progress, int total) { + // Guard against throttling calling this method after the upload is done + if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) { + _localNotificationService.showOrUpdateManualUploadStatus( + "backup_background_service_in_progress_notification".tr(), + formatAssetBackupProgress(state.currentAssetIndex, state.totalAssetsToUpload), + maxProgress: state.totalAssetsToUpload, + progress: state.currentAssetIndex, + showActions: true, + ); + } + } + + void _updateDetailProgress(String? title, int progress, int total) { + // Guard against throttling calling this method after the upload is done + if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) { + final String msg = total > 0 ? humanReadableBytesProgress(progress, total) : ""; + // only update if message actually differs (to stop many useless notification updates on large assets or slow connections) + if (msg != _lastPrintedDetailContent || title != _lastPrintedDetailTitle) { + _lastPrintedDetailContent = msg; + _lastPrintedDetailTitle = title; + _localNotificationService.showOrUpdateManualUploadStatus( + title ?? 'Uploading', + msg, + progress: total > 0 ? (progress * 1000) ~/ total : 0, + maxProgress: 1000, + isDetailed: true, + // Detailed noitifcation is displayed for Single asset uploads. Show actions for such case + showActions: state.totalAssetsToUpload == 1, + ); + } + } + } + + void _onAssetUploaded(SuccessUploadAsset result) { + state = state.copyWith(successfulUploads: state.successfulUploads + 1); + _backupProvider.updateDiskInfo(); + } + + void _onAssetUploadError(ErrorUploadAsset errorAssetInfo) { + ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo); + } + + void _onProgress(int sent, int total) { + double lastUploadSpeed = state.progressInFileSpeed; + List lastUploadSpeeds = state.progressInFileSpeeds.toList(); + DateTime lastUpdateTime = state.progressInFileSpeedUpdateTime; + int lastSentBytes = state.progressInFileSpeedUpdateSentBytes; + + final now = DateTime.now(); + final duration = now.difference(lastUpdateTime); + + // Keep the upload speed average span limited, to keep it somewhat relevant + if (lastUploadSpeeds.length > 10) { + lastUploadSpeeds.removeAt(0); + } + + if (duration.inSeconds > 0) { + lastUploadSpeeds.add(((sent - lastSentBytes) / duration.inSeconds).abs().roundToDouble()); + + lastUploadSpeed = lastUploadSpeeds.average.abs().roundToDouble(); + lastUpdateTime = now; + lastSentBytes = sent; + } + + state = state.copyWith( + progressInPercentage: (sent.toDouble() / total.toDouble() * 100), + progressInFileSize: humanReadableFileBytesProgress(sent, total), + progressInFileSpeed: lastUploadSpeed, + progressInFileSpeeds: lastUploadSpeeds, + progressInFileSpeedUpdateTime: lastUpdateTime, + progressInFileSpeedUpdateSentBytes: lastSentBytes, + ); + + if (state.showDetailedNotification) { + final title = "backup_background_service_current_upload_notification".tr( + namedArgs: {'filename': state.currentUploadAsset.fileName}, + ); + _throttledDetailNotify(title: title, progress: sent, total: total); + } + } + + void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) { + state = state.copyWith(currentUploadAsset: currentUploadAsset, currentAssetIndex: state.currentAssetIndex + 1); + if (state.totalAssetsToUpload > 1) { + _throttledNotifiy(); + } + if (state.showDetailedNotification) { + _throttledDetailNotify.title = "backup_background_service_current_upload_notification".tr( + namedArgs: {'filename': currentUploadAsset.fileName}, + ); + _throttledDetailNotify.progress = 0; + _throttledDetailNotify.total = 0; + } + } + + Future _startUpload(Iterable allManualUploads) async { + bool hasErrors = false; + try { + _backupProvider.updateBackupProgress(BackUpProgressEnum.manualInProgress); + + if (ref.read(galleryPermissionNotifier.notifier).hasPermission) { + await ref.read(fileMediaRepositoryProvider).clearFileCache(); + + final allAssetsFromDevice = allManualUploads.where((e) => e.isLocal && !e.isRemote).toList(); + + if (allAssetsFromDevice.length != allManualUploads.length) { + _log.warning( + '[_startUpload] Refreshed upload list -> ${allManualUploads.length - allAssetsFromDevice.length} asset will not be uploaded', + ); + } + + final selectedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.select); + final excludedBackupAlbums = await _backupAlbumService.getAllBySelection(BackupSelection.exclude); + + // Get candidates from selected albums and excluded albums + Set candidates = await _backupService.buildUploadCandidates( + selectedBackupAlbums, + excludedBackupAlbums, + useTimeFilter: false, + ); + + // Extrack candidate from allAssetsFromDevice + final uploadAssets = candidates.where( + (candidate) => + allAssetsFromDevice.firstWhereOrNull((asset) => asset.localId == candidate.asset.localId) != null, + ); + + if (uploadAssets.isEmpty) { + dPrint(() => "[_startUpload] No Assets to upload - Abort Process"); + _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); + return false; + } + + state = state.copyWith( + progressInPercentage: 0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + totalAssetsToUpload: uploadAssets.length, + successfulUploads: 0, + currentAssetIndex: 0, + currentUploadAsset: CurrentUploadAsset( + id: '...', + fileCreatedAt: DateTime.parse('2020-10-04'), + fileName: '...', + fileType: '...', + ), + ); + // Reset Error List + ref.watch(errorBackupListProvider.notifier).empty(); + + if (state.totalAssetsToUpload > 1) { + _throttledNotifiy(); + } + + // Show detailed asset if enabled in settings or if a single asset is uploaded + bool showDetailedNotification = + ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.backgroundBackupSingleProgress) || + state.totalAssetsToUpload == 1; + state = state.copyWith(showDetailedNotification: showDetailedNotification); + final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null; + + _cancelToken?.complete(); + _cancelToken = Completer(); + final bool ok = await ref + .read(backupServiceProvider) + .backupAsset( + uploadAssets, + _cancelToken!, + pmProgressHandler: pmProgressHandler, + onSuccess: _onAssetUploaded, + onProgress: _onProgress, + onCurrentAsset: _onSetCurrentBackupAsset, + onError: _onAssetUploadError, + ); + + // Close detailed notification + await _localNotificationService.closeNotification(LocalNotificationService.manualUploadDetailedNotificationID); + + _log.info( + '[_startUpload] Manual Upload Completed - success: ${state.successfulUploads},' + ' failed: ${state.totalAssetsToUpload - state.successfulUploads}', + ); + + // User cancelled upload + if (!ok && _cancelToken == null) { + await _localNotificationService.showOrUpdateManualUploadStatus( + "backup_manual_title".tr(), + "backup_manual_cancelled".tr(), + presentBanner: true, + ); + hasErrors = true; + } else if (state.successfulUploads == 0 || (!ok && _cancelToken != null)) { + await _localNotificationService.showOrUpdateManualUploadStatus( + "backup_manual_title".tr(), + "failed".tr(), + presentBanner: true, + ); + hasErrors = true; + } else { + await _localNotificationService.showOrUpdateManualUploadStatus( + "backup_manual_title".tr(), + "backup_manual_success".tr(), + presentBanner: true, + ); + } + } else { + unawaited(openAppSettings()); + dPrint(() => "[_startUpload] Do not have permission to the gallery"); + } + } catch (e) { + dPrint(() => "ERROR _startUpload: ${e.toString()}"); + hasErrors = true; + } finally { + _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); + _handleAppInActivity(); + await _localNotificationService.closeNotification(LocalNotificationService.manualUploadDetailedNotificationID); + await _backupProvider.notifyBackgroundServiceCanRun(); + } + return !hasErrors; + } + + void _handleAppInActivity() { + final appState = ref.read(appStateProvider.notifier).getAppState(); + // The app is currently in background. Perform the necessary cleanups which + // are on-hold for upload completion + if (appState != AppLifeCycleEnum.active && appState != AppLifeCycleEnum.resumed) { + ref.read(backupProvider.notifier).cancelBackup(); + } + } + + void cancelBackup() { + if (_backupProvider.backupProgress != BackUpProgressEnum.inProgress && + _backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) { + _backupProvider.notifyBackgroundServiceCanRun(); + } + _cancelToken?.complete(); + _cancelToken = null; + if (_backupProvider.backupProgress != BackUpProgressEnum.manualInProgress) { + _backupProvider.updateBackupProgress(BackUpProgressEnum.idle); + } + state = state.copyWith( + progressInPercentage: 0, + progressInFileSize: "0 B / 0 B", + progressInFileSpeed: 0, + progressInFileSpeedUpdateTime: DateTime.now(), + progressInFileSpeedUpdateSentBytes: 0, + ); + } + + Future uploadAssets(BuildContext context, Iterable allManualUploads) async { + // assumes the background service is currently running and + // waits until it has stopped to start the backup. + final bool hasLock = await ref.read(backgroundServiceProvider).acquireLock(); + if (!hasLock) { + dPrint(() => "[uploadAssets] could not acquire lock, exiting"); + ImmichToast.show( + context: context, + msg: "failed".tr(), + toastType: ToastType.info, + gravity: ToastGravity.BOTTOM, + durationInSecond: 3, + ); + return false; + } + + bool showInProgress = false; + + // check if backup is already in process - then return + if (_backupProvider.backupProgress == BackUpProgressEnum.manualInProgress) { + dPrint(() => "[uploadAssets] Manual upload is already running - abort"); + showInProgress = true; + } + + if (_backupProvider.backupProgress == BackUpProgressEnum.inProgress) { + dPrint(() => "[uploadAssets] Auto Backup is already in progress - abort"); + showInProgress = true; + return false; + } + + if (_backupProvider.backupProgress == BackUpProgressEnum.inBackground) { + dPrint(() => "[uploadAssets] Background backup is running - abort"); + showInProgress = true; + } + + if (showInProgress) { + if (context.mounted) { + ImmichToast.show( + context: context, + msg: "backup_manual_in_progress".tr(), + toastType: ToastType.info, + gravity: ToastGravity.BOTTOM, + durationInSecond: 3, + ); + } + return false; + } + + return _startUpload(allManualUploads); + } +} diff --git a/mobile/lib/providers/cast.provider.dart b/mobile/lib/providers/cast.provider.dart index 776888146b..fea95f42aa 100644 --- a/mobile/lib/providers/cast.provider.dart +++ b/mobile/lib/providers/cast.provider.dart @@ -1,7 +1,6 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart' as old_asset_entity; import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/services/gcast.service.dart'; @@ -53,13 +52,34 @@ class CastNotifier extends StateNotifier { } void loadMedia(RemoteAsset asset, bool reload) { - unawaited(_gCastService.loadMedia(asset, reload)); + _gCastService.loadMedia(asset, reload); + } + + // TODO: remove this when we migrate to new timeline + void loadMediaOld(old_asset_entity.Asset asset, bool reload) { + final remoteAsset = RemoteAsset( + id: asset.remoteId.toString(), + name: asset.name, + ownerId: asset.ownerId.toString(), + checksum: asset.checksum, + type: asset.type == old_asset_entity.AssetType.image + ? AssetType.image + : asset.type == old_asset_entity.AssetType.video + ? AssetType.video + : AssetType.other, + createdAt: asset.fileCreatedAt, + updatedAt: asset.updatedAt, + isEdited: false, + ); + + _gCastService.loadMedia(remoteAsset, reload); } Future connect(CastDestinationType type, dynamic device) async { switch (type) { case CastDestinationType.googleCast: await _gCastService.connect(device); + break; } } diff --git a/mobile/lib/providers/cleanup.provider.dart b/mobile/lib/providers/cleanup.provider.dart index df51c36fdf..4d0bdba301 100644 --- a/mobile/lib/providers/cleanup.provider.dart +++ b/mobile/lib/providers/cleanup.provider.dart @@ -1,53 +1,80 @@ -import 'dart:async'; - -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/cleanup.service.dart'; -part 'cleanup.provider.freezed.dart'; +class CleanupState { + final DateTime? selectedDate; + final List assetsToDelete; + final int totalBytes; + final bool isScanning; + final bool isDeleting; + final AssetKeepType keepMediaType; + final bool keepFavorites; + final Set keepAlbumIds; -@freezed -abstract class CleanupState with _$CleanupState { - const factory CleanupState({ + const CleanupState({ + this.selectedDate, + this.assetsToDelete = const [], + this.totalBytes = 0, + this.isScanning = false, + this.isDeleting = false, + this.keepMediaType = AssetKeepType.none, + this.keepFavorites = true, + this.keepAlbumIds = const {}, + }); + + CleanupState copyWith({ DateTime? selectedDate, - @Default([]) List assetsToDelete, - @Default(0) int totalBytes, - @Default(false) bool isScanning, - @Default(false) bool isDeleting, - @Default(AssetKeepType.none) AssetKeepType keepMediaType, - @Default(true) bool keepFavorites, - @Default({}) Set keepAlbumIds, - }) = _CleanupState; + List? assetsToDelete, + int? totalBytes, + bool? isScanning, + bool? isDeleting, + AssetKeepType? keepMediaType, + bool? keepFavorites, + Set? keepAlbumIds, + }) { + return CleanupState( + selectedDate: selectedDate ?? this.selectedDate, + assetsToDelete: assetsToDelete ?? this.assetsToDelete, + totalBytes: totalBytes ?? this.totalBytes, + isScanning: isScanning ?? this.isScanning, + isDeleting: isDeleting ?? this.isDeleting, + keepMediaType: keepMediaType ?? this.keepMediaType, + keepFavorites: keepFavorites ?? this.keepFavorites, + keepAlbumIds: keepAlbumIds ?? this.keepAlbumIds, + ); + } } final cleanupProvider = StateNotifierProvider((ref) { return CleanupNotifier( ref.watch(cleanupServiceProvider), ref.watch(currentUserProvider)?.id, - ref.watch(settingsProvider), + ref.watch(appSettingsServiceProvider), ); }); class CleanupNotifier extends StateNotifier { final CleanupService _cleanupService; final String? _userId; - final SettingsRepository _settingsRepository; + final AppSettingsService _appSettingsService; - CleanupNotifier(this._cleanupService, this._userId, this._settingsRepository) : super(const CleanupState()) { + CleanupNotifier(this._cleanupService, this._userId, this._appSettingsService) : super(const CleanupState()) { _loadPersistedSettings(); } void _loadPersistedSettings() { - final cleanup = _settingsRepository.appConfig.cleanup; - final keepFavorites = cleanup.keepFavorites; - final keepMediaType = cleanup.keepMediaType; - final keepAlbumIds = cleanup.keepAlbumIds.toSet(); - final cutoffDaysAgo = cleanup.cutoffDaysAgo; + final keepFavorites = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepFavorites); + final keepMediaTypeIndex = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepMediaType); + final keepAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepAlbumIds); + final cutoffDaysAgo = _appSettingsService.getSetting(AppSettingsEnum.cleanupCutoffDaysAgo); + + final keepMediaType = AssetKeepType.values[keepMediaTypeIndex.clamp(0, AssetKeepType.values.length - 1)]; + final keepAlbumIds = keepAlbumIdsString.isEmpty ? {} : keepAlbumIdsString.split(',').toSet(); final selectedDate = cutoffDaysAgo >= 0 ? DateTime.now().subtract(Duration(days: cutoffDaysAgo)) : null; state = state.copyWith( @@ -62,18 +89,18 @@ class CleanupNotifier extends StateNotifier { state = state.copyWith(selectedDate: date, assetsToDelete: []); if (date != null) { final daysAgo = DateTime.now().difference(date).inDays; - unawaited(_settingsRepository.write(.cleanupCutoffDaysAgo, daysAgo)); + _appSettingsService.setSetting(AppSettingsEnum.cleanupCutoffDaysAgo, daysAgo); } } void setKeepMediaType(AssetKeepType keepMediaType) { state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []); - unawaited(_settingsRepository.write(.cleanupKeepMediaType, keepMediaType)); + _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepMediaType, keepMediaType.index); } void setKeepFavorites(bool keepFavorites) { state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []); - unawaited(_settingsRepository.write(.cleanupKeepFavorites, keepFavorites)); + _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepFavorites, keepFavorites); } void toggleKeepAlbum(String albumId) { @@ -93,7 +120,7 @@ class CleanupNotifier extends StateNotifier { } void _persistExcludedAlbumIds(Set albumIds) { - unawaited(_settingsRepository.write(.cleanupKeepAlbumIds, albumIds.toList())); + _appSettingsService.setSetting(AppSettingsEnum.cleanupKeepAlbumIds, albumIds.join(',')); } void cleanupStaleAlbumIds(Set existingAlbumIds) { @@ -106,10 +133,8 @@ class CleanupNotifier extends StateNotifier { } void applyDefaultAlbumSelections(List<(String id, String name)> albums) { - final isInitialized = _settingsRepository.appConfig.cleanup.defaultsInitialized; - if (isInitialized) { - return; - } + final isInitialized = _appSettingsService.getSetting(AppSettingsEnum.cleanupDefaultsInitialized); + if (isInitialized) return; final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums); @@ -119,7 +144,7 @@ class CleanupNotifier extends StateNotifier { _persistExcludedAlbumIds(keepAlbumIds); } - unawaited(_settingsRepository.write(.cleanupDefaultsInitialized, true)); + _appSettingsService.setSetting(AppSettingsEnum.cleanupDefaultsInitialized, true); } Future scanAssets() async { diff --git a/mobile/lib/providers/db.provider.dart b/mobile/lib/providers/db.provider.dart new file mode 100644 index 0000000000..e03e037f36 --- /dev/null +++ b/mobile/lib/providers/db.provider.dart @@ -0,0 +1,5 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:isar/isar.dart'; + +// overwritten in main.dart due to async loading +final dbProvider = Provider((_) => throw UnimplementedError()); diff --git a/mobile/lib/providers/feature_message.provider.dart b/mobile/lib/providers/feature_message.provider.dart deleted file mode 100644 index 459464524d..0000000000 --- a/mobile/lib/providers/feature_message.provider.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/services/feature_message.service.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; - -final featureMessageServiceProvider = Provider( - (ref) => FeatureMessageService(ref.read(settingsProvider)), -); diff --git a/mobile/lib/providers/folder.provider.dart b/mobile/lib/providers/folder.provider.dart index 816a88996e..696d7e19fd 100644 --- a/mobile/lib/providers/folder.provider.dart +++ b/mobile/lib/providers/folder.provider.dart @@ -1,8 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/models/folder/root_folder.model.dart'; import 'package:immich_mobile/services/folder.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:logging/logging.dart'; class FolderStructureNotifier extends StateNotifier> { @@ -26,7 +26,7 @@ final folderStructureProvider = StateNotifierProvider>> { +class FolderRenderListNotifier extends StateNotifier> { final FolderService _folderService; final RootFolder _folder; final Logger _log = Logger("FolderAssetsNotifier"); @@ -36,7 +36,8 @@ class FolderRenderListNotifier extends StateNotifier fetchAssets(SortOrder order) async { try { final assets = await _folderService.getFolderAssets(_folder, order); - state = AsyncData(assets); + final renderList = await RenderList.fromAssets(assets, GroupAssetsBy.none); + state = AsyncData(renderList); } catch (e, stack) { _log.severe("Failed to fetch folder assets", e, stack); state = AsyncError(e, stack); @@ -45,9 +46,6 @@ class FolderRenderListNotifier extends StateNotifier>, RootFolder>(( - ref, - folder, - ) { + StateNotifierProvider.family, RootFolder>((ref, folder) { return FolderRenderListNotifier(ref.watch(folderServiceProvider), folder); }); diff --git a/mobile/lib/providers/gallery_permission.provider.dart b/mobile/lib/providers/gallery_permission.provider.dart index 6be504bd2e..6e4fc69926 100644 --- a/mobile/lib/providers/gallery_permission.provider.dart +++ b/mobile/lib/providers/gallery_permission.provider.dart @@ -1,28 +1,108 @@ -import 'dart:async'; +import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/services/device_permission.service.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:permission_handler/permission_handler.dart'; -final galleryPermissionServiceProvider = Provider((ref) { - return DevicePermissionService(ref.watch(permissionRepositoryProvider)); -}); - -class GalleryPermissionNotifier extends StateNotifier { - GalleryPermissionNotifier(this._service) : super(.denied) { - unawaited(getGalleryPermissionStatus()); +class GalleryPermissionNotifier extends StateNotifier { + GalleryPermissionNotifier() + : super(PermissionStatus.denied) // Denied is the initial state + { + // Sets the initial state + getGalleryPermissionStatus(); } - final DevicePermissionService _service; + get hasPermission => state.isGranted || state.isLimited; - bool get hasPermission => state.hasAccess; + /// Requests the gallery permission + Future requestGalleryPermission() async { + PermissionStatus result; + // Android 32 and below uses Permission.storage + if (Platform.isAndroid) { + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt <= 32) { + // Android 32 and below need storage + final permission = await Permission.storage.request(); + result = permission; + } else { + // Android 33 need photo & video + final photos = await Permission.photos.request(); + if (!photos.isGranted) { + // Don't ask twice for the same permission + state = photos; + return photos; + } + final videos = await Permission.videos.request(); - Future requestGalleryPermission() async => state = await _service.requestGallery(); + // Return the joint result of those two permissions + final PermissionStatus status; + if ((photos.isGranted && videos.isGranted) || (photos.isLimited && videos.isLimited)) { + status = PermissionStatus.granted; + } else if (photos.isDenied || videos.isDenied) { + status = PermissionStatus.denied; + } else if (photos.isPermanentlyDenied || videos.isPermanentlyDenied) { + status = PermissionStatus.permanentlyDenied; + } else { + status = PermissionStatus.denied; + } - Future getGalleryPermissionStatus() async => state = await _service.galleryStatus(); + result = status; + } + if (result == PermissionStatus.granted && androidInfo.version.sdkInt >= 29) { + result = await Permission.accessMediaLocation.request(); + } + } else { + // iOS can use photos + final photos = await Permission.photos.request(); + result = photos; + } + state = result; + return result; + } + + /// Checks the current state of the gallery permissions without + /// requesting them again + Future getGalleryPermissionStatus() async { + PermissionStatus result; + // Android 32 and below uses Permission.storage + if (Platform.isAndroid) { + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt <= 32) { + // Android 32 and below need storage + final permission = await Permission.storage.status; + result = permission; + } else { + // Android 33 needs photo & video + final photos = await Permission.photos.status; + final videos = await Permission.videos.status; + + // Return the joint result of those two permissions + final PermissionStatus status; + if ((photos.isGranted && videos.isGranted) || (photos.isLimited && videos.isLimited)) { + status = PermissionStatus.granted; + } else if (photos.isDenied || videos.isDenied) { + status = PermissionStatus.denied; + } else if (photos.isPermanentlyDenied || videos.isPermanentlyDenied) { + status = PermissionStatus.permanentlyDenied; + } else { + status = PermissionStatus.denied; + } + + result = status; + } + if (state == PermissionStatus.granted && androidInfo.version.sdkInt >= 29) { + result = await Permission.accessMediaLocation.status; + } + } else { + // iOS can use photos + final photos = await Permission.photos.status; + result = photos; + } + state = result; + return result; + } } -final galleryPermissionNotifier = StateNotifierProvider( - (ref) => GalleryPermissionNotifier(ref.watch(galleryPermissionServiceProvider)), +final galleryPermissionNotifier = StateNotifierProvider( + (ref) => GalleryPermissionNotifier(), ); diff --git a/mobile/lib/providers/haptic_feedback.provider.dart b/mobile/lib/providers/haptic_feedback.provider.dart index 850935163d..711c6fa4e2 100644 --- a/mobile/lib/providers/haptic_feedback.provider.dart +++ b/mobile/lib/providers/haptic_feedback.provider.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; @@ -15,33 +13,33 @@ class HapticNotifier extends StateNotifier { HapticNotifier(this._ref) : super(null); - void selectionClick() { + selectionClick() { if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) { - unawaited(HapticFeedback.selectionClick()); + HapticFeedback.selectionClick(); } } - void lightImpact() { + lightImpact() { if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) { - unawaited(HapticFeedback.lightImpact()); + HapticFeedback.lightImpact(); } } - void mediumImpact() { + mediumImpact() { if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) { - unawaited(HapticFeedback.mediumImpact()); + HapticFeedback.mediumImpact(); } } - void heavyImpact() { + heavyImpact() { if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) { - unawaited(HapticFeedback.heavyImpact()); + HapticFeedback.heavyImpact(); } } - void vibrate() { + vibrate() { if (_ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableHapticFeedback)) { - unawaited(HapticFeedback.vibrate()); + HapticFeedback.vibrate(); } } } diff --git a/mobile/lib/providers/image/exceptions/image_loading_exception.dart b/mobile/lib/providers/image/exceptions/image_loading_exception.dart new file mode 100644 index 0000000000..98f633a88f --- /dev/null +++ b/mobile/lib/providers/image/exceptions/image_loading_exception.dart @@ -0,0 +1,5 @@ +/// An exception for the [ImageLoader] and the Immich image providers +class ImageLoadingException implements Exception { + final String message; + const ImageLoadingException(this.message); +} diff --git a/mobile/lib/providers/immich_logo_provider.dart b/mobile/lib/providers/immich_logo_provider.dart index d9e51eccac..b24294fc2e 100644 --- a/mobile/lib/providers/immich_logo_provider.dart +++ b/mobile/lib/providers/immich_logo_provider.dart @@ -2,9 +2,13 @@ import 'dart:convert'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final immichLogoProvider = FutureProvider.autoDispose((ref) async { +part 'immich_logo_provider.g.dart'; + +@riverpod +Future immichLogo(Ref _) async { final json = await rootBundle.loadString('assets/immich-logo.json'); final j = jsonDecode(json); return base64Decode(j['content']); -}); +} diff --git a/mobile/lib/providers/immich_logo_provider.g.dart b/mobile/lib/providers/immich_logo_provider.g.dart new file mode 100644 index 0000000000..f1af433c1b --- /dev/null +++ b/mobile/lib/providers/immich_logo_provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'immich_logo_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$immichLogoHash() => r'6de7fcca1ef9acef6ab7398eb0c664080747e0ea'; + +/// See also [immichLogo]. +@ProviderFor(immichLogo) +final immichLogoProvider = AutoDisposeFutureProvider.internal( + immichLogo, + name: r'immichLogoProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$immichLogoHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ImmichLogoRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/action.provider.dart b/mobile/lib/providers/infrastructure/action.provider.dart index 3fb9f8acbc..bad0d986d0 100644 --- a/mobile/lib/providers/infrastructure/action.provider.dart +++ b/mobile/lib/providers/infrastructure/action.provider.dart @@ -1,49 +1,49 @@ -// ignore_for_file: use-ref-and-state-synchronously - import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/remote_album.service.dart'; +import 'package:immich_mobile/domain/services/asset.service.dart'; +import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/backup/asset_upload_progress.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/asset_viewer/asset.provider.dart' show assetExifProvider; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/services/action.service.dart'; +import 'package:immich_mobile/services/download.service.dart'; +import 'package:immich_mobile/services/timeline.service.dart'; import 'package:immich_mobile/services/foreground_upload.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final actionProvider = NotifierProvider(ActionNotifier.new, dependencies: [multiSelectProvider]); +final actionProvider = NotifierProvider( + ActionNotifier.new, + dependencies: [multiSelectProvider, timelineServiceProvider], +); class ActionResult { final int count; final bool success; final String? error; - final List remoteAssetIds; - final int failedCount; - const ActionResult({ - required this.count, - required this.success, - this.error, - this.remoteAssetIds = const [], - this.failedCount = 0, - }); + const ActionResult({required this.count, required this.success, this.error}); @override - String toString() => - 'ActionResult(count: $count, success: $success, error: $error, remoteAssetIds: $remoteAssetIds, failedCount: $failedCount)'; + String toString() => 'ActionResult(count: $count, success: $success, error: $error)'; } class ActionNotifier extends Notifier { final Logger _logger = Logger('ActionNotifier'); late ActionService _service; late ForegroundUploadService _foregroundUploadService; + late DownloadService _downloadService; + late AssetService _assetService; ActionNotifier() : super(); @@ -51,17 +51,79 @@ class ActionNotifier extends Notifier { void build() { _foregroundUploadService = ref.watch(foregroundUploadServiceProvider); _service = ref.watch(actionServiceProvider); + _assetService = ref.watch(assetServiceProvider); + _downloadService = ref.watch(downloadServiceProvider); + _downloadService.onImageDownloadStatus = _downloadImageCallback; + _downloadService.onVideoDownloadStatus = _downloadVideoCallback; + _downloadService.onLivePhotoDownloadStatus = _downloadLivePhotoCallback; + } + + void _downloadImageCallback(TaskStatusUpdate update) { + if (update.status == TaskStatus.complete) { + _downloadService.saveImageWithPath(update.task); + } + } + + void _downloadVideoCallback(TaskStatusUpdate update) { + if (update.status == TaskStatus.complete) { + _downloadService.saveVideo(update.task); + } + } + + void _downloadLivePhotoCallback(TaskStatusUpdate update) async { + if (update.status == TaskStatus.complete) { + final livePhotosId = LivePhotosMetadata.fromJson(update.task.metaData).id; + unawaited(_downloadService.saveLivePhotos(update.task, livePhotosId)); + } } List _getRemoteIdsForSource(ActionSource source) { return _getAssets(source).whereType().toIds().toList(growable: false); } + List _getLocalIdsForSource(ActionSource source, {bool ignoreLocalOnly = false}) { + final Set assets = _getAssets(source); + final List localIds = []; + + for (final asset in assets) { + if (ignoreLocalOnly && asset.storage != AssetState.merged) { + continue; + } + if (asset is LocalAsset) { + localIds.add(asset.id); + } else if (asset is RemoteAsset && asset.localId != null) { + localIds.add(asset.localId!); + } + } + + return localIds; + } + + List _getOwnedRemoteIdsForSource(ActionSource source) { + final ownerId = ref.read(currentUserProvider)?.id; + return _getAssets(source).whereType().ownedAssets(ownerId).toIds().toList(growable: false); + } + + List _getOwnedRemoteAssetsForSource(ActionSource source) { + final ownerId = ref.read(currentUserProvider)?.id; + return _getIdsForSource(source).ownedAssets(ownerId).toList(); + } + + Iterable _getIdsForSource(ActionSource source) { + final Set assets = _getAssets(source); + return switch (T) { + const (RemoteAsset) => assets.whereType(), + const (LocalAsset) => assets.whereType(), + _ => const [], + } + as Iterable; + } + Set _getAssets(ActionSource source) { return switch (source) { ActionSource.timeline => ref.read(multiSelectProvider).selectedAssets, ActionSource.viewer => switch (ref.read(assetViewerProvider).currentAsset) { - final BaseAsset asset => {asset}, + BaseAsset asset => {asset}, null => const {}, }, }; @@ -77,74 +139,226 @@ class ActionNotifier extends Notifier { return ActionResult(count: assets.length, success: true); } - Future emptyTrash(String userId) async { + Future shareLink(ActionSource source, BuildContext context) async { + final ids = _getRemoteIdsForSource(source); try { - final count = await _service.emptyTrash(userId); - return ActionResult(count: count, success: true); + await _service.shareLink(ids, context); + return ActionResult(count: ids.length, success: true); } catch (error, stack) { - _logger.severe('Failed to empty trash', error, stack); - return ActionResult(count: 0, success: false, error: error.toString()); + _logger.severe('Failed to create shared link for assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); } } - Future restoreAllTrash(String userId) async { + Future favorite(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); try { - final count = await _service.restoreAllTrash(userId); - return ActionResult(count: count, success: true); + await _service.favorite(ids); + return ActionResult(count: ids.length, success: true); } catch (error, stack) { - _logger.severe('Failed to restore all trash assets', error, stack); - return ActionResult(count: 0, success: false, error: error.toString()); + _logger.severe('Failed to favorite assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); } } - Future addToAlbum(ActionSource source, RemoteAlbum album) async { - final selected = _getAssets(source).toList(growable: false); - if (selected.isEmpty) { - return const ActionResult(count: 0, success: true); + Future unFavorite(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.unFavorite(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to unfavorite assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future archive(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.archive(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to archive assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future unArchive(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.unArchive(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to unarchive assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future moveToLockFolder(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + final localIds = _getLocalIdsForSource(source, ignoreLocalOnly: true); + try { + await _service.moveToLockFolder(ids, localIds); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to move assets to lock folder', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future removeFromLockFolder(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.removeFromLockFolder(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to remove assets from lock folder', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future trash(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + + try { + await _service.trash(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to trash assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future restoreTrash(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.restoreTrash(ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to restore trash assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future trashRemoteAndDeleteLocal(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + final localIds = _getLocalIdsForSource(source); + try { + await _service.trashRemoteAndDeleteLocal(ids, localIds); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to delete assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future deleteRemoteAndLocal(ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + final localIds = _getLocalIdsForSource(source); + try { + await _service.deleteRemoteAndLocal(ids, localIds); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to delete assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future deleteLocal(ActionSource source, BuildContext context) async { + final assets = _getAssets(source); + bool? backedUpOnly = assets.every((asset) => asset.storage == AssetState.merged) + ? true + : await showDialog( + context: context, + builder: (BuildContext context) => DeleteLocalOnlyDialog(onDeleteLocal: (_) {}), + ); + + if (backedUpOnly == null) { + // User cancelled the dialog + return null; } - final candidates = RemoteAlbumService.categorizeCandidates(selected); - final remoteIds = candidates.remoteAssetIds; - final localAssets = candidates.localAssetsToUpload; - final albumNotifier = ref.read(remoteAlbumProvider.notifier); + final List ids; + if (backedUpOnly) { + ids = assets.where((asset) => asset.storage == AssetState.merged).map((asset) => asset.localId!).toList(); + } else { + ids = _getLocalIdsForSource(source); + } - int addedRemote = 0; - int failedRemote = 0; - if (remoteIds.isNotEmpty) { - try { - final result = await albumNotifier.addAssets(album.id, remoteIds); - addedRemote = result.added; - failedRemote = result.failed; - } catch (error, stack) { - _logger.severe('Failed to add assets to album ${album.id}', error, stack); - return ActionResult(count: 0, success: false, error: error.toString()); + try { + final deletedCount = await _service.deleteLocal(ids); + return ActionResult(count: deletedCount, success: true); + } catch (error, stack) { + _logger.severe('Failed to delete assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future editLocation(ActionSource source, BuildContext context) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + final isEdited = await _service.editLocation(ids, context); + if (!isEdited) { + return null; } + + // This must be called since editing location + // does not update the currentAsset which means + // the exif provider will not be refreshed automatically + if (source == ActionSource.viewer) { + final currentAsset = ref.read(assetViewerProvider).currentAsset; + if (currentAsset != null) { + ref.invalidate(assetExifProvider(currentAsset)); + } + } + + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to edit location for assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future editDateTime(ActionSource source, BuildContext context) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + final isEdited = await _service.editDateTime(ids, context); + if (!isEdited) { + return null; + } + + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to edit date and time for assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future removeFromAlbum(ActionSource source, String albumId) async { + final ids = _getRemoteIdsForSource(source); + try { + final removedCount = await _service.removeFromAlbum(ids, albumId); + return ActionResult(count: removedCount, success: true); + } catch (error, stack) { + _logger.severe('Failed to remove assets from album', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future setAlbumCover(ActionSource source, String albumId) async { + final assets = _getAssets(source); + final asset = assets.first; + if (asset is! RemoteAsset) { + return const ActionResult(count: 1, success: false, error: 'Asset must be remote'); } - // Keep the selection available for retry if the remote add fails. Once the - // album mutation succeeds, clear timeline selection so upload overlays can render. - if (source == ActionSource.timeline) { - ref.read(multiSelectProvider.notifier).reset(); + try { + await _service.setAlbumCover(albumId, asset.id); + return const ActionResult(count: 1, success: true); + } catch (error, stack) { + _logger.severe('Failed to set album cover', error, stack); + return ActionResult(count: 1, success: false, error: error.toString()); } - - if (localAssets.isEmpty) { - return ActionResult(count: addedRemote, success: true, failedCount: failedRemote); - } - - final uploadResult = await upload( - source, - assets: localAssets, - onAssetUploaded: (asset, remoteId) async { - await albumNotifier.linkUploadedAssetToAlbum(album.id, asset, remoteId); - }, - ); - - return ActionResult( - count: addedRemote + uploadResult.count, - success: uploadResult.success, - error: uploadResult.error, - failedCount: failedRemote, - ); } Future updateDescription(ActionSource source, String description) async { @@ -163,7 +377,7 @@ class ActionNotifier extends Notifier { } } - Future updateRating(ActionSource source, int? rating) async { + Future updateRating(ActionSource source, int rating) async { final ids = _getRemoteIdsForSource(source); if (ids.length != 1) { _logger.warning('updateRating called with multiple assets, expected single asset'); @@ -179,24 +393,69 @@ class ActionNotifier extends Notifier { } } - Future upload( - ActionSource source, { - List? assets, - FutureOr Function(LocalAsset asset, String remoteId)? onAssetUploaded, - }) async { - final assetsToUpload = assets ?? _getAssets(source).whereType().toList(); - final assetById = {for (final a in assetsToUpload) a.id: a}; - final uploadedAssetIds = {}; - final failedAssetIds = {}; - final postUploadTasks = >[]; - if (assetsToUpload.isEmpty) { - return const ActionResult(count: 0, success: false, error: 'No assets to upload'); + Future stack(String userId, ActionSource source) async { + final ids = _getOwnedRemoteIdsForSource(source); + try { + await _service.stack(userId, ids); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to stack assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); } + } + + Future unStack(ActionSource source) async { + final assets = _getOwnedRemoteAssetsForSource(source); + try { + await _service.unStack(assets.map((e) => e.stackId).nonNulls.toList()); + if (source == ActionSource.viewer) { + final updatedParent = await _assetService.getRemoteAsset(assets.first.id); + if (updatedParent != null) { + ref.read(assetViewerProvider.notifier).setAsset(updatedParent); + } + } + + return ActionResult(count: assets.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to unstack assets', error, stack); + return ActionResult(count: assets.length, success: false); + } + } + + Future shareAssets( + ActionSource source, + BuildContext context, { + Completer? cancelCompleter, + }) async { + final ids = _getAssets(source).toList(growable: false); + + try { + await _service.shareAssets(ids, context, cancelCompleter: cancelCompleter); + return ActionResult(count: ids.length, success: true); + } catch (error, stack) { + _logger.severe('Failed to share assets', error, stack); + return ActionResult(count: ids.length, success: false, error: error.toString()); + } + } + + Future downloadAll(ActionSource source) async { + final assets = _getAssets(source).whereType().toList(growable: false); + try { + final didEnqueue = await _service.downloadAll(assets); + final enqueueCount = didEnqueue.where((e) => e).length; + return ActionResult(count: enqueueCount, success: true); + } catch (error, stack) { + _logger.severe('Failed to download assets', error, stack); + return ActionResult(count: assets.length, success: false, error: error.toString()); + } + } + + Future upload(ActionSource source, {List? assets}) async { + final assetsToUpload = assets ?? _getAssets(source).whereType().toList(); final progressNotifier = ref.read(assetUploadProgressProvider.notifier); final cancelToken = Completer(); ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken; - final remoteAssetIds = []; // Initialize progress for all assets for (final asset in assetsToUpload) { @@ -213,45 +472,17 @@ class ActionNotifier extends Notifier { progressNotifier.setProgress(localAssetId, progress); }, onSuccess: (localAssetId, remoteAssetId) { - remoteAssetIds.add(remoteAssetId); progressNotifier.remove(localAssetId); - uploadedAssetIds.add(localAssetId); - final asset = assetById[localAssetId]; - final callback = onAssetUploaded; - if (asset != null && callback != null) { - postUploadTasks.add( - Future.sync(() => callback(asset, remoteAssetId)).catchError((Object error, StackTrace stack) { - failedAssetIds.add(localAssetId); - progressNotifier.setError(localAssetId); - _logger.warning('Post-upload callback failed for $localAssetId', error, stack); - }), - ); - } }, onError: (localAssetId, errorMessage) { - failedAssetIds.add(localAssetId); progressNotifier.setError(localAssetId); }, ), ); - - await Future.wait(postUploadTasks); - final successCount = uploadedAssetIds.difference(failedAssetIds).length; - final isSuccess = successCount == assetsToUpload.length && failedAssetIds.isEmpty; - - return ActionResult( - count: successCount, - success: isSuccess, - error: isSuccess ? null : 'Failed to upload ${assetsToUpload.length - successCount} assets', - ); + return ActionResult(count: assetsToUpload.length, success: true); } catch (error, stack) { _logger.severe('Failed manually upload assets', error, stack); - - return ActionResult( - count: uploadedAssetIds.difference(failedAssetIds).length, - success: false, - error: error.toString(), - ); + return ActionResult(count: assetsToUpload.length, success: false, error: error.toString()); } finally { ref.read(manualUploadCancelTokenProvider.notifier).state = null; Future.delayed(const Duration(seconds: 2), () { @@ -263,4 +494,9 @@ class ActionNotifier extends Notifier { extension on Iterable { Iterable toIds() => map((e) => e.id); + + Iterable ownedAssets(String? ownerId) { + if (ownerId == null) return const []; + return whereType().where((a) => a.ownerId == ownerId); + } } diff --git a/mobile/lib/providers/infrastructure/album.provider.dart b/mobile/lib/providers/infrastructure/album.provider.dart index 579cc17838..1ddabc1604 100644 --- a/mobile/lib/providers/infrastructure/album.provider.dart +++ b/mobile/lib/providers/infrastructure/album.provider.dart @@ -5,27 +5,36 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/local_album.service.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; + +final localAlbumRepository = Provider( + (ref) => DriftLocalAlbumRepository(ref.watch(driftProvider)), +); + +final localAlbumServiceProvider = Provider( + (ref) => LocalAlbumService(ref.watch(localAlbumRepository)), +); final localAlbumProvider = FutureProvider>( - (ref) => LocalAlbumService(ref.watch(driftProvider).localAlbumRepository) + (ref) => LocalAlbumService(ref.watch(localAlbumRepository)) .getAll(sortBy: {SortLocalAlbumsBy.newestAsset}) .then((albums) => albums.where((album) => album.assetCount > 0).toList()), ); final localAlbumThumbnailProvider = FutureProvider.family( - (ref, albumId) => LocalAlbumService(ref.watch(driftProvider).localAlbumRepository).getThumbnail(albumId), + (ref, albumId) => LocalAlbumService(ref.watch(localAlbumRepository)).getThumbnail(albumId), +); + +final remoteAlbumRepository = Provider( + (ref) => DriftRemoteAlbumRepository(ref.watch(driftProvider)), ); final remoteAlbumServiceProvider = Provider( - (ref) => RemoteAlbumService( - ref.watch(driftProvider).remoteAlbumRepository, - ref.watch(driftAlbumApiRepositoryProvider), - ref.watch(foregroundUploadServiceProvider), - ), + (ref) => RemoteAlbumService(ref.watch(remoteAlbumRepository), ref.watch(driftAlbumApiRepositoryProvider)), + dependencies: [remoteAlbumRepository], ); final remoteAlbumProvider = NotifierProvider( diff --git a/mobile/lib/providers/infrastructure/asset.provider.dart b/mobile/lib/providers/infrastructure/asset.provider.dart index ea89d1b9fb..70cb200bf1 100644 --- a/mobile/lib/providers/infrastructure/asset.provider.dart +++ b/mobile/lib/providers/infrastructure/asset.provider.dart @@ -1,21 +1,29 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/asset.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/repositories/asset_api.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -final assetServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return AssetService( - remoteRepository: db.remoteAssetRepository, - exifRepository: db.remoteExifRepository, - localRepository: db.localAssetRepository, - apiRepository: ref.watch(assetApiRepositoryProvider), - mediaRepository: ref.watch(assetMediaRepositoryProvider), - trashedLocalRepository: db.trashedLocalAssetRepository, - ); -}); +final localAssetRepository = Provider( + (ref) => DriftLocalAssetRepository(ref.watch(driftProvider)), +); + +final remoteAssetRepositoryProvider = Provider( + (ref) => RemoteAssetRepository(ref.watch(driftProvider)), +); + +final trashedLocalAssetRepository = Provider( + (ref) => DriftTrashedLocalAssetRepository(ref.watch(driftProvider)), +); + +final assetServiceProvider = Provider( + (ref) => AssetService( + remoteAssetRepository: ref.watch(remoteAssetRepositoryProvider), + localAssetRepository: ref.watch(localAssetRepository), + ), +); final placesProvider = FutureProvider>((ref) { final assetService = ref.watch(assetServiceProvider); diff --git a/mobile/lib/providers/infrastructure/cancel.provider.dart b/mobile/lib/providers/infrastructure/cancel.provider.dart index 9d4a6790f2..6851861e1a 100644 --- a/mobile/lib/providers/infrastructure/cancel.provider.dart +++ b/mobile/lib/providers/infrastructure/cancel.provider.dart @@ -1,9 +1,8 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; -/// Holds the isolate's cancellation signal. -final cancellationProvider = Provider>( +/// Provider holding a boolean function that returns true when cancellation is requested. +/// A computation running in the isolate uses the function to implement cooperative cancellation. +final cancellationProvider = Provider( // This will be overridden in the isolate's container. // Throwing ensures it's not used without an override. (ref) => throw UnimplementedError( diff --git a/mobile/lib/providers/infrastructure/db.provider.dart b/mobile/lib/providers/infrastructure/db.provider.dart index 2b4ba0129f..d38bcbfb55 100644 --- a/mobile/lib/providers/infrastructure/db.provider.dart +++ b/mobile/lib/providers/infrastructure/db.provider.dart @@ -2,6 +2,13 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:isar/isar.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'db.provider.g.dart'; + +@Riverpod(keepAlive: true) +Isar isar(Ref ref) => throw UnimplementedError('isar'); Drift Function(Ref ref) driftOverride(Drift drift) => (ref) { ref.onDispose(() => unawaited(drift.close())); diff --git a/mobile/lib/providers/infrastructure/db.provider.g.dart b/mobile/lib/providers/infrastructure/db.provider.g.dart new file mode 100644 index 0000000000..46abfb66a9 --- /dev/null +++ b/mobile/lib/providers/infrastructure/db.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'db.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$isarHash() => r'69d3a06aa7e69a4381478e03f7956eb07d7f7feb'; + +/// See also [isar]. +@ProviderFor(isar) +final isarProvider = Provider.internal( + isar, + name: r'isarProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$isarHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef IsarRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/device_asset.provider.dart b/mobile/lib/providers/infrastructure/device_asset.provider.dart new file mode 100644 index 0000000000..7854af016a --- /dev/null +++ b/mobile/lib/providers/infrastructure/device_asset.provider.dart @@ -0,0 +1,7 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; + +final deviceAssetRepositoryProvider = Provider( + (ref) => IsarDeviceAssetRepository(ref.watch(isarProvider)), +); diff --git a/mobile/lib/providers/infrastructure/exif.provider.dart b/mobile/lib/providers/infrastructure/exif.provider.dart new file mode 100644 index 0000000000..c126f6cac0 --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.dart @@ -0,0 +1,9 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'exif.provider.g.dart'; + +@Riverpod(keepAlive: true) +IsarExifRepository exifRepository(Ref ref) => IsarExifRepository(ref.watch(isarProvider)); diff --git a/mobile/lib/providers/infrastructure/exif.provider.g.dart b/mobile/lib/providers/infrastructure/exif.provider.g.dart new file mode 100644 index 0000000000..0261558707 --- /dev/null +++ b/mobile/lib/providers/infrastructure/exif.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'exif.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$exifRepositoryHash() => r'bf4a3f6a50d954a23d317659b4f3e2f381066463'; + +/// See also [exifRepository]. +@ProviderFor(exifRepository) +final exifRepositoryProvider = Provider.internal( + exifRepository, + name: r'exifRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$exifRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef ExifRepositoryRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/map.provider.dart b/mobile/lib/providers/infrastructure/map.provider.dart index 7f2fa5b40c..d9d261521e 100644 --- a/mobile/lib/providers/infrastructure/map.provider.dart +++ b/mobile/lib/providers/infrastructure/map.provider.dart @@ -1,10 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/map.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/map.repository.dart'; import 'package:immich_mobile/presentation/widgets/map/map.state.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +final mapRepositoryProvider = Provider((ref) => DriftMapRepository(ref.watch(driftProvider))); + final mapServiceProvider = Provider( (ref) { final user = ref.watch(currentUserProvider); @@ -16,11 +19,12 @@ final mapServiceProvider = Provider( ? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id] : [user.id]; - final mapFactory = MapFactory(mapRepository: ref.watch(driftProvider).mapRepository); - final mapService = mapFactory.remote(users, ref.watch(mapStateProvider).toOptions()); + final mapService = ref.watch(mapFactoryProvider).remote(users, ref.watch(mapStateProvider).toOptions()); return mapService; }, // Empty dependencies to inform the framework that this provider // might be used in a ProviderScope dependencies: const [], ); + +final mapFactoryProvider = Provider((ref) => MapFactory(mapRepository: ref.watch(mapRepositoryProvider))); diff --git a/mobile/lib/providers/infrastructure/memory.provider.dart b/mobile/lib/providers/infrastructure/memory.provider.dart index a2817f9c07..6fc75b8e6a 100644 --- a/mobile/lib/providers/infrastructure/memory.provider.dart +++ b/mobile/lib/providers/infrastructure/memory.provider.dart @@ -1,10 +1,17 @@ -import 'dart:async'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/services/memory.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +final driftMemoryRepositoryProvider = Provider( + (ref) => DriftMemoryRepository(ref.watch(driftProvider)), +); + +final driftMemoryServiceProvider = Provider( + (ref) => DriftMemoryService(ref.watch(driftMemoryRepositoryProvider)), +); final driftMemoryFutureProvider = FutureProvider.autoDispose>((ref) { final (userId, enabled) = ref.watch(currentUserProvider.select((user) => (user?.id, user?.memoryEnabled ?? true))); @@ -12,11 +19,6 @@ final driftMemoryFutureProvider = FutureProvider.autoDispose>( return const []; } - final now = DateTime.now(); - final nextMidnight = DateTime(now.year, now.month, now.day + 1); - final timer = Timer(nextMidnight.difference(now) + const Duration(seconds: 5), ref.invalidateSelf); - ref.onDispose(timer.cancel); - - final service = DriftMemoryService(ref.watch(driftProvider).memoryRepository); + final service = ref.watch(driftMemoryServiceProvider); return service.getMemoryLane(userId); }); diff --git a/mobile/lib/providers/infrastructure/ocr.provider.dart b/mobile/lib/providers/infrastructure/ocr.provider.dart deleted file mode 100644 index 20cfdbf5b2..0000000000 --- a/mobile/lib/providers/infrastructure/ocr.provider.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/ocr.model.dart'; -import 'package:immich_mobile/domain/services/ocr.service.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; - -final ocrAssetProvider = FutureProvider.autoDispose.family?, String>((ref, assetId) async { - final service = OcrService(ref.watch(driftProvider).ocrRepository); - return service.get(assetId); -}); diff --git a/mobile/lib/providers/infrastructure/partner.provider.dart b/mobile/lib/providers/infrastructure/partner.provider.dart new file mode 100644 index 0000000000..f4ba4cc73a --- /dev/null +++ b/mobile/lib/providers/infrastructure/partner.provider.dart @@ -0,0 +1,87 @@ +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/partner.service.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class PartnerNotifier extends Notifier> { + late DriftPartnerService _driftPartnerService; + + @override + List build() { + _driftPartnerService = ref.read(driftPartnerServiceProvider); + return []; + } + + Future _loadPartners() async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + return; + } + + state = await _driftPartnerService.getSharedWith(currentUser.id); + } + + Future> getPartners(String userId) async { + final partners = await _driftPartnerService.getSharedWith(userId); + state = partners; + return partners; + } + + Future toggleShowInTimeline(String partnerId, String userId) async { + await _driftPartnerService.toggleShowInTimeline(partnerId, userId); + await _loadPartners(); + } + + Future addPartner(PartnerUserDto partner) async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + return; + } + + await _driftPartnerService.addPartner(partner.id, currentUser.id); + await _loadPartners(); + ref.invalidate(driftAvailablePartnerProvider); + ref.invalidate(driftSharedByPartnerProvider); + } + + Future removePartner(PartnerUserDto partner) async { + final currentUser = ref.read(currentUserProvider); + if (currentUser == null) { + return; + } + + await _driftPartnerService.removePartner(partner.id, currentUser.id); + await _loadPartners(); + ref.invalidate(driftAvailablePartnerProvider); + ref.invalidate(driftSharedByPartnerProvider); + } +} + +final driftAvailablePartnerProvider = FutureProvider.autoDispose>((ref) { + final currentUser = ref.watch(currentUserProvider); + if (currentUser == null) { + return []; + } + + return ref.watch(driftPartnerServiceProvider).getAvailablePartners(currentUser.id); +}); + +final driftSharedByPartnerProvider = FutureProvider.autoDispose>((ref) { + final currentUser = ref.watch(currentUserProvider); + if (currentUser == null) { + return []; + } + + return ref.watch(driftPartnerServiceProvider).getSharedBy(currentUser.id); +}); + +final driftSharedWithPartnerProvider = FutureProvider.autoDispose>((ref) { + final currentUser = ref.watch(currentUserProvider); + if (currentUser == null) { + return []; + } + + return ref.watch(driftPartnerServiceProvider).getSharedWith(currentUser.id); +}); diff --git a/mobile/lib/providers/infrastructure/people.provider.dart b/mobile/lib/providers/infrastructure/people.provider.dart index 3d8c674efa..94a1b2447f 100644 --- a/mobile/lib/providers/infrastructure/people.provider.dart +++ b/mobile/lib/providers/infrastructure/people.provider.dart @@ -1,21 +1,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/services/people.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/people.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; import 'package:immich_mobile/repositories/person_api.repository.dart'; -final driftPeopleServiceProvider = Provider( - (ref) => DriftPeopleService(ref.watch(driftProvider).peopleRepository, ref.watch(personApiRepositoryProvider)), +final driftPeopleRepositoryProvider = Provider( + (ref) => DriftPeopleRepository(ref.watch(driftProvider)), ); -final driftPeopleAssetProvider = FutureProvider.family, String>((ref, assetId) async { +final driftPeopleServiceProvider = Provider( + (ref) => DriftPeopleService(ref.watch(driftPeopleRepositoryProvider), ref.watch(personApiRepositoryProvider)), +); + +final driftPeopleAssetProvider = FutureProvider.family, String>((ref, assetId) async { final service = ref.watch(driftPeopleServiceProvider); return service.getAssetPeople(assetId); }); -final getAllPeopleProvider = StreamProvider>((ref) async* { +final driftGetAllPeopleProvider = FutureProvider>((ref) async { final service = ref.watch(driftPeopleServiceProvider); - final prefs = await ref.watch(userMetadataPreferencesProvider.future); - yield* service.watch(minFaces: prefs?.minimumFaces ?? 3); + return service.getAllPeople(); }); diff --git a/mobile/lib/providers/infrastructure/platform.provider.dart b/mobile/lib/providers/infrastructure/platform.provider.dart index 9f85235927..01d0f61d1c 100644 --- a/mobile/lib/providers/infrastructure/platform.provider.dart +++ b/mobile/lib/providers/infrastructure/platform.provider.dart @@ -3,10 +3,9 @@ import 'package:immich_mobile/domain/services/background_worker.service.dart'; import 'package:immich_mobile/platform/background_worker_api.g.dart'; import 'package:immich_mobile/platform/background_worker_lock_api.g.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/platform/local_image_api.g.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/platform/local_image_api.g.dart'; import 'package:immich_mobile/platform/network_api.g.dart'; -import 'package:immich_mobile/platform/permission_api.g.dart'; import 'package:immich_mobile/platform/remote_image_api.g.dart'; final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi())); @@ -17,8 +16,6 @@ final backgroundWorkerLockServiceProvider = Provider((_) => NativeSyncApi()); -final permissionApiProvider = Provider((_) => PermissionApi()); - final connectivityApiProvider = Provider((_) => ConnectivityApi()); final localImageApi = LocalImageApi(); diff --git a/mobile/lib/providers/infrastructure/readonly_mode.provider.dart b/mobile/lib/providers/infrastructure/readonly_mode.provider.dart index be94a8a341..9e96c3cfc4 100644 --- a/mobile/lib/providers/infrastructure/readonly_mode.provider.dart +++ b/mobile/lib/providers/infrastructure/readonly_mode.provider.dart @@ -1,8 +1,5 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; @@ -17,12 +14,11 @@ class ReadOnlyModeNotifier extends Notifier { } void setMode(bool value) { - final isLoggedIn = ref.read(authProvider).isAuthenticated; - unawaited(_appSettingService.setSetting(AppSettingsEnum.readonlyModeEnabled, value)); + _appSettingService.setSetting(AppSettingsEnum.readonlyModeEnabled, value); state = value; - if (value && isLoggedIn) { - unawaited(ref.read(appRouterProvider).navigate(const MainTimelineRoute())); + if (value) { + ref.read(appRouterProvider).navigate(const MainTimelineRoute()); } } diff --git a/mobile/lib/providers/infrastructure/remote_album.provider.dart b/mobile/lib/providers/infrastructure/remote_album.provider.dart index b76a8213c6..606ce3f129 100644 --- a/mobile/lib/providers/infrastructure/remote_album.provider.dart +++ b/mobile/lib/providers/infrastructure/remote_album.provider.dart @@ -1,8 +1,4 @@ -// ignore_for_file: use-ref-and-state-synchronously - -import 'dart:async'; - -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; @@ -10,24 +6,32 @@ import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -import 'package:immich_mobile/providers/album/pending_album_uploads.provider.dart'; -import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; import 'package:logging/logging.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'remote_album.provider.freezed.dart'; +class RemoteAlbumState { + final List albums; -@Freezed(toStringOverride: false) -abstract class RemoteAlbumState with _$RemoteAlbumState { - const RemoteAlbumState._(); + const RemoteAlbumState({required this.albums}); - const factory RemoteAlbumState({required List albums}) = _RemoteAlbumState; + RemoteAlbumState copyWith({List? albums}) { + return RemoteAlbumState(albums: albums ?? this.albums); + } - // Explicitly don't log albums @override String toString() => 'RemoteAlbumState(albums: ${albums.length})'; + + @override + bool operator ==(covariant RemoteAlbumState other) { + if (identical(this, other)) return true; + final listEquals = const DeepCollectionEquality().equals; + + return listEquals(other.albums, albums); + } + + @override + int get hashCode => albums.hashCode; } class RemoteAlbumNotifier extends Notifier { @@ -78,17 +82,7 @@ class RemoteAlbumNotifier extends Notifier { List assetIds = const [], }) async { try { - final currentUser = ref.read(currentUserProvider); - if (currentUser == null) { - throw Exception('User not logged in'); - } - - final album = await _remoteAlbumService.createAlbum( - title: title, - owner: currentUser, - description: description, - assetIds: assetIds, - ); + final album = await _remoteAlbumService.createAlbum(title: title, description: description, assetIds: assetIds); state = state.copyWith(albums: [...state.albums, album]); @@ -99,46 +93,6 @@ class RemoteAlbumNotifier extends Notifier { } } - /// Creates an album from a heterogeneous asset selection. Already-remote - /// assets seed the album immediately; local-only assets are uploaded in the - /// background and linked one-by-one as each upload completes. - Future createAlbumWithAssets({ - required String title, - String? description, - Iterable assets = const [], - }) async { - try { - final currentUser = ref.read(currentUserProvider); - if (currentUser == null) { - throw Exception('User not logged in'); - } - - final candidates = RemoteAlbumService.categorizeCandidates(assets); - final album = await _remoteAlbumService.createAlbum( - title: title, - owner: currentUser, - description: description, - assetIds: candidates.remoteAssetIds, - ); - - state = state.copyWith(albums: [...state.albums, album]); - - if (candidates.localAssetsToUpload.isNotEmpty) { - unawaited( - addAssetsToAlbum( - album.id, - candidates.localAssetsToUpload, - ).then((_) {}).catchError((Object _, StackTrace _) {}), - ); - } - - return album; - } catch (error, stack) { - _logger.severe('Failed to create album with assets', error, stack); - rethrow; - } - } - Future updateAlbum( String albumId, { String? name, @@ -189,97 +143,8 @@ class RemoteAlbumNotifier extends Notifier { return _remoteAlbumService.getAssets(albumId); } - Future<({int added, int failed})> addAssets(String albumId, List assetIds) async { - final result = await _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); - if (result.added > 0) { - await _refreshAlbumInState(albumId); - } - return result; - } - - /// Links a freshly-uploaded local asset to an album using its new remote ID, - /// upserting a placeholder remote asset row so the local DB join survives - /// until the next sync catches up. - Future linkUploadedAssetToAlbum(String albumId, LocalAsset source, String remoteId) async { - final currentUser = ref.read(currentUserProvider); - if (currentUser == null) { - throw Exception('User not logged in'); - } - - final added = await _remoteAlbumService.linkUploadedAssetToAlbum(albumId, remoteId, currentUser, source); - if (added > 0) { - await _refreshAlbumInState(albumId); - } - return added; - } - - /// Adds a heterogeneous asset selection to an album. Already-remote assets - /// are linked immediately; local-only assets are queued in - /// [pendingAlbumUploadsProvider] (so the album page can show them with - /// progress indicators), uploaded, and linked one-by-one as each finishes. - Future addAssetsToAlbum(String albumId, Iterable assets) async { - final currentUser = ref.read(currentUserProvider); - if (currentUser == null) { - throw Exception('User not logged in'); - } - - final candidates = RemoteAlbumService.categorizeCandidates(assets); - final pendingNotifier = ref.read(pendingAlbumUploadsProvider(albumId).notifier); - pendingNotifier.enqueue(candidates.localAssetsToUpload); - - Completer? cancelToken; - if (candidates.localAssetsToUpload.isNotEmpty) { - cancelToken = Completer(); - ref.read(manualUploadCancelTokenProvider.notifier).state = cancelToken; - } - - try { - final added = await _remoteAlbumService.addAssetsToAlbum( - albumId: albumId, - uploader: currentUser, - candidates: candidates, - cancelToken: cancelToken, - uploadCallbacks: UploadCallbacks( - onProgress: (localAssetId, _, bytes, totalBytes) { - final progress = totalBytes > 0 ? bytes / totalBytes : 0.0; - pendingNotifier.updateProgress(localAssetId, progress); - }, - onSuccess: (localAssetId, _) => pendingNotifier.remove(localAssetId), - onError: (localAssetId, _) => pendingNotifier.markFailed(localAssetId), - ), - ); - if (added > 0) { - await _refreshAlbumInState(albumId); - } - return added; - } catch (error, stack) { - if (candidates.localAssetsToUpload.isNotEmpty) { - pendingNotifier.markAllFailed(); - } - _logger.severe('Failed to add assets to album $albumId', error, stack); - rethrow; - } finally { - if (cancelToken != null) { - if (cancelToken.isCompleted) { - pendingNotifier.clear(); - } - if (ref.read(manualUploadCancelTokenProvider) == cancelToken) { - ref.read(manualUploadCancelTokenProvider.notifier).state = null; - } - } - } - } - - /// Re-reads a single album from the local DB and replaces it in [state] so - /// that views bound to the album list (counts, thumbnails) reflect the - /// latest junction-table changes without a full `refresh()`. - Future _refreshAlbumInState(String albumId) async { - final updated = await _remoteAlbumService.get(albumId); - if (updated == null) { - return; - } - - state = state.copyWith(albums: state.albums.map((album) => album.id == albumId ? updated : album).toList()); + Future addAssets(String albumId, List assetIds) { + return _remoteAlbumService.addAssets(albumId: albumId, assetIds: assetIds); } Future addUsers(String albumId, List userIds) { @@ -302,9 +167,9 @@ class RemoteAlbumNotifier extends Notifier { } } -final remoteAlbumDateRangeProvider = StreamProvider.autoDispose.family<(DateTime, DateTime), String>((ref, albumId) { +final remoteAlbumDateRangeProvider = FutureProvider.family<(DateTime, DateTime), String>((ref, albumId) async { final service = ref.watch(remoteAlbumServiceProvider); - return service.watchDateRange(albumId); + return service.getDateRange(albumId); }); final remoteAlbumSharedUsersProvider = FutureProvider.autoDispose.family, String>((ref, albumId) async { diff --git a/mobile/lib/providers/infrastructure/setting.provider.dart b/mobile/lib/providers/infrastructure/setting.provider.dart index 9e33017cab..7d8be72cd0 100644 --- a/mobile/lib/providers/infrastructure/setting.provider.dart +++ b/mobile/lib/providers/infrastructure/setting.provider.dart @@ -1,5 +1,3 @@ -// ignore_for_file: use-ref-and-state-synchronously - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/domain/services/setting.service.dart'; diff --git a/mobile/lib/providers/infrastructure/settings.provider.dart b/mobile/lib/providers/infrastructure/settings.provider.dart deleted file mode 100644 index d2b9dce1d6..0000000000 --- a/mobile/lib/providers/infrastructure/settings.provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; - -final settingsProvider = Provider.autoDispose((_) => SettingsRepository.instance); - -final appConfigProvider = Provider.autoDispose((ref) { - final repo = ref.watch(settingsProvider); - final subscription = repo.watchConfig().listen((event) => ref.state = event); - ref.onDispose(subscription.cancel); - return repo.appConfig; -}); diff --git a/mobile/lib/providers/infrastructure/stack.provider.dart b/mobile/lib/providers/infrastructure/stack.provider.dart new file mode 100644 index 0000000000..0528fd0c91 --- /dev/null +++ b/mobile/lib/providers/infrastructure/stack.provider.dart @@ -0,0 +1,5 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/infrastructure/repositories/stack.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; + +final driftStackProvider = Provider((ref) => DriftStackRepository(ref.watch(driftProvider))); diff --git a/mobile/lib/providers/infrastructure/store.provider.dart b/mobile/lib/providers/infrastructure/store.provider.dart index ba4d045b06..0bf42f3e8b 100644 --- a/mobile/lib/providers/infrastructure/store.provider.dart +++ b/mobile/lib/providers/infrastructure/store.provider.dart @@ -1,4 +1,13 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final storeServiceProvider = Provider((_) => StoreService.I); +part 'store.provider.g.dart'; + +@Riverpod(keepAlive: true) +IsarStoreRepository storeRepository(Ref ref) => IsarStoreRepository(ref.watch(isarProvider)); + +@Riverpod(keepAlive: true) +StoreService storeService(Ref _) => StoreService.I; diff --git a/mobile/lib/providers/infrastructure/store.provider.g.dart b/mobile/lib/providers/infrastructure/store.provider.g.dart new file mode 100644 index 0000000000..98c978cb60 --- /dev/null +++ b/mobile/lib/providers/infrastructure/store.provider.g.dart @@ -0,0 +1,44 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'store.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$storeRepositoryHash() => r'659cb134466e4b0d5f04e2fc93e426350d99545f'; + +/// See also [storeRepository]. +@ProviderFor(storeRepository) +final storeRepositoryProvider = Provider.internal( + storeRepository, + name: r'storeRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$storeRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef StoreRepositoryRef = ProviderRef; +String _$storeServiceHash() => r'250e10497c42df360e9e1f9a618d0b19c1b5b0a0'; + +/// See also [storeService]. +@ProviderFor(storeService) +final storeServiceProvider = Provider.internal( + storeService, + name: r'storeServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$storeServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef StoreServiceRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/sync.provider.dart b/mobile/lib/providers/infrastructure/sync.provider.dart index 618c6039ff..5b9f29225e 100644 --- a/mobile/lib/providers/infrastructure/sync.provider.dart +++ b/mobile/lib/providers/infrastructure/sync.provider.dart @@ -3,50 +3,53 @@ 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_migration.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/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; -final syncStreamServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return SyncStreamService( +final syncMigrationRepositoryProvider = Provider((ref) => SyncMigrationRepository(ref.watch(driftProvider))); + +final syncStreamServiceProvider = Provider( + (ref) => SyncStreamService( syncApiRepository: ref.watch(syncApiRepositoryProvider), - syncStreamRepository: db.syncStreamRepository, - localAssetRepository: db.localAssetRepository, - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - assetMediaRepository: ref.watch(assetMediaRepositoryProvider), - permissionRepository: ref.watch(permissionRepositoryProvider), - syncMigrationRepository: db.syncMigrationRepository, + syncStreamRepository: ref.watch(syncStreamRepositoryProvider), + localAssetRepository: ref.watch(localAssetRepository), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + localFilesManager: ref.watch(localFilesManagerRepositoryProvider), + storageRepository: ref.watch(storageRepositoryProvider), + syncMigrationRepository: ref.watch(syncMigrationRepositoryProvider), api: ref.watch(apiServiceProvider), - cancellation: ref.watch(cancellationProvider), - ); -}); + cancelChecker: ref.watch(cancellationProvider), + ), +); final syncApiRepositoryProvider = Provider((ref) => SyncApiRepository(ref.watch(apiServiceProvider))); -final localSyncServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return LocalSyncService( - localAlbumRepository: db.localAlbumRepository, - localAssetRepository: db.localAssetRepository, - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - assetMediaRepository: ref.watch(assetMediaRepositoryProvider), - permissionRepository: ref.watch(permissionRepositoryProvider), - nativeSyncApi: ref.watch(nativeSyncApiProvider), - cancellation: ref.watch(cancellationProvider), - ); -}); +final syncStreamRepositoryProvider = Provider((ref) => SyncStreamRepository(ref.watch(driftProvider))); -final hashServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return HashService( - localAlbumRepository: db.localAlbumRepository, - localAssetRepository: db.localAssetRepository, +final localSyncServiceProvider = Provider( + (ref) => LocalSyncService( + localAlbumRepository: ref.watch(localAlbumRepository), + localAssetRepository: ref.watch(localAssetRepository), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + localFilesManager: ref.watch(localFilesManagerRepositoryProvider), + storageRepository: ref.watch(storageRepositoryProvider), nativeSyncApi: ref.watch(nativeSyncApiProvider), - trashedLocalAssetRepository: db.trashedLocalAssetRepository, - cancellation: ref.watch(cancellationProvider), - ); -}); + ), +); + +final hashServiceProvider = Provider( + (ref) => HashService( + localAlbumRepository: ref.watch(localAlbumRepository), + localAssetRepository: ref.watch(localAssetRepository), + nativeSyncApi: ref.watch(nativeSyncApiProvider), + trashedLocalAssetRepository: ref.watch(trashedLocalAssetRepository), + ), +); diff --git a/mobile/lib/providers/infrastructure/tag.provider.dart b/mobile/lib/providers/infrastructure/tag.provider.dart index 152582aa93..23d4d86861 100644 --- a/mobile/lib/providers/infrastructure/tag.provider.dart +++ b/mobile/lib/providers/infrastructure/tag.provider.dart @@ -1,24 +1,16 @@ -// ignore_for_file: use-ref-and-state-synchronously - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/domain/services/tag.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/tags_api.repository.dart'; class TagNotifier extends AsyncNotifier> { @override Future> build() async { - return ref.watch(tagServiceProvider).getAllTags(); - } - - Future bulkTagAssets(List assetIds, List tagIds) async { - return ref.read(tagServiceProvider).bulkTagAssets(assetIds, tagIds); - } - - Future> upsertTags(List tags) async { - final upsertedTags = await ref.read(tagServiceProvider).upsertTags(tags); - - state = AsyncValue.data({...?state.valueOrNull, ...upsertedTags}); - return upsertedTags; + final repo = ref.read(tagsApiRepositoryProvider); + final allTags = await repo.getAllTags(); + if (allTags == null) { + return {}; + } + return allTags.map((t) => Tag.fromDto(t)).toSet(); } } diff --git a/mobile/lib/providers/infrastructure/timeline.provider.dart b/mobile/lib/providers/infrastructure/timeline.provider.dart index 667b2624d7..06ec0242b2 100644 --- a/mobile/lib/providers/infrastructure/timeline.provider.dart +++ b/mobile/lib/providers/infrastructure/timeline.provider.dart @@ -1,11 +1,15 @@ -import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +final timelineRepositoryProvider = Provider( + (ref) => DriftTimelineRepository(ref.watch(driftProvider)), +); + final timelineArgsProvider = Provider.autoDispose( (ref) => throw UnimplementedError('Will be overridden through a ProviderScope.'), ); @@ -19,13 +23,13 @@ final timelineServiceProvider = Provider( }, // Empty dependencies to inform the framework that this provider // might be used in a ProviderScope - dependencies: const [], + dependencies: [], ); final timelineFactoryProvider = Provider( (ref) => TimelineFactory( - timelineRepository: ref.watch(driftProvider).timelineRepository, - settingsRepository: ref.watch(settingsProvider), + timelineRepository: ref.watch(timelineRepositoryProvider), + settingsService: ref.watch(settingsProvider), ), ); @@ -35,11 +39,5 @@ final timelineUsersProvider = StreamProvider>((ref) { return Stream.value([]); } - // Drift re-emits a fresh but content-identical list on unrelated table updates, - // which would dispose and rebuild the timeline service mid-load - return ref - .watch(driftProvider) - .timelineRepository - .watchTimelineUserIds(currentUserId) - .distinct(const ListEquality().equals); + return ref.watch(timelineRepositoryProvider).watchTimelineUserIds(currentUserId); }); diff --git a/mobile/lib/providers/infrastructure/toast.provider.dart b/mobile/lib/providers/infrastructure/toast.provider.dart deleted file mode 100644 index eaaffd6fca..0000000000 --- a/mobile/lib/providers/infrastructure/toast.provider.dart +++ /dev/null @@ -1,4 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/services/toast.service.dart'; - -final toastServiceProvider = Provider((ref) => const .new()); diff --git a/mobile/lib/providers/infrastructure/trash_sync.provider.dart b/mobile/lib/providers/infrastructure/trash_sync.provider.dart index 13e1424d75..a783080f33 100644 --- a/mobile/lib/providers/infrastructure/trash_sync.provider.dart +++ b/mobile/lib/providers/infrastructure/trash_sync.provider.dart @@ -1,11 +1,11 @@ import 'package:async/async.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; typedef TrashedAssetsCount = ({int total, int hashed}); final trashedAssetsCountProvider = StreamProvider((ref) { - final repo = ref.watch(driftProvider).trashedLocalAssetRepository; + final repo = ref.watch(trashedLocalAssetRepository); final total$ = repo.watchCount(); final hashed$ = repo.watchHashedCount(); return StreamZip([total$, hashed$]).map((values) => (total: values[0], hashed: values[1])); diff --git a/mobile/lib/providers/infrastructure/user.provider.dart b/mobile/lib/providers/infrastructure/user.provider.dart index a36082f31b..922b9866bb 100644 --- a/mobile/lib/providers/infrastructure/user.provider.dart +++ b/mobile/lib/providers/infrastructure/user.provider.dart @@ -1,22 +1,39 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/partner.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/partner.provider.dart'; import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final userApiRepositoryProvider = Provider((ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi)); +part 'user.provider.g.dart'; -final userServiceProvider = Provider( - (ref) => UserService( - userApiRepository: ref.watch(userApiRepositoryProvider), - storeService: ref.watch(storeServiceProvider), - ), +@Riverpod(keepAlive: true) +IsarUserRepository userRepository(Ref ref) => IsarUserRepository(ref.watch(isarProvider)); + +@Riverpod(keepAlive: true) +UserApiRepository userApiRepository(Ref ref) => UserApiRepository(ref.watch(apiServiceProvider).usersApi); + +@Riverpod(keepAlive: true) +UserService userService(Ref ref) => UserService( + isarUserRepository: ref.watch(userRepositoryProvider), + userApiRepository: ref.watch(userApiRepositoryProvider), + storeService: ref.watch(storeServiceProvider), ); -final partnerServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); - return PartnerService(db.userRepository, db.partnerRepository, ref.watch(partnerApiRepositoryProvider)); -}); +/// Drifts +final driftPartnerRepositoryProvider = Provider( + (ref) => DriftPartnerRepository(ref.watch(driftProvider)), +); + +final driftPartnerServiceProvider = Provider( + (ref) => DriftPartnerService(ref.watch(driftPartnerRepositoryProvider), ref.watch(partnerApiRepositoryProvider)), +); + +final partnerUsersProvider = NotifierProvider>(PartnerNotifier.new); diff --git a/mobile/lib/providers/infrastructure/user.provider.g.dart b/mobile/lib/providers/infrastructure/user.provider.g.dart new file mode 100644 index 0000000000..f9148bf3a7 --- /dev/null +++ b/mobile/lib/providers/infrastructure/user.provider.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$userRepositoryHash() => r'538791a4ad126ed086c9db682c67fc5c654d54f3'; + +/// See also [userRepository]. +@ProviderFor(userRepository) +final userRepositoryProvider = Provider.internal( + userRepository, + name: r'userRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$userRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserRepositoryRef = ProviderRef; +String _$userApiRepositoryHash() => r'8a7340ca4544c8c6b20225c65bff2abb9e96baa2'; + +/// See also [userApiRepository]. +@ProviderFor(userApiRepository) +final userApiRepositoryProvider = Provider.internal( + userApiRepository, + name: r'userApiRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$userApiRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserApiRepositoryRef = ProviderRef; +String _$userServiceHash() => r'181414dddc7891be6237e13d568c287a804228d1'; + +/// See also [userService]. +@ProviderFor(userService) +final userServiceProvider = Provider.internal( + userService, + name: r'userServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$userServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef UserServiceRef = ProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/infrastructure/user_metadata.provider.dart b/mobile/lib/providers/infrastructure/user_metadata.provider.dart index dd29b41a44..9a463463f5 100644 --- a/mobile/lib/providers/infrastructure/user_metadata.provider.dart +++ b/mobile/lib/providers/infrastructure/user_metadata.provider.dart @@ -1,19 +1,22 @@ -import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user_metadata.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/user_metadata.repository.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +final userMetadataRepository = Provider( + (ref) => DriftUserMetadataRepository(ref.watch(driftProvider)), +); + final userMetadataProvider = FutureProvider>((ref) async { - final repository = ref.watch(driftProvider).userMetadataRepository; + final repository = ref.watch(userMetadataRepository); final user = ref.watch(currentUserProvider); - if (user == null) { - return []; - } + if (user == null) return []; return repository.getUserMetadata(user.id); }); final userMetadataPreferencesProvider = FutureProvider((ref) async { final metadataList = await ref.watch(userMetadataProvider.future); - return metadataList.firstWhereOrNull((meta) => meta.preferences != null)?.preferences; + final metadataWithPrefs = metadataList.firstWhere((meta) => meta.preferences != null); + return metadataWithPrefs.preferences; }); diff --git a/mobile/lib/providers/local_auth.provider.dart b/mobile/lib/providers/local_auth.provider.dart index d99f28823f..44fc5ad80c 100644 --- a/mobile/lib/providers/local_auth.provider.dart +++ b/mobile/lib/providers/local_auth.provider.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:immich_mobile/services/local_auth.service.dart'; import 'package:immich_mobile/services/secure_storage.service.dart'; @@ -23,11 +21,9 @@ class LocalAuthNotifier extends StateNotifier { LocalAuthNotifier(this._localAuthService, this._secureStorageService) : super(const BiometricStatus(availableBiometrics: [], canAuthenticate: false)) { - unawaited( - _localAuthService.getStatus().then((value) { - state = state.copyWith(canAuthenticate: value.canAuthenticate, availableBiometrics: value.availableBiometrics); - }), - ); + _localAuthService.getStatus().then((value) { + state = state.copyWith(canAuthenticate: value.canAuthenticate, availableBiometrics: value.availableBiometrics); + }); } Future registerBiometric(BuildContext context, String pinCode) async { @@ -43,7 +39,6 @@ class LocalAuthNotifier extends StateNotifier { } Future authenticate(BuildContext context, String? message) async { - final t = StaticTranslations.instance; String errorMessage = ""; try { @@ -52,20 +47,23 @@ class LocalAuthNotifier extends StateNotifier { switch (error.code) { case "NotEnrolled": _log.warning("User is not enrolled in biometrics"); - errorMessage = t.biometric_no_options; + errorMessage = "biometric_no_options".tr(); + break; case "NotAvailable": _log.warning("Biometric authentication is not available"); - errorMessage = t.biometric_not_available; + errorMessage = "biometric_not_available".tr(); + break; case "LockedOut": _log.warning("User is locked out of biometric authentication"); - errorMessage = t.biometric_locked_out; + errorMessage = "biometric_locked_out".tr(); + break; default: _log.warning("Failed to authenticate with unknown reason"); - errorMessage = t.failed_to_authenticate; + errorMessage = 'failed_to_authenticate'.tr(); } } catch (error) { _log.warning("Error during authentication: $error"); - errorMessage = t.failed_to_authenticate; + errorMessage = 'failed_to_authenticate'.tr(); } finally { if (errorMessage.isNotEmpty) { context.showSnackBar( diff --git a/mobile/lib/providers/map/map_marker.provider.dart b/mobile/lib/providers/map/map_marker.provider.dart index ab73f94c10..e107dd3602 100644 --- a/mobile/lib/providers/map/map_marker.provider.dart +++ b/mobile/lib/providers/map/map_marker.provider.dart @@ -2,14 +2,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/providers/map/map_service.provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final mapMarkersProvider = FutureProvider.autoDispose>((ref) async { +part 'map_marker.provider.g.dart'; + +@riverpod +Future> mapMarkers(Ref ref) async { final service = ref.read(mapServiceProvider); final mapState = ref.read(mapStateNotifierProvider); DateTime? fileCreatedAfter; bool? isFavorite; - final bool isIncludeArchived = mapState.includeArchived; - final bool isWithPartners = mapState.withPartners; + bool isIncludeArchived = mapState.includeArchived; + bool isWithPartners = mapState.withPartners; if (mapState.relativeTime != 0) { fileCreatedAfter = DateTime.now().subtract(Duration(days: mapState.relativeTime)); @@ -27,4 +31,4 @@ final mapMarkersProvider = FutureProvider.autoDispose>((ref) asy ); return markers.toList(); -}); +} diff --git a/mobile/lib/providers/map/map_marker.provider.g.dart b/mobile/lib/providers/map/map_marker.provider.g.dart new file mode 100644 index 0000000000..80a21a39b2 --- /dev/null +++ b/mobile/lib/providers/map/map_marker.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'map_marker.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$mapMarkersHash() => r'a0c129fcddbf1b9bce4aafcd2e47a858ab6ef1c9'; + +/// See also [mapMarkers]. +@ProviderFor(mapMarkers) +final mapMarkersProvider = AutoDisposeFutureProvider>.internal( + mapMarkers, + name: r'mapMarkersProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$mapMarkersHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef MapMarkersRef = AutoDisposeFutureProviderRef>; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/map/map_service.provider.dart b/mobile/lib/providers/map/map_service.provider.dart index a1d47746e0..4ae199789f 100644 --- a/mobile/lib/providers/map/map_service.provider.dart +++ b/mobile/lib/providers/map/map_service.provider.dart @@ -1,5 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/services/map.service.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final mapServiceProvider = Provider.autoDispose((ref) => MapService(ref.watch(apiServiceProvider))); +part 'map_service.provider.g.dart'; + +@riverpod +MapService mapService(Ref ref) => MapService(ref.watch(apiServiceProvider)); diff --git a/mobile/lib/providers/map/map_service.provider.g.dart b/mobile/lib/providers/map/map_service.provider.g.dart new file mode 100644 index 0000000000..e8eb1cd1ee --- /dev/null +++ b/mobile/lib/providers/map/map_service.provider.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'map_service.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$mapServiceHash() => r'ffc8f38b726083452b9df236ed58903879348987'; + +/// See also [mapService]. +@ProviderFor(mapService) +final mapServiceProvider = AutoDisposeProvider.internal( + mapService, + name: r'mapServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$mapServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef MapServiceRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/map/map_state.provider.dart b/mobile/lib/providers/map/map_state.provider.dart index 12e9e59fac..31f2849df6 100644 --- a/mobile/lib/providers/map/map_state.provider.dart +++ b/mobile/lib/providers/map/map_state.provider.dart @@ -1,39 +1,39 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/map/map_state.model.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -final mapStateNotifierProvider = NotifierProvider(MapStateNotifier.new); +part 'map_state.provider.g.dart'; -class MapStateNotifier extends Notifier { +@Riverpod(keepAlive: true) +class MapStateNotifier extends _$MapStateNotifier { @override MapState build() { - final mapConfig = ref.read(appConfigProvider.select((config) => config.map)); + final appSettingsProvider = ref.read(appSettingsServiceProvider); final lightStyleUrl = ref.read(serverInfoProvider).serverConfig.mapLightStyleUrl; final darkStyleUrl = ref.read(serverInfoProvider).serverConfig.mapDarkStyleUrl; return MapState( - themeMode: mapConfig.themeMode, - showFavoriteOnly: mapConfig.favoritesOnly, - includeArchived: mapConfig.includeArchived, - withPartners: mapConfig.withPartners, - relativeTime: mapConfig.relativeDays, + themeMode: ThemeMode.values[appSettingsProvider.getSetting(AppSettingsEnum.mapThemeMode)], + showFavoriteOnly: appSettingsProvider.getSetting(AppSettingsEnum.mapShowFavoriteOnly), + includeArchived: appSettingsProvider.getSetting(AppSettingsEnum.mapIncludeArchived), + withPartners: appSettingsProvider.getSetting(AppSettingsEnum.mapwithPartners), + relativeTime: appSettingsProvider.getSetting(AppSettingsEnum.mapRelativeDate), lightStyleFetched: AsyncData(lightStyleUrl), darkStyleFetched: AsyncData(darkStyleUrl), ); } void switchTheme(ThemeMode mode) { - unawaited(ref.read(settingsProvider).write(.mapThemeMode, mode)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index); state = state.copyWith(themeMode: mode); } void switchFavoriteOnly(bool isFavoriteOnly) { - unawaited(ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly); state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true); } @@ -42,17 +42,17 @@ class MapStateNotifier extends Notifier { } void switchIncludeArchived(bool isIncludeArchived) { - unawaited(ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived); state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true); } void switchWithPartners(bool isWithPartners) { - unawaited(ref.read(settingsProvider).write(.mapWithPartners, isWithPartners)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners); state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true); } void setRelativeTime(int relativeTime) { - unawaited(ref.read(settingsProvider).write(.mapRelativeDate, relativeTime)); + ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeTime); state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true); } } diff --git a/mobile/lib/providers/map/map_state.provider.g.dart b/mobile/lib/providers/map/map_state.provider.g.dart new file mode 100644 index 0000000000..94d0ff8698 --- /dev/null +++ b/mobile/lib/providers/map/map_state.provider.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'map_state.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$mapStateNotifierHash() => r'22e4e571bd0730dbc34b109255a62b920e9c7d66'; + +/// See also [MapStateNotifier]. +@ProviderFor(MapStateNotifier) +final mapStateNotifierProvider = + NotifierProvider.internal( + MapStateNotifier.new, + name: r'mapStateNotifierProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$mapStateNotifierHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +typedef _$MapStateNotifier = Notifier; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/memory.provider.dart b/mobile/lib/providers/memory.provider.dart new file mode 100644 index 0000000000..7fef3060cc --- /dev/null +++ b/mobile/lib/providers/memory.provider.dart @@ -0,0 +1,9 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/services/memory.service.dart'; + +final memoryFutureProvider = FutureProvider.autoDispose?>((ref) async { + final service = ref.watch(memoryServiceProvider); + + return await service.getMemoryLane(); +}); diff --git a/mobile/lib/providers/permission.provider.dart b/mobile/lib/providers/notification_permission.provider.dart similarity index 54% rename from mobile/lib/providers/permission.provider.dart rename to mobile/lib/providers/notification_permission.provider.dart index bd4dc7b61e..da0badd4ec 100644 --- a/mobile/lib/providers/permission.provider.dart +++ b/mobile/lib/providers/notification_permission.provider.dart @@ -1,18 +1,13 @@ -// ignore_for_file: use-ref-and-state-synchronously - -import 'dart:async'; import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/platform/permission_api.g.dart' as pm; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:permission_handler/permission_handler.dart'; class NotificationPermissionNotifier extends StateNotifier { NotificationPermissionNotifier() : super(Platform.isAndroid ? PermissionStatus.granted : PermissionStatus.restricted) { // Sets the initial state - unawaited(getNotificationPermission().then((p) => state = p)); + getNotificationPermission().then((p) => state = p); } /// Requests the notification permission @@ -44,26 +39,3 @@ class NotificationPermissionNotifier extends StateNotifier { final notificationPermissionProvider = StateNotifierProvider( (ref) => NotificationPermissionNotifier(), ); - -final batteryOptimizationProvider = AsyncNotifierProvider( - BatteryOptimizationNotifier.new, -); - -class BatteryOptimizationNotifier extends AsyncNotifier { - Future getBatteryOptimizationPermission() async { - final isIgnoring = await ref.read(permissionApiProvider).isIgnoringBatteryOptimizations().then((p) => p.toStatus()); - state = AsyncValue.data(isIgnoring); - return isIgnoring; - } - - @override - FutureOr build() => getBatteryOptimizationPermission(); -} - -extension on pm.PermissionStatus { - PermissionStatus toStatus() => switch (this) { - pm.PermissionStatus.granted => PermissionStatus.granted, - pm.PermissionStatus.denied => PermissionStatus.denied, - pm.PermissionStatus.permanentlyDenied => PermissionStatus.permanentlyDenied, - }; -} diff --git a/mobile/lib/providers/oauth.provider.dart b/mobile/lib/providers/oauth.provider.dart index fd39e0907d..14b3353943 100644 --- a/mobile/lib/providers/oauth.provider.dart +++ b/mobile/lib/providers/oauth.provider.dart @@ -1,5 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/services/oauth.service.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; final oAuthServiceProvider = Provider((ref) => OAuthService(ref.watch(apiServiceProvider))); diff --git a/mobile/lib/providers/partner.provider.dart b/mobile/lib/providers/partner.provider.dart new file mode 100644 index 0000000000..5a85cea1d4 --- /dev/null +++ b/mobile/lib/providers/partner.provider.dart @@ -0,0 +1,89 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/providers/album/suggested_shared_users.provider.dart'; +import 'package:immich_mobile/services/partner.service.dart'; + +class PartnerSharedWithNotifier extends StateNotifier> { + final PartnerService _partnerService; + late final StreamSubscription> streamSub; + + PartnerSharedWithNotifier(this._partnerService) : super([]) { + Function eq = const ListEquality().equals; + _partnerService + .getSharedWith() + .then((partners) { + if (!eq(state, partners)) { + state = partners; + } + }) + .then((_) { + streamSub = _partnerService.watchSharedWith().listen((partners) { + if (!eq(state, partners)) { + state = partners; + } + }); + }); + } + + Future updatePartner(UserDto partner, {required bool inTimeline}) { + return _partnerService.updatePartner(partner, inTimeline: inTimeline); + } + + @override + void dispose() { + if (mounted) { + streamSub.cancel(); + } + super.dispose(); + } +} + +final partnerSharedWithProvider = StateNotifierProvider>((ref) { + return PartnerSharedWithNotifier(ref.watch(partnerServiceProvider)); +}); + +class PartnerSharedByNotifier extends StateNotifier> { + final PartnerService _partnerService; + late final StreamSubscription> streamSub; + + PartnerSharedByNotifier(this._partnerService) : super([]) { + Function eq = const ListEquality().equals; + _partnerService + .getSharedBy() + .then((partners) { + if (!eq(state, partners)) { + state = partners; + } + }) + .then((_) { + streamSub = _partnerService.watchSharedBy().listen((partners) { + if (!eq(state, partners)) { + state = partners; + } + }); + }); + } + + @override + void dispose() { + if (mounted) { + streamSub.cancel(); + } + super.dispose(); + } +} + +final partnerSharedByProvider = StateNotifierProvider>((ref) { + return PartnerSharedByNotifier(ref.watch(partnerServiceProvider)); +}); + +final partnerAvailableProvider = FutureProvider.autoDispose>((ref) async { + final otherUsers = await ref.watch(otherUsersProvider.future); + final currentPartners = ref.watch(partnerSharedByProvider); + final available = Set.of(otherUsers); + available.removeAll(currentPartners); + return available.toList(); +}); diff --git a/mobile/lib/providers/routes.provider.dart b/mobile/lib/providers/routes.provider.dart index 776389b330..c51f67bc0e 100644 --- a/mobile/lib/providers/routes.provider.dart +++ b/mobile/lib/providers/routes.provider.dart @@ -1,16 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/routing/router.dart'; - -@visibleForTesting -bool isRouteInStack(Ref ref, String routeName) { - final router = ref.watch(appRouterProvider); - return router.stackData.any((route) => route.name == routeName); -} - -final inLockedViewProvider = Provider((ref) => isRouteInStack(ref, DriftLockedFolderRoute.name)); -final isAssetViewerOpenProvider = Provider((ref) => isRouteInStack(ref, AssetViewerRoute.name)); +final inLockedViewProvider = StateProvider((ref) => false); final currentRouteNameProvider = StateProvider((ref) => null); final previousRouteNameProvider = StateProvider((ref) => null); final previousRouteDataProvider = StateProvider((ref) => null); diff --git a/mobile/lib/providers/search/all_motion_photos.provider.dart b/mobile/lib/providers/search/all_motion_photos.provider.dart new file mode 100644 index 0000000000..48bc1bb80c --- /dev/null +++ b/mobile/lib/providers/search/all_motion_photos.provider.dart @@ -0,0 +1,7 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/asset.service.dart'; + +final allMotionPhotosProvider = FutureProvider>((ref) async { + return ref.watch(assetServiceProvider).getMotionAssets(); +}); diff --git a/mobile/lib/providers/search/paginated_search.provider.dart b/mobile/lib/providers/search/paginated_search.provider.dart new file mode 100644 index 0000000000..9a37d83320 --- /dev/null +++ b/mobile/lib/providers/search/paginated_search.provider.dart @@ -0,0 +1,46 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/search/search_result.model.dart'; +import 'package:immich_mobile/services/timeline.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/services/search.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'paginated_search.provider.g.dart'; + +final paginatedSearchProvider = StateNotifierProvider( + (ref) => PaginatedSearchNotifier(ref.watch(searchServiceProvider)), +); + +class PaginatedSearchNotifier extends StateNotifier { + final SearchService _searchService; + + PaginatedSearchNotifier(this._searchService) : super(const SearchResult(assets: [], nextPage: 1)); + + Future search(SearchFilter filter) async { + if (state.nextPage == null) { + return false; + } + + final result = await _searchService.search(filter, state.nextPage!); + + if (result == null) { + return false; + } + + state = SearchResult(assets: [...state.assets, ...result.assets], nextPage: result.nextPage); + + return true; + } + + clear() { + state = const SearchResult(assets: [], nextPage: 1); + } +} + +@riverpod +Future paginatedSearchRenderList(Ref ref) { + final result = ref.watch(paginatedSearchProvider); + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.getTimelineFromAssets(result.assets, GroupAssetsBy.none); +} diff --git a/mobile/lib/providers/search/paginated_search.provider.g.dart b/mobile/lib/providers/search/paginated_search.provider.g.dart new file mode 100644 index 0000000000..e984997967 --- /dev/null +++ b/mobile/lib/providers/search/paginated_search.provider.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'paginated_search.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$paginatedSearchRenderListHash() => + r'22d715ff7864e5a946be38322ce7813616f899c2'; + +/// See also [paginatedSearchRenderList]. +@ProviderFor(paginatedSearchRenderList) +final paginatedSearchRenderListProvider = + AutoDisposeFutureProvider.internal( + paginatedSearchRenderList, + name: r'paginatedSearchRenderListProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$paginatedSearchRenderListHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef PaginatedSearchRenderListRef = AutoDisposeFutureProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/search/people.provider.dart b/mobile/lib/providers/search/people.provider.dart new file mode 100644 index 0000000000..3ff8d67983 --- /dev/null +++ b/mobile/lib/providers/search/people.provider.dart @@ -0,0 +1,40 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/person.model.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/services/person.service.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'people.provider.g.dart'; + +@riverpod +Future> getAllPeople(Ref ref) async { + final PersonService personService = ref.read(personServiceProvider); + + final people = await personService.getAllPeople(); + + return people; +} + +@riverpod +Future personAssets(Ref ref, String personId) async { + final PersonService personService = ref.read(personServiceProvider); + final assets = await personService.getPersonAssets(personId); + + final settings = ref.read(appSettingsServiceProvider); + final groupBy = GroupAssetsBy.values[settings.getSetting(AppSettingsEnum.groupAssetsBy)]; + return await RenderList.fromAssets(assets, groupBy); +} + +@riverpod +Future updatePersonName(Ref ref, String personId, String updatedName) async { + final PersonService personService = ref.read(personServiceProvider); + final person = await personService.updateName(personId, updatedName); + + if (person != null && person.name == updatedName) { + ref.invalidate(getAllPeopleProvider); + return true; + } + return false; +} diff --git a/mobile/lib/providers/search/people.provider.g.dart b/mobile/lib/providers/search/people.provider.g.dart new file mode 100644 index 0000000000..9595c36eec --- /dev/null +++ b/mobile/lib/providers/search/people.provider.g.dart @@ -0,0 +1,302 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'people.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$getAllPeopleHash() => r'2c5e6a207683f15ab209650615fdf9cb7f76c736'; + +/// See also [getAllPeople]. +@ProviderFor(getAllPeople) +final getAllPeopleProvider = + AutoDisposeFutureProvider>.internal( + getAllPeople, + name: r'getAllPeopleProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$getAllPeopleHash, + dependencies: null, + allTransitiveDependencies: null, + ); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef GetAllPeopleRef = AutoDisposeFutureProviderRef>; +String _$personAssetsHash() => r'c1d35ee0e024bd6915e21bc724be4b458a14bc24'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [personAssets]. +@ProviderFor(personAssets) +const personAssetsProvider = PersonAssetsFamily(); + +/// See also [personAssets]. +class PersonAssetsFamily extends Family> { + /// See also [personAssets]. + const PersonAssetsFamily(); + + /// See also [personAssets]. + PersonAssetsProvider call(String personId) { + return PersonAssetsProvider(personId); + } + + @override + PersonAssetsProvider getProviderOverride( + covariant PersonAssetsProvider provider, + ) { + return call(provider.personId); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'personAssetsProvider'; +} + +/// See also [personAssets]. +class PersonAssetsProvider extends AutoDisposeFutureProvider { + /// See also [personAssets]. + PersonAssetsProvider(String personId) + : this._internal( + (ref) => personAssets(ref as PersonAssetsRef, personId), + from: personAssetsProvider, + name: r'personAssetsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$personAssetsHash, + dependencies: PersonAssetsFamily._dependencies, + allTransitiveDependencies: + PersonAssetsFamily._allTransitiveDependencies, + personId: personId, + ); + + PersonAssetsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.personId, + }) : super.internal(); + + final String personId; + + @override + Override overrideWith( + FutureOr Function(PersonAssetsRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: PersonAssetsProvider._internal( + (ref) => create(ref as PersonAssetsRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + personId: personId, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _PersonAssetsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is PersonAssetsProvider && other.personId == personId; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, personId.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin PersonAssetsRef on AutoDisposeFutureProviderRef { + /// The parameter `personId` of this provider. + String get personId; +} + +class _PersonAssetsProviderElement + extends AutoDisposeFutureProviderElement + with PersonAssetsRef { + _PersonAssetsProviderElement(super.provider); + + @override + String get personId => (origin as PersonAssetsProvider).personId; +} + +String _$updatePersonNameHash() => r'45f7693172de522a227406d8198811434cf2bbbc'; + +/// See also [updatePersonName]. +@ProviderFor(updatePersonName) +const updatePersonNameProvider = UpdatePersonNameFamily(); + +/// See also [updatePersonName]. +class UpdatePersonNameFamily extends Family> { + /// See also [updatePersonName]. + const UpdatePersonNameFamily(); + + /// See also [updatePersonName]. + UpdatePersonNameProvider call(String personId, String updatedName) { + return UpdatePersonNameProvider(personId, updatedName); + } + + @override + UpdatePersonNameProvider getProviderOverride( + covariant UpdatePersonNameProvider provider, + ) { + return call(provider.personId, provider.updatedName); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'updatePersonNameProvider'; +} + +/// See also [updatePersonName]. +class UpdatePersonNameProvider extends AutoDisposeFutureProvider { + /// See also [updatePersonName]. + UpdatePersonNameProvider(String personId, String updatedName) + : this._internal( + (ref) => + updatePersonName(ref as UpdatePersonNameRef, personId, updatedName), + from: updatePersonNameProvider, + name: r'updatePersonNameProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$updatePersonNameHash, + dependencies: UpdatePersonNameFamily._dependencies, + allTransitiveDependencies: + UpdatePersonNameFamily._allTransitiveDependencies, + personId: personId, + updatedName: updatedName, + ); + + UpdatePersonNameProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.personId, + required this.updatedName, + }) : super.internal(); + + final String personId; + final String updatedName; + + @override + Override overrideWith( + FutureOr Function(UpdatePersonNameRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: UpdatePersonNameProvider._internal( + (ref) => create(ref as UpdatePersonNameRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + personId: personId, + updatedName: updatedName, + ), + ); + } + + @override + AutoDisposeFutureProviderElement createElement() { + return _UpdatePersonNameProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is UpdatePersonNameProvider && + other.personId == personId && + other.updatedName == updatedName; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, personId.hashCode); + hash = _SystemHash.combine(hash, updatedName.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin UpdatePersonNameRef on AutoDisposeFutureProviderRef { + /// The parameter `personId` of this provider. + String get personId; + + /// The parameter `updatedName` of this provider. + String get updatedName; +} + +class _UpdatePersonNameProviderElement + extends AutoDisposeFutureProviderElement + with UpdatePersonNameRef { + _UpdatePersonNameProviderElement(super.provider); + + @override + String get personId => (origin as UpdatePersonNameProvider).personId; + @override + String get updatedName => (origin as UpdatePersonNameProvider).updatedName; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/search/recently_taken_asset.provider.dart b/mobile/lib/providers/search/recently_taken_asset.provider.dart new file mode 100644 index 0000000000..157e7c2a74 --- /dev/null +++ b/mobile/lib/providers/search/recently_taken_asset.provider.dart @@ -0,0 +1,9 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/asset.service.dart'; + +final recentlyTakenAssetProvider = FutureProvider>((ref) async { + final assetService = ref.read(assetServiceProvider); + + return assetService.getRecentlyTakenAssets(); +}); diff --git a/mobile/lib/providers/search/search_filter.provider.dart b/mobile/lib/providers/search/search_filter.provider.dart index 5da0a11fe7..2a81060522 100644 --- a/mobile/lib/providers/search/search_filter.provider.dart +++ b/mobile/lib/providers/search/search_filter.provider.dart @@ -1,34 +1,28 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/services/search.service.dart'; import 'package:openapi/api.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; -part 'search_filter.provider.freezed.dart'; +part 'search_filter.provider.g.dart'; -@freezed -abstract class SearchSuggestionArgs with _$SearchSuggestionArgs { - const factory SearchSuggestionArgs({ - required SearchSuggestionType type, - String? locationCountry, - String? locationState, - String? make, - String? model, - }) = _SearchSuggestionArgs; -} - -final getSearchSuggestionsProvider = FutureProvider.autoDispose.family, SearchSuggestionArgs>(( - ref, - args, -) async { +@riverpod +Future> getSearchSuggestions( + Ref ref, + SearchSuggestionType type, { + String? locationCountry, + String? locationState, + String? make, + String? model, +}) async { final SearchService service = ref.read(searchServiceProvider); final suggestions = await service.getSearchSuggestions( - args.type, - country: args.locationCountry, - state: args.locationState, - make: args.make, - model: args.model, + type, + country: locationCountry, + state: locationState, + make: make, + model: model, ); return suggestions ?? []; -}); +} diff --git a/mobile/lib/providers/search/search_filter.provider.g.dart b/mobile/lib/providers/search/search_filter.provider.g.dart new file mode 100644 index 0000000000..5a322ca285 --- /dev/null +++ b/mobile/lib/providers/search/search_filter.provider.g.dart @@ -0,0 +1,231 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search_filter.provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$getSearchSuggestionsHash() => + r'bc30a65e8fcb273cbd07bab876baf67bcc794737'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +/// See also [getSearchSuggestions]. +@ProviderFor(getSearchSuggestions) +const getSearchSuggestionsProvider = GetSearchSuggestionsFamily(); + +/// See also [getSearchSuggestions]. +class GetSearchSuggestionsFamily extends Family>> { + /// See also [getSearchSuggestions]. + const GetSearchSuggestionsFamily(); + + /// See also [getSearchSuggestions]. + GetSearchSuggestionsProvider call( + SearchSuggestionType type, { + String? locationCountry, + String? locationState, + String? make, + String? model, + }) { + return GetSearchSuggestionsProvider( + type, + locationCountry: locationCountry, + locationState: locationState, + make: make, + model: model, + ); + } + + @override + GetSearchSuggestionsProvider getProviderOverride( + covariant GetSearchSuggestionsProvider provider, + ) { + return call( + provider.type, + locationCountry: provider.locationCountry, + locationState: provider.locationState, + make: provider.make, + model: provider.model, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'getSearchSuggestionsProvider'; +} + +/// See also [getSearchSuggestions]. +class GetSearchSuggestionsProvider + extends AutoDisposeFutureProvider> { + /// See also [getSearchSuggestions]. + GetSearchSuggestionsProvider( + SearchSuggestionType type, { + String? locationCountry, + String? locationState, + String? make, + String? model, + }) : this._internal( + (ref) => getSearchSuggestions( + ref as GetSearchSuggestionsRef, + type, + locationCountry: locationCountry, + locationState: locationState, + make: make, + model: model, + ), + from: getSearchSuggestionsProvider, + name: r'getSearchSuggestionsProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$getSearchSuggestionsHash, + dependencies: GetSearchSuggestionsFamily._dependencies, + allTransitiveDependencies: + GetSearchSuggestionsFamily._allTransitiveDependencies, + type: type, + locationCountry: locationCountry, + locationState: locationState, + make: make, + model: model, + ); + + GetSearchSuggestionsProvider._internal( + super._createNotifier, { + required super.name, + required super.dependencies, + required super.allTransitiveDependencies, + required super.debugGetCreateSourceHash, + required super.from, + required this.type, + required this.locationCountry, + required this.locationState, + required this.make, + required this.model, + }) : super.internal(); + + final SearchSuggestionType type; + final String? locationCountry; + final String? locationState; + final String? make; + final String? model; + + @override + Override overrideWith( + FutureOr> Function(GetSearchSuggestionsRef provider) create, + ) { + return ProviderOverride( + origin: this, + override: GetSearchSuggestionsProvider._internal( + (ref) => create(ref as GetSearchSuggestionsRef), + from: from, + name: null, + dependencies: null, + allTransitiveDependencies: null, + debugGetCreateSourceHash: null, + type: type, + locationCountry: locationCountry, + locationState: locationState, + make: make, + model: model, + ), + ); + } + + @override + AutoDisposeFutureProviderElement> createElement() { + return _GetSearchSuggestionsProviderElement(this); + } + + @override + bool operator ==(Object other) { + return other is GetSearchSuggestionsProvider && + other.type == type && + other.locationCountry == locationCountry && + other.locationState == locationState && + other.make == make && + other.model == model; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, type.hashCode); + hash = _SystemHash.combine(hash, locationCountry.hashCode); + hash = _SystemHash.combine(hash, locationState.hashCode); + hash = _SystemHash.combine(hash, make.hashCode); + hash = _SystemHash.combine(hash, model.hashCode); + + return _SystemHash.finish(hash); + } +} + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +mixin GetSearchSuggestionsRef on AutoDisposeFutureProviderRef> { + /// The parameter `type` of this provider. + SearchSuggestionType get type; + + /// The parameter `locationCountry` of this provider. + String? get locationCountry; + + /// The parameter `locationState` of this provider. + String? get locationState; + + /// The parameter `make` of this provider. + String? get make; + + /// The parameter `model` of this provider. + String? get model; +} + +class _GetSearchSuggestionsProviderElement + extends AutoDisposeFutureProviderElement> + with GetSearchSuggestionsRef { + _GetSearchSuggestionsProviderElement(super.provider); + + @override + SearchSuggestionType get type => + (origin as GetSearchSuggestionsProvider).type; + @override + String? get locationCountry => + (origin as GetSearchSuggestionsProvider).locationCountry; + @override + String? get locationState => + (origin as GetSearchSuggestionsProvider).locationState; + @override + String? get make => (origin as GetSearchSuggestionsProvider).make; + @override + String? get model => (origin as GetSearchSuggestionsProvider).model; +} + +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/providers/search/search_input_focus.provider.dart b/mobile/lib/providers/search/search_input_focus.provider.dart index 4ebbfc6553..4f6ed41ee0 100644 --- a/mobile/lib/providers/search/search_input_focus.provider.dart +++ b/mobile/lib/providers/search/search_input_focus.provider.dart @@ -2,7 +2,5 @@ import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; final searchInputFocusProvider = Provider((ref) { - final focusNode = FocusNode(); - ref.onDispose(focusNode.dispose); - return focusNode; + return FocusNode(); }); diff --git a/mobile/lib/providers/search/search_page_state.provider.dart b/mobile/lib/providers/search/search_page_state.provider.dart index c6a72c922e..23d5606922 100644 --- a/mobile/lib/providers/search/search_page_state.provider.dart +++ b/mobile/lib/providers/search/search_page_state.provider.dart @@ -29,7 +29,7 @@ final getAllPlacesProvider = FutureProvider.autoDispose SearchCuratedContent(label: data.exifInfo.orElse(null)!.city.orElse(null)!, id: data.id)) + .map((data) => SearchCuratedContent(label: data.exifInfo!.city!, id: data.id)) .toList(); return curatedContent; diff --git a/mobile/lib/providers/server_info.provider.dart b/mobile/lib/providers/server_info.provider.dart index 79593cce95..98300894f9 100644 --- a/mobile/lib/providers/server_info.provider.dart +++ b/mobile/lib/providers/server_info.provider.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/models/server_info/server_config.model.dart'; @@ -58,11 +56,11 @@ class ServerInfoNotifier extends StateNotifier { } } - Future _checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async { - state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion ?? state.latestVersion); + _checkServerVersionMismatch(ServerVersion serverVersion, {ServerVersion? latestVersion}) async { + state = state.copyWith(serverVersion: serverVersion, latestVersion: latestVersion); - final packageInfo = await PackageInfo.fromPlatform(); - final SemVer clientVersion = SemVer.fromString(packageInfo.version); + var packageInfo = await PackageInfo.fromPlatform(); + SemVer clientVersion = SemVer.fromString(packageInfo.version); if (serverVersion < clientVersion || (latestVersion != null && serverVersion < latestVersion)) { state = state.copyWith(versionStatus: VersionStatus.serverOutOfDate); @@ -77,12 +75,12 @@ class ServerInfoNotifier extends StateNotifier { state = state.copyWith(versionStatus: VersionStatus.upToDate); } - void handleReleaseInfo(ServerVersion serverVersion, ServerVersion? latestVersion) { + handleReleaseInfo(ServerVersion serverVersion, ServerVersion? latestVersion) { // Update local server version - unawaited(_checkServerVersionMismatch(serverVersion, latestVersion: latestVersion)); + _checkServerVersionMismatch(serverVersion, latestVersion: latestVersion); } - Future getServerFeatures() async { + getServerFeatures() async { final serverFeatures = await _serverInfoService.getServerFeatures(); if (serverFeatures == null) { return; @@ -90,7 +88,7 @@ class ServerInfoNotifier extends StateNotifier { state = state.copyWith(serverFeatures: serverFeatures); } - Future getServerConfig() async { + getServerConfig() async { final serverConfig = await _serverInfoService.getServerConfig(); if (serverConfig == null) { return; diff --git a/mobile/lib/providers/shared_link.provider.dart b/mobile/lib/providers/shared_link.provider.dart index 096919f28a..fb44aea203 100644 --- a/mobile/lib/providers/shared_link.provider.dart +++ b/mobile/lib/providers/shared_link.provider.dart @@ -8,7 +8,7 @@ class SharedLinksNotifier extends StateNotifier>> { final SharedLinkService _sharedLinkService; SharedLinksNotifier(this._sharedLinkService) : super(const AsyncLoading()) { - unawaited(fetchLinks()); + fetchLinks(); } Future fetchLinks() async { diff --git a/mobile/lib/providers/sync_status.provider.dart b/mobile/lib/providers/sync_status.provider.dart index eb183fbb13..203184fc87 100644 --- a/mobile/lib/providers/sync_status.provider.dart +++ b/mobile/lib/providers/sync_status.provider.dart @@ -1,9 +1,5 @@ -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; - -part 'sync_status.provider.freezed.dart'; enum SyncStatus { idle, @@ -11,33 +7,66 @@ enum SyncStatus { success, error; - String localized() { - final t = StaticTranslations.instance; + localized() { return switch (this) { - SyncStatus.idle => t.idle, - SyncStatus.syncing => t.running, - SyncStatus.success => t.success, - SyncStatus.error => t.error, + SyncStatus.idle => "idle".tr(), + SyncStatus.syncing => "running".tr(), + SyncStatus.success => "success".tr(), + SyncStatus.error => "error".tr(), }; } } -@freezed -abstract class SyncStatusState with _$SyncStatusState { - const SyncStatusState._(); +class SyncStatusState { + final SyncStatus remoteSyncStatus; + final SyncStatus localSyncStatus; + final SyncStatus hashJobStatus; + final SyncStatus cloudIdSyncStatus; - const factory SyncStatusState({ - @Default(SyncStatus.idle) SyncStatus remoteSyncStatus, - @Default(SyncStatus.idle) SyncStatus localSyncStatus, - @Default(SyncStatus.idle) SyncStatus hashJobStatus, - @Default(SyncStatus.idle) SyncStatus cloudIdSyncStatus, + final String? errorMessage; + + const SyncStatusState({ + this.remoteSyncStatus = SyncStatus.idle, + this.localSyncStatus = SyncStatus.idle, + this.hashJobStatus = SyncStatus.idle, + this.cloudIdSyncStatus = SyncStatus.idle, + this.errorMessage, + }); + + SyncStatusState copyWith({ + SyncStatus? remoteSyncStatus, + SyncStatus? localSyncStatus, + SyncStatus? hashJobStatus, + SyncStatus? cloudIdSyncStatus, String? errorMessage, - }) = _SyncStatusState; + }) { + return SyncStatusState( + remoteSyncStatus: remoteSyncStatus ?? this.remoteSyncStatus, + localSyncStatus: localSyncStatus ?? this.localSyncStatus, + hashJobStatus: hashJobStatus ?? this.hashJobStatus, + cloudIdSyncStatus: cloudIdSyncStatus ?? this.cloudIdSyncStatus, + errorMessage: errorMessage ?? this.errorMessage, + ); + } bool get isRemoteSyncing => remoteSyncStatus == SyncStatus.syncing; bool get isLocalSyncing => localSyncStatus == SyncStatus.syncing; bool get isHashing => hashJobStatus == SyncStatus.syncing; bool get isCloudIdSyncing => cloudIdSyncStatus == SyncStatus.syncing; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is SyncStatusState && + other.remoteSyncStatus == remoteSyncStatus && + other.localSyncStatus == localSyncStatus && + other.hashJobStatus == hashJobStatus && + other.cloudIdSyncStatus == cloudIdSyncStatus && + other.errorMessage == errorMessage; + } + + @override + int get hashCode => Object.hash(remoteSyncStatus, localSyncStatus, hashJobStatus, cloudIdSyncStatus, errorMessage); } class SyncStatusNotifier extends Notifier { @@ -57,11 +86,7 @@ class SyncStatusNotifier extends Notifier { /// void setRemoteSyncStatus(SyncStatus status, [String? errorMessage]) { - // TODO(agg23): These error messages probably should be cleared, not preserved on null - state = state.copyWith( - remoteSyncStatus: status, - errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, - ); + state = state.copyWith(remoteSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); } void startRemoteSync() => setRemoteSyncStatus(SyncStatus.syncing); @@ -73,11 +98,7 @@ class SyncStatusNotifier extends Notifier { /// void setLocalSyncStatus(SyncStatus status, [String? errorMessage]) { - // TODO(agg23): These error messages probably should be cleared, not preserved on null - state = state.copyWith( - localSyncStatus: status, - errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, - ); + state = state.copyWith(localSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); } void startLocalSync() => setLocalSyncStatus(SyncStatus.syncing); @@ -89,11 +110,7 @@ class SyncStatusNotifier extends Notifier { /// void setHashJobStatus(SyncStatus status, [String? errorMessage]) { - // TODO(agg23): These error messages probably should be cleared, not preserved on null - state = state.copyWith( - hashJobStatus: status, - errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, - ); + state = state.copyWith(hashJobStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); } void startHashJob() => setHashJobStatus(SyncStatus.syncing); @@ -105,11 +122,7 @@ class SyncStatusNotifier extends Notifier { /// void setCloudIdSyncStatus(SyncStatus status, [String? errorMessage]) { - // TODO(agg23): These error messages probably should be cleared, not preserved on null - state = state.copyWith( - cloudIdSyncStatus: status, - errorMessage: (status == SyncStatus.error ? errorMessage : null) ?? state.errorMessage, - ); + state = state.copyWith(cloudIdSyncStatus: status, errorMessage: status == SyncStatus.error ? errorMessage : null); } void startCloudIdSync() => setCloudIdSyncStatus(SyncStatus.syncing); diff --git a/mobile/lib/providers/theme.provider.dart b/mobile/lib/providers/theme.provider.dart index 962781586e..1d5511f1ff 100644 --- a/mobile/lib/providers/theme.provider.dart +++ b/mobile/lib/providers/theme.provider.dart @@ -1,17 +1,58 @@ +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; + +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; -import 'package:immich_mobile/theme/dynamic_theme.dart'; import 'package:immich_mobile/theme/theme_data.dart'; +import 'package:immich_mobile/theme/dynamic_theme.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +final immichThemeModeProvider = StateProvider((ref) { + final themeMode = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.themeMode); + + dPrint(() => "Current themeMode $themeMode"); + + if (themeMode == ThemeMode.light.name) { + return ThemeMode.light; + } else if (themeMode == ThemeMode.dark.name) { + return ThemeMode.dark; + } else { + return ThemeMode.system; + } +}); + +final immichThemePresetProvider = StateProvider((ref) { + final appSettingsProvider = ref.watch(appSettingsServiceProvider); + final primaryColorPreset = appSettingsProvider.getSetting(AppSettingsEnum.primaryColor); + + dPrint(() => "Current theme preset $primaryColorPreset"); + + try { + return ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorPreset); + } catch (e) { + dPrint(() => "Theme preset $primaryColorPreset not found. Applying default preset."); + appSettingsProvider.setSetting(AppSettingsEnum.primaryColor, defaultColorPresetName); + return defaultColorPreset; + } +}); + +final dynamicThemeSettingProvider = StateProvider((ref) { + return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.dynamicTheme); +}); + +final colorfulInterfaceSettingProvider = StateProvider((ref) { + return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.colorfulInterface); +}); // Provider for current selected theme final immichThemeProvider = StateProvider((ref) { - final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme)); - + final primaryColorPreset = ref.read(immichThemePresetProvider); + final useSystemColor = ref.watch(dynamicThemeSettingProvider); + final useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider); final ImmichTheme? dynamicTheme = DynamicTheme.theme; - final currentTheme = (themeConfig.dynamicTheme && dynamicTheme != null) - ? dynamicTheme - : themeConfig.primaryColor.themeOfPreset; + final currentTheme = (useSystemColor && dynamicTheme != null) ? dynamicTheme : primaryColorPreset.themeOfPreset; - return themeConfig.colorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme); + return useColorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme); }); diff --git a/mobile/lib/providers/timeline.provider.dart b/mobile/lib/providers/timeline.provider.dart new file mode 100644 index 0000000000..71ea308dbf --- /dev/null +++ b/mobile/lib/providers/timeline.provider.dart @@ -0,0 +1,68 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/locale_provider.dart'; +import 'package:immich_mobile/services/timeline.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; + +final singleUserTimelineProvider = StreamProvider.family((ref, userId) { + if (userId == null) { + return const Stream.empty(); + } + + ref.watch(localeProvider); + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchHomeTimeline(userId); +}, dependencies: [localeProvider]); + +final multiUsersTimelineProvider = StreamProvider.family>((ref, userIds) { + ref.watch(localeProvider); + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchMultiUsersTimeline(userIds); +}, dependencies: [localeProvider]); + +final albumTimelineProvider = StreamProvider.autoDispose.family((ref, id) { + final album = ref.watch(albumWatcher(id)).value; + final timelineService = ref.watch(timelineServiceProvider); + + if (album != null) { + return timelineService.watchAlbumTimeline(album); + } + + return const Stream.empty(); +}); + +final archiveTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchArchiveTimeline(); +}); + +final favoriteTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchFavoriteTimeline(); +}); + +final trashTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchTrashTimeline(); +}); + +final allVideosTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchAllVideosTimeline(); +}); + +final assetSelectionTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchAssetSelectionTimeline(); +}); + +final assetsTimelineProvider = FutureProvider.family>((ref, assets) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.getTimelineFromAssets(assets, null); +}); + +final lockedTimelineProvider = StreamProvider((ref) { + final timelineService = ref.watch(timelineServiceProvider); + return timelineService.watchLockedTimelineProvider(); +}); diff --git a/mobile/lib/providers/timeline/multiselect.provider.dart b/mobile/lib/providers/timeline/multiselect.provider.dart index f74201e983..6e375f3852 100644 --- a/mobile/lib/providers/timeline/multiselect.provider.dart +++ b/mobile/lib/providers/timeline/multiselect.provider.dart @@ -1,27 +1,20 @@ -// ignore_for_file: use-ref-and-state-synchronously - -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -part 'multiselect.provider.freezed.dart'; - final multiSelectProvider = NotifierProvider( MultiSelectNotifier.new, dependencies: [timelineServiceProvider], ); -@freezed -abstract class MultiSelectState with _$MultiSelectState { - const MultiSelectState._(); +class MultiSelectState { + final Set selectedAssets; + final Set lockedSelectionAssets; + final bool forceEnable; - const factory MultiSelectState({ - required Set selectedAssets, - required Set lockedSelectionAssets, - @Default(false) bool forceEnable, - }) = _MultiSelectState; + const MultiSelectState({required this.selectedAssets, required this.lockedSelectionAssets, this.forceEnable = false}); bool get isEnabled => selectedAssets.isNotEmpty; @@ -29,11 +22,42 @@ abstract class MultiSelectState with _$MultiSelectState { bool get hasRemote => selectedAssets.any((asset) => asset.storage == AssetState.remote || asset.storage == AssetState.merged); + bool get hasStacked => selectedAssets.any((asset) => asset is RemoteAsset && asset.stackId != null); + bool get hasMerged => selectedAssets.any((asset) => asset.storage == AssetState.merged); bool get onlyLocal => selectedAssets.any((asset) => asset.storage == AssetState.local); bool get onlyRemote => selectedAssets.any((asset) => asset.storage == AssetState.remote); + + MultiSelectState copyWith({ + Set? selectedAssets, + Set? lockedSelectionAssets, + bool? forceEnable, + }) { + return MultiSelectState( + selectedAssets: selectedAssets ?? this.selectedAssets, + lockedSelectionAssets: lockedSelectionAssets ?? this.lockedSelectionAssets, + forceEnable: forceEnable ?? this.forceEnable, + ); + } + + @override + String toString() => + 'MultiSelectState(selectedAssets: $selectedAssets, lockedSelectionAssets: $lockedSelectionAssets, forceEnable: $forceEnable)'; + + @override + bool operator ==(covariant MultiSelectState other) { + if (identical(this, other)) return true; + final setEquals = const DeepCollectionEquality().equals; + + return setEquals(other.selectedAssets, selectedAssets) && + setEquals(other.lockedSelectionAssets, lockedSelectionAssets) && + other.forceEnable == forceEnable; + } + + @override + int get hashCode => selectedAssets.hashCode ^ lockedSelectionAssets.hashCode ^ forceEnable.hashCode; } class MultiSelectNotifier extends Notifier { @@ -76,7 +100,7 @@ class MultiSelectNotifier extends Notifier { } /// Bucket bulk operations - Future selectBucket(int offset, int bucketCount) async { + void selectBucket(int offset, int bucketCount) async { final assets = await _timelineService.loadAssets(offset, bucketCount); final selectedAssets = state.selectedAssets.toSet(); @@ -85,7 +109,7 @@ class MultiSelectNotifier extends Notifier { state = state.copyWith(selectedAssets: selectedAssets); } - Future deselectBucket(int offset, int bucketCount) async { + void deselectBucket(int offset, int bucketCount) async { final assets = await _timelineService.loadAssets(offset, bucketCount); final selectedAssets = state.selectedAssets.toSet(); @@ -94,15 +118,13 @@ class MultiSelectNotifier extends Notifier { state = state.copyWith(selectedAssets: selectedAssets); } - Future toggleBucketSelection(int offset, int bucketCount) async { + void toggleBucketSelection(int offset, int bucketCount) async { final assets = await _timelineService.loadAssets(offset, bucketCount); toggleBucketSelectionByAssets(assets); } void toggleBucketSelectionByAssets(List bucketAssets) { - if (bucketAssets.isEmpty) { - return; - } + if (bucketAssets.isEmpty) return; // Check if all assets in this bucket are currently selected final allSelected = bucketAssets.every((asset) => state.selectedAssets.contains(asset)); @@ -128,9 +150,7 @@ class MultiSelectNotifier extends Notifier { final bucketSelectionProvider = Provider.family>((ref, bucketAssets) { final selectedAssets = ref.watch(multiSelectProvider.select((s) => s.selectedAssets)); - if (bucketAssets.isEmpty) { - return false; - } + if (bucketAssets.isEmpty) return false; // Check if all assets in the bucket are selected return bucketAssets.every((asset) => selectedAssets.contains(asset)); diff --git a/mobile/lib/providers/trash.provider.dart b/mobile/lib/providers/trash.provider.dart new file mode 100644 index 0000000000..41b9160b9b --- /dev/null +++ b/mobile/lib/providers/trash.provider.dart @@ -0,0 +1,45 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/trash.service.dart'; +import 'package:logging/logging.dart'; + +class TrashNotifier extends StateNotifier { + final TrashService _trashService; + final _log = Logger('TrashNotifier'); + + TrashNotifier(this._trashService) : super(false); + + Future emptyTrash() async { + try { + await _trashService.emptyTrash(); + state = true; + } catch (error, stack) { + _log.severe("Cannot empty trash", error, stack); + state = false; + } + } + + Future restoreAssets(Iterable assetList) async { + try { + await _trashService.restoreAssets(assetList); + return true; + } catch (error, stack) { + _log.severe("Cannot restore assets", error, stack); + return false; + } + } + + Future restoreTrash() async { + try { + await _trashService.restoreTrash(); + state = true; + } catch (error, stack) { + _log.severe("Cannot restore trash", error, stack); + state = false; + } + } +} + +final trashProvider = StateNotifierProvider((ref) { + return TrashNotifier(ref.watch(trashServiceProvider)); +}); diff --git a/mobile/lib/providers/upload_profile_image.provider.dart b/mobile/lib/providers/upload_profile_image.provider.dart index 9b5e83622e..a2b7a23f05 100644 --- a/mobile/lib/providers/upload_profile_image.provider.dart +++ b/mobile/lib/providers/upload_profile_image.provider.dart @@ -1,18 +1,58 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:convert'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/utils/debug_print.dart'; -part 'upload_profile_image.provider.freezed.dart'; - enum UploadProfileStatus { idle, loading, success, failure } -@freezed -abstract class UploadProfileImageState with _$UploadProfileImageState { - const factory UploadProfileImageState({required UploadProfileStatus status, required String profileImagePath}) = - _UploadProfileImageState; +class UploadProfileImageState { + // enum + final UploadProfileStatus status; + final String profileImagePath; + const UploadProfileImageState({required this.status, required this.profileImagePath}); + + UploadProfileImageState copyWith({UploadProfileStatus? status, String? profileImagePath}) { + return UploadProfileImageState( + status: status ?? this.status, + profileImagePath: profileImagePath ?? this.profileImagePath, + ); + } + + Map toMap() { + final result = {}; + + result.addAll({'status': status.index}); + result.addAll({'profileImagePath': profileImagePath}); + + return result; + } + + factory UploadProfileImageState.fromMap(Map map) { + return UploadProfileImageState( + status: UploadProfileStatus.values[map['status'] ?? 0], + profileImagePath: map['profileImagePath'] ?? '', + ); + } + + String toJson() => json.encode(toMap()); + + factory UploadProfileImageState.fromJson(String source) => UploadProfileImageState.fromMap(json.decode(source)); + + @override + String toString() => 'UploadProfileImageState(status: $status, profileImagePath: $profileImagePath)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is UploadProfileImageState && other.status == status && other.profileImagePath == profileImagePath; + } + + @override + int get hashCode => status.hashCode ^ profileImagePath.hashCode; } class UploadProfileImageNotifier extends StateNotifier { @@ -24,7 +64,7 @@ class UploadProfileImageNotifier extends StateNotifier Future upload(XFile file, {String? fileName}) async { state = state.copyWith(status: UploadProfileStatus.loading); - final profileImagePath = await _userService.createProfileImage(fileName ?? file.name, await file.readAsBytes()); + var profileImagePath = await _userService.createProfileImage(fileName ?? file.name, await file.readAsBytes()); if (profileImagePath != null) { dPrint(() => "Successfully upload profile image"); @@ -38,5 +78,5 @@ class UploadProfileImageNotifier extends StateNotifier } final uploadProfileImageProvider = StateNotifierProvider( - (ref) => UploadProfileImageNotifier(ref.watch(userServiceProvider)), + ((ref) => UploadProfileImageNotifier(ref.watch(userServiceProvider))), ); diff --git a/mobile/lib/providers/user.provider.dart b/mobile/lib/providers/user.provider.dart index 427a1bf1c7..10dcb2aff5 100644 --- a/mobile/lib/providers/user.provider.dart +++ b/mobile/lib/providers/user.provider.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/services/timeline.service.dart'; class CurrentUserProvider extends StateNotifier { CurrentUserProvider(this._userService) : super(null) { @@ -14,7 +16,7 @@ class CurrentUserProvider extends StateNotifier { final UserService _userService; late final StreamSubscription streamSub; - Future refresh() async { + refresh() async { try { await _userService.refreshMyUser(); } catch (_) {} @@ -22,7 +24,7 @@ class CurrentUserProvider extends StateNotifier { @override void dispose() { - unawaited(streamSub.cancel()); + streamSub.cancel(); super.dispose(); } } @@ -31,10 +33,27 @@ final currentUserProvider = StateNotifierProvider return CurrentUserProvider(ref.watch(userServiceProvider)); }); -final authUserProvider = Provider((ref) { - final user = ref.watch(currentUserProvider); - if (user == null) { - throw Exception('User must be logged in to access this provider'); +class TimelineUserIdsProvider extends StateNotifier> { + TimelineUserIdsProvider(this._timelineService) : super([]) { + final listEquality = const ListEquality(); + _timelineService.getTimelineUserIds().then((users) => state = users); + streamSub = _timelineService.watchTimelineUserIds().listen((users) { + if (!listEquality.equals(state, users)) { + state = users; + } + }); } - return user; + + late final StreamSubscription> streamSub; + final TimelineService _timelineService; + + @override + void dispose() { + streamSub.cancel(); + super.dispose(); + } +} + +final timelineUsersIdsProvider = StateNotifierProvider>((ref) { + return TimelineUserIdsProvider(ref.watch(timelineServiceProvider)); }); diff --git a/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart b/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart deleted file mode 100644 index 75cd304a3a..0000000000 --- a/mobile/lib/providers/view_intent/view_intent_file_path.provider.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; - -class ViewIntentFilePathNotifier extends Notifier { - @override - String? build() => null; - - void setPath(String path) { - if (state == path) { - return; - } - state = path; - } - - void clear() { - if (state == null) { - return; - } - state = null; - } - - void clearIfMatch(String path) { - if (state != path) { - return; - } - state = null; - } -} - -final viewIntentFilePathProvider = NotifierProvider( - ViewIntentFilePathNotifier.new, -); diff --git a/mobile/lib/providers/view_intent/view_intent_handler.provider.dart b/mobile/lib/providers/view_intent/view_intent_handler.provider.dart deleted file mode 100644 index b266887cab..0000000000 --- a/mobile/lib/providers/view_intent/view_intent_handler.provider.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:io'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler_android.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler_stub.dart'; - -abstract class ViewIntentHandler { - void init(); - - Future onAppResumed(); - - Future flushDeferredViewIntent(); - - Future handle(ViewIntentPayload attachment); -} - -final viewIntentHandlerProvider = Provider((ref) { - if (Platform.isAndroid) { - return AndroidViewIntentHandler(ref); - } - - return const StubViewIntentHandler(); -}); diff --git a/mobile/lib/providers/view_intent/view_intent_handler_android.dart b/mobile/lib/providers/view_intent/view_intent_handler_android.dart deleted file mode 100644 index 51415af7b1..0000000000 --- a/mobile/lib/providers/view_intent/view_intent_handler_android.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_file_path.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/view_intent.service.dart'; -import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; -import 'package:logging/logging.dart'; - -class AndroidViewIntentHandler implements ViewIntentHandler { - final Ref _ref; - final ViewIntentService _viewIntentService; - final ViewIntentAssetResolver _viewIntentAssetResolver; - final AppRouter _router; - static final Logger _logger = Logger('ViewIntentHandler'); - - AndroidViewIntentHandler(Ref ref) - : _ref = ref, - _viewIntentService = ref.read(viewIntentServiceProvider), - _viewIntentAssetResolver = ref.read(viewIntentAssetResolverProvider), - _router = ref.watch(appRouterProvider); - - @override - void init() { - // Covers cold start from a view intent before the first lifecycle "resumed". - unawaited(onAppResumed()); - } - - @override - Future onAppResumed() => _checkForViewIntent(); - - @override - Future flushDeferredViewIntent() => _flushPending(); - - Future _checkForViewIntent() async { - final attachment = await _viewIntentService.consumeViewIntent(); - if (attachment != null) { - await handle(attachment); - return; - } - - if (_ref.read(viewIntentPendingProvider) == null) { - await _viewIntentService.cleanupStaleTempFiles(); - } - } - - Future _flushPending() async { - final pendingAttachment = _ref.read(viewIntentPendingProvider.notifier).takeIfFresh(); - _logger.info('flushPending, pendingAttachment:$pendingAttachment'); - if (pendingAttachment != null) { - await handle(pendingAttachment); - } - } - - @override - Future handle(ViewIntentPayload attachment) async { - _logger.info( - 'handle attachment, mimeType:${attachment.mimeType}, localAssetId=${attachment.localAssetId}, path=${attachment.path}, isAuthenticated:${_ref.read(authProvider).isAuthenticated}', - ); - - if (!_ref.read(authProvider).isAuthenticated) { - _ref.read(viewIntentPendingProvider.notifier).defer(attachment); - return; - } - - final resolvedAsset = await _viewIntentAssetResolver.resolve(attachment); - _logger.fine('resolved view intent asset: ${resolvedAsset.asset}'); - await _openAssetViewer( - resolvedAsset.asset, - resolvedAsset.timelineService, - viewIntentFilePath: resolvedAsset.viewIntentFilePath, - ); - } - - Future _openAssetViewer(BaseAsset asset, TimelineService timelineService, {String? viewIntentFilePath}) async { - final notifier = _ref.read(assetViewerProvider.notifier); - notifier.reset(); - if (asset.isVideo) { - notifier.setControls(false); - } - notifier.setAsset(asset); - - if (viewIntentFilePath != null) { - _ref.read(viewIntentFilePathProvider.notifier).setPath(viewIntentFilePath); - unawaited(_viewIntentService.setManagedTempFilePath(viewIntentFilePath)); - } else { - _ref.read(viewIntentFilePathProvider.notifier).clear(); - unawaited(_viewIntentService.cleanupManagedTempFile()); - } - - await _router.replaceAll([ - const TabShellRoute(), - AssetViewerRoute(key: UniqueKey(), initialIndex: 0, timelineService: timelineService), - ]); - } -} diff --git a/mobile/lib/providers/view_intent/view_intent_handler_stub.dart b/mobile/lib/providers/view_intent/view_intent_handler_stub.dart deleted file mode 100644 index ebc6d7425b..0000000000 --- a/mobile/lib/providers/view_intent/view_intent_handler_stub.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; - -class StubViewIntentHandler implements ViewIntentHandler { - const StubViewIntentHandler(); - - @override - void init() {} - - @override - Future onAppResumed() async {} - - @override - Future flushDeferredViewIntent() async {} - - @override - Future handle(ViewIntentPayload attachment) async {} -} diff --git a/mobile/lib/providers/view_intent/view_intent_pending.provider.dart b/mobile/lib/providers/view_intent/view_intent_pending.provider.dart deleted file mode 100644 index c3f68eff79..0000000000 --- a/mobile/lib/providers/view_intent/view_intent_pending.provider.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; - -final viewIntentNowProvider = Provider((ref) => DateTime.now); - -final viewIntentPendingProvider = NotifierProvider( - ViewIntentPendingNotifier.new, -); - -class ViewIntentPendingNotifier extends Notifier { - static const _ttl = Duration(minutes: 10); - - DateTime? _deferredAt; - - @override - ViewIntentPayload? build() => null; - - void defer(ViewIntentPayload attachment) { - _deferredAt = ref.read(viewIntentNowProvider)(); - state = attachment; - } - - ViewIntentPayload? takeIfFresh() { - final attachment = state; - final deferredAt = _deferredAt; - state = null; - _deferredAt = null; - - if (attachment == null) { - return null; - } - - if (deferredAt != null && ref.read(viewIntentNowProvider)().difference(deferredAt) > _ttl) { - return null; - } - - return attachment; - } -} diff --git a/mobile/lib/providers/websocket.provider.dart b/mobile/lib/providers/websocket.provider.dart index 32531c354b..6643404786 100644 --- a/mobile/lib/providers/websocket.provider.dart +++ b/mobile/lib/providers/websocket.provider.dart @@ -1,34 +1,82 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; import 'package:immich_mobile/models/server_info/server_version.model.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/services/sync.service.dart'; import 'package:immich_mobile/utils/debounce.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:socket_io_client/socket_io_client.dart'; -part 'websocket.provider.freezed.dart'; +enum PendingAction { assetDelete, assetUploaded, assetHidden, assetTrash } -@freezed -abstract class WebsocketState with _$WebsocketState { - const factory WebsocketState({Socket? socket, required bool isConnected}) = _WebsocketState; +class PendingChange { + final String id; + final PendingAction action; + final dynamic value; + + const PendingChange(this.id, this.action, this.value); + + @override + String toString() => 'PendingChange(id: $id, action: $action, value: $value)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is PendingChange && other.id == id && other.action == action; + } + + @override + int get hashCode => id.hashCode ^ action.hashCode; +} + +class WebsocketState { + final Socket? socket; + final bool isConnected; + final List pendingChanges; + + const WebsocketState({this.socket, required this.isConnected, required this.pendingChanges}); + + WebsocketState copyWith({Socket? socket, bool? isConnected, List? pendingChanges}) { + return WebsocketState( + socket: socket ?? this.socket, + isConnected: isConnected ?? this.isConnected, + pendingChanges: pendingChanges ?? this.pendingChanges, + ); + } + + @override + String toString() => 'WebsocketState(socket: $socket, isConnected: $isConnected)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is WebsocketState && other.socket == socket && other.isConnected == isConnected; + } + + @override + int get hashCode => socket.hashCode ^ isConnected.hashCode; } class WebsocketNotifier extends StateNotifier { - WebsocketNotifier(this._ref) : super(const WebsocketState(socket: null, isConnected: false)); + WebsocketNotifier(this._ref) : super(const WebsocketState(socket: null, isConnected: false, pendingChanges: [])); final _log = Logger('WebsocketNotifier'); final Ref _ref; + final Debouncer _debounce = Debouncer(interval: const Duration(milliseconds: 500)); final Debouncer _batchDebouncer = Debouncer( interval: const Duration(seconds: 5), @@ -39,16 +87,12 @@ class WebsocketNotifier extends StateNotifier { @override void dispose() { _batchDebouncer.dispose(); - state.socket?.dispose(); super.dispose(); } - /// Connects websocket to server unless an active socket already exists + /// Connects websocket to server unless already connected void connect() { - if (state.socket?.active == true) { - return; - } - state.socket?.dispose(); + if (state.isConnected) return; final authenticationState = _ref.read(authProvider); if (authenticationState.isAuthenticated) { @@ -56,7 +100,7 @@ class WebsocketNotifier extends StateNotifier { final endpoint = Uri.parse(Store.get(StoreKey.serverEndpoint)); dPrint(() => "Attempting to connect to websocket"); // Configure socket transports must be specified - final Socket socket = io( + Socket socket = io( endpoint.origin, OptionBuilder() .setPath("${endpoint.path}/socket.io") @@ -69,38 +113,38 @@ class WebsocketNotifier extends StateNotifier { .build(), ); - state = WebsocketState(isConnected: false, socket: socket); - socket.onConnect((_) { dPrint(() => "Established Websocket Connection"); - state = WebsocketState(isConnected: true, socket: socket); + state = WebsocketState(isConnected: true, socket: socket, pendingChanges: state.pendingChanges); }); socket.onDisconnect((_) { dPrint(() => "Disconnect to Websocket Connection"); - state = WebsocketState(isConnected: false, socket: socket); + state = WebsocketState(isConnected: false, socket: null, pendingChanges: state.pendingChanges); }); socket.on('error', (errorMessage) { _log.severe("Websocket Error - $errorMessage"); - state = WebsocketState(isConnected: false, socket: socket); + state = WebsocketState(isConnected: false, socket: null, pendingChanges: state.pendingChanges); }); - socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReadyV1); - socket.on('AssetUploadReadyV2', _handleSyncAssetUploadReadyV2); - socket.on('AssetEditReadyV1', _handleSyncAssetEditReadyV1); - socket.on('AssetEditReadyV2', _handleSyncAssetEditReadyV2); - socket.on('on_album_update', _handleRemoteChange); - socket.on('on_asset_stack_update', _handleRemoteChange); - socket.on('on_asset_delete', _handleRemoteChange); - socket.on('on_asset_trash', _handleRemoteChange); - socket.on('on_asset_restore', _handleRemoteChange); - socket.on('on_asset_hidden', _handleRemoteChange); - socket.on('on_asset_update', _handleRemoteChange); + if (!Store.isBetaTimelineEnabled) { + socket.on('on_upload_success', _handleOnUploadSuccess); + socket.on('on_asset_delete', _handleOnAssetDelete); + socket.on('on_asset_trash', _handleOnAssetTrash); + socket.on('on_asset_restore', _handleServerUpdates); + socket.on('on_asset_update', _handleServerUpdates); + socket.on('on_asset_stack_update', _handleServerUpdates); + socket.on('on_asset_hidden', _handleOnAssetHidden); + } else { + socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady); + socket.on('AssetEditReadyV1', _handleSyncAssetEditReady); + } + socket.on('on_config_update', _handleOnConfigUpdate); socket.on('on_new_release', _handleReleaseUpdates); } catch (e) { - dPrint(() => "[WEBSOCKET] Catch Websocket Error - $e"); + dPrint(() => "[WEBSOCKET] Catch Websocket Error - ${e.toString()}"); } } } @@ -111,36 +155,132 @@ class WebsocketNotifier extends StateNotifier { _batchedAssetUploadReady.clear(); state.socket?.dispose(); - state = const WebsocketState(isConnected: false, socket: null); + state = WebsocketState(isConnected: false, socket: null, pendingChanges: state.pendingChanges); } - Future waitForEvent(String event, bool Function(dynamic)? predicate, Duration timeout) { - final completer = Completer(); + void stopListenToEvent(String eventName) { + state.socket?.off(eventName); + } - void handler(dynamic data) { - if (predicate == null || predicate(data)) { - completer.complete(); - state.socket?.off(event, handler); - } - } + void stopListenToOldEvents() { + state.socket?.off('on_upload_success'); + state.socket?.off('on_asset_delete'); + state.socket?.off('on_asset_trash'); + state.socket?.off('on_asset_restore'); + state.socket?.off('on_asset_update'); + state.socket?.off('on_asset_stack_update'); + state.socket?.off('on_asset_hidden'); + } - state.socket?.on(event, handler); + void startListeningToOldEvents() { + state.socket?.on('on_upload_success', _handleOnUploadSuccess); + state.socket?.on('on_asset_delete', _handleOnAssetDelete); + state.socket?.on('on_asset_trash', _handleOnAssetTrash); + state.socket?.on('on_asset_restore', _handleServerUpdates); + state.socket?.on('on_asset_update', _handleServerUpdates); + state.socket?.on('on_asset_stack_update', _handleServerUpdates); + state.socket?.on('on_asset_hidden', _handleOnAssetHidden); + } - return completer.future.timeout( - timeout, - onTimeout: () { - state.socket?.off(event, handler); - completer.completeError(TimeoutException("Timeout waiting for event: $event")); - }, + void stopListeningToBetaEvents() { + state.socket?.off('AssetUploadReadyV1'); + state.socket?.off('AssetEditReadyV1'); + } + + void startListeningToBetaEvents() { + state.socket?.on('AssetUploadReadyV1', _handleSyncAssetUploadReady); + state.socket?.on('AssetEditReadyV1', _handleSyncAssetEditReady); + } + + void listenUploadEvent() { + dPrint(() => "Start listening to event on_upload_success"); + state.socket?.on('on_upload_success', _handleOnUploadSuccess); + } + + void addPendingChange(PendingAction action, dynamic value) { + final now = DateTime.now(); + state = state.copyWith( + pendingChanges: [...state.pendingChanges, PendingChange(now.millisecondsSinceEpoch.toString(), action, value)], ); + _debounce.run(handlePendingChanges); + } + + Future _handlePendingTrashes() async { + final trashChanges = state.pendingChanges.where((c) => c.action == PendingAction.assetTrash).toList(); + if (trashChanges.isNotEmpty) { + List remoteIds = trashChanges.expand((a) => (a.value as List).map((e) => e.toString())).toList(); + + await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds); + await _ref.read(assetProvider.notifier).getAllAsset(); + + state = state.copyWith(pendingChanges: state.pendingChanges.whereNot((c) => trashChanges.contains(c)).toList()); + } + } + + Future _handlePendingDeletes() async { + final deleteChanges = state.pendingChanges.where((c) => c.action == PendingAction.assetDelete).toList(); + if (deleteChanges.isNotEmpty) { + List remoteIds = deleteChanges.map((a) => a.value.toString()).toList(); + await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds); + state = state.copyWith(pendingChanges: state.pendingChanges.whereNot((c) => deleteChanges.contains(c)).toList()); + } + } + + Future _handlePendingUploaded() async { + final uploadedChanges = state.pendingChanges.where((c) => c.action == PendingAction.assetUploaded).toList(); + if (uploadedChanges.isNotEmpty) { + List remoteAssets = uploadedChanges.map((a) => AssetResponseDto.fromJson(a.value)).toList(); + for (final dto in remoteAssets) { + if (dto != null) { + final newAsset = Asset.remote(dto); + await _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset); + } + } + state = state.copyWith( + pendingChanges: state.pendingChanges.whereNot((c) => uploadedChanges.contains(c)).toList(), + ); + } + } + + Future _handlingPendingHidden() async { + final hiddenChanges = state.pendingChanges.where((c) => c.action == PendingAction.assetHidden).toList(); + if (hiddenChanges.isNotEmpty) { + List remoteIds = hiddenChanges.map((a) => a.value.toString()).toList(); + final db = _ref.watch(dbProvider); + await db.writeTxn(() => db.assets.deleteAllByRemoteId(remoteIds)); + + state = state.copyWith(pendingChanges: state.pendingChanges.whereNot((c) => hiddenChanges.contains(c)).toList()); + } + } + + Future handlePendingChanges() async { + await _handlePendingUploaded(); + await _handlePendingDeletes(); + await _handlingPendingHidden(); + await _handlePendingTrashes(); } void _handleOnConfigUpdate(dynamic _) { - unawaited(_ref.read(serverInfoProvider.notifier).getServerFeatures()); - unawaited(_ref.read(serverInfoProvider.notifier).getServerConfig()); + _ref.read(serverInfoProvider.notifier).getServerFeatures(); + _ref.read(serverInfoProvider.notifier).getServerConfig(); } - void _handleReleaseUpdates(dynamic data) { + // Refresh updated assets + void _handleServerUpdates(dynamic _) { + _ref.read(assetProvider.notifier).getAllAsset(); + } + + void _handleOnUploadSuccess(dynamic data) => addPendingChange(PendingAction.assetUploaded, data); + + void _handleOnAssetDelete(dynamic data) => addPendingChange(PendingAction.assetDelete, data); + + void _handleOnAssetTrash(dynamic data) { + addPendingChange(PendingAction.assetTrash, data); + } + + void _handleOnAssetHidden(dynamic data) => addPendingChange(PendingAction.assetHidden, data); + + _handleReleaseUpdates(dynamic data) { // Json guard if (data is! Map) { return; @@ -164,39 +304,26 @@ class WebsocketNotifier extends StateNotifier { _ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion); } - void _handleSyncAssetUploadReadyV1(dynamic data) { + void _handleSyncAssetUploadReady(dynamic data) { _batchedAssetUploadReady.add(data); - _batchDebouncer.run(_processBatchedAssetUploadReadyV1); + _batchDebouncer.run(_processBatchedAssetUploadReady); } - void _handleSyncAssetUploadReadyV2(dynamic data) { - _batchedAssetUploadReady.add(data); - _batchDebouncer.run(_processBatchedAssetUploadReadyV2); + void _handleSyncAssetEditReady(dynamic data) { + unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEdit(data)); } - void _handleSyncAssetEditReadyV1(dynamic data) { - unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data)); - } - - void _handleRemoteChange(dynamic _) { - unawaited(_ref.read(backgroundSyncProvider).syncRemote(enqueue: true)); - } - - void _handleSyncAssetEditReadyV2(dynamic data) { - unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data)); - } - - void _processBatchedAssetUploadReadyV1() { + void _processBatchedAssetUploadReady() { if (_batchedAssetUploadReady.isEmpty) { return; } - final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums; + final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false); try { unawaited( - _ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) { + _ref.read(backgroundSyncProvider).syncWebsocketBatch(_batchedAssetUploadReady.toList()).then((_) { if (isSyncAlbumEnabled) { - unawaited(_ref.read(backgroundSyncProvider).syncLinkedAlbum()); + _ref.read(backgroundSyncProvider).syncLinkedAlbum(); } }), ); @@ -206,27 +333,6 @@ class WebsocketNotifier extends StateNotifier { _batchedAssetUploadReady.clear(); } - - void _processBatchedAssetUploadReadyV2() { - if (_batchedAssetUploadReady.isEmpty) { - return; - } - - final isSyncAlbumEnabled = _ref.read(appConfigProvider).backup.syncAlbums; - try { - unawaited( - _ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) { - if (isSyncAlbumEnabled) { - unawaited(_ref.read(backgroundSyncProvider).syncLinkedAlbum()); - } - }), - ); - } catch (error) { - _log.severe("Error processing batched AssetUploadReadyV2 events: $error"); - } - - _batchedAssetUploadReady.clear(); - } } final websocketProvider = StateNotifierProvider((ref) { diff --git a/mobile/lib/repositories/activity_api.repository.dart b/mobile/lib/repositories/activity_api.repository.dart index 9ae83b0e7b..e8f9abc8c8 100644 --- a/mobile/lib/repositories/activity_api.repository.dart +++ b/mobile/lib/repositories/activity_api.repository.dart @@ -23,8 +23,8 @@ class ActivityApiRepository extends ApiRepository { final dto = ActivityCreateDto( albumId: albumId, type: type == ActivityType.comment ? ReactionType.comment : ReactionType.like, - assetId: assetId == null ? const Optional.absent() : Optional.present(assetId), - comment: comment == null ? const Optional.absent() : Optional.present(comment), + assetId: assetId, + comment: comment, ); final response = await checkNull(_api.createActivity(dto)); return _toActivity(response); @@ -45,6 +45,6 @@ class ActivityApiRepository extends ApiRepository { type: dto.type == ReactionType.comment ? ActivityType.comment : ActivityType.like, user: UserConverter.fromSimpleUserDto(dto.user), assetId: dto.assetId, - comment: dto.comment.orElse(null), + comment: dto.comment, ); } diff --git a/mobile/lib/repositories/album.repository.dart b/mobile/lib/repositories/album.repository.dart new file mode 100644 index 0000000000..2d24004944 --- /dev/null +++ b/mobile/lib/repositories/album.repository.dart @@ -0,0 +1,139 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; +import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +enum AlbumSort { remoteId, localId } + +final albumRepositoryProvider = Provider((ref) => AlbumRepository(ref.watch(dbProvider))); + +class AlbumRepository extends DatabaseRepository { + const AlbumRepository(super.db); + + Future count({bool? local}) { + final baseQuery = db.albums.where(); + final QueryBuilder query = switch (local) { + null => baseQuery.noOp(), + true => baseQuery.localIdIsNotNull(), + false => baseQuery.remoteIdIsNotNull(), + }; + return query.count(); + } + + Future create(Album album) => txn(() => db.albums.store(album)); + + Future getByName(String name, {bool? shared, bool? remote, bool? owner}) { + var query = db.albums.filter().nameEqualTo(name); + if (shared != null) { + query = query.sharedEqualTo(shared); + } + final isarUserId = fastHash(Store.get(StoreKey.currentUser).id); + if (owner == true) { + query = query.owner((q) => q.isarIdEqualTo(isarUserId)); + } else if (owner == false) { + query = query.owner((q) => q.not().isarIdEqualTo(isarUserId)); + } + if (remote == true) { + query = query.localIdIsNull(); + } else if (remote == false) { + query = query.remoteIdIsNull(); + } + return query.findFirst(); + } + + Future update(Album album) => txn(() => db.albums.store(album)); + + Future delete(int albumId) => txn(() => db.albums.delete(albumId)); + + Future> getAll({bool? shared, bool? remote, int? ownerId, AlbumSort? sortBy}) { + final baseQuery = db.albums.where(); + final QueryBuilder afterWhere; + if (remote == null) { + afterWhere = baseQuery.noOp(); + } else if (remote) { + afterWhere = baseQuery.remoteIdIsNotNull(); + } else { + afterWhere = baseQuery.localIdIsNotNull(); + } + QueryBuilder filterQuery = afterWhere.filter().noOp(); + if (shared != null) { + filterQuery = filterQuery.sharedEqualTo(true); + } + if (ownerId != null) { + filterQuery = filterQuery.owner((q) => q.isarIdEqualTo(ownerId)); + } + final QueryBuilder query = switch (sortBy) { + null => filterQuery.noOp(), + AlbumSort.remoteId => filterQuery.sortByRemoteId(), + AlbumSort.localId => filterQuery.sortByLocalId(), + }; + return query.findAll(); + } + + Future get(int id) => db.albums.get(id); + + Future getByRemoteId(String remoteId) { + return db.albums.filter().remoteIdEqualTo(remoteId).findFirst(); + } + + Future removeUsers(Album album, List users) => + txn(() => album.sharedUsers.update(unlink: users.map(entity.User.fromDto))); + + Future addAssets(Album album, List assets) => txn(() => album.assets.update(link: assets)); + + Future removeAssets(Album album, List assets) => txn(() => album.assets.update(unlink: assets)); + + Future recalculateMetadata(Album album) async { + album.startDate = await album.assets.filter().fileCreatedAtProperty().min(); + album.endDate = await album.assets.filter().fileCreatedAtProperty().max(); + album.lastModifiedAssetTimestamp = await album.assets.filter().updatedAtProperty().max(); + return album; + } + + Future addUsers(Album album, List users) => + txn(() => album.sharedUsers.update(link: users.map(entity.User.fromDto))); + + Future deleteAllLocal() => txn(() => db.albums.where().localIdIsNotNull().deleteAll()); + + Future> search(String searchTerm, QuickFilterMode filterMode) async { + var query = db.albums.filter().nameContains(searchTerm, caseSensitive: false).remoteIdIsNotNull(); + final isarUserId = fastHash(Store.get(StoreKey.currentUser).id); + + switch (filterMode) { + case QuickFilterMode.sharedWithMe: + query = query.owner((q) => q.not().isarIdEqualTo(isarUserId)); + case QuickFilterMode.myAlbums: + query = query.owner((q) => q.isarIdEqualTo(isarUserId)); + case QuickFilterMode.all: + break; + } + + return await query.findAll(); + } + + Future clearTable() async { + await txn(() async { + await db.albums.clear(); + }); + } + + Stream> watchRemoteAlbums() { + return db.albums.where().remoteIdIsNotNull().watch(); + } + + Stream> watchLocalAlbums() { + return db.albums.where().localIdIsNotNull().watch(); + } + + Stream watchAlbum(int id) { + return db.albums.watchObject(id, fireImmediately: true); + } +} diff --git a/mobile/lib/repositories/album_api.repository.dart b/mobile/lib/repositories/album_api.repository.dart new file mode 100644 index 0000000000..525f0906ba --- /dev/null +++ b/mobile/lib/repositories/album_api.repository.dart @@ -0,0 +1,171 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart' show AlbumAssetOrder, RemoteAlbum; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; +import 'package:immich_mobile/infrastructure/utils/user.converter.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/repositories/api.repository.dart'; +import 'package:openapi/api.dart'; + +final albumApiRepositoryProvider = Provider((ref) => AlbumApiRepository(ref.watch(apiServiceProvider).albumsApi)); + +class AlbumApiRepository extends ApiRepository { + final AlbumsApi _api; + + AlbumApiRepository(this._api); + + Future get(String id) async { + final dto = await checkNull(_api.getAlbumInfo(id)); + return _toAlbum(dto); + } + + Future> getAll({bool? shared}) async { + final dtos = await checkNull(_api.getAllAlbums(shared: shared)); + return dtos.map(_toAlbum).toList(); + } + + Future create( + String name, { + required Iterable assetIds, + Iterable sharedUserIds = const [], + String? description, + }) async { + final users = sharedUserIds.map((id) => AlbumUserCreateDto(userId: id, role: AlbumUserRole.editor)); + final responseDto = await checkNull( + _api.createAlbum( + CreateAlbumDto( + albumName: name, + description: description, + assetIds: assetIds.toList(), + albumUsers: users.toList(), + ), + ), + ); + return _toAlbum(responseDto); + } + + // TODO: Change name after removing old method + Future createDriftAlbum(String name, {required Iterable assetIds, String? description}) async { + final responseDto = await checkNull( + _api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())), + ); + + return _toRemoteAlbum(responseDto); + } + + Future update( + String albumId, { + String? name, + String? thumbnailAssetId, + String? description, + bool? activityEnabled, + SortOrder? sortOrder, + }) async { + AssetOrder? order; + if (sortOrder != null) { + order = sortOrder == SortOrder.asc ? AssetOrder.asc : AssetOrder.desc; + } + + final response = await checkNull( + _api.updateAlbumInfo( + albumId, + UpdateAlbumDto( + albumName: name, + albumThumbnailAssetId: thumbnailAssetId, + description: description, + isActivityEnabled: activityEnabled, + order: order, + ), + ), + ); + + return _toAlbum(response); + } + + Future delete(String albumId) { + return _api.deleteAlbum(albumId); + } + + Future<({List added, List duplicates})> addAssets(String albumId, Iterable assetIds) async { + final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); + + final List added = []; + final List duplicates = []; + + for (final result in response) { + if (result.success) { + added.add(result.id); + } else if (result.error == BulkIdResponseDtoErrorEnum.duplicate) { + duplicates.add(result.id); + } + } + return (added: added, duplicates: duplicates); + } + + Future<({List removed, List failed})> removeAssets(String albumId, Iterable assetIds) async { + final response = await checkNull(_api.removeAssetFromAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); + final List removed = [], failed = []; + for (final dto in response) { + if (dto.success) { + removed.add(dto.id); + } else { + failed.add(dto.id); + } + } + return (removed: removed, failed: failed); + } + + Future addUsers(String albumId, Iterable userIds) async { + final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList(); + final response = await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers))); + return _toAlbum(response); + } + + Future removeUser(String albumId, {required String userId}) { + return _api.removeUserFromAlbum(albumId, userId); + } + + static Album _toAlbum(AlbumResponseDto dto) { + final Album album = Album( + remoteId: dto.id, + name: dto.albumName, + createdAt: dto.createdAt, + modifiedAt: dto.updatedAt, + 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, + ); + album.remoteAssetCount = dto.assetCount; + album.owner.value = entity.User.fromDto(UserConverter.fromSimpleUserDto(dto.owner)); + album.remoteThumbnailAssetId = dto.albumThumbnailAssetId; + final users = dto.albumUsers.map((albumUser) => UserConverter.fromSimpleUserDto(albumUser.user)); + album.sharedUsers.addAll(users.map(entity.User.fromDto)); + final assets = dto.assets.map(Asset.remote).toList(); + album.assets.addAll(assets); + + return album; + } + + static RemoteAlbum _toRemoteAlbum(AlbumResponseDto dto) { + return RemoteAlbum( + id: dto.id, + name: dto.albumName, + ownerId: dto.owner.id, + description: dto.description, + createdAt: dto.createdAt, + updatedAt: dto.updatedAt, + thumbnailAssetId: dto.albumThumbnailAssetId, + isActivityEnabled: dto.isActivityEnabled, + order: dto.order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, + assetCount: dto.assetCount, + ownerName: dto.owner.name, + isShared: dto.albumUsers.length > 2, + ); + } +} diff --git a/mobile/lib/repositories/album_media.repository.dart b/mobile/lib/repositories/album_media.repository.dart new file mode 100644 index 0000000000..89860f4e75 --- /dev/null +++ b/mobile/lib/repositories/album_media.repository.dart @@ -0,0 +1,99 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:photo_manager/photo_manager.dart' hide AssetType; + +final albumMediaRepositoryProvider = Provider((ref) => const AlbumMediaRepository()); + +class AlbumMediaRepository { + const AlbumMediaRepository(); + + bool get useCustomFilter => Store.get(StoreKey.photoManagerCustomFilter, true); + + FilterOptionGroup? _getAlbumFilter({ + DateTimeCond? updateTimeCond, + bool? containsPathModified, + List? 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; + + Future> getAll() async { + final filter = useCustomFilter + ? CustomFilter.sql(where: '${CustomColumns.base.width} > 0') + : FilterOptionGroup(containsPathModified: true); + + final List assetPathEntities = await PhotoManager.getAssetPathList( + hasAll: true, + filterOption: filter, + ); + return assetPathEntities.map(_toAlbum).toList(); + } + + Future> getAssetIds(String albumId) async { + final album = await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); + final List assets = await album.getAssetListRange(start: 0, end: 0x7fffffffffffffff); + return assets.map((e) => e.id).toList(); + } + + Future getAssetCount(String albumId) async { + final album = await AssetPathEntity.fromId(albumId, filterOption: _getAlbumFilter()); + return album.assetCountAsync; + } + + Future> getAssets( + String albumId, { + int start = 0, + int end = 0x7fffffffffffffff, + DateTime? modifiedFrom, + DateTime? modifiedUntil, + bool orderByModificationDate = false, + }) async { + final onDevice = await AssetPathEntity.fromId( + albumId, + filterOption: _getAlbumFilter( + updateTimeCond: modifiedFrom == null && modifiedUntil == null + ? null + : DateTimeCond(min: modifiedFrom ?? DateTime.utc(-271820), max: modifiedUntil ?? DateTime.utc(275760)), + orderBy: orderByModificationDate ? [const OrderOption(type: OrderOptionType.updateDate)] : [], + ), + ); + + final List assets = await onDevice.getAssetListRange(start: start, end: end); + return assets.map(AssetMediaRepository.toAsset).toList().cast(); + } + + Future get(String id) async { + final assetPathEntity = await AssetPathEntity.fromId(id, filterOption: _getAlbumFilter(containsPathModified: true)); + return _toAlbum(assetPathEntity); + } + + static Album _toAlbum(AssetPathEntity assetPathEntity) { + final Album album = Album( + name: assetPathEntity.name, + createdAt: assetPathEntity.lastModified?.toUtc() ?? DateTime.now().toUtc(), + modifiedAt: assetPathEntity.lastModified?.toUtc() ?? DateTime.now().toUtc(), + shared: false, + activityEnabled: false, + ); + album.owner.value = User.fromDto(Store.get(StoreKey.currentUser)); + album.localId = assetPathEntity.id; + album.isAll = assetPathEntity.isAll; + return album; + } +} diff --git a/mobile/lib/repositories/api.repository.dart b/mobile/lib/repositories/api.repository.dart index 8a5c690b3b..646e2480e9 100644 --- a/mobile/lib/repositories/api.repository.dart +++ b/mobile/lib/repositories/api.repository.dart @@ -3,9 +3,7 @@ import 'package:immich_mobile/constants/errors.dart'; abstract class ApiRepository { Future checkNull(Future future) async { final response = await future; - if (response == null) { - throw const NoResponseDtoError(); - } + if (response == null) throw const NoResponseDtoError(); return response; } } diff --git a/mobile/lib/repositories/asset.repository.dart b/mobile/lib/repositories/asset.repository.dart new file mode 100644 index 0000000000..79af8b4921 --- /dev/null +++ b/mobile/lib/repositories/asset.repository.dart @@ -0,0 +1,220 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:isar/isar.dart'; + +enum AssetSort { checksum, ownerIdChecksum } + +final assetRepositoryProvider = Provider((ref) => AssetRepository(ref.watch(dbProvider))); + +class AssetRepository extends DatabaseRepository { + const AssetRepository(super.db); + + Future> getByAlbum( + Album album, { + Iterable notOwnedBy = const [], + String? ownerId, + AssetState? state, + AssetSort? sortBy, + }) { + var query = album.assets.filter(); + final isarUserIds = notOwnedBy.map(fastHash).toList(); + if (notOwnedBy.length == 1) { + query = query.not().ownerIdEqualTo(isarUserIds.first); + } else if (notOwnedBy.isNotEmpty) { + query = query.not().anyOf(isarUserIds, (q, int id) => q.ownerIdEqualTo(id)); + } + if (ownerId != null) { + query = query.ownerIdEqualTo(fastHash(ownerId)); + } + + if (state != null) { + query = switch (state) { + AssetState.local => query.remoteIdIsNull(), + AssetState.remote => query.localIdIsNull(), + AssetState.merged => query.localIdIsNotNull().remoteIdIsNotNull(), + }; + } + + final QueryBuilder sortedQuery = switch (sortBy) { + null => query.noOp(), + AssetSort.checksum => query.sortByChecksum(), + AssetSort.ownerIdChecksum => query.sortByOwnerId().thenByChecksum(), + }; + + return sortedQuery.findAll(); + } + + Future deleteByIds(List ids) => txn(() async { + await db.assets.deleteAll(ids); + await db.exifInfos.deleteAll(ids); + }); + + Future getByRemoteId(String id) => db.assets.getByRemoteId(id); + + Future> getAllByRemoteId(Iterable ids, {AssetState? state}) async { + if (ids.isEmpty) { + return []; + } + + return _getAllByRemoteIdImpl(ids, state).findAll(); + } + + QueryBuilder _getAllByRemoteIdImpl(Iterable ids, AssetState? state) { + final query = db.assets.remote(ids).filter(); + return switch (state) { + null => query.noOp(), + AssetState.local => query.remoteIdIsNull(), + AssetState.remote => query.localIdIsNull(), + AssetState.merged => query.localIdIsNotEmpty().remoteIdIsNotNull(), + }; + } + + Future> getAll({required String ownerId, AssetState? state, AssetSort? sortBy, int? limit}) { + final baseQuery = db.assets.where(); + final isarUserIds = fastHash(ownerId); + final QueryBuilder filteredQuery = switch (state) { + null => baseQuery.ownerIdEqualToAnyChecksum(isarUserIds).noOp(), + AssetState.local => baseQuery.remoteIdIsNull().filter().localIdIsNotNull().ownerIdEqualTo(isarUserIds), + AssetState.remote => baseQuery.localIdIsNull().filter().remoteIdIsNotNull().ownerIdEqualTo(isarUserIds), + AssetState.merged => + baseQuery.ownerIdEqualToAnyChecksum(isarUserIds).filter().remoteIdIsNotNull().localIdIsNotNull(), + }; + + final QueryBuilder query = switch (sortBy) { + null => filteredQuery.noOp(), + AssetSort.checksum => filteredQuery.sortByChecksum(), + AssetSort.ownerIdChecksum => filteredQuery.sortByOwnerId().thenByChecksum(), + }; + + return limit == null ? query.findAll() : query.limit(limit).findAll(); + } + + Future> updateAll(List assets) async { + await txn(() => db.assets.putAll(assets)); + return assets; + } + + Future> getMatches({ + required List assets, + required String ownerId, + AssetState? state, + int limit = 100, + }) { + final baseQuery = db.assets.where(); + final QueryBuilder query = switch (state) { + null => baseQuery.noOp(), + AssetState.local => baseQuery.remoteIdIsNull().filter().localIdIsNotNull(), + AssetState.remote => baseQuery.localIdIsNull().filter().remoteIdIsNotNull(), + AssetState.merged => baseQuery.localIdIsNotNull().filter().remoteIdIsNotNull(), + }; + return _getMatchesImpl(query, fastHash(ownerId), assets, limit); + } + + Future update(Asset asset) async { + await txn(() => asset.put(db)); + return asset; + } + + Future upsertDuplicatedAssets(Iterable duplicatedAssets) => + txn(() => db.duplicatedAssets.putAll(duplicatedAssets.map(DuplicatedAsset.new).toList())); + + Future> getAllDuplicatedAssetIds() => db.duplicatedAssets.where().idProperty().findAll(); + + Future getByOwnerIdChecksum(int ownerId, String checksum) => + db.assets.getByOwnerIdChecksum(ownerId, checksum); + + Future> getAllByOwnerIdChecksum(List ownerIds, List checksums) => + db.assets.getAllByOwnerIdChecksum(ownerIds, checksums); + + Future> getAllLocal() => db.assets.where().localIdIsNotNull().findAll(); + + Future deleteAllByRemoteId(List ids, {AssetState? state}) => + txn(() => _getAllByRemoteIdImpl(ids, state).deleteAll()); + + Future> getStackAssets(String stackId) { + return db.assets + .filter() + .isArchivedEqualTo(false) + .isTrashedEqualTo(false) + .stackIdEqualTo(stackId) + // orders primary asset first as its ID is null + .sortByStackPrimaryAssetId() + .thenByFileCreatedAtDesc() + .findAll(); + } + + Future clearTable() async { + await txn(() async { + await db.assets.clear(); + }); + } + + Stream watchAsset(int id, {bool fireImmediately = false}) { + return db.assets.watchObject(id, fireImmediately: fireImmediately); + } + + Future> getTrashAssets(String userId) { + return db.assets + .where() + .remoteIdIsNotNull() + .filter() + .ownerIdEqualTo(fastHash(userId)) + .isTrashedEqualTo(true) + .findAll(); + } + + Future> getRecentlyTakenAssets(String userId) { + return db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .sortByFileCreatedAtDesc() + .findAll(); + } + + Future> getMotionAssets(String userId) { + return db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .livePhotoVideoIdIsNotNull() + .findAll(); + } +} + +Future> _getMatchesImpl( + QueryBuilder query, + int ownerId, + List assets, + int limit, +) => query + .ownerIdEqualTo(ownerId) + .anyOf( + assets, + (q, Asset a) => q + .fileNameEqualTo(a.fileName) + .and() + .durationInSecondsEqualTo(a.durationInSeconds) + .and() + .fileCreatedAtBetween( + a.fileCreatedAt.subtract(const Duration(hours: 12)), + a.fileCreatedAt.add(const Duration(hours: 12)), + ) + .and() + .not() + .checksumEqualTo(a.checksum), + ) + .sortByFileName() + .thenByFileCreatedAt() + .thenByFileModifiedAt() + .limit(limit) + .findAll(); diff --git a/mobile/lib/repositories/asset_api.repository.dart b/mobile/lib/repositories/asset_api.repository.dart index 9b92b491bf..011b1edc94 100644 --- a/mobile/lib/repositories/asset_api.repository.dart +++ b/mobile/lib/repositories/asset_api.repository.dart @@ -1,18 +1,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart' hide AssetEditAction; +import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/stack.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; -import 'package:immich_mobile/utils/option.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; -import 'package:openapi/api.dart' as api show AssetVisibility; -import 'package:openapi/api.dart' hide AssetVisibility; +import 'package:openapi/api.dart'; final assetApiRepositoryProvider = Provider( (ref) => AssetApiRepository( ref.watch(apiServiceProvider).assetsApi, + ref.watch(apiServiceProvider).searchApi, ref.watch(apiServiceProvider).stacksApi, ref.watch(apiServiceProvider).trashApi, ), @@ -20,32 +19,55 @@ final assetApiRepositoryProvider = Provider( class AssetApiRepository extends ApiRepository { final AssetsApi _api; + final SearchApi _searchApi; final StacksApi _stacksApi; final TrashApi _trashApi; - AssetApiRepository(this._api, this._stacksApi, this._trashApi); + AssetApiRepository(this._api, this._searchApi, this._stacksApi, this._trashApi); + + Future update(String id, {String? description}) async { + final response = await checkNull(_api.updateAsset(id, UpdateAssetDto(description: description))); + return Asset.remote(response); + } + + Future> search({List personIds = const []}) async { + // TODO this always fetches all assets, change API and usage to actually do pagination + final List result = []; + bool hasNext = true; + int currentPage = 1; + while (hasNext) { + final response = await checkNull( + _searchApi.searchAssets(MetadataSearchDto(personIds: personIds, page: currentPage, size: 1000)), + ); + result.addAll(response.assets.items.map(Asset.remote)); + hasNext = response.assets.nextPage != null; + currentPage++; + } + return result; + } Future delete(List ids, bool force) async { - return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: Optional.present(force))); + return _api.deleteAssets(AssetBulkDeleteDto(ids: ids, force: force)); } Future restoreTrash(List ids) async { await _trashApi.restoreAssets(BulkIdsDto(ids: ids)); } - Future emptyTrash() async { - final response = await _trashApi.emptyTrash(); - return response?.count ?? 0; + Future updateVisibility(List ids, AssetVisibilityEnum visibility) async { + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: _mapVisibility(visibility))); } - Future restoreAllTrash() async { - final response = await _trashApi.restoreTrash(); - return response?.count ?? 0; + Future updateFavorite(List ids, bool isFavorite) async { + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, isFavorite: isFavorite)); } - // TODO(shenlong): remove after action migration - Future updateVisibility(List ids, AssetVisibility visibility) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, visibility: Optional.present(_mapVisibility(visibility)))); + Future updateLocation(List ids, LatLng location) async { + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, latitude: location.latitude, longitude: location.longitude)); + } + + Future updateDateTime(List ids, DateTime dateTime) async { + return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: dateTime.toIso8601String())); } Future stack(List ids) async { @@ -62,67 +84,26 @@ class AssetApiRepository extends ApiRepository { return _api.downloadAssetWithHttpInfo(id, edited: edited); } - api.AssetVisibility _mapVisibility(AssetVisibility visibility) => switch (visibility) { - AssetVisibility.timeline => api.AssetVisibility.timeline, - AssetVisibility.hidden => api.AssetVisibility.hidden, - AssetVisibility.locked => api.AssetVisibility.locked, - AssetVisibility.archive => api.AssetVisibility.archive, + _mapVisibility(AssetVisibilityEnum visibility) => switch (visibility) { + AssetVisibilityEnum.timeline => AssetVisibility.timeline, + AssetVisibilityEnum.hidden => AssetVisibility.hidden, + AssetVisibilityEnum.locked => AssetVisibility.locked, + AssetVisibilityEnum.archive => AssetVisibility.archive, }; Future getAssetMIMEType(String assetId) async { final response = await checkNull(_api.getAssetInfo(assetId)); // we need to get the MIME of the thumbnail once that gets added to the API - return response.originalMimeType.orElse(null); + return response.originalMimeType; } Future updateDescription(String assetId, String description) { - return _api.updateAsset(assetId, UpdateAssetDto(description: Optional.present(description))); + return _api.updateAsset(assetId, UpdateAssetDto(description: description)); } - Future updateRating(String assetId, int? rating) { - return _api.updateAsset(assetId, UpdateAssetDto(rating: Optional.present(rating))); - } - - Future editAsset(String assetId, List edits) { - return _api.editAsset(assetId, AssetEditsCreateDto(edits: edits.map((e) => e.toApi()).toList())); - } - - Future removeEdits(String assetId) async { - return _api.removeAssetEdits(assetId); - } - - Future update( - List remoteIds, { - Option isFavorite = const .none(), - Option visibility = const .none(), - Option dateTimeOriginal = const .none(), - Option location = const .none(), - }) { - return _api.updateAssets( - AssetBulkUpdateDto( - ids: remoteIds, - isFavorite: isFavorite.toOptional(), - visibility: visibility.map(_mapVisibility).toOptional(), - dateTimeOriginal: dateTimeOriginal.toOptional(), - latitude: location.map((loc) => loc.latitude).toOptional(), - longitude: location.map((loc) => loc.longitude).toOptional(), - ), - ); - } - - Future updateLocation(List ids, LatLng location) async { - return _api.updateAssets( - AssetBulkUpdateDto( - ids: ids, - latitude: Optional.present(location.latitude), - longitude: Optional.present(location.longitude), - ), - ); - } - - Future updateDateTime(List ids, String dateTime) async { - return _api.updateAssets(AssetBulkUpdateDto(ids: ids, dateTimeOriginal: Optional.present(dateTime))); + Future updateRating(String assetId, int rating) { + return _api.updateAsset(assetId, UpdateAssetDto(rating: rating)); } } @@ -131,22 +112,3 @@ extension on StackResponseDto { return StackResponse(id: id, primaryAssetId: primaryAssetId, assetIds: assets.map((asset) => asset.id).toList()); } } - -extension on AssetEdit { - AssetEditActionItemDto toApi() { - return switch (this) { - CropEdit(:final parameters) => AssetEditActionItemDto( - action: AssetEditAction.crop, - parameters: parameters.toJson(), - ), - RotateEdit(:final parameters) => AssetEditActionItemDto( - action: AssetEditAction.rotate, - parameters: parameters.toJson(), - ), - MirrorEdit(:final parameters) => AssetEditActionItemDto( - action: AssetEditAction.mirror, - parameters: parameters.toJson(), - ), - }; - } -} diff --git a/mobile/lib/repositories/asset_media.repository.dart b/mobile/lib/repositories/asset_media.repository.dart index 320a123ddc..fecfe6df4d 100644 --- a/mobile/lib/repositories/asset_media.repository.dart +++ b/mobile/lib/repositories/asset_media.repository.dart @@ -1,83 +1,88 @@ import 'dart:async'; import 'dart:io'; -import 'package:background_downloader/background_downloader.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart' as asset_entity; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/extensions/response_extensions.dart'; +import 'package:immich_mobile/repositories/asset_api.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; import 'package:logging/logging.dart'; -import 'package:openapi/api.dart'; -import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:share_plus/share_plus.dart'; -typedef _ShareFile = ({File file, bool cleanup, String displayName}); - -final assetMediaRepositoryProvider = Provider( - (ref) => AssetMediaRepository(ref.watch(nativeSyncApiProvider), ref.watch(storageRepositoryProvider)), -); +final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.watch(assetApiRepositoryProvider))); class AssetMediaRepository { - final NativeSyncApi _nativeSyncApi; - final StorageRepository _storageRepository; + final AssetApiRepository _assetApiRepository; static final Logger _log = Logger("AssetMediaRepository"); - const AssetMediaRepository(this._nativeSyncApi, this._storageRepository); + const AssetMediaRepository(this._assetApiRepository); Future _androidSupportsTrash() async { if (Platform.isAndroid) { - final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - final int sdkVersion = androidInfo.version.sdkInt; + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + int sdkVersion = androidInfo.version.sdkInt; return sdkVersion >= 31; } return false; } - Future> deleteAll(List ids, {bool trash = true}) async { - if (trash && CurrentPlatform.isAndroid && await _androidSupportsTrash()) { - return PhotoManager.editor.android.moveToTrash( - ids.map((e) => AssetEntity(id: e, width: 1, height: 1, typeInt: 0)).toList(), - ); + Future> deleteAll(List ids) async { + if (CurrentPlatform.isAndroid) { + if (await _androidSupportsTrash()) { + return PhotoManager.editor.android.moveToTrash( + ids.map((e) => AssetEntity(id: e, width: 1, height: 1, typeInt: 0)).toList(), + ); + } else { + return PhotoManager.editor.deleteWithIds(ids); + } } return PhotoManager.editor.deleteWithIds(ids); } - Future _restoreFromTrashById(String mediaId, int type) async { - try { - return await _nativeSyncApi.restoreFromTrashById(mediaId, type); - } catch (e, s) { - _log.warning('Error restore file from trash by Id', e, s); - return false; - } - } - - Future> restoreAssetsFromTrash(Iterable assets) async { - final restoredIds = []; - for (final asset in assets) { - _log.info("Restoring from trash, localId: ${asset.id}, checksum: ${asset.checksum}"); - final result = await _restoreFromTrashById(asset.id, asset.type.index); - if (result) { - restoredIds.add(asset.id); - } - } - return restoredIds; - } - - Future get(String id) async { + Future get(String id) async { final entity = await AssetEntity.fromId(id); - return entity; + return toAsset(entity); + } + + static asset_entity.Asset? toAsset(AssetEntity? local) { + if (local == null) return null; + + final asset_entity.Asset asset = asset_entity.Asset( + checksum: "", + localId: local.id, + ownerId: fastHash(Store.get(StoreKey.currentUser).id), + fileCreatedAt: local.createDateTime, + fileModifiedAt: local.modifiedDateTime, + updatedAt: local.modifiedDateTime, + durationInSeconds: local.duration, + type: asset_entity.AssetType.values[local.typeInt], + fileName: local.title!, + width: local.width, + height: local.height, + isFavorite: local.isFavorite, + ); + + if (asset.fileCreatedAt.year == 1970) { + asset.fileCreatedAt = asset.fileModifiedAt; + } + + if (local.latitude != null) { + asset.exifInfo = ExifInfo(latitude: local.latitude, longitude: local.longitude); + } + + asset.local = local; + return asset; } Future getOriginalFilename(String id) async { @@ -111,208 +116,50 @@ class AssetMediaRepository { ); } - String _sanitizeFilename(String filename) { - return filename.replaceAll(RegExp(r'[\\/]'), '_'); - } - - String _getPreviewFilename(BaseAsset asset) { - final sanitizedFilename = _sanitizeFilename(asset.name); - final baseName = p.basenameWithoutExtension(sanitizedFilename); - final fallbackName = asset.remoteId ?? asset.localId ?? 'asset'; - return '${baseName.isEmpty ? fallbackName : baseName}-preview.jpg'; - } - - bool _isCancelled(Completer? cancelCompleter) => cancelCompleter?.isCompleted ?? false; - - Future<_ShareFile?> _getLocalOriginalShareFile(BaseAsset asset, String localId) async { - final file = await _storageRepository.getFileForAsset(localId); - if (file == null) { - _log.warning("Local original file not found for sharing: $asset"); - return null; - } - - return (file: file, cleanup: CurrentPlatform.isIOS, displayName: _sanitizeFilename(asset.name)); - } - - Future<_ShareFile?> _downloadRemoteShareFile({ - required String taskId, - required String url, - required String displayName, - Completer? cancelCompleter, - required void Function(double progress) onProgress, - }) async { - final task = DownloadTask( - taskId: taskId, - url: url, - headers: ApiService.getRequestHeaders(), - filename: '$taskId-$displayName', - baseDirectory: BaseDirectory.temporary, - group: kShareDownloadGroup, - updates: Updates.statusAndProgress, - ); - final downloader = FileDownloader(); - final statusUpdate = await downloader.download( - task, - onProgress: (value) { - if (_isCancelled(cancelCompleter)) { - unawaited(downloader.cancelTaskWithId(taskId)); - return; - } - onProgress(value); - }, - ); - - if (_isCancelled(cancelCompleter)) { - return null; - } - - if (statusUpdate.status == TaskStatus.complete) { - return (file: File(await task.filePath()), cleanup: true, displayName: displayName); - } - - _log.severe("Download for $displayName failed with status ${statusUpdate.status}", statusUpdate.exception); - return null; - } - - Future<_ShareFile?> _getRemoteOriginalShareFile( - BaseAsset asset, - String remoteId, { - Completer? cancelCompleter, - required void Function(double progress) onProgress, - }) { - return _downloadRemoteShareFile( - taskId: 'share-original-$remoteId-${DateTime.now().microsecondsSinceEpoch}', - url: getOriginalUrlForRemoteId(remoteId, edited: asset.isEdited), - displayName: _sanitizeFilename(asset.name), - cancelCompleter: cancelCompleter, - onProgress: onProgress, - ); - } - - Future<_ShareFile?> _getRemotePreviewShareFile( - BaseAsset asset, - String remoteId, { - Completer? cancelCompleter, - required void Function(double progress) onProgress, - }) { - return _downloadRemoteShareFile( - taskId: 'share-preview-$remoteId-${DateTime.now().microsecondsSinceEpoch}', - url: getThumbnailUrlForRemoteId(remoteId, type: AssetMediaSize.preview, edited: asset.isEdited), - displayName: _getPreviewFilename(asset), - cancelCompleter: cancelCompleter, - onProgress: onProgress, - ); - } - - Future<_ShareFile?> _getOriginalShareFile( - BaseAsset asset, { - Completer? cancelCompleter, - required void Function(double progress) onProgress, - }) { - final localId = asset.localId; - if (localId != null && !asset.isEdited) { - return _getLocalOriginalShareFile(asset, localId); - } - - final remoteId = asset.remoteId; - if (remoteId == null) { - _log.warning("Asset has no remote ID for sharing: $asset"); - return Future.value(null); - } - - return _getRemoteOriginalShareFile(asset, remoteId, cancelCompleter: cancelCompleter, onProgress: onProgress); - } - - Future<_ShareFile?> _getPreviewShareFile( - BaseAsset asset, { - Completer? cancelCompleter, - required void Function(double progress) onProgress, - }) async { - final remoteId = asset.remoteId; - if (remoteId != null) { - final remotePreview = await _getRemotePreviewShareFile( - asset, - remoteId, - cancelCompleter: cancelCompleter, - onProgress: onProgress, - ); - if (remotePreview != null || asset.isEdited) { - return remotePreview; - } - } - - final localId = asset.localId; - if (localId != null) { - return _getLocalOriginalShareFile(asset, localId); - } - - _log.warning("Asset has no local or remote ID for preview sharing: $asset"); - return null; - } - - Future shareAssets( - List assets, - BuildContext context, { - ShareAssetType fileType = ShareAssetType.original, - Completer? cancelCompleter, - void Function(double progress)? onAssetDownloadProgress, - }) async { + // TODO: make this more efficient + Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) async { final downloadedXFiles = []; final tempFiles = []; - final totalAssets = assets.length; - var processedAssets = 0; - void updateProgress([double currentAssetProgress = 0.0]) { - if (totalAssets <= 0) { - onAssetDownloadProgress?.call(1.0); - return; - } - - final normalizedAssetProgress = currentAssetProgress.clamp(0.0, 1.0); - final overallProgress = ((processedAssets + normalizedAssetProgress) / totalAssets).clamp(0.0, 1.0); - onAssetDownloadProgress?.call(overallProgress); - } - - updateProgress(); - - for (final asset in assets) { - if (_isCancelled(cancelCompleter)) { + for (var asset in assets) { + if (cancelCompleter != null && cancelCompleter.isCompleted) { + // if cancelled, delete any temp files created so far await _cleanupTempFiles(tempFiles); return 0; } - final effectiveFileType = asset.isVideo ? ShareAssetType.original : fileType; + final localId = (asset is LocalAsset) + ? asset.id + : asset is RemoteAsset + ? asset.localId + : null; + if (localId != null && !asset.isEdited) { + File? f = await AssetEntity(id: localId, width: 1, height: 1, typeInt: 0).originFile; + downloadedXFiles.add(XFile(f!.path)); + if (CurrentPlatform.isIOS) { + tempFiles.add(f); + } + } else { + final remoteId = (asset is RemoteAsset) ? asset.id : asset.remoteId; + if (remoteId == null) { + _log.warning("Asset has no remote ID for sharing: $asset"); + continue; + } - final shareFile = switch (effectiveFileType) { - ShareAssetType.original => await _getOriginalShareFile( - asset, - cancelCompleter: cancelCompleter, - onProgress: updateProgress, - ), - ShareAssetType.preview => await _getPreviewShareFile( - asset, - cancelCompleter: cancelCompleter, - onProgress: updateProgress, - ), - }; + final tempDir = await getTemporaryDirectory(); + final name = asset.name; + final tempFile = await File('${tempDir.path}/$name').create(); + final res = await _assetApiRepository.downloadAsset(remoteId, edited: true); - if (_isCancelled(cancelCompleter)) { - await _cleanupTempFiles(tempFiles); - return 0; + if (res.statusCode != 200) { + _log.severe("Download for $name failed", res.toLoggerString()); + continue; + } + + await tempFile.writeAsBytes(res.bodyBytes); + downloadedXFiles.add(XFile(tempFile.path)); + tempFiles.add(tempFile); } - - if (shareFile == null) { - processedAssets++; - updateProgress(); - continue; - } - - downloadedXFiles.add(XFile(shareFile.file.path, name: shareFile.displayName)); - if (shareFile.cleanup) { - tempFiles.add(shareFile.file); - } - processedAssets++; - updateProgress(); } if (downloadedXFiles.isEmpty) { @@ -320,7 +167,7 @@ class AssetMediaRepository { return 0; } - if (_isCancelled(cancelCompleter) || !context.mounted) { + if (cancelCompleter != null && cancelCompleter.isCompleted) { await _cleanupTempFiles(tempFiles); return 0; } diff --git a/mobile/lib/repositories/auth.repository.dart b/mobile/lib/repositories/auth.repository.dart index dd9aed3a03..a8544ef6c0 100644 --- a/mobile/lib/repositories/auth.repository.dart +++ b/mobile/lib/repositories/auth.repository.dart @@ -1,40 +1,65 @@ +import 'dart:convert'; + import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +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/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; final authRepositoryProvider = Provider( - (ref) => AuthRepository(ref.watch(driftProvider), ref.watch(settingsProvider)), + (ref) => AuthRepository(ref.watch(dbProvider), ref.watch(driftProvider)), ); -class AuthRepository { +class AuthRepository extends DatabaseRepository { final Drift _drift; - final SettingsRepository _settings; - const AuthRepository(this._drift, this._settings); + const AuthRepository(super.db, this._drift); Future clearLocalData() async { await SyncStreamRepository(_drift).reset(); + + return db.writeTxn(() { + return Future.wait([ + db.assets.clear(), + db.exifInfos.clear(), + db.albums.clear(), + db.eTags.clear(), + db.users.clear(), + ]); + }); } bool getEndpointSwitchingFeature() { - return _settings.appConfig.network.autoEndpointSwitching; + return Store.tryGet(StoreKey.autoEndpointSwitching) ?? false; } String? getPreferredWifiName() { - return _settings.appConfig.network.preferredWifiName; + return Store.tryGet(StoreKey.preferredWifiName); } String? getLocalEndpoint() { - return _settings.appConfig.network.localEndpoint; + return Store.tryGet(StoreKey.localEndpoint); } List getExternalEndpointList() { - return _settings.appConfig.network.externalEndpointList - .map((url) => AuxilaryEndpoint(url: url, status: .valid)) - .toList(); + final jsonString = Store.tryGet(StoreKey.externalEndpointList); + + if (jsonString == null) { + return []; + } + + final List jsonList = jsonDecode(jsonString); + final endpointList = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); + + return endpointList; } } diff --git a/mobile/lib/repositories/auth_api.repository.dart b/mobile/lib/repositories/auth_api.repository.dart index 447826b9aa..4b0880ddcf 100644 --- a/mobile/lib/repositories/auth_api.repository.dart +++ b/mobile/lib/repositories/auth_api.repository.dart @@ -13,7 +13,7 @@ class AuthApiRepository extends ApiRepository { AuthApiRepository(this._apiService); Future changePassword(String newPassword) async { - await _apiService.usersApi.updateMyUser(UserUpdateMeDto(password: Optional.present(newPassword))); + await _apiService.usersApi.updateMyUser(UserUpdateMeDto(password: newPassword)); } Future login(String email, String password) async { @@ -25,14 +25,12 @@ class AuthApiRepository extends ApiRepository { } Future logout() async { - if (_apiService.apiClient.basePath.isEmpty) { - return; - } + if (_apiService.apiClient.basePath.isEmpty) return; await _apiService.authenticationApi.logout().timeout(const Duration(seconds: 7)); } - LoginResponse _mapLoginReponse(LoginResponseDto dto) { + _mapLoginReponse(LoginResponseDto dto) { return LoginResponse( accessToken: dto.accessToken, isAdmin: dto.isAdmin, @@ -46,7 +44,7 @@ class AuthApiRepository extends ApiRepository { Future unlockPinCode(String pinCode) async { try { - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(pinCode))); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: pinCode)); return true; } catch (_) { return false; diff --git a/mobile/lib/repositories/backup.repository.dart b/mobile/lib/repositories/backup.repository.dart new file mode 100644 index 0000000000..6cee6a4427 --- /dev/null +++ b/mobile/lib/repositories/backup.repository.dart @@ -0,0 +1,32 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:isar/isar.dart'; + +enum BackupAlbumSort { id } + +final backupAlbumRepositoryProvider = Provider((ref) => BackupAlbumRepository(ref.watch(dbProvider))); + +class BackupAlbumRepository extends DatabaseRepository { + const BackupAlbumRepository(super.db); + + Future> getAll({BackupAlbumSort? sort}) { + final baseQuery = db.backupAlbums.where(); + final QueryBuilder query = switch (sort) { + null => baseQuery.noOp(), + BackupAlbumSort.id => baseQuery.sortById(), + }; + return query.findAll(); + } + + Future> getIdsBySelection(BackupSelection backup) => + db.backupAlbums.filter().selectionEqualTo(backup).idProperty().findAll(); + + Future> getAllBySelection(BackupSelection backup) => + db.backupAlbums.filter().selectionEqualTo(backup).findAll(); + + Future deleteAll(List ids) => txn(() => db.backupAlbums.deleteAll(ids)); + + Future updateAll(List backupAlbums) => txn(() => db.backupAlbums.putAll(backupAlbums)); +} diff --git a/mobile/lib/repositories/biometric.repository.dart b/mobile/lib/repositories/biometric.repository.dart index 7da4bfe757..8bd5a85542 100644 --- a/mobile/lib/repositories/biometric.repository.dart +++ b/mobile/lib/repositories/biometric.repository.dart @@ -1,5 +1,5 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/auth/biometric_status.model.dart'; import 'package:local_auth/local_auth.dart'; @@ -19,6 +19,6 @@ class BiometricRepository { } Future authenticate(String? message) async { - return _localAuth.authenticate(localizedReason: message ?? StaticTranslations.instance.please_auth_to_access); + return _localAuth.authenticate(localizedReason: message ?? 'please_auth_to_access'.tr()); } } diff --git a/mobile/lib/repositories/database.repository.dart b/mobile/lib/repositories/database.repository.dart new file mode 100644 index 0000000000..71c15e1c40 --- /dev/null +++ b/mobile/lib/repositories/database.repository.dart @@ -0,0 +1,25 @@ +import 'dart:async'; +import 'package:immich_mobile/interfaces/database.interface.dart'; +import 'package:isar/isar.dart'; + +/// copied from Isar; needed to check if an async transaction is already active +const Symbol _zoneTxn = #zoneTxn; + +abstract class DatabaseRepository implements IDatabaseRepository { + final Isar db; + const DatabaseRepository(this.db); + + bool get inTxn => Zone.current[_zoneTxn] != null; + + Future txn(Future Function() callback) => inTxn ? callback() : transaction(callback); + + @override + Future transaction(Future Function() callback) => db.writeTxn(callback); +} + +extension Asd on QueryBuilder { + QueryBuilder noOp() { + // ignore: invalid_use_of_protected_member + return QueryBuilder.apply(this, (query) => query); + } +} diff --git a/mobile/lib/repositories/download.repository.dart b/mobile/lib/repositories/download.repository.dart index 8504487860..c578746a4c 100644 --- a/mobile/lib/repositories/download.repository.dart +++ b/mobile/lib/repositories/download.repository.dart @@ -10,7 +10,6 @@ import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; -// ignore: dispose-provided-instances final downloadRepositoryProvider = Provider((ref) => DownloadRepository()); class DownloadRepository { @@ -32,10 +31,6 @@ class DownloadRepository { void Function(TaskProgressUpdate)? onTaskProgress; - // #29900: `taskStatusCallback` is called before the DB has been updated, causing a race between the two Live Photo tasks - // This callback instead listens directly to DB updates - void Function(TaskRecord)? onLivePhotoRecordComplete; - DownloadRepository() { _downloader.registerCallbacks( group: kDownloadGroupImage, @@ -54,10 +49,6 @@ class DownloadRepository { taskStatusCallback: (update) => onLivePhotoDownloadStatus?.call(update), taskProgressCallback: (update) => onTaskProgress?.call(update), ); - - _downloader.database.updates - .where((record) => record.group == kDownloadGroupLivePhoto && record.status == TaskStatus.complete) - .listen((record) => onLivePhotoRecordComplete?.call(record)); } Future> downloadAll(List tasks) { diff --git a/mobile/lib/repositories/drift_album_api_repository.dart b/mobile/lib/repositories/drift_album_api_repository.dart index 8c21af6599..557050323a 100644 --- a/mobile/lib/repositories/drift_album_api_repository.dart +++ b/mobile/lib/repositories/drift_album_api_repository.dart @@ -1,10 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; // ignore: import_rule_openapi -import 'package:openapi/api.dart' hide AlbumUserRole; +import 'package:openapi/api.dart'; final driftAlbumApiRepositoryProvider = Provider( (ref) => DriftAlbumApiRepository(ref.watch(apiServiceProvider).albumsApi), @@ -15,31 +14,17 @@ class DriftAlbumApiRepository extends ApiRepository { DriftAlbumApiRepository(this._api); - Future createDriftAlbum( - String name, - UserDto owner, { - required Iterable assetIds, - String? description, - }) async { + Future createDriftAlbum(String name, {required Iterable assetIds, String? description}) async { final responseDto = await checkNull( - _api.createAlbum( - CreateAlbumDto( - albumName: name, - description: description == null - ? const Optional.absent() - : Optional.present(description.isEmpty ? null : description), - assetIds: Optional.present(assetIds.toList()), - ), - ), + _api.createAlbum(CreateAlbumDto(albumName: name, description: description, assetIds: assetIds.toList())), ); - return responseDto.toRemoteAlbum(owner); + return responseDto.toRemoteAlbum(); } Future<({List removed, List failed})> removeAssets(String albumId, Iterable assetIds) async { final response = await checkNull(_api.removeAssetFromAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); - final List removed = []; - final List failed = []; + final List removed = [], failed = []; for (final dto in response) { if (dto.success) { removed.add(dto.id); @@ -50,20 +35,13 @@ class DriftAlbumApiRepository extends ApiRepository { return (removed: removed, failed: failed); } - Future<({List added, List failed})> addAssets( - String albumId, - Iterable assetIds, { - Future? abortTrigger, - }) async { - final response = await checkNull( - _api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()), abortTrigger: abortTrigger), - ); - final List added = []; - final List failed = []; + Future<({List added, List failed})> addAssets(String albumId, Iterable assetIds) async { + final response = await checkNull(_api.addAssetsToAlbum(albumId, BulkIdsDto(ids: assetIds.toList()))); + final List added = [], failed = []; for (final dto in response) { if (dto.success) { added.add(dto.id); - } else if (dto.error.orElse(null) != BulkIdErrorReason.duplicate) { + } else { failed.add(dto.id); } } @@ -72,8 +50,7 @@ class DriftAlbumApiRepository extends ApiRepository { } Future updateAlbum( - String albumId, - UserDto owner, { + String albumId, { String? name, String? description, String? thumbnailAssetId, @@ -89,29 +66,26 @@ class DriftAlbumApiRepository extends ApiRepository { _api.updateAlbumInfo( albumId, UpdateAlbumDto( - albumName: name == null ? const Optional.absent() : Optional.present(name), - description: description == null - ? const Optional.absent() - : Optional.present(description.isEmpty ? null : description), - albumThumbnailAssetId: thumbnailAssetId == null - ? const Optional.absent() - : Optional.present(thumbnailAssetId), - isActivityEnabled: isActivityEnabled == null ? const Optional.absent() : Optional.present(isActivityEnabled), - order: apiOrder == null ? const Optional.absent() : Optional.present(apiOrder), + albumName: name, + description: description, + albumThumbnailAssetId: thumbnailAssetId, + isActivityEnabled: isActivityEnabled, + order: apiOrder, ), ), ); - return responseDto.toRemoteAlbum(owner); + return responseDto.toRemoteAlbum(); } Future deleteAlbum(String albumId) { return _api.deleteAlbum(albumId); } - Future addUsers(String albumId, Iterable userIds) async { + Future addUsers(String albumId, Iterable userIds) async { final albumUsers = userIds.map((userId) => AlbumUserAddDto(userId: userId)).toList(); - await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers))); + final response = await checkNull(_api.addUsersToAlbum(albumId, AddUsersDto(albumUsers: albumUsers))); + return response.toRemoteAlbum(); } Future removeUser(String albumId, {required String userId}) async { @@ -119,27 +93,25 @@ class DriftAlbumApiRepository extends ApiRepository { } Future setActivityStatus(String albumId, bool isEnabled) async { - final response = await checkNull( - _api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: Optional.present(isEnabled))), - ); + final response = await checkNull(_api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: isEnabled))); return response.isActivityEnabled; } } extension on AlbumResponseDto { - RemoteAlbum toRemoteAlbum(final UserDto user) { + RemoteAlbum toRemoteAlbum() { return RemoteAlbum( id: id, name: albumName, - ownerId: user.id, - ownerName: user.name, + ownerId: owner.id, description: description, createdAt: createdAt, updatedAt: updatedAt, thumbnailAssetId: albumThumbnailAssetId, isActivityEnabled: isActivityEnabled, - order: order.orElse(null) == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, + order: order == AssetOrder.asc ? AlbumAssetOrder.asc : AlbumAssetOrder.desc, assetCount: assetCount, + ownerName: owner.name, isShared: albumUsers.length > 2, ); } diff --git a/mobile/lib/repositories/etag.repository.dart b/mobile/lib/repositories/etag.repository.dart new file mode 100644 index 0000000000..768d95b95c --- /dev/null +++ b/mobile/lib/repositories/etag.repository.dart @@ -0,0 +1,27 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:isar/isar.dart'; + +final etagRepositoryProvider = Provider((ref) => ETagRepository(ref.watch(dbProvider))); + +class ETagRepository extends DatabaseRepository { + const ETagRepository(super.db); + + Future> getAllIds() => db.eTags.where().idProperty().findAll(); + + Future get(String id) => db.eTags.getById(id); + + Future upsertAll(List etags) => txn(() => db.eTags.putAll(etags)); + + Future deleteByIds(List ids) => txn(() => db.eTags.deleteAllById(ids)); + + Future getById(String id) => db.eTags.getById(id); + + Future clearTable() async { + await txn(() async { + await db.eTags.clear(); + }); + } +} diff --git a/mobile/lib/repositories/file_media.repository.dart b/mobile/lib/repositories/file_media.repository.dart index c54813a757..f5cdb6d5c0 100644 --- a/mobile/lib/repositories/file_media.repository.dart +++ b/mobile/lib/repositories/file_media.repository.dart @@ -3,12 +3,18 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart' hide AssetType; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:photo_manager/photo_manager.dart' hide AssetType; final fileMediaRepositoryProvider = Provider((ref) => const FileMediaRepository()); class FileMediaRepository { const FileMediaRepository(); + Future saveImage(Uint8List data, {required String title, String? relativePath}) async { + final entity = await PhotoManager.editor.saveImage(data, filename: title, title: title, relativePath: relativePath); + return AssetMediaRepository.toAsset(entity); + } Future saveLocalAsset(Uint8List data, {required String title, String? relativePath}) async { final entity = await PhotoManager.editor.saveImage(data, filename: title, title: title, relativePath: relativePath); @@ -24,18 +30,24 @@ class FileMediaRepository { ); } - Future saveImageWithFile(String filePath, {String? title, String? relativePath}) async { + Future saveImageWithFile(String filePath, {String? title, String? relativePath}) async { final entity = await PhotoManager.editor.saveImageWithPath(filePath, title: title, relativePath: relativePath); - return entity; + return AssetMediaRepository.toAsset(entity); } - Future saveLivePhoto({required File image, required File video, required String title}) async { + Future saveLivePhoto({required File image, required File video, required String title}) async { final entity = await PhotoManager.editor.darwin.saveLivePhoto(imageFile: image, videoFile: video, title: title); - return entity; + return AssetMediaRepository.toAsset(entity); } - Future saveVideo(File file, {required String title, String? relativePath}) async { + Future saveVideo(File file, {required String title, String? relativePath}) async { final entity = await PhotoManager.editor.saveVideo(file, title: title, relativePath: relativePath); - return entity; + return AssetMediaRepository.toAsset(entity); } + + Future clearFileCache() => PhotoManager.clearFileCache(); + + Future enableBackgroundAccess() => PhotoManager.setIgnorePermissionCheck(true); + + Future requestExtendedPermissions() => PhotoManager.requestPermissionExtend(); } diff --git a/mobile/lib/repositories/folder_api.repository.dart b/mobile/lib/repositories/folder_api.repository.dart index 8c9959389c..d20ca8e0a9 100644 --- a/mobile/lib/repositories/folder_api.repository.dart +++ b/mobile/lib/repositories/folder_api.repository.dart @@ -1,6 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/repositories/api.repository.dart'; import 'package:logging/logging.dart'; @@ -24,10 +23,10 @@ class FolderApiRepository extends ApiRepository { } } - Future> getAssetsForPath(String? path) async { + Future> getAssetsForPath(String? path) async { try { final list = await _api.getAssetsByOriginalPath(path ?? '/'); - return list != null ? list.map((e) => e.toDtoWithExif()).toList() : []; + return list != null ? list.map(Asset.remote).toList() : []; } catch (e, stack) { _log.severe("Failed to fetch Assets by original path", e, stack); return []; diff --git a/mobile/lib/repositories/gcast.repository.dart b/mobile/lib/repositories/gcast.repository.dart index b8ffe79b04..db3e0f45d0 100644 --- a/mobile/lib/repositories/gcast.repository.dart +++ b/mobile/lib/repositories/gcast.repository.dart @@ -1,7 +1,7 @@ import 'package:cast/device.dart'; -import 'package:cast/discovery_service.dart'; import 'package:cast/session.dart'; import 'package:cast/session_manager.dart'; +import 'package:cast/discovery_service.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; final gCastRepositoryProvider = Provider((_) { diff --git a/mobile/lib/repositories/local_files_manager.repository.dart b/mobile/lib/repositories/local_files_manager.repository.dart new file mode 100644 index 0000000000..6a6200b2e1 --- /dev/null +++ b/mobile/lib/repositories/local_files_manager.repository.dart @@ -0,0 +1,51 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/services/local_files_manager.service.dart'; +import 'package:logging/logging.dart'; + +final localFilesManagerRepositoryProvider = Provider( + (ref) => LocalFilesManagerRepository(ref.watch(localFileManagerServiceProvider)), +); + +class LocalFilesManagerRepository { + LocalFilesManagerRepository(this._service); + + final Logger _logger = Logger('LocalFilesManagerRepo'); + final LocalFilesManagerService _service; + + Future moveToTrash(List mediaUrls) async { + return await _service.moveToTrash(mediaUrls); + } + + Future restoreFromTrash(String fileName, int type) async { + return await _service.restoreFromTrash(fileName, type); + } + + Future requestManageMediaPermission() async { + return await _service.requestManageMediaPermission(); + } + + Future hasManageMediaPermission() async { + return await _service.hasManageMediaPermission(); + } + + Future manageMediaPermission() async { + return await _service.manageMediaPermission(); + } + + Future> restoreAssetsFromTrash(Iterable assets) async { + final restoredIds = []; + for (final asset in assets) { + _logger.info("Restoring from trash, localId: ${asset.id}, remoteId: ${asset.checksum}"); + try { + final result = await _service.restoreFromTrashById(asset.id, asset.type.index); + if (result) { + restoredIds.add(asset.id); + } + } catch (e) { + _logger.warning("Restoring failure: $e"); + } + } + return restoredIds; + } +} diff --git a/mobile/lib/repositories/partner.repository.dart b/mobile/lib/repositories/partner.repository.dart new file mode 100644 index 0000000000..7f5ce62e0c --- /dev/null +++ b/mobile/lib/repositories/partner.repository.dart @@ -0,0 +1,34 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:isar/isar.dart'; + +final partnerRepositoryProvider = Provider((ref) => PartnerRepository(ref.watch(dbProvider))); + +class PartnerRepository extends DatabaseRepository { + const PartnerRepository(super.db); + + Future> getSharedBy() async { + return (await db.users.filter().isPartnerSharedByEqualTo(true).sortById().findAll()).map((u) => u.toDto()).toList(); + } + + Future> getSharedWith() async { + return (await db.users.filter().isPartnerSharedWithEqualTo(true).sortById().findAll()) + .map((u) => u.toDto()) + .toList(); + } + + Stream> watchSharedBy() { + return (db.users.filter().isPartnerSharedByEqualTo(true).sortById().watch()).map( + (users) => users.map((u) => u.toDto()).toList(), + ); + } + + Stream> watchSharedWith() { + return (db.users.filter().isPartnerSharedWithEqualTo(true).sortById().watch()).map( + (users) => users.map((u) => u.toDto()).toList(), + ); + } +} diff --git a/mobile/lib/repositories/partner_api.repository.dart b/mobile/lib/repositories/partner_api.repository.dart index eaca839bfd..d497da4d4c 100644 --- a/mobile/lib/repositories/partner_api.repository.dart +++ b/mobile/lib/repositories/partner_api.repository.dart @@ -16,13 +16,13 @@ class PartnerApiRepository extends ApiRepository { Future> getAll(Direction direction) async { final response = await checkNull( - _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.sharedBy : PartnerDirection.sharedWith), + _api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.by : PartnerDirection.with_), ); return response.map(UserConverter.fromPartnerDto).toList(); } - Future create(String sharedWithId) async { - final dto = await checkNull(_api.createPartner(PartnerCreateDto(sharedWithId: sharedWithId))); + Future create(String id) async { + final dto = await checkNull(_api.createPartnerDeprecated(id)); return UserConverter.fromPartnerDto(dto); } diff --git a/mobile/lib/repositories/permission.repository.dart b/mobile/lib/repositories/permission.repository.dart index 932be4a290..74230a3652 100644 --- a/mobile/lib/repositories/permission.repository.dart +++ b/mobile/lib/repositories/permission.repository.dart @@ -1,83 +1,45 @@ -import 'package:device_info_plus/device_info_plus.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/platform/permission_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:permission_handler/permission_handler.dart' as ph; +import 'package:permission_handler/permission_handler.dart'; -final permissionRepositoryProvider = Provider((ref) { - return DevicePermissionRepository(ref.watch(permissionApiProvider)); +final permissionRepositoryProvider = Provider((_) { + return const PermissionRepository(); }); -class DevicePermissionRepository { - final PermissionApi _permissionApi; +class PermissionRepository implements IPermissionRepository { + const PermissionRepository(); - const DevicePermissionRepository(this._permissionApi); - - Future getStatus(DevicePermission permission) async => - (await permission.handler.status).toDevicePermissionStatus(); - - Future request(DevicePermission permission) async => - (await permission.handler.request()).toDevicePermissionStatus(); - - // TODO(shenlong): Move this to it's own device info repo - Future getAndroidSdkVersion() async { - if (CurrentPlatform.isIOS) { - throw UnsupportedError("This method is available only on Android"); - } - - final androidInfo = await DeviceInfoPlugin().androidInfo; - return androidInfo.version.sdkInt; + @override + Future hasLocationWhenInUsePermission() { + return Permission.locationWhenInUse.isGranted; } - Future hasLocationWhenInUsePermission() => ph.Permission.locationWhenInUse.isGranted; - + @override Future requestLocationWhenInUsePermission() async { - final result = await ph.Permission.locationWhenInUse.request(); + final result = await Permission.locationWhenInUse.request(); return result.isGranted; } + @override Future hasLocationAlwaysPermission() { - return ph.Permission.locationAlways.isGranted; + return Permission.locationAlways.isGranted; } + @override Future requestLocationAlwaysPermission() async { - final result = await ph.Permission.locationAlways.request(); + final result = await Permission.locationAlways.request(); return result.isGranted; } + @override Future openSettings() { - return ph.openAppSettings(); - } - - Future hasManageMediaPermission() { - return _permissionApi.hasManageMediaPermission(); - } - - Future requestManageMediaPermission() { - return _permissionApi.requestManageMediaPermission(); - } - - Future manageMediaPermission() { - return _permissionApi.manageMediaPermission(); + return openAppSettings(); } } -extension on DevicePermission { - ph.Permission get handler => switch (this) { - .photos => ph.Permission.photos, - .videos => ph.Permission.videos, - .storage => ph.Permission.storage, - .mediaLocation => ph.Permission.accessMediaLocation, - }; -} - -extension on ph.PermissionStatus { - DevicePermissionStatus toDevicePermissionStatus() => switch (this) { - .granted => .granted, - .limited => .limited, - .permanentlyDenied => .permanentlyDenied, - _ => .denied, - }; +abstract interface class IPermissionRepository { + Future hasLocationWhenInUsePermission(); + Future requestLocationWhenInUsePermission(); + Future hasLocationAlwaysPermission(); + Future requestLocationAlwaysPermission(); + Future openSettings(); } diff --git a/mobile/lib/repositories/person_api.repository.dart b/mobile/lib/repositories/person_api.repository.dart index 262868eea1..bbf55e674a 100644 --- a/mobile/lib/repositories/person_api.repository.dart +++ b/mobile/lib/repositories/person_api.repository.dart @@ -11,16 +11,23 @@ class PersonApiRepository extends ApiRepository { PersonApiRepository(this._api); - Future update(String id, {String? name, DateTime? birthday}) async { + Future> getAll() async { + final dto = await checkNull(_api.getAllPeople()); + return dto.people.map(_toPerson).toList(); + } + + Future update(String id, {String? name, DateTime? birthday}) async { final birthdayUtc = birthday == null ? null : DateTime.utc(birthday.year, birthday.month, birthday.day); - final dto = PersonUpdateDto( - name: name == null ? const Optional.absent() : Optional.present(name), - birthDate: birthdayUtc == null ? const Optional.absent() : Optional.present(birthdayUtc), - ); + final dto = PersonUpdateDto(name: name, birthDate: birthdayUtc); final response = await checkNull(_api.updatePerson(id, dto)); return _toPerson(response); } - static Person _toPerson(PersonResponseDto dto) => - .new(birthDate: dto.birthDate, id: dto.id, name: dto.name, updatedAt: dto.updatedAt.orElse(null)); + static PersonDto _toPerson(PersonResponseDto dto) => PersonDto( + birthDate: dto.birthDate, + id: dto.id, + isHidden: dto.isHidden, + name: dto.name, + thumbnailPath: dto.thumbnailPath, + ); } diff --git a/mobile/lib/repositories/sessions_api.repository.dart b/mobile/lib/repositories/sessions_api.repository.dart index b2ed54c26c..f25e724f19 100644 --- a/mobile/lib/repositories/sessions_api.repository.dart +++ b/mobile/lib/repositories/sessions_api.repository.dart @@ -15,13 +15,7 @@ class SessionsAPIRepository extends ApiRepository { Future createSession(String deviceType, String deviceOS, {int? duration}) async { final dto = await checkNull( - _api.createSession( - SessionCreateDto( - deviceType: Optional.present(deviceType), - deviceOS: Optional.present(deviceOS), - duration: duration == null ? const Optional.absent() : Optional.present(duration), - ), - ), + _api.createSession(SessionCreateDto(deviceType: deviceType, deviceOS: deviceOS, duration: duration)), ); return SessionCreateResponse( @@ -29,7 +23,7 @@ class SessionsAPIRepository extends ApiRepository { current: dto.current, deviceType: deviceType, deviceOS: deviceOS, - expiresAt: dto.expiresAt.orElse(null), + expiresAt: dto.expiresAt, createdAt: dto.createdAt, updatedAt: dto.updatedAt, token: dto.token, diff --git a/mobile/lib/repositories/timeline.repository.dart b/mobile/lib/repositories/timeline.repository.dart new file mode 100644 index 0000000000..c8c173b6f6 --- /dev/null +++ b/mobile/lib/repositories/timeline.repository.dart @@ -0,0 +1,146 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/repositories/database.repository.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:isar/isar.dart'; + +final timelineRepositoryProvider = Provider((ref) => TimelineRepository(ref.watch(dbProvider))); + +class TimelineRepository extends DatabaseRepository { + const TimelineRepository(super.db); + + Future> getTimelineUserIds(String id) { + return db.users.filter().inTimelineEqualTo(true).or().idEqualTo(id).idProperty().findAll(); + } + + Stream> watchTimelineUsers(String id) { + return db.users.filter().inTimelineEqualTo(true).or().idEqualTo(id).idProperty().watch(); + } + + Stream watchArchiveTimeline(String userId) { + final query = db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .isTrashedEqualTo(false) + .visibilityEqualTo(AssetVisibilityEnum.archive) + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, GroupAssetsBy.none); + } + + Stream watchFavoriteTimeline(String userId) { + final query = db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .isFavoriteEqualTo(true) + .not() + .visibilityEqualTo(AssetVisibilityEnum.locked) + .isTrashedEqualTo(false) + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, GroupAssetsBy.none); + } + + Stream watchAlbumTimeline(Album album, GroupAssetsBy groupAssetByOption) { + final query = album.assets.filter().isTrashedEqualTo(false).not().visibilityEqualTo(AssetVisibilityEnum.locked); + + final withSortedOption = switch (album.sortOrder) { + SortOrder.asc => query.sortByFileCreatedAt(), + SortOrder.desc => query.sortByFileCreatedAtDesc(), + }; + + return _watchRenderList(withSortedOption, groupAssetByOption); + } + + Stream watchTrashTimeline(String userId) { + final query = db.assets.filter().ownerIdEqualTo(fastHash(userId)).isTrashedEqualTo(true).sortByFileCreatedAtDesc(); + + return _watchRenderList(query, GroupAssetsBy.none); + } + + Stream watchAllVideosTimeline(String userId) { + final query = db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .isTrashedEqualTo(false) + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .typeEqualTo(AssetType.video) + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, GroupAssetsBy.none); + } + + Stream watchHomeTimeline(String userId, GroupAssetsBy groupAssetByOption) { + final query = db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .isTrashedEqualTo(false) + .stackPrimaryAssetIdIsNull() + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, groupAssetByOption); + } + + Stream watchMultiUsersTimeline(List userIds, GroupAssetsBy groupAssetByOption) { + final isarUserIds = userIds.map(fastHash).toList(); + final query = db.assets + .where() + .anyOf(isarUserIds, (qb, id) => qb.ownerIdEqualToAnyChecksum(id)) + .filter() + .isTrashedEqualTo(false) + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .stackPrimaryAssetIdIsNull() + .sortByFileCreatedAtDesc(); + return _watchRenderList(query, groupAssetByOption); + } + + Future getTimelineFromAssets(List assets, GroupAssetsBy getGroupByOption) { + return RenderList.fromAssets(assets, getGroupByOption); + } + + Stream watchAssetSelectionTimeline(String userId) { + final query = db.assets + .where() + .remoteIdIsNotNull() + .filter() + .ownerIdEqualTo(fastHash(userId)) + .visibilityEqualTo(AssetVisibilityEnum.timeline) + .isTrashedEqualTo(false) + .stackPrimaryAssetIdIsNull() + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, GroupAssetsBy.none); + } + + Stream watchLockedTimeline(String userId, GroupAssetsBy getGroupByOption) { + final query = db.assets + .where() + .ownerIdEqualToAnyChecksum(fastHash(userId)) + .filter() + .visibilityEqualTo(AssetVisibilityEnum.locked) + .isTrashedEqualTo(false) + .sortByFileCreatedAtDesc(); + + return _watchRenderList(query, getGroupByOption); + } + + Stream _watchRenderList( + QueryBuilder query, + GroupAssetsBy groupAssetsBy, + ) async* { + yield await RenderList.fromQuery(query, groupAssetsBy); + await for (final _ in query.watchLazy()) { + yield await RenderList.fromQuery(query, groupAssetsBy); + } + } +} diff --git a/mobile/lib/repositories/upload.repository.dart b/mobile/lib/repositories/upload.repository.dart index 136684093c..98c6202e19 100644 --- a/mobile/lib/repositories/upload.repository.dart +++ b/mobile/lib/repositories/upload.repository.dart @@ -4,13 +4,13 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:http/http.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; +import 'package:http/http.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; final uploadRepositoryProvider = Provider((ref) => UploadRepository()); @@ -95,34 +95,23 @@ class UploadRepository { required Completer? cancelToken, void Function(int bytes, int totalBytes)? onProgress, required String logContext, - Client? httpClient, }) async { final String savedEndpoint = Store.get(StoreKey.serverEndpoint); - - ProgressMultipartRequest buildRequest() { - final request = ProgressMultipartRequest( - 'POST', - Uri.parse('$savedEndpoint/assets'), - abortTrigger: cancelToken?.future, - onProgress: onProgress, - ); - request.fields.addAll(fields); - request.files.add(MultipartFile("assetData", file.openRead(), file.lengthSync(), filename: originalFileName)); - return request; - } + final baseRequest = ProgressMultipartRequest( + 'POST', + Uri.parse('$savedEndpoint/assets'), + abortTrigger: cancelToken?.future, + onProgress: onProgress, + ); try { - final client = httpClient ?? NetworkRepository.client; - StreamedResponse response; - try { - response = await client.send(buildRequest()); - } on RequestAbortedException { - rethrow; - } on ClientException catch (error) { - logger.warning("Upload $logContext failed before a response, resending once: $error"); - response = await client.send(buildRequest()); - } + final fileStream = file.openRead(); + final assetRawUploadData = MultipartFile("assetData", fileStream, file.lengthSync(), filename: originalFileName); + baseRequest.fields.addAll(fields); + baseRequest.files.add(assetRawUploadData); + + final response = await NetworkRepository.client.send(baseRequest); final responseBodyString = await response.stream.bytesToString(); if (![200, 201].contains(response.statusCode)) { @@ -155,7 +144,7 @@ class UploadRepository { logger.warning("Upload $logContext was cancelled"); return UploadResult.cancelled(); } catch (error, stackTrace) { - logger.warning("Error uploading $logContext: $error: $stackTrace"); + logger.warning("Error uploading $logContext: ${error.toString()}: $stackTrace"); return UploadResult.error(errorMessage: error.toString()); } } @@ -172,9 +161,7 @@ class ProgressMultipartRequest extends MultipartRequest with Abortable { @override ByteStream finalize() { final byteStream = super.finalize(); - if (onProgress == null) { - return byteStream; - } + if (onProgress == null) return byteStream; final total = contentLength; var bytes = 0; diff --git a/mobile/lib/repositories/widget.repository.dart b/mobile/lib/repositories/widget.repository.dart index 87209ac983..f21d31e1ec 100644 --- a/mobile/lib/repositories/widget.repository.dart +++ b/mobile/lib/repositories/widget.repository.dart @@ -1,6 +1,5 @@ import 'package:home_widget/home_widget.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; final widgetRepositoryProvider = Provider((_) => const WidgetRepository()); @@ -15,7 +14,7 @@ class WidgetRepository { await HomeWidget.updateWidget(iOSName: iosName, qualifiedAndroidName: androidName); } - Future setAppGroupId() async { - await HomeWidget.setAppGroupId(await networkApi.getAppGroupId()); + Future setAppGroupId(String appGroupId) async { + await HomeWidget.setAppGroupId(appGroupId); } } diff --git a/mobile/lib/routing/app_navigation_observer.dart b/mobile/lib/routing/app_navigation_observer.dart index 90381cf49c..b05a28172d 100644 --- a/mobile/lib/routing/app_navigation_observer.dart +++ b/mobile/lib/routing/app_navigation_observer.dart @@ -4,6 +4,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; class AppNavigationObserver extends AutoRouterObserver { /// Riverpod Instance @@ -11,41 +12,51 @@ class AppNavigationObserver extends AutoRouterObserver { AppNavigationObserver({required this.ref}); + @override + Future didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async { + unawaited(Future(() => ref.read(inLockedViewProvider.notifier).state = false)); + } + @override void didPush(Route route, Route? previousRoute) { - ref.invalidate(inLockedViewProvider); - ref.invalidate(isAssetViewerOpenProvider); - unawaited( - Future(() { - ref.read(currentRouteNameProvider.notifier).state = route.settings.name; - ref.read(previousRouteNameProvider.notifier).state = previousRoute?.settings.name; - ref.read(previousRouteDataProvider.notifier).state = previousRoute?.settings; - }), - ); + _handleLockedViewState(route, previousRoute); + _handleDriftLockedFolderState(route, previousRoute); + Future(() { + ref.read(currentRouteNameProvider.notifier).state = route.settings.name; + ref.read(previousRouteNameProvider.notifier).state = previousRoute?.settings.name; + ref.read(previousRouteDataProvider.notifier).state = previousRoute?.settings; + }); } - @override - void didPop(Route route, Route? previousRoute) { - ref.invalidate(inLockedViewProvider); - ref.invalidate(isAssetViewerOpenProvider); - } -} + _handleLockedViewState(Route route, Route? previousRoute) { + final isInLockedView = ref.read(inLockedViewProvider); + final isFromLockedViewToDetailView = + route.settings.name == GalleryViewerRoute.name && previousRoute?.settings.name == LockedRoute.name; -/// Tracks routes that are undergoing a pop transition -class TransitioningRouteObserver extends NavigatorObserver { - int _transitioningRoutes = 0; + final isFromDetailViewToInfoPanelView = + route.settings.name == null && previousRoute?.settings.name == GalleryViewerRoute.name && isInLockedView; - /// Whether a "popping" route is still on screen - bool get hasTransitioningRoute => _transitioningRoutes > 0; - - @override - void didPop(Route route, Route? previousRoute) { - if (route is! TransitionRoute) { - return; + if (route.settings.name == LockedRoute.name || isFromLockedViewToDetailView || isFromDetailViewToInfoPanelView) { + Future(() => ref.read(inLockedViewProvider.notifier).state = true); + } else { + Future(() => ref.read(inLockedViewProvider.notifier).state = false); } + } - _transitioningRoutes += 1; - // Transition completed and route disposed - unawaited(route.completed.whenComplete(() => _transitioningRoutes -= 1)); + _handleDriftLockedFolderState(Route route, Route? previousRoute) { + final isInLockedView = ref.read(inLockedViewProvider); + final isFromLockedViewToDetailView = + route.settings.name == AssetViewerRoute.name && previousRoute?.settings.name == DriftLockedFolderRoute.name; + + final isFromDetailViewToInfoPanelView = + route.settings.name == null && previousRoute?.settings.name == AssetViewerRoute.name && isInLockedView; + + if (route.settings.name == DriftLockedFolderRoute.name || + isFromLockedViewToDetailView || + isFromDetailViewToInfoPanelView) { + Future(() => ref.read(inLockedViewProvider.notifier).state = true); + } else { + Future(() => ref.read(inLockedViewProvider.notifier).state = false); + } } } diff --git a/mobile/lib/routing/auth_guard.dart b/mobile/lib/routing/auth_guard.dart index 2fc27be4f4..b0cd9ea9ea 100644 --- a/mobile/lib/routing/auth_guard.dart +++ b/mobile/lib/routing/auth_guard.dart @@ -7,70 +7,43 @@ import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/auth.service.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; class AuthGuard extends AutoRouteGuard { final ApiService _apiService; - final AuthService _authService; final _log = Logger("AuthGuard"); - bool _validateInFlight = false; - AuthGuard(this._apiService, this._authService); + AuthGuard(this._apiService); @override - void onNavigation(NavigationResolver resolver, StackRouter router) { - // Synchronously check for the access token. auto_route awaits async - // guards, so we keep this function fully sync and validate the token in - // the background — otherwise a slow validateAccessToken() request would - // block the route transition for as long as the OS-level HTTP timeout. - try { - Store.get(StoreKey.accessToken); - } on StoreKeyNotFoundException catch (_) { - _log.warning('No access token in the store.'); - resolver.next(false); - unawaited(router.replaceAll([const LoginRoute()])); - return; - } - + void onNavigation(NavigationResolver resolver, StackRouter router) async { resolver.next(true); - unawaited(_validateAccessTokenInBackground(router)); - } - Future _validateAccessTokenInBackground(StackRouter router) async { - if (_validateInFlight) { - return; - } - final token = Store.tryGet(StoreKey.accessToken); - if (token == null) { - return; - } - _validateInFlight = true; try { + // Look in the store for an access token + Store.get(StoreKey.accessToken); + + // Validate the access token with the server final res = await _apiService.authenticationApi.validateAccessToken(); if (res == null || res.authStatus != true) { - // Token may have changed during validation (user logged out + logged in - // again); only act if it still applies to the current session. - if (Store.tryGet(StoreKey.accessToken) != token) { - return; - } + // If the access token is invalid, take user back to login _log.fine('User token is invalid. Redirecting to login'); - await router.replaceAll([const LoginRoute()]); - await _authService.clearLocalData(); + unawaited(router.replaceAll([const LoginRoute()])); } + } on StoreKeyNotFoundException catch (_) { + // If there is no access token, take us to the login page + _log.warning('No access token in the store.'); + unawaited(router.replaceAll([const LoginRoute()])); + return; } on ApiException catch (e) { - if (e.code != HttpStatus.unauthorized) { + // On an unauthorized request, take us to the login page + if (e.code == HttpStatus.unauthorized) { + _log.warning("Unauthorized access token."); + unawaited(router.replaceAll([const LoginRoute()])); return; } - if (Store.tryGet(StoreKey.accessToken) != token) { - return; - } - _log.warning("Unauthorized access token."); - await router.replaceAll([const LoginRoute()]); - await _authService.clearLocalData(); } catch (e) { + // Otherwise, this is not fatal, but we still log the warning _log.warning('Error validating access token from server: $e'); - } finally { - _validateInFlight = false; } } } diff --git a/mobile/lib/routing/backup_permission_guard.dart b/mobile/lib/routing/backup_permission_guard.dart new file mode 100644 index 0000000000..f52516f2e5 --- /dev/null +++ b/mobile/lib/routing/backup_permission_guard.dart @@ -0,0 +1,21 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:immich_mobile/providers/gallery_permission.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; + +class BackupPermissionGuard extends AutoRouteGuard { + final GalleryPermissionNotifier _permission; + + const BackupPermissionGuard(this._permission); + + @override + void onNavigation(NavigationResolver resolver, StackRouter router) async { + final p = _permission.hasPermission; + if (p) { + resolver.next(true); + } else { + unawaited(router.push(const PermissionOnboardingRoute())); + } + } +} diff --git a/mobile/lib/routing/duplicate_guard.dart b/mobile/lib/routing/duplicate_guard.dart index bffe6064ee..c55c7318d0 100644 --- a/mobile/lib/routing/duplicate_guard.dart +++ b/mobile/lib/routing/duplicate_guard.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/utils/debug_print.dart'; class DuplicateGuard extends AutoRouteGuard { const DuplicateGuard(); @override - Future onNavigation(NavigationResolver resolver, StackRouter router) async { + void onNavigation(NavigationResolver resolver, StackRouter router) async { // Duplicate navigation if (resolver.route.name == router.current.name) { dPrint(() => 'DuplicateGuard: Preventing duplicate route navigation for ${resolver.route.name}'); diff --git a/mobile/lib/routing/gallery_guard.dart b/mobile/lib/routing/gallery_guard.dart new file mode 100644 index 0000000000..6a4b1bddab --- /dev/null +++ b/mobile/lib/routing/gallery_guard.dart @@ -0,0 +1,34 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:immich_mobile/routing/router.dart'; + +/// Handles duplicate navigation to this route (primarily for deep linking) +class GalleryGuard extends AutoRouteGuard { + const GalleryGuard(); + @override + void onNavigation(NavigationResolver resolver, StackRouter router) async { + final newRouteName = resolver.route.name; + final currentTopRouteName = router.stack.isNotEmpty ? router.stack.last.name : null; + + if (currentTopRouteName == newRouteName) { + // Replace instead of pushing duplicate + final args = resolver.route.args as GalleryViewerRouteArgs; + + unawaited( + router.replace( + GalleryViewerRoute( + renderList: args.renderList, + initialIndex: args.initialIndex, + heroOffset: args.heroOffset, + showStack: args.showStack, + ), + ), + ); + // Prevent further navigation since we replaced the route + resolver.next(false); + return; + } + resolver.next(true); + } +} diff --git a/mobile/lib/routing/locked_guard.dart b/mobile/lib/routing/locked_guard.dart index da0a025bca..ddb6a7e694 100644 --- a/mobile/lib/routing/locked_guard.dart +++ b/mobile/lib/routing/locked_guard.dart @@ -21,7 +21,7 @@ class LockedGuard extends AutoRouteGuard { LockedGuard(this._apiService, this._secureStorageService, this._localAuth); @override - Future onNavigation(NavigationResolver resolver, StackRouter router) async { + void onNavigation(NavigationResolver resolver, StackRouter router) async { final authStatus = await _apiService.authenticationApi.getAuthStatus(); if (authStatus == null) { @@ -55,17 +55,20 @@ class LockedGuard extends AutoRouteGuard { return; } - await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: Optional.present(securePinCode))); + await _apiService.authenticationApi.unlockAuthSession(SessionUnlockDto(pinCode: securePinCode)); resolver.next(true); } on PlatformException catch (error) { switch (error.code) { case auth_error.notAvailable: _log.severe("notAvailable: $error"); + break; case auth_error.notEnrolled: _log.severe("not enrolled"); + break; default: _log.severe("error"); + break; } resolver.next(false); diff --git a/mobile/lib/routing/router.dart b/mobile/lib/routing/router.dart index 1d6455782c..b385bcbf71 100644 --- a/mobile/lib/routing/router.dart +++ b/mobile/lib/routing/router.dart @@ -1,38 +1,81 @@ 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/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/models/folder/recursive_folder.model.dart'; +import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; +import 'package:immich_mobile/pages/album/album_additional_shared_user_selection.page.dart'; +import 'package:immich_mobile/pages/album/album_asset_selection.page.dart'; +import 'package:immich_mobile/pages/album/album_options.page.dart'; +import 'package:immich_mobile/pages/album/album_shared_user_selection.page.dart'; +import 'package:immich_mobile/pages/album/album_viewer.page.dart'; +import 'package:immich_mobile/pages/albums/albums.page.dart'; +import 'package:immich_mobile/pages/backup/album_preview.page.dart'; +import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart'; +import 'package:immich_mobile/pages/backup/backup_controller.page.dart'; +import 'package:immich_mobile/pages/backup/backup_options.page.dart'; import 'package:immich_mobile/pages/backup/drift_backup.page.dart'; import 'package:immich_mobile/pages/backup/drift_backup_album_selection.page.dart'; import 'package:immich_mobile/pages/backup/drift_backup_asset_detail.page.dart'; import 'package:immich_mobile/pages/backup/drift_backup_options.page.dart'; import 'package:immich_mobile/pages/backup/drift_upload_detail.page.dart'; +import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart'; +import 'package:immich_mobile/pages/common/activities.page.dart'; import 'package:immich_mobile/pages/common/app_log.page.dart'; import 'package:immich_mobile/pages/common/app_log_detail.page.dart'; +import 'package:immich_mobile/pages/common/change_experience.page.dart'; +import 'package:immich_mobile/pages/common/create_album.page.dart'; +import 'package:immich_mobile/pages/common/gallery_viewer.page.dart'; import 'package:immich_mobile/pages/common/headers_settings.page.dart'; +import 'package:immich_mobile/pages/common/native_video_viewer.page.dart'; import 'package:immich_mobile/pages/common/settings.page.dart'; import 'package:immich_mobile/pages/common/splash_screen.page.dart'; +import 'package:immich_mobile/pages/common/tab_controller.page.dart'; import 'package:immich_mobile/pages/common/tab_shell.page.dart'; +import 'package:immich_mobile/pages/editing/crop.page.dart'; +import 'package:immich_mobile/pages/editing/edit.page.dart'; +import 'package:immich_mobile/pages/editing/filter.page.dart'; +import 'package:immich_mobile/pages/library/archive.page.dart'; +import 'package:immich_mobile/pages/library/favorite.page.dart'; import 'package:immich_mobile/pages/library/folder/folder.page.dart'; +import 'package:immich_mobile/pages/library/library.page.dart'; +import 'package:immich_mobile/pages/library/local_albums.page.dart'; +import 'package:immich_mobile/pages/library/locked/locked.page.dart'; import 'package:immich_mobile/pages/library/locked/pin_auth.page.dart'; +import 'package:immich_mobile/pages/library/partner/drift_partner.page.dart'; import 'package:immich_mobile/pages/library/partner/partner.page.dart'; +import 'package:immich_mobile/pages/library/partner/partner_detail.page.dart'; +import 'package:immich_mobile/pages/library/people/people_collection.page.dart'; +import 'package:immich_mobile/pages/library/places/places_collection.page.dart'; import 'package:immich_mobile/pages/library/shared_link/shared_link.page.dart'; import 'package:immich_mobile/pages/library/shared_link/shared_link_edit.page.dart'; +import 'package:immich_mobile/pages/library/trash.page.dart'; import 'package:immich_mobile/pages/login/change_password.page.dart'; import 'package:immich_mobile/pages/login/login.page.dart'; +import 'package:immich_mobile/pages/onboarding/permission_onboarding.page.dart'; +import 'package:immich_mobile/pages/photos/memory.page.dart'; +import 'package:immich_mobile/pages/photos/photos.page.dart'; +import 'package:immich_mobile/pages/search/all_motion_videos.page.dart'; +import 'package:immich_mobile/pages/search/all_people.page.dart'; +import 'package:immich_mobile/pages/search/all_places.page.dart'; +import 'package:immich_mobile/pages/search/all_videos.page.dart'; +import 'package:immich_mobile/pages/search/map/map.page.dart'; import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart'; +import 'package:immich_mobile/pages/search/person_result.page.dart'; +import 'package:immich_mobile/pages/search/recently_taken.page.dart'; +import 'package:immich_mobile/pages/search/search.page.dart'; import 'package:immich_mobile/pages/settings/sync_status.page.dart'; import 'package:immich_mobile/pages/share_intent/share_intent.page.dart'; import 'package:immich_mobile/presentation/pages/cleanup_preview.page.dart'; @@ -57,36 +100,38 @@ import 'package:immich_mobile/presentation/pages/drift_people_collection.page.da import 'package:immich_mobile/presentation/pages/drift_person.page.dart'; import 'package:immich_mobile/presentation/pages/drift_place.page.dart'; import 'package:immich_mobile/presentation/pages/drift_place_detail.page.dart'; -import 'package:immich_mobile/presentation/pages/drift_recently_added.page.dart'; import 'package:immich_mobile/presentation/pages/drift_recently_taken.page.dart'; import 'package:immich_mobile/presentation/pages/drift_remote_album.page.dart'; -import 'package:immich_mobile/presentation/pages/drift_slideshow.page.dart'; import 'package:immich_mobile/presentation/pages/drift_trash.page.dart'; import 'package:immich_mobile/presentation/pages/drift_user_selection.page.dart'; import 'package:immich_mobile/presentation/pages/drift_video.page.dart'; -import 'package:immich_mobile/presentation/pages/edit/drift_edit.page.dart'; -import 'package:immich_mobile/presentation/pages/feature_message/whats_new.page.dart'; -import 'package:immich_mobile/presentation/pages/local_timeline.page.dart'; +import 'package:immich_mobile/presentation/pages/editing/drift_crop.page.dart'; import 'package:immich_mobile/presentation/pages/profile/profile_picture_crop.page.dart'; +import 'package:immich_mobile/presentation/pages/editing/drift_edit.page.dart'; +import 'package:immich_mobile/presentation/pages/editing/drift_filter.page.dart'; +import 'package:immich_mobile/presentation/pages/local_timeline.page.dart'; import 'package:immich_mobile/presentation/pages/search/drift_search.page.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.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'; +import 'package:immich_mobile/routing/backup_permission_guard.dart'; +import 'package:immich_mobile/routing/custom_transition_builders.dart'; import 'package:immich_mobile/routing/duplicate_guard.dart'; +import 'package:immich_mobile/routing/gallery_guard.dart'; import 'package:immich_mobile/routing/locked_guard.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/auth.service.dart'; import 'package:immich_mobile/services/local_auth.service.dart'; import 'package:immich_mobile/services/secure_storage.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; part 'router.gr.dart'; final appRouterProvider = Provider( - // ignore: dispose-provided-instances (ref) => AppRouter( ref.watch(apiServiceProvider), - ref.watch(authServiceProvider), + ref.watch(galleryPermissionNotifier.notifier), ref.watch(secureStorageServiceProvider), ref.watch(localAuthServiceProvider), ), @@ -96,17 +141,21 @@ final appRouterProvider = Provider( class AppRouter extends RootStackRouter { late final AuthGuard _authGuard; late final DuplicateGuard _duplicateGuard; + late final BackupPermissionGuard _backupPermissionGuard; late final LockedGuard _lockedGuard; + late final GalleryGuard _galleryGuard; AppRouter( ApiService apiService, - AuthService authService, + GalleryPermissionNotifier galleryPermissionNotifier, SecureStorageService secureStorageService, LocalAuthService localAuthService, ) { - _authGuard = AuthGuard(apiService, authService); + _authGuard = AuthGuard(apiService); _duplicateGuard = const DuplicateGuard(); _lockedGuard = LockedGuard(apiService, secureStorageService, localAuthService); + _backupPermissionGuard = BackupPermissionGuard(galleryPermissionNotifier); + _galleryGuard = const GalleryGuard(); } @override @@ -115,8 +164,20 @@ class AppRouter extends RootStackRouter { @override late final List routes = [ AutoRoute(page: SplashScreenRoute.page, initial: true), + AutoRoute(page: PermissionOnboardingRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LoginRoute.page), AutoRoute(page: ChangePasswordRoute.page), + AutoRoute(page: SearchRoute.page, guards: [_authGuard, _duplicateGuard], maintainState: false), + AutoRoute( + page: TabControllerRoute.page, + guards: [_authGuard, _duplicateGuard], + children: [ + AutoRoute(page: PhotosRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: SearchRoute.page, guards: [_authGuard, _duplicateGuard], maintainState: false), + AutoRoute(page: LibraryRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: AlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), + ], + ), AutoRoute( page: TabShellRoute.page, guards: [_authGuard, _duplicateGuard], @@ -127,18 +188,105 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), ], ), + CustomRoute( + page: GalleryViewerRoute.page, + guards: [_authGuard, _galleryGuard], + transitionsBuilder: CustomTransitionsBuilders.zoomedPage, + ), + AutoRoute(page: BackupControllerRoute.page, guards: [_authGuard, _duplicateGuard, _backupPermissionGuard]), + AutoRoute(page: AllPlacesRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: CreateAlbumRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: EditImageRoute.page), + AutoRoute(page: CropImageRoute.page), + AutoRoute(page: FilterImageRoute.page), AutoRoute(page: ProfilePictureCropRoute.page), + CustomRoute( + page: FavoritesRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + AutoRoute(page: AllVideosRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: AllMotionPhotosRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: RecentlyTakenRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: AlbumAssetSelectionRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideBottom, + ), + CustomRoute( + page: AlbumSharedUserSelectionRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideBottom, + ), + AutoRoute(page: AlbumViewerRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: AlbumAdditionalSharedUserSelectionRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideBottom, + ), + AutoRoute(page: BackupAlbumSelectionRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: AlbumPreviewRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: FailedBackupStatusRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideBottom, + ), AutoRoute(page: SettingsRoute.page, guards: [_duplicateGuard]), AutoRoute(page: SettingsSubRoute.page, guards: [_duplicateGuard]), - AutoRoute(page: WhatsNewRoute.page, guards: [_duplicateGuard]), AutoRoute(page: AppLogRoute.page, guards: [_duplicateGuard]), AutoRoute(page: AppLogDetailRoute.page, guards: [_duplicateGuard]), + CustomRoute( + page: ArchiveRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( + page: PartnerRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), AutoRoute(page: FolderRoute.page, guards: [_authGuard]), + AutoRoute(page: PartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: PersonResultRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: AllPeopleRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: MemoryRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: MapRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: AlbumOptionsRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: TrashRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: SharedLinkRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: SharedLinkEditRoute.page, guards: [_authGuard, _duplicateGuard]), + CustomRoute( + page: ActivitiesRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + durationInMilliseconds: 200, + ), CustomRoute(page: MapLocationPickerRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: BackupOptionsRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: HeaderSettingsRoute.page, guards: [_duplicateGuard]), + CustomRoute( + page: PeopleCollectionRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( + page: AlbumsRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( + page: LocalAlbumsRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + CustomRoute( + page: PlacesCollectionRoute.page, + guards: [_authGuard, _duplicateGuard], + transitionsBuilder: TransitionsBuilders.slideLeft, + ), + AutoRoute(page: NativeVideoViewerRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: ShareIntentRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: LockedRoute.page, guards: [_authGuard, _lockedGuard, _duplicateGuard]), AutoRoute(page: PinAuthRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: LocalMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: RemoteMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]), @@ -154,7 +302,7 @@ class AppRouter extends RootStackRouter { customRouteBuilder: (context, child, page) => PageRouteBuilder( fullscreenDialog: page.fullscreenDialog, settings: page, - pageBuilder: (_, _, _) => child, + pageBuilder: (_, __, ___) => child, opaque: false, transitionsBuilder: TransitionsBuilders.fadeIn, ), @@ -170,13 +318,13 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftAssetSelectionTimelineRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPartnerDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftRecentlyTakenRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: DriftRecentlyAddedRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftLocalAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftCreateAlbumRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPlaceRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftPlaceDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftUserSelectionRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: PartnerRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: ChangeExperienceRoute.page, guards: [_authGuard, _duplicateGuard]), + AutoRoute(page: DriftPartnerRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftUploadDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: SyncStatusRoute.page, guards: [_duplicateGuard]), AutoRoute(page: DriftPeopleCollectionRoute.page, guards: [_authGuard, _duplicateGuard]), @@ -185,12 +333,13 @@ class AppRouter extends RootStackRouter { AutoRoute(page: DriftAlbumOptionsRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftMapRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftEditImageRoute.page), + AutoRoute(page: DriftCropImageRoute.page), + AutoRoute(page: DriftFilterImageRoute.page), AutoRoute(page: DriftActivitiesRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]), AutoRoute(page: CleanupPreviewRoute.page, guards: [_authGuard, _duplicateGuard]), - AutoRoute(page: DriftSlideshowRoute.page, guards: [_authGuard, _duplicateGuard]), // required to handle all deeplinks in deep_link.service.dart // auto_route_library#1722 RedirectRoute(path: '*', redirectTo: '/'), diff --git a/mobile/lib/routing/router.gr.dart b/mobile/lib/routing/router.gr.dart new file mode 100644 index 0000000000..2d57c16573 --- /dev/null +++ b/mobile/lib/routing/router.gr.dart @@ -0,0 +1,2836 @@ +// dart format width=80 +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ************************************************************************** +// AutoRouterGenerator +// ************************************************************************** + +// ignore_for_file: type=lint +// coverage:ignore-file + +part of 'router.dart'; + +/// generated route for +/// [ActivitiesPage] +class ActivitiesRoute extends PageRouteInfo { + const ActivitiesRoute({List? children}) + : super(ActivitiesRoute.name, initialChildren: children); + + static const String name = 'ActivitiesRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ActivitiesPage(); + }, + ); +} + +/// generated route for +/// [AlbumAdditionalSharedUserSelectionPage] +class AlbumAdditionalSharedUserSelectionRoute + extends PageRouteInfo { + AlbumAdditionalSharedUserSelectionRoute({ + Key? key, + required Album album, + List? children, + }) : super( + AlbumAdditionalSharedUserSelectionRoute.name, + args: AlbumAdditionalSharedUserSelectionRouteArgs( + key: key, + album: album, + ), + initialChildren: children, + ); + + static const String name = 'AlbumAdditionalSharedUserSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AlbumAdditionalSharedUserSelectionPage( + key: args.key, + album: args.album, + ); + }, + ); +} + +class AlbumAdditionalSharedUserSelectionRouteArgs { + const AlbumAdditionalSharedUserSelectionRouteArgs({ + this.key, + required this.album, + }); + + final Key? key; + + final Album album; + + @override + String toString() { + return 'AlbumAdditionalSharedUserSelectionRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [AlbumAssetSelectionPage] +class AlbumAssetSelectionRoute + extends PageRouteInfo { + AlbumAssetSelectionRoute({ + Key? key, + required Set existingAssets, + bool canDeselect = false, + List? children, + }) : super( + AlbumAssetSelectionRoute.name, + args: AlbumAssetSelectionRouteArgs( + key: key, + existingAssets: existingAssets, + canDeselect: canDeselect, + ), + initialChildren: children, + ); + + static const String name = 'AlbumAssetSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AlbumAssetSelectionPage( + key: args.key, + existingAssets: args.existingAssets, + canDeselect: args.canDeselect, + ); + }, + ); +} + +class AlbumAssetSelectionRouteArgs { + const AlbumAssetSelectionRouteArgs({ + this.key, + required this.existingAssets, + this.canDeselect = false, + }); + + final Key? key; + + final Set existingAssets; + + final bool canDeselect; + + @override + String toString() { + return 'AlbumAssetSelectionRouteArgs{key: $key, existingAssets: $existingAssets, canDeselect: $canDeselect}'; + } +} + +/// generated route for +/// [AlbumOptionsPage] +class AlbumOptionsRoute extends PageRouteInfo { + const AlbumOptionsRoute({List? children}) + : super(AlbumOptionsRoute.name, initialChildren: children); + + static const String name = 'AlbumOptionsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AlbumOptionsPage(); + }, + ); +} + +/// generated route for +/// [AlbumPreviewPage] +class AlbumPreviewRoute extends PageRouteInfo { + AlbumPreviewRoute({ + Key? key, + required Album album, + List? children, + }) : super( + AlbumPreviewRoute.name, + args: AlbumPreviewRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'AlbumPreviewRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AlbumPreviewPage(key: args.key, album: args.album); + }, + ); +} + +class AlbumPreviewRouteArgs { + const AlbumPreviewRouteArgs({this.key, required this.album}); + + final Key? key; + + final Album album; + + @override + String toString() { + return 'AlbumPreviewRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [AlbumSharedUserSelectionPage] +class AlbumSharedUserSelectionRoute + extends PageRouteInfo { + AlbumSharedUserSelectionRoute({ + Key? key, + required Set assets, + List? children, + }) : super( + AlbumSharedUserSelectionRoute.name, + args: AlbumSharedUserSelectionRouteArgs(key: key, assets: assets), + initialChildren: children, + ); + + static const String name = 'AlbumSharedUserSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AlbumSharedUserSelectionPage(key: args.key, assets: args.assets); + }, + ); +} + +class AlbumSharedUserSelectionRouteArgs { + const AlbumSharedUserSelectionRouteArgs({this.key, required this.assets}); + + final Key? key; + + final Set assets; + + @override + String toString() { + return 'AlbumSharedUserSelectionRouteArgs{key: $key, assets: $assets}'; + } +} + +/// generated route for +/// [AlbumViewerPage] +class AlbumViewerRoute extends PageRouteInfo { + AlbumViewerRoute({ + Key? key, + required int albumId, + List? children, + }) : super( + AlbumViewerRoute.name, + args: AlbumViewerRouteArgs(key: key, albumId: albumId), + initialChildren: children, + ); + + static const String name = 'AlbumViewerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AlbumViewerPage(key: args.key, albumId: args.albumId); + }, + ); +} + +class AlbumViewerRouteArgs { + const AlbumViewerRouteArgs({this.key, required this.albumId}); + + final Key? key; + + final int albumId; + + @override + String toString() { + return 'AlbumViewerRouteArgs{key: $key, albumId: $albumId}'; + } +} + +/// generated route for +/// [AlbumsPage] +class AlbumsRoute extends PageRouteInfo { + const AlbumsRoute({List? children}) + : super(AlbumsRoute.name, initialChildren: children); + + static const String name = 'AlbumsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AlbumsPage(); + }, + ); +} + +/// generated route for +/// [AllMotionPhotosPage] +class AllMotionPhotosRoute extends PageRouteInfo { + const AllMotionPhotosRoute({List? children}) + : super(AllMotionPhotosRoute.name, initialChildren: children); + + static const String name = 'AllMotionPhotosRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AllMotionPhotosPage(); + }, + ); +} + +/// generated route for +/// [AllPeoplePage] +class AllPeopleRoute extends PageRouteInfo { + const AllPeopleRoute({List? children}) + : super(AllPeopleRoute.name, initialChildren: children); + + static const String name = 'AllPeopleRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AllPeoplePage(); + }, + ); +} + +/// generated route for +/// [AllPlacesPage] +class AllPlacesRoute extends PageRouteInfo { + const AllPlacesRoute({List? children}) + : super(AllPlacesRoute.name, initialChildren: children); + + static const String name = 'AllPlacesRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AllPlacesPage(); + }, + ); +} + +/// generated route for +/// [AllVideosPage] +class AllVideosRoute extends PageRouteInfo { + const AllVideosRoute({List? children}) + : super(AllVideosRoute.name, initialChildren: children); + + static const String name = 'AllVideosRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AllVideosPage(); + }, + ); +} + +/// generated route for +/// [AppLogDetailPage] +class AppLogDetailRoute extends PageRouteInfo { + AppLogDetailRoute({ + Key? key, + required LogMessage logMessage, + List? children, + }) : super( + AppLogDetailRoute.name, + args: AppLogDetailRouteArgs(key: key, logMessage: logMessage), + initialChildren: children, + ); + + static const String name = 'AppLogDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AppLogDetailPage(key: args.key, logMessage: args.logMessage); + }, + ); +} + +class AppLogDetailRouteArgs { + const AppLogDetailRouteArgs({this.key, required this.logMessage}); + + final Key? key; + + final LogMessage logMessage; + + @override + String toString() { + return 'AppLogDetailRouteArgs{key: $key, logMessage: $logMessage}'; + } +} + +/// generated route for +/// [AppLogPage] +class AppLogRoute extends PageRouteInfo { + const AppLogRoute({List? children}) + : super(AppLogRoute.name, initialChildren: children); + + static const String name = 'AppLogRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const AppLogPage(); + }, + ); +} + +/// generated route for +/// [ArchivePage] +class ArchiveRoute extends PageRouteInfo { + const ArchiveRoute({List? children}) + : super(ArchiveRoute.name, initialChildren: children); + + static const String name = 'ArchiveRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ArchivePage(); + }, + ); +} + +/// generated route for +/// [AssetTroubleshootPage] +class AssetTroubleshootRoute extends PageRouteInfo { + AssetTroubleshootRoute({ + Key? key, + required BaseAsset asset, + List? children, + }) : super( + AssetTroubleshootRoute.name, + args: AssetTroubleshootRouteArgs(key: key, asset: asset), + initialChildren: children, + ); + + static const String name = 'AssetTroubleshootRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AssetTroubleshootPage(key: args.key, asset: args.asset); + }, + ); +} + +class AssetTroubleshootRouteArgs { + const AssetTroubleshootRouteArgs({this.key, required this.asset}); + + final Key? key; + + final BaseAsset asset; + + @override + String toString() { + return 'AssetTroubleshootRouteArgs{key: $key, asset: $asset}'; + } +} + +/// generated route for +/// [AssetViewerPage] +class AssetViewerRoute extends PageRouteInfo { + AssetViewerRoute({ + Key? key, + required int initialIndex, + required TimelineService timelineService, + int? heroOffset, + RemoteAlbum? currentAlbum, + List? children, + }) : super( + AssetViewerRoute.name, + args: AssetViewerRouteArgs( + key: key, + initialIndex: initialIndex, + timelineService: timelineService, + heroOffset: heroOffset, + currentAlbum: currentAlbum, + ), + initialChildren: children, + ); + + static const String name = 'AssetViewerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return AssetViewerPage( + key: args.key, + initialIndex: args.initialIndex, + timelineService: args.timelineService, + heroOffset: args.heroOffset, + currentAlbum: args.currentAlbum, + ); + }, + ); +} + +class AssetViewerRouteArgs { + const AssetViewerRouteArgs({ + this.key, + required this.initialIndex, + required this.timelineService, + this.heroOffset, + this.currentAlbum, + }); + + final Key? key; + + final int initialIndex; + + final TimelineService timelineService; + + final int? heroOffset; + + final RemoteAlbum? currentAlbum; + + @override + String toString() { + return 'AssetViewerRouteArgs{key: $key, initialIndex: $initialIndex, timelineService: $timelineService, heroOffset: $heroOffset, currentAlbum: $currentAlbum}'; + } +} + +/// generated route for +/// [BackupAlbumSelectionPage] +class BackupAlbumSelectionRoute extends PageRouteInfo { + const BackupAlbumSelectionRoute({List? children}) + : super(BackupAlbumSelectionRoute.name, initialChildren: children); + + static const String name = 'BackupAlbumSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const BackupAlbumSelectionPage(); + }, + ); +} + +/// generated route for +/// [BackupControllerPage] +class BackupControllerRoute extends PageRouteInfo { + const BackupControllerRoute({List? children}) + : super(BackupControllerRoute.name, initialChildren: children); + + static const String name = 'BackupControllerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const BackupControllerPage(); + }, + ); +} + +/// generated route for +/// [BackupOptionsPage] +class BackupOptionsRoute extends PageRouteInfo { + const BackupOptionsRoute({List? children}) + : super(BackupOptionsRoute.name, initialChildren: children); + + static const String name = 'BackupOptionsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const BackupOptionsPage(); + }, + ); +} + +/// generated route for +/// [ChangeExperiencePage] +class ChangeExperienceRoute extends PageRouteInfo { + ChangeExperienceRoute({ + Key? key, + required bool switchingToBeta, + List? children, + }) : super( + ChangeExperienceRoute.name, + args: ChangeExperienceRouteArgs( + key: key, + switchingToBeta: switchingToBeta, + ), + initialChildren: children, + ); + + static const String name = 'ChangeExperienceRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return ChangeExperiencePage( + key: args.key, + switchingToBeta: args.switchingToBeta, + ); + }, + ); +} + +class ChangeExperienceRouteArgs { + const ChangeExperienceRouteArgs({this.key, required this.switchingToBeta}); + + final Key? key; + + final bool switchingToBeta; + + @override + String toString() { + return 'ChangeExperienceRouteArgs{key: $key, switchingToBeta: $switchingToBeta}'; + } +} + +/// generated route for +/// [ChangePasswordPage] +class ChangePasswordRoute extends PageRouteInfo { + const ChangePasswordRoute({List? children}) + : super(ChangePasswordRoute.name, initialChildren: children); + + static const String name = 'ChangePasswordRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const ChangePasswordPage(); + }, + ); +} + +/// generated route for +/// [CleanupPreviewPage] +class CleanupPreviewRoute extends PageRouteInfo { + CleanupPreviewRoute({ + Key? key, + required List assets, + List? children, + }) : super( + CleanupPreviewRoute.name, + args: CleanupPreviewRouteArgs(key: key, assets: assets), + initialChildren: children, + ); + + static const String name = 'CleanupPreviewRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return CleanupPreviewPage(key: args.key, assets: args.assets); + }, + ); +} + +class CleanupPreviewRouteArgs { + const CleanupPreviewRouteArgs({this.key, required this.assets}); + + final Key? key; + + final List assets; + + @override + String toString() { + return 'CleanupPreviewRouteArgs{key: $key, assets: $assets}'; + } +} + +/// generated route for +/// [CreateAlbumPage] +class CreateAlbumRoute extends PageRouteInfo { + CreateAlbumRoute({ + Key? key, + List? assets, + List? children, + }) : super( + CreateAlbumRoute.name, + args: CreateAlbumRouteArgs(key: key, assets: assets), + initialChildren: children, + ); + + static const String name = 'CreateAlbumRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const CreateAlbumRouteArgs(), + ); + return CreateAlbumPage(key: args.key, assets: args.assets); + }, + ); +} + +class CreateAlbumRouteArgs { + const CreateAlbumRouteArgs({this.key, this.assets}); + + final Key? key; + + final List? assets; + + @override + String toString() { + return 'CreateAlbumRouteArgs{key: $key, assets: $assets}'; + } +} + +/// generated route for +/// [CropImagePage] +class CropImageRoute extends PageRouteInfo { + CropImageRoute({ + Key? key, + required Image image, + required Asset asset, + List? children, + }) : super( + CropImageRoute.name, + args: CropImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); + + static const String name = 'CropImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return CropImagePage(key: args.key, image: args.image, asset: args.asset); + }, + ); +} + +class CropImageRouteArgs { + const CropImageRouteArgs({ + this.key, + required this.image, + required this.asset, + }); + + final Key? key; + + final Image image; + + final Asset asset; + + @override + String toString() { + return 'CropImageRouteArgs{key: $key, image: $image, asset: $asset}'; + } +} + +/// generated route for +/// [DownloadInfoPage] +class DownloadInfoRoute extends PageRouteInfo { + const DownloadInfoRoute({List? children}) + : super(DownloadInfoRoute.name, initialChildren: children); + + static const String name = 'DownloadInfoRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DownloadInfoPage(); + }, + ); +} + +/// generated route for +/// [DriftActivitiesPage] +class DriftActivitiesRoute extends PageRouteInfo { + DriftActivitiesRoute({ + Key? key, + required RemoteAlbum album, + String? assetId, + String? assetName, + List? children, + }) : super( + DriftActivitiesRoute.name, + args: DriftActivitiesRouteArgs( + key: key, + album: album, + assetId: assetId, + assetName: assetName, + ), + initialChildren: children, + ); + + static const String name = 'DriftActivitiesRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftActivitiesPage( + key: args.key, + album: args.album, + assetId: args.assetId, + assetName: args.assetName, + ); + }, + ); +} + +class DriftActivitiesRouteArgs { + const DriftActivitiesRouteArgs({ + this.key, + required this.album, + this.assetId, + this.assetName, + }); + + final Key? key; + + final RemoteAlbum album; + + final String? assetId; + + final String? assetName; + + @override + String toString() { + return 'DriftActivitiesRouteArgs{key: $key, album: $album, assetId: $assetId, assetName: $assetName}'; + } +} + +/// generated route for +/// [DriftAlbumOptionsPage] +class DriftAlbumOptionsRoute extends PageRouteInfo { + DriftAlbumOptionsRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + DriftAlbumOptionsRoute.name, + args: DriftAlbumOptionsRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'DriftAlbumOptionsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftAlbumOptionsPage(key: args.key, album: args.album); + }, + ); +} + +class DriftAlbumOptionsRouteArgs { + const DriftAlbumOptionsRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'DriftAlbumOptionsRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [DriftAlbumsPage] +class DriftAlbumsRoute extends PageRouteInfo { + const DriftAlbumsRoute({List? children}) + : super(DriftAlbumsRoute.name, initialChildren: children); + + static const String name = 'DriftAlbumsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftAlbumsPage(); + }, + ); +} + +/// generated route for +/// [DriftArchivePage] +class DriftArchiveRoute extends PageRouteInfo { + const DriftArchiveRoute({List? children}) + : super(DriftArchiveRoute.name, initialChildren: children); + + static const String name = 'DriftArchiveRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftArchivePage(); + }, + ); +} + +/// generated route for +/// [DriftAssetSelectionTimelinePage] +class DriftAssetSelectionTimelineRoute + extends PageRouteInfo { + DriftAssetSelectionTimelineRoute({ + Key? key, + Set lockedSelectionAssets = const {}, + List? children, + }) : super( + DriftAssetSelectionTimelineRoute.name, + args: DriftAssetSelectionTimelineRouteArgs( + key: key, + lockedSelectionAssets: lockedSelectionAssets, + ), + initialChildren: children, + ); + + static const String name = 'DriftAssetSelectionTimelineRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const DriftAssetSelectionTimelineRouteArgs(), + ); + return DriftAssetSelectionTimelinePage( + key: args.key, + lockedSelectionAssets: args.lockedSelectionAssets, + ); + }, + ); +} + +class DriftAssetSelectionTimelineRouteArgs { + const DriftAssetSelectionTimelineRouteArgs({ + this.key, + this.lockedSelectionAssets = const {}, + }); + + final Key? key; + + final Set lockedSelectionAssets; + + @override + String toString() { + return 'DriftAssetSelectionTimelineRouteArgs{key: $key, lockedSelectionAssets: $lockedSelectionAssets}'; + } +} + +/// generated route for +/// [DriftBackupAlbumSelectionPage] +class DriftBackupAlbumSelectionRoute extends PageRouteInfo { + const DriftBackupAlbumSelectionRoute({List? children}) + : super(DriftBackupAlbumSelectionRoute.name, initialChildren: children); + + static const String name = 'DriftBackupAlbumSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftBackupAlbumSelectionPage(); + }, + ); +} + +/// generated route for +/// [DriftBackupAssetDetailPage] +class DriftBackupAssetDetailRoute extends PageRouteInfo { + const DriftBackupAssetDetailRoute({List? children}) + : super(DriftBackupAssetDetailRoute.name, initialChildren: children); + + static const String name = 'DriftBackupAssetDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftBackupAssetDetailPage(); + }, + ); +} + +/// generated route for +/// [DriftBackupOptionsPage] +class DriftBackupOptionsRoute extends PageRouteInfo { + const DriftBackupOptionsRoute({List? children}) + : super(DriftBackupOptionsRoute.name, initialChildren: children); + + static const String name = 'DriftBackupOptionsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftBackupOptionsPage(); + }, + ); +} + +/// generated route for +/// [DriftBackupPage] +class DriftBackupRoute extends PageRouteInfo { + const DriftBackupRoute({List? children}) + : super(DriftBackupRoute.name, initialChildren: children); + + static const String name = 'DriftBackupRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftBackupPage(); + }, + ); +} + +/// generated route for +/// [DriftCreateAlbumPage] +class DriftCreateAlbumRoute extends PageRouteInfo { + const DriftCreateAlbumRoute({List? children}) + : super(DriftCreateAlbumRoute.name, initialChildren: children); + + static const String name = 'DriftCreateAlbumRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftCreateAlbumPage(); + }, + ); +} + +/// generated route for +/// [DriftCropImagePage] +class DriftCropImageRoute extends PageRouteInfo { + DriftCropImageRoute({ + Key? key, + required Image image, + required BaseAsset asset, + List? children, + }) : super( + DriftCropImageRoute.name, + args: DriftCropImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); + + static const String name = 'DriftCropImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftCropImagePage( + key: args.key, + image: args.image, + asset: args.asset, + ); + }, + ); +} + +class DriftCropImageRouteArgs { + const DriftCropImageRouteArgs({ + this.key, + required this.image, + required this.asset, + }); + + final Key? key; + + final Image image; + + final BaseAsset asset; + + @override + String toString() { + return 'DriftCropImageRouteArgs{key: $key, image: $image, asset: $asset}'; + } +} + +/// generated route for +/// [DriftEditImagePage] +class DriftEditImageRoute extends PageRouteInfo { + DriftEditImageRoute({ + Key? key, + required BaseAsset asset, + required Image image, + required bool isEdited, + List? children, + }) : super( + DriftEditImageRoute.name, + args: DriftEditImageRouteArgs( + key: key, + asset: asset, + image: image, + isEdited: isEdited, + ), + initialChildren: children, + ); + + static const String name = 'DriftEditImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftEditImagePage( + key: args.key, + asset: args.asset, + image: args.image, + isEdited: args.isEdited, + ); + }, + ); +} + +class DriftEditImageRouteArgs { + const DriftEditImageRouteArgs({ + this.key, + required this.asset, + required this.image, + required this.isEdited, + }); + + final Key? key; + + final BaseAsset asset; + + final Image image; + + final bool isEdited; + + @override + String toString() { + return 'DriftEditImageRouteArgs{key: $key, asset: $asset, image: $image, isEdited: $isEdited}'; + } +} + +/// generated route for +/// [DriftFavoritePage] +class DriftFavoriteRoute extends PageRouteInfo { + const DriftFavoriteRoute({List? children}) + : super(DriftFavoriteRoute.name, initialChildren: children); + + static const String name = 'DriftFavoriteRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftFavoritePage(); + }, + ); +} + +/// generated route for +/// [DriftFilterImagePage] +class DriftFilterImageRoute extends PageRouteInfo { + DriftFilterImageRoute({ + Key? key, + required Image image, + required BaseAsset asset, + List? children, + }) : super( + DriftFilterImageRoute.name, + args: DriftFilterImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); + + static const String name = 'DriftFilterImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftFilterImagePage( + key: args.key, + image: args.image, + asset: args.asset, + ); + }, + ); +} + +class DriftFilterImageRouteArgs { + const DriftFilterImageRouteArgs({ + this.key, + required this.image, + required this.asset, + }); + + final Key? key; + + final Image image; + + final BaseAsset asset; + + @override + String toString() { + return 'DriftFilterImageRouteArgs{key: $key, image: $image, asset: $asset}'; + } +} + +/// generated route for +/// [DriftLibraryPage] +class DriftLibraryRoute extends PageRouteInfo { + const DriftLibraryRoute({List? children}) + : super(DriftLibraryRoute.name, initialChildren: children); + + static const String name = 'DriftLibraryRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftLibraryPage(); + }, + ); +} + +/// generated route for +/// [DriftLocalAlbumsPage] +class DriftLocalAlbumsRoute extends PageRouteInfo { + const DriftLocalAlbumsRoute({List? children}) + : super(DriftLocalAlbumsRoute.name, initialChildren: children); + + static const String name = 'DriftLocalAlbumsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftLocalAlbumsPage(); + }, + ); +} + +/// generated route for +/// [DriftLockedFolderPage] +class DriftLockedFolderRoute extends PageRouteInfo { + const DriftLockedFolderRoute({List? children}) + : super(DriftLockedFolderRoute.name, initialChildren: children); + + static const String name = 'DriftLockedFolderRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftLockedFolderPage(); + }, + ); +} + +/// generated route for +/// [DriftMapPage] +class DriftMapRoute extends PageRouteInfo { + DriftMapRoute({ + Key? key, + LatLng? initialLocation, + List? children, + }) : super( + DriftMapRoute.name, + args: DriftMapRouteArgs(key: key, initialLocation: initialLocation), + initialChildren: children, + ); + + static const String name = 'DriftMapRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const DriftMapRouteArgs(), + ); + return DriftMapPage(key: args.key, initialLocation: args.initialLocation); + }, + ); +} + +class DriftMapRouteArgs { + const DriftMapRouteArgs({this.key, this.initialLocation}); + + final Key? key; + + final LatLng? initialLocation; + + @override + String toString() { + return 'DriftMapRouteArgs{key: $key, initialLocation: $initialLocation}'; + } +} + +/// generated route for +/// [DriftMemoryPage] +class DriftMemoryRoute extends PageRouteInfo { + DriftMemoryRoute({ + required List memories, + required int memoryIndex, + Key? key, + List? children, + }) : super( + DriftMemoryRoute.name, + args: DriftMemoryRouteArgs( + memories: memories, + memoryIndex: memoryIndex, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'DriftMemoryRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftMemoryPage( + memories: args.memories, + memoryIndex: args.memoryIndex, + key: args.key, + ); + }, + ); +} + +class DriftMemoryRouteArgs { + const DriftMemoryRouteArgs({ + required this.memories, + required this.memoryIndex, + this.key, + }); + + final List memories; + + final int memoryIndex; + + final Key? key; + + @override + String toString() { + return 'DriftMemoryRouteArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}'; + } +} + +/// generated route for +/// [DriftPartnerDetailPage] +class DriftPartnerDetailRoute + extends PageRouteInfo { + DriftPartnerDetailRoute({ + Key? key, + required PartnerUserDto partner, + List? children, + }) : super( + DriftPartnerDetailRoute.name, + args: DriftPartnerDetailRouteArgs(key: key, partner: partner), + initialChildren: children, + ); + + static const String name = 'DriftPartnerDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftPartnerDetailPage(key: args.key, partner: args.partner); + }, + ); +} + +class DriftPartnerDetailRouteArgs { + const DriftPartnerDetailRouteArgs({this.key, required this.partner}); + + final Key? key; + + final PartnerUserDto partner; + + @override + String toString() { + return 'DriftPartnerDetailRouteArgs{key: $key, partner: $partner}'; + } +} + +/// generated route for +/// [DriftPartnerPage] +class DriftPartnerRoute extends PageRouteInfo { + const DriftPartnerRoute({List? children}) + : super(DriftPartnerRoute.name, initialChildren: children); + + static const String name = 'DriftPartnerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftPartnerPage(); + }, + ); +} + +/// generated route for +/// [DriftPeopleCollectionPage] +class DriftPeopleCollectionRoute extends PageRouteInfo { + const DriftPeopleCollectionRoute({List? children}) + : super(DriftPeopleCollectionRoute.name, initialChildren: children); + + static const String name = 'DriftPeopleCollectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftPeopleCollectionPage(); + }, + ); +} + +/// generated route for +/// [DriftPersonPage] +class DriftPersonRoute extends PageRouteInfo { + DriftPersonRoute({ + Key? key, + required DriftPerson person, + List? children, + }) : super( + DriftPersonRoute.name, + args: DriftPersonRouteArgs(key: key, person: person), + initialChildren: children, + ); + + static const String name = 'DriftPersonRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftPersonPage(key: args.key, person: args.person); + }, + ); +} + +class DriftPersonRouteArgs { + const DriftPersonRouteArgs({this.key, required this.person}); + + final Key? key; + + final DriftPerson person; + + @override + String toString() { + return 'DriftPersonRouteArgs{key: $key, person: $person}'; + } +} + +/// generated route for +/// [DriftPlaceDetailPage] +class DriftPlaceDetailRoute extends PageRouteInfo { + DriftPlaceDetailRoute({ + Key? key, + required String place, + List? children, + }) : super( + DriftPlaceDetailRoute.name, + args: DriftPlaceDetailRouteArgs(key: key, place: place), + initialChildren: children, + ); + + static const String name = 'DriftPlaceDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftPlaceDetailPage(key: args.key, place: args.place); + }, + ); +} + +class DriftPlaceDetailRouteArgs { + const DriftPlaceDetailRouteArgs({this.key, required this.place}); + + final Key? key; + + final String place; + + @override + String toString() { + return 'DriftPlaceDetailRouteArgs{key: $key, place: $place}'; + } +} + +/// generated route for +/// [DriftPlacePage] +class DriftPlaceRoute extends PageRouteInfo { + DriftPlaceRoute({ + Key? key, + LatLng? currentLocation, + List? children, + }) : super( + DriftPlaceRoute.name, + args: DriftPlaceRouteArgs(key: key, currentLocation: currentLocation), + initialChildren: children, + ); + + static const String name = 'DriftPlaceRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const DriftPlaceRouteArgs(), + ); + return DriftPlacePage( + key: args.key, + currentLocation: args.currentLocation, + ); + }, + ); +} + +class DriftPlaceRouteArgs { + const DriftPlaceRouteArgs({this.key, this.currentLocation}); + + final Key? key; + + final LatLng? currentLocation; + + @override + String toString() { + return 'DriftPlaceRouteArgs{key: $key, currentLocation: $currentLocation}'; + } +} + +/// generated route for +/// [DriftRecentlyTakenPage] +class DriftRecentlyTakenRoute extends PageRouteInfo { + const DriftRecentlyTakenRoute({List? children}) + : super(DriftRecentlyTakenRoute.name, initialChildren: children); + + static const String name = 'DriftRecentlyTakenRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftRecentlyTakenPage(); + }, + ); +} + +/// generated route for +/// [DriftSearchPage] +class DriftSearchRoute extends PageRouteInfo { + const DriftSearchRoute({List? children}) + : super(DriftSearchRoute.name, initialChildren: children); + + static const String name = 'DriftSearchRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftSearchPage(); + }, + ); +} + +/// generated route for +/// [DriftTrashPage] +class DriftTrashRoute extends PageRouteInfo { + const DriftTrashRoute({List? children}) + : super(DriftTrashRoute.name, initialChildren: children); + + static const String name = 'DriftTrashRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftTrashPage(); + }, + ); +} + +/// generated route for +/// [DriftUploadDetailPage] +class DriftUploadDetailRoute extends PageRouteInfo { + const DriftUploadDetailRoute({List? children}) + : super(DriftUploadDetailRoute.name, initialChildren: children); + + static const String name = 'DriftUploadDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftUploadDetailPage(); + }, + ); +} + +/// generated route for +/// [DriftUserSelectionPage] +class DriftUserSelectionRoute + extends PageRouteInfo { + DriftUserSelectionRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + DriftUserSelectionRoute.name, + args: DriftUserSelectionRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'DriftUserSelectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return DriftUserSelectionPage(key: args.key, album: args.album); + }, + ); +} + +class DriftUserSelectionRouteArgs { + const DriftUserSelectionRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'DriftUserSelectionRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [DriftVideoPage] +class DriftVideoRoute extends PageRouteInfo { + const DriftVideoRoute({List? children}) + : super(DriftVideoRoute.name, initialChildren: children); + + static const String name = 'DriftVideoRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const DriftVideoPage(); + }, + ); +} + +/// generated route for +/// [EditImagePage] +class EditImageRoute extends PageRouteInfo { + EditImageRoute({ + Key? key, + required Asset asset, + required Image image, + required bool isEdited, + List? children, + }) : super( + EditImageRoute.name, + args: EditImageRouteArgs( + key: key, + asset: asset, + image: image, + isEdited: isEdited, + ), + initialChildren: children, + ); + + static const String name = 'EditImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return EditImagePage( + key: args.key, + asset: args.asset, + image: args.image, + isEdited: args.isEdited, + ); + }, + ); +} + +class EditImageRouteArgs { + const EditImageRouteArgs({ + this.key, + required this.asset, + required this.image, + required this.isEdited, + }); + + final Key? key; + + final Asset asset; + + final Image image; + + final bool isEdited; + + @override + String toString() { + return 'EditImageRouteArgs{key: $key, asset: $asset, image: $image, isEdited: $isEdited}'; + } +} + +/// generated route for +/// [FailedBackupStatusPage] +class FailedBackupStatusRoute extends PageRouteInfo { + const FailedBackupStatusRoute({List? children}) + : super(FailedBackupStatusRoute.name, initialChildren: children); + + static const String name = 'FailedBackupStatusRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const FailedBackupStatusPage(); + }, + ); +} + +/// generated route for +/// [FavoritesPage] +class FavoritesRoute extends PageRouteInfo { + const FavoritesRoute({List? children}) + : super(FavoritesRoute.name, initialChildren: children); + + static const String name = 'FavoritesRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const FavoritesPage(); + }, + ); +} + +/// generated route for +/// [FilterImagePage] +class FilterImageRoute extends PageRouteInfo { + FilterImageRoute({ + Key? key, + required Image image, + required Asset asset, + List? children, + }) : super( + FilterImageRoute.name, + args: FilterImageRouteArgs(key: key, image: image, asset: asset), + initialChildren: children, + ); + + static const String name = 'FilterImageRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return FilterImagePage( + key: args.key, + image: args.image, + asset: args.asset, + ); + }, + ); +} + +class FilterImageRouteArgs { + const FilterImageRouteArgs({ + this.key, + required this.image, + required this.asset, + }); + + final Key? key; + + final Image image; + + final Asset asset; + + @override + String toString() { + return 'FilterImageRouteArgs{key: $key, image: $image, asset: $asset}'; + } +} + +/// generated route for +/// [FolderPage] +class FolderRoute extends PageRouteInfo { + FolderRoute({ + Key? key, + RecursiveFolder? folder, + List? children, + }) : super( + FolderRoute.name, + args: FolderRouteArgs(key: key, folder: folder), + initialChildren: children, + ); + + static const String name = 'FolderRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const FolderRouteArgs(), + ); + return FolderPage(key: args.key, folder: args.folder); + }, + ); +} + +class FolderRouteArgs { + const FolderRouteArgs({this.key, this.folder}); + + final Key? key; + + final RecursiveFolder? folder; + + @override + String toString() { + return 'FolderRouteArgs{key: $key, folder: $folder}'; + } +} + +/// generated route for +/// [GalleryViewerPage] +class GalleryViewerRoute extends PageRouteInfo { + GalleryViewerRoute({ + Key? key, + required RenderList renderList, + int initialIndex = 0, + int heroOffset = 0, + bool showStack = false, + List? children, + }) : super( + GalleryViewerRoute.name, + args: GalleryViewerRouteArgs( + key: key, + renderList: renderList, + initialIndex: initialIndex, + heroOffset: heroOffset, + showStack: showStack, + ), + initialChildren: children, + ); + + static const String name = 'GalleryViewerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return GalleryViewerPage( + key: args.key, + renderList: args.renderList, + initialIndex: args.initialIndex, + heroOffset: args.heroOffset, + showStack: args.showStack, + ); + }, + ); +} + +class GalleryViewerRouteArgs { + const GalleryViewerRouteArgs({ + this.key, + required this.renderList, + this.initialIndex = 0, + this.heroOffset = 0, + this.showStack = false, + }); + + final Key? key; + + final RenderList renderList; + + final int initialIndex; + + final int heroOffset; + + final bool showStack; + + @override + String toString() { + return 'GalleryViewerRouteArgs{key: $key, renderList: $renderList, initialIndex: $initialIndex, heroOffset: $heroOffset, showStack: $showStack}'; + } +} + +/// generated route for +/// [HeaderSettingsPage] +class HeaderSettingsRoute extends PageRouteInfo { + const HeaderSettingsRoute({List? children}) + : super(HeaderSettingsRoute.name, initialChildren: children); + + static const String name = 'HeaderSettingsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const HeaderSettingsPage(); + }, + ); +} + +/// generated route for +/// [LibraryPage] +class LibraryRoute extends PageRouteInfo { + const LibraryRoute({List? children}) + : super(LibraryRoute.name, initialChildren: children); + + static const String name = 'LibraryRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const LibraryPage(); + }, + ); +} + +/// generated route for +/// [LocalAlbumsPage] +class LocalAlbumsRoute extends PageRouteInfo { + const LocalAlbumsRoute({List? children}) + : super(LocalAlbumsRoute.name, initialChildren: children); + + static const String name = 'LocalAlbumsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const LocalAlbumsPage(); + }, + ); +} + +/// generated route for +/// [LocalMediaSummaryPage] +class LocalMediaSummaryRoute extends PageRouteInfo { + const LocalMediaSummaryRoute({List? children}) + : super(LocalMediaSummaryRoute.name, initialChildren: children); + + static const String name = 'LocalMediaSummaryRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const LocalMediaSummaryPage(); + }, + ); +} + +/// generated route for +/// [LocalTimelinePage] +class LocalTimelineRoute extends PageRouteInfo { + LocalTimelineRoute({ + Key? key, + required LocalAlbum album, + List? children, + }) : super( + LocalTimelineRoute.name, + args: LocalTimelineRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'LocalTimelineRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return LocalTimelinePage(key: args.key, album: args.album); + }, + ); +} + +class LocalTimelineRouteArgs { + const LocalTimelineRouteArgs({this.key, required this.album}); + + final Key? key; + + final LocalAlbum album; + + @override + String toString() { + return 'LocalTimelineRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [LockedPage] +class LockedRoute extends PageRouteInfo { + const LockedRoute({List? children}) + : super(LockedRoute.name, initialChildren: children); + + static const String name = 'LockedRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const LockedPage(); + }, + ); +} + +/// generated route for +/// [LoginPage] +class LoginRoute extends PageRouteInfo { + const LoginRoute({List? children}) + : super(LoginRoute.name, initialChildren: children); + + static const String name = 'LoginRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const LoginPage(); + }, + ); +} + +/// generated route for +/// [MainTimelinePage] +class MainTimelineRoute extends PageRouteInfo { + const MainTimelineRoute({List? children}) + : super(MainTimelineRoute.name, initialChildren: children); + + static const String name = 'MainTimelineRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const MainTimelinePage(); + }, + ); +} + +/// generated route for +/// [MapLocationPickerPage] +class MapLocationPickerRoute extends PageRouteInfo { + MapLocationPickerRoute({ + Key? key, + LatLng initialLatLng = const LatLng(0, 0), + List? children, + }) : super( + MapLocationPickerRoute.name, + args: MapLocationPickerRouteArgs( + key: key, + initialLatLng: initialLatLng, + ), + initialChildren: children, + ); + + static const String name = 'MapLocationPickerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const MapLocationPickerRouteArgs(), + ); + return MapLocationPickerPage( + key: args.key, + initialLatLng: args.initialLatLng, + ); + }, + ); +} + +class MapLocationPickerRouteArgs { + const MapLocationPickerRouteArgs({ + this.key, + this.initialLatLng = const LatLng(0, 0), + }); + + final Key? key; + + final LatLng initialLatLng; + + @override + String toString() { + return 'MapLocationPickerRouteArgs{key: $key, initialLatLng: $initialLatLng}'; + } +} + +/// generated route for +/// [MapPage] +class MapRoute extends PageRouteInfo { + MapRoute({Key? key, LatLng? initialLocation, List? children}) + : super( + MapRoute.name, + args: MapRouteArgs(key: key, initialLocation: initialLocation), + initialChildren: children, + ); + + static const String name = 'MapRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const MapRouteArgs(), + ); + return MapPage(key: args.key, initialLocation: args.initialLocation); + }, + ); +} + +class MapRouteArgs { + const MapRouteArgs({this.key, this.initialLocation}); + + final Key? key; + + final LatLng? initialLocation; + + @override + String toString() { + return 'MapRouteArgs{key: $key, initialLocation: $initialLocation}'; + } +} + +/// generated route for +/// [MemoryPage] +class MemoryRoute extends PageRouteInfo { + MemoryRoute({ + required List memories, + required int memoryIndex, + Key? key, + List? children, + }) : super( + MemoryRoute.name, + args: MemoryRouteArgs( + memories: memories, + memoryIndex: memoryIndex, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'MemoryRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return MemoryPage( + memories: args.memories, + memoryIndex: args.memoryIndex, + key: args.key, + ); + }, + ); +} + +class MemoryRouteArgs { + const MemoryRouteArgs({ + required this.memories, + required this.memoryIndex, + this.key, + }); + + final List memories; + + final int memoryIndex; + + final Key? key; + + @override + String toString() { + return 'MemoryRouteArgs{memories: $memories, memoryIndex: $memoryIndex, key: $key}'; + } +} + +/// generated route for +/// [NativeVideoViewerPage] +class NativeVideoViewerRoute extends PageRouteInfo { + NativeVideoViewerRoute({ + Key? key, + required Asset asset, + required Widget image, + bool showControls = true, + int playbackDelayFactor = 1, + List? children, + }) : super( + NativeVideoViewerRoute.name, + args: NativeVideoViewerRouteArgs( + key: key, + asset: asset, + image: image, + showControls: showControls, + playbackDelayFactor: playbackDelayFactor, + ), + initialChildren: children, + ); + + static const String name = 'NativeVideoViewerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return NativeVideoViewerPage( + key: args.key, + asset: args.asset, + image: args.image, + showControls: args.showControls, + playbackDelayFactor: args.playbackDelayFactor, + ); + }, + ); +} + +class NativeVideoViewerRouteArgs { + const NativeVideoViewerRouteArgs({ + this.key, + required this.asset, + required this.image, + this.showControls = true, + this.playbackDelayFactor = 1, + }); + + final Key? key; + + final Asset asset; + + final Widget image; + + final bool showControls; + + final int playbackDelayFactor; + + @override + String toString() { + return 'NativeVideoViewerRouteArgs{key: $key, asset: $asset, image: $image, showControls: $showControls, playbackDelayFactor: $playbackDelayFactor}'; + } +} + +/// generated route for +/// [PartnerDetailPage] +class PartnerDetailRoute extends PageRouteInfo { + PartnerDetailRoute({ + Key? key, + required UserDto partner, + List? children, + }) : super( + PartnerDetailRoute.name, + args: PartnerDetailRouteArgs(key: key, partner: partner), + initialChildren: children, + ); + + static const String name = 'PartnerDetailRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return PartnerDetailPage(key: args.key, partner: args.partner); + }, + ); +} + +class PartnerDetailRouteArgs { + const PartnerDetailRouteArgs({this.key, required this.partner}); + + final Key? key; + + final UserDto partner; + + @override + String toString() { + return 'PartnerDetailRouteArgs{key: $key, partner: $partner}'; + } +} + +/// generated route for +/// [PartnerPage] +class PartnerRoute extends PageRouteInfo { + const PartnerRoute({List? children}) + : super(PartnerRoute.name, initialChildren: children); + + static const String name = 'PartnerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const PartnerPage(); + }, + ); +} + +/// generated route for +/// [PeopleCollectionPage] +class PeopleCollectionRoute extends PageRouteInfo { + const PeopleCollectionRoute({List? children}) + : super(PeopleCollectionRoute.name, initialChildren: children); + + static const String name = 'PeopleCollectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const PeopleCollectionPage(); + }, + ); +} + +/// generated route for +/// [PermissionOnboardingPage] +class PermissionOnboardingRoute extends PageRouteInfo { + const PermissionOnboardingRoute({List? children}) + : super(PermissionOnboardingRoute.name, initialChildren: children); + + static const String name = 'PermissionOnboardingRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const PermissionOnboardingPage(); + }, + ); +} + +/// generated route for +/// [PersonResultPage] +class PersonResultRoute extends PageRouteInfo { + PersonResultRoute({ + Key? key, + required String personId, + required String personName, + List? children, + }) : super( + PersonResultRoute.name, + args: PersonResultRouteArgs( + key: key, + personId: personId, + personName: personName, + ), + initialChildren: children, + ); + + static const String name = 'PersonResultRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return PersonResultPage( + key: args.key, + personId: args.personId, + personName: args.personName, + ); + }, + ); +} + +class PersonResultRouteArgs { + const PersonResultRouteArgs({ + this.key, + required this.personId, + required this.personName, + }); + + final Key? key; + + final String personId; + + final String personName; + + @override + String toString() { + return 'PersonResultRouteArgs{key: $key, personId: $personId, personName: $personName}'; + } +} + +/// generated route for +/// [PhotosPage] +class PhotosRoute extends PageRouteInfo { + const PhotosRoute({List? children}) + : super(PhotosRoute.name, initialChildren: children); + + static const String name = 'PhotosRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const PhotosPage(); + }, + ); +} + +/// generated route for +/// [PinAuthPage] +class PinAuthRoute extends PageRouteInfo { + PinAuthRoute({ + Key? key, + bool createPinCode = false, + List? children, + }) : super( + PinAuthRoute.name, + args: PinAuthRouteArgs(key: key, createPinCode: createPinCode), + initialChildren: children, + ); + + static const String name = 'PinAuthRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const PinAuthRouteArgs(), + ); + return PinAuthPage(key: args.key, createPinCode: args.createPinCode); + }, + ); +} + +class PinAuthRouteArgs { + const PinAuthRouteArgs({this.key, this.createPinCode = false}); + + final Key? key; + + final bool createPinCode; + + @override + String toString() { + return 'PinAuthRouteArgs{key: $key, createPinCode: $createPinCode}'; + } +} + +/// generated route for +/// [PlacesCollectionPage] +class PlacesCollectionRoute extends PageRouteInfo { + PlacesCollectionRoute({ + Key? key, + LatLng? currentLocation, + List? children, + }) : super( + PlacesCollectionRoute.name, + args: PlacesCollectionRouteArgs( + key: key, + currentLocation: currentLocation, + ), + initialChildren: children, + ); + + static const String name = 'PlacesCollectionRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const PlacesCollectionRouteArgs(), + ); + return PlacesCollectionPage( + key: args.key, + currentLocation: args.currentLocation, + ); + }, + ); +} + +class PlacesCollectionRouteArgs { + const PlacesCollectionRouteArgs({this.key, this.currentLocation}); + + final Key? key; + + final LatLng? currentLocation; + + @override + String toString() { + return 'PlacesCollectionRouteArgs{key: $key, currentLocation: $currentLocation}'; + } +} + +/// generated route for +/// [ProfilePictureCropPage] +class ProfilePictureCropRoute + extends PageRouteInfo { + ProfilePictureCropRoute({ + Key? key, + required BaseAsset asset, + List? children, + }) : super( + ProfilePictureCropRoute.name, + args: ProfilePictureCropRouteArgs(key: key, asset: asset), + initialChildren: children, + ); + + static const String name = 'ProfilePictureCropRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return ProfilePictureCropPage(key: args.key, asset: args.asset); + }, + ); +} + +class ProfilePictureCropRouteArgs { + const ProfilePictureCropRouteArgs({this.key, required this.asset}); + + final Key? key; + + final BaseAsset asset; + + @override + String toString() { + return 'ProfilePictureCropRouteArgs{key: $key, asset: $asset}'; + } +} + +/// generated route for +/// [RecentlyTakenPage] +class RecentlyTakenRoute extends PageRouteInfo { + const RecentlyTakenRoute({List? children}) + : super(RecentlyTakenRoute.name, initialChildren: children); + + static const String name = 'RecentlyTakenRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const RecentlyTakenPage(); + }, + ); +} + +/// generated route for +/// [RemoteAlbumPage] +class RemoteAlbumRoute extends PageRouteInfo { + RemoteAlbumRoute({ + Key? key, + required RemoteAlbum album, + List? children, + }) : super( + RemoteAlbumRoute.name, + args: RemoteAlbumRouteArgs(key: key, album: album), + initialChildren: children, + ); + + static const String name = 'RemoteAlbumRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return RemoteAlbumPage(key: args.key, album: args.album); + }, + ); +} + +class RemoteAlbumRouteArgs { + const RemoteAlbumRouteArgs({this.key, required this.album}); + + final Key? key; + + final RemoteAlbum album; + + @override + String toString() { + return 'RemoteAlbumRouteArgs{key: $key, album: $album}'; + } +} + +/// generated route for +/// [RemoteMediaSummaryPage] +class RemoteMediaSummaryRoute extends PageRouteInfo { + const RemoteMediaSummaryRoute({List? 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 { + SearchRoute({ + Key? key, + SearchFilter? prefilter, + List? children, + }) : super( + SearchRoute.name, + args: SearchRouteArgs(key: key, prefilter: prefilter), + initialChildren: children, + ); + + static const String name = 'SearchRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const SearchRouteArgs(), + ); + return SearchPage(key: args.key, prefilter: args.prefilter); + }, + ); +} + +class SearchRouteArgs { + const SearchRouteArgs({this.key, this.prefilter}); + + final Key? key; + + final SearchFilter? prefilter; + + @override + String toString() { + return 'SearchRouteArgs{key: $key, prefilter: $prefilter}'; + } +} + +/// generated route for +/// [SettingsPage] +class SettingsRoute extends PageRouteInfo { + const SettingsRoute({List? children}) + : super(SettingsRoute.name, initialChildren: children); + + static const String name = 'SettingsRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const SettingsPage(); + }, + ); +} + +/// generated route for +/// [SettingsSubPage] +class SettingsSubRoute extends PageRouteInfo { + SettingsSubRoute({ + required SettingSection section, + Key? key, + List? children, + }) : super( + SettingsSubRoute.name, + args: SettingsSubRouteArgs(section: section, key: key), + initialChildren: children, + ); + + static const String name = 'SettingsSubRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return SettingsSubPage(args.section, key: args.key); + }, + ); +} + +class SettingsSubRouteArgs { + const SettingsSubRouteArgs({required this.section, this.key}); + + final SettingSection section; + + final Key? key; + + @override + String toString() { + return 'SettingsSubRouteArgs{section: $section, key: $key}'; + } +} + +/// generated route for +/// [ShareIntentPage] +class ShareIntentRoute extends PageRouteInfo { + ShareIntentRoute({ + Key? key, + required List attachments, + List? children, + }) : super( + ShareIntentRoute.name, + args: ShareIntentRouteArgs(key: key, attachments: attachments), + initialChildren: children, + ); + + static const String name = 'ShareIntentRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return ShareIntentPage(key: args.key, attachments: args.attachments); + }, + ); +} + +class ShareIntentRouteArgs { + const ShareIntentRouteArgs({this.key, required this.attachments}); + + final Key? key; + + final List attachments; + + @override + String toString() { + return 'ShareIntentRouteArgs{key: $key, attachments: $attachments}'; + } +} + +/// generated route for +/// [SharedLinkEditPage] +class SharedLinkEditRoute extends PageRouteInfo { + SharedLinkEditRoute({ + Key? key, + SharedLink? existingLink, + List? assetsList, + String? albumId, + List? children, + }) : super( + SharedLinkEditRoute.name, + args: SharedLinkEditRouteArgs( + key: key, + existingLink: existingLink, + assetsList: assetsList, + albumId: albumId, + ), + initialChildren: children, + ); + + static const String name = 'SharedLinkEditRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + final args = data.argsAs( + orElse: () => const SharedLinkEditRouteArgs(), + ); + return SharedLinkEditPage( + key: args.key, + existingLink: args.existingLink, + assetsList: args.assetsList, + albumId: args.albumId, + ); + }, + ); +} + +class SharedLinkEditRouteArgs { + const SharedLinkEditRouteArgs({ + this.key, + this.existingLink, + this.assetsList, + this.albumId, + }); + + final Key? key; + + final SharedLink? existingLink; + + final List? assetsList; + + final String? albumId; + + @override + String toString() { + return 'SharedLinkEditRouteArgs{key: $key, existingLink: $existingLink, assetsList: $assetsList, albumId: $albumId}'; + } +} + +/// generated route for +/// [SharedLinkPage] +class SharedLinkRoute extends PageRouteInfo { + const SharedLinkRoute({List? children}) + : super(SharedLinkRoute.name, initialChildren: children); + + static const String name = 'SharedLinkRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const SharedLinkPage(); + }, + ); +} + +/// generated route for +/// [SplashScreenPage] +class SplashScreenRoute extends PageRouteInfo { + const SplashScreenRoute({List? children}) + : super(SplashScreenRoute.name, initialChildren: children); + + static const String name = 'SplashScreenRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const SplashScreenPage(); + }, + ); +} + +/// generated route for +/// [SyncStatusPage] +class SyncStatusRoute extends PageRouteInfo { + const SyncStatusRoute({List? children}) + : super(SyncStatusRoute.name, initialChildren: children); + + static const String name = 'SyncStatusRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const SyncStatusPage(); + }, + ); +} + +/// generated route for +/// [TabControllerPage] +class TabControllerRoute extends PageRouteInfo { + const TabControllerRoute({List? children}) + : super(TabControllerRoute.name, initialChildren: children); + + static const String name = 'TabControllerRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const TabControllerPage(); + }, + ); +} + +/// generated route for +/// [TabShellPage] +class TabShellRoute extends PageRouteInfo { + const TabShellRoute({List? children}) + : super(TabShellRoute.name, initialChildren: children); + + static const String name = 'TabShellRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const TabShellPage(); + }, + ); +} + +/// generated route for +/// [TrashPage] +class TrashRoute extends PageRouteInfo { + const TrashRoute({List? children}) + : super(TrashRoute.name, initialChildren: children); + + static const String name = 'TrashRoute'; + + static PageInfo page = PageInfo( + name, + builder: (data) { + return const TrashPage(); + }, + ); +} diff --git a/mobile/lib/services/action.service.dart b/mobile/lib/services/action.service.dart index fcfb96d988..c435bf9d79 100644 --- a/mobile/lib/services/action.service.dart +++ b/mobile/lib/services/action.service.dart @@ -1,30 +1,209 @@ import 'dart:async'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/download.repository.dart'; +import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/timezone.dart'; +import 'package:immich_mobile/widgets/common/date_time_picker.dart'; +import 'package:immich_mobile/widgets/common/location_picker.dart'; +import 'package:maplibre_gl/maplibre_gl.dart' as maplibre; +import 'package:riverpod_annotation/riverpod_annotation.dart'; final actionServiceProvider = Provider( - (ref) => ActionService(ref.watch(assetApiRepositoryProvider), ref.watch(driftProvider).remoteAssetRepository), + (ref) => ActionService( + ref.watch(assetApiRepositoryProvider), + ref.watch(remoteAssetRepositoryProvider), + ref.watch(localAssetRepository), + ref.watch(driftAlbumApiRepositoryProvider), + ref.watch(remoteAlbumRepository), + ref.watch(trashedLocalAssetRepository), + ref.watch(assetMediaRepositoryProvider), + ref.watch(downloadRepositoryProvider), + ), ); class ActionService { final AssetApiRepository _assetApiRepository; final RemoteAssetRepository _remoteAssetRepository; + final DriftLocalAssetRepository _localAssetRepository; + final DriftAlbumApiRepository _albumApiRepository; + final DriftRemoteAlbumRepository _remoteAlbumRepository; + final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository; + final AssetMediaRepository _assetMediaRepository; + final DownloadRepository _downloadRepository; - const ActionService(this._assetApiRepository, this._remoteAssetRepository); + const ActionService( + this._assetApiRepository, + this._remoteAssetRepository, + this._localAssetRepository, + this._albumApiRepository, + this._remoteAlbumRepository, + this._trashedLocalAssetRepository, + this._assetMediaRepository, + this._downloadRepository, + ); - Future emptyTrash(String userId) async { - final count = await _assetApiRepository.emptyTrash(); - await _remoteAssetRepository.emptyTrash(userId); - return count; + Future shareLink(List remoteIds, BuildContext context) async { + unawaited(context.pushRoute(SharedLinkEditRoute(assetsList: remoteIds))); } - Future restoreAllTrash(String userId) async { - final count = await _assetApiRepository.restoreAllTrash(); - await _remoteAssetRepository.restoreAllTrash(userId); - return count; + Future favorite(List remoteIds) async { + await _assetApiRepository.updateFavorite(remoteIds, true); + await _remoteAssetRepository.updateFavorite(remoteIds, true); + } + + Future unFavorite(List remoteIds) async { + await _assetApiRepository.updateFavorite(remoteIds, false); + await _remoteAssetRepository.updateFavorite(remoteIds, false); + } + + Future archive(List remoteIds) async { + await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.archive); + await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.archive); + } + + Future unArchive(List remoteIds) async { + await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.timeline); + await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.timeline); + } + + Future moveToLockFolder(List remoteIds, List localIds) async { + await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.locked); + await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.locked); + + // Ask user if they want to delete local copies + if (localIds.isNotEmpty) { + await _deleteLocalAssets(localIds); + } + } + + Future removeFromLockFolder(List remoteIds) async { + await _assetApiRepository.updateVisibility(remoteIds, AssetVisibilityEnum.timeline); + await _remoteAssetRepository.updateVisibility(remoteIds, AssetVisibility.timeline); + } + + Future trash(List remoteIds) async { + await _assetApiRepository.delete(remoteIds, false); + await _remoteAssetRepository.trash(remoteIds); + } + + Future restoreTrash(List ids) async { + await _assetApiRepository.restoreTrash(ids); + await _remoteAssetRepository.restoreTrash(ids); + } + + Future trashRemoteAndDeleteLocal(List remoteIds, List localIds) async { + await _assetApiRepository.delete(remoteIds, false); + await _remoteAssetRepository.trash(remoteIds); + + if (localIds.isNotEmpty) { + await _deleteLocalAssets(localIds); + } + } + + Future deleteRemoteAndLocal(List remoteIds, List localIds) async { + await _assetApiRepository.delete(remoteIds, true); + await _remoteAssetRepository.delete(remoteIds); + + if (localIds.isNotEmpty) { + await _deleteLocalAssets(localIds); + } + } + + Future deleteLocal(List localIds) async { + return await _deleteLocalAssets(localIds); + } + + Future editLocation(List remoteIds, BuildContext context) async { + maplibre.LatLng? initialLatLng; + if (remoteIds.length == 1) { + final exif = await _remoteAssetRepository.getExif(remoteIds[0]); + + if (exif?.latitude != null && exif?.longitude != null) { + initialLatLng = maplibre.LatLng(exif!.latitude!, exif.longitude!); + } + } + + final location = await showLocationPicker(context: context, initialLatLng: initialLatLng); + + if (location == null) { + return false; + } + + await _assetApiRepository.updateLocation(remoteIds, location); + await _remoteAssetRepository.updateLocation(remoteIds, location); + + return true; + } + + Future editDateTime(List remoteIds, BuildContext context) async { + DateTime? initialDate; + String? timeZone; + Duration? offset; + + if (remoteIds.length == 1) { + final assetId = remoteIds.first; + final asset = await _remoteAssetRepository.get(assetId); + if (asset == null) { + return false; + } + + final exifData = await _remoteAssetRepository.getExif(assetId); + + // Use EXIF timezone information if available (matching web app and display behavior) + DateTime dt = asset.createdAt.toLocal(); + offset = dt.timeZoneOffset; + + if (exifData?.dateTimeOriginal != null) { + timeZone = exifData!.timeZone; + (dt, offset) = applyTimezoneOffset(dateTime: exifData.dateTimeOriginal!, timeZone: exifData.timeZone); + } + + initialDate = dt; + } + + final dateTime = await showDateTimePicker( + context: context, + initialDateTime: initialDate, + initialTZ: timeZone, + initialTZOffset: offset, + ); + + if (dateTime == null) { + return false; + } + + // convert dateTime to DateTime object + final parsedDateTime = DateTime.parse(dateTime); + + await _assetApiRepository.updateDateTime(remoteIds, parsedDateTime); + await _remoteAssetRepository.updateDateTime(remoteIds, parsedDateTime); + + return true; + } + + Future removeFromAlbum(List remoteIds, String albumId) async { + final result = await _albumApiRepository.removeAssets(albumId, remoteIds); + if (result.removed.isNotEmpty) { + await _remoteAlbumRepository.removeAssets(albumId, result.removed); + } + return result.removed.length; } Future updateDescription(String assetId, String description) async { @@ -35,11 +214,48 @@ class ActionService { return true; } - Future updateRating(String assetId, int? rating) async { + Future updateRating(String assetId, int rating) async { // update remote first, then local to ensure consistency await _assetApiRepository.updateRating(assetId, rating); await _remoteAssetRepository.updateRating(assetId, rating); return true; } + + Future stack(String userId, List remoteIds) async { + final stack = await _assetApiRepository.stack(remoteIds); + await _remoteAssetRepository.stack(userId, stack); + } + + Future unStack(List stackIds) async { + await _remoteAssetRepository.unStack(stackIds); + await _assetApiRepository.unStack(stackIds); + } + + Future shareAssets(List assets, BuildContext context, {Completer? cancelCompleter}) { + return _assetMediaRepository.shareAssets(assets, context, cancelCompleter: cancelCompleter); + } + + Future> downloadAll(List assets) { + return _downloadRepository.downloadAllAssets(assets); + } + + Future setAlbumCover(String albumId, String assetId) async { + final updatedAlbum = await _albumApiRepository.updateAlbum(albumId, thumbnailAssetId: assetId); + await _remoteAlbumRepository.update(updatedAlbum); + return true; + } + + Future _deleteLocalAssets(List localIds) async { + final deletedIds = await _assetMediaRepository.deleteAll(localIds); + if (deletedIds.isEmpty) { + return 0; + } + if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) { + await _trashedLocalAssetRepository.applyTrashedAssets(deletedIds); + } else { + await _localAssetRepository.delete(deletedIds); + } + return deletedIds.length; + } } diff --git a/mobile/lib/services/activity.service.dart b/mobile/lib/services/activity.service.dart index 0d4709d0d5..382a7fe107 100644 --- a/mobile/lib/services/activity.service.dart +++ b/mobile/lib/services/activity.service.dart @@ -9,6 +9,7 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da import 'package:immich_mobile/repositories/activity_api.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:logging/logging.dart'; +import 'package:immich_mobile/entities/store.entity.dart' as immich_store; class ActivityService with ErrorLoggerMixin { final ActivityApiRepository _activityApiRepository; @@ -28,6 +29,14 @@ class ActivityService with ErrorLoggerMixin { ); } + Future getStatistics(String albumId, {String? assetId}) async { + return logError( + () => _activityApiRepository.getStats(albumId, assetId: assetId), + defaultValue: const ActivityStats(comments: 0), + errorMessage: "Failed to statistics for album $albumId", + ); + } + Future removeActivity(String id) async { return logError( () async { @@ -51,16 +60,20 @@ class ActivityService with ErrorLoggerMixin { } Future buildAssetViewerRoute(String assetId, WidgetRef ref) async { - final asset = await _assetService.getRemoteAsset(assetId); - if (asset == null) { - return null; + if (immich_store.Store.isBetaTimelineEnabled) { + final asset = await _assetService.getRemoteAsset(assetId); + if (asset == null) { + return null; + } + + AssetViewer.setAsset(ref, asset); + return AssetViewerRoute( + initialIndex: 0, + timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.albumActivities), + currentAlbum: ref.read(currentRemoteAlbumProvider), + ); } - AssetViewer.setAsset(ref, asset); - return AssetViewerRoute( - initialIndex: 0, - timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.albumActivities), - currentAlbum: ref.read(currentRemoteAlbumProvider), - ); + return null; } } diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart new file mode 100644 index 0000000000..8d77b569e6 --- /dev/null +++ b/mobile/lib/services/album.service.dart @@ -0,0 +1,425 @@ +import 'dart:async'; +import 'dart:collection'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart' as entity; +import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; +import 'package:immich_mobile/models/albums/album_search.model.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/album.repository.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; +import 'package:immich_mobile/services/entity.service.dart'; +import 'package:immich_mobile/services/sync.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:logging/logging.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +final albumServiceProvider = Provider( + (ref) => AlbumService( + ref.watch(syncServiceProvider), + ref.watch(userServiceProvider), + ref.watch(entityServiceProvider), + ref.watch(albumRepositoryProvider), + ref.watch(assetRepositoryProvider), + ref.watch(backupAlbumRepositoryProvider), + ref.watch(albumMediaRepositoryProvider), + ref.watch(albumApiRepositoryProvider), + ), +); + +class AlbumService { + final SyncService _syncService; + final UserService _userService; + final EntityService _entityService; + final AlbumRepository _albumRepository; + final AssetRepository _assetRepository; + final BackupAlbumRepository _backupAlbumRepository; + final AlbumMediaRepository _albumMediaRepository; + final AlbumApiRepository _albumApiRepository; + final Logger _log = Logger('AlbumService'); + Completer _localCompleter = Completer()..complete(false); + Completer _remoteCompleter = Completer()..complete(false); + + AlbumService( + this._syncService, + this._userService, + this._entityService, + this._albumRepository, + this._assetRepository, + this._backupAlbumRepository, + this._albumMediaRepository, + this._albumApiRepository, + ); + + /// Checks all selected device albums for changes of albums and their assets + /// Updates the local database and returns `true` if there were any changes + Future refreshDeviceAlbums() async { + if (!_localCompleter.isCompleted) { + // guard against concurrent calls + _log.info("refreshDeviceAlbums is already in progress"); + return _localCompleter.future; + } + _localCompleter = Completer(); + final Stopwatch sw = Stopwatch()..start(); + bool changes = false; + try { + final (selectedIds, excludedIds, onDevice) = await ( + _backupAlbumRepository.getIdsBySelection(BackupSelection.select).then((value) => value.toSet()), + _backupAlbumRepository.getIdsBySelection(BackupSelection.exclude).then((value) => value.toSet()), + _albumMediaRepository.getAll(), + ).wait; + _log.info("Found ${onDevice.length} device albums"); + if (selectedIds.isEmpty) { + final numLocal = await _albumRepository.count(local: true); + if (numLocal > 0) { + await _syncService.removeAllLocalAlbumsAndAssets(); + } + return false; + } + Set? excludedAssets; + if (excludedIds.isNotEmpty) { + if (Platform.isIOS) { + // iOS and Android device album working principle differ significantly + // on iOS, an asset can be in multiple albums + // on Android, an asset can only be in exactly one album (folder!) at the same time + // thus, on Android, excluding an album can be done by ignoring that album + // however, on iOS, it it necessary to load the assets from all excluded + // albums and check every asset from any selected album against the set + // of excluded assets + excludedAssets = await _loadExcludedAssetIds(onDevice, excludedIds); + _log.info("Found ${excludedAssets.length} assets to exclude"); + } + // remove all excluded albums + onDevice.removeWhere((e) => excludedIds.contains(e.localId)); + _log.info("Ignoring ${excludedIds.length} excluded albums resulting in ${onDevice.length} device albums"); + } + + final allAlbum = onDevice.firstWhereOrNull((album) => album.isAll); + final hasAll = allAlbum != null && selectedIds.contains(allAlbum.localId); + if (hasAll) { + if (Platform.isAndroid) { + // remove the virtual "Recent" album and keep and individual albums + // on Android, the virtual "Recent" `lastModified` value is always null + onDevice.removeWhere((album) => album.isAll); + _log.info("'Recents' is selected, keeping all individual albums"); + } + } else { + // keep only the explicitly selected albums + onDevice.removeWhere((album) => !selectedIds.contains(album.localId)); + _log.info("'Recents' is not selected, keeping only selected albums"); + } + changes = await _syncService.syncLocalAlbumAssetsToDb(onDevice, excludedAssets); + _log.info("Syncing completed. Changes: $changes"); + } finally { + _localCompleter.complete(changes); + } + dPrint(() => "refreshDeviceAlbums took ${sw.elapsedMilliseconds}ms"); + return changes; + } + + Future> _loadExcludedAssetIds(List albums, Set excludedAlbumIds) async { + final Set result = HashSet(); + for (final batchAlbums in albums.where((album) => excludedAlbumIds.contains(album.localId)).slices(5)) { + await batchAlbums + .map((album) => _albumMediaRepository.getAssetIds(album.localId!).then((assetIds) => result.addAll(assetIds))) + .wait; + } + return result; + } + + /// Checks remote albums (owned if `isShared` is false) for changes, + /// updates the local database and returns `true` if there were any changes + Future refreshRemoteAlbums() async { + if (!_remoteCompleter.isCompleted) { + // guard against concurrent calls + return _remoteCompleter.future; + } + _remoteCompleter = Completer(); + final Stopwatch sw = Stopwatch()..start(); + bool changes = false; + try { + final users = await _syncService.getUsersFromServer(); + if (users != null) { + await _syncService.syncUsersFromServer(users); + } + final (sharedAlbum, ownedAlbum) = await ( + // Note: `shared: true` is required to get albums that don't belong to + // us due to unusual behaviour on the API but this will also return our + // own shared albums + _albumApiRepository.getAll(shared: true), + // Passing null (or nothing) for `shared` returns only albums that + // explicitly belong to us + _albumApiRepository.getAll(shared: null), + ).wait; + + final albums = HashSet(equals: (a, b) => a.remoteId == b.remoteId, hashCode: (a) => a.remoteId.hashCode); + + albums.addAll(sharedAlbum); + albums.addAll(ownedAlbum); + + changes = await _syncService.syncRemoteAlbumsToDb(albums.toList()); + } finally { + _remoteCompleter.complete(changes); + } + dPrint(() => "refreshRemoteAlbums took ${sw.elapsedMilliseconds}ms"); + return changes; + } + + Future createAlbum( + String albumName, + Iterable assets, [ + Iterable sharedUsers = const [], + ]) async { + final Album album = await _albumApiRepository.create( + albumName, + assetIds: assets.map((asset) => asset.remoteId!), + sharedUserIds: sharedUsers.map((user) => user.id), + ); + await _entityService.fillAlbumWithDatabaseEntities(album); + return _albumRepository.create(album); + } + + /* + * Creates names like Untitled, Untitled (1), Untitled (2), ... + */ + Future _getNextAlbumName() async { + const baseName = "Untitled"; + for (int round = 0; ; round++) { + final proposedName = "$baseName${round == 0 ? "" : " ($round)"}"; + + if (null == await _albumRepository.getByName(proposedName, owner: true)) { + return proposedName; + } + } + } + + Future createAlbumWithGeneratedName(Iterable assets) async { + return createAlbum(await _getNextAlbumName(), assets, []); + } + + Future addAssets(Album album, Iterable assets) async { + try { + final result = await _albumApiRepository.addAssets(album.remoteId!, assets.map((asset) => asset.remoteId!)); + + final List addedAssets = result.added + .map((id) => assets.firstWhere((asset) => asset.remoteId == id)) + .toList(); + + await _updateAssets(album.id, add: addedAssets); + + return AlbumAddAssetsResponse(alreadyInAlbum: result.duplicates, successfullyAdded: addedAssets.length); + } catch (e) { + dPrint(() => "Error addAssets ${e.toString()}"); + } + return null; + } + + Future _updateAssets(int albumId, {List add = const [], List remove = const []}) => + _albumRepository.transaction(() async { + final album = await _albumRepository.get(albumId); + if (album == null) return; + await _albumRepository.addAssets(album, add); + await _albumRepository.removeAssets(album, remove); + await _albumRepository.recalculateMetadata(album); + await _albumRepository.update(album); + }); + + Future setActivityStatus(Album album, bool enabled) async { + try { + final updatedAlbum = await _albumApiRepository.update(album.remoteId!, activityEnabled: enabled); + album.activityEnabled = updatedAlbum.activityEnabled; + await _albumRepository.update(album); + return true; + } catch (e) { + dPrint(() => "Error setActivityEnabled ${e.toString()}"); + } + return false; + } + + Future deleteAlbum(Album album) async { + try { + final userId = _userService.getMyUser().id; + if (album.owner.value?.isarId == fastHash(userId)) { + await _albumApiRepository.delete(album.remoteId!); + } + if (album.shared) { + final foreignAssets = await _assetRepository.getByAlbum(album, notOwnedBy: [userId]); + await _albumRepository.delete(album.id); + + final List albums = await _albumRepository.getAll(shared: true); + final List existing = []; + for (Album album in albums) { + existing.addAll(await _assetRepository.getByAlbum(album, notOwnedBy: [userId])); + } + final List idsToRemove = _syncService.sharedAssetsToRemove(foreignAssets, existing); + if (idsToRemove.isNotEmpty) { + await _assetRepository.deleteByIds(idsToRemove); + } + } else { + await _albumRepository.delete(album.id); + } + return true; + } catch (e) { + dPrint(() => "Error deleteAlbum ${e.toString()}"); + } + return false; + } + + Future leaveAlbum(Album album) async { + try { + await _albumApiRepository.removeUser(album.remoteId!, userId: "me"); + return true; + } catch (e) { + dPrint(() => "Error leaveAlbum ${e.toString()}"); + return false; + } + } + + Future removeAsset(Album album, Iterable assets) async { + try { + final result = await _albumApiRepository.removeAssets(album.remoteId!, assets.map((asset) => asset.remoteId!)); + final toRemove = result.removed.map((id) => assets.firstWhere((asset) => asset.remoteId == id)); + await _updateAssets(album.id, remove: toRemove.toList()); + return true; + } catch (e) { + dPrint(() => "Error removeAssetFromAlbum ${e.toString()}"); + } + return false; + } + + Future removeUser(Album album, UserDto user) async { + try { + await _albumApiRepository.removeUser(album.remoteId!, userId: user.id); + + album.sharedUsers.remove(entity.User.fromDto(user)); + await _albumRepository.removeUsers(album, [user]); + final a = await _albumRepository.get(album.id); + // trigger watcher + await _albumRepository.update(a!); + + return true; + } catch (error) { + dPrint(() => "Error removeUser ${error.toString()}"); + return false; + } + } + + Future addUsers(Album album, List userIds) async { + try { + final updatedAlbum = await _albumApiRepository.addUsers(album.remoteId!, userIds); + + album.sharedUsers.addAll(updatedAlbum.remoteUsers); + album.shared = true; + + await _albumRepository.addUsers(album, album.sharedUsers.map((u) => u.toDto()).toList()); + await _albumRepository.update(album); + + return true; + } catch (error) { + dPrint(() => "Error addUsers ${error.toString()}"); + } + return false; + } + + Future changeTitleAlbum(Album album, String newAlbumTitle) async { + try { + final updatedAlbum = await _albumApiRepository.update(album.remoteId!, name: newAlbumTitle); + + album.name = updatedAlbum.name; + await _albumRepository.update(album); + return true; + } catch (e) { + dPrint(() => "Error changeTitleAlbum ${e.toString()}"); + return false; + } + } + + Future 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) { + dPrint(() => "Error changeDescriptionAlbum ${e.toString()}"); + return false; + } + } + + Future getAlbumByName(String name, {bool? remote, bool? shared, bool? owner}) => + _albumRepository.getByName(name, remote: remote, shared: shared, owner: owner); + + /// + /// Add the uploaded asset to the selected albums + /// + Future syncUploadAlbums(List albumNames, List assetIds) async { + for (final albumName in albumNames) { + Album? album = await getAlbumByName(albumName, remote: true, owner: true); + album ??= await createAlbum(albumName, []); + if (album != null && album.remoteId != null) { + await _albumApiRepository.addAssets(album.remoteId!, assetIds); + } + } + } + + Future> getAllRemoteAlbums() async { + return _albumRepository.getAll(remote: true); + } + + Future> getAllLocalAlbums() async { + return _albumRepository.getAll(remote: false); + } + + Stream> watchRemoteAlbums() { + return _albumRepository.watchRemoteAlbums(); + } + + Stream> watchLocalAlbums() { + return _albumRepository.watchLocalAlbums(); + } + + /// Get album by Isar ID + Future getAlbumById(int id) { + return _albumRepository.get(id); + } + + Future getAlbumByRemoteId(String remoteId) { + return _albumRepository.getByRemoteId(remoteId); + } + + Stream watchAlbum(int id) { + return _albumRepository.watchAlbum(id); + } + + Future> search(String searchTerm, QuickFilterMode filterMode) async { + return _albumRepository.search(searchTerm, filterMode); + } + + Future updateSortOrder(Album album, SortOrder order) async { + try { + final updateAlbum = await _albumApiRepository.update(album.remoteId!, sortOrder: order); + album.sortOrder = updateAlbum.sortOrder; + + return _albumRepository.update(album); + } catch (error, stackTrace) { + _log.severe("Error updating album sort order", error, stackTrace); + } + return null; + } + + Future clearTable() async { + await _albumRepository.clearTable(); + } +} diff --git a/mobile/lib/services/api.service.dart b/mobile/lib/services/api.service.dart index d1aec9cc86..ec4720f313 100644 --- a/mobile/lib/services/api.service.dart +++ b/mobile/lib/services/api.service.dart @@ -6,14 +6,14 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; class ApiService { - final ApiClient _apiClient = ApiClient(basePath: ''); + late ApiClient _apiClient; late UsersApi usersApi; late AuthenticationApi authenticationApi; @@ -53,8 +53,8 @@ class ApiService { _apiClient.client = NetworkRepository.client; } - void setEndpoint(String endpoint) { - _apiClient.basePath = endpoint; + setEndpoint(String endpoint) { + _apiClient = ApiClient(basePath: endpoint); _apiClient.client = NetworkRepository.client; usersApi = UsersApi(_apiClient); authenticationApi = AuthenticationApi(_apiClient); @@ -96,12 +96,12 @@ class ApiService { /// port - optional (default: based on schema) /// path - optional Future resolveEndpoint(String serverUrl) async { - String url = normalizeServerUrl(serverUrl); + String url = sanitizeUrl(serverUrl); // Check for /.well-known/immich final wellKnownEndpoint = await _getWellKnownEndpoint(url); if (wellKnownEndpoint.isNotEmpty) { - url = normalizeServerUrl(wellKnownEndpoint); + url = sanitizeUrl(wellKnownEndpoint); } if (!await _isEndpointAvailable(url)) { @@ -113,10 +113,12 @@ class ApiService { } Future _isEndpointAvailable(String serverUrl) async { - final endpoint = serverUrl.endsWith('/api') ? serverUrl : '$serverUrl/api'; + if (!serverUrl.endsWith('/api')) { + serverUrl += '/api'; + } try { - setEndpoint(endpoint); + await setEndpoint(serverUrl); await serverInfoApi.pingServer().timeout(const Duration(seconds: 5)); } on TimeoutException catch (_) { return false; @@ -153,7 +155,7 @@ class ApiService { } Future setDeviceInfoHeader() async { - final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); if (Platform.isIOS) { final iosInfo = await deviceInfoPlugin.iosInfo; @@ -175,21 +177,28 @@ class ApiService { if (serverEndpoint != null && serverEndpoint.isNotEmpty) { urls.add(serverEndpoint); } - final network = SettingsRepository.instance.appConfig.network; - final localEndpoint = network.localEndpoint; + final localEndpoint = Store.tryGet(StoreKey.localEndpoint); if (localEndpoint != null && localEndpoint.isNotEmpty) { urls.add(localEndpoint); } - for (final url in network.externalEndpointList) { - if (url.isNotEmpty) { - urls.add(url); + final externalJson = Store.tryGet(StoreKey.externalEndpointList); + if (externalJson != null) { + final List list = jsonDecode(externalJson); + for (final entry in list) { + final url = AuxilaryEndpoint.fromJson(entry).url; + if (url.isNotEmpty) urls.add(url); } } return urls; } static Map getRequestHeaders() { - return SettingsRepository.instance.appConfig.network.customHeaders; + var customHeadersStr = Store.get(StoreKey.customHeaders, ""); + if (customHeadersStr.isEmpty) { + return const {}; + } + + return (jsonDecode(customHeadersStr) as Map).cast(); } ApiClient get apiClient => _apiClient; diff --git a/mobile/lib/services/app_settings.service.dart b/mobile/lib/services/app_settings.service.dart index 28bce32bb5..db4fc9965a 100644 --- a/mobile/lib/services/app_settings.service.dart +++ b/mobile/lib/services/app_settings.service.dart @@ -1,11 +1,66 @@ +import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; enum AppSettingsEnum { + loadPreview(StoreKey.loadPreview, "loadPreview", true), + loadOriginal(StoreKey.loadOriginal, "loadOriginal", false), + themeMode(StoreKey.themeMode, "themeMode", "system"), // "light","dark","system" + primaryColor(StoreKey.primaryColor, "primaryColor", defaultColorPresetName), + dynamicTheme(StoreKey.dynamicTheme, "dynamicTheme", false), + colorfulInterface(StoreKey.colorfulInterface, "colorfulInterface", true), + tilesPerRow(StoreKey.tilesPerRow, "tilesPerRow", 4), + dynamicLayout(StoreKey.dynamicLayout, "dynamicLayout", false), + groupAssetsBy(StoreKey.groupAssetsBy, "groupBy", 0), + uploadErrorNotificationGracePeriod( + StoreKey.uploadErrorNotificationGracePeriod, + "uploadErrorNotificationGracePeriod", + 2, + ), + backgroundBackupTotalProgress(StoreKey.backgroundBackupTotalProgress, "backgroundBackupTotalProgress", true), + backgroundBackupSingleProgress( + StoreKey.backgroundBackupSingleProgress, + "backgroundBackupSingleProgress", + false, + ), + storageIndicator(StoreKey.storageIndicator, "storageIndicator", true), + thumbnailCacheSize(StoreKey.thumbnailCacheSize, "thumbnailCacheSize", 10000), + imageCacheSize(StoreKey.imageCacheSize, "imageCacheSize", 350), + albumThumbnailCacheSize(StoreKey.albumThumbnailCacheSize, "albumThumbnailCacheSize", 200), + selectedAlbumSortOrder(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2), advancedTroubleshooting(StoreKey.advancedTroubleshooting, null, false), manageLocalMediaAndroid(StoreKey.manageLocalMediaAndroid, null, false), + logLevel(StoreKey.logLevel, null, 5), // Level.INFO = 5 + preferRemoteImage(StoreKey.preferRemoteImage, null, false), + loopVideo(StoreKey.loopVideo, "loopVideo", true), + loadOriginalVideo(StoreKey.loadOriginalVideo, "loadOriginalVideo", false), + autoPlayVideo(StoreKey.autoPlayVideo, "autoPlayVideo", true), + tapToNavigate(StoreKey.tapToNavigate, "tapToNavigate", false), + mapThemeMode(StoreKey.mapThemeMode, null, 0), + mapShowFavoriteOnly(StoreKey.mapShowFavoriteOnly, null, false), + mapIncludeArchived(StoreKey.mapIncludeArchived, null, false), + mapwithPartners(StoreKey.mapwithPartners, null, false), + mapRelativeDate(StoreKey.mapRelativeDate, null, 0), + allowSelfSignedSSLCert(StoreKey.selfSignedCert, null, false), + ignoreIcloudAssets(StoreKey.ignoreIcloudAssets, null, false), + selectedAlbumSortReverse(StoreKey.selectedAlbumSortReverse, null, true), enableHapticFeedback(StoreKey.enableHapticFeedback, null, true), - readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false); + syncAlbums(StoreKey.syncAlbums, null, false), + autoEndpointSwitching(StoreKey.autoEndpointSwitching, null, false), + photoManagerCustomFilter(StoreKey.photoManagerCustomFilter, null, true), + betaTimeline(StoreKey.betaTimeline, null, true), + enableBackup(StoreKey.enableBackup, null, false), + useCellularForUploadVideos(StoreKey.useWifiForUploadVideos, null, false), + useCellularForUploadPhotos(StoreKey.useWifiForUploadPhotos, null, false), + readonlyModeEnabled(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false), + albumGridView(StoreKey.albumGridView, "albumGridView", false), + backupRequireCharging(StoreKey.backupRequireCharging, null, false), + backupTriggerDelay(StoreKey.backupTriggerDelay, null, 30), + cleanupKeepFavorites(StoreKey.cleanupKeepFavorites, null, true), + cleanupKeepMediaType(StoreKey.cleanupKeepMediaType, null, 0), + cleanupKeepAlbumIds(StoreKey.cleanupKeepAlbumIds, null, ""), + cleanupCutoffDaysAgo(StoreKey.cleanupCutoffDaysAgo, null, -1), + cleanupDefaultsInitialized(StoreKey.cleanupDefaultsInitialized, null, false); const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue); diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart new file mode 100644 index 0000000000..b9fab35442 --- /dev/null +++ b/mobile/lib/services/asset.service.dart @@ -0,0 +1,465 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/asset_api.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; +import 'package:immich_mobile/repositories/etag.repository.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/sync.service.dart'; +import 'package:logging/logging.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; +import 'package:openapi/api.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +final assetServiceProvider = Provider( + (ref) => AssetService( + ref.watch(assetApiRepositoryProvider), + ref.watch(assetRepositoryProvider), + ref.watch(exifRepositoryProvider), + ref.watch(userRepositoryProvider), + ref.watch(etagRepositoryProvider), + ref.watch(backupAlbumRepositoryProvider), + ref.watch(apiServiceProvider), + ref.watch(syncServiceProvider), + ref.watch(backupServiceProvider), + ref.watch(albumServiceProvider), + ref.watch(userServiceProvider), + ref.watch(assetMediaRepositoryProvider), + ), +); + +class AssetService { + final AssetApiRepository _assetApiRepository; + final AssetRepository _assetRepository; + final IsarExifRepository _exifInfoRepository; + final IsarUserRepository _isarUserRepository; + final ETagRepository _etagRepository; + final BackupAlbumRepository _backupRepository; + final ApiService _apiService; + final SyncService _syncService; + final BackupService _backupService; + final AlbumService _albumService; + final UserService _userService; + final AssetMediaRepository _assetMediaRepository; + final log = Logger('AssetService'); + + AssetService( + this._assetApiRepository, + this._assetRepository, + this._exifInfoRepository, + this._isarUserRepository, + this._etagRepository, + this._backupRepository, + this._apiService, + this._syncService, + this._backupService, + this._albumService, + this._userService, + this._assetMediaRepository, + ); + + /// Checks the server for updated assets and updates the local database if + /// required. Returns `true` if there were any changes. + Future refreshRemoteAssets() async { + final syncedUserIds = await _etagRepository.getAllIds(); + final List syncedUsers = syncedUserIds.isEmpty + ? [] + : (await _isarUserRepository.getByUserIds(syncedUserIds)).nonNulls.toList(); + final Stopwatch sw = Stopwatch()..start(); + final bool changes = await _syncService.syncRemoteAssetsToDb( + users: syncedUsers, + getChangedAssets: _getRemoteAssetChanges, + loadAssets: _getRemoteAssets, + ); + dPrint(() => "refreshRemoteAssets full took ${sw.elapsedMilliseconds}ms"); + return changes; + } + + /// Returns `(null, null)` if changes are invalid -> requires full sync + Future<(List? toUpsert, List? toDelete)> _getRemoteAssetChanges( + List users, + DateTime since, + ) async { + final dto = AssetDeltaSyncDto(updatedAfter: since, userIds: users.map((e) => e.id).toList()); + final changes = await _apiService.syncApi.getDeltaSync(dto); + return changes == null || changes.needsFullSync + ? (null, null) + : (changes.upserted.map(Asset.remote).toList(), changes.deleted); + } + + /// Returns the list of people of the given asset id. + // If the server is not reachable `null` is returned. + Future?> getRemotePeopleOfAsset(String remoteId) async { + try { + final AssetResponseDto? dto = await _apiService.assetsApi.getAssetInfo(remoteId); + + return dto?.people; + } catch (error, stack) { + log.severe('Error while getting remote asset info: ${error.toString()}', error, stack); + + return null; + } + } + + /// Returns `null` if the server state did not change, else list of assets + Future?> _getRemoteAssets(UserDto user, DateTime until) async { + const int chunkSize = 10000; + try { + final List allAssets = []; + String? lastId; + // will break on error or once all assets are loaded + while (true) { + final dto = AssetFullSyncDto(limit: chunkSize, updatedUntil: until, lastId: lastId, userId: user.id); + log.fine("Requesting $chunkSize assets from $lastId"); + final List? assets = await _apiService.syncApi.getFullSyncForUser(dto); + if (assets == null) return null; + log.fine("Received ${assets.length} assets from ${assets.firstOrNull?.id} to ${assets.lastOrNull?.id}"); + allAssets.addAll(assets.map(Asset.remote)); + if (assets.length != chunkSize) break; + lastId = assets.last.id; + } + return allAssets; + } catch (error, stack) { + log.severe('Error while getting remote assets', error, stack); + return null; + } + } + + /// Loads the exif information from the database. If there is none, loads + /// the exif info from the server (remote assets only) + Future loadExif(Asset a) async { + a.exifInfo ??= (await _exifInfoRepository.get(a.id)); + // fileSize is always filled on the server but not set on client + if (a.exifInfo?.fileSize == null) { + if (a.isRemote) { + final dto = await _apiService.assetsApi.getAssetInfo(a.remoteId!); + if (dto != null && dto.exifInfo != null) { + final newExif = Asset.remote(dto).exifInfo!.copyWith(assetId: a.id); + a.exifInfo = newExif; + if (newExif != a.exifInfo) { + if (a.isInDb) { + await _assetRepository.transaction(() => _assetRepository.update(a)); + } else { + dPrint(() => "[loadExif] parameter Asset is not from DB!"); + } + } + } + } else { + // TODO implement local exif info parsing + } + } + return a; + } + + Future updateAssets(List assets, UpdateAssetDto updateAssetDto) async { + return await _apiService.assetsApi.updateAssets( + AssetBulkUpdateDto( + ids: assets.map((e) => e.remoteId!).toList(), + dateTimeOriginal: updateAssetDto.dateTimeOriginal, + isFavorite: updateAssetDto.isFavorite, + visibility: updateAssetDto.visibility, + latitude: updateAssetDto.latitude, + longitude: updateAssetDto.longitude, + ), + ); + } + + Future> changeFavoriteStatus(List assets, bool isFavorite) async { + try { + await updateAssets(assets, UpdateAssetDto(isFavorite: isFavorite)); + + for (var element in assets) { + element.isFavorite = isFavorite; + } + + await _syncService.upsertAssetsWithExif(assets); + + return assets; + } catch (error, stack) { + log.severe("Error while changing favorite status", error, stack); + return []; + } + } + + Future> changeArchiveStatus(List assets, bool isArchived) async { + try { + await updateAssets( + assets, + UpdateAssetDto(visibility: isArchived ? AssetVisibility.archive : AssetVisibility.timeline), + ); + + for (var element in assets) { + element.isArchived = isArchived; + element.visibility = isArchived ? AssetVisibilityEnum.archive : AssetVisibilityEnum.timeline; + } + + await _syncService.upsertAssetsWithExif(assets); + + return assets; + } catch (error, stack) { + log.severe("Error while changing archive status", error, stack); + return []; + } + } + + Future?> changeDateTime(List assets, String updatedDt) async { + try { + await updateAssets(assets, UpdateAssetDto(dateTimeOriginal: updatedDt)); + + for (var element in assets) { + element.fileCreatedAt = DateTime.parse(updatedDt); + element.exifInfo = element.exifInfo?.copyWith(dateTimeOriginal: DateTime.parse(updatedDt)); + } + + await _syncService.upsertAssetsWithExif(assets); + + return assets; + } catch (error, stack) { + log.severe("Error while changing date/time status", error, stack); + return Future.value(null); + } + } + + Future?> changeLocation(List assets, LatLng location) async { + try { + await updateAssets(assets, UpdateAssetDto(latitude: location.latitude, longitude: location.longitude)); + + for (var element in assets) { + element.exifInfo = element.exifInfo?.copyWith(latitude: location.latitude, longitude: location.longitude); + } + + await _syncService.upsertAssetsWithExif(assets); + + return assets; + } catch (error, stack) { + log.severe("Error while changing location status", error, stack); + return Future.value(null); + } + } + + Future syncUploadedAssetToAlbums() async { + try { + final selectedAlbums = await _backupRepository.getAllBySelection(BackupSelection.select); + final excludedAlbums = await _backupRepository.getAllBySelection(BackupSelection.exclude); + + final candidates = await _backupService.buildUploadCandidates( + selectedAlbums, + excludedAlbums, + useTimeFilter: false, + ); + + await refreshRemoteAssets(); + final owner = _userService.getMyUser(); + final remoteAssets = await _assetRepository.getAll(ownerId: owner.id, state: AssetState.merged); + + /// Map + Map> assetToAlbums = {}; + + for (BackupCandidate candidate in candidates) { + final asset = remoteAssets.firstWhereOrNull((a) => a.localId == candidate.asset.localId); + + if (asset != null) { + for (final albumName in candidate.albumNames) { + assetToAlbums.putIfAbsent(albumName, () => []).add(asset.remoteId!); + } + } + } + + // Upload assets to albums + for (final entry in assetToAlbums.entries) { + final albumName = entry.key; + final assetIds = entry.value; + + await _albumService.syncUploadAlbums([albumName], assetIds); + } + } catch (error, stack) { + log.severe("Error while syncing uploaded asset to albums", error, stack); + } + } + + Future setDescription(Asset asset, String newDescription) async { + final remoteAssetId = asset.remoteId; + final localExifId = asset.exifInfo?.assetId; + + // Guard [remoteAssetId] and [localExifId] null + if (remoteAssetId == null || localExifId == null) { + return; + } + + final result = await _assetApiRepository.update(remoteAssetId, description: newDescription); + + final description = result.exifInfo?.description; + + if (description != null) { + var exifInfo = await _exifInfoRepository.get(localExifId); + + if (exifInfo != null) { + await _exifInfoRepository.update(exifInfo.copyWith(description: description)); + } + } + } + + Future getDescription(Asset asset) async { + final localExifId = asset.exifInfo?.assetId; + + // Guard [remoteAssetId] and [localExifId] null + if (localExifId == null) { + return ""; + } + + final exifInfo = await _exifInfoRepository.get(localExifId); + + return exifInfo?.description ?? ""; + } + + Future getAspectRatio(Asset asset) async { + if (asset.isRemote) { + asset = await loadExif(asset); + } else if (asset.isLocal) { + await asset.localAsync; + } + + final aspectRatio = asset.aspectRatio; + if (aspectRatio != null) { + return aspectRatio; + } + + final width = asset.width; + final height = asset.height; + if (width != null && height != null) { + // we don't know the orientation, so assume it's normal + return width / height; + } + + return 1.0; + } + + Future> getStackAssets(String stackId) { + return _assetRepository.getStackAssets(stackId); + } + + Future clearTable() { + return _assetRepository.clearTable(); + } + + /// Delete assets from local file system and unreference from the database + Future deleteLocalAssets(Iterable assets) async { + // Delete files from local gallery + final candidates = assets.where((asset) => asset.isLocal); + + final deletedIds = await _assetMediaRepository.deleteAll(candidates.map((asset) => asset.localId!).toList()); + + // Modify local database by removing the reference to the local assets + if (deletedIds.isNotEmpty) { + // Delete records from local database + final isarIds = assets.where((asset) => asset.storage == AssetState.local).map((asset) => asset.id).toList(); + await _assetRepository.deleteByIds(isarIds); + + // Modify Merged asset to be remote only + final updatedAssets = assets.where((asset) => asset.storage == AssetState.merged).map((asset) { + asset.localId = null; + return asset; + }).toList(); + + await _assetRepository.updateAll(updatedAssets); + } + } + + /// Delete assets from the server and unreference from the database + Future deleteRemoteAssets(Iterable assets, {bool shouldDeletePermanently = false}) async { + final candidates = assets.where((a) => a.isRemote); + + if (candidates.isEmpty) { + return; + } + + await _apiService.assetsApi.deleteAssets( + AssetBulkDeleteDto(ids: candidates.map((a) => a.remoteId!).toList(), force: shouldDeletePermanently), + ); + + /// Update asset info bassed on the deletion type. + final payload = shouldDeletePermanently + ? assets.where((asset) => asset.storage == AssetState.merged).map((asset) { + asset.remoteId = null; + asset.visibility = AssetVisibilityEnum.timeline; + return asset; + }) + : assets.where((asset) => asset.isRemote).map((asset) { + asset.isTrashed = true; + return asset; + }); + + await _assetRepository.transaction(() async { + await _assetRepository.updateAll(payload.toList()); + + if (shouldDeletePermanently) { + final remoteAssetIds = assets + .where((asset) => asset.storage == AssetState.remote) + .map((asset) => asset.id) + .toList(); + await _assetRepository.deleteByIds(remoteAssetIds); + } + }); + } + + /// Delete assets on both local file system and the server. + /// Unreference from the database. + Future deleteAssets(Iterable assets, {bool shouldDeletePermanently = false}) async { + final hasLocal = assets.any((asset) => asset.isLocal); + final hasRemote = assets.any((asset) => asset.isRemote); + + if (hasLocal) { + await deleteLocalAssets(assets); + } + + if (hasRemote) { + await deleteRemoteAssets(assets, shouldDeletePermanently: shouldDeletePermanently); + } + } + + Stream watchAsset(int id, {bool fireImmediately = false}) { + return _assetRepository.watchAsset(id, fireImmediately: fireImmediately); + } + + Future> getRecentlyTakenAssets() { + final me = _userService.getMyUser(); + return _assetRepository.getRecentlyTakenAssets(me.id); + } + + Future> getMotionAssets() { + final me = _userService.getMyUser(); + return _assetRepository.getMotionAssets(me.id); + } + + Future setVisibility(List assets, AssetVisibilityEnum visibility) async { + await _assetApiRepository.updateVisibility(assets.map((asset) => asset.remoteId!).toList(), visibility); + + final updatedAssets = assets.map((asset) { + asset.visibility = visibility; + return asset; + }).toList(); + + await _assetRepository.updateAll(updatedAssets); + } + + Future getAssetByRemoteId(String remoteId) async { + final assets = await _assetRepository.getAllByRemoteId([remoteId]); + return assets.isNotEmpty ? assets.first : null; + } +} diff --git a/mobile/lib/services/auth.service.dart b/mobile/lib/services/auth.service.dart index 0e02c7b1b9..667681e579 100644 --- a/mobile/lib/services/auth.service.dart +++ b/mobile/lib/services/auth.service.dart @@ -1,19 +1,19 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/models/auth/login_response.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/network.service.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -25,6 +25,7 @@ final authServiceProvider = Provider( ref.watch(apiServiceProvider), ref.watch(networkServiceProvider), ref.watch(backgroundSyncProvider), + ref.watch(appSettingsServiceProvider), ), ); @@ -34,6 +35,7 @@ class AuthService { final ApiService _apiService; final NetworkService _networkService; final BackgroundSyncManager _backgroundSyncManager; + final AppSettingsService _appSettingsService; final _log = Logger("AuthService"); AuthService( @@ -42,6 +44,7 @@ class AuthService { this._apiService, this._networkService, this._backgroundSyncManager, + this._appSettingsService, ); /// Validates the provided server URL by resolving and setting the endpoint. @@ -100,7 +103,7 @@ class AuthService { _log.severe("Error clearing local data", error, stackTrace); }); - await SettingsRepository.instance.write(SettingsKey.backupEnabled, false); + await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false); } } @@ -110,7 +113,7 @@ class AuthService { /// - Authentication repository data /// - Current user information /// - Access token - /// - Server-specific endpoint configuration + /// - Asset ETag /// /// All deletions are executed in parallel using [Future.wait]. Future clearLocalData() async { @@ -120,12 +123,11 @@ class AuthService { _authRepository.clearLocalData(), Store.delete(StoreKey.currentUser), Store.delete(StoreKey.accessToken), - SettingsRepository.instance.clear(const [ - .networkAutoEndpointSwitching, - .networkPreferredWifiName, - .networkLocalEndpoint, - .networkExternalEndpointList, - ]), + Store.delete(StoreKey.assetETag), + Store.delete(StoreKey.autoEndpointSwitching), + Store.delete(StoreKey.preferredWifiName), + Store.delete(StoreKey.localEndpoint), + Store.delete(StoreKey.externalEndpointList), ]); } diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart new file mode 100644 index 0000000000..03278d25fc --- /dev/null +++ b/mobile/lib/services/background.service.dart @@ -0,0 +1,595 @@ +import 'dart:async'; +import 'dart:developer'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:ui' show DartPluginRegistrant, IsolateNameServer, PluginUtilities; + +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/auth.service.dart'; +import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/localization.service.dart'; +import 'package:immich_mobile/utils/backup_progress.dart'; +import 'package:immich_mobile/utils/bootstrap.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_mobile/utils/diff.dart'; +import 'package:path_provider_foundation/path_provider_foundation.dart'; +import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; + +final backgroundServiceProvider = Provider((ref) => BackgroundService()); + +/// Background backup service +class BackgroundService { + static const String _portNameLock = "immichLock"; + static const MethodChannel _foregroundChannel = MethodChannel('immich/foregroundChannel'); + static const MethodChannel _backgroundChannel = MethodChannel('immich/backgroundChannel'); + static const notifyInterval = Duration(milliseconds: 400); + bool _isBackgroundInitialized = false; + Completer? _cancellationToken; + bool _canceledBySystem = false; + int _wantsLockTime = 0; + bool _hasLock = false; + SendPort? _waitingIsolate; + ReceivePort? _rp; + bool _errorGracePeriodExceeded = true; + int _uploadedAssetsCount = 0; + int _assetsToUploadCount = 0; + String _lastPrintedDetailContent = ""; + String? _lastPrintedDetailTitle; + late final ThrottleProgressUpdate _throttledNotifiy = ThrottleProgressUpdate(_updateProgress, notifyInterval); + late final ThrottleProgressUpdate _throttledDetailNotify = ThrottleProgressUpdate( + _updateDetailProgress, + notifyInterval, + ); + + bool get isBackgroundInitialized { + return _isBackgroundInitialized; + } + + /// Ensures that the background service is enqueued if enabled in settings + Future resumeServiceIfEnabled() async { + return await isBackgroundBackupEnabled() && await enableService(); + } + + /// Enqueues the background service + Future enableService({bool immediate = false}) async { + try { + final callback = PluginUtilities.getCallbackHandle(_nativeEntry)!; + final String title = "backup_background_service_default_notification".tr(); + final bool ok = await _foregroundChannel.invokeMethod('enable', [callback.toRawHandle(), title, immediate]); + return ok; + } catch (error) { + return false; + } + } + + /// Configures the background service + Future configureService({ + bool requireUnmetered = true, + bool requireCharging = false, + int triggerUpdateDelay = 5000, + int triggerMaxDelay = 50000, + }) async { + try { + final bool ok = await _foregroundChannel.invokeMethod('configure', [ + requireUnmetered, + requireCharging, + triggerUpdateDelay, + triggerMaxDelay, + ]); + return ok; + } catch (error) { + return false; + } + } + + /// Cancels the background service (if currently running) and removes it from work queue + Future disableService() async { + try { + final ok = await _foregroundChannel.invokeMethod('disable'); + return ok; + } catch (error) { + return false; + } + } + + /// Returns `true` if the background service is enabled + Future isBackgroundBackupEnabled() async { + try { + return await _foregroundChannel.invokeMethod("isEnabled"); + } catch (error) { + return false; + } + } + + /// Returns `true` if battery optimizations are disabled + Future isIgnoringBatteryOptimizations() async { + // iOS does not need battery optimizations enabled + if (Platform.isIOS) { + return true; + } + try { + return await _foregroundChannel.invokeMethod('isIgnoringBatteryOptimizations'); + } catch (error) { + return false; + } + } + + // Yet to be implemented + Future digestFile(String path) { + return _foregroundChannel.invokeMethod("digestFile", [path]); + } + + Future?> digestFiles(List paths) { + return _foregroundChannel.invokeListMethod("digestFiles", paths); + } + + /// Updates the notification shown by the background service + Future _updateNotification({ + String? title, + String? content, + int progress = 0, + int max = 0, + bool indeterminate = false, + bool isDetail = false, + bool onlyIfFG = false, + }) async { + try { + if (_isBackgroundInitialized) { + return _backgroundChannel.invokeMethod('updateNotification', [ + title, + content, + progress, + max, + indeterminate, + isDetail, + onlyIfFG, + ]); + } + } catch (error) { + dPrint(() => "[_updateNotification] failed to communicate with plugin"); + } + return false; + } + + /// Shows a new priority notification + Future _showErrorNotification({required String title, String? content, String? individualTag}) async { + try { + if (_isBackgroundInitialized && _errorGracePeriodExceeded) { + return await _backgroundChannel.invokeMethod('showError', [title, content, individualTag]); + } + } catch (error) { + dPrint(() => "[_showErrorNotification] failed to communicate with plugin"); + } + return false; + } + + Future _clearErrorNotifications() async { + try { + if (_isBackgroundInitialized) { + return await _backgroundChannel.invokeMethod('clearErrorNotifications'); + } + } catch (error) { + dPrint(() => "[_clearErrorNotifications] failed to communicate with plugin"); + } + return false; + } + + /// await to ensure this thread (foreground or background) has exclusive access + Future acquireLock() async { + if (_hasLock) { + dPrint(() => "WARNING: [acquireLock] called more than once"); + return true; + } + final int lockTime = Timeline.now; + _wantsLockTime = lockTime; + final ReceivePort rp = ReceivePort(_portNameLock); + _rp = rp; + final SendPort sp = rp.sendPort; + + while (!IsolateNameServer.registerPortWithName(sp, _portNameLock)) { + try { + await _checkLockReleasedWithHeartbeat(lockTime); + } catch (error) { + return false; + } + if (_wantsLockTime != lockTime) { + return false; + } + } + _hasLock = true; + rp.listen(_heartbeatListener); + return true; + } + + Future _checkLockReleasedWithHeartbeat(final int lockTime) async { + SendPort? other = IsolateNameServer.lookupPortByName(_portNameLock); + if (other != null) { + final ReceivePort tempRp = ReceivePort(); + final SendPort tempSp = tempRp.sendPort; + final bs = tempRp.asBroadcastStream(); + while (_wantsLockTime == lockTime) { + other.send(tempSp); + final dynamic answer = await bs.first.timeout(const Duration(seconds: 3), onTimeout: () => null); + if (_wantsLockTime != lockTime) { + break; + } + if (answer == null) { + // other isolate failed to answer, assuming it exited without releasing the lock + if (other == IsolateNameServer.lookupPortByName(_portNameLock)) { + IsolateNameServer.removePortNameMapping(_portNameLock); + } + break; + } else if (answer == true) { + // other isolate released the lock + break; + } else if (answer == false) { + // other isolate is still active + } + final dynamic isFinished = await bs.first.timeout(const Duration(seconds: 3), onTimeout: () => false); + if (isFinished == true) { + break; + } + } + tempRp.close(); + } + } + + void _heartbeatListener(dynamic msg) { + if (msg is SendPort) { + _waitingIsolate = msg; + msg.send(false); + } + } + + /// releases the exclusive access lock + void releaseLock() { + _wantsLockTime = 0; + if (_hasLock) { + IsolateNameServer.removePortNameMapping(_portNameLock); + _waitingIsolate?.send(true); + _waitingIsolate = null; + _hasLock = false; + } + _rp?.close(); + _rp = null; + } + + void _setupBackgroundCallHandler() { + _backgroundChannel.setMethodCallHandler(_callHandler); + _isBackgroundInitialized = true; + _backgroundChannel.invokeMethod('initialized'); + } + + Future _callHandler(MethodCall call) async { + DartPluginRegistrant.ensureInitialized(); + if (Platform.isIOS) { + // NOTE: I'm not sure this is strictly necessary anymore, but + // out of an abundance of caution, we will keep it in until someone + // can say for sure + PathProviderFoundation.registerWith(); + } + switch (call.method) { + case "backgroundProcessing": + case "onAssetsChanged": + try { + unawaited(_clearErrorNotifications()); + + // iOS should time out after some threshold so it doesn't wait + // indefinitely and can run later + // Android is fine to wait here until the lock releases + final waitForLock = Platform.isIOS + ? acquireLock().timeout(const Duration(seconds: 5), onTimeout: () => false) + : acquireLock(); + + final bool hasAccess = await waitForLock; + if (!hasAccess) { + dPrint(() => "[_callHandler] could not acquire lock, exiting"); + return false; + } + + final translationsOk = await loadTranslations(); + if (!translationsOk) { + dPrint(() => "[_callHandler] could not load translations"); + } + + final bool ok = await _onAssetsChanged(); + return ok; + } catch (error) { + dPrint(() => error.toString()); + return false; + } finally { + releaseLock(); + } + case "systemStop": + _canceledBySystem = true; + _cancellationToken?.complete(); + _cancellationToken = null; + return true; + default: + dPrint(() => "Unknown method ${call.method}"); + return false; + } + } + + Future _onAssetsChanged() async { + final (isar, drift, logDb) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false, listenStoreUpdates: false); + + final ref = ProviderContainer( + overrides: [ + dbProvider.overrideWithValue(isar), + isarProvider.overrideWithValue(isar), + driftProvider.overrideWith(driftOverride(drift)), + ], + ); + + await ref.read(authServiceProvider).setOpenApiServiceEndpoint(); + dPrint(() => "[BG UPLOAD] Using endpoint: ${ref.read(apiServiceProvider).apiClient.basePath}"); + + final selectedAlbums = await ref.read(backupAlbumRepositoryProvider).getAllBySelection(BackupSelection.select); + final excludedAlbums = await ref.read(backupAlbumRepositoryProvider).getAllBySelection(BackupSelection.exclude); + if (selectedAlbums.isEmpty) { + return true; + } + + await ref.read(fileMediaRepositoryProvider).enableBackgroundAccess(); + + do { + final bool backupOk = await _runBackup( + ref.read(backupServiceProvider), + ref.read(appSettingsServiceProvider), + selectedAlbums, + excludedAlbums, + ); + if (backupOk) { + await Store.delete(StoreKey.backupFailedSince); + final backupAlbums = [...selectedAlbums, ...excludedAlbums]; + backupAlbums.sortBy((e) => e.id); + + final dbAlbums = await ref.read(backupAlbumRepositoryProvider).getAll(sort: BackupAlbumSort.id); + final List toDelete = []; + final List toUpsert = []; + // stores the most recent `lastBackup` per album but always keeps the `selection` from the most recent DB state + diffSortedListsSync( + dbAlbums, + backupAlbums, + compare: (BackupAlbum a, BackupAlbum b) => a.id.compareTo(b.id), + both: (BackupAlbum a, BackupAlbum b) { + a.lastBackup = a.lastBackup.isAfter(b.lastBackup) ? a.lastBackup : b.lastBackup; + toUpsert.add(a); + return true; + }, + onlyFirst: (BackupAlbum a) => toUpsert.add(a), + onlySecond: (BackupAlbum b) => toDelete.add(b.isarId), + ); + await ref.read(backupAlbumRepositoryProvider).deleteAll(toDelete); + await ref.read(backupAlbumRepositoryProvider).updateAll(toUpsert); + } else if (Store.tryGet(StoreKey.backupFailedSince) == null) { + await Store.put(StoreKey.backupFailedSince, DateTime.now()); + return false; + } + // Android should check for new assets added while performing backup + } while (Platform.isAndroid && true == await _backgroundChannel.invokeMethod("hasContentChanged")); + return true; + } + + Future _runBackup( + BackupService backupService, + AppSettingsService settingsService, + List selectedAlbums, + List excludedAlbums, + ) async { + _errorGracePeriodExceeded = _isErrorGracePeriodExceeded(settingsService); + final bool notifyTotalProgress = settingsService.getSetting(AppSettingsEnum.backgroundBackupTotalProgress); + final bool notifySingleProgress = settingsService.getSetting(AppSettingsEnum.backgroundBackupSingleProgress); + + if (_canceledBySystem) { + return false; + } + + Set toUpload = await backupService.buildUploadCandidates(selectedAlbums, excludedAlbums); + + try { + toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); + } catch (e) { + unawaited( + _showErrorNotification( + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_connection_failed_message".tr(), + ), + ); + return false; + } + + if (_canceledBySystem) { + return false; + } + + if (toUpload.isEmpty) { + return true; + } + _assetsToUploadCount = toUpload.length; + _uploadedAssetsCount = 0; + unawaited( + _updateNotification( + title: "backup_background_service_in_progress_notification".tr(), + content: notifyTotalProgress ? formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount) : null, + progress: 0, + max: notifyTotalProgress ? _assetsToUploadCount : 0, + indeterminate: !notifyTotalProgress, + onlyIfFG: !notifyTotalProgress, + ), + ); + + _cancellationToken?.complete(); + _cancellationToken = Completer(); + final pmProgressHandler = Platform.isIOS ? PMProgressHandler() : null; + + final bool ok = await backupService.backupAsset( + toUpload, + _cancellationToken!, + pmProgressHandler: pmProgressHandler, + onSuccess: (result) => _onAssetUploaded(shouldNotify: notifyTotalProgress), + onProgress: (bytes, totalBytes) => _onProgress(bytes, totalBytes, shouldNotify: notifySingleProgress), + onCurrentAsset: (asset) => _onSetCurrentBackupAsset(asset, shouldNotify: notifySingleProgress), + onError: _onBackupError, + isBackground: true, + ); + + if (!ok && !_cancellationToken!.isCompleted) { + unawaited( + _showErrorNotification( + title: "backup_background_service_error_title".tr(), + content: "backup_background_service_backup_failed_message".tr(), + ), + ); + } + + return ok; + } + + void _onAssetUploaded({bool shouldNotify = false}) { + if (!shouldNotify) { + return; + } + + _uploadedAssetsCount++; + _throttledNotifiy(); + } + + void _onProgress(int bytes, int totalBytes, {bool shouldNotify = false}) { + if (!shouldNotify) { + return; + } + + _throttledDetailNotify(progress: bytes, total: totalBytes); + } + + void _updateDetailProgress(String? title, int progress, int total) { + final String msg = total > 0 ? humanReadableBytesProgress(progress, total) : ""; + // only update if message actually differs (to stop many useless notification updates on large assets or slow connections) + if (msg != _lastPrintedDetailContent || _lastPrintedDetailTitle != title) { + _lastPrintedDetailContent = msg; + _lastPrintedDetailTitle = title; + _updateNotification( + progress: total > 0 ? (progress * 1000) ~/ total : 0, + max: 1000, + isDetail: true, + title: title, + content: msg, + ); + } + } + + void _updateProgress(String? title, int progress, int total) { + _updateNotification( + progress: _uploadedAssetsCount, + max: _assetsToUploadCount, + title: title, + content: formatAssetBackupProgress(_uploadedAssetsCount, _assetsToUploadCount), + ); + } + + void _onBackupError(ErrorUploadAsset errorAssetInfo) { + _showErrorNotification( + title: "backup_background_service_upload_failure_notification".tr( + namedArgs: {'filename': errorAssetInfo.fileName}, + ), + individualTag: errorAssetInfo.id, + ); + } + + void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset, {bool shouldNotify = false}) { + if (!shouldNotify) { + return; + } + + _throttledDetailNotify.title = "backup_background_service_current_upload_notification".tr( + namedArgs: {'filename': currentUploadAsset.fileName}, + ); + _throttledDetailNotify.progress = 0; + _throttledDetailNotify.total = 0; + } + + bool _isErrorGracePeriodExceeded(AppSettingsService appSettingsService) { + final int value = appSettingsService.getSetting(AppSettingsEnum.uploadErrorNotificationGracePeriod); + if (value == 0) { + return true; + } else if (value == 5) { + return false; + } + final DateTime? failedSince = Store.tryGet(StoreKey.backupFailedSince); + if (failedSince == null) { + return false; + } + final Duration duration = DateTime.now().difference(failedSince); + if (value == 1) { + return duration > const Duration(minutes: 30); + } else if (value == 2) { + return duration > const Duration(hours: 2); + } else if (value == 3) { + return duration > const Duration(hours: 8); + } else if (value == 4) { + return duration > const Duration(hours: 24); + } + assert(false, "Invalid value"); + return true; + } + + Future getIOSBackupLastRun(IosBackgroundTask task) async { + if (!Platform.isIOS) { + return null; + } + // Seconds since last run + final double? lastRun = task == IosBackgroundTask.fetch + ? await _foregroundChannel.invokeMethod('lastBackgroundFetchTime') + : await _foregroundChannel.invokeMethod('lastBackgroundProcessingTime'); + if (lastRun == null) { + return null; + } + final time = DateTime.fromMillisecondsSinceEpoch(lastRun.toInt() * 1000); + return time; + } + + Future getIOSBackupNumberOfProcesses() async { + if (!Platform.isIOS) { + return 0; + } + return await _foregroundChannel.invokeMethod('numberOfBackgroundProcesses'); + } + + Future getIOSBackgroundAppRefreshEnabled() async { + if (!Platform.isIOS) { + return false; + } + return await _foregroundChannel.invokeMethod('backgroundAppRefreshEnabled'); + } +} + +enum IosBackgroundTask { fetch, processing } + +/// entry point called by Kotlin/Java code; needs to be a top-level function +@pragma('vm:entry-point') +void _nativeEntry() { + WidgetsFlutterBinding.ensureInitialized(); + DartPluginRegistrant.ensureInitialized(); + BackgroundService backgroundService = BackgroundService(); + backgroundService._setupBackgroundCallHandler(); +} diff --git a/mobile/lib/services/background_upload.service.dart b/mobile/lib/services/background_upload.service.dart index b526cc4b4f..d54a677c24 100644 --- a/mobile/lib/services/background_upload.service.dart +++ b/mobile/lib/services/background_upload.service.dart @@ -4,7 +4,6 @@ import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; import 'package:flutter/foundation.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; @@ -14,27 +13,25 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; -import 'package:openapi/api.dart' as api; import 'package:path/path.dart' as p; -part 'background_upload.service.freezed.dart'; - final backgroundUploadServiceProvider = Provider((ref) { - final db = ref.watch(driftProvider); final service = BackgroundUploadService( ref.watch(uploadRepositoryProvider), ref.watch(storageRepositoryProvider), - db.localAssetRepository, - db.backupRepository, + ref.watch(localAssetRepository), + ref.watch(backupRepositoryProvider), + ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); @@ -43,15 +40,20 @@ final backgroundUploadServiceProvider = Provider((ref) { }); /// Metadata for upload tasks to track live photo handling -@Freezed(fromJson: false, toJson: false) -abstract class UploadTaskMetadata with _$UploadTaskMetadata { - const UploadTaskMetadata._(); +class UploadTaskMetadata { + final String localAssetId; + final bool isLivePhotos; + final String livePhotoVideoId; - const factory UploadTaskMetadata({ - required String localAssetId, - required bool isLivePhotos, - required String livePhotoVideoId, - }) = _UploadTaskMetadata; + const UploadTaskMetadata({required this.localAssetId, required this.isLivePhotos, required this.livePhotoVideoId}); + + UploadTaskMetadata copyWith({String? localAssetId, bool? isLivePhotos, String? livePhotoVideoId}) { + return UploadTaskMetadata( + localAssetId: localAssetId ?? this.localAssetId, + isLivePhotos: isLivePhotos ?? this.isLivePhotos, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + ); + } Map toMap() { return { @@ -69,10 +71,26 @@ abstract class UploadTaskMetadata with _$UploadTaskMetadata { ); } + String toJson() => json.encode(toMap()); + factory UploadTaskMetadata.fromJson(String source) => UploadTaskMetadata.fromMap(json.decode(source) as Map); - String toJson() => json.encode(toMap()); + @override + String toString() => + 'UploadTaskMetadata(localAssetId: $localAssetId, isLivePhotos: $isLivePhotos, livePhotoVideoId: $livePhotoVideoId)'; + + @override + bool operator ==(covariant UploadTaskMetadata other) { + if (identical(this, other)) return true; + + return other.localAssetId == localAssetId && + other.isLivePhotos == isLivePhotos && + other.livePhotoVideoId == livePhotoVideoId; + } + + @override + int get hashCode => localAssetId.hashCode ^ isLivePhotos.hashCode ^ livePhotoVideoId.hashCode; } /// Service for handling background uploads using iOS URLSession (background_downloader) @@ -85,6 +103,7 @@ class BackgroundUploadService { this._storageRepository, this._localAssetRepository, this._backupRepository, + this._appSettingsService, this._assetMediaRepository, ) { _uploadRepository.onUploadStatus = _onUploadCallback; @@ -93,8 +112,9 @@ class BackgroundUploadService { final UploadRepository _uploadRepository; final StorageRepository _storageRepository; - final LocalAssetRepository _localAssetRepository; - final BackupRepository _backupRepository; + final DriftLocalAssetRepository _localAssetRepository; + final DriftBackupRepository _backupRepository; + final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('BackgroundUploadService'); @@ -116,12 +136,12 @@ class BackgroundUploadService { if (!_taskStatusController.isClosed) { _taskStatusController.add(update); } - unawaited(_handleTaskStatusUpdate(update)); + _handleTaskStatusUpdate(update); } void dispose() { - unawaited(_taskStatusController.close()); - unawaited(_taskProgressController.close()); + _taskStatusController.close(); + _taskProgressController.close(); } /// Enqueue tasks to the background upload queue @@ -152,7 +172,7 @@ class BackgroundUploadService { const batchSize = 100; final batch = candidates.take(batchSize).toList(); - final List tasks = []; + List tasks = []; for (final asset in batch) { final task = await getUploadTask(asset); @@ -186,7 +206,7 @@ class BackgroundUploadService { return _uploadRepository.start(); } - Future _handleTaskStatusUpdate(TaskStatusUpdate update) async { + void _handleTaskStatusUpdate(TaskStatusUpdate update) async { switch (update.status) { case TaskStatus.complete: unawaited(_handleLivePhoto(update)); @@ -200,6 +220,8 @@ class BackgroundUploadService { } } + break; + default: break; } @@ -269,12 +291,15 @@ class BackgroundUploadService { return null; } - final fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; - // Some apps (e.g. DJI/Fusion) return names without an extension; fall back to the asset name for those. - final extension = p.extension(file.path).isNotEmpty ? p.extension(file.path) : p.extension(asset.name); - final originalFileName = p.setExtension(fileName, extension); + String fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; + final hasExtension = p.extension(fileName).isNotEmpty; + if (!hasExtension) { + fileName = p.setExtension(fileName, p.extension(asset.name)); + } - final String metadata = UploadTaskMetadata( + final originalFileName = entity.isLivePhoto ? p.setExtension(fileName, p.extension(file.path)) : fileName; + + String metadata = UploadTaskMetadata( localAssetId: asset.id, isLivePhotos: entity.isLivePhoto, livePhotoVideoId: '', @@ -293,8 +318,6 @@ class BackgroundUploadService { priority: priority, isFavorite: asset.isFavorite, requiresWiFi: requiresWiFi, - // Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset - fields: entity.isLivePhoto ? {'visibility': api.AssetVisibility.hidden.toString()} : null, cloudId: entity.isLivePhoto ? null : asset.cloudId, adjustmentTime: entity.isLivePhoto ? null : asset.adjustmentTime?.toIso8601String(), latitude: entity.isLivePhoto ? null : asset.latitude?.toString(), @@ -338,14 +361,15 @@ class BackgroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - final backup = SettingsRepository.instance.appConfig.backup; - if (asset.isVideo && backup.useCellularForVideos) { - return false; + bool requiresWiFi = true; + + if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { + requiresWiFi = false; + } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { + requiresWiFi = false; } - if (!asset.isVideo && backup.useCellularForPhotos) { - return false; - } - return true; + + return requiresWiFi; } Future buildUploadTask( @@ -372,14 +396,13 @@ class BackgroundUploadService { final (baseDirectory, directory, filename) = await Task.split(filePath: file.path); final fieldsMap = { 'filename': originalFileName ?? filename, - // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': deviceAssetId ?? '', 'deviceId': deviceId, 'fileCreatedAt': createdAt.toUtc().toIso8601String(), 'fileModifiedAt': modifiedAt.toUtc().toIso8601String(), 'isFavorite': isFavorite?.toString() ?? 'false', 'duration': '0', - ...?fields, + if (fields != null) ...fields, if (CurrentPlatform.isIOS && cloudId != null) 'metadata': jsonEncode([ RemoteAssetMetadataItem( diff --git a/mobile/lib/services/backup.service.dart b/mobile/lib/services/backup.service.dart new file mode 100644 index 0000000000..9b6a26be03 --- /dev/null +++ b/mobile/lib/services/backup.service.dart @@ -0,0 +1,473 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:http/http.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; +import 'package:immich_mobile/repositories/upload.repository.dart'; +import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/error_upload_asset.model.dart'; +import 'package:immich_mobile/models/backup/success_upload_asset.model.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:logging/logging.dart'; +import 'package:openapi/api.dart'; +import 'package:path/path.dart' as p; +import 'package:permission_handler/permission_handler.dart' as pm; +import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; +import 'package:immich_mobile/utils/debug_print.dart'; + +final backupServiceProvider = Provider( + (ref) => BackupService( + ref.watch(apiServiceProvider), + ref.watch(appSettingsServiceProvider), + ref.watch(albumServiceProvider), + ref.watch(albumMediaRepositoryProvider), + ref.watch(fileMediaRepositoryProvider), + ref.watch(assetRepositoryProvider), + ref.watch(assetMediaRepositoryProvider), + ), +); + +class BackupService { + final ApiService _apiService; + final Logger _log = Logger("BackupService"); + final AppSettingsService _appSetting; + final AlbumService _albumService; + final AlbumMediaRepository _albumMediaRepository; + final FileMediaRepository _fileMediaRepository; + final AssetRepository _assetRepository; + final AssetMediaRepository _assetMediaRepository; + + BackupService( + this._apiService, + this._appSetting, + this._albumService, + this._albumMediaRepository, + this._fileMediaRepository, + this._assetRepository, + this._assetMediaRepository, + ); + + Future?> getDeviceBackupAsset() async { + final String deviceId = Store.get(StoreKey.deviceId); + + try { + return await _apiService.assetsApi.getAllUserAssetsByDeviceId(deviceId); + } catch (e) { + dPrint(() => 'Error [getDeviceBackupAsset] ${e.toString()}'); + return null; + } + } + + Future _saveDuplicatedAssetIds(List deviceAssetIds) => + _assetRepository.transaction(() => _assetRepository.upsertDuplicatedAssets(deviceAssetIds)); + + /// Get duplicated asset id from database + Future> getDuplicatedAssetIds() async { + final duplicates = await _assetRepository.getAllDuplicatedAssetIds(); + return duplicates.toSet(); + } + + /// Returns all assets newer than the last successful backup per album + /// if `useTimeFilter` is set to true, all assets will be returned + Future> buildUploadCandidates( + List selectedBackupAlbums, + List excludedBackupAlbums, { + bool useTimeFilter = true, + }) async { + final now = DateTime.now(); + + final Set toAdd = await _fetchAssetsAndUpdateLastBackup( + selectedBackupAlbums, + now, + useTimeFilter: useTimeFilter, + ); + + if (toAdd.isEmpty) return {}; + + final Set toRemove = await _fetchAssetsAndUpdateLastBackup( + excludedBackupAlbums, + now, + useTimeFilter: useTimeFilter, + ); + + return toAdd.difference(toRemove); + } + + Future> _fetchAssetsAndUpdateLastBackup( + List backupAlbums, + DateTime now, { + bool useTimeFilter = true, + }) async { + Set candidates = {}; + + for (final BackupAlbum backupAlbum in backupAlbums) { + final Album localAlbum; + try { + localAlbum = await _albumMediaRepository.get(backupAlbum.id); + } on StateError { + // the album no longer exists + continue; + } + + if (useTimeFilter && localAlbum.modifiedAt.isBefore(backupAlbum.lastBackup)) { + continue; + } + final List assets; + try { + assets = await _albumMediaRepository.getAssets( + backupAlbum.id, + modifiedFrom: useTimeFilter + ? + // subtract 2 seconds to prevent missing assets due to rounding issues + backupAlbum.lastBackup.subtract(const Duration(seconds: 2)) + : null, + modifiedUntil: useTimeFilter ? now : null, + ); + } on StateError { + // either there are no assets matching the filter criteria OR the album no longer exists + continue; + } + + // Add album's name to the asset info + for (final asset in assets) { + List albumNames = [localAlbum.name]; + + final existingAsset = candidates.firstWhereOrNull((candidate) => candidate.asset.localId == asset.localId); + + if (existingAsset != null) { + albumNames.addAll(existingAsset.albumNames); + candidates.remove(existingAsset); + } + + candidates.add(BackupCandidate(asset: asset, albumNames: albumNames)); + } + + backupAlbum.lastBackup = now; + } + + return candidates; + } + + /// Returns a new list of assets not yet uploaded + Future> removeAlreadyUploadedAssets(Set candidates) async { + if (candidates.isEmpty) { + return candidates; + } + + final Set duplicatedAssetIds = await getDuplicatedAssetIds(); + candidates.removeWhere((candidate) => duplicatedAssetIds.contains(candidate.asset.localId)); + + if (candidates.isEmpty) { + return candidates; + } + + final Set existing = {}; + try { + final String deviceId = Store.get(StoreKey.deviceId); + final CheckExistingAssetsResponseDto? duplicates = await _apiService.assetsApi.checkExistingAssets( + CheckExistingAssetsDto(deviceAssetIds: candidates.map((c) => c.asset.localId!).toList(), deviceId: deviceId), + ); + if (duplicates != null) { + existing.addAll(duplicates.existingIds); + } + } on ApiException { + // workaround for older server versions or when checking for too many assets at once + final List? allAssetsInDatabase = await getDeviceBackupAsset(); + if (allAssetsInDatabase != null) { + existing.addAll(allAssetsInDatabase); + } + } + + if (existing.isNotEmpty) { + candidates.removeWhere((c) => existing.contains(c.asset.localId)); + } + + return candidates; + } + + Future _checkPermissions() async { + if (Platform.isAndroid && !(await pm.Permission.accessMediaLocation.status).isGranted) { + // double check that permission is granted here, to guard against + // uploading corrupt assets without EXIF information + _log.warning( + "Media location permission is not granted. " + "Cannot access original assets for backup.", + ); + + return false; + } + + // DON'T KNOW WHY BUT THIS HELPS BACKGROUND BACKUP TO WORK ON IOS + if (Platform.isIOS) { + await _fileMediaRepository.requestExtendedPermissions(); + } + + return true; + } + + /// Upload images before video assets for background tasks + /// these are further sorted by using their creation date + List _sortPhotosFirst(List candidates) { + return candidates.sorted((a, b) { + final cmp = a.asset.type.index - b.asset.type.index; + if (cmp != 0) return cmp; + return a.asset.fileCreatedAt.compareTo(b.asset.fileCreatedAt); + }); + } + + Future backupAsset( + Iterable assets, + Completer cancelToken, { + bool isBackground = false, + PMProgressHandler? pmProgressHandler, + required void Function(SuccessUploadAsset result) onSuccess, + required void Function(int bytes, int totalBytes) onProgress, + required void Function(CurrentUploadAsset asset) onCurrentAsset, + required void Function(ErrorUploadAsset error) onError, + }) async { + final bool isIgnoreIcloudAssets = _appSetting.getSetting(AppSettingsEnum.ignoreIcloudAssets); + final shouldSyncAlbums = _appSetting.getSetting(AppSettingsEnum.syncAlbums); + final String deviceId = Store.get(StoreKey.deviceId); + final String savedEndpoint = Store.get(StoreKey.serverEndpoint); + final List duplicatedAssetIds = []; + bool anyErrors = false; + + final hasPermission = await _checkPermissions(); + if (!hasPermission) { + return false; + } + + List candidates = assets.toList(); + if (isBackground) { + candidates = _sortPhotosFirst(candidates); + } + + for (final candidate in candidates) { + final Asset asset = candidate.asset; + File? file; + File? livePhotoFile; + + try { + final isAvailableLocally = await asset.local!.isLocallyAvailable(isOrigin: true); + + // Handle getting files from iCloud + if (!isAvailableLocally && Platform.isIOS) { + // Skip iCloud assets if the user has disabled this feature + if (isIgnoreIcloudAssets) { + continue; + } + + onCurrentAsset( + CurrentUploadAsset( + id: asset.localId!, + fileCreatedAt: asset.fileCreatedAt.year == 1970 ? asset.fileModifiedAt : asset.fileCreatedAt, + fileName: asset.fileName, + fileType: _getAssetType(asset.type), + iCloudAsset: true, + ), + ); + + file = await asset.local!.loadFile(progressHandler: pmProgressHandler); + if (asset.local!.isLivePhoto) { + livePhotoFile = await asset.local!.loadFile(withSubtype: true, progressHandler: pmProgressHandler); + } + } else { + file = await asset.local!.originFile.timeout(const Duration(seconds: 5)); + + if (asset.local!.isLivePhoto) { + livePhotoFile = await asset.local!.originFileWithSubtype.timeout(const Duration(seconds: 5)); + } + } + + if (file != null) { + String? originalFileName = await _assetMediaRepository.getOriginalFilename(asset.localId!); + originalFileName ??= asset.fileName; + + if (asset.local!.isLivePhoto) { + if (livePhotoFile == null) { + _log.warning("Failed to obtain motion part of the livePhoto - $originalFileName"); + } + } + + final fileStream = file.openRead(); + final assetRawUploadData = MultipartFile( + "assetData", + fileStream, + file.lengthSync(), + filename: originalFileName, + ); + + final baseRequest = ProgressMultipartRequest( + 'POST', + Uri.parse('$savedEndpoint/assets'), + abortTrigger: cancelToken.future, + onProgress: ((bytes, totalBytes) => onProgress(bytes, totalBytes)), + ); + + baseRequest.fields['deviceAssetId'] = asset.localId!; + baseRequest.fields['deviceId'] = deviceId; + baseRequest.fields['fileCreatedAt'] = asset.fileCreatedAt.toUtc().toIso8601String(); + baseRequest.fields['fileModifiedAt'] = asset.fileModifiedAt.toUtc().toIso8601String(); + baseRequest.fields['isFavorite'] = asset.isFavorite.toString(); + baseRequest.fields['duration'] = asset.duration.toString(); + baseRequest.files.add(assetRawUploadData); + + onCurrentAsset( + CurrentUploadAsset( + id: asset.localId!, + fileCreatedAt: asset.fileCreatedAt.year == 1970 ? asset.fileModifiedAt : asset.fileCreatedAt, + fileName: originalFileName, + fileType: _getAssetType(asset.type), + fileSize: file.lengthSync(), + iCloudAsset: false, + ), + ); + + String? livePhotoVideoId; + if (asset.local!.isLivePhoto && livePhotoFile != null) { + livePhotoVideoId = await uploadLivePhotoVideo(originalFileName, livePhotoFile, baseRequest, cancelToken); + } + + if (livePhotoVideoId != null) { + baseRequest.fields['livePhotoVideoId'] = livePhotoVideoId; + } + + final response = await NetworkRepository.client.send(baseRequest); + + final responseBody = jsonDecode(await response.stream.bytesToString()); + + if (![200, 201].contains(response.statusCode)) { + final error = responseBody; + final errorMessage = error['message'] ?? error['error']; + + dPrint( + () => + "Error(${error['statusCode']}) uploading ${asset.localId} | $originalFileName | Created on ${asset.fileCreatedAt} | ${error['error']}", + ); + + onError( + ErrorUploadAsset( + asset: asset, + id: asset.localId!, + fileCreatedAt: asset.fileCreatedAt, + fileName: originalFileName, + fileType: _getAssetType(candidate.asset.type), + errorMessage: errorMessage, + ), + ); + + if (errorMessage == "Quota has been exceeded!") { + anyErrors = true; + break; + } + + continue; + } + + bool isDuplicate = false; + if (response.statusCode == 200) { + isDuplicate = true; + duplicatedAssetIds.add(asset.localId!); + } + + onSuccess( + SuccessUploadAsset( + candidate: candidate, + remoteAssetId: responseBody['id'] as String, + isDuplicate: isDuplicate, + ), + ); + + if (shouldSyncAlbums) { + await _albumService.syncUploadAlbums(candidate.albumNames, [responseBody['id'] as String]); + } + } + } on RequestAbortedException { + dPrint(() => "Backup was cancelled by the user"); + anyErrors = true; + break; + } catch (error, stackTrace) { + dPrint(() => "Error backup asset: ${error.toString()}: $stackTrace"); + anyErrors = true; + continue; + } finally { + if (Platform.isIOS) { + try { + await file?.delete(); + await livePhotoFile?.delete(); + } catch (e) { + dPrint(() => "ERROR deleting file: ${e.toString()}"); + } + } + } + } + + if (duplicatedAssetIds.isNotEmpty) { + await _saveDuplicatedAssetIds(duplicatedAssetIds); + } + + return !anyErrors; + } + + Future uploadLivePhotoVideo( + String originalFileName, + File? livePhotoVideoFile, + MultipartRequest baseRequest, + Completer cancelToken, + ) async { + if (livePhotoVideoFile == null) { + return null; + } + final livePhotoTitle = p.setExtension(originalFileName, p.extension(livePhotoVideoFile.path)); + final fileStream = livePhotoVideoFile.openRead(); + final livePhotoRawUploadData = MultipartFile( + "assetData", + fileStream, + livePhotoVideoFile.lengthSync(), + filename: livePhotoTitle, + ); + final livePhotoReq = ProgressMultipartRequest(baseRequest.method, baseRequest.url, abortTrigger: cancelToken.future) + ..headers.addAll(baseRequest.headers) + ..fields.addAll(baseRequest.fields); + + livePhotoReq.files.add(livePhotoRawUploadData); + + var response = await NetworkRepository.client.send(livePhotoReq); + + var responseBody = jsonDecode(await response.stream.bytesToString()); + + if (![200, 201].contains(response.statusCode)) { + var error = responseBody; + + dPrint( + () => "Error(${error['statusCode']}) uploading livePhoto for assetId | $livePhotoTitle | ${error['error']}", + ); + } + + return responseBody.containsKey('id') ? responseBody['id'] : null; + } + + String _getAssetType(AssetType assetType) => switch (assetType) { + AssetType.audio => "AUDIO", + AssetType.image => "IMAGE", + AssetType.video => "VIDEO", + AssetType.other => "OTHER", + }; +} diff --git a/mobile/lib/services/backup_album.service.dart b/mobile/lib/services/backup_album.service.dart new file mode 100644 index 0000000000..ef9d1031de --- /dev/null +++ b/mobile/lib/services/backup_album.service.dart @@ -0,0 +1,33 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; + +final backupAlbumServiceProvider = Provider((ref) { + return BackupAlbumService(ref.watch(backupAlbumRepositoryProvider)); +}); + +class BackupAlbumService { + final BackupAlbumRepository _backupAlbumRepository; + + const BackupAlbumService(this._backupAlbumRepository); + + Future> getAll({BackupAlbumSort? sort}) { + return _backupAlbumRepository.getAll(sort: sort); + } + + Future> getIdsBySelection(BackupSelection backup) { + return _backupAlbumRepository.getIdsBySelection(backup); + } + + Future> getAllBySelection(BackupSelection backup) { + return _backupAlbumRepository.getAllBySelection(backup); + } + + Future deleteAll(List ids) { + return _backupAlbumRepository.deleteAll(ids); + } + + Future updateAll(List backupAlbums) { + return _backupAlbumRepository.updateAll(backupAlbums); + } +} diff --git a/mobile/lib/services/backup_verification.service.dart b/mobile/lib/services/backup_verification.service.dart new file mode 100644 index 0000000000..2efd52cc81 --- /dev/null +++ b/mobile/lib/services/backup_verification.service.dart @@ -0,0 +1,192 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/infrastructure/utils/exif.converter.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/utils/bootstrap.dart'; +import 'package:immich_mobile/utils/diff.dart'; + +/// Finds duplicates originating from missing EXIF information +class BackupVerificationService { + final UserService _userService; + final FileMediaRepository _fileMediaRepository; + final AssetRepository _assetRepository; + final IsarExifRepository _exifInfoRepository; + + const BackupVerificationService( + this._userService, + this._fileMediaRepository, + this._assetRepository, + this._exifInfoRepository, + ); + + /// Returns at most [limit] assets that were backed up without exif + Future> findWronglyBackedUpAssets({int limit = 100}) async { + final owner = _userService.getMyUser().id; + final List onlyLocal = await _assetRepository.getAll(ownerId: owner, state: AssetState.local, limit: limit); + final List remoteMatches = await _assetRepository.getMatches( + assets: onlyLocal, + ownerId: owner, + state: AssetState.remote, + limit: limit, + ); + final List localMatches = await _assetRepository.getMatches( + assets: remoteMatches, + ownerId: owner, + state: AssetState.local, + limit: limit, + ); + + final List deleteCandidates = [], originals = []; + + await diffSortedLists( + remoteMatches, + localMatches, + compare: (a, b) => a.fileName.compareTo(b.fileName), + both: (a, b) async { + a.exifInfo = await _exifInfoRepository.get(a.id); + deleteCandidates.add(a); + originals.add(b); + return false; + }, + onlyFirst: (a) {}, + onlySecond: (b) {}, + ); + final isolateToken = ServicesBinding.rootIsolateToken!; + final List toDelete; + if (deleteCandidates.length > 10) { + // performs 2 checks in parallel for a nice speedup + final half = deleteCandidates.length ~/ 2; + final lower = compute(_computeSaveToDelete, ( + deleteCandidates: deleteCandidates.slice(0, half), + originals: originals.slice(0, half), + endpoint: Store.get(StoreKey.serverEndpoint), + rootIsolateToken: isolateToken, + fileMediaRepository: _fileMediaRepository, + )); + final upper = compute(_computeSaveToDelete, ( + deleteCandidates: deleteCandidates.slice(half), + originals: originals.slice(half), + endpoint: Store.get(StoreKey.serverEndpoint), + rootIsolateToken: isolateToken, + fileMediaRepository: _fileMediaRepository, + )); + toDelete = await lower + await upper; + } else { + toDelete = await compute(_computeSaveToDelete, ( + deleteCandidates: deleteCandidates, + originals: originals, + endpoint: Store.get(StoreKey.serverEndpoint), + rootIsolateToken: isolateToken, + fileMediaRepository: _fileMediaRepository, + )); + } + return toDelete; + } + + static Future> _computeSaveToDelete( + ({ + List deleteCandidates, + List originals, + String endpoint, + RootIsolateToken rootIsolateToken, + FileMediaRepository fileMediaRepository, + }) + tuple, + ) async { + assert(tuple.deleteCandidates.length == tuple.originals.length); + final List result = []; + BackgroundIsolateBinaryMessenger.ensureInitialized(tuple.rootIsolateToken); + final (isar, drift, logDb) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDb); + await tuple.fileMediaRepository.enableBackgroundAccess(); + final ApiService apiService = ApiService(); + apiService.setEndpoint(tuple.endpoint); + for (int i = 0; i < tuple.deleteCandidates.length; i++) { + if (await _compareAssets(tuple.deleteCandidates[i], tuple.originals[i], apiService)) { + result.add(tuple.deleteCandidates[i]); + } + } + return result; + } + + static Future _compareAssets(Asset remote, Asset local, ApiService apiService) async { + if (remote.checksum == local.checksum) return false; + ExifInfo? exif = remote.exifInfo; + if (exif != null && exif.latitude != null) return false; + if (exif == null || exif.fileSize == null) { + final dto = await apiService.assetsApi.getAssetInfo(remote.remoteId!); + if (dto != null && dto.exifInfo != null) { + exif = ExifDtoConverter.fromDto(dto.exifInfo!); + } + } + final file = await local.local!.originFile; + if (exif != null && file != null && exif.fileSize != null) { + final origSize = await file.length(); + if (exif.fileSize! == origSize || exif.fileSize! != origSize) { + final latLng = await local.local!.latlngAsync(); + + if (exif.latitude == null && + latLng.latitude != null && + (remote.fileCreatedAt.isAtSameMomentAs(local.fileCreatedAt) || + remote.fileModifiedAt.isAtSameMomentAs(local.fileModifiedAt) || + _sameExceptTimeZone(remote.fileCreatedAt, local.fileCreatedAt))) { + if (remote.type == AssetType.video) { + // it's very unlikely that a video of same length, filesize, name + // and date is wrong match. Cannot easily compare videos anyway + return true; + } + + // for images: make sure they are pixel-wise identical + // (skip first few KBs containing metadata) + final Uint64List localImage = _fakeDecodeImg(await file.readAsBytes()); + final res = await apiService.assetsApi.downloadAssetWithHttpInfo(remote.remoteId!); + final Uint64List remoteImage = _fakeDecodeImg(res.bodyBytes); + + final eq = const ListEquality().equals(remoteImage, localImage); + return eq; + } + } + } + + return false; + } + + static Uint64List _fakeDecodeImg(Uint8List bytes) { + const headerLength = 131072; // assume header is at most 128 KB + final start = bytes.length < headerLength * 2 ? (bytes.length ~/ (4 * 8)) * 8 : headerLength; + return bytes.buffer.asUint64List(start); + } + + static bool _sameExceptTimeZone(DateTime a, DateTime b) { + final ms = a.isAfter(b) + ? a.millisecondsSinceEpoch - b.millisecondsSinceEpoch + : b.millisecondsSinceEpoch - a.microsecondsSinceEpoch; + final x = ms / (1000 * 60 * 30); + final y = ms ~/ (1000 * 60 * 30); + return y.toDouble() == x && y < 24; + } +} + +final backupVerificationServiceProvider = Provider( + (ref) => BackupVerificationService( + ref.watch(userServiceProvider), + ref.watch(fileMediaRepositoryProvider), + ref.watch(assetRepositoryProvider), + ref.watch(exifRepositoryProvider), + ), +); diff --git a/mobile/lib/services/cleanup.service.dart b/mobile/lib/services/cleanup.service.dart index 06cda2cf0d..fca5584859 100644 --- a/mobile/lib/services/cleanup.service.dart +++ b/mobile/lib/services/cleanup.service.dart @@ -2,17 +2,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; final cleanupServiceProvider = Provider((ref) { - return CleanupService(ref.watch(driftProvider).localAssetRepository, ref.watch(assetMediaRepositoryProvider)); + return CleanupService(ref.watch(localAssetRepository), ref.watch(assetMediaRepositoryProvider)); }); class CleanupService { static final int _deleteBatchSize = CurrentPlatform.isAndroid ? 2000 : 10000; - final LocalAssetRepository _localAssetRepository; + final DriftLocalAssetRepository _localAssetRepository; final AssetMediaRepository _assetMediaRepository; const CleanupService(this._localAssetRepository, this._assetMediaRepository); @@ -46,7 +46,7 @@ class CleanupService { final deletedIds = await _assetMediaRepository.deleteAll(batch); if (deletedIds.isNotEmpty) { - await _localAssetRepository.deleteAssets(deletedIds); + await _localAssetRepository.delete(deletedIds); deletedCount += deletedIds.length; } } diff --git a/mobile/lib/services/deep_link.service.dart b/mobile/lib/services/deep_link.service.dart index 738cb78721..9d2bdbe4a0 100644 --- a/mobile/lib/services/deep_link.service.dart +++ b/mobile/lib/services/deep_link.service.dart @@ -7,27 +7,48 @@ import 'package:immich_mobile/domain/services/memory.service.dart'; import 'package:immich_mobile/domain/services/people.service.dart'; import 'package:immich_mobile/domain/services/remote_album.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.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/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/services/memory.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; final deepLinkServiceProvider = Provider( (ref) => DeepLinkService( + ref.watch(memoryServiceProvider), + ref.watch(assetServiceProvider), + ref.watch(albumServiceProvider), + ref.watch(currentAssetProvider.notifier), + ref.watch(currentAlbumProvider.notifier), + // Below is used for beta timeline ref.watch(timelineFactoryProvider), ref.watch(beta_asset_provider.assetServiceProvider), ref.watch(remoteAlbumServiceProvider), - DriftMemoryService(ref.watch(driftProvider).memoryRepository), + ref.watch(driftMemoryServiceProvider), ref.watch(driftPeopleServiceProvider), ref.watch(currentUserProvider), ), ); class DeepLinkService { + /// TODO: Remove this when beta is default + final MemoryService _memoryService; + final AssetService _assetService; + final AlbumService _albumService; + final CurrentAsset _currentAsset; + final CurrentAlbum _currentAlbum; + + /// Used for beta timeline final TimelineFactory _betaTimelineFactory; final beta_asset_service.AssetService _betaAssetService; final RemoteAlbumService _betaRemoteAlbumService; @@ -37,6 +58,11 @@ class DeepLinkService { final UserDto? _currentUser; const DeepLinkService( + this._memoryService, + this._assetService, + this._albumService, + this._currentAsset, + this._currentAlbum, this._betaTimelineFactory, this._betaAssetService, this._betaRemoteAlbumService, @@ -45,22 +71,42 @@ class DeepLinkService { this._currentUser, ); - Future handleScheme(PlatformDeepLink link, WidgetRef ref) async { + DeepLink _handleColdStart(PageRouteInfo route, bool isColdStart) { + return DeepLink([ + // we need something to segue back to if the app was cold started + // TODO: use MainTimelineRoute this when beta is default + if (isColdStart) (Store.isBetaTimelineEnabled) ? const TabShellRoute() : const PhotosRoute(), + route, + ]); + } + + Future handleScheme(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async { // get everything after the scheme, since Uri cannot parse path final intent = link.uri.host; final queryParams = link.uri.queryParameters; - return switch (intent) { - "memory" => await _buildMemoryDeepLink(queryParams['id']), + PageRouteInfo? deepLinkRoute = switch (intent) { + "memory" => await _buildMemoryDeepLink(queryParams['id'] ?? ''), "asset" => await _buildAssetDeepLink(queryParams['id'] ?? '', ref), "album" => await _buildAlbumDeepLink(queryParams['id'] ?? ''), "people" => await _buildPeopleDeepLink(queryParams['id'] ?? ''), "activity" => await _buildActivityDeepLink(queryParams['albumId'] ?? ''), _ => null, }; + + // Deep link resolution failed, safely handle it based on the app state + if (deepLinkRoute == null) { + if (isColdStart) { + return DeepLink.defaultPath; + } + + return DeepLink.none; + } + + return _handleColdStart(deepLinkRoute, isColdStart); } - Future handleMyImmichApp(PlatformDeepLink link, WidgetRef ref) async { + Future handleMyImmichApp(PlatformDeepLink link, WidgetRef ref, bool isColdStart) async { final path = link.uri.path; const uuidRegex = r'[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}'; @@ -68,75 +114,119 @@ class DeepLinkService { final albumRegex = RegExp('/albums/($uuidRegex)'); final peopleRegex = RegExp('/people/($uuidRegex)'); + PageRouteInfo? deepLinkRoute; if (assetRegex.hasMatch(path)) { final assetId = assetRegex.firstMatch(path)?.group(1) ?? ''; - // /albums//photos/ links carry the album context, - // which drives the like/comment UI in the viewer - final albumId = albumRegex.firstMatch(path)?.group(1); - return _buildAssetDeepLink(assetId, ref, albumId: albumId); - } - if (albumRegex.hasMatch(path)) { + deepLinkRoute = await _buildAssetDeepLink(assetId, ref); + } else if (albumRegex.hasMatch(path)) { final albumId = albumRegex.firstMatch(path)?.group(1) ?? ''; - return _buildAlbumDeepLink(albumId); - } - if (peopleRegex.hasMatch(path)) { + deepLinkRoute = await _buildAlbumDeepLink(albumId); + } else if (peopleRegex.hasMatch(path)) { final peopleId = peopleRegex.firstMatch(path)?.group(1) ?? ''; - return _buildPeopleDeepLink(peopleId); + deepLinkRoute = await _buildPeopleDeepLink(peopleId); + } else if (path == "/memory") { + deepLinkRoute = await _buildMemoryDeepLink(null); } - return null; + // Deep link resolution failed, safely handle it based on the app state + if (deepLinkRoute == null) { + if (isColdStart) return DeepLink.defaultPath; + return DeepLink.none; + } + + return _handleColdStart(deepLinkRoute, isColdStart); } Future _buildMemoryDeepLink(String? memoryId) async { - List memories = []; + if (Store.isBetaTimelineEnabled) { + List memories = []; - if (memoryId == null) { - if (_currentUser == null) { + if (memoryId == null) { + if (_currentUser == null) { + return null; + } + + memories = await _betaMemoryService.getMemoryLane(_currentUser.id); + } else { + final memory = await _betaMemoryService.get(memoryId); + if (memory != null) { + memories = [memory]; + } + } + + if (memories.isEmpty) { return null; } - memories = await _betaMemoryService.getMemoryLane(_currentUser.id); + return DriftMemoryRoute(memories: memories, memoryIndex: 0); } else { - final memory = await _betaMemoryService.get(memoryId); - if (memory != null) { - memories = [memory]; + // TODO: Remove this when beta is default + if (memoryId == null) { + return null; } - } + final memory = await _memoryService.getMemoryById(memoryId); - if (memories.isEmpty) { - return null; - } + if (memory == null) { + return null; + } - return DriftMemoryRoute(memories: memories, memoryIndex: 0); + return MemoryRoute(memories: [memory], memoryIndex: 0); + } } - Future _buildAssetDeepLink(String assetId, WidgetRef ref, {String? albumId}) async { - final asset = await _betaAssetService.getRemoteAsset(assetId); - if (asset == null) { - return null; + Future _buildAssetDeepLink(String assetId, WidgetRef ref) async { + if (Store.isBetaTimelineEnabled) { + final asset = await _betaAssetService.getRemoteAsset(assetId); + if (asset == null) { + return null; + } + + AssetViewer.setAsset(ref, asset); + return AssetViewerRoute( + initialIndex: 0, + timelineService: _betaTimelineFactory.fromAssets([asset], TimelineOrigin.deepLink), + ); + } else { + // TODO: Remove this when beta is default + final asset = await _assetService.getAssetByRemoteId(assetId); + if (asset == null) { + return null; + } + + _currentAsset.set(asset); + final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.auto); + + return GalleryViewerRoute(renderList: renderList, initialIndex: 0, heroOffset: 0, showStack: true); } - - final album = albumId != null ? await _betaRemoteAlbumService.get(albumId) : null; - - AssetViewer.setAsset(ref, asset); - return AssetViewerRoute( - initialIndex: 0, - timelineService: _betaTimelineFactory.fromAssets([asset], TimelineOrigin.deepLink), - currentAlbum: album, - ); } Future _buildAlbumDeepLink(String albumId) async { - final album = await _betaRemoteAlbumService.get(albumId); + if (Store.isBetaTimelineEnabled) { + final album = await _betaRemoteAlbumService.get(albumId); - if (album == null) { - return null; + if (album == null) { + return null; + } + + return RemoteAlbumRoute(album: album); + } else { + // TODO: Remove this when beta is default + final album = await _albumService.getAlbumByRemoteId(albumId); + + if (album == null) { + return null; + } + + _currentAlbum.set(album); + return AlbumViewerRoute(albumId: album.id); } - - return RemoteAlbumRoute(album: album); } Future _buildActivityDeepLink(String albumId) async { + if (Store.isBetaTimelineEnabled == false) { + return null; + } + final album = await _betaRemoteAlbumService.get(albumId); if (album == null || album.isActivityEnabled == false) { @@ -147,6 +237,10 @@ class DeepLinkService { } Future _buildPeopleDeepLink(String personId) async { + if (Store.isBetaTimelineEnabled == false) { + return null; + } + final person = await _betaPeopleService.get(personId); if (person == null) { diff --git a/mobile/lib/services/device.service.dart b/mobile/lib/services/device.service.dart new file mode 100644 index 0000000000..50a0d93b24 --- /dev/null +++ b/mobile/lib/services/device.service.dart @@ -0,0 +1,25 @@ +import 'package:flutter_udid/flutter_udid.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; + +final deviceServiceProvider = Provider((ref) => const DeviceService()); + +class DeviceService { + const DeviceService(); + + createDeviceId() { + return FlutterUdid.consistentUdid; + } + + /// Returns the device ID from local storage or creates a new one if not found. + /// + /// This method first attempts to retrieve the device ID from the local store using + /// [StoreKey.deviceId]. If no device ID is found (returns null), it generates a + /// new device ID by calling [createDeviceId]. + /// + /// Returns a [String] representing the device's unique identifier. + String getDeviceId() { + return Store.tryGet(StoreKey.deviceId) ?? createDeviceId(); + } +} diff --git a/mobile/lib/services/download.service.dart b/mobile/lib/services/download.service.dart index 09a56f0ce9..8e810ced2a 100644 --- a/mobile/lib/services/download.service.dart +++ b/mobile/lib/services/download.service.dart @@ -1,15 +1,16 @@ -// ignore_for_file: avoid_slow_async_io - -import 'dart:async'; import 'dart:io'; import 'package:background_downloader/background_downloader.dart'; -import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; import 'package:logging/logging.dart'; final downloadServiceProvider = Provider( @@ -25,26 +26,11 @@ class DownloadService { void Function(TaskStatusUpdate)? onLivePhotoDownloadStatus; void Function(TaskProgressUpdate)? onTaskProgress; - /// Active Live Photo IDs undergoing saving - final Set _savingLivePhotoIds = {}; - DownloadService(this._fileMediaRepository, this._downloadRepository) { _downloadRepository.onImageDownloadStatus = _onImageDownloadCallback; _downloadRepository.onVideoDownloadStatus = _onVideoDownloadCallback; _downloadRepository.onLivePhotoDownloadStatus = _onLivePhotoDownloadCallback; _downloadRepository.onTaskProgress = _onTaskProgressCallback; - _downloadRepository.onLivePhotoRecordComplete = _onLivePhotoRecordComplete; - - unawaited(_savePreviouslyCompletedLivePhotos()); - } - - Future _savePreviouslyCompletedLivePhotos() async { - // Specifically fetch Live Photo video components only, as to not double fetch assets - final records = await _downloadRepository.getLiveVideoTasks(); - final completedIds = records.map((record) => LivePhotosMetadata.fromJson(record.task.metaData).id).toSet(); - for (final id in completedIds) { - await _saveLivePhotos(id); - } } void _onTaskProgressCallback(TaskProgressUpdate update) { @@ -52,18 +38,10 @@ class DownloadService { } void _onImageDownloadCallback(TaskStatusUpdate update) { - if (update.status == TaskStatus.complete) { - unawaited(_saveImageWithPath(update.task)); - } - onImageDownloadStatus?.call(update); } void _onVideoDownloadCallback(TaskStatusUpdate update) { - if (update.status == TaskStatus.complete) { - unawaited(_saveVideo(update.task)); - } - onVideoDownloadStatus?.call(update); } @@ -71,17 +49,12 @@ class DownloadService { onLivePhotoDownloadStatus?.call(update); } - Future _onLivePhotoRecordComplete(TaskRecord record) async { - final livePhotosId = LivePhotosMetadata.fromJson(record.task.metaData).id; - await _saveLivePhotos(livePhotosId); - } - - Future _saveImageWithPath(Task task) async { + Future saveImageWithPath(Task task) async { final filePath = await task.filePath(); final title = task.filename; final relativePath = Platform.isAndroid ? 'DCIM/Immich' : null; try { - final resultAsset = await _fileMediaRepository.saveImageWithFile( + final Asset? resultAsset = await _fileMediaRepository.saveImageWithFile( filePath, title: title, relativePath: relativePath, @@ -97,13 +70,13 @@ class DownloadService { } } - Future _saveVideo(Task task) async { + Future saveVideo(Task task) async { final filePath = await task.filePath(); final title = task.filename; final relativePath = Platform.isAndroid ? 'DCIM/Immich' : null; final file = File(filePath); try { - final resultAsset = await _fileMediaRepository.saveVideo(file, title: title, relativePath: relativePath); + final Asset? resultAsset = await _fileMediaRepository.saveVideo(file, title: title, relativePath: relativePath); return resultAsset != null; } catch (error, stack) { _log.severe("Error saving video", error, stack); @@ -115,21 +88,14 @@ class DownloadService { } } - Future _saveLivePhotos(String livePhotosId) async { + Future saveLivePhotos(Task task, String livePhotosId) async { final records = await _downloadRepository.getLiveVideoTasks(); + if (records.length < 2) { + return false; + } + final imageRecord = _findTaskRecord(records, livePhotosId, LivePhotosPart.image); final videoRecord = _findTaskRecord(records, livePhotosId, LivePhotosPart.video); - - if (imageRecord == null || videoRecord == null) { - return false; - } - - // Write semaphore for this `livePhotoId` - if (!_savingLivePhotoIds.add(livePhotosId)) { - return false; - } - - final title = imageRecord.task.filename; final imageFilePath = await imageRecord.task.filePath(); final videoFilePath = await videoRecord.task.filePath(); @@ -137,14 +103,14 @@ class DownloadService { final result = await _fileMediaRepository.saveLivePhoto( image: File(imageFilePath), video: File(videoFilePath), - title: title, + title: task.filename, ); return result != null; } on PlatformException catch (error, stack) { // Handle saving MotionPhotos on iOS if (error.code.startsWith('PHPhotosErrorDomain')) { - final result = await _fileMediaRepository.saveImageWithFile(imageFilePath, title: title); + final result = await _fileMediaRepository.saveImageWithFile(imageFilePath, title: task.filename); return result != null; } _log.severe("Error saving live photo", error, stack); @@ -164,17 +130,72 @@ class DownloadService { } await _downloadRepository.deleteRecordsWithIds([imageRecord.task.taskId, videoRecord.task.taskId]); - _savingLivePhotoIds.remove(livePhotosId); } } Future cancelDownload(String id) async { return await FileDownloader().cancelTaskWithId(id); } + + Future> downloadAll(List assets) async { + return await _downloadRepository.downloadAll(assets.expand(_createDownloadTasks).toList()); + } + + Future download(Asset asset) async { + final tasks = _createDownloadTasks(asset); + await _downloadRepository.downloadAll(tasks); + } + + List _createDownloadTasks(Asset asset) { + if (asset.isImage && asset.livePhotoVideoId != null && Platform.isIOS) { + return [ + _buildDownloadTask( + asset.remoteId!, + asset.fileName, + group: kDownloadGroupLivePhoto, + metadata: LivePhotosMetadata(part: LivePhotosPart.image, id: asset.remoteId!).toJson(), + ), + _buildDownloadTask( + asset.livePhotoVideoId!, + asset.fileName.toUpperCase().replaceAll(RegExp(r"\.(JPG|HEIC)$"), '.MOV'), + group: kDownloadGroupLivePhoto, + metadata: LivePhotosMetadata(part: LivePhotosPart.video, id: asset.remoteId!).toJson(), + ), + ]; + } + + if (asset.remoteId == null) { + return []; + } + + return [ + _buildDownloadTask( + asset.remoteId!, + asset.fileName, + group: asset.isImage ? kDownloadGroupImage : kDownloadGroupVideo, + ), + ]; + } + + DownloadTask _buildDownloadTask(String id, String filename, {String? group, String? metadata}) { + final path = r'/assets/{id}/original'.replaceAll('{id}', id); + final serverEndpoint = Store.get(StoreKey.serverEndpoint); + final headers = ApiService.getRequestHeaders(); + + return DownloadTask( + taskId: id, + url: serverEndpoint + path, + headers: headers, + filename: filename, + updates: Updates.statusAndProgress, + group: group ?? '', + metaData: metadata ?? '', + ); + } } -TaskRecord? _findTaskRecord(List records, String livePhotosId, LivePhotosPart part) { - return records.firstWhereOrNull((record) { +TaskRecord _findTaskRecord(List records, String livePhotosId, LivePhotosPart part) { + return records.firstWhere((record) { final metadata = LivePhotosMetadata.fromJson(record.task.metaData); return metadata.id == livePhotosId && metadata.part == part; }); diff --git a/mobile/lib/services/entity.service.dart b/mobile/lib/services/entity.service.dart new file mode 100644 index 0000000000..fe7358fce6 --- /dev/null +++ b/mobile/lib/services/entity.service.dart @@ -0,0 +1,44 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; + +class EntityService { + final AssetRepository _assetRepository; + final IsarUserRepository _isarUserRepository; + const EntityService(this._assetRepository, this._isarUserRepository); + + Future fillAlbumWithDatabaseEntities(Album album) async { + final ownerId = album.ownerId; + if (ownerId != null) { + // replace owner with user from database + final user = await _isarUserRepository.getByUserId(ownerId); + album.owner.value = user == null ? null : User.fromDto(user); + } + final thumbnailAssetId = album.remoteThumbnailAssetId ?? album.thumbnail.value?.remoteId; + if (thumbnailAssetId != null) { + // set thumbnail with asset from database + album.thumbnail.value = await _assetRepository.getByRemoteId(thumbnailAssetId); + } + if (album.remoteUsers.isNotEmpty) { + // replace all users with users from database + final users = await _isarUserRepository.getByUserIds(album.remoteUsers.map((user) => user.id).toList()); + album.sharedUsers.clear(); + album.sharedUsers.addAll(users.nonNulls.map(User.fromDto)); + album.shared = true; + } + if (album.remoteAssets.isNotEmpty) { + // replace all assets with assets from database + final assets = await _assetRepository.getAllByRemoteId(album.remoteAssets.map((asset) => asset.remoteId!)); + album.assets.clear(); + album.assets.addAll(assets); + } + return album; + } +} + +final entityServiceProvider = Provider( + (ref) => EntityService(ref.watch(assetRepositoryProvider), ref.watch(userRepositoryProvider)), +); diff --git a/mobile/lib/services/etag.service.dart b/mobile/lib/services/etag.service.dart new file mode 100644 index 0000000000..00eb83fcea --- /dev/null +++ b/mobile/lib/services/etag.service.dart @@ -0,0 +1,14 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/repositories/etag.repository.dart'; + +final etagServiceProvider = Provider((ref) => ETagService(ref.watch(etagRepositoryProvider))); + +class ETagService { + final ETagRepository _eTagRepository; + + const ETagService(this._eTagRepository); + + Future clearTable() { + return _eTagRepository.clearTable(); + } +} diff --git a/mobile/lib/services/exif.service.dart b/mobile/lib/services/exif.service.dart new file mode 100644 index 0000000000..57f793b21e --- /dev/null +++ b/mobile/lib/services/exif.service.dart @@ -0,0 +1,15 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; + +final exifServiceProvider = Provider((ref) => ExifService(ref.watch(exifRepositoryProvider))); + +class ExifService { + final IsarExifRepository _exifInfoRepository; + + const ExifService(this._exifInfoRepository); + + Future clearTable() { + return _exifInfoRepository.deleteAll(); + } +} diff --git a/mobile/lib/services/folder.service.dart b/mobile/lib/services/folder.service.dart index 011acbe869..91fb455110 100644 --- a/mobile/lib/services/folder.service.dart +++ b/mobile/lib/services/folder.service.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.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/repositories/folder_api.repository.dart'; @@ -18,24 +18,22 @@ class FolderService { final paths = await _folderApiRepository.getAllUniquePaths(); // Create folder structure - final Map> folderMap = {}; + Map> folderMap = {}; for (String fullPath in paths) { - if (fullPath == '/') { - continue; - } + if (fullPath == '/') continue; // Ensure the path starts with a slash if (!fullPath.startsWith('/')) { fullPath = '/$fullPath'; } - final List segments = fullPath.split('/')..removeWhere((s) => s.isEmpty); + List segments = fullPath.split('/')..removeWhere((s) => s.isEmpty); String currentPath = ''; for (int i = 0; i < segments.length; i++) { - final String parentPath = currentPath.isEmpty ? '_root_' : currentPath; + String parentPath = currentPath.isEmpty ? '_root_' : currentPath; currentPath = i == 0 ? '/${segments[i]}' : '$currentPath/${segments[i]}'; if (!folderMap.containsKey(parentPath)) { @@ -55,7 +53,7 @@ class FolderService { } void attachSubfolders(RecursiveFolder folder) { - final String fullPath = folder.path.isEmpty ? '/${folder.name}' : '${folder.path}/${folder.name}'; + String fullPath = folder.path.isEmpty ? '/${folder.name}' : '${folder.path}/${folder.name}'; if (folderMap.containsKey(fullPath)) { folder.subfolders.addAll(folderMap[fullPath]!); @@ -67,7 +65,7 @@ class FolderService { } } - final List rootSubfolders = folderMap['_root_'] ?? []; + List rootSubfolders = folderMap['_root_'] ?? []; // Sort root subfolders based on order parameter rootSubfolders.sort((a, b) => order == SortOrder.desc ? b.name.compareTo(a.name) : a.name.compareTo(b.name)); @@ -78,17 +76,17 @@ class FolderService { return RootFolder(subfolders: rootSubfolders, path: '/'); } - Future> getFolderAssets(RootFolder folder, SortOrder order) async { + Future> getFolderAssets(RootFolder folder, SortOrder order) async { try { if (folder is RecursiveFolder) { String fullPath = folder.path.isEmpty ? folder.name : '${folder.path}/${folder.name}'; fullPath = fullPath[0] == '/' ? fullPath.substring(1) : fullPath; - final result = await _folderApiRepository.getAssetsForPath(fullPath); + var result = await _folderApiRepository.getAssetsForPath(fullPath); if (order == SortOrder.desc) { - result.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + result.sort((a, b) => b.fileCreatedAt.compareTo(a.fileCreatedAt)); } else { - result.sort((a, b) => a.createdAt.compareTo(b.createdAt)); + result.sort((a, b) => a.fileCreatedAt.compareTo(b.fileCreatedAt)); } return result; diff --git a/mobile/lib/services/foreground_upload.service.dart b/mobile/lib/services/foreground_upload.service.dart index ac42e2482f..ce02c9c56b 100644 --- a/mobile/lib/services/foreground_upload.service.dart +++ b/mobile/lib/services/foreground_upload.service.dart @@ -2,26 +2,24 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' hide AssetVisibility; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/extensions/network_capability_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/network_capability_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/repositories/asset_media.repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:logging/logging.dart'; -import 'package:openapi/api.dart'; import 'package:path/path.dart' as p; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; @@ -36,12 +34,12 @@ class UploadCallbacks { } final foregroundUploadServiceProvider = Provider((ref) { - // ignore: dispose-provided-instances return ForegroundUploadService( ref.watch(uploadRepositoryProvider), ref.watch(storageRepositoryProvider), - ref.watch(driftProvider).backupRepository, + ref.watch(backupRepositoryProvider), ref.watch(connectivityApiProvider), + ref.watch(appSettingsServiceProvider), ref.watch(assetMediaRepositoryProvider), ); }); @@ -57,13 +55,15 @@ class ForegroundUploadService { this._storageRepository, this._backupRepository, this._connectivityApi, + this._appSettingsService, this._assetMediaRepository, ); final UploadRepository _uploadRepository; final StorageRepository _storageRepository; - final BackupRepository _backupRepository; + final DriftBackupRepository _backupRepository; final ConnectivityApi _connectivityApi; + final AppSettingsService _appSettingsService; final AssetMediaRepository _assetMediaRepository; final Logger _logger = Logger('ForegroundUploadService'); @@ -103,7 +103,7 @@ class ForegroundUploadService { final requireWifi = _shouldRequireWiFi(asset); return requireWifi && !hasWifi; }, - processItem: (asset) => uploadSingleAsset(asset, cancelToken, callbacks: callbacks), + processItem: (asset) => _uploadSingleAsset(asset, cancelToken, callbacks: callbacks), ); } } @@ -129,7 +129,7 @@ class ForegroundUploadService { continue; } - await uploadSingleAsset(asset, cancelToken, callbacks: callbacks); + await _uploadSingleAsset(asset, cancelToken, callbacks: callbacks); } } @@ -146,7 +146,7 @@ class ForegroundUploadService { await _executeWithWorkerPool( items: localAssets, cancelToken: cancelToken, - processItem: (asset) => uploadSingleAsset(asset, cancelToken, callbacks: callbacks), + processItem: (asset) => _uploadSingleAsset(asset, cancelToken, callbacks: callbacks), ); } @@ -155,7 +155,7 @@ class ForegroundUploadService { List files, { Completer? cancelToken, void Function(String fileId, int bytes, int totalBytes)? onProgress, - void Function(String fileId, String remoteAssetId)? onSuccess, + void Function(String fileId)? onSuccess, void Function(String fileId, String errorMessage)? onError, }) async { if (files.isEmpty) { @@ -175,7 +175,7 @@ class ForegroundUploadService { ); if (result.isSuccess) { - onSuccess?.call(fileId, result.remoteAssetId!); + onSuccess?.call(fileId); } else if (!result.isCancelled && result.errorMessage != null) { onError?.call(fileId, result.errorMessage!); } @@ -236,23 +236,21 @@ class ForegroundUploadService { await Future.wait(workerFutures); } - @visibleForTesting - Future uploadSingleAsset( + Future _uploadSingleAsset( LocalAsset asset, Completer? cancelToken, { required UploadCallbacks callbacks, }) async { - final t = StaticTranslations.instance; - final assetNotFoundOnDevice = CurrentPlatform.isAndroid - ? t.asset_not_found_on_device_android - : t.asset_not_found_on_device_ios; File? file; File? livePhotoFile; try { final entity = await _storageRepository.getAssetEntityForAsset(asset); if (entity == null) { - callbacks.onError?.call(asset.localId!, assetNotFoundOnDevice); + callbacks.onError?.call( + asset.localId!, + CurrentPlatform.isAndroid ? "asset_not_found_on_device_android".t() : "asset_not_found_on_device_ios".t(), + ); return; } @@ -286,7 +284,10 @@ class ForegroundUploadService { file = await _storageRepository.getFileForAsset(asset.id); if (file == null) { _logger.warning("Failed to get file ${asset.id} - ${asset.name}"); - callbacks.onError?.call(asset.localId!, assetNotFoundOnDevice); + callbacks.onError?.call( + asset.localId!, + CurrentPlatform.isAndroid ? "asset_not_found_on_device_android".t() : "asset_not_found_on_device_ios".t(), + ); return; } @@ -295,31 +296,40 @@ class ForegroundUploadService { livePhotoFile = await _storageRepository.getMotionFileForAsset(asset); if (livePhotoFile == null) { _logger.warning("Failed to obtain motion part of the livePhoto - ${asset.name}"); - callbacks.onError?.call(asset.localId!, assetNotFoundOnDevice); + callbacks.onError?.call( + asset.localId!, + CurrentPlatform.isAndroid ? "asset_not_found_on_device_android".t() : "asset_not_found_on_device_ios".t(), + ); } } } if (file == null) { _logger.warning("Failed to obtain file from iCloud for asset ${asset.id} - ${asset.name}"); - callbacks.onError?.call(asset.localId!, t.asset_not_found_on_icloud); + callbacks.onError?.call(asset.localId!, "asset_not_found_on_icloud".t()); return; } - final fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; - // Some apps (e.g. DJI/Fusion) return names without an extension; fall back to the asset name for those. - final extension = p.extension(file.path).isNotEmpty ? p.extension(file.path) : p.extension(asset.name); - final originalFileName = p.setExtension(fileName, extension); + String fileName = await _assetMediaRepository.getOriginalFilename(asset.id) ?? asset.name; + + /// Handle special file name from DJI or Fusion app + /// If the file name has no extension, likely due to special renaming template by specific apps + /// we append the original extension from the asset name + final hasExtension = p.extension(fileName).isNotEmpty; + if (!hasExtension) { + fileName = p.setExtension(fileName, p.extension(asset.name)); + } + + final originalFileName = entity.isLivePhoto ? p.setExtension(fileName, p.extension(file.path)) : fileName; final deviceId = Store.get(StoreKey.deviceId); final fields = { - // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': asset.localId!, 'deviceId': deviceId, 'fileCreatedAt': asset.createdAt.toUtc().toIso8601String(), 'fileModifiedAt': asset.updatedAt.toUtc().toIso8601String(), 'isFavorite': asset.isFavorite.toString(), - 'duration': (asset.durationMs ?? 0).toString(), + 'duration': asset.duration.toString(), }; // Upload live photo video first if available @@ -331,8 +341,7 @@ class ForegroundUploadService { final livePhotoResult = await _uploadRepository.uploadFile( file: livePhotoFile, originalFileName: livePhotoTitle, - // Visibility hidden on upload to prevent the server from running regular jobs on the live photo asset - fields: {...fields, 'visibility': AssetVisibility.hidden.toString()}, + fields: fields, cancelToken: cancelToken, onProgress: onProgress != null ? (bytes, totalBytes) => onProgress(asset.localId!, livePhotoTitle, bytes, totalBytes) @@ -380,6 +389,7 @@ class ForegroundUploadService { if (result.isSuccess && result.remoteAssetId != null) { callbacks.onSuccess?.call(asset.localId!, result.remoteAssetId!); } else if (result.isCancelled) { + _logger.warning(() => "Backup was cancelled by the user"); shouldAbortUpload = true; } else if (result.errorMessage != null) { _logger.severe( @@ -394,7 +404,7 @@ class ForegroundUploadService { } } } catch (error, stackTrace) { - _logger.severe(() => "Error backup asset: $error", stackTrace); + _logger.severe(() => "Error backup asset: ${error.toString()}", stackTrace); callbacks.onError?.call(asset.localId!, error.toString()); } finally { if (Platform.isIOS) { @@ -402,7 +412,7 @@ class ForegroundUploadService { await file?.delete(); await livePhotoFile?.delete(); } catch (error, stackTrace) { - _logger.severe(() => "ERROR deleting file: $error", stackTrace); + _logger.severe(() => "ERROR deleting file: ${error.toString()}", stackTrace); } } } @@ -415,14 +425,12 @@ class ForegroundUploadService { void Function(int bytes, int totalBytes)? onProgress, }) async { try { - // ignore: avoid_slow_async_io final stats = await file.stat(); final fileCreatedAt = stats.changed; final fileModifiedAt = stats.modified; final filename = p.basename(file.path); final fields = { - // deviceAssetId/deviceId required by server v2.7.5 and below (drop in v4.0 per #27818). 'deviceAssetId': deviceAssetId, 'deviceId': Store.get(StoreKey.deviceId), 'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(), @@ -445,13 +453,14 @@ class ForegroundUploadService { } bool _shouldRequireWiFi(LocalAsset asset) { - final backup = SettingsRepository.instance.appConfig.backup; - if (asset.isVideo && backup.useCellularForVideos) { - return false; + bool requiresWiFi = true; + + if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) { + requiresWiFi = false; + } else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) { + requiresWiFi = false; } - if (!asset.isVideo && backup.useCellularForPhotos) { - return false; - } - return true; + + return requiresWiFi; } } diff --git a/mobile/lib/services/gcast.service.dart b/mobile/lib/services/gcast.service.dart index d9fc44a34d..dcf7685237 100644 --- a/mobile/lib/services/gcast.service.dart +++ b/mobile/lib/services/gcast.service.dart @@ -62,6 +62,7 @@ class GCastService { switch (message['type']) { case "MEDIA_STATUS": _handleMediaStatus(message); + break; } } @@ -76,10 +77,13 @@ class GCastService { switch (status['playerState']) { case "PLAYING": onCastState?.call(CastState.playing); + break; case "PAUSED": onCastState?.call(CastState.paused); + break; case "BUFFERING": onCastState?.call(CastState.buffering); + break; case "IDLE": onCastState?.call(CastState.idle); @@ -87,6 +91,8 @@ class GCastService { if (status["idleReason"] == "FINISHED") { _mediaStatusPollingTimer?.cancel(); } + + break; } if (status["media"] != null && status["media"]["duration"] != null) { @@ -141,7 +147,7 @@ class GCastService { return bufferedExpiration.isAfter(DateTime.now()); } - Future loadMedia(RemoteAsset asset, bool reload) async { + void loadMedia(RemoteAsset asset, bool reload) async { if (!isConnected) { return; } else if (asset.id == currentAssetId && !reload) { diff --git a/mobile/lib/services/hash.service.dart b/mobile/lib/services/hash.service.dart new file mode 100644 index 0000000000..9d1f4e51e8 --- /dev/null +++ b/mobile/lib/services/hash.service.dart @@ -0,0 +1,191 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/device_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/device_asset.provider.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:logging/logging.dart'; + +class HashService { + HashService({ + required IsarDeviceAssetRepository deviceAssetRepository, + required BackgroundService backgroundService, + this.batchSizeLimit = kBatchHashSizeLimit, + int? batchFileLimit, + }) : _deviceAssetRepository = deviceAssetRepository, + _backgroundService = backgroundService, + batchFileLimit = batchFileLimit ?? kBatchHashFileLimit; + + final IsarDeviceAssetRepository _deviceAssetRepository; + final BackgroundService _backgroundService; + final int batchSizeLimit; + final int batchFileLimit; + final _log = Logger('HashService'); + + /// Processes a list of local [Asset]s, storing their hash and returning only those + /// that were successfully hashed. Hashes are looked up in a DB table + /// [DeviceAsset] by local id. Only missing entries are newly hashed and added to the DB table. + Future> hashAssets(List assets) async { + assets.sort(Asset.compareByLocalId); + + // Get and sort DB entries - guaranteed to be a subset of assets + final hashesInDB = await _deviceAssetRepository.getByIds(assets.map((a) => a.localId!).toList()); + hashesInDB.sort((a, b) => a.assetId.compareTo(b.assetId)); + + int dbIndex = 0; + int bytesProcessed = 0; + final hashedAssets = []; + final toBeHashed = <_AssetPath>[]; + final toBeDeleted = []; + + for (int assetIndex = 0; assetIndex < assets.length; assetIndex++) { + final asset = assets[assetIndex]; + DeviceAsset? matchingDbEntry; + + if (dbIndex < hashesInDB.length) { + final deviceAsset = hashesInDB[dbIndex]; + if (deviceAsset.assetId == asset.localId) { + matchingDbEntry = deviceAsset; + dbIndex++; + } + } + + if (matchingDbEntry != null && + matchingDbEntry.hash.isNotEmpty && + matchingDbEntry.modifiedTime.isAtSameMomentAs(asset.fileModifiedAt)) { + // Reuse the existing hash + hashedAssets.add(asset.copyWith(checksum: base64.encode(matchingDbEntry.hash))); + continue; + } + + final file = await _tryGetAssetFile(asset); + if (file == null) { + // Can't access file, delete any DB entry + if (matchingDbEntry != null) { + toBeDeleted.add(matchingDbEntry.assetId); + } + continue; + } + + bytesProcessed += await file.length(); + toBeHashed.add(_AssetPath(asset: asset, path: file.path)); + + if (_shouldProcessBatch(toBeHashed.length, bytesProcessed)) { + hashedAssets.addAll(await _processBatch(toBeHashed, toBeDeleted)); + toBeHashed.clear(); + toBeDeleted.clear(); + bytesProcessed = 0; + } + } + assert(dbIndex == hashesInDB.length, "All hashes should've been processed"); + + // Process any remaining files + if (toBeHashed.isNotEmpty) { + hashedAssets.addAll(await _processBatch(toBeHashed, toBeDeleted)); + } + + // Clean up deleted references + if (toBeDeleted.isNotEmpty) { + await _deviceAssetRepository.deleteIds(toBeDeleted); + } + + return hashedAssets; + } + + bool _shouldProcessBatch(int assetCount, int bytesProcessed) => + assetCount >= batchFileLimit || bytesProcessed >= batchSizeLimit; + + Future _tryGetAssetFile(Asset asset) async { + try { + final file = await asset.local!.originFile; + if (file == null) { + _log.warning( + "Failed to get file for asset ${asset.localId ?? ''}, name: ${asset.fileName}, created on: ${asset.fileCreatedAt}, skipping", + ); + return null; + } + return file; + } catch (error, stackTrace) { + _log.warning( + "Error getting file to hash for asset ${asset.localId ?? ''}, name: ${asset.fileName}, created on: ${asset.fileCreatedAt}, skipping", + error, + stackTrace, + ); + return null; + } + } + + /// Processes a batch of files and returns a list of successfully hashed assets after saving + /// them in [DeviceAssetToHash] for future retrieval + Future> _processBatch(List<_AssetPath> toBeHashed, List toBeDeleted) async { + _log.info("Hashing ${toBeHashed.length} files"); + final hashes = await _hashFiles(toBeHashed.map((e) => e.path).toList()); + assert( + hashes.length == toBeHashed.length, + "Number of Hashes returned from platform should be the same as the input", + ); + + final hashedAssets = []; + final toBeAdded = []; + + for (final (index, hash) in hashes.indexed) { + final asset = toBeHashed.elementAtOrNull(index)?.asset; + if (asset != null && hash?.length == 20) { + hashedAssets.add(asset.copyWith(checksum: base64.encode(hash!))); + toBeAdded.add(DeviceAsset(assetId: asset.localId!, hash: hash, modifiedTime: asset.fileModifiedAt)); + } else { + _log.warning("Failed to hash file ${asset?.localId ?? ''}"); + if (asset != null) { + toBeDeleted.add(asset.localId!); + } + } + } + + // Update the DB for future retrieval + await _deviceAssetRepository.transaction(() async { + await _deviceAssetRepository.updateAll(toBeAdded); + await _deviceAssetRepository.deleteIds(toBeDeleted); + }); + + _log.fine("Hashed ${hashedAssets.length}/${toBeHashed.length} assets"); + return hashedAssets; + } + + /// Hashes the given files and returns a list of the same length. + /// Files that could not be hashed will have a `null` value + Future> _hashFiles(List paths) async { + try { + final hashes = await _backgroundService.digestFiles(paths); + if (hashes != null) { + return hashes; + } + _log.severe("Hashing ${paths.length} files failed"); + } catch (e, s) { + _log.severe("Error occurred while hashing assets", e, s); + } + return List.filled(paths.length, null); + } +} + +class _AssetPath { + final Asset asset; + final String path; + + const _AssetPath({required this.asset, required this.path}); + + _AssetPath copyWith({Asset? asset, String? path}) { + return _AssetPath(asset: asset ?? this.asset, path: path ?? this.path); + } +} + +final hashServiceProvider = Provider( + (ref) => HashService( + deviceAssetRepository: ref.watch(deviceAssetRepositoryProvider), + backgroundService: ref.watch(backgroundServiceProvider), + ), +); diff --git a/mobile/lib/services/immich_logger.service.dart b/mobile/lib/services/immich_logger.service.dart index bfc9bd9b51..fab4b9966a 100644 --- a/mobile/lib/services/immich_logger.service.dart +++ b/mobile/lib/services/immich_logger.service.dart @@ -39,10 +39,6 @@ abstract final class ImmichLogger { await io.close(); } - if (!context.mounted) { - return; - } - final box = context.findRenderObject() as RenderBox?; // Share file diff --git a/mobile/lib/services/local_files_manager.service.dart b/mobile/lib/services/local_files_manager.service.dart new file mode 100644 index 0000000000..0cc00f3e4b --- /dev/null +++ b/mobile/lib/services/local_files_manager.service.dart @@ -0,0 +1,66 @@ +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:logging/logging.dart'; + +final localFileManagerServiceProvider = Provider((ref) => const LocalFilesManagerService()); + +class LocalFilesManagerService { + const LocalFilesManagerService(); + + static final Logger _logger = Logger('LocalFilesManager'); + static const MethodChannel _channel = MethodChannel('file_trash'); + + Future moveToTrash(List mediaUrls) async { + try { + return await _channel.invokeMethod('moveToTrash', {'mediaUrls': mediaUrls}); + } catch (e, s) { + _logger.warning('Error moving file to trash', e, s); + return false; + } + } + + Future restoreFromTrash(String fileName, int type) async { + try { + return await _channel.invokeMethod('restoreFromTrash', {'fileName': fileName, 'type': type}); + } catch (e, s) { + _logger.warning('Error restore file from trash', e, s); + return false; + } + } + + Future restoreFromTrashById(String mediaId, int type) async { + try { + return await _channel.invokeMethod('restoreFromTrash', {'mediaId': mediaId, 'type': type}); + } catch (e, s) { + _logger.warning('Error restore file from trash by Id', e, s); + return false; + } + } + + Future requestManageMediaPermission() async { + try { + return await _channel.invokeMethod('requestManageMediaPermission'); + } catch (e, s) { + _logger.warning('Error requesting manage media permission', e, s); + return false; + } + } + + Future hasManageMediaPermission() async { + try { + return await _channel.invokeMethod('hasManageMediaPermission'); + } catch (e, s) { + _logger.warning('Error requesting manage media permission state', e, s); + return false; + } + } + + Future manageMediaPermission() async { + try { + return await _channel.invokeMethod('manageMediaPermission'); + } catch (e, s) { + _logger.warning('Error requesting manage media permission settings', e, s); + return false; + } + } +} diff --git a/mobile/lib/services/local_notification.service.dart b/mobile/lib/services/local_notification.service.dart new file mode 100644 index 0000000000..bf85f4a9a9 --- /dev/null +++ b/mobile/lib/services/local_notification.service.dart @@ -0,0 +1,118 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/notification_permission.provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +final localNotificationService = Provider( + (ref) => LocalNotificationService(ref.watch(notificationPermissionProvider), ref), +); + +class LocalNotificationService { + final FlutterLocalNotificationsPlugin _localNotificationPlugin = FlutterLocalNotificationsPlugin(); + final PermissionStatus _permissionStatus; + final Ref ref; + + LocalNotificationService(this._permissionStatus, this.ref); + + static const manualUploadNotificationID = 4; + static const manualUploadDetailedNotificationID = 5; + static const manualUploadChannelName = 'Manual Asset Upload'; + static const manualUploadChannelID = 'immich/manualUpload'; + static const manualUploadChannelNameDetailed = 'Manual Asset Upload Detailed'; + static const manualUploadDetailedChannelID = 'immich/manualUploadDetailed'; + static const cancelUploadActionID = 'cancel_upload'; + + Future setup() async { + const androidSetting = AndroidInitializationSettings('@drawable/notification_icon'); + const iosSetting = DarwinInitializationSettings(); + + const initSettings = InitializationSettings(android: androidSetting, iOS: iosSetting); + + await _localNotificationPlugin.initialize( + initSettings, + onDidReceiveNotificationResponse: _onDidReceiveForegroundNotificationResponse, + ); + } + + Future _showOrUpdateNotification( + int id, + String title, + String body, + AndroidNotificationDetails androidNotificationDetails, + DarwinNotificationDetails iosNotificationDetails, + ) async { + final notificationDetails = NotificationDetails(android: androidNotificationDetails, iOS: iosNotificationDetails); + + if (_permissionStatus == PermissionStatus.granted) { + await _localNotificationPlugin.show(id, title, body, notificationDetails); + } + } + + Future closeNotification(int id) { + return _localNotificationPlugin.cancel(id); + } + + Future showOrUpdateManualUploadStatus( + String title, + String body, { + bool? isDetailed, + bool? presentBanner, + bool? showActions, + int? maxProgress, + int? progress, + }) { + var notificationlId = manualUploadNotificationID; + var androidChannelID = manualUploadChannelID; + var androidChannelName = manualUploadChannelName; + // Separate Notification for Info/Alerts and Progress + if (isDetailed != null && isDetailed) { + notificationlId = manualUploadDetailedNotificationID; + androidChannelID = manualUploadDetailedChannelID; + androidChannelName = manualUploadChannelNameDetailed; + } + // Progress notification + final androidNotificationDetails = (maxProgress != null && progress != null) + ? AndroidNotificationDetails( + androidChannelID, + androidChannelName, + ticker: title, + showProgress: true, + onlyAlertOnce: true, + maxProgress: maxProgress, + progress: progress, + indeterminate: false, + playSound: false, + priority: Priority.low, + importance: Importance.low, + ongoing: true, + actions: (showActions ?? false) + ? [ + const AndroidNotificationAction(cancelUploadActionID, 'Cancel', showsUserInterface: true), + ] + : null, + ) + // Non-progress notification + : AndroidNotificationDetails(androidChannelID, androidChannelName, playSound: false); + + final iosNotificationDetails = DarwinNotificationDetails( + presentBadge: true, + presentList: true, + presentBanner: presentBanner, + ); + + return _showOrUpdateNotification(notificationlId, title, body, androidNotificationDetails, iosNotificationDetails); + } + + void _onDidReceiveForegroundNotificationResponse(NotificationResponse notificationResponse) { + // Handle notification actions + switch (notificationResponse.actionId) { + case cancelUploadActionID: + { + dPrint(() => "User cancelled manual upload operation"); + ref.read(manualUploadProvider.notifier).cancelBackup(); + } + } + } +} diff --git a/mobile/lib/services/map.service.dart b/mobile/lib/services/map.service.dart index b439af8668..5b50e8a890 100644 --- a/mobile/lib/services/map.service.dart +++ b/mobile/lib/services/map.service.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:immich_mobile/mixins/error_logger.mixin.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/services/api.service.dart'; @@ -13,7 +11,7 @@ class MapService with ErrorLoggerMixin { final logger = Logger("MapService"); MapService(this._apiService) { - unawaited(_setMapUserAgentHeader()); + _setMapUserAgentHeader(); } Future _setMapUserAgentHeader() async { diff --git a/mobile/lib/services/memory.service.dart b/mobile/lib/services/memory.service.dart new file mode 100644 index 0000000000..e485bb0957 --- /dev/null +++ b/mobile/lib/services/memory.service.dart @@ -0,0 +1,71 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:logging/logging.dart'; + +final memoryServiceProvider = StateProvider((ref) { + return MemoryService(ref.watch(apiServiceProvider), ref.watch(assetRepositoryProvider)); +}); + +class MemoryService { + final log = Logger("MemoryService"); + + final ApiService _apiService; + final AssetRepository _assetRepository; + + MemoryService(this._apiService, this._assetRepository); + + Future?> getMemoryLane() async { + try { + final now = DateTime.now(); + final data = await _apiService.memoriesApi.searchMemories( + for_: DateTime.utc(now.year, now.month, now.day, 0, 0, 0), + ); + + if (data == null) { + return null; + } + + List memories = []; + + for (final memory in data) { + final dbAssets = await _assetRepository.getAllByRemoteId(memory.assets.map((e) => e.id)); + final yearsAgo = now.year - memory.data.year; + if (dbAssets.isNotEmpty) { + final String title = 'years_ago'.t(args: {'years': yearsAgo.toString()}); + memories.add(Memory(title: title, assets: dbAssets)); + } + } + + return memories.isNotEmpty ? memories : null; + } catch (error, stack) { + log.severe("Cannot get memories", error, stack); + return null; + } + } + + Future getMemoryById(String id) async { + try { + final memoryResponse = await _apiService.memoriesApi.getMemory(id); + + if (memoryResponse == null) { + return null; + } + final dbAssets = await _assetRepository.getAllByRemoteId(memoryResponse.assets.map((e) => e.id)); + if (dbAssets.isEmpty) { + log.warning("No assets found for memory with ID: $id"); + return null; + } + final yearsAgo = DateTime.now().year - memoryResponse.data.year; + final String title = 'years_ago'.t(args: {'years': yearsAgo.toString()}); + + return Memory(title: title, assets: dbAssets); + } catch (error, stack) { + log.severe("Cannot get memory with ID: $id", error, stack); + return null; + } + } +} diff --git a/mobile/lib/services/network.service.dart b/mobile/lib/services/network.service.dart index e31ecb617e..8622400e7a 100644 --- a/mobile/lib/services/network.service.dart +++ b/mobile/lib/services/network.service.dart @@ -8,7 +8,7 @@ final networkServiceProvider = Provider((ref) { class NetworkService { final NetworkRepository _repository; - final DevicePermissionRepository _permissionRepository; + final IPermissionRepository _permissionRepository; const NetworkService(this._repository, this._permissionRepository); diff --git a/mobile/lib/services/oauth.service.dart b/mobile/lib/services/oauth.service.dart index d8b1604e00..99ceca3229 100644 --- a/mobile/lib/services/oauth.service.dart +++ b/mobile/lib/services/oauth.service.dart @@ -18,11 +18,7 @@ class OAuthService { log.info("Starting OAuth flow with redirect URI: $redirectUri"); final dto = await _apiService.oAuthApi.startOAuth( - OAuthConfigDto( - redirectUri: redirectUri, - state: Optional.present(state), - codeChallenge: Optional.present(codeChallenge), - ), + OAuthConfigDto(redirectUri: redirectUri, state: state, codeChallenge: codeChallenge), ); final authUrl = dto?.url; @@ -41,7 +37,7 @@ class OAuthService { } return await _apiService.oAuthApi.finishOAuth( - OAuthCallbackDto(url: result, state: Optional.present(state), codeVerifier: Optional.present(codeVerifier)), + OAuthCallbackDto(url: result, state: state, codeVerifier: codeVerifier), ); } } diff --git a/mobile/lib/services/partner.service.dart b/mobile/lib/services/partner.service.dart new file mode 100644 index 0000000000..b8e5ae9a4d --- /dev/null +++ b/mobile/lib/services/partner.service.dart @@ -0,0 +1,73 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/partner.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; +import 'package:logging/logging.dart'; + +final partnerServiceProvider = Provider( + (ref) => PartnerService( + ref.watch(partnerApiRepositoryProvider), + ref.watch(userRepositoryProvider), + ref.watch(partnerRepositoryProvider), + ), +); + +class PartnerService { + final PartnerApiRepository _partnerApiRepository; + final PartnerRepository _partnerRepository; + final IsarUserRepository _isarUserRepository; + final Logger _log = Logger("PartnerService"); + + PartnerService(this._partnerApiRepository, this._isarUserRepository, this._partnerRepository); + + Future> getSharedWith() async { + return _partnerRepository.getSharedWith(); + } + + Future> getSharedBy() async { + return _partnerRepository.getSharedBy(); + } + + Stream> watchSharedWith() { + return _partnerRepository.watchSharedWith(); + } + + Stream> watchSharedBy() { + return _partnerRepository.watchSharedBy(); + } + + Future removePartner(UserDto partner) async { + try { + await _partnerApiRepository.delete(partner.id); + await _isarUserRepository.update(partner.copyWith(isPartnerSharedBy: false)); + } catch (e) { + _log.warning("Failed to remove partner ${partner.id}", e); + return false; + } + return true; + } + + Future addPartner(UserDto partner) async { + try { + await _partnerApiRepository.create(partner.id); + await _isarUserRepository.update(partner.copyWith(isPartnerSharedBy: true)); + return true; + } catch (e) { + _log.warning("Failed to add partner ${partner.id}", e); + } + return false; + } + + Future updatePartner(UserDto partner, {required bool inTimeline}) async { + try { + final dto = await _partnerApiRepository.update(partner.id, inTimeline: inTimeline); + await _isarUserRepository.update(partner.copyWith(inTimeline: dto.inTimeline)); + return true; + } catch (e) { + _log.warning("Failed to update partner ${partner.id}", e); + } + return false; + } +} diff --git a/mobile/lib/services/person.service.dart b/mobile/lib/services/person.service.dart new file mode 100644 index 0000000000..37b16a8d29 --- /dev/null +++ b/mobile/lib/services/person.service.dart @@ -0,0 +1,54 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/person.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/asset_api.repository.dart'; +import 'package:immich_mobile/repositories/person_api.repository.dart'; +import 'package:logging/logging.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'person.service.g.dart'; + +@riverpod +PersonService personService(Ref ref) => PersonService( + ref.watch(personApiRepositoryProvider), + ref.watch(assetApiRepositoryProvider), + ref.read(assetRepositoryProvider), +); + +class PersonService { + final Logger _log = Logger("PersonService"); + final PersonApiRepository _personApiRepository; + final AssetApiRepository _assetApiRepository; + final AssetRepository _assetRepository; + + PersonService(this._personApiRepository, this._assetApiRepository, this._assetRepository); + + Future> getAllPeople() async { + try { + return await _personApiRepository.getAll(); + } catch (error, stack) { + _log.severe("Error while fetching curated people", error, stack); + return []; + } + } + + Future> getPersonAssets(String id) async { + try { + final assets = await _assetApiRepository.search(personIds: [id]); + return await _assetRepository.getAllByRemoteId(assets.map((a) => a.remoteId!)); + } catch (error, stack) { + _log.severe("Error while fetching person assets", error, stack); + } + return []; + } + + Future updateName(String id, String name) async { + try { + return await _personApiRepository.update(id, name: name); + } catch (error, stack) { + _log.severe("Error while updating person name", error, stack); + } + return null; + } +} diff --git a/mobile/lib/services/person.service.g.dart b/mobile/lib/services/person.service.g.dart new file mode 100644 index 0000000000..8c2d46b3bd --- /dev/null +++ b/mobile/lib/services/person.service.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person.service.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$personServiceHash() => r'10883bccc6c402205e6785cf9ee6cd7142cd0983'; + +/// See also [personService]. +@ProviderFor(personService) +final personServiceProvider = AutoDisposeProvider.internal( + personService, + name: r'personServiceProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$personServiceHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef PersonServiceRef = AutoDisposeProviderRef; +// ignore_for_file: type=lint +// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/mobile/lib/services/search.service.dart b/mobile/lib/services/search.service.dart index e64c529f26..f33adf80f9 100644 --- a/mobile/lib/services/search.service.dart +++ b/mobile/lib/services/search.service.dart @@ -1,22 +1,31 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/string_extensions.dart'; import 'package:immich_mobile/infrastructure/repositories/search_api.repository.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/models/search/search_result.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/providers/infrastructure/search.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; final searchServiceProvider = Provider( - (ref) => SearchService(ref.watch(apiServiceProvider), ref.watch(searchApiRepositoryProvider)), + (ref) => SearchService( + ref.watch(apiServiceProvider), + ref.watch(assetRepositoryProvider), + ref.watch(searchApiRepositoryProvider), + ), ); class SearchService { final ApiService _apiService; + final AssetRepository _assetRepository; final SearchApiRepository _searchApiRepository; final _log = Logger("SearchService"); - SearchService(this._apiService, this._searchApiRepository); + SearchService(this._apiService, this._assetRepository, this._searchApiRepository); Future?> getSearchSuggestions( SearchSuggestionType type, { @@ -34,11 +43,29 @@ class SearchService { model: model, ); } catch (e) { - dPrint(() => "[ERROR] [getSearchSuggestions] $e"); + dPrint(() => "[ERROR] [getSearchSuggestions] ${e.toString()}"); return []; } } + Future search(SearchFilter filter, int page) async { + try { + final response = await _searchApiRepository.search(filter, page); + + if (response == null || response.assets.items.isEmpty) { + return null; + } + + return SearchResult( + assets: await _assetRepository.getAllByRemoteId(response.assets.items.map((e) => e.id)), + nextPage: response.assets.nextPage?.toInt(), + ); + } catch (error, stackTrace) { + _log.severe("Failed to search for assets", error, stackTrace); + } + return null; + } + Future?> getExploreData() async { try { return await _apiService.searchApi.getExploreData(); diff --git a/mobile/lib/services/server_info.service.dart b/mobile/lib/services/server_info.service.dart index fb8b347caa..460e135421 100644 --- a/mobile/lib/services/server_info.service.dart +++ b/mobile/lib/services/server_info.service.dart @@ -21,7 +21,7 @@ class ServerInfoService { return ServerDiskInfo.fromDto(dto); } } catch (e) { - dPrint(() => "Error [getDiskInfo] $e"); + dPrint(() => "Error [getDiskInfo] ${e.toString()}"); } return null; } @@ -33,7 +33,7 @@ class ServerInfoService { return ServerVersion.fromDto(dto); } } catch (e) { - dPrint(() => "Error [getServerVersion] $e"); + dPrint(() => "Error [getServerVersion] ${e.toString()}"); } return null; } @@ -45,7 +45,7 @@ class ServerInfoService { return ServerFeatures.fromDto(dto); } } catch (e) { - dPrint(() => "Error [getServerFeatures] $e"); + dPrint(() => "Error [getServerFeatures] ${e.toString()}"); } return null; } @@ -57,7 +57,7 @@ class ServerInfoService { return ServerConfig.fromDto(dto); } } catch (e) { - dPrint(() => "Error [getServerConfig] $e"); + dPrint(() => "Error [getServerConfig] ${e.toString()}"); } return null; } diff --git a/mobile/lib/services/share.service.dart b/mobile/lib/services/share.service.dart new file mode 100644 index 0000000000..a0998d6d3d --- /dev/null +++ b/mobile/lib/services/share.service.dart @@ -0,0 +1,74 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/response_extensions.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:logging/logging.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; + +final shareServiceProvider = Provider((ref) => ShareService(ref.watch(apiServiceProvider))); + +class ShareService { + final ApiService _apiService; + final Logger _log = Logger("ShareService"); + + ShareService(this._apiService); + + Future shareAsset(Asset asset, BuildContext context) async { + return await shareAssets([asset], context); + } + + Future shareAssets(List assets, BuildContext context) async { + try { + final downloadedXFiles = []; + + for (var asset in assets) { + if (asset.isLocal) { + // Prefer local assets to share + File? f = await asset.local!.originFile; + downloadedXFiles.add(XFile(f!.path)); + } else if (asset.isRemote) { + // Download remote asset otherwise + final tempDir = await getTemporaryDirectory(); + final fileName = asset.fileName; + final tempFile = await File('${tempDir.path}/$fileName').create(); + final res = await _apiService.assetsApi.downloadAssetWithHttpInfo(asset.remoteId!); + + if (res.statusCode != 200) { + _log.severe("Asset download for ${asset.fileName} failed", res.toLoggerString()); + continue; + } + + tempFile.writeAsBytesSync(res.bodyBytes); + downloadedXFiles.add(XFile(tempFile.path)); + } + } + + if (downloadedXFiles.isEmpty) { + _log.warning("No asset can be retrieved for share"); + return false; + } + + if (downloadedXFiles.length != assets.length) { + _log.warning("Partial share - Requested: ${assets.length}, Sharing: ${downloadedXFiles.length}"); + } + + final size = MediaQuery.of(context).size; + unawaited( + Share.shareXFiles( + downloadedXFiles, + sharePositionOrigin: Rect.fromPoints(Offset.zero, Offset(size.width / 3, size.height)), + ), + ); + return true; + } catch (error) { + _log.severe("Share failed", error); + } + return false; + } +} diff --git a/mobile/lib/services/share_intent_service.dart b/mobile/lib/services/share_intent_service.dart index a5ab5d8bc9..fca5c4a188 100644 --- a/mobile/lib/services/share_intent_service.dart +++ b/mobile/lib/services/share_intent_service.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart'; import 'package:immich_mobile/repositories/share_handler.repository.dart'; @@ -14,6 +12,6 @@ class ShareIntentService { void init() { shareHandlerRepository.onSharedMedia = onSharedMedia; - unawaited(shareHandlerRepository.init()); + shareHandlerRepository.init(); } } diff --git a/mobile/lib/services/shared_link.service.dart b/mobile/lib/services/shared_link.service.dart index 86ce409cdf..46e83f0fc4 100644 --- a/mobile/lib/services/shared_link.service.dart +++ b/mobile/lib/services/shared_link.service.dart @@ -2,7 +2,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/api.provider.dart'; import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/utils/option.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; @@ -49,26 +48,26 @@ class SharedLinkService { if (type == SharedLinkType.ALBUM) { dto = SharedLinkCreateDto( type: type, - albumId: albumId == null ? const Optional.absent() : Optional.present(albumId), - showMetadata: Optional.present(showMeta), - allowDownload: Optional.present(allowDownload), - allowUpload: Optional.present(allowUpload), - expiresAt: expiresAt == null ? const Optional.absent() : Optional.present(expiresAt), - description: description == null ? const Optional.absent() : Optional.present(description), - password: password == null ? const Optional.absent() : Optional.present(password), - slug: slug == null ? const Optional.absent() : Optional.present(slug), + albumId: albumId, + showMetadata: showMeta, + allowDownload: allowDownload, + allowUpload: allowUpload, + expiresAt: expiresAt, + description: description, + password: password, + slug: slug, ); } else if (assetIds != null) { dto = SharedLinkCreateDto( type: type, - showMetadata: Optional.present(showMeta), - allowDownload: Optional.present(allowDownload), - allowUpload: Optional.present(allowUpload), - expiresAt: expiresAt == null ? const Optional.absent() : Optional.present(expiresAt), - description: description == null ? const Optional.absent() : Optional.present(description), - password: password == null ? const Optional.absent() : Optional.present(password), - slug: slug == null ? const Optional.absent() : Optional.present(slug), - assetIds: Optional.present(assetIds), + showMetadata: showMeta, + allowDownload: allowDownload, + allowUpload: allowUpload, + expiresAt: expiresAt, + description: description, + password: password, + slug: slug, + assetIds: assetIds, ); } @@ -89,22 +88,24 @@ class SharedLinkService { required bool? showMeta, required bool? allowDownload, required bool? allowUpload, - Option password = const Option.none(), - Option description = const Option.none(), + bool? changeExpiry = false, + String? description, + String? password, String? slug, - Option expiresAt = const Option.none(), + DateTime? expiresAt, }) async { try { final responseDto = await _apiService.sharedLinksApi.updateSharedLink( id, SharedLinkEditDto( - showMetadata: showMeta == null ? const Optional.absent() : Optional.present(showMeta), - allowDownload: allowDownload == null ? const Optional.absent() : Optional.present(allowDownload), - allowUpload: allowUpload == null ? const Optional.absent() : Optional.present(allowUpload), - password: password.toOptional(), - description: description.toOptional(), - expiresAt: expiresAt.toOptional(), - slug: slug == null ? const Optional.absent() : Optional.present(slug), + showMetadata: showMeta, + allowDownload: allowDownload, + allowUpload: allowUpload, + expiresAt: expiresAt, + description: description, + password: password, + slug: slug, + changeExpiryTime: changeExpiry, ), ); if (responseDto != null) { diff --git a/mobile/lib/services/stack.service.dart b/mobile/lib/services/stack.service.dart new file mode 100644 index 0000000000..88189c6bcd --- /dev/null +++ b/mobile/lib/services/stack.service.dart @@ -0,0 +1,64 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:openapi/api.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +class StackService { + const StackService(this._api, this._assetRepository); + + final ApiService _api; + final AssetRepository _assetRepository; + + Future getStack(String stackId) async { + try { + return _api.stacksApi.getStack(stackId); + } catch (error) { + dPrint(() => "Error while fetching stack: $error"); + } + return null; + } + + Future createStack(List assetIds) async { + try { + return _api.stacksApi.createStack(StackCreateDto(assetIds: assetIds)); + } catch (error) { + dPrint(() => "Error while creating stack: $error"); + } + return null; + } + + Future updateStack(String stackId, String primaryAssetId) async { + try { + return await _api.stacksApi.updateStack(stackId, StackUpdateDto(primaryAssetId: primaryAssetId)); + } catch (error) { + dPrint(() => "Error while updating stack children: $error"); + } + return null; + } + + Future deleteStack(String stackId, List assets) async { + try { + await _api.stacksApi.deleteStack(stackId); + + // Update local database to trigger rerendering + final List removeAssets = []; + for (final asset in assets) { + asset.stackId = null; + asset.stackPrimaryAssetId = null; + asset.stackCount = 0; + + removeAssets.add(asset); + } + await _assetRepository.transaction(() => _assetRepository.updateAll(removeAssets)); + } catch (error) { + dPrint(() => "Error while deleting stack: $error"); + } + } +} + +final stackServiceProvider = Provider( + (ref) => StackService(ref.watch(apiServiceProvider), ref.watch(assetRepositoryProvider)), +); diff --git a/mobile/lib/services/sync.service.dart b/mobile/lib/services/sync.service.dart new file mode 100644 index 0000000000..f5b55f36eb --- /dev/null +++ b/mobile/lib/services/sync.service.dart @@ -0,0 +1,945 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/exif.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/album.repository.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/etag.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/partner.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/entity.service.dart'; +import 'package:immich_mobile/services/hash.service.dart'; +import 'package:immich_mobile/utils/async_mutex.dart'; +import 'package:immich_mobile/utils/datetime_comparison.dart'; +import 'package:immich_mobile/utils/diff.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:logging/logging.dart'; + +final syncServiceProvider = Provider( + (ref) => SyncService( + ref.watch(hashServiceProvider), + ref.watch(entityServiceProvider), + ref.watch(albumMediaRepositoryProvider), + ref.watch(albumApiRepositoryProvider), + ref.watch(albumRepositoryProvider), + ref.watch(assetRepositoryProvider), + ref.watch(exifRepositoryProvider), + ref.watch(partnerRepositoryProvider), + ref.watch(userRepositoryProvider), + ref.watch(userServiceProvider), + ref.watch(etagRepositoryProvider), + ref.watch(appSettingsServiceProvider), + ref.watch(localFilesManagerRepositoryProvider), + ref.watch(partnerApiRepositoryProvider), + ref.watch(userApiRepositoryProvider), + ), +); + +class SyncService { + final HashService _hashService; + final EntityService _entityService; + final AlbumMediaRepository _albumMediaRepository; + final AlbumApiRepository _albumApiRepository; + final AlbumRepository _albumRepository; + final AssetRepository _assetRepository; + final IsarExifRepository _exifInfoRepository; + final IsarUserRepository _isarUserRepository; + final UserService _userService; + final PartnerRepository _partnerRepository; + final ETagRepository _eTagRepository; + final PartnerApiRepository _partnerApiRepository; + final UserApiRepository _userApiRepository; + final AsyncMutex _lock = AsyncMutex(); + final Logger _log = Logger('SyncService'); + final AppSettingsService _appSettingsService; + final LocalFilesManagerRepository _localFilesManager; + + SyncService( + this._hashService, + this._entityService, + this._albumMediaRepository, + this._albumApiRepository, + this._albumRepository, + this._assetRepository, + this._exifInfoRepository, + this._partnerRepository, + this._isarUserRepository, + this._userService, + this._eTagRepository, + this._appSettingsService, + this._localFilesManager, + this._partnerApiRepository, + this._userApiRepository, + ); + + // public methods: + + /// Syncs users from the server to the local database + /// Returns `true`if there were any changes + Future syncUsersFromServer(List users) => _lock.run(() => _syncUsersFromServer(users)); + + /// Syncs remote assets owned by the logged-in user to the DB + /// Returns `true` if there were any changes + Future syncRemoteAssetsToDb({ + required List users, + required Future<(List? toUpsert, List? toDelete)> Function(List users, DateTime since) + getChangedAssets, + required FutureOr?> Function(UserDto user, DateTime until) loadAssets, + }) => _lock.run( + () async => + await _syncRemoteAssetChanges(users, getChangedAssets) ?? + await _syncRemoteAssetsFull(getUsersFromServer, loadAssets), + ); + + /// Syncs remote albums to the database + /// returns `true` if there were any changes + Future syncRemoteAlbumsToDb(List remote) => _lock.run(() => _syncRemoteAlbumsToDb(remote)); + + /// Syncs all device albums and their assets to the database + /// Returns `true` if there were any changes + Future syncLocalAlbumAssetsToDb(List onDevice, [Set? excludedAssets]) => + _lock.run(() => _syncLocalAlbumAssetsToDb(onDevice, excludedAssets)); + + /// returns all Asset IDs that are not contained in the existing list + List sharedAssetsToRemove(List deleteCandidates, List existing) { + if (deleteCandidates.isEmpty) { + return []; + } + deleteCandidates.sort(Asset.compareById); + existing.sort(Asset.compareById); + return _diffAssets(existing, deleteCandidates, compare: Asset.compareById).$3.map((e) => e.id).toList(); + } + + /// Syncs a new asset to the db. Returns `true` if successful + Future syncNewAssetToDb(Asset newAsset) => _lock.run(() => _syncNewAssetToDb(newAsset)); + + Future removeAllLocalAlbumsAndAssets() => _lock.run(_removeAllLocalAlbumsAndAssets); + + // private methods: + + /// Syncs users from the server to the local database + /// Returns `true`if there were any changes + Future _syncUsersFromServer(List users) async { + users.sortBy((u) => u.id); + final dbUsers = await _isarUserRepository.getAll(sortBy: SortUserBy.id); + final List toDelete = []; + final List toUpsert = []; + final changes = diffSortedListsSync( + users, + dbUsers, + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), + both: (UserDto a, UserDto b) { + if ((a.updatedAt == null && b.updatedAt != null) || + (a.updatedAt != null && b.updatedAt == null) || + (a.updatedAt != null && b.updatedAt != null && !a.updatedAt!.isAtSameMomentAs(b.updatedAt!)) || + a.isPartnerSharedBy != b.isPartnerSharedBy || + a.isPartnerSharedWith != b.isPartnerSharedWith || + a.inTimeline != b.inTimeline) { + toUpsert.add(a); + return true; + } + return false; + }, + onlyFirst: (UserDto a) => toUpsert.add(a), + onlySecond: (UserDto b) => toDelete.add(b.id), + ); + if (changes) { + await _isarUserRepository.transaction(() async { + await _isarUserRepository.delete(toDelete); + await _isarUserRepository.updateAll(toUpsert); + }); + } + return changes; + } + + /// Syncs a new asset to the db. Returns `true` if successful + Future _syncNewAssetToDb(Asset a) async { + final Asset? inDb = await _assetRepository.getByOwnerIdChecksum(a.ownerId, a.checksum); + if (inDb != null) { + // unify local/remote assets by replacing the + // local-only asset in the DB with a local&remote asset + a = inDb.updatedCopy(a); + } + try { + await _assetRepository.update(a); + } catch (e) { + _log.severe("Failed to put new asset into db", e); + return false; + } + return true; + } + + /// Efficiently syncs assets via changes. Returns `null` when a full sync is required. + Future _syncRemoteAssetChanges( + List users, + Future<(List? toUpsert, List? toDelete)> Function(List users, DateTime since) + getChangedAssets, + ) async { + final currentUser = _userService.getMyUser(); + final DateTime? since = (await _eTagRepository.get(currentUser.id))?.time?.toUtc(); + if (since == null) return null; + final DateTime now = DateTime.now(); + final (toUpsert, toDelete) = await getChangedAssets(users, since); + if (toUpsert == null || toDelete == null) { + await _clearUserAssetsETag(users); + return null; + } + try { + if (toDelete.isNotEmpty) { + await handleRemoteAssetRemoval(toDelete); + } + if (toUpsert.isNotEmpty) { + final (_, updated) = await _linkWithExistingFromDb(toUpsert); + await upsertAssetsWithExif(updated); + } + if (toUpsert.isNotEmpty || toDelete.isNotEmpty) { + await _updateUserAssetsETag(users, now); + return true; + } + return false; + } catch (e) { + _log.severe("Failed to sync remote assets to db", e); + } + return null; + } + + Future _moveToTrashMatchedAssets(Iterable idsToDelete) async { + final List localAssets = await _assetRepository.getAllLocal(); + final List matchedAssets = localAssets.where((asset) => idsToDelete.contains(asset.remoteId)).toList(); + + final mediaUrls = await Future.wait(matchedAssets.map((asset) => asset.local?.getMediaUrl() ?? Future.value(null))); + + await _localFilesManager.moveToTrash(mediaUrls.nonNulls.toList()); + } + + /// Deletes remote-only assets, updates merged assets to be local-only + Future handleRemoteAssetRemoval(List idsToDelete) async { + return _assetRepository.transaction(() async { + await _assetRepository.deleteAllByRemoteId(idsToDelete, state: AssetState.remote); + final merged = await _assetRepository.getAllByRemoteId(idsToDelete, state: AssetState.merged); + if (Platform.isAndroid && _appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) { + await _moveToTrashMatchedAssets(idsToDelete); + } + if (merged.isEmpty) return; + for (final Asset asset in merged) { + asset.remoteId = null; + asset.isTrashed = false; + } + await _assetRepository.updateAll(merged); + }); + } + + Future> _getAllAccessibleUsers() async { + final sharedWith = (await _partnerRepository.getSharedWith()).toSet(); + sharedWith.add(_userService.getMyUser()); + return sharedWith.toList(); + } + + /// Syncs assets by loading and comparing all assets from the server. + Future _syncRemoteAssetsFull( + FutureOr?> Function() refreshUsers, + FutureOr?> Function(UserDto user, DateTime until) loadAssets, + ) async { + final serverUsers = await refreshUsers(); + if (serverUsers == null) { + _log.warning("_syncRemoteAssetsFull aborted because user refresh failed"); + return false; + } + await _syncUsersFromServer(serverUsers); + final List users = await _getAllAccessibleUsers(); + bool changes = false; + for (UserDto u in users) { + changes |= await _syncRemoteAssetsForUser(u, loadAssets); + } + return changes; + } + + Future _syncRemoteAssetsForUser( + UserDto user, + FutureOr?> Function(UserDto user, DateTime until) loadAssets, + ) async { + final DateTime now = DateTime.now().toUtc(); + final List? remote = await loadAssets(user, now); + if (remote == null) { + return false; + } + final List inDb = await _assetRepository.getAll(ownerId: user.id, sortBy: AssetSort.checksum); + assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); + + remote.sort(Asset.compareByChecksum); + + // filter our duplicates that might be introduced by the chunked retrieval + remote.uniqueConsecutive(compare: Asset.compareByChecksum); + + final (toAdd, toUpdate, toRemove) = _diffAssets(remote, inDb, remote: true); + if (toAdd.isEmpty && toUpdate.isEmpty && toRemove.isEmpty) { + await _updateUserAssetsETag([user], now); + return false; + } + final idsToDelete = toRemove.map((e) => e.id).toList(); + try { + await _assetRepository.deleteByIds(idsToDelete); + await upsertAssetsWithExif(toAdd + toUpdate); + } catch (e) { + _log.severe("Failed to sync remote assets to db", e); + } + await _updateUserAssetsETag([user], now); + return true; + } + + Future _updateUserAssetsETag(List users, DateTime time) { + final etags = users.map((u) => ETag(id: u.id, time: time)).toList(); + return _eTagRepository.upsertAll(etags); + } + + Future _clearUserAssetsETag(List users) { + final ids = users.map((u) => u.id).toList(); + return _eTagRepository.deleteByIds(ids); + } + + /// Syncs remote albums to the database + /// returns `true` if there were any changes + Future _syncRemoteAlbumsToDb(List remoteAlbums) async { + remoteAlbums.sortBy((e) => e.remoteId!); + + final List dbAlbums = await _albumRepository.getAll(remote: true, sortBy: AlbumSort.remoteId); + + final List toDelete = []; + final List existing = []; + + final bool changes = await diffSortedLists( + remoteAlbums, + dbAlbums, + compare: (remoteAlbum, dbAlbum) => remoteAlbum.remoteId!.compareTo(dbAlbum.remoteId!), + both: (remoteAlbum, dbAlbum) => _syncRemoteAlbum(remoteAlbum, dbAlbum, toDelete, existing), + onlyFirst: (remoteAlbum) => _addAlbumFromServer(remoteAlbum, existing), + onlySecond: (dbAlbum) => _removeAlbumFromDb(dbAlbum, toDelete), + ); + + if (toDelete.isNotEmpty) { + final List idsToRemove = sharedAssetsToRemove(toDelete, existing); + if (idsToRemove.isNotEmpty) { + await _assetRepository.deleteByIds(idsToRemove); + } + } else { + assert(toDelete.isEmpty); + } + return changes; + } + + /// syncs albums from the server to the local database (does not support + /// syncing changes from local back to server) + /// accumulates + Future _syncRemoteAlbum(Album dto, Album album, List deleteCandidates, List existing) async { + if (!_hasRemoteAlbumChanged(dto, album)) { + return false; + } + // loadDetails (/api/album/:id) will not include lastModifiedAssetTimestamp, + // i.e. it will always be null. Save it here. + final originalDto = dto; + dto = await _albumApiRepository.get(dto.remoteId!); + + final assetsInDb = await _assetRepository.getByAlbum(album, sortBy: AssetSort.ownerIdChecksum); + assert(assetsInDb.isSorted(Asset.compareByOwnerChecksum), "inDb unsorted!"); + final List assetsOnRemote = dto.remoteAssets.toList(); + assetsOnRemote.sort(Asset.compareByOwnerChecksum); + final (toAdd, toUpdate, toUnlink) = _diffAssets(assetsOnRemote, assetsInDb, compare: Asset.compareByOwnerChecksum); + + // update shared users + final List sharedUsers = album.sharedUsers.map((u) => u.toDto()).toList(growable: false); + sharedUsers.sort((a, b) => a.id.compareTo(b.id)); + final List users = dto.remoteUsers.map((u) => u.toDto()).toList()..sort((a, b) => a.id.compareTo(b.id)); + final List userIdsToAdd = []; + final List usersToUnlink = []; + diffSortedListsSync( + users, + sharedUsers, + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), + both: (a, b) => false, + onlyFirst: (UserDto a) => userIdsToAdd.add(a.id), + onlySecond: (UserDto a) => usersToUnlink.add(a), + ); + + // for shared album: put missing album assets into local DB + final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd); + await upsertAssetsWithExif(updated); + final assetsToLink = existingInDb + updated; + final usersToLink = await _isarUserRepository.getByUserIds(userIdsToAdd); + + album.name = dto.name; + album.description = dto.description; + album.shared = dto.shared; + album.createdAt = dto.createdAt; + album.modifiedAt = dto.modifiedAt; + album.startDate = dto.startDate; + album.endDate = dto.endDate; + album.lastModifiedAssetTimestamp = originalDto.lastModifiedAssetTimestamp; + album.shared = dto.shared; + album.activityEnabled = dto.activityEnabled; + album.sortOrder = dto.sortOrder; + + final remoteThumbnailAssetId = dto.remoteThumbnailAssetId; + if (remoteThumbnailAssetId != null && album.thumbnail.value?.remoteId != remoteThumbnailAssetId) { + album.thumbnail.value = await _assetRepository.getByRemoteId(remoteThumbnailAssetId); + } + + // write & commit all changes to DB + try { + await _assetRepository.transaction(() async { + await _assetRepository.updateAll(toUpdate); + await _albumRepository.addUsers(album, usersToLink.nonNulls.toList()); + await _albumRepository.removeUsers(album, usersToUnlink); + await _albumRepository.addAssets(album, assetsToLink); + await _albumRepository.removeAssets(album, toUnlink); + await _albumRepository.recalculateMetadata(album); + await _albumRepository.update(album); + }); + _log.info("Synced changes of remote album ${album.name} to DB"); + } catch (e) { + _log.severe("Failed to sync remote album to database", e); + } + + if (album.shared || dto.shared) { + final userId = (_userService.getMyUser()).id; + final foreign = await _assetRepository.getByAlbum(album, notOwnedBy: [userId]); + existing.addAll(foreign); + + // delete assets in DB unless they belong to this user or part of some other shared album + final isarUserId = fastHash(userId); + deleteCandidates.addAll(toUnlink.where((a) => a.ownerId != isarUserId)); + } + + return true; + } + + /// Adds a remote album to the database while making sure to add any foreign + /// (shared) assets to the database beforehand + /// accumulates assets already existing in the database + Future _addAlbumFromServer(Album album, List existing) async { + if (album.remoteAssetCount != album.remoteAssets.length) { + album = await _albumApiRepository.get(album.remoteId!); + } + if (album.remoteAssetCount == album.remoteAssets.length) { + // in case an album contains assets not yet present in local DB: + // put missing album assets into local DB + final (existingInDb, updated) = await _linkWithExistingFromDb(album.remoteAssets.toList()); + existing.addAll(existingInDb); + await upsertAssetsWithExif(updated); + + await _entityService.fillAlbumWithDatabaseEntities(album); + await _albumRepository.create(album); + } else { + _log.warning( + "Failed to add album from server: assetCount ${album.remoteAssetCount} != " + "asset array length ${album.remoteAssets.length} for album ${album.name}", + ); + } + } + + /// Accumulates all suitable album assets to the `deleteCandidates` and + /// removes the album from the database. + Future _removeAlbumFromDb(Album album, List deleteCandidates) async { + if (album.isLocal) { + _log.info("Removing local album $album from DB"); + // delete assets in DB unless they are remote or part of some other album + deleteCandidates.addAll(await _assetRepository.getByAlbum(album, state: AssetState.local)); + } else if (album.shared) { + // delete assets in DB unless they belong to this user or are part of some other shared album or belong to a partner + final userIds = (await _getAllAccessibleUsers()).map((user) => user.id); + final orphanedAssets = await _assetRepository.getByAlbum(album, notOwnedBy: userIds); + deleteCandidates.addAll(orphanedAssets); + } + try { + await _albumRepository.delete(album.id); + _log.info("Removed local album $album from DB"); + } catch (e) { + _log.severe("Failed to remove local album $album from DB", e); + } + } + + /// Syncs all device albums and their assets to the database + /// Returns `true` if there were any changes + Future _syncLocalAlbumAssetsToDb(List onDevice, [Set? excludedAssets]) async { + onDevice.sort((a, b) => a.localId!.compareTo(b.localId!)); + final inDb = await _albumRepository.getAll(remote: false, sortBy: AlbumSort.localId); + final List deleteCandidates = []; + final List existing = []; + final bool anyChanges = await diffSortedLists( + onDevice, + inDb, + compare: (Album a, Album b) => a.localId!.compareTo(b.localId!), + both: (Album a, Album b) => _syncAlbumInDbAndOnDevice(a, b, deleteCandidates, existing, excludedAssets), + onlyFirst: (Album a) => _addAlbumFromDevice(a, existing, excludedAssets), + onlySecond: (Album a) => _removeAlbumFromDb(a, deleteCandidates), + ); + _log.fine("Syncing all local albums almost done. Collected ${deleteCandidates.length} asset candidates to delete"); + final (toDelete, toUpdate) = _handleAssetRemoval(deleteCandidates, existing, remote: false); + _log.fine("${toDelete.length} assets to delete, ${toUpdate.length} to update"); + if (toDelete.isNotEmpty || toUpdate.isNotEmpty) { + await _assetRepository.transaction(() async { + await _assetRepository.deleteByIds(toDelete); + await _assetRepository.updateAll(toUpdate); + }); + _log.info("Removed ${toDelete.length} and updated ${toUpdate.length} local assets from DB"); + } + return anyChanges; + } + + /// Syncs the device album to the album in the database + /// returns `true` if there were any changes + /// Accumulates asset candidates to delete and those already existing in DB + Future _syncAlbumInDbAndOnDevice( + Album deviceAlbum, + Album dbAlbum, + List deleteCandidates, + List existing, [ + Set? excludedAssets, + bool forceRefresh = false, + ]) async { + _log.info("Syncing a local album to DB: ${deviceAlbum.name}"); + if (!forceRefresh && !await _hasAlbumChangeOnDevice(deviceAlbum, dbAlbum)) { + _log.info("Local album ${deviceAlbum.name} has not changed. Skipping sync."); + return false; + } + _log.info("Local album ${deviceAlbum.name} has changed. Syncing..."); + if (!forceRefresh && excludedAssets == null && await _syncDeviceAlbumFast(deviceAlbum, dbAlbum)) { + _log.info("Fast synced local album ${deviceAlbum.name} to DB"); + return true; + } + // general case, e.g. some assets have been deleted or there are excluded albums on iOS + final inDb = await _assetRepository.getByAlbum( + dbAlbum, + ownerId: (_userService.getMyUser()).id, + sortBy: AssetSort.checksum, + ); + + assert(inDb.isSorted(Asset.compareByChecksum), "inDb not sorted!"); + final int assetCountOnDevice = await _albumMediaRepository.getAssetCount(deviceAlbum.localId!); + final List onDevice = await _getHashedAssets(deviceAlbum, excludedAssets: excludedAssets); + _removeDuplicates(onDevice); + // _removeDuplicates sorts `onDevice` by checksum + final (toAdd, toUpdate, toDelete) = _diffAssets(onDevice, inDb); + if (toAdd.isEmpty && + toUpdate.isEmpty && + toDelete.isEmpty && + dbAlbum.name == deviceAlbum.name && + dbAlbum.description == deviceAlbum.description && + dbAlbum.modifiedAt.isAtSameMomentAs(deviceAlbum.modifiedAt)) { + // changes only affeted excluded albums + _log.info("Only excluded assets in local album ${deviceAlbum.name} changed. Stopping sync."); + if (assetCountOnDevice != (await _eTagRepository.getById(deviceAlbum.eTagKeyAssetCount))?.assetCount) { + await _eTagRepository.upsertAll([ETag(id: deviceAlbum.eTagKeyAssetCount, assetCount: assetCountOnDevice)]); + } + return false; + } + _log.info( + "Syncing local album ${deviceAlbum.name}. ${toAdd.length} assets to add, ${toUpdate.length} to update, ${toDelete.length} to delete", + ); + final (existingInDb, updated) = await _linkWithExistingFromDb(toAdd); + _log.info( + "Linking assets to add with existing from db. ${existingInDb.length} existing, ${updated.length} to update", + ); + 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)) { + dbAlbum.thumbnail.value = null; + } + try { + await _assetRepository.transaction(() async { + await _assetRepository.updateAll(updated + toUpdate); + await _albumRepository.addAssets(dbAlbum, existingInDb + updated); + await _albumRepository.removeAssets(dbAlbum, toDelete); + await _albumRepository.recalculateMetadata(dbAlbum); + await _albumRepository.update(dbAlbum); + await _eTagRepository.upsertAll([ETag(id: deviceAlbum.eTagKeyAssetCount, assetCount: assetCountOnDevice)]); + }); + _log.info("Synced changes of local album ${deviceAlbum.name} to DB"); + } catch (e) { + _log.severe("Failed to update synced album ${deviceAlbum.name} in DB", e); + } + + return true; + } + + /// fast path for common case: only new assets were added to device album + /// returns `true` if successful, else `false` + Future _syncDeviceAlbumFast(Album deviceAlbum, Album dbAlbum) async { + if (!deviceAlbum.modifiedAt.isAfter(dbAlbum.modifiedAt)) { + _log.info("Local album ${deviceAlbum.name} has not changed. Skipping sync."); + return false; + } + final int totalOnDevice = await _albumMediaRepository.getAssetCount(deviceAlbum.localId!); + final int lastKnownTotal = (await _eTagRepository.getById(deviceAlbum.eTagKeyAssetCount))?.assetCount ?? 0; + if (totalOnDevice <= lastKnownTotal) { + _log.info("Local album ${deviceAlbum.name} totalOnDevice is less than lastKnownTotal. Skipping sync."); + return false; + } + final List newAssets = await _getHashedAssets( + deviceAlbum, + modifiedFrom: dbAlbum.modifiedAt.add(const Duration(seconds: 1)), + modifiedUntil: deviceAlbum.modifiedAt, + ); + + if (totalOnDevice != lastKnownTotal + newAssets.length) { + _log.info( + "Local album ${deviceAlbum.name} totalOnDevice is not equal to lastKnownTotal + newAssets.length. Skipping sync.", + ); + return false; + } + dbAlbum.modifiedAt = deviceAlbum.modifiedAt; + _removeDuplicates(newAssets); + final (existingInDb, updated) = await _linkWithExistingFromDb(newAssets); + try { + await _assetRepository.transaction(() async { + await _assetRepository.updateAll(updated); + await _albumRepository.addAssets(dbAlbum, existingInDb + updated); + await _albumRepository.recalculateMetadata(dbAlbum); + await _albumRepository.update(dbAlbum); + await _eTagRepository.upsertAll([ETag(id: deviceAlbum.eTagKeyAssetCount, assetCount: totalOnDevice)]); + }); + _log.info("Fast synced local album ${deviceAlbum.name} to DB"); + } catch (e) { + _log.severe("Failed to fast sync local album ${deviceAlbum.name} to DB", e); + return false; + } + + return true; + } + + /// Adds a new album from the device to the database and Accumulates all + /// assets already existing in the database to the list of `existing` assets + Future _addAlbumFromDevice(Album album, List existing, [Set? excludedAssets]) async { + _log.info("Adding a new local album to DB: ${album.name}"); + final assets = await _getHashedAssets(album, excludedAssets: excludedAssets); + _removeDuplicates(assets); + final (existingInDb, updated) = await _linkWithExistingFromDb(assets); + _log.info("${existingInDb.length} assets already existed in DB, to upsert ${updated.length}"); + await upsertAssetsWithExif(updated); + existing.addAll(existingInDb); + album.assets.addAll(existingInDb); + album.assets.addAll(updated); + final thumb = existingInDb.firstOrNull ?? updated.firstOrNull; + album.thumbnail.value = thumb; + try { + await _albumRepository.create(album); + final int assetCount = await _albumMediaRepository.getAssetCount(album.localId!); + await _eTagRepository.upsertAll([ETag(id: album.eTagKeyAssetCount, assetCount: assetCount)]); + _log.info("Added a new local album to DB: ${album.name}"); + } catch (e) { + _log.severe("Failed to add new local album ${album.name} to DB", e); + } + } + + /// Returns a tuple (existing, updated) + Future<(List existing, List updated)> _linkWithExistingFromDb(List assets) async { + if (assets.isEmpty) return ([].cast(), [].cast()); + + final List inDb = await _assetRepository.getAllByOwnerIdChecksum( + assets.map((a) => a.ownerId).toInt64List(), + assets.map((a) => a.checksum).toList(growable: false), + ); + assert(inDb.length == assets.length); + final List existing = [], toUpsert = []; + for (int i = 0; i < assets.length; i++) { + final Asset? b = inDb[i]; + if (b == null) { + toUpsert.add(assets[i]); + continue; + } + if (b.canUpdate(assets[i])) { + final updated = b.updatedCopy(assets[i]); + assert(updated.isInDb); + toUpsert.add(updated); + } else { + existing.add(b); + } + } + assert(existing.length + toUpsert.length == assets.length); + return (existing, toUpsert); + } + + Future _toggleTrashStatusForAssets(List assetsList) async { + final trashMediaUrls = []; + + for (final asset in assetsList) { + if (asset.isTrashed) { + final mediaUrl = await asset.local?.getMediaUrl(); + if (mediaUrl == null) { + _log.warning("Failed to get media URL for asset ${asset.name} while moving to trash"); + continue; + } + trashMediaUrls.add(mediaUrl); + } else { + await _localFilesManager.restoreFromTrash(asset.fileName, asset.type.index); + } + } + + if (trashMediaUrls.isNotEmpty) { + await _localFilesManager.moveToTrash(trashMediaUrls); + } + } + + /// Inserts or updates the assets in the database with their ExifInfo (if any) + Future upsertAssetsWithExif(List assets) async { + if (assets.isEmpty) return; + + if (Platform.isAndroid && _appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) { + await _toggleTrashStatusForAssets(assets); + } + + try { + await _assetRepository.transaction(() async { + await _assetRepository.updateAll(assets); + for (final Asset added in assets) { + added.exifInfo = added.exifInfo?.copyWith(assetId: added.id); + } + final exifInfos = assets.map((e) => e.exifInfo).nonNulls.toList(); + await _exifInfoRepository.updateAll(exifInfos); + }); + _log.info("Upserted ${assets.length} assets into the DB"); + } catch (e) { + _log.severe("Failed to upsert ${assets.length} assets into the DB", e); + // give details on the errors + assets.sort(Asset.compareByOwnerChecksum); + final inDb = await _assetRepository.getAllByOwnerIdChecksum( + assets.map((e) => e.ownerId).toInt64List(), + assets.map((e) => e.checksum).toList(growable: false), + ); + for (int i = 0; i < assets.length; i++) { + final Asset a = assets[i]; + final Asset? b = inDb[i]; + if (b == null) { + if (!a.isInDb) { + _log.warning("Trying to update an asset that does not exist in DB:\n$a"); + } + } else if (a.id != b.id) { + _log.warning("Trying to insert another asset with the same checksum+owner. In DB:\n$b\nTo insert:\n$a"); + } + } + for (int i = 1; i < assets.length; i++) { + if (Asset.compareByOwnerChecksum(assets[i - 1], assets[i]) == 0) { + _log.warning("Trying to insert duplicate assets:\n${assets[i - 1]}\n${assets[i]}"); + } + } + } + } + + /// Returns all assets that were successfully hashed + Future> _getHashedAssets( + Album album, { + int start = 0, + int end = 0x7fffffffffffffff, + DateTime? modifiedFrom, + DateTime? modifiedUntil, + Set? excludedAssets, + }) async { + final entities = await _albumMediaRepository.getAssets( + album.localId!, + start: start, + end: end, + modifiedFrom: modifiedFrom, + modifiedUntil: modifiedUntil, + ); + final filtered = excludedAssets == null + ? entities + : entities.where((e) => !excludedAssets.contains(e.localId!)).toList(); + return _hashService.hashAssets(filtered); + } + + List _removeDuplicates(List assets) { + final int before = assets.length; + assets.sort(Asset.compareByOwnerChecksumCreatedModified); + assets.uniqueConsecutive(compare: Asset.compareByOwnerChecksum, onDuplicate: (a, b) => {}); + final int duplicates = before - assets.length; + if (duplicates > 0) { + _log.warning("Ignored $duplicates duplicate assets on device"); + } + return assets; + } + + /// returns `true` if the albums differ on the surface + Future _hasAlbumChangeOnDevice(Album deviceAlbum, 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))?.assetCount; + } + + Future _removeAllLocalAlbumsAndAssets() async { + try { + final assets = await _assetRepository.getAllLocal(); + final (toDelete, toUpdate) = _handleAssetRemoval(assets, [], remote: false); + await _assetRepository.transaction(() async { + await _assetRepository.deleteByIds(toDelete); + await _assetRepository.updateAll(toUpdate); + await _albumRepository.deleteAllLocal(); + }); + return true; + } catch (e) { + _log.severe("Failed to remove all local albums and assets", e); + return false; + } + } + + Future?> getUsersFromServer() async { + List? users; + try { + users = await _userApiRepository.getAll(); + } catch (e) { + _log.warning("Failed to fetch users", e); + users = null; + } + final List sharedBy = await _partnerApiRepository.getAll(Direction.sharedByMe); + final List sharedWith = await _partnerApiRepository.getAll(Direction.sharedWithMe); + + if (users == null) { + _log.warning("Failed to refresh users"); + return null; + } + + users.sortBy((u) => u.id); + sharedBy.sortBy((u) => u.id); + sharedWith.sortBy((u) => u.id); + + final updatedSharedBy = []; + + diffSortedListsSync( + users, + sharedBy, + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), + both: (UserDto a, UserDto b) { + updatedSharedBy.add(a.copyWith(isPartnerSharedBy: true)); + return true; + }, + onlyFirst: (UserDto a) => updatedSharedBy.add(a), + onlySecond: (UserDto b) => updatedSharedBy.add(b), + ); + + final updatedSharedWith = []; + + diffSortedListsSync( + updatedSharedBy, + sharedWith, + compare: (UserDto a, UserDto b) => a.id.compareTo(b.id), + both: (UserDto a, UserDto b) { + updatedSharedWith.add(a.copyWith(inTimeline: b.inTimeline, isPartnerSharedWith: true)); + return true; + }, + onlyFirst: (UserDto a) => updatedSharedWith.add(a), + onlySecond: (UserDto b) => updatedSharedWith.add(b), + ); + + return updatedSharedWith; + } +} + +/// Returns a triple(toAdd, toUpdate, toRemove) +(List toAdd, List toUpdate, List toRemove) _diffAssets( + List assets, + List inDb, { + bool? remote, + int Function(Asset, Asset) compare = Asset.compareByChecksum, +}) { + // fast paths for trivial cases: reduces memory usage during initial sync etc. + if (assets.isEmpty && inDb.isEmpty) { + return const ([], [], []); + } else if (assets.isEmpty && remote == null) { + // remove all from database + return (const [], const [], inDb); + } else if (inDb.isEmpty) { + // add all assets + return (assets, const [], const []); + } + + final List toAdd = []; + final List toUpdate = []; + final List toRemove = []; + diffSortedListsSync( + inDb, + assets, + compare: compare, + both: (Asset a, Asset b) { + if (a.canUpdate(b)) { + toUpdate.add(a.updatedCopy(b)); + return true; + } + return false; + }, + onlyFirst: (Asset a) { + if (remote == true && a.isLocal) { + if (a.remoteId != null) { + a.remoteId = null; + toUpdate.add(a); + } + } else if (remote == false && a.isRemote) { + if (a.isLocal) { + a.localId = null; + toUpdate.add(a); + } + } else { + toRemove.add(a); + } + }, + onlySecond: (Asset b) => toAdd.add(b), + ); + return (toAdd, toUpdate, toRemove); +} + +/// returns a tuple (toDelete toUpdate) when assets are to be deleted +(List toDelete, List toUpdate) _handleAssetRemoval( + List deleteCandidates, + List existing, { + bool? remote, +}) { + if (deleteCandidates.isEmpty) { + return const ([], []); + } + deleteCandidates.sort(Asset.compareById); + deleteCandidates.uniqueConsecutive(compare: Asset.compareById); + existing.sort(Asset.compareById); + existing.uniqueConsecutive(compare: Asset.compareById); + final (tooAdd, toUpdate, toRemove) = _diffAssets( + existing, + deleteCandidates, + compare: Asset.compareById, + remote: remote, + ); + assert(tooAdd.isEmpty, "toAdd should be empty in _handleAssetRemoval"); + return (toRemove.map((e) => e.id).toList(), toUpdate); +} + +/// returns `true` if the albums differ on the surface +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 || + !remoteAlbum.modifiedAt.isAtSameMomentAs(dbAlbum.modifiedAt) || + !isAtSameMomentAs(remoteAlbum.startDate, dbAlbum.startDate) || + !isAtSameMomentAs(remoteAlbum.endDate, dbAlbum.endDate) || + !isAtSameMomentAs(remoteAlbum.lastModifiedAssetTimestamp, dbAlbum.lastModifiedAssetTimestamp); +} diff --git a/mobile/lib/services/timeline.service.dart b/mobile/lib/services/timeline.service.dart new file mode 100644 index 0000000000..eaff1027d8 --- /dev/null +++ b/mobile/lib/services/timeline.service.dart @@ -0,0 +1,98 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/timeline.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; + +final timelineServiceProvider = Provider((ref) { + return TimelineService( + ref.watch(timelineRepositoryProvider), + ref.watch(appSettingsServiceProvider), + ref.watch(userServiceProvider), + ); +}); + +class TimelineService { + final TimelineRepository _timelineRepository; + final AppSettingsService _appSettingsService; + final UserService _userService; + + const TimelineService(this._timelineRepository, this._appSettingsService, this._userService); + + Future> getTimelineUserIds() async { + final me = _userService.getMyUser(); + return _timelineRepository.getTimelineUserIds(me.id); + } + + Stream> watchTimelineUserIds() async* { + final me = _userService.getMyUser(); + yield* _timelineRepository.watchTimelineUsers(me.id); + } + + Stream watchHomeTimeline(String userId) { + return _timelineRepository.watchHomeTimeline(userId, _getGroupByOption()); + } + + Stream watchMultiUsersTimeline(List userIds) { + return _timelineRepository.watchMultiUsersTimeline(userIds, _getGroupByOption()); + } + + Stream watchArchiveTimeline() async* { + final user = _userService.getMyUser(); + + yield* _timelineRepository.watchArchiveTimeline(user.id); + } + + Stream watchFavoriteTimeline() async* { + final user = _userService.getMyUser(); + + yield* _timelineRepository.watchFavoriteTimeline(user.id); + } + + Stream watchAlbumTimeline(Album album) async* { + yield* _timelineRepository.watchAlbumTimeline(album, _getGroupByOption()); + } + + Stream watchTrashTimeline() async* { + final user = _userService.getMyUser(); + + yield* _timelineRepository.watchTrashTimeline(user.id); + } + + Stream watchAllVideosTimeline() { + final user = _userService.getMyUser(); + + return _timelineRepository.watchAllVideosTimeline(user.id); + } + + Future getTimelineFromAssets(List assets, GroupAssetsBy? groupBy) { + GroupAssetsBy groupOption = GroupAssetsBy.none; + if (groupBy == null) { + groupOption = _getGroupByOption(); + } else { + groupOption = groupBy; + } + + return _timelineRepository.getTimelineFromAssets(assets, groupOption); + } + + Stream watchAssetSelectionTimeline() async* { + final user = _userService.getMyUser(); + + yield* _timelineRepository.watchAssetSelectionTimeline(user.id); + } + + GroupAssetsBy _getGroupByOption() { + return GroupAssetsBy.values[_appSettingsService.getSetting(AppSettingsEnum.groupAssetsBy)]; + } + + Stream watchLockedTimelineProvider() async* { + final user = _userService.getMyUser(); + + yield* _timelineRepository.watchLockedTimeline(user.id, _getGroupByOption()); + } +} diff --git a/mobile/lib/services/toast.service.dart b/mobile/lib/services/toast.service.dart deleted file mode 100644 index 1cbb526351..0000000000 --- a/mobile/lib/services/toast.service.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'dart:async'; - -import 'package:immich_ui/immich_ui.dart'; - -class ToastOption { - final Duration? timeout; - final FutureOr Function()? onUndo; - - const ToastOption({this.timeout, this.onUndo}); -} - -class ToastService { - const ToastService(); - - FutureOr success(String message, {ToastOption? toast}) { - snackbar.success(message, duration: toast?.timeout, action: toast?.action); - } - - FutureOr info(String message, {ToastOption? toast}) { - snackbar.info(message, duration: toast?.timeout, action: toast?.action); - } - - FutureOr error(String message, {ToastOption? toast}) { - snackbar.error(message, duration: toast?.timeout, action: toast?.action); - } -} - -extension on ToastOption { - SnackbarAction? get action { - final onUndo = this.onUndo; - if (onUndo == null) { - return null; - } - - return SnackbarAction(onPressed: onUndo); - } -} diff --git a/mobile/lib/services/trash.service.dart b/mobile/lib/services/trash.service.dart new file mode 100644 index 0000000000..2c51a68c59 --- /dev/null +++ b/mobile/lib/services/trash.service.dart @@ -0,0 +1,75 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:openapi/api.dart'; + +final trashServiceProvider = Provider((ref) { + return TrashService( + ref.watch(apiServiceProvider), + ref.watch(assetRepositoryProvider), + ref.watch(userServiceProvider), + ); +}); + +class TrashService { + final ApiService _apiService; + final AssetRepository _assetRepository; + final UserService _userService; + + const TrashService(this._apiService, this._assetRepository, this._userService); + + Future restoreAssets(Iterable assetList) async { + final remoteAssets = assetList.where((a) => a.isRemote); + await _apiService.trashApi.restoreAssets(BulkIdsDto(ids: remoteAssets.map((e) => e.remoteId!).toList())); + + final updatedAssets = remoteAssets.map((asset) { + asset.isTrashed = false; + return asset; + }).toList(); + + await _assetRepository.updateAll(updatedAssets); + } + + Future emptyTrash() async { + final user = _userService.getMyUser(); + + await _apiService.trashApi.emptyTrash(); + + final trashedAssets = await _assetRepository.getTrashAssets(user.id); + final ids = trashedAssets.map((e) => e.remoteId!).toList(); + + await _assetRepository.transaction(() async { + await _assetRepository.deleteAllByRemoteId(ids, state: AssetState.remote); + + final merged = await _assetRepository.getAllByRemoteId(ids, state: AssetState.merged); + if (merged.isEmpty) { + return; + } + + for (final Asset asset in merged) { + asset.remoteId = null; + asset.isTrashed = false; + } + + await _assetRepository.updateAll(merged); + }); + } + + Future restoreTrash() async { + final user = _userService.getMyUser(); + + await _apiService.trashApi.restoreTrash(); + + final trashedAssets = await _assetRepository.getTrashAssets(user.id); + final updatedAssets = trashedAssets.map((asset) { + asset.isTrashed = false; + return asset; + }).toList(); + + await _assetRepository.updateAll(updatedAssets); + } +} diff --git a/mobile/lib/services/view_intent.service.dart b/mobile/lib/services/view_intent.service.dart deleted file mode 100644 index e822d1ebb6..0000000000 --- a/mobile/lib/services/view_intent.service.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:io'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; - -final viewIntentServiceProvider = Provider((ref) => ViewIntentService(ViewIntentHostApi())); - -class ViewIntentService { - final ViewIntentHostApi _viewIntentHostApi; - final Future Function() _temporaryDirectory; - String? _managedTempFilePath; - final Set _activeUploadPaths = {}; - - ViewIntentService(this._viewIntentHostApi, {Future Function()? temporaryDirectory}) - : _temporaryDirectory = temporaryDirectory ?? getTemporaryDirectory; - - Future consumeViewIntent() async { - try { - return await _viewIntentHostApi.consumeViewIntent(); - } catch (_) { - // Ignore errors - view intent might not be present - return null; - } - } - - Future setManagedTempFilePath(String path) async { - final previous = _managedTempFilePath; - if (previous == path) { - return; - } - _managedTempFilePath = path; - if (previous != null) { - await cleanupTempFile(previous); - } - } - - Future cleanupManagedTempFile() async { - final path = _managedTempFilePath; - _managedTempFilePath = null; - if (path != null) { - await cleanupTempFile(path); - } - } - - Future cleanupManagedTempFileIfCurrent(String path) async { - if (_managedTempFilePath == path) { - _managedTempFilePath = null; - } - await cleanupTempFile(path); - } - - Future cleanupTempFile(String path) async { - if (!_isManagedTempFile(path)) { - return; - } - if (_activeUploadPaths.contains(path)) { - return; - } - - try { - final file = File(path); - // ignore: avoid_slow_async_io - if (await file.exists()) { - await file.delete(); - } - } catch (_) { - // Best-effort cleanup only. - } - } - - Future cleanupStaleTempFiles() async { - try { - final tempDirectory = await _temporaryDirectory(); - await for (final entity in tempDirectory.list()) { - if (entity is! File) { - continue; - } - - final path = entity.path; - if (!_isManagedTempFile(path) || path == _managedTempFilePath || _activeUploadPaths.contains(path)) { - continue; - } - - await entity.delete(); - } - } catch (_) { - // Best-effort cleanup only. - } - } - - void markUploadActive(String path) { - _activeUploadPaths.add(path); - } - - Future markUploadInactive(String path) async { - if (!_activeUploadPaths.remove(path)) { - return; - } - if (_managedTempFilePath != path) { - await cleanupTempFile(path); - } - } - - bool _isManagedTempFile(String path) { - return p.basename(path).startsWith('view_intent_') && p.basename(p.dirname(path)) == 'cache'; - } -} diff --git a/mobile/lib/services/view_intent_asset_resolver.service.dart b/mobile/lib/services/view_intent_asset_resolver.service.dart deleted file mode 100644 index 94296f5293..0000000000 --- a/mobile/lib/services/view_intent_asset_resolver.service.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; -import 'package:immich_mobile/models/view_intent/view_intent_payload.extension.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:logging/logging.dart'; - -class ViewIntentResolvedAsset { - final BaseAsset asset; - final TimelineService timelineService; - - final String? viewIntentFilePath; - - const ViewIntentResolvedAsset({required this.asset, required this.timelineService, this.viewIntentFilePath}); -} - -final viewIntentAssetResolverProvider = Provider( - (ref) => ViewIntentAssetResolver( - localAssetRepository: ref.read(driftProvider).localAssetRepository, - timelineFactory: ref.read(timelineFactoryProvider), - ), -); - -class ViewIntentAssetResolver { - final LocalAssetRepository _localAssetRepository; - final TimelineFactory _timelineFactory; - static final Logger _logger = Logger('ViewIntentAssetResolver'); - - const ViewIntentAssetResolver({required this._localAssetRepository, required this._timelineFactory}); - - Future resolve(ViewIntentPayload attachment) async { - final localAssetId = attachment.localAssetId; - final path = attachment.path; - _logger.fine('resolve start, localAssetId=$localAssetId, path=$path, mimeType=${attachment.mimeType}'); - - if (localAssetId == null && path == null) { - throw StateError('ViewIntent resolution requires either a localAssetId or a materialized file path.'); - } - - final localAsset = localAssetId != null ? await _localAssetRepository.getById(localAssetId) : null; - final asset = localAsset ?? _toTransientAsset(attachment); - - return ViewIntentResolvedAsset( - asset: asset, - timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.deepLink), - viewIntentFilePath: localAsset == null ? path : null, - ); - } - - LocalAsset _toTransientAsset(ViewIntentPayload attachment) { - final now = DateTime.now(); - return LocalAsset( - id: attachment.localAssetId ?? '-${attachment.path!.hashCode.abs()}', - name: attachment.fileName, - type: attachment.isVideo ? AssetType.video : AssetType.image, - createdAt: now, - updatedAt: now, - isEdited: false, - playbackStyle: attachment.playbackStyle, - ); - } -} diff --git a/mobile/lib/services/widget.service.dart b/mobile/lib/services/widget.service.dart index 91f65dd684..23ec0aa770 100644 --- a/mobile/lib/services/widget.service.dart +++ b/mobile/lib/services/widget.service.dart @@ -12,7 +12,7 @@ class WidgetService { const WidgetService(this._repository); Future writeCredentials(String serverURL, String sessionKey, String? customHeaders) async { - await _repository.setAppGroupId(); + await _repository.setAppGroupId(appShareGroupId); await _repository.saveData(kWidgetServerEndpoint, serverURL); await _repository.saveData(kWidgetAuthToken, sessionKey); @@ -25,7 +25,7 @@ class WidgetService { } Future clearCredentials() async { - await _repository.setAppGroupId(); + await _repository.setAppGroupId(appShareGroupId); await _repository.saveData(kWidgetServerEndpoint, ""); await _repository.saveData(kWidgetAuthToken, ""); await _repository.saveData(kWidgetCustomHeaders, ""); diff --git a/mobile/lib/theme/dynamic_theme.dart b/mobile/lib/theme/dynamic_theme.dart index 088679b508..7f7c4d05d7 100644 --- a/mobile/lib/theme/dynamic_theme.dart +++ b/mobile/lib/theme/dynamic_theme.dart @@ -1,5 +1,6 @@ -import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; +import 'package:dynamic_color/dynamic_color.dart'; + import 'package:immich_mobile/theme/theme_data.dart'; import 'package:immich_mobile/utils/debug_print.dart'; diff --git a/mobile/lib/utils/action_button.utils.dart b/mobile/lib/utils/action_button.utils.dart index 4e0a5efff5..2aad2f264a 100644 --- a/mobile/lib/utils/action_button.utils.dart +++ b/mobile/lib/utils/action_button.utils.dart @@ -1,32 +1,35 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_mobile/presentation/actions/cast.action.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/presentation/actions/open_in_browser.action.dart'; -import 'package:immich_mobile/presentation/actions/remove_from_album.action.dart'; -import 'package:immich_mobile/presentation/actions/restore.action.dart'; -import 'package:immich_mobile/presentation/actions/set_album_cover.action.dart'; -import 'package:immich_mobile/presentation/actions/set_profile_picture.action.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_mobile/presentation/actions/similar_photos.action.dart'; -import 'package:immich_mobile/presentation/actions/slideshow.action.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; -import 'package:immich_mobile/presentation/actions/upload.action.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/like_activity_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/open_in_browser_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_album_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/remove_from_lock_folder_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/set_album_cover.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/share_link_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/similar_photos_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/set_profile_picture_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart'; import 'package:immich_mobile/routing/router.dart'; class ActionButtonContext { @@ -41,6 +44,7 @@ class ActionButtonContext { final ActionSource source; final bool isCasting; final TimelineOrigin timelineOrigin; + final ThemeData? originalTheme; final int selectedCount; const ActionButtonContext({ @@ -55,6 +59,7 @@ class ActionButtonContext { required this.source, this.isCasting = false, this.timelineOrigin = TimelineOrigin.main, + this.originalTheme, this.selectedCount = 1, }); } @@ -69,7 +74,6 @@ enum ActionButtonType { similarPhotos, setProfilePicture, viewInTimeline, - slideshow, download, upload, openInBrowser, @@ -79,8 +83,9 @@ enum ActionButtonType { moveToLockFolder, removeFromLockFolder, removeFromAlbum, - restoreTrash, + trash, deleteLocal, + deletePermanent, delete, advancedInfo; @@ -105,12 +110,20 @@ enum ActionButtonType { !context.isInLockedView && // context.asset.hasRemote && // !context.asset.hasLocal, - ActionButtonType.restoreTrash => + ActionButtonType.trash => context.isOwner && // !context.isInLockedView && // context.asset.hasRemote && // - context.timelineOrigin == TimelineOrigin.trash, - ActionButtonType.delete => true, + context.isTrashEnabled, + ActionButtonType.deletePermanent => + context.isOwner && // + context.asset.hasRemote && // + !context.isTrashEnabled || + context.isInLockedView, + ActionButtonType.delete => + context.isOwner && // + !context.isInLockedView && // + context.asset.hasRemote, ActionButtonType.moveToLockFolder => context.isOwner && // !context.isInLockedView && // @@ -121,7 +134,7 @@ enum ActionButtonType { context.asset.hasRemote, ActionButtonType.deleteLocal => !context.isInLockedView && // - context.asset.isMerged, + context.asset.hasLocal, ActionButtonType.upload => !context.isInLockedView && // context.asset.storage == AssetState.local, @@ -135,7 +148,6 @@ enum ActionButtonType { context.selectedCount == 1, ActionButtonType.unstack => context.isOwner && // - context.timelineOrigin != TimelineOrigin.trash && !context.isInLockedView && // context.isStacked, ActionButtonType.openInBrowser => context.asset.hasRemote && !context.isInLockedView, @@ -161,66 +173,110 @@ enum ActionButtonType { context.timelineOrigin != TimelineOrigin.localAlbum && context.isOwner, ActionButtonType.cast => context.isCasting || context.asset.hasRemote, - ActionButtonType.slideshow => true, }; } - Widget buildButton( + ConsumerWidget buildButton( ActionButtonContext context, [ BuildContext? buildContext, bool iconOnly = false, bool menuItem = false, ]) { return switch (this) { - ActionButtonType.advancedInfo => ActionMenuItem(action: AssetDebugAction(source: context.source)), - ActionButtonType.share => ActionMenuItem(action: ShareAction(source: context.source)), - ActionButtonType.shareLink => ActionMenuItem(action: ShareLinkAction(source: context.source)), - ActionButtonType.slideshow => const ActionMenuItem(action: SlideshowAction()), - ActionButtonType.archive || - ActionButtonType.unarchive => ActionMenuItem(action: ArchiveAction(source: context.source)), - ActionButtonType.download => ActionMenuItem(action: DownloadAction(source: context.source)), - ActionButtonType.restoreTrash => ActionMenuItem(action: RestoreAction(source: context.source)), - ActionButtonType.delete => ActionMenuItem(action: DeleteAction(source: context.source)), - ActionButtonType.moveToLockFolder || - ActionButtonType.removeFromLockFolder => ActionMenuItem(action: LockAction(source: context.source)), - ActionButtonType.deleteLocal => ActionMenuItem(action: CleanupLocalAction(source: context.source)), - ActionButtonType.upload => ActionMenuItem( - action: UploadAction(source: context.source, showProgress: context.source == ActionSource.viewer), + ActionButtonType.advancedInfo => AdvancedInfoActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, ), - ActionButtonType.removeFromAlbum => ActionMenuItem( - action: RemoveFromAlbumAction(source: context.source, albumId: context.currentAlbum!.id), + ActionButtonType.share => ShareActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.shareLink => ShareLinkActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, ), - ActionButtonType.setAlbumCover => ActionMenuItem( - action: SetAlbumCoverAction(source: context.source, albumId: context.currentAlbum!.id), + ActionButtonType.archive => ArchiveActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.unarchive => UnArchiveActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.download => DownloadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.trash => TrashActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.deletePermanent => DeletePermanentActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.delete => DeleteActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.moveToLockFolder => MoveToLockFolderActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.removeFromLockFolder => RemoveFromLockFolderActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.deleteLocal => DeleteLocalActionButton( + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.upload => UploadActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.removeFromAlbum => RemoveFromAlbumActionButton( + albumId: context.currentAlbum!.id, + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.setAlbumCover => SetAlbumCoverActionButton( + albumId: context.currentAlbum!.id, + source: context.source, + iconOnly: iconOnly, + menuItem: menuItem, ), ActionButtonType.likeActivity => LikeActivityActionButton(iconOnly: iconOnly, menuItem: menuItem), - ActionButtonType.unstack => ActionMenuItem(action: StackAction(source: context.source)), - ActionButtonType.openInBrowser => ActionMenuItem( - action: OpenInBrowserAction(remoteId: context.asset.remoteId!, origin: context.timelineOrigin), + ActionButtonType.unstack => UnStackActionButton(source: context.source, iconOnly: iconOnly, menuItem: menuItem), + ActionButtonType.openInBrowser => OpenInBrowserActionButton( + remoteId: context.asset.remoteId!, + origin: context.timelineOrigin, + iconOnly: iconOnly, + menuItem: menuItem, + iconColor: context.originalTheme?.iconTheme.color, ), - ActionButtonType.similarPhotos => ActionMenuItem( - action: SimilarPhotosAction(assetId: (context.asset as RemoteAsset).id), + ActionButtonType.similarPhotos => SimilarPhotosActionButton( + assetId: (context.asset as RemoteAsset).id, + iconOnly: iconOnly, + menuItem: menuItem, + ), + ActionButtonType.setProfilePicture => SetProfilePictureActionButton( + asset: context.asset, + iconOnly: iconOnly, + menuItem: menuItem, ), - ActionButtonType.setProfilePicture => ActionMenuItem(action: SetProfilePictureAction(asset: context.asset)), ActionButtonType.openInfo => BaseActionButton( - label: StaticTranslations.instance.info, + label: 'info'.tr(), iconData: Icons.info_outline, + iconColor: context.originalTheme?.iconTheme.color, menuItem: true, onPressed: () => EventStream.shared.emit(const ViewerShowDetailsEvent()), ), ActionButtonType.viewInTimeline => BaseActionButton( - label: StaticTranslations.instance.view_in_timeline, + label: 'view_in_timeline'.tr(), iconData: Icons.image_search, + iconColor: context.originalTheme?.iconTheme.color, iconOnly: iconOnly, menuItem: menuItem, onPressed: buildContext == null ? null : () async { - await buildContext.router.navigate(const TabShellRoute(children: [MainTimelineRoute()])); + await buildContext.maybePop(); + await buildContext.navigateTo(const TabShellRoute(children: [MainTimelineRoute()])); EventStream.shared.emit(ScrollToDateEvent(context.asset.createdAt)); }, ), - ActionButtonType.cast => const ActionMenuItem(action: CastAction()), + ActionButtonType.cast => CastActionButton(iconOnly: iconOnly, menuItem: menuItem), }; } @@ -231,6 +287,8 @@ enum ActionButtonType { // 0: info ActionButtonType.openInfo => 0, // 10: move, remove, and delete + ActionButtonType.trash => 10, + ActionButtonType.deletePermanent => 10, ActionButtonType.removeFromLockFolder => 10, ActionButtonType.removeFromAlbum => 10, ActionButtonType.unstack => 10, @@ -239,7 +297,6 @@ enum ActionButtonType { ActionButtonType.moveToLockFolder => 10, ActionButtonType.deleteLocal => 10, ActionButtonType.delete => 10, - ActionButtonType.restoreTrash => 10, // 90: advancedInfo ActionButtonType.advancedInfo => 90, // 1: others @@ -257,14 +314,13 @@ class ActionButtonBuilder { ActionButtonType.delete, ActionButtonType.archive, ActionButtonType.unarchive, - ActionButtonType.restoreTrash, }; static List build(ActionButtonContext context) { return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList(); } - static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext) { + static List buildViewerKebabMenu(ActionButtonContext context, BuildContext buildContext, WidgetRef ref) { final visibleButtons = defaultViewerKebabMenuOrder .where((type) => !defaultViewerBottomBarButtons.contains(type) && type.shouldShow(context)) .toList(); @@ -280,7 +336,7 @@ class ActionButtonBuilder { if (lastGroup != null && type.kebabMenuGroup != lastGroup) { result.add(const Divider(height: 1)); } - result.add(type.buildButton(context, buildContext, false, true)); + result.add(type.buildButton(context, buildContext, false, true).build(buildContext, ref)); lastGroup = type.kebabMenuGroup; } diff --git a/mobile/lib/utils/album_filter.utils.dart b/mobile/lib/utils/album_filter.utils.dart index daa8aff613..8f9363d4d9 100644 --- a/mobile/lib/utils/album_filter.utils.dart +++ b/mobile/lib/utils/album_filter.utils.dart @@ -1,15 +1,25 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; -part 'album_filter.utils.freezed.dart'; +class AlbumFilter { + String? userId; + String? query; + QuickFilterMode mode; -@freezed -abstract class AlbumFilter with _$AlbumFilter { - const factory AlbumFilter({required QuickFilterMode mode, String? userId, String? query}) = _AlbumFilter; + AlbumFilter({required this.mode, this.userId, this.query}); + + AlbumFilter copyWith({String? userId, String? query, QuickFilterMode? mode}) { + return AlbumFilter(userId: userId ?? this.userId, query: query ?? this.query, mode: mode ?? this.mode); + } } -@freezed -abstract class AlbumSort with _$AlbumSort { - const factory AlbumSort({required AlbumSortMode mode, @Default(false) bool isReverse}) = _AlbumSort; +class AlbumSort { + AlbumSortMode mode; + bool isReverse; + + AlbumSort({required this.mode, this.isReverse = false}); + + AlbumSort copyWith({AlbumSortMode? mode, bool? isReverse}) { + return AlbumSort(mode: mode ?? this.mode, isReverse: isReverse ?? this.isReverse); + } } diff --git a/mobile/lib/utils/asset_filter.dart b/mobile/lib/utils/asset_filter.dart deleted file mode 100644 index a4de5d2761..0000000000 --- a/mobile/lib/utils/asset_filter.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; - -extension type const AssetFilter(Iterable assets) implements Iterable { - AssetFilter where(bool Function(T asset) test) => AssetFilter(assets.where(test)); - AssetFilter whereNot(bool Function(T asset) test) => AssetFilter(assets.where((asset) => !test(asset))); - - AssetFilter type(AssetType type) => where((asset) => asset.type == type); - AssetFilter favorite({bool isFavorite = true}) => where((asset) => asset.isFavorite == isFavorite); - - AssetFilter remote() => AssetFilter(assets.whereType()); - AssetFilter owned(String ownerId) => remote().where((asset) => asset.ownerId == ownerId); - AssetFilter visibility(AssetVisibility visibility) => - remote().where((asset) => asset.visibility == visibility); - AssetFilter notVisibility(AssetVisibility visibility) => - remote().where((asset) => asset.visibility != visibility); - AssetFilter archived({bool isArchived = true}) => - remote().where((asset) => asset.isArchived == isArchived); - AssetFilter locked({bool isLocked = true}) => remote().where((asset) => asset.isLocked == isLocked); - AssetFilter stacked({bool isStacked = true}) => remote().where((asset) => asset.isStacked == isStacked); - AssetFilter trashed({bool isTrashed = true}) => remote().where((asset) => asset.isTrashed == isTrashed); - - AssetFilter local() => AssetFilter(assets.whereType()); - AssetFilter backedUp({bool isBackedUp = true}) => where((asset) => asset.isMerged == isBackedUp); -} diff --git a/mobile/lib/utils/async_mutex.dart b/mobile/lib/utils/async_mutex.dart index 6c54c1220d..b97ab9b052 100644 --- a/mobile/lib/utils/async_mutex.dart +++ b/mobile/lib/utils/async_mutex.dart @@ -12,12 +12,10 @@ class AsyncMutex { Future run(Future Function() operation) { final completer = Completer(); _enqueued++; - unawaited( - _running.whenComplete(() { - _enqueued--; - completer.complete(Future.sync(operation)); - }), - ); + _running.whenComplete(() { + _enqueued--; + completer.complete(Future.sync(operation)); + }); return _running = completer.future; } } diff --git a/mobile/lib/utils/backup_progress.dart b/mobile/lib/utils/backup_progress.dart new file mode 100644 index 0000000000..36050f5e20 --- /dev/null +++ b/mobile/lib/utils/backup_progress.dart @@ -0,0 +1,83 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:easy_localization/easy_localization.dart'; + +final NumberFormat numberFormat = NumberFormat("###0.##"); + +String formatAssetBackupProgress(int uploadedAssets, int assetsToUpload) { + final int percent = (uploadedAssets * 100) ~/ assetsToUpload; + return "$percent% ($uploadedAssets/$assetsToUpload)"; +} + +/// prints progress in useful (kilo/mega/giga)bytes +String humanReadableFileBytesProgress(int bytes, int bytesTotal) { + String unit = "KB"; + + if (bytesTotal >= 0x40000000) { + unit = "GB"; + bytes >>= 20; + bytesTotal >>= 20; + } else if (bytesTotal >= 0x100000) { + unit = "MB"; + bytes >>= 10; + bytesTotal >>= 10; + } else if (bytesTotal < 0x400) { + return "${(bytes).toStringAsFixed(2)} B / ${(bytesTotal).toStringAsFixed(2)} B"; + } + + return "${(bytes / 1024.0).toStringAsFixed(2)} $unit / ${(bytesTotal / 1024.0).toStringAsFixed(2)} $unit"; +} + +/// prints percentage and absolute progress in useful (kilo/mega/giga)bytes +String humanReadableBytesProgress(int bytes, int bytesTotal) { + String unit = "KB"; // Kilobyte + if (bytesTotal >= 0x40000000) { + unit = "GB"; // Gigabyte + bytes >>= 20; + bytesTotal >>= 20; + } else if (bytesTotal >= 0x100000) { + unit = "MB"; // Megabyte + bytes >>= 10; + bytesTotal >>= 10; + } else if (bytesTotal < 0x400) { + return "$bytes / $bytesTotal B"; + } + final int percent = (bytes * 100) ~/ bytesTotal; + final String done = numberFormat.format(bytes / 1024.0); + final String total = numberFormat.format(bytesTotal / 1024.0); + return "$percent% ($done/$total$unit)"; +} + +class ThrottleProgressUpdate { + ThrottleProgressUpdate(this._fun, Duration interval) : _interval = interval.inMicroseconds; + final void Function(String?, int, int) _fun; + final int _interval; + int _invokedAt = 0; + Timer? _timer; + + String? title; + int progress = 0; + int total = 0; + + void call({final String? title, final int progress = 0, final int total = 0}) { + final time = Timeline.now; + this.title = title ?? this.title; + this.progress = progress; + this.total = total; + if (time > _invokedAt + _interval) { + _timer?.cancel(); + _onTimeElapsed(); + } else { + _timer ??= Timer(Duration(microseconds: _interval), _onTimeElapsed); + } + } + + void _onTimeElapsed() { + _invokedAt = Timeline.now; + _fun(title, progress, total); + _timer = null; + // clear title to not send/overwrite it next time if unchanged + title = null; + } +} diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index e6116afe53..d63a92ba37 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -1,96 +1,113 @@ +import 'dart:io'; + import 'package:background_downloader/background_downloader.dart'; +import 'package:flutter/foundation.dart'; import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/android_device_asset.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; -import 'package:logging/logging.dart'; -import 'package:photo_manager/photo_manager.dart'; -import 'package:sqlite3/common.dart'; +import 'package:isar/isar.dart'; +import 'package:path_provider/path_provider.dart'; void configureFileDownloaderNotifications() { - final t = StaticTranslations.instance; - FileDownloader().configureNotificationForGroup( kDownloadGroupImage, - running: TaskNotification(t.downloading_media, '${t.file_name_text}: {filename}'), - complete: TaskNotification(t.download_finished, '${t.file_name_text}: {filename}'), + running: TaskNotification('downloading_media'.t(), '${'file_name_text'.t()}: {filename}'), + complete: TaskNotification('download_finished'.t(), '${'file_name_text'.t()}: {filename}'), progressBar: true, ); FileDownloader().configureNotificationForGroup( kDownloadGroupVideo, - running: TaskNotification(t.downloading_media, '${t.file_name_text}: {filename}'), - complete: TaskNotification(t.download_finished, '${t.file_name_text}: {filename}'), + running: TaskNotification('downloading_media'.t(), '${'file_name_text'.t()}: {filename}'), + complete: TaskNotification('download_finished'.t(), '${'file_name_text'.t()}: {filename}'), progressBar: true, ); FileDownloader().configureNotificationForGroup( kManualUploadGroup, - running: TaskNotification(t.uploading_media, t.backup_background_service_in_progress_notification), - complete: TaskNotification(t.upload_finished, t.backup_background_service_complete_notification), + running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), + complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), groupNotificationId: kManualUploadGroup, ); FileDownloader().configureNotificationForGroup( kBackupGroup, - running: TaskNotification(t.uploading_media, t.backup_background_service_in_progress_notification), - complete: TaskNotification(t.upload_finished, t.backup_background_service_complete_notification), + running: TaskNotification('uploading_media'.t(), 'backup_background_service_in_progress_notification'.t()), + complete: TaskNotification('upload_finished'.t(), 'backup_background_service_complete_notification'.t()), groupNotificationId: kBackupGroup, ); } abstract final class Bootstrap { - static Future<(Drift, DriftLogger)> initDomain({bool listenStoreUpdates = true, bool shouldBufferLogs = true}) async { - await configureSqliteCache(); - final (db, updatePool) = await openSqliteConnectionWithUpdatePool(name: 'immich'); - final drift = Drift.sqlite(db, updatePool); - final StoreRepository storeRepo = StoreRepository(drift); + static Future<(Isar isar, Drift drift, DriftLogger logDb)> initDB() async { + final drift = Drift(); + final logDb = DriftLogger(); + + Isar? isar = Isar.getInstance(); + + if (isar != null) { + return (isar, drift, logDb); + } + + final dir = await getApplicationDocumentsDirectory(); + isar = await Isar.open( + [ + StoreValueSchema, + AssetSchema, + AlbumSchema, + ExifInfoSchema, + UserSchema, + BackupAlbumSchema, + DuplicatedAssetSchema, + ETagSchema, + if (Platform.isAndroid) AndroidDeviceAssetSchema, + if (Platform.isIOS) IOSDeviceAssetSchema, + DeviceAssetEntitySchema, + ], + directory: dir.path, + maxSizeMiB: 2048, + inspector: kDebugMode, + ); + + return (isar, drift, logDb); + } + + static Future initDomain( + Isar db, + Drift drift, + DriftLogger logDb, { + bool listenStoreUpdates = true, + bool shouldBufferLogs = true, + }) async { + final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? true; + final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db); await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); - final settingsRepo = await SettingsRepository.ensureInitialized(drift); - final logDb = await _initLogger(settingsRepository: settingsRepo, shouldBufferLogs: shouldBufferLogs); + await LogService.init( + logRepository: LogRepository(logDb), + storeRepository: storeRepo, + shouldBuffer: shouldBufferLogs, + ); await NetworkRepository.init(); - // Remove once all asset operations are migrated to Native APIs - await PhotoManager.setIgnorePermissionCheck(true); - return (drift, logDb); } } - -Future _initLogger({required SettingsRepository settingsRepository, bool shouldBufferLogs = true}) async { - Future open() async => DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs')); - - DriftLogger logDb = await open(); - bool wasCorrupt = false; - try { - await logDb.customSelect('SELECT COUNT(*) FROM logger_messages').get(); - } on SqliteException catch (error) { - if (error.resultCode != SqlError.SQLITE_CORRUPT && error.resultCode != SqlError.SQLITE_NOTADB) { - await logDb.close(); - rethrow; - } - dPrint(() => 'Logs database is corrupt, recreating it'); - await logDb.close(); - await deleteSqliteDatabase(name: 'immich_logs'); - logDb = await open(); - wasCorrupt = true; - } - - await LogService.init( - logRepository: LogRepository(logDb), - settingsRepository: settingsRepository, - shouldBuffer: shouldBufferLogs, - ); - if (wasCorrupt) { - Logger('bootstrap:initLogger').warning('Logs database was corrupt and has been recreated'); - } - return logDb; -} diff --git a/mobile/lib/utils/bytes_units.dart b/mobile/lib/utils/bytes_units.dart index 2c5c5f18a9..66de6493ab 100644 --- a/mobile/lib/utils/bytes_units.dart +++ b/mobile/lib/utils/bytes_units.dart @@ -18,10 +18,8 @@ String formatBytes(int bytes) { } String formatHumanReadableBytes(int bytes, int decimals) { - if (bytes <= 0) { - return "0 B"; - } + if (bytes <= 0) return "0 B"; const suffixes = ["B", "KiB", "MiB", "GiB", "TiB"]; - final i = (log(bytes) / log(1024)).floor(); + var i = (log(bytes) / log(1024)).floor(); return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}'; } diff --git a/mobile/lib/utils/color_filter_generator.dart b/mobile/lib/utils/color_filter_generator.dart new file mode 100644 index 0000000000..92aed4b1a0 --- /dev/null +++ b/mobile/lib/utils/color_filter_generator.dart @@ -0,0 +1,99 @@ +import 'package:flutter/widgets.dart'; + +class InvertionFilter extends StatelessWidget { + final Widget? child; + const InvertionFilter({super.key, this.child}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: const ColorFilter.matrix([ + -1, 0, 0, 0, 255, // + 0, -1, 0, 0, 255, // + 0, 0, -1, 0, 255, // + 0, 0, 0, 1, 0, // + ]), + child: child, + ); + } +} + +// -1 - darkest, 1 - brightest, 0 - unchanged +class BrightnessFilter extends StatelessWidget { + final Widget? child; + final double brightness; + const BrightnessFilter({super.key, this.child, this.brightness = 0}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: ColorFilter.matrix(_ColorFilterGenerator.brightnessAdjustMatrix(brightness)), + child: child, + ); + } +} + +// -1 - greyscale, 1 - most saturated, 0 - unchanged +class SaturationFilter extends StatelessWidget { + final Widget? child; + final double saturation; + const SaturationFilter({super.key, this.child, this.saturation = 0}); + + @override + Widget build(BuildContext context) { + return ColorFiltered( + colorFilter: ColorFilter.matrix(_ColorFilterGenerator.saturationAdjustMatrix(saturation)), + child: child, + ); + } +} + +class _ColorFilterGenerator { + static List brightnessAdjustMatrix(double value) { + value = value * 10; + + if (value == 0) { + return [ + 1, 0, 0, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + ]; + } + + return List.from([ + 1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0, // + ]).map((i) => i.toDouble()).toList(); + } + + static List saturationAdjustMatrix(double value) { + value = value * 100; + + if (value == 0) { + return [ + 1, 0, 0, 0, 0, // + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + ]; + } + + double x = ((1 + ((value > 0) ? ((3 * value) / 100) : (value / 100)))).toDouble(); + double lumR = 0.3086; + double lumG = 0.6094; + double lumB = 0.082; + + return List.from([ + (lumR * (1 - x)) + x, lumG * (1 - x), lumB * (1 - x), // + 0, 0, // + lumR * (1 - x), // + (lumG * (1 - x)) + x, // + lumB * (1 - x), // + 0, 0, // + lumR * (1 - x), // + lumG * (1 - x), // + (lumB * (1 - x)) + x, // + 0, 0, 0, 0, 0, 1, 0, // + ]).map((i) => i.toDouble()).toList(); + } +} diff --git a/mobile/lib/utils/datetime_comparison.dart b/mobile/lib/utils/datetime_comparison.dart new file mode 100644 index 0000000000..f8ddcfea11 --- /dev/null +++ b/mobile/lib/utils/datetime_comparison.dart @@ -0,0 +1,2 @@ +bool isAtSameMomentAs(DateTime? a, DateTime? b) => + (a == null && b == null) || ((a != null && b != null) && a.isAtSameMomentAs(b)); diff --git a/mobile/lib/utils/diff.dart b/mobile/lib/utils/diff.dart index fae3a5d516..ea20de16cc 100644 --- a/mobile/lib/utils/diff.dart +++ b/mobile/lib/utils/diff.dart @@ -16,8 +16,7 @@ Future diffSortedLists( assert(la.isSorted(compare), "first argument must be sorted"); assert(lb.isSorted(compare), "second argument must be sorted"); bool diff = false; - int i = 0; - int j = 0; + int i = 0, j = 0; for (; i < la.length && j < lb.length;) { final int order = compare(la[i], lb[j]); if (order == 0) { @@ -54,8 +53,7 @@ bool diffSortedListsSync( assert(la.isSorted(compare), "first argument must be sorted"); assert(lb.isSorted(compare), "second argument must be sorted"); bool diff = false; - int i = 0; - int j = 0; + int i = 0, j = 0; for (; i < la.length && j < lb.length;) { final int order = compare(la[i], lb[j]); if (order == 0) { diff --git a/mobile/lib/utils/editor.utils.dart b/mobile/lib/utils/editor.utils.dart deleted file mode 100644 index c3440cce1a..0000000000 --- a/mobile/lib/utils/editor.utils.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/utils/matrix.utils.dart'; -import 'package:openapi/api.dart' hide AssetEditAction; - -Rect convertCropParametersToRect(CropParameters parameters, int originalWidth, int originalHeight) { - return Rect.fromLTWH( - parameters.x.toDouble() / originalWidth, - parameters.y.toDouble() / originalHeight, - parameters.width.toDouble() / originalWidth, - parameters.height.toDouble() / originalHeight, - ); -} - -CropParameters convertRectToCropParameters(Rect rect, int originalWidth, int originalHeight) { - final x = (rect.left * originalWidth).truncate(); - final y = (rect.top * originalHeight).truncate(); - final width = (rect.width * originalWidth).truncate(); - final height = (rect.height * originalHeight).truncate(); - - return CropParameters( - x: max(x, 0).clamp(0, originalWidth), - y: max(y, 0).clamp(0, originalHeight), - width: max(width, 0).clamp(0, originalWidth - x), - height: max(height, 0).clamp(0, originalHeight - y), - ); -} - -AffineMatrix buildAffineFromEdits(List edits) { - return AffineMatrix.compose( - edits.map((edit) { - return switch (edit) { - RotateEdit(:final parameters) => AffineMatrix.rotate(parameters.angle * pi / 180), - MirrorEdit(:final parameters) => - parameters.axis == MirrorAxis.horizontal ? AffineMatrix.flipY() : AffineMatrix.flipX(), - CropEdit() => AffineMatrix.identity(), - }; - }).toList(), - ); -} - -bool isCloseToZero(double value, [double epsilon = 1e-15]) { - return value.abs() < epsilon; -} - -typedef NormalizedTransform = ({double rotation, bool mirrorHorizontal, bool mirrorVertical}); - -NormalizedTransform normalizeTransformEdits(List edits) { - final matrix = buildAffineFromEdits(edits); - - final double a = matrix.a; - final double b = matrix.b; - final double c = matrix.c; - final double d = matrix.d; - - final rotation = ((isCloseToZero(a) ? asin(c) : acos(a)) * 180) / pi; - - return ( - rotation: rotation < 0 ? 360 + rotation : rotation, - mirrorHorizontal: false, - mirrorVertical: isCloseToZero(a) ? b == c : a == -d, - ); -} diff --git a/mobile/lib/utils/error_handler.dart b/mobile/lib/utils/error_handler.dart deleted file mode 100644 index dcac1431ab..0000000000 --- a/mobile/lib/utils/error_handler.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/widgets.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:openapi/api.dart'; -// ignore: depend_on_referenced_packages -import 'package:stack_trace/stack_trace.dart'; - -void handleError(Object error, {StackTrace? stack, String? description}) { - String? stackTrace; - if (stack != null) { - final trace = Trace.from(stack); - final clean = trace.foldFrames( - (frame) => frame.package == 'flutter' || frame.package == 'flutter_test' || frame.isCore, - terse: true, - ); - stackTrace = clean.toString(); - } - - dPrint( - () => 'Error${description != null ? ' ($description)' : ''}: $error${stackTrace != null ? '\n$stackTrace' : ''}', - ); - - final String message; - if (serverErrorMessage(error) case final String serverMessage) { - message = serverMessage; - } else if (isConnectionError(error)) { - message = StaticTranslations.instance.login_form_server_error; - } else { - message = StaticTranslations.instance.scaffold_body_error_occurred; - } - - snackbar.error(message); -} - -@visibleForTesting -String? serverErrorMessage(Object error) { - if (error is! ApiException || error.innerException != null || error.message == null) { - return null; - } - - try { - final body = jsonDecode(error.message!); - if (body is Map && body['message'] != null) { - final message = body['message']; - return message is List ? message.join(', ') : message.toString(); - } - } catch (_) { - // The body was not JSON; fall back to the raw payload below. - } - return error.message; -} - -@visibleForTesting -bool isConnectionError(Object error) => error is ApiException && error.innerException != null; diff --git a/mobile/lib/utils/hooks/app_settings_update_hook.dart b/mobile/lib/utils/hooks/app_settings_update_hook.dart index 6b7ba357f3..954e44229a 100644 --- a/mobile/lib/utils/hooks/app_settings_update_hook.dart +++ b/mobile/lib/utils/hooks/app_settings_update_hook.dart @@ -1,15 +1,13 @@ -import 'dart:async'; - import 'package:flutter/cupertino.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; -import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; ValueNotifier useAppSettingsState(AppSettingsEnum key) { final notifier = useState(Store.get(key.storeKey, key.defaultValue)); // Listen to changes to the notifier and update app settings - useValueChanged(notifier.value, (_, _) => unawaited(Store.put(key.storeKey, notifier.value))); + useValueChanged(notifier.value, (_, __) => Store.put(key.storeKey, notifier.value)); return notifier; } diff --git a/mobile/lib/utils/hooks/blurhash_hook.dart b/mobile/lib/utils/hooks/blurhash_hook.dart index 534c0ad8fb..ac5fd31724 100644 --- a/mobile/lib/utils/hooks/blurhash_hook.dart +++ b/mobile/lib/utils/hooks/blurhash_hook.dart @@ -1,10 +1,20 @@ import 'dart:convert'; import 'dart:typed_data'; - import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:thumbhash/thumbhash.dart' as thumbhash; +ObjectRef useBlurHashRef(Asset? asset) { + if (asset?.thumbhash == null) { + return useRef(null); + } + + final rbga = thumbhash.thumbHashToRGBA(base64Decode(asset!.thumbhash!)); + + return useRef(thumbhash.rgbaToBmp(rbga)); +} + ObjectRef useDriftBlurHashRef(RemoteAsset? asset) { if (asset?.thumbHash == null) { return useRef(null); diff --git a/mobile/lib/utils/hooks/crop_controller_hook.dart b/mobile/lib/utils/hooks/crop_controller_hook.dart index cea901bd2e..663bca3dbf 100644 --- a/mobile/lib/utils/hooks/crop_controller_hook.dart +++ b/mobile/lib/utils/hooks/crop_controller_hook.dart @@ -1,7 +1,6 @@ -import 'dart:ui'; // Import the dart:ui library for Rect - -import 'package:crop_image/crop_image.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:crop_image/crop_image.dart'; +import 'dart:ui'; // Import the dart:ui library for Rect /// A hook that provides a [CropController] instance. CropController useCropController() { diff --git a/mobile/lib/utils/image_converter.dart b/mobile/lib/utils/image_converter.dart index 1a3a00130a..6711e2bd56 100644 --- a/mobile/lib/utils/image_converter.dart +++ b/mobile/lib/utils/image_converter.dart @@ -12,18 +12,16 @@ import 'package:flutter/material.dart'; Future imageToUint8List(Image image) async { final Completer completer = Completer(); image.image - .resolve(ImageConfiguration.empty) + .resolve(const ImageConfiguration()) .addListener( ImageStreamListener((ImageInfo info, bool _) { - unawaited( - info.image.toByteData(format: ImageByteFormat.png).then((byteData) { - if (byteData != null) { - completer.complete(byteData.buffer.asUint8List()); - } else { - completer.completeError('Failed to convert image to bytes'); - } - }), - ); + info.image.toByteData(format: ImageByteFormat.png).then((byteData) { + if (byteData != null) { + completer.complete(byteData.buffer.asUint8List()); + } else { + completer.completeError('Failed to convert image to bytes'); + } + }); }, onError: (exception, stackTrace) => completer.completeError(exception)), ); return completer.future; diff --git a/mobile/lib/utils/image_url_builder.dart b/mobile/lib/utils/image_url_builder.dart index 4f584061d7..079f0e51fa 100644 --- a/mobile/lib/utils/image_url_builder.dart +++ b/mobile/lib/utils/image_url_builder.dart @@ -1,7 +1,47 @@ import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:openapi/api.dart'; +String getThumbnailUrl(final Asset asset, {AssetMediaSize type = AssetMediaSize.thumbnail}) { + return getThumbnailUrlForRemoteId(asset.remoteId!, type: type); +} + +String getThumbnailCacheKey(final Asset asset, {AssetMediaSize type = AssetMediaSize.thumbnail}) { + return getThumbnailCacheKeyForRemoteId(asset.remoteId!, asset.thumbhash!, type: type); +} + +String getThumbnailCacheKeyForRemoteId( + final String id, + final String thumbhash, { + AssetMediaSize type = AssetMediaSize.thumbnail, +}) { + if (type == AssetMediaSize.thumbnail) { + return 'thumbnail-image-$id-$thumbhash'; + } else { + return '${id}_${thumbhash}_previewStage'; + } +} + +String getAlbumThumbnailUrl(final Album album, {AssetMediaSize type = AssetMediaSize.thumbnail}) { + if (album.thumbnail.value?.remoteId == null) { + return ''; + } + return getThumbnailUrlForRemoteId(album.thumbnail.value!.remoteId!, type: type); +} + +String getAlbumThumbNailCacheKey(final Album album, {AssetMediaSize type = AssetMediaSize.thumbnail}) { + if (album.thumbnail.value?.remoteId == null) { + return ''; + } + return getThumbnailCacheKeyForRemoteId( + album.thumbnail.value!.remoteId!, + album.thumbnail.value!.thumbhash!, + type: type, + ); +} + String getOriginalUrlForRemoteId(final String id, {bool edited = true}) { return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/original?edited=$edited'; } @@ -12,7 +52,7 @@ String getThumbnailUrlForRemoteId( bool edited = true, String? thumbhash, }) { - final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=$type&edited=$edited'; + final url = '${Store.get(StoreKey.serverEndpoint)}/assets/$id/thumbnail?size=${type.value}&edited=$edited'; return thumbhash != null ? '$url&c=${Uri.encodeComponent(thumbhash)}' : url; } @@ -20,7 +60,6 @@ String getPlaybackUrlForRemoteId(final String id) { return '${Store.get(StoreKey.serverEndpoint)}/assets/$id/video/playback?'; } -String getFaceThumbnailUrl(final String personId, {DateTime? updatedAt}) { - final url = '${Store.get(StoreKey.serverEndpoint)}/people/$personId/thumbnail'; - return updatedAt != null ? '$url?c=${updatedAt.millisecondsSinceEpoch}' : url; +String getFaceThumbnailUrl(final String personId) { + return '${Store.get(StoreKey.serverEndpoint)}/people/$personId/thumbnail'; } diff --git a/mobile/lib/utils/immich_loading_overlay.dart b/mobile/lib/utils/immich_loading_overlay.dart new file mode 100644 index 0000000000..be49c3bae9 --- /dev/null +++ b/mobile/lib/utils/immich_loading_overlay.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart'; + +final _loadingEntry = OverlayEntry( + builder: (context) => SizedBox.square( + dimension: double.infinity, + child: DecoratedBox( + decoration: BoxDecoration(color: context.colorScheme.surface.withAlpha(200)), + child: const Center( + child: DelayedLoadingIndicator(delay: Duration(seconds: 1), fadeInDuration: Duration(milliseconds: 400)), + ), + ), + ), +); + +ValueNotifier useProcessingOverlay() { + return use(const _LoadingOverlay()); +} + +class _LoadingOverlay extends Hook> { + const _LoadingOverlay(); + + @override + _LoadingOverlayState createState() => _LoadingOverlayState(); +} + +class _LoadingOverlayState extends HookState, _LoadingOverlay> { + late final _isLoading = ValueNotifier(false)..addListener(_listener); + OverlayEntry? _loadingOverlay; + + void _listener() { + setState(() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_isLoading.value) { + _loadingOverlay?.remove(); + _loadingOverlay = _loadingEntry; + Overlay.of(context).insert(_loadingEntry); + } else { + _loadingOverlay?.remove(); + _loadingOverlay = null; + } + }); + }); + } + + @override + ValueNotifier build(BuildContext context) { + return _isLoading; + } + + @override + void dispose() { + _isLoading.dispose(); + super.dispose(); + } + + @override + Object? get debugValue => _isLoading.value; + + @override + String get debugLabel => 'useProcessingOverlay<>'; +} diff --git a/mobile/lib/utils/isolate.dart b/mobile/lib/utils/isolate.dart index ab3b19b78f..c8224b9c55 100644 --- a/mobile/lib/utils/isolate.dart +++ b/mobile/lib/utils/isolate.dart @@ -5,12 +5,14 @@ import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/infrastructure/cancel.provider.dart'; import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; import 'package:immich_mobile/utils/bootstrap.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/wm_executor.dart'; import 'package:logging/logging.dart'; -import 'package:worker_manager/worker_manager.dart' show Cancelable; +import 'package:worker_manager/worker_manager.dart'; class InvalidIsolateUsageException implements Exception { const InvalidIsolateUsageException(); @@ -29,27 +31,63 @@ Cancelable runInIsolateGentle({ throw const InvalidIsolateUsageException(); } - return workerManagerPatch.executeGentle((onCancel) async { - BackgroundIsolateBinaryMessenger.ensureInitialized(token); - DartPluginRegistrant.ensureInitialized(); + return workerManagerPatch.executeGentle((cancelledChecker) async { + T? result; + await runZonedGuarded( + () async { + BackgroundIsolateBinaryMessenger.ensureInitialized(token); + DartPluginRegistrant.ensureInitialized(); - final log = Logger("IsolateLogger"); - final (drift, logDb) = await Bootstrap.initDomain(shouldBufferLogs: false, listenStoreUpdates: false); - final ref = ProviderContainer( - overrides: [cancellationProvider.overrideWithValue(onCancel), driftProvider.overrideWith(driftOverride(drift))], + final (isar, drift, logDb) = await Bootstrap.initDB(); + await Bootstrap.initDomain(isar, drift, logDb, shouldBufferLogs: false, listenStoreUpdates: false); + final ref = ProviderContainer( + overrides: [ + // TODO: Remove once isar is removed + dbProvider.overrideWithValue(isar), + isarProvider.overrideWithValue(isar), + cancellationProvider.overrideWithValue(cancelledChecker), + driftProvider.overrideWith(driftOverride(drift)), + ], + ); + + Logger log = Logger("IsolateLogger"); + + try { + result = await computation(ref); + } on CanceledError { + log.warning("Computation cancelled ${debugLabel == null ? '' : ' for $debugLabel'}"); + } catch (error, stack) { + log.severe("Error in runInIsolateGentle ${debugLabel == null ? '' : ' for $debugLabel'}", error, stack); + } finally { + try { + ref.dispose(); + + await Store.dispose(); + await LogService.I.dispose(); + await logDb.close(); + await drift.close(); + + // Close Isar safely + try { + if (isar.isOpen) { + await isar.close(); + } + } catch (e) { + dPrint(() => "Error closing Isar: $e"); + } + } catch (error, stack) { + dPrint(() => "Error closing resources in isolate: $error, $stack"); + } finally { + ref.dispose(); + // Delay to ensure all resources are released + await Future.delayed(const Duration(seconds: 2)); + } + } + }, + (error, stack) { + dPrint(() => "Error in isolate $debugLabel zone: $error, $stack"); + }, ); - - try { - return await computation(ref); - } catch (error, stack) { - log.severe("Error in runInIsolateGentle${debugLabel == null ? '' : ' for $debugLabel'}", error, stack); - return null; - } finally { - ref.dispose(); - await Store.dispose(); - await LogService.I.dispose(); - await logDb.close(); - await drift.close(); - } + return result; }); } diff --git a/mobile/lib/utils/isolate_worker.dart b/mobile/lib/utils/isolate_worker.dart deleted file mode 100644 index 60048c2c81..0000000000 --- a/mobile/lib/utils/isolate_worker.dart +++ /dev/null @@ -1,163 +0,0 @@ -// Forked from worker_manager's `WorkerImpl` (src/worker/worker_io.dart): a -// `CancelRequest` completes the computation's [Completer] (so it can await -// cancellation and unwind) instead of flipping a polled flag, and [shutdown] -// lets the isolate drain and exit on its own rather than force-killing it. Only -// the gentle-with-cancellation path immich uses is kept. -// -// ignore_for_file: implementation_imports - -import 'dart:async'; -import 'dart:isolate'; - -import 'package:worker_manager/src/scheduling/task.dart'; -import 'package:worker_manager/src/worker/cancel_request.dart'; -import 'package:worker_manager/src/worker/result.dart'; - -/// A worker computation that receives a [Completer] which completes on -/// cancellation: await its future to react promptly, or read `isCompleted`. -typedef GentleExecution = FutureOr Function(Completer onCancel); - -class _Shutdown { - const _Shutdown(); -} - -class IsolateWorker { - IsolateWorker(); - - Isolate? _isolate; - RawReceivePort? _receivePort; - SendPort? _sendPort; - Completer? _sendPortReceived; - Completer? _result; - - String? taskId; - - bool get initialized => _sendPortReceived?.isCompleted ?? false; - - bool get initializing { - final sendPortReceived = _sendPortReceived; - return sendPortReceived != null && !sendPortReceived.isCompleted; - } - - Future initialize() async { - final sendPortReceived = _sendPortReceived = Completer(); - final receivePort = _receivePort = RawReceivePort(); - receivePort.handler = (Object message) { - if (message is SendPort) { - _sendPort = message; - sendPortReceived.complete(); - } else if (message is ResultSuccess) { - _result?.complete(message.value); - _afterTask(); - } else if (message is ResultError) { - _result?.completeError(message.error, message.stackTrace); - _afterTask(); - } - }; - _isolate = await Isolate.spawn(_isolateEntry, receivePort.sendPort, errorsAreFatal: false); - await sendPortReceived.future; - } - - Future work(Task task) async { - taskId = task.id; - final result = _result = Completer(); - _sendPort!.send(task.execution); - return await (result.future as Future); - } - - /// Cancels the current task without retiring the worker. - void cancelGentle() => _sendPort?.send(CancelRequest()); - - /// Cancels any in-flight task and awaits the isolate exiting on its own — no - /// force-kill, so `finally` blocks and native cleanup always run. - /// - /// Detaches the slot up front so a concurrent [initialize] can revive it - /// without colliding (revival installs fresh ports while this drains the ones - /// it captured locally). A revived worker is always idle, so the still-live - /// receive-port handler can't misroute a result. - Future shutdown() async { - final sendPortReceived = _sendPortReceived; - if (sendPortReceived != null && !sendPortReceived.isCompleted) { - await sendPortReceived.future; - } - - final isolate = _isolate; - final receivePort = _receivePort; - final sendPort = _sendPort; - if (isolate == null || receivePort == null || sendPort == null) { - return; - } - _isolate = null; - _sendPort = null; - _sendPortReceived = null; - // Not _result: an in-flight task still delivers it before exiting; nulling - // here would drop that and hang work()'s caller. - - final exited = Completer(); - final exitPort = RawReceivePort(); - exitPort.handler = (_) { - if (!exited.isCompleted) { - exited.complete(); - } - exitPort.close(); - }; - isolate.addOnExitListener(exitPort.sendPort); - sendPort.send(const _Shutdown()); - await exited.future; - receivePort.close(); - } - - void _afterTask() { - taskId = null; - _result = null; - } - - static void _isolateEntry(SendPort sendPort) { - final receivePort = RawReceivePort(); - sendPort.send(receivePort.sendPort); - // One task at a time, so a single completer suffices; null between tasks. - Completer? onCancel; - void cancel() { - if (onCancel?.isCompleted == false) { - onCancel!.complete(); - } - } - - var shuttingDown = false; - var running = false; - receivePort.handler = (message) async { - if (message is _Shutdown) { - shuttingDown = true; - cancel(); - if (!running) { - Isolate.exit(); - } - return; - } - if (message is CancelRequest) { - cancel(); - return; - } - final execution = message as GentleExecution; - onCancel = Completer(); - running = true; - Result result; - try { - result = ResultSuccess(await execution(onCancel!)); - } catch (error, stackTrace) { - result = ResultError(error, stackTrace); - } finally { - onCancel = null; - running = false; - } - if (shuttingDown) { - // An isolate that has used platform channels can't exit on its own (Flutter's BackgroundIsolateBinaryMessenger - // opens an undisposable port), so closing our ports isn't enough. Isolate.exit delivers the result as its final - // message and terminates. It's abrupt (skips pending finally/microtasks) but safe here: the computation and its - // `finally` are already done and there's no await before this, so nothing pending is skipped. - Isolate.exit(sendPort, result); - } - sendPort.send(result); - }; - } -} diff --git a/mobile/lib/utils/map_utils.dart b/mobile/lib/utils/map_utils.dart index 2a89b63422..6213b214a9 100644 --- a/mobile/lib/utils/map_utils.dart +++ b/mobile/lib/utils/map_utils.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/map/map_marker.model.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:logging/logging.dart'; @@ -68,11 +68,7 @@ class MapUtils { bool silent = false, }) async { try { - final serviceEnabled = await Geolocator.isLocationServiceEnabled(); - if (!context.mounted) { - return (null, LocationPermission.unableToDetermine); - } - + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled && !silent) { unawaited(showDialog(context: context, builder: (context) => _LocationServiceDisabledDialog())); return (null, LocationPermission.deniedForever); @@ -82,10 +78,6 @@ class MapUtils { bool shouldRequestPermission = false; if (permission == LocationPermission.denied && !silent) { - if (!context.mounted) { - return (null, LocationPermission.unableToDetermine); - } - shouldRequestPermission = await showDialog( context: context, builder: (context) => _LocationPermissionDisabledDialog(), @@ -103,7 +95,7 @@ class MapUtils { return (null, LocationPermission.deniedForever); } - final Position currentUserLocation = await Geolocator.getCurrentPosition( + Position currentUserLocation = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 0, @@ -121,10 +113,10 @@ class MapUtils { class _LocationServiceDisabledDialog extends ConfirmDialog { _LocationServiceDisabledDialog() : super( - title: StaticTranslations.instance.map_location_service_disabled_title, - content: StaticTranslations.instance.map_location_service_disabled_content, - cancel: StaticTranslations.instance.cancel, - ok: StaticTranslations.instance.yes, + title: 'map_location_service_disabled_title'.tr(), + content: 'map_location_service_disabled_content'.tr(), + cancel: 'cancel'.tr(), + ok: 'yes'.tr(), onOk: () async { await Geolocator.openLocationSettings(); }, @@ -134,10 +126,10 @@ class _LocationServiceDisabledDialog extends ConfirmDialog { class _LocationPermissionDisabledDialog extends ConfirmDialog { _LocationPermissionDisabledDialog() : super( - title: StaticTranslations.instance.map_no_location_permission_title, - content: StaticTranslations.instance.map_no_location_permission_content, - cancel: StaticTranslations.instance.cancel, - ok: StaticTranslations.instance.yes, + title: 'map_no_location_permission_title'.tr(), + content: 'map_no_location_permission_content'.tr(), + cancel: 'cancel'.tr(), + ok: 'yes'.tr(), onOk: () {}, ); } diff --git a/mobile/lib/utils/matrix.utils.dart b/mobile/lib/utils/matrix.utils.dart deleted file mode 100644 index 8363a8b93d..0000000000 --- a/mobile/lib/utils/matrix.utils.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:math'; - -class AffineMatrix { - final double a; - final double b; - final double c; - final double d; - final double e; - final double f; - - const AffineMatrix(this.a, this.b, this.c, this.d, this.e, this.f); - - @override - String toString() { - return 'AffineMatrix(a: $a, b: $b, c: $c, d: $d, e: $e, f: $f)'; - } - - factory AffineMatrix.identity() { - return const AffineMatrix(1, 0, 0, 1, 0, 0); - } - - AffineMatrix multiply(AffineMatrix other) { - return AffineMatrix( - a * other.a + c * other.b, - b * other.a + d * other.b, - a * other.c + c * other.d, - b * other.c + d * other.d, - a * other.e + c * other.f + e, - b * other.e + d * other.f + f, - ); - } - - factory AffineMatrix.compose([List transformations = const []]) { - return transformations.fold(AffineMatrix.identity(), (acc, matrix) => acc.multiply(matrix)); - } - - factory AffineMatrix.rotate(double angle) { - final cosAngle = cos(angle); - final sinAngle = sin(angle); - return AffineMatrix(cosAngle, -sinAngle, sinAngle, cosAngle, 0, 0); - } - - factory AffineMatrix.flipY() { - return const AffineMatrix(-1, 0, 0, 1, 0, 0); - } - - factory AffineMatrix.flipX() { - return const AffineMatrix(1, 0, 0, -1, 0, 0); - } -} diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index 56fccf4610..76916cee1e 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -1,320 +1,485 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; -import 'package:flutter/material.dart'; -import 'package:immich_mobile/constants/colors.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/feature_message.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/android_device_asset.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart' as isar_backup_album; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; import 'package:immich_mobile/infrastructure/repositories/network.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; -import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; -import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/platform/network_api.g.dart'; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; +import 'package:immich_mobile/services/api.service.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/datetime_helpers.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; +import 'package:immich_mobile/utils/diff.dart'; +import 'package:isar/isar.dart'; +// ignore: import_rule_photo_manager +import 'package:photo_manager/photo_manager.dart'; -const int targetVersion = 26; +const int targetVersion = 25; -Future migrateDatabaseIfNeeded(Drift drift) async { - final int? storedVersion = Store.tryGet(StoreKey.version); - final version = storedVersion ?? targetVersion; +Future migrateDatabaseIfNeeded(Isar db, Drift drift) async { + final hasVersion = Store.tryGet(StoreKey.version) != null; + final int version = Store.get(StoreKey.version, targetVersion); + if (version < 9) { + await Store.put(StoreKey.version, targetVersion); + final value = await db.storeValues.get(StoreKey.currentUser.id); + if (value != null) { + final id = value.intValue; + if (id != null) { + await db.writeTxn(() async { + final user = await db.users.get(id); + await db.storeValues.put(StoreValue(StoreKey.currentUser.id, strValue: user?.id)); + }); + } + } + } + + if (version < 10) { + await Store.put(StoreKey.version, targetVersion); + await _migrateDeviceAsset(db); + } + + if (version < 13) { + await Store.put(StoreKey.photoManagerCustomFilter, true); + } + + // This means that the SQLite DB is just created and has no version + if (version < 14 || !hasVersion) { + await migrateStoreToSqlite(db, drift); + await Store.populateCache(); + } + + final syncStreamRepository = SyncStreamRepository(drift); + await handleBetaMigration(version, await _isNewInstallation(db, drift), syncStreamRepository); + + if (version < 17 && Store.isBetaTimelineEnabled) { + final delay = Store.get(StoreKey.backupTriggerDelay, AppSettingsEnum.backupTriggerDelay.defaultValue); + if (delay >= 1000) { + await Store.put(StoreKey.backupTriggerDelay, (delay / 1000).toInt()); + } + } + + if (version < 18 && Store.isBetaTimelineEnabled) { + await syncStreamRepository.reset(); + await Store.put(StoreKey.shouldResetSync, true); + } + + if (version < 19 && Store.isBetaTimelineEnabled) { + if (!await _populateLocalAssetTime(drift)) { + return; + } + } + + if (version < 20 && Store.isBetaTimelineEnabled) { + await _syncLocalAlbumIsIosSharedAlbum(drift); + } + + if (version < 21) { + final certData = SSLClientCertStoreVal.load(); + if (certData != null) { + await networkApi.addCertificate(ClientCertData(data: certData.data, password: certData.password ?? "")); + } + } + + if (version < 23 && Store.isBetaTimelineEnabled) { + await _populateLocalAssetPlaybackStyle(drift); + } + + if (version < 24 && Store.isBetaTimelineEnabled) { + await _applyLocalAssetOrientation(drift); + } if (version < 25) { - await _migrateTo25(); - } - - if (version < 26) { - await _migrateTo26(drift); - } - - if (storedVersion == null) { - await FeatureMessageService(SettingsRepository.instance).markSeen(); - } - - await Store.put(StoreKey.version, targetVersion); - return; -} - -Future _migrateTo25() async { - final accessToken = Store.tryGet(StoreKey.accessToken); - if (accessToken == null || accessToken.isEmpty) { - return; - } - - final urls = []; - final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint); - if (serverEndpoint != null && serverEndpoint.isNotEmpty) { - urls.add(serverEndpoint); - } - final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint); - if (localEndpoint != null && localEndpoint.isNotEmpty) { - urls.add(localEndpoint); - } - final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList); - if (externalJson != null) { - final List list = jsonDecode(externalJson); - for (final entry in list) { - final url = AuxilaryEndpoint.fromJson(entry).url; - if (url.isNotEmpty) { - urls.add(url); + final accessToken = Store.tryGet(StoreKey.accessToken); + if (accessToken != null && accessToken.isNotEmpty) { + final serverUrls = ApiService.getServerUrls(); + if (serverUrls.isNotEmpty) { + await NetworkRepository.setHeaders(ApiService.getRequestHeaders(), serverUrls, token: accessToken); } } } - if (urls.isEmpty) { + + if (version < 22 && !Store.isBetaTimelineEnabled) { + await Store.put(StoreKey.needBetaMigration, true); + } + + if (targetVersion >= 12) { + await Store.put(StoreKey.version, targetVersion); return; } - final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, ""); - final headers = customHeadersStr.isEmpty - ? const {} - : (jsonDecode(customHeadersStr) as Map).cast(); + final shouldTruncate = version < 8 || version < targetVersion; - await NetworkRepository.setHeaders(headers, urls, token: accessToken); + if (shouldTruncate) { + await _migrateTo(db, targetVersion); + } } -Future _migrateTo26(Drift drift) async { - final migrator = _StoreMigrator(drift); - await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, SettingsKey.logLevel, LogLevel.values); - // Theme - await migrator.migrateEnumName(StoreKey.legacyThemeMode, SettingsKey.themeMode, ThemeMode.values); - await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, SettingsKey.themePrimaryColor, ImmichColorPreset.values); - await migrator.migrateBool(StoreKey.legacyDynamicTheme, SettingsKey.themeDynamic); - await migrator.migrateBool(StoreKey.legacyColorfulInterface, SettingsKey.themeColorfulInterface); - // Cleanup - final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id); - if (cleanupKeepAlbumIds != null) { - final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList(); - migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, SettingsKey.cleanupKeepAlbumIds, ids); - } - await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, SettingsKey.cleanupKeepFavorites); - await migrator.migrateEnumIndex( - StoreKey.legacyCleanupKeepMediaType, - SettingsKey.cleanupKeepMediaType, - AssetKeepType.values, - ); - await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, SettingsKey.cleanupCutoffDaysAgo); - await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, SettingsKey.cleanupDefaultsInitialized); - // Map - await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, SettingsKey.mapShowFavoriteOnly); - await migrator.migrateInt(StoreKey.legacyMapRelativeDate, SettingsKey.mapRelativeDate); - await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, SettingsKey.mapIncludeArchived); - await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, SettingsKey.mapThemeMode, ThemeMode.values); - await migrator.migrateBool(StoreKey.legacyMapwithPartners, SettingsKey.mapWithPartners); - // Timeline - await migrator.migrateInt(StoreKey.legacyTilesPerRow, SettingsKey.timelineTilesPerRow); - await migrator.migrateEnumIndex( - StoreKey.legacyGroupAssetsBy, - SettingsKey.timelineGroupAssetsBy, - GroupAssetsBy.values, - ); - await migrator.migrateBool(StoreKey.legacyStorageIndicator, SettingsKey.timelineStorageIndicator); - // Image - await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, SettingsKey.imagePreferRemote); - await migrator.migrateBool(StoreKey.legacyLoadOriginal, SettingsKey.imageLoadOriginal); - // Viewer - await migrator.migrateBool(StoreKey.legacyLoopVideo, SettingsKey.viewerLoopVideo); - await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, SettingsKey.viewerLoadOriginalVideo); - await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, SettingsKey.viewerAutoPlayVideo); - await migrator.migrateBool(StoreKey.legacyTapToNavigate, SettingsKey.viewerTapToNavigate); - // Network - await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, SettingsKey.networkAutoEndpointSwitching); - final preferredWifiName = await migrator.readLegacyStoreString(StoreKey.legacyPreferredWifiName.id); - migrator.stage(StoreKey.legacyPreferredWifiName, SettingsKey.networkPreferredWifiName, preferredWifiName); - final localEndpoint = await migrator.readLegacyStoreString(StoreKey.legacyLocalEndpoint.id); - migrator.stage(StoreKey.legacyLocalEndpoint, SettingsKey.networkLocalEndpoint, localEndpoint); - await _migrateExternalEndpointList(migrator); - await _migrateCustomHeaders(migrator); - // Album - await _migrateAlbumSortMode(migrator); - await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, SettingsKey.albumIsReverse); - await migrator.migrateBool(StoreKey.legacyAlbumGridView, SettingsKey.albumIsGrid); - // Backup - await migrator.migrateBool(StoreKey.legacyEnableBackup, SettingsKey.backupEnabled); - await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, SettingsKey.backupUseCellularForVideos); - await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, SettingsKey.backupUseCellularForPhotos); - await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, SettingsKey.backupRequireCharging); - await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, SettingsKey.backupTriggerDelay); - await migrator.migrateBool(StoreKey.legacySyncAlbums, SettingsKey.backupSyncAlbums); - await migrator.complete(); -} - -Future _migrateAlbumSortMode(_StoreMigrator migrator) async { - final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id); - final mode = AlbumSortMode.values.firstWhereOrNull((e) => raw != null && e.storeIndex == raw); - if (mode == null) { - return; - } - - migrator.stage(StoreKey.legacySelectedAlbumSortOrder, SettingsKey.albumSortMode, mode); -} - -Future _migrateExternalEndpointList(_StoreMigrator migrator) async { - final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id); - if (raw == null) { - return; - } - - final urls = []; - try { - final decoded = jsonDecode(raw); - if (decoded is List) { - for (final entry in decoded) { - final url = AuxilaryEndpoint.fromJson(entry).url; - if (url.isNotEmpty) { - urls.add(url); - } - } +Future handleBetaMigration(int version, bool isNewInstallation, SyncStreamRepository syncStreamRepository) async { + // Handle migration only for this version + // TODO: remove when old timeline is removed + final isBeta = Store.tryGet(StoreKey.betaTimeline); + final needBetaMigration = Store.tryGet(StoreKey.needBetaMigration); + if (version <= 15 && needBetaMigration == null) { + // For new installations, no migration needed + // For existing installations, only migrate if beta timeline is not enabled (null or false) + if (isNewInstallation || isBeta == true) { + await Store.put(StoreKey.needBetaMigration, false); + await Store.put(StoreKey.betaTimeline, true); + } else { + await Store.put(StoreKey.needBetaMigration, true); } - } on FormatException { - // ignore invalid entries } - migrator.stage(StoreKey.legacyExternalEndpointList, SettingsKey.networkExternalEndpointList, urls); + if (version > 15) { + if (isBeta == null || isBeta) { + await Store.put(StoreKey.needBetaMigration, false); + await Store.put(StoreKey.betaTimeline, true); + } else { + await Store.put(StoreKey.needBetaMigration, false); + } + } + + if (version < 16) { + await syncStreamRepository.reset(); + await Store.put(StoreKey.shouldResetSync, true); + } } -Future _migrateCustomHeaders(_StoreMigrator migrator) async { - final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id); - if (raw == null) { +Future _isNewInstallation(Isar db, Drift drift) async { + try { + final isarUserCount = await db.users.count(); + if (isarUserCount > 0) { + return false; + } + + final isarAssetCount = await db.assets.count(); + if (isarAssetCount > 0) { + return false; + } + + final driftStoreCount = await drift.storeEntity.select().get().then((list) => list.length); + if (driftStoreCount > 0) { + return false; + } + + final driftAssetCount = await drift.localAssetEntity.select().get().then((list) => list.length); + if (driftAssetCount > 0) { + return false; + } + + return true; + } catch (error) { + dPrint(() => "[MIGRATION] Error checking if new installation: $error"); + return false; + } +} + +Future _migrateTo(Isar db, int version) async { + await Store.delete(StoreKey.assetETag); + await db.writeTxn(() async { + await db.assets.clear(); + await db.exifInfos.clear(); + await db.albums.clear(); + await db.eTags.clear(); + await db.users.clear(); + }); + await Store.put(StoreKey.version, version); +} + +Future _migrateDeviceAsset(Isar db) async { + final ids = Platform.isAndroid + ? (await db.androidDeviceAssets.where().findAll()) + .map((a) => _DeviceAsset(assetId: a.id.toString(), hash: a.hash)) + .toList() + : (await db.iOSDeviceAssets.where().findAll()).map((i) => _DeviceAsset(assetId: i.id, hash: i.hash)).toList(); + + final PermissionState ps = await PhotoManager.requestPermissionExtend(); + if (!ps.hasAccess) { + dPrint(() => "[MIGRATION] Photo library permission not granted. Skipping device asset migration."); return; } - final headers = {}; + List<_DeviceAsset> localAssets = []; + final List paths = await PhotoManager.getAssetPathList(onlyAll: true); + + if (paths.isEmpty) { + localAssets = (await db.assets.where().anyOf(ids, (query, id) => query.localIdEqualTo(id.assetId)).findAll()) + .map((a) => _DeviceAsset(assetId: a.localId!, dateTime: a.fileModifiedAt)) + .toList(); + } else { + final AssetPathEntity albumWithAll = paths.first; + final int assetCount = await albumWithAll.assetCountAsync; + + final List allDeviceAssets = await albumWithAll.getAssetListRange(start: 0, end: assetCount); + + localAssets = allDeviceAssets.map((a) => _DeviceAsset(assetId: a.id, dateTime: a.modifiedDateTime)).toList(); + } + + dPrint(() => "[MIGRATION] Device Asset Ids length - ${ids.length}"); + dPrint(() => "[MIGRATION] Local Asset Ids length - ${localAssets.length}"); + ids.sort((a, b) => a.assetId.compareTo(b.assetId)); + localAssets.sort((a, b) => a.assetId.compareTo(b.assetId)); + final List toAdd = []; + await diffSortedLists( + ids, + localAssets, + compare: (a, b) => a.assetId.compareTo(b.assetId), + both: (deviceAsset, asset) { + toAdd.add( + DeviceAssetEntity(assetId: deviceAsset.assetId, hash: deviceAsset.hash!, modifiedTime: asset.dateTime!), + ); + return false; + }, + onlyFirst: (deviceAsset) { + dPrint(() => '[MIGRATION] Local asset not found in DeviceAsset: ${deviceAsset.assetId}'); + }, + onlySecond: (asset) { + dPrint(() => '[MIGRATION] Local asset not found in DeviceAsset: ${asset.assetId}'); + }, + ); + + dPrint(() => "[MIGRATION] Total number of device assets migrated - ${toAdd.length}"); + + await db.writeTxn(() async { + await db.deviceAssetEntitys.putAll(toAdd); + }); +} + +Future _populateLocalAssetTime(Drift db) async { try { - final decoded = jsonDecode(raw); - if (decoded is Map) { - decoded.forEach((key, value) { - if (key is String && value is String) { - headers[key] = value; + final nativeApi = NativeSyncApi(); + final albums = await nativeApi.getAlbums(); + for (final album in albums) { + final assets = await nativeApi.getAssetsForAlbum(album.id); + await db.batch((batch) async { + for (final asset in assets) { + batch.update( + db.localAssetEntity, + LocalAssetEntityCompanion( + longitude: Value(asset.longitude), + latitude: Value(asset.latitude), + adjustmentTime: Value(tryFromSecondsSinceEpoch(asset.adjustmentTime, isUtc: true)), + updatedAt: Value(tryFromSecondsSinceEpoch(asset.updatedAt, isUtc: true) ?? DateTime.timestamp()), + ), + where: (t) => t.id.equals(asset.id), + ); } }); } - } on FormatException { - // ignore invalid entries - } - migrator.stage(StoreKey.legacyCustomHeaders, SettingsKey.networkCustomHeaders, headers); + return true; + } catch (error) { + dPrint(() => "[MIGRATION] Error while populating asset time: $error"); + return false; + } } -class _StoreMigrator { - final Drift _db; - final Map _cache = {}; - final List _migratedStoreIds = []; - - _StoreMigrator(this._db); - - Future migrateEnumIndex(StoreKey legacyKey, SettingsKey newKey, List values) async { - final index = await readLegacyStoreInt(legacyKey.id); - if (index == null) { - return; - } - - final enumValue = values.elementAtOrNull(index); - if (enumValue == null) { - return; - } - - _cache[newKey] = enumValue; - _migratedStoreIds.add(legacyKey.id); - } - - Future migrateEnumName( - StoreKey legacyKey, - SettingsKey newKey, - List values, - ) async { - final name = await readLegacyStoreString(legacyKey.id); - if (name == null) { - return; - } - - final enumValue = values.firstWhereOrNull((e) => e.name == name); - if (enumValue == null) { - return; - } - - _cache[newKey] = enumValue; - _migratedStoreIds.add(legacyKey.id); - } - - Future migrateBool(StoreKey legacyKey, SettingsKey newKey) async { - final intValue = await readLegacyStoreInt(legacyKey.id); - if (intValue == null) { - return; - } - - final boolValue = intValue != 0; - _cache[newKey] = boolValue; - _migratedStoreIds.add(legacyKey.id); - } - - Future migrateInt(StoreKey legacyKey, SettingsKey newKey) async { - final intValue = await readLegacyStoreInt(legacyKey.id); - if (intValue == null) { - return; - } - - _cache[newKey] = intValue; - _migratedStoreIds.add(legacyKey.id); - } - - Future migrateString(StoreKey legacyKey, SettingsKey newKey) async { - final value = await readLegacyStoreString(legacyKey.id); - if (value == null) { - return; - } - - _cache[newKey] = value; - _migratedStoreIds.add(legacyKey.id); - } - - void stage(StoreKey legacyKey, SettingsKey newKey, U value) { - _cache[newKey] = value; - _migratedStoreIds.add(legacyKey.id); - } - - Future complete() async { - await _db.batch((batch) { - for (final entry in _cache.entries) { - if (entry.value == defaultConfig.read(entry.key)) { - continue; - } - - String? resolvedValue; - if (entry.value != null) { - resolvedValue = entry.key.encode(entry.value); - } - - batch.insert( - _db.settingsEntity, - SettingsEntityCompanion(key: Value(entry.key.name), value: Value(resolvedValue)), - mode: InsertMode.insertOrReplace, +Future _syncLocalAlbumIsIosSharedAlbum(Drift db) async { + try { + final nativeApi = NativeSyncApi(); + final albums = await nativeApi.getAlbums(); + await db.batch((batch) { + for (final album in albums) { + batch.update( + db.localAlbumEntity, + LocalAlbumEntityCompanion(isIosSharedAlbum: Value(album.isCloud)), + where: (t) => t.id.equals(album.id), ); } }); - await deleteLegacyStoreRows(_migratedStoreIds); - } - - Future readLegacyStoreString(int id) async { - final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull(); - return row?.stringValue; - } - - Future readLegacyStoreInt(int id) async { - final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull(); - return row?.intValue; - } - - Future deleteLegacyStoreRows(List ids) async { - if (ids.isEmpty) { - return; - } - await (_db.storeEntity.delete()..where((t) => t.id.isIn(ids))).go(); + dPrint(() => "[MIGRATION] Successfully updated isIosSharedAlbum for ${albums.length} albums"); + } catch (error) { + dPrint(() => "[MIGRATION] Error while syncing local album isIosSharedAlbum: $error"); } } + +Future migrateDeviceAssetToSqlite(Isar db, Drift drift) async { + try { + final isarDeviceAssets = await db.deviceAssetEntitys.where().findAll(); + await drift.batch((batch) { + for (final deviceAsset in isarDeviceAssets) { + batch.update( + drift.localAssetEntity, + LocalAssetEntityCompanion(checksum: Value(base64.encode(deviceAsset.hash))), + where: (t) => t.id.equals(deviceAsset.assetId), + ); + } + }); + } catch (error) { + dPrint(() => "[MIGRATION] Error while migrating device assets to SQLite: $error"); + } +} + +Future migrateBackupAlbumsToSqlite(Isar db, Drift drift) async { + try { + final isarBackupAlbums = await db.backupAlbums.where().findAll(); + // Recents is a virtual album on Android, and we don't have it with the new sync + // If recents is selected previously, select all albums during migration except the excluded ones + if (Platform.isAndroid) { + final recentAlbum = isarBackupAlbums.firstWhereOrNull((album) => album.id == 'isAll'); + if (recentAlbum != null) { + await drift.localAlbumEntity.update().write( + const LocalAlbumEntityCompanion(backupSelection: Value(BackupSelection.selected)), + ); + final excluded = isarBackupAlbums + .where((album) => album.selection == isar_backup_album.BackupSelection.exclude) + .map((album) => album.id) + .toList(); + await drift.batch((batch) async { + for (final id in excluded) { + batch.update( + drift.localAlbumEntity, + const LocalAlbumEntityCompanion(backupSelection: Value(BackupSelection.excluded)), + where: (t) => t.id.equals(id), + ); + } + }); + return; + } + } + + await drift.batch((batch) { + for (final album in isarBackupAlbums) { + batch.update( + drift.localAlbumEntity, + LocalAlbumEntityCompanion( + backupSelection: Value(switch (album.selection) { + isar_backup_album.BackupSelection.none => BackupSelection.none, + isar_backup_album.BackupSelection.select => BackupSelection.selected, + isar_backup_album.BackupSelection.exclude => BackupSelection.excluded, + }), + ), + where: (t) => t.id.equals(album.id), + ); + } + }); + } catch (error) { + dPrint(() => "[MIGRATION] Error while migrating backup albums to SQLite: $error"); + } +} + +Future migrateStoreToSqlite(Isar db, Drift drift) async { + try { + final isarStoreValues = await db.storeValues.where().findAll(); + await drift.batch((batch) { + for (final storeValue in isarStoreValues) { + final companion = StoreEntityCompanion( + id: Value(storeValue.id), + stringValue: Value(storeValue.strValue), + intValue: Value(storeValue.intValue), + ); + batch.insert(drift.storeEntity, companion, onConflict: DoUpdate((_) => companion)); + } + }); + } catch (error) { + dPrint(() => "[MIGRATION] Error while migrating store values to SQLite: $error"); + } +} + +Future migrateStoreToIsar(Isar db, Drift drift) async { + try { + final driftStoreValues = await drift.storeEntity + .select() + .map((entity) => StoreValue(entity.id, intValue: entity.intValue, strValue: entity.stringValue)) + .get(); + + await db.writeTxn(() async { + await db.storeValues.putAll(driftStoreValues); + }); + } catch (error) { + dPrint(() => "[MIGRATION] Error while migrating store values to Isar: $error"); + } +} + +Future _populateLocalAssetPlaybackStyle(Drift db) async { + try { + final nativeApi = NativeSyncApi(); + + final albums = await nativeApi.getAlbums(); + for (final album in albums) { + final assets = await nativeApi.getAssetsForAlbum(album.id); + await db.batch((batch) { + for (final asset in assets) { + batch.update( + db.localAssetEntity, + LocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))), + where: (t) => t.id.equals(asset.id), + ); + } + }); + } + + if (Platform.isAndroid) { + final trashedAssetMap = await nativeApi.getTrashedAssets(); + for (final entry in trashedAssetMap.cast>().entries) { + final assets = entry.value.cast(); + await db.batch((batch) { + for (final asset in assets) { + batch.update( + db.trashedLocalAssetEntity, + TrashedLocalAssetEntityCompanion(playbackStyle: Value(_toPlaybackStyle(asset.playbackStyle))), + where: (t) => t.id.equals(asset.id), + ); + } + }); + } + dPrint(() => "[MIGRATION] Successfully populated playbackStyle for local and trashed assets"); + } else { + dPrint(() => "[MIGRATION] Successfully populated playbackStyle for local assets"); + } + } catch (error) { + dPrint(() => "[MIGRATION] Error while populating playbackStyle: $error"); + } +} + +Future _applyLocalAssetOrientation(Drift db) { + final query = db.localAssetEntity.update() + ..where((filter) => (filter.orientation.equals(90) | (filter.orientation.equals(270)))); + return query.write( + LocalAssetEntityCompanion.custom( + width: db.localAssetEntity.height, + height: db.localAssetEntity.width, + orientation: const Variable(0), + ), + ); +} + +AssetPlaybackStyle _toPlaybackStyle(PlatformAssetPlaybackStyle style) => switch (style) { + PlatformAssetPlaybackStyle.unknown => AssetPlaybackStyle.unknown, + PlatformAssetPlaybackStyle.image => AssetPlaybackStyle.image, + PlatformAssetPlaybackStyle.video => AssetPlaybackStyle.video, + PlatformAssetPlaybackStyle.imageAnimated => AssetPlaybackStyle.imageAnimated, + PlatformAssetPlaybackStyle.livePhoto => AssetPlaybackStyle.livePhoto, + PlatformAssetPlaybackStyle.videoLooping => AssetPlaybackStyle.videoLooping, +}; + +class _DeviceAsset { + final String assetId; + final List? hash; + final DateTime? dateTime; + + const _DeviceAsset({required this.assetId, this.hash, this.dateTime}); +} diff --git a/mobile/lib/utils/openapi_patching.dart b/mobile/lib/utils/openapi_patching.dart index 7afd3c8276..090889ff32 100644 --- a/mobile/lib/utils/openapi_patching.dart +++ b/mobile/lib/utils/openapi_patching.dart @@ -1,65 +1,72 @@ -import 'package:flutter/foundation.dart'; import 'package:openapi/api.dart'; -abstract interface class _Dynamic { - Object? resolve(); -} - -class _CurrentTimestamp implements _Dynamic { - const _CurrentTimestamp(); - - @override - Object? resolve() => DateTime.now().toIso8601String(); -} - -const _now = _CurrentTimestamp(); - -@visibleForTesting -final Map> openApiPatches = { - 'UserPreferencesResponseDto': { - 'download.includeEmbeddedVideos': false, - 'folders': FoldersResponse(enabled: false, sidebarWeb: false).toJson(), - 'memories': MemoriesResponse(enabled: true, duration: 5).toJson(), - 'ratings': RatingsResponse(enabled: false).toJson(), - 'people': PeopleResponse(enabled: true, sidebarWeb: false).toJson(), - 'tags': TagsResponse(enabled: false, sidebarWeb: false).toJson(), - 'sharedLinks': SharedLinksResponse(enabled: true, sidebarWeb: false).toJson(), - 'cast': CastResponse(gCastEnabled: false).toJson(), - 'albums': {'defaultAssetOrder': 'desc'}, - 'recentlyAdded': RecentlyAddedResponse(sidebarWeb: false).toJson(), - }, - 'ServerConfigDto': { - 'mapLightStyleUrl': 'https://tiles.immich.cloud/v1/style/light.json', - 'mapDarkStyleUrl': 'https://tiles.immich.cloud/v1/style/dark.json', - 'minFaces': 3, - }, - 'UserResponseDto': {'profileChangedAt': _now}, - 'AssetResponseDto': {'visibility': 'timeline', 'createdAt': _now, 'isEdited': false}, - 'UserAdminResponseDto': {'profileChangedAt': _now, 'clusterGroupId': ''}, - 'LoginResponseDto': {'isOnboarded': false}, - 'SyncUserV1': {'profileChangedAt': _now, 'hasProfileImage': false}, - 'SyncAssetV1': {'isEdited': false}, - 'ServerFeaturesDto': {'ocr': false, 'realtimeTranscoding': false}, - 'MemoriesResponse': {'duration': 5}, - 'WorkflowResponseDto': {'logging': false}, -}; - -void upgradeDto(dynamic value, String targetType) { - if (value is! Map) { - return; +dynamic upgradeDto(dynamic value, String targetType) { + switch (targetType) { + case 'UserPreferencesResponseDto': + if (value is Map) { + addDefault(value, 'download.includeEmbeddedVideos', false); + addDefault(value, 'folders', FoldersResponse().toJson()); + addDefault(value, 'memories', MemoriesResponse().toJson()); + addDefault(value, 'ratings', RatingsResponse().toJson()); + addDefault(value, 'people', PeopleResponse().toJson()); + addDefault(value, 'tags', TagsResponse().toJson()); + addDefault(value, 'sharedLinks', SharedLinksResponse().toJson()); + addDefault(value, 'cast', CastResponse().toJson()); + addDefault(value, 'albums', {'defaultAssetOrder': 'desc'}); + } + break; + case 'ServerConfigDto': + if (value is Map) { + addDefault(value, 'mapLightStyleUrl', 'https://tiles.immich.cloud/v1/style/light.json'); + addDefault(value, 'mapDarkStyleUrl', 'https://tiles.immich.cloud/v1/style/dark.json'); + } + case 'UserResponseDto': + if (value is Map) { + addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); + } + break; + case 'AssetResponseDto': + if (value is Map) { + addDefault(value, 'visibility', 'timeline'); + addDefault(value, 'createdAt', DateTime.now().toIso8601String()); + addDefault(value, 'isEdited', false); + } + break; + case 'UserAdminResponseDto': + if (value is Map) { + addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); + } + break; + case 'LoginResponseDto': + if (value is Map) { + addDefault(value, 'isOnboarded', false); + } + break; + case 'SyncUserV1': + if (value is Map) { + addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); + addDefault(value, 'hasProfileImage', false); + } + case 'SyncAssetV1': + if (value is Map) { + addDefault(value, 'isEdited', false); + } + case 'ServerFeaturesDto': + if (value is Map) { + addDefault(value, 'ocr', false); + } + break; + case 'MemoriesResponse': + if (value is Map) { + addDefault(value, 'duration', 5); + } + break; } - final fields = openApiPatches[targetType]; - if (fields == null) { - return; - } - fields.forEach((key, defaultValue) { - addDefault(value, key, defaultValue is _Dynamic ? defaultValue.resolve() : defaultValue); - }); } -void addDefault(dynamic value, String keys, dynamic defaultValue) { +addDefault(dynamic value, String keys, dynamic defaultValue) { // Loop through the keys and assign the default value if the key is not present - final List keyList = keys.split('.'); + List keyList = keys.split('.'); dynamic current = value; for (int i = 0; i < keyList.length - 1; i++) { diff --git a/mobile/lib/utils/option.dart b/mobile/lib/utils/option.dart index e88ae59c1f..3470e8489e 100644 --- a/mobile/lib/utils/option.dart +++ b/mobile/lib/utils/option.dart @@ -1,6 +1,3 @@ -import 'package:drift/drift.dart'; -import 'package:openapi/api.dart' show Optional; - sealed class Option { const Option(); @@ -22,11 +19,6 @@ sealed class Option { None() => null, }; - Option map(U Function(T value) f) => switch (this) { - Some(:final value) => Some(f(value)), - None() => None(), - }; - U fold(U Function(T value) onSome, U Function() onNone) => switch (this) { Some(:final value) => onSome(value), None() => onNone(), @@ -61,20 +53,6 @@ final class None extends Option { int get hashCode => 0; } -extension NullableOptionExtension on Option? { - T? patch(T? current) => this == null ? current : this!.unwrapOrNull; -} - -extension OptionToOptional on Option { - Optional toOptional() => switch (this) { - None() => const Optional.absent(), - Some(:final value) => Optional.present(value), - }; -} - -extension OptionToDriftValue on Option { - Value toDriftValue() => switch (this) { - Some(:final value) => Value(value), - None() => const Value.absent(), - }; +extension ObjectOptionExtension on T? { + Option toOption() => Option.fromNullable(this); } diff --git a/mobile/lib/utils/people.utils.dart b/mobile/lib/utils/people.utils.dart index bd2b607e0b..ddd1867269 100644 --- a/mobile/lib/utils/people.utils.dart +++ b/mobile/lib/utils/people.utils.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/domain/models/person.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/people/person_edit_birthday_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart'; String formatAge(DateTime birthDate, DateTime referenceDate) { - final int ageInYears = _calculateAge(birthDate, referenceDate); - final int ageInMonths = _calculateAgeInMonths(birthDate, referenceDate); + int ageInYears = _calculateAge(birthDate, referenceDate); + int ageInMonths = _calculateAgeInMonths(birthDate, referenceDate); - final t = StaticTranslations.instance; if (ageInMonths <= 11) { - return t.person_age_months(months: ageInMonths); + return "person_age_months".t(args: {'months': ageInMonths.toString()}); } else if (ageInMonths > 12 && ageInMonths <= 23) { - return t.person_age_year_months(months: ageInMonths - 12); + return "person_age_year_months".t(args: {'months': (ageInMonths - 12).toString()}); } else { - return t.person_age_years(years: ageInYears); + return "person_age_years".t(args: {'years': ageInYears.toString()}); } } @@ -34,7 +33,7 @@ int _calculateAgeInMonths(DateTime birthDate, DateTime referenceDate) { (referenceDate.day < birthDate.day ? 1 : 0); } -Future showNameEditModal(BuildContext context, Person person) { +Future showNameEditModal(BuildContext context, DriftPerson person) { return showDialog( context: context, useRootNavigator: false, @@ -44,7 +43,7 @@ Future showNameEditModal(BuildContext context, Person person) { ); } -Future showBirthdayEditModal(BuildContext context, Person person) { +Future showBirthdayEditModal(BuildContext context, DriftPerson person) { return showDialog( context: context, useRootNavigator: false, diff --git a/mobile/lib/utils/provider_utils.dart b/mobile/lib/utils/provider_utils.dart index 9524433c05..6c2d6e0f11 100644 --- a/mobile/lib/utils/provider_utils.dart +++ b/mobile/lib/utils/provider_utils.dart @@ -2,17 +2,21 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/providers/infrastructure/search.provider.dart'; import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; import 'package:immich_mobile/repositories/activity_api.repository.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:immich_mobile/repositories/person_api.repository.dart'; +import 'package:immich_mobile/repositories/timeline.repository.dart'; void invalidateAllApiRepositoryProviders(WidgetRef ref) { ref.invalidate(userApiRepositoryProvider); ref.invalidate(activityApiRepositoryProvider); ref.invalidate(partnerApiRepositoryProvider); + ref.invalidate(albumApiRepositoryProvider); ref.invalidate(personApiRepositoryProvider); ref.invalidate(assetApiRepositoryProvider); + ref.invalidate(timelineRepositoryProvider); ref.invalidate(searchApiRepositoryProvider); // Drift diff --git a/mobile/lib/utils/selection_handlers.dart b/mobile/lib/utils/selection_handlers.dart new file mode 100644 index 0000000000..f0d333e262 --- /dev/null +++ b/mobile/lib/utils/selection_handlers.dart @@ -0,0 +1,143 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/services/share.service.dart'; +import 'package:immich_mobile/widgets/common/date_time_picker.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:immich_mobile/widgets/common/location_picker.dart'; +import 'package:immich_mobile/widgets/common/share_dialog.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; + +void handleShareAssets(WidgetRef ref, BuildContext context, Iterable selection) { + showDialog( + context: context, + builder: (BuildContext buildContext) { + ref.watch(shareServiceProvider).shareAssets(selection.toList(), context).then((bool status) { + if (!status) { + ImmichToast.show( + context: context, + msg: 'image_viewer_page_state_provider_share_error'.tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + buildContext.pop(); + }); + return const ShareDialog(); + }, + barrierDismissible: false, + useRootNavigator: false, + ); +} + +Future handleArchiveAssets( + WidgetRef ref, + BuildContext context, + List selection, { + bool? shouldArchive, + ToastGravity toastGravity = ToastGravity.BOTTOM, +}) async { + if (selection.isNotEmpty) { + shouldArchive ??= !selection.every((a) => a.isArchived); + await ref.read(assetProvider.notifier).toggleArchive(selection, shouldArchive); + final message = shouldArchive + ? 'moved_to_archive'.t(context: context, args: {'count': selection.length}) + : 'moved_to_library'.t(context: context, args: {'count': selection.length}); + if (context.mounted) { + ImmichToast.show(context: context, msg: message, gravity: toastGravity); + } + } +} + +Future handleFavoriteAssets( + WidgetRef ref, + BuildContext context, + List selection, { + bool? shouldFavorite, + ToastGravity toastGravity = ToastGravity.BOTTOM, +}) async { + if (selection.isNotEmpty) { + shouldFavorite ??= !selection.every((a) => a.isFavorite); + await ref.watch(assetProvider.notifier).toggleFavorite(selection, shouldFavorite); + + final assetOrAssets = selection.length > 1 ? 'assets' : 'asset'; + final toastMessage = shouldFavorite + ? 'Added ${selection.length} $assetOrAssets to favorites' + : 'Removed ${selection.length} $assetOrAssets from favorites'; + if (context.mounted) { + ImmichToast.show(context: context, msg: toastMessage, gravity: toastGravity); + } + } +} + +Future handleEditDateTime(WidgetRef ref, BuildContext context, List selection) async { + DateTime? initialDate; + String? timeZone; + Duration? offset; + if (selection.length == 1) { + final asset = selection.first; + final assetWithExif = await ref.watch(assetServiceProvider).loadExif(asset); + final (dt, oft) = assetWithExif.getTZAdjustedTimeAndOffset(); + initialDate = dt; + offset = oft; + timeZone = assetWithExif.exifInfo?.timeZone; + } + final dateTime = await showDateTimePicker( + context: context, + initialDateTime: initialDate, + initialTZ: timeZone, + initialTZOffset: offset, + ); + + if (dateTime == null) { + return; + } + + await ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime); +} + +Future handleEditLocation(WidgetRef ref, BuildContext context, List selection) async { + LatLng? initialLatLng; + if (selection.length == 1) { + final asset = selection.first; + final assetWithExif = await ref.watch(assetServiceProvider).loadExif(asset); + if (assetWithExif.exifInfo?.latitude != null && assetWithExif.exifInfo?.longitude != null) { + initialLatLng = LatLng(assetWithExif.exifInfo!.latitude!, assetWithExif.exifInfo!.longitude!); + } + } + + final location = await showLocationPicker(context: context, initialLatLng: initialLatLng); + + if (location == null) { + return; + } + + await ref.read(assetServiceProvider).changeLocation(selection.toList(), location); +} + +Future handleSetAssetsVisibility( + WidgetRef ref, + BuildContext context, + AssetVisibilityEnum visibility, + List selection, +) async { + if (selection.isNotEmpty) { + await ref.watch(assetProvider.notifier).setLockedView(selection, visibility); + + final assetOrAssets = selection.length > 1 ? 'assets' : 'asset'; + final toastMessage = visibility == AssetVisibilityEnum.locked + ? 'Added ${selection.length} $assetOrAssets to locked folder' + : 'Removed ${selection.length} $assetOrAssets from locked folder'; + if (context.mounted) { + ImmichToast.show(context: context, msg: toastMessage, gravity: ToastGravity.BOTTOM); + } + } +} diff --git a/mobile/lib/utils/semver.dart b/mobile/lib/utils/semver.dart index 8080af00b0..aebfd2fe4c 100644 --- a/mobile/lib/utils/semver.dart +++ b/mobile/lib/utils/semver.dart @@ -1,42 +1,36 @@ -enum SemVerType { major, minor, patch, prerelease } +enum SemVerType { major, minor, patch } class SemVer { final int major; final int minor; final int patch; - final int? prerelease; - const SemVer({required this.major, required this.minor, required this.patch, this.prerelease}); + const SemVer({required this.major, required this.minor, required this.patch}); @override String toString() { - return '$major.$minor.$patch${prerelease == null ? '' : '-rc.$prerelease'}'; + return '$major.$minor.$patch'; } - SemVer copyWith({int? major, int? minor, int? patch, int? prerelease}) { - return SemVer( - major: major ?? this.major, - minor: minor ?? this.minor, - patch: patch ?? this.patch, - prerelease: prerelease ?? this.prerelease, - ); + SemVer copyWith({int? major, int? minor, int? patch}) { + return SemVer(major: major ?? this.major, minor: minor ?? this.minor, patch: patch ?? this.patch); } - static final _pattern = RegExp(r'^v?(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?(?:[-+].*)?$', caseSensitive: false); - factory SemVer.fromString(String version) { - final match = _pattern.firstMatch(version); - if (match == null) { + if (version.toLowerCase().startsWith("v")) { + version = version.substring(1); + } + + final parts = version.split("-")[0].split('.'); + if (parts.length != 3) { throw FormatException('Invalid semantic version string: $version'); } - final prerelease = match.group(4); - return SemVer( - major: int.parse(match.group(1)!), - minor: int.parse(match.group(2)!), - patch: int.parse(match.group(3)!), - prerelease: prerelease == null ? null : int.parse(prerelease), - ); + try { + return SemVer(major: int.parse(parts[0]), minor: int.parse(parts[1]), patch: int.parse(parts[2])); + } catch (e) { + throw FormatException('Invalid semantic version string: $version'); + } } bool operator >(SemVer other) { @@ -46,10 +40,7 @@ class SemVer { if (minor != other.minor) { return minor > other.minor; } - if (patch != other.patch) { - return patch > other.patch; - } - return _comparePrerelease(other) > 0; + return patch > other.patch; } bool operator <(SemVer other) { @@ -59,23 +50,7 @@ class SemVer { if (minor != other.minor) { return minor < other.minor; } - if (patch != other.patch) { - return patch < other.patch; - } - return _comparePrerelease(other) < 0; - } - - int _comparePrerelease(SemVer other) { - if (prerelease == other.prerelease) { - return 0; - } - if (prerelease == null) { - return 1; - } - if (other.prerelease == null) { - return -1; - } - return prerelease!.compareTo(other.prerelease!); + return patch < other.patch; } bool operator >=(SemVer other) { @@ -88,15 +63,9 @@ class SemVer { @override bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } + if (identical(this, other)) return true; - return other is SemVer && - other.major == major && - other.minor == minor && - other.patch == patch && - other.prerelease == prerelease; + return other is SemVer && other.major == major && other.minor == minor && other.patch == patch; } SemVerType? differenceType(SemVer other) { @@ -109,13 +78,10 @@ class SemVer { if (patch != other.patch) { return SemVerType.patch; } - if (prerelease != other.prerelease) { - return SemVerType.prerelease; - } return null; } @override - int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode ^ prerelease.hashCode; + int get hashCode => major.hashCode ^ minor.hashCode ^ patch.hashCode; } diff --git a/mobile/lib/utils/string_helper.dart b/mobile/lib/utils/string_helper.dart new file mode 100644 index 0000000000..201d141531 --- /dev/null +++ b/mobile/lib/utils/string_helper.dart @@ -0,0 +1,7 @@ +extension StringExtension on String { + String capitalizeFirstLetter() { + return "${this[0].toUpperCase()}${substring(1).toLowerCase()}"; + } +} + +String s(num count) => (count == 1 ? '' : 's'); diff --git a/mobile/lib/utils/system_ui.utils.dart b/mobile/lib/utils/system_ui.utils.dart deleted file mode 100644 index 0d6d65082f..0000000000 --- a/mobile/lib/utils/system_ui.utils.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/services.dart'; - -/// Restore the system bars and return to edge-to-edge layout. -/// -/// On Android 15+/API 36 edge-to-edge is enforced, so calling -/// setEnabledSystemUIMode(edgeToEdge) does NOT re-show bars that an immersive -/// mode (immersive / immersiveSticky) previously hid. Explicitly request all -/// overlays first, then return to edge-to-edge layout. -Future restoreEdgeToEdge() async { - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); - await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); -} diff --git a/mobile/lib/utils/throttle.dart b/mobile/lib/utils/throttle.dart new file mode 100644 index 0000000000..8b41d92318 --- /dev/null +++ b/mobile/lib/utils/throttle.dart @@ -0,0 +1,51 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; + +/// Throttles function calls with the [interval] provided. +/// Also make sures to call the last Action after the elapsed interval +class Throttler { + final Duration interval; + DateTime? _lastActionTime; + + Throttler({required this.interval}); + + T? run(T Function() action) { + if (_lastActionTime == null || (DateTime.now().difference(_lastActionTime!) > interval)) { + final response = action(); + _lastActionTime = DateTime.now(); + return response; + } + + return null; + } + + void dispose() { + _lastActionTime = null; + } +} + +/// Creates a [Throttler] that will be disposed automatically. If no [interval] is provided, a +/// default interval of 300ms is used to throttle the function calls +Throttler useThrottler({Duration interval = const Duration(milliseconds: 300), List? keys}) => + use(_ThrottleHook(interval: interval, keys: keys)); + +class _ThrottleHook extends Hook { + const _ThrottleHook({required this.interval, super.keys}); + + final Duration interval; + + @override + HookState> createState() => _ThrottlerHookState(); +} + +class _ThrottlerHookState extends HookState { + late final throttler = Throttler(interval: hook.interval); + + @override + Throttler build(_) => throttler; + + @override + void dispose() => throttler.dispose(); + + @override + String get debugLabel => 'useThrottler'; +} diff --git a/mobile/lib/utils/thumbnail_utils.dart b/mobile/lib/utils/thumbnail_utils.dart new file mode 100644 index 0000000000..685dc2b1c2 --- /dev/null +++ b/mobile/lib/utils/thumbnail_utils.dart @@ -0,0 +1,49 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; + +String getAltText(ExifInfo? exifInfo, DateTime fileCreatedAt, AssetType type, List peopleNames) { + if (exifInfo?.description != null && exifInfo!.description!.isNotEmpty) { + return exifInfo.description!; + } + final (template, args) = getAltTextTemplate(exifInfo, fileCreatedAt, type, peopleNames); + return template.t(args: args); +} + +(String, Map) getAltTextTemplate( + ExifInfo? exifInfo, + DateTime fileCreatedAt, + AssetType type, + List peopleNames, +) { + final isVideo = type == AssetType.video; + final hasLocation = exifInfo?.city != null && exifInfo?.country != null; + final date = DateFormat.yMMMMd().format(fileCreatedAt); + final args = { + "isVideo": isVideo.toString(), + "date": date, + "city": exifInfo?.city ?? "", + "country": exifInfo?.country ?? "", + "person1": peopleNames.elementAtOrNull(0) ?? "", + "person2": peopleNames.elementAtOrNull(1) ?? "", + "person3": peopleNames.elementAtOrNull(2) ?? "", + "additionalCount": (peopleNames.length - 3).toString(), + }; + final template = hasLocation + ? (switch (peopleNames.length) { + 0 => "image_alt_text_date_place", + 1 => "image_alt_text_date_place_1_person", + 2 => "image_alt_text_date_place_2_people", + 3 => "image_alt_text_date_place_3_people", + _ => "image_alt_text_date_place_4_or_more_people", + }) + : (switch (peopleNames.length) { + 0 => "image_alt_text_date", + 1 => "image_alt_text_date_1_person", + 2 => "image_alt_text_date_2_people", + 3 => "image_alt_text_date_3_people", + _ => "image_alt_text_date_4_or_more_people", + }); + return (template, args); +} diff --git a/mobile/lib/utils/timezone.dart b/mobile/lib/utils/timezone.dart index 980d51de8e..d75122062f 100644 --- a/mobile/lib/utils/timezone.dart +++ b/mobile/lib/utils/timezone.dart @@ -21,12 +21,10 @@ import 'package:timezone/timezone.dart'; return (dt, dt.timeZoneOffset); } on LocationNotFoundException { // Handle UTC offset format (e.g., "UTC+08:00") - final RegExp re = RegExp(r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$', caseSensitive: false); + RegExp re = RegExp(r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$', caseSensitive: false); final m = re.firstMatch(timeZone); if (m != null) { - final hours = int.parse(m.group(1) ?? '0'); - final minutes = int.parse(m.group(2) ?? '0'); - final duration = Duration(hours: hours, minutes: hours.isNegative ? -minutes : minutes); + final duration = Duration(hours: int.parse(m.group(1) ?? '0'), minutes: int.parse(m.group(2) ?? '0')); dt = dt.add(duration); return (dt, duration); } diff --git a/mobile/lib/utils/url_helper.dart b/mobile/lib/utils/url_helper.dart index 3bcdca06f3..e3d5b8ed57 100644 --- a/mobile/lib/utils/url_helper.dart +++ b/mobile/lib/utils/url_helper.dart @@ -2,31 +2,12 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:punycode/punycode.dart'; -/// Normalizes a server URL, guaranteeing that it has a schema and no trailing slashes -String normalizeServerUrl(String url) { - final trimmedUrl = url.trim(); - +String sanitizeUrl(String url) { // Add schema if none is set - final urlWithSchema = trimmedUrl.contains('://') ? trimmedUrl : "https://$trimmedUrl"; + final urlWithSchema = url.trimLeft().startsWith(RegExp(r"https?://")) ? url : "https://$url"; // Remove trailing slash(es) - return urlWithSchema.replaceFirst(RegExp(r"/+$"), ""); -} - -/// Validates a user-entered server URL -bool _validateServerUrl(String url) { - final parsedUrl = Uri.tryParse(url); - return parsedUrl != null && parsedUrl.scheme.startsWith(RegExp(r'^https?$')) && parsedUrl.host.isNotEmpty; -} - -/// Normalizes and validates that a server URL is supported -bool normalizeAndValidateServerUrl(String? url) { - if (url == null || url.isEmpty) { - return true; - } - - final normalizedUrl = normalizeServerUrl(url); - return _validateServerUrl(normalizedUrl); + return urlWithSchema.trimRight().replaceFirst(RegExp(r"/+$"), ""); } String? getServerUrl() { @@ -43,17 +24,6 @@ String? getServerUrl() { ); } -String? buildSharedLinkUrl({required String? baseUrl, required String key, String? slug}) { - if (baseUrl == null || baseUrl.isEmpty) { - return null; - } - - final normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl : '$baseUrl/'; - final path = (slug != null && slug.isNotEmpty) ? 's/$slug' : 'share/$key'; - - return '$normalizedBaseUrl$path'; -} - /// Converts a Unicode URL to its ASCII-compatible encoding (Punycode). /// /// This is especially useful for internationalized domain names (IDNs), @@ -72,17 +42,13 @@ String? buildSharedLinkUrl({required String? baseUrl, required String key, Strin /// String punycodeEncodeUrl(String serverUrl) { final serverUri = Uri.tryParse(serverUrl); - if (serverUri == null || serverUri.host.isEmpty) { - return ''; - } + if (serverUri == null || serverUri.host.isEmpty) return ''; final encodedHost = Uri.decodeComponent(serverUri.host) .split('.') .map((segment) { // If segment is already ASCII, then return as it is. - if (segment.runes.every((c) => c < 0x80)) { - return segment; - } + if (segment.runes.every((c) => c < 0x80)) return segment; return 'xn--${punycodeEncode(segment)}'; }) .join('.'); @@ -109,9 +75,7 @@ String punycodeEncodeUrl(String serverUrl) { /// String? punycodeDecodeUrl(String? serverUrl) { final serverUri = serverUrl != null ? Uri.tryParse(serverUrl) : null; - if (serverUri == null || serverUri.host.isEmpty) { - return null; - } + if (serverUri == null || serverUri.host.isEmpty) return null; final decodedHost = serverUri.host .split('.') diff --git a/mobile/lib/utils/version_compatibility.dart b/mobile/lib/utils/version_compatibility.dart index 0fbc37a11d..fa8dfb0b9e 100644 --- a/mobile/lib/utils/version_compatibility.dart +++ b/mobile/lib/utils/version_compatibility.dart @@ -1,16 +1,11 @@ -import 'package:immich_mobile/utils/semver.dart'; - -String? getVersionCompatibilityMessage({required SemVer serverVersion, required SemVer appVersion}) { - // Add latest compat info up top - - // ensure mobile app major version is not behind server major version - if (appVersion.major < serverVersion.major) { - return 'Your mobile app version is not compatible with the server! Please update your mobile app to the latest version.'; +String? getVersionCompatibilityMessage(int appMajor, int appMinor, int serverMajor, int serverMinor) { + if (serverMajor != appMajor) { + return 'Your app major version is not compatible with the server!'; } - // ensure mobile app major version is not ahead of server major version by more than 1 major version - if (appVersion.major > serverVersion.major + 1) { - return 'Your server version is not compatible with the mobile app! Please update your server to the latest version.'; + // Add latest compat info up top + if (serverMinor < 106 && appMinor >= 106) { + return 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login'; } return null; diff --git a/mobile/lib/widgets/activities/activity_text_field.dart b/mobile/lib/widgets/activities/activity_text_field.dart new file mode 100644 index 0000000000..d21cdfbc94 --- /dev/null +++ b/mobile/lib/widgets/activities/activity_text_field.dart @@ -0,0 +1,85 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/activity.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/user.provider.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +class ActivityTextField extends HookConsumerWidget { + final bool isEnabled; + final String? likeId; + final Function(String) onSubmit; + + const ActivityTextField({required this.onSubmit, this.isEnabled = true, this.likeId, super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final album = ref.watch(currentAlbumProvider)!; + final asset = ref.watch(currentAssetProvider); + final activityNotifier = ref.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier); + final user = ref.watch(currentUserProvider); + final inputController = useTextEditingController(); + final inputFocusNode = useFocusNode(); + final liked = likeId != null; + + // Show keyboard immediately on activities open + useEffect(() { + inputFocusNode.requestFocus(); + return null; + }, []); + + // Pass text to callback and reset controller + void onEditingComplete() { + onSubmit(inputController.text); + inputController.clear(); + inputFocusNode.unfocus(); + } + + Future addLike() async { + await activityNotifier.addLike(); + } + + Future removeLike() async { + if (liked) { + await activityNotifier.removeActivity(likeId!); + } + } + + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: TextField( + controller: inputController, + enabled: isEnabled, + focusNode: inputFocusNode, + textInputAction: TextInputAction.send, + autofocus: false, + decoration: InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + prefixIcon: user != null + ? Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: UserCircleAvatar(user: user, size: 30), + ) + : null, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 10), + child: IconButton( + icon: Icon(liked ? Icons.thumb_up : Icons.thumb_up_off_alt), + onPressed: liked ? removeLike : addLike, + ), + ), + suffixIconColor: liked ? context.primaryColor : null, + hintText: !isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(), + hintStyle: TextStyle(fontWeight: FontWeight.normal, fontSize: 14, color: Colors.grey[600]), + ), + onEditingComplete: onEditingComplete, + onTapOutside: (_) => inputFocusNode.unfocus(), + ), + ); + } +} diff --git a/mobile/lib/widgets/activities/activity_tile.dart b/mobile/lib/widgets/activities/activity_tile.dart new file mode 100644 index 0000000000..ac3b6c95a4 --- /dev/null +++ b/mobile/lib/widgets/activities/activity_tile.dart @@ -0,0 +1,113 @@ +import 'package:auto_route/auto_route.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/extensions/datetime_extensions.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +class ActivityTile extends HookConsumerWidget { + final Activity activity; + final bool isBottomSheet; + + const ActivityTile(this.activity, {super.key, this.isBottomSheet = false}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetProvider); + final isLike = activity.type == ActivityType.like; + // Asset thumbnail is displayed when we are accessing activities from the album page + // currentAssetProvider will not be set until we open the gallery viewer + final showAssetThumbnail = asset == null && activity.assetId != null && !isBottomSheet; + + onTap() async { + final activityService = ref.read(activityServiceProvider); + final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); + if (route != null) { + await context.pushRoute(route); + } + } + + return ListTile( + minVerticalPadding: 15, + leading: isLike + ? Container( + width: isBottomSheet ? 30 : 44, + alignment: Alignment.center, + child: Icon(Icons.thumb_up, color: context.primaryColor), + ) + : isBottomSheet + ? UserCircleAvatar(user: activity.user, size: 30) + : UserCircleAvatar(user: activity.user), + title: _ActivityTitle( + userName: activity.user.name, + createdAt: activity.createdAt.timeAgo(), + leftAlign: isBottomSheet ? false : (isLike || showAssetThumbnail), + ), + // No subtitle for like, so center title + titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center, + trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!, onTap) : null, + subtitle: !isLike ? Text(activity.comment!) : null, + ); + } +} + +class _ActivityTitle extends StatelessWidget { + final String userName; + final String createdAt; + final bool leftAlign; + + const _ActivityTitle({required this.userName, required this.createdAt, required this.leftAlign}); + + @override + Widget build(BuildContext context) { + final textColor = context.isDarkTheme ? Colors.white : Colors.black; + final textStyle = context.textTheme.bodyMedium?.copyWith(color: textColor.withValues(alpha: 0.6)); + + return Row( + mainAxisAlignment: leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween, + mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max, + children: [ + Text(userName, style: textStyle, overflow: TextOverflow.ellipsis), + if (leftAlign) Text(" â€ĸ ", style: textStyle), + Expanded( + child: Text( + createdAt, + style: textStyle, + overflow: TextOverflow.ellipsis, + textAlign: leftAlign ? TextAlign.left : TextAlign.right, + ), + ), + ], + ); + } +} + +class _ActivityAssetThumbnail extends StatelessWidget { + final String assetId; + final GestureTapCallback? onTap; + + const _ActivityAssetThumbnail(this.assetId, this.onTap); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + width: 40, + height: 30, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(4)), + image: DecorationImage( + image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: ""), + fit: BoxFit.cover, + ), + ), + child: const SizedBox.shrink(), + ), + ); + } +} diff --git a/mobile/lib/widgets/activities/comment_bubble.dart b/mobile/lib/widgets/activities/comment_bubble.dart index 5431420741..401e4b8e99 100644 --- a/mobile/lib/widgets/activities/comment_bubble.dart +++ b/mobile/lib/widgets/activities/comment_bubble.dart @@ -28,20 +28,14 @@ class CommentBubble extends ConsumerWidget { final isLike = activity.type == ActivityType.like; final bgColor = isOwn ? context.colorScheme.primaryContainer : context.colorScheme.surfaceContainer; - final activityNotifier = ref.watch( - albumActivityProvider((album.id, isAssetActivity ? activity.assetId : null)).notifier, + final activityNotifier = ref.read( + albumActivityProvider(album.id, isAssetActivity ? activity.assetId : null).notifier, ); Future openAssetViewer() async { final activityService = ref.read(activityServiceProvider); final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref); - if (!context.mounted) { - return; - } - - if (route != null) { - await context.pushRoute(route); - } + if (route != null) await context.pushRoute(route); } // avatar (hidden for own messages) diff --git a/mobile/lib/widgets/activities/dismissible_activity.dart b/mobile/lib/widgets/activities/dismissible_activity.dart index f1305ba3aa..806181ecdc 100644 --- a/mobile/lib/widgets/activities/dismissible_activity.dart +++ b/mobile/lib/widgets/activities/dismissible_activity.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/widgets/activities/activity_tile.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +/// Wraps an [ActivityTile] and makes it dismissible class DismissibleActivity extends StatelessWidget { final String activityId; final Widget body; @@ -24,9 +25,9 @@ class DismissibleActivity extends StatelessWidget { context: context, builder: (context) => ConfirmDialog( onOk: () {}, - title: context.t.shared_album_activity_remove_title, - content: context.t.shared_album_activity_remove_content, - ok: context.t.delete, + title: "shared_album_activity_remove_title", + content: "shared_album_activity_remove_content", + ok: "delete", ), ) : Future.value(false), diff --git a/mobile/lib/widgets/album/add_to_album_bottom_sheet.dart b/mobile/lib/widgets/album/add_to_album_bottom_sheet.dart new file mode 100644 index 0000000000..d8f6a8885a --- /dev/null +++ b/mobile/lib/widgets/album/add_to_album_bottom_sheet.dart @@ -0,0 +1,98 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/widgets/album/add_to_album_sliverlist.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/widgets/common/drag_sheet.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class AddToAlbumBottomSheet extends HookConsumerWidget { + /// The asset to add to an album + final List assets; + + const AddToAlbumBottomSheet({super.key, required this.assets}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList(); + final albumService = ref.watch(albumServiceProvider); + + useEffect(() { + // Fetch album updates, e.g., cover image + ref.read(albumProvider.notifier).refreshRemoteAlbums(); + + return null; + }, []); + + void addToAlbum(Album album) async { + final result = await albumService.addAssets(album, assets); + + if (result != null) { + if (result.alreadyInAlbum.isNotEmpty) { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}), + ); + } else { + ImmichToast.show( + context: context, + msg: 'add_to_album_bottom_sheet_added'.tr(namedArgs: {"album": album.name}), + ); + } + } + context.pop(); + } + + return Card( + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15)), + ), + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 12), + const Align(alignment: Alignment.center, child: CustomDraggingHandle()), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('add_to_album'.tr(), style: context.textTheme.displayMedium), + TextButton.icon( + icon: Icon(Icons.add, color: context.primaryColor), + label: Text('common_create_new_album'.tr(), style: TextStyle(color: context.primaryColor)), + onPressed: () { + context.pushRoute(CreateAlbumRoute(assets: assets)); + }, + ), + ], + ), + ], + ), + ), + ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: AddToAlbumSliverList( + albums: albums, + sharedAlbums: albums.where((a) => a.shared).toList(), + onAddToAlbum: addToAlbum, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/album/add_to_album_sliverlist.dart b/mobile/lib/widgets/album/add_to_album_sliverlist.dart new file mode 100644 index 0000000000..defbd90388 --- /dev/null +++ b/mobile/lib/widgets/album/add_to_album_sliverlist.dart @@ -0,0 +1,65 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/widgets/album/album_thumbnail_listtile.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; + +class AddToAlbumSliverList extends HookConsumerWidget { + /// The asset to add to an album + final List albums; + final List sharedAlbums; + final void Function(Album) onAddToAlbum; + final bool enabled; + + const AddToAlbumSliverList({ + super.key, + required this.onAddToAlbum, + required this.albums, + required this.sharedAlbums, + this.enabled = true, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumSortMode = ref.watch(albumSortByOptionsProvider); + final albumSortIsReverse = ref.watch(albumSortOrderProvider); + final sortedAlbums = albumSortMode.sortFn(albums, albumSortIsReverse); + final sortedSharedAlbums = albumSortMode.sortFn(sharedAlbums, albumSortIsReverse); + + return SliverList( + delegate: SliverChildBuilderDelegate(childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1), ( + context, + index, + ) { + // Build shared expander + if (index == 0 && sortedSharedAlbums.isNotEmpty) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: ExpansionTile( + title: Text('shared'.tr()), + tilePadding: const EdgeInsets.symmetric(horizontal: 10.0), + leading: const Icon(Icons.group), + children: [ + ListView.builder( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + itemCount: sortedSharedAlbums.length, + itemBuilder: (context, index) => AlbumThumbnailListTile( + album: sortedSharedAlbums[index], + onTap: enabled ? () => onAddToAlbum(sortedSharedAlbums[index]) : () {}, + ), + ), + ], + ), + ); + } + + // Build albums list + final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0); + final album = sortedAlbums[offset]; + return AlbumThumbnailListTile(album: album, onTap: enabled ? () => onAddToAlbum(album) : () {}); + }), + ); + } +} diff --git a/mobile/lib/widgets/album/album_thumbnail_card.dart b/mobile/lib/widgets/album/album_thumbnail_card.dart new file mode 100644 index 0000000000..6c56f5d843 --- /dev/null +++ b/mobile/lib/widgets/album/album_thumbnail_card.dart @@ -0,0 +1,111 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; + +class AlbumThumbnailCard extends ConsumerWidget { + final Function()? onTap; + + /// Whether or not to show the owner of the album (or "Owned") + /// in the subtitle of the album + final bool showOwner; + final bool showTitle; + + const AlbumThumbnailCard({super.key, required this.album, this.onTap, this.showOwner = false, this.showTitle = true}); + + final Album album; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return LayoutBuilder( + builder: (context, constraints) { + var cardSize = constraints.maxWidth; + + buildEmptyThumbnail() { + return Container( + height: cardSize, + width: cardSize, + decoration: BoxDecoration(color: context.colorScheme.surfaceContainerHigh), + child: Center( + child: Icon(Icons.no_photography, size: cardSize * .15, color: context.colorScheme.primary), + ), + ); + } + + buildAlbumThumbnail() => ImmichThumbnail(asset: album.thumbnail.value, width: cardSize, height: cardSize); + + buildAlbumTextRow() { + // Add the owner name to the subtitle + String? owner; + if (showOwner) { + if (album.ownerId == ref.read(currentUserProvider)?.id) { + owner = 'owned'.tr(); + } else if (album.ownerName != null) { + owner = 'shared_by_user'.t(context: context, args: {'user': album.ownerName!}); + } + } + + return Text.rich( + TextSpan( + children: [ + TextSpan( + text: 'items_count'.t(context: context, args: {'count': album.assetCount}), + ), + if (owner != null) const TextSpan(text: ' â€ĸ '), + if (owner != null) TextSpan(text: owner), + ], + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ), + overflow: TextOverflow.fade, + ); + } + + return GestureDetector( + onTap: onTap, + child: Flex( + direction: Axis.vertical, + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: cardSize, + height: cardSize, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(20)), + child: album.thumbnail.value == null ? buildEmptyThumbnail() : buildAlbumThumbnail(), + ), + ), + if (showTitle) ...[ + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: SizedBox( + width: cardSize, + child: Text( + album.name, + overflow: TextOverflow.ellipsis, + style: context.textTheme.titleSmall?.copyWith( + color: context.colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + buildAlbumTextRow(), + ], + ], + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/mobile/lib/widgets/album/album_thumbnail_listtile.dart b/mobile/lib/widgets/album/album_thumbnail_listtile.dart new file mode 100644 index 0000000000..386084b034 --- /dev/null +++ b/mobile/lib/widgets/album/album_thumbnail_listtile.dart @@ -0,0 +1,93 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:openapi/api.dart'; + +class AlbumThumbnailListTile extends StatelessWidget { + const AlbumThumbnailListTile({super.key, required this.album, this.onTap}); + + final Album album; + final void Function()? onTap; + + @override + Widget build(BuildContext context) { + var cardSize = 68.0; + + buildEmptyThumbnail() { + return Container( + decoration: BoxDecoration(color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[200]), + child: SizedBox( + height: cardSize, + width: cardSize, + child: const Center(child: Icon(Icons.no_photography)), + ), + ); + } + + buildAlbumThumbnail() { + return SizedBox( + width: cardSize, + height: cardSize, + child: Thumbnail( + imageProvider: RemoteImageProvider(url: getAlbumThumbnailUrl(album, type: AssetMediaSize.thumbnail)), + ), + ); + } + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: + onTap ?? + () { + context.pushRoute(AlbumViewerRoute(albumId: album.id)); + }, + child: Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: album.thumbnail.value == null ? buildEmptyThumbnail() : buildAlbumThumbnail(), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + album.name, + overflow: TextOverflow.ellipsis, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'items_count'.t(context: context, args: {'count': album.assetCount}), + style: const TextStyle(fontSize: 12), + ), + if (album.shared) ...[ + const Text(' â€ĸ ', style: TextStyle(fontSize: 12)), + Text('shared'.tr(), style: const TextStyle(fontSize: 12)), + ], + ], + ), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/album/album_title_text_field.dart b/mobile/lib/widgets/album/album_title_text_field.dart new file mode 100644 index 0000000000..0a7438b7ae --- /dev/null +++ b/mobile/lib/widgets/album/album_title_text_field.dart @@ -0,0 +1,74 @@ +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/album_title.provider.dart'; + +class AlbumTitleTextField extends ConsumerWidget { + const AlbumTitleTextField({ + super.key, + required this.isAlbumTitleEmpty, + required this.albumTitleTextFieldFocusNode, + required this.albumTitleController, + required this.isAlbumTitleTextFieldFocus, + }); + + final ValueNotifier isAlbumTitleEmpty; + final FocusNode albumTitleTextFieldFocusNode; + final TextEditingController albumTitleController; + final ValueNotifier isAlbumTitleTextFieldFocus; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return TextField( + onChanged: (v) { + if (v.isEmpty) { + isAlbumTitleEmpty.value = true; + } else { + isAlbumTitleEmpty.value = false; + } + + ref.watch(albumTitleProvider.notifier).setAlbumTitle(v); + }, + focusNode: albumTitleTextFieldFocusNode, + style: TextStyle(fontSize: 28, color: context.colorScheme.onSurface, fontWeight: FontWeight.bold), + controller: albumTitleController, + onTap: () { + isAlbumTitleTextFieldFocus.value = true; + + if (albumTitleController.text == 'Untitled') { + albumTitleController.clear(); + } + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), + suffixIcon: !isAlbumTitleEmpty.value && isAlbumTitleTextFieldFocus.value + ? IconButton( + onPressed: () { + albumTitleController.clear(); + isAlbumTitleEmpty.value = true; + }, + icon: Icon(Icons.cancel_rounded, color: context.primaryColor), + splashRadius: 10, + ) + : null, + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + borderRadius: BorderRadius.all(Radius.circular(10)), + ), + hintText: 'add_a_title'.tr(), + hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith( + fontSize: 28, + fontWeight: FontWeight.bold, + ), + focusColor: Colors.grey[300], + fillColor: context.colorScheme.surfaceContainerHigh, + filled: isAlbumTitleTextFieldFocus.value, + ), + ); + } +} diff --git a/mobile/lib/widgets/album/album_viewer_appbar.dart b/mobile/lib/widgets/album/album_viewer_appbar.dart new file mode 100644 index 0000000000..4fd4b31013 --- /dev/null +++ b/mobile/lib/widgets/album/album_viewer_appbar.dart @@ -0,0 +1,307 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/activity_statistics.provider.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/album/album_viewer.provider.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class AlbumViewerAppbar extends HookConsumerWidget implements PreferredSizeWidget { + const AlbumViewerAppbar({ + super.key, + required this.userId, + required this.titleFocusNode, + required this.descriptionFocusNode, + this.onAddPhotos, + this.onAddUsers, + required this.onActivities, + }); + + final String userId; + final FocusNode titleFocusNode; + final FocusNode descriptionFocusNode; + final void Function()? onAddPhotos; + final void Function()? onAddUsers; + final void Function() onActivities; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumState = useState(ref.read(currentAlbumProvider)); + final album = albumState.value; + ref.listen(currentAlbumProvider, (_, newAlbum) { + final oldAlbum = albumState.value; + if (oldAlbum != null && newAlbum != null && oldAlbum.id == newAlbum.id) { + return; + } + + albumState.value = newAlbum; + }); + + if (album == null) { + return const SizedBox(); + } + + final albumViewer = ref.watch(albumViewerProvider); + final newAlbumTitle = albumViewer.editTitleText; + final newAlbumDescription = albumViewer.editDescriptionText; + final isEditAlbum = albumViewer.isEditAlbum; + + final comments = album.shared ? ref.watch(activityStatisticsProvider(album.remoteId!)) : 0; + + deleteAlbum() async { + final bool success = await ref.watch(albumProvider.notifier).deleteAlbum(album); + + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); + + if (!success) { + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_delete".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + } + + Future onDeleteAlbumPressed() { + return showDialog( + context: context, + barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('delete_album').tr(), + content: const Text('album_viewer_appbar_delete_confirm').tr(), + actions: [ + TextButton( + onPressed: () => context.pop('Cancel'), + child: Text( + 'cancel', + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), + ).tr(), + ), + TextButton( + onPressed: () { + context.pop('Confirm'); + deleteAlbum(); + }, + child: Text( + 'confirm', + style: TextStyle(fontWeight: FontWeight.bold, color: context.colorScheme.error), + ).tr(), + ), + ], + ); + }, + ); + } + + void onLeaveAlbumPressed() async { + bool isSuccess = await ref.watch(albumProvider.notifier).leaveAlbum(album); + + if (isSuccess) { + unawaited(context.navigateTo(const TabControllerRoute(children: [AlbumsRoute()]))); + } else { + context.pop(); + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_leave".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + } + + buildBottomSheetActions() { + return [ + album.ownerId == userId + ? ListTile( + leading: const Icon(Icons.delete_forever_rounded), + title: const Text('delete_album', style: TextStyle(fontWeight: FontWeight.w500)).tr(), + onTap: onDeleteAlbumPressed, + ) + : ListTile( + leading: const Icon(Icons.person_remove_rounded), + title: const Text( + 'album_viewer_appbar_share_leave', + style: TextStyle(fontWeight: FontWeight.w500), + ).tr(), + onTap: onLeaveAlbumPressed, + ), + ]; + // } + } + + void onSortOrderToggled() async { + final updatedAlbum = await ref.read(albumProvider.notifier).toggleSortOrder(album); + + if (updatedAlbum == null) { + ImmichToast.show( + context: context, + msg: "error_change_sort_album".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + + context.pop(); + } + + void buildBottomSheet() { + final ownerActions = [ + ListTile( + leading: const Icon(Icons.person_add_alt_rounded), + onTap: () { + context.pop(); + final onAddUsers = this.onAddUsers; + if (onAddUsers != null) { + onAddUsers(); + } + }, + title: const Text("album_viewer_page_share_add_users", style: TextStyle(fontWeight: FontWeight.w500)).tr(), + ), + ListTile( + leading: const Icon(Icons.swap_vert_rounded), + onTap: onSortOrderToggled, + title: const Text("change_display_order", style: TextStyle(fontWeight: FontWeight.w500)).tr(), + ), + ListTile( + leading: const Icon(Icons.link_rounded), + onTap: () { + context.pushRoute(SharedLinkEditRoute(albumId: album.remoteId)); + context.pop(); + }, + title: const Text("control_bottom_app_bar_share_link", style: TextStyle(fontWeight: FontWeight.w500)).tr(), + ), + ListTile( + leading: const Icon(Icons.settings_rounded), + onTap: () => context.navigateTo(const AlbumOptionsRoute()), + title: const Text("options", style: TextStyle(fontWeight: FontWeight.w500)).tr(), + ), + ]; + + final commonActions = [ + ListTile( + leading: const Icon(Icons.add_photo_alternate_outlined), + onTap: () { + context.pop(); + final onAddPhotos = this.onAddPhotos; + if (onAddPhotos != null) { + onAddPhotos(); + } + }, + title: const Text("add_photos", style: TextStyle(fontWeight: FontWeight.w500)).tr(), + ), + ]; + showModalBottomSheet( + backgroundColor: context.scaffoldBackgroundColor, + isScrollControlled: false, + context: context, + builder: (context) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: ListView( + shrinkWrap: true, + children: [ + ...buildBottomSheetActions(), + if (onAddPhotos != null) ...commonActions, + if (onAddPhotos != null && userId == album.ownerId) ...ownerActions, + ], + ), + ), + ); + }, + ); + } + + Widget buildActivitiesButton() { + return IconButton( + onPressed: onActivities, + icon: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon(Icons.mode_comment_outlined), + if (comments != 0) + Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + comments.toString(), + style: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor), + ), + ), + ], + ), + ); + } + + buildLeadingButton() { + if (isEditAlbum) { + return IconButton( + onPressed: () async { + if (newAlbumTitle.isNotEmpty) { + bool isSuccess = await ref.watch(albumViewerProvider.notifier).changeAlbumTitle(album, newAlbumTitle); + if (!isSuccess) { + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_title".tr(), + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } + titleFocusNode.unfocus(); + } else if (newAlbumDescription.isNotEmpty) { + bool isSuccessDescription = await ref + .watch(albumViewerProvider.notifier) + .changeAlbumDescription(album, newAlbumDescription); + if (!isSuccessDescription) { + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_description".tr(), + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } + descriptionFocusNode.unfocus(); + } else { + titleFocusNode.unfocus(); + descriptionFocusNode.unfocus(); + ref.read(albumViewerProvider.notifier).disableEditAlbum(); + } + }, + icon: const Icon(Icons.check_rounded), + splashRadius: 25, + ); + } else { + return IconButton( + onPressed: context.maybePop, + icon: const Icon(Icons.arrow_back_ios_rounded), + splashRadius: 25, + ); + } + } + + return AppBar( + elevation: 0, + backgroundColor: context.scaffoldBackgroundColor, + leading: buildLeadingButton(), + centerTitle: false, + actions: [ + if (album.shared && (album.activityEnabled || comments != 0)) buildActivitiesButton(), + if (album.isRemote) ...[ + IconButton(splashRadius: 25, onPressed: buildBottomSheet, icon: const Icon(Icons.more_horiz_rounded)), + ], + ], + ); + } + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); +} diff --git a/mobile/lib/widgets/album/album_viewer_editable_description.dart b/mobile/lib/widgets/album/album_viewer_editable_description.dart new file mode 100644 index 0000000000..decd268ff3 --- /dev/null +++ b/mobile/lib/widgets/album/album_viewer_editable_description.dart @@ -0,0 +1,82 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album_viewer.provider.dart'; + +class AlbumViewerEditableDescription extends HookConsumerWidget { + final String albumDescription; + final FocusNode descriptionFocusNode; + const AlbumViewerEditableDescription({super.key, required this.albumDescription, required this.descriptionFocusNode}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumViewerState = ref.watch(albumViewerProvider); + + final descriptionTextEditController = useTextEditingController( + text: albumViewerState.isEditAlbum && albumViewerState.editDescriptionText.isNotEmpty + ? albumViewerState.editDescriptionText + : albumDescription, + ); + + void onFocusModeChange() { + if (!descriptionFocusNode.hasFocus && descriptionTextEditController.text.isEmpty) { + ref.watch(albumViewerProvider.notifier).setEditDescriptionText(""); + descriptionTextEditController.text = ""; + } + } + + useEffect(() { + descriptionFocusNode.addListener(onFocusModeChange); + return () { + descriptionFocusNode.removeListener(onFocusModeChange); + }; + }, []); + + return Material( + color: Colors.transparent, + child: TextField( + onChanged: (value) { + if (value.isEmpty) { + } else { + ref.watch(albumViewerProvider.notifier).setEditDescriptionText(value); + } + }, + focusNode: descriptionFocusNode, + style: context.textTheme.bodyLarge, + maxLines: 3, + minLines: 1, + controller: descriptionTextEditController, + onTap: () { + context.focusScope.requestFocus(descriptionFocusNode); + + ref.watch(albumViewerProvider.notifier).setEditDescriptionText(albumDescription); + ref.watch(albumViewerProvider.notifier).enableEditAlbum(); + + if (descriptionTextEditController.text == '') { + descriptionTextEditController.clear(); + } + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.all(8), + suffixIcon: descriptionFocusNode.hasFocus + ? IconButton( + onPressed: () { + descriptionTextEditController.clear(); + }, + icon: Icon(Icons.cancel_rounded, color: context.primaryColor), + splashRadius: 10, + ) + : null, + enabledBorder: const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)), + focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)), + focusColor: Colors.grey[300], + fillColor: context.scaffoldBackgroundColor, + filled: descriptionFocusNode.hasFocus, + hintText: 'add_a_description'.tr(), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/album/album_viewer_editable_title.dart b/mobile/lib/widgets/album/album_viewer_editable_title.dart new file mode 100644 index 0000000000..c84e613017 --- /dev/null +++ b/mobile/lib/widgets/album/album_viewer_editable_title.dart @@ -0,0 +1,81 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album_viewer.provider.dart'; + +class AlbumViewerEditableTitle extends HookConsumerWidget { + final String albumName; + final FocusNode titleFocusNode; + const AlbumViewerEditableTitle({super.key, required this.albumName, required this.titleFocusNode}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final albumViewerState = ref.watch(albumViewerProvider); + + final titleTextEditController = useTextEditingController( + text: albumViewerState.isEditAlbum && albumViewerState.editTitleText.isNotEmpty + ? albumViewerState.editTitleText + : albumName, + ); + + void onFocusModeChange() { + if (!titleFocusNode.hasFocus && titleTextEditController.text.isEmpty) { + ref.watch(albumViewerProvider.notifier).setEditTitleText("Untitled"); + titleTextEditController.text = "Untitled"; + } + } + + useEffect(() { + titleFocusNode.addListener(onFocusModeChange); + return () { + titleFocusNode.removeListener(onFocusModeChange); + }; + }, []); + + return Material( + color: Colors.transparent, + child: TextField( + onChanged: (value) { + if (value.isEmpty) { + } else { + ref.watch(albumViewerProvider.notifier).setEditTitleText(value); + } + }, + focusNode: titleFocusNode, + style: context.textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.w700), + controller: titleTextEditController, + onTap: () { + context.focusScope.requestFocus(titleFocusNode); + + ref.watch(albumViewerProvider.notifier).setEditTitleText(albumName); + ref.watch(albumViewerProvider.notifier).enableEditAlbum(); + + if (titleTextEditController.text == 'Untitled') { + titleTextEditController.clear(); + } + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0), + suffixIcon: titleFocusNode.hasFocus + ? IconButton( + onPressed: () { + titleTextEditController.clear(); + }, + icon: Icon(Icons.cancel_rounded, color: context.primaryColor), + splashRadius: 10, + ) + : null, + enabledBorder: const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)), + focusedBorder: const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)), + focusColor: Colors.grey[300], + fillColor: context.scaffoldBackgroundColor, + filled: titleFocusNode.hasFocus, + hintText: 'add_a_title'.tr(), + hintStyle: context.themeData.inputDecorationTheme.hintStyle?.copyWith(fontSize: 28), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/album/remote_album_shared_user_icons.dart b/mobile/lib/widgets/album/remote_album_shared_user_icons.dart index be908259d4..2025fa7583 100644 --- a/mobile/lib/widgets/album/remote_album_shared_user_icons.dart +++ b/mobile/lib/widgets/album/remote_album_shared_user_icons.dart @@ -30,12 +30,12 @@ class RemoteAlbumSharedUserIcons extends ConsumerWidget { height: 50, child: ListView.builder( scrollDirection: Axis.horizontal, - itemBuilder: (context, index) { + itemBuilder: ((context, index) { return Padding( padding: const EdgeInsets.only(right: 4.0), child: UserCircleAvatar(user: sharedUsers[index], size: 36, hasBorder: true), ); - }, + }), itemCount: sharedUsers.length, ), ), diff --git a/mobile/lib/widgets/album/shared_album_thumbnail_image.dart b/mobile/lib/widgets/album/shared_album_thumbnail_image.dart new file mode 100644 index 0000000000..b21e86d145 --- /dev/null +++ b/mobile/lib/widgets/album/shared_album_thumbnail_image.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; + +class SharedAlbumThumbnailImage extends HookConsumerWidget { + final Asset asset; + + const SharedAlbumThumbnailImage({super.key, required this.asset}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return GestureDetector( + onTap: () { + // debugPrint("View ${asset.id}"); + }, + child: Stack(children: [ImmichThumbnail(asset: asset, width: 500, height: 500)]), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/asset_drag_region.dart b/mobile/lib/widgets/asset_grid/asset_drag_region.dart new file mode 100644 index 0000000000..71e55acbd6 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/asset_drag_region.dart @@ -0,0 +1,207 @@ +// Based on https://stackoverflow.com/a/52625182 + +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +class AssetDragRegion extends StatefulWidget { + final Widget child; + + final void Function(AssetIndex valueKey)? onStart; + final void Function(AssetIndex valueKey)? onAssetEnter; + final void Function()? onEnd; + final void Function()? onScrollStart; + final void Function(ScrollDirection direction)? onScroll; + + const AssetDragRegion({ + super.key, + required this.child, + this.onStart, + this.onAssetEnter, + this.onEnd, + this.onScrollStart, + this.onScroll, + }); + @override + State createState() => _AssetDragRegionState(); +} + +class _AssetDragRegionState extends State { + late AssetIndex? assetUnderPointer; + late AssetIndex? anchorAsset; + + // Scroll related state + static const double scrollOffset = 0.10; + double? topScrollOffset; + double? bottomScrollOffset; + Timer? scrollTimer; + late bool scrollNotified; + + @override + void initState() { + super.initState(); + assetUnderPointer = null; + anchorAsset = null; + scrollNotified = false; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + topScrollOffset = null; + bottomScrollOffset = null; + } + + @override + void dispose() { + scrollTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RawGestureDetector( + gestures: { + _CustomLongPressGestureRecognizer: GestureRecognizerFactoryWithHandlers<_CustomLongPressGestureRecognizer>( + () => _CustomLongPressGestureRecognizer(), + _registerCallbacks, + ), + }, + child: widget.child, + ); + } + + void _registerCallbacks(_CustomLongPressGestureRecognizer recognizer) { + recognizer.onLongPressMoveUpdate = (details) => _onLongPressMove(details); + recognizer.onLongPressStart = (details) => _onLongPressStart(details); + recognizer.onLongPressUp = _onLongPressEnd; + } + + AssetIndex? _getValueKeyAtPositon(Offset position) { + final box = context.findAncestorRenderObjectOfType(); + if (box == null) return null; + + final hitTestResult = BoxHitTestResult(); + final local = box.globalToLocal(position); + if (!box.hitTest(hitTestResult, position: local)) return null; + + return (hitTestResult.path.firstWhereOrNull((hit) => hit.target is _AssetIndexProxy)?.target as _AssetIndexProxy?) + ?.index; + } + + void _onLongPressStart(LongPressStartDetails event) { + /// Calculate widget height and scroll offset when long press starting instead of in [initState] + /// or [didChangeDependencies] as the grid might still be rendering into view to get the actual size + final height = context.size?.height; + if (height != null && (topScrollOffset == null || bottomScrollOffset == null)) { + topScrollOffset = height * scrollOffset; + bottomScrollOffset = height - topScrollOffset!; + } + + final initialHit = _getValueKeyAtPositon(event.globalPosition); + anchorAsset = initialHit; + if (initialHit == null) return; + + if (anchorAsset != null) { + widget.onStart?.call(anchorAsset!); + } + } + + void _onLongPressEnd() { + scrollNotified = false; + scrollTimer?.cancel(); + widget.onEnd?.call(); + } + + void _onLongPressMove(LongPressMoveUpdateDetails event) { + if (anchorAsset == null) return; + if (topScrollOffset == null || bottomScrollOffset == null) return; + + final currentDy = event.localPosition.dy; + + if (currentDy > bottomScrollOffset!) { + scrollTimer ??= Timer.periodic( + const Duration(milliseconds: 50), + (_) => widget.onScroll?.call(ScrollDirection.forward), + ); + } else if (currentDy < topScrollOffset!) { + scrollTimer ??= Timer.periodic( + const Duration(milliseconds: 50), + (_) => widget.onScroll?.call(ScrollDirection.reverse), + ); + } else { + scrollTimer?.cancel(); + scrollTimer = null; + } + + final currentlyTouchingAsset = _getValueKeyAtPositon(event.globalPosition); + if (currentlyTouchingAsset == null) return; + + if (assetUnderPointer != currentlyTouchingAsset) { + if (!scrollNotified) { + scrollNotified = true; + widget.onScrollStart?.call(); + } + + widget.onAssetEnter?.call(currentlyTouchingAsset); + assetUnderPointer = currentlyTouchingAsset; + } + } +} + +class _CustomLongPressGestureRecognizer extends LongPressGestureRecognizer { + @override + void rejectGesture(int pointer) { + acceptGesture(pointer); + } +} + +class AssetIndexWrapper extends SingleChildRenderObjectWidget { + final int rowIndex; + final int sectionIndex; + + const AssetIndexWrapper({required Widget super.child, required this.rowIndex, required this.sectionIndex, super.key}); + + @override + // ignore: library_private_types_in_public_api + _AssetIndexProxy createRenderObject(BuildContext context) { + return _AssetIndexProxy( + index: AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex), + ); + } + + @override + void updateRenderObject( + BuildContext context, + // ignore: library_private_types_in_public_api + _AssetIndexProxy renderObject, + ) { + renderObject.index = AssetIndex(rowIndex: rowIndex, sectionIndex: sectionIndex); + } +} + +class _AssetIndexProxy extends RenderProxyBox { + AssetIndex index; + + _AssetIndexProxy({required this.index}); +} + +class AssetIndex { + final int rowIndex; + final int sectionIndex; + + const AssetIndex({required this.rowIndex, required this.sectionIndex}); + + @override + bool operator ==(covariant AssetIndex other) { + if (identical(this, other)) return true; + + return other.rowIndex == rowIndex && other.sectionIndex == sectionIndex; + } + + @override + int get hashCode => rowIndex.hashCode ^ sectionIndex.hashCode; +} diff --git a/mobile/lib/widgets/asset_grid/asset_grid_data_structure.dart b/mobile/lib/widgets/asset_grid/asset_grid_data_structure.dart new file mode 100644 index 0000000000..d95d6efe2e --- /dev/null +++ b/mobile/lib/widgets/asset_grid/asset_grid_data_structure.dart @@ -0,0 +1,307 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:isar/isar.dart'; +import 'package:logging/logging.dart'; + +final log = Logger('AssetGridDataStructure'); + +enum RenderAssetGridElementType { assets, assetRow, groupDividerTitle, monthTitle } + +class RenderAssetGridElement { + final RenderAssetGridElementType type; + final String? title; + final DateTime date; + final int count; + final int offset; + final int totalCount; + + const RenderAssetGridElement( + this.type, { + this.title, + required this.date, + this.count = 0, + this.offset = 0, + this.totalCount = 0, + }); +} + +enum GroupAssetsBy { day, month, auto, none } + +class RenderList { + final List elements; + final List? allAssets; + final QueryBuilder? query; + final int totalAssets; + + /// reference to batch of assets loaded from DB with offset [_bufOffset] + List _buf = []; + + /// global offset of assets in [_buf] + int _bufOffset = 0; + + RenderList(this.elements, this.query, this.allAssets) : totalAssets = allAssets?.length ?? query!.countSync(); + + bool get isEmpty => totalAssets == 0; + + /// Loads the requested assets from the database to an internal buffer if not cached + /// and returns a slice of that buffer + List loadAssets(int offset, int count) { + assert(offset >= 0); + assert(count > 0); + assert(offset + count <= totalAssets); + if (allAssets != null) { + // if we already loaded all assets (e.g. from search result) + // simply return the requested slice of that array + return allAssets!.slice(offset, offset + count); + } else if (query != null) { + // general case: we have the query to load assets via offset from the DB on demand + if (offset < _bufOffset || offset + count > _bufOffset + _buf.length) { + // the requested slice (offset:offset+count) is not contained in the cache buffer `_buf` + // thus, fill the buffer with a new batch of assets that at least contains the requested + // assets and some more + + final bool forward = _bufOffset < offset; + // if the requested offset is greater than the cached offset, the user scrolls forward "down" + const batchSize = 256; + const oppositeSize = 64; + + // make sure to load a meaningful amount of data (and not only the requested slice) + // otherwise, each call to [loadAssets] would result in DB call trashing performance + // fills small requests to [batchSize], adds some legroom into the opposite scroll direction for large requests + final len = max(batchSize, count + oppositeSize); + // when scrolling forward, start shortly before the requested offset... + // when scrolling backward, end shortly after the requested offset... + // ... to guard against the user scrolling in the other direction + // a tiny bit resulting in a another required load from the DB + final start = max(0, forward ? offset - oppositeSize : (len > batchSize ? offset : offset + count - len)); + // load the calculated batch (start:start+len) from the DB and put it into the buffer + _buf = query!.offset(start).limit(len).findAllSync(); + _bufOffset = start; + } + assert(_bufOffset <= offset); + assert(_bufOffset + _buf.length >= offset + count); + // return the requested slice from the buffer (we made sure before that the assets are loaded!) + return _buf.slice(offset - _bufOffset, offset - _bufOffset + count); + } + throw Exception("RenderList has neither assets nor query"); + } + + /// Returns the requested asset either from cached buffer or directly from the database + Asset loadAsset(int index) { + if (allAssets != null) { + // all assets are already loaded (e.g. from search result) + return allAssets![index]; + } else if (query != null) { + // general case: we have the DB query to load asset(s) on demand + if (index >= _bufOffset && index < _bufOffset + _buf.length) { + // lucky case: the requested asset is already cached in the buffer! + return _buf[index - _bufOffset]; + } + // request the asset from the database (not changing the buffer!) + final asset = query!.offset(index).findFirstSync(); + if (asset == null) { + throw Exception("Asset at index $index does no longer exist in database"); + } + return asset; + } + throw Exception("RenderList has neither assets nor query"); + } + + static Future fromQuery(QueryBuilder query, GroupAssetsBy groupBy) => + _buildRenderList(null, query, groupBy); + + static Future _buildRenderList( + List? assets, + QueryBuilder? query, + GroupAssetsBy groupBy, + ) async { + final List elements = []; + + const pageSize = 50000; + const sectionSize = 60; // divides evenly by 2,3,4,5,6 + + if (groupBy == GroupAssetsBy.none) { + final int total = assets?.length ?? query!.countSync(); + + final dateLoader = query != null ? DateBatchLoader(query: query, batchSize: 1000 * sectionSize) : null; + + for (int i = 0; i < total; i += sectionSize) { + final date = assets != null ? assets[i].fileCreatedAt : await dateLoader?.getDate(i); + + final int count = i + sectionSize > total ? total - i : sectionSize; + if (date == null) break; + elements.add( + RenderAssetGridElement( + RenderAssetGridElementType.assets, + date: date, + count: count, + totalCount: total, + offset: i, + ), + ); + } + return RenderList(elements, query, assets); + } + + final formatSameYear = groupBy == GroupAssetsBy.month ? DateFormat.MMMM() : DateFormat.MMMEd(); + final formatOtherYear = groupBy == GroupAssetsBy.month ? DateFormat.yMMMM() : DateFormat.yMMMEd(); + final currentYear = DateTime.now().year; + final formatMergedSameYear = DateFormat.MMMd(); + final formatMergedOtherYear = DateFormat.yMMMd(); + + int offset = 0; + DateTime? last; + DateTime? current; + int lastOffset = 0; + int count = 0; + int monthCount = 0; + int lastMonthIndex = 0; + + String formatDateRange(DateTime from, DateTime to) { + final startDate = (from.year == currentYear ? formatMergedSameYear : formatMergedOtherYear).format(from); + final endDate = (to.year == currentYear ? formatMergedSameYear : formatMergedOtherYear).format(to); + if (DateTime(from.year, from.month, from.day) == DateTime(to.year, to.month, to.day)) { + // format range with time when both dates are on the same day + final startTime = DateFormat.Hm().format(from); + final endTime = DateFormat.Hm().format(to); + return "$startDate $startTime - $endTime"; + } + return "$startDate - $endDate"; + } + + void mergeMonth() { + if (last != null && groupBy == GroupAssetsBy.auto && monthCount <= 30 && elements.length > lastMonthIndex + 1) { + // merge all days into a single section + assert(elements[lastMonthIndex].date.month == last.month); + final e = elements[lastMonthIndex]; + + elements[lastMonthIndex] = RenderAssetGridElement( + RenderAssetGridElementType.monthTitle, + date: e.date, + count: monthCount, + totalCount: monthCount, + offset: e.offset, + title: formatDateRange(e.date, elements.last.date), + ); + elements.removeRange(lastMonthIndex + 1, elements.length); + } + } + + void addElems(DateTime d, DateTime? prevDate) { + final bool newMonth = last == null || last.year != d.year || last.month != d.month; + if (newMonth) { + mergeMonth(); + lastMonthIndex = elements.length; + monthCount = 0; + } + for (int j = 0; j < count; j += sectionSize) { + final type = j == 0 + ? (groupBy != GroupAssetsBy.month && newMonth + ? RenderAssetGridElementType.monthTitle + : RenderAssetGridElementType.groupDividerTitle) + : (groupBy == GroupAssetsBy.auto + ? RenderAssetGridElementType.groupDividerTitle + : RenderAssetGridElementType.assets); + final sectionCount = j + sectionSize > count ? count - j : sectionSize; + assert(sectionCount > 0 && sectionCount <= sectionSize); + elements.add( + RenderAssetGridElement( + type, + date: d, + count: sectionCount, + totalCount: groupBy == GroupAssetsBy.auto ? sectionCount : count, + offset: lastOffset + j, + title: j == 0 + ? (d.year == currentYear ? formatSameYear.format(d) : formatOtherYear.format(d)) + : (groupBy == GroupAssetsBy.auto ? formatDateRange(d, prevDate ?? d) : null), + ), + ); + } + monthCount += count; + } + + DateTime? prevDate; + while (true) { + // this iterates all assets (only their createdAt property) in batches + // memory usage is okay, however runtime is linear with number of assets + // TODO replace with groupBy once Isar supports such queries + final dates = assets != null + ? assets.map((a) => a.fileCreatedAt) + : await query!.offset(offset).limit(pageSize).fileCreatedAtProperty().findAll(); + int i = 0; + for (final date in dates) { + final d = DateTime(date.year, date.month, groupBy == GroupAssetsBy.month ? 1 : date.day); + current ??= d; + if (current != d) { + addElems(current, prevDate); + last = current; + current = d; + lastOffset = offset + i; + count = 0; + } + prevDate = date; + count++; + i++; + } + + if (assets != null || dates.length != pageSize) break; + offset += pageSize; + } + if (count > 0 && current != null) { + addElems(current, prevDate); + mergeMonth(); + } + assert(elements.every((e) => e.count <= sectionSize), "too large section"); + return RenderList(elements, query, assets); + } + + static RenderList empty() => RenderList([], null, []); + + static Future fromAssets(List assets, GroupAssetsBy groupBy) => + _buildRenderList(assets, null, groupBy); + + /// Deletes an asset from the render list and clears the buffer + /// This is only a workaround for deleted images still appearing in the gallery + void deleteAsset(Asset deleteAsset) { + allAssets?.remove(deleteAsset); + _buf.clear(); + _bufOffset = 0; + } +} + +class DateBatchLoader { + final QueryBuilder query; + final int batchSize; + + List _buffer = []; + int _bufferStart = 0; + + DateBatchLoader({required this.query, required this.batchSize}); + + Future getDate(int index) async { + if (!_isIndexInBuffer(index)) { + await _loadBatch(index); + } + + if (_isIndexInBuffer(index)) { + return _buffer[index - _bufferStart]; + } + + return null; + } + + Future _loadBatch(int targetIndex) async { + final batchStart = (targetIndex ~/ batchSize) * batchSize; + + _buffer = await query.offset(batchStart).limit(batchSize).fileCreatedAtProperty().findAll(); + + _bufferStart = batchStart; + } + + bool _isIndexInBuffer(int index) { + return index >= _bufferStart && index < _bufferStart + _buffer.length; + } +} diff --git a/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart new file mode 100644 index 0000000000..cd2dc70dae --- /dev/null +++ b/mobile/lib/widgets/asset_grid/control_bottom_app_bar.dart @@ -0,0 +1,388 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/widgets/album/add_to_album_sliverlist.dart'; +import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; +import 'package:immich_mobile/models/asset_selection_state.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; +import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/widgets/common/drag_sheet.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/draggable_scroll_controller.dart'; + +final controlBottomAppBarNotifier = ControlBottomAppBarNotifier(); + +class ControlBottomAppBarNotifier with ChangeNotifier { + void minimize() { + notifyListeners(); + } +} + +class ControlBottomAppBar extends HookConsumerWidget { + final void Function(bool shareLocal) onShare; + final void Function()? onFavorite; + final void Function()? onArchive; + final void Function([bool force])? onDelete; + final void Function([bool force])? onDeleteServer; + final void Function(bool onlyBackedUp)? onDeleteLocal; + final Function(Album album) onAddToAlbum; + final void Function() onCreateNewAlbum; + final void Function() onUpload; + final void Function()? onStack; + final void Function()? onEditTime; + final void Function()? onEditLocation; + final void Function()? onRemoveFromAlbum; + final void Function()? onToggleLocked; + final void Function()? onDownload; + + final bool enabled; + final bool unfavorite; + final bool unarchive; + final AssetSelectionState selectionAssetState; + final List selectedAssets; + + const ControlBottomAppBar({ + super.key, + required this.onShare, + this.onFavorite, + this.onArchive, + this.onDelete, + this.onDeleteServer, + this.onDeleteLocal, + required this.onAddToAlbum, + required this.onCreateNewAlbum, + required this.onUpload, + this.onDownload, + this.onStack, + this.onEditTime, + this.onEditLocation, + this.onRemoveFromAlbum, + this.onToggleLocked, + this.selectionAssetState = const AssetSelectionState(), + this.selectedAssets = const [], + this.enabled = true, + this.unarchive = false, + this.unfavorite = false, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final hasRemote = selectionAssetState.hasRemote || selectionAssetState.hasMerged; + final hasLocal = selectionAssetState.hasLocal || selectionAssetState.hasMerged; + final trashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); + final albums = ref.watch(albumProvider).where((a) => a.isRemote).toList(); + final sharedAlbums = ref.watch(albumProvider).where((a) => a.shared).toList(); + const bottomPadding = 0.24; + final scrollController = useDraggableScrollController(); + final isInLockedView = ref.watch(inLockedViewProvider); + + void minimize() { + scrollController.animateTo(bottomPadding, duration: const Duration(milliseconds: 300), curve: Curves.easeOut); + } + + useEffect(() { + controlBottomAppBarNotifier.addListener(minimize); + return () { + controlBottomAppBarNotifier.removeListener(minimize); + }; + }, []); + + void showForceDeleteDialog(Function(bool) deleteCb, {String? alertMsg}) { + showDialog( + context: context, + builder: (BuildContext context) { + return DeleteDialog(alert: alertMsg, onDelete: () => deleteCb(true)); + }, + ); + } + + /// Show existing AddToAlbumBottomSheet + void showAddToAlbumBottomSheet() { + showModalBottomSheet( + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))), + context: context, + builder: (BuildContext _) { + return AddToAlbumBottomSheet(assets: selectedAssets); + }, + ); + } + + void handleRemoteDelete(bool force, Function(bool) deleteCb, {String? alertMsg}) { + if (!force) { + deleteCb(force); + return; + } + return showForceDeleteDialog(deleteCb, alertMsg: alertMsg); + } + + List renderActionButtons() { + return [ + ControlBoxButton( + iconData: Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded, + label: "share".tr(), + onPressed: enabled ? () => onShare(true) : null, + ), + if (!isInLockedView && hasRemote) + ControlBoxButton( + iconData: Icons.link_rounded, + label: "share_link".tr(), + onPressed: enabled ? () => onShare(false) : null, + ), + if (!isInLockedView && hasRemote && albums.isNotEmpty) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 100), + child: ControlBoxButton( + iconData: Icons.photo_album, + label: "add_to_album".tr(), + onPressed: enabled ? showAddToAlbumBottomSheet : null, + ), + ), + if (hasRemote && onArchive != null) + ControlBoxButton( + iconData: unarchive ? Icons.unarchive_outlined : Icons.archive_outlined, + label: (unarchive ? "unarchive" : "archive").tr(), + onPressed: enabled ? onArchive : null, + ), + if (hasRemote && onFavorite != null) + ControlBoxButton( + iconData: unfavorite ? Icons.favorite_border_rounded : Icons.favorite_rounded, + label: (unfavorite ? "unfavorite" : "favorite").tr(), + onPressed: enabled ? onFavorite : null, + ), + if (hasRemote && onDownload != null) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: ControlBoxButton(iconData: Icons.download, label: "download".tr(), onPressed: onDownload), + ), + if (hasLocal && hasRemote && onDelete != null && !isInLockedView) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: ControlBoxButton( + iconData: Icons.delete_sweep_outlined, + label: "delete".tr(), + onPressed: enabled ? () => handleRemoteDelete(!trashEnabled, onDelete!) : null, + onLongPressed: enabled ? () => showForceDeleteDialog(onDelete!) : null, + ), + ), + if (hasRemote && onDeleteServer != null && !isInLockedView) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 85), + child: ControlBoxButton( + iconData: Icons.cloud_off_outlined, + label: trashEnabled + ? "control_bottom_app_bar_trash_from_immich".tr() + : "control_bottom_app_bar_delete_from_immich".tr(), + onPressed: enabled + ? () => handleRemoteDelete(!trashEnabled, onDeleteServer!, alertMsg: "delete_dialog_alert_remote") + : null, + onLongPressed: enabled + ? () => showForceDeleteDialog(onDeleteServer!, alertMsg: "delete_dialog_alert_remote") + : null, + ), + ), + if (isInLockedView) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 110), + child: ControlBoxButton( + iconData: Icons.delete_forever, + label: "delete_dialog_title".tr(), + onPressed: enabled + ? () => showForceDeleteDialog(onDeleteServer!, alertMsg: "delete_dialog_alert_remote") + : null, + ), + ), + if (hasLocal && onDeleteLocal != null && !isInLockedView) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 95), + child: ControlBoxButton( + iconData: Icons.no_cell_outlined, + label: "control_bottom_app_bar_delete_from_local".tr(), + onPressed: enabled + ? () { + if (!selectionAssetState.hasLocal) { + return onDeleteLocal?.call(true); + } + + showDialog( + context: context, + builder: (BuildContext context) { + return DeleteLocalOnlyDialog(onDeleteLocal: onDeleteLocal!); + }, + ); + } + : null, + ), + ), + if (hasRemote && onEditTime != null) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 95), + child: ControlBoxButton( + iconData: Icons.edit_calendar_outlined, + label: "control_bottom_app_bar_edit_time".tr(), + onPressed: enabled ? onEditTime : null, + ), + ), + if (hasRemote && onEditLocation != null) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: ControlBoxButton( + iconData: Icons.edit_location_alt_outlined, + label: "control_bottom_app_bar_edit_location".tr(), + onPressed: enabled ? onEditLocation : null, + ), + ), + if (hasRemote) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 100), + child: ControlBoxButton( + iconData: isInLockedView ? Icons.lock_open_rounded : Icons.lock_outline_rounded, + label: isInLockedView ? "remove_from_locked_folder".tr() : "move_to_locked_folder".tr(), + onPressed: enabled ? onToggleLocked : null, + ), + ), + if (!selectionAssetState.hasLocal && selectionAssetState.selectedCount > 1 && onStack != null) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: ControlBoxButton( + iconData: Icons.filter_none_rounded, + label: "stack".tr(), + onPressed: enabled ? onStack : null, + ), + ), + if (onRemoveFromAlbum != null) + ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 90), + child: ControlBoxButton( + iconData: Icons.remove_circle_outline, + label: 'remove_from_album'.tr(), + onPressed: enabled ? onRemoveFromAlbum : null, + ), + ), + if (selectionAssetState.hasLocal) + ControlBoxButton( + iconData: Icons.backup_outlined, + label: "upload".tr(), + onPressed: enabled + ? () => showDialog( + context: context, + builder: (BuildContext context) { + return UploadDialog(onUpload: onUpload); + }, + ) + : null, + ), + ]; + } + + getInitialSize() { + if (isInLockedView) { + return bottomPadding; + } + if (hasRemote) { + return 0.35; + } + return bottomPadding; + } + + getMaxChildSize() { + if (isInLockedView) { + return bottomPadding; + } + if (hasRemote) { + return 0.65; + } + return bottomPadding; + } + + return DraggableScrollableSheet( + initialChildSize: getInitialSize(), + minChildSize: bottomPadding, + maxChildSize: getMaxChildSize(), + snap: true, + controller: scrollController, + builder: (BuildContext context, ScrollController scrollController) { + return Card( + color: context.colorScheme.surfaceContainerHigh, + surfaceTintColor: context.colorScheme.surfaceContainerHigh, + elevation: 6.0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + ), + margin: const EdgeInsets.all(0), + child: CustomScrollView( + controller: scrollController, + slivers: [ + SliverToBoxAdapter( + child: Column( + children: [ + const SizedBox(height: 12), + const CustomDraggingHandle(), + const SizedBox(height: 12), + SizedBox( + height: 120, + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: renderActionButtons(), + ), + ), + if (hasRemote && !isInLockedView) ...[ + const Divider(indent: 16, endIndent: 16, thickness: 1), + _AddToAlbumTitleRow(onCreateNewAlbum: enabled ? onCreateNewAlbum : null), + ], + ], + ), + ), + if (hasRemote && !isInLockedView) + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16), + sliver: AddToAlbumSliverList( + albums: albums, + sharedAlbums: sharedAlbums, + onAddToAlbum: onAddToAlbum, + enabled: enabled, + ), + ), + ], + ), + ); + }, + ); + } +} + +class _AddToAlbumTitleRow extends StatelessWidget { + const _AddToAlbumTitleRow({required this.onCreateNewAlbum}); + + final VoidCallback? onCreateNewAlbum; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("add_to_album", style: context.textTheme.titleSmall).tr(), + TextButton.icon( + onPressed: onCreateNewAlbum, + icon: Icon(Icons.add, color: context.primaryColor), + label: Text( + "common_create_new_album", + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 14), + ).tr(), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/delete_dialog.dart b/mobile/lib/widgets/asset_grid/delete_dialog.dart index 9b0367a463..adb22889a8 100644 --- a/mobile/lib/widgets/asset_grid/delete_dialog.dart +++ b/mobile/lib/widgets/asset_grid/delete_dialog.dart @@ -1,69 +1,81 @@ -import 'package:flutter/material.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; - -class DeleteLocalOnlyDialog extends StatelessWidget { - final void Function(bool onlyMerged) onDeleteLocal; - - const DeleteLocalOnlyDialog({super.key, required this.onDeleteLocal}); - - @override - Widget build(BuildContext context) { - void onDeleteBackedUpOnly() { - context.pop(true); - onDeleteLocal(true); - } - - void onForceDelete() { - context.pop(false); - onDeleteLocal(false); - } - - return AlertDialog( - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), - title: Text(context.t.delete_dialog_title), - content: Text(context.t.delete_dialog_alert_local_non_backed_up), - actions: [ - SizedBox( - width: double.infinity, - height: 48, - child: FilledButton( - onPressed: () => context.pop(), - style: FilledButton.styleFrom( - backgroundColor: context.colorScheme.surfaceDim, - foregroundColor: context.primaryColor, - ), - child: Text(context.t.cancel, style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - height: 48, - - child: FilledButton( - onPressed: onDeleteBackedUpOnly, - style: FilledButton.styleFrom( - backgroundColor: context.colorScheme.errorContainer, - foregroundColor: context.colorScheme.onErrorContainer, - ), - child: Text( - context.t.delete_local_dialog_ok_backed_up_only, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ), - ), - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - height: 48, - child: FilledButton( - onPressed: onForceDelete, - style: FilledButton.styleFrom(backgroundColor: Colors.red[400], foregroundColor: Colors.white), - child: Text(context.t.delete_local_dialog_ok_force, style: const TextStyle(fontWeight: FontWeight.bold)), - ), - ), - ], - ); - } -} +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; + +class DeleteDialog extends ConfirmDialog { + const DeleteDialog({super.key, String? alert, required Function onDelete}) + : super( + title: "delete_dialog_title", + content: alert ?? "delete_dialog_alert", + cancel: "cancel", + ok: "delete", + onOk: onDelete, + ); +} + +class DeleteLocalOnlyDialog extends StatelessWidget { + final void Function(bool onlyMerged) onDeleteLocal; + + const DeleteLocalOnlyDialog({super.key, required this.onDeleteLocal}); + + @override + Widget build(BuildContext context) { + void onDeleteBackedUpOnly() { + context.pop(true); + onDeleteLocal(true); + } + + void onForceDelete() { + context.pop(false); + onDeleteLocal(false); + } + + return AlertDialog( + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), + title: const Text("delete_dialog_title").tr(), + content: const Text("delete_dialog_alert_local_non_backed_up").tr(), + actions: [ + SizedBox( + width: double.infinity, + height: 48, + child: FilledButton( + onPressed: () => context.pop(), + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.surfaceDim, + foregroundColor: context.primaryColor, + ), + child: const Text("cancel", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 48, + + child: FilledButton( + onPressed: onDeleteBackedUpOnly, + style: FilledButton.styleFrom( + backgroundColor: context.colorScheme.errorContainer, + foregroundColor: context.colorScheme.onErrorContainer, + ), + child: const Text( + "delete_local_dialog_ok_backed_up_only", + style: TextStyle(fontWeight: FontWeight.bold), + ).tr(), + ), + ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + height: 48, + child: FilledButton( + onPressed: onForceDelete, + style: FilledButton.styleFrom(backgroundColor: Colors.red[400], foregroundColor: Colors.white), + child: const Text("delete_local_dialog_ok_force", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart b/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart new file mode 100644 index 0000000000..93a1d53f4e --- /dev/null +++ b/mobile/lib/widgets/asset_grid/disable_multi_select_button.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; + +class DisableMultiSelectButton extends ConsumerWidget { + const DisableMultiSelectButton({super.key, required this.onPressed, required this.selectedItemCount}); + + final Function onPressed; + final int selectedItemCount; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only(left: 16.0, top: 8.0), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ElevatedButton.icon( + onPressed: () => onPressed(), + icon: Icon(Icons.close_rounded, color: context.colorScheme.onPrimary), + label: Text( + '$selectedItemCount', + style: context.textTheme.titleMedium?.copyWith(height: 2.5, color: context.colorScheme.onPrimary), + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/draggable_scrollbar.dart b/mobile/lib/widgets/asset_grid/draggable_scrollbar.dart new file mode 100644 index 0000000000..3de52c2816 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/draggable_scrollbar.dart @@ -0,0 +1,559 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +/// Build the Scroll Thumb and label using the current configuration +typedef ScrollThumbBuilder = + Widget Function( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }); + +/// Build a Text widget using the current scroll offset +typedef LabelTextBuilder = Text Function(double offsetY); + +/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged +/// for quick navigation of the BoxScrollView. +class DraggableScrollbar extends StatefulWidget { + /// The view that will be scrolled with the scroll thumb + final CustomScrollView child; + + /// A function that builds a thumb using the current configuration + final ScrollThumbBuilder scrollThumbBuilder; + + /// The height of the scroll thumb + final double heightScrollThumb; + + /// The background color of the label and thumb + final Color backgroundColor; + + /// The amount of padding that should surround the thumb + final EdgeInsetsGeometry? padding; + + /// Determines how quickly the scrollbar will animate in and out + final Duration scrollbarAnimationDuration; + + /// How long should the thumb be visible before fading out + final Duration scrollbarTimeToFade; + + /// Build a Text widget from the current offset in the BoxScrollView + final LabelTextBuilder? labelTextBuilder; + + /// Determines box constraints for Container displaying label + final BoxConstraints? labelConstraints; + + /// The ScrollController for the BoxScrollView + final ScrollController controller; + + /// Determines scrollThumb displaying. If you draw own ScrollThumb and it is true you just don't need to use animation parameters in [scrollThumbBuilder] + final bool alwaysVisibleScrollThumb; + + DraggableScrollbar({ + super.key, + this.alwaysVisibleScrollThumb = false, + required this.heightScrollThumb, + required this.backgroundColor, + required this.scrollThumbBuilder, + required this.child, + required this.controller, + this.padding, + this.scrollbarAnimationDuration = const Duration(milliseconds: 300), + this.scrollbarTimeToFade = const Duration(milliseconds: 600), + this.labelTextBuilder, + this.labelConstraints, + }) : assert(child.scrollDirection == Axis.vertical); + + DraggableScrollbar.rrect({ + super.key, + Key? scrollThumbKey, + this.alwaysVisibleScrollThumb = false, + required this.child, + required this.controller, + this.heightScrollThumb = 48.0, + this.backgroundColor = Colors.white, + this.padding, + this.scrollbarAnimationDuration = const Duration(milliseconds: 300), + this.scrollbarTimeToFade = const Duration(milliseconds: 600), + this.labelTextBuilder, + this.labelConstraints, + }) : assert(child.scrollDirection == Axis.vertical), + scrollThumbBuilder = _thumbRRectBuilder(alwaysVisibleScrollThumb); + + DraggableScrollbar.arrows({ + super.key, + Key? scrollThumbKey, + this.alwaysVisibleScrollThumb = false, + required this.child, + required this.controller, + this.heightScrollThumb = 48.0, + this.backgroundColor = Colors.white, + this.padding, + this.scrollbarAnimationDuration = const Duration(milliseconds: 300), + this.scrollbarTimeToFade = const Duration(milliseconds: 600), + this.labelTextBuilder, + this.labelConstraints, + }) : assert(child.scrollDirection == Axis.vertical), + scrollThumbBuilder = _thumbArrowBuilder(alwaysVisibleScrollThumb); + + DraggableScrollbar.semicircle({ + super.key, + Key? scrollThumbKey, + this.alwaysVisibleScrollThumb = false, + required this.child, + required this.controller, + this.heightScrollThumb = 48.0, + this.backgroundColor = Colors.white, + this.padding, + this.scrollbarAnimationDuration = const Duration(milliseconds: 300), + this.scrollbarTimeToFade = const Duration(milliseconds: 600), + this.labelTextBuilder, + this.labelConstraints, + }) : assert(child.scrollDirection == Axis.vertical), + scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb); + + @override + DraggableScrollbarState createState() => DraggableScrollbarState(); + + static buildScrollThumbAndLabel({ + required Widget scrollThumb, + required Color backgroundColor, + required Animation? thumbAnimation, + required Animation? labelAnimation, + required Text? labelText, + required BoxConstraints? labelConstraints, + required bool alwaysVisibleScrollThumb, + }) { + var scrollThumbAndLabel = labelText == null + ? scrollThumb + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ScrollLabel( + animation: labelAnimation, + backgroundColor: backgroundColor, + constraints: labelConstraints, + child: labelText, + ), + scrollThumb, + ], + ); + + if (alwaysVisibleScrollThumb) { + return scrollThumbAndLabel; + } + return SlideFadeTransition(animation: thumbAnimation!, child: scrollThumbAndLabel); + } + + static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { + return ( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }) { + final scrollThumb = CustomPaint( + key: scrollThumbKey, + foregroundPainter: ArrowCustomPainter(Colors.white), + child: Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(height), + bottomLeft: Radius.circular(height), + topRight: const Radius.circular(4.0), + bottomRight: const Radius.circular(4.0), + ), + child: Container(constraints: BoxConstraints.tight(Size(width, height))), + ), + ); + + return buildScrollThumbAndLabel( + scrollThumb: scrollThumb, + backgroundColor: backgroundColor, + thumbAnimation: thumbAnimation, + labelAnimation: labelAnimation, + labelText: labelText, + labelConstraints: labelConstraints, + alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + ); + }; + } + + static ScrollThumbBuilder _thumbArrowBuilder(bool alwaysVisibleScrollThumb) { + return ( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }) { + final scrollThumb = ClipPath( + clipper: const ArrowClipper(), + child: Container( + height: height, + width: 20.0, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ), + ), + ); + + return buildScrollThumbAndLabel( + scrollThumb: scrollThumb, + backgroundColor: backgroundColor, + thumbAnimation: thumbAnimation, + labelAnimation: labelAnimation, + labelText: labelText, + labelConstraints: labelConstraints, + alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + ); + }; + } + + static ScrollThumbBuilder _thumbRRectBuilder(bool alwaysVisibleScrollThumb) { + return ( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }) { + final scrollThumb = Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(7.0)), + child: Container(constraints: BoxConstraints.tight(Size(16.0, height))), + ); + + return buildScrollThumbAndLabel( + scrollThumb: scrollThumb, + backgroundColor: backgroundColor, + thumbAnimation: thumbAnimation, + labelAnimation: labelAnimation, + labelText: labelText, + labelConstraints: labelConstraints, + alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + ); + }; + } +} + +class ScrollLabel extends StatelessWidget { + final Animation? animation; + final Color backgroundColor; + final Text child; + + final BoxConstraints? constraints; + static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0); + + const ScrollLabel({ + super.key, + required this.child, + required this.animation, + required this.backgroundColor, + this.constraints = _defaultConstraints, + }); + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: animation!, + child: Container( + margin: const EdgeInsets.only(right: 12.0), + child: Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + child: Container(constraints: constraints ?? _defaultConstraints, alignment: Alignment.center, child: child), + ), + ), + ); + } +} + +class DraggableScrollbarState extends State with TickerProviderStateMixin { + late double _barOffset; + late double _viewOffset; + late bool _isDragInProcess; + + late AnimationController _thumbAnimationController; + late Animation _thumbAnimation; + late AnimationController _labelAnimationController; + late Animation _labelAnimation; + Timer? _fadeoutTimer; + + @override + void initState() { + super.initState(); + _barOffset = 0.0; + _viewOffset = 0.0; + _isDragInProcess = false; + + _thumbAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration); + + _thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastOutSlowIn); + + _labelAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration); + + _labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn); + } + + @override + void dispose() { + _thumbAnimationController.dispose(); + _labelAnimationController.dispose(); + _fadeoutTimer?.cancel(); + super.dispose(); + } + + double get barMaxScrollExtent => context.size!.height - widget.heightScrollThumb; + + double get barMinScrollExtent => 0; + + double get viewMaxScrollExtent => widget.controller.position.maxScrollExtent; + + double get viewMinScrollExtent => widget.controller.position.minScrollExtent; + + @override + Widget build(BuildContext context) { + Text? labelText; + if (widget.labelTextBuilder != null && _isDragInProcess) { + labelText = widget.labelTextBuilder!(_viewOffset + _barOffset + widget.heightScrollThumb / 2); + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + //print("LayoutBuilder constraints=$constraints"); + + return NotificationListener( + onNotification: (ScrollNotification notification) { + changePosition(notification); + return false; + }, + child: Stack( + children: [ + RepaintBoundary(child: widget.child), + RepaintBoundary( + child: GestureDetector( + onVerticalDragStart: _onVerticalDragStart, + onVerticalDragUpdate: _onVerticalDragUpdate, + onVerticalDragEnd: _onVerticalDragEnd, + child: Container( + alignment: Alignment.topRight, + margin: EdgeInsets.only(top: _barOffset), + padding: widget.padding, + child: widget.scrollThumbBuilder( + widget.backgroundColor, + _thumbAnimation, + _labelAnimation, + widget.heightScrollThumb, + labelText: labelText, + labelConstraints: widget.labelConstraints, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + //scroll bar has received notification that it's view was scrolled + //so it should also changes his position + //but only if it isn't dragged + changePosition(ScrollNotification notification) { + if (_isDragInProcess) { + return; + } + + setState(() { + if (notification is ScrollUpdateNotification) { + _barOffset += getBarDelta(notification.scrollDelta!, barMaxScrollExtent, viewMaxScrollExtent); + + if (_barOffset < barMinScrollExtent) { + _barOffset = barMinScrollExtent; + } + if (_barOffset > barMaxScrollExtent) { + _barOffset = barMaxScrollExtent; + } + + _viewOffset += notification.scrollDelta!; + if (_viewOffset < widget.controller.position.minScrollExtent) { + _viewOffset = widget.controller.position.minScrollExtent; + } + if (_viewOffset > viewMaxScrollExtent) { + _viewOffset = viewMaxScrollExtent; + } + } + + if (notification is ScrollUpdateNotification || notification is OverscrollNotification) { + if (_thumbAnimationController.status != AnimationStatus.forward) { + _thumbAnimationController.forward(); + } + + _fadeoutTimer?.cancel(); + _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () { + _thumbAnimationController.reverse(); + _labelAnimationController.reverse(); + _fadeoutTimer = null; + }); + } + }); + } + + double getBarDelta(double scrollViewDelta, double barMaxScrollExtent, double viewMaxScrollExtent) { + return scrollViewDelta * barMaxScrollExtent / viewMaxScrollExtent; + } + + double getScrollViewDelta(double barDelta, double barMaxScrollExtent, double viewMaxScrollExtent) { + return barDelta * viewMaxScrollExtent / barMaxScrollExtent; + } + + void _onVerticalDragStart(DragStartDetails details) { + setState(() { + _isDragInProcess = true; + _labelAnimationController.forward(); + _fadeoutTimer?.cancel(); + }); + } + + void _onVerticalDragUpdate(DragUpdateDetails details) { + setState(() { + if (_thumbAnimationController.status != AnimationStatus.forward) { + _thumbAnimationController.forward(); + } + if (_isDragInProcess) { + _barOffset += details.delta.dy; + + if (_barOffset < barMinScrollExtent) { + _barOffset = barMinScrollExtent; + } + if (_barOffset > barMaxScrollExtent) { + _barOffset = barMaxScrollExtent; + } + + double viewDelta = getScrollViewDelta(details.delta.dy, barMaxScrollExtent, viewMaxScrollExtent); + + _viewOffset = widget.controller.position.pixels + viewDelta; + if (_viewOffset < widget.controller.position.minScrollExtent) { + _viewOffset = widget.controller.position.minScrollExtent; + } + if (_viewOffset > viewMaxScrollExtent) { + _viewOffset = viewMaxScrollExtent; + } + widget.controller.jumpTo(_viewOffset); + } + }); + } + + void _onVerticalDragEnd(DragEndDetails details) { + _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () { + _thumbAnimationController.reverse(); + _labelAnimationController.reverse(); + _fadeoutTimer = null; + }); + setState(() { + _isDragInProcess = false; + }); + } +} + +/// Draws 2 triangles like arrow up and arrow down +class ArrowCustomPainter extends CustomPainter { + Color color; + + ArrowCustomPainter(this.color); + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = color; + const width = 12.0; + const height = 8.0; + final baseX = size.width / 2; + final baseY = size.height / 2; + + canvas.drawPath(_trianglePath(Offset(baseX, baseY - 2.0), width, height, true), paint); + canvas.drawPath(_trianglePath(Offset(baseX, baseY + 2.0), width, height, false), paint); + } + + static Path _trianglePath(Offset o, double width, double height, bool isUp) { + return Path() + ..moveTo(o.dx, o.dy) + ..lineTo(o.dx + width, o.dy) + ..lineTo(o.dx + (width / 2), isUp ? o.dy - height : o.dy + height) + ..close(); + } +} + +///This cut 2 lines in arrow shape +class ArrowClipper extends CustomClipper { + const ArrowClipper(); + @override + Path getClip(Size size) { + Path path = Path(); + path.lineTo(0.0, size.height); + path.lineTo(size.width, size.height); + path.lineTo(size.width, 0.0); + path.lineTo(0.0, 0.0); + path.close(); + + double arrowWidth = 8.0; + double startPointX = (size.width - arrowWidth) / 2; + double startPointY = size.height / 2 - arrowWidth / 2; + path.moveTo(startPointX, startPointY); + path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2); + path.lineTo(startPointX + arrowWidth, startPointY); + path.lineTo(startPointX + arrowWidth, startPointY + 1.0); + path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0); + path.lineTo(startPointX, startPointY + 1.0); + path.close(); + + startPointY = size.height / 2 + arrowWidth / 2; + path.moveTo(startPointX + arrowWidth, startPointY); + path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2); + path.lineTo(startPointX, startPointY); + path.lineTo(startPointX, startPointY - 1.0); + path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0); + path.lineTo(startPointX + arrowWidth, startPointY - 1.0); + path.close(); + + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} + +class SlideFadeTransition extends StatelessWidget { + final Animation animation; + final Widget child; + + const SlideFadeTransition({super.key, required this.animation, required this.child}); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animation, + builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!, + child: SlideTransition( + position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(animation), + child: FadeTransition(opacity: animation, child: child), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/draggable_scrollbar_custom.dart b/mobile/lib/widgets/asset_grid/draggable_scrollbar_custom.dart new file mode 100644 index 0000000000..17f35311f0 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/draggable_scrollbar_custom.dart @@ -0,0 +1,490 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +/// Build the Scroll Thumb and label using the current configuration +typedef ScrollThumbBuilder = + Widget Function( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }); + +/// Build a Text widget using the current scroll offset +typedef LabelTextBuilder = Text Function(int item); + +/// A widget that will display a BoxScrollView with a ScrollThumb that can be dragged +/// for quick navigation of the BoxScrollView. +class DraggableScrollbar extends StatefulWidget { + /// The view that will be scrolled with the scroll thumb + final ScrollablePositionedList child; + + final ItemPositionsListener itemPositionsListener; + + /// A function that builds a thumb using the current configuration + final ScrollThumbBuilder scrollThumbBuilder; + + /// The height of the scroll thumb + final double heightScrollThumb; + + /// The background color of the label and thumb + final Color backgroundColor; + + /// The amount of padding that should surround the thumb + final EdgeInsetsGeometry? padding; + + /// The height offset of the thumb/bar from the bottom of the page + final double? heightOffset; + + /// Determines how quickly the scrollbar will animate in and out + final Duration scrollbarAnimationDuration; + + /// How long should the thumb be visible before fading out + final Duration scrollbarTimeToFade; + + /// Build a Text widget from the current offset in the BoxScrollView + final LabelTextBuilder? labelTextBuilder; + + /// Determines box constraints for Container displaying label + final BoxConstraints? labelConstraints; + + /// The ScrollController for the BoxScrollView + final ItemScrollController controller; + + /// Determines scrollThumb displaying. If you draw own ScrollThumb and it is true you just don't need to use animation parameters in [scrollThumbBuilder] + final bool alwaysVisibleScrollThumb; + + final Function(bool scrolling) scrollStateListener; + + DraggableScrollbar.semicircle({ + super.key, + Key? scrollThumbKey, + this.alwaysVisibleScrollThumb = false, + required this.child, + required this.controller, + required this.itemPositionsListener, + required this.scrollStateListener, + this.heightScrollThumb = 48.0, + this.backgroundColor = Colors.white, + this.padding, + this.heightOffset, + this.scrollbarAnimationDuration = const Duration(milliseconds: 300), + this.scrollbarTimeToFade = const Duration(milliseconds: 600), + this.labelTextBuilder, + this.labelConstraints, + }) : assert(child.scrollDirection == Axis.vertical), + scrollThumbBuilder = _thumbSemicircleBuilder(heightScrollThumb * 0.6, scrollThumbKey, alwaysVisibleScrollThumb); + + @override + DraggableScrollbarState createState() => DraggableScrollbarState(); + + static buildScrollThumbAndLabel({ + required Widget scrollThumb, + required Color backgroundColor, + required Animation? thumbAnimation, + required Animation? labelAnimation, + required Text? labelText, + required BoxConstraints? labelConstraints, + required bool alwaysVisibleScrollThumb, + }) { + var scrollThumbAndLabel = labelText == null + ? scrollThumb + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ScrollLabel( + animation: labelAnimation, + backgroundColor: backgroundColor, + constraints: labelConstraints, + child: labelText, + ), + scrollThumb, + ], + ); + + if (alwaysVisibleScrollThumb) { + return scrollThumbAndLabel; + } + return SlideFadeTransition(animation: thumbAnimation!, child: scrollThumbAndLabel); + } + + static ScrollThumbBuilder _thumbSemicircleBuilder(double width, Key? scrollThumbKey, bool alwaysVisibleScrollThumb) { + return ( + Color backgroundColor, + Animation thumbAnimation, + Animation labelAnimation, + double height, { + Text? labelText, + BoxConstraints? labelConstraints, + }) { + final scrollThumb = CustomPaint( + key: scrollThumbKey, + foregroundPainter: ArrowCustomPainter(Colors.white), + child: Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(height), + bottomLeft: Radius.circular(height), + topRight: const Radius.circular(4.0), + bottomRight: const Radius.circular(4.0), + ), + child: Container(constraints: BoxConstraints.tight(Size(width, height))), + ), + ); + + return buildScrollThumbAndLabel( + scrollThumb: scrollThumb, + backgroundColor: backgroundColor, + thumbAnimation: thumbAnimation, + labelAnimation: labelAnimation, + labelText: labelText, + labelConstraints: labelConstraints, + alwaysVisibleScrollThumb: alwaysVisibleScrollThumb, + ); + }; + } +} + +class ScrollLabel extends StatelessWidget { + final Animation? animation; + final Color backgroundColor; + final Text child; + + final BoxConstraints? constraints; + static const BoxConstraints _defaultConstraints = BoxConstraints.tightFor(width: 72.0, height: 28.0); + + const ScrollLabel({ + super.key, + required this.child, + required this.animation, + required this.backgroundColor, + this.constraints = _defaultConstraints, + }); + + @override + Widget build(BuildContext context) { + return FadeTransition( + opacity: animation!, + child: Container( + margin: const EdgeInsets.only(right: 12.0), + child: Material( + elevation: 4.0, + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(16.0)), + child: Container( + constraints: constraints ?? _defaultConstraints, + padding: const EdgeInsets.symmetric(horizontal: 10.0), + alignment: Alignment.center, + child: child, + ), + ), + ), + ); + } +} + +class DraggableScrollbarState extends State with TickerProviderStateMixin { + late double _barOffset; + late bool _isDragInProcess; + late int _currentItem; + + late AnimationController _thumbAnimationController; + late Animation _thumbAnimation; + late AnimationController _labelAnimationController; + late Animation _labelAnimation; + Timer? _fadeoutTimer; + + @override + void initState() { + super.initState(); + _barOffset = 0.0; + _isDragInProcess = false; + _currentItem = 0; + + _thumbAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration); + + _thumbAnimation = CurvedAnimation(parent: _thumbAnimationController, curve: Curves.fastOutSlowIn); + + _labelAnimationController = AnimationController(vsync: this, duration: widget.scrollbarAnimationDuration); + + _labelAnimation = CurvedAnimation(parent: _labelAnimationController, curve: Curves.fastOutSlowIn); + } + + @override + void dispose() { + _thumbAnimationController.dispose(); + _labelAnimationController.dispose(); + _fadeoutTimer?.cancel(); + super.dispose(); + } + + double get barMaxScrollExtent => (context.size?.height ?? 0) - widget.heightScrollThumb - (widget.heightOffset ?? 0); + + double get barMinScrollExtent => 0; + + int get maxItemCount => widget.child.itemCount; + + @override + Widget build(BuildContext context) { + Text? labelText; + if (widget.labelTextBuilder != null && _isDragInProcess) { + labelText = widget.labelTextBuilder!(_currentItem); + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + //print("LayoutBuilder constraints=$constraints"); + + return NotificationListener( + onNotification: (ScrollNotification notification) { + changePosition(notification); + return false; + }, + child: Stack( + children: [ + RepaintBoundary(child: widget.child), + RepaintBoundary( + child: GestureDetector( + onVerticalDragStart: _onVerticalDragStart, + onVerticalDragUpdate: _onVerticalDragUpdate, + onVerticalDragEnd: _onVerticalDragEnd, + child: Container( + alignment: Alignment.topRight, + margin: EdgeInsets.only(top: _barOffset), + padding: widget.padding, + child: widget.scrollThumbBuilder( + widget.backgroundColor, + _thumbAnimation, + _labelAnimation, + widget.heightScrollThumb, + labelText: labelText, + labelConstraints: widget.labelConstraints, + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + // scroll bar has received notification that it's view was scrolled + // so it should also changes his position + // but only if it isn't dragged + changePosition(ScrollNotification notification) { + if (_isDragInProcess) { + return; + } + + setState(() { + try { + int firstItemIndex = widget.itemPositionsListener.itemPositions.value.first.index; + + if (notification is ScrollUpdateNotification) { + _barOffset = (firstItemIndex / maxItemCount) * barMaxScrollExtent; + + if (_barOffset < barMinScrollExtent) { + _barOffset = barMinScrollExtent; + } + if (_barOffset > barMaxScrollExtent) { + _barOffset = barMaxScrollExtent; + } + } + + if (notification is ScrollUpdateNotification || notification is OverscrollNotification) { + if (_thumbAnimationController.status != AnimationStatus.forward) { + _thumbAnimationController.forward(); + } + + if (itemPosition < maxItemCount) { + _currentItem = itemPosition; + } + + _fadeoutTimer?.cancel(); + _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () { + _thumbAnimationController.reverse(); + _labelAnimationController.reverse(); + _fadeoutTimer = null; + }); + } + } catch (_) {} + }); + } + + void _onVerticalDragStart(DragStartDetails details) { + setState(() { + _isDragInProcess = true; + _labelAnimationController.forward(); + _fadeoutTimer?.cancel(); + }); + + widget.scrollStateListener(true); + } + + int get itemPosition { + int numberOfItems = widget.child.itemCount; + return ((_barOffset / barMaxScrollExtent) * numberOfItems).toInt(); + } + + void _jumpToBarPosition() { + if (itemPosition > maxItemCount - 1) { + return; + } + + _currentItem = itemPosition; + + /// If the bar is at the bottom but the item position is still smaller than the max item count (due to rounding error) + /// jump to the end of the list + if (barMaxScrollExtent - _barOffset < 10 && itemPosition < maxItemCount) { + widget.controller.jumpTo(index: maxItemCount); + + return; + } + + widget.controller.jumpTo(index: itemPosition); + } + + Timer? dragHaltTimer; + int lastTimerPosition = 0; + + void _onVerticalDragUpdate(DragUpdateDetails details) { + setState(() { + if (_thumbAnimationController.status != AnimationStatus.forward) { + _thumbAnimationController.forward(); + } + if (_isDragInProcess) { + _barOffset += details.delta.dy; + + if (_barOffset < barMinScrollExtent) { + _barOffset = barMinScrollExtent; + } + if (_barOffset > barMaxScrollExtent) { + _barOffset = barMaxScrollExtent; + } + + if (itemPosition != lastTimerPosition) { + lastTimerPosition = itemPosition; + dragHaltTimer?.cancel(); + widget.scrollStateListener(true); + + dragHaltTimer = Timer(const Duration(milliseconds: 500), () { + widget.scrollStateListener(false); + }); + } + + _jumpToBarPosition(); + } + }); + } + + void _onVerticalDragEnd(DragEndDetails details) { + _fadeoutTimer = Timer(widget.scrollbarTimeToFade, () { + _thumbAnimationController.reverse(); + _labelAnimationController.reverse(); + _fadeoutTimer = null; + }); + + setState(() { + _jumpToBarPosition(); + _isDragInProcess = false; + }); + + widget.scrollStateListener(false); + } +} + +/// Draws 2 triangles like arrow up and arrow down +class ArrowCustomPainter extends CustomPainter { + Color color; + + ArrowCustomPainter(this.color); + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = color; + const width = 12.0; + const height = 8.0; + final baseX = size.width / 2; + final baseY = size.height / 2; + + canvas.drawPath(_trianglePath(Offset(baseX, baseY - 2.0), width, height, true), paint); + canvas.drawPath(_trianglePath(Offset(baseX, baseY + 2.0), width, height, false), paint); + } + + static Path _trianglePath(Offset o, double width, double height, bool isUp) { + return Path() + ..moveTo(o.dx, o.dy) + ..lineTo(o.dx + width, o.dy) + ..lineTo(o.dx + (width / 2), isUp ? o.dy - height : o.dy + height) + ..close(); + } +} + +///This cut 2 lines in arrow shape +class ArrowClipper extends CustomClipper { + const ArrowClipper(); + @override + Path getClip(Size size) { + Path path = Path(); + path.lineTo(0.0, size.height); + path.lineTo(size.width, size.height); + path.lineTo(size.width, 0.0); + path.lineTo(0.0, 0.0); + path.close(); + + double arrowWidth = 8.0; + double startPointX = (size.width - arrowWidth) / 2; + double startPointY = size.height / 2 - arrowWidth / 2; + path.moveTo(startPointX, startPointY); + path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2); + path.lineTo(startPointX + arrowWidth, startPointY); + path.lineTo(startPointX + arrowWidth, startPointY + 1.0); + path.lineTo(startPointX + arrowWidth / 2, startPointY - arrowWidth / 2 + 1.0); + path.lineTo(startPointX, startPointY + 1.0); + path.close(); + + startPointY = size.height / 2 + arrowWidth / 2; + path.moveTo(startPointX + arrowWidth, startPointY); + path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2); + path.lineTo(startPointX, startPointY); + path.lineTo(startPointX, startPointY - 1.0); + path.lineTo(startPointX + arrowWidth / 2, startPointY + arrowWidth / 2 - 1.0); + path.lineTo(startPointX + arrowWidth, startPointY - 1.0); + path.close(); + + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} + +class SlideFadeTransition extends StatelessWidget { + final Animation animation; + final Widget child; + + const SlideFadeTransition({super.key, required this.animation, required this.child}); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animation, + builder: (context, child) => animation.value == 0.0 ? const SizedBox() : child!, + child: SlideTransition( + position: Tween(begin: const Offset(0.3, 0.0), end: const Offset(0.0, 0.0)).animate(animation), + child: FadeTransition(opacity: animation, child: child), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/group_divider_title.dart b/mobile/lib/widgets/asset_grid/group_divider_title.dart new file mode 100644 index 0000000000..1464c941f0 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/group_divider_title.dart @@ -0,0 +1,84 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; + +class GroupDividerTitle extends HookConsumerWidget { + const GroupDividerTitle({ + super.key, + required this.text, + required this.multiselectEnabled, + required this.onSelect, + required this.onDeselect, + required this.selected, + }); + + final String text; + final bool multiselectEnabled; + final Function onSelect; + final Function onDeselect; + final bool selected; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final appSettingService = ref.watch(appSettingsServiceProvider); + final groupBy = useState(GroupAssetsBy.day); + + useEffect(() { + groupBy.value = GroupAssetsBy.values[appSettingService.getSetting(AppSettingsEnum.groupAssetsBy)]; + return null; + }, []); + + void handleTitleIconClick() { + ref.read(hapticFeedbackProvider.notifier).heavyImpact(); + if (selected) { + onDeselect(); + } else { + onSelect(); + } + } + + return Padding( + padding: EdgeInsets.only( + top: groupBy.value == GroupAssetsBy.month ? 32.0 : 16.0, + bottom: 16.0, + left: 12.0, + right: 12.0, + ), + child: Row( + children: [ + Text( + text, + style: groupBy.value == GroupAssetsBy.month + ? context.textTheme.bodyLarge?.copyWith(fontSize: 24.0) + : context.textTheme.labelLarge?.copyWith( + color: context.textTheme.labelLarge?.color?.withAlpha(250), + fontWeight: FontWeight.w500, + ), + ), + const Spacer(), + GestureDetector( + onTap: handleTitleIconClick, + child: multiselectEnabled && selected + ? Icon( + Icons.check_circle_rounded, + color: context.primaryColor, + semanticLabel: "unselect_all_in".tr(namedArgs: {"group": text}), + ) + : Icon( + Icons.check_circle_outline_rounded, + color: context.colorScheme.onSurfaceSecondary, + semanticLabel: "select_all_in".tr(namedArgs: {"group": text}), + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid.dart new file mode 100644 index 0000000000..ab6b350a7b --- /dev/null +++ b/mobile/lib/widgets/asset_grid/immich_asset_grid.dart @@ -0,0 +1,135 @@ +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid_view.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +class ImmichAssetGrid extends HookConsumerWidget { + final int? assetsPerRow; + final double margin; + final bool? showStorageIndicator; + final ImmichAssetGridSelectionListener? listener; + final bool selectionActive; + final List? assets; + final RenderList? renderList; + final Future Function()? onRefresh; + final Set? preselectedAssets; + final bool canDeselect; + final bool? dynamicLayout; + final bool showMultiSelectIndicator; + final void Function(Iterable itemPositions)? visibleItemsListener; + final Widget? topWidget; + final bool shrinkWrap; + final bool showDragScroll; + final bool showDragScrollLabel; + final bool showStack; + + const ImmichAssetGrid({ + super.key, + this.assets, + this.onRefresh, + this.renderList, + this.assetsPerRow, + this.showStorageIndicator, + this.listener, + this.margin = 2.0, + this.selectionActive = false, + this.preselectedAssets, + this.canDeselect = true, + this.dynamicLayout, + this.showMultiSelectIndicator = true, + this.visibleItemsListener, + this.topWidget, + this.shrinkWrap = false, + this.showDragScroll = true, + this.showDragScrollLabel = true, + this.showStack = false, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + var settings = ref.watch(appSettingsServiceProvider); + + final perRow = useState(assetsPerRow ?? settings.getSetting(AppSettingsEnum.tilesPerRow)!); + final scaleFactor = useState(7.0 - perRow.value); + final baseScaleFactor = useState(7.0 - perRow.value); + + /// assets need different hero tags across tabs / modals + /// otherwise, hero animations are performed across tabs (looks buggy!) + int heroOffset() { + const int range = 1152921504606846976; // 2^60 + final tabScope = TabsRouterScope.of(context); + if (tabScope != null) { + final int tabIndex = tabScope.controller.activeIndex; + return tabIndex * range; + } + return range * 7; + } + + Widget buildAssetGridView(RenderList renderList) { + return RawGestureDetector( + gestures: { + CustomScaleGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => CustomScaleGestureRecognizer(), + (CustomScaleGestureRecognizer scale) { + scale.onStart = (details) { + baseScaleFactor.value = scaleFactor.value; + }; + + scale.onUpdate = (details) { + scaleFactor.value = max(min(5.0, baseScaleFactor.value * details.scale), 1.0); + if (7 - scaleFactor.value.toInt() != perRow.value) { + perRow.value = 7 - scaleFactor.value.toInt(); + settings.setSetting(AppSettingsEnum.tilesPerRow, perRow.value); + } + }; + }, + ), + }, + child: ImmichAssetGridView( + onRefresh: onRefresh, + assetsPerRow: perRow.value, + listener: listener, + showStorageIndicator: showStorageIndicator ?? settings.getSetting(AppSettingsEnum.storageIndicator), + renderList: renderList, + margin: margin, + selectionActive: selectionActive, + preselectedAssets: preselectedAssets, + canDeselect: canDeselect, + dynamicLayout: dynamicLayout ?? settings.getSetting(AppSettingsEnum.dynamicLayout), + showMultiSelectIndicator: showMultiSelectIndicator, + visibleItemsListener: visibleItemsListener, + topWidget: topWidget, + heroOffset: heroOffset(), + shrinkWrap: shrinkWrap, + showDragScroll: showDragScroll, + showStack: showStack, + showLabel: showDragScrollLabel, + ), + ); + } + + if (renderList != null) return buildAssetGridView(renderList!); + + final renderListFuture = ref.watch(assetsTimelineProvider(assets!)); + return renderListFuture.widgetWhen(onData: (renderList) => buildAssetGridView(renderList)); + } +} + +/// accepts a gesture even though it should reject it (because child won) +class CustomScaleGestureRecognizer extends ScaleGestureRecognizer { + @override + void rejectGesture(int pointer) { + acceptGesture(pointer); + } +} diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart new file mode 100644 index 0000000000..c323c573b4 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart @@ -0,0 +1,828 @@ +import 'dart:collection'; +import 'dart:developer'; +import 'dart:math'; + +import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart'; +import 'package:immich_mobile/widgets/asset_grid/disable_multi_select_button.dart'; +import 'package:immich_mobile/widgets/asset_grid/draggable_scrollbar_custom.dart'; +import 'package:immich_mobile/widgets/asset_grid/group_divider_title.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +typedef ImmichAssetGridSelectionListener = void Function(bool, Set); + +class ImmichAssetGridView extends ConsumerStatefulWidget { + final RenderList renderList; + final int assetsPerRow; + final double margin; + final bool showStorageIndicator; + final ImmichAssetGridSelectionListener? listener; + final bool selectionActive; + final Future Function()? onRefresh; + final Set? preselectedAssets; + final bool canDeselect; + final bool dynamicLayout; + final bool showMultiSelectIndicator; + final void Function(Iterable itemPositions)? visibleItemsListener; + final Widget? topWidget; + final int heroOffset; + final bool shrinkWrap; + final bool showDragScroll; + final bool showStack; + final bool showLabel; + + const ImmichAssetGridView({ + super.key, + required this.renderList, + required this.assetsPerRow, + required this.showStorageIndicator, + this.listener, + this.margin = 5.0, + this.selectionActive = false, + this.onRefresh, + this.preselectedAssets, + this.canDeselect = true, + this.dynamicLayout = true, + this.showMultiSelectIndicator = true, + this.visibleItemsListener, + this.topWidget, + this.heroOffset = 0, + this.shrinkWrap = false, + this.showDragScroll = true, + this.showStack = false, + this.showLabel = true, + }); + + @override + createState() { + return ImmichAssetGridViewState(); + } +} + +class ImmichAssetGridViewState extends ConsumerState { + final ItemScrollController _itemScrollController = ItemScrollController(); + final ScrollOffsetController _scrollOffsetController = ScrollOffsetController(); + final ItemPositionsListener _itemPositionsListener = ItemPositionsListener.create(); + late final KeepAliveLink currentAssetLink; + + /// The timestamp when the haptic feedback was last invoked + int _hapticFeedbackTS = 0; + DateTime? _prevItemTime; + bool _scrolling = false; + final Set _selectedAssets = LinkedHashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id); + + bool _dragging = false; + int? _dragAnchorAssetIndex; + int? _dragAnchorSectionIndex; + final Set _draggedAssets = HashSet(equals: (a, b) => a.id == b.id, hashCode: (a) => a.id); + + ScrollPhysics? _scrollPhysics; + + Set _getSelectedAssets() { + return Set.from(_selectedAssets); + } + + void _callSelectionListener(bool selectionActive) { + widget.listener?.call(selectionActive, _getSelectedAssets()); + } + + void _selectAssets(List assets) { + setState(() { + if (_dragging) { + _draggedAssets.addAll(assets); + } + _selectedAssets.addAll(assets); + _callSelectionListener(true); + }); + } + + void _deselectAssets(List assets) { + final assetsToDeselect = assets.where( + (a) => widget.canDeselect || !(widget.preselectedAssets?.contains(a) ?? false), + ); + + setState(() { + _selectedAssets.removeAll(assetsToDeselect); + if (_dragging) { + _draggedAssets.removeAll(assetsToDeselect); + } + _callSelectionListener(_selectedAssets.isNotEmpty); + }); + } + + void _deselectAll() { + setState(() { + _selectedAssets.clear(); + _dragAnchorAssetIndex = null; + _dragAnchorSectionIndex = null; + _draggedAssets.clear(); + _dragging = false; + if (!widget.canDeselect && widget.preselectedAssets != null && widget.preselectedAssets!.isNotEmpty) { + _selectedAssets.addAll(widget.preselectedAssets!); + } + _callSelectionListener(false); + }); + } + + bool _allAssetsSelected(List assets) { + return widget.selectionActive && assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null; + } + + Future _scrollToIndex(int index) async { + // if the index is so far down, that the end of the list is reached on the screen + // the scroll_position widget crashes. This is a workaround to prevent this. + // If the index is within the last 10 elements, we jump instead of scrolling. + if (widget.renderList.elements.length <= index + 10) { + _itemScrollController.jumpTo(index: index); + return; + } + await _itemScrollController.scrollTo(index: index, alignment: 0, duration: const Duration(milliseconds: 500)); + } + + Widget _itemBuilder(BuildContext c, int position) { + int index = position; + if (widget.topWidget != null) { + if (index == 0) { + return widget.topWidget!; + } + index--; + } + + final section = widget.renderList.elements[index]; + return _Section( + showStorageIndicator: widget.showStorageIndicator, + selectedAssets: _selectedAssets, + selectionActive: widget.selectionActive, + sectionIndex: index, + section: section, + margin: widget.margin, + renderList: widget.renderList, + assetsPerRow: widget.assetsPerRow, + scrolling: _scrolling, + dynamicLayout: widget.dynamicLayout, + selectAssets: _selectAssets, + deselectAssets: _deselectAssets, + allAssetsSelected: _allAssetsSelected, + showStack: widget.showStack, + heroOffset: widget.heroOffset, + onAssetTap: (asset) { + ref.read(currentAssetProvider.notifier).set(asset); + ref.read(isPlayingMotionVideoProvider.notifier).playing = false; + if (asset.isVideo) { + ref.read(showControlsProvider.notifier).show = false; + } + }, + ); + } + + Text _labelBuilder(int pos) { + final maxLength = widget.renderList.elements.length; + if (pos < 0 || pos >= maxLength) { + return const Text(""); + } + + final date = widget.renderList.elements[pos % maxLength].date; + + return Text( + DateFormat.yMMMM().format(date), + style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ); + } + + Widget _buildMultiSelectIndicator() { + return DisableMultiSelectButton(onPressed: () => _deselectAll(), selectedItemCount: _selectedAssets.length); + } + + Widget _buildAssetGrid() { + final useDragScrolling = widget.showDragScroll && widget.renderList.totalAssets >= 20; + + void dragScrolling(bool active) { + if (active != _scrolling) { + setState(() { + _scrolling = active; + }); + } + } + + bool appBarOffset() { + return (ref.watch(tabProvider).index == 0 && ModalRoute.of(context)?.settings.name == TabControllerRoute.name) || + (ModalRoute.of(context)?.settings.name == AlbumViewerRoute.name); + } + + final listWidget = ScrollablePositionedList.builder( + padding: EdgeInsets.only(top: appBarOffset() ? 60 : 0, bottom: 220), + itemBuilder: _itemBuilder, + itemPositionsListener: _itemPositionsListener, + physics: _scrollPhysics, + itemScrollController: _itemScrollController, + scrollOffsetController: _scrollOffsetController, + itemCount: widget.renderList.elements.length + (widget.topWidget != null ? 1 : 0), + addRepaintBoundaries: true, + shrinkWrap: widget.shrinkWrap, + ); + + final child = (useDragScrolling && ModalRoute.of(context) != null) + ? DraggableScrollbar.semicircle( + scrollStateListener: dragScrolling, + itemPositionsListener: _itemPositionsListener, + controller: _itemScrollController, + backgroundColor: context.isDarkTheme + ? context.colorScheme.primary.darken(amount: .5) + : context.colorScheme.primary, + labelTextBuilder: widget.showLabel ? _labelBuilder : null, + padding: appBarOffset() ? const EdgeInsets.only(top: 60) : const EdgeInsets.only(), + heightOffset: appBarOffset() ? 60 : 0, + labelConstraints: const BoxConstraints(maxHeight: 28), + scrollbarAnimationDuration: const Duration(milliseconds: 300), + scrollbarTimeToFade: const Duration(milliseconds: 1000), + child: listWidget, + ) + : listWidget; + + return widget.onRefresh == null + ? child + : appBarOffset() + ? RefreshIndicator(onRefresh: widget.onRefresh!, edgeOffset: 30, child: child) + : RefreshIndicator(onRefresh: widget.onRefresh!, child: child); + } + + void _scrollToDate() { + final date = scrollToDateNotifierProvider.value; + if (date == null) { + ImmichToast.show( + context: context, + msg: "Scroll To Date failed, date is null.", + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + return; + } + + // Search for the index of the exact date in the list + var index = widget.renderList.elements.indexWhere( + (e) => e.date.year == date.year && e.date.month == date.month && e.date.day == date.day, + ); + + // If the exact date is not found, the timeline is grouped by month, + // thus we search for the month + if (index == -1) { + index = widget.renderList.elements.indexWhere((e) => e.date.year == date.year && e.date.month == date.month); + } + + if (index < widget.renderList.elements.length) { + // Not sure why the index is shifted, but it works. :3 + _scrollToIndex(index + 1); + } else { + ImmichToast.show( + context: context, + msg: "The date (${DateFormat.yMd().format(date)}) could not be found in the timeline.", + gravity: ToastGravity.BOTTOM, + toastType: ToastType.error, + ); + } + } + + @override + void didUpdateWidget(ImmichAssetGridView oldWidget) { + super.didUpdateWidget(oldWidget); + if (!widget.selectionActive) { + setState(() { + _selectedAssets.clear(); + }); + } + } + + @override + void initState() { + super.initState(); + currentAssetLink = ref.read(currentAssetProvider.notifier).ref.keepAlive(); + scrollToTopNotifierProvider.addListener(_scrollToTop); + scrollToDateNotifierProvider.addListener(_scrollToDate); + + if (widget.visibleItemsListener != null) { + _itemPositionsListener.itemPositions.addListener(_positionListener); + } + if (widget.preselectedAssets != null) { + _selectedAssets.addAll(widget.preselectedAssets!); + } + + _itemPositionsListener.itemPositions.addListener(_hapticsListener); + } + + @override + void dispose() { + scrollToTopNotifierProvider.removeListener(_scrollToTop); + scrollToDateNotifierProvider.removeListener(_scrollToDate); + if (widget.visibleItemsListener != null) { + _itemPositionsListener.itemPositions.removeListener(_positionListener); + } + _itemPositionsListener.itemPositions.removeListener(_hapticsListener); + currentAssetLink.close(); + super.dispose(); + } + + void _positionListener() { + final values = _itemPositionsListener.itemPositions.value; + widget.visibleItemsListener?.call(values); + } + + void _hapticsListener() { + /// throttle interval for the haptic feedback in microseconds. + /// Currently set to 100ms. + const feedbackInterval = 100000; + + final values = _itemPositionsListener.itemPositions.value; + final start = values.firstOrNull; + + if (start != null) { + final pos = start.index; + final maxLength = widget.renderList.elements.length; + if (pos < 0 || pos >= maxLength) { + return; + } + + final date = widget.renderList.elements[pos].date; + + // only provide the feedback if the prev. date is known. + // Otherwise the app would provide the haptic feedback + // on startup. + if (_prevItemTime == null) { + _prevItemTime = date; + } else if (_prevItemTime?.year != date.year || _prevItemTime?.month != date.month) { + _prevItemTime = date; + + final now = Timeline.now; + if (now > (_hapticFeedbackTS + feedbackInterval)) { + _hapticFeedbackTS = now; + ref.read(hapticFeedbackProvider.notifier).mediumImpact(); + } + } + } + } + + void _scrollToTop() { + // for some reason, this is necessary as well in order + // to correctly reposition the drag thumb scroll bar + _itemScrollController.jumpTo(index: 0); + _itemScrollController.scrollTo(index: 0, duration: const Duration(milliseconds: 200)); + } + + void _setDragStartIndex(AssetIndex index) { + setState(() { + _scrollPhysics = const ClampingScrollPhysics(); + _dragAnchorAssetIndex = index.rowIndex; + _dragAnchorSectionIndex = index.sectionIndex; + _dragging = true; + }); + } + + void _stopDrag() { + WidgetsBinding.instance.addPostFrameCallback((_) { + // Update the physics post frame to prevent sudden change in physics on iOS. + setState(() { + _scrollPhysics = null; + }); + }); + setState(() { + _dragging = false; + _draggedAssets.clear(); + }); + } + + void _dragDragScroll(ScrollDirection direction) { + _scrollOffsetController.animateScroll( + offset: direction == ScrollDirection.forward ? 175 : -175, + duration: const Duration(milliseconds: 125), + ); + } + + void _handleDragAssetEnter(AssetIndex index) { + if (_dragAnchorSectionIndex == null || _dragAnchorAssetIndex == null) { + return; + } + + final dragAnchorSectionIndex = _dragAnchorSectionIndex!; + final dragAnchorAssetIndex = _dragAnchorAssetIndex!; + + late final int startSectionIndex; + late final int startSectionAssetIndex; + late final int endSectionIndex; + late final int endSectionAssetIndex; + + if (index.sectionIndex < dragAnchorSectionIndex) { + startSectionIndex = index.sectionIndex; + startSectionAssetIndex = index.rowIndex; + endSectionIndex = dragAnchorSectionIndex; + endSectionAssetIndex = dragAnchorAssetIndex; + } else if (index.sectionIndex > dragAnchorSectionIndex) { + startSectionIndex = dragAnchorSectionIndex; + startSectionAssetIndex = dragAnchorAssetIndex; + endSectionIndex = index.sectionIndex; + endSectionAssetIndex = index.rowIndex; + } else { + startSectionIndex = dragAnchorSectionIndex; + endSectionIndex = dragAnchorSectionIndex; + + // If same section, assign proper start / end asset Index + if (dragAnchorAssetIndex < index.rowIndex) { + startSectionAssetIndex = dragAnchorAssetIndex; + endSectionAssetIndex = index.rowIndex; + } else { + startSectionAssetIndex = index.rowIndex; + endSectionAssetIndex = dragAnchorAssetIndex; + } + } + + final selectedAssets = {}; + var currentSectionIndex = startSectionIndex; + while (currentSectionIndex < endSectionIndex) { + final section = widget.renderList.elements.elementAtOrNull(currentSectionIndex); + if (section == null) continue; + + final sectionAssets = widget.renderList.loadAssets(section.offset, section.count); + + if (currentSectionIndex == startSectionIndex) { + selectedAssets.addAll(sectionAssets.slice(startSectionAssetIndex, sectionAssets.length)); + } else { + selectedAssets.addAll(sectionAssets); + } + + currentSectionIndex += 1; + } + + final section = widget.renderList.elements.elementAtOrNull(endSectionIndex); + if (section != null) { + final sectionAssets = widget.renderList.loadAssets(section.offset, section.count); + if (startSectionIndex == endSectionIndex) { + selectedAssets.addAll(sectionAssets.slice(startSectionAssetIndex, endSectionAssetIndex + 1)); + } else { + selectedAssets.addAll(sectionAssets.slice(0, endSectionAssetIndex + 1)); + } + } + + _deselectAssets(_draggedAssets.toList()); + _draggedAssets.clear(); + _draggedAssets.addAll(selectedAssets); + _selectAssets(_draggedAssets.toList()); + } + + @override + Widget build(BuildContext context) { + return PopScope( + canPop: !(widget.selectionActive && _selectedAssets.isNotEmpty), + onPopInvokedWithResult: (didPop, _) { + if (didPop) { + return; + } else { + /// `preselectedAssets` is only present when opening the asset grid from the + /// "add to album" button. + /// + /// `_selectedAssets` includes `preselectedAssets` on initialization. + if (_selectedAssets.length > (widget.preselectedAssets?.length ?? 0)) { + /// `_deselectAll` only deselects the selected assets, + /// doesn't affect the preselected ones. + _deselectAll(); + return; + } else { + Navigator.of(context).canPop() ? Navigator.of(context).pop() : null; + } + } + }, + child: Stack( + children: [ + AssetDragRegion( + onStart: _setDragStartIndex, + onAssetEnter: _handleDragAssetEnter, + onEnd: _stopDrag, + onScroll: _dragDragScroll, + onScrollStart: () => + WidgetsBinding.instance.addPostFrameCallback((_) => controlBottomAppBarNotifier.minimize()), + child: _buildAssetGrid(), + ), + if (widget.showMultiSelectIndicator && widget.selectionActive) _buildMultiSelectIndicator(), + ], + ), + ); + } +} + +/// A single row of all placeholder widgets +class _PlaceholderRow extends StatelessWidget { + final int number; + final double width; + final double height; + final double margin; + + const _PlaceholderRow({ + super.key, + required this.number, + required this.width, + required this.height, + required this.margin, + }); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + for (int i = 0; i < number; i++) + ThumbnailPlaceholder( + key: ValueKey(i), + width: width, + height: height, + margin: EdgeInsets.only(bottom: margin, right: i + 1 == number ? 0.0 : margin), + ), + ], + ); + } +} + +/// A section for the render grid +class _Section extends StatelessWidget { + final RenderAssetGridElement section; + final int sectionIndex; + final Set selectedAssets; + final bool scrolling; + final double margin; + final int assetsPerRow; + final RenderList renderList; + final bool selectionActive; + final bool dynamicLayout; + final void Function(List) selectAssets; + final void Function(List) deselectAssets; + final bool Function(List) allAssetsSelected; + final bool showStack; + final int heroOffset; + final bool showStorageIndicator; + final void Function(Asset) onAssetTap; + + const _Section({ + required this.section, + required this.sectionIndex, + required this.scrolling, + required this.margin, + required this.assetsPerRow, + required this.renderList, + required this.selectionActive, + required this.dynamicLayout, + required this.selectAssets, + required this.deselectAssets, + required this.allAssetsSelected, + required this.selectedAssets, + required this.showStack, + required this.heroOffset, + required this.showStorageIndicator, + required this.onAssetTap, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final width = constraints.maxWidth / assetsPerRow - margin * (assetsPerRow - 1) / assetsPerRow; + final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow; + final List assetsToRender = scrolling ? [] : renderList.loadAssets(section.offset, section.count); + return Column( + key: ValueKey(section.offset), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (section.type == RenderAssetGridElementType.monthTitle) _MonthTitle(date: section.date), + if (section.type == RenderAssetGridElementType.groupDividerTitle || + section.type == RenderAssetGridElementType.monthTitle) + _Title( + selectionActive: selectionActive, + title: section.title!, + assets: scrolling ? [] : renderList.loadAssets(section.offset, section.totalCount), + allAssetsSelected: allAssetsSelected, + selectAssets: selectAssets, + deselectAssets: deselectAssets, + ), + for (int i = 0; i < rows; i++) + scrolling + ? _PlaceholderRow( + key: ValueKey(i), + number: i + 1 == rows ? section.count - i * assetsPerRow : assetsPerRow, + width: width, + height: width, + margin: margin, + ) + : _AssetRow( + key: ValueKey(i), + rowStartIndex: i * assetsPerRow, + sectionIndex: sectionIndex, + assets: assetsToRender.nestedSlice(i * assetsPerRow, min((i + 1) * assetsPerRow, section.count)), + absoluteOffset: section.offset + i * assetsPerRow, + width: width, + assetsPerRow: assetsPerRow, + margin: margin, + dynamicLayout: dynamicLayout, + renderList: renderList, + selectedAssets: selectedAssets, + isSelectionActive: selectionActive, + showStack: showStack, + heroOffset: heroOffset, + showStorageIndicator: showStorageIndicator, + selectionActive: selectionActive, + onSelect: (asset) => selectAssets([asset]), + onDeselect: (asset) => deselectAssets([asset]), + onAssetTap: onAssetTap, + ), + ], + ); + }, + ); + } +} + +/// The month title row for a section +class _MonthTitle extends StatelessWidget { + final DateTime date; + + const _MonthTitle({required this.date}); + + @override + Widget build(BuildContext context) { + final monthFormat = DateTime.now().year == date.year ? DateFormat.MMMM() : DateFormat.yMMMM(); + final String title = monthFormat.format(date); + return Padding( + key: Key("month-$title"), + padding: const EdgeInsets.only(left: 12.0, top: 24.0), + child: Text( + toBeginningOfSentenceCase(title, context.locale.languageCode), + style: const TextStyle(fontSize: 26, fontWeight: FontWeight.w500), + ), + ); + } +} + +/// A title row +class _Title extends StatelessWidget { + final String title; + final List assets; + final bool selectionActive; + final void Function(List) selectAssets; + final void Function(List) deselectAssets; + final bool Function(List) allAssetsSelected; + + const _Title({ + required this.title, + required this.assets, + required this.selectionActive, + required this.selectAssets, + required this.deselectAssets, + required this.allAssetsSelected, + }); + + @override + Widget build(BuildContext context) { + return GroupDividerTitle( + text: toBeginningOfSentenceCase(title, context.locale.languageCode), + multiselectEnabled: selectionActive, + onSelect: () => selectAssets(assets), + onDeselect: () => deselectAssets(assets), + selected: allAssetsSelected(assets), + ); + } +} + +/// The row of assets +class _AssetRow extends StatelessWidget { + final List assets; + final int rowStartIndex; + final int sectionIndex; + final Set selectedAssets; + final int absoluteOffset; + final double width; + final bool dynamicLayout; + final double margin; + final int assetsPerRow; + final RenderList renderList; + final bool selectionActive; + final bool showStorageIndicator; + final int heroOffset; + final bool showStack; + final void Function(Asset) onAssetTap; + final void Function(Asset)? onSelect; + final void Function(Asset)? onDeselect; + final bool isSelectionActive; + + const _AssetRow({ + super.key, + required this.rowStartIndex, + required this.sectionIndex, + required this.assets, + required this.absoluteOffset, + required this.width, + required this.dynamicLayout, + required this.margin, + required this.assetsPerRow, + required this.renderList, + required this.selectionActive, + required this.showStorageIndicator, + required this.heroOffset, + required this.showStack, + required this.isSelectionActive, + required this.selectedAssets, + required this.onAssetTap, + this.onSelect, + this.onDeselect, + }); + + @override + Widget build(BuildContext context) { + // Default: All assets have the same width + final widthDistribution = List.filled(assets.length, 1.0); + + if (dynamicLayout) { + final aspectRatios = assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList(); + final meanAspectRatio = aspectRatios.sum / assets.length; + + // 1: mean width + // 0.5: width < mean - threshold + // 1.5: width > mean + threshold + final arConfiguration = aspectRatios.map((e) { + if (e - meanAspectRatio > 0.3) return 1.5; + if (e - meanAspectRatio < -0.3) return 0.5; + return 1.0; + }); + + // Normalize: + final sum = arConfiguration.sum; + widthDistribution.setRange(0, widthDistribution.length, arConfiguration.map((e) => (e * assets.length) / sum)); + } + return Row( + key: key, + children: assets.mapIndexed((int index, Asset asset) { + final bool last = index + 1 == assetsPerRow; + final isSelected = isSelectionActive && selectedAssets.contains(asset); + return Container( + width: width * widthDistribution[index], + height: width, + margin: EdgeInsets.only(bottom: margin, right: last ? 0.0 : margin), + child: GestureDetector( + onTap: () { + if (selectionActive) { + if (isSelected) { + onDeselect?.call(asset); + } else { + onSelect?.call(asset); + } + } else { + final asset = renderList.loadAsset(absoluteOffset + index); + onAssetTap(asset); + context.pushRoute( + GalleryViewerRoute( + renderList: renderList, + initialIndex: absoluteOffset + index, + heroOffset: heroOffset, + showStack: showStack, + ), + ); + } + }, + onLongPress: () { + onSelect?.call(asset); + HapticFeedback.heavyImpact(); + }, + child: AssetIndexWrapper( + rowIndex: rowStartIndex + index, + sectionIndex: sectionIndex, + child: ThumbnailImage( + asset: asset, + multiselectEnabled: selectionActive, + isSelected: isSelectionActive && selectedAssets.contains(asset), + showStorageIndicator: showStorageIndicator, + heroOffset: heroOffset, + showStack: showStack, + ), + ), + ), + ); + }).toList(), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/multiselect_grid.dart b/mobile/lib/widgets/asset_grid/multiselect_grid.dart new file mode 100644 index 0000000000..c0d8a6bea2 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/multiselect_grid.dart @@ -0,0 +1,458 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/asset_selection_state.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/multiselect.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:immich_mobile/services/stack.service.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class MultiselectGrid extends HookConsumerWidget { + const MultiselectGrid({ + super.key, + required this.renderListProvider, + this.onRefresh, + this.buildLoadingIndicator, + this.onRemoveFromAlbum, + this.topWidget, + this.stackEnabled = false, + this.dragScrollLabelEnabled = true, + this.archiveEnabled = false, + this.deleteEnabled = true, + this.favoriteEnabled = true, + this.editEnabled = false, + this.unarchive = false, + this.unfavorite = false, + this.downloadEnabled = true, + this.emptyIndicator, + }); + + final ProviderListenable> renderListProvider; + final Future Function()? onRefresh; + final Widget Function()? buildLoadingIndicator; + final Future Function(Iterable)? onRemoveFromAlbum; + final Widget? topWidget; + final bool stackEnabled; + final bool dragScrollLabelEnabled; + final bool archiveEnabled; + final bool unarchive; + final bool deleteEnabled; + final bool downloadEnabled; + final bool favoriteEnabled; + final bool unfavorite; + final bool editEnabled; + final Widget? emptyIndicator; + Widget buildDefaultLoadingIndicator() => const Center(child: CircularProgressIndicator()); + + Widget buildEmptyIndicator() => emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr()); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final multiselectEnabled = ref.watch(multiselectProvider.notifier); + final selectionEnabledHook = useState(false); + final selectionAssetState = useState(const AssetSelectionState()); + + final selection = useState({}); + final currentUser = ref.watch(currentUserProvider); + final processing = useProcessingOverlay(); + + useEffect(() { + selectionEnabledHook.addListener(() { + multiselectEnabled.state = selectionEnabledHook.value; + }); + + return () { + // This does not work in tests + if (kReleaseMode) { + selectionEnabledHook.dispose(); + } + }; + }, []); + + void selectionListener(bool multiselect, Set selectedAssets) { + selectionEnabledHook.value = multiselect; + selection.value = selectedAssets; + selectionAssetState.value = AssetSelectionState.fromSelection(selectedAssets); + } + + errorBuilder(String? msg) => msg != null && msg.isNotEmpty + ? () => ImmichToast.show(context: context, msg: msg, gravity: ToastGravity.BOTTOM) + : null; + + Iterable ownedRemoteSelection({String? localErrorMessage, String? ownerErrorMessage}) { + final assets = selection.value; + return assets + .remoteOnly(errorCallback: errorBuilder(localErrorMessage)) + .ownedOnly(currentUser, errorCallback: errorBuilder(ownerErrorMessage)); + } + + Iterable remoteSelection({String? errorMessage}) => + selection.value.remoteOnly(errorCallback: errorBuilder(errorMessage)); + + void onShareAssets(bool shareLocal) { + processing.value = true; + if (shareLocal) { + // Share = Download + Send to OS specific share sheet + handleShareAssets(ref, context, selection.value); + } else { + final ids = remoteSelection(errorMessage: "home_page_share_err_local".tr()).map((e) => e.remoteId!); + context.pushRoute(SharedLinkEditRoute(assetsList: ids.toList())); + } + processing.value = false; + selectionEnabledHook.value = false; + } + + void onFavoriteAssets() async { + processing.value = true; + try { + final remoteAssets = ownedRemoteSelection( + localErrorMessage: 'home_page_favorite_err_local'.tr(), + ownerErrorMessage: 'home_page_favorite_err_partner'.tr(), + ); + if (remoteAssets.isNotEmpty) { + await handleFavoriteAssets(ref, context, remoteAssets.toList()); + } + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + void onArchiveAsset() async { + processing.value = true; + try { + final remoteAssets = ownedRemoteSelection( + localErrorMessage: 'home_page_archive_err_local'.tr(), + ownerErrorMessage: 'home_page_archive_err_partner'.tr(), + ); + await handleArchiveAssets(ref, context, remoteAssets.toList()); + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + void onDelete([bool force = false]) async { + processing.value = true; + try { + final toDelete = selection.value + .ownedOnly(currentUser, errorCallback: errorBuilder('home_page_delete_err_partner'.tr())) + .toList(); + final isDeleted = await ref.read(assetProvider.notifier).deleteAssets(toDelete, force: force); + + if (isDeleted) { + ImmichToast.show( + context: context, + msg: force + ? 'assets_deleted_permanently'.tr(namedArgs: {'count': "${selection.value.length}"}) + : 'assets_trashed'.tr(namedArgs: {'count': "${selection.value.length}"}), + gravity: ToastGravity.BOTTOM, + ); + selectionEnabledHook.value = false; + } + } finally { + processing.value = false; + } + } + + void onDeleteLocal(bool isMergedAsset) async { + processing.value = true; + try { + final localAssets = selection.value.where((a) => a.isLocal).toList(); + + final toDelete = isMergedAsset ? localAssets.where((e) => e.storage == AssetState.merged) : localAssets; + + if (toDelete.isEmpty) { + return; + } + + final isDeleted = await ref.read(assetProvider.notifier).deleteLocalAssets(toDelete.toList()); + + if (isDeleted) { + final deletedCount = localAssets.where((e) => !isMergedAsset || e.isRemote).length; + + ImmichToast.show( + context: context, + msg: 'assets_removed_permanently_from_device'.tr(namedArgs: {'count': "$deletedCount"}), + gravity: ToastGravity.BOTTOM, + ); + + selectionEnabledHook.value = false; + } + } finally { + processing.value = false; + } + } + + void onDownload() async { + processing.value = true; + try { + final toDownload = selection.value.toList(); + + final results = await ref.read(downloadStateProvider.notifier).downloadAllAsset(toDownload); + + final totalCount = toDownload.length; + final successCount = results.where((e) => e).length; + final failedCount = totalCount - successCount; + + final msg = failedCount > 0 + ? 'assets_downloaded_failed'.t(context: context, args: {'count': successCount, 'error': failedCount}) + : 'assets_downloaded_successfully'.t(context: context, args: {'count': successCount}); + + ImmichToast.show(context: context, msg: msg, gravity: ToastGravity.BOTTOM); + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + void onDeleteRemote([bool shouldDeletePermanently = false]) async { + processing.value = true; + try { + final toDelete = ownedRemoteSelection( + localErrorMessage: 'home_page_delete_remote_err_local'.tr(), + ownerErrorMessage: 'home_page_delete_err_partner'.tr(), + ).toList(); + + final isDeleted = await ref + .read(assetProvider.notifier) + .deleteRemoteAssets(toDelete, shouldDeletePermanently: shouldDeletePermanently); + if (isDeleted) { + ImmichToast.show( + context: context, + msg: shouldDeletePermanently + ? 'assets_deleted_permanently_from_server'.tr(namedArgs: {'count': "${toDelete.length}"}) + : 'assets_trashed_from_server'.tr(namedArgs: {'count': "${toDelete.length}"}), + gravity: ToastGravity.BOTTOM, + ); + } + } finally { + selectionEnabledHook.value = false; + processing.value = false; + } + } + + void onUpload() { + processing.value = true; + selectionEnabledHook.value = false; + try { + ref + .read(manualUploadProvider.notifier) + .uploadAssets(context, selection.value.where((a) => a.storage == AssetState.local)); + } finally { + processing.value = false; + } + } + + void onAddToAlbum(Album album) async { + processing.value = true; + try { + final Iterable assets = remoteSelection(errorMessage: "home_page_add_to_album_err_local".tr()); + if (assets.isEmpty) { + return; + } + final result = await ref.read(albumServiceProvider).addAssets(album, assets); + + if (result != null) { + if (result.alreadyInAlbum.isNotEmpty) { + ImmichToast.show( + context: context, + msg: "home_page_add_to_album_conflicts".tr( + namedArgs: { + "album": album.name, + "added": result.successfullyAdded.toString(), + "failed": result.alreadyInAlbum.length.toString(), + }, + ), + ); + } else { + ImmichToast.show( + context: context, + msg: "home_page_add_to_album_success".tr( + namedArgs: {"album": album.name, "added": result.successfullyAdded.toString()}, + ), + toastType: ToastType.success, + ); + } + } + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + void onCreateNewAlbum() async { + processing.value = true; + try { + final Iterable assets = remoteSelection(errorMessage: "home_page_add_to_album_err_local".tr()); + if (assets.isEmpty) { + return; + } + final result = await ref.read(albumServiceProvider).createAlbumWithGeneratedName(assets); + + if (result != null) { + unawaited(ref.watch(albumProvider.notifier).refreshRemoteAlbums()); + selectionEnabledHook.value = false; + + unawaited(context.pushRoute(AlbumViewerRoute(albumId: result.id))); + } + } finally { + processing.value = false; + } + } + + void onStack() async { + try { + processing.value = true; + if (!selectionEnabledHook.value || selection.value.length < 2) { + return; + } + + await ref.read(stackServiceProvider).createStack(selection.value.map((e) => e.remoteId!).toList()); + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + void onEditTime() async { + try { + final remoteAssets = ownedRemoteSelection( + localErrorMessage: 'home_page_favorite_err_local'.tr(), + ownerErrorMessage: 'home_page_favorite_err_partner'.tr(), + ); + + if (remoteAssets.isNotEmpty) { + unawaited(handleEditDateTime(ref, context, remoteAssets.toList())); + } + } finally { + selectionEnabledHook.value = false; + } + } + + void onEditLocation() async { + try { + final remoteAssets = ownedRemoteSelection( + localErrorMessage: 'home_page_favorite_err_local'.tr(), + ownerErrorMessage: 'home_page_favorite_err_partner'.tr(), + ); + + if (remoteAssets.isNotEmpty) { + unawaited(handleEditLocation(ref, context, remoteAssets.toList())); + } + } finally { + selectionEnabledHook.value = false; + } + } + + void onToggleLockedVisibility() async { + processing.value = true; + try { + final remoteAssets = ownedRemoteSelection( + localErrorMessage: 'home_page_locked_error_local'.tr(), + ownerErrorMessage: 'home_page_locked_error_partner'.tr(), + ); + if (remoteAssets.isNotEmpty) { + final isInLockedView = ref.read(inLockedViewProvider); + final visibility = isInLockedView ? AssetVisibilityEnum.timeline : AssetVisibilityEnum.locked; + + await handleSetAssetsVisibility(ref, context, visibility, remoteAssets.toList()); + } + } finally { + processing.value = false; + selectionEnabledHook.value = false; + } + } + + Future Function() wrapLongRunningFun(Future Function() fun, {bool showOverlay = true}) => () async { + if (showOverlay) processing.value = true; + try { + final result = await fun(); + if (result.runtimeType != bool || result == true) { + selectionEnabledHook.value = false; + } + return result; + } finally { + if (showOverlay) processing.value = false; + } + }; + + return SafeArea( + top: true, + bottom: false, + child: Stack( + children: [ + ref + .watch(renderListProvider) + .when( + data: (data) => data.isEmpty && (buildLoadingIndicator != null || topWidget == null) + ? (buildLoadingIndicator ?? buildEmptyIndicator)() + : ImmichAssetGrid( + renderList: data, + listener: selectionListener, + selectionActive: selectionEnabledHook.value, + onRefresh: onRefresh == null ? null : wrapLongRunningFun(onRefresh!, showOverlay: false), + topWidget: topWidget, + showStack: stackEnabled, + showDragScrollLabel: dragScrollLabelEnabled, + ), + error: (error, _) => Center(child: Text(error.toString())), + loading: buildLoadingIndicator ?? buildDefaultLoadingIndicator, + ), + if (selectionEnabledHook.value) + ControlBottomAppBar( + key: const ValueKey("controlBottomAppBar"), + onShare: onShareAssets, + onFavorite: favoriteEnabled ? onFavoriteAssets : null, + onArchive: archiveEnabled ? onArchiveAsset : null, + onDelete: deleteEnabled ? onDelete : null, + onDeleteServer: deleteEnabled ? onDeleteRemote : null, + onDownload: downloadEnabled ? onDownload : null, + + /// local file deletion is allowed irrespective of [deleteEnabled] since it has + /// nothing to do with the state of the asset in the Immich server + onDeleteLocal: onDeleteLocal, + onAddToAlbum: onAddToAlbum, + onCreateNewAlbum: onCreateNewAlbum, + onUpload: onUpload, + enabled: !processing.value, + selectionAssetState: selectionAssetState.value, + selectedAssets: selection.value.toList(), + onStack: stackEnabled ? onStack : null, + onEditTime: editEnabled ? onEditTime : null, + onEditLocation: editEnabled ? onEditLocation : null, + unfavorite: unfavorite, + unarchive: unarchive, + onToggleLocked: onToggleLockedVisibility, + onRemoveFromAlbum: onRemoveFromAlbum != null + ? wrapLongRunningFun(() => onRemoveFromAlbum!(selection.value)) + : null, + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/multiselect_grid_status_indicator.dart b/mobile/lib/widgets/asset_grid/multiselect_grid_status_indicator.dart new file mode 100644 index 0000000000..3a1fa82a28 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/multiselect_grid_status_indicator.dart @@ -0,0 +1,26 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/asset_viewer/render_list_status_provider.dart'; +import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart'; + +class MultiselectGridStatusIndicator extends HookConsumerWidget { + const MultiselectGridStatusIndicator({super.key, this.buildLoadingIndicator, this.emptyIndicator}); + + final Widget Function()? buildLoadingIndicator; + final Widget? emptyIndicator; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final renderListStatus = ref.watch(renderListStatusProvider); + return switch (renderListStatus) { + RenderListStatusEnum.loading => + buildLoadingIndicator == null + ? const Center(child: DelayedLoadingIndicator(delay: Duration(milliseconds: 500))) + : buildLoadingIndicator!(), + RenderListStatusEnum.empty => emptyIndicator ?? Center(child: const Text("no_assets_to_show").tr()), + RenderListStatusEnum.error => Center(child: const Text("error_loading_assets").tr()), + RenderListStatusEnum.complete => const SizedBox(), + }; + } +} diff --git a/mobile/lib/widgets/asset_grid/thumbnail_image.dart b/mobile/lib/widgets/asset_grid/thumbnail_image.dart new file mode 100644 index 0000000000..93385b88b3 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/thumbnail_image.dart @@ -0,0 +1,259 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/constants/constants.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/duration_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/widgets/common/immich_thumbnail.dart'; + +class ThumbnailImage extends StatelessWidget { + /// The asset to show the thumbnail image for + final Asset asset; + + /// Whether to show the storage indicator icont over the image or not + final bool showStorageIndicator; + + /// Whether to show the show stack icon over the image or not + final bool showStack; + + /// Whether to show the checkmark indicating that this image is selected + final bool isSelected; + + /// Can override [isSelected] and never show the selection indicator + final bool multiselectEnabled; + + /// If we are allowed to deselect this image + final bool canDeselect; + + /// The offset index to apply to this hero tag for animation + final int heroOffset; + + const ThumbnailImage({ + super.key, + required this.asset, + this.showStorageIndicator = true, + this.showStack = false, + this.isSelected = false, + this.multiselectEnabled = false, + this.heroOffset = 0, + this.canDeselect = true, + }); + + @override + Widget build(BuildContext context) { + final assetContainerColor = context.isDarkTheme + ? context.primaryColor.darken(amount: 0.6) + : context.primaryColor.lighten(amount: 0.8); + + return Stack( + children: [ + AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.decelerate, + decoration: BoxDecoration( + border: multiselectEnabled && isSelected + ? canDeselect + ? Border.all(color: assetContainerColor, width: 8) + : const Border( + top: BorderSide(color: Colors.grey, width: 8), + right: BorderSide(color: Colors.grey, width: 8), + bottom: BorderSide(color: Colors.grey, width: 8), + left: BorderSide(color: Colors.grey, width: 8), + ) + : const Border(), + ), + child: Stack( + children: [ + _ImageIcon( + heroOffset: heroOffset, + asset: asset, + assetContainerColor: assetContainerColor, + multiselectEnabled: multiselectEnabled, + canDeselect: canDeselect, + isSelected: isSelected, + ), + if (showStorageIndicator) _StorageIcon(storage: asset.storage), + if (asset.isFavorite) + const Positioned(left: 8, bottom: 5, child: Icon(Icons.favorite, color: Colors.white, size: 16)), + if (asset.isVideo) _VideoIcon(duration: asset.duration), + if (asset.stackCount > 0) _StackIcon(isVideo: asset.isVideo, stackCount: asset.stackCount), + ], + ), + ), + if (multiselectEnabled) + isSelected + ? const Padding( + padding: EdgeInsets.all(3.0), + child: Align(alignment: Alignment.topLeft, child: _SelectedIcon()), + ) + : const Icon(Icons.circle_outlined, color: Colors.white), + ], + ); + } +} + +class _SelectedIcon extends StatelessWidget { + const _SelectedIcon(); + + @override + Widget build(BuildContext context) { + final assetContainerColor = context.isDarkTheme + ? context.primaryColor.darken(amount: 0.6) + : context.primaryColor.lighten(amount: 0.8); + + return DecoratedBox( + decoration: BoxDecoration(shape: BoxShape.circle, color: assetContainerColor), + child: Icon(Icons.check_circle_rounded, color: context.primaryColor), + ); + } +} + +class _VideoIcon extends StatelessWidget { + final Duration duration; + + const _VideoIcon({required this.duration}); + + @override + Widget build(BuildContext context) { + return Positioned( + top: 5, + right: 8, + child: Row( + children: [ + Text( + duration.format(), + style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + const SizedBox(width: 3), + const Icon(Icons.play_circle_fill_rounded, color: Colors.white, size: 18), + ], + ), + ); + } +} + +class _StackIcon extends StatelessWidget { + final bool isVideo; + final int stackCount; + + const _StackIcon({required this.isVideo, required this.stackCount}); + + @override + Widget build(BuildContext context) { + return Positioned( + top: isVideo ? 28 : 5, + right: 8, + child: Row( + children: [ + if (stackCount > 1) + Text( + "$stackCount", + style: const TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + if (stackCount > 1) const SizedBox(width: 3), + const Icon(Icons.burst_mode_rounded, color: Colors.white, size: 18), + ], + ), + ); + } +} + +class _StorageIcon extends StatelessWidget { + final AssetState storage; + + const _StorageIcon({required this.storage}); + + @override + Widget build(BuildContext context) { + return switch (storage) { + AssetState.local => const Positioned( + right: 8, + bottom: 5, + child: Icon( + Icons.cloud_off_outlined, + color: Color.fromRGBO(255, 255, 255, 0.8), + size: 16, + shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))], + ), + ), + AssetState.remote => const Positioned( + right: 8, + bottom: 5, + child: Icon( + Icons.cloud_outlined, + color: Color.fromRGBO(255, 255, 255, 0.8), + size: 16, + shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))], + ), + ), + AssetState.merged => const Positioned( + right: 8, + bottom: 5, + child: Icon( + Icons.cloud_done_outlined, + color: Color.fromRGBO(255, 255, 255, 0.8), + size: 16, + shadows: [Shadow(blurRadius: 5.0, color: Color.fromRGBO(0, 0, 0, 0.6), offset: Offset(0.0, 0.0))], + ), + ), + }; + } +} + +class _ImageIcon extends StatelessWidget { + final int heroOffset; + final Asset asset; + final Color assetContainerColor; + final bool multiselectEnabled; + final bool canDeselect; + final bool isSelected; + + const _ImageIcon({ + required this.heroOffset, + required this.asset, + required this.assetContainerColor, + required this.multiselectEnabled, + required this.canDeselect, + required this.isSelected, + }); + + @override + Widget build(BuildContext context) { + // Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id + final isDto = asset.id == noDbId; + final image = SizedBox.expand( + child: Hero( + tag: isDto ? '${asset.remoteId}-$heroOffset' : asset.id + heroOffset, + child: Stack( + children: [ + SizedBox.expand(child: ImmichThumbnail(asset: asset, height: 250, width: 250)), + const DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Color.fromRGBO(0, 0, 0, 0.1), + Colors.transparent, + Colors.transparent, + Color.fromRGBO(0, 0, 0, 0.1), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0, 0.3, 0.6, 1], + ), + ), + ), + ], + ), + ), + ); + + if (!multiselectEnabled || !isSelected) { + return image; + } + + return DecoratedBox( + decoration: canDeselect ? BoxDecoration(color: assetContainerColor) : const BoxDecoration(color: Colors.grey), + child: ClipRRect(borderRadius: const BorderRadius.all(Radius.circular(15.0)), child: image), + ); + } +} diff --git a/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart b/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart index 12105aa498..a84dfbae37 100644 --- a/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart +++ b/mobile/lib/widgets/asset_grid/thumbnail_placeholder.dart @@ -11,7 +11,7 @@ class ThumbnailPlaceholder extends StatelessWidget { @override Widget build(BuildContext context) { - final gradientColors = [ + var gradientColors = [ context.colorScheme.surfaceContainer, context.colorScheme.surfaceContainer.darken(amount: .1), ]; diff --git a/mobile/lib/widgets/asset_grid/upload_dialog.dart b/mobile/lib/widgets/asset_grid/upload_dialog.dart new file mode 100644 index 0000000000..86e2759566 --- /dev/null +++ b/mobile/lib/widgets/asset_grid/upload_dialog.dart @@ -0,0 +1,14 @@ +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; + +class UploadDialog extends ConfirmDialog { + final Function onUpload; + + const UploadDialog({super.key, required this.onUpload}) + : super( + title: 'upload_dialog_title', + content: 'upload_dialog_info', + cancel: 'cancel', + ok: 'upload', + onOk: onUpload, + ); +} diff --git a/mobile/lib/widgets/asset_viewer/advanced_bottom_sheet.dart b/mobile/lib/widgets/asset_viewer/advanced_bottom_sheet.dart new file mode 100644 index 0000000000..1a3ef3eac3 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/advanced_bottom_sheet.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; + +class AdvancedBottomSheet extends HookConsumerWidget { + final Asset assetDetail; + final ScrollController? scrollController; + + const AdvancedBottomSheet({super.key, required this.assetDetail, this.scrollController}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SingleChildScrollView( + controller: scrollController, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 8.0), + child: LayoutBuilder( + builder: (context, constraints) { + // One column + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Align(child: Text("ADVANCED INFO", style: TextStyle(fontSize: 12.0))), + const SizedBox(height: 32.0), + Container( + decoration: BoxDecoration( + color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[200], + borderRadius: const BorderRadius.all(Radius.circular(15.0)), + ), + child: Padding( + padding: const EdgeInsets.only(right: 16.0, left: 16, top: 8, bottom: 16), + child: ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + children: [ + Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: assetDetail.toString())).then((_) { + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "Copied to clipboard", + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), + ), + ); + }); + }, + icon: Icon(Icons.copy, size: 16.0, color: context.primaryColor), + ), + ), + SelectableText( + assetDetail.toString(), + style: const TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.bold, + fontFamily: "GoogleSansCode", + ), + showCursor: true, + ), + ], + ), + ), + ), + const SizedBox(height: 32.0), + ], + ); + }, + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/animated_play_pause.dart b/mobile/lib/widgets/asset_viewer/animated_play_pause.dart index bad8a6345c..4be7f49b5a 100644 --- a/mobile/lib/widgets/asset_viewer/animated_play_pause.dart +++ b/mobile/lib/widgets/asset_viewer/animated_play_pause.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'dart:ui'; import 'package:flutter/material.dart'; @@ -28,9 +27,9 @@ class AnimatedPlayPauseState extends State with SingleTickerP super.didUpdateWidget(oldWidget); if (widget.playing != oldWidget.playing) { if (widget.playing) { - unawaited(animationController.forward()); + animationController.forward(); } else { - unawaited(animationController.reverse()); + animationController.reverse(); } } } diff --git a/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart new file mode 100644 index 0000000000..22a7deffff --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/bottom_gallery_bar.dart @@ -0,0 +1,362 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/pages/editing/edit.page.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.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/stack.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart'; +import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class BottomGalleryBar extends ConsumerWidget { + final ValueNotifier assetIndex; + final bool showStack; + final ValueNotifier stackIndex; + final ValueNotifier totalAssets; + final PageController controller; + final RenderList renderList; + + const BottomGalleryBar({ + super.key, + required this.showStack, + required this.stackIndex, + required this.assetIndex, + required this.controller, + required this.totalAssets, + required this.renderList, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isInLockedView = ref.watch(inLockedViewProvider); + final asset = ref.watch(currentAssetProvider); + if (asset == null) { + return const SizedBox(); + } + final isOwner = asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? ''); + final showControls = ref.watch(showControlsProvider); + final stackId = asset.stackId; + + final stackItems = showStack && stackId != null ? ref.watch(assetStackStateProvider(stackId)) : []; + bool isStackPrimaryAsset = asset.stackPrimaryAssetId == null; + final navStack = AutoRouter.of(context).stackData; + final isTrashEnabled = ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash)); + final isFromTrash = + isTrashEnabled && navStack.length > 2 && navStack.elementAt(navStack.length - 2).name == TrashRoute.name; + final isInAlbum = ref.watch(currentAlbumProvider)?.isRemote ?? false; + + void removeAssetFromStack() { + if (stackIndex.value > 0 && showStack && stackId != null) { + ref.read(assetStackStateProvider(stackId).notifier).removeChild(stackIndex.value - 1); + } + } + + void handleDelete() async { + Future onDelete(bool force) async { + final isDeleted = await ref.read(assetProvider.notifier).deleteAssets({asset}, force: force); + if (isDeleted && isStackPrimaryAsset) { + // Workaround for asset remaining in the gallery + renderList.deleteAsset(asset); + + // `assetIndex == totalAssets.value - 1` handle the case of removing the last asset + // to not throw the error when the next preCache index is called + if (totalAssets.value == 1 || assetIndex.value == totalAssets.value - 1) { + // Handle only one asset + await context.maybePop(); + } + + totalAssets.value -= 1; + } + if (isDeleted) { + ref.read(currentAssetProvider.notifier).set(renderList.loadAsset(assetIndex.value)); + } + return isDeleted; + } + + // Asset is trashed + if (isTrashEnabled && !isFromTrash) { + final isDeleted = await onDelete(false); + if (isDeleted) { + // Can only trash assets stored in server. Local assets are always permanently removed for now + if (context.mounted && asset.isRemote && isStackPrimaryAsset) { + ImmichToast.show( + durationInSecond: 1, + context: context, + msg: 'asset_trashed'.tr(), + gravity: ToastGravity.BOTTOM, + ); + } + removeAssetFromStack(); + } + return; + } + + // Asset is permanently removed + unawaited( + showDialog( + context: context, + builder: (BuildContext _) { + return DeleteDialog( + onDelete: () async { + final isDeleted = await onDelete(true); + if (isDeleted) { + removeAssetFromStack(); + } + }, + ); + }, + ), + ); + } + + unStack() async { + if (asset.stackId == null) { + return; + } + + await ref.read(stackServiceProvider).deleteStack(asset.stackId!, stackItems); + } + + void showStackActionItems() { + showModalBottomSheet( + context: context, + enableDrag: false, + builder: (BuildContext ctx) { + return SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Icons.filter_none_outlined, size: 18), + onTap: () async { + await unStack(); + ctx.pop(); + await context.maybePop(); + }, + title: const Text("viewer_unstack", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + ), + ], + ), + ), + ); + }, + ); + } + + shareAsset() { + if (asset.isOffline) { + ImmichToast.show( + durationInSecond: 1, + context: context, + msg: 'asset_action_share_err_offline'.tr(), + gravity: ToastGravity.BOTTOM, + ); + return; + } + ref.read(downloadStateProvider.notifier).shareAsset(asset, context); + } + + void handleEdit() async { + final image = Image(image: ImmichImage.imageProvider(asset: asset)); + + unawaited( + context.navigator.push( + MaterialPageRoute( + builder: (context) => EditImagePage(asset: asset, image: image, isEdited: false), + ), + ), + ); + } + + handleArchive() { + ref.read(assetProvider.notifier).toggleArchive([asset]); + if (isStackPrimaryAsset) { + context.maybePop(); + return; + } + removeAssetFromStack(); + } + + handleDownload() { + if (asset.isLocal) { + return; + } + if (asset.isOffline) { + ImmichToast.show( + durationInSecond: 1, + context: context, + msg: 'asset_action_share_err_offline'.tr(), + gravity: ToastGravity.BOTTOM, + ); + return; + } + + ref.read(downloadStateProvider.notifier).downloadAsset(asset); + } + + handleRemoveFromAlbum() async { + final album = ref.read(currentAlbumProvider); + final bool isSuccess = album != null && await ref.read(albumProvider.notifier).removeAsset(album, [asset]); + + if (isSuccess) { + // Workaround for asset remaining in the gallery + renderList.deleteAsset(asset); + + if (totalAssets.value == 1) { + // Handle empty viewer + await context.maybePop(); + } else { + // changing this also for the last asset causes the parent to rebuild with an error + totalAssets.value -= 1; + } + if (assetIndex.value == totalAssets.value && assetIndex.value > 0) { + // handle the case of removing the last asset in the list + assetIndex.value -= 1; + } + } else { + ImmichToast.show( + context: context, + msg: "album_viewer_appbar_share_err_remove".tr(), + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + } + } + + final List> albumActions = [ + { + BottomNavigationBarItem( + icon: Icon(Platform.isAndroid ? Icons.share_rounded : Icons.ios_share_rounded), + label: 'share'.tr(), + tooltip: 'share'.tr(), + ): (_) => + shareAsset(), + }, + if (asset.isImage && !isInLockedView) + { + BottomNavigationBarItem( + icon: const Icon(Icons.tune_outlined), + label: 'edit'.tr(), + tooltip: 'edit'.tr(), + ): (_) => + handleEdit(), + }, + if (isOwner && !isInLockedView) + { + asset.isArchived + ? BottomNavigationBarItem( + icon: const Icon(Icons.unarchive_rounded), + label: 'unarchive'.tr(), + tooltip: 'unarchive'.tr(), + ) + : BottomNavigationBarItem( + icon: const Icon(Icons.archive_outlined), + label: 'archive'.tr(), + tooltip: 'archive'.tr(), + ): (_) => + handleArchive(), + }, + if (isOwner && asset.stackCount > 0 && !isInLockedView) + { + BottomNavigationBarItem( + icon: const Icon(Icons.burst_mode_outlined), + label: 'stack'.tr(), + tooltip: 'stack'.tr(), + ): (_) => + showStackActionItems(), + }, + if (isOwner && !isInAlbum) + { + BottomNavigationBarItem( + icon: const Icon(Icons.delete_outline), + label: 'delete'.tr(), + tooltip: 'delete'.tr(), + ): (_) => + handleDelete(), + }, + if (!isOwner) + { + BottomNavigationBarItem( + icon: const Icon(Icons.download_outlined), + label: 'download'.tr(), + tooltip: 'download'.tr(), + ): (_) => + handleDownload(), + }, + if (isInAlbum) + { + BottomNavigationBarItem( + icon: const Icon(Icons.remove_circle_outline), + label: 'remove_from_album'.tr(), + tooltip: 'remove_from_album'.tr(), + ): (_) => + handleRemoveFromAlbum(), + }, + ]; + return IgnorePointer( + ignoring: !showControls, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: showControls ? 1.0 : 0.0, + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.bottomCenter, + end: Alignment.topCenter, + colors: [Colors.black, Colors.transparent], + ), + ), + position: DecorationPosition.background, + child: Padding( + padding: const EdgeInsets.only(top: 40.0), + child: Column( + children: [ + if (asset.isVideo) VideoControls(videoPlayerName: asset.id.toString()), + BottomNavigationBar( + elevation: 0.0, + backgroundColor: Colors.transparent, + unselectedIconTheme: const IconThemeData(color: Colors.white), + selectedIconTheme: const IconThemeData(color: Colors.white), + unselectedLabelStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, height: 2.3), + selectedLabelStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.w500, height: 2.3), + unselectedFontSize: 14, + selectedFontSize: 14, + selectedItemColor: Colors.white, + unselectedItemColor: Colors.white, + showSelectedLabels: true, + showUnselectedLabels: true, + items: albumActions.map((e) => e.keys.first).toList(growable: false), + onTap: (index) { + albumActions[index].values.first.call(index); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/cast_dialog.dart b/mobile/lib/widgets/asset_viewer/cast_dialog.dart index 70b1b3d3e5..d406f29a22 100644 --- a/mobile/lib/widgets/asset_viewer/cast_dialog.dart +++ b/mobile/lib/widgets/asset_viewer/cast_dialog.dart @@ -1,9 +1,9 @@ import 'dart:async'; +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/generated/translations.g.dart'; import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; @@ -23,21 +23,21 @@ class CastDialog extends ConsumerWidget { } return AlertDialog( - title: Text(context.t.cast, style: const TextStyle(fontWeight: FontWeight.bold)), + title: const Text("cast", style: TextStyle(fontWeight: FontWeight.bold)).tr(), content: SizedBox( width: 250, height: 250, child: FutureBuilder>( - future: ref.watch(castProvider.notifier).getDevices(), + future: ref.read(castProvider.notifier).getDevices(), builder: (context, snapshot) { if (snapshot.hasError) { - return Text(context.t.error_saving_image(error: snapshot.error.toString())); + return Text('error_saving_image'.tr(args: [snapshot.error.toString()])); } else if (!snapshot.hasData) { return const SizedBox(height: 48, child: Center(child: CircularProgressIndicator())); } if (snapshot.data!.isEmpty) { - return Text(context.t.no_cast_devices_found); + return const Text('no_cast_devices_found').tr(); } final devices = snapshot.data!; @@ -47,12 +47,12 @@ class CastDialog extends ConsumerWidget { final List sectionedList = []; if (connected.isNotEmpty) { - sectionedList.add(context.t.connected_device); + sectionedList.add("connected_device"); sectionedList.addAll(connected); } if (others.isNotEmpty) { - sectionedList.add(context.t.discovered_devices); + sectionedList.add("discovered_devices"); sectionedList.addAll(others); } @@ -66,7 +66,7 @@ class CastDialog extends ConsumerWidget { // It's a section header return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text(item, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + child: Text(item, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16)).tr(), ); } else { final (deviceName, type, deviceObj) = item as (String, CastDestinationType, dynamic); @@ -110,16 +110,16 @@ class CastDialog extends ConsumerWidget { TextButton( onPressed: () => ref.read(castProvider.notifier).disconnect(), child: Text( - context.t.stop_casting, + "stop_casting", style: TextStyle(color: context.colorScheme.secondary, fontWeight: FontWeight.bold), - ), + ).tr(), ), TextButton( onPressed: () => context.pop(), child: Text( - context.t.close, + "close", style: TextStyle(color: context.colorScheme.primary, fontWeight: FontWeight.bold), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/widgets/asset_viewer/center_play_button.dart b/mobile/lib/widgets/asset_viewer/center_play_button.dart new file mode 100644 index 0000000000..55d8be8095 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/center_play_button.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/widgets/asset_viewer/animated_play_pause.dart'; + +class CenterPlayButton extends StatelessWidget { + const CenterPlayButton({ + super.key, + required this.backgroundColor, + this.iconColor, + required this.show, + required this.isPlaying, + required this.isFinished, + this.onPressed, + }); + + final Color backgroundColor; + final Color? iconColor; + final bool show; + final bool isPlaying; + final bool isFinished; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return Center( + child: UnconstrainedBox( + child: AnimatedOpacity( + opacity: show ? 1.0 : 0.0, + duration: const Duration(milliseconds: 100), + child: DecoratedBox( + decoration: BoxDecoration(color: backgroundColor, shape: BoxShape.circle), + child: IconButton( + iconSize: 32, + padding: const EdgeInsets.all(12.0), + icon: isFinished + ? Icon(Icons.replay, color: iconColor) + : AnimatedPlayPause(color: iconColor, playing: isPlaying), + onPressed: onPressed, + ), + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart new file mode 100644 index 0000000000..09c0e9d091 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/custom_video_player_controls.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/cast/cast_manager_state.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/utils/hooks/timer_hook.dart'; +import 'package:immich_mobile/widgets/asset_viewer/center_play_button.dart'; +import 'package:immich_mobile/widgets/common/delayed_loading_indicator.dart'; + +class CustomVideoPlayerControls extends HookConsumerWidget { + final String videoId; + final Duration hideTimerDuration; + + const CustomVideoPlayerControls({ + super.key, + required this.videoId, + this.hideTimerDuration = const Duration(seconds: 5), + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assetIsVideo = ref.watch(currentAssetProvider.select((asset) => asset != null && asset.isVideo)); + final showControls = ref.watch(showControlsProvider); + final status = ref.watch(videoPlayerProvider(videoId).select((value) => value.status)); + + final cast = ref.watch(castProvider); + + // A timer to hide the controls + final hideTimer = useTimer(hideTimerDuration, () { + if (!context.mounted) { + return; + } + final s = ref.read(videoPlayerProvider(videoId)).status; + + // Do not hide on paused + if (s != VideoPlaybackStatus.paused && s != VideoPlaybackStatus.completed && assetIsVideo) { + ref.read(showControlsProvider.notifier).show = false; + } + }); + final showBuffering = status == VideoPlaybackStatus.buffering && !cast.isCasting; + + /// Shows the controls and starts the timer to hide them + void showControlsAndStartHideTimer() { + hideTimer.reset(); + ref.read(showControlsProvider.notifier).show = true; + } + + // When playback starts, reset the hide timer + ref.listen(videoPlayerProvider(videoId).select((v) => v.status), (previous, next) { + if (next == VideoPlaybackStatus.playing) { + hideTimer.reset(); + } + }); + + /// Toggles between playing and pausing depending on the state of the video + void togglePlay() { + showControlsAndStartHideTimer(); + + if (cast.isCasting) { + if (cast.castState == CastState.playing) { + ref.read(castProvider.notifier).pause(); + } else if (cast.castState == CastState.paused) { + ref.read(castProvider.notifier).play(); + } else if (cast.castState == CastState.idle) { + // resend the play command since its finished + final asset = ref.read(currentAssetProvider); + if (asset == null) { + return; + } + ref.read(castProvider.notifier).loadMediaOld(asset, true); + } + return; + } + + final notifier = ref.read(videoPlayerProvider(videoId).notifier); + if (status == VideoPlaybackStatus.playing) { + notifier.pause(); + } else if (status == VideoPlaybackStatus.completed) { + notifier.restart(); + } else { + notifier.play(); + } + } + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: showControlsAndStartHideTimer, + child: AbsorbPointer( + absorbing: !showControls, + child: Stack( + children: [ + if (showBuffering) + const Center(child: DelayedLoadingIndicator(fadeInDuration: Duration(milliseconds: 400))) + else + GestureDetector( + onTap: () => ref.read(showControlsProvider.notifier).show = false, + child: CenterPlayButton( + backgroundColor: Colors.black54, + iconColor: Colors.white, + isFinished: status == VideoPlaybackStatus.completed, + isPlaying: + status == VideoPlaybackStatus.playing || (cast.isCasting && cast.castState == CastState.playing), + show: assetIsVideo && showControls, + onPressed: togglePlay, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/description_input.dart b/mobile/lib/widgets/asset_viewer/description_input.dart new file mode 100644 index 0000000000..b0cefd63fa --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/description_input.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; +import 'package:logging/logging.dart'; + +class DescriptionInput extends HookConsumerWidget { + DescriptionInput({super.key, required this.asset, this.exifInfo}); + + final Asset asset; + final ExifInfo? exifInfo; + final Logger _log = Logger('DescriptionInput'); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = useTextEditingController(); + final focusNode = useFocusNode(); + final isFocus = useState(false); + final isTextEmpty = useState(controller.text.isEmpty); + final assetService = ref.watch(assetServiceProvider); + final owner = ref.watch(currentUserProvider); + final hasError = useState(false); + final assetWithExif = ref.watch(assetDetailProvider(asset)); + final hasDescription = useState(false); + final isOwner = fastHash(owner?.id ?? '') == asset.ownerId; + + useEffect(() { + assetService.getDescription(asset).then((value) { + controller.text = value; + hasDescription.value = value.isNotEmpty; + }); + return null; + }, [assetWithExif.value]); + + if (!isOwner && !hasDescription.value) { + return const SizedBox.shrink(); + } + + submitDescription(String description) async { + hasError.value = false; + try { + await assetService.setDescription(asset, description); + controller.text = description; + } catch (error, stack) { + hasError.value = true; + _log.severe("Error updating description", error, stack); + ImmichToast.show(context: context, msg: "description_input_submit_error".tr(), toastType: ToastType.error); + } + } + + Widget? suffixIcon; + if (hasError.value) { + suffixIcon = const Icon(Icons.warning_outlined); + } else if (!isTextEmpty.value && isFocus.value) { + suffixIcon = IconButton( + onPressed: () { + controller.clear(); + isTextEmpty.value = true; + }, + icon: Icon(Icons.cancel_rounded, color: context.colorScheme.onSurfaceSecondary), + splashRadius: 10, + ); + } + + return TextField( + enabled: isOwner, + focusNode: focusNode, + onTap: () => isFocus.value = true, + onChanged: (value) { + isTextEmpty.value = false; + }, + onTapOutside: (a) async { + isFocus.value = false; + focusNode.unfocus(); + + if (exifInfo?.description != controller.text) { + await submitDescription(controller.text); + } + }, + autofocus: false, + maxLines: null, + keyboardType: TextInputType.multiline, + controller: controller, + style: context.textTheme.labelLarge, + decoration: InputDecoration( + hintText: 'description_input_hint_text'.tr(), + border: InputBorder.none, + suffixIcon: suffixIcon, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + disabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_date_time.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_date_time.dart new file mode 100644 index 0000000000..df8f6593df --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_date_time.dart @@ -0,0 +1,44 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/duration_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; + +class AssetDateTime extends ConsumerWidget { + final Asset asset; + + const AssetDateTime({super.key, required this.asset}); + + String getDateTimeString(Asset a) { + final (deltaTime, timeZone) = a.getTZAdjustedTimeAndOffset(); + final date = DateFormat.yMMMEd().format(deltaTime); + final time = DateFormat.jm().format(deltaTime); + return '$date â€ĸ $time GMT${timeZone.formatAsOffset()}'; + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + final watchedAsset = ref.watch(assetDetailProvider(asset)); + String formattedDateTime = getDateTimeString(asset); + + void editDateTime() async { + await handleEditDateTime(ref, context, [asset]); + + if (watchedAsset.value != null) { + formattedDateTime = getDateTimeString(watchedAsset.value!); + } + } + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(formattedDateTime, style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600)), + if (asset.isRemote) IconButton(onPressed: editDateTime, icon: const Icon(Icons.edit_outlined), iconSize: 20), + ], + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart new file mode 100644 index 0000000000..f0f9a2efcb --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_details.dart @@ -0,0 +1,40 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/camera_info.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/file_info.dart'; + +class AssetDetails extends ConsumerWidget { + final Asset asset; + final ExifInfo? exifInfo; + + const AssetDetails({super.key, required this.asset, this.exifInfo}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assetWithExif = ref.watch(assetDetailProvider(asset)); + final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo; + + return Padding( + padding: const EdgeInsets.only(top: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "exif_bottom_sheet_details", + style: context.textTheme.labelMedium?.copyWith( + color: context.textTheme.labelMedium?.color?.withAlpha(200), + fontWeight: FontWeight.w600, + ), + ).tr(), + FileInfo(asset: asset), + if (exifInfo?.make != null) CameraInfo(exifInfo: exifInfo!), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart new file mode 100644 index 0000000000..6edf226e8b --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/asset_location.dart @@ -0,0 +1,88 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/exif_map.dart'; + +class AssetLocation extends HookConsumerWidget { + final Asset asset; + + const AssetLocation({super.key, required this.asset}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final assetWithExif = ref.watch(assetDetailProvider(asset)); + final ExifInfo? exifInfo = (assetWithExif.value ?? asset).exifInfo; + final hasCoordinates = exifInfo?.hasCoordinates ?? false; + + void editLocation() { + handleEditLocation(ref, context, [assetWithExif.value ?? asset]); + } + + // Guard no lat/lng + if (!hasCoordinates) { + return asset.isRemote + ? ListTile( + minLeadingWidth: 0, + contentPadding: const EdgeInsets.all(0), + leading: const Icon(Icons.location_on), + title: Text( + "add_a_location", + style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), + ).tr(), + onTap: editLocation, + ) + : const SizedBox.shrink(); + } + + Widget getLocationName() { + if (exifInfo == null) { + return const SizedBox.shrink(); + } + + final cityName = exifInfo.city; + final stateName = exifInfo.state; + + bool hasLocationName = (cityName != null && stateName != null); + + return hasLocationName + ? Text("$cityName, $stateName", style: context.textTheme.labelLarge) + : const SizedBox.shrink(); + } + + return Padding( + padding: EdgeInsets.only(top: asset.isRemote ? 0 : 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "exif_bottom_sheet_location", + style: context.textTheme.labelMedium?.copyWith( + color: context.textTheme.labelMedium?.color?.withAlpha(200), + fontWeight: FontWeight.w600, + ), + ).tr(), + if (asset.isRemote) + IconButton(onPressed: editLocation, icon: const Icon(Icons.edit_outlined), iconSize: 20), + ], + ), + asset.isRemote ? const SizedBox.shrink() : const SizedBox(height: 16), + ExifMap(exifInfo: exifInfo!, markerId: asset.remoteId, markerAssetThumbhash: asset.thumbhash), + const SizedBox(height: 16), + getLocationName(), + Text( + "${exifInfo.latitude!.toStringAsFixed(4)}, ${exifInfo.longitude!.toStringAsFixed(4)}", + style: context.textTheme.labelMedium?.copyWith(color: context.textTheme.labelMedium?.color?.withAlpha(150)), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart new file mode 100644 index 0000000000..5ae29d32c7 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/camera_info.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; + +class CameraInfo extends StatelessWidget { + final ExifInfo exifInfo; + + const CameraInfo({super.key, required this.exifInfo}); + + @override + Widget build(BuildContext context) { + final textColor = context.isDarkTheme ? Colors.white : Colors.black; + return ListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + leading: Icon(Icons.camera, color: textColor.withAlpha(200)), + title: Text("${exifInfo.make} ${exifInfo.model}", style: context.textTheme.labelLarge), + subtitle: exifInfo.f != null || exifInfo.exposureSeconds != null || exifInfo.mm != null || exifInfo.iso != null + ? Text( + "ƒ/${exifInfo.fNumber} ${exifInfo.exposureTime} ${exifInfo.focalLength} mm ISO ${exifInfo.iso ?? ''} ", + style: context.textTheme.bodySmall, + ) + : null, + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/detail_panel.dart b/mobile/lib/widgets/asset_viewer/detail_panel/detail_panel.dart new file mode 100644 index 0000000000..97c9477c97 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/detail_panel.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/widgets/asset_viewer/description_input.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/asset_date_time.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/asset_details.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/asset_location.dart'; +import 'package:immich_mobile/widgets/asset_viewer/detail_panel/people_info.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; + +class DetailPanel extends HookConsumerWidget { + final Asset asset; + final ScrollController? scrollController; + + const DetailPanel({super.key, required this.asset, this.scrollController}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ListView( + controller: scrollController, + shrinkWrap: true, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + children: [ + AssetDateTime(asset: asset), + asset.isRemote ? DescriptionInput(asset: asset) : const SizedBox.shrink(), + PeopleInfo(asset: asset), + AssetLocation(asset: asset), + AssetDetails(asset: asset), + ], + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart index 18bb27d024..f48ee06fdd 100644 --- a/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart +++ b/mobile/lib/widgets/asset_viewer/detail_panel/exif_map.dart @@ -39,7 +39,7 @@ class ExifMap extends StatelessWidget { const zoomLevel = 16; if (Platform.isAndroid) { - final Uri uri = Uri( + Uri uri = Uri( scheme: 'geo', host: '$latitude,$longitude', queryParameters: {'z': '$zoomLevel', 'q': '$latitude,$longitude'}, @@ -48,8 +48,8 @@ class ExifMap extends StatelessWidget { return uri; } } else if (Platform.isIOS) { - final params = {'ll': '$latitude,$longitude', 'q': '$latitude,$longitude', 'z': '$zoomLevel'}; - final Uri uri = Uri.https('maps.apple.com', '/', params); + var params = {'ll': '$latitude,$longitude', 'q': '$latitude,$longitude', 'z': '$zoomLevel'}; + Uri uri = Uri.https('maps.apple.com', '/', params); if (await canLaunchUrl(uri)) { return uri; } @@ -73,7 +73,7 @@ class ExifMap extends StatelessWidget { assetMarkerRemoteId: markerId, assetThumbhash: markerAssetThumbhash, onTap: (tapPosition, latLong) async { - final Uri? uri = await createCoordinatesUri(); + Uri? uri = await createCoordinatesUri(); if (uri == null) { return; diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/file_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/file_info.dart new file mode 100644 index 0000000000..78d9ac1776 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/file_info.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/utils/bytes_units.dart'; + +class FileInfo extends StatelessWidget { + final Asset asset; + + const FileInfo({super.key, required this.asset}); + + @override + Widget build(BuildContext context) { + final textColor = context.isDarkTheme ? Colors.white : Colors.black; + + final height = asset.orientatedHeight ?? asset.height; + final width = asset.orientatedWidth ?? asset.width; + String resolution = height != null && width != null ? "$width x $height " : ""; + String fileSize = asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo!.fileSize!) : ""; + String text = resolution + fileSize; + final imgSizeString = text.isNotEmpty ? text : null; + + String? title; + String? subtitle; + + if (imgSizeString == null && asset.fileName.isNotEmpty) { + // There is only filename + title = asset.fileName; + } else if (imgSizeString != null && asset.fileName.isNotEmpty) { + // There is both filename and size information + title = asset.fileName; + subtitle = imgSizeString; + } else if (imgSizeString != null && asset.fileName.isEmpty) { + title = imgSizeString; + } else { + return const SizedBox.shrink(); + } + + return ListTile( + contentPadding: const EdgeInsets.all(0), + dense: true, + leading: Icon(Icons.image, color: textColor.withAlpha(200)), + titleAlignment: ListTileTitleAlignment.center, + title: Text(title, style: context.textTheme.labelLarge), + subtitle: subtitle == null ? null : Text(subtitle), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart new file mode 100644 index 0000000000..b96cbc777d --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/detail_panel/people_info.dart @@ -0,0 +1,91 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/asset_people.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/people.utils.dart'; +import 'package:immich_mobile/widgets/search/curated_people_row.dart'; +import 'package:immich_mobile/widgets/search/person_name_edit_form.dart'; + +class PeopleInfo extends ConsumerWidget { + final Asset asset; + final EdgeInsets? padding; + + const PeopleInfo({super.key, required this.asset, this.padding}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final peopleProvider = ref.watch(assetPeopleNotifierProvider(asset).notifier); + final people = ref.watch(assetPeopleNotifierProvider(asset)).value?.where((p) => !p.isHidden); + + showPersonNameEditModel(String personId, String personName) { + return showDialog( + context: context, + useRootNavigator: false, + builder: (BuildContext context) { + return PersonNameEditForm(personId: personId, personName: personName); + }, + ).then((_) { + // ensure the people list is up-to-date. + peopleProvider.refresh(); + }); + } + + final curatedPeople = + people + ?.map( + (p) => SearchCuratedContent( + id: p.id, + label: p.name, + subtitle: p.birthDate != null && p.birthDate!.isBefore(asset.fileCreatedAt) + ? formatAge(p.birthDate!, asset.fileCreatedAt) + : null, + ), + ) + .toList() ?? + []; + + return AnimatedCrossFade( + crossFadeState: (people?.isEmpty ?? true) ? CrossFadeState.showFirst : CrossFadeState.showSecond, + duration: const Duration(milliseconds: 200), + firstChild: Container(), + secondChild: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Column( + children: [ + Padding( + padding: padding ?? EdgeInsets.zero, + child: Align( + alignment: Alignment.topLeft, + child: Text( + "exif_bottom_sheet_people", + style: context.textTheme.labelMedium?.copyWith( + color: context.textTheme.labelMedium?.color?.withAlpha(200), + fontWeight: FontWeight.w600, + ), + ).tr(), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: CuratedPeopleRow( + padding: padding, + content: curatedPeople, + onTap: (content, index) { + context + .pushRoute(PersonResultRoute(personId: content.id, personName: content.label)) + .then((_) => peopleProvider.refresh()); + }, + onNameTap: (person, index) => {showPersonNameEditModel(person.id, person.label)}, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart new file mode 100644 index 0000000000..dcb0334801 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/gallery_app_bar.dart @@ -0,0 +1,119 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; +import 'package:immich_mobile/providers/partner.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/providers/trash.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/hash.dart'; +import 'package:immich_mobile/widgets/album/add_to_album_bottom_sheet.dart'; +import 'package:immich_mobile/widgets/asset_grid/upload_dialog.dart'; +import 'package:immich_mobile/widgets/asset_viewer/top_control_app_bar.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class GalleryAppBar extends ConsumerWidget { + final void Function() showInfo; + + const GalleryAppBar({super.key, required this.showInfo}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final asset = ref.watch(currentAssetProvider); + if (asset == null) { + return const SizedBox(); + } + final album = ref.watch(currentAlbumProvider); + final isOwner = asset.ownerId == fastHash(ref.watch(currentUserProvider)?.id ?? ''); + final showControls = ref.watch(showControlsProvider); + + final isPartner = ref.watch(partnerSharedWithProvider).map((e) => fastHash(e.id)).contains(asset.ownerId); + + toggleFavorite(Asset asset) => ref.read(assetProvider.notifier).toggleFavorite([asset]); + + handleActivities() { + if (album != null && album.shared && album.remoteId != null) { + context.pushRoute(const ActivitiesRoute()); + } + } + + handleRestore(Asset asset) async { + final result = await ref.read(trashProvider.notifier).restoreAssets([asset]); + + if (result && context.mounted) { + ImmichToast.show(context: context, msg: 'asset_restored_successfully'.tr(), gravity: ToastGravity.BOTTOM); + } + } + + handleUpload(Asset asset) { + showDialog( + context: context, + builder: (BuildContext _) { + return UploadDialog( + onUpload: () { + ref.read(manualUploadProvider.notifier).uploadAssets(context, [asset]); + }, + ); + }, + ); + } + + addToAlbum(Asset addToAlbumAsset) { + showModalBottomSheet( + elevation: 0, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15.0))), + context: context, + builder: (BuildContext _) { + return AddToAlbumBottomSheet(assets: [addToAlbumAsset]); + }, + ); + } + + handleDownloadAsset() { + ref.read(downloadStateProvider.notifier).downloadAsset(asset); + } + + handleLocateAsset() async { + // Go back to the gallery + await context.maybePop(); + await context.navigateTo(const TabControllerRoute(children: [PhotosRoute()])); + ref.read(tabProvider.notifier).update((state) => state = TabEnum.home); + // Scroll to the asset's date + scrollToDateNotifierProvider.scrollToDate(asset.fileCreatedAt); + } + + return IgnorePointer( + ignoring: !showControls, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: showControls ? 1.0 : 0.0, + child: Container( + color: Colors.black.withValues(alpha: 0.4), + child: TopControlAppBar( + isOwner: isOwner, + isPartner: isPartner, + asset: asset, + onMoreInfoPressed: showInfo, + onLocatePressed: handleLocateAsset, + onFavorite: toggleFavorite, + onRestorePressed: () => handleRestore(asset), + onUploadPressed: asset.isLocal ? () => handleUpload(asset) : null, + onDownloadPressed: asset.isLocal ? null : handleDownloadAsset, + onAddToAlbumPressed: () => addToAlbum(asset), + onActivitiesPressed: handleActivities, + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/motion_photo_button.dart b/mobile/lib/widgets/asset_viewer/motion_photo_button.dart new file mode 100644 index 0000000000..f5479ab86e --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/motion_photo_button.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; + +class MotionPhotoButton extends ConsumerWidget { + const MotionPhotoButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isPlaying = ref.watch(isPlayingMotionVideoProvider); + + return IconButton( + onPressed: () { + ref.read(isPlayingMotionVideoProvider.notifier).toggle(); + }, + icon: isPlaying + ? const Icon(Icons.motion_photos_pause_outlined, color: grey200) + : const Icon(Icons.play_circle_outline_rounded, color: grey200), + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart new file mode 100644 index 0000000000..35f3840797 --- /dev/null +++ b/mobile/lib/widgets/asset_viewer/top_control_app_bar.dart @@ -0,0 +1,182 @@ +import 'package:auto_route/auto_route.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/activity_statistics.provider.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; +import 'package:immich_mobile/providers/routes.provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; +import 'package:immich_mobile/widgets/asset_viewer/motion_photo_button.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; + +class TopControlAppBar extends HookConsumerWidget { + const TopControlAppBar({ + super.key, + required this.asset, + required this.onMoreInfoPressed, + required this.onDownloadPressed, + required this.onLocatePressed, + required this.onAddToAlbumPressed, + required this.onRestorePressed, + required this.onFavorite, + required this.onUploadPressed, + required this.isOwner, + required this.onActivitiesPressed, + required this.isPartner, + }); + + final Asset asset; + final Function onMoreInfoPressed; + final VoidCallback? onUploadPressed; + final VoidCallback? onDownloadPressed; + final VoidCallback onLocatePressed; + final VoidCallback onAddToAlbumPressed; + final VoidCallback onRestorePressed; + final VoidCallback onActivitiesPressed; + final Function(Asset) onFavorite; + final bool isOwner; + final bool isPartner; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isInLockedView = ref.watch(inLockedViewProvider); + const double iconSize = 22.0; + final a = ref.watch(assetWatcher(asset)).value ?? asset; + final album = ref.watch(currentAlbumProvider); + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + final websocketConnected = ref.watch(websocketProvider.select((c) => c.isConnected)); + + final comments = album != null && album.remoteId != null && asset.remoteId != null + ? ref.watch(activityStatisticsProvider(album.remoteId!, asset.remoteId)) + : 0; + + Widget buildFavoriteButton(a) { + return IconButton( + onPressed: () => onFavorite(a), + icon: Icon(a.isFavorite ? Icons.favorite : Icons.favorite_border, color: Colors.grey[200]), + ); + } + + Widget buildLocateButton() { + return IconButton( + onPressed: () { + onLocatePressed(); + }, + icon: Icon(Icons.image_search, color: Colors.grey[200]), + ); + } + + Widget buildMoreInfoButton() { + return IconButton( + onPressed: () { + onMoreInfoPressed(); + }, + icon: Icon(Icons.info_outline_rounded, color: Colors.grey[200]), + ); + } + + Widget buildDownloadButton() { + return IconButton( + onPressed: onDownloadPressed, + icon: Icon(Icons.cloud_download_outlined, color: Colors.grey[200]), + ); + } + + Widget buildAddToAlbumButton() { + return IconButton( + onPressed: () { + onAddToAlbumPressed(); + }, + icon: Icon(Icons.add, color: Colors.grey[200]), + ); + } + + Widget buildRestoreButton() { + return IconButton( + onPressed: () { + onRestorePressed(); + }, + icon: Icon(Icons.history_rounded, color: Colors.grey[200]), + ); + } + + Widget buildActivitiesButton() { + return IconButton( + onPressed: () { + onActivitiesPressed(); + }, + icon: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(Icons.mode_comment_outlined, color: Colors.grey[200]), + if (comments != 0) + Padding( + padding: const EdgeInsets.only(left: 5), + child: Text( + comments.toString(), + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.grey[200]), + ), + ), + ], + ), + ); + } + + Widget buildUploadButton() { + return IconButton( + onPressed: onUploadPressed, + icon: Icon(Icons.backup_outlined, color: Colors.grey[200]), + ); + } + + Widget buildBackButton() { + return IconButton( + onPressed: () { + context.maybePop(); + }, + icon: Icon(Icons.arrow_back_ios_new_rounded, size: 20.0, color: Colors.grey[200]), + ); + } + + Widget buildCastButton() { + return IconButton( + onPressed: () { + showDialog(context: context, builder: (context) => const CastDialog()); + }, + icon: Icon( + isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded, + size: 20.0, + color: isCasting ? context.primaryColor : Colors.grey[200], + ), + ); + } + + bool isInHomePage = ref.read(tabProvider.notifier).state == TabEnum.home; + bool? isInTrash = ref.read(currentAssetProvider)?.isTrashed; + + return AppBar( + foregroundColor: Colors.grey[100], + backgroundColor: Colors.transparent, + leading: buildBackButton(), + actionsIconTheme: const IconThemeData(size: iconSize), + shape: const Border(), + actions: [ + if (asset.isRemote && isOwner) buildFavoriteButton(a), + if (isOwner && !isInHomePage && !(isInTrash ?? false) && !isInLockedView) buildLocateButton(), + if (asset.livePhotoVideoId != null) const MotionPhotoButton(), + if (asset.isLocal && !asset.isRemote) buildUploadButton(), + if (asset.isRemote && !asset.isLocal && isOwner) buildDownloadButton(), + if (asset.isRemote && (isOwner || isPartner) && !asset.isTrashed && !isInLockedView) buildAddToAlbumButton(), + if (isCasting || (asset.isRemote && websocketConnected)) buildCastButton(), + if (asset.isTrashed) buildRestoreButton(), + if (album != null && album.shared && !isInLockedView) buildActivitiesButton(), + buildMoreInfoButton(), + ], + ); + } +} diff --git a/mobile/lib/widgets/asset_viewer/video_controls.dart b/mobile/lib/widgets/asset_viewer/video_controls.dart index b5678b8d9f..89b0f0ec30 100644 --- a/mobile/lib/widgets/asset_viewer/video_controls.dart +++ b/mobile/lib/widgets/asset_viewer/video_controls.dart @@ -4,11 +4,11 @@ import 'package:async/async.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/colors.dart'; -import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/models/cast/cast_manager_state.dart'; import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/widgets/asset_viewer/animated_play_pause.dart'; class VideoControls extends ConsumerStatefulWidget { @@ -49,9 +49,7 @@ class _VideoControlsState extends ConsumerState { } void _onHideTimer() { - if (!mounted) { - return; - } + if (!mounted) return; if (ref.read(_provider).status == VideoPlaybackStatus.playing) { ref.read(assetViewerProvider.notifier).setControls(false); } @@ -93,13 +91,11 @@ class _VideoControlsState extends ConsumerState { final isFinished = !isCasting && videoStatus == VideoPlaybackStatus.completed; ref.listen(assetViewerProvider.select((v) => v.showingControls), (prev, showing) { - if (showing && prev != showing) { - _hideTimer.reset(); - } + if (showing && prev != showing) _hideTimer.reset(); }); - ref.listen(_provider.select((v) => v.status), (_, _) => _hideTimer.reset()); + ref.listen(_provider.select((v) => v.status), (_, __) => _hideTimer.reset()); - final notifier = ref.watch(_provider.notifier); + final notifier = ref.read(_provider.notifier); final isLoaded = duration != Duration.zero; return Padding( @@ -123,15 +119,13 @@ class _VideoControlsState extends ConsumerState { onPressed: () => _toggle(isCasting), ), const Spacer(), - IgnorePointer( - child: Text( - "${position.format()} / ${duration.format()}", - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w500, - fontFeatures: [FontFeature.tabularFigures()], - shadows: VideoControls._controlShadows, - ), + Text( + "${position.format()} / ${duration.format()}", + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w500, + fontFeatures: [FontFeature.tabularFigures()], + shadows: VideoControls._controlShadows, ), ), const SizedBox(width: 12), diff --git a/mobile/lib/widgets/backup/album_info_card.dart b/mobile/lib/widgets/backup/album_info_card.dart new file mode 100644 index 0000000000..d635e136bc --- /dev/null +++ b/mobile/lib/widgets/backup/album_info_card.dart @@ -0,0 +1,185 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/available_album.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class AlbumInfoCard extends HookConsumerWidget { + final AvailableAlbum album; + + const AlbumInfoCard({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(album); + final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(album); + final syncAlbum = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); + + final isDarkTheme = context.isDarkTheme; + + ColorFilter selectedFilter = ColorFilter.mode(context.primaryColor.withAlpha(100), BlendMode.darken); + ColorFilter excludedFilter = ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken); + ColorFilter unselectedFilter = const ColorFilter.mode(Colors.black, BlendMode.color); + + buildSelectedTextBox() { + if (isSelected) { + return Chip( + visualDensity: VisualDensity.compact, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), + label: Text( + "album_info_card_backup_album_included", + style: TextStyle( + fontSize: 10, + color: isDarkTheme ? Colors.black : Colors.white, + fontWeight: FontWeight.bold, + ), + ).tr(), + backgroundColor: context.primaryColor, + ); + } else if (isExcluded) { + return Chip( + visualDensity: VisualDensity.compact, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), + label: Text( + "album_info_card_backup_album_excluded", + style: TextStyle( + fontSize: 10, + color: isDarkTheme ? Colors.black : Colors.white, + fontWeight: FontWeight.bold, + ), + ).tr(), + backgroundColor: Colors.red[300], + ); + } + + return const SizedBox(); + } + + buildImageFilter() { + if (isSelected) { + return selectedFilter; + } else if (isExcluded) { + return excludedFilter; + } else { + return unselectedFilter; + } + } + + return GestureDetector( + onTap: () { + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + + if (isSelected) { + ref.read(backupProvider.notifier).removeAlbumForBackup(album); + } else { + ref.read(backupProvider.notifier).addAlbumForBackup(album); + if (syncAlbum) { + ref.read(albumProvider.notifier).createSyncAlbum(album.name); + } + } + }, + onDoubleTap: () { + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + + if (isExcluded) { + // Remove from exclude album list + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); + } else { + // Add to exclude album list + + if (album.id == 'isAll' || album.name == 'Recents') { + ImmichToast.show( + context: context, + msg: 'Cannot exclude album contains all assets', + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + return; + } + + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); + } + }, + child: Card( + clipBehavior: Clip.hardEdge, + margin: const EdgeInsets.all(1), + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all( + Radius.circular(12), // if you need this + ), + side: BorderSide( + color: isDarkTheme ? const Color.fromARGB(255, 37, 35, 35) : const Color(0xFFC9C9C9), + width: 1, + ), + ), + elevation: 0, + borderOnForeground: false, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Stack( + clipBehavior: Clip.hardEdge, + children: [ + ColorFiltered( + colorFilter: buildImageFilter(), + child: const Image( + width: double.infinity, + height: double.infinity, + image: AssetImage('assets/immich-logo.png'), + fit: BoxFit.cover, + ), + ), + Positioned(bottom: 10, right: 25, child: buildSelectedTextBox()), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(left: 25), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + album.name, + style: TextStyle(fontSize: 14, color: context.primaryColor, fontWeight: FontWeight.bold), + ), + Padding( + padding: const EdgeInsets.only(top: 2.0), + child: Text( + album.assetCount.toString() + (album.isAll ? " (${'all'.tr()})" : ""), + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), + ), + ], + ), + ), + IconButton( + onPressed: () { + context.pushRoute(AlbumPreviewRoute(album: album.album)); + }, + icon: Icon(Icons.image_outlined, color: context.primaryColor, size: 24), + splashRadius: 25, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/album_info_list_tile.dart b/mobile/lib/widgets/backup/album_info_list_tile.dart new file mode 100644 index 0000000000..9796f45e8b --- /dev/null +++ b/mobile/lib/widgets/backup/album_info_list_tile.dart @@ -0,0 +1,98 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/available_album.model.dart'; +import 'package:immich_mobile/providers/album/album.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/common/immich_toast.dart'; + +class AlbumInfoListTile extends HookConsumerWidget { + final AvailableAlbum album; + + const AlbumInfoListTile({super.key, required this.album}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bool isSelected = ref.watch(backupProvider).selectedBackupAlbums.contains(album); + final bool isExcluded = ref.watch(backupProvider).excludedBackupAlbums.contains(album); + final syncAlbum = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums); + + buildTileColor() { + if (isSelected) { + return context.isDarkTheme ? context.primaryColor.withAlpha(100) : context.primaryColor.withAlpha(25); + } else if (isExcluded) { + return context.isDarkTheme ? Colors.red[300]?.withAlpha(150) : Colors.red[100]?.withAlpha(150); + } else { + return Colors.transparent; + } + } + + buildIcon() { + if (isSelected) { + return Icon(Icons.check_circle_rounded, color: context.colorScheme.primary); + } + + if (isExcluded) { + return Icon(Icons.remove_circle_rounded, color: context.colorScheme.error); + } + + return Icon(Icons.circle, color: context.colorScheme.surfaceContainerHighest); + } + + return GestureDetector( + onDoubleTap: () { + ref.watch(hapticFeedbackProvider.notifier).selectionClick(); + + if (isExcluded) { + // Remove from exclude album list + ref.read(backupProvider.notifier).removeExcludedAlbumForBackup(album); + } else { + // Add to exclude album list + + if (album.id == 'isAll' || album.name == 'Recents') { + ImmichToast.show( + context: context, + msg: 'Cannot exclude album contains all assets', + toastType: ToastType.error, + gravity: ToastGravity.BOTTOM, + ); + return; + } + + ref.read(backupProvider.notifier).addExcludedAlbumForBackup(album); + } + }, + child: ListTile( + tileColor: buildTileColor(), + contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + onTap: () { + ref.read(hapticFeedbackProvider.notifier).selectionClick(); + if (isSelected) { + ref.read(backupProvider.notifier).removeAlbumForBackup(album); + } else { + ref.read(backupProvider.notifier).addAlbumForBackup(album); + if (syncAlbum) { + ref.read(albumProvider.notifier).createSyncAlbum(album.name); + } + } + }, + leading: buildIcon(), + title: Text(album.name, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + subtitle: Text(album.assetCount.toString()), + trailing: IconButton( + onPressed: () { + context.pushRoute(AlbumPreviewRoute(album: album.album)); + }, + icon: Icon(Icons.image_outlined, color: context.primaryColor, size: 24), + splashRadius: 25, + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/asset_info_table.dart b/mobile/lib/widgets/backup/asset_info_table.dart new file mode 100644 index 0000000000..2cccded2bb --- /dev/null +++ b/mobile/lib/widgets/backup/asset_info_table.dart @@ -0,0 +1,105 @@ +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/extensions/theme_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupAssetInfoTable extends ConsumerWidget { + const BackupAssetInfoTable({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select((value) => value.backupProgress == BackUpProgressEnum.manualInProgress), + ); + + final isUploadInProgress = ref.watch( + backupProvider.select( + (value) => + value.backupProgress == BackUpProgressEnum.inProgress || + value.backupProgress == BackUpProgressEnum.inBackground || + value.backupProgress == BackUpProgressEnum.manualInProgress, + ), + ); + + final asset = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.currentUploadAsset)) + : ref.watch(backupProvider.select((value) => value.currentUploadAsset)); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Table( + border: TableBorder.all(color: context.colorScheme.outlineVariant, width: 1), + children: [ + TableRow( + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: + Text( + 'backup_controller_page_filename', + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr( + namedArgs: isUploadInProgress + ? {'filename': asset.fileName, 'size': asset.fileType.toLowerCase()} + : {'filename': "-", 'size': "-"}, + ), + ), + ), + ], + ), + TableRow( + children: [ + TableCell( + verticalAlignment: TableCellVerticalAlignment.middle, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + "backup_controller_page_created", + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr(namedArgs: {'date': isUploadInProgress ? _getAssetCreationDate(asset) : "-"}), + ), + ), + ], + ), + TableRow( + children: [ + TableCell( + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Text( + "backup_controller_page_id", + style: TextStyle( + color: context.colorScheme.onSurfaceSecondary, + fontWeight: FontWeight.bold, + fontSize: 10.0, + ), + ).tr(namedArgs: {'id': isUploadInProgress ? asset.id : "-"}), + ), + ), + ], + ), + ], + ), + ); + } + + @pragma('vm:prefer-inline') + String _getAssetCreationDate(CurrentUploadAsset asset) { + return DateFormat.yMMMMd().format(asset.fileCreatedAt.toLocal()); + } +} diff --git a/mobile/lib/widgets/backup/backup_info_card.dart b/mobile/lib/widgets/backup/backup_info_card.dart index 4efd940e36..7911679577 100644 --- a/mobile/lib/widgets/backup/backup_info_card.dart +++ b/mobile/lib/widgets/backup/backup_info_card.dart @@ -1,7 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; class BackupInfoCard extends StatelessWidget { final String title; @@ -72,11 +73,11 @@ class BackupInfoCard extends StatelessWidget { ], ), Text( - context.t.backup_info_card_assets, + "backup_info_card_assets", style: context.textTheme.labelLarge?.copyWith( color: context.colorScheme.onSurface.withAlpha(isLoading ? 50 : 255), ), - ), + ).tr(), ], ), ), @@ -92,7 +93,7 @@ class BackupInfoCard extends StatelessWidget { ), onTap: onTap, title: Text( - context.t.view_details, + "view_details".t(context: context), style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(200)), ), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: context.colorScheme.onSurfaceVariant), diff --git a/mobile/lib/widgets/backup/current_backup_asset_info_box.dart b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart new file mode 100644 index 0000000000..c2f94e706a --- /dev/null +++ b/mobile/lib/widgets/backup/current_backup_asset_info_box.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/backup/asset_info_table.dart'; +import 'package:immich_mobile/widgets/backup/error_chip.dart'; +import 'package:immich_mobile/widgets/backup/icloud_download_progress_bar.dart'; +import 'package:immich_mobile/widgets/backup/upload_progress_bar.dart'; +import 'package:immich_mobile/widgets/backup/upload_stats.dart'; + +class CurrentUploadingAssetInfoBox extends StatelessWidget { + const CurrentUploadingAssetInfoBox({super.key}); + + @override + Widget build(BuildContext context) { + return ListTile( + isThreeLine: true, + leading: Icon(Icons.image_outlined, color: context.primaryColor, size: 30), + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("backup_controller_page_uploading_file_info", style: context.textTheme.titleSmall).tr(), + const BackupErrorChip(), + ], + ), + subtitle: Column( + children: [ + if (Platform.isIOS) const IcloudDownloadProgressBar(), + const BackupUploadProgressBar(), + const BackupUploadStats(), + const BackupAssetInfoTable(), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/drift_album_info_list_tile.dart b/mobile/lib/widgets/backup/drift_album_info_list_tile.dart index 41a215fb99..84128ddde2 100644 --- a/mobile/lib/widgets/backup/drift_album_info_list_tile.dart +++ b/mobile/lib/widgets/backup/drift_album_info_list_tile.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -22,7 +20,7 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { final bool isSelected = album.backupSelection == BackupSelection.selected; final bool isExcluded = album.backupSelection == BackupSelection.excluded; - Color? buildTileColor() { + buildTileColor() { if (isSelected) { return context.isDarkTheme ? context.primaryColor.withAlpha(100) : context.primaryColor.withAlpha(25); } else if (isExcluded) { @@ -32,7 +30,7 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { } } - Icon buildIcon() { + buildIcon() { if (isSelected) { return Icon(Icons.check_circle_rounded, color: context.colorScheme.primary); } @@ -53,10 +51,10 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { return GestureDetector( onDoubleTap: () { - ref.read(hapticFeedbackProvider.notifier).selectionClick(); + ref.watch(hapticFeedbackProvider.notifier).selectionClick(); if (isExcluded) { - unawaited(ref.read(backupAlbumProvider.notifier).deselectAlbum(album)); + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); } else { if (album.id == 'isAll' || album.name == 'Recents') { ImmichToast.show( @@ -68,7 +66,7 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { return; } - unawaited(ref.read(backupAlbumProvider.notifier).excludeAlbum(album)); + ref.read(backupAlbumProvider.notifier).excludeAlbum(album); } }, child: ListTile( @@ -77,9 +75,9 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { onTap: () { ref.read(hapticFeedbackProvider.notifier).selectionClick(); if (isSelected) { - unawaited(ref.read(backupAlbumProvider.notifier).deselectAlbum(album)); + ref.read(backupAlbumProvider.notifier).deselectAlbum(album); } else { - unawaited(ref.read(backupAlbumProvider.notifier).selectAlbum(album)); + ref.read(backupAlbumProvider.notifier).selectAlbum(album); } }, leading: buildIcon(), @@ -87,7 +85,7 @@ class DriftAlbumInfoListTile extends HookConsumerWidget { subtitle: buildSubtitle(), trailing: IconButton( onPressed: () { - unawaited(context.pushRoute(LocalTimelineRoute(album: album))); + context.pushRoute(LocalTimelineRoute(album: album)); }, icon: Icon(Icons.image_outlined, color: context.primaryColor, size: 24), splashRadius: 25, diff --git a/mobile/lib/widgets/backup/error_chip.dart b/mobile/lib/widgets/backup/error_chip.dart new file mode 100644 index 0000000000..191049cd75 --- /dev/null +++ b/mobile/lib/widgets/backup/error_chip.dart @@ -0,0 +1,28 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/backup/error_chip_text.dart'; + +class BackupErrorChip extends ConsumerWidget { + const BackupErrorChip({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final hasErrors = ref.watch(errorBackupListProvider.select((value) => value.isNotEmpty)); + if (!hasErrors) { + return const SizedBox(); + } + + return ActionChip( + avatar: const Icon(Icons.info, color: red400), + elevation: 1, + visualDensity: VisualDensity.compact, + label: const BackupErrorChipText(), + backgroundColor: Colors.white, + onPressed: () => context.pushRoute(const FailedBackupStatusRoute()), + ); + } +} diff --git a/mobile/lib/widgets/backup/error_chip_text.dart b/mobile/lib/widgets/backup/error_chip_text.dart new file mode 100644 index 0000000000..c987dfd331 --- /dev/null +++ b/mobile/lib/widgets/backup/error_chip_text.dart @@ -0,0 +1,22 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/constants/colors.dart'; +import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; + +class BackupErrorChipText extends ConsumerWidget { + const BackupErrorChipText({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final count = ref.watch(errorBackupListProvider).length; + if (count == 0) { + return const SizedBox(); + } + + return const Text( + "backup_controller_page_failed", + style: TextStyle(color: red400, fontWeight: FontWeight.bold, fontSize: 11), + ).tr(namedArgs: {'count': count.toString()}); + } +} diff --git a/mobile/lib/widgets/backup/icloud_download_progress_bar.dart b/mobile/lib/widgets/backup/icloud_download_progress_bar.dart new file mode 100644 index 0000000000..9f0f7ec3eb --- /dev/null +++ b/mobile/lib/widgets/backup/icloud_download_progress_bar.dart @@ -0,0 +1,43 @@ +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/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class IcloudDownloadProgressBar extends ConsumerWidget { + const IcloudDownloadProgressBar({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select((value) => value.backupProgress == BackUpProgressEnum.manualInProgress), + ); + + final isIcloudAsset = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.currentUploadAsset.isIcloudAsset)) + : ref.watch(backupProvider.select((value) => value.currentUploadAsset.isIcloudAsset)); + + if (!isIcloudAsset) { + return const SizedBox(); + } + + final iCloudDownloadProgress = ref.watch(backupProvider.select((value) => value.iCloudDownloadProgress)); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + children: [ + SizedBox(width: 110, child: Text("iCloud Download", style: context.textTheme.labelSmall)), + Expanded( + child: LinearProgressIndicator( + minHeight: 10.0, + value: iCloudDownloadProgress / 100.0, + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + ), + ), + Text(" ${iCloudDownloadProgress ~/ 1}%", style: const TextStyle(fontSize: 12)), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/ios_debug_info_tile.dart b/mobile/lib/widgets/backup/ios_debug_info_tile.dart new file mode 100644 index 0000000000..be333c6460 --- /dev/null +++ b/mobile/lib/widgets/backup/ios_debug_info_tile.dart @@ -0,0 +1,49 @@ +import 'package:intl/intl.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/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; + +/// This is a simple debug widget which should be removed later on when we are +/// more confident about background sync +class IosDebugInfoTile extends HookConsumerWidget { + final IOSBackgroundSettings settings; + const IosDebugInfoTile({super.key, required this.settings}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final fetch = settings.timeOfLastFetch; + final processing = settings.timeOfLastProcessing; + final processes = settings.numberOfBackgroundTasksQueued; + + final String title; + if (processes == 0) { + title = 'ios_debug_info_no_processes_queued'.t(context: context); + } else { + title = 'ios_debug_info_processes_queued'.t(context: context, args: {'count': processes}); + } + + final df = DateFormat.yMd().add_jm(); + final String subtitle; + if (fetch == null && processing == null) { + subtitle = 'ios_debug_info_no_sync_yet'.t(context: context); + } else if (fetch != null && processing == null) { + subtitle = 'ios_debug_info_fetch_ran_at'.t(context: context, args: {'dateTime': df.format(fetch)}); + } else if (processing != null && fetch == null) { + subtitle = 'ios_debug_info_processing_ran_at'.t(context: context, args: {'dateTime': df.format(processing)}); + } else { + final fetchOrProcessing = fetch!.isAfter(processing!) ? fetch : processing; + subtitle = 'ios_debug_info_last_sync_at'.t(context: context, args: {'dateTime': df.format(fetchOrProcessing)}); + } + + return ListTile( + title: Text( + title, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: context.primaryColor), + ), + subtitle: Text(subtitle, style: const TextStyle(fontSize: 14)), + leading: Icon(Icons.bug_report, color: context.primaryColor), + ); + } +} diff --git a/mobile/lib/widgets/backup/upload_progress_bar.dart b/mobile/lib/widgets/backup/upload_progress_bar.dart new file mode 100644 index 0000000000..641ed14878 --- /dev/null +++ b/mobile/lib/widgets/backup/upload_progress_bar.dart @@ -0,0 +1,45 @@ +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/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupUploadProgressBar extends ConsumerWidget { + const BackupUploadProgressBar({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select((value) => value.backupProgress == BackUpProgressEnum.manualInProgress), + ); + + final isIcloudAsset = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.currentUploadAsset.isIcloudAsset)) + : ref.watch(backupProvider.select((value) => value.currentUploadAsset.isIcloudAsset)); + + final uploadProgress = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.progressInPercentage)) + : ref.watch(backupProvider.select((value) => value.progressInPercentage)); + + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Row( + children: [ + if (isIcloudAsset) SizedBox(width: 110, child: Text("Immich Upload", style: context.textTheme.labelSmall)), + Expanded( + child: LinearProgressIndicator( + minHeight: 10.0, + value: uploadProgress / 100.0, + borderRadius: const BorderRadius.all(Radius.circular(10.0)), + ), + ), + Text( + " ${uploadProgress.toStringAsFixed(0)}%", + style: const TextStyle(fontSize: 12, fontFamily: "GoogleSansCode"), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/backup/upload_stats.dart b/mobile/lib/widgets/backup/upload_stats.dart new file mode 100644 index 0000000000..38f99e53fc --- /dev/null +++ b/mobile/lib/widgets/backup/upload_stats.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; + +class BackupUploadStats extends ConsumerWidget { + const BackupUploadStats({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isManualUpload = ref.watch( + backupProvider.select((value) => value.backupProgress == BackUpProgressEnum.manualInProgress), + ); + + final uploadFileProgress = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.progressInFileSize)) + : ref.watch(backupProvider.select((value) => value.progressInFileSize)); + + final uploadFileSpeed = isManualUpload + ? ref.watch(manualUploadProvider.select((value) => value.progressInFileSpeed)) + : ref.watch(backupProvider.select((value) => value.progressInFileSpeed)); + + return Padding( + padding: const EdgeInsets.only(top: 2.0, bottom: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(uploadFileProgress, style: const TextStyle(fontSize: 10, fontFamily: "GoogleSansCode")), + Text( + _formatUploadFileSpeed(uploadFileSpeed), + style: const TextStyle(fontSize: 10, fontFamily: "GoogleSansCode"), + ), + ], + ), + ); + } + + @pragma('vm:prefer-inline') + String _formatUploadFileSpeed(double uploadFileSpeed) { + if (uploadFileSpeed < 1024) { + return '${uploadFileSpeed.toStringAsFixed(2)} B/s'; + } else if (uploadFileSpeed < 1024 * 1024) { + return '${(uploadFileSpeed / 1024).toStringAsFixed(2)} KB/s'; + } else if (uploadFileSpeed < 1024 * 1024 * 1024) { + return '${(uploadFileSpeed / (1024 * 1024)).toStringAsFixed(2)} MB/s'; + } else { + return '${(uploadFileSpeed / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB/s'; + } + } +} diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart index fffa6746f6..c330fb4649 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_dialog.dart @@ -1,19 +1,22 @@ import 'dart:async'; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; -import 'package:immich_mobile/pages/common/settings.page.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; +import 'package:immich_mobile/pages/common/settings.page.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_profile_info.dart'; @@ -29,28 +32,28 @@ class ImmichAppBarDialog extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { ref.watch(localeProvider); - final ServerDiskInfo backupState = ref.watch(backupProvider); + BackUpState backupState = ref.watch(backupProvider); final theme = context.themeData; - final bool isHorizontal = !context.isMobile; + bool isHorizontal = !context.isMobile; final horizontalPadding = isHorizontal ? 100.0 : 20.0; final user = ref.watch(currentUserProvider); final isLoggingOut = useState(false); final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); useEffect(() { - unawaited(ref.read(backupProvider.notifier).updateDiskInfo()); - unawaited(ref.read(currentUserProvider.notifier).refresh()); + ref.read(backupProvider.notifier).updateDiskInfo(); + ref.read(currentUserProvider.notifier).refresh(); return null; }, []); - SizedBox buildTopRow() { + buildTopRow() { return SizedBox( height: 56, child: Stack( alignment: Alignment.centerLeft, children: [ IconButton( - onPressed: () => ContextHelper(context).pop(), + onPressed: () => context.pop(), icon: Icon(Icons.close, size: 20, color: context.colorScheme.onSurfaceVariant), ), Align( @@ -68,7 +71,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { ); } - ListTile buildActionButton(IconData icon, String text, Function() onTap, {Widget? trailing}) { + buildActionButton(IconData icon, String text, Function() onTap, {Widget? trailing}) { return ListTile( dense: true, visualDensity: VisualDensity.standard, @@ -78,40 +81,36 @@ class ImmichAppBarDialog extends HookConsumerWidget { title: Text( text, style: theme.textTheme.labelLarge?.copyWith(color: theme.textTheme.labelLarge?.color?.withAlpha(250)), - ), + ).tr(), onTap: onTap, trailing: trailing, ); } - ListTile buildSettingButton() { - return buildActionButton( - Icons.settings_outlined, - context.t.settings, - () => context.pushRoute(const SettingsRoute()), - ); + buildSettingButton() { + return buildActionButton(Icons.settings_outlined, "settings", () => context.pushRoute(const SettingsRoute())); } - ListTile buildFreeUpSpaceButton() { + buildFreeUpSpaceButton() { return buildActionButton( Icons.cleaning_services_outlined, - context.t.free_up_space, + "free_up_space", () => context.pushRoute(SettingsSubRoute(section: SettingSection.freeUpSpace)), ); } - ListTile buildAppLogButton() { + buildAppLogButton() { return buildActionButton( Icons.assignment_outlined, - context.t.profile_drawer_app_logs, + "profile_drawer_app_logs", () => context.pushRoute(const AppLogRoute()), ); } - ListTile buildSignOutButton() { + buildSignOutButton() { return buildActionButton( Icons.logout_rounded, - context.t.sign_out, + "sign_out", () async { if (isLoggingOut.value) { return; @@ -122,21 +121,17 @@ class ImmichAppBarDialog extends HookConsumerWidget { context: context, builder: (BuildContext ctx) { return ConfirmDialog( - title: context.t.app_bar_signout_dialog_title, - content: context.t.app_bar_signout_dialog_content, - ok: context.t.yes, + title: "app_bar_signout_dialog_title", + content: "app_bar_signout_dialog_content", + ok: "yes", onOk: () async { isLoggingOut.value = true; await ref.read(authProvider.notifier).logout().whenComplete(() => isLoggingOut.value = false); - if (!context.mounted) { - return; - } + ref.read(manualUploadProvider.notifier).cancelBackup(); + ref.read(backupProvider.notifier).cancelBackup(); + unawaited(ref.read(assetProvider.notifier).clearAllAssets()); ref.read(websocketProvider.notifier).disconnect(); - if (!context.mounted) { - return; - } - unawaited(context.replaceRoute(const LoginRoute())); }, ); @@ -151,9 +146,9 @@ class ImmichAppBarDialog extends HookConsumerWidget { } Widget buildStorageInformation() { - var percentage = backupState.diskUsagePercentage / 100; - var usedDiskSpace = backupState.diskUse; - var totalDiskSpace = backupState.diskSize; + var percentage = backupState.serverInfo.diskUsagePercentage / 100; + var usedDiskSpace = backupState.serverInfo.diskUse; + var totalDiskSpace = backupState.serverInfo.diskSize; if (user != null && user.hasQuota) { usedDiskSpace = formatBytes(user.quotaUsageInBytes); @@ -167,22 +162,22 @@ class ImmichAppBarDialog extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 12, children: [ - Text(context.t.backup_controller_page_server_storage, style: context.textTheme.labelLarge), + Text("backup_controller_page_server_storage".tr(), style: context.textTheme.labelLarge), LinearProgressIndicator( minHeight: 10.0, value: percentage, borderRadius: const BorderRadius.all(Radius.circular(10.0)), ), Text( - context.t.backup_controller_page_storage_format(used: usedDiskSpace, total: totalDiskSpace), + 'backup_controller_page_storage_format', style: context.textTheme.bodySmall, - ), + ).tr(namedArgs: {'used': usedDiskSpace, 'total': totalDiskSpace}), ], ), ); } - Padding buildFooter() { + buildFooter() { return Padding( padding: const EdgeInsets.only(top: 10, bottom: 20), child: Row( @@ -190,30 +185,24 @@ class ImmichAppBarDialog extends HookConsumerWidget { children: [ InkWell( onTap: () { - ContextHelper(context).pop(); - unawaited(launchUrl(Uri.parse('https://docs.immich.app'), mode: LaunchMode.externalApplication)); + context.pop(); + launchUrl(Uri.parse('https://docs.immich.app'), mode: LaunchMode.externalApplication); }, - child: Text(context.t.documentation, style: context.textTheme.bodySmall), + child: Text("documentation", style: context.textTheme.bodySmall).tr(), ), const SizedBox(width: 20, child: Text("â€ĸ", textAlign: TextAlign.center)), InkWell( onTap: () { - ContextHelper(context).pop(); - unawaited( - launchUrl(Uri.parse('https://github.com/immich-app/immich'), mode: LaunchMode.externalApplication), - ); + context.pop(); + launchUrl(Uri.parse('https://github.com/immich-app/immich'), mode: LaunchMode.externalApplication); }, - child: Text(context.t.profile_drawer_github, style: context.textTheme.bodySmall), + child: Text("profile_drawer_github", style: context.textTheme.bodySmall).tr(), ), const SizedBox(width: 20, child: Text("â€ĸ", textAlign: TextAlign.center)), InkWell( onTap: () async { - ContextHelper(context).pop(); + context.pop(); final packageInfo = await PackageInfo.fromPlatform(); - if (!context.mounted) { - return; - } - showLicensePage( context: context, applicationIcon: const Padding( @@ -223,14 +212,14 @@ class ImmichAppBarDialog extends HookConsumerWidget { applicationVersion: packageInfo.version, ); }, - child: Text(context.t.licenses, style: context.textTheme.bodySmall), + child: Text("licenses", style: context.textTheme.bodySmall).tr(), ), ], ), ); } - Padding buildReadonlyMessage() { + buildReadonlyMessage() { return Padding( padding: const EdgeInsets.only(left: 10.0, right: 10.0), child: ListTile( @@ -241,10 +230,10 @@ class ImmichAppBarDialog extends HookConsumerWidget { minLeadingWidth: 20, tileColor: theme.primaryColor.withAlpha(80), title: Text( - context.t.profile_drawer_readonly_mode, + "profile_drawer_readonly_mode", style: theme.textTheme.labelLarge?.copyWith(color: theme.textTheme.labelLarge?.color?.withAlpha(250)), textAlign: TextAlign.center, - ), + ).tr(), ), ); } @@ -252,7 +241,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { return Dismissible( behavior: HitTestBehavior.translucent, direction: DismissDirection.down, - onDismissed: (_) => ContextHelper(context).pop(), + onDismissed: (_) => context.pop(), key: const Key('app_bar_dialog'), child: Dialog( clipBehavior: Clip.hardEdge, @@ -286,7 +275,7 @@ class ImmichAppBarDialog extends HookConsumerWidget { ], ), ), - if (isReadonlyModeEnabled) buildReadonlyMessage(), + if (Store.isBetaTimelineEnabled && isReadonlyModeEnabled) buildReadonlyMessage(), buildAppLogButton(), buildFreeUpSpaceButton(), buildSettingButton(), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart index bd8e883ef0..a9fdb9a43f 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_profile_info.dart @@ -1,11 +1,12 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; @@ -24,7 +25,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget { final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); final user = ref.watch(currentUserProvider); - Widget buildUserProfileImage() { + buildUserProfileImage() { if (user == null) { return const CircleAvatar( radius: 20, @@ -42,17 +43,17 @@ class AppBarProfileInfoBox extends HookConsumerWidget { return userImage; } - Future pickUserProfileImage() async { + pickUserProfileImage() async { final XFile? image = await ImagePicker().pickImage(source: ImageSource.gallery, maxHeight: 1024, maxWidth: 1024); - if (image != null && context.mounted) { - final success = await ref.read(uploadProfileImageProvider.notifier).upload(image); + if (image != null) { + var success = await ref.watch(uploadProfileImageProvider.notifier).upload(image); - if (success && context.mounted) { + if (success) { final profileImagePath = ref.read(uploadProfileImageProvider).profileImagePath; - ref.read(authProvider.notifier).updateUserProfileImagePath(profileImagePath); + ref.watch(authProvider.notifier).updateUserProfileImagePath(profileImagePath); if (user != null) { - unawaited(ref.read(currentUserProvider.notifier).refresh()); + ref.read(currentUserProvider.notifier).refresh(); } unawaited(ref.read(backupProvider.notifier).updateDiskInfo()); @@ -61,14 +62,18 @@ class AppBarProfileInfoBox extends HookConsumerWidget { } void toggleReadonlyMode() { - final isReadonlyModeEnabled = ref.read(readonlyModeProvider); + // read only mode is only supported int he beta experience + // TODO: remove this check when the beta UI goes stable + if (!Store.isBetaTimelineEnabled) return; + + final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); ref.read(readonlyModeProvider.notifier).toggleReadonlyMode(); context.scaffoldMessenger.showSnackBar( SnackBar( duration: const Duration(seconds: 2), content: Text( - isReadonlyModeEnabled ? context.t.readonly_mode_disabled : context.t.readonly_mode_enabled, + (isReadonlyModeEnabled ? "readonly_mode_disabled" : "readonly_mode_enabled").tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), ), diff --git a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart index 901ffb2496..2809505c58 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/app_bar_server_info.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -20,20 +18,20 @@ class AppBarServerInfo extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { ref.watch(localeProvider); - final ServerInfo serverInfoState = ref.watch(serverInfoProvider); + ServerInfo serverInfoState = ref.watch(serverInfoProvider); final user = ref.watch(currentUserProvider); final bool showVersionWarning = ref.watch(versionWarningPresentProvider(user)); final appInfo = useState({}); - Future getPackageInfo() async { - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); + getPackageInfo() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); appInfo.value = {"version": packageInfo.version, "buildNumber": packageInfo.buildNumber}; } useEffect(() { - unawaited(getPackageInfo()); + getPackageInfo(); return null; }, []); @@ -46,21 +44,25 @@ class AppBarServerInfo extends HookConsumerWidget { children: [ if (showVersionWarning) ...[const ServerUpdateNotification(), divider], _ServerInfoItem( - label: context.t.server_info_box_app_version, + label: "server_info_box_app_version".tr(), text: "${appInfo.value["version"]} build.${appInfo.value["buildNumber"]}", ), divider, _ServerInfoItem( - label: context.t.server_version, - text: serverInfoState.serverVersion.major > 0 ? "${serverInfoState.serverVersion}" : "--", + label: "server_version".tr(), + text: serverInfoState.serverVersion.major > 0 + ? "${serverInfoState.serverVersion.major}.${serverInfoState.serverVersion.minor}.${serverInfoState.serverVersion.patch}" + : "--", ), divider, - _ServerInfoItem(label: context.t.server_info_box_server_url, text: getServerUrl() ?? '--', tooltip: true), + _ServerInfoItem(label: "server_info_box_server_url".tr(), text: getServerUrl() ?? '--', tooltip: true), if (serverInfoState.latestVersion != null) ...[ divider, _ServerInfoItem( - label: context.t.latest_version, - text: serverInfoState.latestVersion!.major > 0 ? "${serverInfoState.latestVersion!}" : "--", + label: "latest_version".tr(), + text: serverInfoState.latestVersion!.major > 0 + ? "${serverInfoState.latestVersion!.major}.${serverInfoState.latestVersion!.minor}.${serverInfoState.latestVersion!.patch}" + : "--", tooltip: true, icon: serverInfoState.versionStatus == VersionStatus.serverOutOfDate ? const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: 12) @@ -89,7 +91,7 @@ class _ServerInfoItem extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (icon != null) ...[icon! as Widget, const SizedBox(width: 8)], + if (icon != null) ...[icon as Widget, const SizedBox(width: 8)], Text( label, style: TextStyle( diff --git a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart index 8261f71ad8..179eab8e7d 100644 --- a/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart +++ b/mobile/lib/widgets/common/app_bar_dialog/server_update_notification.dart @@ -1,11 +1,10 @@ -import 'dart:async'; import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/constants.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -17,11 +16,9 @@ class ServerUpdateNotification extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final serverInfoState = ref.watch(serverInfoProvider); - const Color errorColor = Color.fromARGB(85, 253, 97, 83); - final Color infoColor = context.isDarkTheme - ? context.primaryColor.withAlpha(55) - : context.primaryColor.withAlpha(25); - Future openUpdateLink() { + Color errorColor = const Color.fromARGB(85, 253, 97, 83); + Color infoColor = context.isDarkTheme ? context.primaryColor.withAlpha(55) : context.primaryColor.withAlpha(25); + void openUpdateLink() { String url; if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate) { url = kImmichLatestRelease; @@ -36,7 +33,7 @@ class ServerUpdateNotification extends HookConsumerWidget { } } - return launchUrlString(url, mode: LaunchMode.externalApplication); + launchUrlString(url, mode: LaunchMode.externalApplication); } return SizedBox( @@ -69,15 +66,15 @@ class ServerUpdateNotification extends HookConsumerWidget { serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[ const SizedBox(width: 8), TextButton( - onPressed: () => unawaited(openUpdateLink()), + onPressed: openUpdateLink, style: TextButton.styleFrom( padding: const EdgeInsets.all(4), - minimumSize: Size.zero, + minimumSize: const Size(0, 0), tapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: serverInfoState.versionStatus == VersionStatus.clientOutOfDate - ? Text(context.t.action_common_update) - : Text(context.t.view), + ? Text("action_common_update".tr(context: context)) + : Text("view".tr()), ), ], ], diff --git a/mobile/lib/widgets/common/confirm_dialog.dart b/mobile/lib/widgets/common/confirm_dialog.dart index 74b7f4a04c..153c124595 100644 --- a/mobile/lib/widgets/common/confirm_dialog.dart +++ b/mobile/lib/widgets/common/confirm_dialog.dart @@ -1,41 +1,48 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class ConfirmDialog extends StatelessWidget { - final Function? onOk; + final Function onOk; final String title; final String content; - final String? cancel; - final String? ok; + final String cancel; + final String ok; - const ConfirmDialog({super.key, this.onOk, required this.title, required this.content, this.cancel, this.ok}); + const ConfirmDialog({ + super.key, + required this.onOk, + required this.title, + required this.content, + this.cancel = "cancel", + this.ok = "backup_controller_page_background_battery_info_ok", + }); @override Widget build(BuildContext context) { void onOkPressed() { - onOk?.call(); + onOk(); context.pop(true); } return AlertDialog( shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), - title: Text(title), - content: Text(content), + title: Text(title).tr(), + content: Text(content).tr(), actions: [ TextButton( onPressed: () => context.pop(false), child: Text( - cancel ?? context.t.cancel, + cancel, style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), - ), + ).tr(), ), TextButton( onPressed: onOkPressed, child: Text( - ok ?? context.t.backup_controller_page_background_battery_info_ok, + ok, style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.bold), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/widgets/common/date_time_picker.dart b/mobile/lib/widgets/common/date_time_picker.dart index 6b4f714e5f..9cc8de29ee 100644 --- a/mobile/lib/widgets/common/date_time_picker.dart +++ b/mobile/lib/widgets/common/date_time_picker.dart @@ -1,17 +1,13 @@ import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/widgets/common/dropdown_search_menu.dart'; -import 'package:intl/intl.dart'; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart'; -part 'date_time_picker.freezed.dart'; - Future showDateTimePicker({ required BuildContext context, DateTime? initialDateTime, @@ -45,7 +41,7 @@ class _DateTimePicker extends HookWidget { } } - final Duration? tzOffset = initialTZOffset ?? initialDateTime?.timeZoneOffset; + Duration? tzOffset = initialTZOffset ?? initialDateTime?.timeZoneOffset; if (tzOffset != null) { final offsetInMilli = tzOffset.inMilliseconds; @@ -84,7 +80,7 @@ class _DateTimePicker extends HookWidget { ) .toList(); - Future pickDate() async { + void pickDate() async { final now = DateTime.now(); // Handles cases where the date from the asset is far off in the future final initialDate = date.value.isAfter(now) ? now : date.value; @@ -94,7 +90,7 @@ class _DateTimePicker extends HookWidget { firstDate: DateTime(1800), lastDate: now, ); - if (newDate == null || !context.mounted) { + if (newDate == null) { return; } @@ -120,26 +116,26 @@ class _DateTimePicker extends HookWidget { TextButton( onPressed: () => context.pop(), child: Text( - context.t.cancel, + "cancel", style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: context.colorScheme.error, ), - ), + ).tr(), ), TextButton( onPressed: popWithDateTime, child: Text( - context.t.action_common_update, + "action_common_update", style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), - ), + ).tr(), ), ], content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(context.t.date_and_time, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), + const Text("date_and_time", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)).tr(), const SizedBox(height: 32), ListTile( tileColor: context.colorScheme.surfaceContainerHighest, @@ -155,8 +151,8 @@ class _DateTimePicker extends HookWidget { const SizedBox(height: 24), DropdownSearchMenu( trailingIcon: Icon(Icons.arrow_drop_down, color: context.primaryColor), - hintText: context.t.timezone, - label: Text(context.t.timezone), + hintText: "timezone".tr(), + label: const Text('timezone').tr(), textStyle: context.textTheme.bodyMedium, onSelected: (value) => tzOffset.value = value, initialSelection: tzOffset.value, @@ -168,19 +164,37 @@ class _DateTimePicker extends HookWidget { } } -@freezed -abstract class _TimeZoneOffset with _$TimeZoneOffset implements Comparable<_TimeZoneOffset> { - const _TimeZoneOffset._(); +class _TimeZoneOffset implements Comparable<_TimeZoneOffset> { + final String display; + final Location location; - const factory _TimeZoneOffset({required String display, required Location location}) = __TimeZoneOffset; + const _TimeZoneOffset({required this.display, required this.location}); - factory _TimeZoneOffset.fromLocation(tz.Location l) => - _TimeZoneOffset(display: _getFormattedOffset(l.currentTimeZone.offset, l), location: l); + _TimeZoneOffset copyWith({String? display, Location? location}) { + return _TimeZoneOffset(display: display ?? this.display, location: location ?? this.location); + } int get offsetInMilliseconds => location.currentTimeZone.offset; + _TimeZoneOffset.fromLocation(tz.Location l) + : display = _getFormattedOffset(l.currentTimeZone.offset, l), + location = l; + @override int compareTo(_TimeZoneOffset other) { return offsetInMilliseconds.compareTo(other.offsetInMilliseconds); } + + @override + String toString() => '_TimeZoneOffset(display: $display, location: $location)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is _TimeZoneOffset && other.display == display && other.offsetInMilliseconds == offsetInMilliseconds; + } + + @override + int get hashCode => display.hashCode ^ offsetInMilliseconds.hashCode ^ location.hashCode; } diff --git a/mobile/lib/widgets/common/drag_sheet.dart b/mobile/lib/widgets/common/drag_sheet.dart new file mode 100644 index 0000000000..5d1fda1beb --- /dev/null +++ b/mobile/lib/widgets/common/drag_sheet.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; + +class CustomDraggingHandle extends StatelessWidget { + const CustomDraggingHandle({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + height: 4, + width: 30, + decoration: BoxDecoration( + color: context.themeData.dividerColor, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + ); + } +} + +class ControlBoxButton extends StatelessWidget { + const ControlBoxButton({super.key, required this.label, required this.iconData, this.onPressed, this.onLongPressed}); + + final String label; + final IconData iconData; + final void Function()? onPressed; + final void Function()? onLongPressed; + + @override + Widget build(BuildContext context) { + final minWidth = context.isMobile ? MediaQuery.sizeOf(context).width / 4.5 : 75.0; + + return MaterialButton( + padding: const EdgeInsets.all(10), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))), + onPressed: onPressed, + onLongPress: onLongPressed, + minWidth: minWidth, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon(iconData, size: 24), + const SizedBox(height: 8), + Text( + label, + style: const TextStyle(fontSize: 14.0, fontWeight: FontWeight.w400), + maxLines: 3, + textAlign: TextAlign.center, + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/common/dropdown_search_menu.dart b/mobile/lib/widgets/common/dropdown_search_menu.dart index ba40bb98d5..bf0c75c8aa 100644 --- a/mobile/lib/widgets/common/dropdown_search_menu.dart +++ b/mobile/lib/widgets/common/dropdown_search_menu.dart @@ -1,11 +1,9 @@ -import 'dart:async'; - import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class DropdownSearchMenu extends HookWidget { const DropdownSearchMenu({ @@ -81,7 +79,7 @@ class DropdownSearchMenu extends HookWidget { autofocus: true, focusNode: focusNode, controller: textEditingController, - decoration: inputDecoration.copyWith(hintText: context.t.search_timezone), + decoration: inputDecoration.copyWith(hintText: "search_timezone".tr()), maxLines: 1, style: context.textTheme.bodyMedium, expands: false, @@ -116,7 +114,7 @@ class DropdownSearchMenu extends HookWidget { final bool highlight = AutocompleteHighlightedOption.of(context) == index; if (highlight) { SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { - unawaited(Scrollable.ensureVisible(context, alignment: 0.5)); + Scrollable.ensureVisible(context, alignment: 0.5); }, debugLabel: 'AutocompleteOptions.ensureVisible'); } return Container( diff --git a/mobile/lib/widgets/common/immich_app_bar.dart b/mobile/lib/widgets/common/immich_app_bar.dart new file mode 100644 index 0000000000..56b7e91eec --- /dev/null +++ b/mobile/lib/widgets/common/immich_app_bar.dart @@ -0,0 +1,170 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/backup/backup_state.model.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/cast.provider.dart'; +import 'package:immich_mobile/providers/server_info.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart'; +import 'package:immich_mobile/widgets/common/app_bar_dialog/app_bar_dialog.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; + +class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget { + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + final List? actions; + final bool showUploadButton; + + const ImmichAppBar({super.key, this.actions, this.showUploadButton = true}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final BackUpState backupState = ref.watch(backupProvider); + final bool isEnableAutoBackup = backupState.backgroundBackup || backupState.autoBackup; + final user = ref.watch(currentUserProvider); + final bool versionWarningPresent = ref.watch(versionWarningPresentProvider(user)); + final isDarkTheme = context.isDarkTheme; + const widgetSize = 30.0; + final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + + buildProfileIndicator() { + return InkWell( + onTap: () => + showDialog(context: context, useRootNavigator: false, builder: (ctx) => const ImmichAppBarDialog()), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Badge( + label: Container( + decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(widgetSize / 2)), + child: const Icon(Icons.info, color: Color.fromARGB(255, 243, 188, 106), size: widgetSize / 2), + ), + backgroundColor: Colors.transparent, + alignment: Alignment.bottomRight, + isLabelVisible: versionWarningPresent, + offset: const Offset(-2, -12), + child: user == null + ? const Icon(Icons.face_outlined, size: widgetSize) + : Semantics( + label: "logged_in_as".tr(namedArgs: {"user": user.name}), + child: UserCircleAvatar(size: 32, user: user), + ), + ), + ); + } + + getBackupBadgeIcon() { + final iconColor = isDarkTheme ? Colors.white : Colors.black; + + if (isEnableAutoBackup) { + if (backupState.backupProgress == BackUpProgressEnum.inProgress) { + return Container( + padding: const EdgeInsets.all(3.5), + child: CircularProgressIndicator( + strokeWidth: 2, + strokeCap: StrokeCap.round, + valueColor: AlwaysStoppedAnimation(iconColor), + semanticsLabel: 'backup_controller_page_backup'.tr(), + ), + ); + } else if (backupState.backupProgress != BackUpProgressEnum.inBackground && + backupState.backupProgress != BackUpProgressEnum.manualInProgress) { + return Icon( + Icons.check_outlined, + size: 9, + color: iconColor, + semanticLabel: 'backup_controller_page_backup'.tr(), + ); + } + } + + if (!isEnableAutoBackup) { + return Icon( + Icons.cloud_off_rounded, + size: 9, + color: iconColor, + semanticLabel: 'backup_controller_page_backup'.tr(), + ); + } + } + + buildBackupIndicator() { + final indicatorIcon = getBackupBadgeIcon(); + final badgeBackground = context.colorScheme.surfaceContainer; + + return InkWell( + onTap: () => context.pushRoute(const BackupControllerRoute()), + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Badge( + label: Container( + width: widgetSize / 2, + height: widgetSize / 2, + decoration: BoxDecoration( + color: badgeBackground, + border: Border.all(color: context.colorScheme.outline.withValues(alpha: .3)), + borderRadius: BorderRadius.circular(widgetSize / 2), + ), + child: indicatorIcon, + ), + backgroundColor: Colors.transparent, + alignment: Alignment.bottomRight, + isLabelVisible: indicatorIcon != null, + offset: const Offset(-2, -12), + child: Icon(Icons.backup_rounded, size: widgetSize, color: context.primaryColor), + ), + ); + } + + return AppBar( + backgroundColor: context.themeData.appBarTheme.backgroundColor, + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), + automaticallyImplyLeading: false, + centerTitle: false, + title: Builder( + builder: (BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top: 3.0), + child: SvgPicture.asset( + context.isDarkTheme ? 'assets/immich-logo-inline-dark.svg' : 'assets/immich-logo-inline-light.svg', + height: 40, + ), + ), + const Tooltip( + triggerMode: TooltipTriggerMode.tap, + showDuration: Duration(seconds: 4), + message: + "The old timeline is deprecated and will be removed in a future release. Kindly switch to the new timeline under Advanced Settings.", + child: Padding( + padding: EdgeInsets.only(top: 3.0), + child: Icon(Icons.error_rounded, fill: 1, color: Colors.amber, size: 20), + ), + ), + ], + ); + }, + ), + actions: [ + if (actions != null) + ...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)), + if (isCasting) + Padding( + padding: const EdgeInsets.only(right: 12), + child: IconButton( + onPressed: () { + showDialog(context: context, builder: (context) => const CastDialog()); + }, + icon: Icon(isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded), + ), + ), + if (showUploadButton) Padding(padding: const EdgeInsets.only(right: 20), child: buildBackupIndicator()), + Padding(padding: const EdgeInsets.only(right: 20), child: buildProfileIndicator()), + ], + ); + } +} diff --git a/mobile/lib/widgets/common/immich_image.dart b/mobile/lib/widgets/common/immich_image.dart new file mode 100644 index 0000000000..57978e83ff --- /dev/null +++ b/mobile/lib/widgets/common/immich_image.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; +import 'package:octo_image/octo_image.dart'; + +class ImmichImage extends StatelessWidget { + const ImmichImage( + this.asset, { + this.width, + this.height, + this.fit = BoxFit.cover, + this.placeholder = const ThumbnailPlaceholder(), + super.key, + }); + + final Asset? asset; + final Widget? placeholder; + final double? width; + final double? height; + final BoxFit fit; + + // Helper function to return the image provider for the asset + // either by using the asset ID or the asset itself + /// [asset] is the Asset to request, or else use [assetId] to get a remote + /// image provider + static ImageProvider imageProvider({Asset? asset, String? assetId, double width = 1080, double height = 1920}) { + if (asset == null && assetId == null) { + throw Exception('Must supply either asset or assetId'); + } + + if (asset == null) { + return RemoteFullImageProvider( + assetId: assetId!, + thumbhash: '', + assetType: base_asset.AssetType.video, + isAnimated: false, + ); + } + + if (useLocal(asset)) { + return LocalFullImageProvider( + id: asset.localId!, + assetType: base_asset.AssetType.video, + size: Size(width, height), + isAnimated: false, + ); + } else { + return RemoteFullImageProvider( + assetId: asset.remoteId!, + thumbhash: asset.thumbhash ?? '', + assetType: base_asset.AssetType.video, + isAnimated: false, + ); + } + } + + // Whether to use the local asset image provider or a remote one + static bool useLocal(Asset asset) => + !asset.isRemote || asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false); + + @override + Widget build(BuildContext context) { + if (asset == null) { + return Container( + color: Colors.grey, + width: width, + height: height, + child: const Center(child: Icon(Icons.no_photography)), + ); + } + + final imageProviderInstance = ImmichImage.imageProvider(asset: asset, width: context.width, height: context.height); + + return OctoImage( + fadeInDuration: const Duration(milliseconds: 0), + fadeOutDuration: const Duration(milliseconds: 100), + placeholderBuilder: (context) { + if (placeholder != null) { + return placeholder!; + } + return const SizedBox(); + }, + image: imageProviderInstance, + width: width, + height: height, + fit: fit, + errorBuilder: (context, error, stackTrace) { + imageProviderInstance.evict(); + + return Icon(Icons.image_not_supported_outlined, size: 32, color: Colors.red[200]); + }, + ); + } +} diff --git a/mobile/lib/widgets/common/immich_loading_indicator.dart b/mobile/lib/widgets/common/immich_loading_indicator.dart index 1fdc5213df..52f957f7e7 100644 --- a/mobile/lib/widgets/common/immich_loading_indicator.dart +++ b/mobile/lib/widgets/common/immich_loading_indicator.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; @@ -11,12 +9,11 @@ class ImmichLoadingIndicator extends HookWidget { @override Widget build(BuildContext context) { - final logoAnimationController = useAnimationController(duration: const Duration(seconds: 6)); - unawaited(logoAnimationController.reverse()); - unawaited(logoAnimationController.repeat()); + final logoAnimationController = useAnimationController(duration: const Duration(seconds: 6)) + ..reverse() + ..repeat(); - final borderAnimationController = useAnimationController(duration: const Duration(seconds: 6)); - unawaited(borderAnimationController.repeat()); + final borderAnimationController = useAnimationController(duration: const Duration(seconds: 6))..repeat(); return Container( height: 80, diff --git a/mobile/lib/widgets/common/immich_sliver_app_bar.dart b/mobile/lib/widgets/common/immich_sliver_app_bar.dart index b966601c31..cb429c9f48 100644 --- a/mobile/lib/widgets/common/immich_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/immich_sliver_app_bar.dart @@ -1,18 +1,18 @@ -import 'dart:async'; import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/setting.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/server_info/server_info.model.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/cast.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; @@ -73,7 +73,7 @@ class ImmichSliverAppBar extends ConsumerWidget { onPressed: () => showDialog(context: context, builder: (context) => const CastDialog()), icon: Icon(isCasting ? Icons.cast_connected_rounded : Icons.cast_rounded), ), - ...?actions, + if (actions != null) ...actions!, if (showUploadButton && !isReadonlyModeEnabled) const _BackupIndicator(), const _ProfileIndicator(), const SizedBox(width: 8), @@ -113,14 +113,14 @@ class _ProfileIndicator extends ConsumerWidget { final isIpad = defaultTargetPlatform == TargetPlatform.iOS && !context.isMobile; void toggleReadonlyMode() { - final isReadonlyModeEnabled = ref.read(readonlyModeProvider); + final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); ref.read(readonlyModeProvider.notifier).toggleReadonlyMode(); context.scaffoldMessenger.showSnackBar( SnackBar( duration: const Duration(seconds: 2), content: Text( - isReadonlyModeEnabled ? context.t.readonly_mode_disabled : context.t.readonly_mode_enabled, + (isReadonlyModeEnabled ? "readonly_mode_disabled" : "readonly_mode_enabled").tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), ), @@ -142,8 +142,8 @@ class _ProfileIndicator extends ConsumerWidget { color: serverInfoState.versionStatus == VersionStatus.error ? context.colorScheme.error : context.primaryColor, - size: widgetSize / 2 - 3, - semanticLabel: context.t.new_version_available, + size: widgetSize / 2, + semanticLabel: 'new_version_available'.tr(), ), ), backgroundColor: Colors.transparent, @@ -153,7 +153,7 @@ class _ProfileIndicator extends ConsumerWidget { child: user == null ? const Icon(Icons.face_outlined, size: widgetSize) : Semantics( - label: context.t.logged_in_as(user: user.name), + label: "logged_in_as".tr(namedArgs: {"user": user.name}), child: AbsorbPointer( child: Builder( builder: (context) => UserCircleAvatar( @@ -177,15 +177,7 @@ class _BackupIndicator extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled)); - final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); - final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); - final indicatorIcon = _getBackupBadgeIcon( - context, - backupEnabled: backupEnabled, - hasError: hasError, - isUploading: isUploading, - ); + final indicatorIcon = _getBackupBadgeIcon(context, ref); return IconButton( onPressed: () => context.pushRoute(const DriftBackupRoute()), @@ -200,59 +192,65 @@ class _BackupIndicator extends ConsumerWidget { ); } - Widget? _getBackupBadgeIcon( - BuildContext context, { - required bool backupEnabled, - required bool hasError, - required bool isUploading, - }) { + Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) { + final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup); + final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none)); final isDarkTheme = context.isDarkTheme; final iconColor = isDarkTheme ? Colors.white : Colors.black; + final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty)); - if (!backupEnabled) { - return _BadgeLabel( - Icon( - Icons.cloud_off_rounded, - size: 9, - color: iconColor, - semanticLabel: context.t.backup_controller_page_backup, - ), - ); - } + return StreamBuilder( + stream: backupStateStream, + initialData: false, + builder: (ctx, snapshot) { + final backupEnabled = snapshot.data ?? false; - if (hasError) { - return _BadgeLabel( - Icon( - Icons.warning_rounded, - size: 12, - color: context.colorScheme.error, - semanticLabel: context.t.backup_controller_page_backup, - ), - backgroundColor: context.colorScheme.errorContainer, - ); - } - - if (isUploading) { - return _BadgeLabel( - Container( - padding: const EdgeInsets.all(3.5), - child: Theme( - data: context.themeData.copyWith( - progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), + if (!backupEnabled) { + return _BadgeLabel( + Icon( + Icons.cloud_off_rounded, + size: 9, + color: iconColor, + semanticLabel: 'backup_controller_page_backup'.tr(), ), - child: CircularProgressIndicator( - strokeWidth: 2, - strokeCap: StrokeCap.round, - valueColor: AlwaysStoppedAnimation(iconColor), - semanticsLabel: context.t.backup_controller_page_backup, - ), - ), - ), - ); - } + ); + } - return _BadgeLabel( - Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: context.t.backup_controller_page_backup), + if (hasError) { + return _BadgeLabel( + Icon( + Icons.warning_rounded, + size: 12, + color: context.colorScheme.error, + semanticLabel: 'backup_controller_page_backup'.tr(), + ), + backgroundColor: context.colorScheme.errorContainer, + ); + } + + if (isUploading) { + return _BadgeLabel( + Container( + padding: const EdgeInsets.all(3.5), + child: Theme( + data: context.themeData.copyWith( + progressIndicatorTheme: context.themeData.progressIndicatorTheme.copyWith(year2023: true), + ), + child: CircularProgressIndicator( + strokeWidth: 2, + strokeCap: StrokeCap.round, + valueColor: AlwaysStoppedAnimation(iconColor), + semanticsLabel: 'backup_controller_page_backup'.tr(), + ), + ), + ), + ); + } + + return _BadgeLabel( + Icon(Icons.check_outlined, size: 9, color: iconColor, semanticLabel: 'backup_controller_page_backup'.tr()), + ); + }, ); } } @@ -320,13 +318,13 @@ class _SyncStatusIndicatorState extends ConsumerState<_SyncStatusIndicator> with // Control animations based on sync status if (isSyncing) { if (!_rotationController.isAnimating) { - unawaited(_rotationController.repeat()); + _rotationController.repeat(); } _dismissalController.reset(); } else { _rotationController.stop(); if (_dismissalController.status == AnimationStatus.dismissed) { - unawaited(_dismissalController.forward()); + _dismissalController.forward(); } } diff --git a/mobile/lib/widgets/common/immich_thumbnail.dart b/mobile/lib/widgets/common/immich_thumbnail.dart new file mode 100644 index 0000000000..f17353c3aa --- /dev/null +++ b/mobile/lib/widgets/common/immich_thumbnail.dart @@ -0,0 +1,88 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/hooks/blurhash_hook.dart'; +import 'package:immich_mobile/utils/thumbnail_utils.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; +import 'package:immich_mobile/widgets/common/thumbhash_placeholder.dart'; +import 'package:octo_image/octo_image.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as base_asset; + +class ImmichThumbnail extends HookConsumerWidget { + const ImmichThumbnail({this.asset, this.width = 250, this.height = 250, this.fit = BoxFit.cover, super.key}); + + final Asset? asset; + final double width; + final double height; + final BoxFit fit; + + /// Helper function to return the image provider for the asset thumbnail + /// either by using the asset ID or the asset itself + /// [asset] is the Asset to request, or else use [assetId] to get a remote + /// image provider + static ImageProvider imageProvider({Asset? asset, String? assetId, int thumbnailSize = 256}) { + if (asset == null && assetId == null) { + throw Exception('Must supply either asset or assetId'); + } + + if (asset == null) { + return RemoteImageProvider.thumbnail(assetId: assetId!, thumbhash: ""); + } + + if (ImmichImage.useLocal(asset)) { + return LocalThumbProvider( + id: asset.localId!, + assetType: base_asset.AssetType.video, + size: Size(thumbnailSize.toDouble(), thumbnailSize.toDouble()), + ); + } else { + return RemoteImageProvider.thumbnail(assetId: asset.remoteId!, thumbhash: asset.thumbhash ?? ""); + } + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + Uint8List? blurhash = useBlurHashRef(asset).value; + + if (asset == null) { + return Container( + color: Colors.grey, + width: width, + height: height, + child: const Center(child: Icon(Icons.no_photography)), + ); + } + + final assetAltText = getAltText(asset!.exifInfo, asset!.fileCreatedAt, asset!.type, []); + + final thumbnailProviderInstance = ImmichThumbnail.imageProvider(asset: asset); + + customErrorBuilder(BuildContext ctx, Object error, StackTrace? stackTrace) { + thumbnailProviderInstance.evict(); + + final originalErrorWidgetBuilder = blurHashErrorBuilder(blurhash, fit: fit); + return originalErrorWidgetBuilder(ctx, error, stackTrace); + } + + return Semantics( + label: assetAltText, + child: OctoImage.fromSet( + placeholderFadeInDuration: Duration.zero, + fadeInDuration: Duration.zero, + fadeOutDuration: const Duration(milliseconds: 100), + octoSet: OctoSet( + placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit), + errorBuilder: customErrorBuilder, + ), + image: thumbnailProviderInstance, + width: width, + height: height, + fit: fit, + ), + ); + } +} diff --git a/mobile/lib/widgets/common/immich_toast.dart b/mobile/lib/widgets/common/immich_toast.dart index 0a06de4d91..3e7ab273d8 100644 --- a/mobile/lib/widgets/common/immich_toast.dart +++ b/mobile/lib/widgets/common/immich_toast.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart'; enum ToastType { info, success, error } class ImmichToast { - static void show({ + static show({ required BuildContext context, required String msg, ToastType toastType = ToastType.info, @@ -50,10 +50,9 @@ class ImmichToast { ), ), positionedToastBuilder: (context, child, gravity) { - final isTop = gravity == ToastGravity.TOP; return Positioned( - top: isTop ? 150 : null, - bottom: isTop ? null : 150 + MediaQuery.of(context).viewInsets.bottom, + top: gravity == ToastGravity.TOP ? 150 : null, + bottom: gravity == ToastGravity.BOTTOM ? 150 : null, left: MediaQuery.of(context).size.width / 2 - 150, right: MediaQuery.of(context).size.width / 2 - 150, child: IgnorePointer(child: child), diff --git a/mobile/lib/widgets/common/local_album_sliver_app_bar.dart b/mobile/lib/widgets/common/local_album_sliver_app_bar.dart index 1de1d1a262..85d8f4bb01 100644 --- a/mobile/lib/widgets/common/local_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/local_album_sliver_app_bar.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; class LocalAlbumsSliverAppBar extends StatelessWidget { const LocalAlbumsSliverAppBar({super.key}); @@ -15,7 +15,7 @@ class LocalAlbumsSliverAppBar extends StatelessWidget { shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(5))), automaticallyImplyLeading: true, centerTitle: true, - title: Text(context.t.on_this_device), + title: Text("on_this_device".t(context: context)), ); } } diff --git a/mobile/lib/widgets/common/location_picker.dart b/mobile/lib/widgets/common/location_picker.dart index cf3240977b..4736b182ed 100644 --- a/mobile/lib/widgets/common/location_picker.dart +++ b/mobile/lib/widgets/common/location_picker.dart @@ -1,10 +1,10 @@ import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/string_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -73,11 +73,11 @@ class _LocationPicker extends HookWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(context.t.edit_location_dialog_title, style: context.textTheme.titleMedium), + Text("edit_location_dialog_title", style: context.textTheme.titleMedium).tr(), Align( alignment: Alignment.center, child: TextButton.icon( - icon: Text(context.t.location_picker_choose_on_map), + icon: const Text("location_picker_choose_on_map").tr(), label: const Icon(Icons.map_outlined, size: 16), onPressed: onMapTap, ), @@ -85,9 +85,9 @@ class _LocationPicker extends HookWidget { const SizedBox(height: 12), _ManualPickerInput( controller: latitudeController, - decorationText: context.t.latitude, - hintText: context.t.location_picker_latitude_hint, - errorText: context.t.location_picker_latitude_error, + decorationText: "latitude", + hintText: "location_picker_latitude_hint", + errorText: "location_picker_latitude_error", focusNode: latitiudeFocusNode, validator: _validateLat, onUpdated: onLatitudeUpdated, @@ -95,9 +95,9 @@ class _LocationPicker extends HookWidget { const SizedBox(height: 24), _ManualPickerInput( controller: longitudeController, - decorationText: context.t.longitude, - hintText: context.t.location_picker_longitude_hint, - errorText: context.t.location_picker_longitude_error, + decorationText: "longitude", + hintText: "location_picker_longitude_hint", + errorText: "location_picker_longitude_error", focusNode: longitudeFocusNode, validator: _validateLong, onUpdated: onLongitudeEditingCompleted, @@ -107,21 +107,21 @@ class _LocationPicker extends HookWidget { ), actions: [ TextButton( - onPressed: () => ContextHelper(context).pop(), + onPressed: () => context.pop(), child: Text( - context.t.cancel, + "cancel", style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: context.colorScheme.error, ), - ), + ).tr(), ), TextButton( onPressed: () => context.maybePop(latlng), child: Text( - context.t.action_common_update, + "action_common_update", style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), - ), + ).tr(), ), ], ); @@ -163,13 +163,13 @@ class _ManualPickerInput extends HookWidget { textInputAction: TextInputAction.done, autofocus: false, decoration: InputDecoration( - labelText: decorationText, + labelText: decorationText.tr(), labelStyle: TextStyle(fontWeight: FontWeight.bold, color: context.primaryColor), floatingLabelBehavior: FloatingLabelBehavior.auto, border: const OutlineInputBorder(), - hintText: hintText, + hintText: hintText.tr(), hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), - errorText: isValid.value ? null : errorText, + errorText: isValid.value ? null : errorText.tr(), ), onEditingComplete: onEditingComplete, keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true), diff --git a/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart b/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart index ac53bca72f..44b547a5f1 100644 --- a/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/mesmerizing_sliver_app_bar.dart @@ -8,7 +8,7 @@ import 'package:immich_mobile/domain/models/events.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; @@ -134,7 +134,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { - unawaited(_slideController.forward()); + _slideController.forward(); } }); } @@ -228,7 +228,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { @override void dispose() { - unawaited(_reloadSubscription?.cancel()); + _reloadSubscription?.cancel(); super.dispose(); } @@ -237,7 +237,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { final assetCount = ref.watch(timelineServiceProvider.select((s) => s.totalAssets)); return Text( - context.t.items_count(count: assetCount), + 'items_count'.t(context: context, args: {"count": assetCount}), style: context.textTheme.labelLarge?.copyWith( // letterSpacing: 0.2, fontWeight: FontWeight.bold, @@ -311,17 +311,13 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic void _startAnimationCycle() { if (_isZoomingIn) { - unawaited( - _zoomController.forward().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.forward().then((_) { + _loadNextAsset(); + }); } else { - unawaited( - _zoomController.reverse().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.reverse().then((_) { + _loadNextAsset(); + }); } } @@ -397,8 +393,10 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic builder: (context, child) { return Transform.scale( scale: _zoomAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Transform.translate( offset: _panAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Stack( fit: StackFit.expand, children: [ diff --git a/mobile/lib/widgets/common/person_sliver_app_bar.dart b/mobile/lib/widgets/common/person_sliver_app_bar.dart index 56eb3c6094..a2a9d1bdbd 100644 --- a/mobile/lib/widgets/common/person_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/person_sliver_app_bar.dart @@ -11,13 +11,13 @@ import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/people.utils.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; class PersonSliverAppBar extends ConsumerStatefulWidget { const PersonSliverAppBar({ @@ -28,7 +28,7 @@ class PersonSliverAppBar extends ConsumerStatefulWidget { required this.onBirthdayTap, }); - final Person person; + final DriftPerson person; final VoidCallback onNameTap; final VoidCallback onBirthdayTap; final VoidCallback onShowOptions; @@ -56,8 +56,8 @@ class _MesmerizingSliverAppBarState extends ConsumerState { @override Widget build(BuildContext context) { final isMultiSelectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled)); - final Color? actionIconColor = Color.lerp(Colors.white, context.primaryColor, _scrollProgress); - final List actionIconShadows = [ + Color? actionIconColor = Color.lerp(Colors.white, context.primaryColor, _scrollProgress); + List actionIconShadows = [ if (_scrollProgress < 0.95) Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5)) else @@ -137,7 +137,7 @@ class _MesmerizingSliverAppBarState extends ConsumerState { class _ExpandedBackground extends ConsumerStatefulWidget { final double scrollProgress; - final Person person; + final DriftPerson person; final VoidCallback onNameTap; final VoidCallback onBirthdayTap; @@ -169,7 +169,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { - unawaited(_slideController.forward()); + _slideController.forward(); } }); } @@ -230,9 +230,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S elevation: 3, child: CircleAvatar( maxRadius: 84 / 2, - backgroundImage: RemoteImageProvider( - url: getFaceThumbnailUrl(widget.person.id, updatedAt: widget.person.updatedAt), - ), + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(widget.person.id)), ), ), ), @@ -261,7 +259,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S ), ) : Text( - context.t.add_a_name, + 'add_a_name'.tr(), style: context.textTheme.titleLarge?.copyWith( color: Colors.grey[400], fontSize: 36, @@ -293,7 +291,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S ) else Text( - context.t.add_birthday, + 'add_birthday'.tr(), style: context.textTheme.labelLarge?.copyWith( color: Colors.grey[400], height: 1.2, @@ -335,7 +333,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { @override void dispose() { - unawaited(_reloadSubscription?.cancel()); + _reloadSubscription?.cancel(); super.dispose(); } @@ -344,7 +342,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { final assetCount = ref.watch(timelineServiceProvider.select((s) => s.totalAssets)); return Text( - context.t.items_count(count: assetCount), + 'items_count'.t(context: context, args: {"count": assetCount}), style: context.textTheme.labelLarge?.copyWith( fontWeight: FontWeight.bold, color: Colors.white, @@ -416,17 +414,13 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic void _startAnimationCycle() { if (_isZoomingIn) { - unawaited( - _zoomController.forward().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.forward().then((_) { + _loadNextAsset(); + }); } else { - unawaited( - _zoomController.reverse().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.reverse().then((_) { + _loadNextAsset(); + }); } } @@ -502,8 +496,10 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic builder: (context, child) { return Transform.scale( scale: _zoomAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Transform.translate( offset: _panAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Stack( fit: StackFit.expand, children: [ diff --git a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart index 4b9532eb88..50746f5cbd 100644 --- a/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/remote_album_sliver_app_bar.dart @@ -12,13 +12,12 @@ import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/datetime_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart'; class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget { @@ -64,9 +63,9 @@ class _MesmerizingSliverAppBarState extends ConsumerState actionIconShadows = [ + List actionIconShadows = [ if (_scrollProgress < 0.95) Shadow(offset: const Offset(0, 2), blurRadius: 5, color: Colors.black.withValues(alpha: 0.5)) else @@ -90,10 +89,6 @@ class _MesmerizingSliverAppBarState extends ConsumerState context.maybePop(), ), actions: [ - IconButton( - onPressed: () => context.pushRoute(DriftSlideshowRoute(timeline: ref.read(timelineServiceProvider))), - icon: Icon(Icons.slideshow_outlined, color: actionIconColor, shadows: actionIconShadows), - ), if (currentAlbum.isActivityEnabled && currentAlbum.isShared) IconButton( icon: Icon(Icons.chat_outlined, color: actionIconColor, shadows: actionIconShadows), @@ -172,7 +167,7 @@ class _ExpandedBackgroundState extends ConsumerState<_ExpandedBackground> with S Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { - unawaited(_slideController.forward()); + _slideController.forward(); } }); } @@ -309,7 +304,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { @override void dispose() { - unawaited(_reloadSubscription?.cancel()); + _reloadSubscription?.cancel(); super.dispose(); } @@ -318,7 +313,7 @@ class _ItemCountTextState extends ConsumerState<_ItemCountText> { final assetCount = ref.watch(timelineServiceProvider.select((s) => s.totalAssets)); return Text( - context.t.items_count(count: assetCount), + 'items_count'.t(context: context, args: {"count": assetCount}), style: context.textTheme.labelLarge?.copyWith( color: Colors.white, shadows: [const Shadow(offset: Offset(0, 2), blurRadius: 12, color: Colors.black87)], @@ -390,17 +385,13 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic void _startAnimationCycle() { if (_isZoomingIn) { - unawaited( - _zoomController.forward().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.forward().then((_) { + _loadNextAsset(); + }); } else { - unawaited( - _zoomController.reverse().then((_) { - unawaited(_loadNextAsset()); - }), - ); + _zoomController.reverse().then((_) { + _loadNextAsset(); + }); } } @@ -476,8 +467,10 @@ class _RandomAssetBackgroundState extends State<_RandomAssetBackground> with Tic builder: (context, child) { return Transform.scale( scale: _zoomAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Transform.translate( offset: _panAnimation.value, + filterQuality: Platform.isAndroid ? FilterQuality.low : null, child: Stack( fit: StackFit.expand, children: [ diff --git a/mobile/lib/widgets/common/scaffold_error_body.dart b/mobile/lib/widgets/common/scaffold_error_body.dart index 5f3fb1cb0d..2e2d8fb506 100644 --- a/mobile/lib/widgets/common/scaffold_error_body.dart +++ b/mobile/lib/widgets/common/scaffold_error_body.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; // Error widget to be used in Scaffold when an AsyncError is received class ScaffoldErrorBody extends StatelessWidget { @@ -15,11 +15,7 @@ class ScaffoldErrorBody extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - context.t.scaffold_body_error_occurred, - style: context.textTheme.displayMedium, - textAlign: TextAlign.center, - ), + Text("scaffold_body_error_occurred", style: context.textTheme.displayMedium, textAlign: TextAlign.center).tr(), if (withIcon) Center( child: Padding( diff --git a/mobile/lib/widgets/common/selection_sliver_app_bar.dart b/mobile/lib/widgets/common/selection_sliver_app_bar.dart index f6fb2fc88f..ac74e69e64 100644 --- a/mobile/lib/widgets/common/selection_sliver_app_bar.dart +++ b/mobile/lib/widgets/common/selection_sliver_app_bar.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; class SelectionSliverAppBar extends ConsumerStatefulWidget { @@ -23,7 +23,7 @@ class _SelectionSliverAppBarState extends ConsumerState { return !toExclude.contains(asset); }).toSet(); - void onDone(Set selected) { + onDone(Set selected) { ref.read(multiSelectProvider.notifier).reset(); context.pop>(selected); } @@ -43,12 +43,12 @@ class _SelectionSliverAppBarState extends ConsumerState { }, ), centerTitle: true, - title: Text(context.t.selected_count(count: filteredAssets.length)), + title: Text("Select {count}".t(context: context, args: {'count': filteredAssets.length.toString()})), actions: [ TextButton( onPressed: () => onDone(filteredAssets), child: Text( - context.t.done, + 'done'.t(context: context), style: context.textTheme.titleSmall?.copyWith(color: context.colorScheme.primary), ), ), diff --git a/mobile/lib/widgets/common/share_dialog.dart b/mobile/lib/widgets/common/share_dialog.dart new file mode 100644 index 0000000000..625390c4b7 --- /dev/null +++ b/mobile/lib/widgets/common/share_dialog.dart @@ -0,0 +1,19 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class ShareDialog extends StatelessWidget { + const ShareDialog({super.key}); + + @override + Widget build(BuildContext context) { + return AlertDialog( + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const CircularProgressIndicator(), + Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/common/tag_picker.dart b/mobile/lib/widgets/common/tag_picker.dart index 2b4f07536f..0ab25d14cb 100644 --- a/mobile/lib/widgets/common/tag_picker.dart +++ b/mobile/lib/widgets/common/tag_picker.dart @@ -1,93 +1,26 @@ import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/tag.model.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/infrastructure/tag.provider.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; -String _trimSlashes(String s) => s.replaceAll(RegExp(r'^/+|/+$'), ''); - -Future<(Set, Set)?> showTagPickerModal({required BuildContext context, Set? initialSelection}) { - return showDialog<(Set, Set)?>( - context: context, - builder: (context) => _TagPickerModal(initialSelection: initialSelection), - ); -} - -class _TagPickerModal extends HookWidget { - final Set? initialSelection; - - const _TagPickerModal({this.initialSelection}); - - @override - Widget build(BuildContext context) { - final selectedTagIds = useState>(initialSelection ?? {}); - final newTagValues = useState>({}); - - void onSelectExistingTag(Iterable tags) { - selectedTagIds.value = tags.map((tag) => tag.id).toSet(); - } - - void onSelectNewTag(Set tags) { - newTagValues.value = tags; - } - - return AlertDialog( - contentPadding: const EdgeInsets.symmetric(vertical: 16, horizontal: 0), - actions: [ - TextButton( - onPressed: () => context.pop(), - child: Text( - context.t.cancel, - style: context.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.w600, - color: context.colorScheme.error, - ), - ), - ), - TextButton( - onPressed: () => context.pop((selectedTagIds.value, newTagValues.value)), - child: Text( - context.t.action_common_update, - style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor), - ), - ), - ], - content: SizedBox( - width: MediaQuery.of(context).size.width * 0.8, - height: MediaQuery.of(context).size.height * 0.6, - child: TagPicker( - onSelectExistingTag: onSelectExistingTag, - filter: selectedTagIds.value, - onSelectNewTag: onSelectNewTag, - ), - ), - ); - } -} - class TagPicker extends HookConsumerWidget { - const TagPicker({super.key, required this.onSelectExistingTag, required this.filter, this.onSelectNewTag}); + const TagPicker({super.key, required this.onSelect, required this.filter}); + final Function(Iterable) onSelect; final Set filter; - /// Callback when existing tags are selected/deselected. - final Function(Iterable) onSelectExistingTag; - - /// If not null, shows a tile to create a new tag with user's filter input. - final Function(Set)? onSelectNewTag; - @override Widget build(BuildContext context, WidgetRef ref) { final formFocus = useFocusNode(); final searchQuery = useState(''); final tags = ref.watch(tagProvider); final selectedTagIds = useState>(filter); - const borderRadius = BorderRadius.all(Radius.circular(10)); - final selectedNewTagValues = useState>({}); + final borderRadius = const BorderRadius.all(Radius.circular(10)); return Column( children: [ @@ -98,7 +31,7 @@ class TagPicker extends HookConsumerWidget { onChanged: (value) => searchQuery.value = value, onTapOutside: (_) => formFocus.unfocus(), filled: true, - hintText: context.t.filter_tags, + hintText: 'filter_tags'.tr(), ), ), Padding( @@ -108,59 +41,19 @@ class TagPicker extends HookConsumerWidget { Expanded( child: tags.widgetWhen( onData: (tags) { - final trimmedQuery = _trimSlashes(searchQuery.value); final queryResult = tags - .where((t) => t.value.toLowerCase().contains(trimmedQuery.toLowerCase())) + .where((t) => t.value.toLowerCase().contains(searchQuery.value.toLowerCase())) .toList(); - final showCreateTile = - (onSelectNewTag != null) && - trimmedQuery.isNotEmpty && - !tags.any((t) => t.value.toLowerCase() == trimmedQuery.toLowerCase()); - final isCreateSelected = selectedNewTagValues.value.contains(trimmedQuery); return ListView.builder( - itemCount: queryResult.length + (showCreateTile ? 1 : 0), + itemCount: queryResult.length, padding: const EdgeInsets.all(8), itemBuilder: (context, index) { - if (showCreateTile && index == queryResult.length) { - // Create new tag tile - return Padding( - padding: const EdgeInsets.only(bottom: 2.0), - child: DecoratedBox( - decoration: BoxDecoration( - color: isCreateSelected ? context.primaryColor : context.primaryColor.withAlpha(25), - borderRadius: const BorderRadius.all(Radius.circular(10)), - ), - child: ListTile( - title: Text( - trimmedQuery, - style: context.textTheme.bodyLarge?.copyWith( - color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, - ), - ), - trailing: Icon( - Icons.add, - color: isCreateSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, - ), - onTap: () { - final newSelectedNewTagValues = {...selectedNewTagValues.value}; - if (isCreateSelected) { - newSelectedNewTagValues.remove(trimmedQuery); - } else { - newSelectedNewTagValues.add(trimmedQuery); - } - selectedNewTagValues.value = newSelectedNewTagValues; - onSelectNewTag!.call(newSelectedNewTagValues); - }, - ), - ), - ); - } final tag = queryResult[index]; final isSelected = selectedTagIds.value.any((id) => id == tag.id); return Padding( padding: const EdgeInsets.only(bottom: 2.0), - child: DecoratedBox( + child: Container( decoration: BoxDecoration( color: isSelected ? context.primaryColor : context.primaryColor.withAlpha(25), borderRadius: borderRadius, @@ -180,7 +73,7 @@ class TagPicker extends HookConsumerWidget { newSelected.add(tag.id); } selectedTagIds.value = newSelected; - onSelectExistingTag(tags.where((t) => newSelected.contains(t.id))); + onSelect(tags.where((t) => newSelected.contains(t.id))); }, ), ), diff --git a/mobile/lib/widgets/common/thumbhash_placeholder.dart b/mobile/lib/widgets/common/thumbhash_placeholder.dart index 8a9c2eb928..0cb1222989 100644 --- a/mobile/lib/widgets/common/thumbhash_placeholder.dart +++ b/mobile/lib/widgets/common/thumbhash_placeholder.dart @@ -4,6 +4,15 @@ import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; import 'package:immich_mobile/widgets/common/fade_in_placeholder_image.dart'; import 'package:octo_image/octo_image.dart'; +/// Simple set to show [OctoPlaceholder.circularProgressIndicator] as +/// placeholder and [OctoError.icon] as error. +OctoSet blurHashOrPlaceholder(Uint8List? blurhash, {BoxFit? fit, Text? errorMessage}) { + return OctoSet( + placeholderBuilder: blurHashPlaceholderBuilder(blurhash, fit: fit), + errorBuilder: blurHashErrorBuilder(blurhash, fit: fit, message: errorMessage), + ); +} + OctoPlaceholderBuilder blurHashPlaceholderBuilder(Uint8List? blurhash, {BoxFit? fit}) { return (context) => blurhash == null ? const ThumbnailPlaceholder() diff --git a/mobile/lib/widgets/common/user_circle_avatar.dart b/mobile/lib/widgets/common/user_circle_avatar.dart index 11257da77b..c6e4f4719e 100644 --- a/mobile/lib/widgets/common/user_circle_avatar.dart +++ b/mobile/lib/widgets/common/user_circle_avatar.dart @@ -1,19 +1,21 @@ import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; -class UserCircleAvatar extends StatelessWidget { +// ignore: must_be_immutable +class UserCircleAvatar extends ConsumerWidget { final UserDto user; - final double size; - final bool hasBorder; - final double opacity; + double size; + bool hasBorder; + double opacity; - const UserCircleAvatar({super.key, this.size = 44, this.hasBorder = false, this.opacity = 1, required this.user}); + UserCircleAvatar({super.key, this.size = 44, this.hasBorder = false, this.opacity = 1, required this.user}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final userAvatarColor = user.avatarColor.toColor().withValues(alpha: opacity); final profileImageUrl = '${Store.get(StoreKey.serverEndpoint)}/users/${user.id}/profile-image?d=${user.profileChangedAt.millisecondsSinceEpoch}'; diff --git a/mobile/lib/widgets/forms/change_password_form.dart b/mobile/lib/widgets/forms/change_password_form.dart index 65a4c6f88e..179b05a712 100644 --- a/mobile/lib/widgets/forms/change_password_form.dart +++ b/mobile/lib/widgets/forms/change_password_form.dart @@ -1,11 +1,14 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/manual_upload.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/providers/asset.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -29,13 +32,13 @@ class ChangePasswordForm extends HookConsumerWidget { alignment: WrapAlignment.start, children: [ Text( - context.t.change_password, + 'change_password'.tr(), style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: context.primaryColor), ), Padding( padding: const EdgeInsets.symmetric(vertical: 24.0), child: Text( - context.t.change_password_form_description(name: authState.name), + 'change_password_form_description'.tr(namedArgs: {'name': authState.name}), style: TextStyle(fontSize: 14, color: context.colorScheme.onSurface, fontWeight: FontWeight.w600), ), ), @@ -55,44 +58,41 @@ class ChangePasswordForm extends HookConsumerWidget { passwordController: passwordController, onPressed: () async { if (formKey.currentState!.validate()) { - final isSuccess = await ref + var isSuccess = await ref .read(authProvider.notifier) .changePassword(passwordController.value.text); - if (!isSuccess && context.mounted) { + if (isSuccess) { + await ref.read(authProvider.notifier).logout(); + + ref.read(manualUploadProvider.notifier).cancelBackup(); + ref.read(backupProvider.notifier).cancelBackup(); + await ref.read(assetProvider.notifier).clearAllAssets(); + ref.read(websocketProvider.notifier).disconnect(); + + AutoRouter.of(context).back(); + ImmichToast.show( context: context, - msg: context.t.login_password_changed_error, + msg: "login_password_changed_success".tr(), + toastType: ToastType.success, + gravity: ToastGravity.TOP, + ); + } else { + ImmichToast.show( + context: context, + msg: "login_password_changed_error".tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); - return; } - - if (!context.mounted) { - return; - } - - await ref.read(authProvider.notifier).logout(); - if (!context.mounted) { - return; - } - - ref.read(websocketProvider.notifier).disconnect(); - AutoRouter.of(context).back(); - ImmichToast.show( - context: context, - msg: context.t.login_password_changed_success, - toastType: ToastType.success, - gravity: ToastGravity.TOP, - ); } }, ), TextButton.icon( icon: const Icon(Icons.arrow_back), onPressed: () => AutoRouter.of(context).back(), - label: Text(context.t.back), + label: const Text('back').tr(), ), ], ), @@ -116,9 +116,9 @@ class PasswordInput extends StatelessWidget { obscureText: true, controller: controller, decoration: InputDecoration( - labelText: context.t.change_password_form_new_password, + labelText: 'change_password_form_new_password'.tr(), border: const OutlineInputBorder(), - hintText: context.t.change_password_form_new_password, + hintText: 'change_password_form_new_password'.tr(), ), ); } @@ -132,7 +132,7 @@ class ConfirmPasswordInput extends StatelessWidget { String? _validateInput(String? email) { if (confirmController.value != originalController.value) { - return StaticTranslations.instance.change_password_form_password_mismatch; + return 'change_password_form_password_mismatch'.tr(); } return null; } @@ -143,8 +143,8 @@ class ConfirmPasswordInput extends StatelessWidget { obscureText: true, controller: confirmController, decoration: InputDecoration( - labelText: context.t.change_password_form_confirm_password, - hintText: context.t.change_password_form_reenter_new_password, + labelText: 'change_password_form_confirm_password'.tr(), + hintText: 'change_password_form_reenter_new_password'.tr(), border: const OutlineInputBorder(), ), validator: _validateInput, @@ -153,20 +153,20 @@ class ConfirmPasswordInput extends StatelessWidget { } } -class ChangePasswordButton extends StatelessWidget { +class ChangePasswordButton extends ConsumerWidget { final TextEditingController passwordController; final VoidCallback onPressed; const ChangePasswordButton({super.key, required this.passwordController, required this.onPressed}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return ElevatedButton( style: ElevatedButton.styleFrom( visualDensity: VisualDensity.standard, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 25), ), onPressed: onPressed, - child: Text(context.t.change_password, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), + child: Text('change_password'.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), ); } } diff --git a/mobile/lib/widgets/forms/login/email_input.dart b/mobile/lib/widgets/forms/login/email_input.dart new file mode 100644 index 0000000000..4d90d918ac --- /dev/null +++ b/mobile/lib/widgets/forms/login/email_input.dart @@ -0,0 +1,41 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class EmailInput extends StatelessWidget { + final TextEditingController controller; + final FocusNode? focusNode; + final Function()? onSubmit; + + const EmailInput({super.key, required this.controller, this.focusNode, this.onSubmit}); + + String? _validateInput(String? email) { + if (email == null || email == '') return null; + if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr(); + if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr(); + if (email.contains(' ') || !email.contains('@')) { + return 'login_form_err_invalid_email'.tr(); + } + return null; + } + + @override + Widget build(BuildContext context) { + return TextFormField( + autofocus: true, + controller: controller, + decoration: InputDecoration( + labelText: 'email'.tr(), + border: const OutlineInputBorder(), + hintText: 'login_form_email_hint'.tr(), + hintStyle: const TextStyle(fontWeight: FontWeight.normal, fontSize: 14), + ), + validator: _validateInput, + autovalidateMode: AutovalidateMode.always, + autofillHints: const [AutofillHints.email], + keyboardType: TextInputType.emailAddress, + onFieldSubmitted: (_) => onSubmit?.call(), + focusNode: focusNode, + textInputAction: TextInputAction.next, + ); + } +} diff --git a/mobile/lib/widgets/forms/login/loading_icon.dart b/mobile/lib/widgets/forms/login/loading_icon.dart new file mode 100644 index 0000000000..052ce43ac7 --- /dev/null +++ b/mobile/lib/widgets/forms/login/loading_icon.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; + +class LoadingIcon extends StatelessWidget { + const LoadingIcon({super.key}); + + @override + Widget build(BuildContext context) { + return const Padding( + padding: EdgeInsets.only(top: 18.0), + child: SizedBox(width: 24, height: 24, child: FittedBox(child: CircularProgressIndicator(strokeWidth: 2))), + ); + } +} diff --git a/mobile/lib/widgets/forms/login/login_button.dart b/mobile/lib/widgets/forms/login/login_button.dart new file mode 100644 index 0000000000..0f9fb21d8f --- /dev/null +++ b/mobile/lib/widgets/forms/login/login_button.dart @@ -0,0 +1,19 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; + +class LoginButton extends ConsumerWidget { + final Function() onPressed; + + const LoginButton({super.key, required this.onPressed}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ElevatedButton.icon( + style: ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: 12)), + onPressed: onPressed, + icon: const Icon(Icons.login_rounded), + label: const Text("login", style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)).tr(), + ); + } +} diff --git a/mobile/lib/widgets/forms/login/login_form.dart b/mobile/lib/widgets/forms/login/login_form.dart index aa9750a2a5..2aa770f104 100644 --- a/mobile/lib/widgets/forms/login/login_form.dart +++ b/mobile/lib/widgets/forms/login/login_form.dart @@ -5,6 +5,7 @@ import 'dart:math'; import 'package:auto_route/auto_route.dart'; import 'package:crypto/crypto.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; @@ -13,20 +14,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/feature_message.provider.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/oauth.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/provider_utils.dart'; -import 'package:immich_mobile/utils/semver.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/version_compatibility.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; @@ -36,27 +34,30 @@ import 'package:immich_ui/immich_ui.dart'; import 'package:logging/logging.dart'; import 'package:openapi/api.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; class LoginForm extends HookConsumerWidget { LoginForm({super.key}); final log = Logger('LoginForm'); - String? _validateUrl(String? url) => - normalizeAndValidateServerUrl(url) ? null : StaticTranslations.instance.login_form_err_invalid_url; + String? _validateUrl(String? url) { + if (url == null || url.isEmpty) return null; + + final parsedUrl = Uri.tryParse(url); + if (parsedUrl == null || !parsedUrl.isAbsolute || !parsedUrl.scheme.startsWith("http") || parsedUrl.host.isEmpty) { + return 'login_form_err_invalid_url'.tr(); + } + + return null; + } String? _validateEmail(String? email) { - if (email == null || email == '') { - return null; - } - if (email.endsWith(' ')) { - return StaticTranslations.instance.login_form_err_trailing_whitespace; - } - if (email.startsWith(' ')) { - return StaticTranslations.instance.login_form_err_leading_whitespace; - } + if (email == null || email == '') return null; + if (email.endsWith(' ')) return 'login_form_err_trailing_whitespace'.tr(); + if (email.startsWith(' ')) return 'login_form_err_leading_whitespace'.tr(); if (email.contains(' ') || !email.contains('@')) { - return StaticTranslations.instance.login_form_err_invalid_email; + return 'login_form_err_invalid_email'.tr(); } return null; } @@ -70,19 +71,27 @@ class LoginForm extends HookConsumerWidget { final isOauthEnable = useState(false); final isPasswordLoginEnable = useState(false); final oAuthButtonLabel = useState('OAuth'); - final logoAnimationController = useAnimationController(duration: const Duration(seconds: 60)); - unawaited(logoAnimationController.repeat()); + final logoAnimationController = useAnimationController(duration: const Duration(seconds: 60))..repeat(); final serverInfo = ref.watch(serverInfoProvider); final warningMessage = useState(null); final loginFormKey = GlobalKey(); final ValueNotifier serverEndpoint = useState(null); - Future checkVersionMismatch() async { + checkVersionMismatch() async { try { final packageInfo = await PackageInfo.fromPlatform(); - final appSemVer = SemVer.fromString(packageInfo.version); - final serverSemVer = serverInfo.serverVersion; - warningMessage.value = getVersionCompatibilityMessage(serverVersion: serverSemVer, appVersion: appSemVer); + final appVersion = packageInfo.version; + final appMajorVersion = int.parse(appVersion.split('.')[0]); + final appMinorVersion = int.parse(appVersion.split('.')[1]); + final serverMajorVersion = serverInfo.serverVersion.major; + final serverMinorVersion = serverInfo.serverVersion.minor; + + warningMessage.value = getVersionCompatibilityMessage( + appMajorVersion, + appMinorVersion, + serverMajorVersion, + serverMinorVersion, + ); } catch (error) { warningMessage.value = 'Error checking version compatibility'; } @@ -91,25 +100,19 @@ class LoginForm extends HookConsumerWidget { /// Fetch the server login credential and enables oAuth login if necessary /// Returns true if successful, false otherwise Future getServerAuthSettings() async { - final sanitizeServerUrl = normalizeServerUrl(serverEndpointController.text); + final sanitizeServerUrl = sanitizeUrl(serverEndpointController.text); final serverUrl = punycodeEncodeUrl(sanitizeServerUrl); // Guard empty URL if (serverUrl.isEmpty) { - ImmichToast.show(context: context, msg: context.t.login_form_server_empty, toastType: ToastType.error); + ImmichToast.show(context: context, msg: "login_form_server_empty".tr(), toastType: ToastType.error); } try { final endpoint = await ref.read(authProvider.notifier).validateServerUrl(serverUrl); - if (!context.mounted) { - return; - } // Fetch and load server config and features await ref.read(serverInfoProvider.notifier).getServerInfo(); - if (!context.mounted) { - return; - } final serverInfo = ref.read(serverInfoProvider); final features = serverInfo.serverFeatures; @@ -121,39 +124,27 @@ class LoginForm extends HookConsumerWidget { serverEndpoint.value = endpoint; } on ApiException catch (e) { - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: e.message ?? context.t.login_form_api_exception, + msg: e.message ?? 'login_form_api_exception'.tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); isOauthEnable.value = false; isPasswordLoginEnable.value = true; } on HandshakeException { - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: context.t.login_form_handshake_exception, + msg: 'login_form_handshake_exception'.tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); isOauthEnable.value = false; isPasswordLoginEnable.value = true; } catch (e) { - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: context.t.login_form_server_error, + msg: 'login_form_server_error'.tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); @@ -170,13 +161,13 @@ class LoginForm extends HookConsumerWidget { return null; }, []); - void populateTestLoginInfo() { + populateTestLoginInfo() { emailController.text = 'demo@immich.app'; passwordController.text = 'demo'; serverEndpointController.text = 'https://demo.immich.app'; } - void populateTestLoginInfo1() { + populateTestLoginInfo1() { emailController.text = 'testuser@email.com'; passwordController.text = 'password'; serverEndpointController.text = 'http://10.1.15.216:2283/api'; @@ -184,24 +175,18 @@ class LoginForm extends HookConsumerWidget { Future handleSyncFlow() async { final backgroundManager = ref.read(backgroundSyncProvider); - final viewIntentHandler = ref.read(viewIntentHandlerProvider); await backgroundManager.syncLocal(full: true); await backgroundManager.syncRemote(); - await viewIntentHandler.flushDeferredViewIntent(); await backgroundManager.hashAssets(); - if (SettingsRepository.instance.appConfig.backup.syncAlbums) { + if (Store.get(StoreKey.syncAlbums, false)) { await backgroundManager.syncLinkedAlbum(); } } - Future getManageMediaPermission() async { - final hasPermission = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); - if (!context.mounted) { - return; - } - + getManageMediaPermission() async { + final hasPermission = await ref.read(localFilesManagerRepositoryProvider).hasManageMediaPermission(); if (!hasPermission) { await showDialog( context: context, @@ -210,15 +195,15 @@ class LoginForm extends HookConsumerWidget { shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), elevation: 5, title: Text( - context.t.manage_media_access_title, + 'manage_media_access_title', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: context.primaryColor), - ), + ).tr(), content: SingleChildScrollView( child: ListBody( children: [ - Text(context.t.manage_media_access_subtitle, style: const TextStyle(fontSize: 14)), + const Text('manage_media_access_subtitle', style: TextStyle(fontSize: 14)).tr(), const SizedBox(height: 4), - Text(context.t.manage_media_access_rationale, style: const TextStyle(fontSize: 12)), + const Text('manage_media_access_rationale', style: TextStyle(fontSize: 12)).tr(), ], ), ), @@ -226,17 +211,17 @@ class LoginForm extends HookConsumerWidget { TextButton( onPressed: () => Navigator.of(context).pop(), child: Text( - context.t.cancel, + 'cancel'.tr(), style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), ), ), TextButton( onPressed: () { - unawaited(ref.read(permissionRepositoryProvider).requestManageMediaPermission()); + ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); Navigator.of(context).pop(); }, child: Text( - context.t.manage_media_access_settings, + 'manage_media_access_settings'.tr(), style: TextStyle(fontWeight: FontWeight.w600, color: context.primaryColor), ), ), @@ -249,7 +234,7 @@ class LoginForm extends HookConsumerWidget { bool isSyncRemoteDeletionsMode() => Platform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false); - Future login() async { + login() async { TextInput.finishAutofillContext(); // Invalidate all api repository provider instance to take into account new access token @@ -258,39 +243,26 @@ class LoginForm extends HookConsumerWidget { try { final result = await ref.read(authProvider.notifier).login(emailController.text, passwordController.text); - if (!context.mounted) { - return; - } - if (result.shouldChangePassword && !result.isAdmin) { unawaited(context.pushRoute(const ChangePasswordRoute())); } else { - await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - if (isSyncRemoteDeletionsMode()) { - await getManageMediaPermission(); - } - unawaited(handleSyncFlow()); - if (!context.mounted) { + final isBeta = Store.isBetaTimelineEnabled; + if (isBeta) { + await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); + if (isSyncRemoteDeletionsMode()) { + await getManageMediaPermission(); + } + unawaited(handleSyncFlow()); + ref.read(websocketProvider.notifier).connect(); + unawaited(context.replaceRoute(const TabShellRoute())); return; } - - ref.read(websocketProvider.notifier).connect(); - unawaited(ref.read(featureMessageServiceProvider).markSeen()); - if (!context.mounted) { - return; - } - - unawaited(context.router.replaceAll([const TabShellRoute()])); - return; + unawaited(context.replaceRoute(const TabControllerRoute())); } } catch (error) { - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: context.t.login_form_failed_login, + msg: "login_form_failed_login".tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); @@ -316,13 +288,13 @@ class LoginForm extends HookConsumerWidget { } Future generatePKCECodeChallenge(String codeVerifier) async { - final bytes = utf8.encode(codeVerifier); - final digest = sha256.convert(bytes); + var bytes = utf8.encode(codeVerifier); + var digest = sha256.convert(bytes); return base64Url.encode(digest.bytes).replaceAll('=', ''); } - Future oAuthLogin() async { - final oAuthService = ref.read(oAuthServiceProvider); + oAuthLogin() async { + var oAuthService = ref.watch(oAuthServiceProvider); String? oAuthServerUrl; final state = generateRandomString(32); @@ -332,7 +304,7 @@ class LoginForm extends HookConsumerWidget { try { oAuthServerUrl = await oAuthService.getOAuthServerUrl( - normalizeServerUrl(serverEndpointController.text), + sanitizeUrl(serverEndpointController.text), state, codeChallenge, ); @@ -342,13 +314,9 @@ class LoginForm extends HookConsumerWidget { } catch (error, stack) { log.severe('Error getting OAuth server Url: $error', stack); - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: context.t.login_form_failed_get_oauth_server_config, + msg: "login_form_failed_get_oauth_server_config".tr(), toastType: ToastType.error, gravity: ToastGravity.TOP, ); @@ -359,37 +327,36 @@ class LoginForm extends HookConsumerWidget { try { final loginResponseDto = await oAuthService.oAuthLogin(oAuthServerUrl, state, codeVerifier); - if (loginResponseDto == null || !context.mounted) { + if (loginResponseDto == null) { return; } log.info("Finished OAuth login with response: ${loginResponseDto.userEmail}"); final isSuccess = await ref - .read(authProvider.notifier) + .watch(authProvider.notifier) .saveAuthInfo(accessToken: loginResponseDto.accessToken); - if (isSuccess && context.mounted) { - await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); - if (isSyncRemoteDeletionsMode()) { - await getManageMediaPermission(); + if (isSuccess) { + final permission = ref.watch(galleryPermissionNotifier); + final isBeta = Store.isBetaTimelineEnabled; + if (!isBeta && (permission.isGranted || permission.isLimited)) { + unawaited(ref.watch(backupProvider.notifier).resumeBackup()); } - unawaited(handleSyncFlow()); - if (!context.mounted) { + if (isBeta) { + await ref.read(galleryPermissionNotifier.notifier).requestGalleryPermission(); + if (isSyncRemoteDeletionsMode()) { + await getManageMediaPermission(); + } + unawaited(handleSyncFlow()); + unawaited(context.replaceRoute(const TabShellRoute())); return; } - - unawaited(ref.read(featureMessageServiceProvider).markSeen()); - unawaited(context.router.replaceAll([const TabShellRoute()])); - return; + unawaited(context.replaceRoute(const TabControllerRoute())); } } catch (error, stack) { log.severe('Error logging in with OAuth: $error', stack); - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, msg: error.toString(), @@ -398,13 +365,9 @@ class LoginForm extends HookConsumerWidget { ); } finally {} } else { - if (!context.mounted) { - return; - } - ImmichToast.show( context: context, - msg: context.t.login_form_failed_get_oauth_server_disable, + msg: "login_form_failed_get_oauth_server_disable".tr(), toastType: ToastType.info, gravity: ToastGravity.TOP, ); @@ -412,8 +375,8 @@ class LoginForm extends HookConsumerWidget { } } - SingleChildRenderObjectWidget buildVersionCompatWarning() { - unawaited(checkVersionMismatch()); + buildVersionCompatWarning() { + checkVersionMismatch(); if (warningMessage.value == null) { return const SizedBox.shrink(); @@ -424,21 +387,11 @@ class LoginForm extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - color: context.isDarkTheme ? Colors.amber.shade700 : Colors.amber.shade100, - borderRadius: const BorderRadius.all(Radius.circular(12)), - border: Border.all(color: context.isDarkTheme ? Colors.amber.shade800 : Colors.amber[200]!, width: 2), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Icon(Icons.warning_amber_rounded, color: Colors.amber.shade800), - const SizedBox(width: 8), - Expanded( - child: Padding(padding: const EdgeInsets.only(top: 2), child: Text(warningMessage.value!)), - ), - ], + color: context.isDarkTheme ? Colors.red.shade700 : Colors.red.shade100, + borderRadius: const BorderRadius.all(Radius.circular(8)), + border: Border.all(color: context.isDarkTheme ? Colors.red.shade900 : Colors.red[200]!), ), + child: Text(warningMessage.value!, textAlign: TextAlign.center), ), ); } @@ -450,20 +403,23 @@ class LoginForm extends HookConsumerWidget { mainAxisSize: MainAxisSize.max, children: [ ImmichForm( - onSubmit: getServerAuthSettings, - submitText: context.t.next, + submitText: 'next'.t(context: context), submitIcon: Icons.arrow_forward_rounded, - builder: (_, form) => ImmichURLInput( + onSubmit: getServerAuthSettings, + child: ImmichTextInput( controller: serverEndpointController, - label: context.t.login_form_endpoint_url, - hintText: context.t.login_form_endpoint_hint, + label: 'login_form_endpoint_url'.t(context: context), + hintText: 'login_form_endpoint_hint'.t(context: context), validator: _validateUrl, - keyboardAction: .next, - onSubmit: (_) => form.submit(), + keyboardAction: TextInputAction.next, + keyboardType: TextInputType.url, + autofillHints: const [AutofillHints.url], + autoCorrect: false, + onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), ), ), ImmichTextButton( - labelText: context.t.settings, + labelText: 'settings'.t(context: context), icon: Icons.settings, variant: ImmichVariant.ghost, onPressed: () => context.pushRoute(const SettingsRoute()), @@ -480,53 +436,56 @@ class LoginForm extends HookConsumerWidget { Padding( padding: const EdgeInsets.only(bottom: ImmichSpacing.md), child: Text( - normalizeServerUrl(serverEndpointController.text), + sanitizeUrl(serverEndpointController.text), style: context.textTheme.displaySmall, textAlign: TextAlign.center, ), ), if (isPasswordLoginEnable.value) ImmichForm( - onSubmit: login, - submitText: context.t.login, + submitText: 'login'.t(context: context), submitIcon: Icons.login_rounded, - builder: (context, form) => Column( + onSubmit: login, + child: Column( spacing: ImmichSpacing.md, children: [ - ImmichEmailInput( + ImmichTextInput( controller: emailController, - label: context.t.email, - hintText: context.t.login_form_email_hint, + label: 'email'.t(context: context), + hintText: 'login_form_email_hint'.t(context: context), validator: _validateEmail, keyboardAction: TextInputAction.next, - onSubmit: (_) => passwordFocusNode.requestFocus(), + keyboardType: TextInputType.emailAddress, + autofillHints: const [AutofillHints.email], + onSubmit: (_, _) => passwordFocusNode.requestFocus(), ), ImmichPasswordInput( controller: passwordController, focusNode: passwordFocusNode, - label: context.t.password, - hintText: context.t.login_form_password_hint, + label: 'password'.t(context: context), + hintText: 'login_form_password_hint'.t(context: context), keyboardAction: TextInputAction.go, - onSubmit: (_) => form.submit(), + onSubmit: (ctx, _) => ImmichForm.of(ctx).submit(), ), ], ), ), if (isOauthEnable.value) ImmichForm( - onSubmit: oAuthLogin, submitText: oAuthButtonLabel.value, submitIcon: Icons.pin_outlined, - builder: (context, _) => isPasswordLoginEnable.value + onSubmit: oAuthLogin, + child: isPasswordLoginEnable.value ? Padding( padding: const EdgeInsets.only(left: 18.0, right: 18.0, top: 12.0), child: Divider(color: context.isDarkTheme ? Colors.white : Colors.black, height: 5), ) : const SizedBox.shrink(), ), - if (!isOauthEnable.value && !isPasswordLoginEnable.value) Center(child: Text(context.t.login_disabled)), + if (!isOauthEnable.value && !isPasswordLoginEnable.value) + Center(child: const Text('login_disabled').tr()), ImmichTextButton( - labelText: context.t.back, + labelText: 'back'.t(context: context), icon: Icons.arrow_back, variant: ImmichVariant.ghost, onPressed: () => serverEndpoint.value = null, diff --git a/mobile/lib/widgets/forms/pin_input.dart b/mobile/lib/widgets/forms/pin_input.dart index 8bcfd51282..c4f0d8f3b7 100644 --- a/mobile/lib/widgets/forms/pin_input.dart +++ b/mobile/lib/widgets/forms/pin_input.dart @@ -26,9 +26,9 @@ class PinInput extends StatelessWidget { @override Widget build(BuildContext context) { - Size getPinSize() { - const minimumPadding = 18.0; - const gapWidth = 3.0; + getPinSize() { + final minimumPadding = 18.0; + final gapWidth = 3.0; final screenWidth = context.width; final pinWidth = (screenWidth - (minimumPadding * 2) - (gapWidth * 5)) / (length ?? 6); diff --git a/mobile/lib/widgets/forms/pin_registration_form.dart b/mobile/lib/widgets/forms/pin_registration_form.dart index bc14388021..d126169aad 100644 --- a/mobile/lib/widgets/forms/pin_registration_form.dart +++ b/mobile/lib/widgets/forms/pin_registration_form.dart @@ -1,8 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/widgets/forms/pin_input.dart'; @@ -29,7 +29,7 @@ class PinRegistrationForm extends HookConsumerWidget { return true; } - Future createNewPinCode() async { + createNewPinCode() async { final isValid = validatePinCode(); if (!isValid) { hasError.value = true; @@ -42,10 +42,6 @@ class PinRegistrationForm extends HookConsumerWidget { onDone(); } catch (error) { hasError.value = true; - if (!context.mounted) { - return; - } - context.showSnackBar(SnackBar(content: Text(error.toString()))); } } @@ -58,7 +54,7 @@ class PinRegistrationForm extends HookConsumerWidget { SizedBox( width: context.width * 0.7, child: Text( - context.t.setup_pin_code, + 'setup_pin_code'.tr(), style: context.textTheme.labelLarge!.copyWith(fontSize: 24), textAlign: TextAlign.center, ), @@ -66,7 +62,7 @@ class PinRegistrationForm extends HookConsumerWidget { SizedBox( width: context.width * 0.8, child: Text( - context.t.new_pin_code_subtitle, + 'new_pin_code_subtitle'.tr(), style: context.textTheme.bodyLarge!.copyWith(fontSize: 16), textAlign: TextAlign.center, ), @@ -74,7 +70,7 @@ class PinRegistrationForm extends HookConsumerWidget { const SizedBox(height: 32), PinInput( controller: newPinCodeController, - label: context.t.new_pin_code, + label: 'new_pin_code'.tr(), length: 6, autoFocus: true, hasError: hasError.value, @@ -87,7 +83,7 @@ class PinRegistrationForm extends HookConsumerWidget { const SizedBox(height: 32), PinInput( controller: confirmPinCodeController, - label: context.t.confirm_new_pin_code, + label: 'confirm_new_pin_code'.tr(), length: 6, hasError: hasError.value, onChanged: (input) { @@ -102,7 +98,7 @@ class PinRegistrationForm extends HookConsumerWidget { child: Row( children: [ Expanded( - child: ElevatedButton(onPressed: createNewPinCode, child: Text(context.t.create)), + child: ElevatedButton(onPressed: createNewPinCode, child: Text('create'.tr())), ), ], ), diff --git a/mobile/lib/widgets/forms/pin_verification_form.dart b/mobile/lib/widgets/forms/pin_verification_form.dart index 6f1ba25fd0..2b7e3e8251 100644 --- a/mobile/lib/widgets/forms/pin_verification_form.dart +++ b/mobile/lib/widgets/forms/pin_verification_form.dart @@ -1,8 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/widgets/forms/pin_input.dart'; @@ -29,7 +29,7 @@ class PinVerificationForm extends HookConsumerWidget { final hasError = useState(false); final isVerified = useState(false); - Future verifyPin(String pinCode) async { + verifyPin(String pinCode) async { final isUnlocked = await ref.read(authProvider.notifier).unlockPinCode(pinCode); if (isUnlocked) { @@ -60,7 +60,7 @@ class PinVerificationForm extends HookConsumerWidget { SizedBox( width: context.width * 0.7, child: Text( - description ?? context.t.enter_your_pin_code_subtitle, + description ?? 'enter_your_pin_code_subtitle'.tr(), style: context.textTheme.labelLarge!.copyWith(fontSize: 18), textAlign: TextAlign.center, ), diff --git a/mobile/lib/widgets/map/asset_marker_icon.dart b/mobile/lib/widgets/map/asset_marker_icon.dart index 75881195a4..ff6058161b 100644 --- a/mobile/lib/widgets/map/asset_marker_icon.dart +++ b/mobile/lib/widgets/map/asset_marker_icon.dart @@ -69,15 +69,15 @@ class _PinPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - final Paint primaryBrush = Paint() + Paint primaryBrush = Paint() ..color = primaryColor ..style = PaintingStyle.fill; - final Paint secondaryBrush = Paint() + Paint secondaryBrush = Paint() ..color = secondaryColor ..style = PaintingStyle.fill; - final Paint lineBrush = Paint() + Paint lineBrush = Paint() ..color = primaryColor ..style = PaintingStyle.stroke ..strokeWidth = 2; diff --git a/mobile/lib/widgets/map/map_app_bar.dart b/mobile/lib/widgets/map/map_app_bar.dart new file mode 100644 index 0000000000..73706c7661 --- /dev/null +++ b/mobile/lib/widgets/map/map_app_bar.dart @@ -0,0 +1,128 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/map/map_state.provider.dart'; +import 'package:immich_mobile/utils/immich_loading_overlay.dart'; +import 'package:immich_mobile/utils/selection_handlers.dart'; +import 'package:immich_mobile/widgets/map/map_settings_sheet.dart'; + +class MapAppBar extends HookWidget implements PreferredSizeWidget { + final ValueNotifier> selectedAssets; + + const MapAppBar({super.key, required this.selectedAssets}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only(top: context.padding.top + 25), + child: ValueListenableBuilder( + valueListenable: selectedAssets, + builder: (ctx, value, child) => + value.isNotEmpty ? _SelectionRow(selectedAssets: selectedAssets) : const _NonSelectionRow(), + ), + ); + } + + @override + Size get preferredSize => const Size.fromHeight(100); +} + +class _NonSelectionRow extends StatelessWidget { + const _NonSelectionRow(); + + @override + Widget build(BuildContext context) { + void onSettingsPressed() { + showModalBottomSheet( + elevation: 0.0, + showDragHandle: true, + isScrollControlled: true, + context: context, + builder: (_) => const MapSettingsSheet(), + ); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => context.maybePop(), + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.arrow_back_ios_new_rounded), + ), + ElevatedButton( + onPressed: onSettingsPressed, + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.more_vert_rounded), + ), + ], + ); + } +} + +class _SelectionRow extends HookConsumerWidget { + final ValueNotifier> selectedAssets; + + const _SelectionRow({required this.selectedAssets}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isProcessing = useProcessingOverlay(); + + Future handleProcessing(FutureOr Function() action, [bool reloadMarkers = false]) async { + isProcessing.value = true; + await action(); + // Reset state + selectedAssets.value = {}; + isProcessing.value = false; + if (reloadMarkers) { + ref.read(mapStateNotifierProvider.notifier).setRefetchMarkers(true); + } + } + + return Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 20), + child: ElevatedButton.icon( + onPressed: () => selectedAssets.value = {}, + icon: const Icon(Icons.close_rounded), + label: Text( + '${selectedAssets.value.length}', + style: context.textTheme.titleMedium?.copyWith(color: context.colorScheme.onPrimary), + ), + ), + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + ElevatedButton( + onPressed: () => handleProcessing(() => handleShareAssets(ref, context, selectedAssets.value.toList())), + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.ios_share_rounded), + ), + ElevatedButton( + onPressed: () => + handleProcessing(() => handleFavoriteAssets(ref, context, selectedAssets.value.toList())), + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.favorite), + ), + ElevatedButton( + onPressed: () => + handleProcessing(() => handleArchiveAssets(ref, context, selectedAssets.value.toList()), true), + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.archive), + ), + ], + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/map/map_asset_grid.dart b/mobile/lib/widgets/map/map_asset_grid.dart new file mode 100644 index 0000000000..b6c1e708a7 --- /dev/null +++ b/mobile/lib/widgets/map/map_asset_grid.dart @@ -0,0 +1,289 @@ +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/collection_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/models/map/map_event.model.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/providers/timeline.provider.dart'; +import 'package:immich_mobile/utils/color_filter_generator.dart'; +import 'package:immich_mobile/utils/throttle.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; +import 'package:immich_mobile/widgets/common/drag_sheet.dart'; +import 'package:logging/logging.dart'; +import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; + +class MapAssetGrid extends HookConsumerWidget { + final Stream mapEventStream; + final Function(String)? onGridAssetChanged; + final Function(String)? onZoomToAsset; + final Function(bool, Set)? onAssetsSelected; + final ValueNotifier> selectedAssets; + final ScrollController controller; + + const MapAssetGrid({ + required this.mapEventStream, + this.onGridAssetChanged, + this.onZoomToAsset, + this.onAssetsSelected, + required this.selectedAssets, + required this.controller, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final log = Logger("MapAssetGrid"); + final assetsInBounds = useState>([]); + final cachedRenderList = useRef(null); + final lastRenderElementIndex = useRef(null); + final assetInSheet = useValueNotifier(null); + final gridScrollThrottler = useThrottler(interval: const Duration(milliseconds: 300)); + + // Add a cache for assets we've already loaded + final assetCache = useRef>({}); + + void handleMapEvents(MapEvent event) async { + if (event is MapAssetsInBoundsUpdated) { + final assetIds = event.assetRemoteIds; + final missingIds = []; + final currentAssets = []; + + for (final id in assetIds) { + final asset = assetCache.value[id]; + if (asset != null) { + currentAssets.add(asset); + } else { + missingIds.add(id); + } + } + + // Only fetch missing assets + if (missingIds.isNotEmpty) { + final newAssets = await ref.read(dbProvider).assets.getAllByRemoteId(missingIds); + + // Add new assets to cache and current list + for (final asset in newAssets) { + if (asset.remoteId != null) { + assetCache.value[asset.remoteId!] = asset; + currentAssets.add(asset); + } + } + } + + assetsInBounds.value = currentAssets; + return; + } + } + + useOnStreamChange(mapEventStream, onData: handleMapEvents); + + // Hard-restrict to 4 assets / row in portrait mode + const assetsPerRow = 4; + + void handleVisibleItems(Iterable positions) { + final orderedPos = positions.sortedByField((p) => p.index); + // Index of row where the items are mostly visible + const partialOffset = 0.20; + final item = orderedPos.firstWhereOrNull((p) => p.itemTrailingEdge > partialOffset); + + // Guard no elements, reset state + // Also fail fast when the sheet is just opened and the user is yet to scroll (i.e leading = 0) + if (item == null || item.itemLeadingEdge == 0) { + lastRenderElementIndex.value = null; + return; + } + + final renderElement = cachedRenderList.value?.elements.elementAtOrNull(item.index); + // Guard no render list or render element + if (renderElement == null) { + return; + } + // Reset index + lastRenderElementIndex.value == item.index; + + // + // | 1 | 2 | 3 | 4 | 5 | 6 | + // + // | 7 | 8 | 9 | + // + // | 10 | + + // Skip through the assets from the previous row + final rowOffset = renderElement.offset; + // Column offset = (total trailingEdge - trailingEdge crossed) / offset for each asset + final totalOffset = item.itemTrailingEdge - item.itemLeadingEdge; + final edgeOffset = + (totalOffset - partialOffset) / + // Round the total count to the next multiple of [assetsPerRow] + ((renderElement.totalCount / assetsPerRow) * assetsPerRow).floor(); + + // trailing should never be above the totalOffset + final columnOffset = (totalOffset - math.min(item.itemTrailingEdge, totalOffset)) ~/ edgeOffset; + final assetOffset = rowOffset + columnOffset; + final selectedAsset = cachedRenderList.value?.allAssets?.elementAtOrNull(assetOffset)?.remoteId; + + if (selectedAsset != null) { + onGridAssetChanged?.call(selectedAsset); + assetInSheet.value = selectedAsset; + } + } + + return Card( + margin: EdgeInsets.zero, + child: Stack( + children: [ + /// The Align and FractionallySizedBox are to prevent the Asset Grid from going behind the + /// _MapSheetDragRegion and thereby displaying content behind the top right and top left curves + Align( + alignment: Alignment.bottomCenter, + child: FractionallySizedBox( + // Place it just below the drag handle + heightFactor: 0.87, + child: assetsInBounds.value.isNotEmpty + ? ref + .watch(assetsTimelineProvider(assetsInBounds.value)) + .when( + data: (renderList) { + // Cache render list here to use it back during visibleItemsListener + cachedRenderList.value = renderList; + return ValueListenableBuilder( + valueListenable: selectedAssets, + builder: (_, value, __) => ImmichAssetGrid( + shrinkWrap: true, + renderList: renderList, + showDragScroll: false, + assetsPerRow: assetsPerRow, + showMultiSelectIndicator: false, + selectionActive: value.isNotEmpty, + listener: onAssetsSelected, + visibleItemsListener: (pos) => gridScrollThrottler.run(() => handleVisibleItems(pos)), + ), + ); + }, + error: (error, stackTrace) { + log.warning("Cannot get assets in the current map bounds", error, stackTrace); + return const SizedBox.shrink(); + }, + loading: () => const SizedBox.shrink(), + ) + : const _MapNoAssetsInSheet(), + ), + ), + _MapSheetDragRegion( + controller: controller, + assetsInBoundCount: assetsInBounds.value.length, + assetInSheet: assetInSheet, + onZoomToAsset: onZoomToAsset, + ), + ], + ), + ); + } +} + +class _MapNoAssetsInSheet extends StatelessWidget { + const _MapNoAssetsInSheet(); + + @override + Widget build(BuildContext context) { + const image = Image(height: 150, width: 150, image: AssetImage('assets/lighthouse.png')); + + return Center( + child: ListView( + shrinkWrap: true, + children: [ + context.isDarkTheme + ? const InvertionFilter( + child: SaturationFilter(saturation: -1, child: BrightnessFilter(brightness: -5, child: image)), + ) + : image, + const SizedBox(height: 20), + Center( + child: Text("map_zoom_to_see_photos".tr(), style: context.textTheme.displayLarge?.copyWith(fontSize: 18)), + ), + ], + ), + ); + } +} + +class _MapSheetDragRegion extends StatelessWidget { + final ScrollController controller; + final int assetsInBoundCount; + final ValueNotifier assetInSheet; + final Function(String)? onZoomToAsset; + + const _MapSheetDragRegion({ + required this.controller, + required this.assetsInBoundCount, + required this.assetInSheet, + this.onZoomToAsset, + }); + + @override + Widget build(BuildContext context) { + final assetsInBoundsText = "map_assets_in_bounds".t(context: context, args: {'count': assetsInBoundCount}); + + return SingleChildScrollView( + controller: controller, + physics: const ClampingScrollPhysics(), + child: Card( + margin: EdgeInsets.zero, + shape: context.isMobile + ? const RoundedRectangleBorder( + borderRadius: BorderRadius.only(topRight: Radius.circular(20), topLeft: Radius.circular(20)), + ) + : const BeveledRectangleBorder(), + elevation: 0.0, + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 15), + const CustomDraggingHandle(), + const SizedBox(height: 15), + Center( + child: Text( + assetsInBoundsText, + style: TextStyle( + fontSize: 20, + color: context.textTheme.displayLarge?.color?.withValues(alpha: 0.75), + fontWeight: FontWeight.w500, + ), + ), + ), + const SizedBox(height: 8), + ], + ), + ValueListenableBuilder( + valueListenable: assetInSheet, + builder: (_, value, __) => Visibility( + visible: value != null, + child: Positioned( + right: 18, + top: 24, + child: IconButton( + icon: Icon(Icons.map_outlined, color: context.textTheme.displayLarge?.color), + iconSize: 24, + tooltip: 'zoom_to_bounds'.tr(), + onPressed: () => onZoomToAsset?.call(value!), + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/map/map_bottom_sheet.dart b/mobile/lib/widgets/map/map_bottom_sheet.dart new file mode 100644 index 0000000000..fba9e9a041 --- /dev/null +++ b/mobile/lib/widgets/map/map_bottom_sheet.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/map/map_event.model.dart'; +import 'package:immich_mobile/utils/draggable_scroll_controller.dart'; +import 'package:immich_mobile/widgets/map/map_asset_grid.dart'; + +class MapBottomSheet extends HookConsumerWidget { + final Stream mapEventStream; + final Function(String)? onGridAssetChanged; + final Function(String)? onZoomToAsset; + final Function()? onZoomToLocation; + final Function(bool, Set)? onAssetsSelected; + final ValueNotifier> selectedAssets; + + const MapBottomSheet({ + required this.mapEventStream, + this.onGridAssetChanged, + this.onZoomToAsset, + this.onAssetsSelected, + this.onZoomToLocation, + required this.selectedAssets, + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + const sheetMinExtent = 0.1; + final sheetController = useDraggableScrollController(); + final bottomSheetOffset = useValueNotifier(sheetMinExtent); + final isBottomSheetOpened = useRef(false); + + void handleMapEvents(MapEvent event) async { + if (event is MapCloseBottomSheet) { + await sheetController.animateTo( + 0.1, + duration: const Duration(milliseconds: 200), + curve: Curves.linearToEaseOut, + ); + } + } + + useOnStreamChange(mapEventStream, onData: handleMapEvents); + + bool onScrollNotification(DraggableScrollableNotification notification) { + isBottomSheetOpened.value = notification.extent > (notification.maxExtent * 0.9); + bottomSheetOffset.value = notification.extent; + // do not bubble + return true; + } + + return Stack( + children: [ + NotificationListener( + onNotification: onScrollNotification, + child: DraggableScrollableSheet( + controller: sheetController, + minChildSize: sheetMinExtent, + maxChildSize: 0.8, + initialChildSize: sheetMinExtent, + snap: true, + snapSizes: [sheetMinExtent, 0.5, 0.8], + shouldCloseOnMinExtent: false, + builder: (ctx, scrollController) => MapAssetGrid( + controller: scrollController, + mapEventStream: mapEventStream, + selectedAssets: selectedAssets, + onAssetsSelected: onAssetsSelected, + // Do not bother with the event if the bottom sheet is not user scrolled + onGridAssetChanged: (assetId) => isBottomSheetOpened.value ? onGridAssetChanged?.call(assetId) : null, + onZoomToAsset: onZoomToAsset, + ), + ), + ), + ValueListenableBuilder( + valueListenable: bottomSheetOffset, + builder: (context, value, child) { + return Positioned( + right: 0, + bottom: context.height * (value + 0.02), + child: AnimatedOpacity( + opacity: value < 0.8 ? 1 : 0, + duration: const Duration(milliseconds: 150), + child: ElevatedButton( + onPressed: onZoomToLocation, + style: ElevatedButton.styleFrom(shape: const CircleBorder()), + child: const Icon(Icons.my_location), + ), + ), + ); + }, + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/map/map_settings/map_custom_time_range.dart b/mobile/lib/widgets/map/map_settings/map_custom_time_range.dart deleted file mode 100644 index 6fa929852a..0000000000 --- a/mobile/lib/widgets/map/map_settings/map_custom_time_range.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:immich_mobile/domain/models/time_range.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/utils/option.dart'; - -class MapTimeRange extends StatelessWidget { - const MapTimeRange({super.key, required this.timeRange, required this.onChanged}); - - final TimeRange timeRange; - final Function(TimeRange) onChanged; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - title: Text(context.t.date_after), - subtitle: Text( - timeRange.from != null - ? DateFormat.yMMMd(context.locale.toLanguageTag()).add_jm().format(timeRange.from!) - : context.t.not_set, - ), - trailing: timeRange.from != null - ? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(timeRange.clearFrom())) - : null, - onTap: () async { - final initial = timeRange.from ?? DateTime.now(); - final currentTo = timeRange.to; - - final picked = await showDatePicker( - context: context, - initialDate: currentTo != null && initial.isAfter(currentTo) ? currentTo : initial, - firstDate: DateTime(1970), - lastDate: currentTo ?? DateTime.now(), - ); - - if (picked != null) { - onChanged(timeRange.copyWith(from: Option.some(picked))); - } - }, - ), - ListTile( - title: Text(context.t.date_before), - subtitle: Text( - timeRange.to != null - ? DateFormat.yMMMd(context.locale.toLanguageTag()).add_jm().format(timeRange.to!) - : context.t.not_set, - ), - trailing: timeRange.to != null - ? IconButton(icon: const Icon(Icons.close), onPressed: () => onChanged(timeRange.clearTo())) - : null, - onTap: () async { - final initial = timeRange.to ?? DateTime.now(); - final currentFrom = timeRange.from; - - final picked = await showDatePicker( - context: context, - initialDate: currentFrom != null && initial.isBefore(currentFrom) ? currentFrom : initial, - firstDate: currentFrom ?? DateTime(1970), - lastDate: DateTime.now(), - ); - - if (picked != null) { - onChanged(timeRange.copyWith(to: Option.some(picked))); - } - }, - ), - ], - ); - } -} diff --git a/mobile/lib/widgets/map/map_settings/map_settings_time_dropdown.dart b/mobile/lib/widgets/map/map_settings/map_settings_time_dropdown.dart index eeebc28dda..2a4dacaff7 100644 --- a/mobile/lib/widgets/map/map_settings/map_settings_time_dropdown.dart +++ b/mobile/lib/widgets/map/map_settings/map_settings_time_dropdown.dart @@ -1,6 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; class MapTimeDropDown extends StatelessWidget { final int relativeTime; @@ -19,7 +20,7 @@ class MapTimeDropDown extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - context.t.date_range, + "date_range".t(context: context), style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), Flexible( @@ -29,21 +30,24 @@ class MapTimeDropDown extends StatelessWidget { initialSelection: relativeTime, onSelected: (value) => onTimeChange(value!), dropdownMenuEntries: [ - DropdownMenuEntry(value: 0, label: context.t.all), - DropdownMenuEntry(value: 1, label: context.t.map_settings_date_range_option_day), - DropdownMenuEntry(value: 7, label: context.t.map_settings_date_range_option_days(days: 7)), - DropdownMenuEntry(value: 30, label: context.t.map_settings_date_range_option_days(days: 30)), + DropdownMenuEntry(value: 0, label: "all".t(context: context)), + DropdownMenuEntry(value: 1, label: "map_settings_date_range_option_day".t(context: context)), + DropdownMenuEntry(value: 7, label: "map_settings_date_range_option_days".tr(namedArgs: {'days': "7"})), + DropdownMenuEntry( + value: 30, + label: "map_settings_date_range_option_days".tr(namedArgs: {'days': "30"}), + ), DropdownMenuEntry( value: now .difference(DateTime(now.year - 1, now.month, now.day, now.hour, now.minute, now.second)) .inDays, - label: context.t.map_settings_date_range_option_year, + label: "map_settings_date_range_option_year".t(context: context), ), DropdownMenuEntry( value: now .difference(DateTime(now.year - 3, now.month, now.day, now.hour, now.minute, now.second)) .inDays, - label: context.t.map_settings_date_range_option_years(years: 3), + label: "map_settings_date_range_option_years".t(args: {'years': "3"}), ), ], ), diff --git a/mobile/lib/widgets/map/map_settings/map_theme_picker.dart b/mobile/lib/widgets/map/map_settings/map_theme_picker.dart index 6b53a273a0..7866c0ecdc 100644 --- a/mobile/lib/widgets/map/map_settings/map_theme_picker.dart +++ b/mobile/lib/widgets/map/map_settings/map_theme_picker.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; @@ -18,7 +18,7 @@ class MapThemePicker extends StatelessWidget { padding: const EdgeInsets.only(bottom: 20), child: Center( child: Text( - context.t.map_settings_theme_settings, + "map_settings_theme_settings".t(context: context), style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), ), @@ -69,7 +69,7 @@ class _BorderedMapThumbnail extends StatelessWidget { Widget build(BuildContext context) { return Column( children: [ - DecoratedBox( + Container( decoration: BoxDecoration( border: Border.fromBorderSide( BorderSide(width: 4, color: shouldHighlight ? context.colorScheme.onSurface : Colors.transparent), @@ -79,7 +79,7 @@ class _BorderedMapThumbnail extends StatelessWidget { child: MapThumbnail( zoom: 2, centre: const LatLng(47, 5), - onTap: (_, _) => onThemeChange(mode), + onTap: (_, __) => onThemeChange(mode), themeMode: mode, showAttribution: false, ), diff --git a/mobile/lib/widgets/map/map_settings_sheet.dart b/mobile/lib/widgets/map/map_settings_sheet.dart new file mode 100644 index 0000000000..644056d153 --- /dev/null +++ b/mobile/lib/widgets/map/map_settings_sheet.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/map/map_state.provider.dart'; +import 'package:immich_mobile/widgets/map/map_settings/map_settings_list_tile.dart'; +import 'package:immich_mobile/widgets/map/map_settings/map_settings_time_dropdown.dart'; +import 'package:immich_mobile/widgets/map/map_settings/map_theme_picker.dart'; + +class MapSettingsSheet extends HookConsumerWidget { + const MapSettingsSheet({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final mapState = ref.watch(mapStateNotifierProvider); + + return DraggableScrollableSheet( + expand: false, + initialChildSize: 0.6, + builder: (ctx, scrollController) => SingleChildScrollView( + controller: scrollController, + child: Card( + elevation: 0.0, + shadowColor: Colors.transparent, + margin: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + MapThemePicker( + themeMode: mapState.themeMode, + onThemeChange: (mode) => ref.read(mapStateNotifierProvider.notifier).switchTheme(mode), + ), + const Divider(height: 30, thickness: 2), + MapSettingsListTile( + title: "map_settings_only_show_favorites", + selected: mapState.showFavoriteOnly, + onChanged: (favoriteOnly) => + ref.read(mapStateNotifierProvider.notifier).switchFavoriteOnly(favoriteOnly), + ), + MapSettingsListTile( + title: "map_settings_include_show_archived", + selected: mapState.includeArchived, + onChanged: (includeArchive) => + ref.read(mapStateNotifierProvider.notifier).switchIncludeArchived(includeArchive), + ), + MapSettingsListTile( + title: "map_settings_include_show_partners", + selected: mapState.withPartners, + onChanged: (withPartners) => + ref.read(mapStateNotifierProvider.notifier).switchWithPartners(withPartners), + ), + MapTimeDropDown( + relativeTime: mapState.relativeTime, + onTimeChange: (time) => ref.read(mapStateNotifierProvider.notifier).setRelativeTime(time), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/map/map_theme_override.dart b/mobile/lib/widgets/map/map_theme_override.dart index 93cbd8846f..57f970b0d1 100644 --- a/mobile/lib/widgets/map/map_theme_override.dart +++ b/mobile/lib/widgets/map/map_theme_override.dart @@ -66,10 +66,10 @@ class _MapThemeOverrideState extends ConsumerState with Widget @override Widget build(BuildContext context) { _theme = widget.themeMode ?? ref.watch(mapStateNotifierProvider.select((v) => v.themeMode)); - final appTheme = ref.watch(immichThemeProvider); + var appTheme = ref.watch(immichThemeProvider); final locale = ref.watch(localeProvider); - useValueChanged(_theme, (_, _) { + useValueChanged(_theme, (_, __) { if (_theme == ThemeMode.system) { WidgetsBinding.instance.addObserver(this); } else { diff --git a/mobile/lib/widgets/map/map_thumbnail.dart b/mobile/lib/widgets/map/map_thumbnail.dart index 84c2ac0100..7defb52264 100644 --- a/mobile/lib/widgets/map/map_thumbnail.dart +++ b/mobile/lib/widgets/map/map_thumbnail.dart @@ -2,11 +2,12 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart'; -import 'package:immich_mobile/widgets/map/asset_marker_icon.dart'; import 'package:immich_mobile/widgets/map/map_theme_override.dart'; +import 'package:immich_mobile/widgets/map/asset_marker_icon.dart'; import 'package:maplibre_gl/maplibre_gl.dart'; /// A non-interactive thumbnail of a map in the given coordinates with optional markers @@ -14,7 +15,7 @@ import 'package:maplibre_gl/maplibre_gl.dart'; /// User can provide either a [assetMarkerRemoteId] to display the asset's thumbnail or set /// [showMarkerPin] to true which would display a marker pin instead. If both are provided, /// [assetMarkerRemoteId] will take precedence -class MapThumbnail extends HookWidget { +class MapThumbnail extends HookConsumerWidget { final Function(Point, LatLng)? onTap; final LatLng centre; final String? assetMarkerRemoteId; @@ -43,7 +44,7 @@ class MapThumbnail extends HookWidget { }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final controller = useRef(null); final styleLoaded = useState(false); diff --git a/mobile/lib/widgets/map/positioned_asset_marker_icon.dart b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart new file mode 100644 index 0000000000..b6d7241cf4 --- /dev/null +++ b/mobile/lib/widgets/map/positioned_asset_marker_icon.dart @@ -0,0 +1,43 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/widgets/map/asset_marker_icon.dart'; + +class PositionedAssetMarkerIcon extends StatelessWidget { + final Point point; + final String assetRemoteId; + final String assetThumbhash; + final double size; + final int durationInMilliseconds; + + final Function()? onTap; + + const PositionedAssetMarkerIcon({ + required this.point, + required this.assetRemoteId, + required this.assetThumbhash, + this.size = 100, + this.durationInMilliseconds = 100, + this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context) { + final ratio = Platform.isIOS ? 1.0 : context.devicePixelRatio; + return AnimatedPositioned( + left: point.x / ratio - size / 2, + top: point.y / ratio - size, + duration: Duration(milliseconds: durationInMilliseconds), + child: GestureDetector( + onTap: () => onTap?.call(), + child: SizedBox.square( + dimension: size, + child: AssetMarkerIcon(id: assetRemoteId, thumbhash: assetThumbhash, key: Key(assetRemoteId)), + ), + ), + ); + } +} diff --git a/mobile/lib/widgets/memories/memory_bottom_info.dart b/mobile/lib/widgets/memories/memory_bottom_info.dart new file mode 100644 index 0000000000..4b43821782 --- /dev/null +++ b/mobile/lib/widgets/memories/memory_bottom_info.dart @@ -0,0 +1,50 @@ +// ignore_for_file: require_trailing_commas + +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/models/memories/memory.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; + +class MemoryBottomInfo extends StatelessWidget { + final Memory memory; + + const MemoryBottomInfo({super.key, required this.memory}); + + @override + Widget build(BuildContext context) { + final df = DateFormat.yMMMMd(); + return Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + memory.title, + style: TextStyle(color: Colors.grey[400], fontSize: 13.0, fontWeight: FontWeight.w500), + ), + Text( + df.format(memory.assets[0].fileCreatedAt), + style: const TextStyle(color: Colors.white, fontSize: 15.0, fontWeight: FontWeight.w500), + ), + ], + ), + MaterialButton( + minWidth: 0, + onPressed: () { + context.maybePop(); + scrollToDateNotifierProvider.scrollToDate(memory.assets[0].fileCreatedAt); + }, + shape: const CircleBorder(), + color: Colors.white.withValues(alpha: 0.2), + elevation: 0, + child: const Icon(Icons.open_in_new, color: Colors.white), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/memories/memory_card.dart b/mobile/lib/widgets/memories/memory_card.dart new file mode 100644 index 0000000000..189cc67428 --- /dev/null +++ b/mobile/lib/widgets/memories/memory_card.dart @@ -0,0 +1,117 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/pages/common/native_video_viewer.page.dart'; +import 'package:immich_mobile/utils/hooks/blurhash_hook.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; + +class MemoryCard extends StatelessWidget { + final Asset asset; + final String title; + final bool showTitle; + final Function()? onVideoEnded; + + const MemoryCard({required this.asset, required this.title, required this.showTitle, this.onVideoEnded, super.key}); + + @override + Widget build(BuildContext context) { + return Card( + color: Colors.black, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(25.0)), + side: BorderSide(color: Colors.black, width: 1.0), + ), + clipBehavior: Clip.hardEdge, + child: Stack( + children: [ + SizedBox.expand(child: _BlurredBackdrop(asset: asset)), + LayoutBuilder( + builder: (context, constraints) { + // Determine the fit using the aspect ratio + BoxFit fit = BoxFit.contain; + if (asset.width != null && asset.height != null) { + final aspectRatio = asset.width! / asset.height!; + final phoneAspectRatio = constraints.maxWidth / constraints.maxHeight; + // Look for a 25% difference in either direction + if (phoneAspectRatio * .75 < aspectRatio && phoneAspectRatio * 1.25 > aspectRatio) { + // Cover to look nice if we have nearly the same aspect ratio + fit = BoxFit.cover; + } + } + + if (asset.isImage) { + return Hero( + tag: 'memory-${asset.id}', + child: ImmichImage(asset, fit: fit, height: double.infinity, width: double.infinity), + ); + } else { + return Hero( + tag: 'memory-${asset.id}', + child: SizedBox( + width: context.width, + height: context.height, + child: NativeVideoViewerPage( + key: ValueKey(asset.id), + asset: asset, + showControls: false, + playbackDelayFactor: 2, + image: ImmichImage(asset, width: context.width, height: context.height, fit: BoxFit.contain), + ), + ), + ); + } + }, + ), + if (showTitle) + Positioned( + left: 18.0, + bottom: 18.0, + child: Text( + title, + style: context.textTheme.headlineMedium?.copyWith(color: Colors.white, fontWeight: FontWeight.w500), + ), + ), + ], + ), + ); + } +} + +class _BlurredBackdrop extends HookWidget { + final Asset asset; + + const _BlurredBackdrop({required this.asset}); + + @override + Widget build(BuildContext context) { + final blurhash = useBlurHashRef(asset).value; + if (blurhash != null) { + // Use a nice cheap blur hash image decoration + return Container( + decoration: BoxDecoration( + image: DecorationImage(image: MemoryImage(blurhash), fit: BoxFit.cover), + ), + child: Container(color: Colors.black.withValues(alpha: 0.2)), + ); + } else { + // Fall back to using a more expensive image filtered + // Since the ImmichImage is already precached, we can + // safely use that as the image provider + return ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: ImmichImage.imageProvider(asset: asset, height: context.height, width: context.width), + fit: BoxFit.cover, + ), + ), + child: Container(color: Colors.black.withValues(alpha: 0.2)), + ), + ); + } + } +} diff --git a/mobile/lib/widgets/memories/memory_epilogue.dart b/mobile/lib/widgets/memories/memory_epilogue.dart index 0335b586ac..b866ed049a 100644 --- a/mobile/lib/widgets/memories/memory_epilogue.dart +++ b/mobile/lib/widgets/memories/memory_epilogue.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class MemoryEpilogue extends StatefulWidget { final Function()? onStartOver; @@ -45,23 +45,23 @@ class _MemoryEpilogueState extends State with TickerProviderStat ), const SizedBox(height: 16.0), Text( - context.t.memories_all_caught_up, + "memories_all_caught_up", style: context.textTheme.headlineMedium?.copyWith(color: Colors.white), - ), + ).tr(), const SizedBox(height: 16.0), Text( - context.t.memories_check_back_tomorrow, + "memories_check_back_tomorrow", style: context.textTheme.bodyMedium?.copyWith(color: Colors.white), - ), + ).tr(), const SizedBox(height: 16.0), TextButton( onPressed: widget.onStartOver, child: Text( - context.t.memories_start_over, + "memories_start_over", style: context.textTheme.displayMedium?.copyWith( color: context.isDarkTheme ? context.colorScheme.primary : context.colorScheme.inversePrimary, ), - ), + ).tr(), ), ], ), @@ -85,9 +85,9 @@ class _MemoryEpilogueState extends State with TickerProviderStat ), ), Text( - context.t.memories_swipe_to_close, + "memories_swipe_to_close", style: context.textTheme.bodyMedium?.copyWith(color: Colors.white), - ), + ).tr(), ], ), ), diff --git a/mobile/lib/widgets/memories/memory_lane.dart b/mobile/lib/widgets/memories/memory_lane.dart new file mode 100644 index 0000000000..4cba83bea7 --- /dev/null +++ b/mobile/lib/widgets/memories/memory_lane.dart @@ -0,0 +1,91 @@ +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/models/memories/memory.model.dart'; +import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/providers/memory.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/widgets/common/immich_image.dart'; + +class MemoryLane extends HookConsumerWidget { + const MemoryLane({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final memoryLaneFutureProvider = ref.watch(memoryFutureProvider); + + final memoryLane = memoryLaneFutureProvider + .whenData( + (memories) => memories != null + ? ConstrainedBox( + constraints: const BoxConstraints(maxHeight: 200), + child: CarouselView( + itemExtent: 145.0, + shrinkExtent: 1.0, + elevation: 2, + backgroundColor: Colors.black, + overlayColor: WidgetStateProperty.all(Colors.white.withValues(alpha: 0.1)), + onTap: (memoryIndex) { + ref.read(hapticFeedbackProvider.notifier).heavyImpact(); + if (memories[memoryIndex].assets.isNotEmpty) { + final asset = memories[memoryIndex].assets[0]; + ref.read(currentAssetProvider.notifier).set(asset); + } + context.pushRoute(MemoryRoute(memories: memories, memoryIndex: memoryIndex)); + }, + children: memories + .mapIndexed((index, memory) => MemoryCard(index: index, memory: memory)) + .toList(), + ), + ) + : const SizedBox(), + ) + .value; + + return memoryLane ?? const SizedBox(); + } +} + +class MemoryCard extends ConsumerWidget { + const MemoryCard({super.key, required this.index, required this.memory}); + + final int index; + final Memory memory; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Center( + child: Stack( + children: [ + ColorFiltered( + colorFilter: ColorFilter.mode(Colors.black.withValues(alpha: 0.2), BlendMode.darken), + child: Hero( + tag: 'memory-${memory.assets[0].id}', + child: ImmichImage( + memory.assets[0], + fit: BoxFit.cover, + width: 205, + height: 200, + placeholder: const ThumbnailPlaceholder(width: 105, height: 200), + ), + ), + ), + Positioned( + bottom: 16, + left: 16, + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 114), + child: Text( + memory.title, + style: const TextStyle(fontWeight: FontWeight.w600, color: Colors.white, fontSize: 15), + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/photo_view/photo_view_gallery.dart b/mobile/lib/widgets/photo_view/photo_view_gallery.dart index 3c41b2ca19..aa33d18403 100644 --- a/mobile/lib/widgets/photo_view/photo_view_gallery.dart +++ b/mobile/lib/widgets/photo_view/photo_view_gallery.dart @@ -128,8 +128,8 @@ class PhotoViewGallery extends StatefulWidget { /// The builder must return a [PhotoViewGalleryPageOptions]. const PhotoViewGallery.builder({ super.key, - required int this.itemCount, - required PhotoViewGalleryBuilder this.builder, + required this.itemCount, + required this.builder, this.loadingBuilder, this.backgroundDecoration, this.wantKeepAlive = false, @@ -145,7 +145,9 @@ class PhotoViewGallery extends StatefulWidget { this.customSize, this.allowImplicitScrolling = false, this.enablePanAlways = false, - }) : pageOptions = null; + }) : pageOptions = null, + assert(itemCount != null), + assert(builder != null); /// A list of options to describe the items in the gallery final List? pageOptions; @@ -350,9 +352,9 @@ class _PhotoViewGalleryState extends State { /// The [maxScale], [minScale] and [initialScale] options may be [double] or a [PhotoViewComputedScale] constant /// class PhotoViewGalleryPageOptions { - const PhotoViewGalleryPageOptions({ + PhotoViewGalleryPageOptions({ this.key, - required ImageProvider this.imageProvider, + required this.imageProvider, this.heroAttributes, this.semanticLabel, this.minScale, @@ -377,7 +379,8 @@ class PhotoViewGalleryPageOptions { this.disableGestures, this.errorBuilder, }) : child = null, - childSize = null; + childSize = null, + assert(imageProvider != null); const PhotoViewGalleryPageOptions.customChild({ this.key, diff --git a/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart b/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart index 08c300e9ae..b9475a9ee2 100644 --- a/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart +++ b/mobile/lib/widgets/photo_view/src/controller/photo_view_controller.dart @@ -209,7 +209,7 @@ class PhotoViewController implements PhotoViewControllerBase PhotoViewHeroAttributes? get heroAttributes => widget.heroAttributes; + late ScaleBoundaries cachedScaleBoundaries = widget.scaleBoundaries; + void handleScaleAnimation() { scale = _scaleAnimation!.value; } @@ -203,7 +203,7 @@ class PhotoViewCoreState extends State } else if (scaleState == PhotoViewScaleState.zoomedIn) { animateRotation(controller.rotation, 0); if (_shouldAllowPanRotate()) { - animatePosition(controller.position, clampPosition()); + animatePosition(controller.position, Offset.zero); } } @@ -242,8 +242,9 @@ class PhotoViewCoreState extends State return; } _scaleAnimation = Tween(begin: from, end: to).animate(_scaleAnimationController); - _scaleAnimationController.value = 0.0; - unawaited(_scaleAnimationController.fling(velocity: 0.4)); + _scaleAnimationController + ..value = 0.0 + ..fling(velocity: 0.4); } void animatePosition(Offset from, Offset to) { @@ -251,8 +252,9 @@ class PhotoViewCoreState extends State return; } _positionAnimation = Tween(begin: from, end: to).animate(_positionAnimationController); - _positionAnimationController.value = 0.0; - unawaited(_positionAnimationController.fling(velocity: 0.4)); + _positionAnimationController + ..value = 0.0 + ..fling(velocity: 0.4); } void animateRotation(double from, double to) { @@ -260,8 +262,9 @@ class PhotoViewCoreState extends State return; } _rotationAnimation = Tween(begin: from, end: to).animate(_rotationAnimationController); - _rotationAnimationController.value = 0.0; - unawaited(_rotationAnimationController.fling(velocity: 0.4)); + _rotationAnimationController + ..value = 0.0 + ..fling(velocity: 0.4); } void onAnimationStatus(AnimationStatus status) { @@ -300,7 +303,7 @@ class PhotoViewCoreState extends State controller.scaleAnimationBuilder(_animateControllerScale); controller.rotationAnimationBuilder(_animateControllerRotation); - _updateScaleBoundaries(); + cachedScaleBoundaries = widget.scaleBoundaries; _scaleAnimationController = AnimationController(vsync: this) ..addListener(handleScaleAnimation) @@ -331,29 +334,14 @@ class PhotoViewCoreState extends State widget.onTapDown?.call(context, details, controller.value); } - void _updateScaleBoundaries() { - final prev = controller.scaleBoundaries; - if (prev == widget.scaleBoundaries) { - return; - } - - if (prev != null && controller.scale != null && prev.initialScale > 0) { - final ratio = widget.scaleBoundaries.initialScale / prev.initialScale; - controller.setScaleInvisibly(controller.scale! * ratio); - } else { - markNeedsScaleRecalc = true; - } - controller.scaleBoundaries = widget.scaleBoundaries; - } - - @override - void didUpdateWidget(PhotoViewCore oldWidget) { - super.didUpdateWidget(oldWidget); - _updateScaleBoundaries(); - } - @override Widget build(BuildContext context) { + // Check if we need a recalc on the scale + if (widget.scaleBoundaries != cachedScaleBoundaries) { + markNeedsScaleRecalc = true; + cachedScaleBoundaries = widget.scaleBoundaries; + } + return StreamBuilder( stream: controller.outputStateStream, initialData: controller.prevValue, @@ -435,7 +423,7 @@ class PhotoViewCoreState extends State ? SizedBox( width: scaleBoundaries.childSize.width * scale, height: scaleBoundaries.childSize.height * scale, - child: widget.customChild, + child: widget.customChild!, ) : Image( key: widget.heroAttributes?.tag != null ? ObjectKey(widget.heroAttributes!.tag) : null, diff --git a/mobile/lib/widgets/photo_view/src/core/photo_view_gesture_detector.dart b/mobile/lib/widgets/photo_view/src/core/photo_view_gesture_detector.dart index 8f408e043e..7b55e0e37e 100644 --- a/mobile/lib/widgets/photo_view/src/core/photo_view_gesture_detector.dart +++ b/mobile/lib/widgets/photo_view/src/core/photo_view_gesture_detector.dart @@ -121,6 +121,7 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer { super.debugOwner, this.validateAxis, this.touchSlopFactor = 1, + PointerDeviceKind? kind, this.disableScaleGestures = false, }) : super(supportedDevices: null); final HitCornersDetector? hitDetector; diff --git a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart index e55df57105..d8d2ae7ee5 100644 --- a/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart +++ b/mobile/lib/widgets/photo_view/src/photo_view_wrappers.dart @@ -75,13 +75,14 @@ class ImageWrapper extends StatefulWidget { final int index; @override - State createState() => _ImageWrapperState(); + createState() => _ImageWrapperState(); } class _ImageWrapperState extends State { ImageStreamListener? _imageStreamListener; ImageStream? _imageStream; ImageChunkEvent? _loadingProgress; + ImageInfo? _imageInfo; bool _loading = true; Size? _imageSize; Object? _lastException; @@ -121,7 +122,7 @@ class _ImageWrapperState extends State { // retrieve image from the provider void _resolveImage() { - final ImageStream newStream = widget.imageProvider.resolve(ImageConfiguration.empty); + final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration()); _updateSourceStream(newStream); } @@ -134,15 +135,17 @@ class _ImageWrapperState extends State { } void handleImageFrame(ImageInfo info, bool synchronousCall) { - void setupCB() { + setupCB() { _imageSize = Size(info.image.width.toDouble(), info.image.height.toDouble()); _loading = false; + _imageInfo = _imageInfo; _loadingProgress = null; _lastException = null; _lastStack = null; _didLoadSynchronously = synchronousCall; + widget.controller.scaleBoundaries = scaleBoundaries; } synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB); diff --git a/mobile/lib/widgets/photo_view/src/utils/ignorable_change_notifier.dart b/mobile/lib/widgets/photo_view/src/utils/ignorable_change_notifier.dart index 27aa256b14..3ca31cb8f9 100644 --- a/mobile/lib/widgets/photo_view/src/utils/ignorable_change_notifier.dart +++ b/mobile/lib/widgets/photo_view/src/utils/ignorable_change_notifier.dart @@ -28,12 +28,12 @@ class IgnorableChangeNotifier extends ChangeNotifier { return super.hasListeners || (_ignorableListeners?.isNotEmpty ?? false); } - void addIgnorableListener(VoidCallback listener) { + void addIgnorableListener(listener) { assert(_debugAssertNotDisposed()); _ignorableListeners!.add(listener); } - void removeIgnorableListener(VoidCallback listener) { + void removeIgnorableListener(listener) { assert(_debugAssertNotDisposed()); _ignorableListeners!.remove(listener); } diff --git a/mobile/lib/widgets/photo_view/src/utils/photo_view_utils.dart b/mobile/lib/widgets/photo_view/src/utils/photo_view_utils.dart index c1ee1ab769..d120955250 100644 --- a/mobile/lib/widgets/photo_view/src/utils/photo_view_utils.dart +++ b/mobile/lib/widgets/photo_view/src/utils/photo_view_utils.dart @@ -32,10 +32,12 @@ class ScaleBoundaries { double get minScale { assert(_minScale is double || _minScale is PhotoViewComputedScale); if (_minScale == PhotoViewComputedScale.contained) { - return _scaleForContained(outerSize, childSize) * (_minScale as PhotoViewComputedScale).multiplier; + return _scaleForContained(outerSize, childSize) * + (_minScale as PhotoViewComputedScale).multiplier; // ignore: avoid_as } if (_minScale == PhotoViewComputedScale.covered) { - return _scaleForCovering(outerSize, childSize) * (_minScale as PhotoViewComputedScale).multiplier; + return _scaleForCovering(outerSize, childSize) * + (_minScale as PhotoViewComputedScale).multiplier; // ignore: avoid_as } assert(_minScale >= 0.0); return _minScale; @@ -44,16 +46,16 @@ class ScaleBoundaries { double get maxScale { assert(_maxScale is double || _maxScale is PhotoViewComputedScale); if (_maxScale == PhotoViewComputedScale.contained) { - return (_scaleForContained(outerSize, childSize) * (_maxScale as PhotoViewComputedScale).multiplier).clamp( - minScale, - double.infinity, - ); + return (_scaleForContained(outerSize, childSize) * + (_maxScale as PhotoViewComputedScale) // ignore: avoid_as + .multiplier) + .clamp(minScale, double.infinity); } if (_maxScale == PhotoViewComputedScale.covered) { - return (_scaleForCovering(outerSize, childSize) * (_maxScale as PhotoViewComputedScale).multiplier).clamp( - minScale, - double.infinity, - ); + return (_scaleForCovering(outerSize, childSize) * + (_maxScale as PhotoViewComputedScale) // ignore: avoid_as + .multiplier) + .clamp(minScale, double.infinity); } return _maxScale.clamp(minScale, double.infinity); } @@ -61,10 +63,14 @@ class ScaleBoundaries { double get initialScale { assert(_initialScale is double || _initialScale is PhotoViewComputedScale); if (_initialScale == PhotoViewComputedScale.contained) { - return _scaleForContained(outerSize, childSize) * (_initialScale as PhotoViewComputedScale).multiplier; + return _scaleForContained(outerSize, childSize) * + (_initialScale as PhotoViewComputedScale) // ignore: avoid_as + .multiplier; } if (_initialScale == PhotoViewComputedScale.covered) { - return _scaleForCovering(outerSize, childSize) * (_initialScale as PhotoViewComputedScale).multiplier; + return _scaleForCovering(outerSize, childSize) * + (_initialScale as PhotoViewComputedScale) // ignore: avoid_as + .multiplier; } return _initialScale.clamp(minScale, maxScale); } diff --git a/mobile/lib/widgets/search/curated_people_row.dart b/mobile/lib/widgets/search/curated_people_row.dart new file mode 100644 index 0000000000..9155de2131 --- /dev/null +++ b/mobile/lib/widgets/search/curated_people_row.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; + +class CuratedPeopleRow extends StatelessWidget { + static const double imageSize = 60.0; + + final List content; + final EdgeInsets? padding; + + /// Callback with the content and the index when tapped + final Function(SearchCuratedContent, int)? onTap; + final Function(SearchCuratedContent, int)? onNameTap; + + const CuratedPeopleRow({super.key, required this.content, this.onTap, this.padding, required this.onNameTap}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: SingleChildScrollView( + padding: padding, + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: List.generate(content.length, (index) { + final person = content[index]; + return Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => onTap?.call(person, index), + child: SizedBox( + height: imageSize, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: imageSize / 2, + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), + ), + ), + ), + ), + const SizedBox(height: 8), + SizedBox(width: imageSize, child: _buildPersonLabel(context, person, index)), + ], + ), + ); + }), + ), + ), + ); + } + + Widget _buildPersonLabel(BuildContext context, SearchCuratedContent person, int index) { + if (person.label.isEmpty) { + return GestureDetector( + onTap: () => onNameTap?.call(person, index), + child: Text( + "exif_bottom_sheet_person_add_person", + style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ).tr(), + ); + } + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + person.label, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: context.textTheme.labelLarge, + maxLines: 2, + ), + if (person.subtitle != null) Text(person.subtitle!, textAlign: TextAlign.center), + ], + ); + } +} diff --git a/mobile/lib/widgets/search/curated_places_row.dart b/mobile/lib/widgets/search/curated_places_row.dart new file mode 100644 index 0000000000..9d21292bde --- /dev/null +++ b/mobile/lib/widgets/search/curated_places_row.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/widgets/search/search_map_thumbnail.dart'; +import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart'; + +class CuratedPlacesRow extends StatelessWidget { + const CuratedPlacesRow({ + super.key, + required this.content, + required this.imageSize, + this.isMapEnabled = true, + this.onTap, + }); + + final bool isMapEnabled; + final List content; + final double imageSize; + + /// Callback with the content and the index when tapped + final Function(SearchCuratedContent, int)? onTap; + + @override + Widget build(BuildContext context) { + // Calculating the actual index of the content based on the whether map is enabled or not. + // If enabled, inject map as the first item in the list (index 0) and so the actual content will start from index 1 + final int actualContentIndex = isMapEnabled ? 1 : 0; + + return SizedBox( + height: imageSize, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.symmetric(horizontal: 16), + separatorBuilder: (context, index) => const SizedBox(width: 10), + itemBuilder: (context, index) { + // Injecting Map thumbnail as the first element + if (isMapEnabled && index == 0) { + return SizedBox.square( + dimension: imageSize, + child: SearchMapThumbnail(size: imageSize), + ); + } + final actualIndex = index - actualContentIndex; + final object = content[actualIndex]; + final thumbnailRequestUrl = '${Store.get(StoreKey.serverEndpoint)}/assets/${object.id}/thumbnail'; + return SizedBox.square( + dimension: imageSize, + child: ThumbnailWithInfo( + imageUrl: thumbnailRequestUrl, + textInfo: object.label, + onTap: () => onTap?.call(object, actualIndex), + ), + ); + }, + itemCount: content.length + actualContentIndex, + ), + ); + } +} diff --git a/mobile/lib/widgets/search/explore_grid.dart b/mobile/lib/widgets/search/explore_grid.dart new file mode 100644 index 0000000000..6af20df029 --- /dev/null +++ b/mobile/lib/widgets/search/explore_grid.dart @@ -0,0 +1,69 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/models/search/search_curated_content.model.dart'; +import 'package:immich_mobile/models/search/search_filter.model.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart'; + +class ExploreGrid extends StatelessWidget { + final List curatedContent; + final bool isPeople; + + const ExploreGrid({super.key, required this.curatedContent, this.isPeople = false}); + + @override + Widget build(BuildContext context) { + if (curatedContent.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + height: 100, + width: 100, + child: ThumbnailWithInfo(textInfo: '', onTap: () {}), + ), + ); + } + + return GridView.builder( + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 140, + mainAxisSpacing: 4, + crossAxisSpacing: 4, + ), + itemBuilder: (context, index) { + final content = curatedContent[index]; + final thumbnailRequestUrl = isPeople + ? getFaceThumbnailUrl(content.id) + : '${Store.get(StoreKey.serverEndpoint)}/assets/${content.id}/thumbnail'; + + return ThumbnailWithInfo( + imageUrl: thumbnailRequestUrl, + textInfo: content.label, + borderRadius: 0, + onTap: () { + isPeople + ? context.pushRoute(PersonResultRoute(personId: content.id, personName: content.label)) + : context.pushRoute( + SearchRoute( + prefilter: SearchFilter( + people: {}, + location: SearchLocationFilter(city: content.label), + camera: SearchCameraFilter(), + date: SearchDateFilter(), + display: SearchDisplayFilters(isNotInAlbum: false, isArchive: false, isFavorite: false), + rating: SearchRatingFilter(), + mediaType: AssetType.other, + ), + ), + ); + }, + ); + }, + itemCount: curatedContent.length, + ); + } +} diff --git a/mobile/lib/widgets/search/person_name_edit_form.dart b/mobile/lib/widgets/search/person_name_edit_form.dart new file mode 100644 index 0000000000..3fa443121a --- /dev/null +++ b/mobile/lib/widgets/search/person_name_edit_form.dart @@ -0,0 +1,65 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; + +class PersonNameEditFormResult { + final bool success; + final String updatedName; + + const PersonNameEditFormResult(this.success, this.updatedName); +} + +class PersonNameEditForm extends HookConsumerWidget { + final String personId; + final String personName; + + const PersonNameEditForm({super.key, required this.personId, required this.personName}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final controller = useTextEditingController(text: personName); + final isError = useState(false); + + return AlertDialog( + title: const Text("add_a_name", style: TextStyle(fontWeight: FontWeight.bold)).tr(), + content: SingleChildScrollView( + child: TextFormField( + controller: controller, + textCapitalization: TextCapitalization.words, + autofocus: true, + decoration: InputDecoration( + hintText: 'name'.tr(), + border: const OutlineInputBorder(), + errorText: isError.value ? 'Error occurred' : null, + ), + ), + ), + actions: [ + TextButton( + onPressed: () => context.pop(const PersonNameEditFormResult(false, '')), + child: Text( + "cancel", + style: TextStyle(color: Colors.red[300], fontWeight: FontWeight.bold), + ).tr(), + ), + TextButton( + onPressed: () async { + isError.value = false; + final result = await ref.read(updatePersonNameProvider(personId, controller.text).future); + isError.value = !result; + if (result) { + context.pop(PersonNameEditFormResult(true, controller.text)); + } + }, + child: Text( + "save", + style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold), + ).tr(), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/search/search_filter/camera_picker.dart b/mobile/lib/widgets/search/search_filter/camera_picker.dart index aad5fda18f..a5204c2fbc 100644 --- a/mobile/lib/widgets/search/search_filter/camera_picker.dart +++ b/mobile/lib/widgets/search/search_filter/camera_picker.dart @@ -1,8 +1,8 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/providers/search/search_filter.provider.dart'; import 'package:immich_mobile/widgets/search/search_filter/common/dropdown.dart'; @@ -21,15 +21,9 @@ class CameraPicker extends HookConsumerWidget { final selectedMake = useState(filter?.make); final selectedModel = useState(filter?.model); - final make = ref.watch( - getSearchSuggestionsProvider(const SearchSuggestionArgs(type: SearchSuggestionType.cameraMake)), - ); + final make = ref.watch(getSearchSuggestionsProvider(SearchSuggestionType.cameraMake)); - final models = ref.watch( - getSearchSuggestionsProvider( - SearchSuggestionArgs(type: SearchSuggestionType.cameraModel, make: selectedMake.value), - ), - ); + final models = ref.watch(getSearchSuggestionsProvider(SearchSuggestionType.cameraModel, make: selectedMake.value)); final makeWidget = SearchDropdown( dropdownMenuEntries: switch (make) { @@ -37,7 +31,7 @@ class CameraPicker extends HookConsumerWidget { AsyncData(:final value) => value.map((e) => DropdownMenuEntry(value: e, label: e)).toList(), _ => [], }, - label: Text(context.t.make), + label: const Text('make').tr(), controller: makeTextController, leadingIcon: const Icon(Icons.photo_camera_rounded), onSelected: (value) { @@ -56,7 +50,7 @@ class CameraPicker extends HookConsumerWidget { AsyncData(:final value) => value.map((e) => DropdownMenuEntry(value: e, label: e)).toList(), _ => [], }, - label: Text(context.t.model), + label: const Text('model').tr(), controller: modelTextController, leadingIcon: const Icon(Icons.camera), onSelected: (value) { diff --git a/mobile/lib/widgets/search/search_filter/display_option_picker.dart b/mobile/lib/widgets/search/search_filter/display_option_picker.dart index 83ddc98107..a64eab7b71 100644 --- a/mobile/lib/widgets/search/search_filter/display_option_picker.dart +++ b/mobile/lib/widgets/search/search_filter/display_option_picker.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; enum DisplayOption { notInAlbum, favorite, archive } @@ -23,7 +23,7 @@ class DisplayOptionPicker extends HookWidget { shrinkWrap: true, children: [ CheckboxListTile( - title: Text(context.t.search_filter_display_option_not_in_album), + title: const Text('search_filter_display_option_not_in_album').tr(), value: options.value[DisplayOption.notInAlbum], onChanged: (bool? value) { options.value = {...options.value, DisplayOption.notInAlbum: value!}; @@ -31,7 +31,7 @@ class DisplayOptionPicker extends HookWidget { }, ), CheckboxListTile( - title: Text(context.t.favorite), + title: const Text('favorite').tr(), value: options.value[DisplayOption.favorite], onChanged: (value) { options.value = {...options.value, DisplayOption.favorite: value!}; @@ -39,7 +39,7 @@ class DisplayOptionPicker extends HookWidget { }, ), CheckboxListTile( - title: Text(context.t.archive), + title: const Text('archive').tr(), value: options.value[DisplayOption.archive], onChanged: (value) { options.value = {...options.value, DisplayOption.archive: value!}; diff --git a/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart b/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart index a74ea41c38..dee42ec5a0 100644 --- a/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart +++ b/mobile/lib/widgets/search/search_filter/filter_bottom_sheet_scaffold.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class FilterBottomSheetScaffold extends StatelessWidget { const FilterBottomSheetScaffold({ @@ -20,7 +20,7 @@ class FilterBottomSheetScaffold extends StatelessWidget { @override Widget build(BuildContext context) { - Widget buildChildWidget() { + buildChildWidget() { if (expanded != null && expanded == true) { return Expanded(child: child); } @@ -46,7 +46,7 @@ class FilterBottomSheetScaffold extends StatelessWidget { onClear(); context.pop(); }, - child: Text(context.t.clear), + child: const Text('clear').tr(), ), if (onSearch != null) const SizedBox(width: 8), if (onSearch != null) @@ -56,7 +56,7 @@ class FilterBottomSheetScaffold extends StatelessWidget { onSearch!(); context.pop(); }, - child: Text(context.t.search_filter_apply), + child: const Text('search_filter_apply').tr(), ), ], ), diff --git a/mobile/lib/widgets/search/search_filter/location_picker.dart b/mobile/lib/widgets/search/search_filter/location_picker.dart index ab35145a28..608183a2f6 100644 --- a/mobile/lib/widgets/search/search_filter/location_picker.dart +++ b/mobile/lib/widgets/search/search_filter/location_picker.dart @@ -1,7 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; import 'package:immich_mobile/providers/search/search_filter.provider.dart'; import 'package:immich_mobile/widgets/search/search_filter/common/dropdown.dart'; @@ -25,31 +25,25 @@ class LocationPicker extends HookConsumerWidget { final countries = ref.watch( getSearchSuggestionsProvider( - SearchSuggestionArgs( - type: SearchSuggestionType.country, - locationCountry: selectedCountry.value, - locationState: selectedState.value, - ), + SearchSuggestionType.country, + locationCountry: selectedCountry.value, + locationState: selectedState.value, ), ); final states = ref.watch( getSearchSuggestionsProvider( - SearchSuggestionArgs( - type: SearchSuggestionType.state, - locationCountry: selectedCountry.value, - locationState: selectedState.value, - ), + SearchSuggestionType.state, + locationCountry: selectedCountry.value, + locationState: selectedState.value, ), ); final cities = ref.watch( getSearchSuggestionsProvider( - SearchSuggestionArgs( - type: SearchSuggestionType.city, - locationCountry: selectedCountry.value, - locationState: selectedState.value, - ), + SearchSuggestionType.city, + locationCountry: selectedCountry.value, + locationState: selectedState.value, ), ); @@ -61,7 +55,7 @@ class LocationPicker extends HookConsumerWidget { AsyncData(:final value) => value.map((e) => DropdownMenuEntry(value: e, label: e)).toList(), _ => [], }, - label: Text(context.t.country), + label: const Text('country').tr(), controller: countryTextController, onSelected: (value) { if (value.toString() == selectedCountry.value) { @@ -80,7 +74,7 @@ class LocationPicker extends HookConsumerWidget { AsyncData(:final value) => value.map((e) => DropdownMenuEntry(value: e, label: e)).toList(), _ => [], }, - label: Text(context.t.state), + label: const Text('state').tr(), controller: stateTextController, onSelected: (value) { if (value.toString() == selectedState.value) { @@ -98,7 +92,7 @@ class LocationPicker extends HookConsumerWidget { AsyncData(:final value) => value.map((e) => DropdownMenuEntry(value: e, label: e)).toList(), _ => [], }, - label: Text(context.t.city), + label: const Text('city').tr(), controller: cityTextController, onSelected: (value) { selectedCity.value = value.toString(); diff --git a/mobile/lib/widgets/search/search_filter/media_type_picker.dart b/mobile/lib/widgets/search/search_filter/media_type_picker.dart index dd0767a97a..e0e34b654e 100644 --- a/mobile/lib/widgets/search/search_filter/media_type_picker.dart +++ b/mobile/lib/widgets/search/search_filter/media_type_picker.dart @@ -1,7 +1,7 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; class MediaTypePicker extends HookWidget { const MediaTypePicker({super.key, required this.onSelect, this.filter}); @@ -21,9 +21,9 @@ class MediaTypePicker extends HookWidget { groupValue: selectedMediaType.value, child: Column( children: [ - RadioListTile(key: const Key("all"), title: Text(context.t.all), value: AssetType.other), - RadioListTile(key: const Key("image"), title: Text(context.t.image), value: AssetType.image), - RadioListTile(key: const Key("video"), title: Text(context.t.video), value: AssetType.video), + RadioListTile(key: const Key("all"), title: const Text("all").tr(), value: AssetType.other), + RadioListTile(key: const Key("image"), title: const Text("image").tr(), value: AssetType.image), + RadioListTile(key: const Key("video"), title: const Text("video").tr(), value: AssetType.video), ], ), ); diff --git a/mobile/lib/widgets/search/search_filter/people_picker.dart b/mobile/lib/widgets/search/search_filter/people_picker.dart index ee738265b2..978b70239c 100644 --- a/mobile/lib/widgets/search/search_filter/people_picker.dart +++ b/mobile/lib/widgets/search/search_filter/people_picker.dart @@ -1,112 +1,104 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/person.model.dart'; -import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/extensions/string_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/pages/common/large_leading_tile.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; -import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; -import 'package:immich_mobile/widgets/common/search_field.dart'; - -class PeoplePicker extends HookConsumerWidget { - const PeoplePicker({super.key, required this.onSelect, this.filter}); - - final Function(Set) onSelect; - final Set? filter; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final formFocus = useFocusNode(); - const imageSize = 60.0; - final searchQuery = useState(''); - final people = ref.watch(getAllPeopleProvider); - final selectedPeople = useState>(filter ?? {}); - - return Column( - children: [ - Padding( - padding: const EdgeInsets.all(8), - child: SearchField( - focusNode: formFocus, - onChanged: (value) => searchQuery.value = value, - onTapOutside: (_) => formFocus.unfocus(), - filled: true, - hintText: context.t.filter_people, - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 0), - child: Divider(color: context.colorScheme.surfaceContainerHighest, thickness: 1), - ), - Expanded( - child: people.widgetWhen( - onData: (people) { - final filtered = people - .where( - (person) => person.name.toLowerCase().removeDiacritics().contains( - searchQuery.value.toLowerCase().removeDiacritics(), - ), - ) - .toList(); - return ListView.builder( - shrinkWrap: true, - itemCount: filtered.length, - padding: const EdgeInsets.all(8), - itemBuilder: (context, index) { - final person = filtered[index]; - final isSelected = selectedPeople.value.contains(person); - - return Padding( - key: ValueKey(person.id), - padding: const EdgeInsets.only(bottom: 2.0), - child: LargeLeadingTile( - title: Text( - person.name.nullIfEmpty ?? context.t.no_name, - style: context.textTheme.bodyLarge?.copyWith( - fontSize: 20, - fontWeight: FontWeight.w500, - color: isSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, - ), - ), - leading: SizedBox( - height: imageSize, - child: Material( - shape: const CircleBorder(side: BorderSide.none), - elevation: 3, - child: CircleAvatar( - key: ValueKey(person.id), - maxRadius: imageSize / 2, - backgroundImage: RemoteImageProvider( - url: getFaceThumbnailUrl(person.id, updatedAt: person.updatedAt), - ), - ), - ), - ), - onTap: () { - if (selectedPeople.value.contains(person)) { - selectedPeople.value.remove(person); - } else { - selectedPeople.value.add(person); - } - - selectedPeople.value = {...selectedPeople.value}; - onSelect(selectedPeople.value); - }, - selected: isSelected, - selectedTileColor: context.primaryColor, - tileColor: context.primaryColor.withAlpha(25), - ), - ); - }, - ); - }, - ), - ), - ], - ); - } -} +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/person.model.dart'; +import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/pages/common/large_leading_tile.dart'; +import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; +import 'package:immich_mobile/providers/search/people.provider.dart'; +import 'package:immich_mobile/utils/image_url_builder.dart'; +import 'package:immich_mobile/widgets/common/search_field.dart'; + +class PeoplePicker extends HookConsumerWidget { + const PeoplePicker({super.key, required this.onSelect, this.filter}); + + final Function(Set) onSelect; + final Set? filter; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final formFocus = useFocusNode(); + final imageSize = 60.0; + final searchQuery = useState(''); + final people = ref.watch(getAllPeopleProvider); + final selectedPeople = useState>(filter ?? {}); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: SearchField( + focusNode: formFocus, + onChanged: (value) => searchQuery.value = value, + onTapOutside: (_) => formFocus.unfocus(), + filled: true, + hintText: 'filter_people'.tr(), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 0), + child: Divider(color: context.colorScheme.surfaceContainerHighest, thickness: 1), + ), + Expanded( + child: people.widgetWhen( + onData: (people) { + return ListView.builder( + shrinkWrap: true, + itemCount: people + .where((person) => person.name.toLowerCase().contains(searchQuery.value.toLowerCase())) + .length, + padding: const EdgeInsets.all(8), + itemBuilder: (context, index) { + final person = people + .where((person) => person.name.toLowerCase().contains(searchQuery.value.toLowerCase())) + .toList()[index]; + final isSelected = selectedPeople.value.contains(person); + + return Padding( + padding: const EdgeInsets.only(bottom: 2.0), + child: LargeLeadingTile( + title: Text( + person.name, + style: context.textTheme.bodyLarge?.copyWith( + fontSize: 20, + fontWeight: FontWeight.w500, + color: isSelected ? context.colorScheme.onPrimary : context.colorScheme.onSurface, + ), + ), + leading: SizedBox( + height: imageSize, + child: Material( + shape: const CircleBorder(side: BorderSide.none), + elevation: 3, + child: CircleAvatar( + maxRadius: imageSize / 2, + backgroundImage: RemoteImageProvider(url: getFaceThumbnailUrl(person.id)), + ), + ), + ), + onTap: () { + if (selectedPeople.value.contains(person)) { + selectedPeople.value.remove(person); + } else { + selectedPeople.value.add(person); + } + + selectedPeople.value = {...selectedPeople.value}; + onSelect(selectedPeople.value); + }, + selected: isSelected, + selectedTileColor: context.primaryColor, + tileColor: context.primaryColor.withAlpha(25), + ), + ); + }, + ); + }, + ), + ), + ], + ); + } +} diff --git a/mobile/lib/widgets/search/search_filter/star_rating_picker.dart b/mobile/lib/widgets/search/search_filter/star_rating_picker.dart index 643f4f167c..5591b0e264 100644 --- a/mobile/lib/widgets/search/search_filter/star_rating_picker.dart +++ b/mobile/lib/widgets/search/search_filter/star_rating_picker.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart'; -import 'package:immich_mobile/utils/option.dart'; class StarRatingPicker extends HookWidget { const StarRatingPicker({super.key, required this.onSelect, this.filter}); @@ -14,12 +13,10 @@ class StarRatingPicker extends HookWidget { final selectedRating = useState(filter); return RadioGroup( - groupValue: selectedRating.value?.rating.fold((v) => v ?? 0, () => null), + groupValue: selectedRating.value?.rating, onChanged: (int? newValue) { - if (newValue == null) { - return; - } - final newFilter = SearchRatingFilter(rating: Option.some(newValue == 0 ? null : newValue)); + if (newValue == null) return; + final newFilter = SearchRatingFilter(rating: newValue); selectedRating.value = newFilter; onSelect(newFilter); }, @@ -28,7 +25,7 @@ class StarRatingPicker extends HookWidget { 6, (index) => RadioListTile( key: Key("star_$index"), - title: Text(context.t.rating_count(count: index)), + title: Text('rating_count'.t(args: {'count': (index)})), value: index, ), ), diff --git a/mobile/lib/widgets/search/search_map_thumbnail.dart b/mobile/lib/widgets/search/search_map_thumbnail.dart new file mode 100644 index 0000000000..7533e46f1a --- /dev/null +++ b/mobile/lib/widgets/search/search_map_thumbnail.dart @@ -0,0 +1,27 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/widgets/map/map_thumbnail.dart'; +import 'package:immich_mobile/widgets/search/thumbnail_with_info_container.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; + +class SearchMapThumbnail extends StatelessWidget { + const SearchMapThumbnail({super.key, this.size = 60.0}); + + final double size; + final bool showTitle = true; + + @override + Widget build(BuildContext context) { + return ThumbnailWithInfoContainer( + label: 'search_page_your_map'.tr(), + onTap: () { + context.pushRoute(MapRoute()); + }, + child: IgnorePointer( + child: MapThumbnail(zoom: 2, centre: const LatLng(47, 5), height: size, width: size, showAttribution: false), + ), + ); + } +} diff --git a/mobile/lib/widgets/search/search_row_section.dart b/mobile/lib/widgets/search/search_row_section.dart new file mode 100644 index 0000000000..b8584fefef --- /dev/null +++ b/mobile/lib/widgets/search/search_row_section.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:immich_mobile/widgets/search/search_row_title.dart'; + +class SearchRowSection extends StatelessWidget { + const SearchRowSection({ + super.key, + required this.onViewAllPressed, + required this.title, + this.isEmpty = false, + required this.child, + }); + + final Function() onViewAllPressed; + final String title; + final bool isEmpty; + final Widget child; + + @override + Widget build(BuildContext context) { + if (isEmpty) { + return const SizedBox.shrink(); + } + + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SearchRowTitle(onViewAllPressed: onViewAllPressed, title: title), + ), + child, + ], + ); + } +} diff --git a/mobile/lib/widgets/search/search_row_title.dart b/mobile/lib/widgets/search/search_row_title.dart index cf4cc62c18..dc0a4ba6cb 100644 --- a/mobile/lib/widgets/search/search_row_title.dart +++ b/mobile/lib/widgets/search/search_row_title.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; class SearchRowTitle extends StatelessWidget { const SearchRowTitle({super.key, required this.onViewAllPressed, required this.title}); @@ -17,9 +17,9 @@ class SearchRowTitle extends StatelessWidget { TextButton( onPressed: onViewAllPressed, child: Text( - context.t.search_page_view_all_button, + 'search_page_view_all_button', style: context.textTheme.labelLarge?.copyWith(color: context.primaryColor), - ), + ).tr(), ), ], ); diff --git a/mobile/lib/widgets/search/thumbnail_with_info.dart b/mobile/lib/widgets/search/thumbnail_with_info.dart index d25a324860..7ba8257c8a 100644 --- a/mobile/lib/widgets/search/thumbnail_with_info.dart +++ b/mobile/lib/widgets/search/thumbnail_with_info.dart @@ -22,7 +22,7 @@ class ThumbnailWithInfo extends StatelessWidget { @override Widget build(BuildContext context) { - final textAndIconColor = context.isDarkTheme ? Colors.grey[100] : Colors.grey[700]; + var textAndIconColor = context.isDarkTheme ? Colors.grey[100] : Colors.grey[700]; return ThumbnailWithInfoContainer( onTap: onTap, borderRadius: borderRadius, diff --git a/mobile/lib/widgets/settings/advanced_settings.dart b/mobile/lib/widgets/settings/advanced_settings.dart index 44a992345f..d5905a246c 100644 --- a/mobile/lib/widgets/settings/advanced_settings.dart +++ b/mobile/lib/widgets/settings/advanced_settings.dart @@ -1,21 +1,22 @@ -import 'dart:async'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart'; import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart'; +import 'package:immich_mobile/widgets/settings/local_storage_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_action_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -32,38 +33,34 @@ class AdvancedSettings extends HookConsumerWidget { final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid); final isManageMediaSupported = useState(false); final manageMediaAndroidPermission = useState(false); - final levelId = useState(ref.watch(appConfigProvider).logLevel.index); - final preferRemote = useState(ref.watch(appConfigProvider).image.preferRemote); - useValueChanged( - preferRemote.value, - (_, _) => unawaited(ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value)), - ); + final levelId = useAppSettingsState(AppSettingsEnum.logLevel); + final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage); + final useAlternatePMFilter = useAppSettingsState(AppSettingsEnum.photoManagerCustomFilter); final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled); final logLevel = Level.LEVELS[levelId.value].name; - useValueChanged( - levelId.value, - (_, _) => unawaited(LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel())), - ); + useValueChanged(levelId.value, (_, __) => LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel())); Future checkAndroidVersion() async { if (Platform.isAndroid) { - final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); - final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; - final int sdkVersion = androidInfo.version.sdkInt; + DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); + AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; + int sdkVersion = androidInfo.version.sdkInt; return sdkVersion >= 31; } return false; } useEffect(() { - unawaited(() async { + () async { isManageMediaSupported.value = await checkAndroidVersion(); - if (isManageMediaSupported.value && context.mounted) { - manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission(); + if (isManageMediaSupported.value) { + manageMediaAndroidPermission.value = await ref + .read(localFilesManagerRepositoryProvider) + .hasManageMediaPermission(); } - }()); + }(); return null; }, []); @@ -71,8 +68,8 @@ class AdvancedSettings extends HookConsumerWidget { SettingsSwitchListTile( enabled: true, valueNotifier: advancedTroubleshooting, - title: context.t.advanced_settings_troubleshooting_title, - subtitle: context.t.advanced_settings_troubleshooting_subtitle, + title: "advanced_settings_troubleshooting_title".tr(), + subtitle: "advanced_settings_troubleshooting_subtitle".tr(), ), if (isManageMediaSupported.value) Column( @@ -80,32 +77,32 @@ class AdvancedSettings extends HookConsumerWidget { SettingsSwitchListTile( enabled: true, valueNotifier: manageLocalMediaAndroid, - title: context.t.advanced_settings_sync_remote_deletions_title, - subtitle: context.t.advanced_settings_sync_remote_deletions_subtitle, + title: "advanced_settings_sync_remote_deletions_title".tr(), + subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(), onChanged: (value) async { if (value) { - final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission(); + final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission(); manageLocalMediaAndroid.value = result; manageMediaAndroidPermission.value = result; } }, ), SettingsActionTile( - title: context.t.manage_media_access_title, - statusText: manageMediaAndroidPermission.value ? context.t.allowed : context.t.not_allowed, - subtitle: context.t.manage_media_access_rationale, + title: "manage_media_access_title".tr(), + statusText: manageMediaAndroidPermission.value ? "allowed".tr() : "not_allowed".tr(), + subtitle: "manage_media_access_rationale".tr(), statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value ? const Color.fromARGB(255, 243, 188, 106) : null, onActionTap: () async { - final result = await ref.read(permissionRepositoryProvider).manageMediaPermission(); + final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission(); manageMediaAndroidPermission.value = result; }, ), ], ), SettingsSliderListTile( - text: context.t.advanced_settings_log_level_title(level: logLevel), + text: "advanced_settings_log_level_title".tr(namedArgs: {'level': logLevel}), valueNotifier: levelId, maxValue: 8, minValue: 1, @@ -114,46 +111,51 @@ class AdvancedSettings extends HookConsumerWidget { ), SettingsSwitchListTile( valueNotifier: preferRemote, - title: context.t.advanced_settings_prefer_remote_title, - subtitle: context.t.advanced_settings_prefer_remote_subtitle, + title: "advanced_settings_prefer_remote_title".tr(), + subtitle: "advanced_settings_prefer_remote_subtitle".tr(), ), + if (!Store.isBetaTimelineEnabled) const LocalStorageSettings(), const CustomProxyHeaderSettings(), const SslClientCertSettings(), - SettingsSwitchListTile( - valueNotifier: readonlyModeEnabled, - title: context.t.advanced_settings_readonly_mode_title, - subtitle: context.t.advanced_settings_readonly_mode_subtitle, - onChanged: (value) { - readonlyModeEnabled.value = value; - ref.read(readonlyModeProvider.notifier).setReadonlyMode(value); - context.scaffoldMessenger.showSnackBar( - SnackBar( - duration: const Duration(seconds: 2), - content: Text( - value ? context.t.readonly_mode_enabled : context.t.readonly_mode_disabled, - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + if (!Store.isBetaTimelineEnabled) + SettingsSwitchListTile( + valueNotifier: useAlternatePMFilter, + title: "advanced_settings_enable_alternate_media_filter_title".tr(), + subtitle: "advanced_settings_enable_alternate_media_filter_subtitle".tr(), + ), + if (!Store.isBetaTimelineEnabled) const BetaTimelineListTile(), + if (Store.isBetaTimelineEnabled) + SettingsSwitchListTile( + valueNotifier: readonlyModeEnabled, + title: "advanced_settings_readonly_mode_title".tr(), + subtitle: "advanced_settings_readonly_mode_subtitle".tr(), + onChanged: (value) { + readonlyModeEnabled.value = value; + ref.read(readonlyModeProvider.notifier).setReadonlyMode(value); + context.scaffoldMessenger.showSnackBar( + SnackBar( + duration: const Duration(seconds: 2), + content: Text( + (value ? "readonly_mode_enabled" : "readonly_mode_disabled").tr(), + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ), ), - ), - ); - }, - ), + ); + }, + ), ListTile( - title: Text(context.t.advanced_settings_clear_image_cache, style: const TextStyle(fontWeight: FontWeight.w500)), + title: Text("advanced_settings_clear_image_cache".tr(), style: const TextStyle(fontWeight: FontWeight.w500)), leading: const Icon(Icons.playlist_remove_rounded), onTap: () async { final int clearedBytes; try { clearedBytes = await remoteImageApi.clearCache(); } catch (e) { - if (!context.mounted) { - return; - } - context.scaffoldMessenger.showSnackBar( SnackBar( duration: const Duration(seconds: 2), content: Text( - context.t.advanced_settings_clear_image_cache_error, + "advanced_settings_clear_image_cache_error".tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.themeData.colorScheme.error), ), ), @@ -161,7 +163,7 @@ class AdvancedSettings extends HookConsumerWidget { return; } - if (clearedBytes < 0 || !context.mounted) { + if (clearedBytes < 0) { return; } @@ -171,7 +173,7 @@ class AdvancedSettings extends HookConsumerWidget { SnackBar( duration: const Duration(seconds: 2), content: Text( - context.t.advanced_settings_clear_image_cache_success(size: clearedMB), + "advanced_settings_clear_image_cache_success".tr(namedArgs: {'size': clearedMB}), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), ), ), diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart index a68ad0fe5e..08e66df48d 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_group_settings.dart @@ -1,13 +1,12 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; @@ -16,34 +15,44 @@ class GroupSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy))); + final groupByIndex = useAppSettingsState(AppSettingsEnum.groupAssetsBy); + final groupBy = GroupAssetsBy.values[groupByIndex.value]; Future updateAppSettings(GroupAssetsBy groupBy) async { - await ref.read(settingsProvider).write(.timelineGroupAssetsBy, groupBy); + await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index); ref.invalidate(appSettingsServiceProvider); - ref.invalidate(timelineServiceProvider); } void changeGroupValue(GroupAssetsBy? value) { if (value != null) { - groupBy.value = value; - unawaited(updateAppSettings(value)); + groupByIndex.value = value.index; + unawaited(updateAppSettings(groupBy)); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.asset_list_group_by_sub_title, icon: Icons.group_work_outlined), + SettingGroupTitle( + title: "asset_list_group_by_sub_title".t(context: context), + icon: Icons.group_work_outlined, + ), SettingsRadioListTile( groups: [ SettingsRadioGroup( - title: context.t.asset_list_layout_settings_group_by_month_day, + title: 'asset_list_layout_settings_group_by_month_day'.t(context: context), value: GroupAssetsBy.day, ), - SettingsRadioGroup(title: context.t.month, value: GroupAssetsBy.month), + SettingsRadioGroup( + title: 'month'.t(context: context), + value: GroupAssetsBy.month, + ), + SettingsRadioGroup( + title: 'asset_list_layout_settings_group_automatically'.t(context: context), + value: GroupAssetsBy.auto, + ), ], - groupBy: groupBy.value, + groupBy: groupBy, onRadioChanged: changeGroupValue, ), ], diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart index 651cc43656..2d5c9f06eb 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_layout_settings.dart @@ -1,38 +1,44 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; class LayoutSettings extends HookConsumerWidget { const LayoutSettings({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final tilesPerRow = useState(ref.watch(appConfigProvider.select((s) => s.timeline.tilesPerRow))); - useValueChanged(tilesPerRow.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.timelineTilesPerRow, tilesPerRow.value)); - }); + final useDynamicLayout = useAppSettingsState(AppSettingsEnum.dynamicLayout); + final tilesPerRow = useAppSettingsState(AppSettingsEnum.tilesPerRow); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.asset_list_layout_sub_title, icon: Icons.view_module_outlined), + SettingGroupTitle( + title: "asset_list_layout_sub_title".t(context: context), + icon: Icons.view_module_outlined, + ), + if (!Store.isBetaTimelineEnabled) + SettingsSwitchListTile( + valueNotifier: useDynamicLayout, + title: "asset_list_layout_settings_dynamic_layout_title".t(context: context), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), + ), SettingsSliderListTile( valueNotifier: tilesPerRow, - text: context.t.theme_setting_asset_list_tiles_per_row_title(count: tilesPerRow.value), + text: 'theme_setting_asset_list_tiles_per_row_title'.tr(namedArgs: {'count': "${tilesPerRow.value}"}), label: "${tilesPerRow.value}", maxValue: 6, minValue: 2, noDivisons: 4, - onChangeEnd: (value) { - ref.invalidate(appSettingsServiceProvider); - }, + onChangeEnd: (_) => ref.invalidate(appSettingsServiceProvider), ), ], ); diff --git a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart index 08fe897c5f..82394bdc07 100644 --- a/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart +++ b/mobile/lib/widgets/settings/asset_list_settings/asset_list_settings.dart @@ -1,11 +1,10 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/setting.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -16,14 +15,13 @@ class AssetListSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator))); + final showStorageIndicator = useAppSettingsState(AppSettingsEnum.storageIndicator); final assetListSetting = [ SettingsSwitchListTile( - valueNotifier: storageIndicator, - title: context.t.theme_setting_asset_list_storage_indicator_title, - onChanged: (value) { - unawaited(ref.read(settingsProvider).write(.timelineStorageIndicator, value)); + valueNotifier: showStorageIndicator, + title: 'theme_setting_asset_list_storage_indicator_title'.tr(), + onChanged: (_) { ref.invalidate(appSettingsServiceProvider); ref.invalidate(settingsProvider); }, diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart index 25829327e0..a2bca2745f 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart'; -import 'package:immich_mobile/widgets/settings/asset_viewer_settings/slideshow_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_viewer_settings/video_viewer_settings.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -14,7 +13,6 @@ class AssetViewerSettings extends StatelessWidget { const ImageViewerQualitySetting(), const ImageViewerTapToNavigateSetting(), const VideoViewerSettings(), - const SlideshowSettings(), ]; return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true); diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart index 5b5917addc..e437b82dd4 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_quality_setting.dart @@ -1,36 +1,38 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ImageViewerQualitySetting extends HookConsumerWidget { const ImageViewerQualitySetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final isOriginal = useState(ref.watch(appConfigProvider).image.loadOriginal); - useValueChanged(isOriginal.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.imageLoadOriginal, isOriginal.value)); - }); + final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview); + final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SettingGroupTitle( - title: context.t.photos, + title: "photos".t(context: context), icon: Icons.image_outlined, - subtitle: context.t.setting_image_viewer_help, + subtitle: "setting_image_viewer_help".t(context: context), + ), + SettingsSwitchListTile( + valueNotifier: isPreview, + title: "setting_image_viewer_preview_title".t(context: context), + subtitle: "setting_image_viewer_preview_subtitle".t(context: context), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), SettingsSwitchListTile( valueNotifier: isOriginal, - title: context.t.setting_image_viewer_original_title, - subtitle: context.t.setting_image_viewer_original_subtitle, + title: "setting_image_viewer_original_title".t(context: context), + subtitle: "setting_image_viewer_original_subtitle".t(context: context), onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), ], diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart index f8dc6b0e1b..759162cab8 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/image_viewer_tap_to_navigate_setting.dart @@ -1,31 +1,28 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ImageViewerTapToNavigateSetting extends HookConsumerWidget { const ImageViewerTapToNavigateSetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final tapToNavigate = useState(ref.watch(appConfigProvider).viewer.tapToNavigate); - useValueChanged(tapToNavigate.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.viewerTapToNavigate, tapToNavigate.value)); - }); + final tapToNavigate = useAppSettingsState(AppSettingsEnum.tapToNavigate); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingsSubTitle(title: context.t.setting_image_navigation_title), + SettingsSubTitle(title: "setting_image_navigation_title".tr()), SettingsSwitchListTile( valueNotifier: tapToNavigate, - title: context.t.setting_image_navigation_enable_title, - subtitle: context.t.setting_image_navigation_enable_subtitle, + title: "setting_image_navigation_enable_title".tr(), + subtitle: "setting_image_navigation_enable_subtitle".tr(), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), ], ); diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart deleted file mode 100644 index 72d1685d33..0000000000 --- a/mobile/lib/widgets/settings/asset_viewer_settings/slideshow_settings.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; -import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; -import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; -import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; -import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; - -class SlideshowSettings extends HookConsumerWidget { - const SlideshowSettings({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final slideshow = ref.watch(appConfigProvider).slideshow; - final useRepeat = useState(slideshow.repeat); - final useDuration = useState(slideshow.duration); - final useLook = useState(slideshow.look); - final useDirection = useState(slideshow.direction); - - useValueChanged(useRepeat.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.slideshowRepeat, useRepeat.value)); - }); - useValueChanged(useDuration.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.slideshowDuration, useDuration.value)); - }); - useValueChanged(useLook.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.slideshowLook, useLook.value)); - }); - useValueChanged(useDirection.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.slideshowDirection, useDirection.value)); - }); - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SettingGroupTitle(title: context.t.slideshow, icon: Icons.slideshow_outlined), - SettingsSwitchListTile( - valueNotifier: useRepeat, - title: context.t.slideshow_repeat, - subtitle: context.t.slideshow_repeat_description, - ), - SettingsSliderListTile( - valueNotifier: useDuration, - text: context.t.duration, - minValue: 5, - noDivisons: 5, - maxValue: 30, - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: SettingsSubTitle(title: context.t.look), - ), - SettingsRadioListTile( - groups: [ - SettingsRadioGroup(title: context.t.contain, value: SlideshowLook.contain), - SettingsRadioGroup(title: context.t.cover, value: SlideshowLook.cover), - SettingsRadioGroup(title: context.t.blurred_background, value: SlideshowLook.blurredBackground), - ], - groupBy: useLook.value, - onRadioChanged: (value) { - if (value != null) { - useLook.value = value; - } - }, - ), - Padding( - padding: const EdgeInsets.only(top: 20), - child: SettingsSubTitle(title: context.t.direction), - ), - Padding( - padding: const EdgeInsets.only(bottom: 32), - child: SettingsRadioListTile( - groups: [ - SettingsRadioGroup(title: context.t.forward, value: SlideshowDirection.forward), - SettingsRadioGroup(title: context.t.backward, value: SlideshowDirection.backward), - SettingsRadioGroup(title: context.t.shuffle, value: SlideshowDirection.shuffle), - ], - groupBy: useDirection.value, - onRadioChanged: (value) { - if (value != null) { - useDirection.value = value; - } - }, - ), - ), - ], - ); - } -} diff --git a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart index bb14d4b327..c03dcc51b4 100644 --- a/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart +++ b/mobile/lib/widgets/settings/asset_viewer_settings/video_viewer_settings.dart @@ -1,51 +1,45 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class VideoViewerSettings extends HookConsumerWidget { const VideoViewerSettings({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final viewer = ref.watch(appConfigProvider).viewer; - final useAutoPlayVideo = useState(viewer.autoPlayVideo); - final useLoopVideo = useState(viewer.loopVideo); - final useOriginalVideo = useState(viewer.loadOriginalVideo); - - useValueChanged(useAutoPlayVideo.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value)); - }); - useValueChanged(useLoopVideo.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.viewerLoopVideo, useLoopVideo.value)); - }); - useValueChanged(useOriginalVideo.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.viewerLoadOriginalVideo, useOriginalVideo.value)); - }); + final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo); + final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo); + final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.videos, icon: Icons.video_camera_back_outlined), + SettingGroupTitle( + title: "videos".t(context: context), + icon: Icons.video_camera_back_outlined, + ), SettingsSwitchListTile( valueNotifier: useAutoPlayVideo, - title: context.t.setting_video_viewer_auto_play_title, - subtitle: context.t.setting_video_viewer_auto_play_subtitle, + title: "setting_video_viewer_auto_play_title".t(context: context), + subtitle: "setting_video_viewer_auto_play_subtitle".t(context: context), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), SettingsSwitchListTile( valueNotifier: useLoopVideo, - title: context.t.setting_video_viewer_looping_title, - subtitle: context.t.loop_videos_description, + title: "setting_video_viewer_looping_title".t(context: context), + subtitle: "loop_videos_description".t(context: context), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), SettingsSwitchListTile( valueNotifier: useOriginalVideo, - title: context.t.setting_video_viewer_original_video_title, - subtitle: context.t.setting_video_viewer_original_video_subtitle, + title: "setting_video_viewer_original_video_title".t(context: context), + subtitle: "setting_video_viewer_original_video_subtitle".t(context: context), + onChanged: (_) => ref.invalidate(appSettingsServiceProvider), ), ], ); diff --git a/mobile/lib/widgets/settings/backup_settings/background_settings.dart b/mobile/lib/widgets/settings/backup_settings/background_settings.dart new file mode 100644 index 0000000000..038a567dc2 --- /dev/null +++ b/mobile/lib/widgets/settings/backup_settings/background_settings.dart @@ -0,0 +1,204 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/providers/backup/backup.provider.dart'; +import 'package:immich_mobile/providers/backup/ios_background_settings.provider.dart'; +import 'package:immich_mobile/widgets/backup/ios_debug_info_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class BackgroundBackupSettings extends ConsumerWidget { + const BackgroundBackupSettings({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isBackgroundEnabled = ref.watch(backupProvider.select((s) => s.backgroundBackup)); + final iosSettings = ref.watch(iOSBackgroundSettingsProvider); + + void showErrorToUser(String msg) { + final snackBar = SnackBar( + content: Text(msg.tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor)), + backgroundColor: Colors.red, + ); + context.scaffoldMessenger.showSnackBar(snackBar); + } + + void showBatteryOptimizationInfoToUser() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext ctx) { + return AlertDialog( + title: const Text('backup_controller_page_background_battery_info_title').tr(), + content: SingleChildScrollView( + child: const Text('backup_controller_page_background_battery_info_message').tr(), + ), + actions: [ + ElevatedButton( + onPressed: () => + launchUrl(Uri.parse('https://dontkillmyapp.com'), mode: LaunchMode.externalApplication), + child: const Text( + "backup_controller_page_background_battery_info_link", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), + ).tr(), + ), + ElevatedButton( + child: const Text( + 'backup_controller_page_background_battery_info_ok', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12), + ).tr(), + onPressed: () => ctx.pop(), + ), + ], + ); + }, + ); + } + + if (!isBackgroundEnabled) { + return SettingsButtonListTile( + icon: Icons.cloud_sync_outlined, + title: 'backup_controller_page_background_is_off'.tr(), + subtileText: 'backup_controller_page_background_description'.tr(), + buttonText: 'backup_controller_page_background_turn_on'.tr(), + onButtonTap: () => ref + .read(backupProvider.notifier) + .configureBackgroundBackup( + enabled: true, + onError: showErrorToUser, + onBatteryInfo: showBatteryOptimizationInfoToUser, + ), + ); + } + + return Column( + children: [ + if (!Platform.isIOS || iosSettings?.appRefreshEnabled == true) + _BackgroundSettingsEnabled(onError: showErrorToUser, onBatteryInfo: showBatteryOptimizationInfoToUser), + if (Platform.isIOS && iosSettings?.appRefreshEnabled != true) const _IOSBackgroundRefreshDisabled(), + if (Platform.isIOS && iosSettings != null) IosDebugInfoTile(settings: iosSettings), + ], + ); + } +} + +class _IOSBackgroundRefreshDisabled extends StatelessWidget { + const _IOSBackgroundRefreshDisabled(); + + @override + Widget build(BuildContext context) { + return SettingsButtonListTile( + icon: Icons.task_outlined, + title: 'backup_controller_page_background_app_refresh_disabled_title'.tr(), + subtileText: 'backup_controller_page_background_app_refresh_disabled_content'.tr(), + buttonText: 'backup_controller_page_background_app_refresh_enable_button_text'.tr(), + onButtonTap: () => openAppSettings(), + ); + } +} + +class _BackgroundSettingsEnabled extends HookConsumerWidget { + final void Function(String msg) onError; + final void Function() onBatteryInfo; + + const _BackgroundSettingsEnabled({required this.onError, required this.onBatteryInfo}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isWifiRequired = ref.watch(backupProvider.select((s) => s.backupRequireWifi)); + final isWifiRequiredNotifier = useValueNotifier(isWifiRequired); + useValueChanged( + isWifiRequired, + (_, __) => WidgetsBinding.instance.addPostFrameCallback((_) => isWifiRequiredNotifier.value = isWifiRequired), + ); + + final isChargingRequired = ref.watch(backupProvider.select((s) => s.backupRequireCharging)); + final isChargingRequiredNotifier = useValueNotifier(isChargingRequired); + useValueChanged( + isChargingRequired, + (_, __) => + WidgetsBinding.instance.addPostFrameCallback((_) => isChargingRequiredNotifier.value = isChargingRequired), + ); + + int backupDelayToSliderValue(int ms) => switch (ms) { + 5000 => 0, + 30000 => 1, + 120000 => 2, + _ => 3, + }; + + int backupDelayToMilliseconds(int v) => switch (v) { + 0 => 5000, + 1 => 30000, + 2 => 120000, + _ => 600000, + }; + + String formatBackupDelaySliderValue(int v) => switch (v) { + 0 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '5'}), + 1 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '30'}), + 2 => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '2'}), + _ => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '10'}), + }; + + final backupTriggerDelay = ref.watch(backupProvider.select((s) => s.backupTriggerDelay)); + final triggerDelay = useState(backupDelayToSliderValue(backupTriggerDelay)); + useValueChanged( + triggerDelay.value, + (_, __) => ref + .read(backupProvider.notifier) + .configureBackgroundBackup( + triggerDelay: backupDelayToMilliseconds(triggerDelay.value), + onError: onError, + onBatteryInfo: onBatteryInfo, + ), + ); + + return SettingsButtonListTile( + icon: Icons.cloud_sync_rounded, + iconColor: context.primaryColor, + title: 'backup_controller_page_background_is_on'.tr(), + buttonText: 'backup_controller_page_background_turn_off'.tr(), + onButtonTap: () => ref + .read(backupProvider.notifier) + .configureBackgroundBackup(enabled: false, onError: onError, onBatteryInfo: onBatteryInfo), + subtitle: Column( + children: [ + SettingsSwitchListTile( + valueNotifier: isWifiRequiredNotifier, + title: 'backup_controller_page_background_wifi'.tr(), + icon: Icons.wifi, + onChanged: (enabled) => ref + .read(backupProvider.notifier) + .configureBackgroundBackup(requireWifi: enabled, onError: onError, onBatteryInfo: onBatteryInfo), + ), + SettingsSwitchListTile( + valueNotifier: isChargingRequiredNotifier, + title: 'backup_controller_page_background_charging'.tr(), + icon: Icons.charging_station, + onChanged: (enabled) => ref + .read(backupProvider.notifier) + .configureBackgroundBackup(requireCharging: enabled, onError: onError, onBatteryInfo: onBatteryInfo), + ), + if (Platform.isAndroid) + SettingsSliderListTile( + valueNotifier: triggerDelay, + text: 'backup_controller_page_background_delay'.tr( + namedArgs: {'duration': formatBackupDelaySliderValue(triggerDelay.value)}, + ), + maxValue: 3.0, + noDivisons: 3, + label: formatBackupDelaySliderValue(triggerDelay.value), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/widgets/settings/backup_settings/backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/backup_settings.dart new file mode 100644 index 0000000000..50aa57da9f --- /dev/null +++ b/mobile/lib/widgets/settings/backup_settings/backup_settings.dart @@ -0,0 +1,82 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/providers/backup/backup_verification.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:immich_mobile/widgets/settings/backup_settings/background_settings.dart'; +import 'package:immich_mobile/widgets/settings/backup_settings/foreground_settings.dart'; +import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; + +class BackupSettings extends HookConsumerWidget { + const BackupSettings({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final ignoreIcloudAssets = useAppSettingsState(AppSettingsEnum.ignoreIcloudAssets); + final isAdvancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting); + final albumSync = useAppSettingsState(AppSettingsEnum.syncAlbums); + final isCorruptCheckInProgress = ref.watch(backupVerificationProvider); + final isAlbumSyncInProgress = useState(false); + + syncAlbums() async { + isAlbumSyncInProgress.value = true; + try { + await ref.read(assetServiceProvider).syncUploadedAssetToAlbums(); + } catch (_) { + } finally { + Future.delayed(const Duration(seconds: 1), () { + isAlbumSyncInProgress.value = false; + }); + } + } + + final backupSettings = [ + const ForegroundBackupSettings(), + const BackgroundBackupSettings(), + if (Platform.isIOS) + SettingsSwitchListTile( + valueNotifier: ignoreIcloudAssets, + title: 'ignore_icloud_photos'.tr(), + subtitle: 'ignore_icloud_photos_description'.tr(), + ), + if (Platform.isAndroid && isAdvancedTroubleshooting.value) + SettingsButtonListTile( + icon: Icons.warning_rounded, + title: 'check_corrupt_asset_backup'.tr(), + subtitle: isCorruptCheckInProgress + ? const Column( + children: [ + SizedBox(height: 20), + Center(child: CircularProgressIndicator()), + SizedBox(height: 20), + ], + ) + : null, + subtileText: !isCorruptCheckInProgress ? 'check_corrupt_asset_backup_description'.tr() : null, + buttonText: 'check_corrupt_asset_backup_button'.tr(), + onButtonTap: !isCorruptCheckInProgress + ? () => ref.read(backupVerificationProvider.notifier).performBackupCheck(context) + : null, + ), + if (albumSync.value) + SettingsButtonListTile( + icon: Icons.photo_album_outlined, + title: 'sync_albums'.tr(), + subtitle: Text("sync_albums_manual_subtitle".tr()), + buttonText: 'sync_albums'.tr(), + child: isAlbumSyncInProgress.value + ? const CircularProgressIndicator() + : ElevatedButton(onPressed: syncAlbums, child: Text('sync'.tr())), + ), + ]; + + return SettingsSubPageScaffold(settings: backupSettings, showDivider: true); + } +} diff --git a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart index ae9ff87a91..2c179c42ea 100644 --- a/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart +++ b/mobile/lib/widgets/settings/backup_settings/drift_backup_settings.dart @@ -1,41 +1,52 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/setting_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; -class DriftBackupSettings extends StatelessWidget { +class DriftBackupSettings extends ConsumerWidget { const DriftBackupSettings({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return SettingsSubPageScaffold( settings: [ - SettingGroupTitle(title: context.t.network_requirements, icon: Icons.cell_tower), - const _UseCellularForVideosButton(), - const _UseCellularForPhotosButton(), + SettingGroupTitle( + title: "network_requirements".t(context: context), + icon: Icons.cell_tower, + ), + const _UseWifiForUploadVideosButton(), + const _UseWifiForUploadPhotosButton(), if (CurrentPlatform.isAndroid) ...[ const Divider(), - SettingGroupTitle(title: context.t.background_options, icon: Icons.charging_station_rounded), + SettingGroupTitle( + title: "background_options".t(context: context), + icon: Icons.charging_station_rounded, + ), const _BackupOnlyWhenChargingButton(), const _BackupDelaySlider(), ], const Divider(), - SettingGroupTitle(title: context.t.backup_albums_sync, icon: Icons.sync), + SettingGroupTitle( + title: "backup_albums_sync".t(context: context), + icon: Icons.sync, + ), const _AlbumSyncActionButton(), ], ); @@ -58,15 +69,11 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> }); try { - final backgroundSync = ref.read(backgroundSyncProvider); - await backgroundSync.syncLinkedAlbum(); - await backgroundSync.syncRemote(); + await ref.read(backgroundSyncProvider).syncLinkedAlbum(); + await ref.read(backgroundSyncProvider).syncRemote(); } catch (_) { } finally { Future.delayed(const Duration(seconds: 1), () { - if (!mounted) { - return; - } setState(() { isAlbumSyncInProgress = false; }); @@ -89,58 +96,64 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> @override Widget build(BuildContext context) { - final albumSyncEnable = ref.watch(appConfigProvider.select((c) => c.backup.syncAlbums)); return Padding( padding: const EdgeInsets.only(left: 8.0), child: ListView( shrinkWrap: true, children: [ - Column( - children: [ - SettingListTile( - title: context.t.sync_albums, - subtitle: context.t.sync_upload_album_setting_subtitle, - trailing: Switch( - value: albumSyncEnable, - onChanged: (bool newValue) async { - await ref.read(settingsProvider).write(.backupSyncAlbums, newValue); + StreamBuilder( + stream: Store.watch(StoreKey.syncAlbums), + initialData: Store.tryGet(StoreKey.syncAlbums) ?? false, + builder: (context, snapshot) { + final albumSyncEnable = snapshot.data ?? false; + return Column( + children: [ + SettingListTile( + title: "sync_albums".t(context: context), + subtitle: "sync_upload_album_setting_subtitle".t(context: context), + trailing: Switch( + value: albumSyncEnable, + onChanged: (bool newValue) async { + await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue); - if (newValue == true) { - await _manageLinkedAlbums(); - } - }, - ), - ), - AnimatedSize( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: albumSyncEnable ? 1.0 : 0.0, - child: albumSyncEnable - ? SettingListTile( - onTap: _manualSyncAlbums, - contentPadding: const EdgeInsets.only(left: 32, right: 16), - title: context.t.organize_into_albums, - subtitle: context.t.organize_into_albums_description, - trailing: isAlbumSyncInProgress - ? const SizedBox( - width: 32, - height: 32, - child: CircularProgressIndicator.adaptive(strokeWidth: 2), - ) - : IconButton( - onPressed: _manualSyncAlbums, - icon: const Icon(Icons.sync_rounded), - color: context.colorScheme.onSurface.withValues(alpha: 0.7), - iconSize: 20, - constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - ), - ) - : const SizedBox.shrink(), - ), - ), - ], + if (newValue == true) { + await _manageLinkedAlbums(); + } + }, + ), + ), + AnimatedSize( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: albumSyncEnable ? 1.0 : 0.0, + child: albumSyncEnable + ? SettingListTile( + onTap: _manualSyncAlbums, + contentPadding: const EdgeInsets.only(left: 32, right: 16), + title: "organize_into_albums".t(context: context), + subtitle: "organize_into_albums_description".t(context: context), + trailing: isAlbumSyncInProgress + ? const SizedBox( + width: 32, + height: 32, + child: CircularProgressIndicator.adaptive(strokeWidth: 2), + ) + : IconButton( + onPressed: _manualSyncAlbums, + icon: const Icon(Icons.sync_rounded), + color: context.colorScheme.onSurface.withValues(alpha: 0.7), + iconSize: 20, + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + ), + ) + : const SizedBox.shrink(), + ), + ), + ], + ); + }, ), ], ), @@ -148,34 +161,60 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton> } } -class _BackupSwitchTile extends ConsumerWidget { - final SettingsKey metadataKey; - final bool Function(AppConfig) selector; - final String title; - final String subtitle; - final void Function(bool)? onChanged; +class _SettingsSwitchTile extends ConsumerStatefulWidget { + final AppSettingsEnum appSettingsEnum; + final String titleKey; + final String subtitleKey; + final void Function(bool?)? onChanged; - const _BackupSwitchTile({ - required this.metadataKey, - required this.selector, - required this.title, - required this.subtitle, + const _SettingsSwitchTile({ + required this.appSettingsEnum, + required this.titleKey, + required this.subtitleKey, this.onChanged, }); @override - Widget build(BuildContext context, WidgetRef ref) { - final value = ref.watch(appConfigProvider.select(selector)); + ConsumerState createState() => _SettingsSwitchTileState(); +} + +class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> { + late final Stream valueStream; + late final StreamSubscription subscription; + + @override + void initState() { + super.initState(); + valueStream = Store.watch(widget.appSettingsEnum.storeKey).asBroadcastStream(); + subscription = valueStream.listen((value) { + widget.onChanged?.call(value); + }); + } + + @override + void dispose() { + subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 8.0), child: SettingListTile( - title: title, - subtitle: subtitle, - trailing: Switch( - value: value, - onChanged: (bool newValue) async { - await ref.read(settingsProvider).write(metadataKey, newValue); - onChanged?.call(newValue); + title: widget.titleKey.t(context: context), + subtitle: widget.subtitleKey.t(context: context), + trailing: StreamBuilder( + stream: valueStream, + initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue, + builder: (context, snapshot) { + final value = snapshot.data ?? false; + return Switch( + value: value, + onChanged: (bool newValue) async { + await ref.read(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue); + }, + ); }, ), ), @@ -183,30 +222,28 @@ class _BackupSwitchTile extends ConsumerWidget { } } -class _UseCellularForVideosButton extends StatelessWidget { - const _UseCellularForVideosButton(); +class _UseWifiForUploadVideosButton extends ConsumerWidget { + const _UseWifiForUploadVideosButton(); @override - Widget build(BuildContext context) { - return _BackupSwitchTile( - metadataKey: SettingsKey.backupUseCellularForVideos, - selector: (c) => c.backup.useCellularForVideos, - title: context.t.videos, - subtitle: context.t.network_requirement_videos_upload, + Widget build(BuildContext context, WidgetRef ref) { + return const _SettingsSwitchTile( + appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos, + titleKey: "videos", + subtitleKey: "network_requirement_videos_upload", ); } } -class _UseCellularForPhotosButton extends StatelessWidget { - const _UseCellularForPhotosButton(); +class _UseWifiForUploadPhotosButton extends ConsumerWidget { + const _UseWifiForUploadPhotosButton(); @override - Widget build(BuildContext context) { - return _BackupSwitchTile( - metadataKey: SettingsKey.backupUseCellularForPhotos, - selector: (c) => c.backup.useCellularForPhotos, - title: context.t.photos, - subtitle: context.t.network_requirement_photos_upload, + Widget build(BuildContext context, WidgetRef ref) { + return const _SettingsSwitchTile( + appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos, + titleKey: "photos", + subtitleKey: "network_requirement_photos_upload", ); } } @@ -216,22 +253,29 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final fgService = ref.watch(backgroundWorkerFgServiceProvider); - return _BackupSwitchTile( - metadataKey: SettingsKey.backupRequireCharging, - selector: (c) => c.backup.requireCharging, - title: context.t.charging, - subtitle: context.t.charging_requirement_mobile_backup, + return _SettingsSwitchTile( + appSettingsEnum: AppSettingsEnum.backupRequireCharging, + titleKey: "charging", + subtitleKey: "charging_requirement_mobile_backup", onChanged: (value) { - unawaited(fgService.configure(requireCharging: value)); + ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false); }, ); } } -class _BackupDelaySlider extends ConsumerWidget { +class _BackupDelaySlider extends ConsumerStatefulWidget { const _BackupDelaySlider(); + @override + ConsumerState<_BackupDelaySlider> createState() => _BackupDelaySliderState(); +} + +class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> { + late final Stream valueStream; + late final StreamSubscription subscription; + late int currentValue; + static int backupDelayToSliderValue(int ms) => switch (ms) { 5 => 0, 30 => 1, @@ -246,43 +290,65 @@ class _BackupDelaySlider extends ConsumerWidget { _ => 600, }; - static String formatBackupDelaySliderValue(BuildContext context, int v) => switch (v) { - 0 => context.t.setting_notifications_notify_seconds(count: 5), - 1 => context.t.setting_notifications_notify_seconds(count: 30), - 2 => context.t.setting_notifications_notify_minutes(count: 2), - _ => context.t.setting_notifications_notify_minutes(count: 10), + static String formatBackupDelaySliderValue(int v) => switch (v) { + 0 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '5'}), + 1 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '30'}), + 2 => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '2'}), + _ => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '10'}), }; @override - Widget build(BuildContext context, WidgetRef ref) { - final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay)); - final currentValue = backupDelayToSliderValue(triggerDelay); + void initState() { + super.initState(); + final initialValue = + Store.tryGet(AppSettingsEnum.backupTriggerDelay.storeKey) ?? AppSettingsEnum.backupTriggerDelay.defaultValue; + currentValue = backupDelayToSliderValue(initialValue); + + valueStream = Store.watch(AppSettingsEnum.backupTriggerDelay.storeKey).asBroadcastStream(); + subscription = valueStream.listen((value) { + if (mounted && value != null) { + setState(() { + currentValue = backupDelayToSliderValue(value); + }); + } + }); + } + + @override + void dispose() { + subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.only(left: 24.0, top: 8.0), child: Text( - context.t.backup_controller_page_background_delay( - duration: formatBackupDelaySliderValue(context, currentValue), + 'backup_controller_page_background_delay'.tr( + namedArgs: {'duration': formatBackupDelaySliderValue(currentValue)}, ), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), ), ), Slider( value: currentValue.toDouble(), - onChanged: (double v) async { - final seconds = backupDelayToSeconds(v.toInt()); - await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds); + onChanged: (double v) { + setState(() { + currentValue = v.toInt(); + }); }, onChangeEnd: (double v) async { - final seconds = backupDelayToSeconds(v.toInt()); - await ref.read(settingsProvider).write(SettingsKey.backupTriggerDelay, seconds); + final milliseconds = backupDelayToSeconds(v.toInt()); + await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds); }, max: 3.0, min: 0.0, divisions: 3, - label: formatBackupDelaySliderValue(context, currentValue), + label: formatBackupDelaySliderValue(currentValue), ), ], ); diff --git a/mobile/lib/widgets/settings/backup_settings/foreground_settings.dart b/mobile/lib/widgets/settings/backup_settings/foreground_settings.dart new file mode 100644 index 0000000000..a2ff00fe45 --- /dev/null +++ b/mobile/lib/widgets/settings/backup_settings/foreground_settings.dart @@ -0,0 +1,35 @@ +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/backup/backup.provider.dart'; +import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; + +class ForegroundBackupSettings extends ConsumerWidget { + const ForegroundBackupSettings({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isAutoBackup = ref.watch(backupProvider.select((s) => s.autoBackup)); + + void onButtonTap() => ref.read(backupProvider.notifier).setAutoBackup(!isAutoBackup); + + if (isAutoBackup) { + return SettingsButtonListTile( + icon: Icons.cloud_done_rounded, + iconColor: context.primaryColor, + title: 'backup_controller_page_status_on'.tr(), + buttonText: 'backup_controller_page_turn_off'.tr(), + onButtonTap: onButtonTap, + ); + } + + return SettingsButtonListTile( + icon: Icons.cloud_off_rounded, + title: 'backup_controller_page_status_off'.tr(), + subtileText: 'backup_controller_page_desc_backup'.tr(), + buttonText: 'backup_controller_page_turn_on'.tr(), + onButtonTap: onButtonTap, + ); + } +} diff --git a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart index d97be93ac4..92787077a1 100644 --- a/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart +++ b/mobile/lib/widgets/settings/beta_sync_settings/sync_status_and_actions.dart @@ -3,17 +3,16 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/server_capability.model.dart'; -import 'package:immich_mobile/domain/services/local_album.service.dart'; -import 'package:immich_mobile/domain/services/memory.service.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/background_sync.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/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/storage.provider.dart'; import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; @@ -40,13 +39,12 @@ class SyncStatusAndActions extends HookConsumerWidget { final documentsDir = await getApplicationDocumentsDirectory(); final dbFile = File(path.join(documentsDir.path, 'immich.sqlite')); - // ignore: avoid_slow_async_io if (!await dbFile.exists()) { - if (!context.mounted) { - return; + if (context.mounted) { + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Database file not found".t(context: context))), + ); } - - context.scaffoldMessenger.showSnackBar(const SnackBar(content: Text('Database file not found'))); return; } @@ -55,10 +53,6 @@ class SyncStatusAndActions extends HookConsumerWidget { await dbFile.copy(exportFile.path); - if (!context.mounted) { - return; - } - final size = MediaQuery.of(context).size; await Share.shareXFiles( [XFile(exportFile.path)], @@ -67,22 +61,22 @@ class SyncStatusAndActions extends HookConsumerWidget { ); Future.delayed(const Duration(seconds: 30), () async { - // ignore: avoid_slow_async_io if (await exportFile.exists()) { await exportFile.delete(); } }); - if (!context.mounted) { - return; - } - context.scaffoldMessenger.showSnackBar(const SnackBar(content: Text('Database exported successfully'))); + if (context.mounted) { + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Database exported successfully".t(context: context))), + ); + } } catch (e) { - if (!context.mounted) { - return; + if (context.mounted) { + context.scaffoldMessenger.showSnackBar( + SnackBar(content: Text("Failed to export database: $e".t(context: context))), + ); } - - context.scaffoldMessenger.showSnackBar(SnackBar(content: Text('Failed to export database: $e'))); } } @@ -102,10 +96,6 @@ class SyncStatusAndActions extends HookConsumerWidget { TextButton( onPressed: () async { await ref.read(driftProvider).reset(); - if (!context.mounted) { - return; - } - context.pop(); unawaited( showDialog( @@ -133,59 +123,65 @@ class SyncStatusAndActions extends HookConsumerWidget { const _SyncStatsCounts(), const Divider(height: 10), const SizedBox(height: 16), - SettingGroupTitle(title: context.t.jobs), + SettingGroupTitle(title: "jobs".t(context: context)), SettingListTile( - title: context.t.sync_local, - subtitle: context.t.tap_to_run_job, + title: "sync_local".t(context: context), + subtitle: "tap_to_run_job".t(context: context), leading: const Icon(Icons.sync), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus), onTap: () { - unawaited(ref.read(backgroundSyncProvider).syncLocal(full: true)); + ref.read(backgroundSyncProvider).syncLocal(full: true); }, ), SettingListTile( - title: context.t.sync_remote, - subtitle: context.t.tap_to_run_job, + title: "sync_remote".t(context: context), + subtitle: "tap_to_run_job".t(context: context), leading: const Icon(Icons.cloud_sync), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus), onTap: () { - unawaited(ref.read(backgroundSyncProvider).syncRemote()); + ref.read(backgroundSyncProvider).syncRemote(); }, ), - if (CurrentPlatform.isIOS && serverVersion.supports(.cloudIdMetadata)) + if (CurrentPlatform.isIOS && serverVersion.isAtLeast(major: 2, minor: 5)) SettingListTile( - title: 'Sync Cloud Ids', + title: "Sync Cloud Ids".t(context: context), leading: const Icon(Icons.cloud_circle_rounded), - subtitle: context.t.tap_to_run_job, + subtitle: "tap_to_run_job".t(context: context), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).cloudIdSyncStatus), - onTap: ref.watch(backgroundSyncProvider).syncCloudIds, + onTap: ref.read(backgroundSyncProvider).syncCloudIds, ), SettingListTile( - title: context.t.hash_asset, + title: "hash_asset".t(context: context), leading: const Icon(Icons.tag), - subtitle: context.t.tap_to_run_job, + subtitle: "tap_to_run_job".t(context: context), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus), onTap: () { - unawaited(ref.read(backgroundSyncProvider).hashAssets()); + ref.read(backgroundSyncProvider).hashAssets(); }, ), const Divider(height: 1), const SizedBox(height: 16), - SettingGroupTitle(title: context.t.actions), + SettingGroupTitle(title: "actions".t(context: context)), ListTile( - title: Text(context.t.clear_file_cache, style: const TextStyle(fontWeight: FontWeight.w500)), + title: Text( + "clear_file_cache".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), + ), leading: const Icon(Icons.playlist_remove_rounded), onTap: clearFileCache, ), ListTile( - title: Text(context.t.export_database, style: const TextStyle(fontWeight: FontWeight.w500)), - subtitle: Text(context.t.export_database_description), + title: Text( + "export_database".t(context: context), + style: const TextStyle(fontWeight: FontWeight.w500), + ), + subtitle: Text("export_database_description".t(context: context)), leading: const Icon(Icons.download), onTap: exportDatabase, ), ListTile( title: Text( - context.t.reset_sqlite, + "reset_sqlite".t(context: context), style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500), ), leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error), @@ -219,11 +215,10 @@ class _SyncStatsCounts extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final db = ref.watch(driftProvider); final assetService = ref.watch(assetServiceProvider); - final localAlbumService = LocalAlbumService(db.localAlbumRepository); + final localAlbumService = ref.watch(localAlbumServiceProvider); final remoteAlbumService = ref.watch(remoteAlbumServiceProvider); - final memoryService = DriftMemoryService(db.memoryRepository); + final memoryService = ref.watch(driftMemoryServiceProvider); final appSettingsService = ref.watch(appSettingsServiceProvider); Future> loadCounts() async { @@ -272,7 +267,7 @@ class _SyncStatsCounts extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.assets), + SettingGroupTitle(title: "assets".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), // 1. Wrap in IntrinsicHeight @@ -285,16 +280,24 @@ class _SyncStatsCounts extends ConsumerWidget { spacing: 8.0, children: [ Expanded( - child: EntityCountTile(label: context.t.local, count: localAssetCount, icon: Icons.smartphone), + child: EntityCountTile( + label: "local".t(context: context), + count: localAssetCount, + icon: Icons.smartphone, + ), ), Expanded( - child: EntityCountTile(label: context.t.remote, count: remoteAssetCount, icon: Icons.cloud), + child: EntityCountTile( + label: "remote".t(context: context), + count: remoteAssetCount, + icon: Icons.cloud, + ), ), ], ), ), ), - SettingGroupTitle(title: context.t.albums), + SettingGroupTitle(title: "albums".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: IntrinsicHeight( @@ -305,16 +308,24 @@ class _SyncStatsCounts extends ConsumerWidget { spacing: 8.0, children: [ Expanded( - child: EntityCountTile(label: context.t.local, count: localAlbumCount, icon: Icons.smartphone), + child: EntityCountTile( + label: "local".t(context: context), + count: localAlbumCount, + icon: Icons.smartphone, + ), ), Expanded( - child: EntityCountTile(label: context.t.remote, count: remoteAlbumCount, icon: Icons.cloud), + child: EntityCountTile( + label: "remote".t(context: context), + count: remoteAlbumCount, + icon: Icons.cloud, + ), ), ], ), ), ), - SettingGroupTitle(title: context.t.other), + SettingGroupTitle(title: "other".t(context: context)), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), child: IntrinsicHeight( @@ -325,10 +336,18 @@ class _SyncStatsCounts extends ConsumerWidget { spacing: 8.0, children: [ Expanded( - child: EntityCountTile(label: context.t.memories, count: memoryCount, icon: Icons.calendar_today), + child: EntityCountTile( + label: "memories".t(context: context), + count: memoryCount, + icon: Icons.calendar_today, + ), ), Expanded( - child: EntityCountTile(label: context.t.hashed_assets, count: localHashedCount, icon: Icons.tag), + child: EntityCountTile( + label: "hashed_assets".t(context: context), + count: localHashedCount, + icon: Icons.tag, + ), ), ], ), @@ -337,7 +356,7 @@ class _SyncStatsCounts extends ConsumerWidget { // To be removed once the experimental feature is stable if (CurrentPlatform.isAndroid && appSettingsService.getSetting(AppSettingsEnum.manageLocalMediaAndroid)) ...[ - SettingGroupTitle(title: context.t.trash), + SettingGroupTitle(title: "trash".t(context: context)), Consumer( builder: (context, ref, _) { final counts = ref.watch(trashedAssetsCountProvider); @@ -353,13 +372,17 @@ class _SyncStatsCounts extends ConsumerWidget { children: [ Expanded( child: EntityCountTile( - label: context.t.local, + label: "local".t(context: context), count: c.total, icon: Icons.delete_outline, ), ), Expanded( - child: EntityCountTile(label: context.t.hashed_assets, count: c.hashed, icon: Icons.tag), + child: EntityCountTile( + label: "hashed_assets".t(context: context), + count: c.hashed, + icon: Icons.tag, + ), ), ], ), diff --git a/mobile/lib/widgets/settings/beta_timeline_list_tile.dart b/mobile/lib/widgets/settings/beta_timeline_list_tile.dart new file mode 100644 index 0000000000..21e0edb34c --- /dev/null +++ b/mobile/lib/widgets/settings/beta_timeline_list_tile.dart @@ -0,0 +1,71 @@ +import 'dart:async'; + +import 'package:auto_route/auto_route.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/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/providers/auth.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/widgets/settings/setting_list_tile.dart'; + +class BetaTimelineListTile extends ConsumerWidget { + const BetaTimelineListTile({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final betaTimelineValue = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.betaTimeline); + final auth = ref.watch(authProvider); + + if (!auth.isAuthenticated) { + return const SizedBox.shrink(); + } + + void onSwitchChanged(bool value) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: value ? const Text("Enable New Timeline") : const Text("Disable New Timeline"), + content: value + ? const Text("Are you sure you want to enable the new timeline?") + : const Text("Are you sure you want to disable the new timeline?"), + actions: [ + TextButton( + onPressed: () { + context.pop(); + }, + child: Text( + "cancel".t(context: context), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: context.colorScheme.outline), + ), + ), + ElevatedButton( + onPressed: () async { + Navigator.of(context).pop(); + unawaited(context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)])); + }, + child: Text("ok".t(context: context)), + ), + ], + ); + }, + ); + } + + return Padding( + padding: const EdgeInsets.only(left: 4.0), + child: SettingListTile( + title: "new_timeline".t(context: context), + trailing: Switch.adaptive( + value: betaTimelineValue, + onChanged: onSwitchChanged, + activeThumbColor: context.primaryColor, + ), + onTap: () => onSwitchChanged(!betaTimelineValue), + ), + ); + } +} diff --git a/mobile/lib/widgets/settings/free_up_space_settings.dart b/mobile/lib/widgets/settings/free_up_space_settings.dart index 07dfe95b21..ee7ee20b00 100644 --- a/mobile/lib/widgets/settings/free_up_space_settings.dart +++ b/mobile/lib/widgets/settings/free_up_space_settings.dart @@ -1,6 +1,5 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; @@ -8,13 +7,12 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/platform_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/cleanup.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/utils/bytes_units.dart'; -import 'package:intl/intl.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; class FreeUpSpaceSettings extends ConsumerStatefulWidget { @@ -32,18 +30,18 @@ class _FreeUpSpaceSettingsState extends ConsumerState { @override void initState() { super.initState(); - unawaited(WakelockPlus.enable()); + WakelockPlus.enable(); WidgetsBinding.instance.addPostFrameCallback((_) { - unawaited(_initializeAlbumDefaults()); + _initializeAlbumDefaults(); }); } Future _initializeAlbumDefaults() async { - final notifier = ref.read(cleanupProvider.notifier); final albums = await ref.read(localAlbumProvider.future); final existingAlbumIds = albums.map((a) => a.id).toSet(); final albumsWithNames = albums.map((a) => (a.id, a.name)).toList(); + final notifier = ref.read(cleanupProvider.notifier); notifier.applyDefaultAlbumSelections(albumsWithNames); notifier.cleanupStaleAlbumIds(existingAlbumIds); } @@ -70,7 +68,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { void _goToScanStep() { ref.read(hapticFeedbackProvider.notifier).mediumImpact(); setState(() => _currentStep = CleanupStep.scan); - unawaited(_scanAssets()); + _scanAssets(); } void _setPresetDate(int daysAgo) { @@ -82,9 +80,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { bool _isPresetSelected(int? daysAgo) { final state = ref.read(cleanupProvider); - if (state.selectedDate == null) { - return false; - } + if (state.selectedDate == null) return false; final expectedDate = daysAgo != null ? DateTime.now().subtract(Duration(days: daysAgo)) : DateTime(2000); @@ -105,7 +101,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { lastDate: DateTime.now(), ); - if (picked != null && context.mounted) { + if (picked != null) { ref.read(cleanupProvider.notifier).setSelectedDate(picked); setState(() => _hasScanned = false); } @@ -122,10 +118,6 @@ class _FreeUpSpaceSettingsState extends ConsumerState { ref.read(hapticFeedbackProvider.notifier).mediumImpact(); await ref.read(cleanupProvider.notifier).scanAssets(); - if (!mounted) { - return; - } - final state = ref.read(cleanupProvider); setState(() { @@ -150,7 +142,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { _DeleteConfirmationDialog(assetCount: state.assetsToDelete.length, cutoffDate: state.selectedDate!), ); - if (confirmed != true || !context.mounted) { + if (confirmed != true) { return; } @@ -175,13 +167,13 @@ class _FreeUpSpaceSettingsState extends ConsumerState { void _showAssetsPreview(List assets) { ref.read(hapticFeedbackProvider.notifier).mediumImpact(); - unawaited(context.pushRoute(CleanupPreviewRoute(assets: assets))); + context.pushRoute(CleanupPreviewRoute(assets: assets)); } @override - void dispose() { + dispose() { super.dispose(); - unawaited(WakelockPlus.disable()); + WakelockPlus.disable(); } @override @@ -236,21 +228,21 @@ class _FreeUpSpaceSettingsState extends ConsumerState { final parts = []; if (state.keepMediaType == AssetKeepType.photosOnly) { - parts.add(context.t.all_photos); + parts.add('all_photos'.t(context: context)); } else if (state.keepMediaType == AssetKeepType.videosOnly) { - parts.add(context.t.all_videos); + parts.add('all_videos'.t(context: context)); } if (state.keepFavorites) { - parts.add(context.t.favorites); + parts.add('favorites'.t(context: context)); } if (state.keepAlbumIds.isNotEmpty) { - parts.add(context.t.keep_albums_count(count: state.keepAlbumIds.length)); + parts.add('keep_albums_count'.t(context: context, args: {'count': state.keepAlbumIds.length.toString()})); } if (parts.isEmpty) { - return context.t.none; + return 'none'.t(context: context); } return parts.join(', '); @@ -274,7 +266,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { borderRadius: const BorderRadius.all(Radius.circular(12)), border: Border.all(color: context.primaryColor.withValues(alpha: 0.25)), ), - child: Text(context.t.free_up_space_description, style: context.textTheme.bodyMedium), + child: Text('free_up_space_description'.t(context: context), style: context.textTheme.bodyMedium), ), ), @@ -307,7 +299,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { color: hasKeepSettings ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant, ), title: Text( - context.t.keep_on_device, + 'keep_on_device'.t(context: context), style: context.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, color: hasKeepSettings ? context.colorScheme.primary : null, @@ -315,8 +307,8 @@ class _FreeUpSpaceSettingsState extends ConsumerState { ), subtitle: Text( hasKeepSettings - ? context.t.keeping(items: getKeepSettingsSummary()) - : context.t.keep_on_device_hint, + ? 'keeping'.t(context: context, args: {'items': getKeepSettingsSummary()}) + : 'keep_on_device_hint'.t(context: context), style: context.textTheme.bodySmall?.copyWith( color: hasKeepSettings ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant, ), @@ -327,12 +319,12 @@ class _FreeUpSpaceSettingsState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text(context.t.keep_description, style: subtitleStyle), + Text('keep_description'.t(context: context), style: subtitleStyle), const SizedBox(height: 4), SwitchListTile( contentPadding: EdgeInsets.zero, title: Text( - context.t.keep_favorites, + 'keep_favorites'.t(context: context), style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), @@ -352,7 +344,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { ), const SizedBox(height: 16), Text( - context.t.always_keep, + 'always_keep'.t(context: context), style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), const SizedBox(height: 4), @@ -362,12 +354,12 @@ class _FreeUpSpaceSettingsState extends ConsumerState { const ButtonSegment(value: AssetKeepType.none, label: Text('—')), ButtonSegment( value: AssetKeepType.photosOnly, - label: Text(context.t.photos), + label: Text('photos'.t(context: context)), icon: const Icon(Icons.photo), ), ButtonSegment( value: AssetKeepType.videosOnly, - label: Text(context.t.videos), + label: Text('videos'.t(context: context)), icon: const Icon(Icons.videocam), ), ], @@ -381,8 +373,8 @@ class _FreeUpSpaceSettingsState extends ConsumerState { const SizedBox(height: 8), Text( state.keepMediaType == AssetKeepType.photosOnly - ? context.t.always_keep_photos_hint - : context.t.always_keep_videos_hint, + ? 'always_keep_photos_hint'.t(context: context) + : 'always_keep_videos_hint'.t(context: context), style: context.textTheme.bodySmall?.copyWith( color: context.colorScheme.onSurfaceVariant, ), @@ -407,13 +399,13 @@ class _FreeUpSpaceSettingsState extends ConsumerState { setState(() => _currentStep = CleanupStep.values[step]); } }, - controlsBuilder: (_, _) => const SizedBox.shrink(), + controlsBuilder: (_, __) => const SizedBox.shrink(), steps: [ // Step 1: Select Cutoff Date Step( stepStyle: styleForState(step1State), title: Text( - context.t.select_cutoff_date, + 'select_cutoff_date'.t(context: context), style: context.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: step1State == StepState.complete @@ -433,7 +425,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { content: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text(context.t.cutoff_date_description, style: subtitleStyle), + Text('cutoff_date_description'.t(context: context), style: subtitleStyle), const SizedBox(height: 16), GridView.count( shrinkWrap: true, @@ -445,39 +437,39 @@ class _FreeUpSpaceSettingsState extends ConsumerState { children: [ _DatePresetCard( value: '30', - unit: context.t.cutoff_day(count: 30), + unit: 'cutoff_day'.t(context: context, args: {'count': '30'}), onTap: () => _setPresetDate(30), isSelected: _isPresetSelected(30), ), _DatePresetCard( value: '60', - unit: context.t.cutoff_day(count: 60), + unit: 'cutoff_day'.t(context: context, args: {'count': '60'}), onTap: () => _setPresetDate(60), isSelected: _isPresetSelected(60), ), _DatePresetCard( value: '90', - unit: context.t.cutoff_day(count: 90), + unit: 'cutoff_day'.t(context: context, args: {'count': '90'}), onTap: () => _setPresetDate(90), isSelected: _isPresetSelected(90), ), _DatePresetCard( value: '1', - unit: context.t.cutoff_year(count: 1), + unit: 'cutoff_year'.t(context: context, args: {'count': '1'}), onTap: () => _setPresetDate(365), isSelected: _isPresetSelected(365), ), _DatePresetCard( value: '2', - unit: context.t.cutoff_year(count: 2), + unit: 'cutoff_year'.t(context: context, args: {'count': '2'}), onTap: () => _setPresetDate(730), isSelected: _isPresetSelected(730), ), _DatePresetCard( value: '3', - unit: context.t.cutoff_year(count: 3), + unit: 'cutoff_year'.t(context: context, args: {'count': '3'}), onTap: () => _setPresetDate(1095), isSelected: _isPresetSelected(1095), ), @@ -487,14 +479,14 @@ class _FreeUpSpaceSettingsState extends ConsumerState { OutlinedButton.icon( onPressed: _selectDate, icon: const Icon(Icons.calendar_today), - label: Text(context.t.custom_date), + label: Text('custom_date'.t(context: context)), style: OutlinedButton.styleFrom(minimumSize: const Size(double.infinity, 48)), ), const SizedBox(height: 16), ElevatedButton.icon( onPressed: hasDate ? _goToScanStep : null, icon: const Icon(Icons.arrow_forward), - label: Text(context.t.continue$), + label: Text('continue'.t(context: context)), style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 48)), ), ], @@ -507,7 +499,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { Step( stepStyle: styleForState(step2State), title: Text( - context.t.scan, + 'scan'.t(context: context), style: context.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: step2State == StepState.complete @@ -520,11 +512,17 @@ class _FreeUpSpaceSettingsState extends ConsumerState { subtitle: _hasScanned ? Text( state.totalBytes > 0 - ? context.t.cleanup_found_assets_with_size( - count: state.assetsToDelete.length, - size: formatBytes(state.totalBytes), + ? 'cleanup_found_assets_with_size'.t( + context: context, + args: { + 'count': state.assetsToDelete.length.toString(), + 'size': formatBytes(state.totalBytes), + }, ) - : context.t.cleanup_found_assets(count: state.assetsToDelete.length), + : 'cleanup_found_assets'.t( + context: context, + args: {'count': state.assetsToDelete.length.toString()}, + ), style: context.textTheme.bodyMedium?.copyWith( color: state.assetsToDelete.isNotEmpty ? context.colorScheme.primary @@ -535,7 +533,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { : null, content: Column( children: [ - Text(context.t.cleanup_step3_description, style: subtitleStyle), + Text('cleanup_step3_description'.t(context: context), style: subtitleStyle), if (CurrentPlatform.isIOS) ...[ const SizedBox(height: 12), Container( @@ -550,7 +548,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { const SizedBox(width: 12), Expanded( child: Text( - context.t.cleanup_icloud_shared_albums_excluded, + 'cleanup_icloud_shared_albums_excluded'.t(context: context), style: context.textTheme.labelLarge, ), ), @@ -571,7 +569,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { : ElevatedButton.icon( onPressed: state.isScanning ? null : _scanAssets, icon: const Icon(Icons.search), - label: Text(_hasScanned ? context.t.rescan : context.t.scan), + label: Text(_hasScanned ? 'rescan'.t(context: context) : 'scan'.t(context: context)), style: ElevatedButton.styleFrom(minimumSize: const Size(double.infinity, 48)), ), if (_hasScanned && state.assetsToDelete.isEmpty) ...[ @@ -587,7 +585,10 @@ class _FreeUpSpaceSettingsState extends ConsumerState { const Icon(Icons.info, color: Colors.orange), const SizedBox(width: 12), Expanded( - child: Text(context.t.cleanup_no_assets_found, style: context.textTheme.bodyMedium), + child: Text( + 'cleanup_no_assets_found'.t(context: context), + style: context.textTheme.bodyMedium, + ), ), ], ), @@ -603,7 +604,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { Step( stepStyle: styleForState(step3State, isDestructive: true), title: Text( - context.t.move_to_device_trash, + 'move_to_device_trash'.t(context: context), style: context.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: step3State == StepState.disabled @@ -626,9 +627,12 @@ class _FreeUpSpaceSettingsState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.cleanup_step4_summary( - count: state.assetsToDelete.length, - date: DateFormat.yMMMd().format(state.selectedDate!), + 'cleanup_step4_summary'.t( + context: context, + args: { + 'count': state.assetsToDelete.length.toString(), + 'date': DateFormat.yMMMd().format(state.selectedDate!), + }, ), style: context.textTheme.labelLarge?.copyWith(fontSize: 15), ), @@ -640,7 +644,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState { OutlinedButton.icon( onPressed: () => _showAssetsPreview(state.assetsToDelete), icon: const Icon(Icons.preview), - label: Text(context.t.preview), + label: Text('preview'.t(context: context)), style: OutlinedButton.styleFrom(minimumSize: const Size(double.infinity, 48)), ), const SizedBox(height: 12), @@ -653,7 +657,11 @@ class _FreeUpSpaceSettingsState extends ConsumerState { child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.delete_forever), - label: Text(state.isDeleting ? context.t.cleanup_deleting : context.t.move_to_device_trash), + label: Text( + state.isDeleting + ? 'cleanup_deleting'.t(context: context) + : 'move_to_device_trash'.t(context: context), + ), style: ElevatedButton.styleFrom( backgroundColor: context.colorScheme.error, foregroundColor: context.colorScheme.onError, @@ -685,20 +693,26 @@ class _DeleteConfirmationDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - title: Text(context.t.cleanup_confirm_prompt_title), + title: Text('cleanup_confirm_prompt_title'.t(context: context)), content: Text( - context.t.cleanup_confirm_description(count: assetCount, date: DateFormat.yMMMd().format(cutoffDate)), + 'cleanup_confirm_description'.t( + context: context, + args: {'count': assetCount.toString(), 'date': DateFormat.yMMMd().format(cutoffDate)}, + ), style: context.textTheme.labelLarge?.copyWith(fontSize: 15), ), actions: [ - TextButton(onPressed: () => ContextHelper(context).pop(false), child: Text(context.t.cancel)), + TextButton( + onPressed: () => context.pop(false), + child: Text('cancel'.t(context: context)), + ), ElevatedButton( - onPressed: () => ContextHelper(context).pop(true), + onPressed: () => context.pop(true), style: ElevatedButton.styleFrom( backgroundColor: context.colorScheme.error, foregroundColor: context.colorScheme.onError, ), - child: Text(context.t.confirm), + child: Text('confirm'.t(context: context)), ), ], ); @@ -714,24 +728,29 @@ class _DeleteSuccessDialog extends StatelessWidget { Widget build(BuildContext context) { return AlertDialog( icon: Icon(Icons.check_circle, color: context.colorScheme.primary, size: 48), - title: Text(context.t.success), + title: Text('success'.t(context: context)), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text( - context.t.cleanup_deleted_assets(count: deletedCount), + 'cleanup_deleted_assets'.t(context: context, args: {'count': deletedCount.toString()}), style: context.textTheme.labelLarge?.copyWith(fontSize: 16), textAlign: TextAlign.center, ), const SizedBox(height: 16), Text( - context.t.cleanup_trash_hint, + 'cleanup_trash_hint'.t(context: context), style: context.textTheme.labelLarge?.copyWith(fontSize: 16, color: context.primaryColor), textAlign: TextAlign.center, ), ], ), - actions: [ElevatedButton(onPressed: () => ContextHelper(context).pop(), child: Text(context.t.done))], + actions: [ + ElevatedButton( + onPressed: () => context.pop(), + child: Text('done'.t(context: context)), + ), + ], ); } } @@ -752,7 +771,7 @@ class _DatePresetCard extends StatelessWidget { child: InkWell( onTap: onTap, borderRadius: const BorderRadius.all(Radius.circular(12)), - child: DecoratedBox( + child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(12)), border: Border.all(color: isSelected ? context.colorScheme.primary : Colors.transparent, width: 1), @@ -797,7 +816,7 @@ class _KeepAlbumsSection extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.keep_albums, + 'keep_albums'.t(context: context), style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), @@ -807,13 +826,13 @@ class _KeepAlbumsSection extends ConsumerWidget { child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator(strokeWidth: 2)), ), error: (error, stack) => Text( - context.t.error_loading_albums, + 'error_loading_albums'.t(context: context), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.error), ), data: (albums) { if (albums.isEmpty) { return Text( - context.t.no_albums_found, + 'no_albums_found'.t(context: context), style: context.textTheme.bodyMedium?.copyWith( color: context.colorScheme.onSurface.withValues(alpha: 0.6), ), @@ -843,7 +862,7 @@ class _KeepAlbumsSection extends ConsumerWidget { if (albumIds.isNotEmpty) ...[ const SizedBox(height: 8), Text( - context.t.keep_albums_count(count: albumIds.length), + 'keep_albums_count'.t(context: context, args: {'count': albumIds.length.toString()}), style: context.textTheme.bodySmall?.copyWith( color: context.colorScheme.primary, fontWeight: FontWeight.w500, diff --git a/mobile/lib/widgets/settings/language_settings.dart b/mobile/lib/widgets/settings/language_settings.dart index 61f2088979..d0b3ac0021 100644 --- a/mobile/lib/widgets/settings/language_settings.dart +++ b/mobile/lib/widgets/settings/language_settings.dart @@ -1,15 +1,15 @@ import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/services/localization.service.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/widgets/common/search_field.dart'; -class LanguageSettings extends HookWidget { +class LanguageSettings extends HookConsumerWidget { const LanguageSettings({super.key}); Future _applyLanguageChange( @@ -20,10 +20,6 @@ class LanguageSettings extends HookWidget { isLoading.value = true; await Future.delayed(const Duration(milliseconds: 500)); try { - if (!context.mounted) { - return; - } - await context.setLocale(selectedLocale.value); await loadTranslations(); } finally { @@ -32,7 +28,7 @@ class LanguageSettings extends HookWidget { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final localeEntries = useMemoized(() => locales.entries.toList(), const []); final currentLocale = context.locale; final filteredLocaleEntries = useState>>(localeEntries); @@ -88,7 +84,7 @@ class LanguageSettings extends HookWidget { padding: const EdgeInsets.all(8), itemCount: filteredLocaleEntries.value.length, itemExtent: 64.0, - scrollCacheExtent: const .pixels(100), + cacheExtent: 100, itemBuilder: (context, index) { final countryName = filteredLocaleEntries.value[index].key; final localeValue = filteredLocaleEntries.value[index].value; @@ -152,7 +148,7 @@ class _LanguageSearchBar extends StatelessWidget { child: SearchField( autofocus: false, contentPadding: const EdgeInsets.all(12), - hintText: context.t.language_search_hint, + hintText: 'language_search_hint'.t(context: context), prefixIcon: const Icon(Icons.search_rounded), suffixIcon: controller.text.isNotEmpty ? IconButton(icon: const Icon(Icons.clear_rounded), onPressed: onClear) @@ -179,12 +175,12 @@ class _LanguageNotFound extends StatelessWidget { Icon(Icons.search_off_rounded, size: 64, color: context.colorScheme.onSurface.withValues(alpha: 0.4)), const SizedBox(height: 8), Text( - context.t.language_no_results_title, + 'language_no_results_title'.t(context: context), style: context.textTheme.titleMedium?.copyWith(color: context.colorScheme.onSurface), ), const SizedBox(height: 4), Text( - context.t.language_no_results_subtitle, + 'language_no_results_subtitle'.t(context: context), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.8)), ), ], @@ -214,7 +210,7 @@ class _LanguageApplyButton extends StatelessWidget { child: isLoading ? const SizedBox.square(dimension: 24, child: CircularProgressIndicator(strokeWidth: 2)) : Text( - context.t.setting_languages_apply, + 'setting_languages_apply'.t(context: context), style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16.0), ), ), diff --git a/mobile/lib/widgets/settings/local_storage_settings.dart b/mobile/lib/widgets/settings/local_storage_settings.dart new file mode 100644 index 0000000000..af9e4079bb --- /dev/null +++ b/mobile/lib/widgets/settings/local_storage_settings.dart @@ -0,0 +1,51 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' show useEffect, useState; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/theme_extensions.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; + +class LocalStorageSettings extends HookConsumerWidget { + const LocalStorageSettings({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final isarDb = ref.watch(dbProvider); + final cacheItemCount = useState(0); + + useEffect(() { + cacheItemCount.value = isarDb.duplicatedAssets.countSync(); + return null; + }, []); + + void clearCache() async { + await isarDb.writeTxn(() => isarDb.duplicatedAssets.clear()); + cacheItemCount.value = await isarDb.duplicatedAssets.count(); + } + + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 20), + dense: true, + title: Text( + "cache_settings_duplicated_assets_title", + style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), + ).tr(namedArgs: {'count': "${cacheItemCount.value}"}), + subtitle: Text( + "cache_settings_duplicated_assets_subtitle", + style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ).tr(), + trailing: TextButton( + onPressed: cacheItemCount.value > 0 ? clearCache : null, + child: Text( + "cache_settings_duplicated_assets_clear_button", + style: TextStyle( + fontSize: 12, + color: cacheItemCount.value > 0 ? Colors.red : Colors.grey, + fontWeight: FontWeight.bold, + ), + ).tr(), + ), + ); + } +} diff --git a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart index 349b32287b..735971e0c2 100644 --- a/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart +++ b/mobile/lib/widgets/settings/networking_settings/endpoint_input.dart @@ -1,12 +1,10 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart'; -import 'package:immich_ui/immich_ui.dart'; class EndpointInput extends StatefulHookConsumerWidget { const EndpointInput({ @@ -55,7 +53,7 @@ class EndpointInputState extends ConsumerState { void _onOutFocus() { if (!focusNode.hasFocus && isInputValid) { - unawaited(validateAuxilaryServerUrl()); + validateAuxilaryServerUrl(); } } @@ -78,11 +76,11 @@ class EndpointInputState extends ConsumerState { try { if (url == null || url.isEmpty || !Uri.parse(url).isAbsolute) { isInputValid = false; - return context.t.validate_endpoint_error; + return 'validate_endpoint_error'.tr(); } } catch (_) { isInputValid = false; - return context.t.validate_endpoint_error; + return 'validate_endpoint_error'.tr(); } isInputValid = true; @@ -113,12 +111,28 @@ class EndpointInputState extends ConsumerState { status: auxCheckStatus, enabled: widget.enabled, ), - subtitle: ImmichURLInput( + subtitle: TextFormField( enabled: widget.enabled, - autovalidateMode: .onUserInteraction, + onTapOutside: (_) => focusNode.unfocus(), + autovalidateMode: AutovalidateMode.onUserInteraction, validator: validateUrl, - keyboardAction: .next, - hintText: 'http(s)://immich.domain.com', + keyboardType: TextInputType.url, + style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 14), + decoration: InputDecoration( + hintText: 'http(s)://immich.domain.com', + contentPadding: const EdgeInsets.all(16), + filled: true, + fillColor: context.colorScheme.surfaceContainer, + border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.red[300]!), + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!), + borderRadius: const BorderRadius.all(Radius.circular(16)), + ), + ), controller: controller, focusNode: focusNode, ), diff --git a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart index 63b094b440..ba21acf49c 100644 --- a/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/external_network_preference.dart @@ -1,14 +1,13 @@ -import 'dart:async'; +import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; -import 'package:immich_mobile/providers/api.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart'; class ExternalNetworkPreference extends HookConsumerWidget { @@ -21,38 +20,38 @@ class ExternalNetworkPreference extends HookConsumerWidget { final entries = useState([const AuxilaryEndpoint(url: '', status: AuxCheckStatus.unknown)]); final canSave = useState(false); - Future saveEndpointList() { + saveEndpointList() { canSave.value = entries.value.every((e) => e.status == AuxCheckStatus.valid); - final urls = entries.value - .where((e) => e.status == AuxCheckStatus.valid && e.url.isNotEmpty) - .map((e) => e.url) - .toList(); + final endpointList = entries.value.where((url) => url.status == AuxCheckStatus.valid).toList(); - return ref.read(settingsProvider).write(SettingsKey.networkExternalEndpointList, urls); + final jsonString = jsonEncode(endpointList); + + Store.put(StoreKey.externalEndpointList, jsonString); } - Future updateValidationStatus(String url, int index, AuxCheckStatus status) async { + updateValidationStatus(String url, int index, AuxCheckStatus status) { entries.value[index] = entries.value[index].copyWith(url: url, status: status); - await saveEndpointList(); - if (status == AuxCheckStatus.valid && context.mounted) { - await ref.read(apiServiceProvider).updateHeaders(); - } + saveEndpointList(); } - void handleReorder(int oldIndex, int newIndex) { + handleReorder(int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final entry = entries.value.removeAt(oldIndex); entries.value.insert(newIndex, entry); entries.value = [...entries.value]; - unawaited(saveEndpointList()); + saveEndpointList(); } - void handleDismiss(int index) { + handleDismiss(int index) { entries.value = [...entries.value..removeAt(index)]; - unawaited(saveEndpointList()); + saveEndpointList(); } Widget proxyDecorator(Widget child, int _, Animation animation) { @@ -70,13 +69,14 @@ class ExternalNetworkPreference extends HookConsumerWidget { } useEffect(() { - final urls = ref.read(appConfigProvider).network.externalEndpointList; + final jsonString = Store.tryGet(StoreKey.externalEndpointList); - if (urls.isEmpty) { + if (jsonString == null) { return null; } - entries.value = urls.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList(); + final List jsonList = jsonDecode(jsonString); + entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList(); return null; }, const []); @@ -103,7 +103,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24), - child: Text(context.t.external_network_sheet_info, style: context.textTheme.bodyMedium), + child: Text("external_network_sheet_info".t(context: context), style: context.textTheme.bodyMedium), ), const SizedBox(height: 4), Divider(color: context.colorScheme.surfaceContainerHighest), @@ -115,7 +115,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: entries.value.length, - onReorderItem: handleReorder, + onReorder: handleReorder, itemBuilder: (context, index) { return EndpointInput( key: Key(index.toString()), @@ -135,7 +135,7 @@ class ExternalNetworkPreference extends HookConsumerWidget { height: 48, child: OutlinedButton.icon( icon: const Icon(Icons.add), - label: Text(context.t.add_endpoint), + label: Text('add_endpoint'.t(context: context)), onPressed: enabled ? () { entries.value = [ diff --git a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart index 3c4fa73543..c89c8e149e 100644 --- a/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart +++ b/mobile/lib/widgets/settings/networking_settings/local_network_preference.dart @@ -1,44 +1,37 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; -import 'package:immich_ui/immich_ui.dart'; class LocalNetworkPreference extends HookConsumerWidget { const LocalNetworkPreference({super.key, required this.enabled}); final bool enabled; - Future _showEditDialog( - BuildContext context, - String title, - String hintText, - String initialValue, { - bool isUrlField = false, - }) { + Future _showEditDialog(BuildContext context, String title, String hintText, String initialValue) { final controller = TextEditingController(text: initialValue); return showDialog( context: context, builder: (context) => AlertDialog( title: Text(title), - content: isUrlField - ? ImmichURLInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText) - : ImmichTextInput(controller: controller, autofocus: true, keyboardAction: .done, hintText: hintText), + content: TextField( + controller: controller, + autofocus: true, + decoration: InputDecoration(border: const OutlineInputBorder(), hintText: hintText), + ), actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: Text(context.t.cancel.toUpperCase(), style: const TextStyle(color: Colors.red)), - ), - TextButton( - onPressed: () => Navigator.pop(context, controller.text), - child: Text(context.t.save.toUpperCase()), + child: Text('cancel'.tr().toUpperCase(), style: const TextStyle(color: Colors.red)), ), + TextButton(onPressed: () => Navigator.pop(context, controller.text), child: Text('save'.tr().toUpperCase())), ], ), ); @@ -64,36 +57,30 @@ class LocalNetworkPreference extends HookConsumerWidget { return null; }, []); - Future saveWifiName(String wifiName) { + saveWifiName(String wifiName) { wifiNameText.value = wifiName; return ref.read(authProvider.notifier).saveWifiName(wifiName); } - Future saveLocalEndpoint(String url) { + saveLocalEndpoint(String url) { localEndpointText.value = url; return ref.read(authProvider.notifier).saveLocalEndpoint(url); } - Future handleEditWifiName() async { - final wifiName = await _showEditDialog( - context, - context.t.wifi_name, - context.t.your_wifi_name, - wifiNameText.value, - ); + handleEditWifiName() async { + final wifiName = await _showEditDialog(context, "wifi_name".tr(), "your_wifi_name".tr(), wifiNameText.value); if (wifiName != null) { await saveWifiName(wifiName); } } - Future handleEditServerEndpoint() async { + handleEditServerEndpoint() async { final localEndpoint = await _showEditDialog( context, - context.t.server_endpoint, + "server_endpoint".tr(), "http://local-ip:2283", localEndpointText.value, - isUrlField: true, ); if (localEndpoint != null) { @@ -101,17 +88,14 @@ class LocalNetworkPreference extends HookConsumerWidget { } } - Future autofillCurrentNetwork() async { + autofillCurrentNetwork() async { final wifiName = await ref.read(networkProvider.notifier).getWifiName(); - if (!context.mounted) { - return; - } if (wifiName == null) { context.showSnackBar( SnackBar( content: Text( - context.t.get_wifiname_error, + "get_wifiname_error".tr(), style: context.textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w500, color: context.colorScheme.onSecondary, @@ -156,7 +140,7 @@ class LocalNetworkPreference extends HookConsumerWidget { children: [ Padding( padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24), - child: Text(context.t.local_network_sheet_info, style: context.textTheme.bodyMedium), + child: Text("local_network_sheet_info".tr(), style: context.textTheme.bodyMedium), ), const SizedBox(height: 4), Divider(color: context.colorScheme.surfaceContainerHighest), @@ -164,9 +148,9 @@ class LocalNetworkPreference extends HookConsumerWidget { enabled: enabled, contentPadding: const EdgeInsets.only(left: 24, right: 8), leading: const Icon(Icons.wifi_rounded), - title: Text(context.t.wifi_name), + title: Text("wifi_name".tr()), subtitle: wifiNameText.value.isEmpty - ? Text(context.t.enter_wifi_name) + ? Text("enter_wifi_name".tr()) : Text( wifiNameText.value, style: context.textTheme.labelLarge?.copyWith( @@ -184,7 +168,7 @@ class LocalNetworkPreference extends HookConsumerWidget { enabled: enabled, contentPadding: const EdgeInsets.only(left: 24, right: 8), leading: const Icon(Icons.lan_rounded), - title: Text(context.t.server_endpoint), + title: Text("server_endpoint".t(context: context)), subtitle: localEndpointText.value.isEmpty ? const Text("http://local-ip:2283") : Text( @@ -206,7 +190,7 @@ class LocalNetworkPreference extends HookConsumerWidget { height: 48, child: OutlinedButton.icon( icon: const Icon(Icons.wifi_find_rounded), - label: Text(context.t.use_current_connection), + label: Text('use_current_connection'.t(context: context)), onPressed: enabled ? autofillCurrentNetwork : null, ), ), diff --git a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart index 80b0b78fc2..981bec2c0c 100644 --- a/mobile/lib/widgets/settings/networking_settings/networking_settings.dart +++ b/mobile/lib/widgets/settings/networking_settings/networking_settings.dart @@ -1,13 +1,13 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart'; @@ -20,10 +20,7 @@ class NetworkingSettings extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentEndpoint = getServerUrl(); - final featureEnabled = useState(ref.watch(appConfigProvider).network.autoEndpointSwitching); - useValueChanged(featureEnabled.value, (_, _) { - unawaited(ref.read(settingsProvider).write(.networkAutoEndpointSwitching, featureEnabled.value)); - }); + final featureEnabled = useAppSettingsState(AppSettingsEnum.autoEndpointSwitching); Future checkWifiReadPermission() async { final [hasLocationInUse, hasLocationAlways] = await Future.wait([ @@ -38,20 +35,16 @@ class NetworkingSettings extends HookConsumerWidget { context: context, builder: (context) { return AlertDialog( - title: Text(context.t.location_permission), - content: Text(context.t.location_permission_content), + title: Text("location_permission".tr()), + content: Text("location_permission_content".tr()), actions: [ TextButton( onPressed: () async { final isGrant = await ref.read(networkProvider.notifier).requestWifiReadPermission(); - if (!context.mounted) { - return; - } - Navigator.pop(context, isGrant); }, - child: Text(context.t.grant_permission), + child: Text("grant_permission".tr()), ), ], ); @@ -59,29 +52,21 @@ class NetworkingSettings extends HookConsumerWidget { ); } - if (!context.mounted) { - return; - } - if (!hasLocationAlways) { isGrantLocationAlwaysPermission = await showDialog( context: context, builder: (context) { return AlertDialog( - title: Text(context.t.background_location_permission), - content: Text(context.t.background_location_permission_content), + title: Text("background_location_permission".tr()), + content: Text("background_location_permission_content".tr()), actions: [ TextButton( onPressed: () async { final isGrant = await ref.read(networkProvider.notifier).requestWifiReadBackgroundPermission(); - if (!context.mounted) { - return; - } - Navigator.pop(context, isGrant); }, - child: Text(context.t.grant_permission), + child: Text("grant_permission".tr()), ), ], ); @@ -96,7 +81,7 @@ class NetworkingSettings extends HookConsumerWidget { useEffect(() { if (featureEnabled.value == true) { - unawaited(checkWifiReadPermission()); + checkWifiReadPermission(); } return null; }, [featureEnabled.value]); @@ -106,7 +91,7 @@ class NetworkingSettings extends HookConsumerWidget { children: [ const SizedBox(height: 8), SettingGroupTitle( - title: context.t.current_server_address, + title: "current_server_address".t(context: context), icon: (currentEndpoint?.startsWith('https') ?? false) ? Icons.https_outlined : Icons.http_outlined, ), Padding( @@ -135,14 +120,20 @@ class NetworkingSettings extends HookConsumerWidget { SettingsSwitchListTile( enabled: true, valueNotifier: featureEnabled, - title: context.t.automatic_endpoint_switching_title, - subtitle: context.t.automatic_endpoint_switching_subtitle, + title: "automatic_endpoint_switching_title".tr(), + subtitle: "automatic_endpoint_switching_subtitle".tr(), ), const SizedBox(height: 8), - SettingGroupTitle(title: context.t.local_network, icon: Icons.home_outlined), + SettingGroupTitle( + title: "local_network".t(context: context), + icon: Icons.home_outlined, + ), LocalNetworkPreference(enabled: featureEnabled.value), const SizedBox(height: 16), - SettingGroupTitle(title: context.t.external_network, icon: Icons.dns_outlined), + SettingGroupTitle( + title: "external_network".t(context: context), + icon: Icons.dns_outlined, + ), ExternalNetworkPreference(enabled: featureEnabled.value), ], ); diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index c3d175999d..d9eab26bda 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -1,12 +1,14 @@ -import 'dart:async'; - +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/generated/translations.g.dart'; -import 'package:immich_mobile/providers/permission.provider.dart'; +import 'package:immich_mobile/providers/notification_permission.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; +import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; +import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:permission_handler/permission_handler.dart'; class NotificationSetting extends HookConsumerWidget { @@ -15,54 +17,87 @@ class NotificationSetting extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final permissionService = ref.watch(notificationPermissionProvider); + + final sliderValue = useAppSettingsState(AppSettingsEnum.uploadErrorNotificationGracePeriod); + final totalProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupTotalProgress); + final singleProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupSingleProgress); + final hasPermission = permissionService == PermissionStatus.granted; - void openAppNotificationSettings(BuildContext ctx) { + openAppNotificationSettings(BuildContext ctx) { ctx.pop(); - unawaited(openAppSettings()); + openAppSettings(); } // When permissions are permanently denied, you need to go to settings to // allow them - void showPermissionsDialog() { - unawaited( - showDialog( - context: context, - builder: (ctx) => AlertDialog( - content: Text(ctx.t.notification_permission_dialog_content), - actions: [ - TextButton(child: Text(ctx.t.cancel), onPressed: () => ctx.pop()), - TextButton(onPressed: () => openAppNotificationSettings(ctx), child: Text(ctx.t.settings)), - ], - ), + showPermissionsDialog() { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + content: const Text('notification_permission_dialog_content').tr(), + actions: [ + TextButton(child: const Text('cancel').tr(), onPressed: () => ctx.pop()), + TextButton(onPressed: () => openAppNotificationSettings(ctx), child: const Text('settings').tr()), + ], ), ); } + final String formattedValue = _formatSliderValue(sliderValue.value.toDouble()); + final notificationSettings = [ if (!hasPermission) SettingsButtonListTile( icon: Icons.notifications_outlined, - title: context.t.notification_permission_list_tile_title, - subtileText: context.t.notification_permission_list_tile_content, - buttonText: context.t.notification_permission_list_tile_enable_button, + title: 'notification_permission_list_tile_title'.tr(), + subtileText: 'notification_permission_list_tile_content'.tr(), + buttonText: 'notification_permission_list_tile_enable_button'.tr(), onButtonTap: () => - ref.read(notificationPermissionProvider.notifier).requestNotificationPermission().then((permission) { + ref.watch(notificationPermissionProvider.notifier).requestNotificationPermission().then((permission) { if (permission == PermissionStatus.permanentlyDenied) { showPermissionsDialog(); } }), - ) - else - SettingsButtonListTile( - icon: Icons.notifications_active_outlined, - title: context.t.notification_enabled_list_tile_title, - subtileText: context.t.notification_enabled_list_tile_content, - buttonText: context.t.notification_enabled_list_tile_open_button, - onButtonTap: () => openAppSettings(), ), + SettingsSwitchListTile( + enabled: hasPermission, + valueNotifier: totalProgressValue, + title: 'setting_notifications_total_progress_title'.tr(), + subtitle: 'setting_notifications_total_progress_subtitle'.tr(), + ), + SettingsSwitchListTile( + enabled: hasPermission, + valueNotifier: singleProgressValue, + title: 'setting_notifications_single_progress_title'.tr(), + subtitle: 'setting_notifications_single_progress_subtitle'.tr(), + ), + SettingsSliderListTile( + enabled: hasPermission, + valueNotifier: sliderValue, + text: 'setting_notifications_notify_failures_grace_period'.tr(namedArgs: {'duration': formattedValue}), + maxValue: 5.0, + noDivisons: 5, + label: formattedValue, + ), ]; return SettingsSubPageScaffold(settings: notificationSettings); } } + +String _formatSliderValue(double v) { + if (v == 0.0) { + return 'setting_notifications_notify_immediately'.tr(); + } else if (v == 1.0) { + return 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '30'}); + } else if (v == 2.0) { + return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '2'}); + } else if (v == 3.0) { + return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'}); + } else if (v == 4.0) { + return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'}); + } else { + return 'setting_notifications_notify_never'.tr(); + } +} diff --git a/mobile/lib/widgets/settings/preference_settings/haptic_setting.dart b/mobile/lib/widgets/settings/preference_settings/haptic_setting.dart index 9aaae683ab..5e745dd61d 100644 --- a/mobile/lib/widgets/settings/preference_settings/haptic_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/haptic_setting.dart @@ -1,30 +1,34 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; -class HapticSetting extends HookWidget { +class HapticSetting extends HookConsumerWidget { const HapticSetting({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final hapticFeedbackSetting = useAppSettingsState(AppSettingsEnum.enableHapticFeedback); final isHapticFeedbackEnabled = useValueNotifier(hapticFeedbackSetting.value); - void onHapticFeedbackChange(bool isEnabled) { + onHapticFeedbackChange(bool isEnabled) { hapticFeedbackSetting.value = isEnabled; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.haptic_feedback_title, icon: Icons.vibration_outlined), + SettingGroupTitle( + title: "haptic_feedback_title".t(context: context), + icon: Icons.vibration_outlined, + ), SettingsSwitchListTile( valueNotifier: isHapticFeedbackEnabled, - title: context.t.enabled, + title: 'enabled'.t(context: context), onChanged: onHapticFeedbackChange, ), ], diff --git a/mobile/lib/widgets/settings/preference_settings/preference_setting.dart b/mobile/lib/widgets/settings/preference_settings/preference_setting.dart index afb3b478f0..144fbf9758 100644 --- a/mobile/lib/widgets/settings/preference_settings/preference_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/preference_setting.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/haptic_setting.dart'; -import 'package:immich_mobile/widgets/settings/preference_settings/share_setting.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/theme_setting.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; @@ -9,7 +8,7 @@ class PreferenceSetting extends StatelessWidget { @override Widget build(BuildContext context) { - const preferenceSettings = [ThemeSetting(), HapticSetting(), ShareSetting()]; + const preferenceSettings = [ThemeSetting(), HapticSetting()]; return const SettingsSubPageScaffold(settings: preferenceSettings, showDivider: true); } diff --git a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart index 37235a182a..22c9154981 100644 --- a/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/primary_color_setting.dart @@ -1,52 +1,61 @@ -import 'dart:async'; - +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/colors.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; import 'package:immich_mobile/providers/theme.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/theme/color_scheme.dart'; import 'package:immich_mobile/theme/dynamic_theme.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class PrimaryColorSetting extends HookConsumerWidget { const PrimaryColorSetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final themeProvider = ref.watch(immichThemeProvider); - final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme)); + final themeProvider = ref.read(immichThemeProvider); + final primaryColorSetting = useAppSettingsState(AppSettingsEnum.primaryColor); + final systemPrimaryColorSetting = useAppSettingsState(AppSettingsEnum.dynamicTheme); + + final currentPreset = useValueNotifier(ref.read(immichThemePresetProvider)); const tileSize = 55.0; + useValueChanged( + primaryColorSetting.value, + (_, __) => currentPreset.value = ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorSetting.value), + ); + void popBottomSheet() { Future.delayed(const Duration(milliseconds: 200), () { - if (!context.mounted) { - return; - } - Navigator.pop(context); }); } - void onUseSystemColorChange(bool newValue) { - unawaited(ref.read(settingsProvider).write(.themeDynamic, newValue)); + onUseSystemColorChange(bool newValue) { + systemPrimaryColorSetting.value = newValue; + ref.watch(dynamicThemeSettingProvider.notifier).state = newValue; + ref.invalidate(immichThemeProvider); popBottomSheet(); } - void onPrimaryColorChange(ImmichColorPreset colorPreset) { - unawaited(ref.read(settingsProvider).write(.themePrimaryColor, colorPreset)); + onPrimaryColorChange(ImmichColorPreset colorPreset) { + primaryColorSetting.value = colorPreset.name; + ref.watch(immichThemePresetProvider.notifier).state = colorPreset; + ref.invalidate(immichThemeProvider); //turn off system color setting - if (themeConfig.dynamicTheme) { - unawaited(ref.read(settingsProvider).write(.themeDynamic, false)); + if (systemPrimaryColorSetting.value) { + onUseSystemColorChange(false); + } else { + popBottomSheet(); } - popBottomSheet(); } - Container buildPrimaryColorTile({ + buildPrimaryColorTile({ required Color topColor, required Color bottomColor, required double tileSize, @@ -75,7 +84,7 @@ class PrimaryColorSetting extends HookConsumerWidget { right: 0, top: 0, bottom: 0, - child: DecoratedBox( + child: Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(100)), color: Colors.grey[900]?.withValues(alpha: .4), @@ -91,13 +100,13 @@ class PrimaryColorSetting extends HookConsumerWidget { ); } - Column bottomSheetContent() { + bottomSheetContent() { return Column( mainAxisSize: MainAxisSize.min, children: [ Align( alignment: Alignment.center, - child: Text(context.t.theme_setting_primary_color_title, style: context.textTheme.titleLarge), + child: Text("theme_setting_primary_color_title".tr(), style: context.textTheme.titleLarge), ), if (DynamicTheme.isAvailable) Container( @@ -110,10 +119,10 @@ class PrimaryColorSetting extends HookConsumerWidget { tileColor: context.colorScheme.surfaceContainerHigh, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15))), title: Text( - context.t.theme_setting_system_primary_color_title, + 'theme_setting_system_primary_color_title'.tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5), ), - value: themeConfig.dynamicTheme, + value: systemPrimaryColorSetting.value, onChanged: onUseSystemColorChange, ), ), @@ -131,7 +140,7 @@ class PrimaryColorSetting extends HookConsumerWidget { topColor: theme.light.primary, bottomColor: theme.dark.primary, tileSize: tileSize, - showSelector: themeConfig.primaryColor == preset && !themeConfig.dynamicTheme, + showSelector: currentPreset.value == preset && !systemPrimaryColorSetting.value, ), ); }).toList(), @@ -157,11 +166,11 @@ class PrimaryColorSetting extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.theme_setting_primary_color_title, + "theme_setting_primary_color_title".tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), ), Text( - context.t.theme_setting_primary_color_subtitle, + "theme_setting_primary_color_subtitle".tr(), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), ], diff --git a/mobile/lib/widgets/settings/preference_settings/share_setting.dart b/mobile/lib/widgets/settings/preference_settings/share_setting.dart deleted file mode 100644 index 1881703023..0000000000 --- a/mobile/lib/widgets/settings/preference_settings/share_setting.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; -import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; - -class ShareSetting extends HookConsumerWidget { - const ShareSetting({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final fileType = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.share.fileType))); - - void onChanged(ShareAssetType? value) { - if (value != null) { - fileType.value = value; - unawaited(ref.read(settingsProvider).write(SettingsKey.shareFileType, value)); - } - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SettingGroupTitle(title: context.t.default_share_quality, icon: Icons.ios_share_outlined), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text( - context.t.default_quality_subtitle, - style: context.textTheme.bodyMedium!.copyWith(color: context.textTheme.bodyMedium!.color!.withAlpha(215)), - ), - ), - SettingsRadioListTile( - groups: [ - SettingsRadioGroup(title: context.t.share_original, value: ShareAssetType.original), - SettingsRadioGroup(title: context.t.share_preview, value: ShareAssetType.preview), - ], - groupBy: fileType.value, - onRadioChanged: onChanged, - ), - ], - ); - } -} diff --git a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart index cf1a22c45f..fc20fb7bed 100644 --- a/mobile/lib/widgets/settings/preference_settings/theme_setting.dart +++ b/mobile/lib/widgets/settings/preference_settings/theme_setting.dart @@ -1,74 +1,99 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/extensions/translate_extensions.dart'; +import 'package:immich_mobile/providers/theme.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; +import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; class ThemeSetting extends HookConsumerWidget { const ThemeSetting({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final currentTheme = useState(ref.watch(appConfigProvider.select((config) => config.theme.mode))); + final currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode); + final currentTheme = useValueNotifier(ref.read(immichThemeModeProvider)); final isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark); final isSystemTheme = useValueNotifier(currentTheme.value == ThemeMode.system); - final colorfulInterface = useValueNotifier( - ref.watch(appConfigProvider.select((config) => config.theme.colorfulInterface)), + + final applyThemeToBackgroundSetting = useAppSettingsState(AppSettingsEnum.colorfulInterface); + final applyThemeToBackgroundProvider = useValueNotifier(ref.read(colorfulInterfaceSettingProvider)); + + useValueChanged( + currentThemeString.value, + (_, __) => currentTheme.value = switch (currentThemeString.value) { + "light" => ThemeMode.light, + "dark" => ThemeMode.dark, + _ => ThemeMode.system, + }, + ); + + useValueChanged( + applyThemeToBackgroundSetting.value, + (_, __) => applyThemeToBackgroundProvider.value = applyThemeToBackgroundSetting.value, ); void onThemeChange(bool isDark) { - currentTheme.value = isDark ? ThemeMode.dark : ThemeMode.light; - unawaited(ref.read(settingsProvider).write(.themeMode, currentTheme.value)); + if (isDark) { + ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark; + currentThemeString.value = "dark"; + } else { + ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light; + currentThemeString.value = "light"; + } } void onSystemThemeChange(bool isSystem) { if (isSystem) { - currentTheme.value = ThemeMode.system; + currentThemeString.value = "system"; isSystemTheme.value = true; + ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.system; } else { final currentSystemBrightness = context.platformBrightness; isSystemTheme.value = false; isDarkTheme.value = currentSystemBrightness == Brightness.dark; if (currentSystemBrightness == Brightness.light) { - currentTheme.value = ThemeMode.light; + currentThemeString.value = "light"; + ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light; } else if (currentSystemBrightness == Brightness.dark) { - currentTheme.value = ThemeMode.dark; + currentThemeString.value = "dark"; + ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark; } } - unawaited(ref.read(settingsProvider).write(.themeMode, currentTheme.value)); } void onSurfaceColorSettingChange(bool useColorfulInterface) { - unawaited(ref.read(settingsProvider).write(.themeColorfulInterface, useColorfulInterface)); - colorfulInterface.value = useColorfulInterface; + applyThemeToBackgroundSetting.value = useColorfulInterface; + ref.watch(colorfulInterfaceSettingProvider.notifier).state = useColorfulInterface; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingGroupTitle(title: context.t.theme, icon: Icons.color_lens_outlined), + SettingGroupTitle( + title: "theme".t(context: context), + icon: Icons.color_lens_outlined, + ), SettingsSwitchListTile( valueNotifier: isSystemTheme, - title: context.t.theme_setting_system_theme_switch, + title: 'theme_setting_system_theme_switch'.t(context: context), onChanged: onSystemThemeChange, ), if (currentTheme.value != ThemeMode.system) SettingsSwitchListTile( valueNotifier: isDarkTheme, - title: context.t.map_settings_dark_mode, + title: 'map_settings_dark_mode'.t(context: context), onChanged: onThemeChange, ), const PrimaryColorSetting(), SettingsSwitchListTile( - valueNotifier: colorfulInterface, - title: context.t.theme_setting_colorful_interface_title, - subtitle: context.t.theme_setting_colorful_interface_subtitle, + valueNotifier: applyThemeToBackgroundProvider, + title: "theme_setting_colorful_interface_title".t(context: context), + subtitle: 'theme_setting_colorful_interface_subtitle'.t(context: context), onChanged: onSurfaceColorSettingChange, ), ], diff --git a/mobile/lib/widgets/settings/settings_button_list_tile.dart b/mobile/lib/widgets/settings/settings_button_list_tile.dart index 1e06c0b2b4..0e8d75b22f 100644 --- a/mobile/lib/widgets/settings/settings_button_list_tile.dart +++ b/mobile/lib/widgets/settings/settings_button_list_tile.dart @@ -41,7 +41,7 @@ class SettingsButtonListTile extends StatelessWidget { subtileText!, style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), - ?subtitle, + if (subtitle != null) subtitle!, const SizedBox(height: 6), child ?? ElevatedButton(onPressed: onButtonTap, child: Text(buttonText)), ], diff --git a/mobile/lib/widgets/settings/settings_switch_list_tile.dart b/mobile/lib/widgets/settings/settings_switch_list_tile.dart index 4d358fa2b6..f5d6dfd05a 100644 --- a/mobile/lib/widgets/settings/settings_switch_list_tile.dart +++ b/mobile/lib/widgets/settings/settings_switch_list_tile.dart @@ -29,9 +29,7 @@ class SettingsSwitchListTile extends StatelessWidget { @override Widget build(BuildContext context) { void onSwitchChanged(bool value) { - if (!enabled) { - return; - } + if (!enabled) return; valueNotifier.value = value; onChanged?.call(value); @@ -44,7 +42,7 @@ class SettingsSwitchListTile extends StatelessWidget { onChanged: onSwitchChanged, activeThumbColor: enabled ? context.primaryColor : context.themeData.disabledColor, dense: true, - secondary: icon != null ? Icon(icon, color: valueNotifier.value ? context.primaryColor : null) : null, + secondary: icon != null ? Icon(icon!, color: valueNotifier.value ? context.primaryColor : null) : null, title: Text( title, style: diff --git a/mobile/lib/widgets/settings/ssl_client_cert_settings.dart b/mobile/lib/widgets/settings/ssl_client_cert_settings.dart index f37aa04b33..77ad7ee179 100644 --- a/mobile/lib/widgets/settings/ssl_client_cert_settings.dart +++ b/mobile/lib/widgets/settings/ssl_client_cert_settings.dart @@ -1,10 +1,10 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/platform/network_api.g.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:logging/logging.dart'; @@ -44,15 +44,12 @@ class _SslClientCertSettingsState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 20), horizontalTitleGap: 20, isThreeLine: true, - title: Text( - context.t.client_cert_title, - style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500), - ), + title: Text("client_cert_title".tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - context.t.client_cert_subtitle, + "client_cert_subtitle".tr(), style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary), ), const SizedBox(height: 6), @@ -61,8 +58,8 @@ class _SslClientCertSettingsState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ - ElevatedButton(onPressed: importCert, child: Text(context.t.client_cert_import)), - ElevatedButton(onPressed: !isCertExist ? null : removeCert, child: Text(context.t.remove)), + ElevatedButton(onPressed: importCert, child: Text("client_cert_import".tr())), + ElevatedButton(onPressed: !isCertExist ? null : removeCert, child: Text("remove".tr())), ], ), ], @@ -82,20 +79,20 @@ class _SslClientCertSettingsState extends State { Future importCert() async { try { final styling = ClientCertPrompt( - title: context.t.client_cert_password_title, - message: context.t.client_cert_password_message, - cancel: context.t.cancel, - confirm: context.t.confirm, + title: "client_cert_password_title".tr(), + message: "client_cert_password_message".tr(), + cancel: "cancel".tr(), + confirm: "confirm".tr(), ); await networkApi.selectCertificate(styling); setState(() => isCertExist = true); - showMessage(StaticTranslations.instance.client_cert_import_success_msg); + showMessage("client_cert_import_success_msg".tr()); } catch (e) { if (_isCancellation(e)) { return; } _log.severe("Error importing client cert", e); - showMessage(StaticTranslations.instance.client_cert_invalid_msg); + showMessage("client_cert_invalid_msg".tr()); } } @@ -103,13 +100,13 @@ class _SslClientCertSettingsState extends State { try { await networkApi.removeCertificate(); setState(() => isCertExist = false); - showMessage(StaticTranslations.instance.client_cert_remove_msg); + showMessage("client_cert_remove_msg".tr()); } catch (e) { if (_isCancellation(e)) { return; } _log.severe("Error removing client cert", e); - showMessage(StaticTranslations.instance.client_cert_invalid_msg); + showMessage("client_cert_invalid_msg".tr()); } } diff --git a/mobile/lib/widgets/shared_link/shared_link_item.dart b/mobile/lib/widgets/shared_link/shared_link_item.dart index 2471c19cea..19da80b833 100644 --- a/mobile/lib/widgets/shared_link/shared_link_item.dart +++ b/mobile/lib/widgets/shared_link/shared_link_item.dart @@ -1,140 +1,201 @@ import 'dart:math' as math; import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; import 'package:immich_mobile/models/shared_link/shared_link.model.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/shared_link.provider.dart'; import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/utils/debug_print.dart'; import 'package:immich_mobile/utils/image_url_builder.dart'; import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/widgets/search/thumbnail_with_info.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; class SharedLinkItem extends ConsumerWidget { final SharedLink sharedLink; const SharedLinkItem(this.sharedLink, {super.key}); - bool isExpired() => sharedLink.expiresAt?.isBefore(DateTime.now()) ?? false; - - Widget buildExpiryDuration(BuildContext context) { - var expiresText = context.t.shared_link_expires_never; - IconData expiryIcon = Icons.schedule; + bool isExpired() { + if (sharedLink.expiresAt != null) { + return DateTime.now().isAfter(sharedLink.expiresAt!); + } + return false; + } + Widget getExpiryDuration(bool isDarkMode) { + var expiresText = "shared_link_expires_never".tr(); if (sharedLink.expiresAt != null) { if (isExpired()) { - expiresText = context.t.expired; - expiryIcon = Icons.timer_off_outlined; + return Text("expired", style: TextStyle(color: Colors.red[300])).tr(); } - final difference = sharedLink.expiresAt!.difference(DateTime.now()); dPrint(() => "Difference: $difference"); - if (difference.inDays > 0) { var dayDifference = difference.inDays; if (difference.inHours % 24 > 12) { dayDifference += 1; } - expiresText = context.t.shared_link_expires_days(count: dayDifference); + expiresText = "shared_link_expires_days".tr(namedArgs: {'count': dayDifference.toString()}); } else if (difference.inHours > 0) { - expiresText = context.t.shared_link_expires_hours(count: difference.inHours); + expiresText = "shared_link_expires_hours".tr(namedArgs: {'count': difference.inHours.toString()}); } else if (difference.inMinutes > 0) { - expiresText = context.t.shared_link_expires_minutes(count: difference.inMinutes); + expiresText = "shared_link_expires_minutes".tr(namedArgs: {'count': difference.inMinutes.toString()}); } else if (difference.inSeconds > 0) { - expiresText = context.t.shared_link_expires_seconds(count: difference.inSeconds); + expiresText = "shared_link_expires_seconds".tr(namedArgs: {'count': difference.inSeconds.toString()}); } } - - return Row( - children: [ - Icon(expiryIcon, size: 12, color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), - const SizedBox(width: 4), - Text( - expiresText, - style: TextStyle(color: isExpired() ? context.colorScheme.error : context.colorScheme.onSurface), - ), - ], - ); + return Text(expiresText, style: TextStyle(color: isDarkMode ? Colors.grey[400] : Colors.grey[600])); } @override Widget build(BuildContext context, WidgetRef ref) { + final colorScheme = context.colorScheme; + final isDarkMode = colorScheme.brightness == Brightness.dark; final thumbnailUrl = sharedLink.thumbAssetId != null ? getThumbnailUrlForRemoteId(sharedLink.thumbAssetId!) : null; final imageSize = math.min(context.width / 4, 100.0); - Future copyShareLinkToClipboard() async { + void copyShareLinkToClipboard() { final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); - final serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); - final shareUrl = buildSharedLinkUrl(baseUrl: serverUrl, slug: sharedLink.slug, key: sharedLink.key); - - if (shareUrl == null) { + var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); + if (serverUrl != null && !serverUrl.endsWith('/')) { + serverUrl += '/'; + } + if (serverUrl == null) { ImmichToast.show( context: context, gravity: ToastGravity.BOTTOM, toastType: ToastType.error, - msg: context.t.shared_link_error_server_url_fetch, + msg: "shared_link_error_server_url_fetch".tr(), ); return; } - await Clipboard.setData(ClipboardData(text: shareUrl)); - if (!context.mounted) { - return; - } - context.scaffoldMessenger.showSnackBar( - SnackBar( - content: Text( - context.t.shared_link_clipboard_copied_massage, - style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + final hasSlug = sharedLink.slug?.isNotEmpty == true; + final urlPath = hasSlug ? sharedLink.slug : sharedLink.key; + final basePath = hasSlug ? 's' : 'share'; + Clipboard.setData(ClipboardData(text: "$serverUrl$basePath/$urlPath")).then((_) { + context.scaffoldMessenger.showSnackBar( + SnackBar( + content: Text( + "shared_link_clipboard_copied_massage", + style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor), + ).tr(), + duration: const Duration(seconds: 2), ), - duration: const Duration(seconds: 2), - ), + ); + }); + } + + Future deleteShareLink() async { + return showDialog( + context: context, + builder: (BuildContext context) { + return ConfirmDialog( + title: "delete_shared_link_dialog_title", + content: "confirm_delete_shared_link", + onOk: () => ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id), + ); + }, ); } Widget buildThumbnail() { + if (thumbnailUrl == null) { + return Container( + height: imageSize * 1.2, + width: imageSize, + decoration: BoxDecoration(color: isDarkMode ? Colors.grey[800] : Colors.grey[200]), + child: Center( + child: Icon(Icons.image_not_supported_outlined, color: isDarkMode ? Colors.grey[100] : Colors.grey[700]), + ), + ); + } return SizedBox( height: imageSize * 1.2, width: imageSize, - child: thumbnailUrl == null - ? const Card( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), - child: Icon(Icons.image_not_supported_outlined), - ) - : ThumbnailWithInfo( - imageUrl: thumbnailUrl, - key: key, - textInfo: '', - noImageIcon: Icons.image_not_supported_outlined, - onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), - ), - ); - } - - Widget buildInfoChip(String labelText) { - return Card.outlined( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), - child: Text(labelText, style: const TextStyle(fontSize: 11)), + padding: const EdgeInsets.only(right: 4.0), + child: ThumbnailWithInfo( + imageUrl: thumbnailUrl, + key: key, + textInfo: '', + noImageIcon: Icons.image_not_supported_outlined, + onTap: () {}, + ), ), ); } - Widget buildShareParameterInfos() { + Widget buildInfoChip(String labelText) { + return Padding( + padding: const EdgeInsets.only(right: 10), + child: Chip( + backgroundColor: colorScheme.primary, + label: Text( + labelText, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.black : Colors.white, + ), + ), + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(25))), + ), + ); + } + + Widget buildBottomInfo() { return Row( - spacing: 4, children: [ - if (sharedLink.allowUpload) buildInfoChip(context.t.upload), - if (sharedLink.allowDownload) buildInfoChip(context.t.download), - if (sharedLink.showMetadata) buildInfoChip(context.t.shared_link_info_chip_metadata), + if (sharedLink.allowUpload) buildInfoChip("upload".tr()), + if (sharedLink.allowDownload) buildInfoChip("download".tr()), + if (sharedLink.showMetadata) buildInfoChip("shared_link_info_chip_metadata".tr()), + ], + ); + } + + Widget buildSharedLinkActions() { + const actionIconSize = 20.0; + return Row( + children: [ + IconButton( + splashRadius: 25, + constraints: const BoxConstraints(), + iconSize: actionIconSize, + icon: const Icon(Icons.delete_outline), + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part + ), + onPressed: deleteShareLink, + ), + IconButton( + splashRadius: 25, + constraints: const BoxConstraints(), + iconSize: actionIconSize, + icon: const Icon(Icons.edit_outlined), + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part + ), + onPressed: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), + ), + IconButton( + splashRadius: 25, + constraints: const BoxConstraints(), + iconSize: actionIconSize, + icon: const Icon(Icons.copy_outlined), + style: const ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, // the '2023' part + ), + onPressed: copyShareLinkToClipboard, + ), ], ); } @@ -143,64 +204,69 @@ class SharedLinkItem extends ConsumerWidget { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox(height: 5), - Text( - sharedLink.title, - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, + getExpiryDuration(isDarkMode), + Padding( + padding: const EdgeInsets.only(top: 5), + child: Tooltip( + verticalOffset: 0, + decoration: BoxDecoration( + color: colorScheme.primary.withValues(alpha: 0.9), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), + message: sharedLink.title, + preferBelow: false, + triggerMode: TooltipTriggerMode.tap, + child: Text( + sharedLink.title, + style: TextStyle( + color: colorScheme.primary, + fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, + ), + ), ), ), - if (sharedLink.description?.isNotEmpty ?? false) - Text(sharedLink.description!, overflow: TextOverflow.ellipsis), - buildExpiryDuration(context), - buildShareParameterInfos(), + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Tooltip( + verticalOffset: 0, + decoration: BoxDecoration( + color: colorScheme.primary.withValues(alpha: 0.9), + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + textStyle: TextStyle(color: isDarkMode ? Colors.black : Colors.white, fontWeight: FontWeight.bold), + message: sharedLink.description ?? "", + preferBelow: false, + triggerMode: TooltipTriggerMode.tap, + child: Text(sharedLink.description ?? "", overflow: TextOverflow.ellipsis), + ), + ), + Padding(padding: const EdgeInsets.only(right: 15), child: buildSharedLinkActions()), + ], + ), + buildBottomInfo(), ], ); } - return Dismissible( - key: ValueKey(sharedLink.id), - direction: DismissDirection.endToStart, - background: Container( - color: Theme.of(context).colorScheme.error, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - child: Icon(Icons.delete, color: Theme.of(context).colorScheme.onError), - ), - confirmDismiss: (_) async { - final confirmed = await showDialog( - context: context, - builder: (BuildContext context) => ConfirmDialog( - title: context.t.delete_shared_link_dialog_title, - content: context.t.confirm_delete_shared_link, - onOk: () {}, - ), - ); - - if (confirmed == true && context.mounted) { - await ref.read(sharedLinksStateProvider.notifier).deleteLink(sharedLink.id); - return true; - } - - return false; - }, - child: InkWell( - onTap: () => context.pushRoute(SharedLinkEditRoute(existingLink: sharedLink)), - onLongPress: copyShareLinkToClipboard, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildThumbnail(), - const SizedBox(width: 12), - Expanded(child: buildSharedLinkDetails()), - ], - ), + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding(padding: const EdgeInsets.only(left: 15), child: buildThumbnail()), + Expanded( + child: Padding(padding: const EdgeInsets.only(left: 15), child: buildSharedLinkDetails()), + ), + ], ), - ), + const Padding(padding: EdgeInsets.all(20), child: Divider(height: 0)), + ], ); } } diff --git a/mobile/lib/wm_executor.dart b/mobile/lib/wm_executor.dart index aac6d3e174..73e882e8e6 100644 --- a/mobile/lib/wm_executor.dart +++ b/mobile/lib/wm_executor.dart @@ -6,8 +6,8 @@ import 'dart:math'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; -import 'package:immich_mobile/utils/isolate_worker.dart'; import 'package:worker_manager/src/number_of_processors/processors_io.dart'; +import 'package:worker_manager/src/worker/worker.dart'; import 'package:worker_manager/worker_manager.dart'; final workerManagerPatch = _Executor(); @@ -16,13 +16,6 @@ final workerManagerPatch = _Executor(); const _minId = -9007199254740992; const _maxId = 9007199254740992; -class _GentleTask extends Task implements Gentle { - @override - final GentleExecution execution; - - _GentleTask({required super.id, required super.completer, required super.workPriority, required this.execution}); -} - class Mixinable { late final itSelf = this as T; } @@ -50,22 +43,17 @@ mixin _ExecutorLogger on Mixinable<_Executor> { } void logMessage(String message) { - if (log) { - print(message); - } + if (log) print(message); } } class _Executor extends Mixinable<_Executor> with _ExecutorLogger { final _queue = PriorityQueue(); - final _pool = []; + final _pool = []; var _nextTaskId = _minId; var _dynamicSpawning = false; var _isolatesCount = numberOfProcessors; - @visibleForTesting - UnmodifiableListView get pool => UnmodifiableListView(_pool); - @override Future init({int? isolatesCount, bool? dynamicSpawning}) async { if (_pool.isNotEmpty) { @@ -87,30 +75,69 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { @override Future dispose() async { _queue.clear(); - final shutdown = _pool.map((worker) => worker.shutdown()).toList(growable: false); + for (final worker in _pool) { + worker.kill(); + } _pool.clear(); - await Future.wait(shutdown); super.dispose(); } - /// Runs [execution] on a worker isolate; its [Completer] completes when the - /// returned [Cancelable] is cancelled. - Cancelable executeGentle(GentleExecution execution, {WorkPriority priority = WorkPriority.immediately}) { - if (_nextTaskId + 1 == _maxId) { - _nextTaskId = _minId; + Cancelable execute(Execute execution, {WorkPriority priority = WorkPriority.immediately}) { + return _createCancelable(execution: execution, priority: priority); + } + + Cancelable executeNow(ExecuteGentle execution) { + final task = TaskGentle( + id: "", + workPriority: WorkPriority.immediately, + execution: execution, + completer: Completer(), + ); + + Future run() async { + try { + final result = await execution(() => task.canceled); + task.complete(result, null, null); + } catch (error, st) { + task.complete(null, error, st); + } } - final id = _nextTaskId.toString(); - _nextTaskId++; - final task = _GentleTask(id: id, workPriority: priority, execution: execution, completer: Completer()); - _queue.add(task); - _schedule(); - logTaskAdded(task.id); + + run(); return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); } + Cancelable executeWithPort( + ExecuteWithPort execution, { + WorkPriority priority = WorkPriority.immediately, + required void Function(T value) onMessage, + }) { + return _createCancelable( + execution: execution, + priority: priority, + onMessage: (message) => onMessage(message as T), + ); + } + + Cancelable executeGentle(ExecuteGentle execution, {WorkPriority priority = WorkPriority.immediately}) { + return _createCancelable(execution: execution, priority: priority); + } + + Cancelable executeGentleWithPort( + ExecuteGentleWithPort execution, { + WorkPriority priority = WorkPriority.immediately, + required void Function(T value) onMessage, + }) { + return _createCancelable( + execution: execution, + priority: priority, + onMessage: (message) => onMessage(message as T), + ); + } + void _createWorkers() { for (var i = 0; i < _isolatesCount; i++) { - _pool.add(IsolateWorker()); + _pool.add(Worker()); } } @@ -118,6 +145,45 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { await Future.wait(_pool.map((e) => e.initialize())); } + Cancelable _createCancelable({ + required Function execution, + WorkPriority priority = WorkPriority.immediately, + void Function(Object value)? onMessage, + }) { + if (_nextTaskId + 1 == _maxId) { + _nextTaskId = _minId; + } + final id = _nextTaskId.toString(); + _nextTaskId++; + late final Task task; + final completer = Completer(); + if (execution is Execute) { + task = TaskRegular(id: id, workPriority: priority, execution: execution, completer: completer); + } else if (execution is ExecuteWithPort) { + task = TaskWithPort( + id: id, + workPriority: priority, + execution: execution, + completer: completer, + onMessage: onMessage!, + ); + } else if (execution is ExecuteGentle) { + task = TaskGentle(id: id, workPriority: priority, execution: execution, completer: completer); + } else if (execution is ExecuteGentleWithPort) { + task = TaskGentleWithPort( + id: id, + workPriority: priority, + execution: execution, + completer: completer, + onMessage: onMessage!, + ); + } + _queue.add(task); + _schedule(); + logTaskAdded(task.id); + return Cancelable(completer: task.completer, onCancel: () => _cancel(task)); + } + Future _ensureWorkersInitialized() async { if (_pool.isEmpty) { _createWorkers(); @@ -133,7 +199,7 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { if (_pool.every((worker) => worker.taskId != null)) { return; } - if (_dynamicSpawning && _queue.isNotEmpty) { + if (_dynamicSpawning) { final freeWorker = _pool.firstWhereOrNull( (worker) => worker.taskId == null && !worker.initialized && !worker.initializing, ); @@ -145,44 +211,41 @@ class _Executor extends Mixinable<_Executor> with _ExecutorLogger { void _schedule() { final availableWorker = _pool.firstWhereOrNull((worker) => worker.taskId == null && worker.initialized); if (availableWorker == null) { - unawaited(_ensureWorkersInitialized()); - return; - } - if (_queue.isEmpty) { + _ensureWorkersInitialized(); return; } + if (_queue.isEmpty) return; final task = _queue.removeFirst(); - unawaited( - availableWorker - .work(task) - .then( - (value) { - //might be completed by cancel and it is normal. - //Assuming that worker finished with error and cleaned gracefully - task.complete(value, null, null); - }, - onError: (error, st) { - task.complete(null, error, st); - }, - ) - .whenComplete(() { - if (_dynamicSpawning && _queue.isEmpty) { - // Retire the idle worker; shutdown() nulls its fields so the husk - // stays pooled and is revived by initialize() if work arrives. - unawaited(availableWorker.shutdown()); - } - _schedule(); - }), - ); + availableWorker + .work(task) + .then( + (value) { + //could be completed already by cancel and it is normal. + //Assuming that worker finished with error and cleaned gracefully + task.complete(value, null, null); + }, + onError: (error, st) { + task.complete(null, error, st); + }, + ) + .whenComplete(() { + if (_dynamicSpawning && _queue.isEmpty) availableWorker.kill(); + _schedule(); + }); } @override void _cancel(Task task) { task.cancel(); _queue.remove(task); - // All tasks are gentle: signal cancellation; the worker unwinds on its own. - _pool.firstWhereOrNull((worker) => worker.taskId == task.id)?.cancelGentle(); + final targetWorker = _pool.firstWhereOrNull((worker) => worker.taskId == task.id); + if (task is Gentle) { + targetWorker?.cancelGentle(); + } else { + targetWorker?.kill(); + if (!_dynamicSpawning) targetWorker?.initialize(); + } super._cancel(task); } } diff --git a/mobile/makefile b/mobile/makefile new file mode 100644 index 0000000000..3a0a263687 --- /dev/null +++ b/mobile/makefile @@ -0,0 +1,55 @@ +.PHONY: build watch create_app_icon create_splash build_release_android pigeon test analyze format + +build: + dart run build_runner build --delete-conflicting-outputs +# Remove once auto_route updated to 10.1.0 + dart format lib/routing/router.gr.dart + +pigeon: + dart run pigeon --input pigeon/native_sync_api.dart + dart run pigeon --input pigeon/local_image_api.dart + dart run pigeon --input pigeon/remote_image_api.dart + dart run pigeon --input pigeon/background_worker_api.dart + dart run pigeon --input pigeon/background_worker_lock_api.dart + dart run pigeon --input pigeon/connectivity_api.dart + dart run pigeon --input pigeon/network_api.dart + dart format lib/platform/native_sync_api.g.dart + dart format lib/platform/local_image_api.g.dart + dart format lib/platform/remote_image_api.g.dart + dart format lib/platform/background_worker_api.g.dart + dart format lib/platform/background_worker_lock_api.g.dart + dart format lib/platform/connectivity_api.g.dart + dart format lib/platform/network_api.g.dart + +watch: + dart run build_runner watch --delete-conflicting-outputs + +create_app_icon: + flutter pub run flutter_launcher_icons:main + +create_splash: + flutter pub run flutter_native_splash:create + +build_release_android: + flutter build appbundle + +migration: + dart run drift_dev make-migrations + +translation: + pnpm --prefix ../i18n run format:fix + dart run easy_localization:generate -S ../i18n + dart run bin/generate_keys.dart + dart format lib/generated/codegen_loader.g.dart + dart format lib/generated/translations.g.dart + +analyze: + dart analyze --fatal-infos + dcm analyze lib --fatal-style --fatal-warnings + +format: +# Ignore generated files manually until https://github.com/dart-lang/dart_style/issues/864 is resolved + dart format --set-exit-if-changed $$(find lib -name '*.dart' -not \( -name 'generated_plugin_registrant.dart' -o -name '*.g.dart' -o -name '*.drift.dart' \)) + +test: + flutter test diff --git a/mobile/mise.lock b/mobile/mise.lock deleted file mode 100644 index cd3ac25848..0000000000 --- a/mobile/mise.lock +++ /dev/null @@ -1,128 +0,0 @@ -# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html - -[[tools."aqua:flutter/flutter"]] -version = "3.44.9" -backend = "aqua:flutter/flutter" - -[tools."aqua:flutter/flutter"."platforms.linux-arm64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.9-stable.tar.xz" - -[tools."aqua:flutter/flutter"."platforms.linux-arm64-musl"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.9-stable.tar.xz" - -[tools."aqua:flutter/flutter"."platforms.linux-x64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.9-stable.tar.xz" - -[tools."aqua:flutter/flutter"."platforms.linux-x64-musl"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.44.9-stable.tar.xz" - -[tools."aqua:flutter/flutter"."platforms.macos-arm64"] -checksum = "blake3:e5c14d6b5e917a8578327d2564ab78ece83cd685c74c29572ddaf7655f42205d" -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_arm64_3.44.9-stable.zip" - -[tools."aqua:flutter/flutter"."platforms.macos-x64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/macos/flutter_macos_3.44.9-stable.zip" - -[tools."aqua:flutter/flutter"."platforms.windows-x64"] -url = "https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_3.44.9-stable.zip" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-linux-arm-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64"] -checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-arm64-musl"] -checksum = "sha256:253da2512b149913dfe345bf9a62a79acb2d730f66e71162ba4a92dfc4224b82" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543838" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-linux-x64-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64"] -checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" - -[tools."github:CQLabs/homebrew-dcm"."platforms.linux-x64-musl"] -checksum = "sha256:477e086d4099c12f21e5ccd83b005d5fb945dd4cac4fd127fd9a08d7649af1cf" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-linux-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543797" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-macos-arm-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.macos-arm64"] -checksum = "sha256:30bede64367d09067093cc57af6ec9496d7717898138ded5cb98a16ac8dd9d93" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-arm-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543757" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-macos-x64-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.macos-x64"] -checksum = "sha256:e56cb99872be7445a4de1d37e5438ca70e3bcd83be7a2b9b385e3538881f8068" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-macos-x64-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543727" - -[[tools."github:CQLabs/homebrew-dcm"]] -version = "1.37.0" -backend = "github:CQLabs/homebrew-dcm" - -[tools."github:CQLabs/homebrew-dcm".options] -asset_pattern = "dcm-windows-release.zip" - -[tools."github:CQLabs/homebrew-dcm"."platforms.windows-x64"] -checksum = "sha256:f133470daa3fb0427f039b424392af7e917d7e7db6b556aa2a968ab0e31587da" -url = "https://github.com/CQLabs/homebrew-dcm/releases/download/1.37.0/dcm-windows-release.zip" -url_api = "https://api.github.com/repos/CQLabs/homebrew-dcm/releases/assets/404543660" - -[[tools.java]] -version = "21.0.2" -backend = "core:java" - -[tools.java.options] -shorthand_vendor = "openjdk" - -[tools.java."platforms.linux-arm64"] -checksum = "sha256:08db1392a48d4eb5ea5315cf8f18b89dbaf36cda663ba882cf03c704c9257ec2" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-aarch64_bin.tar.gz" - -[tools.java."platforms.linux-x64"] -checksum = "sha256:a2def047a73941e01a73739f92755f86b895811afb1f91243db214cff5bdac3f" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_linux-x64_bin.tar.gz" - -[tools.java."platforms.macos-arm64"] -checksum = "sha256:b3d588e16ec1e0ef9805d8a696591bd518a5cea62567da8f53b5ce32d11d22e4" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-aarch64_bin.tar.gz" - -[tools.java."platforms.macos-x64"] -checksum = "sha256:8fd09e15dc406387a0aba70bf5d99692874e999bf9cd9208b452b5d76ac922d3" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz" - -[tools.java."platforms.windows-x64"] -checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882664" -url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip" - -[[tools."npm:@openapitools/openapi-generator-cli"]] -version = "2.40.1" -backend = "npm:@openapitools/openapi-generator-cli" diff --git a/mobile/mise.toml b/mobile/mise.toml index 348ce1a0a5..88b8902053 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -1,35 +1,13 @@ [tools] -java = "21.0.2" - -[tools."aqua:flutter/flutter"] -version = "3.44.9" -postinstall = "bash {{config_root}}/ios/scripts/xcode_flutter_patch.sh" +flutter = "3.35.7" [tools."github:CQLabs/homebrew-dcm"] -version = "1.37.0" +version = "1.30.0" bin = "dcm" -postinstall = "chmod +x \"$MISE_TOOL_INSTALL_PATH/dcm\" || true" - -[tools."github:CQLabs/homebrew-dcm".platforms] -linux-x64 = { asset_pattern = "dcm-linux-x64-release.zip" } -linux-arm64 = { asset_pattern = "dcm-linux-arm-release.zip" } -macos-x64 = { asset_pattern = "dcm-macos-x64-release.zip" } -macos-arm64 = { asset_pattern = "dcm-macos-arm-release.zip" } -windows-x64 = { asset_pattern = "dcm-windows-release.zip" } - -[tasks.codegen] -alias = "codegen" -description = "Generate all codegen artifacts" -depends = [ - "//:open-api-dart", - "codegen:dart", - "codegen:drift:migration", - "codegen:drift:schema", - "codegen:pigeon", - "codegen:translation", -] +postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm" [tasks."codegen:dart"] +alias = "codegen" description = "Execute build_runner to auto-generate dart code" sources = [ "pubspec.yaml", @@ -39,53 +17,30 @@ sources = [ ] outputs = { auto = true } run = [ - "dart run build_runner build", + "dart run build_runner build --delete-conflicting-outputs", "dart format lib/routing/router.gr.dart", ] -depends = ["//:open-api-dart"] -wait_for = ["codegen:drift:migration"] - -[tasks."codegen:drift:migration"] -alias = "migration" -description = "Generate Drift migrations" -sources = [ - "build.yaml", - "pubspec.yaml", - "drift_schemas/main/*.json", - "lib/infrastructure/entities/*.entity.dart", - "lib/infrastructure/entities/*.drift", - "lib/infrastructure/repositories/*.repository.dart", -] -outputs = ["lib/infrastructure/repositories/db.repository.steps.dart"] -run = "dart run drift_dev make-migrations" -depends = ["//:open-api-dart"] - -[tasks."codegen:drift:schema"] -description = "Generate Drift migration schema test code" -sources = ["drift_schemas/main/*.json"] -outputs = { auto = true } -run = "dart run drift_dev schema generate --data-classes --companions drift_schemas/main/ test/drift/main/generated/" -depends = ["//:open-api-dart"] -wait_for = ["codegen:drift:migration"] [tasks."codegen:watch"] alias = "watch" description = "Watch and auto-generate dart code" -run = "dart run build_runner watch" +run = "dart run build_runner watch --delete-conflicting-outputs" [tasks."codegen:pigeon"] alias = "pigeon" description = "Generate pigeon platform code" -run = [ - "ls pigeon/*.dart | xargs -n1 -P4 -I{} dart run pigeon --input {}", - "dart format lib/platform/", +depends = [ + "pigeon:native-sync", + "pigeon:thumbnail", + "pigeon:background-worker", + "pigeon:background-worker-lock", + "pigeon:connectivity", ] -depends = ["//:open-api-dart"] [tasks."codegen:translation"] alias = "translation" description = "Generate translations from i18n JSONs" -depends = ["//:i18n:format-fix", "i18n:loader", "i18n:keys"] +run = [{ task = "//:i18n:format-fix" }, { tasks = ["i18n:loader", "i18n:keys"] }] [tasks."codegen:app-icon"] description = "Generate app icons" @@ -99,15 +54,13 @@ run = "flutter pub run flutter_native_splash:create" description = "Run mobile tests" run = "flutter test" -[tasks.analyze] -alias = "lint" +[tasks.lint] description = "Analyze Dart code" depends = ["analyze:dart", "analyze:dcm"] -[tasks."analyze-fix"] -alias = "lint-fix" +[tasks."lint-fix"] description = "Auto-fix Dart code" -depends = ["analyze-fix:dart", "analyze-fix:dcm"] +depends = ["analyze:fix:dart", "analyze:fix:dcm"] [tasks.format] description = "Format Dart code" @@ -117,71 +70,101 @@ run = "dart format --set-exit-if-changed $(find lib -name '*.dart' -not \\( -nam description = "Build Android release" run = "flutter build appbundle" -[tasks.install] -alias = "install" -description = "Install flutter dependencies" -depends = ["//:open-api-dart"] -run = [ - # This receives passed `--enforce-lockfile` args - "flutter pub get", - { task = "install:ios" }, -] +[tasks."drift:migration"] +alias = "migration" +description = "Generate database migrations" +run = "dart run drift_dev make-migrations" -[tasks."install:ci"] -description = "Install flutter dependencies in CI" -run = [ - { task = "install", args = ["--enforce-lockfile"] }, -] - -[tasks.checkout] -description = "Bring a checkout up to date after switching branches" -run = [ - { task = "install" }, - { task = "checkout:ios" }, - { task = "codegen" }, -] - -[tasks."checkout:ios"] -description = "Bring the iOS project up to date after switching branches" -run = "bash ./scripts/checkout_ios.sh" - -[tasks.start] -alias = "start" -description = "Start flutter app" -run = "flutter run" # Internal tasks +[tasks."pigeon:native-sync"] +description = "Generate native sync API pigeon code" +hide = true +sources = ["pigeon/native_sync_api.dart"] +outputs = [ + "lib/platform/native_sync_api.g.dart", + "ios/Runner/Sync/Messages.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/sync/Messages.g.kt", +] +run = [ + "dart run pigeon --input pigeon/native_sync_api.dart", + "dart format lib/platform/native_sync_api.g.dart", +] + +[tasks."pigeon:thumbnail"] +description = "Generate thumbnail API pigeon code" +hide = true +sources = ["pigeon/thumbnail_api.dart"] +outputs = [ + "lib/platform/thumbnail_api.g.dart", + "ios/Runner/Images/Thumbnails.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/images/Thumbnails.g.kt", +] +run = [ + "dart run pigeon --input pigeon/thumbnail_api.dart", + "dart format lib/platform/thumbnail_api.g.dart", +] + +[tasks."pigeon:background-worker"] +description = "Generate background worker API pigeon code" +hide = true +sources = ["pigeon/background_worker_api.dart"] +outputs = [ + "lib/platform/background_worker_api.g.dart", + "ios/Runner/Background/BackgroundWorker.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorker.g.kt", +] +run = [ + "dart run pigeon --input pigeon/background_worker_api.dart", + "dart format lib/platform/background_worker_api.g.dart", +] + +[tasks."pigeon:background-worker-lock"] +description = "Generate background worker lock API pigeon code" +hide = true +sources = ["pigeon/background_worker_lock_api.dart"] +outputs = [ + "lib/platform/background_worker_lock_api.g.dart", + "android/app/src/main/kotlin/app/alextran/immich/background/BackgroundWorkerLock.g.kt", +] +run = [ + "dart run pigeon --input pigeon/background_worker_lock_api.dart", + "dart format lib/platform/background_worker_lock_api.g.dart", +] + +[tasks."pigeon:connectivity"] +description = "Generate connectivity API pigeon code" +hide = true +sources = ["pigeon/connectivity_api.dart"] +outputs = [ + "lib/platform/connectivity_api.g.dart", + "ios/Runner/Connectivity/Connectivity.g.swift", + "android/app/src/main/kotlin/app/alextran/immich/connectivity/Connectivity.g.kt", +] +run = [ + "dart run pigeon --input pigeon/connectivity_api.dart", + "dart format lib/platform/connectivity_api.g.dart", +] + [tasks."i18n:loader"] description = "Generate i18n loader" hide = true +sources = ["i18n/"] +outputs = "lib/generated/codegen_loader.g.dart" run = [ "dart run easy_localization:generate -S ../i18n", "dart format lib/generated/codegen_loader.g.dart", ] -depends = ["//:open-api-dart"] -wait_for = ["//:i18n:format-fix"] [tasks."i18n:keys"] description = "Generate i18n keys" hide = true +sources = ["i18n/en.json"] +outputs = "lib/generated/translations.g.dart" run = [ "dart run bin/generate_keys.dart", "dart format lib/generated/translations.g.dart", ] -wait_for = ["//:i18n:format-fix"] - -[tasks."install:ios"] -description = "Install CocoaPods dependencies" -hide = true -sources = ["ios/Podfile", "ios/Podfile.lock", ".flutter-plugins-dependencies"] -outputs = ["ios/Pods/Manifest.lock"] -run = ''' -echo "Running pod install" -if [ "$(uname)" != "Darwin" ]; then - exit 0 -fi -cd ios && pod install -''' [tasks."analyze:dart"] description = "Run Dart analysis" @@ -193,23 +176,12 @@ description = "Run Dart Code Metrics" hide = true run = "dcm analyze lib --fatal-style --fatal-warnings" -[tasks."analyze-fix:dart"] +[tasks."analyze:fix:dart"] description = "Auto-fix Dart analysis" hide = true run = "dart fix --apply" -[tasks."analyze-fix:dcm"] +[tasks."analyze:fix:dcm"] description = "Auto-fix Dart Code Metrics" hide = true run = "dcm fix lib" - - -[tasks.checklist] -run = [ - { task = "codegen:pigeon" }, - { task = "codegen:dart" }, - { task = "codegen:translation" }, - { task = "analyze" }, - { task = "format" }, - { task = "test" }, -] diff --git a/mobile/openapi/.gitignore b/mobile/openapi/.gitignore new file mode 100644 index 0000000000..0f74d293b9 --- /dev/null +++ b/mobile/openapi/.gitignore @@ -0,0 +1,19 @@ +# See https://dart.dev/guides/libraries/private-files + +.dart_tool/ +.packages +build/ + +# Except for application packages +pubspec.lock + +doc/api/ + +# IntelliJ +*.iml +*.ipr +*.iws +.idea/ + +# Mac +.DS_Store diff --git a/mobile/openapi/.openapi-generator-ignore b/mobile/openapi/.openapi-generator-ignore new file mode 100644 index 0000000000..7484ee590a --- /dev/null +++ b/mobile/openapi/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/mobile/openapi/.openapi-generator/VERSION b/mobile/openapi/.openapi-generator/VERSION new file mode 100644 index 0000000000..09a6d30847 --- /dev/null +++ b/mobile/openapi/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.8.0 diff --git a/mobile/openapi/.travis.yml b/mobile/openapi/.travis.yml new file mode 100644 index 0000000000..2774ccbba0 --- /dev/null +++ b/mobile/openapi/.travis.yml @@ -0,0 +1,14 @@ +# +# AUTO-GENERATED FILE, DO NOT MODIFY! +# +# https://docs.travis-ci.com/user/languages/dart/ +# +language: dart +dart: +# Install a specific stable release +- "2.12" +install: +- pub get + +script: +- pub run test diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md new file mode 100644 index 0000000000..6de228aecd --- /dev/null +++ b/mobile/openapi/README.md @@ -0,0 +1,714 @@ +# openapi +Immich API + +This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: + +- API version: 2.7.3 +- Generator version: 7.8.0 +- Build package: org.openapitools.codegen.languages.DartClientCodegen + +## Requirements + +Dart 2.12 or later + +## Installation & Usage + +### Github +If this Dart package is published to Github, add the following dependency to your pubspec.yaml +``` +dependencies: + openapi: + git: https://github.com/GIT_USER_ID/GIT_REPO_ID.git +``` + +### Local +To use the package in your local drive, add the following dependency to your pubspec.yaml +``` +dependencies: + openapi: + path: /path/to/openapi +``` + +## Tests + +TODO + +## Getting Started + +Please follow the [installation procedure](#installation--usage) and then run the following: + +```dart +import 'package:openapi/api.dart'; + +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = APIKeysApi(); +final aPIKeyCreateDto = APIKeyCreateDto(); // APIKeyCreateDto | + +try { + final result = api_instance.createApiKey(aPIKeyCreateDto); + print(result); +} catch (e) { + print('Exception when calling APIKeysApi->createApiKey: $e\n'); +} + +``` + +## Documentation for API Endpoints + +All URIs are relative to */api* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*APIKeysApi* | [**createApiKey**](doc//APIKeysApi.md#createapikey) | **POST** /api-keys | Create an API key +*APIKeysApi* | [**deleteApiKey**](doc//APIKeysApi.md#deleteapikey) | **DELETE** /api-keys/{id} | Delete an API key +*APIKeysApi* | [**getApiKey**](doc//APIKeysApi.md#getapikey) | **GET** /api-keys/{id} | Retrieve an API key +*APIKeysApi* | [**getApiKeys**](doc//APIKeysApi.md#getapikeys) | **GET** /api-keys | List all API keys +*APIKeysApi* | [**getMyApiKey**](doc//APIKeysApi.md#getmyapikey) | **GET** /api-keys/me | Retrieve the current API key +*APIKeysApi* | [**updateApiKey**](doc//APIKeysApi.md#updateapikey) | **PUT** /api-keys/{id} | Update an API key +*ActivitiesApi* | [**createActivity**](doc//ActivitiesApi.md#createactivity) | **POST** /activities | Create an activity +*ActivitiesApi* | [**deleteActivity**](doc//ActivitiesApi.md#deleteactivity) | **DELETE** /activities/{id} | Delete an activity +*ActivitiesApi* | [**getActivities**](doc//ActivitiesApi.md#getactivities) | **GET** /activities | List all activities +*ActivitiesApi* | [**getActivityStatistics**](doc//ActivitiesApi.md#getactivitystatistics) | **GET** /activities/statistics | Retrieve activity statistics +*AlbumsApi* | [**addAssetsToAlbum**](doc//AlbumsApi.md#addassetstoalbum) | **PUT** /albums/{id}/assets | Add assets to an album +*AlbumsApi* | [**addAssetsToAlbums**](doc//AlbumsApi.md#addassetstoalbums) | **PUT** /albums/assets | Add assets to albums +*AlbumsApi* | [**addUsersToAlbum**](doc//AlbumsApi.md#adduserstoalbum) | **PUT** /albums/{id}/users | Share album with users +*AlbumsApi* | [**createAlbum**](doc//AlbumsApi.md#createalbum) | **POST** /albums | Create an album +*AlbumsApi* | [**deleteAlbum**](doc//AlbumsApi.md#deletealbum) | **DELETE** /albums/{id} | Delete an album +*AlbumsApi* | [**getAlbumInfo**](doc//AlbumsApi.md#getalbuminfo) | **GET** /albums/{id} | Retrieve an album +*AlbumsApi* | [**getAlbumStatistics**](doc//AlbumsApi.md#getalbumstatistics) | **GET** /albums/statistics | Retrieve album statistics +*AlbumsApi* | [**getAllAlbums**](doc//AlbumsApi.md#getallalbums) | **GET** /albums | List all albums +*AlbumsApi* | [**removeAssetFromAlbum**](doc//AlbumsApi.md#removeassetfromalbum) | **DELETE** /albums/{id}/assets | Remove assets from an album +*AlbumsApi* | [**removeUserFromAlbum**](doc//AlbumsApi.md#removeuserfromalbum) | **DELETE** /albums/{id}/user/{userId} | Remove user from album +*AlbumsApi* | [**updateAlbumInfo**](doc//AlbumsApi.md#updatealbuminfo) | **PATCH** /albums/{id} | Update an album +*AlbumsApi* | [**updateAlbumUser**](doc//AlbumsApi.md#updatealbumuser) | **PUT** /albums/{id}/user/{userId} | Update user role +*AssetsApi* | [**checkBulkUpload**](doc//AssetsApi.md#checkbulkupload) | **POST** /assets/bulk-upload-check | Check bulk upload +*AssetsApi* | [**checkExistingAssets**](doc//AssetsApi.md#checkexistingassets) | **POST** /assets/exist | Check existing assets +*AssetsApi* | [**copyAsset**](doc//AssetsApi.md#copyasset) | **PUT** /assets/copy | Copy asset +*AssetsApi* | [**deleteAssetMetadata**](doc//AssetsApi.md#deleteassetmetadata) | **DELETE** /assets/{id}/metadata/{key} | Delete asset metadata by key +*AssetsApi* | [**deleteAssets**](doc//AssetsApi.md#deleteassets) | **DELETE** /assets | Delete assets +*AssetsApi* | [**deleteBulkAssetMetadata**](doc//AssetsApi.md#deletebulkassetmetadata) | **DELETE** /assets/metadata | Delete asset metadata +*AssetsApi* | [**downloadAsset**](doc//AssetsApi.md#downloadasset) | **GET** /assets/{id}/original | Download original asset +*AssetsApi* | [**editAsset**](doc//AssetsApi.md#editasset) | **PUT** /assets/{id}/edits | Apply edits to an existing asset +*AssetsApi* | [**getAllUserAssetsByDeviceId**](doc//AssetsApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID +*AssetsApi* | [**getAssetEdits**](doc//AssetsApi.md#getassetedits) | **GET** /assets/{id}/edits | Retrieve edits for an existing asset +*AssetsApi* | [**getAssetInfo**](doc//AssetsApi.md#getassetinfo) | **GET** /assets/{id} | Retrieve an asset +*AssetsApi* | [**getAssetMetadata**](doc//AssetsApi.md#getassetmetadata) | **GET** /assets/{id}/metadata | Get asset metadata +*AssetsApi* | [**getAssetMetadataByKey**](doc//AssetsApi.md#getassetmetadatabykey) | **GET** /assets/{id}/metadata/{key} | Retrieve asset metadata by key +*AssetsApi* | [**getAssetOcr**](doc//AssetsApi.md#getassetocr) | **GET** /assets/{id}/ocr | Retrieve asset OCR data +*AssetsApi* | [**getAssetStatistics**](doc//AssetsApi.md#getassetstatistics) | **GET** /assets/statistics | Get asset statistics +*AssetsApi* | [**getRandom**](doc//AssetsApi.md#getrandom) | **GET** /assets/random | Get random assets +*AssetsApi* | [**playAssetVideo**](doc//AssetsApi.md#playassetvideo) | **GET** /assets/{id}/video/playback | Play asset video +*AssetsApi* | [**removeAssetEdits**](doc//AssetsApi.md#removeassetedits) | **DELETE** /assets/{id}/edits | Remove edits from an existing asset +*AssetsApi* | [**replaceAsset**](doc//AssetsApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset +*AssetsApi* | [**runAssetJobs**](doc//AssetsApi.md#runassetjobs) | **POST** /assets/jobs | Run an asset job +*AssetsApi* | [**updateAsset**](doc//AssetsApi.md#updateasset) | **PUT** /assets/{id} | Update an asset +*AssetsApi* | [**updateAssetMetadata**](doc//AssetsApi.md#updateassetmetadata) | **PUT** /assets/{id}/metadata | Update asset metadata +*AssetsApi* | [**updateAssets**](doc//AssetsApi.md#updateassets) | **PUT** /assets | Update assets +*AssetsApi* | [**updateBulkAssetMetadata**](doc//AssetsApi.md#updatebulkassetmetadata) | **PUT** /assets/metadata | Upsert asset metadata +*AssetsApi* | [**uploadAsset**](doc//AssetsApi.md#uploadasset) | **POST** /assets | Upload asset +*AssetsApi* | [**viewAsset**](doc//AssetsApi.md#viewasset) | **GET** /assets/{id}/thumbnail | View asset thumbnail +*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password | Change password +*AuthenticationApi* | [**changePinCode**](doc//AuthenticationApi.md#changepincode) | **PUT** /auth/pin-code | Change pin code +*AuthenticationApi* | [**finishOAuth**](doc//AuthenticationApi.md#finishoauth) | **POST** /oauth/callback | Finish OAuth +*AuthenticationApi* | [**getAuthStatus**](doc//AuthenticationApi.md#getauthstatus) | **GET** /auth/status | Retrieve auth status +*AuthenticationApi* | [**linkOAuthAccount**](doc//AuthenticationApi.md#linkoauthaccount) | **POST** /oauth/link | Link OAuth account +*AuthenticationApi* | [**lockAuthSession**](doc//AuthenticationApi.md#lockauthsession) | **POST** /auth/session/lock | Lock auth session +*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login | Login +*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout | Logout +*AuthenticationApi* | [**redirectOAuthToMobile**](doc//AuthenticationApi.md#redirectoauthtomobile) | **GET** /oauth/mobile-redirect | Redirect OAuth to mobile +*AuthenticationApi* | [**resetPinCode**](doc//AuthenticationApi.md#resetpincode) | **DELETE** /auth/pin-code | Reset pin code +*AuthenticationApi* | [**setupPinCode**](doc//AuthenticationApi.md#setuppincode) | **POST** /auth/pin-code | Setup pin code +*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up | Register admin +*AuthenticationApi* | [**startOAuth**](doc//AuthenticationApi.md#startoauth) | **POST** /oauth/authorize | Start OAuth +*AuthenticationApi* | [**unlinkOAuthAccount**](doc//AuthenticationApi.md#unlinkoauthaccount) | **POST** /oauth/unlink | Unlink OAuth account +*AuthenticationApi* | [**unlockAuthSession**](doc//AuthenticationApi.md#unlockauthsession) | **POST** /auth/session/unlock | Unlock auth session +*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken | Validate access token +*AuthenticationAdminApi* | [**unlinkAllOAuthAccountsAdmin**](doc//AuthenticationAdminApi.md#unlinkalloauthaccountsadmin) | **POST** /admin/auth/unlink-all | Unlink all OAuth accounts +*DatabaseBackupsAdminApi* | [**deleteDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#deletedatabasebackup) | **DELETE** /admin/database-backups | Delete database backup +*DatabaseBackupsAdminApi* | [**downloadDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#downloaddatabasebackup) | **GET** /admin/database-backups/{filename} | Download database backup +*DatabaseBackupsAdminApi* | [**listDatabaseBackups**](doc//DatabaseBackupsAdminApi.md#listdatabasebackups) | **GET** /admin/database-backups | List database backups +*DatabaseBackupsAdminApi* | [**startDatabaseRestoreFlow**](doc//DatabaseBackupsAdminApi.md#startdatabaserestoreflow) | **POST** /admin/database-backups/start-restore | Start database backup restore flow +*DatabaseBackupsAdminApi* | [**uploadDatabaseBackup**](doc//DatabaseBackupsAdminApi.md#uploaddatabasebackup) | **POST** /admin/database-backups/upload | Upload database backup +*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner +*DeprecatedApi* | [**getAllUserAssetsByDeviceId**](doc//DeprecatedApi.md#getalluserassetsbydeviceid) | **GET** /assets/device/{deviceId} | Retrieve assets by device ID +*DeprecatedApi* | [**getDeltaSync**](doc//DeprecatedApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user +*DeprecatedApi* | [**getFullSyncForUser**](doc//DeprecatedApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user +*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status +*DeprecatedApi* | [**getRandom**](doc//DeprecatedApi.md#getrandom) | **GET** /assets/random | Get random assets +*DeprecatedApi* | [**replaceAsset**](doc//DeprecatedApi.md#replaceasset) | **PUT** /assets/{id}/original | Replace asset +*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs +*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive +*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information +*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate +*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates +*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates +*DuplicatesApi* | [**resolveDuplicates**](doc//DuplicatesApi.md#resolveduplicates) | **POST** /duplicates/resolve | Resolve duplicate groups +*FacesApi* | [**createFace**](doc//FacesApi.md#createface) | **POST** /faces | Create a face +*FacesApi* | [**deleteFace**](doc//FacesApi.md#deleteface) | **DELETE** /faces/{id} | Delete a face +*FacesApi* | [**getFaces**](doc//FacesApi.md#getfaces) | **GET** /faces | Retrieve faces for asset +*FacesApi* | [**reassignFacesById**](doc//FacesApi.md#reassignfacesbyid) | **PUT** /faces/{id} | Re-assign a face to another person +*JobsApi* | [**createJob**](doc//JobsApi.md#createjob) | **POST** /jobs | Create a manual job +*JobsApi* | [**getQueuesLegacy**](doc//JobsApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status +*JobsApi* | [**runQueueCommandLegacy**](doc//JobsApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs +*LibrariesApi* | [**createLibrary**](doc//LibrariesApi.md#createlibrary) | **POST** /libraries | Create a library +*LibrariesApi* | [**deleteLibrary**](doc//LibrariesApi.md#deletelibrary) | **DELETE** /libraries/{id} | Delete a library +*LibrariesApi* | [**getAllLibraries**](doc//LibrariesApi.md#getalllibraries) | **GET** /libraries | Retrieve libraries +*LibrariesApi* | [**getLibrary**](doc//LibrariesApi.md#getlibrary) | **GET** /libraries/{id} | Retrieve a library +*LibrariesApi* | [**getLibraryStatistics**](doc//LibrariesApi.md#getlibrarystatistics) | **GET** /libraries/{id}/statistics | Retrieve library statistics +*LibrariesApi* | [**scanLibrary**](doc//LibrariesApi.md#scanlibrary) | **POST** /libraries/{id}/scan | Scan a library +*LibrariesApi* | [**updateLibrary**](doc//LibrariesApi.md#updatelibrary) | **PUT** /libraries/{id} | Update a library +*LibrariesApi* | [**validate**](doc//LibrariesApi.md#validate) | **POST** /libraries/{id}/validate | Validate library settings +*MaintenanceAdminApi* | [**detectPriorInstall**](doc//MaintenanceAdminApi.md#detectpriorinstall) | **GET** /admin/maintenance/detect-install | Detect existing install +*MaintenanceAdminApi* | [**getMaintenanceStatus**](doc//MaintenanceAdminApi.md#getmaintenancestatus) | **GET** /admin/maintenance/status | Get maintenance mode status +*MaintenanceAdminApi* | [**maintenanceLogin**](doc//MaintenanceAdminApi.md#maintenancelogin) | **POST** /admin/maintenance/login | Log into maintenance mode +*MaintenanceAdminApi* | [**setMaintenanceMode**](doc//MaintenanceAdminApi.md#setmaintenancemode) | **POST** /admin/maintenance | Set maintenance mode +*MapApi* | [**getMapMarkers**](doc//MapApi.md#getmapmarkers) | **GET** /map/markers | Retrieve map markers +*MapApi* | [**reverseGeocode**](doc//MapApi.md#reversegeocode) | **GET** /map/reverse-geocode | Reverse geocode coordinates +*MemoriesApi* | [**addMemoryAssets**](doc//MemoriesApi.md#addmemoryassets) | **PUT** /memories/{id}/assets | Add assets to a memory +*MemoriesApi* | [**createMemory**](doc//MemoriesApi.md#creatememory) | **POST** /memories | Create a memory +*MemoriesApi* | [**deleteMemory**](doc//MemoriesApi.md#deletememory) | **DELETE** /memories/{id} | Delete a memory +*MemoriesApi* | [**getMemory**](doc//MemoriesApi.md#getmemory) | **GET** /memories/{id} | Retrieve a memory +*MemoriesApi* | [**memoriesStatistics**](doc//MemoriesApi.md#memoriesstatistics) | **GET** /memories/statistics | Retrieve memories statistics +*MemoriesApi* | [**removeMemoryAssets**](doc//MemoriesApi.md#removememoryassets) | **DELETE** /memories/{id}/assets | Remove assets from a memory +*MemoriesApi* | [**searchMemories**](doc//MemoriesApi.md#searchmemories) | **GET** /memories | Retrieve memories +*MemoriesApi* | [**updateMemory**](doc//MemoriesApi.md#updatememory) | **PUT** /memories/{id} | Update a memory +*NotificationsApi* | [**deleteNotification**](doc//NotificationsApi.md#deletenotification) | **DELETE** /notifications/{id} | Delete a notification +*NotificationsApi* | [**deleteNotifications**](doc//NotificationsApi.md#deletenotifications) | **DELETE** /notifications | Delete notifications +*NotificationsApi* | [**getNotification**](doc//NotificationsApi.md#getnotification) | **GET** /notifications/{id} | Get a notification +*NotificationsApi* | [**getNotifications**](doc//NotificationsApi.md#getnotifications) | **GET** /notifications | Retrieve notifications +*NotificationsApi* | [**updateNotification**](doc//NotificationsApi.md#updatenotification) | **PUT** /notifications/{id} | Update a notification +*NotificationsApi* | [**updateNotifications**](doc//NotificationsApi.md#updatenotifications) | **PUT** /notifications | Update notifications +*NotificationsAdminApi* | [**createNotification**](doc//NotificationsAdminApi.md#createnotification) | **POST** /admin/notifications | Create a notification +*NotificationsAdminApi* | [**getNotificationTemplateAdmin**](doc//NotificationsAdminApi.md#getnotificationtemplateadmin) | **POST** /admin/notifications/templates/{name} | Render email template +*NotificationsAdminApi* | [**sendTestEmailAdmin**](doc//NotificationsAdminApi.md#sendtestemailadmin) | **POST** /admin/notifications/test-email | Send test email +*PartnersApi* | [**createPartner**](doc//PartnersApi.md#createpartner) | **POST** /partners | Create a partner +*PartnersApi* | [**createPartnerDeprecated**](doc//PartnersApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner +*PartnersApi* | [**getPartners**](doc//PartnersApi.md#getpartners) | **GET** /partners | Retrieve partners +*PartnersApi* | [**removePartner**](doc//PartnersApi.md#removepartner) | **DELETE** /partners/{id} | Remove a partner +*PartnersApi* | [**updatePartner**](doc//PartnersApi.md#updatepartner) | **PUT** /partners/{id} | Update a partner +*PeopleApi* | [**createPerson**](doc//PeopleApi.md#createperson) | **POST** /people | Create a person +*PeopleApi* | [**deletePeople**](doc//PeopleApi.md#deletepeople) | **DELETE** /people | Delete people +*PeopleApi* | [**deletePerson**](doc//PeopleApi.md#deleteperson) | **DELETE** /people/{id} | Delete person +*PeopleApi* | [**getAllPeople**](doc//PeopleApi.md#getallpeople) | **GET** /people | Get all people +*PeopleApi* | [**getPerson**](doc//PeopleApi.md#getperson) | **GET** /people/{id} | Get a person +*PeopleApi* | [**getPersonStatistics**](doc//PeopleApi.md#getpersonstatistics) | **GET** /people/{id}/statistics | Get person statistics +*PeopleApi* | [**getPersonThumbnail**](doc//PeopleApi.md#getpersonthumbnail) | **GET** /people/{id}/thumbnail | Get person thumbnail +*PeopleApi* | [**mergePerson**](doc//PeopleApi.md#mergeperson) | **POST** /people/{id}/merge | Merge people +*PeopleApi* | [**reassignFaces**](doc//PeopleApi.md#reassignfaces) | **PUT** /people/{id}/reassign | Reassign faces +*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people +*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person +*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin +*PluginsApi* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers +*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins +*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue +*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue +*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs +*QueuesApi* | [**getQueues**](doc//QueuesApi.md#getqueues) | **GET** /queues | List all queues +*QueuesApi* | [**updateQueue**](doc//QueuesApi.md#updatequeue) | **PUT** /queues/{name} | Update a queue +*SearchApi* | [**getAssetsByCity**](doc//SearchApi.md#getassetsbycity) | **GET** /search/cities | Retrieve assets by city +*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | Retrieve explore data +*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions | Retrieve search suggestions +*SearchApi* | [**searchAssetStatistics**](doc//SearchApi.md#searchassetstatistics) | **POST** /search/statistics | Search asset statistics +*SearchApi* | [**searchAssets**](doc//SearchApi.md#searchassets) | **POST** /search/metadata | Search assets by metadata +*SearchApi* | [**searchLargeAssets**](doc//SearchApi.md#searchlargeassets) | **POST** /search/large-assets | Search large assets +*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | Search people +*SearchApi* | [**searchPlaces**](doc//SearchApi.md#searchplaces) | **GET** /search/places | Search places +*SearchApi* | [**searchRandom**](doc//SearchApi.md#searchrandom) | **POST** /search/random | Search random assets +*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart | Smart asset search +*ServerApi* | [**deleteServerLicense**](doc//ServerApi.md#deleteserverlicense) | **DELETE** /server/license | Delete server product key +*ServerApi* | [**getAboutInfo**](doc//ServerApi.md#getaboutinfo) | **GET** /server/about | Get server information +*ServerApi* | [**getApkLinks**](doc//ServerApi.md#getapklinks) | **GET** /server/apk-links | Get APK links +*ServerApi* | [**getServerConfig**](doc//ServerApi.md#getserverconfig) | **GET** /server/config | Get config +*ServerApi* | [**getServerFeatures**](doc//ServerApi.md#getserverfeatures) | **GET** /server/features | Get features +*ServerApi* | [**getServerLicense**](doc//ServerApi.md#getserverlicense) | **GET** /server/license | Get product key +*ServerApi* | [**getServerStatistics**](doc//ServerApi.md#getserverstatistics) | **GET** /server/statistics | Get statistics +*ServerApi* | [**getServerVersion**](doc//ServerApi.md#getserverversion) | **GET** /server/version | Get server version +*ServerApi* | [**getStorage**](doc//ServerApi.md#getstorage) | **GET** /server/storage | Get storage +*ServerApi* | [**getSupportedMediaTypes**](doc//ServerApi.md#getsupportedmediatypes) | **GET** /server/media-types | Get supported media types +*ServerApi* | [**getTheme**](doc//ServerApi.md#gettheme) | **GET** /server/theme | Get theme +*ServerApi* | [**getVersionCheck**](doc//ServerApi.md#getversioncheck) | **GET** /server/version-check | Get version check status +*ServerApi* | [**getVersionHistory**](doc//ServerApi.md#getversionhistory) | **GET** /server/version-history | Get version history +*ServerApi* | [**pingServer**](doc//ServerApi.md#pingserver) | **GET** /server/ping | Ping +*ServerApi* | [**setServerLicense**](doc//ServerApi.md#setserverlicense) | **PUT** /server/license | Set server product key +*SessionsApi* | [**createSession**](doc//SessionsApi.md#createsession) | **POST** /sessions | Create a session +*SessionsApi* | [**deleteAllSessions**](doc//SessionsApi.md#deleteallsessions) | **DELETE** /sessions | Delete all sessions +*SessionsApi* | [**deleteSession**](doc//SessionsApi.md#deletesession) | **DELETE** /sessions/{id} | Delete a session +*SessionsApi* | [**getSessions**](doc//SessionsApi.md#getsessions) | **GET** /sessions | Retrieve sessions +*SessionsApi* | [**lockSession**](doc//SessionsApi.md#locksession) | **POST** /sessions/{id}/lock | Lock a session +*SessionsApi* | [**updateSession**](doc//SessionsApi.md#updatesession) | **PUT** /sessions/{id} | Update a session +*SharedLinksApi* | [**addSharedLinkAssets**](doc//SharedLinksApi.md#addsharedlinkassets) | **PUT** /shared-links/{id}/assets | Add assets to a shared link +*SharedLinksApi* | [**createSharedLink**](doc//SharedLinksApi.md#createsharedlink) | **POST** /shared-links | Create a shared link +*SharedLinksApi* | [**getAllSharedLinks**](doc//SharedLinksApi.md#getallsharedlinks) | **GET** /shared-links | Retrieve all shared links +*SharedLinksApi* | [**getMySharedLink**](doc//SharedLinksApi.md#getmysharedlink) | **GET** /shared-links/me | Retrieve current shared link +*SharedLinksApi* | [**getSharedLinkById**](doc//SharedLinksApi.md#getsharedlinkbyid) | **GET** /shared-links/{id} | Retrieve a shared link +*SharedLinksApi* | [**removeSharedLink**](doc//SharedLinksApi.md#removesharedlink) | **DELETE** /shared-links/{id} | Delete a shared link +*SharedLinksApi* | [**removeSharedLinkAssets**](doc//SharedLinksApi.md#removesharedlinkassets) | **DELETE** /shared-links/{id}/assets | Remove assets from a shared link +*SharedLinksApi* | [**sharedLinkLogin**](doc//SharedLinksApi.md#sharedlinklogin) | **POST** /shared-links/login | Shared link login +*SharedLinksApi* | [**updateSharedLink**](doc//SharedLinksApi.md#updatesharedlink) | **PATCH** /shared-links/{id} | Update a shared link +*StacksApi* | [**createStack**](doc//StacksApi.md#createstack) | **POST** /stacks | Create a stack +*StacksApi* | [**deleteStack**](doc//StacksApi.md#deletestack) | **DELETE** /stacks/{id} | Delete a stack +*StacksApi* | [**deleteStacks**](doc//StacksApi.md#deletestacks) | **DELETE** /stacks | Delete stacks +*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} | Retrieve a stack +*StacksApi* | [**removeAssetFromStack**](doc//StacksApi.md#removeassetfromstack) | **DELETE** /stacks/{id}/assets/{assetId} | Remove an asset from a stack +*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks | Retrieve stacks +*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} | Update a stack +*SyncApi* | [**deleteSyncAck**](doc//SyncApi.md#deletesyncack) | **DELETE** /sync/ack | Delete acknowledgements +*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync | Get delta sync for user +*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync | Get full sync for user +*SyncApi* | [**getSyncAck**](doc//SyncApi.md#getsyncack) | **GET** /sync/ack | Retrieve acknowledgements +*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream | Stream sync changes +*SyncApi* | [**sendSyncAck**](doc//SyncApi.md#sendsyncack) | **POST** /sync/ack | Acknowledge changes +*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config | Get system configuration +*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults | Get system configuration defaults +*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options | Get storage template options +*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config | Update system configuration +*SystemMetadataApi* | [**getAdminOnboarding**](doc//SystemMetadataApi.md#getadminonboarding) | **GET** /system-metadata/admin-onboarding | Retrieve admin onboarding +*SystemMetadataApi* | [**getReverseGeocodingState**](doc//SystemMetadataApi.md#getreversegeocodingstate) | **GET** /system-metadata/reverse-geocoding-state | Retrieve reverse geocoding state +*SystemMetadataApi* | [**getVersionCheckState**](doc//SystemMetadataApi.md#getversioncheckstate) | **GET** /system-metadata/version-check-state | Retrieve version check state +*SystemMetadataApi* | [**updateAdminOnboarding**](doc//SystemMetadataApi.md#updateadminonboarding) | **POST** /system-metadata/admin-onboarding | Update admin onboarding +*TagsApi* | [**bulkTagAssets**](doc//TagsApi.md#bulktagassets) | **PUT** /tags/assets | Tag assets +*TagsApi* | [**createTag**](doc//TagsApi.md#createtag) | **POST** /tags | Create a tag +*TagsApi* | [**deleteTag**](doc//TagsApi.md#deletetag) | **DELETE** /tags/{id} | Delete a tag +*TagsApi* | [**getAllTags**](doc//TagsApi.md#getalltags) | **GET** /tags | Retrieve tags +*TagsApi* | [**getTagById**](doc//TagsApi.md#gettagbyid) | **GET** /tags/{id} | Retrieve a tag +*TagsApi* | [**tagAssets**](doc//TagsApi.md#tagassets) | **PUT** /tags/{id}/assets | Tag assets +*TagsApi* | [**untagAssets**](doc//TagsApi.md#untagassets) | **DELETE** /tags/{id}/assets | Untag assets +*TagsApi* | [**updateTag**](doc//TagsApi.md#updatetag) | **PUT** /tags/{id} | Update a tag +*TagsApi* | [**upsertTags**](doc//TagsApi.md#upserttags) | **PUT** /tags | Upsert tags +*TimelineApi* | [**getTimeBucket**](doc//TimelineApi.md#gettimebucket) | **GET** /timeline/bucket | Get time bucket +*TimelineApi* | [**getTimeBuckets**](doc//TimelineApi.md#gettimebuckets) | **GET** /timeline/buckets | Get time buckets +*TrashApi* | [**emptyTrash**](doc//TrashApi.md#emptytrash) | **POST** /trash/empty | Empty trash +*TrashApi* | [**restoreAssets**](doc//TrashApi.md#restoreassets) | **POST** /trash/restore/assets | Restore assets +*TrashApi* | [**restoreTrash**](doc//TrashApi.md#restoretrash) | **POST** /trash/restore | Restore trash +*UsersApi* | [**createProfileImage**](doc//UsersApi.md#createprofileimage) | **POST** /users/profile-image | Create user profile image +*UsersApi* | [**deleteProfileImage**](doc//UsersApi.md#deleteprofileimage) | **DELETE** /users/profile-image | Delete user profile image +*UsersApi* | [**deleteUserLicense**](doc//UsersApi.md#deleteuserlicense) | **DELETE** /users/me/license | Delete user product key +*UsersApi* | [**deleteUserOnboarding**](doc//UsersApi.md#deleteuseronboarding) | **DELETE** /users/me/onboarding | Delete user onboarding +*UsersApi* | [**getMyPreferences**](doc//UsersApi.md#getmypreferences) | **GET** /users/me/preferences | Get my preferences +*UsersApi* | [**getMyUser**](doc//UsersApi.md#getmyuser) | **GET** /users/me | Get current user +*UsersApi* | [**getProfileImage**](doc//UsersApi.md#getprofileimage) | **GET** /users/{id}/profile-image | Retrieve user profile image +*UsersApi* | [**getUser**](doc//UsersApi.md#getuser) | **GET** /users/{id} | Retrieve a user +*UsersApi* | [**getUserLicense**](doc//UsersApi.md#getuserlicense) | **GET** /users/me/license | Retrieve user product key +*UsersApi* | [**getUserOnboarding**](doc//UsersApi.md#getuseronboarding) | **GET** /users/me/onboarding | Retrieve user onboarding +*UsersApi* | [**searchUsers**](doc//UsersApi.md#searchusers) | **GET** /users | Get all users +*UsersApi* | [**setUserLicense**](doc//UsersApi.md#setuserlicense) | **PUT** /users/me/license | Set user product key +*UsersApi* | [**setUserOnboarding**](doc//UsersApi.md#setuseronboarding) | **PUT** /users/me/onboarding | Update user onboarding +*UsersApi* | [**updateMyPreferences**](doc//UsersApi.md#updatemypreferences) | **PUT** /users/me/preferences | Update my preferences +*UsersApi* | [**updateMyUser**](doc//UsersApi.md#updatemyuser) | **PUT** /users/me | Update current user +*UsersAdminApi* | [**createUserAdmin**](doc//UsersAdminApi.md#createuseradmin) | **POST** /admin/users | Create a user +*UsersAdminApi* | [**deleteUserAdmin**](doc//UsersAdminApi.md#deleteuseradmin) | **DELETE** /admin/users/{id} | Delete a user +*UsersAdminApi* | [**getUserAdmin**](doc//UsersAdminApi.md#getuseradmin) | **GET** /admin/users/{id} | Retrieve a user +*UsersAdminApi* | [**getUserPreferencesAdmin**](doc//UsersAdminApi.md#getuserpreferencesadmin) | **GET** /admin/users/{id}/preferences | Retrieve user preferences +*UsersAdminApi* | [**getUserSessionsAdmin**](doc//UsersAdminApi.md#getusersessionsadmin) | **GET** /admin/users/{id}/sessions | Retrieve user sessions +*UsersAdminApi* | [**getUserStatisticsAdmin**](doc//UsersAdminApi.md#getuserstatisticsadmin) | **GET** /admin/users/{id}/statistics | Retrieve user statistics +*UsersAdminApi* | [**restoreUserAdmin**](doc//UsersAdminApi.md#restoreuseradmin) | **POST** /admin/users/{id}/restore | Restore a deleted user +*UsersAdminApi* | [**searchUsersAdmin**](doc//UsersAdminApi.md#searchusersadmin) | **GET** /admin/users | Search users +*UsersAdminApi* | [**updateUserAdmin**](doc//UsersAdminApi.md#updateuseradmin) | **PUT** /admin/users/{id} | Update a user +*UsersAdminApi* | [**updateUserPreferencesAdmin**](doc//UsersAdminApi.md#updateuserpreferencesadmin) | **PUT** /admin/users/{id}/preferences | Update user preferences +*ViewsApi* | [**getAssetsByOriginalPath**](doc//ViewsApi.md#getassetsbyoriginalpath) | **GET** /view/folder | Retrieve assets by original path +*ViewsApi* | [**getUniqueOriginalPaths**](doc//ViewsApi.md#getuniqueoriginalpaths) | **GET** /view/folder/unique-paths | Retrieve unique paths +*WorkflowsApi* | [**createWorkflow**](doc//WorkflowsApi.md#createworkflow) | **POST** /workflows | Create a workflow +*WorkflowsApi* | [**deleteWorkflow**](doc//WorkflowsApi.md#deleteworkflow) | **DELETE** /workflows/{id} | Delete a workflow +*WorkflowsApi* | [**getWorkflow**](doc//WorkflowsApi.md#getworkflow) | **GET** /workflows/{id} | Retrieve a workflow +*WorkflowsApi* | [**getWorkflows**](doc//WorkflowsApi.md#getworkflows) | **GET** /workflows | List all workflows +*WorkflowsApi* | [**updateWorkflow**](doc//WorkflowsApi.md#updateworkflow) | **PUT** /workflows/{id} | Update a workflow + + +## Documentation For Models + + - [APIKeyCreateDto](doc//APIKeyCreateDto.md) + - [APIKeyCreateResponseDto](doc//APIKeyCreateResponseDto.md) + - [APIKeyResponseDto](doc//APIKeyResponseDto.md) + - [APIKeyUpdateDto](doc//APIKeyUpdateDto.md) + - [ActivityCreateDto](doc//ActivityCreateDto.md) + - [ActivityResponseDto](doc//ActivityResponseDto.md) + - [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md) + - [AddUsersDto](doc//AddUsersDto.md) + - [AdminOnboardingUpdateDto](doc//AdminOnboardingUpdateDto.md) + - [AlbumResponseDto](doc//AlbumResponseDto.md) + - [AlbumStatisticsResponseDto](doc//AlbumStatisticsResponseDto.md) + - [AlbumUserAddDto](doc//AlbumUserAddDto.md) + - [AlbumUserCreateDto](doc//AlbumUserCreateDto.md) + - [AlbumUserResponseDto](doc//AlbumUserResponseDto.md) + - [AlbumUserRole](doc//AlbumUserRole.md) + - [AlbumsAddAssetsDto](doc//AlbumsAddAssetsDto.md) + - [AlbumsAddAssetsResponseDto](doc//AlbumsAddAssetsResponseDto.md) + - [AlbumsResponse](doc//AlbumsResponse.md) + - [AlbumsUpdate](doc//AlbumsUpdate.md) + - [AssetBulkDeleteDto](doc//AssetBulkDeleteDto.md) + - [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md) + - [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md) + - [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md) + - [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md) + - [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md) + - [AssetCopyDto](doc//AssetCopyDto.md) + - [AssetDeltaSyncDto](doc//AssetDeltaSyncDto.md) + - [AssetDeltaSyncResponseDto](doc//AssetDeltaSyncResponseDto.md) + - [AssetEditAction](doc//AssetEditAction.md) + - [AssetEditActionItemDto](doc//AssetEditActionItemDto.md) + - [AssetEditActionItemDtoParameters](doc//AssetEditActionItemDtoParameters.md) + - [AssetEditActionItemResponseDto](doc//AssetEditActionItemResponseDto.md) + - [AssetEditsCreateDto](doc//AssetEditsCreateDto.md) + - [AssetEditsResponseDto](doc//AssetEditsResponseDto.md) + - [AssetFaceCreateDto](doc//AssetFaceCreateDto.md) + - [AssetFaceDeleteDto](doc//AssetFaceDeleteDto.md) + - [AssetFaceResponseDto](doc//AssetFaceResponseDto.md) + - [AssetFaceUpdateDto](doc//AssetFaceUpdateDto.md) + - [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md) + - [AssetFaceWithoutPersonResponseDto](doc//AssetFaceWithoutPersonResponseDto.md) + - [AssetFullSyncDto](doc//AssetFullSyncDto.md) + - [AssetIdsDto](doc//AssetIdsDto.md) + - [AssetIdsResponseDto](doc//AssetIdsResponseDto.md) + - [AssetJobName](doc//AssetJobName.md) + - [AssetJobsDto](doc//AssetJobsDto.md) + - [AssetMediaResponseDto](doc//AssetMediaResponseDto.md) + - [AssetMediaSize](doc//AssetMediaSize.md) + - [AssetMediaStatus](doc//AssetMediaStatus.md) + - [AssetMetadataBulkDeleteDto](doc//AssetMetadataBulkDeleteDto.md) + - [AssetMetadataBulkDeleteItemDto](doc//AssetMetadataBulkDeleteItemDto.md) + - [AssetMetadataBulkResponseDto](doc//AssetMetadataBulkResponseDto.md) + - [AssetMetadataBulkUpsertDto](doc//AssetMetadataBulkUpsertDto.md) + - [AssetMetadataBulkUpsertItemDto](doc//AssetMetadataBulkUpsertItemDto.md) + - [AssetMetadataResponseDto](doc//AssetMetadataResponseDto.md) + - [AssetMetadataUpsertDto](doc//AssetMetadataUpsertDto.md) + - [AssetMetadataUpsertItemDto](doc//AssetMetadataUpsertItemDto.md) + - [AssetOcrResponseDto](doc//AssetOcrResponseDto.md) + - [AssetOrder](doc//AssetOrder.md) + - [AssetResponseDto](doc//AssetResponseDto.md) + - [AssetStackResponseDto](doc//AssetStackResponseDto.md) + - [AssetStatsResponseDto](doc//AssetStatsResponseDto.md) + - [AssetTypeEnum](doc//AssetTypeEnum.md) + - [AssetVisibility](doc//AssetVisibility.md) + - [AudioCodec](doc//AudioCodec.md) + - [AuthStatusResponseDto](doc//AuthStatusResponseDto.md) + - [AvatarUpdate](doc//AvatarUpdate.md) + - [BulkIdErrorReason](doc//BulkIdErrorReason.md) + - [BulkIdResponseDto](doc//BulkIdResponseDto.md) + - [BulkIdsDto](doc//BulkIdsDto.md) + - [CLIPConfig](doc//CLIPConfig.md) + - [CQMode](doc//CQMode.md) + - [CastResponse](doc//CastResponse.md) + - [CastUpdate](doc//CastUpdate.md) + - [ChangePasswordDto](doc//ChangePasswordDto.md) + - [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md) + - [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md) + - [Colorspace](doc//Colorspace.md) + - [ContributorCountResponseDto](doc//ContributorCountResponseDto.md) + - [CreateAlbumDto](doc//CreateAlbumDto.md) + - [CreateLibraryDto](doc//CreateLibraryDto.md) + - [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md) + - [CropParameters](doc//CropParameters.md) + - [DatabaseBackupConfig](doc//DatabaseBackupConfig.md) + - [DatabaseBackupDeleteDto](doc//DatabaseBackupDeleteDto.md) + - [DatabaseBackupDto](doc//DatabaseBackupDto.md) + - [DatabaseBackupListResponseDto](doc//DatabaseBackupListResponseDto.md) + - [DownloadArchiveDto](doc//DownloadArchiveDto.md) + - [DownloadArchiveInfo](doc//DownloadArchiveInfo.md) + - [DownloadInfoDto](doc//DownloadInfoDto.md) + - [DownloadResponse](doc//DownloadResponse.md) + - [DownloadResponseDto](doc//DownloadResponseDto.md) + - [DownloadUpdate](doc//DownloadUpdate.md) + - [DuplicateDetectionConfig](doc//DuplicateDetectionConfig.md) + - [DuplicateResolveDto](doc//DuplicateResolveDto.md) + - [DuplicateResolveGroupDto](doc//DuplicateResolveGroupDto.md) + - [DuplicateResponseDto](doc//DuplicateResponseDto.md) + - [EmailNotificationsResponse](doc//EmailNotificationsResponse.md) + - [EmailNotificationsUpdate](doc//EmailNotificationsUpdate.md) + - [ExifResponseDto](doc//ExifResponseDto.md) + - [FaceDto](doc//FaceDto.md) + - [FacialRecognitionConfig](doc//FacialRecognitionConfig.md) + - [FoldersResponse](doc//FoldersResponse.md) + - [FoldersUpdate](doc//FoldersUpdate.md) + - [ImageFormat](doc//ImageFormat.md) + - [JobCreateDto](doc//JobCreateDto.md) + - [JobName](doc//JobName.md) + - [JobSettingsDto](doc//JobSettingsDto.md) + - [LibraryResponseDto](doc//LibraryResponseDto.md) + - [LibraryStatsResponseDto](doc//LibraryStatsResponseDto.md) + - [LicenseKeyDto](doc//LicenseKeyDto.md) + - [LicenseResponseDto](doc//LicenseResponseDto.md) + - [LogLevel](doc//LogLevel.md) + - [LoginCredentialDto](doc//LoginCredentialDto.md) + - [LoginResponseDto](doc//LoginResponseDto.md) + - [LogoutResponseDto](doc//LogoutResponseDto.md) + - [MachineLearningAvailabilityChecksDto](doc//MachineLearningAvailabilityChecksDto.md) + - [MaintenanceAction](doc//MaintenanceAction.md) + - [MaintenanceAuthDto](doc//MaintenanceAuthDto.md) + - [MaintenanceDetectInstallResponseDto](doc//MaintenanceDetectInstallResponseDto.md) + - [MaintenanceDetectInstallStorageFolderDto](doc//MaintenanceDetectInstallStorageFolderDto.md) + - [MaintenanceLoginDto](doc//MaintenanceLoginDto.md) + - [MaintenanceStatusResponseDto](doc//MaintenanceStatusResponseDto.md) + - [ManualJobName](doc//ManualJobName.md) + - [MapMarkerResponseDto](doc//MapMarkerResponseDto.md) + - [MapReverseGeocodeResponseDto](doc//MapReverseGeocodeResponseDto.md) + - [MemoriesResponse](doc//MemoriesResponse.md) + - [MemoriesUpdate](doc//MemoriesUpdate.md) + - [MemoryCreateDto](doc//MemoryCreateDto.md) + - [MemoryResponseDto](doc//MemoryResponseDto.md) + - [MemorySearchOrder](doc//MemorySearchOrder.md) + - [MemoryStatisticsResponseDto](doc//MemoryStatisticsResponseDto.md) + - [MemoryType](doc//MemoryType.md) + - [MemoryUpdateDto](doc//MemoryUpdateDto.md) + - [MergePersonDto](doc//MergePersonDto.md) + - [MetadataSearchDto](doc//MetadataSearchDto.md) + - [MirrorAxis](doc//MirrorAxis.md) + - [MirrorParameters](doc//MirrorParameters.md) + - [NotificationCreateDto](doc//NotificationCreateDto.md) + - [NotificationDeleteAllDto](doc//NotificationDeleteAllDto.md) + - [NotificationDto](doc//NotificationDto.md) + - [NotificationLevel](doc//NotificationLevel.md) + - [NotificationType](doc//NotificationType.md) + - [NotificationUpdateAllDto](doc//NotificationUpdateAllDto.md) + - [NotificationUpdateDto](doc//NotificationUpdateDto.md) + - [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md) + - [OAuthCallbackDto](doc//OAuthCallbackDto.md) + - [OAuthConfigDto](doc//OAuthConfigDto.md) + - [OAuthTokenEndpointAuthMethod](doc//OAuthTokenEndpointAuthMethod.md) + - [OcrConfig](doc//OcrConfig.md) + - [OnThisDayDto](doc//OnThisDayDto.md) + - [OnboardingDto](doc//OnboardingDto.md) + - [OnboardingResponseDto](doc//OnboardingResponseDto.md) + - [PartnerCreateDto](doc//PartnerCreateDto.md) + - [PartnerDirection](doc//PartnerDirection.md) + - [PartnerResponseDto](doc//PartnerResponseDto.md) + - [PartnerUpdateDto](doc//PartnerUpdateDto.md) + - [PeopleResponse](doc//PeopleResponse.md) + - [PeopleResponseDto](doc//PeopleResponseDto.md) + - [PeopleUpdate](doc//PeopleUpdate.md) + - [PeopleUpdateDto](doc//PeopleUpdateDto.md) + - [PeopleUpdateItem](doc//PeopleUpdateItem.md) + - [Permission](doc//Permission.md) + - [PersonCreateDto](doc//PersonCreateDto.md) + - [PersonResponseDto](doc//PersonResponseDto.md) + - [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md) + - [PersonUpdateDto](doc//PersonUpdateDto.md) + - [PersonWithFacesResponseDto](doc//PersonWithFacesResponseDto.md) + - [PinCodeChangeDto](doc//PinCodeChangeDto.md) + - [PinCodeResetDto](doc//PinCodeResetDto.md) + - [PinCodeSetupDto](doc//PinCodeSetupDto.md) + - [PlacesResponseDto](doc//PlacesResponseDto.md) + - [PluginActionResponseDto](doc//PluginActionResponseDto.md) + - [PluginContextType](doc//PluginContextType.md) + - [PluginFilterResponseDto](doc//PluginFilterResponseDto.md) + - [PluginResponseDto](doc//PluginResponseDto.md) + - [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md) + - [PluginTriggerType](doc//PluginTriggerType.md) + - [PurchaseResponse](doc//PurchaseResponse.md) + - [PurchaseUpdate](doc//PurchaseUpdate.md) + - [QueueCommand](doc//QueueCommand.md) + - [QueueCommandDto](doc//QueueCommandDto.md) + - [QueueDeleteDto](doc//QueueDeleteDto.md) + - [QueueJobResponseDto](doc//QueueJobResponseDto.md) + - [QueueJobStatus](doc//QueueJobStatus.md) + - [QueueName](doc//QueueName.md) + - [QueueResponseDto](doc//QueueResponseDto.md) + - [QueueResponseLegacyDto](doc//QueueResponseLegacyDto.md) + - [QueueStatisticsDto](doc//QueueStatisticsDto.md) + - [QueueStatusLegacyDto](doc//QueueStatusLegacyDto.md) + - [QueueUpdateDto](doc//QueueUpdateDto.md) + - [QueuesResponseLegacyDto](doc//QueuesResponseLegacyDto.md) + - [RandomSearchDto](doc//RandomSearchDto.md) + - [RatingsResponse](doc//RatingsResponse.md) + - [RatingsUpdate](doc//RatingsUpdate.md) + - [ReactionLevel](doc//ReactionLevel.md) + - [ReactionType](doc//ReactionType.md) + - [ReverseGeocodingStateResponseDto](doc//ReverseGeocodingStateResponseDto.md) + - [RotateParameters](doc//RotateParameters.md) + - [SearchAlbumResponseDto](doc//SearchAlbumResponseDto.md) + - [SearchAssetResponseDto](doc//SearchAssetResponseDto.md) + - [SearchExploreItem](doc//SearchExploreItem.md) + - [SearchExploreResponseDto](doc//SearchExploreResponseDto.md) + - [SearchFacetCountResponseDto](doc//SearchFacetCountResponseDto.md) + - [SearchFacetResponseDto](doc//SearchFacetResponseDto.md) + - [SearchResponseDto](doc//SearchResponseDto.md) + - [SearchStatisticsResponseDto](doc//SearchStatisticsResponseDto.md) + - [SearchSuggestionType](doc//SearchSuggestionType.md) + - [ServerAboutResponseDto](doc//ServerAboutResponseDto.md) + - [ServerApkLinksDto](doc//ServerApkLinksDto.md) + - [ServerConfigDto](doc//ServerConfigDto.md) + - [ServerFeaturesDto](doc//ServerFeaturesDto.md) + - [ServerMediaTypesResponseDto](doc//ServerMediaTypesResponseDto.md) + - [ServerPingResponse](doc//ServerPingResponse.md) + - [ServerStatsResponseDto](doc//ServerStatsResponseDto.md) + - [ServerStorageResponseDto](doc//ServerStorageResponseDto.md) + - [ServerThemeDto](doc//ServerThemeDto.md) + - [ServerVersionHistoryResponseDto](doc//ServerVersionHistoryResponseDto.md) + - [ServerVersionResponseDto](doc//ServerVersionResponseDto.md) + - [SessionCreateDto](doc//SessionCreateDto.md) + - [SessionCreateResponseDto](doc//SessionCreateResponseDto.md) + - [SessionResponseDto](doc//SessionResponseDto.md) + - [SessionUnlockDto](doc//SessionUnlockDto.md) + - [SessionUpdateDto](doc//SessionUpdateDto.md) + - [SetMaintenanceModeDto](doc//SetMaintenanceModeDto.md) + - [SharedLinkCreateDto](doc//SharedLinkCreateDto.md) + - [SharedLinkEditDto](doc//SharedLinkEditDto.md) + - [SharedLinkLoginDto](doc//SharedLinkLoginDto.md) + - [SharedLinkResponseDto](doc//SharedLinkResponseDto.md) + - [SharedLinkType](doc//SharedLinkType.md) + - [SharedLinksResponse](doc//SharedLinksResponse.md) + - [SharedLinksUpdate](doc//SharedLinksUpdate.md) + - [SignUpDto](doc//SignUpDto.md) + - [SmartSearchDto](doc//SmartSearchDto.md) + - [SourceType](doc//SourceType.md) + - [StackCreateDto](doc//StackCreateDto.md) + - [StackResponseDto](doc//StackResponseDto.md) + - [StackUpdateDto](doc//StackUpdateDto.md) + - [StatisticsSearchDto](doc//StatisticsSearchDto.md) + - [StorageFolder](doc//StorageFolder.md) + - [SyncAckDeleteDto](doc//SyncAckDeleteDto.md) + - [SyncAckDto](doc//SyncAckDto.md) + - [SyncAckSetDto](doc//SyncAckSetDto.md) + - [SyncAlbumDeleteV1](doc//SyncAlbumDeleteV1.md) + - [SyncAlbumToAssetDeleteV1](doc//SyncAlbumToAssetDeleteV1.md) + - [SyncAlbumToAssetV1](doc//SyncAlbumToAssetV1.md) + - [SyncAlbumUserDeleteV1](doc//SyncAlbumUserDeleteV1.md) + - [SyncAlbumUserV1](doc//SyncAlbumUserV1.md) + - [SyncAlbumV1](doc//SyncAlbumV1.md) + - [SyncAssetDeleteV1](doc//SyncAssetDeleteV1.md) + - [SyncAssetEditDeleteV1](doc//SyncAssetEditDeleteV1.md) + - [SyncAssetEditV1](doc//SyncAssetEditV1.md) + - [SyncAssetExifV1](doc//SyncAssetExifV1.md) + - [SyncAssetFaceDeleteV1](doc//SyncAssetFaceDeleteV1.md) + - [SyncAssetFaceV1](doc//SyncAssetFaceV1.md) + - [SyncAssetFaceV2](doc//SyncAssetFaceV2.md) + - [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md) + - [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md) + - [SyncAssetV1](doc//SyncAssetV1.md) + - [SyncAuthUserV1](doc//SyncAuthUserV1.md) + - [SyncEntityType](doc//SyncEntityType.md) + - [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md) + - [SyncMemoryAssetV1](doc//SyncMemoryAssetV1.md) + - [SyncMemoryDeleteV1](doc//SyncMemoryDeleteV1.md) + - [SyncMemoryV1](doc//SyncMemoryV1.md) + - [SyncPartnerDeleteV1](doc//SyncPartnerDeleteV1.md) + - [SyncPartnerV1](doc//SyncPartnerV1.md) + - [SyncPersonDeleteV1](doc//SyncPersonDeleteV1.md) + - [SyncPersonV1](doc//SyncPersonV1.md) + - [SyncRequestType](doc//SyncRequestType.md) + - [SyncStackDeleteV1](doc//SyncStackDeleteV1.md) + - [SyncStackV1](doc//SyncStackV1.md) + - [SyncStreamDto](doc//SyncStreamDto.md) + - [SyncUserDeleteV1](doc//SyncUserDeleteV1.md) + - [SyncUserMetadataDeleteV1](doc//SyncUserMetadataDeleteV1.md) + - [SyncUserMetadataV1](doc//SyncUserMetadataV1.md) + - [SyncUserV1](doc//SyncUserV1.md) + - [SystemConfigBackupsDto](doc//SystemConfigBackupsDto.md) + - [SystemConfigDto](doc//SystemConfigDto.md) + - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) + - [SystemConfigFacesDto](doc//SystemConfigFacesDto.md) + - [SystemConfigGeneratedFullsizeImageDto](doc//SystemConfigGeneratedFullsizeImageDto.md) + - [SystemConfigGeneratedImageDto](doc//SystemConfigGeneratedImageDto.md) + - [SystemConfigImageDto](doc//SystemConfigImageDto.md) + - [SystemConfigJobDto](doc//SystemConfigJobDto.md) + - [SystemConfigLibraryDto](doc//SystemConfigLibraryDto.md) + - [SystemConfigLibraryScanDto](doc//SystemConfigLibraryScanDto.md) + - [SystemConfigLibraryWatchDto](doc//SystemConfigLibraryWatchDto.md) + - [SystemConfigLoggingDto](doc//SystemConfigLoggingDto.md) + - [SystemConfigMachineLearningDto](doc//SystemConfigMachineLearningDto.md) + - [SystemConfigMapDto](doc//SystemConfigMapDto.md) + - [SystemConfigMetadataDto](doc//SystemConfigMetadataDto.md) + - [SystemConfigNewVersionCheckDto](doc//SystemConfigNewVersionCheckDto.md) + - [SystemConfigNightlyTasksDto](doc//SystemConfigNightlyTasksDto.md) + - [SystemConfigNotificationsDto](doc//SystemConfigNotificationsDto.md) + - [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md) + - [SystemConfigPasswordLoginDto](doc//SystemConfigPasswordLoginDto.md) + - [SystemConfigReverseGeocodingDto](doc//SystemConfigReverseGeocodingDto.md) + - [SystemConfigServerDto](doc//SystemConfigServerDto.md) + - [SystemConfigSmtpDto](doc//SystemConfigSmtpDto.md) + - [SystemConfigSmtpTransportDto](doc//SystemConfigSmtpTransportDto.md) + - [SystemConfigStorageTemplateDto](doc//SystemConfigStorageTemplateDto.md) + - [SystemConfigTemplateEmailsDto](doc//SystemConfigTemplateEmailsDto.md) + - [SystemConfigTemplateStorageOptionDto](doc//SystemConfigTemplateStorageOptionDto.md) + - [SystemConfigTemplatesDto](doc//SystemConfigTemplatesDto.md) + - [SystemConfigThemeDto](doc//SystemConfigThemeDto.md) + - [SystemConfigTrashDto](doc//SystemConfigTrashDto.md) + - [SystemConfigUserDto](doc//SystemConfigUserDto.md) + - [TagBulkAssetsDto](doc//TagBulkAssetsDto.md) + - [TagBulkAssetsResponseDto](doc//TagBulkAssetsResponseDto.md) + - [TagCreateDto](doc//TagCreateDto.md) + - [TagResponseDto](doc//TagResponseDto.md) + - [TagUpdateDto](doc//TagUpdateDto.md) + - [TagUpsertDto](doc//TagUpsertDto.md) + - [TagsResponse](doc//TagsResponse.md) + - [TagsUpdate](doc//TagsUpdate.md) + - [TemplateDto](doc//TemplateDto.md) + - [TemplateResponseDto](doc//TemplateResponseDto.md) + - [TestEmailResponseDto](doc//TestEmailResponseDto.md) + - [TimeBucketAssetResponseDto](doc//TimeBucketAssetResponseDto.md) + - [TimeBucketsResponseDto](doc//TimeBucketsResponseDto.md) + - [ToneMapping](doc//ToneMapping.md) + - [TranscodeHWAccel](doc//TranscodeHWAccel.md) + - [TranscodePolicy](doc//TranscodePolicy.md) + - [TrashResponseDto](doc//TrashResponseDto.md) + - [UpdateAlbumDto](doc//UpdateAlbumDto.md) + - [UpdateAlbumUserDto](doc//UpdateAlbumUserDto.md) + - [UpdateAssetDto](doc//UpdateAssetDto.md) + - [UpdateLibraryDto](doc//UpdateLibraryDto.md) + - [UsageByUserDto](doc//UsageByUserDto.md) + - [UserAdminCreateDto](doc//UserAdminCreateDto.md) + - [UserAdminDeleteDto](doc//UserAdminDeleteDto.md) + - [UserAdminResponseDto](doc//UserAdminResponseDto.md) + - [UserAdminUpdateDto](doc//UserAdminUpdateDto.md) + - [UserAvatarColor](doc//UserAvatarColor.md) + - [UserLicense](doc//UserLicense.md) + - [UserMetadataKey](doc//UserMetadataKey.md) + - [UserPreferencesResponseDto](doc//UserPreferencesResponseDto.md) + - [UserPreferencesUpdateDto](doc//UserPreferencesUpdateDto.md) + - [UserResponseDto](doc//UserResponseDto.md) + - [UserStatus](doc//UserStatus.md) + - [UserUpdateMeDto](doc//UserUpdateMeDto.md) + - [ValidateAccessTokenResponseDto](doc//ValidateAccessTokenResponseDto.md) + - [ValidateLibraryDto](doc//ValidateLibraryDto.md) + - [ValidateLibraryImportPathResponseDto](doc//ValidateLibraryImportPathResponseDto.md) + - [ValidateLibraryResponseDto](doc//ValidateLibraryResponseDto.md) + - [VersionCheckStateResponseDto](doc//VersionCheckStateResponseDto.md) + - [VideoCodec](doc//VideoCodec.md) + - [VideoContainer](doc//VideoContainer.md) + - [WorkflowActionItemDto](doc//WorkflowActionItemDto.md) + - [WorkflowActionResponseDto](doc//WorkflowActionResponseDto.md) + - [WorkflowCreateDto](doc//WorkflowCreateDto.md) + - [WorkflowFilterItemDto](doc//WorkflowFilterItemDto.md) + - [WorkflowFilterResponseDto](doc//WorkflowFilterResponseDto.md) + - [WorkflowResponseDto](doc//WorkflowResponseDto.md) + - [WorkflowUpdateDto](doc//WorkflowUpdateDto.md) + + +## Documentation For Authorization + + +Authentication schemes defined for the API: +### bearer + +- **Type**: HTTP Bearer authentication + +### cookie + +- **Type**: API key +- **API key parameter name**: immich_access_token +- **Location**: + +### api_key + +- **Type**: API key +- **API key parameter name**: x-api-key +- **Location**: HTTP header + + +## Author + + + diff --git a/mobile/openapi/git_push.sh b/mobile/openapi/git_push.sh new file mode 100644 index 0000000000..f53a75d4fa --- /dev/null +++ b/mobile/openapi/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart new file mode 100644 index 0000000000..6b554fb644 --- /dev/null +++ b/mobile/openapi/lib/api.dart @@ -0,0 +1,440 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +library openapi.api; + +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:immich_mobile/utils/openapi_patching.dart'; +import 'package:http/http.dart'; +import 'package:intl/intl.dart'; +import 'package:meta/meta.dart'; + +part 'api_client.dart'; +part 'api_helper.dart'; +part 'api_exception.dart'; +part 'auth/authentication.dart'; +part 'auth/api_key_auth.dart'; +part 'auth/oauth.dart'; +part 'auth/http_basic_auth.dart'; +part 'auth/http_bearer_auth.dart'; + +part 'api/api_keys_api.dart'; +part 'api/activities_api.dart'; +part 'api/albums_api.dart'; +part 'api/assets_api.dart'; +part 'api/authentication_api.dart'; +part 'api/authentication_admin_api.dart'; +part 'api/database_backups_admin_api.dart'; +part 'api/deprecated_api.dart'; +part 'api/download_api.dart'; +part 'api/duplicates_api.dart'; +part 'api/faces_api.dart'; +part 'api/jobs_api.dart'; +part 'api/libraries_api.dart'; +part 'api/maintenance_admin_api.dart'; +part 'api/map_api.dart'; +part 'api/memories_api.dart'; +part 'api/notifications_api.dart'; +part 'api/notifications_admin_api.dart'; +part 'api/partners_api.dart'; +part 'api/people_api.dart'; +part 'api/plugins_api.dart'; +part 'api/queues_api.dart'; +part 'api/search_api.dart'; +part 'api/server_api.dart'; +part 'api/sessions_api.dart'; +part 'api/shared_links_api.dart'; +part 'api/stacks_api.dart'; +part 'api/sync_api.dart'; +part 'api/system_config_api.dart'; +part 'api/system_metadata_api.dart'; +part 'api/tags_api.dart'; +part 'api/timeline_api.dart'; +part 'api/trash_api.dart'; +part 'api/users_api.dart'; +part 'api/users_admin_api.dart'; +part 'api/views_api.dart'; +part 'api/workflows_api.dart'; + +part 'model/api_key_create_dto.dart'; +part 'model/api_key_create_response_dto.dart'; +part 'model/api_key_response_dto.dart'; +part 'model/api_key_update_dto.dart'; +part 'model/activity_create_dto.dart'; +part 'model/activity_response_dto.dart'; +part 'model/activity_statistics_response_dto.dart'; +part 'model/add_users_dto.dart'; +part 'model/admin_onboarding_update_dto.dart'; +part 'model/album_response_dto.dart'; +part 'model/album_statistics_response_dto.dart'; +part 'model/album_user_add_dto.dart'; +part 'model/album_user_create_dto.dart'; +part 'model/album_user_response_dto.dart'; +part 'model/album_user_role.dart'; +part 'model/albums_add_assets_dto.dart'; +part 'model/albums_add_assets_response_dto.dart'; +part 'model/albums_response.dart'; +part 'model/albums_update.dart'; +part 'model/asset_bulk_delete_dto.dart'; +part 'model/asset_bulk_update_dto.dart'; +part 'model/asset_bulk_upload_check_dto.dart'; +part 'model/asset_bulk_upload_check_item.dart'; +part 'model/asset_bulk_upload_check_response_dto.dart'; +part 'model/asset_bulk_upload_check_result.dart'; +part 'model/asset_copy_dto.dart'; +part 'model/asset_delta_sync_dto.dart'; +part 'model/asset_delta_sync_response_dto.dart'; +part 'model/asset_edit_action.dart'; +part 'model/asset_edit_action_item_dto.dart'; +part 'model/asset_edit_action_item_dto_parameters.dart'; +part 'model/asset_edit_action_item_response_dto.dart'; +part 'model/asset_edits_create_dto.dart'; +part 'model/asset_edits_response_dto.dart'; +part 'model/asset_face_create_dto.dart'; +part 'model/asset_face_delete_dto.dart'; +part 'model/asset_face_response_dto.dart'; +part 'model/asset_face_update_dto.dart'; +part 'model/asset_face_update_item.dart'; +part 'model/asset_face_without_person_response_dto.dart'; +part 'model/asset_full_sync_dto.dart'; +part 'model/asset_ids_dto.dart'; +part 'model/asset_ids_response_dto.dart'; +part 'model/asset_job_name.dart'; +part 'model/asset_jobs_dto.dart'; +part 'model/asset_media_response_dto.dart'; +part 'model/asset_media_size.dart'; +part 'model/asset_media_status.dart'; +part 'model/asset_metadata_bulk_delete_dto.dart'; +part 'model/asset_metadata_bulk_delete_item_dto.dart'; +part 'model/asset_metadata_bulk_response_dto.dart'; +part 'model/asset_metadata_bulk_upsert_dto.dart'; +part 'model/asset_metadata_bulk_upsert_item_dto.dart'; +part 'model/asset_metadata_response_dto.dart'; +part 'model/asset_metadata_upsert_dto.dart'; +part 'model/asset_metadata_upsert_item_dto.dart'; +part 'model/asset_ocr_response_dto.dart'; +part 'model/asset_order.dart'; +part 'model/asset_response_dto.dart'; +part 'model/asset_stack_response_dto.dart'; +part 'model/asset_stats_response_dto.dart'; +part 'model/asset_type_enum.dart'; +part 'model/asset_visibility.dart'; +part 'model/audio_codec.dart'; +part 'model/auth_status_response_dto.dart'; +part 'model/avatar_update.dart'; +part 'model/bulk_id_error_reason.dart'; +part 'model/bulk_id_response_dto.dart'; +part 'model/bulk_ids_dto.dart'; +part 'model/clip_config.dart'; +part 'model/cq_mode.dart'; +part 'model/cast_response.dart'; +part 'model/cast_update.dart'; +part 'model/change_password_dto.dart'; +part 'model/check_existing_assets_dto.dart'; +part 'model/check_existing_assets_response_dto.dart'; +part 'model/colorspace.dart'; +part 'model/contributor_count_response_dto.dart'; +part 'model/create_album_dto.dart'; +part 'model/create_library_dto.dart'; +part 'model/create_profile_image_response_dto.dart'; +part 'model/crop_parameters.dart'; +part 'model/database_backup_config.dart'; +part 'model/database_backup_delete_dto.dart'; +part 'model/database_backup_dto.dart'; +part 'model/database_backup_list_response_dto.dart'; +part 'model/download_archive_dto.dart'; +part 'model/download_archive_info.dart'; +part 'model/download_info_dto.dart'; +part 'model/download_response.dart'; +part 'model/download_response_dto.dart'; +part 'model/download_update.dart'; +part 'model/duplicate_detection_config.dart'; +part 'model/duplicate_resolve_dto.dart'; +part 'model/duplicate_resolve_group_dto.dart'; +part 'model/duplicate_response_dto.dart'; +part 'model/email_notifications_response.dart'; +part 'model/email_notifications_update.dart'; +part 'model/exif_response_dto.dart'; +part 'model/face_dto.dart'; +part 'model/facial_recognition_config.dart'; +part 'model/folders_response.dart'; +part 'model/folders_update.dart'; +part 'model/image_format.dart'; +part 'model/job_create_dto.dart'; +part 'model/job_name.dart'; +part 'model/job_settings_dto.dart'; +part 'model/library_response_dto.dart'; +part 'model/library_stats_response_dto.dart'; +part 'model/license_key_dto.dart'; +part 'model/license_response_dto.dart'; +part 'model/log_level.dart'; +part 'model/login_credential_dto.dart'; +part 'model/login_response_dto.dart'; +part 'model/logout_response_dto.dart'; +part 'model/machine_learning_availability_checks_dto.dart'; +part 'model/maintenance_action.dart'; +part 'model/maintenance_auth_dto.dart'; +part 'model/maintenance_detect_install_response_dto.dart'; +part 'model/maintenance_detect_install_storage_folder_dto.dart'; +part 'model/maintenance_login_dto.dart'; +part 'model/maintenance_status_response_dto.dart'; +part 'model/manual_job_name.dart'; +part 'model/map_marker_response_dto.dart'; +part 'model/map_reverse_geocode_response_dto.dart'; +part 'model/memories_response.dart'; +part 'model/memories_update.dart'; +part 'model/memory_create_dto.dart'; +part 'model/memory_response_dto.dart'; +part 'model/memory_search_order.dart'; +part 'model/memory_statistics_response_dto.dart'; +part 'model/memory_type.dart'; +part 'model/memory_update_dto.dart'; +part 'model/merge_person_dto.dart'; +part 'model/metadata_search_dto.dart'; +part 'model/mirror_axis.dart'; +part 'model/mirror_parameters.dart'; +part 'model/notification_create_dto.dart'; +part 'model/notification_delete_all_dto.dart'; +part 'model/notification_dto.dart'; +part 'model/notification_level.dart'; +part 'model/notification_type.dart'; +part 'model/notification_update_all_dto.dart'; +part 'model/notification_update_dto.dart'; +part 'model/o_auth_authorize_response_dto.dart'; +part 'model/o_auth_callback_dto.dart'; +part 'model/o_auth_config_dto.dart'; +part 'model/o_auth_token_endpoint_auth_method.dart'; +part 'model/ocr_config.dart'; +part 'model/on_this_day_dto.dart'; +part 'model/onboarding_dto.dart'; +part 'model/onboarding_response_dto.dart'; +part 'model/partner_create_dto.dart'; +part 'model/partner_direction.dart'; +part 'model/partner_response_dto.dart'; +part 'model/partner_update_dto.dart'; +part 'model/people_response.dart'; +part 'model/people_response_dto.dart'; +part 'model/people_update.dart'; +part 'model/people_update_dto.dart'; +part 'model/people_update_item.dart'; +part 'model/permission.dart'; +part 'model/person_create_dto.dart'; +part 'model/person_response_dto.dart'; +part 'model/person_statistics_response_dto.dart'; +part 'model/person_update_dto.dart'; +part 'model/person_with_faces_response_dto.dart'; +part 'model/pin_code_change_dto.dart'; +part 'model/pin_code_reset_dto.dart'; +part 'model/pin_code_setup_dto.dart'; +part 'model/places_response_dto.dart'; +part 'model/plugin_action_response_dto.dart'; +part 'model/plugin_context_type.dart'; +part 'model/plugin_filter_response_dto.dart'; +part 'model/plugin_response_dto.dart'; +part 'model/plugin_trigger_response_dto.dart'; +part 'model/plugin_trigger_type.dart'; +part 'model/purchase_response.dart'; +part 'model/purchase_update.dart'; +part 'model/queue_command.dart'; +part 'model/queue_command_dto.dart'; +part 'model/queue_delete_dto.dart'; +part 'model/queue_job_response_dto.dart'; +part 'model/queue_job_status.dart'; +part 'model/queue_name.dart'; +part 'model/queue_response_dto.dart'; +part 'model/queue_response_legacy_dto.dart'; +part 'model/queue_statistics_dto.dart'; +part 'model/queue_status_legacy_dto.dart'; +part 'model/queue_update_dto.dart'; +part 'model/queues_response_legacy_dto.dart'; +part 'model/random_search_dto.dart'; +part 'model/ratings_response.dart'; +part 'model/ratings_update.dart'; +part 'model/reaction_level.dart'; +part 'model/reaction_type.dart'; +part 'model/reverse_geocoding_state_response_dto.dart'; +part 'model/rotate_parameters.dart'; +part 'model/search_album_response_dto.dart'; +part 'model/search_asset_response_dto.dart'; +part 'model/search_explore_item.dart'; +part 'model/search_explore_response_dto.dart'; +part 'model/search_facet_count_response_dto.dart'; +part 'model/search_facet_response_dto.dart'; +part 'model/search_response_dto.dart'; +part 'model/search_statistics_response_dto.dart'; +part 'model/search_suggestion_type.dart'; +part 'model/server_about_response_dto.dart'; +part 'model/server_apk_links_dto.dart'; +part 'model/server_config_dto.dart'; +part 'model/server_features_dto.dart'; +part 'model/server_media_types_response_dto.dart'; +part 'model/server_ping_response.dart'; +part 'model/server_stats_response_dto.dart'; +part 'model/server_storage_response_dto.dart'; +part 'model/server_theme_dto.dart'; +part 'model/server_version_history_response_dto.dart'; +part 'model/server_version_response_dto.dart'; +part 'model/session_create_dto.dart'; +part 'model/session_create_response_dto.dart'; +part 'model/session_response_dto.dart'; +part 'model/session_unlock_dto.dart'; +part 'model/session_update_dto.dart'; +part 'model/set_maintenance_mode_dto.dart'; +part 'model/shared_link_create_dto.dart'; +part 'model/shared_link_edit_dto.dart'; +part 'model/shared_link_login_dto.dart'; +part 'model/shared_link_response_dto.dart'; +part 'model/shared_link_type.dart'; +part 'model/shared_links_response.dart'; +part 'model/shared_links_update.dart'; +part 'model/sign_up_dto.dart'; +part 'model/smart_search_dto.dart'; +part 'model/source_type.dart'; +part 'model/stack_create_dto.dart'; +part 'model/stack_response_dto.dart'; +part 'model/stack_update_dto.dart'; +part 'model/statistics_search_dto.dart'; +part 'model/storage_folder.dart'; +part 'model/sync_ack_delete_dto.dart'; +part 'model/sync_ack_dto.dart'; +part 'model/sync_ack_set_dto.dart'; +part 'model/sync_album_delete_v1.dart'; +part 'model/sync_album_to_asset_delete_v1.dart'; +part 'model/sync_album_to_asset_v1.dart'; +part 'model/sync_album_user_delete_v1.dart'; +part 'model/sync_album_user_v1.dart'; +part 'model/sync_album_v1.dart'; +part 'model/sync_asset_delete_v1.dart'; +part 'model/sync_asset_edit_delete_v1.dart'; +part 'model/sync_asset_edit_v1.dart'; +part 'model/sync_asset_exif_v1.dart'; +part 'model/sync_asset_face_delete_v1.dart'; +part 'model/sync_asset_face_v1.dart'; +part 'model/sync_asset_face_v2.dart'; +part 'model/sync_asset_metadata_delete_v1.dart'; +part 'model/sync_asset_metadata_v1.dart'; +part 'model/sync_asset_v1.dart'; +part 'model/sync_auth_user_v1.dart'; +part 'model/sync_entity_type.dart'; +part 'model/sync_memory_asset_delete_v1.dart'; +part 'model/sync_memory_asset_v1.dart'; +part 'model/sync_memory_delete_v1.dart'; +part 'model/sync_memory_v1.dart'; +part 'model/sync_partner_delete_v1.dart'; +part 'model/sync_partner_v1.dart'; +part 'model/sync_person_delete_v1.dart'; +part 'model/sync_person_v1.dart'; +part 'model/sync_request_type.dart'; +part 'model/sync_stack_delete_v1.dart'; +part 'model/sync_stack_v1.dart'; +part 'model/sync_stream_dto.dart'; +part 'model/sync_user_delete_v1.dart'; +part 'model/sync_user_metadata_delete_v1.dart'; +part 'model/sync_user_metadata_v1.dart'; +part 'model/sync_user_v1.dart'; +part 'model/system_config_backups_dto.dart'; +part 'model/system_config_dto.dart'; +part 'model/system_config_f_fmpeg_dto.dart'; +part 'model/system_config_faces_dto.dart'; +part 'model/system_config_generated_fullsize_image_dto.dart'; +part 'model/system_config_generated_image_dto.dart'; +part 'model/system_config_image_dto.dart'; +part 'model/system_config_job_dto.dart'; +part 'model/system_config_library_dto.dart'; +part 'model/system_config_library_scan_dto.dart'; +part 'model/system_config_library_watch_dto.dart'; +part 'model/system_config_logging_dto.dart'; +part 'model/system_config_machine_learning_dto.dart'; +part 'model/system_config_map_dto.dart'; +part 'model/system_config_metadata_dto.dart'; +part 'model/system_config_new_version_check_dto.dart'; +part 'model/system_config_nightly_tasks_dto.dart'; +part 'model/system_config_notifications_dto.dart'; +part 'model/system_config_o_auth_dto.dart'; +part 'model/system_config_password_login_dto.dart'; +part 'model/system_config_reverse_geocoding_dto.dart'; +part 'model/system_config_server_dto.dart'; +part 'model/system_config_smtp_dto.dart'; +part 'model/system_config_smtp_transport_dto.dart'; +part 'model/system_config_storage_template_dto.dart'; +part 'model/system_config_template_emails_dto.dart'; +part 'model/system_config_template_storage_option_dto.dart'; +part 'model/system_config_templates_dto.dart'; +part 'model/system_config_theme_dto.dart'; +part 'model/system_config_trash_dto.dart'; +part 'model/system_config_user_dto.dart'; +part 'model/tag_bulk_assets_dto.dart'; +part 'model/tag_bulk_assets_response_dto.dart'; +part 'model/tag_create_dto.dart'; +part 'model/tag_response_dto.dart'; +part 'model/tag_update_dto.dart'; +part 'model/tag_upsert_dto.dart'; +part 'model/tags_response.dart'; +part 'model/tags_update.dart'; +part 'model/template_dto.dart'; +part 'model/template_response_dto.dart'; +part 'model/test_email_response_dto.dart'; +part 'model/time_bucket_asset_response_dto.dart'; +part 'model/time_buckets_response_dto.dart'; +part 'model/tone_mapping.dart'; +part 'model/transcode_hw_accel.dart'; +part 'model/transcode_policy.dart'; +part 'model/trash_response_dto.dart'; +part 'model/update_album_dto.dart'; +part 'model/update_album_user_dto.dart'; +part 'model/update_asset_dto.dart'; +part 'model/update_library_dto.dart'; +part 'model/usage_by_user_dto.dart'; +part 'model/user_admin_create_dto.dart'; +part 'model/user_admin_delete_dto.dart'; +part 'model/user_admin_response_dto.dart'; +part 'model/user_admin_update_dto.dart'; +part 'model/user_avatar_color.dart'; +part 'model/user_license.dart'; +part 'model/user_metadata_key.dart'; +part 'model/user_preferences_response_dto.dart'; +part 'model/user_preferences_update_dto.dart'; +part 'model/user_response_dto.dart'; +part 'model/user_status.dart'; +part 'model/user_update_me_dto.dart'; +part 'model/validate_access_token_response_dto.dart'; +part 'model/validate_library_dto.dart'; +part 'model/validate_library_import_path_response_dto.dart'; +part 'model/validate_library_response_dto.dart'; +part 'model/version_check_state_response_dto.dart'; +part 'model/video_codec.dart'; +part 'model/video_container.dart'; +part 'model/workflow_action_item_dto.dart'; +part 'model/workflow_action_response_dto.dart'; +part 'model/workflow_create_dto.dart'; +part 'model/workflow_filter_item_dto.dart'; +part 'model/workflow_filter_response_dto.dart'; +part 'model/workflow_response_dto.dart'; +part 'model/workflow_update_dto.dart'; + + +/// An [ApiClient] instance that uses the default values obtained from +/// the OpenAPI specification file. +var defaultApiClient = ApiClient(); + +const _delimiters = {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'}; +const _dateEpochMarker = 'epoch'; +const _deepEquality = DeepCollectionEquality(); +final _dateFormatter = DateFormat('yyyy-MM-dd'); +final _regList = RegExp(r'^List<(.*)>$'); +final _regSet = RegExp(r'^Set<(.*)>$'); +final _regMap = RegExp(r'^Map$'); + +bool _isEpochMarker(String? pattern) => pattern == _dateEpochMarker || pattern == '/$_dateEpochMarker/'; diff --git a/mobile/openapi/lib/api/activities_api.dart b/mobile/openapi/lib/api/activities_api.dart new file mode 100644 index 0000000000..697598ac97 --- /dev/null +++ b/mobile/openapi/lib/api/activities_api.dart @@ -0,0 +1,291 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ActivitiesApi { + ActivitiesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create an activity + /// + /// Create a like or a comment for an album, or an asset in an album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [ActivityCreateDto] activityCreateDto (required): + Future createActivityWithHttpInfo(ActivityCreateDto activityCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/activities'; + + // ignore: prefer_final_locals + Object? postBody = activityCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create an activity + /// + /// Create a like or a comment for an album, or an asset in an album. + /// + /// Parameters: + /// + /// * [ActivityCreateDto] activityCreateDto (required): + Future createActivity(ActivityCreateDto activityCreateDto,) async { + final response = await createActivityWithHttpInfo(activityCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ActivityResponseDto',) as ActivityResponseDto; + + } + return null; + } + + /// Delete an activity + /// + /// Removes a like or comment from a given album or asset in an album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteActivityWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/activities/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete an activity + /// + /// Removes a like or comment from a given album or asset in an album. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteActivity(String id,) async { + final response = await deleteActivityWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// List all activities + /// + /// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] albumId (required): + /// Album ID + /// + /// * [String] assetId: + /// Asset ID (if activity is for an asset) + /// + /// * [ReactionLevel] level: + /// Filter by activity level + /// + /// * [ReactionType] type: + /// Filter by activity type + /// + /// * [String] userId: + /// Filter by user ID + Future getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/activities'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'albumId', albumId)); + if (assetId != null) { + queryParams.addAll(_queryParams('', 'assetId', assetId)); + } + if (level != null) { + queryParams.addAll(_queryParams('', 'level', level)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all activities + /// + /// Returns a list of activities for the selected asset or album. The activities are returned in sorted order, with the oldest activities appearing first. + /// + /// Parameters: + /// + /// * [String] albumId (required): + /// Album ID + /// + /// * [String] assetId: + /// Asset ID (if activity is for an asset) + /// + /// * [ReactionLevel] level: + /// Filter by activity level + /// + /// * [ReactionType] type: + /// Filter by activity type + /// + /// * [String] userId: + /// Filter by user ID + Future?> getActivities(String albumId, { String? assetId, ReactionLevel? level, ReactionType? type, String? userId, }) async { + final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, level: level, type: type, userId: userId, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve activity statistics + /// + /// Returns the number of likes and comments for a given album or asset in an album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] albumId (required): + /// Album ID + /// + /// * [String] assetId: + /// Asset ID (if activity is for an asset) + Future getActivityStatisticsWithHttpInfo(String albumId, { String? assetId, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/activities/statistics'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'albumId', albumId)); + if (assetId != null) { + queryParams.addAll(_queryParams('', 'assetId', assetId)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve activity statistics + /// + /// Returns the number of likes and comments for a given album or asset in an album. + /// + /// Parameters: + /// + /// * [String] albumId (required): + /// Album ID + /// + /// * [String] assetId: + /// Asset ID (if activity is for an asset) + Future getActivityStatistics(String albumId, { String? assetId, }) async { + final response = await getActivityStatisticsWithHttpInfo(albumId, assetId: assetId, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ActivityStatisticsResponseDto',) as ActivityStatisticsResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/albums_api.dart b/mobile/openapi/lib/api/albums_api.dart new file mode 100644 index 0000000000..e2db95b9e0 --- /dev/null +++ b/mobile/openapi/lib/api/albums_api.dart @@ -0,0 +1,774 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AlbumsApi { + AlbumsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Add assets to an album + /// + /// Add multiple assets to a specific album by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future addAssetsToAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Add assets to an album + /// + /// Add multiple assets to a specific album by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future?> addAssetsToAlbum(String id, BulkIdsDto bulkIdsDto, { String? key, String? slug, }) async { + final response = await addAssetsToAlbumWithHttpInfo(id, bulkIdsDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Add assets to albums + /// + /// Send a list of asset IDs and album IDs to add each asset to each album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AlbumsAddAssetsDto] albumsAddAssetsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future addAssetsToAlbumsWithHttpInfo(AlbumsAddAssetsDto albumsAddAssetsDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/assets'; + + // ignore: prefer_final_locals + Object? postBody = albumsAddAssetsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Add assets to albums + /// + /// Send a list of asset IDs and album IDs to add each asset to each album. + /// + /// Parameters: + /// + /// * [AlbumsAddAssetsDto] albumsAddAssetsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future addAssetsToAlbums(AlbumsAddAssetsDto albumsAddAssetsDto, { String? key, String? slug, }) async { + final response = await addAssetsToAlbumsWithHttpInfo(albumsAddAssetsDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumsAddAssetsResponseDto',) as AlbumsAddAssetsResponseDto; + + } + return null; + } + + /// Share album with users + /// + /// Share an album with multiple users. Each user can be given a specific role in the album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AddUsersDto] addUsersDto (required): + Future addUsersToAlbumWithHttpInfo(String id, AddUsersDto addUsersDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}/users' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = addUsersDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Share album with users + /// + /// Share an album with multiple users. Each user can be given a specific role in the album. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AddUsersDto] addUsersDto (required): + Future addUsersToAlbum(String id, AddUsersDto addUsersDto,) async { + final response = await addUsersToAlbumWithHttpInfo(id, addUsersDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto; + + } + return null; + } + + /// Create an album + /// + /// Create a new album. The album can also be created with initial users and assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [CreateAlbumDto] createAlbumDto (required): + Future createAlbumWithHttpInfo(CreateAlbumDto createAlbumDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums'; + + // ignore: prefer_final_locals + Object? postBody = createAlbumDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create an album + /// + /// Create a new album. The album can also be created with initial users and assets. + /// + /// Parameters: + /// + /// * [CreateAlbumDto] createAlbumDto (required): + Future createAlbum(CreateAlbumDto createAlbumDto,) async { + final response = await createAlbumWithHttpInfo(createAlbumDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto; + + } + return null; + } + + /// Delete an album + /// + /// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteAlbumWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete an album + /// + /// Delete a specific album by its ID. Note the album is initially trashed and then immediately scheduled for deletion, but relies on a background job to complete the process. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteAlbum(String id,) async { + final response = await deleteAlbumWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve an album + /// + /// Retrieve information about a specific album by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [bool] withoutAssets: + /// Exclude assets from response + Future getAlbumInfoWithHttpInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + if (withoutAssets != null) { + queryParams.addAll(_queryParams('', 'withoutAssets', withoutAssets)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve an album + /// + /// Retrieve information about a specific album by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [bool] withoutAssets: + /// Exclude assets from response + Future getAlbumInfo(String id, { String? key, String? slug, bool? withoutAssets, }) async { + final response = await getAlbumInfoWithHttpInfo(id, key: key, slug: slug, withoutAssets: withoutAssets, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto; + + } + return null; + } + + /// Retrieve album statistics + /// + /// Returns statistics about the albums available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getAlbumStatisticsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/statistics'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve album statistics + /// + /// Returns statistics about the albums available to the authenticated user. + Future getAlbumStatistics() async { + final response = await getAlbumStatisticsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumStatisticsResponseDto',) as AlbumStatisticsResponseDto; + + } + return null; + } + + /// List all albums + /// + /// Retrieve a list of albums available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] assetId: + /// Filter albums containing this asset ID (ignores shared parameter) + /// + /// * [bool] shared: + /// Filter by shared status: true = only shared, false = not shared, undefined = all owned albums + Future getAllAlbumsWithHttpInfo({ String? assetId, bool? shared, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (assetId != null) { + queryParams.addAll(_queryParams('', 'assetId', assetId)); + } + if (shared != null) { + queryParams.addAll(_queryParams('', 'shared', shared)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all albums + /// + /// Retrieve a list of albums available to the authenticated user. + /// + /// Parameters: + /// + /// * [String] assetId: + /// Filter albums containing this asset ID (ignores shared parameter) + /// + /// * [bool] shared: + /// Filter by shared status: true = only shared, false = not shared, undefined = all owned albums + Future?> getAllAlbums({ String? assetId, bool? shared, }) async { + final response = await getAllAlbumsWithHttpInfo( assetId: assetId, shared: shared, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Remove assets from an album + /// + /// Remove multiple assets from a specific album by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future removeAssetFromAlbumWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove assets from an album + /// + /// Remove multiple assets from a specific album by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> removeAssetFromAlbum(String id, BulkIdsDto bulkIdsDto,) async { + final response = await removeAssetFromAlbumWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Remove user from album + /// + /// Remove a user from an album. Use an ID of \"me\" to leave a shared album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] userId (required): + Future removeUserFromAlbumWithHttpInfo(String id, String userId,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}/user/{userId}' + .replaceAll('{id}', id) + .replaceAll('{userId}', userId); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove user from album + /// + /// Remove a user from an album. Use an ID of \"me\" to leave a shared album. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] userId (required): + Future removeUserFromAlbum(String id, String userId,) async { + final response = await removeUserFromAlbumWithHttpInfo(id, userId,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Update an album + /// + /// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateAlbumDto] updateAlbumDto (required): + Future updateAlbumInfoWithHttpInfo(String id, UpdateAlbumDto updateAlbumDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updateAlbumDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PATCH', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update an album + /// + /// Update the information of a specific album by its ID. This endpoint can be used to update the album name, description, sort order, etc. However, it is not used to add or remove assets or users from the album. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateAlbumDto] updateAlbumDto (required): + Future updateAlbumInfo(String id, UpdateAlbumDto updateAlbumDto,) async { + final response = await updateAlbumInfoWithHttpInfo(id, updateAlbumDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AlbumResponseDto',) as AlbumResponseDto; + + } + return null; + } + + /// Update user role + /// + /// Change the role for a specific user in a specific album. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] userId (required): + /// + /// * [UpdateAlbumUserDto] updateAlbumUserDto (required): + Future updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/albums/{id}/user/{userId}' + .replaceAll('{id}', id) + .replaceAll('{userId}', userId); + + // ignore: prefer_final_locals + Object? postBody = updateAlbumUserDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update user role + /// + /// Change the role for a specific user in a specific album. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] userId (required): + /// + /// * [UpdateAlbumUserDto] updateAlbumUserDto (required): + Future updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto,) async { + final response = await updateAlbumUserWithHttpInfo(id, userId, updateAlbumUserDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/api_keys_api.dart b/mobile/openapi/lib/api/api_keys_api.dart new file mode 100644 index 0000000000..0bd26575c6 --- /dev/null +++ b/mobile/openapi/lib/api/api_keys_api.dart @@ -0,0 +1,340 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class APIKeysApi { + APIKeysApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create an API key + /// + /// Creates a new API key. It will be limited to the permissions specified. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [APIKeyCreateDto] aPIKeyCreateDto (required): + Future createApiKeyWithHttpInfo(APIKeyCreateDto aPIKeyCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys'; + + // ignore: prefer_final_locals + Object? postBody = aPIKeyCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create an API key + /// + /// Creates a new API key. It will be limited to the permissions specified. + /// + /// Parameters: + /// + /// * [APIKeyCreateDto] aPIKeyCreateDto (required): + Future createApiKey(APIKeyCreateDto aPIKeyCreateDto,) async { + final response = await createApiKeyWithHttpInfo(aPIKeyCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'APIKeyCreateResponseDto',) as APIKeyCreateResponseDto; + + } + return null; + } + + /// Delete an API key + /// + /// Deletes an API key identified by its ID. The current user must own this API key. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteApiKeyWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete an API key + /// + /// Deletes an API key identified by its ID. The current user must own this API key. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteApiKey(String id,) async { + final response = await deleteApiKeyWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve an API key + /// + /// Retrieve an API key by its ID. The current user must own this API key. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getApiKeyWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve an API key + /// + /// Retrieve an API key by its ID. The current user must own this API key. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getApiKey(String id,) async { + final response = await getApiKeyWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'APIKeyResponseDto',) as APIKeyResponseDto; + + } + return null; + } + + /// List all API keys + /// + /// Retrieve all API keys of the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future getApiKeysWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all API keys + /// + /// Retrieve all API keys of the current user. + Future?> getApiKeys() async { + final response = await getApiKeysWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve the current API key + /// + /// Retrieve the API key that is used to access this endpoint. + /// + /// Note: This method returns the HTTP [Response]. + Future getMyApiKeyWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys/me'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve the current API key + /// + /// Retrieve the API key that is used to access this endpoint. + Future getMyApiKey() async { + final response = await getMyApiKeyWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'APIKeyResponseDto',) as APIKeyResponseDto; + + } + return null; + } + + /// Update an API key + /// + /// Updates the name and permissions of an API key by its ID. The current user must own this API key. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [APIKeyUpdateDto] aPIKeyUpdateDto (required): + Future updateApiKeyWithHttpInfo(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/api-keys/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = aPIKeyUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update an API key + /// + /// Updates the name and permissions of an API key by its ID. The current user must own this API key. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [APIKeyUpdateDto] aPIKeyUpdateDto (required): + Future updateApiKey(String id, APIKeyUpdateDto aPIKeyUpdateDto,) async { + final response = await updateApiKeyWithHttpInfo(id, aPIKeyUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'APIKeyResponseDto',) as APIKeyResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/assets_api.dart b/mobile/openapi/lib/api/assets_api.dart new file mode 100644 index 0000000000..a026b99028 --- /dev/null +++ b/mobile/openapi/lib/api/assets_api.dart @@ -0,0 +1,1839 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AssetsApi { + AssetsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Check bulk upload + /// + /// Determine which assets have already been uploaded to the server based on their SHA1 checksums. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required): + Future checkBulkUploadWithHttpInfo(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/bulk-upload-check'; + + // ignore: prefer_final_locals + Object? postBody = assetBulkUploadCheckDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Check bulk upload + /// + /// Determine which assets have already been uploaded to the server based on their SHA1 checksums. + /// + /// Parameters: + /// + /// * [AssetBulkUploadCheckDto] assetBulkUploadCheckDto (required): + Future checkBulkUpload(AssetBulkUploadCheckDto assetBulkUploadCheckDto,) async { + final response = await checkBulkUploadWithHttpInfo(assetBulkUploadCheckDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetBulkUploadCheckResponseDto',) as AssetBulkUploadCheckResponseDto; + + } + return null; + } + + /// Check existing assets + /// + /// Checks if multiple assets exist on the server and returns all existing - used by background backup + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [CheckExistingAssetsDto] checkExistingAssetsDto (required): + Future checkExistingAssetsWithHttpInfo(CheckExistingAssetsDto checkExistingAssetsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/exist'; + + // ignore: prefer_final_locals + Object? postBody = checkExistingAssetsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Check existing assets + /// + /// Checks if multiple assets exist on the server and returns all existing - used by background backup + /// + /// Parameters: + /// + /// * [CheckExistingAssetsDto] checkExistingAssetsDto (required): + Future checkExistingAssets(CheckExistingAssetsDto checkExistingAssetsDto,) async { + final response = await checkExistingAssetsWithHttpInfo(checkExistingAssetsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'CheckExistingAssetsResponseDto',) as CheckExistingAssetsResponseDto; + + } + return null; + } + + /// Copy asset + /// + /// Copy asset information like albums, tags, etc. from one asset to another. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetCopyDto] assetCopyDto (required): + Future copyAssetWithHttpInfo(AssetCopyDto assetCopyDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/copy'; + + // ignore: prefer_final_locals + Object? postBody = assetCopyDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Copy asset + /// + /// Copy asset information like albums, tags, etc. from one asset to another. + /// + /// Parameters: + /// + /// * [AssetCopyDto] assetCopyDto (required): + Future copyAsset(AssetCopyDto assetCopyDto,) async { + final response = await copyAssetWithHttpInfo(assetCopyDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete asset metadata by key + /// + /// Delete a specific metadata key-value pair associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Asset ID + /// + /// * [String] key (required): + /// Metadata key + Future deleteAssetMetadataWithHttpInfo(String id, String key,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/metadata/{key}' + .replaceAll('{id}', id) + .replaceAll('{key}', key); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete asset metadata by key + /// + /// Delete a specific metadata key-value pair associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Asset ID + /// + /// * [String] key (required): + /// Metadata key + Future deleteAssetMetadata(String id, String key,) async { + final response = await deleteAssetMetadataWithHttpInfo(id, key,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete assets + /// + /// Deletes multiple assets at the same time. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetBulkDeleteDto] assetBulkDeleteDto (required): + Future deleteAssetsWithHttpInfo(AssetBulkDeleteDto assetBulkDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets'; + + // ignore: prefer_final_locals + Object? postBody = assetBulkDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete assets + /// + /// Deletes multiple assets at the same time. + /// + /// Parameters: + /// + /// * [AssetBulkDeleteDto] assetBulkDeleteDto (required): + Future deleteAssets(AssetBulkDeleteDto assetBulkDeleteDto,) async { + final response = await deleteAssetsWithHttpInfo(assetBulkDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete asset metadata + /// + /// Delete metadata key-value pairs for multiple assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required): + Future deleteBulkAssetMetadataWithHttpInfo(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/metadata'; + + // ignore: prefer_final_locals + Object? postBody = assetMetadataBulkDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete asset metadata + /// + /// Delete metadata key-value pairs for multiple assets. + /// + /// Parameters: + /// + /// * [AssetMetadataBulkDeleteDto] assetMetadataBulkDeleteDto (required): + Future deleteBulkAssetMetadata(AssetMetadataBulkDeleteDto assetMetadataBulkDeleteDto,) async { + final response = await deleteBulkAssetMetadataWithHttpInfo(assetMetadataBulkDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Download original asset + /// + /// Downloads the original file of the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] edited: + /// Return edited asset if available + /// + /// * [String] key: + /// + /// * [String] slug: + Future downloadAssetWithHttpInfo(String id, { bool? edited, String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/original' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (edited != null) { + queryParams.addAll(_queryParams('', 'edited', edited)); + } + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Download original asset + /// + /// Downloads the original file of the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] edited: + /// Return edited asset if available + /// + /// * [String] key: + /// + /// * [String] slug: + Future downloadAsset(String id, { bool? edited, String? key, String? slug, }) async { + final response = await downloadAssetWithHttpInfo(id, edited: edited, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// Apply edits to an existing asset + /// + /// Apply a series of edit actions (crop, rotate, mirror) to the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetEditsCreateDto] assetEditsCreateDto (required): + Future editAssetWithHttpInfo(String id, AssetEditsCreateDto assetEditsCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/edits' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetEditsCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Apply edits to an existing asset + /// + /// Apply a series of edit actions (crop, rotate, mirror) to the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetEditsCreateDto] assetEditsCreateDto (required): + Future editAsset(String id, AssetEditsCreateDto assetEditsCreateDto,) async { + final response = await editAssetWithHttpInfo(id, assetEditsCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto; + + } + return null; + } + + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + /// Device ID + Future getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/device/{deviceId}' + .replaceAll('{deviceId}', deviceId); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + /// Device ID + Future?> getAllUserAssetsByDeviceId(String deviceId,) async { + final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve edits for an existing asset + /// + /// Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getAssetEditsWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/edits' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve edits for an existing asset + /// + /// Retrieve a series of edit actions (crop, rotate, mirror) associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getAssetEdits(String id,) async { + final response = await getAssetEditsWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetEditsResponseDto',) as AssetEditsResponseDto; + + } + return null; + } + + /// Retrieve an asset + /// + /// Retrieve detailed information about a specific asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getAssetInfoWithHttpInfo(String id, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve an asset + /// + /// Retrieve detailed information about a specific asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getAssetInfo(String id, { String? key, String? slug, }) async { + final response = await getAssetInfoWithHttpInfo(id, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetResponseDto',) as AssetResponseDto; + + } + return null; + } + + /// Get asset metadata + /// + /// Retrieve all metadata key-value pairs associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getAssetMetadataWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/metadata' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get asset metadata + /// + /// Retrieve all metadata key-value pairs associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + Future?> getAssetMetadata(String id,) async { + final response = await getAssetMetadataWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve asset metadata by key + /// + /// Retrieve the value of a specific metadata key associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Asset ID + /// + /// * [String] key (required): + /// Metadata key + Future getAssetMetadataByKeyWithHttpInfo(String id, String key,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/metadata/{key}' + .replaceAll('{id}', id) + .replaceAll('{key}', key); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve asset metadata by key + /// + /// Retrieve the value of a specific metadata key associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Asset ID + /// + /// * [String] key (required): + /// Metadata key + Future getAssetMetadataByKey(String id, String key,) async { + final response = await getAssetMetadataByKeyWithHttpInfo(id, key,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMetadataResponseDto',) as AssetMetadataResponseDto; + + } + return null; + } + + /// Retrieve asset OCR data + /// + /// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getAssetOcrWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/ocr' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve asset OCR data + /// + /// Retrieve all OCR (Optical Character Recognition) data associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + Future?> getAssetOcr(String id,) async { + final response = await getAssetOcrWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Get asset statistics + /// + /// Retrieve various statistics about the assets owned by the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isTrashed: + /// Filter by trash status + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + Future getAssetStatisticsWithHttpInfo({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/statistics'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (visibility != null) { + queryParams.addAll(_queryParams('', 'visibility', visibility)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get asset statistics + /// + /// Retrieve various statistics about the assets owned by the authenticated user. + /// + /// Parameters: + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isTrashed: + /// Filter by trash status + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + Future getAssetStatistics({ bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + final response = await getAssetStatisticsWithHttpInfo( isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetStatsResponseDto',) as AssetStatsResponseDto; + + } + return null; + } + + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [num] count: + /// Number of random assets to return + Future getRandomWithHttpInfo({ num? count, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/random'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (count != null) { + queryParams.addAll(_queryParams('', 'count', count)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. + /// + /// Parameters: + /// + /// * [num] count: + /// Number of random assets to return + Future?> getRandom({ num? count, }) async { + final response = await getRandomWithHttpInfo( count: count, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Play asset video + /// + /// Streams the video file for the specified asset. This endpoint also supports byte range requests. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future playAssetVideoWithHttpInfo(String id, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/video/playback' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Play asset video + /// + /// Streams the video file for the specified asset. This endpoint also supports byte range requests. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future playAssetVideo(String id, { String? key, String? slug, }) async { + final response = await playAssetVideoWithHttpInfo(id, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// Remove edits from an existing asset + /// + /// Removes all edit actions (crop, rotate, mirror) associated with the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removeAssetEditsWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/edits' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove edits from an existing asset + /// + /// Removes all edit actions (crop, rotate, mirror) associated with the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removeAssetEdits(String id,) async { + final response = await removeAssetEditsWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Replace asset + /// + /// Replace the asset with new file, without changing its id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + Future replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/original' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['multipart/form-data']; + + bool hasFields = false; + final mp = MultipartRequest('PUT', Uri.parse(apiPath)); + if (assetData != null) { + hasFields = true; + mp.fields[r'assetData'] = assetData.field; + mp.files.add(assetData); + } + if (deviceAssetId != null) { + hasFields = true; + mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId); + } + if (deviceId != null) { + hasFields = true; + mp.fields[r'deviceId'] = parameterToString(deviceId); + } + if (duration != null) { + hasFields = true; + mp.fields[r'duration'] = parameterToString(duration); + } + if (fileCreatedAt != null) { + hasFields = true; + mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt); + } + if (fileModifiedAt != null) { + hasFields = true; + mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt); + } + if (filename != null) { + hasFields = true; + mp.fields[r'filename'] = parameterToString(filename); + } + if (hasFields) { + postBody = mp; + } + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Replace asset + /// + /// Replace the asset with new file, without changing its id. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + Future replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async { + final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto; + + } + return null; + } + + /// Run an asset job + /// + /// Run a specific job on a set of assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobsWithHttpInfo(AssetJobsDto assetJobsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/jobs'; + + // ignore: prefer_final_locals + Object? postBody = assetJobsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Run an asset job + /// + /// Run a specific job on a set of assets. + /// + /// Parameters: + /// + /// * [AssetJobsDto] assetJobsDto (required): + Future runAssetJobs(AssetJobsDto assetJobsDto,) async { + final response = await runAssetJobsWithHttpInfo(assetJobsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Update an asset + /// + /// Update information of a specific asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateAssetDto] updateAssetDto (required): + Future updateAssetWithHttpInfo(String id, UpdateAssetDto updateAssetDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updateAssetDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update an asset + /// + /// Update information of a specific asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateAssetDto] updateAssetDto (required): + Future updateAsset(String id, UpdateAssetDto updateAssetDto,) async { + final response = await updateAssetWithHttpInfo(id, updateAssetDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetResponseDto',) as AssetResponseDto; + + } + return null; + } + + /// Update asset metadata + /// + /// Update or add metadata key-value pairs for the specified asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetMetadataUpsertDto] assetMetadataUpsertDto (required): + Future updateAssetMetadataWithHttpInfo(String id, AssetMetadataUpsertDto assetMetadataUpsertDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/metadata' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetMetadataUpsertDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update asset metadata + /// + /// Update or add metadata key-value pairs for the specified asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetMetadataUpsertDto] assetMetadataUpsertDto (required): + Future?> updateAssetMetadata(String id, AssetMetadataUpsertDto assetMetadataUpsertDto,) async { + final response = await updateAssetMetadataWithHttpInfo(id, assetMetadataUpsertDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update assets + /// + /// Updates multiple assets at the same time. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): + Future updateAssetsWithHttpInfo(AssetBulkUpdateDto assetBulkUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets'; + + // ignore: prefer_final_locals + Object? postBody = assetBulkUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update assets + /// + /// Updates multiple assets at the same time. + /// + /// Parameters: + /// + /// * [AssetBulkUpdateDto] assetBulkUpdateDto (required): + Future updateAssets(AssetBulkUpdateDto assetBulkUpdateDto,) async { + final response = await updateAssetsWithHttpInfo(assetBulkUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Upsert asset metadata + /// + /// Upsert metadata key-value pairs for multiple assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required): + Future updateBulkAssetMetadataWithHttpInfo(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/metadata'; + + // ignore: prefer_final_locals + Object? postBody = assetMetadataBulkUpsertDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Upsert asset metadata + /// + /// Upsert metadata key-value pairs for multiple assets. + /// + /// Parameters: + /// + /// * [AssetMetadataBulkUpsertDto] assetMetadataBulkUpsertDto (required): + Future?> updateBulkAssetMetadata(AssetMetadataBulkUpsertDto assetMetadataBulkUpsertDto,) async { + final response = await updateBulkAssetMetadataWithHttpInfo(assetMetadataBulkUpsertDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Upload asset + /// + /// Uploads a new asset to the server. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] xImmichChecksum: + /// sha1 checksum that can be used for duplicate detection before the file is uploaded + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + /// + /// * [bool] isFavorite: + /// Mark as favorite + /// + /// * [String] livePhotoVideoId: + /// Live photo video ID + /// + /// * [List] metadata: + /// Asset metadata items + /// + /// * [MultipartFile] sidecarData: + /// Sidecar file data + /// + /// * [AssetVisibility] visibility: + /// Asset visibility + Future uploadAssetWithHttpInfo(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + if (xImmichChecksum != null) { + headerParams[r'x-immich-checksum'] = parameterToString(xImmichChecksum); + } + + const contentTypes = ['multipart/form-data']; + + bool hasFields = false; + final mp = MultipartRequest('POST', Uri.parse(apiPath)); + if (assetData != null) { + hasFields = true; + mp.fields[r'assetData'] = assetData.field; + mp.files.add(assetData); + } + if (deviceAssetId != null) { + hasFields = true; + mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId); + } + if (deviceId != null) { + hasFields = true; + mp.fields[r'deviceId'] = parameterToString(deviceId); + } + if (duration != null) { + hasFields = true; + mp.fields[r'duration'] = parameterToString(duration); + } + if (fileCreatedAt != null) { + hasFields = true; + mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt); + } + if (fileModifiedAt != null) { + hasFields = true; + mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt); + } + if (filename != null) { + hasFields = true; + mp.fields[r'filename'] = parameterToString(filename); + } + if (isFavorite != null) { + hasFields = true; + mp.fields[r'isFavorite'] = parameterToString(isFavorite); + } + if (livePhotoVideoId != null) { + hasFields = true; + mp.fields[r'livePhotoVideoId'] = parameterToString(livePhotoVideoId); + } + if (metadata != null) { + hasFields = true; + mp.fields[r'metadata'] = parameterToString(metadata); + } + if (sidecarData != null) { + hasFields = true; + mp.fields[r'sidecarData'] = sidecarData.field; + mp.files.add(sidecarData); + } + if (visibility != null) { + hasFields = true; + mp.fields[r'visibility'] = parameterToString(visibility); + } + if (hasFields) { + postBody = mp; + } + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Upload asset + /// + /// Uploads a new asset to the server. + /// + /// Parameters: + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] xImmichChecksum: + /// sha1 checksum that can be used for duplicate detection before the file is uploaded + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + /// + /// * [bool] isFavorite: + /// Mark as favorite + /// + /// * [String] livePhotoVideoId: + /// Live photo video ID + /// + /// * [List] metadata: + /// Asset metadata items + /// + /// * [MultipartFile] sidecarData: + /// Sidecar file data + /// + /// * [AssetVisibility] visibility: + /// Asset visibility + Future uploadAsset(MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async { + final response = await uploadAssetWithHttpInfo(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto; + + } + return null; + } + + /// View asset thumbnail + /// + /// Retrieve the thumbnail image for the specified asset. Viewing the fullsize thumbnail might redirect to downloadAsset, which requires a different permission. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] edited: + /// Return edited asset if available + /// + /// * [String] key: + /// + /// * [AssetMediaSize] size: + /// Asset media size + /// + /// * [String] slug: + Future viewAssetWithHttpInfo(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/thumbnail' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (edited != null) { + queryParams.addAll(_queryParams('', 'edited', edited)); + } + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// View asset thumbnail + /// + /// Retrieve the thumbnail image for the specified asset. Viewing the fullsize thumbnail might redirect to downloadAsset, which requires a different permission. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] edited: + /// Return edited asset if available + /// + /// * [String] key: + /// + /// * [AssetMediaSize] size: + /// Asset media size + /// + /// * [String] slug: + Future viewAsset(String id, { bool? edited, String? key, AssetMediaSize? size, String? slug, }) async { + final response = await viewAssetWithHttpInfo(id, edited: edited, key: key, size: size, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/authentication_admin_api.dart b/mobile/openapi/lib/api/authentication_admin_api.dart new file mode 100644 index 0000000000..0a4b91ebc3 --- /dev/null +++ b/mobile/openapi/lib/api/authentication_admin_api.dart @@ -0,0 +1,58 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AuthenticationAdminApi { + AuthenticationAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Unlink all OAuth accounts + /// + /// Unlinks all OAuth accounts associated with user accounts in the system. + /// + /// Note: This method returns the HTTP [Response]. + Future unlinkAllOAuthAccountsAdminWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/auth/unlink-all'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Unlink all OAuth accounts + /// + /// Unlinks all OAuth accounts associated with user accounts in the system. + Future unlinkAllOAuthAccountsAdmin() async { + final response = await unlinkAllOAuthAccountsAdminWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/authentication_api.dart b/mobile/openapi/lib/api/authentication_api.dart new file mode 100644 index 0000000000..52d46a525b --- /dev/null +++ b/mobile/openapi/lib/api/authentication_api.dart @@ -0,0 +1,818 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AuthenticationApi { + AuthenticationApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Change password + /// + /// Change the password of the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [ChangePasswordDto] changePasswordDto (required): + Future changePasswordWithHttpInfo(ChangePasswordDto changePasswordDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/change-password'; + + // ignore: prefer_final_locals + Object? postBody = changePasswordDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Change password + /// + /// Change the password of the current user. + /// + /// Parameters: + /// + /// * [ChangePasswordDto] changePasswordDto (required): + Future changePassword(ChangePasswordDto changePasswordDto,) async { + final response = await changePasswordWithHttpInfo(changePasswordDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Change pin code + /// + /// Change the pin code for the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PinCodeChangeDto] pinCodeChangeDto (required): + Future changePinCodeWithHttpInfo(PinCodeChangeDto pinCodeChangeDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/pin-code'; + + // ignore: prefer_final_locals + Object? postBody = pinCodeChangeDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Change pin code + /// + /// Change the pin code for the current user. + /// + /// Parameters: + /// + /// * [PinCodeChangeDto] pinCodeChangeDto (required): + Future changePinCode(PinCodeChangeDto pinCodeChangeDto,) async { + final response = await changePinCodeWithHttpInfo(pinCodeChangeDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Finish OAuth + /// + /// Complete the OAuth authorization process by exchanging the authorization code for a session token. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future finishOAuthWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/callback'; + + // ignore: prefer_final_locals + Object? postBody = oAuthCallbackDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Finish OAuth + /// + /// Complete the OAuth authorization process by exchanging the authorization code for a session token. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future finishOAuth(OAuthCallbackDto oAuthCallbackDto,) async { + final response = await finishOAuthWithHttpInfo(oAuthCallbackDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto; + + } + return null; + } + + /// Retrieve auth status + /// + /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. + /// + /// Note: This method returns the HTTP [Response]. + Future getAuthStatusWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/status'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve auth status + /// + /// Get information about the current session, including whether the user has a password, and if the session can access locked assets. + Future getAuthStatus() async { + final response = await getAuthStatusWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AuthStatusResponseDto',) as AuthStatusResponseDto; + + } + return null; + } + + /// Link OAuth account + /// + /// Link an OAuth account to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future linkOAuthAccountWithHttpInfo(OAuthCallbackDto oAuthCallbackDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/link'; + + // ignore: prefer_final_locals + Object? postBody = oAuthCallbackDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Link OAuth account + /// + /// Link an OAuth account to the authenticated user. + /// + /// Parameters: + /// + /// * [OAuthCallbackDto] oAuthCallbackDto (required): + Future linkOAuthAccount(OAuthCallbackDto oAuthCallbackDto,) async { + final response = await linkOAuthAccountWithHttpInfo(oAuthCallbackDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Lock auth session + /// + /// Remove elevated access to locked assets from the current session. + /// + /// Note: This method returns the HTTP [Response]. + Future lockAuthSessionWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/session/lock'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Lock auth session + /// + /// Remove elevated access to locked assets from the current session. + Future lockAuthSession() async { + final response = await lockAuthSessionWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Login + /// + /// Login with username and password and receive a session token. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [LoginCredentialDto] loginCredentialDto (required): + Future loginWithHttpInfo(LoginCredentialDto loginCredentialDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/login'; + + // ignore: prefer_final_locals + Object? postBody = loginCredentialDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Login + /// + /// Login with username and password and receive a session token. + /// + /// Parameters: + /// + /// * [LoginCredentialDto] loginCredentialDto (required): + Future login(LoginCredentialDto loginCredentialDto,) async { + final response = await loginWithHttpInfo(loginCredentialDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LoginResponseDto',) as LoginResponseDto; + + } + return null; + } + + /// Logout + /// + /// Logout the current user and invalidate the session token. + /// + /// Note: This method returns the HTTP [Response]. + Future logoutWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/logout'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Logout + /// + /// Logout the current user and invalidate the session token. + Future logout() async { + final response = await logoutWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LogoutResponseDto',) as LogoutResponseDto; + + } + return null; + } + + /// Redirect OAuth to mobile + /// + /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. + /// + /// Note: This method returns the HTTP [Response]. + Future redirectOAuthToMobileWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/mobile-redirect'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Redirect OAuth to mobile + /// + /// Requests to this URL are automatically forwarded to the mobile app, and is used in some cases for OAuth redirecting. + Future redirectOAuthToMobile() async { + final response = await redirectOAuthToMobileWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Reset pin code + /// + /// Reset the pin code for the current user by providing the account password + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PinCodeResetDto] pinCodeResetDto (required): + Future resetPinCodeWithHttpInfo(PinCodeResetDto pinCodeResetDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/pin-code'; + + // ignore: prefer_final_locals + Object? postBody = pinCodeResetDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Reset pin code + /// + /// Reset the pin code for the current user by providing the account password + /// + /// Parameters: + /// + /// * [PinCodeResetDto] pinCodeResetDto (required): + Future resetPinCode(PinCodeResetDto pinCodeResetDto,) async { + final response = await resetPinCodeWithHttpInfo(pinCodeResetDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Setup pin code + /// + /// Setup a new pin code for the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PinCodeSetupDto] pinCodeSetupDto (required): + Future setupPinCodeWithHttpInfo(PinCodeSetupDto pinCodeSetupDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/pin-code'; + + // ignore: prefer_final_locals + Object? postBody = pinCodeSetupDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Setup pin code + /// + /// Setup a new pin code for the current user. + /// + /// Parameters: + /// + /// * [PinCodeSetupDto] pinCodeSetupDto (required): + Future setupPinCode(PinCodeSetupDto pinCodeSetupDto,) async { + final response = await setupPinCodeWithHttpInfo(pinCodeSetupDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Register admin + /// + /// Create the first admin user in the system. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SignUpDto] signUpDto (required): + Future signUpAdminWithHttpInfo(SignUpDto signUpDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/admin-sign-up'; + + // ignore: prefer_final_locals + Object? postBody = signUpDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Register admin + /// + /// Create the first admin user in the system. + /// + /// Parameters: + /// + /// * [SignUpDto] signUpDto (required): + Future signUpAdmin(SignUpDto signUpDto,) async { + final response = await signUpAdminWithHttpInfo(signUpDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Start OAuth + /// + /// Initiate the OAuth authorization process. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OAuthConfigDto] oAuthConfigDto (required): + Future startOAuthWithHttpInfo(OAuthConfigDto oAuthConfigDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/authorize'; + + // ignore: prefer_final_locals + Object? postBody = oAuthConfigDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Start OAuth + /// + /// Initiate the OAuth authorization process. + /// + /// Parameters: + /// + /// * [OAuthConfigDto] oAuthConfigDto (required): + Future startOAuth(OAuthConfigDto oAuthConfigDto,) async { + final response = await startOAuthWithHttpInfo(oAuthConfigDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OAuthAuthorizeResponseDto',) as OAuthAuthorizeResponseDto; + + } + return null; + } + + /// Unlink OAuth account + /// + /// Unlink the OAuth account from the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future unlinkOAuthAccountWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/oauth/unlink'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Unlink OAuth account + /// + /// Unlink the OAuth account from the authenticated user. + Future unlinkOAuthAccount() async { + final response = await unlinkOAuthAccountWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Unlock auth session + /// + /// Temporarily grant the session elevated access to locked assets by providing the correct PIN code. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SessionUnlockDto] sessionUnlockDto (required): + Future unlockAuthSessionWithHttpInfo(SessionUnlockDto sessionUnlockDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/session/unlock'; + + // ignore: prefer_final_locals + Object? postBody = sessionUnlockDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Unlock auth session + /// + /// Temporarily grant the session elevated access to locked assets by providing the correct PIN code. + /// + /// Parameters: + /// + /// * [SessionUnlockDto] sessionUnlockDto (required): + Future unlockAuthSession(SessionUnlockDto sessionUnlockDto,) async { + final response = await unlockAuthSessionWithHttpInfo(sessionUnlockDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Validate access token + /// + /// Validate the current authorization method is still valid. + /// + /// Note: This method returns the HTTP [Response]. + Future validateAccessTokenWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/auth/validateToken'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Validate access token + /// + /// Validate the current authorization method is still valid. + Future validateAccessToken() async { + final response = await validateAccessTokenWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ValidateAccessTokenResponseDto',) as ValidateAccessTokenResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/database_backups_admin_api.dart b/mobile/openapi/lib/api/database_backups_admin_api.dart new file mode 100644 index 0000000000..fbd485f86f --- /dev/null +++ b/mobile/openapi/lib/api/database_backups_admin_api.dart @@ -0,0 +1,269 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class DatabaseBackupsAdminApi { + DatabaseBackupsAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Delete database backup + /// + /// Delete a backup by its filename + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): + Future deleteDatabaseBackupWithHttpInfo(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/database-backups'; + + // ignore: prefer_final_locals + Object? postBody = databaseBackupDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete database backup + /// + /// Delete a backup by its filename + /// + /// Parameters: + /// + /// * [DatabaseBackupDeleteDto] databaseBackupDeleteDto (required): + Future deleteDatabaseBackup(DatabaseBackupDeleteDto databaseBackupDeleteDto,) async { + final response = await deleteDatabaseBackupWithHttpInfo(databaseBackupDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Download database backup + /// + /// Downloads the database backup file + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] filename (required): + Future downloadDatabaseBackupWithHttpInfo(String filename,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/database-backups/{filename}' + .replaceAll('{filename}', filename); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Download database backup + /// + /// Downloads the database backup file + /// + /// Parameters: + /// + /// * [String] filename (required): + Future downloadDatabaseBackup(String filename,) async { + final response = await downloadDatabaseBackupWithHttpInfo(filename,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// List database backups + /// + /// Get the list of the successful and failed backups + /// + /// Note: This method returns the HTTP [Response]. + Future listDatabaseBackupsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/database-backups'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List database backups + /// + /// Get the list of the successful and failed backups + Future listDatabaseBackups() async { + final response = await listDatabaseBackupsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'DatabaseBackupListResponseDto',) as DatabaseBackupListResponseDto; + + } + return null; + } + + /// Start database backup restore flow + /// + /// Put Immich into maintenance mode to restore a backup (Immich must not be configured) + /// + /// Note: This method returns the HTTP [Response]. + Future startDatabaseRestoreFlowWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/database-backups/start-restore'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Start database backup restore flow + /// + /// Put Immich into maintenance mode to restore a backup (Immich must not be configured) + Future startDatabaseRestoreFlow() async { + final response = await startDatabaseRestoreFlowWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Upload database backup + /// + /// Uploads .sql/.sql.gz file to restore backup from + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MultipartFile] file: + Future uploadDatabaseBackupWithHttpInfo({ MultipartFile? file, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/database-backups/upload'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['multipart/form-data']; + + bool hasFields = false; + final mp = MultipartRequest('POST', Uri.parse(apiPath)); + if (file != null) { + hasFields = true; + mp.fields[r'file'] = file.field; + mp.files.add(file); + } + if (hasFields) { + postBody = mp; + } + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Upload database backup + /// + /// Uploads .sql/.sql.gz file to restore backup from + /// + /// Parameters: + /// + /// * [MultipartFile] file: + Future uploadDatabaseBackup({ MultipartFile? file, }) async { + final response = await uploadDatabaseBackupWithHttpInfo( file: file, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/deprecated_api.dart b/mobile/openapi/lib/api/deprecated_api.dart new file mode 100644 index 0000000000..33bcaf062c --- /dev/null +++ b/mobile/openapi/lib/api/deprecated_api.dart @@ -0,0 +1,576 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class DeprecatedApi { + DeprecatedApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future createPartnerDeprecatedWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Parameters: + /// + /// * [String] id (required): + Future createPartnerDeprecated(String id,) async { + final response = await createPartnerDeprecatedWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } + + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + /// Device ID + Future getAllUserAssetsByDeviceIdWithHttpInfo(String deviceId,) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/device/{deviceId}' + .replaceAll('{deviceId}', deviceId); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve assets by device ID + /// + /// Get all asset of a device that are in the database, ID only. + /// + /// Parameters: + /// + /// * [String] deviceId (required): + /// Device ID + Future?> getAllUserAssetsByDeviceId(String deviceId,) async { + final response = await getAllUserAssetsByDeviceIdWithHttpInfo(deviceId,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSyncWithHttpInfo(AssetDeltaSyncDto assetDeltaSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/delta-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetDeltaSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSync(AssetDeltaSyncDto assetDeltaSyncDto,) async { + final response = await getDeltaSyncWithHttpInfo(assetDeltaSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetDeltaSyncResponseDto',) as AssetDeltaSyncResponseDto; + + } + return null; + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future getFullSyncForUserWithHttpInfo(AssetFullSyncDto assetFullSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/full-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetFullSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future?> getFullSyncForUser(AssetFullSyncDto assetFullSyncDto,) async { + final response = await getFullSyncForUserWithHttpInfo(assetFullSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. + /// + /// Note: This method returns the HTTP [Response]. + Future getQueuesLegacyWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/jobs'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. + Future getQueuesLegacy() async { + final response = await getQueuesLegacyWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto; + + } + return null; + } + + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [num] count: + /// Number of random assets to return + Future getRandomWithHttpInfo({ num? count, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/random'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (count != null) { + queryParams.addAll(_queryParams('', 'count', count)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get random assets + /// + /// Retrieve a specified number of random assets for the authenticated user. + /// + /// Parameters: + /// + /// * [num] count: + /// Number of random assets to return + Future?> getRandom({ num? count, }) async { + final response = await getRandomWithHttpInfo( count: count, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Replace asset + /// + /// Replace the asset with new file, without changing its id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + Future replaceAssetWithHttpInfo(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/assets/{id}/original' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['multipart/form-data']; + + bool hasFields = false; + final mp = MultipartRequest('PUT', Uri.parse(apiPath)); + if (assetData != null) { + hasFields = true; + mp.fields[r'assetData'] = assetData.field; + mp.files.add(assetData); + } + if (deviceAssetId != null) { + hasFields = true; + mp.fields[r'deviceAssetId'] = parameterToString(deviceAssetId); + } + if (deviceId != null) { + hasFields = true; + mp.fields[r'deviceId'] = parameterToString(deviceId); + } + if (duration != null) { + hasFields = true; + mp.fields[r'duration'] = parameterToString(duration); + } + if (fileCreatedAt != null) { + hasFields = true; + mp.fields[r'fileCreatedAt'] = parameterToString(fileCreatedAt); + } + if (fileModifiedAt != null) { + hasFields = true; + mp.fields[r'fileModifiedAt'] = parameterToString(fileModifiedAt); + } + if (filename != null) { + hasFields = true; + mp.fields[r'filename'] = parameterToString(filename); + } + if (hasFields) { + postBody = mp; + } + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Replace asset + /// + /// Replace the asset with new file, without changing its id. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MultipartFile] assetData (required): + /// Asset file data + /// + /// * [String] deviceAssetId (required): + /// Device asset ID + /// + /// * [String] deviceId (required): + /// Device ID + /// + /// * [DateTime] fileCreatedAt (required): + /// File creation date + /// + /// * [DateTime] fileModifiedAt (required): + /// File modification date + /// + /// * [String] key: + /// + /// * [String] slug: + /// + /// * [String] duration: + /// Duration (for videos) + /// + /// * [String] filename: + /// Filename + Future replaceAsset(String id, MultipartFile assetData, String deviceAssetId, String deviceId, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? duration, String? filename, }) async { + final response = await replaceAssetWithHttpInfo(id, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, key: key, slug: slug, duration: duration, filename: filename, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetMediaResponseDto',) as AssetMediaResponseDto; + + } + return null; + } + + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/jobs/{name}' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody = queueCommandDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async { + final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/download_api.dart b/mobile/openapi/lib/api/download_api.dart new file mode 100644 index 0000000000..4d0c5c8165 --- /dev/null +++ b/mobile/openapi/lib/api/download_api.dart @@ -0,0 +1,160 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class DownloadApi { + DownloadApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Download asset archive + /// + /// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DownloadArchiveDto] downloadArchiveDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future downloadArchiveWithHttpInfo(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/download/archive'; + + // ignore: prefer_final_locals + Object? postBody = downloadArchiveDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Download asset archive + /// + /// Download a ZIP archive containing the specified assets. The assets must have been previously requested via the \"getDownloadInfo\" endpoint. + /// + /// Parameters: + /// + /// * [DownloadArchiveDto] downloadArchiveDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future downloadArchive(DownloadArchiveDto downloadArchiveDto, { String? key, String? slug, }) async { + final response = await downloadArchiveWithHttpInfo(downloadArchiveDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// Retrieve download information + /// + /// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DownloadInfoDto] downloadInfoDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getDownloadInfoWithHttpInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/download/info'; + + // ignore: prefer_final_locals + Object? postBody = downloadInfoDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve download information + /// + /// Retrieve information about how to request a download for the specified assets or album. The response includes groups of assets that can be downloaded together. + /// + /// Parameters: + /// + /// * [DownloadInfoDto] downloadInfoDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future getDownloadInfo(DownloadInfoDto downloadInfoDto, { String? key, String? slug, }) async { + final response = await getDownloadInfoWithHttpInfo(downloadInfoDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'DownloadResponseDto',) as DownloadResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/duplicates_api.dart b/mobile/openapi/lib/api/duplicates_api.dart new file mode 100644 index 0000000000..e873537592 --- /dev/null +++ b/mobile/openapi/lib/api/duplicates_api.dart @@ -0,0 +1,225 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class DuplicatesApi { + DuplicatesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Delete a duplicate + /// + /// Delete a single duplicate asset specified by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteDuplicateWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/duplicates/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a duplicate + /// + /// Delete a single duplicate asset specified by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteDuplicate(String id,) async { + final response = await deleteDuplicateWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete duplicates + /// + /// Delete multiple duplicate assets specified by their IDs. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deleteDuplicatesWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/duplicates'; + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete duplicates + /// + /// Delete multiple duplicate assets specified by their IDs. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deleteDuplicates(BulkIdsDto bulkIdsDto,) async { + final response = await deleteDuplicatesWithHttpInfo(bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve duplicates + /// + /// Retrieve a list of duplicate assets available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getAssetDuplicatesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/duplicates'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve duplicates + /// + /// Retrieve a list of duplicate assets available to the authenticated user. + Future?> getAssetDuplicates() async { + final response = await getAssetDuplicatesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Resolve duplicate groups + /// + /// Resolve duplicate groups by synchronizing metadata across assets and deleting/trashing duplicates. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DuplicateResolveDto] duplicateResolveDto (required): + Future resolveDuplicatesWithHttpInfo(DuplicateResolveDto duplicateResolveDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/duplicates/resolve'; + + // ignore: prefer_final_locals + Object? postBody = duplicateResolveDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Resolve duplicate groups + /// + /// Resolve duplicate groups by synchronizing metadata across assets and deleting/trashing duplicates. + /// + /// Parameters: + /// + /// * [DuplicateResolveDto] duplicateResolveDto (required): + Future?> resolveDuplicates(DuplicateResolveDto duplicateResolveDto,) async { + final response = await resolveDuplicatesWithHttpInfo(duplicateResolveDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/faces_api.dart b/mobile/openapi/lib/api/faces_api.dart new file mode 100644 index 0000000000..43d63b47b9 --- /dev/null +++ b/mobile/openapi/lib/api/faces_api.dart @@ -0,0 +1,243 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class FacesApi { + FacesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a face + /// + /// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetFaceCreateDto] assetFaceCreateDto (required): + Future createFaceWithHttpInfo(AssetFaceCreateDto assetFaceCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/faces'; + + // ignore: prefer_final_locals + Object? postBody = assetFaceCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a face + /// + /// Create a new face that has not been discovered by facial recognition. The content of the bounding box is considered a face. + /// + /// Parameters: + /// + /// * [AssetFaceCreateDto] assetFaceCreateDto (required): + Future createFace(AssetFaceCreateDto assetFaceCreateDto,) async { + final response = await createFaceWithHttpInfo(assetFaceCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete a face + /// + /// Delete a face identified by the id. Optionally can be force deleted. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetFaceDeleteDto] assetFaceDeleteDto (required): + Future deleteFaceWithHttpInfo(String id, AssetFaceDeleteDto assetFaceDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/faces/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetFaceDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a face + /// + /// Delete a face identified by the id. Optionally can be force deleted. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetFaceDeleteDto] assetFaceDeleteDto (required): + Future deleteFace(String id, AssetFaceDeleteDto assetFaceDeleteDto,) async { + final response = await deleteFaceWithHttpInfo(id, assetFaceDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve faces for asset + /// + /// Retrieve all faces belonging to an asset. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Face ID + Future getFacesWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/faces'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'id', id)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve faces for asset + /// + /// Retrieve all faces belonging to an asset. + /// + /// Parameters: + /// + /// * [String] id (required): + /// Face ID + Future?> getFaces(String id,) async { + final response = await getFacesWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Re-assign a face to another person + /// + /// Re-assign the face provided in the body to the person identified by the id in the path parameter. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [FaceDto] faceDto (required): + Future reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/faces/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = faceDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Re-assign a face to another person + /// + /// Re-assign the face provided in the body to the person identified by the id in the path parameter. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [FaceDto] faceDto (required): + Future reassignFacesById(String id, FaceDto faceDto,) async { + final response = await reassignFacesByIdWithHttpInfo(id, faceDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/jobs_api.dart b/mobile/openapi/lib/api/jobs_api.dart new file mode 100644 index 0000000000..41517f8144 --- /dev/null +++ b/mobile/openapi/lib/api/jobs_api.dart @@ -0,0 +1,177 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class JobsApi { + JobsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a manual job + /// + /// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [JobCreateDto] jobCreateDto (required): + Future createJobWithHttpInfo(JobCreateDto jobCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/jobs'; + + // ignore: prefer_final_locals + Object? postBody = jobCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a manual job + /// + /// Run a specific job. Most jobs are queued automatically, but this endpoint allows for manual creation of a handful of jobs, including various cleanup tasks, as well as creating a new database backup. + /// + /// Parameters: + /// + /// * [JobCreateDto] jobCreateDto (required): + Future createJob(JobCreateDto jobCreateDto,) async { + final response = await createJobWithHttpInfo(jobCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. + /// + /// Note: This method returns the HTTP [Response]. + Future getQueuesLegacyWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/jobs'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve queue counts and status + /// + /// Retrieve the counts of the current queue, as well as the current status. + Future getQueuesLegacy() async { + final response = await getQueuesLegacyWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueuesResponseLegacyDto',) as QueuesResponseLegacyDto; + + } + return null; + } + + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacyWithHttpInfo(QueueName name, QueueCommandDto queueCommandDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/jobs/{name}' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody = queueCommandDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Run jobs + /// + /// Queue all assets for a specific job type. Defaults to only queueing assets that have not yet been processed, but the force command can be used to re-process all assets. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueCommandDto] queueCommandDto (required): + Future runQueueCommandLegacy(QueueName name, QueueCommandDto queueCommandDto,) async { + final response = await runQueueCommandLegacyWithHttpInfo(name, queueCommandDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseLegacyDto',) as QueueResponseLegacyDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/libraries_api.dart b/mobile/openapi/lib/api/libraries_api.dart new file mode 100644 index 0000000000..ca59f823fe --- /dev/null +++ b/mobile/openapi/lib/api/libraries_api.dart @@ -0,0 +1,459 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class LibrariesApi { + LibrariesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a library + /// + /// Create a new external library. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [CreateLibraryDto] createLibraryDto (required): + Future createLibraryWithHttpInfo(CreateLibraryDto createLibraryDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries'; + + // ignore: prefer_final_locals + Object? postBody = createLibraryDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a library + /// + /// Create a new external library. + /// + /// Parameters: + /// + /// * [CreateLibraryDto] createLibraryDto (required): + Future createLibrary(CreateLibraryDto createLibraryDto,) async { + final response = await createLibraryWithHttpInfo(createLibraryDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryResponseDto',) as LibraryResponseDto; + + } + return null; + } + + /// Delete a library + /// + /// Delete an external library by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteLibraryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a library + /// + /// Delete an external library by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteLibrary(String id,) async { + final response = await deleteLibraryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve libraries + /// + /// Retrieve a list of external libraries. + /// + /// Note: This method returns the HTTP [Response]. + Future getAllLibrariesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve libraries + /// + /// Retrieve a list of external libraries. + Future?> getAllLibraries() async { + final response = await getAllLibrariesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve a library + /// + /// Retrieve an external library by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getLibraryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a library + /// + /// Retrieve an external library by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getLibrary(String id,) async { + final response = await getLibraryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryResponseDto',) as LibraryResponseDto; + + } + return null; + } + + /// Retrieve library statistics + /// + /// Retrieve statistics for a specific external library, including number of videos, images, and storage usage. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getLibraryStatisticsWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}/statistics' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve library statistics + /// + /// Retrieve statistics for a specific external library, including number of videos, images, and storage usage. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getLibraryStatistics(String id,) async { + final response = await getLibraryStatisticsWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryStatsResponseDto',) as LibraryStatsResponseDto; + + } + return null; + } + + /// Scan a library + /// + /// Queue a scan for the external library to find and import new assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future scanLibraryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}/scan' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Scan a library + /// + /// Queue a scan for the external library to find and import new assets. + /// + /// Parameters: + /// + /// * [String] id (required): + Future scanLibrary(String id,) async { + final response = await scanLibraryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Update a library + /// + /// Update an existing external library. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateLibraryDto] updateLibraryDto (required): + Future updateLibraryWithHttpInfo(String id, UpdateLibraryDto updateLibraryDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = updateLibraryDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a library + /// + /// Update an existing external library. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UpdateLibraryDto] updateLibraryDto (required): + Future updateLibrary(String id, UpdateLibraryDto updateLibraryDto,) async { + final response = await updateLibraryWithHttpInfo(id, updateLibraryDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LibraryResponseDto',) as LibraryResponseDto; + + } + return null; + } + + /// Validate library settings + /// + /// Validate the settings of an external library. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [ValidateLibraryDto] validateLibraryDto (required): + Future validateWithHttpInfo(String id, ValidateLibraryDto validateLibraryDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/libraries/{id}/validate' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = validateLibraryDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Validate library settings + /// + /// Validate the settings of an external library. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [ValidateLibraryDto] validateLibraryDto (required): + Future validate(String id, ValidateLibraryDto validateLibraryDto,) async { + final response = await validateWithHttpInfo(id, validateLibraryDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ValidateLibraryResponseDto',) as ValidateLibraryResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/maintenance_admin_api.dart b/mobile/openapi/lib/api/maintenance_admin_api.dart new file mode 100644 index 0000000000..0f953f1634 --- /dev/null +++ b/mobile/openapi/lib/api/maintenance_admin_api.dart @@ -0,0 +1,218 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MaintenanceAdminApi { + MaintenanceAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Detect existing install + /// + /// Collect integrity checks and other heuristics about local data. + /// + /// Note: This method returns the HTTP [Response]. + Future detectPriorInstallWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/maintenance/detect-install'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Detect existing install + /// + /// Collect integrity checks and other heuristics about local data. + Future detectPriorInstall() async { + final response = await detectPriorInstallWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceDetectInstallResponseDto',) as MaintenanceDetectInstallResponseDto; + + } + return null; + } + + /// Get maintenance mode status + /// + /// Fetch information about the currently running maintenance action. + /// + /// Note: This method returns the HTTP [Response]. + Future getMaintenanceStatusWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/maintenance/status'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get maintenance mode status + /// + /// Fetch information about the currently running maintenance action. + Future getMaintenanceStatus() async { + final response = await getMaintenanceStatusWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceStatusResponseDto',) as MaintenanceStatusResponseDto; + + } + return null; + } + + /// Log into maintenance mode + /// + /// Login with maintenance token or cookie to receive current information and perform further actions. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MaintenanceLoginDto] maintenanceLoginDto (required): + Future maintenanceLoginWithHttpInfo(MaintenanceLoginDto maintenanceLoginDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/maintenance/login'; + + // ignore: prefer_final_locals + Object? postBody = maintenanceLoginDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Log into maintenance mode + /// + /// Login with maintenance token or cookie to receive current information and perform further actions. + /// + /// Parameters: + /// + /// * [MaintenanceLoginDto] maintenanceLoginDto (required): + Future maintenanceLogin(MaintenanceLoginDto maintenanceLoginDto,) async { + final response = await maintenanceLoginWithHttpInfo(maintenanceLoginDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MaintenanceAuthDto',) as MaintenanceAuthDto; + + } + return null; + } + + /// Set maintenance mode + /// + /// Put Immich into or take it out of maintenance mode + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SetMaintenanceModeDto] setMaintenanceModeDto (required): + Future setMaintenanceModeWithHttpInfo(SetMaintenanceModeDto setMaintenanceModeDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/maintenance'; + + // ignore: prefer_final_locals + Object? postBody = setMaintenanceModeDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Set maintenance mode + /// + /// Put Immich into or take it out of maintenance mode + /// + /// Parameters: + /// + /// * [SetMaintenanceModeDto] setMaintenanceModeDto (required): + Future setMaintenanceMode(SetMaintenanceModeDto setMaintenanceModeDto,) async { + final response = await setMaintenanceModeWithHttpInfo(setMaintenanceModeDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/map_api.dart b/mobile/openapi/lib/api/map_api.dart new file mode 100644 index 0000000000..4ce62bd96c --- /dev/null +++ b/mobile/openapi/lib/api/map_api.dart @@ -0,0 +1,198 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MapApi { + MapApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve map markers + /// + /// Retrieve a list of latitude and longitude coordinates for every asset with location data. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DateTime] fileCreatedAfter: + /// Filter assets created after this date + /// + /// * [DateTime] fileCreatedBefore: + /// Filter assets created before this date + /// + /// * [bool] isArchived: + /// Filter by archived status + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] withPartners: + /// Include partner assets + /// + /// * [bool] withSharedAlbums: + /// Include shared album assets + Future getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/map/markers'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (fileCreatedAfter != null) { + queryParams.addAll(_queryParams('', 'fileCreatedAfter', fileCreatedAfter)); + } + if (fileCreatedBefore != null) { + queryParams.addAll(_queryParams('', 'fileCreatedBefore', fileCreatedBefore)); + } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } + if (withSharedAlbums != null) { + queryParams.addAll(_queryParams('', 'withSharedAlbums', withSharedAlbums)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve map markers + /// + /// Retrieve a list of latitude and longitude coordinates for every asset with location data. + /// + /// Parameters: + /// + /// * [DateTime] fileCreatedAfter: + /// Filter assets created after this date + /// + /// * [DateTime] fileCreatedBefore: + /// Filter assets created before this date + /// + /// * [bool] isArchived: + /// Filter by archived status + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] withPartners: + /// Include partner assets + /// + /// * [bool] withSharedAlbums: + /// Include shared album assets + Future?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, bool? withSharedAlbums, }) async { + final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, withSharedAlbums: withSharedAlbums, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Reverse geocode coordinates + /// + /// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [double] lat (required): + /// Latitude (-90 to 90) + /// + /// * [double] lon (required): + /// Longitude (-180 to 180) + Future reverseGeocodeWithHttpInfo(double lat, double lon,) async { + // ignore: prefer_const_declarations + final apiPath = r'/map/reverse-geocode'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'lat', lat)); + queryParams.addAll(_queryParams('', 'lon', lon)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Reverse geocode coordinates + /// + /// Retrieve location information (e.g., city, country) for given latitude and longitude coordinates. + /// + /// Parameters: + /// + /// * [double] lat (required): + /// Latitude (-90 to 90) + /// + /// * [double] lon (required): + /// Longitude (-180 to 180) + Future?> reverseGeocode(double lat, double lon,) async { + final response = await reverseGeocodeWithHttpInfo(lat, lon,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/memories_api.dart b/mobile/openapi/lib/api/memories_api.dart new file mode 100644 index 0000000000..913205428e --- /dev/null +++ b/mobile/openapi/lib/api/memories_api.dart @@ -0,0 +1,586 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MemoriesApi { + MemoriesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Add assets to a memory + /// + /// Add a list of asset IDs to a specific memory. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future addMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Add assets to a memory + /// + /// Add a list of asset IDs to a specific memory. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> addMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await addMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Create a memory + /// + /// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MemoryCreateDto] memoryCreateDto (required): + Future createMemoryWithHttpInfo(MemoryCreateDto memoryCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories'; + + // ignore: prefer_final_locals + Object? postBody = memoryCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a memory + /// + /// Create a new memory by providing a name, description, and a list of asset IDs to include in the memory. + /// + /// Parameters: + /// + /// * [MemoryCreateDto] memoryCreateDto (required): + Future createMemory(MemoryCreateDto memoryCreateDto,) async { + final response = await createMemoryWithHttpInfo(memoryCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } + + /// Delete a memory + /// + /// Delete a specific memory by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteMemoryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a memory + /// + /// Delete a specific memory by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteMemory(String id,) async { + final response = await deleteMemoryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve a memory + /// + /// Retrieve a specific memory by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getMemoryWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a memory + /// + /// Retrieve a specific memory by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getMemory(String id,) async { + final response = await getMemoryWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } + + /// Retrieve memories statistics + /// + /// Retrieve statistics about memories, such as total count and other relevant metrics. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DateTime] for_: + /// Filter by date + /// + /// * [bool] isSaved: + /// Filter by saved status + /// + /// * [bool] isTrashed: + /// Include trashed memories + /// + /// * [MemorySearchOrder] order: + /// Sort order + /// + /// * [int] size: + /// Number of memories to return + /// + /// * [MemoryType] type: + /// Memory type + Future memoriesStatisticsWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/statistics'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (for_ != null) { + queryParams.addAll(_queryParams('', 'for', for_)); + } + if (isSaved != null) { + queryParams.addAll(_queryParams('', 'isSaved', isSaved)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve memories statistics + /// + /// Retrieve statistics about memories, such as total count and other relevant metrics. + /// + /// Parameters: + /// + /// * [DateTime] for_: + /// Filter by date + /// + /// * [bool] isSaved: + /// Filter by saved status + /// + /// * [bool] isTrashed: + /// Include trashed memories + /// + /// * [MemorySearchOrder] order: + /// Sort order + /// + /// * [int] size: + /// Number of memories to return + /// + /// * [MemoryType] type: + /// Memory type + Future memoriesStatistics({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + final response = await memoriesStatisticsWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryStatisticsResponseDto',) as MemoryStatisticsResponseDto; + + } + return null; + } + + /// Remove assets from a memory + /// + /// Remove a list of asset IDs from a specific memory. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future removeMemoryAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove assets from a memory + /// + /// Remove a list of asset IDs from a specific memory. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> removeMemoryAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await removeMemoryAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve memories + /// + /// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [DateTime] for_: + /// Filter by date + /// + /// * [bool] isSaved: + /// Filter by saved status + /// + /// * [bool] isTrashed: + /// Include trashed memories + /// + /// * [MemorySearchOrder] order: + /// Sort order + /// + /// * [int] size: + /// Number of memories to return + /// + /// * [MemoryType] type: + /// Memory type + Future searchMemoriesWithHttpInfo({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (for_ != null) { + queryParams.addAll(_queryParams('', 'for', for_)); + } + if (isSaved != null) { + queryParams.addAll(_queryParams('', 'isSaved', isSaved)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve memories + /// + /// Retrieve a list of memories. Memories are sorted descending by creation date by default, although they can also be sorted in ascending order, or randomly. + /// + /// Parameters: + /// + /// * [DateTime] for_: + /// Filter by date + /// + /// * [bool] isSaved: + /// Filter by saved status + /// + /// * [bool] isTrashed: + /// Include trashed memories + /// + /// * [MemorySearchOrder] order: + /// Sort order + /// + /// * [int] size: + /// Number of memories to return + /// + /// * [MemoryType] type: + /// Memory type + Future?> searchMemories({ DateTime? for_, bool? isSaved, bool? isTrashed, MemorySearchOrder? order, int? size, MemoryType? type, }) async { + final response = await searchMemoriesWithHttpInfo( for_: for_, isSaved: isSaved, isTrashed: isTrashed, order: order, size: size, type: type, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a memory + /// + /// Update an existing memory by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MemoryUpdateDto] memoryUpdateDto (required): + Future updateMemoryWithHttpInfo(String id, MemoryUpdateDto memoryUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/memories/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = memoryUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a memory + /// + /// Update an existing memory by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MemoryUpdateDto] memoryUpdateDto (required): + Future updateMemory(String id, MemoryUpdateDto memoryUpdateDto,) async { + final response = await updateMemoryWithHttpInfo(id, memoryUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MemoryResponseDto',) as MemoryResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/notifications_admin_api.dart b/mobile/openapi/lib/api/notifications_admin_api.dart new file mode 100644 index 0000000000..7821553d30 --- /dev/null +++ b/mobile/openapi/lib/api/notifications_admin_api.dart @@ -0,0 +1,191 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class NotificationsAdminApi { + NotificationsAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a notification + /// + /// Create a new notification for a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [NotificationCreateDto] notificationCreateDto (required): + Future createNotificationWithHttpInfo(NotificationCreateDto notificationCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/notifications'; + + // ignore: prefer_final_locals + Object? postBody = notificationCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a notification + /// + /// Create a new notification for a specific user. + /// + /// Parameters: + /// + /// * [NotificationCreateDto] notificationCreateDto (required): + Future createNotification(NotificationCreateDto notificationCreateDto,) async { + final response = await createNotificationWithHttpInfo(notificationCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto; + + } + return null; + } + + /// Render email template + /// + /// Retrieve a preview of the provided email template. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] name (required): + /// + /// * [TemplateDto] templateDto (required): + Future getNotificationTemplateAdminWithHttpInfo(String name, TemplateDto templateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/notifications/templates/{name}' + .replaceAll('{name}', name); + + // ignore: prefer_final_locals + Object? postBody = templateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Render email template + /// + /// Retrieve a preview of the provided email template. + /// + /// Parameters: + /// + /// * [String] name (required): + /// + /// * [TemplateDto] templateDto (required): + Future getNotificationTemplateAdmin(String name, TemplateDto templateDto,) async { + final response = await getNotificationTemplateAdminWithHttpInfo(name, templateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TemplateResponseDto',) as TemplateResponseDto; + + } + return null; + } + + /// Send test email + /// + /// Send a test email using the provided SMTP configuration. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): + Future sendTestEmailAdminWithHttpInfo(SystemConfigSmtpDto systemConfigSmtpDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/notifications/test-email'; + + // ignore: prefer_final_locals + Object? postBody = systemConfigSmtpDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Send test email + /// + /// Send a test email using the provided SMTP configuration. + /// + /// Parameters: + /// + /// * [SystemConfigSmtpDto] systemConfigSmtpDto (required): + Future sendTestEmailAdmin(SystemConfigSmtpDto systemConfigSmtpDto,) async { + final response = await sendTestEmailAdminWithHttpInfo(systemConfigSmtpDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TestEmailResponseDto',) as TestEmailResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/notifications_api.dart b/mobile/openapi/lib/api/notifications_api.dart new file mode 100644 index 0000000000..d4e2b1d80f --- /dev/null +++ b/mobile/openapi/lib/api/notifications_api.dart @@ -0,0 +1,373 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class NotificationsApi { + NotificationsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Delete a notification + /// + /// Delete a specific notification. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteNotificationWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a notification + /// + /// Delete a specific notification. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteNotification(String id,) async { + final response = await deleteNotificationWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete notifications + /// + /// Delete a list of notifications at once. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [NotificationDeleteAllDto] notificationDeleteAllDto (required): + Future deleteNotificationsWithHttpInfo(NotificationDeleteAllDto notificationDeleteAllDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications'; + + // ignore: prefer_final_locals + Object? postBody = notificationDeleteAllDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete notifications + /// + /// Delete a list of notifications at once. + /// + /// Parameters: + /// + /// * [NotificationDeleteAllDto] notificationDeleteAllDto (required): + Future deleteNotifications(NotificationDeleteAllDto notificationDeleteAllDto,) async { + final response = await deleteNotificationsWithHttpInfo(notificationDeleteAllDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Get a notification + /// + /// Retrieve a specific notification identified by id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getNotificationWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get a notification + /// + /// Retrieve a specific notification identified by id. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getNotification(String id,) async { + final response = await getNotificationWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto; + + } + return null; + } + + /// Retrieve notifications + /// + /// Retrieve a list of notifications. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id: + /// Filter by notification ID + /// + /// * [NotificationLevel] level: + /// Filter by notification level + /// + /// * [NotificationType] type: + /// Filter by notification type + /// + /// * [bool] unread: + /// Filter by unread status + Future getNotificationsWithHttpInfo({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (level != null) { + queryParams.addAll(_queryParams('', 'level', level)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + if (unread != null) { + queryParams.addAll(_queryParams('', 'unread', unread)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve notifications + /// + /// Retrieve a list of notifications. + /// + /// Parameters: + /// + /// * [String] id: + /// Filter by notification ID + /// + /// * [NotificationLevel] level: + /// Filter by notification level + /// + /// * [NotificationType] type: + /// Filter by notification type + /// + /// * [bool] unread: + /// Filter by unread status + Future?> getNotifications({ String? id, NotificationLevel? level, NotificationType? type, bool? unread, }) async { + final response = await getNotificationsWithHttpInfo( id: id, level: level, type: type, unread: unread, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a notification + /// + /// Update a specific notification to set its read status. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [NotificationUpdateDto] notificationUpdateDto (required): + Future updateNotificationWithHttpInfo(String id, NotificationUpdateDto notificationUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = notificationUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a notification + /// + /// Update a specific notification to set its read status. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [NotificationUpdateDto] notificationUpdateDto (required): + Future updateNotification(String id, NotificationUpdateDto notificationUpdateDto,) async { + final response = await updateNotificationWithHttpInfo(id, notificationUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'NotificationDto',) as NotificationDto; + + } + return null; + } + + /// Update notifications + /// + /// Update a list of notifications. Allows to bulk-set the read status of notifications. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [NotificationUpdateAllDto] notificationUpdateAllDto (required): + Future updateNotificationsWithHttpInfo(NotificationUpdateAllDto notificationUpdateAllDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/notifications'; + + // ignore: prefer_final_locals + Object? postBody = notificationUpdateAllDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update notifications + /// + /// Update a list of notifications. Allows to bulk-set the read status of notifications. + /// + /// Parameters: + /// + /// * [NotificationUpdateAllDto] notificationUpdateAllDto (required): + Future updateNotifications(NotificationUpdateAllDto notificationUpdateAllDto,) async { + final response = await updateNotificationsWithHttpInfo(notificationUpdateAllDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/partners_api.dart b/mobile/openapi/lib/api/partners_api.dart new file mode 100644 index 0000000000..3b15b90909 --- /dev/null +++ b/mobile/openapi/lib/api/partners_api.dart @@ -0,0 +1,304 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PartnersApi { + PartnersApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PartnerCreateDto] partnerCreateDto (required): + Future createPartnerWithHttpInfo(PartnerCreateDto partnerCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners'; + + // ignore: prefer_final_locals + Object? postBody = partnerCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Parameters: + /// + /// * [PartnerCreateDto] partnerCreateDto (required): + Future createPartner(PartnerCreateDto partnerCreateDto,) async { + final response = await createPartnerWithHttpInfo(partnerCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future createPartnerDeprecatedWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a partner + /// + /// Create a new partner to share assets with. + /// + /// Parameters: + /// + /// * [String] id (required): + Future createPartnerDeprecated(String id,) async { + final response = await createPartnerDeprecatedWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } + + /// Retrieve partners + /// + /// Retrieve a list of partners with whom assets are shared. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PartnerDirection] direction (required): + /// Partner direction + Future getPartnersWithHttpInfo(PartnerDirection direction,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'direction', direction)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve partners + /// + /// Retrieve a list of partners with whom assets are shared. + /// + /// Parameters: + /// + /// * [PartnerDirection] direction (required): + /// Partner direction + Future?> getPartners(PartnerDirection direction,) async { + final response = await getPartnersWithHttpInfo(direction,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Remove a partner + /// + /// Stop sharing assets with a partner. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removePartnerWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove a partner + /// + /// Stop sharing assets with a partner. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removePartner(String id,) async { + final response = await removePartnerWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Update a partner + /// + /// Specify whether a partner's assets should appear in the user's timeline. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [PartnerUpdateDto] partnerUpdateDto (required): + Future updatePartnerWithHttpInfo(String id, PartnerUpdateDto partnerUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/partners/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = partnerUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a partner + /// + /// Specify whether a partner's assets should appear in the user's timeline. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [PartnerUpdateDto] partnerUpdateDto (required): + Future updatePartner(String id, PartnerUpdateDto partnerUpdateDto,) async { + final response = await updatePartnerWithHttpInfo(id, partnerUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PartnerResponseDto',) as PartnerResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/people_api.dart b/mobile/openapi/lib/api/people_api.dart new file mode 100644 index 0000000000..c8c1821423 --- /dev/null +++ b/mobile/openapi/lib/api/people_api.dart @@ -0,0 +1,688 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PeopleApi { + PeopleApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a person + /// + /// Create a new person that can have multiple faces assigned to them. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PersonCreateDto] personCreateDto (required): + Future createPersonWithHttpInfo(PersonCreateDto personCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people'; + + // ignore: prefer_final_locals + Object? postBody = personCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a person + /// + /// Create a new person that can have multiple faces assigned to them. + /// + /// Parameters: + /// + /// * [PersonCreateDto] personCreateDto (required): + Future createPerson(PersonCreateDto personCreateDto,) async { + final response = await createPersonWithHttpInfo(personCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto; + + } + return null; + } + + /// Delete people + /// + /// Bulk delete a list of people at once. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deletePeopleWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people'; + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete people + /// + /// Bulk delete a list of people at once. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deletePeople(BulkIdsDto bulkIdsDto,) async { + final response = await deletePeopleWithHttpInfo(bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete person + /// + /// Delete an individual person. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deletePersonWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete person + /// + /// Delete an individual person. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deletePerson(String id,) async { + final response = await deletePersonWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Get all people + /// + /// Retrieve a list of all people. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] closestAssetId: + /// Closest asset ID for similarity search + /// + /// * [String] closestPersonId: + /// Closest person ID for similarity search + /// + /// * [num] page: + /// Page number for pagination + /// + /// * [num] size: + /// Number of items per page + /// + /// * [bool] withHidden: + /// Include hidden people + Future getAllPeopleWithHttpInfo({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/people'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (closestAssetId != null) { + queryParams.addAll(_queryParams('', 'closestAssetId', closestAssetId)); + } + if (closestPersonId != null) { + queryParams.addAll(_queryParams('', 'closestPersonId', closestPersonId)); + } + if (page != null) { + queryParams.addAll(_queryParams('', 'page', page)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (withHidden != null) { + queryParams.addAll(_queryParams('', 'withHidden', withHidden)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get all people + /// + /// Retrieve a list of all people. + /// + /// Parameters: + /// + /// * [String] closestAssetId: + /// Closest asset ID for similarity search + /// + /// * [String] closestPersonId: + /// Closest person ID for similarity search + /// + /// * [num] page: + /// Page number for pagination + /// + /// * [num] size: + /// Number of items per page + /// + /// * [bool] withHidden: + /// Include hidden people + Future getAllPeople({ String? closestAssetId, String? closestPersonId, num? page, num? size, bool? withHidden, }) async { + final response = await getAllPeopleWithHttpInfo( closestAssetId: closestAssetId, closestPersonId: closestPersonId, page: page, size: size, withHidden: withHidden, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PeopleResponseDto',) as PeopleResponseDto; + + } + return null; + } + + /// Get a person + /// + /// Retrieve a person by id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPersonWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get a person + /// + /// Retrieve a person by id. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPerson(String id,) async { + final response = await getPersonWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto; + + } + return null; + } + + /// Get person statistics + /// + /// Retrieve statistics about a specific person. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPersonStatisticsWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}/statistics' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get person statistics + /// + /// Retrieve statistics about a specific person. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPersonStatistics(String id,) async { + final response = await getPersonStatisticsWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonStatisticsResponseDto',) as PersonStatisticsResponseDto; + + } + return null; + } + + /// Get person thumbnail + /// + /// Retrieve the thumbnail file for a person. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPersonThumbnailWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}/thumbnail' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get person thumbnail + /// + /// Retrieve the thumbnail file for a person. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPersonThumbnail(String id,) async { + final response = await getPersonThumbnailWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// Merge people + /// + /// Merge a list of people into the person specified in the path parameter. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MergePersonDto] mergePersonDto (required): + Future mergePersonWithHttpInfo(String id, MergePersonDto mergePersonDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}/merge' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = mergePersonDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Merge people + /// + /// Merge a list of people into the person specified in the path parameter. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [MergePersonDto] mergePersonDto (required): + Future?> mergePerson(String id, MergePersonDto mergePersonDto,) async { + final response = await mergePersonWithHttpInfo(id, mergePersonDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Reassign faces + /// + /// Bulk reassign a list of faces to a different person. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetFaceUpdateDto] assetFaceUpdateDto (required): + Future reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}/reassign' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetFaceUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Reassign faces + /// + /// Bulk reassign a list of faces to a different person. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetFaceUpdateDto] assetFaceUpdateDto (required): + Future?> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async { + final response = await reassignFacesWithHttpInfo(id, assetFaceUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update people + /// + /// Bulk update multiple people at once. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [PeopleUpdateDto] peopleUpdateDto (required): + Future updatePeopleWithHttpInfo(PeopleUpdateDto peopleUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people'; + + // ignore: prefer_final_locals + Object? postBody = peopleUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update people + /// + /// Bulk update multiple people at once. + /// + /// Parameters: + /// + /// * [PeopleUpdateDto] peopleUpdateDto (required): + Future?> updatePeople(PeopleUpdateDto peopleUpdateDto,) async { + final response = await updatePeopleWithHttpInfo(peopleUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update person + /// + /// Update an individual person. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [PersonUpdateDto] personUpdateDto (required): + Future updatePersonWithHttpInfo(String id, PersonUpdateDto personUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/people/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = personUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update person + /// + /// Update an individual person. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [PersonUpdateDto] personUpdateDto (required): + Future updatePerson(String id, PersonUpdateDto personUpdateDto,) async { + final response = await updatePersonWithHttpInfo(id, personUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/plugins_api.dart b/mobile/openapi/lib/api/plugins_api.dart new file mode 100644 index 0000000000..5735fba379 --- /dev/null +++ b/mobile/openapi/lib/api/plugins_api.dart @@ -0,0 +1,177 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PluginsApi { + PluginsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve a plugin + /// + /// Retrieve information about a specific plugin by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPluginWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a plugin + /// + /// Retrieve information about a specific plugin by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getPlugin(String id,) async { + final response = await getPluginWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PluginResponseDto',) as PluginResponseDto; + + } + return null; + } + + /// List all plugin triggers + /// + /// Retrieve a list of all available plugin triggers. + /// + /// Note: This method returns the HTTP [Response]. + Future getPluginTriggersWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins/triggers'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all plugin triggers + /// + /// Retrieve a list of all available plugin triggers. + Future?> getPluginTriggers() async { + final response = await getPluginTriggersWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// List all plugins + /// + /// Retrieve a list of plugins available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getPluginsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/plugins'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all plugins + /// + /// Retrieve a list of plugins available to the authenticated user. + Future?> getPlugins() async { + final response = await getPluginsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/queues_api.dart b/mobile/openapi/lib/api/queues_api.dart new file mode 100644 index 0000000000..ecb556e434 --- /dev/null +++ b/mobile/openapi/lib/api/queues_api.dart @@ -0,0 +1,318 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class QueuesApi { + QueuesApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Empty a queue + /// + /// Removes all jobs from the specified queue. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueDeleteDto] queueDeleteDto (required): + Future emptyQueueWithHttpInfo(QueueName name, QueueDeleteDto queueDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/queues/{name}/jobs' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody = queueDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Empty a queue + /// + /// Removes all jobs from the specified queue. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueDeleteDto] queueDeleteDto (required): + Future emptyQueue(QueueName name, QueueDeleteDto queueDeleteDto,) async { + final response = await emptyQueueWithHttpInfo(name, queueDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve a queue + /// + /// Retrieves a specific queue by its name. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + Future getQueueWithHttpInfo(QueueName name,) async { + // ignore: prefer_const_declarations + final apiPath = r'/queues/{name}' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a queue + /// + /// Retrieves a specific queue by its name. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + Future getQueue(QueueName name,) async { + final response = await getQueueWithHttpInfo(name,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto; + + } + return null; + } + + /// Retrieve queue jobs + /// + /// Retrieves a list of queue jobs from the specified queue. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [List] status: + /// Filter jobs by status + Future getQueueJobsWithHttpInfo(QueueName name, { List? status, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/queues/{name}/jobs' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (status != null) { + queryParams.addAll(_queryParams('multi', 'status', status)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve queue jobs + /// + /// Retrieves a list of queue jobs from the specified queue. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [List] status: + /// Filter jobs by status + Future?> getQueueJobs(QueueName name, { List? status, }) async { + final response = await getQueueJobsWithHttpInfo(name, status: status, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// List all queues + /// + /// Retrieves a list of queues. + /// + /// Note: This method returns the HTTP [Response]. + Future getQueuesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/queues'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all queues + /// + /// Retrieves a list of queues. + Future?> getQueues() async { + final response = await getQueuesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a queue + /// + /// Change the paused status of a specific queue. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueUpdateDto] queueUpdateDto (required): + Future updateQueueWithHttpInfo(QueueName name, QueueUpdateDto queueUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/queues/{name}' + .replaceAll('{name}', name.toString()); + + // ignore: prefer_final_locals + Object? postBody = queueUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a queue + /// + /// Change the paused status of a specific queue. + /// + /// Parameters: + /// + /// * [QueueName] name (required): + /// Queue name + /// + /// * [QueueUpdateDto] queueUpdateDto (required): + Future updateQueue(QueueName name, QueueUpdateDto queueUpdateDto,) async { + final response = await updateQueueWithHttpInfo(name, queueUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'QueueResponseDto',) as QueueResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart new file mode 100644 index 0000000000..085958de66 --- /dev/null +++ b/mobile/openapi/lib/api/search_api.dart @@ -0,0 +1,943 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SearchApi { + SearchApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve assets by city + /// + /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. + /// + /// Note: This method returns the HTTP [Response]. + Future getAssetsByCityWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/search/cities'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve assets by city + /// + /// Retrieve a list of assets with each asset belonging to a different city. This endpoint is used on the places pages to show a single thumbnail for each city the user has assets in. + Future?> getAssetsByCity() async { + final response = await getAssetsByCityWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve explore data + /// + /// Retrieve data for the explore section, such as popular people and places. + /// + /// Note: This method returns the HTTP [Response]. + Future getExploreDataWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/search/explore'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve explore data + /// + /// Retrieve data for the explore section, such as popular people and places. + Future?> getExploreData() async { + final response = await getExploreDataWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve search suggestions + /// + /// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SearchSuggestionType] type (required): + /// Suggestion type + /// + /// * [String] country: + /// Filter by country + /// + /// * [bool] includeNull: + /// Include null values in suggestions + /// + /// * [String] lensModel: + /// Filter by lens model + /// + /// * [String] make: + /// Filter by camera make + /// + /// * [String] model: + /// Filter by camera model + /// + /// * [String] state: + /// Filter by state/province + Future getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/suggestions'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (country != null) { + queryParams.addAll(_queryParams('', 'country', country)); + } + if (includeNull != null) { + queryParams.addAll(_queryParams('', 'includeNull', includeNull)); + } + if (lensModel != null) { + queryParams.addAll(_queryParams('', 'lensModel', lensModel)); + } + if (make != null) { + queryParams.addAll(_queryParams('', 'make', make)); + } + if (model != null) { + queryParams.addAll(_queryParams('', 'model', model)); + } + if (state != null) { + queryParams.addAll(_queryParams('', 'state', state)); + } + queryParams.addAll(_queryParams('', 'type', type)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve search suggestions + /// + /// Retrieve search suggestions based on partial input. This endpoint is used for typeahead search features. + /// + /// Parameters: + /// + /// * [SearchSuggestionType] type (required): + /// Suggestion type + /// + /// * [String] country: + /// Filter by country + /// + /// * [bool] includeNull: + /// Include null values in suggestions + /// + /// * [String] lensModel: + /// Filter by lens model + /// + /// * [String] make: + /// Filter by camera make + /// + /// * [String] model: + /// Filter by camera model + /// + /// * [String] state: + /// Filter by state/province + Future?> getSearchSuggestions(SearchSuggestionType type, { String? country, bool? includeNull, String? lensModel, String? make, String? model, String? state, }) async { + final response = await getSearchSuggestionsWithHttpInfo(type, country: country, includeNull: includeNull, lensModel: lensModel, make: make, model: model, state: state, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Search asset statistics + /// + /// Retrieve statistical data about assets based on search criteria, such as the total matching count. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [StatisticsSearchDto] statisticsSearchDto (required): + Future searchAssetStatisticsWithHttpInfo(StatisticsSearchDto statisticsSearchDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/statistics'; + + // ignore: prefer_final_locals + Object? postBody = statisticsSearchDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search asset statistics + /// + /// Retrieve statistical data about assets based on search criteria, such as the total matching count. + /// + /// Parameters: + /// + /// * [StatisticsSearchDto] statisticsSearchDto (required): + Future searchAssetStatistics(StatisticsSearchDto statisticsSearchDto,) async { + final response = await searchAssetStatisticsWithHttpInfo(statisticsSearchDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SearchStatisticsResponseDto',) as SearchStatisticsResponseDto; + + } + return null; + } + + /// Search assets by metadata + /// + /// Search for assets based on various metadata criteria. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MetadataSearchDto] metadataSearchDto (required): + Future searchAssetsWithHttpInfo(MetadataSearchDto metadataSearchDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/metadata'; + + // ignore: prefer_final_locals + Object? postBody = metadataSearchDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search assets by metadata + /// + /// Search for assets based on various metadata criteria. + /// + /// Parameters: + /// + /// * [MetadataSearchDto] metadataSearchDto (required): + Future searchAssets(MetadataSearchDto metadataSearchDto,) async { + final response = await searchAssetsWithHttpInfo(metadataSearchDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SearchResponseDto',) as SearchResponseDto; + + } + return null; + } + + /// Search large assets + /// + /// Search for assets that are considered large based on specified criteria. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [List] albumIds: + /// Filter by album IDs + /// + /// * [String] city: + /// Filter by city name + /// + /// * [String] country: + /// Filter by country name + /// + /// * [DateTime] createdAfter: + /// Filter by creation date (after) + /// + /// * [DateTime] createdBefore: + /// Filter by creation date (before) + /// + /// * [String] deviceId: + /// Device ID to filter by + /// + /// * [bool] isEncoded: + /// Filter by encoded status + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isMotion: + /// Filter by motion photo status + /// + /// * [bool] isNotInAlbum: + /// Filter assets not in any album + /// + /// * [bool] isOffline: + /// Filter by offline status + /// + /// * [String] lensModel: + /// Filter by lens model + /// + /// * [String] libraryId: + /// Library ID to filter by + /// + /// * [String] make: + /// Filter by camera make + /// + /// * [int] minFileSize: + /// Minimum file size in bytes + /// + /// * [String] model: + /// Filter by camera model + /// + /// * [String] ocr: + /// Filter by OCR text content + /// + /// * [List] personIds: + /// Filter by person IDs + /// + /// * [num] rating: + /// Filter by rating [1-5], or null for unrated + /// + /// * [num] size: + /// Number of results to return + /// + /// * [String] state: + /// Filter by state/province name + /// + /// * [List] tagIds: + /// Filter by tag IDs + /// + /// * [DateTime] takenAfter: + /// Filter by taken date (after) + /// + /// * [DateTime] takenBefore: + /// Filter by taken date (before) + /// + /// * [DateTime] trashedAfter: + /// Filter by trash date (after) + /// + /// * [DateTime] trashedBefore: + /// Filter by trash date (before) + /// + /// * [AssetTypeEnum] type: + /// Asset type filter + /// + /// * [DateTime] updatedAfter: + /// Filter by update date (after) + /// + /// * [DateTime] updatedBefore: + /// Filter by update date (before) + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + /// + /// * [bool] withDeleted: + /// Include deleted assets + /// + /// * [bool] withExif: + /// Include EXIF data in response + Future searchLargeAssetsWithHttpInfo({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/large-assets'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (albumIds != null) { + queryParams.addAll(_queryParams('multi', 'albumIds', albumIds)); + } + if (city != null) { + queryParams.addAll(_queryParams('', 'city', city)); + } + if (country != null) { + queryParams.addAll(_queryParams('', 'country', country)); + } + if (createdAfter != null) { + queryParams.addAll(_queryParams('', 'createdAfter', createdAfter)); + } + if (createdBefore != null) { + queryParams.addAll(_queryParams('', 'createdBefore', createdBefore)); + } + if (deviceId != null) { + queryParams.addAll(_queryParams('', 'deviceId', deviceId)); + } + if (isEncoded != null) { + queryParams.addAll(_queryParams('', 'isEncoded', isEncoded)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isMotion != null) { + queryParams.addAll(_queryParams('', 'isMotion', isMotion)); + } + if (isNotInAlbum != null) { + queryParams.addAll(_queryParams('', 'isNotInAlbum', isNotInAlbum)); + } + if (isOffline != null) { + queryParams.addAll(_queryParams('', 'isOffline', isOffline)); + } + if (lensModel != null) { + queryParams.addAll(_queryParams('', 'lensModel', lensModel)); + } + if (libraryId != null) { + queryParams.addAll(_queryParams('', 'libraryId', libraryId)); + } + if (make != null) { + queryParams.addAll(_queryParams('', 'make', make)); + } + if (minFileSize != null) { + queryParams.addAll(_queryParams('', 'minFileSize', minFileSize)); + } + if (model != null) { + queryParams.addAll(_queryParams('', 'model', model)); + } + if (ocr != null) { + queryParams.addAll(_queryParams('', 'ocr', ocr)); + } + if (personIds != null) { + queryParams.addAll(_queryParams('multi', 'personIds', personIds)); + } + if (rating != null) { + queryParams.addAll(_queryParams('', 'rating', rating)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (state != null) { + queryParams.addAll(_queryParams('', 'state', state)); + } + if (tagIds != null) { + queryParams.addAll(_queryParams('multi', 'tagIds', tagIds)); + } + if (takenAfter != null) { + queryParams.addAll(_queryParams('', 'takenAfter', takenAfter)); + } + if (takenBefore != null) { + queryParams.addAll(_queryParams('', 'takenBefore', takenBefore)); + } + if (trashedAfter != null) { + queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter)); + } + if (trashedBefore != null) { + queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + if (updatedAfter != null) { + queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); + } + if (updatedBefore != null) { + queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore)); + } + if (visibility != null) { + queryParams.addAll(_queryParams('', 'visibility', visibility)); + } + if (withDeleted != null) { + queryParams.addAll(_queryParams('', 'withDeleted', withDeleted)); + } + if (withExif != null) { + queryParams.addAll(_queryParams('', 'withExif', withExif)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search large assets + /// + /// Search for assets that are considered large based on specified criteria. + /// + /// Parameters: + /// + /// * [List] albumIds: + /// Filter by album IDs + /// + /// * [String] city: + /// Filter by city name + /// + /// * [String] country: + /// Filter by country name + /// + /// * [DateTime] createdAfter: + /// Filter by creation date (after) + /// + /// * [DateTime] createdBefore: + /// Filter by creation date (before) + /// + /// * [String] deviceId: + /// Device ID to filter by + /// + /// * [bool] isEncoded: + /// Filter by encoded status + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isMotion: + /// Filter by motion photo status + /// + /// * [bool] isNotInAlbum: + /// Filter assets not in any album + /// + /// * [bool] isOffline: + /// Filter by offline status + /// + /// * [String] lensModel: + /// Filter by lens model + /// + /// * [String] libraryId: + /// Library ID to filter by + /// + /// * [String] make: + /// Filter by camera make + /// + /// * [int] minFileSize: + /// Minimum file size in bytes + /// + /// * [String] model: + /// Filter by camera model + /// + /// * [String] ocr: + /// Filter by OCR text content + /// + /// * [List] personIds: + /// Filter by person IDs + /// + /// * [num] rating: + /// Filter by rating [1-5], or null for unrated + /// + /// * [num] size: + /// Number of results to return + /// + /// * [String] state: + /// Filter by state/province name + /// + /// * [List] tagIds: + /// Filter by tag IDs + /// + /// * [DateTime] takenAfter: + /// Filter by taken date (after) + /// + /// * [DateTime] takenBefore: + /// Filter by taken date (before) + /// + /// * [DateTime] trashedAfter: + /// Filter by trash date (after) + /// + /// * [DateTime] trashedBefore: + /// Filter by trash date (before) + /// + /// * [AssetTypeEnum] type: + /// Asset type filter + /// + /// * [DateTime] updatedAfter: + /// Filter by update date (after) + /// + /// * [DateTime] updatedBefore: + /// Filter by update date (before) + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + /// + /// * [bool] withDeleted: + /// Include deleted assets + /// + /// * [bool] withExif: + /// Include EXIF data in response + Future?> searchLargeAssets({ List? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List? personIds, num? rating, num? size, String? state, List? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, }) async { + final response = await searchLargeAssetsWithHttpInfo( albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Search people + /// + /// Search for people by name. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] name (required): + /// Person name to search for + /// + /// * [bool] withHidden: + /// Include hidden people + Future searchPersonWithHttpInfo(String name, { bool? withHidden, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/person'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'name', name)); + if (withHidden != null) { + queryParams.addAll(_queryParams('', 'withHidden', withHidden)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search people + /// + /// Search for people by name. + /// + /// Parameters: + /// + /// * [String] name (required): + /// Person name to search for + /// + /// * [bool] withHidden: + /// Include hidden people + Future?> searchPerson(String name, { bool? withHidden, }) async { + final response = await searchPersonWithHttpInfo(name, withHidden: withHidden, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Search places + /// + /// Search for places by name. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] name (required): + /// Place name to search for + Future searchPlacesWithHttpInfo(String name,) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/places'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'name', name)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search places + /// + /// Search for places by name. + /// + /// Parameters: + /// + /// * [String] name (required): + /// Place name to search for + Future?> searchPlaces(String name,) async { + final response = await searchPlacesWithHttpInfo(name,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Search random assets + /// + /// Retrieve a random selection of assets based on the provided criteria. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [RandomSearchDto] randomSearchDto (required): + Future searchRandomWithHttpInfo(RandomSearchDto randomSearchDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/random'; + + // ignore: prefer_final_locals + Object? postBody = randomSearchDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search random assets + /// + /// Retrieve a random selection of assets based on the provided criteria. + /// + /// Parameters: + /// + /// * [RandomSearchDto] randomSearchDto (required): + Future?> searchRandom(RandomSearchDto randomSearchDto,) async { + final response = await searchRandomWithHttpInfo(randomSearchDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Smart asset search + /// + /// Perform a smart search for assets by using machine learning vectors to determine relevance. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SmartSearchDto] smartSearchDto (required): + Future searchSmartWithHttpInfo(SmartSearchDto smartSearchDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/search/smart'; + + // ignore: prefer_final_locals + Object? postBody = smartSearchDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Smart asset search + /// + /// Perform a smart search for assets by using machine learning vectors to determine relevance. + /// + /// Parameters: + /// + /// * [SmartSearchDto] smartSearchDto (required): + Future searchSmart(SmartSearchDto smartSearchDto,) async { + final response = await searchSmartWithHttpInfo(smartSearchDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SearchResponseDto',) as SearchResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/server_api.dart b/mobile/openapi/lib/api/server_api.dart new file mode 100644 index 0000000000..f5b70a9ea4 --- /dev/null +++ b/mobile/openapi/lib/api/server_api.dart @@ -0,0 +1,741 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ServerApi { + ServerApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Delete server product key + /// + /// Delete the currently set server product key. + /// + /// Note: This method returns the HTTP [Response]. + Future deleteServerLicenseWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/license'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete server product key + /// + /// Delete the currently set server product key. + Future deleteServerLicense() async { + final response = await deleteServerLicenseWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Get server information + /// + /// Retrieve a list of information about the server. + /// + /// Note: This method returns the HTTP [Response]. + Future getAboutInfoWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/about'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get server information + /// + /// Retrieve a list of information about the server. + Future getAboutInfo() async { + final response = await getAboutInfoWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerAboutResponseDto',) as ServerAboutResponseDto; + + } + return null; + } + + /// Get APK links + /// + /// Retrieve links to the APKs for the current server version. + /// + /// Note: This method returns the HTTP [Response]. + Future getApkLinksWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/apk-links'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get APK links + /// + /// Retrieve links to the APKs for the current server version. + Future getApkLinks() async { + final response = await getApkLinksWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerApkLinksDto',) as ServerApkLinksDto; + + } + return null; + } + + /// Get config + /// + /// Retrieve the current server configuration. + /// + /// Note: This method returns the HTTP [Response]. + Future getServerConfigWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/config'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get config + /// + /// Retrieve the current server configuration. + Future getServerConfig() async { + final response = await getServerConfigWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerConfigDto',) as ServerConfigDto; + + } + return null; + } + + /// Get features + /// + /// Retrieve available features supported by this server. + /// + /// Note: This method returns the HTTP [Response]. + Future getServerFeaturesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/features'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get features + /// + /// Retrieve available features supported by this server. + Future getServerFeatures() async { + final response = await getServerFeaturesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerFeaturesDto',) as ServerFeaturesDto; + + } + return null; + } + + /// Get product key + /// + /// Retrieve information about whether the server currently has a product key registered. + /// + /// Note: This method returns the HTTP [Response]. + Future getServerLicenseWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/license'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get product key + /// + /// Retrieve information about whether the server currently has a product key registered. + Future getServerLicense() async { + final response = await getServerLicenseWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LicenseResponseDto',) as LicenseResponseDto; + + } + return null; + } + + /// Get statistics + /// + /// Retrieve statistics about the entire Immich instance such as asset counts. + /// + /// Note: This method returns the HTTP [Response]. + Future getServerStatisticsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/statistics'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get statistics + /// + /// Retrieve statistics about the entire Immich instance such as asset counts. + Future getServerStatistics() async { + final response = await getServerStatisticsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerStatsResponseDto',) as ServerStatsResponseDto; + + } + return null; + } + + /// Get server version + /// + /// Retrieve the current server version in semantic versioning (semver) format. + /// + /// Note: This method returns the HTTP [Response]. + Future getServerVersionWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/version'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get server version + /// + /// Retrieve the current server version in semantic versioning (semver) format. + Future getServerVersion() async { + final response = await getServerVersionWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerVersionResponseDto',) as ServerVersionResponseDto; + + } + return null; + } + + /// Get storage + /// + /// Retrieve the current storage utilization information of the server. + /// + /// Note: This method returns the HTTP [Response]. + Future getStorageWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/storage'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get storage + /// + /// Retrieve the current storage utilization information of the server. + Future getStorage() async { + final response = await getStorageWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerStorageResponseDto',) as ServerStorageResponseDto; + + } + return null; + } + + /// Get supported media types + /// + /// Retrieve all media types supported by the server. + /// + /// Note: This method returns the HTTP [Response]. + Future getSupportedMediaTypesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/media-types'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get supported media types + /// + /// Retrieve all media types supported by the server. + Future getSupportedMediaTypes() async { + final response = await getSupportedMediaTypesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerMediaTypesResponseDto',) as ServerMediaTypesResponseDto; + + } + return null; + } + + /// Get theme + /// + /// Retrieve the custom CSS, if existent. + /// + /// Note: This method returns the HTTP [Response]. + Future getThemeWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/theme'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get theme + /// + /// Retrieve the custom CSS, if existent. + Future getTheme() async { + final response = await getThemeWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerThemeDto',) as ServerThemeDto; + + } + return null; + } + + /// Get version check status + /// + /// Retrieve information about the last time the version check ran. + /// + /// Note: This method returns the HTTP [Response]. + Future getVersionCheckWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/version-check'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get version check status + /// + /// Retrieve information about the last time the version check ran. + Future getVersionCheck() async { + final response = await getVersionCheckWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'VersionCheckStateResponseDto',) as VersionCheckStateResponseDto; + + } + return null; + } + + /// Get version history + /// + /// Retrieve a list of past versions the server has been on. + /// + /// Note: This method returns the HTTP [Response]. + Future getVersionHistoryWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/version-history'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get version history + /// + /// Retrieve a list of past versions the server has been on. + Future?> getVersionHistory() async { + final response = await getVersionHistoryWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Ping + /// + /// Pong + /// + /// Note: This method returns the HTTP [Response]. + Future pingServerWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/server/ping'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Ping + /// + /// Pong + Future pingServer() async { + final response = await pingServerWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ServerPingResponse',) as ServerPingResponse; + + } + return null; + } + + /// Set server product key + /// + /// Validate and set the server product key if successful. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [LicenseKeyDto] licenseKeyDto (required): + Future setServerLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/server/license'; + + // ignore: prefer_final_locals + Object? postBody = licenseKeyDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Set server product key + /// + /// Validate and set the server product key if successful. + /// + /// Parameters: + /// + /// * [LicenseKeyDto] licenseKeyDto (required): + Future setServerLicense(LicenseKeyDto licenseKeyDto,) async { + final response = await setServerLicenseWithHttpInfo(licenseKeyDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LicenseResponseDto',) as LicenseResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/sessions_api.dart b/mobile/openapi/lib/api/sessions_api.dart new file mode 100644 index 0000000000..da508059bc --- /dev/null +++ b/mobile/openapi/lib/api/sessions_api.dart @@ -0,0 +1,324 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SessionsApi { + SessionsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a session + /// + /// Create a session as a child to the current session. This endpoint is used for casting. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SessionCreateDto] sessionCreateDto (required): + Future createSessionWithHttpInfo(SessionCreateDto sessionCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions'; + + // ignore: prefer_final_locals + Object? postBody = sessionCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a session + /// + /// Create a session as a child to the current session. This endpoint is used for casting. + /// + /// Parameters: + /// + /// * [SessionCreateDto] sessionCreateDto (required): + Future createSession(SessionCreateDto sessionCreateDto,) async { + final response = await createSessionWithHttpInfo(sessionCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SessionCreateResponseDto',) as SessionCreateResponseDto; + + } + return null; + } + + /// Delete all sessions + /// + /// Delete all sessions for the user. This will not delete the current session. + /// + /// Note: This method returns the HTTP [Response]. + Future deleteAllSessionsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete all sessions + /// + /// Delete all sessions for the user. This will not delete the current session. + Future deleteAllSessions() async { + final response = await deleteAllSessionsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete a session + /// + /// Delete a specific session by id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteSessionWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a session + /// + /// Delete a specific session by id. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteSession(String id,) async { + final response = await deleteSessionWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve sessions + /// + /// Retrieve a list of sessions for the user. + /// + /// Note: This method returns the HTTP [Response]. + Future getSessionsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve sessions + /// + /// Retrieve a list of sessions for the user. + Future?> getSessions() async { + final response = await getSessionsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Lock a session + /// + /// Lock a specific session by id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future lockSessionWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions/{id}/lock' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Lock a session + /// + /// Lock a specific session by id. + /// + /// Parameters: + /// + /// * [String] id (required): + Future lockSession(String id,) async { + final response = await lockSessionWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Update a session + /// + /// Update a specific session identified by id. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [SessionUpdateDto] sessionUpdateDto (required): + Future updateSessionWithHttpInfo(String id, SessionUpdateDto sessionUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sessions/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = sessionUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a session + /// + /// Update a specific session identified by id. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [SessionUpdateDto] sessionUpdateDto (required): + Future updateSession(String id, SessionUpdateDto sessionUpdateDto,) async { + final response = await updateSessionWithHttpInfo(id, sessionUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SessionResponseDto',) as SessionResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/shared_links_api.dart b/mobile/openapi/lib/api/shared_links_api.dart new file mode 100644 index 0000000000..084662ace8 --- /dev/null +++ b/mobile/openapi/lib/api/shared_links_api.dart @@ -0,0 +1,614 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SharedLinksApi { + SharedLinksApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Add assets to a shared link + /// + /// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetIdsDto] assetIdsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future addSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Add assets to a shared link + /// + /// Add assets to a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetIdsDto] assetIdsDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future?> addSharedLinkAssets(String id, AssetIdsDto assetIdsDto, { String? key, String? slug, }) async { + final response = await addSharedLinkAssetsWithHttpInfo(id, assetIdsDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Create a shared link + /// + /// Create a new shared link. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SharedLinkCreateDto] sharedLinkCreateDto (required): + Future createSharedLinkWithHttpInfo(SharedLinkCreateDto sharedLinkCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links'; + + // ignore: prefer_final_locals + Object? postBody = sharedLinkCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a shared link + /// + /// Create a new shared link. + /// + /// Parameters: + /// + /// * [SharedLinkCreateDto] sharedLinkCreateDto (required): + Future createSharedLink(SharedLinkCreateDto sharedLinkCreateDto,) async { + final response = await createSharedLinkWithHttpInfo(sharedLinkCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto; + + } + return null; + } + + /// Retrieve all shared links + /// + /// Retrieve a list of all shared links. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] albumId: + /// Filter by album ID + /// + /// * [String] id: + /// Filter by shared link ID + Future getAllSharedLinksWithHttpInfo({ String? albumId, String? id, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (albumId != null) { + queryParams.addAll(_queryParams('', 'albumId', albumId)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve all shared links + /// + /// Retrieve a list of all shared links. + /// + /// Parameters: + /// + /// * [String] albumId: + /// Filter by album ID + /// + /// * [String] id: + /// Filter by shared link ID + Future?> getAllSharedLinks({ String? albumId, String? id, }) async { + final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, id: id, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve current shared link + /// + /// Retrieve the current shared link associated with authentication method. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] key: + /// + /// * [String] password: + /// Link password + /// + /// * [String] slug: + /// + /// * [String] token: + /// Access token + Future getMySharedLinkWithHttpInfo({ String? key, String? password, String? slug, String? token, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/me'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (password != null) { + queryParams.addAll(_queryParams('', 'password', password)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + if (token != null) { + queryParams.addAll(_queryParams('', 'token', token)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve current shared link + /// + /// Retrieve the current shared link associated with authentication method. + /// + /// Parameters: + /// + /// * [String] key: + /// + /// * [String] password: + /// Link password + /// + /// * [String] slug: + /// + /// * [String] token: + /// Access token + Future getMySharedLink({ String? key, String? password, String? slug, String? token, }) async { + final response = await getMySharedLinkWithHttpInfo( key: key, password: password, slug: slug, token: token, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto; + + } + return null; + } + + /// Retrieve a shared link + /// + /// Retrieve a specific shared link by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getSharedLinkByIdWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a shared link + /// + /// Retrieve a specific shared link by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getSharedLinkById(String id,) async { + final response = await getSharedLinkByIdWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto; + + } + return null; + } + + /// Delete a shared link + /// + /// Delete a specific shared link by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removeSharedLinkWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a shared link + /// + /// Delete a specific shared link by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future removeSharedLink(String id,) async { + final response = await removeSharedLinkWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Remove assets from a shared link + /// + /// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetIdsDto] assetIdsDto (required): + Future removeSharedLinkAssetsWithHttpInfo(String id, AssetIdsDto assetIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = assetIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove assets from a shared link + /// + /// Remove assets from a specific shared link by its ID. This endpoint is only relevant for shared link of type individual. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [AssetIdsDto] assetIdsDto (required): + Future?> removeSharedLinkAssets(String id, AssetIdsDto assetIdsDto,) async { + final response = await removeSharedLinkAssetsWithHttpInfo(id, assetIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Shared link login + /// + /// Login to a password protected shared link + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SharedLinkLoginDto] sharedLinkLoginDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future sharedLinkLoginWithHttpInfo(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/login'; + + // ignore: prefer_final_locals + Object? postBody = sharedLinkLoginDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Shared link login + /// + /// Login to a password protected shared link + /// + /// Parameters: + /// + /// * [SharedLinkLoginDto] sharedLinkLoginDto (required): + /// + /// * [String] key: + /// + /// * [String] slug: + Future sharedLinkLogin(SharedLinkLoginDto sharedLinkLoginDto, { String? key, String? slug, }) async { + final response = await sharedLinkLoginWithHttpInfo(sharedLinkLoginDto, key: key, slug: slug, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto; + + } + return null; + } + + /// Update a shared link + /// + /// Update an existing shared link by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [SharedLinkEditDto] sharedLinkEditDto (required): + Future updateSharedLinkWithHttpInfo(String id, SharedLinkEditDto sharedLinkEditDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/shared-links/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = sharedLinkEditDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PATCH', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a shared link + /// + /// Update an existing shared link by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [SharedLinkEditDto] sharedLinkEditDto (required): + Future updateSharedLink(String id, SharedLinkEditDto sharedLinkEditDto,) async { + final response = await updateSharedLinkWithHttpInfo(id, sharedLinkEditDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/stacks_api.dart b/mobile/openapi/lib/api/stacks_api.dart new file mode 100644 index 0000000000..a691af2a7d --- /dev/null +++ b/mobile/openapi/lib/api/stacks_api.dart @@ -0,0 +1,408 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class StacksApi { + StacksApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a stack + /// + /// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [StackCreateDto] stackCreateDto (required): + Future createStackWithHttpInfo(StackCreateDto stackCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks'; + + // ignore: prefer_final_locals + Object? postBody = stackCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a stack + /// + /// Create a new stack by providing a name and a list of asset IDs to include in the stack. If any of the provided asset IDs are primary assets of an existing stack, the existing stack will be merged into the newly created stack. + /// + /// Parameters: + /// + /// * [StackCreateDto] stackCreateDto (required): + Future createStack(StackCreateDto stackCreateDto,) async { + final response = await createStackWithHttpInfo(stackCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'StackResponseDto',) as StackResponseDto; + + } + return null; + } + + /// Delete a stack + /// + /// Delete a specific stack by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteStackWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a stack + /// + /// Delete a specific stack by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteStack(String id,) async { + final response = await deleteStackWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete stacks + /// + /// Delete multiple stacks by providing a list of stack IDs. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deleteStacksWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks'; + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete stacks + /// + /// Delete multiple stacks by providing a list of stack IDs. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future deleteStacks(BulkIdsDto bulkIdsDto,) async { + final response = await deleteStacksWithHttpInfo(bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve a stack + /// + /// Retrieve a specific stack by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getStackWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a stack + /// + /// Retrieve a specific stack by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getStack(String id,) async { + final response = await getStackWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'StackResponseDto',) as StackResponseDto; + + } + return null; + } + + /// Remove an asset from a stack + /// + /// Remove a specific asset from a stack by providing the stack ID and asset ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] assetId (required): + /// + /// * [String] id (required): + Future removeAssetFromStackWithHttpInfo(String assetId, String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks/{id}/assets/{assetId}' + .replaceAll('{assetId}', assetId) + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Remove an asset from a stack + /// + /// Remove a specific asset from a stack by providing the stack ID and asset ID. + /// + /// Parameters: + /// + /// * [String] assetId (required): + /// + /// * [String] id (required): + Future removeAssetFromStack(String assetId, String id,) async { + final response = await removeAssetFromStackWithHttpInfo(assetId, id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve stacks + /// + /// Retrieve a list of stacks. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] primaryAssetId: + /// Filter by primary asset ID + Future searchStacksWithHttpInfo({ String? primaryAssetId, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (primaryAssetId != null) { + queryParams.addAll(_queryParams('', 'primaryAssetId', primaryAssetId)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve stacks + /// + /// Retrieve a list of stacks. + /// + /// Parameters: + /// + /// * [String] primaryAssetId: + /// Filter by primary asset ID + Future?> searchStacks({ String? primaryAssetId, }) async { + final response = await searchStacksWithHttpInfo( primaryAssetId: primaryAssetId, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a stack + /// + /// Update an existing stack by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [StackUpdateDto] stackUpdateDto (required): + Future updateStackWithHttpInfo(String id, StackUpdateDto stackUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/stacks/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = stackUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a stack + /// + /// Update an existing stack by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [StackUpdateDto] stackUpdateDto (required): + Future updateStack(String id, StackUpdateDto stackUpdateDto,) async { + final response = await updateStackWithHttpInfo(id, stackUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'StackResponseDto',) as StackResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart new file mode 100644 index 0000000000..6194fd0f89 --- /dev/null +++ b/mobile/openapi/lib/api/sync_api.dart @@ -0,0 +1,328 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SyncApi { + SyncApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Delete acknowledgements + /// + /// Delete specific synchronization acknowledgments. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SyncAckDeleteDto] syncAckDeleteDto (required): + Future deleteSyncAckWithHttpInfo(SyncAckDeleteDto syncAckDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/ack'; + + // ignore: prefer_final_locals + Object? postBody = syncAckDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete acknowledgements + /// + /// Delete specific synchronization acknowledgments. + /// + /// Parameters: + /// + /// * [SyncAckDeleteDto] syncAckDeleteDto (required): + Future deleteSyncAck(SyncAckDeleteDto syncAckDeleteDto,) async { + final response = await deleteSyncAckWithHttpInfo(syncAckDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSyncWithHttpInfo(AssetDeltaSyncDto assetDeltaSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/delta-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetDeltaSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get delta sync for user + /// + /// Retrieve changed assets since the last sync for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetDeltaSyncDto] assetDeltaSyncDto (required): + Future getDeltaSync(AssetDeltaSyncDto assetDeltaSyncDto,) async { + final response = await getDeltaSyncWithHttpInfo(assetDeltaSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetDeltaSyncResponseDto',) as AssetDeltaSyncResponseDto; + + } + return null; + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future getFullSyncForUserWithHttpInfo(AssetFullSyncDto assetFullSyncDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/full-sync'; + + // ignore: prefer_final_locals + Object? postBody = assetFullSyncDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get full sync for user + /// + /// Retrieve all assets for a full synchronization for the authenticated user. + /// + /// Parameters: + /// + /// * [AssetFullSyncDto] assetFullSyncDto (required): + Future?> getFullSyncForUser(AssetFullSyncDto assetFullSyncDto,) async { + final response = await getFullSyncForUserWithHttpInfo(assetFullSyncDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve acknowledgements + /// + /// Retrieve the synchronization acknowledgments for the current session. + /// + /// Note: This method returns the HTTP [Response]. + Future getSyncAckWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/ack'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve acknowledgements + /// + /// Retrieve the synchronization acknowledgments for the current session. + Future?> getSyncAck() async { + final response = await getSyncAckWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Stream sync changes + /// + /// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SyncStreamDto] syncStreamDto (required): + Future getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/stream'; + + // ignore: prefer_final_locals + Object? postBody = syncStreamDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Stream sync changes + /// + /// Retrieve a JSON lines streamed response of changes for synchronization. This endpoint is used by the mobile app to efficiently stay up to date with changes. + /// + /// Parameters: + /// + /// * [SyncStreamDto] syncStreamDto (required): + Future getSyncStream(SyncStreamDto syncStreamDto,) async { + final response = await getSyncStreamWithHttpInfo(syncStreamDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Acknowledge changes + /// + /// Send a list of synchronization acknowledgements to confirm that the latest changes have been received. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SyncAckSetDto] syncAckSetDto (required): + Future sendSyncAckWithHttpInfo(SyncAckSetDto syncAckSetDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/sync/ack'; + + // ignore: prefer_final_locals + Object? postBody = syncAckSetDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Acknowledge changes + /// + /// Send a list of synchronization acknowledgements to confirm that the latest changes have been received. + /// + /// Parameters: + /// + /// * [SyncAckSetDto] syncAckSetDto (required): + Future sendSyncAck(SyncAckSetDto syncAckSetDto,) async { + final response = await sendSyncAckWithHttpInfo(syncAckSetDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart new file mode 100644 index 0000000000..b04da71273 --- /dev/null +++ b/mobile/openapi/lib/api/system_config_api.dart @@ -0,0 +1,218 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SystemConfigApi { + SystemConfigApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Get system configuration + /// + /// Retrieve the current system configuration. + /// + /// Note: This method returns the HTTP [Response]. + Future getConfigWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-config'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get system configuration + /// + /// Retrieve the current system configuration. + Future getConfig() async { + final response = await getConfigWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SystemConfigDto',) as SystemConfigDto; + + } + return null; + } + + /// Get system configuration defaults + /// + /// Retrieve the default values for the system configuration. + /// + /// Note: This method returns the HTTP [Response]. + Future getConfigDefaultsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-config/defaults'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get system configuration defaults + /// + /// Retrieve the default values for the system configuration. + Future getConfigDefaults() async { + final response = await getConfigDefaultsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SystemConfigDto',) as SystemConfigDto; + + } + return null; + } + + /// Get storage template options + /// + /// Retrieve exemplary storage template options. + /// + /// Note: This method returns the HTTP [Response]. + Future getStorageTemplateOptionsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-config/storage-template-options'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get storage template options + /// + /// Retrieve exemplary storage template options. + Future getStorageTemplateOptions() async { + final response = await getStorageTemplateOptionsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SystemConfigTemplateStorageOptionDto',) as SystemConfigTemplateStorageOptionDto; + + } + return null; + } + + /// Update system configuration + /// + /// Update the system configuration with a new system configuration. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [SystemConfigDto] systemConfigDto (required): + Future updateConfigWithHttpInfo(SystemConfigDto systemConfigDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/system-config'; + + // ignore: prefer_final_locals + Object? postBody = systemConfigDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update system configuration + /// + /// Update the system configuration with a new system configuration. + /// + /// Parameters: + /// + /// * [SystemConfigDto] systemConfigDto (required): + Future updateConfig(SystemConfigDto systemConfigDto,) async { + final response = await updateConfigWithHttpInfo(systemConfigDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SystemConfigDto',) as SystemConfigDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/system_metadata_api.dart b/mobile/openapi/lib/api/system_metadata_api.dart new file mode 100644 index 0000000000..63fd7628ec --- /dev/null +++ b/mobile/openapi/lib/api/system_metadata_api.dart @@ -0,0 +1,210 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SystemMetadataApi { + SystemMetadataApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve admin onboarding + /// + /// Retrieve the current admin onboarding status. + /// + /// Note: This method returns the HTTP [Response]. + Future getAdminOnboardingWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-metadata/admin-onboarding'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve admin onboarding + /// + /// Retrieve the current admin onboarding status. + Future getAdminOnboarding() async { + final response = await getAdminOnboardingWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AdminOnboardingUpdateDto',) as AdminOnboardingUpdateDto; + + } + return null; + } + + /// Retrieve reverse geocoding state + /// + /// Retrieve the current state of the reverse geocoding import. + /// + /// Note: This method returns the HTTP [Response]. + Future getReverseGeocodingStateWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-metadata/reverse-geocoding-state'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve reverse geocoding state + /// + /// Retrieve the current state of the reverse geocoding import. + Future getReverseGeocodingState() async { + final response = await getReverseGeocodingStateWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'ReverseGeocodingStateResponseDto',) as ReverseGeocodingStateResponseDto; + + } + return null; + } + + /// Retrieve version check state + /// + /// Retrieve the current state of the version check process. + /// + /// Note: This method returns the HTTP [Response]. + Future getVersionCheckStateWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/system-metadata/version-check-state'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve version check state + /// + /// Retrieve the current state of the version check process. + Future getVersionCheckState() async { + final response = await getVersionCheckStateWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'VersionCheckStateResponseDto',) as VersionCheckStateResponseDto; + + } + return null; + } + + /// Update admin onboarding + /// + /// Update the admin onboarding status. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [AdminOnboardingUpdateDto] adminOnboardingUpdateDto (required): + Future updateAdminOnboardingWithHttpInfo(AdminOnboardingUpdateDto adminOnboardingUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/system-metadata/admin-onboarding'; + + // ignore: prefer_final_locals + Object? postBody = adminOnboardingUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update admin onboarding + /// + /// Update the admin onboarding status. + /// + /// Parameters: + /// + /// * [AdminOnboardingUpdateDto] adminOnboardingUpdateDto (required): + Future updateAdminOnboarding(AdminOnboardingUpdateDto adminOnboardingUpdateDto,) async { + final response = await updateAdminOnboardingWithHttpInfo(adminOnboardingUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } +} diff --git a/mobile/openapi/lib/api/tags_api.dart b/mobile/openapi/lib/api/tags_api.dart new file mode 100644 index 0000000000..a6840f9483 --- /dev/null +++ b/mobile/openapi/lib/api/tags_api.dart @@ -0,0 +1,535 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class TagsApi { + TagsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Tag assets + /// + /// Add multiple tags to multiple assets in a single request. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [TagBulkAssetsDto] tagBulkAssetsDto (required): + Future bulkTagAssetsWithHttpInfo(TagBulkAssetsDto tagBulkAssetsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/assets'; + + // ignore: prefer_final_locals + Object? postBody = tagBulkAssetsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Tag assets + /// + /// Add multiple tags to multiple assets in a single request. + /// + /// Parameters: + /// + /// * [TagBulkAssetsDto] tagBulkAssetsDto (required): + Future bulkTagAssets(TagBulkAssetsDto tagBulkAssetsDto,) async { + final response = await bulkTagAssetsWithHttpInfo(tagBulkAssetsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TagBulkAssetsResponseDto',) as TagBulkAssetsResponseDto; + + } + return null; + } + + /// Create a tag + /// + /// Create a new tag by providing a name and optional color. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [TagCreateDto] tagCreateDto (required): + Future createTagWithHttpInfo(TagCreateDto tagCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags'; + + // ignore: prefer_final_locals + Object? postBody = tagCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a tag + /// + /// Create a new tag by providing a name and optional color. + /// + /// Parameters: + /// + /// * [TagCreateDto] tagCreateDto (required): + Future createTag(TagCreateDto tagCreateDto,) async { + final response = await createTagWithHttpInfo(tagCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TagResponseDto',) as TagResponseDto; + + } + return null; + } + + /// Delete a tag + /// + /// Delete a specific tag by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteTagWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a tag + /// + /// Delete a specific tag by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteTag(String id,) async { + final response = await deleteTagWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve tags + /// + /// Retrieve a list of all tags. + /// + /// Note: This method returns the HTTP [Response]. + Future getAllTagsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/tags'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve tags + /// + /// Retrieve a list of all tags. + Future?> getAllTags() async { + final response = await getAllTagsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve a tag + /// + /// Retrieve a specific tag by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getTagByIdWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a tag + /// + /// Retrieve a specific tag by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getTagById(String id,) async { + final response = await getTagByIdWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TagResponseDto',) as TagResponseDto; + + } + return null; + } + + /// Tag assets + /// + /// Add a tag to all the specified assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future tagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Tag assets + /// + /// Add a tag to all the specified assets. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> tagAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await tagAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Untag assets + /// + /// Remove a tag from all the specified assets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future untagAssetsWithHttpInfo(String id, BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/{id}/assets' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Untag assets + /// + /// Remove a tag from all the specified assets. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future?> untagAssets(String id, BulkIdsDto bulkIdsDto,) async { + final response = await untagAssetsWithHttpInfo(id, bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a tag + /// + /// Update an existing tag identified by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [TagUpdateDto] tagUpdateDto (required): + Future updateTagWithHttpInfo(String id, TagUpdateDto tagUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = tagUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a tag + /// + /// Update an existing tag identified by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [TagUpdateDto] tagUpdateDto (required): + Future updateTag(String id, TagUpdateDto tagUpdateDto,) async { + final response = await updateTagWithHttpInfo(id, tagUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TagResponseDto',) as TagResponseDto; + + } + return null; + } + + /// Upsert tags + /// + /// Create or update multiple tags in a single request. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [TagUpsertDto] tagUpsertDto (required): + Future upsertTagsWithHttpInfo(TagUpsertDto tagUpsertDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/tags'; + + // ignore: prefer_final_locals + Object? postBody = tagUpsertDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Upsert tags + /// + /// Create or update multiple tags in a single request. + /// + /// Parameters: + /// + /// * [TagUpsertDto] tagUpsertDto (required): + Future?> upsertTags(TagUpsertDto tagUpsertDto,) async { + final response = await upsertTagsWithHttpInfo(tagUpsertDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/timeline_api.dart b/mobile/openapi/lib/api/timeline_api.dart new file mode 100644 index 0000000000..f82c362ff7 --- /dev/null +++ b/mobile/openapi/lib/api/timeline_api.dart @@ -0,0 +1,378 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class TimelineApi { + TimelineApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Get time bucket + /// + /// Retrieve a string of all asset ids in a given time bucket. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] timeBucket (required): + /// Time bucket identifier in YYYY-MM-DD format (e.g., \"2024-01-01\" for January 2024) + /// + /// * [String] albumId: + /// Filter assets belonging to a specific album + /// + /// * [String] bbox: + /// Bounding box coordinates as west,south,east,north (WGS84) + /// + /// * [bool] isFavorite: + /// Filter by favorite status (true for favorites only, false for non-favorites only) + /// + /// * [bool] isTrashed: + /// Filter by trash status (true for trashed assets only, false for non-trashed only) + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) + /// + /// * [String] personId: + /// Filter assets containing a specific person (face recognition) + /// + /// * [String] slug: + /// + /// * [String] tagId: + /// Filter assets with a specific tag + /// + /// * [String] userId: + /// Filter assets by specific user ID + /// + /// * [AssetVisibility] visibility: + /// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED) + /// + /// * [bool] withCoordinates: + /// Include location data in the response + /// + /// * [bool] withPartners: + /// Include assets shared by partners + /// + /// * [bool] withStacked: + /// Include stacked assets in the response. When true, only primary assets from stacks are returned. + Future getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/timeline/bucket'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (albumId != null) { + queryParams.addAll(_queryParams('', 'albumId', albumId)); + } + if (bbox != null) { + queryParams.addAll(_queryParams('', 'bbox', bbox)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (personId != null) { + queryParams.addAll(_queryParams('', 'personId', personId)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + if (tagId != null) { + queryParams.addAll(_queryParams('', 'tagId', tagId)); + } + queryParams.addAll(_queryParams('', 'timeBucket', timeBucket)); + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + if (visibility != null) { + queryParams.addAll(_queryParams('', 'visibility', visibility)); + } + if (withCoordinates != null) { + queryParams.addAll(_queryParams('', 'withCoordinates', withCoordinates)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } + if (withStacked != null) { + queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get time bucket + /// + /// Retrieve a string of all asset ids in a given time bucket. + /// + /// Parameters: + /// + /// * [String] timeBucket (required): + /// Time bucket identifier in YYYY-MM-DD format (e.g., \"2024-01-01\" for January 2024) + /// + /// * [String] albumId: + /// Filter assets belonging to a specific album + /// + /// * [String] bbox: + /// Bounding box coordinates as west,south,east,north (WGS84) + /// + /// * [bool] isFavorite: + /// Filter by favorite status (true for favorites only, false for non-favorites only) + /// + /// * [bool] isTrashed: + /// Filter by trash status (true for trashed assets only, false for non-trashed only) + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) + /// + /// * [String] personId: + /// Filter assets containing a specific person (face recognition) + /// + /// * [String] slug: + /// + /// * [String] tagId: + /// Filter assets with a specific tag + /// + /// * [String] userId: + /// Filter assets by specific user ID + /// + /// * [AssetVisibility] visibility: + /// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED) + /// + /// * [bool] withCoordinates: + /// Include location data in the response + /// + /// * [bool] withPartners: + /// Include assets shared by partners + /// + /// * [bool] withStacked: + /// Include stacked assets in the response. When true, only primary assets from stacks are returned. + Future getTimeBucket(String timeBucket, { String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TimeBucketAssetResponseDto',) as TimeBucketAssetResponseDto; + + } + return null; + } + + /// Get time buckets + /// + /// Retrieve a list of all minimal time buckets. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] albumId: + /// Filter assets belonging to a specific album + /// + /// * [String] bbox: + /// Bounding box coordinates as west,south,east,north (WGS84) + /// + /// * [bool] isFavorite: + /// Filter by favorite status (true for favorites only, false for non-favorites only) + /// + /// * [bool] isTrashed: + /// Filter by trash status (true for trashed assets only, false for non-trashed only) + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) + /// + /// * [String] personId: + /// Filter assets containing a specific person (face recognition) + /// + /// * [String] slug: + /// + /// * [String] tagId: + /// Filter assets with a specific tag + /// + /// * [String] userId: + /// Filter assets by specific user ID + /// + /// * [AssetVisibility] visibility: + /// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED) + /// + /// * [bool] withCoordinates: + /// Include location data in the response + /// + /// * [bool] withPartners: + /// Include assets shared by partners + /// + /// * [bool] withStacked: + /// Include stacked assets in the response. When true, only primary assets from stacks are returned. + Future getTimeBucketsWithHttpInfo({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/timeline/buckets'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (albumId != null) { + queryParams.addAll(_queryParams('', 'albumId', albumId)); + } + if (bbox != null) { + queryParams.addAll(_queryParams('', 'bbox', bbox)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (key != null) { + queryParams.addAll(_queryParams('', 'key', key)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (personId != null) { + queryParams.addAll(_queryParams('', 'personId', personId)); + } + if (slug != null) { + queryParams.addAll(_queryParams('', 'slug', slug)); + } + if (tagId != null) { + queryParams.addAll(_queryParams('', 'tagId', tagId)); + } + if (userId != null) { + queryParams.addAll(_queryParams('', 'userId', userId)); + } + if (visibility != null) { + queryParams.addAll(_queryParams('', 'visibility', visibility)); + } + if (withCoordinates != null) { + queryParams.addAll(_queryParams('', 'withCoordinates', withCoordinates)); + } + if (withPartners != null) { + queryParams.addAll(_queryParams('', 'withPartners', withPartners)); + } + if (withStacked != null) { + queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get time buckets + /// + /// Retrieve a list of all minimal time buckets. + /// + /// Parameters: + /// + /// * [String] albumId: + /// Filter assets belonging to a specific album + /// + /// * [String] bbox: + /// Bounding box coordinates as west,south,east,north (WGS84) + /// + /// * [bool] isFavorite: + /// Filter by favorite status (true for favorites only, false for non-favorites only) + /// + /// * [bool] isTrashed: + /// Filter by trash status (true for trashed assets only, false for non-trashed only) + /// + /// * [String] key: + /// + /// * [AssetOrder] order: + /// Sort order for assets within time buckets (ASC for oldest first, DESC for newest first) + /// + /// * [String] personId: + /// Filter assets containing a specific person (face recognition) + /// + /// * [String] slug: + /// + /// * [String] tagId: + /// Filter assets with a specific tag + /// + /// * [String] userId: + /// Filter assets by specific user ID + /// + /// * [AssetVisibility] visibility: + /// Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED) + /// + /// * [bool] withCoordinates: + /// Include location data in the response + /// + /// * [bool] withPartners: + /// Include assets shared by partners + /// + /// * [bool] withStacked: + /// Include stacked assets in the response. When true, only primary assets from stacks are returned. + Future?> getTimeBuckets({ String? albumId, String? bbox, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? slug, String? tagId, String? userId, AssetVisibility? visibility, bool? withCoordinates, bool? withPartners, bool? withStacked, }) async { + final response = await getTimeBucketsWithHttpInfo( albumId: albumId, bbox: bbox, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, slug: slug, tagId: tagId, userId: userId, visibility: visibility, withCoordinates: withCoordinates, withPartners: withPartners, withStacked: withStacked, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/trash_api.dart b/mobile/openapi/lib/api/trash_api.dart new file mode 100644 index 0000000000..f1dcbb8896 --- /dev/null +++ b/mobile/openapi/lib/api/trash_api.dart @@ -0,0 +1,170 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class TrashApi { + TrashApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Empty trash + /// + /// Permanently delete all items in the trash. + /// + /// Note: This method returns the HTTP [Response]. + Future emptyTrashWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/trash/empty'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Empty trash + /// + /// Permanently delete all items in the trash. + Future emptyTrash() async { + final response = await emptyTrashWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TrashResponseDto',) as TrashResponseDto; + + } + return null; + } + + /// Restore assets + /// + /// Restore specific assets from the trash. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future restoreAssetsWithHttpInfo(BulkIdsDto bulkIdsDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/trash/restore/assets'; + + // ignore: prefer_final_locals + Object? postBody = bulkIdsDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Restore assets + /// + /// Restore specific assets from the trash. + /// + /// Parameters: + /// + /// * [BulkIdsDto] bulkIdsDto (required): + Future restoreAssets(BulkIdsDto bulkIdsDto,) async { + final response = await restoreAssetsWithHttpInfo(bulkIdsDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TrashResponseDto',) as TrashResponseDto; + + } + return null; + } + + /// Restore trash + /// + /// Restore all items in the trash. + /// + /// Note: This method returns the HTTP [Response]. + Future restoreTrashWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/trash/restore'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Restore trash + /// + /// Restore all items in the trash. + Future restoreTrash() async { + final response = await restoreTrashWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TrashResponseDto',) as TrashResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/users_admin_api.dart b/mobile/openapi/lib/api/users_admin_api.dart new file mode 100644 index 0000000000..59a4b60096 --- /dev/null +++ b/mobile/openapi/lib/api/users_admin_api.dart @@ -0,0 +1,647 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class UsersAdminApi { + UsersAdminApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a user + /// + /// Create a new user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [UserAdminCreateDto] userAdminCreateDto (required): + Future createUserAdminWithHttpInfo(UserAdminCreateDto userAdminCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users'; + + // ignore: prefer_final_locals + Object? postBody = userAdminCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a user + /// + /// Create a new user. + /// + /// Parameters: + /// + /// * [UserAdminCreateDto] userAdminCreateDto (required): + Future createUserAdmin(UserAdminCreateDto userAdminCreateDto,) async { + final response = await createUserAdminWithHttpInfo(userAdminCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Delete a user + /// + /// Delete a user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserAdminDeleteDto] userAdminDeleteDto (required): + Future deleteUserAdminWithHttpInfo(String id, UserAdminDeleteDto userAdminDeleteDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = userAdminDeleteDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a user + /// + /// Delete a user. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserAdminDeleteDto] userAdminDeleteDto (required): + Future deleteUserAdmin(String id, UserAdminDeleteDto userAdminDeleteDto,) async { + final response = await deleteUserAdminWithHttpInfo(id, userAdminDeleteDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserAdminWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserAdmin(String id,) async { + final response = await getUserAdminWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Retrieve user preferences + /// + /// Retrieve the preferences of a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserPreferencesAdminWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/preferences' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user preferences + /// + /// Retrieve the preferences of a specific user. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserPreferencesAdmin(String id,) async { + final response = await getUserPreferencesAdminWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto; + + } + return null; + } + + /// Retrieve user sessions + /// + /// Retrieve all sessions for a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserSessionsAdminWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/sessions' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user sessions + /// + /// Retrieve all sessions for a specific user. + /// + /// Parameters: + /// + /// * [String] id (required): + Future?> getUserSessionsAdmin(String id,) async { + final response = await getUserSessionsAdminWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve user statistics + /// + /// Retrieve asset statistics for a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isTrashed: + /// Filter by trash status + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + Future getUserStatisticsAdminWithHttpInfo(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/statistics' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isTrashed != null) { + queryParams.addAll(_queryParams('', 'isTrashed', isTrashed)); + } + if (visibility != null) { + queryParams.addAll(_queryParams('', 'visibility', visibility)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user statistics + /// + /// Retrieve asset statistics for a specific user. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [bool] isFavorite: + /// Filter by favorite status + /// + /// * [bool] isTrashed: + /// Filter by trash status + /// + /// * [AssetVisibility] visibility: + /// Filter by visibility + Future getUserStatisticsAdmin(String id, { bool? isFavorite, bool? isTrashed, AssetVisibility? visibility, }) async { + final response = await getUserStatisticsAdminWithHttpInfo(id, isFavorite: isFavorite, isTrashed: isTrashed, visibility: visibility, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetStatsResponseDto',) as AssetStatsResponseDto; + + } + return null; + } + + /// Restore a deleted user + /// + /// Restore a previously deleted user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future restoreUserAdminWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/restore' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Restore a deleted user + /// + /// Restore a previously deleted user. + /// + /// Parameters: + /// + /// * [String] id (required): + Future restoreUserAdmin(String id,) async { + final response = await restoreUserAdminWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Search users + /// + /// Search for users. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id: + /// User ID filter + /// + /// * [bool] withDeleted: + /// Include deleted users + Future searchUsersAdminWithHttpInfo({ String? id, bool? withDeleted, }) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (withDeleted != null) { + queryParams.addAll(_queryParams('', 'withDeleted', withDeleted)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Search users + /// + /// Search for users. + /// + /// Parameters: + /// + /// * [String] id: + /// User ID filter + /// + /// * [bool] withDeleted: + /// Include deleted users + Future?> searchUsersAdmin({ String? id, bool? withDeleted, }) async { + final response = await searchUsersAdminWithHttpInfo( id: id, withDeleted: withDeleted, ); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a user + /// + /// Update an existing user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserAdminUpdateDto] userAdminUpdateDto (required): + Future updateUserAdminWithHttpInfo(String id, UserAdminUpdateDto userAdminUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = userAdminUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a user + /// + /// Update an existing user. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserAdminUpdateDto] userAdminUpdateDto (required): + Future updateUserAdmin(String id, UserAdminUpdateDto userAdminUpdateDto,) async { + final response = await updateUserAdminWithHttpInfo(id, userAdminUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Update user preferences + /// + /// Update the preferences of a specific user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): + Future updateUserPreferencesAdminWithHttpInfo(String id, UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/admin/users/{id}/preferences' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = userPreferencesUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update user preferences + /// + /// Update the preferences of a specific user. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): + Future updateUserPreferencesAdmin(String id, UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + final response = await updateUserPreferencesAdminWithHttpInfo(id, userPreferencesUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/users_api.dart b/mobile/openapi/lib/api/users_api.dart new file mode 100644 index 0000000000..7ccae02c76 --- /dev/null +++ b/mobile/openapi/lib/api/users_api.dart @@ -0,0 +1,787 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class UsersApi { + UsersApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create user profile image + /// + /// Upload and set a new profile image for the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [MultipartFile] file (required): + /// Profile image file + Future createProfileImageWithHttpInfo(MultipartFile file,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/profile-image'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['multipart/form-data']; + + bool hasFields = false; + final mp = MultipartRequest('POST', Uri.parse(apiPath)); + if (file != null) { + hasFields = true; + mp.fields[r'file'] = file.field; + mp.files.add(file); + } + if (hasFields) { + postBody = mp; + } + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create user profile image + /// + /// Upload and set a new profile image for the current user. + /// + /// Parameters: + /// + /// * [MultipartFile] file (required): + /// Profile image file + Future createProfileImage(MultipartFile file,) async { + final response = await createProfileImageWithHttpInfo(file,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'CreateProfileImageResponseDto',) as CreateProfileImageResponseDto; + + } + return null; + } + + /// Delete user profile image + /// + /// Delete the profile image of the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future deleteProfileImageWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/profile-image'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete user profile image + /// + /// Delete the profile image of the current user. + Future deleteProfileImage() async { + final response = await deleteProfileImageWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete user product key + /// + /// Delete the registered product key for the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future deleteUserLicenseWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/license'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete user product key + /// + /// Delete the registered product key for the current user. + Future deleteUserLicense() async { + final response = await deleteUserLicenseWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Delete user onboarding + /// + /// Delete the onboarding status of the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future deleteUserOnboardingWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/onboarding'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete user onboarding + /// + /// Delete the onboarding status of the current user. + Future deleteUserOnboarding() async { + final response = await deleteUserOnboardingWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Get my preferences + /// + /// Retrieve the preferences for the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future getMyPreferencesWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/preferences'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get my preferences + /// + /// Retrieve the preferences for the current user. + Future getMyPreferences() async { + final response = await getMyPreferencesWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto; + + } + return null; + } + + /// Get current user + /// + /// Retrieve information about the user making the API request. + /// + /// Note: This method returns the HTTP [Response]. + Future getMyUserWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get current user + /// + /// Retrieve information about the user making the API request. + Future getMyUser() async { + final response = await getMyUserWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } + + /// Retrieve user profile image + /// + /// Retrieve the profile image file for a user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getProfileImageWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/{id}/profile-image' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user profile image + /// + /// Retrieve the profile image file for a user. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getProfileImage(String id,) async { + final response = await getProfileImageWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile; + + } + return null; + } + + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUserWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a user + /// + /// Retrieve a specific user by their ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getUser(String id,) async { + final response = await getUserWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserResponseDto',) as UserResponseDto; + + } + return null; + } + + /// Retrieve user product key + /// + /// Retrieve information about whether the current user has a registered product key. + /// + /// Note: This method returns the HTTP [Response]. + Future getUserLicenseWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/license'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user product key + /// + /// Retrieve information about whether the current user has a registered product key. + Future getUserLicense() async { + final response = await getUserLicenseWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LicenseResponseDto',) as LicenseResponseDto; + + } + return null; + } + + /// Retrieve user onboarding + /// + /// Retrieve the onboarding status of the current user. + /// + /// Note: This method returns the HTTP [Response]. + Future getUserOnboardingWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/onboarding'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve user onboarding + /// + /// Retrieve the onboarding status of the current user. + Future getUserOnboarding() async { + final response = await getUserOnboardingWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OnboardingResponseDto',) as OnboardingResponseDto; + + } + return null; + } + + /// Get all users + /// + /// Retrieve a list of all users on the server. + /// + /// Note: This method returns the HTTP [Response]. + Future searchUsersWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/users'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Get all users + /// + /// Retrieve a list of all users on the server. + Future?> searchUsers() async { + final response = await searchUsersWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Set user product key + /// + /// Register a product key for the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [LicenseKeyDto] licenseKeyDto (required): + Future setUserLicenseWithHttpInfo(LicenseKeyDto licenseKeyDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/license'; + + // ignore: prefer_final_locals + Object? postBody = licenseKeyDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Set user product key + /// + /// Register a product key for the current user. + /// + /// Parameters: + /// + /// * [LicenseKeyDto] licenseKeyDto (required): + Future setUserLicense(LicenseKeyDto licenseKeyDto,) async { + final response = await setUserLicenseWithHttpInfo(licenseKeyDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'LicenseResponseDto',) as LicenseResponseDto; + + } + return null; + } + + /// Update user onboarding + /// + /// Update the onboarding status of the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [OnboardingDto] onboardingDto (required): + Future setUserOnboardingWithHttpInfo(OnboardingDto onboardingDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/onboarding'; + + // ignore: prefer_final_locals + Object? postBody = onboardingDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update user onboarding + /// + /// Update the onboarding status of the current user. + /// + /// Parameters: + /// + /// * [OnboardingDto] onboardingDto (required): + Future setUserOnboarding(OnboardingDto onboardingDto,) async { + final response = await setUserOnboardingWithHttpInfo(onboardingDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'OnboardingResponseDto',) as OnboardingResponseDto; + + } + return null; + } + + /// Update my preferences + /// + /// Update the preferences of the current user. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): + Future updateMyPreferencesWithHttpInfo(UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me/preferences'; + + // ignore: prefer_final_locals + Object? postBody = userPreferencesUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update my preferences + /// + /// Update the preferences of the current user. + /// + /// Parameters: + /// + /// * [UserPreferencesUpdateDto] userPreferencesUpdateDto (required): + Future updateMyPreferences(UserPreferencesUpdateDto userPreferencesUpdateDto,) async { + final response = await updateMyPreferencesWithHttpInfo(userPreferencesUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserPreferencesResponseDto',) as UserPreferencesResponseDto; + + } + return null; + } + + /// Update current user + /// + /// Update the current user making teh API request. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [UserUpdateMeDto] userUpdateMeDto (required): + Future updateMyUserWithHttpInfo(UserUpdateMeDto userUpdateMeDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/users/me'; + + // ignore: prefer_final_locals + Object? postBody = userUpdateMeDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update current user + /// + /// Update the current user making teh API request. + /// + /// Parameters: + /// + /// * [UserUpdateMeDto] userUpdateMeDto (required): + Future updateMyUser(UserUpdateMeDto userUpdateMeDto,) async { + final response = await updateMyUserWithHttpInfo(userUpdateMeDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'UserAdminResponseDto',) as UserAdminResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/views_api.dart b/mobile/openapi/lib/api/views_api.dart new file mode 100644 index 0000000000..a45e89d58f --- /dev/null +++ b/mobile/openapi/lib/api/views_api.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ViewsApi { + ViewsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Retrieve assets by original path + /// + /// Retrieve assets that are children of a specific folder. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] path (required): + Future getAssetsByOriginalPathWithHttpInfo(String path,) async { + // ignore: prefer_const_declarations + final apiPath = r'/view/folder'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + queryParams.addAll(_queryParams('', 'path', path)); + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve assets by original path + /// + /// Retrieve assets that are children of a specific folder. + /// + /// Parameters: + /// + /// * [String] path (required): + Future?> getAssetsByOriginalPath(String path,) async { + final response = await getAssetsByOriginalPathWithHttpInfo(path,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Retrieve unique paths + /// + /// Retrieve a list of unique folder paths from asset original paths. + /// + /// Note: This method returns the HTTP [Response]. + Future getUniqueOriginalPathsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/view/folder/unique-paths'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve unique paths + /// + /// Retrieve a list of unique folder paths from asset original paths. + Future?> getUniqueOriginalPaths() async { + final response = await getUniqueOriginalPathsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } +} diff --git a/mobile/openapi/lib/api/workflows_api.dart b/mobile/openapi/lib/api/workflows_api.dart new file mode 100644 index 0000000000..c589ec9823 --- /dev/null +++ b/mobile/openapi/lib/api/workflows_api.dart @@ -0,0 +1,292 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class WorkflowsApi { + WorkflowsApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient; + + final ApiClient apiClient; + + /// Create a workflow + /// + /// Create a new workflow, the workflow can also be created with empty filters and actions. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [WorkflowCreateDto] workflowCreateDto (required): + Future createWorkflowWithHttpInfo(WorkflowCreateDto workflowCreateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows'; + + // ignore: prefer_final_locals + Object? postBody = workflowCreateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Create a workflow + /// + /// Create a new workflow, the workflow can also be created with empty filters and actions. + /// + /// Parameters: + /// + /// * [WorkflowCreateDto] workflowCreateDto (required): + Future createWorkflow(WorkflowCreateDto workflowCreateDto,) async { + final response = await createWorkflowWithHttpInfo(workflowCreateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; + + } + return null; + } + + /// Delete a workflow + /// + /// Delete a workflow by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteWorkflowWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'DELETE', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Delete a workflow + /// + /// Delete a workflow by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future deleteWorkflow(String id,) async { + final response = await deleteWorkflowWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } + + /// Retrieve a workflow + /// + /// Retrieve information about a specific workflow by its ID. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflowWithHttpInfo(String id,) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Retrieve a workflow + /// + /// Retrieve information about a specific workflow by its ID. + /// + /// Parameters: + /// + /// * [String] id (required): + Future getWorkflow(String id,) async { + final response = await getWorkflowWithHttpInfo(id,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; + + } + return null; + } + + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + /// + /// Note: This method returns the HTTP [Response]. + Future getWorkflowsWithHttpInfo() async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + apiPath, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// List all workflows + /// + /// Retrieve a list of workflows available to the authenticated user. + Future?> getWorkflows() async { + final response = await getWorkflowsWithHttpInfo(); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + final responseBody = await _decodeBodyBytes(response); + return (await apiClient.deserializeAsync(responseBody, 'List') as List) + .cast() + .toList(growable: false); + + } + return null; + } + + /// Update a workflow + /// + /// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc. + /// + /// Note: This method returns the HTTP [Response]. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [WorkflowUpdateDto] workflowUpdateDto (required): + Future updateWorkflowWithHttpInfo(String id, WorkflowUpdateDto workflowUpdateDto,) async { + // ignore: prefer_const_declarations + final apiPath = r'/workflows/{id}' + .replaceAll('{id}', id); + + // ignore: prefer_final_locals + Object? postBody = workflowUpdateDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + apiPath, + 'PUT', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Update a workflow + /// + /// Update the information of a specific workflow by its ID. This endpoint can be used to update the workflow name, description, trigger type, filters and actions order, etc. + /// + /// Parameters: + /// + /// * [String] id (required): + /// + /// * [WorkflowUpdateDto] workflowUpdateDto (required): + Future updateWorkflow(String id, WorkflowUpdateDto workflowUpdateDto,) async { + final response = await updateWorkflowWithHttpInfo(id, workflowUpdateDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + // When a remote server returns no body with a status of 204, we shall not decode it. + // At the time of writing this, `dart:convert` will throw an "Unexpected end of input" + // FormatException when trying to decode an empty string. + if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) { + return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'WorkflowResponseDto',) as WorkflowResponseDto; + + } + return null; + } +} diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart new file mode 100644 index 0000000000..48e5f5874b --- /dev/null +++ b/mobile/openapi/lib/api_client.dart @@ -0,0 +1,966 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ApiClient { + ApiClient({this.basePath = '/api', this.authentication,}); + + final String basePath; + final Authentication? authentication; + + var _client = Client(); + final _defaultHeaderMap = {}; + + /// Returns the current HTTP [Client] instance to use in this class. + /// + /// The return value is guaranteed to never be null. + Client get client => _client; + + /// Requests to use a new HTTP [Client] in this class. + set client(Client newClient) { + _client = newClient; + } + + Map get defaultHeaderMap => _defaultHeaderMap; + + void addDefaultHeader(String key, String value) { + _defaultHeaderMap[key] = value; + } + + // We don't use a Map for queryParams. + // If collectionFormat is 'multi', a key might appear multiple times. + Future invokeAPI( + String path, + String method, + List queryParams, + Object? body, + Map headerParams, + Map formParams, + String? contentType, + ) async { + await authentication?.applyToParams(queryParams, headerParams); + + headerParams.addAll(_defaultHeaderMap); + if (contentType != null) { + headerParams['Content-Type'] = contentType; + } + + final urlEncodedQueryParams = queryParams.map((param) => '$param'); + final queryString = urlEncodedQueryParams.isNotEmpty ? '?${urlEncodedQueryParams.join('&')}' : ''; + final uri = Uri.parse('$basePath$path$queryString'); + + try { + // Special case for uploading a single file which isn't a 'multipart/form-data'. + if ( + body is MultipartFile && (contentType == null || + !contentType.toLowerCase().startsWith('multipart/form-data')) + ) { + final request = StreamedRequest(method, uri); + request.headers.addAll(headerParams); + request.contentLength = body.length; + body.finalize().listen( + request.sink.add, + onDone: request.sink.close, + // ignore: avoid_types_on_closure_parameters + onError: (Object error, StackTrace trace) => request.sink.close(), + cancelOnError: true, + ); + final response = await _client.send(request); + return Response.fromStream(response); + } + + if (body is MultipartRequest) { + final request = MultipartRequest(method, uri); + request.fields.addAll(body.fields); + request.files.addAll(body.files); + request.headers.addAll(body.headers); + request.headers.addAll(headerParams); + final response = await _client.send(request); + return Response.fromStream(response); + } + + final msgBody = contentType == 'application/x-www-form-urlencoded' + ? formParams + : await serializeAsync(body); + final nullableHeaderParams = headerParams.isEmpty ? null : headerParams; + + switch(method) { + case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,); + case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,); + case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams, body: msgBody,); + case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,); + case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,); + case 'GET': return await _client.get(uri, headers: nullableHeaderParams,); + } + } on SocketException catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, + 'Socket operation failed: $method $path', + error, + trace, + ); + } on TlsException catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, + 'TLS/SSL communication failed: $method $path', + error, + trace, + ); + } on IOException catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, + 'I/O operation failed: $method $path', + error, + trace, + ); + } on ClientException catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, + 'HTTP connection failed: $method $path', + error, + trace, + ); + } on Exception catch (error, trace) { + throw ApiException.withInner( + HttpStatus.badRequest, + 'Exception occurred: $method $path', + error, + trace, + ); + } + + throw ApiException( + HttpStatus.badRequest, + 'Invalid HTTP operation: $method $path', + ); + } + + Future deserializeAsync(String value, String targetType, {bool growable = false,}) => + // ignore: deprecated_member_use_from_same_package + deserialize(value, targetType, growable: growable); + + @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.') + Future deserialize(String value, String targetType, {bool growable = false,}) async { + // Remove all spaces. Necessary for regular expressions as well. + targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments + + // If the expected target type is String, nothing to do... + return targetType == 'String' + ? value + : fromJson(await compute((String j) => json.decode(j), value), targetType, growable: growable); + } + + // ignore: deprecated_member_use_from_same_package + Future serializeAsync(Object? value) async => serialize(value); + + @Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use serializeAsync() instead.') + String serialize(Object? value) => value == null ? '' : json.encode(value); + + /// Returns a native instance of an OpenAPI class matching the [specified type][targetType]. + static dynamic fromJson(dynamic value, String targetType, {bool growable = false,}) { + try { + switch (targetType) { + case 'String': + return value is String ? value : value.toString(); + case 'int': + return value is int ? value : int.parse('$value'); + case 'double': + return value is double ? value : double.parse('$value'); + case 'bool': + if (value is bool) { + return value; + } + final valueString = '$value'.toLowerCase(); + return valueString == 'true' || valueString == '1'; + case 'DateTime': + return value is DateTime ? value : DateTime.tryParse(value); + case 'APIKeyCreateDto': + return APIKeyCreateDto.fromJson(value); + case 'APIKeyCreateResponseDto': + return APIKeyCreateResponseDto.fromJson(value); + case 'APIKeyResponseDto': + return APIKeyResponseDto.fromJson(value); + case 'APIKeyUpdateDto': + return APIKeyUpdateDto.fromJson(value); + case 'ActivityCreateDto': + return ActivityCreateDto.fromJson(value); + case 'ActivityResponseDto': + return ActivityResponseDto.fromJson(value); + case 'ActivityStatisticsResponseDto': + return ActivityStatisticsResponseDto.fromJson(value); + case 'AddUsersDto': + return AddUsersDto.fromJson(value); + case 'AdminOnboardingUpdateDto': + return AdminOnboardingUpdateDto.fromJson(value); + case 'AlbumResponseDto': + return AlbumResponseDto.fromJson(value); + case 'AlbumStatisticsResponseDto': + return AlbumStatisticsResponseDto.fromJson(value); + case 'AlbumUserAddDto': + return AlbumUserAddDto.fromJson(value); + case 'AlbumUserCreateDto': + return AlbumUserCreateDto.fromJson(value); + case 'AlbumUserResponseDto': + return AlbumUserResponseDto.fromJson(value); + case 'AlbumUserRole': + return AlbumUserRoleTypeTransformer().decode(value); + case 'AlbumsAddAssetsDto': + return AlbumsAddAssetsDto.fromJson(value); + case 'AlbumsAddAssetsResponseDto': + return AlbumsAddAssetsResponseDto.fromJson(value); + case 'AlbumsResponse': + return AlbumsResponse.fromJson(value); + case 'AlbumsUpdate': + return AlbumsUpdate.fromJson(value); + case 'AssetBulkDeleteDto': + return AssetBulkDeleteDto.fromJson(value); + case 'AssetBulkUpdateDto': + return AssetBulkUpdateDto.fromJson(value); + case 'AssetBulkUploadCheckDto': + return AssetBulkUploadCheckDto.fromJson(value); + case 'AssetBulkUploadCheckItem': + return AssetBulkUploadCheckItem.fromJson(value); + case 'AssetBulkUploadCheckResponseDto': + return AssetBulkUploadCheckResponseDto.fromJson(value); + case 'AssetBulkUploadCheckResult': + return AssetBulkUploadCheckResult.fromJson(value); + case 'AssetCopyDto': + return AssetCopyDto.fromJson(value); + case 'AssetDeltaSyncDto': + return AssetDeltaSyncDto.fromJson(value); + case 'AssetDeltaSyncResponseDto': + return AssetDeltaSyncResponseDto.fromJson(value); + case 'AssetEditAction': + return AssetEditActionTypeTransformer().decode(value); + case 'AssetEditActionItemDto': + return AssetEditActionItemDto.fromJson(value); + case 'AssetEditActionItemDtoParameters': + return AssetEditActionItemDtoParameters.fromJson(value); + case 'AssetEditActionItemResponseDto': + return AssetEditActionItemResponseDto.fromJson(value); + case 'AssetEditsCreateDto': + return AssetEditsCreateDto.fromJson(value); + case 'AssetEditsResponseDto': + return AssetEditsResponseDto.fromJson(value); + case 'AssetFaceCreateDto': + return AssetFaceCreateDto.fromJson(value); + case 'AssetFaceDeleteDto': + return AssetFaceDeleteDto.fromJson(value); + case 'AssetFaceResponseDto': + return AssetFaceResponseDto.fromJson(value); + case 'AssetFaceUpdateDto': + return AssetFaceUpdateDto.fromJson(value); + case 'AssetFaceUpdateItem': + return AssetFaceUpdateItem.fromJson(value); + case 'AssetFaceWithoutPersonResponseDto': + return AssetFaceWithoutPersonResponseDto.fromJson(value); + case 'AssetFullSyncDto': + return AssetFullSyncDto.fromJson(value); + case 'AssetIdsDto': + return AssetIdsDto.fromJson(value); + case 'AssetIdsResponseDto': + return AssetIdsResponseDto.fromJson(value); + case 'AssetJobName': + return AssetJobNameTypeTransformer().decode(value); + case 'AssetJobsDto': + return AssetJobsDto.fromJson(value); + case 'AssetMediaResponseDto': + return AssetMediaResponseDto.fromJson(value); + case 'AssetMediaSize': + return AssetMediaSizeTypeTransformer().decode(value); + case 'AssetMediaStatus': + return AssetMediaStatusTypeTransformer().decode(value); + case 'AssetMetadataBulkDeleteDto': + return AssetMetadataBulkDeleteDto.fromJson(value); + case 'AssetMetadataBulkDeleteItemDto': + return AssetMetadataBulkDeleteItemDto.fromJson(value); + case 'AssetMetadataBulkResponseDto': + return AssetMetadataBulkResponseDto.fromJson(value); + case 'AssetMetadataBulkUpsertDto': + return AssetMetadataBulkUpsertDto.fromJson(value); + case 'AssetMetadataBulkUpsertItemDto': + return AssetMetadataBulkUpsertItemDto.fromJson(value); + case 'AssetMetadataResponseDto': + return AssetMetadataResponseDto.fromJson(value); + case 'AssetMetadataUpsertDto': + return AssetMetadataUpsertDto.fromJson(value); + case 'AssetMetadataUpsertItemDto': + return AssetMetadataUpsertItemDto.fromJson(value); + case 'AssetOcrResponseDto': + return AssetOcrResponseDto.fromJson(value); + case 'AssetOrder': + return AssetOrderTypeTransformer().decode(value); + case 'AssetResponseDto': + return AssetResponseDto.fromJson(value); + case 'AssetStackResponseDto': + return AssetStackResponseDto.fromJson(value); + case 'AssetStatsResponseDto': + return AssetStatsResponseDto.fromJson(value); + case 'AssetTypeEnum': + return AssetTypeEnumTypeTransformer().decode(value); + case 'AssetVisibility': + return AssetVisibilityTypeTransformer().decode(value); + case 'AudioCodec': + return AudioCodecTypeTransformer().decode(value); + case 'AuthStatusResponseDto': + return AuthStatusResponseDto.fromJson(value); + case 'AvatarUpdate': + return AvatarUpdate.fromJson(value); + case 'BulkIdErrorReason': + return BulkIdErrorReasonTypeTransformer().decode(value); + case 'BulkIdResponseDto': + return BulkIdResponseDto.fromJson(value); + case 'BulkIdsDto': + return BulkIdsDto.fromJson(value); + case 'CLIPConfig': + return CLIPConfig.fromJson(value); + case 'CQMode': + return CQModeTypeTransformer().decode(value); + case 'CastResponse': + return CastResponse.fromJson(value); + case 'CastUpdate': + return CastUpdate.fromJson(value); + case 'ChangePasswordDto': + return ChangePasswordDto.fromJson(value); + case 'CheckExistingAssetsDto': + return CheckExistingAssetsDto.fromJson(value); + case 'CheckExistingAssetsResponseDto': + return CheckExistingAssetsResponseDto.fromJson(value); + case 'Colorspace': + return ColorspaceTypeTransformer().decode(value); + case 'ContributorCountResponseDto': + return ContributorCountResponseDto.fromJson(value); + case 'CreateAlbumDto': + return CreateAlbumDto.fromJson(value); + case 'CreateLibraryDto': + return CreateLibraryDto.fromJson(value); + case 'CreateProfileImageResponseDto': + return CreateProfileImageResponseDto.fromJson(value); + case 'CropParameters': + return CropParameters.fromJson(value); + case 'DatabaseBackupConfig': + return DatabaseBackupConfig.fromJson(value); + case 'DatabaseBackupDeleteDto': + return DatabaseBackupDeleteDto.fromJson(value); + case 'DatabaseBackupDto': + return DatabaseBackupDto.fromJson(value); + case 'DatabaseBackupListResponseDto': + return DatabaseBackupListResponseDto.fromJson(value); + case 'DownloadArchiveDto': + return DownloadArchiveDto.fromJson(value); + case 'DownloadArchiveInfo': + return DownloadArchiveInfo.fromJson(value); + case 'DownloadInfoDto': + return DownloadInfoDto.fromJson(value); + case 'DownloadResponse': + return DownloadResponse.fromJson(value); + case 'DownloadResponseDto': + return DownloadResponseDto.fromJson(value); + case 'DownloadUpdate': + return DownloadUpdate.fromJson(value); + case 'DuplicateDetectionConfig': + return DuplicateDetectionConfig.fromJson(value); + case 'DuplicateResolveDto': + return DuplicateResolveDto.fromJson(value); + case 'DuplicateResolveGroupDto': + return DuplicateResolveGroupDto.fromJson(value); + case 'DuplicateResponseDto': + return DuplicateResponseDto.fromJson(value); + case 'EmailNotificationsResponse': + return EmailNotificationsResponse.fromJson(value); + case 'EmailNotificationsUpdate': + return EmailNotificationsUpdate.fromJson(value); + case 'ExifResponseDto': + return ExifResponseDto.fromJson(value); + case 'FaceDto': + return FaceDto.fromJson(value); + case 'FacialRecognitionConfig': + return FacialRecognitionConfig.fromJson(value); + case 'FoldersResponse': + return FoldersResponse.fromJson(value); + case 'FoldersUpdate': + return FoldersUpdate.fromJson(value); + case 'ImageFormat': + return ImageFormatTypeTransformer().decode(value); + case 'JobCreateDto': + return JobCreateDto.fromJson(value); + case 'JobName': + return JobNameTypeTransformer().decode(value); + case 'JobSettingsDto': + return JobSettingsDto.fromJson(value); + case 'LibraryResponseDto': + return LibraryResponseDto.fromJson(value); + case 'LibraryStatsResponseDto': + return LibraryStatsResponseDto.fromJson(value); + case 'LicenseKeyDto': + return LicenseKeyDto.fromJson(value); + case 'LicenseResponseDto': + return LicenseResponseDto.fromJson(value); + case 'LogLevel': + return LogLevelTypeTransformer().decode(value); + case 'LoginCredentialDto': + return LoginCredentialDto.fromJson(value); + case 'LoginResponseDto': + return LoginResponseDto.fromJson(value); + case 'LogoutResponseDto': + return LogoutResponseDto.fromJson(value); + case 'MachineLearningAvailabilityChecksDto': + return MachineLearningAvailabilityChecksDto.fromJson(value); + case 'MaintenanceAction': + return MaintenanceActionTypeTransformer().decode(value); + case 'MaintenanceAuthDto': + return MaintenanceAuthDto.fromJson(value); + case 'MaintenanceDetectInstallResponseDto': + return MaintenanceDetectInstallResponseDto.fromJson(value); + case 'MaintenanceDetectInstallStorageFolderDto': + return MaintenanceDetectInstallStorageFolderDto.fromJson(value); + case 'MaintenanceLoginDto': + return MaintenanceLoginDto.fromJson(value); + case 'MaintenanceStatusResponseDto': + return MaintenanceStatusResponseDto.fromJson(value); + case 'ManualJobName': + return ManualJobNameTypeTransformer().decode(value); + case 'MapMarkerResponseDto': + return MapMarkerResponseDto.fromJson(value); + case 'MapReverseGeocodeResponseDto': + return MapReverseGeocodeResponseDto.fromJson(value); + case 'MemoriesResponse': + return MemoriesResponse.fromJson(value); + case 'MemoriesUpdate': + return MemoriesUpdate.fromJson(value); + case 'MemoryCreateDto': + return MemoryCreateDto.fromJson(value); + case 'MemoryResponseDto': + return MemoryResponseDto.fromJson(value); + case 'MemorySearchOrder': + return MemorySearchOrderTypeTransformer().decode(value); + case 'MemoryStatisticsResponseDto': + return MemoryStatisticsResponseDto.fromJson(value); + case 'MemoryType': + return MemoryTypeTypeTransformer().decode(value); + case 'MemoryUpdateDto': + return MemoryUpdateDto.fromJson(value); + case 'MergePersonDto': + return MergePersonDto.fromJson(value); + case 'MetadataSearchDto': + return MetadataSearchDto.fromJson(value); + case 'MirrorAxis': + return MirrorAxisTypeTransformer().decode(value); + case 'MirrorParameters': + return MirrorParameters.fromJson(value); + case 'NotificationCreateDto': + return NotificationCreateDto.fromJson(value); + case 'NotificationDeleteAllDto': + return NotificationDeleteAllDto.fromJson(value); + case 'NotificationDto': + return NotificationDto.fromJson(value); + case 'NotificationLevel': + return NotificationLevelTypeTransformer().decode(value); + case 'NotificationType': + return NotificationTypeTypeTransformer().decode(value); + case 'NotificationUpdateAllDto': + return NotificationUpdateAllDto.fromJson(value); + case 'NotificationUpdateDto': + return NotificationUpdateDto.fromJson(value); + case 'OAuthAuthorizeResponseDto': + return OAuthAuthorizeResponseDto.fromJson(value); + case 'OAuthCallbackDto': + return OAuthCallbackDto.fromJson(value); + case 'OAuthConfigDto': + return OAuthConfigDto.fromJson(value); + case 'OAuthTokenEndpointAuthMethod': + return OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); + case 'OcrConfig': + return OcrConfig.fromJson(value); + case 'OnThisDayDto': + return OnThisDayDto.fromJson(value); + case 'OnboardingDto': + return OnboardingDto.fromJson(value); + case 'OnboardingResponseDto': + return OnboardingResponseDto.fromJson(value); + case 'PartnerCreateDto': + return PartnerCreateDto.fromJson(value); + case 'PartnerDirection': + return PartnerDirectionTypeTransformer().decode(value); + case 'PartnerResponseDto': + return PartnerResponseDto.fromJson(value); + case 'PartnerUpdateDto': + return PartnerUpdateDto.fromJson(value); + case 'PeopleResponse': + return PeopleResponse.fromJson(value); + case 'PeopleResponseDto': + return PeopleResponseDto.fromJson(value); + case 'PeopleUpdate': + return PeopleUpdate.fromJson(value); + case 'PeopleUpdateDto': + return PeopleUpdateDto.fromJson(value); + case 'PeopleUpdateItem': + return PeopleUpdateItem.fromJson(value); + case 'Permission': + return PermissionTypeTransformer().decode(value); + case 'PersonCreateDto': + return PersonCreateDto.fromJson(value); + case 'PersonResponseDto': + return PersonResponseDto.fromJson(value); + case 'PersonStatisticsResponseDto': + return PersonStatisticsResponseDto.fromJson(value); + case 'PersonUpdateDto': + return PersonUpdateDto.fromJson(value); + case 'PersonWithFacesResponseDto': + return PersonWithFacesResponseDto.fromJson(value); + case 'PinCodeChangeDto': + return PinCodeChangeDto.fromJson(value); + case 'PinCodeResetDto': + return PinCodeResetDto.fromJson(value); + case 'PinCodeSetupDto': + return PinCodeSetupDto.fromJson(value); + case 'PlacesResponseDto': + return PlacesResponseDto.fromJson(value); + case 'PluginActionResponseDto': + return PluginActionResponseDto.fromJson(value); + case 'PluginContextType': + return PluginContextTypeTypeTransformer().decode(value); + case 'PluginFilterResponseDto': + return PluginFilterResponseDto.fromJson(value); + case 'PluginResponseDto': + return PluginResponseDto.fromJson(value); + case 'PluginTriggerResponseDto': + return PluginTriggerResponseDto.fromJson(value); + case 'PluginTriggerType': + return PluginTriggerTypeTypeTransformer().decode(value); + case 'PurchaseResponse': + return PurchaseResponse.fromJson(value); + case 'PurchaseUpdate': + return PurchaseUpdate.fromJson(value); + case 'QueueCommand': + return QueueCommandTypeTransformer().decode(value); + case 'QueueCommandDto': + return QueueCommandDto.fromJson(value); + case 'QueueDeleteDto': + return QueueDeleteDto.fromJson(value); + case 'QueueJobResponseDto': + return QueueJobResponseDto.fromJson(value); + case 'QueueJobStatus': + return QueueJobStatusTypeTransformer().decode(value); + case 'QueueName': + return QueueNameTypeTransformer().decode(value); + case 'QueueResponseDto': + return QueueResponseDto.fromJson(value); + case 'QueueResponseLegacyDto': + return QueueResponseLegacyDto.fromJson(value); + case 'QueueStatisticsDto': + return QueueStatisticsDto.fromJson(value); + case 'QueueStatusLegacyDto': + return QueueStatusLegacyDto.fromJson(value); + case 'QueueUpdateDto': + return QueueUpdateDto.fromJson(value); + case 'QueuesResponseLegacyDto': + return QueuesResponseLegacyDto.fromJson(value); + case 'RandomSearchDto': + return RandomSearchDto.fromJson(value); + case 'RatingsResponse': + return RatingsResponse.fromJson(value); + case 'RatingsUpdate': + return RatingsUpdate.fromJson(value); + case 'ReactionLevel': + return ReactionLevelTypeTransformer().decode(value); + case 'ReactionType': + return ReactionTypeTypeTransformer().decode(value); + case 'ReverseGeocodingStateResponseDto': + return ReverseGeocodingStateResponseDto.fromJson(value); + case 'RotateParameters': + return RotateParameters.fromJson(value); + case 'SearchAlbumResponseDto': + return SearchAlbumResponseDto.fromJson(value); + case 'SearchAssetResponseDto': + return SearchAssetResponseDto.fromJson(value); + case 'SearchExploreItem': + return SearchExploreItem.fromJson(value); + case 'SearchExploreResponseDto': + return SearchExploreResponseDto.fromJson(value); + case 'SearchFacetCountResponseDto': + return SearchFacetCountResponseDto.fromJson(value); + case 'SearchFacetResponseDto': + return SearchFacetResponseDto.fromJson(value); + case 'SearchResponseDto': + return SearchResponseDto.fromJson(value); + case 'SearchStatisticsResponseDto': + return SearchStatisticsResponseDto.fromJson(value); + case 'SearchSuggestionType': + return SearchSuggestionTypeTypeTransformer().decode(value); + case 'ServerAboutResponseDto': + return ServerAboutResponseDto.fromJson(value); + case 'ServerApkLinksDto': + return ServerApkLinksDto.fromJson(value); + case 'ServerConfigDto': + return ServerConfigDto.fromJson(value); + case 'ServerFeaturesDto': + return ServerFeaturesDto.fromJson(value); + case 'ServerMediaTypesResponseDto': + return ServerMediaTypesResponseDto.fromJson(value); + case 'ServerPingResponse': + return ServerPingResponse.fromJson(value); + case 'ServerStatsResponseDto': + return ServerStatsResponseDto.fromJson(value); + case 'ServerStorageResponseDto': + return ServerStorageResponseDto.fromJson(value); + case 'ServerThemeDto': + return ServerThemeDto.fromJson(value); + case 'ServerVersionHistoryResponseDto': + return ServerVersionHistoryResponseDto.fromJson(value); + case 'ServerVersionResponseDto': + return ServerVersionResponseDto.fromJson(value); + case 'SessionCreateDto': + return SessionCreateDto.fromJson(value); + case 'SessionCreateResponseDto': + return SessionCreateResponseDto.fromJson(value); + case 'SessionResponseDto': + return SessionResponseDto.fromJson(value); + case 'SessionUnlockDto': + return SessionUnlockDto.fromJson(value); + case 'SessionUpdateDto': + return SessionUpdateDto.fromJson(value); + case 'SetMaintenanceModeDto': + return SetMaintenanceModeDto.fromJson(value); + case 'SharedLinkCreateDto': + return SharedLinkCreateDto.fromJson(value); + case 'SharedLinkEditDto': + return SharedLinkEditDto.fromJson(value); + case 'SharedLinkLoginDto': + return SharedLinkLoginDto.fromJson(value); + case 'SharedLinkResponseDto': + return SharedLinkResponseDto.fromJson(value); + case 'SharedLinkType': + return SharedLinkTypeTypeTransformer().decode(value); + case 'SharedLinksResponse': + return SharedLinksResponse.fromJson(value); + case 'SharedLinksUpdate': + return SharedLinksUpdate.fromJson(value); + case 'SignUpDto': + return SignUpDto.fromJson(value); + case 'SmartSearchDto': + return SmartSearchDto.fromJson(value); + case 'SourceType': + return SourceTypeTypeTransformer().decode(value); + case 'StackCreateDto': + return StackCreateDto.fromJson(value); + case 'StackResponseDto': + return StackResponseDto.fromJson(value); + case 'StackUpdateDto': + return StackUpdateDto.fromJson(value); + case 'StatisticsSearchDto': + return StatisticsSearchDto.fromJson(value); + case 'StorageFolder': + return StorageFolderTypeTransformer().decode(value); + case 'SyncAckDeleteDto': + return SyncAckDeleteDto.fromJson(value); + case 'SyncAckDto': + return SyncAckDto.fromJson(value); + case 'SyncAckSetDto': + return SyncAckSetDto.fromJson(value); + case 'SyncAlbumDeleteV1': + return SyncAlbumDeleteV1.fromJson(value); + case 'SyncAlbumToAssetDeleteV1': + return SyncAlbumToAssetDeleteV1.fromJson(value); + case 'SyncAlbumToAssetV1': + return SyncAlbumToAssetV1.fromJson(value); + case 'SyncAlbumUserDeleteV1': + return SyncAlbumUserDeleteV1.fromJson(value); + case 'SyncAlbumUserV1': + return SyncAlbumUserV1.fromJson(value); + case 'SyncAlbumV1': + return SyncAlbumV1.fromJson(value); + case 'SyncAssetDeleteV1': + return SyncAssetDeleteV1.fromJson(value); + case 'SyncAssetEditDeleteV1': + return SyncAssetEditDeleteV1.fromJson(value); + case 'SyncAssetEditV1': + return SyncAssetEditV1.fromJson(value); + case 'SyncAssetExifV1': + return SyncAssetExifV1.fromJson(value); + case 'SyncAssetFaceDeleteV1': + return SyncAssetFaceDeleteV1.fromJson(value); + case 'SyncAssetFaceV1': + return SyncAssetFaceV1.fromJson(value); + case 'SyncAssetFaceV2': + return SyncAssetFaceV2.fromJson(value); + case 'SyncAssetMetadataDeleteV1': + return SyncAssetMetadataDeleteV1.fromJson(value); + case 'SyncAssetMetadataV1': + return SyncAssetMetadataV1.fromJson(value); + case 'SyncAssetV1': + return SyncAssetV1.fromJson(value); + case 'SyncAuthUserV1': + return SyncAuthUserV1.fromJson(value); + case 'SyncEntityType': + return SyncEntityTypeTypeTransformer().decode(value); + case 'SyncMemoryAssetDeleteV1': + return SyncMemoryAssetDeleteV1.fromJson(value); + case 'SyncMemoryAssetV1': + return SyncMemoryAssetV1.fromJson(value); + case 'SyncMemoryDeleteV1': + return SyncMemoryDeleteV1.fromJson(value); + case 'SyncMemoryV1': + return SyncMemoryV1.fromJson(value); + case 'SyncPartnerDeleteV1': + return SyncPartnerDeleteV1.fromJson(value); + case 'SyncPartnerV1': + return SyncPartnerV1.fromJson(value); + case 'SyncPersonDeleteV1': + return SyncPersonDeleteV1.fromJson(value); + case 'SyncPersonV1': + return SyncPersonV1.fromJson(value); + case 'SyncRequestType': + return SyncRequestTypeTypeTransformer().decode(value); + case 'SyncStackDeleteV1': + return SyncStackDeleteV1.fromJson(value); + case 'SyncStackV1': + return SyncStackV1.fromJson(value); + case 'SyncStreamDto': + return SyncStreamDto.fromJson(value); + case 'SyncUserDeleteV1': + return SyncUserDeleteV1.fromJson(value); + case 'SyncUserMetadataDeleteV1': + return SyncUserMetadataDeleteV1.fromJson(value); + case 'SyncUserMetadataV1': + return SyncUserMetadataV1.fromJson(value); + case 'SyncUserV1': + return SyncUserV1.fromJson(value); + case 'SystemConfigBackupsDto': + return SystemConfigBackupsDto.fromJson(value); + case 'SystemConfigDto': + return SystemConfigDto.fromJson(value); + case 'SystemConfigFFmpegDto': + return SystemConfigFFmpegDto.fromJson(value); + case 'SystemConfigFacesDto': + return SystemConfigFacesDto.fromJson(value); + case 'SystemConfigGeneratedFullsizeImageDto': + return SystemConfigGeneratedFullsizeImageDto.fromJson(value); + case 'SystemConfigGeneratedImageDto': + return SystemConfigGeneratedImageDto.fromJson(value); + case 'SystemConfigImageDto': + return SystemConfigImageDto.fromJson(value); + case 'SystemConfigJobDto': + return SystemConfigJobDto.fromJson(value); + case 'SystemConfigLibraryDto': + return SystemConfigLibraryDto.fromJson(value); + case 'SystemConfigLibraryScanDto': + return SystemConfigLibraryScanDto.fromJson(value); + case 'SystemConfigLibraryWatchDto': + return SystemConfigLibraryWatchDto.fromJson(value); + case 'SystemConfigLoggingDto': + return SystemConfigLoggingDto.fromJson(value); + case 'SystemConfigMachineLearningDto': + return SystemConfigMachineLearningDto.fromJson(value); + case 'SystemConfigMapDto': + return SystemConfigMapDto.fromJson(value); + case 'SystemConfigMetadataDto': + return SystemConfigMetadataDto.fromJson(value); + case 'SystemConfigNewVersionCheckDto': + return SystemConfigNewVersionCheckDto.fromJson(value); + case 'SystemConfigNightlyTasksDto': + return SystemConfigNightlyTasksDto.fromJson(value); + case 'SystemConfigNotificationsDto': + return SystemConfigNotificationsDto.fromJson(value); + case 'SystemConfigOAuthDto': + return SystemConfigOAuthDto.fromJson(value); + case 'SystemConfigPasswordLoginDto': + return SystemConfigPasswordLoginDto.fromJson(value); + case 'SystemConfigReverseGeocodingDto': + return SystemConfigReverseGeocodingDto.fromJson(value); + case 'SystemConfigServerDto': + return SystemConfigServerDto.fromJson(value); + case 'SystemConfigSmtpDto': + return SystemConfigSmtpDto.fromJson(value); + case 'SystemConfigSmtpTransportDto': + return SystemConfigSmtpTransportDto.fromJson(value); + case 'SystemConfigStorageTemplateDto': + return SystemConfigStorageTemplateDto.fromJson(value); + case 'SystemConfigTemplateEmailsDto': + return SystemConfigTemplateEmailsDto.fromJson(value); + case 'SystemConfigTemplateStorageOptionDto': + return SystemConfigTemplateStorageOptionDto.fromJson(value); + case 'SystemConfigTemplatesDto': + return SystemConfigTemplatesDto.fromJson(value); + case 'SystemConfigThemeDto': + return SystemConfigThemeDto.fromJson(value); + case 'SystemConfigTrashDto': + return SystemConfigTrashDto.fromJson(value); + case 'SystemConfigUserDto': + return SystemConfigUserDto.fromJson(value); + case 'TagBulkAssetsDto': + return TagBulkAssetsDto.fromJson(value); + case 'TagBulkAssetsResponseDto': + return TagBulkAssetsResponseDto.fromJson(value); + case 'TagCreateDto': + return TagCreateDto.fromJson(value); + case 'TagResponseDto': + return TagResponseDto.fromJson(value); + case 'TagUpdateDto': + return TagUpdateDto.fromJson(value); + case 'TagUpsertDto': + return TagUpsertDto.fromJson(value); + case 'TagsResponse': + return TagsResponse.fromJson(value); + case 'TagsUpdate': + return TagsUpdate.fromJson(value); + case 'TemplateDto': + return TemplateDto.fromJson(value); + case 'TemplateResponseDto': + return TemplateResponseDto.fromJson(value); + case 'TestEmailResponseDto': + return TestEmailResponseDto.fromJson(value); + case 'TimeBucketAssetResponseDto': + return TimeBucketAssetResponseDto.fromJson(value); + case 'TimeBucketsResponseDto': + return TimeBucketsResponseDto.fromJson(value); + case 'ToneMapping': + return ToneMappingTypeTransformer().decode(value); + case 'TranscodeHWAccel': + return TranscodeHWAccelTypeTransformer().decode(value); + case 'TranscodePolicy': + return TranscodePolicyTypeTransformer().decode(value); + case 'TrashResponseDto': + return TrashResponseDto.fromJson(value); + case 'UpdateAlbumDto': + return UpdateAlbumDto.fromJson(value); + case 'UpdateAlbumUserDto': + return UpdateAlbumUserDto.fromJson(value); + case 'UpdateAssetDto': + return UpdateAssetDto.fromJson(value); + case 'UpdateLibraryDto': + return UpdateLibraryDto.fromJson(value); + case 'UsageByUserDto': + return UsageByUserDto.fromJson(value); + case 'UserAdminCreateDto': + return UserAdminCreateDto.fromJson(value); + case 'UserAdminDeleteDto': + return UserAdminDeleteDto.fromJson(value); + case 'UserAdminResponseDto': + return UserAdminResponseDto.fromJson(value); + case 'UserAdminUpdateDto': + return UserAdminUpdateDto.fromJson(value); + case 'UserAvatarColor': + return UserAvatarColorTypeTransformer().decode(value); + case 'UserLicense': + return UserLicense.fromJson(value); + case 'UserMetadataKey': + return UserMetadataKeyTypeTransformer().decode(value); + case 'UserPreferencesResponseDto': + return UserPreferencesResponseDto.fromJson(value); + case 'UserPreferencesUpdateDto': + return UserPreferencesUpdateDto.fromJson(value); + case 'UserResponseDto': + return UserResponseDto.fromJson(value); + case 'UserStatus': + return UserStatusTypeTransformer().decode(value); + case 'UserUpdateMeDto': + return UserUpdateMeDto.fromJson(value); + case 'ValidateAccessTokenResponseDto': + return ValidateAccessTokenResponseDto.fromJson(value); + case 'ValidateLibraryDto': + return ValidateLibraryDto.fromJson(value); + case 'ValidateLibraryImportPathResponseDto': + return ValidateLibraryImportPathResponseDto.fromJson(value); + case 'ValidateLibraryResponseDto': + return ValidateLibraryResponseDto.fromJson(value); + case 'VersionCheckStateResponseDto': + return VersionCheckStateResponseDto.fromJson(value); + case 'VideoCodec': + return VideoCodecTypeTransformer().decode(value); + case 'VideoContainer': + return VideoContainerTypeTransformer().decode(value); + case 'WorkflowActionItemDto': + return WorkflowActionItemDto.fromJson(value); + case 'WorkflowActionResponseDto': + return WorkflowActionResponseDto.fromJson(value); + case 'WorkflowCreateDto': + return WorkflowCreateDto.fromJson(value); + case 'WorkflowFilterItemDto': + return WorkflowFilterItemDto.fromJson(value); + case 'WorkflowFilterResponseDto': + return WorkflowFilterResponseDto.fromJson(value); + case 'WorkflowResponseDto': + return WorkflowResponseDto.fromJson(value); + case 'WorkflowUpdateDto': + return WorkflowUpdateDto.fromJson(value); + default: + dynamic match; + if (value is List && (match = _regList.firstMatch(targetType)?.group(1)) != null) { + return value + .map((dynamic v) => fromJson(v, match, growable: growable,)) + .toList(growable: growable); + } + if (value is Set && (match = _regSet.firstMatch(targetType)?.group(1)) != null) { + return value + .map((dynamic v) => fromJson(v, match, growable: growable,)) + .toSet(); + } + if (value is Map && (match = _regMap.firstMatch(targetType)?.group(1)) != null) { + return Map.fromIterables( + value.keys.cast(), + value.values.map((dynamic v) => fromJson(v, match, growable: growable,)), + ); + } + } + } on Exception catch (error, trace) { + throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', error, trace,); + } + throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization',); + } +} + +/// Primarily intended for use in an isolate. +class DeserializationMessage { + const DeserializationMessage({ + required this.json, + required this.targetType, + this.growable = false, + }); + + /// The JSON value to deserialize. + final String json; + + /// Target type to deserialize to. + final String targetType; + + /// Whether to make deserialized lists or maps growable. + final bool growable; +} + +/// Primarily intended for use in an isolate. +Future decodeAsync(DeserializationMessage message) async { + // Remove all spaces. Necessary for regular expressions as well. + final targetType = message.targetType.replaceAll(' ', ''); + + // If the expected target type is String, nothing to do... + return targetType == 'String' + ? message.json + : json.decode(message.json); +} + +/// Primarily intended for use in an isolate. +Future deserializeAsync(DeserializationMessage message) async { + // Remove all spaces. Necessary for regular expressions as well. + final targetType = message.targetType.replaceAll(' ', ''); + + // If the expected target type is String, nothing to do... + return targetType == 'String' + ? message.json + : ApiClient.fromJson( + json.decode(message.json), + targetType, + growable: message.growable, + ); +} + +/// Primarily intended for use in an isolate. +Future serializeAsync(Object? value) async => value == null ? '' : json.encode(value); diff --git a/mobile/openapi/lib/api_exception.dart b/mobile/openapi/lib/api_exception.dart new file mode 100644 index 0000000000..53077d686d --- /dev/null +++ b/mobile/openapi/lib/api_exception.dart @@ -0,0 +1,33 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ApiException implements Exception { + ApiException(this.code, this.message); + + ApiException.withInner(this.code, this.message, this.innerException, this.stackTrace); + + int code = 0; + String? message; + Exception? innerException; + StackTrace? stackTrace; + + @override + String toString() { + if (message == null) { + return 'ApiException'; + } + if (innerException == null) { + return 'ApiException $code: $message'; + } + return 'ApiException $code: $message (Inner exception: $innerException)\n\n$stackTrace'; + } +} diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart new file mode 100644 index 0000000000..830325a5b6 --- /dev/null +++ b/mobile/openapi/lib/api_helper.dart @@ -0,0 +1,239 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueryParam { + const QueryParam(this.name, this.value); + + final String name; + final String value; + + @override + String toString() => '${Uri.encodeQueryComponent(name)}=${Uri.encodeQueryComponent(value)}'; +} + +// Ported from the Java version. +Iterable _queryParams(String collectionFormat, String name, dynamic value,) { + // Assertions to run in debug mode only. + assert(name.isNotEmpty, 'Parameter cannot be an empty string.'); + + final params = []; + + if (value is List) { + if (collectionFormat == 'multi') { + return value.map((dynamic v) => QueryParam(name, parameterToString(v)),); + } + + // Default collection format is 'csv'. + if (collectionFormat.isEmpty) { + collectionFormat = 'csv'; // ignore: parameter_assignments + } + + final delimiter = _delimiters[collectionFormat] ?? ','; + + params.add(QueryParam(name, value.map(parameterToString).join(delimiter),)); + } else if (value != null) { + params.add(QueryParam(name, parameterToString(value))); + } + + return params; +} + +/// Format the given parameter object into a [String]. +String parameterToString(dynamic value) { + if (value == null) { + return ''; + } + if (value is DateTime) { + return value.toUtc().toIso8601String(); + } + if (value is AlbumUserRole) { + return AlbumUserRoleTypeTransformer().encode(value).toString(); + } + if (value is AssetEditAction) { + return AssetEditActionTypeTransformer().encode(value).toString(); + } + if (value is AssetJobName) { + return AssetJobNameTypeTransformer().encode(value).toString(); + } + if (value is AssetMediaSize) { + return AssetMediaSizeTypeTransformer().encode(value).toString(); + } + if (value is AssetMediaStatus) { + return AssetMediaStatusTypeTransformer().encode(value).toString(); + } + if (value is AssetOrder) { + return AssetOrderTypeTransformer().encode(value).toString(); + } + if (value is AssetTypeEnum) { + return AssetTypeEnumTypeTransformer().encode(value).toString(); + } + if (value is AssetVisibility) { + return AssetVisibilityTypeTransformer().encode(value).toString(); + } + if (value is AudioCodec) { + return AudioCodecTypeTransformer().encode(value).toString(); + } + if (value is BulkIdErrorReason) { + return BulkIdErrorReasonTypeTransformer().encode(value).toString(); + } + if (value is CQMode) { + return CQModeTypeTransformer().encode(value).toString(); + } + if (value is Colorspace) { + return ColorspaceTypeTransformer().encode(value).toString(); + } + if (value is ImageFormat) { + return ImageFormatTypeTransformer().encode(value).toString(); + } + if (value is JobName) { + return JobNameTypeTransformer().encode(value).toString(); + } + if (value is LogLevel) { + return LogLevelTypeTransformer().encode(value).toString(); + } + if (value is MaintenanceAction) { + return MaintenanceActionTypeTransformer().encode(value).toString(); + } + if (value is ManualJobName) { + return ManualJobNameTypeTransformer().encode(value).toString(); + } + if (value is MemorySearchOrder) { + return MemorySearchOrderTypeTransformer().encode(value).toString(); + } + if (value is MemoryType) { + return MemoryTypeTypeTransformer().encode(value).toString(); + } + if (value is MirrorAxis) { + return MirrorAxisTypeTransformer().encode(value).toString(); + } + if (value is NotificationLevel) { + return NotificationLevelTypeTransformer().encode(value).toString(); + } + if (value is NotificationType) { + return NotificationTypeTypeTransformer().encode(value).toString(); + } + if (value is OAuthTokenEndpointAuthMethod) { + return OAuthTokenEndpointAuthMethodTypeTransformer().encode(value).toString(); + } + if (value is PartnerDirection) { + return PartnerDirectionTypeTransformer().encode(value).toString(); + } + if (value is Permission) { + return PermissionTypeTransformer().encode(value).toString(); + } + if (value is PluginContextType) { + return PluginContextTypeTypeTransformer().encode(value).toString(); + } + if (value is PluginTriggerType) { + return PluginTriggerTypeTypeTransformer().encode(value).toString(); + } + if (value is QueueCommand) { + return QueueCommandTypeTransformer().encode(value).toString(); + } + if (value is QueueJobStatus) { + return QueueJobStatusTypeTransformer().encode(value).toString(); + } + if (value is QueueName) { + return QueueNameTypeTransformer().encode(value).toString(); + } + if (value is ReactionLevel) { + return ReactionLevelTypeTransformer().encode(value).toString(); + } + if (value is ReactionType) { + return ReactionTypeTypeTransformer().encode(value).toString(); + } + if (value is SearchSuggestionType) { + return SearchSuggestionTypeTypeTransformer().encode(value).toString(); + } + if (value is SharedLinkType) { + return SharedLinkTypeTypeTransformer().encode(value).toString(); + } + if (value is SourceType) { + return SourceTypeTypeTransformer().encode(value).toString(); + } + if (value is StorageFolder) { + return StorageFolderTypeTransformer().encode(value).toString(); + } + if (value is SyncEntityType) { + return SyncEntityTypeTypeTransformer().encode(value).toString(); + } + if (value is SyncRequestType) { + return SyncRequestTypeTypeTransformer().encode(value).toString(); + } + if (value is ToneMapping) { + return ToneMappingTypeTransformer().encode(value).toString(); + } + if (value is TranscodeHWAccel) { + return TranscodeHWAccelTypeTransformer().encode(value).toString(); + } + if (value is TranscodePolicy) { + return TranscodePolicyTypeTransformer().encode(value).toString(); + } + if (value is UserAvatarColor) { + return UserAvatarColorTypeTransformer().encode(value).toString(); + } + if (value is UserMetadataKey) { + return UserMetadataKeyTypeTransformer().encode(value).toString(); + } + if (value is UserStatus) { + return UserStatusTypeTransformer().encode(value).toString(); + } + if (value is VideoCodec) { + return VideoCodecTypeTransformer().encode(value).toString(); + } + if (value is VideoContainer) { + return VideoContainerTypeTransformer().encode(value).toString(); + } + return value.toString(); +} + +/// Returns the decoded body as UTF-8 if the given headers indicate an 'application/json' +/// content type. Otherwise, returns the decoded body as decoded by dart:http package. +Future _decodeBodyBytes(Response response) async { + final contentType = response.headers['content-type']; + return contentType != null && contentType.toLowerCase().startsWith('application/json') + ? response.bodyBytes.isEmpty ? '' : utf8.decode(response.bodyBytes) + : response.body; +} + +/// Returns a valid [T] value found at the specified Map [key], null otherwise. +T? mapValueOfType(dynamic map, String key) { + final dynamic value = map is Map ? map[key] : null; + return value is T ? value : null; +} + +/// Returns a valid Map found at the specified Map [key], null otherwise. +Map? mapCastOfType(dynamic map, String key) { + final dynamic value = map is Map ? map[key] : null; + return value is Map ? value.cast() : null; +} + +/// Returns a valid [DateTime] found at the specified Map [key], null otherwise. +DateTime? mapDateTime(dynamic map, String key, [String? pattern]) { + final dynamic value = map is Map ? map[key] : null; + if (value != null) { + int? millis; + if (value is int) { + millis = value; + } else if (value is String) { + if (_isEpochMarker(pattern)) { + millis = int.tryParse(value); + } else { + return DateTime.tryParse(value); + } + } + if (millis != null) { + return DateTime.fromMillisecondsSinceEpoch(millis, isUtc: true); + } + } + return null; +} diff --git a/mobile/openapi/lib/auth/api_key_auth.dart b/mobile/openapi/lib/auth/api_key_auth.dart new file mode 100644 index 0000000000..6c5621798f --- /dev/null +++ b/mobile/openapi/lib/auth/api_key_auth.dart @@ -0,0 +1,40 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ApiKeyAuth implements Authentication { + ApiKeyAuth(this.location, this.paramName); + + final String location; + final String paramName; + + String apiKeyPrefix = ''; + String apiKey = ''; + + @override + Future applyToParams(List queryParams, Map headerParams,) async { + final paramValue = apiKeyPrefix.isEmpty ? apiKey : '$apiKeyPrefix $apiKey'; + + if (paramValue.isNotEmpty) { + if (location == 'query') { + queryParams.add(QueryParam(paramName, paramValue)); + } else if (location == 'header') { + headerParams[paramName] = paramValue; + } else if (location == 'cookie') { + headerParams.update( + 'Cookie', + (existingCookie) => '$existingCookie; $paramName=$paramValue', + ifAbsent: () => '$paramName=$paramValue', + ); + } + } + } +} diff --git a/mobile/openapi/lib/auth/authentication.dart b/mobile/openapi/lib/auth/authentication.dart new file mode 100644 index 0000000000..5377fb6f34 --- /dev/null +++ b/mobile/openapi/lib/auth/authentication.dart @@ -0,0 +1,17 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +// ignore: one_member_abstracts +abstract class Authentication { + /// Apply authentication settings to header and query params. + Future applyToParams(List queryParams, Map headerParams); +} diff --git a/mobile/openapi/lib/auth/http_basic_auth.dart b/mobile/openapi/lib/auth/http_basic_auth.dart new file mode 100644 index 0000000000..5e8b1c4147 --- /dev/null +++ b/mobile/openapi/lib/auth/http_basic_auth.dart @@ -0,0 +1,26 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class HttpBasicAuth implements Authentication { + HttpBasicAuth({this.username = '', this.password = ''}); + + String username; + String password; + + @override + Future applyToParams(List queryParams, Map headerParams,) async { + if (username.isNotEmpty && password.isNotEmpty) { + final credentials = '$username:$password'; + headerParams['Authorization'] = 'Basic ${base64.encode(utf8.encode(credentials))}'; + } + } +} diff --git a/mobile/openapi/lib/auth/http_bearer_auth.dart b/mobile/openapi/lib/auth/http_bearer_auth.dart new file mode 100644 index 0000000000..847dc056e1 --- /dev/null +++ b/mobile/openapi/lib/auth/http_bearer_auth.dart @@ -0,0 +1,49 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +typedef HttpBearerAuthProvider = String Function(); + +class HttpBearerAuth implements Authentication { + HttpBearerAuth(); + + dynamic _accessToken; + + dynamic get accessToken => _accessToken; + + set accessToken(dynamic accessToken) { + if (accessToken is! String && accessToken is! HttpBearerAuthProvider) { + throw ArgumentError('accessToken value must be either a String or a String Function().'); + } + _accessToken = accessToken; + } + + @override + Future applyToParams(List queryParams, Map headerParams,) async { + if (_accessToken == null) { + return; + } + + String accessToken; + + if (_accessToken is String) { + accessToken = _accessToken; + } else if (_accessToken is HttpBearerAuthProvider) { + accessToken = _accessToken!(); + } else { + return; + } + + if (accessToken.isNotEmpty) { + headerParams['Authorization'] = 'Bearer $accessToken'; + } + } +} diff --git a/mobile/openapi/lib/auth/oauth.dart b/mobile/openapi/lib/auth/oauth.dart new file mode 100644 index 0000000000..73fd8202dc --- /dev/null +++ b/mobile/openapi/lib/auth/oauth.dart @@ -0,0 +1,24 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OAuth implements Authentication { + OAuth({this.accessToken = ''}); + + String accessToken; + + @override + Future applyToParams(List queryParams, Map headerParams,) async { + if (accessToken.isNotEmpty) { + headerParams['Authorization'] = 'Bearer $accessToken'; + } + } +} diff --git a/mobile/openapi/lib/model/activity_create_dto.dart b/mobile/openapi/lib/model/activity_create_dto.dart new file mode 100644 index 0000000000..fb4b6d084e --- /dev/null +++ b/mobile/openapi/lib/model/activity_create_dto.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ActivityCreateDto { + /// Returns a new [ActivityCreateDto] instance. + ActivityCreateDto({ + required this.albumId, + this.assetId, + this.comment, + required this.type, + }); + + /// Album ID + String albumId; + + /// Asset ID (if activity is for an asset) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? assetId; + + /// Comment text (required if type is comment) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? comment; + + /// Activity type (like or comment) + ReactionType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is ActivityCreateDto && + other.albumId == albumId && + other.assetId == assetId && + other.comment == comment && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (assetId == null ? 0 : assetId!.hashCode) + + (comment == null ? 0 : comment!.hashCode) + + (type.hashCode); + + @override + String toString() => 'ActivityCreateDto[albumId=$albumId, assetId=$assetId, comment=$comment, type=$type]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + if (this.assetId != null) { + json[r'assetId'] = this.assetId; + } else { + // json[r'assetId'] = null; + } + if (this.comment != null) { + json[r'comment'] = this.comment; + } else { + // json[r'comment'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [ActivityCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ActivityCreateDto? fromJson(dynamic value) { + upgradeDto(value, "ActivityCreateDto"); + if (value is Map) { + final json = value.cast(); + + return ActivityCreateDto( + albumId: mapValueOfType(json, r'albumId')!, + assetId: mapValueOfType(json, r'assetId'), + comment: mapValueOfType(json, r'comment'), + type: ReactionType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ActivityCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ActivityCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ActivityCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ActivityCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/activity_response_dto.dart b/mobile/openapi/lib/model/activity_response_dto.dart new file mode 100644 index 0000000000..dadb45d8ac --- /dev/null +++ b/mobile/openapi/lib/model/activity_response_dto.dart @@ -0,0 +1,151 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ActivityResponseDto { + /// Returns a new [ActivityResponseDto] instance. + ActivityResponseDto({ + required this.assetId, + this.comment, + required this.createdAt, + required this.id, + required this.type, + required this.user, + }); + + /// Asset ID (if activity is for an asset) + String? assetId; + + /// Comment text (for comment activities) + String? comment; + + /// Creation date + DateTime createdAt; + + /// Activity ID + String id; + + /// Activity type + ReactionType type; + + UserResponseDto user; + + @override + bool operator ==(Object other) => identical(this, other) || other is ActivityResponseDto && + other.assetId == assetId && + other.comment == comment && + other.createdAt == createdAt && + other.id == id && + other.type == type && + other.user == user; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId == null ? 0 : assetId!.hashCode) + + (comment == null ? 0 : comment!.hashCode) + + (createdAt.hashCode) + + (id.hashCode) + + (type.hashCode) + + (user.hashCode); + + @override + String toString() => 'ActivityResponseDto[assetId=$assetId, comment=$comment, createdAt=$createdAt, id=$id, type=$type, user=$user]'; + + Map toJson() { + final json = {}; + if (this.assetId != null) { + json[r'assetId'] = this.assetId; + } else { + // json[r'assetId'] = null; + } + if (this.comment != null) { + json[r'comment'] = this.comment; + } else { + // json[r'comment'] = null; + } + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'id'] = this.id; + json[r'type'] = this.type; + json[r'user'] = this.user; + return json; + } + + /// Returns a new [ActivityResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ActivityResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ActivityResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ActivityResponseDto( + assetId: mapValueOfType(json, r'assetId'), + comment: mapValueOfType(json, r'comment'), + createdAt: mapDateTime(json, r'createdAt', r'')!, + id: mapValueOfType(json, r'id')!, + type: ReactionType.fromJson(json[r'type'])!, + user: UserResponseDto.fromJson(json[r'user'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ActivityResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ActivityResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ActivityResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ActivityResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'createdAt', + 'id', + 'type', + 'user', + }; +} + diff --git a/mobile/openapi/lib/model/activity_statistics_response_dto.dart b/mobile/openapi/lib/model/activity_statistics_response_dto.dart new file mode 100644 index 0000000000..15ad2a170e --- /dev/null +++ b/mobile/openapi/lib/model/activity_statistics_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ActivityStatisticsResponseDto { + /// Returns a new [ActivityStatisticsResponseDto] instance. + ActivityStatisticsResponseDto({ + required this.comments, + required this.likes, + }); + + /// Number of comments + int comments; + + /// Number of likes + int likes; + + @override + bool operator ==(Object other) => identical(this, other) || other is ActivityStatisticsResponseDto && + other.comments == comments && + other.likes == likes; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (comments.hashCode) + + (likes.hashCode); + + @override + String toString() => 'ActivityStatisticsResponseDto[comments=$comments, likes=$likes]'; + + Map toJson() { + final json = {}; + json[r'comments'] = this.comments; + json[r'likes'] = this.likes; + return json; + } + + /// Returns a new [ActivityStatisticsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ActivityStatisticsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ActivityStatisticsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ActivityStatisticsResponseDto( + comments: mapValueOfType(json, r'comments')!, + likes: mapValueOfType(json, r'likes')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ActivityStatisticsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ActivityStatisticsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ActivityStatisticsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ActivityStatisticsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'comments', + 'likes', + }; +} + diff --git a/mobile/openapi/lib/model/add_users_dto.dart b/mobile/openapi/lib/model/add_users_dto.dart new file mode 100644 index 0000000000..1dad234811 --- /dev/null +++ b/mobile/openapi/lib/model/add_users_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AddUsersDto { + /// Returns a new [AddUsersDto] instance. + AddUsersDto({ + this.albumUsers = const [], + }); + + /// Album users to add + List albumUsers; + + @override + bool operator ==(Object other) => identical(this, other) || other is AddUsersDto && + _deepEquality.equals(other.albumUsers, albumUsers); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumUsers.hashCode); + + @override + String toString() => 'AddUsersDto[albumUsers=$albumUsers]'; + + Map toJson() { + final json = {}; + json[r'albumUsers'] = this.albumUsers; + return json; + } + + /// Returns a new [AddUsersDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AddUsersDto? fromJson(dynamic value) { + upgradeDto(value, "AddUsersDto"); + if (value is Map) { + final json = value.cast(); + + return AddUsersDto( + albumUsers: AlbumUserAddDto.listFromJson(json[r'albumUsers']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AddUsersDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AddUsersDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AddUsersDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AddUsersDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumUsers', + }; +} + diff --git a/mobile/openapi/lib/model/admin_onboarding_update_dto.dart b/mobile/openapi/lib/model/admin_onboarding_update_dto.dart new file mode 100644 index 0000000000..6daba2a796 --- /dev/null +++ b/mobile/openapi/lib/model/admin_onboarding_update_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AdminOnboardingUpdateDto { + /// Returns a new [AdminOnboardingUpdateDto] instance. + AdminOnboardingUpdateDto({ + required this.isOnboarded, + }); + + /// Is admin onboarded + bool isOnboarded; + + @override + bool operator ==(Object other) => identical(this, other) || other is AdminOnboardingUpdateDto && + other.isOnboarded == isOnboarded; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isOnboarded.hashCode); + + @override + String toString() => 'AdminOnboardingUpdateDto[isOnboarded=$isOnboarded]'; + + Map toJson() { + final json = {}; + json[r'isOnboarded'] = this.isOnboarded; + return json; + } + + /// Returns a new [AdminOnboardingUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AdminOnboardingUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "AdminOnboardingUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return AdminOnboardingUpdateDto( + isOnboarded: mapValueOfType(json, r'isOnboarded')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AdminOnboardingUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AdminOnboardingUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AdminOnboardingUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AdminOnboardingUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isOnboarded', + }; +} + diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart new file mode 100644 index 0000000000..43e686fbdc --- /dev/null +++ b/mobile/openapi/lib/model/album_response_dto.dart @@ -0,0 +1,297 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumResponseDto { + /// Returns a new [AlbumResponseDto] instance. + AlbumResponseDto({ + required this.albumName, + required this.albumThumbnailAssetId, + this.albumUsers = const [], + required this.assetCount, + this.assets = const [], + this.contributorCounts = const [], + required this.createdAt, + required this.description, + this.endDate, + required this.hasSharedLink, + required this.id, + required this.isActivityEnabled, + this.lastModifiedAssetTimestamp, + this.order, + required this.owner, + required this.ownerId, + required this.shared, + this.startDate, + required this.updatedAt, + }); + + /// Album name + String albumName; + + /// Thumbnail asset ID + String? albumThumbnailAssetId; + + List albumUsers; + + /// Number of assets + int assetCount; + + List assets; + + List contributorCounts; + + /// Creation date + DateTime createdAt; + + /// Album description + String description; + + /// End date (latest asset) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? endDate; + + /// Has shared link + bool hasSharedLink; + + /// Album ID + String id; + + /// Activity feed enabled + bool isActivityEnabled; + + /// Last modified asset timestamp + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? lastModifiedAssetTimestamp; + + /// Asset sort order + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetOrder? order; + + UserResponseDto owner; + + /// Owner user ID + String ownerId; + + /// Is shared album + bool shared; + + /// Start date (earliest asset) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? startDate; + + /// Last update date + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto && + other.albumName == albumName && + other.albumThumbnailAssetId == albumThumbnailAssetId && + _deepEquality.equals(other.albumUsers, albumUsers) && + other.assetCount == assetCount && + _deepEquality.equals(other.assets, assets) && + _deepEquality.equals(other.contributorCounts, contributorCounts) && + other.createdAt == createdAt && + other.description == description && + other.endDate == endDate && + other.hasSharedLink == hasSharedLink && + other.id == id && + other.isActivityEnabled == isActivityEnabled && + other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp && + other.order == order && + other.owner == owner && + other.ownerId == ownerId && + other.shared == shared && + other.startDate == startDate && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumName.hashCode) + + (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + + (albumUsers.hashCode) + + (assetCount.hashCode) + + (assets.hashCode) + + (contributorCounts.hashCode) + + (createdAt.hashCode) + + (description.hashCode) + + (endDate == null ? 0 : endDate!.hashCode) + + (hasSharedLink.hashCode) + + (id.hashCode) + + (isActivityEnabled.hashCode) + + (lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) + + (order == null ? 0 : order!.hashCode) + + (owner.hashCode) + + (ownerId.hashCode) + + (shared.hashCode) + + (startDate == null ? 0 : startDate!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'AlbumResponseDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, contributorCounts=$contributorCounts, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, id=$id, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, owner=$owner, ownerId=$ownerId, shared=$shared, startDate=$startDate, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'albumName'] = this.albumName; + if (this.albumThumbnailAssetId != null) { + json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; + } else { + // json[r'albumThumbnailAssetId'] = null; + } + json[r'albumUsers'] = this.albumUsers; + json[r'assetCount'] = this.assetCount; + json[r'assets'] = this.assets; + json[r'contributorCounts'] = this.contributorCounts; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'description'] = this.description; + if (this.endDate != null) { + json[r'endDate'] = this.endDate!.toUtc().toIso8601String(); + } else { + // json[r'endDate'] = null; + } + json[r'hasSharedLink'] = this.hasSharedLink; + json[r'id'] = this.id; + json[r'isActivityEnabled'] = this.isActivityEnabled; + if (this.lastModifiedAssetTimestamp != null) { + json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String(); + } else { + // json[r'lastModifiedAssetTimestamp'] = null; + } + if (this.order != null) { + json[r'order'] = this.order; + } else { + // json[r'order'] = null; + } + json[r'owner'] = this.owner; + json[r'ownerId'] = this.ownerId; + json[r'shared'] = this.shared; + if (this.startDate != null) { + json[r'startDate'] = this.startDate!.toUtc().toIso8601String(); + } else { + // json[r'startDate'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [AlbumResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumResponseDto( + albumName: mapValueOfType(json, r'albumName')!, + albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), + albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']), + assetCount: mapValueOfType(json, r'assetCount')!, + assets: AssetResponseDto.listFromJson(json[r'assets']), + contributorCounts: ContributorCountResponseDto.listFromJson(json[r'contributorCounts']), + createdAt: mapDateTime(json, r'createdAt', r'')!, + description: mapValueOfType(json, r'description')!, + endDate: mapDateTime(json, r'endDate', r''), + hasSharedLink: mapValueOfType(json, r'hasSharedLink')!, + id: mapValueOfType(json, r'id')!, + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, + lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''), + order: AssetOrder.fromJson(json[r'order']), + owner: UserResponseDto.fromJson(json[r'owner'])!, + ownerId: mapValueOfType(json, r'ownerId')!, + shared: mapValueOfType(json, r'shared')!, + startDate: mapDateTime(json, r'startDate', r''), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumName', + 'albumThumbnailAssetId', + 'albumUsers', + 'assetCount', + 'assets', + 'createdAt', + 'description', + 'hasSharedLink', + 'id', + 'isActivityEnabled', + 'owner', + 'ownerId', + 'shared', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/album_statistics_response_dto.dart b/mobile/openapi/lib/model/album_statistics_response_dto.dart new file mode 100644 index 0000000000..127334e687 --- /dev/null +++ b/mobile/openapi/lib/model/album_statistics_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumStatisticsResponseDto { + /// Returns a new [AlbumStatisticsResponseDto] instance. + AlbumStatisticsResponseDto({ + required this.notShared, + required this.owned, + required this.shared, + }); + + /// Number of non-shared albums + int notShared; + + /// Number of owned albums + int owned; + + /// Number of shared albums + int shared; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumStatisticsResponseDto && + other.notShared == notShared && + other.owned == owned && + other.shared == shared; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (notShared.hashCode) + + (owned.hashCode) + + (shared.hashCode); + + @override + String toString() => 'AlbumStatisticsResponseDto[notShared=$notShared, owned=$owned, shared=$shared]'; + + Map toJson() { + final json = {}; + json[r'notShared'] = this.notShared; + json[r'owned'] = this.owned; + json[r'shared'] = this.shared; + return json; + } + + /// Returns a new [AlbumStatisticsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumStatisticsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumStatisticsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumStatisticsResponseDto( + notShared: mapValueOfType(json, r'notShared')!, + owned: mapValueOfType(json, r'owned')!, + shared: mapValueOfType(json, r'shared')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumStatisticsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumStatisticsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumStatisticsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumStatisticsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'notShared', + 'owned', + 'shared', + }; +} + diff --git a/mobile/openapi/lib/model/album_user_add_dto.dart b/mobile/openapi/lib/model/album_user_add_dto.dart new file mode 100644 index 0000000000..c448a0b4b7 --- /dev/null +++ b/mobile/openapi/lib/model/album_user_add_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumUserAddDto { + /// Returns a new [AlbumUserAddDto] instance. + AlbumUserAddDto({ + this.role = AlbumUserRole.editor, + required this.userId, + }); + + /// Album user role + AlbumUserRole role; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumUserAddDto && + other.role == role && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (role.hashCode) + + (userId.hashCode); + + @override + String toString() => 'AlbumUserAddDto[role=$role, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'role'] = this.role; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [AlbumUserAddDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumUserAddDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumUserAddDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumUserAddDto( + role: AlbumUserRole.fromJson(json[r'role']) ?? AlbumUserRole.editor, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumUserAddDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumUserAddDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumUserAddDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumUserAddDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/album_user_create_dto.dart b/mobile/openapi/lib/model/album_user_create_dto.dart new file mode 100644 index 0000000000..8006748341 --- /dev/null +++ b/mobile/openapi/lib/model/album_user_create_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumUserCreateDto { + /// Returns a new [AlbumUserCreateDto] instance. + AlbumUserCreateDto({ + required this.role, + required this.userId, + }); + + /// Album user role + AlbumUserRole role; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumUserCreateDto && + other.role == role && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (role.hashCode) + + (userId.hashCode); + + @override + String toString() => 'AlbumUserCreateDto[role=$role, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'role'] = this.role; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [AlbumUserCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumUserCreateDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumUserCreateDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumUserCreateDto( + role: AlbumUserRole.fromJson(json[r'role'])!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumUserCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumUserCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumUserCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumUserCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'role', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/album_user_response_dto.dart b/mobile/openapi/lib/model/album_user_response_dto.dart new file mode 100644 index 0000000000..8d0c01cfb8 --- /dev/null +++ b/mobile/openapi/lib/model/album_user_response_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumUserResponseDto { + /// Returns a new [AlbumUserResponseDto] instance. + AlbumUserResponseDto({ + required this.role, + required this.user, + }); + + /// Album user role + AlbumUserRole role; + + UserResponseDto user; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumUserResponseDto && + other.role == role && + other.user == user; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (role.hashCode) + + (user.hashCode); + + @override + String toString() => 'AlbumUserResponseDto[role=$role, user=$user]'; + + Map toJson() { + final json = {}; + json[r'role'] = this.role; + json[r'user'] = this.user; + return json; + } + + /// Returns a new [AlbumUserResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumUserResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumUserResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumUserResponseDto( + role: AlbumUserRole.fromJson(json[r'role'])!, + user: UserResponseDto.fromJson(json[r'user'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumUserResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumUserResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumUserResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumUserResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'role', + 'user', + }; +} + diff --git a/mobile/openapi/lib/model/album_user_role.dart b/mobile/openapi/lib/model/album_user_role.dart new file mode 100644 index 0000000000..d797fdc2e8 --- /dev/null +++ b/mobile/openapi/lib/model/album_user_role.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Album user role +class AlbumUserRole { + /// Instantiate a new enum with the provided [value]. + const AlbumUserRole._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const editor = AlbumUserRole._(r'editor'); + static const viewer = AlbumUserRole._(r'viewer'); + + /// List of all possible values in this [enum][AlbumUserRole]. + static const values = [ + editor, + viewer, + ]; + + static AlbumUserRole? fromJson(dynamic value) => AlbumUserRoleTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumUserRole.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AlbumUserRole] to String, +/// and [decode] dynamic data back to [AlbumUserRole]. +class AlbumUserRoleTypeTransformer { + factory AlbumUserRoleTypeTransformer() => _instance ??= const AlbumUserRoleTypeTransformer._(); + + const AlbumUserRoleTypeTransformer._(); + + String encode(AlbumUserRole data) => data.value; + + /// Decodes a [dynamic value][data] to a AlbumUserRole. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AlbumUserRole? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'editor': return AlbumUserRole.editor; + case r'viewer': return AlbumUserRole.viewer; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AlbumUserRoleTypeTransformer] instance. + static AlbumUserRoleTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/albums_add_assets_dto.dart b/mobile/openapi/lib/model/albums_add_assets_dto.dart new file mode 100644 index 0000000000..d6aa3db1c1 --- /dev/null +++ b/mobile/openapi/lib/model/albums_add_assets_dto.dart @@ -0,0 +1,113 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumsAddAssetsDto { + /// Returns a new [AlbumsAddAssetsDto] instance. + AlbumsAddAssetsDto({ + this.albumIds = const [], + this.assetIds = const [], + }); + + /// Album IDs + List albumIds; + + /// Asset IDs + List assetIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumsAddAssetsDto && + _deepEquality.equals(other.albumIds, albumIds) && + _deepEquality.equals(other.assetIds, assetIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumIds.hashCode) + + (assetIds.hashCode); + + @override + String toString() => 'AlbumsAddAssetsDto[albumIds=$albumIds, assetIds=$assetIds]'; + + Map toJson() { + final json = {}; + json[r'albumIds'] = this.albumIds; + json[r'assetIds'] = this.assetIds; + return json; + } + + /// Returns a new [AlbumsAddAssetsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumsAddAssetsDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumsAddAssetsDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumsAddAssetsDto( + albumIds: json[r'albumIds'] is Iterable + ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) + : const [], + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumsAddAssetsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumsAddAssetsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumsAddAssetsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumsAddAssetsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumIds', + 'assetIds', + }; +} + diff --git a/mobile/openapi/lib/model/albums_add_assets_response_dto.dart b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart new file mode 100644 index 0000000000..743a9f0645 --- /dev/null +++ b/mobile/openapi/lib/model/albums_add_assets_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumsAddAssetsResponseDto { + /// Returns a new [AlbumsAddAssetsResponseDto] instance. + AlbumsAddAssetsResponseDto({ + this.error, + required this.success, + }); + + /// Error reason + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + BulkIdErrorReason? error; + + /// Operation success + bool success; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumsAddAssetsResponseDto && + other.error == error && + other.success == success; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (error == null ? 0 : error!.hashCode) + + (success.hashCode); + + @override + String toString() => 'AlbumsAddAssetsResponseDto[error=$error, success=$success]'; + + Map toJson() { + final json = {}; + if (this.error != null) { + json[r'error'] = this.error; + } else { + // json[r'error'] = null; + } + json[r'success'] = this.success; + return json; + } + + /// Returns a new [AlbumsAddAssetsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumsAddAssetsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AlbumsAddAssetsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AlbumsAddAssetsResponseDto( + error: BulkIdErrorReason.fromJson(json[r'error']), + success: mapValueOfType(json, r'success')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumsAddAssetsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumsAddAssetsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumsAddAssetsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumsAddAssetsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'success', + }; +} + diff --git a/mobile/openapi/lib/model/albums_response.dart b/mobile/openapi/lib/model/albums_response.dart new file mode 100644 index 0000000000..520ee171c1 --- /dev/null +++ b/mobile/openapi/lib/model/albums_response.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumsResponse { + /// Returns a new [AlbumsResponse] instance. + AlbumsResponse({ + this.defaultAssetOrder = AssetOrder.desc, + }); + + /// Default asset order for albums + AssetOrder defaultAssetOrder; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumsResponse && + other.defaultAssetOrder == defaultAssetOrder; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (defaultAssetOrder.hashCode); + + @override + String toString() => 'AlbumsResponse[defaultAssetOrder=$defaultAssetOrder]'; + + Map toJson() { + final json = {}; + json[r'defaultAssetOrder'] = this.defaultAssetOrder; + return json; + } + + /// Returns a new [AlbumsResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumsResponse? fromJson(dynamic value) { + upgradeDto(value, "AlbumsResponse"); + if (value is Map) { + final json = value.cast(); + + return AlbumsResponse( + defaultAssetOrder: AssetOrder.fromJson(json[r'defaultAssetOrder'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumsResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumsResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumsResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumsResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'defaultAssetOrder', + }; +} + diff --git a/mobile/openapi/lib/model/albums_update.dart b/mobile/openapi/lib/model/albums_update.dart new file mode 100644 index 0000000000..107c65dd1e --- /dev/null +++ b/mobile/openapi/lib/model/albums_update.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AlbumsUpdate { + /// Returns a new [AlbumsUpdate] instance. + AlbumsUpdate({ + this.defaultAssetOrder, + }); + + /// Default asset order for albums + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetOrder? defaultAssetOrder; + + @override + bool operator ==(Object other) => identical(this, other) || other is AlbumsUpdate && + other.defaultAssetOrder == defaultAssetOrder; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (defaultAssetOrder == null ? 0 : defaultAssetOrder!.hashCode); + + @override + String toString() => 'AlbumsUpdate[defaultAssetOrder=$defaultAssetOrder]'; + + Map toJson() { + final json = {}; + if (this.defaultAssetOrder != null) { + json[r'defaultAssetOrder'] = this.defaultAssetOrder; + } else { + // json[r'defaultAssetOrder'] = null; + } + return json; + } + + /// Returns a new [AlbumsUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AlbumsUpdate? fromJson(dynamic value) { + upgradeDto(value, "AlbumsUpdate"); + if (value is Map) { + final json = value.cast(); + + return AlbumsUpdate( + defaultAssetOrder: AssetOrder.fromJson(json[r'defaultAssetOrder']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AlbumsUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AlbumsUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AlbumsUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AlbumsUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/api_key_create_dto.dart b/mobile/openapi/lib/model/api_key_create_dto.dart new file mode 100644 index 0000000000..e64b127820 --- /dev/null +++ b/mobile/openapi/lib/model/api_key_create_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class APIKeyCreateDto { + /// Returns a new [APIKeyCreateDto] instance. + APIKeyCreateDto({ + this.name, + this.permissions = const [], + }); + + /// API key name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// List of permissions + List permissions; + + @override + bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateDto && + other.name == name && + _deepEquality.equals(other.permissions, permissions); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (name == null ? 0 : name!.hashCode) + + (permissions.hashCode); + + @override + String toString() => 'APIKeyCreateDto[name=$name, permissions=$permissions]'; + + Map toJson() { + final json = {}; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'permissions'] = this.permissions; + return json; + } + + /// Returns a new [APIKeyCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static APIKeyCreateDto? fromJson(dynamic value) { + upgradeDto(value, "APIKeyCreateDto"); + if (value is Map) { + final json = value.cast(); + + return APIKeyCreateDto( + name: mapValueOfType(json, r'name'), + permissions: Permission.listFromJson(json[r'permissions']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = APIKeyCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = APIKeyCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of APIKeyCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = APIKeyCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'permissions', + }; +} + diff --git a/mobile/openapi/lib/model/api_key_create_response_dto.dart b/mobile/openapi/lib/model/api_key_create_response_dto.dart new file mode 100644 index 0000000000..7540c4bb26 --- /dev/null +++ b/mobile/openapi/lib/model/api_key_create_response_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class APIKeyCreateResponseDto { + /// Returns a new [APIKeyCreateResponseDto] instance. + APIKeyCreateResponseDto({ + required this.apiKey, + required this.secret, + }); + + APIKeyResponseDto apiKey; + + /// API key secret (only shown once) + String secret; + + @override + bool operator ==(Object other) => identical(this, other) || other is APIKeyCreateResponseDto && + other.apiKey == apiKey && + other.secret == secret; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (apiKey.hashCode) + + (secret.hashCode); + + @override + String toString() => 'APIKeyCreateResponseDto[apiKey=$apiKey, secret=$secret]'; + + Map toJson() { + final json = {}; + json[r'apiKey'] = this.apiKey; + json[r'secret'] = this.secret; + return json; + } + + /// Returns a new [APIKeyCreateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static APIKeyCreateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "APIKeyCreateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return APIKeyCreateResponseDto( + apiKey: APIKeyResponseDto.fromJson(json[r'apiKey'])!, + secret: mapValueOfType(json, r'secret')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = APIKeyCreateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = APIKeyCreateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of APIKeyCreateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = APIKeyCreateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'apiKey', + 'secret', + }; +} + diff --git a/mobile/openapi/lib/model/api_key_response_dto.dart b/mobile/openapi/lib/model/api_key_response_dto.dart new file mode 100644 index 0000000000..32ba543342 --- /dev/null +++ b/mobile/openapi/lib/model/api_key_response_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class APIKeyResponseDto { + /// Returns a new [APIKeyResponseDto] instance. + APIKeyResponseDto({ + required this.createdAt, + required this.id, + required this.name, + this.permissions = const [], + required this.updatedAt, + }); + + /// Creation date + DateTime createdAt; + + /// API key ID + String id; + + /// API key name + String name; + + /// List of permissions + List permissions; + + /// Last update date + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is APIKeyResponseDto && + other.createdAt == createdAt && + other.id == id && + other.name == name && + _deepEquality.equals(other.permissions, permissions) && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (id.hashCode) + + (name.hashCode) + + (permissions.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'APIKeyResponseDto[createdAt=$createdAt, id=$id, name=$name, permissions=$permissions, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'id'] = this.id; + json[r'name'] = this.name; + json[r'permissions'] = this.permissions; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [APIKeyResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static APIKeyResponseDto? fromJson(dynamic value) { + upgradeDto(value, "APIKeyResponseDto"); + if (value is Map) { + final json = value.cast(); + + return APIKeyResponseDto( + createdAt: mapDateTime(json, r'createdAt', r'')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + permissions: Permission.listFromJson(json[r'permissions']), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = APIKeyResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = APIKeyResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of APIKeyResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = APIKeyResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'id', + 'name', + 'permissions', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/api_key_update_dto.dart b/mobile/openapi/lib/model/api_key_update_dto.dart new file mode 100644 index 0000000000..ba107bcda2 --- /dev/null +++ b/mobile/openapi/lib/model/api_key_update_dto.dart @@ -0,0 +1,117 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class APIKeyUpdateDto { + /// Returns a new [APIKeyUpdateDto] instance. + APIKeyUpdateDto({ + this.name, + this.permissions = const [], + }); + + /// API key name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// List of permissions + List permissions; + + @override + bool operator ==(Object other) => identical(this, other) || other is APIKeyUpdateDto && + other.name == name && + _deepEquality.equals(other.permissions, permissions); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (name == null ? 0 : name!.hashCode) + + (permissions.hashCode); + + @override + String toString() => 'APIKeyUpdateDto[name=$name, permissions=$permissions]'; + + Map toJson() { + final json = {}; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'permissions'] = this.permissions; + return json; + } + + /// Returns a new [APIKeyUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static APIKeyUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "APIKeyUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return APIKeyUpdateDto( + name: mapValueOfType(json, r'name'), + permissions: Permission.listFromJson(json[r'permissions']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = APIKeyUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = APIKeyUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of APIKeyUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = APIKeyUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_delete_dto.dart b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart new file mode 100644 index 0000000000..055ef16015 --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_delete_dto.dart @@ -0,0 +1,120 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkDeleteDto { + /// Returns a new [AssetBulkDeleteDto] instance. + AssetBulkDeleteDto({ + this.force, + this.ids = const [], + }); + + /// Force delete even if in use + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? force; + + /// IDs to process + List ids; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkDeleteDto && + other.force == force && + _deepEquality.equals(other.ids, ids); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (force == null ? 0 : force!.hashCode) + + (ids.hashCode); + + @override + String toString() => 'AssetBulkDeleteDto[force=$force, ids=$ids]'; + + Map toJson() { + final json = {}; + if (this.force != null) { + json[r'force'] = this.force; + } else { + // json[r'force'] = null; + } + json[r'ids'] = this.ids; + return json; + } + + /// Returns a new [AssetBulkDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkDeleteDto( + force: mapValueOfType(json, r'force'), + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_update_dto.dart b/mobile/openapi/lib/model/asset_bulk_update_dto.dart new file mode 100644 index 0000000000..99bac7abfa --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_update_dto.dart @@ -0,0 +1,275 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkUpdateDto { + /// Returns a new [AssetBulkUpdateDto] instance. + AssetBulkUpdateDto({ + this.dateTimeOriginal, + this.dateTimeRelative, + this.description, + this.duplicateId, + this.ids = const [], + this.isFavorite, + this.latitude, + this.longitude, + this.rating, + this.timeZone, + this.visibility, + }); + + /// Original date and time + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? dateTimeOriginal; + + /// Relative time offset in seconds + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? dateTimeRelative; + + /// Asset description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Duplicate ID + String? duplicateId; + + /// Asset IDs to update + List ids; + + /// Mark as favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Latitude coordinate + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? latitude; + + /// Longitude coordinate + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? longitude; + + /// Rating in range [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Time zone (IANA timezone) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? timeZone; + + /// Asset visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto && + other.dateTimeOriginal == dateTimeOriginal && + other.dateTimeRelative == dateTimeRelative && + other.description == description && + other.duplicateId == duplicateId && + _deepEquality.equals(other.ids, ids) && + other.isFavorite == isFavorite && + other.latitude == latitude && + other.longitude == longitude && + other.rating == rating && + other.timeZone == timeZone && + other.visibility == visibility; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (dateTimeRelative == null ? 0 : dateTimeRelative!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (duplicateId == null ? 0 : duplicateId!.hashCode) + + (ids.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (timeZone == null ? 0 : timeZone!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode); + + @override + String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, dateTimeRelative=$dateTimeRelative, description=$description, duplicateId=$duplicateId, ids=$ids, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, rating=$rating, timeZone=$timeZone, visibility=$visibility]'; + + Map toJson() { + final json = {}; + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal; + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.dateTimeRelative != null) { + json[r'dateTimeRelative'] = this.dateTimeRelative; + } else { + // json[r'dateTimeRelative'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.duplicateId != null) { + json[r'duplicateId'] = this.duplicateId; + } else { + // json[r'duplicateId'] = null; + } + json[r'ids'] = this.ids; + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.timeZone != null) { + json[r'timeZone'] = this.timeZone; + } else { + // json[r'timeZone'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + return json; + } + + /// Returns a new [AssetBulkUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkUpdateDto( + dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), + dateTimeRelative: num.parse('${json[r'dateTimeRelative']}'), + description: mapValueOfType(json, r'description'), + duplicateId: mapValueOfType(json, r'duplicateId'), + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + isFavorite: mapValueOfType(json, r'isFavorite'), + latitude: num.parse('${json[r'latitude']}'), + longitude: num.parse('${json[r'longitude']}'), + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + timeZone: mapValueOfType(json, r'timeZone'), + visibility: AssetVisibility.fromJson(json[r'visibility']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_dto.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_dto.dart new file mode 100644 index 0000000000..66f46795e8 --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkUploadCheckDto { + /// Returns a new [AssetBulkUploadCheckDto] instance. + AssetBulkUploadCheckDto({ + this.assets = const [], + }); + + /// Assets to check + List assets; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckDto && + _deepEquality.equals(other.assets, assets); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode); + + @override + String toString() => 'AssetBulkUploadCheckDto[assets=$assets]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + return json; + } + + /// Returns a new [AssetBulkUploadCheckDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUploadCheckDto? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkUploadCheckDto"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkUploadCheckDto( + assets: AssetBulkUploadCheckItem.listFromJson(json[r'assets']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUploadCheckDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUploadCheckDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUploadCheckDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_item.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_item.dart new file mode 100644 index 0000000000..65f81926e3 --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_item.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkUploadCheckItem { + /// Returns a new [AssetBulkUploadCheckItem] instance. + AssetBulkUploadCheckItem({ + required this.checksum, + required this.id, + }); + + /// Base64 or hex encoded SHA1 hash + String checksum; + + /// Asset ID + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckItem && + other.checksum == checksum && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksum.hashCode) + + (id.hashCode); + + @override + String toString() => 'AssetBulkUploadCheckItem[checksum=$checksum, id=$id]'; + + Map toJson() { + final json = {}; + json[r'checksum'] = this.checksum; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [AssetBulkUploadCheckItem] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUploadCheckItem? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkUploadCheckItem"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkUploadCheckItem( + checksum: mapValueOfType(json, r'checksum')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckItem.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUploadCheckItem.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUploadCheckItem-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUploadCheckItem.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_response_dto.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_response_dto.dart new file mode 100644 index 0000000000..b37bb0de8a --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkUploadCheckResponseDto { + /// Returns a new [AssetBulkUploadCheckResponseDto] instance. + AssetBulkUploadCheckResponseDto({ + this.results = const [], + }); + + /// Upload check results + List results; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResponseDto && + _deepEquality.equals(other.results, results); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (results.hashCode); + + @override + String toString() => 'AssetBulkUploadCheckResponseDto[results=$results]'; + + Map toJson() { + final json = {}; + json[r'results'] = this.results; + return json; + } + + /// Returns a new [AssetBulkUploadCheckResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUploadCheckResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkUploadCheckResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkUploadCheckResponseDto( + results: AssetBulkUploadCheckResult.listFromJson(json[r'results']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUploadCheckResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUploadCheckResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUploadCheckResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'results', + }; +} + diff --git a/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart new file mode 100644 index 0000000000..b56370f689 --- /dev/null +++ b/mobile/openapi/lib/model/asset_bulk_upload_check_result.dart @@ -0,0 +1,305 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetBulkUploadCheckResult { + /// Returns a new [AssetBulkUploadCheckResult] instance. + AssetBulkUploadCheckResult({ + required this.action, + this.assetId, + required this.id, + this.isTrashed, + this.reason, + }); + + /// Upload action + AssetBulkUploadCheckResultActionEnum action; + + /// Existing asset ID if duplicate + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? assetId; + + /// Asset ID + String id; + + /// Whether existing asset is trashed + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isTrashed; + + /// Rejection reason if rejected + AssetBulkUploadCheckResultReasonEnum? reason; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetBulkUploadCheckResult && + other.action == action && + other.assetId == assetId && + other.id == id && + other.isTrashed == isTrashed && + other.reason == reason; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (assetId == null ? 0 : assetId!.hashCode) + + (id.hashCode) + + (isTrashed == null ? 0 : isTrashed!.hashCode) + + (reason == null ? 0 : reason!.hashCode); + + @override + String toString() => 'AssetBulkUploadCheckResult[action=$action, assetId=$assetId, id=$id, isTrashed=$isTrashed, reason=$reason]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + if (this.assetId != null) { + json[r'assetId'] = this.assetId; + } else { + // json[r'assetId'] = null; + } + json[r'id'] = this.id; + if (this.isTrashed != null) { + json[r'isTrashed'] = this.isTrashed; + } else { + // json[r'isTrashed'] = null; + } + if (this.reason != null) { + json[r'reason'] = this.reason; + } else { + // json[r'reason'] = null; + } + return json; + } + + /// Returns a new [AssetBulkUploadCheckResult] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetBulkUploadCheckResult? fromJson(dynamic value) { + upgradeDto(value, "AssetBulkUploadCheckResult"); + if (value is Map) { + final json = value.cast(); + + return AssetBulkUploadCheckResult( + action: AssetBulkUploadCheckResultActionEnum.fromJson(json[r'action'])!, + assetId: mapValueOfType(json, r'assetId'), + id: mapValueOfType(json, r'id')!, + isTrashed: mapValueOfType(json, r'isTrashed'), + reason: AssetBulkUploadCheckResultReasonEnum.fromJson(json[r'reason']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckResult.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetBulkUploadCheckResult.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetBulkUploadCheckResult-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetBulkUploadCheckResult.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + 'id', + }; +} + +/// Upload action +class AssetBulkUploadCheckResultActionEnum { + /// Instantiate a new enum with the provided [value]. + const AssetBulkUploadCheckResultActionEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const accept = AssetBulkUploadCheckResultActionEnum._(r'accept'); + static const reject = AssetBulkUploadCheckResultActionEnum._(r'reject'); + + /// List of all possible values in this [enum][AssetBulkUploadCheckResultActionEnum]. + static const values = [ + accept, + reject, + ]; + + static AssetBulkUploadCheckResultActionEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultActionEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckResultActionEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultActionEnum] to String, +/// and [decode] dynamic data back to [AssetBulkUploadCheckResultActionEnum]. +class AssetBulkUploadCheckResultActionEnumTypeTransformer { + factory AssetBulkUploadCheckResultActionEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultActionEnumTypeTransformer._(); + + const AssetBulkUploadCheckResultActionEnumTypeTransformer._(); + + String encode(AssetBulkUploadCheckResultActionEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultActionEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetBulkUploadCheckResultActionEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'accept': return AssetBulkUploadCheckResultActionEnum.accept; + case r'reject': return AssetBulkUploadCheckResultActionEnum.reject; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetBulkUploadCheckResultActionEnumTypeTransformer] instance. + static AssetBulkUploadCheckResultActionEnumTypeTransformer? _instance; +} + + +/// Rejection reason if rejected +class AssetBulkUploadCheckResultReasonEnum { + /// Instantiate a new enum with the provided [value]. + const AssetBulkUploadCheckResultReasonEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const duplicate = AssetBulkUploadCheckResultReasonEnum._(r'duplicate'); + static const unsupportedFormat = AssetBulkUploadCheckResultReasonEnum._(r'unsupported-format'); + + /// List of all possible values in this [enum][AssetBulkUploadCheckResultReasonEnum]. + static const values = [ + duplicate, + unsupportedFormat, + ]; + + static AssetBulkUploadCheckResultReasonEnum? fromJson(dynamic value) => AssetBulkUploadCheckResultReasonEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetBulkUploadCheckResultReasonEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetBulkUploadCheckResultReasonEnum] to String, +/// and [decode] dynamic data back to [AssetBulkUploadCheckResultReasonEnum]. +class AssetBulkUploadCheckResultReasonEnumTypeTransformer { + factory AssetBulkUploadCheckResultReasonEnumTypeTransformer() => _instance ??= const AssetBulkUploadCheckResultReasonEnumTypeTransformer._(); + + const AssetBulkUploadCheckResultReasonEnumTypeTransformer._(); + + String encode(AssetBulkUploadCheckResultReasonEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetBulkUploadCheckResultReasonEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetBulkUploadCheckResultReasonEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'duplicate': return AssetBulkUploadCheckResultReasonEnum.duplicate; + case r'unsupported-format': return AssetBulkUploadCheckResultReasonEnum.unsupportedFormat; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetBulkUploadCheckResultReasonEnumTypeTransformer] instance. + static AssetBulkUploadCheckResultReasonEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/asset_copy_dto.dart b/mobile/openapi/lib/model/asset_copy_dto.dart new file mode 100644 index 0000000000..2e68c5c113 --- /dev/null +++ b/mobile/openapi/lib/model/asset_copy_dto.dart @@ -0,0 +1,149 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetCopyDto { + /// Returns a new [AssetCopyDto] instance. + AssetCopyDto({ + this.albums = true, + this.favorite = true, + this.sharedLinks = true, + this.sidecar = true, + required this.sourceId, + this.stack = true, + required this.targetId, + }); + + /// Copy album associations + bool albums; + + /// Copy favorite status + bool favorite; + + /// Copy shared links + bool sharedLinks; + + /// Copy sidecar file + bool sidecar; + + /// Source asset ID + String sourceId; + + /// Copy stack association + bool stack; + + /// Target asset ID + String targetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetCopyDto && + other.albums == albums && + other.favorite == favorite && + other.sharedLinks == sharedLinks && + other.sidecar == sidecar && + other.sourceId == sourceId && + other.stack == stack && + other.targetId == targetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albums.hashCode) + + (favorite.hashCode) + + (sharedLinks.hashCode) + + (sidecar.hashCode) + + (sourceId.hashCode) + + (stack.hashCode) + + (targetId.hashCode); + + @override + String toString() => 'AssetCopyDto[albums=$albums, favorite=$favorite, sharedLinks=$sharedLinks, sidecar=$sidecar, sourceId=$sourceId, stack=$stack, targetId=$targetId]'; + + Map toJson() { + final json = {}; + json[r'albums'] = this.albums; + json[r'favorite'] = this.favorite; + json[r'sharedLinks'] = this.sharedLinks; + json[r'sidecar'] = this.sidecar; + json[r'sourceId'] = this.sourceId; + json[r'stack'] = this.stack; + json[r'targetId'] = this.targetId; + return json; + } + + /// Returns a new [AssetCopyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetCopyDto? fromJson(dynamic value) { + upgradeDto(value, "AssetCopyDto"); + if (value is Map) { + final json = value.cast(); + + return AssetCopyDto( + albums: mapValueOfType(json, r'albums') ?? true, + favorite: mapValueOfType(json, r'favorite') ?? true, + sharedLinks: mapValueOfType(json, r'sharedLinks') ?? true, + sidecar: mapValueOfType(json, r'sidecar') ?? true, + sourceId: mapValueOfType(json, r'sourceId')!, + stack: mapValueOfType(json, r'stack') ?? true, + targetId: mapValueOfType(json, r'targetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetCopyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetCopyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetCopyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetCopyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'sourceId', + 'targetId', + }; +} + diff --git a/mobile/openapi/lib/model/asset_delta_sync_dto.dart b/mobile/openapi/lib/model/asset_delta_sync_dto.dart new file mode 100644 index 0000000000..22c09752d2 --- /dev/null +++ b/mobile/openapi/lib/model/asset_delta_sync_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetDeltaSyncDto { + /// Returns a new [AssetDeltaSyncDto] instance. + AssetDeltaSyncDto({ + required this.updatedAfter, + this.userIds = const [], + }); + + /// Sync assets updated after this date + DateTime updatedAfter; + + /// User IDs to sync + List userIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetDeltaSyncDto && + other.updatedAfter == updatedAfter && + _deepEquality.equals(other.userIds, userIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (updatedAfter.hashCode) + + (userIds.hashCode); + + @override + String toString() => 'AssetDeltaSyncDto[updatedAfter=$updatedAfter, userIds=$userIds]'; + + Map toJson() { + final json = {}; + json[r'updatedAfter'] = this.updatedAfter.toUtc().toIso8601String(); + json[r'userIds'] = this.userIds; + return json; + } + + /// Returns a new [AssetDeltaSyncDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetDeltaSyncDto? fromJson(dynamic value) { + upgradeDto(value, "AssetDeltaSyncDto"); + if (value is Map) { + final json = value.cast(); + + return AssetDeltaSyncDto( + updatedAfter: mapDateTime(json, r'updatedAfter', r'')!, + userIds: json[r'userIds'] is Iterable + ? (json[r'userIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetDeltaSyncDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetDeltaSyncDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetDeltaSyncDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetDeltaSyncDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'updatedAfter', + 'userIds', + }; +} + diff --git a/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart b/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart new file mode 100644 index 0000000000..7351840b11 --- /dev/null +++ b/mobile/openapi/lib/model/asset_delta_sync_response_dto.dart @@ -0,0 +1,120 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetDeltaSyncResponseDto { + /// Returns a new [AssetDeltaSyncResponseDto] instance. + AssetDeltaSyncResponseDto({ + this.deleted = const [], + required this.needsFullSync, + this.upserted = const [], + }); + + /// Deleted asset IDs + List deleted; + + /// Whether full sync is needed + bool needsFullSync; + + /// Upserted assets + List upserted; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetDeltaSyncResponseDto && + _deepEquality.equals(other.deleted, deleted) && + other.needsFullSync == needsFullSync && + _deepEquality.equals(other.upserted, upserted); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deleted.hashCode) + + (needsFullSync.hashCode) + + (upserted.hashCode); + + @override + String toString() => 'AssetDeltaSyncResponseDto[deleted=$deleted, needsFullSync=$needsFullSync, upserted=$upserted]'; + + Map toJson() { + final json = {}; + json[r'deleted'] = this.deleted; + json[r'needsFullSync'] = this.needsFullSync; + json[r'upserted'] = this.upserted; + return json; + } + + /// Returns a new [AssetDeltaSyncResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetDeltaSyncResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetDeltaSyncResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetDeltaSyncResponseDto( + deleted: json[r'deleted'] is Iterable + ? (json[r'deleted'] as Iterable).cast().toList(growable: false) + : const [], + needsFullSync: mapValueOfType(json, r'needsFullSync')!, + upserted: AssetResponseDto.listFromJson(json[r'upserted']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetDeltaSyncResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetDeltaSyncResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetDeltaSyncResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetDeltaSyncResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deleted', + 'needsFullSync', + 'upserted', + }; +} + diff --git a/mobile/openapi/lib/model/asset_edit_action.dart b/mobile/openapi/lib/model/asset_edit_action.dart new file mode 100644 index 0000000000..3754cb4501 --- /dev/null +++ b/mobile/openapi/lib/model/asset_edit_action.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Type of edit action to perform +class AssetEditAction { + /// Instantiate a new enum with the provided [value]. + const AssetEditAction._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const crop = AssetEditAction._(r'crop'); + static const rotate = AssetEditAction._(r'rotate'); + static const mirror = AssetEditAction._(r'mirror'); + + /// List of all possible values in this [enum][AssetEditAction]. + static const values = [ + crop, + rotate, + mirror, + ]; + + static AssetEditAction? fromJson(dynamic value) => AssetEditActionTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditAction.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetEditAction] to String, +/// and [decode] dynamic data back to [AssetEditAction]. +class AssetEditActionTypeTransformer { + factory AssetEditActionTypeTransformer() => _instance ??= const AssetEditActionTypeTransformer._(); + + const AssetEditActionTypeTransformer._(); + + String encode(AssetEditAction data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetEditAction. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetEditAction? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'crop': return AssetEditAction.crop; + case r'rotate': return AssetEditAction.rotate; + case r'mirror': return AssetEditAction.mirror; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetEditActionTypeTransformer] instance. + static AssetEditActionTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_edit_action_item_dto.dart b/mobile/openapi/lib/model/asset_edit_action_item_dto.dart new file mode 100644 index 0000000000..7829de4bd5 --- /dev/null +++ b/mobile/openapi/lib/model/asset_edit_action_item_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetEditActionItemDto { + /// Returns a new [AssetEditActionItemDto] instance. + AssetEditActionItemDto({ + required this.action, + required this.parameters, + }); + + /// Type of edit action to perform + AssetEditAction action; + + AssetEditActionItemDtoParameters parameters; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDto && + other.action == action && + other.parameters == parameters; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (parameters.hashCode); + + @override + String toString() => 'AssetEditActionItemDto[action=$action, parameters=$parameters]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + json[r'parameters'] = this.parameters; + return json; + } + + /// Returns a new [AssetEditActionItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEditActionItemDto? fromJson(dynamic value) { + upgradeDto(value, "AssetEditActionItemDto"); + if (value is Map) { + final json = value.cast(); + + return AssetEditActionItemDto( + action: AssetEditAction.fromJson(json[r'action'])!, + parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditActionItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEditActionItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEditActionItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetEditActionItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + 'parameters', + }; +} + diff --git a/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart b/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart new file mode 100644 index 0000000000..fc67aa022f --- /dev/null +++ b/mobile/openapi/lib/model/asset_edit_action_item_dto_parameters.dart @@ -0,0 +1,153 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetEditActionItemDtoParameters { + /// Returns a new [AssetEditActionItemDtoParameters] instance. + AssetEditActionItemDtoParameters({ + required this.height, + required this.width, + required this.x, + required this.y, + required this.angle, + required this.axis, + }); + + /// Height of the crop + /// + /// Minimum value: 1 + num height; + + /// Width of the crop + /// + /// Minimum value: 1 + num width; + + /// Top-Left X coordinate of crop + /// + /// Minimum value: 0 + num x; + + /// Top-Left Y coordinate of crop + /// + /// Minimum value: 0 + num y; + + /// Rotation angle in degrees + num angle; + + /// Axis to mirror along + MirrorAxis axis; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemDtoParameters && + other.height == height && + other.width == width && + other.x == x && + other.y == y && + other.angle == angle && + other.axis == axis; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (height.hashCode) + + (width.hashCode) + + (x.hashCode) + + (y.hashCode) + + (angle.hashCode) + + (axis.hashCode); + + @override + String toString() => 'AssetEditActionItemDtoParameters[height=$height, width=$width, x=$x, y=$y, angle=$angle, axis=$axis]'; + + Map toJson() { + final json = {}; + json[r'height'] = this.height; + json[r'width'] = this.width; + json[r'x'] = this.x; + json[r'y'] = this.y; + json[r'angle'] = this.angle; + json[r'axis'] = this.axis; + return json; + } + + /// Returns a new [AssetEditActionItemDtoParameters] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEditActionItemDtoParameters? fromJson(dynamic value) { + upgradeDto(value, "AssetEditActionItemDtoParameters"); + if (value is Map) { + final json = value.cast(); + + return AssetEditActionItemDtoParameters( + height: num.parse('${json[r'height']}'), + width: num.parse('${json[r'width']}'), + x: num.parse('${json[r'x']}'), + y: num.parse('${json[r'y']}'), + angle: num.parse('${json[r'angle']}'), + axis: MirrorAxis.fromJson(json[r'axis'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditActionItemDtoParameters.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEditActionItemDtoParameters.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEditActionItemDtoParameters-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetEditActionItemDtoParameters.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'height', + 'width', + 'x', + 'y', + 'angle', + 'axis', + }; +} + diff --git a/mobile/openapi/lib/model/asset_edit_action_item_response_dto.dart b/mobile/openapi/lib/model/asset_edit_action_item_response_dto.dart new file mode 100644 index 0000000000..a23a1ef5f3 --- /dev/null +++ b/mobile/openapi/lib/model/asset_edit_action_item_response_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetEditActionItemResponseDto { + /// Returns a new [AssetEditActionItemResponseDto] instance. + AssetEditActionItemResponseDto({ + required this.action, + required this.id, + required this.parameters, + }); + + /// Type of edit action to perform + AssetEditAction action; + + String id; + + AssetEditActionItemDtoParameters parameters; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEditActionItemResponseDto && + other.action == action && + other.id == id && + other.parameters == parameters; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (id.hashCode) + + (parameters.hashCode); + + @override + String toString() => 'AssetEditActionItemResponseDto[action=$action, id=$id, parameters=$parameters]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + json[r'id'] = this.id; + json[r'parameters'] = this.parameters; + return json; + } + + /// Returns a new [AssetEditActionItemResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEditActionItemResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetEditActionItemResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetEditActionItemResponseDto( + action: AssetEditAction.fromJson(json[r'action'])!, + id: mapValueOfType(json, r'id')!, + parameters: AssetEditActionItemDtoParameters.fromJson(json[r'parameters'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditActionItemResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEditActionItemResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEditActionItemResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetEditActionItemResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + 'id', + 'parameters', + }; +} + diff --git a/mobile/openapi/lib/model/asset_edits_create_dto.dart b/mobile/openapi/lib/model/asset_edits_create_dto.dart new file mode 100644 index 0000000000..9f6fc66904 --- /dev/null +++ b/mobile/openapi/lib/model/asset_edits_create_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetEditsCreateDto { + /// Returns a new [AssetEditsCreateDto] instance. + AssetEditsCreateDto({ + this.edits = const [], + }); + + /// List of edit actions to apply (crop, rotate, or mirror) + List edits; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEditsCreateDto && + _deepEquality.equals(other.edits, edits); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (edits.hashCode); + + @override + String toString() => 'AssetEditsCreateDto[edits=$edits]'; + + Map toJson() { + final json = {}; + json[r'edits'] = this.edits; + return json; + } + + /// Returns a new [AssetEditsCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEditsCreateDto? fromJson(dynamic value) { + upgradeDto(value, "AssetEditsCreateDto"); + if (value is Map) { + final json = value.cast(); + + return AssetEditsCreateDto( + edits: AssetEditActionItemDto.listFromJson(json[r'edits']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditsCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEditsCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEditsCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetEditsCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'edits', + }; +} + diff --git a/mobile/openapi/lib/model/asset_edits_response_dto.dart b/mobile/openapi/lib/model/asset_edits_response_dto.dart new file mode 100644 index 0000000000..322b4c0a4c --- /dev/null +++ b/mobile/openapi/lib/model/asset_edits_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetEditsResponseDto { + /// Returns a new [AssetEditsResponseDto] instance. + AssetEditsResponseDto({ + required this.assetId, + this.edits = const [], + }); + + /// Asset ID these edits belong to + String assetId; + + /// List of edit actions applied to the asset + List edits; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetEditsResponseDto && + other.assetId == assetId && + _deepEquality.equals(other.edits, edits); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (edits.hashCode); + + @override + String toString() => 'AssetEditsResponseDto[assetId=$assetId, edits=$edits]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'edits'] = this.edits; + return json; + } + + /// Returns a new [AssetEditsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetEditsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetEditsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetEditsResponseDto( + assetId: mapValueOfType(json, r'assetId')!, + edits: AssetEditActionItemResponseDto.listFromJson(json[r'edits']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetEditsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetEditsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetEditsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetEditsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'edits', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_create_dto.dart b/mobile/openapi/lib/model/asset_face_create_dto.dart new file mode 100644 index 0000000000..3ecc20c699 --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_create_dto.dart @@ -0,0 +1,163 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceCreateDto { + /// Returns a new [AssetFaceCreateDto] instance. + AssetFaceCreateDto({ + required this.assetId, + required this.height, + required this.imageHeight, + required this.imageWidth, + required this.personId, + required this.width, + required this.x, + required this.y, + }); + + /// Asset ID + String assetId; + + /// Face bounding box height + int height; + + /// Image height in pixels + int imageHeight; + + /// Image width in pixels + int imageWidth; + + /// Person ID + String personId; + + /// Face bounding box width + int width; + + /// Face bounding box X coordinate + int x; + + /// Face bounding box Y coordinate + int y; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceCreateDto && + other.assetId == assetId && + other.height == height && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth && + other.personId == personId && + other.width == width && + other.x == x && + other.y == y; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (height.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode) + + (personId.hashCode) + + (width.hashCode) + + (x.hashCode) + + (y.hashCode); + + @override + String toString() => 'AssetFaceCreateDto[assetId=$assetId, height=$height, imageHeight=$imageHeight, imageWidth=$imageWidth, personId=$personId, width=$width, x=$x, y=$y]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'height'] = this.height; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + json[r'personId'] = this.personId; + json[r'width'] = this.width; + json[r'x'] = this.x; + json[r'y'] = this.y; + return json; + } + + /// Returns a new [AssetFaceCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceCreateDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceCreateDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceCreateDto( + assetId: mapValueOfType(json, r'assetId')!, + height: mapValueOfType(json, r'height')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + personId: mapValueOfType(json, r'personId')!, + width: mapValueOfType(json, r'width')!, + x: mapValueOfType(json, r'x')!, + y: mapValueOfType(json, r'y')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'height', + 'imageHeight', + 'imageWidth', + 'personId', + 'width', + 'x', + 'y', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_delete_dto.dart b/mobile/openapi/lib/model/asset_face_delete_dto.dart new file mode 100644 index 0000000000..a1f3731bea --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_delete_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceDeleteDto { + /// Returns a new [AssetFaceDeleteDto] instance. + AssetFaceDeleteDto({ + required this.force, + }); + + /// Force delete even if person has other faces + bool force; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceDeleteDto && + other.force == force; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (force.hashCode); + + @override + String toString() => 'AssetFaceDeleteDto[force=$force]'; + + Map toJson() { + final json = {}; + json[r'force'] = this.force; + return json; + } + + /// Returns a new [AssetFaceDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceDeleteDto( + force: mapValueOfType(json, r'force')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'force', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_response_dto.dart b/mobile/openapi/lib/model/asset_face_response_dto.dart new file mode 100644 index 0000000000..61d972a0c4 --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_response_dto.dart @@ -0,0 +1,185 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceResponseDto { + /// Returns a new [AssetFaceResponseDto] instance. + AssetFaceResponseDto({ + required this.boundingBoxX1, + required this.boundingBoxX2, + required this.boundingBoxY1, + required this.boundingBoxY2, + required this.id, + required this.imageHeight, + required this.imageWidth, + required this.person, + this.sourceType, + }); + + /// Bounding box X1 coordinate + int boundingBoxX1; + + /// Bounding box X2 coordinate + int boundingBoxX2; + + /// Bounding box Y1 coordinate + int boundingBoxY1; + + /// Bounding box Y2 coordinate + int boundingBoxY2; + + /// Face ID + String id; + + /// Image height in pixels + int imageHeight; + + /// Image width in pixels + int imageWidth; + + /// Person associated with face + PersonResponseDto? person; + + /// Face detection source type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + SourceType? sourceType; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceResponseDto && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxY2 == boundingBoxY2 && + other.id == id && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth && + other.person == person && + other.sourceType == sourceType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (boundingBoxX1.hashCode) + + (boundingBoxX2.hashCode) + + (boundingBoxY1.hashCode) + + (boundingBoxY2.hashCode) + + (id.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode) + + (person == null ? 0 : person!.hashCode) + + (sourceType == null ? 0 : sourceType!.hashCode); + + @override + String toString() => 'AssetFaceResponseDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, person=$person, sourceType=$sourceType]'; + + Map toJson() { + final json = {}; + json[r'boundingBoxX1'] = this.boundingBoxX1; + json[r'boundingBoxX2'] = this.boundingBoxX2; + json[r'boundingBoxY1'] = this.boundingBoxY1; + json[r'boundingBoxY2'] = this.boundingBoxY2; + json[r'id'] = this.id; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + if (this.person != null) { + json[r'person'] = this.person; + } else { + // json[r'person'] = null; + } + if (this.sourceType != null) { + json[r'sourceType'] = this.sourceType; + } else { + // json[r'sourceType'] = null; + } + return json; + } + + /// Returns a new [AssetFaceResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceResponseDto( + boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, + boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, + boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, + boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, + id: mapValueOfType(json, r'id')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + person: PersonResponseDto.fromJson(json[r'person']), + sourceType: SourceType.fromJson(json[r'sourceType']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'boundingBoxX1', + 'boundingBoxX2', + 'boundingBoxY1', + 'boundingBoxY2', + 'id', + 'imageHeight', + 'imageWidth', + 'person', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_update_dto.dart b/mobile/openapi/lib/model/asset_face_update_dto.dart new file mode 100644 index 0000000000..1027627552 --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_update_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceUpdateDto { + /// Returns a new [AssetFaceUpdateDto] instance. + AssetFaceUpdateDto({ + this.data = const [], + }); + + /// Face update items + List data; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateDto && + _deepEquality.equals(other.data, data); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (data.hashCode); + + @override + String toString() => 'AssetFaceUpdateDto[data=$data]'; + + Map toJson() { + final json = {}; + json[r'data'] = this.data; + return json; + } + + /// Returns a new [AssetFaceUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceUpdateDto( + data: AssetFaceUpdateItem.listFromJson(json[r'data']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'data', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_update_item.dart b/mobile/openapi/lib/model/asset_face_update_item.dart new file mode 100644 index 0000000000..a81b21e139 --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_update_item.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceUpdateItem { + /// Returns a new [AssetFaceUpdateItem] instance. + AssetFaceUpdateItem({ + required this.assetId, + required this.personId, + }); + + /// Asset ID + String assetId; + + /// Person ID + String personId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateItem && + other.assetId == assetId && + other.personId == personId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (personId.hashCode); + + @override + String toString() => 'AssetFaceUpdateItem[assetId=$assetId, personId=$personId]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'personId'] = this.personId; + return json; + } + + /// Returns a new [AssetFaceUpdateItem] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceUpdateItem? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceUpdateItem"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceUpdateItem( + assetId: mapValueOfType(json, r'assetId')!, + personId: mapValueOfType(json, r'personId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceUpdateItem.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceUpdateItem.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceUpdateItem-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceUpdateItem.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'personId', + }; +} + diff --git a/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart b/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart new file mode 100644 index 0000000000..1ae5cef07e --- /dev/null +++ b/mobile/openapi/lib/model/asset_face_without_person_response_dto.dart @@ -0,0 +1,172 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFaceWithoutPersonResponseDto { + /// Returns a new [AssetFaceWithoutPersonResponseDto] instance. + AssetFaceWithoutPersonResponseDto({ + required this.boundingBoxX1, + required this.boundingBoxX2, + required this.boundingBoxY1, + required this.boundingBoxY2, + required this.id, + required this.imageHeight, + required this.imageWidth, + this.sourceType, + }); + + /// Bounding box X1 coordinate + int boundingBoxX1; + + /// Bounding box X2 coordinate + int boundingBoxX2; + + /// Bounding box Y1 coordinate + int boundingBoxY1; + + /// Bounding box Y2 coordinate + int boundingBoxY2; + + /// Face ID + String id; + + /// Image height in pixels + int imageHeight; + + /// Image width in pixels + int imageWidth; + + /// Face detection source type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + SourceType? sourceType; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFaceWithoutPersonResponseDto && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxY2 == boundingBoxY2 && + other.id == id && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth && + other.sourceType == sourceType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (boundingBoxX1.hashCode) + + (boundingBoxX2.hashCode) + + (boundingBoxY1.hashCode) + + (boundingBoxY2.hashCode) + + (id.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode) + + (sourceType == null ? 0 : sourceType!.hashCode); + + @override + String toString() => 'AssetFaceWithoutPersonResponseDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, sourceType=$sourceType]'; + + Map toJson() { + final json = {}; + json[r'boundingBoxX1'] = this.boundingBoxX1; + json[r'boundingBoxX2'] = this.boundingBoxX2; + json[r'boundingBoxY1'] = this.boundingBoxY1; + json[r'boundingBoxY2'] = this.boundingBoxY2; + json[r'id'] = this.id; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + if (this.sourceType != null) { + json[r'sourceType'] = this.sourceType; + } else { + // json[r'sourceType'] = null; + } + return json; + } + + /// Returns a new [AssetFaceWithoutPersonResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFaceWithoutPersonResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFaceWithoutPersonResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFaceWithoutPersonResponseDto( + boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, + boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, + boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, + boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, + id: mapValueOfType(json, r'id')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + sourceType: SourceType.fromJson(json[r'sourceType']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFaceWithoutPersonResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFaceWithoutPersonResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFaceWithoutPersonResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFaceWithoutPersonResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'boundingBoxX1', + 'boundingBoxX2', + 'boundingBoxY1', + 'boundingBoxY2', + 'id', + 'imageHeight', + 'imageWidth', + }; +} + diff --git a/mobile/openapi/lib/model/asset_full_sync_dto.dart b/mobile/openapi/lib/model/asset_full_sync_dto.dart new file mode 100644 index 0000000000..3fabb1cac6 --- /dev/null +++ b/mobile/openapi/lib/model/asset_full_sync_dto.dart @@ -0,0 +1,147 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetFullSyncDto { + /// Returns a new [AssetFullSyncDto] instance. + AssetFullSyncDto({ + this.lastId, + required this.limit, + required this.updatedUntil, + this.userId, + }); + + /// Last asset ID (pagination) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? lastId; + + /// Maximum number of assets to return + /// + /// Minimum value: 1 + int limit; + + /// Sync assets updated until this date + DateTime updatedUntil; + + /// Filter by user ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetFullSyncDto && + other.lastId == lastId && + other.limit == limit && + other.updatedUntil == updatedUntil && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (lastId == null ? 0 : lastId!.hashCode) + + (limit.hashCode) + + (updatedUntil.hashCode) + + (userId == null ? 0 : userId!.hashCode); + + @override + String toString() => 'AssetFullSyncDto[lastId=$lastId, limit=$limit, updatedUntil=$updatedUntil, userId=$userId]'; + + Map toJson() { + final json = {}; + if (this.lastId != null) { + json[r'lastId'] = this.lastId; + } else { + // json[r'lastId'] = null; + } + json[r'limit'] = this.limit; + json[r'updatedUntil'] = this.updatedUntil.toUtc().toIso8601String(); + if (this.userId != null) { + json[r'userId'] = this.userId; + } else { + // json[r'userId'] = null; + } + return json; + } + + /// Returns a new [AssetFullSyncDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetFullSyncDto? fromJson(dynamic value) { + upgradeDto(value, "AssetFullSyncDto"); + if (value is Map) { + final json = value.cast(); + + return AssetFullSyncDto( + lastId: mapValueOfType(json, r'lastId'), + limit: mapValueOfType(json, r'limit')!, + updatedUntil: mapDateTime(json, r'updatedUntil', r'')!, + userId: mapValueOfType(json, r'userId'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetFullSyncDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetFullSyncDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetFullSyncDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetFullSyncDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'limit', + 'updatedUntil', + }; +} + diff --git a/mobile/openapi/lib/model/asset_ids_dto.dart b/mobile/openapi/lib/model/asset_ids_dto.dart new file mode 100644 index 0000000000..85e5cc3aee --- /dev/null +++ b/mobile/openapi/lib/model/asset_ids_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetIdsDto { + /// Returns a new [AssetIdsDto] instance. + AssetIdsDto({ + this.assetIds = const [], + }); + + /// Asset IDs + List assetIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetIdsDto && + _deepEquality.equals(other.assetIds, assetIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode); + + @override + String toString() => 'AssetIdsDto[assetIds=$assetIds]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + return json; + } + + /// Returns a new [AssetIdsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetIdsDto? fromJson(dynamic value) { + upgradeDto(value, "AssetIdsDto"); + if (value is Map) { + final json = value.cast(); + + return AssetIdsDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetIdsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetIdsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetIdsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetIdsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + }; +} + diff --git a/mobile/openapi/lib/model/asset_ids_response_dto.dart b/mobile/openapi/lib/model/asset_ids_response_dto.dart new file mode 100644 index 0000000000..9745283021 --- /dev/null +++ b/mobile/openapi/lib/model/asset_ids_response_dto.dart @@ -0,0 +1,198 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetIdsResponseDto { + /// Returns a new [AssetIdsResponseDto] instance. + AssetIdsResponseDto({ + required this.assetId, + this.error, + required this.success, + }); + + /// Asset ID + String assetId; + + /// Error reason if failed + AssetIdsResponseDtoErrorEnum? error; + + /// Whether operation succeeded + bool success; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetIdsResponseDto && + other.assetId == assetId && + other.error == error && + other.success == success; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (error == null ? 0 : error!.hashCode) + + (success.hashCode); + + @override + String toString() => 'AssetIdsResponseDto[assetId=$assetId, error=$error, success=$success]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + if (this.error != null) { + json[r'error'] = this.error; + } else { + // json[r'error'] = null; + } + json[r'success'] = this.success; + return json; + } + + /// Returns a new [AssetIdsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetIdsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetIdsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetIdsResponseDto( + assetId: mapValueOfType(json, r'assetId')!, + error: AssetIdsResponseDtoErrorEnum.fromJson(json[r'error']), + success: mapValueOfType(json, r'success')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetIdsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetIdsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetIdsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetIdsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'success', + }; +} + +/// Error reason if failed +class AssetIdsResponseDtoErrorEnum { + /// Instantiate a new enum with the provided [value]. + const AssetIdsResponseDtoErrorEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const duplicate = AssetIdsResponseDtoErrorEnum._(r'duplicate'); + static const noPermission = AssetIdsResponseDtoErrorEnum._(r'no_permission'); + static const notFound = AssetIdsResponseDtoErrorEnum._(r'not_found'); + + /// List of all possible values in this [enum][AssetIdsResponseDtoErrorEnum]. + static const values = [ + duplicate, + noPermission, + notFound, + ]; + + static AssetIdsResponseDtoErrorEnum? fromJson(dynamic value) => AssetIdsResponseDtoErrorEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetIdsResponseDtoErrorEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetIdsResponseDtoErrorEnum] to String, +/// and [decode] dynamic data back to [AssetIdsResponseDtoErrorEnum]. +class AssetIdsResponseDtoErrorEnumTypeTransformer { + factory AssetIdsResponseDtoErrorEnumTypeTransformer() => _instance ??= const AssetIdsResponseDtoErrorEnumTypeTransformer._(); + + const AssetIdsResponseDtoErrorEnumTypeTransformer._(); + + String encode(AssetIdsResponseDtoErrorEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetIdsResponseDtoErrorEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetIdsResponseDtoErrorEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'duplicate': return AssetIdsResponseDtoErrorEnum.duplicate; + case r'no_permission': return AssetIdsResponseDtoErrorEnum.noPermission; + case r'not_found': return AssetIdsResponseDtoErrorEnum.notFound; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetIdsResponseDtoErrorEnumTypeTransformer] instance. + static AssetIdsResponseDtoErrorEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/asset_job_name.dart b/mobile/openapi/lib/model/asset_job_name.dart new file mode 100644 index 0000000000..7625677bb5 --- /dev/null +++ b/mobile/openapi/lib/model/asset_job_name.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Job name +class AssetJobName { + /// Instantiate a new enum with the provided [value]. + const AssetJobName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const refreshFaces = AssetJobName._(r'refresh-faces'); + static const refreshMetadata = AssetJobName._(r'refresh-metadata'); + static const regenerateThumbnail = AssetJobName._(r'regenerate-thumbnail'); + static const transcodeVideo = AssetJobName._(r'transcode-video'); + + /// List of all possible values in this [enum][AssetJobName]. + static const values = [ + refreshFaces, + refreshMetadata, + regenerateThumbnail, + transcodeVideo, + ]; + + static AssetJobName? fromJson(dynamic value) => AssetJobNameTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetJobName] to String, +/// and [decode] dynamic data back to [AssetJobName]. +class AssetJobNameTypeTransformer { + factory AssetJobNameTypeTransformer() => _instance ??= const AssetJobNameTypeTransformer._(); + + const AssetJobNameTypeTransformer._(); + + String encode(AssetJobName data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetJobName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetJobName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'refresh-faces': return AssetJobName.refreshFaces; + case r'refresh-metadata': return AssetJobName.refreshMetadata; + case r'regenerate-thumbnail': return AssetJobName.regenerateThumbnail; + case r'transcode-video': return AssetJobName.transcodeVideo; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetJobNameTypeTransformer] instance. + static AssetJobNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_jobs_dto.dart b/mobile/openapi/lib/model/asset_jobs_dto.dart new file mode 100644 index 0000000000..0aa5544a3a --- /dev/null +++ b/mobile/openapi/lib/model/asset_jobs_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetJobsDto { + /// Returns a new [AssetJobsDto] instance. + AssetJobsDto({ + this.assetIds = const [], + required this.name, + }); + + /// Asset IDs + List assetIds; + + /// Job name + AssetJobName name; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetJobsDto && + _deepEquality.equals(other.assetIds, assetIds) && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (name.hashCode); + + @override + String toString() => 'AssetJobsDto[assetIds=$assetIds, name=$name]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [AssetJobsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetJobsDto? fromJson(dynamic value) { + upgradeDto(value, "AssetJobsDto"); + if (value is Map) { + final json = value.cast(); + + return AssetJobsDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + name: AssetJobName.fromJson(json[r'name'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetJobsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetJobsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetJobsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetJobsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/asset_media_response_dto.dart b/mobile/openapi/lib/model/asset_media_response_dto.dart new file mode 100644 index 0000000000..905e738b6e --- /dev/null +++ b/mobile/openapi/lib/model/asset_media_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMediaResponseDto { + /// Returns a new [AssetMediaResponseDto] instance. + AssetMediaResponseDto({ + required this.id, + required this.status, + }); + + /// Asset media ID + String id; + + /// Upload status + AssetMediaStatus status; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMediaResponseDto && + other.id == id && + other.status == status; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode) + + (status.hashCode); + + @override + String toString() => 'AssetMediaResponseDto[id=$id, status=$status]'; + + Map toJson() { + final json = {}; + json[r'id'] = this.id; + json[r'status'] = this.status; + return json; + } + + /// Returns a new [AssetMediaResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMediaResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMediaResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMediaResponseDto( + id: mapValueOfType(json, r'id')!, + status: AssetMediaStatus.fromJson(json[r'status'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMediaResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMediaResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMediaResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMediaResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'status', + }; +} + diff --git a/mobile/openapi/lib/model/asset_media_size.dart b/mobile/openapi/lib/model/asset_media_size.dart new file mode 100644 index 0000000000..087d19da1f --- /dev/null +++ b/mobile/openapi/lib/model/asset_media_size.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class AssetMediaSize { + /// Instantiate a new enum with the provided [value]. + const AssetMediaSize._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const original = AssetMediaSize._(r'original'); + static const fullsize = AssetMediaSize._(r'fullsize'); + static const preview = AssetMediaSize._(r'preview'); + static const thumbnail = AssetMediaSize._(r'thumbnail'); + + /// List of all possible values in this [enum][AssetMediaSize]. + static const values = [ + original, + fullsize, + preview, + thumbnail, + ]; + + static AssetMediaSize? fromJson(dynamic value) => AssetMediaSizeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMediaSize.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetMediaSize] to String, +/// and [decode] dynamic data back to [AssetMediaSize]. +class AssetMediaSizeTypeTransformer { + factory AssetMediaSizeTypeTransformer() => _instance ??= const AssetMediaSizeTypeTransformer._(); + + const AssetMediaSizeTypeTransformer._(); + + String encode(AssetMediaSize data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetMediaSize. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetMediaSize? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'original': return AssetMediaSize.original; + case r'fullsize': return AssetMediaSize.fullsize; + case r'preview': return AssetMediaSize.preview; + case r'thumbnail': return AssetMediaSize.thumbnail; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetMediaSizeTypeTransformer] instance. + static AssetMediaSizeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_media_status.dart b/mobile/openapi/lib/model/asset_media_status.dart new file mode 100644 index 0000000000..b45918e5c3 --- /dev/null +++ b/mobile/openapi/lib/model/asset_media_status.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Upload status +class AssetMediaStatus { + /// Instantiate a new enum with the provided [value]. + const AssetMediaStatus._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const created = AssetMediaStatus._(r'created'); + static const replaced = AssetMediaStatus._(r'replaced'); + static const duplicate = AssetMediaStatus._(r'duplicate'); + + /// List of all possible values in this [enum][AssetMediaStatus]. + static const values = [ + created, + replaced, + duplicate, + ]; + + static AssetMediaStatus? fromJson(dynamic value) => AssetMediaStatusTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMediaStatus.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetMediaStatus] to String, +/// and [decode] dynamic data back to [AssetMediaStatus]. +class AssetMediaStatusTypeTransformer { + factory AssetMediaStatusTypeTransformer() => _instance ??= const AssetMediaStatusTypeTransformer._(); + + const AssetMediaStatusTypeTransformer._(); + + String encode(AssetMediaStatus data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetMediaStatus. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetMediaStatus? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'created': return AssetMediaStatus.created; + case r'replaced': return AssetMediaStatus.replaced; + case r'duplicate': return AssetMediaStatus.duplicate; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetMediaStatusTypeTransformer] instance. + static AssetMediaStatusTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_delete_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_delete_dto.dart new file mode 100644 index 0000000000..6376ebc531 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_bulk_delete_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataBulkDeleteDto { + /// Returns a new [AssetMetadataBulkDeleteDto] instance. + AssetMetadataBulkDeleteDto({ + this.items = const [], + }); + + /// Metadata items to delete + List items; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteDto && + _deepEquality.equals(other.items, items); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (items.hashCode); + + @override + String toString() => 'AssetMetadataBulkDeleteDto[items=$items]'; + + Map toJson() { + final json = {}; + json[r'items'] = this.items; + return json; + } + + /// Returns a new [AssetMetadataBulkDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataBulkDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataBulkDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataBulkDeleteDto( + items: AssetMetadataBulkDeleteItemDto.listFromJson(json[r'items']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataBulkDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataBulkDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataBulkDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataBulkDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'items', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_delete_item_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_delete_item_dto.dart new file mode 100644 index 0000000000..90417b79e0 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_bulk_delete_item_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataBulkDeleteItemDto { + /// Returns a new [AssetMetadataBulkDeleteItemDto] instance. + AssetMetadataBulkDeleteItemDto({ + required this.assetId, + required this.key, + }); + + /// Asset ID + String assetId; + + /// Metadata key + String key; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkDeleteItemDto && + other.assetId == assetId && + other.key == key; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (key.hashCode); + + @override + String toString() => 'AssetMetadataBulkDeleteItemDto[assetId=$assetId, key=$key]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'key'] = this.key; + return json; + } + + /// Returns a new [AssetMetadataBulkDeleteItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataBulkDeleteItemDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataBulkDeleteItemDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataBulkDeleteItemDto( + assetId: mapValueOfType(json, r'assetId')!, + key: mapValueOfType(json, r'key')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataBulkDeleteItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataBulkDeleteItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataBulkDeleteItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataBulkDeleteItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'key', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart new file mode 100644 index 0000000000..b79a693726 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_bulk_response_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataBulkResponseDto { + /// Returns a new [AssetMetadataBulkResponseDto] instance. + AssetMetadataBulkResponseDto({ + required this.assetId, + required this.key, + required this.updatedAt, + required this.value, + }); + + /// Asset ID + String assetId; + + /// Metadata key + String key; + + /// Last update date + DateTime updatedAt; + + /// Metadata value (object) + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkResponseDto && + other.assetId == assetId && + other.key == key && + other.updatedAt == updatedAt && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (key.hashCode) + + (updatedAt.hashCode) + + (value.hashCode); + + @override + String toString() => 'AssetMetadataBulkResponseDto[assetId=$assetId, key=$key, updatedAt=$updatedAt, value=$value]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'key'] = this.key; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + json[r'value'] = this.value; + return json; + } + + /// Returns a new [AssetMetadataBulkResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataBulkResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataBulkResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataBulkResponseDto( + assetId: mapValueOfType(json, r'assetId')!, + key: mapValueOfType(json, r'key')!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataBulkResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataBulkResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataBulkResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataBulkResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'key', + 'updatedAt', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_upsert_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_upsert_dto.dart new file mode 100644 index 0000000000..a5e770b02a --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_bulk_upsert_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataBulkUpsertDto { + /// Returns a new [AssetMetadataBulkUpsertDto] instance. + AssetMetadataBulkUpsertDto({ + this.items = const [], + }); + + /// Metadata items to upsert + List items; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertDto && + _deepEquality.equals(other.items, items); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (items.hashCode); + + @override + String toString() => 'AssetMetadataBulkUpsertDto[items=$items]'; + + Map toJson() { + final json = {}; + json[r'items'] = this.items; + return json; + } + + /// Returns a new [AssetMetadataBulkUpsertDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataBulkUpsertDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataBulkUpsertDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataBulkUpsertDto( + items: AssetMetadataBulkUpsertItemDto.listFromJson(json[r'items']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataBulkUpsertDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataBulkUpsertDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataBulkUpsertDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataBulkUpsertDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'items', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_bulk_upsert_item_dto.dart b/mobile/openapi/lib/model/asset_metadata_bulk_upsert_item_dto.dart new file mode 100644 index 0000000000..caaf379b30 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_bulk_upsert_item_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataBulkUpsertItemDto { + /// Returns a new [AssetMetadataBulkUpsertItemDto] instance. + AssetMetadataBulkUpsertItemDto({ + required this.assetId, + required this.key, + required this.value, + }); + + /// Asset ID + String assetId; + + /// Metadata key + String key; + + /// Metadata value (object) + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataBulkUpsertItemDto && + other.assetId == assetId && + other.key == key && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (key.hashCode) + + (value.hashCode); + + @override + String toString() => 'AssetMetadataBulkUpsertItemDto[assetId=$assetId, key=$key, value=$value]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'key'] = this.key; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [AssetMetadataBulkUpsertItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataBulkUpsertItemDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataBulkUpsertItemDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataBulkUpsertItemDto( + assetId: mapValueOfType(json, r'assetId')!, + key: mapValueOfType(json, r'key')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataBulkUpsertItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataBulkUpsertItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataBulkUpsertItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataBulkUpsertItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'key', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_response_dto.dart b/mobile/openapi/lib/model/asset_metadata_response_dto.dart new file mode 100644 index 0000000000..2c3faab178 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataResponseDto { + /// Returns a new [AssetMetadataResponseDto] instance. + AssetMetadataResponseDto({ + required this.key, + required this.updatedAt, + required this.value, + }); + + /// Metadata key + String key; + + /// Last update date + DateTime updatedAt; + + /// Metadata value (object) + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataResponseDto && + other.key == key && + other.updatedAt == updatedAt && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (key.hashCode) + + (updatedAt.hashCode) + + (value.hashCode); + + @override + String toString() => 'AssetMetadataResponseDto[key=$key, updatedAt=$updatedAt, value=$value]'; + + Map toJson() { + final json = {}; + json[r'key'] = this.key; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + json[r'value'] = this.value; + return json; + } + + /// Returns a new [AssetMetadataResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataResponseDto( + key: mapValueOfType(json, r'key')!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'key', + 'updatedAt', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_upsert_dto.dart b/mobile/openapi/lib/model/asset_metadata_upsert_dto.dart new file mode 100644 index 0000000000..b1473d4826 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_upsert_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataUpsertDto { + /// Returns a new [AssetMetadataUpsertDto] instance. + AssetMetadataUpsertDto({ + this.items = const [], + }); + + /// Metadata items to upsert + List items; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataUpsertDto && + _deepEquality.equals(other.items, items); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (items.hashCode); + + @override + String toString() => 'AssetMetadataUpsertDto[items=$items]'; + + Map toJson() { + final json = {}; + json[r'items'] = this.items; + return json; + } + + /// Returns a new [AssetMetadataUpsertDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataUpsertDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataUpsertDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataUpsertDto( + items: AssetMetadataUpsertItemDto.listFromJson(json[r'items']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataUpsertDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataUpsertDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataUpsertDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataUpsertDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'items', + }; +} + diff --git a/mobile/openapi/lib/model/asset_metadata_upsert_item_dto.dart b/mobile/openapi/lib/model/asset_metadata_upsert_item_dto.dart new file mode 100644 index 0000000000..8a6bcb9b01 --- /dev/null +++ b/mobile/openapi/lib/model/asset_metadata_upsert_item_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetMetadataUpsertItemDto { + /// Returns a new [AssetMetadataUpsertItemDto] instance. + AssetMetadataUpsertItemDto({ + required this.key, + required this.value, + }); + + /// Metadata key + String key; + + /// Metadata value (object) + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetMetadataUpsertItemDto && + other.key == key && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (key.hashCode) + + (value.hashCode); + + @override + String toString() => 'AssetMetadataUpsertItemDto[key=$key, value=$value]'; + + Map toJson() { + final json = {}; + json[r'key'] = this.key; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [AssetMetadataUpsertItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetMetadataUpsertItemDto? fromJson(dynamic value) { + upgradeDto(value, "AssetMetadataUpsertItemDto"); + if (value is Map) { + final json = value.cast(); + + return AssetMetadataUpsertItemDto( + key: mapValueOfType(json, r'key')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetMetadataUpsertItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetMetadataUpsertItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetMetadataUpsertItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetMetadataUpsertItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'key', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/asset_ocr_response_dto.dart b/mobile/openapi/lib/model/asset_ocr_response_dto.dart new file mode 100644 index 0000000000..c7937c6eb2 --- /dev/null +++ b/mobile/openapi/lib/model/asset_ocr_response_dto.dart @@ -0,0 +1,206 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetOcrResponseDto { + /// Returns a new [AssetOcrResponseDto] instance. + AssetOcrResponseDto({ + required this.assetId, + required this.boxScore, + required this.id, + required this.text, + required this.textScore, + required this.x1, + required this.x2, + required this.x3, + required this.x4, + required this.y1, + required this.y2, + required this.y3, + required this.y4, + }); + + String assetId; + + /// Confidence score for text detection box + double boxScore; + + String id; + + /// Recognized text + String text; + + /// Confidence score for text recognition + double textScore; + + /// Normalized x coordinate of box corner 1 (0-1) + double x1; + + /// Normalized x coordinate of box corner 2 (0-1) + double x2; + + /// Normalized x coordinate of box corner 3 (0-1) + double x3; + + /// Normalized x coordinate of box corner 4 (0-1) + double x4; + + /// Normalized y coordinate of box corner 1 (0-1) + double y1; + + /// Normalized y coordinate of box corner 2 (0-1) + double y2; + + /// Normalized y coordinate of box corner 3 (0-1) + double y3; + + /// Normalized y coordinate of box corner 4 (0-1) + double y4; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetOcrResponseDto && + other.assetId == assetId && + other.boxScore == boxScore && + other.id == id && + other.text == text && + other.textScore == textScore && + other.x1 == x1 && + other.x2 == x2 && + other.x3 == x3 && + other.x4 == x4 && + other.y1 == y1 && + other.y2 == y2 && + other.y3 == y3 && + other.y4 == y4; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (boxScore.hashCode) + + (id.hashCode) + + (text.hashCode) + + (textScore.hashCode) + + (x1.hashCode) + + (x2.hashCode) + + (x3.hashCode) + + (x4.hashCode) + + (y1.hashCode) + + (y2.hashCode) + + (y3.hashCode) + + (y4.hashCode); + + @override + String toString() => 'AssetOcrResponseDto[assetId=$assetId, boxScore=$boxScore, id=$id, text=$text, textScore=$textScore, x1=$x1, x2=$x2, x3=$x3, x4=$x4, y1=$y1, y2=$y2, y3=$y3, y4=$y4]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'boxScore'] = this.boxScore; + json[r'id'] = this.id; + json[r'text'] = this.text; + json[r'textScore'] = this.textScore; + json[r'x1'] = this.x1; + json[r'x2'] = this.x2; + json[r'x3'] = this.x3; + json[r'x4'] = this.x4; + json[r'y1'] = this.y1; + json[r'y2'] = this.y2; + json[r'y3'] = this.y3; + json[r'y4'] = this.y4; + return json; + } + + /// Returns a new [AssetOcrResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetOcrResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetOcrResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetOcrResponseDto( + assetId: mapValueOfType(json, r'assetId')!, + boxScore: (mapValueOfType(json, r'boxScore')!).toDouble(), + id: mapValueOfType(json, r'id')!, + text: mapValueOfType(json, r'text')!, + textScore: (mapValueOfType(json, r'textScore')!).toDouble(), + x1: (mapValueOfType(json, r'x1')!).toDouble(), + x2: (mapValueOfType(json, r'x2')!).toDouble(), + x3: (mapValueOfType(json, r'x3')!).toDouble(), + x4: (mapValueOfType(json, r'x4')!).toDouble(), + y1: (mapValueOfType(json, r'y1')!).toDouble(), + y2: (mapValueOfType(json, r'y2')!).toDouble(), + y3: (mapValueOfType(json, r'y3')!).toDouble(), + y4: (mapValueOfType(json, r'y4')!).toDouble(), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetOcrResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetOcrResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetOcrResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetOcrResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'boxScore', + 'id', + 'text', + 'textScore', + 'x1', + 'x2', + 'x3', + 'x4', + 'y1', + 'y2', + 'y3', + 'y4', + }; +} + diff --git a/mobile/openapi/lib/model/asset_order.dart b/mobile/openapi/lib/model/asset_order.dart new file mode 100644 index 0000000000..21edd95ff6 --- /dev/null +++ b/mobile/openapi/lib/model/asset_order.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Asset sort order +class AssetOrder { + /// Instantiate a new enum with the provided [value]. + const AssetOrder._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const asc = AssetOrder._(r'asc'); + static const desc = AssetOrder._(r'desc'); + + /// List of all possible values in this [enum][AssetOrder]. + static const values = [ + asc, + desc, + ]; + + static AssetOrder? fromJson(dynamic value) => AssetOrderTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetOrder.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetOrder] to String, +/// and [decode] dynamic data back to [AssetOrder]. +class AssetOrderTypeTransformer { + factory AssetOrderTypeTransformer() => _instance ??= const AssetOrderTypeTransformer._(); + + const AssetOrderTypeTransformer._(); + + String encode(AssetOrder data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetOrder. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetOrder? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'asc': return AssetOrder.asc; + case r'desc': return AssetOrder.desc; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetOrderTypeTransformer] instance. + static AssetOrderTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart new file mode 100644 index 0000000000..078dd0bdaf --- /dev/null +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -0,0 +1,461 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetResponseDto { + /// Returns a new [AssetResponseDto] instance. + AssetResponseDto({ + required this.checksum, + required this.createdAt, + required this.deviceAssetId, + required this.deviceId, + this.duplicateId, + required this.duration, + this.exifInfo, + required this.fileCreatedAt, + required this.fileModifiedAt, + required this.hasMetadata, + required this.height, + required this.id, + required this.isArchived, + required this.isEdited, + required this.isFavorite, + required this.isOffline, + required this.isTrashed, + this.libraryId, + this.livePhotoVideoId, + required this.localDateTime, + required this.originalFileName, + this.originalMimeType, + required this.originalPath, + this.owner, + required this.ownerId, + this.people = const [], + this.resized, + this.stack, + this.tags = const [], + required this.thumbhash, + required this.type, + this.unassignedFaces = const [], + required this.updatedAt, + required this.visibility, + required this.width, + }); + + /// Base64 encoded SHA1 hash + String checksum; + + /// The UTC timestamp when the asset was originally uploaded to Immich. + DateTime createdAt; + + /// Device asset ID + String deviceAssetId; + + /// Device ID + String deviceId; + + /// Duplicate group ID + String? duplicateId; + + /// Video duration (for videos) + String duration; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + ExifResponseDto? exifInfo; + + /// The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. + DateTime fileCreatedAt; + + /// The UTC timestamp when the file was last modified on the filesystem. This reflects the last time the physical file was changed, which may be different from when the photo was originally taken. + DateTime fileModifiedAt; + + /// Whether asset has metadata + bool hasMetadata; + + /// Asset height + num? height; + + /// Asset ID + String id; + + /// Is archived + bool isArchived; + + /// Is edited + bool isEdited; + + /// Is favorite + bool isFavorite; + + /// Is offline + bool isOffline; + + /// Is trashed + bool isTrashed; + + /// Library ID + String? libraryId; + + /// Live photo video ID + String? livePhotoVideoId; + + /// The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer's local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by \"local\" days and months. + DateTime localDateTime; + + /// Original file name + String originalFileName; + + /// Original MIME type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? originalMimeType; + + /// Original file path + String originalPath; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + UserResponseDto? owner; + + /// Owner user ID + String ownerId; + + List people; + + /// Is resized + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? resized; + + AssetStackResponseDto? stack; + + List tags; + + /// Thumbhash for thumbnail generation (base64) also used as the c query param for thumbnail cache busting. + String? thumbhash; + + /// Asset type + AssetTypeEnum type; + + List unassignedFaces; + + /// The UTC timestamp when the asset record was last updated in the database. This is automatically maintained by the database and reflects when any field in the asset was last modified. + DateTime updatedAt; + + /// Asset visibility + AssetVisibility visibility; + + /// Asset width + num? width; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto && + other.checksum == checksum && + other.createdAt == createdAt && + other.deviceAssetId == deviceAssetId && + other.deviceId == deviceId && + other.duplicateId == duplicateId && + other.duration == duration && + other.exifInfo == exifInfo && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.hasMetadata == hasMetadata && + other.height == height && + other.id == id && + other.isArchived == isArchived && + other.isEdited == isEdited && + other.isFavorite == isFavorite && + other.isOffline == isOffline && + other.isTrashed == isTrashed && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.localDateTime == localDateTime && + other.originalFileName == originalFileName && + other.originalMimeType == originalMimeType && + other.originalPath == originalPath && + other.owner == owner && + other.ownerId == ownerId && + _deepEquality.equals(other.people, people) && + other.resized == resized && + other.stack == stack && + _deepEquality.equals(other.tags, tags) && + other.thumbhash == thumbhash && + other.type == type && + _deepEquality.equals(other.unassignedFaces, unassignedFaces) && + other.updatedAt == updatedAt && + other.visibility == visibility && + other.width == width; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksum.hashCode) + + (createdAt.hashCode) + + (deviceAssetId.hashCode) + + (deviceId.hashCode) + + (duplicateId == null ? 0 : duplicateId!.hashCode) + + (duration.hashCode) + + (exifInfo == null ? 0 : exifInfo!.hashCode) + + (fileCreatedAt.hashCode) + + (fileModifiedAt.hashCode) + + (hasMetadata.hashCode) + + (height == null ? 0 : height!.hashCode) + + (id.hashCode) + + (isArchived.hashCode) + + (isEdited.hashCode) + + (isFavorite.hashCode) + + (isOffline.hashCode) + + (isTrashed.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (localDateTime.hashCode) + + (originalFileName.hashCode) + + (originalMimeType == null ? 0 : originalMimeType!.hashCode) + + (originalPath.hashCode) + + (owner == null ? 0 : owner!.hashCode) + + (ownerId.hashCode) + + (people.hashCode) + + (resized == null ? 0 : resized!.hashCode) + + (stack == null ? 0 : stack!.hashCode) + + (tags.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode) + + (unassignedFaces.hashCode) + + (updatedAt.hashCode) + + (visibility.hashCode) + + (width == null ? 0 : width!.hashCode); + + @override + String toString() => 'AssetResponseDto[checksum=$checksum, createdAt=$createdAt, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, height=$height, id=$id, isArchived=$isArchived, isEdited=$isEdited, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, visibility=$visibility, width=$width]'; + + Map toJson() { + final json = {}; + json[r'checksum'] = this.checksum; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'deviceAssetId'] = this.deviceAssetId; + json[r'deviceId'] = this.deviceId; + if (this.duplicateId != null) { + json[r'duplicateId'] = this.duplicateId; + } else { + // json[r'duplicateId'] = null; + } + json[r'duration'] = this.duration; + if (this.exifInfo != null) { + json[r'exifInfo'] = this.exifInfo; + } else { + // json[r'exifInfo'] = null; + } + json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String(); + json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String(); + json[r'hasMetadata'] = this.hasMetadata; + if (this.height != null) { + json[r'height'] = this.height; + } else { + // json[r'height'] = null; + } + json[r'id'] = this.id; + json[r'isArchived'] = this.isArchived; + json[r'isEdited'] = this.isEdited; + json[r'isFavorite'] = this.isFavorite; + json[r'isOffline'] = this.isOffline; + json[r'isTrashed'] = this.isTrashed; + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.livePhotoVideoId != null) { + json[r'livePhotoVideoId'] = this.livePhotoVideoId; + } else { + // json[r'livePhotoVideoId'] = null; + } + json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String(); + json[r'originalFileName'] = this.originalFileName; + if (this.originalMimeType != null) { + json[r'originalMimeType'] = this.originalMimeType; + } else { + // json[r'originalMimeType'] = null; + } + json[r'originalPath'] = this.originalPath; + if (this.owner != null) { + json[r'owner'] = this.owner; + } else { + // json[r'owner'] = null; + } + json[r'ownerId'] = this.ownerId; + json[r'people'] = this.people; + if (this.resized != null) { + json[r'resized'] = this.resized; + } else { + // json[r'resized'] = null; + } + if (this.stack != null) { + json[r'stack'] = this.stack; + } else { + // json[r'stack'] = null; + } + json[r'tags'] = this.tags; + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + json[r'unassignedFaces'] = this.unassignedFaces; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + json[r'visibility'] = this.visibility; + if (this.width != null) { + json[r'width'] = this.width; + } else { + // json[r'width'] = null; + } + return json; + } + + /// Returns a new [AssetResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetResponseDto( + checksum: mapValueOfType(json, r'checksum')!, + createdAt: mapDateTime(json, r'createdAt', r'')!, + deviceAssetId: mapValueOfType(json, r'deviceAssetId')!, + deviceId: mapValueOfType(json, r'deviceId')!, + duplicateId: mapValueOfType(json, r'duplicateId'), + duration: mapValueOfType(json, r'duration')!, + exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!, + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!, + hasMetadata: mapValueOfType(json, r'hasMetadata')!, + height: json[r'height'] == null + ? null + : num.parse('${json[r'height']}'), + id: mapValueOfType(json, r'id')!, + isArchived: mapValueOfType(json, r'isArchived')!, + isEdited: mapValueOfType(json, r'isEdited')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isOffline: mapValueOfType(json, r'isOffline')!, + isTrashed: mapValueOfType(json, r'isTrashed')!, + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + localDateTime: mapDateTime(json, r'localDateTime', r'')!, + originalFileName: mapValueOfType(json, r'originalFileName')!, + originalMimeType: mapValueOfType(json, r'originalMimeType'), + originalPath: mapValueOfType(json, r'originalPath')!, + owner: UserResponseDto.fromJson(json[r'owner']), + ownerId: mapValueOfType(json, r'ownerId')!, + people: PersonWithFacesResponseDto.listFromJson(json[r'people']), + resized: mapValueOfType(json, r'resized'), + stack: AssetStackResponseDto.fromJson(json[r'stack']), + tags: TagResponseDto.listFromJson(json[r'tags']), + thumbhash: mapValueOfType(json, r'thumbhash'), + type: AssetTypeEnum.fromJson(json[r'type'])!, + unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + visibility: AssetVisibility.fromJson(json[r'visibility'])!, + width: json[r'width'] == null + ? null + : num.parse('${json[r'width']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'createdAt', + 'deviceAssetId', + 'deviceId', + 'duration', + 'fileCreatedAt', + 'fileModifiedAt', + 'hasMetadata', + 'height', + 'id', + 'isArchived', + 'isEdited', + 'isFavorite', + 'isOffline', + 'isTrashed', + 'localDateTime', + 'originalFileName', + 'originalPath', + 'ownerId', + 'thumbhash', + 'type', + 'updatedAt', + 'visibility', + 'width', + }; +} + diff --git a/mobile/openapi/lib/model/asset_stack_response_dto.dart b/mobile/openapi/lib/model/asset_stack_response_dto.dart new file mode 100644 index 0000000000..229e7aa710 --- /dev/null +++ b/mobile/openapi/lib/model/asset_stack_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetStackResponseDto { + /// Returns a new [AssetStackResponseDto] instance. + AssetStackResponseDto({ + required this.assetCount, + required this.id, + required this.primaryAssetId, + }); + + /// Number of assets in stack + int assetCount; + + /// Stack ID + String id; + + /// Primary asset ID + String primaryAssetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetStackResponseDto && + other.assetCount == assetCount && + other.id == id && + other.primaryAssetId == primaryAssetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetCount.hashCode) + + (id.hashCode) + + (primaryAssetId.hashCode); + + @override + String toString() => 'AssetStackResponseDto[assetCount=$assetCount, id=$id, primaryAssetId=$primaryAssetId]'; + + Map toJson() { + final json = {}; + json[r'assetCount'] = this.assetCount; + json[r'id'] = this.id; + json[r'primaryAssetId'] = this.primaryAssetId; + return json; + } + + /// Returns a new [AssetStackResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetStackResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetStackResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetStackResponseDto( + assetCount: mapValueOfType(json, r'assetCount')!, + id: mapValueOfType(json, r'id')!, + primaryAssetId: mapValueOfType(json, r'primaryAssetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetStackResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetStackResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetStackResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetStackResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetCount', + 'id', + 'primaryAssetId', + }; +} + diff --git a/mobile/openapi/lib/model/asset_stats_response_dto.dart b/mobile/openapi/lib/model/asset_stats_response_dto.dart new file mode 100644 index 0000000000..201550c87f --- /dev/null +++ b/mobile/openapi/lib/model/asset_stats_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AssetStatsResponseDto { + /// Returns a new [AssetStatsResponseDto] instance. + AssetStatsResponseDto({ + required this.images, + required this.total, + required this.videos, + }); + + /// Number of images + int images; + + /// Total number of assets + int total; + + /// Number of videos + int videos; + + @override + bool operator ==(Object other) => identical(this, other) || other is AssetStatsResponseDto && + other.images == images && + other.total == total && + other.videos == videos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (images.hashCode) + + (total.hashCode) + + (videos.hashCode); + + @override + String toString() => 'AssetStatsResponseDto[images=$images, total=$total, videos=$videos]'; + + Map toJson() { + final json = {}; + json[r'images'] = this.images; + json[r'total'] = this.total; + json[r'videos'] = this.videos; + return json; + } + + /// Returns a new [AssetStatsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AssetStatsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AssetStatsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AssetStatsResponseDto( + images: mapValueOfType(json, r'images')!, + total: mapValueOfType(json, r'total')!, + videos: mapValueOfType(json, r'videos')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetStatsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AssetStatsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AssetStatsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AssetStatsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'images', + 'total', + 'videos', + }; +} + diff --git a/mobile/openapi/lib/model/asset_type_enum.dart b/mobile/openapi/lib/model/asset_type_enum.dart new file mode 100644 index 0000000000..b6e0351198 --- /dev/null +++ b/mobile/openapi/lib/model/asset_type_enum.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Asset type +class AssetTypeEnum { + /// Instantiate a new enum with the provided [value]. + const AssetTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = AssetTypeEnum._(r'IMAGE'); + static const VIDEO = AssetTypeEnum._(r'VIDEO'); + static const AUDIO = AssetTypeEnum._(r'AUDIO'); + static const OTHER = AssetTypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][AssetTypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static AssetTypeEnum? fromJson(dynamic value) => AssetTypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetTypeEnum] to String, +/// and [decode] dynamic data back to [AssetTypeEnum]. +class AssetTypeEnumTypeTransformer { + factory AssetTypeEnumTypeTransformer() => _instance ??= const AssetTypeEnumTypeTransformer._(); + + const AssetTypeEnumTypeTransformer._(); + + String encode(AssetTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetTypeEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'IMAGE': return AssetTypeEnum.IMAGE; + case r'VIDEO': return AssetTypeEnum.VIDEO; + case r'AUDIO': return AssetTypeEnum.AUDIO; + case r'OTHER': return AssetTypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetTypeEnumTypeTransformer] instance. + static AssetTypeEnumTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/asset_visibility.dart b/mobile/openapi/lib/model/asset_visibility.dart new file mode 100644 index 0000000000..6290dffb2e --- /dev/null +++ b/mobile/openapi/lib/model/asset_visibility.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Asset visibility +class AssetVisibility { + /// Instantiate a new enum with the provided [value]. + const AssetVisibility._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const archive = AssetVisibility._(r'archive'); + static const timeline = AssetVisibility._(r'timeline'); + static const hidden = AssetVisibility._(r'hidden'); + static const locked = AssetVisibility._(r'locked'); + + /// List of all possible values in this [enum][AssetVisibility]. + static const values = [ + archive, + timeline, + hidden, + locked, + ]; + + static AssetVisibility? fromJson(dynamic value) => AssetVisibilityTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AssetVisibility.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AssetVisibility] to String, +/// and [decode] dynamic data back to [AssetVisibility]. +class AssetVisibilityTypeTransformer { + factory AssetVisibilityTypeTransformer() => _instance ??= const AssetVisibilityTypeTransformer._(); + + const AssetVisibilityTypeTransformer._(); + + String encode(AssetVisibility data) => data.value; + + /// Decodes a [dynamic value][data] to a AssetVisibility. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AssetVisibility? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'archive': return AssetVisibility.archive; + case r'timeline': return AssetVisibility.timeline; + case r'hidden': return AssetVisibility.hidden; + case r'locked': return AssetVisibility.locked; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AssetVisibilityTypeTransformer] instance. + static AssetVisibilityTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/audio_codec.dart b/mobile/openapi/lib/model/audio_codec.dart new file mode 100644 index 0000000000..be1ff0dcb9 --- /dev/null +++ b/mobile/openapi/lib/model/audio_codec.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Target audio codec +class AudioCodec { + /// Instantiate a new enum with the provided [value]. + const AudioCodec._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const mp3 = AudioCodec._(r'mp3'); + static const aac = AudioCodec._(r'aac'); + static const libopus = AudioCodec._(r'libopus'); + static const opus = AudioCodec._(r'opus'); + static const pcmS16le = AudioCodec._(r'pcm_s16le'); + + /// List of all possible values in this [enum][AudioCodec]. + static const values = [ + mp3, + aac, + libopus, + opus, + pcmS16le, + ]; + + static AudioCodec? fromJson(dynamic value) => AudioCodecTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AudioCodec.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [AudioCodec] to String, +/// and [decode] dynamic data back to [AudioCodec]. +class AudioCodecTypeTransformer { + factory AudioCodecTypeTransformer() => _instance ??= const AudioCodecTypeTransformer._(); + + const AudioCodecTypeTransformer._(); + + String encode(AudioCodec data) => data.value; + + /// Decodes a [dynamic value][data] to a AudioCodec. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + AudioCodec? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'mp3': return AudioCodec.mp3; + case r'aac': return AudioCodec.aac; + case r'libopus': return AudioCodec.libopus; + case r'opus': return AudioCodec.opus; + case r'pcm_s16le': return AudioCodec.pcmS16le; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [AudioCodecTypeTransformer] instance. + static AudioCodecTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/auth_status_response_dto.dart b/mobile/openapi/lib/model/auth_status_response_dto.dart new file mode 100644 index 0000000000..23b9d40525 --- /dev/null +++ b/mobile/openapi/lib/model/auth_status_response_dto.dart @@ -0,0 +1,154 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AuthStatusResponseDto { + /// Returns a new [AuthStatusResponseDto] instance. + AuthStatusResponseDto({ + this.expiresAt, + required this.isElevated, + required this.password, + required this.pinCode, + this.pinExpiresAt, + }); + + /// Session expiration date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? expiresAt; + + /// Is elevated session + bool isElevated; + + /// Has password set + bool password; + + /// Has PIN code set + bool pinCode; + + /// PIN expiration date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? pinExpiresAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is AuthStatusResponseDto && + other.expiresAt == expiresAt && + other.isElevated == isElevated && + other.password == password && + other.pinCode == pinCode && + other.pinExpiresAt == pinExpiresAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (isElevated.hashCode) + + (password.hashCode) + + (pinCode.hashCode) + + (pinExpiresAt == null ? 0 : pinExpiresAt!.hashCode); + + @override + String toString() => 'AuthStatusResponseDto[expiresAt=$expiresAt, isElevated=$isElevated, password=$password, pinCode=$pinCode, pinExpiresAt=$pinExpiresAt]'; + + Map toJson() { + final json = {}; + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt; + } else { + // json[r'expiresAt'] = null; + } + json[r'isElevated'] = this.isElevated; + json[r'password'] = this.password; + json[r'pinCode'] = this.pinCode; + if (this.pinExpiresAt != null) { + json[r'pinExpiresAt'] = this.pinExpiresAt; + } else { + // json[r'pinExpiresAt'] = null; + } + return json; + } + + /// Returns a new [AuthStatusResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AuthStatusResponseDto? fromJson(dynamic value) { + upgradeDto(value, "AuthStatusResponseDto"); + if (value is Map) { + final json = value.cast(); + + return AuthStatusResponseDto( + expiresAt: mapValueOfType(json, r'expiresAt'), + isElevated: mapValueOfType(json, r'isElevated')!, + password: mapValueOfType(json, r'password')!, + pinCode: mapValueOfType(json, r'pinCode')!, + pinExpiresAt: mapValueOfType(json, r'pinExpiresAt'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AuthStatusResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AuthStatusResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AuthStatusResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AuthStatusResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isElevated', + 'password', + 'pinCode', + }; +} + diff --git a/mobile/openapi/lib/model/avatar_update.dart b/mobile/openapi/lib/model/avatar_update.dart new file mode 100644 index 0000000000..a817832dab --- /dev/null +++ b/mobile/openapi/lib/model/avatar_update.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class AvatarUpdate { + /// Returns a new [AvatarUpdate] instance. + AvatarUpdate({ + this.color, + }); + + /// Avatar color + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + UserAvatarColor? color; + + @override + bool operator ==(Object other) => identical(this, other) || other is AvatarUpdate && + other.color == color; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (color == null ? 0 : color!.hashCode); + + @override + String toString() => 'AvatarUpdate[color=$color]'; + + Map toJson() { + final json = {}; + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + return json; + } + + /// Returns a new [AvatarUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static AvatarUpdate? fromJson(dynamic value) { + upgradeDto(value, "AvatarUpdate"); + if (value is Map) { + final json = value.cast(); + + return AvatarUpdate( + color: UserAvatarColor.fromJson(json[r'color']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = AvatarUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = AvatarUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of AvatarUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = AvatarUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/bulk_id_error_reason.dart b/mobile/openapi/lib/model/bulk_id_error_reason.dart new file mode 100644 index 0000000000..fd6c61d6fd --- /dev/null +++ b/mobile/openapi/lib/model/bulk_id_error_reason.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Error reason +class BulkIdErrorReason { + /// Instantiate a new enum with the provided [value]. + const BulkIdErrorReason._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const duplicate = BulkIdErrorReason._(r'duplicate'); + static const noPermission = BulkIdErrorReason._(r'no_permission'); + static const notFound = BulkIdErrorReason._(r'not_found'); + static const unknown = BulkIdErrorReason._(r'unknown'); + static const validation = BulkIdErrorReason._(r'validation'); + + /// List of all possible values in this [enum][BulkIdErrorReason]. + static const values = [ + duplicate, + noPermission, + notFound, + unknown, + validation, + ]; + + static BulkIdErrorReason? fromJson(dynamic value) => BulkIdErrorReasonTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = BulkIdErrorReason.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [BulkIdErrorReason] to String, +/// and [decode] dynamic data back to [BulkIdErrorReason]. +class BulkIdErrorReasonTypeTransformer { + factory BulkIdErrorReasonTypeTransformer() => _instance ??= const BulkIdErrorReasonTypeTransformer._(); + + const BulkIdErrorReasonTypeTransformer._(); + + String encode(BulkIdErrorReason data) => data.value; + + /// Decodes a [dynamic value][data] to a BulkIdErrorReason. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + BulkIdErrorReason? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'duplicate': return BulkIdErrorReason.duplicate; + case r'no_permission': return BulkIdErrorReason.noPermission; + case r'not_found': return BulkIdErrorReason.notFound; + case r'unknown': return BulkIdErrorReason.unknown; + case r'validation': return BulkIdErrorReason.validation; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [BulkIdErrorReasonTypeTransformer] instance. + static BulkIdErrorReasonTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/bulk_id_response_dto.dart b/mobile/openapi/lib/model/bulk_id_response_dto.dart new file mode 100644 index 0000000000..1fa8536964 --- /dev/null +++ b/mobile/openapi/lib/model/bulk_id_response_dto.dart @@ -0,0 +1,221 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class BulkIdResponseDto { + /// Returns a new [BulkIdResponseDto] instance. + BulkIdResponseDto({ + this.error, + this.errorMessage, + required this.id, + required this.success, + }); + + /// Error reason if failed + BulkIdResponseDtoErrorEnum? error; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? errorMessage; + + /// ID + String id; + + /// Whether operation succeeded + bool success; + + @override + bool operator ==(Object other) => identical(this, other) || other is BulkIdResponseDto && + other.error == error && + other.errorMessage == errorMessage && + other.id == id && + other.success == success; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (error == null ? 0 : error!.hashCode) + + (errorMessage == null ? 0 : errorMessage!.hashCode) + + (id.hashCode) + + (success.hashCode); + + @override + String toString() => 'BulkIdResponseDto[error=$error, errorMessage=$errorMessage, id=$id, success=$success]'; + + Map toJson() { + final json = {}; + if (this.error != null) { + json[r'error'] = this.error; + } else { + // json[r'error'] = null; + } + if (this.errorMessage != null) { + json[r'errorMessage'] = this.errorMessage; + } else { + // json[r'errorMessage'] = null; + } + json[r'id'] = this.id; + json[r'success'] = this.success; + return json; + } + + /// Returns a new [BulkIdResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static BulkIdResponseDto? fromJson(dynamic value) { + upgradeDto(value, "BulkIdResponseDto"); + if (value is Map) { + final json = value.cast(); + + return BulkIdResponseDto( + error: BulkIdResponseDtoErrorEnum.fromJson(json[r'error']), + errorMessage: mapValueOfType(json, r'errorMessage'), + id: mapValueOfType(json, r'id')!, + success: mapValueOfType(json, r'success')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = BulkIdResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = BulkIdResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of BulkIdResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = BulkIdResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + 'success', + }; +} + +/// Error reason if failed +class BulkIdResponseDtoErrorEnum { + /// Instantiate a new enum with the provided [value]. + const BulkIdResponseDtoErrorEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const duplicate = BulkIdResponseDtoErrorEnum._(r'duplicate'); + static const noPermission = BulkIdResponseDtoErrorEnum._(r'no_permission'); + static const notFound = BulkIdResponseDtoErrorEnum._(r'not_found'); + static const unknown = BulkIdResponseDtoErrorEnum._(r'unknown'); + static const validation = BulkIdResponseDtoErrorEnum._(r'validation'); + + /// List of all possible values in this [enum][BulkIdResponseDtoErrorEnum]. + static const values = [ + duplicate, + noPermission, + notFound, + unknown, + validation, + ]; + + static BulkIdResponseDtoErrorEnum? fromJson(dynamic value) => BulkIdResponseDtoErrorEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = BulkIdResponseDtoErrorEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [BulkIdResponseDtoErrorEnum] to String, +/// and [decode] dynamic data back to [BulkIdResponseDtoErrorEnum]. +class BulkIdResponseDtoErrorEnumTypeTransformer { + factory BulkIdResponseDtoErrorEnumTypeTransformer() => _instance ??= const BulkIdResponseDtoErrorEnumTypeTransformer._(); + + const BulkIdResponseDtoErrorEnumTypeTransformer._(); + + String encode(BulkIdResponseDtoErrorEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a BulkIdResponseDtoErrorEnum. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + BulkIdResponseDtoErrorEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'duplicate': return BulkIdResponseDtoErrorEnum.duplicate; + case r'no_permission': return BulkIdResponseDtoErrorEnum.noPermission; + case r'not_found': return BulkIdResponseDtoErrorEnum.notFound; + case r'unknown': return BulkIdResponseDtoErrorEnum.unknown; + case r'validation': return BulkIdResponseDtoErrorEnum.validation; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [BulkIdResponseDtoErrorEnumTypeTransformer] instance. + static BulkIdResponseDtoErrorEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/bulk_ids_dto.dart b/mobile/openapi/lib/model/bulk_ids_dto.dart new file mode 100644 index 0000000000..7e7864a285 --- /dev/null +++ b/mobile/openapi/lib/model/bulk_ids_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class BulkIdsDto { + /// Returns a new [BulkIdsDto] instance. + BulkIdsDto({ + this.ids = const [], + }); + + /// IDs to process + List ids; + + @override + bool operator ==(Object other) => identical(this, other) || other is BulkIdsDto && + _deepEquality.equals(other.ids, ids); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ids.hashCode); + + @override + String toString() => 'BulkIdsDto[ids=$ids]'; + + Map toJson() { + final json = {}; + json[r'ids'] = this.ids; + return json; + } + + /// Returns a new [BulkIdsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static BulkIdsDto? fromJson(dynamic value) { + upgradeDto(value, "BulkIdsDto"); + if (value is Map) { + final json = value.cast(); + + return BulkIdsDto( + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = BulkIdsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = BulkIdsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of BulkIdsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = BulkIdsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/cast_response.dart b/mobile/openapi/lib/model/cast_response.dart new file mode 100644 index 0000000000..0b7f0738fe --- /dev/null +++ b/mobile/openapi/lib/model/cast_response.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CastResponse { + /// Returns a new [CastResponse] instance. + CastResponse({ + this.gCastEnabled = false, + }); + + /// Whether Google Cast is enabled + bool gCastEnabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is CastResponse && + other.gCastEnabled == gCastEnabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (gCastEnabled.hashCode); + + @override + String toString() => 'CastResponse[gCastEnabled=$gCastEnabled]'; + + Map toJson() { + final json = {}; + json[r'gCastEnabled'] = this.gCastEnabled; + return json; + } + + /// Returns a new [CastResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CastResponse? fromJson(dynamic value) { + upgradeDto(value, "CastResponse"); + if (value is Map) { + final json = value.cast(); + + return CastResponse( + gCastEnabled: mapValueOfType(json, r'gCastEnabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CastResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CastResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CastResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CastResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'gCastEnabled', + }; +} + diff --git a/mobile/openapi/lib/model/cast_update.dart b/mobile/openapi/lib/model/cast_update.dart new file mode 100644 index 0000000000..8dbf80f171 --- /dev/null +++ b/mobile/openapi/lib/model/cast_update.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CastUpdate { + /// Returns a new [CastUpdate] instance. + CastUpdate({ + this.gCastEnabled, + }); + + /// Whether Google Cast is enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? gCastEnabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is CastUpdate && + other.gCastEnabled == gCastEnabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (gCastEnabled == null ? 0 : gCastEnabled!.hashCode); + + @override + String toString() => 'CastUpdate[gCastEnabled=$gCastEnabled]'; + + Map toJson() { + final json = {}; + if (this.gCastEnabled != null) { + json[r'gCastEnabled'] = this.gCastEnabled; + } else { + // json[r'gCastEnabled'] = null; + } + return json; + } + + /// Returns a new [CastUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CastUpdate? fromJson(dynamic value) { + upgradeDto(value, "CastUpdate"); + if (value is Map) { + final json = value.cast(); + + return CastUpdate( + gCastEnabled: mapValueOfType(json, r'gCastEnabled'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CastUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CastUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CastUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CastUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/change_password_dto.dart b/mobile/openapi/lib/model/change_password_dto.dart new file mode 100644 index 0000000000..3dd6e437da --- /dev/null +++ b/mobile/openapi/lib/model/change_password_dto.dart @@ -0,0 +1,117 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ChangePasswordDto { + /// Returns a new [ChangePasswordDto] instance. + ChangePasswordDto({ + this.invalidateSessions = false, + required this.newPassword, + required this.password, + }); + + /// Invalidate all other sessions + bool invalidateSessions; + + /// New password (min 8 characters) + String newPassword; + + /// Current password + String password; + + @override + bool operator ==(Object other) => identical(this, other) || other is ChangePasswordDto && + other.invalidateSessions == invalidateSessions && + other.newPassword == newPassword && + other.password == password; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (invalidateSessions.hashCode) + + (newPassword.hashCode) + + (password.hashCode); + + @override + String toString() => 'ChangePasswordDto[invalidateSessions=$invalidateSessions, newPassword=$newPassword, password=$password]'; + + Map toJson() { + final json = {}; + json[r'invalidateSessions'] = this.invalidateSessions; + json[r'newPassword'] = this.newPassword; + json[r'password'] = this.password; + return json; + } + + /// Returns a new [ChangePasswordDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ChangePasswordDto? fromJson(dynamic value) { + upgradeDto(value, "ChangePasswordDto"); + if (value is Map) { + final json = value.cast(); + + return ChangePasswordDto( + invalidateSessions: mapValueOfType(json, r'invalidateSessions') ?? false, + newPassword: mapValueOfType(json, r'newPassword')!, + password: mapValueOfType(json, r'password')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ChangePasswordDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ChangePasswordDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ChangePasswordDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ChangePasswordDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'newPassword', + 'password', + }; +} + diff --git a/mobile/openapi/lib/model/check_existing_assets_dto.dart b/mobile/openapi/lib/model/check_existing_assets_dto.dart new file mode 100644 index 0000000000..6e4a471092 --- /dev/null +++ b/mobile/openapi/lib/model/check_existing_assets_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CheckExistingAssetsDto { + /// Returns a new [CheckExistingAssetsDto] instance. + CheckExistingAssetsDto({ + this.deviceAssetIds = const [], + required this.deviceId, + }); + + /// Device asset IDs to check + List deviceAssetIds; + + /// Device ID + String deviceId; + + @override + bool operator ==(Object other) => identical(this, other) || other is CheckExistingAssetsDto && + _deepEquality.equals(other.deviceAssetIds, deviceAssetIds) && + other.deviceId == deviceId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deviceAssetIds.hashCode) + + (deviceId.hashCode); + + @override + String toString() => 'CheckExistingAssetsDto[deviceAssetIds=$deviceAssetIds, deviceId=$deviceId]'; + + Map toJson() { + final json = {}; + json[r'deviceAssetIds'] = this.deviceAssetIds; + json[r'deviceId'] = this.deviceId; + return json; + } + + /// Returns a new [CheckExistingAssetsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CheckExistingAssetsDto? fromJson(dynamic value) { + upgradeDto(value, "CheckExistingAssetsDto"); + if (value is Map) { + final json = value.cast(); + + return CheckExistingAssetsDto( + deviceAssetIds: json[r'deviceAssetIds'] is Iterable + ? (json[r'deviceAssetIds'] as Iterable).cast().toList(growable: false) + : const [], + deviceId: mapValueOfType(json, r'deviceId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CheckExistingAssetsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CheckExistingAssetsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CheckExistingAssetsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CheckExistingAssetsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deviceAssetIds', + 'deviceId', + }; +} + diff --git a/mobile/openapi/lib/model/check_existing_assets_response_dto.dart b/mobile/openapi/lib/model/check_existing_assets_response_dto.dart new file mode 100644 index 0000000000..9fb13f100f --- /dev/null +++ b/mobile/openapi/lib/model/check_existing_assets_response_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CheckExistingAssetsResponseDto { + /// Returns a new [CheckExistingAssetsResponseDto] instance. + CheckExistingAssetsResponseDto({ + this.existingIds = const [], + }); + + /// Existing asset IDs + List existingIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is CheckExistingAssetsResponseDto && + _deepEquality.equals(other.existingIds, existingIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (existingIds.hashCode); + + @override + String toString() => 'CheckExistingAssetsResponseDto[existingIds=$existingIds]'; + + Map toJson() { + final json = {}; + json[r'existingIds'] = this.existingIds; + return json; + } + + /// Returns a new [CheckExistingAssetsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CheckExistingAssetsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "CheckExistingAssetsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return CheckExistingAssetsResponseDto( + existingIds: json[r'existingIds'] is Iterable + ? (json[r'existingIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CheckExistingAssetsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CheckExistingAssetsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CheckExistingAssetsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CheckExistingAssetsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'existingIds', + }; +} + diff --git a/mobile/openapi/lib/model/clip_config.dart b/mobile/openapi/lib/model/clip_config.dart new file mode 100644 index 0000000000..915e4975ed --- /dev/null +++ b/mobile/openapi/lib/model/clip_config.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CLIPConfig { + /// Returns a new [CLIPConfig] instance. + CLIPConfig({ + required this.enabled, + required this.modelName, + }); + + /// Whether the task is enabled + bool enabled; + + /// Name of the model to use + String modelName; + + @override + bool operator ==(Object other) => identical(this, other) || other is CLIPConfig && + other.enabled == enabled && + other.modelName == modelName; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (modelName.hashCode); + + @override + String toString() => 'CLIPConfig[enabled=$enabled, modelName=$modelName]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'modelName'] = this.modelName; + return json; + } + + /// Returns a new [CLIPConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CLIPConfig? fromJson(dynamic value) { + upgradeDto(value, "CLIPConfig"); + if (value is Map) { + final json = value.cast(); + + return CLIPConfig( + enabled: mapValueOfType(json, r'enabled')!, + modelName: mapValueOfType(json, r'modelName')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CLIPConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CLIPConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CLIPConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CLIPConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'modelName', + }; +} + diff --git a/mobile/openapi/lib/model/colorspace.dart b/mobile/openapi/lib/model/colorspace.dart new file mode 100644 index 0000000000..e871e140fb --- /dev/null +++ b/mobile/openapi/lib/model/colorspace.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Colorspace +class Colorspace { + /// Instantiate a new enum with the provided [value]. + const Colorspace._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const srgb = Colorspace._(r'srgb'); + static const p3 = Colorspace._(r'p3'); + + /// List of all possible values in this [enum][Colorspace]. + static const values = [ + srgb, + p3, + ]; + + static Colorspace? fromJson(dynamic value) => ColorspaceTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = Colorspace.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [Colorspace] to String, +/// and [decode] dynamic data back to [Colorspace]. +class ColorspaceTypeTransformer { + factory ColorspaceTypeTransformer() => _instance ??= const ColorspaceTypeTransformer._(); + + const ColorspaceTypeTransformer._(); + + String encode(Colorspace data) => data.value; + + /// Decodes a [dynamic value][data] to a Colorspace. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + Colorspace? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'srgb': return Colorspace.srgb; + case r'p3': return Colorspace.p3; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ColorspaceTypeTransformer] instance. + static ColorspaceTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/contributor_count_response_dto.dart b/mobile/openapi/lib/model/contributor_count_response_dto.dart new file mode 100644 index 0000000000..1bef8f29d8 --- /dev/null +++ b/mobile/openapi/lib/model/contributor_count_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ContributorCountResponseDto { + /// Returns a new [ContributorCountResponseDto] instance. + ContributorCountResponseDto({ + required this.assetCount, + required this.userId, + }); + + /// Number of assets contributed + int assetCount; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is ContributorCountResponseDto && + other.assetCount == assetCount && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetCount.hashCode) + + (userId.hashCode); + + @override + String toString() => 'ContributorCountResponseDto[assetCount=$assetCount, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'assetCount'] = this.assetCount; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [ContributorCountResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ContributorCountResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ContributorCountResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ContributorCountResponseDto( + assetCount: mapValueOfType(json, r'assetCount')!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ContributorCountResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ContributorCountResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ContributorCountResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ContributorCountResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetCount', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/cq_mode.dart b/mobile/openapi/lib/model/cq_mode.dart new file mode 100644 index 0000000000..efd788b5fb --- /dev/null +++ b/mobile/openapi/lib/model/cq_mode.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// CQ mode +class CQMode { + /// Instantiate a new enum with the provided [value]. + const CQMode._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const auto = CQMode._(r'auto'); + static const cqp = CQMode._(r'cqp'); + static const icq = CQMode._(r'icq'); + + /// List of all possible values in this [enum][CQMode]. + static const values = [ + auto, + cqp, + icq, + ]; + + static CQMode? fromJson(dynamic value) => CQModeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CQMode.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [CQMode] to String, +/// and [decode] dynamic data back to [CQMode]. +class CQModeTypeTransformer { + factory CQModeTypeTransformer() => _instance ??= const CQModeTypeTransformer._(); + + const CQModeTypeTransformer._(); + + String encode(CQMode data) => data.value; + + /// Decodes a [dynamic value][data] to a CQMode. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + CQMode? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'auto': return CQMode.auto; + case r'cqp': return CQMode.cqp; + case r'icq': return CQMode.icq; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [CQModeTypeTransformer] instance. + static CQModeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/create_album_dto.dart b/mobile/openapi/lib/model/create_album_dto.dart new file mode 100644 index 0000000000..183a41c772 --- /dev/null +++ b/mobile/openapi/lib/model/create_album_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CreateAlbumDto { + /// Returns a new [CreateAlbumDto] instance. + CreateAlbumDto({ + required this.albumName, + this.albumUsers = const [], + this.assetIds = const [], + this.description, + }); + + /// Album name + String albumName; + + /// Album users + List albumUsers; + + /// Initial asset IDs + List assetIds; + + /// Album description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + @override + bool operator ==(Object other) => identical(this, other) || other is CreateAlbumDto && + other.albumName == albumName && + _deepEquality.equals(other.albumUsers, albumUsers) && + _deepEquality.equals(other.assetIds, assetIds) && + other.description == description; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumName.hashCode) + + (albumUsers.hashCode) + + (assetIds.hashCode) + + (description == null ? 0 : description!.hashCode); + + @override + String toString() => 'CreateAlbumDto[albumName=$albumName, albumUsers=$albumUsers, assetIds=$assetIds, description=$description]'; + + Map toJson() { + final json = {}; + json[r'albumName'] = this.albumName; + json[r'albumUsers'] = this.albumUsers; + json[r'assetIds'] = this.assetIds; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + return json; + } + + /// Returns a new [CreateAlbumDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CreateAlbumDto? fromJson(dynamic value) { + upgradeDto(value, "CreateAlbumDto"); + if (value is Map) { + final json = value.cast(); + + return CreateAlbumDto( + albumName: mapValueOfType(json, r'albumName')!, + albumUsers: AlbumUserCreateDto.listFromJson(json[r'albumUsers']), + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + description: mapValueOfType(json, r'description'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CreateAlbumDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CreateAlbumDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CreateAlbumDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CreateAlbumDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumName', + }; +} + diff --git a/mobile/openapi/lib/model/create_library_dto.dart b/mobile/openapi/lib/model/create_library_dto.dart new file mode 100644 index 0000000000..69942fee5c --- /dev/null +++ b/mobile/openapi/lib/model/create_library_dto.dart @@ -0,0 +1,138 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CreateLibraryDto { + /// Returns a new [CreateLibraryDto] instance. + CreateLibraryDto({ + this.exclusionPatterns = const {}, + this.importPaths = const {}, + this.name, + required this.ownerId, + }); + + /// Exclusion patterns (max 128) + Set exclusionPatterns; + + /// Import paths (max 128) + Set importPaths; + + /// Library name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// Owner user ID + String ownerId; + + @override + bool operator ==(Object other) => identical(this, other) || other is CreateLibraryDto && + _deepEquality.equals(other.exclusionPatterns, exclusionPatterns) && + _deepEquality.equals(other.importPaths, importPaths) && + other.name == name && + other.ownerId == ownerId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (exclusionPatterns.hashCode) + + (importPaths.hashCode) + + (name == null ? 0 : name!.hashCode) + + (ownerId.hashCode); + + @override + String toString() => 'CreateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths, name=$name, ownerId=$ownerId]'; + + Map toJson() { + final json = {}; + json[r'exclusionPatterns'] = this.exclusionPatterns.toList(growable: false); + json[r'importPaths'] = this.importPaths.toList(growable: false); + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'ownerId'] = this.ownerId; + return json; + } + + /// Returns a new [CreateLibraryDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CreateLibraryDto? fromJson(dynamic value) { + upgradeDto(value, "CreateLibraryDto"); + if (value is Map) { + final json = value.cast(); + + return CreateLibraryDto( + exclusionPatterns: json[r'exclusionPatterns'] is Iterable + ? (json[r'exclusionPatterns'] as Iterable).cast().toSet() + : const {}, + importPaths: json[r'importPaths'] is Iterable + ? (json[r'importPaths'] as Iterable).cast().toSet() + : const {}, + name: mapValueOfType(json, r'name'), + ownerId: mapValueOfType(json, r'ownerId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CreateLibraryDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CreateLibraryDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CreateLibraryDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CreateLibraryDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ownerId', + }; +} + diff --git a/mobile/openapi/lib/model/create_profile_image_response_dto.dart b/mobile/openapi/lib/model/create_profile_image_response_dto.dart new file mode 100644 index 0000000000..20d7cbd5e7 --- /dev/null +++ b/mobile/openapi/lib/model/create_profile_image_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CreateProfileImageResponseDto { + /// Returns a new [CreateProfileImageResponseDto] instance. + CreateProfileImageResponseDto({ + required this.profileChangedAt, + required this.profileImagePath, + required this.userId, + }); + + /// Profile image change date + DateTime profileChangedAt; + + /// Profile image file path + String profileImagePath; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is CreateProfileImageResponseDto && + other.profileChangedAt == profileChangedAt && + other.profileImagePath == profileImagePath && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (profileChangedAt.hashCode) + + (profileImagePath.hashCode) + + (userId.hashCode); + + @override + String toString() => 'CreateProfileImageResponseDto[profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + json[r'profileImagePath'] = this.profileImagePath; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [CreateProfileImageResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CreateProfileImageResponseDto? fromJson(dynamic value) { + upgradeDto(value, "CreateProfileImageResponseDto"); + if (value is Map) { + final json = value.cast(); + + return CreateProfileImageResponseDto( + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CreateProfileImageResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CreateProfileImageResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CreateProfileImageResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CreateProfileImageResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'profileChangedAt', + 'profileImagePath', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/crop_parameters.dart b/mobile/openapi/lib/model/crop_parameters.dart new file mode 100644 index 0000000000..8c5b884596 --- /dev/null +++ b/mobile/openapi/lib/model/crop_parameters.dart @@ -0,0 +1,135 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class CropParameters { + /// Returns a new [CropParameters] instance. + CropParameters({ + required this.height, + required this.width, + required this.x, + required this.y, + }); + + /// Height of the crop + /// + /// Minimum value: 1 + num height; + + /// Width of the crop + /// + /// Minimum value: 1 + num width; + + /// Top-Left X coordinate of crop + /// + /// Minimum value: 0 + num x; + + /// Top-Left Y coordinate of crop + /// + /// Minimum value: 0 + num y; + + @override + bool operator ==(Object other) => identical(this, other) || other is CropParameters && + other.height == height && + other.width == width && + other.x == x && + other.y == y; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (height.hashCode) + + (width.hashCode) + + (x.hashCode) + + (y.hashCode); + + @override + String toString() => 'CropParameters[height=$height, width=$width, x=$x, y=$y]'; + + Map toJson() { + final json = {}; + json[r'height'] = this.height; + json[r'width'] = this.width; + json[r'x'] = this.x; + json[r'y'] = this.y; + return json; + } + + /// Returns a new [CropParameters] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static CropParameters? fromJson(dynamic value) { + upgradeDto(value, "CropParameters"); + if (value is Map) { + final json = value.cast(); + + return CropParameters( + height: num.parse('${json[r'height']}'), + width: num.parse('${json[r'width']}'), + x: num.parse('${json[r'x']}'), + y: num.parse('${json[r'y']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = CropParameters.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = CropParameters.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of CropParameters-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = CropParameters.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'height', + 'width', + 'x', + 'y', + }; +} + diff --git a/mobile/openapi/lib/model/database_backup_config.dart b/mobile/openapi/lib/model/database_backup_config.dart new file mode 100644 index 0000000000..419968c3f3 --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_config.dart @@ -0,0 +1,120 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupConfig { + /// Returns a new [DatabaseBackupConfig] instance. + DatabaseBackupConfig({ + required this.cronExpression, + required this.enabled, + required this.keepLastAmount, + }); + + /// Cron expression + String cronExpression; + + /// Enabled + bool enabled; + + /// Keep last amount + /// + /// Minimum value: 1 + num keepLastAmount; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupConfig && + other.cronExpression == cronExpression && + other.enabled == enabled && + other.keepLastAmount == keepLastAmount; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (cronExpression.hashCode) + + (enabled.hashCode) + + (keepLastAmount.hashCode); + + @override + String toString() => 'DatabaseBackupConfig[cronExpression=$cronExpression, enabled=$enabled, keepLastAmount=$keepLastAmount]'; + + Map toJson() { + final json = {}; + json[r'cronExpression'] = this.cronExpression; + json[r'enabled'] = this.enabled; + json[r'keepLastAmount'] = this.keepLastAmount; + return json; + } + + /// Returns a new [DatabaseBackupConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupConfig? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupConfig"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupConfig( + cronExpression: mapValueOfType(json, r'cronExpression')!, + enabled: mapValueOfType(json, r'enabled')!, + keepLastAmount: num.parse('${json[r'keepLastAmount']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'cronExpression', + 'enabled', + 'keepLastAmount', + }; +} + diff --git a/mobile/openapi/lib/model/database_backup_delete_dto.dart b/mobile/openapi/lib/model/database_backup_delete_dto.dart new file mode 100644 index 0000000000..8bc33a81dc --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_delete_dto.dart @@ -0,0 +1,101 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupDeleteDto { + /// Returns a new [DatabaseBackupDeleteDto] instance. + DatabaseBackupDeleteDto({ + this.backups = const [], + }); + + List backups; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupDeleteDto && + _deepEquality.equals(other.backups, backups); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backups.hashCode); + + @override + String toString() => 'DatabaseBackupDeleteDto[backups=$backups]'; + + Map toJson() { + final json = {}; + json[r'backups'] = this.backups; + return json; + } + + /// Returns a new [DatabaseBackupDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupDeleteDto( + backups: json[r'backups'] is Iterable + ? (json[r'backups'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backups', + }; +} + diff --git a/mobile/openapi/lib/model/database_backup_dto.dart b/mobile/openapi/lib/model/database_backup_dto.dart new file mode 100644 index 0000000000..34912a55e0 --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_dto.dart @@ -0,0 +1,115 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupDto { + /// Returns a new [DatabaseBackupDto] instance. + DatabaseBackupDto({ + required this.filename, + required this.filesize, + required this.timezone, + }); + + String filename; + + num filesize; + + String timezone; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupDto && + other.filename == filename && + other.filesize == filesize && + other.timezone == timezone; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (filename.hashCode) + + (filesize.hashCode) + + (timezone.hashCode); + + @override + String toString() => 'DatabaseBackupDto[filename=$filename, filesize=$filesize, timezone=$timezone]'; + + Map toJson() { + final json = {}; + json[r'filename'] = this.filename; + json[r'filesize'] = this.filesize; + json[r'timezone'] = this.timezone; + return json; + } + + /// Returns a new [DatabaseBackupDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupDto? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupDto"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupDto( + filename: mapValueOfType(json, r'filename')!, + filesize: num.parse('${json[r'filesize']}'), + timezone: mapValueOfType(json, r'timezone')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'filename', + 'filesize', + 'timezone', + }; +} + diff --git a/mobile/openapi/lib/model/database_backup_list_response_dto.dart b/mobile/openapi/lib/model/database_backup_list_response_dto.dart new file mode 100644 index 0000000000..16985dd605 --- /dev/null +++ b/mobile/openapi/lib/model/database_backup_list_response_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DatabaseBackupListResponseDto { + /// Returns a new [DatabaseBackupListResponseDto] instance. + DatabaseBackupListResponseDto({ + this.backups = const [], + }); + + List backups; + + @override + bool operator ==(Object other) => identical(this, other) || other is DatabaseBackupListResponseDto && + _deepEquality.equals(other.backups, backups); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backups.hashCode); + + @override + String toString() => 'DatabaseBackupListResponseDto[backups=$backups]'; + + Map toJson() { + final json = {}; + json[r'backups'] = this.backups; + return json; + } + + /// Returns a new [DatabaseBackupListResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DatabaseBackupListResponseDto? fromJson(dynamic value) { + upgradeDto(value, "DatabaseBackupListResponseDto"); + if (value is Map) { + final json = value.cast(); + + return DatabaseBackupListResponseDto( + backups: DatabaseBackupDto.listFromJson(json[r'backups']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DatabaseBackupListResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DatabaseBackupListResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DatabaseBackupListResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DatabaseBackupListResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backups', + }; +} + diff --git a/mobile/openapi/lib/model/download_archive_dto.dart b/mobile/openapi/lib/model/download_archive_dto.dart new file mode 100644 index 0000000000..20e8527f18 --- /dev/null +++ b/mobile/openapi/lib/model/download_archive_dto.dart @@ -0,0 +1,120 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadArchiveDto { + /// Returns a new [DownloadArchiveDto] instance. + DownloadArchiveDto({ + this.assetIds = const [], + this.edited, + }); + + /// Asset IDs + List assetIds; + + /// Download edited asset if available + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? edited; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadArchiveDto && + _deepEquality.equals(other.assetIds, assetIds) && + other.edited == edited; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (edited == null ? 0 : edited!.hashCode); + + @override + String toString() => 'DownloadArchiveDto[assetIds=$assetIds, edited=$edited]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + if (this.edited != null) { + json[r'edited'] = this.edited; + } else { + // json[r'edited'] = null; + } + return json; + } + + /// Returns a new [DownloadArchiveDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadArchiveDto? fromJson(dynamic value) { + upgradeDto(value, "DownloadArchiveDto"); + if (value is Map) { + final json = value.cast(); + + return DownloadArchiveDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + edited: mapValueOfType(json, r'edited'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadArchiveDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadArchiveDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadArchiveDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadArchiveDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + }; +} + diff --git a/mobile/openapi/lib/model/download_archive_info.dart b/mobile/openapi/lib/model/download_archive_info.dart new file mode 100644 index 0000000000..97a3346a67 --- /dev/null +++ b/mobile/openapi/lib/model/download_archive_info.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadArchiveInfo { + /// Returns a new [DownloadArchiveInfo] instance. + DownloadArchiveInfo({ + this.assetIds = const [], + required this.size, + }); + + /// Asset IDs in this archive + List assetIds; + + /// Archive size in bytes + int size; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadArchiveInfo && + _deepEquality.equals(other.assetIds, assetIds) && + other.size == size; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (size.hashCode); + + @override + String toString() => 'DownloadArchiveInfo[assetIds=$assetIds, size=$size]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'size'] = this.size; + return json; + } + + /// Returns a new [DownloadArchiveInfo] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadArchiveInfo? fromJson(dynamic value) { + upgradeDto(value, "DownloadArchiveInfo"); + if (value is Map) { + final json = value.cast(); + + return DownloadArchiveInfo( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + size: mapValueOfType(json, r'size')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadArchiveInfo.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadArchiveInfo.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadArchiveInfo-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadArchiveInfo.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + 'size', + }; +} + diff --git a/mobile/openapi/lib/model/download_info_dto.dart b/mobile/openapi/lib/model/download_info_dto.dart new file mode 100644 index 0000000000..a1ba44920e --- /dev/null +++ b/mobile/openapi/lib/model/download_info_dto.dart @@ -0,0 +1,157 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadInfoDto { + /// Returns a new [DownloadInfoDto] instance. + DownloadInfoDto({ + this.albumId, + this.archiveSize, + this.assetIds = const [], + this.userId, + }); + + /// Album ID to download + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? albumId; + + /// Archive size limit in bytes + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + int? archiveSize; + + /// Asset IDs to download + List assetIds; + + /// User ID to download assets from + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadInfoDto && + other.albumId == albumId && + other.archiveSize == archiveSize && + _deepEquality.equals(other.assetIds, assetIds) && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId == null ? 0 : albumId!.hashCode) + + (archiveSize == null ? 0 : archiveSize!.hashCode) + + (assetIds.hashCode) + + (userId == null ? 0 : userId!.hashCode); + + @override + String toString() => 'DownloadInfoDto[albumId=$albumId, archiveSize=$archiveSize, assetIds=$assetIds, userId=$userId]'; + + Map toJson() { + final json = {}; + if (this.albumId != null) { + json[r'albumId'] = this.albumId; + } else { + // json[r'albumId'] = null; + } + if (this.archiveSize != null) { + json[r'archiveSize'] = this.archiveSize; + } else { + // json[r'archiveSize'] = null; + } + json[r'assetIds'] = this.assetIds; + if (this.userId != null) { + json[r'userId'] = this.userId; + } else { + // json[r'userId'] = null; + } + return json; + } + + /// Returns a new [DownloadInfoDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadInfoDto? fromJson(dynamic value) { + upgradeDto(value, "DownloadInfoDto"); + if (value is Map) { + final json = value.cast(); + + return DownloadInfoDto( + albumId: mapValueOfType(json, r'albumId'), + archiveSize: mapValueOfType(json, r'archiveSize'), + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + userId: mapValueOfType(json, r'userId'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadInfoDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadInfoDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadInfoDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadInfoDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/download_response.dart b/mobile/openapi/lib/model/download_response.dart new file mode 100644 index 0000000000..32e9487475 --- /dev/null +++ b/mobile/openapi/lib/model/download_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadResponse { + /// Returns a new [DownloadResponse] instance. + DownloadResponse({ + required this.archiveSize, + this.includeEmbeddedVideos = false, + }); + + /// Maximum archive size in bytes + int archiveSize; + + /// Whether to include embedded videos in downloads + bool includeEmbeddedVideos; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadResponse && + other.archiveSize == archiveSize && + other.includeEmbeddedVideos == includeEmbeddedVideos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (archiveSize.hashCode) + + (includeEmbeddedVideos.hashCode); + + @override + String toString() => 'DownloadResponse[archiveSize=$archiveSize, includeEmbeddedVideos=$includeEmbeddedVideos]'; + + Map toJson() { + final json = {}; + json[r'archiveSize'] = this.archiveSize; + json[r'includeEmbeddedVideos'] = this.includeEmbeddedVideos; + return json; + } + + /// Returns a new [DownloadResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadResponse? fromJson(dynamic value) { + upgradeDto(value, "DownloadResponse"); + if (value is Map) { + final json = value.cast(); + + return DownloadResponse( + archiveSize: mapValueOfType(json, r'archiveSize')!, + includeEmbeddedVideos: mapValueOfType(json, r'includeEmbeddedVideos')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'archiveSize', + 'includeEmbeddedVideos', + }; +} + diff --git a/mobile/openapi/lib/model/download_response_dto.dart b/mobile/openapi/lib/model/download_response_dto.dart new file mode 100644 index 0000000000..81912e1d30 --- /dev/null +++ b/mobile/openapi/lib/model/download_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadResponseDto { + /// Returns a new [DownloadResponseDto] instance. + DownloadResponseDto({ + this.archives = const [], + required this.totalSize, + }); + + /// Archive information + List archives; + + /// Total size in bytes + int totalSize; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadResponseDto && + _deepEquality.equals(other.archives, archives) && + other.totalSize == totalSize; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (archives.hashCode) + + (totalSize.hashCode); + + @override + String toString() => 'DownloadResponseDto[archives=$archives, totalSize=$totalSize]'; + + Map toJson() { + final json = {}; + json[r'archives'] = this.archives; + json[r'totalSize'] = this.totalSize; + return json; + } + + /// Returns a new [DownloadResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadResponseDto? fromJson(dynamic value) { + upgradeDto(value, "DownloadResponseDto"); + if (value is Map) { + final json = value.cast(); + + return DownloadResponseDto( + archives: DownloadArchiveInfo.listFromJson(json[r'archives']), + totalSize: mapValueOfType(json, r'totalSize')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'archives', + 'totalSize', + }; +} + diff --git a/mobile/openapi/lib/model/download_update.dart b/mobile/openapi/lib/model/download_update.dart new file mode 100644 index 0000000000..4acc1c8bd3 --- /dev/null +++ b/mobile/openapi/lib/model/download_update.dart @@ -0,0 +1,129 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DownloadUpdate { + /// Returns a new [DownloadUpdate] instance. + DownloadUpdate({ + this.archiveSize, + this.includeEmbeddedVideos, + }); + + /// Maximum archive size in bytes + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + int? archiveSize; + + /// Whether to include embedded videos in downloads + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? includeEmbeddedVideos; + + @override + bool operator ==(Object other) => identical(this, other) || other is DownloadUpdate && + other.archiveSize == archiveSize && + other.includeEmbeddedVideos == includeEmbeddedVideos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (archiveSize == null ? 0 : archiveSize!.hashCode) + + (includeEmbeddedVideos == null ? 0 : includeEmbeddedVideos!.hashCode); + + @override + String toString() => 'DownloadUpdate[archiveSize=$archiveSize, includeEmbeddedVideos=$includeEmbeddedVideos]'; + + Map toJson() { + final json = {}; + if (this.archiveSize != null) { + json[r'archiveSize'] = this.archiveSize; + } else { + // json[r'archiveSize'] = null; + } + if (this.includeEmbeddedVideos != null) { + json[r'includeEmbeddedVideos'] = this.includeEmbeddedVideos; + } else { + // json[r'includeEmbeddedVideos'] = null; + } + return json; + } + + /// Returns a new [DownloadUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DownloadUpdate? fromJson(dynamic value) { + upgradeDto(value, "DownloadUpdate"); + if (value is Map) { + final json = value.cast(); + + return DownloadUpdate( + archiveSize: mapValueOfType(json, r'archiveSize'), + includeEmbeddedVideos: mapValueOfType(json, r'includeEmbeddedVideos'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DownloadUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DownloadUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DownloadUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DownloadUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/duplicate_detection_config.dart b/mobile/openapi/lib/model/duplicate_detection_config.dart new file mode 100644 index 0000000000..43233826ef --- /dev/null +++ b/mobile/openapi/lib/model/duplicate_detection_config.dart @@ -0,0 +1,112 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DuplicateDetectionConfig { + /// Returns a new [DuplicateDetectionConfig] instance. + DuplicateDetectionConfig({ + required this.enabled, + required this.maxDistance, + }); + + /// Whether the task is enabled + bool enabled; + + /// Maximum distance threshold for duplicate detection + /// + /// Minimum value: 0.001 + /// Maximum value: 0.1 + double maxDistance; + + @override + bool operator ==(Object other) => identical(this, other) || other is DuplicateDetectionConfig && + other.enabled == enabled && + other.maxDistance == maxDistance; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (maxDistance.hashCode); + + @override + String toString() => 'DuplicateDetectionConfig[enabled=$enabled, maxDistance=$maxDistance]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'maxDistance'] = this.maxDistance; + return json; + } + + /// Returns a new [DuplicateDetectionConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DuplicateDetectionConfig? fromJson(dynamic value) { + upgradeDto(value, "DuplicateDetectionConfig"); + if (value is Map) { + final json = value.cast(); + + return DuplicateDetectionConfig( + enabled: mapValueOfType(json, r'enabled')!, + maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DuplicateDetectionConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DuplicateDetectionConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DuplicateDetectionConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DuplicateDetectionConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'maxDistance', + }; +} + diff --git a/mobile/openapi/lib/model/duplicate_resolve_dto.dart b/mobile/openapi/lib/model/duplicate_resolve_dto.dart new file mode 100644 index 0000000000..3466d3a620 --- /dev/null +++ b/mobile/openapi/lib/model/duplicate_resolve_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DuplicateResolveDto { + /// Returns a new [DuplicateResolveDto] instance. + DuplicateResolveDto({ + this.groups = const [], + }); + + /// List of duplicate groups to resolve + List groups; + + @override + bool operator ==(Object other) => identical(this, other) || other is DuplicateResolveDto && + _deepEquality.equals(other.groups, groups); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (groups.hashCode); + + @override + String toString() => 'DuplicateResolveDto[groups=$groups]'; + + Map toJson() { + final json = {}; + json[r'groups'] = this.groups; + return json; + } + + /// Returns a new [DuplicateResolveDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DuplicateResolveDto? fromJson(dynamic value) { + upgradeDto(value, "DuplicateResolveDto"); + if (value is Map) { + final json = value.cast(); + + return DuplicateResolveDto( + groups: DuplicateResolveGroupDto.listFromJson(json[r'groups']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DuplicateResolveDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DuplicateResolveDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DuplicateResolveDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DuplicateResolveDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'groups', + }; +} + diff --git a/mobile/openapi/lib/model/duplicate_resolve_group_dto.dart b/mobile/openapi/lib/model/duplicate_resolve_group_dto.dart new file mode 100644 index 0000000000..94ca53eb7d --- /dev/null +++ b/mobile/openapi/lib/model/duplicate_resolve_group_dto.dart @@ -0,0 +1,121 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DuplicateResolveGroupDto { + /// Returns a new [DuplicateResolveGroupDto] instance. + DuplicateResolveGroupDto({ + required this.duplicateId, + this.keepAssetIds = const [], + this.trashAssetIds = const [], + }); + + String duplicateId; + + /// Asset IDs to keep + List keepAssetIds; + + /// Asset IDs to trash or delete + List trashAssetIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is DuplicateResolveGroupDto && + other.duplicateId == duplicateId && + _deepEquality.equals(other.keepAssetIds, keepAssetIds) && + _deepEquality.equals(other.trashAssetIds, trashAssetIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (duplicateId.hashCode) + + (keepAssetIds.hashCode) + + (trashAssetIds.hashCode); + + @override + String toString() => 'DuplicateResolveGroupDto[duplicateId=$duplicateId, keepAssetIds=$keepAssetIds, trashAssetIds=$trashAssetIds]'; + + Map toJson() { + final json = {}; + json[r'duplicateId'] = this.duplicateId; + json[r'keepAssetIds'] = this.keepAssetIds; + json[r'trashAssetIds'] = this.trashAssetIds; + return json; + } + + /// Returns a new [DuplicateResolveGroupDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DuplicateResolveGroupDto? fromJson(dynamic value) { + upgradeDto(value, "DuplicateResolveGroupDto"); + if (value is Map) { + final json = value.cast(); + + return DuplicateResolveGroupDto( + duplicateId: mapValueOfType(json, r'duplicateId')!, + keepAssetIds: json[r'keepAssetIds'] is Iterable + ? (json[r'keepAssetIds'] as Iterable).cast().toList(growable: false) + : const [], + trashAssetIds: json[r'trashAssetIds'] is Iterable + ? (json[r'trashAssetIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DuplicateResolveGroupDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DuplicateResolveGroupDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DuplicateResolveGroupDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DuplicateResolveGroupDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'duplicateId', + 'keepAssetIds', + 'trashAssetIds', + }; +} + diff --git a/mobile/openapi/lib/model/duplicate_response_dto.dart b/mobile/openapi/lib/model/duplicate_response_dto.dart new file mode 100644 index 0000000000..f0ddbb4fdd --- /dev/null +++ b/mobile/openapi/lib/model/duplicate_response_dto.dart @@ -0,0 +1,120 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class DuplicateResponseDto { + /// Returns a new [DuplicateResponseDto] instance. + DuplicateResponseDto({ + this.assets = const [], + required this.duplicateId, + this.suggestedKeepAssetIds = const [], + }); + + /// Duplicate assets + List assets; + + /// Duplicate group ID + String duplicateId; + + /// Suggested asset IDs to keep based on file size and EXIF data + List suggestedKeepAssetIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is DuplicateResponseDto && + _deepEquality.equals(other.assets, assets) && + other.duplicateId == duplicateId && + _deepEquality.equals(other.suggestedKeepAssetIds, suggestedKeepAssetIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode) + + (duplicateId.hashCode) + + (suggestedKeepAssetIds.hashCode); + + @override + String toString() => 'DuplicateResponseDto[assets=$assets, duplicateId=$duplicateId, suggestedKeepAssetIds=$suggestedKeepAssetIds]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + json[r'duplicateId'] = this.duplicateId; + json[r'suggestedKeepAssetIds'] = this.suggestedKeepAssetIds; + return json; + } + + /// Returns a new [DuplicateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static DuplicateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "DuplicateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return DuplicateResponseDto( + assets: AssetResponseDto.listFromJson(json[r'assets']), + duplicateId: mapValueOfType(json, r'duplicateId')!, + suggestedKeepAssetIds: json[r'suggestedKeepAssetIds'] is Iterable + ? (json[r'suggestedKeepAssetIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = DuplicateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = DuplicateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of DuplicateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = DuplicateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + 'duplicateId', + 'suggestedKeepAssetIds', + }; +} + diff --git a/mobile/openapi/lib/model/email_notifications_response.dart b/mobile/openapi/lib/model/email_notifications_response.dart new file mode 100644 index 0000000000..08a3d580c6 --- /dev/null +++ b/mobile/openapi/lib/model/email_notifications_response.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class EmailNotificationsResponse { + /// Returns a new [EmailNotificationsResponse] instance. + EmailNotificationsResponse({ + required this.albumInvite, + required this.albumUpdate, + required this.enabled, + }); + + /// Whether to receive email notifications for album invites + bool albumInvite; + + /// Whether to receive email notifications for album updates + bool albumUpdate; + + /// Whether email notifications are enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is EmailNotificationsResponse && + other.albumInvite == albumInvite && + other.albumUpdate == albumUpdate && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumInvite.hashCode) + + (albumUpdate.hashCode) + + (enabled.hashCode); + + @override + String toString() => 'EmailNotificationsResponse[albumInvite=$albumInvite, albumUpdate=$albumUpdate, enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'albumInvite'] = this.albumInvite; + json[r'albumUpdate'] = this.albumUpdate; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [EmailNotificationsResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static EmailNotificationsResponse? fromJson(dynamic value) { + upgradeDto(value, "EmailNotificationsResponse"); + if (value is Map) { + final json = value.cast(); + + return EmailNotificationsResponse( + albumInvite: mapValueOfType(json, r'albumInvite')!, + albumUpdate: mapValueOfType(json, r'albumUpdate')!, + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = EmailNotificationsResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = EmailNotificationsResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of EmailNotificationsResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = EmailNotificationsResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumInvite', + 'albumUpdate', + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/email_notifications_update.dart b/mobile/openapi/lib/model/email_notifications_update.dart new file mode 100644 index 0000000000..e158e45598 --- /dev/null +++ b/mobile/openapi/lib/model/email_notifications_update.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class EmailNotificationsUpdate { + /// Returns a new [EmailNotificationsUpdate] instance. + EmailNotificationsUpdate({ + this.albumInvite, + this.albumUpdate, + this.enabled, + }); + + /// Whether to receive email notifications for album invites + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? albumInvite; + + /// Whether to receive email notifications for album updates + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? albumUpdate; + + /// Whether email notifications are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is EmailNotificationsUpdate && + other.albumInvite == albumInvite && + other.albumUpdate == albumUpdate && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumInvite == null ? 0 : albumInvite!.hashCode) + + (albumUpdate == null ? 0 : albumUpdate!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode); + + @override + String toString() => 'EmailNotificationsUpdate[albumInvite=$albumInvite, albumUpdate=$albumUpdate, enabled=$enabled]'; + + Map toJson() { + final json = {}; + if (this.albumInvite != null) { + json[r'albumInvite'] = this.albumInvite; + } else { + // json[r'albumInvite'] = null; + } + if (this.albumUpdate != null) { + json[r'albumUpdate'] = this.albumUpdate; + } else { + // json[r'albumUpdate'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + return json; + } + + /// Returns a new [EmailNotificationsUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static EmailNotificationsUpdate? fromJson(dynamic value) { + upgradeDto(value, "EmailNotificationsUpdate"); + if (value is Map) { + final json = value.cast(); + + return EmailNotificationsUpdate( + albumInvite: mapValueOfType(json, r'albumInvite'), + albumUpdate: mapValueOfType(json, r'albumUpdate'), + enabled: mapValueOfType(json, r'enabled'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = EmailNotificationsUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = EmailNotificationsUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of EmailNotificationsUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = EmailNotificationsUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/exif_response_dto.dart b/mobile/openapi/lib/model/exif_response_dto.dart new file mode 100644 index 0000000000..6bb58a8ab9 --- /dev/null +++ b/mobile/openapi/lib/model/exif_response_dto.dart @@ -0,0 +1,371 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ExifResponseDto { + /// Returns a new [ExifResponseDto] instance. + ExifResponseDto({ + this.city, + this.country, + this.dateTimeOriginal, + this.description, + this.exifImageHeight, + this.exifImageWidth, + this.exposureTime, + this.fNumber, + this.fileSizeInByte, + this.focalLength, + this.iso, + this.latitude, + this.lensModel, + this.longitude, + this.make, + this.model, + this.modifyDate, + this.orientation, + this.projectionType, + this.rating, + this.state, + this.timeZone, + }); + + /// City name + String? city; + + /// Country name + String? country; + + /// Original date/time + DateTime? dateTimeOriginal; + + /// Image description + String? description; + + /// Image height in pixels + num? exifImageHeight; + + /// Image width in pixels + num? exifImageWidth; + + /// Exposure time + String? exposureTime; + + /// F-number (aperture) + num? fNumber; + + /// File size in bytes + int? fileSizeInByte; + + /// Focal length in mm + num? focalLength; + + /// ISO sensitivity + num? iso; + + /// GPS latitude + num? latitude; + + /// Lens model + String? lensModel; + + /// GPS longitude + num? longitude; + + /// Camera make + String? make; + + /// Camera model + String? model; + + /// Modification date/time + DateTime? modifyDate; + + /// Image orientation + String? orientation; + + /// Projection type + String? projectionType; + + /// Rating + num? rating; + + /// State/province name + String? state; + + /// Time zone + String? timeZone; + + @override + bool operator ==(Object other) => identical(this, other) || other is ExifResponseDto && + other.city == city && + other.country == country && + other.dateTimeOriginal == dateTimeOriginal && + other.description == description && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileSizeInByte == fileSizeInByte && + other.focalLength == focalLength && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.timeZone == timeZone; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (focalLength == null ? 0 : focalLength!.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (timeZone == null ? 0 : timeZone!.hashCode); + + @override + String toString() => 'ExifResponseDto[city=$city, country=$country, dateTimeOriginal=$dateTimeOriginal, description=$description, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileSizeInByte=$fileSizeInByte, focalLength=$focalLength, iso=$iso, latitude=$latitude, lensModel=$lensModel, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, projectionType=$projectionType, rating=$rating, state=$state, timeZone=$timeZone]'; + + Map toJson() { + final json = {}; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal!.toUtc().toIso8601String(); + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.focalLength != null) { + json[r'focalLength'] = this.focalLength; + } else { + // json[r'focalLength'] = null; + } + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate!.toUtc().toIso8601String(); + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.timeZone != null) { + json[r'timeZone'] = this.timeZone; + } else { + // json[r'timeZone'] = null; + } + return json; + } + + /// Returns a new [ExifResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ExifResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ExifResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ExifResponseDto( + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''), + description: mapValueOfType(json, r'description'), + exifImageHeight: json[r'exifImageHeight'] == null + ? null + : num.parse('${json[r'exifImageHeight']}'), + exifImageWidth: json[r'exifImageWidth'] == null + ? null + : num.parse('${json[r'exifImageWidth']}'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: json[r'fNumber'] == null + ? null + : num.parse('${json[r'fNumber']}'), + fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), + focalLength: json[r'focalLength'] == null + ? null + : num.parse('${json[r'focalLength']}'), + iso: json[r'iso'] == null + ? null + : num.parse('${json[r'iso']}'), + latitude: json[r'latitude'] == null + ? null + : num.parse('${json[r'latitude']}'), + lensModel: mapValueOfType(json, r'lensModel'), + longitude: json[r'longitude'] == null + ? null + : num.parse('${json[r'longitude']}'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapDateTime(json, r'modifyDate', r''), + orientation: mapValueOfType(json, r'orientation'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + state: mapValueOfType(json, r'state'), + timeZone: mapValueOfType(json, r'timeZone'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ExifResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ExifResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ExifResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ExifResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/face_dto.dart b/mobile/openapi/lib/model/face_dto.dart new file mode 100644 index 0000000000..ec5f5c8a6c --- /dev/null +++ b/mobile/openapi/lib/model/face_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class FaceDto { + /// Returns a new [FaceDto] instance. + FaceDto({ + required this.id, + }); + + /// Face ID + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is FaceDto && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (id.hashCode); + + @override + String toString() => 'FaceDto[id=$id]'; + + Map toJson() { + final json = {}; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [FaceDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static FaceDto? fromJson(dynamic value) { + upgradeDto(value, "FaceDto"); + if (value is Map) { + final json = value.cast(); + + return FaceDto( + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = FaceDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = FaceDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of FaceDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = FaceDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/facial_recognition_config.dart b/mobile/openapi/lib/model/facial_recognition_config.dart new file mode 100644 index 0000000000..4b9d7a6e9e --- /dev/null +++ b/mobile/openapi/lib/model/facial_recognition_config.dart @@ -0,0 +1,144 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class FacialRecognitionConfig { + /// Returns a new [FacialRecognitionConfig] instance. + FacialRecognitionConfig({ + required this.enabled, + required this.maxDistance, + required this.minFaces, + required this.minScore, + required this.modelName, + }); + + /// Whether the task is enabled + bool enabled; + + /// Maximum distance threshold for face recognition + /// + /// Minimum value: 0.1 + /// Maximum value: 2 + double maxDistance; + + /// Minimum number of faces required for recognition + /// + /// Minimum value: 1 + int minFaces; + + /// Minimum confidence score for face detection + /// + /// Minimum value: 0.1 + /// Maximum value: 1 + double minScore; + + /// Name of the model to use + String modelName; + + @override + bool operator ==(Object other) => identical(this, other) || other is FacialRecognitionConfig && + other.enabled == enabled && + other.maxDistance == maxDistance && + other.minFaces == minFaces && + other.minScore == minScore && + other.modelName == modelName; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (maxDistance.hashCode) + + (minFaces.hashCode) + + (minScore.hashCode) + + (modelName.hashCode); + + @override + String toString() => 'FacialRecognitionConfig[enabled=$enabled, maxDistance=$maxDistance, minFaces=$minFaces, minScore=$minScore, modelName=$modelName]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'maxDistance'] = this.maxDistance; + json[r'minFaces'] = this.minFaces; + json[r'minScore'] = this.minScore; + json[r'modelName'] = this.modelName; + return json; + } + + /// Returns a new [FacialRecognitionConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static FacialRecognitionConfig? fromJson(dynamic value) { + upgradeDto(value, "FacialRecognitionConfig"); + if (value is Map) { + final json = value.cast(); + + return FacialRecognitionConfig( + enabled: mapValueOfType(json, r'enabled')!, + maxDistance: (mapValueOfType(json, r'maxDistance')!).toDouble(), + minFaces: mapValueOfType(json, r'minFaces')!, + minScore: (mapValueOfType(json, r'minScore')!).toDouble(), + modelName: mapValueOfType(json, r'modelName')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = FacialRecognitionConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = FacialRecognitionConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of FacialRecognitionConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = FacialRecognitionConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'maxDistance', + 'minFaces', + 'minScore', + 'modelName', + }; +} + diff --git a/mobile/openapi/lib/model/folders_response.dart b/mobile/openapi/lib/model/folders_response.dart new file mode 100644 index 0000000000..906a95a83c --- /dev/null +++ b/mobile/openapi/lib/model/folders_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class FoldersResponse { + /// Returns a new [FoldersResponse] instance. + FoldersResponse({ + this.enabled = false, + this.sidebarWeb = false, + }); + + /// Whether folders are enabled + bool enabled; + + /// Whether folders appear in web sidebar + bool sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is FoldersResponse && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (sidebarWeb.hashCode); + + @override + String toString() => 'FoldersResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'sidebarWeb'] = this.sidebarWeb; + return json; + } + + /// Returns a new [FoldersResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static FoldersResponse? fromJson(dynamic value) { + upgradeDto(value, "FoldersResponse"); + if (value is Map) { + final json = value.cast(); + + return FoldersResponse( + enabled: mapValueOfType(json, r'enabled')!, + sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = FoldersResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = FoldersResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of FoldersResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = FoldersResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'sidebarWeb', + }; +} + diff --git a/mobile/openapi/lib/model/folders_update.dart b/mobile/openapi/lib/model/folders_update.dart new file mode 100644 index 0000000000..edd58014d4 --- /dev/null +++ b/mobile/openapi/lib/model/folders_update.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class FoldersUpdate { + /// Returns a new [FoldersUpdate] instance. + FoldersUpdate({ + this.enabled, + this.sidebarWeb, + }); + + /// Whether folders are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Whether folders appear in web sidebar + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is FoldersUpdate && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled == null ? 0 : enabled!.hashCode) + + (sidebarWeb == null ? 0 : sidebarWeb!.hashCode); + + @override + String toString() => 'FoldersUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + if (this.sidebarWeb != null) { + json[r'sidebarWeb'] = this.sidebarWeb; + } else { + // json[r'sidebarWeb'] = null; + } + return json; + } + + /// Returns a new [FoldersUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static FoldersUpdate? fromJson(dynamic value) { + upgradeDto(value, "FoldersUpdate"); + if (value is Map) { + final json = value.cast(); + + return FoldersUpdate( + enabled: mapValueOfType(json, r'enabled'), + sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = FoldersUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = FoldersUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of FoldersUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = FoldersUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/image_format.dart b/mobile/openapi/lib/model/image_format.dart new file mode 100644 index 0000000000..1a0dde5def --- /dev/null +++ b/mobile/openapi/lib/model/image_format.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Image format +class ImageFormat { + /// Instantiate a new enum with the provided [value]. + const ImageFormat._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const jpeg = ImageFormat._(r'jpeg'); + static const webp = ImageFormat._(r'webp'); + + /// List of all possible values in this [enum][ImageFormat]. + static const values = [ + jpeg, + webp, + ]; + + static ImageFormat? fromJson(dynamic value) => ImageFormatTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ImageFormat.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ImageFormat] to String, +/// and [decode] dynamic data back to [ImageFormat]. +class ImageFormatTypeTransformer { + factory ImageFormatTypeTransformer() => _instance ??= const ImageFormatTypeTransformer._(); + + const ImageFormatTypeTransformer._(); + + String encode(ImageFormat data) => data.value; + + /// Decodes a [dynamic value][data] to a ImageFormat. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ImageFormat? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'jpeg': return ImageFormat.jpeg; + case r'webp': return ImageFormat.webp; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ImageFormatTypeTransformer] instance. + static ImageFormatTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/job_create_dto.dart b/mobile/openapi/lib/model/job_create_dto.dart new file mode 100644 index 0000000000..3a3412384e --- /dev/null +++ b/mobile/openapi/lib/model/job_create_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class JobCreateDto { + /// Returns a new [JobCreateDto] instance. + JobCreateDto({ + required this.name, + }); + + /// Job name + ManualJobName name; + + @override + bool operator ==(Object other) => identical(this, other) || other is JobCreateDto && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (name.hashCode); + + @override + String toString() => 'JobCreateDto[name=$name]'; + + Map toJson() { + final json = {}; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [JobCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static JobCreateDto? fromJson(dynamic value) { + upgradeDto(value, "JobCreateDto"); + if (value is Map) { + final json = value.cast(); + + return JobCreateDto( + name: ManualJobName.fromJson(json[r'name'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = JobCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = JobCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of JobCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = JobCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/job_name.dart b/mobile/openapi/lib/model/job_name.dart new file mode 100644 index 0000000000..96b9339b7d --- /dev/null +++ b/mobile/openapi/lib/model/job_name.dart @@ -0,0 +1,247 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Job name +class JobName { + /// Instantiate a new enum with the provided [value]. + const JobName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetDelete = JobName._(r'AssetDelete'); + static const assetDeleteCheck = JobName._(r'AssetDeleteCheck'); + static const assetDetectFacesQueueAll = JobName._(r'AssetDetectFacesQueueAll'); + static const assetDetectFaces = JobName._(r'AssetDetectFaces'); + static const assetDetectDuplicatesQueueAll = JobName._(r'AssetDetectDuplicatesQueueAll'); + static const assetDetectDuplicates = JobName._(r'AssetDetectDuplicates'); + static const assetEditThumbnailGeneration = JobName._(r'AssetEditThumbnailGeneration'); + static const assetEncodeVideoQueueAll = JobName._(r'AssetEncodeVideoQueueAll'); + static const assetEncodeVideo = JobName._(r'AssetEncodeVideo'); + static const assetEmptyTrash = JobName._(r'AssetEmptyTrash'); + static const assetExtractMetadataQueueAll = JobName._(r'AssetExtractMetadataQueueAll'); + static const assetExtractMetadata = JobName._(r'AssetExtractMetadata'); + static const assetFileMigration = JobName._(r'AssetFileMigration'); + static const assetGenerateThumbnailsQueueAll = JobName._(r'AssetGenerateThumbnailsQueueAll'); + static const assetGenerateThumbnails = JobName._(r'AssetGenerateThumbnails'); + static const auditLogCleanup = JobName._(r'AuditLogCleanup'); + static const auditTableCleanup = JobName._(r'AuditTableCleanup'); + static const databaseBackup = JobName._(r'DatabaseBackup'); + static const facialRecognitionQueueAll = JobName._(r'FacialRecognitionQueueAll'); + static const facialRecognition = JobName._(r'FacialRecognition'); + static const fileDelete = JobName._(r'FileDelete'); + static const fileMigrationQueueAll = JobName._(r'FileMigrationQueueAll'); + static const libraryDeleteCheck = JobName._(r'LibraryDeleteCheck'); + static const libraryDelete = JobName._(r'LibraryDelete'); + static const libraryRemoveAsset = JobName._(r'LibraryRemoveAsset'); + static const libraryScanAssetsQueueAll = JobName._(r'LibraryScanAssetsQueueAll'); + static const librarySyncAssets = JobName._(r'LibrarySyncAssets'); + static const librarySyncFilesQueueAll = JobName._(r'LibrarySyncFilesQueueAll'); + static const librarySyncFiles = JobName._(r'LibrarySyncFiles'); + static const libraryScanQueueAll = JobName._(r'LibraryScanQueueAll'); + static const memoryCleanup = JobName._(r'MemoryCleanup'); + static const memoryGenerate = JobName._(r'MemoryGenerate'); + static const notificationsCleanup = JobName._(r'NotificationsCleanup'); + static const notifyUserSignup = JobName._(r'NotifyUserSignup'); + static const notifyAlbumInvite = JobName._(r'NotifyAlbumInvite'); + static const notifyAlbumUpdate = JobName._(r'NotifyAlbumUpdate'); + static const userDelete = JobName._(r'UserDelete'); + static const userDeleteCheck = JobName._(r'UserDeleteCheck'); + static const userSyncUsage = JobName._(r'UserSyncUsage'); + static const personCleanup = JobName._(r'PersonCleanup'); + static const personFileMigration = JobName._(r'PersonFileMigration'); + static const personGenerateThumbnail = JobName._(r'PersonGenerateThumbnail'); + static const sessionCleanup = JobName._(r'SessionCleanup'); + static const sendMail = JobName._(r'SendMail'); + static const sidecarQueueAll = JobName._(r'SidecarQueueAll'); + static const sidecarCheck = JobName._(r'SidecarCheck'); + static const sidecarWrite = JobName._(r'SidecarWrite'); + static const smartSearchQueueAll = JobName._(r'SmartSearchQueueAll'); + static const smartSearch = JobName._(r'SmartSearch'); + static const storageTemplateMigration = JobName._(r'StorageTemplateMigration'); + static const storageTemplateMigrationSingle = JobName._(r'StorageTemplateMigrationSingle'); + static const tagCleanup = JobName._(r'TagCleanup'); + static const versionCheck = JobName._(r'VersionCheck'); + static const ocrQueueAll = JobName._(r'OcrQueueAll'); + static const ocr = JobName._(r'Ocr'); + static const workflowRun = JobName._(r'WorkflowRun'); + + /// List of all possible values in this [enum][JobName]. + static const values = [ + assetDelete, + assetDeleteCheck, + assetDetectFacesQueueAll, + assetDetectFaces, + assetDetectDuplicatesQueueAll, + assetDetectDuplicates, + assetEditThumbnailGeneration, + assetEncodeVideoQueueAll, + assetEncodeVideo, + assetEmptyTrash, + assetExtractMetadataQueueAll, + assetExtractMetadata, + assetFileMigration, + assetGenerateThumbnailsQueueAll, + assetGenerateThumbnails, + auditLogCleanup, + auditTableCleanup, + databaseBackup, + facialRecognitionQueueAll, + facialRecognition, + fileDelete, + fileMigrationQueueAll, + libraryDeleteCheck, + libraryDelete, + libraryRemoveAsset, + libraryScanAssetsQueueAll, + librarySyncAssets, + librarySyncFilesQueueAll, + librarySyncFiles, + libraryScanQueueAll, + memoryCleanup, + memoryGenerate, + notificationsCleanup, + notifyUserSignup, + notifyAlbumInvite, + notifyAlbumUpdate, + userDelete, + userDeleteCheck, + userSyncUsage, + personCleanup, + personFileMigration, + personGenerateThumbnail, + sessionCleanup, + sendMail, + sidecarQueueAll, + sidecarCheck, + sidecarWrite, + smartSearchQueueAll, + smartSearch, + storageTemplateMigration, + storageTemplateMigrationSingle, + tagCleanup, + versionCheck, + ocrQueueAll, + ocr, + workflowRun, + ]; + + static JobName? fromJson(dynamic value) => JobNameTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = JobName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [JobName] to String, +/// and [decode] dynamic data back to [JobName]. +class JobNameTypeTransformer { + factory JobNameTypeTransformer() => _instance ??= const JobNameTypeTransformer._(); + + const JobNameTypeTransformer._(); + + String encode(JobName data) => data.value; + + /// Decodes a [dynamic value][data] to a JobName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + JobName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetDelete': return JobName.assetDelete; + case r'AssetDeleteCheck': return JobName.assetDeleteCheck; + case r'AssetDetectFacesQueueAll': return JobName.assetDetectFacesQueueAll; + case r'AssetDetectFaces': return JobName.assetDetectFaces; + case r'AssetDetectDuplicatesQueueAll': return JobName.assetDetectDuplicatesQueueAll; + case r'AssetDetectDuplicates': return JobName.assetDetectDuplicates; + case r'AssetEditThumbnailGeneration': return JobName.assetEditThumbnailGeneration; + case r'AssetEncodeVideoQueueAll': return JobName.assetEncodeVideoQueueAll; + case r'AssetEncodeVideo': return JobName.assetEncodeVideo; + case r'AssetEmptyTrash': return JobName.assetEmptyTrash; + case r'AssetExtractMetadataQueueAll': return JobName.assetExtractMetadataQueueAll; + case r'AssetExtractMetadata': return JobName.assetExtractMetadata; + case r'AssetFileMigration': return JobName.assetFileMigration; + case r'AssetGenerateThumbnailsQueueAll': return JobName.assetGenerateThumbnailsQueueAll; + case r'AssetGenerateThumbnails': return JobName.assetGenerateThumbnails; + case r'AuditLogCleanup': return JobName.auditLogCleanup; + case r'AuditTableCleanup': return JobName.auditTableCleanup; + case r'DatabaseBackup': return JobName.databaseBackup; + case r'FacialRecognitionQueueAll': return JobName.facialRecognitionQueueAll; + case r'FacialRecognition': return JobName.facialRecognition; + case r'FileDelete': return JobName.fileDelete; + case r'FileMigrationQueueAll': return JobName.fileMigrationQueueAll; + case r'LibraryDeleteCheck': return JobName.libraryDeleteCheck; + case r'LibraryDelete': return JobName.libraryDelete; + case r'LibraryRemoveAsset': return JobName.libraryRemoveAsset; + case r'LibraryScanAssetsQueueAll': return JobName.libraryScanAssetsQueueAll; + case r'LibrarySyncAssets': return JobName.librarySyncAssets; + case r'LibrarySyncFilesQueueAll': return JobName.librarySyncFilesQueueAll; + case r'LibrarySyncFiles': return JobName.librarySyncFiles; + case r'LibraryScanQueueAll': return JobName.libraryScanQueueAll; + case r'MemoryCleanup': return JobName.memoryCleanup; + case r'MemoryGenerate': return JobName.memoryGenerate; + case r'NotificationsCleanup': return JobName.notificationsCleanup; + case r'NotifyUserSignup': return JobName.notifyUserSignup; + case r'NotifyAlbumInvite': return JobName.notifyAlbumInvite; + case r'NotifyAlbumUpdate': return JobName.notifyAlbumUpdate; + case r'UserDelete': return JobName.userDelete; + case r'UserDeleteCheck': return JobName.userDeleteCheck; + case r'UserSyncUsage': return JobName.userSyncUsage; + case r'PersonCleanup': return JobName.personCleanup; + case r'PersonFileMigration': return JobName.personFileMigration; + case r'PersonGenerateThumbnail': return JobName.personGenerateThumbnail; + case r'SessionCleanup': return JobName.sessionCleanup; + case r'SendMail': return JobName.sendMail; + case r'SidecarQueueAll': return JobName.sidecarQueueAll; + case r'SidecarCheck': return JobName.sidecarCheck; + case r'SidecarWrite': return JobName.sidecarWrite; + case r'SmartSearchQueueAll': return JobName.smartSearchQueueAll; + case r'SmartSearch': return JobName.smartSearch; + case r'StorageTemplateMigration': return JobName.storageTemplateMigration; + case r'StorageTemplateMigrationSingle': return JobName.storageTemplateMigrationSingle; + case r'TagCleanup': return JobName.tagCleanup; + case r'VersionCheck': return JobName.versionCheck; + case r'OcrQueueAll': return JobName.ocrQueueAll; + case r'Ocr': return JobName.ocr; + case r'WorkflowRun': return JobName.workflowRun; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [JobNameTypeTransformer] instance. + static JobNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/job_settings_dto.dart b/mobile/openapi/lib/model/job_settings_dto.dart new file mode 100644 index 0000000000..73a0187ddd --- /dev/null +++ b/mobile/openapi/lib/model/job_settings_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class JobSettingsDto { + /// Returns a new [JobSettingsDto] instance. + JobSettingsDto({ + required this.concurrency, + }); + + /// Concurrency + /// + /// Minimum value: 1 + int concurrency; + + @override + bool operator ==(Object other) => identical(this, other) || other is JobSettingsDto && + other.concurrency == concurrency; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (concurrency.hashCode); + + @override + String toString() => 'JobSettingsDto[concurrency=$concurrency]'; + + Map toJson() { + final json = {}; + json[r'concurrency'] = this.concurrency; + return json; + } + + /// Returns a new [JobSettingsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static JobSettingsDto? fromJson(dynamic value) { + upgradeDto(value, "JobSettingsDto"); + if (value is Map) { + final json = value.cast(); + + return JobSettingsDto( + concurrency: mapValueOfType(json, r'concurrency')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = JobSettingsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = JobSettingsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of JobSettingsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = JobSettingsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'concurrency', + }; +} + diff --git a/mobile/openapi/lib/model/library_response_dto.dart b/mobile/openapi/lib/model/library_response_dto.dart new file mode 100644 index 0000000000..aa9158e591 --- /dev/null +++ b/mobile/openapi/lib/model/library_response_dto.dart @@ -0,0 +1,180 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LibraryResponseDto { + /// Returns a new [LibraryResponseDto] instance. + LibraryResponseDto({ + required this.assetCount, + required this.createdAt, + this.exclusionPatterns = const [], + required this.id, + this.importPaths = const [], + required this.name, + required this.ownerId, + required this.refreshedAt, + required this.updatedAt, + }); + + /// Number of assets + int assetCount; + + /// Creation date + DateTime createdAt; + + /// Exclusion patterns + List exclusionPatterns; + + /// Library ID + String id; + + /// Import paths + List importPaths; + + /// Library name + String name; + + /// Owner user ID + String ownerId; + + /// Last refresh date + DateTime? refreshedAt; + + /// Last update date + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is LibraryResponseDto && + other.assetCount == assetCount && + other.createdAt == createdAt && + _deepEquality.equals(other.exclusionPatterns, exclusionPatterns) && + other.id == id && + _deepEquality.equals(other.importPaths, importPaths) && + other.name == name && + other.ownerId == ownerId && + other.refreshedAt == refreshedAt && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetCount.hashCode) + + (createdAt.hashCode) + + (exclusionPatterns.hashCode) + + (id.hashCode) + + (importPaths.hashCode) + + (name.hashCode) + + (ownerId.hashCode) + + (refreshedAt == null ? 0 : refreshedAt!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'LibraryResponseDto[assetCount=$assetCount, createdAt=$createdAt, exclusionPatterns=$exclusionPatterns, id=$id, importPaths=$importPaths, name=$name, ownerId=$ownerId, refreshedAt=$refreshedAt, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'assetCount'] = this.assetCount; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'exclusionPatterns'] = this.exclusionPatterns; + json[r'id'] = this.id; + json[r'importPaths'] = this.importPaths; + json[r'name'] = this.name; + json[r'ownerId'] = this.ownerId; + if (this.refreshedAt != null) { + json[r'refreshedAt'] = this.refreshedAt!.toUtc().toIso8601String(); + } else { + // json[r'refreshedAt'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [LibraryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LibraryResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LibraryResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LibraryResponseDto( + assetCount: mapValueOfType(json, r'assetCount')!, + createdAt: mapDateTime(json, r'createdAt', r'')!, + exclusionPatterns: json[r'exclusionPatterns'] is Iterable + ? (json[r'exclusionPatterns'] as Iterable).cast().toList(growable: false) + : const [], + id: mapValueOfType(json, r'id')!, + importPaths: json[r'importPaths'] is Iterable + ? (json[r'importPaths'] as Iterable).cast().toList(growable: false) + : const [], + name: mapValueOfType(json, r'name')!, + ownerId: mapValueOfType(json, r'ownerId')!, + refreshedAt: mapDateTime(json, r'refreshedAt', r''), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LibraryResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LibraryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LibraryResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LibraryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetCount', + 'createdAt', + 'exclusionPatterns', + 'id', + 'importPaths', + 'name', + 'ownerId', + 'refreshedAt', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/library_stats_response_dto.dart b/mobile/openapi/lib/model/library_stats_response_dto.dart new file mode 100644 index 0000000000..6eec3ae8d7 --- /dev/null +++ b/mobile/openapi/lib/model/library_stats_response_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LibraryStatsResponseDto { + /// Returns a new [LibraryStatsResponseDto] instance. + LibraryStatsResponseDto({ + this.photos = 0, + this.total = 0, + this.usage = 0, + this.videos = 0, + }); + + /// Number of photos + int photos; + + /// Total number of assets + int total; + + /// Storage usage in bytes + int usage; + + /// Number of videos + int videos; + + @override + bool operator ==(Object other) => identical(this, other) || other is LibraryStatsResponseDto && + other.photos == photos && + other.total == total && + other.usage == usage && + other.videos == videos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (photos.hashCode) + + (total.hashCode) + + (usage.hashCode) + + (videos.hashCode); + + @override + String toString() => 'LibraryStatsResponseDto[photos=$photos, total=$total, usage=$usage, videos=$videos]'; + + Map toJson() { + final json = {}; + json[r'photos'] = this.photos; + json[r'total'] = this.total; + json[r'usage'] = this.usage; + json[r'videos'] = this.videos; + return json; + } + + /// Returns a new [LibraryStatsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LibraryStatsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LibraryStatsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LibraryStatsResponseDto( + photos: mapValueOfType(json, r'photos')!, + total: mapValueOfType(json, r'total')!, + usage: mapValueOfType(json, r'usage')!, + videos: mapValueOfType(json, r'videos')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LibraryStatsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LibraryStatsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LibraryStatsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LibraryStatsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'photos', + 'total', + 'usage', + 'videos', + }; +} + diff --git a/mobile/openapi/lib/model/license_key_dto.dart b/mobile/openapi/lib/model/license_key_dto.dart new file mode 100644 index 0000000000..ea1fee9d7a --- /dev/null +++ b/mobile/openapi/lib/model/license_key_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LicenseKeyDto { + /// Returns a new [LicenseKeyDto] instance. + LicenseKeyDto({ + required this.activationKey, + required this.licenseKey, + }); + + /// Activation key + String activationKey; + + /// License key (format: IM(SV|CL)(-XXXX){8}) + String licenseKey; + + @override + bool operator ==(Object other) => identical(this, other) || other is LicenseKeyDto && + other.activationKey == activationKey && + other.licenseKey == licenseKey; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (activationKey.hashCode) + + (licenseKey.hashCode); + + @override + String toString() => 'LicenseKeyDto[activationKey=$activationKey, licenseKey=$licenseKey]'; + + Map toJson() { + final json = {}; + json[r'activationKey'] = this.activationKey; + json[r'licenseKey'] = this.licenseKey; + return json; + } + + /// Returns a new [LicenseKeyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LicenseKeyDto? fromJson(dynamic value) { + upgradeDto(value, "LicenseKeyDto"); + if (value is Map) { + final json = value.cast(); + + return LicenseKeyDto( + activationKey: mapValueOfType(json, r'activationKey')!, + licenseKey: mapValueOfType(json, r'licenseKey')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LicenseKeyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LicenseKeyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LicenseKeyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LicenseKeyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'activationKey', + 'licenseKey', + }; +} + diff --git a/mobile/openapi/lib/model/license_response_dto.dart b/mobile/openapi/lib/model/license_response_dto.dart new file mode 100644 index 0000000000..84ff72c1eb --- /dev/null +++ b/mobile/openapi/lib/model/license_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LicenseResponseDto { + /// Returns a new [LicenseResponseDto] instance. + LicenseResponseDto({ + required this.activatedAt, + required this.activationKey, + required this.licenseKey, + }); + + /// Activation date + DateTime activatedAt; + + /// Activation key + String activationKey; + + /// License key (format: IM(SV|CL)(-XXXX){8}) + String licenseKey; + + @override + bool operator ==(Object other) => identical(this, other) || other is LicenseResponseDto && + other.activatedAt == activatedAt && + other.activationKey == activationKey && + other.licenseKey == licenseKey; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (activatedAt.hashCode) + + (activationKey.hashCode) + + (licenseKey.hashCode); + + @override + String toString() => 'LicenseResponseDto[activatedAt=$activatedAt, activationKey=$activationKey, licenseKey=$licenseKey]'; + + Map toJson() { + final json = {}; + json[r'activatedAt'] = this.activatedAt.toUtc().toIso8601String(); + json[r'activationKey'] = this.activationKey; + json[r'licenseKey'] = this.licenseKey; + return json; + } + + /// Returns a new [LicenseResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LicenseResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LicenseResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LicenseResponseDto( + activatedAt: mapDateTime(json, r'activatedAt', r'')!, + activationKey: mapValueOfType(json, r'activationKey')!, + licenseKey: mapValueOfType(json, r'licenseKey')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LicenseResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LicenseResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LicenseResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LicenseResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'activatedAt', + 'activationKey', + 'licenseKey', + }; +} + diff --git a/mobile/openapi/lib/model/log_level.dart b/mobile/openapi/lib/model/log_level.dart new file mode 100644 index 0000000000..2129096da2 --- /dev/null +++ b/mobile/openapi/lib/model/log_level.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class LogLevel { + /// Instantiate a new enum with the provided [value]. + const LogLevel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const verbose = LogLevel._(r'verbose'); + static const debug = LogLevel._(r'debug'); + static const log = LogLevel._(r'log'); + static const warn = LogLevel._(r'warn'); + static const error = LogLevel._(r'error'); + static const fatal = LogLevel._(r'fatal'); + + /// List of all possible values in this [enum][LogLevel]. + static const values = [ + verbose, + debug, + log, + warn, + error, + fatal, + ]; + + static LogLevel? fromJson(dynamic value) => LogLevelTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LogLevel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [LogLevel] to String, +/// and [decode] dynamic data back to [LogLevel]. +class LogLevelTypeTransformer { + factory LogLevelTypeTransformer() => _instance ??= const LogLevelTypeTransformer._(); + + const LogLevelTypeTransformer._(); + + String encode(LogLevel data) => data.value; + + /// Decodes a [dynamic value][data] to a LogLevel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + LogLevel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'verbose': return LogLevel.verbose; + case r'debug': return LogLevel.debug; + case r'log': return LogLevel.log; + case r'warn': return LogLevel.warn; + case r'error': return LogLevel.error; + case r'fatal': return LogLevel.fatal; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [LogLevelTypeTransformer] instance. + static LogLevelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/login_credential_dto.dart b/mobile/openapi/lib/model/login_credential_dto.dart new file mode 100644 index 0000000000..1fdfdc3d40 --- /dev/null +++ b/mobile/openapi/lib/model/login_credential_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LoginCredentialDto { + /// Returns a new [LoginCredentialDto] instance. + LoginCredentialDto({ + required this.email, + required this.password, + }); + + /// User email + String email; + + /// User password + String password; + + @override + bool operator ==(Object other) => identical(this, other) || other is LoginCredentialDto && + other.email == email && + other.password == password; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (email.hashCode) + + (password.hashCode); + + @override + String toString() => 'LoginCredentialDto[email=$email, password=$password]'; + + Map toJson() { + final json = {}; + json[r'email'] = this.email; + json[r'password'] = this.password; + return json; + } + + /// Returns a new [LoginCredentialDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LoginCredentialDto? fromJson(dynamic value) { + upgradeDto(value, "LoginCredentialDto"); + if (value is Map) { + final json = value.cast(); + + return LoginCredentialDto( + email: mapValueOfType(json, r'email')!, + password: mapValueOfType(json, r'password')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LoginCredentialDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LoginCredentialDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LoginCredentialDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LoginCredentialDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'email', + 'password', + }; +} + diff --git a/mobile/openapi/lib/model/login_response_dto.dart b/mobile/openapi/lib/model/login_response_dto.dart new file mode 100644 index 0000000000..c6938c2393 --- /dev/null +++ b/mobile/openapi/lib/model/login_response_dto.dart @@ -0,0 +1,163 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LoginResponseDto { + /// Returns a new [LoginResponseDto] instance. + LoginResponseDto({ + required this.accessToken, + required this.isAdmin, + required this.isOnboarded, + required this.name, + required this.profileImagePath, + required this.shouldChangePassword, + required this.userEmail, + required this.userId, + }); + + /// Access token + String accessToken; + + /// Is admin user + bool isAdmin; + + /// Is onboarded + bool isOnboarded; + + /// User name + String name; + + /// Profile image path + String profileImagePath; + + /// Should change password + bool shouldChangePassword; + + /// User email + String userEmail; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is LoginResponseDto && + other.accessToken == accessToken && + other.isAdmin == isAdmin && + other.isOnboarded == isOnboarded && + other.name == name && + other.profileImagePath == profileImagePath && + other.shouldChangePassword == shouldChangePassword && + other.userEmail == userEmail && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (accessToken.hashCode) + + (isAdmin.hashCode) + + (isOnboarded.hashCode) + + (name.hashCode) + + (profileImagePath.hashCode) + + (shouldChangePassword.hashCode) + + (userEmail.hashCode) + + (userId.hashCode); + + @override + String toString() => 'LoginResponseDto[accessToken=$accessToken, isAdmin=$isAdmin, isOnboarded=$isOnboarded, name=$name, profileImagePath=$profileImagePath, shouldChangePassword=$shouldChangePassword, userEmail=$userEmail, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'accessToken'] = this.accessToken; + json[r'isAdmin'] = this.isAdmin; + json[r'isOnboarded'] = this.isOnboarded; + json[r'name'] = this.name; + json[r'profileImagePath'] = this.profileImagePath; + json[r'shouldChangePassword'] = this.shouldChangePassword; + json[r'userEmail'] = this.userEmail; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [LoginResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LoginResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LoginResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LoginResponseDto( + accessToken: mapValueOfType(json, r'accessToken')!, + isAdmin: mapValueOfType(json, r'isAdmin')!, + isOnboarded: mapValueOfType(json, r'isOnboarded')!, + name: mapValueOfType(json, r'name')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, + userEmail: mapValueOfType(json, r'userEmail')!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LoginResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LoginResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LoginResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LoginResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'accessToken', + 'isAdmin', + 'isOnboarded', + 'name', + 'profileImagePath', + 'shouldChangePassword', + 'userEmail', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/logout_response_dto.dart b/mobile/openapi/lib/model/logout_response_dto.dart new file mode 100644 index 0000000000..b50db2c28b --- /dev/null +++ b/mobile/openapi/lib/model/logout_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class LogoutResponseDto { + /// Returns a new [LogoutResponseDto] instance. + LogoutResponseDto({ + required this.redirectUri, + required this.successful, + }); + + /// Redirect URI + String redirectUri; + + /// Logout successful + bool successful; + + @override + bool operator ==(Object other) => identical(this, other) || other is LogoutResponseDto && + other.redirectUri == redirectUri && + other.successful == successful; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (redirectUri.hashCode) + + (successful.hashCode); + + @override + String toString() => 'LogoutResponseDto[redirectUri=$redirectUri, successful=$successful]'; + + Map toJson() { + final json = {}; + json[r'redirectUri'] = this.redirectUri; + json[r'successful'] = this.successful; + return json; + } + + /// Returns a new [LogoutResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static LogoutResponseDto? fromJson(dynamic value) { + upgradeDto(value, "LogoutResponseDto"); + if (value is Map) { + final json = value.cast(); + + return LogoutResponseDto( + redirectUri: mapValueOfType(json, r'redirectUri')!, + successful: mapValueOfType(json, r'successful')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = LogoutResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = LogoutResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of LogoutResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = LogoutResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'redirectUri', + 'successful', + }; +} + diff --git a/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart new file mode 100644 index 0000000000..dc0cf5fac0 --- /dev/null +++ b/mobile/openapi/lib/model/machine_learning_availability_checks_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MachineLearningAvailabilityChecksDto { + /// Returns a new [MachineLearningAvailabilityChecksDto] instance. + MachineLearningAvailabilityChecksDto({ + required this.enabled, + required this.interval, + required this.timeout, + }); + + /// Enabled + bool enabled; + + num interval; + + num timeout; + + @override + bool operator ==(Object other) => identical(this, other) || other is MachineLearningAvailabilityChecksDto && + other.enabled == enabled && + other.interval == interval && + other.timeout == timeout; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (interval.hashCode) + + (timeout.hashCode); + + @override + String toString() => 'MachineLearningAvailabilityChecksDto[enabled=$enabled, interval=$interval, timeout=$timeout]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'interval'] = this.interval; + json[r'timeout'] = this.timeout; + return json; + } + + /// Returns a new [MachineLearningAvailabilityChecksDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MachineLearningAvailabilityChecksDto? fromJson(dynamic value) { + upgradeDto(value, "MachineLearningAvailabilityChecksDto"); + if (value is Map) { + final json = value.cast(); + + return MachineLearningAvailabilityChecksDto( + enabled: mapValueOfType(json, r'enabled')!, + interval: num.parse('${json[r'interval']}'), + timeout: num.parse('${json[r'timeout']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MachineLearningAvailabilityChecksDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MachineLearningAvailabilityChecksDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MachineLearningAvailabilityChecksDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MachineLearningAvailabilityChecksDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'interval', + 'timeout', + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_action.dart b/mobile/openapi/lib/model/maintenance_action.dart new file mode 100644 index 0000000000..ebf5ec0f71 --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_action.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Maintenance action +class MaintenanceAction { + /// Instantiate a new enum with the provided [value]. + const MaintenanceAction._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const start = MaintenanceAction._(r'start'); + static const end = MaintenanceAction._(r'end'); + static const selectDatabaseRestore = MaintenanceAction._(r'select_database_restore'); + static const restoreDatabase = MaintenanceAction._(r'restore_database'); + + /// List of all possible values in this [enum][MaintenanceAction]. + static const values = [ + start, + end, + selectDatabaseRestore, + restoreDatabase, + ]; + + static MaintenanceAction? fromJson(dynamic value) => MaintenanceActionTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceAction.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MaintenanceAction] to String, +/// and [decode] dynamic data back to [MaintenanceAction]. +class MaintenanceActionTypeTransformer { + factory MaintenanceActionTypeTransformer() => _instance ??= const MaintenanceActionTypeTransformer._(); + + const MaintenanceActionTypeTransformer._(); + + String encode(MaintenanceAction data) => data.value; + + /// Decodes a [dynamic value][data] to a MaintenanceAction. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MaintenanceAction? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'start': return MaintenanceAction.start; + case r'end': return MaintenanceAction.end; + case r'select_database_restore': return MaintenanceAction.selectDatabaseRestore; + case r'restore_database': return MaintenanceAction.restoreDatabase; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MaintenanceActionTypeTransformer] instance. + static MaintenanceActionTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/maintenance_auth_dto.dart b/mobile/openapi/lib/model/maintenance_auth_dto.dart new file mode 100644 index 0000000000..f9511bdd2b --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_auth_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MaintenanceAuthDto { + /// Returns a new [MaintenanceAuthDto] instance. + MaintenanceAuthDto({ + required this.username, + }); + + /// Maintenance username + String username; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceAuthDto && + other.username == username; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (username.hashCode); + + @override + String toString() => 'MaintenanceAuthDto[username=$username]'; + + Map toJson() { + final json = {}; + json[r'username'] = this.username; + return json; + } + + /// Returns a new [MaintenanceAuthDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceAuthDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceAuthDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceAuthDto( + username: mapValueOfType(json, r'username')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceAuthDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceAuthDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceAuthDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceAuthDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'username', + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_detect_install_response_dto.dart b/mobile/openapi/lib/model/maintenance_detect_install_response_dto.dart new file mode 100644 index 0000000000..1c364a6fdc --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_detect_install_response_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MaintenanceDetectInstallResponseDto { + /// Returns a new [MaintenanceDetectInstallResponseDto] instance. + MaintenanceDetectInstallResponseDto({ + this.storage = const [], + }); + + List storage; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceDetectInstallResponseDto && + _deepEquality.equals(other.storage, storage); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (storage.hashCode); + + @override + String toString() => 'MaintenanceDetectInstallResponseDto[storage=$storage]'; + + Map toJson() { + final json = {}; + json[r'storage'] = this.storage; + return json; + } + + /// Returns a new [MaintenanceDetectInstallResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceDetectInstallResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceDetectInstallResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceDetectInstallResponseDto( + storage: MaintenanceDetectInstallStorageFolderDto.listFromJson(json[r'storage']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceDetectInstallResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceDetectInstallResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceDetectInstallResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceDetectInstallResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'storage', + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart b/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart new file mode 100644 index 0000000000..ad524914b4 --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_detect_install_storage_folder_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MaintenanceDetectInstallStorageFolderDto { + /// Returns a new [MaintenanceDetectInstallStorageFolderDto] instance. + MaintenanceDetectInstallStorageFolderDto({ + required this.files, + required this.folder, + required this.readable, + required this.writable, + }); + + /// Number of files in the folder + num files; + + /// Storage folder + StorageFolder folder; + + /// Whether the folder is readable + bool readable; + + /// Whether the folder is writable + bool writable; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceDetectInstallStorageFolderDto && + other.files == files && + other.folder == folder && + other.readable == readable && + other.writable == writable; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (files.hashCode) + + (folder.hashCode) + + (readable.hashCode) + + (writable.hashCode); + + @override + String toString() => 'MaintenanceDetectInstallStorageFolderDto[files=$files, folder=$folder, readable=$readable, writable=$writable]'; + + Map toJson() { + final json = {}; + json[r'files'] = this.files; + json[r'folder'] = this.folder; + json[r'readable'] = this.readable; + json[r'writable'] = this.writable; + return json; + } + + /// Returns a new [MaintenanceDetectInstallStorageFolderDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceDetectInstallStorageFolderDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceDetectInstallStorageFolderDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceDetectInstallStorageFolderDto( + files: num.parse('${json[r'files']}'), + folder: StorageFolder.fromJson(json[r'folder'])!, + readable: mapValueOfType(json, r'readable')!, + writable: mapValueOfType(json, r'writable')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceDetectInstallStorageFolderDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceDetectInstallStorageFolderDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceDetectInstallStorageFolderDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceDetectInstallStorageFolderDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'files', + 'folder', + 'readable', + 'writable', + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_login_dto.dart b/mobile/openapi/lib/model/maintenance_login_dto.dart new file mode 100644 index 0000000000..64cf6b234b --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_login_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MaintenanceLoginDto { + /// Returns a new [MaintenanceLoginDto] instance. + MaintenanceLoginDto({ + this.token, + }); + + /// Maintenance token + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? token; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceLoginDto && + other.token == token; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (token == null ? 0 : token!.hashCode); + + @override + String toString() => 'MaintenanceLoginDto[token=$token]'; + + Map toJson() { + final json = {}; + if (this.token != null) { + json[r'token'] = this.token; + } else { + // json[r'token'] = null; + } + return json; + } + + /// Returns a new [MaintenanceLoginDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceLoginDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceLoginDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceLoginDto( + token: mapValueOfType(json, r'token'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceLoginDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceLoginDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceLoginDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceLoginDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/maintenance_status_response_dto.dart b/mobile/openapi/lib/model/maintenance_status_response_dto.dart new file mode 100644 index 0000000000..52dbb5b95b --- /dev/null +++ b/mobile/openapi/lib/model/maintenance_status_response_dto.dart @@ -0,0 +1,159 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MaintenanceStatusResponseDto { + /// Returns a new [MaintenanceStatusResponseDto] instance. + MaintenanceStatusResponseDto({ + required this.action, + required this.active, + this.error, + this.progress, + this.task, + }); + + /// Maintenance action + MaintenanceAction action; + + bool active; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? error; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? progress; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? task; + + @override + bool operator ==(Object other) => identical(this, other) || other is MaintenanceStatusResponseDto && + other.action == action && + other.active == active && + other.error == error && + other.progress == progress && + other.task == task; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (active.hashCode) + + (error == null ? 0 : error!.hashCode) + + (progress == null ? 0 : progress!.hashCode) + + (task == null ? 0 : task!.hashCode); + + @override + String toString() => 'MaintenanceStatusResponseDto[action=$action, active=$active, error=$error, progress=$progress, task=$task]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + json[r'active'] = this.active; + if (this.error != null) { + json[r'error'] = this.error; + } else { + // json[r'error'] = null; + } + if (this.progress != null) { + json[r'progress'] = this.progress; + } else { + // json[r'progress'] = null; + } + if (this.task != null) { + json[r'task'] = this.task; + } else { + // json[r'task'] = null; + } + return json; + } + + /// Returns a new [MaintenanceStatusResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MaintenanceStatusResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MaintenanceStatusResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MaintenanceStatusResponseDto( + action: MaintenanceAction.fromJson(json[r'action'])!, + active: mapValueOfType(json, r'active')!, + error: mapValueOfType(json, r'error'), + progress: num.parse('${json[r'progress']}'), + task: mapValueOfType(json, r'task'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MaintenanceStatusResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MaintenanceStatusResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MaintenanceStatusResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MaintenanceStatusResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + 'active', + }; +} + diff --git a/mobile/openapi/lib/model/manual_job_name.dart b/mobile/openapi/lib/model/manual_job_name.dart new file mode 100644 index 0000000000..d09790a81a --- /dev/null +++ b/mobile/openapi/lib/model/manual_job_name.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Job name +class ManualJobName { + /// Instantiate a new enum with the provided [value]. + const ManualJobName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const personCleanup = ManualJobName._(r'person-cleanup'); + static const tagCleanup = ManualJobName._(r'tag-cleanup'); + static const userCleanup = ManualJobName._(r'user-cleanup'); + static const memoryCleanup = ManualJobName._(r'memory-cleanup'); + static const memoryCreate = ManualJobName._(r'memory-create'); + static const backupDatabase = ManualJobName._(r'backup-database'); + + /// List of all possible values in this [enum][ManualJobName]. + static const values = [ + personCleanup, + tagCleanup, + userCleanup, + memoryCleanup, + memoryCreate, + backupDatabase, + ]; + + static ManualJobName? fromJson(dynamic value) => ManualJobNameTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ManualJobName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ManualJobName] to String, +/// and [decode] dynamic data back to [ManualJobName]. +class ManualJobNameTypeTransformer { + factory ManualJobNameTypeTransformer() => _instance ??= const ManualJobNameTypeTransformer._(); + + const ManualJobNameTypeTransformer._(); + + String encode(ManualJobName data) => data.value; + + /// Decodes a [dynamic value][data] to a ManualJobName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ManualJobName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'person-cleanup': return ManualJobName.personCleanup; + case r'tag-cleanup': return ManualJobName.tagCleanup; + case r'user-cleanup': return ManualJobName.userCleanup; + case r'memory-cleanup': return ManualJobName.memoryCleanup; + case r'memory-create': return ManualJobName.memoryCreate; + case r'backup-database': return ManualJobName.backupDatabase; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ManualJobNameTypeTransformer] instance. + static ManualJobNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/map_marker_response_dto.dart b/mobile/openapi/lib/model/map_marker_response_dto.dart new file mode 100644 index 0000000000..c0a47a5458 --- /dev/null +++ b/mobile/openapi/lib/model/map_marker_response_dto.dart @@ -0,0 +1,157 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MapMarkerResponseDto { + /// Returns a new [MapMarkerResponseDto] instance. + MapMarkerResponseDto({ + required this.city, + required this.country, + required this.id, + required this.lat, + required this.lon, + required this.state, + }); + + /// City name + String? city; + + /// Country name + String? country; + + /// Asset ID + String id; + + /// Latitude + double lat; + + /// Longitude + double lon; + + /// State/Province name + String? state; + + @override + bool operator ==(Object other) => identical(this, other) || other is MapMarkerResponseDto && + other.city == city && + other.country == country && + other.id == id && + other.lat == lat && + other.lon == lon && + other.state == state; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (id.hashCode) + + (lat.hashCode) + + (lon.hashCode) + + (state == null ? 0 : state!.hashCode); + + @override + String toString() => 'MapMarkerResponseDto[city=$city, country=$country, id=$id, lat=$lat, lon=$lon, state=$state]'; + + Map toJson() { + final json = {}; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + json[r'id'] = this.id; + json[r'lat'] = this.lat; + json[r'lon'] = this.lon; + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + return json; + } + + /// Returns a new [MapMarkerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MapMarkerResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MapMarkerResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MapMarkerResponseDto( + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + id: mapValueOfType(json, r'id')!, + lat: (mapValueOfType(json, r'lat')!).toDouble(), + lon: (mapValueOfType(json, r'lon')!).toDouble(), + state: mapValueOfType(json, r'state'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MapMarkerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MapMarkerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MapMarkerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MapMarkerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'city', + 'country', + 'id', + 'lat', + 'lon', + 'state', + }; +} + diff --git a/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart b/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart new file mode 100644 index 0000000000..85435485e6 --- /dev/null +++ b/mobile/openapi/lib/model/map_reverse_geocode_response_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MapReverseGeocodeResponseDto { + /// Returns a new [MapReverseGeocodeResponseDto] instance. + MapReverseGeocodeResponseDto({ + required this.city, + required this.country, + required this.state, + }); + + /// City name + String? city; + + /// Country name + String? country; + + /// State/Province name + String? state; + + @override + bool operator ==(Object other) => identical(this, other) || other is MapReverseGeocodeResponseDto && + other.city == city && + other.country == country && + other.state == state; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (state == null ? 0 : state!.hashCode); + + @override + String toString() => 'MapReverseGeocodeResponseDto[city=$city, country=$country, state=$state]'; + + Map toJson() { + final json = {}; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + return json; + } + + /// Returns a new [MapReverseGeocodeResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MapReverseGeocodeResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MapReverseGeocodeResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MapReverseGeocodeResponseDto( + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + state: mapValueOfType(json, r'state'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MapReverseGeocodeResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MapReverseGeocodeResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MapReverseGeocodeResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MapReverseGeocodeResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'city', + 'country', + 'state', + }; +} + diff --git a/mobile/openapi/lib/model/memories_response.dart b/mobile/openapi/lib/model/memories_response.dart new file mode 100644 index 0000000000..63d4094cd0 --- /dev/null +++ b/mobile/openapi/lib/model/memories_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoriesResponse { + /// Returns a new [MemoriesResponse] instance. + MemoriesResponse({ + this.duration = 5, + this.enabled = true, + }); + + /// Memory duration in seconds + int duration; + + /// Whether memories are enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoriesResponse && + other.duration == duration && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (duration.hashCode) + + (enabled.hashCode); + + @override + String toString() => 'MemoriesResponse[duration=$duration, enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'duration'] = this.duration; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [MemoriesResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoriesResponse? fromJson(dynamic value) { + upgradeDto(value, "MemoriesResponse"); + if (value is Map) { + final json = value.cast(); + + return MemoriesResponse( + duration: mapValueOfType(json, r'duration')!, + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoriesResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoriesResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoriesResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoriesResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'duration', + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/memories_update.dart b/mobile/openapi/lib/model/memories_update.dart new file mode 100644 index 0000000000..d27cef022d --- /dev/null +++ b/mobile/openapi/lib/model/memories_update.dart @@ -0,0 +1,129 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoriesUpdate { + /// Returns a new [MemoriesUpdate] instance. + MemoriesUpdate({ + this.duration, + this.enabled, + }); + + /// Memory duration in seconds + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + int? duration; + + /// Whether memories are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoriesUpdate && + other.duration == duration && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (duration == null ? 0 : duration!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode); + + @override + String toString() => 'MemoriesUpdate[duration=$duration, enabled=$enabled]'; + + Map toJson() { + final json = {}; + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + return json; + } + + /// Returns a new [MemoriesUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoriesUpdate? fromJson(dynamic value) { + upgradeDto(value, "MemoriesUpdate"); + if (value is Map) { + final json = value.cast(); + + return MemoriesUpdate( + duration: mapValueOfType(json, r'duration'), + enabled: mapValueOfType(json, r'enabled'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoriesUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoriesUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoriesUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoriesUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/memory_create_dto.dart b/mobile/openapi/lib/model/memory_create_dto.dart new file mode 100644 index 0000000000..5b8eeed8fb --- /dev/null +++ b/mobile/openapi/lib/model/memory_create_dto.dart @@ -0,0 +1,199 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoryCreateDto { + /// Returns a new [MemoryCreateDto] instance. + MemoryCreateDto({ + this.assetIds = const [], + required this.data, + this.hideAt, + this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + required this.type, + }); + + /// Asset IDs to associate with memory + List assetIds; + + OnThisDayDto data; + + /// Date when memory should be hidden + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? hideAt; + + /// Is memory saved + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isSaved; + + /// Memory date + DateTime memoryAt; + + /// Date when memory was seen + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? seenAt; + + /// Date when memory should be shown + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? showAt; + + /// Memory type + MemoryType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryCreateDto && + _deepEquality.equals(other.assetIds, assetIds) && + other.data == data && + other.hideAt == hideAt && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.seenAt == seenAt && + other.showAt == showAt && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (data.hashCode) + + (hideAt == null ? 0 : hideAt!.hashCode) + + (isSaved == null ? 0 : isSaved!.hashCode) + + (memoryAt.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode) + + (showAt == null ? 0 : showAt!.hashCode) + + (type.hashCode); + + @override + String toString() => 'MemoryCreateDto[assetIds=$assetIds, data=$data, hideAt=$hideAt, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, showAt=$showAt, type=$type]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'data'] = this.data; + if (this.hideAt != null) { + json[r'hideAt'] = this.hideAt!.toUtc().toIso8601String(); + } else { + // json[r'hideAt'] = null; + } + if (this.isSaved != null) { + json[r'isSaved'] = this.isSaved; + } else { + // json[r'isSaved'] = null; + } + json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String(); + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + if (this.showAt != null) { + json[r'showAt'] = this.showAt!.toUtc().toIso8601String(); + } else { + // json[r'showAt'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [MemoryCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryCreateDto? fromJson(dynamic value) { + upgradeDto(value, "MemoryCreateDto"); + if (value is Map) { + final json = value.cast(); + + return MemoryCreateDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + data: OnThisDayDto.fromJson(json[r'data'])!, + hideAt: mapDateTime(json, r'hideAt', r''), + isSaved: mapValueOfType(json, r'isSaved'), + memoryAt: mapDateTime(json, r'memoryAt', r'')!, + seenAt: mapDateTime(json, r'seenAt', r''), + showAt: mapDateTime(json, r'showAt', r''), + type: MemoryType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoryCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoryCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'data', + 'memoryAt', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/memory_response_dto.dart b/mobile/openapi/lib/model/memory_response_dto.dart new file mode 100644 index 0000000000..1835095cf7 --- /dev/null +++ b/mobile/openapi/lib/model/memory_response_dto.dart @@ -0,0 +1,242 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoryResponseDto { + /// Returns a new [MemoryResponseDto] instance. + MemoryResponseDto({ + this.assets = const [], + required this.createdAt, + required this.data, + this.deletedAt, + this.hideAt, + required this.id, + required this.isSaved, + required this.memoryAt, + required this.ownerId, + this.seenAt, + this.showAt, + required this.type, + required this.updatedAt, + }); + + List assets; + + /// Creation date + DateTime createdAt; + + OnThisDayDto data; + + /// Deletion date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? deletedAt; + + /// Date when memory should be hidden + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? hideAt; + + /// Memory ID + String id; + + /// Is memory saved + bool isSaved; + + /// Memory date + DateTime memoryAt; + + /// Owner user ID + String ownerId; + + /// Date when memory was seen + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? seenAt; + + /// Date when memory should be shown + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? showAt; + + /// Memory type + MemoryType type; + + /// Last update date + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryResponseDto && + _deepEquality.equals(other.assets, assets) && + other.createdAt == createdAt && + other.data == data && + other.deletedAt == deletedAt && + other.hideAt == hideAt && + other.id == id && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.ownerId == ownerId && + other.seenAt == seenAt && + other.showAt == showAt && + other.type == type && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode) + + (createdAt.hashCode) + + (data.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (hideAt == null ? 0 : hideAt!.hashCode) + + (id.hashCode) + + (isSaved.hashCode) + + (memoryAt.hashCode) + + (ownerId.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode) + + (showAt == null ? 0 : showAt!.hashCode) + + (type.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'MemoryResponseDto[assets=$assets, createdAt=$createdAt, data=$data, deletedAt=$deletedAt, hideAt=$hideAt, id=$id, isSaved=$isSaved, memoryAt=$memoryAt, ownerId=$ownerId, seenAt=$seenAt, showAt=$showAt, type=$type, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'data'] = this.data; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + if (this.hideAt != null) { + json[r'hideAt'] = this.hideAt!.toUtc().toIso8601String(); + } else { + // json[r'hideAt'] = null; + } + json[r'id'] = this.id; + json[r'isSaved'] = this.isSaved; + json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String(); + json[r'ownerId'] = this.ownerId; + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + if (this.showAt != null) { + json[r'showAt'] = this.showAt!.toUtc().toIso8601String(); + } else { + // json[r'showAt'] = null; + } + json[r'type'] = this.type; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [MemoryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MemoryResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MemoryResponseDto( + assets: AssetResponseDto.listFromJson(json[r'assets']), + createdAt: mapDateTime(json, r'createdAt', r'')!, + data: OnThisDayDto.fromJson(json[r'data'])!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + hideAt: mapDateTime(json, r'hideAt', r''), + id: mapValueOfType(json, r'id')!, + isSaved: mapValueOfType(json, r'isSaved')!, + memoryAt: mapDateTime(json, r'memoryAt', r'')!, + ownerId: mapValueOfType(json, r'ownerId')!, + seenAt: mapDateTime(json, r'seenAt', r''), + showAt: mapDateTime(json, r'showAt', r''), + type: MemoryType.fromJson(json[r'type'])!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + 'createdAt', + 'data', + 'id', + 'isSaved', + 'memoryAt', + 'ownerId', + 'type', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/memory_search_order.dart b/mobile/openapi/lib/model/memory_search_order.dart new file mode 100644 index 0000000000..bdf5b59894 --- /dev/null +++ b/mobile/openapi/lib/model/memory_search_order.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MemorySearchOrder { + /// Instantiate a new enum with the provided [value]. + const MemorySearchOrder._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const asc = MemorySearchOrder._(r'asc'); + static const desc = MemorySearchOrder._(r'desc'); + static const random = MemorySearchOrder._(r'random'); + + /// List of all possible values in this [enum][MemorySearchOrder]. + static const values = [ + asc, + desc, + random, + ]; + + static MemorySearchOrder? fromJson(dynamic value) => MemorySearchOrderTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemorySearchOrder.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MemorySearchOrder] to String, +/// and [decode] dynamic data back to [MemorySearchOrder]. +class MemorySearchOrderTypeTransformer { + factory MemorySearchOrderTypeTransformer() => _instance ??= const MemorySearchOrderTypeTransformer._(); + + const MemorySearchOrderTypeTransformer._(); + + String encode(MemorySearchOrder data) => data.value; + + /// Decodes a [dynamic value][data] to a MemorySearchOrder. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MemorySearchOrder? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'asc': return MemorySearchOrder.asc; + case r'desc': return MemorySearchOrder.desc; + case r'random': return MemorySearchOrder.random; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MemorySearchOrderTypeTransformer] instance. + static MemorySearchOrderTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/memory_statistics_response_dto.dart b/mobile/openapi/lib/model/memory_statistics_response_dto.dart new file mode 100644 index 0000000000..bde78de481 --- /dev/null +++ b/mobile/openapi/lib/model/memory_statistics_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoryStatisticsResponseDto { + /// Returns a new [MemoryStatisticsResponseDto] instance. + MemoryStatisticsResponseDto({ + required this.total, + }); + + /// Total number of memories + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryStatisticsResponseDto && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (total.hashCode); + + @override + String toString() => 'MemoryStatisticsResponseDto[total=$total]'; + + Map toJson() { + final json = {}; + json[r'total'] = this.total; + return json; + } + + /// Returns a new [MemoryStatisticsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryStatisticsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "MemoryStatisticsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return MemoryStatisticsResponseDto( + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryStatisticsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoryStatisticsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryStatisticsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoryStatisticsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'total', + }; +} + diff --git a/mobile/openapi/lib/model/memory_type.dart b/mobile/openapi/lib/model/memory_type.dart new file mode 100644 index 0000000000..aee7bd1ba1 --- /dev/null +++ b/mobile/openapi/lib/model/memory_type.dart @@ -0,0 +1,82 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class MemoryType { + /// Instantiate a new enum with the provided [value]. + const MemoryType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const onThisDay = MemoryType._(r'on_this_day'); + + /// List of all possible values in this [enum][MemoryType]. + static const values = [ + onThisDay, + ]; + + static MemoryType? fromJson(dynamic value) => MemoryTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MemoryType] to String, +/// and [decode] dynamic data back to [MemoryType]. +class MemoryTypeTypeTransformer { + factory MemoryTypeTypeTransformer() => _instance ??= const MemoryTypeTypeTransformer._(); + + const MemoryTypeTypeTransformer._(); + + String encode(MemoryType data) => data.value; + + /// Decodes a [dynamic value][data] to a MemoryType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MemoryType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'on_this_day': return MemoryType.onThisDay; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MemoryTypeTypeTransformer] instance. + static MemoryTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/memory_update_dto.dart b/mobile/openapi/lib/model/memory_update_dto.dart new file mode 100644 index 0000000000..4905b161bf --- /dev/null +++ b/mobile/openapi/lib/model/memory_update_dto.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MemoryUpdateDto { + /// Returns a new [MemoryUpdateDto] instance. + MemoryUpdateDto({ + this.isSaved, + this.memoryAt, + this.seenAt, + }); + + /// Is memory saved + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isSaved; + + /// Memory date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? memoryAt; + + /// Date when memory was seen + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? seenAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is MemoryUpdateDto && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.seenAt == seenAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isSaved == null ? 0 : isSaved!.hashCode) + + (memoryAt == null ? 0 : memoryAt!.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode); + + @override + String toString() => 'MemoryUpdateDto[isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt]'; + + Map toJson() { + final json = {}; + if (this.isSaved != null) { + json[r'isSaved'] = this.isSaved; + } else { + // json[r'isSaved'] = null; + } + if (this.memoryAt != null) { + json[r'memoryAt'] = this.memoryAt!.toUtc().toIso8601String(); + } else { + // json[r'memoryAt'] = null; + } + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + return json; + } + + /// Returns a new [MemoryUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MemoryUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "MemoryUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return MemoryUpdateDto( + isSaved: mapValueOfType(json, r'isSaved'), + memoryAt: mapDateTime(json, r'memoryAt', r''), + seenAt: mapDateTime(json, r'seenAt', r''), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MemoryUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MemoryUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MemoryUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MemoryUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/merge_person_dto.dart b/mobile/openapi/lib/model/merge_person_dto.dart new file mode 100644 index 0000000000..8a647890c3 --- /dev/null +++ b/mobile/openapi/lib/model/merge_person_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MergePersonDto { + /// Returns a new [MergePersonDto] instance. + MergePersonDto({ + this.ids = const [], + }); + + /// Person IDs to merge + List ids; + + @override + bool operator ==(Object other) => identical(this, other) || other is MergePersonDto && + _deepEquality.equals(other.ids, ids); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ids.hashCode); + + @override + String toString() => 'MergePersonDto[ids=$ids]'; + + Map toJson() { + final json = {}; + json[r'ids'] = this.ids; + return json; + } + + /// Returns a new [MergePersonDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MergePersonDto? fromJson(dynamic value) { + upgradeDto(value, "MergePersonDto"); + if (value is Map) { + final json = value.cast(); + + return MergePersonDto( + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MergePersonDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MergePersonDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MergePersonDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MergePersonDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/metadata_search_dto.dart b/mobile/openapi/lib/model/metadata_search_dto.dart new file mode 100644 index 0000000000..4dbc90d407 --- /dev/null +++ b/mobile/openapi/lib/model/metadata_search_dto.dart @@ -0,0 +1,821 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MetadataSearchDto { + /// Returns a new [MetadataSearchDto] instance. + MetadataSearchDto({ + this.albumIds = const [], + this.checksum, + this.city, + this.country, + this.createdAfter, + this.createdBefore, + this.description, + this.deviceAssetId, + this.deviceId, + this.encodedVideoPath, + this.id, + this.isEncoded, + this.isFavorite, + this.isMotion, + this.isNotInAlbum, + this.isOffline, + this.lensModel, + this.libraryId, + this.make, + this.model, + this.ocr, + this.order = AssetOrder.desc, + this.originalFileName, + this.originalPath, + this.page, + this.personIds = const [], + this.previewPath, + this.rating, + this.size, + this.state, + this.tagIds = const [], + this.takenAfter, + this.takenBefore, + this.thumbnailPath, + this.trashedAfter, + this.trashedBefore, + this.type, + this.updatedAfter, + this.updatedBefore, + this.visibility, + this.withDeleted, + this.withExif, + this.withPeople, + this.withStacked, + }); + + /// Filter by album IDs + List albumIds; + + /// Filter by file checksum + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? checksum; + + /// Filter by city name + String? city; + + /// Filter by country name + String? country; + + /// Filter by creation date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdAfter; + + /// Filter by creation date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdBefore; + + /// Filter by description text + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Filter by device asset ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceAssetId; + + /// Device ID to filter by + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceId; + + /// Filter by encoded video file path + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? encodedVideoPath; + + /// Filter by asset ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? id; + + /// Filter by encoded status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isEncoded; + + /// Filter by favorite status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Filter by motion photo status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isMotion; + + /// Filter assets not in any album + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isNotInAlbum; + + /// Filter by offline status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isOffline; + + /// Filter by lens model + String? lensModel; + + /// Library ID to filter by + String? libraryId; + + /// Filter by camera make + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? make; + + /// Filter by camera model + String? model; + + /// Filter by OCR text content + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + + /// Sort order + AssetOrder order; + + /// Filter by original file name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? originalFileName; + + /// Filter by original file path + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? originalPath; + + /// Page number + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? page; + + /// Filter by person IDs + List personIds; + + /// Filter by preview file path + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? previewPath; + + /// Filter by rating [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Number of results to return + /// + /// Minimum value: 1 + /// Maximum value: 1000 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? size; + + /// Filter by state/province name + String? state; + + /// Filter by tag IDs + List? tagIds; + + /// Filter by taken date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenAfter; + + /// Filter by taken date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenBefore; + + /// Filter by thumbnail file path + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thumbnailPath; + + /// Filter by trash date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedAfter; + + /// Filter by trash date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedBefore; + + /// Asset type filter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetTypeEnum? type; + + /// Filter by update date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAfter; + + /// Filter by update date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedBefore; + + /// Filter by visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + /// Include deleted assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withDeleted; + + /// Include EXIF data in response + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withExif; + + /// Include people data in response + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withPeople; + + /// Include stacked assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withStacked; + + @override + bool operator ==(Object other) => identical(this, other) || other is MetadataSearchDto && + _deepEquality.equals(other.albumIds, albumIds) && + other.checksum == checksum && + other.city == city && + other.country == country && + other.createdAfter == createdAfter && + other.createdBefore == createdBefore && + other.description == description && + other.deviceAssetId == deviceAssetId && + other.deviceId == deviceId && + other.encodedVideoPath == encodedVideoPath && + other.id == id && + other.isEncoded == isEncoded && + other.isFavorite == isFavorite && + other.isMotion == isMotion && + other.isNotInAlbum == isNotInAlbum && + other.isOffline == isOffline && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.make == make && + other.model == model && + other.ocr == ocr && + other.order == order && + other.originalFileName == originalFileName && + other.originalPath == originalPath && + other.page == page && + _deepEquality.equals(other.personIds, personIds) && + other.previewPath == previewPath && + other.rating == rating && + other.size == size && + other.state == state && + _deepEquality.equals(other.tagIds, tagIds) && + other.takenAfter == takenAfter && + other.takenBefore == takenBefore && + other.thumbnailPath == thumbnailPath && + other.trashedAfter == trashedAfter && + other.trashedBefore == trashedBefore && + other.type == type && + other.updatedAfter == updatedAfter && + other.updatedBefore == updatedBefore && + other.visibility == visibility && + other.withDeleted == withDeleted && + other.withExif == withExif && + other.withPeople == withPeople && + other.withStacked == withStacked; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumIds.hashCode) + + (checksum == null ? 0 : checksum!.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAfter == null ? 0 : createdAfter!.hashCode) + + (createdBefore == null ? 0 : createdBefore!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (deviceAssetId == null ? 0 : deviceAssetId!.hashCode) + + (deviceId == null ? 0 : deviceId!.hashCode) + + (encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) + + (id == null ? 0 : id!.hashCode) + + (isEncoded == null ? 0 : isEncoded!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isMotion == null ? 0 : isMotion!.hashCode) + + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + + (isOffline == null ? 0 : isOffline!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + + (order.hashCode) + + (originalFileName == null ? 0 : originalFileName!.hashCode) + + (originalPath == null ? 0 : originalPath!.hashCode) + + (page == null ? 0 : page!.hashCode) + + (personIds.hashCode) + + (previewPath == null ? 0 : previewPath!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (size == null ? 0 : size!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (tagIds == null ? 0 : tagIds!.hashCode) + + (takenAfter == null ? 0 : takenAfter!.hashCode) + + (takenBefore == null ? 0 : takenBefore!.hashCode) + + (thumbnailPath == null ? 0 : thumbnailPath!.hashCode) + + (trashedAfter == null ? 0 : trashedAfter!.hashCode) + + (trashedBefore == null ? 0 : trashedBefore!.hashCode) + + (type == null ? 0 : type!.hashCode) + + (updatedAfter == null ? 0 : updatedAfter!.hashCode) + + (updatedBefore == null ? 0 : updatedBefore!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode) + + (withDeleted == null ? 0 : withDeleted!.hashCode) + + (withExif == null ? 0 : withExif!.hashCode) + + (withPeople == null ? 0 : withPeople!.hashCode) + + (withStacked == null ? 0 : withStacked!.hashCode); + + @override + String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + + Map toJson() { + final json = {}; + json[r'albumIds'] = this.albumIds; + if (this.checksum != null) { + json[r'checksum'] = this.checksum; + } else { + // json[r'checksum'] = null; + } + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.createdAfter != null) { + json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String(); + } else { + // json[r'createdAfter'] = null; + } + if (this.createdBefore != null) { + json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String(); + } else { + // json[r'createdBefore'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.deviceAssetId != null) { + json[r'deviceAssetId'] = this.deviceAssetId; + } else { + // json[r'deviceAssetId'] = null; + } + if (this.deviceId != null) { + json[r'deviceId'] = this.deviceId; + } else { + // json[r'deviceId'] = null; + } + if (this.encodedVideoPath != null) { + json[r'encodedVideoPath'] = this.encodedVideoPath; + } else { + // json[r'encodedVideoPath'] = null; + } + if (this.id != null) { + json[r'id'] = this.id; + } else { + // json[r'id'] = null; + } + if (this.isEncoded != null) { + json[r'isEncoded'] = this.isEncoded; + } else { + // json[r'isEncoded'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isMotion != null) { + json[r'isMotion'] = this.isMotion; + } else { + // json[r'isMotion'] = null; + } + if (this.isNotInAlbum != null) { + json[r'isNotInAlbum'] = this.isNotInAlbum; + } else { + // json[r'isNotInAlbum'] = null; + } + if (this.isOffline != null) { + json[r'isOffline'] = this.isOffline; + } else { + // json[r'isOffline'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } + json[r'order'] = this.order; + if (this.originalFileName != null) { + json[r'originalFileName'] = this.originalFileName; + } else { + // json[r'originalFileName'] = null; + } + if (this.originalPath != null) { + json[r'originalPath'] = this.originalPath; + } else { + // json[r'originalPath'] = null; + } + if (this.page != null) { + json[r'page'] = this.page; + } else { + // json[r'page'] = null; + } + json[r'personIds'] = this.personIds; + if (this.previewPath != null) { + json[r'previewPath'] = this.previewPath; + } else { + // json[r'previewPath'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.size != null) { + json[r'size'] = this.size; + } else { + // json[r'size'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.tagIds != null) { + json[r'tagIds'] = this.tagIds; + } else { + // json[r'tagIds'] = null; + } + if (this.takenAfter != null) { + json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String(); + } else { + // json[r'takenAfter'] = null; + } + if (this.takenBefore != null) { + json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String(); + } else { + // json[r'takenBefore'] = null; + } + if (this.thumbnailPath != null) { + json[r'thumbnailPath'] = this.thumbnailPath; + } else { + // json[r'thumbnailPath'] = null; + } + if (this.trashedAfter != null) { + json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); + } else { + // json[r'trashedAfter'] = null; + } + if (this.trashedBefore != null) { + json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String(); + } else { + // json[r'trashedBefore'] = null; + } + if (this.type != null) { + json[r'type'] = this.type; + } else { + // json[r'type'] = null; + } + if (this.updatedAfter != null) { + json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String(); + } else { + // json[r'updatedAfter'] = null; + } + if (this.updatedBefore != null) { + json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String(); + } else { + // json[r'updatedBefore'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + if (this.withDeleted != null) { + json[r'withDeleted'] = this.withDeleted; + } else { + // json[r'withDeleted'] = null; + } + if (this.withExif != null) { + json[r'withExif'] = this.withExif; + } else { + // json[r'withExif'] = null; + } + if (this.withPeople != null) { + json[r'withPeople'] = this.withPeople; + } else { + // json[r'withPeople'] = null; + } + if (this.withStacked != null) { + json[r'withStacked'] = this.withStacked; + } else { + // json[r'withStacked'] = null; + } + return json; + } + + /// Returns a new [MetadataSearchDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MetadataSearchDto? fromJson(dynamic value) { + upgradeDto(value, "MetadataSearchDto"); + if (value is Map) { + final json = value.cast(); + + return MetadataSearchDto( + albumIds: json[r'albumIds'] is Iterable + ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) + : const [], + checksum: mapValueOfType(json, r'checksum'), + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + createdAfter: mapDateTime(json, r'createdAfter', r''), + createdBefore: mapDateTime(json, r'createdBefore', r''), + description: mapValueOfType(json, r'description'), + deviceAssetId: mapValueOfType(json, r'deviceAssetId'), + deviceId: mapValueOfType(json, r'deviceId'), + encodedVideoPath: mapValueOfType(json, r'encodedVideoPath'), + id: mapValueOfType(json, r'id'), + isEncoded: mapValueOfType(json, r'isEncoded'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isMotion: mapValueOfType(json, r'isMotion'), + isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), + isOffline: mapValueOfType(json, r'isOffline'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), + order: AssetOrder.fromJson(json[r'order']) ?? AssetOrder.desc, + originalFileName: mapValueOfType(json, r'originalFileName'), + originalPath: mapValueOfType(json, r'originalPath'), + page: num.parse('${json[r'page']}'), + personIds: json[r'personIds'] is Iterable + ? (json[r'personIds'] as Iterable).cast().toList(growable: false) + : const [], + previewPath: mapValueOfType(json, r'previewPath'), + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + size: num.parse('${json[r'size']}'), + state: mapValueOfType(json, r'state'), + tagIds: json[r'tagIds'] is Iterable + ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) + : const [], + takenAfter: mapDateTime(json, r'takenAfter', r''), + takenBefore: mapDateTime(json, r'takenBefore', r''), + thumbnailPath: mapValueOfType(json, r'thumbnailPath'), + trashedAfter: mapDateTime(json, r'trashedAfter', r''), + trashedBefore: mapDateTime(json, r'trashedBefore', r''), + type: AssetTypeEnum.fromJson(json[r'type']), + updatedAfter: mapDateTime(json, r'updatedAfter', r''), + updatedBefore: mapDateTime(json, r'updatedBefore', r''), + visibility: AssetVisibility.fromJson(json[r'visibility']), + withDeleted: mapValueOfType(json, r'withDeleted'), + withExif: mapValueOfType(json, r'withExif'), + withPeople: mapValueOfType(json, r'withPeople'), + withStacked: mapValueOfType(json, r'withStacked'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MetadataSearchDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MetadataSearchDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MetadataSearchDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MetadataSearchDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/mirror_axis.dart b/mobile/openapi/lib/model/mirror_axis.dart new file mode 100644 index 0000000000..4deeeb047c --- /dev/null +++ b/mobile/openapi/lib/model/mirror_axis.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Axis to mirror along +class MirrorAxis { + /// Instantiate a new enum with the provided [value]. + const MirrorAxis._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const horizontal = MirrorAxis._(r'horizontal'); + static const vertical = MirrorAxis._(r'vertical'); + + /// List of all possible values in this [enum][MirrorAxis]. + static const values = [ + horizontal, + vertical, + ]; + + static MirrorAxis? fromJson(dynamic value) => MirrorAxisTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MirrorAxis.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [MirrorAxis] to String, +/// and [decode] dynamic data back to [MirrorAxis]. +class MirrorAxisTypeTransformer { + factory MirrorAxisTypeTransformer() => _instance ??= const MirrorAxisTypeTransformer._(); + + const MirrorAxisTypeTransformer._(); + + String encode(MirrorAxis data) => data.value; + + /// Decodes a [dynamic value][data] to a MirrorAxis. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + MirrorAxis? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'horizontal': return MirrorAxis.horizontal; + case r'vertical': return MirrorAxis.vertical; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [MirrorAxisTypeTransformer] instance. + static MirrorAxisTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/mirror_parameters.dart b/mobile/openapi/lib/model/mirror_parameters.dart new file mode 100644 index 0000000000..e8b8db685b --- /dev/null +++ b/mobile/openapi/lib/model/mirror_parameters.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class MirrorParameters { + /// Returns a new [MirrorParameters] instance. + MirrorParameters({ + required this.axis, + }); + + /// Axis to mirror along + MirrorAxis axis; + + @override + bool operator ==(Object other) => identical(this, other) || other is MirrorParameters && + other.axis == axis; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (axis.hashCode); + + @override + String toString() => 'MirrorParameters[axis=$axis]'; + + Map toJson() { + final json = {}; + json[r'axis'] = this.axis; + return json; + } + + /// Returns a new [MirrorParameters] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static MirrorParameters? fromJson(dynamic value) { + upgradeDto(value, "MirrorParameters"); + if (value is Map) { + final json = value.cast(); + + return MirrorParameters( + axis: MirrorAxis.fromJson(json[r'axis'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = MirrorParameters.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = MirrorParameters.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of MirrorParameters-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = MirrorParameters.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'axis', + }; +} + diff --git a/mobile/openapi/lib/model/notification_create_dto.dart b/mobile/openapi/lib/model/notification_create_dto.dart new file mode 100644 index 0000000000..1288da8670 --- /dev/null +++ b/mobile/openapi/lib/model/notification_create_dto.dart @@ -0,0 +1,187 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class NotificationCreateDto { + /// Returns a new [NotificationCreateDto] instance. + NotificationCreateDto({ + this.data, + this.description, + this.level, + this.readAt, + required this.title, + this.type, + required this.userId, + }); + + /// Additional notification data + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? data; + + /// Notification description + String? description; + + /// Notification level + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + NotificationLevel? level; + + /// Date when notification was read + DateTime? readAt; + + /// Notification title + String title; + + /// Notification type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + NotificationType? type; + + /// User ID to send notification to + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is NotificationCreateDto && + other.data == data && + other.description == description && + other.level == level && + other.readAt == readAt && + other.title == title && + other.type == type && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (data == null ? 0 : data!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (level == null ? 0 : level!.hashCode) + + (readAt == null ? 0 : readAt!.hashCode) + + (title.hashCode) + + (type == null ? 0 : type!.hashCode) + + (userId.hashCode); + + @override + String toString() => 'NotificationCreateDto[data=$data, description=$description, level=$level, readAt=$readAt, title=$title, type=$type, userId=$userId]'; + + Map toJson() { + final json = {}; + if (this.data != null) { + json[r'data'] = this.data; + } else { + // json[r'data'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.level != null) { + json[r'level'] = this.level; + } else { + // json[r'level'] = null; + } + if (this.readAt != null) { + json[r'readAt'] = this.readAt!.toUtc().toIso8601String(); + } else { + // json[r'readAt'] = null; + } + json[r'title'] = this.title; + if (this.type != null) { + json[r'type'] = this.type; + } else { + // json[r'type'] = null; + } + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [NotificationCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static NotificationCreateDto? fromJson(dynamic value) { + upgradeDto(value, "NotificationCreateDto"); + if (value is Map) { + final json = value.cast(); + + return NotificationCreateDto( + data: mapValueOfType(json, r'data'), + description: mapValueOfType(json, r'description'), + level: NotificationLevel.fromJson(json[r'level']), + readAt: mapDateTime(json, r'readAt', r''), + title: mapValueOfType(json, r'title')!, + type: NotificationType.fromJson(json[r'type']), + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = NotificationCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of NotificationCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = NotificationCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'title', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/notification_delete_all_dto.dart b/mobile/openapi/lib/model/notification_delete_all_dto.dart new file mode 100644 index 0000000000..1b398a4f33 --- /dev/null +++ b/mobile/openapi/lib/model/notification_delete_all_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class NotificationDeleteAllDto { + /// Returns a new [NotificationDeleteAllDto] instance. + NotificationDeleteAllDto({ + this.ids = const [], + }); + + /// Notification IDs to delete + List ids; + + @override + bool operator ==(Object other) => identical(this, other) || other is NotificationDeleteAllDto && + _deepEquality.equals(other.ids, ids); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ids.hashCode); + + @override + String toString() => 'NotificationDeleteAllDto[ids=$ids]'; + + Map toJson() { + final json = {}; + json[r'ids'] = this.ids; + return json; + } + + /// Returns a new [NotificationDeleteAllDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static NotificationDeleteAllDto? fromJson(dynamic value) { + upgradeDto(value, "NotificationDeleteAllDto"); + if (value is Map) { + final json = value.cast(); + + return NotificationDeleteAllDto( + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationDeleteAllDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = NotificationDeleteAllDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of NotificationDeleteAllDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = NotificationDeleteAllDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/notification_dto.dart b/mobile/openapi/lib/model/notification_dto.dart new file mode 100644 index 0000000000..30d43de115 --- /dev/null +++ b/mobile/openapi/lib/model/notification_dto.dart @@ -0,0 +1,190 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class NotificationDto { + /// Returns a new [NotificationDto] instance. + NotificationDto({ + required this.createdAt, + this.data, + this.description, + required this.id, + required this.level, + this.readAt, + required this.title, + required this.type, + }); + + /// Creation date + DateTime createdAt; + + /// Additional notification data + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? data; + + /// Notification description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Notification ID + String id; + + /// Notification level + NotificationLevel level; + + /// Date when notification was read + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? readAt; + + /// Notification title + String title; + + /// Notification type + NotificationType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is NotificationDto && + other.createdAt == createdAt && + other.data == data && + other.description == description && + other.id == id && + other.level == level && + other.readAt == readAt && + other.title == title && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (data == null ? 0 : data!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (id.hashCode) + + (level.hashCode) + + (readAt == null ? 0 : readAt!.hashCode) + + (title.hashCode) + + (type.hashCode); + + @override + String toString() => 'NotificationDto[createdAt=$createdAt, data=$data, description=$description, id=$id, level=$level, readAt=$readAt, title=$title, type=$type]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.data != null) { + json[r'data'] = this.data; + } else { + // json[r'data'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + json[r'id'] = this.id; + json[r'level'] = this.level; + if (this.readAt != null) { + json[r'readAt'] = this.readAt!.toUtc().toIso8601String(); + } else { + // json[r'readAt'] = null; + } + json[r'title'] = this.title; + json[r'type'] = this.type; + return json; + } + + /// Returns a new [NotificationDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static NotificationDto? fromJson(dynamic value) { + upgradeDto(value, "NotificationDto"); + if (value is Map) { + final json = value.cast(); + + return NotificationDto( + createdAt: mapDateTime(json, r'createdAt', r'')!, + data: mapValueOfType(json, r'data'), + description: mapValueOfType(json, r'description'), + id: mapValueOfType(json, r'id')!, + level: NotificationLevel.fromJson(json[r'level'])!, + readAt: mapDateTime(json, r'readAt', r''), + title: mapValueOfType(json, r'title')!, + type: NotificationType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = NotificationDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of NotificationDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = NotificationDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'id', + 'level', + 'title', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/notification_level.dart b/mobile/openapi/lib/model/notification_level.dart new file mode 100644 index 0000000000..554863ae4f --- /dev/null +++ b/mobile/openapi/lib/model/notification_level.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class NotificationLevel { + /// Instantiate a new enum with the provided [value]. + const NotificationLevel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const success = NotificationLevel._(r'success'); + static const error = NotificationLevel._(r'error'); + static const warning = NotificationLevel._(r'warning'); + static const info = NotificationLevel._(r'info'); + + /// List of all possible values in this [enum][NotificationLevel]. + static const values = [ + success, + error, + warning, + info, + ]; + + static NotificationLevel? fromJson(dynamic value) => NotificationLevelTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationLevel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [NotificationLevel] to String, +/// and [decode] dynamic data back to [NotificationLevel]. +class NotificationLevelTypeTransformer { + factory NotificationLevelTypeTransformer() => _instance ??= const NotificationLevelTypeTransformer._(); + + const NotificationLevelTypeTransformer._(); + + String encode(NotificationLevel data) => data.value; + + /// Decodes a [dynamic value][data] to a NotificationLevel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + NotificationLevel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'success': return NotificationLevel.success; + case r'error': return NotificationLevel.error; + case r'warning': return NotificationLevel.warning; + case r'info': return NotificationLevel.info; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [NotificationLevelTypeTransformer] instance. + static NotificationLevelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/notification_type.dart b/mobile/openapi/lib/model/notification_type.dart new file mode 100644 index 0000000000..b5885aa441 --- /dev/null +++ b/mobile/openapi/lib/model/notification_type.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class NotificationType { + /// Instantiate a new enum with the provided [value]. + const NotificationType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const jobFailed = NotificationType._(r'JobFailed'); + static const backupFailed = NotificationType._(r'BackupFailed'); + static const systemMessage = NotificationType._(r'SystemMessage'); + static const albumInvite = NotificationType._(r'AlbumInvite'); + static const albumUpdate = NotificationType._(r'AlbumUpdate'); + static const custom = NotificationType._(r'Custom'); + + /// List of all possible values in this [enum][NotificationType]. + static const values = [ + jobFailed, + backupFailed, + systemMessage, + albumInvite, + albumUpdate, + custom, + ]; + + static NotificationType? fromJson(dynamic value) => NotificationTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [NotificationType] to String, +/// and [decode] dynamic data back to [NotificationType]. +class NotificationTypeTypeTransformer { + factory NotificationTypeTypeTransformer() => _instance ??= const NotificationTypeTypeTransformer._(); + + const NotificationTypeTypeTransformer._(); + + String encode(NotificationType data) => data.value; + + /// Decodes a [dynamic value][data] to a NotificationType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + NotificationType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'JobFailed': return NotificationType.jobFailed; + case r'BackupFailed': return NotificationType.backupFailed; + case r'SystemMessage': return NotificationType.systemMessage; + case r'AlbumInvite': return NotificationType.albumInvite; + case r'AlbumUpdate': return NotificationType.albumUpdate; + case r'Custom': return NotificationType.custom; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [NotificationTypeTypeTransformer] instance. + static NotificationTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/notification_update_all_dto.dart b/mobile/openapi/lib/model/notification_update_all_dto.dart new file mode 100644 index 0000000000..a157058324 --- /dev/null +++ b/mobile/openapi/lib/model/notification_update_all_dto.dart @@ -0,0 +1,114 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class NotificationUpdateAllDto { + /// Returns a new [NotificationUpdateAllDto] instance. + NotificationUpdateAllDto({ + this.ids = const [], + this.readAt, + }); + + /// Notification IDs to update + List ids; + + /// Date when notifications were read + DateTime? readAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateAllDto && + _deepEquality.equals(other.ids, ids) && + other.readAt == readAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ids.hashCode) + + (readAt == null ? 0 : readAt!.hashCode); + + @override + String toString() => 'NotificationUpdateAllDto[ids=$ids, readAt=$readAt]'; + + Map toJson() { + final json = {}; + json[r'ids'] = this.ids; + if (this.readAt != null) { + json[r'readAt'] = this.readAt!.toUtc().toIso8601String(); + } else { + // json[r'readAt'] = null; + } + return json; + } + + /// Returns a new [NotificationUpdateAllDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static NotificationUpdateAllDto? fromJson(dynamic value) { + upgradeDto(value, "NotificationUpdateAllDto"); + if (value is Map) { + final json = value.cast(); + + return NotificationUpdateAllDto( + ids: json[r'ids'] is Iterable + ? (json[r'ids'] as Iterable).cast().toList(growable: false) + : const [], + readAt: mapDateTime(json, r'readAt', r''), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationUpdateAllDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = NotificationUpdateAllDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of NotificationUpdateAllDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = NotificationUpdateAllDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ids', + }; +} + diff --git a/mobile/openapi/lib/model/notification_update_dto.dart b/mobile/openapi/lib/model/notification_update_dto.dart new file mode 100644 index 0000000000..eddf9c7e12 --- /dev/null +++ b/mobile/openapi/lib/model/notification_update_dto.dart @@ -0,0 +1,103 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class NotificationUpdateDto { + /// Returns a new [NotificationUpdateDto] instance. + NotificationUpdateDto({ + this.readAt, + }); + + /// Date when notification was read + DateTime? readAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is NotificationUpdateDto && + other.readAt == readAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (readAt == null ? 0 : readAt!.hashCode); + + @override + String toString() => 'NotificationUpdateDto[readAt=$readAt]'; + + Map toJson() { + final json = {}; + if (this.readAt != null) { + json[r'readAt'] = this.readAt!.toUtc().toIso8601String(); + } else { + // json[r'readAt'] = null; + } + return json; + } + + /// Returns a new [NotificationUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static NotificationUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "NotificationUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return NotificationUpdateDto( + readAt: mapDateTime(json, r'readAt', r''), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = NotificationUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = NotificationUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of NotificationUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = NotificationUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/o_auth_authorize_response_dto.dart b/mobile/openapi/lib/model/o_auth_authorize_response_dto.dart new file mode 100644 index 0000000000..7eedc45673 --- /dev/null +++ b/mobile/openapi/lib/model/o_auth_authorize_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OAuthAuthorizeResponseDto { + /// Returns a new [OAuthAuthorizeResponseDto] instance. + OAuthAuthorizeResponseDto({ + required this.url, + }); + + /// OAuth authorization URL + String url; + + @override + bool operator ==(Object other) => identical(this, other) || other is OAuthAuthorizeResponseDto && + other.url == url; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (url.hashCode); + + @override + String toString() => 'OAuthAuthorizeResponseDto[url=$url]'; + + Map toJson() { + final json = {}; + json[r'url'] = this.url; + return json; + } + + /// Returns a new [OAuthAuthorizeResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OAuthAuthorizeResponseDto? fromJson(dynamic value) { + upgradeDto(value, "OAuthAuthorizeResponseDto"); + if (value is Map) { + final json = value.cast(); + + return OAuthAuthorizeResponseDto( + url: mapValueOfType(json, r'url')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OAuthAuthorizeResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OAuthAuthorizeResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OAuthAuthorizeResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OAuthAuthorizeResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'url', + }; +} + diff --git a/mobile/openapi/lib/model/o_auth_callback_dto.dart b/mobile/openapi/lib/model/o_auth_callback_dto.dart new file mode 100644 index 0000000000..d94374935a --- /dev/null +++ b/mobile/openapi/lib/model/o_auth_callback_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OAuthCallbackDto { + /// Returns a new [OAuthCallbackDto] instance. + OAuthCallbackDto({ + this.codeVerifier, + this.state, + required this.url, + }); + + /// OAuth code verifier (PKCE) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? codeVerifier; + + /// OAuth state parameter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? state; + + /// OAuth callback URL + String url; + + @override + bool operator ==(Object other) => identical(this, other) || other is OAuthCallbackDto && + other.codeVerifier == codeVerifier && + other.state == state && + other.url == url; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (codeVerifier == null ? 0 : codeVerifier!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (url.hashCode); + + @override + String toString() => 'OAuthCallbackDto[codeVerifier=$codeVerifier, state=$state, url=$url]'; + + Map toJson() { + final json = {}; + if (this.codeVerifier != null) { + json[r'codeVerifier'] = this.codeVerifier; + } else { + // json[r'codeVerifier'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + json[r'url'] = this.url; + return json; + } + + /// Returns a new [OAuthCallbackDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OAuthCallbackDto? fromJson(dynamic value) { + upgradeDto(value, "OAuthCallbackDto"); + if (value is Map) { + final json = value.cast(); + + return OAuthCallbackDto( + codeVerifier: mapValueOfType(json, r'codeVerifier'), + state: mapValueOfType(json, r'state'), + url: mapValueOfType(json, r'url')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OAuthCallbackDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OAuthCallbackDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OAuthCallbackDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OAuthCallbackDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'url', + }; +} + diff --git a/mobile/openapi/lib/model/o_auth_config_dto.dart b/mobile/openapi/lib/model/o_auth_config_dto.dart new file mode 100644 index 0000000000..1c9ce8d5b8 --- /dev/null +++ b/mobile/openapi/lib/model/o_auth_config_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OAuthConfigDto { + /// Returns a new [OAuthConfigDto] instance. + OAuthConfigDto({ + this.codeChallenge, + required this.redirectUri, + this.state, + }); + + /// OAuth code challenge (PKCE) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? codeChallenge; + + /// OAuth redirect URI + String redirectUri; + + /// OAuth state parameter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? state; + + @override + bool operator ==(Object other) => identical(this, other) || other is OAuthConfigDto && + other.codeChallenge == codeChallenge && + other.redirectUri == redirectUri && + other.state == state; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (codeChallenge == null ? 0 : codeChallenge!.hashCode) + + (redirectUri.hashCode) + + (state == null ? 0 : state!.hashCode); + + @override + String toString() => 'OAuthConfigDto[codeChallenge=$codeChallenge, redirectUri=$redirectUri, state=$state]'; + + Map toJson() { + final json = {}; + if (this.codeChallenge != null) { + json[r'codeChallenge'] = this.codeChallenge; + } else { + // json[r'codeChallenge'] = null; + } + json[r'redirectUri'] = this.redirectUri; + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + return json; + } + + /// Returns a new [OAuthConfigDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OAuthConfigDto? fromJson(dynamic value) { + upgradeDto(value, "OAuthConfigDto"); + if (value is Map) { + final json = value.cast(); + + return OAuthConfigDto( + codeChallenge: mapValueOfType(json, r'codeChallenge'), + redirectUri: mapValueOfType(json, r'redirectUri')!, + state: mapValueOfType(json, r'state'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OAuthConfigDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OAuthConfigDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OAuthConfigDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OAuthConfigDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'redirectUri', + }; +} + diff --git a/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart new file mode 100644 index 0000000000..77466d61d9 --- /dev/null +++ b/mobile/openapi/lib/model/o_auth_token_endpoint_auth_method.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Token endpoint auth method +class OAuthTokenEndpointAuthMethod { + /// Instantiate a new enum with the provided [value]. + const OAuthTokenEndpointAuthMethod._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const post = OAuthTokenEndpointAuthMethod._(r'client_secret_post'); + static const basic = OAuthTokenEndpointAuthMethod._(r'client_secret_basic'); + + /// List of all possible values in this [enum][OAuthTokenEndpointAuthMethod]. + static const values = [ + post, + basic, + ]; + + static OAuthTokenEndpointAuthMethod? fromJson(dynamic value) => OAuthTokenEndpointAuthMethodTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OAuthTokenEndpointAuthMethod.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [OAuthTokenEndpointAuthMethod] to String, +/// and [decode] dynamic data back to [OAuthTokenEndpointAuthMethod]. +class OAuthTokenEndpointAuthMethodTypeTransformer { + factory OAuthTokenEndpointAuthMethodTypeTransformer() => _instance ??= const OAuthTokenEndpointAuthMethodTypeTransformer._(); + + const OAuthTokenEndpointAuthMethodTypeTransformer._(); + + String encode(OAuthTokenEndpointAuthMethod data) => data.value; + + /// Decodes a [dynamic value][data] to a OAuthTokenEndpointAuthMethod. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + OAuthTokenEndpointAuthMethod? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'client_secret_post': return OAuthTokenEndpointAuthMethod.post; + case r'client_secret_basic': return OAuthTokenEndpointAuthMethod.basic; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [OAuthTokenEndpointAuthMethodTypeTransformer] instance. + static OAuthTokenEndpointAuthMethodTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/ocr_config.dart b/mobile/openapi/lib/model/ocr_config.dart new file mode 100644 index 0000000000..d97cd5ffca --- /dev/null +++ b/mobile/openapi/lib/model/ocr_config.dart @@ -0,0 +1,144 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OcrConfig { + /// Returns a new [OcrConfig] instance. + OcrConfig({ + required this.enabled, + required this.maxResolution, + required this.minDetectionScore, + required this.minRecognitionScore, + required this.modelName, + }); + + /// Whether the task is enabled + bool enabled; + + /// Maximum resolution for OCR processing + /// + /// Minimum value: 1 + int maxResolution; + + /// Minimum confidence score for text detection + /// + /// Minimum value: 0.1 + /// Maximum value: 1 + double minDetectionScore; + + /// Minimum confidence score for text recognition + /// + /// Minimum value: 0.1 + /// Maximum value: 1 + double minRecognitionScore; + + /// Name of the model to use + String modelName; + + @override + bool operator ==(Object other) => identical(this, other) || other is OcrConfig && + other.enabled == enabled && + other.maxResolution == maxResolution && + other.minDetectionScore == minDetectionScore && + other.minRecognitionScore == minRecognitionScore && + other.modelName == modelName; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (maxResolution.hashCode) + + (minDetectionScore.hashCode) + + (minRecognitionScore.hashCode) + + (modelName.hashCode); + + @override + String toString() => 'OcrConfig[enabled=$enabled, maxResolution=$maxResolution, minDetectionScore=$minDetectionScore, minRecognitionScore=$minRecognitionScore, modelName=$modelName]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'maxResolution'] = this.maxResolution; + json[r'minDetectionScore'] = this.minDetectionScore; + json[r'minRecognitionScore'] = this.minRecognitionScore; + json[r'modelName'] = this.modelName; + return json; + } + + /// Returns a new [OcrConfig] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OcrConfig? fromJson(dynamic value) { + upgradeDto(value, "OcrConfig"); + if (value is Map) { + final json = value.cast(); + + return OcrConfig( + enabled: mapValueOfType(json, r'enabled')!, + maxResolution: mapValueOfType(json, r'maxResolution')!, + minDetectionScore: (mapValueOfType(json, r'minDetectionScore')!).toDouble(), + minRecognitionScore: (mapValueOfType(json, r'minRecognitionScore')!).toDouble(), + modelName: mapValueOfType(json, r'modelName')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OcrConfig.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OcrConfig.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OcrConfig-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OcrConfig.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'maxResolution', + 'minDetectionScore', + 'minRecognitionScore', + 'modelName', + }; +} + diff --git a/mobile/openapi/lib/model/on_this_day_dto.dart b/mobile/openapi/lib/model/on_this_day_dto.dart new file mode 100644 index 0000000000..93ec956f58 --- /dev/null +++ b/mobile/openapi/lib/model/on_this_day_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OnThisDayDto { + /// Returns a new [OnThisDayDto] instance. + OnThisDayDto({ + required this.year, + }); + + /// Year for on this day memory + /// + /// Minimum value: 1 + num year; + + @override + bool operator ==(Object other) => identical(this, other) || other is OnThisDayDto && + other.year == year; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (year.hashCode); + + @override + String toString() => 'OnThisDayDto[year=$year]'; + + Map toJson() { + final json = {}; + json[r'year'] = this.year; + return json; + } + + /// Returns a new [OnThisDayDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OnThisDayDto? fromJson(dynamic value) { + upgradeDto(value, "OnThisDayDto"); + if (value is Map) { + final json = value.cast(); + + return OnThisDayDto( + year: num.parse('${json[r'year']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OnThisDayDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OnThisDayDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OnThisDayDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OnThisDayDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'year', + }; +} + diff --git a/mobile/openapi/lib/model/onboarding_dto.dart b/mobile/openapi/lib/model/onboarding_dto.dart new file mode 100644 index 0000000000..8499bc9b9a --- /dev/null +++ b/mobile/openapi/lib/model/onboarding_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OnboardingDto { + /// Returns a new [OnboardingDto] instance. + OnboardingDto({ + required this.isOnboarded, + }); + + /// Is user onboarded + bool isOnboarded; + + @override + bool operator ==(Object other) => identical(this, other) || other is OnboardingDto && + other.isOnboarded == isOnboarded; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isOnboarded.hashCode); + + @override + String toString() => 'OnboardingDto[isOnboarded=$isOnboarded]'; + + Map toJson() { + final json = {}; + json[r'isOnboarded'] = this.isOnboarded; + return json; + } + + /// Returns a new [OnboardingDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OnboardingDto? fromJson(dynamic value) { + upgradeDto(value, "OnboardingDto"); + if (value is Map) { + final json = value.cast(); + + return OnboardingDto( + isOnboarded: mapValueOfType(json, r'isOnboarded')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OnboardingDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OnboardingDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OnboardingDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OnboardingDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isOnboarded', + }; +} + diff --git a/mobile/openapi/lib/model/onboarding_response_dto.dart b/mobile/openapi/lib/model/onboarding_response_dto.dart new file mode 100644 index 0000000000..2b0dbe2b96 --- /dev/null +++ b/mobile/openapi/lib/model/onboarding_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class OnboardingResponseDto { + /// Returns a new [OnboardingResponseDto] instance. + OnboardingResponseDto({ + required this.isOnboarded, + }); + + /// Is user onboarded + bool isOnboarded; + + @override + bool operator ==(Object other) => identical(this, other) || other is OnboardingResponseDto && + other.isOnboarded == isOnboarded; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isOnboarded.hashCode); + + @override + String toString() => 'OnboardingResponseDto[isOnboarded=$isOnboarded]'; + + Map toJson() { + final json = {}; + json[r'isOnboarded'] = this.isOnboarded; + return json; + } + + /// Returns a new [OnboardingResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static OnboardingResponseDto? fromJson(dynamic value) { + upgradeDto(value, "OnboardingResponseDto"); + if (value is Map) { + final json = value.cast(); + + return OnboardingResponseDto( + isOnboarded: mapValueOfType(json, r'isOnboarded')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = OnboardingResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = OnboardingResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of OnboardingResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = OnboardingResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isOnboarded', + }; +} + diff --git a/mobile/openapi/lib/model/partner_create_dto.dart b/mobile/openapi/lib/model/partner_create_dto.dart new file mode 100644 index 0000000000..30aa96ff30 --- /dev/null +++ b/mobile/openapi/lib/model/partner_create_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PartnerCreateDto { + /// Returns a new [PartnerCreateDto] instance. + PartnerCreateDto({ + required this.sharedWithId, + }); + + /// User ID to share with + String sharedWithId; + + @override + bool operator ==(Object other) => identical(this, other) || other is PartnerCreateDto && + other.sharedWithId == sharedWithId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (sharedWithId.hashCode); + + @override + String toString() => 'PartnerCreateDto[sharedWithId=$sharedWithId]'; + + Map toJson() { + final json = {}; + json[r'sharedWithId'] = this.sharedWithId; + return json; + } + + /// Returns a new [PartnerCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PartnerCreateDto? fromJson(dynamic value) { + upgradeDto(value, "PartnerCreateDto"); + if (value is Map) { + final json = value.cast(); + + return PartnerCreateDto( + sharedWithId: mapValueOfType(json, r'sharedWithId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PartnerCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PartnerCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PartnerCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'sharedWithId', + }; +} + diff --git a/mobile/openapi/lib/model/partner_direction.dart b/mobile/openapi/lib/model/partner_direction.dart new file mode 100644 index 0000000000..c43c0df75d --- /dev/null +++ b/mobile/openapi/lib/model/partner_direction.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class PartnerDirection { + /// Instantiate a new enum with the provided [value]. + const PartnerDirection._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const by = PartnerDirection._(r'shared-by'); + static const with_ = PartnerDirection._(r'shared-with'); + + /// List of all possible values in this [enum][PartnerDirection]. + static const values = [ + by, + with_, + ]; + + static PartnerDirection? fromJson(dynamic value) => PartnerDirectionTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerDirection.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PartnerDirection] to String, +/// and [decode] dynamic data back to [PartnerDirection]. +class PartnerDirectionTypeTransformer { + factory PartnerDirectionTypeTransformer() => _instance ??= const PartnerDirectionTypeTransformer._(); + + const PartnerDirectionTypeTransformer._(); + + String encode(PartnerDirection data) => data.value; + + /// Decodes a [dynamic value][data] to a PartnerDirection. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PartnerDirection? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'shared-by': return PartnerDirection.by; + case r'shared-with': return PartnerDirection.with_; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PartnerDirectionTypeTransformer] instance. + static PartnerDirectionTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/partner_response_dto.dart b/mobile/openapi/lib/model/partner_response_dto.dart new file mode 100644 index 0000000000..5789938d18 --- /dev/null +++ b/mobile/openapi/lib/model/partner_response_dto.dart @@ -0,0 +1,163 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PartnerResponseDto { + /// Returns a new [PartnerResponseDto] instance. + PartnerResponseDto({ + required this.avatarColor, + required this.email, + required this.id, + this.inTimeline, + required this.name, + required this.profileChangedAt, + required this.profileImagePath, + }); + + /// Avatar color + UserAvatarColor avatarColor; + + /// User email + String email; + + /// User ID + String id; + + /// Show in timeline + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? inTimeline; + + /// User name + String name; + + /// Profile change date + DateTime profileChangedAt; + + /// Profile image path + String profileImagePath; + + @override + bool operator ==(Object other) => identical(this, other) || other is PartnerResponseDto && + other.avatarColor == avatarColor && + other.email == email && + other.id == id && + other.inTimeline == inTimeline && + other.name == name && + other.profileChangedAt == profileChangedAt && + other.profileImagePath == profileImagePath; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + + (email.hashCode) + + (id.hashCode) + + (inTimeline == null ? 0 : inTimeline!.hashCode) + + (name.hashCode) + + (profileChangedAt.hashCode) + + (profileImagePath.hashCode); + + @override + String toString() => 'PartnerResponseDto[avatarColor=$avatarColor, email=$email, id=$id, inTimeline=$inTimeline, name=$name, profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath]'; + + Map toJson() { + final json = {}; + json[r'avatarColor'] = this.avatarColor; + json[r'email'] = this.email; + json[r'id'] = this.id; + if (this.inTimeline != null) { + json[r'inTimeline'] = this.inTimeline; + } else { + // json[r'inTimeline'] = null; + } + json[r'name'] = this.name; + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + json[r'profileImagePath'] = this.profileImagePath; + return json; + } + + /// Returns a new [PartnerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PartnerResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PartnerResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PartnerResponseDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, + email: mapValueOfType(json, r'email')!, + id: mapValueOfType(json, r'id')!, + inTimeline: mapValueOfType(json, r'inTimeline'), + name: mapValueOfType(json, r'name')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PartnerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PartnerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PartnerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'avatarColor', + 'email', + 'id', + 'name', + 'profileChangedAt', + 'profileImagePath', + }; +} + diff --git a/mobile/openapi/lib/model/partner_update_dto.dart b/mobile/openapi/lib/model/partner_update_dto.dart new file mode 100644 index 0000000000..db3516e3a1 --- /dev/null +++ b/mobile/openapi/lib/model/partner_update_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PartnerUpdateDto { + /// Returns a new [PartnerUpdateDto] instance. + PartnerUpdateDto({ + required this.inTimeline, + }); + + /// Show partner assets in timeline + bool inTimeline; + + @override + bool operator ==(Object other) => identical(this, other) || other is PartnerUpdateDto && + other.inTimeline == inTimeline; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (inTimeline.hashCode); + + @override + String toString() => 'PartnerUpdateDto[inTimeline=$inTimeline]'; + + Map toJson() { + final json = {}; + json[r'inTimeline'] = this.inTimeline; + return json; + } + + /// Returns a new [PartnerUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PartnerUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "PartnerUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return PartnerUpdateDto( + inTimeline: mapValueOfType(json, r'inTimeline')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PartnerUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PartnerUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PartnerUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PartnerUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'inTimeline', + }; +} + diff --git a/mobile/openapi/lib/model/people_response.dart b/mobile/openapi/lib/model/people_response.dart new file mode 100644 index 0000000000..c09560e08c --- /dev/null +++ b/mobile/openapi/lib/model/people_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PeopleResponse { + /// Returns a new [PeopleResponse] instance. + PeopleResponse({ + this.enabled = true, + this.sidebarWeb = false, + }); + + /// Whether people are enabled + bool enabled; + + /// Whether people appear in web sidebar + bool sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleResponse && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (sidebarWeb.hashCode); + + @override + String toString() => 'PeopleResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'sidebarWeb'] = this.sidebarWeb; + return json; + } + + /// Returns a new [PeopleResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleResponse? fromJson(dynamic value) { + upgradeDto(value, "PeopleResponse"); + if (value is Map) { + final json = value.cast(); + + return PeopleResponse( + enabled: mapValueOfType(json, r'enabled')!, + sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'sidebarWeb', + }; +} + diff --git a/mobile/openapi/lib/model/people_response_dto.dart b/mobile/openapi/lib/model/people_response_dto.dart new file mode 100644 index 0000000000..f345657e73 --- /dev/null +++ b/mobile/openapi/lib/model/people_response_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PeopleResponseDto { + /// Returns a new [PeopleResponseDto] instance. + PeopleResponseDto({ + this.hasNextPage, + required this.hidden, + this.people = const [], + required this.total, + }); + + /// Whether there are more pages + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? hasNextPage; + + /// Number of hidden people + int hidden; + + /// List of people + List people; + + /// Total number of people + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleResponseDto && + other.hasNextPage == hasNextPage && + other.hidden == hidden && + _deepEquality.equals(other.people, people) && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (hasNextPage == null ? 0 : hasNextPage!.hashCode) + + (hidden.hashCode) + + (people.hashCode) + + (total.hashCode); + + @override + String toString() => 'PeopleResponseDto[hasNextPage=$hasNextPage, hidden=$hidden, people=$people, total=$total]'; + + Map toJson() { + final json = {}; + if (this.hasNextPage != null) { + json[r'hasNextPage'] = this.hasNextPage; + } else { + // json[r'hasNextPage'] = null; + } + json[r'hidden'] = this.hidden; + json[r'people'] = this.people; + json[r'total'] = this.total; + return json; + } + + /// Returns a new [PeopleResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PeopleResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PeopleResponseDto( + hasNextPage: mapValueOfType(json, r'hasNextPage'), + hidden: mapValueOfType(json, r'hidden')!, + people: PersonResponseDto.listFromJson(json[r'people']), + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'hidden', + 'people', + 'total', + }; +} + diff --git a/mobile/openapi/lib/model/people_update.dart b/mobile/openapi/lib/model/people_update.dart new file mode 100644 index 0000000000..fe16479bac --- /dev/null +++ b/mobile/openapi/lib/model/people_update.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PeopleUpdate { + /// Returns a new [PeopleUpdate] instance. + PeopleUpdate({ + this.enabled, + this.sidebarWeb, + }); + + /// Whether people are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Whether people appear in web sidebar + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleUpdate && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled == null ? 0 : enabled!.hashCode) + + (sidebarWeb == null ? 0 : sidebarWeb!.hashCode); + + @override + String toString() => 'PeopleUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + if (this.sidebarWeb != null) { + json[r'sidebarWeb'] = this.sidebarWeb; + } else { + // json[r'sidebarWeb'] = null; + } + return json; + } + + /// Returns a new [PeopleUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleUpdate? fromJson(dynamic value) { + upgradeDto(value, "PeopleUpdate"); + if (value is Map) { + final json = value.cast(); + + return PeopleUpdate( + enabled: mapValueOfType(json, r'enabled'), + sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/people_update_dto.dart b/mobile/openapi/lib/model/people_update_dto.dart new file mode 100644 index 0000000000..c9ce74d659 --- /dev/null +++ b/mobile/openapi/lib/model/people_update_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PeopleUpdateDto { + /// Returns a new [PeopleUpdateDto] instance. + PeopleUpdateDto({ + this.people = const [], + }); + + /// People to update + List people; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateDto && + _deepEquality.equals(other.people, people); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (people.hashCode); + + @override + String toString() => 'PeopleUpdateDto[people=$people]'; + + Map toJson() { + final json = {}; + json[r'people'] = this.people; + return json; + } + + /// Returns a new [PeopleUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "PeopleUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return PeopleUpdateDto( + people: PeopleUpdateItem.listFromJson(json[r'people']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'people', + }; +} + diff --git a/mobile/openapi/lib/model/people_update_item.dart b/mobile/openapi/lib/model/people_update_item.dart new file mode 100644 index 0000000000..5e20aeb464 --- /dev/null +++ b/mobile/openapi/lib/model/people_update_item.dart @@ -0,0 +1,196 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PeopleUpdateItem { + /// Returns a new [PeopleUpdateItem] instance. + PeopleUpdateItem({ + this.birthDate, + this.color, + this.featureFaceAssetId, + required this.id, + this.isFavorite, + this.isHidden, + this.name, + }); + + /// Person date of birth + DateTime? birthDate; + + /// Person color (hex) + String? color; + + /// Asset ID used for feature face thumbnail + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? featureFaceAssetId; + + /// Person ID + String id; + + /// Mark as favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Person visibility (hidden) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isHidden; + + /// Person name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + @override + bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && + other.birthDate == birthDate && + other.color == color && + other.featureFaceAssetId == featureFaceAssetId && + other.id == id && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + + (id.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isHidden == null ? 0 : isHidden!.hashCode) + + (name == null ? 0 : name!.hashCode); + + @override + String toString() => 'PeopleUpdateItem[birthDate=$birthDate, color=$color, featureFaceAssetId=$featureFaceAssetId, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + if (this.featureFaceAssetId != null) { + json[r'featureFaceAssetId'] = this.featureFaceAssetId; + } else { + // json[r'featureFaceAssetId'] = null; + } + json[r'id'] = this.id; + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isHidden != null) { + json[r'isHidden'] = this.isHidden; + } else { + // json[r'isHidden'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + return json; + } + + /// Returns a new [PeopleUpdateItem] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PeopleUpdateItem? fromJson(dynamic value) { + upgradeDto(value, "PeopleUpdateItem"); + if (value is Map) { + final json = value.cast(); + + return PeopleUpdateItem( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), + id: mapValueOfType(json, r'id')!, + isFavorite: mapValueOfType(json, r'isFavorite'), + isHidden: mapValueOfType(json, r'isHidden'), + name: mapValueOfType(json, r'name'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PeopleUpdateItem.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PeopleUpdateItem.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PeopleUpdateItem-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PeopleUpdateItem.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/permission.dart b/mobile/openapi/lib/model/permission.dart new file mode 100644 index 0000000000..9092ede786 --- /dev/null +++ b/mobile/openapi/lib/model/permission.dart @@ -0,0 +1,547 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// List of permissions +class Permission { + /// Instantiate a new enum with the provided [value]. + const Permission._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const all = Permission._(r'all'); + static const activityPeriodCreate = Permission._(r'activity.create'); + static const activityPeriodRead = Permission._(r'activity.read'); + static const activityPeriodUpdate = Permission._(r'activity.update'); + static const activityPeriodDelete = Permission._(r'activity.delete'); + static const activityPeriodStatistics = Permission._(r'activity.statistics'); + static const apiKeyPeriodCreate = Permission._(r'apiKey.create'); + static const apiKeyPeriodRead = Permission._(r'apiKey.read'); + static const apiKeyPeriodUpdate = Permission._(r'apiKey.update'); + static const apiKeyPeriodDelete = Permission._(r'apiKey.delete'); + static const assetPeriodRead = Permission._(r'asset.read'); + static const assetPeriodUpdate = Permission._(r'asset.update'); + static const assetPeriodDelete = Permission._(r'asset.delete'); + static const assetPeriodStatistics = Permission._(r'asset.statistics'); + static const assetPeriodShare = Permission._(r'asset.share'); + static const assetPeriodView = Permission._(r'asset.view'); + static const assetPeriodDownload = Permission._(r'asset.download'); + static const assetPeriodUpload = Permission._(r'asset.upload'); + static const assetPeriodReplace = Permission._(r'asset.replace'); + static const assetPeriodCopy = Permission._(r'asset.copy'); + static const assetPeriodDerive = Permission._(r'asset.derive'); + static const assetPeriodEditPeriodGet = Permission._(r'asset.edit.get'); + static const assetPeriodEditPeriodCreate = Permission._(r'asset.edit.create'); + static const assetPeriodEditPeriodDelete = Permission._(r'asset.edit.delete'); + static const albumPeriodCreate = Permission._(r'album.create'); + static const albumPeriodRead = Permission._(r'album.read'); + static const albumPeriodUpdate = Permission._(r'album.update'); + static const albumPeriodDelete = Permission._(r'album.delete'); + static const albumPeriodStatistics = Permission._(r'album.statistics'); + static const albumPeriodShare = Permission._(r'album.share'); + static const albumPeriodDownload = Permission._(r'album.download'); + static const albumAssetPeriodCreate = Permission._(r'albumAsset.create'); + static const albumAssetPeriodDelete = Permission._(r'albumAsset.delete'); + static const albumUserPeriodCreate = Permission._(r'albumUser.create'); + static const albumUserPeriodUpdate = Permission._(r'albumUser.update'); + static const albumUserPeriodDelete = Permission._(r'albumUser.delete'); + static const authPeriodChangePassword = Permission._(r'auth.changePassword'); + static const authDevicePeriodDelete = Permission._(r'authDevice.delete'); + static const archivePeriodRead = Permission._(r'archive.read'); + static const backupPeriodList = Permission._(r'backup.list'); + static const backupPeriodDownload = Permission._(r'backup.download'); + static const backupPeriodUpload = Permission._(r'backup.upload'); + static const backupPeriodDelete = Permission._(r'backup.delete'); + static const duplicatePeriodRead = Permission._(r'duplicate.read'); + static const duplicatePeriodDelete = Permission._(r'duplicate.delete'); + static const facePeriodCreate = Permission._(r'face.create'); + static const facePeriodRead = Permission._(r'face.read'); + static const facePeriodUpdate = Permission._(r'face.update'); + static const facePeriodDelete = Permission._(r'face.delete'); + static const folderPeriodRead = Permission._(r'folder.read'); + static const jobPeriodCreate = Permission._(r'job.create'); + static const jobPeriodRead = Permission._(r'job.read'); + static const libraryPeriodCreate = Permission._(r'library.create'); + static const libraryPeriodRead = Permission._(r'library.read'); + static const libraryPeriodUpdate = Permission._(r'library.update'); + static const libraryPeriodDelete = Permission._(r'library.delete'); + static const libraryPeriodStatistics = Permission._(r'library.statistics'); + static const timelinePeriodRead = Permission._(r'timeline.read'); + static const timelinePeriodDownload = Permission._(r'timeline.download'); + static const maintenance = Permission._(r'maintenance'); + static const mapPeriodRead = Permission._(r'map.read'); + static const mapPeriodSearch = Permission._(r'map.search'); + static const memoryPeriodCreate = Permission._(r'memory.create'); + static const memoryPeriodRead = Permission._(r'memory.read'); + static const memoryPeriodUpdate = Permission._(r'memory.update'); + static const memoryPeriodDelete = Permission._(r'memory.delete'); + static const memoryPeriodStatistics = Permission._(r'memory.statistics'); + static const memoryAssetPeriodCreate = Permission._(r'memoryAsset.create'); + static const memoryAssetPeriodDelete = Permission._(r'memoryAsset.delete'); + static const notificationPeriodCreate = Permission._(r'notification.create'); + static const notificationPeriodRead = Permission._(r'notification.read'); + static const notificationPeriodUpdate = Permission._(r'notification.update'); + static const notificationPeriodDelete = Permission._(r'notification.delete'); + static const partnerPeriodCreate = Permission._(r'partner.create'); + static const partnerPeriodRead = Permission._(r'partner.read'); + static const partnerPeriodUpdate = Permission._(r'partner.update'); + static const partnerPeriodDelete = Permission._(r'partner.delete'); + static const personPeriodCreate = Permission._(r'person.create'); + static const personPeriodRead = Permission._(r'person.read'); + static const personPeriodUpdate = Permission._(r'person.update'); + static const personPeriodDelete = Permission._(r'person.delete'); + static const personPeriodStatistics = Permission._(r'person.statistics'); + static const personPeriodMerge = Permission._(r'person.merge'); + static const personPeriodReassign = Permission._(r'person.reassign'); + static const pinCodePeriodCreate = Permission._(r'pinCode.create'); + static const pinCodePeriodUpdate = Permission._(r'pinCode.update'); + static const pinCodePeriodDelete = Permission._(r'pinCode.delete'); + static const pluginPeriodCreate = Permission._(r'plugin.create'); + static const pluginPeriodRead = Permission._(r'plugin.read'); + static const pluginPeriodUpdate = Permission._(r'plugin.update'); + static const pluginPeriodDelete = Permission._(r'plugin.delete'); + static const serverPeriodAbout = Permission._(r'server.about'); + static const serverPeriodApkLinks = Permission._(r'server.apkLinks'); + static const serverPeriodStorage = Permission._(r'server.storage'); + static const serverPeriodStatistics = Permission._(r'server.statistics'); + static const serverPeriodVersionCheck = Permission._(r'server.versionCheck'); + static const serverLicensePeriodRead = Permission._(r'serverLicense.read'); + static const serverLicensePeriodUpdate = Permission._(r'serverLicense.update'); + static const serverLicensePeriodDelete = Permission._(r'serverLicense.delete'); + static const sessionPeriodCreate = Permission._(r'session.create'); + static const sessionPeriodRead = Permission._(r'session.read'); + static const sessionPeriodUpdate = Permission._(r'session.update'); + static const sessionPeriodDelete = Permission._(r'session.delete'); + static const sessionPeriodLock = Permission._(r'session.lock'); + static const sharedLinkPeriodCreate = Permission._(r'sharedLink.create'); + static const sharedLinkPeriodRead = Permission._(r'sharedLink.read'); + static const sharedLinkPeriodUpdate = Permission._(r'sharedLink.update'); + static const sharedLinkPeriodDelete = Permission._(r'sharedLink.delete'); + static const stackPeriodCreate = Permission._(r'stack.create'); + static const stackPeriodRead = Permission._(r'stack.read'); + static const stackPeriodUpdate = Permission._(r'stack.update'); + static const stackPeriodDelete = Permission._(r'stack.delete'); + static const syncPeriodStream = Permission._(r'sync.stream'); + static const syncCheckpointPeriodRead = Permission._(r'syncCheckpoint.read'); + static const syncCheckpointPeriodUpdate = Permission._(r'syncCheckpoint.update'); + static const syncCheckpointPeriodDelete = Permission._(r'syncCheckpoint.delete'); + static const systemConfigPeriodRead = Permission._(r'systemConfig.read'); + static const systemConfigPeriodUpdate = Permission._(r'systemConfig.update'); + static const systemMetadataPeriodRead = Permission._(r'systemMetadata.read'); + static const systemMetadataPeriodUpdate = Permission._(r'systemMetadata.update'); + static const tagPeriodCreate = Permission._(r'tag.create'); + static const tagPeriodRead = Permission._(r'tag.read'); + static const tagPeriodUpdate = Permission._(r'tag.update'); + static const tagPeriodDelete = Permission._(r'tag.delete'); + static const tagPeriodAsset = Permission._(r'tag.asset'); + static const userPeriodRead = Permission._(r'user.read'); + static const userPeriodUpdate = Permission._(r'user.update'); + static const userLicensePeriodCreate = Permission._(r'userLicense.create'); + static const userLicensePeriodRead = Permission._(r'userLicense.read'); + static const userLicensePeriodUpdate = Permission._(r'userLicense.update'); + static const userLicensePeriodDelete = Permission._(r'userLicense.delete'); + static const userOnboardingPeriodRead = Permission._(r'userOnboarding.read'); + static const userOnboardingPeriodUpdate = Permission._(r'userOnboarding.update'); + static const userOnboardingPeriodDelete = Permission._(r'userOnboarding.delete'); + static const userPreferencePeriodRead = Permission._(r'userPreference.read'); + static const userPreferencePeriodUpdate = Permission._(r'userPreference.update'); + static const userProfileImagePeriodCreate = Permission._(r'userProfileImage.create'); + static const userProfileImagePeriodRead = Permission._(r'userProfileImage.read'); + static const userProfileImagePeriodUpdate = Permission._(r'userProfileImage.update'); + static const userProfileImagePeriodDelete = Permission._(r'userProfileImage.delete'); + static const queuePeriodRead = Permission._(r'queue.read'); + static const queuePeriodUpdate = Permission._(r'queue.update'); + static const queueJobPeriodCreate = Permission._(r'queueJob.create'); + static const queueJobPeriodRead = Permission._(r'queueJob.read'); + static const queueJobPeriodUpdate = Permission._(r'queueJob.update'); + static const queueJobPeriodDelete = Permission._(r'queueJob.delete'); + static const workflowPeriodCreate = Permission._(r'workflow.create'); + static const workflowPeriodRead = Permission._(r'workflow.read'); + static const workflowPeriodUpdate = Permission._(r'workflow.update'); + static const workflowPeriodDelete = Permission._(r'workflow.delete'); + static const adminUserPeriodCreate = Permission._(r'adminUser.create'); + static const adminUserPeriodRead = Permission._(r'adminUser.read'); + static const adminUserPeriodUpdate = Permission._(r'adminUser.update'); + static const adminUserPeriodDelete = Permission._(r'adminUser.delete'); + static const adminSessionPeriodRead = Permission._(r'adminSession.read'); + static const adminAuthPeriodUnlinkAll = Permission._(r'adminAuth.unlinkAll'); + + /// List of all possible values in this [enum][Permission]. + static const values = [ + all, + activityPeriodCreate, + activityPeriodRead, + activityPeriodUpdate, + activityPeriodDelete, + activityPeriodStatistics, + apiKeyPeriodCreate, + apiKeyPeriodRead, + apiKeyPeriodUpdate, + apiKeyPeriodDelete, + assetPeriodRead, + assetPeriodUpdate, + assetPeriodDelete, + assetPeriodStatistics, + assetPeriodShare, + assetPeriodView, + assetPeriodDownload, + assetPeriodUpload, + assetPeriodReplace, + assetPeriodCopy, + assetPeriodDerive, + assetPeriodEditPeriodGet, + assetPeriodEditPeriodCreate, + assetPeriodEditPeriodDelete, + albumPeriodCreate, + albumPeriodRead, + albumPeriodUpdate, + albumPeriodDelete, + albumPeriodStatistics, + albumPeriodShare, + albumPeriodDownload, + albumAssetPeriodCreate, + albumAssetPeriodDelete, + albumUserPeriodCreate, + albumUserPeriodUpdate, + albumUserPeriodDelete, + authPeriodChangePassword, + authDevicePeriodDelete, + archivePeriodRead, + backupPeriodList, + backupPeriodDownload, + backupPeriodUpload, + backupPeriodDelete, + duplicatePeriodRead, + duplicatePeriodDelete, + facePeriodCreate, + facePeriodRead, + facePeriodUpdate, + facePeriodDelete, + folderPeriodRead, + jobPeriodCreate, + jobPeriodRead, + libraryPeriodCreate, + libraryPeriodRead, + libraryPeriodUpdate, + libraryPeriodDelete, + libraryPeriodStatistics, + timelinePeriodRead, + timelinePeriodDownload, + maintenance, + mapPeriodRead, + mapPeriodSearch, + memoryPeriodCreate, + memoryPeriodRead, + memoryPeriodUpdate, + memoryPeriodDelete, + memoryPeriodStatistics, + memoryAssetPeriodCreate, + memoryAssetPeriodDelete, + notificationPeriodCreate, + notificationPeriodRead, + notificationPeriodUpdate, + notificationPeriodDelete, + partnerPeriodCreate, + partnerPeriodRead, + partnerPeriodUpdate, + partnerPeriodDelete, + personPeriodCreate, + personPeriodRead, + personPeriodUpdate, + personPeriodDelete, + personPeriodStatistics, + personPeriodMerge, + personPeriodReassign, + pinCodePeriodCreate, + pinCodePeriodUpdate, + pinCodePeriodDelete, + pluginPeriodCreate, + pluginPeriodRead, + pluginPeriodUpdate, + pluginPeriodDelete, + serverPeriodAbout, + serverPeriodApkLinks, + serverPeriodStorage, + serverPeriodStatistics, + serverPeriodVersionCheck, + serverLicensePeriodRead, + serverLicensePeriodUpdate, + serverLicensePeriodDelete, + sessionPeriodCreate, + sessionPeriodRead, + sessionPeriodUpdate, + sessionPeriodDelete, + sessionPeriodLock, + sharedLinkPeriodCreate, + sharedLinkPeriodRead, + sharedLinkPeriodUpdate, + sharedLinkPeriodDelete, + stackPeriodCreate, + stackPeriodRead, + stackPeriodUpdate, + stackPeriodDelete, + syncPeriodStream, + syncCheckpointPeriodRead, + syncCheckpointPeriodUpdate, + syncCheckpointPeriodDelete, + systemConfigPeriodRead, + systemConfigPeriodUpdate, + systemMetadataPeriodRead, + systemMetadataPeriodUpdate, + tagPeriodCreate, + tagPeriodRead, + tagPeriodUpdate, + tagPeriodDelete, + tagPeriodAsset, + userPeriodRead, + userPeriodUpdate, + userLicensePeriodCreate, + userLicensePeriodRead, + userLicensePeriodUpdate, + userLicensePeriodDelete, + userOnboardingPeriodRead, + userOnboardingPeriodUpdate, + userOnboardingPeriodDelete, + userPreferencePeriodRead, + userPreferencePeriodUpdate, + userProfileImagePeriodCreate, + userProfileImagePeriodRead, + userProfileImagePeriodUpdate, + userProfileImagePeriodDelete, + queuePeriodRead, + queuePeriodUpdate, + queueJobPeriodCreate, + queueJobPeriodRead, + queueJobPeriodUpdate, + queueJobPeriodDelete, + workflowPeriodCreate, + workflowPeriodRead, + workflowPeriodUpdate, + workflowPeriodDelete, + adminUserPeriodCreate, + adminUserPeriodRead, + adminUserPeriodUpdate, + adminUserPeriodDelete, + adminSessionPeriodRead, + adminAuthPeriodUnlinkAll, + ]; + + static Permission? fromJson(dynamic value) => PermissionTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = Permission.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [Permission] to String, +/// and [decode] dynamic data back to [Permission]. +class PermissionTypeTransformer { + factory PermissionTypeTransformer() => _instance ??= const PermissionTypeTransformer._(); + + const PermissionTypeTransformer._(); + + String encode(Permission data) => data.value; + + /// Decodes a [dynamic value][data] to a Permission. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + Permission? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'all': return Permission.all; + case r'activity.create': return Permission.activityPeriodCreate; + case r'activity.read': return Permission.activityPeriodRead; + case r'activity.update': return Permission.activityPeriodUpdate; + case r'activity.delete': return Permission.activityPeriodDelete; + case r'activity.statistics': return Permission.activityPeriodStatistics; + case r'apiKey.create': return Permission.apiKeyPeriodCreate; + case r'apiKey.read': return Permission.apiKeyPeriodRead; + case r'apiKey.update': return Permission.apiKeyPeriodUpdate; + case r'apiKey.delete': return Permission.apiKeyPeriodDelete; + case r'asset.read': return Permission.assetPeriodRead; + case r'asset.update': return Permission.assetPeriodUpdate; + case r'asset.delete': return Permission.assetPeriodDelete; + case r'asset.statistics': return Permission.assetPeriodStatistics; + case r'asset.share': return Permission.assetPeriodShare; + case r'asset.view': return Permission.assetPeriodView; + case r'asset.download': return Permission.assetPeriodDownload; + case r'asset.upload': return Permission.assetPeriodUpload; + case r'asset.replace': return Permission.assetPeriodReplace; + case r'asset.copy': return Permission.assetPeriodCopy; + case r'asset.derive': return Permission.assetPeriodDerive; + case r'asset.edit.get': return Permission.assetPeriodEditPeriodGet; + case r'asset.edit.create': return Permission.assetPeriodEditPeriodCreate; + case r'asset.edit.delete': return Permission.assetPeriodEditPeriodDelete; + case r'album.create': return Permission.albumPeriodCreate; + case r'album.read': return Permission.albumPeriodRead; + case r'album.update': return Permission.albumPeriodUpdate; + case r'album.delete': return Permission.albumPeriodDelete; + case r'album.statistics': return Permission.albumPeriodStatistics; + case r'album.share': return Permission.albumPeriodShare; + case r'album.download': return Permission.albumPeriodDownload; + case r'albumAsset.create': return Permission.albumAssetPeriodCreate; + case r'albumAsset.delete': return Permission.albumAssetPeriodDelete; + case r'albumUser.create': return Permission.albumUserPeriodCreate; + case r'albumUser.update': return Permission.albumUserPeriodUpdate; + case r'albumUser.delete': return Permission.albumUserPeriodDelete; + case r'auth.changePassword': return Permission.authPeriodChangePassword; + case r'authDevice.delete': return Permission.authDevicePeriodDelete; + case r'archive.read': return Permission.archivePeriodRead; + case r'backup.list': return Permission.backupPeriodList; + case r'backup.download': return Permission.backupPeriodDownload; + case r'backup.upload': return Permission.backupPeriodUpload; + case r'backup.delete': return Permission.backupPeriodDelete; + case r'duplicate.read': return Permission.duplicatePeriodRead; + case r'duplicate.delete': return Permission.duplicatePeriodDelete; + case r'face.create': return Permission.facePeriodCreate; + case r'face.read': return Permission.facePeriodRead; + case r'face.update': return Permission.facePeriodUpdate; + case r'face.delete': return Permission.facePeriodDelete; + case r'folder.read': return Permission.folderPeriodRead; + case r'job.create': return Permission.jobPeriodCreate; + case r'job.read': return Permission.jobPeriodRead; + case r'library.create': return Permission.libraryPeriodCreate; + case r'library.read': return Permission.libraryPeriodRead; + case r'library.update': return Permission.libraryPeriodUpdate; + case r'library.delete': return Permission.libraryPeriodDelete; + case r'library.statistics': return Permission.libraryPeriodStatistics; + case r'timeline.read': return Permission.timelinePeriodRead; + case r'timeline.download': return Permission.timelinePeriodDownload; + case r'maintenance': return Permission.maintenance; + case r'map.read': return Permission.mapPeriodRead; + case r'map.search': return Permission.mapPeriodSearch; + case r'memory.create': return Permission.memoryPeriodCreate; + case r'memory.read': return Permission.memoryPeriodRead; + case r'memory.update': return Permission.memoryPeriodUpdate; + case r'memory.delete': return Permission.memoryPeriodDelete; + case r'memory.statistics': return Permission.memoryPeriodStatistics; + case r'memoryAsset.create': return Permission.memoryAssetPeriodCreate; + case r'memoryAsset.delete': return Permission.memoryAssetPeriodDelete; + case r'notification.create': return Permission.notificationPeriodCreate; + case r'notification.read': return Permission.notificationPeriodRead; + case r'notification.update': return Permission.notificationPeriodUpdate; + case r'notification.delete': return Permission.notificationPeriodDelete; + case r'partner.create': return Permission.partnerPeriodCreate; + case r'partner.read': return Permission.partnerPeriodRead; + case r'partner.update': return Permission.partnerPeriodUpdate; + case r'partner.delete': return Permission.partnerPeriodDelete; + case r'person.create': return Permission.personPeriodCreate; + case r'person.read': return Permission.personPeriodRead; + case r'person.update': return Permission.personPeriodUpdate; + case r'person.delete': return Permission.personPeriodDelete; + case r'person.statistics': return Permission.personPeriodStatistics; + case r'person.merge': return Permission.personPeriodMerge; + case r'person.reassign': return Permission.personPeriodReassign; + case r'pinCode.create': return Permission.pinCodePeriodCreate; + case r'pinCode.update': return Permission.pinCodePeriodUpdate; + case r'pinCode.delete': return Permission.pinCodePeriodDelete; + case r'plugin.create': return Permission.pluginPeriodCreate; + case r'plugin.read': return Permission.pluginPeriodRead; + case r'plugin.update': return Permission.pluginPeriodUpdate; + case r'plugin.delete': return Permission.pluginPeriodDelete; + case r'server.about': return Permission.serverPeriodAbout; + case r'server.apkLinks': return Permission.serverPeriodApkLinks; + case r'server.storage': return Permission.serverPeriodStorage; + case r'server.statistics': return Permission.serverPeriodStatistics; + case r'server.versionCheck': return Permission.serverPeriodVersionCheck; + case r'serverLicense.read': return Permission.serverLicensePeriodRead; + case r'serverLicense.update': return Permission.serverLicensePeriodUpdate; + case r'serverLicense.delete': return Permission.serverLicensePeriodDelete; + case r'session.create': return Permission.sessionPeriodCreate; + case r'session.read': return Permission.sessionPeriodRead; + case r'session.update': return Permission.sessionPeriodUpdate; + case r'session.delete': return Permission.sessionPeriodDelete; + case r'session.lock': return Permission.sessionPeriodLock; + case r'sharedLink.create': return Permission.sharedLinkPeriodCreate; + case r'sharedLink.read': return Permission.sharedLinkPeriodRead; + case r'sharedLink.update': return Permission.sharedLinkPeriodUpdate; + case r'sharedLink.delete': return Permission.sharedLinkPeriodDelete; + case r'stack.create': return Permission.stackPeriodCreate; + case r'stack.read': return Permission.stackPeriodRead; + case r'stack.update': return Permission.stackPeriodUpdate; + case r'stack.delete': return Permission.stackPeriodDelete; + case r'sync.stream': return Permission.syncPeriodStream; + case r'syncCheckpoint.read': return Permission.syncCheckpointPeriodRead; + case r'syncCheckpoint.update': return Permission.syncCheckpointPeriodUpdate; + case r'syncCheckpoint.delete': return Permission.syncCheckpointPeriodDelete; + case r'systemConfig.read': return Permission.systemConfigPeriodRead; + case r'systemConfig.update': return Permission.systemConfigPeriodUpdate; + case r'systemMetadata.read': return Permission.systemMetadataPeriodRead; + case r'systemMetadata.update': return Permission.systemMetadataPeriodUpdate; + case r'tag.create': return Permission.tagPeriodCreate; + case r'tag.read': return Permission.tagPeriodRead; + case r'tag.update': return Permission.tagPeriodUpdate; + case r'tag.delete': return Permission.tagPeriodDelete; + case r'tag.asset': return Permission.tagPeriodAsset; + case r'user.read': return Permission.userPeriodRead; + case r'user.update': return Permission.userPeriodUpdate; + case r'userLicense.create': return Permission.userLicensePeriodCreate; + case r'userLicense.read': return Permission.userLicensePeriodRead; + case r'userLicense.update': return Permission.userLicensePeriodUpdate; + case r'userLicense.delete': return Permission.userLicensePeriodDelete; + case r'userOnboarding.read': return Permission.userOnboardingPeriodRead; + case r'userOnboarding.update': return Permission.userOnboardingPeriodUpdate; + case r'userOnboarding.delete': return Permission.userOnboardingPeriodDelete; + case r'userPreference.read': return Permission.userPreferencePeriodRead; + case r'userPreference.update': return Permission.userPreferencePeriodUpdate; + case r'userProfileImage.create': return Permission.userProfileImagePeriodCreate; + case r'userProfileImage.read': return Permission.userProfileImagePeriodRead; + case r'userProfileImage.update': return Permission.userProfileImagePeriodUpdate; + case r'userProfileImage.delete': return Permission.userProfileImagePeriodDelete; + case r'queue.read': return Permission.queuePeriodRead; + case r'queue.update': return Permission.queuePeriodUpdate; + case r'queueJob.create': return Permission.queueJobPeriodCreate; + case r'queueJob.read': return Permission.queueJobPeriodRead; + case r'queueJob.update': return Permission.queueJobPeriodUpdate; + case r'queueJob.delete': return Permission.queueJobPeriodDelete; + case r'workflow.create': return Permission.workflowPeriodCreate; + case r'workflow.read': return Permission.workflowPeriodRead; + case r'workflow.update': return Permission.workflowPeriodUpdate; + case r'workflow.delete': return Permission.workflowPeriodDelete; + case r'adminUser.create': return Permission.adminUserPeriodCreate; + case r'adminUser.read': return Permission.adminUserPeriodRead; + case r'adminUser.update': return Permission.adminUserPeriodUpdate; + case r'adminUser.delete': return Permission.adminUserPeriodDelete; + case r'adminSession.read': return Permission.adminSessionPeriodRead; + case r'adminAuth.unlinkAll': return Permission.adminAuthPeriodUnlinkAll; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PermissionTypeTransformer] instance. + static PermissionTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/person_create_dto.dart b/mobile/openapi/lib/model/person_create_dto.dart new file mode 100644 index 0000000000..f2ba702c2f --- /dev/null +++ b/mobile/openapi/lib/model/person_create_dto.dart @@ -0,0 +1,169 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PersonCreateDto { + /// Returns a new [PersonCreateDto] instance. + PersonCreateDto({ + this.birthDate, + this.color, + this.isFavorite, + this.isHidden, + this.name, + }); + + /// Person date of birth + DateTime? birthDate; + + /// Person color (hex) + String? color; + + /// Mark as favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Person visibility (hidden) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isHidden; + + /// Person name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + @override + bool operator ==(Object other) => identical(this, other) || other is PersonCreateDto && + other.birthDate == birthDate && + other.color == color && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isHidden == null ? 0 : isHidden!.hashCode) + + (name == null ? 0 : name!.hashCode); + + @override + String toString() => 'PersonCreateDto[birthDate=$birthDate, color=$color, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isHidden != null) { + json[r'isHidden'] = this.isHidden; + } else { + // json[r'isHidden'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + return json; + } + + /// Returns a new [PersonCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PersonCreateDto? fromJson(dynamic value) { + upgradeDto(value, "PersonCreateDto"); + if (value is Map) { + final json = value.cast(); + + return PersonCreateDto( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isHidden: mapValueOfType(json, r'isHidden'), + name: mapValueOfType(json, r'name'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PersonCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PersonCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PersonCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PersonCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/person_response_dto.dart b/mobile/openapi/lib/model/person_response_dto.dart new file mode 100644 index 0000000000..455dfb98d6 --- /dev/null +++ b/mobile/openapi/lib/model/person_response_dto.dart @@ -0,0 +1,194 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PersonResponseDto { + /// Returns a new [PersonResponseDto] instance. + PersonResponseDto({ + required this.birthDate, + this.color, + required this.id, + this.isFavorite, + required this.isHidden, + required this.name, + required this.thumbnailPath, + this.updatedAt, + }); + + /// Person date of birth + DateTime? birthDate; + + /// Person color (hex) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? color; + + /// Person ID + String id; + + /// Is favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Is hidden + bool isHidden; + + /// Person name + String name; + + /// Thumbnail path + String thumbnailPath; + + /// Last update date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && + other.birthDate == birthDate && + other.color == color && + other.id == id && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name && + other.thumbnailPath == thumbnailPath && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (id.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isHidden.hashCode) + + (name.hashCode) + + (thumbnailPath.hashCode) + + (updatedAt == null ? 0 : updatedAt!.hashCode); + + @override + String toString() => 'PersonResponseDto[birthDate=$birthDate, color=$color, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + json[r'id'] = this.id; + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + json[r'isHidden'] = this.isHidden; + json[r'name'] = this.name; + json[r'thumbnailPath'] = this.thumbnailPath; + if (this.updatedAt != null) { + json[r'updatedAt'] = this.updatedAt!.toUtc().toIso8601String(); + } else { + // json[r'updatedAt'] = null; + } + return json; + } + + /// Returns a new [PersonResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PersonResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PersonResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PersonResponseDto( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + id: mapValueOfType(json, r'id')!, + isFavorite: mapValueOfType(json, r'isFavorite'), + isHidden: mapValueOfType(json, r'isHidden')!, + name: mapValueOfType(json, r'name')!, + thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, + updatedAt: mapDateTime(json, r'updatedAt', r''), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PersonResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PersonResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PersonResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PersonResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'birthDate', + 'id', + 'isHidden', + 'name', + 'thumbnailPath', + }; +} + diff --git a/mobile/openapi/lib/model/person_statistics_response_dto.dart b/mobile/openapi/lib/model/person_statistics_response_dto.dart new file mode 100644 index 0000000000..d2b45c8ccb --- /dev/null +++ b/mobile/openapi/lib/model/person_statistics_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PersonStatisticsResponseDto { + /// Returns a new [PersonStatisticsResponseDto] instance. + PersonStatisticsResponseDto({ + required this.assets, + }); + + /// Number of assets + int assets; + + @override + bool operator ==(Object other) => identical(this, other) || other is PersonStatisticsResponseDto && + other.assets == assets; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode); + + @override + String toString() => 'PersonStatisticsResponseDto[assets=$assets]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + return json; + } + + /// Returns a new [PersonStatisticsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PersonStatisticsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PersonStatisticsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PersonStatisticsResponseDto( + assets: mapValueOfType(json, r'assets')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PersonStatisticsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PersonStatisticsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PersonStatisticsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PersonStatisticsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + }; +} + diff --git a/mobile/openapi/lib/model/person_update_dto.dart b/mobile/openapi/lib/model/person_update_dto.dart new file mode 100644 index 0000000000..b56940e51d --- /dev/null +++ b/mobile/openapi/lib/model/person_update_dto.dart @@ -0,0 +1,187 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PersonUpdateDto { + /// Returns a new [PersonUpdateDto] instance. + PersonUpdateDto({ + this.birthDate, + this.color, + this.featureFaceAssetId, + this.isFavorite, + this.isHidden, + this.name, + }); + + /// Person date of birth + DateTime? birthDate; + + /// Person color (hex) + String? color; + + /// Asset ID used for feature face thumbnail + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? featureFaceAssetId; + + /// Mark as favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Person visibility (hidden) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isHidden; + + /// Person name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + @override + bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && + other.birthDate == birthDate && + other.color == color && + other.featureFaceAssetId == featureFaceAssetId && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isHidden == null ? 0 : isHidden!.hashCode) + + (name == null ? 0 : name!.hashCode); + + @override + String toString() => 'PersonUpdateDto[birthDate=$birthDate, color=$color, featureFaceAssetId=$featureFaceAssetId, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + if (this.featureFaceAssetId != null) { + json[r'featureFaceAssetId'] = this.featureFaceAssetId; + } else { + // json[r'featureFaceAssetId'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isHidden != null) { + json[r'isHidden'] = this.isHidden; + } else { + // json[r'isHidden'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + return json; + } + + /// Returns a new [PersonUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PersonUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "PersonUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return PersonUpdateDto( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + featureFaceAssetId: mapValueOfType(json, r'featureFaceAssetId'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isHidden: mapValueOfType(json, r'isHidden'), + name: mapValueOfType(json, r'name'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PersonUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PersonUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PersonUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PersonUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/person_with_faces_response_dto.dart b/mobile/openapi/lib/model/person_with_faces_response_dto.dart new file mode 100644 index 0000000000..f31c04b69f --- /dev/null +++ b/mobile/openapi/lib/model/person_with_faces_response_dto.dart @@ -0,0 +1,203 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PersonWithFacesResponseDto { + /// Returns a new [PersonWithFacesResponseDto] instance. + PersonWithFacesResponseDto({ + required this.birthDate, + this.color, + this.faces = const [], + required this.id, + this.isFavorite, + required this.isHidden, + required this.name, + required this.thumbnailPath, + this.updatedAt, + }); + + /// Person date of birth + DateTime? birthDate; + + /// Person color (hex) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? color; + + /// Face detections + List faces; + + /// Person ID + String id; + + /// Is favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Is hidden + bool isHidden; + + /// Person name + String name; + + /// Thumbnail path + String thumbnailPath; + + /// Last update date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto && + other.birthDate == birthDate && + other.color == color && + _deepEquality.equals(other.faces, faces) && + other.id == id && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name && + other.thumbnailPath == thumbnailPath && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (faces.hashCode) + + (id.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isHidden.hashCode) + + (name.hashCode) + + (thumbnailPath.hashCode) + + (updatedAt == null ? 0 : updatedAt!.hashCode); + + @override + String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, color=$color, faces=$faces, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + json[r'faces'] = this.faces; + json[r'id'] = this.id; + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + json[r'isHidden'] = this.isHidden; + json[r'name'] = this.name; + json[r'thumbnailPath'] = this.thumbnailPath; + if (this.updatedAt != null) { + json[r'updatedAt'] = this.updatedAt!.toUtc().toIso8601String(); + } else { + // json[r'updatedAt'] = null; + } + return json; + } + + /// Returns a new [PersonWithFacesResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PersonWithFacesResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PersonWithFacesResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PersonWithFacesResponseDto( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + faces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'faces']), + id: mapValueOfType(json, r'id')!, + isFavorite: mapValueOfType(json, r'isFavorite'), + isHidden: mapValueOfType(json, r'isHidden')!, + name: mapValueOfType(json, r'name')!, + thumbnailPath: mapValueOfType(json, r'thumbnailPath')!, + updatedAt: mapDateTime(json, r'updatedAt', r''), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PersonWithFacesResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PersonWithFacesResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PersonWithFacesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PersonWithFacesResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'birthDate', + 'faces', + 'id', + 'isHidden', + 'name', + 'thumbnailPath', + }; +} + diff --git a/mobile/openapi/lib/model/pin_code_change_dto.dart b/mobile/openapi/lib/model/pin_code_change_dto.dart new file mode 100644 index 0000000000..068cc9e91b --- /dev/null +++ b/mobile/openapi/lib/model/pin_code_change_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PinCodeChangeDto { + /// Returns a new [PinCodeChangeDto] instance. + PinCodeChangeDto({ + required this.newPinCode, + this.password, + this.pinCode, + }); + + /// New PIN code (4-6 digits) + String newPinCode; + + /// User password (required if PIN code is not provided) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? password; + + /// New PIN code (4-6 digits) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? pinCode; + + @override + bool operator ==(Object other) => identical(this, other) || other is PinCodeChangeDto && + other.newPinCode == newPinCode && + other.password == password && + other.pinCode == pinCode; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (newPinCode.hashCode) + + (password == null ? 0 : password!.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode); + + @override + String toString() => 'PinCodeChangeDto[newPinCode=$newPinCode, password=$password, pinCode=$pinCode]'; + + Map toJson() { + final json = {}; + json[r'newPinCode'] = this.newPinCode; + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + return json; + } + + /// Returns a new [PinCodeChangeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PinCodeChangeDto? fromJson(dynamic value) { + upgradeDto(value, "PinCodeChangeDto"); + if (value is Map) { + final json = value.cast(); + + return PinCodeChangeDto( + newPinCode: mapValueOfType(json, r'newPinCode')!, + password: mapValueOfType(json, r'password'), + pinCode: mapValueOfType(json, r'pinCode'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PinCodeChangeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PinCodeChangeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PinCodeChangeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PinCodeChangeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'newPinCode', + }; +} + diff --git a/mobile/openapi/lib/model/pin_code_reset_dto.dart b/mobile/openapi/lib/model/pin_code_reset_dto.dart new file mode 100644 index 0000000000..c37be76f18 --- /dev/null +++ b/mobile/openapi/lib/model/pin_code_reset_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PinCodeResetDto { + /// Returns a new [PinCodeResetDto] instance. + PinCodeResetDto({ + this.password, + this.pinCode, + }); + + /// User password (required if PIN code is not provided) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? password; + + /// New PIN code (4-6 digits) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? pinCode; + + @override + bool operator ==(Object other) => identical(this, other) || other is PinCodeResetDto && + other.password == password && + other.pinCode == pinCode; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (password == null ? 0 : password!.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode); + + @override + String toString() => 'PinCodeResetDto[password=$password, pinCode=$pinCode]'; + + Map toJson() { + final json = {}; + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + return json; + } + + /// Returns a new [PinCodeResetDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PinCodeResetDto? fromJson(dynamic value) { + upgradeDto(value, "PinCodeResetDto"); + if (value is Map) { + final json = value.cast(); + + return PinCodeResetDto( + password: mapValueOfType(json, r'password'), + pinCode: mapValueOfType(json, r'pinCode'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PinCodeResetDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PinCodeResetDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PinCodeResetDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PinCodeResetDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/pin_code_setup_dto.dart b/mobile/openapi/lib/model/pin_code_setup_dto.dart new file mode 100644 index 0000000000..e2f08f102b --- /dev/null +++ b/mobile/openapi/lib/model/pin_code_setup_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PinCodeSetupDto { + /// Returns a new [PinCodeSetupDto] instance. + PinCodeSetupDto({ + required this.pinCode, + }); + + /// PIN code (4-6 digits) + String pinCode; + + @override + bool operator ==(Object other) => identical(this, other) || other is PinCodeSetupDto && + other.pinCode == pinCode; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (pinCode.hashCode); + + @override + String toString() => 'PinCodeSetupDto[pinCode=$pinCode]'; + + Map toJson() { + final json = {}; + json[r'pinCode'] = this.pinCode; + return json; + } + + /// Returns a new [PinCodeSetupDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PinCodeSetupDto? fromJson(dynamic value) { + upgradeDto(value, "PinCodeSetupDto"); + if (value is Map) { + final json = value.cast(); + + return PinCodeSetupDto( + pinCode: mapValueOfType(json, r'pinCode')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PinCodeSetupDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PinCodeSetupDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PinCodeSetupDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PinCodeSetupDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'pinCode', + }; +} + diff --git a/mobile/openapi/lib/model/places_response_dto.dart b/mobile/openapi/lib/model/places_response_dto.dart new file mode 100644 index 0000000000..94aa58eba4 --- /dev/null +++ b/mobile/openapi/lib/model/places_response_dto.dart @@ -0,0 +1,154 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PlacesResponseDto { + /// Returns a new [PlacesResponseDto] instance. + PlacesResponseDto({ + this.admin1name, + this.admin2name, + required this.latitude, + required this.longitude, + required this.name, + }); + + /// Administrative level 1 name (state/province) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? admin1name; + + /// Administrative level 2 name (county/district) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? admin2name; + + /// Latitude coordinate + num latitude; + + /// Longitude coordinate + num longitude; + + /// Place name + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is PlacesResponseDto && + other.admin1name == admin1name && + other.admin2name == admin2name && + other.latitude == latitude && + other.longitude == longitude && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (admin1name == null ? 0 : admin1name!.hashCode) + + (admin2name == null ? 0 : admin2name!.hashCode) + + (latitude.hashCode) + + (longitude.hashCode) + + (name.hashCode); + + @override + String toString() => 'PlacesResponseDto[admin1name=$admin1name, admin2name=$admin2name, latitude=$latitude, longitude=$longitude, name=$name]'; + + Map toJson() { + final json = {}; + if (this.admin1name != null) { + json[r'admin1name'] = this.admin1name; + } else { + // json[r'admin1name'] = null; + } + if (this.admin2name != null) { + json[r'admin2name'] = this.admin2name; + } else { + // json[r'admin2name'] = null; + } + json[r'latitude'] = this.latitude; + json[r'longitude'] = this.longitude; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [PlacesResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PlacesResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PlacesResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PlacesResponseDto( + admin1name: mapValueOfType(json, r'admin1name'), + admin2name: mapValueOfType(json, r'admin2name'), + latitude: num.parse('${json[r'latitude']}'), + longitude: num.parse('${json[r'longitude']}'), + name: mapValueOfType(json, r'name')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PlacesResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PlacesResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PlacesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PlacesResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'latitude', + 'longitude', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_action_response_dto.dart b/mobile/openapi/lib/model/plugin_action_response_dto.dart new file mode 100644 index 0000000000..34fa314ba9 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_action_response_dto.dart @@ -0,0 +1,158 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginActionResponseDto { + /// Returns a new [PluginActionResponseDto] instance. + PluginActionResponseDto({ + required this.description, + required this.id, + required this.methodName, + required this.pluginId, + required this.schema, + this.supportedContexts = const [], + required this.title, + }); + + /// Action description + String description; + + /// Action ID + String id; + + /// Method name + String methodName; + + /// Plugin ID + String pluginId; + + /// Action schema + Object? schema; + + /// Supported contexts + List supportedContexts; + + /// Action title + String title; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginActionResponseDto && + other.description == description && + other.id == id && + other.methodName == methodName && + other.pluginId == pluginId && + other.schema == schema && + _deepEquality.equals(other.supportedContexts, supportedContexts) && + other.title == title; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (methodName.hashCode) + + (pluginId.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (supportedContexts.hashCode) + + (title.hashCode); + + @override + String toString() => 'PluginActionResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'methodName'] = this.methodName; + json[r'pluginId'] = this.pluginId; + if (this.schema != null) { + json[r'schema'] = this.schema; + } else { + // json[r'schema'] = null; + } + json[r'supportedContexts'] = this.supportedContexts; + json[r'title'] = this.title; + return json; + } + + /// Returns a new [PluginActionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginActionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginActionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginActionResponseDto( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + methodName: mapValueOfType(json, r'methodName')!, + pluginId: mapValueOfType(json, r'pluginId')!, + schema: mapValueOfType(json, r'schema'), + supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), + title: mapValueOfType(json, r'title')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginActionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginActionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginActionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginActionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'methodName', + 'pluginId', + 'schema', + 'supportedContexts', + 'title', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_context_type.dart b/mobile/openapi/lib/model/plugin_context_type.dart new file mode 100644 index 0000000000..6f4ac91fdb --- /dev/null +++ b/mobile/openapi/lib/model/plugin_context_type.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Context type +class PluginContextType { + /// Instantiate a new enum with the provided [value]. + const PluginContextType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const asset = PluginContextType._(r'asset'); + static const album = PluginContextType._(r'album'); + static const person = PluginContextType._(r'person'); + + /// List of all possible values in this [enum][PluginContextType]. + static const values = [ + asset, + album, + person, + ]; + + static PluginContextType? fromJson(dynamic value) => PluginContextTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginContextType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PluginContextType] to String, +/// and [decode] dynamic data back to [PluginContextType]. +class PluginContextTypeTypeTransformer { + factory PluginContextTypeTypeTransformer() => _instance ??= const PluginContextTypeTypeTransformer._(); + + const PluginContextTypeTypeTransformer._(); + + String encode(PluginContextType data) => data.value; + + /// Decodes a [dynamic value][data] to a PluginContextType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PluginContextType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'asset': return PluginContextType.asset; + case r'album': return PluginContextType.album; + case r'person': return PluginContextType.person; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PluginContextTypeTypeTransformer] instance. + static PluginContextTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/plugin_filter_response_dto.dart b/mobile/openapi/lib/model/plugin_filter_response_dto.dart new file mode 100644 index 0000000000..ea6411a9c1 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_filter_response_dto.dart @@ -0,0 +1,158 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginFilterResponseDto { + /// Returns a new [PluginFilterResponseDto] instance. + PluginFilterResponseDto({ + required this.description, + required this.id, + required this.methodName, + required this.pluginId, + required this.schema, + this.supportedContexts = const [], + required this.title, + }); + + /// Filter description + String description; + + /// Filter ID + String id; + + /// Method name + String methodName; + + /// Plugin ID + String pluginId; + + /// Filter schema + Object? schema; + + /// Supported contexts + List supportedContexts; + + /// Filter title + String title; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginFilterResponseDto && + other.description == description && + other.id == id && + other.methodName == methodName && + other.pluginId == pluginId && + other.schema == schema && + _deepEquality.equals(other.supportedContexts, supportedContexts) && + other.title == title; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (methodName.hashCode) + + (pluginId.hashCode) + + (schema == null ? 0 : schema!.hashCode) + + (supportedContexts.hashCode) + + (title.hashCode); + + @override + String toString() => 'PluginFilterResponseDto[description=$description, id=$id, methodName=$methodName, pluginId=$pluginId, schema=$schema, supportedContexts=$supportedContexts, title=$title]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'methodName'] = this.methodName; + json[r'pluginId'] = this.pluginId; + if (this.schema != null) { + json[r'schema'] = this.schema; + } else { + // json[r'schema'] = null; + } + json[r'supportedContexts'] = this.supportedContexts; + json[r'title'] = this.title; + return json; + } + + /// Returns a new [PluginFilterResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginFilterResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginFilterResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginFilterResponseDto( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + methodName: mapValueOfType(json, r'methodName')!, + pluginId: mapValueOfType(json, r'pluginId')!, + schema: mapValueOfType(json, r'schema'), + supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']), + title: mapValueOfType(json, r'title')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginFilterResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginFilterResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginFilterResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginFilterResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'methodName', + 'pluginId', + 'schema', + 'supportedContexts', + 'title', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_response_dto.dart b/mobile/openapi/lib/model/plugin_response_dto.dart new file mode 100644 index 0000000000..7a99896475 --- /dev/null +++ b/mobile/openapi/lib/model/plugin_response_dto.dart @@ -0,0 +1,181 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginResponseDto { + /// Returns a new [PluginResponseDto] instance. + PluginResponseDto({ + this.actions = const [], + required this.author, + required this.createdAt, + required this.description, + this.filters = const [], + required this.id, + required this.name, + required this.title, + required this.updatedAt, + required this.version, + }); + + /// Plugin actions + List actions; + + /// Plugin author + String author; + + /// Creation date + String createdAt; + + /// Plugin description + String description; + + /// Plugin filters + List filters; + + /// Plugin ID + String id; + + /// Plugin name + String name; + + /// Plugin title + String title; + + /// Last update date + String updatedAt; + + /// Plugin version + String version; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginResponseDto && + _deepEquality.equals(other.actions, actions) && + other.author == author && + other.createdAt == createdAt && + other.description == description && + _deepEquality.equals(other.filters, filters) && + other.id == id && + other.name == name && + other.title == title && + other.updatedAt == updatedAt && + other.version == version; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (author.hashCode) + + (createdAt.hashCode) + + (description.hashCode) + + (filters.hashCode) + + (id.hashCode) + + (name.hashCode) + + (title.hashCode) + + (updatedAt.hashCode) + + (version.hashCode); + + @override + String toString() => 'PluginResponseDto[actions=$actions, author=$author, createdAt=$createdAt, description=$description, filters=$filters, id=$id, name=$name, title=$title, updatedAt=$updatedAt, version=$version]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + json[r'author'] = this.author; + json[r'createdAt'] = this.createdAt; + json[r'description'] = this.description; + json[r'filters'] = this.filters; + json[r'id'] = this.id; + json[r'name'] = this.name; + json[r'title'] = this.title; + json[r'updatedAt'] = this.updatedAt; + json[r'version'] = this.version; + return json; + } + + /// Returns a new [PluginResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginResponseDto( + actions: PluginActionResponseDto.listFromJson(json[r'actions']), + author: mapValueOfType(json, r'author')!, + createdAt: mapValueOfType(json, r'createdAt')!, + description: mapValueOfType(json, r'description')!, + filters: PluginFilterResponseDto.listFromJson(json[r'filters']), + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + title: mapValueOfType(json, r'title')!, + updatedAt: mapValueOfType(json, r'updatedAt')!, + version: mapValueOfType(json, r'version')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'author', + 'createdAt', + 'description', + 'filters', + 'id', + 'name', + 'title', + 'updatedAt', + 'version', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_trigger_response_dto.dart b/mobile/openapi/lib/model/plugin_trigger_response_dto.dart new file mode 100644 index 0000000000..16a9604bcd --- /dev/null +++ b/mobile/openapi/lib/model/plugin_trigger_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PluginTriggerResponseDto { + /// Returns a new [PluginTriggerResponseDto] instance. + PluginTriggerResponseDto({ + required this.contextType, + required this.type, + }); + + /// Context type + PluginContextType contextType; + + /// Trigger type + PluginTriggerType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto && + other.contextType == contextType && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (contextType.hashCode) + + (type.hashCode); + + @override + String toString() => 'PluginTriggerResponseDto[contextType=$contextType, type=$type]'; + + Map toJson() { + final json = {}; + json[r'contextType'] = this.contextType; + json[r'type'] = this.type; + return json; + } + + /// Returns a new [PluginTriggerResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PluginTriggerResponseDto? fromJson(dynamic value) { + upgradeDto(value, "PluginTriggerResponseDto"); + if (value is Map) { + final json = value.cast(); + + return PluginTriggerResponseDto( + contextType: PluginContextType.fromJson(json[r'contextType'])!, + type: PluginTriggerType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginTriggerResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PluginTriggerResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PluginTriggerResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PluginTriggerResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'contextType', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/plugin_trigger_type.dart b/mobile/openapi/lib/model/plugin_trigger_type.dart new file mode 100644 index 0000000000..9ae64acf6c --- /dev/null +++ b/mobile/openapi/lib/model/plugin_trigger_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Trigger type +class PluginTriggerType { + /// Instantiate a new enum with the provided [value]. + const PluginTriggerType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const assetCreate = PluginTriggerType._(r'AssetCreate'); + static const personRecognized = PluginTriggerType._(r'PersonRecognized'); + + /// List of all possible values in this [enum][PluginTriggerType]. + static const values = [ + assetCreate, + personRecognized, + ]; + + static PluginTriggerType? fromJson(dynamic value) => PluginTriggerTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PluginTriggerType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [PluginTriggerType] to String, +/// and [decode] dynamic data back to [PluginTriggerType]. +class PluginTriggerTypeTypeTransformer { + factory PluginTriggerTypeTypeTransformer() => _instance ??= const PluginTriggerTypeTypeTransformer._(); + + const PluginTriggerTypeTypeTransformer._(); + + String encode(PluginTriggerType data) => data.value; + + /// Decodes a [dynamic value][data] to a PluginTriggerType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + PluginTriggerType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AssetCreate': return PluginTriggerType.assetCreate; + case r'PersonRecognized': return PluginTriggerType.personRecognized; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [PluginTriggerTypeTypeTransformer] instance. + static PluginTriggerTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/purchase_response.dart b/mobile/openapi/lib/model/purchase_response.dart new file mode 100644 index 0000000000..e55c286629 --- /dev/null +++ b/mobile/openapi/lib/model/purchase_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PurchaseResponse { + /// Returns a new [PurchaseResponse] instance. + PurchaseResponse({ + required this.hideBuyButtonUntil, + required this.showSupportBadge, + }); + + /// Date until which to hide buy button + String hideBuyButtonUntil; + + /// Whether to show support badge + bool showSupportBadge; + + @override + bool operator ==(Object other) => identical(this, other) || other is PurchaseResponse && + other.hideBuyButtonUntil == hideBuyButtonUntil && + other.showSupportBadge == showSupportBadge; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (hideBuyButtonUntil.hashCode) + + (showSupportBadge.hashCode); + + @override + String toString() => 'PurchaseResponse[hideBuyButtonUntil=$hideBuyButtonUntil, showSupportBadge=$showSupportBadge]'; + + Map toJson() { + final json = {}; + json[r'hideBuyButtonUntil'] = this.hideBuyButtonUntil; + json[r'showSupportBadge'] = this.showSupportBadge; + return json; + } + + /// Returns a new [PurchaseResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PurchaseResponse? fromJson(dynamic value) { + upgradeDto(value, "PurchaseResponse"); + if (value is Map) { + final json = value.cast(); + + return PurchaseResponse( + hideBuyButtonUntil: mapValueOfType(json, r'hideBuyButtonUntil')!, + showSupportBadge: mapValueOfType(json, r'showSupportBadge')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PurchaseResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PurchaseResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PurchaseResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PurchaseResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'hideBuyButtonUntil', + 'showSupportBadge', + }; +} + diff --git a/mobile/openapi/lib/model/purchase_update.dart b/mobile/openapi/lib/model/purchase_update.dart new file mode 100644 index 0000000000..913faf9bc4 --- /dev/null +++ b/mobile/openapi/lib/model/purchase_update.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class PurchaseUpdate { + /// Returns a new [PurchaseUpdate] instance. + PurchaseUpdate({ + this.hideBuyButtonUntil, + this.showSupportBadge, + }); + + /// Date until which to hide buy button + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? hideBuyButtonUntil; + + /// Whether to show support badge + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? showSupportBadge; + + @override + bool operator ==(Object other) => identical(this, other) || other is PurchaseUpdate && + other.hideBuyButtonUntil == hideBuyButtonUntil && + other.showSupportBadge == showSupportBadge; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (hideBuyButtonUntil == null ? 0 : hideBuyButtonUntil!.hashCode) + + (showSupportBadge == null ? 0 : showSupportBadge!.hashCode); + + @override + String toString() => 'PurchaseUpdate[hideBuyButtonUntil=$hideBuyButtonUntil, showSupportBadge=$showSupportBadge]'; + + Map toJson() { + final json = {}; + if (this.hideBuyButtonUntil != null) { + json[r'hideBuyButtonUntil'] = this.hideBuyButtonUntil; + } else { + // json[r'hideBuyButtonUntil'] = null; + } + if (this.showSupportBadge != null) { + json[r'showSupportBadge'] = this.showSupportBadge; + } else { + // json[r'showSupportBadge'] = null; + } + return json; + } + + /// Returns a new [PurchaseUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static PurchaseUpdate? fromJson(dynamic value) { + upgradeDto(value, "PurchaseUpdate"); + if (value is Map) { + final json = value.cast(); + + return PurchaseUpdate( + hideBuyButtonUntil: mapValueOfType(json, r'hideBuyButtonUntil'), + showSupportBadge: mapValueOfType(json, r'showSupportBadge'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = PurchaseUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = PurchaseUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of PurchaseUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = PurchaseUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/queue_command.dart b/mobile/openapi/lib/model/queue_command.dart new file mode 100644 index 0000000000..3cf689a02d --- /dev/null +++ b/mobile/openapi/lib/model/queue_command.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Queue command to execute +class QueueCommand { + /// Instantiate a new enum with the provided [value]. + const QueueCommand._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const start = QueueCommand._(r'start'); + static const pause = QueueCommand._(r'pause'); + static const resume = QueueCommand._(r'resume'); + static const empty = QueueCommand._(r'empty'); + static const clearFailed = QueueCommand._(r'clear-failed'); + + /// List of all possible values in this [enum][QueueCommand]. + static const values = [ + start, + pause, + resume, + empty, + clearFailed, + ]; + + static QueueCommand? fromJson(dynamic value) => QueueCommandTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueCommand.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [QueueCommand] to String, +/// and [decode] dynamic data back to [QueueCommand]. +class QueueCommandTypeTransformer { + factory QueueCommandTypeTransformer() => _instance ??= const QueueCommandTypeTransformer._(); + + const QueueCommandTypeTransformer._(); + + String encode(QueueCommand data) => data.value; + + /// Decodes a [dynamic value][data] to a QueueCommand. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + QueueCommand? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'start': return QueueCommand.start; + case r'pause': return QueueCommand.pause; + case r'resume': return QueueCommand.resume; + case r'empty': return QueueCommand.empty; + case r'clear-failed': return QueueCommand.clearFailed; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [QueueCommandTypeTransformer] instance. + static QueueCommandTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/queue_command_dto.dart b/mobile/openapi/lib/model/queue_command_dto.dart new file mode 100644 index 0000000000..9e1eea15db --- /dev/null +++ b/mobile/openapi/lib/model/queue_command_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueCommandDto { + /// Returns a new [QueueCommandDto] instance. + QueueCommandDto({ + required this.command, + this.force, + }); + + /// Queue command to execute + QueueCommand command; + + /// Force the command execution (if applicable) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? force; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueCommandDto && + other.command == command && + other.force == force; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (command.hashCode) + + (force == null ? 0 : force!.hashCode); + + @override + String toString() => 'QueueCommandDto[command=$command, force=$force]'; + + Map toJson() { + final json = {}; + json[r'command'] = this.command; + if (this.force != null) { + json[r'force'] = this.force; + } else { + // json[r'force'] = null; + } + return json; + } + + /// Returns a new [QueueCommandDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueCommandDto? fromJson(dynamic value) { + upgradeDto(value, "QueueCommandDto"); + if (value is Map) { + final json = value.cast(); + + return QueueCommandDto( + command: QueueCommand.fromJson(json[r'command'])!, + force: mapValueOfType(json, r'force'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueCommandDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueCommandDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueCommandDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueCommandDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'command', + }; +} + diff --git a/mobile/openapi/lib/model/queue_delete_dto.dart b/mobile/openapi/lib/model/queue_delete_dto.dart new file mode 100644 index 0000000000..d319238f92 --- /dev/null +++ b/mobile/openapi/lib/model/queue_delete_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueDeleteDto { + /// Returns a new [QueueDeleteDto] instance. + QueueDeleteDto({ + this.failed, + }); + + /// If true, will also remove failed jobs from the queue. + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? failed; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueDeleteDto && + other.failed == failed; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (failed == null ? 0 : failed!.hashCode); + + @override + String toString() => 'QueueDeleteDto[failed=$failed]'; + + Map toJson() { + final json = {}; + if (this.failed != null) { + json[r'failed'] = this.failed; + } else { + // json[r'failed'] = null; + } + return json; + } + + /// Returns a new [QueueDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "QueueDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return QueueDeleteDto( + failed: mapValueOfType(json, r'failed'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/queue_job_response_dto.dart b/mobile/openapi/lib/model/queue_job_response_dto.dart new file mode 100644 index 0000000000..2ce63784eb --- /dev/null +++ b/mobile/openapi/lib/model/queue_job_response_dto.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueJobResponseDto { + /// Returns a new [QueueJobResponseDto] instance. + QueueJobResponseDto({ + required this.data, + this.id, + required this.name, + required this.timestamp, + }); + + /// Job data payload + Object data; + + /// Job ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? id; + + /// Job name + JobName name; + + /// Job creation timestamp + int timestamp; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueJobResponseDto && + other.data == data && + other.id == id && + other.name == name && + other.timestamp == timestamp; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (data.hashCode) + + (id == null ? 0 : id!.hashCode) + + (name.hashCode) + + (timestamp.hashCode); + + @override + String toString() => 'QueueJobResponseDto[data=$data, id=$id, name=$name, timestamp=$timestamp]'; + + Map toJson() { + final json = {}; + json[r'data'] = this.data; + if (this.id != null) { + json[r'id'] = this.id; + } else { + // json[r'id'] = null; + } + json[r'name'] = this.name; + json[r'timestamp'] = this.timestamp; + return json; + } + + /// Returns a new [QueueJobResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueJobResponseDto? fromJson(dynamic value) { + upgradeDto(value, "QueueJobResponseDto"); + if (value is Map) { + final json = value.cast(); + + return QueueJobResponseDto( + data: mapValueOfType(json, r'data')!, + id: mapValueOfType(json, r'id'), + name: JobName.fromJson(json[r'name'])!, + timestamp: mapValueOfType(json, r'timestamp')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueJobResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueJobResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueJobResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueJobResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'data', + 'name', + 'timestamp', + }; +} + diff --git a/mobile/openapi/lib/model/queue_job_status.dart b/mobile/openapi/lib/model/queue_job_status.dart new file mode 100644 index 0000000000..03a1371cc5 --- /dev/null +++ b/mobile/openapi/lib/model/queue_job_status.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class QueueJobStatus { + /// Instantiate a new enum with the provided [value]. + const QueueJobStatus._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const active = QueueJobStatus._(r'active'); + static const failed = QueueJobStatus._(r'failed'); + static const completed = QueueJobStatus._(r'completed'); + static const delayed = QueueJobStatus._(r'delayed'); + static const waiting = QueueJobStatus._(r'waiting'); + static const paused = QueueJobStatus._(r'paused'); + + /// List of all possible values in this [enum][QueueJobStatus]. + static const values = [ + active, + failed, + completed, + delayed, + waiting, + paused, + ]; + + static QueueJobStatus? fromJson(dynamic value) => QueueJobStatusTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueJobStatus.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [QueueJobStatus] to String, +/// and [decode] dynamic data back to [QueueJobStatus]. +class QueueJobStatusTypeTransformer { + factory QueueJobStatusTypeTransformer() => _instance ??= const QueueJobStatusTypeTransformer._(); + + const QueueJobStatusTypeTransformer._(); + + String encode(QueueJobStatus data) => data.value; + + /// Decodes a [dynamic value][data] to a QueueJobStatus. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + QueueJobStatus? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'active': return QueueJobStatus.active; + case r'failed': return QueueJobStatus.failed; + case r'completed': return QueueJobStatus.completed; + case r'delayed': return QueueJobStatus.delayed; + case r'waiting': return QueueJobStatus.waiting; + case r'paused': return QueueJobStatus.paused; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [QueueJobStatusTypeTransformer] instance. + static QueueJobStatusTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/queue_name.dart b/mobile/openapi/lib/model/queue_name.dart new file mode 100644 index 0000000000..d94304d0d3 --- /dev/null +++ b/mobile/openapi/lib/model/queue_name.dart @@ -0,0 +1,133 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class QueueName { + /// Instantiate a new enum with the provided [value]. + const QueueName._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const thumbnailGeneration = QueueName._(r'thumbnailGeneration'); + static const metadataExtraction = QueueName._(r'metadataExtraction'); + static const videoConversion = QueueName._(r'videoConversion'); + static const faceDetection = QueueName._(r'faceDetection'); + static const facialRecognition = QueueName._(r'facialRecognition'); + static const smartSearch = QueueName._(r'smartSearch'); + static const duplicateDetection = QueueName._(r'duplicateDetection'); + static const backgroundTask = QueueName._(r'backgroundTask'); + static const storageTemplateMigration = QueueName._(r'storageTemplateMigration'); + static const migration = QueueName._(r'migration'); + static const search = QueueName._(r'search'); + static const sidecar = QueueName._(r'sidecar'); + static const library_ = QueueName._(r'library'); + static const notifications = QueueName._(r'notifications'); + static const backupDatabase = QueueName._(r'backupDatabase'); + static const ocr = QueueName._(r'ocr'); + static const workflow = QueueName._(r'workflow'); + static const editor = QueueName._(r'editor'); + + /// List of all possible values in this [enum][QueueName]. + static const values = [ + thumbnailGeneration, + metadataExtraction, + videoConversion, + faceDetection, + facialRecognition, + smartSearch, + duplicateDetection, + backgroundTask, + storageTemplateMigration, + migration, + search, + sidecar, + library_, + notifications, + backupDatabase, + ocr, + workflow, + editor, + ]; + + static QueueName? fromJson(dynamic value) => QueueNameTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueName.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [QueueName] to String, +/// and [decode] dynamic data back to [QueueName]. +class QueueNameTypeTransformer { + factory QueueNameTypeTransformer() => _instance ??= const QueueNameTypeTransformer._(); + + const QueueNameTypeTransformer._(); + + String encode(QueueName data) => data.value; + + /// Decodes a [dynamic value][data] to a QueueName. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + QueueName? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'thumbnailGeneration': return QueueName.thumbnailGeneration; + case r'metadataExtraction': return QueueName.metadataExtraction; + case r'videoConversion': return QueueName.videoConversion; + case r'faceDetection': return QueueName.faceDetection; + case r'facialRecognition': return QueueName.facialRecognition; + case r'smartSearch': return QueueName.smartSearch; + case r'duplicateDetection': return QueueName.duplicateDetection; + case r'backgroundTask': return QueueName.backgroundTask; + case r'storageTemplateMigration': return QueueName.storageTemplateMigration; + case r'migration': return QueueName.migration; + case r'search': return QueueName.search; + case r'sidecar': return QueueName.sidecar; + case r'library': return QueueName.library_; + case r'notifications': return QueueName.notifications; + case r'backupDatabase': return QueueName.backupDatabase; + case r'ocr': return QueueName.ocr; + case r'workflow': return QueueName.workflow; + case r'editor': return QueueName.editor; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [QueueNameTypeTransformer] instance. + static QueueNameTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/queue_response_dto.dart b/mobile/openapi/lib/model/queue_response_dto.dart new file mode 100644 index 0000000000..ac9244514c --- /dev/null +++ b/mobile/openapi/lib/model/queue_response_dto.dart @@ -0,0 +1,117 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueResponseDto { + /// Returns a new [QueueResponseDto] instance. + QueueResponseDto({ + required this.isPaused, + required this.name, + required this.statistics, + }); + + /// Whether the queue is paused + bool isPaused; + + /// Queue name + QueueName name; + + QueueStatisticsDto statistics; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueResponseDto && + other.isPaused == isPaused && + other.name == name && + other.statistics == statistics; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isPaused.hashCode) + + (name.hashCode) + + (statistics.hashCode); + + @override + String toString() => 'QueueResponseDto[isPaused=$isPaused, name=$name, statistics=$statistics]'; + + Map toJson() { + final json = {}; + json[r'isPaused'] = this.isPaused; + json[r'name'] = this.name; + json[r'statistics'] = this.statistics; + return json; + } + + /// Returns a new [QueueResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueResponseDto? fromJson(dynamic value) { + upgradeDto(value, "QueueResponseDto"); + if (value is Map) { + final json = value.cast(); + + return QueueResponseDto( + isPaused: mapValueOfType(json, r'isPaused')!, + name: QueueName.fromJson(json[r'name'])!, + statistics: QueueStatisticsDto.fromJson(json[r'statistics'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isPaused', + 'name', + 'statistics', + }; +} + diff --git a/mobile/openapi/lib/model/queue_response_legacy_dto.dart b/mobile/openapi/lib/model/queue_response_legacy_dto.dart new file mode 100644 index 0000000000..214b0b31f6 --- /dev/null +++ b/mobile/openapi/lib/model/queue_response_legacy_dto.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueResponseLegacyDto { + /// Returns a new [QueueResponseLegacyDto] instance. + QueueResponseLegacyDto({ + required this.jobCounts, + required this.queueStatus, + }); + + QueueStatisticsDto jobCounts; + + QueueStatusLegacyDto queueStatus; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueResponseLegacyDto && + other.jobCounts == jobCounts && + other.queueStatus == queueStatus; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (jobCounts.hashCode) + + (queueStatus.hashCode); + + @override + String toString() => 'QueueResponseLegacyDto[jobCounts=$jobCounts, queueStatus=$queueStatus]'; + + Map toJson() { + final json = {}; + json[r'jobCounts'] = this.jobCounts; + json[r'queueStatus'] = this.queueStatus; + return json; + } + + /// Returns a new [QueueResponseLegacyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueResponseLegacyDto? fromJson(dynamic value) { + upgradeDto(value, "QueueResponseLegacyDto"); + if (value is Map) { + final json = value.cast(); + + return QueueResponseLegacyDto( + jobCounts: QueueStatisticsDto.fromJson(json[r'jobCounts'])!, + queueStatus: QueueStatusLegacyDto.fromJson(json[r'queueStatus'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueResponseLegacyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueResponseLegacyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueResponseLegacyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueResponseLegacyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'jobCounts', + 'queueStatus', + }; +} + diff --git a/mobile/openapi/lib/model/queue_statistics_dto.dart b/mobile/openapi/lib/model/queue_statistics_dto.dart new file mode 100644 index 0000000000..c9a37ee30a --- /dev/null +++ b/mobile/openapi/lib/model/queue_statistics_dto.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueStatisticsDto { + /// Returns a new [QueueStatisticsDto] instance. + QueueStatisticsDto({ + required this.active, + required this.completed, + required this.delayed, + required this.failed, + required this.paused, + required this.waiting, + }); + + /// Number of active jobs + int active; + + /// Number of completed jobs + int completed; + + /// Number of delayed jobs + int delayed; + + /// Number of failed jobs + int failed; + + /// Number of paused jobs + int paused; + + /// Number of waiting jobs + int waiting; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueStatisticsDto && + other.active == active && + other.completed == completed && + other.delayed == delayed && + other.failed == failed && + other.paused == paused && + other.waiting == waiting; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (active.hashCode) + + (completed.hashCode) + + (delayed.hashCode) + + (failed.hashCode) + + (paused.hashCode) + + (waiting.hashCode); + + @override + String toString() => 'QueueStatisticsDto[active=$active, completed=$completed, delayed=$delayed, failed=$failed, paused=$paused, waiting=$waiting]'; + + Map toJson() { + final json = {}; + json[r'active'] = this.active; + json[r'completed'] = this.completed; + json[r'delayed'] = this.delayed; + json[r'failed'] = this.failed; + json[r'paused'] = this.paused; + json[r'waiting'] = this.waiting; + return json; + } + + /// Returns a new [QueueStatisticsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueStatisticsDto? fromJson(dynamic value) { + upgradeDto(value, "QueueStatisticsDto"); + if (value is Map) { + final json = value.cast(); + + return QueueStatisticsDto( + active: mapValueOfType(json, r'active')!, + completed: mapValueOfType(json, r'completed')!, + delayed: mapValueOfType(json, r'delayed')!, + failed: mapValueOfType(json, r'failed')!, + paused: mapValueOfType(json, r'paused')!, + waiting: mapValueOfType(json, r'waiting')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueStatisticsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueStatisticsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueStatisticsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueStatisticsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'active', + 'completed', + 'delayed', + 'failed', + 'paused', + 'waiting', + }; +} + diff --git a/mobile/openapi/lib/model/queue_status_legacy_dto.dart b/mobile/openapi/lib/model/queue_status_legacy_dto.dart new file mode 100644 index 0000000000..de6ce63319 --- /dev/null +++ b/mobile/openapi/lib/model/queue_status_legacy_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueStatusLegacyDto { + /// Returns a new [QueueStatusLegacyDto] instance. + QueueStatusLegacyDto({ + required this.isActive, + required this.isPaused, + }); + + /// Whether the queue is currently active (has running jobs) + bool isActive; + + /// Whether the queue is paused + bool isPaused; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueStatusLegacyDto && + other.isActive == isActive && + other.isPaused == isPaused; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isActive.hashCode) + + (isPaused.hashCode); + + @override + String toString() => 'QueueStatusLegacyDto[isActive=$isActive, isPaused=$isPaused]'; + + Map toJson() { + final json = {}; + json[r'isActive'] = this.isActive; + json[r'isPaused'] = this.isPaused; + return json; + } + + /// Returns a new [QueueStatusLegacyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueStatusLegacyDto? fromJson(dynamic value) { + upgradeDto(value, "QueueStatusLegacyDto"); + if (value is Map) { + final json = value.cast(); + + return QueueStatusLegacyDto( + isActive: mapValueOfType(json, r'isActive')!, + isPaused: mapValueOfType(json, r'isPaused')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueStatusLegacyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueStatusLegacyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueStatusLegacyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueStatusLegacyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'isActive', + 'isPaused', + }; +} + diff --git a/mobile/openapi/lib/model/queue_update_dto.dart b/mobile/openapi/lib/model/queue_update_dto.dart new file mode 100644 index 0000000000..28aafe95f7 --- /dev/null +++ b/mobile/openapi/lib/model/queue_update_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueueUpdateDto { + /// Returns a new [QueueUpdateDto] instance. + QueueUpdateDto({ + this.isPaused, + }); + + /// Whether to pause the queue + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isPaused; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueueUpdateDto && + other.isPaused == isPaused; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isPaused == null ? 0 : isPaused!.hashCode); + + @override + String toString() => 'QueueUpdateDto[isPaused=$isPaused]'; + + Map toJson() { + final json = {}; + if (this.isPaused != null) { + json[r'isPaused'] = this.isPaused; + } else { + // json[r'isPaused'] = null; + } + return json; + } + + /// Returns a new [QueueUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueueUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "QueueUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return QueueUpdateDto( + isPaused: mapValueOfType(json, r'isPaused'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueueUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueueUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueueUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueueUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/queues_response_legacy_dto.dart b/mobile/openapi/lib/model/queues_response_legacy_dto.dart new file mode 100644 index 0000000000..c7bc23cb4d --- /dev/null +++ b/mobile/openapi/lib/model/queues_response_legacy_dto.dart @@ -0,0 +1,235 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class QueuesResponseLegacyDto { + /// Returns a new [QueuesResponseLegacyDto] instance. + QueuesResponseLegacyDto({ + required this.backgroundTask, + required this.backupDatabase, + required this.duplicateDetection, + required this.editor, + required this.faceDetection, + required this.facialRecognition, + required this.library_, + required this.metadataExtraction, + required this.migration, + required this.notifications, + required this.ocr, + required this.search, + required this.sidecar, + required this.smartSearch, + required this.storageTemplateMigration, + required this.thumbnailGeneration, + required this.videoConversion, + required this.workflow, + }); + + QueueResponseLegacyDto backgroundTask; + + QueueResponseLegacyDto backupDatabase; + + QueueResponseLegacyDto duplicateDetection; + + QueueResponseLegacyDto editor; + + QueueResponseLegacyDto faceDetection; + + QueueResponseLegacyDto facialRecognition; + + QueueResponseLegacyDto library_; + + QueueResponseLegacyDto metadataExtraction; + + QueueResponseLegacyDto migration; + + QueueResponseLegacyDto notifications; + + QueueResponseLegacyDto ocr; + + QueueResponseLegacyDto search; + + QueueResponseLegacyDto sidecar; + + QueueResponseLegacyDto smartSearch; + + QueueResponseLegacyDto storageTemplateMigration; + + QueueResponseLegacyDto thumbnailGeneration; + + QueueResponseLegacyDto videoConversion; + + QueueResponseLegacyDto workflow; + + @override + bool operator ==(Object other) => identical(this, other) || other is QueuesResponseLegacyDto && + other.backgroundTask == backgroundTask && + other.backupDatabase == backupDatabase && + other.duplicateDetection == duplicateDetection && + other.editor == editor && + other.faceDetection == faceDetection && + other.facialRecognition == facialRecognition && + other.library_ == library_ && + other.metadataExtraction == metadataExtraction && + other.migration == migration && + other.notifications == notifications && + other.ocr == ocr && + other.search == search && + other.sidecar == sidecar && + other.smartSearch == smartSearch && + other.storageTemplateMigration == storageTemplateMigration && + other.thumbnailGeneration == thumbnailGeneration && + other.videoConversion == videoConversion && + other.workflow == workflow; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backgroundTask.hashCode) + + (backupDatabase.hashCode) + + (duplicateDetection.hashCode) + + (editor.hashCode) + + (faceDetection.hashCode) + + (facialRecognition.hashCode) + + (library_.hashCode) + + (metadataExtraction.hashCode) + + (migration.hashCode) + + (notifications.hashCode) + + (ocr.hashCode) + + (search.hashCode) + + (sidecar.hashCode) + + (smartSearch.hashCode) + + (storageTemplateMigration.hashCode) + + (thumbnailGeneration.hashCode) + + (videoConversion.hashCode) + + (workflow.hashCode); + + @override + String toString() => 'QueuesResponseLegacyDto[backgroundTask=$backgroundTask, backupDatabase=$backupDatabase, duplicateDetection=$duplicateDetection, editor=$editor, faceDetection=$faceDetection, facialRecognition=$facialRecognition, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, storageTemplateMigration=$storageTemplateMigration, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; + + Map toJson() { + final json = {}; + json[r'backgroundTask'] = this.backgroundTask; + json[r'backupDatabase'] = this.backupDatabase; + json[r'duplicateDetection'] = this.duplicateDetection; + json[r'editor'] = this.editor; + json[r'faceDetection'] = this.faceDetection; + json[r'facialRecognition'] = this.facialRecognition; + json[r'library'] = this.library_; + json[r'metadataExtraction'] = this.metadataExtraction; + json[r'migration'] = this.migration; + json[r'notifications'] = this.notifications; + json[r'ocr'] = this.ocr; + json[r'search'] = this.search; + json[r'sidecar'] = this.sidecar; + json[r'smartSearch'] = this.smartSearch; + json[r'storageTemplateMigration'] = this.storageTemplateMigration; + json[r'thumbnailGeneration'] = this.thumbnailGeneration; + json[r'videoConversion'] = this.videoConversion; + json[r'workflow'] = this.workflow; + return json; + } + + /// Returns a new [QueuesResponseLegacyDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static QueuesResponseLegacyDto? fromJson(dynamic value) { + upgradeDto(value, "QueuesResponseLegacyDto"); + if (value is Map) { + final json = value.cast(); + + return QueuesResponseLegacyDto( + backgroundTask: QueueResponseLegacyDto.fromJson(json[r'backgroundTask'])!, + backupDatabase: QueueResponseLegacyDto.fromJson(json[r'backupDatabase'])!, + duplicateDetection: QueueResponseLegacyDto.fromJson(json[r'duplicateDetection'])!, + editor: QueueResponseLegacyDto.fromJson(json[r'editor'])!, + faceDetection: QueueResponseLegacyDto.fromJson(json[r'faceDetection'])!, + facialRecognition: QueueResponseLegacyDto.fromJson(json[r'facialRecognition'])!, + library_: QueueResponseLegacyDto.fromJson(json[r'library'])!, + metadataExtraction: QueueResponseLegacyDto.fromJson(json[r'metadataExtraction'])!, + migration: QueueResponseLegacyDto.fromJson(json[r'migration'])!, + notifications: QueueResponseLegacyDto.fromJson(json[r'notifications'])!, + ocr: QueueResponseLegacyDto.fromJson(json[r'ocr'])!, + search: QueueResponseLegacyDto.fromJson(json[r'search'])!, + sidecar: QueueResponseLegacyDto.fromJson(json[r'sidecar'])!, + smartSearch: QueueResponseLegacyDto.fromJson(json[r'smartSearch'])!, + storageTemplateMigration: QueueResponseLegacyDto.fromJson(json[r'storageTemplateMigration'])!, + thumbnailGeneration: QueueResponseLegacyDto.fromJson(json[r'thumbnailGeneration'])!, + videoConversion: QueueResponseLegacyDto.fromJson(json[r'videoConversion'])!, + workflow: QueueResponseLegacyDto.fromJson(json[r'workflow'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = QueuesResponseLegacyDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = QueuesResponseLegacyDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of QueuesResponseLegacyDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = QueuesResponseLegacyDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backgroundTask', + 'backupDatabase', + 'duplicateDetection', + 'editor', + 'faceDetection', + 'facialRecognition', + 'library', + 'metadataExtraction', + 'migration', + 'notifications', + 'ocr', + 'search', + 'sidecar', + 'smartSearch', + 'storageTemplateMigration', + 'thumbnailGeneration', + 'videoConversion', + 'workflow', + }; +} + diff --git a/mobile/openapi/lib/model/random_search_dto.dart b/mobile/openapi/lib/model/random_search_dto.dart new file mode 100644 index 0000000000..d5803c9cc7 --- /dev/null +++ b/mobile/openapi/lib/model/random_search_dto.dart @@ -0,0 +1,631 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class RandomSearchDto { + /// Returns a new [RandomSearchDto] instance. + RandomSearchDto({ + this.albumIds = const [], + this.city, + this.country, + this.createdAfter, + this.createdBefore, + this.deviceId, + this.isEncoded, + this.isFavorite, + this.isMotion, + this.isNotInAlbum, + this.isOffline, + this.lensModel, + this.libraryId, + this.make, + this.model, + this.ocr, + this.personIds = const [], + this.rating, + this.size, + this.state, + this.tagIds = const [], + this.takenAfter, + this.takenBefore, + this.trashedAfter, + this.trashedBefore, + this.type, + this.updatedAfter, + this.updatedBefore, + this.visibility, + this.withDeleted, + this.withExif, + this.withPeople, + this.withStacked, + }); + + /// Filter by album IDs + List albumIds; + + /// Filter by city name + String? city; + + /// Filter by country name + String? country; + + /// Filter by creation date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdAfter; + + /// Filter by creation date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdBefore; + + /// Device ID to filter by + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceId; + + /// Filter by encoded status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isEncoded; + + /// Filter by favorite status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Filter by motion photo status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isMotion; + + /// Filter assets not in any album + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isNotInAlbum; + + /// Filter by offline status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isOffline; + + /// Filter by lens model + String? lensModel; + + /// Library ID to filter by + String? libraryId; + + /// Filter by camera make + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? make; + + /// Filter by camera model + String? model; + + /// Filter by OCR text content + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + + /// Filter by person IDs + List personIds; + + /// Filter by rating [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Number of results to return + /// + /// Minimum value: 1 + /// Maximum value: 1000 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? size; + + /// Filter by state/province name + String? state; + + /// Filter by tag IDs + List? tagIds; + + /// Filter by taken date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenAfter; + + /// Filter by taken date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenBefore; + + /// Filter by trash date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedAfter; + + /// Filter by trash date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedBefore; + + /// Asset type filter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetTypeEnum? type; + + /// Filter by update date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAfter; + + /// Filter by update date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedBefore; + + /// Filter by visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + /// Include deleted assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withDeleted; + + /// Include EXIF data in response + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withExif; + + /// Include people data in response + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withPeople; + + /// Include stacked assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withStacked; + + @override + bool operator ==(Object other) => identical(this, other) || other is RandomSearchDto && + _deepEquality.equals(other.albumIds, albumIds) && + other.city == city && + other.country == country && + other.createdAfter == createdAfter && + other.createdBefore == createdBefore && + other.deviceId == deviceId && + other.isEncoded == isEncoded && + other.isFavorite == isFavorite && + other.isMotion == isMotion && + other.isNotInAlbum == isNotInAlbum && + other.isOffline == isOffline && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.make == make && + other.model == model && + other.ocr == ocr && + _deepEquality.equals(other.personIds, personIds) && + other.rating == rating && + other.size == size && + other.state == state && + _deepEquality.equals(other.tagIds, tagIds) && + other.takenAfter == takenAfter && + other.takenBefore == takenBefore && + other.trashedAfter == trashedAfter && + other.trashedBefore == trashedBefore && + other.type == type && + other.updatedAfter == updatedAfter && + other.updatedBefore == updatedBefore && + other.visibility == visibility && + other.withDeleted == withDeleted && + other.withExif == withExif && + other.withPeople == withPeople && + other.withStacked == withStacked; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumIds.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAfter == null ? 0 : createdAfter!.hashCode) + + (createdBefore == null ? 0 : createdBefore!.hashCode) + + (deviceId == null ? 0 : deviceId!.hashCode) + + (isEncoded == null ? 0 : isEncoded!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isMotion == null ? 0 : isMotion!.hashCode) + + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + + (isOffline == null ? 0 : isOffline!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + + (personIds.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (size == null ? 0 : size!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (tagIds == null ? 0 : tagIds!.hashCode) + + (takenAfter == null ? 0 : takenAfter!.hashCode) + + (takenBefore == null ? 0 : takenBefore!.hashCode) + + (trashedAfter == null ? 0 : trashedAfter!.hashCode) + + (trashedBefore == null ? 0 : trashedBefore!.hashCode) + + (type == null ? 0 : type!.hashCode) + + (updatedAfter == null ? 0 : updatedAfter!.hashCode) + + (updatedBefore == null ? 0 : updatedBefore!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode) + + (withDeleted == null ? 0 : withDeleted!.hashCode) + + (withExif == null ? 0 : withExif!.hashCode) + + (withPeople == null ? 0 : withPeople!.hashCode) + + (withStacked == null ? 0 : withStacked!.hashCode); + + @override + String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]'; + + Map toJson() { + final json = {}; + json[r'albumIds'] = this.albumIds; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.createdAfter != null) { + json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String(); + } else { + // json[r'createdAfter'] = null; + } + if (this.createdBefore != null) { + json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String(); + } else { + // json[r'createdBefore'] = null; + } + if (this.deviceId != null) { + json[r'deviceId'] = this.deviceId; + } else { + // json[r'deviceId'] = null; + } + if (this.isEncoded != null) { + json[r'isEncoded'] = this.isEncoded; + } else { + // json[r'isEncoded'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isMotion != null) { + json[r'isMotion'] = this.isMotion; + } else { + // json[r'isMotion'] = null; + } + if (this.isNotInAlbum != null) { + json[r'isNotInAlbum'] = this.isNotInAlbum; + } else { + // json[r'isNotInAlbum'] = null; + } + if (this.isOffline != null) { + json[r'isOffline'] = this.isOffline; + } else { + // json[r'isOffline'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } + json[r'personIds'] = this.personIds; + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.size != null) { + json[r'size'] = this.size; + } else { + // json[r'size'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.tagIds != null) { + json[r'tagIds'] = this.tagIds; + } else { + // json[r'tagIds'] = null; + } + if (this.takenAfter != null) { + json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String(); + } else { + // json[r'takenAfter'] = null; + } + if (this.takenBefore != null) { + json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String(); + } else { + // json[r'takenBefore'] = null; + } + if (this.trashedAfter != null) { + json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); + } else { + // json[r'trashedAfter'] = null; + } + if (this.trashedBefore != null) { + json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String(); + } else { + // json[r'trashedBefore'] = null; + } + if (this.type != null) { + json[r'type'] = this.type; + } else { + // json[r'type'] = null; + } + if (this.updatedAfter != null) { + json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String(); + } else { + // json[r'updatedAfter'] = null; + } + if (this.updatedBefore != null) { + json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String(); + } else { + // json[r'updatedBefore'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + if (this.withDeleted != null) { + json[r'withDeleted'] = this.withDeleted; + } else { + // json[r'withDeleted'] = null; + } + if (this.withExif != null) { + json[r'withExif'] = this.withExif; + } else { + // json[r'withExif'] = null; + } + if (this.withPeople != null) { + json[r'withPeople'] = this.withPeople; + } else { + // json[r'withPeople'] = null; + } + if (this.withStacked != null) { + json[r'withStacked'] = this.withStacked; + } else { + // json[r'withStacked'] = null; + } + return json; + } + + /// Returns a new [RandomSearchDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static RandomSearchDto? fromJson(dynamic value) { + upgradeDto(value, "RandomSearchDto"); + if (value is Map) { + final json = value.cast(); + + return RandomSearchDto( + albumIds: json[r'albumIds'] is Iterable + ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) + : const [], + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + createdAfter: mapDateTime(json, r'createdAfter', r''), + createdBefore: mapDateTime(json, r'createdBefore', r''), + deviceId: mapValueOfType(json, r'deviceId'), + isEncoded: mapValueOfType(json, r'isEncoded'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isMotion: mapValueOfType(json, r'isMotion'), + isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), + isOffline: mapValueOfType(json, r'isOffline'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), + personIds: json[r'personIds'] is Iterable + ? (json[r'personIds'] as Iterable).cast().toList(growable: false) + : const [], + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + size: num.parse('${json[r'size']}'), + state: mapValueOfType(json, r'state'), + tagIds: json[r'tagIds'] is Iterable + ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) + : const [], + takenAfter: mapDateTime(json, r'takenAfter', r''), + takenBefore: mapDateTime(json, r'takenBefore', r''), + trashedAfter: mapDateTime(json, r'trashedAfter', r''), + trashedBefore: mapDateTime(json, r'trashedBefore', r''), + type: AssetTypeEnum.fromJson(json[r'type']), + updatedAfter: mapDateTime(json, r'updatedAfter', r''), + updatedBefore: mapDateTime(json, r'updatedBefore', r''), + visibility: AssetVisibility.fromJson(json[r'visibility']), + withDeleted: mapValueOfType(json, r'withDeleted'), + withExif: mapValueOfType(json, r'withExif'), + withPeople: mapValueOfType(json, r'withPeople'), + withStacked: mapValueOfType(json, r'withStacked'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = RandomSearchDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = RandomSearchDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of RandomSearchDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = RandomSearchDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/ratings_response.dart b/mobile/openapi/lib/model/ratings_response.dart new file mode 100644 index 0000000000..4346fa5c58 --- /dev/null +++ b/mobile/openapi/lib/model/ratings_response.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class RatingsResponse { + /// Returns a new [RatingsResponse] instance. + RatingsResponse({ + this.enabled = false, + }); + + /// Whether ratings are enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is RatingsResponse && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'RatingsResponse[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [RatingsResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static RatingsResponse? fromJson(dynamic value) { + upgradeDto(value, "RatingsResponse"); + if (value is Map) { + final json = value.cast(); + + return RatingsResponse( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = RatingsResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = RatingsResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of RatingsResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = RatingsResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/ratings_update.dart b/mobile/openapi/lib/model/ratings_update.dart new file mode 100644 index 0000000000..8079172e21 --- /dev/null +++ b/mobile/openapi/lib/model/ratings_update.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class RatingsUpdate { + /// Returns a new [RatingsUpdate] instance. + RatingsUpdate({ + this.enabled, + }); + + /// Whether ratings are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is RatingsUpdate && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled == null ? 0 : enabled!.hashCode); + + @override + String toString() => 'RatingsUpdate[enabled=$enabled]'; + + Map toJson() { + final json = {}; + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + return json; + } + + /// Returns a new [RatingsUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static RatingsUpdate? fromJson(dynamic value) { + upgradeDto(value, "RatingsUpdate"); + if (value is Map) { + final json = value.cast(); + + return RatingsUpdate( + enabled: mapValueOfType(json, r'enabled'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = RatingsUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = RatingsUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of RatingsUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = RatingsUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/reaction_level.dart b/mobile/openapi/lib/model/reaction_level.dart new file mode 100644 index 0000000000..29568b9d11 --- /dev/null +++ b/mobile/openapi/lib/model/reaction_level.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ReactionLevel { + /// Instantiate a new enum with the provided [value]. + const ReactionLevel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const album = ReactionLevel._(r'album'); + static const asset = ReactionLevel._(r'asset'); + + /// List of all possible values in this [enum][ReactionLevel]. + static const values = [ + album, + asset, + ]; + + static ReactionLevel? fromJson(dynamic value) => ReactionLevelTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReactionLevel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ReactionLevel] to String, +/// and [decode] dynamic data back to [ReactionLevel]. +class ReactionLevelTypeTransformer { + factory ReactionLevelTypeTransformer() => _instance ??= const ReactionLevelTypeTransformer._(); + + const ReactionLevelTypeTransformer._(); + + String encode(ReactionLevel data) => data.value; + + /// Decodes a [dynamic value][data] to a ReactionLevel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ReactionLevel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'album': return ReactionLevel.album; + case r'asset': return ReactionLevel.asset; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ReactionLevelTypeTransformer] instance. + static ReactionLevelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/reaction_type.dart b/mobile/openapi/lib/model/reaction_type.dart new file mode 100644 index 0000000000..4c788138fb --- /dev/null +++ b/mobile/openapi/lib/model/reaction_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class ReactionType { + /// Instantiate a new enum with the provided [value]. + const ReactionType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const comment = ReactionType._(r'comment'); + static const like = ReactionType._(r'like'); + + /// List of all possible values in this [enum][ReactionType]. + static const values = [ + comment, + like, + ]; + + static ReactionType? fromJson(dynamic value) => ReactionTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReactionType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ReactionType] to String, +/// and [decode] dynamic data back to [ReactionType]. +class ReactionTypeTypeTransformer { + factory ReactionTypeTypeTransformer() => _instance ??= const ReactionTypeTypeTransformer._(); + + const ReactionTypeTypeTransformer._(); + + String encode(ReactionType data) => data.value; + + /// Decodes a [dynamic value][data] to a ReactionType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ReactionType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'comment': return ReactionType.comment; + case r'like': return ReactionType.like; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ReactionTypeTypeTransformer] instance. + static ReactionTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart new file mode 100644 index 0000000000..6ad8c1a7b9 --- /dev/null +++ b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart @@ -0,0 +1,117 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ReverseGeocodingStateResponseDto { + /// Returns a new [ReverseGeocodingStateResponseDto] instance. + ReverseGeocodingStateResponseDto({ + required this.lastImportFileName, + required this.lastUpdate, + }); + + /// Last import file name + String? lastImportFileName; + + /// Last update timestamp + String? lastUpdate; + + @override + bool operator ==(Object other) => identical(this, other) || other is ReverseGeocodingStateResponseDto && + other.lastImportFileName == lastImportFileName && + other.lastUpdate == lastUpdate; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (lastImportFileName == null ? 0 : lastImportFileName!.hashCode) + + (lastUpdate == null ? 0 : lastUpdate!.hashCode); + + @override + String toString() => 'ReverseGeocodingStateResponseDto[lastImportFileName=$lastImportFileName, lastUpdate=$lastUpdate]'; + + Map toJson() { + final json = {}; + if (this.lastImportFileName != null) { + json[r'lastImportFileName'] = this.lastImportFileName; + } else { + // json[r'lastImportFileName'] = null; + } + if (this.lastUpdate != null) { + json[r'lastUpdate'] = this.lastUpdate; + } else { + // json[r'lastUpdate'] = null; + } + return json; + } + + /// Returns a new [ReverseGeocodingStateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ReverseGeocodingStateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ReverseGeocodingStateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ReverseGeocodingStateResponseDto( + lastImportFileName: mapValueOfType(json, r'lastImportFileName'), + lastUpdate: mapValueOfType(json, r'lastUpdate'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ReverseGeocodingStateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ReverseGeocodingStateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ReverseGeocodingStateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ReverseGeocodingStateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'lastImportFileName', + 'lastUpdate', + }; +} + diff --git a/mobile/openapi/lib/model/rotate_parameters.dart b/mobile/openapi/lib/model/rotate_parameters.dart new file mode 100644 index 0000000000..33609e83e5 --- /dev/null +++ b/mobile/openapi/lib/model/rotate_parameters.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class RotateParameters { + /// Returns a new [RotateParameters] instance. + RotateParameters({ + required this.angle, + }); + + /// Rotation angle in degrees + num angle; + + @override + bool operator ==(Object other) => identical(this, other) || other is RotateParameters && + other.angle == angle; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (angle.hashCode); + + @override + String toString() => 'RotateParameters[angle=$angle]'; + + Map toJson() { + final json = {}; + json[r'angle'] = this.angle; + return json; + } + + /// Returns a new [RotateParameters] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static RotateParameters? fromJson(dynamic value) { + upgradeDto(value, "RotateParameters"); + if (value is Map) { + final json = value.cast(); + + return RotateParameters( + angle: num.parse('${json[r'angle']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = RotateParameters.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = RotateParameters.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of RotateParameters-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = RotateParameters.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'angle', + }; +} + diff --git a/mobile/openapi/lib/model/search_album_response_dto.dart b/mobile/openapi/lib/model/search_album_response_dto.dart new file mode 100644 index 0000000000..8841251e4a --- /dev/null +++ b/mobile/openapi/lib/model/search_album_response_dto.dart @@ -0,0 +1,125 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchAlbumResponseDto { + /// Returns a new [SearchAlbumResponseDto] instance. + SearchAlbumResponseDto({ + required this.count, + this.facets = const [], + this.items = const [], + required this.total, + }); + + /// Number of albums in this page + int count; + + List facets; + + List items; + + /// Total number of matching albums + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchAlbumResponseDto && + other.count == count && + _deepEquality.equals(other.facets, facets) && + _deepEquality.equals(other.items, items) && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode) + + (facets.hashCode) + + (items.hashCode) + + (total.hashCode); + + @override + String toString() => 'SearchAlbumResponseDto[count=$count, facets=$facets, items=$items, total=$total]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + json[r'facets'] = this.facets; + json[r'items'] = this.items; + json[r'total'] = this.total; + return json; + } + + /// Returns a new [SearchAlbumResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchAlbumResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchAlbumResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchAlbumResponseDto( + count: mapValueOfType(json, r'count')!, + facets: SearchFacetResponseDto.listFromJson(json[r'facets']), + items: AlbumResponseDto.listFromJson(json[r'items']), + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchAlbumResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchAlbumResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchAlbumResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchAlbumResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + 'facets', + 'items', + 'total', + }; +} + diff --git a/mobile/openapi/lib/model/search_asset_response_dto.dart b/mobile/openapi/lib/model/search_asset_response_dto.dart new file mode 100644 index 0000000000..acb81f28e2 --- /dev/null +++ b/mobile/openapi/lib/model/search_asset_response_dto.dart @@ -0,0 +1,138 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchAssetResponseDto { + /// Returns a new [SearchAssetResponseDto] instance. + SearchAssetResponseDto({ + required this.count, + this.facets = const [], + this.items = const [], + required this.nextPage, + required this.total, + }); + + /// Number of assets in this page + int count; + + List facets; + + List items; + + /// Next page token + String? nextPage; + + /// Total number of matching assets + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchAssetResponseDto && + other.count == count && + _deepEquality.equals(other.facets, facets) && + _deepEquality.equals(other.items, items) && + other.nextPage == nextPage && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode) + + (facets.hashCode) + + (items.hashCode) + + (nextPage == null ? 0 : nextPage!.hashCode) + + (total.hashCode); + + @override + String toString() => 'SearchAssetResponseDto[count=$count, facets=$facets, items=$items, nextPage=$nextPage, total=$total]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + json[r'facets'] = this.facets; + json[r'items'] = this.items; + if (this.nextPage != null) { + json[r'nextPage'] = this.nextPage; + } else { + // json[r'nextPage'] = null; + } + json[r'total'] = this.total; + return json; + } + + /// Returns a new [SearchAssetResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchAssetResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchAssetResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchAssetResponseDto( + count: mapValueOfType(json, r'count')!, + facets: SearchFacetResponseDto.listFromJson(json[r'facets']), + items: AssetResponseDto.listFromJson(json[r'items']), + nextPage: mapValueOfType(json, r'nextPage'), + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchAssetResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchAssetResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchAssetResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchAssetResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + 'facets', + 'items', + 'nextPage', + 'total', + }; +} + diff --git a/mobile/openapi/lib/model/search_explore_item.dart b/mobile/openapi/lib/model/search_explore_item.dart new file mode 100644 index 0000000000..4089011879 --- /dev/null +++ b/mobile/openapi/lib/model/search_explore_item.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchExploreItem { + /// Returns a new [SearchExploreItem] instance. + SearchExploreItem({ + required this.data, + required this.value, + }); + + AssetResponseDto data; + + /// Explore value + String value; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchExploreItem && + other.data == data && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (data.hashCode) + + (value.hashCode); + + @override + String toString() => 'SearchExploreItem[data=$data, value=$value]'; + + Map toJson() { + final json = {}; + json[r'data'] = this.data; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [SearchExploreItem] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchExploreItem? fromJson(dynamic value) { + upgradeDto(value, "SearchExploreItem"); + if (value is Map) { + final json = value.cast(); + + return SearchExploreItem( + data: AssetResponseDto.fromJson(json[r'data'])!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchExploreItem.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchExploreItem.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchExploreItem-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchExploreItem.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'data', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/search_explore_response_dto.dart b/mobile/openapi/lib/model/search_explore_response_dto.dart new file mode 100644 index 0000000000..07ce26c9b8 --- /dev/null +++ b/mobile/openapi/lib/model/search_explore_response_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchExploreResponseDto { + /// Returns a new [SearchExploreResponseDto] instance. + SearchExploreResponseDto({ + required this.fieldName, + this.items = const [], + }); + + /// Explore field name + String fieldName; + + List items; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchExploreResponseDto && + other.fieldName == fieldName && + _deepEquality.equals(other.items, items); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (fieldName.hashCode) + + (items.hashCode); + + @override + String toString() => 'SearchExploreResponseDto[fieldName=$fieldName, items=$items]'; + + Map toJson() { + final json = {}; + json[r'fieldName'] = this.fieldName; + json[r'items'] = this.items; + return json; + } + + /// Returns a new [SearchExploreResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchExploreResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchExploreResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchExploreResponseDto( + fieldName: mapValueOfType(json, r'fieldName')!, + items: SearchExploreItem.listFromJson(json[r'items']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchExploreResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchExploreResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchExploreResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchExploreResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'fieldName', + 'items', + }; +} + diff --git a/mobile/openapi/lib/model/search_facet_count_response_dto.dart b/mobile/openapi/lib/model/search_facet_count_response_dto.dart new file mode 100644 index 0000000000..8318fbfb3b --- /dev/null +++ b/mobile/openapi/lib/model/search_facet_count_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchFacetCountResponseDto { + /// Returns a new [SearchFacetCountResponseDto] instance. + SearchFacetCountResponseDto({ + required this.count, + required this.value, + }); + + /// Number of assets with this facet value + int count; + + /// Facet value + String value; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchFacetCountResponseDto && + other.count == count && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode) + + (value.hashCode); + + @override + String toString() => 'SearchFacetCountResponseDto[count=$count, value=$value]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [SearchFacetCountResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchFacetCountResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchFacetCountResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchFacetCountResponseDto( + count: mapValueOfType(json, r'count')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchFacetCountResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchFacetCountResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchFacetCountResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchFacetCountResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/search_facet_response_dto.dart b/mobile/openapi/lib/model/search_facet_response_dto.dart new file mode 100644 index 0000000000..43b5ac5c81 --- /dev/null +++ b/mobile/openapi/lib/model/search_facet_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchFacetResponseDto { + /// Returns a new [SearchFacetResponseDto] instance. + SearchFacetResponseDto({ + this.counts = const [], + required this.fieldName, + }); + + /// Facet counts + List counts; + + /// Facet field name + String fieldName; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchFacetResponseDto && + _deepEquality.equals(other.counts, counts) && + other.fieldName == fieldName; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (counts.hashCode) + + (fieldName.hashCode); + + @override + String toString() => 'SearchFacetResponseDto[counts=$counts, fieldName=$fieldName]'; + + Map toJson() { + final json = {}; + json[r'counts'] = this.counts; + json[r'fieldName'] = this.fieldName; + return json; + } + + /// Returns a new [SearchFacetResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchFacetResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchFacetResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchFacetResponseDto( + counts: SearchFacetCountResponseDto.listFromJson(json[r'counts']), + fieldName: mapValueOfType(json, r'fieldName')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchFacetResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchFacetResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchFacetResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchFacetResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'counts', + 'fieldName', + }; +} + diff --git a/mobile/openapi/lib/model/search_response_dto.dart b/mobile/openapi/lib/model/search_response_dto.dart new file mode 100644 index 0000000000..ca742ae35c --- /dev/null +++ b/mobile/openapi/lib/model/search_response_dto.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchResponseDto { + /// Returns a new [SearchResponseDto] instance. + SearchResponseDto({ + required this.albums, + required this.assets, + }); + + SearchAlbumResponseDto albums; + + SearchAssetResponseDto assets; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchResponseDto && + other.albums == albums && + other.assets == assets; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albums.hashCode) + + (assets.hashCode); + + @override + String toString() => 'SearchResponseDto[albums=$albums, assets=$assets]'; + + Map toJson() { + final json = {}; + json[r'albums'] = this.albums; + json[r'assets'] = this.assets; + return json; + } + + /// Returns a new [SearchResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchResponseDto( + albums: SearchAlbumResponseDto.fromJson(json[r'albums'])!, + assets: SearchAssetResponseDto.fromJson(json[r'assets'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albums', + 'assets', + }; +} + diff --git a/mobile/openapi/lib/model/search_statistics_response_dto.dart b/mobile/openapi/lib/model/search_statistics_response_dto.dart new file mode 100644 index 0000000000..5aebe4d6a9 --- /dev/null +++ b/mobile/openapi/lib/model/search_statistics_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SearchStatisticsResponseDto { + /// Returns a new [SearchStatisticsResponseDto] instance. + SearchStatisticsResponseDto({ + required this.total, + }); + + /// Total number of matching assets + int total; + + @override + bool operator ==(Object other) => identical(this, other) || other is SearchStatisticsResponseDto && + other.total == total; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (total.hashCode); + + @override + String toString() => 'SearchStatisticsResponseDto[total=$total]'; + + Map toJson() { + final json = {}; + json[r'total'] = this.total; + return json; + } + + /// Returns a new [SearchStatisticsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SearchStatisticsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SearchStatisticsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SearchStatisticsResponseDto( + total: mapValueOfType(json, r'total')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchStatisticsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SearchStatisticsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SearchStatisticsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SearchStatisticsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'total', + }; +} + diff --git a/mobile/openapi/lib/model/search_suggestion_type.dart b/mobile/openapi/lib/model/search_suggestion_type.dart new file mode 100644 index 0000000000..b18fe687c4 --- /dev/null +++ b/mobile/openapi/lib/model/search_suggestion_type.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + + +class SearchSuggestionType { + /// Instantiate a new enum with the provided [value]. + const SearchSuggestionType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const country = SearchSuggestionType._(r'country'); + static const state = SearchSuggestionType._(r'state'); + static const city = SearchSuggestionType._(r'city'); + static const cameraMake = SearchSuggestionType._(r'camera-make'); + static const cameraModel = SearchSuggestionType._(r'camera-model'); + static const cameraLensModel = SearchSuggestionType._(r'camera-lens-model'); + + /// List of all possible values in this [enum][SearchSuggestionType]. + static const values = [ + country, + state, + city, + cameraMake, + cameraModel, + cameraLensModel, + ]; + + static SearchSuggestionType? fromJson(dynamic value) => SearchSuggestionTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SearchSuggestionType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SearchSuggestionType] to String, +/// and [decode] dynamic data back to [SearchSuggestionType]. +class SearchSuggestionTypeTypeTransformer { + factory SearchSuggestionTypeTypeTransformer() => _instance ??= const SearchSuggestionTypeTypeTransformer._(); + + const SearchSuggestionTypeTypeTransformer._(); + + String encode(SearchSuggestionType data) => data.value; + + /// Decodes a [dynamic value][data] to a SearchSuggestionType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + SearchSuggestionType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'country': return SearchSuggestionType.country; + case r'state': return SearchSuggestionType.state; + case r'city': return SearchSuggestionType.city; + case r'camera-make': return SearchSuggestionType.cameraMake; + case r'camera-model': return SearchSuggestionType.cameraModel; + case r'camera-lens-model': return SearchSuggestionType.cameraLensModel; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SearchSuggestionTypeTypeTransformer] instance. + static SearchSuggestionTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/server_about_response_dto.dart b/mobile/openapi/lib/model/server_about_response_dto.dart new file mode 100644 index 0000000000..1ae53763fe --- /dev/null +++ b/mobile/openapi/lib/model/server_about_response_dto.dart @@ -0,0 +1,442 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerAboutResponseDto { + /// Returns a new [ServerAboutResponseDto] instance. + ServerAboutResponseDto({ + this.build, + this.buildImage, + this.buildImageUrl, + this.buildUrl, + this.exiftool, + this.ffmpeg, + this.imagemagick, + this.libvips, + required this.licensed, + this.nodejs, + this.repository, + this.repositoryUrl, + this.sourceCommit, + this.sourceRef, + this.sourceUrl, + this.thirdPartyBugFeatureUrl, + this.thirdPartyDocumentationUrl, + this.thirdPartySourceUrl, + this.thirdPartySupportUrl, + required this.version, + required this.versionUrl, + }); + + /// Build identifier + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? build; + + /// Build image name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? buildImage; + + /// Build image URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? buildImageUrl; + + /// Build URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? buildUrl; + + /// ExifTool version + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? exiftool; + + /// FFmpeg version + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ffmpeg; + + /// ImageMagick version + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? imagemagick; + + /// libvips version + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? libvips; + + /// Whether the server is licensed + bool licensed; + + /// Node.js version + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? nodejs; + + /// Repository name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? repository; + + /// Repository URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? repositoryUrl; + + /// Source commit hash + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? sourceCommit; + + /// Source reference (branch/tag) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? sourceRef; + + /// Source URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? sourceUrl; + + /// Third-party bug/feature URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartyBugFeatureUrl; + + /// Third-party documentation URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartyDocumentationUrl; + + /// Third-party source URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartySourceUrl; + + /// Third-party support URL + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartySupportUrl; + + /// Server version + String version; + + /// URL to version information + String versionUrl; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerAboutResponseDto && + other.build == build && + other.buildImage == buildImage && + other.buildImageUrl == buildImageUrl && + other.buildUrl == buildUrl && + other.exiftool == exiftool && + other.ffmpeg == ffmpeg && + other.imagemagick == imagemagick && + other.libvips == libvips && + other.licensed == licensed && + other.nodejs == nodejs && + other.repository == repository && + other.repositoryUrl == repositoryUrl && + other.sourceCommit == sourceCommit && + other.sourceRef == sourceRef && + other.sourceUrl == sourceUrl && + other.thirdPartyBugFeatureUrl == thirdPartyBugFeatureUrl && + other.thirdPartyDocumentationUrl == thirdPartyDocumentationUrl && + other.thirdPartySourceUrl == thirdPartySourceUrl && + other.thirdPartySupportUrl == thirdPartySupportUrl && + other.version == version && + other.versionUrl == versionUrl; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (build == null ? 0 : build!.hashCode) + + (buildImage == null ? 0 : buildImage!.hashCode) + + (buildImageUrl == null ? 0 : buildImageUrl!.hashCode) + + (buildUrl == null ? 0 : buildUrl!.hashCode) + + (exiftool == null ? 0 : exiftool!.hashCode) + + (ffmpeg == null ? 0 : ffmpeg!.hashCode) + + (imagemagick == null ? 0 : imagemagick!.hashCode) + + (libvips == null ? 0 : libvips!.hashCode) + + (licensed.hashCode) + + (nodejs == null ? 0 : nodejs!.hashCode) + + (repository == null ? 0 : repository!.hashCode) + + (repositoryUrl == null ? 0 : repositoryUrl!.hashCode) + + (sourceCommit == null ? 0 : sourceCommit!.hashCode) + + (sourceRef == null ? 0 : sourceRef!.hashCode) + + (sourceUrl == null ? 0 : sourceUrl!.hashCode) + + (thirdPartyBugFeatureUrl == null ? 0 : thirdPartyBugFeatureUrl!.hashCode) + + (thirdPartyDocumentationUrl == null ? 0 : thirdPartyDocumentationUrl!.hashCode) + + (thirdPartySourceUrl == null ? 0 : thirdPartySourceUrl!.hashCode) + + (thirdPartySupportUrl == null ? 0 : thirdPartySupportUrl!.hashCode) + + (version.hashCode) + + (versionUrl.hashCode); + + @override + String toString() => 'ServerAboutResponseDto[build=$build, buildImage=$buildImage, buildImageUrl=$buildImageUrl, buildUrl=$buildUrl, exiftool=$exiftool, ffmpeg=$ffmpeg, imagemagick=$imagemagick, libvips=$libvips, licensed=$licensed, nodejs=$nodejs, repository=$repository, repositoryUrl=$repositoryUrl, sourceCommit=$sourceCommit, sourceRef=$sourceRef, sourceUrl=$sourceUrl, thirdPartyBugFeatureUrl=$thirdPartyBugFeatureUrl, thirdPartyDocumentationUrl=$thirdPartyDocumentationUrl, thirdPartySourceUrl=$thirdPartySourceUrl, thirdPartySupportUrl=$thirdPartySupportUrl, version=$version, versionUrl=$versionUrl]'; + + Map toJson() { + final json = {}; + if (this.build != null) { + json[r'build'] = this.build; + } else { + // json[r'build'] = null; + } + if (this.buildImage != null) { + json[r'buildImage'] = this.buildImage; + } else { + // json[r'buildImage'] = null; + } + if (this.buildImageUrl != null) { + json[r'buildImageUrl'] = this.buildImageUrl; + } else { + // json[r'buildImageUrl'] = null; + } + if (this.buildUrl != null) { + json[r'buildUrl'] = this.buildUrl; + } else { + // json[r'buildUrl'] = null; + } + if (this.exiftool != null) { + json[r'exiftool'] = this.exiftool; + } else { + // json[r'exiftool'] = null; + } + if (this.ffmpeg != null) { + json[r'ffmpeg'] = this.ffmpeg; + } else { + // json[r'ffmpeg'] = null; + } + if (this.imagemagick != null) { + json[r'imagemagick'] = this.imagemagick; + } else { + // json[r'imagemagick'] = null; + } + if (this.libvips != null) { + json[r'libvips'] = this.libvips; + } else { + // json[r'libvips'] = null; + } + json[r'licensed'] = this.licensed; + if (this.nodejs != null) { + json[r'nodejs'] = this.nodejs; + } else { + // json[r'nodejs'] = null; + } + if (this.repository != null) { + json[r'repository'] = this.repository; + } else { + // json[r'repository'] = null; + } + if (this.repositoryUrl != null) { + json[r'repositoryUrl'] = this.repositoryUrl; + } else { + // json[r'repositoryUrl'] = null; + } + if (this.sourceCommit != null) { + json[r'sourceCommit'] = this.sourceCommit; + } else { + // json[r'sourceCommit'] = null; + } + if (this.sourceRef != null) { + json[r'sourceRef'] = this.sourceRef; + } else { + // json[r'sourceRef'] = null; + } + if (this.sourceUrl != null) { + json[r'sourceUrl'] = this.sourceUrl; + } else { + // json[r'sourceUrl'] = null; + } + if (this.thirdPartyBugFeatureUrl != null) { + json[r'thirdPartyBugFeatureUrl'] = this.thirdPartyBugFeatureUrl; + } else { + // json[r'thirdPartyBugFeatureUrl'] = null; + } + if (this.thirdPartyDocumentationUrl != null) { + json[r'thirdPartyDocumentationUrl'] = this.thirdPartyDocumentationUrl; + } else { + // json[r'thirdPartyDocumentationUrl'] = null; + } + if (this.thirdPartySourceUrl != null) { + json[r'thirdPartySourceUrl'] = this.thirdPartySourceUrl; + } else { + // json[r'thirdPartySourceUrl'] = null; + } + if (this.thirdPartySupportUrl != null) { + json[r'thirdPartySupportUrl'] = this.thirdPartySupportUrl; + } else { + // json[r'thirdPartySupportUrl'] = null; + } + json[r'version'] = this.version; + json[r'versionUrl'] = this.versionUrl; + return json; + } + + /// Returns a new [ServerAboutResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerAboutResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerAboutResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerAboutResponseDto( + build: mapValueOfType(json, r'build'), + buildImage: mapValueOfType(json, r'buildImage'), + buildImageUrl: mapValueOfType(json, r'buildImageUrl'), + buildUrl: mapValueOfType(json, r'buildUrl'), + exiftool: mapValueOfType(json, r'exiftool'), + ffmpeg: mapValueOfType(json, r'ffmpeg'), + imagemagick: mapValueOfType(json, r'imagemagick'), + libvips: mapValueOfType(json, r'libvips'), + licensed: mapValueOfType(json, r'licensed')!, + nodejs: mapValueOfType(json, r'nodejs'), + repository: mapValueOfType(json, r'repository'), + repositoryUrl: mapValueOfType(json, r'repositoryUrl'), + sourceCommit: mapValueOfType(json, r'sourceCommit'), + sourceRef: mapValueOfType(json, r'sourceRef'), + sourceUrl: mapValueOfType(json, r'sourceUrl'), + thirdPartyBugFeatureUrl: mapValueOfType(json, r'thirdPartyBugFeatureUrl'), + thirdPartyDocumentationUrl: mapValueOfType(json, r'thirdPartyDocumentationUrl'), + thirdPartySourceUrl: mapValueOfType(json, r'thirdPartySourceUrl'), + thirdPartySupportUrl: mapValueOfType(json, r'thirdPartySupportUrl'), + version: mapValueOfType(json, r'version')!, + versionUrl: mapValueOfType(json, r'versionUrl')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerAboutResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerAboutResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerAboutResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerAboutResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'licensed', + 'version', + 'versionUrl', + }; +} + diff --git a/mobile/openapi/lib/model/server_apk_links_dto.dart b/mobile/openapi/lib/model/server_apk_links_dto.dart new file mode 100644 index 0000000000..2227018468 --- /dev/null +++ b/mobile/openapi/lib/model/server_apk_links_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerApkLinksDto { + /// Returns a new [ServerApkLinksDto] instance. + ServerApkLinksDto({ + required this.arm64v8a, + required this.armeabiv7a, + required this.universal, + required this.x8664, + }); + + /// APK download link for ARM64 v8a architecture + String arm64v8a; + + /// APK download link for ARM EABI v7a architecture + String armeabiv7a; + + /// APK download link for universal architecture + String universal; + + /// APK download link for x86_64 architecture + String x8664; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerApkLinksDto && + other.arm64v8a == arm64v8a && + other.armeabiv7a == armeabiv7a && + other.universal == universal && + other.x8664 == x8664; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (arm64v8a.hashCode) + + (armeabiv7a.hashCode) + + (universal.hashCode) + + (x8664.hashCode); + + @override + String toString() => 'ServerApkLinksDto[arm64v8a=$arm64v8a, armeabiv7a=$armeabiv7a, universal=$universal, x8664=$x8664]'; + + Map toJson() { + final json = {}; + json[r'arm64v8a'] = this.arm64v8a; + json[r'armeabiv7a'] = this.armeabiv7a; + json[r'universal'] = this.universal; + json[r'x86_64'] = this.x8664; + return json; + } + + /// Returns a new [ServerApkLinksDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerApkLinksDto? fromJson(dynamic value) { + upgradeDto(value, "ServerApkLinksDto"); + if (value is Map) { + final json = value.cast(); + + return ServerApkLinksDto( + arm64v8a: mapValueOfType(json, r'arm64v8a')!, + armeabiv7a: mapValueOfType(json, r'armeabiv7a')!, + universal: mapValueOfType(json, r'universal')!, + x8664: mapValueOfType(json, r'x86_64')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerApkLinksDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerApkLinksDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerApkLinksDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerApkLinksDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'arm64v8a', + 'armeabiv7a', + 'universal', + 'x86_64', + }; +} + diff --git a/mobile/openapi/lib/model/server_config_dto.dart b/mobile/openapi/lib/model/server_config_dto.dart new file mode 100644 index 0000000000..fec096d51a --- /dev/null +++ b/mobile/openapi/lib/model/server_config_dto.dart @@ -0,0 +1,190 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerConfigDto { + /// Returns a new [ServerConfigDto] instance. + ServerConfigDto({ + required this.externalDomain, + required this.isInitialized, + required this.isOnboarded, + required this.loginPageMessage, + required this.maintenanceMode, + required this.mapDarkStyleUrl, + required this.mapLightStyleUrl, + required this.oauthButtonText, + required this.publicUsers, + required this.trashDays, + required this.userDeleteDelay, + }); + + /// External domain URL + String externalDomain; + + /// Whether the server has been initialized + bool isInitialized; + + /// Whether the admin has completed onboarding + bool isOnboarded; + + /// Login page message + String loginPageMessage; + + /// Whether maintenance mode is active + bool maintenanceMode; + + /// Map dark style URL + String mapDarkStyleUrl; + + /// Map light style URL + String mapLightStyleUrl; + + /// OAuth button text + String oauthButtonText; + + /// Whether public user registration is enabled + bool publicUsers; + + /// Number of days before trashed assets are permanently deleted + int trashDays; + + /// Delay in days before deleted users are permanently removed + int userDeleteDelay; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerConfigDto && + other.externalDomain == externalDomain && + other.isInitialized == isInitialized && + other.isOnboarded == isOnboarded && + other.loginPageMessage == loginPageMessage && + other.maintenanceMode == maintenanceMode && + other.mapDarkStyleUrl == mapDarkStyleUrl && + other.mapLightStyleUrl == mapLightStyleUrl && + other.oauthButtonText == oauthButtonText && + other.publicUsers == publicUsers && + other.trashDays == trashDays && + other.userDeleteDelay == userDeleteDelay; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (externalDomain.hashCode) + + (isInitialized.hashCode) + + (isOnboarded.hashCode) + + (loginPageMessage.hashCode) + + (maintenanceMode.hashCode) + + (mapDarkStyleUrl.hashCode) + + (mapLightStyleUrl.hashCode) + + (oauthButtonText.hashCode) + + (publicUsers.hashCode) + + (trashDays.hashCode) + + (userDeleteDelay.hashCode); + + @override + String toString() => 'ServerConfigDto[externalDomain=$externalDomain, isInitialized=$isInitialized, isOnboarded=$isOnboarded, loginPageMessage=$loginPageMessage, maintenanceMode=$maintenanceMode, mapDarkStyleUrl=$mapDarkStyleUrl, mapLightStyleUrl=$mapLightStyleUrl, oauthButtonText=$oauthButtonText, publicUsers=$publicUsers, trashDays=$trashDays, userDeleteDelay=$userDeleteDelay]'; + + Map toJson() { + final json = {}; + json[r'externalDomain'] = this.externalDomain; + json[r'isInitialized'] = this.isInitialized; + json[r'isOnboarded'] = this.isOnboarded; + json[r'loginPageMessage'] = this.loginPageMessage; + json[r'maintenanceMode'] = this.maintenanceMode; + json[r'mapDarkStyleUrl'] = this.mapDarkStyleUrl; + json[r'mapLightStyleUrl'] = this.mapLightStyleUrl; + json[r'oauthButtonText'] = this.oauthButtonText; + json[r'publicUsers'] = this.publicUsers; + json[r'trashDays'] = this.trashDays; + json[r'userDeleteDelay'] = this.userDeleteDelay; + return json; + } + + /// Returns a new [ServerConfigDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerConfigDto? fromJson(dynamic value) { + upgradeDto(value, "ServerConfigDto"); + if (value is Map) { + final json = value.cast(); + + return ServerConfigDto( + externalDomain: mapValueOfType(json, r'externalDomain')!, + isInitialized: mapValueOfType(json, r'isInitialized')!, + isOnboarded: mapValueOfType(json, r'isOnboarded')!, + loginPageMessage: mapValueOfType(json, r'loginPageMessage')!, + maintenanceMode: mapValueOfType(json, r'maintenanceMode')!, + mapDarkStyleUrl: mapValueOfType(json, r'mapDarkStyleUrl')!, + mapLightStyleUrl: mapValueOfType(json, r'mapLightStyleUrl')!, + oauthButtonText: mapValueOfType(json, r'oauthButtonText')!, + publicUsers: mapValueOfType(json, r'publicUsers')!, + trashDays: mapValueOfType(json, r'trashDays')!, + userDeleteDelay: mapValueOfType(json, r'userDeleteDelay')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerConfigDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerConfigDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerConfigDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerConfigDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'externalDomain', + 'isInitialized', + 'isOnboarded', + 'loginPageMessage', + 'maintenanceMode', + 'mapDarkStyleUrl', + 'mapLightStyleUrl', + 'oauthButtonText', + 'publicUsers', + 'trashDays', + 'userDeleteDelay', + }; +} + diff --git a/mobile/openapi/lib/model/server_features_dto.dart b/mobile/openapi/lib/model/server_features_dto.dart new file mode 100644 index 0000000000..79494b74eb --- /dev/null +++ b/mobile/openapi/lib/model/server_features_dto.dart @@ -0,0 +1,226 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerFeaturesDto { + /// Returns a new [ServerFeaturesDto] instance. + ServerFeaturesDto({ + required this.configFile, + required this.duplicateDetection, + required this.email, + required this.facialRecognition, + required this.importFaces, + required this.map, + required this.oauth, + required this.oauthAutoLaunch, + required this.ocr, + required this.passwordLogin, + required this.reverseGeocoding, + required this.search, + required this.sidecar, + required this.smartSearch, + required this.trash, + }); + + /// Whether config file is available + bool configFile; + + /// Whether duplicate detection is enabled + bool duplicateDetection; + + /// Whether email notifications are enabled + bool email; + + /// Whether facial recognition is enabled + bool facialRecognition; + + /// Whether face import is enabled + bool importFaces; + + /// Whether map feature is enabled + bool map; + + /// Whether OAuth is enabled + bool oauth; + + /// Whether OAuth auto-launch is enabled + bool oauthAutoLaunch; + + /// Whether OCR is enabled + bool ocr; + + /// Whether password login is enabled + bool passwordLogin; + + /// Whether reverse geocoding is enabled + bool reverseGeocoding; + + /// Whether search is enabled + bool search; + + /// Whether sidecar files are supported + bool sidecar; + + /// Whether smart search is enabled + bool smartSearch; + + /// Whether trash feature is enabled + bool trash; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerFeaturesDto && + other.configFile == configFile && + other.duplicateDetection == duplicateDetection && + other.email == email && + other.facialRecognition == facialRecognition && + other.importFaces == importFaces && + other.map == map && + other.oauth == oauth && + other.oauthAutoLaunch == oauthAutoLaunch && + other.ocr == ocr && + other.passwordLogin == passwordLogin && + other.reverseGeocoding == reverseGeocoding && + other.search == search && + other.sidecar == sidecar && + other.smartSearch == smartSearch && + other.trash == trash; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (configFile.hashCode) + + (duplicateDetection.hashCode) + + (email.hashCode) + + (facialRecognition.hashCode) + + (importFaces.hashCode) + + (map.hashCode) + + (oauth.hashCode) + + (oauthAutoLaunch.hashCode) + + (ocr.hashCode) + + (passwordLogin.hashCode) + + (reverseGeocoding.hashCode) + + (search.hashCode) + + (sidecar.hashCode) + + (smartSearch.hashCode) + + (trash.hashCode); + + @override + String toString() => 'ServerFeaturesDto[configFile=$configFile, duplicateDetection=$duplicateDetection, email=$email, facialRecognition=$facialRecognition, importFaces=$importFaces, map=$map, oauth=$oauth, oauthAutoLaunch=$oauthAutoLaunch, ocr=$ocr, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, trash=$trash]'; + + Map toJson() { + final json = {}; + json[r'configFile'] = this.configFile; + json[r'duplicateDetection'] = this.duplicateDetection; + json[r'email'] = this.email; + json[r'facialRecognition'] = this.facialRecognition; + json[r'importFaces'] = this.importFaces; + json[r'map'] = this.map; + json[r'oauth'] = this.oauth; + json[r'oauthAutoLaunch'] = this.oauthAutoLaunch; + json[r'ocr'] = this.ocr; + json[r'passwordLogin'] = this.passwordLogin; + json[r'reverseGeocoding'] = this.reverseGeocoding; + json[r'search'] = this.search; + json[r'sidecar'] = this.sidecar; + json[r'smartSearch'] = this.smartSearch; + json[r'trash'] = this.trash; + return json; + } + + /// Returns a new [ServerFeaturesDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerFeaturesDto? fromJson(dynamic value) { + upgradeDto(value, "ServerFeaturesDto"); + if (value is Map) { + final json = value.cast(); + + return ServerFeaturesDto( + configFile: mapValueOfType(json, r'configFile')!, + duplicateDetection: mapValueOfType(json, r'duplicateDetection')!, + email: mapValueOfType(json, r'email')!, + facialRecognition: mapValueOfType(json, r'facialRecognition')!, + importFaces: mapValueOfType(json, r'importFaces')!, + map: mapValueOfType(json, r'map')!, + oauth: mapValueOfType(json, r'oauth')!, + oauthAutoLaunch: mapValueOfType(json, r'oauthAutoLaunch')!, + ocr: mapValueOfType(json, r'ocr')!, + passwordLogin: mapValueOfType(json, r'passwordLogin')!, + reverseGeocoding: mapValueOfType(json, r'reverseGeocoding')!, + search: mapValueOfType(json, r'search')!, + sidecar: mapValueOfType(json, r'sidecar')!, + smartSearch: mapValueOfType(json, r'smartSearch')!, + trash: mapValueOfType(json, r'trash')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerFeaturesDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerFeaturesDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerFeaturesDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerFeaturesDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'configFile', + 'duplicateDetection', + 'email', + 'facialRecognition', + 'importFaces', + 'map', + 'oauth', + 'oauthAutoLaunch', + 'ocr', + 'passwordLogin', + 'reverseGeocoding', + 'search', + 'sidecar', + 'smartSearch', + 'trash', + }; +} + diff --git a/mobile/openapi/lib/model/server_media_types_response_dto.dart b/mobile/openapi/lib/model/server_media_types_response_dto.dart new file mode 100644 index 0000000000..6a2aaeb9e1 --- /dev/null +++ b/mobile/openapi/lib/model/server_media_types_response_dto.dart @@ -0,0 +1,124 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerMediaTypesResponseDto { + /// Returns a new [ServerMediaTypesResponseDto] instance. + ServerMediaTypesResponseDto({ + this.image = const [], + this.sidecar = const [], + this.video = const [], + }); + + /// Supported image MIME types + List image; + + /// Supported sidecar MIME types + List sidecar; + + /// Supported video MIME types + List video; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerMediaTypesResponseDto && + _deepEquality.equals(other.image, image) && + _deepEquality.equals(other.sidecar, sidecar) && + _deepEquality.equals(other.video, video); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (image.hashCode) + + (sidecar.hashCode) + + (video.hashCode); + + @override + String toString() => 'ServerMediaTypesResponseDto[image=$image, sidecar=$sidecar, video=$video]'; + + Map toJson() { + final json = {}; + json[r'image'] = this.image; + json[r'sidecar'] = this.sidecar; + json[r'video'] = this.video; + return json; + } + + /// Returns a new [ServerMediaTypesResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerMediaTypesResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerMediaTypesResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerMediaTypesResponseDto( + image: json[r'image'] is Iterable + ? (json[r'image'] as Iterable).cast().toList(growable: false) + : const [], + sidecar: json[r'sidecar'] is Iterable + ? (json[r'sidecar'] as Iterable).cast().toList(growable: false) + : const [], + video: json[r'video'] is Iterable + ? (json[r'video'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerMediaTypesResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerMediaTypesResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerMediaTypesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerMediaTypesResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'image', + 'sidecar', + 'video', + }; +} + diff --git a/mobile/openapi/lib/model/server_ping_response.dart b/mobile/openapi/lib/model/server_ping_response.dart new file mode 100644 index 0000000000..621ebfa294 --- /dev/null +++ b/mobile/openapi/lib/model/server_ping_response.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerPingResponse { + /// Returns a new [ServerPingResponse] instance. + ServerPingResponse({ + required this.res, + }); + + String res; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerPingResponse && + other.res == res; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (res.hashCode); + + @override + String toString() => 'ServerPingResponse[res=$res]'; + + Map toJson() { + final json = {}; + json[r'res'] = this.res; + return json; + } + + /// Returns a new [ServerPingResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerPingResponse? fromJson(dynamic value) { + upgradeDto(value, "ServerPingResponse"); + if (value is Map) { + final json = value.cast(); + + return ServerPingResponse( + res: mapValueOfType(json, r'res')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerPingResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerPingResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerPingResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerPingResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'res', + }; +} + diff --git a/mobile/openapi/lib/model/server_stats_response_dto.dart b/mobile/openapi/lib/model/server_stats_response_dto.dart new file mode 100644 index 0000000000..ef2fa458e2 --- /dev/null +++ b/mobile/openapi/lib/model/server_stats_response_dto.dart @@ -0,0 +1,144 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerStatsResponseDto { + /// Returns a new [ServerStatsResponseDto] instance. + ServerStatsResponseDto({ + this.photos = 0, + this.usage = 0, + this.usageByUser = const [], + this.usagePhotos = 0, + this.usageVideos = 0, + this.videos = 0, + }); + + /// Total number of photos + int photos; + + /// Total storage usage in bytes + int usage; + + List usageByUser; + + /// Storage usage for photos in bytes + int usagePhotos; + + /// Storage usage for videos in bytes + int usageVideos; + + /// Total number of videos + int videos; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerStatsResponseDto && + other.photos == photos && + other.usage == usage && + _deepEquality.equals(other.usageByUser, usageByUser) && + other.usagePhotos == usagePhotos && + other.usageVideos == usageVideos && + other.videos == videos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (photos.hashCode) + + (usage.hashCode) + + (usageByUser.hashCode) + + (usagePhotos.hashCode) + + (usageVideos.hashCode) + + (videos.hashCode); + + @override + String toString() => 'ServerStatsResponseDto[photos=$photos, usage=$usage, usageByUser=$usageByUser, usagePhotos=$usagePhotos, usageVideos=$usageVideos, videos=$videos]'; + + Map toJson() { + final json = {}; + json[r'photos'] = this.photos; + json[r'usage'] = this.usage; + json[r'usageByUser'] = this.usageByUser; + json[r'usagePhotos'] = this.usagePhotos; + json[r'usageVideos'] = this.usageVideos; + json[r'videos'] = this.videos; + return json; + } + + /// Returns a new [ServerStatsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerStatsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerStatsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerStatsResponseDto( + photos: mapValueOfType(json, r'photos')!, + usage: mapValueOfType(json, r'usage')!, + usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser']), + usagePhotos: mapValueOfType(json, r'usagePhotos')!, + usageVideos: mapValueOfType(json, r'usageVideos')!, + videos: mapValueOfType(json, r'videos')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerStatsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerStatsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerStatsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerStatsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'photos', + 'usage', + 'usageByUser', + 'usagePhotos', + 'usageVideos', + 'videos', + }; +} + diff --git a/mobile/openapi/lib/model/server_storage_response_dto.dart b/mobile/openapi/lib/model/server_storage_response_dto.dart new file mode 100644 index 0000000000..476b048b4d --- /dev/null +++ b/mobile/openapi/lib/model/server_storage_response_dto.dart @@ -0,0 +1,154 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerStorageResponseDto { + /// Returns a new [ServerStorageResponseDto] instance. + ServerStorageResponseDto({ + required this.diskAvailable, + required this.diskAvailableRaw, + required this.diskSize, + required this.diskSizeRaw, + required this.diskUsagePercentage, + required this.diskUse, + required this.diskUseRaw, + }); + + /// Available disk space (human-readable format) + String diskAvailable; + + /// Available disk space in bytes + int diskAvailableRaw; + + /// Total disk size (human-readable format) + String diskSize; + + /// Total disk size in bytes + int diskSizeRaw; + + /// Disk usage percentage (0-100) + double diskUsagePercentage; + + /// Used disk space (human-readable format) + String diskUse; + + /// Used disk space in bytes + int diskUseRaw; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerStorageResponseDto && + other.diskAvailable == diskAvailable && + other.diskAvailableRaw == diskAvailableRaw && + other.diskSize == diskSize && + other.diskSizeRaw == diskSizeRaw && + other.diskUsagePercentage == diskUsagePercentage && + other.diskUse == diskUse && + other.diskUseRaw == diskUseRaw; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (diskAvailable.hashCode) + + (diskAvailableRaw.hashCode) + + (diskSize.hashCode) + + (diskSizeRaw.hashCode) + + (diskUsagePercentage.hashCode) + + (diskUse.hashCode) + + (diskUseRaw.hashCode); + + @override + String toString() => 'ServerStorageResponseDto[diskAvailable=$diskAvailable, diskAvailableRaw=$diskAvailableRaw, diskSize=$diskSize, diskSizeRaw=$diskSizeRaw, diskUsagePercentage=$diskUsagePercentage, diskUse=$diskUse, diskUseRaw=$diskUseRaw]'; + + Map toJson() { + final json = {}; + json[r'diskAvailable'] = this.diskAvailable; + json[r'diskAvailableRaw'] = this.diskAvailableRaw; + json[r'diskSize'] = this.diskSize; + json[r'diskSizeRaw'] = this.diskSizeRaw; + json[r'diskUsagePercentage'] = this.diskUsagePercentage; + json[r'diskUse'] = this.diskUse; + json[r'diskUseRaw'] = this.diskUseRaw; + return json; + } + + /// Returns a new [ServerStorageResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerStorageResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerStorageResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerStorageResponseDto( + diskAvailable: mapValueOfType(json, r'diskAvailable')!, + diskAvailableRaw: mapValueOfType(json, r'diskAvailableRaw')!, + diskSize: mapValueOfType(json, r'diskSize')!, + diskSizeRaw: mapValueOfType(json, r'diskSizeRaw')!, + diskUsagePercentage: (mapValueOfType(json, r'diskUsagePercentage')!).toDouble(), + diskUse: mapValueOfType(json, r'diskUse')!, + diskUseRaw: mapValueOfType(json, r'diskUseRaw')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerStorageResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerStorageResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerStorageResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerStorageResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'diskAvailable', + 'diskAvailableRaw', + 'diskSize', + 'diskSizeRaw', + 'diskUsagePercentage', + 'diskUse', + 'diskUseRaw', + }; +} + diff --git a/mobile/openapi/lib/model/server_theme_dto.dart b/mobile/openapi/lib/model/server_theme_dto.dart new file mode 100644 index 0000000000..957cf84d55 --- /dev/null +++ b/mobile/openapi/lib/model/server_theme_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerThemeDto { + /// Returns a new [ServerThemeDto] instance. + ServerThemeDto({ + required this.customCss, + }); + + /// Custom CSS for theming + String customCss; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerThemeDto && + other.customCss == customCss; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (customCss.hashCode); + + @override + String toString() => 'ServerThemeDto[customCss=$customCss]'; + + Map toJson() { + final json = {}; + json[r'customCss'] = this.customCss; + return json; + } + + /// Returns a new [ServerThemeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerThemeDto? fromJson(dynamic value) { + upgradeDto(value, "ServerThemeDto"); + if (value is Map) { + final json = value.cast(); + + return ServerThemeDto( + customCss: mapValueOfType(json, r'customCss')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerThemeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerThemeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerThemeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerThemeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'customCss', + }; +} + diff --git a/mobile/openapi/lib/model/server_version_history_response_dto.dart b/mobile/openapi/lib/model/server_version_history_response_dto.dart new file mode 100644 index 0000000000..c3b7049016 --- /dev/null +++ b/mobile/openapi/lib/model/server_version_history_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerVersionHistoryResponseDto { + /// Returns a new [ServerVersionHistoryResponseDto] instance. + ServerVersionHistoryResponseDto({ + required this.createdAt, + required this.id, + required this.version, + }); + + /// When this version was first seen + DateTime createdAt; + + /// Version history entry ID + String id; + + /// Version string + String version; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerVersionHistoryResponseDto && + other.createdAt == createdAt && + other.id == id && + other.version == version; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (id.hashCode) + + (version.hashCode); + + @override + String toString() => 'ServerVersionHistoryResponseDto[createdAt=$createdAt, id=$id, version=$version]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'id'] = this.id; + json[r'version'] = this.version; + return json; + } + + /// Returns a new [ServerVersionHistoryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerVersionHistoryResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerVersionHistoryResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerVersionHistoryResponseDto( + createdAt: mapDateTime(json, r'createdAt', r'')!, + id: mapValueOfType(json, r'id')!, + version: mapValueOfType(json, r'version')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerVersionHistoryResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerVersionHistoryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerVersionHistoryResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerVersionHistoryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'id', + 'version', + }; +} + diff --git a/mobile/openapi/lib/model/server_version_response_dto.dart b/mobile/openapi/lib/model/server_version_response_dto.dart new file mode 100644 index 0000000000..a13cd81ad7 --- /dev/null +++ b/mobile/openapi/lib/model/server_version_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ServerVersionResponseDto { + /// Returns a new [ServerVersionResponseDto] instance. + ServerVersionResponseDto({ + required this.major, + required this.minor, + required this.patch_, + }); + + /// Major version number + int major; + + /// Minor version number + int minor; + + /// Patch version number + int patch_; + + @override + bool operator ==(Object other) => identical(this, other) || other is ServerVersionResponseDto && + other.major == major && + other.minor == minor && + other.patch_ == patch_; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (major.hashCode) + + (minor.hashCode) + + (patch_.hashCode); + + @override + String toString() => 'ServerVersionResponseDto[major=$major, minor=$minor, patch_=$patch_]'; + + Map toJson() { + final json = {}; + json[r'major'] = this.major; + json[r'minor'] = this.minor; + json[r'patch'] = this.patch_; + return json; + } + + /// Returns a new [ServerVersionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ServerVersionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ServerVersionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ServerVersionResponseDto( + major: mapValueOfType(json, r'major')!, + minor: mapValueOfType(json, r'minor')!, + patch_: mapValueOfType(json, r'patch')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ServerVersionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ServerVersionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ServerVersionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ServerVersionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'major', + 'minor', + 'patch', + }; +} + diff --git a/mobile/openapi/lib/model/session_create_dto.dart b/mobile/openapi/lib/model/session_create_dto.dart new file mode 100644 index 0000000000..3874bc3303 --- /dev/null +++ b/mobile/openapi/lib/model/session_create_dto.dart @@ -0,0 +1,147 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SessionCreateDto { + /// Returns a new [SessionCreateDto] instance. + SessionCreateDto({ + this.deviceOS, + this.deviceType, + this.duration, + }); + + /// Device OS + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceOS; + + /// Device type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceType; + + /// Session duration in seconds + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? duration; + + @override + bool operator ==(Object other) => identical(this, other) || other is SessionCreateDto && + other.deviceOS == deviceOS && + other.deviceType == deviceType && + other.duration == duration; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deviceOS == null ? 0 : deviceOS!.hashCode) + + (deviceType == null ? 0 : deviceType!.hashCode) + + (duration == null ? 0 : duration!.hashCode); + + @override + String toString() => 'SessionCreateDto[deviceOS=$deviceOS, deviceType=$deviceType, duration=$duration]'; + + Map toJson() { + final json = {}; + if (this.deviceOS != null) { + json[r'deviceOS'] = this.deviceOS; + } else { + // json[r'deviceOS'] = null; + } + if (this.deviceType != null) { + json[r'deviceType'] = this.deviceType; + } else { + // json[r'deviceType'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + return json; + } + + /// Returns a new [SessionCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SessionCreateDto? fromJson(dynamic value) { + upgradeDto(value, "SessionCreateDto"); + if (value is Map) { + final json = value.cast(); + + return SessionCreateDto( + deviceOS: mapValueOfType(json, r'deviceOS'), + deviceType: mapValueOfType(json, r'deviceType'), + duration: num.parse('${json[r'duration']}'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SessionCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SessionCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SessionCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SessionCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/session_create_response_dto.dart b/mobile/openapi/lib/model/session_create_response_dto.dart new file mode 100644 index 0000000000..f35232b0e8 --- /dev/null +++ b/mobile/openapi/lib/model/session_create_response_dto.dart @@ -0,0 +1,194 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SessionCreateResponseDto { + /// Returns a new [SessionCreateResponseDto] instance. + SessionCreateResponseDto({ + required this.appVersion, + required this.createdAt, + required this.current, + required this.deviceOS, + required this.deviceType, + this.expiresAt, + required this.id, + required this.isPendingSyncReset, + required this.token, + required this.updatedAt, + }); + + /// App version + String? appVersion; + + /// Creation date + String createdAt; + + /// Is current session + bool current; + + /// Device OS + String deviceOS; + + /// Device type + String deviceType; + + /// Expiration date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? expiresAt; + + /// Session ID + String id; + + /// Is pending sync reset + bool isPendingSyncReset; + + /// Session token + String token; + + /// Last update date + String updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SessionCreateResponseDto && + other.appVersion == appVersion && + other.createdAt == createdAt && + other.current == current && + other.deviceOS == deviceOS && + other.deviceType == deviceType && + other.expiresAt == expiresAt && + other.id == id && + other.isPendingSyncReset == isPendingSyncReset && + other.token == token && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (appVersion == null ? 0 : appVersion!.hashCode) + + (createdAt.hashCode) + + (current.hashCode) + + (deviceOS.hashCode) + + (deviceType.hashCode) + + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (id.hashCode) + + (isPendingSyncReset.hashCode) + + (token.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SessionCreateResponseDto[appVersion=$appVersion, createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, token=$token, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + if (this.appVersion != null) { + json[r'appVersion'] = this.appVersion; + } else { + // json[r'appVersion'] = null; + } + json[r'createdAt'] = this.createdAt; + json[r'current'] = this.current; + json[r'deviceOS'] = this.deviceOS; + json[r'deviceType'] = this.deviceType; + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt; + } else { + // json[r'expiresAt'] = null; + } + json[r'id'] = this.id; + json[r'isPendingSyncReset'] = this.isPendingSyncReset; + json[r'token'] = this.token; + json[r'updatedAt'] = this.updatedAt; + return json; + } + + /// Returns a new [SessionCreateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SessionCreateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SessionCreateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SessionCreateResponseDto( + appVersion: mapValueOfType(json, r'appVersion'), + createdAt: mapValueOfType(json, r'createdAt')!, + current: mapValueOfType(json, r'current')!, + deviceOS: mapValueOfType(json, r'deviceOS')!, + deviceType: mapValueOfType(json, r'deviceType')!, + expiresAt: mapValueOfType(json, r'expiresAt'), + id: mapValueOfType(json, r'id')!, + isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, + token: mapValueOfType(json, r'token')!, + updatedAt: mapValueOfType(json, r'updatedAt')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SessionCreateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SessionCreateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SessionCreateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SessionCreateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'appVersion', + 'createdAt', + 'current', + 'deviceOS', + 'deviceType', + 'id', + 'isPendingSyncReset', + 'token', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/session_response_dto.dart b/mobile/openapi/lib/model/session_response_dto.dart new file mode 100644 index 0000000000..ed84160827 --- /dev/null +++ b/mobile/openapi/lib/model/session_response_dto.dart @@ -0,0 +1,185 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SessionResponseDto { + /// Returns a new [SessionResponseDto] instance. + SessionResponseDto({ + required this.appVersion, + required this.createdAt, + required this.current, + required this.deviceOS, + required this.deviceType, + this.expiresAt, + required this.id, + required this.isPendingSyncReset, + required this.updatedAt, + }); + + /// App version + String? appVersion; + + /// Creation date + String createdAt; + + /// Is current session + bool current; + + /// Device OS + String deviceOS; + + /// Device type + String deviceType; + + /// Expiration date + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? expiresAt; + + /// Session ID + String id; + + /// Is pending sync reset + bool isPendingSyncReset; + + /// Last update date + String updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SessionResponseDto && + other.appVersion == appVersion && + other.createdAt == createdAt && + other.current == current && + other.deviceOS == deviceOS && + other.deviceType == deviceType && + other.expiresAt == expiresAt && + other.id == id && + other.isPendingSyncReset == isPendingSyncReset && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (appVersion == null ? 0 : appVersion!.hashCode) + + (createdAt.hashCode) + + (current.hashCode) + + (deviceOS.hashCode) + + (deviceType.hashCode) + + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (id.hashCode) + + (isPendingSyncReset.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SessionResponseDto[appVersion=$appVersion, createdAt=$createdAt, current=$current, deviceOS=$deviceOS, deviceType=$deviceType, expiresAt=$expiresAt, id=$id, isPendingSyncReset=$isPendingSyncReset, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + if (this.appVersion != null) { + json[r'appVersion'] = this.appVersion; + } else { + // json[r'appVersion'] = null; + } + json[r'createdAt'] = this.createdAt; + json[r'current'] = this.current; + json[r'deviceOS'] = this.deviceOS; + json[r'deviceType'] = this.deviceType; + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt; + } else { + // json[r'expiresAt'] = null; + } + json[r'id'] = this.id; + json[r'isPendingSyncReset'] = this.isPendingSyncReset; + json[r'updatedAt'] = this.updatedAt; + return json; + } + + /// Returns a new [SessionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SessionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SessionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SessionResponseDto( + appVersion: mapValueOfType(json, r'appVersion'), + createdAt: mapValueOfType(json, r'createdAt')!, + current: mapValueOfType(json, r'current')!, + deviceOS: mapValueOfType(json, r'deviceOS')!, + deviceType: mapValueOfType(json, r'deviceType')!, + expiresAt: mapValueOfType(json, r'expiresAt'), + id: mapValueOfType(json, r'id')!, + isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset')!, + updatedAt: mapValueOfType(json, r'updatedAt')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SessionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SessionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SessionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SessionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'appVersion', + 'createdAt', + 'current', + 'deviceOS', + 'deviceType', + 'id', + 'isPendingSyncReset', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/session_unlock_dto.dart b/mobile/openapi/lib/model/session_unlock_dto.dart new file mode 100644 index 0000000000..48ee75fb05 --- /dev/null +++ b/mobile/openapi/lib/model/session_unlock_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SessionUnlockDto { + /// Returns a new [SessionUnlockDto] instance. + SessionUnlockDto({ + this.password, + this.pinCode, + }); + + /// User password (required if PIN code is not provided) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? password; + + /// New PIN code (4-6 digits) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? pinCode; + + @override + bool operator ==(Object other) => identical(this, other) || other is SessionUnlockDto && + other.password == password && + other.pinCode == pinCode; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (password == null ? 0 : password!.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode); + + @override + String toString() => 'SessionUnlockDto[password=$password, pinCode=$pinCode]'; + + Map toJson() { + final json = {}; + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + return json; + } + + /// Returns a new [SessionUnlockDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SessionUnlockDto? fromJson(dynamic value) { + upgradeDto(value, "SessionUnlockDto"); + if (value is Map) { + final json = value.cast(); + + return SessionUnlockDto( + password: mapValueOfType(json, r'password'), + pinCode: mapValueOfType(json, r'pinCode'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SessionUnlockDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SessionUnlockDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SessionUnlockDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SessionUnlockDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/session_update_dto.dart b/mobile/openapi/lib/model/session_update_dto.dart new file mode 100644 index 0000000000..3ab430deaa --- /dev/null +++ b/mobile/openapi/lib/model/session_update_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SessionUpdateDto { + /// Returns a new [SessionUpdateDto] instance. + SessionUpdateDto({ + this.isPendingSyncReset, + }); + + /// Reset pending sync state + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isPendingSyncReset; + + @override + bool operator ==(Object other) => identical(this, other) || other is SessionUpdateDto && + other.isPendingSyncReset == isPendingSyncReset; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (isPendingSyncReset == null ? 0 : isPendingSyncReset!.hashCode); + + @override + String toString() => 'SessionUpdateDto[isPendingSyncReset=$isPendingSyncReset]'; + + Map toJson() { + final json = {}; + if (this.isPendingSyncReset != null) { + json[r'isPendingSyncReset'] = this.isPendingSyncReset; + } else { + // json[r'isPendingSyncReset'] = null; + } + return json; + } + + /// Returns a new [SessionUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SessionUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "SessionUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return SessionUpdateDto( + isPendingSyncReset: mapValueOfType(json, r'isPendingSyncReset'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SessionUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SessionUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SessionUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SessionUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/set_maintenance_mode_dto.dart b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart new file mode 100644 index 0000000000..14bf584bb9 --- /dev/null +++ b/mobile/openapi/lib/model/set_maintenance_mode_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SetMaintenanceModeDto { + /// Returns a new [SetMaintenanceModeDto] instance. + SetMaintenanceModeDto({ + required this.action, + this.restoreBackupFilename, + }); + + /// Maintenance action + MaintenanceAction action; + + /// Restore backup filename + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? restoreBackupFilename; + + @override + bool operator ==(Object other) => identical(this, other) || other is SetMaintenanceModeDto && + other.action == action && + other.restoreBackupFilename == restoreBackupFilename; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (restoreBackupFilename == null ? 0 : restoreBackupFilename!.hashCode); + + @override + String toString() => 'SetMaintenanceModeDto[action=$action, restoreBackupFilename=$restoreBackupFilename]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + if (this.restoreBackupFilename != null) { + json[r'restoreBackupFilename'] = this.restoreBackupFilename; + } else { + // json[r'restoreBackupFilename'] = null; + } + return json; + } + + /// Returns a new [SetMaintenanceModeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SetMaintenanceModeDto? fromJson(dynamic value) { + upgradeDto(value, "SetMaintenanceModeDto"); + if (value is Map) { + final json = value.cast(); + + return SetMaintenanceModeDto( + action: MaintenanceAction.fromJson(json[r'action'])!, + restoreBackupFilename: mapValueOfType(json, r'restoreBackupFilename'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SetMaintenanceModeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SetMaintenanceModeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SetMaintenanceModeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SetMaintenanceModeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + }; +} + diff --git a/mobile/openapi/lib/model/shared_link_create_dto.dart b/mobile/openapi/lib/model/shared_link_create_dto.dart new file mode 100644 index 0000000000..2675ad4beb --- /dev/null +++ b/mobile/openapi/lib/model/shared_link_create_dto.dart @@ -0,0 +1,210 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinkCreateDto { + /// Returns a new [SharedLinkCreateDto] instance. + SharedLinkCreateDto({ + this.albumId, + this.allowDownload = true, + this.allowUpload, + this.assetIds = const [], + this.description, + this.expiresAt, + this.password, + this.showMetadata = true, + this.slug, + required this.type, + }); + + /// Album ID (for album sharing) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? albumId; + + /// Allow downloads + bool allowDownload; + + /// Allow uploads + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? allowUpload; + + /// Asset IDs (for individual assets) + List assetIds; + + /// Link description + String? description; + + /// Expiration date + DateTime? expiresAt; + + /// Link password + String? password; + + /// Show metadata + bool showMetadata; + + /// Custom URL slug + String? slug; + + /// Shared link type + SharedLinkType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinkCreateDto && + other.albumId == albumId && + other.allowDownload == allowDownload && + other.allowUpload == allowUpload && + _deepEquality.equals(other.assetIds, assetIds) && + other.description == description && + other.expiresAt == expiresAt && + other.password == password && + other.showMetadata == showMetadata && + other.slug == slug && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId == null ? 0 : albumId!.hashCode) + + (allowDownload.hashCode) + + (allowUpload == null ? 0 : allowUpload!.hashCode) + + (assetIds.hashCode) + + (description == null ? 0 : description!.hashCode) + + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (password == null ? 0 : password!.hashCode) + + (showMetadata.hashCode) + + (slug == null ? 0 : slug!.hashCode) + + (type.hashCode); + + @override + String toString() => 'SharedLinkCreateDto[albumId=$albumId, allowDownload=$allowDownload, allowUpload=$allowUpload, assetIds=$assetIds, description=$description, expiresAt=$expiresAt, password=$password, showMetadata=$showMetadata, slug=$slug, type=$type]'; + + Map toJson() { + final json = {}; + if (this.albumId != null) { + json[r'albumId'] = this.albumId; + } else { + // json[r'albumId'] = null; + } + json[r'allowDownload'] = this.allowDownload; + if (this.allowUpload != null) { + json[r'allowUpload'] = this.allowUpload; + } else { + // json[r'allowUpload'] = null; + } + json[r'assetIds'] = this.assetIds; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String(); + } else { + // json[r'expiresAt'] = null; + } + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + json[r'showMetadata'] = this.showMetadata; + if (this.slug != null) { + json[r'slug'] = this.slug; + } else { + // json[r'slug'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SharedLinkCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinkCreateDto? fromJson(dynamic value) { + upgradeDto(value, "SharedLinkCreateDto"); + if (value is Map) { + final json = value.cast(); + + return SharedLinkCreateDto( + albumId: mapValueOfType(json, r'albumId'), + allowDownload: mapValueOfType(json, r'allowDownload') ?? true, + allowUpload: mapValueOfType(json, r'allowUpload'), + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + description: mapValueOfType(json, r'description'), + expiresAt: mapDateTime(json, r'expiresAt', r''), + password: mapValueOfType(json, r'password'), + showMetadata: mapValueOfType(json, r'showMetadata') ?? true, + slug: mapValueOfType(json, r'slug'), + type: SharedLinkType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinkCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinkCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinkCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinkCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/shared_link_edit_dto.dart b/mobile/openapi/lib/model/shared_link_edit_dto.dart new file mode 100644 index 0000000000..b22232add6 --- /dev/null +++ b/mobile/openapi/lib/model/shared_link_edit_dto.dart @@ -0,0 +1,211 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinkEditDto { + /// Returns a new [SharedLinkEditDto] instance. + SharedLinkEditDto({ + this.allowDownload, + this.allowUpload, + this.changeExpiryTime, + this.description, + this.expiresAt, + this.password, + this.showMetadata, + this.slug, + }); + + /// Allow downloads + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? allowDownload; + + /// Allow uploads + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? allowUpload; + + /// Whether to change the expiry time. Few clients cannot send null to set the expiryTime to never. Setting this flag and not sending expiryAt is considered as null instead. Clients that can send null values can ignore this. + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? changeExpiryTime; + + /// Link description + String? description; + + /// Expiration date + DateTime? expiresAt; + + /// Link password + String? password; + + /// Show metadata + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? showMetadata; + + /// Custom URL slug + String? slug; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinkEditDto && + other.allowDownload == allowDownload && + other.allowUpload == allowUpload && + other.changeExpiryTime == changeExpiryTime && + other.description == description && + other.expiresAt == expiresAt && + other.password == password && + other.showMetadata == showMetadata && + other.slug == slug; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (allowDownload == null ? 0 : allowDownload!.hashCode) + + (allowUpload == null ? 0 : allowUpload!.hashCode) + + (changeExpiryTime == null ? 0 : changeExpiryTime!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (password == null ? 0 : password!.hashCode) + + (showMetadata == null ? 0 : showMetadata!.hashCode) + + (slug == null ? 0 : slug!.hashCode); + + @override + String toString() => 'SharedLinkEditDto[allowDownload=$allowDownload, allowUpload=$allowUpload, changeExpiryTime=$changeExpiryTime, description=$description, expiresAt=$expiresAt, password=$password, showMetadata=$showMetadata, slug=$slug]'; + + Map toJson() { + final json = {}; + if (this.allowDownload != null) { + json[r'allowDownload'] = this.allowDownload; + } else { + // json[r'allowDownload'] = null; + } + if (this.allowUpload != null) { + json[r'allowUpload'] = this.allowUpload; + } else { + // json[r'allowUpload'] = null; + } + if (this.changeExpiryTime != null) { + json[r'changeExpiryTime'] = this.changeExpiryTime; + } else { + // json[r'changeExpiryTime'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String(); + } else { + // json[r'expiresAt'] = null; + } + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + if (this.showMetadata != null) { + json[r'showMetadata'] = this.showMetadata; + } else { + // json[r'showMetadata'] = null; + } + if (this.slug != null) { + json[r'slug'] = this.slug; + } else { + // json[r'slug'] = null; + } + return json; + } + + /// Returns a new [SharedLinkEditDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinkEditDto? fromJson(dynamic value) { + upgradeDto(value, "SharedLinkEditDto"); + if (value is Map) { + final json = value.cast(); + + return SharedLinkEditDto( + allowDownload: mapValueOfType(json, r'allowDownload'), + allowUpload: mapValueOfType(json, r'allowUpload'), + changeExpiryTime: mapValueOfType(json, r'changeExpiryTime'), + description: mapValueOfType(json, r'description'), + expiresAt: mapDateTime(json, r'expiresAt', r''), + password: mapValueOfType(json, r'password'), + showMetadata: mapValueOfType(json, r'showMetadata'), + slug: mapValueOfType(json, r'slug'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinkEditDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinkEditDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinkEditDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinkEditDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/shared_link_login_dto.dart b/mobile/openapi/lib/model/shared_link_login_dto.dart new file mode 100644 index 0000000000..1ab1bc9349 --- /dev/null +++ b/mobile/openapi/lib/model/shared_link_login_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinkLoginDto { + /// Returns a new [SharedLinkLoginDto] instance. + SharedLinkLoginDto({ + required this.password, + }); + + /// Shared link password + String password; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinkLoginDto && + other.password == password; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (password.hashCode); + + @override + String toString() => 'SharedLinkLoginDto[password=$password]'; + + Map toJson() { + final json = {}; + json[r'password'] = this.password; + return json; + } + + /// Returns a new [SharedLinkLoginDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinkLoginDto? fromJson(dynamic value) { + upgradeDto(value, "SharedLinkLoginDto"); + if (value is Map) { + final json = value.cast(); + + return SharedLinkLoginDto( + password: mapValueOfType(json, r'password')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinkLoginDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinkLoginDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinkLoginDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinkLoginDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'password', + }; +} + diff --git a/mobile/openapi/lib/model/shared_link_response_dto.dart b/mobile/openapi/lib/model/shared_link_response_dto.dart new file mode 100644 index 0000000000..d9aec48c39 --- /dev/null +++ b/mobile/openapi/lib/model/shared_link_response_dto.dart @@ -0,0 +1,252 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinkResponseDto { + /// Returns a new [SharedLinkResponseDto] instance. + SharedLinkResponseDto({ + this.album, + required this.allowDownload, + required this.allowUpload, + this.assets = const [], + required this.createdAt, + required this.description, + required this.expiresAt, + required this.id, + required this.key, + required this.password, + required this.showMetadata, + required this.slug, + this.token, + required this.type, + required this.userId, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AlbumResponseDto? album; + + /// Allow downloads + bool allowDownload; + + /// Allow uploads + bool allowUpload; + + List assets; + + /// Creation date + DateTime createdAt; + + /// Link description + String? description; + + /// Expiration date + DateTime? expiresAt; + + /// Shared link ID + String id; + + /// Encryption key (base64url) + String key; + + /// Has password + String? password; + + /// Show metadata + bool showMetadata; + + /// Custom URL slug + String? slug; + + /// Access token + String? token; + + /// Shared link type + SharedLinkType type; + + /// Owner user ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinkResponseDto && + other.album == album && + other.allowDownload == allowDownload && + other.allowUpload == allowUpload && + _deepEquality.equals(other.assets, assets) && + other.createdAt == createdAt && + other.description == description && + other.expiresAt == expiresAt && + other.id == id && + other.key == key && + other.password == password && + other.showMetadata == showMetadata && + other.slug == slug && + other.token == token && + other.type == type && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (album == null ? 0 : album!.hashCode) + + (allowDownload.hashCode) + + (allowUpload.hashCode) + + (assets.hashCode) + + (createdAt.hashCode) + + (description == null ? 0 : description!.hashCode) + + (expiresAt == null ? 0 : expiresAt!.hashCode) + + (id.hashCode) + + (key.hashCode) + + (password == null ? 0 : password!.hashCode) + + (showMetadata.hashCode) + + (slug == null ? 0 : slug!.hashCode) + + (token == null ? 0 : token!.hashCode) + + (type.hashCode) + + (userId.hashCode); + + @override + String toString() => 'SharedLinkResponseDto[album=$album, allowDownload=$allowDownload, allowUpload=$allowUpload, assets=$assets, createdAt=$createdAt, description=$description, expiresAt=$expiresAt, id=$id, key=$key, password=$password, showMetadata=$showMetadata, slug=$slug, token=$token, type=$type, userId=$userId]'; + + Map toJson() { + final json = {}; + if (this.album != null) { + json[r'album'] = this.album; + } else { + // json[r'album'] = null; + } + json[r'allowDownload'] = this.allowDownload; + json[r'allowUpload'] = this.allowUpload; + json[r'assets'] = this.assets; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.expiresAt != null) { + json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String(); + } else { + // json[r'expiresAt'] = null; + } + json[r'id'] = this.id; + json[r'key'] = this.key; + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + json[r'showMetadata'] = this.showMetadata; + if (this.slug != null) { + json[r'slug'] = this.slug; + } else { + // json[r'slug'] = null; + } + if (this.token != null) { + json[r'token'] = this.token; + } else { + // json[r'token'] = null; + } + json[r'type'] = this.type; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [SharedLinkResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinkResponseDto? fromJson(dynamic value) { + upgradeDto(value, "SharedLinkResponseDto"); + if (value is Map) { + final json = value.cast(); + + return SharedLinkResponseDto( + album: AlbumResponseDto.fromJson(json[r'album']), + allowDownload: mapValueOfType(json, r'allowDownload')!, + allowUpload: mapValueOfType(json, r'allowUpload')!, + assets: AssetResponseDto.listFromJson(json[r'assets']), + createdAt: mapDateTime(json, r'createdAt', r'')!, + description: mapValueOfType(json, r'description'), + expiresAt: mapDateTime(json, r'expiresAt', r''), + id: mapValueOfType(json, r'id')!, + key: mapValueOfType(json, r'key')!, + password: mapValueOfType(json, r'password'), + showMetadata: mapValueOfType(json, r'showMetadata')!, + slug: mapValueOfType(json, r'slug'), + token: mapValueOfType(json, r'token'), + type: SharedLinkType.fromJson(json[r'type'])!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinkResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinkResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinkResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinkResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'allowDownload', + 'allowUpload', + 'assets', + 'createdAt', + 'description', + 'expiresAt', + 'id', + 'key', + 'password', + 'showMetadata', + 'slug', + 'type', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/shared_link_type.dart b/mobile/openapi/lib/model/shared_link_type.dart new file mode 100644 index 0000000000..6a17a9c763 --- /dev/null +++ b/mobile/openapi/lib/model/shared_link_type.dart @@ -0,0 +1,85 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Shared link type +class SharedLinkType { + /// Instantiate a new enum with the provided [value]. + const SharedLinkType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const ALBUM = SharedLinkType._(r'ALBUM'); + static const INDIVIDUAL = SharedLinkType._(r'INDIVIDUAL'); + + /// List of all possible values in this [enum][SharedLinkType]. + static const values = [ + ALBUM, + INDIVIDUAL, + ]; + + static SharedLinkType? fromJson(dynamic value) => SharedLinkTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinkType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SharedLinkType] to String, +/// and [decode] dynamic data back to [SharedLinkType]. +class SharedLinkTypeTypeTransformer { + factory SharedLinkTypeTypeTransformer() => _instance ??= const SharedLinkTypeTypeTransformer._(); + + const SharedLinkTypeTypeTransformer._(); + + String encode(SharedLinkType data) => data.value; + + /// Decodes a [dynamic value][data] to a SharedLinkType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + SharedLinkType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'ALBUM': return SharedLinkType.ALBUM; + case r'INDIVIDUAL': return SharedLinkType.INDIVIDUAL; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SharedLinkTypeTypeTransformer] instance. + static SharedLinkTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/shared_links_response.dart b/mobile/openapi/lib/model/shared_links_response.dart new file mode 100644 index 0000000000..510e94e43f --- /dev/null +++ b/mobile/openapi/lib/model/shared_links_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinksResponse { + /// Returns a new [SharedLinksResponse] instance. + SharedLinksResponse({ + this.enabled = true, + this.sidebarWeb = false, + }); + + /// Whether shared links are enabled + bool enabled; + + /// Whether shared links appear in web sidebar + bool sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinksResponse && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (sidebarWeb.hashCode); + + @override + String toString() => 'SharedLinksResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'sidebarWeb'] = this.sidebarWeb; + return json; + } + + /// Returns a new [SharedLinksResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinksResponse? fromJson(dynamic value) { + upgradeDto(value, "SharedLinksResponse"); + if (value is Map) { + final json = value.cast(); + + return SharedLinksResponse( + enabled: mapValueOfType(json, r'enabled')!, + sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinksResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinksResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinksResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinksResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'sidebarWeb', + }; +} + diff --git a/mobile/openapi/lib/model/shared_links_update.dart b/mobile/openapi/lib/model/shared_links_update.dart new file mode 100644 index 0000000000..8e792b4f49 --- /dev/null +++ b/mobile/openapi/lib/model/shared_links_update.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SharedLinksUpdate { + /// Returns a new [SharedLinksUpdate] instance. + SharedLinksUpdate({ + this.enabled, + this.sidebarWeb, + }); + + /// Whether shared links are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Whether shared links appear in web sidebar + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is SharedLinksUpdate && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled == null ? 0 : enabled!.hashCode) + + (sidebarWeb == null ? 0 : sidebarWeb!.hashCode); + + @override + String toString() => 'SharedLinksUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + if (this.sidebarWeb != null) { + json[r'sidebarWeb'] = this.sidebarWeb; + } else { + // json[r'sidebarWeb'] = null; + } + return json; + } + + /// Returns a new [SharedLinksUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SharedLinksUpdate? fromJson(dynamic value) { + upgradeDto(value, "SharedLinksUpdate"); + if (value is Map) { + final json = value.cast(); + + return SharedLinksUpdate( + enabled: mapValueOfType(json, r'enabled'), + sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SharedLinksUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SharedLinksUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SharedLinksUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SharedLinksUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/sign_up_dto.dart b/mobile/openapi/lib/model/sign_up_dto.dart new file mode 100644 index 0000000000..54c8fa07d2 --- /dev/null +++ b/mobile/openapi/lib/model/sign_up_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SignUpDto { + /// Returns a new [SignUpDto] instance. + SignUpDto({ + required this.email, + required this.name, + required this.password, + }); + + /// User email + String email; + + /// User name + String name; + + /// User password + String password; + + @override + bool operator ==(Object other) => identical(this, other) || other is SignUpDto && + other.email == email && + other.name == name && + other.password == password; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (email.hashCode) + + (name.hashCode) + + (password.hashCode); + + @override + String toString() => 'SignUpDto[email=$email, name=$name, password=$password]'; + + Map toJson() { + final json = {}; + json[r'email'] = this.email; + json[r'name'] = this.name; + json[r'password'] = this.password; + return json; + } + + /// Returns a new [SignUpDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SignUpDto? fromJson(dynamic value) { + upgradeDto(value, "SignUpDto"); + if (value is Map) { + final json = value.cast(); + + return SignUpDto( + email: mapValueOfType(json, r'email')!, + name: mapValueOfType(json, r'name')!, + password: mapValueOfType(json, r'password')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SignUpDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SignUpDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SignUpDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SignUpDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'email', + 'name', + 'password', + }; +} + diff --git a/mobile/openapi/lib/model/smart_search_dto.dart b/mobile/openapi/lib/model/smart_search_dto.dart new file mode 100644 index 0000000000..5f8214467f --- /dev/null +++ b/mobile/openapi/lib/model/smart_search_dto.dart @@ -0,0 +1,669 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SmartSearchDto { + /// Returns a new [SmartSearchDto] instance. + SmartSearchDto({ + this.albumIds = const [], + this.city, + this.country, + this.createdAfter, + this.createdBefore, + this.deviceId, + this.isEncoded, + this.isFavorite, + this.isMotion, + this.isNotInAlbum, + this.isOffline, + this.language, + this.lensModel, + this.libraryId, + this.make, + this.model, + this.ocr, + this.page, + this.personIds = const [], + this.query, + this.queryAssetId, + this.rating, + this.size, + this.state, + this.tagIds = const [], + this.takenAfter, + this.takenBefore, + this.trashedAfter, + this.trashedBefore, + this.type, + this.updatedAfter, + this.updatedBefore, + this.visibility, + this.withDeleted, + this.withExif, + }); + + /// Filter by album IDs + List albumIds; + + /// Filter by city name + String? city; + + /// Filter by country name + String? country; + + /// Filter by creation date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdAfter; + + /// Filter by creation date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdBefore; + + /// Device ID to filter by + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceId; + + /// Filter by encoded status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isEncoded; + + /// Filter by favorite status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Filter by motion photo status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isMotion; + + /// Filter assets not in any album + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isNotInAlbum; + + /// Filter by offline status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isOffline; + + /// Search language code + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? language; + + /// Filter by lens model + String? lensModel; + + /// Library ID to filter by + String? libraryId; + + /// Filter by camera make + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? make; + + /// Filter by camera model + String? model; + + /// Filter by OCR text content + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + + /// Page number + /// + /// Minimum value: 1 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? page; + + /// Filter by person IDs + List personIds; + + /// Natural language search query + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? query; + + /// Asset ID to use as search reference + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? queryAssetId; + + /// Filter by rating [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Number of results to return + /// + /// Minimum value: 1 + /// Maximum value: 1000 + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? size; + + /// Filter by state/province name + String? state; + + /// Filter by tag IDs + List? tagIds; + + /// Filter by taken date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenAfter; + + /// Filter by taken date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenBefore; + + /// Filter by trash date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedAfter; + + /// Filter by trash date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedBefore; + + /// Asset type filter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetTypeEnum? type; + + /// Filter by update date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAfter; + + /// Filter by update date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedBefore; + + /// Filter by visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + /// Include deleted assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withDeleted; + + /// Include EXIF data in response + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? withExif; + + @override + bool operator ==(Object other) => identical(this, other) || other is SmartSearchDto && + _deepEquality.equals(other.albumIds, albumIds) && + other.city == city && + other.country == country && + other.createdAfter == createdAfter && + other.createdBefore == createdBefore && + other.deviceId == deviceId && + other.isEncoded == isEncoded && + other.isFavorite == isFavorite && + other.isMotion == isMotion && + other.isNotInAlbum == isNotInAlbum && + other.isOffline == isOffline && + other.language == language && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.make == make && + other.model == model && + other.ocr == ocr && + other.page == page && + _deepEquality.equals(other.personIds, personIds) && + other.query == query && + other.queryAssetId == queryAssetId && + other.rating == rating && + other.size == size && + other.state == state && + _deepEquality.equals(other.tagIds, tagIds) && + other.takenAfter == takenAfter && + other.takenBefore == takenBefore && + other.trashedAfter == trashedAfter && + other.trashedBefore == trashedBefore && + other.type == type && + other.updatedAfter == updatedAfter && + other.updatedBefore == updatedBefore && + other.visibility == visibility && + other.withDeleted == withDeleted && + other.withExif == withExif; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumIds.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAfter == null ? 0 : createdAfter!.hashCode) + + (createdBefore == null ? 0 : createdBefore!.hashCode) + + (deviceId == null ? 0 : deviceId!.hashCode) + + (isEncoded == null ? 0 : isEncoded!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isMotion == null ? 0 : isMotion!.hashCode) + + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + + (isOffline == null ? 0 : isOffline!.hashCode) + + (language == null ? 0 : language!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + + (page == null ? 0 : page!.hashCode) + + (personIds.hashCode) + + (query == null ? 0 : query!.hashCode) + + (queryAssetId == null ? 0 : queryAssetId!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (size == null ? 0 : size!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (tagIds == null ? 0 : tagIds!.hashCode) + + (takenAfter == null ? 0 : takenAfter!.hashCode) + + (takenBefore == null ? 0 : takenBefore!.hashCode) + + (trashedAfter == null ? 0 : trashedAfter!.hashCode) + + (trashedBefore == null ? 0 : trashedBefore!.hashCode) + + (type == null ? 0 : type!.hashCode) + + (updatedAfter == null ? 0 : updatedAfter!.hashCode) + + (updatedBefore == null ? 0 : updatedBefore!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode) + + (withDeleted == null ? 0 : withDeleted!.hashCode) + + (withExif == null ? 0 : withExif!.hashCode); + + @override + String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]'; + + Map toJson() { + final json = {}; + json[r'albumIds'] = this.albumIds; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.createdAfter != null) { + json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String(); + } else { + // json[r'createdAfter'] = null; + } + if (this.createdBefore != null) { + json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String(); + } else { + // json[r'createdBefore'] = null; + } + if (this.deviceId != null) { + json[r'deviceId'] = this.deviceId; + } else { + // json[r'deviceId'] = null; + } + if (this.isEncoded != null) { + json[r'isEncoded'] = this.isEncoded; + } else { + // json[r'isEncoded'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isMotion != null) { + json[r'isMotion'] = this.isMotion; + } else { + // json[r'isMotion'] = null; + } + if (this.isNotInAlbum != null) { + json[r'isNotInAlbum'] = this.isNotInAlbum; + } else { + // json[r'isNotInAlbum'] = null; + } + if (this.isOffline != null) { + json[r'isOffline'] = this.isOffline; + } else { + // json[r'isOffline'] = null; + } + if (this.language != null) { + json[r'language'] = this.language; + } else { + // json[r'language'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } + if (this.page != null) { + json[r'page'] = this.page; + } else { + // json[r'page'] = null; + } + json[r'personIds'] = this.personIds; + if (this.query != null) { + json[r'query'] = this.query; + } else { + // json[r'query'] = null; + } + if (this.queryAssetId != null) { + json[r'queryAssetId'] = this.queryAssetId; + } else { + // json[r'queryAssetId'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.size != null) { + json[r'size'] = this.size; + } else { + // json[r'size'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.tagIds != null) { + json[r'tagIds'] = this.tagIds; + } else { + // json[r'tagIds'] = null; + } + if (this.takenAfter != null) { + json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String(); + } else { + // json[r'takenAfter'] = null; + } + if (this.takenBefore != null) { + json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String(); + } else { + // json[r'takenBefore'] = null; + } + if (this.trashedAfter != null) { + json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); + } else { + // json[r'trashedAfter'] = null; + } + if (this.trashedBefore != null) { + json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String(); + } else { + // json[r'trashedBefore'] = null; + } + if (this.type != null) { + json[r'type'] = this.type; + } else { + // json[r'type'] = null; + } + if (this.updatedAfter != null) { + json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String(); + } else { + // json[r'updatedAfter'] = null; + } + if (this.updatedBefore != null) { + json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String(); + } else { + // json[r'updatedBefore'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + if (this.withDeleted != null) { + json[r'withDeleted'] = this.withDeleted; + } else { + // json[r'withDeleted'] = null; + } + if (this.withExif != null) { + json[r'withExif'] = this.withExif; + } else { + // json[r'withExif'] = null; + } + return json; + } + + /// Returns a new [SmartSearchDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SmartSearchDto? fromJson(dynamic value) { + upgradeDto(value, "SmartSearchDto"); + if (value is Map) { + final json = value.cast(); + + return SmartSearchDto( + albumIds: json[r'albumIds'] is Iterable + ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) + : const [], + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + createdAfter: mapDateTime(json, r'createdAfter', r''), + createdBefore: mapDateTime(json, r'createdBefore', r''), + deviceId: mapValueOfType(json, r'deviceId'), + isEncoded: mapValueOfType(json, r'isEncoded'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isMotion: mapValueOfType(json, r'isMotion'), + isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), + isOffline: mapValueOfType(json, r'isOffline'), + language: mapValueOfType(json, r'language'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), + page: num.parse('${json[r'page']}'), + personIds: json[r'personIds'] is Iterable + ? (json[r'personIds'] as Iterable).cast().toList(growable: false) + : const [], + query: mapValueOfType(json, r'query'), + queryAssetId: mapValueOfType(json, r'queryAssetId'), + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + size: num.parse('${json[r'size']}'), + state: mapValueOfType(json, r'state'), + tagIds: json[r'tagIds'] is Iterable + ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) + : const [], + takenAfter: mapDateTime(json, r'takenAfter', r''), + takenBefore: mapDateTime(json, r'takenBefore', r''), + trashedAfter: mapDateTime(json, r'trashedAfter', r''), + trashedBefore: mapDateTime(json, r'trashedBefore', r''), + type: AssetTypeEnum.fromJson(json[r'type']), + updatedAfter: mapDateTime(json, r'updatedAfter', r''), + updatedBefore: mapDateTime(json, r'updatedBefore', r''), + visibility: AssetVisibility.fromJson(json[r'visibility']), + withDeleted: mapValueOfType(json, r'withDeleted'), + withExif: mapValueOfType(json, r'withExif'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SmartSearchDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SmartSearchDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SmartSearchDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SmartSearchDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/source_type.dart b/mobile/openapi/lib/model/source_type.dart new file mode 100644 index 0000000000..ed164172a3 --- /dev/null +++ b/mobile/openapi/lib/model/source_type.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Face detection source type +class SourceType { + /// Instantiate a new enum with the provided [value]. + const SourceType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const machineLearning = SourceType._(r'machine-learning'); + static const exif = SourceType._(r'exif'); + static const manual = SourceType._(r'manual'); + + /// List of all possible values in this [enum][SourceType]. + static const values = [ + machineLearning, + exif, + manual, + ]; + + static SourceType? fromJson(dynamic value) => SourceTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SourceType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SourceType] to String, +/// and [decode] dynamic data back to [SourceType]. +class SourceTypeTypeTransformer { + factory SourceTypeTypeTransformer() => _instance ??= const SourceTypeTypeTransformer._(); + + const SourceTypeTypeTransformer._(); + + String encode(SourceType data) => data.value; + + /// Decodes a [dynamic value][data] to a SourceType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + SourceType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'machine-learning': return SourceType.machineLearning; + case r'exif': return SourceType.exif; + case r'manual': return SourceType.manual; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SourceTypeTypeTransformer] instance. + static SourceTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/stack_create_dto.dart b/mobile/openapi/lib/model/stack_create_dto.dart new file mode 100644 index 0000000000..6b08c83401 --- /dev/null +++ b/mobile/openapi/lib/model/stack_create_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class StackCreateDto { + /// Returns a new [StackCreateDto] instance. + StackCreateDto({ + this.assetIds = const [], + }); + + /// Asset IDs (first becomes primary, min 2) + List assetIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is StackCreateDto && + _deepEquality.equals(other.assetIds, assetIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode); + + @override + String toString() => 'StackCreateDto[assetIds=$assetIds]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + return json; + } + + /// Returns a new [StackCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static StackCreateDto? fromJson(dynamic value) { + upgradeDto(value, "StackCreateDto"); + if (value is Map) { + final json = value.cast(); + + return StackCreateDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StackCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = StackCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of StackCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = StackCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + }; +} + diff --git a/mobile/openapi/lib/model/stack_response_dto.dart b/mobile/openapi/lib/model/stack_response_dto.dart new file mode 100644 index 0000000000..638dfb5255 --- /dev/null +++ b/mobile/openapi/lib/model/stack_response_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class StackResponseDto { + /// Returns a new [StackResponseDto] instance. + StackResponseDto({ + this.assets = const [], + required this.id, + required this.primaryAssetId, + }); + + /// Stack assets + List assets; + + /// Stack ID + String id; + + /// Primary asset ID + String primaryAssetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is StackResponseDto && + _deepEquality.equals(other.assets, assets) && + other.id == id && + other.primaryAssetId == primaryAssetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assets.hashCode) + + (id.hashCode) + + (primaryAssetId.hashCode); + + @override + String toString() => 'StackResponseDto[assets=$assets, id=$id, primaryAssetId=$primaryAssetId]'; + + Map toJson() { + final json = {}; + json[r'assets'] = this.assets; + json[r'id'] = this.id; + json[r'primaryAssetId'] = this.primaryAssetId; + return json; + } + + /// Returns a new [StackResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static StackResponseDto? fromJson(dynamic value) { + upgradeDto(value, "StackResponseDto"); + if (value is Map) { + final json = value.cast(); + + return StackResponseDto( + assets: AssetResponseDto.listFromJson(json[r'assets']), + id: mapValueOfType(json, r'id')!, + primaryAssetId: mapValueOfType(json, r'primaryAssetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StackResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = StackResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of StackResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = StackResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assets', + 'id', + 'primaryAssetId', + }; +} + diff --git a/mobile/openapi/lib/model/stack_update_dto.dart b/mobile/openapi/lib/model/stack_update_dto.dart new file mode 100644 index 0000000000..e81c204f97 --- /dev/null +++ b/mobile/openapi/lib/model/stack_update_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class StackUpdateDto { + /// Returns a new [StackUpdateDto] instance. + StackUpdateDto({ + this.primaryAssetId, + }); + + /// Primary asset ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? primaryAssetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is StackUpdateDto && + other.primaryAssetId == primaryAssetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (primaryAssetId == null ? 0 : primaryAssetId!.hashCode); + + @override + String toString() => 'StackUpdateDto[primaryAssetId=$primaryAssetId]'; + + Map toJson() { + final json = {}; + if (this.primaryAssetId != null) { + json[r'primaryAssetId'] = this.primaryAssetId; + } else { + // json[r'primaryAssetId'] = null; + } + return json; + } + + /// Returns a new [StackUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static StackUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "StackUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return StackUpdateDto( + primaryAssetId: mapValueOfType(json, r'primaryAssetId'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StackUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = StackUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of StackUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = StackUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/statistics_search_dto.dart b/mobile/openapi/lib/model/statistics_search_dto.dart new file mode 100644 index 0000000000..d5bbf448a3 --- /dev/null +++ b/mobile/openapi/lib/model/statistics_search_dto.dart @@ -0,0 +1,556 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class StatisticsSearchDto { + /// Returns a new [StatisticsSearchDto] instance. + StatisticsSearchDto({ + this.albumIds = const [], + this.city, + this.country, + this.createdAfter, + this.createdBefore, + this.description, + this.deviceId, + this.isEncoded, + this.isFavorite, + this.isMotion, + this.isNotInAlbum, + this.isOffline, + this.lensModel, + this.libraryId, + this.make, + this.model, + this.ocr, + this.personIds = const [], + this.rating, + this.state, + this.tagIds = const [], + this.takenAfter, + this.takenBefore, + this.trashedAfter, + this.trashedBefore, + this.type, + this.updatedAfter, + this.updatedBefore, + this.visibility, + }); + + /// Filter by album IDs + List albumIds; + + /// Filter by city name + String? city; + + /// Filter by country name + String? country; + + /// Filter by creation date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdAfter; + + /// Filter by creation date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? createdBefore; + + /// Filter by description text + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Device ID to filter by + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? deviceId; + + /// Filter by encoded status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isEncoded; + + /// Filter by favorite status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Filter by motion photo status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isMotion; + + /// Filter assets not in any album + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isNotInAlbum; + + /// Filter by offline status + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isOffline; + + /// Filter by lens model + String? lensModel; + + /// Library ID to filter by + String? libraryId; + + /// Filter by camera make + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? make; + + /// Filter by camera model + String? model; + + /// Filter by OCR text content + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? ocr; + + /// Filter by person IDs + List personIds; + + /// Filter by rating [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Filter by state/province name + String? state; + + /// Filter by tag IDs + List? tagIds; + + /// Filter by taken date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenAfter; + + /// Filter by taken date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? takenBefore; + + /// Filter by trash date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedAfter; + + /// Filter by trash date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? trashedBefore; + + /// Asset type filter + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetTypeEnum? type; + + /// Filter by update date (after) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedAfter; + + /// Filter by update date (before) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DateTime? updatedBefore; + + /// Filter by visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + @override + bool operator ==(Object other) => identical(this, other) || other is StatisticsSearchDto && + _deepEquality.equals(other.albumIds, albumIds) && + other.city == city && + other.country == country && + other.createdAfter == createdAfter && + other.createdBefore == createdBefore && + other.description == description && + other.deviceId == deviceId && + other.isEncoded == isEncoded && + other.isFavorite == isFavorite && + other.isMotion == isMotion && + other.isNotInAlbum == isNotInAlbum && + other.isOffline == isOffline && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.make == make && + other.model == model && + other.ocr == ocr && + _deepEquality.equals(other.personIds, personIds) && + other.rating == rating && + other.state == state && + _deepEquality.equals(other.tagIds, tagIds) && + other.takenAfter == takenAfter && + other.takenBefore == takenBefore && + other.trashedAfter == trashedAfter && + other.trashedBefore == trashedBefore && + other.type == type && + other.updatedAfter == updatedAfter && + other.updatedBefore == updatedBefore && + other.visibility == visibility; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumIds.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAfter == null ? 0 : createdAfter!.hashCode) + + (createdBefore == null ? 0 : createdBefore!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (deviceId == null ? 0 : deviceId!.hashCode) + + (isEncoded == null ? 0 : isEncoded!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isMotion == null ? 0 : isMotion!.hashCode) + + (isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) + + (isOffline == null ? 0 : isOffline!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (ocr == null ? 0 : ocr!.hashCode) + + (personIds.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (tagIds == null ? 0 : tagIds!.hashCode) + + (takenAfter == null ? 0 : takenAfter!.hashCode) + + (takenBefore == null ? 0 : takenBefore!.hashCode) + + (trashedAfter == null ? 0 : trashedAfter!.hashCode) + + (trashedBefore == null ? 0 : trashedBefore!.hashCode) + + (type == null ? 0 : type!.hashCode) + + (updatedAfter == null ? 0 : updatedAfter!.hashCode) + + (updatedBefore == null ? 0 : updatedBefore!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode); + + @override + String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, deviceId=$deviceId, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]'; + + Map toJson() { + final json = {}; + json[r'albumIds'] = this.albumIds; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.createdAfter != null) { + json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String(); + } else { + // json[r'createdAfter'] = null; + } + if (this.createdBefore != null) { + json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String(); + } else { + // json[r'createdBefore'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.deviceId != null) { + json[r'deviceId'] = this.deviceId; + } else { + // json[r'deviceId'] = null; + } + if (this.isEncoded != null) { + json[r'isEncoded'] = this.isEncoded; + } else { + // json[r'isEncoded'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.isMotion != null) { + json[r'isMotion'] = this.isMotion; + } else { + // json[r'isMotion'] = null; + } + if (this.isNotInAlbum != null) { + json[r'isNotInAlbum'] = this.isNotInAlbum; + } else { + // json[r'isNotInAlbum'] = null; + } + if (this.isOffline != null) { + json[r'isOffline'] = this.isOffline; + } else { + // json[r'isOffline'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.ocr != null) { + json[r'ocr'] = this.ocr; + } else { + // json[r'ocr'] = null; + } + json[r'personIds'] = this.personIds; + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.tagIds != null) { + json[r'tagIds'] = this.tagIds; + } else { + // json[r'tagIds'] = null; + } + if (this.takenAfter != null) { + json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String(); + } else { + // json[r'takenAfter'] = null; + } + if (this.takenBefore != null) { + json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String(); + } else { + // json[r'takenBefore'] = null; + } + if (this.trashedAfter != null) { + json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String(); + } else { + // json[r'trashedAfter'] = null; + } + if (this.trashedBefore != null) { + json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String(); + } else { + // json[r'trashedBefore'] = null; + } + if (this.type != null) { + json[r'type'] = this.type; + } else { + // json[r'type'] = null; + } + if (this.updatedAfter != null) { + json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String(); + } else { + // json[r'updatedAfter'] = null; + } + if (this.updatedBefore != null) { + json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String(); + } else { + // json[r'updatedBefore'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + return json; + } + + /// Returns a new [StatisticsSearchDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static StatisticsSearchDto? fromJson(dynamic value) { + upgradeDto(value, "StatisticsSearchDto"); + if (value is Map) { + final json = value.cast(); + + return StatisticsSearchDto( + albumIds: json[r'albumIds'] is Iterable + ? (json[r'albumIds'] as Iterable).cast().toList(growable: false) + : const [], + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + createdAfter: mapDateTime(json, r'createdAfter', r''), + createdBefore: mapDateTime(json, r'createdBefore', r''), + description: mapValueOfType(json, r'description'), + deviceId: mapValueOfType(json, r'deviceId'), + isEncoded: mapValueOfType(json, r'isEncoded'), + isFavorite: mapValueOfType(json, r'isFavorite'), + isMotion: mapValueOfType(json, r'isMotion'), + isNotInAlbum: mapValueOfType(json, r'isNotInAlbum'), + isOffline: mapValueOfType(json, r'isOffline'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + ocr: mapValueOfType(json, r'ocr'), + personIds: json[r'personIds'] is Iterable + ? (json[r'personIds'] as Iterable).cast().toList(growable: false) + : const [], + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + state: mapValueOfType(json, r'state'), + tagIds: json[r'tagIds'] is Iterable + ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) + : const [], + takenAfter: mapDateTime(json, r'takenAfter', r''), + takenBefore: mapDateTime(json, r'takenBefore', r''), + trashedAfter: mapDateTime(json, r'trashedAfter', r''), + trashedBefore: mapDateTime(json, r'trashedBefore', r''), + type: AssetTypeEnum.fromJson(json[r'type']), + updatedAfter: mapDateTime(json, r'updatedAfter', r''), + updatedBefore: mapDateTime(json, r'updatedBefore', r''), + visibility: AssetVisibility.fromJson(json[r'visibility']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StatisticsSearchDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = StatisticsSearchDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of StatisticsSearchDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = StatisticsSearchDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/storage_folder.dart b/mobile/openapi/lib/model/storage_folder.dart new file mode 100644 index 0000000000..8579d48f28 --- /dev/null +++ b/mobile/openapi/lib/model/storage_folder.dart @@ -0,0 +1,97 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Storage folder +class StorageFolder { + /// Instantiate a new enum with the provided [value]. + const StorageFolder._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const encodedVideo = StorageFolder._(r'encoded-video'); + static const library_ = StorageFolder._(r'library'); + static const upload = StorageFolder._(r'upload'); + static const profile = StorageFolder._(r'profile'); + static const thumbs = StorageFolder._(r'thumbs'); + static const backups = StorageFolder._(r'backups'); + + /// List of all possible values in this [enum][StorageFolder]. + static const values = [ + encodedVideo, + library_, + upload, + profile, + thumbs, + backups, + ]; + + static StorageFolder? fromJson(dynamic value) => StorageFolderTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = StorageFolder.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [StorageFolder] to String, +/// and [decode] dynamic data back to [StorageFolder]. +class StorageFolderTypeTransformer { + factory StorageFolderTypeTransformer() => _instance ??= const StorageFolderTypeTransformer._(); + + const StorageFolderTypeTransformer._(); + + String encode(StorageFolder data) => data.value; + + /// Decodes a [dynamic value][data] to a StorageFolder. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + StorageFolder? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'encoded-video': return StorageFolder.encodedVideo; + case r'library': return StorageFolder.library_; + case r'upload': return StorageFolder.upload; + case r'profile': return StorageFolder.profile; + case r'thumbs': return StorageFolder.thumbs; + case r'backups': return StorageFolder.backups; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [StorageFolderTypeTransformer] instance. + static StorageFolderTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/sync_ack_delete_dto.dart b/mobile/openapi/lib/model/sync_ack_delete_dto.dart new file mode 100644 index 0000000000..b72ae8c5a6 --- /dev/null +++ b/mobile/openapi/lib/model/sync_ack_delete_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAckDeleteDto { + /// Returns a new [SyncAckDeleteDto] instance. + SyncAckDeleteDto({ + this.types = const [], + }); + + /// Sync entity types to delete acks for + List types; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAckDeleteDto && + _deepEquality.equals(other.types, types); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (types.hashCode); + + @override + String toString() => 'SyncAckDeleteDto[types=$types]'; + + Map toJson() { + final json = {}; + json[r'types'] = this.types; + return json; + } + + /// Returns a new [SyncAckDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAckDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "SyncAckDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return SyncAckDeleteDto( + types: SyncEntityType.listFromJson(json[r'types']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAckDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAckDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAckDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAckDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/sync_ack_dto.dart b/mobile/openapi/lib/model/sync_ack_dto.dart new file mode 100644 index 0000000000..747f671557 --- /dev/null +++ b/mobile/openapi/lib/model/sync_ack_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAckDto { + /// Returns a new [SyncAckDto] instance. + SyncAckDto({ + required this.ack, + required this.type, + }); + + /// Acknowledgment ID + String ack; + + /// Sync entity type + SyncEntityType type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAckDto && + other.ack == ack && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (ack.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAckDto[ack=$ack, type=$type]'; + + Map toJson() { + final json = {}; + json[r'ack'] = this.ack; + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SyncAckDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAckDto? fromJson(dynamic value) { + upgradeDto(value, "SyncAckDto"); + if (value is Map) { + final json = value.cast(); + + return SyncAckDto( + ack: mapValueOfType(json, r'ack')!, + type: SyncEntityType.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAckDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAckDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAckDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAckDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'ack', + 'type', + }; +} + diff --git a/mobile/openapi/lib/model/sync_ack_set_dto.dart b/mobile/openapi/lib/model/sync_ack_set_dto.dart new file mode 100644 index 0000000000..531a9dc763 --- /dev/null +++ b/mobile/openapi/lib/model/sync_ack_set_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAckSetDto { + /// Returns a new [SyncAckSetDto] instance. + SyncAckSetDto({ + this.acks = const [], + }); + + /// Acknowledgment IDs (max 1000) + List acks; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAckSetDto && + _deepEquality.equals(other.acks, acks); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (acks.hashCode); + + @override + String toString() => 'SyncAckSetDto[acks=$acks]'; + + Map toJson() { + final json = {}; + json[r'acks'] = this.acks; + return json; + } + + /// Returns a new [SyncAckSetDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAckSetDto? fromJson(dynamic value) { + upgradeDto(value, "SyncAckSetDto"); + if (value is Map) { + final json = value.cast(); + + return SyncAckSetDto( + acks: json[r'acks'] is Iterable + ? (json[r'acks'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAckSetDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAckSetDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAckSetDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAckSetDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'acks', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_delete_v1.dart b/mobile/openapi/lib/model/sync_album_delete_v1.dart new file mode 100644 index 0000000000..a6fdf5c68c --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumDeleteV1 { + /// Returns a new [SyncAlbumDeleteV1] instance. + SyncAlbumDeleteV1({ + required this.albumId, + }); + + /// Album ID + String albumId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumDeleteV1 && + other.albumId == albumId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode); + + @override + String toString() => 'SyncAlbumDeleteV1[albumId=$albumId]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + return json; + } + + /// Returns a new [SyncAlbumDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumDeleteV1( + albumId: mapValueOfType(json, r'albumId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_to_asset_delete_v1.dart b/mobile/openapi/lib/model/sync_album_to_asset_delete_v1.dart new file mode 100644 index 0000000000..08952b90ed --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_to_asset_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumToAssetDeleteV1 { + /// Returns a new [SyncAlbumToAssetDeleteV1] instance. + SyncAlbumToAssetDeleteV1({ + required this.albumId, + required this.assetId, + }); + + /// Album ID + String albumId; + + /// Asset ID + String assetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumToAssetDeleteV1 && + other.albumId == albumId && + other.assetId == assetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (assetId.hashCode); + + @override + String toString() => 'SyncAlbumToAssetDeleteV1[albumId=$albumId, assetId=$assetId]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'assetId'] = this.assetId; + return json; + } + + /// Returns a new [SyncAlbumToAssetDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumToAssetDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumToAssetDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumToAssetDeleteV1( + albumId: mapValueOfType(json, r'albumId')!, + assetId: mapValueOfType(json, r'assetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumToAssetDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumToAssetDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumToAssetDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumToAssetDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'assetId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_to_asset_v1.dart b/mobile/openapi/lib/model/sync_album_to_asset_v1.dart new file mode 100644 index 0000000000..5f38b35088 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_to_asset_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumToAssetV1 { + /// Returns a new [SyncAlbumToAssetV1] instance. + SyncAlbumToAssetV1({ + required this.albumId, + required this.assetId, + }); + + /// Album ID + String albumId; + + /// Asset ID + String assetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumToAssetV1 && + other.albumId == albumId && + other.assetId == assetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (assetId.hashCode); + + @override + String toString() => 'SyncAlbumToAssetV1[albumId=$albumId, assetId=$assetId]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'assetId'] = this.assetId; + return json; + } + + /// Returns a new [SyncAlbumToAssetV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumToAssetV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumToAssetV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumToAssetV1( + albumId: mapValueOfType(json, r'albumId')!, + assetId: mapValueOfType(json, r'assetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumToAssetV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumToAssetV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumToAssetV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumToAssetV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'assetId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_user_delete_v1.dart b/mobile/openapi/lib/model/sync_album_user_delete_v1.dart new file mode 100644 index 0000000000..526bcc6b6e --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_user_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumUserDeleteV1 { + /// Returns a new [SyncAlbumUserDeleteV1] instance. + SyncAlbumUserDeleteV1({ + required this.albumId, + required this.userId, + }); + + /// Album ID + String albumId; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumUserDeleteV1 && + other.albumId == albumId && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (userId.hashCode); + + @override + String toString() => 'SyncAlbumUserDeleteV1[albumId=$albumId, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [SyncAlbumUserDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumUserDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumUserDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumUserDeleteV1( + albumId: mapValueOfType(json, r'albumId')!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumUserDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumUserDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumUserDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumUserDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_user_v1.dart b/mobile/openapi/lib/model/sync_album_user_v1.dart new file mode 100644 index 0000000000..3fc8972069 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_user_v1.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumUserV1 { + /// Returns a new [SyncAlbumUserV1] instance. + SyncAlbumUserV1({ + required this.albumId, + required this.role, + required this.userId, + }); + + /// Album ID + String albumId; + + /// Album user role + AlbumUserRole role; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumUserV1 && + other.albumId == albumId && + other.role == role && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (role.hashCode) + + (userId.hashCode); + + @override + String toString() => 'SyncAlbumUserV1[albumId=$albumId, role=$role, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'role'] = this.role; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [SyncAlbumUserV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumUserV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumUserV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumUserV1( + albumId: mapValueOfType(json, r'albumId')!, + role: AlbumUserRole.fromJson(json[r'role'])!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumUserV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumUserV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumUserV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumUserV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'role', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_v1.dart b/mobile/openapi/lib/model/sync_album_v1.dart new file mode 100644 index 0000000000..6c89d93724 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_v1.dart @@ -0,0 +1,175 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAlbumV1 { + /// Returns a new [SyncAlbumV1] instance. + SyncAlbumV1({ + required this.createdAt, + required this.description, + required this.id, + required this.isActivityEnabled, + required this.name, + required this.order, + required this.ownerId, + required this.thumbnailAssetId, + required this.updatedAt, + }); + + /// Created at + DateTime createdAt; + + /// Album description + String description; + + /// Album ID + String id; + + /// Is activity enabled + bool isActivityEnabled; + + /// Album name + String name; + + AssetOrder order; + + /// Owner ID + String ownerId; + + /// Thumbnail asset ID + String? thumbnailAssetId; + + /// Updated at + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumV1 && + other.createdAt == createdAt && + other.description == description && + other.id == id && + other.isActivityEnabled == isActivityEnabled && + other.name == name && + other.order == order && + other.ownerId == ownerId && + other.thumbnailAssetId == thumbnailAssetId && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (description.hashCode) + + (id.hashCode) + + (isActivityEnabled.hashCode) + + (name.hashCode) + + (order.hashCode) + + (ownerId.hashCode) + + (thumbnailAssetId == null ? 0 : thumbnailAssetId!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SyncAlbumV1[createdAt=$createdAt, description=$description, id=$id, isActivityEnabled=$isActivityEnabled, name=$name, order=$order, ownerId=$ownerId, thumbnailAssetId=$thumbnailAssetId, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'isActivityEnabled'] = this.isActivityEnabled; + json[r'name'] = this.name; + json[r'order'] = this.order; + json[r'ownerId'] = this.ownerId; + if (this.thumbnailAssetId != null) { + json[r'thumbnailAssetId'] = this.thumbnailAssetId; + } else { + // json[r'thumbnailAssetId'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncAlbumV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumV1( + createdAt: mapDateTime(json, r'createdAt', r'')!, + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled')!, + name: mapValueOfType(json, r'name')!, + order: AssetOrder.fromJson(json[r'order'])!, + ownerId: mapValueOfType(json, r'ownerId')!, + thumbnailAssetId: mapValueOfType(json, r'thumbnailAssetId'), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'description', + 'id', + 'isActivityEnabled', + 'name', + 'order', + 'ownerId', + 'thumbnailAssetId', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_delete_v1.dart new file mode 100644 index 0000000000..1d5a947774 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetDeleteV1 { + /// Returns a new [SyncAssetDeleteV1] instance. + SyncAssetDeleteV1({ + required this.assetId, + }); + + /// Asset ID + String assetId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetDeleteV1 && + other.assetId == assetId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode); + + @override + String toString() => 'SyncAssetDeleteV1[assetId=$assetId]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + return json; + } + + /// Returns a new [SyncAssetDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetDeleteV1( + assetId: mapValueOfType(json, r'assetId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_edit_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_edit_delete_v1.dart new file mode 100644 index 0000000000..68af280290 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_edit_delete_v1.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetEditDeleteV1 { + /// Returns a new [SyncAssetEditDeleteV1] instance. + SyncAssetEditDeleteV1({ + required this.editId, + }); + + String editId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetEditDeleteV1 && + other.editId == editId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (editId.hashCode); + + @override + String toString() => 'SyncAssetEditDeleteV1[editId=$editId]'; + + Map toJson() { + final json = {}; + json[r'editId'] = this.editId; + return json; + } + + /// Returns a new [SyncAssetEditDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetEditDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetEditDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetEditDeleteV1( + editId: mapValueOfType(json, r'editId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetEditDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetEditDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetEditDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetEditDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'editId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_edit_v1.dart b/mobile/openapi/lib/model/sync_asset_edit_v1.dart new file mode 100644 index 0000000000..3cc2673bfc --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_edit_v1.dart @@ -0,0 +1,131 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetEditV1 { + /// Returns a new [SyncAssetEditV1] instance. + SyncAssetEditV1({ + required this.action, + required this.assetId, + required this.id, + required this.parameters, + required this.sequence, + }); + + AssetEditAction action; + + String assetId; + + String id; + + Object parameters; + + int sequence; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetEditV1 && + other.action == action && + other.assetId == assetId && + other.id == id && + other.parameters == parameters && + other.sequence == sequence; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (action.hashCode) + + (assetId.hashCode) + + (id.hashCode) + + (parameters.hashCode) + + (sequence.hashCode); + + @override + String toString() => 'SyncAssetEditV1[action=$action, assetId=$assetId, id=$id, parameters=$parameters, sequence=$sequence]'; + + Map toJson() { + final json = {}; + json[r'action'] = this.action; + json[r'assetId'] = this.assetId; + json[r'id'] = this.id; + json[r'parameters'] = this.parameters; + json[r'sequence'] = this.sequence; + return json; + } + + /// Returns a new [SyncAssetEditV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetEditV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetEditV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetEditV1( + action: AssetEditAction.fromJson(json[r'action'])!, + assetId: mapValueOfType(json, r'assetId')!, + id: mapValueOfType(json, r'id')!, + parameters: mapValueOfType(json, r'parameters')!, + sequence: mapValueOfType(json, r'sequence')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetEditV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetEditV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetEditV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetEditV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'action', + 'assetId', + 'id', + 'parameters', + 'sequence', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_exif_v1.dart b/mobile/openapi/lib/model/sync_asset_exif_v1.dart new file mode 100644 index 0000000000..ff9efdfea3 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_exif_v1.dart @@ -0,0 +1,412 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetExifV1 { + /// Returns a new [SyncAssetExifV1] instance. + SyncAssetExifV1({ + required this.assetId, + required this.city, + required this.country, + required this.dateTimeOriginal, + required this.description, + required this.exifImageHeight, + required this.exifImageWidth, + required this.exposureTime, + required this.fNumber, + required this.fileSizeInByte, + required this.focalLength, + required this.fps, + required this.iso, + required this.latitude, + required this.lensModel, + required this.longitude, + required this.make, + required this.model, + required this.modifyDate, + required this.orientation, + required this.profileDescription, + required this.projectionType, + required this.rating, + required this.state, + required this.timeZone, + }); + + /// Asset ID + String assetId; + + /// City + String? city; + + /// Country + String? country; + + /// Date time original + DateTime? dateTimeOriginal; + + /// Description + String? description; + + /// Exif image height + int? exifImageHeight; + + /// Exif image width + int? exifImageWidth; + + /// Exposure time + String? exposureTime; + + /// F number + double? fNumber; + + /// File size in byte + int? fileSizeInByte; + + /// Focal length + double? focalLength; + + /// FPS + double? fps; + + /// ISO + int? iso; + + /// Latitude + double? latitude; + + /// Lens model + String? lensModel; + + /// Longitude + double? longitude; + + /// Make + String? make; + + /// Model + String? model; + + /// Modify date + DateTime? modifyDate; + + /// Orientation + String? orientation; + + /// Profile description + String? profileDescription; + + /// Projection type + String? projectionType; + + /// Rating + int? rating; + + /// State + String? state; + + /// Time zone + String? timeZone; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetExifV1 && + other.assetId == assetId && + other.city == city && + other.country == country && + other.dateTimeOriginal == dateTimeOriginal && + other.description == description && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileSizeInByte == fileSizeInByte && + other.focalLength == focalLength && + other.fps == fps && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.profileDescription == profileDescription && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.timeZone == timeZone; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (city == null ? 0 : city!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (focalLength == null ? 0 : focalLength!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (profileDescription == null ? 0 : profileDescription!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (timeZone == null ? 0 : timeZone!.hashCode); + + @override + String toString() => 'SyncAssetExifV1[assetId=$assetId, city=$city, country=$country, dateTimeOriginal=$dateTimeOriginal, description=$description, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileSizeInByte=$fileSizeInByte, focalLength=$focalLength, fps=$fps, iso=$iso, latitude=$latitude, lensModel=$lensModel, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, profileDescription=$profileDescription, projectionType=$projectionType, rating=$rating, state=$state, timeZone=$timeZone]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal!.toUtc().toIso8601String(); + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.focalLength != null) { + json[r'focalLength'] = this.focalLength; + } else { + // json[r'focalLength'] = null; + } + if (this.fps != null) { + json[r'fps'] = this.fps; + } else { + // json[r'fps'] = null; + } + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate!.toUtc().toIso8601String(); + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + if (this.profileDescription != null) { + json[r'profileDescription'] = this.profileDescription; + } else { + // json[r'profileDescription'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + if (this.timeZone != null) { + json[r'timeZone'] = this.timeZone; + } else { + // json[r'timeZone'] = null; + } + return json; + } + + /// Returns a new [SyncAssetExifV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetExifV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetExifV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetExifV1( + assetId: mapValueOfType(json, r'assetId')!, + city: mapValueOfType(json, r'city'), + country: mapValueOfType(json, r'country'), + dateTimeOriginal: mapDateTime(json, r'dateTimeOriginal', r''), + description: mapValueOfType(json, r'description'), + exifImageHeight: mapValueOfType(json, r'exifImageHeight'), + exifImageWidth: mapValueOfType(json, r'exifImageWidth'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: (mapValueOfType(json, r'fNumber'))?.toDouble(), + fileSizeInByte: mapValueOfType(json, r'fileSizeInByte'), + focalLength: (mapValueOfType(json, r'focalLength'))?.toDouble(), + fps: (mapValueOfType(json, r'fps'))?.toDouble(), + iso: mapValueOfType(json, r'iso'), + latitude: (mapValueOfType(json, r'latitude'))?.toDouble(), + lensModel: mapValueOfType(json, r'lensModel'), + longitude: (mapValueOfType(json, r'longitude'))?.toDouble(), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapDateTime(json, r'modifyDate', r''), + orientation: mapValueOfType(json, r'orientation'), + profileDescription: mapValueOfType(json, r'profileDescription'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: mapValueOfType(json, r'rating'), + state: mapValueOfType(json, r'state'), + timeZone: mapValueOfType(json, r'timeZone'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetExifV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetExifV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetExifV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetExifV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'city', + 'country', + 'dateTimeOriginal', + 'description', + 'exifImageHeight', + 'exifImageWidth', + 'exposureTime', + 'fNumber', + 'fileSizeInByte', + 'focalLength', + 'fps', + 'iso', + 'latitude', + 'lensModel', + 'longitude', + 'make', + 'model', + 'modifyDate', + 'orientation', + 'profileDescription', + 'projectionType', + 'rating', + 'state', + 'timeZone', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_face_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_face_delete_v1.dart new file mode 100644 index 0000000000..9cfb8814a7 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_face_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetFaceDeleteV1 { + /// Returns a new [SyncAssetFaceDeleteV1] instance. + SyncAssetFaceDeleteV1({ + required this.assetFaceId, + }); + + /// Asset face ID + String assetFaceId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetFaceDeleteV1 && + other.assetFaceId == assetFaceId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetFaceId.hashCode); + + @override + String toString() => 'SyncAssetFaceDeleteV1[assetFaceId=$assetFaceId]'; + + Map toJson() { + final json = {}; + json[r'assetFaceId'] = this.assetFaceId; + return json; + } + + /// Returns a new [SyncAssetFaceDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetFaceDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetFaceDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetFaceDeleteV1( + assetFaceId: mapValueOfType(json, r'assetFaceId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetFaceDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetFaceDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetFaceDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetFaceDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetFaceId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_face_v1.dart b/mobile/openapi/lib/model/sync_asset_face_v1.dart new file mode 100644 index 0000000000..647a07d5eb --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_face_v1.dart @@ -0,0 +1,179 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetFaceV1 { + /// Returns a new [SyncAssetFaceV1] instance. + SyncAssetFaceV1({ + required this.assetId, + required this.boundingBoxX1, + required this.boundingBoxX2, + required this.boundingBoxY1, + required this.boundingBoxY2, + required this.id, + required this.imageHeight, + required this.imageWidth, + required this.personId, + required this.sourceType, + }); + + /// Asset ID + String assetId; + + int boundingBoxX1; + + int boundingBoxX2; + + int boundingBoxY1; + + int boundingBoxY2; + + /// Asset face ID + String id; + + int imageHeight; + + int imageWidth; + + /// Person ID + String? personId; + + /// Source type + String sourceType; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetFaceV1 && + other.assetId == assetId && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxY2 == boundingBoxY2 && + other.id == id && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth && + other.personId == personId && + other.sourceType == sourceType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (boundingBoxX1.hashCode) + + (boundingBoxX2.hashCode) + + (boundingBoxY1.hashCode) + + (boundingBoxY2.hashCode) + + (id.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode) + + (personId == null ? 0 : personId!.hashCode) + + (sourceType.hashCode); + + @override + String toString() => 'SyncAssetFaceV1[assetId=$assetId, boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, personId=$personId, sourceType=$sourceType]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'boundingBoxX1'] = this.boundingBoxX1; + json[r'boundingBoxX2'] = this.boundingBoxX2; + json[r'boundingBoxY1'] = this.boundingBoxY1; + json[r'boundingBoxY2'] = this.boundingBoxY2; + json[r'id'] = this.id; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + if (this.personId != null) { + json[r'personId'] = this.personId; + } else { + // json[r'personId'] = null; + } + json[r'sourceType'] = this.sourceType; + return json; + } + + /// Returns a new [SyncAssetFaceV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetFaceV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetFaceV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetFaceV1( + assetId: mapValueOfType(json, r'assetId')!, + boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, + boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, + boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, + boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, + id: mapValueOfType(json, r'id')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + personId: mapValueOfType(json, r'personId'), + sourceType: mapValueOfType(json, r'sourceType')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetFaceV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetFaceV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetFaceV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetFaceV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'boundingBoxX1', + 'boundingBoxX2', + 'boundingBoxY1', + 'boundingBoxY2', + 'id', + 'imageHeight', + 'imageWidth', + 'personId', + 'sourceType', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_face_v2.dart b/mobile/openapi/lib/model/sync_asset_face_v2.dart new file mode 100644 index 0000000000..688d71229f --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_face_v2.dart @@ -0,0 +1,201 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetFaceV2 { + /// Returns a new [SyncAssetFaceV2] instance. + SyncAssetFaceV2({ + required this.assetId, + required this.boundingBoxX1, + required this.boundingBoxX2, + required this.boundingBoxY1, + required this.boundingBoxY2, + required this.deletedAt, + required this.id, + required this.imageHeight, + required this.imageWidth, + required this.isVisible, + required this.personId, + required this.sourceType, + }); + + /// Asset ID + String assetId; + + int boundingBoxX1; + + int boundingBoxX2; + + int boundingBoxY1; + + int boundingBoxY2; + + /// Face deleted at + DateTime? deletedAt; + + /// Asset face ID + String id; + + int imageHeight; + + int imageWidth; + + /// Is the face visible in the asset + bool isVisible; + + /// Person ID + String? personId; + + /// Source type + String sourceType; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetFaceV2 && + other.assetId == assetId && + other.boundingBoxX1 == boundingBoxX1 && + other.boundingBoxX2 == boundingBoxX2 && + other.boundingBoxY1 == boundingBoxY1 && + other.boundingBoxY2 == boundingBoxY2 && + other.deletedAt == deletedAt && + other.id == id && + other.imageHeight == imageHeight && + other.imageWidth == imageWidth && + other.isVisible == isVisible && + other.personId == personId && + other.sourceType == sourceType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (boundingBoxX1.hashCode) + + (boundingBoxX2.hashCode) + + (boundingBoxY1.hashCode) + + (boundingBoxY2.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (id.hashCode) + + (imageHeight.hashCode) + + (imageWidth.hashCode) + + (isVisible.hashCode) + + (personId == null ? 0 : personId!.hashCode) + + (sourceType.hashCode); + + @override + String toString() => 'SyncAssetFaceV2[assetId=$assetId, boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, deletedAt=$deletedAt, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, isVisible=$isVisible, personId=$personId, sourceType=$sourceType]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'boundingBoxX1'] = this.boundingBoxX1; + json[r'boundingBoxX2'] = this.boundingBoxX2; + json[r'boundingBoxY1'] = this.boundingBoxY1; + json[r'boundingBoxY2'] = this.boundingBoxY2; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'id'] = this.id; + json[r'imageHeight'] = this.imageHeight; + json[r'imageWidth'] = this.imageWidth; + json[r'isVisible'] = this.isVisible; + if (this.personId != null) { + json[r'personId'] = this.personId; + } else { + // json[r'personId'] = null; + } + json[r'sourceType'] = this.sourceType; + return json; + } + + /// Returns a new [SyncAssetFaceV2] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetFaceV2? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetFaceV2"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetFaceV2( + assetId: mapValueOfType(json, r'assetId')!, + boundingBoxX1: mapValueOfType(json, r'boundingBoxX1')!, + boundingBoxX2: mapValueOfType(json, r'boundingBoxX2')!, + boundingBoxY1: mapValueOfType(json, r'boundingBoxY1')!, + boundingBoxY2: mapValueOfType(json, r'boundingBoxY2')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + id: mapValueOfType(json, r'id')!, + imageHeight: mapValueOfType(json, r'imageHeight')!, + imageWidth: mapValueOfType(json, r'imageWidth')!, + isVisible: mapValueOfType(json, r'isVisible')!, + personId: mapValueOfType(json, r'personId'), + sourceType: mapValueOfType(json, r'sourceType')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetFaceV2.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetFaceV2.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetFaceV2-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetFaceV2.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'boundingBoxX1', + 'boundingBoxX2', + 'boundingBoxY1', + 'boundingBoxY2', + 'deletedAt', + 'id', + 'imageHeight', + 'imageWidth', + 'isVisible', + 'personId', + 'sourceType', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_metadata_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_metadata_delete_v1.dart new file mode 100644 index 0000000000..326555ef13 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_metadata_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetMetadataDeleteV1 { + /// Returns a new [SyncAssetMetadataDeleteV1] instance. + SyncAssetMetadataDeleteV1({ + required this.assetId, + required this.key, + }); + + /// Asset ID + String assetId; + + /// Key + String key; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataDeleteV1 && + other.assetId == assetId && + other.key == key; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (key.hashCode); + + @override + String toString() => 'SyncAssetMetadataDeleteV1[assetId=$assetId, key=$key]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'key'] = this.key; + return json; + } + + /// Returns a new [SyncAssetMetadataDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetMetadataDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetMetadataDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetMetadataDeleteV1( + assetId: mapValueOfType(json, r'assetId')!, + key: mapValueOfType(json, r'key')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetMetadataDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetMetadataDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetMetadataDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetMetadataDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'key', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_metadata_v1.dart b/mobile/openapi/lib/model/sync_asset_metadata_v1.dart new file mode 100644 index 0000000000..4a66623939 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_metadata_v1.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetMetadataV1 { + /// Returns a new [SyncAssetMetadataV1] instance. + SyncAssetMetadataV1({ + required this.assetId, + required this.key, + required this.value, + }); + + /// Asset ID + String assetId; + + /// Key + String key; + + /// Value + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetMetadataV1 && + other.assetId == assetId && + other.key == key && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (key.hashCode) + + (value.hashCode); + + @override + String toString() => 'SyncAssetMetadataV1[assetId=$assetId, key=$key, value=$value]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'key'] = this.key; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [SyncAssetMetadataV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetMetadataV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetMetadataV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetMetadataV1( + assetId: mapValueOfType(json, r'assetId')!, + key: mapValueOfType(json, r'key')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetMetadataV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetMetadataV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetMetadataV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetMetadataV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'key', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_v1.dart b/mobile/openapi/lib/model/sync_asset_v1.dart new file mode 100644 index 0000000000..debde4488e --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_v1.dart @@ -0,0 +1,306 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAssetV1 { + /// Returns a new [SyncAssetV1] instance. + SyncAssetV1({ + required this.checksum, + required this.deletedAt, + required this.duration, + required this.fileCreatedAt, + required this.fileModifiedAt, + required this.height, + required this.id, + required this.isEdited, + required this.isFavorite, + required this.libraryId, + required this.livePhotoVideoId, + required this.localDateTime, + required this.originalFileName, + required this.ownerId, + required this.stackId, + required this.thumbhash, + required this.type, + required this.visibility, + required this.width, + }); + + /// Checksum + String checksum; + + /// Deleted at + DateTime? deletedAt; + + /// Duration + String? duration; + + /// File created at + DateTime? fileCreatedAt; + + /// File modified at + DateTime? fileModifiedAt; + + /// Asset height + int? height; + + /// Asset ID + String id; + + /// Is edited + bool isEdited; + + /// Is favorite + bool isFavorite; + + /// Library ID + String? libraryId; + + /// Live photo video ID + String? livePhotoVideoId; + + /// Local date time + DateTime? localDateTime; + + /// Original file name + String originalFileName; + + /// Owner ID + String ownerId; + + /// Stack ID + String? stackId; + + /// Thumbhash + String? thumbhash; + + /// Asset type + AssetTypeEnum type; + + /// Asset visibility + AssetVisibility visibility; + + /// Asset width + int? width; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetV1 && + other.checksum == checksum && + other.deletedAt == deletedAt && + other.duration == duration && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.height == height && + other.id == id && + other.isEdited == isEdited && + other.isFavorite == isFavorite && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.localDateTime == localDateTime && + other.originalFileName == originalFileName && + other.ownerId == ownerId && + other.stackId == stackId && + other.thumbhash == thumbhash && + other.type == type && + other.visibility == visibility && + other.width == width; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checksum.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) + + (fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) + + (height == null ? 0 : height!.hashCode) + + (id.hashCode) + + (isEdited.hashCode) + + (isFavorite.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (localDateTime == null ? 0 : localDateTime!.hashCode) + + (originalFileName.hashCode) + + (ownerId.hashCode) + + (stackId == null ? 0 : stackId!.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode) + + (visibility.hashCode) + + (width == null ? 0 : width!.hashCode); + + @override + String toString() => 'SyncAssetV1[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]'; + + Map toJson() { + final json = {}; + json[r'checksum'] = this.checksum; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.fileCreatedAt != null) { + json[r'fileCreatedAt'] = this.fileCreatedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileCreatedAt'] = null; + } + if (this.fileModifiedAt != null) { + json[r'fileModifiedAt'] = this.fileModifiedAt!.toUtc().toIso8601String(); + } else { + // json[r'fileModifiedAt'] = null; + } + if (this.height != null) { + json[r'height'] = this.height; + } else { + // json[r'height'] = null; + } + json[r'id'] = this.id; + json[r'isEdited'] = this.isEdited; + json[r'isFavorite'] = this.isFavorite; + if (this.libraryId != null) { + json[r'libraryId'] = this.libraryId; + } else { + // json[r'libraryId'] = null; + } + if (this.livePhotoVideoId != null) { + json[r'livePhotoVideoId'] = this.livePhotoVideoId; + } else { + // json[r'livePhotoVideoId'] = null; + } + if (this.localDateTime != null) { + json[r'localDateTime'] = this.localDateTime!.toUtc().toIso8601String(); + } else { + // json[r'localDateTime'] = null; + } + json[r'originalFileName'] = this.originalFileName; + json[r'ownerId'] = this.ownerId; + if (this.stackId != null) { + json[r'stackId'] = this.stackId; + } else { + // json[r'stackId'] = null; + } + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + json[r'visibility'] = this.visibility; + if (this.width != null) { + json[r'width'] = this.width; + } else { + // json[r'width'] = null; + } + return json; + } + + /// Returns a new [SyncAssetV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetV1( + checksum: mapValueOfType(json, r'checksum')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + duration: mapValueOfType(json, r'duration'), + fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r''), + fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r''), + height: mapValueOfType(json, r'height'), + id: mapValueOfType(json, r'id')!, + isEdited: mapValueOfType(json, r'isEdited')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + localDateTime: mapDateTime(json, r'localDateTime', r''), + originalFileName: mapValueOfType(json, r'originalFileName')!, + ownerId: mapValueOfType(json, r'ownerId')!, + stackId: mapValueOfType(json, r'stackId'), + thumbhash: mapValueOfType(json, r'thumbhash'), + type: AssetTypeEnum.fromJson(json[r'type'])!, + visibility: AssetVisibility.fromJson(json[r'visibility'])!, + width: mapValueOfType(json, r'width'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'deletedAt', + 'duration', + 'fileCreatedAt', + 'fileModifiedAt', + 'height', + 'id', + 'isEdited', + 'isFavorite', + 'libraryId', + 'livePhotoVideoId', + 'localDateTime', + 'originalFileName', + 'ownerId', + 'stackId', + 'thumbhash', + 'type', + 'visibility', + 'width', + }; +} + diff --git a/mobile/openapi/lib/model/sync_auth_user_v1.dart b/mobile/openapi/lib/model/sync_auth_user_v1.dart new file mode 100644 index 0000000000..0edd804c6a --- /dev/null +++ b/mobile/openapi/lib/model/sync_auth_user_v1.dart @@ -0,0 +1,226 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncAuthUserV1 { + /// Returns a new [SyncAuthUserV1] instance. + SyncAuthUserV1({ + required this.avatarColor, + required this.deletedAt, + required this.email, + required this.hasProfileImage, + required this.id, + required this.isAdmin, + required this.name, + required this.oauthId, + required this.pinCode, + required this.profileChangedAt, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + required this.storageLabel, + }); + + /// User avatar color + UserAvatarColor? avatarColor; + + /// User deleted at + DateTime? deletedAt; + + /// User email + String email; + + /// User has profile image + bool hasProfileImage; + + /// User ID + String id; + + /// User is admin + bool isAdmin; + + /// User name + String name; + + /// User OAuth ID + String oauthId; + + /// User pin code + String? pinCode; + + /// User profile changed at + DateTime profileChangedAt; + + int? quotaSizeInBytes; + + int quotaUsageInBytes; + + /// User storage label + String? storageLabel; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAuthUserV1 && + other.avatarColor == avatarColor && + other.deletedAt == deletedAt && + other.email == email && + other.hasProfileImage == hasProfileImage && + other.id == id && + other.isAdmin == isAdmin && + other.name == name && + other.oauthId == oauthId && + other.pinCode == pinCode && + other.profileChangedAt == profileChangedAt && + other.quotaSizeInBytes == quotaSizeInBytes && + other.quotaUsageInBytes == quotaUsageInBytes && + other.storageLabel == storageLabel; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (email.hashCode) + + (hasProfileImage.hashCode) + + (id.hashCode) + + (isAdmin.hashCode) + + (name.hashCode) + + (oauthId.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode) + + (profileChangedAt.hashCode) + + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + + (quotaUsageInBytes.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode); + + @override + String toString() => 'SyncAuthUserV1[avatarColor=$avatarColor, deletedAt=$deletedAt, email=$email, hasProfileImage=$hasProfileImage, id=$id, isAdmin=$isAdmin, name=$name, oauthId=$oauthId, pinCode=$pinCode, profileChangedAt=$profileChangedAt, quotaSizeInBytes=$quotaSizeInBytes, quotaUsageInBytes=$quotaUsageInBytes, storageLabel=$storageLabel]'; + + Map toJson() { + final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'email'] = this.email; + json[r'hasProfileImage'] = this.hasProfileImage; + json[r'id'] = this.id; + json[r'isAdmin'] = this.isAdmin; + json[r'name'] = this.name; + json[r'oauthId'] = this.oauthId; + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + if (this.quotaSizeInBytes != null) { + json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; + } else { + // json[r'quotaSizeInBytes'] = null; + } + json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + return json; + } + + /// Returns a new [SyncAuthUserV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAuthUserV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAuthUserV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAuthUserV1( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + deletedAt: mapDateTime(json, r'deletedAt', r''), + email: mapValueOfType(json, r'email')!, + hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, + id: mapValueOfType(json, r'id')!, + isAdmin: mapValueOfType(json, r'isAdmin')!, + name: mapValueOfType(json, r'name')!, + oauthId: mapValueOfType(json, r'oauthId')!, + pinCode: mapValueOfType(json, r'pinCode'), + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), + quotaUsageInBytes: mapValueOfType(json, r'quotaUsageInBytes')!, + storageLabel: mapValueOfType(json, r'storageLabel'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAuthUserV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAuthUserV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAuthUserV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAuthUserV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'avatarColor', + 'deletedAt', + 'email', + 'hasProfileImage', + 'id', + 'isAdmin', + 'name', + 'oauthId', + 'pinCode', + 'profileChangedAt', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'storageLabel', + }; +} + diff --git a/mobile/openapi/lib/model/sync_entity_type.dart b/mobile/openapi/lib/model/sync_entity_type.dart new file mode 100644 index 0000000000..e8db2dc4d3 --- /dev/null +++ b/mobile/openapi/lib/model/sync_entity_type.dart @@ -0,0 +1,229 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Sync entity type +class SyncEntityType { + /// Instantiate a new enum with the provided [value]. + const SyncEntityType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const authUserV1 = SyncEntityType._(r'AuthUserV1'); + static const userV1 = SyncEntityType._(r'UserV1'); + static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1'); + static const assetV1 = SyncEntityType._(r'AssetV1'); + static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1'); + static const assetExifV1 = SyncEntityType._(r'AssetExifV1'); + static const assetEditV1 = SyncEntityType._(r'AssetEditV1'); + static const assetEditDeleteV1 = SyncEntityType._(r'AssetEditDeleteV1'); + static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1'); + static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1'); + static const partnerV1 = SyncEntityType._(r'PartnerV1'); + static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1'); + static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1'); + static const partnerAssetBackfillV1 = SyncEntityType._(r'PartnerAssetBackfillV1'); + static const partnerAssetDeleteV1 = SyncEntityType._(r'PartnerAssetDeleteV1'); + static const partnerAssetExifV1 = SyncEntityType._(r'PartnerAssetExifV1'); + static const partnerAssetExifBackfillV1 = SyncEntityType._(r'PartnerAssetExifBackfillV1'); + static const partnerStackBackfillV1 = SyncEntityType._(r'PartnerStackBackfillV1'); + static const partnerStackDeleteV1 = SyncEntityType._(r'PartnerStackDeleteV1'); + static const partnerStackV1 = SyncEntityType._(r'PartnerStackV1'); + static const albumV1 = SyncEntityType._(r'AlbumV1'); + static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1'); + static const albumUserV1 = SyncEntityType._(r'AlbumUserV1'); + static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1'); + static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1'); + static const albumAssetCreateV1 = SyncEntityType._(r'AlbumAssetCreateV1'); + static const albumAssetUpdateV1 = SyncEntityType._(r'AlbumAssetUpdateV1'); + static const albumAssetBackfillV1 = SyncEntityType._(r'AlbumAssetBackfillV1'); + static const albumAssetExifCreateV1 = SyncEntityType._(r'AlbumAssetExifCreateV1'); + static const albumAssetExifUpdateV1 = SyncEntityType._(r'AlbumAssetExifUpdateV1'); + static const albumAssetExifBackfillV1 = SyncEntityType._(r'AlbumAssetExifBackfillV1'); + static const albumToAssetV1 = SyncEntityType._(r'AlbumToAssetV1'); + static const albumToAssetDeleteV1 = SyncEntityType._(r'AlbumToAssetDeleteV1'); + static const albumToAssetBackfillV1 = SyncEntityType._(r'AlbumToAssetBackfillV1'); + static const memoryV1 = SyncEntityType._(r'MemoryV1'); + static const memoryDeleteV1 = SyncEntityType._(r'MemoryDeleteV1'); + static const memoryToAssetV1 = SyncEntityType._(r'MemoryToAssetV1'); + static const memoryToAssetDeleteV1 = SyncEntityType._(r'MemoryToAssetDeleteV1'); + static const stackV1 = SyncEntityType._(r'StackV1'); + static const stackDeleteV1 = SyncEntityType._(r'StackDeleteV1'); + static const personV1 = SyncEntityType._(r'PersonV1'); + static const personDeleteV1 = SyncEntityType._(r'PersonDeleteV1'); + static const assetFaceV1 = SyncEntityType._(r'AssetFaceV1'); + static const assetFaceV2 = SyncEntityType._(r'AssetFaceV2'); + static const assetFaceDeleteV1 = SyncEntityType._(r'AssetFaceDeleteV1'); + static const userMetadataV1 = SyncEntityType._(r'UserMetadataV1'); + static const userMetadataDeleteV1 = SyncEntityType._(r'UserMetadataDeleteV1'); + static const syncAckV1 = SyncEntityType._(r'SyncAckV1'); + static const syncResetV1 = SyncEntityType._(r'SyncResetV1'); + static const syncCompleteV1 = SyncEntityType._(r'SyncCompleteV1'); + + /// List of all possible values in this [enum][SyncEntityType]. + static const values = [ + authUserV1, + userV1, + userDeleteV1, + assetV1, + assetDeleteV1, + assetExifV1, + assetEditV1, + assetEditDeleteV1, + assetMetadataV1, + assetMetadataDeleteV1, + partnerV1, + partnerDeleteV1, + partnerAssetV1, + partnerAssetBackfillV1, + partnerAssetDeleteV1, + partnerAssetExifV1, + partnerAssetExifBackfillV1, + partnerStackBackfillV1, + partnerStackDeleteV1, + partnerStackV1, + albumV1, + albumDeleteV1, + albumUserV1, + albumUserBackfillV1, + albumUserDeleteV1, + albumAssetCreateV1, + albumAssetUpdateV1, + albumAssetBackfillV1, + albumAssetExifCreateV1, + albumAssetExifUpdateV1, + albumAssetExifBackfillV1, + albumToAssetV1, + albumToAssetDeleteV1, + albumToAssetBackfillV1, + memoryV1, + memoryDeleteV1, + memoryToAssetV1, + memoryToAssetDeleteV1, + stackV1, + stackDeleteV1, + personV1, + personDeleteV1, + assetFaceV1, + assetFaceV2, + assetFaceDeleteV1, + userMetadataV1, + userMetadataDeleteV1, + syncAckV1, + syncResetV1, + syncCompleteV1, + ]; + + static SyncEntityType? fromJson(dynamic value) => SyncEntityTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncEntityType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncEntityType] to String, +/// and [decode] dynamic data back to [SyncEntityType]. +class SyncEntityTypeTypeTransformer { + factory SyncEntityTypeTypeTransformer() => _instance ??= const SyncEntityTypeTypeTransformer._(); + + const SyncEntityTypeTypeTransformer._(); + + String encode(SyncEntityType data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncEntityType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + SyncEntityType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AuthUserV1': return SyncEntityType.authUserV1; + case r'UserV1': return SyncEntityType.userV1; + case r'UserDeleteV1': return SyncEntityType.userDeleteV1; + case r'AssetV1': return SyncEntityType.assetV1; + case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1; + case r'AssetExifV1': return SyncEntityType.assetExifV1; + case r'AssetEditV1': return SyncEntityType.assetEditV1; + case r'AssetEditDeleteV1': return SyncEntityType.assetEditDeleteV1; + case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1; + case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1; + case r'PartnerV1': return SyncEntityType.partnerV1; + case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1; + case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1; + case r'PartnerAssetBackfillV1': return SyncEntityType.partnerAssetBackfillV1; + case r'PartnerAssetDeleteV1': return SyncEntityType.partnerAssetDeleteV1; + case r'PartnerAssetExifV1': return SyncEntityType.partnerAssetExifV1; + case r'PartnerAssetExifBackfillV1': return SyncEntityType.partnerAssetExifBackfillV1; + case r'PartnerStackBackfillV1': return SyncEntityType.partnerStackBackfillV1; + case r'PartnerStackDeleteV1': return SyncEntityType.partnerStackDeleteV1; + case r'PartnerStackV1': return SyncEntityType.partnerStackV1; + case r'AlbumV1': return SyncEntityType.albumV1; + case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1; + case r'AlbumUserV1': return SyncEntityType.albumUserV1; + case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1; + case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1; + case r'AlbumAssetCreateV1': return SyncEntityType.albumAssetCreateV1; + case r'AlbumAssetUpdateV1': return SyncEntityType.albumAssetUpdateV1; + case r'AlbumAssetBackfillV1': return SyncEntityType.albumAssetBackfillV1; + case r'AlbumAssetExifCreateV1': return SyncEntityType.albumAssetExifCreateV1; + case r'AlbumAssetExifUpdateV1': return SyncEntityType.albumAssetExifUpdateV1; + case r'AlbumAssetExifBackfillV1': return SyncEntityType.albumAssetExifBackfillV1; + case r'AlbumToAssetV1': return SyncEntityType.albumToAssetV1; + case r'AlbumToAssetDeleteV1': return SyncEntityType.albumToAssetDeleteV1; + case r'AlbumToAssetBackfillV1': return SyncEntityType.albumToAssetBackfillV1; + case r'MemoryV1': return SyncEntityType.memoryV1; + case r'MemoryDeleteV1': return SyncEntityType.memoryDeleteV1; + case r'MemoryToAssetV1': return SyncEntityType.memoryToAssetV1; + case r'MemoryToAssetDeleteV1': return SyncEntityType.memoryToAssetDeleteV1; + case r'StackV1': return SyncEntityType.stackV1; + case r'StackDeleteV1': return SyncEntityType.stackDeleteV1; + case r'PersonV1': return SyncEntityType.personV1; + case r'PersonDeleteV1': return SyncEntityType.personDeleteV1; + case r'AssetFaceV1': return SyncEntityType.assetFaceV1; + case r'AssetFaceV2': return SyncEntityType.assetFaceV2; + case r'AssetFaceDeleteV1': return SyncEntityType.assetFaceDeleteV1; + case r'UserMetadataV1': return SyncEntityType.userMetadataV1; + case r'UserMetadataDeleteV1': return SyncEntityType.userMetadataDeleteV1; + case r'SyncAckV1': return SyncEntityType.syncAckV1; + case r'SyncResetV1': return SyncEntityType.syncResetV1; + case r'SyncCompleteV1': return SyncEntityType.syncCompleteV1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncEntityTypeTypeTransformer] instance. + static SyncEntityTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/sync_memory_asset_delete_v1.dart b/mobile/openapi/lib/model/sync_memory_asset_delete_v1.dart new file mode 100644 index 0000000000..c37682d02d --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_asset_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncMemoryAssetDeleteV1 { + /// Returns a new [SyncMemoryAssetDeleteV1] instance. + SyncMemoryAssetDeleteV1({ + required this.assetId, + required this.memoryId, + }); + + /// Asset ID + String assetId; + + /// Memory ID + String memoryId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryAssetDeleteV1 && + other.assetId == assetId && + other.memoryId == memoryId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (memoryId.hashCode); + + @override + String toString() => 'SyncMemoryAssetDeleteV1[assetId=$assetId, memoryId=$memoryId]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'memoryId'] = this.memoryId; + return json; + } + + /// Returns a new [SyncMemoryAssetDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryAssetDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryAssetDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryAssetDeleteV1( + assetId: mapValueOfType(json, r'assetId')!, + memoryId: mapValueOfType(json, r'memoryId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryAssetDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryAssetDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryAssetDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryAssetDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'memoryId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_memory_asset_v1.dart b/mobile/openapi/lib/model/sync_memory_asset_v1.dart new file mode 100644 index 0000000000..2cfab98afd --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_asset_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncMemoryAssetV1 { + /// Returns a new [SyncMemoryAssetV1] instance. + SyncMemoryAssetV1({ + required this.assetId, + required this.memoryId, + }); + + /// Asset ID + String assetId; + + /// Memory ID + String memoryId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryAssetV1 && + other.assetId == assetId && + other.memoryId == memoryId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetId.hashCode) + + (memoryId.hashCode); + + @override + String toString() => 'SyncMemoryAssetV1[assetId=$assetId, memoryId=$memoryId]'; + + Map toJson() { + final json = {}; + json[r'assetId'] = this.assetId; + json[r'memoryId'] = this.memoryId; + return json; + } + + /// Returns a new [SyncMemoryAssetV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryAssetV1? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryAssetV1"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryAssetV1( + assetId: mapValueOfType(json, r'assetId')!, + memoryId: mapValueOfType(json, r'memoryId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryAssetV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryAssetV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryAssetV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryAssetV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetId', + 'memoryId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_memory_delete_v1.dart b/mobile/openapi/lib/model/sync_memory_delete_v1.dart new file mode 100644 index 0000000000..d5f63ec8fa --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncMemoryDeleteV1 { + /// Returns a new [SyncMemoryDeleteV1] instance. + SyncMemoryDeleteV1({ + required this.memoryId, + }); + + /// Memory ID + String memoryId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryDeleteV1 && + other.memoryId == memoryId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (memoryId.hashCode); + + @override + String toString() => 'SyncMemoryDeleteV1[memoryId=$memoryId]'; + + Map toJson() { + final json = {}; + json[r'memoryId'] = this.memoryId; + return json; + } + + /// Returns a new [SyncMemoryDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryDeleteV1( + memoryId: mapValueOfType(json, r'memoryId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'memoryId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_memory_v1.dart b/mobile/openapi/lib/model/sync_memory_v1.dart new file mode 100644 index 0000000000..c506738d97 --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_v1.dart @@ -0,0 +1,215 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncMemoryV1 { + /// Returns a new [SyncMemoryV1] instance. + SyncMemoryV1({ + required this.createdAt, + required this.data, + required this.deletedAt, + required this.hideAt, + required this.id, + required this.isSaved, + required this.memoryAt, + required this.ownerId, + required this.seenAt, + required this.showAt, + required this.type, + required this.updatedAt, + }); + + /// Created at + DateTime createdAt; + + /// Data + Object data; + + /// Deleted at + DateTime? deletedAt; + + /// Hide at + DateTime? hideAt; + + /// Memory ID + String id; + + /// Is saved + bool isSaved; + + /// Memory at + DateTime memoryAt; + + /// Owner ID + String ownerId; + + /// Seen at + DateTime? seenAt; + + /// Show at + DateTime? showAt; + + /// Memory type + MemoryType type; + + /// Updated at + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryV1 && + other.createdAt == createdAt && + other.data == data && + other.deletedAt == deletedAt && + other.hideAt == hideAt && + other.id == id && + other.isSaved == isSaved && + other.memoryAt == memoryAt && + other.ownerId == ownerId && + other.seenAt == seenAt && + other.showAt == showAt && + other.type == type && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (data.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (hideAt == null ? 0 : hideAt!.hashCode) + + (id.hashCode) + + (isSaved.hashCode) + + (memoryAt.hashCode) + + (ownerId.hashCode) + + (seenAt == null ? 0 : seenAt!.hashCode) + + (showAt == null ? 0 : showAt!.hashCode) + + (type.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SyncMemoryV1[createdAt=$createdAt, data=$data, deletedAt=$deletedAt, hideAt=$hideAt, id=$id, isSaved=$isSaved, memoryAt=$memoryAt, ownerId=$ownerId, seenAt=$seenAt, showAt=$showAt, type=$type, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'data'] = this.data; + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + if (this.hideAt != null) { + json[r'hideAt'] = this.hideAt!.toUtc().toIso8601String(); + } else { + // json[r'hideAt'] = null; + } + json[r'id'] = this.id; + json[r'isSaved'] = this.isSaved; + json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String(); + json[r'ownerId'] = this.ownerId; + if (this.seenAt != null) { + json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String(); + } else { + // json[r'seenAt'] = null; + } + if (this.showAt != null) { + json[r'showAt'] = this.showAt!.toUtc().toIso8601String(); + } else { + // json[r'showAt'] = null; + } + json[r'type'] = this.type; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncMemoryV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryV1? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryV1"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryV1( + createdAt: mapDateTime(json, r'createdAt', r'')!, + data: mapValueOfType(json, r'data')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + hideAt: mapDateTime(json, r'hideAt', r''), + id: mapValueOfType(json, r'id')!, + isSaved: mapValueOfType(json, r'isSaved')!, + memoryAt: mapDateTime(json, r'memoryAt', r'')!, + ownerId: mapValueOfType(json, r'ownerId')!, + seenAt: mapDateTime(json, r'seenAt', r''), + showAt: mapDateTime(json, r'showAt', r''), + type: MemoryType.fromJson(json[r'type'])!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'data', + 'deletedAt', + 'hideAt', + 'id', + 'isSaved', + 'memoryAt', + 'ownerId', + 'seenAt', + 'showAt', + 'type', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_partner_delete_v1.dart b/mobile/openapi/lib/model/sync_partner_delete_v1.dart new file mode 100644 index 0000000000..64dfb4eb98 --- /dev/null +++ b/mobile/openapi/lib/model/sync_partner_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncPartnerDeleteV1 { + /// Returns a new [SyncPartnerDeleteV1] instance. + SyncPartnerDeleteV1({ + required this.sharedById, + required this.sharedWithId, + }); + + /// Shared by ID + String sharedById; + + /// Shared with ID + String sharedWithId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncPartnerDeleteV1 && + other.sharedById == sharedById && + other.sharedWithId == sharedWithId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (sharedById.hashCode) + + (sharedWithId.hashCode); + + @override + String toString() => 'SyncPartnerDeleteV1[sharedById=$sharedById, sharedWithId=$sharedWithId]'; + + Map toJson() { + final json = {}; + json[r'sharedById'] = this.sharedById; + json[r'sharedWithId'] = this.sharedWithId; + return json; + } + + /// Returns a new [SyncPartnerDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncPartnerDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncPartnerDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncPartnerDeleteV1( + sharedById: mapValueOfType(json, r'sharedById')!, + sharedWithId: mapValueOfType(json, r'sharedWithId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncPartnerDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncPartnerDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncPartnerDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncPartnerDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'sharedById', + 'sharedWithId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_partner_v1.dart b/mobile/openapi/lib/model/sync_partner_v1.dart new file mode 100644 index 0000000000..9f9c3d14c1 --- /dev/null +++ b/mobile/openapi/lib/model/sync_partner_v1.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncPartnerV1 { + /// Returns a new [SyncPartnerV1] instance. + SyncPartnerV1({ + required this.inTimeline, + required this.sharedById, + required this.sharedWithId, + }); + + /// In timeline + bool inTimeline; + + /// Shared by ID + String sharedById; + + /// Shared with ID + String sharedWithId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncPartnerV1 && + other.inTimeline == inTimeline && + other.sharedById == sharedById && + other.sharedWithId == sharedWithId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (inTimeline.hashCode) + + (sharedById.hashCode) + + (sharedWithId.hashCode); + + @override + String toString() => 'SyncPartnerV1[inTimeline=$inTimeline, sharedById=$sharedById, sharedWithId=$sharedWithId]'; + + Map toJson() { + final json = {}; + json[r'inTimeline'] = this.inTimeline; + json[r'sharedById'] = this.sharedById; + json[r'sharedWithId'] = this.sharedWithId; + return json; + } + + /// Returns a new [SyncPartnerV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncPartnerV1? fromJson(dynamic value) { + upgradeDto(value, "SyncPartnerV1"); + if (value is Map) { + final json = value.cast(); + + return SyncPartnerV1( + inTimeline: mapValueOfType(json, r'inTimeline')!, + sharedById: mapValueOfType(json, r'sharedById')!, + sharedWithId: mapValueOfType(json, r'sharedWithId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncPartnerV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncPartnerV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncPartnerV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncPartnerV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'inTimeline', + 'sharedById', + 'sharedWithId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_person_delete_v1.dart b/mobile/openapi/lib/model/sync_person_delete_v1.dart new file mode 100644 index 0000000000..526bc26187 --- /dev/null +++ b/mobile/openapi/lib/model/sync_person_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncPersonDeleteV1 { + /// Returns a new [SyncPersonDeleteV1] instance. + SyncPersonDeleteV1({ + required this.personId, + }); + + /// Person ID + String personId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncPersonDeleteV1 && + other.personId == personId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (personId.hashCode); + + @override + String toString() => 'SyncPersonDeleteV1[personId=$personId]'; + + Map toJson() { + final json = {}; + json[r'personId'] = this.personId; + return json; + } + + /// Returns a new [SyncPersonDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncPersonDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncPersonDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncPersonDeleteV1( + personId: mapValueOfType(json, r'personId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncPersonDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncPersonDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncPersonDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncPersonDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'personId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_person_v1.dart b/mobile/openapi/lib/model/sync_person_v1.dart new file mode 100644 index 0000000000..fc2c36aa8c --- /dev/null +++ b/mobile/openapi/lib/model/sync_person_v1.dart @@ -0,0 +1,193 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncPersonV1 { + /// Returns a new [SyncPersonV1] instance. + SyncPersonV1({ + required this.birthDate, + required this.color, + required this.createdAt, + required this.faceAssetId, + required this.id, + required this.isFavorite, + required this.isHidden, + required this.name, + required this.ownerId, + required this.updatedAt, + }); + + /// Birth date + DateTime? birthDate; + + /// Color + String? color; + + /// Created at + DateTime createdAt; + + /// Face asset ID + String? faceAssetId; + + /// Person ID + String id; + + /// Is favorite + bool isFavorite; + + /// Is hidden + bool isHidden; + + /// Person name + String name; + + /// Owner ID + String ownerId; + + /// Updated at + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncPersonV1 && + other.birthDate == birthDate && + other.color == color && + other.createdAt == createdAt && + other.faceAssetId == faceAssetId && + other.id == id && + other.isFavorite == isFavorite && + other.isHidden == isHidden && + other.name == name && + other.ownerId == ownerId && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (birthDate == null ? 0 : birthDate!.hashCode) + + (color == null ? 0 : color!.hashCode) + + (createdAt.hashCode) + + (faceAssetId == null ? 0 : faceAssetId!.hashCode) + + (id.hashCode) + + (isFavorite.hashCode) + + (isHidden.hashCode) + + (name.hashCode) + + (ownerId.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SyncPersonV1[birthDate=$birthDate, color=$color, createdAt=$createdAt, faceAssetId=$faceAssetId, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, ownerId=$ownerId, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + if (this.birthDate != null) { + json[r'birthDate'] = this.birthDate!.toUtc().toIso8601String(); + } else { + // json[r'birthDate'] = null; + } + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.faceAssetId != null) { + json[r'faceAssetId'] = this.faceAssetId; + } else { + // json[r'faceAssetId'] = null; + } + json[r'id'] = this.id; + json[r'isFavorite'] = this.isFavorite; + json[r'isHidden'] = this.isHidden; + json[r'name'] = this.name; + json[r'ownerId'] = this.ownerId; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncPersonV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncPersonV1? fromJson(dynamic value) { + upgradeDto(value, "SyncPersonV1"); + if (value is Map) { + final json = value.cast(); + + return SyncPersonV1( + birthDate: mapDateTime(json, r'birthDate', r''), + color: mapValueOfType(json, r'color'), + createdAt: mapDateTime(json, r'createdAt', r'')!, + faceAssetId: mapValueOfType(json, r'faceAssetId'), + id: mapValueOfType(json, r'id')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isHidden: mapValueOfType(json, r'isHidden')!, + name: mapValueOfType(json, r'name')!, + ownerId: mapValueOfType(json, r'ownerId')!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncPersonV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncPersonV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncPersonV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncPersonV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'birthDate', + 'color', + 'createdAt', + 'faceAssetId', + 'id', + 'isFavorite', + 'isHidden', + 'name', + 'ownerId', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_request_type.dart b/mobile/openapi/lib/model/sync_request_type.dart new file mode 100644 index 0000000000..671081c0a5 --- /dev/null +++ b/mobile/openapi/lib/model/sync_request_type.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Sync request types +class SyncRequestType { + /// Instantiate a new enum with the provided [value]. + const SyncRequestType._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const albumsV1 = SyncRequestType._(r'AlbumsV1'); + static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1'); + static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1'); + static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1'); + static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1'); + static const assetsV1 = SyncRequestType._(r'AssetsV1'); + static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1'); + static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1'); + static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1'); + static const authUsersV1 = SyncRequestType._(r'AuthUsersV1'); + static const memoriesV1 = SyncRequestType._(r'MemoriesV1'); + static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1'); + static const partnersV1 = SyncRequestType._(r'PartnersV1'); + static const partnerAssetsV1 = SyncRequestType._(r'PartnerAssetsV1'); + static const partnerAssetExifsV1 = SyncRequestType._(r'PartnerAssetExifsV1'); + static const partnerStacksV1 = SyncRequestType._(r'PartnerStacksV1'); + static const stacksV1 = SyncRequestType._(r'StacksV1'); + static const usersV1 = SyncRequestType._(r'UsersV1'); + static const peopleV1 = SyncRequestType._(r'PeopleV1'); + static const assetFacesV1 = SyncRequestType._(r'AssetFacesV1'); + static const assetFacesV2 = SyncRequestType._(r'AssetFacesV2'); + static const userMetadataV1 = SyncRequestType._(r'UserMetadataV1'); + + /// List of all possible values in this [enum][SyncRequestType]. + static const values = [ + albumsV1, + albumUsersV1, + albumToAssetsV1, + albumAssetsV1, + albumAssetExifsV1, + assetsV1, + assetExifsV1, + assetEditsV1, + assetMetadataV1, + authUsersV1, + memoriesV1, + memoryToAssetsV1, + partnersV1, + partnerAssetsV1, + partnerAssetExifsV1, + partnerStacksV1, + stacksV1, + usersV1, + peopleV1, + assetFacesV1, + assetFacesV2, + userMetadataV1, + ]; + + static SyncRequestType? fromJson(dynamic value) => SyncRequestTypeTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncRequestType.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncRequestType] to String, +/// and [decode] dynamic data back to [SyncRequestType]. +class SyncRequestTypeTypeTransformer { + factory SyncRequestTypeTypeTransformer() => _instance ??= const SyncRequestTypeTypeTransformer._(); + + const SyncRequestTypeTypeTransformer._(); + + String encode(SyncRequestType data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncRequestType. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + SyncRequestType? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'AlbumsV1': return SyncRequestType.albumsV1; + case r'AlbumUsersV1': return SyncRequestType.albumUsersV1; + case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1; + case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1; + case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1; + case r'AssetsV1': return SyncRequestType.assetsV1; + case r'AssetExifsV1': return SyncRequestType.assetExifsV1; + case r'AssetEditsV1': return SyncRequestType.assetEditsV1; + case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1; + case r'AuthUsersV1': return SyncRequestType.authUsersV1; + case r'MemoriesV1': return SyncRequestType.memoriesV1; + case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1; + case r'PartnersV1': return SyncRequestType.partnersV1; + case r'PartnerAssetsV1': return SyncRequestType.partnerAssetsV1; + case r'PartnerAssetExifsV1': return SyncRequestType.partnerAssetExifsV1; + case r'PartnerStacksV1': return SyncRequestType.partnerStacksV1; + case r'StacksV1': return SyncRequestType.stacksV1; + case r'UsersV1': return SyncRequestType.usersV1; + case r'PeopleV1': return SyncRequestType.peopleV1; + case r'AssetFacesV1': return SyncRequestType.assetFacesV1; + case r'AssetFacesV2': return SyncRequestType.assetFacesV2; + case r'UserMetadataV1': return SyncRequestType.userMetadataV1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncRequestTypeTypeTransformer] instance. + static SyncRequestTypeTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/sync_stack_delete_v1.dart b/mobile/openapi/lib/model/sync_stack_delete_v1.dart new file mode 100644 index 0000000000..2a7398291a --- /dev/null +++ b/mobile/openapi/lib/model/sync_stack_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncStackDeleteV1 { + /// Returns a new [SyncStackDeleteV1] instance. + SyncStackDeleteV1({ + required this.stackId, + }); + + /// Stack ID + String stackId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncStackDeleteV1 && + other.stackId == stackId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (stackId.hashCode); + + @override + String toString() => 'SyncStackDeleteV1[stackId=$stackId]'; + + Map toJson() { + final json = {}; + json[r'stackId'] = this.stackId; + return json; + } + + /// Returns a new [SyncStackDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncStackDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncStackDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncStackDeleteV1( + stackId: mapValueOfType(json, r'stackId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncStackDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncStackDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncStackDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncStackDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'stackId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_stack_v1.dart b/mobile/openapi/lib/model/sync_stack_v1.dart new file mode 100644 index 0000000000..e4487ccfaf --- /dev/null +++ b/mobile/openapi/lib/model/sync_stack_v1.dart @@ -0,0 +1,136 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncStackV1 { + /// Returns a new [SyncStackV1] instance. + SyncStackV1({ + required this.createdAt, + required this.id, + required this.ownerId, + required this.primaryAssetId, + required this.updatedAt, + }); + + /// Created at + DateTime createdAt; + + /// Stack ID + String id; + + /// Owner ID + String ownerId; + + /// Primary asset ID + String primaryAssetId; + + /// Updated at + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncStackV1 && + other.createdAt == createdAt && + other.id == id && + other.ownerId == ownerId && + other.primaryAssetId == primaryAssetId && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (createdAt.hashCode) + + (id.hashCode) + + (ownerId.hashCode) + + (primaryAssetId.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'SyncStackV1[createdAt=$createdAt, id=$id, ownerId=$ownerId, primaryAssetId=$primaryAssetId, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'id'] = this.id; + json[r'ownerId'] = this.ownerId; + json[r'primaryAssetId'] = this.primaryAssetId; + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncStackV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncStackV1? fromJson(dynamic value) { + upgradeDto(value, "SyncStackV1"); + if (value is Map) { + final json = value.cast(); + + return SyncStackV1( + createdAt: mapDateTime(json, r'createdAt', r'')!, + id: mapValueOfType(json, r'id')!, + ownerId: mapValueOfType(json, r'ownerId')!, + primaryAssetId: mapValueOfType(json, r'primaryAssetId')!, + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncStackV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncStackV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncStackV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncStackV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'id', + 'ownerId', + 'primaryAssetId', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_stream_dto.dart b/mobile/openapi/lib/model/sync_stream_dto.dart new file mode 100644 index 0000000000..932477cb15 --- /dev/null +++ b/mobile/openapi/lib/model/sync_stream_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncStreamDto { + /// Returns a new [SyncStreamDto] instance. + SyncStreamDto({ + this.reset, + this.types = const [], + }); + + /// Reset sync state + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? reset; + + /// Sync request types + List types; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncStreamDto && + other.reset == reset && + _deepEquality.equals(other.types, types); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (reset == null ? 0 : reset!.hashCode) + + (types.hashCode); + + @override + String toString() => 'SyncStreamDto[reset=$reset, types=$types]'; + + Map toJson() { + final json = {}; + if (this.reset != null) { + json[r'reset'] = this.reset; + } else { + // json[r'reset'] = null; + } + json[r'types'] = this.types; + return json; + } + + /// Returns a new [SyncStreamDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncStreamDto? fromJson(dynamic value) { + upgradeDto(value, "SyncStreamDto"); + if (value is Map) { + final json = value.cast(); + + return SyncStreamDto( + reset: mapValueOfType(json, r'reset'), + types: SyncRequestType.listFromJson(json[r'types']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncStreamDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncStreamDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncStreamDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncStreamDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'types', + }; +} + diff --git a/mobile/openapi/lib/model/sync_user_delete_v1.dart b/mobile/openapi/lib/model/sync_user_delete_v1.dart new file mode 100644 index 0000000000..bbbdc147dd --- /dev/null +++ b/mobile/openapi/lib/model/sync_user_delete_v1.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncUserDeleteV1 { + /// Returns a new [SyncUserDeleteV1] instance. + SyncUserDeleteV1({ + required this.userId, + }); + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncUserDeleteV1 && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (userId.hashCode); + + @override + String toString() => 'SyncUserDeleteV1[userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [SyncUserDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncUserDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncUserDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncUserDeleteV1( + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncUserDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncUserDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncUserDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncUserDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_user_metadata_delete_v1.dart b/mobile/openapi/lib/model/sync_user_metadata_delete_v1.dart new file mode 100644 index 0000000000..61340a8f82 --- /dev/null +++ b/mobile/openapi/lib/model/sync_user_metadata_delete_v1.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncUserMetadataDeleteV1 { + /// Returns a new [SyncUserMetadataDeleteV1] instance. + SyncUserMetadataDeleteV1({ + required this.key, + required this.userId, + }); + + /// User metadata key + UserMetadataKey key; + + /// User ID + String userId; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncUserMetadataDeleteV1 && + other.key == key && + other.userId == userId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (key.hashCode) + + (userId.hashCode); + + @override + String toString() => 'SyncUserMetadataDeleteV1[key=$key, userId=$userId]'; + + Map toJson() { + final json = {}; + json[r'key'] = this.key; + json[r'userId'] = this.userId; + return json; + } + + /// Returns a new [SyncUserMetadataDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncUserMetadataDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncUserMetadataDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncUserMetadataDeleteV1( + key: UserMetadataKey.fromJson(json[r'key'])!, + userId: mapValueOfType(json, r'userId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncUserMetadataDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncUserMetadataDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncUserMetadataDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncUserMetadataDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'key', + 'userId', + }; +} + diff --git a/mobile/openapi/lib/model/sync_user_metadata_v1.dart b/mobile/openapi/lib/model/sync_user_metadata_v1.dart new file mode 100644 index 0000000000..23803d0be4 --- /dev/null +++ b/mobile/openapi/lib/model/sync_user_metadata_v1.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncUserMetadataV1 { + /// Returns a new [SyncUserMetadataV1] instance. + SyncUserMetadataV1({ + required this.key, + required this.userId, + required this.value, + }); + + /// User metadata key + UserMetadataKey key; + + /// User ID + String userId; + + /// User metadata value + Object value; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncUserMetadataV1 && + other.key == key && + other.userId == userId && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (key.hashCode) + + (userId.hashCode) + + (value.hashCode); + + @override + String toString() => 'SyncUserMetadataV1[key=$key, userId=$userId, value=$value]'; + + Map toJson() { + final json = {}; + json[r'key'] = this.key; + json[r'userId'] = this.userId; + json[r'value'] = this.value; + return json; + } + + /// Returns a new [SyncUserMetadataV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncUserMetadataV1? fromJson(dynamic value) { + upgradeDto(value, "SyncUserMetadataV1"); + if (value is Map) { + final json = value.cast(); + + return SyncUserMetadataV1( + key: UserMetadataKey.fromJson(json[r'key'])!, + userId: mapValueOfType(json, r'userId')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncUserMetadataV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncUserMetadataV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncUserMetadataV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncUserMetadataV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'key', + 'userId', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/sync_user_v1.dart b/mobile/openapi/lib/model/sync_user_v1.dart new file mode 100644 index 0000000000..6d425130a3 --- /dev/null +++ b/mobile/openapi/lib/model/sync_user_v1.dart @@ -0,0 +1,162 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SyncUserV1 { + /// Returns a new [SyncUserV1] instance. + SyncUserV1({ + required this.avatarColor, + required this.deletedAt, + required this.email, + required this.hasProfileImage, + required this.id, + required this.name, + required this.profileChangedAt, + }); + + /// User avatar color + UserAvatarColor? avatarColor; + + /// User deleted at + DateTime? deletedAt; + + /// User email + String email; + + /// User has profile image + bool hasProfileImage; + + /// User ID + String id; + + /// User name + String name; + + /// User profile changed at + DateTime profileChangedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncUserV1 && + other.avatarColor == avatarColor && + other.deletedAt == deletedAt && + other.email == email && + other.hasProfileImage == hasProfileImage && + other.id == id && + other.name == name && + other.profileChangedAt == profileChangedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (email.hashCode) + + (hasProfileImage.hashCode) + + (id.hashCode) + + (name.hashCode) + + (profileChangedAt.hashCode); + + @override + String toString() => 'SyncUserV1[avatarColor=$avatarColor, deletedAt=$deletedAt, email=$email, hasProfileImage=$hasProfileImage, id=$id, name=$name, profileChangedAt=$profileChangedAt]'; + + Map toJson() { + final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'email'] = this.email; + json[r'hasProfileImage'] = this.hasProfileImage; + json[r'id'] = this.id; + json[r'name'] = this.name; + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [SyncUserV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncUserV1? fromJson(dynamic value) { + upgradeDto(value, "SyncUserV1"); + if (value is Map) { + final json = value.cast(); + + return SyncUserV1( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + deletedAt: mapDateTime(json, r'deletedAt', r''), + email: mapValueOfType(json, r'email')!, + hasProfileImage: mapValueOfType(json, r'hasProfileImage')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncUserV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncUserV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncUserV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncUserV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'avatarColor', + 'deletedAt', + 'email', + 'hasProfileImage', + 'id', + 'name', + 'profileChangedAt', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_backups_dto.dart b/mobile/openapi/lib/model/system_config_backups_dto.dart new file mode 100644 index 0000000000..82cd6e59eb --- /dev/null +++ b/mobile/openapi/lib/model/system_config_backups_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigBackupsDto { + /// Returns a new [SystemConfigBackupsDto] instance. + SystemConfigBackupsDto({ + required this.database, + }); + + DatabaseBackupConfig database; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigBackupsDto && + other.database == database; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (database.hashCode); + + @override + String toString() => 'SystemConfigBackupsDto[database=$database]'; + + Map toJson() { + final json = {}; + json[r'database'] = this.database; + return json; + } + + /// Returns a new [SystemConfigBackupsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigBackupsDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigBackupsDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigBackupsDto( + database: DatabaseBackupConfig.fromJson(json[r'database'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigBackupsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigBackupsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigBackupsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigBackupsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'database', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_dto.dart b/mobile/openapi/lib/model/system_config_dto.dart new file mode 100644 index 0000000000..38dbb30f0c --- /dev/null +++ b/mobile/openapi/lib/model/system_config_dto.dart @@ -0,0 +1,259 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigDto { + /// Returns a new [SystemConfigDto] instance. + SystemConfigDto({ + required this.backup, + required this.ffmpeg, + required this.image, + required this.job, + required this.library_, + required this.logging, + required this.machineLearning, + required this.map, + required this.metadata, + required this.newVersionCheck, + required this.nightlyTasks, + required this.notifications, + required this.oauth, + required this.passwordLogin, + required this.reverseGeocoding, + required this.server, + required this.storageTemplate, + required this.templates, + required this.theme, + required this.trash, + required this.user, + }); + + SystemConfigBackupsDto backup; + + SystemConfigFFmpegDto ffmpeg; + + SystemConfigImageDto image; + + SystemConfigJobDto job; + + SystemConfigLibraryDto library_; + + SystemConfigLoggingDto logging; + + SystemConfigMachineLearningDto machineLearning; + + SystemConfigMapDto map; + + SystemConfigMetadataDto metadata; + + SystemConfigNewVersionCheckDto newVersionCheck; + + SystemConfigNightlyTasksDto nightlyTasks; + + SystemConfigNotificationsDto notifications; + + SystemConfigOAuthDto oauth; + + SystemConfigPasswordLoginDto passwordLogin; + + SystemConfigReverseGeocodingDto reverseGeocoding; + + SystemConfigServerDto server; + + SystemConfigStorageTemplateDto storageTemplate; + + SystemConfigTemplatesDto templates; + + SystemConfigThemeDto theme; + + SystemConfigTrashDto trash; + + SystemConfigUserDto user; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigDto && + other.backup == backup && + other.ffmpeg == ffmpeg && + other.image == image && + other.job == job && + other.library_ == library_ && + other.logging == logging && + other.machineLearning == machineLearning && + other.map == map && + other.metadata == metadata && + other.newVersionCheck == newVersionCheck && + other.nightlyTasks == nightlyTasks && + other.notifications == notifications && + other.oauth == oauth && + other.passwordLogin == passwordLogin && + other.reverseGeocoding == reverseGeocoding && + other.server == server && + other.storageTemplate == storageTemplate && + other.templates == templates && + other.theme == theme && + other.trash == trash && + other.user == user; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backup.hashCode) + + (ffmpeg.hashCode) + + (image.hashCode) + + (job.hashCode) + + (library_.hashCode) + + (logging.hashCode) + + (machineLearning.hashCode) + + (map.hashCode) + + (metadata.hashCode) + + (newVersionCheck.hashCode) + + (nightlyTasks.hashCode) + + (notifications.hashCode) + + (oauth.hashCode) + + (passwordLogin.hashCode) + + (reverseGeocoding.hashCode) + + (server.hashCode) + + (storageTemplate.hashCode) + + (templates.hashCode) + + (theme.hashCode) + + (trash.hashCode) + + (user.hashCode); + + @override + String toString() => 'SystemConfigDto[backup=$backup, ffmpeg=$ffmpeg, image=$image, job=$job, library_=$library_, logging=$logging, machineLearning=$machineLearning, map=$map, metadata=$metadata, newVersionCheck=$newVersionCheck, nightlyTasks=$nightlyTasks, notifications=$notifications, oauth=$oauth, passwordLogin=$passwordLogin, reverseGeocoding=$reverseGeocoding, server=$server, storageTemplate=$storageTemplate, templates=$templates, theme=$theme, trash=$trash, user=$user]'; + + Map toJson() { + final json = {}; + json[r'backup'] = this.backup; + json[r'ffmpeg'] = this.ffmpeg; + json[r'image'] = this.image; + json[r'job'] = this.job; + json[r'library'] = this.library_; + json[r'logging'] = this.logging; + json[r'machineLearning'] = this.machineLearning; + json[r'map'] = this.map; + json[r'metadata'] = this.metadata; + json[r'newVersionCheck'] = this.newVersionCheck; + json[r'nightlyTasks'] = this.nightlyTasks; + json[r'notifications'] = this.notifications; + json[r'oauth'] = this.oauth; + json[r'passwordLogin'] = this.passwordLogin; + json[r'reverseGeocoding'] = this.reverseGeocoding; + json[r'server'] = this.server; + json[r'storageTemplate'] = this.storageTemplate; + json[r'templates'] = this.templates; + json[r'theme'] = this.theme; + json[r'trash'] = this.trash; + json[r'user'] = this.user; + return json; + } + + /// Returns a new [SystemConfigDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigDto( + backup: SystemConfigBackupsDto.fromJson(json[r'backup'])!, + ffmpeg: SystemConfigFFmpegDto.fromJson(json[r'ffmpeg'])!, + image: SystemConfigImageDto.fromJson(json[r'image'])!, + job: SystemConfigJobDto.fromJson(json[r'job'])!, + library_: SystemConfigLibraryDto.fromJson(json[r'library'])!, + logging: SystemConfigLoggingDto.fromJson(json[r'logging'])!, + machineLearning: SystemConfigMachineLearningDto.fromJson(json[r'machineLearning'])!, + map: SystemConfigMapDto.fromJson(json[r'map'])!, + metadata: SystemConfigMetadataDto.fromJson(json[r'metadata'])!, + newVersionCheck: SystemConfigNewVersionCheckDto.fromJson(json[r'newVersionCheck'])!, + nightlyTasks: SystemConfigNightlyTasksDto.fromJson(json[r'nightlyTasks'])!, + notifications: SystemConfigNotificationsDto.fromJson(json[r'notifications'])!, + oauth: SystemConfigOAuthDto.fromJson(json[r'oauth'])!, + passwordLogin: SystemConfigPasswordLoginDto.fromJson(json[r'passwordLogin'])!, + reverseGeocoding: SystemConfigReverseGeocodingDto.fromJson(json[r'reverseGeocoding'])!, + server: SystemConfigServerDto.fromJson(json[r'server'])!, + storageTemplate: SystemConfigStorageTemplateDto.fromJson(json[r'storageTemplate'])!, + templates: SystemConfigTemplatesDto.fromJson(json[r'templates'])!, + theme: SystemConfigThemeDto.fromJson(json[r'theme'])!, + trash: SystemConfigTrashDto.fromJson(json[r'trash'])!, + user: SystemConfigUserDto.fromJson(json[r'user'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backup', + 'ffmpeg', + 'image', + 'job', + 'library', + 'logging', + 'machineLearning', + 'map', + 'metadata', + 'newVersionCheck', + 'nightlyTasks', + 'notifications', + 'oauth', + 'passwordLogin', + 'reverseGeocoding', + 'server', + 'storageTemplate', + 'templates', + 'theme', + 'trash', + 'user', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart new file mode 100644 index 0000000000..6c7acbd218 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_f_fmpeg_dto.dart @@ -0,0 +1,293 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigFFmpegDto { + /// Returns a new [SystemConfigFFmpegDto] instance. + SystemConfigFFmpegDto({ + required this.accel, + required this.accelDecode, + this.acceptedAudioCodecs = const [], + this.acceptedContainers = const [], + this.acceptedVideoCodecs = const [], + required this.bframes, + required this.cqMode, + required this.crf, + required this.gopSize, + required this.maxBitrate, + required this.preferredHwDevice, + required this.preset, + required this.refs, + required this.targetAudioCodec, + required this.targetResolution, + required this.targetVideoCodec, + required this.temporalAQ, + required this.threads, + required this.tonemap, + required this.transcode, + required this.twoPass, + }); + + /// Transcode hardware acceleration + TranscodeHWAccel accel; + + /// Accelerated decode + bool accelDecode; + + /// Accepted audio codecs + List acceptedAudioCodecs; + + /// Accepted containers + List acceptedContainers; + + /// Accepted video codecs + List acceptedVideoCodecs; + + /// B-frames + /// + /// Minimum value: -1 + /// Maximum value: 16 + int bframes; + + /// CQ mode + CQMode cqMode; + + /// CRF + /// + /// Minimum value: 0 + /// Maximum value: 51 + int crf; + + /// GOP size + /// + /// Minimum value: 0 + int gopSize; + + /// Max bitrate + String maxBitrate; + + /// Preferred hardware device + String preferredHwDevice; + + /// Preset + String preset; + + /// References + /// + /// Minimum value: 0 + /// Maximum value: 6 + int refs; + + /// Target audio codec + AudioCodec targetAudioCodec; + + /// Target resolution + String targetResolution; + + /// Target video codec + VideoCodec targetVideoCodec; + + /// Temporal AQ + bool temporalAQ; + + /// Threads + /// + /// Minimum value: 0 + int threads; + + /// Tone mapping + ToneMapping tonemap; + + /// Transcode policy + TranscodePolicy transcode; + + /// Two pass + bool twoPass; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigFFmpegDto && + other.accel == accel && + other.accelDecode == accelDecode && + _deepEquality.equals(other.acceptedAudioCodecs, acceptedAudioCodecs) && + _deepEquality.equals(other.acceptedContainers, acceptedContainers) && + _deepEquality.equals(other.acceptedVideoCodecs, acceptedVideoCodecs) && + other.bframes == bframes && + other.cqMode == cqMode && + other.crf == crf && + other.gopSize == gopSize && + other.maxBitrate == maxBitrate && + other.preferredHwDevice == preferredHwDevice && + other.preset == preset && + other.refs == refs && + other.targetAudioCodec == targetAudioCodec && + other.targetResolution == targetResolution && + other.targetVideoCodec == targetVideoCodec && + other.temporalAQ == temporalAQ && + other.threads == threads && + other.tonemap == tonemap && + other.transcode == transcode && + other.twoPass == twoPass; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (accel.hashCode) + + (accelDecode.hashCode) + + (acceptedAudioCodecs.hashCode) + + (acceptedContainers.hashCode) + + (acceptedVideoCodecs.hashCode) + + (bframes.hashCode) + + (cqMode.hashCode) + + (crf.hashCode) + + (gopSize.hashCode) + + (maxBitrate.hashCode) + + (preferredHwDevice.hashCode) + + (preset.hashCode) + + (refs.hashCode) + + (targetAudioCodec.hashCode) + + (targetResolution.hashCode) + + (targetVideoCodec.hashCode) + + (temporalAQ.hashCode) + + (threads.hashCode) + + (tonemap.hashCode) + + (transcode.hashCode) + + (twoPass.hashCode); + + @override + String toString() => 'SystemConfigFFmpegDto[accel=$accel, accelDecode=$accelDecode, acceptedAudioCodecs=$acceptedAudioCodecs, acceptedContainers=$acceptedContainers, acceptedVideoCodecs=$acceptedVideoCodecs, bframes=$bframes, cqMode=$cqMode, crf=$crf, gopSize=$gopSize, maxBitrate=$maxBitrate, preferredHwDevice=$preferredHwDevice, preset=$preset, refs=$refs, targetAudioCodec=$targetAudioCodec, targetResolution=$targetResolution, targetVideoCodec=$targetVideoCodec, temporalAQ=$temporalAQ, threads=$threads, tonemap=$tonemap, transcode=$transcode, twoPass=$twoPass]'; + + Map toJson() { + final json = {}; + json[r'accel'] = this.accel; + json[r'accelDecode'] = this.accelDecode; + json[r'acceptedAudioCodecs'] = this.acceptedAudioCodecs; + json[r'acceptedContainers'] = this.acceptedContainers; + json[r'acceptedVideoCodecs'] = this.acceptedVideoCodecs; + json[r'bframes'] = this.bframes; + json[r'cqMode'] = this.cqMode; + json[r'crf'] = this.crf; + json[r'gopSize'] = this.gopSize; + json[r'maxBitrate'] = this.maxBitrate; + json[r'preferredHwDevice'] = this.preferredHwDevice; + json[r'preset'] = this.preset; + json[r'refs'] = this.refs; + json[r'targetAudioCodec'] = this.targetAudioCodec; + json[r'targetResolution'] = this.targetResolution; + json[r'targetVideoCodec'] = this.targetVideoCodec; + json[r'temporalAQ'] = this.temporalAQ; + json[r'threads'] = this.threads; + json[r'tonemap'] = this.tonemap; + json[r'transcode'] = this.transcode; + json[r'twoPass'] = this.twoPass; + return json; + } + + /// Returns a new [SystemConfigFFmpegDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigFFmpegDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigFFmpegDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigFFmpegDto( + accel: TranscodeHWAccel.fromJson(json[r'accel'])!, + accelDecode: mapValueOfType(json, r'accelDecode')!, + acceptedAudioCodecs: AudioCodec.listFromJson(json[r'acceptedAudioCodecs']), + acceptedContainers: VideoContainer.listFromJson(json[r'acceptedContainers']), + acceptedVideoCodecs: VideoCodec.listFromJson(json[r'acceptedVideoCodecs']), + bframes: mapValueOfType(json, r'bframes')!, + cqMode: CQMode.fromJson(json[r'cqMode'])!, + crf: mapValueOfType(json, r'crf')!, + gopSize: mapValueOfType(json, r'gopSize')!, + maxBitrate: mapValueOfType(json, r'maxBitrate')!, + preferredHwDevice: mapValueOfType(json, r'preferredHwDevice')!, + preset: mapValueOfType(json, r'preset')!, + refs: mapValueOfType(json, r'refs')!, + targetAudioCodec: AudioCodec.fromJson(json[r'targetAudioCodec'])!, + targetResolution: mapValueOfType(json, r'targetResolution')!, + targetVideoCodec: VideoCodec.fromJson(json[r'targetVideoCodec'])!, + temporalAQ: mapValueOfType(json, r'temporalAQ')!, + threads: mapValueOfType(json, r'threads')!, + tonemap: ToneMapping.fromJson(json[r'tonemap'])!, + transcode: TranscodePolicy.fromJson(json[r'transcode'])!, + twoPass: mapValueOfType(json, r'twoPass')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigFFmpegDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigFFmpegDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigFFmpegDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigFFmpegDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'accel', + 'accelDecode', + 'acceptedAudioCodecs', + 'acceptedContainers', + 'acceptedVideoCodecs', + 'bframes', + 'cqMode', + 'crf', + 'gopSize', + 'maxBitrate', + 'preferredHwDevice', + 'preset', + 'refs', + 'targetAudioCodec', + 'targetResolution', + 'targetVideoCodec', + 'temporalAQ', + 'threads', + 'tonemap', + 'transcode', + 'twoPass', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_faces_dto.dart b/mobile/openapi/lib/model/system_config_faces_dto.dart new file mode 100644 index 0000000000..f57303c310 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_faces_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigFacesDto { + /// Returns a new [SystemConfigFacesDto] instance. + SystemConfigFacesDto({ + required this.import_, + }); + + /// Import + bool import_; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigFacesDto && + other.import_ == import_; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (import_.hashCode); + + @override + String toString() => 'SystemConfigFacesDto[import_=$import_]'; + + Map toJson() { + final json = {}; + json[r'import'] = this.import_; + return json; + } + + /// Returns a new [SystemConfigFacesDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigFacesDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigFacesDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigFacesDto( + import_: mapValueOfType(json, r'import')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigFacesDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigFacesDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigFacesDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigFacesDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'import', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart b/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart new file mode 100644 index 0000000000..b5640f82c8 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_generated_fullsize_image_dto.dart @@ -0,0 +1,129 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigGeneratedFullsizeImageDto { + /// Returns a new [SystemConfigGeneratedFullsizeImageDto] instance. + SystemConfigGeneratedFullsizeImageDto({ + required this.enabled, + required this.format, + this.progressive = false, + required this.quality, + }); + + /// Enabled + bool enabled; + + /// Image format + ImageFormat format; + + /// Progressive + bool progressive; + + /// Quality + /// + /// Minimum value: 1 + /// Maximum value: 100 + int quality; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigGeneratedFullsizeImageDto && + other.enabled == enabled && + other.format == format && + other.progressive == progressive && + other.quality == quality; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (format.hashCode) + + (progressive.hashCode) + + (quality.hashCode); + + @override + String toString() => 'SystemConfigGeneratedFullsizeImageDto[enabled=$enabled, format=$format, progressive=$progressive, quality=$quality]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'format'] = this.format; + json[r'progressive'] = this.progressive; + json[r'quality'] = this.quality; + return json; + } + + /// Returns a new [SystemConfigGeneratedFullsizeImageDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigGeneratedFullsizeImageDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigGeneratedFullsizeImageDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigGeneratedFullsizeImageDto( + enabled: mapValueOfType(json, r'enabled')!, + format: ImageFormat.fromJson(json[r'format'])!, + progressive: mapValueOfType(json, r'progressive') ?? false, + quality: mapValueOfType(json, r'quality')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigGeneratedFullsizeImageDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigGeneratedFullsizeImageDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigGeneratedFullsizeImageDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigGeneratedFullsizeImageDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'format', + 'quality', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_generated_image_dto.dart b/mobile/openapi/lib/model/system_config_generated_image_dto.dart new file mode 100644 index 0000000000..3e8fed2c68 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_generated_image_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigGeneratedImageDto { + /// Returns a new [SystemConfigGeneratedImageDto] instance. + SystemConfigGeneratedImageDto({ + required this.format, + this.progressive = false, + required this.quality, + required this.size, + }); + + /// Image format + ImageFormat format; + + bool progressive; + + /// Quality + /// + /// Minimum value: 1 + /// Maximum value: 100 + int quality; + + /// Size + /// + /// Minimum value: 1 + int size; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigGeneratedImageDto && + other.format == format && + other.progressive == progressive && + other.quality == quality && + other.size == size; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (format.hashCode) + + (progressive.hashCode) + + (quality.hashCode) + + (size.hashCode); + + @override + String toString() => 'SystemConfigGeneratedImageDto[format=$format, progressive=$progressive, quality=$quality, size=$size]'; + + Map toJson() { + final json = {}; + json[r'format'] = this.format; + json[r'progressive'] = this.progressive; + json[r'quality'] = this.quality; + json[r'size'] = this.size; + return json; + } + + /// Returns a new [SystemConfigGeneratedImageDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigGeneratedImageDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigGeneratedImageDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigGeneratedImageDto( + format: ImageFormat.fromJson(json[r'format'])!, + progressive: mapValueOfType(json, r'progressive') ?? false, + quality: mapValueOfType(json, r'quality')!, + size: mapValueOfType(json, r'size')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigGeneratedImageDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigGeneratedImageDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigGeneratedImageDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigGeneratedImageDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'format', + 'quality', + 'size', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_image_dto.dart b/mobile/openapi/lib/model/system_config_image_dto.dart new file mode 100644 index 0000000000..217a666a67 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_image_dto.dart @@ -0,0 +1,133 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigImageDto { + /// Returns a new [SystemConfigImageDto] instance. + SystemConfigImageDto({ + required this.colorspace, + required this.extractEmbedded, + required this.fullsize, + required this.preview, + required this.thumbnail, + }); + + /// Colorspace + Colorspace colorspace; + + /// Extract embedded + bool extractEmbedded; + + SystemConfigGeneratedFullsizeImageDto fullsize; + + SystemConfigGeneratedImageDto preview; + + SystemConfigGeneratedImageDto thumbnail; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigImageDto && + other.colorspace == colorspace && + other.extractEmbedded == extractEmbedded && + other.fullsize == fullsize && + other.preview == preview && + other.thumbnail == thumbnail; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (colorspace.hashCode) + + (extractEmbedded.hashCode) + + (fullsize.hashCode) + + (preview.hashCode) + + (thumbnail.hashCode); + + @override + String toString() => 'SystemConfigImageDto[colorspace=$colorspace, extractEmbedded=$extractEmbedded, fullsize=$fullsize, preview=$preview, thumbnail=$thumbnail]'; + + Map toJson() { + final json = {}; + json[r'colorspace'] = this.colorspace; + json[r'extractEmbedded'] = this.extractEmbedded; + json[r'fullsize'] = this.fullsize; + json[r'preview'] = this.preview; + json[r'thumbnail'] = this.thumbnail; + return json; + } + + /// Returns a new [SystemConfigImageDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigImageDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigImageDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigImageDto( + colorspace: Colorspace.fromJson(json[r'colorspace'])!, + extractEmbedded: mapValueOfType(json, r'extractEmbedded')!, + fullsize: SystemConfigGeneratedFullsizeImageDto.fromJson(json[r'fullsize'])!, + preview: SystemConfigGeneratedImageDto.fromJson(json[r'preview'])!, + thumbnail: SystemConfigGeneratedImageDto.fromJson(json[r'thumbnail'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigImageDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigImageDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigImageDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigImageDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'colorspace', + 'extractEmbedded', + 'fullsize', + 'preview', + 'thumbnail', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_job_dto.dart b/mobile/openapi/lib/model/system_config_job_dto.dart new file mode 100644 index 0000000000..d54db6809f --- /dev/null +++ b/mobile/openapi/lib/model/system_config_job_dto.dart @@ -0,0 +1,203 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigJobDto { + /// Returns a new [SystemConfigJobDto] instance. + SystemConfigJobDto({ + required this.backgroundTask, + required this.editor, + required this.faceDetection, + required this.library_, + required this.metadataExtraction, + required this.migration, + required this.notifications, + required this.ocr, + required this.search, + required this.sidecar, + required this.smartSearch, + required this.thumbnailGeneration, + required this.videoConversion, + required this.workflow, + }); + + JobSettingsDto backgroundTask; + + JobSettingsDto editor; + + JobSettingsDto faceDetection; + + JobSettingsDto library_; + + JobSettingsDto metadataExtraction; + + JobSettingsDto migration; + + JobSettingsDto notifications; + + JobSettingsDto ocr; + + JobSettingsDto search; + + JobSettingsDto sidecar; + + JobSettingsDto smartSearch; + + JobSettingsDto thumbnailGeneration; + + JobSettingsDto videoConversion; + + JobSettingsDto workflow; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigJobDto && + other.backgroundTask == backgroundTask && + other.editor == editor && + other.faceDetection == faceDetection && + other.library_ == library_ && + other.metadataExtraction == metadataExtraction && + other.migration == migration && + other.notifications == notifications && + other.ocr == ocr && + other.search == search && + other.sidecar == sidecar && + other.smartSearch == smartSearch && + other.thumbnailGeneration == thumbnailGeneration && + other.videoConversion == videoConversion && + other.workflow == workflow; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (backgroundTask.hashCode) + + (editor.hashCode) + + (faceDetection.hashCode) + + (library_.hashCode) + + (metadataExtraction.hashCode) + + (migration.hashCode) + + (notifications.hashCode) + + (ocr.hashCode) + + (search.hashCode) + + (sidecar.hashCode) + + (smartSearch.hashCode) + + (thumbnailGeneration.hashCode) + + (videoConversion.hashCode) + + (workflow.hashCode); + + @override + String toString() => 'SystemConfigJobDto[backgroundTask=$backgroundTask, editor=$editor, faceDetection=$faceDetection, library_=$library_, metadataExtraction=$metadataExtraction, migration=$migration, notifications=$notifications, ocr=$ocr, search=$search, sidecar=$sidecar, smartSearch=$smartSearch, thumbnailGeneration=$thumbnailGeneration, videoConversion=$videoConversion, workflow=$workflow]'; + + Map toJson() { + final json = {}; + json[r'backgroundTask'] = this.backgroundTask; + json[r'editor'] = this.editor; + json[r'faceDetection'] = this.faceDetection; + json[r'library'] = this.library_; + json[r'metadataExtraction'] = this.metadataExtraction; + json[r'migration'] = this.migration; + json[r'notifications'] = this.notifications; + json[r'ocr'] = this.ocr; + json[r'search'] = this.search; + json[r'sidecar'] = this.sidecar; + json[r'smartSearch'] = this.smartSearch; + json[r'thumbnailGeneration'] = this.thumbnailGeneration; + json[r'videoConversion'] = this.videoConversion; + json[r'workflow'] = this.workflow; + return json; + } + + /// Returns a new [SystemConfigJobDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigJobDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigJobDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigJobDto( + backgroundTask: JobSettingsDto.fromJson(json[r'backgroundTask'])!, + editor: JobSettingsDto.fromJson(json[r'editor'])!, + faceDetection: JobSettingsDto.fromJson(json[r'faceDetection'])!, + library_: JobSettingsDto.fromJson(json[r'library'])!, + metadataExtraction: JobSettingsDto.fromJson(json[r'metadataExtraction'])!, + migration: JobSettingsDto.fromJson(json[r'migration'])!, + notifications: JobSettingsDto.fromJson(json[r'notifications'])!, + ocr: JobSettingsDto.fromJson(json[r'ocr'])!, + search: JobSettingsDto.fromJson(json[r'search'])!, + sidecar: JobSettingsDto.fromJson(json[r'sidecar'])!, + smartSearch: JobSettingsDto.fromJson(json[r'smartSearch'])!, + thumbnailGeneration: JobSettingsDto.fromJson(json[r'thumbnailGeneration'])!, + videoConversion: JobSettingsDto.fromJson(json[r'videoConversion'])!, + workflow: JobSettingsDto.fromJson(json[r'workflow'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigJobDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigJobDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigJobDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigJobDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'backgroundTask', + 'editor', + 'faceDetection', + 'library', + 'metadataExtraction', + 'migration', + 'notifications', + 'ocr', + 'search', + 'sidecar', + 'smartSearch', + 'thumbnailGeneration', + 'videoConversion', + 'workflow', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_library_dto.dart b/mobile/openapi/lib/model/system_config_library_dto.dart new file mode 100644 index 0000000000..e728b0bf20 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_library_dto.dart @@ -0,0 +1,107 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigLibraryDto { + /// Returns a new [SystemConfigLibraryDto] instance. + SystemConfigLibraryDto({ + required this.scan, + required this.watch, + }); + + SystemConfigLibraryScanDto scan; + + SystemConfigLibraryWatchDto watch; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigLibraryDto && + other.scan == scan && + other.watch == watch; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (scan.hashCode) + + (watch.hashCode); + + @override + String toString() => 'SystemConfigLibraryDto[scan=$scan, watch=$watch]'; + + Map toJson() { + final json = {}; + json[r'scan'] = this.scan; + json[r'watch'] = this.watch; + return json; + } + + /// Returns a new [SystemConfigLibraryDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigLibraryDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigLibraryDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigLibraryDto( + scan: SystemConfigLibraryScanDto.fromJson(json[r'scan'])!, + watch: SystemConfigLibraryWatchDto.fromJson(json[r'watch'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigLibraryDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigLibraryDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigLibraryDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigLibraryDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'scan', + 'watch', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_library_scan_dto.dart b/mobile/openapi/lib/model/system_config_library_scan_dto.dart new file mode 100644 index 0000000000..28ea603c2a --- /dev/null +++ b/mobile/openapi/lib/model/system_config_library_scan_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigLibraryScanDto { + /// Returns a new [SystemConfigLibraryScanDto] instance. + SystemConfigLibraryScanDto({ + required this.cronExpression, + required this.enabled, + }); + + String cronExpression; + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigLibraryScanDto && + other.cronExpression == cronExpression && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (cronExpression.hashCode) + + (enabled.hashCode); + + @override + String toString() => 'SystemConfigLibraryScanDto[cronExpression=$cronExpression, enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'cronExpression'] = this.cronExpression; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigLibraryScanDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigLibraryScanDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigLibraryScanDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigLibraryScanDto( + cronExpression: mapValueOfType(json, r'cronExpression')!, + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigLibraryScanDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigLibraryScanDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigLibraryScanDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigLibraryScanDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'cronExpression', + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_library_watch_dto.dart b/mobile/openapi/lib/model/system_config_library_watch_dto.dart new file mode 100644 index 0000000000..b4f171bd25 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_library_watch_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigLibraryWatchDto { + /// Returns a new [SystemConfigLibraryWatchDto] instance. + SystemConfigLibraryWatchDto({ + required this.enabled, + }); + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigLibraryWatchDto && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'SystemConfigLibraryWatchDto[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigLibraryWatchDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigLibraryWatchDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigLibraryWatchDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigLibraryWatchDto( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigLibraryWatchDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigLibraryWatchDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigLibraryWatchDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigLibraryWatchDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_logging_dto.dart b/mobile/openapi/lib/model/system_config_logging_dto.dart new file mode 100644 index 0000000000..54278893db --- /dev/null +++ b/mobile/openapi/lib/model/system_config_logging_dto.dart @@ -0,0 +1,108 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigLoggingDto { + /// Returns a new [SystemConfigLoggingDto] instance. + SystemConfigLoggingDto({ + required this.enabled, + required this.level, + }); + + /// Enabled + bool enabled; + + LogLevel level; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigLoggingDto && + other.enabled == enabled && + other.level == level; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (level.hashCode); + + @override + String toString() => 'SystemConfigLoggingDto[enabled=$enabled, level=$level]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'level'] = this.level; + return json; + } + + /// Returns a new [SystemConfigLoggingDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigLoggingDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigLoggingDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigLoggingDto( + enabled: mapValueOfType(json, r'enabled')!, + level: LogLevel.fromJson(json[r'level'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigLoggingDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigLoggingDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigLoggingDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigLoggingDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'level', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_machine_learning_dto.dart b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart new file mode 100644 index 0000000000..2a0f1ffbc6 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_machine_learning_dto.dart @@ -0,0 +1,150 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigMachineLearningDto { + /// Returns a new [SystemConfigMachineLearningDto] instance. + SystemConfigMachineLearningDto({ + required this.availabilityChecks, + required this.clip, + required this.duplicateDetection, + required this.enabled, + required this.facialRecognition, + required this.ocr, + this.urls = const [], + }); + + MachineLearningAvailabilityChecksDto availabilityChecks; + + CLIPConfig clip; + + DuplicateDetectionConfig duplicateDetection; + + /// Enabled + bool enabled; + + FacialRecognitionConfig facialRecognition; + + OcrConfig ocr; + + List urls; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigMachineLearningDto && + other.availabilityChecks == availabilityChecks && + other.clip == clip && + other.duplicateDetection == duplicateDetection && + other.enabled == enabled && + other.facialRecognition == facialRecognition && + other.ocr == ocr && + _deepEquality.equals(other.urls, urls); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (availabilityChecks.hashCode) + + (clip.hashCode) + + (duplicateDetection.hashCode) + + (enabled.hashCode) + + (facialRecognition.hashCode) + + (ocr.hashCode) + + (urls.hashCode); + + @override + String toString() => 'SystemConfigMachineLearningDto[availabilityChecks=$availabilityChecks, clip=$clip, duplicateDetection=$duplicateDetection, enabled=$enabled, facialRecognition=$facialRecognition, ocr=$ocr, urls=$urls]'; + + Map toJson() { + final json = {}; + json[r'availabilityChecks'] = this.availabilityChecks; + json[r'clip'] = this.clip; + json[r'duplicateDetection'] = this.duplicateDetection; + json[r'enabled'] = this.enabled; + json[r'facialRecognition'] = this.facialRecognition; + json[r'ocr'] = this.ocr; + json[r'urls'] = this.urls; + return json; + } + + /// Returns a new [SystemConfigMachineLearningDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigMachineLearningDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigMachineLearningDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigMachineLearningDto( + availabilityChecks: MachineLearningAvailabilityChecksDto.fromJson(json[r'availabilityChecks'])!, + clip: CLIPConfig.fromJson(json[r'clip'])!, + duplicateDetection: DuplicateDetectionConfig.fromJson(json[r'duplicateDetection'])!, + enabled: mapValueOfType(json, r'enabled')!, + facialRecognition: FacialRecognitionConfig.fromJson(json[r'facialRecognition'])!, + ocr: OcrConfig.fromJson(json[r'ocr'])!, + urls: json[r'urls'] is Iterable + ? (json[r'urls'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigMachineLearningDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigMachineLearningDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigMachineLearningDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigMachineLearningDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'availabilityChecks', + 'clip', + 'duplicateDetection', + 'enabled', + 'facialRecognition', + 'ocr', + 'urls', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_map_dto.dart b/mobile/openapi/lib/model/system_config_map_dto.dart new file mode 100644 index 0000000000..109babd374 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_map_dto.dart @@ -0,0 +1,116 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigMapDto { + /// Returns a new [SystemConfigMapDto] instance. + SystemConfigMapDto({ + required this.darkStyle, + required this.enabled, + required this.lightStyle, + }); + + String darkStyle; + + /// Enabled + bool enabled; + + String lightStyle; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigMapDto && + other.darkStyle == darkStyle && + other.enabled == enabled && + other.lightStyle == lightStyle; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (darkStyle.hashCode) + + (enabled.hashCode) + + (lightStyle.hashCode); + + @override + String toString() => 'SystemConfigMapDto[darkStyle=$darkStyle, enabled=$enabled, lightStyle=$lightStyle]'; + + Map toJson() { + final json = {}; + json[r'darkStyle'] = this.darkStyle; + json[r'enabled'] = this.enabled; + json[r'lightStyle'] = this.lightStyle; + return json; + } + + /// Returns a new [SystemConfigMapDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigMapDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigMapDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigMapDto( + darkStyle: mapValueOfType(json, r'darkStyle')!, + enabled: mapValueOfType(json, r'enabled')!, + lightStyle: mapValueOfType(json, r'lightStyle')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigMapDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigMapDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigMapDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigMapDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'darkStyle', + 'enabled', + 'lightStyle', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_metadata_dto.dart b/mobile/openapi/lib/model/system_config_metadata_dto.dart new file mode 100644 index 0000000000..3c32fc551d --- /dev/null +++ b/mobile/openapi/lib/model/system_config_metadata_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigMetadataDto { + /// Returns a new [SystemConfigMetadataDto] instance. + SystemConfigMetadataDto({ + required this.faces, + }); + + SystemConfigFacesDto faces; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigMetadataDto && + other.faces == faces; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (faces.hashCode); + + @override + String toString() => 'SystemConfigMetadataDto[faces=$faces]'; + + Map toJson() { + final json = {}; + json[r'faces'] = this.faces; + return json; + } + + /// Returns a new [SystemConfigMetadataDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigMetadataDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigMetadataDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigMetadataDto( + faces: SystemConfigFacesDto.fromJson(json[r'faces'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigMetadataDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigMetadataDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigMetadataDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigMetadataDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'faces', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_new_version_check_dto.dart b/mobile/openapi/lib/model/system_config_new_version_check_dto.dart new file mode 100644 index 0000000000..ec2b400dfd --- /dev/null +++ b/mobile/openapi/lib/model/system_config_new_version_check_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigNewVersionCheckDto { + /// Returns a new [SystemConfigNewVersionCheckDto] instance. + SystemConfigNewVersionCheckDto({ + required this.enabled, + }); + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigNewVersionCheckDto && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'SystemConfigNewVersionCheckDto[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigNewVersionCheckDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigNewVersionCheckDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigNewVersionCheckDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigNewVersionCheckDto( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigNewVersionCheckDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigNewVersionCheckDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigNewVersionCheckDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigNewVersionCheckDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart b/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart new file mode 100644 index 0000000000..cfb18b181e --- /dev/null +++ b/mobile/openapi/lib/model/system_config_nightly_tasks_dto.dart @@ -0,0 +1,144 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigNightlyTasksDto { + /// Returns a new [SystemConfigNightlyTasksDto] instance. + SystemConfigNightlyTasksDto({ + required this.clusterNewFaces, + required this.databaseCleanup, + required this.generateMemories, + required this.missingThumbnails, + required this.startTime, + required this.syncQuotaUsage, + }); + + /// Cluster new faces + bool clusterNewFaces; + + /// Database cleanup + bool databaseCleanup; + + /// Generate memories + bool generateMemories; + + /// Missing thumbnails + bool missingThumbnails; + + String startTime; + + /// Sync quota usage + bool syncQuotaUsage; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigNightlyTasksDto && + other.clusterNewFaces == clusterNewFaces && + other.databaseCleanup == databaseCleanup && + other.generateMemories == generateMemories && + other.missingThumbnails == missingThumbnails && + other.startTime == startTime && + other.syncQuotaUsage == syncQuotaUsage; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (clusterNewFaces.hashCode) + + (databaseCleanup.hashCode) + + (generateMemories.hashCode) + + (missingThumbnails.hashCode) + + (startTime.hashCode) + + (syncQuotaUsage.hashCode); + + @override + String toString() => 'SystemConfigNightlyTasksDto[clusterNewFaces=$clusterNewFaces, databaseCleanup=$databaseCleanup, generateMemories=$generateMemories, missingThumbnails=$missingThumbnails, startTime=$startTime, syncQuotaUsage=$syncQuotaUsage]'; + + Map toJson() { + final json = {}; + json[r'clusterNewFaces'] = this.clusterNewFaces; + json[r'databaseCleanup'] = this.databaseCleanup; + json[r'generateMemories'] = this.generateMemories; + json[r'missingThumbnails'] = this.missingThumbnails; + json[r'startTime'] = this.startTime; + json[r'syncQuotaUsage'] = this.syncQuotaUsage; + return json; + } + + /// Returns a new [SystemConfigNightlyTasksDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigNightlyTasksDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigNightlyTasksDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigNightlyTasksDto( + clusterNewFaces: mapValueOfType(json, r'clusterNewFaces')!, + databaseCleanup: mapValueOfType(json, r'databaseCleanup')!, + generateMemories: mapValueOfType(json, r'generateMemories')!, + missingThumbnails: mapValueOfType(json, r'missingThumbnails')!, + startTime: mapValueOfType(json, r'startTime')!, + syncQuotaUsage: mapValueOfType(json, r'syncQuotaUsage')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigNightlyTasksDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigNightlyTasksDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigNightlyTasksDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigNightlyTasksDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'clusterNewFaces', + 'databaseCleanup', + 'generateMemories', + 'missingThumbnails', + 'startTime', + 'syncQuotaUsage', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_notifications_dto.dart b/mobile/openapi/lib/model/system_config_notifications_dto.dart new file mode 100644 index 0000000000..35d3d31833 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_notifications_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigNotificationsDto { + /// Returns a new [SystemConfigNotificationsDto] instance. + SystemConfigNotificationsDto({ + required this.smtp, + }); + + SystemConfigSmtpDto smtp; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigNotificationsDto && + other.smtp == smtp; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (smtp.hashCode); + + @override + String toString() => 'SystemConfigNotificationsDto[smtp=$smtp]'; + + Map toJson() { + final json = {}; + json[r'smtp'] = this.smtp; + return json; + } + + /// Returns a new [SystemConfigNotificationsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigNotificationsDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigNotificationsDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigNotificationsDto( + smtp: SystemConfigSmtpDto.fromJson(json[r'smtp'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigNotificationsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigNotificationsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigNotificationsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigNotificationsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'smtp', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_o_auth_dto.dart b/mobile/openapi/lib/model/system_config_o_auth_dto.dart new file mode 100644 index 0000000000..82195e498b --- /dev/null +++ b/mobile/openapi/lib/model/system_config_o_auth_dto.dart @@ -0,0 +1,260 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigOAuthDto { + /// Returns a new [SystemConfigOAuthDto] instance. + SystemConfigOAuthDto({ + required this.autoLaunch, + required this.autoRegister, + required this.buttonText, + required this.clientId, + required this.clientSecret, + required this.defaultStorageQuota, + required this.enabled, + required this.issuerUrl, + required this.mobileOverrideEnabled, + required this.mobileRedirectUri, + required this.profileSigningAlgorithm, + required this.roleClaim, + required this.scope, + required this.signingAlgorithm, + required this.storageLabelClaim, + required this.storageQuotaClaim, + required this.timeout, + required this.tokenEndpointAuthMethod, + }); + + /// Auto launch + bool autoLaunch; + + /// Auto register + bool autoRegister; + + /// Button text + String buttonText; + + /// Client ID + String clientId; + + /// Client secret + String clientSecret; + + /// Default storage quota + /// + /// Minimum value: 0 + int? defaultStorageQuota; + + /// Enabled + bool enabled; + + /// Issuer URL + String issuerUrl; + + /// Mobile override enabled + bool mobileOverrideEnabled; + + /// Mobile redirect URI + String mobileRedirectUri; + + /// Profile signing algorithm + String profileSigningAlgorithm; + + /// Role claim + String roleClaim; + + /// Scope + String scope; + + String signingAlgorithm; + + /// Storage label claim + String storageLabelClaim; + + /// Storage quota claim + String storageQuotaClaim; + + /// Timeout + /// + /// Minimum value: 1 + int timeout; + + /// Token endpoint auth method + OAuthTokenEndpointAuthMethod tokenEndpointAuthMethod; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigOAuthDto && + other.autoLaunch == autoLaunch && + other.autoRegister == autoRegister && + other.buttonText == buttonText && + other.clientId == clientId && + other.clientSecret == clientSecret && + other.defaultStorageQuota == defaultStorageQuota && + other.enabled == enabled && + other.issuerUrl == issuerUrl && + other.mobileOverrideEnabled == mobileOverrideEnabled && + other.mobileRedirectUri == mobileRedirectUri && + other.profileSigningAlgorithm == profileSigningAlgorithm && + other.roleClaim == roleClaim && + other.scope == scope && + other.signingAlgorithm == signingAlgorithm && + other.storageLabelClaim == storageLabelClaim && + other.storageQuotaClaim == storageQuotaClaim && + other.timeout == timeout && + other.tokenEndpointAuthMethod == tokenEndpointAuthMethod; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (autoLaunch.hashCode) + + (autoRegister.hashCode) + + (buttonText.hashCode) + + (clientId.hashCode) + + (clientSecret.hashCode) + + (defaultStorageQuota == null ? 0 : defaultStorageQuota!.hashCode) + + (enabled.hashCode) + + (issuerUrl.hashCode) + + (mobileOverrideEnabled.hashCode) + + (mobileRedirectUri.hashCode) + + (profileSigningAlgorithm.hashCode) + + (roleClaim.hashCode) + + (scope.hashCode) + + (signingAlgorithm.hashCode) + + (storageLabelClaim.hashCode) + + (storageQuotaClaim.hashCode) + + (timeout.hashCode) + + (tokenEndpointAuthMethod.hashCode); + + @override + String toString() => 'SystemConfigOAuthDto[autoLaunch=$autoLaunch, autoRegister=$autoRegister, buttonText=$buttonText, clientId=$clientId, clientSecret=$clientSecret, defaultStorageQuota=$defaultStorageQuota, enabled=$enabled, issuerUrl=$issuerUrl, mobileOverrideEnabled=$mobileOverrideEnabled, mobileRedirectUri=$mobileRedirectUri, profileSigningAlgorithm=$profileSigningAlgorithm, roleClaim=$roleClaim, scope=$scope, signingAlgorithm=$signingAlgorithm, storageLabelClaim=$storageLabelClaim, storageQuotaClaim=$storageQuotaClaim, timeout=$timeout, tokenEndpointAuthMethod=$tokenEndpointAuthMethod]'; + + Map toJson() { + final json = {}; + json[r'autoLaunch'] = this.autoLaunch; + json[r'autoRegister'] = this.autoRegister; + json[r'buttonText'] = this.buttonText; + json[r'clientId'] = this.clientId; + json[r'clientSecret'] = this.clientSecret; + if (this.defaultStorageQuota != null) { + json[r'defaultStorageQuota'] = this.defaultStorageQuota; + } else { + // json[r'defaultStorageQuota'] = null; + } + json[r'enabled'] = this.enabled; + json[r'issuerUrl'] = this.issuerUrl; + json[r'mobileOverrideEnabled'] = this.mobileOverrideEnabled; + json[r'mobileRedirectUri'] = this.mobileRedirectUri; + json[r'profileSigningAlgorithm'] = this.profileSigningAlgorithm; + json[r'roleClaim'] = this.roleClaim; + json[r'scope'] = this.scope; + json[r'signingAlgorithm'] = this.signingAlgorithm; + json[r'storageLabelClaim'] = this.storageLabelClaim; + json[r'storageQuotaClaim'] = this.storageQuotaClaim; + json[r'timeout'] = this.timeout; + json[r'tokenEndpointAuthMethod'] = this.tokenEndpointAuthMethod; + return json; + } + + /// Returns a new [SystemConfigOAuthDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigOAuthDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigOAuthDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigOAuthDto( + autoLaunch: mapValueOfType(json, r'autoLaunch')!, + autoRegister: mapValueOfType(json, r'autoRegister')!, + buttonText: mapValueOfType(json, r'buttonText')!, + clientId: mapValueOfType(json, r'clientId')!, + clientSecret: mapValueOfType(json, r'clientSecret')!, + defaultStorageQuota: mapValueOfType(json, r'defaultStorageQuota'), + enabled: mapValueOfType(json, r'enabled')!, + issuerUrl: mapValueOfType(json, r'issuerUrl')!, + mobileOverrideEnabled: mapValueOfType(json, r'mobileOverrideEnabled')!, + mobileRedirectUri: mapValueOfType(json, r'mobileRedirectUri')!, + profileSigningAlgorithm: mapValueOfType(json, r'profileSigningAlgorithm')!, + roleClaim: mapValueOfType(json, r'roleClaim')!, + scope: mapValueOfType(json, r'scope')!, + signingAlgorithm: mapValueOfType(json, r'signingAlgorithm')!, + storageLabelClaim: mapValueOfType(json, r'storageLabelClaim')!, + storageQuotaClaim: mapValueOfType(json, r'storageQuotaClaim')!, + timeout: mapValueOfType(json, r'timeout')!, + tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.fromJson(json[r'tokenEndpointAuthMethod'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigOAuthDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigOAuthDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigOAuthDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigOAuthDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'autoLaunch', + 'autoRegister', + 'buttonText', + 'clientId', + 'clientSecret', + 'defaultStorageQuota', + 'enabled', + 'issuerUrl', + 'mobileOverrideEnabled', + 'mobileRedirectUri', + 'profileSigningAlgorithm', + 'roleClaim', + 'scope', + 'signingAlgorithm', + 'storageLabelClaim', + 'storageQuotaClaim', + 'timeout', + 'tokenEndpointAuthMethod', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_password_login_dto.dart b/mobile/openapi/lib/model/system_config_password_login_dto.dart new file mode 100644 index 0000000000..1328a6acaa --- /dev/null +++ b/mobile/openapi/lib/model/system_config_password_login_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigPasswordLoginDto { + /// Returns a new [SystemConfigPasswordLoginDto] instance. + SystemConfigPasswordLoginDto({ + required this.enabled, + }); + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigPasswordLoginDto && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'SystemConfigPasswordLoginDto[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigPasswordLoginDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigPasswordLoginDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigPasswordLoginDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigPasswordLoginDto( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigPasswordLoginDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigPasswordLoginDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigPasswordLoginDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigPasswordLoginDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart b/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart new file mode 100644 index 0000000000..0374e19be1 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_reverse_geocoding_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigReverseGeocodingDto { + /// Returns a new [SystemConfigReverseGeocodingDto] instance. + SystemConfigReverseGeocodingDto({ + required this.enabled, + }); + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigReverseGeocodingDto && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode); + + @override + String toString() => 'SystemConfigReverseGeocodingDto[enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigReverseGeocodingDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigReverseGeocodingDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigReverseGeocodingDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigReverseGeocodingDto( + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigReverseGeocodingDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigReverseGeocodingDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigReverseGeocodingDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigReverseGeocodingDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_server_dto.dart b/mobile/openapi/lib/model/system_config_server_dto.dart new file mode 100644 index 0000000000..200f75f7c6 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_server_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigServerDto { + /// Returns a new [SystemConfigServerDto] instance. + SystemConfigServerDto({ + required this.externalDomain, + required this.loginPageMessage, + required this.publicUsers, + }); + + /// External domain + String externalDomain; + + /// Login page message + String loginPageMessage; + + /// Public users + bool publicUsers; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigServerDto && + other.externalDomain == externalDomain && + other.loginPageMessage == loginPageMessage && + other.publicUsers == publicUsers; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (externalDomain.hashCode) + + (loginPageMessage.hashCode) + + (publicUsers.hashCode); + + @override + String toString() => 'SystemConfigServerDto[externalDomain=$externalDomain, loginPageMessage=$loginPageMessage, publicUsers=$publicUsers]'; + + Map toJson() { + final json = {}; + json[r'externalDomain'] = this.externalDomain; + json[r'loginPageMessage'] = this.loginPageMessage; + json[r'publicUsers'] = this.publicUsers; + return json; + } + + /// Returns a new [SystemConfigServerDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigServerDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigServerDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigServerDto( + externalDomain: mapValueOfType(json, r'externalDomain')!, + loginPageMessage: mapValueOfType(json, r'loginPageMessage')!, + publicUsers: mapValueOfType(json, r'publicUsers')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigServerDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigServerDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigServerDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigServerDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'externalDomain', + 'loginPageMessage', + 'publicUsers', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_smtp_dto.dart b/mobile/openapi/lib/model/system_config_smtp_dto.dart new file mode 100644 index 0000000000..a3d14cda63 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_smtp_dto.dart @@ -0,0 +1,126 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigSmtpDto { + /// Returns a new [SystemConfigSmtpDto] instance. + SystemConfigSmtpDto({ + required this.enabled, + required this.from, + required this.replyTo, + required this.transport, + }); + + /// Whether SMTP email notifications are enabled + bool enabled; + + /// Email address to send from + String from; + + /// Email address for replies + String replyTo; + + SystemConfigSmtpTransportDto transport; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigSmtpDto && + other.enabled == enabled && + other.from == from && + other.replyTo == replyTo && + other.transport == transport; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (from.hashCode) + + (replyTo.hashCode) + + (transport.hashCode); + + @override + String toString() => 'SystemConfigSmtpDto[enabled=$enabled, from=$from, replyTo=$replyTo, transport=$transport]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'from'] = this.from; + json[r'replyTo'] = this.replyTo; + json[r'transport'] = this.transport; + return json; + } + + /// Returns a new [SystemConfigSmtpDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigSmtpDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigSmtpDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigSmtpDto( + enabled: mapValueOfType(json, r'enabled')!, + from: mapValueOfType(json, r'from')!, + replyTo: mapValueOfType(json, r'replyTo')!, + transport: SystemConfigSmtpTransportDto.fromJson(json[r'transport'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigSmtpDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigSmtpDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigSmtpDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigSmtpDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'from', + 'replyTo', + 'transport', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart new file mode 100644 index 0000000000..9e16e5badf --- /dev/null +++ b/mobile/openapi/lib/model/system_config_smtp_transport_dto.dart @@ -0,0 +1,148 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigSmtpTransportDto { + /// Returns a new [SystemConfigSmtpTransportDto] instance. + SystemConfigSmtpTransportDto({ + required this.host, + required this.ignoreCert, + required this.password, + required this.port, + required this.secure, + required this.username, + }); + + /// SMTP server hostname + String host; + + /// Whether to ignore SSL certificate errors + bool ignoreCert; + + /// SMTP password + String password; + + /// SMTP server port + /// + /// Minimum value: 0 + /// Maximum value: 65535 + num port; + + /// Whether to use secure connection (TLS/SSL) + bool secure; + + /// SMTP username + String username; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigSmtpTransportDto && + other.host == host && + other.ignoreCert == ignoreCert && + other.password == password && + other.port == port && + other.secure == secure && + other.username == username; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (host.hashCode) + + (ignoreCert.hashCode) + + (password.hashCode) + + (port.hashCode) + + (secure.hashCode) + + (username.hashCode); + + @override + String toString() => 'SystemConfigSmtpTransportDto[host=$host, ignoreCert=$ignoreCert, password=$password, port=$port, secure=$secure, username=$username]'; + + Map toJson() { + final json = {}; + json[r'host'] = this.host; + json[r'ignoreCert'] = this.ignoreCert; + json[r'password'] = this.password; + json[r'port'] = this.port; + json[r'secure'] = this.secure; + json[r'username'] = this.username; + return json; + } + + /// Returns a new [SystemConfigSmtpTransportDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigSmtpTransportDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigSmtpTransportDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigSmtpTransportDto( + host: mapValueOfType(json, r'host')!, + ignoreCert: mapValueOfType(json, r'ignoreCert')!, + password: mapValueOfType(json, r'password')!, + port: num.parse('${json[r'port']}'), + secure: mapValueOfType(json, r'secure')!, + username: mapValueOfType(json, r'username')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigSmtpTransportDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigSmtpTransportDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigSmtpTransportDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigSmtpTransportDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'host', + 'ignoreCert', + 'password', + 'port', + 'secure', + 'username', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_storage_template_dto.dart b/mobile/openapi/lib/model/system_config_storage_template_dto.dart new file mode 100644 index 0000000000..f9f37e48ad --- /dev/null +++ b/mobile/openapi/lib/model/system_config_storage_template_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigStorageTemplateDto { + /// Returns a new [SystemConfigStorageTemplateDto] instance. + SystemConfigStorageTemplateDto({ + required this.enabled, + required this.hashVerificationEnabled, + required this.template, + }); + + /// Enabled + bool enabled; + + /// Hash verification enabled + bool hashVerificationEnabled; + + /// Template + String template; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigStorageTemplateDto && + other.enabled == enabled && + other.hashVerificationEnabled == hashVerificationEnabled && + other.template == template; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (hashVerificationEnabled.hashCode) + + (template.hashCode); + + @override + String toString() => 'SystemConfigStorageTemplateDto[enabled=$enabled, hashVerificationEnabled=$hashVerificationEnabled, template=$template]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'hashVerificationEnabled'] = this.hashVerificationEnabled; + json[r'template'] = this.template; + return json; + } + + /// Returns a new [SystemConfigStorageTemplateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigStorageTemplateDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigStorageTemplateDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigStorageTemplateDto( + enabled: mapValueOfType(json, r'enabled')!, + hashVerificationEnabled: mapValueOfType(json, r'hashVerificationEnabled')!, + template: mapValueOfType(json, r'template')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigStorageTemplateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigStorageTemplateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigStorageTemplateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigStorageTemplateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'hashVerificationEnabled', + 'template', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_template_emails_dto.dart b/mobile/openapi/lib/model/system_config_template_emails_dto.dart new file mode 100644 index 0000000000..9db85509f5 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_template_emails_dto.dart @@ -0,0 +1,115 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigTemplateEmailsDto { + /// Returns a new [SystemConfigTemplateEmailsDto] instance. + SystemConfigTemplateEmailsDto({ + required this.albumInviteTemplate, + required this.albumUpdateTemplate, + required this.welcomeTemplate, + }); + + String albumInviteTemplate; + + String albumUpdateTemplate; + + String welcomeTemplate; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigTemplateEmailsDto && + other.albumInviteTemplate == albumInviteTemplate && + other.albumUpdateTemplate == albumUpdateTemplate && + other.welcomeTemplate == welcomeTemplate; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumInviteTemplate.hashCode) + + (albumUpdateTemplate.hashCode) + + (welcomeTemplate.hashCode); + + @override + String toString() => 'SystemConfigTemplateEmailsDto[albumInviteTemplate=$albumInviteTemplate, albumUpdateTemplate=$albumUpdateTemplate, welcomeTemplate=$welcomeTemplate]'; + + Map toJson() { + final json = {}; + json[r'albumInviteTemplate'] = this.albumInviteTemplate; + json[r'albumUpdateTemplate'] = this.albumUpdateTemplate; + json[r'welcomeTemplate'] = this.welcomeTemplate; + return json; + } + + /// Returns a new [SystemConfigTemplateEmailsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigTemplateEmailsDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigTemplateEmailsDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigTemplateEmailsDto( + albumInviteTemplate: mapValueOfType(json, r'albumInviteTemplate')!, + albumUpdateTemplate: mapValueOfType(json, r'albumUpdateTemplate')!, + welcomeTemplate: mapValueOfType(json, r'welcomeTemplate')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigTemplateEmailsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigTemplateEmailsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigTemplateEmailsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigTemplateEmailsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumInviteTemplate', + 'albumUpdateTemplate', + 'welcomeTemplate', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_template_storage_option_dto.dart b/mobile/openapi/lib/model/system_config_template_storage_option_dto.dart new file mode 100644 index 0000000000..6f81513039 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_template_storage_option_dto.dart @@ -0,0 +1,179 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigTemplateStorageOptionDto { + /// Returns a new [SystemConfigTemplateStorageOptionDto] instance. + SystemConfigTemplateStorageOptionDto({ + this.dayOptions = const [], + this.hourOptions = const [], + this.minuteOptions = const [], + this.monthOptions = const [], + this.presetOptions = const [], + this.secondOptions = const [], + this.weekOptions = const [], + this.yearOptions = const [], + }); + + /// Available day format options for storage template + List dayOptions; + + /// Available hour format options for storage template + List hourOptions; + + /// Available minute format options for storage template + List minuteOptions; + + /// Available month format options for storage template + List monthOptions; + + /// Available preset template options + List presetOptions; + + /// Available second format options for storage template + List secondOptions; + + /// Available week format options for storage template + List weekOptions; + + /// Available year format options for storage template + List yearOptions; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigTemplateStorageOptionDto && + _deepEquality.equals(other.dayOptions, dayOptions) && + _deepEquality.equals(other.hourOptions, hourOptions) && + _deepEquality.equals(other.minuteOptions, minuteOptions) && + _deepEquality.equals(other.monthOptions, monthOptions) && + _deepEquality.equals(other.presetOptions, presetOptions) && + _deepEquality.equals(other.secondOptions, secondOptions) && + _deepEquality.equals(other.weekOptions, weekOptions) && + _deepEquality.equals(other.yearOptions, yearOptions); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (dayOptions.hashCode) + + (hourOptions.hashCode) + + (minuteOptions.hashCode) + + (monthOptions.hashCode) + + (presetOptions.hashCode) + + (secondOptions.hashCode) + + (weekOptions.hashCode) + + (yearOptions.hashCode); + + @override + String toString() => 'SystemConfigTemplateStorageOptionDto[dayOptions=$dayOptions, hourOptions=$hourOptions, minuteOptions=$minuteOptions, monthOptions=$monthOptions, presetOptions=$presetOptions, secondOptions=$secondOptions, weekOptions=$weekOptions, yearOptions=$yearOptions]'; + + Map toJson() { + final json = {}; + json[r'dayOptions'] = this.dayOptions; + json[r'hourOptions'] = this.hourOptions; + json[r'minuteOptions'] = this.minuteOptions; + json[r'monthOptions'] = this.monthOptions; + json[r'presetOptions'] = this.presetOptions; + json[r'secondOptions'] = this.secondOptions; + json[r'weekOptions'] = this.weekOptions; + json[r'yearOptions'] = this.yearOptions; + return json; + } + + /// Returns a new [SystemConfigTemplateStorageOptionDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigTemplateStorageOptionDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigTemplateStorageOptionDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigTemplateStorageOptionDto( + dayOptions: json[r'dayOptions'] is Iterable + ? (json[r'dayOptions'] as Iterable).cast().toList(growable: false) + : const [], + hourOptions: json[r'hourOptions'] is Iterable + ? (json[r'hourOptions'] as Iterable).cast().toList(growable: false) + : const [], + minuteOptions: json[r'minuteOptions'] is Iterable + ? (json[r'minuteOptions'] as Iterable).cast().toList(growable: false) + : const [], + monthOptions: json[r'monthOptions'] is Iterable + ? (json[r'monthOptions'] as Iterable).cast().toList(growable: false) + : const [], + presetOptions: json[r'presetOptions'] is Iterable + ? (json[r'presetOptions'] as Iterable).cast().toList(growable: false) + : const [], + secondOptions: json[r'secondOptions'] is Iterable + ? (json[r'secondOptions'] as Iterable).cast().toList(growable: false) + : const [], + weekOptions: json[r'weekOptions'] is Iterable + ? (json[r'weekOptions'] as Iterable).cast().toList(growable: false) + : const [], + yearOptions: json[r'yearOptions'] is Iterable + ? (json[r'yearOptions'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigTemplateStorageOptionDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigTemplateStorageOptionDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigTemplateStorageOptionDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigTemplateStorageOptionDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'dayOptions', + 'hourOptions', + 'minuteOptions', + 'monthOptions', + 'presetOptions', + 'secondOptions', + 'weekOptions', + 'yearOptions', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_templates_dto.dart b/mobile/openapi/lib/model/system_config_templates_dto.dart new file mode 100644 index 0000000000..a5e8834978 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_templates_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigTemplatesDto { + /// Returns a new [SystemConfigTemplatesDto] instance. + SystemConfigTemplatesDto({ + required this.email, + }); + + SystemConfigTemplateEmailsDto email; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigTemplatesDto && + other.email == email; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (email.hashCode); + + @override + String toString() => 'SystemConfigTemplatesDto[email=$email]'; + + Map toJson() { + final json = {}; + json[r'email'] = this.email; + return json; + } + + /// Returns a new [SystemConfigTemplatesDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigTemplatesDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigTemplatesDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigTemplatesDto( + email: SystemConfigTemplateEmailsDto.fromJson(json[r'email'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigTemplatesDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigTemplatesDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigTemplatesDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigTemplatesDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'email', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_theme_dto.dart b/mobile/openapi/lib/model/system_config_theme_dto.dart new file mode 100644 index 0000000000..fca38f71fb --- /dev/null +++ b/mobile/openapi/lib/model/system_config_theme_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigThemeDto { + /// Returns a new [SystemConfigThemeDto] instance. + SystemConfigThemeDto({ + required this.customCss, + }); + + /// Custom CSS for theming + String customCss; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigThemeDto && + other.customCss == customCss; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (customCss.hashCode); + + @override + String toString() => 'SystemConfigThemeDto[customCss=$customCss]'; + + Map toJson() { + final json = {}; + json[r'customCss'] = this.customCss; + return json; + } + + /// Returns a new [SystemConfigThemeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigThemeDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigThemeDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigThemeDto( + customCss: mapValueOfType(json, r'customCss')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigThemeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigThemeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigThemeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigThemeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'customCss', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_trash_dto.dart b/mobile/openapi/lib/model/system_config_trash_dto.dart new file mode 100644 index 0000000000..9bdaef92d3 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_trash_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigTrashDto { + /// Returns a new [SystemConfigTrashDto] instance. + SystemConfigTrashDto({ + required this.days, + required this.enabled, + }); + + /// Days + /// + /// Minimum value: 0 + int days; + + /// Enabled + bool enabled; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigTrashDto && + other.days == days && + other.enabled == enabled; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (days.hashCode) + + (enabled.hashCode); + + @override + String toString() => 'SystemConfigTrashDto[days=$days, enabled=$enabled]'; + + Map toJson() { + final json = {}; + json[r'days'] = this.days; + json[r'enabled'] = this.enabled; + return json; + } + + /// Returns a new [SystemConfigTrashDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigTrashDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigTrashDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigTrashDto( + days: mapValueOfType(json, r'days')!, + enabled: mapValueOfType(json, r'enabled')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigTrashDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigTrashDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigTrashDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigTrashDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'days', + 'enabled', + }; +} + diff --git a/mobile/openapi/lib/model/system_config_user_dto.dart b/mobile/openapi/lib/model/system_config_user_dto.dart new file mode 100644 index 0000000000..a7313560e6 --- /dev/null +++ b/mobile/openapi/lib/model/system_config_user_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class SystemConfigUserDto { + /// Returns a new [SystemConfigUserDto] instance. + SystemConfigUserDto({ + required this.deleteDelay, + }); + + /// Delete delay + /// + /// Minimum value: 1 + int deleteDelay; + + @override + bool operator ==(Object other) => identical(this, other) || other is SystemConfigUserDto && + other.deleteDelay == deleteDelay; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deleteDelay.hashCode); + + @override + String toString() => 'SystemConfigUserDto[deleteDelay=$deleteDelay]'; + + Map toJson() { + final json = {}; + json[r'deleteDelay'] = this.deleteDelay; + return json; + } + + /// Returns a new [SystemConfigUserDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SystemConfigUserDto? fromJson(dynamic value) { + upgradeDto(value, "SystemConfigUserDto"); + if (value is Map) { + final json = value.cast(); + + return SystemConfigUserDto( + deleteDelay: mapValueOfType(json, r'deleteDelay')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SystemConfigUserDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SystemConfigUserDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SystemConfigUserDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SystemConfigUserDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deleteDelay', + }; +} + diff --git a/mobile/openapi/lib/model/tag_bulk_assets_dto.dart b/mobile/openapi/lib/model/tag_bulk_assets_dto.dart new file mode 100644 index 0000000000..16abc3bcdc --- /dev/null +++ b/mobile/openapi/lib/model/tag_bulk_assets_dto.dart @@ -0,0 +1,113 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagBulkAssetsDto { + /// Returns a new [TagBulkAssetsDto] instance. + TagBulkAssetsDto({ + this.assetIds = const [], + this.tagIds = const [], + }); + + /// Asset IDs + List assetIds; + + /// Tag IDs + List tagIds; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagBulkAssetsDto && + _deepEquality.equals(other.assetIds, assetIds) && + _deepEquality.equals(other.tagIds, tagIds); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (assetIds.hashCode) + + (tagIds.hashCode); + + @override + String toString() => 'TagBulkAssetsDto[assetIds=$assetIds, tagIds=$tagIds]'; + + Map toJson() { + final json = {}; + json[r'assetIds'] = this.assetIds; + json[r'tagIds'] = this.tagIds; + return json; + } + + /// Returns a new [TagBulkAssetsDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagBulkAssetsDto? fromJson(dynamic value) { + upgradeDto(value, "TagBulkAssetsDto"); + if (value is Map) { + final json = value.cast(); + + return TagBulkAssetsDto( + assetIds: json[r'assetIds'] is Iterable + ? (json[r'assetIds'] as Iterable).cast().toList(growable: false) + : const [], + tagIds: json[r'tagIds'] is Iterable + ? (json[r'tagIds'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagBulkAssetsDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagBulkAssetsDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagBulkAssetsDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagBulkAssetsDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'assetIds', + 'tagIds', + }; +} + diff --git a/mobile/openapi/lib/model/tag_bulk_assets_response_dto.dart b/mobile/openapi/lib/model/tag_bulk_assets_response_dto.dart new file mode 100644 index 0000000000..5566846e3c --- /dev/null +++ b/mobile/openapi/lib/model/tag_bulk_assets_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagBulkAssetsResponseDto { + /// Returns a new [TagBulkAssetsResponseDto] instance. + TagBulkAssetsResponseDto({ + required this.count, + }); + + /// Number of assets tagged + int count; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagBulkAssetsResponseDto && + other.count == count; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode); + + @override + String toString() => 'TagBulkAssetsResponseDto[count=$count]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + return json; + } + + /// Returns a new [TagBulkAssetsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagBulkAssetsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TagBulkAssetsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TagBulkAssetsResponseDto( + count: mapValueOfType(json, r'count')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagBulkAssetsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagBulkAssetsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagBulkAssetsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagBulkAssetsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + }; +} + diff --git a/mobile/openapi/lib/model/tag_create_dto.dart b/mobile/openapi/lib/model/tag_create_dto.dart new file mode 100644 index 0000000000..fd6a10163c --- /dev/null +++ b/mobile/openapi/lib/model/tag_create_dto.dart @@ -0,0 +1,130 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagCreateDto { + /// Returns a new [TagCreateDto] instance. + TagCreateDto({ + this.color, + required this.name, + this.parentId, + }); + + /// Tag color (hex) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? color; + + /// Tag name + String name; + + /// Parent tag ID + String? parentId; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagCreateDto && + other.color == color && + other.name == name && + other.parentId == parentId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (color == null ? 0 : color!.hashCode) + + (name.hashCode) + + (parentId == null ? 0 : parentId!.hashCode); + + @override + String toString() => 'TagCreateDto[color=$color, name=$name, parentId=$parentId]'; + + Map toJson() { + final json = {}; + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + json[r'name'] = this.name; + if (this.parentId != null) { + json[r'parentId'] = this.parentId; + } else { + // json[r'parentId'] = null; + } + return json; + } + + /// Returns a new [TagCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagCreateDto? fromJson(dynamic value) { + upgradeDto(value, "TagCreateDto"); + if (value is Map) { + final json = value.cast(); + + return TagCreateDto( + color: mapValueOfType(json, r'color'), + name: mapValueOfType(json, r'name')!, + parentId: mapValueOfType(json, r'parentId'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart new file mode 100644 index 0000000000..9a71912153 --- /dev/null +++ b/mobile/openapi/lib/model/tag_response_dto.dart @@ -0,0 +1,172 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagResponseDto { + /// Returns a new [TagResponseDto] instance. + TagResponseDto({ + this.color, + required this.createdAt, + required this.id, + required this.name, + this.parentId, + required this.updatedAt, + required this.value, + }); + + /// Tag color (hex) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? color; + + /// Creation date + DateTime createdAt; + + /// Tag ID + String id; + + /// Tag name + String name; + + /// Parent tag ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? parentId; + + /// Last update date + DateTime updatedAt; + + /// Tag value (full path) + String value; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagResponseDto && + other.color == color && + other.createdAt == createdAt && + other.id == id && + other.name == name && + other.parentId == parentId && + other.updatedAt == updatedAt && + other.value == value; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (color == null ? 0 : color!.hashCode) + + (createdAt.hashCode) + + (id.hashCode) + + (name.hashCode) + + (parentId == null ? 0 : parentId!.hashCode) + + (updatedAt.hashCode) + + (value.hashCode); + + @override + String toString() => 'TagResponseDto[color=$color, createdAt=$createdAt, id=$id, name=$name, parentId=$parentId, updatedAt=$updatedAt, value=$value]'; + + Map toJson() { + final json = {}; + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + json[r'id'] = this.id; + json[r'name'] = this.name; + if (this.parentId != null) { + json[r'parentId'] = this.parentId; + } else { + // json[r'parentId'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + json[r'value'] = this.value; + return json; + } + + /// Returns a new [TagResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TagResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TagResponseDto( + color: mapValueOfType(json, r'color'), + createdAt: mapDateTime(json, r'createdAt', r'')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + parentId: mapValueOfType(json, r'parentId'), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + value: mapValueOfType(json, r'value')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'createdAt', + 'id', + 'name', + 'updatedAt', + 'value', + }; +} + diff --git a/mobile/openapi/lib/model/tag_update_dto.dart b/mobile/openapi/lib/model/tag_update_dto.dart new file mode 100644 index 0000000000..98cb6af523 --- /dev/null +++ b/mobile/openapi/lib/model/tag_update_dto.dart @@ -0,0 +1,103 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagUpdateDto { + /// Returns a new [TagUpdateDto] instance. + TagUpdateDto({ + this.color, + }); + + /// Tag color (hex) + String? color; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagUpdateDto && + other.color == color; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (color == null ? 0 : color!.hashCode); + + @override + String toString() => 'TagUpdateDto[color=$color]'; + + Map toJson() { + final json = {}; + if (this.color != null) { + json[r'color'] = this.color; + } else { + // json[r'color'] = null; + } + return json; + } + + /// Returns a new [TagUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "TagUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return TagUpdateDto( + color: mapValueOfType(json, r'color'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/tag_upsert_dto.dart b/mobile/openapi/lib/model/tag_upsert_dto.dart new file mode 100644 index 0000000000..3581ef1e8f --- /dev/null +++ b/mobile/openapi/lib/model/tag_upsert_dto.dart @@ -0,0 +1,102 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagUpsertDto { + /// Returns a new [TagUpsertDto] instance. + TagUpsertDto({ + this.tags = const [], + }); + + /// Tag names to upsert + List tags; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagUpsertDto && + _deepEquality.equals(other.tags, tags); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (tags.hashCode); + + @override + String toString() => 'TagUpsertDto[tags=$tags]'; + + Map toJson() { + final json = {}; + json[r'tags'] = this.tags; + return json; + } + + /// Returns a new [TagUpsertDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagUpsertDto? fromJson(dynamic value) { + upgradeDto(value, "TagUpsertDto"); + if (value is Map) { + final json = value.cast(); + + return TagUpsertDto( + tags: json[r'tags'] is Iterable + ? (json[r'tags'] as Iterable).cast().toList(growable: false) + : const [], + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagUpsertDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagUpsertDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagUpsertDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagUpsertDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'tags', + }; +} + diff --git a/mobile/openapi/lib/model/tags_response.dart b/mobile/openapi/lib/model/tags_response.dart new file mode 100644 index 0000000000..1e4a4bd109 --- /dev/null +++ b/mobile/openapi/lib/model/tags_response.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagsResponse { + /// Returns a new [TagsResponse] instance. + TagsResponse({ + this.enabled = true, + this.sidebarWeb = true, + }); + + /// Whether tags are enabled + bool enabled; + + /// Whether tags appear in web sidebar + bool sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagsResponse && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled.hashCode) + + (sidebarWeb.hashCode); + + @override + String toString() => 'TagsResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + json[r'enabled'] = this.enabled; + json[r'sidebarWeb'] = this.sidebarWeb; + return json; + } + + /// Returns a new [TagsResponse] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagsResponse? fromJson(dynamic value) { + upgradeDto(value, "TagsResponse"); + if (value is Map) { + final json = value.cast(); + + return TagsResponse( + enabled: mapValueOfType(json, r'enabled')!, + sidebarWeb: mapValueOfType(json, r'sidebarWeb')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagsResponse.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagsResponse.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagsResponse-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagsResponse.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'enabled', + 'sidebarWeb', + }; +} + diff --git a/mobile/openapi/lib/model/tags_update.dart b/mobile/openapi/lib/model/tags_update.dart new file mode 100644 index 0000000000..e42357e3d4 --- /dev/null +++ b/mobile/openapi/lib/model/tags_update.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TagsUpdate { + /// Returns a new [TagsUpdate] instance. + TagsUpdate({ + this.enabled, + this.sidebarWeb, + }); + + /// Whether tags are enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Whether tags appear in web sidebar + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? sidebarWeb; + + @override + bool operator ==(Object other) => identical(this, other) || other is TagsUpdate && + other.enabled == enabled && + other.sidebarWeb == sidebarWeb; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (enabled == null ? 0 : enabled!.hashCode) + + (sidebarWeb == null ? 0 : sidebarWeb!.hashCode); + + @override + String toString() => 'TagsUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]'; + + Map toJson() { + final json = {}; + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + if (this.sidebarWeb != null) { + json[r'sidebarWeb'] = this.sidebarWeb; + } else { + // json[r'sidebarWeb'] = null; + } + return json; + } + + /// Returns a new [TagsUpdate] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TagsUpdate? fromJson(dynamic value) { + upgradeDto(value, "TagsUpdate"); + if (value is Map) { + final json = value.cast(); + + return TagsUpdate( + enabled: mapValueOfType(json, r'enabled'), + sidebarWeb: mapValueOfType(json, r'sidebarWeb'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TagsUpdate.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TagsUpdate.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TagsUpdate-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TagsUpdate.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/template_dto.dart b/mobile/openapi/lib/model/template_dto.dart new file mode 100644 index 0000000000..b1eab848ed --- /dev/null +++ b/mobile/openapi/lib/model/template_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TemplateDto { + /// Returns a new [TemplateDto] instance. + TemplateDto({ + required this.template, + }); + + /// Template name + String template; + + @override + bool operator ==(Object other) => identical(this, other) || other is TemplateDto && + other.template == template; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (template.hashCode); + + @override + String toString() => 'TemplateDto[template=$template]'; + + Map toJson() { + final json = {}; + json[r'template'] = this.template; + return json; + } + + /// Returns a new [TemplateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TemplateDto? fromJson(dynamic value) { + upgradeDto(value, "TemplateDto"); + if (value is Map) { + final json = value.cast(); + + return TemplateDto( + template: mapValueOfType(json, r'template')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TemplateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TemplateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TemplateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TemplateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'template', + }; +} + diff --git a/mobile/openapi/lib/model/template_response_dto.dart b/mobile/openapi/lib/model/template_response_dto.dart new file mode 100644 index 0000000000..f19c1eae7d --- /dev/null +++ b/mobile/openapi/lib/model/template_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TemplateResponseDto { + /// Returns a new [TemplateResponseDto] instance. + TemplateResponseDto({ + required this.html, + required this.name, + }); + + /// Template HTML content + String html; + + /// Template name + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is TemplateResponseDto && + other.html == html && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (html.hashCode) + + (name.hashCode); + + @override + String toString() => 'TemplateResponseDto[html=$html, name=$name]'; + + Map toJson() { + final json = {}; + json[r'html'] = this.html; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [TemplateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TemplateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TemplateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TemplateResponseDto( + html: mapValueOfType(json, r'html')!, + name: mapValueOfType(json, r'name')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TemplateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TemplateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TemplateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TemplateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'html', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/test_email_response_dto.dart b/mobile/openapi/lib/model/test_email_response_dto.dart new file mode 100644 index 0000000000..e14783f3c4 --- /dev/null +++ b/mobile/openapi/lib/model/test_email_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TestEmailResponseDto { + /// Returns a new [TestEmailResponseDto] instance. + TestEmailResponseDto({ + required this.messageId, + }); + + /// Email message ID + String messageId; + + @override + bool operator ==(Object other) => identical(this, other) || other is TestEmailResponseDto && + other.messageId == messageId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (messageId.hashCode); + + @override + String toString() => 'TestEmailResponseDto[messageId=$messageId]'; + + Map toJson() { + final json = {}; + json[r'messageId'] = this.messageId; + return json; + } + + /// Returns a new [TestEmailResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TestEmailResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TestEmailResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TestEmailResponseDto( + messageId: mapValueOfType(json, r'messageId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TestEmailResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TestEmailResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TestEmailResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TestEmailResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'messageId', + }; +} + diff --git a/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart new file mode 100644 index 0000000000..720323cd14 --- /dev/null +++ b/mobile/openapi/lib/model/time_bucket_asset_response_dto.dart @@ -0,0 +1,286 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TimeBucketAssetResponseDto { + /// Returns a new [TimeBucketAssetResponseDto] instance. + TimeBucketAssetResponseDto({ + this.city = const [], + this.country = const [], + this.duration = const [], + this.fileCreatedAt = const [], + this.id = const [], + this.isFavorite = const [], + this.isImage = const [], + this.isTrashed = const [], + this.latitude = const [], + this.livePhotoVideoId = const [], + this.localOffsetHours = const [], + this.longitude = const [], + this.ownerId = const [], + this.projectionType = const [], + this.ratio = const [], + this.stack = const [], + this.thumbhash = const [], + this.visibility = const [], + }); + + /// Array of city names extracted from EXIF GPS data + List city; + + /// Array of country names extracted from EXIF GPS data + List country; + + /// Array of video durations in HH:MM:SS format (null for images) + List duration; + + /// Array of file creation timestamps in UTC + List fileCreatedAt; + + /// Array of asset IDs in the time bucket + List id; + + /// Array indicating whether each asset is favorited + List isFavorite; + + /// Array indicating whether each asset is an image (false for videos) + List isImage; + + /// Array indicating whether each asset is in the trash + List isTrashed; + + /// Array of latitude coordinates extracted from EXIF GPS data + List latitude; + + /// Array of live photo video asset IDs (null for non-live photos) + List livePhotoVideoId; + + /// Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective. + List localOffsetHours; + + /// Array of longitude coordinates extracted from EXIF GPS data + List longitude; + + /// Array of owner IDs for each asset + List ownerId; + + /// Array of projection types for 360° content (e.g., \"EQUIRECTANGULAR\", \"CUBEFACE\", \"CYLINDRICAL\") + List projectionType; + + /// Array of aspect ratios (width/height) for each asset + List ratio; + + /// Array of stack information as [stackId, assetCount] tuples (null for non-stacked assets) + List?> stack; + + /// Array of BlurHash strings for generating asset previews (base64 encoded) + List thumbhash; + + /// Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED) + List visibility; + + @override + bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto && + _deepEquality.equals(other.city, city) && + _deepEquality.equals(other.country, country) && + _deepEquality.equals(other.duration, duration) && + _deepEquality.equals(other.fileCreatedAt, fileCreatedAt) && + _deepEquality.equals(other.id, id) && + _deepEquality.equals(other.isFavorite, isFavorite) && + _deepEquality.equals(other.isImage, isImage) && + _deepEquality.equals(other.isTrashed, isTrashed) && + _deepEquality.equals(other.latitude, latitude) && + _deepEquality.equals(other.livePhotoVideoId, livePhotoVideoId) && + _deepEquality.equals(other.localOffsetHours, localOffsetHours) && + _deepEquality.equals(other.longitude, longitude) && + _deepEquality.equals(other.ownerId, ownerId) && + _deepEquality.equals(other.projectionType, projectionType) && + _deepEquality.equals(other.ratio, ratio) && + _deepEquality.equals(other.stack, stack) && + _deepEquality.equals(other.thumbhash, thumbhash) && + _deepEquality.equals(other.visibility, visibility); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (city.hashCode) + + (country.hashCode) + + (duration.hashCode) + + (fileCreatedAt.hashCode) + + (id.hashCode) + + (isFavorite.hashCode) + + (isImage.hashCode) + + (isTrashed.hashCode) + + (latitude.hashCode) + + (livePhotoVideoId.hashCode) + + (localOffsetHours.hashCode) + + (longitude.hashCode) + + (ownerId.hashCode) + + (projectionType.hashCode) + + (ratio.hashCode) + + (stack.hashCode) + + (thumbhash.hashCode) + + (visibility.hashCode); + + @override + String toString() => 'TimeBucketAssetResponseDto[city=$city, country=$country, duration=$duration, fileCreatedAt=$fileCreatedAt, id=$id, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, localOffsetHours=$localOffsetHours, longitude=$longitude, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash, visibility=$visibility]'; + + Map toJson() { + final json = {}; + json[r'city'] = this.city; + json[r'country'] = this.country; + json[r'duration'] = this.duration; + json[r'fileCreatedAt'] = this.fileCreatedAt; + json[r'id'] = this.id; + json[r'isFavorite'] = this.isFavorite; + json[r'isImage'] = this.isImage; + json[r'isTrashed'] = this.isTrashed; + json[r'latitude'] = this.latitude; + json[r'livePhotoVideoId'] = this.livePhotoVideoId; + json[r'localOffsetHours'] = this.localOffsetHours; + json[r'longitude'] = this.longitude; + json[r'ownerId'] = this.ownerId; + json[r'projectionType'] = this.projectionType; + json[r'ratio'] = this.ratio; + json[r'stack'] = this.stack; + json[r'thumbhash'] = this.thumbhash; + json[r'visibility'] = this.visibility; + return json; + } + + /// Returns a new [TimeBucketAssetResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TimeBucketAssetResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TimeBucketAssetResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TimeBucketAssetResponseDto( + city: json[r'city'] is Iterable + ? (json[r'city'] as Iterable).cast().toList(growable: false) + : const [], + country: json[r'country'] is Iterable + ? (json[r'country'] as Iterable).cast().toList(growable: false) + : const [], + duration: json[r'duration'] is Iterable + ? (json[r'duration'] as Iterable).cast().toList(growable: false) + : const [], + fileCreatedAt: json[r'fileCreatedAt'] is Iterable + ? (json[r'fileCreatedAt'] as Iterable).cast().toList(growable: false) + : const [], + id: json[r'id'] is Iterable + ? (json[r'id'] as Iterable).cast().toList(growable: false) + : const [], + isFavorite: json[r'isFavorite'] is Iterable + ? (json[r'isFavorite'] as Iterable).cast().toList(growable: false) + : const [], + isImage: json[r'isImage'] is Iterable + ? (json[r'isImage'] as Iterable).cast().toList(growable: false) + : const [], + isTrashed: json[r'isTrashed'] is Iterable + ? (json[r'isTrashed'] as Iterable).cast().toList(growable: false) + : const [], + latitude: json[r'latitude'] is Iterable + ? (json[r'latitude'] as Iterable).cast().toList(growable: false) + : const [], + livePhotoVideoId: json[r'livePhotoVideoId'] is Iterable + ? (json[r'livePhotoVideoId'] as Iterable).cast().toList(growable: false) + : const [], + localOffsetHours: json[r'localOffsetHours'] is Iterable + ? (json[r'localOffsetHours'] as Iterable).cast().toList(growable: false) + : const [], + longitude: json[r'longitude'] is Iterable + ? (json[r'longitude'] as Iterable).cast().toList(growable: false) + : const [], + ownerId: json[r'ownerId'] is Iterable + ? (json[r'ownerId'] as Iterable).cast().toList(growable: false) + : const [], + projectionType: json[r'projectionType'] is Iterable + ? (json[r'projectionType'] as Iterable).cast().toList(growable: false) + : const [], + ratio: json[r'ratio'] is Iterable + ? (json[r'ratio'] as Iterable).cast().toList(growable: false) + : const [], + stack: json[r'stack'] is List + ? (json[r'stack'] as List).map((e) => + e == null ? null : (e as List).cast() + ).toList() + : const [], + thumbhash: json[r'thumbhash'] is Iterable + ? (json[r'thumbhash'] as Iterable).cast().toList(growable: false) + : const [], + visibility: AssetVisibility.listFromJson(json[r'visibility']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TimeBucketAssetResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TimeBucketAssetResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TimeBucketAssetResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TimeBucketAssetResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'city', + 'country', + 'duration', + 'fileCreatedAt', + 'id', + 'isFavorite', + 'isImage', + 'isTrashed', + 'livePhotoVideoId', + 'localOffsetHours', + 'ownerId', + 'projectionType', + 'ratio', + 'thumbhash', + 'visibility', + }; +} + diff --git a/mobile/openapi/lib/model/time_buckets_response_dto.dart b/mobile/openapi/lib/model/time_buckets_response_dto.dart new file mode 100644 index 0000000000..11faa815e2 --- /dev/null +++ b/mobile/openapi/lib/model/time_buckets_response_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TimeBucketsResponseDto { + /// Returns a new [TimeBucketsResponseDto] instance. + TimeBucketsResponseDto({ + required this.count, + required this.timeBucket, + }); + + /// Number of assets in this time bucket + int count; + + /// Time bucket identifier in YYYY-MM-DD format representing the start of the time period + String timeBucket; + + @override + bool operator ==(Object other) => identical(this, other) || other is TimeBucketsResponseDto && + other.count == count && + other.timeBucket == timeBucket; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode) + + (timeBucket.hashCode); + + @override + String toString() => 'TimeBucketsResponseDto[count=$count, timeBucket=$timeBucket]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + json[r'timeBucket'] = this.timeBucket; + return json; + } + + /// Returns a new [TimeBucketsResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TimeBucketsResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TimeBucketsResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TimeBucketsResponseDto( + count: mapValueOfType(json, r'count')!, + timeBucket: mapValueOfType(json, r'timeBucket')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TimeBucketsResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TimeBucketsResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TimeBucketsResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TimeBucketsResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + 'timeBucket', + }; +} + diff --git a/mobile/openapi/lib/model/tone_mapping.dart b/mobile/openapi/lib/model/tone_mapping.dart new file mode 100644 index 0000000000..a1db2f5c9c --- /dev/null +++ b/mobile/openapi/lib/model/tone_mapping.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Tone mapping +class ToneMapping { + /// Instantiate a new enum with the provided [value]. + const ToneMapping._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const hable = ToneMapping._(r'hable'); + static const mobius = ToneMapping._(r'mobius'); + static const reinhard = ToneMapping._(r'reinhard'); + static const disabled = ToneMapping._(r'disabled'); + + /// List of all possible values in this [enum][ToneMapping]. + static const values = [ + hable, + mobius, + reinhard, + disabled, + ]; + + static ToneMapping? fromJson(dynamic value) => ToneMappingTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ToneMapping.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [ToneMapping] to String, +/// and [decode] dynamic data back to [ToneMapping]. +class ToneMappingTypeTransformer { + factory ToneMappingTypeTransformer() => _instance ??= const ToneMappingTypeTransformer._(); + + const ToneMappingTypeTransformer._(); + + String encode(ToneMapping data) => data.value; + + /// Decodes a [dynamic value][data] to a ToneMapping. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + ToneMapping? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'hable': return ToneMapping.hable; + case r'mobius': return ToneMapping.mobius; + case r'reinhard': return ToneMapping.reinhard; + case r'disabled': return ToneMapping.disabled; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [ToneMappingTypeTransformer] instance. + static ToneMappingTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/transcode_hw_accel.dart b/mobile/openapi/lib/model/transcode_hw_accel.dart new file mode 100644 index 0000000000..22d20de320 --- /dev/null +++ b/mobile/openapi/lib/model/transcode_hw_accel.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Transcode hardware acceleration +class TranscodeHWAccel { + /// Instantiate a new enum with the provided [value]. + const TranscodeHWAccel._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const nvenc = TranscodeHWAccel._(r'nvenc'); + static const qsv = TranscodeHWAccel._(r'qsv'); + static const vaapi = TranscodeHWAccel._(r'vaapi'); + static const rkmpp = TranscodeHWAccel._(r'rkmpp'); + static const disabled = TranscodeHWAccel._(r'disabled'); + + /// List of all possible values in this [enum][TranscodeHWAccel]. + static const values = [ + nvenc, + qsv, + vaapi, + rkmpp, + disabled, + ]; + + static TranscodeHWAccel? fromJson(dynamic value) => TranscodeHWAccelTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TranscodeHWAccel.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [TranscodeHWAccel] to String, +/// and [decode] dynamic data back to [TranscodeHWAccel]. +class TranscodeHWAccelTypeTransformer { + factory TranscodeHWAccelTypeTransformer() => _instance ??= const TranscodeHWAccelTypeTransformer._(); + + const TranscodeHWAccelTypeTransformer._(); + + String encode(TranscodeHWAccel data) => data.value; + + /// Decodes a [dynamic value][data] to a TranscodeHWAccel. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + TranscodeHWAccel? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'nvenc': return TranscodeHWAccel.nvenc; + case r'qsv': return TranscodeHWAccel.qsv; + case r'vaapi': return TranscodeHWAccel.vaapi; + case r'rkmpp': return TranscodeHWAccel.rkmpp; + case r'disabled': return TranscodeHWAccel.disabled; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [TranscodeHWAccelTypeTransformer] instance. + static TranscodeHWAccelTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/transcode_policy.dart b/mobile/openapi/lib/model/transcode_policy.dart new file mode 100644 index 0000000000..ab3a876a93 --- /dev/null +++ b/mobile/openapi/lib/model/transcode_policy.dart @@ -0,0 +1,94 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Transcode policy +class TranscodePolicy { + /// Instantiate a new enum with the provided [value]. + const TranscodePolicy._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const all = TranscodePolicy._(r'all'); + static const optimal = TranscodePolicy._(r'optimal'); + static const bitrate = TranscodePolicy._(r'bitrate'); + static const required_ = TranscodePolicy._(r'required'); + static const disabled = TranscodePolicy._(r'disabled'); + + /// List of all possible values in this [enum][TranscodePolicy]. + static const values = [ + all, + optimal, + bitrate, + required_, + disabled, + ]; + + static TranscodePolicy? fromJson(dynamic value) => TranscodePolicyTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TranscodePolicy.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [TranscodePolicy] to String, +/// and [decode] dynamic data back to [TranscodePolicy]. +class TranscodePolicyTypeTransformer { + factory TranscodePolicyTypeTransformer() => _instance ??= const TranscodePolicyTypeTransformer._(); + + const TranscodePolicyTypeTransformer._(); + + String encode(TranscodePolicy data) => data.value; + + /// Decodes a [dynamic value][data] to a TranscodePolicy. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + TranscodePolicy? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'all': return TranscodePolicy.all; + case r'optimal': return TranscodePolicy.optimal; + case r'bitrate': return TranscodePolicy.bitrate; + case r'required': return TranscodePolicy.required_; + case r'disabled': return TranscodePolicy.disabled; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [TranscodePolicyTypeTransformer] instance. + static TranscodePolicyTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/trash_response_dto.dart b/mobile/openapi/lib/model/trash_response_dto.dart new file mode 100644 index 0000000000..7edd5d032a --- /dev/null +++ b/mobile/openapi/lib/model/trash_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class TrashResponseDto { + /// Returns a new [TrashResponseDto] instance. + TrashResponseDto({ + required this.count, + }); + + /// Number of items in trash + int count; + + @override + bool operator ==(Object other) => identical(this, other) || other is TrashResponseDto && + other.count == count; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (count.hashCode); + + @override + String toString() => 'TrashResponseDto[count=$count]'; + + Map toJson() { + final json = {}; + json[r'count'] = this.count; + return json; + } + + /// Returns a new [TrashResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static TrashResponseDto? fromJson(dynamic value) { + upgradeDto(value, "TrashResponseDto"); + if (value is Map) { + final json = value.cast(); + + return TrashResponseDto( + count: mapValueOfType(json, r'count')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = TrashResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = TrashResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of TrashResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = TrashResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'count', + }; +} + diff --git a/mobile/openapi/lib/model/update_album_dto.dart b/mobile/openapi/lib/model/update_album_dto.dart new file mode 100644 index 0000000000..46ce8b0ecc --- /dev/null +++ b/mobile/openapi/lib/model/update_album_dto.dart @@ -0,0 +1,181 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UpdateAlbumDto { + /// Returns a new [UpdateAlbumDto] instance. + UpdateAlbumDto({ + this.albumName, + this.albumThumbnailAssetId, + this.description, + this.isActivityEnabled, + this.order, + }); + + /// Album name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? albumName; + + /// Album thumbnail asset ID + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? albumThumbnailAssetId; + + /// Album description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Enable activity feed + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isActivityEnabled; + + /// Asset sort order + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetOrder? order; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumDto && + other.albumName == albumName && + other.albumThumbnailAssetId == albumThumbnailAssetId && + other.description == description && + other.isActivityEnabled == isActivityEnabled && + other.order == order; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumName == null ? 0 : albumName!.hashCode) + + (albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (isActivityEnabled == null ? 0 : isActivityEnabled!.hashCode) + + (order == null ? 0 : order!.hashCode); + + @override + String toString() => 'UpdateAlbumDto[albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, description=$description, isActivityEnabled=$isActivityEnabled, order=$order]'; + + Map toJson() { + final json = {}; + if (this.albumName != null) { + json[r'albumName'] = this.albumName; + } else { + // json[r'albumName'] = null; + } + if (this.albumThumbnailAssetId != null) { + json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId; + } else { + // json[r'albumThumbnailAssetId'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.isActivityEnabled != null) { + json[r'isActivityEnabled'] = this.isActivityEnabled; + } else { + // json[r'isActivityEnabled'] = null; + } + if (this.order != null) { + json[r'order'] = this.order; + } else { + // json[r'order'] = null; + } + return json; + } + + /// Returns a new [UpdateAlbumDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdateAlbumDto? fromJson(dynamic value) { + upgradeDto(value, "UpdateAlbumDto"); + if (value is Map) { + final json = value.cast(); + + return UpdateAlbumDto( + albumName: mapValueOfType(json, r'albumName'), + albumThumbnailAssetId: mapValueOfType(json, r'albumThumbnailAssetId'), + description: mapValueOfType(json, r'description'), + isActivityEnabled: mapValueOfType(json, r'isActivityEnabled'), + order: AssetOrder.fromJson(json[r'order']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdateAlbumDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdateAlbumDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdateAlbumDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdateAlbumDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/update_album_user_dto.dart b/mobile/openapi/lib/model/update_album_user_dto.dart new file mode 100644 index 0000000000..9d934eb465 --- /dev/null +++ b/mobile/openapi/lib/model/update_album_user_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UpdateAlbumUserDto { + /// Returns a new [UpdateAlbumUserDto] instance. + UpdateAlbumUserDto({ + required this.role, + }); + + /// Album user role + AlbumUserRole role; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumUserDto && + other.role == role; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (role.hashCode); + + @override + String toString() => 'UpdateAlbumUserDto[role=$role]'; + + Map toJson() { + final json = {}; + json[r'role'] = this.role; + return json; + } + + /// Returns a new [UpdateAlbumUserDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdateAlbumUserDto? fromJson(dynamic value) { + upgradeDto(value, "UpdateAlbumUserDto"); + if (value is Map) { + final json = value.cast(); + + return UpdateAlbumUserDto( + role: AlbumUserRole.fromJson(json[r'role'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdateAlbumUserDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdateAlbumUserDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdateAlbumUserDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdateAlbumUserDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'role', + }; +} + diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart new file mode 100644 index 0000000000..8526995934 --- /dev/null +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -0,0 +1,228 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UpdateAssetDto { + /// Returns a new [UpdateAssetDto] instance. + UpdateAssetDto({ + this.dateTimeOriginal, + this.description, + this.isFavorite, + this.latitude, + this.livePhotoVideoId, + this.longitude, + this.rating, + this.visibility, + }); + + /// Original date and time + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? dateTimeOriginal; + + /// Asset description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Mark as favorite + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isFavorite; + + /// Latitude coordinate + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? latitude; + + /// Live photo video ID + String? livePhotoVideoId; + + /// Longitude coordinate + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? longitude; + + /// Rating in range [1-5], or null for unrated + /// + /// Minimum value: -1 + /// Maximum value: 5 + num? rating; + + /// Asset visibility + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AssetVisibility? visibility; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && + other.dateTimeOriginal == dateTimeOriginal && + other.description == description && + other.isFavorite == isFavorite && + other.latitude == latitude && + other.livePhotoVideoId == livePhotoVideoId && + other.longitude == longitude && + other.rating == rating && + other.visibility == visibility; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (description == null ? 0 : description!.hashCode) + + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (visibility == null ? 0 : visibility!.hashCode); + + @override + String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isFavorite=$isFavorite, latitude=$latitude, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, rating=$rating, visibility=$visibility]'; + + Map toJson() { + final json = {}; + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal; + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.isFavorite != null) { + json[r'isFavorite'] = this.isFavorite; + } else { + // json[r'isFavorite'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.livePhotoVideoId != null) { + json[r'livePhotoVideoId'] = this.livePhotoVideoId; + } else { + // json[r'livePhotoVideoId'] = null; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.visibility != null) { + json[r'visibility'] = this.visibility; + } else { + // json[r'visibility'] = null; + } + return json; + } + + /// Returns a new [UpdateAssetDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdateAssetDto? fromJson(dynamic value) { + upgradeDto(value, "UpdateAssetDto"); + if (value is Map) { + final json = value.cast(); + + return UpdateAssetDto( + dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), + description: mapValueOfType(json, r'description'), + isFavorite: mapValueOfType(json, r'isFavorite'), + latitude: num.parse('${json[r'latitude']}'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + longitude: num.parse('${json[r'longitude']}'), + rating: json[r'rating'] == null + ? null + : num.parse('${json[r'rating']}'), + visibility: AssetVisibility.fromJson(json[r'visibility']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdateAssetDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdateAssetDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdateAssetDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdateAssetDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/update_library_dto.dart b/mobile/openapi/lib/model/update_library_dto.dart new file mode 100644 index 0000000000..628bdc0055 --- /dev/null +++ b/mobile/openapi/lib/model/update_library_dto.dart @@ -0,0 +1,129 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UpdateLibraryDto { + /// Returns a new [UpdateLibraryDto] instance. + UpdateLibraryDto({ + this.exclusionPatterns = const {}, + this.importPaths = const {}, + this.name, + }); + + /// Exclusion patterns (max 128) + Set exclusionPatterns; + + /// Import paths (max 128) + Set importPaths; + + /// Library name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + @override + bool operator ==(Object other) => identical(this, other) || other is UpdateLibraryDto && + _deepEquality.equals(other.exclusionPatterns, exclusionPatterns) && + _deepEquality.equals(other.importPaths, importPaths) && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (exclusionPatterns.hashCode) + + (importPaths.hashCode) + + (name == null ? 0 : name!.hashCode); + + @override + String toString() => 'UpdateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths, name=$name]'; + + Map toJson() { + final json = {}; + json[r'exclusionPatterns'] = this.exclusionPatterns.toList(growable: false); + json[r'importPaths'] = this.importPaths.toList(growable: false); + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + return json; + } + + /// Returns a new [UpdateLibraryDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UpdateLibraryDto? fromJson(dynamic value) { + upgradeDto(value, "UpdateLibraryDto"); + if (value is Map) { + final json = value.cast(); + + return UpdateLibraryDto( + exclusionPatterns: json[r'exclusionPatterns'] is Iterable + ? (json[r'exclusionPatterns'] as Iterable).cast().toSet() + : const {}, + importPaths: json[r'importPaths'] is Iterable + ? (json[r'importPaths'] as Iterable).cast().toSet() + : const {}, + name: mapValueOfType(json, r'name'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UpdateLibraryDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UpdateLibraryDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UpdateLibraryDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UpdateLibraryDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/usage_by_user_dto.dart b/mobile/openapi/lib/model/usage_by_user_dto.dart new file mode 100644 index 0000000000..da1fe600a5 --- /dev/null +++ b/mobile/openapi/lib/model/usage_by_user_dto.dart @@ -0,0 +1,167 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UsageByUserDto { + /// Returns a new [UsageByUserDto] instance. + UsageByUserDto({ + required this.photos, + required this.quotaSizeInBytes, + required this.usage, + required this.usagePhotos, + required this.usageVideos, + required this.userId, + required this.userName, + required this.videos, + }); + + /// Number of photos + int photos; + + /// User quota size in bytes (null if unlimited) + int? quotaSizeInBytes; + + /// Total storage usage in bytes + int usage; + + /// Storage usage for photos in bytes + int usagePhotos; + + /// Storage usage for videos in bytes + int usageVideos; + + /// User ID + String userId; + + /// User name + String userName; + + /// Number of videos + int videos; + + @override + bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto && + other.photos == photos && + other.quotaSizeInBytes == quotaSizeInBytes && + other.usage == usage && + other.usagePhotos == usagePhotos && + other.usageVideos == usageVideos && + other.userId == userId && + other.userName == userName && + other.videos == videos; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (photos.hashCode) + + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + + (usage.hashCode) + + (usagePhotos.hashCode) + + (usageVideos.hashCode) + + (userId.hashCode) + + (userName.hashCode) + + (videos.hashCode); + + @override + String toString() => 'UsageByUserDto[photos=$photos, quotaSizeInBytes=$quotaSizeInBytes, usage=$usage, usagePhotos=$usagePhotos, usageVideos=$usageVideos, userId=$userId, userName=$userName, videos=$videos]'; + + Map toJson() { + final json = {}; + json[r'photos'] = this.photos; + if (this.quotaSizeInBytes != null) { + json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; + } else { + // json[r'quotaSizeInBytes'] = null; + } + json[r'usage'] = this.usage; + json[r'usagePhotos'] = this.usagePhotos; + json[r'usageVideos'] = this.usageVideos; + json[r'userId'] = this.userId; + json[r'userName'] = this.userName; + json[r'videos'] = this.videos; + return json; + } + + /// Returns a new [UsageByUserDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UsageByUserDto? fromJson(dynamic value) { + upgradeDto(value, "UsageByUserDto"); + if (value is Map) { + final json = value.cast(); + + return UsageByUserDto( + photos: mapValueOfType(json, r'photos')!, + quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), + usage: mapValueOfType(json, r'usage')!, + usagePhotos: mapValueOfType(json, r'usagePhotos')!, + usageVideos: mapValueOfType(json, r'usageVideos')!, + userId: mapValueOfType(json, r'userId')!, + userName: mapValueOfType(json, r'userName')!, + videos: mapValueOfType(json, r'videos')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UsageByUserDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UsageByUserDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UsageByUserDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UsageByUserDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'photos', + 'quotaSizeInBytes', + 'usage', + 'usagePhotos', + 'usageVideos', + 'userId', + 'userName', + 'videos', + }; +} + diff --git a/mobile/openapi/lib/model/user_admin_create_dto.dart b/mobile/openapi/lib/model/user_admin_create_dto.dart new file mode 100644 index 0000000000..485b2e00e5 --- /dev/null +++ b/mobile/openapi/lib/model/user_admin_create_dto.dart @@ -0,0 +1,222 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserAdminCreateDto { + /// Returns a new [UserAdminCreateDto] instance. + UserAdminCreateDto({ + this.avatarColor, + required this.email, + this.isAdmin, + required this.name, + this.notify, + required this.password, + this.pinCode, + this.quotaSizeInBytes, + this.shouldChangePassword, + this.storageLabel, + }); + + /// Avatar color + UserAvatarColor? avatarColor; + + /// User email + String email; + + /// Grant admin privileges + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isAdmin; + + /// User name + String name; + + /// Send notification email + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? notify; + + /// User password + String password; + + /// PIN code + String? pinCode; + + /// Storage quota in bytes + /// + /// Minimum value: 0 + int? quotaSizeInBytes; + + /// Require password change on next login + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? shouldChangePassword; + + /// Storage label + String? storageLabel; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserAdminCreateDto && + other.avatarColor == avatarColor && + other.email == email && + other.isAdmin == isAdmin && + other.name == name && + other.notify == notify && + other.password == password && + other.pinCode == pinCode && + other.quotaSizeInBytes == quotaSizeInBytes && + other.shouldChangePassword == shouldChangePassword && + other.storageLabel == storageLabel; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + + (email.hashCode) + + (isAdmin == null ? 0 : isAdmin!.hashCode) + + (name.hashCode) + + (notify == null ? 0 : notify!.hashCode) + + (password.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode) + + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + + (shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode); + + @override + String toString() => 'UserAdminCreateDto[avatarColor=$avatarColor, email=$email, isAdmin=$isAdmin, name=$name, notify=$notify, password=$password, pinCode=$pinCode, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; + + Map toJson() { + final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } + json[r'email'] = this.email; + if (this.isAdmin != null) { + json[r'isAdmin'] = this.isAdmin; + } else { + // json[r'isAdmin'] = null; + } + json[r'name'] = this.name; + if (this.notify != null) { + json[r'notify'] = this.notify; + } else { + // json[r'notify'] = null; + } + json[r'password'] = this.password; + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + if (this.quotaSizeInBytes != null) { + json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; + } else { + // json[r'quotaSizeInBytes'] = null; + } + if (this.shouldChangePassword != null) { + json[r'shouldChangePassword'] = this.shouldChangePassword; + } else { + // json[r'shouldChangePassword'] = null; + } + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + return json; + } + + /// Returns a new [UserAdminCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserAdminCreateDto? fromJson(dynamic value) { + upgradeDto(value, "UserAdminCreateDto"); + if (value is Map) { + final json = value.cast(); + + return UserAdminCreateDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + email: mapValueOfType(json, r'email')!, + isAdmin: mapValueOfType(json, r'isAdmin'), + name: mapValueOfType(json, r'name')!, + notify: mapValueOfType(json, r'notify'), + password: mapValueOfType(json, r'password')!, + pinCode: mapValueOfType(json, r'pinCode'), + quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword'), + storageLabel: mapValueOfType(json, r'storageLabel'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAdminCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserAdminCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserAdminCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserAdminCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'email', + 'name', + 'password', + }; +} + diff --git a/mobile/openapi/lib/model/user_admin_delete_dto.dart b/mobile/openapi/lib/model/user_admin_delete_dto.dart new file mode 100644 index 0000000000..6be70f37b7 --- /dev/null +++ b/mobile/openapi/lib/model/user_admin_delete_dto.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserAdminDeleteDto { + /// Returns a new [UserAdminDeleteDto] instance. + UserAdminDeleteDto({ + this.force, + }); + + /// Force delete even if user has assets + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? force; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserAdminDeleteDto && + other.force == force; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (force == null ? 0 : force!.hashCode); + + @override + String toString() => 'UserAdminDeleteDto[force=$force]'; + + Map toJson() { + final json = {}; + if (this.force != null) { + json[r'force'] = this.force; + } else { + // json[r'force'] = null; + } + return json; + } + + /// Returns a new [UserAdminDeleteDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserAdminDeleteDto? fromJson(dynamic value) { + upgradeDto(value, "UserAdminDeleteDto"); + if (value is Map) { + final json = value.cast(); + + return UserAdminDeleteDto( + force: mapValueOfType(json, r'force'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAdminDeleteDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserAdminDeleteDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserAdminDeleteDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserAdminDeleteDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/user_admin_response_dto.dart b/mobile/openapi/lib/model/user_admin_response_dto.dart new file mode 100644 index 0000000000..706f65cf35 --- /dev/null +++ b/mobile/openapi/lib/model/user_admin_response_dto.dart @@ -0,0 +1,264 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserAdminResponseDto { + /// Returns a new [UserAdminResponseDto] instance. + UserAdminResponseDto({ + required this.avatarColor, + required this.createdAt, + required this.deletedAt, + required this.email, + required this.id, + required this.isAdmin, + required this.license, + required this.name, + required this.oauthId, + required this.profileChangedAt, + required this.profileImagePath, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + required this.shouldChangePassword, + required this.status, + required this.storageLabel, + required this.updatedAt, + }); + + /// Avatar color + UserAvatarColor avatarColor; + + /// Creation date + DateTime createdAt; + + /// Deletion date + DateTime? deletedAt; + + /// User email + String email; + + /// User ID + String id; + + /// Is admin user + bool isAdmin; + + /// User license + UserLicense? license; + + /// User name + String name; + + /// OAuth ID + String oauthId; + + /// Profile change date + DateTime profileChangedAt; + + /// Profile image path + String profileImagePath; + + /// Storage quota in bytes + int? quotaSizeInBytes; + + /// Storage usage in bytes + int? quotaUsageInBytes; + + /// Require password change on next login + bool shouldChangePassword; + + /// User status + UserStatus status; + + /// Storage label + String? storageLabel; + + /// Last update date + DateTime updatedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserAdminResponseDto && + other.avatarColor == avatarColor && + other.createdAt == createdAt && + other.deletedAt == deletedAt && + other.email == email && + other.id == id && + other.isAdmin == isAdmin && + other.license == license && + other.name == name && + other.oauthId == oauthId && + other.profileChangedAt == profileChangedAt && + other.profileImagePath == profileImagePath && + other.quotaSizeInBytes == quotaSizeInBytes && + other.quotaUsageInBytes == quotaUsageInBytes && + other.shouldChangePassword == shouldChangePassword && + other.status == status && + other.storageLabel == storageLabel && + other.updatedAt == updatedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + + (createdAt.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (email.hashCode) + + (id.hashCode) + + (isAdmin.hashCode) + + (license == null ? 0 : license!.hashCode) + + (name.hashCode) + + (oauthId.hashCode) + + (profileChangedAt.hashCode) + + (profileImagePath.hashCode) + + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + + (quotaUsageInBytes == null ? 0 : quotaUsageInBytes!.hashCode) + + (shouldChangePassword.hashCode) + + (status.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode) + + (updatedAt.hashCode); + + @override + String toString() => 'UserAdminResponseDto[avatarColor=$avatarColor, createdAt=$createdAt, deletedAt=$deletedAt, email=$email, id=$id, isAdmin=$isAdmin, license=$license, name=$name, oauthId=$oauthId, profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath, quotaSizeInBytes=$quotaSizeInBytes, quotaUsageInBytes=$quotaUsageInBytes, shouldChangePassword=$shouldChangePassword, status=$status, storageLabel=$storageLabel, updatedAt=$updatedAt]'; + + Map toJson() { + final json = {}; + json[r'avatarColor'] = this.avatarColor; + json[r'createdAt'] = this.createdAt.toUtc().toIso8601String(); + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String(); + } else { + // json[r'deletedAt'] = null; + } + json[r'email'] = this.email; + json[r'id'] = this.id; + json[r'isAdmin'] = this.isAdmin; + if (this.license != null) { + json[r'license'] = this.license; + } else { + // json[r'license'] = null; + } + json[r'name'] = this.name; + json[r'oauthId'] = this.oauthId; + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + json[r'profileImagePath'] = this.profileImagePath; + if (this.quotaSizeInBytes != null) { + json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; + } else { + // json[r'quotaSizeInBytes'] = null; + } + if (this.quotaUsageInBytes != null) { + json[r'quotaUsageInBytes'] = this.quotaUsageInBytes; + } else { + // json[r'quotaUsageInBytes'] = null; + } + json[r'shouldChangePassword'] = this.shouldChangePassword; + json[r'status'] = this.status; + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String(); + return json; + } + + /// Returns a new [UserAdminResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserAdminResponseDto? fromJson(dynamic value) { + upgradeDto(value, "UserAdminResponseDto"); + if (value is Map) { + final json = value.cast(); + + return UserAdminResponseDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, + createdAt: mapDateTime(json, r'createdAt', r'')!, + deletedAt: mapDateTime(json, r'deletedAt', r''), + email: mapValueOfType(json, r'email')!, + id: mapValueOfType(json, r'id')!, + isAdmin: mapValueOfType(json, r'isAdmin')!, + license: UserLicense.fromJson(json[r'license']), + name: mapValueOfType(json, r'name')!, + oauthId: mapValueOfType(json, r'oauthId')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), + quotaUsageInBytes: mapValueOfType(json, r'quotaUsageInBytes'), + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword')!, + status: UserStatus.fromJson(json[r'status'])!, + storageLabel: mapValueOfType(json, r'storageLabel'), + updatedAt: mapDateTime(json, r'updatedAt', r'')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAdminResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserAdminResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserAdminResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserAdminResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'avatarColor', + 'createdAt', + 'deletedAt', + 'email', + 'id', + 'isAdmin', + 'license', + 'name', + 'oauthId', + 'profileChangedAt', + 'profileImagePath', + 'quotaSizeInBytes', + 'quotaUsageInBytes', + 'shouldChangePassword', + 'status', + 'storageLabel', + 'updatedAt', + }; +} + diff --git a/mobile/openapi/lib/model/user_admin_update_dto.dart b/mobile/openapi/lib/model/user_admin_update_dto.dart new file mode 100644 index 0000000000..3cce65745f --- /dev/null +++ b/mobile/openapi/lib/model/user_admin_update_dto.dart @@ -0,0 +1,231 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserAdminUpdateDto { + /// Returns a new [UserAdminUpdateDto] instance. + UserAdminUpdateDto({ + this.avatarColor, + this.email, + this.isAdmin, + this.name, + this.password, + this.pinCode, + this.quotaSizeInBytes, + this.shouldChangePassword, + this.storageLabel, + }); + + /// Avatar color + UserAvatarColor? avatarColor; + + /// User email + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? email; + + /// Grant admin privileges + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? isAdmin; + + /// User name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// User password + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? password; + + /// PIN code + String? pinCode; + + /// Storage quota in bytes + /// + /// Minimum value: 0 + int? quotaSizeInBytes; + + /// Require password change on next login + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? shouldChangePassword; + + /// Storage label + String? storageLabel; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserAdminUpdateDto && + other.avatarColor == avatarColor && + other.email == email && + other.isAdmin == isAdmin && + other.name == name && + other.password == password && + other.pinCode == pinCode && + other.quotaSizeInBytes == quotaSizeInBytes && + other.shouldChangePassword == shouldChangePassword && + other.storageLabel == storageLabel; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + + (email == null ? 0 : email!.hashCode) + + (isAdmin == null ? 0 : isAdmin!.hashCode) + + (name == null ? 0 : name!.hashCode) + + (password == null ? 0 : password!.hashCode) + + (pinCode == null ? 0 : pinCode!.hashCode) + + (quotaSizeInBytes == null ? 0 : quotaSizeInBytes!.hashCode) + + (shouldChangePassword == null ? 0 : shouldChangePassword!.hashCode) + + (storageLabel == null ? 0 : storageLabel!.hashCode); + + @override + String toString() => 'UserAdminUpdateDto[avatarColor=$avatarColor, email=$email, isAdmin=$isAdmin, name=$name, password=$password, pinCode=$pinCode, quotaSizeInBytes=$quotaSizeInBytes, shouldChangePassword=$shouldChangePassword, storageLabel=$storageLabel]'; + + Map toJson() { + final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } + if (this.email != null) { + json[r'email'] = this.email; + } else { + // json[r'email'] = null; + } + if (this.isAdmin != null) { + json[r'isAdmin'] = this.isAdmin; + } else { + // json[r'isAdmin'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + if (this.pinCode != null) { + json[r'pinCode'] = this.pinCode; + } else { + // json[r'pinCode'] = null; + } + if (this.quotaSizeInBytes != null) { + json[r'quotaSizeInBytes'] = this.quotaSizeInBytes; + } else { + // json[r'quotaSizeInBytes'] = null; + } + if (this.shouldChangePassword != null) { + json[r'shouldChangePassword'] = this.shouldChangePassword; + } else { + // json[r'shouldChangePassword'] = null; + } + if (this.storageLabel != null) { + json[r'storageLabel'] = this.storageLabel; + } else { + // json[r'storageLabel'] = null; + } + return json; + } + + /// Returns a new [UserAdminUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserAdminUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "UserAdminUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return UserAdminUpdateDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + email: mapValueOfType(json, r'email'), + isAdmin: mapValueOfType(json, r'isAdmin'), + name: mapValueOfType(json, r'name'), + password: mapValueOfType(json, r'password'), + pinCode: mapValueOfType(json, r'pinCode'), + quotaSizeInBytes: mapValueOfType(json, r'quotaSizeInBytes'), + shouldChangePassword: mapValueOfType(json, r'shouldChangePassword'), + storageLabel: mapValueOfType(json, r'storageLabel'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAdminUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserAdminUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserAdminUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserAdminUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/user_avatar_color.dart b/mobile/openapi/lib/model/user_avatar_color.dart new file mode 100644 index 0000000000..4fcf518550 --- /dev/null +++ b/mobile/openapi/lib/model/user_avatar_color.dart @@ -0,0 +1,109 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Avatar color +class UserAvatarColor { + /// Instantiate a new enum with the provided [value]. + const UserAvatarColor._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const primary = UserAvatarColor._(r'primary'); + static const pink = UserAvatarColor._(r'pink'); + static const red = UserAvatarColor._(r'red'); + static const yellow = UserAvatarColor._(r'yellow'); + static const blue = UserAvatarColor._(r'blue'); + static const green = UserAvatarColor._(r'green'); + static const purple = UserAvatarColor._(r'purple'); + static const orange = UserAvatarColor._(r'orange'); + static const gray = UserAvatarColor._(r'gray'); + static const amber = UserAvatarColor._(r'amber'); + + /// List of all possible values in this [enum][UserAvatarColor]. + static const values = [ + primary, + pink, + red, + yellow, + blue, + green, + purple, + orange, + gray, + amber, + ]; + + static UserAvatarColor? fromJson(dynamic value) => UserAvatarColorTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserAvatarColor.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [UserAvatarColor] to String, +/// and [decode] dynamic data back to [UserAvatarColor]. +class UserAvatarColorTypeTransformer { + factory UserAvatarColorTypeTransformer() => _instance ??= const UserAvatarColorTypeTransformer._(); + + const UserAvatarColorTypeTransformer._(); + + String encode(UserAvatarColor data) => data.value; + + /// Decodes a [dynamic value][data] to a UserAvatarColor. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + UserAvatarColor? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'primary': return UserAvatarColor.primary; + case r'pink': return UserAvatarColor.pink; + case r'red': return UserAvatarColor.red; + case r'yellow': return UserAvatarColor.yellow; + case r'blue': return UserAvatarColor.blue; + case r'green': return UserAvatarColor.green; + case r'purple': return UserAvatarColor.purple; + case r'orange': return UserAvatarColor.orange; + case r'gray': return UserAvatarColor.gray; + case r'amber': return UserAvatarColor.amber; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [UserAvatarColorTypeTransformer] instance. + static UserAvatarColorTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/user_license.dart b/mobile/openapi/lib/model/user_license.dart new file mode 100644 index 0000000000..f02dc73bef --- /dev/null +++ b/mobile/openapi/lib/model/user_license.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserLicense { + /// Returns a new [UserLicense] instance. + UserLicense({ + required this.activatedAt, + required this.activationKey, + required this.licenseKey, + }); + + /// Activation date + DateTime activatedAt; + + /// Activation key + String activationKey; + + /// License key + String licenseKey; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserLicense && + other.activatedAt == activatedAt && + other.activationKey == activationKey && + other.licenseKey == licenseKey; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (activatedAt.hashCode) + + (activationKey.hashCode) + + (licenseKey.hashCode); + + @override + String toString() => 'UserLicense[activatedAt=$activatedAt, activationKey=$activationKey, licenseKey=$licenseKey]'; + + Map toJson() { + final json = {}; + json[r'activatedAt'] = this.activatedAt.toUtc().toIso8601String(); + json[r'activationKey'] = this.activationKey; + json[r'licenseKey'] = this.licenseKey; + return json; + } + + /// Returns a new [UserLicense] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserLicense? fromJson(dynamic value) { + upgradeDto(value, "UserLicense"); + if (value is Map) { + final json = value.cast(); + + return UserLicense( + activatedAt: mapDateTime(json, r'activatedAt', r'')!, + activationKey: mapValueOfType(json, r'activationKey')!, + licenseKey: mapValueOfType(json, r'licenseKey')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserLicense.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserLicense.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserLicense-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserLicense.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'activatedAt', + 'activationKey', + 'licenseKey', + }; +} + diff --git a/mobile/openapi/lib/model/user_metadata_key.dart b/mobile/openapi/lib/model/user_metadata_key.dart new file mode 100644 index 0000000000..2b4c11a73d --- /dev/null +++ b/mobile/openapi/lib/model/user_metadata_key.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// User metadata key +class UserMetadataKey { + /// Instantiate a new enum with the provided [value]. + const UserMetadataKey._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const preferences = UserMetadataKey._(r'preferences'); + static const license = UserMetadataKey._(r'license'); + static const onboarding = UserMetadataKey._(r'onboarding'); + + /// List of all possible values in this [enum][UserMetadataKey]. + static const values = [ + preferences, + license, + onboarding, + ]; + + static UserMetadataKey? fromJson(dynamic value) => UserMetadataKeyTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserMetadataKey.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [UserMetadataKey] to String, +/// and [decode] dynamic data back to [UserMetadataKey]. +class UserMetadataKeyTypeTransformer { + factory UserMetadataKeyTypeTransformer() => _instance ??= const UserMetadataKeyTypeTransformer._(); + + const UserMetadataKeyTypeTransformer._(); + + String encode(UserMetadataKey data) => data.value; + + /// Decodes a [dynamic value][data] to a UserMetadataKey. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + UserMetadataKey? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'preferences': return UserMetadataKey.preferences; + case r'license': return UserMetadataKey.license; + case r'onboarding': return UserMetadataKey.onboarding; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [UserMetadataKeyTypeTransformer] instance. + static UserMetadataKeyTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/user_preferences_response_dto.dart b/mobile/openapi/lib/model/user_preferences_response_dto.dart new file mode 100644 index 0000000000..7a6e0252af --- /dev/null +++ b/mobile/openapi/lib/model/user_preferences_response_dto.dart @@ -0,0 +1,179 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserPreferencesResponseDto { + /// Returns a new [UserPreferencesResponseDto] instance. + UserPreferencesResponseDto({ + required this.albums, + required this.cast, + required this.download, + required this.emailNotifications, + required this.folders, + required this.memories, + required this.people, + required this.purchase, + required this.ratings, + required this.sharedLinks, + required this.tags, + }); + + AlbumsResponse albums; + + CastResponse cast; + + DownloadResponse download; + + EmailNotificationsResponse emailNotifications; + + FoldersResponse folders; + + MemoriesResponse memories; + + PeopleResponse people; + + PurchaseResponse purchase; + + RatingsResponse ratings; + + SharedLinksResponse sharedLinks; + + TagsResponse tags; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserPreferencesResponseDto && + other.albums == albums && + other.cast == cast && + other.download == download && + other.emailNotifications == emailNotifications && + other.folders == folders && + other.memories == memories && + other.people == people && + other.purchase == purchase && + other.ratings == ratings && + other.sharedLinks == sharedLinks && + other.tags == tags; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albums.hashCode) + + (cast.hashCode) + + (download.hashCode) + + (emailNotifications.hashCode) + + (folders.hashCode) + + (memories.hashCode) + + (people.hashCode) + + (purchase.hashCode) + + (ratings.hashCode) + + (sharedLinks.hashCode) + + (tags.hashCode); + + @override + String toString() => 'UserPreferencesResponseDto[albums=$albums, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]'; + + Map toJson() { + final json = {}; + json[r'albums'] = this.albums; + json[r'cast'] = this.cast; + json[r'download'] = this.download; + json[r'emailNotifications'] = this.emailNotifications; + json[r'folders'] = this.folders; + json[r'memories'] = this.memories; + json[r'people'] = this.people; + json[r'purchase'] = this.purchase; + json[r'ratings'] = this.ratings; + json[r'sharedLinks'] = this.sharedLinks; + json[r'tags'] = this.tags; + return json; + } + + /// Returns a new [UserPreferencesResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserPreferencesResponseDto? fromJson(dynamic value) { + upgradeDto(value, "UserPreferencesResponseDto"); + if (value is Map) { + final json = value.cast(); + + return UserPreferencesResponseDto( + albums: AlbumsResponse.fromJson(json[r'albums'])!, + cast: CastResponse.fromJson(json[r'cast'])!, + download: DownloadResponse.fromJson(json[r'download'])!, + emailNotifications: EmailNotificationsResponse.fromJson(json[r'emailNotifications'])!, + folders: FoldersResponse.fromJson(json[r'folders'])!, + memories: MemoriesResponse.fromJson(json[r'memories'])!, + people: PeopleResponse.fromJson(json[r'people'])!, + purchase: PurchaseResponse.fromJson(json[r'purchase'])!, + ratings: RatingsResponse.fromJson(json[r'ratings'])!, + sharedLinks: SharedLinksResponse.fromJson(json[r'sharedLinks'])!, + tags: TagsResponse.fromJson(json[r'tags'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserPreferencesResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserPreferencesResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserPreferencesResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserPreferencesResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albums', + 'cast', + 'download', + 'emailNotifications', + 'folders', + 'memories', + 'people', + 'purchase', + 'ratings', + 'sharedLinks', + 'tags', + }; +} + diff --git a/mobile/openapi/lib/model/user_preferences_update_dto.dart b/mobile/openapi/lib/model/user_preferences_update_dto.dart new file mode 100644 index 0000000000..3b9b178b55 --- /dev/null +++ b/mobile/openapi/lib/model/user_preferences_update_dto.dart @@ -0,0 +1,295 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserPreferencesUpdateDto { + /// Returns a new [UserPreferencesUpdateDto] instance. + UserPreferencesUpdateDto({ + this.albums, + this.avatar, + this.cast, + this.download, + this.emailNotifications, + this.folders, + this.memories, + this.people, + this.purchase, + this.ratings, + this.sharedLinks, + this.tags, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AlbumsUpdate? albums; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + AvatarUpdate? avatar; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + CastUpdate? cast; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + DownloadUpdate? download; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + EmailNotificationsUpdate? emailNotifications; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + FoldersUpdate? folders; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + MemoriesUpdate? memories; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + PeopleUpdate? people; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + PurchaseUpdate? purchase; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + RatingsUpdate? ratings; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + SharedLinksUpdate? sharedLinks; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + TagsUpdate? tags; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserPreferencesUpdateDto && + other.albums == albums && + other.avatar == avatar && + other.cast == cast && + other.download == download && + other.emailNotifications == emailNotifications && + other.folders == folders && + other.memories == memories && + other.people == people && + other.purchase == purchase && + other.ratings == ratings && + other.sharedLinks == sharedLinks && + other.tags == tags; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albums == null ? 0 : albums!.hashCode) + + (avatar == null ? 0 : avatar!.hashCode) + + (cast == null ? 0 : cast!.hashCode) + + (download == null ? 0 : download!.hashCode) + + (emailNotifications == null ? 0 : emailNotifications!.hashCode) + + (folders == null ? 0 : folders!.hashCode) + + (memories == null ? 0 : memories!.hashCode) + + (people == null ? 0 : people!.hashCode) + + (purchase == null ? 0 : purchase!.hashCode) + + (ratings == null ? 0 : ratings!.hashCode) + + (sharedLinks == null ? 0 : sharedLinks!.hashCode) + + (tags == null ? 0 : tags!.hashCode); + + @override + String toString() => 'UserPreferencesUpdateDto[albums=$albums, avatar=$avatar, cast=$cast, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]'; + + Map toJson() { + final json = {}; + if (this.albums != null) { + json[r'albums'] = this.albums; + } else { + // json[r'albums'] = null; + } + if (this.avatar != null) { + json[r'avatar'] = this.avatar; + } else { + // json[r'avatar'] = null; + } + if (this.cast != null) { + json[r'cast'] = this.cast; + } else { + // json[r'cast'] = null; + } + if (this.download != null) { + json[r'download'] = this.download; + } else { + // json[r'download'] = null; + } + if (this.emailNotifications != null) { + json[r'emailNotifications'] = this.emailNotifications; + } else { + // json[r'emailNotifications'] = null; + } + if (this.folders != null) { + json[r'folders'] = this.folders; + } else { + // json[r'folders'] = null; + } + if (this.memories != null) { + json[r'memories'] = this.memories; + } else { + // json[r'memories'] = null; + } + if (this.people != null) { + json[r'people'] = this.people; + } else { + // json[r'people'] = null; + } + if (this.purchase != null) { + json[r'purchase'] = this.purchase; + } else { + // json[r'purchase'] = null; + } + if (this.ratings != null) { + json[r'ratings'] = this.ratings; + } else { + // json[r'ratings'] = null; + } + if (this.sharedLinks != null) { + json[r'sharedLinks'] = this.sharedLinks; + } else { + // json[r'sharedLinks'] = null; + } + if (this.tags != null) { + json[r'tags'] = this.tags; + } else { + // json[r'tags'] = null; + } + return json; + } + + /// Returns a new [UserPreferencesUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserPreferencesUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "UserPreferencesUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return UserPreferencesUpdateDto( + albums: AlbumsUpdate.fromJson(json[r'albums']), + avatar: AvatarUpdate.fromJson(json[r'avatar']), + cast: CastUpdate.fromJson(json[r'cast']), + download: DownloadUpdate.fromJson(json[r'download']), + emailNotifications: EmailNotificationsUpdate.fromJson(json[r'emailNotifications']), + folders: FoldersUpdate.fromJson(json[r'folders']), + memories: MemoriesUpdate.fromJson(json[r'memories']), + people: PeopleUpdate.fromJson(json[r'people']), + purchase: PurchaseUpdate.fromJson(json[r'purchase']), + ratings: RatingsUpdate.fromJson(json[r'ratings']), + sharedLinks: SharedLinksUpdate.fromJson(json[r'sharedLinks']), + tags: TagsUpdate.fromJson(json[r'tags']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserPreferencesUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserPreferencesUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserPreferencesUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserPreferencesUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/user_response_dto.dart b/mobile/openapi/lib/model/user_response_dto.dart new file mode 100644 index 0000000000..bf0e2cbf09 --- /dev/null +++ b/mobile/openapi/lib/model/user_response_dto.dart @@ -0,0 +1,145 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserResponseDto { + /// Returns a new [UserResponseDto] instance. + UserResponseDto({ + required this.avatarColor, + required this.email, + required this.id, + required this.name, + required this.profileChangedAt, + required this.profileImagePath, + }); + + /// Avatar color + UserAvatarColor avatarColor; + + /// User email + String email; + + /// User ID + String id; + + /// User name + String name; + + /// Profile change date + DateTime profileChangedAt; + + /// Profile image path + String profileImagePath; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserResponseDto && + other.avatarColor == avatarColor && + other.email == email && + other.id == id && + other.name == name && + other.profileChangedAt == profileChangedAt && + other.profileImagePath == profileImagePath; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor.hashCode) + + (email.hashCode) + + (id.hashCode) + + (name.hashCode) + + (profileChangedAt.hashCode) + + (profileImagePath.hashCode); + + @override + String toString() => 'UserResponseDto[avatarColor=$avatarColor, email=$email, id=$id, name=$name, profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath]'; + + Map toJson() { + final json = {}; + json[r'avatarColor'] = this.avatarColor; + json[r'email'] = this.email; + json[r'id'] = this.id; + json[r'name'] = this.name; + json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String(); + json[r'profileImagePath'] = this.profileImagePath; + return json; + } + + /// Returns a new [UserResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserResponseDto? fromJson(dynamic value) { + upgradeDto(value, "UserResponseDto"); + if (value is Map) { + final json = value.cast(); + + return UserResponseDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!, + email: mapValueOfType(json, r'email')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!, + profileImagePath: mapValueOfType(json, r'profileImagePath')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'avatarColor', + 'email', + 'id', + 'name', + 'profileChangedAt', + 'profileImagePath', + }; +} + diff --git a/mobile/openapi/lib/model/user_status.dart b/mobile/openapi/lib/model/user_status.dart new file mode 100644 index 0000000000..130bd650f2 --- /dev/null +++ b/mobile/openapi/lib/model/user_status.dart @@ -0,0 +1,88 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// User status +class UserStatus { + /// Instantiate a new enum with the provided [value]. + const UserStatus._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const active = UserStatus._(r'active'); + static const removing = UserStatus._(r'removing'); + static const deleted = UserStatus._(r'deleted'); + + /// List of all possible values in this [enum][UserStatus]. + static const values = [ + active, + removing, + deleted, + ]; + + static UserStatus? fromJson(dynamic value) => UserStatusTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserStatus.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [UserStatus] to String, +/// and [decode] dynamic data back to [UserStatus]. +class UserStatusTypeTransformer { + factory UserStatusTypeTransformer() => _instance ??= const UserStatusTypeTransformer._(); + + const UserStatusTypeTransformer._(); + + String encode(UserStatus data) => data.value; + + /// Decodes a [dynamic value][data] to a UserStatus. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + UserStatus? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'active': return UserStatus.active; + case r'removing': return UserStatus.removing; + case r'deleted': return UserStatus.deleted; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [UserStatusTypeTransformer] instance. + static UserStatusTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/user_update_me_dto.dart b/mobile/openapi/lib/model/user_update_me_dto.dart new file mode 100644 index 0000000000..066c435eb3 --- /dev/null +++ b/mobile/openapi/lib/model/user_update_me_dto.dart @@ -0,0 +1,157 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class UserUpdateMeDto { + /// Returns a new [UserUpdateMeDto] instance. + UserUpdateMeDto({ + this.avatarColor, + this.email, + this.name, + this.password, + }); + + /// Avatar color + UserAvatarColor? avatarColor; + + /// User email + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? email; + + /// User name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// User password (deprecated, use change password endpoint) + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? password; + + @override + bool operator ==(Object other) => identical(this, other) || other is UserUpdateMeDto && + other.avatarColor == avatarColor && + other.email == email && + other.name == name && + other.password == password; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (avatarColor == null ? 0 : avatarColor!.hashCode) + + (email == null ? 0 : email!.hashCode) + + (name == null ? 0 : name!.hashCode) + + (password == null ? 0 : password!.hashCode); + + @override + String toString() => 'UserUpdateMeDto[avatarColor=$avatarColor, email=$email, name=$name, password=$password]'; + + Map toJson() { + final json = {}; + if (this.avatarColor != null) { + json[r'avatarColor'] = this.avatarColor; + } else { + // json[r'avatarColor'] = null; + } + if (this.email != null) { + json[r'email'] = this.email; + } else { + // json[r'email'] = null; + } + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + if (this.password != null) { + json[r'password'] = this.password; + } else { + // json[r'password'] = null; + } + return json; + } + + /// Returns a new [UserUpdateMeDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static UserUpdateMeDto? fromJson(dynamic value) { + upgradeDto(value, "UserUpdateMeDto"); + if (value is Map) { + final json = value.cast(); + + return UserUpdateMeDto( + avatarColor: UserAvatarColor.fromJson(json[r'avatarColor']), + email: mapValueOfType(json, r'email'), + name: mapValueOfType(json, r'name'), + password: mapValueOfType(json, r'password'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = UserUpdateMeDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = UserUpdateMeDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of UserUpdateMeDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = UserUpdateMeDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/validate_access_token_response_dto.dart b/mobile/openapi/lib/model/validate_access_token_response_dto.dart new file mode 100644 index 0000000000..16b9d0f925 --- /dev/null +++ b/mobile/openapi/lib/model/validate_access_token_response_dto.dart @@ -0,0 +1,100 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ValidateAccessTokenResponseDto { + /// Returns a new [ValidateAccessTokenResponseDto] instance. + ValidateAccessTokenResponseDto({ + required this.authStatus, + }); + + /// Authentication status + bool authStatus; + + @override + bool operator ==(Object other) => identical(this, other) || other is ValidateAccessTokenResponseDto && + other.authStatus == authStatus; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (authStatus.hashCode); + + @override + String toString() => 'ValidateAccessTokenResponseDto[authStatus=$authStatus]'; + + Map toJson() { + final json = {}; + json[r'authStatus'] = this.authStatus; + return json; + } + + /// Returns a new [ValidateAccessTokenResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ValidateAccessTokenResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ValidateAccessTokenResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ValidateAccessTokenResponseDto( + authStatus: mapValueOfType(json, r'authStatus')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ValidateAccessTokenResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ValidateAccessTokenResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ValidateAccessTokenResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ValidateAccessTokenResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'authStatus', + }; +} + diff --git a/mobile/openapi/lib/model/validate_library_dto.dart b/mobile/openapi/lib/model/validate_library_dto.dart new file mode 100644 index 0000000000..59c3680782 --- /dev/null +++ b/mobile/openapi/lib/model/validate_library_dto.dart @@ -0,0 +1,111 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ValidateLibraryDto { + /// Returns a new [ValidateLibraryDto] instance. + ValidateLibraryDto({ + this.exclusionPatterns = const {}, + this.importPaths = const {}, + }); + + /// Exclusion patterns (max 128) + Set exclusionPatterns; + + /// Import paths to validate (max 128) + Set importPaths; + + @override + bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryDto && + _deepEquality.equals(other.exclusionPatterns, exclusionPatterns) && + _deepEquality.equals(other.importPaths, importPaths); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (exclusionPatterns.hashCode) + + (importPaths.hashCode); + + @override + String toString() => 'ValidateLibraryDto[exclusionPatterns=$exclusionPatterns, importPaths=$importPaths]'; + + Map toJson() { + final json = {}; + json[r'exclusionPatterns'] = this.exclusionPatterns.toList(growable: false); + json[r'importPaths'] = this.importPaths.toList(growable: false); + return json; + } + + /// Returns a new [ValidateLibraryDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ValidateLibraryDto? fromJson(dynamic value) { + upgradeDto(value, "ValidateLibraryDto"); + if (value is Map) { + final json = value.cast(); + + return ValidateLibraryDto( + exclusionPatterns: json[r'exclusionPatterns'] is Iterable + ? (json[r'exclusionPatterns'] as Iterable).cast().toSet() + : const {}, + importPaths: json[r'importPaths'] is Iterable + ? (json[r'importPaths'] as Iterable).cast().toSet() + : const {}, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ValidateLibraryDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ValidateLibraryDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ValidateLibraryDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ValidateLibraryDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart b/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart new file mode 100644 index 0000000000..78cc03dc94 --- /dev/null +++ b/mobile/openapi/lib/model/validate_library_import_path_response_dto.dart @@ -0,0 +1,127 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ValidateLibraryImportPathResponseDto { + /// Returns a new [ValidateLibraryImportPathResponseDto] instance. + ValidateLibraryImportPathResponseDto({ + required this.importPath, + this.isValid = false, + this.message, + }); + + /// Import path + String importPath; + + /// Is valid + bool isValid; + + /// Validation message + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? message; + + @override + bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryImportPathResponseDto && + other.importPath == importPath && + other.isValid == isValid && + other.message == message; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (importPath.hashCode) + + (isValid.hashCode) + + (message == null ? 0 : message!.hashCode); + + @override + String toString() => 'ValidateLibraryImportPathResponseDto[importPath=$importPath, isValid=$isValid, message=$message]'; + + Map toJson() { + final json = {}; + json[r'importPath'] = this.importPath; + json[r'isValid'] = this.isValid; + if (this.message != null) { + json[r'message'] = this.message; + } else { + // json[r'message'] = null; + } + return json; + } + + /// Returns a new [ValidateLibraryImportPathResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ValidateLibraryImportPathResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ValidateLibraryImportPathResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ValidateLibraryImportPathResponseDto( + importPath: mapValueOfType(json, r'importPath')!, + isValid: mapValueOfType(json, r'isValid')!, + message: mapValueOfType(json, r'message'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ValidateLibraryImportPathResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ValidateLibraryImportPathResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ValidateLibraryImportPathResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ValidateLibraryImportPathResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'importPath', + 'isValid', + }; +} + diff --git a/mobile/openapi/lib/model/validate_library_response_dto.dart b/mobile/openapi/lib/model/validate_library_response_dto.dart new file mode 100644 index 0000000000..37f6ad07d1 --- /dev/null +++ b/mobile/openapi/lib/model/validate_library_response_dto.dart @@ -0,0 +1,99 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class ValidateLibraryResponseDto { + /// Returns a new [ValidateLibraryResponseDto] instance. + ValidateLibraryResponseDto({ + this.importPaths = const [], + }); + + /// Validation results for import paths + List importPaths; + + @override + bool operator ==(Object other) => identical(this, other) || other is ValidateLibraryResponseDto && + _deepEquality.equals(other.importPaths, importPaths); + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (importPaths.hashCode); + + @override + String toString() => 'ValidateLibraryResponseDto[importPaths=$importPaths]'; + + Map toJson() { + final json = {}; + json[r'importPaths'] = this.importPaths; + return json; + } + + /// Returns a new [ValidateLibraryResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static ValidateLibraryResponseDto? fromJson(dynamic value) { + upgradeDto(value, "ValidateLibraryResponseDto"); + if (value is Map) { + final json = value.cast(); + + return ValidateLibraryResponseDto( + importPaths: ValidateLibraryImportPathResponseDto.listFromJson(json[r'importPaths']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = ValidateLibraryResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = ValidateLibraryResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of ValidateLibraryResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = ValidateLibraryResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/lib/model/version_check_state_response_dto.dart b/mobile/openapi/lib/model/version_check_state_response_dto.dart new file mode 100644 index 0000000000..71075a681c --- /dev/null +++ b/mobile/openapi/lib/model/version_check_state_response_dto.dart @@ -0,0 +1,117 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class VersionCheckStateResponseDto { + /// Returns a new [VersionCheckStateResponseDto] instance. + VersionCheckStateResponseDto({ + required this.checkedAt, + required this.releaseVersion, + }); + + /// Last check timestamp + String? checkedAt; + + /// Release version + String? releaseVersion; + + @override + bool operator ==(Object other) => identical(this, other) || other is VersionCheckStateResponseDto && + other.checkedAt == checkedAt && + other.releaseVersion == releaseVersion; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (checkedAt == null ? 0 : checkedAt!.hashCode) + + (releaseVersion == null ? 0 : releaseVersion!.hashCode); + + @override + String toString() => 'VersionCheckStateResponseDto[checkedAt=$checkedAt, releaseVersion=$releaseVersion]'; + + Map toJson() { + final json = {}; + if (this.checkedAt != null) { + json[r'checkedAt'] = this.checkedAt; + } else { + // json[r'checkedAt'] = null; + } + if (this.releaseVersion != null) { + json[r'releaseVersion'] = this.releaseVersion; + } else { + // json[r'releaseVersion'] = null; + } + return json; + } + + /// Returns a new [VersionCheckStateResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static VersionCheckStateResponseDto? fromJson(dynamic value) { + upgradeDto(value, "VersionCheckStateResponseDto"); + if (value is Map) { + final json = value.cast(); + + return VersionCheckStateResponseDto( + checkedAt: mapValueOfType(json, r'checkedAt'), + releaseVersion: mapValueOfType(json, r'releaseVersion'), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = VersionCheckStateResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = VersionCheckStateResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of VersionCheckStateResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = VersionCheckStateResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checkedAt', + 'releaseVersion', + }; +} + diff --git a/mobile/openapi/lib/model/video_codec.dart b/mobile/openapi/lib/model/video_codec.dart new file mode 100644 index 0000000000..ba6441c8f7 --- /dev/null +++ b/mobile/openapi/lib/model/video_codec.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Target video codec +class VideoCodec { + /// Instantiate a new enum with the provided [value]. + const VideoCodec._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const h264 = VideoCodec._(r'h264'); + static const hevc = VideoCodec._(r'hevc'); + static const vp9 = VideoCodec._(r'vp9'); + static const av1 = VideoCodec._(r'av1'); + + /// List of all possible values in this [enum][VideoCodec]. + static const values = [ + h264, + hevc, + vp9, + av1, + ]; + + static VideoCodec? fromJson(dynamic value) => VideoCodecTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = VideoCodec.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [VideoCodec] to String, +/// and [decode] dynamic data back to [VideoCodec]. +class VideoCodecTypeTransformer { + factory VideoCodecTypeTransformer() => _instance ??= const VideoCodecTypeTransformer._(); + + const VideoCodecTypeTransformer._(); + + String encode(VideoCodec data) => data.value; + + /// Decodes a [dynamic value][data] to a VideoCodec. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + VideoCodec? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'h264': return VideoCodec.h264; + case r'hevc': return VideoCodec.hevc; + case r'vp9': return VideoCodec.vp9; + case r'av1': return VideoCodec.av1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [VideoCodecTypeTransformer] instance. + static VideoCodecTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/video_container.dart b/mobile/openapi/lib/model/video_container.dart new file mode 100644 index 0000000000..b1a47c8721 --- /dev/null +++ b/mobile/openapi/lib/model/video_container.dart @@ -0,0 +1,91 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +/// Accepted containers +class VideoContainer { + /// Instantiate a new enum with the provided [value]. + const VideoContainer._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const mov = VideoContainer._(r'mov'); + static const mp4 = VideoContainer._(r'mp4'); + static const ogg = VideoContainer._(r'ogg'); + static const webm = VideoContainer._(r'webm'); + + /// List of all possible values in this [enum][VideoContainer]. + static const values = [ + mov, + mp4, + ogg, + webm, + ]; + + static VideoContainer? fromJson(dynamic value) => VideoContainerTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = VideoContainer.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [VideoContainer] to String, +/// and [decode] dynamic data back to [VideoContainer]. +class VideoContainerTypeTransformer { + factory VideoContainerTypeTransformer() => _instance ??= const VideoContainerTypeTransformer._(); + + const VideoContainerTypeTransformer._(); + + String encode(VideoContainer data) => data.value; + + /// Decodes a [dynamic value][data] to a VideoContainer. + /// + /// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully, + /// then null is returned. However, if [allowNull] is false and the [dynamic value][data] + /// cannot be decoded successfully, then an [UnimplementedError] is thrown. + /// + /// The [allowNull] is very handy when an API changes and a new enum value is added or removed, + /// and users are still using an old app with the old code. + VideoContainer? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'mov': return VideoContainer.mov; + case r'mp4': return VideoContainer.mp4; + case r'ogg': return VideoContainer.ogg; + case r'webm': return VideoContainer.webm; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [VideoContainerTypeTransformer] instance. + static VideoContainerTypeTransformer? _instance; +} + diff --git a/mobile/openapi/lib/model/workflow_action_item_dto.dart b/mobile/openapi/lib/model/workflow_action_item_dto.dart new file mode 100644 index 0000000000..9222dd6ba7 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_action_item_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowActionItemDto { + /// Returns a new [WorkflowActionItemDto] instance. + WorkflowActionItemDto({ + this.actionConfig, + required this.pluginActionId, + }); + + /// Action configuration + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? actionConfig; + + /// Plugin action ID + String pluginActionId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowActionItemDto && + other.actionConfig == actionConfig && + other.pluginActionId == pluginActionId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actionConfig == null ? 0 : actionConfig!.hashCode) + + (pluginActionId.hashCode); + + @override + String toString() => 'WorkflowActionItemDto[actionConfig=$actionConfig, pluginActionId=$pluginActionId]'; + + Map toJson() { + final json = {}; + if (this.actionConfig != null) { + json[r'actionConfig'] = this.actionConfig; + } else { + // json[r'actionConfig'] = null; + } + json[r'pluginActionId'] = this.pluginActionId; + return json; + } + + /// Returns a new [WorkflowActionItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowActionItemDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowActionItemDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowActionItemDto( + actionConfig: mapValueOfType(json, r'actionConfig'), + pluginActionId: mapValueOfType(json, r'pluginActionId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowActionItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowActionItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowActionItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowActionItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'pluginActionId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_action_response_dto.dart b/mobile/openapi/lib/model/workflow_action_response_dto.dart new file mode 100644 index 0000000000..8f77e9cf2b --- /dev/null +++ b/mobile/openapi/lib/model/workflow_action_response_dto.dart @@ -0,0 +1,140 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowActionResponseDto { + /// Returns a new [WorkflowActionResponseDto] instance. + WorkflowActionResponseDto({ + required this.actionConfig, + required this.id, + required this.order, + required this.pluginActionId, + required this.workflowId, + }); + + /// Action configuration + Object? actionConfig; + + /// Action ID + String id; + + /// Action order + num order; + + /// Plugin action ID + String pluginActionId; + + /// Workflow ID + String workflowId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowActionResponseDto && + other.actionConfig == actionConfig && + other.id == id && + other.order == order && + other.pluginActionId == pluginActionId && + other.workflowId == workflowId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actionConfig == null ? 0 : actionConfig!.hashCode) + + (id.hashCode) + + (order.hashCode) + + (pluginActionId.hashCode) + + (workflowId.hashCode); + + @override + String toString() => 'WorkflowActionResponseDto[actionConfig=$actionConfig, id=$id, order=$order, pluginActionId=$pluginActionId, workflowId=$workflowId]'; + + Map toJson() { + final json = {}; + if (this.actionConfig != null) { + json[r'actionConfig'] = this.actionConfig; + } else { + // json[r'actionConfig'] = null; + } + json[r'id'] = this.id; + json[r'order'] = this.order; + json[r'pluginActionId'] = this.pluginActionId; + json[r'workflowId'] = this.workflowId; + return json; + } + + /// Returns a new [WorkflowActionResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowActionResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowActionResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowActionResponseDto( + actionConfig: mapValueOfType(json, r'actionConfig'), + id: mapValueOfType(json, r'id')!, + order: num.parse('${json[r'order']}'), + pluginActionId: mapValueOfType(json, r'pluginActionId')!, + workflowId: mapValueOfType(json, r'workflowId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowActionResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowActionResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowActionResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowActionResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actionConfig', + 'id', + 'order', + 'pluginActionId', + 'workflowId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_create_dto.dart b/mobile/openapi/lib/model/workflow_create_dto.dart new file mode 100644 index 0000000000..38665a1912 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_create_dto.dart @@ -0,0 +1,163 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowCreateDto { + /// Returns a new [WorkflowCreateDto] instance. + WorkflowCreateDto({ + this.actions = const [], + this.description, + this.enabled, + this.filters = const [], + required this.name, + required this.triggerType, + }); + + /// Workflow actions + List actions; + + /// Workflow description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Workflow enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Workflow filters + List filters; + + /// Workflow name + String name; + + /// Workflow trigger type + PluginTriggerType triggerType; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowCreateDto && + _deepEquality.equals(other.actions, actions) && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.name == name && + other.triggerType == triggerType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (description == null ? 0 : description!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (filters.hashCode) + + (name.hashCode) + + (triggerType.hashCode); + + @override + String toString() => 'WorkflowCreateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'filters'] = this.filters; + json[r'name'] = this.name; + json[r'triggerType'] = this.triggerType; + return json; + } + + /// Returns a new [WorkflowCreateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowCreateDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowCreateDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowCreateDto( + actions: WorkflowActionItemDto.listFromJson(json[r'actions']), + description: mapValueOfType(json, r'description'), + enabled: mapValueOfType(json, r'enabled'), + filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), + name: mapValueOfType(json, r'name')!, + triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowCreateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowCreateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowCreateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowCreateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'filters', + 'name', + 'triggerType', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_filter_item_dto.dart b/mobile/openapi/lib/model/workflow_filter_item_dto.dart new file mode 100644 index 0000000000..52e29c3e93 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_filter_item_dto.dart @@ -0,0 +1,118 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowFilterItemDto { + /// Returns a new [WorkflowFilterItemDto] instance. + WorkflowFilterItemDto({ + this.filterConfig, + required this.pluginFilterId, + }); + + /// Filter configuration + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + Object? filterConfig; + + /// Plugin filter ID + String pluginFilterId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterItemDto && + other.filterConfig == filterConfig && + other.pluginFilterId == pluginFilterId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (filterConfig == null ? 0 : filterConfig!.hashCode) + + (pluginFilterId.hashCode); + + @override + String toString() => 'WorkflowFilterItemDto[filterConfig=$filterConfig, pluginFilterId=$pluginFilterId]'; + + Map toJson() { + final json = {}; + if (this.filterConfig != null) { + json[r'filterConfig'] = this.filterConfig; + } else { + // json[r'filterConfig'] = null; + } + json[r'pluginFilterId'] = this.pluginFilterId; + return json; + } + + /// Returns a new [WorkflowFilterItemDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowFilterItemDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowFilterItemDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowFilterItemDto( + filterConfig: mapValueOfType(json, r'filterConfig'), + pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowFilterItemDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowFilterItemDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowFilterItemDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowFilterItemDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'pluginFilterId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_filter_response_dto.dart b/mobile/openapi/lib/model/workflow_filter_response_dto.dart new file mode 100644 index 0000000000..355378adac --- /dev/null +++ b/mobile/openapi/lib/model/workflow_filter_response_dto.dart @@ -0,0 +1,140 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowFilterResponseDto { + /// Returns a new [WorkflowFilterResponseDto] instance. + WorkflowFilterResponseDto({ + required this.filterConfig, + required this.id, + required this.order, + required this.pluginFilterId, + required this.workflowId, + }); + + /// Filter configuration + Object? filterConfig; + + /// Filter ID + String id; + + /// Filter order + num order; + + /// Plugin filter ID + String pluginFilterId; + + /// Workflow ID + String workflowId; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowFilterResponseDto && + other.filterConfig == filterConfig && + other.id == id && + other.order == order && + other.pluginFilterId == pluginFilterId && + other.workflowId == workflowId; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (filterConfig == null ? 0 : filterConfig!.hashCode) + + (id.hashCode) + + (order.hashCode) + + (pluginFilterId.hashCode) + + (workflowId.hashCode); + + @override + String toString() => 'WorkflowFilterResponseDto[filterConfig=$filterConfig, id=$id, order=$order, pluginFilterId=$pluginFilterId, workflowId=$workflowId]'; + + Map toJson() { + final json = {}; + if (this.filterConfig != null) { + json[r'filterConfig'] = this.filterConfig; + } else { + // json[r'filterConfig'] = null; + } + json[r'id'] = this.id; + json[r'order'] = this.order; + json[r'pluginFilterId'] = this.pluginFilterId; + json[r'workflowId'] = this.workflowId; + return json; + } + + /// Returns a new [WorkflowFilterResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowFilterResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowFilterResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowFilterResponseDto( + filterConfig: mapValueOfType(json, r'filterConfig'), + id: mapValueOfType(json, r'id')!, + order: num.parse('${json[r'order']}'), + pluginFilterId: mapValueOfType(json, r'pluginFilterId')!, + workflowId: mapValueOfType(json, r'workflowId')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowFilterResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowFilterResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowFilterResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowFilterResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'filterConfig', + 'id', + 'order', + 'pluginFilterId', + 'workflowId', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_response_dto.dart b/mobile/openapi/lib/model/workflow_response_dto.dart new file mode 100644 index 0000000000..ae3e6510aa --- /dev/null +++ b/mobile/openapi/lib/model/workflow_response_dto.dart @@ -0,0 +1,176 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowResponseDto { + /// Returns a new [WorkflowResponseDto] instance. + WorkflowResponseDto({ + this.actions = const [], + required this.createdAt, + required this.description, + required this.enabled, + this.filters = const [], + required this.id, + required this.name, + required this.ownerId, + required this.triggerType, + }); + + /// Workflow actions + List actions; + + /// Creation date + String createdAt; + + /// Workflow description + String description; + + /// Workflow enabled + bool enabled; + + /// Workflow filters + List filters; + + /// Workflow ID + String id; + + /// Workflow name + String? name; + + /// Owner user ID + String ownerId; + + /// Workflow trigger type + PluginTriggerType triggerType; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowResponseDto && + _deepEquality.equals(other.actions, actions) && + other.createdAt == createdAt && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.id == id && + other.name == name && + other.ownerId == ownerId && + other.triggerType == triggerType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (createdAt.hashCode) + + (description.hashCode) + + (enabled.hashCode) + + (filters.hashCode) + + (id.hashCode) + + (name == null ? 0 : name!.hashCode) + + (ownerId.hashCode) + + (triggerType.hashCode); + + @override + String toString() => 'WorkflowResponseDto[actions=$actions, createdAt=$createdAt, description=$description, enabled=$enabled, filters=$filters, id=$id, name=$name, ownerId=$ownerId, triggerType=$triggerType]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + json[r'createdAt'] = this.createdAt; + json[r'description'] = this.description; + json[r'enabled'] = this.enabled; + json[r'filters'] = this.filters; + json[r'id'] = this.id; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + json[r'ownerId'] = this.ownerId; + json[r'triggerType'] = this.triggerType; + return json; + } + + /// Returns a new [WorkflowResponseDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowResponseDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowResponseDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowResponseDto( + actions: WorkflowActionResponseDto.listFromJson(json[r'actions']), + createdAt: mapValueOfType(json, r'createdAt')!, + description: mapValueOfType(json, r'description')!, + enabled: mapValueOfType(json, r'enabled')!, + filters: WorkflowFilterResponseDto.listFromJson(json[r'filters']), + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name'), + ownerId: mapValueOfType(json, r'ownerId')!, + triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowResponseDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowResponseDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowResponseDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowResponseDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'actions', + 'createdAt', + 'description', + 'enabled', + 'filters', + 'id', + 'name', + 'ownerId', + 'triggerType', + }; +} + diff --git a/mobile/openapi/lib/model/workflow_update_dto.dart b/mobile/openapi/lib/model/workflow_update_dto.dart new file mode 100644 index 0000000000..9891fff079 --- /dev/null +++ b/mobile/openapi/lib/model/workflow_update_dto.dart @@ -0,0 +1,179 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// +// @dart=2.18 + +// ignore_for_file: unused_element, unused_import +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: lines_longer_than_80_chars + +part of openapi.api; + +class WorkflowUpdateDto { + /// Returns a new [WorkflowUpdateDto] instance. + WorkflowUpdateDto({ + this.actions = const [], + this.description, + this.enabled, + this.filters = const [], + this.name, + this.triggerType, + }); + + /// Workflow actions + List actions; + + /// Workflow description + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? description; + + /// Workflow enabled + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + bool? enabled; + + /// Workflow filters + List filters; + + /// Workflow name + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? name; + + /// Workflow trigger type + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + PluginTriggerType? triggerType; + + @override + bool operator ==(Object other) => identical(this, other) || other is WorkflowUpdateDto && + _deepEquality.equals(other.actions, actions) && + other.description == description && + other.enabled == enabled && + _deepEquality.equals(other.filters, filters) && + other.name == name && + other.triggerType == triggerType; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (actions.hashCode) + + (description == null ? 0 : description!.hashCode) + + (enabled == null ? 0 : enabled!.hashCode) + + (filters.hashCode) + + (name == null ? 0 : name!.hashCode) + + (triggerType == null ? 0 : triggerType!.hashCode); + + @override + String toString() => 'WorkflowUpdateDto[actions=$actions, description=$description, enabled=$enabled, filters=$filters, name=$name, triggerType=$triggerType]'; + + Map toJson() { + final json = {}; + json[r'actions'] = this.actions; + if (this.description != null) { + json[r'description'] = this.description; + } else { + // json[r'description'] = null; + } + if (this.enabled != null) { + json[r'enabled'] = this.enabled; + } else { + // json[r'enabled'] = null; + } + json[r'filters'] = this.filters; + if (this.name != null) { + json[r'name'] = this.name; + } else { + // json[r'name'] = null; + } + if (this.triggerType != null) { + json[r'triggerType'] = this.triggerType; + } else { + // json[r'triggerType'] = null; + } + return json; + } + + /// Returns a new [WorkflowUpdateDto] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static WorkflowUpdateDto? fromJson(dynamic value) { + upgradeDto(value, "WorkflowUpdateDto"); + if (value is Map) { + final json = value.cast(); + + return WorkflowUpdateDto( + actions: WorkflowActionItemDto.listFromJson(json[r'actions']), + description: mapValueOfType(json, r'description'), + enabled: mapValueOfType(json, r'enabled'), + filters: WorkflowFilterItemDto.listFromJson(json[r'filters']), + name: mapValueOfType(json, r'name'), + triggerType: PluginTriggerType.fromJson(json[r'triggerType']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = WorkflowUpdateDto.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = WorkflowUpdateDto.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of WorkflowUpdateDto-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = WorkflowUpdateDto.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + }; +} + diff --git a/mobile/openapi/pubspec.yaml b/mobile/openapi/pubspec.yaml new file mode 100644 index 0000000000..1c26f8707c --- /dev/null +++ b/mobile/openapi/pubspec.yaml @@ -0,0 +1,17 @@ +# +# AUTO-GENERATED FILE, DO NOT MODIFY! +# + +name: 'openapi' +version: '1.0.0' +description: 'OpenAPI API client' +homepage: 'homepage' +environment: + sdk: '>=2.12.0 <4.0.0' +dependencies: + collection: '>=1.17.0 <2.0.0' + http: '>=0.13.0 <2.0.0' + intl: any + meta: '>=1.1.8 <2.0.0' + immich_mobile: + path: ../ diff --git a/mobile/packages/ui/lib/immich_ui.dart b/mobile/packages/ui/lib/immich_ui.dart index 85875717a3..c9e510a162 100644 --- a/mobile/packages/ui/lib/immich_ui.dart +++ b/mobile/packages/ui/lib/immich_ui.dart @@ -1,17 +1,11 @@ -export 'src/color_override.dart'; export 'src/components/close_button.dart'; -export 'src/components/column_button.dart'; -export 'src/components/email_input.dart'; export 'src/components/form.dart'; export 'src/components/formatted_text.dart'; export 'src/components/icon_button.dart'; -export 'src/components/menu_item.dart'; export 'src/components/password_input.dart'; export 'src/components/text_button.dart'; export 'src/components/text_input.dart'; -export 'src/components/url_input.dart'; export 'src/constants.dart'; -export 'src/snackbar.dart'; export 'src/theme.dart'; export 'src/translation.dart'; export 'src/types.dart'; diff --git a/mobile/packages/ui/lib/src/color_override.dart b/mobile/packages/ui/lib/src/color_override.dart deleted file mode 100644 index 873f5e42ef..0000000000 --- a/mobile/packages/ui/lib/src/color_override.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/widgets.dart'; - -class ImmichColorOverride extends InheritedWidget { - const ImmichColorOverride({super.key, required this.color, required super.child}); - - final Color? color; - - static Color? maybeOf(BuildContext context) => - context.dependOnInheritedWidgetOfExactType()?.color; - - @override - bool updateShouldNotify(ImmichColorOverride oldWidget) => color != oldWidget.color; -} diff --git a/mobile/packages/ui/lib/src/components/close_button.dart b/mobile/packages/ui/lib/src/components/close_button.dart index 031f3992aa..d23c309c91 100644 --- a/mobile/packages/ui/lib/src/components/close_button.dart +++ b/mobile/packages/ui/lib/src/components/close_button.dart @@ -16,9 +16,10 @@ class ImmichCloseButton extends StatelessWidget { @override Widget build(BuildContext context) => ImmichIconButton( - icon: Icons.close, - color: color, - variant: variant, - onPressed: onPressed ?? () => Navigator.of(context).pop(), - ); + key: key, + icon: Icons.close, + color: color, + variant: variant, + onPressed: onPressed ?? () => Navigator.of(context).pop(), + ); } diff --git a/mobile/packages/ui/lib/src/components/column_button.dart b/mobile/packages/ui/lib/src/components/column_button.dart deleted file mode 100644 index 62b4c45009..0000000000 --- a/mobile/packages/ui/lib/src/components/column_button.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'dart:async'; -import 'dart:core'; - -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/constants.dart'; -import 'package:immich_ui/src/internal.dart'; - -class ImmichColumnButton extends StatefulWidget { - final IconData icon; - final String label; - final FutureOr Function() onPressed; - final FutureOr Function()? onLongPress; - final bool disabled; - final bool? loading; - - const ImmichColumnButton({ - super.key, - required this.icon, - required this.label, - required this.onPressed, - this.onLongPress, - this.disabled = false, - this.loading, - }); - - @override - State createState() => _ImmichColumnButtonState(); -} - -class _ImmichColumnButtonState extends State { - bool _running = false; - bool get _isLoading => widget.loading ?? _running; - bool get _isDisabled => widget.disabled || _isLoading; - - Future _runAction(FutureOr Function() action) async { - setState(() => _running = true); - try { - await action(); - } finally { - if (mounted) { - setState(() => _running = false); - } - } - } - - VoidCallback? get _onPressed => _isDisabled ? null : () => _runAction(widget.onPressed); - - VoidCallback? get _onLongPress => - _isDisabled || widget.onLongPress == null ? null : () => _runAction(widget.onLongPress!); - - @override - Widget build(BuildContext context) { - final foreground = context.colorOverride ?? Theme.of(context).colorScheme.onSurface; - - return TextButton( - onPressed: _onPressed, - onLongPress: _onLongPress, - style: TextButton.styleFrom( - foregroundColor: foreground, - padding: const .symmetric(horizontal: ImmichSpacing.sm, vertical: ImmichSpacing.md), - tapTargetSize: .shrinkWrap, - shape: const RoundedRectangleBorder(borderRadius: .all(.circular(ImmichRadius.xl))), - ), - child: ConstrainedBox( - constraints: const .new(maxWidth: 90), - child: Column( - mainAxisSize: .min, - children: [ - _isLoading - ? const SizedBox.square( - dimension: ImmichIconSize.md, - child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg), - ) - : Icon(widget.icon, size: ImmichIconSize.md), - const SizedBox(height: ImmichSpacing.sm), - Text( - widget.label, - maxLines: 2, - textAlign: .center, - overflow: .ellipsis, - style: const .new(fontSize: ImmichTextSize.body, fontWeight: .w500), - ), - ], - ), - ), - ); - } -} diff --git a/mobile/packages/ui/lib/src/components/email_input.dart b/mobile/packages/ui/lib/src/components/email_input.dart deleted file mode 100644 index 27d79e9f0e..0000000000 --- a/mobile/packages/ui/lib/src/components/email_input.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:immich_ui/src/components/text_input.dart'; - -/// An ImmichTextInput customized for receiving email addresses -class ImmichEmailInput extends ImmichTextInput { - const ImmichEmailInput({ - super.key, - super.controller, - super.focusNode, - super.label, - super.hintText, - super.validator, - super.onSubmit, - super.keyboardAction, - super.suffixIcon, - super.enabled, - super.autofocus, - super.autovalidateMode, - }) : super( - keyboardType: .emailAddress, - autofillHints: const [AutofillHints.email], - autocorrect: false, - smartDashesType: .disabled, - smartQuotesType: .disabled, - ); -} diff --git a/mobile/packages/ui/lib/src/components/form.dart b/mobile/packages/ui/lib/src/components/form.dart index 4b6e3af085..9e8c161806 100644 --- a/mobile/packages/ui/lib/src/components/form.dart +++ b/mobile/packages/ui/lib/src/components/form.dart @@ -4,98 +4,95 @@ import 'package:flutter/material.dart'; import 'package:immich_ui/immich_ui.dart'; import 'package:immich_ui/src/internal.dart'; -class ImmichFormController extends ChangeNotifier { - ImmichFormController({this.onSubmit}); - - FutureOr Function()? onSubmit; - final formKey = GlobalKey(); - - bool _isDisposed = false; - bool _isLoading = false; - bool get isLoading => _isLoading; - - @override - void dispose() { - _isDisposed = true; - super.dispose(); - } - - Future submit() async { - if (_isLoading) { - return; - } - if (!(formKey.currentState?.validate() ?? false)) { - return; - } - - _isLoading = true; - notifyListeners(); - try { - await onSubmit?.call(); - } finally { - _isLoading = false; - if (!_isDisposed) { - notifyListeners(); - } - } - } -} - class ImmichForm extends StatefulWidget { - final FutureOr Function()? onSubmit; - final Widget Function(BuildContext context, ImmichFormController form) builder; final String? submitText; final IconData? submitIcon; + final FutureOr Function()? onSubmit; + final Widget child; - const ImmichForm({super.key, this.onSubmit, this.submitText, this.submitIcon, required this.builder}); + const ImmichForm({ + super.key, + this.submitText, + this.submitIcon, + required this.onSubmit, + required this.child, + }); @override - State createState() => _ImmichFormState(); + State createState() => ImmichFormState(); + + static ImmichFormState of(BuildContext context) { + final scope = context.dependOnInheritedWidgetOfExactType<_ImmichFormScope>(); + if (scope == null) { + throw FlutterError( + 'ImmichForm.of() called with a context that does not contain an ImmichForm.\n' + 'No ImmichForm ancestor could be found starting from the context that was passed to ' + 'ImmichForm.of(). This usually happens when the context provided is ' + 'from a widget above the ImmichForm.\n' + 'The context used was:\n' + '$context', + ); + } + return scope._formState; + } } -class _ImmichFormState extends State { - late final ImmichFormController _controller; +class ImmichFormState extends State { + final _formKey = GlobalKey(); + bool _isLoading = false; - @override - void initState() { - super.initState(); - _controller = ImmichFormController(onSubmit: widget.onSubmit); - } + FutureOr submit() async { + final isValid = _formKey.currentState?.validate() ?? false; + if (!isValid) { + return; + } - @override - void didUpdateWidget(ImmichForm oldWidget) { - super.didUpdateWidget(oldWidget); - _controller.onSubmit = widget.onSubmit; - } + setState(() { + _isLoading = true; + }); - @override - void dispose() { - _controller.dispose(); - super.dispose(); + try { + await widget.onSubmit?.call(); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } } @override Widget build(BuildContext context) { final submitText = widget.submitText ?? context.translations.submit; - return Form( - key: _controller.formKey, - child: Column( - spacing: ImmichSpacing.md, - children: [ - widget.builder(context, _controller), - ListenableBuilder( - listenable: _controller, - builder: (context, _) => ImmichTextButton( + return _ImmichFormScope( + formState: this, + child: Form( + key: _formKey, + child: Column( + spacing: ImmichSpacing.md, + children: [ + widget.child, + ImmichTextButton( labelText: submitText, icon: widget.submitIcon, - variant: .filled, - loading: _controller.isLoading, - onPressed: _controller.submit, - disabled: _controller.onSubmit == null, + variant: ImmichVariant.filled, + loading: _isLoading, + onPressed: submit, + disabled: widget.onSubmit == null, ), - ), - ], + ], + ), ), ); } } + +class _ImmichFormScope extends InheritedWidget { + const _ImmichFormScope({required super.child, required ImmichFormState formState}) : _formState = formState; + + final ImmichFormState _formState; + + @override + bool updateShouldNotify(_ImmichFormScope oldWidget) => oldWidget._formState != _formState; +} diff --git a/mobile/packages/ui/lib/src/components/formatted_text.dart b/mobile/packages/ui/lib/src/components/formatted_text.dart index 1cb75c8e0a..95e42d834d 100644 --- a/mobile/packages/ui/lib/src/components/formatted_text.dart +++ b/mobile/packages/ui/lib/src/components/formatted_text.dart @@ -94,12 +94,12 @@ class _ImmichFormattedTextState extends State { final tag = match.group(1)!.toLowerCase(); final content = match.group(2)!; - final span = widget.spanBuilder?.call(tag); - final style = span?.style ?? _defaultTextStyle(tag); + final formattedSpan = (widget.spanBuilder ?? _defaultSpanBuilder)(tag); + final style = formattedSpan.style ?? _defaultTextStyle(tag); GestureRecognizer? recognizer; - if (span?.onTap != null) { - recognizer = TapGestureRecognizer()..onTap = span!.onTap; + if (formattedSpan.onTap != null) { + recognizer = TapGestureRecognizer()..onTap = formattedSpan.onTap; _recognizers.add(recognizer); } spans.add(TextSpan(text: content, style: style, recognizer: recognizer)); @@ -114,12 +114,19 @@ class _ImmichFormattedTextState extends State { return spans; } + FormattedSpan _defaultSpanBuilder(String tag) => switch (tag) { + 'b' => const FormattedSpan(style: TextStyle(fontWeight: FontWeight.bold)), + 'link' => const FormattedSpan(style: TextStyle(decoration: TextDecoration.underline)), + _ when tag.endsWith('-link') => const FormattedSpan(style: TextStyle(decoration: TextDecoration.underline)), + _ => const FormattedSpan(), + }; + TextStyle? _defaultTextStyle(String tag) => switch (tag) { - 'b' => const TextStyle(fontWeight: FontWeight.bold), - 'link' => const TextStyle(decoration: TextDecoration.underline), - _ when tag.endsWith('-link') => const TextStyle(decoration: TextDecoration.underline), - _ => null, - }; + 'b' => const TextStyle(fontWeight: FontWeight.bold), + 'link' => const TextStyle(decoration: TextDecoration.underline), + _ when tag.endsWith('-link') => const TextStyle(decoration: TextDecoration.underline), + _ => null, + }; @override Widget build(BuildContext context) { diff --git a/mobile/packages/ui/lib/src/components/icon_button.dart b/mobile/packages/ui/lib/src/components/icon_button.dart index c0e1bcc3fa..dc140b71f9 100644 --- a/mobile/packages/ui/lib/src/components/icon_button.dart +++ b/mobile/packages/ui/lib/src/components/icon_button.dart @@ -1,89 +1,54 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:immich_ui/src/internal.dart'; +import 'package:immich_ui/src/types.dart'; -class ImmichIconButton extends StatefulWidget { +class ImmichIconButton extends StatelessWidget { final IconData icon; - final FutureOr Function() onPressed; - final FutureOr Function()? onLongPress; + final VoidCallback onPressed; final ImmichVariant variant; final ImmichColor color; final bool disabled; - final bool? loading; const ImmichIconButton({ super.key, required this.icon, required this.onPressed, - this.onLongPress, - this.color = .primary, - this.variant = .filled, + this.color = ImmichColor.primary, + this.variant = ImmichVariant.filled, this.disabled = false, - this.loading, }); - @override - State createState() => _ImmichIconButtonState(); -} - -class _ImmichIconButtonState extends State { - bool _running = false; - bool get _isLoading => widget.loading ?? _running; - bool get _isDisabled => widget.disabled || _isLoading; - - Future _runAction(FutureOr Function() action) async { - setState(() => _running = true); - try { - await action(); - } finally { - if (mounted) { - setState(() => _running = false); - } - } - } - - VoidCallback? get _onPressed => _isDisabled ? null : () => _runAction(widget.onPressed); - - VoidCallback? get _onLongPress => - _isDisabled || widget.onLongPress == null ? null : () => _runAction(widget.onLongPress!); - @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - final background = switch (widget.variant) { - .filled => switch (widget.color) { - .primary => colorScheme.primary, - .secondary => colorScheme.secondary, - }, - .ghost => Colors.transparent, + final background = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => colorScheme.primary, + ImmichColor.secondary => colorScheme.secondary, + }, + ImmichVariant.ghost => Colors.transparent, }; - final foreground = - context.colorOverride ?? - switch (widget.variant) { - .filled => switch (widget.color) { - .primary => colorScheme.onPrimary, - .secondary => colorScheme.onSecondary, - }, - .ghost => switch (widget.color) { - .primary => colorScheme.primary, - .secondary => colorScheme.secondary, - }, - }; + final foreground = switch (variant) { + ImmichVariant.filled => switch (color) { + ImmichColor.primary => colorScheme.onPrimary, + ImmichColor.secondary => colorScheme.onSecondary, + }, + ImmichVariant.ghost => switch (color) { + ImmichColor.primary => colorScheme.primary, + ImmichColor.secondary => colorScheme.secondary, + }, + }; + + final effectiveOnPressed = disabled ? null : onPressed; return IconButton( - icon: _isLoading - ? const SizedBox.square( - dimension: ImmichIconSize.sm, - child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.md), - ) - : Icon(widget.icon), - onPressed: _onPressed, - onLongPress: _onLongPress, - style: IconButton.styleFrom(backgroundColor: background, foregroundColor: foreground), + icon: Icon(icon), + onPressed: effectiveOnPressed, + style: IconButton.styleFrom( + backgroundColor: background, + foregroundColor: foreground, + ), ); } } diff --git a/mobile/packages/ui/lib/src/components/menu_item.dart b/mobile/packages/ui/lib/src/components/menu_item.dart deleted file mode 100644 index 84a0936e5d..0000000000 --- a/mobile/packages/ui/lib/src/components/menu_item.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/constants.dart'; -import 'package:immich_ui/src/internal.dart'; - -class ImmichMenu extends StatefulWidget { - final List children; - final MenuAnchorChildBuilder builder; - final MenuStyle? style; - final bool consumeOutsideTap; - final Widget? child; - - const ImmichMenu({ - super.key, - required this.children, - required this.builder, - this.style, - this.consumeOutsideTap = false, - this.child, - }); - - @override - State createState() => _ImmichMenuState(); -} - -class _ImmichMenuState extends State { - final _controller = MenuController(); - - @override - Widget build(BuildContext context) { - return _ImmichMenuScope( - controller: _controller, - child: MenuAnchor( - controller: _controller, - style: widget.style, - consumeOutsideTap: widget.consumeOutsideTap, - menuChildren: widget.children, - builder: widget.builder, - child: widget.child, - ), - ); - } -} - -class _ImmichMenuScope extends InheritedWidget { - final MenuController controller; - - const _ImmichMenuScope({required this.controller, required super.child}); - - static MenuController? maybeOf(BuildContext context) => - context.dependOnInheritedWidgetOfExactType<_ImmichMenuScope>()?.controller; - - @override - bool updateShouldNotify(_ImmichMenuScope oldWidget) => controller != oldWidget.controller; -} - -class ImmichMenuItem extends StatefulWidget { - final IconData icon; - final String label; - final FutureOr Function() onPressed; - final bool disabled; - - const ImmichMenuItem({ - super.key, - required this.icon, - required this.label, - required this.onPressed, - this.disabled = false, - }); - - @override - State createState() => _ImmichMenuItemState(); -} - -class _ImmichMenuItemState extends State { - Future _onPressed(MenuController? controller) async { - try { - await widget.onPressed(); - } finally { - controller?.close(); - } - } - - @override - Widget build(BuildContext context) { - final controller = _ImmichMenuScope.maybeOf(context); - return MenuItemButton( - onPressed: widget.disabled ? null : () => _onPressed(controller), - closeOnActivate: controller == null, - style: MenuItemButton.styleFrom( - foregroundColor: context.colorOverride, - alignment: .centerLeft, - padding: const .symmetric(horizontal: ImmichSpacing.lg, vertical: ImmichSpacing.md), - ), - leadingIcon: Icon(widget.icon, size: ImmichIconSize.sm), - child: Text(widget.label, style: const .new(fontSize: ImmichTextSize.body)), - ); - } -} diff --git a/mobile/packages/ui/lib/src/components/password_input.dart b/mobile/packages/ui/lib/src/components/password_input.dart index aa66d43b4d..bd5a149354 100644 --- a/mobile/packages/ui/lib/src/components/password_input.dart +++ b/mobile/packages/ui/lib/src/components/password_input.dart @@ -8,7 +8,7 @@ class ImmichPasswordInput extends StatefulWidget { final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(String value)? onSubmit; + final void Function(BuildContext, String)? onSubmit; final TextInputAction? keyboardAction; const ImmichPasswordInput({ @@ -47,14 +47,12 @@ class _ImmichPasswordInputState extends State { onSubmit: widget.onSubmit, keyboardAction: widget.keyboardAction, obscureText: !_visible, - autocorrect: false, - smartDashesType: .disabled, - smartQuotesType: .disabled, suffixIcon: IconButton( onPressed: _toggleVisibility, icon: Icon(_visible ? Icons.visibility_off_rounded : Icons.visibility_rounded), ), - autofillHints: const [AutofillHints.password], + autofillHints: [AutofillHints.password], + keyboardType: TextInputType.text, ); } } diff --git a/mobile/packages/ui/lib/src/components/text_button.dart b/mobile/packages/ui/lib/src/components/text_button.dart index 6cbc08473b..6dc677aee2 100644 --- a/mobile/packages/ui/lib/src/components/text_button.dart +++ b/mobile/packages/ui/lib/src/components/text_button.dart @@ -1,91 +1,85 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; +import 'package:immich_ui/src/constants.dart'; +import 'package:immich_ui/src/types.dart'; -class ImmichTextButton extends StatefulWidget { +class ImmichTextButton extends StatelessWidget { final String labelText; final IconData? icon; final FutureOr Function() onPressed; - final FutureOr Function()? onLongPress; final ImmichVariant variant; + final ImmichColor color; final bool expanded; + final bool loading; final bool disabled; - final bool? loading; const ImmichTextButton({ super.key, required this.labelText, this.icon, required this.onPressed, - this.onLongPress, - this.variant = .filled, + this.variant = ImmichVariant.filled, + this.color = ImmichColor.primary, this.expanded = true, - + this.loading = false, this.disabled = false, - this.loading, }); - @override - State createState() => _ImmichTextButtonState(); -} - -class _ImmichTextButtonState extends State { - bool _running = false; - bool get _isLoading => widget.loading ?? _running; - bool get _isDisabled => widget.disabled || _isLoading; - - Future _runAction(FutureOr Function() action) async { - setState(() => _running = true); - try { - await action(); - } finally { - if (mounted) { - setState(() => _running = false); - } - } - } - - VoidCallback? get _onPressed => _isDisabled ? null : () => _runAction(widget.onPressed); - - VoidCallback? get _onLongPress => - _isDisabled || widget.onLongPress == null ? null : () => _runAction(widget.onLongPress!); - - @override - Widget build(BuildContext context) { - final Widget? icon = _isLoading + Widget _buildButton(ImmichVariant variant) { + final Widget? effectiveIcon = loading ? const SizedBox.square( dimension: ImmichIconSize.md, child: CircularProgressIndicator(strokeWidth: ImmichBorderWidth.lg), ) - : widget.icon != null - ? Icon(widget.icon, fontWeight: .w600) - : null; + : icon != null + ? Icon(icon, fontWeight: FontWeight.w600) + : null; + final hasIcon = effectiveIcon != null; - final label = Text( - widget.labelText, - style: const .new(fontSize: ImmichTextSize.body, fontWeight: .bold), - ); - final style = ElevatedButton.styleFrom(padding: const .symmetric(vertical: ImmichSpacing.md)); + final label = Text(labelText, style: const TextStyle(fontSize: ImmichTextSize.body, fontWeight: FontWeight.bold)); + final style = ElevatedButton.styleFrom(padding: const EdgeInsets.symmetric(vertical: ImmichSpacing.md)); - final button = switch (widget.variant) { - ImmichVariant.filled => ElevatedButton.icon( - style: style, - onPressed: _onPressed, - onLongPress: _onLongPress, - icon: icon, - label: label, - ), - ImmichVariant.ghost => TextButton.icon( - style: style, - onPressed: _onPressed, - onLongPress: _onLongPress, - icon: icon, - label: label, - ), - }; + final effectiveOnPressed = disabled || loading ? null : onPressed; - if (widget.expanded) { + switch (variant) { + case ImmichVariant.filled: + if (hasIcon) { + return ElevatedButton.icon( + style: style, + onPressed: effectiveOnPressed, + icon: effectiveIcon, + label: label, + ); + } + + return ElevatedButton( + style: style, + onPressed: effectiveOnPressed, + child: label, + ); + case ImmichVariant.ghost: + if (hasIcon) { + return TextButton.icon( + style: style, + onPressed: effectiveOnPressed, + icon: effectiveIcon, + label: label, + ); + } + + return TextButton( + style: style, + onPressed: effectiveOnPressed, + child: label, + ); + } + } + + @override + Widget build(BuildContext context) { + final button = _buildButton(variant); + if (expanded) { return SizedBox(width: double.infinity, child: button); } return button; diff --git a/mobile/packages/ui/lib/src/components/text_input.dart b/mobile/packages/ui/lib/src/components/text_input.dart index 71d3248ac1..1b3fb91f51 100644 --- a/mobile/packages/ui/lib/src/components/text_input.dart +++ b/mobile/packages/ui/lib/src/components/text_input.dart @@ -1,46 +1,33 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class ImmichTextInput extends StatefulWidget { - final String? label; + final String label; final String? hintText; final TextEditingController? controller; final FocusNode? focusNode; final String? Function(String?)? validator; - final void Function(String value)? onSubmit; + final void Function(BuildContext, String)? onSubmit; final TextInputType keyboardType; final TextInputAction? keyboardAction; final List? autofillHints; final Widget? suffixIcon; final bool obscureText; - final bool autocorrect; - final SmartDashesType? smartDashesType; - final SmartQuotesType? smartQuotesType; - final List? inputFormatters; - final bool enabled; - final bool autofocus; - final AutovalidateMode? autovalidateMode; + final bool autoCorrect; const ImmichTextInput({ super.key, this.controller, this.focusNode, - this.label, + required this.label, this.hintText, this.validator, this.onSubmit, - this.keyboardType = .text, + this.keyboardType = TextInputType.text, this.keyboardAction, this.autofillHints, this.suffixIcon, this.obscureText = false, - this.autocorrect = true, - this.smartDashesType, - this.smartQuotesType, - this.inputFormatters, - this.enabled = true, - this.autofocus = false, - this.autovalidateMode, + this.autoCorrect = true, }); @override @@ -49,6 +36,7 @@ class ImmichTextInput extends StatefulWidget { class _ImmichTextInputState extends State { late final FocusNode _focusNode; + String? _error; @override void initState() { @@ -64,26 +52,40 @@ class _ImmichTextInputState extends State { super.dispose(); } + String? _validateInput(String? value) { + setState(() { + _error = widget.validator?.call(value); + }); + return null; + } + + bool get _hasError => _error != null && _error!.isNotEmpty; + @override Widget build(BuildContext context) { + final themeData = Theme.of(context); + return TextFormField( controller: widget.controller, focusNode: _focusNode, - enabled: widget.enabled, - autofocus: widget.autofocus, - autovalidateMode: widget.autovalidateMode, - decoration: InputDecoration(hintText: widget.hintText, labelText: widget.label, suffixIcon: widget.suffixIcon), + decoration: InputDecoration( + hintText: widget.hintText, + labelText: widget.label, + labelStyle: themeData.inputDecorationTheme.labelStyle?.copyWith( + color: _hasError ? themeData.colorScheme.error : null, + ), + errorText: _error, + suffixIcon: widget.suffixIcon, + ), obscureText: widget.obscureText, - validator: widget.validator, - textInputAction: widget.keyboardAction, - onTapOutside: (_) => _focusNode.unfocus(), - onFieldSubmitted: (value) => widget.onSubmit?.call(value), + validator: _validateInput, keyboardType: widget.keyboardType, + textInputAction: widget.keyboardAction, + autocorrect: widget.autoCorrect, autofillHints: widget.autofillHints, - autocorrect: widget.autocorrect, - smartDashesType: widget.smartDashesType, - smartQuotesType: widget.smartQuotesType, - inputFormatters: widget.inputFormatters, + onTap: () => setState(() => _error = null), + onTapOutside: (_) => _focusNode.unfocus(), + onFieldSubmitted: (value) => widget.onSubmit?.call(context, value), ); } } diff --git a/mobile/packages/ui/lib/src/components/url_input.dart b/mobile/packages/ui/lib/src/components/url_input.dart deleted file mode 100644 index b23136a122..0000000000 --- a/mobile/packages/ui/lib/src/components/url_input.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:immich_ui/src/components/text_input.dart'; - -class ImmichURLInput extends ImmichTextInput { - ImmichURLInput({ - super.key, - super.controller, - super.focusNode, - super.label, - super.hintText, - super.validator, - super.onSubmit, - super.keyboardAction, - super.suffixIcon, - super.enabled, - super.autofocus, - super.autovalidateMode, - }) : super( - keyboardType: .url, - autofillHints: const [AutofillHints.url], - autocorrect: false, - smartDashesType: .disabled, - smartQuotesType: .disabled, - inputFormatters: _formatters, - ); - - static final List _formatters = List.unmodifiable([ - FilteringTextInputFormatter.deny(RegExp(r'\s')), - ]); -} diff --git a/mobile/packages/ui/lib/src/internal.dart b/mobile/packages/ui/lib/src/internal.dart index 4ca1f4b073..7f503927ff 100644 --- a/mobile/packages/ui/lib/src/internal.dart +++ b/mobile/packages/ui/lib/src/internal.dart @@ -1,11 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:immich_ui/src/color_override.dart'; import 'package:immich_ui/src/translation.dart'; extension TranslationHelper on BuildContext { ImmichTranslations get translations => ImmichTranslationProvider.of(this); } - -extension ColorHelper on BuildContext { - Color? get colorOverride => ImmichColorOverride.maybeOf(this); -} diff --git a/mobile/packages/ui/lib/src/previews.dart b/mobile/packages/ui/lib/src/previews.dart deleted file mode 100644 index 076bc253c9..0000000000 --- a/mobile/packages/ui/lib/src/previews.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/widget_previews.dart'; -import 'package:immich_ui/src/theme.dart'; - -const ColorScheme _lightColorScheme = ColorScheme.light( - primary: Color(0xFF4250AF), - onPrimary: Colors.white, - primaryContainer: Color(0xFFD4D6F0), - onPrimaryContainer: Color(0xFF181E44), - secondary: Color(0xFF737373), - onSecondary: Colors.white, - error: Color(0xFFE53E3E), - onError: Colors.white, - surface: Color(0xFFFAFAFA), - onSurface: Color(0xFF1A1C1E), - surfaceContainerHighest: Color(0xFFE3E4E8), - outline: Color(0xFFD1D3D9), - outlineVariant: Color(0xFFD4D4D4), -); - -const ColorScheme _darkColorScheme = ColorScheme.dark( - primary: Color(0xFFACCBFA), - onPrimary: Color(0xFF0F1433), - primaryContainer: Color(0xFF616D94), - onPrimaryContainer: Color(0xFFD4D6F0), - secondary: Color(0xFFC4C6D0), - onSecondary: Color(0xFF2E3042), - error: Color(0xFFE88080), - onError: Color(0xFF0F1433), - surface: Color(0xFF0A0A0A), - onSurface: Color(0xFFE3E3E6), - surfaceContainerHighest: Color(0xFF262626), - outline: Color(0xFF8E9099), - outlineVariant: Color(0xFF43464F), -); - -PreviewThemeData immichPreviewTheme() => PreviewThemeData( - materialLight: ThemeData(colorScheme: _lightColorScheme, useMaterial3: true), - materialDark: ThemeData(colorScheme: _darkColorScheme, useMaterial3: true), - ); - -Widget immichPreviewWrapper(Widget child) { - return Builder( - builder: (context) => ImmichThemeProvider( - colorScheme: Theme.of(context).colorScheme, - child: Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - body: Padding( - padding: const EdgeInsets.all(16), - child: Align(alignment: Alignment.topLeft, child: child), - ), - ), - ), - ); -} - -final class ImmichPreview extends Preview { - const ImmichPreview({super.name, super.group, super.size, super.textScaleFactor}) - : super(theme: immichPreviewTheme, wrapper: immichPreviewWrapper); -} diff --git a/mobile/packages/ui/lib/src/previews/close_button.dart b/mobile/packages/ui/lib/src/previews/close_button.dart deleted file mode 100644 index 678d6bed18..0000000000 --- a/mobile/packages/ui/lib/src/previews/close_button.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/close_button.dart'; -import 'package:immich_ui/src/previews.dart'; -import 'package:immich_ui/src/types.dart'; - -void _previewNoop() {} - -@ImmichPreview(group: 'CloseButton', name: 'Variants') -Widget previewCloseButtonVariants() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichCloseButton(onPressed: _previewNoop), - ImmichCloseButton(onPressed: _previewNoop, variant: ImmichVariant.filled), - ], - ); - -@ImmichPreview(group: 'CloseButton', name: 'Colors') -Widget previewCloseButtonColors() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichCloseButton(onPressed: _previewNoop), - ImmichCloseButton(onPressed: _previewNoop, color: ImmichColor.secondary), - ], - ); diff --git a/mobile/packages/ui/lib/src/previews/column_button.dart b/mobile/packages/ui/lib/src/previews/column_button.dart deleted file mode 100644 index 95f5bfee53..0000000000 --- a/mobile/packages/ui/lib/src/previews/column_button.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/column_button.dart'; -import 'package:immich_ui/src/previews.dart'; - -void _previewNoop() {} - -@ImmichPreview(group: 'ColumnButton', name: 'Default') -Widget previewColumnButtonDefault() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichColumnButton(onPressed: _previewNoop, icon: Icons.favorite_border_rounded, label: 'Favorite'), - ImmichColumnButton(onPressed: _previewNoop, icon: Icons.archive_outlined, label: 'Archive'), - ImmichColumnButton(onPressed: _previewNoop, icon: Icons.delete_outline_rounded, label: 'Delete'), - ], -); - -@ImmichPreview(group: 'ColumnButton', name: 'Loading') -Widget previewColumnButtonLoading() => ImmichColumnButton( - onPressed: () => Future.delayed(const .new(seconds: 2)), - icon: Icons.download, - label: 'Download', -); - -@ImmichPreview(group: 'ColumnButton', name: 'Disabled') -Widget previewColumnButtonDisabled() => - const ImmichColumnButton(onPressed: _previewNoop, icon: Icons.ios_share_rounded, label: 'Share', disabled: true); diff --git a/mobile/packages/ui/lib/src/previews/email_input.dart b/mobile/packages/ui/lib/src/previews/email_input.dart deleted file mode 100644 index b9d1d64ea0..0000000000 --- a/mobile/packages/ui/lib/src/previews/email_input.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/email_input.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'EmailInput', name: 'Basic') -Widget previewEmailInput() => const _PreviewEmailInput(); - -class _PreviewEmailInput extends StatefulWidget { - const _PreviewEmailInput(); - - @override - State<_PreviewEmailInput> createState() => _PreviewEmailInputState(); -} - -class _PreviewEmailInputState extends State<_PreviewEmailInput> { - final _controller = TextEditingController(); - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ImmichEmailInput(label: 'Email', hintText: 'user@immich.app', controller: _controller); - } -} diff --git a/mobile/packages/ui/lib/src/previews/form.dart b/mobile/packages/ui/lib/src/previews/form.dart deleted file mode 100644 index 9e488cfd68..0000000000 --- a/mobile/packages/ui/lib/src/previews/form.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/form.dart'; -import 'package:immich_ui/src/components/password_input.dart'; -import 'package:immich_ui/src/components/text_input.dart'; -import 'package:immich_ui/src/constants.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'Form', name: 'Login Form') -Widget previewFormLogin() => const _PreviewLoginForm(); - -class _PreviewLoginForm extends StatefulWidget { - const _PreviewLoginForm(); - - @override - State<_PreviewLoginForm> createState() => _PreviewLoginFormState(); -} - -class _PreviewLoginFormState extends State<_PreviewLoginForm> { - final _emailController = TextEditingController(); - final _passwordController = TextEditingController(); - String _result = ''; - - @override - void dispose() { - _emailController.dispose(); - _passwordController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ImmichForm( - submitText: 'Login', - submitIcon: Icons.login, - onSubmit: () async { - await Future.delayed(const Duration(seconds: 1)); - if (!mounted) { - return; - } - setState(() { - _result = 'Form submitted!'; - }); - }, - builder: (context, form) => Column( - spacing: ImmichSpacing.sm, - children: [ - ImmichTextInput( - label: 'Email', - controller: _emailController, - keyboardType: TextInputType.emailAddress, - validator: (value) => value?.isEmpty ?? true ? 'Required' : null, - ), - ImmichPasswordInput( - label: 'Password', - controller: _passwordController, - validator: (value) => value?.isEmpty ?? true ? 'Required' : null, - onSubmit: (_) => form.submit(), - ), - ], - ), - ), - if (_result.isNotEmpty) ...[ - const SizedBox(height: 16), - Text(_result, style: const TextStyle(color: Colors.green)), - ], - ], - ); - } -} diff --git a/mobile/packages/ui/lib/src/previews/formatted_text.dart b/mobile/packages/ui/lib/src/previews/formatted_text.dart deleted file mode 100644 index 9ef3b4b851..0000000000 --- a/mobile/packages/ui/lib/src/previews/formatted_text.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/formatted_text.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'FormattedText', name: 'Bold') -Widget previewFormattedTextBold() => const ImmichFormattedText('This is bold text.'); - -@ImmichPreview(group: 'FormattedText', name: 'Links') -Widget previewFormattedTextLinks() => const _PreviewFormattedTextLinks(); - -@ImmichPreview(group: 'FormattedText', name: 'Mixed Content') -Widget previewFormattedTextMixed() => const _PreviewFormattedTextMixed(); - -class _PreviewFormattedTextLinks extends StatelessWidget { - const _PreviewFormattedTextLinks(); - - @override - Widget build(BuildContext context) { - return ImmichFormattedText( - 'Read the documentation or visit GitHub.', - spanBuilder: (tag) => FormattedSpan( - onTap: switch (tag) { - 'docs-link' => - () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))), - 'github-link' => - () => ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))), - _ => null, - }, - ), - ); - } -} - -class _PreviewFormattedTextMixed extends StatelessWidget { - const _PreviewFormattedTextMixed(); - - @override - Widget build(BuildContext context) { - return ImmichFormattedText( - 'You can use bold text and links together.', - spanBuilder: (tag) => switch (tag) { - 'b' => const FormattedSpan(style: TextStyle(fontWeight: FontWeight.bold)), - _ => FormattedSpan( - onTap: () => - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Link clicked!'))), - ), - }, - ); - } -} diff --git a/mobile/packages/ui/lib/src/previews/icon_button.dart b/mobile/packages/ui/lib/src/previews/icon_button.dart deleted file mode 100644 index 6a0196bc81..0000000000 --- a/mobile/packages/ui/lib/src/previews/icon_button.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/icon_button.dart'; -import 'package:immich_ui/src/previews.dart'; -import 'package:immich_ui/src/types.dart'; - -void _previewNoop() {} - -@ImmichPreview(group: 'IconButton', name: 'Variants') -Widget previewIconButtonVariants() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichIconButton(icon: Icons.add, onPressed: _previewNoop), - ImmichIconButton(icon: Icons.edit, onPressed: _previewNoop, variant: ImmichVariant.ghost), - ], - ); - -@ImmichPreview(group: 'IconButton', name: 'Colors') -Widget previewIconButtonColors() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichIconButton(icon: Icons.favorite, onPressed: _previewNoop), - ImmichIconButton(icon: Icons.delete, onPressed: _previewNoop, color: ImmichColor.secondary), - ], - ); - -@ImmichPreview(group: 'IconButton', name: 'Disabled') -Widget previewIconButtonDisabled() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichIconButton(icon: Icons.settings, onPressed: _previewNoop, disabled: true), - ImmichIconButton( - icon: Icons.settings, - onPressed: _previewNoop, - disabled: true, - variant: ImmichVariant.ghost, - ), - ], - ); diff --git a/mobile/packages/ui/lib/src/previews/menu_item.dart b/mobile/packages/ui/lib/src/previews/menu_item.dart deleted file mode 100644 index 759c55cf9c..0000000000 --- a/mobile/packages/ui/lib/src/previews/menu_item.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/menu_item.dart'; -import 'package:immich_ui/src/previews.dart'; - -void _previewNoop() {} - -@ImmichPreview(group: 'MenuItem', name: 'Default') -Widget previewMenuItemDefault() => const Column( - mainAxisSize: MainAxisSize.min, - children: [ - ImmichMenuItem(onPressed: _previewNoop, icon: Icons.info_outline, label: 'Info'), - ImmichMenuItem(onPressed: _previewNoop, icon: Icons.help_outline_rounded, label: 'Troubleshoot'), - ImmichMenuItem(onPressed: _previewNoop, icon: Icons.cast_rounded, label: 'Cast'), - ], -); - -@ImmichPreview(group: 'MenuItem', name: 'Disabled') -Widget previewMenuItemDisabled() => - const ImmichMenuItem(onPressed: _previewNoop, icon: Icons.delete_outline_rounded, label: 'Delete', disabled: true); diff --git a/mobile/packages/ui/lib/src/previews/password_input.dart b/mobile/packages/ui/lib/src/previews/password_input.dart deleted file mode 100644 index 72bd9cbfc5..0000000000 --- a/mobile/packages/ui/lib/src/previews/password_input.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/password_input.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'PasswordInput', name: 'With Validator') -Widget previewPasswordInput() => ImmichPasswordInput( - label: 'Password', - hintText: 'Enter your password', - validator: (value) { - if (value == null || value.isEmpty) { - return 'Password is required'; - } - if (value.length < 8) { - return 'Password must be at least 8 characters'; - } - return null; - }, - ); diff --git a/mobile/packages/ui/lib/src/previews/snackbar.dart b/mobile/packages/ui/lib/src/previews/snackbar.dart deleted file mode 100644 index 1f98369e32..0000000000 --- a/mobile/packages/ui/lib/src/previews/snackbar.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/constants.dart'; -import 'package:immich_ui/src/previews.dart'; -import 'package:immich_ui/src/snackbar.dart'; - -@ImmichPreview(group: 'Snackbar', name: 'Types') -Widget previewSnackbarTypes() => const _SnackbarDemo(); - -class _SnackbarDemo extends StatelessWidget { - const _SnackbarDemo(); - - @override - Widget build(BuildContext context) { - return ScaffoldMessenger( - key: scaffoldMessengerKey, - child: Scaffold( - backgroundColor: Colors.transparent, - body: Center( - child: Wrap( - spacing: ImmichSpacing.md, - runSpacing: ImmichSpacing.md, - children: [ - ElevatedButton(onPressed: () => snackbar.info('Info message'), child: const Text('Info')), - ElevatedButton(onPressed: () => snackbar.success('Saved'), child: const Text('Success')), - ElevatedButton(onPressed: () => snackbar.error('Something failed'), child: const Text('Error')), - ], - ), - ), - ), - ); - } -} diff --git a/mobile/packages/ui/lib/src/previews/text_button.dart b/mobile/packages/ui/lib/src/previews/text_button.dart deleted file mode 100644 index fc1af0be3e..0000000000 --- a/mobile/packages/ui/lib/src/previews/text_button.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/text_button.dart'; -import 'package:immich_ui/src/previews.dart'; -import 'package:immich_ui/src/types.dart'; - -void _previewNoop() {} - -@ImmichPreview(group: 'TextButton', name: 'Variants') -Widget previewTextButtonVariants() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton(onPressed: _previewNoop, labelText: 'Filled', expanded: false), - ImmichTextButton(onPressed: _previewNoop, labelText: 'Ghost', variant: ImmichVariant.ghost, expanded: false), - ], -); - -@ImmichPreview(group: 'TextButton', name: 'With Icons') -Widget previewTextButtonWithIcons() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton(onPressed: _previewNoop, labelText: 'With Icon', icon: Icons.add, expanded: false), - ImmichTextButton( - onPressed: _previewNoop, - labelText: 'Download', - icon: Icons.download, - variant: ImmichVariant.ghost, - expanded: false, - ), - ], -); - -@ImmichPreview(group: 'TextButton', name: 'Loading') -Widget previewTextButtonLoading() => ImmichTextButton( - onPressed: () => Future.delayed(const Duration(seconds: 2)), - labelText: 'Click me', - expanded: false, -); - -@ImmichPreview(group: 'TextButton', name: 'Disabled') -Widget previewTextButtonDisabled() => const Wrap( - spacing: 12, - runSpacing: 12, - children: [ - ImmichTextButton(onPressed: _previewNoop, labelText: 'Disabled', disabled: true, expanded: false), - ImmichTextButton( - onPressed: _previewNoop, - labelText: 'Disabled Ghost', - variant: ImmichVariant.ghost, - disabled: true, - expanded: false, - ), - ], -); diff --git a/mobile/packages/ui/lib/src/previews/text_input.dart b/mobile/packages/ui/lib/src/previews/text_input.dart deleted file mode 100644 index fab58c8cc4..0000000000 --- a/mobile/packages/ui/lib/src/previews/text_input.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/text_input.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'TextInput', name: 'Basic') -Widget previewTextInputBasic() => const _PreviewTextInputBasic(); - -@ImmichPreview(group: 'TextInput', name: 'With Validator') -Widget previewTextInputValidator() => const _PreviewTextInputValidator(); - -class _PreviewTextInputBasic extends StatefulWidget { - const _PreviewTextInputBasic(); - - @override - State<_PreviewTextInputBasic> createState() => _PreviewTextInputBasicState(); -} - -class _PreviewTextInputBasicState extends State<_PreviewTextInputBasic> { - final _controller = TextEditingController(); - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ImmichTextInput( - label: 'Email', - hintText: 'Enter your email', - controller: _controller, - keyboardType: TextInputType.emailAddress, - ); - } -} - -class _PreviewTextInputValidator extends StatefulWidget { - const _PreviewTextInputValidator(); - - @override - State<_PreviewTextInputValidator> createState() => _PreviewTextInputValidatorState(); -} - -class _PreviewTextInputValidatorState extends State<_PreviewTextInputValidator> { - final _controller = TextEditingController(); - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ImmichTextInput( - label: 'Username', - controller: _controller, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Username is required'; - } - if (value.length < 3) { - return 'Username must be at least 3 characters'; - } - return null; - }, - ); - } -} diff --git a/mobile/packages/ui/lib/src/previews/url_input.dart b/mobile/packages/ui/lib/src/previews/url_input.dart deleted file mode 100644 index 6819ac5796..0000000000 --- a/mobile/packages/ui/lib/src/previews/url_input.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:immich_ui/src/components/url_input.dart'; -import 'package:immich_ui/src/previews.dart'; - -@ImmichPreview(group: 'URLInput', name: 'Basic') -Widget previewUrlInput() => const _PreviewUrlInput(); - -class _PreviewUrlInput extends StatefulWidget { - const _PreviewUrlInput(); - - @override - State<_PreviewUrlInput> createState() => _PreviewUrlInputState(); -} - -class _PreviewUrlInputState extends State<_PreviewUrlInput> { - final _controller = TextEditingController(); - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ImmichURLInput(label: 'Server URL', hintText: 'https://demo.immich.com', controller: _controller); - } -} diff --git a/mobile/packages/ui/lib/src/snackbar.dart b/mobile/packages/ui/lib/src/snackbar.dart deleted file mode 100644 index 4e3c818df3..0000000000 --- a/mobile/packages/ui/lib/src/snackbar.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:immich_ui/src/internal.dart'; - -final scaffoldMessengerKey = GlobalKey(); - -class SnackbarAction { - // Optional label for the action button. Falls back to the localized "Undo" label when null - final String? label; - final FutureOr Function() onPressed; - - const SnackbarAction({this.label, required this.onPressed}); -} - -class SnackbarManager { - const SnackbarManager(); - - ScaffoldFeatureController? show( - String message, - SnackbarType type, { - Duration? duration, - SnackbarAction? action, - }) { - final messenger = scaffoldMessengerKey.currentState; - final context = scaffoldMessengerKey.currentContext; - if (messenger == null || context == null) { - return null; - } - - duration ??= const .new(seconds: 4); - messenger.hideCurrentSnackBar(); - return messenger.showSnackBar(_build(context, message, type, duration, action)); - } - - SnackBar _build(BuildContext context, String message, SnackbarType type, Duration duration, SnackbarAction? action) { - final theme = Theme.of(context); - final colors = theme.extension() ?? ImmichColors.harmonized(theme.colorScheme); - final (IconData icon, Color background, Color foreground) = switch (type) { - .info => (Icons.info_rounded, colors.info, colors.onInfo), - .success => (Icons.check_circle_rounded, colors.success, colors.onSuccess), - .error => (Icons.warning_rounded, colors.error, colors.onError), - }; - - SnackBarAction? snackAction; - if (action != null) { - snackAction = .new( - label: action.label ?? context.translations.undo, - onPressed: action.onPressed, - textColor: foreground, - ); - } - - return SnackBar( - behavior: .floating, - backgroundColor: background, - duration: duration, - shape: const RoundedRectangleBorder(borderRadius: .all(.circular(ImmichRadius.sm))), - // A snackbar carrying an action stays up until it is tapped by default; our actions are optional - persist: false, - content: Row( - children: [ - Icon(icon, color: foreground, size: ImmichIconSize.sm), - const SizedBox(width: ImmichSpacing.md), - Expanded( - child: Text( - message, - maxLines: 2, - overflow: .ellipsis, - style: .new(color: foreground, fontWeight: .w600, fontSize: ImmichTextSize.body), - ), - ), - ], - ), - action: snackAction, - ); - } - - ScaffoldFeatureController? info( - String message, { - Duration? duration, - SnackbarAction? action, - }) => show(message, .info, duration: duration, action: action); - - ScaffoldFeatureController? success( - String message, { - Duration? duration, - SnackbarAction? action, - }) => show(message, .success, duration: duration, action: action); - - ScaffoldFeatureController? error( - String message, { - Duration? duration, - SnackbarAction? action, - }) => show(message, .error, duration: duration, action: action); -} - -const snackbar = SnackbarManager(); diff --git a/mobile/packages/ui/lib/src/theme.dart b/mobile/packages/ui/lib/src/theme.dart index 6ee4729810..387723b8ce 100644 --- a/mobile/packages/ui/lib/src/theme.dart +++ b/mobile/packages/ui/lib/src/theme.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; import 'package:immich_ui/src/constants.dart'; -import 'package:material_color_utilities/blend/blend.dart'; -import 'package:material_color_utilities/hct/hct.dart'; -import 'package:material_color_utilities/palettes/tonal_palette.dart'; class ImmichThemeProvider extends StatelessWidget { final ColorScheme colorScheme; @@ -14,26 +11,27 @@ class ImmichThemeProvider extends StatelessWidget { Widget build(BuildContext context) { return Theme( data: Theme.of(context).copyWith( - extensions: [ImmichColors.harmonized(colorScheme)], colorScheme: colorScheme, brightness: colorScheme.brightness, inputDecorationTheme: InputDecorationTheme( floatingLabelBehavior: FloatingLabelBehavior.always, - border: WidgetStateInputBorder.resolveWith((states) { - final color = states.contains(WidgetState.error) - ? colorScheme.error - : states.contains(WidgetState.focused) - ? colorScheme.primary - : colorScheme.outline; - return OutlineInputBorder( - borderSide: BorderSide(color: color), - borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), - ); - }), - labelStyle: WidgetStateTextStyle.resolveWith((states) { - final color = states.contains(WidgetState.error) ? colorScheme.error : colorScheme.primary; - return TextStyle(color: color, fontWeight: FontWeight.w600); - }), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.primary), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.primary), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.error), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: colorScheme.error), + borderRadius: const BorderRadius.all(Radius.circular(ImmichRadius.md)), + ), + labelStyle: TextStyle(color: colorScheme.primary, fontWeight: FontWeight.w600), hintStyle: const TextStyle(fontSize: ImmichTextSize.body), errorStyle: TextStyle(color: colorScheme.error, fontWeight: FontWeight.w600), ), @@ -42,71 +40,3 @@ class ImmichThemeProvider extends StatelessWidget { ); } } - -class ImmichColors extends ThemeExtension { - final Color info; - final Color onInfo; - final Color success; - final Color onSuccess; - final Color error; - final Color onError; - - const ImmichColors({ - required this.info, - required this.onInfo, - required this.success, - required this.onSuccess, - required this.error, - required this.onError, - }); - - factory ImmichColors.harmonized(ColorScheme scheme) { - final (info, onInfo) = scheme.harmonized(const Color(0xFF1984E9)); - final (success, onSuccess) = scheme.harmonized(const Color(0xFF10C14D)); - final (error, onError) = scheme.harmonized(const Color(0xFFFA2921)); - return ImmichColors( - info: info, - onInfo: onInfo, - success: success, - onSuccess: onSuccess, - error: error, - onError: onError, - ); - } - - @override - ImmichColors copyWith({Color? info, Color? onInfo, Color? success, Color? onSuccess, Color? error, Color? onError}) { - return ImmichColors( - info: info ?? this.info, - onInfo: onInfo ?? this.onInfo, - success: success ?? this.success, - onSuccess: onSuccess ?? this.onSuccess, - error: error ?? this.error, - onError: onError ?? this.onError, - ); - } - - @override - ImmichColors lerp(ImmichColors? other, double t) { - if (other == null) { - return this; - } - return ImmichColors( - info: Color.lerp(info, other.info, t)!, - onInfo: Color.lerp(onInfo, other.onInfo, t)!, - success: Color.lerp(success, other.success, t)!, - onSuccess: Color.lerp(onSuccess, other.onSuccess, t)!, - error: Color.lerp(error, other.error, t)!, - onError: Color.lerp(onError, other.onError, t)!, - ); - } -} - -extension on ColorScheme { - (Color container, Color onContainer) harmonized(Color seed) { - final hct = Hct.fromInt(Blend.harmonize(seed.toARGB32(), primary.toARGB32())); - final tones = TonalPalette.of(hct.hue, hct.chroma); - final isDark = brightness == Brightness.dark; - return (Color(tones.get(isDark ? 30 : 90)), Color(tones.get(isDark ? 90 : 10))); - } -} diff --git a/mobile/packages/ui/lib/src/translation.dart b/mobile/packages/ui/lib/src/translation.dart index 1bca33f4f1..cd51f74422 100644 --- a/mobile/packages/ui/lib/src/translation.dart +++ b/mobile/packages/ui/lib/src/translation.dart @@ -1,24 +1,27 @@ import 'package:flutter/material.dart'; class ImmichTranslations { - final String submit; - final String password; - final String undo; + late String submit; + late String password; - const ImmichTranslations({String? submit, String? password, String? undo}) - : submit = submit ?? 'Submit', - password = password ?? 'Password', - undo = undo ?? 'Undo'; + ImmichTranslations({String? submit, String? password}) { + this.submit = submit ?? 'Submit'; + this.password = password ?? 'Password'; + } } class ImmichTranslationProvider extends InheritedWidget { final ImmichTranslations? translations; - const ImmichTranslationProvider({super.key, this.translations, required super.child}); + const ImmichTranslationProvider({ + super.key, + this.translations, + required super.child, + }); static ImmichTranslations of(BuildContext context) { final provider = context.dependOnInheritedWidgetOfExactType(); - return provider?.translations ?? const .new(); + return provider?.translations ?? ImmichTranslations(); } @override diff --git a/mobile/packages/ui/lib/src/types.dart b/mobile/packages/ui/lib/src/types.dart index 8193eb88af..2c0c7b7760 100644 --- a/mobile/packages/ui/lib/src/types.dart +++ b/mobile/packages/ui/lib/src/types.dart @@ -1,5 +1,9 @@ -enum ImmichVariant { filled, ghost } +enum ImmichVariant { + filled, + ghost, +} -enum ImmichColor { primary, secondary } - -enum SnackbarType { info, success, error } +enum ImmichColor { + primary, + secondary, +} diff --git a/mobile/packages/ui/pubspec.lock b/mobile/packages/ui/pubspec.lock index b58a985de1..697e1debf5 100644 --- a/mobile/packages/ui/pubspec.lock +++ b/mobile/packages/ui/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -21,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" clock: dependency: transitive description: @@ -87,26 +87,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: - dependency: "direct main" + dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.16.0" path: dependency: transitive description: @@ -124,10 +124,10 @@ packages: dependency: transitive description: name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.2" + version: "1.10.1" stack_trace: dependency: transitive description: @@ -164,10 +164,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.6" vector_math: dependency: transitive description: @@ -180,10 +180,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.2.0" + version: "15.0.2" sdks: - dart: ">=3.12.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/mobile/packages/ui/pubspec.yaml b/mobile/packages/ui/pubspec.yaml index b924becdc5..a25dfb6ca4 100644 --- a/mobile/packages/ui/pubspec.yaml +++ b/mobile/packages/ui/pubspec.yaml @@ -2,12 +2,11 @@ name: immich_ui publish_to: none environment: - sdk: '>=3.12.0 <4.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: flutter: sdk: flutter - material_color_utilities: any dev_dependencies: flutter_test: diff --git a/mobile/packages/ui/showcase/.gitignore b/mobile/packages/ui/showcase/.gitignore new file mode 100644 index 0000000000..b285cd608b --- /dev/null +++ b/mobile/packages/ui/showcase/.gitignore @@ -0,0 +1,11 @@ +# Build artifacts +build/ + +# Test cache and generated files +.dart_tool/ +.packages +.flutter-plugins +.flutter-plugins-dependencies + +# IDE-specific files +.vscode/ \ No newline at end of file diff --git a/mobile/packages/ui/showcase/.metadata b/mobile/packages/ui/showcase/.metadata new file mode 100644 index 0000000000..b95fa4d74e --- /dev/null +++ b/mobile/packages/ui/showcase/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: web + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/mobile/packages/ui/showcase/analysis_options.yaml b/mobile/packages/ui/showcase/analysis_options.yaml new file mode 100644 index 0000000000..f9b303465f --- /dev/null +++ b/mobile/packages/ui/showcase/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/mobile/packages/ui/showcase/assets/immich-text-dark.png b/mobile/packages/ui/showcase/assets/immich-text-dark.png new file mode 100644 index 0000000000..215687af8f Binary files /dev/null and b/mobile/packages/ui/showcase/assets/immich-text-dark.png differ diff --git a/mobile/packages/ui/showcase/assets/immich-text-light.png b/mobile/packages/ui/showcase/assets/immich-text-light.png new file mode 100644 index 0000000000..478158d39c Binary files /dev/null and b/mobile/packages/ui/showcase/assets/immich-text-light.png differ diff --git a/mobile/packages/ui/showcase/assets/immich_logo.png b/mobile/packages/ui/showcase/assets/immich_logo.png new file mode 100644 index 0000000000..49fd3ae289 Binary files /dev/null and b/mobile/packages/ui/showcase/assets/immich_logo.png differ diff --git a/mobile/packages/ui/showcase/assets/themes/github_dark.json b/mobile/packages/ui/showcase/assets/themes/github_dark.json new file mode 100644 index 0000000000..bd4801482e --- /dev/null +++ b/mobile/packages/ui/showcase/assets/themes/github_dark.json @@ -0,0 +1,339 @@ +{ + "name": "GitHub Dark", + "settings": [ + { + "settings": { + "foreground": "#e1e4e8", + "background": "#24292e" + } + }, + { + "scope": [ + "comment", + "punctuation.definition.comment", + "string.comment" + ], + "settings": { + "foreground": "#6a737d" + } + }, + { + "scope": [ + "constant", + "entity.name.constant", + "variable.other.constant", + "variable.other.enummember", + "variable.language" + ], + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": [ + "entity", + "entity.name" + ], + "settings": { + "foreground": "#b392f0" + } + }, + { + "scope": "variable.parameter.function", + "settings": { + "foreground": "#e1e4e8" + } + }, + { + "scope": "entity.name.tag", + "settings": { + "foreground": "#85e89d" + } + }, + { + "scope": "keyword", + "settings": { + "foreground": "#f97583" + } + }, + { + "scope": [ + "storage", + "storage.type" + ], + "settings": { + "foreground": "#f97583" + } + }, + { + "scope": [ + "storage.modifier.package", + "storage.modifier.import", + "storage.type.java" + ], + "settings": { + "foreground": "#e1e4e8" + } + }, + { + "scope": [ + "string", + "punctuation.definition.string", + "string punctuation.section.embedded source" + ], + "settings": { + "foreground": "#9ecbff" + } + }, + { + "scope": "support", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "meta.property-name", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "variable", + "settings": { + "foreground": "#ffab70" + } + }, + { + "scope": "variable.other", + "settings": { + "foreground": "#e1e4e8" + } + }, + { + "scope": "invalid.broken", + "settings": { + "fontStyle": "italic", + "foreground": "#fdaeb7" + } + }, + { + "scope": "invalid.deprecated", + "settings": { + "fontStyle": "italic", + "foreground": "#fdaeb7" + } + }, + { + "scope": "invalid.illegal", + "settings": { + "fontStyle": "italic", + "foreground": "#fdaeb7" + } + }, + { + "scope": "invalid.unimplemented", + "settings": { + "fontStyle": "italic", + "foreground": "#fdaeb7" + } + }, + { + "scope": "message.error", + "settings": { + "foreground": "#fdaeb7" + } + }, + { + "scope": "string variable", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": [ + "source.regexp", + "string.regexp" + ], + "settings": { + "foreground": "#dbedff" + } + }, + { + "scope": [ + "string.regexp.character-class", + "string.regexp constant.character.escape", + "string.regexp source.ruby.embedded", + "string.regexp string.regexp.arbitrary-repitition" + ], + "settings": { + "foreground": "#dbedff" + } + }, + { + "scope": "string.regexp constant.character.escape", + "settings": { + "fontStyle": "bold", + "foreground": "#85e89d" + } + }, + { + "scope": "support.constant", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "support.variable", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "meta.module-reference", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "punctuation.definition.list.begin.markdown", + "settings": { + "foreground": "#ffab70" + } + }, + { + "scope": [ + "markup.heading", + "markup.heading entity.name" + ], + "settings": { + "fontStyle": "bold", + "foreground": "#79b8ff" + } + }, + { + "scope": "markup.quote", + "settings": { + "foreground": "#85e89d" + } + }, + { + "scope": "markup.italic", + "settings": { + "fontStyle": "italic", + "foreground": "#e1e4e8" + } + }, + { + "scope": "markup.bold", + "settings": { + "fontStyle": "bold", + "foreground": "#e1e4e8" + } + }, + { + "scope": "markup.underline", + "settings": { + "fontStyle": "underline" + } + }, + { + "scope": "markup.inline.raw", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": [ + "markup.deleted", + "meta.diff.header.from-file", + "punctuation.definition.deleted" + ], + "settings": { + "foreground": "#fdaeb7" + } + }, + { + "scope": [ + "markup.inserted", + "meta.diff.header.to-file", + "punctuation.definition.inserted" + ], + "settings": { + "foreground": "#85e89d" + } + }, + { + "scope": [ + "markup.changed", + "punctuation.definition.changed" + ], + "settings": { + "foreground": "#ffab70" + } + }, + { + "scope": [ + "markup.ignored", + "markup.untracked" + ], + "settings": { + "foreground": "#2f363d" + } + }, + { + "scope": "meta.diff.range", + "settings": { + "fontStyle": "bold", + "foreground": "#b392f0" + } + }, + { + "scope": "meta.diff.header", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": "meta.separator", + "settings": { + "fontStyle": "bold", + "foreground": "#79b8ff" + } + }, + { + "scope": "meta.output", + "settings": { + "foreground": "#79b8ff" + } + }, + { + "scope": [ + "brackethighlighter.tag", + "brackethighlighter.curly", + "brackethighlighter.round", + "brackethighlighter.square", + "brackethighlighter.angle", + "brackethighlighter.quote" + ], + "settings": { + "foreground": "#d1d5da" + } + }, + { + "scope": "brackethighlighter.unmatched", + "settings": { + "foreground": "#fdaeb7" + } + }, + { + "scope": [ + "constant.other.reference.link", + "string.other.link" + ], + "settings": { + "fontStyle": "underline", + "foreground": "#dbedff" + } + } + ] +} diff --git a/mobile/packages/ui/showcase/lib/app_theme.dart b/mobile/packages/ui/showcase/lib/app_theme.dart new file mode 100644 index 0000000000..995bf3c91e --- /dev/null +++ b/mobile/packages/ui/showcase/lib/app_theme.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + // Light theme colors + static const _primary500 = Color(0xFF4250AF); + static const _primary100 = Color(0xFFD4D6F0); + static const _primary900 = Color(0xFF181E44); + static const _danger500 = Color(0xFFE53E3E); + static const _light50 = Color(0xFFFAFAFA); + static const _light300 = Color(0xFFD4D4D4); + static const _light500 = Color(0xFF737373); + + // Dark theme colors + static const _darkPrimary500 = Color(0xFFACCBFA); + static const _darkPrimary300 = Color(0xFF616D94); + static const _darkDanger500 = Color(0xFFE88080); + static const _darkLight50 = Color(0xFF0A0A0A); + static const _darkLight100 = Color(0xFF171717); + static const _darkLight200 = Color(0xFF262626); + + static ThemeData get lightTheme { + return ThemeData( + colorScheme: const ColorScheme.light( + primary: _primary500, + onPrimary: Colors.white, + primaryContainer: _primary100, + onPrimaryContainer: _primary900, + secondary: _light500, + onSecondary: Colors.white, + error: _danger500, + onError: Colors.white, + surface: _light50, + onSurface: Color(0xFF1A1C1E), + surfaceContainerHighest: Color(0xFFE3E4E8), + outline: Color(0xFFD1D3D9), + outlineVariant: _light300, + ), + useMaterial3: true, + fontFamily: 'GoogleSans', + scaffoldBackgroundColor: _light50, + cardTheme: const CardThemeData( + elevation: 0, + color: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + side: BorderSide(color: _light300, width: 1), + ), + ), + appBarTheme: const AppBarTheme( + centerTitle: false, + elevation: 0, + backgroundColor: Colors.white, + surfaceTintColor: Colors.transparent, + foregroundColor: Color(0xFF1A1C1E), + ), + ); + } + + static ThemeData get darkTheme { + return ThemeData( + colorScheme: const ColorScheme.dark( + primary: _darkPrimary500, + onPrimary: Color(0xFF0F1433), + primaryContainer: _darkPrimary300, + onPrimaryContainer: _primary100, + secondary: Color(0xFFC4C6D0), + onSecondary: Color(0xFF2E3042), + error: _darkDanger500, + onError: Color(0xFF0F1433), + surface: _darkLight50, + onSurface: Color(0xFFE3E3E6), + surfaceContainerHighest: _darkLight200, + outline: Color(0xFF8E9099), + outlineVariant: Color(0xFF43464F), + ), + useMaterial3: true, + fontFamily: 'GoogleSans', + scaffoldBackgroundColor: _darkLight50, + cardTheme: const CardThemeData( + elevation: 0, + color: _darkLight100, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + side: BorderSide(color: _darkLight200, width: 1), + ), + ), + appBarTheme: const AppBarTheme( + centerTitle: false, + elevation: 0, + backgroundColor: _darkLight50, + surfaceTintColor: Colors.transparent, + foregroundColor: Color(0xFFE3E3E6), + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/constants.dart b/mobile/packages/ui/showcase/lib/constants.dart new file mode 100644 index 0000000000..cfca4cfda9 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/constants.dart @@ -0,0 +1,16 @@ +const String appTitle = '@immich/ui'; + +class LayoutConstants { + static const double sidebarWidth = 220.0; + + static const double gridSpacing = 16.0; + static const double gridAspectRatio = 2.5; + + static const double borderRadiusSmall = 6.0; + static const double borderRadiusMedium = 8.0; + static const double borderRadiusLarge = 12.0; + + static const double iconSizeSmall = 16.0; + static const double iconSizeMedium = 18.0; + static const double iconSizeLarge = 20.0; +} diff --git a/mobile/packages/ui/showcase/lib/main.dart b/mobile/packages/ui/showcase/lib/main.dart new file mode 100644 index 0000000000..6cd2df4fe5 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/main.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/app_theme.dart'; +import 'package:showcase/constants.dart'; +import 'package:showcase/router.dart'; +import 'package:showcase/widgets/example_card.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await initializeCodeHighlighter(); + runApp(const ShowcaseApp()); +} + +class ShowcaseApp extends StatefulWidget { + const ShowcaseApp({super.key}); + + @override + State createState() => _ShowcaseAppState(); +} + +class _ShowcaseAppState extends State { + ThemeMode _themeMode = ThemeMode.light; + late final GoRouter _router; + + @override + void initState() { + super.initState(); + _router = AppRouter.createRouter(_toggleTheme); + } + + void _toggleTheme() { + setState(() { + _themeMode = _themeMode == ThemeMode.light + ? ThemeMode.dark + : ThemeMode.light; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: appTitle, + themeMode: _themeMode, + routerConfig: _router, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + debugShowCheckedModeBanner: false, + builder: (context, child) => ImmichThemeProvider( + colorScheme: Theme.of(context).colorScheme, + child: child!, + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart new file mode 100644 index 0000000000..1bae98e0a4 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/close_button_page.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class CloseButtonPage extends StatelessWidget { + const CloseButtonPage({super.key}); + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.closeButton.name, + child: ComponentExamples( + title: 'ImmichCloseButton', + subtitle: 'Pre-configured close button for dialogs and sheets.', + examples: [ + ExampleCard( + title: 'Default & Custom', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichCloseButton(onPressed: () {}), + ImmichCloseButton( + variant: ImmichVariant.filled, + onPressed: () {}, + ), + ImmichCloseButton( + color: ImmichColor.secondary, + onPressed: () {}, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart new file mode 100644 index 0000000000..7e36ac7537 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_bold_text.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; + +class FormattedTextBoldText extends StatelessWidget { + const FormattedTextBoldText({super.key}); + + @override + Widget build(BuildContext context) { + return ImmichFormattedText('This is bold text.'); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart new file mode 100644 index 0000000000..3910a5117a --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_links.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; + +class FormattedTextLinks extends StatelessWidget { + const FormattedTextLinks({super.key}); + + @override + Widget build(BuildContext context) { + return ImmichFormattedText( + 'Read the documentation or visit GitHub.', + spanBuilder: (tag) => FormattedSpan( + onTap: switch (tag) { + 'docs-link' => () => ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Docs link clicked!'))), + 'github-link' => () => ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('GitHub link clicked!'))), + _ => null, + }, + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart new file mode 100644 index 0000000000..3490b1c386 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/examples/formatted_text_mixed_tags.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; + +class FormattedTextMixedContent extends StatelessWidget { + const FormattedTextMixedContent({super.key}); + + @override + Widget build(BuildContext context) { + return ImmichFormattedText( + 'You can use bold text and links together.', + spanBuilder: (tag) => switch (tag) { + 'b' => const FormattedSpan( + style: TextStyle(fontWeight: FontWeight.bold), + ), + _ => FormattedSpan( + onTap: () => ScaffoldMessenger.of( + context, + ).showSnackBar(const SnackBar(content: Text('Link clicked!'))), + ), + }, + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/form_page.dart b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart new file mode 100644 index 0000000000..14567031de --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/form_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class FormPage extends StatefulWidget { + const FormPage({super.key}); + + @override + State createState() => _FormPageState(); +} + +class _FormPageState extends State { + final _emailController = TextEditingController(); + final _passwordController = TextEditingController(); + String _result = ''; + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.form.name, + child: ComponentExamples( + title: 'ImmichForm', + subtitle: + 'Form container with built-in validation and submit handling.', + examples: [ + ExampleCard( + title: 'Login Form', + preview: Column( + children: [ + ImmichForm( + submitText: 'Login', + submitIcon: Icons.login, + onSubmit: () async { + await Future.delayed(const Duration(seconds: 1)); + setState(() { + _result = 'Form submitted!'; + }); + }, + child: Column( + spacing: 10, + children: [ + ImmichTextInput( + label: 'Email', + controller: _emailController, + keyboardType: TextInputType.emailAddress, + validator: (value) => + value?.isEmpty ?? true ? 'Required' : null, + ), + ImmichPasswordInput( + label: 'Password', + controller: _passwordController, + validator: (value) => + value?.isEmpty ?? true ? 'Required' : null, + ), + ], + ), + ), + if (_result.isNotEmpty) ...[ + const SizedBox(height: 16), + Text(_result, style: const TextStyle(color: Colors.green)), + ], + ], + ), + ), + ], + ), + ); + } + + @override + void dispose() { + _emailController.dispose(); + _passwordController.dispose(); + super.dispose(); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart b/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart new file mode 100644 index 0000000000..b827e0340b --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/formatted_text_page.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:showcase/pages/components/examples/formatted_text_bold_text.dart'; +import 'package:showcase/pages/components/examples/formatted_text_links.dart'; +import 'package:showcase/pages/components/examples/formatted_text_mixed_tags.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class FormattedTextPage extends StatelessWidget { + const FormattedTextPage({super.key}); + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.formattedText.name, + child: ComponentExamples( + title: 'ImmichFormattedText', + subtitle: 'Render text with HTML formatting (bold, links).', + examples: [ + ExampleCard( + title: 'Bold Text', + preview: const FormattedTextBoldText(), + code: 'formatted_text_bold_text.dart', + ), + ExampleCard( + title: 'Links', + preview: const FormattedTextLinks(), + code: 'formatted_text_links.dart', + ), + ExampleCard( + title: 'Mixed Content', + preview: const FormattedTextMixedContent(), + code: 'formatted_text_mixed_tags.dart', + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart new file mode 100644 index 0000000000..4418b1de4f --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/icon_button_page.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class IconButtonPage extends StatelessWidget { + const IconButtonPage({super.key}); + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.iconButton.name, + child: ComponentExamples( + title: 'ImmichIconButton', + subtitle: 'Icon-only button with customizable styling.', + examples: [ + ExampleCard( + title: 'Variants & Colors', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichIconButton( + icon: Icons.add, + onPressed: () {}, + variant: ImmichVariant.filled, + ), + ImmichIconButton( + icon: Icons.edit, + onPressed: () {}, + variant: ImmichVariant.ghost, + ), + ImmichIconButton( + icon: Icons.delete, + onPressed: () {}, + color: ImmichColor.secondary, + ), + ImmichIconButton( + icon: Icons.settings, + onPressed: () {}, + disabled: true, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart new file mode 100644 index 0000000000..772dd7882f --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/password_input_page.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class PasswordInputPage extends StatelessWidget { + const PasswordInputPage({super.key}); + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.passwordInput.name, + child: ComponentExamples( + title: 'ImmichPasswordInput', + subtitle: 'Password field with visibility toggle.', + examples: [ + ExampleCard( + title: 'Password Input', + preview: ImmichPasswordInput( + label: 'Password', + hintText: 'Enter your password', + validator: (value) { + if (value == null || value.isEmpty) { + return 'Password is required'; + } + if (value.length < 8) { + return 'Password must be at least 8 characters'; + } + return null; + }, + ), + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart new file mode 100644 index 0000000000..59e5b86294 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/text_button_page.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class TextButtonPage extends StatefulWidget { + const TextButtonPage({super.key}); + + @override + State createState() => _TextButtonPageState(); +} + +class _TextButtonPageState extends State { + bool _isLoading = false; + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.textButton.name, + child: ComponentExamples( + title: 'ImmichTextButton', + subtitle: + 'A versatile button component with multiple variants and color options.', + examples: [ + ExampleCard( + title: 'Variants', + description: + 'Filled and ghost variants for different visual hierarchy', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton( + onPressed: () {}, + labelText: 'Filled', + variant: ImmichVariant.filled, + expanded: false, + ), + ImmichTextButton( + onPressed: () {}, + labelText: 'Ghost', + variant: ImmichVariant.ghost, + expanded: false, + ), + ], + ), + ), + ExampleCard( + title: 'Colors', + description: 'Primary and secondary color options', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton( + onPressed: () {}, + labelText: 'Primary', + color: ImmichColor.primary, + expanded: false, + ), + ImmichTextButton( + onPressed: () {}, + labelText: 'Secondary', + color: ImmichColor.secondary, + expanded: false, + ), + ], + ), + ), + ExampleCard( + title: 'With Icons', + description: 'Add leading icons', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton( + onPressed: () {}, + labelText: 'With Icon', + icon: Icons.add, + expanded: false, + ), + ImmichTextButton( + onPressed: () {}, + labelText: 'Download', + icon: Icons.download, + variant: ImmichVariant.ghost, + expanded: false, + ), + ], + ), + ), + ExampleCard( + title: 'Loading State', + description: 'Shows loading indicator during async operations', + preview: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ImmichTextButton( + onPressed: () async { + setState(() => _isLoading = true); + await Future.delayed(const Duration(seconds: 2)); + if (mounted) setState(() => _isLoading = false); + }, + labelText: _isLoading ? 'Loading...' : 'Click Me', + loading: _isLoading, + expanded: false, + ), + ], + ), + ), + ExampleCard( + title: 'Disabled State', + description: 'Buttons can be disabled', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + ImmichTextButton( + onPressed: () {}, + labelText: 'Disabled', + disabled: true, + expanded: false, + ), + ImmichTextButton( + onPressed: () {}, + labelText: 'Disabled Ghost', + variant: ImmichVariant.ghost, + disabled: true, + expanded: false, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart b/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart new file mode 100644 index 0000000000..5a0bfec6cd --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/components/text_input_page.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class TextInputPage extends StatefulWidget { + const TextInputPage({super.key}); + + @override + State createState() => _TextInputPageState(); +} + +class _TextInputPageState extends State { + final _controller1 = TextEditingController(); + final _controller2 = TextEditingController(); + + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.textInput.name, + child: ComponentExamples( + title: 'ImmichTextInput', + subtitle: 'Text field with validation support.', + examples: [ + ExampleCard( + title: 'Basic Usage', + preview: Column( + children: [ + ImmichTextInput( + label: 'Email', + hintText: 'Enter your email', + controller: _controller1, + keyboardType: TextInputType.emailAddress, + ), + const SizedBox(height: 16), + ImmichTextInput( + label: 'Username', + controller: _controller2, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Username is required'; + } + if (value.length < 3) { + return 'Username must be at least 3 characters'; + } + return null; + }, + ), + ], + ), + ), + ], + ), + ); + } + + @override + void dispose() { + _controller1.dispose(); + _controller2.dispose(); + super.dispose(); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart b/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart new file mode 100644 index 0000000000..17de02d80a --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/design_system/constants_page.dart @@ -0,0 +1,396 @@ +import 'package:flutter/material.dart'; +import 'package:immich_ui/immich_ui.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/component_examples.dart'; +import 'package:showcase/widgets/example_card.dart'; +import 'package:showcase/widgets/page_title.dart'; + +class ConstantsPage extends StatefulWidget { + const ConstantsPage({super.key}); + + @override + State createState() => _ConstantsPageState(); +} + +class _ConstantsPageState extends State { + @override + Widget build(BuildContext context) { + return PageTitle( + title: AppRoute.constants.name, + child: ComponentExamples( + title: 'Constants', + subtitle: 'Consistent spacing, sizing, and styling constants.', + expand: true, + examples: [ + const ExampleCard( + title: 'Spacing', + description: 'ImmichSpacing (4.0 → 48.0)', + preview: Column( + children: [ + _SpacingBox(label: 'xs', size: ImmichSpacing.xs), + _SpacingBox(label: 'sm', size: ImmichSpacing.sm), + _SpacingBox(label: 'md', size: ImmichSpacing.md), + _SpacingBox(label: 'lg', size: ImmichSpacing.lg), + _SpacingBox(label: 'xl', size: ImmichSpacing.xl), + _SpacingBox(label: 'xxl', size: ImmichSpacing.xxl), + _SpacingBox(label: 'xxxl', size: ImmichSpacing.xxxl), + ], + ), + ), + const ExampleCard( + title: 'Border Radius', + description: 'ImmichRadius (0.0 → 24.0)', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + _RadiusBox(label: 'none', radius: ImmichRadius.none), + _RadiusBox(label: 'xs', radius: ImmichRadius.xs), + _RadiusBox(label: 'sm', radius: ImmichRadius.sm), + _RadiusBox(label: 'md', radius: ImmichRadius.md), + _RadiusBox(label: 'lg', radius: ImmichRadius.lg), + _RadiusBox(label: 'xl', radius: ImmichRadius.xl), + _RadiusBox(label: 'xxl', radius: ImmichRadius.xxl), + ], + ), + ), + const ExampleCard( + title: 'Icon Sizes', + description: 'ImmichIconSize (16.0 → 48.0)', + preview: Wrap( + spacing: 16, + runSpacing: 16, + alignment: WrapAlignment.start, + children: [ + _IconSizeBox(label: 'xs', size: ImmichIconSize.xs), + _IconSizeBox(label: 'sm', size: ImmichIconSize.sm), + _IconSizeBox(label: 'md', size: ImmichIconSize.md), + _IconSizeBox(label: 'lg', size: ImmichIconSize.lg), + _IconSizeBox(label: 'xl', size: ImmichIconSize.xl), + _IconSizeBox(label: 'xxl', size: ImmichIconSize.xxl), + ], + ), + ), + const ExampleCard( + title: 'Text Sizes', + description: 'ImmichTextSize (10.0 → 60.0)', + preview: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Caption', + style: TextStyle(fontSize: ImmichTextSize.caption), + ), + Text('Label', style: TextStyle(fontSize: ImmichTextSize.label)), + Text('Body', style: TextStyle(fontSize: ImmichTextSize.body)), + Text('H6', style: TextStyle(fontSize: ImmichTextSize.h6)), + Text('H5', style: TextStyle(fontSize: ImmichTextSize.h5)), + Text('H4', style: TextStyle(fontSize: ImmichTextSize.h4)), + Text('H3', style: TextStyle(fontSize: ImmichTextSize.h3)), + Text('H2', style: TextStyle(fontSize: ImmichTextSize.h2)), + Text('H1', style: TextStyle(fontSize: ImmichTextSize.h1)), + ], + ), + ), + const ExampleCard( + title: 'Elevation', + description: 'ImmichElevation (0.0 → 16.0)', + preview: Wrap( + spacing: 12, + runSpacing: 12, + children: [ + _ElevationBox(label: 'none', elevation: ImmichElevation.none), + _ElevationBox(label: 'xs', elevation: ImmichElevation.xs), + _ElevationBox(label: 'sm', elevation: ImmichElevation.sm), + _ElevationBox(label: 'md', elevation: ImmichElevation.md), + _ElevationBox(label: 'lg', elevation: ImmichElevation.lg), + _ElevationBox(label: 'xl', elevation: ImmichElevation.xl), + _ElevationBox(label: 'xxl', elevation: ImmichElevation.xxl), + ], + ), + ), + const ExampleCard( + title: 'Border Width', + description: 'ImmichBorderWidth (0.5 → 4.0)', + preview: Column( + children: [ + _BorderBox( + label: 'hairline', + borderWidth: ImmichBorderWidth.hairline, + ), + _BorderBox(label: 'base', borderWidth: ImmichBorderWidth.base), + _BorderBox(label: 'md', borderWidth: ImmichBorderWidth.md), + _BorderBox(label: 'lg', borderWidth: ImmichBorderWidth.lg), + _BorderBox(label: 'xl', borderWidth: ImmichBorderWidth.xl), + ], + ), + ), + const ExampleCard( + title: 'Animation Durations', + description: 'ImmichDuration (100ms → 700ms)', + preview: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + _AnimatedDurationBox( + label: 'Extra Fast', + duration: ImmichDuration.extraFast, + ), + _AnimatedDurationBox( + label: 'Fast', + duration: ImmichDuration.fast, + ), + _AnimatedDurationBox( + label: 'Normal', + duration: ImmichDuration.normal, + ), + _AnimatedDurationBox( + label: 'Slow', + duration: ImmichDuration.slow, + ), + _AnimatedDurationBox( + label: 'Extra Slow', + duration: ImmichDuration.extraSlow, + ), + ], + ), + ), + ], + ), + ); + } +} + +class _SpacingBox extends StatelessWidget { + final String label; + final double size; + + const _SpacingBox({required this.label, required this.size}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + SizedBox( + width: 60, + child: Text( + label, + style: const TextStyle(fontFamily: 'GoogleSansCode'), + ), + ), + Container( + width: size, + height: 24, + color: Theme.of(context).colorScheme.primary, + ), + const SizedBox(width: 8), + Text('${size.toStringAsFixed(1)}px'), + ], + ), + ); + } +} + +class _RadiusBox extends StatelessWidget { + final String label; + final double radius; + + const _RadiusBox({required this.label, required this.radius}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: BorderRadius.circular(radius), + ), + ), + const SizedBox(height: 4), + Text(label, style: const TextStyle(fontSize: 12)), + ], + ); + } +} + +class _IconSizeBox extends StatelessWidget { + final String label; + final double size; + + const _IconSizeBox({required this.label, required this.size}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Icon(Icons.palette_rounded, size: size), + const SizedBox(height: 4), + Text(label, style: const TextStyle(fontSize: 12)), + Text( + '${size.toStringAsFixed(0)}px', + style: const TextStyle(fontSize: 10, color: Colors.grey), + ), + ], + ); + } +} + +class _ElevationBox extends StatelessWidget { + final String label; + final double elevation; + + const _ElevationBox({required this.label, required this.elevation}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Material( + elevation: elevation, + borderRadius: const BorderRadius.all(Radius.circular(8)), + child: Container( + width: 60, + height: 60, + alignment: Alignment.center, + child: Text(label, style: const TextStyle(fontSize: 12)), + ), + ), + const SizedBox(height: 4), + Text( + elevation.toStringAsFixed(1), + style: const TextStyle(fontSize: 10), + ), + ], + ); + } +} + +class _BorderBox extends StatelessWidget { + final String label; + final double borderWidth; + + const _BorderBox({required this.label, required this.borderWidth}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + SizedBox( + width: 80, + child: Text( + label, + style: const TextStyle(fontFamily: 'GoogleSansCode'), + ), + ), + Expanded( + child: Container( + height: 40, + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: borderWidth, + ), + borderRadius: const BorderRadius.all(Radius.circular(4)), + ), + ), + ), + const SizedBox(width: 8), + Text('${borderWidth.toStringAsFixed(1)}px'), + ], + ), + ); + } +} + +class _AnimatedDurationBox extends StatefulWidget { + final String label; + final Duration duration; + + const _AnimatedDurationBox({required this.label, required this.duration}); + + @override + State<_AnimatedDurationBox> createState() => _AnimatedDurationBoxState(); +} + +class _AnimatedDurationBoxState extends State<_AnimatedDurationBox> { + bool _atEnd = false; + bool _isAnimating = false; + + void _playAnimation() async { + if (_isAnimating) return; + setState(() => _isAnimating = true); + setState(() => _atEnd = true); + await Future.delayed(widget.duration); + if (!mounted) return; + setState(() => _atEnd = false); + await Future.delayed(widget.duration); + if (!mounted) return; + setState(() => _isAnimating = false); + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return Row( + children: [ + SizedBox( + width: 90, + child: Text( + widget.label, + style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 12), + ), + ), + Expanded( + child: Container( + height: 32, + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(6), + ), + child: AnimatedAlign( + duration: widget.duration, + curve: Curves.easeInOut, + alignment: _atEnd ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + width: 60, + height: 28, + margin: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: colorScheme.primary, + borderRadius: BorderRadius.circular(4), + ), + alignment: Alignment.center, + child: Text( + '${widget.duration.inMilliseconds}ms', + style: TextStyle( + fontSize: 11, + color: colorScheme.onPrimary, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: _isAnimating ? null : _playAnimation, + icon: Icon( + Icons.play_arrow_rounded, + color: _isAnimating ? colorScheme.outline : colorScheme.primary, + ), + iconSize: 24, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + ), + ], + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/pages/home_page.dart b/mobile/packages/ui/showcase/lib/pages/home_page.dart new file mode 100644 index 0000000000..de7af6c26b --- /dev/null +++ b/mobile/packages/ui/showcase/lib/pages/home_page.dart @@ -0,0 +1,118 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:showcase/constants.dart'; +import 'package:showcase/routes.dart'; + +class HomePage extends StatelessWidget { + final VoidCallback onThemeToggle; + + const HomePage({super.key, required this.onThemeToggle}); + + @override + Widget build(BuildContext context) { + return Title( + title: appTitle, + color: Theme.of(context).colorScheme.primary, + child: ListView( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32), + children: [ + Text( + appTitle, + style: Theme.of(context).textTheme.displaySmall?.copyWith( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 12), + Text( + 'A collection of Flutter components that are shared across all Immich projects', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w400, + height: 1.5, + ), + ), + const SizedBox(height: 48), + ...routesByCategory.entries.map((entry) { + if (entry.key == AppRouteCategory.root) { + return const SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + entry.key.displayName, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 16), + GridView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: LayoutConstants.gridSpacing, + mainAxisSpacing: LayoutConstants.gridSpacing, + childAspectRatio: LayoutConstants.gridAspectRatio, + ), + itemCount: entry.value.length, + itemBuilder: (context, index) { + return _ComponentCard(route: entry.value[index]); + }, + ), + const SizedBox(height: 48), + ], + ); + }), + ], + ), + ); + } +} + +class _ComponentCard extends StatelessWidget { + final AppRoute route; + + const _ComponentCard({required this.route}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => context.go(route.path), + borderRadius: const BorderRadius.all(Radius.circular(LayoutConstants.borderRadiusLarge)), + child: Card( + child: Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Icon(route.icon, size: 32, color: Theme.of(context).colorScheme.primary), + const SizedBox(height: 16), + Text( + route.name, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + + const SizedBox(height: 8), + Text( + route.description, + style: Theme.of( + context, + ).textTheme.bodyMedium?.copyWith(color: Theme.of(context).colorScheme.onSurfaceVariant, height: 1.4), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/router.dart b/mobile/packages/ui/showcase/lib/router.dart new file mode 100644 index 0000000000..34393da508 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/router.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:showcase/pages/components/close_button_page.dart'; +import 'package:showcase/pages/components/form_page.dart'; +import 'package:showcase/pages/components/formatted_text_page.dart'; +import 'package:showcase/pages/components/icon_button_page.dart'; +import 'package:showcase/pages/components/password_input_page.dart'; +import 'package:showcase/pages/components/text_button_page.dart'; +import 'package:showcase/pages/components/text_input_page.dart'; +import 'package:showcase/pages/design_system/constants_page.dart'; +import 'package:showcase/pages/home_page.dart'; +import 'package:showcase/routes.dart'; +import 'package:showcase/widgets/shell_layout.dart'; + +class AppRouter { + static GoRouter createRouter(VoidCallback onThemeToggle) { + return GoRouter( + initialLocation: AppRoute.home.path, + routes: [ + ShellRoute( + builder: (context, state, child) => + ShellLayout(onThemeToggle: onThemeToggle, child: child), + routes: AppRoute.values + .map( + (route) => GoRoute( + path: route.path, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: switch (route) { + AppRoute.home => HomePage(onThemeToggle: onThemeToggle), + AppRoute.textButton => const TextButtonPage(), + AppRoute.iconButton => const IconButtonPage(), + AppRoute.closeButton => const CloseButtonPage(), + AppRoute.textInput => const TextInputPage(), + AppRoute.passwordInput => const PasswordInputPage(), + AppRoute.form => const FormPage(), + AppRoute.formattedText => const FormattedTextPage(), + AppRoute.constants => const ConstantsPage(), + }, + ), + ), + ) + .toList(), + ), + ], + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/routes.dart b/mobile/packages/ui/showcase/lib/routes.dart new file mode 100644 index 0000000000..4feeeafdb6 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/routes.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +enum AppRouteCategory { + root(''), + forms('Forms'), + buttons('Buttons'), + designSystem('Design System'); + + final String displayName; + const AppRouteCategory(this.displayName); +} + +enum AppRoute { + home( + name: 'Home', + description: 'Home page', + path: '/', + category: AppRouteCategory.root, + icon: Icons.home_outlined, + ), + textButton( + name: 'Text Button', + description: 'Versatile button with filled and ghost variants', + path: '/text-button', + category: AppRouteCategory.buttons, + icon: Icons.smart_button_rounded, + ), + iconButton( + name: 'Icon Button', + description: 'Icon-only button with customizable styling', + path: '/icon-button', + category: AppRouteCategory.buttons, + icon: Icons.radio_button_unchecked_rounded, + ), + closeButton( + name: 'Close Button', + description: 'Pre-configured close button for dialogs', + path: '/close-button', + category: AppRouteCategory.buttons, + icon: Icons.close_rounded, + ), + textInput( + name: 'Text Input', + description: 'Text field with validation support', + path: '/text-input', + category: AppRouteCategory.forms, + icon: Icons.text_fields_outlined, + ), + passwordInput( + name: 'Password Input', + description: 'Password field with visibility toggle', + path: '/password-input', + category: AppRouteCategory.forms, + icon: Icons.password_outlined, + ), + form( + name: 'Form', + description: 'Form container with built-in validation', + path: '/form', + category: AppRouteCategory.forms, + icon: Icons.description_outlined, + ), + formattedText( + name: 'Formatted Text', + description: 'Render text with HTML formatting', + path: '/formatted-text', + category: AppRouteCategory.forms, + icon: Icons.code_rounded, + ), + constants( + name: 'Constants', + description: 'Spacing, colors, typography, and more', + path: '/constants', + category: AppRouteCategory.designSystem, + icon: Icons.palette_outlined, + ); + + final String name; + final String description; + final String path; + final AppRouteCategory category; + final IconData icon; + + const AppRoute({ + required this.name, + required this.description, + required this.path, + required this.category, + required this.icon, + }); +} + +final routesByCategory = AppRoute.values + .fold>>({}, (map, route) { + map.putIfAbsent(route.category, () => []).add(route); + return map; + }); diff --git a/mobile/packages/ui/showcase/lib/widgets/component_examples.dart b/mobile/packages/ui/showcase/lib/widgets/component_examples.dart new file mode 100644 index 0000000000..21e6516079 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/widgets/component_examples.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; + +class ComponentExamples extends StatelessWidget { + final String title; + final String? subtitle; + final List examples; + final bool expand; + + const ComponentExamples({ + super.key, + required this.title, + this.subtitle, + required this.examples, + this.expand = false, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.fromLTRB(10, 24, 24, 24), + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: _PageHeader(title: title, subtitle: subtitle), + ), + const SliverPadding(padding: EdgeInsets.only(top: 24)), + if (expand) + SliverList.builder( + itemCount: examples.length, + itemBuilder: (context, index) => examples[index], + ) + else + SliverLayoutBuilder( + builder: (context, constraints) { + return SliverList.builder( + itemCount: examples.length, + itemBuilder: (context, index) => Align( + alignment: Alignment.centerLeft, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: constraints.crossAxisExtent * 0.6, + maxWidth: constraints.crossAxisExtent, + ), + child: IntrinsicWidth(child: examples[index]), + ), + ), + ); + }, + ), + ], + ), + ); + } +} + +class _PageHeader extends StatelessWidget { + final String title; + final String? subtitle; + + const _PageHeader({required this.title, this.subtitle}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of( + context, + ).textTheme.headlineLarge?.copyWith(fontWeight: FontWeight.bold), + ), + if (subtitle != null) ...[ + const SizedBox(height: 8), + Text( + subtitle!, + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ], + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/widgets/example_card.dart b/mobile/packages/ui/showcase/lib/widgets/example_card.dart new file mode 100644 index 0000000000..fea561afb6 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/widgets/example_card.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:showcase/constants.dart'; +import 'package:syntax_highlight/syntax_highlight.dart'; + +late final Highlighter _codeHighlighter; + +Future initializeCodeHighlighter() async { + await Highlighter.initialize(['dart']); + final darkTheme = await HighlighterTheme.loadFromAssets([ + 'assets/themes/github_dark.json', + ], const TextStyle(color: Color(0xFFe1e4e8))); + + _codeHighlighter = Highlighter(language: 'dart', theme: darkTheme); +} + +class ExampleCard extends StatefulWidget { + final String title; + final String? description; + final Widget preview; + final String? code; + + const ExampleCard({ + super.key, + required this.title, + this.description, + required this.preview, + this.code, + }); + + @override + State createState() => _ExampleCardState(); +} + +class _ExampleCardState extends State { + bool _showPreview = true; + String? code; + + @override + void initState() { + super.initState(); + if (widget.code != null) { + rootBundle + .loadString('lib/pages/components/examples/${widget.code!}') + .then((value) { + setState(() { + code = value; + }); + }); + } + } + + @override + Widget build(BuildContext context) { + return Card( + elevation: 1, + margin: const EdgeInsets.only(bottom: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: Theme.of(context).textTheme.titleMedium + ?.copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 4), + if (widget.description != null) + Text( + widget.description!, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), + if (code != null) ...[ + const SizedBox(width: 16), + Row( + children: [ + _ToggleButton( + icon: Icons.visibility_rounded, + label: 'Preview', + isSelected: _showPreview, + onTap: () => setState(() => _showPreview = true), + ), + const SizedBox(width: 8), + _ToggleButton( + icon: Icons.code_rounded, + label: 'Code', + isSelected: !_showPreview, + onTap: () => setState(() => _showPreview = false), + ), + ], + ), + ], + ], + ), + ), + const Divider(height: 1), + if (_showPreview) + Padding( + padding: const EdgeInsets.all(16.0), + child: SizedBox(width: double.infinity, child: widget.preview), + ) + else + Container( + width: double.infinity, + decoration: const BoxDecoration( + color: Color(0xFF24292e), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular( + LayoutConstants.borderRadiusMedium, + ), + bottomRight: Radius.circular( + LayoutConstants.borderRadiusMedium, + ), + ), + ), + child: _CodeCard(code: code!), + ), + ], + ), + ); + } +} + +class _ToggleButton extends StatelessWidget { + final IconData icon; + final String label; + final bool isSelected; + final VoidCallback onTap; + + const _ToggleButton({ + required this.icon, + required this.label, + required this.isSelected, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + borderRadius: const BorderRadius.all(Radius.circular(24)), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).colorScheme.primary.withValues(alpha: 0.7) + : Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(24)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + icon, + size: 16, + color: Theme.of(context).colorScheme.onPrimary, + ), + const SizedBox(width: 6), + Text( + label, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, + ), + ), + ], + ), + ), + ); + } +} + +class _CodeCard extends StatelessWidget { + final String code; + + const _CodeCard({required this.code}); + + @override + Widget build(BuildContext context) { + final lines = code.split('\n'); + final lineNumberColor = Colors.white.withValues(alpha: 0.4); + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: List.generate( + lines.length, + (index) => SizedBox( + height: 20, + child: Text( + '${index + 1}', + style: TextStyle( + fontFamily: 'GoogleSansCode', + fontSize: 13, + color: lineNumberColor, + height: 1.5, + ), + ), + ), + ), + ), + const SizedBox(width: 16), + SelectableText.rich( + _codeHighlighter.highlight(code), + style: const TextStyle( + fontFamily: 'GoogleSansCode', + fontSize: 13, + height: 1.54, + ), + ), + ], + ), + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/widgets/page_title.dart b/mobile/packages/ui/showcase/lib/widgets/page_title.dart new file mode 100644 index 0000000000..eae3bf6ffb --- /dev/null +++ b/mobile/packages/ui/showcase/lib/widgets/page_title.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +class PageTitle extends StatelessWidget { + final String title; + final Widget child; + + const PageTitle({super.key, required this.title, required this.child}); + + @override + Widget build(BuildContext context) { + return Title( + title: '$title | @immich/ui', + color: Theme.of(context).colorScheme.primary, + child: child, + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart b/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart new file mode 100644 index 0000000000..8bcb687e75 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/widgets/shell_layout.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:showcase/constants.dart'; +import 'package:showcase/widgets/sidebar_navigation.dart'; + +class ShellLayout extends StatelessWidget { + final Widget child; + final VoidCallback onThemeToggle; + + const ShellLayout({ + super.key, + required this.child, + required this.onThemeToggle, + }); + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + appBar: AppBar( + title: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.asset('assets/immich_logo.png', height: 32, width: 32), + const SizedBox(width: 8), + Image.asset( + isDark + ? 'assets/immich-text-dark.png' + : 'assets/immich-text-light.png', + height: 24, + filterQuality: FilterQuality.none, + isAntiAlias: true, + ), + ], + ), + actions: [ + IconButton( + icon: Icon( + isDark ? Icons.light_mode_outlined : Icons.dark_mode_outlined, + size: LayoutConstants.iconSizeLarge, + ), + onPressed: onThemeToggle, + tooltip: 'Toggle theme', + ), + ], + shape: Border( + bottom: BorderSide(color: Theme.of(context).dividerColor, width: 1), + ), + ), + body: Row( + children: [ + const SidebarNavigation(), + const VerticalDivider(), + Expanded(child: child), + ], + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart b/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart new file mode 100644 index 0000000000..10eba170e6 --- /dev/null +++ b/mobile/packages/ui/showcase/lib/widgets/sidebar_navigation.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:showcase/constants.dart'; +import 'package:showcase/routes.dart'; + +class SidebarNavigation extends StatelessWidget { + const SidebarNavigation({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: LayoutConstants.sidebarWidth, + decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface), + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 16), + children: [ + ...routesByCategory.entries.expand((entry) { + final category = entry.key; + final routes = entry.value; + return [ + if (category != AppRouteCategory.root) _CategoryHeader(category), + ...routes.map((route) => _NavItem(route)), + const SizedBox(height: 24), + ]; + }), + ], + ), + ); + } +} + +class _CategoryHeader extends StatelessWidget { + final AppRouteCategory category; + + const _CategoryHeader(this.category); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 12, top: 8, bottom: 8), + child: Text( + category.displayName, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + ); + } +} + +class _NavItem extends StatelessWidget { + final AppRoute route; + + const _NavItem(this.route); + + @override + Widget build(BuildContext context) { + final currentRoute = GoRouterState.of(context).uri.toString(); + final isSelected = currentRoute == route.path; + final isDark = Theme.of(context).brightness == Brightness.dark; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () { + context.go(route.path); + }, + borderRadius: BorderRadius.circular( + LayoutConstants.borderRadiusMedium, + ), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: isSelected + ? (isDark + ? Colors.white.withValues(alpha: 0.1) + : Theme.of( + context, + ).colorScheme.primaryContainer.withValues(alpha: 0.5)) + : Colors.transparent, + borderRadius: BorderRadius.circular( + LayoutConstants.borderRadiusMedium, + ), + ), + child: Row( + children: [ + Icon( + route.icon, + size: 20, + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 16), + Expanded( + child: Text( + route.name, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: isSelected + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/mobile/packages/ui/showcase/pubspec.lock b/mobile/packages/ui/showcase/pubspec.lock new file mode 100644 index 0000000000..c79e6c18c7 --- /dev/null +++ b/mobile/packages/ui/showcase/pubspec.lock @@ -0,0 +1,377 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + device_info_plus: + dependency: transitive + description: + name: device_info_plus + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + url: "https://pub.dev" + source: hosted + version: "11.5.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f + url: "https://pub.dev" + source: hosted + version: "7.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + url: "https://pub.dev" + source: hosted + version: "2.1.5" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: eff94d2a6fc79fa8b811dde79c7549808c2346037ee107a1121b4a644c745f2a + url: "https://pub.dev" + source: hosted + version: "17.0.1" + immich_ui: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.0" + irondash_engine_context: + dependency: transitive + description: + name: irondash_engine_context + sha256: "2bb0bc13dfda9f5aaef8dde06ecc5feb1379f5bb387d59716d799554f3f305d7" + url: "https://pub.dev" + source: hosted + version: "0.5.5" + irondash_message_channel: + dependency: transitive + description: + name: irondash_message_channel + sha256: b4101669776509c76133b8917ab8cfc704d3ad92a8c450b92934dd8884a2f060 + url: "https://pub.dev" + source: hosted + version: "0.7.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pixel_snap: + dependency: transitive + description: + name: pixel_snap + sha256: "677410ea37b07cd37ecb6d5e6c0d8d7615a7cf3bd92ba406fd1ac57e937d1fb0" + url: "https://pub.dev" + source: hosted + version: "0.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + super_clipboard: + dependency: transitive + description: + name: super_clipboard + sha256: e73f3bb7e66cc9260efa1dc507f979138e7e106c3521e2dda2d0311f6d728a16 + url: "https://pub.dev" + source: hosted + version: "0.9.1" + super_native_extensions: + dependency: transitive + description: + name: super_native_extensions + sha256: b9611dcb68f1047d6f3ef11af25e4e68a21b1a705bbcc3eb8cb4e9f5c3148569 + url: "https://pub.dev" + source: hosted + version: "0.9.1" + syntax_highlight: + dependency: "direct main" + description: + name: syntax_highlight + sha256: "4d3ba40658cadba6ba55d697f29f00b43538ebb6eb4a0ca0e895c568eaced138" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + url: "https://pub.dev" + source: hosted + version: "4.5.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae" + url: "https://pub.dev" + source: hosted + version: "2.1.0" +sdks: + dart: ">=3.9.2 <4.0.0" + flutter: ">=3.35.0" diff --git a/mobile/packages/ui/showcase/pubspec.yaml b/mobile/packages/ui/showcase/pubspec.yaml new file mode 100644 index 0000000000..e45ce07e66 --- /dev/null +++ b/mobile/packages/ui/showcase/pubspec.yaml @@ -0,0 +1,47 @@ +name: showcase +publish_to: 'none' + +version: 1.0.0+1 + +environment: + sdk: ^3.9.2 + +dependencies: + flutter: + sdk: flutter + immich_ui: + path: ../ + go_router: ^17.0.1 + syntax_highlight: ^0.5.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^6.0.0 + +flutter: + uses-material-design: true + assets: + - assets/ + - assets/themes/ + - lib/pages/components/examples/ + + fonts: + - family: GoogleSans + fonts: + - asset: ../../../fonts/GoogleSans/GoogleSans-Regular.ttf + - asset: ../../../fonts/GoogleSans/GoogleSans-Italic.ttf + style: italic + - asset: ../../../fonts/GoogleSans/GoogleSans-Medium.ttf + weight: 500 + - asset: ../../../fonts/GoogleSans/GoogleSans-SemiBold.ttf + weight: 600 + - asset: ../../../fonts/GoogleSans/GoogleSans-Bold.ttf + weight: 700 + - family: GoogleSansCode + fonts: + - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Regular.ttf + - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-Medium.ttf + weight: 500 + - asset: ../../../fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf + weight: 600 \ No newline at end of file diff --git a/mobile/packages/ui/showcase/web/favicon.ico b/mobile/packages/ui/showcase/web/favicon.ico new file mode 100644 index 0000000000..7ec34e9e53 Binary files /dev/null and b/mobile/packages/ui/showcase/web/favicon.ico differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000..49fd3ae289 Binary files /dev/null and b/mobile/packages/ui/showcase/web/icons/Icon-maskable-192.png differ diff --git a/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png b/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000..a7220554bc Binary files /dev/null and b/mobile/packages/ui/showcase/web/icons/Icon-maskable-512.png differ diff --git a/mobile/packages/ui/showcase/web/icons/apple-icon-180.png b/mobile/packages/ui/showcase/web/icons/apple-icon-180.png new file mode 100644 index 0000000000..4e642631a3 Binary files /dev/null and b/mobile/packages/ui/showcase/web/icons/apple-icon-180.png differ diff --git a/mobile/packages/ui/showcase/web/index.html b/mobile/packages/ui/showcase/web/index.html new file mode 100644 index 0000000000..abf42ad1fd --- /dev/null +++ b/mobile/packages/ui/showcase/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + @immich/ui + + + + + + diff --git a/mobile/packages/ui/showcase/web/manifest.json b/mobile/packages/ui/showcase/web/manifest.json new file mode 100644 index 0000000000..25b44bd1ae --- /dev/null +++ b/mobile/packages/ui/showcase/web/manifest.json @@ -0,0 +1,37 @@ +{ + "name": "@immich/ui Showcase", + "short_name": "@immich/ui", + "start_url": ".", + "display": "standalone", + "background_color": "#FCFCFD", + "theme_color": "#4250AF", + "description": "Immich UI component library showcase and documentation", + "orientation": "landscape", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/mobile/packages/ui/test/color_override_test.dart b/mobile/packages/ui/test/color_override_test.dart deleted file mode 100644 index ebd3b19c9d..0000000000 --- a/mobile/packages/ui/test/color_override_test.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_ui/src/color_override.dart'; -import 'package:immich_ui/src/components/icon_button.dart'; - -import 'test_utils.dart'; - -void main() { - group('ImmichColorOverride', () { - testWidgets('exposes the override color to descendants', (tester) async { - Color? captured; - await tester.pumpTestWidget( - ImmichColorOverride( - color: Colors.green, - child: Builder( - builder: (context) { - captured = ImmichColorOverride.maybeOf(context); - return const SizedBox.shrink(); - }, - ), - ), - ); - - expect(captured, Colors.green); - }); - - testWidgets('maybeOf returns null when there is no override', (tester) async { - Color? captured = Colors.black; - await tester.pumpTestWidget( - Builder( - builder: (context) { - captured = ImmichColorOverride.maybeOf(context); - return const SizedBox.shrink(); - }, - ), - ); - - expect(captured, isNull); - }); - - testWidgets('a descendant component adopts the override as its foreground', (tester) async { - await tester.pumpTestWidget( - ImmichColorOverride( - color: Colors.green, - child: ImmichIconButton(icon: Icons.add, onPressed: () {}), - ), - ); - - final button = tester.widget(find.byType(IconButton)); - expect(button.style?.foregroundColor?.resolve({}), Colors.green); - }); - }); -} diff --git a/mobile/packages/ui/test/formatted_text_test.dart b/mobile/packages/ui/test/formatted_text_test.dart index 5f26822855..54ef343727 100644 --- a/mobile/packages/ui/test/formatted_text_test.dart +++ b/mobile/packages/ui/test/formatted_text_test.dart @@ -10,9 +10,7 @@ List _getContentSpans(WidgetTester tester) { final richText = tester.widget(find.byType(RichText)); final root = richText.text as TextSpan; final wrapper = root.children?.firstOrNull; - if (wrapper is TextSpan) { - return wrapper.children ?? []; - } + if (wrapper is TextSpan) return wrapper.children ?? []; return []; } @@ -59,7 +57,7 @@ void main() { ); final text = tester.widget(find.byType(Text)); - final richText = text.textSpan! as TextSpan; + final richText = text.textSpan as TextSpan; expect(richText.style?.fontSize, 16); expect(richText.style?.color, Colors.purple); diff --git a/mobile/packages/ui/test/smart_punctuation_test.dart b/mobile/packages/ui/test/smart_punctuation_test.dart deleted file mode 100644 index 93f1f7a92d..0000000000 --- a/mobile/packages/ui/test/smart_punctuation_test.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_ui/src/components/email_input.dart'; -import 'package:immich_ui/src/components/password_input.dart'; - -import 'test_utils.dart'; - -void main() { - EditableText editable(WidgetTester tester) => tester.widget(find.byType(EditableText)); - - testWidgets('ImmichEmailInput disables smart punctuation', (tester) async { - await tester.pumpTestWidget(const ImmichEmailInput()); - - expect(editable(tester).smartDashesType, SmartDashesType.disabled); - expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); - }); - - testWidgets('ImmichPasswordInput disables smart punctuation', (tester) async { - await tester.pumpTestWidget(const ImmichPasswordInput()); - - expect(editable(tester).obscureText, isTrue); - expect(editable(tester).smartDashesType, SmartDashesType.disabled); - expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); - - await tester.tap(find.byIcon(Icons.visibility_rounded)); - await tester.pump(); - - expect(editable(tester).obscureText, isFalse); - expect(editable(tester).smartDashesType, SmartDashesType.disabled); - expect(editable(tester).smartQuotesType, SmartQuotesType.disabled); - }); -} diff --git a/mobile/packages/ui/test/snackbar_test.dart b/mobile/packages/ui/test/snackbar_test.dart deleted file mode 100644 index 1cc57c6d55..0000000000 --- a/mobile/packages/ui/test/snackbar_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_ui/src/snackbar.dart'; - -import 'test_utils.dart'; - -void main() { - group('SnackbarManager', () { - testWidgets('shows the message', (tester) async { - await tester.pumpTestWidget(const SizedBox()); - - snackbar.success('hello'); - await tester.pump(); - - expect(find.text('hello'), findsOneWidget); - expect(find.byType(SnackBar), findsOneWidget); - }); - - testWidgets('replaces the current snackbar', (tester) async { - await tester.pumpTestWidget(const SizedBox()); - - snackbar.info('first'); - await tester.pump(); - snackbar.error('second'); - await tester.pump(); - - expect(find.text('first'), findsNothing); - expect(find.text('second'), findsOneWidget); - }); - - testWidgets('no-ops when the messenger is unmounted', (tester) async { - expect(snackbar.show('x', .info), isNull); - }); - }); -} diff --git a/mobile/packages/ui/test/test_utils.dart b/mobile/packages/ui/test/test_utils.dart index 36c166649b..42cc74da87 100644 --- a/mobile/packages/ui/test/test_utils.dart +++ b/mobile/packages/ui/test/test_utils.dart @@ -1,14 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_ui/src/snackbar.dart'; extension WidgetTesterExtension on WidgetTester { + /// Pumps a widget wrapped in MaterialApp and Scaffold for testing. Future pumpTestWidget(Widget widget) { - return pumpWidget( - MaterialApp( - scaffoldMessengerKey: scaffoldMessengerKey, - home: Scaffold(body: widget), - ), - ); + return pumpWidget(MaterialApp(home: Scaffold(body: widget))); } } diff --git a/mobile/pigeon/background_worker_api.dart b/mobile/pigeon/background_worker_api.dart index 06395fae7b..a40d290199 100644 --- a/mobile/pigeon/background_worker_api.dart +++ b/mobile/pigeon/background_worker_api.dart @@ -47,7 +47,7 @@ abstract class BackgroundWorkerFlutterApi { // Android Only: Called when the Android background upload is triggered @async - void onAndroidUpload(int? maxMinutes); + void onAndroidUpload(); @async void cancel(); diff --git a/mobile/pigeon/local_image_api.dart b/mobile/pigeon/local_image_api.dart index 46643b7956..eb538d7b1a 100644 --- a/mobile/pigeon/local_image_api.dart +++ b/mobile/pigeon/local_image_api.dart @@ -5,7 +5,8 @@ import 'package:pigeon/pigeon.dart'; dartOut: 'lib/platform/local_image_api.g.dart', swiftOut: 'ios/Runner/Images/LocalImages.g.swift', swiftOptions: SwiftOptions(includeErrorClass: false), - kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt', + kotlinOut: + 'android/app/src/main/kotlin/app/alextran/immich/images/LocalImages.g.kt', kotlinOptions: KotlinOptions(package: 'app.alextran.immich.images'), dartOptions: DartOptions(), dartPackageName: 'immich_mobile', diff --git a/mobile/pigeon/native_sync_api.dart b/mobile/pigeon/native_sync_api.dart index 433b154cd1..cd55addd99 100644 --- a/mobile/pigeon/native_sync_api.dart +++ b/mobile/pigeon/native_sync_api.dart @@ -11,7 +11,14 @@ import 'package:pigeon/pigeon.dart'; dartPackageName: 'immich_mobile', ), ) -enum PlatformAssetPlaybackStyle { unknown, image, video, imageAnimated, livePhoto, videoLooping } +enum PlatformAssetPlaybackStyle { + unknown, + image, + video, + imageAnimated, + livePhoto, + videoLooping, +} class PlatformAsset { final String id; @@ -25,7 +32,7 @@ class PlatformAsset { final int? updatedAt; final int? width; final int? height; - final int durationMs; + final int durationInSeconds; final int orientation; final bool isFavorite; @@ -43,7 +50,7 @@ class PlatformAsset { this.updatedAt, this.width, this.height, - this.durationMs = 0, + this.durationInSeconds = 0, this.orientation = 0, this.isFavorite = false, this.adjustmentTime, @@ -105,26 +112,25 @@ class CloudIdResult { @HostApi() abstract class NativeSyncApi { - @async bool shouldFullSync(); - @async + @TaskQueue(type: TaskQueueType.serialBackgroundThread) SyncDelta getMediaChanges(); void checkpointSync(); void clearSyncCheckpoint(); - @async + @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getAssetIdsForAlbum(String albumId); - @async + @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getAlbums(); @TaskQueue(type: TaskQueueType.serialBackgroundThread) int getAssetsCountSince(String albumId, int timestamp); - @async + @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getAssetsForAlbum(String albumId, {int? updatedTimeCond}); @async @@ -133,14 +139,9 @@ abstract class NativeSyncApi { void cancelHashing(); - void cancelSync(); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) Map> getTrashedAssets(); - @async - bool restoreFromTrashById(String mediaId, int type); - @TaskQueue(type: TaskQueueType.serialBackgroundThread) List getCloudIdForAssetIds(List assetIds); } diff --git a/mobile/pigeon/network_api.dart b/mobile/pigeon/network_api.dart index a701995342..704efed770 100644 --- a/mobile/pigeon/network_api.dart +++ b/mobile/pigeon/network_api.dart @@ -44,6 +44,4 @@ abstract class NetworkApi { int getClientPointer(); void setRequestHeaders(Map headers, List serverUrls, String? token); - - String getAppGroupId(); } diff --git a/mobile/pigeon/permission_api.dart b/mobile/pigeon/permission_api.dart deleted file mode 100644 index 42099d4e7c..0000000000 --- a/mobile/pigeon/permission_api.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:pigeon/pigeon.dart'; - -enum PermissionStatus { granted, denied, permanentlyDenied } - -@ConfigurePigeon( - PigeonOptions( - dartOut: 'lib/platform/permission_api.g.dart', - swiftOut: 'ios/Runner/Permission/PermissionApi.g.swift', - swiftOptions: SwiftOptions(includeErrorClass: false), - kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt', - kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'), - dartOptions: DartOptions(), - dartPackageName: 'immich_mobile', - ), -) -@HostApi() -abstract class PermissionApi { - PermissionStatus isIgnoringBatteryOptimizations(); - - bool hasManageMediaPermission(); - - @async - bool requestManageMediaPermission(); - - @async - bool manageMediaPermission(); -} diff --git a/mobile/pigeon/remote_image_api.dart b/mobile/pigeon/remote_image_api.dart index 277b7016fd..7f0135acb8 100644 --- a/mobile/pigeon/remote_image_api.dart +++ b/mobile/pigeon/remote_image_api.dart @@ -13,15 +13,8 @@ import 'package:pigeon/pigeon.dart'; ) @HostApi() abstract class RemoteImageApi { - /// Width and height are the physical decode size, or null for the source size. @async - Map? requestImage( - String url, { - required int requestId, - required bool preferEncoded, - int? width, - int? height, - }); + Map? requestImage(String url, {required int requestId, required bool preferEncoded}); void cancelRequest(int requestId); diff --git a/mobile/pigeon/view_intent_api.dart b/mobile/pigeon/view_intent_api.dart deleted file mode 100644 index f6a5162fef..0000000000 --- a/mobile/pigeon/view_intent_api.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:pigeon/pigeon.dart'; - -@ConfigurePigeon( - PigeonOptions( - dartOut: 'lib/platform/view_intent_api.g.dart', - kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/viewintent/ViewIntent.g.kt', - kotlinOptions: KotlinOptions(package: 'app.alextran.immich.viewintent'), - dartOptions: DartOptions(), - dartPackageName: 'immich_mobile', - ), -) -class ViewIntentPayload { - final String? path; - final String mimeType; - final String? localAssetId; - - const ViewIntentPayload({this.path, required this.mimeType, this.localAssetId}); -} - -@HostApi() -abstract class ViewIntentHostApi { - @async - ViewIntentPayload? consumeViewIntent(); -} diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 65cd03c3ae..9d5f431792 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,18 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "3b19a47f6ea7c2632760777c78174f47f6aec1e05f0cd611380d4593b8af1dbc" + sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 url: "https://pub.dev" source: hosted - version: "96.0.0" + version: "80.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "0c516bc4ad36a1a75759e54d5047cb9d15cded4459df01aa35a0b5ec7db2c2a0" + sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "7.3.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4 + url: "https://pub.dev" + source: hosted + version: "0.13.0" ansicolor: dependency: transitive description: @@ -29,10 +37,10 @@ packages: dependency: transitive description: name: archive - sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + sha256: "0c64e928dcbefddecd234205422bcfc2b5e6d31be0b86fef0d0dd48d7b4c9742" url: "https://pub.dev" source: hosted - version: "4.0.9" + version: "4.0.4" args: dependency: transitive description: @@ -45,36 +53,36 @@ packages: dependency: "direct main" description: name: async - sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.13.1" + version: "2.13.0" auto_route: dependency: "direct main" description: name: auto_route - sha256: e9acfeb3df33d188fce4ad0239ef4238f333b7aa4d95ec52af3c2b9360dcd969 + sha256: "1d1bd908a1fec327719326d5d0791edd37f16caff6493c01003689fb03315ad7" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "9.3.0+1" auto_route_generator: dependency: "direct dev" description: name: auto_route_generator - sha256: "7aa0e90874928e78709f0a21a69fb5bc2ae1aa932dec862930d2af85c40adb01" + sha256: c2e359d8932986d4d1bcad7a428143f81384ce10fef8d4aa5bc29e1f83766a46 url: "https://pub.dev" source: hosted - version: "10.5.0" + version: "9.3.1" background_downloader: dependency: "direct main" description: name: background_downloader - sha256: d27fcaf94f738032f1e6db098230799bf31ae7bf184f173fc9a752fcdae75c0e + sha256: a913b37cc47a656a225e9562b69576000d516f705482f392e2663500e6ff6032 url: "https://pub.dev" source: hosted - version: "9.5.6" + version: "9.3.0" bonsoir: - dependency: "direct overridden" + dependency: transitive description: name: bonsoir sha256: "2e2cf3be580deccad9a48dcaddddf90de092e74b7de2015ef58fb24e11d66496" @@ -133,34 +141,50 @@ packages: dependency: transitive description: name: build - sha256: "45d14a0fb23e018d8287c32fc98d726ce466b231928ed9b9200f29bd3ccd39ae" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.0.4" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + url: "https://pub.dev" + source: hosted + version: "2.4.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "5367e521935b102bdf1e735d2aab461e36b2edca6517662d088dd04cc39f8d16" + sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" url: "https://pub.dev" source: hosted - version: "2.15.1" + version: "2.4.15" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + url: "https://pub.dev" + source: hosted + version: "8.0.0" built_collection: dependency: transitive description: @@ -173,10 +197,10 @@ packages: dependency: transitive description: name: built_value - sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 url: "https://pub.dev" source: hosted - version: "8.12.6" + version: "8.9.5" cast: dependency: "direct main" description: @@ -189,10 +213,10 @@ packages: dependency: transitive description: name: characters - sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.0" charcode: dependency: transitive description: @@ -205,10 +229,18 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" cli_util: dependency: transitive description: @@ -225,22 +257,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - code_assets: - dependency: transitive - description: - name: code_assets - sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 - url: "https://pub.dev" - source: hosted - version: "1.2.1" code_builder: dependency: transitive description: name: code_builder - sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.11.1" + version: "4.10.1" collection: dependency: "direct main" description: @@ -253,18 +277,18 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "25cebb79dfe304022550e0c3c893ae051ded07183d2607f7b88473cb3ade33cb" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" + sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.0.1" convert: dependency: transitive description: @@ -277,26 +301,26 @@ packages: dependency: "direct main" description: name: crop_image - sha256: "27cbce1685a595efee62caab81c98b49b636f765c1da86353f58f5b2bf2775d8" + sha256: "4fdebd00d0c7d1a6e3abeb1e3843efbc202204b867f3e377fcebcf77aaf69a17" url: "https://pub.dev" source: hosted - version: "1.0.17" + version: "1.0.16" cross_file: dependency: transitive description: name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.5+2" + version: "0.3.4+2" crypto: dependency: "direct main" description: name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.0.6" csslib: dependency: transitive description: @@ -309,43 +333,83 @@ packages: dependency: "direct main" description: path: "pkgs/cupertino_http" - ref: "58b03c756b81d16b3975a8ae4b91d25360123bb5" - resolved-ref: "58b03c756b81d16b3975a8ae4b91d25360123bb5" + ref: a0a933358517c6d01cff37fc2a2752ee2d744a3c + resolved-ref: a0a933358517c6d01cff37fc2a2752ee2d744a3c url: "https://github.com/mertalev/http" source: git version: "3.0.0-wip" + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "409c485fd14f544af1da965d5a0d160ee57cd58b63eeaa7280a4f28cf5bda7f1" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: "107e0a43606138015777590ee8ce32f26ba7415c25b722ff0908a6f5d7a4c228" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be" + url: "https://pub.dev" + source: hosted + version: "0.7.5" + custom_lint_visitor: + dependency: transitive + description: + name: custom_lint_visitor + sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" + url: "https://pub.dev" + source: hosted + version: "1.0.0+7.3.0" dart_style: dependency: transitive description: name: dart_style - sha256: "29f7ecc274a86d32920b1d9cfc7502fa87220da41ec60b55f329559d5732e2b2" + sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" url: "https://pub.dev" source: hosted - version: "3.1.7" + version: "3.1.0" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" dbus: dependency: transitive description: name: dbus - sha256: "792974a4007974fbc5c1b5433eb2330a9db3e368c3f906253af4c007d0f49a91" + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" url: "https://pub.dev" source: hosted - version: "0.7.13" + version: "0.7.11" desktop_webview_window: dependency: transitive description: name: desktop_webview_window - sha256: b6fdae2cbf9571879b1761c12f27facaf82e22d0bdc74d049907c2a09a432957 + sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.2.3" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: b4fed1b2835da9d670d7bed7db79ae2a94b0f5ad6312268158a9b5479abbacdd + sha256: dd0e8e02186b2196c7848c9d394a5fd6e5b57a43a546082c5820b1ec72317e33 url: "https://pub.dev" source: hosted - version: "12.4.0" + version: "12.2.0" device_info_plus_platform_interface: dependency: transitive description: @@ -354,38 +418,31 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.3" - diacritic: - dependency: "direct main" - description: - name: diacritic - sha256: "12981945ec38931748836cd76f2b38773118d0baef3c68404bdfde9566147876" - url: "https://pub.dev" - source: hosted - version: "0.1.6" drift: dependency: "direct main" description: - name: drift - sha256: "6cc0b623c0e83f7080524d8396e9301b1d78b9c66a4fdceeb0f798211303254c" - url: "https://pub.dev" - source: hosted - version: "2.34.0" + path: drift + ref: "53ef7e9f19fe8f68416251760b4b99fe43f1c575" + resolved-ref: "53ef7e9f19fe8f68416251760b4b99fe43f1c575" + url: "https://github.com/immich-app/drift" + source: git + version: "2.26.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: "9cfff1576b49725da0d32c040651a41ae195e8c4af8d8da301593e41d7abc2f7" + sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588" url: "https://pub.dev" source: hosted - version: "2.34.0" - drift_sqlite_async: + version: "2.26.0" + drift_flutter: dependency: "direct main" description: - name: drift_sqlite_async - sha256: "28c666847ecbc2e90dbcf8e8c2eecbf847a38af7e6480efc08f3a3c39e060f8d" + name: drift_flutter + sha256: b52bd710f809db11e25259d429d799d034ba1c5224ce6a73fe8419feb980d44c url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.2.6" dynamic_color: dependency: "direct main" description: @@ -422,10 +479,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" file: dependency: "direct dev" description: @@ -438,34 +495,34 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.5" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.6.2" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" url: "https://pub.dev" source: hosted - version: "0.9.3+5" + version: "0.9.3+4" fixnum: dependency: transitive description: @@ -479,6 +536,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_displaymode: dependency: "direct main" description: @@ -512,10 +577,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "5.0.0" flutter_local_notifications: dependency: "direct main" description: @@ -549,18 +614,18 @@ packages: dependency: "direct dev" description: name: flutter_native_splash - sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" + sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" url: "https://pub.dev" source: hosted - version: "2.0.35" + version: "2.0.27" flutter_riverpod: dependency: transitive description: @@ -621,10 +686,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.2.1" flutter_test: dependency: "direct dev" description: flutter @@ -634,26 +699,26 @@ packages: dependency: "direct main" description: name: flutter_udid - sha256: f23555e9d34d5a7dc70fdfa3ab8915b401786879d278568b3c0e801af9a14d2e + sha256: "166bee5989a58c66b8b62000ea65edccc7c8167bbafdbb08022638db330dd030" url: "https://pub.dev" source: hosted - version: "4.1.6" + version: "4.0.0" flutter_web_auth_2: dependency: "direct main" description: name: flutter_web_auth_2 - sha256: "8f9303471dcd96670878c9b7c0c4e14c37595b2add67465f6a868f17a5872dfc" + sha256: "561c32d32ed537853de43852c35849cf1d37f3482f41f22b718ab6112f96b333" url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.0-alpha.0" flutter_web_auth_2_platform_interface: dependency: transitive description: name: flutter_web_auth_2_platform_interface - sha256: ba0fbba55bffb47242025f96852ad1ffba34bc451568f56ef36e613612baffab + sha256: "45927587ebb2364cd273675ec95f6f67b81725754b416cef2b65cdc63fd3e853" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.0.0-alpha.0" flutter_web_plugins: dependency: transitive description: flutter @@ -663,26 +728,18 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" url: "https://pub.dev" source: hosted - version: "8.2.14" - freezed: - dependency: "direct dev" - description: - name: freezed - sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131 - url: "https://pub.dev" - source: hosted - version: "3.2.5" + version: "8.2.12" freezed_annotation: - dependency: "direct main" + dependency: transitive description: name: freezed_annotation - sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.0.0" frontend_server_client: dependency: transitive description: @@ -716,18 +773,18 @@ packages: dependency: transitive description: name: geolocator_android - sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" + sha256: "114072db5d1dce0ec0b36af2697f55c133bc89a2c8dd513e137c0afe59696ed4" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.1+1" geolocator_apple: dependency: transitive description: name: geolocator_apple - sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3 url: "https://pub.dev" source: hosted - version: "2.3.13" + version: "2.3.9" geolocator_linux: dependency: transitive description: @@ -740,10 +797,10 @@ packages: dependency: transitive description: name: geolocator_platform_interface - sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" url: "https://pub.dev" source: hosted - version: "4.2.6" + version: "4.2.4" geolocator_web: dependency: transitive description: @@ -756,10 +813,10 @@ packages: dependency: transitive description: name: geolocator_windows - sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" url: "https://pub.dev" source: hosted - version: "0.2.5" + version: "0.2.3" glob: dependency: transitive description: @@ -788,18 +845,10 @@ packages: dependency: "direct main" description: name: home_widget - sha256: fece84c7464099873c3ace38394ad7053509a4b0e36e57a1105a6aa31f47f23c + sha256: "908d033514a981f829fd98213909e11a428104327be3b422718aa643ac9d084a" url: "https://pub.dev" source: hosted - version: "0.9.3" - hooks: - dependency: transitive - description: - name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" - url: "https://pub.dev" - source: hosted - version: "2.0.2" + version: "0.8.1" hooks_riverpod: dependency: "direct main" description: @@ -812,10 +861,10 @@ packages: dependency: transitive description: name: hotreloader - sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.3.0" html: dependency: transitive description: @@ -828,10 +877,10 @@ packages: dependency: "direct main" description: name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.5.0" http_multi_server: dependency: transitive description: @@ -860,42 +909,42 @@ packages: dependency: transitive description: name: image - sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52" + sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" url: "https://pub.dev" source: hosted - version: "4.9.1" + version: "4.5.4" image_picker: dependency: "direct main" description: name: image_picker - sha256: d8402284df184bc05f4a2210c6c23983b0720f4cd87cbd05c5390a78af602667 + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.0" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "6f3a1995eafb000333174fae92202622033b0ee7fd917a6cd3730295264df84a" + sha256: "58a85e6f09fe9c4484d53d18a0bd6271b72c53fce1d05e6f745ae36d8c18efca" url: "https://pub.dev" source: hosted - version: "0.8.13+19" + version: "0.8.13+5" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 + sha256: e675c22790bcc24e9abd455deead2b7a88de4b79f7327a281812f14de1a56f58 url: "https://pub.dev" source: hosted - version: "0.8.13+6" + version: "0.8.13+1" image_picker_linux: dependency: transitive description: @@ -916,10 +965,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" url: "https://pub.dev" source: hosted - version: "2.11.1" + version: "2.11.0" image_picker_windows: dependency: transitive description: @@ -928,6 +977,13 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2" + immich_mobile_immich_lint: + dependency: "direct dev" + description: + path: immich_lint + relative: true + source: path + version: "0.0.0" immich_ui: dependency: "direct main" description: @@ -956,6 +1012,40 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + isar: + dependency: "direct main" + description: + path: "packages/isar" + ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + url: "https://github.com/immich-app/isar" + source: git + version: "3.1.8" + isar_community: + dependency: transitive + description: + name: isar_community + sha256: "28f59e54636c45ba0bb1b3b7f2656f1c50325f740cea6efcd101900be3fba546" + url: "https://pub.dev" + source: hosted + version: "3.3.0-dev.3" + isar_community_flutter_libs: + dependency: "direct main" + description: + name: isar_community_flutter_libs + sha256: c2934fe755bb3181cb67602fd5df0d080b3d3eb52799f98623aa4fc5acbea010 + url: "https://pub.dev" + source: hosted + version: "3.3.0-dev.3" + isar_generator: + dependency: "direct dev" + description: + path: "packages/isar_generator" + ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + resolved-ref: bb1dca40fe87a001122e5d43bc6254718cb49f3a + url: "https://github.com/immich-app/isar" + source: git + version: "3.1.8" jni: dependency: transitive description: @@ -976,10 +1066,10 @@ packages: dependency: transitive description: name: json_annotation - sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "4.12.0" + version: "4.9.0" leak_tracker: dependency: transitive description: @@ -1004,22 +1094,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" - lean_builder: - dependency: transitive - description: - name: lean_builder - sha256: c16e95ddf7b2d49dd551357b7212fe2ce9f13ec7ad1b1e660c157184031e96c0 - url: "https://pub.dev" - source: hosted - version: "0.1.10" lints: dependency: transitive description: name: lints - sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "5.1.1" local_auth: dependency: "direct main" description: @@ -1032,26 +1114,26 @@ packages: dependency: transitive description: name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e" url: "https://pub.dev" source: hosted - version: "1.0.56" + version: "1.0.49" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + sha256: "630996cd7b7f28f5ab92432c4b35d055dd03a747bc319e5ffbb3c4806a3e50d2" url: "https://pub.dev" source: hosted - version: "1.6.1" + version: "1.4.3" local_auth_platform_interface: dependency: transitive description: name: local_auth_platform_interface - sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.0.10" local_auth_windows: dependency: transitive description: @@ -1072,50 +1154,50 @@ packages: dependency: "direct main" description: name: maplibre_gl - sha256: "8b24dce6a050ba44779154c550e0318654ee12c572fa5fd1ad0c12e90d061356" + sha256: "5c7b1008396b2a321bada7d986ed60f9423406fbc7bd16f7ce91b385dfa054cd" url: "https://pub.dev" source: hosted - version: "0.27.0" + version: "0.22.0" maplibre_gl_platform_interface: dependency: transitive description: name: maplibre_gl_platform_interface - sha256: "8a1952b77ce841162fcd2601dc857f5b3f8eea46d34025b901ef803bb15d2adc" + sha256: "08ee0a2d0853ea945a0ab619d52c0c714f43144145cd67478fc6880b52f37509" url: "https://pub.dev" source: hosted - version: "0.27.0" + version: "0.22.0" maplibre_gl_web: dependency: transitive description: name: maplibre_gl_web - sha256: c9c7bb1183cc9c6baafcef0b5b741420db869ca45e474ca80edf732168a286ee + sha256: "2b13d4b1955a9a54e38a718f2324e56e4983c080fc6de316f6f4b5458baacb58" url: "https://pub.dev" source: hosted - version: "0.27.0" + version: "0.22.0" matcher: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1128,18 +1210,10 @@ packages: dependency: "direct dev" description: name: mocktail - sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa" + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" url: "https://pub.dev" source: hosted - version: "1.0.5" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 - url: "https://pub.dev" - source: hosted - version: "0.19.1" + version: "1.0.4" native_video_player: dependency: "direct main" description: @@ -1153,10 +1227,10 @@ packages: dependency: "direct main" description: name: network_info_plus - sha256: f926b2ba86aa0086a0dfbb9e5072089bc213d854135c1712f1d29fc89ba3c877 + sha256: "08f4166bbb77da9e407edef6322a33f87b18c0ca46483fb25606cb3d2bfcdd2a" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.1.3" network_info_plus_platform_interface: dependency: transitive description: @@ -1174,14 +1248,13 @@ packages: source: hosted version: "0.5.0" objective_c: - dependency: "direct overridden" + dependency: transitive description: - path: "pkgs/objective_c" - ref: "2915556701f4734a784222f290a82adb1b101682" - resolved-ref: "2915556701f4734a784222f290a82adb1b101682" - url: "https://github.com/mertalev/native" - source: git - version: "9.4.1" + name: objective_c + sha256: "1f81ed9e41909d44162d7ec8663b2c647c202317cc0b56d3d56f6a13146a0b64" + url: "https://pub.dev" + source: hosted + version: "9.1.0" octo_image: dependency: "direct main" description: @@ -1202,7 +1275,7 @@ packages: openapi: dependency: "direct main" description: - path: "generated/openapi" + path: openapi relative: true source: path version: "1.0.0" @@ -1210,26 +1283,26 @@ packages: dependency: transitive description: name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.3.1" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.0" path: dependency: "direct main" description: @@ -1250,26 +1323,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" url: "https://pub.dev" source: hosted - version: "2.2.23" + version: "2.2.16" path_provider_foundation: dependency: "direct main" description: name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + sha256: efaec349ddfc181528345c56f8eda9d6cccd71c177511b132c6a0ddaefaa2738 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.4.3" path_provider_linux: dependency: transitive description: @@ -1314,10 +1387,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: e20daf680eef1ca62ffe8c8c526b778cc386d50137c77ac71c8ec9c88c13fb9d + sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98 url: "https://pub.dev" source: hosted - version: "9.4.9" + version: "9.4.6" permission_handler_html: dependency: transitive description: @@ -1346,26 +1419,26 @@ packages: dependency: transitive description: name: petitparser - sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.1" photo_manager: dependency: "direct main" description: name: photo_manager - sha256: fb3bc8ea653370f88742b3baa304700107c83d12748aa58b2b9f2ed3ef15e6c2 + sha256: a0d9a7a9bc35eda02d33766412bde6d883a8b0acb86bbe37dac5f691a0894e8a url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.7.1" pigeon: dependency: "direct dev" description: name: pigeon - sha256: "04cfefc8add8b47ddf9ccac8b92bb4edeb67c87f185c623ba0db118ac99334ad" + sha256: "0045b172d1da43c40cb3f58e80e04b50a65cba20b8b70dc880af04181f7758da" url: "https://pub.dev" source: hosted - version: "26.3.4" + version: "26.0.2" pinput: dependency: "direct main" description: @@ -1394,26 +1467,26 @@ packages: dependency: transitive description: name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" url: "https://pub.dev" source: hosted - version: "1.5.2" + version: "1.5.1" posix: dependency: transitive description: name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.0.1" process: dependency: transitive description: name: process - sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.5" + version: "5.0.3" protobuf: dependency: transitive description: @@ -1454,14 +1527,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" - record_use: - dependency: transitive - description: - name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" - url: "https://pub.dev" - source: hosted - version: "0.6.0" riverpod: dependency: transitive description: @@ -1470,6 +1535,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611" + url: "https://pub.dev" + source: hosted + version: "0.5.10" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8 + url: "https://pub.dev" + source: hosted + version: "2.6.1" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36" + url: "https://pub.dev" + source: hosted + version: "2.6.5" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "89a52b7334210dbff8605c3edf26cfe69b15062beed5cbfeff2c3812c33c9e35" + url: "https://pub.dev" + source: hosted + version: "2.6.5" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" scroll_date_picker: dependency: "direct main" description: @@ -1538,26 +1643,26 @@ packages: dependency: transitive description: name: shared_preferences - sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: a2c49fc1fed7140cadd892d765bd47edbe4ac0b9c7e7e3c493dcb58126f99cf0 + sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" url: "https://pub.dev" source: hosted - version: "2.4.25" + version: "2.4.8" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.6" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1570,10 +1675,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: @@ -1640,58 +1745,90 @@ packages: dependency: transitive description: name: source_gen - sha256: ec37cc0e6694374cbef59ed79685572c870a54ede6fa30a3e420feb3adffea02 + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" url: "https://pub.dev" source: hosted - version: "4.2.3" + version: "2.0.0" source_span: dependency: transitive description: name: source_span - sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.2" - sqlite3: - dependency: "direct main" - description: - name: sqlite3 - sha256: "61c7930bebf32c552ac5808c91bf33802e66711c9216090d3b5579c9f862e80c" - url: "https://pub.dev" - source: hosted - version: "3.4.0" - sqlite3_connection_pool: - dependency: "direct main" - description: - name: sqlite3_connection_pool - sha256: ad51972d37bdf82929888e4c85e722bd9a0b5ff8ad389f89b1b00d19603c0d2d - url: "https://pub.dev" - source: hosted - version: "0.2.7" - sqlite3_web: + version: "1.10.1" + sprintf: dependency: transitive description: - name: sqlite3_web - sha256: fab6f6921f8fc3f703536c018d709ccb4ad5654d92f53e9b206c222f77c50021 + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" url: "https://pub.dev" source: hosted - version: "0.8.1" - sqlite_async: - dependency: "direct main" + version: "7.0.0" + sqflite: + dependency: transitive description: - name: sqlite_async - sha256: e3e486e536253e17260c52b6f824b749adff3f1fed878d4abe629796b858290b + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "0.14.2" + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + url: "https://pub.dev" + source: hosted + version: "2.5.5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "310af39c40dd0bb2058538333c9d9840a2725ae0b9f77e4fd09ad6696aa8f66e" + url: "https://pub.dev" + source: hosted + version: "2.7.5" + sqlite3_flutter_libs: + dependency: transitive + description: + name: sqlite3_flutter_libs + sha256: "7adb4cc96dc08648a5eb1d80a7619070796ca6db03901ff2b6dcb15ee30468f3" + url: "https://pub.dev" + source: hosted + version: "0.5.31" sqlparser: dependency: transitive description: name: sqlparser - sha256: ecdc06d4a7d79dcbc928d99afd2f7f5b0f98a637c46f89be83d911617f759978 + sha256: "27dd0a9f0c02e22ac0eb42a23df9ea079ce69b52bb4a3b478d64e0ef34a263ee" url: "https://pub.dev" source: hosted - version: "0.44.4" + version: "0.41.0" stack_trace: dependency: transitive description: @@ -1740,6 +1877,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + url: "https://pub.dev" + source: hosted + version: "3.3.1" term_glyph: dependency: transitive description: @@ -1752,10 +1897,10 @@ packages: dependency: transitive description: name: test_api - sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.6" thumbhash: dependency: "direct main" description: @@ -1764,6 +1909,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0+1" + time: + dependency: transitive + description: + name: time + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + url: "https://pub.dev" + source: hosted + version: "2.1.5" timezone: dependency: "direct main" description: @@ -1772,6 +1925,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.4" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" typed_data: dependency: transitive description: @@ -1784,10 +1945,10 @@ packages: dependency: transitive description: name: universal_io - sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.2.2" universal_platform: dependency: transitive description: @@ -1808,34 +1969,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" url: "https://pub.dev" source: hosted - version: "6.3.32" + version: "6.3.15" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.5" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -1848,34 +2009,34 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.4" uuid: dependency: "direct main" description: name: uuid - sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.6.0" + version: "4.5.1" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.1.18" vector_graphics_codec: dependency: transitive description: @@ -1888,10 +2049,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "7ee12e6dffe0fc8e755179d6d91b3b34f5924223fc104d85572ef9180d73d172" + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc url: "https://pub.dev" source: hosted - version: "1.2.5" + version: "1.1.19" vector_math: dependency: transitive description: @@ -1904,10 +2065,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "15.2.0" + version: "15.0.0" wakelock_plus: dependency: "direct main" description: @@ -1920,18 +2081,18 @@ packages: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: b13f99e992e7ae6a152e16c5559d3c07ff445b13330192662494e614ca3e7d7b + sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.3.0" watcher: dependency: transitive description: name: watcher - sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.1.1" web: dependency: transitive description: @@ -1968,10 +2129,10 @@ packages: dependency: transitive description: name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + sha256: b89e6e24d1454e149ab20fbb225af58660f0c0bf4475544650700d8e2da54aef url: "https://pub.dev" source: hosted - version: "5.15.0" + version: "5.11.0" win32_registry: dependency: transitive description: @@ -1992,10 +2153,10 @@ packages: dependency: "direct main" description: name: worker_manager - sha256: "887587eb97e517bca88dea761bea96edc495513ec91e4c489dcf110967ba79ff" + sha256: "1bce9f894a0c187856f5fc0e150e7fe1facce326f048ca6172947754dac3d4f3" url: "https://pub.dev" source: hosted - version: "7.2.9" + version: "7.2.7" xdg_directories: dependency: transitive description: @@ -2008,10 +2169,10 @@ packages: dependency: transitive description: name: xml - sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.6.1" xxh3: dependency: transitive description: @@ -2029,5 +2190,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.12.0 <4.0.0" - flutter: "3.44.9" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.7" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 9bda1b20ff..a6ed0046de 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,80 +2,86 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 3.1.0+3057 +version: 2.7.3+3044 environment: - sdk: '>=3.12.0 <4.0.0' - flutter: 3.44.9 + sdk: '>=3.8.0 <4.0.0' + flutter: 3.35.7 dependencies: - async: ^2.13.1 - auto_route: ^11.1.0 - background_downloader: ^9.5.6 + async: ^2.13.0 + auto_route: ^9.2.0 + background_downloader: ^9.3.0 cast: ^2.1.0 collection: ^1.19.1 - connectivity_plus: ^7.3.0 - crop_image: ^1.0.17 - crypto: ^3.0.7 - device_info_plus: ^12.4.0 - diacritic: ^0.1.6 - drift: ^2.34.0 - drift_sqlite_async: 0.3.1 + connectivity_plus: ^6.1.3 + crop_image: ^1.0.16 + crypto: ^3.0.6 + device_info_plus: ^12.2.0 + # DB + drift: ^2.26.0 + drift_flutter: ^0.2.6 dynamic_color: ^1.8.1 easy_localization: ^3.0.8 - ffi: ^2.2.0 + ffi: ^2.1.4 flutter: sdk: flutter + flutter_cache_manager: ^3.4.1 flutter_displaymode: ^0.7.0 flutter_hooks: ^0.21.3+1 - flutter_local_notifications: ^17.2.4 + flutter_local_notifications: ^17.2.1+2 flutter_secure_storage: ^9.2.4 - flutter_svg: ^2.2.4 - flutter_udid: ^4.1.6 - flutter_web_auth_2: ^5.0.2 - fluttertoast: ^8.2.14 + flutter_svg: ^2.2.1 + flutter_udid: ^4.0.0 + flutter_web_auth_2: ^5.0.0-alpha.0 + fluttertoast: ^8.2.12 geolocator: ^14.0.2 - home_widget: ^0.9.3 + home_widget: ^0.8.1 hooks_riverpod: ^2.6.1 - http: ^1.6.0 - image_picker: ^1.2.3 + http: ^1.5.0 + image_picker: ^1.2.0 immich_ui: path: './packages/ui' intl: ^0.20.2 + isar: + git: + url: https://github.com/immich-app/isar + ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a' + path: packages/isar/ + isar_community_flutter_libs: 3.3.0-dev.3 local_auth: ^2.3.0 logging: ^1.3.0 - maplibre_gl: ^0.27.0 + maplibre_gl: ^0.22.0 + native_video_player: git: url: https://github.com/immich-app/native_video_player ref: 'cdf621bdb7edaf996e118a58a48f6441187d79c6' - network_info_plus: ^6.1.4 + network_info_plus: ^6.1.3 octo_image: ^2.1.0 openapi: - path: generated/openapi - package_info_plus: ^8.3.1 + path: openapi + package_info_plus: ^8.3.0 path: ^1.9.1 - path_provider: ^2.1.6 - path_provider_foundation: ^2.6.0 + path_provider: ^2.1.5 + path_provider_foundation: ^2.4.3 permission_handler: ^11.4.0 - photo_manager: 3.9.0 + photo_manager: ^3.7.1 pinput: ^5.0.2 punycode: ^1.0.0 + riverpod_annotation: ^2.6.1 scroll_date_picker: ^3.8.0 scrollable_positioned_list: ^0.3.8 share_handler: ^0.0.25 share_plus: ^10.1.4 sliver_tools: ^0.2.12 stream_transform: ^2.1.1 - sqlite3: ^3.4.0 - sqlite_async: 0.14.2 - sqlite3_connection_pool: ^0.2.7 thumbhash: 0.1.0+1 timezone: ^0.9.4 url_launcher: ^6.3.2 - uuid: ^4.6.0 - wakelock_plus: ^1.3.3 - worker_manager: ^7.2.9 + uuid: ^4.5.1 + wakelock_plus: ^1.3.0 + worker_manager: ^7.2.7 web_socket: ^1.0.1 socket_io_client: git: @@ -84,49 +90,53 @@ dependencies: cupertino_http: git: url: https://github.com/mertalev/http - ref: '58b03c756b81d16b3975a8ae4b91d25360123bb5' + ref: 'a0a933358517c6d01cff37fc2a2752ee2d744a3c' # https://github.com/dart-lang/http/pull/1876 path: pkgs/cupertino_http/ ok_http: git: url: https://github.com/mertalev/http ref: '549c24b0a4d3881a9a44b70f4873450d43c1c4af' # https://github.com/dart-lang/http/pull/1877 path: pkgs/ok_http/ - freezed_annotation: ^3.1.0 dev_dependencies: - auto_route_generator: ^10.5.0 - build_runner: ^2.15.1 + auto_route_generator: ^9.0.0 + build_runner: ^2.4.8 + custom_lint: ^0.7.5 # Drift generator - drift_dev: ^2.34.0 + drift_dev: ^2.26.0 fake_async: ^1.3.3 file: ^7.0.1 # for MemoryFileSystem flutter_launcher_icons: ^0.14.4 - flutter_lints: ^6.0.0 + flutter_lints: ^5.0.0 flutter_native_splash: ^2.4.7 flutter_test: sdk: flutter - freezed: ^3.2.5 + immich_mobile_immich_lint: + path: './immich_lint' integration_test: sdk: flutter - mocktail: ^1.0.5 - # Type safe platform code - pigeon: ^26.3.4 - -# cast 2.1.0 declares a loose bonsoir range but its code targets the 5.x API. -# Pin bonsoir to 5.x until cast releases a version compatible with bonsoir 6.x. -dependency_overrides: - bonsoir: ^5.1.11 - objective_c: + isar_generator: git: - url: https://github.com/mertalev/native - ref: '2915556701f4734a784222f290a82adb1b101682' # https://github.com/dart-lang/native/pull/3458 - path: pkgs/objective_c/ + url: https://github.com/immich-app/isar + ref: 'bb1dca40fe87a001122e5d43bc6254718cb49f3a' + path: packages/isar_generator/ + mocktail: ^1.0.4 + # Type safe platform code + pigeon: ^26.0.2 + riverpod_generator: ^2.6.1 + riverpod_lint: ^2.6.1 + +dependency_overrides: + drift: + git: + url: https://github.com/immich-app/drift + ref: '53ef7e9f19fe8f68416251760b4b99fe43f1c575' + path: drift/ flutter: uses-material-design: true assets: - assets/ - - assets/feature_message/ fonts: - family: GoogleSans fonts: @@ -159,4 +169,4 @@ flutter_launcher_icons: analyzer: exclude: - - generated/** + - openapi/** diff --git a/mobile/scripts/checkout_ios.sh b/mobile/scripts/checkout_ios.sh deleted file mode 100644 index 74706e0d23..0000000000 --- a/mobile/scripts/checkout_ios.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env bash -# Reconciles the iOS project with the current Flutter setup -# -# `flutter build ios --config-only` is relatively expensive, so we attempt to avoid running it when possible - -set -euo pipefail - -stamp="build/ios_checkout.stamp" - -# All tracked files that can invalidate the Xcode + Flutter build state -inputs_digest() { - git ls-files -z ios pubspec.yaml pubspec.lock mise.toml mise.lock \ - | xargs -0 shasum \ - | shasum \ - | cut -d ' ' -f 1 -} - -# Echoes why the iOS project needs reconciling, or returns 1 if it does not. -# The first three cases can happen with no tracked file having changed at all, -# so comparing the digest alone is not enough. -staleness() { - local flutter_root - flutter_root="$(sed -n 's/^FLUTTER_ROOT=//p' ios/Flutter/Generated.xcconfig 2>/dev/null || true)" - - if [[ -z $flutter_root ]]; then - echo "misconfigured FLUTTER_ROOT" - elif [[ ! -d $flutter_root ]]; then - echo "the Flutter SDK has changed" - elif ! cmp -s ios/Podfile.lock ios/Pods/Manifest.lock; then - # This is also done by Xcode's "Check Pods Manifest.lock" build phase - echo "Cocoapods out of sync" - elif [[ ! -f $stamp ]]; then - echo "first checkout" - elif [[ $(<"$stamp") != "$(inputs_digest)" ]]; then - echo "tracked build state has changed" - else - return 1 - fi -} - -[[ $(uname) == Darwin ]] || exit 0 - -if ! reason="$(staleness)"; then - exit 0 -fi - -echo "Reconciling iOS project: $reason" -flutter build ios --config-only --debug - -# Save tracked files manifest -mkdir -p "$(dirname "$stamp")" -inputs_digest > "$stamp" diff --git a/mobile/scripts/fdroid_build_isar.sh b/mobile/scripts/fdroid_build_isar.sh new file mode 100755 index 0000000000..a145268356 --- /dev/null +++ b/mobile/scripts/fdroid_build_isar.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env sh + +test -d .isar || exit +cp .isar-cargo.lock .isar/Cargo.lock +(cd .isar || exit +bash tool/build_android.sh x86 +bash tool/build_android.sh x64 +bash tool/build_android.sh armv7 +bash tool/build_android.sh arm64 +mv libisar_android_arm64.so libisar.so +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/arm64-v8a/ +mv libisar_android_armv7.so libisar.so +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/armeabi-v7a/ +mv libisar_android_x64.so libisar.so +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/x86_64/ +mv libisar_android_x86.so libisar.so +mv libisar.so ../.pub-cache/hosted/pub.dev/isar_community_flutter_libs-*/android/src/main/jniLibs/x86/ +) diff --git a/mobile/scripts/fdroid_update_isar.sh b/mobile/scripts/fdroid_update_isar.sh new file mode 100755 index 0000000000..814f50a8a1 --- /dev/null +++ b/mobile/scripts/fdroid_update_isar.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +isar_version="$(awk '/isar: /{gsub(/\^/, "", $2); print $2}' pubspec.yaml)" +checked_out_version="$(git -C .isar describe --tags)" + +if [ "$isar_version" = "$checked_out_version" ]; then + echo "isar is up-to-date." + exit 0 +fi +echo "Updating from version $checked_out_version to $isar_version." + +git -C .isar checkout "$isar_version" +cargo generate-lockfile --manifest-path .isar/Cargo.toml +mv .isar/Cargo.lock .isar-cargo.lock diff --git a/mobile/test/api.mocks.dart b/mobile/test/api.mocks.dart index 40cdd29143..c6a3a90582 100644 --- a/mobile/test/api.mocks.dart +++ b/mobile/test/api.mocks.dart @@ -1,12 +1,8 @@ -import 'package:immich_mobile/platform/connectivity_api.g.dart'; -import 'package:immich_mobile/repositories/partner_api.repository.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; +class MockAssetsApi extends Mock implements AssetsApi {} + class MockSyncApi extends Mock implements SyncApi {} class MockServerApi extends Mock implements ServerApi {} - -class MockPartnerApiRepository extends Mock implements PartnerApiRepository {} - -class MockConnectivityApi extends Mock implements ConnectivityApi {} diff --git a/mobile/test/domain/models/base_asset_test.dart b/mobile/test/domain/models/base_asset_test.dart deleted file mode 100644 index 6a6c836aa7..0000000000 --- a/mobile/test/domain/models/base_asset_test.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -import '../../unit/factories/local_asset_factory.dart'; -import '../../unit/factories/remote_asset_factory.dart'; - -void main() { - group('BaseAsset.refersToSameAsset', () { - test('search/folder copy (localId null) matches the merged DB copy (localId set)', () { - // #29472: search and folder assets arrive with localId null, then the viewer - // watches the DB copy which fills localId. heroTag embeds localId, so it - // diverges for the same asset and isCurrent used to go false. - final searchCopy = RemoteAssetFactory.create(id: 'asset-1'); - final mergedCopy = searchCopy.copyWith(localId: 'local-1'); - - expect(searchCopy.localId, isNull); - expect(mergedCopy.localId, 'local-1'); - expect(searchCopy.heroTag, isNot(mergedCopy.heroTag)); - - expect(searchCopy.refersToSameAsset(mergedCopy), isTrue); - expect(mergedCopy.refersToSameAsset(searchCopy), isTrue); - }); - - test('different remote assets are not the same', () { - final a = RemoteAssetFactory.create(id: 'asset-1'); - final b = RemoteAssetFactory.create(id: 'asset-2'); - - expect(a.refersToSameAsset(b), isFalse); - }); - - test('same checksum but different remote ids are not the same (duplicate files)', () { - final a = RemoteAssetFactory.create(id: 'asset-1'); - final b = RemoteAssetFactory.create(id: 'asset-2').copyWith(checksum: a.checksum); - - expect(a.checksum, b.checksum); - expect(a.refersToSameAsset(b), isFalse); - }); - - test('falls back to checksum when only one side has an id (local-only vs remote-only)', () { - final remoteOnly = RemoteAssetFactory.create(id: 'asset-1'); - final localOnly = LocalAssetFactory.create(id: 'local-1').copyWith(checksum: remoteOnly.checksum); - - expect(remoteOnly.refersToSameAsset(localOnly), isTrue); - expect(localOnly.refersToSameAsset(remoteOnly), isTrue); - }); - }); - - group('BaseAsset.id', () { - test('survives the DB copy filling localId', () { - final searchCopy = RemoteAssetFactory.create(id: 'asset-1'); - final mergedCopy = searchCopy.copyWith(localId: 'local-1'); - - expect(searchCopy.heroTag, isNot(mergedCopy.heroTag)); - expect(mergedCopy.id, searchCopy.id); - }); - - test('survives a local asset gaining a remote id', () { - final localOnly = LocalAssetFactory.create(id: 'local-1'); - final uploaded = localOnly.copyWith(remoteId: 'asset-1'); - - expect(localOnly.heroTag, isNot(uploaded.heroTag)); - expect(uploaded.id, localOnly.id); - }); - - test('differs between assets', () { - final a = RemoteAssetFactory.create(id: 'asset-1'); - final b = RemoteAssetFactory.create(id: 'asset-2'); - final local = LocalAssetFactory.create(id: 'local-1'); - - expect(a.id, isNot(b.id)); - expect(local.id, isNot(a.id)); - }); - }); -} diff --git a/mobile/test/domain/repositories/sync_stream_repository_test.dart b/mobile/test/domain/repositories/sync_stream_repository_test.dart index 13da161149..a26683213c 100644 --- a/mobile/test/domain/repositories/sync_stream_repository_test.dart +++ b/mobile/test/domain/repositories/sync_stream_repository_test.dart @@ -1,11 +1,6 @@ import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; import 'package:openapi/api.dart'; @@ -16,7 +11,7 @@ SyncUserV1 _createUser({String id = 'user-1'}) { name: 'Test User', email: 'test@test.com', deletedAt: null, - avatarColor: const Optional.absent(), + avatarColor: null, hasProfileImage: false, profileChangedAt: DateTime(2024, 1, 1), ); @@ -26,52 +21,25 @@ SyncAssetV1 _createAsset({ required String id, required String checksum, required String fileName, + String ownerId = 'user-1', int? width, int? height, - String? libraryId, - bool isFavorite = false, }) { return SyncAssetV1( id: id, checksum: checksum, originalFileName: fileName, type: AssetTypeEnum.IMAGE, - ownerId: 'user-1', - isFavorite: isFavorite, + ownerId: ownerId, + isFavorite: false, fileCreatedAt: DateTime(2024, 1, 1), fileModifiedAt: DateTime(2024, 1, 1), - createdAt: DateTime(2024, 1, 1), localDateTime: DateTime(2024, 1, 1), visibility: AssetVisibility.timeline, width: width, height: height, deletedAt: null, duration: null, - libraryId: libraryId, - livePhotoVideoId: null, - stackId: null, - thumbhash: null, - isEdited: false, - ); -} - -SyncAssetV2 _createAssetV2({required String id, required String checksum, required String fileName}) { - return SyncAssetV2( - id: id, - checksum: checksum, - originalFileName: fileName, - type: AssetTypeEnum.IMAGE, - ownerId: 'user-1', - isFavorite: false, - fileCreatedAt: DateTime(2024, 1, 1), - fileModifiedAt: DateTime(2024, 1, 1), - createdAt: DateTime(2024, 1, 1), - localDateTime: DateTime(2024, 1, 1), - visibility: AssetVisibility.timeline, - width: null, - height: null, - deletedAt: null, - duration: 0, libraryId: null, livePhotoVideoId: null, stackId: null, @@ -215,152 +183,4 @@ void main() { expect(result.height, equals(existingHeight), reason: 'Height should remain as originally set'); }); }); - - group('SyncStreamRepository - reset()', () { - test('nulls linkedRemoteAlbumId on localAlbumEntity so FK refs do not dangle', () async { - const localAlbumId = 'local-1'; - const remoteAlbumId = 'remote-1'; - - await db.remoteAlbumEntity.insertOne( - RemoteAlbumEntityCompanion.insert(id: remoteAlbumId, name: 'Movies', order: AlbumAssetOrder.desc), - ); - await db.localAlbumEntity.insertOne( - LocalAlbumEntityCompanion.insert( - id: localAlbumId, - name: 'Movies', - backupSelection: BackupSelection.selected, - linkedRemoteAlbumId: const drift.Value(remoteAlbumId), - ), - ); - - // sanity: link is set before reset - final before = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); - expect(before.linkedRemoteAlbumId, equals(remoteAlbumId)); - - await sut.reset(); - - final after = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); - expect( - after.linkedRemoteAlbumId, - isNull, - reason: - 'reset() runs with PRAGMA foreign_keys = OFF so the ON DELETE SET NULL cascade does not fire — the link must be nulled manually', - ); - expect(after.name, equals('Movies'), reason: 'local album row itself must be preserved'); - expect(after.backupSelection, equals(BackupSelection.selected)); - - final remoteRows = await db.remoteAlbumEntity.select().get(); - expect(remoteRows, isEmpty, reason: 'reset() still wipes remoteAlbumEntity'); - }); - - test('preserves localAlbumEntity rows that have no linkedRemoteAlbumId', () async { - const localAlbumId = 'local-unlinked'; - await db.localAlbumEntity.insertOne( - LocalAlbumEntityCompanion.insert(id: localAlbumId, name: 'Camera', backupSelection: BackupSelection.none), - ); - - await sut.reset(); - - final after = await (db.localAlbumEntity.select()..where((t) => t.id.equals(localAlbumId))).getSingle(); - expect(after.linkedRemoteAlbumId, isNull); - expect(after.name, equals('Camera')); - expect(after.backupSelection, equals(BackupSelection.none)); - }); - }); - - group('SyncStreamRepository - updateAssets upsert dedupe (#22522 #27186)', () { - Future seedExif(String assetId) => - db.remoteExifEntity.insertOne(RemoteExifEntityCompanion.insert(assetId: assetId)); - - Future exifExists(String assetId) async { - final rows = await (db.remoteExifEntity.select()..where((t) => t.assetId.equals(assetId))).get(); - return rows.isNotEmpty; - } - - test('same-id update keeps the child row and updates fields', () async { - await sut.updateUsersV1([_createUser()]); - final asset = _createAsset(id: 'a', checksum: 'AAA', fileName: 'photo.jpg'); - await sut.updateAssetsV1([asset]); - await seedExif(asset.id); - - final renamed = _createAsset(id: asset.id, checksum: asset.checksum, fileName: 'renamed.jpg', isFavorite: true); - await sut.updateAssetsV1([renamed]); - - expect(await exifExists(asset.id), isTrue, reason: 'DO UPDATE keeps the row, the child survives'); - final row = await (db.remoteAssetEntity.select()..where((t) => t.id.equals(asset.id))).getSingle(); - expect(row.name, renamed.originalFileName); - expect(row.isFavorite, isTrue); - }); - - test('reupload with a new id replaces the stale row and cascades its child', () async { - await sut.updateUsersV1([_createUser()]); - final stale = _createAsset(id: 'stale', checksum: 'AAA', fileName: 'photo.jpg'); - await sut.updateAssetsV1([stale]); - await seedExif(stale.id); - - final fresh = _createAsset(id: 'fresh', checksum: stale.checksum, fileName: stale.originalFileName); - await sut.updateAssetsV1([fresh]); - - final rows = await db.remoteAssetEntity.select().get(); - expect(rows, hasLength(1), reason: 'no 2067, stale row replaced away'); - expect(rows.single.id, fresh.id); - expect(await exifExists(stale.id), isFalse, reason: 'the stale child cascades with the replaced row'); - - // same scenario through V2 - final staleV2 = _createAssetV2(id: 'stale2', checksum: 'BBB', fileName: 'photo2.jpg'); - await sut.updateAssetsV2([staleV2]); - await seedExif(staleV2.id); - final freshV2 = _createAssetV2(id: 'fresh2', checksum: staleV2.checksum, fileName: staleV2.originalFileName); - await sut.updateAssetsV2([freshV2]); - - final rows2 = await db.remoteAssetEntity.select().get(); - expect(rows2.map((r) => r.id), containsAll([fresh.id, freshV2.id])); - expect(await exifExists(staleV2.id), isFalse); - }); - - test('library variant replaces only the matching library row', () async { - await sut.updateUsersV1([_createUser()]); - final staleLib = _createAsset(id: 'stale-lib', checksum: 'AAA', fileName: 'photo.jpg', libraryId: 'lib-1'); - final keepNull = _createAsset(id: 'keep-null', checksum: staleLib.checksum, fileName: staleLib.originalFileName); - await sut.updateAssetsV1([staleLib, keepNull]); - - final freshLib = _createAsset( - id: 'fresh-lib', - checksum: staleLib.checksum, - fileName: staleLib.originalFileName, - libraryId: staleLib.libraryId, - ); - await sut.updateAssetsV1([freshLib]); - - final rows = await db.remoteAssetEntity.select().get(); - expect(rows.map((r) => r.id).toSet(), { - freshLib.id, - keepNull.id, - }, reason: 'library NULL and NOT NULL match different partial indexes'); - }); - - test('batch-internal duplicates keep the last payload asset', () async { - await sut.updateUsersV1([_createUser()]); - final first = _createAsset(id: 'first-id', checksum: 'AAA', fileName: 'photo.jpg'); - final last = _createAsset(id: 'last-id', checksum: first.checksum, fileName: first.originalFileName); - final firstLib = _createAsset( - id: 'first-lib', - checksum: 'BBB', - fileName: first.originalFileName, - libraryId: 'lib-1', - ); - final lastLib = _createAsset( - id: 'last-lib', - checksum: firstLib.checksum, - fileName: firstLib.originalFileName, - libraryId: firstLib.libraryId, - ); - - await sut.updateAssetsV1([first, last, firstLib, lastLib]); - - final rows = await db.remoteAssetEntity.select().get(); - expect(rows, hasLength(2), reason: 'REPLACE makes batch-internal duplicates last-wins, no crash'); - expect(rows.map((r) => r.id).toSet(), {last.id, lastLib.id}); - }); - }); } diff --git a/mobile/test/domain/service.mock.dart b/mobile/test/domain/service.mock.dart new file mode 100644 index 0000000000..56b4802f88 --- /dev/null +++ b/mobile/test/domain/service.mock.dart @@ -0,0 +1,20 @@ +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/domain/utils/background_sync.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:immich_mobile/services/background_upload.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockStoreService extends Mock implements StoreService {} + +class MockUserService extends Mock implements UserService {} + +class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {} + +class MockNativeSyncApi extends Mock implements NativeSyncApi {} + +class MockAppSettingsService extends Mock implements AppSettingsService {} + +class MockBackgroundUploadService extends Mock implements BackgroundUploadService {} + diff --git a/mobile/test/domain/services/album.service_test.dart b/mobile/test/domain/services/album.service_test.dart new file mode 100644 index 0000000000..9110a09471 --- /dev/null +++ b/mobile/test/domain/services/album.service_test.dart @@ -0,0 +1,115 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/album/album.model.dart'; +import 'package:immich_mobile/domain/services/remote_album.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../infrastructure/repository.mock.dart'; + +void main() { + late RemoteAlbumService sut; + late DriftRemoteAlbumRepository mockRemoteAlbumRepo; + late DriftAlbumApiRepository mockAlbumApiRepo; + + final albumA = RemoteAlbum( + id: '1', + name: 'Album A', + description: "", + isActivityEnabled: false, + order: AlbumAssetOrder.asc, + assetCount: 1, + createdAt: DateTime(2023, 1, 1), + updatedAt: DateTime(2023, 1, 2), + ownerId: 'owner1', + ownerName: "Test User", + isShared: false, + ); + + final albumB = RemoteAlbum( + id: '2', + name: 'Album B', + description: "", + isActivityEnabled: false, + order: AlbumAssetOrder.desc, + assetCount: 2, + createdAt: DateTime(2023, 2, 1), + updatedAt: DateTime(2023, 2, 2), + ownerId: 'owner2', + ownerName: "Test User", + isShared: false, + ); + + setUp(() { + mockRemoteAlbumRepo = MockRemoteAlbumRepository(); + mockAlbumApiRepo = MockDriftAlbumApiRepository(); + + when( + () => mockRemoteAlbumRepo.getSortedAlbumIds(any(), aggregation: AssetDateAggregation.end), + ).thenAnswer((_) async => ['1', '2']); + + when( + () => mockRemoteAlbumRepo.getSortedAlbumIds(any(), aggregation: AssetDateAggregation.start), + ).thenAnswer((_) async => ['1', '2']); + + sut = RemoteAlbumService(mockRemoteAlbumRepo, mockAlbumApiRepo); + }); + + group('sortAlbums', () { + test('should sort correctly based on name', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.title); + expect(result, [albumA, albumB]); + }); + + test('should sort correctly based on createdAt', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.created); + expect(result, [albumB, albumA]); + }); + + test('should sort correctly based on updatedAt', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.lastModified); + expect(result, [albumB, albumA]); + }); + + test('should sort correctly based on assetCount', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.assetCount); + expect(result, [albumB, albumA]); + }); + + test('should sort correctly based on newestAssetTimestamp', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.mostRecent); + expect(result, [albumB, albumA]); + }); + + test('should sort correctly based on oldestAssetTimestamp', () async { + final albums = [albumB, albumA]; + + final result = await sut.sortAlbums(albums, AlbumSortMode.mostOldest); + expect(result, [albumA, albumB]); + }); + + test('should flip order when isReverse is true for all modes', () async { + final albums = [albumB, albumA]; + + for (final mode in AlbumSortMode.values) { + final normal = await sut.sortAlbums(albums, mode, isReverse: false); + final reversed = await sut.sortAlbums(albums, mode, isReverse: true); + + // reversed should be the exact inverse of normal + expect(reversed, normal.reversed.toList(), reason: 'Mode: $mode'); + } + }); + }); +} diff --git a/mobile/test/domain/services/asset.service_test.dart b/mobile/test/domain/services/asset.service_test.dart new file mode 100644 index 0000000000..04e49f89f9 --- /dev/null +++ b/mobile/test/domain/services/asset.service_test.dart @@ -0,0 +1,185 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/domain/services/asset.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../infrastructure/repository.mock.dart'; +import '../../test_utils.dart'; + +void main() { + late AssetService sut; + late MockRemoteAssetRepository mockRemoteAssetRepository; + late MockDriftLocalAssetRepository mockLocalAssetRepository; + + setUp(() { + mockRemoteAssetRepository = MockRemoteAssetRepository(); + mockLocalAssetRepository = MockDriftLocalAssetRepository(); + sut = AssetService( + remoteAssetRepository: mockRemoteAssetRepository, + localAssetRepository: mockLocalAssetRepository, + ); + }); + + group('getAspectRatio', () { + test('flips dimensions on Android for 90° and 270° orientations', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + addTearDown(() => debugDefaultTargetPlatformOverride = null); + + for (final orientation in [90, 270]) { + final localAsset = TestUtils.createLocalAsset( + id: 'local-$orientation', + width: 1920, + height: 1080, + orientation: orientation, + ); + + final result = await sut.getAspectRatio(localAsset); + + expect(result, 1080 / 1920, reason: 'Orientation $orientation should flip on Android'); + } + }); + + test('does not flip dimensions on iOS regardless of orientation', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + addTearDown(() => debugDefaultTargetPlatformOverride = null); + + for (final orientation in [0, 90, 270]) { + final localAsset = TestUtils.createLocalAsset( + id: 'local-$orientation', + width: 1920, + height: 1080, + orientation: orientation, + ); + + final result = await sut.getAspectRatio(localAsset); + + expect(result, 1920 / 1080, reason: 'iOS should never flip dimensions'); + } + }); + + test('fetches dimensions from remote repository when missing from asset', () async { + final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: null, height: null); + + final exif = const ExifInfo(orientation: '1'); + + final fetchedAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: 1920, height: 1080); + + when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif); + when(() => mockRemoteAssetRepository.get('remote-1')).thenAnswer((_) async => fetchedAsset); + + final result = await sut.getAspectRatio(remoteAsset); + + expect(result, 1920 / 1080); + verify(() => mockRemoteAssetRepository.get('remote-1')).called(1); + }); + + test('fetches dimensions from local repository when missing from local asset', () async { + final localAsset = TestUtils.createLocalAsset(id: 'local-1', width: null, height: null, orientation: 0); + + final fetchedAsset = TestUtils.createLocalAsset(id: 'local-1', width: 1920, height: 1080, orientation: 0); + + when(() => mockLocalAssetRepository.get('local-1')).thenAnswer((_) async => fetchedAsset); + + final result = await sut.getAspectRatio(localAsset); + + expect(result, 1920 / 1080); + verify(() => mockLocalAssetRepository.get('local-1')).called(1); + }); + + test('uses fetched asset orientation when dimensions are missing on Android', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + addTearDown(() => debugDefaultTargetPlatformOverride = null); + + // Original asset has default orientation 0, but dimensions are missing + final localAsset = TestUtils.createLocalAsset(id: 'local-1', width: null, height: null, orientation: 0); + + // Fetched asset has 90° orientation and proper dimensions + final fetchedAsset = TestUtils.createLocalAsset(id: 'local-1', width: 1920, height: 1080, orientation: 90); + + when(() => mockLocalAssetRepository.get('local-1')).thenAnswer((_) async => fetchedAsset); + + final result = await sut.getAspectRatio(localAsset); + + // Should flip dimensions since fetched asset has 90° orientation + expect(result, 1080 / 1920); + verify(() => mockLocalAssetRepository.get('local-1')).called(1); + }); + + test('returns 1.0 when dimensions are still unavailable after fetching', () async { + final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: null, height: null); + + final exif = const ExifInfo(orientation: '1'); + + when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif); + when(() => mockRemoteAssetRepository.get('remote-1')).thenAnswer((_) async => null); + + final result = await sut.getAspectRatio(remoteAsset); + + expect(result, 1.0); + }); + + test('returns 1.0 when height is zero', () async { + final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-1', width: 1920, height: 0); + + final exif = const ExifInfo(orientation: '1'); + + when(() => mockRemoteAssetRepository.getExif('remote-1')).thenAnswer((_) async => exif); + + final result = await sut.getAspectRatio(remoteAsset); + + expect(result, 1.0); + }); + + test('handles local asset with remoteId using local orientation not remote exif', () async { + // When a LocalAsset has a remoteId (merged), we should use local orientation + // because the width/height come from the local asset (pre-corrected on iOS) + final localAsset = TestUtils.createLocalAsset( + id: 'local-1', + remoteId: 'remote-1', + width: 1920, + height: 1080, + orientation: 0, + ); + + final result = await sut.getAspectRatio(localAsset); + + expect(result, 1920 / 1080); + // Should not call remote exif for LocalAsset + verifyNever(() => mockRemoteAssetRepository.getExif(any())); + }); + + test('handles local asset with remoteId and 90 degree rotation on Android', () async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + addTearDown(() => debugDefaultTargetPlatformOverride = null); + + final localAsset = TestUtils.createLocalAsset( + id: 'local-1', + remoteId: 'remote-1', + width: 1920, + height: 1080, + orientation: 90, + ); + + final result = await sut.getAspectRatio(localAsset); + + expect(result, 1080 / 1920); + }); + + test('should not flip remote asset dimensions', () async { + final flippedOrientations = ['1', '2', '3', '4', '5', '6', '7', '8', '90', '-90']; + + for (final orientation in flippedOrientations) { + final remoteAsset = TestUtils.createRemoteAsset(id: 'remote-$orientation', width: 1920, height: 1080); + + final exif = ExifInfo(orientation: orientation); + + when(() => mockRemoteAssetRepository.getExif('remote-$orientation')).thenAnswer((_) async => exif); + + final result = await sut.getAspectRatio(remoteAsset); + + expect(result, 1920 / 1080, reason: 'Should not flipped remote asset dimensions for orientation $orientation'); + } + }); + }); +} diff --git a/mobile/test/domain/services/hash_service_test.dart b/mobile/test/domain/services/hash_service_test.dart new file mode 100644 index 0000000000..9f36a5635e --- /dev/null +++ b/mobile/test/domain/services/hash_service_test.dart @@ -0,0 +1,194 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/domain/services/hash.service.dart'; +import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../fixtures/album.stub.dart'; +import '../../fixtures/asset.stub.dart'; +import '../../infrastructure/repository.mock.dart'; +import '../service.mock.dart'; + +void main() { + late HashService sut; + late MockLocalAlbumRepository mockAlbumRepo; + late MockLocalAssetRepository mockAssetRepo; + late MockNativeSyncApi mockNativeApi; + late MockTrashedLocalAssetRepository mockTrashedAssetRepo; + + setUp(() { + mockAlbumRepo = MockLocalAlbumRepository(); + mockAssetRepo = MockLocalAssetRepository(); + mockNativeApi = MockNativeSyncApi(); + mockTrashedAssetRepo = MockTrashedLocalAssetRepository(); + + sut = HashService( + localAlbumRepository: mockAlbumRepo, + localAssetRepository: mockAssetRepo, + nativeSyncApi: mockNativeApi, + trashedLocalAssetRepository: mockTrashedAssetRepo, + ); + + registerFallbackValue(LocalAlbumStub.recent); + registerFallbackValue(LocalAssetStub.image1); + registerFallbackValue({}); + + when(() => mockAssetRepo.reconcileHashesFromCloudId()).thenAnswer((_) async => {}); + when(() => mockAssetRepo.updateHashes(any())).thenAnswer((_) async => {}); + }); + + group('HashService hashAssets', () { + test('skips albums with no assets to hash', () async { + when( + () => mockAlbumRepo.getBackupAlbums(), + ).thenAnswer((_) async => [LocalAlbumStub.recent.copyWith(assetCount: 0)]); + when(() => mockAlbumRepo.getAssetsToHash(LocalAlbumStub.recent.id)).thenAnswer((_) async => []); + + await sut.hashAssets(); + + verifyNever(() => mockNativeApi.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess'))); + }); + }); + + group('HashService _hashAssets', () { + test('skips empty batches', () async { + final album = LocalAlbumStub.recent; + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => []); + + await sut.hashAssets(); + + verifyNever(() => mockNativeApi.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess'))); + }); + + test('processes assets when available', () async { + final album = LocalAlbumStub.recent; + final asset = LocalAssetStub.image1; + + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); + when( + () => mockNativeApi.hashAssets([asset.id], allowNetworkAccess: false), + ).thenAnswer((_) async => [HashResult(assetId: asset.id, hash: 'test-hash')]); + + await sut.hashAssets(); + + verify(() => mockNativeApi.hashAssets([asset.id], allowNetworkAccess: false)).called(1); + final captured = verify(() => mockAssetRepo.updateHashes(captureAny())).captured.first as Map; + expect(captured.length, 1); + expect(captured[asset.id], 'test-hash'); + }); + + test('handles failed hashes', () async { + final album = LocalAlbumStub.recent; + final asset = LocalAssetStub.image1; + + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); + when( + () => mockNativeApi.hashAssets([asset.id], allowNetworkAccess: false), + ).thenAnswer((_) async => [HashResult(assetId: asset.id, error: 'Failed to hash')]); + + await sut.hashAssets(); + + final captured = verify(() => mockAssetRepo.updateHashes(captureAny())).captured.first as Map; + expect(captured.length, 0); + }); + + test('handles null hash results', () async { + final album = LocalAlbumStub.recent; + final asset = LocalAssetStub.image1; + + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); + when( + () => mockNativeApi.hashAssets([asset.id], allowNetworkAccess: false), + ).thenAnswer((_) async => [HashResult(assetId: asset.id, hash: null)]); + + await sut.hashAssets(); + + final captured = verify(() => mockAssetRepo.updateHashes(captureAny())).captured.first as Map; + expect(captured.length, 0); + }); + + test('batches by size limit', () async { + const batchSize = 2; + final sut = HashService( + localAlbumRepository: mockAlbumRepo, + localAssetRepository: mockAssetRepo, + nativeSyncApi: mockNativeApi, + batchSize: batchSize, + trashedLocalAssetRepository: mockTrashedAssetRepo, + ); + + final album = LocalAlbumStub.recent; + final asset1 = LocalAssetStub.image1; + final asset2 = LocalAssetStub.image2; + final asset3 = LocalAssetStub.image1.copyWith(id: 'image3', name: 'image3.jpg'); + + final capturedCalls = >[]; + + when(() => mockAssetRepo.updateHashes(any())).thenAnswer((_) async => {}); + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset1, asset2, asset3]); + when(() => mockNativeApi.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess'))).thenAnswer(( + invocation, + ) async { + final assetIds = invocation.positionalArguments[0] as List; + capturedCalls.add(List.from(assetIds)); + return assetIds.map((id) => HashResult(assetId: id, hash: '$id-hash')).toList(); + }); + + await sut.hashAssets(); + + expect(capturedCalls.length, 2, reason: 'Should make exactly 2 calls to hashAssets'); + expect(capturedCalls[0], [asset1.id, asset2.id], reason: 'First call should batch the first two assets'); + expect(capturedCalls[1], [asset3.id], reason: 'Second call should have the remaining asset'); + + verify(() => mockAssetRepo.updateHashes(any())).called(2); + }); + + test('handles mixed success and failure in batch', () async { + final album = LocalAlbumStub.recent; + final asset1 = LocalAssetStub.image1; + final asset2 = LocalAssetStub.image2; + + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [album]); + when(() => mockAlbumRepo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset1, asset2]); + when(() => mockNativeApi.hashAssets([asset1.id, asset2.id], allowNetworkAccess: false)).thenAnswer( + (_) async => [ + HashResult(assetId: asset1.id, hash: 'asset1-hash'), + HashResult(assetId: asset2.id, error: 'Failed to hash asset2'), + ], + ); + + await sut.hashAssets(); + + final captured = verify(() => mockAssetRepo.updateHashes(captureAny())).captured.first as Map; + expect(captured.length, 1); + expect(captured[asset1.id], 'asset1-hash'); + }); + + test('uses allowNetworkAccess based on album backup selection', () async { + final selectedAlbum = LocalAlbumStub.recent.copyWith(backupSelection: BackupSelection.selected); + final nonSelectedAlbum = LocalAlbumStub.recent.copyWith(id: 'album2', backupSelection: BackupSelection.excluded); + final asset1 = LocalAssetStub.image1; + final asset2 = LocalAssetStub.image2; + + when(() => mockAlbumRepo.getBackupAlbums()).thenAnswer((_) async => [selectedAlbum, nonSelectedAlbum]); + when(() => mockAlbumRepo.getAssetsToHash(selectedAlbum.id)).thenAnswer((_) async => [asset1]); + when(() => mockAlbumRepo.getAssetsToHash(nonSelectedAlbum.id)).thenAnswer((_) async => [asset2]); + when(() => mockNativeApi.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess'))).thenAnswer(( + invocation, + ) async { + final assetIds = invocation.positionalArguments[0] as List; + return assetIds.map((id) => HashResult(assetId: id, hash: '$id-hash')).toList(); + }); + + await sut.hashAssets(); + + verify(() => mockNativeApi.hashAssets([asset1.id], allowNetworkAccess: true)).called(1); + verify(() => mockNativeApi.hashAssets([asset2.id], allowNetworkAccess: false)).called(1); + }); + }); +} diff --git a/mobile/test/domain/services/local_sync_service_test.dart b/mobile/test/domain/services/local_sync_service_test.dart index 2cbabd77e9..df65fa3306 100644 --- a/mobile/test/domain/services/local_sync_service_test.dart +++ b/mobile/test/domain/services/local_sync_service_test.dart @@ -10,24 +10,26 @@ import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:mocktail/mocktail.dart'; +import '../../domain/service.mock.dart'; import '../../fixtures/asset.stub.dart'; import '../../infrastructure/repository.mock.dart'; +import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; -import '../../service.mocks.dart'; void main() { late LocalSyncService sut; - late LocalAlbumRepository mockLocalAlbumRepository; - late LocalAssetRepository mockLocalAssetRepository; - late TrashedLocalAssetRepository mockTrashedLocalAssetRepository; - late AssetMediaRepository mockAssetMediaRepository; - late MockPermissionRepository mockPermissionRepository; + late DriftLocalAlbumRepository mockLocalAlbumRepository; + late DriftLocalAssetRepository mockLocalAssetRepository; + late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepository; + late LocalFilesManagerRepository mockLocalFilesManager; + late StorageRepository mockStorageRepository; late MockNativeSyncApi mockNativeSyncApi; late Drift db; @@ -36,7 +38,7 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.android; db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); }); tearDownAll(() async { @@ -49,8 +51,8 @@ void main() { mockLocalAlbumRepository = MockLocalAlbumRepository(); mockLocalAssetRepository = MockLocalAssetRepository(); mockTrashedLocalAssetRepository = MockTrashedLocalAssetRepository(); - mockAssetMediaRepository = MockAssetMediaRepository(); - mockPermissionRepository = MockPermissionRepository(); + mockLocalFilesManager = MockLocalFilesManagerRepository(); + mockStorageRepository = MockStorageRepository(); mockNativeSyncApi = MockNativeSyncApi(); when(() => mockNativeSyncApi.shouldFullSync()).thenAnswer((_) async => false); @@ -63,28 +65,25 @@ void main() { when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer((_) async => {}); when(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())).thenAnswer((_) async {}); when(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())).thenAnswer((_) async {}); - when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - return ids; - }); + when(() => mockLocalFilesManager.moveToTrash(any>())).thenAnswer((_) async => true); sut = LocalSyncService( localAlbumRepository: mockLocalAlbumRepository, localAssetRepository: mockLocalAssetRepository, trashedLocalAssetRepository: mockTrashedLocalAssetRepository, - assetMediaRepository: mockAssetMediaRepository, - permissionRepository: mockPermissionRepository, + localFilesManager: mockLocalFilesManager, + storageRepository: mockStorageRepository, nativeSyncApi: mockNativeSyncApi, ); await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); }); group('LocalSyncService - syncTrashedAssets gating', () { test('invokes syncTrashedAssets when Android flag enabled and permission granted', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -94,7 +93,7 @@ void main() { test('skips syncTrashedAssets when store flag disabled', () async { await Store.put(StoreKey.manageLocalMediaAndroid, false); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -103,7 +102,7 @@ void main() { test('skips syncTrashedAssets when MANAGE_MEDIA permission absent', () async { await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => false); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => false); await sut.sync(); @@ -115,7 +114,7 @@ void main() { addTearDown(() => debugDefaultTargetPlatformOverride = TargetPlatform.android); await Store.put(StoreKey.manageLocalMediaAndroid, true); - when(() => mockPermissionRepository.hasManageMediaPermission()).thenAnswer((_) async => true); + when(() => mockLocalFilesManager.hasManageMediaPermission()).thenAnswer((_) async => true); await sut.sync(); @@ -129,16 +128,16 @@ void main() { id: 'remote-id', name: 'remote.jpg', type: AssetType.image.index, - durationMs: 0, + durationInSeconds: 0, orientation: 0, isFavorite: false, - playbackStyle: PlatformAssetPlaybackStyle.image, + playbackStyle: PlatformAssetPlaybackStyle.image ); final assetsToRestore = [LocalAssetStub.image1]; when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => assetsToRestore); final restoredIds = ['image1']; - when(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requested = invocation.positionalArguments.first as Iterable; expect(requested, orderedEquals(assetsToRestore)); return restoredIds; @@ -151,6 +150,10 @@ void main() { }, ); + final assetEntity = MockAssetEntity(); + when(() => assetEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-trash'); + when(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).thenAnswer((_) async => assetEntity); + await sut.processTrashedAssets({ 'album-a': [platformAsset], }); @@ -165,11 +168,12 @@ void main() { expect(trashedEntry.asset.name, platformAsset.name); verify(() => mockTrashedLocalAssetRepository.getToTrash()).called(1); - verify(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())).called(1); + verify(() => mockLocalFilesManager.restoreAssetsFromTrash(any())).called(1); verify(() => mockTrashedLocalAssetRepository.applyRestoredAssets(restoredIds)).called(1); - final moveArgs = verify(() => mockAssetMediaRepository.deleteAll(captureAny())).captured.single as List; - expect(moveArgs, ['local-trash']); + verify(() => mockStorageRepository.getAssetEntityForAsset(localAssetToTrash)).called(1); + final moveArgs = verify(() => mockLocalFilesManager.moveToTrash(captureAny())).captured.single as List; + expect(moveArgs, ['content://local-trash']); final trashArgs = verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single as Map>; @@ -177,26 +181,6 @@ void main() { expect(trashArgs['album-a'], [localAssetToTrash]); }); - test('records only local assets that were moved to device trash', () async { - final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); - final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); - when(() => mockTrashedLocalAssetRepository.getToTrash()).thenAnswer( - (_) async => { - 'album-a': [movedAsset], - 'album-b': [skippedAsset], - }, - ); - when(() => mockAssetMediaRepository.deleteAll(any())).thenAnswer((_) async => ['moved-local']); - - await sut.processTrashedAssets({}); - - final trashArgs = - verify(() => mockTrashedLocalAssetRepository.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, ['album-a']); - expect(trashArgs['album-a'], [movedAsset]); - }); - test('does not attempt restore when repository has no assets to restore', () async { when(() => mockTrashedLocalAssetRepository.getToRestore()).thenAnswer((_) async => []); @@ -206,7 +190,7 @@ void main() { verify(() => mockTrashedLocalAssetRepository.processTrashSnapshot(captureAny())).captured.single as Iterable; expect(trashedSnapshot, isEmpty); - verifyNever(() => mockAssetMediaRepository.restoreAssetsFromTrash(any())); + verifyNever(() => mockLocalFilesManager.restoreAssetsFromTrash(any())); verifyNever(() => mockTrashedLocalAssetRepository.applyRestoredAssets(any())); }); @@ -215,7 +199,7 @@ void main() { await sut.processTrashedAssets({}); - verifyNever(() => mockAssetMediaRepository.deleteAll(any())); + verifyNever(() => mockLocalFilesManager.moveToTrash(any())); verifyNever(() => mockTrashedLocalAssetRepository.trashLocalAsset(any())); }); }); @@ -226,12 +210,12 @@ void main() { id: 'test-id', name: 'test.jpg', type: AssetType.image.index, - durationMs: 0, + durationInSeconds: 0, orientation: 0, isFavorite: false, createdAt: 1700000000, updatedAt: 1732000000, - playbackStyle: PlatformAssetPlaybackStyle.image, + playbackStyle: PlatformAssetPlaybackStyle.image ); final localAsset = platformAsset.toLocalAsset(); diff --git a/mobile/test/domain/services/log_service_test.dart b/mobile/test/domain/services/log_service_test.dart index 6a82d1dce3..95f677ba98 100644 --- a/mobile/test/domain/services/log_service_test.dart +++ b/mobile/test/domain/services/log_service_test.dart @@ -1,11 +1,11 @@ import 'package:collection/collection.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/constants.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/log.service.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:logging/logging.dart'; import 'package:mocktail/mocktail.dart'; @@ -29,23 +29,21 @@ final _kWarnLog = LogMessage( void main() { late LogService sut; late LogRepository mockLogRepo; - late MockSettingsRepository mockSettingsRepository; + late IsarStoreRepository mockStoreRepo; setUp(() async { mockLogRepo = MockLogRepository(); - mockSettingsRepository = MockSettingsRepository(); + mockStoreRepo = MockStoreRepository(); registerFallbackValue(_kInfoLog); - registerFallbackValue(LogLevel.info); when(() => mockLogRepo.truncate(limit: any(named: 'limit'))).thenAnswer((_) async => {}); - when(() => mockSettingsRepository.appConfig).thenReturn(const AppConfig(logLevel: LogLevel.fine)); - when(() => mockSettingsRepository.write(SettingsKey.logLevel, any())).thenAnswer((_) async {}); + when(() => mockStoreRepo.tryGet(StoreKey.logLevel)).thenAnswer((_) async => LogLevel.fine.index); when(() => mockLogRepo.getAll()).thenAnswer((_) async => []); when(() => mockLogRepo.insert(any())).thenAnswer((_) async => true); when(() => mockLogRepo.insertAll(any())).thenAnswer((_) async => true); - sut = await LogService.create(logRepository: mockLogRepo, settingsRepository: mockSettingsRepository); + sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo); }); tearDown(() async { @@ -58,22 +56,21 @@ void main() { expect(limit, kLogTruncateLimit); }); - test('Sets log level based on the metadata repository', () { - verify(() => mockSettingsRepository.appConfig).called(1); + test('Sets log level based on the store setting', () { + verify(() => mockStoreRepo.tryGet(StoreKey.logLevel)).called(1); expect(Logger.root.level, Level.FINE); }); }); group("Log Service Set Level:", () { setUp(() async { + when(() => mockStoreRepo.upsert(StoreKey.logLevel, any())).thenAnswer((_) async => true); await sut.setLogLevel(LogLevel.shout); }); - test('Updates the log level via metadata repository', () { - final captured = verify( - () => mockSettingsRepository.write(SettingsKey.logLevel, captureAny()), - ).captured.firstOrNull; - expect(captured, LogLevel.shout); + test('Updates the log level in store', () { + final index = verify(() => mockStoreRepo.upsert(StoreKey.logLevel, captureAny())).captured.firstOrNull; + expect(index, LogLevel.shout.index); }); test('Sets log level on logger', () { @@ -84,11 +81,7 @@ void main() { group("Log Service Buffer:", () { test('Buffers logs until timer elapses', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create( - logRepository: mockLogRepo, - settingsRepository: mockSettingsRepository, - shouldBuffer: true, - ); + sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -102,11 +95,7 @@ void main() { test('Batch inserts all logs on timer', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create( - logRepository: mockLogRepo, - settingsRepository: mockSettingsRepository, - shouldBuffer: true, - ); + sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -123,11 +112,7 @@ void main() { test('Does not buffer when off', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create( - logRepository: mockLogRepo, - settingsRepository: mockSettingsRepository, - shouldBuffer: false, - ); + sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: false); final logger = Logger(_kInfoLog.logger!); logger.info(_kInfoLog.message); @@ -157,11 +142,7 @@ void main() { test('Combines result from both DB + Buffer', () { TestUtils.fakeAsync((time) async { - sut = await LogService.create( - logRepository: mockLogRepo, - settingsRepository: mockSettingsRepository, - shouldBuffer: true, - ); + sut = await LogService.create(logRepository: mockLogRepo, storeRepository: mockStoreRepo, shouldBuffer: true); final logger = Logger(_kWarnLog.logger!); logger.warning(_kWarnLog.message); diff --git a/mobile/test/domain/services/store_service_test.dart b/mobile/test/domain/services/store_service_test.dart index adab8863c1..996170b518 100644 --- a/mobile/test/domain/services/store_service_test.dart +++ b/mobile/test/domain/services/store_service_test.dart @@ -9,32 +9,47 @@ import 'package:mocktail/mocktail.dart'; import '../../infrastructure/repository.mock.dart'; const _kAccessToken = '#ThisIsAToken'; -const _kAdvancedTroubleshooting = false; -const _kVersion = 2; +const _kBackgroundBackup = false; +const _kGroupAssetsBy = 2; +final _kBackupFailedSince = DateTime.utc(2023); void main() { late StoreService sut; - late StoreRepository mockDriftStoreRepo; + late IsarStoreRepository mockStoreRepo; + late DriftStoreRepository mockDriftStoreRepo; late StreamController>> controller; setUp(() async { controller = StreamController>>.broadcast(); - mockDriftStoreRepo = MockStoreRepository(); + mockStoreRepo = MockStoreRepository(); + mockDriftStoreRepo = MockDriftStoreRepository(); // For generics, we need to provide fallback to each concrete type to avoid runtime errors registerFallbackValue(StoreKey.accessToken); - registerFallbackValue(StoreKey.version); - registerFallbackValue(StoreKey.advancedTroubleshooting); + registerFallbackValue(StoreKey.backupTriggerDelay); + registerFallbackValue(StoreKey.backgroundBackup); + registerFallbackValue(StoreKey.backupFailedSince); + + when(() => mockStoreRepo.getAll()).thenAnswer( + (_) async => [ + const StoreDto(StoreKey.accessToken, _kAccessToken), + const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup), + const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy), + StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince), + ], + ); + when(() => mockStoreRepo.watchAll()).thenAnswer((_) => controller.stream); when(() => mockDriftStoreRepo.getAll()).thenAnswer( (_) async => [ const StoreDto(StoreKey.accessToken, _kAccessToken), - const StoreDto(StoreKey.advancedTroubleshooting, _kAdvancedTroubleshooting), - const StoreDto(StoreKey.version, _kVersion), + const StoreDto(StoreKey.backgroundBackup, _kBackgroundBackup), + const StoreDto(StoreKey.groupAssetsBy, _kGroupAssetsBy), + StoreDto(StoreKey.backupFailedSince, _kBackupFailedSince), ], ); when(() => mockDriftStoreRepo.watchAll()).thenAnswer((_) => controller.stream); - sut = await StoreService.create(storeRepository: mockDriftStoreRepo); + sut = await StoreService.create(storeRepository: mockStoreRepo); }); tearDown(() async { @@ -44,10 +59,11 @@ void main() { group("Store Service Init:", () { test('Populates the internal cache on init', () { - verify(() => mockDriftStoreRepo.getAll()).called(1); + verify(() => mockStoreRepo.getAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), _kAccessToken); - expect(sut.tryGet(StoreKey.advancedTroubleshooting), _kAdvancedTroubleshooting); - expect(sut.tryGet(StoreKey.version), _kVersion); + expect(sut.tryGet(StoreKey.backgroundBackup), _kBackgroundBackup); + expect(sut.tryGet(StoreKey.groupAssetsBy), _kGroupAssetsBy); + expect(sut.tryGet(StoreKey.backupFailedSince), _kBackupFailedSince); // Other keys should be null expect(sut.tryGet(StoreKey.currentUser), isNull); }); @@ -58,7 +74,7 @@ void main() { await pumpEventQueue(); - verify(() => mockDriftStoreRepo.watchAll()).called(1); + verify(() => mockStoreRepo.watchAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), _kAccessToken.toUpperCase()); }); }); @@ -79,18 +95,19 @@ void main() { group('Store Service put:', () { setUp(() { + when(() => mockStoreRepo.upsert(any>(), any())).thenAnswer((_) async => true); when(() => mockDriftStoreRepo.upsert(any>(), any())).thenAnswer((_) async => true); }); test('Skip insert when value is not modified', () async { await sut.put(StoreKey.accessToken, _kAccessToken); - verifyNever(() => mockDriftStoreRepo.upsert(StoreKey.accessToken, any())); + verifyNever(() => mockStoreRepo.upsert(StoreKey.accessToken, any())); }); test('Insert value when modified', () async { final newAccessToken = _kAccessToken.toUpperCase(); await sut.put(StoreKey.accessToken, newAccessToken); - verify(() => mockDriftStoreRepo.upsert(StoreKey.accessToken, newAccessToken)).called(1); + verify(() => mockStoreRepo.upsert(StoreKey.accessToken, newAccessToken)).called(1); expect(sut.tryGet(StoreKey.accessToken), newAccessToken); }); }); @@ -100,6 +117,7 @@ void main() { setUp(() { valueController = StreamController.broadcast(); + when(() => mockStoreRepo.watch(any>())).thenAnswer((_) => valueController.stream); when(() => mockDriftStoreRepo.watch(any>())).thenAnswer((_) => valueController.stream); }); @@ -118,18 +136,19 @@ void main() { } await pumpEventQueue(); - verify(() => mockDriftStoreRepo.watch(StoreKey.accessToken)).called(1); + verify(() => mockStoreRepo.watch(StoreKey.accessToken)).called(1); }); }); group('Store Service delete:', () { setUp(() { - when(() => mockDriftStoreRepo.deleteValue(any>())).thenAnswer((_) async => true); + when(() => mockStoreRepo.delete(any>())).thenAnswer((_) async => true); + when(() => mockDriftStoreRepo.delete(any>())).thenAnswer((_) async => true); }); test('Removes the value from the DB', () async { await sut.delete(StoreKey.accessToken); - verify(() => mockDriftStoreRepo.deleteValue(StoreKey.accessToken)).called(1); + verify(() => mockStoreRepo.delete(StoreKey.accessToken)).called(1); }); test('Removes the value from the cache', () async { @@ -140,15 +159,17 @@ void main() { group('Store Service clear:', () { setUp(() { + when(() => mockStoreRepo.deleteAll()).thenAnswer((_) async => true); when(() => mockDriftStoreRepo.deleteAll()).thenAnswer((_) async => true); }); test('Clears all values from the store', () async { await sut.clear(); - verify(() => mockDriftStoreRepo.deleteAll()).called(1); + verify(() => mockStoreRepo.deleteAll()).called(1); expect(sut.tryGet(StoreKey.accessToken), isNull); - expect(sut.tryGet(StoreKey.advancedTroubleshooting), isNull); - expect(sut.tryGet(StoreKey.version), isNull); + expect(sut.tryGet(StoreKey.backgroundBackup), isNull); + expect(sut.tryGet(StoreKey.groupAssetsBy), isNull); + expect(sut.tryGet(StoreKey.backupFailedSince), isNull); }); }); } diff --git a/mobile/test/domain/services/sync_linked_album_service_test.dart b/mobile/test/domain/services/sync_linked_album_service_test.dart deleted file mode 100644 index 60b230ba65..0000000000 --- a/mobile/test/domain/services/sync_linked_album_service_test.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/services/sync_linked_album.service.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/store.provider.dart'; -import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../infrastructure/repository.mock.dart'; -import '../../service.mocks.dart'; - -void main() { - // A container with the service's deps overridden but cancellationProvider left - // alone, i.e. the root (main) isolate, where cancellationProvider has no - // override and throws if read. The UI reads this provider here. - ProviderContainer rootContainer() { - final drift = MockDrift(); - when(() => drift.localAlbumRepository).thenReturn(MockLocalAlbumRepository()); - when(() => drift.remoteAlbumRepository).thenReturn(MockRemoteAlbumRepository()); - final container = ProviderContainer( - overrides: [ - driftProvider.overrideWithValue(drift), - driftAlbumApiRepositoryProvider.overrideWithValue(MockDriftAlbumApiRepository()), - storeServiceProvider.overrideWithValue(MockStoreService()), - ], - ); - addTearDown(container.dispose); - return container; - } - - // Regression for #29125 (Sync Albums toggle) and #29119 (can't leave the album - // selection screen): #28694 made the provider watch cancellationProvider, so - // reading it off the isolate threw. The cancellation now lives on the isolate - // call path, not the provider, so the UI can build it. - test('builds on the root isolate without a cancellationProvider override', () { - final container = rootContainer(); - - expect(() => container.read(syncLinkedAlbumServiceProvider), returnsNormally); - expect(container.read(syncLinkedAlbumServiceProvider), isA()); - }); - - test('manageLinkedAlbums runs from the UI without a cancellation signal', () { - final service = rootContainer().read(syncLinkedAlbumServiceProvider); - - expect(service.manageLinkedAlbums(const [], 'user-1'), completes); - }); -} diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 58b96a2fc6..a182c6cdca 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -12,11 +12,12 @@ import 'package:immich_mobile/domain/services/sync_stream.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.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/infrastructure/repositories/trashed_local_asset.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; import 'package:immich_mobile/utils/semver.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; @@ -25,6 +26,7 @@ import '../../api.mocks.dart'; import '../../fixtures/asset.stub.dart'; import '../../fixtures/sync_stream.stub.dart'; import '../../infrastructure/repository.mock.dart'; +import '../../mocks/asset_entity.mock.dart'; import '../../repository.mocks.dart'; import '../../service.mocks.dart'; @@ -36,14 +38,22 @@ class _AbortCallbackWrapper { class _MockAbortCallbackWrapper extends Mock implements _AbortCallbackWrapper {} +class _CancellationWrapper { + const _CancellationWrapper(); + + bool call() => false; +} + +class _MockCancellationWrapper extends Mock implements _CancellationWrapper {} + void main() { late SyncStreamService sut; late SyncStreamRepository mockSyncStreamRepo; late SyncApiRepository mockSyncApiRepo; - late LocalAssetRepository mockLocalAssetRepo; - late TrashedLocalAssetRepository mockTrashedLocalAssetRepo; - late AssetMediaRepository mockAssetMediaRepo; - late MockPermissionRepository mockPermissionRepo; + late DriftLocalAssetRepository mockLocalAssetRepo; + late DriftTrashedLocalAssetRepository mockTrashedLocalAssetRepo; + late LocalFilesManagerRepository mockLocalFilesManagerRepo; + late StorageRepository mockStorageRepo; late MockApiService mockApi; late MockServerApi mockServerApi; late MockSyncMigrationRepository mockSyncMigrationRepo; @@ -60,7 +70,7 @@ void main() { registerFallbackValue(const SemVer(major: 2, minor: 5, patch: 0)); db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); }); tearDownAll(() async { @@ -69,15 +79,15 @@ void main() { await db.close(); }); - Future successHandler(Invocation _) async => true; + successHandler(Invocation _) async => true; setUp(() async { mockSyncStreamRepo = MockSyncStreamRepository(); mockSyncApiRepo = MockSyncApiRepository(); mockLocalAssetRepo = MockLocalAssetRepository(); mockTrashedLocalAssetRepo = MockTrashedLocalAssetRepository(); - mockAssetMediaRepo = MockAssetMediaRepository(); - mockPermissionRepo = MockPermissionRepository(); + mockLocalFilesManagerRepo = MockLocalFilesManagerRepository(); + mockStorageRepo = MockStorageRepository(); mockAbortCallbackWrapper = _MockAbortCallbackWrapper(); mockResetCallbackWrapper = _MockAbortCallbackWrapper(); mockApi = MockApiService(); @@ -86,13 +96,9 @@ void main() { when(() => mockAbortCallbackWrapper()).thenReturn(false); - when( - () => mockSyncApiRepo.streamChanges( - any(), - serverVersion: any(named: 'serverVersion'), - abortSignal: any(named: 'abortSignal'), - ), - ).thenAnswer((invocation) async { + when(() => mockSyncApiRepo.streamChanges(any(), serverVersion: any(named: 'serverVersion'))).thenAnswer(( + invocation, + ) async { handleEventsCallback = invocation.positionalArguments.first; }); @@ -101,7 +107,6 @@ void main() { any(), onReset: any(named: 'onReset'), serverVersion: any(named: 'serverVersion'), - abortSignal: any(named: 'abortSignal'), ), ).thenAnswer((invocation) async { handleEventsCallback = invocation.positionalArguments.first; @@ -113,7 +118,7 @@ void main() { when(() => mockApi.serverInfoApi).thenReturn(mockServerApi); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 1, minor: 132, patch_: 0, prerelease: null)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 1, minor: 132, patch_: 0)); when(() => mockSyncStreamRepo.updateUsersV1(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer(successHandler); @@ -154,8 +159,8 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -165,12 +170,10 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => []); when(() => mockTrashedLocalAssetRepo.applyRestoredAssets(any())).thenAnswer((_) async {}); hasManageMediaPermission = false; - when(() => mockPermissionRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - return ids; - }); - when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); + when(() => mockLocalFilesManagerRepo.hasManageMediaPermission()).thenAnswer((_) async => hasManageMediaPermission); + when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((_) async => true); + when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((_) async => []); + when(() => mockStorageRepo.getAssetEntityForAsset(any())).thenAnswer((_) async => null); await Store.put(StoreKey.manageLocalMediaAndroid, false); }); @@ -230,16 +233,17 @@ void main() { }); test("aborts and stops processing if cancelled during iteration", () async { - final cancellation = Completer(); + final cancellationChecker = _MockCancellationWrapper(); + when(() => cancellationChecker()).thenReturn(false); sut = SyncStreamService( syncApiRepository: mockSyncApiRepo, syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, - cancellation: cancellation, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, + cancelChecker: cancellationChecker.call, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -248,7 +252,7 @@ void main() { final events = [SyncStreamStub.userDeleteV1, SyncStreamStub.userV1Admin, SyncStreamStub.partnerDeleteV1]; when(() => mockSyncStreamRepo.deleteUsersV1(any())).thenAnswer((_) async { - cancellation.complete(); + when(() => cancellationChecker()).thenReturn(true); }); await handleEventsCallback(events, mockAbortCallbackWrapper.call, mockResetCallbackWrapper.call); @@ -263,7 +267,8 @@ void main() { }); test("aborts and stops processing if cancelled before processing batch", () async { - final cancellation = Completer(); + final cancellationChecker = _MockCancellationWrapper(); + when(() => cancellationChecker()).thenReturn(false); final processingCompleter = Completer(); bool handler1Started = false; @@ -277,9 +282,9 @@ void main() { syncStreamRepository: mockSyncStreamRepo, localAssetRepository: mockLocalAssetRepo, trashedLocalAssetRepository: mockTrashedLocalAssetRepo, - assetMediaRepository: mockAssetMediaRepo, - permissionRepository: mockPermissionRepo, - cancellation: cancellation, + localFilesManager: mockLocalFilesManagerRepo, + storageRepository: mockStorageRepo, + cancelChecker: cancellationChecker.call, api: mockApi, syncMigrationRepository: mockSyncMigrationRepo, ); @@ -298,7 +303,7 @@ void main() { expect(handler1Started, isTrue); // Signal cancellation while handler 1 is waiting - cancellation.complete(); + when(() => cancellationChecker()).thenReturn(true); await pumpEventQueue(); processingCompleter.complete(); @@ -414,15 +419,23 @@ void main() { 'album-b': [mergedAsset], }; when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { - final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; - expect(requestedRemoteIds.toSet(), equals({'remote-1', 'remote-2', 'remote-3'})); + final Iterable requestedChecksums = invocation.positionalArguments.first as Iterable; + expect(requestedChecksums.toSet(), equals({'checksum-local', 'checksum-merged', 'checksum-remote-only'})); return assetsByAlbum; }); - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((invocation) async { - final ids = invocation.positionalArguments.first as List; - expect(ids, unorderedEquals(['local-only', 'merged-local'])); - return ids; + final localEntity = MockAssetEntity(); + when(() => localEntity.getMediaUrl()).thenAnswer((_) async => 'content://local-only'); + when(() => mockStorageRepo.getAssetEntityForAsset(localAsset)).thenAnswer((_) async => localEntity); + + final mergedEntity = MockAssetEntity(); + when(() => mergedEntity.getMediaUrl()).thenAnswer((_) async => 'content://merged-local'); + when(() => mockStorageRepo.getAssetEntityForAsset(mergedAsset)).thenAnswer((_) async => mergedEntity); + + when(() => mockLocalFilesManagerRepo.moveToTrash(any())).thenAnswer((invocation) async { + final urls = invocation.positionalArguments.first as List; + expect(urls, unorderedEquals(['content://local-only', 'content://merged-local'])); + return true; }); final events = [ @@ -448,51 +461,10 @@ void main() { await simulateEvents(events); - final trashArgs = - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, unorderedEquals(['album-a', 'album-b'])); - expect(trashArgs['album-a'], [localAsset]); - expect(trashArgs['album-b'], [mergedAsset]); - verify(() => mockAssetMediaRepo.deleteAll(any())).called(1); + verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(assetsByAlbum)).called(1); verify(() => mockSyncApiRepo.ack(['asset-remote-only-3'])).called(1); }); - test("records only assets that were moved to device trash", () async { - final movedAsset = LocalAssetStub.image1.copyWith(id: 'moved-local', checksum: 'checksum-moved'); - final skippedAsset = LocalAssetStub.image2.copyWith(id: 'skipped-local', checksum: 'checksum-skipped'); - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer( - (_) async => { - 'album-a': [movedAsset], - 'album-b': [skippedAsset], - }, - ); - when(() => mockAssetMediaRepo.deleteAll(any())).thenAnswer((_) async => ['moved-local']); - - final events = [ - SyncStreamStub.assetTrashed( - id: 'remote-moved', - checksum: movedAsset.checksum!, - ack: 'asset-remote-moved', - trashedAt: DateTime(2025, 5, 1), - ), - SyncStreamStub.assetTrashed( - id: 'remote-skipped', - checksum: skippedAsset.checksum!, - ack: 'asset-remote-skipped', - trashedAt: DateTime(2025, 5, 2), - ), - ]; - - await simulateEvents(events); - - final trashArgs = - verify(() => mockTrashedLocalAssetRepo.trashLocalAsset(captureAny())).captured.single - as Map>; - expect(trashArgs.keys, ['album-a']); - expect(trashArgs['album-a'], [movedAsset]); - }); - test("skips device trashing when no local assets match the remote trash payload", () async { final events = [ SyncStreamStub.assetTrashed( @@ -506,23 +478,17 @@ void main() { await simulateEvents(events); verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockAssetMediaRepo.deleteAll(any())); + verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); verifyNever(() => mockTrashedLocalAssetRepo.trashLocalAsset(any())); }); - test("requests local deletions lookup by remote ids for permanent remote delete events", () async { - when(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).thenAnswer((invocation) async { - final Iterable requestedRemoteIds = invocation.positionalArguments.first as Iterable; - expect(requestedRemoteIds.toSet(), equals({'remote-asset'})); - return {}; - }); - + test("does not request local deletions for permanent remote delete events", () async { final events = [SyncStreamStub.assetDeleteV1]; await simulateEvents(events); - verify(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())).called(1); - verifyNever(() => mockAssetMediaRepo.deleteAll(any())); + verifyNever(() => mockLocalAssetRepo.getAssetsFromBackupAlbums(any())); + verifyNever(() => mockLocalFilesManagerRepo.moveToTrash(any())); verify(() => mockSyncStreamRepo.deleteAssetsV1(any())).called(1); }); @@ -533,7 +499,7 @@ void main() { when(() => mockTrashedLocalAssetRepo.getToRestore()).thenAnswer((_) async => trashedAssets); final restoredIds = ['trashed-1']; - when(() => mockAssetMediaRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { + when(() => mockLocalFilesManagerRepo.restoreAssetsFromTrash(any())).thenAnswer((invocation) async { final Iterable requestedAssets = invocation.positionalArguments.first as Iterable; expect(requestedAssets, orderedEquals(trashedAssets)); return restoredIds; @@ -554,7 +520,7 @@ void main() { await Store.put(StoreKey.syncMigrationStatus, "[]"); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1)); await sut.sync(); @@ -582,7 +548,7 @@ void main() { await Store.put(StoreKey.syncMigrationStatus, "[]"); when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0, prerelease: null)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 5, patch_: 0)); await sut.sync(); verifyInOrder([ @@ -612,7 +578,7 @@ void main() { when( () => mockServerApi.getServerVersion(), - ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1, prerelease: null)); + ).thenAnswer((_) async => ServerVersionResponseDto(major: 2, minor: 4, patch_: 1)); await sut.sync(); diff --git a/mobile/test/domain/services/user_service_test.dart b/mobile/test/domain/services/user_service_test.dart index 7b46a44339..395f38a207 100644 --- a/mobile/test/domain/services/user_service_test.dart +++ b/mobile/test/domain/services/user_service_test.dart @@ -4,22 +4,29 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/user_api.repository.dart'; import 'package:mocktail/mocktail.dart'; import '../../fixtures/user.stub.dart'; import '../../infrastructure/repository.mock.dart'; -import '../../service.mocks.dart'; +import '../service.mock.dart'; void main() { late UserService sut; + late IsarUserRepository mockUserRepo; late UserApiRepository mockUserApiRepo; late StoreService mockStoreService; setUp(() { + mockUserRepo = MockIsarUserRepository(); mockUserApiRepo = MockUserApiRepository(); mockStoreService = MockStoreService(); - sut = UserService(userApiRepository: mockUserApiRepo, storeService: mockStoreService); + sut = UserService( + isarUserRepository: mockUserRepo, + userApiRepository: mockUserApiRepo, + storeService: mockStoreService, + ); registerFallbackValue(UserStub.admin); when(() => mockStoreService.get(StoreKey.currentUser)).thenReturn(UserStub.admin); @@ -70,9 +77,11 @@ void main() { test('should return user from api and store it', () async { when(() => mockUserApiRepo.getMyUser()).thenAnswer((_) async => UserStub.admin); when(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).thenAnswer((_) async => true); + when(() => mockUserRepo.update(UserStub.admin)).thenAnswer((_) async => UserStub.admin); final result = await sut.refreshMyUser(); verify(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)).called(1); + verify(() => mockUserRepo.update(UserStub.admin)).called(1); expect(result, UserStub.admin); }); @@ -81,6 +90,7 @@ void main() { final result = await sut.refreshMyUser(); verifyNever(() => mockStoreService.put(StoreKey.currentUser, UserStub.admin)); + verifyNever(() => mockUserRepo.update(UserStub.admin)); expect(result, isNull); }); }); @@ -94,10 +104,12 @@ void main() { () => mockUserApiRepo.createProfileImage(name: profileImagePath, data: Uint8List(0)), ).thenAnswer((_) async => profileImagePath); when(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).thenAnswer((_) async => true); + when(() => mockUserRepo.update(updatedUser)).thenAnswer((_) async => UserStub.admin); final result = await sut.createProfileImage(profileImagePath, Uint8List(0)); verify(() => mockStoreService.put(StoreKey.currentUser, updatedUser)).called(1); + verify(() => mockUserRepo.update(updatedUser)).called(1); expect(result, profileImagePath); }); @@ -111,6 +123,7 @@ void main() { final result = await sut.createProfileImage(profileImagePath, Uint8List(0)); verifyNever(() => mockStoreService.put(StoreKey.currentUser, updatedUser)); + verifyNever(() => mockUserRepo.update(updatedUser)); expect(result, isNull); }); }); diff --git a/mobile/test/drift/main/generated/schema.dart b/mobile/test/drift/main/generated/schema.dart new file mode 100644 index 0000000000..37f5ef1021 --- /dev/null +++ b/mobile/test/drift/main/generated/schema.dart @@ -0,0 +1,106 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; +import 'package:drift/internal/migrations.dart'; +import 'schema_v1.dart' as v1; +import 'schema_v2.dart' as v2; +import 'schema_v3.dart' as v3; +import 'schema_v4.dart' as v4; +import 'schema_v5.dart' as v5; +import 'schema_v6.dart' as v6; +import 'schema_v7.dart' as v7; +import 'schema_v8.dart' as v8; +import 'schema_v9.dart' as v9; +import 'schema_v10.dart' as v10; +import 'schema_v11.dart' as v11; +import 'schema_v12.dart' as v12; +import 'schema_v13.dart' as v13; +import 'schema_v14.dart' as v14; +import 'schema_v15.dart' as v15; +import 'schema_v16.dart' as v16; +import 'schema_v17.dart' as v17; +import 'schema_v18.dart' as v18; +import 'schema_v19.dart' as v19; +import 'schema_v20.dart' as v20; +import 'schema_v21.dart' as v21; +import 'schema_v22.dart' as v22; + +class GeneratedHelper implements SchemaInstantiationHelper { + @override + GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { + switch (version) { + case 1: + return v1.DatabaseAtV1(db); + case 2: + return v2.DatabaseAtV2(db); + case 3: + return v3.DatabaseAtV3(db); + case 4: + return v4.DatabaseAtV4(db); + case 5: + return v5.DatabaseAtV5(db); + case 6: + return v6.DatabaseAtV6(db); + case 7: + return v7.DatabaseAtV7(db); + case 8: + return v8.DatabaseAtV8(db); + case 9: + return v9.DatabaseAtV9(db); + case 10: + return v10.DatabaseAtV10(db); + case 11: + return v11.DatabaseAtV11(db); + case 12: + return v12.DatabaseAtV12(db); + case 13: + return v13.DatabaseAtV13(db); + case 14: + return v14.DatabaseAtV14(db); + case 15: + return v15.DatabaseAtV15(db); + case 16: + return v16.DatabaseAtV16(db); + case 17: + return v17.DatabaseAtV17(db); + case 18: + return v18.DatabaseAtV18(db); + case 19: + return v19.DatabaseAtV19(db); + case 20: + return v20.DatabaseAtV20(db); + case 21: + return v21.DatabaseAtV21(db); + case 22: + return v22.DatabaseAtV22(db); + default: + throw MissingSchemaException(version, versions); + } + } + + static const versions = const [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + ]; +} diff --git a/mobile/test/drift/main/generated/schema_v1.dart b/mobile/test/drift/main/generated/schema_v1.dart new file mode 100644 index 0000000000..ca9e6ca1b0 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v1.dart @@ -0,0 +1,5995 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn profileImagePath = GeneratedColumn( + 'profile_image_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + profileImagePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_image_path'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + ), + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final String? profileImagePath; + final DateTime updatedAt; + final int? quotaSizeInBytes; + final int quotaUsageInBytes; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + this.profileImagePath, + required this.updatedAt, + this.quotaSizeInBytes, + required this.quotaUsageInBytes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + if (!nullToAbsent || profileImagePath != null) { + map['profile_image_path'] = Variable(profileImagePath); + } + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || quotaSizeInBytes != null) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + } + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + profileImagePath: serializer.fromJson(json['profileImagePath']), + updatedAt: serializer.fromJson(json['updatedAt']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'profileImagePath': serializer.toJson(profileImagePath), + 'updatedAt': serializer.toJson(updatedAt), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + Value profileImagePath = const Value.absent(), + DateTime? updatedAt, + Value quotaSizeInBytes = const Value.absent(), + int? quotaUsageInBytes, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath.present + ? profileImagePath.value + : this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes.present + ? quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + profileImagePath: data.profileImagePath.present + ? data.profileImagePath.value + : this.profileImagePath, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.profileImagePath == this.profileImagePath && + other.updatedAt == this.updatedAt && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value profileImagePath; + final Value updatedAt; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? profileImagePath, + Expression? updatedAt, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (profileImagePath != null) 'profile_image_path': profileImagePath, + if (updatedAt != null) 'updated_at': updatedAt, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? profileImagePath, + Value? updatedAt, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath ?? this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (profileImagePath.present) { + map['profile_image_path'] = Variable(profileImagePath.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id)', + ), + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbnailPath = GeneratedColumn( + 'thumbnail_path', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + thumbnailPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_path'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final String thumbnailPath; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.thumbnailPath, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['thumbnail_path'] = Variable(thumbnailPath); + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + thumbnailPath: serializer.fromJson(json['thumbnailPath']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'thumbnailPath': serializer.toJson(thumbnailPath), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + String? thumbnailPath, + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + thumbnailPath: data.thumbnailPath.present + ? data.thumbnailPath.value + : this.thumbnailPath, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.thumbnailPath == this.thumbnailPath && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value thumbnailPath; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.thumbnailPath = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required String thumbnailPath, + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + thumbnailPath = Value(thumbnailPath), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? thumbnailPath, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (thumbnailPath != null) 'thumbnail_path': thumbnailPath, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? thumbnailPath, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (thumbnailPath.present) { + map['thumbnail_path'] = Variable(thumbnailPath.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV1 extends GeneratedDatabase { + DatabaseAtV1(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index uQRemoteAssetOwnerChecksum = Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + localAssetEntity, + stackEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + localAlbumEntity, + localAlbumAssetEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + ]; + @override + int get schemaVersion => 1; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v10.dart b/mobile/test/drift/main/generated/schema_v10.dart new file mode 100644 index 0000000000..ba75530242 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v10.dart @@ -0,0 +1,7159 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV10 extends GeneratedDatabase { + DatabaseAtV10(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 10; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v11.dart b/mobile/test/drift/main/generated/schema_v11.dart new file mode 100644 index 0000000000..fe27f1bb3d --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v11.dart @@ -0,0 +1,7198 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV11 extends GeneratedDatabase { + DatabaseAtV11(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 11; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v12.dart b/mobile/test/drift/main/generated/schema_v12.dart new file mode 100644 index 0000000000..c42df284ec --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v12.dart @@ -0,0 +1,7198 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV12 extends GeneratedDatabase { + DatabaseAtV12(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 12; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v13.dart b/mobile/test/drift/main/generated/schema_v13.dart new file mode 100644 index 0000000000..da0d853678 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v13.dart @@ -0,0 +1,7765 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV13 extends GeneratedDatabase { + DatabaseAtV13(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 13; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v14.dart b/mobile/test/drift/main/generated/schema_v14.dart new file mode 100644 index 0000000000..242aa581f3 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v14.dart @@ -0,0 +1,7878 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV14 extends GeneratedDatabase { + DatabaseAtV14(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 14; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v15.dart b/mobile/test/drift/main/generated/schema_v15.dart new file mode 100644 index 0000000000..fa419d7395 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v15.dart @@ -0,0 +1,7913 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV15 extends GeneratedDatabase { + DatabaseAtV15(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 15; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v16.dart b/mobile/test/drift/main/generated/schema_v16.dart new file mode 100644 index 0000000000..0690288d7f --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v16.dart @@ -0,0 +1,8299 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV16 extends GeneratedDatabase { + DatabaseAtV16(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 16; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v17.dart b/mobile/test/drift/main/generated/schema_v17.dart new file mode 100644 index 0000000000..042c069ecd --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v17.dart @@ -0,0 +1,8337 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV17 extends GeneratedDatabase { + DatabaseAtV17(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 17; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v18.dart b/mobile/test/drift/main/generated/schema_v18.dart new file mode 100644 index 0000000000..c0b1e68894 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v18.dart @@ -0,0 +1,8342 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV18 extends GeneratedDatabase { + DatabaseAtV18(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxLatLng, + idxRemoteAssetCloudId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 18; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v19.dart b/mobile/test/drift/main/generated/schema_v19.dart new file mode 100644 index 0000000000..4a8dea806e --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v19.dart @@ -0,0 +1,8397 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV19 extends GeneratedDatabase { + DatabaseAtV19(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAlbumOwnerId = Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetLocalDateTimeDay = Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + late final Index idxRemoteAssetLocalDateTimeMonth = Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 19; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v2.dart b/mobile/test/drift/main/generated/schema_v2.dart new file mode 100644 index 0000000000..903d13b9a2 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v2.dart @@ -0,0 +1,5995 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn profileImagePath = GeneratedColumn( + 'profile_image_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + profileImagePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_image_path'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + ), + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final String? profileImagePath; + final DateTime updatedAt; + final int? quotaSizeInBytes; + final int quotaUsageInBytes; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + this.profileImagePath, + required this.updatedAt, + this.quotaSizeInBytes, + required this.quotaUsageInBytes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + if (!nullToAbsent || profileImagePath != null) { + map['profile_image_path'] = Variable(profileImagePath); + } + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || quotaSizeInBytes != null) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + } + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + profileImagePath: serializer.fromJson(json['profileImagePath']), + updatedAt: serializer.fromJson(json['updatedAt']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'profileImagePath': serializer.toJson(profileImagePath), + 'updatedAt': serializer.toJson(updatedAt), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + Value profileImagePath = const Value.absent(), + DateTime? updatedAt, + Value quotaSizeInBytes = const Value.absent(), + int? quotaUsageInBytes, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath.present + ? profileImagePath.value + : this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes.present + ? quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + profileImagePath: data.profileImagePath.present + ? data.profileImagePath.value + : this.profileImagePath, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.profileImagePath == this.profileImagePath && + other.updatedAt == this.updatedAt && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value profileImagePath; + final Value updatedAt; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? profileImagePath, + Expression? updatedAt, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (profileImagePath != null) 'profile_image_path': profileImagePath, + if (updatedAt != null) 'updated_at': updatedAt, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? profileImagePath, + Value? updatedAt, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath ?? this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (profileImagePath.present) { + map['profile_image_path'] = Variable(profileImagePath.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id)', + ), + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbnailPath = GeneratedColumn( + 'thumbnail_path', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + thumbnailPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_path'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final String thumbnailPath; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.thumbnailPath, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['thumbnail_path'] = Variable(thumbnailPath); + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + thumbnailPath: serializer.fromJson(json['thumbnailPath']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'thumbnailPath': serializer.toJson(thumbnailPath), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + String? thumbnailPath, + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + thumbnailPath: data.thumbnailPath.present + ? data.thumbnailPath.value + : this.thumbnailPath, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.thumbnailPath == this.thumbnailPath && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value thumbnailPath; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.thumbnailPath = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required String thumbnailPath, + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + thumbnailPath = Value(thumbnailPath), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? thumbnailPath, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (thumbnailPath != null) 'thumbnail_path': thumbnailPath, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? thumbnailPath, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (thumbnailPath.present) { + map['thumbnail_path'] = Variable(thumbnailPath.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV2 extends GeneratedDatabase { + DatabaseAtV2(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index uQRemoteAssetOwnerChecksum = Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + localAssetEntity, + stackEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + localAlbumEntity, + localAlbumAssetEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + ]; + @override + int get schemaVersion => 2; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v20.dart b/mobile/test/drift/main/generated/schema_v20.dart new file mode 100644 index 0000000000..8f7b204f7a --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v20.dart @@ -0,0 +1,8471 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_visible" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final bool isVisible; + final DateTime? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + bool? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV20 extends GeneratedDatabase { + DatabaseAtV20(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAlbumOwnerId = Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetLocalDateTimeDay = Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + late final Index idxRemoteAssetLocalDateTimeMonth = Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 20; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v21.dart b/mobile/test/drift/main/generated/schema_v21.dart new file mode 100644 index 0000000000..846eb4aabc --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v21.dart @@ -0,0 +1,8545 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_visible" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final bool isVisible; + final DateTime? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + bool? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV21 extends GeneratedDatabase { + DatabaseAtV21(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAlbumOwnerId = Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetLocalDateTimeDay = Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + late final Index idxRemoteAssetLocalDateTimeMonth = Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + ]; + @override + int get schemaVersion => 21; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v22.dart b/mobile/test/drift/main/generated/schema_v22.dart new file mode 100644 index 0000000000..b8f5971686 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v22.dart @@ -0,0 +1,8845 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + const UserEntityData({ + required this.id, + required this.name, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + hasProfileImage, + profileChangedAt, + avatarColor, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isEdited = GeneratedColumn( + 'is_edited', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_edited" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + isEdited: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_edited'], + )!, + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + final bool isEdited; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + required this.isEdited, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + map['is_edited'] = Variable(isEdited); + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + isEdited: serializer.fromJson(json['isEdited']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + 'isEdited': serializer.toJson(isEdited), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + bool? isEdited, + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + isEdited, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId && + other.isEdited == this.isEdited); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + final Value isEdited; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + this.isEdited = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + Expression? isEdited, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + if (isEdited != null) 'is_edited': isEdited, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + Value? isEdited, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + isEdited: isEdited ?? this.isEdited, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + if (isEdited.present) { + map['is_edited'] = Variable(isEdited.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId, ') + ..write('isEdited: $isEdited') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn iCloudId = GeneratedColumn( + 'i_cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + iCloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}i_cloud_id'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + final String? iCloudId; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + final int playbackStyle; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + this.iCloudId, + this.adjustmentTime, + this.latitude, + this.longitude, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + if (!nullToAbsent || iCloudId != null) { + map['i_cloud_id'] = Variable(iCloudId); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + iCloudId: serializer.fromJson(json['iCloudId']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'iCloudId': serializer.toJson(iCloudId), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + Value iCloudId = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + int? playbackStyle, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + iCloudId, + adjustmentTime, + latitude, + longitude, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.iCloudId == this.iCloudId && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.playbackStyle == this.playbackStyle); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value iCloudId; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + final Value playbackStyle; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.iCloudId = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? iCloudId, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (iCloudId != null) 'i_cloud_id': iCloudId, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? iCloudId, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + Value? playbackStyle, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + iCloudId: iCloudId ?? this.iCloudId, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (iCloudId.present) { + map['i_cloud_id'] = Variable(iCloudId.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('iCloudId: $iCloudId, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [assetId, albumId, marker_]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + final bool? marker_; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumAssetEntityData copyWith({ + String? assetId, + String? albumId, + Value marker_ = const Value.absent(), + }) => LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId, marker_); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId && + other.marker_ == this.marker_); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + final Value marker_; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + this.marker_ = const Value.absent(), + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + Value? marker_, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class AuthUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn avatarColor = GeneratedColumn( + 'avatar_color', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn pinCode = GeneratedColumn( + 'pin_code', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'auth_user_entity'; + @override + Set get $primaryKey => {id}; + @override + AuthUserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthUserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + avatarColor: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}avatar_color'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + )!, + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + pinCode: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pin_code'], + ), + ); + } + + @override + AuthUserEntity createAlias(String alias) { + return AuthUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AuthUserEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String email; + final bool isAdmin; + final bool hasProfileImage; + final DateTime profileChangedAt; + final int avatarColor; + final int quotaSizeInBytes; + final int quotaUsageInBytes; + final String? pinCode; + const AuthUserEntityData({ + required this.id, + required this.name, + required this.email, + required this.isAdmin, + required this.hasProfileImage, + required this.profileChangedAt, + required this.avatarColor, + required this.quotaSizeInBytes, + required this.quotaUsageInBytes, + this.pinCode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['email'] = Variable(email); + map['is_admin'] = Variable(isAdmin); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['avatar_color'] = Variable(avatarColor); + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + if (!nullToAbsent || pinCode != null) { + map['pin_code'] = Variable(pinCode); + } + return map; + } + + factory AuthUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthUserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + isAdmin: serializer.fromJson(json['isAdmin']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + avatarColor: serializer.fromJson(json['avatarColor']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + pinCode: serializer.fromJson(json['pinCode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'isAdmin': serializer.toJson(isAdmin), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'avatarColor': serializer.toJson(avatarColor), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + 'pinCode': serializer.toJson(pinCode), + }; + } + + AuthUserEntityData copyWith({ + String? id, + String? name, + String? email, + bool? isAdmin, + bool? hasProfileImage, + DateTime? profileChangedAt, + int? avatarColor, + int? quotaSizeInBytes, + int? quotaUsageInBytes, + Value pinCode = const Value.absent(), + }) => AuthUserEntityData( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode.present ? pinCode.value : this.pinCode, + ); + AuthUserEntityData copyWithCompanion(AuthUserEntityCompanion data) { + return AuthUserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + email: data.email.present ? data.email.value : this.email, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + avatarColor: data.avatarColor.present + ? data.avatarColor.value + : this.avatarColor, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + pinCode: data.pinCode.present ? data.pinCode.value : this.pinCode, + ); + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + email, + isAdmin, + hasProfileImage, + profileChangedAt, + avatarColor, + quotaSizeInBytes, + quotaUsageInBytes, + pinCode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthUserEntityData && + other.id == this.id && + other.name == this.name && + other.email == this.email && + other.isAdmin == this.isAdmin && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.avatarColor == this.avatarColor && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes && + other.pinCode == this.pinCode); +} + +class AuthUserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value email; + final Value isAdmin; + final Value hasProfileImage; + final Value profileChangedAt; + final Value avatarColor; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + final Value pinCode; + const AuthUserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.avatarColor = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }); + AuthUserEntityCompanion.insert({ + required String id, + required String name, + required String email, + this.isAdmin = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + required int avatarColor, + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + this.pinCode = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email), + avatarColor = Value(avatarColor); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? email, + Expression? isAdmin, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? avatarColor, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + Expression? pinCode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (isAdmin != null) 'is_admin': isAdmin, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (avatarColor != null) 'avatar_color': avatarColor, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + if (pinCode != null) 'pin_code': pinCode, + }); + } + + AuthUserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? email, + Value? isAdmin, + Value? hasProfileImage, + Value? profileChangedAt, + Value? avatarColor, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + Value? pinCode, + }) { + return AuthUserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + email: email ?? this.email, + isAdmin: isAdmin ?? this.isAdmin, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + avatarColor: avatarColor ?? this.avatarColor, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + pinCode: pinCode ?? this.pinCode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (avatarColor.present) { + map['avatar_color'] = Variable(avatarColor.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + if (pinCode.present) { + map['pin_code'] = Variable(pinCode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthUserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('isAdmin: $isAdmin, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('avatarColor: $avatarColor, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes, ') + ..write('pinCode: $pinCode') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class RemoteAssetCloudIdEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetCloudIdEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn cloudId = GeneratedColumn( + 'cloud_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn adjustmentTime = + GeneratedColumn( + 'adjustment_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_cloud_id_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteAssetCloudIdEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetCloudIdEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + cloudId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}cloud_id'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + ), + adjustmentTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}adjustment_time'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + ); + } + + @override + RemoteAssetCloudIdEntity createAlias(String alias) { + return RemoteAssetCloudIdEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetCloudIdEntityData extends DataClass + implements Insertable { + final String assetId; + final String? cloudId; + final DateTime? createdAt; + final DateTime? adjustmentTime; + final double? latitude; + final double? longitude; + const RemoteAssetCloudIdEntityData({ + required this.assetId, + this.cloudId, + this.createdAt, + this.adjustmentTime, + this.latitude, + this.longitude, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || cloudId != null) { + map['cloud_id'] = Variable(cloudId); + } + if (!nullToAbsent || createdAt != null) { + map['created_at'] = Variable(createdAt); + } + if (!nullToAbsent || adjustmentTime != null) { + map['adjustment_time'] = Variable(adjustmentTime); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + return map; + } + + factory RemoteAssetCloudIdEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetCloudIdEntityData( + assetId: serializer.fromJson(json['assetId']), + cloudId: serializer.fromJson(json['cloudId']), + createdAt: serializer.fromJson(json['createdAt']), + adjustmentTime: serializer.fromJson(json['adjustmentTime']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'cloudId': serializer.toJson(cloudId), + 'createdAt': serializer.toJson(createdAt), + 'adjustmentTime': serializer.toJson(adjustmentTime), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + }; + } + + RemoteAssetCloudIdEntityData copyWith({ + String? assetId, + Value cloudId = const Value.absent(), + Value createdAt = const Value.absent(), + Value adjustmentTime = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + }) => RemoteAssetCloudIdEntityData( + assetId: assetId ?? this.assetId, + cloudId: cloudId.present ? cloudId.value : this.cloudId, + createdAt: createdAt.present ? createdAt.value : this.createdAt, + adjustmentTime: adjustmentTime.present + ? adjustmentTime.value + : this.adjustmentTime, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + ); + RemoteAssetCloudIdEntityData copyWithCompanion( + RemoteAssetCloudIdEntityCompanion data, + ) { + return RemoteAssetCloudIdEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + adjustmentTime: data.adjustmentTime.present + ? data.adjustmentTime.value + : this.adjustmentTime, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityData(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + assetId, + cloudId, + createdAt, + adjustmentTime, + latitude, + longitude, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetCloudIdEntityData && + other.assetId == this.assetId && + other.cloudId == this.cloudId && + other.createdAt == this.createdAt && + other.adjustmentTime == this.adjustmentTime && + other.latitude == this.latitude && + other.longitude == this.longitude); +} + +class RemoteAssetCloudIdEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value cloudId; + final Value createdAt; + final Value adjustmentTime; + final Value latitude; + final Value longitude; + const RemoteAssetCloudIdEntityCompanion({ + this.assetId = const Value.absent(), + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }); + RemoteAssetCloudIdEntityCompanion.insert({ + required String assetId, + this.cloudId = const Value.absent(), + this.createdAt = const Value.absent(), + this.adjustmentTime = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? cloudId, + Expression? createdAt, + Expression? adjustmentTime, + Expression? latitude, + Expression? longitude, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (cloudId != null) 'cloud_id': cloudId, + if (createdAt != null) 'created_at': createdAt, + if (adjustmentTime != null) 'adjustment_time': adjustmentTime, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + }); + } + + RemoteAssetCloudIdEntityCompanion copyWith({ + Value? assetId, + Value? cloudId, + Value? createdAt, + Value? adjustmentTime, + Value? latitude, + Value? longitude, + }) { + return RemoteAssetCloudIdEntityCompanion( + assetId: assetId ?? this.assetId, + cloudId: cloudId ?? this.cloudId, + createdAt: createdAt ?? this.createdAt, + adjustmentTime: adjustmentTime ?? this.adjustmentTime, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (cloudId.present) { + map['cloud_id'] = Variable(cloudId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (adjustmentTime.present) { + map['adjustment_time'] = Variable(adjustmentTime.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetCloudIdEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('cloudId: $cloudId, ') + ..write('createdAt: $createdAt, ') + ..write('adjustmentTime: $adjustmentTime, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isVisible = GeneratedColumn( + 'is_visible', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_visible" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + isVisible: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_visible'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + final bool isVisible; + final DateTime? deletedAt; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + required this.isVisible, + this.deletedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + map['is_visible'] = Variable(isVisible); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + isVisible: serializer.fromJson(json['isVisible']), + deletedAt: serializer.fromJson(json['deletedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + 'isVisible': serializer.toJson(isVisible), + 'deletedAt': serializer.toJson(deletedAt), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + bool? isVisible, + Value deletedAt = const Value.absent(), + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + isVisible: data.isVisible.present ? data.isVisible.value : this.isVisible, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + isVisible, + deletedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType && + other.isVisible == this.isVisible && + other.deletedAt == this.deletedAt); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + final Value isVisible; + final Value deletedAt; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + this.isVisible = const Value.absent(), + this.deletedAt = const Value.absent(), + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + Expression? isVisible, + Expression? deletedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + if (isVisible != null) 'is_visible': isVisible, + if (deletedAt != null) 'deleted_at': deletedAt, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + Value? isVisible, + Value? deletedAt, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + isVisible: isVisible ?? this.isVisible, + deletedAt: deletedAt ?? this.deletedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (isVisible.present) { + map['is_visible'] = Variable(isVisible.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType, ') + ..write('isVisible: $isVisible, ') + ..write('deletedAt: $deletedAt') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class TrashedLocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + TrashedLocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn source = GeneratedColumn( + 'source', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn playbackStyle = GeneratedColumn( + 'playback_style', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'trashed_local_asset_entity'; + @override + Set get $primaryKey => {id, albumId}; + @override + TrashedLocalAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TrashedLocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + source: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}source'], + )!, + playbackStyle: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}playback_style'], + )!, + ); + } + + @override + TrashedLocalAssetEntity createAlias(String alias) { + return TrashedLocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class TrashedLocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String albumId; + final String? checksum; + final bool isFavorite; + final int orientation; + final int source; + final int playbackStyle; + const TrashedLocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.albumId, + this.checksum, + required this.isFavorite, + required this.orientation, + required this.source, + required this.playbackStyle, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['album_id'] = Variable(albumId); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + map['source'] = Variable(source); + map['playback_style'] = Variable(playbackStyle); + return map; + } + + factory TrashedLocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TrashedLocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + albumId: serializer.fromJson(json['albumId']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + source: serializer.fromJson(json['source']), + playbackStyle: serializer.fromJson(json['playbackStyle']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'albumId': serializer.toJson(albumId), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + 'source': serializer.toJson(source), + 'playbackStyle': serializer.toJson(playbackStyle), + }; + } + + TrashedLocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? albumId, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + int? source, + int? playbackStyle, + }) => TrashedLocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + TrashedLocalAssetEntityData copyWithCompanion( + TrashedLocalAssetEntityCompanion data, + ) { + return TrashedLocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + source: data.source.present ? data.source.value : this.source, + playbackStyle: data.playbackStyle.present + ? data.playbackStyle.value + : this.playbackStyle, + ); + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + albumId, + checksum, + isFavorite, + orientation, + source, + playbackStyle, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TrashedLocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.albumId == this.albumId && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation && + other.source == this.source && + other.playbackStyle == this.playbackStyle); +} + +class TrashedLocalAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value albumId; + final Value checksum; + final Value isFavorite; + final Value orientation; + final Value source; + final Value playbackStyle; + const TrashedLocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.albumId = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + this.source = const Value.absent(), + this.playbackStyle = const Value.absent(), + }); + TrashedLocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String albumId, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + required int source, + this.playbackStyle = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + albumId = Value(albumId), + source = Value(source); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? albumId, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + Expression? source, + Expression? playbackStyle, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (albumId != null) 'album_id': albumId, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + if (source != null) 'source': source, + if (playbackStyle != null) 'playback_style': playbackStyle, + }); + } + + TrashedLocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? albumId, + Value? checksum, + Value? isFavorite, + Value? orientation, + Value? source, + Value? playbackStyle, + }) { + return TrashedLocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + albumId: albumId ?? this.albumId, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + source: source ?? this.source, + playbackStyle: playbackStyle ?? this.playbackStyle, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (source.present) { + map['source'] = Variable(source.value); + } + if (playbackStyle.present) { + map['playback_style'] = Variable(playbackStyle.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TrashedLocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('albumId: $albumId, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation, ') + ..write('source: $source, ') + ..write('playbackStyle: $playbackStyle') + ..write(')')) + .toString(); + } +} + +class AssetEditEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetEditEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn action = GeneratedColumn( + 'action', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn parameters = GeneratedColumn( + 'parameters', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + late final GeneratedColumn sequence = GeneratedColumn( + 'sequence', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + action, + parameters, + sequence, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_edit_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetEditEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetEditEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + action: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}action'], + )!, + parameters: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}parameters'], + )!, + sequence: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}sequence'], + )!, + ); + } + + @override + AssetEditEntity createAlias(String alias) { + return AssetEditEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetEditEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final int action; + final Uint8List parameters; + final int sequence; + const AssetEditEntityData({ + required this.id, + required this.assetId, + required this.action, + required this.parameters, + required this.sequence, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + map['action'] = Variable(action); + map['parameters'] = Variable(parameters); + map['sequence'] = Variable(sequence); + return map; + } + + factory AssetEditEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetEditEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + action: serializer.fromJson(json['action']), + parameters: serializer.fromJson(json['parameters']), + sequence: serializer.fromJson(json['sequence']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'action': serializer.toJson(action), + 'parameters': serializer.toJson(parameters), + 'sequence': serializer.toJson(sequence), + }; + } + + AssetEditEntityData copyWith({ + String? id, + String? assetId, + int? action, + Uint8List? parameters, + int? sequence, + }) => AssetEditEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + AssetEditEntityData copyWithCompanion(AssetEditEntityCompanion data) { + return AssetEditEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + action: data.action.present ? data.action.value : this.action, + parameters: data.parameters.present + ? data.parameters.value + : this.parameters, + sequence: data.sequence.present ? data.sequence.value : this.sequence, + ); + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + action, + $driftBlobEquality.hash(parameters), + sequence, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetEditEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.action == this.action && + $driftBlobEquality.equals(other.parameters, this.parameters) && + other.sequence == this.sequence); +} + +class AssetEditEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value action; + final Value parameters; + final Value sequence; + const AssetEditEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.action = const Value.absent(), + this.parameters = const Value.absent(), + this.sequence = const Value.absent(), + }); + AssetEditEntityCompanion.insert({ + required String id, + required String assetId, + required int action, + required Uint8List parameters, + required int sequence, + }) : id = Value(id), + assetId = Value(assetId), + action = Value(action), + parameters = Value(parameters), + sequence = Value(sequence); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? action, + Expression? parameters, + Expression? sequence, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (action != null) 'action': action, + if (parameters != null) 'parameters': parameters, + if (sequence != null) 'sequence': sequence, + }); + } + + AssetEditEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? action, + Value? parameters, + Value? sequence, + }) { + return AssetEditEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + action: action ?? this.action, + parameters: parameters ?? this.parameters, + sequence: sequence ?? this.sequence, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (action.present) { + map['action'] = Variable(action.value); + } + if (parameters.present) { + map['parameters'] = Variable(parameters.value); + } + if (sequence.present) { + map['sequence'] = Variable(sequence.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetEditEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('action: $action, ') + ..write('parameters: $parameters, ') + ..write('sequence: $sequence') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV22 extends GeneratedDatabase { + DatabaseAtV22(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAlbumAssetAlbumAsset = Index( + 'idx_local_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAlbumOwnerId = Index( + 'idx_remote_album_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_owner_id ON remote_album_entity (owner_id)', + ); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxLocalAssetCloudId = Index( + 'idx_local_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)', + ); + late final Index idxStackPrimaryAssetId = Index( + 'idx_stack_primary_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final Index idxRemoteAssetStackId = Index( + 'idx_remote_asset_stack_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)', + ); + late final Index idxRemoteAssetLocalDateTimeDay = Index( + 'idx_remote_asset_local_date_time_day', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))', + ); + late final Index idxRemoteAssetLocalDateTimeMonth = Index( + 'idx_remote_asset_local_date_time_month', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))', + ); + late final AuthUserEntity authUserEntity = AuthUserEntity(this); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final RemoteAssetCloudIdEntity remoteAssetCloudIdEntity = + RemoteAssetCloudIdEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final TrashedLocalAssetEntity trashedLocalAssetEntity = + TrashedLocalAssetEntity(this); + late final AssetEditEntity assetEditEntity = AssetEditEntity(this); + late final Index idxPartnerSharedWithId = Index( + 'idx_partner_shared_with_id', + 'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)', + ); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + late final Index idxRemoteAlbumAssetAlbumAsset = Index( + 'idx_remote_album_asset_album_asset', + 'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)', + ); + late final Index idxRemoteAssetCloudId = Index( + 'idx_remote_asset_cloud_id', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)', + ); + late final Index idxPersonOwnerId = Index( + 'idx_person_owner_id', + 'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)', + ); + late final Index idxAssetFacePersonId = Index( + 'idx_asset_face_person_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)', + ); + late final Index idxAssetFaceAssetId = Index( + 'idx_asset_face_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)', + ); + late final Index idxTrashedLocalAssetChecksum = Index( + 'idx_trashed_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)', + ); + late final Index idxTrashedLocalAssetAlbum = Index( + 'idx_trashed_local_asset_album', + 'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)', + ); + late final Index idxAssetEditAssetId = Index( + 'idx_asset_edit_asset_id', + 'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAlbumAssetAlbumAsset, + idxRemoteAlbumOwnerId, + idxLocalAssetChecksum, + idxLocalAssetCloudId, + idxStackPrimaryAssetId, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + idxRemoteAssetStackId, + idxRemoteAssetLocalDateTimeDay, + idxRemoteAssetLocalDateTimeMonth, + authUserEntity, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + remoteAssetCloudIdEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + trashedLocalAssetEntity, + assetEditEntity, + idxPartnerSharedWithId, + idxLatLng, + idxRemoteAlbumAssetAlbumAsset, + idxRemoteAssetCloudId, + idxPersonOwnerId, + idxAssetFacePersonId, + idxAssetFaceAssetId, + idxTrashedLocalAssetChecksum, + idxTrashedLocalAssetAlbum, + idxAssetEditAssetId, + ]; + @override + int get schemaVersion => 22; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v3.dart b/mobile/test/drift/main/generated/schema_v3.dart new file mode 100644 index 0000000000..e4382a9fb9 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v3.dart @@ -0,0 +1,5992 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn profileImagePath = GeneratedColumn( + 'profile_image_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + profileImagePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_image_path'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + ), + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final String? profileImagePath; + final DateTime updatedAt; + final int? quotaSizeInBytes; + final int quotaUsageInBytes; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + this.profileImagePath, + required this.updatedAt, + this.quotaSizeInBytes, + required this.quotaUsageInBytes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + if (!nullToAbsent || profileImagePath != null) { + map['profile_image_path'] = Variable(profileImagePath); + } + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || quotaSizeInBytes != null) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + } + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + profileImagePath: serializer.fromJson(json['profileImagePath']), + updatedAt: serializer.fromJson(json['updatedAt']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'profileImagePath': serializer.toJson(profileImagePath), + 'updatedAt': serializer.toJson(updatedAt), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + Value profileImagePath = const Value.absent(), + DateTime? updatedAt, + Value quotaSizeInBytes = const Value.absent(), + int? quotaUsageInBytes, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath.present + ? profileImagePath.value + : this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes.present + ? quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + profileImagePath: data.profileImagePath.present + ? data.profileImagePath.value + : this.profileImagePath, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.profileImagePath == this.profileImagePath && + other.updatedAt == this.updatedAt && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value profileImagePath; + final Value updatedAt; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? profileImagePath, + Expression? updatedAt, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (profileImagePath != null) 'profile_image_path': profileImagePath, + if (updatedAt != null) 'updated_at': updatedAt, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? profileImagePath, + Value? updatedAt, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath ?? this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (profileImagePath.present) { + map['profile_image_path'] = Variable(profileImagePath.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbnailPath = GeneratedColumn( + 'thumbnail_path', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + thumbnailPath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_path'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final String thumbnailPath; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.thumbnailPath, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['thumbnail_path'] = Variable(thumbnailPath); + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + thumbnailPath: serializer.fromJson(json['thumbnailPath']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'thumbnailPath': serializer.toJson(thumbnailPath), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + String? thumbnailPath, + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + thumbnailPath: data.thumbnailPath.present + ? data.thumbnailPath.value + : this.thumbnailPath, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + thumbnailPath, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.thumbnailPath == this.thumbnailPath && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value thumbnailPath; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.thumbnailPath = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required String thumbnailPath, + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + thumbnailPath = Value(thumbnailPath), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? thumbnailPath, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (thumbnailPath != null) 'thumbnail_path': thumbnailPath, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? thumbnailPath, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + thumbnailPath: thumbnailPath ?? this.thumbnailPath, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (thumbnailPath.present) { + map['thumbnail_path'] = Variable(thumbnailPath.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('thumbnailPath: $thumbnailPath, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV3 extends GeneratedDatabase { + DatabaseAtV3(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index uQRemoteAssetOwnerChecksum = Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + localAssetEntity, + stackEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + localAlbumEntity, + localAlbumAssetEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + ]; + @override + int get schemaVersion => 3; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v4.dart b/mobile/test/drift/main/generated/schema_v4.dart new file mode 100644 index 0000000000..9eabea714f --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v4.dart @@ -0,0 +1,6441 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn profileImagePath = GeneratedColumn( + 'profile_image_path', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn quotaSizeInBytes = GeneratedColumn( + 'quota_size_in_bytes', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn quotaUsageInBytes = GeneratedColumn( + 'quota_usage_in_bytes', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + profileImagePath: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}profile_image_path'], + ), + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + quotaSizeInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_size_in_bytes'], + ), + quotaUsageInBytes: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}quota_usage_in_bytes'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final String? profileImagePath; + final DateTime updatedAt; + final int? quotaSizeInBytes; + final int quotaUsageInBytes; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + this.profileImagePath, + required this.updatedAt, + this.quotaSizeInBytes, + required this.quotaUsageInBytes, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + if (!nullToAbsent || profileImagePath != null) { + map['profile_image_path'] = Variable(profileImagePath); + } + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || quotaSizeInBytes != null) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes); + } + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + profileImagePath: serializer.fromJson(json['profileImagePath']), + updatedAt: serializer.fromJson(json['updatedAt']), + quotaSizeInBytes: serializer.fromJson(json['quotaSizeInBytes']), + quotaUsageInBytes: serializer.fromJson(json['quotaUsageInBytes']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'profileImagePath': serializer.toJson(profileImagePath), + 'updatedAt': serializer.toJson(updatedAt), + 'quotaSizeInBytes': serializer.toJson(quotaSizeInBytes), + 'quotaUsageInBytes': serializer.toJson(quotaUsageInBytes), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + Value profileImagePath = const Value.absent(), + DateTime? updatedAt, + Value quotaSizeInBytes = const Value.absent(), + int? quotaUsageInBytes, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath.present + ? profileImagePath.value + : this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes.present + ? quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + profileImagePath: data.profileImagePath.present + ? data.profileImagePath.value + : this.profileImagePath, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + quotaSizeInBytes: data.quotaSizeInBytes.present + ? data.quotaSizeInBytes.value + : this.quotaSizeInBytes, + quotaUsageInBytes: data.quotaUsageInBytes.present + ? data.quotaUsageInBytes.value + : this.quotaUsageInBytes, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + profileImagePath, + updatedAt, + quotaSizeInBytes, + quotaUsageInBytes, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.profileImagePath == this.profileImagePath && + other.updatedAt == this.updatedAt && + other.quotaSizeInBytes == this.quotaSizeInBytes && + other.quotaUsageInBytes == this.quotaUsageInBytes); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value profileImagePath; + final Value updatedAt; + final Value quotaSizeInBytes; + final Value quotaUsageInBytes; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.profileImagePath = const Value.absent(), + this.updatedAt = const Value.absent(), + this.quotaSizeInBytes = const Value.absent(), + this.quotaUsageInBytes = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? profileImagePath, + Expression? updatedAt, + Expression? quotaSizeInBytes, + Expression? quotaUsageInBytes, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (profileImagePath != null) 'profile_image_path': profileImagePath, + if (updatedAt != null) 'updated_at': updatedAt, + if (quotaSizeInBytes != null) 'quota_size_in_bytes': quotaSizeInBytes, + if (quotaUsageInBytes != null) 'quota_usage_in_bytes': quotaUsageInBytes, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? profileImagePath, + Value? updatedAt, + Value? quotaSizeInBytes, + Value? quotaUsageInBytes, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + profileImagePath: profileImagePath ?? this.profileImagePath, + updatedAt: updatedAt ?? this.updatedAt, + quotaSizeInBytes: quotaSizeInBytes ?? this.quotaSizeInBytes, + quotaUsageInBytes: quotaUsageInBytes ?? this.quotaUsageInBytes, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (profileImagePath.present) { + map['profile_image_path'] = Variable(profileImagePath.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (quotaSizeInBytes.present) { + map['quota_size_in_bytes'] = Variable(quotaSizeInBytes.value); + } + if (quotaUsageInBytes.present) { + map['quota_usage_in_bytes'] = Variable(quotaUsageInBytes.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('profileImagePath: $profileImagePath, ') + ..write('updatedAt: $updatedAt, ') + ..write('quotaSizeInBytes: $quotaSizeInBytes, ') + ..write('quotaUsageInBytes: $quotaUsageInBytes') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV4 extends GeneratedDatabase { + DatabaseAtV4(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index uQRemoteAssetOwnerChecksum = Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + @override + int get schemaVersion => 4; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v5.dart b/mobile/test/drift/main/generated/schema_v5.dart new file mode 100644 index 0000000000..5c94ff26cb --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v5.dart @@ -0,0 +1,6402 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final DateTime updatedAt; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + DateTime? updatedAt, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.updatedAt == this.updatedAt); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value updatedAt; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? updatedAt, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV5 extends GeneratedDatabase { + DatabaseAtV5(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index uQRemoteAssetOwnerChecksum = Index( + 'UQ_remote_asset_owner_checksum', + 'CREATE UNIQUE INDEX UQ_remote_asset_owner_checksum ON remote_asset_entity (checksum, owner_id)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + uQRemoteAssetOwnerChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + @override + int get schemaVersion => 5; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v6.dart b/mobile/test/drift/main/generated/schema_v6.dart new file mode 100644 index 0000000000..97b91caa07 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v6.dart @@ -0,0 +1,6448 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final DateTime updatedAt; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + DateTime? updatedAt, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.updatedAt == this.updatedAt); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value updatedAt; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? updatedAt, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV6 extends GeneratedDatabase { + DatabaseAtV6(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + ]; + @override + int get schemaVersion => 6; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v7.dart b/mobile/test/drift/main/generated/schema_v7.dart new file mode 100644 index 0000000000..276a0cdc74 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v7.dart @@ -0,0 +1,6453 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final DateTime updatedAt; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + DateTime? updatedAt, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.updatedAt == this.updatedAt); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value updatedAt; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? updatedAt, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV7 extends GeneratedDatabase { + DatabaseAtV7(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 7; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v8.dart b/mobile/test/drift/main/generated/schema_v8.dart new file mode 100644 index 0000000000..13520e6372 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v8.dart @@ -0,0 +1,6663 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final DateTime updatedAt; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + DateTime? updatedAt, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.updatedAt == this.updatedAt); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value updatedAt; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? updatedAt, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int 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 toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value marker_ = const Value.absent(), + }) => 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(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + 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_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..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_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? marker_, + }) { + return 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_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV8 extends GeneratedDatabase { + DatabaseAtV8(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 8; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/generated/schema_v9.dart b/mobile/test/drift/main/generated/schema_v9.dart new file mode 100644 index 0000000000..c2ccb58737 --- /dev/null +++ b/mobile/test/drift/main/generated/schema_v9.dart @@ -0,0 +1,6712 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class UserEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isAdmin = GeneratedColumn( + 'is_admin', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_admin" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn email = GeneratedColumn( + 'email', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn hasProfileImage = GeneratedColumn( + 'has_profile_image', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("has_profile_image" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn profileChangedAt = + GeneratedColumn( + 'profile_changed_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + @override + List get $columns => [ + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_entity'; + @override + Set get $primaryKey => {id}; + @override + UserEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + isAdmin: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_admin'], + )!, + email: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}email'], + )!, + hasProfileImage: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}has_profile_image'], + )!, + profileChangedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}profile_changed_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ); + } + + @override + UserEntity createAlias(String alias) { + return UserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserEntityData extends DataClass implements Insertable { + final String id; + final String name; + final bool isAdmin; + final String email; + final bool hasProfileImage; + final DateTime profileChangedAt; + final DateTime updatedAt; + const UserEntityData({ + required this.id, + required this.name, + required this.isAdmin, + required this.email, + required this.hasProfileImage, + required this.profileChangedAt, + required this.updatedAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['is_admin'] = Variable(isAdmin); + map['email'] = Variable(email); + map['has_profile_image'] = Variable(hasProfileImage); + map['profile_changed_at'] = Variable(profileChangedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + factory UserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + isAdmin: serializer.fromJson(json['isAdmin']), + email: serializer.fromJson(json['email']), + hasProfileImage: serializer.fromJson(json['hasProfileImage']), + profileChangedAt: serializer.fromJson(json['profileChangedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'isAdmin': serializer.toJson(isAdmin), + 'email': serializer.toJson(email), + 'hasProfileImage': serializer.toJson(hasProfileImage), + 'profileChangedAt': serializer.toJson(profileChangedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserEntityData copyWith({ + String? id, + String? name, + bool? isAdmin, + String? email, + bool? hasProfileImage, + DateTime? profileChangedAt, + DateTime? updatedAt, + }) => UserEntityData( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + UserEntityData copyWithCompanion(UserEntityCompanion data) { + return UserEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + isAdmin: data.isAdmin.present ? data.isAdmin.value : this.isAdmin, + email: data.email.present ? data.email.value : this.email, + hasProfileImage: data.hasProfileImage.present + ? data.hasProfileImage.value + : this.hasProfileImage, + profileChangedAt: data.profileChangedAt.present + ? data.profileChangedAt.value + : this.profileChangedAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ); + } + + @override + String toString() { + return (StringBuffer('UserEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + isAdmin, + email, + hasProfileImage, + profileChangedAt, + updatedAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserEntityData && + other.id == this.id && + other.name == this.name && + other.isAdmin == this.isAdmin && + other.email == this.email && + other.hasProfileImage == this.hasProfileImage && + other.profileChangedAt == this.profileChangedAt && + other.updatedAt == this.updatedAt); +} + +class UserEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value isAdmin; + final Value email; + final Value hasProfileImage; + final Value profileChangedAt; + final Value updatedAt; + const UserEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.isAdmin = const Value.absent(), + this.email = const Value.absent(), + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserEntityCompanion.insert({ + required String id, + required String name, + this.isAdmin = const Value.absent(), + required String email, + this.hasProfileImage = const Value.absent(), + this.profileChangedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : id = Value(id), + name = Value(name), + email = Value(email); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? isAdmin, + Expression? email, + Expression? hasProfileImage, + Expression? profileChangedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (isAdmin != null) 'is_admin': isAdmin, + if (email != null) 'email': email, + if (hasProfileImage != null) 'has_profile_image': hasProfileImage, + if (profileChangedAt != null) 'profile_changed_at': profileChangedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserEntityCompanion copyWith({ + Value? id, + Value? name, + Value? isAdmin, + Value? email, + Value? hasProfileImage, + Value? profileChangedAt, + Value? updatedAt, + }) { + return UserEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + isAdmin: isAdmin ?? this.isAdmin, + email: email ?? this.email, + hasProfileImage: hasProfileImage ?? this.hasProfileImage, + profileChangedAt: profileChangedAt ?? this.profileChangedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (isAdmin.present) { + map['is_admin'] = Variable(isAdmin.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (hasProfileImage.present) { + map['has_profile_image'] = Variable(hasProfileImage.value); + } + if (profileChangedAt.present) { + map['profile_changed_at'] = Variable(profileChangedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('isAdmin: $isAdmin, ') + ..write('email: $email, ') + ..write('hasProfileImage: $hasProfileImage, ') + ..write('profileChangedAt: $profileChangedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class RemoteAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn localDateTime = + GeneratedColumn( + 'local_date_time', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn thumbHash = GeneratedColumn( + 'thumb_hash', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn livePhotoVideoId = GeneratedColumn( + 'live_photo_video_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn visibility = GeneratedColumn( + 'visibility', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stackId = GeneratedColumn( + 'stack_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn libraryId = GeneratedColumn( + 'library_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + )!, + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + localDateTime: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}local_date_time'], + ), + thumbHash: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumb_hash'], + ), + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + livePhotoVideoId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}live_photo_video_id'], + ), + visibility: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}visibility'], + )!, + stackId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}stack_id'], + ), + libraryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}library_id'], + ), + ); + } + + @override + RemoteAssetEntity createAlias(String alias) { + return RemoteAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String checksum; + final bool isFavorite; + final String ownerId; + final DateTime? localDateTime; + final String? thumbHash; + final DateTime? deletedAt; + final String? livePhotoVideoId; + final int visibility; + final String? stackId; + final String? libraryId; + const RemoteAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + required this.checksum, + required this.isFavorite, + required this.ownerId, + this.localDateTime, + this.thumbHash, + this.deletedAt, + this.livePhotoVideoId, + required this.visibility, + this.stackId, + this.libraryId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + map['checksum'] = Variable(checksum); + map['is_favorite'] = Variable(isFavorite); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || localDateTime != null) { + map['local_date_time'] = Variable(localDateTime); + } + if (!nullToAbsent || thumbHash != null) { + map['thumb_hash'] = Variable(thumbHash); + } + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + if (!nullToAbsent || livePhotoVideoId != null) { + map['live_photo_video_id'] = Variable(livePhotoVideoId); + } + map['visibility'] = Variable(visibility); + if (!nullToAbsent || stackId != null) { + map['stack_id'] = Variable(stackId); + } + if (!nullToAbsent || libraryId != null) { + map['library_id'] = Variable(libraryId); + } + return map; + } + + factory RemoteAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + ownerId: serializer.fromJson(json['ownerId']), + localDateTime: serializer.fromJson(json['localDateTime']), + thumbHash: serializer.fromJson(json['thumbHash']), + deletedAt: serializer.fromJson(json['deletedAt']), + livePhotoVideoId: serializer.fromJson(json['livePhotoVideoId']), + visibility: serializer.fromJson(json['visibility']), + stackId: serializer.fromJson(json['stackId']), + libraryId: serializer.fromJson(json['libraryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'ownerId': serializer.toJson(ownerId), + 'localDateTime': serializer.toJson(localDateTime), + 'thumbHash': serializer.toJson(thumbHash), + 'deletedAt': serializer.toJson(deletedAt), + 'livePhotoVideoId': serializer.toJson(livePhotoVideoId), + 'visibility': serializer.toJson(visibility), + 'stackId': serializer.toJson(stackId), + 'libraryId': serializer.toJson(libraryId), + }; + } + + RemoteAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + String? checksum, + bool? isFavorite, + String? ownerId, + Value localDateTime = const Value.absent(), + Value thumbHash = const Value.absent(), + Value deletedAt = const Value.absent(), + Value livePhotoVideoId = const Value.absent(), + int? visibility, + Value stackId = const Value.absent(), + Value libraryId = const Value.absent(), + }) => RemoteAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime.present + ? localDateTime.value + : this.localDateTime, + thumbHash: thumbHash.present ? thumbHash.value : this.thumbHash, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + livePhotoVideoId: livePhotoVideoId.present + ? livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId.present ? stackId.value : this.stackId, + libraryId: libraryId.present ? libraryId.value : this.libraryId, + ); + RemoteAssetEntityData copyWithCompanion(RemoteAssetEntityCompanion data) { + return RemoteAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + localDateTime: data.localDateTime.present + ? data.localDateTime.value + : this.localDateTime, + thumbHash: data.thumbHash.present ? data.thumbHash.value : this.thumbHash, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + livePhotoVideoId: data.livePhotoVideoId.present + ? data.livePhotoVideoId.value + : this.livePhotoVideoId, + visibility: data.visibility.present + ? data.visibility.value + : this.visibility, + stackId: data.stackId.present ? data.stackId.value : this.stackId, + libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + ownerId, + localDateTime, + thumbHash, + deletedAt, + livePhotoVideoId, + visibility, + stackId, + libraryId, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.ownerId == this.ownerId && + other.localDateTime == this.localDateTime && + other.thumbHash == this.thumbHash && + other.deletedAt == this.deletedAt && + other.livePhotoVideoId == this.livePhotoVideoId && + other.visibility == this.visibility && + other.stackId == this.stackId && + other.libraryId == this.libraryId); +} + +class RemoteAssetEntityCompanion + extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value ownerId; + final Value localDateTime; + final Value thumbHash; + final Value deletedAt; + final Value livePhotoVideoId; + final Value visibility; + final Value stackId; + final Value libraryId; + const RemoteAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.ownerId = const Value.absent(), + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + this.visibility = const Value.absent(), + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }); + RemoteAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + required String checksum, + this.isFavorite = const Value.absent(), + required String ownerId, + this.localDateTime = const Value.absent(), + this.thumbHash = const Value.absent(), + this.deletedAt = const Value.absent(), + this.livePhotoVideoId = const Value.absent(), + required int visibility, + this.stackId = const Value.absent(), + this.libraryId = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id), + checksum = Value(checksum), + ownerId = Value(ownerId), + visibility = Value(visibility); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? ownerId, + Expression? localDateTime, + Expression? thumbHash, + Expression? deletedAt, + Expression? livePhotoVideoId, + Expression? visibility, + Expression? stackId, + Expression? libraryId, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (ownerId != null) 'owner_id': ownerId, + if (localDateTime != null) 'local_date_time': localDateTime, + if (thumbHash != null) 'thumb_hash': thumbHash, + if (deletedAt != null) 'deleted_at': deletedAt, + if (livePhotoVideoId != null) 'live_photo_video_id': livePhotoVideoId, + if (visibility != null) 'visibility': visibility, + if (stackId != null) 'stack_id': stackId, + if (libraryId != null) 'library_id': libraryId, + }); + } + + RemoteAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? ownerId, + Value? localDateTime, + Value? thumbHash, + Value? deletedAt, + Value? livePhotoVideoId, + Value? visibility, + Value? stackId, + Value? libraryId, + }) { + return RemoteAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + ownerId: ownerId ?? this.ownerId, + localDateTime: localDateTime ?? this.localDateTime, + thumbHash: thumbHash ?? this.thumbHash, + deletedAt: deletedAt ?? this.deletedAt, + livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId, + visibility: visibility ?? this.visibility, + stackId: stackId ?? this.stackId, + libraryId: libraryId ?? this.libraryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (localDateTime.present) { + map['local_date_time'] = Variable(localDateTime.value); + } + if (thumbHash.present) { + map['thumb_hash'] = Variable(thumbHash.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (livePhotoVideoId.present) { + map['live_photo_video_id'] = Variable(livePhotoVideoId.value); + } + if (visibility.present) { + map['visibility'] = Variable(visibility.value); + } + if (stackId.present) { + map['stack_id'] = Variable(stackId.value); + } + if (libraryId.present) { + map['library_id'] = Variable(libraryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('ownerId: $ownerId, ') + ..write('localDateTime: $localDateTime, ') + ..write('thumbHash: $thumbHash, ') + ..write('deletedAt: $deletedAt, ') + ..write('livePhotoVideoId: $livePhotoVideoId, ') + ..write('visibility: $visibility, ') + ..write('stackId: $stackId, ') + ..write('libraryId: $libraryId') + ..write(')')) + .toString(); + } +} + +class StackEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StackEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn primaryAssetId = GeneratedColumn( + 'primary_asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + primaryAssetId, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'stack_entity'; + @override + Set get $primaryKey => {id}; + @override + StackEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StackEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + primaryAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}primary_asset_id'], + )!, + ); + } + + @override + StackEntity createAlias(String alias) { + return StackEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StackEntityData extends DataClass implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String primaryAssetId; + const StackEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.primaryAssetId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['primary_asset_id'] = Variable(primaryAssetId); + return map; + } + + factory StackEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StackEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + primaryAssetId: serializer.fromJson(json['primaryAssetId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'primaryAssetId': serializer.toJson(primaryAssetId), + }; + } + + StackEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? primaryAssetId, + }) => StackEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + StackEntityData copyWithCompanion(StackEntityCompanion data) { + return StackEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + primaryAssetId: data.primaryAssetId.present + ? data.primaryAssetId.value + : this.primaryAssetId, + ); + } + + @override + String toString() { + return (StringBuffer('StackEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, createdAt, updatedAt, ownerId, primaryAssetId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StackEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.primaryAssetId == this.primaryAssetId); +} + +class StackEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value primaryAssetId; + const StackEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.primaryAssetId = const Value.absent(), + }); + StackEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String primaryAssetId, + }) : id = Value(id), + ownerId = Value(ownerId), + primaryAssetId = Value(primaryAssetId); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? primaryAssetId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (primaryAssetId != null) 'primary_asset_id': primaryAssetId, + }); + } + + StackEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? primaryAssetId, + }) { + return StackEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + primaryAssetId: primaryAssetId ?? this.primaryAssetId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (primaryAssetId.present) { + map['primary_asset_id'] = Variable(primaryAssetId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StackEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('primaryAssetId: $primaryAssetId') + ..write(')')) + .toString(); + } +} + +class LocalAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn durationInSeconds = GeneratedColumn( + 'duration_in_seconds', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn checksum = GeneratedColumn( + 'checksum', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [ + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_asset_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAssetEntityData( + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + durationInSeconds: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}duration_in_seconds'], + ), + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + checksum: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}checksum'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}orientation'], + )!, + ); + } + + @override + LocalAssetEntity createAlias(String alias) { + return LocalAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAssetEntityData extends DataClass + implements Insertable { + final String name; + final int type; + final DateTime createdAt; + final DateTime updatedAt; + final int? width; + final int? height; + final int? durationInSeconds; + final String id; + final String? checksum; + final bool isFavorite; + final int orientation; + const LocalAssetEntityData({ + required this.name, + required this.type, + required this.createdAt, + required this.updatedAt, + this.width, + this.height, + this.durationInSeconds, + required this.id, + this.checksum, + required this.isFavorite, + required this.orientation, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['name'] = Variable(name); + map['type'] = Variable(type); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || durationInSeconds != null) { + map['duration_in_seconds'] = Variable(durationInSeconds); + } + map['id'] = Variable(id); + if (!nullToAbsent || checksum != null) { + map['checksum'] = Variable(checksum); + } + map['is_favorite'] = Variable(isFavorite); + map['orientation'] = Variable(orientation); + return map; + } + + factory LocalAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAssetEntityData( + name: serializer.fromJson(json['name']), + type: serializer.fromJson(json['type']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + width: serializer.fromJson(json['width']), + height: serializer.fromJson(json['height']), + durationInSeconds: serializer.fromJson(json['durationInSeconds']), + id: serializer.fromJson(json['id']), + checksum: serializer.fromJson(json['checksum']), + isFavorite: serializer.fromJson(json['isFavorite']), + orientation: serializer.fromJson(json['orientation']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'name': serializer.toJson(name), + 'type': serializer.toJson(type), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'width': serializer.toJson(width), + 'height': serializer.toJson(height), + 'durationInSeconds': serializer.toJson(durationInSeconds), + 'id': serializer.toJson(id), + 'checksum': serializer.toJson(checksum), + 'isFavorite': serializer.toJson(isFavorite), + 'orientation': serializer.toJson(orientation), + }; + } + + LocalAssetEntityData copyWith({ + String? name, + int? type, + DateTime? createdAt, + DateTime? updatedAt, + Value width = const Value.absent(), + Value height = const Value.absent(), + Value durationInSeconds = const Value.absent(), + String? id, + Value checksum = const Value.absent(), + bool? isFavorite, + int? orientation, + }) => LocalAssetEntityData( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width.present ? width.value : this.width, + height: height.present ? height.value : this.height, + durationInSeconds: durationInSeconds.present + ? durationInSeconds.value + : this.durationInSeconds, + id: id ?? this.id, + checksum: checksum.present ? checksum.value : this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + LocalAssetEntityData copyWithCompanion(LocalAssetEntityCompanion data) { + return LocalAssetEntityData( + name: data.name.present ? data.name.value : this.name, + type: data.type.present ? data.type.value : this.type, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + width: data.width.present ? data.width.value : this.width, + height: data.height.present ? data.height.value : this.height, + durationInSeconds: data.durationInSeconds.present + ? data.durationInSeconds.value + : this.durationInSeconds, + id: data.id.present ? data.id.value : this.id, + checksum: data.checksum.present ? data.checksum.value : this.checksum, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityData(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + name, + type, + createdAt, + updatedAt, + width, + height, + durationInSeconds, + id, + checksum, + isFavorite, + orientation, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAssetEntityData && + other.name == this.name && + other.type == this.type && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.width == this.width && + other.height == this.height && + other.durationInSeconds == this.durationInSeconds && + other.id == this.id && + other.checksum == this.checksum && + other.isFavorite == this.isFavorite && + other.orientation == this.orientation); +} + +class LocalAssetEntityCompanion extends UpdateCompanion { + final Value name; + final Value type; + final Value createdAt; + final Value updatedAt; + final Value width; + final Value height; + final Value durationInSeconds; + final Value id; + final Value checksum; + final Value isFavorite; + final Value orientation; + const LocalAssetEntityCompanion({ + this.name = const Value.absent(), + this.type = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + this.id = const Value.absent(), + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }); + LocalAssetEntityCompanion.insert({ + required String name, + required int type, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.width = const Value.absent(), + this.height = const Value.absent(), + this.durationInSeconds = const Value.absent(), + required String id, + this.checksum = const Value.absent(), + this.isFavorite = const Value.absent(), + this.orientation = const Value.absent(), + }) : name = Value(name), + type = Value(type), + id = Value(id); + static Insertable custom({ + Expression? name, + Expression? type, + Expression? createdAt, + Expression? updatedAt, + Expression? width, + Expression? height, + Expression? durationInSeconds, + Expression? id, + Expression? checksum, + Expression? isFavorite, + Expression? orientation, + }) { + return RawValuesInsertable({ + if (name != null) 'name': name, + if (type != null) 'type': type, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (width != null) 'width': width, + if (height != null) 'height': height, + if (durationInSeconds != null) 'duration_in_seconds': durationInSeconds, + if (id != null) 'id': id, + if (checksum != null) 'checksum': checksum, + if (isFavorite != null) 'is_favorite': isFavorite, + if (orientation != null) 'orientation': orientation, + }); + } + + LocalAssetEntityCompanion copyWith({ + Value? name, + Value? type, + Value? createdAt, + Value? updatedAt, + Value? width, + Value? height, + Value? durationInSeconds, + Value? id, + Value? checksum, + Value? isFavorite, + Value? orientation, + }) { + return LocalAssetEntityCompanion( + name: name ?? this.name, + type: type ?? this.type, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + width: width ?? this.width, + height: height ?? this.height, + durationInSeconds: durationInSeconds ?? this.durationInSeconds, + id: id ?? this.id, + checksum: checksum ?? this.checksum, + isFavorite: isFavorite ?? this.isFavorite, + orientation: orientation ?? this.orientation, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (name.present) { + map['name'] = Variable(name.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (durationInSeconds.present) { + map['duration_in_seconds'] = Variable(durationInSeconds.value); + } + if (id.present) { + map['id'] = Variable(id.value); + } + if (checksum.present) { + map['checksum'] = Variable(checksum.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAssetEntityCompanion(') + ..write('name: $name, ') + ..write('type: $type, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('width: $width, ') + ..write('height: $height, ') + ..write('durationInSeconds: $durationInSeconds, ') + ..write('id: $id, ') + ..write('checksum: $checksum, ') + ..write('isFavorite: $isFavorite, ') + ..write('orientation: $orientation') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const CustomExpression('\'\''), + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn thumbnailAssetId = GeneratedColumn( + 'thumbnail_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn isActivityEnabled = GeneratedColumn( + 'is_activity_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_activity_enabled" IN (0, 1))', + ), + defaultValue: const CustomExpression('1'), + ); + late final GeneratedColumn order = GeneratedColumn( + 'order', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_entity'; + @override + Set get $primaryKey => {id}; + @override + RemoteAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + thumbnailAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}thumbnail_asset_id'], + ), + isActivityEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_activity_enabled'], + )!, + order: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}order'], + )!, + ); + } + + @override + RemoteAlbumEntity createAlias(String alias) { + return RemoteAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final String description; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String? thumbnailAssetId; + final bool isActivityEnabled; + final int order; + const RemoteAlbumEntityData({ + required this.id, + required this.name, + required this.description, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + this.thumbnailAssetId, + required this.isActivityEnabled, + required this.order, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + if (!nullToAbsent || thumbnailAssetId != null) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId); + } + map['is_activity_enabled'] = Variable(isActivityEnabled); + map['order'] = Variable(order); + return map; + } + + factory RemoteAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + thumbnailAssetId: serializer.fromJson(json['thumbnailAssetId']), + isActivityEnabled: serializer.fromJson(json['isActivityEnabled']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'thumbnailAssetId': serializer.toJson(thumbnailAssetId), + 'isActivityEnabled': serializer.toJson(isActivityEnabled), + 'order': serializer.toJson(order), + }; + } + + RemoteAlbumEntityData copyWith({ + String? id, + String? name, + String? description, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + Value thumbnailAssetId = const Value.absent(), + bool? isActivityEnabled, + int? order, + }) => RemoteAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId.present + ? thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + RemoteAlbumEntityData copyWithCompanion(RemoteAlbumEntityCompanion data) { + return RemoteAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: data.description.present + ? data.description.value + : this.description, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + thumbnailAssetId: data.thumbnailAssetId.present + ? data.thumbnailAssetId.value + : this.thumbnailAssetId, + isActivityEnabled: data.isActivityEnabled.present + ? data.isActivityEnabled.value + : this.isActivityEnabled, + order: data.order.present ? data.order.value : this.order, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + description, + createdAt, + updatedAt, + ownerId, + thumbnailAssetId, + isActivityEnabled, + order, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.thumbnailAssetId == this.thumbnailAssetId && + other.isActivityEnabled == this.isActivityEnabled && + other.order == this.order); +} + +class RemoteAlbumEntityCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value thumbnailAssetId; + final Value isActivityEnabled; + final Value order; + const RemoteAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + this.order = const Value.absent(), + }); + RemoteAlbumEntityCompanion.insert({ + required String id, + required String name, + this.description = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + this.thumbnailAssetId = const Value.absent(), + this.isActivityEnabled = const Value.absent(), + required int order, + }) : id = Value(id), + name = Value(name), + ownerId = Value(ownerId), + order = Value(order); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? thumbnailAssetId, + Expression? isActivityEnabled, + Expression? order, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (thumbnailAssetId != null) 'thumbnail_asset_id': thumbnailAssetId, + if (isActivityEnabled != null) 'is_activity_enabled': isActivityEnabled, + if (order != null) 'order': order, + }); + } + + RemoteAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? description, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? thumbnailAssetId, + Value? isActivityEnabled, + Value? order, + }) { + return RemoteAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + thumbnailAssetId: thumbnailAssetId ?? this.thumbnailAssetId, + isActivityEnabled: isActivityEnabled ?? this.isActivityEnabled, + order: order ?? this.order, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (thumbnailAssetId.present) { + map['thumbnail_asset_id'] = Variable(thumbnailAssetId.value); + } + if (isActivityEnabled.present) { + map['is_activity_enabled'] = Variable(isActivityEnabled.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('thumbnailAssetId: $thumbnailAssetId, ') + ..write('isActivityEnabled: $isActivityEnabled, ') + ..write('order: $order') + ..write(')')) + .toString(); + } +} + +class LocalAlbumEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn backupSelection = GeneratedColumn( + 'backup_selection', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn isIosSharedAlbum = GeneratedColumn( + 'is_ios_shared_album', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_ios_shared_album" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn linkedRemoteAlbumId = + GeneratedColumn( + 'linked_remote_album_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn marker_ = GeneratedColumn( + 'marker', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("marker" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_entity'; + @override + Set get $primaryKey => {id}; + @override + LocalAlbumEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + backupSelection: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}backup_selection'], + )!, + isIosSharedAlbum: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_ios_shared_album'], + )!, + linkedRemoteAlbumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}linked_remote_album_id'], + ), + marker_: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}marker'], + ), + ); + } + + @override + LocalAlbumEntity createAlias(String alias) { + return LocalAlbumEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumEntityData extends DataClass + implements Insertable { + final String id; + final String name; + final DateTime updatedAt; + final int backupSelection; + final bool isIosSharedAlbum; + final String? linkedRemoteAlbumId; + final bool? marker_; + const LocalAlbumEntityData({ + required this.id, + required this.name, + required this.updatedAt, + required this.backupSelection, + required this.isIosSharedAlbum, + this.linkedRemoteAlbumId, + this.marker_, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['updated_at'] = Variable(updatedAt); + map['backup_selection'] = Variable(backupSelection); + map['is_ios_shared_album'] = Variable(isIosSharedAlbum); + if (!nullToAbsent || linkedRemoteAlbumId != null) { + map['linked_remote_album_id'] = Variable(linkedRemoteAlbumId); + } + if (!nullToAbsent || marker_ != null) { + map['marker'] = Variable(marker_); + } + return map; + } + + factory LocalAlbumEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumEntityData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + updatedAt: serializer.fromJson(json['updatedAt']), + backupSelection: serializer.fromJson(json['backupSelection']), + isIosSharedAlbum: serializer.fromJson(json['isIosSharedAlbum']), + linkedRemoteAlbumId: serializer.fromJson( + json['linkedRemoteAlbumId'], + ), + marker_: serializer.fromJson(json['marker_']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'updatedAt': serializer.toJson(updatedAt), + 'backupSelection': serializer.toJson(backupSelection), + 'isIosSharedAlbum': serializer.toJson(isIosSharedAlbum), + 'linkedRemoteAlbumId': serializer.toJson(linkedRemoteAlbumId), + 'marker_': serializer.toJson(marker_), + }; + } + + LocalAlbumEntityData copyWith({ + String? id, + String? name, + DateTime? updatedAt, + int? backupSelection, + bool? isIosSharedAlbum, + Value linkedRemoteAlbumId = const Value.absent(), + Value marker_ = const Value.absent(), + }) => LocalAlbumEntityData( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId.present + ? linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: marker_.present ? marker_.value : this.marker_, + ); + LocalAlbumEntityData copyWithCompanion(LocalAlbumEntityCompanion data) { + return LocalAlbumEntityData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + backupSelection: data.backupSelection.present + ? data.backupSelection.value + : this.backupSelection, + isIosSharedAlbum: data.isIosSharedAlbum.present + ? data.isIosSharedAlbum.value + : this.isIosSharedAlbum, + linkedRemoteAlbumId: data.linkedRemoteAlbumId.present + ? data.linkedRemoteAlbumId.value + : this.linkedRemoteAlbumId, + marker_: data.marker_.present ? data.marker_.value : this.marker_, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + updatedAt, + backupSelection, + isIosSharedAlbum, + linkedRemoteAlbumId, + marker_, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumEntityData && + other.id == this.id && + other.name == this.name && + other.updatedAt == this.updatedAt && + other.backupSelection == this.backupSelection && + other.isIosSharedAlbum == this.isIosSharedAlbum && + other.linkedRemoteAlbumId == this.linkedRemoteAlbumId && + other.marker_ == this.marker_); +} + +class LocalAlbumEntityCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value updatedAt; + final Value backupSelection; + final Value isIosSharedAlbum; + final Value linkedRemoteAlbumId; + final Value marker_; + const LocalAlbumEntityCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.updatedAt = const Value.absent(), + this.backupSelection = const Value.absent(), + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }); + LocalAlbumEntityCompanion.insert({ + required String id, + required String name, + this.updatedAt = const Value.absent(), + required int backupSelection, + this.isIosSharedAlbum = const Value.absent(), + this.linkedRemoteAlbumId = const Value.absent(), + this.marker_ = const Value.absent(), + }) : id = Value(id), + name = Value(name), + backupSelection = Value(backupSelection); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? updatedAt, + Expression? backupSelection, + Expression? isIosSharedAlbum, + Expression? linkedRemoteAlbumId, + Expression? marker_, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + 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 (linkedRemoteAlbumId != null) + 'linked_remote_album_id': linkedRemoteAlbumId, + if (marker_ != null) 'marker': marker_, + }); + } + + LocalAlbumEntityCompanion copyWith({ + Value? id, + Value? name, + Value? updatedAt, + Value? backupSelection, + Value? isIosSharedAlbum, + Value? linkedRemoteAlbumId, + Value? marker_, + }) { + return LocalAlbumEntityCompanion( + id: id ?? this.id, + name: name ?? this.name, + updatedAt: updatedAt ?? this.updatedAt, + backupSelection: backupSelection ?? this.backupSelection, + isIosSharedAlbum: isIosSharedAlbum ?? this.isIosSharedAlbum, + linkedRemoteAlbumId: linkedRemoteAlbumId ?? this.linkedRemoteAlbumId, + marker_: marker_ ?? this.marker_, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (backupSelection.present) { + map['backup_selection'] = Variable(backupSelection.value); + } + if (isIosSharedAlbum.present) { + map['is_ios_shared_album'] = Variable(isIosSharedAlbum.value); + } + if (linkedRemoteAlbumId.present) { + map['linked_remote_album_id'] = Variable( + linkedRemoteAlbumId.value, + ); + } + if (marker_.present) { + map['marker'] = Variable(marker_.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumEntityCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('updatedAt: $updatedAt, ') + ..write('backupSelection: $backupSelection, ') + ..write('isIosSharedAlbum: $isIosSharedAlbum, ') + ..write('linkedRemoteAlbumId: $linkedRemoteAlbumId, ') + ..write('marker_: $marker_') + ..write(')')) + .toString(); + } +} + +class LocalAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LocalAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES local_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'local_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + LocalAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocalAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + LocalAlbumAssetEntity createAlias(String alias) { + return LocalAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class LocalAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const LocalAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory LocalAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocalAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + LocalAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + LocalAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + LocalAlbumAssetEntityData copyWithCompanion( + LocalAlbumAssetEntityCompanion data, + ) { + return LocalAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocalAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class LocalAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const LocalAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + LocalAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + LocalAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return LocalAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocalAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class UserMetadataEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + UserMetadataEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn key = GeneratedColumn( + 'key', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn value = GeneratedColumn( + 'value', + aliasedName, + false, + type: DriftSqlType.blob, + requiredDuringInsert: true, + ); + @override + List get $columns => [userId, key, value]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_metadata_entity'; + @override + Set get $primaryKey => {userId, key}; + @override + UserMetadataEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserMetadataEntityData( + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + key: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}key'], + )!, + value: attachedDatabase.typeMapping.read( + DriftSqlType.blob, + data['${effectivePrefix}value'], + )!, + ); + } + + @override + UserMetadataEntity createAlias(String alias) { + return UserMetadataEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class UserMetadataEntityData extends DataClass + implements Insertable { + final String userId; + final int key; + final Uint8List value; + const UserMetadataEntityData({ + required this.userId, + required this.key, + required this.value, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['key'] = Variable(key); + map['value'] = Variable(value); + return map; + } + + factory UserMetadataEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserMetadataEntityData( + userId: serializer.fromJson(json['userId']), + key: serializer.fromJson(json['key']), + value: serializer.fromJson(json['value']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'key': serializer.toJson(key), + 'value': serializer.toJson(value), + }; + } + + UserMetadataEntityData copyWith({ + String? userId, + int? key, + Uint8List? value, + }) => UserMetadataEntityData( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + UserMetadataEntityData copyWithCompanion(UserMetadataEntityCompanion data) { + return UserMetadataEntityData( + userId: data.userId.present ? data.userId.value : this.userId, + key: data.key.present ? data.key.value : this.key, + value: data.value.present ? data.value.value : this.value, + ); + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityData(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, key, $driftBlobEquality.hash(value)); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserMetadataEntityData && + other.userId == this.userId && + other.key == this.key && + $driftBlobEquality.equals(other.value, this.value)); +} + +class UserMetadataEntityCompanion + extends UpdateCompanion { + final Value userId; + final Value key; + final Value value; + const UserMetadataEntityCompanion({ + this.userId = const Value.absent(), + this.key = const Value.absent(), + this.value = const Value.absent(), + }); + UserMetadataEntityCompanion.insert({ + required String userId, + required int key, + required Uint8List value, + }) : userId = Value(userId), + key = Value(key), + value = Value(value); + static Insertable custom({ + Expression? userId, + Expression? key, + Expression? value, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (key != null) 'key': key, + if (value != null) 'value': value, + }); + } + + UserMetadataEntityCompanion copyWith({ + Value? userId, + Value? key, + Value? value, + }) { + return UserMetadataEntityCompanion( + userId: userId ?? this.userId, + key: key ?? this.key, + value: value ?? this.value, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (key.present) { + map['key'] = Variable(key.value); + } + if (value.present) { + map['value'] = Variable(value.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserMetadataEntityCompanion(') + ..write('userId: $userId, ') + ..write('key: $key, ') + ..write('value: $value') + ..write(')')) + .toString(); + } +} + +class PartnerEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PartnerEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn sharedById = GeneratedColumn( + 'shared_by_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn sharedWithId = GeneratedColumn( + 'shared_with_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn inTimeline = GeneratedColumn( + 'in_timeline', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("in_timeline" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + @override + List get $columns => [sharedById, sharedWithId, inTimeline]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'partner_entity'; + @override + Set get $primaryKey => {sharedById, sharedWithId}; + @override + PartnerEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PartnerEntityData( + sharedById: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_by_id'], + )!, + sharedWithId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}shared_with_id'], + )!, + inTimeline: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}in_timeline'], + )!, + ); + } + + @override + PartnerEntity createAlias(String alias) { + return PartnerEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PartnerEntityData extends DataClass + implements Insertable { + final String sharedById; + final String sharedWithId; + final bool inTimeline; + const PartnerEntityData({ + required this.sharedById, + required this.sharedWithId, + required this.inTimeline, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['shared_by_id'] = Variable(sharedById); + map['shared_with_id'] = Variable(sharedWithId); + map['in_timeline'] = Variable(inTimeline); + return map; + } + + factory PartnerEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PartnerEntityData( + sharedById: serializer.fromJson(json['sharedById']), + sharedWithId: serializer.fromJson(json['sharedWithId']), + inTimeline: serializer.fromJson(json['inTimeline']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'sharedById': serializer.toJson(sharedById), + 'sharedWithId': serializer.toJson(sharedWithId), + 'inTimeline': serializer.toJson(inTimeline), + }; + } + + PartnerEntityData copyWith({ + String? sharedById, + String? sharedWithId, + bool? inTimeline, + }) => PartnerEntityData( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + PartnerEntityData copyWithCompanion(PartnerEntityCompanion data) { + return PartnerEntityData( + sharedById: data.sharedById.present + ? data.sharedById.value + : this.sharedById, + sharedWithId: data.sharedWithId.present + ? data.sharedWithId.value + : this.sharedWithId, + inTimeline: data.inTimeline.present + ? data.inTimeline.value + : this.inTimeline, + ); + } + + @override + String toString() { + return (StringBuffer('PartnerEntityData(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(sharedById, sharedWithId, inTimeline); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PartnerEntityData && + other.sharedById == this.sharedById && + other.sharedWithId == this.sharedWithId && + other.inTimeline == this.inTimeline); +} + +class PartnerEntityCompanion extends UpdateCompanion { + final Value sharedById; + final Value sharedWithId; + final Value inTimeline; + const PartnerEntityCompanion({ + this.sharedById = const Value.absent(), + this.sharedWithId = const Value.absent(), + this.inTimeline = const Value.absent(), + }); + PartnerEntityCompanion.insert({ + required String sharedById, + required String sharedWithId, + this.inTimeline = const Value.absent(), + }) : sharedById = Value(sharedById), + sharedWithId = Value(sharedWithId); + static Insertable custom({ + Expression? sharedById, + Expression? sharedWithId, + Expression? inTimeline, + }) { + return RawValuesInsertable({ + if (sharedById != null) 'shared_by_id': sharedById, + if (sharedWithId != null) 'shared_with_id': sharedWithId, + if (inTimeline != null) 'in_timeline': inTimeline, + }); + } + + PartnerEntityCompanion copyWith({ + Value? sharedById, + Value? sharedWithId, + Value? inTimeline, + }) { + return PartnerEntityCompanion( + sharedById: sharedById ?? this.sharedById, + sharedWithId: sharedWithId ?? this.sharedWithId, + inTimeline: inTimeline ?? this.inTimeline, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (sharedById.present) { + map['shared_by_id'] = Variable(sharedById.value); + } + if (sharedWithId.present) { + map['shared_with_id'] = Variable(sharedWithId.value); + } + if (inTimeline.present) { + map['in_timeline'] = Variable(inTimeline.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PartnerEntityCompanion(') + ..write('sharedById: $sharedById, ') + ..write('sharedWithId: $sharedWithId, ') + ..write('inTimeline: $inTimeline') + ..write(')')) + .toString(); + } +} + +class RemoteExifEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteExifEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn city = GeneratedColumn( + 'city', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn state = GeneratedColumn( + 'state', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn country = GeneratedColumn( + 'country', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn dateTimeOriginal = + GeneratedColumn( + 'date_time_original', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn description = GeneratedColumn( + 'description', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn height = GeneratedColumn( + 'height', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn width = GeneratedColumn( + 'width', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn exposureTime = GeneratedColumn( + 'exposure_time', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn fNumber = GeneratedColumn( + 'f_number', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn fileSize = GeneratedColumn( + 'file_size', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn focalLength = GeneratedColumn( + 'focal_length', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn latitude = GeneratedColumn( + 'latitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn longitude = GeneratedColumn( + 'longitude', + aliasedName, + true, + type: DriftSqlType.double, + requiredDuringInsert: false, + ); + late final GeneratedColumn iso = GeneratedColumn( + 'iso', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn make = GeneratedColumn( + 'make', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn model = GeneratedColumn( + 'model', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn lens = GeneratedColumn( + 'lens', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn orientation = GeneratedColumn( + 'orientation', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn timeZone = GeneratedColumn( + 'time_zone', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn rating = GeneratedColumn( + 'rating', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn projectionType = GeneratedColumn( + 'projection_type', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_exif_entity'; + @override + Set get $primaryKey => {assetId}; + @override + RemoteExifEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteExifEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + city: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}city'], + ), + state: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}state'], + ), + country: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}country'], + ), + dateTimeOriginal: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}date_time_original'], + ), + description: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}description'], + ), + height: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}height'], + ), + width: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}width'], + ), + exposureTime: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}exposure_time'], + ), + fNumber: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}f_number'], + ), + fileSize: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}file_size'], + ), + focalLength: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}focal_length'], + ), + latitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}latitude'], + ), + longitude: attachedDatabase.typeMapping.read( + DriftSqlType.double, + data['${effectivePrefix}longitude'], + ), + iso: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}iso'], + ), + make: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}make'], + ), + model: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}model'], + ), + lens: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}lens'], + ), + orientation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}orientation'], + ), + timeZone: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}time_zone'], + ), + rating: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}rating'], + ), + projectionType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}projection_type'], + ), + ); + } + + @override + RemoteExifEntity createAlias(String alias) { + return RemoteExifEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteExifEntityData extends DataClass + implements Insertable { + final String assetId; + final String? city; + final String? state; + final String? country; + final DateTime? dateTimeOriginal; + final String? description; + final int? height; + final int? width; + final String? exposureTime; + final double? fNumber; + final int? fileSize; + final double? focalLength; + final double? latitude; + final double? longitude; + final int? iso; + final String? make; + final String? model; + final String? lens; + final String? orientation; + final String? timeZone; + final int? rating; + final String? projectionType; + const RemoteExifEntityData({ + required this.assetId, + this.city, + this.state, + this.country, + this.dateTimeOriginal, + this.description, + this.height, + this.width, + this.exposureTime, + this.fNumber, + this.fileSize, + this.focalLength, + this.latitude, + this.longitude, + this.iso, + this.make, + this.model, + this.lens, + this.orientation, + this.timeZone, + this.rating, + this.projectionType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || city != null) { + map['city'] = Variable(city); + } + if (!nullToAbsent || state != null) { + map['state'] = Variable(state); + } + if (!nullToAbsent || country != null) { + map['country'] = Variable(country); + } + if (!nullToAbsent || dateTimeOriginal != null) { + map['date_time_original'] = Variable(dateTimeOriginal); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || height != null) { + map['height'] = Variable(height); + } + if (!nullToAbsent || width != null) { + map['width'] = Variable(width); + } + if (!nullToAbsent || exposureTime != null) { + map['exposure_time'] = Variable(exposureTime); + } + if (!nullToAbsent || fNumber != null) { + map['f_number'] = Variable(fNumber); + } + if (!nullToAbsent || fileSize != null) { + map['file_size'] = Variable(fileSize); + } + if (!nullToAbsent || focalLength != null) { + map['focal_length'] = Variable(focalLength); + } + if (!nullToAbsent || latitude != null) { + map['latitude'] = Variable(latitude); + } + if (!nullToAbsent || longitude != null) { + map['longitude'] = Variable(longitude); + } + if (!nullToAbsent || iso != null) { + map['iso'] = Variable(iso); + } + if (!nullToAbsent || make != null) { + map['make'] = Variable(make); + } + if (!nullToAbsent || model != null) { + map['model'] = Variable(model); + } + if (!nullToAbsent || lens != null) { + map['lens'] = Variable(lens); + } + if (!nullToAbsent || orientation != null) { + map['orientation'] = Variable(orientation); + } + if (!nullToAbsent || timeZone != null) { + map['time_zone'] = Variable(timeZone); + } + if (!nullToAbsent || rating != null) { + map['rating'] = Variable(rating); + } + if (!nullToAbsent || projectionType != null) { + map['projection_type'] = Variable(projectionType); + } + return map; + } + + factory RemoteExifEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteExifEntityData( + assetId: serializer.fromJson(json['assetId']), + city: serializer.fromJson(json['city']), + state: serializer.fromJson(json['state']), + country: serializer.fromJson(json['country']), + dateTimeOriginal: serializer.fromJson( + json['dateTimeOriginal'], + ), + description: serializer.fromJson(json['description']), + height: serializer.fromJson(json['height']), + width: serializer.fromJson(json['width']), + exposureTime: serializer.fromJson(json['exposureTime']), + fNumber: serializer.fromJson(json['fNumber']), + fileSize: serializer.fromJson(json['fileSize']), + focalLength: serializer.fromJson(json['focalLength']), + latitude: serializer.fromJson(json['latitude']), + longitude: serializer.fromJson(json['longitude']), + iso: serializer.fromJson(json['iso']), + make: serializer.fromJson(json['make']), + model: serializer.fromJson(json['model']), + lens: serializer.fromJson(json['lens']), + orientation: serializer.fromJson(json['orientation']), + timeZone: serializer.fromJson(json['timeZone']), + rating: serializer.fromJson(json['rating']), + projectionType: serializer.fromJson(json['projectionType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'city': serializer.toJson(city), + 'state': serializer.toJson(state), + 'country': serializer.toJson(country), + 'dateTimeOriginal': serializer.toJson(dateTimeOriginal), + 'description': serializer.toJson(description), + 'height': serializer.toJson(height), + 'width': serializer.toJson(width), + 'exposureTime': serializer.toJson(exposureTime), + 'fNumber': serializer.toJson(fNumber), + 'fileSize': serializer.toJson(fileSize), + 'focalLength': serializer.toJson(focalLength), + 'latitude': serializer.toJson(latitude), + 'longitude': serializer.toJson(longitude), + 'iso': serializer.toJson(iso), + 'make': serializer.toJson(make), + 'model': serializer.toJson(model), + 'lens': serializer.toJson(lens), + 'orientation': serializer.toJson(orientation), + 'timeZone': serializer.toJson(timeZone), + 'rating': serializer.toJson(rating), + 'projectionType': serializer.toJson(projectionType), + }; + } + + RemoteExifEntityData copyWith({ + String? assetId, + Value city = const Value.absent(), + Value state = const Value.absent(), + Value country = const Value.absent(), + Value dateTimeOriginal = const Value.absent(), + Value description = const Value.absent(), + Value height = const Value.absent(), + Value width = const Value.absent(), + Value exposureTime = const Value.absent(), + Value fNumber = const Value.absent(), + Value fileSize = const Value.absent(), + Value focalLength = const Value.absent(), + Value latitude = const Value.absent(), + Value longitude = const Value.absent(), + Value iso = const Value.absent(), + Value make = const Value.absent(), + Value model = const Value.absent(), + Value lens = const Value.absent(), + Value orientation = const Value.absent(), + Value timeZone = const Value.absent(), + Value rating = const Value.absent(), + Value projectionType = const Value.absent(), + }) => RemoteExifEntityData( + assetId: assetId ?? this.assetId, + city: city.present ? city.value : this.city, + state: state.present ? state.value : this.state, + country: country.present ? country.value : this.country, + dateTimeOriginal: dateTimeOriginal.present + ? dateTimeOriginal.value + : this.dateTimeOriginal, + description: description.present ? description.value : this.description, + height: height.present ? height.value : this.height, + width: width.present ? width.value : this.width, + exposureTime: exposureTime.present ? exposureTime.value : this.exposureTime, + fNumber: fNumber.present ? fNumber.value : this.fNumber, + fileSize: fileSize.present ? fileSize.value : this.fileSize, + focalLength: focalLength.present ? focalLength.value : this.focalLength, + latitude: latitude.present ? latitude.value : this.latitude, + longitude: longitude.present ? longitude.value : this.longitude, + iso: iso.present ? iso.value : this.iso, + make: make.present ? make.value : this.make, + model: model.present ? model.value : this.model, + lens: lens.present ? lens.value : this.lens, + orientation: orientation.present ? orientation.value : this.orientation, + timeZone: timeZone.present ? timeZone.value : this.timeZone, + rating: rating.present ? rating.value : this.rating, + projectionType: projectionType.present + ? projectionType.value + : this.projectionType, + ); + RemoteExifEntityData copyWithCompanion(RemoteExifEntityCompanion data) { + return RemoteExifEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + city: data.city.present ? data.city.value : this.city, + state: data.state.present ? data.state.value : this.state, + country: data.country.present ? data.country.value : this.country, + dateTimeOriginal: data.dateTimeOriginal.present + ? data.dateTimeOriginal.value + : this.dateTimeOriginal, + description: data.description.present + ? data.description.value + : this.description, + height: data.height.present ? data.height.value : this.height, + width: data.width.present ? data.width.value : this.width, + exposureTime: data.exposureTime.present + ? data.exposureTime.value + : this.exposureTime, + fNumber: data.fNumber.present ? data.fNumber.value : this.fNumber, + fileSize: data.fileSize.present ? data.fileSize.value : this.fileSize, + focalLength: data.focalLength.present + ? data.focalLength.value + : this.focalLength, + latitude: data.latitude.present ? data.latitude.value : this.latitude, + longitude: data.longitude.present ? data.longitude.value : this.longitude, + iso: data.iso.present ? data.iso.value : this.iso, + make: data.make.present ? data.make.value : this.make, + model: data.model.present ? data.model.value : this.model, + lens: data.lens.present ? data.lens.value : this.lens, + orientation: data.orientation.present + ? data.orientation.value + : this.orientation, + timeZone: data.timeZone.present ? data.timeZone.value : this.timeZone, + rating: data.rating.present ? data.rating.value : this.rating, + projectionType: data.projectionType.present + ? data.projectionType.value + : this.projectionType, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityData(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + assetId, + city, + state, + country, + dateTimeOriginal, + description, + height, + width, + exposureTime, + fNumber, + fileSize, + focalLength, + latitude, + longitude, + iso, + make, + model, + lens, + orientation, + timeZone, + rating, + projectionType, + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteExifEntityData && + other.assetId == this.assetId && + other.city == this.city && + other.state == this.state && + other.country == this.country && + other.dateTimeOriginal == this.dateTimeOriginal && + other.description == this.description && + other.height == this.height && + other.width == this.width && + other.exposureTime == this.exposureTime && + other.fNumber == this.fNumber && + other.fileSize == this.fileSize && + other.focalLength == this.focalLength && + other.latitude == this.latitude && + other.longitude == this.longitude && + other.iso == this.iso && + other.make == this.make && + other.model == this.model && + other.lens == this.lens && + other.orientation == this.orientation && + other.timeZone == this.timeZone && + other.rating == this.rating && + other.projectionType == this.projectionType); +} + +class RemoteExifEntityCompanion extends UpdateCompanion { + final Value assetId; + final Value city; + final Value state; + final Value country; + final Value dateTimeOriginal; + final Value description; + final Value height; + final Value width; + final Value exposureTime; + final Value fNumber; + final Value fileSize; + final Value focalLength; + final Value latitude; + final Value longitude; + final Value iso; + final Value make; + final Value model; + final Value lens; + final Value orientation; + final Value timeZone; + final Value rating; + final Value projectionType; + const RemoteExifEntityCompanion({ + this.assetId = const Value.absent(), + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }); + RemoteExifEntityCompanion.insert({ + required String assetId, + this.city = const Value.absent(), + this.state = const Value.absent(), + this.country = const Value.absent(), + this.dateTimeOriginal = const Value.absent(), + this.description = const Value.absent(), + this.height = const Value.absent(), + this.width = const Value.absent(), + this.exposureTime = const Value.absent(), + this.fNumber = const Value.absent(), + this.fileSize = const Value.absent(), + this.focalLength = const Value.absent(), + this.latitude = const Value.absent(), + this.longitude = const Value.absent(), + this.iso = const Value.absent(), + this.make = const Value.absent(), + this.model = const Value.absent(), + this.lens = const Value.absent(), + this.orientation = const Value.absent(), + this.timeZone = const Value.absent(), + this.rating = const Value.absent(), + this.projectionType = const Value.absent(), + }) : assetId = Value(assetId); + static Insertable custom({ + Expression? assetId, + Expression? city, + Expression? state, + Expression? country, + Expression? dateTimeOriginal, + Expression? description, + Expression? height, + Expression? width, + Expression? exposureTime, + Expression? fNumber, + Expression? fileSize, + Expression? focalLength, + Expression? latitude, + Expression? longitude, + Expression? iso, + Expression? make, + Expression? model, + Expression? lens, + Expression? orientation, + Expression? timeZone, + Expression? rating, + Expression? projectionType, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (city != null) 'city': city, + if (state != null) 'state': state, + if (country != null) 'country': country, + if (dateTimeOriginal != null) 'date_time_original': dateTimeOriginal, + if (description != null) 'description': description, + if (height != null) 'height': height, + if (width != null) 'width': width, + if (exposureTime != null) 'exposure_time': exposureTime, + if (fNumber != null) 'f_number': fNumber, + if (fileSize != null) 'file_size': fileSize, + if (focalLength != null) 'focal_length': focalLength, + if (latitude != null) 'latitude': latitude, + if (longitude != null) 'longitude': longitude, + if (iso != null) 'iso': iso, + if (make != null) 'make': make, + if (model != null) 'model': model, + if (lens != null) 'lens': lens, + if (orientation != null) 'orientation': orientation, + if (timeZone != null) 'time_zone': timeZone, + if (rating != null) 'rating': rating, + if (projectionType != null) 'projection_type': projectionType, + }); + } + + RemoteExifEntityCompanion copyWith({ + Value? assetId, + Value? city, + Value? state, + Value? country, + Value? dateTimeOriginal, + Value? description, + Value? height, + Value? width, + Value? exposureTime, + Value? fNumber, + Value? fileSize, + Value? focalLength, + Value? latitude, + Value? longitude, + Value? iso, + Value? make, + Value? model, + Value? lens, + Value? orientation, + Value? timeZone, + Value? rating, + Value? projectionType, + }) { + return RemoteExifEntityCompanion( + assetId: assetId ?? this.assetId, + city: city ?? this.city, + state: state ?? this.state, + country: country ?? this.country, + dateTimeOriginal: dateTimeOriginal ?? this.dateTimeOriginal, + description: description ?? this.description, + height: height ?? this.height, + width: width ?? this.width, + exposureTime: exposureTime ?? this.exposureTime, + fNumber: fNumber ?? this.fNumber, + fileSize: fileSize ?? this.fileSize, + focalLength: focalLength ?? this.focalLength, + latitude: latitude ?? this.latitude, + longitude: longitude ?? this.longitude, + iso: iso ?? this.iso, + make: make ?? this.make, + model: model ?? this.model, + lens: lens ?? this.lens, + orientation: orientation ?? this.orientation, + timeZone: timeZone ?? this.timeZone, + rating: rating ?? this.rating, + projectionType: projectionType ?? this.projectionType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (city.present) { + map['city'] = Variable(city.value); + } + if (state.present) { + map['state'] = Variable(state.value); + } + if (country.present) { + map['country'] = Variable(country.value); + } + if (dateTimeOriginal.present) { + map['date_time_original'] = Variable(dateTimeOriginal.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (height.present) { + map['height'] = Variable(height.value); + } + if (width.present) { + map['width'] = Variable(width.value); + } + if (exposureTime.present) { + map['exposure_time'] = Variable(exposureTime.value); + } + if (fNumber.present) { + map['f_number'] = Variable(fNumber.value); + } + if (fileSize.present) { + map['file_size'] = Variable(fileSize.value); + } + if (focalLength.present) { + map['focal_length'] = Variable(focalLength.value); + } + if (latitude.present) { + map['latitude'] = Variable(latitude.value); + } + if (longitude.present) { + map['longitude'] = Variable(longitude.value); + } + if (iso.present) { + map['iso'] = Variable(iso.value); + } + if (make.present) { + map['make'] = Variable(make.value); + } + if (model.present) { + map['model'] = Variable(model.value); + } + if (lens.present) { + map['lens'] = Variable(lens.value); + } + if (orientation.present) { + map['orientation'] = Variable(orientation.value); + } + if (timeZone.present) { + map['time_zone'] = Variable(timeZone.value); + } + if (rating.present) { + map['rating'] = Variable(rating.value); + } + if (projectionType.present) { + map['projection_type'] = Variable(projectionType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteExifEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('city: $city, ') + ..write('state: $state, ') + ..write('country: $country, ') + ..write('dateTimeOriginal: $dateTimeOriginal, ') + ..write('description: $description, ') + ..write('height: $height, ') + ..write('width: $width, ') + ..write('exposureTime: $exposureTime, ') + ..write('fNumber: $fNumber, ') + ..write('fileSize: $fileSize, ') + ..write('focalLength: $focalLength, ') + ..write('latitude: $latitude, ') + ..write('longitude: $longitude, ') + ..write('iso: $iso, ') + ..write('make: $make, ') + ..write('model: $model, ') + ..write('lens: $lens, ') + ..write('orientation: $orientation, ') + ..write('timeZone: $timeZone, ') + ..write('rating: $rating, ') + ..write('projectionType: $projectionType') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, albumId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_asset_entity'; + @override + Set get $primaryKey => {assetId, albumId}; + @override + RemoteAlbumAssetEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + ); + } + + @override + RemoteAlbumAssetEntity createAlias(String alias) { + return RemoteAlbumAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String albumId; + const RemoteAlbumAssetEntityData({ + required this.assetId, + required this.albumId, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['album_id'] = Variable(albumId); + return map; + } + + factory RemoteAlbumAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + albumId: serializer.fromJson(json['albumId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'albumId': serializer.toJson(albumId), + }; + } + + RemoteAlbumAssetEntityData copyWith({String? assetId, String? albumId}) => + RemoteAlbumAssetEntityData( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + RemoteAlbumAssetEntityData copyWithCompanion( + RemoteAlbumAssetEntityCompanion data, + ) { + return RemoteAlbumAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + albumId: data.albumId.present ? data.albumId.value : this.albumId, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, albumId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumAssetEntityData && + other.assetId == this.assetId && + other.albumId == this.albumId); +} + +class RemoteAlbumAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value albumId; + const RemoteAlbumAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.albumId = const Value.absent(), + }); + RemoteAlbumAssetEntityCompanion.insert({ + required String assetId, + required String albumId, + }) : assetId = Value(assetId), + albumId = Value(albumId); + static Insertable custom({ + Expression? assetId, + Expression? albumId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (albumId != null) 'album_id': albumId, + }); + } + + RemoteAlbumAssetEntityCompanion copyWith({ + Value? assetId, + Value? albumId, + }) { + return RemoteAlbumAssetEntityCompanion( + assetId: assetId ?? this.assetId, + albumId: albumId ?? this.albumId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('albumId: $albumId') + ..write(')')) + .toString(); + } +} + +class RemoteAlbumUserEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + RemoteAlbumUserEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn albumId = GeneratedColumn( + 'album_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_album_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn role = GeneratedColumn( + 'role', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [albumId, userId, role]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'remote_album_user_entity'; + @override + Set get $primaryKey => {albumId, userId}; + @override + RemoteAlbumUserEntityData map( + Map data, { + String? tablePrefix, + }) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RemoteAlbumUserEntityData( + albumId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}album_id'], + )!, + userId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}user_id'], + )!, + role: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}role'], + )!, + ); + } + + @override + RemoteAlbumUserEntity createAlias(String alias) { + return RemoteAlbumUserEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class RemoteAlbumUserEntityData extends DataClass + implements Insertable { + final String albumId; + final String userId; + final int role; + const RemoteAlbumUserEntityData({ + required this.albumId, + required this.userId, + required this.role, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['album_id'] = Variable(albumId); + map['user_id'] = Variable(userId); + map['role'] = Variable(role); + return map; + } + + factory RemoteAlbumUserEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RemoteAlbumUserEntityData( + albumId: serializer.fromJson(json['albumId']), + userId: serializer.fromJson(json['userId']), + role: serializer.fromJson(json['role']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'albumId': serializer.toJson(albumId), + 'userId': serializer.toJson(userId), + 'role': serializer.toJson(role), + }; + } + + RemoteAlbumUserEntityData copyWith({ + String? albumId, + String? userId, + int? role, + }) => RemoteAlbumUserEntityData( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + RemoteAlbumUserEntityData copyWithCompanion( + RemoteAlbumUserEntityCompanion data, + ) { + return RemoteAlbumUserEntityData( + albumId: data.albumId.present ? data.albumId.value : this.albumId, + userId: data.userId.present ? data.userId.value : this.userId, + role: data.role.present ? data.role.value : this.role, + ); + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityData(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(albumId, userId, role); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RemoteAlbumUserEntityData && + other.albumId == this.albumId && + other.userId == this.userId && + other.role == this.role); +} + +class RemoteAlbumUserEntityCompanion + extends UpdateCompanion { + final Value albumId; + final Value userId; + final Value role; + const RemoteAlbumUserEntityCompanion({ + this.albumId = const Value.absent(), + this.userId = const Value.absent(), + this.role = const Value.absent(), + }); + RemoteAlbumUserEntityCompanion.insert({ + required String albumId, + required String userId, + required int role, + }) : albumId = Value(albumId), + userId = Value(userId), + role = Value(role); + static Insertable custom({ + Expression? albumId, + Expression? userId, + Expression? role, + }) { + return RawValuesInsertable({ + if (albumId != null) 'album_id': albumId, + if (userId != null) 'user_id': userId, + if (role != null) 'role': role, + }); + } + + RemoteAlbumUserEntityCompanion copyWith({ + Value? albumId, + Value? userId, + Value? role, + }) { + return RemoteAlbumUserEntityCompanion( + albumId: albumId ?? this.albumId, + userId: userId ?? this.userId, + role: role ?? this.role, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (albumId.present) { + map['album_id'] = Variable(albumId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (role.present) { + map['role'] = Variable(role.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RemoteAlbumUserEntityCompanion(') + ..write('albumId: $albumId, ') + ..write('userId: $userId, ') + ..write('role: $role') + ..write(')')) + .toString(); + } +} + +class MemoryEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn deletedAt = GeneratedColumn( + 'deleted_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn type = GeneratedColumn( + 'type', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn data = GeneratedColumn( + 'data', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn isSaved = GeneratedColumn( + 'is_saved', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_saved" IN (0, 1))', + ), + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn memoryAt = GeneratedColumn( + 'memory_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + late final GeneratedColumn seenAt = GeneratedColumn( + 'seen_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn showAt = GeneratedColumn( + 'show_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + late final GeneratedColumn hideAt = GeneratedColumn( + 'hide_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_entity'; + @override + Set get $primaryKey => {id}; + @override + MemoryEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + deletedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}deleted_at'], + ), + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + type: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}type'], + )!, + data: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}data'], + )!, + isSaved: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_saved'], + )!, + memoryAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}memory_at'], + )!, + seenAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}seen_at'], + ), + showAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}show_at'], + ), + hideAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}hide_at'], + ), + ); + } + + @override + MemoryEntity createAlias(String alias) { + return MemoryEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final DateTime? deletedAt; + final String ownerId; + final int type; + final String data; + final bool isSaved; + final DateTime memoryAt; + final DateTime? seenAt; + final DateTime? showAt; + final DateTime? hideAt; + const MemoryEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + this.deletedAt, + required this.ownerId, + required this.type, + required this.data, + required this.isSaved, + required this.memoryAt, + this.seenAt, + this.showAt, + this.hideAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + if (!nullToAbsent || deletedAt != null) { + map['deleted_at'] = Variable(deletedAt); + } + map['owner_id'] = Variable(ownerId); + map['type'] = Variable(type); + map['data'] = Variable(data); + map['is_saved'] = Variable(isSaved); + map['memory_at'] = Variable(memoryAt); + if (!nullToAbsent || seenAt != null) { + map['seen_at'] = Variable(seenAt); + } + if (!nullToAbsent || showAt != null) { + map['show_at'] = Variable(showAt); + } + if (!nullToAbsent || hideAt != null) { + map['hide_at'] = Variable(hideAt); + } + return map; + } + + factory MemoryEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + deletedAt: serializer.fromJson(json['deletedAt']), + ownerId: serializer.fromJson(json['ownerId']), + type: serializer.fromJson(json['type']), + data: serializer.fromJson(json['data']), + isSaved: serializer.fromJson(json['isSaved']), + memoryAt: serializer.fromJson(json['memoryAt']), + seenAt: serializer.fromJson(json['seenAt']), + showAt: serializer.fromJson(json['showAt']), + hideAt: serializer.fromJson(json['hideAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'deletedAt': serializer.toJson(deletedAt), + 'ownerId': serializer.toJson(ownerId), + 'type': serializer.toJson(type), + 'data': serializer.toJson(data), + 'isSaved': serializer.toJson(isSaved), + 'memoryAt': serializer.toJson(memoryAt), + 'seenAt': serializer.toJson(seenAt), + 'showAt': serializer.toJson(showAt), + 'hideAt': serializer.toJson(hideAt), + }; + } + + MemoryEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + Value deletedAt = const Value.absent(), + String? ownerId, + int? type, + String? data, + bool? isSaved, + DateTime? memoryAt, + Value seenAt = const Value.absent(), + Value showAt = const Value.absent(), + Value hideAt = const Value.absent(), + }) => MemoryEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt.present ? deletedAt.value : this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt.present ? seenAt.value : this.seenAt, + showAt: showAt.present ? showAt.value : this.showAt, + hideAt: hideAt.present ? hideAt.value : this.hideAt, + ); + MemoryEntityData copyWithCompanion(MemoryEntityCompanion data) { + return MemoryEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + deletedAt: data.deletedAt.present ? data.deletedAt.value : this.deletedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + type: data.type.present ? data.type.value : this.type, + data: data.data.present ? data.data.value : this.data, + isSaved: data.isSaved.present ? data.isSaved.value : this.isSaved, + memoryAt: data.memoryAt.present ? data.memoryAt.value : this.memoryAt, + seenAt: data.seenAt.present ? data.seenAt.value : this.seenAt, + showAt: data.showAt.present ? data.showAt.value : this.showAt, + hideAt: data.hideAt.present ? data.hideAt.value : this.hideAt, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + deletedAt, + ownerId, + type, + data, + isSaved, + memoryAt, + seenAt, + showAt, + hideAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.deletedAt == this.deletedAt && + other.ownerId == this.ownerId && + other.type == this.type && + other.data == this.data && + other.isSaved == this.isSaved && + other.memoryAt == this.memoryAt && + other.seenAt == this.seenAt && + other.showAt == this.showAt && + other.hideAt == this.hideAt); +} + +class MemoryEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value deletedAt; + final Value ownerId; + final Value type; + final Value data; + final Value isSaved; + final Value memoryAt; + final Value seenAt; + final Value showAt; + final Value hideAt; + const MemoryEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.type = const Value.absent(), + this.data = const Value.absent(), + this.isSaved = const Value.absent(), + this.memoryAt = const Value.absent(), + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }); + MemoryEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.deletedAt = const Value.absent(), + required String ownerId, + required int type, + required String data, + this.isSaved = const Value.absent(), + required DateTime memoryAt, + this.seenAt = const Value.absent(), + this.showAt = const Value.absent(), + this.hideAt = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + type = Value(type), + data = Value(data), + memoryAt = Value(memoryAt); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? deletedAt, + Expression? ownerId, + Expression? type, + Expression? data, + Expression? isSaved, + Expression? memoryAt, + Expression? seenAt, + Expression? showAt, + Expression? hideAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (deletedAt != null) 'deleted_at': deletedAt, + if (ownerId != null) 'owner_id': ownerId, + if (type != null) 'type': type, + if (data != null) 'data': data, + if (isSaved != null) 'is_saved': isSaved, + if (memoryAt != null) 'memory_at': memoryAt, + if (seenAt != null) 'seen_at': seenAt, + if (showAt != null) 'show_at': showAt, + if (hideAt != null) 'hide_at': hideAt, + }); + } + + MemoryEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? deletedAt, + Value? ownerId, + Value? type, + Value? data, + Value? isSaved, + Value? memoryAt, + Value? seenAt, + Value? showAt, + Value? hideAt, + }) { + return MemoryEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + deletedAt: deletedAt ?? this.deletedAt, + ownerId: ownerId ?? this.ownerId, + type: type ?? this.type, + data: data ?? this.data, + isSaved: isSaved ?? this.isSaved, + memoryAt: memoryAt ?? this.memoryAt, + seenAt: seenAt ?? this.seenAt, + showAt: showAt ?? this.showAt, + hideAt: hideAt ?? this.hideAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (deletedAt.present) { + map['deleted_at'] = Variable(deletedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + if (isSaved.present) { + map['is_saved'] = Variable(isSaved.value); + } + if (memoryAt.present) { + map['memory_at'] = Variable(memoryAt.value); + } + if (seenAt.present) { + map['seen_at'] = Variable(seenAt.value); + } + if (showAt.present) { + map['show_at'] = Variable(showAt.value); + } + if (hideAt.present) { + map['hide_at'] = Variable(hideAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('deletedAt: $deletedAt, ') + ..write('ownerId: $ownerId, ') + ..write('type: $type, ') + ..write('data: $data, ') + ..write('isSaved: $isSaved, ') + ..write('memoryAt: $memoryAt, ') + ..write('seenAt: $seenAt, ') + ..write('showAt: $showAt, ') + ..write('hideAt: $hideAt') + ..write(')')) + .toString(); + } +} + +class MemoryAssetEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MemoryAssetEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn memoryId = GeneratedColumn( + 'memory_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES memory_entity (id) ON DELETE CASCADE', + ), + ); + @override + List get $columns => [assetId, memoryId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'memory_asset_entity'; + @override + Set get $primaryKey => {assetId, memoryId}; + @override + MemoryAssetEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MemoryAssetEntityData( + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + memoryId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}memory_id'], + )!, + ); + } + + @override + MemoryAssetEntity createAlias(String alias) { + return MemoryAssetEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class MemoryAssetEntityData extends DataClass + implements Insertable { + final String assetId; + final String memoryId; + const MemoryAssetEntityData({required this.assetId, required this.memoryId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['asset_id'] = Variable(assetId); + map['memory_id'] = Variable(memoryId); + return map; + } + + factory MemoryAssetEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MemoryAssetEntityData( + assetId: serializer.fromJson(json['assetId']), + memoryId: serializer.fromJson(json['memoryId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'assetId': serializer.toJson(assetId), + 'memoryId': serializer.toJson(memoryId), + }; + } + + MemoryAssetEntityData copyWith({String? assetId, String? memoryId}) => + MemoryAssetEntityData( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + MemoryAssetEntityData copyWithCompanion(MemoryAssetEntityCompanion data) { + return MemoryAssetEntityData( + assetId: data.assetId.present ? data.assetId.value : this.assetId, + memoryId: data.memoryId.present ? data.memoryId.value : this.memoryId, + ); + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityData(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(assetId, memoryId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MemoryAssetEntityData && + other.assetId == this.assetId && + other.memoryId == this.memoryId); +} + +class MemoryAssetEntityCompanion + extends UpdateCompanion { + final Value assetId; + final Value memoryId; + const MemoryAssetEntityCompanion({ + this.assetId = const Value.absent(), + this.memoryId = const Value.absent(), + }); + MemoryAssetEntityCompanion.insert({ + required String assetId, + required String memoryId, + }) : assetId = Value(assetId), + memoryId = Value(memoryId); + static Insertable custom({ + Expression? assetId, + Expression? memoryId, + }) { + return RawValuesInsertable({ + if (assetId != null) 'asset_id': assetId, + if (memoryId != null) 'memory_id': memoryId, + }); + } + + MemoryAssetEntityCompanion copyWith({ + Value? assetId, + Value? memoryId, + }) { + return MemoryAssetEntityCompanion( + assetId: assetId ?? this.assetId, + memoryId: memoryId ?? this.memoryId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (memoryId.present) { + map['memory_id'] = Variable(memoryId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MemoryAssetEntityCompanion(') + ..write('assetId: $assetId, ') + ..write('memoryId: $memoryId') + ..write(')')) + .toString(); + } +} + +class PersonEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PersonEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: const CustomExpression('CURRENT_TIMESTAMP'), + ); + late final GeneratedColumn ownerId = GeneratedColumn( + 'owner_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES user_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn faceAssetId = GeneratedColumn( + 'face_asset_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn isFavorite = GeneratedColumn( + 'is_favorite', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_favorite" IN (0, 1))', + ), + ); + late final GeneratedColumn isHidden = GeneratedColumn( + 'is_hidden', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_hidden" IN (0, 1))', + ), + ); + late final GeneratedColumn color = GeneratedColumn( + 'color', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn birthDate = GeneratedColumn( + 'birth_date', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'person_entity'; + @override + Set get $primaryKey => {id}; + @override + PersonEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PersonEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + updatedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}updated_at'], + )!, + ownerId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}owner_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + faceAssetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}face_asset_id'], + ), + isFavorite: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_favorite'], + )!, + isHidden: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_hidden'], + )!, + color: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}color'], + ), + birthDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}birth_date'], + ), + ); + } + + @override + PersonEntity createAlias(String alias) { + return PersonEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class PersonEntityData extends DataClass + implements Insertable { + final String id; + final DateTime createdAt; + final DateTime updatedAt; + final String ownerId; + final String name; + final String? faceAssetId; + final bool isFavorite; + final bool isHidden; + final String? color; + final DateTime? birthDate; + const PersonEntityData({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.ownerId, + required this.name, + this.faceAssetId, + required this.isFavorite, + required this.isHidden, + this.color, + this.birthDate, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['owner_id'] = Variable(ownerId); + map['name'] = Variable(name); + if (!nullToAbsent || faceAssetId != null) { + map['face_asset_id'] = Variable(faceAssetId); + } + map['is_favorite'] = Variable(isFavorite); + map['is_hidden'] = Variable(isHidden); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || birthDate != null) { + map['birth_date'] = Variable(birthDate); + } + return map; + } + + factory PersonEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PersonEntityData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ownerId: serializer.fromJson(json['ownerId']), + name: serializer.fromJson(json['name']), + faceAssetId: serializer.fromJson(json['faceAssetId']), + isFavorite: serializer.fromJson(json['isFavorite']), + isHidden: serializer.fromJson(json['isHidden']), + color: serializer.fromJson(json['color']), + birthDate: serializer.fromJson(json['birthDate']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'ownerId': serializer.toJson(ownerId), + 'name': serializer.toJson(name), + 'faceAssetId': serializer.toJson(faceAssetId), + 'isFavorite': serializer.toJson(isFavorite), + 'isHidden': serializer.toJson(isHidden), + 'color': serializer.toJson(color), + 'birthDate': serializer.toJson(birthDate), + }; + } + + PersonEntityData copyWith({ + String? id, + DateTime? createdAt, + DateTime? updatedAt, + String? ownerId, + String? name, + Value faceAssetId = const Value.absent(), + bool? isFavorite, + bool? isHidden, + Value color = const Value.absent(), + Value birthDate = const Value.absent(), + }) => PersonEntityData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId.present ? faceAssetId.value : this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color.present ? color.value : this.color, + birthDate: birthDate.present ? birthDate.value : this.birthDate, + ); + PersonEntityData copyWithCompanion(PersonEntityCompanion data) { + return PersonEntityData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt, + ownerId: data.ownerId.present ? data.ownerId.value : this.ownerId, + name: data.name.present ? data.name.value : this.name, + faceAssetId: data.faceAssetId.present + ? data.faceAssetId.value + : this.faceAssetId, + isFavorite: data.isFavorite.present + ? data.isFavorite.value + : this.isFavorite, + isHidden: data.isHidden.present ? data.isHidden.value : this.isHidden, + color: data.color.present ? data.color.value : this.color, + birthDate: data.birthDate.present ? data.birthDate.value : this.birthDate, + ); + } + + @override + String toString() { + return (StringBuffer('PersonEntityData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + createdAt, + updatedAt, + ownerId, + name, + faceAssetId, + isFavorite, + isHidden, + color, + birthDate, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PersonEntityData && + other.id == this.id && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.ownerId == this.ownerId && + other.name == this.name && + other.faceAssetId == this.faceAssetId && + other.isFavorite == this.isFavorite && + other.isHidden == this.isHidden && + other.color == this.color && + other.birthDate == this.birthDate); +} + +class PersonEntityCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value updatedAt; + final Value ownerId; + final Value name; + final Value faceAssetId; + final Value isFavorite; + final Value isHidden; + final Value color; + final Value birthDate; + const PersonEntityCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.ownerId = const Value.absent(), + this.name = const Value.absent(), + this.faceAssetId = const Value.absent(), + this.isFavorite = const Value.absent(), + this.isHidden = const Value.absent(), + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }); + PersonEntityCompanion.insert({ + required String id, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + required String ownerId, + required String name, + this.faceAssetId = const Value.absent(), + required bool isFavorite, + required bool isHidden, + this.color = const Value.absent(), + this.birthDate = const Value.absent(), + }) : id = Value(id), + ownerId = Value(ownerId), + name = Value(name), + isFavorite = Value(isFavorite), + isHidden = Value(isHidden); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? updatedAt, + Expression? ownerId, + Expression? name, + Expression? faceAssetId, + Expression? isFavorite, + Expression? isHidden, + Expression? color, + Expression? birthDate, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (ownerId != null) 'owner_id': ownerId, + if (name != null) 'name': name, + if (faceAssetId != null) 'face_asset_id': faceAssetId, + if (isFavorite != null) 'is_favorite': isFavorite, + if (isHidden != null) 'is_hidden': isHidden, + if (color != null) 'color': color, + if (birthDate != null) 'birth_date': birthDate, + }); + } + + PersonEntityCompanion copyWith({ + Value? id, + Value? createdAt, + Value? updatedAt, + Value? ownerId, + Value? name, + Value? faceAssetId, + Value? isFavorite, + Value? isHidden, + Value? color, + Value? birthDate, + }) { + return PersonEntityCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ownerId: ownerId ?? this.ownerId, + name: name ?? this.name, + faceAssetId: faceAssetId ?? this.faceAssetId, + isFavorite: isFavorite ?? this.isFavorite, + isHidden: isHidden ?? this.isHidden, + color: color ?? this.color, + birthDate: birthDate ?? this.birthDate, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (ownerId.present) { + map['owner_id'] = Variable(ownerId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (faceAssetId.present) { + map['face_asset_id'] = Variable(faceAssetId.value); + } + if (isFavorite.present) { + map['is_favorite'] = Variable(isFavorite.value); + } + if (isHidden.present) { + map['is_hidden'] = Variable(isHidden.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (birthDate.present) { + map['birth_date'] = Variable(birthDate.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PersonEntityCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('ownerId: $ownerId, ') + ..write('name: $name, ') + ..write('faceAssetId: $faceAssetId, ') + ..write('isFavorite: $isFavorite, ') + ..write('isHidden: $isHidden, ') + ..write('color: $color, ') + ..write('birthDate: $birthDate') + ..write(')')) + .toString(); + } +} + +class AssetFaceEntity extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AssetFaceEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn assetId = GeneratedColumn( + 'asset_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES remote_asset_entity (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn personId = GeneratedColumn( + 'person_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES person_entity (id) ON DELETE SET NULL', + ), + ); + late final GeneratedColumn imageWidth = GeneratedColumn( + 'image_width', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn imageHeight = GeneratedColumn( + 'image_height', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX1 = GeneratedColumn( + 'bounding_box_x1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY1 = GeneratedColumn( + 'bounding_box_y1', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxX2 = GeneratedColumn( + 'bounding_box_x2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn boundingBoxY2 = GeneratedColumn( + 'bounding_box_y2', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'asset_face_entity'; + @override + Set get $primaryKey => {id}; + @override + AssetFaceEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AssetFaceEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + assetId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}asset_id'], + )!, + personId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}person_id'], + ), + imageWidth: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_width'], + )!, + imageHeight: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}image_height'], + )!, + boundingBoxX1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x1'], + )!, + boundingBoxY1: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y1'], + )!, + boundingBoxX2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_x2'], + )!, + boundingBoxY2: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}bounding_box_y2'], + )!, + sourceType: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}source_type'], + )!, + ); + } + + @override + AssetFaceEntity createAlias(String alias) { + return AssetFaceEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class AssetFaceEntityData extends DataClass + implements Insertable { + final String id; + final String assetId; + final String? personId; + final int imageWidth; + final int imageHeight; + final int boundingBoxX1; + final int boundingBoxY1; + final int boundingBoxX2; + final int boundingBoxY2; + final String sourceType; + const AssetFaceEntityData({ + required this.id, + required this.assetId, + this.personId, + required this.imageWidth, + required this.imageHeight, + required this.boundingBoxX1, + required this.boundingBoxY1, + required this.boundingBoxX2, + required this.boundingBoxY2, + required this.sourceType, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['asset_id'] = Variable(assetId); + if (!nullToAbsent || personId != null) { + map['person_id'] = Variable(personId); + } + map['image_width'] = Variable(imageWidth); + map['image_height'] = Variable(imageHeight); + map['bounding_box_x1'] = Variable(boundingBoxX1); + map['bounding_box_y1'] = Variable(boundingBoxY1); + map['bounding_box_x2'] = Variable(boundingBoxX2); + map['bounding_box_y2'] = Variable(boundingBoxY2); + map['source_type'] = Variable(sourceType); + return map; + } + + factory AssetFaceEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AssetFaceEntityData( + id: serializer.fromJson(json['id']), + assetId: serializer.fromJson(json['assetId']), + personId: serializer.fromJson(json['personId']), + imageWidth: serializer.fromJson(json['imageWidth']), + imageHeight: serializer.fromJson(json['imageHeight']), + boundingBoxX1: serializer.fromJson(json['boundingBoxX1']), + boundingBoxY1: serializer.fromJson(json['boundingBoxY1']), + boundingBoxX2: serializer.fromJson(json['boundingBoxX2']), + boundingBoxY2: serializer.fromJson(json['boundingBoxY2']), + sourceType: serializer.fromJson(json['sourceType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'assetId': serializer.toJson(assetId), + 'personId': serializer.toJson(personId), + 'imageWidth': serializer.toJson(imageWidth), + 'imageHeight': serializer.toJson(imageHeight), + 'boundingBoxX1': serializer.toJson(boundingBoxX1), + 'boundingBoxY1': serializer.toJson(boundingBoxY1), + 'boundingBoxX2': serializer.toJson(boundingBoxX2), + 'boundingBoxY2': serializer.toJson(boundingBoxY2), + 'sourceType': serializer.toJson(sourceType), + }; + } + + AssetFaceEntityData copyWith({ + String? id, + String? assetId, + Value personId = const Value.absent(), + int? imageWidth, + int? imageHeight, + int? boundingBoxX1, + int? boundingBoxY1, + int? boundingBoxX2, + int? boundingBoxY2, + String? sourceType, + }) => AssetFaceEntityData( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId.present ? personId.value : this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + AssetFaceEntityData copyWithCompanion(AssetFaceEntityCompanion data) { + return AssetFaceEntityData( + id: data.id.present ? data.id.value : this.id, + assetId: data.assetId.present ? data.assetId.value : this.assetId, + personId: data.personId.present ? data.personId.value : this.personId, + imageWidth: data.imageWidth.present + ? data.imageWidth.value + : this.imageWidth, + imageHeight: data.imageHeight.present + ? data.imageHeight.value + : this.imageHeight, + boundingBoxX1: data.boundingBoxX1.present + ? data.boundingBoxX1.value + : this.boundingBoxX1, + boundingBoxY1: data.boundingBoxY1.present + ? data.boundingBoxY1.value + : this.boundingBoxY1, + boundingBoxX2: data.boundingBoxX2.present + ? data.boundingBoxX2.value + : this.boundingBoxX2, + boundingBoxY2: data.boundingBoxY2.present + ? data.boundingBoxY2.value + : this.boundingBoxY2, + sourceType: data.sourceType.present + ? data.sourceType.value + : this.sourceType, + ); + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityData(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + assetId, + personId, + imageWidth, + imageHeight, + boundingBoxX1, + boundingBoxY1, + boundingBoxX2, + boundingBoxY2, + sourceType, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AssetFaceEntityData && + other.id == this.id && + other.assetId == this.assetId && + other.personId == this.personId && + other.imageWidth == this.imageWidth && + other.imageHeight == this.imageHeight && + other.boundingBoxX1 == this.boundingBoxX1 && + other.boundingBoxY1 == this.boundingBoxY1 && + other.boundingBoxX2 == this.boundingBoxX2 && + other.boundingBoxY2 == this.boundingBoxY2 && + other.sourceType == this.sourceType); +} + +class AssetFaceEntityCompanion extends UpdateCompanion { + final Value id; + final Value assetId; + final Value personId; + final Value imageWidth; + final Value imageHeight; + final Value boundingBoxX1; + final Value boundingBoxY1; + final Value boundingBoxX2; + final Value boundingBoxY2; + final Value sourceType; + const AssetFaceEntityCompanion({ + this.id = const Value.absent(), + this.assetId = const Value.absent(), + this.personId = const Value.absent(), + this.imageWidth = const Value.absent(), + this.imageHeight = const Value.absent(), + this.boundingBoxX1 = const Value.absent(), + this.boundingBoxY1 = const Value.absent(), + this.boundingBoxX2 = const Value.absent(), + this.boundingBoxY2 = const Value.absent(), + this.sourceType = const Value.absent(), + }); + AssetFaceEntityCompanion.insert({ + required String id, + required String assetId, + this.personId = const Value.absent(), + required int imageWidth, + required int imageHeight, + required int boundingBoxX1, + required int boundingBoxY1, + required int boundingBoxX2, + required int boundingBoxY2, + required String sourceType, + }) : id = Value(id), + assetId = Value(assetId), + imageWidth = Value(imageWidth), + imageHeight = Value(imageHeight), + boundingBoxX1 = Value(boundingBoxX1), + boundingBoxY1 = Value(boundingBoxY1), + boundingBoxX2 = Value(boundingBoxX2), + boundingBoxY2 = Value(boundingBoxY2), + sourceType = Value(sourceType); + static Insertable custom({ + Expression? id, + Expression? assetId, + Expression? personId, + Expression? imageWidth, + Expression? imageHeight, + Expression? boundingBoxX1, + Expression? boundingBoxY1, + Expression? boundingBoxX2, + Expression? boundingBoxY2, + Expression? sourceType, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (assetId != null) 'asset_id': assetId, + if (personId != null) 'person_id': personId, + if (imageWidth != null) 'image_width': imageWidth, + if (imageHeight != null) 'image_height': imageHeight, + if (boundingBoxX1 != null) 'bounding_box_x1': boundingBoxX1, + if (boundingBoxY1 != null) 'bounding_box_y1': boundingBoxY1, + if (boundingBoxX2 != null) 'bounding_box_x2': boundingBoxX2, + if (boundingBoxY2 != null) 'bounding_box_y2': boundingBoxY2, + if (sourceType != null) 'source_type': sourceType, + }); + } + + AssetFaceEntityCompanion copyWith({ + Value? id, + Value? assetId, + Value? personId, + Value? imageWidth, + Value? imageHeight, + Value? boundingBoxX1, + Value? boundingBoxY1, + Value? boundingBoxX2, + Value? boundingBoxY2, + Value? sourceType, + }) { + return AssetFaceEntityCompanion( + id: id ?? this.id, + assetId: assetId ?? this.assetId, + personId: personId ?? this.personId, + imageWidth: imageWidth ?? this.imageWidth, + imageHeight: imageHeight ?? this.imageHeight, + boundingBoxX1: boundingBoxX1 ?? this.boundingBoxX1, + boundingBoxY1: boundingBoxY1 ?? this.boundingBoxY1, + boundingBoxX2: boundingBoxX2 ?? this.boundingBoxX2, + boundingBoxY2: boundingBoxY2 ?? this.boundingBoxY2, + sourceType: sourceType ?? this.sourceType, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (assetId.present) { + map['asset_id'] = Variable(assetId.value); + } + if (personId.present) { + map['person_id'] = Variable(personId.value); + } + if (imageWidth.present) { + map['image_width'] = Variable(imageWidth.value); + } + if (imageHeight.present) { + map['image_height'] = Variable(imageHeight.value); + } + if (boundingBoxX1.present) { + map['bounding_box_x1'] = Variable(boundingBoxX1.value); + } + if (boundingBoxY1.present) { + map['bounding_box_y1'] = Variable(boundingBoxY1.value); + } + if (boundingBoxX2.present) { + map['bounding_box_x2'] = Variable(boundingBoxX2.value); + } + if (boundingBoxY2.present) { + map['bounding_box_y2'] = Variable(boundingBoxY2.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AssetFaceEntityCompanion(') + ..write('id: $id, ') + ..write('assetId: $assetId, ') + ..write('personId: $personId, ') + ..write('imageWidth: $imageWidth, ') + ..write('imageHeight: $imageHeight, ') + ..write('boundingBoxX1: $boundingBoxX1, ') + ..write('boundingBoxY1: $boundingBoxY1, ') + ..write('boundingBoxX2: $boundingBoxX2, ') + ..write('boundingBoxY2: $boundingBoxY2, ') + ..write('sourceType: $sourceType') + ..write(')')) + .toString(); + } +} + +class StoreEntity extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + StoreEntity(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn stringValue = GeneratedColumn( + 'string_value', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn intValue = GeneratedColumn( + 'int_value', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [id, stringValue, intValue]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'store_entity'; + @override + Set get $primaryKey => {id}; + @override + StoreEntityData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StoreEntityData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + stringValue: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}string_value'], + ), + intValue: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}int_value'], + ), + ); + } + + @override + StoreEntity createAlias(String alias) { + return StoreEntity(attachedDatabase, alias); + } + + @override + bool get withoutRowId => true; + @override + bool get isStrict => true; +} + +class StoreEntityData extends DataClass implements Insertable { + final int id; + final String? stringValue; + final int? intValue; + const StoreEntityData({required this.id, this.stringValue, this.intValue}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || stringValue != null) { + map['string_value'] = Variable(stringValue); + } + if (!nullToAbsent || intValue != null) { + map['int_value'] = Variable(intValue); + } + return map; + } + + factory StoreEntityData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StoreEntityData( + id: serializer.fromJson(json['id']), + stringValue: serializer.fromJson(json['stringValue']), + intValue: serializer.fromJson(json['intValue']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'stringValue': serializer.toJson(stringValue), + 'intValue': serializer.toJson(intValue), + }; + } + + StoreEntityData copyWith({ + int? id, + Value stringValue = const Value.absent(), + Value intValue = const Value.absent(), + }) => StoreEntityData( + id: id ?? this.id, + stringValue: stringValue.present ? stringValue.value : this.stringValue, + intValue: intValue.present ? intValue.value : this.intValue, + ); + StoreEntityData copyWithCompanion(StoreEntityCompanion data) { + return StoreEntityData( + id: data.id.present ? data.id.value : this.id, + stringValue: data.stringValue.present + ? data.stringValue.value + : this.stringValue, + intValue: data.intValue.present ? data.intValue.value : this.intValue, + ); + } + + @override + String toString() { + return (StringBuffer('StoreEntityData(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, stringValue, intValue); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StoreEntityData && + other.id == this.id && + other.stringValue == this.stringValue && + other.intValue == this.intValue); +} + +class StoreEntityCompanion extends UpdateCompanion { + final Value id; + final Value stringValue; + final Value intValue; + const StoreEntityCompanion({ + this.id = const Value.absent(), + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }); + StoreEntityCompanion.insert({ + required int id, + this.stringValue = const Value.absent(), + this.intValue = const Value.absent(), + }) : id = Value(id); + static Insertable custom({ + Expression? id, + Expression? stringValue, + Expression? intValue, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (stringValue != null) 'string_value': stringValue, + if (intValue != null) 'int_value': intValue, + }); + } + + StoreEntityCompanion copyWith({ + Value? id, + Value? stringValue, + Value? intValue, + }) { + return StoreEntityCompanion( + id: id ?? this.id, + stringValue: stringValue ?? this.stringValue, + intValue: intValue ?? this.intValue, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (stringValue.present) { + map['string_value'] = Variable(stringValue.value); + } + if (intValue.present) { + map['int_value'] = Variable(intValue.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StoreEntityCompanion(') + ..write('id: $id, ') + ..write('stringValue: $stringValue, ') + ..write('intValue: $intValue') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV9 extends GeneratedDatabase { + DatabaseAtV9(QueryExecutor e) : super(e); + late final UserEntity userEntity = UserEntity(this); + late final RemoteAssetEntity remoteAssetEntity = RemoteAssetEntity(this); + late final StackEntity stackEntity = StackEntity(this); + late final LocalAssetEntity localAssetEntity = LocalAssetEntity(this); + late final RemoteAlbumEntity remoteAlbumEntity = RemoteAlbumEntity(this); + late final LocalAlbumEntity localAlbumEntity = LocalAlbumEntity(this); + late final LocalAlbumAssetEntity localAlbumAssetEntity = + LocalAlbumAssetEntity(this); + late final Index idxLocalAssetChecksum = Index( + 'idx_local_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)', + ); + late final Index idxRemoteAssetOwnerChecksum = Index( + 'idx_remote_asset_owner_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)', + ); + late final Index uQRemoteAssetsOwnerChecksum = Index( + 'UQ_remote_assets_owner_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)', + ); + late final Index uQRemoteAssetsOwnerLibraryChecksum = Index( + 'UQ_remote_assets_owner_library_checksum', + 'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)', + ); + late final Index idxRemoteAssetChecksum = Index( + 'idx_remote_asset_checksum', + 'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)', + ); + late final UserMetadataEntity userMetadataEntity = UserMetadataEntity(this); + late final PartnerEntity partnerEntity = PartnerEntity(this); + late final RemoteExifEntity remoteExifEntity = RemoteExifEntity(this); + late final RemoteAlbumAssetEntity remoteAlbumAssetEntity = + RemoteAlbumAssetEntity(this); + late final RemoteAlbumUserEntity remoteAlbumUserEntity = + RemoteAlbumUserEntity(this); + late final MemoryEntity memoryEntity = MemoryEntity(this); + late final MemoryAssetEntity memoryAssetEntity = MemoryAssetEntity(this); + late final PersonEntity personEntity = PersonEntity(this); + late final AssetFaceEntity assetFaceEntity = AssetFaceEntity(this); + late final StoreEntity storeEntity = StoreEntity(this); + late final Index idxLatLng = Index( + 'idx_lat_lng', + 'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)', + ); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + userEntity, + remoteAssetEntity, + stackEntity, + localAssetEntity, + remoteAlbumEntity, + localAlbumEntity, + localAlbumAssetEntity, + idxLocalAssetChecksum, + idxRemoteAssetOwnerChecksum, + uQRemoteAssetsOwnerChecksum, + uQRemoteAssetsOwnerLibraryChecksum, + idxRemoteAssetChecksum, + userMetadataEntity, + partnerEntity, + remoteExifEntity, + remoteAlbumAssetEntity, + remoteAlbumUserEntity, + memoryEntity, + memoryAssetEntity, + personEntity, + assetFaceEntity, + storeEntity, + idxLatLng, + ]; + @override + int get schemaVersion => 9; + @override + DriftDatabaseOptions get options => + const DriftDatabaseOptions(storeDateTimeAsText: true); +} diff --git a/mobile/test/drift/main/migration_test.dart b/mobile/test/drift/main/migration_test.dart index ce3f644b98..74467492ae 100644 --- a/mobile/test/drift/main/migration_test.dart +++ b/mobile/test/drift/main/migration_test.dart @@ -1,5 +1,5 @@ // dart format width=80 -// ignore_for_file: unused_import +// ignore_for_file: unused_local_variable, unused_import import 'package:drift/drift.dart'; import 'package:drift_dev/api/migrations_native.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/mobile/test/dto.mocks.dart b/mobile/test/dto.mocks.dart new file mode 100644 index 0000000000..ed53fcdc90 --- /dev/null +++ b/mobile/test/dto.mocks.dart @@ -0,0 +1,6 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:openapi/api.dart'; + +class MockSmartSearchDto extends Mock implements SmartSearchDto {} + +class MockMetadataSearchDto extends Mock implements MetadataSearchDto {} diff --git a/mobile/test/fixtures/album.stub.dart b/mobile/test/fixtures/album.stub.dart index 5141540a25..a22a4b72ab 100644 --- a/mobile/test/fixtures/album.stub.dart +++ b/mobile/test/fixtures/album.stub.dart @@ -1,4 +1,108 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; + +import 'asset.stub.dart'; +import 'user.stub.dart'; + +final class AlbumStub { + const AlbumStub._(); + + static final emptyAlbum = Album( + name: "empty-album", + localId: "empty-album-local", + remoteId: "empty-album-remote", + createdAt: DateTime(2000), + modifiedAt: DateTime(2023), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + ); + + static final sharedWithUser = Album( + name: "empty-album-shared-with-user", + localId: "empty-album-shared-with-user-local", + remoteId: "empty-album-shared-with-user-remote", + createdAt: DateTime(2023), + modifiedAt: DateTime(2023), + shared: true, + activityEnabled: false, + endDate: DateTime(2020), + )..sharedUsers.addAll([User.fromDto(UserStub.admin)]); + + static final oneAsset = Album( + name: "album-with-single-asset", + localId: "album-with-single-asset-local", + remoteId: "album-with-single-asset-remote", + createdAt: DateTime(2022), + modifiedAt: DateTime(2023), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + endDate: DateTime(2023), + )..assets.addAll([AssetStub.image1]); + + static final twoAsset = + Album( + name: "album-with-two-assets", + localId: "album-with-two-assets-local", + remoteId: "album-with-two-assets-remote", + createdAt: DateTime(2001), + modifiedAt: DateTime(2010), + shared: false, + activityEnabled: false, + startDate: DateTime(2019), + endDate: DateTime(2020), + ) + ..assets.addAll([AssetStub.image1, AssetStub.image2]) + ..activityEnabled = true + ..owner.value = User.fromDto(UserStub.admin); + + static final create2020end2020Album = Album( + name: "create2020update2020Album", + localId: "create2020update2020Album-local", + remoteId: "create2020update2020Album-remote", + createdAt: DateTime(2020), + modifiedAt: DateTime(2020), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + endDate: DateTime(2020), + ); + static final create2020end2022Album = Album( + name: "create2020update2021Album", + localId: "create2020update2021Album-local", + remoteId: "create2020update2021Album-remote", + createdAt: DateTime(2020), + modifiedAt: DateTime(2022), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + endDate: DateTime(2022), + ); + static final create2020end2024Album = Album( + name: "create2020update2022Album", + localId: "create2020update2022Album-local", + remoteId: "create2020update2022Album-remote", + createdAt: DateTime(2020), + modifiedAt: DateTime(2024), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + endDate: DateTime(2024), + ); + static final create2020end2026Album = Album( + name: "create2020update2023Album", + localId: "create2020update2023Album-local", + remoteId: "create2020update2023Album-remote", + createdAt: DateTime(2020), + modifiedAt: DateTime(2026), + shared: false, + activityEnabled: false, + startDate: DateTime(2020), + endDate: DateTime(2026), + ); +} abstract final class LocalAlbumStub { const LocalAlbumStub._(); diff --git a/mobile/test/fixtures/asset.stub.dart b/mobile/test/fixtures/asset.stub.dart index 473b900271..90a7f11737 100644 --- a/mobile/test/fixtures/asset.stub.dart +++ b/mobile/test/fixtures/asset.stub.dart @@ -1,4 +1,59 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart' as old; + +final class AssetStub { + const AssetStub._(); + + static final image1 = old.Asset( + checksum: "image1-checksum", + localId: "image1", + remoteId: 'image1-remote', + ownerId: 1, + fileCreatedAt: DateTime(2019), + fileModifiedAt: DateTime(2020), + updatedAt: DateTime.now(), + durationInSeconds: 0, + type: old.AssetType.image, + fileName: "image1.jpg", + isFavorite: true, + isArchived: false, + isTrashed: false, + exifInfo: const ExifInfo(isFlipped: false), + ); + + static final image2 = old.Asset( + checksum: "image2-checksum", + localId: "image2", + remoteId: 'image2-remote', + ownerId: 1, + fileCreatedAt: DateTime(2000), + fileModifiedAt: DateTime(2010), + updatedAt: DateTime.now(), + durationInSeconds: 60, + type: old.AssetType.video, + fileName: "image2.jpg", + isFavorite: false, + isArchived: false, + isTrashed: false, + exifInfo: const ExifInfo(isFlipped: true), + ); + + static final image3 = old.Asset( + checksum: "image3-checksum", + localId: "image3", + ownerId: 1, + fileCreatedAt: DateTime(2025), + fileModifiedAt: DateTime(2025), + updatedAt: DateTime.now(), + durationInSeconds: 60, + type: old.AssetType.image, + fileName: "image3.jpg", + isFavorite: true, + isArchived: false, + isTrashed: false, + ); +} abstract final class LocalAssetStub { const LocalAssetStub._(); diff --git a/mobile/test/fixtures/exif.stub.dart b/mobile/test/fixtures/exif.stub.dart new file mode 100644 index 0000000000..5ad9a41761 --- /dev/null +++ b/mobile/test/fixtures/exif.stub.dart @@ -0,0 +1,18 @@ +import 'package:immich_mobile/domain/models/exif.model.dart'; + +abstract final class ExifStub { + static final size = const ExifInfo(assetId: 1, fileSize: 1000); + + static final gps = const ExifInfo( + assetId: 2, + latitude: 20, + longitude: 20, + city: 'city', + state: 'state', + country: 'country', + ); + + static final rotated90CW = const ExifInfo(assetId: 3, orientation: "90"); + + static final rotated270CW = const ExifInfo(assetId: 4, orientation: "-90"); +} diff --git a/mobile/test/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index 1691fbdc49..c2254c0a03 100644 --- a/mobile/test/fixtures/sync_stream.stub.dart +++ b/mobile/test/fixtures/sync_stream.stub.dart @@ -9,7 +9,7 @@ abstract final class SyncStreamStub { email: "admin@admin", id: "1", name: "Admin", - avatarColor: const Optional.absent(), + avatarColor: null, hasProfileImage: false, profileChangedAt: DateTime(2025), ), @@ -22,7 +22,7 @@ abstract final class SyncStreamStub { email: "user@user", id: "5", name: "User", - avatarColor: const Optional.absent(), + avatarColor: null, hasProfileImage: false, profileChangedAt: DateTime(2025), ), @@ -115,7 +115,6 @@ abstract final class SyncStreamStub { duration: '0', fileCreatedAt: DateTime(2025), fileModifiedAt: DateTime(2025, 1, 2), - createdAt: DateTime(2025, 1, 2), id: id, isFavorite: false, libraryId: null, diff --git a/mobile/test/fixtures/user.stub.dart b/mobile/test/fixtures/user.stub.dart index b92ba71e5b..2ba7177f89 100644 --- a/mobile/test/fixtures/user.stub.dart +++ b/mobile/test/fixtures/user.stub.dart @@ -12,4 +12,24 @@ abstract final class UserStub { profileChangedAt: DateTime(2021), avatarColor: AvatarColor.green, ); + + static final user1 = UserDto( + id: "user1", + email: "user1@test.com", + name: "user1", + isAdmin: false, + updatedAt: DateTime(2022), + profileChangedAt: DateTime(2022), + avatarColor: AvatarColor.red, + ); + + static final user2 = UserDto( + id: "user2", + email: "user2@test.com", + name: "user2", + isAdmin: false, + updatedAt: DateTime(2023), + profileChangedAt: DateTime(2023), + avatarColor: AvatarColor.primary, + ); } diff --git a/mobile/test/flutter_test_config.dart b/mobile/test/flutter_test_config.dart deleted file mode 100644 index afee643fe7..0000000000 --- a/mobile/test/flutter_test_config.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/foundation.dart'; -import 'package:logging/logging.dart'; - -Future testExecutable(FutureOr Function() testMain) async { - Logger.root.level = Level.OFF; - EasyLocalization.logger.enableBuildModes = []; - // ignore: banned-usage - debugPrint = (String? message, {int? wrapWidth}) {}; - return testMain(); -} diff --git a/mobile/test/infrastructure/db_watchers_test.dart b/mobile/test/infrastructure/db_watchers_test.dart deleted file mode 100644 index 2975df226c..0000000000 --- a/mobile/test/infrastructure/db_watchers_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'dart:io'; - -import 'package:drift/drift.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:sqlite3_connection_pool/sqlite3_connection_pool.dart'; -import 'package:sqlite_async/sqlite_async.dart'; - -void main() { - late Directory dir; - late String path; - - Future<(Drift, SqliteDatabase, SqliteConnectionPool)> openDb() async { - final sqliteDb = SqliteDatabase.withFactory(ImmichSqliteOpenFactory(path: path)); - await sqliteDb.initialize(); - final pool = SqliteConnectionPool.open( - name: path, - openConnections: () => throw StateError('pool should already be open'), - ); - return (Drift.sqlite(sqliteDb, pool), sqliteDb, pool); - } - - setUp(() async { - dir = await Directory.systemTemp.createTemp('drift_pool_stream'); - path = '${dir.path}/immich.sqlite'; - }); - - tearDown(() async { - await dir.delete(recursive: true); - }); - - test('watch() in main isolate sees a write from a background isolate', () async { - final (db, dbConnection, _) = await openDb(); - final initialRows = await db.select(db.storeEntity).get(); - expect(initialRows, isEmpty); - - addTearDown(() async { - await db.close(); - await dbConnection.close(); - }); - - final rowCounts = db.select(db.storeEntity).watch().map((rows) => rows.length); - final emissionFuture = expectLater(rowCounts, emitsThrough(1)); - - await compute(_writerTask, path); - await emissionFuture; - }); -} - -Future _writerTask(String path) async { - final (db, dbConnection, sqlitePool) = await _openDb(path); - - try { - await db.into(db.storeEntity).insert(const StoreEntityCompanion(id: Value(1), intValue: Value(42))); - } finally { - await db.close(); - await dbConnection.close(); - sqlitePool.close(); - } -} - -Future<(Drift, SqliteDatabase, SqliteConnectionPool)> _openDb(String path) async { - final sqliteDb = SqliteDatabase.withFactory(ImmichSqliteOpenFactory(path: path)); - await sqliteDb.initialize(); - final pool = SqliteConnectionPool.open( - name: path, - openConnections: () => throw StateError('pool should already be open'), - ); - return (Drift.sqlite(sqliteDb, pool), sqliteDb, pool); -} diff --git a/mobile/test/infrastructure/loaders/remote_image_request_test.dart b/mobile/test/infrastructure/loaders/remote_image_request_test.dart deleted file mode 100644 index 97bdf3e2bf..0000000000 --- a/mobile/test/infrastructure/loaders/remote_image_request_test.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'dart:ffi'; -import 'dart:io'; -import 'dart:ui' as ui; - -import 'package:ffi/ffi.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/loaders/image_request.dart'; -import 'package:immich_mobile/platform/remote_image_api.g.dart'; -import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart'; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - const channel = BasicMessageChannel( - 'dev.flutter.pigeon.immich_mobile.RemoteImageApi.requestImage', - RemoteImageApi.pigeonChannelCodec, - ); - late List args; - - setUp(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, ( - message, - ) async { - args = message! as List; - return [null]; - }); - }); - - tearDown(() { - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); - }); - - Future loadEncoded(String path, ui.Size decodeSize) async { - final bytes = await File(path).readAsBytes(); - final pointer = malloc(bytes.length)..asTypedList(bytes.length).setAll(0, bytes); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, ( - message, - ) async { - return [ - {'pointer': pointer.address, 'length': bytes.length}, - ]; - }); - final request = RemoteImageRequest(uri: 'https://example.test/fallback', decodeSize: decodeSize); - - return (await request.load((_, {getTargetSize}) => throw UnimplementedError()))!.image; - } - - test('passes the requested decode size to the platform', () async { - final request = RemoteImageRequest(uri: 'https://example.test/thumbnail', decodeSize: const ui.Size(319.1, 179.1)); - - await request.load((_, {getTargetSize}) => throw UnimplementedError()); - - expect(args[0], 'https://example.test/thumbnail'); - expect(args[2], isFalse); - expect(args[3], 320); - expect(args[4], 180); - }); - - test('leaves requests without a size unbounded', () async { - final request = RemoteImageRequest(uri: 'https://example.test/thumbnail'); - - await request.load((_, {getTargetSize}) => throw UnimplementedError()); - - expect(args[3], isNull); - expect(args[4], isNull); - }); - - test('leaves encoded animation requests unbounded', () async { - final request = RemoteImageRequest(uri: 'https://example.test/animation', decodeSize: const ui.Size(320, 180)); - - await request.loadCodec(); - - expect(args[2], isTrue); - expect(args[3], isNull); - expect(args[4], isNull); - }); - - test('keeps portrait cover quality in a wide tile', () async { - final image = await loadEncoded('assets/feature_message/ocr.webp', const ui.Size(963, 642)); - - expect(image.width, 963); - expect(image.height, 1453); - image.dispose(); - }); - - test('preserves cover quality for extreme aspect ratios', () async { - final image = await loadEncoded('assets/immich-logo-inline-light.png', const ui.Size.square(320)); - - expect(image.width, 1311); - expect(image.height, 320); - image.dispose(); - }); - - test('does not upscale encoded fallback', () async { - final image = await loadEncoded('assets/feature_message/ocr.webp', const ui.Size.square(2000)); - - expect(image.width, 1206); - expect(image.height, 1819); - image.dispose(); - }); - - test('uses the decode size in the provider cache key', () { - final small = RemoteImageProvider(url: 'https://example.test/thumbnail', decodeSize: const ui.Size.square(160)); - final large = RemoteImageProvider(url: 'https://example.test/thumbnail', decodeSize: const ui.Size.square(320)); - - expect(small, isNot(large)); - }); - - test('shares the cache key when no decode size is set', () { - final first = RemoteImageProvider(url: 'https://example.test/thumbnail'); - final second = RemoteImageProvider(url: 'https://example.test/thumbnail'); - - expect(first, second); - expect(first.hashCode, second.hashCode); - }); -} diff --git a/mobile/test/medium/repositories/backup_repository_test.dart b/mobile/test/infrastructure/repositories/backup_repository_test.dart similarity index 98% rename from mobile/test/medium/repositories/backup_repository_test.dart rename to mobile/test/infrastructure/repositories/backup_repository_test.dart index 8e56502022..c042685779 100644 --- a/mobile/test/medium/repositories/backup_repository_test.dart +++ b/mobile/test/infrastructure/repositories/backup_repository_test.dart @@ -3,15 +3,15 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; import 'package:immich_mobile/utils/option.dart'; -import '../repository_context.dart'; +import '../../medium/repository_context.dart'; void main() { late MediumRepositoryContext ctx; - late BackupRepository sut; + late DriftBackupRepository sut; setUp(() { ctx = MediumRepositoryContext(); - sut = BackupRepository(ctx.db); + sut = DriftBackupRepository(ctx.db); }); tearDown(() async { diff --git a/mobile/test/infrastructure/repositories/exif_repository_test.dart b/mobile/test/infrastructure/repositories/exif_repository_test.dart new file mode 100644 index 0000000000..4e7ee4d79d --- /dev/null +++ b/mobile/test/infrastructure/repositories/exif_repository_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:isar/isar.dart'; + +import '../../fixtures/exif.stub.dart'; +import '../../test_utils.dart'; + +Future _populateExifTable(Isar db) async { + await db.writeTxn(() async { + await db.exifInfos.putAll([ + ExifInfo.fromDto(ExifStub.size), + ExifInfo.fromDto(ExifStub.gps), + ExifInfo.fromDto(ExifStub.rotated90CW), + ExifInfo.fromDto(ExifStub.rotated270CW), + ]); + }); +} + +void main() { + late Isar db; + late IsarExifRepository sut; + + setUp(() async { + db = await TestUtils.initIsar(); + sut = IsarExifRepository(db); + }); + + group("Return with proper orientation", () { + setUp(() async { + await _populateExifTable(db); + }); + + test("isFlipped true for 90CW", () async { + final exif = await sut.get(ExifStub.rotated90CW.assetId!); + expect(exif!.isFlipped, true); + }); + + test("isFlipped true for 270CW", () async { + final exif = await sut.get(ExifStub.rotated270CW.assetId!); + expect(exif!.isFlipped, true); + }); + + test("isFlipped false for the original non-rotated image", () async { + final exif = await sut.get(ExifStub.size.assetId!); + expect(exif!.isFlipped, false); + }); + }); +} diff --git a/mobile/test/infrastructure/repositories/local_album_repository_test.dart b/mobile/test/infrastructure/repositories/local_album_repository_test.dart index 7498360c87..fae0e09171 100644 --- a/mobile/test/infrastructure/repositories/local_album_repository_test.dart +++ b/mobile/test/infrastructure/repositories/local_album_repository_test.dart @@ -2,7 +2,6 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; @@ -17,13 +16,9 @@ void main() { mediumFactory = MediumFactory(db); }); - tearDown(() async { - await db.close(); - }); - group('getAll', () { test('sorts albums by backupSelection & isIosSharedAlbum', () async { - final localAlbumRepo = mediumFactory.getRepository(); + final localAlbumRepo = mediumFactory.getRepository(); await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '1', backupSelection: BackupSelection.none)); await localAlbumRepo.upsert(mediumFactory.localAlbum(id: '2', backupSelection: BackupSelection.excluded)); await localAlbumRepo.upsert( @@ -40,75 +35,4 @@ void main() { expect(albums[3].id, '2'); // excluded }); }); - - group('processDelta', () { - // Regression for #22844: an asset moved (not copied) into a backup album on - // Android was dropped. The delta reports only the asset's new album, and the - // stale link to its old album made the per-album delete sweep wipe the asset. - test('keeps an asset moved to another album that still holds other assets', () async { - final localAlbumRepo = mediumFactory.getRepository(); - - final moved = _localAsset('moved'); - final other = _localAsset('other'); - await localAlbumRepo.upsert( - mediumFactory.localAlbum(id: 'src', backupSelection: BackupSelection.none), - toUpsert: [moved, other], - ); - final anchor = _localAsset('anchor'); - await localAlbumRepo.upsert( - mediumFactory.localAlbum(id: 'dst', backupSelection: BackupSelection.selected), - toUpsert: [anchor], - ); - - // Delta reports the moved asset now living only in the destination album. - await localAlbumRepo.processDelta( - updates: [moved], - deletes: [], - assetAlbums: { - 'moved': ['dst'], - }, - ); - - // Per-album delete sweep, as sync() runs on Android. src now physically - // holds only `other`; dst holds `anchor` and the moved asset. - await localAlbumRepo.syncDeletes('src', ['other']); - await localAlbumRepo.syncDeletes('dst', ['anchor', 'moved']); - - final dstIds = (await localAlbumRepo.getAssets('dst')).map((a) => a.id).toSet(); - final srcIds = await localAlbumRepo.getAssetIds('src'); - - expect(dstIds, contains('moved')); // survived and linked to the backed-up album - expect(srcIds, isNot(contains('moved'))); // stale source link cleared - expect(srcIds, contains('other')); // untouched asset stays put - }); - - test('replaces album membership with exactly what the delta reports', () async { - final localAlbumRepo = mediumFactory.getRepository(); - - final moved = _localAsset('moved'); - await localAlbumRepo.upsert(mediumFactory.localAlbum(id: 'src'), toUpsert: [moved]); - await localAlbumRepo.upsert(mediumFactory.localAlbum(id: 'dst')); - - await localAlbumRepo.processDelta( - updates: [moved], - deletes: [], - assetAlbums: { - 'moved': ['dst'], - }, - ); - - expect(await localAlbumRepo.getAssetIds('src'), isEmpty); - expect(await localAlbumRepo.getAssetIds('dst'), ['moved']); - }); - }); } - -LocalAsset _localAsset(String id) => LocalAsset( - id: id, - name: '$id.jpg', - type: AssetType.image, - createdAt: DateTime(2024), - updatedAt: DateTime(2024), - playbackStyle: AssetPlaybackStyle.image, - isEdited: false, -); diff --git a/mobile/test/medium/repositories/local_asset_repository_test.dart b/mobile/test/infrastructure/repositories/local_asset_repository_test.dart similarity index 90% rename from mobile/test/medium/repositories/local_asset_repository_test.dart rename to mobile/test/infrastructure/repositories/local_asset_repository_test.dart index bd048a13fd..88f8d00e03 100644 --- a/mobile/test/medium/repositories/local_asset_repository_test.dart +++ b/mobile/test/infrastructure/repositories/local_asset_repository_test.dart @@ -4,88 +4,21 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/utils/option.dart'; -import '../repository_context.dart'; +import '../../medium/repository_context.dart'; void main() { late MediumRepositoryContext ctx; - late LocalAssetRepository sut; + late DriftLocalAssetRepository sut; setUp(() { ctx = MediumRepositoryContext(); - sut = LocalAssetRepository(ctx.db); + sut = DriftLocalAssetRepository(ctx.db); }); tearDown(() async { await ctx.dispose(); }); - group('get', () { - late String userId; - - setUp(() async { - final user = await ctx.newUser(); - userId = user.id; - // Owner-scoped queries resolve the current user via authUserEntity. - await ctx.newAuthUser(id: userId); - }); - - test('allows the same checksum to exist for multiple owners (#29973)', () async { - const checksum = 'some-shared-checksum'; - final mine = await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - final partner = await ctx.newUser(); - await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - final local = await ctx.newLocalAsset(checksum: checksum); - - final result = await sut.get(local.id); - - expect(result, isNotNull); - expect(result!.id, local.id); - // We must explicitly get OUR asset, not the partner's - expect(result.remoteId, mine.id); - }); - - test('reports local-only when only a partner has a remote copy (#29973)', () async { - // The current user has NOT uploaded this file; only a partner owns an identical-checksum remote asset - const checksum = 'partner-only'; - final partner = await ctx.newUser(); - await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - final local = await ctx.newLocalAsset(checksum: checksum); - - final result = await sut.get(local.id); - - expect(result, isNotNull); - expect(result!.remoteId, isNull); - expect(result.storage, AssetState.local); - }); - - test('allows the current user to have access to multiple remote rows for one checksum (#29973)', () async { - // A single user can have their own remote asset, a partner's remote asset, and a local asset all with the same checksum - const checksum = 'multi-library'; - final partner = await ctx.newUser(); - await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - final local = await ctx.newLocalAsset(checksum: checksum); - - final result = await sut.get(local.id); - - expect(result, isNotNull); - expect(result!.id, local.id); - expect(result.remoteId, isNotNull); - }); - - test('attaches remoteId to local asset automatically in simple scenarios', () async { - const checksum = 'simple'; - final remote = await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - final local = await ctx.newLocalAsset(checksum: checksum); - - final result = await sut.get(local.id); - - expect(result, isNotNull); - expect(result!.remoteId, remote.id); - expect(result.storage, AssetState.merged); - }); - }); - group('getRemovalCandidates', () { final cutoffDate = DateTime(2024, 1, 1); final beforeCutoff = DateTime(2023, 12, 31); @@ -523,7 +456,7 @@ void main() { test('does not update when longitude does not match', () async { final remoteAsset = await ctx.newRemoteAsset(ownerId: userId); - final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, longitude: .fromNullable(-74.006)); + final cloudIdAsset = await ctx.newRemoteAssetCloudId(id: remoteAsset.id, longitude: (-74.006).toOption()); final localAsset = await ctx.newLocalAsset( checksumOption: const Option.none(), iCloudId: cloudIdAsset.cloudId, diff --git a/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart b/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart index a5fe6f35c4..a25a9d92a7 100644 --- a/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart +++ b/mobile/test/infrastructure/repositories/merged_asset_drift_test.dart @@ -38,7 +38,6 @@ void main() { visibility: AssetVisibility.timeline, createdAt: Value(createdAt), updatedAt: Value(createdAt), - uploadedAt: Value(createdAt), localDateTime: const Value(null), ), ); diff --git a/mobile/test/medium/repositories/remote_album_repository_test.dart b/mobile/test/infrastructure/repositories/remote_album_repository_test.dart similarity index 53% rename from mobile/test/medium/repositories/remote_album_repository_test.dart rename to mobile/test/infrastructure/repositories/remote_album_repository_test.dart index b05a687f16..1bc797f6e1 100644 --- a/mobile/test/medium/repositories/remote_album_repository_test.dart +++ b/mobile/test/infrastructure/repositories/remote_album_repository_test.dart @@ -2,136 +2,21 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; -import '../repository_context.dart'; +import '../../medium/repository_context.dart'; void main() { late MediumRepositoryContext ctx; - late RemoteAlbumRepository sut; + late DriftRemoteAlbumRepository sut; setUp(() async { ctx = MediumRepositoryContext(); - sut = RemoteAlbumRepository(ctx.db); + sut = DriftRemoteAlbumRepository(ctx.db); }); tearDown(() async { await ctx.dispose(); }); - group('addAssets', () { - test('sets the first added asset as thumbnail when the album has no thumbnail', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - await sut.addAssets(album.id, [asset.id]); - - final updated = await sut.get(album.id); - expect(updated?.thumbnailAssetId, asset.id); - expect(updated?.assetCount, 1); - }); - - test('preserves an existing thumbnail when adding assets', () async { - final user = await ctx.newUser(); - final thumbnail = await ctx.newRemoteAsset(ownerId: user.id); - final album = await ctx.newRemoteAlbum(ownerId: user.id, thumbnailAssetId: thumbnail.id); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - await sut.addAssets(album.id, [asset.id]); - - final updated = await sut.get(album.id); - expect(updated?.thumbnailAssetId, thumbnail.id); - expect(updated?.assetCount, 1); - }); - }); - - group('getAll', () { - test('returns album when all of its assets are trashed', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final asset1 = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - final asset2 = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset1.id); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset2.id); - - final albums = await sut.getAll(); - - expect(albums, hasLength(1)); - expect(albums.first.id, album.id); - expect(albums.first.assetCount, 0); - }); - - test('excludes trashed assets from assetCount', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final active1 = await ctx.newRemoteAsset(ownerId: user.id); - final active2 = await ctx.newRemoteAsset(ownerId: user.id); - final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active1.id); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: active2.id); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id); - - final albums = await sut.getAll(); - - expect(albums, hasLength(1)); - expect(albums.first.assetCount, 2); - }); - - test('returns album without assets', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - - final albums = await sut.getAll(); - - expect(albums, hasLength(1)); - expect(albums.first.id, album.id); - expect(albums.first.assetCount, 0); - }); - }); - - group('get', () { - test('returns the album when all of its assets are trashed', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final asset = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id); - - final result = await sut.get(album.id); - - expect(result, isNotNull); - expect(result?.id, album.id); - expect(result?.assetCount, 0); - }); - }); - - group('getAlbumsContainingAsset', () { - test('excludes trashed assets from assetCount', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id); - - final albums = await sut.getAlbumsContainingAsset(asset.id); - - expect(albums, hasLength(1)); - expect(albums.first.id, album.id); - expect(albums.first.assetCount, 1); - }); - - test('returns albums for a trashed asset', () async { - final user = await ctx.newUser(); - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final trashed = await ctx.newRemoteAsset(ownerId: user.id, deletedAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: trashed.id); - - final albums = await sut.getAlbumsContainingAsset(trashed.id); - - expect(albums, hasLength(1)); - expect(albums.first.assetCount, 0); - }); - }); - group('getSortedAlbumIds', () { late String userId; @@ -148,7 +33,7 @@ void main() { test('returns single album when only one album exists', () async { final album = await ctx.newRemoteAlbum(ownerId: userId); final asset = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: asset.id); + await ctx.insertRemoteAlbumAsset(albumId: album.id, assetId: asset.id); final result = await sut.getSortedAlbumIds([album.id], aggregation: AssetDateAggregation.start); expect(result, [album.id]); @@ -159,22 +44,22 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (start: Jan 5) final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (start: Jan 25) final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -191,22 +76,22 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); // Album 2: Assets from Jan 5 to Jan 15 (end: Jan 15) final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset4.id); // Album 3: Assets from Jan 25 to Jan 30 (end: Jan 30) final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 30)); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset5.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset6.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -221,11 +106,11 @@ void main() { test('handles albums with single asset', () async { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); @@ -236,15 +121,15 @@ void main() { // Create 3 albums final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 10)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); // Only request album1 and album3 final result = await sut.getSortedAlbumIds([album1.id, album3.id], aggregation: AssetDateAggregation.start); @@ -258,11 +143,11 @@ void main() { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: sameDate); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final result = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); @@ -274,15 +159,15 @@ void main() { test('handles albums across different years', () async { final album1 = await ctx.newRemoteAlbum(ownerId: userId); final asset1 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2023, 12, 25)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset2 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 5)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset2.id); final album3 = await ctx.newRemoteAlbum(ownerId: userId); final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2025, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album3.id, assetId: asset3.id); final result = await sut.getSortedAlbumIds([ album1.id, @@ -301,15 +186,15 @@ void main() { final asset3 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 15)); final asset4 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 20)); final asset5 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 25)); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset3.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset4.id); - await ctx.newRemoteAlbumAsset(albumId: album1.id, assetId: asset5.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset1.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset2.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset3.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset4.id); + await ctx.insertRemoteAlbumAsset(albumId: album1.id, assetId: asset5.id); final album2 = await ctx.newRemoteAlbum(ownerId: userId); final asset6 = await ctx.newRemoteAsset(ownerId: userId, createdAt: DateTime(2024, 1, 1)); - await ctx.newRemoteAlbumAsset(albumId: album2.id, assetId: asset6.id); + await ctx.insertRemoteAlbumAsset(albumId: album2.id, assetId: asset6.id); final resultStart = await sut.getSortedAlbumIds([album1.id, album2.id], aggregation: AssetDateAggregation.start); diff --git a/mobile/test/infrastructure/repositories/store_repository_test.dart b/mobile/test/infrastructure/repositories/store_repository_test.dart index 08ed5f66ab..18d41e32e0 100644 --- a/mobile/test/infrastructure/repositories/store_repository_test.dart +++ b/mobile/test/infrastructure/repositories/store_repository_test.dart @@ -1,61 +1,45 @@ import 'dart:async'; -import 'package:drift/drift.dart' hide isNull; -import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:isar/isar.dart'; import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; const _kTestAccessToken = "#TestToken"; +final _kTestBackupFailed = DateTime(2025, 2, 20, 11, 45); const _kTestVersion = 10; -const _kTestAdvancedTroubleshooting = false; +const _kTestColorfulInterface = false; final _kTestUser = UserStub.admin; -Future _populateStore(Drift db) async { - await db.batch((batch) async { - batch.insert( - db.storeEntity, - StoreEntityCompanion( - id: Value(StoreKey.advancedTroubleshooting.id), - intValue: const Value(_kTestAdvancedTroubleshooting ? 1 : 0), - stringValue: const Value(null), - ), - ); - batch.insert( - db.storeEntity, - StoreEntityCompanion( - id: Value(StoreKey.accessToken.id), - intValue: const Value(null), - stringValue: const Value(_kTestAccessToken), - ), - ); - batch.insert( - db.storeEntity, - StoreEntityCompanion( - id: Value(StoreKey.version.id), - intValue: const Value(_kTestVersion), - stringValue: const Value(null), - ), - ); +Future _addIntStoreValue(Isar db, StoreKey key, int? value) async { + await db.storeValues.put(StoreValue(key.id, intValue: value, strValue: null)); +} + +Future _addStrStoreValue(Isar db, StoreKey key, String? value) async { + await db.storeValues.put(StoreValue(key.id, intValue: null, strValue: value)); +} + +Future _populateStore(Isar db) async { + await db.writeTxn(() async { + await _addIntStoreValue(db, StoreKey.colorfulInterface, _kTestColorfulInterface ? 1 : 0); + await _addIntStoreValue(db, StoreKey.backupFailedSince, _kTestBackupFailed.millisecondsSinceEpoch); + await _addStrStoreValue(db, StoreKey.accessToken, _kTestAccessToken); + await _addIntStoreValue(db, StoreKey.version, _kTestVersion); }); } void main() { - late Drift db; - late StoreRepository sut; + late Isar db; + late IsarStoreRepository sut; setUp(() async { - db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - sut = StoreRepository(db); - }); - - tearDown(() async { - await db.close(); + db = await TestUtils.initIsar(); + sut = IsarStoreRepository(db); }); group('Store Repository converters:', () { @@ -75,12 +59,20 @@ void main() { expect(accessToken, _kTestAccessToken); }); + test('converts datetime', () async { + DateTime? backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince); + expect(backupFailedSince, isNull); + await sut.upsert(StoreKey.backupFailedSince, _kTestBackupFailed); + backupFailedSince = await sut.tryGet(StoreKey.backupFailedSince); + expect(backupFailedSince, _kTestBackupFailed); + }); + test('converts bool', () async { - bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); - expect(advancedTroubleshooting, isNull); - await sut.upsert(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting); - advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); - expect(advancedTroubleshooting, _kTestAdvancedTroubleshooting); + bool? colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface); + expect(colorfulInterface, isNull); + await sut.upsert(StoreKey.colorfulInterface, _kTestColorfulInterface); + colorfulInterface = await sut.tryGet(StoreKey.colorfulInterface); + expect(colorfulInterface, _kTestColorfulInterface); }); test('converts user', () async { @@ -98,18 +90,18 @@ void main() { }); test('delete()', () async { - bool? advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); - expect(advancedTroubleshooting, isFalse); - await sut.deleteValue(StoreKey.advancedTroubleshooting); - advancedTroubleshooting = await sut.tryGet(StoreKey.advancedTroubleshooting); - expect(advancedTroubleshooting, isNull); + bool? isColorful = await sut.tryGet(StoreKey.colorfulInterface); + expect(isColorful, isFalse); + await sut.delete(StoreKey.colorfulInterface); + isColorful = await sut.tryGet(StoreKey.colorfulInterface); + expect(isColorful, isNull); }); test('deleteAll()', () async { - final count = await db.storeEntity.count().getSingle(); + final count = await db.storeValues.count(); expect(count, isNot(isZero)); await sut.deleteAll(); - unawaited(expectLater(await db.storeEntity.count().getSingle(), isZero)); + unawaited(expectLater(await db.storeValues.count(), isZero)); }); }); @@ -147,13 +139,15 @@ void main() { emitsInOrder([ [ const StoreDto(StoreKey.version, _kTestVersion), + StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), + const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), ], [ const StoreDto(StoreKey.version, _kTestVersion + 10), + StoreDto(StoreKey.backupFailedSince, _kTestBackupFailed), const StoreDto(StoreKey.accessToken, _kTestAccessToken), - const StoreDto(StoreKey.advancedTroubleshooting, _kTestAdvancedTroubleshooting), + const StoreDto(StoreKey.colorfulInterface, _kTestColorfulInterface), ], ]), ), diff --git a/mobile/test/infrastructure/repositories/sync_api_repository_test.dart b/mobile/test/infrastructure/repositories/sync_api_repository_test.dart index 5253838fef..85eebacb14 100644 --- a/mobile/test/infrastructure/repositories/sync_api_repository_test.dart +++ b/mobile/test/infrastructure/repositories/sync_api_repository_test.dart @@ -1,13 +1,10 @@ import 'dart:async'; import 'dart:convert'; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:immich_mobile/domain/models/sync_event.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; import 'package:immich_mobile/utils/semver.dart'; @@ -16,6 +13,7 @@ import 'package:openapi/api.dart'; import '../../api.mocks.dart'; import '../../service.mocks.dart'; +import '../../test_utils.dart'; class MockHttpClient extends Mock implements http.Client {} @@ -37,11 +35,10 @@ void main() { late MockHttpClient mockHttpClient; late MockStreamedResponse mockStreamedResponse; late StreamController> responseStreamController; - const int testBatchSize = 3; + late int testBatchSize = 3; setUpAll(() async { - final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); + await StoreService.init(storeRepository: IsarStoreRepository(await TestUtils.initIsar())); }); setUp(() { @@ -140,7 +137,7 @@ void main() { bool abortWasCalledInCallback = false; final Completer firstBatchReceived = Completer(); - Future onDataCallback(List _, Function() abort, Function() _) async { + Future onDataCallback(List events, Function() abort, Function() _) async { onDataCallCount++; if (onDataCallCount == 1) { abort(); @@ -189,7 +186,7 @@ void main() { final Completer firstBatchReceived = Completer(); final Completer secondBatchReceived = Completer(); - Future onDataCallback(List events, Function() _, Function() _) async { + Future onDataCallback(List events, Function() _, Function() __) async { onDataCallCount++; if (onDataCallCount == 1) { receivedEventsBatch1 = events; @@ -244,7 +241,7 @@ void main() { final streamError = Exception("Network Error"); int onDataCallCount = 0; - Future onDataCallback(List _, Function() _, Function() _) async { + Future onDataCallback(List events, Function() _, Function() __) async { onDataCallCount++; } @@ -270,7 +267,7 @@ void main() { when(() => mockStreamedResponse.stream).thenAnswer((_) => http.ByteStream(errorBodyController.stream)); int onDataCallCount = 0; - Future onDataCallback(List _, Function() _, Function() _) async { + Future onDataCallback(List events, Function() _, Function() __) async { onDataCallCount++; } diff --git a/mobile/test/infrastructure/repository.mock.dart b/mobile/test/infrastructure/repository.mock.dart index ef266b243c..2d4af5b308 100644 --- a/mobile/test/infrastructure/repository.mock.dart +++ b/mobile/test/infrastructure/repository.mock.dart @@ -1,13 +1,10 @@ import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_album.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/sync_api.repository.dart'; @@ -20,40 +17,38 @@ import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; import 'package:immich_mobile/repositories/upload.repository.dart'; import 'package:mocktail/mocktail.dart'; -class MockDrift extends Mock implements Drift {} +class MockStoreRepository extends Mock implements IsarStoreRepository {} -class MockMemoryRepository extends Mock implements MemoryRepository {} - -class MockStoreRepository extends Mock implements StoreRepository {} - -class MockSettingsRepository extends Mock implements SettingsRepository {} +class MockDriftStoreRepository extends Mock implements DriftStoreRepository {} class MockLogRepository extends Mock implements LogRepository {} +class MockIsarUserRepository extends Mock implements IsarUserRepository {} + +class MockDeviceAssetRepository extends Mock implements IsarDeviceAssetRepository {} + class MockSyncStreamRepository extends Mock implements SyncStreamRepository {} -class MockLocalAlbumRepository extends Mock implements LocalAlbumRepository {} +class MockLocalAlbumRepository extends Mock implements DriftLocalAlbumRepository {} -class MockRemoteAlbumRepository extends Mock implements RemoteAlbumRepository {} +class MockRemoteAlbumRepository extends Mock implements DriftRemoteAlbumRepository {} -class MockLocalAssetRepository extends Mock implements LocalAssetRepository {} +class MockLocalAssetRepository extends Mock implements DriftLocalAssetRepository {} + +class MockDriftLocalAssetRepository extends Mock implements DriftLocalAssetRepository {} class MockRemoteAssetRepository extends Mock implements RemoteAssetRepository {} -class MockTrashedLocalAssetRepository extends Mock implements TrashedLocalAssetRepository {} +class MockTrashedLocalAssetRepository extends Mock implements DriftTrashedLocalAssetRepository {} class MockStorageRepository extends Mock implements StorageRepository {} -class MockBackupRepository extends Mock implements BackupRepository {} +class MockDriftBackupRepository extends Mock implements DriftBackupRepository {} class MockUploadRepository extends Mock implements UploadRepository {} class MockSyncMigrationRepository extends Mock implements SyncMigrationRepository {} -class MockUserRepository extends Mock implements UserRepository {} - -class MockPartnerRepository extends Mock implements PartnerRepository {} - // API Repos class MockUserApiRepository extends Mock implements UserApiRepository {} diff --git a/mobile/test/medium/repositories/memory_repository_test.dart b/mobile/test/medium/repositories/memory_repository_test.dart deleted file mode 100644 index af9309f4aa..0000000000 --- a/mobile/test/medium/repositories/memory_repository_test.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late MemoryRepository sut; - - setUp(() { - ctx = MediumRepositoryContext(); - sut = MemoryRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('getAll', () { - // #24745: memories created via the API have no showAt/hideAt. before the fix - // the window filter (show_at <= now AND hide_at >= now) drops them because a - // NULL comparison is never true. - test('includes a memory with null showAt/hideAt', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final memory = await ctx.newMemory(ownerId: user.id); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result, hasLength(1)); - expect(result.first.id, memory.id); - expect(result.first.assets.single.id, asset.id); - }); - - test('includes a memory whose window covers today', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final now = DateTime.now().toUtc(); - final memory = await ctx.newMemory( - ownerId: user.id, - showAt: now.subtract(const Duration(days: 10)), - hideAt: now.add(const Duration(days: 10)), - ); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result.map((m) => m.id), [memory.id]); - }); - - test('excludes a memory whose hideAt is in the past', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final now = DateTime.now().toUtc(); - final memory = await ctx.newMemory( - ownerId: user.id, - showAt: now.subtract(const Duration(days: 20)), - hideAt: now.subtract(const Duration(days: 10)), - ); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result, isEmpty); - }); - - test('excludes a memory whose showAt is in the future', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final now = DateTime.now().toUtc(); - final memory = await ctx.newMemory( - ownerId: user.id, - showAt: now.add(const Duration(days: 10)), - hideAt: now.add(const Duration(days: 20)), - ); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result, isEmpty); - }); - - test('includes a memory with showAt in the past and null hideAt', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final now = DateTime.now().toUtc(); - final memory = await ctx.newMemory(ownerId: user.id, showAt: now.subtract(const Duration(days: 10))); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result.map((m) => m.id), [memory.id]); - }); - - test('excludes a memory with null showAt and hideAt in the past', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final now = DateTime.now().toUtc(); - final memory = await ctx.newMemory(ownerId: user.id, hideAt: now.subtract(const Duration(days: 10))); - await ctx.newMemoryAsset(memoryId: memory.id, assetId: asset.id); - - final result = await sut.getAll(user.id); - - expect(result, isEmpty); - }); - }); -} diff --git a/mobile/test/medium/repositories/partner_repository_test.dart b/mobile/test/medium/repositories/partner_repository_test.dart deleted file mode 100644 index 4d730a0be4..0000000000 --- a/mobile/test/medium/repositories/partner_repository_test.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late PartnerRepository sut; - - setUp(() { - ctx = MediumRepositoryContext(); - sut = PartnerRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('search', () { - test('sharedBy returns users the current user shares their library to', () async { - final me = await ctx.newUser(); - final recipient = await ctx.newUser(); - final sharer = await ctx.newUser(); - await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id); - await ctx.newPartner(sharedById: sharer.id, sharedWithId: me.id); - - final result = await sut.search(me.id, .sharedBy).first; - - expect(result.map((partner) => partner.id), unorderedEquals([recipient.id])); - }); - - test('sharedWith returns users sharing their library with the current user', () async { - final me = await ctx.newUser(); - final recipient = await ctx.newUser(); - final sharer = await ctx.newUser(); - await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id); - await ctx.newPartner(sharedById: sharer.id, sharedWithId: me.id); - - final result = await sut.search(me.id, .sharedWith).first; - - expect(result.map((partner) => partner.id), unorderedEquals([sharer.id])); - }); - - test('emits an updated list when a new partner is added', () async { - final me = await ctx.newUser(); - final recipient = await ctx.newUser(); - - final ids = sut.search(me.id, .sharedBy).map((partners) => partners.map((p) => p.id).toList()); - final expectation = expectLater( - ids, - emitsInOrder([ - isEmpty, - unorderedEquals([recipient.id]), - ]), - ); - - await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id); - await expectation; - }); - }); - - group('create', () { - test('inserts a partnership with the current user as the sharer and inTimeline disabled', () async { - final me = await ctx.newUser(); - final partner = await ctx.newUser(); - - await sut.create(sharedById: me.id, sharedWithId: partner.id); - - final result = (await sut.search(me.id, .sharedBy).first).first; - expect(result.id, partner.id); - expect(result.inTimeline, isFalse); - }); - }); - - group('update', () { - test('toggles the inTimeline flag for an existing partnership', () async { - final me = await ctx.newUser(); - final sharer = await ctx.newUser(); - await ctx.newPartner(sharedById: sharer.id, sharedWithId: me.id, inTimeline: false); - - await sut.updatePartner(sharedById: sharer.id, sharedWithId: me.id, inTimeline: true); - - final result = await sut.get(sharedById: sharer.id, sharedWithId: me.id); - expect(result.inTimeline, isTrue); - }); - }); - - group('delete', () { - test('removes the partnership the current user shares by', () async { - final me = await ctx.newUser(); - final recipient = await ctx.newUser(); - await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id); - - await sut.deletePartner(sharedById: me.id, sharedWithId: recipient.id); - - final rows = await ctx.db.select(ctx.db.partnerEntity).get(); - expect(rows, isEmpty); - }); - }); -} diff --git a/mobile/test/medium/repositories/people_repository_test.dart b/mobile/test/medium/repositories/people_repository_test.dart deleted file mode 100644 index 979dd95373..0000000000 --- a/mobile/test/medium/repositories/people_repository_test.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/repositories/people.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late PeopleRepository sut; - - setUp(() { - ctx = MediumRepositoryContext(); - sut = PeopleRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('getAssetPeople', () { - test('does not duplicate a person with multiple face records on the same asset', () async { - // Regression check for #20585: a join on asset_face_entity returned one row - // per face, so a person appeared twice in the asset details panel when the - // same face was on the asset more than once (e.g., metadata import + ML) - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - final person = await ctx.newPerson(ownerId: user.id); - await ctx.newFace(assetId: asset.id, personId: person.id); - await ctx.newFace(assetId: asset.id, personId: person.id); - - final people = await sut.getAssetPeople(asset.id); - - expect(people, hasLength(1)); - expect(people.single.id, person.id); - }); - - test('returns all distinct people of an asset', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - final person1 = await ctx.newPerson(ownerId: user.id); - final person2 = await ctx.newPerson(ownerId: user.id); - await ctx.newFace(assetId: asset.id, personId: person1.id); - await ctx.newFace(assetId: asset.id, personId: person2.id); - - final people = await sut.getAssetPeople(asset.id); - - expect(people, hasLength(2)); - expect(people.map((person) => person.id), containsAll([person1.id, person2.id])); - }); - - test('does not return hidden people', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - final hidden = await ctx.newPerson(ownerId: user.id, isHidden: true); - await ctx.newFace(assetId: asset.id, personId: hidden.id); - - final people = await sut.getAssetPeople(asset.id); - - expect(people, isEmpty); - }); - - test('does not return people from other assets', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - final otherAsset = await ctx.newRemoteAsset(ownerId: user.id); - - final person = await ctx.newPerson(ownerId: user.id); - await ctx.newFace(assetId: otherAsset.id, personId: person.id); - - final people = await sut.getAssetPeople(asset.id); - - expect(people, isEmpty); - }); - }); -} diff --git a/mobile/test/medium/repositories/remote_asset_repository_test.dart b/mobile/test/medium/repositories/remote_asset_repository_test.dart deleted file mode 100644 index f534355d99..0000000000 --- a/mobile/test/medium/repositories/remote_asset_repository_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_asset.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late RemoteAssetRepository sut; - - setUp(() { - ctx = MediumRepositoryContext(); - sut = RemoteAssetRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('getByChecksum', () { - late String userId; - - setUp(() async { - final user = await ctx.newUser(); - userId = user.id; - await ctx.newAuthUser(id: userId); - }); - - test('returns all assets when a partner shares the checksum', () async { - const checksum = 'shared-partner-checksum'; - final mine = await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - final partner = await ctx.newUser(); - final theirs = await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - - final result = await sut.getAllDebugForChecksum(checksum); - final mineResult = result.firstWhere((asset) => asset.id == mine.id); - final theirResult = result.firstWhere((asset) => asset.id == theirs.id); - - expect(result, isNotEmpty); - expect(mineResult.id, mine.id); - expect(mineResult.ownerId, userId); - - expect(theirResult.id, theirs.id); - expect(theirResult.ownerId, partner.id); - }); - - test('returns partner asset only if there is no matching user asset', () async { - const checksum = 'partner-only'; - final partner = await ctx.newUser(); - final theirs = await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - - final result = await sut.getAllDebugForChecksum(checksum); - - expect(result.length, 1); - expect(result[0].id, theirs.id); - }); - - test('returns the current user\'s asset', () async { - const checksum = 'simple'; - final remote = await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - - final result = await sut.getAllDebugForChecksum(checksum); - - expect(result.length, 1); - expect(result[0].id, remote.id); - }); - }); -} diff --git a/mobile/test/medium/repositories/settings_repository_test.dart b/mobile/test/medium/repositories/settings_repository_test.dart deleted file mode 100644 index 087b3aece4..0000000000 --- a/mobile/test/medium/repositories/settings_repository_test.dart +++ /dev/null @@ -1,156 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; -import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late SettingsRepository sut; - - setUpAll(() async { - ctx = MediumRepositoryContext(); - sut = await SettingsRepository.ensureInitialized(ctx.db); - }); - - tearDownAll(() async { - await ctx.dispose(); - }); - - setUp(() async { - await ctx.db.delete(ctx.db.settingsEntity).go(); - await SettingsRepository.instance.refresh(); - }); - - group('defaults', () { - test('appConfig returns key defaults when DB is empty', () { - expect(sut.appConfig.theme.mode, ThemeMode.system); - }); - - test('appConfig returns key defaults when DB is empty', () { - expect(sut.appConfig.logLevel, LogLevel.info); - }); - }); - - group('write', () { - test('persists a value and reflects it in the composed view', () async { - await sut.write(.themeMode, ThemeMode.dark); - expect(sut.appConfig.theme.mode, ThemeMode.dark); - }); - - test('persists across domains independently', () async { - await sut.write(.themeMode, ThemeMode.light); - await sut.write(.logLevel, LogLevel.severe); - expect(sut.appConfig.theme.mode, ThemeMode.light); - expect(sut.appConfig.logLevel, LogLevel.severe); - }); - - test('removes the row and reverts to default', () async { - await sut.write(.themeMode, ThemeMode.dark); - expect(sut.appConfig.theme.mode, ThemeMode.dark); - - await sut.write(.themeMode, ThemeMode.system); - expect(sut.appConfig.theme.mode, ThemeMode.system); - - final rows = await ctx.db.select(ctx.db.settingsEntity).get(); - expect(rows, isEmpty); - }); - }); - - group('null values', () { - test('writing null to a nullable key clears the row and reverts the cache to null', () async { - await sut.write(SettingsKey.networkPreferredWifiName, 'home-wifi'); - expect(sut.appConfig.network.preferredWifiName, 'home-wifi'); - expect(await ctx.db.select(ctx.db.settingsEntity).get(), hasLength(1)); - - await sut.write(SettingsKey.networkPreferredWifiName, null); - - expect(await ctx.db.select(ctx.db.settingsEntity).get(), isEmpty); - expect(sut.appConfig.network.preferredWifiName, isNull); - }); - - test('writing null to an already-null key is a no-op', () async { - await sut.write(SettingsKey.networkPreferredWifiName, null); - expect(await ctx.db.select(ctx.db.settingsEntity).get(), isEmpty); - }); - - test('a stored NULL value column decodes to null on refresh', () async { - await ctx.db - .into(ctx.db.settingsEntity) - .insert( - SettingsEntityCompanion.insert( - key: SettingsKey.networkPreferredWifiName.name, - value: const .new(null), - updatedAt: .new(DateTime.now()), - ), - ); - - await SettingsRepository.instance.refresh(); - expect(sut.appConfig.network.preferredWifiName, isNull); - }); - }); - - group('delete', () {}); - - group('sync', () { - test('picks up rows that were inserted directly into the DB', () async { - await ctx.db - .into(ctx.db.settingsEntity) - .insert( - SettingsEntityCompanion.insert( - key: SettingsKey.themeMode.name, - value: .new(ThemeMode.dark.name), - updatedAt: .new(DateTime.now()), - ), - ); - - // Cache hasn't seen this row yet — view still returns the default. - expect(sut.appConfig.theme.mode, ThemeMode.system); - - await SettingsRepository.instance.refresh(); - expect(sut.appConfig.theme.mode, ThemeMode.dark); - }); - - test('drops cached values for rows that were deleted out from under the repo', () async { - await sut.write(.themeMode, ThemeMode.dark); - // Wipe the row directly. Cache still holds the old value. - await ctx.db.delete(ctx.db.settingsEntity).go(); - expect(sut.appConfig.theme.mode, ThemeMode.dark); - - await SettingsRepository.instance.refresh(); - expect(sut.appConfig.theme.mode, ThemeMode.system); - }); - - test('skips rows whose key is unknown to SettingsKey', () async { - await ctx.db - .into(ctx.db.settingsEntity) - .insert( - SettingsEntityCompanion.insert( - key: 'app-config.unknown.future-key', - value: const .new('unknown'), - updatedAt: .new(DateTime.now()), - ), - ); - - await SettingsRepository.instance.refresh(); - expect(sut.appConfig.theme.mode, ThemeMode.system); - }); - }); - - group('watch', () { - test('watchAppConfig emits the new value after a write', () async { - final expectation = expectLater(sut.watchConfig().map((c) => c.theme.mode), emitsThrough(ThemeMode.dark)); - await sut.write(SettingsKey.themeMode, ThemeMode.dark); - await expectation; - }); - - test('watchConfig emits the new value after a write', () async { - final expectation = expectLater(sut.watchConfig().map((c) => c.logLevel), emitsThrough(LogLevel.warning)); - await sut.write(SettingsKey.logLevel, LogLevel.warning); - await expectation; - }); - }); -} diff --git a/mobile/test/medium/repositories/timeline_repository_test.dart b/mobile/test/medium/repositories/timeline_repository_test.dart deleted file mode 100644 index 3de31e9665..0000000000 --- a/mobile/test/medium/repositories/timeline_repository_test.dart +++ /dev/null @@ -1,228 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; -import 'package:intl/date_symbol_data_local.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late TimelineRepository sut; - - setUpAll(() async { - await initializeDateFormatting(); - }); - - setUp(() { - ctx = MediumRepositoryContext(); - sut = TimelineRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('remoteAlbum assets', () { - test('no duplicate assets when identical checksum appears in multiple local asset rows', () async { - // Regression check for #23273: a LEFT OUTER JOIN on checksum would fan out and create duplicates - // happens when same photo exists in multiple albums on device - final user = await ctx.newUser(); - const checksum = 'yolo'; - final album = await ctx.newRemoteAlbum(ownerId: user.id); - final remoteAsset = await ctx.newRemoteAsset(ownerId: user.id, checksum: checksum); - await ctx.newRemoteAlbumAsset(albumId: album.id, assetId: remoteAsset.id); - - final localAsset1 = await ctx.newLocalAsset(checksum: checksum); - final localAsset2 = await ctx.newLocalAsset(checksum: checksum); - - final query = sut.remoteAlbum(album.id, .day); - - final buckets = await query.bucketSource().first; - expect(buckets, hasLength(1)); - expect(buckets.single.assetCount, 1); - - final assets = await query.assetSource(0, 10); - expect(assets, hasLength(1)); - expect((assets.first as RemoteAsset).id, remoteAsset.id); - expect([localAsset1.id, localAsset2.id], contains((assets.first as RemoteAsset).localId)); - }); - - test('orders shifted album assets in both directions and keeps normal asset order (#28852)', () async { - final user = await ctx.newUser(); - final descendingAlbum = await ctx.newRemoteAlbum(ownerId: user.id, order: .desc); - final ascendingAlbum = await ctx.newRemoteAlbum(ownerId: user.id, order: .asc); - final shiftedLater = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 2, 12), - localDateTime: DateTime.utc(2024, 9, 3, 12), - ); - final shiftedEarlier = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 3, 12), - localDateTime: DateTime.utc(2024, 9, 2, 12), - ); - final normalLater = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 4, 14), - localDateTime: DateTime.utc(2024, 9, 4, 14), - ); - final normalEarlier = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 4, 12), - localDateTime: DateTime.utc(2024, 9, 4, 12), - ); - final seeded = [shiftedLater, shiftedEarlier, normalLater, normalEarlier]; - for (final asset in seeded) { - await ctx.newRemoteAlbumAsset(albumId: descendingAlbum.id, assetId: asset.id); - await ctx.newRemoteAlbumAsset(albumId: ascendingAlbum.id, assetId: asset.id); - } - - final descending = sut.remoteAlbum(descendingAlbum.id, .day); - final ascending = sut.remoteAlbum(ascendingAlbum.id, .day); - - final buckets = await descending.bucketSource().first; - expect(buckets, hasLength(3)); - expect(buckets.map((bucket) => bucket.assetCount), [2, 1, 1]); - - final descendingAssets = await descending.assetSource(0, 10); - expect(descendingAssets.map((asset) => (asset as RemoteAsset).id), [ - normalLater.id, - normalEarlier.id, - shiftedLater.id, - shiftedEarlier.id, - ]); - - final ascendingAssets = await ascending.assetSource(0, 10); - expect(ascendingAssets.map((asset) => (asset as RemoteAsset).id), [ - shiftedEarlier.id, - shiftedLater.id, - normalEarlier.id, - normalLater.id, - ]); - }); - }); - - group('person assets', () { - test('does not duplicate an asset that has multiple face records for the same person', () async { - // Regression check for #26723: an INNER JOIN between remote_asset_entity and asset_face_entity - // fanned out one asset into N rows when N face records pointed at the same (asset, person) pair - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id); - - final person = await ctx.newPerson(ownerId: user.id); - await ctx.newFace(assetId: asset.id, personId: person.id); - await ctx.newFace(assetId: asset.id, personId: person.id); - - final query = sut.person(user.id, person.id, .day); - - final buckets = await query.bucketSource().first; - expect(buckets, hasLength(1)); - expect(buckets.single.assetCount, 1); - - final assets = await query.assetSource(0, 10); - expect(assets, hasLength(1)); - expect((assets.first as RemoteAsset).id, asset.id); - }); - - test('orders shifted person assets by effective date (#28852)', () async { - final user = await ctx.newUser(); - final person = await ctx.newPerson(ownerId: user.id); - final shiftedLater = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 2, 12), - localDateTime: DateTime.utc(2024, 9, 3, 12), - ); - final shiftedEarlier = await ctx.newRemoteAsset( - ownerId: user.id, - createdAt: DateTime.utc(2024, 9, 3, 12), - localDateTime: DateTime.utc(2024, 9, 2, 12), - ); - await ctx.newFace(assetId: shiftedLater.id, personId: person.id); - await ctx.newFace(assetId: shiftedEarlier.id, personId: person.id); - - final query = sut.person(user.id, person.id, .day); - - final buckets = await query.bucketSource().first; - expect(buckets, hasLength(2)); - - final assets = await query.assetSource(0, 10); - expect(assets.map((asset) => (asset as RemoteAsset).id), [shiftedLater.id, shiftedEarlier.id]); - }); - }); - - group('live photos', () { - test('remote-only live photo contains livePhotoVideoId and is marked as a motion photo', () async { - final user = await ctx.newUser(); - final asset = await ctx.newRemoteAsset(ownerId: user.id, livePhotoVideoId: 'motion-photo-1'); - - final assets = await sut.main([user.id], .day).assetSource(0, 10); - - expect(assets, hasLength(1)); - final remote = assets.single as RemoteAsset; - expect(remote.id, asset.id); - expect(remote.livePhotoVideoId, 'motion-photo-1'); - expect(remote.isMotionPhoto, isTrue); - expect(remote.localId, isNull); - }); - - test('merged live photo resolves localId and is marked as a motion photo', () async { - final user = await ctx.newUser(); - const checksum = 'shared-live-photo-checksum'; - final asset = await ctx.newRemoteAsset(ownerId: user.id, checksum: checksum, livePhotoVideoId: 'motion-photo-2'); - final local = await ctx.newLocalAsset(checksum: checksum); - - final assets = await sut.main([user.id], .day).assetSource(0, 10); - - expect(assets, hasLength(1)); - final remote = assets.single as RemoteAsset; - expect(remote.id, asset.id); - expect(remote.livePhotoVideoId, 'motion-photo-2'); - expect(remote.isMotionPhoto, isTrue); - expect(remote.localId, local.id); - }); - }); - - group('localAlbum assets', () { - late String userId; - late String otherUserId; - - setUp(() async { - final user = await ctx.newUser(); - userId = user.id; - await ctx.newAuthUser(id: userId); - final other = await ctx.newUser(); - otherUserId = other.id; - }); - - test('does not duplicate assets when a partner shares the checksum', () async { - const checksum = 'shared-partner-checksum'; - final album = await ctx.newLocalAlbum(); - final local = await ctx.newLocalAsset(checksum: checksum); - await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); - final myRemote = await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - await ctx.newRemoteAsset(ownerId: otherUserId, checksum: checksum); - - final assets = await sut.localAlbum(album.id, .day).assetSource(0, 10); - - expect(assets, hasLength(1)); - final asset = assets.single as LocalAsset; - expect(asset.id, local.id); - // Must resolve the current user's remote id - expect(asset.remoteId, myRemote.id); - }); - - test('bucket count ignores a partner sharing the checksum', () async { - const checksum = 'shared-partner-checksum'; - final album = await ctx.newLocalAlbum(); - final local = await ctx.newLocalAsset(checksum: checksum); - await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - await ctx.newRemoteAsset(ownerId: otherUserId, checksum: checksum); - - final buckets = await sut.localAlbum(album.id, .day).bucketSource().first; - - expect(buckets, hasLength(1)); - expect(buckets.single.assetCount, 1); - }); - }); -} diff --git a/mobile/test/medium/repositories/trashed_local_asset_repository_test.dart b/mobile/test/medium/repositories/trashed_local_asset_repository_test.dart deleted file mode 100644 index 532502ca3d..0000000000 --- a/mobile/test/medium/repositories/trashed_local_asset_repository_test.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart'; - -import '../repository_context.dart'; - -void main() { - late MediumRepositoryContext ctx; - late TrashedLocalAssetRepository sut; - - setUp(() { - ctx = MediumRepositoryContext(); - sut = TrashedLocalAssetRepository(ctx.db); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('getToRestore', () { - late String userId; - - setUp(() async { - final user = await ctx.newUser(); - userId = user.id; - await ctx.newAuthUser(id: userId); - }); - - test('does not restore based on a partner\'s live remote copy', () async { - const checksum = 'shared-partner-checksum'; - final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); - final trashed = await ctx.newTrashedLocalAsset(albumId: album.id, checksum: checksum); - - // Current user's own remote copy is deleted; only a partner has an active copy. - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum, deletedAt: DateTime(2020, 1, 1)); - final partner = await ctx.newUser(); - await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum); - - final result = await sut.getToRestore(); - - final ids = result.map((a) => a.id); - expect(ids, isNot(contains(trashed.id))); - }); - - test('restores when the current user\'s own remote copy is live', () async { - const checksum = 'my-live-copy'; - final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); - final trashed = await ctx.newTrashedLocalAsset(albumId: album.id, checksum: checksum); - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - - final result = await sut.getToRestore(); - - final ids = result.map((a) => a.id); - expect(ids, contains(trashed.id)); - }); - }); - - group('getToTrash', () { - late String userId; - - setUp(() async { - final user = await ctx.newUser(); - userId = user.id; - await ctx.newAuthUser(id: userId); - }); - - Future addLocalAssetToBackupAlbum(String checksum) async { - final album = await ctx.newLocalAlbum(backupSelection: BackupSelection.selected); - final local = await ctx.newLocalAsset(checksum: checksum); - await ctx.newLocalAlbumAsset(albumId: album.id, assetId: local.id); - return local.id; - } - - test('does not trash when only a partner\'s remote copy is deleted', () async { - const checksum = 'shared-partner-checksum'; - final localId = await addLocalAssetToBackupAlbum(checksum); - - // Current user's own remote copy is live but a partner deleted theirs - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum); - final partner = await ctx.newUser(); - await ctx.newRemoteAsset(ownerId: partner.id, checksum: checksum, deletedAt: DateTime(2020, 1, 1)); - - final result = await sut.getToTrash(); - - final ids = result.values.expand((assets) => assets).map((a) => a.id); - expect(ids, isNot(contains(localId))); - }); - - test('trashes when the current user\'s own remote copy is deleted', () async { - const checksum = 'my-deleted-copy'; - final localId = await addLocalAssetToBackupAlbum(checksum); - await ctx.newRemoteAsset(ownerId: userId, checksum: checksum, deletedAt: DateTime(2020, 1, 1)); - - final result = await sut.getToTrash(); - - final ids = result.values.expand((assets) => assets).map((a) => a.id); - expect(ids, contains(localId)); - }); - }); -} diff --git a/mobile/test/medium/repository_context.dart b/mobile/test/medium/repository_context.dart index c2d7a1f380..2c4758400c 100644 --- a/mobile/test/medium/repository_context.dart +++ b/mobile/test/medium/repository_context.dart @@ -1,35 +1,26 @@ +import 'dart:math'; + import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/memory.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_album_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/partner.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/utils/option.dart'; import 'package:uuid/uuid.dart'; -import '../utils.dart'; - class MediumRepositoryContext { final Drift db; + final Random _random = Random(); MediumRepositoryContext() : db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); @@ -39,7 +30,7 @@ class MediumRepositoryContext { static Value _resolveUndefined(T? plain, Option? option, T fallback) { if (plain != null) { - return .new(plain); + return Value(plain); } return _resolveOption(option, fallback); @@ -50,7 +41,7 @@ class MediumRepositoryContext { return option.fold(Value.new, Value.absent); } - return .new(fallback); + return Value(fallback); } Future newUser({ @@ -60,45 +51,17 @@ class MediumRepositoryContext { DateTime? profileChangedAt, bool? hasProfileImage, }) async { - id ??= TestUtils.uuid(); + id = id ?? const Uuid().v4(); return await db .into(db.userEntity) .insertReturning( UserEntityCompanion( - id: .new(id), - email: .new(email ?? '$id@test.com'), - name: .new(email ?? 'user_$id'), - avatarColor: .new(avatarColor ?? TestUtils.randElement(AvatarColor.values)), - profileChangedAt: .new(TestUtils.date(profileChangedAt)), - hasProfileImage: .new(hasProfileImage ?? false), - ), - ); - } - - /// Seeds a user into `authUserEntity` as the currently authenticated user - Future newAuthUser({String? id, String? email, bool? isAdmin, AvatarColor? avatarColor}) async { - id ??= TestUtils.uuid(); - return db - .into(db.authUserEntity) - .insertReturning( - AuthUserEntityCompanion( - id: .new(id), - email: .new(email ?? '$id@test.com'), - name: .new('user_$id'), - isAdmin: .new(isAdmin ?? false), - avatarColor: .new(avatarColor ?? TestUtils.randElement(AvatarColor.values)), - ), - ); - } - - Future newPartner({required String sharedById, required String sharedWithId, bool? inTimeline}) { - return db - .into(db.partnerEntity) - .insert( - PartnerEntityCompanion( - sharedById: .new(sharedById), - sharedWithId: .new(sharedWithId), - inTimeline: .new(inTimeline ?? false), + id: Value(id), + email: Value(email ?? '$id@test.com'), + name: Value(email ?? 'user_$id'), + avatarColor: Value(avatarColor ?? AvatarColor.values[_random.nextInt(AvatarColor.values.length)]), + profileChangedAt: Value(profileChangedAt ?? DateTime.now()), + hasProfileImage: Value(hasProfileImage ?? false), ), ); } @@ -112,7 +75,7 @@ class MediumRepositoryContext { DateTime? deletedAt, AssetType? type, AssetVisibility? visibility, - int? durationMs, + int? durationInSeconds, int? width, int? height, bool? isFavorite, @@ -121,33 +84,32 @@ class MediumRepositoryContext { String? stackId, String? thumbHash, String? libraryId, - DateTime? localDateTime, }) async { - id ??= TestUtils.uuid(); - createdAt ??= TestUtils.date(); + id = id ?? const Uuid().v4(); + createdAt = createdAt ?? DateTime.now(); return db .into(db.remoteAssetEntity) .insertReturning( RemoteAssetEntityCompanion( - id: .new(id), - name: .new('remote_$id.jpg'), - checksum: .new(TestUtils.uuid(checksum)), - type: .new(type ?? .image), - createdAt: .new(createdAt), - updatedAt: .new(TestUtils.date(updatedAt)), - ownerId: .new(TestUtils.uuid(ownerId)), - visibility: .new(visibility ?? .timeline), - deletedAt: .new(deletedAt), - durationMs: .new(durationMs ?? 0), - width: .new(width ?? TestUtils.randInt(1000)), - height: .new(height ?? TestUtils.randInt(1000)), - isFavorite: .new(isFavorite ?? false), - isEdited: .new(isEdited ?? false), - livePhotoVideoId: .new(livePhotoVideoId), - stackId: .new(stackId), - localDateTime: .new(localDateTime ?? createdAt.toLocal()), - thumbHash: .new(TestUtils.uuid(thumbHash)), - libraryId: .new(TestUtils.uuid(libraryId)), + id: Value(id), + name: Value('remote_$id.jpg'), + checksum: Value(checksum ?? const Uuid().v4()), + type: Value(type ?? AssetType.image), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt ?? DateTime.now()), + ownerId: Value(ownerId ?? const Uuid().v4()), + visibility: Value(visibility ?? AssetVisibility.timeline), + deletedAt: Value(deletedAt), + durationInSeconds: Value(durationInSeconds ?? 0), + width: Value(width ?? _random.nextInt(1000)), + height: Value(height ?? _random.nextInt(1000)), + isFavorite: Value(isFavorite ?? false), + isEdited: Value(isEdited ?? false), + livePhotoVideoId: Value(livePhotoVideoId), + stackId: Value(stackId), + localDateTime: Value(createdAt.toLocal()), + thumbHash: Value(thumbHash ?? const Uuid().v4()), + libraryId: Value(libraryId ?? const Uuid().v4()), ), ); } @@ -165,12 +127,12 @@ class MediumRepositoryContext { .into(db.remoteAssetCloudIdEntity) .insertReturning( RemoteAssetCloudIdEntityCompanion( - assetId: .new(TestUtils.uuid(id)), - cloudId: .new(TestUtils.uuid(cloudId)), - createdAt: .new(TestUtils.date(createdAt)), + assetId: Value(id ?? const Uuid().v4()), + cloudId: Value(cloudId ?? const Uuid().v4()), + createdAt: Value(createdAt ?? DateTime.now()), adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), - latitude: _resolveOption(latitude, TestUtils.randDouble(-90, 90)), - longitude: _resolveOption(longitude, TestUtils.randDouble(-180, 180)), + latitude: _resolveOption(latitude, _random.nextDouble() * 180 - 90), + longitude: _resolveOption(longitude, _random.nextDouble() * 360 - 180), ), ); } @@ -186,81 +148,28 @@ class MediumRepositoryContext { AlbumAssetOrder? order, String? thumbnailAssetId, }) async { - id ??= TestUtils.uuid(); - final album = await db + id = id ?? const Uuid().v4(); + return db .into(db.remoteAlbumEntity) .insertReturning( RemoteAlbumEntityCompanion( - id: .new(id), - name: .new(name ?? 'remote_album_$id'), - createdAt: .new(TestUtils.date(createdAt)), - updatedAt: .new(TestUtils.date(updatedAt)), - description: .new(description ?? 'Description for album $id'), - isActivityEnabled: .new(isActivityEnabled ?? false), - order: .new(order ?? .asc), - thumbnailAssetId: .new(thumbnailAssetId), + id: Value(id), + name: Value(name ?? 'remote_album_$id'), + ownerId: Value(ownerId ?? const Uuid().v4()), + createdAt: Value(createdAt ?? DateTime.now()), + updatedAt: Value(updatedAt ?? DateTime.now()), + description: Value(description ?? 'Description for album $id'), + isActivityEnabled: Value(isActivityEnabled ?? false), + order: Value(order ?? AlbumAssetOrder.asc), + thumbnailAssetId: Value(thumbnailAssetId), ), ); - - await db - .into(db.remoteAlbumUserEntity) - .insert( - RemoteAlbumUserEntityCompanion( - albumId: .new(id), - userId: .new(TestUtils.uuid(ownerId)), - role: const .new(.owner), - ), - ); - - return album; } - Future newRemoteAlbumAsset({required String albumId, required String assetId}) { + Future insertRemoteAlbumAsset({required String albumId, required String assetId}) { return db .into(db.remoteAlbumAssetEntity) - .insert(RemoteAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId))); - } - - Future newPerson({String? id, String? ownerId, String? name, bool? isFavorite, bool? isHidden}) { - id ??= TestUtils.uuid(); - return db - .into(db.personEntity) - .insertReturning( - PersonEntityCompanion( - id: .new(id), - ownerId: .new(TestUtils.uuid(ownerId)), - name: .new(name ?? 'person_$id'), - isFavorite: .new(isFavorite ?? false), - isHidden: .new(isHidden ?? false), - ), - ); - } - - Future newFace({String? assetId, String? personId, int? imageWidth, int? imageHeight}) { - imageWidth ??= TestUtils.randInt(999) + 2; - imageHeight ??= TestUtils.randInt(999) + 2; - - final x1 = TestUtils.randInt(imageWidth - 1); - final y1 = TestUtils.randInt(imageHeight - 1); - final x2 = x1 + 1 + TestUtils.randInt(imageWidth - x1 - 1); - final y2 = y1 + 1 + TestUtils.randInt(imageHeight - y1 - 1); - - return db - .into(db.assetFaceEntity) - .insertReturning( - AssetFaceEntityCompanion( - id: .new(TestUtils.uuid()), - assetId: .new(TestUtils.uuid(assetId)), - personId: .new(TestUtils.uuid(personId)), - imageWidth: .new(imageWidth), - imageHeight: .new(imageHeight), - boundingBoxX1: .new(x1), - boundingBoxY1: .new(y1), - boundingBoxX2: .new(x2), - boundingBoxY2: .new(y2), - sourceType: const .new('machine-learning'), - ), - ); + .insert(RemoteAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId)); } Future newLocalAsset({ @@ -278,57 +187,30 @@ class MediumRepositoryContext { double? longitude, int? width, int? height, - int? durationMs, + int? durationInSeconds, int? orientation, DateTime? updatedAt, }) async { - id ??= TestUtils.uuid(); + id = id ?? const Uuid().v4(); return db .into(db.localAssetEntity) .insertReturning( LocalAssetEntityCompanion( - id: .new(id), - name: .new(name ?? 'local_$id.jpg'), - height: .new(height ?? TestUtils.randInt(1000)), - width: .new(width ?? TestUtils.randInt(1000)), - durationMs: .new(durationMs ?? 0), - orientation: .new(orientation ?? 0), - updatedAt: .new(TestUtils.date(updatedAt)), + id: Value(id), + name: Value(name ?? 'local_$id.jpg'), + height: Value(height ?? _random.nextInt(1000)), + width: Value(width ?? _random.nextInt(1000)), + durationInSeconds: Value(durationInSeconds ?? 0), + orientation: Value(orientation ?? 0), + updatedAt: Value(updatedAt ?? DateTime.now()), checksum: _resolveUndefined(checksum, checksumOption, const Uuid().v4()), - createdAt: .new(TestUtils.date(createdAt)), - type: .new(type ?? .image), - isFavorite: .new(isFavorite ?? false), - iCloudId: .new(TestUtils.uuid(iCloudId)), + createdAt: Value(createdAt ?? DateTime.now()), + type: Value(type ?? AssetType.image), + isFavorite: Value(isFavorite ?? false), + iCloudId: Value(iCloudId ?? const Uuid().v4()), adjustmentTime: _resolveUndefined(adjustmentTime, adjustmentTimeOption, DateTime.now()), - latitude: .new(latitude ?? TestUtils.randDouble(-90, 90)), - longitude: .new(longitude ?? TestUtils.randDouble(-180, 180)), - ), - ); - } - - /// Seeds a trashed local asset into `trashedLocalAssetEntity` - Future newTrashedLocalAsset({ - String? id, - required String albumId, - String? checksum, - TrashOrigin? source, - AssetType? type, - DateTime? createdAt, - bool? isFavorite, - }) async { - id ??= TestUtils.uuid(); - return db - .into(db.trashedLocalAssetEntity) - .insertReturning( - TrashedLocalAssetEntityCompanion( - id: .new(id), - albumId: .new(albumId), - name: .new('trashed_$id.jpg'), - type: .new(type ?? .image), - checksum: .new(checksum), - source: .new(source ?? TrashOrigin.remoteSync), - isFavorite: .new(isFavorite ?? false), - createdAt: .new(TestUtils.date(createdAt)), + latitude: Value(latitude ?? _random.nextDouble() * 180 - 90), + longitude: Value(longitude ?? _random.nextDouble() * 360 - 180), ), ); } @@ -341,55 +223,24 @@ class MediumRepositoryContext { bool? isIosSharedAlbum, String? linkedRemoteAlbumId, }) { - id ??= TestUtils.uuid(); + id = id ?? const Uuid().v4(); return db .into(db.localAlbumEntity) .insertReturning( LocalAlbumEntityCompanion( - id: .new(id), - name: .new(name ?? 'local_album_$id'), - updatedAt: .new(TestUtils.date(updatedAt)), - backupSelection: .new(backupSelection ?? .none), - isIosSharedAlbum: .new(isIosSharedAlbum ?? false), - linkedRemoteAlbumId: .new(linkedRemoteAlbumId), + id: Value(id), + name: Value(name ?? 'local_album_$id'), + updatedAt: Value(updatedAt ?? DateTime.now()), + backupSelection: Value(backupSelection ?? BackupSelection.none), + isIosSharedAlbum: Value(isIosSharedAlbum ?? false), + linkedRemoteAlbumId: Value(linkedRemoteAlbumId), ), ); } - Future newLocalAlbumAsset({required String albumId, required String assetId}) => db - .into(db.localAlbumAssetEntity) - .insert(LocalAlbumAssetEntityCompanion(albumId: .new(albumId), assetId: .new(assetId))); - - Future newMemory({ - String? id, - String? ownerId, - MemoryTypeEnum? type, - int? year, - DateTime? memoryAt, - DateTime? showAt, - DateTime? hideAt, - DateTime? deletedAt, - bool? isSaved, - }) async { - id ??= TestUtils.uuid(); + Future newLocalAlbumAsset({required String albumId, required String assetId}) { return db - .into(db.memoryEntity) - .insertReturning( - MemoryEntityCompanion( - id: .new(id), - ownerId: .new(TestUtils.uuid(ownerId)), - type: .new(type ?? MemoryTypeEnum.onThisDay), - data: .new(MemoryData(year: year ?? 2020).toJson()), - isSaved: .new(isSaved ?? false), - memoryAt: .new(TestUtils.date(memoryAt)), - showAt: .new(showAt), - hideAt: .new(hideAt), - deletedAt: .new(deletedAt), - ), - ); + .into(db.localAlbumAssetEntity) + .insert(LocalAlbumAssetEntityCompanion.insert(albumId: albumId, assetId: assetId)); } - - Future newMemoryAsset({required String memoryId, required String assetId}) => db - .into(db.memoryAssetEntity) - .insert(MemoryAssetEntityCompanion(memoryId: .new(memoryId), assetId: .new(assetId))); } diff --git a/mobile/test/medium/service_context.dart b/mobile/test/medium/service_context.dart deleted file mode 100644 index 6f90b3e344..0000000000 --- a/mobile/test/medium/service_context.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/partner.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/user.repository.dart'; -import 'package:immich_mobile/repositories/partner_api.repository.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../api.mocks.dart'; -import '../utils.dart'; -import 'repository_context.dart'; - -void _stubPartnerApi(MockPartnerApiRepository api) { - final id = TestUtils.uuid(); - final partner = UserDto(id: id, email: '$id@example.com', name: 'name $id', profileChangedAt: TestUtils.now()); - - registerFallbackValue(Direction.sharedByMe); - when(() => api.getAll(any())).thenAnswer((_) async => const []); - when(() => api.create(any())).thenAnswer((_) async => partner); - when(() => api.update(any(), inTimeline: any(named: 'inTimeline'))).thenAnswer((_) async => partner); - when(() => api.delete(any())).thenAnswer((_) async {}); -} - -class MediumServiceContext extends MediumRepositoryContext { - late final UserRepository userRepository = UserRepository(db); - late final PartnerRepository partnerRepository = PartnerRepository(db); - - final partnerApi = MockPartnerApiRepository(); - - MediumServiceContext() { - _stubPartnerApi(partnerApi); - } -} diff --git a/mobile/test/medium/services/partner_service_test.dart b/mobile/test/medium/services/partner_service_test.dart deleted file mode 100644 index 4c31c9212e..0000000000 --- a/mobile/test/medium/services/partner_service_test.dart +++ /dev/null @@ -1,110 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/services/partner.service.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../service_context.dart'; - -void main() { - late MediumServiceContext ctx; - late PartnerService sut; - - setUp(() { - ctx = MediumServiceContext(); - sut = PartnerService(ctx.userRepository, ctx.partnerRepository, ctx.partnerApi); - }); - - tearDown(() async { - await ctx.dispose(); - }); - - group('getCandidates', () { - test('returns the other users and excludes the current user', () async { - final me = await ctx.newUser(); - final other = await ctx.newUser(); - - final result = await sut.getCandidates(me.id).first; - - expect(result.map((user) => user.id), unorderedEquals([other.id])); - }); - - test('excludes users the current user already shares with', () async { - final me = await ctx.newUser(); - final partner = await ctx.newUser(); - final other = await ctx.newUser(); - await ctx.newPartner(sharedById: me.id, sharedWithId: partner.id); - - final result = await sut.getCandidates(me.id).first; - - expect(result.map((user) => user.id), unorderedEquals([other.id])); - }); - - test('includes users who share with the current user but are not shared with back', () async { - final me = await ctx.newUser(); - final inbound = await ctx.newUser(); - await ctx.newPartner(sharedById: inbound.id, sharedWithId: me.id); - - final result = await sut.getCandidates(me.id).first; - - expect(result.map((user) => user.id), unorderedEquals([inbound.id])); - }); - - test('emits an updated list when the current user adds a partner', () async { - final me = await ctx.newUser(); - final a = await ctx.newUser(); - final b = await ctx.newUser(); - - final ids = sut.getCandidates(me.id).map((users) => users.map((user) => user.id).toList()); - final expectation = expectLater( - ids, - emitsInOrder([ - unorderedEquals([a.id, b.id]), - unorderedEquals([b.id]), - ]), - ); - - await ctx.newPartner(sharedById: me.id, sharedWithId: a.id); - await expectation; - }); - }); - - group('create', () { - test('calls the API then persists the partnership locally', () async { - final me = await ctx.newUser(); - final partner = await ctx.newUser(); - - await sut.create(sharedById: me.id, sharedWithId: partner.id); - - verify(() => ctx.partnerApi.create(partner.id)).called(1); - final shared = await sut.search(me.id, .sharedBy).first; - expect(shared.map((p) => p.id), unorderedEquals([partner.id])); - }); - }); - - group('delete', () { - test('calls the API then removes the partnership locally', () async { - final me = await ctx.newUser(); - final recipient = await ctx.newUser(); - await ctx.newPartner(sharedById: me.id, sharedWithId: recipient.id); - - await sut.delete(sharedById: me.id, sharedWithId: recipient.id); - - verify(() => ctx.partnerApi.delete(recipient.id)).called(1); - final shared = await sut.search(me.id, .sharedBy).first; - expect(shared, isEmpty); - }); - }); - - group('update', () { - test('calls the API then updates the inTimeline flag locally', () async { - final me = await ctx.newUser(); - final sharer = await ctx.newUser(); - await ctx.newPartner(sharedById: sharer.id, sharedWithId: me.id, inTimeline: false); - - await sut.update(sharedById: sharer.id, sharedWithId: me.id, inTimeline: true); - - verify(() => ctx.partnerApi.update(sharer.id, inTimeline: true)).called(1); - final partner = await ctx.partnerRepository.get(sharedById: sharer.id, sharedWithId: me.id); - expect(partner.inTimeline, isTrue); - }); - }); -} diff --git a/mobile/test/modules/activity/activities_page_test.dart b/mobile/test/modules/activity/activities_page_test.dart new file mode 100644 index 0000000000..39350530ea --- /dev/null +++ b/mobile/test/modules/activity/activities_page_test.dart @@ -0,0 +1,175 @@ +@Skip('currently failing due to mock HTTP client to download ISAR binaries') +@Tags(['widget']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/pages/common/activities.page.dart'; +import 'package:immich_mobile/providers/activity.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/user.provider.dart'; +import 'package:immich_mobile/widgets/activities/activity_text_field.dart'; +import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../fixtures/album.stub.dart'; +import '../../fixtures/asset.stub.dart'; +import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; +import '../../widget_tester_extensions.dart'; +import '../album/album_mocks.dart'; +import '../asset_viewer/asset_viewer_mocks.dart'; +import '../shared/shared_mocks.dart'; +import 'activity_mocks.dart'; + +final _activities = [ + Activity( + id: '1', + createdAt: DateTime(100), + type: ActivityType.comment, + comment: 'First Activity', + assetId: 'asset-2', + user: UserStub.admin, + ), + Activity( + id: '2', + createdAt: DateTime(200), + type: ActivityType.comment, + comment: 'Second Activity', + user: UserStub.user1, + ), + Activity(id: '3', createdAt: DateTime(300), type: ActivityType.like, assetId: 'asset-1', user: UserStub.user2), + Activity(id: '4', createdAt: DateTime(400), type: ActivityType.like, user: UserStub.user1), +]; + +void main() { + late MockAlbumActivity activityMock; + late MockCurrentAlbumProvider mockCurrentAlbumProvider; + late MockCurrentAssetProvider mockCurrentAssetProvider; + late List overrides; + late Isar db; + + setUpAll(() async { + TestUtils.init(); + db = await TestUtils.initIsar(); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); + await Store.put(StoreKey.accessToken, ''); + }); + + setUp(() async { + mockCurrentAlbumProvider = MockCurrentAlbumProvider(AlbumStub.twoAsset); + mockCurrentAssetProvider = MockCurrentAssetProvider(AssetStub.image1); + activityMock = MockAlbumActivity(_activities); + overrides = [ + albumActivityProvider(AlbumStub.twoAsset.remoteId!, AssetStub.image1.remoteId!).overrideWith(() => activityMock), + currentAlbumProvider.overrideWith(() => mockCurrentAlbumProvider), + currentAssetProvider.overrideWith(() => mockCurrentAssetProvider), + ]; + + await db.writeTxn(() async { + await db.clear(); + // Save all assets + await db.users.put(User.fromDto(UserStub.admin)); + await db.assets.putAll([AssetStub.image1, AssetStub.image2]); + await db.albums.put(AlbumStub.twoAsset); + await AlbumStub.twoAsset.owner.save(); + await AlbumStub.twoAsset.assets.save(); + }); + expect(db.albums.countSync(), 1); + expect(db.assets.countSync(), 2); + expect(db.users.countSync(), 1); + }); + + group("App bar", () { + testWidgets("No title when currentAsset != null", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + + final listTile = tester.widget(find.byType(AppBar)); + expect(listTile.title, isNull); + }); + + testWidgets("Album name as title when currentAsset == null", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + await tester.pumpAndSettle(); + + mockCurrentAssetProvider.state = null; + await tester.pumpAndSettle(); + + expect(find.text(AlbumStub.twoAsset.name), findsOneWidget); + final listTile = tester.widget(find.byType(AppBar)); + expect(listTile.title, isNotNull); + }); + }); + + group("Body", () { + testWidgets("Contains a stack with Activity List and Activity Input", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + await tester.pumpAndSettle(); + + expect(find.descendant(of: find.byType(Stack), matching: find.byType(ActivityTextField)), findsOneWidget); + + expect(find.descendant(of: find.byType(Stack), matching: find.byType(ListView)), findsOneWidget); + }); + + testWidgets("List Contains all dismissible activities", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + await tester.pumpAndSettle(); + + final listFinder = find.descendant(of: find.byType(Stack), matching: find.byType(ListView)); + final listChildren = find.descendant(of: listFinder, matching: find.byType(DismissibleActivity)); + expect(listChildren, findsNWidgets(_activities.length)); + }); + + testWidgets("Submitting text input adds a comment with the text", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + await tester.pumpAndSettle(); + + when(() => activityMock.addComment(any())).thenAnswer((_) => Future.value()); + + final textField = find.byType(TextField); + await tester.enterText(textField, 'Test comment'); + await tester.testTextInput.receiveAction(TextInputAction.done); + + verify(() => activityMock.addComment('Test comment')); + }); + + testWidgets("Owner can remove all activities", (tester) async { + await tester.pumpConsumerWidget(const ActivitiesPage(), overrides: overrides); + await tester.pumpAndSettle(); + + final deletableActivityFinder = find.byWidgetPredicate( + (widget) => widget is DismissibleActivity && widget.onDismiss != null, + ); + expect(deletableActivityFinder, findsNWidgets(_activities.length)); + }); + + testWidgets("Non-Owner can remove only their activities", (tester) async { + final mockCurrentUser = MockCurrentUserProvider(); + + await tester.pumpConsumerWidget( + const ActivitiesPage(), + overrides: [...overrides, currentUserProvider.overrideWith((ref) => mockCurrentUser)], + ); + mockCurrentUser.state = UserStub.user1; + await tester.pumpAndSettle(); + + final deletableActivityFinder = find.byWidgetPredicate( + (widget) => widget is DismissibleActivity && widget.onDismiss != null, + ); + expect(deletableActivityFinder, findsNWidgets(_activities.where((a) => a.user == UserStub.user1).length)); + }); + }); +} diff --git a/mobile/test/modules/activity/activity_mocks.dart b/mobile/test/modules/activity/activity_mocks.dart new file mode 100644 index 0000000000..c50810795e --- /dev/null +++ b/mobile/test/modules/activity/activity_mocks.dart @@ -0,0 +1,19 @@ +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; +import 'package:immich_mobile/providers/activity_statistics.provider.dart'; +import 'package:immich_mobile/services/activity.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class ActivityServiceMock extends Mock implements ActivityService {} + +class MockAlbumActivity extends AlbumActivityInternal with Mock implements AlbumActivity { + List? initActivities; + MockAlbumActivity([this.initActivities]); + + @override + Future> build(String albumId, [String? assetId]) async { + return initActivities ?? []; + } +} + +class ActivityStatisticsMock extends ActivityStatisticsInternal with Mock implements ActivityStatistics {} diff --git a/mobile/test/modules/activity/activity_provider_test.dart b/mobile/test/modules/activity/activity_provider_test.dart new file mode 100644 index 0000000000..84eba62b70 --- /dev/null +++ b/mobile/test/modules/activity/activity_provider_test.dart @@ -0,0 +1,331 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; +import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:immich_mobile/providers/activity_statistics.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; +import 'activity_mocks.dart'; + +final _activities = [ + Activity( + id: '1', + createdAt: DateTime(100), + type: ActivityType.comment, + comment: 'First Activity', + assetId: 'asset-2', + user: UserStub.admin, + ), + Activity( + id: '2', + createdAt: DateTime(200), + type: ActivityType.comment, + comment: 'Second Activity', + user: UserStub.user1, + ), + Activity(id: '3', createdAt: DateTime(300), type: ActivityType.like, assetId: 'asset-1', user: UserStub.admin), + Activity(id: '4', createdAt: DateTime(400), type: ActivityType.like, user: UserStub.user1), +]; + +void main() { + late ActivityServiceMock activityMock; + late ActivityStatisticsMock activityStatisticsMock; + late ActivityStatisticsMock albumActivityStatisticsMock; + late ProviderContainer container; + late AlbumActivityProvider provider; + late ListenerMock>> listener; + + setUpAll(() { + registerFallbackValue(AsyncData>([..._activities])); + }); + + setUp(() async { + activityMock = ActivityServiceMock(); + activityStatisticsMock = ActivityStatisticsMock(); + albumActivityStatisticsMock = ActivityStatisticsMock(); + + container = TestUtils.createContainer( + overrides: [ + activityServiceProvider.overrideWith((ref) => activityMock), + activityStatisticsProvider('test-album', 'test-asset').overrideWith(() => activityStatisticsMock), + activityStatisticsProvider('test-album').overrideWith(() => albumActivityStatisticsMock), + ], + ); + + // Mock values + when(() => activityStatisticsMock.build(any(), any())).thenReturn(0); + when(() => albumActivityStatisticsMock.build(any())).thenReturn(0); + when( + () => activityMock.getAllActivities('test-album', assetId: 'test-asset'), + ).thenAnswer((_) async => [..._activities]); + when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); + + // Init and wait for providers future to complete + provider = albumActivityProvider('test-album', 'test-asset'); + listener = ListenerMock(); + container.listen(provider, listener.call, fireImmediately: true); + + await container.read(provider.future); + }); + + test('Returns a list of activity', () async { + verifyInOrder([ + () => listener.call(null, const AsyncLoading()), + () => listener.call( + const AsyncLoading(), + any( + that: allOf([ + isA>>(), + predicate((AsyncData> ad) => ad.requireValue.every((e) => _activities.contains(e))), + ]), + ), + ), + ]); + + verifyNoMoreInteractions(listener); + }); + + group('addLike()', () { + test('Like successfully added', () async { + final like = Activity(id: '5', createdAt: DateTime(2023), type: ActivityType.like, user: UserStub.admin); + + when( + () => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'), + ).thenAnswer((_) async => AsyncData(like)); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + await container.read(provider.notifier).addLike(); + + verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset')); + + final activities = await container.read(provider.future); + expect(activities, hasLength(5)); + expect(activities, contains(like)); + + // Never bump activity count for new likes + verifyNever(() => activityStatisticsMock.addActivity()); + verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(5)); + expect(albumActivities, contains(like)); + }); + + test('Like failed', () async { + final like = Activity(id: '5', createdAt: DateTime(2023), type: ActivityType.like, user: UserStub.admin); + when( + () => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset'), + ).thenAnswer((_) async => AsyncError(Exception('Mock'), StackTrace.current)); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + await container.read(provider.notifier).addLike(); + + verify(() => activityMock.addActivity('test-album', ActivityType.like, assetId: 'test-asset')); + + final activities = await container.read(provider.future); + expect(activities, hasLength(4)); + expect(activities, isNot(contains(like))); + + verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(4)); + expect(albumActivities, isNot(contains(like))); + }); + }); + + group('removeActivity()', () { + test('Like successfully removed', () async { + when(() => activityMock.removeActivity('3')).thenAnswer((_) async => true); + + await container.read(provider.notifier).removeActivity('3'); + + verify(() => activityMock.removeActivity('3')); + + final activities = await container.read(provider.future); + expect(activities, hasLength(3)); + expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); + + verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); + }); + + test('Remove Like failed', () async { + when(() => activityMock.removeActivity('3')).thenAnswer((_) async => false); + + await container.read(provider.notifier).removeActivity('3'); + + final activities = await container.read(provider.future); + expect(activities, hasLength(4)); + expect(activities, anyElement(predicate((Activity a) => a.id == '3'))); + + verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); + }); + + test('Comment successfully removed', () async { + when(() => activityMock.removeActivity('1')).thenAnswer((_) async => true); + + await container.read(provider.notifier).removeActivity('1'); + + final activities = await container.read(provider.future); + expect(activities, isNot(anyElement(predicate((Activity a) => a.id == '1')))); + + verify(() => activityStatisticsMock.removeActivity()); + verify(() => albumActivityStatisticsMock.removeActivity()); + }); + + test('Removes activity from album state when asset scoped', () async { + when(() => activityMock.removeActivity('3')).thenAnswer((_) async => true); + when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + await container.read(provider.notifier).removeActivity('3'); + + final assetActivities = container.read(provider).requireValue; + final albumActivities = container.read(albumProvider).requireValue; + + expect(assetActivities, hasLength(3)); + expect(assetActivities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); + + expect(albumActivities, hasLength(3)); + expect(albumActivities, isNot(anyElement(predicate((Activity a) => a.id == '3')))); + + verify(() => activityMock.removeActivity('3')); + verifyNever(() => activityStatisticsMock.removeActivity()); + verifyNever(() => albumActivityStatisticsMock.removeActivity()); + }); + }); + + group('addComment()', () { + test('Comment successfully added', () async { + final comment = Activity( + id: '5', + createdAt: DateTime(2023), + type: ActivityType.comment, + user: UserStub.admin, + comment: 'Test-Comment', + assetId: 'test-asset', + ); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + when( + () => activityMock.addActivity( + 'test-album', + ActivityType.comment, + assetId: 'test-asset', + comment: 'Test-Comment', + ), + ).thenAnswer((_) async => AsyncData(comment)); + when(() => activityStatisticsMock.build('test-album', 'test-asset')).thenReturn(4); + when(() => albumActivityStatisticsMock.build('test-album')).thenReturn(2); + + await container.read(provider.notifier).addComment('Test-Comment'); + + verify( + () => activityMock.addActivity( + 'test-album', + ActivityType.comment, + assetId: 'test-asset', + comment: 'Test-Comment', + ), + ); + + final activities = await container.read(provider.future); + expect(activities, hasLength(5)); + expect(activities, contains(comment)); + + verify(() => activityStatisticsMock.addActivity()); + verify(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(5)); + expect(albumActivities, contains(comment)); + }); + + test('Comment successfully added without assetId', () async { + final comment = Activity( + id: '5', + createdAt: DateTime(2023), + type: ActivityType.comment, + user: UserStub.admin, + assetId: 'test-asset', + comment: 'Test-Comment', + ); + + when( + () => activityMock.addActivity('test-album', ActivityType.comment, comment: 'Test-Comment'), + ).thenAnswer((_) async => AsyncData(comment)); + when(() => albumActivityStatisticsMock.build('test-album')).thenReturn(2); + when(() => activityMock.getAllActivities('test-album')).thenAnswer((_) async => [..._activities]); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + await container.read(albumProvider.notifier).addComment('Test-Comment'); + + verify( + () => activityMock.addActivity('test-album', ActivityType.comment, assetId: null, comment: 'Test-Comment'), + ); + + final activities = await container.read(albumProvider.future); + expect(activities, hasLength(5)); + expect(activities, contains(comment)); + + verifyNever(() => activityStatisticsMock.addActivity()); + verify(() => albumActivityStatisticsMock.addActivity()); + }); + + test('Comment failed', () async { + final comment = Activity( + id: '5', + createdAt: DateTime(2023), + type: ActivityType.comment, + user: UserStub.admin, + comment: 'Test-Comment', + assetId: 'test-asset', + ); + + when( + () => activityMock.addActivity( + 'test-album', + ActivityType.comment, + assetId: 'test-asset', + comment: 'Test-Comment', + ), + ).thenAnswer((_) async => AsyncError(Exception('Error'), StackTrace.current)); + + final albumProvider = albumActivityProvider('test-album'); + container.read(albumProvider.notifier); + await container.read(albumProvider.future); + + await container.read(provider.notifier).addComment('Test-Comment'); + + final activities = await container.read(provider.future); + expect(activities, hasLength(4)); + expect(activities, isNot(contains(comment))); + + verifyNever(() => activityStatisticsMock.addActivity()); + verifyNever(() => albumActivityStatisticsMock.addActivity()); + + final albumActivities = container.read(albumProvider).requireValue; + expect(albumActivities, hasLength(4)); + expect(albumActivities, isNot(contains(comment))); + }); + }); +} diff --git a/mobile/test/modules/activity/activity_statistics_provider_test.dart b/mobile/test/modules/activity/activity_statistics_provider_test.dart new file mode 100644 index 0000000000..7fe73868f5 --- /dev/null +++ b/mobile/test/modules/activity/activity_statistics_provider_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/activity_service.provider.dart'; +import 'package:immich_mobile/providers/activity_statistics.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../test_utils.dart'; +import 'activity_mocks.dart'; + +void main() { + late ActivityServiceMock activityMock; + late ProviderContainer container; + late ListenerMock listener; + + setUp(() async { + activityMock = ActivityServiceMock(); + container = TestUtils.createContainer(overrides: [activityServiceProvider.overrideWith((ref) => activityMock)]); + listener = ListenerMock(); + }); + + test('Returns the proper count family', () async { + when( + () => activityMock.getStatistics('test-album', assetId: 'test-asset'), + ).thenAnswer((_) async => const ActivityStats(comments: 5)); + + // Read here to make the getStatistics call + container.read(activityStatisticsProvider('test-album', 'test-asset')); + + container.listen(activityStatisticsProvider('test-album', 'test-asset'), listener.call, fireImmediately: true); + + // Sleep for the getStatistics future to resolve + await Future.delayed(const Duration(milliseconds: 1)); + + verifyInOrder([() => listener.call(null, 0), () => listener.call(0, 5)]); + + verifyNoMoreInteractions(listener); + }); + + test('Adds activity', () async { + when(() => activityMock.getStatistics('test-album')).thenAnswer((_) async => const ActivityStats(comments: 10)); + + final provider = activityStatisticsProvider('test-album'); + container.listen(provider, listener.call, fireImmediately: true); + + // Sleep for the getStatistics future to resolve + await Future.delayed(const Duration(milliseconds: 1)); + + container.read(provider.notifier).addActivity(); + container.read(provider.notifier).addActivity(); + + expect(container.read(provider), 12); + }); + + test('Removes activity', () async { + when( + () => activityMock.getStatistics('new-album', assetId: 'test-asset'), + ).thenAnswer((_) async => const ActivityStats(comments: 10)); + + final provider = activityStatisticsProvider('new-album', 'test-asset'); + container.listen(provider, listener.call, fireImmediately: true); + + // Sleep for the getStatistics future to resolve + await Future.delayed(const Duration(milliseconds: 1)); + + container.read(provider.notifier).removeActivity(); + container.read(provider.notifier).removeActivity(); + + expect(container.read(provider), 8); + }); +} diff --git a/mobile/test/modules/activity/activity_text_field_test.dart b/mobile/test/modules/activity/activity_text_field_test.dart new file mode 100644 index 0000000000..4f4a2c7068 --- /dev/null +++ b/mobile/test/modules/activity/activity_text_field_test.dart @@ -0,0 +1,149 @@ +@Skip('currently failing due to mock HTTP client to download ISAR binaries') +@Tags(['widget']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/providers/activity.provider.dart'; +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:immich_mobile/widgets/activities/activity_text_field.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../fixtures/album.stub.dart'; +import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; +import '../../widget_tester_extensions.dart'; +import '../album/album_mocks.dart'; +import '../shared/shared_mocks.dart'; +import 'activity_mocks.dart'; + +void main() { + late Isar db; + late MockCurrentAlbumProvider mockCurrentAlbumProvider; + late MockAlbumActivity activityMock; + late List overrides; + + setUpAll(() async { + TestUtils.init(); + db = await TestUtils.initIsar(); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); + }); + + setUp(() { + mockCurrentAlbumProvider = MockCurrentAlbumProvider(AlbumStub.twoAsset); + activityMock = MockAlbumActivity(); + overrides = [ + currentAlbumProvider.overrideWith(() => mockCurrentAlbumProvider), + albumActivityProvider(AlbumStub.twoAsset.remoteId!).overrideWith(() => activityMock), + ]; + }); + + testWidgets('Returns an Input text field', (tester) async { + await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides); + + expect(find.byType(TextField), findsOneWidget); + }); + + testWidgets('No UserCircleAvatar when user == null', (tester) async { + final userProvider = MockCurrentUserProvider(); + + await tester.pumpConsumerWidget( + ActivityTextField(onSubmit: (_) {}), + overrides: [currentUserProvider.overrideWith((ref) => userProvider), ...overrides], + ); + + expect(find.byType(UserCircleAvatar), findsNothing); + }); + + testWidgets('UserCircleAvatar displayed when user != null', (tester) async { + await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides); + + expect(find.byType(UserCircleAvatar), findsOneWidget); + }); + + testWidgets('Filled icon if likedId != null', (tester) async { + await tester.pumpConsumerWidget( + ActivityTextField(onSubmit: (_) {}, likeId: '1'), + overrides: overrides, + ); + + expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsOneWidget); + expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsNothing); + }); + + testWidgets('Bordered icon if likedId == null', (tester) async { + await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides); + + expect(find.widgetWithIcon(IconButton, Icons.thumb_up_off_alt), findsOneWidget); + expect(find.widgetWithIcon(IconButton, Icons.thumb_up), findsNothing); + }); + + testWidgets('Adds new like', (tester) async { + await tester.pumpConsumerWidget(ActivityTextField(onSubmit: (_) {}), overrides: overrides); + + when(() => activityMock.addLike()).thenAnswer((_) => Future.value()); + + final suffixIcon = find.byType(IconButton); + await tester.tap(suffixIcon); + + verify(() => activityMock.addLike()); + }); + + testWidgets('Removes like if already liked', (tester) async { + await tester.pumpConsumerWidget( + ActivityTextField(onSubmit: (_) {}, likeId: 'test-suffix'), + overrides: overrides, + ); + + when(() => activityMock.removeActivity(any())).thenAnswer((_) => Future.value()); + + final suffixIcon = find.byType(IconButton); + await tester.tap(suffixIcon); + + verify(() => activityMock.removeActivity('test-suffix')); + }); + + testWidgets('Passes text entered to onSubmit on submit', (tester) async { + String? receivedText; + + await tester.pumpConsumerWidget( + ActivityTextField(onSubmit: (text) => receivedText = text, likeId: 'test-suffix'), + overrides: overrides, + ); + + final textField = find.byType(TextField); + await tester.enterText(textField, 'This is a test comment'); + await tester.testTextInput.receiveAction(TextInputAction.done); + expect(receivedText, 'This is a test comment'); + }); + + testWidgets('Input disabled when isEnabled false', (tester) async { + String? receviedText; + + await tester.pumpConsumerWidget( + ActivityTextField(onSubmit: (text) => receviedText = text, isEnabled: false, likeId: 'test-suffix'), + overrides: overrides, + ); + + final suffixIcon = find.byType(IconButton); + await tester.tap(suffixIcon, warnIfMissed: false); + + final textField = find.byType(TextField); + await tester.enterText(textField, 'This is a test comment'); + await tester.testTextInput.receiveAction(TextInputAction.done); + + expect(receviedText, isNull); + verifyNever(() => activityMock.addLike()); + verifyNever(() => activityMock.removeActivity(any())); + }); +} diff --git a/mobile/test/modules/activity/activity_tile_test.dart b/mobile/test/modules/activity/activity_tile_test.dart new file mode 100644 index 0000000000..538e3c0911 --- /dev/null +++ b/mobile/test/modules/activity/activity_tile_test.dart @@ -0,0 +1,165 @@ +@Skip('currently failing due to mock HTTP client to download ISAR binaries') +@Tags(['widget']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/widgets/activities/activity_tile.dart'; +import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; +import 'package:isar/isar.dart'; + +import '../../fixtures/asset.stub.dart'; +import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; +import '../../widget_tester_extensions.dart'; +import '../asset_viewer/asset_viewer_mocks.dart'; + +void main() { + late MockCurrentAssetProvider assetProvider; + late List overrides; + late Isar db; + + setUpAll(() async { + TestUtils.init(); + db = await TestUtils.initIsar(); + // For UserCircleAvatar + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await Store.put(StoreKey.currentUser, UserStub.admin); + await Store.put(StoreKey.serverEndpoint, ''); + await Store.put(StoreKey.accessToken, ''); + }); + + setUp(() { + assetProvider = MockCurrentAssetProvider(); + overrides = [currentAssetProvider.overrideWith(() => assetProvider)]; + }); + + testWidgets('Returns a ListTile', (tester) async { + await tester.pumpConsumerWidget( + ActivityTile(Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin)), + overrides: overrides, + ); + + expect(find.byType(ListTile), findsOneWidget); + }); + + testWidgets('No trailing widget when activity assetId == null', (tester) async { + await tester.pumpConsumerWidget( + ActivityTile(Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin)), + overrides: overrides, + ); + + final listTile = tester.widget(find.byType(ListTile)); + expect(listTile.trailing, isNull); + }); + + testWidgets('Asset Thumbanil as trailing widget when activity assetId != null', (tester) async { + await tester.pumpConsumerWidget( + ActivityTile( + Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin, assetId: '1'), + ), + overrides: overrides, + ); + + final listTile = tester.widget(find.byType(ListTile)); + expect(listTile.trailing, isNotNull); + // TODO: Validate this to be the common class after migrating ActivityTile#_ActivityAssetThumbnail to a common class + }); + + testWidgets('No trailing widget when current asset != null', (tester) async { + await tester.pumpConsumerWidget( + ActivityTile( + Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin, assetId: '1'), + ), + overrides: overrides, + ); + + assetProvider.state = AssetStub.image1; + await tester.pumpAndSettle(); + + final listTile = tester.widget(find.byType(ListTile)); + expect(listTile.trailing, isNull); + }); + + group('Like Activity', () { + final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin); + + testWidgets('Like contains filled thumbs-up as leading', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + // Leading widget should not be null + final listTile = tester.widget(find.byType(ListTile)); + expect(listTile.leading, isNotNull); + + // And should have a thumb_up icon + final thumbUpIconFinder = find.widgetWithIcon(listTile.leading!.runtimeType, Icons.thumb_up); + + expect(thumbUpIconFinder, findsOneWidget); + }); + + testWidgets('Like title is center aligned', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + final listTile = tester.widget(find.byType(ListTile)); + + expect(listTile.titleAlignment, ListTileTitleAlignment.center); + }); + + testWidgets('No subtitle for likes', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + final listTile = tester.widget(find.byType(ListTile)); + + expect(listTile.subtitle, isNull); + }); + }); + + group('Comment Activity', () { + final activity = Activity( + id: '1', + createdAt: DateTime(100), + type: ActivityType.comment, + comment: 'This is a test comment', + user: UserStub.admin, + ); + + testWidgets('Comment contains User Circle Avatar as leading', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + final userAvatarFinder = find.byType(UserCircleAvatar); + expect(userAvatarFinder, findsOneWidget); + + // Leading widget should not be null + final listTile = tester.widget(find.byType(ListTile)); + expect(listTile.leading, isNotNull); + + // Make sure that the leading widget is the UserCircleAvatar + final userAvatar = tester.widget(userAvatarFinder); + expect(listTile.leading, userAvatar); + }); + + testWidgets('Comment title is top aligned', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + final listTile = tester.widget(find.byType(ListTile)); + + expect(listTile.titleAlignment, ListTileTitleAlignment.top); + }); + + testWidgets('Contains comment text as subtitle', (tester) async { + await tester.pumpConsumerWidget(ActivityTile(activity), overrides: overrides); + + final listTile = tester.widget(find.byType(ListTile)); + + expect(listTile.subtitle, isNotNull); + expect(find.descendant(of: find.byType(ListTile), matching: find.text(activity.comment!)), findsOneWidget); + }); + }); +} diff --git a/mobile/test/modules/activity/dismissible_activity_test.dart b/mobile/test/modules/activity/dismissible_activity_test.dart new file mode 100644 index 0000000000..32516e73ea --- /dev/null +++ b/mobile/test/modules/activity/dismissible_activity_test.dart @@ -0,0 +1,99 @@ +@Tags(['widget']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/models/activities/activity.model.dart'; +import 'package:immich_mobile/widgets/activities/activity_tile.dart'; +import 'package:immich_mobile/widgets/activities/dismissible_activity.dart'; +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../fixtures/user.stub.dart'; +import '../../test_utils.dart'; +import '../../widget_tester_extensions.dart'; +import '../asset_viewer/asset_viewer_mocks.dart'; + +final activity = Activity(id: '1', createdAt: DateTime(100), type: ActivityType.like, user: UserStub.admin); + +void main() { + late MockCurrentAssetProvider assetProvider; + late List overrides; + + setUpAll(() => TestUtils.init()); + + setUp(() { + assetProvider = MockCurrentAssetProvider(); + overrides = [currentAssetProvider.overrideWith(() => assetProvider)]; + }); + + testWidgets('Returns a Dismissible', (tester) async { + await tester.pumpConsumerWidget( + DismissibleActivity('1', ActivityTile(activity), onDismiss: (_) {}), + overrides: overrides, + ); + + expect(find.byType(Dismissible), findsOneWidget); + }); + + testWidgets('Dialog displayed when onDismiss is set', (tester) async { + await tester.pumpConsumerWidget( + DismissibleActivity('1', ActivityTile(activity), onDismiss: (_) {}), + overrides: overrides, + ); + + final dismissible = find.byType(Dismissible); + await tester.drag(dismissible, const Offset(500, 0)); + await tester.pumpAndSettle(); + + expect(find.byType(ConfirmDialog), findsOneWidget); + }); + + testWidgets('Ok action in ConfirmDialog should call onDismiss with activityId', (tester) async { + String? receivedActivityId; + await tester.pumpConsumerWidget( + DismissibleActivity('1', ActivityTile(activity), onDismiss: (id) => receivedActivityId = id), + overrides: overrides, + ); + + final dismissible = find.byType(Dismissible); + await tester.drag(dismissible, const Offset(-500, 0)); + await tester.pumpAndSettle(); + + final okButton = find.text('delete'); + await tester.tap(okButton); + await tester.pumpAndSettle(); + + expect(receivedActivityId, '1'); + }); + + testWidgets('Delete icon for background if onDismiss is set', (tester) async { + await tester.pumpConsumerWidget( + DismissibleActivity('1', ActivityTile(activity), onDismiss: (_) {}), + overrides: overrides, + ); + + final dismissible = find.byType(Dismissible); + await tester.drag(dismissible, const Offset(500, 0)); + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.delete_sweep_rounded), findsOneWidget); + }); + + testWidgets('No delete dialog if onDismiss is not set', (tester) async { + await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides); + + // When onDismiss is not set, the widget should not be wrapped by a Dismissible + expect(find.byType(Dismissible), findsNothing); + expect(find.byType(ConfirmDialog), findsNothing); + }); + + testWidgets('No icon for background if onDismiss is not set', (tester) async { + await tester.pumpConsumerWidget(DismissibleActivity('1', ActivityTile(activity)), overrides: overrides); + + // No Dismissible should exist when onDismiss is not provided, so no delete icon either + expect(find.byType(Dismissible), findsNothing); + expect(find.byIcon(Icons.delete_sweep_rounded), findsNothing); + }); +} diff --git a/mobile/test/modules/album/album_mocks.dart b/mobile/test/modules/album/album_mocks.dart new file mode 100644 index 0000000000..7a1b76e0c7 --- /dev/null +++ b/mobile/test/modules/album/album_mocks.dart @@ -0,0 +1,13 @@ +import 'package:immich_mobile/providers/album/current_album.provider.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockCurrentAlbumProvider extends CurrentAlbum with Mock implements CurrentAlbumInternal { + Album? initAlbum; + MockCurrentAlbumProvider([this.initAlbum]); + + @override + Album? build() { + return initAlbum; + } +} diff --git a/mobile/test/modules/album/album_sort_by_options_provider_test.dart b/mobile/test/modules/album/album_sort_by_options_provider_test.dart new file mode 100644 index 0000000000..a35255bc21 --- /dev/null +++ b/mobile/test/modules/album/album_sort_by_options_provider_test.dart @@ -0,0 +1,270 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../fixtures/album.stub.dart'; +import '../../fixtures/asset.stub.dart'; +import '../../test_utils.dart'; +import '../settings/settings_mocks.dart'; + +void main() { + /// Verify the sort modes + group("AlbumSortMode", () { + late final Isar db; + + setUpAll(() async { + db = await TestUtils.initIsar(); + }); + + final albums = [AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset]; + + setUp(() { + db.writeTxnSync(() { + db.clearSync(); + // Save all assets + db.assets.putAllSync([AssetStub.image1, AssetStub.image2]); + db.albums.putAllSync(albums); + for (final album in albums) { + album.sharedUsers.saveSync(); + album.assets.saveSync(); + } + }); + expect(db.albums.countSync(), 4); + expect(db.assets.countSync(), 2); + }); + + group("Album sort - Created Time", () { + const created = AlbumSortMode.created; + test("Created time - ASC", () { + final sorted = created.sortFn(albums, false); + final sortedList = [AlbumStub.emptyAlbum, AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Created time - DESC", () { + final sorted = created.sortFn(albums, true); + final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset, AlbumStub.emptyAlbum]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + + group("Album sort - Asset count", () { + const assetCount = AlbumSortMode.assetCount; + test("Asset Count - ASC", () { + final sorted = assetCount.sortFn(albums, false); + final sortedList = [AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Asset Count - DESC", () { + final sorted = assetCount.sortFn(albums, true); + final sortedList = [AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser, AlbumStub.emptyAlbum]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + + group("Album sort - Last modified", () { + const lastModified = AlbumSortMode.lastModified; + test("Last modified - ASC", () { + final sorted = lastModified.sortFn(albums, false); + final sortedList = [AlbumStub.twoAsset, AlbumStub.emptyAlbum, AlbumStub.sharedWithUser, AlbumStub.oneAsset]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Last modified - DESC", () { + final sorted = lastModified.sortFn(albums, true); + final sortedList = [AlbumStub.oneAsset, AlbumStub.sharedWithUser, AlbumStub.emptyAlbum, AlbumStub.twoAsset]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + + group("Album sort - Created", () { + const created = AlbumSortMode.created; + test("Created - ASC", () { + final sorted = created.sortFn(albums, false); + final sortedList = [AlbumStub.emptyAlbum, AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Created - DESC", () { + final sorted = created.sortFn(albums, true); + final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.twoAsset, AlbumStub.emptyAlbum]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + + group("Album sort - Most Recent", () { + const mostRecent = AlbumSortMode.mostRecent; + + test("Most Recent - DESC", () { + final sorted = mostRecent.sortFn([ + AlbumStub.create2020end2020Album, + AlbumStub.create2020end2022Album, + AlbumStub.create2020end2024Album, + AlbumStub.create2020end2026Album, + ], false); + final sortedList = [ + AlbumStub.create2020end2026Album, + AlbumStub.create2020end2024Album, + AlbumStub.create2020end2022Album, + AlbumStub.create2020end2020Album, + ]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Most Recent - ASC", () { + final sorted = mostRecent.sortFn([ + AlbumStub.create2020end2020Album, + AlbumStub.create2020end2022Album, + AlbumStub.create2020end2024Album, + AlbumStub.create2020end2026Album, + ], true); + final sortedList = [ + AlbumStub.create2020end2020Album, + AlbumStub.create2020end2022Album, + AlbumStub.create2020end2024Album, + AlbumStub.create2020end2026Album, + ]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + + group("Album sort - Most Oldest", () { + const mostOldest = AlbumSortMode.mostOldest; + + test("Most Oldest - ASC", () { + final sorted = mostOldest.sortFn(albums, false); + final sortedList = [AlbumStub.twoAsset, AlbumStub.emptyAlbum, AlbumStub.oneAsset, AlbumStub.sharedWithUser]; + expect(sorted, orderedEquals(sortedList)); + }); + + test("Most Oldest - DESC", () { + final sorted = mostOldest.sortFn(albums, true); + final sortedList = [AlbumStub.sharedWithUser, AlbumStub.oneAsset, AlbumStub.emptyAlbum, AlbumStub.twoAsset]; + expect(sorted, orderedEquals(sortedList)); + }); + }); + }); + + /// Verify the sort mode provider + group('AlbumSortByOptions', () { + late AppSettingsService settingsMock; + late ProviderContainer container; + + setUp(() async { + settingsMock = MockAppSettingsService(); + container = TestUtils.createContainer( + overrides: [appSettingsServiceProvider.overrideWith((ref) => settingsMock)], + ); + when( + () => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortReverse, any()), + ).thenAnswer((_) async => {}); + when( + () => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortOrder, any()), + ).thenAnswer((_) async => {}); + }); + + test('Returns the default sort mode when none set', () { + // Returns the default value when nothing is set + when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(0); + + expect(container.read(albumSortByOptionsProvider), AlbumSortMode.created); + }); + + test('Returns the correct sort mode with index from Store', () { + // Returns the default value when nothing is set + when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(3); + + expect(container.read(albumSortByOptionsProvider), AlbumSortMode.lastModified); + }); + + test('Properly saves the correct store index of sort mode', () { + container.read(albumSortByOptionsProvider.notifier).changeSortMode(AlbumSortMode.mostOldest); + + verify( + () => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortOrder, AlbumSortMode.mostOldest.storeIndex), + ); + }); + + test('Notifies listeners on state change', () { + when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortOrder)).thenReturn(0); + + final listener = ListenerMock(); + container.listen(albumSortByOptionsProvider, listener.call, fireImmediately: true); + + // Created -> Most Oldest + container.read(albumSortByOptionsProvider.notifier).changeSortMode(AlbumSortMode.mostOldest); + + // Most Oldest -> Title + container.read(albumSortByOptionsProvider.notifier).changeSortMode(AlbumSortMode.title); + + verifyInOrder([ + () => listener.call(null, AlbumSortMode.created), + () => listener.call(AlbumSortMode.created, AlbumSortMode.mostOldest), + () => listener.call(AlbumSortMode.mostOldest, AlbumSortMode.title), + ]); + + verifyNoMoreInteractions(listener); + }); + }); + + /// Verify the sort order provider + group('AlbumSortOrder', () { + late AppSettingsService settingsMock; + late ProviderContainer container; + + registerFallbackValue(AppSettingsEnum.selectedAlbumSortReverse); + + setUp(() async { + settingsMock = MockAppSettingsService(); + container = TestUtils.createContainer( + overrides: [appSettingsServiceProvider.overrideWith((ref) => settingsMock)], + ); + when( + () => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortReverse, any()), + ).thenAnswer((_) async => {}); + when( + () => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortOrder, any()), + ).thenAnswer((_) async => {}); + }); + + test('Returns the default sort order when none set - false', () { + when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse)).thenReturn(false); + + expect(container.read(albumSortOrderProvider), isFalse); + }); + + test('Properly saves the correct order', () { + container.read(albumSortOrderProvider.notifier).changeSortDirection(true); + + verify(() => settingsMock.setSetting(AppSettingsEnum.selectedAlbumSortReverse, true)); + }); + + test('Notifies listeners on state change', () { + when(() => settingsMock.getSetting(AppSettingsEnum.selectedAlbumSortReverse)).thenReturn(false); + + final listener = ListenerMock(); + container.listen(albumSortOrderProvider, listener.call, fireImmediately: true); + + // false -> true + container.read(albumSortOrderProvider.notifier).changeSortDirection(true); + + // true -> false + container.read(albumSortOrderProvider.notifier).changeSortDirection(false); + + verifyInOrder([ + () => listener.call(null, false), + () => listener.call(false, true), + () => listener.call(true, false), + ]); + + verifyNoMoreInteractions(listener); + }); + }); +} diff --git a/mobile/test/modules/asset_viewer/asset_viewer_mocks.dart b/mobile/test/modules/asset_viewer/asset_viewer_mocks.dart new file mode 100644 index 0000000000..89b06d3c09 --- /dev/null +++ b/mobile/test/modules/asset_viewer/asset_viewer_mocks.dart @@ -0,0 +1,13 @@ +import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockCurrentAssetProvider extends CurrentAssetInternal with Mock implements CurrentAsset { + Asset? initAsset; + MockCurrentAssetProvider([this.initAsset]); + + @override + Asset? build() { + return initAsset; + } +} diff --git a/mobile/test/modules/extensions/asset_extensions_test.dart b/mobile/test/modules/extensions/asset_extensions_test.dart new file mode 100644 index 0000000000..2b9b740ca7 --- /dev/null +++ b/mobile/test/modules/extensions/asset_extensions_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/extensions/asset_extensions.dart'; +import 'package:timezone/data/latest.dart'; +import 'package:timezone/timezone.dart'; + +ExifInfo makeExif({DateTime? dateTimeOriginal, String? timeZone}) { + return ExifInfo(dateTimeOriginal: dateTimeOriginal, timeZone: timeZone); +} + +Asset makeAsset({required String id, required DateTime createdAt, ExifInfo? exifInfo}) { + return Asset( + checksum: '', + localId: id, + remoteId: id, + ownerId: 1, + fileCreatedAt: createdAt, + fileModifiedAt: DateTime.now(), + updatedAt: DateTime.now(), + durationInSeconds: 0, + type: AssetType.image, + fileName: id, + isFavorite: false, + isArchived: false, + isTrashed: false, + exifInfo: exifInfo, + ); +} + +void main() { + // Init Timezone DB + initializeTimeZones(); + + group("Returns local time and offset if no exifInfo", () { + test('returns createdAt directly if in local', () { + final createdAt = DateTime(2023, 12, 12, 12, 12, 12); + final a = makeAsset(id: '1', createdAt: createdAt); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + expect(createdAt, dt); + expect(createdAt.timeZoneOffset, tz); + }); + + test('returns createdAt in local if in utc', () { + final createdAt = DateTime.utc(2023, 12, 12, 12, 12, 12); + final a = makeAsset(id: '1', createdAt: createdAt); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + final localCreatedAt = createdAt.toLocal(); + expect(localCreatedAt, dt); + expect(localCreatedAt.timeZoneOffset, tz); + }); + }); + + group("Returns dateTimeOriginal", () { + test('Returns dateTimeOriginal in UTC from exifInfo without timezone', () { + final createdAt = DateTime.parse("2023-01-27T14:00:00-0500"); + final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530"); + final e = makeExif(dateTimeOriginal: dateTimeOriginal); + final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + final dateTimeInUTC = dateTimeOriginal.toUtc(); + expect(dateTimeInUTC, dt); + expect(dateTimeInUTC.timeZoneOffset, tz); + }); + + test('Returns dateTimeOriginal in UTC from exifInfo with invalid timezone', () { + final createdAt = DateTime.parse("2023-01-27T14:00:00-0500"); + final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530"); + final e = makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: "#_#"); // Invalid timezone + final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + final dateTimeInUTC = dateTimeOriginal.toUtc(); + expect(dateTimeInUTC, dt); + expect(dateTimeInUTC.timeZoneOffset, tz); + }); + }); + + group("Returns adjusted time if timezone available", () { + test('With timezone as location', () { + final createdAt = DateTime.parse("2023-01-27T14:00:00-0500"); + final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530"); + const location = "Asia/Hong_Kong"; + final e = makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: location); + final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + final adjustedTime = TZDateTime.from(dateTimeOriginal.toUtc(), getLocation(location)); + expect(adjustedTime, dt); + expect(adjustedTime.timeZoneOffset, tz); + }); + + test('With timezone as offset', () { + final createdAt = DateTime.parse("2023-01-27T14:00:00-0500"); + final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530"); + const offset = "utc+08:00"; + final e = makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: offset); + final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e); + final (dt, tz) = a.getTZAdjustedTimeAndOffset(); + + final location = getLocation("Asia/Hong_Kong"); + final offsetFromLocation = Duration(milliseconds: location.currentTimeZone.offset); + final adjustedTime = dateTimeOriginal.toUtc().add(offsetFromLocation); + + // Adds the offset to the actual time and returns the offset separately + expect(adjustedTime, dt); + expect(offsetFromLocation, tz); + }); + }); +} diff --git a/mobile/test/modules/extensions/builtin_extensions_test.dart b/mobile/test/modules/extensions/builtin_extensions_test.dart index 42538c17bc..e52362f3d3 100644 --- a/mobile/test/modules/extensions/builtin_extensions_test.dart +++ b/mobile/test/modules/extensions/builtin_extensions_test.dart @@ -7,67 +7,12 @@ void main() { test('ok', () { expect("1:02:33".toDuration(), const Duration(hours: 1, minutes: 2, seconds: 33)); }); - test('fractional seconds', () { - expect("0:00:00.500000".toDuration(), const Duration(milliseconds: 500)); - expect("0:00:01.250000".toDuration(), const Duration(seconds: 1, milliseconds: 250)); - expect("1:02:33.123456".toDuration(), const Duration(hours: 1, minutes: 2, seconds: 33, milliseconds: 123)); - }); test('malformed', () { expect("".toDuration(), isNull); expect("1:2".toDuration(), isNull); expect("a:b:c".toDuration(), isNull); }); }); - group('Test removeDiacritics', () { - test('removes acute accents', () { - expect('AmÊlie'.removeDiacritics(), 'Amelie'); - }); - - test('removes grave accents', () { - expect('À la carte'.removeDiacritics(), 'A la carte'); - }); - - test('removes circumflex', () { - expect('hôpital'.removeDiacritics(), 'hopital'); - }); - - test('removes tilde', () { - expect('SÃŖo JoÃŖo'.removeDiacritics(), 'Sao Joao'); - }); - - test('removes diaeresis', () => expect('naïve'.removeDiacritics(), 'naive')); - - test('removes cedilla', () => expect('ça va'.removeDiacritics(), 'ca va')); - - test('handles Hungarian exteded characters (Åą/ő)', () { - expect('ÃĄrvíztÅąrő tÃŧkÃļrfÃērÃŗgÊp'.removeDiacritics(), 'arvizturo tukorfurogep'); - }); - - test('handles Polish characters', () { - expect('Jędrzej Łącki'.removeDiacritics(), 'Jedrzej Lacki'); - }); - - test('handles German umlauts', () => expect('MÃŧller'.removeDiacritics(), 'Muller')); - - test('handles Nordic characters', () => expect('GÃļteborg'.removeDiacritics(), 'Goteborg')); - - test('handles empty string', () => expect(''.removeDiacritics(), '')); - - test('handles string with no diacritics', () { - expect('hello world'.removeDiacritics(), 'hello world'); - }); - - test('handles Ñ/Ãą', () => expect('NiÃąo'.removeDiacritics(), 'Nino')); - - test('diacritic removal is order-independent', () { - const raw = 'ÁrvíztÅąrő'; - expect( - raw.toLowerCase().removeDiacritics(), - raw.removeDiacritics().toLowerCase(), - ); - }); - }); - group('Test uniqueConsecutive', () { test('empty', () { final a = []; diff --git a/mobile/test/modules/home/asset_grid_data_structure_test.dart b/mobile/test/modules/home/asset_grid_data_structure_test.dart new file mode 100644 index 0000000000..3e1fe06c68 --- /dev/null +++ b/mobile/test/modules/home/asset_grid_data_structure_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; + +void main() { + final List testAssets = []; + + for (int i = 0; i < 150; i++) { + int month = i ~/ 31; + int day = (i % 31).toInt(); + + DateTime date = DateTime(2022, month, day); + + testAssets.add( + Asset( + checksum: "", + localId: '$i', + ownerId: 1, + fileCreatedAt: date, + fileModifiedAt: date, + updatedAt: date, + durationInSeconds: 0, + type: AssetType.image, + fileName: '', + isFavorite: false, + isArchived: false, + isTrashed: false, + ), + ); + } + + final List assets = []; + + assets.addAll( + testAssets.sublist(0, 5).map((e) { + e.fileCreatedAt = DateTime(2022, 1, 5); + return e; + }).toList(), + ); + assets.addAll( + testAssets.sublist(5, 10).map((e) { + e.fileCreatedAt = DateTime(2022, 1, 10); + return e; + }).toList(), + ); + assets.addAll( + testAssets.sublist(10, 15).map((e) { + e.fileCreatedAt = DateTime(2022, 2, 17); + return e; + }).toList(), + ); + assets.addAll( + testAssets.sublist(15, 30).map((e) { + e.fileCreatedAt = DateTime(2022, 10, 15); + return e; + }).toList(), + ); + + group('Test grouped', () { + test('test grouped check months', () async { + final renderList = await RenderList.fromAssets(assets, GroupAssetsBy.day); + + // Oct + // Day 1 + // 15 Assets => 5 Rows + // Feb + // Day 1 + // 5 Assets => 2 Rows + // Jan + // Day 2 + // 5 Assets => 2 Rows + // Day 1 + // 5 Assets => 2 Rows + expect(renderList.elements, hasLength(4)); + expect(renderList.elements[0].type, RenderAssetGridElementType.monthTitle); + expect(renderList.elements[0].date.month, 1); + expect(renderList.elements[1].type, RenderAssetGridElementType.groupDividerTitle); + expect(renderList.elements[1].date.month, 1); + expect(renderList.elements[2].type, RenderAssetGridElementType.monthTitle); + expect(renderList.elements[2].date.month, 2); + expect(renderList.elements[3].type, RenderAssetGridElementType.monthTitle); + expect(renderList.elements[3].date.month, 10); + }); + + test('test grouped check types', () async { + final renderList = await RenderList.fromAssets(assets, GroupAssetsBy.day); + + // Oct + // Day 1 + // 15 Assets => 3 Rows + // Feb + // Day 1 + // 5 Assets => 1 Row + // Jan + // Day 2 + // 5 Assets => 1 Row + // Day 1 + // 5 Assets => 1 Row + final types = [ + RenderAssetGridElementType.monthTitle, + RenderAssetGridElementType.groupDividerTitle, + RenderAssetGridElementType.monthTitle, + RenderAssetGridElementType.monthTitle, + ]; + + expect(renderList.elements, hasLength(types.length)); + + for (int i = 0; i < renderList.elements.length; i++) { + expect(renderList.elements[i].type, types[i]); + } + }); + }); +} diff --git a/mobile/test/modules/map/map_theme_override_test.dart b/mobile/test/modules/map/map_theme_override_test.dart index 3ea54bc9b5..de16b7f24f 100644 --- a/mobile/test/modules/map/map_theme_override_test.dart +++ b/mobile/test/modules/map/map_theme_override_test.dart @@ -1,18 +1,17 @@ @Skip('Flaky test, needs investigation') +@Tags(['widget']) library; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/models/map/map_state.model.dart'; import 'package:immich_mobile/providers/locale_provider.dart'; import 'package:immich_mobile/providers/map/map_state.provider.dart'; import 'package:immich_mobile/widgets/map/map_theme_override.dart'; +import 'package:isar/isar.dart'; import '../../test_utils.dart'; import '../../widget_tester_extensions.dart'; @@ -22,17 +21,17 @@ void main() { late MockMapStateNotifier mapStateNotifier; late List overrides; late MapState mapState; - late Drift db; + late Isar db; setUpAll(() async { - db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + db = await TestUtils.initIsar(); TestUtils.init(); }); setUp(() async { mapState = const MapState(themeMode: ThemeMode.dark); mapStateNotifier = MockMapStateNotifier(mapState); - await StoreService.init(storeRepository: StoreRepository(db)); + await StoreService.init(storeRepository: IsarStoreRepository(db)); overrides = [ mapStateNotifierProvider.overrideWith(() => mapStateNotifier), localeProvider.overrideWithValue(const Locale("en")), diff --git a/mobile/test/modules/settings/settings_mocks.dart b/mobile/test/modules/settings/settings_mocks.dart new file mode 100644 index 0000000000..63fd9312b7 --- /dev/null +++ b/mobile/test/modules/settings/settings_mocks.dart @@ -0,0 +1,4 @@ +import 'package:immich_mobile/services/app_settings.service.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAppSettingsService extends Mock implements AppSettingsService {} diff --git a/mobile/test/modules/shared/shared_mocks.dart b/mobile/test/modules/shared/shared_mocks.dart new file mode 100644 index 0000000000..790bbbd815 --- /dev/null +++ b/mobile/test/modules/shared/shared_mocks.dart @@ -0,0 +1,11 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/providers/user.provider.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockCurrentUserProvider extends StateNotifier with Mock implements CurrentUserProvider { + MockCurrentUserProvider() : super(null); + + @override + set state(UserDto? user) => super.state = user; +} diff --git a/mobile/test/modules/shared/sync_service_test.dart b/mobile/test/modules/shared/sync_service_test.dart new file mode 100644 index 0000000000..767a52b8d8 --- /dev/null +++ b/mobile/test/modules/shared/sync_service_test.dart @@ -0,0 +1,285 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/models/user.model.dart'; +import 'package:immich_mobile/domain/services/log.service.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/log.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; +import 'package:immich_mobile/services/sync.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../domain/service.mock.dart'; +import '../../fixtures/asset.stub.dart'; +import '../../infrastructure/repository.mock.dart'; +import '../../repository.mocks.dart'; +import '../../service.mocks.dart'; +import '../../test_utils.dart'; + +void main() { + int assetIdCounter = 0; + Asset makeAsset({ + required String checksum, + String? localId, + String? remoteId, + int ownerId = 590700560494856554, // hash of "1" + }) { + final DateTime date = DateTime(2000); + return Asset( + id: assetIdCounter++, + checksum: checksum, + localId: localId, + remoteId: remoteId, + ownerId: ownerId, + fileCreatedAt: date, + fileModifiedAt: date, + updatedAt: date, + durationInSeconds: 0, + type: AssetType.image, + fileName: localId ?? remoteId ?? "", + isFavorite: false, + isArchived: false, + isTrashed: false, + ); + } + + final owner = UserDto( + id: "1", + updatedAt: DateTime.now(), + email: "a@b.c", + name: "first last", + isAdmin: false, + profileChangedAt: DateTime.now(), + ); + + setUpAll(() async { + final loggerDb = DriftLogger(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + final LogRepository logRepository = LogRepository(loggerDb); + + WidgetsFlutterBinding.ensureInitialized(); + final db = await TestUtils.initIsar(); + + db.writeTxnSync(() => db.clearSync()); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + await Store.put(StoreKey.currentUser, owner); + await LogService.init(logRepository: logRepository, storeRepository: IsarStoreRepository(db)); + }); + + group('Test SyncService grouped', () { + final MockHashService hs = MockHashService(); + final MockEntityService entityService = MockEntityService(); + final MockAlbumRepository albumRepository = MockAlbumRepository(); + final MockAssetRepository assetRepository = MockAssetRepository(); + final MockExifInfoRepository exifInfoRepository = MockExifInfoRepository(); + final MockIsarUserRepository userRepository = MockIsarUserRepository(); + final MockETagRepository eTagRepository = MockETagRepository(); + final MockAlbumMediaRepository albumMediaRepository = MockAlbumMediaRepository(); + final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository(); + final MockAppSettingService appSettingService = MockAppSettingService(); + final MockLocalFilesManagerRepository localFilesManagerRepository = MockLocalFilesManagerRepository(); + final MockPartnerApiRepository partnerApiRepository = MockPartnerApiRepository(); + final MockUserApiRepository userApiRepository = MockUserApiRepository(); + final MockPartnerRepository partnerRepository = MockPartnerRepository(); + final MockUserService userService = MockUserService(); + + final owner = UserDto( + id: "1", + updatedAt: DateTime.now(), + email: "a@b.c", + name: "first last", + isAdmin: false, + profileChangedAt: DateTime(2021), + ); + + late SyncService s; + + final List initialAssets = [ + makeAsset(checksum: "a", remoteId: "0-1"), + makeAsset(checksum: "b", remoteId: "2-1"), + makeAsset(checksum: "c", localId: "1", remoteId: "1-1"), + makeAsset(checksum: "d", localId: "2"), + makeAsset(checksum: "e", localId: "3"), + ]; + setUp(() { + s = SyncService( + hs, + entityService, + albumMediaRepository, + albumApiRepository, + albumRepository, + assetRepository, + exifInfoRepository, + partnerRepository, + userRepository, + userService, + eTagRepository, + appSettingService, + localFilesManagerRepository, + partnerApiRepository, + userApiRepository, + ); + when(() => userService.getMyUser()).thenReturn(owner); + when(() => eTagRepository.get(owner.id)).thenAnswer((_) async => ETag(id: owner.id, time: DateTime.now())); + when(() => eTagRepository.deleteByIds(["1"])).thenAnswer((_) async {}); + when(() => eTagRepository.upsertAll(any())).thenAnswer((_) async {}); + when(() => partnerRepository.getSharedWith()).thenAnswer((_) async => []); + when(() => userRepository.getAll(sortBy: SortUserBy.id)).thenAnswer((_) async => [owner]); + when(() => userRepository.getAll()).thenAnswer((_) async => [owner]); + when( + () => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum), + ).thenAnswer((_) async => initialAssets); + when( + () => assetRepository.getAllByOwnerIdChecksum(any(), any()), + ).thenAnswer((_) async => [initialAssets[3], null, null]); + when(() => assetRepository.updateAll(any())).thenAnswer((_) async => []); + when(() => assetRepository.deleteByIds(any())).thenAnswer((_) async {}); + when(() => exifInfoRepository.updateAll(any())).thenAnswer((_) async => []); + when( + () => assetRepository.transaction(any()), + ).thenAnswer((call) => (call.positionalArguments.first as Function).call()); + when( + () => assetRepository.transaction(any()), + ).thenAnswer((call) => (call.positionalArguments.first as Function).call()); + when(() => userApiRepository.getAll()).thenAnswer((_) async => [owner]); + registerFallbackValue(Direction.sharedByMe); + when(() => partnerApiRepository.getAll(any())).thenAnswer((_) async => []); + }); + test('test inserting existing assets', () async { + final List remoteAssets = [ + makeAsset(checksum: "a", remoteId: "0-1"), + makeAsset(checksum: "b", remoteId: "2-1"), + makeAsset(checksum: "c", remoteId: "1-1"), + ]; + final bool c1 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c1, isFalse); + verifyNever(() => assetRepository.updateAll(any())); + }); + + test('test inserting new assets', () async { + final List remoteAssets = [ + makeAsset(checksum: "a", remoteId: "0-1"), + makeAsset(checksum: "b", remoteId: "2-1"), + makeAsset(checksum: "c", remoteId: "1-1"), + makeAsset(checksum: "d", remoteId: "1-2"), + makeAsset(checksum: "f", remoteId: "1-4"), + makeAsset(checksum: "g", remoteId: "3-1"), + ]; + final bool c1 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c1, isTrue); + final updatedAsset = initialAssets[3].updatedCopy(remoteAssets[3]); + verify(() => assetRepository.updateAll([remoteAssets[4], remoteAssets[5], updatedAsset])); + }); + + test('test syncing duplicate assets', () async { + final List remoteAssets = [ + makeAsset(checksum: "a", remoteId: "0-1"), + makeAsset(checksum: "b", remoteId: "1-1"), + makeAsset(checksum: "c", remoteId: "2-1"), + makeAsset(checksum: "h", remoteId: "2-1b"), + makeAsset(checksum: "i", remoteId: "2-1c"), + makeAsset(checksum: "j", remoteId: "2-1d"), + ]; + final bool c1 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c1, isTrue); + when( + () => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum), + ).thenAnswer((_) async => remoteAssets); + final bool c2 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c2, isFalse); + final currentState = [...remoteAssets]; + when( + () => assetRepository.getAll(ownerId: owner.id, sortBy: AssetSort.checksum), + ).thenAnswer((_) async => currentState); + remoteAssets.removeAt(4); + final bool c3 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c3, isTrue); + remoteAssets.add(makeAsset(checksum: "k", remoteId: "2-1e")); + remoteAssets.add(makeAsset(checksum: "l", remoteId: "2-2")); + final bool c4 = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: _failDiff, + loadAssets: (u, d) => remoteAssets, + ); + expect(c4, isTrue); + }); + + test('test efficient sync', () async { + when( + () => assetRepository.deleteAllByRemoteId([ + initialAssets[1].remoteId!, + initialAssets[2].remoteId!, + ], state: AssetState.remote), + ).thenAnswer((_) async { + return; + }); + when( + () => assetRepository.getAllByRemoteId(["2-1", "1-1"], state: AssetState.merged), + ).thenAnswer((_) async => [initialAssets[2]]); + when( + () => assetRepository.getAllByOwnerIdChecksum(any(), any()), + ).thenAnswer((_) async => [initialAssets[0], null, null]); //afg + final List toUpsert = [ + makeAsset(checksum: "a", remoteId: "0-1"), // changed + makeAsset(checksum: "f", remoteId: "0-2"), // new + makeAsset(checksum: "g", remoteId: "0-3"), // new + ]; + toUpsert[0].isFavorite = true; + final List toDelete = ["2-1", "1-1"]; + final expected = [...toUpsert]; + expected[0].id = initialAssets[0].id; + final bool c = await s.syncRemoteAssetsToDb( + users: [owner], + getChangedAssets: (user, since) async => (toUpsert, toDelete), + loadAssets: (user, date) => throw Exception(), + ); + expect(c, isTrue); + verify(() => assetRepository.updateAll(expected)); + }); + + group("upsertAssetsWithExif", () { + test('test upsert with EXIF data', () async { + final assets = [AssetStub.image1, AssetStub.image2]; + + expect(assets.map((a) => a.exifInfo?.assetId), List.filled(assets.length, null)); + await s.upsertAssetsWithExif(assets); + verify( + () => exifInfoRepository.updateAll( + any(that: containsAll(assets.map((a) => a.exifInfo!.copyWith(assetId: a.id)))), + ), + ); + expect(assets.map((a) => a.exifInfo?.assetId), assets.map((a) => a.id)); + }); + }); + }); +} + +Future<(List?, List?)> _failDiff(List user, DateTime time) => Future.value((null, null)); diff --git a/mobile/test/modules/utils/async_mutex_test.dart b/mobile/test/modules/utils/async_mutex_test.dart index 10fdcbad5e..08cafeb307 100644 --- a/mobile/test/modules/utils/async_mutex_test.dart +++ b/mobile/test/modules/utils/async_mutex_test.dart @@ -6,8 +6,8 @@ import 'package:immich_mobile/utils/async_mutex.dart'; void main() { group('Test AsyncMutex grouped', () { test('test ordered execution', () async { - final AsyncMutex lock = AsyncMutex(); - final List events = []; + AsyncMutex lock = AsyncMutex(); + List events = []; expect(0, lock.enqueued); unawaited(lock.run(() => Future.delayed(const Duration(milliseconds: 10), () => events.add(1)))); expect(1, lock.enqueued); diff --git a/mobile/test/modules/utils/datetime_helpers_test.dart b/mobile/test/modules/utils/datetime_helpers_test.dart index ca196224a0..dfe83b4925 100644 --- a/mobile/test/modules/utils/datetime_helpers_test.dart +++ b/mobile/test/modules/utils/datetime_helpers_test.dart @@ -10,38 +10,38 @@ void main() { test('returns null for value below minimum allowed range', () { // _minMillisecondsSinceEpoch = -62135596800000 - const seconds = -62135596800000 ~/ 1000 - 1; // One second before min allowed + final seconds = -62135596800000 ~/ 1000 - 1; // One second before min allowed final result = tryFromSecondsSinceEpoch(seconds); expect(result, isNull); }); test('returns null for value above maximum allowed range', () { // _maxMillisecondsSinceEpoch = 8640000000000000 - const seconds = 8640000000000000 ~/ 1000 + 1; // One second after max allowed + final seconds = 8640000000000000 ~/ 1000 + 1; // One second after max allowed final result = tryFromSecondsSinceEpoch(seconds); expect(result, isNull); }); test('returns correct DateTime for minimum allowed value', () { - const seconds = -62135596800000 ~/ 1000; // Minimum allowed timestamp + final seconds = -62135596800000 ~/ 1000; // Minimum allowed timestamp final result = tryFromSecondsSinceEpoch(seconds); expect(result, DateTime.fromMillisecondsSinceEpoch(-62135596800000)); }); test('returns correct DateTime for maximum allowed value', () { - const seconds = 8640000000000000 ~/ 1000; // Maximum allowed timestamp + final seconds = 8640000000000000 ~/ 1000; // Maximum allowed timestamp final result = tryFromSecondsSinceEpoch(seconds); expect(result, DateTime.fromMillisecondsSinceEpoch(8640000000000000)); }); test('returns correct DateTime for negative timestamp', () { - const seconds = -1577836800; // Dec 31, 1919 (pre-epoch) + final seconds = -1577836800; // Dec 31, 1919 (pre-epoch) final result = tryFromSecondsSinceEpoch(seconds); expect(result, DateTime.fromMillisecondsSinceEpoch(-1577836800 * 1000)); }); test('returns correct DateTime for zero timestamp', () { - const seconds = 0; // Jan 1, 1970 (epoch) + final seconds = 0; // Jan 1, 1970 (epoch) final result = tryFromSecondsSinceEpoch(seconds); expect(result, DateTime.fromMillisecondsSinceEpoch(0)); }); diff --git a/mobile/test/modules/utils/debouncer_test.dart b/mobile/test/modules/utils/debouncer_test.dart index 036c538f03..7aa13842d6 100644 --- a/mobile/test/modules/utils/debouncer_test.dart +++ b/mobile/test/modules/utils/debouncer_test.dart @@ -11,7 +11,7 @@ class _Counter { void main() { test('Executes the method after the interval', () async { - final counter = _Counter(); + var counter = _Counter(); final debouncer = Debouncer(interval: const Duration(milliseconds: 300)); debouncer.run(() => counter.increment()); expect(counter.count, 0); @@ -20,17 +20,17 @@ void main() { }); test('Executes the method immediately if zero interval', () async { - final counter = _Counter(); - final debouncer = Debouncer(interval: Duration.zero); + var counter = _Counter(); + final debouncer = Debouncer(interval: const Duration(milliseconds: 0)); debouncer.run(() => counter.increment()); // Even though it is supposed to be executed immediately, it is added to the async queue and so // we need this delay to make sure the actual debounced method is called - await Future.delayed(Duration.zero); + await Future.delayed(const Duration(milliseconds: 0)); expect(counter.count, 1); }); test('Delayes method execution after all the calls are completed', () async { - final counter = _Counter(); + var counter = _Counter(); final debouncer = Debouncer(interval: const Duration(milliseconds: 100)); debouncer.run(() => counter.increment()); debouncer.run(() => counter.increment()); diff --git a/mobile/test/modules/utils/migration_test.dart b/mobile/test/modules/utils/migration_test.dart new file mode 100644 index 0000000000..08ab1204a6 --- /dev/null +++ b/mobile/test/modules/utils/migration_test.dart @@ -0,0 +1,131 @@ +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart'; +import 'package:immich_mobile/utils/migration.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../infrastructure/repository.mock.dart'; + +void main() { + late Drift db; + late SyncStreamRepository mockSyncStreamRepository; + + setUpAll(() async { + db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); + mockSyncStreamRepository = MockSyncStreamRepository(); + when(() => mockSyncStreamRepository.reset()).thenAnswer((_) async => {}); + }); + + tearDown(() async { + await Store.clear(); + }); + + group('handleBetaMigration Tests', () { + group("version < 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(14, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(14, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.needBetaMigration), true); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(14, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + + group("version == 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.needBetaMigration), true); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(15, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + + group("version > 15", () { + test('already on new timeline', () async { + await Store.put(StoreKey.betaTimeline, true); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('already on old timeline', () async { + await Store.put(StoreKey.betaTimeline, false); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), false); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + + test('fresh install', () async { + await Store.delete(StoreKey.betaTimeline); + await handleBetaMigration(16, true, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.betaTimeline), true); + expect(Store.tryGet(StoreKey.needBetaMigration), false); + }); + }); + }); + + group('sync reset tests', () { + test('version < 16', () async { + await Store.put(StoreKey.shouldResetSync, false); + + await handleBetaMigration(15, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.shouldResetSync), true); + }); + + test('version >= 16', () async { + await Store.put(StoreKey.shouldResetSync, false); + + await handleBetaMigration(16, false, mockSyncStreamRepository); + + expect(Store.tryGet(StoreKey.shouldResetSync), false); + }); + }); +} diff --git a/mobile/test/modules/utils/openapi_patching_test.dart b/mobile/test/modules/utils/openapi_patching_test.dart index 15d42c34f2..a577b0544f 100644 --- a/mobile/test/modules/utils/openapi_patching_test.dart +++ b/mobile/test/modules/utils/openapi_patching_test.dart @@ -1,8 +1,8 @@ import 'dart:convert'; import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/utils/openapi_patching.dart'; import 'package:openapi/api.dart'; +import 'package:immich_mobile/utils/openapi_patching.dart'; void main() { group('Test OpenApi Patching', () { @@ -21,12 +21,12 @@ void main() { """); upgradeDto(value, targetType); - expect(value['tags'], TagsResponse(enabled: false, sidebarWeb: false).toJson()); + expect(value['tags'], TagsResponse().toJson()); expect(value['download']['includeEmbeddedVideos'], false); }); test('addDefault', () { - final dynamic value = jsonDecode(""" + dynamic value = jsonDecode(""" { "download": { "archiveSize": 4294967296, @@ -47,7 +47,7 @@ void main() { }); test('addDefault with null', () { - final dynamic value = jsonDecode(""" + dynamic value = jsonDecode(""" { "download": { "archiveSize": 4294967296, diff --git a/mobile/test/modules/utils/throttler_test.dart b/mobile/test/modules/utils/throttler_test.dart new file mode 100644 index 0000000000..1757826daf --- /dev/null +++ b/mobile/test/modules/utils/throttler_test.dart @@ -0,0 +1,46 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/utils/throttle.dart'; +import 'package:immich_mobile/utils/debug_print.dart'; + +class _Counter { + int _count = 0; + _Counter(); + + int get count => _count; + void increment() { + dPrint(() => "Counter inside increment: $count"); + _count = _count + 1; + } +} + +void main() { + test('Executes the method immediately if no calls received previously', () async { + var counter = _Counter(); + final throttler = Throttler(interval: const Duration(milliseconds: 300)); + throttler.run(() => counter.increment()); + expect(counter.count, 1); + }); + + test('Does not execute calls before throttle interval', () async { + var counter = _Counter(); + final throttler = Throttler(interval: const Duration(milliseconds: 100)); + throttler.run(() => counter.increment()); + throttler.run(() => counter.increment()); + throttler.run(() => counter.increment()); + throttler.run(() => counter.increment()); + throttler.run(() => counter.increment()); + await Future.delayed(const Duration(seconds: 1)); + expect(counter.count, 1); + }); + + test('Executes the method if received in intervals', () async { + var counter = _Counter(); + final throttler = Throttler(interval: const Duration(milliseconds: 100)); + for (final _ in Iterable.generate(10)) { + throttler.run(() => counter.increment()); + await Future.delayed(const Duration(milliseconds: 50)); + } + await Future.delayed(const Duration(seconds: 1)); + expect(counter.count, 5); + }); +} diff --git a/mobile/test/modules/utils/thumbnail_utils_test.dart b/mobile/test/modules/utils/thumbnail_utils_test.dart new file mode 100644 index 0000000000..dd4588fc80 --- /dev/null +++ b/mobile/test/modules/utils/thumbnail_utils_test.dart @@ -0,0 +1,63 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/exif.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/utils/thumbnail_utils.dart'; + +void main() { + final dateTime = DateTime(2025, 04, 25, 12, 13, 14); + final dateTimeString = DateFormat.yMMMMd().format(dateTime); + + test('returns description if it has one', () { + final result = getAltText(const ExifInfo(description: 'description'), dateTime, AssetType.image, []); + expect(result, 'description'); + }); + + test('returns image alt text with date if no location', () { + final (template, args) = getAltTextTemplate(const ExifInfo(), dateTime, AssetType.image, []); + expect(template, "image_alt_text_date"); + expect(args["isVideo"], "false"); + expect(args["date"], dateTimeString); + }); + + test('returns image alt text with date and place', () { + final (template, args) = getAltTextTemplate( + const ExifInfo(city: 'city', country: 'country'), + dateTime, + AssetType.video, + [], + ); + expect(template, "image_alt_text_date_place"); + expect(args["isVideo"], "true"); + expect(args["date"], dateTimeString); + expect(args["city"], "city"); + expect(args["country"], "country"); + }); + + test('returns image alt text with date and some people', () { + final (template, args) = getAltTextTemplate(const ExifInfo(), dateTime, AssetType.image, ["Alice", "Bob"]); + expect(template, "image_alt_text_date_2_people"); + expect(args["isVideo"], "false"); + expect(args["date"], dateTimeString); + expect(args["person1"], "Alice"); + expect(args["person2"], "Bob"); + }); + + test('returns image alt text with date and location and many people', () { + final (template, args) = getAltTextTemplate( + const ExifInfo(city: "city", country: 'country'), + dateTime, + AssetType.video, + ["Alice", "Bob", "Carol", "David", "Eve"], + ); + expect(template, "image_alt_text_date_place_4_or_more_people"); + expect(args["isVideo"], "true"); + expect(args["date"], dateTimeString); + expect(args["city"], "city"); + expect(args["country"], "country"); + expect(args["person1"], "Alice"); + expect(args["person2"], "Bob"); + expect(args["person3"], "Carol"); + expect(args["additionalCount"], "2"); + }); +} diff --git a/mobile/test/modules/utils/url_helper_test.dart b/mobile/test/modules/utils/url_helper_test.dart index 245f68e767..0e8a8e2aa0 100644 --- a/mobile/test/modules/utils/url_helper_test.dart +++ b/mobile/test/modules/utils/url_helper_test.dart @@ -77,40 +77,6 @@ void main() { }); }); - group('normalizeAndValidateServerUrl', () { - test('should treat null as valid', () { - expect(normalizeAndValidateServerUrl(null), isTrue); - }); - - test('should treat empty string as valid', () { - expect(normalizeAndValidateServerUrl(''), isTrue); - }); - - test('should accept a bare host', () { - expect(normalizeAndValidateServerUrl('demo.immich.app'), isTrue); - }); - - test('should accept a bare host with a port', () { - expect(normalizeAndValidateServerUrl('192.168.1.1:2283'), isTrue); - }); - - test('should accept an http URL', () { - expect(normalizeAndValidateServerUrl('http://demo.immich.app'), isTrue); - }); - - test('should accept an https URL', () { - expect(normalizeAndValidateServerUrl('https://demo.immich.app:2283/api'), isTrue); - }); - - test('should reject a non-http scheme', () { - expect(normalizeAndValidateServerUrl('ftp://demo.immich.app'), isFalse); - }); - - test('should reject scheme only input', () { - expect(normalizeAndValidateServerUrl('https://'), isFalse); - }); - }); - group('punycodeDecodeUrl', () { test('should return null for null input', () { expect(punycodeDecodeUrl(null), isNull); diff --git a/mobile/test/modules/utils/version_compatibility_test.dart b/mobile/test/modules/utils/version_compatibility_test.dart index c4fcada67d..82f89237c0 100644 --- a/mobile/test/modules/utils/version_compatibility_test.dart +++ b/mobile/test/modules/utils/version_compatibility_test.dart @@ -1,47 +1,32 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/utils/semver.dart'; import 'package:immich_mobile/utils/version_compatibility.dart'; void main() { - group('app major version behind server', () { - const message = - 'Your mobile app version is not compatible with the server! Please update your mobile app to the latest version.'; + test('getVersionCompatibilityMessage', () { + String? result; - test('returns message when app major is behind server major', () { - final result = getVersionCompatibilityMessage( - serverVersion: const SemVer(major: 2, minor: 0, patch: 0), - appVersion: const SemVer(major: 1, minor: 200, patch: 0), - ); - expect(result, message); - }); + result = getVersionCompatibilityMessage(1, 0, 2, 0); + expect(result, 'Your app major version is not compatible with the server!'); - test('returns null when app major matches server major', () { - final result = getVersionCompatibilityMessage( - serverVersion: const SemVer(major: 2, minor: 0, patch: 0), - appVersion: const SemVer(major: 2, minor: 0, patch: 0), - ); - expect(result, null); - }); - }); + result = getVersionCompatibilityMessage(1, 106, 1, 105); + expect( + result, + 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login', + ); - group('app major version too far ahead of server', () { - const message = - 'Your server version is not compatible with the mobile app! Please update your server to the latest version.'; + result = getVersionCompatibilityMessage(1, 107, 1, 105); + expect( + result, + 'Your app minor version is not compatible with the server! Please update your server to version v1.106.0 or newer to login', + ); - test('returns message when app major is more than one ahead of server', () { - final result = getVersionCompatibilityMessage( - serverVersion: const SemVer(major: 1, minor: 200, patch: 0), - appVersion: const SemVer(major: 3, minor: 0, patch: 0), - ); - expect(result, message); - }); + result = getVersionCompatibilityMessage(1, 106, 1, 106); + expect(result, null); - test('returns null when app major is exactly one ahead of server', () { - final result = getVersionCompatibilityMessage( - serverVersion: const SemVer(major: 1, minor: 200, patch: 0), - appVersion: const SemVer(major: 2, minor: 0, patch: 0), - ); - expect(result, null); - }); + result = getVersionCompatibilityMessage(1, 107, 1, 106); + expect(result, null); + + result = getVersionCompatibilityMessage(1, 107, 1, 108); + expect(result, null); }); } diff --git a/mobile/test/openapi_patches_coverage.dart b/mobile/test/openapi_patches_coverage.dart deleted file mode 100644 index 956f6793d3..0000000000 --- a/mobile/test/openapi_patches_coverage.dart +++ /dev/null @@ -1,117 +0,0 @@ -// Intentionally NOT named `*_test.dart`: that suffix makes `flutter test` -// auto-discover it, which would run it on every mobile PR. This check is only -// relevant when the OpenAPI spec changes, so the `Check OpenAPI` workflow runs -// it by explicit path with the spec locations in the environment. - -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/utils/openapi_patching.dart'; - -void main() { - test('every newly-required response field has a backward-compat patch', () { - final basePath = Platform.environment['OPENAPI_BASE_SPEC']; - final revisionPath = Platform.environment['OPENAPI_REVISION_SPEC']; - if (basePath == null || revisionPath == null) { - markTestSkipped('set OPENAPI_BASE_SPEC and OPENAPI_REVISION_SPEC to run'); - return; - } - - final baseRequired = _requiredBySchema(_loadSpec(basePath)); - final revisionSpec = _loadSpec(revisionPath); - final revisionRequired = _requiredBySchema(revisionSpec); - final deserialized = _deserializedSchemas(revisionSpec); - final patched = openApiPatches.map( - (type, fields) => MapEntry(type, fields.keys.toSet()), - ); - - final missing = []; - for (final entry in revisionRequired.entries) { - if (!deserialized.contains(entry.key)) { - continue; - } - - // Skip new DTOs - if (!baseRequired.containsKey(entry.key)) { - continue; - } - - final have = patched[entry.key] ?? const {}; - final newlyRequired = entry.value.difference( - baseRequired[entry.key] ?? const {}, - ); - for (final field in newlyRequired) { - if (!have.contains(field)) { - missing.add('${entry.key}.$field'); - } - } - } - missing.sort(); - - expect( - missing, - isEmpty, - reason: - 'Detected a breaking change: $missing\n' - 'Either add a default to openApiPatches in lib/utils/openapi_patching.dart, or make it optional', - ); - }); -} - -Map _loadSpec(String path) => - jsonDecode(File(path).readAsStringSync()) as Map; - -Map _schemas(Map spec) => - ((spec['components'] as Map?)?['schemas'] as Map?) - ?.cast() ?? - const {}; - -Map> _requiredBySchema(Map spec) { - final result = >{}; - _schemas(spec).forEach((name, schema) { - final required = (schema as Map)['required'] as List? ?? const []; - result[name] = required.cast().toSet(); - }); - return result; -} - -Iterable _refsIn(Object? node) sync* { - if (node is Map) { - if (node[r'$ref'] case final String ref) { - yield ref.split('/').last; - } - for (final value in node.values) { - yield* _refsIn(value); - } - } else if (node is List) { - for (final value in node) { - yield* _refsIn(value); - } - } -} - -Set _deserializedSchemas(Map spec) { - final schemas = _schemas(spec); - final reachable = {}; - - final queue = []; - for (final path in (spec['paths'] as Map?)?.values ?? const []) { - if (path is! Map) { - continue; - } - for (final operation in path.values) { - if (operation is Map) { - queue.addAll(_refsIn(operation['responses'])); - } - } - } - while (queue.isNotEmpty) { - final name = queue.removeLast(); - if (!schemas.containsKey(name) || !reachable.add(name)) { - continue; - } - queue.addAll(_refsIn(schemas[name])); - } - return reachable; -} diff --git a/mobile/test/pages/common/download_panel_test.dart b/mobile/test/pages/common/download_panel_test.dart deleted file mode 100644 index f2a6b09d88..0000000000 --- a/mobile/test/pages/common/download_panel_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:background_downloader/background_downloader.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/pages/common/download_panel.dart'; - -import '../../widget_tester_extensions.dart'; - -void main() { - testWidgets('complete uses a checkmark and other statuses use close', (tester) async { - await tester.pumpConsumerWidget( - DownloadTaskTile(progress: 1, fileName: 'photo.jpg', status: TaskStatus.complete, onCancelDownload: () {}), - ); - - expect(find.byIcon(Icons.download_done), findsOneWidget); - expect(find.byIcon(Icons.close), findsNothing); - - await tester.pumpConsumerWidget( - DownloadTaskTile(progress: 1, fileName: 'photo.jpg', status: TaskStatus.failed, onCancelDownload: () {}), - ); - - expect(find.byIcon(Icons.download_done), findsNothing); - expect(find.byIcon(Icons.close), findsOneWidget); - }); -} diff --git a/mobile/test/pages/search/search.page_test.dart b/mobile/test/pages/search/search.page_test.dart new file mode 100644 index 0000000000..9592623a28 --- /dev/null +++ b/mobile/test/pages/search/search.page_test.dart @@ -0,0 +1,98 @@ +@Skip('currently failing due to mock HTTP client to download ISAR binaries') +@Tags(['pages']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/services/store.service.dart'; +import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/pages/search/search.page.dart'; +import 'package:immich_mobile/providers/api.provider.dart'; +import 'package:immich_mobile/providers/db.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:openapi/api.dart'; + +import '../../dto.mocks.dart'; +import '../../service.mocks.dart'; +import '../../test_utils.dart'; +import '../../widget_tester_extensions.dart'; + +void main() { + late List overrides; + late Isar db; + late MockApiService mockApiService; + late MockSearchApi mockSearchApi; + + setUpAll(() async { + TestUtils.init(); + db = await TestUtils.initIsar(); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + mockApiService = MockApiService(); + mockSearchApi = MockSearchApi(); + when(() => mockApiService.searchApi).thenReturn(mockSearchApi); + registerFallbackValue(MockSmartSearchDto()); + registerFallbackValue(MockMetadataSearchDto()); + overrides = [ + dbProvider.overrideWithValue(db), + isarProvider.overrideWithValue(db), + apiServiceProvider.overrideWithValue(mockApiService), + ]; + }); + + final emptyTextSearch = isA().having((s) => s.originalFileName, 'originalFileName', null); + + testWidgets('contextual search with/without text', (tester) async { + await tester.pumpConsumerWidget(const SearchPage(), overrides: overrides); + + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.abc_rounded), findsOneWidget, reason: 'Should have contextual search icon'); + + final searchField = find.byKey(const Key('search_text_field')); + expect(searchField, findsOneWidget); + + await tester.enterText(searchField, 'test'); + await tester.testTextInput.receiveAction(TextInputAction.search); + + var captured = verify(() => mockSearchApi.searchSmart(captureAny())).captured; + + expect(captured.first, isA().having((s) => s.query, 'query', 'test')); + + await tester.enterText(searchField, ''); + await tester.testTextInput.receiveAction(TextInputAction.search); + + captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured; + expect(captured.first, emptyTextSearch); + }); + + testWidgets('not contextual search with/without text', (tester) async { + await tester.pumpConsumerWidget(const SearchPage(), overrides: overrides); + + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key('contextual_search_button'))); + + await tester.pumpAndSettle(); + + expect(find.byIcon(Icons.image_search_rounded), findsOneWidget, reason: 'Should not have contextual search icon'); + + final searchField = find.byKey(const Key('search_text_field')); + expect(searchField, findsOneWidget); + + await tester.enterText(searchField, 'test'); + await tester.testTextInput.receiveAction(TextInputAction.search); + + var captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured; + + expect(captured.first, isA().having((s) => s.originalFileName, 'originalFileName', 'test')); + + await tester.enterText(searchField, ''); + await tester.testTextInput.receiveAction(TextInputAction.search); + + captured = verify(() => mockSearchApi.searchAssets(captureAny())).captured; + expect(captured.first, emptyTextSearch); + }); +} diff --git a/mobile/test/presentation/widgets/asset_viewer/asset_page_zoom_reset_test.dart b/mobile/test/presentation/widgets/asset_viewer/asset_page_zoom_reset_test.dart deleted file mode 100644 index f4ee4f353c..0000000000 --- a/mobile/test/presentation/widgets/asset_viewer/asset_page_zoom_reset_test.dart +++ /dev/null @@ -1,91 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/events.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/domain/utils/event_stream.dart'; -import 'package:immich_mobile/generated/codegen_loader.g.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_page.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; - -import '../../../fixtures/asset.stub.dart'; - -/// Timeline displaying a single asset that we can swap -class _SwappableTimelineService extends TimelineService { - BaseAsset asset; - _SwappableTimelineService(this.asset) - : super(( - assetSource: (_, _) async => [], - bucketSource: () => Stream.value(const [Bucket(assetCount: 1)]), - origin: TimelineOrigin.main, - )); - - @override - int get totalAssets => 1; - @override - BaseAsset? getAssetSafe(int index) => index == 0 ? asset : null; -} - -class _ZoomedNotifier extends AssetViewerStateNotifier { - @override - AssetViewerState build() { - super.build(); - return AssetViewerState(currentAsset: LocalAssetStub.image1, isZoomed: true); - } -} - -void main() { - testWidgets('resets zoom when the displayed asset is replaced on reload', (tester) async { - final timeline = _SwappableTimelineService(LocalAssetStub.image1); - final container = ProviderContainer( - overrides: [ - timelineServiceProvider.overrideWithValue(timeline), - assetViewerProvider.overrideWith(_ZoomedNotifier.new), - ], - ); - addTearDown(container.dispose); - - await tester.pumpWidget( - UncontrolledProviderScope( - container: container, - child: EasyLocalization( - supportedLocales: locales.values.toList(), - path: translationsPath, - startLocale: locales.values.first, - fallbackLocale: locales.values.first, - saveLocale: false, - useFallbackTranslations: true, - assetLoader: const CodegenLoader(), - child: Builder( - builder: (context) => MaterialApp( - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - home: const Material(child: AssetPage(index: 0, heroOffset: 0)), - ), - ), - ), - ), - ); - await tester.pump(const Duration(milliseconds: 600)); - // Ignore any image load errors - tester.takeException(); - - expect(container.read(assetViewerProvider).isZoomed, isTrue); - - // Swap the asset, simulating a delete - timeline.asset = LocalAssetStub.image2; - EventStream.shared.emit(const TimelineReloadEvent()); - - await tester.idle(); - await tester.pump(); - tester.takeException(); - - expect(container.read(assetViewerProvider).isZoomed, isFalse); - }); -} diff --git a/mobile/test/presentation/widgets/asset_viewer/asset_viewer_system_ui_test.dart b/mobile/test/presentation/widgets/asset_viewer/asset_viewer_system_ui_test.dart deleted file mode 100644 index 3ec55bb515..0000000000 --- a/mobile/test/presentation/widgets/asset_viewer/asset_viewer_system_ui_test.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/generated/codegen_loader.g.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; - -import '../../../fixtures/asset.stub.dart'; -import '../../../unit/presentation/presentation_context.dart'; - -class _SeededAssetViewerNotifier extends AssetViewerStateNotifier { - @override - AssetViewerState build() { - super.build(); - return AssetViewerState(currentAsset: LocalAssetStub.image1); - } -} - -TimelineService _stubTimelineService() { - return TimelineService(( - assetSource: (index, count) async => [LocalAssetStub.image1], - bucketSource: () => Stream.value(const [Bucket(assetCount: 1)]), - origin: TimelineOrigin.main, - )); -} - -void main() { - late PresentationContext context; - - setUp(() async { - context = await PresentationContext.create(); - }); - - tearDown(() async { - await context.dispose(); - }); - - testWidgets('status bar icons are light while the asset viewer is open in light mode', (tester) async { - // Emulate arriving from a light-themed page whose AppBar set dark status - // bar icons (the state the viewer is opened from in light mode). - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark); - - await tester.pumpWidget( - EasyLocalization( - supportedLocales: locales.values.toList(), - path: translationsPath, - startLocale: locales.values.first, - fallbackLocale: locales.values.first, - saveLocale: false, - useFallbackTranslations: true, - assetLoader: const CodegenLoader(), - child: ProviderScope( - overrides: [ - ...context.overrides, - timelineServiceProvider.overrideWithValue(_stubTimelineService()), - assetViewerProvider.overrideWith(_SeededAssetViewerNotifier.new), - ], - child: Builder( - builder: (context) => MaterialApp( - debugShowCheckedModeBanner: false, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - home: const Material(child: AssetViewer(initialIndex: 0)), - ), - ), - ), - ), - ); - - // Let post-frame callbacks and the initial frames run without waiting for - // infinite animations (loading spinners) to settle. - await tester.pump(); - await tester.pump(const Duration(milliseconds: 600)); - - // Asset thumbnails cannot load in the test environment (no platform - // channels / real HTTP). Those image errors are irrelevant to the system - // chrome behaviour under test, so drain them. - tester.takeException(); - - expect( - SystemChrome.latestStyle?.statusBarIconBrightness, - Brightness.light, - reason: - 'The asset viewer draws over a black background, so status bar ' - 'icons must be light regardless of the app theme', - ); - }); -} diff --git a/mobile/test/presentation/widgets/asset_viewer/bottom_bar_test.dart b/mobile/test/presentation/widgets/asset_viewer/bottom_bar_test.dart deleted file mode 100644 index f2463c090d..0000000000 --- a/mobile/test/presentation/widgets/asset_viewer/bottom_bar_test.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/widgets/asset_viewer/bottom_bar.widget.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/services/gcast.service.dart'; -import 'package:immich_mobile/utils/asset_filter.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:native_video_player/native_video_player.dart'; - -import '../../../service.mocks.dart'; -import '../../../unit/factories/remote_asset_factory.dart'; -import '../../../widget_tester_extensions.dart'; - -class MockNativeVideoPlayerController extends Mock implements NativeVideoPlayerController {} - -class MockTimelineService extends Mock implements TimelineService {} - -class TestReadOnlyModeNotifier extends ReadOnlyModeNotifier { - @override - bool build() => true; -} - -void main() { - testWidgets('player stays bound after local id arrives', (tester) async { - final searchCopy = RemoteAssetFactory.create(type: .video); - final mergedCopy = searchCopy.copyWith(localId: 'local-1', isFavorite: true); - final assetService = MockAssetService(); - final controller = MockNativeVideoPlayerController(); - final timeline = MockTimelineService(); - final updates = StreamController(sync: true); - when(() => assetService.watchAsset(searchCopy)).thenAnswer((_) => updates.stream); - when(controller.play).thenAnswer((_) async {}); - when(controller.pause).thenAnswer((_) async {}); - when(() => timeline.origin).thenReturn(.search); - - late WidgetRef ref; - - await tester.pumpConsumerWidget( - Consumer( - builder: (context, widgetRef, _) { - ref = widgetRef; - return const ViewerBottomBar(); - }, - ), - overrides: [ - assetServiceProvider.overrideWithValue(assetService), - assetsActionProvider(ActionSource.viewer).overrideWithValue(const AssetFilter({})), - gCastServiceProvider.overrideWithValue(MockGCastService()), - inLockedViewProvider.overrideWithValue(true), - ownedAssetsActionProvider(ActionSource.viewer).overrideWithValue(const AssetFilter({})), - readonlyModeProvider.overrideWith(TestReadOnlyModeNotifier.new), - timelineServiceProvider.overrideWithValue(timeline), - ], - ); - - final viewer = ref.read(assetViewerProvider.notifier); - viewer.setAsset(searchCopy); - await tester.pump(); - ref.read(videoPlayerProvider(searchCopy.id).notifier).attachController(controller); - updates.add(mergedCopy); - await tester.pump(); - await tester.tap(find.byType(IconButton)); - await tester.pump(); - - verify(controller.play).called(1); - viewer.setShowingDetails(true); - await tester.pump(); - verify(controller.pause).called(1); - - await tester.pumpWidget(const SizedBox.shrink()); - unawaited(updates.close()); - }); -} diff --git a/mobile/test/presentation/widgets/images/cache_aware_listener_tracker_mixin_test.dart b/mobile/test/presentation/widgets/images/cache_aware_listener_tracker_mixin_test.dart index 3a5ad159b5..02bb0c1053 100644 --- a/mobile/test/presentation/widgets/images/cache_aware_listener_tracker_mixin_test.dart +++ b/mobile/test/presentation/widgets/images/cache_aware_listener_tracker_mixin_test.dart @@ -29,7 +29,7 @@ void main() { setUp(() { // Create a fresh, real Flutter ImageCache for every test cache = ImageCache(); - uiListener = ImageStreamListener((_, _) {}); + uiListener = ImageStreamListener((_, __) {}); }); group('CacheAwareListenerTrackerMixin with Real ImageCache', () { @@ -130,7 +130,7 @@ void main() { final stream = cache.putIfAbsent(key, () => completer)!; - final uiListener2 = ImageStreamListener((_, _) {}); + final uiListener2 = ImageStreamListener((_, __) {}); stream.addListener(uiListener); stream.addListener(uiListener2); @@ -157,7 +157,7 @@ void main() { expect(completer.wasCancelled, isFalse); // A second UI listener attaches — must NOT be treated as cache - final uiListener2 = ImageStreamListener((_, _) {}); + final uiListener2 = ImageStreamListener((_, __) {}); stream.addListener(uiListener2); // Remove first UI listener; second UI still active → no cancel diff --git a/mobile/test/presentation/widgets/images/local_image_provider_test.dart b/mobile/test/presentation/widgets/images/local_image_provider_test.dart deleted file mode 100644 index 5b556f8a41..0000000000 --- a/mobile/test/presentation/widgets/images/local_image_provider_test.dart +++ /dev/null @@ -1,141 +0,0 @@ -import 'package:flutter/painting.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/presentation/widgets/images/image_provider.dart'; -import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart'; - -import '../../../unit/factories/local_asset_factory.dart'; - -class _StubCompleter extends ImageStreamCompleter {} - -void main() { - late ImageCache cache; - late int loads; - - ImageStreamCompleter load() { - loads++; - return _StubCompleter(); - } - - setUp(() { - cache = ImageCache(); - loads = 0; - }); - - group('LocalFullImageProvider.previewTargetSize', () { - const box = Size(1179, 2556); - const cases = <(String, Size, int?, int?, bool, Size)>[ - ('normal', box, 4032, 3024, true, box), - ('missing dimensions', box, null, null, true, box), - ('invalid dimensions', box, 1000, 0, true, box), - ('long final preview', box, 1000, 30000, true, Size(16384 / 30, 16384)), - ('long first preview', box, 30000, 1000, false, Size(2556, 2556 / 30)), - ('ultra-thin preview', box, 10, 50000, false, Size(1, 2556)), - ('small source', box, 50, 2000, true, Size(50, 2000)), - ('at limit', Size(16384, 100), 1000, 1000, true, Size(16384, 100)), - ('over limit', Size(16385, 100), 1000, 1000, true, Size(1000, 1000)), - ]; - - for (final (name, box, width, height, previewIsFinal, expected) in cases) { - test(name, () { - final actual = LocalFullImageProvider.previewTargetSize( - box.width, - box.height, - width, - height, - previewIsFinal: previewIsFinal, - ); - expect(actual.width, closeTo(expected.width, 1e-6)); - expect(actual.height, closeTo(expected.height, 1e-6)); - }); - } - }); - - group('LocalFullImageProvider equality', () { - LocalFullImageProvider make({int? width, int? height}) => LocalFullImageProvider( - id: 'a', - assetType: AssetType.image, - size: const Size(100, 200), - isAnimated: false, - width: width, - height: height, - ); - - test('uses dimensions in the cache key', () { - final a = make(width: 100, height: 200); - final b = make(width: 100, height: 200); - expect(a, b); - expect(a.hashCode, b.hashCode); - expect(a == make(width: 200, height: 100), isFalse); - }); - }); - - group('LocalThumbProvider caching', () { - test('editing on device re-renders the thumbnail', () { - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image, checksum: 'before'), load); - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image, checksum: 'after'), load); - - expect(loads, 2); - }); - - test('an unchanged thumbnail still comes from the cache', () { - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image, checksum: 'same'), load); - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image, checksum: 'same'), load); - - expect(loads, 1); - }); - - // The rehash clears the checksum before writing the new one, so the tile has to - // follow that step too or it waits for the hash to land before showing the edit. - test('re-renders while the checksum is still being recomputed', () { - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image, checksum: 'before'), load); - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image), load); - - expect(loads, 2); - }); - - test('stays cached while the checksum is missing', () { - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image), load); - cache.putIfAbsent(LocalThumbProvider(id: 'asset-1', assetType: AssetType.image), load); - - expect(loads, 1); - }); - }); - - group('factories', () { - test('thumbnails are keyed by the asset checksum', () { - final asset = LocalAssetFactory.create().copyWith(checksum: 'abc'); - - final provider = getThumbnailImageProvider(asset)! as LocalThumbProvider; - - expect(provider.checksum, 'abc'); - }); - }); - - group('LocalFullImageProvider caching', () { - test('editing on device re-renders the full image', () { - cache.putIfAbsent( - LocalFullImageProvider( - id: 'asset-1', - assetType: AssetType.image, - size: const Size(100, 100), - isAnimated: false, - checksum: 'before', - ), - load, - ); - cache.putIfAbsent( - LocalFullImageProvider( - id: 'asset-1', - assetType: AssetType.image, - size: const Size(100, 100), - isAnimated: false, - checksum: 'after', - ), - load, - ); - - expect(loads, 2); - }); - }); -} diff --git a/mobile/test/presentation/widgets/people/person_edit_birthday_modal_test.dart b/mobile/test/presentation/widgets/people/person_edit_birthday_modal_test.dart deleted file mode 100644 index 387b0ee903..0000000000 --- a/mobile/test/presentation/widgets/people/person_edit_birthday_modal_test.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/presentation/widgets/people/person_edit_birthday_modal.widget.dart'; -import 'package:intl/date_symbol_data_local.dart'; -import 'package:intl/intl.dart'; -import 'package:scroll_date_picker/scroll_date_picker.dart'; - -void main() { - group('datePickerColumnOrder', () { - test('month first (en_US)', () { - expect( - datePickerColumnOrder('M/d/y'), - orderedEquals([DatePickerViewType.month, DatePickerViewType.day, DatePickerViewType.year]), - ); - }); - - test('day first (pl)', () { - expect( - datePickerColumnOrder('dd.MM.y'), - orderedEquals([DatePickerViewType.day, DatePickerViewType.month, DatePickerViewType.year]), - ); - }); - - test('year first (ko)', () { - expect( - datePickerColumnOrder('y. M. d.'), - orderedEquals([DatePickerViewType.year, DatePickerViewType.month, DatePickerViewType.day]), - ); - }); - - test('null pattern falls back to package default', () { - expect(datePickerColumnOrder(null), isNull); - }); - - test('missing field falls back to package default', () { - expect(datePickerColumnOrder('M/y'), isNull); - }); - }); - - group('datePickerColumnOrder with real locale patterns', () { - setUpAll(() async { - await initializeDateFormatting(); - }); - - for (final (locales, order, name) in const [ - ( - ['en', 'en-US', 'en-PH'], - [DatePickerViewType.month, DatePickerViewType.day, DatePickerViewType.year], - 'month/day/year', - ), - ( - ['en-GB', 'fr', 'fr-FR', 'de', 'de-DE', 'pl'], - [DatePickerViewType.day, DatePickerViewType.month, DatePickerViewType.year], - 'day/month/year', - ), - ( - ['ja', 'ja-JP', 'zh', 'zh-CN', 'ko', 'ko-KR'], - [DatePickerViewType.year, DatePickerViewType.month, DatePickerViewType.day], - 'year/month/day', - ), - (['ky'], [DatePickerViewType.year, DatePickerViewType.day, DatePickerViewType.month], 'year/day/month'), - ]) { - for (final locale in locales) { - test('$locale uses $name', () { - expect(datePickerColumnOrder(DateFormat.yMd(locale).pattern), orderedEquals(order)); - }); - } - } - }); -} diff --git a/mobile/test/presentation/widgets/timeline/timeline_args_test.dart b/mobile/test/presentation/widgets/timeline/timeline_args_test.dart deleted file mode 100644 index 439ae9ecf1..0000000000 --- a/mobile/test/presentation/widgets/timeline/timeline_args_test.dart +++ /dev/null @@ -1,194 +0,0 @@ -// ignore_for_file: close_sinks - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart'; -import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; - -// A first fetch that never delivers - the state a suspended or storm-starved -// bucket watch is stuck in when the timeline mounts on a zero-sized first frame -class _FrozenBucketService implements TimelineService { - final _ctrl = StreamController>.broadcast(); - - @override - Stream> Function() get watchBuckets => - () => _ctrl.stream; - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -class _EmptyBucketService implements TimelineService { - const _EmptyBucketService(); - - @override - Stream> Function() get watchBuckets => - () => Stream.value(const []); - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -// Counts how many times the bucket stream is subscribed. Each subscription is a -// fresh photo query, so the count is our proxy for "did the relayout re-run the -// segment query for an input it does not use". -class _CountingBucketService implements TimelineService { - int watchCount = 0; - final _ctrl = StreamController>.broadcast(); - - @override - Stream> Function() get watchBuckets => () { - watchCount++; - return _ctrl.stream; - }; - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -void main() { - testWidgets('timeline args follow constraints after a zero-sized first frame while buckets are still loading', ( - tester, - ) async { - tester.view.physicalSize = Size.zero; - tester.view.devicePixelRatio = 3.0; - addTearDown(tester.view.reset); - - TimelineArgs? probed; - final probe = Consumer( - builder: (_, ref, _) { - probed = ref.watch(timelineArgsProvider); - return const SizedBox.shrink(); - }, - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - timelineServiceProvider.overrideWithValue(_FrozenBucketService()), - appConfigProvider.overrideWithValue(const AppConfig()), - ], - child: MaterialApp(home: Timeline(withScrubber: false, readOnly: true, loadingWidget: probe)), - ), - ); - await tester.pump(); - - expect(probed, isNotNull); - expect(probed!.maxWidth, 0.0); - - tester.view.physicalSize = const Size(1206, 2622); - await tester.pump(); - await tester.pump(); - - expect( - probed!.maxWidth, - 402.0, - reason: 'args locked to the zero-sized first frame leave the timeline blank for the whole session', - ); - }); - - testWidgets('timeline args follow constraints after a zero-sized first frame once buckets resolve', (tester) async { - tester.view.physicalSize = Size.zero; - tester.view.devicePixelRatio = 3.0; - addTearDown(tester.view.reset); - - TimelineArgs? probed; - final probe = SliverToBoxAdapter( - child: Consumer( - builder: (_, ref, _) { - probed = ref.watch(timelineArgsProvider); - return const SizedBox.shrink(); - }, - ), - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - timelineServiceProvider.overrideWithValue(const _EmptyBucketService()), - appConfigProvider.overrideWithValue(const AppConfig()), - ], - child: MaterialApp( - home: Timeline( - withScrubber: false, - readOnly: true, - appBar: const SliverToBoxAdapter(child: SizedBox.shrink()), - topSliverWidget: probe, - ), - ), - ), - ); - await tester.pump(); - await tester.pump(); - - tester.view.physicalSize = const Size(1206, 2622); - await tester.pump(); - await tester.pump(); - await tester.pump(); - - expect(probed, isNotNull); - expect(probed!.maxWidth, 402.0); - }); - - testWidgets('a height-only relayout (multiselect app bar toggle) keeps the timeline, a width change refreshes it', ( - tester, - ) async { - final service = _CountingBucketService(); - tester.view.devicePixelRatio = 3.0; - tester.view.physicalSize = const Size(1206, 2622); - addTearDown(tester.view.reset); - - TimelineArgs? probed; - final probe = Consumer( - builder: (_, ref, _) { - probed = ref.watch(timelineArgsProvider); - return const SizedBox.shrink(); - }, - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - timelineServiceProvider.overrideWithValue(service), - appConfigProvider.overrideWithValue(const AppConfig()), - ], - child: MaterialApp(home: Timeline(withScrubber: false, readOnly: true, loadingWidget: probe)), - ), - ); - await tester.pump(); - - final initialSubscriptions = service.watchCount; - final initialWidth = probed!.maxWidth; - final initialHeight = probed!.maxHeight; - expect(initialSubscriptions, greaterThan(0)); - - // toggling multiselect changes the app bar, so only the available height moves - tester.view.physicalSize = const Size(1206, 2000); - await tester.pump(); - await tester.pump(); - - expect(probed!.maxHeight, isNot(initialHeight), reason: 'the height should have actually changed'); - expect(probed!.maxWidth, initialWidth); - expect( - service.watchCount, - initialSubscriptions, - reason: 'a height-only change must not re-run the bucket query for an input the segments do not use', - ); - - // a real width change (rotation, fold, split screen) should refresh the tiles - tester.view.physicalSize = const Size(1000, 2000); - await tester.pump(); - await tester.pump(); - - expect(probed!.maxWidth, lessThan(initialWidth)); - expect(service.watchCount, greaterThan(initialSubscriptions)); - }); -} diff --git a/mobile/test/presentation/widgets/timeline/timeline_scroll_to_top_test.dart b/mobile/test/presentation/widgets/timeline/timeline_scroll_to_top_test.dart deleted file mode 100644 index e85ced8fb1..0000000000 --- a/mobile/test/presentation/widgets/timeline/timeline_scroll_to_top_test.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'dart:async'; -import 'dart:math' as math; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; -import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/routing/app_navigation_observer.dart'; - -import '../../../fixtures/asset.stub.dart'; - -void main() { - testWidgets('status bar tap only scrolls the timeline when it is the visible page', (tester) async { - tester.view.devicePixelRatio = 3.0; - tester.view.physicalSize = const Size(1206, 2622); - addTearDown(tester.view.reset); - - // Many assets for a tall viewport - final assets = List.generate(200, (i) => LocalAssetStub.image1.copyWith(id: 'a$i')); - final service = TimelineService(( - assetSource: (i, n) async => assets.sublist(i, math.min(i + n, assets.length)), - bucketSource: () => Stream.value([TimeBucket(date: DateTime(2025), assetCount: assets.length)]), - origin: TimelineOrigin.main, - )); - addTearDown(service.dispose); - - final router = RootStackRouter.build( - routes: [ - AutoRoute( - initial: true, - page: PageInfo( - 'Timeline', - builder: (_) => const Timeline( - withScrubber: false, - readOnly: true, - groupBy: GroupAssetsBy.none, - appBar: SliverToBoxAdapter(child: SizedBox.shrink()), - ), - ), - ), - AutoRoute( - path: '/overlay', - page: PageInfo('Overlay', builder: (_) => const Scaffold(body: SizedBox.shrink())), - type: RouteType.custom( - customRouteBuilder: (_, child, page) => - PageRouteBuilder(settings: page, opaque: false, pageBuilder: (_, _, _) => child), - ), - ), - ], - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - timelineServiceProvider.overrideWithValue(service), - appConfigProvider.overrideWithValue(const AppConfig()), - ], - child: MaterialApp.router( - routerConfig: router.config(navigatorObservers: () => [TransitioningRouteObserver()]), - ), - ), - ); - // Segment stream resolves - await tester.pump(); - // Asset buffer settles - await tester.pump(); - // Thumbnails will fail to load - tester.takeException(); - - final position = tester - .state(find.descendant(of: find.byType(Timeline), matching: find.byType(Scrollable)).first) - .position; - - // Closure mimicking the framework making a status bar tap call - Future tapStatusBar() => tester.binding.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.statusBar.name, - SystemChannels.statusBar.codec.encodeMethodCall(const MethodCall('handleScrollToTop')), - null, - ); - - // Scroll down - await tester.drag(find.byType(Timeline), const Offset(0, -1500)); - await tester.pumpAndSettle(); - final initialScrolledPosition = position.pixels; - expect(initialScrolledPosition, greaterThan(200)); - - // Push a subroute. Taps on this status bar should be ignored - unawaited(router.pushPath('/overlay')); - await tester.pumpAndSettle(); - await tapStatusBar(); - await tester.pumpAndSettle(); - expect(position.pixels, initialScrolledPosition, reason: 'ignored while behind a pushed route'); - - // The tap is async, so it can arrive while a pushed route is transitioning to popped - unawaited(router.maybePop()); - await tester.pump(); - await tapStatusBar(); - await tester.pumpAndSettle(); - expect(position.pixels, initialScrolledPosition, reason: 'ignored while the pushed route pops'); - - // Once the timeline is visible, taps on the status bar should scroll to the top - await tapStatusBar(); - await tester.pumpAndSettle(); - expect(position.pixels, 0, reason: 'scrolls the foreground timeline'); - }); -} diff --git a/mobile/test/providers/app_life_cycle_provider_test.dart b/mobile/test/providers/app_life_cycle_provider_test.dart deleted file mode 100644 index d4489a58ba..0000000000 --- a/mobile/test/providers/app_life_cycle_provider_test.dart +++ /dev/null @@ -1,180 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/log.model.dart'; -import 'package:immich_mobile/domain/services/log.service.dart'; -import 'package:immich_mobile/models/auth/auth_state.model.dart'; -import 'package:immich_mobile/models/server_info/server_version.model.dart'; -import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; -import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/providers/websocket.provider.dart'; -import 'package:immich_mobile/utils/upload_speed_calculator.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../infrastructure/repository.mock.dart'; -import '../service.mocks.dart'; - -class FakeLogMessage extends Fake implements LogMessage {} - -class TestAuthNotifier extends AuthNotifier { - TestAuthNotifier(Ref ref) - : super( - MockAuthService(), - MockApiService(), - MockUserService(), - MockSecureStorageService(), - MockWidgetService(), - ref, - ) { - state = const AuthState( - deviceId: 'device-1', - userId: 'user-1', - userEmail: 'user@example.com', - name: 'User', - profileImagePath: '', - isAdmin: false, - isAuthenticated: true, - ); - } - - @override - Future setOpenApiServiceEndpoint() async => 'http://test-server.com'; -} - -class TestWebsocketNotifier extends WebsocketNotifier { - TestWebsocketNotifier(super.ref); - - int connectCount = 0; - int disconnectCount = 0; - final connectCalled = Completer(); - - @override - void connect() { - connectCount++; - if (!connectCalled.isCompleted) { - connectCalled.complete(); - } - throw StateError('unexpected websocket connection'); - } - - @override - void disconnect() => disconnectCount++; -} - -class TestDriftBackupNotifier extends DriftBackupNotifier { - TestDriftBackupNotifier() : super(MockForegroundUploadService(), MockBackgroundUploadService(), UploadSpeedManager()); -} - -void main() { - late LogService logService; - late Completer serverVersion; - late MockServerInfoService serverInfoService; - late MockBackgroundWorkerLockService lockService; - late ProviderContainer container; - late TestWebsocketNotifier websocket; - late AppLifeCycleNotifier lifeCycle; - late int serverVersionCount; - - setUpAll(() async { - final logRepository = MockLogRepository(); - final settingsRepository = MockSettingsRepository(); - registerFallbackValue(FakeLogMessage()); - when(() => logRepository.truncate(limit: any(named: 'limit'))).thenAnswer((_) async {}); - when(() => logRepository.insert(any())).thenAnswer((_) async => true); - when(() => settingsRepository.appConfig).thenReturn(const AppConfig(logLevel: LogLevel.info)); - logService = await LogService.init( - logRepository: logRepository, - settingsRepository: settingsRepository, - shouldBuffer: false, - ); - }); - - tearDownAll(() => logService.dispose()); - - setUp(() { - serverVersion = Completer(); - serverInfoService = MockServerInfoService(); - lockService = MockBackgroundWorkerLockService(); - serverVersionCount = 0; - - when(() => serverInfoService.getServerVersion()).thenAnswer((_) { - serverVersionCount++; - return serverVersionCount == 1 ? serverVersion.future : Future.value(); - }); - when(() => lockService.lock()).thenAnswer((_) async {}); - when(() => lockService.unlock()).thenAnswer((_) async {}); - - container = ProviderContainer( - overrides: [ - authProvider.overrideWith(TestAuthNotifier.new), - serverInfoProvider.overrideWith((_) => ServerInfoNotifier(serverInfoService)), - websocketProvider.overrideWith((ref) { - return websocket = TestWebsocketNotifier(ref); - }), - driftBackupProvider.overrideWith((_) => TestDriftBackupNotifier()), - backgroundWorkerLockServiceProvider.overrideWithValue(lockService), - ], - ); - lifeCycle = container.read(appStateProvider.notifier); - }); - - tearDown(() => container.dispose()); - - Future startResume() async { - await lifeCycle.handleAppPause(); - unawaited(lifeCycle.handleAppResume()); - await untilCalled(() => serverInfoService.getServerVersion()); - } - - Future releaseResume() async { - serverVersion.complete(); - await Future.delayed(Duration.zero); - } - - test('pause during resume does not reconnect websocket', () async { - await startResume(); - await lifeCycle.handleAppPause(); - await releaseResume(); - - expect(lifeCycle.getAppState(), AppLifeCycleEnum.paused); - expect(serverVersionCount, 1); - expect(websocket.disconnectCount, 2); - expect(websocket.connectCount, 0); - }); - - test('inactive resume retries when the app resumes again', () async { - await startResume(); - lifeCycle.handleAppInactivity(); - await releaseResume(); - - unawaited(lifeCycle.handleAppResume()); - await websocket.connectCalled.future; - - expect(lifeCycle.getAppState(), AppLifeCycleEnum.resumed); - expect(serverVersionCount, 2); - expect(websocket.disconnectCount, 1); - expect(websocket.connectCount, 1); - }); - - test('pause after an inactive abort resumes once', () async { - await startResume(); - lifeCycle.handleAppInactivity(); - await releaseResume(); - await lifeCycle.handleAppPause(); - - unawaited(lifeCycle.handleAppResume()); - await websocket.connectCalled.future; - unawaited(lifeCycle.handleAppResume()); - await Future.delayed(Duration.zero); - - expect(lifeCycle.getAppState(), AppLifeCycleEnum.resumed); - expect(serverVersionCount, 2); - expect(websocket.disconnectCount, 2); - expect(websocket.connectCount, 1); - }); -} diff --git a/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart b/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart deleted file mode 100644 index 454923affa..0000000000 --- a/mobile/test/providers/asset_viewer/asset_viewer_provider_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../service.mocks.dart'; -import '../../unit/factories/remote_asset_factory.dart'; - -void main() { - late ProviderContainer container; - late MockAssetService assetService; - - setUpAll(() => registerFallbackValue(RemoteAssetFactory.create())); - - setUp(() { - assetService = MockAssetService(); - when(() => assetService.watchAsset(any())).thenAnswer((_) => const Stream.empty()); - - container = ProviderContainer(overrides: [assetServiceProvider.overrideWithValue(assetService)]); - addTearDown(container.dispose); - }); - - group('AssetViewerStateNotifier asset watching', () { - test('propagates stream updates for the current asset into state', () async { - final controller = StreamController(); - addTearDown(controller.close); - final asset = RemoteAssetFactory.create(); - when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); - - final notifier = container.read(assetViewerProvider.notifier); - notifier.setAsset(asset); - - final updated = asset.copyWith(isFavorite: true); - controller.add(updated); - await pumpEventQueue(); - - expect(container.read(assetViewerProvider).currentAsset, updated); - }); - - test('ignores null stream emissions', () async { - final controller = StreamController(); - addTearDown(controller.close); - final asset = RemoteAssetFactory.create(); - when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); - - container.read(assetViewerProvider.notifier).setAsset(asset); - - controller.add(null); - await pumpEventQueue(); - - expect(container.read(assetViewerProvider).currentAsset, asset); - }); - - test('reset cancels the subscription so later emissions are dropped', () async { - final controller = StreamController(); - addTearDown(controller.close); - final asset = RemoteAssetFactory.create(); - when(() => assetService.watchAsset(asset)).thenAnswer((_) => controller.stream); - - final notifier = container.read(assetViewerProvider.notifier); - notifier.setAsset(asset); - notifier.reset(); - - controller.add(RemoteAssetFactory.create(isFavorite: true)); - await pumpEventQueue(); - - expect(container.read(assetViewerProvider).currentAsset, isNull); - }); - - test('setAsset switches the current asset, cancels the previous watch and listens to the new one', () async { - final first = StreamController(); - final second = StreamController(); - addTearDown(first.close); - addTearDown(second.close); - - final assetOne = RemoteAssetFactory.create(); - final assetTwo = RemoteAssetFactory.create(); - when(() => assetService.watchAsset(assetOne)).thenAnswer((_) => first.stream); - when(() => assetService.watchAsset(assetTwo)).thenAnswer((_) => second.stream); - - final notifier = container.read(assetViewerProvider.notifier); - notifier.setAsset(assetOne); - expect(container.read(assetViewerProvider).currentAsset, assetOne); - - // Updates to the first asset propagate into state. - final updatedOne = assetOne.copyWith(visibility: .archive); - first.add(updatedOne); - await pumpEventQueue(); - expect(container.read(assetViewerProvider).currentAsset, updatedOne); - - // Switch to new asset - notifier.setAsset(assetTwo); - expect(container.read(assetViewerProvider).currentAsset, assetTwo); - - // The previous watch is cancelled: stale emissions from the first stream are dropped. - first.add(assetOne.copyWith(isFavorite: true)); - await pumpEventQueue(); - expect(container.read(assetViewerProvider).currentAsset, assetTwo); - - // The new asset is watched instead: its emissions propagate into state. - final updatedTwo = assetTwo.copyWith(isFavorite: true); - second.add(updatedTwo); - await pumpEventQueue(); - expect(container.read(assetViewerProvider).currentAsset, updatedTwo); - }); - - test('keeps the tapped tile size while swiping', () { - final first = RemoteAssetFactory.create(); - final second = RemoteAssetFactory.create(); - final notifier = container.read(assetViewerProvider.notifier); - - notifier.setAsset(first, thumbnailSize: const Size.square(642)); - expect(container.read(assetViewerProvider).thumbnailSize, const Size.square(642)); - - notifier.setAsset(second); - expect(container.read(assetViewerProvider).thumbnailSize, const Size.square(642)); - - notifier.reset(); - expect(container.read(assetViewerProvider).thumbnailSize, isNull); - }); - }); -} diff --git a/mobile/test/providers/asset_viewer/download_provider_test.dart b/mobile/test/providers/asset_viewer/download_provider_test.dart deleted file mode 100644 index 2c5804747a..0000000000 --- a/mobile/test/providers/asset_viewer/download_provider_test.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:background_downloader/background_downloader.dart'; -import 'package:fake_async/fake_async.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/models/download/livephotos_medatada.model.dart'; -import 'package:immich_mobile/providers/asset_viewer/download.provider.dart'; -import 'package:immich_mobile/services/download.service.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockDownloadService extends Mock implements DownloadService {} - -DownloadTask _task(String id, {String filename = 'photo.jpg', String metaData = ''}) => - DownloadTask(taskId: id, url: 'https://example.com/$filename', filename: filename, metaData: metaData); - -void main() { - late MockDownloadService service; - late DownloadStateNotifier notifier; - late void Function(TaskProgressUpdate) onProgress; - late void Function(TaskStatusUpdate) onImage; - late void Function(TaskStatusUpdate) onLivePhoto; - - setUp(() { - service = MockDownloadService(); - notifier = DownloadStateNotifier(service); - addTearDown(() { - if (notifier.mounted) { - notifier.dispose(); - } - }); - - onProgress = verify(() => service.onTaskProgress = captureAny()).captured.last as void Function(TaskProgressUpdate); - onImage = - verify(() => service.onImageDownloadStatus = captureAny()).captured.last as void Function(TaskStatusUpdate); - onLivePhoto = - verify(() => service.onLivePhotoDownloadStatus = captureAny()).captured.last as void Function(TaskStatusUpdate); - }); - - test('complete flips the entry and keeps it visible', () { - fakeAsync((async) { - final task = _task('task-1'); - onProgress(TaskProgressUpdate(task, 0.4)); - onImage(TaskStatusUpdate(task, TaskStatus.complete)); - - expect(notifier.state.taskProgress['task-1']?.status, TaskStatus.complete); - expect(notifier.state.showProgress, isTrue); - - async.elapse(const Duration(seconds: 5)); - - expect(notifier.state.taskProgress['task-1']?.status, TaskStatus.complete); - expect(notifier.state.showProgress, isTrue); - }); - }); - - test('failed keeps the entry visible', () { - fakeAsync((async) { - final task = _task('task-1'); - onProgress(TaskProgressUpdate(task, 0.4)); - onImage(TaskStatusUpdate(task, TaskStatus.failed)); - - expect(notifier.state.taskProgress['task-1']?.status, TaskStatus.failed); - - async.elapse(const Duration(seconds: 5)); - - expect(notifier.state.taskProgress['task-1']?.status, TaskStatus.failed); - expect(notifier.state.showProgress, isTrue); - }); - }); - - test('a live photo part completion keeps both entries', () { - fakeAsync((async) { - final image = _task( - 'live-image', - metaData: const LivePhotosMetadata(part: LivePhotosPart.image, id: 'live-1').toJson(), - ); - final video = _task( - 'live-video', - filename: 'photo.MOV', - metaData: const LivePhotosMetadata(part: LivePhotosPart.video, id: 'live-1').toJson(), - ); - onProgress(TaskProgressUpdate(image, 0.9)); - onProgress(TaskProgressUpdate(video, 0.9)); - onLivePhoto(TaskStatusUpdate(image, TaskStatus.complete)); - - async.elapse(const Duration(seconds: 5)); - - expect(notifier.state.taskProgress['live-image']?.status, TaskStatus.complete); - expect(notifier.state.taskProgress['live-video']?.status, TaskStatus.running); - expect(notifier.state.showProgress, isTrue); - }); - }); - - test('canceled does not resurrect or alter the entry', () { - fakeAsync((async) { - final task = _task('task-1'); - onProgress(TaskProgressUpdate(task, 0.4)); - onImage(TaskStatusUpdate(task, TaskStatus.canceled)); - - expect(notifier.state.taskProgress['task-1']?.status, TaskStatus.running); - expect(notifier.state.taskProgress['task-1']?.progress, 0.4); - - onImage(TaskStatusUpdate(_task('ghost'), TaskStatus.canceled)); - expect(notifier.state.taskProgress.containsKey('ghost'), isFalse); - }); - }); - - test('a status for an unknown task does not create an entry', () { - fakeAsync((async) { - onImage(TaskStatusUpdate(_task('ghost'), TaskStatus.complete)); - - expect(notifier.state.taskProgress, isEmpty); - expect(notifier.state.showProgress, isFalse); - }); - }); -} diff --git a/mobile/test/providers/backup/drift_backup_provider_test.dart b/mobile/test/providers/backup/drift_backup_provider_test.dart deleted file mode 100644 index 205f564cd9..0000000000 --- a/mobile/test/providers/backup/drift_backup_provider_test.dart +++ /dev/null @@ -1,109 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; -import 'package:immich_mobile/services/background_upload.service.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_mobile/utils/upload_speed_calculator.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockForegroundUploadService extends Mock implements ForegroundUploadService {} - -class MockBackgroundUploadService extends Mock implements BackgroundUploadService {} - -void main() { - late MockForegroundUploadService foregroundUploadService; - late MockBackgroundUploadService backgroundUploadService; - late DriftBackupNotifier notifier; - - setUpAll(() { - registerFallbackValue(Completer()); - registerFallbackValue(const UploadCallbacks()); - }); - - setUp(() { - foregroundUploadService = MockForegroundUploadService(); - backgroundUploadService = MockBackgroundUploadService(); - notifier = DriftBackupNotifier(foregroundUploadService, backgroundUploadService, UploadSpeedManager()); - addTearDown(() { - if (notifier.mounted) { - notifier.dispose(); - } - }); - }); - - void mockCounts({required int total, required int remainder, int processing = 0}) { - when( - () => foregroundUploadService.getBackupCounts('user-1'), - ).thenAnswer((_) async => (total: total, remainder: remainder, processing: processing)); - } - - // Drives a backup run so we can grab the onSuccess callback the notifier wires up. - Future startAndCaptureOnSuccess() async { - void Function(String, String)? onSuccess; - when(() => foregroundUploadService.uploadCandidates(any(), any(), callbacks: any(named: 'callbacks'))).thenAnswer(( - invocation, - ) async { - onSuccess = (invocation.namedArguments[#callbacks] as UploadCallbacks).onSuccess; - }); - await notifier.startForegroundBackup('user-1'); - return onSuccess!; - } - - group('foreground backup counts', () { - test('successes move one asset from remainder to backup', () async { - mockCounts(total: 25, remainder: 25); - final onSuccess = await startAndCaptureOnSuccess(); - - for (var i = 0; i < 10; i++) { - onSuccess('asset-$i', 'remote-$i'); - } - - expect(notifier.state.remainderCount, 15); - expect(notifier.state.backupCount, 10); - expect(notifier.state.backupCount + notifier.state.remainderCount, notifier.state.totalCount); - }); - - test('a duplicate success after pause and resume cannot go below zero', () async { - // #26215: app pauses mid-backup, sync has not recorded the upload yet, so the - // resumed run re-uploads the same asset and the server answers 200 duplicate. - // The start of each run re-baselines the counters from the DB, so the duplicate - // success is counted against a baseline that includes the asset again. - mockCounts(total: 1, remainder: 1); - - final firstRun = await startAndCaptureOnSuccess(); - expect(notifier.state.remainderCount, 1); - firstRun('asset-1', 'remote-1'); - expect(notifier.state.remainderCount, 0); - - notifier.stopForegroundBackup(reason: "test"); - - final resumedRun = await startAndCaptureOnSuccess(); - expect(notifier.state.remainderCount, 1); - verify(() => foregroundUploadService.getBackupCounts('user-1')).called(2); - - resumedRun('asset-1', 'remote-1'); - expect(notifier.state.remainderCount, 0); - expect(notifier.state.backupCount, 1); - }); - - test('a drifted counter state heals at run start', () async { - mockCounts(total: 91, remainder: 7); - notifier.state = notifier.state.copyWith(totalCount: 91, backupCount: 103, remainderCount: -12); - - await startAndCaptureOnSuccess(); - - expect(notifier.state.totalCount, 91); - expect(notifier.state.remainderCount, 7); - expect(notifier.state.backupCount, 84); - }); - - test('a late success after dispose does not throw', () async { - mockCounts(total: 2, remainder: 2); - final onSuccess = await startAndCaptureOnSuccess(); - notifier.dispose(); - - onSuccess('asset-1', 'remote-1'); - }); - }); -} diff --git a/mobile/test/providers/infrastructure/memory_provider_test.dart b/mobile/test/providers/infrastructure/memory_provider_test.dart deleted file mode 100644 index 18e481573e..0000000000 --- a/mobile/test/providers/infrastructure/memory_provider_test.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:fake_async/fake_async.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/user.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/memory.repository.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../infrastructure/repository.mock.dart'; - -class MockUserService extends Mock implements UserService {} - -void main() { - late MockMemoryRepository memoryRepository; - late MockUserService userService; - - UserDto user({bool memoryEnabled = true}) => UserDto( - id: 'user-1', - email: 'user@test.dev', - name: 'user', - memoryEnabled: memoryEnabled, - profileChangedAt: DateTime(2026), - ); - - Drift mockDrift(MemoryRepository repository) { - final drift = MockDrift(); - when(() => drift.memoryRepository).thenReturn(repository); - return drift; - } - - ProviderContainer makeContainer() { - final container = ProviderContainer( - overrides: [ - driftProvider.overrideWithValue(mockDrift(memoryRepository)), - currentUserProvider.overrideWith((ref) => CurrentUserProvider(userService)), - ], - ); - addTearDown(container.dispose); - return container; - } - - setUp(() { - memoryRepository = MockMemoryRepository(); - userService = MockUserService(); - - when(() => memoryRepository.getAll('user-1')).thenAnswer((_) async => []); - when(() => userService.tryGetMyUser()).thenReturn(user()); - when(() => userService.watchMyUser()).thenAnswer((_) => const Stream.empty()); - }); - - group('driftMemoryFutureProvider', () { - test('re-queries after local midnight', () { - fakeAsync((async) { - final container = makeContainer(); - container.listen(driftMemoryFutureProvider, (_, _) {}); - async.flushMicrotasks(); - - verify(() => memoryRepository.getAll('user-1')).called(1); - - async.elapse(const Duration(seconds: 4)); - async.flushMicrotasks(); - verifyNever(() => memoryRepository.getAll('user-1')); - - async.elapse(const Duration(hours: 25)); - async.flushMicrotasks(); - verify(() => memoryRepository.getAll('user-1')).called(greaterThanOrEqualTo(1)); - }); - }); - - test('cancels the midnight timer when disposed', () { - fakeAsync((async) { - final container = makeContainer(); - final subscription = container.listen(driftMemoryFutureProvider, (_, _) {}); - async.flushMicrotasks(); - verify(() => memoryRepository.getAll('user-1')).called(1); - - subscription.close(); - async.elapse(const Duration(hours: 25)); - async.flushMicrotasks(); - - verifyNever(() => memoryRepository.getAll('user-1')); - }); - }); - - test('does not query or arm the timer when memories are disabled', () { - when(() => userService.tryGetMyUser()).thenReturn(user(memoryEnabled: false)); - - fakeAsync((async) { - final container = makeContainer(); - container.listen(driftMemoryFutureProvider, (_, _) {}); - async.flushMicrotasks(); - - async.elapse(const Duration(hours: 25)); - async.flushMicrotasks(); - - verifyNever(() => memoryRepository.getAll(any())); - }); - }); - }); -} diff --git a/mobile/test/providers/view_intent/view_intent_handler_android_test.dart b/mobile/test/providers/view_intent/view_intent_handler_android_test.dart deleted file mode 100644 index be7549e202..0000000000 --- a/mobile/test/providers/view_intent/view_intent_handler_android_test.dart +++ /dev/null @@ -1,269 +0,0 @@ -import 'dart:async'; - -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/asset.service.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/domain/services/user.service.dart'; -import 'package:immich_mobile/models/auth/auth_state.model.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/auth.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_handler_android.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/api.service.dart'; -import 'package:immich_mobile/services/auth.service.dart'; -import 'package:immich_mobile/services/secure_storage.service.dart'; -import 'package:immich_mobile/services/view_intent.service.dart'; -import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; -import 'package:immich_mobile/services/widget.service.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockViewIntentHostApi extends Mock implements ViewIntentHostApi {} - -class MockViewIntentAssetResolver extends Mock implements ViewIntentAssetResolver {} - -class MockAppRouter extends Mock implements AppRouter {} - -class MockAuthService extends Mock implements AuthService {} - -class MockApiService extends Mock implements ApiService {} - -class MockUserService extends Mock implements UserService {} - -class MockSecureStorageService extends Mock implements SecureStorageService {} - -class MockWidgetService extends Mock implements WidgetService {} - -class FakePageRouteInfo extends Fake implements PageRouteInfo {} - -class FakeTimelineService extends Fake implements TimelineService {} - -class FakeAssetService extends Fake implements AssetService { - @override - Stream watchAsset(BaseAsset asset) => const Stream.empty(); -} - -class TestViewIntentService extends ViewIntentService { - ViewIntentPayload? consumedAttachment; - int cleanupStaleTempFilesCalls = 0; - int cleanupManagedTempFileCalls = 0; - final List managedTempPaths = []; - - TestViewIntentService() : super(MockViewIntentHostApi()); - - @override - Future consumeViewIntent() async => consumedAttachment; - - @override - Future cleanupStaleTempFiles() async { - cleanupStaleTempFilesCalls++; - } - - @override - Future cleanupManagedTempFile() async { - cleanupManagedTempFileCalls++; - } - - @override - Future setManagedTempFilePath(String path) async { - managedTempPaths.add(path); - } -} - -class TestAuthNotifier extends AuthNotifier { - TestAuthNotifier(Ref ref, AuthState initial) - : super( - MockAuthService(), - MockApiService(), - MockUserService(), - MockSecureStorageService(), - MockWidgetService(), - ref, - ) { - state = initial; - } - - void setAuthenticated(bool isAuthenticated) { - state = state.copyWith(isAuthenticated: isAuthenticated); - } -} - -final _handlerProvider = Provider((ref) => AndroidViewIntentHandler(ref)); - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - late TestViewIntentService viewIntentService; - late MockViewIntentAssetResolver resolver; - late MockAppRouter router; - late TestAuthNotifier authNotifier; - late ProviderContainer container; - late AndroidViewIntentHandler handler; - late ViewIntentPayload payload; - late LocalAsset deepLinkAsset; - late TimelineService deepLinkTimelineService; - - setUpAll(() { - registerFallbackValue(FakePageRouteInfo()); - registerFallbackValue(>[]); - registerFallbackValue(FakeTimelineService()); - registerFallbackValue( - ViewIntentPayload(path: '/tmp/fallback.jpg', mimeType: 'image/jpeg', localAssetId: 'fallback'), - ); - }); - - setUp(() async { - viewIntentService = TestViewIntentService(); - resolver = MockViewIntentAssetResolver(); - router = MockAppRouter(); - payload = ViewIntentPayload(path: '/tmp/incoming.jpg', mimeType: 'image/jpeg', localAssetId: 'local-1'); - deepLinkAsset = _localAsset(id: 'local-1'); - deepLinkTimelineService = await _createReadyTimelineService([deepLinkAsset], TimelineOrigin.deepLink); - - when(() => router.replaceAll(any())).thenAnswer((_) async {}); - - container = ProviderContainer( - overrides: [ - viewIntentServiceProvider.overrideWithValue(viewIntentService), - viewIntentAssetResolverProvider.overrideWithValue(resolver), - appRouterProvider.overrideWithValue(router), - authProvider.overrideWith((ref) { - authNotifier = TestAuthNotifier(ref, _authState(isAuthenticated: true)); - return authNotifier; - }), - assetServiceProvider.overrideWithValue(FakeAssetService()), - ], - ); - - authNotifier = container.read(authProvider.notifier) as TestAuthNotifier; - handler = container.read(_handlerProvider); - - addTearDown(() async { - await deepLinkTimelineService.dispose(); - container.dispose(); - }); - }); - - test('handle defers unauthenticated attachment', () async { - authNotifier.setAuthenticated(false); - - await handler.handle(payload); - - expect(container.read(viewIntentPendingProvider), payload); - verifyNever(() => resolver.resolve(any())); - }); - - testWidgets('flushDeferredViewIntent consumes the pending attachment and routes the viewer', (tester) async { - authNotifier.setAuthenticated(false); - container.read(viewIntentPendingProvider.notifier).defer(payload); - authNotifier.setAuthenticated(true); - - when(() => resolver.resolve(payload)).thenAnswer((_) async { - return ViewIntentResolvedAsset(asset: deepLinkAsset, timelineService: deepLinkTimelineService); - }); - - unawaited(handler.flushDeferredViewIntent()); - await tester.pump(); - await tester.pump(); - await tester.idle(); - - expect(container.read(viewIntentPendingProvider), isNull); - verify(() => resolver.resolve(payload)).called(1); - }); - - test('flushDeferredViewIntent does nothing when there is no pending attachment', () async { - await handler.flushDeferredViewIntent(); - - verifyNever(() => resolver.resolve(any())); - }); - - test('onAppResumed cleans stale temp files when no attachment is present', () async { - viewIntentService.consumedAttachment = null; - - await handler.onAppResumed(); - - expect(viewIntentService.cleanupStaleTempFilesCalls, 1); - verifyNever(() => resolver.resolve(any())); - }); - - test('onAppResumed does not clean stale temp files while pending attachment exists', () async { - viewIntentService.consumedAttachment = null; - container.read(viewIntentPendingProvider.notifier).defer(payload); - - await handler.onAppResumed(); - - expect(viewIntentService.cleanupStaleTempFilesCalls, 0); - verifyNever(() => resolver.resolve(any())); - }); - - testWidgets('onAppResumed handles attachment immediately when authenticated', (tester) async { - viewIntentService.consumedAttachment = payload; - when( - () => resolver.resolve(payload), - ).thenAnswer((_) async => ViewIntentResolvedAsset(asset: deepLinkAsset, timelineService: deepLinkTimelineService)); - - unawaited(handler.onAppResumed()); - await tester.pump(); - await tester.pump(); - await tester.pump(); - await tester.idle(); - - verify(() => resolver.resolve(payload)).called(1); - // Routes the user to [TabShell, AssetViewer] so back-press lands on the - // main timeline — mirrors the home-screen widget navigation pattern. - final captured = verify(() => router.replaceAll(captureAny())).captured; - expect(captured, hasLength(1)); - final routes = captured.single as List>; - expect(routes, hasLength(2)); - expect(routes[0].routeName, TabShellRoute.name); - expect(routes[1].routeName, AssetViewerRoute.name); - }); -} - -AuthState _authState({required bool isAuthenticated}) { - return AuthState( - deviceId: 'device-1', - userId: 'user-1', - userEmail: 'user@example.com', - isAuthenticated: isAuthenticated, - name: 'User', - isAdmin: false, - profileImagePath: '', - ); -} - -LocalAsset _localAsset({required String id}) { - return LocalAsset( - id: id, - name: '$id.jpg', - checksum: 'checksum-1', - type: AssetType.image, - createdAt: DateTime(2026, 4, 20), - updatedAt: DateTime(2026, 4, 20), - playbackStyle: AssetPlaybackStyle.image, - isEdited: false, - ); -} - -TimelineService _timelineServiceFromAssets(List assets, TimelineOrigin origin) { - return TimelineService(( - assetSource: (index, count) async => assets.skip(index).take(count).toList(), - bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]), - origin: origin, - )); -} - -Future _createReadyTimelineService(List assets, TimelineOrigin origin) async { - final timelineService = _timelineServiceFromAssets(assets, origin); - // Spin a few async ticks so the internal bucket subscription has populated - // the buffer before tests start asserting against totalAssets. - for (var i = 0; i < 20 && timelineService.totalAssets != assets.length; i++) { - await Future.delayed(Duration.zero); - } - return timelineService; -} diff --git a/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart b/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart deleted file mode 100644 index a3982e8029..0000000000 --- a/mobile/test/providers/view_intent/view_intent_pending_provider_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/view_intent/view_intent_pending.provider.dart'; - -void main() { - late DateTime now; - late ProviderContainer container; - - final attachment = ViewIntentPayload( - path: '/tmp/file.jpg', - mimeType: 'image/jpeg', - localAssetId: '42', - ); - - setUp(() { - now = DateTime(2026, 4, 17, 12); - container = ProviderContainer( - overrides: [viewIntentNowProvider.overrideWithValue(() => now)], - ); - addTearDown(container.dispose); - }); - - test('defer stores pending attachment', () { - container.read(viewIntentPendingProvider.notifier).defer(attachment); - - expect(container.read(viewIntentPendingProvider), attachment); - }); - - test('takeIfFresh returns pending attachment once', () { - container.read(viewIntentPendingProvider.notifier).defer(attachment); - - final first = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); - final second = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); - - expect(first, attachment); - expect(second, isNull); - }); - - test('takeIfFresh drops expired attachment', () { - container.read(viewIntentPendingProvider.notifier).defer(attachment); - now = now.add(const Duration(minutes: 11)); - - final result = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); - - expect(result, isNull); - expect(container.read(viewIntentPendingProvider), isNull); - }); - - test('newer deferred attachment replaces older one', () { - final newerAttachment = ViewIntentPayload( - path: '/tmp/file-2.jpg', - mimeType: 'image/jpeg', - localAssetId: '43', - ); - - container.read(viewIntentPendingProvider.notifier).defer(attachment); - container.read(viewIntentPendingProvider.notifier).defer(newerAttachment); - - final result = container.read(viewIntentPendingProvider.notifier).takeIfFresh(); - - expect(result, newerAttachment); - }); -} diff --git a/mobile/test/repositories/drift_album_api_repository_test.dart b/mobile/test/repositories/drift_album_api_repository_test.dart deleted file mode 100644 index 93c45c6687..0000000000 --- a/mobile/test/repositories/drift_album_api_repository_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/repositories/drift_album_api_repository.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:openapi/api.dart'; - -class _MockAlbumsApi extends Mock implements AlbumsApi {} - -void main() { - late _MockAlbumsApi api; - late DriftAlbumApiRepository repo; - - setUpAll(() { - registerFallbackValue(BulkIdsDto(ids: const [])); - }); - - setUp(() { - api = _MockAlbumsApi(); - repo = DriftAlbumApiRepository(api); - }); - - void stubResponse(List response) { - when( - () => api.addAssetsToAlbum(any(), any(), abortTrigger: any(named: 'abortTrigger')), - ).thenAnswer((_) async => response); - } - - test('no_permission failure surfaces as failed, not added (the #22342 bug)', () async { - stubResponse([ - BulkIdResponseDto(id: 'a1', success: false, error: const Optional.present(BulkIdErrorReason.noPermission)), - ]); - - final result = await repo.addAssets('album1', ['a1']); - - expect(result.added, isEmpty); - expect(result.failed, ['a1']); - }); - - test('duplicate is neither added nor failed (genuinely already in album)', () async { - stubResponse([ - BulkIdResponseDto(id: 'a1', success: false, error: const Optional.present(BulkIdErrorReason.duplicate)), - ]); - - final result = await repo.addAssets('album1', ['a1']); - - expect(result.added, isEmpty); - expect(result.failed, isEmpty); - }); - - test('success is added', () async { - stubResponse([BulkIdResponseDto(id: 'a1', success: true)]); - - final result = await repo.addAssets('album1', ['a1']); - - expect(result.added, ['a1']); - expect(result.failed, isEmpty); - }); - - test('not_found and unknown count as failures', () async { - stubResponse([ - BulkIdResponseDto(id: 'a1', success: false, error: const Optional.present(BulkIdErrorReason.notFound)), - BulkIdResponseDto(id: 'a2', success: false, error: const Optional.present(BulkIdErrorReason.unknown)), - ]); - - final result = await repo.addAssets('album1', ['a1', 'a2']); - - expect(result.added, isEmpty); - expect(result.failed, ['a1', 'a2']); - }); - - test('mixed: added kept, no_permission failed, duplicate dropped', () async { - stubResponse([ - BulkIdResponseDto(id: 'ok', success: true), - BulkIdResponseDto(id: 'perm', success: false, error: const Optional.present(BulkIdErrorReason.noPermission)), - BulkIdResponseDto(id: 'dup', success: false, error: const Optional.present(BulkIdErrorReason.duplicate)), - ]); - - final result = await repo.addAssets('album1', ['ok', 'perm', 'dup']); - - expect(result.added, ['ok']); - expect(result.failed, ['perm']); - }); -} diff --git a/mobile/test/repositories/upload_repository_test.dart b/mobile/test/repositories/upload_repository_test.dart deleted file mode 100644 index 516559de50..0000000000 --- a/mobile/test/repositories/upload_repository_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:background_downloader/background_downloader.dart'; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:http/http.dart' as http; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/repositories/upload.repository.dart'; -import 'package:mocktail/mocktail.dart'; - -class _MockHttpClient extends Mock implements http.Client {} - -class _FakeBaseRequest extends Fake implements http.BaseRequest {} - -// keeps the FileDownloader singleton off the disk and off the platform channels -class _NoStorage extends Fake implements PersistentStorage { - @override - Future initialize() async {} -} - -void main() { - late _MockHttpClient client; - late UploadRepository sut; - late File file; - - setUpAll(() async { - TestWidgetsFlutterBinding.ensureInitialized(); - FileDownloader(persistentStorage: _NoStorage()); - final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); - await Store.put(StoreKey.serverEndpoint, 'http://demo.immich.app/api'); - registerFallbackValue(_FakeBaseRequest()); - file = File('${Directory.systemTemp.createTempSync().path}/photo.jpg')..writeAsStringSync('bytes'); - }); - - setUp(() { - client = _MockHttpClient(); - sut = UploadRepository(); - }); - - // consumes the body like a real client would, so a reused request would blow up on the second send - void stubSend(FutureOr Function(int attempt) answer) { - var attempt = 0; - when(() => client.send(any())).thenAnswer((invocation) async { - final request = invocation.positionalArguments.single as http.BaseRequest; - await request.finalize().drain(); - return answer(++attempt); - }); - } - - http.StreamedResponse response(int status, String body) => - http.StreamedResponse(Stream.value(utf8.encode(body)), status); - - Future upload() => sut.uploadFile( - file: file, - originalFileName: 'photo.jpg', - fields: const {'deviceAssetId': 'a1'}, - cancelToken: null, - logContext: 'a1', - httpClient: client, - ); - - test('resends once when the first send dies before a response', () async { - stubSend((attempt) { - if (attempt == 1) { - throw http.ClientException('Broken pipe'); - } - return response(201, '{"id":"remote-1"}'); - }); - - final result = await upload(); - - expect(result.isSuccess, isTrue); - expect(result.remoteAssetId, 'remote-1'); - verify(() => client.send(any())).called(2); - }); - - test('a second transport failure is an error, no third send', () async { - stubSend((_) => throw http.ClientException('Connection reset')); - - final result = await upload(); - - expect(result.isSuccess, isFalse); - expect(result.isCancelled, isFalse); - verify(() => client.send(any())).called(2); - }); - - test('a cancelled upload is not resent', () async { - stubSend((_) => throw http.RequestAbortedException()); - - final result = await upload(); - - expect(result.isCancelled, isTrue); - verify(() => client.send(any())).called(1); - }); - - test('a cancel during the resend still counts as cancelled', () async { - stubSend((attempt) { - if (attempt == 1) { - throw http.ClientException('Broken pipe'); - } - throw http.RequestAbortedException(); - }); - - final result = await upload(); - - expect(result.isCancelled, isTrue); - verify(() => client.send(any())).called(2); - }); - - test('a server error response is not resent', () async { - stubSend((_) => response(500, '{"message":"boom"}')); - - final result = await upload(); - - expect(result.statusCode, 500); - expect(result.errorMessage, 'boom'); - verify(() => client.send(any())).called(1); - }); -} diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index 164e7fe062..4b54ec4055 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -1,25 +1,48 @@ -import 'package:immich_mobile/domain/services/tag.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/remote_exif.repository.dart'; -import 'package:immich_mobile/repositories/asset_api.repository.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/infrastructure/repositories/exif.repository.dart'; +import 'package:immich_mobile/repositories/partner_api.repository.dart'; +import 'package:immich_mobile/repositories/album_media.repository.dart'; +import 'package:immich_mobile/repositories/album_api.repository.dart'; +import 'package:immich_mobile/repositories/partner.repository.dart'; +import 'package:immich_mobile/repositories/etag.repository.dart'; +import 'package:immich_mobile/repositories/local_files_manager.repository.dart'; +import 'package:immich_mobile/repositories/file_media.repository.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/auth.repository.dart'; import 'package:immich_mobile/repositories/auth_api.repository.dart'; -import 'package:immich_mobile/repositories/download.repository.dart'; -import 'package:immich_mobile/repositories/permission.repository.dart'; +import 'package:immich_mobile/repositories/asset.repository.dart'; +import 'package:immich_mobile/repositories/asset_media.repository.dart'; +import 'package:immich_mobile/repositories/album.repository.dart'; +import 'package:immich_mobile/repositories/asset_api.repository.dart'; import 'package:mocktail/mocktail.dart'; +class MockAlbumRepository extends Mock implements AlbumRepository {} + +class MockAssetRepository extends Mock implements AssetRepository {} + +class MockBackupRepository extends Mock implements BackupAlbumRepository {} + +class MockExifInfoRepository extends Mock implements IsarExifRepository {} + +class MockETagRepository extends Mock implements ETagRepository {} + +class MockAlbumMediaRepository extends Mock implements AlbumMediaRepository {} + +class MockBackupAlbumRepository extends Mock implements BackupAlbumRepository {} + class MockAssetApiRepository extends Mock implements AssetApiRepository {} class MockAssetMediaRepository extends Mock implements AssetMediaRepository {} -class MockPermissionRepository extends Mock implements DevicePermissionRepository {} +class MockFileMediaRepository extends Mock implements FileMediaRepository {} + +class MockAlbumApiRepository extends Mock implements AlbumApiRepository {} class MockAuthApiRepository extends Mock implements AuthApiRepository {} class MockAuthRepository extends Mock implements AuthRepository {} -class MockTagService extends Mock implements TagService {} +class MockPartnerRepository extends Mock implements PartnerRepository {} -class MockDownloadRepository extends Mock implements DownloadRepository {} +class MockPartnerApiRepository extends Mock implements PartnerApiRepository {} -class MockRemoteExifRepository extends Mock implements RemoteExifRepository {} +class MockLocalFilesManagerRepository extends Mock implements LocalFilesManagerRepository {} diff --git a/mobile/test/riverpod_mocks.dart b/mobile/test/riverpod_mocks.dart deleted file mode 100644 index 24867b226e..0000000000 --- a/mobile/test/riverpod_mocks.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:immich_mobile/models/server_info/server_version.model.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; - -class StubServerInfoNotifier extends ServerInfoNotifier { - StubServerInfoNotifier(super.serverInfoService, {required ServerVersion version}) { - state = state.copyWith(serverVersion: version); - } -} diff --git a/mobile/test/service.mocks.dart b/mobile/test/service.mocks.dart index ecf9c771bc..87a8c01cf0 100644 --- a/mobile/test/service.mocks.dart +++ b/mobile/test/service.mocks.dart @@ -1,63 +1,31 @@ -import 'package:immich_mobile/domain/services/asset.service.dart'; -import 'package:immich_mobile/domain/services/background_worker.service.dart'; -import 'package:immich_mobile/domain/services/partner.service.dart'; -import 'package:immich_mobile/domain/services/remote_album.service.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/domain/services/user.service.dart'; -import 'package:immich_mobile/domain/utils/background_sync.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; +import 'package:immich_mobile/services/album.service.dart'; import 'package:immich_mobile/services/api.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/services/auth.service.dart'; -import 'package:immich_mobile/services/background_upload.service.dart'; -import 'package:immich_mobile/services/cleanup.service.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_mobile/services/gcast.service.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/entity.service.dart'; +import 'package:immich_mobile/services/hash.service.dart'; import 'package:immich_mobile/services/network.service.dart'; -import 'package:immich_mobile/services/secure_storage.service.dart'; -import 'package:immich_mobile/services/server_info.service.dart'; -import 'package:immich_mobile/services/toast.service.dart'; -import 'package:immich_mobile/services/widget.service.dart'; +import 'package:immich_mobile/services/sync.service.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:openapi/api.dart'; class MockApiService extends Mock implements ApiService {} +class MockAlbumService extends Mock implements AlbumService {} + +class MockBackupService extends Mock implements BackupService {} + +class MockSyncService extends Mock implements SyncService {} + +class MockHashService extends Mock implements HashService {} + +class MockEntityService extends Mock implements EntityService {} + class MockNetworkService extends Mock implements NetworkService {} +class MockSearchApi extends Mock implements SearchApi {} + class MockAppSettingService extends Mock implements AppSettingsService {} -class MockStoreService extends Mock implements StoreService {} - -class MockNativeSyncApi extends Mock implements NativeSyncApi {} - -class MockAppSettingsService extends Mock implements AppSettingsService {} - -class MockPartnerService extends Mock implements PartnerService {} - -class MockAssetService extends Mock implements AssetService {} - -class MockUserService extends Mock implements UserService {} - -class MockRemoteAlbumService extends Mock implements RemoteAlbumService {} - -class MockGCastService extends Mock implements GCastService {} - -class MockForegroundUploadService extends Mock implements ForegroundUploadService {} - -class MockServerInfoService extends Mock implements ServerInfoService {} - -class MockCleanupService extends Mock implements CleanupService {} - -class MockBackgroundSyncManager extends Mock implements BackgroundSyncManager {} - -class MockToastService extends Mock implements ToastService {} - -class MockAuthService extends Mock implements AuthService {} - -class MockSecureStorageService extends Mock implements SecureStorageService {} - -class MockWidgetService extends Mock implements WidgetService {} - -class MockBackgroundUploadService extends Mock implements BackgroundUploadService {} - -class MockBackgroundWorkerLockService extends Mock implements BackgroundWorkerLockService {} +class MockBackgroundService extends Mock implements BackgroundService {} diff --git a/mobile/test/services/action.service_test.dart b/mobile/test/services/action.service_test.dart index b008ff9bfc..87263c9ae7 100644 --- a/mobile/test/services/action.service_test.dart +++ b/mobile/test/services/action.service_test.dart @@ -2,21 +2,31 @@ import 'package:drift/drift.dart' as drift; import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/repositories/download.repository.dart'; import 'package:immich_mobile/services/action.service.dart'; import 'package:mocktail/mocktail.dart'; import '../infrastructure/repository.mock.dart'; import '../repository.mocks.dart'; +class MockDownloadRepository extends Mock implements DownloadRepository {} + void main() { late ActionService sut; late MockAssetApiRepository assetApiRepository; late MockRemoteAssetRepository remoteAssetRepository; + late MockDriftLocalAssetRepository localAssetRepository; + late MockDriftAlbumApiRepository albumApiRepository; + late MockRemoteAlbumRepository remoteAlbumRepository; + late MockTrashedLocalAssetRepository trashedLocalAssetRepository; + late MockAssetMediaRepository assetMediaRepository; + late MockDownloadRepository downloadRepository; late Drift db; @@ -25,7 +35,7 @@ void main() { debugDefaultTargetPlatformOverride = TargetPlatform.android; db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); + await StoreService.init(storeRepository: DriftStoreRepository(db)); }); tearDownAll(() async { @@ -37,37 +47,72 @@ void main() { setUp(() { assetApiRepository = MockAssetApiRepository(); remoteAssetRepository = MockRemoteAssetRepository(); + localAssetRepository = MockDriftLocalAssetRepository(); + albumApiRepository = MockDriftAlbumApiRepository(); + remoteAlbumRepository = MockRemoteAlbumRepository(); + trashedLocalAssetRepository = MockTrashedLocalAssetRepository(); + assetMediaRepository = MockAssetMediaRepository(); + downloadRepository = MockDownloadRepository(); - sut = ActionService(assetApiRepository, remoteAssetRepository); + sut = ActionService( + assetApiRepository, + remoteAssetRepository, + localAssetRepository, + albumApiRepository, + remoteAlbumRepository, + trashedLocalAssetRepository, + assetMediaRepository, + downloadRepository, + ); }); tearDown(() async { await Store.clear(); }); - group('ActionService.updateRating', () { - const assetId = 'asset_id_1'; + group('ActionService.deleteLocal', () { + test('routes deleted ids to trashed repository when Android trash handling is enabled', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, true); + const ids = ['a', 'b']; - test('calls both repositories with the given rating', () async { - when(() => assetApiRepository.updateRating(assetId, 3)).thenAnswer((_) async {}); - when(() => remoteAssetRepository.updateRating(assetId, 3)).thenAnswer((_) async {}); + when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids); + when(() => trashedLocalAssetRepository.applyTrashedAssets(ids)).thenAnswer((_) async {}); - final result = await sut.updateRating(assetId, 3); + final result = await sut.deleteLocal(ids); - expect(result, isTrue); - verify(() => assetApiRepository.updateRating(assetId, 3)).called(1); - verify(() => remoteAssetRepository.updateRating(assetId, 3)).called(1); + expect(result, ids.length); + verify(() => assetMediaRepository.deleteAll(ids)).called(1); + verify(() => trashedLocalAssetRepository.applyTrashedAssets(ids)).called(1); + verifyNever(() => localAssetRepository.delete(any())); }); - test('calls both repositories with null to clear rating', () async { - when(() => assetApiRepository.updateRating(assetId, null)).thenAnswer((_) async {}); - when(() => remoteAssetRepository.updateRating(assetId, null)).thenAnswer((_) async {}); + test('deletes locally when Android trash handling is disabled', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, false); + const ids = ['c']; - final result = await sut.updateRating(assetId, null); + when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => ids); + when(() => localAssetRepository.delete(ids)).thenAnswer((_) async {}); - expect(result, isTrue); - verify(() => assetApiRepository.updateRating(assetId, null)).called(1); - verify(() => remoteAssetRepository.updateRating(assetId, null)).called(1); + final result = await sut.deleteLocal(ids); + + expect(result, ids.length); + verify(() => assetMediaRepository.deleteAll(ids)).called(1); + verify(() => localAssetRepository.delete(ids)).called(1); + verifyNever(() => trashedLocalAssetRepository.applyTrashedAssets(any())); + }); + + test('short-circuits when nothing was deleted', () async { + await Store.put(StoreKey.manageLocalMediaAndroid, true); + const ids = ['x']; + + when(() => assetMediaRepository.deleteAll(ids)).thenAnswer((_) async => []); + + final result = await sut.deleteLocal(ids); + + expect(result, 0); + verify(() => assetMediaRepository.deleteAll(ids)).called(1); + verifyNever(() => trashedLocalAssetRepository.applyTrashedAssets(any())); + verifyNever(() => localAssetRepository.delete(any())); }); }); } diff --git a/mobile/test/services/album.service_test.dart b/mobile/test/services/album.service_test.dart new file mode 100644 index 0000000000..97683cdab1 --- /dev/null +++ b/mobile/test/services/album.service_test.dart @@ -0,0 +1,177 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/services/album.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../domain/service.mock.dart'; +import '../fixtures/album.stub.dart'; +import '../fixtures/asset.stub.dart'; +import '../fixtures/user.stub.dart'; +import '../repository.mocks.dart'; +import '../service.mocks.dart'; + +void main() { + late AlbumService sut; + late MockUserService userService; + late MockSyncService syncService; + late MockEntityService entityService; + late MockAlbumRepository albumRepository; + late MockAssetRepository assetRepository; + late MockBackupRepository backupRepository; + late MockAlbumMediaRepository albumMediaRepository; + late MockAlbumApiRepository albumApiRepository; + + setUp(() { + userService = MockUserService(); + syncService = MockSyncService(); + entityService = MockEntityService(); + albumRepository = MockAlbumRepository(); + assetRepository = MockAssetRepository(); + backupRepository = MockBackupRepository(); + albumMediaRepository = MockAlbumMediaRepository(); + albumApiRepository = MockAlbumApiRepository(); + + when(() => userService.getMyUser()).thenReturn(UserStub.user1); + + when( + () => albumRepository.transaction(any()), + ).thenAnswer((call) => (call.positionalArguments.first as Function).call()); + when( + () => assetRepository.transaction(any()), + ).thenAnswer((call) => (call.positionalArguments.first as Function).call()); + + sut = AlbumService( + syncService, + userService, + entityService, + albumRepository, + assetRepository, + backupRepository, + albumMediaRepository, + albumApiRepository, + ); + }); + + group('refreshDeviceAlbums', () { + test('empty selection with one album in db', () async { + when(() => backupRepository.getIdsBySelection(BackupSelection.exclude)).thenAnswer((_) async => []); + when(() => backupRepository.getIdsBySelection(BackupSelection.select)).thenAnswer((_) async => []); + when(() => albumMediaRepository.getAll()).thenAnswer((_) async => []); + when(() => albumRepository.count(local: true)).thenAnswer((_) async => 1); + when(() => syncService.removeAllLocalAlbumsAndAssets()).thenAnswer((_) async => true); + final result = await sut.refreshDeviceAlbums(); + expect(result, false); + verify(() => syncService.removeAllLocalAlbumsAndAssets()); + }); + + test('one selected albums, two on device', () async { + when(() => backupRepository.getIdsBySelection(BackupSelection.exclude)).thenAnswer((_) async => []); + when( + () => backupRepository.getIdsBySelection(BackupSelection.select), + ).thenAnswer((_) async => [AlbumStub.oneAsset.localId!]); + when(() => albumMediaRepository.getAll()).thenAnswer((_) async => [AlbumStub.oneAsset, AlbumStub.twoAsset]); + when(() => syncService.syncLocalAlbumAssetsToDb(any(), any())).thenAnswer((_) async => true); + final result = await sut.refreshDeviceAlbums(); + expect(result, true); + verify(() => syncService.syncLocalAlbumAssetsToDb([AlbumStub.oneAsset], null)).called(1); + verifyNoMoreInteractions(syncService); + }); + }); + + group('refreshRemoteAlbums', () { + test('is working', () async { + when(() => syncService.getUsersFromServer()).thenAnswer((_) async => []); + when(() => syncService.syncUsersFromServer(any())).thenAnswer((_) async => true); + when(() => albumApiRepository.getAll(shared: true)).thenAnswer((_) async => [AlbumStub.sharedWithUser]); + + when( + () => albumApiRepository.getAll(shared: null), + ).thenAnswer((_) async => [AlbumStub.oneAsset, AlbumStub.twoAsset]); + + when( + () => syncService.syncRemoteAlbumsToDb([AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]), + ).thenAnswer((_) async => true); + final result = await sut.refreshRemoteAlbums(); + expect(result, true); + verify(() => syncService.getUsersFromServer()).called(1); + verify(() => syncService.syncUsersFromServer([])).called(1); + verify(() => albumApiRepository.getAll(shared: true)).called(1); + verify(() => albumApiRepository.getAll(shared: null)).called(1); + verify( + () => syncService.syncRemoteAlbumsToDb([AlbumStub.twoAsset, AlbumStub.oneAsset, AlbumStub.sharedWithUser]), + ).called(1); + verifyNoMoreInteractions(userService); + verifyNoMoreInteractions(albumApiRepository); + verifyNoMoreInteractions(syncService); + }); + }); + + group('createAlbum', () { + test('shared with assets', () async { + when( + () => albumApiRepository.create( + "name", + assetIds: any(named: "assetIds"), + sharedUserIds: any(named: "sharedUserIds"), + ), + ).thenAnswer((_) async => AlbumStub.oneAsset); + + when( + () => entityService.fillAlbumWithDatabaseEntities(AlbumStub.oneAsset), + ).thenAnswer((_) async => AlbumStub.oneAsset); + + when(() => albumRepository.create(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.twoAsset); + + final result = await sut.createAlbum("name", [AssetStub.image1], [UserStub.user1]); + expect(result, AlbumStub.twoAsset); + verify( + () => albumApiRepository.create( + "name", + assetIds: [AssetStub.image1.remoteId!], + sharedUserIds: [UserStub.user1.id], + ), + ).called(1); + verify(() => entityService.fillAlbumWithDatabaseEntities(AlbumStub.oneAsset)).called(1); + }); + }); + + group('addAdditionalAssetToAlbum', () { + test('one added, one duplicate', () async { + when( + () => albumApiRepository.addAssets(AlbumStub.oneAsset.remoteId!, any()), + ).thenAnswer((_) async => (added: [AssetStub.image2.remoteId!], duplicates: [AssetStub.image1.remoteId!])); + when(() => albumRepository.get(AlbumStub.oneAsset.id)).thenAnswer((_) async => AlbumStub.oneAsset); + when(() => albumRepository.addAssets(AlbumStub.oneAsset, [AssetStub.image2])).thenAnswer((_) async {}); + when(() => albumRepository.removeAssets(AlbumStub.oneAsset, [])).thenAnswer((_) async {}); + when(() => albumRepository.recalculateMetadata(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.oneAsset); + when(() => albumRepository.update(AlbumStub.oneAsset)).thenAnswer((_) async => AlbumStub.oneAsset); + + final result = await sut.addAssets(AlbumStub.oneAsset, [AssetStub.image1, AssetStub.image2]); + + expect(result != null, true); + expect(result!.alreadyInAlbum, [AssetStub.image1.remoteId!]); + expect(result.successfullyAdded, 1); + }); + }); + + group('addAdditionalUserToAlbum', () { + test('one added', () async { + when( + () => albumApiRepository.addUsers(AlbumStub.emptyAlbum.remoteId!, any()), + ).thenAnswer((_) async => AlbumStub.sharedWithUser); + + when( + () => albumRepository.addUsers( + AlbumStub.emptyAlbum, + AlbumStub.emptyAlbum.sharedUsers.map((u) => u.toDto()).toList(), + ), + ).thenAnswer((_) async => AlbumStub.emptyAlbum); + + when(() => albumRepository.update(AlbumStub.emptyAlbum)).thenAnswer((_) async => AlbumStub.emptyAlbum); + + final result = await sut.addUsers(AlbumStub.emptyAlbum, [UserStub.user2.id]); + + expect(result, true); + }); + }); +} diff --git a/mobile/test/services/asset.service_test.dart b/mobile/test/services/asset.service_test.dart new file mode 100644 index 0000000000..b741150165 --- /dev/null +++ b/mobile/test/services/asset.service_test.dart @@ -0,0 +1,103 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/services/asset.service.dart'; +import 'package:maplibre_gl/maplibre_gl.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:openapi/api.dart'; + +import '../api.mocks.dart'; +import '../domain/service.mock.dart'; +import '../fixtures/asset.stub.dart'; +import '../infrastructure/repository.mock.dart'; +import '../repository.mocks.dart'; +import '../service.mocks.dart'; + +class FakeAssetBulkUpdateDto extends Fake implements AssetBulkUpdateDto {} + +void main() { + late AssetService sut; + + late MockAssetRepository assetRepository; + late MockAssetApiRepository assetApiRepository; + late MockExifInfoRepository exifInfoRepository; + late MockETagRepository eTagRepository; + late MockBackupAlbumRepository backupAlbumRepository; + late MockIsarUserRepository userRepository; + late MockAssetMediaRepository assetMediaRepository; + late MockApiService apiService; + + late MockSyncService syncService; + late MockAlbumService albumService; + late MockBackupService backupService; + late MockUserService userService; + + setUp(() { + assetRepository = MockAssetRepository(); + assetApiRepository = MockAssetApiRepository(); + exifInfoRepository = MockExifInfoRepository(); + userRepository = MockIsarUserRepository(); + eTagRepository = MockETagRepository(); + backupAlbumRepository = MockBackupAlbumRepository(); + apiService = MockApiService(); + assetMediaRepository = MockAssetMediaRepository(); + + syncService = MockSyncService(); + userService = MockUserService(); + albumService = MockAlbumService(); + backupService = MockBackupService(); + + sut = AssetService( + assetApiRepository, + assetRepository, + exifInfoRepository, + userRepository, + eTagRepository, + backupAlbumRepository, + apiService, + syncService, + backupService, + albumService, + userService, + assetMediaRepository, + ); + + registerFallbackValue(FakeAssetBulkUpdateDto()); + }); + + group("Edit ExifInfo", () { + late AssetsApi assetsApi; + setUp(() { + assetsApi = MockAssetsApi(); + when(() => apiService.assetsApi).thenReturn(assetsApi); + when(() => assetsApi.updateAssets(any())).thenAnswer((_) async => Future.value()); + }); + + test("asset is updated with DateTime", () async { + final assets = [AssetStub.image1, AssetStub.image2]; + final dateTime = DateTime.utc(2025, 6, 4, 2, 57); + await sut.changeDateTime(assets, dateTime.toIso8601String()); + + verify(() => assetsApi.updateAssets(any())).called(1); + final upsertExifCallback = verify(() => syncService.upsertAssetsWithExif(captureAny())); + upsertExifCallback.called(1); + final receivedAssets = upsertExifCallback.captured.firstOrNull as List? ?? []; + final receivedDatetime = receivedAssets.cast().map((a) => a.exifInfo?.dateTimeOriginal ?? DateTime(0)); + expect(receivedDatetime.every((d) => d == dateTime), isTrue); + }); + + test("asset is updated with LatLng", () async { + final assets = [AssetStub.image1, AssetStub.image2]; + final latLng = const LatLng(37.7749, -122.4194); + await sut.changeLocation(assets, latLng); + + verify(() => assetsApi.updateAssets(any())).called(1); + final upsertExifCallback = verify(() => syncService.upsertAssetsWithExif(captureAny())); + upsertExifCallback.called(1); + final receivedAssets = upsertExifCallback.captured.firstOrNull as List? ?? []; + final receivedCoords = receivedAssets.cast().map( + (a) => LatLng(a.exifInfo?.latitude ?? 0, a.exifInfo?.longitude ?? 0), + ); + expect(receivedCoords.every((l) => l == latLng), isTrue); + }); + }); +} diff --git a/mobile/test/services/auth.service_test.dart b/mobile/test/services/auth.service_test.dart index 6d79993aa8..7c7de3cd0e 100644 --- a/mobile/test/services/auth.service_test.dart +++ b/mobile/test/services/auth.service_test.dart @@ -1,17 +1,18 @@ -import 'package:drift/drift.dart' hide isNull; -import 'package:drift/native.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/auth.service.dart'; +import 'package:isar/isar.dart'; import 'package:mocktail/mocktail.dart'; import 'package:openapi/api.dart'; +import '../domain/service.mock.dart'; import '../repository.mocks.dart'; import '../service.mocks.dart'; +import '../test_utils.dart'; void main() { late AuthService sut; @@ -20,7 +21,8 @@ void main() { late MockApiService apiService; late MockNetworkService networkService; late MockBackgroundSyncManager backgroundSyncManager; - late Drift db; + late MockAppSettingService appSettingsService; + late Isar db; setUp(() async { authApiRepository = MockAuthApiRepository(); @@ -28,22 +30,34 @@ void main() { apiService = MockApiService(); networkService = MockNetworkService(); backgroundSyncManager = MockBackgroundSyncManager(); - sut = AuthService(authApiRepository, authRepository, apiService, networkService, backgroundSyncManager); + appSettingsService = MockAppSettingService(); + + sut = AuthService( + authApiRepository, + authRepository, + apiService, + networkService, + backgroundSyncManager, + appSettingsService, + ); registerFallbackValue(Uri()); }); setUpAll(() async { - WidgetsFlutterBinding.ensureInitialized(); - db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); - }); - - tearDownAll(() async { - await db.close(); + db = await TestUtils.initIsar(); + db.writeTxnSync(() => db.clearSync()); + await StoreService.init(storeRepository: IsarStoreRepository(db)); }); group('validateServerUrl', () { + setUpAll(() async { + WidgetsFlutterBinding.ensureInitialized(); + final db = await TestUtils.initIsar(); + db.writeTxnSync(() => db.clearSync()); + await StoreService.init(storeRepository: IsarStoreRepository(db)); + }); + test('Should resolve HTTP endpoint', () async { const testUrl = 'http://ip:2283'; const resolvedUrl = 'http://ip:2283/api'; @@ -97,13 +111,43 @@ void main() { }); }); + group('logout', () { + test('Should logout user', () async { + when(() => authApiRepository.logout()).thenAnswer((_) async => {}); + when(() => backgroundSyncManager.cancel()).thenAnswer((_) async => {}); + when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null)); + when( + () => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false), + ).thenAnswer((_) => Future.value(null)); + await sut.logout(); + + verify(() => authApiRepository.logout()).called(1); + verify(() => backgroundSyncManager.cancel()).called(1); + verify(() => authRepository.clearLocalData()).called(1); + }); + + test('Should clear local data even on server error', () async { + when(() => authApiRepository.logout()).thenThrow(Exception('Server error')); + when(() => backgroundSyncManager.cancel()).thenAnswer((_) async => {}); + when(() => authRepository.clearLocalData()).thenAnswer((_) => Future.value(null)); + when( + () => appSettingsService.setSetting(AppSettingsEnum.enableBackup, false), + ).thenAnswer((_) => Future.value(null)); + await sut.logout(); + + verify(() => authApiRepository.logout()).called(1); + verify(() => backgroundSyncManager.cancel()).called(1); + verify(() => authRepository.clearLocalData()).called(1); + }); + }); + group('setOpenApiServiceEndpoint', () { setUp(() { when(() => networkService.getWifiName()).thenAnswer((_) async => 'TestWifi'); }); test('Should return null if auto endpoint switching is disabled', () async { - when(() => authRepository.getEndpointSwitchingFeature()).thenReturn(false); + when(() => authRepository.getEndpointSwitchingFeature()).thenReturn((false)); final result = await sut.setOpenApiServiceEndpoint(); diff --git a/mobile/test/services/background_upload.service_test.dart b/mobile/test/services/background_upload.service_test.dart index 2d9b5b1209..585ffcb499 100644 --- a/mobile/test/services/background_upload.service_test.dart +++ b/mobile/test/services/background_upload.service_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:drift/drift.dart' hide isNotNull, isNull; +import 'package:drift/drift.dart' hide isNull, isNotNull; import 'package:drift/native.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -11,11 +11,12 @@ import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/services/store.service.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/background_upload.service.dart'; import 'package:mocktail/mocktail.dart'; +import '../domain/service.mock.dart'; import '../fixtures/asset.stub.dart'; import '../infrastructure/repository.mock.dart'; import '../mocks/asset_entity.mock.dart'; @@ -25,20 +26,22 @@ void main() { late BackgroundUploadService sut; late MockUploadRepository mockUploadRepository; late MockStorageRepository mockStorageRepository; - late MockLocalAssetRepository mockLocalAssetRepository; - late MockBackupRepository mockBackupRepository; + late MockDriftLocalAssetRepository mockLocalAssetRepository; + late MockDriftBackupRepository mockBackupRepository; + late MockAppSettingsService mockAppSettingsService; late MockAssetMediaRepository mockAssetMediaRepository; late Drift db; setUpAll(() async { + registerFallbackValue(AppSettingsEnum.useCellularForUploadPhotos); + TestWidgetsFlutterBinding.ensureInitialized(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( const MethodChannel('plugins.flutter.io/path_provider'), (MethodCall methodCall) async => 'test', ); db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); - await SettingsRepository.ensureInitialized(db); + await StoreService.init(storeRepository: DriftStoreRepository(db)); await Store.put(StoreKey.serverEndpoint, 'http://test-server.com'); await Store.put(StoreKey.deviceId, 'test-device-id'); @@ -47,15 +50,20 @@ void main() { setUp(() { mockUploadRepository = MockUploadRepository(); mockStorageRepository = MockStorageRepository(); - mockLocalAssetRepository = MockLocalAssetRepository(); - mockBackupRepository = MockBackupRepository(); + mockLocalAssetRepository = MockDriftLocalAssetRepository(); + mockBackupRepository = MockDriftBackupRepository(); + mockAppSettingsService = MockAppSettingsService(); mockAssetMediaRepository = MockAssetMediaRepository(); + when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)).thenReturn(false); + when(() => mockAppSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)).thenReturn(false); + sut = BackgroundUploadService( mockUploadRepository, mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, + mockAppSettingsService, mockAssetMediaRepository, ); @@ -118,69 +126,8 @@ void main() { expect(task, isNotNull); // For live photos, extension should be changed to match the video file expect(task!.fields['filename'], equals('OriginalLivePhoto.mov')); - expect(task.fields['visibility'], equals('hidden')); verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); }); - - test('should not set visibility for a regular photo', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final mockFile = File('/path/to/file.jpg'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'Regular.jpg'); - - final task = await sut.getUploadTask(asset); - expect(task, isNotNull); - expect(task!.fields.containsKey('visibility'), isFalse); - }); - - test('corrects the extension when iOS returns a rendered file for a .dng asset', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final mockFile = File('/path/to/IMG_6499.jpg'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'IMG_6499.dng'); - - final task = await sut.getUploadTask(asset); - expect(task, isNotNull); - expect(task!.fields['filename'], equals('IMG_6499.jpg')); - }); - - test('keeps the .dng extension for a genuine RAW original', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final mockFile = File('/path/to/IMG_5210.dng'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'IMG_5210.dng'); - - final task = await sut.getUploadTask(asset); - expect(task, isNotNull); - expect(task!.fields['filename'], equals('IMG_5210.dng')); - }); - - test('borrows the extension from the asset name for an extensionless name (DJI/Fusion)', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final mockFile = File('/path/to/DJI_0001'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => mockFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'DJI_0001'); - - final task = await sut.getUploadTask(asset); - expect(task, isNotNull); - expect(task!.fields['filename'], equals('DJI_0001.jpg')); - }); }); group('getLivePhotoUploadTask', () { @@ -201,7 +148,6 @@ void main() { expect(task, isNotNull); expect(task!.fields['filename'], equals('OriginalLivePhoto.HEIC')); expect(task.fields['livePhotoVideoId'], equals('video-id-123')); - expect(task.fields.containsKey('visibility'), isFalse); verify(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).called(1); }); @@ -233,6 +179,7 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, + mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -283,6 +230,7 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, + mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutAndroid.dispose()); @@ -323,6 +271,7 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, + mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -363,6 +312,7 @@ void main() { mockStorageRepository, mockLocalAssetRepository, mockBackupRepository, + mockAppSettingsService, mockAssetMediaRepository, ); addTearDown(() => sutWithV24.dispose()); @@ -395,7 +345,6 @@ void main() { expect(task, isNotNull); expect(task!.fields.containsKey('metadata'), isTrue); expect(task.fields['livePhotoVideoId'], equals('video-123')); - expect(task.fields.containsKey('visibility'), isFalse); final metadata = jsonDecode(task.fields['metadata']!) as List; expect(metadata, hasLength(1)); diff --git a/mobile/test/services/cleanup.service_test.dart b/mobile/test/services/cleanup.service_test.dart index 294b8550ea..2038941ecb 100644 --- a/mobile/test/services/cleanup.service_test.dart +++ b/mobile/test/services/cleanup.service_test.dart @@ -9,11 +9,11 @@ import '../repository.mocks.dart'; void main() { late CleanupService sut; - late MockLocalAssetRepository localAssetRepository; + late MockDriftLocalAssetRepository localAssetRepository; late MockAssetMediaRepository assetMediaRepository; setUp(() { - localAssetRepository = MockLocalAssetRepository(); + localAssetRepository = MockDriftLocalAssetRepository(); assetMediaRepository = MockAssetMediaRepository(); sut = CleanupService(localAssetRepository, assetMediaRepository); }); @@ -24,7 +24,7 @@ void main() { expect(result, 0); verifyNever(() => assetMediaRepository.deleteAll(any())); - verifyNever(() => localAssetRepository.deleteAssets(any())); + verifyNever(() => localAssetRepository.delete(any())); }); test('deletes in a single batch when under limit', () async { @@ -33,13 +33,13 @@ void main() { when(() => assetMediaRepository.deleteAll(any())).thenAnswer((invocation) async { return (invocation.positionalArguments.first as List).toList(); }); - when(() => localAssetRepository.deleteAssets(any())).thenAnswer((_) async {}); + when(() => localAssetRepository.delete(any())).thenAnswer((_) async {}); final result = await sut.deleteLocalAssets(ids); expect(result, ids.length); verify(() => assetMediaRepository.deleteAll(ids)).called(1); - verify(() => localAssetRepository.deleteAssets(ids)).called(1); + verify(() => localAssetRepository.delete(ids)).called(1); }); test('deletes in platform-specific batches when over limit', () async { @@ -52,7 +52,7 @@ void main() { capturedBatches.add(batch); return batch; }); - when(() => localAssetRepository.deleteAssets(any())).thenAnswer((_) async {}); + when(() => localAssetRepository.delete(any())).thenAnswer((_) async {}); final result = await sut.deleteLocalAssets(ids); @@ -67,7 +67,7 @@ void main() { expect(capturedBatches[1].last, 'asset-${batchSize * 2 - 1}'); expect(capturedBatches[2].first, 'asset-${batchSize * 2}'); expect(capturedBatches[2].last, 'asset-${batchSize * 2 + 500}'); - verify(() => localAssetRepository.deleteAssets(any())).called(3); + verify(() => localAssetRepository.delete(any())).called(3); }); }); } diff --git a/mobile/test/services/deep_link_service_test.dart b/mobile/test/services/deep_link_service_test.dart deleted file mode 100644 index c588cf26b4..0000000000 --- a/mobile/test/services/deep_link_service_test.dart +++ /dev/null @@ -1,191 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/memory.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/asset.service.dart'; -import 'package:immich_mobile/domain/services/memory.service.dart'; -import 'package:immich_mobile/domain/services/people.service.dart'; -import 'package:immich_mobile/domain/services/remote_album.service.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/services/deep_link.service.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockTimelineFactory extends Mock implements TimelineFactory {} - -class MockAssetService extends Mock implements AssetService {} - -class MockRemoteAlbumService extends Mock implements RemoteAlbumService {} - -class MockDriftMemoryService extends Mock implements DriftMemoryService {} - -class MockDriftPeopleService extends Mock implements DriftPeopleService {} - -class MockPlatformDeepLink extends Mock implements PlatformDeepLink {} - -class MockWidgetRef extends Mock implements WidgetRef {} - -class MockAssetViewerStateNotifier extends Mock implements AssetViewerStateNotifier {} - -const _assetId = 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb'; -const _albumId = 'cccccccc-4444-5555-6666-dddddddddddd'; -const _memoryId = 'mmmmmmmm-7777-8888-9999-yyyyyyyyyyyy'; -const _userId = 'user-1'; - -final _asset = RemoteAsset( - id: _assetId, - name: 'photo.jpg', - ownerId: _userId, - checksum: 'checksum-1', - type: AssetType.image, - createdAt: DateTime(2026, 6, 12), - updatedAt: DateTime(2026, 6, 12), - isEdited: false, -); - -final _album = RemoteAlbum( - id: _albumId, - name: 'Shared Album', - ownerId: _userId, - description: '', - createdAt: DateTime(2026, 6, 12), - updatedAt: DateTime(2026, 6, 12), - isActivityEnabled: true, - isShared: true, - order: AlbumAssetOrder.asc, - assetCount: 1, - ownerName: 'Owner', -); - -final _memory = DriftMemory( - id: _memoryId, - createdAt: DateTime(2026, 6, 12), - updatedAt: DateTime(2026, 6, 12), - ownerId: _userId, - type: MemoryTypeEnum.onThisDay, - data: const MemoryData(year: 2025), - isSaved: false, - memoryAt: DateTime(2025, 6, 12), - assets: [_asset], -); - -final _user = UserDto( - id: _userId, - email: 'test@immich.app', - name: 'Test User', - profileChangedAt: DateTime(2026, 6, 12), -); - -void main() { - late MockTimelineFactory timelineFactory; - late MockAssetService assetService; - late MockRemoteAlbumService remoteAlbumService; - late MockDriftMemoryService memoryService; - late MockWidgetRef ref; - late List createdTimelineServices; - late DeepLinkService sut; - - setUp(() { - timelineFactory = MockTimelineFactory(); - assetService = MockAssetService(); - remoteAlbumService = MockRemoteAlbumService(); - memoryService = MockDriftMemoryService(); - ref = MockWidgetRef(); - createdTimelineServices = []; - - when(() => timelineFactory.fromAssets(any(), TimelineOrigin.deepLink)).thenAnswer((invocation) { - final assets = List.from(invocation.positionalArguments[0] as List); - final timelineService = TimelineService(( - assetSource: (index, count) async => assets.skip(index).take(count).toList(), - bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]), - origin: TimelineOrigin.deepLink, - )); - createdTimelineServices.add(timelineService); - return timelineService; - }); - - when(() => ref.read(assetViewerProvider.notifier)).thenReturn(MockAssetViewerStateNotifier()); - - sut = DeepLinkService( - timelineFactory, - assetService, - remoteAlbumService, - memoryService, - MockDriftPeopleService(), - _user, - ); - - addTearDown(() async { - for (final timelineService in createdTimelineServices) { - await timelineService.dispose(); - } - }); - }); - - PlatformDeepLink link(String path) { - final deepLink = MockPlatformDeepLink(); - when(() => deepLink.uri).thenReturn(Uri.parse('https://my.immich.app$path')); - return deepLink; - } - - test('album photo link carries the album into the viewer route', () async { - when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset); - when(() => remoteAlbumService.get(_albumId)).thenAnswer((_) async => _album); - - final route = await sut.handleMyImmichApp(link('/albums/$_albumId/photos/$_assetId'), ref); - - expect(route, isA()); - expect((route!.args! as AssetViewerRouteArgs).currentAlbum, _album); - }); - - test('still opens the viewer when the album cannot be resolved', () async { - when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset); - when(() => remoteAlbumService.get(_albumId)).thenAnswer((_) async => null); - - final route = await sut.handleMyImmichApp(link('/albums/$_albumId/photos/$_assetId'), ref); - - expect(route, isA()); - expect((route!.args! as AssetViewerRouteArgs).currentAlbum, isNull); - }); - - test('plain photo link has no album', () async { - when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset); - - final route = await sut.handleMyImmichApp(link('/photos/$_assetId'), ref); - - expect(route, isA()); - expect((route!.args! as AssetViewerRouteArgs).currentAlbum, isNull); - verifyNever(() => remoteAlbumService.get(any())); - }); - - test('memory scheme link without an id opens the memory lane', () async { - when(() => memoryService.getMemoryLane(_userId)).thenAnswer((_) async => [_memory]); - final deepLink = MockPlatformDeepLink(); - when(() => deepLink.uri).thenReturn(Uri.parse('immich://memory')); - - final route = await sut.handleScheme(deepLink, ref); - - expect(route, isA()); - expect((route!.args! as DriftMemoryRouteArgs).memories, [_memory]); - verify(() => memoryService.getMemoryLane(_userId)).called(1); - verifyNever(() => memoryService.get(any())); - }); - - test('memory scheme link with an id opens only the specified memory', () async { - when(() => memoryService.get(_memoryId)).thenAnswer((_) async => _memory); - final deepLink = MockPlatformDeepLink(); - when(() => deepLink.uri).thenReturn(Uri.parse('immich://memory?id=$_memoryId')); - - final route = await sut.handleScheme(deepLink, ref); - - expect(route, isA()); - expect((route!.args! as DriftMemoryRouteArgs).memories, [_memory]); - verifyNever(() => memoryService.getMemoryLane(any())); - verify(() => memoryService.get(_memoryId)).called(1); - }); -} diff --git a/mobile/test/services/entity.service_test.dart b/mobile/test/services/entity.service_test.dart new file mode 100644 index 0000000000..64b9fc604b --- /dev/null +++ b/mobile/test/services/entity.service_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:immich_mobile/services/entity.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../fixtures/asset.stub.dart'; +import '../fixtures/user.stub.dart'; +import '../infrastructure/repository.mock.dart'; +import '../repository.mocks.dart'; + +void main() { + late EntityService sut; + late MockAssetRepository assetRepository; + late MockIsarUserRepository userRepository; + + setUp(() { + assetRepository = MockAssetRepository(); + userRepository = MockIsarUserRepository(); + sut = EntityService(assetRepository, userRepository); + }); + + group('fillAlbumWithDatabaseEntities', () { + test('remote album with owner, thumbnail, sharedUsers and assets', () async { + final Album album = + Album( + name: "album-with-two-assets-and-two-users", + localId: "album-with-two-assets-and-two-users-local", + remoteId: "album-with-two-assets-and-two-users-remote", + createdAt: DateTime(2001), + modifiedAt: DateTime(2010), + shared: true, + activityEnabled: true, + startDate: DateTime(2019), + endDate: DateTime(2020), + ) + ..remoteThumbnailAssetId = AssetStub.image1.remoteId + ..assets.addAll([AssetStub.image1, AssetStub.image1]) + ..owner.value = User.fromDto(UserStub.user1) + ..sharedUsers.addAll([User.fromDto(UserStub.admin), User.fromDto(UserStub.admin)]); + + when(() => userRepository.getByUserId(any())).thenAnswer((_) async => UserStub.admin); + when(() => userRepository.getByUserId(any())).thenAnswer((_) async => UserStub.admin); + + when(() => assetRepository.getByRemoteId(AssetStub.image1.remoteId!)).thenAnswer((_) async => AssetStub.image1); + + when(() => userRepository.getByUserIds(any())).thenAnswer((_) async => [UserStub.user1, UserStub.user2]); + + when(() => assetRepository.getAllByRemoteId(any())).thenAnswer((_) async => [AssetStub.image1, AssetStub.image2]); + + await sut.fillAlbumWithDatabaseEntities(album); + expect(album.owner.value?.toDto(), UserStub.admin); + expect(album.thumbnail.value, AssetStub.image1); + expect(album.remoteUsers.map((u) => u.toDto()).toSet(), {UserStub.user1, UserStub.user2}); + expect(album.remoteAssets.toSet(), {AssetStub.image1, AssetStub.image2}); + }); + + test('remote album without any info', () async { + makeEmptyAlbum() => Album( + name: "album-without-info", + localId: "album-without-info-local", + remoteId: "album-without-info-remote", + createdAt: DateTime(2001), + modifiedAt: DateTime(2010), + shared: false, + activityEnabled: false, + ); + + final album = makeEmptyAlbum(); + await sut.fillAlbumWithDatabaseEntities(album); + verifyNoMoreInteractions(assetRepository); + verifyNoMoreInteractions(userRepository); + expect(album, makeEmptyAlbum()); + }); + }); +} diff --git a/mobile/test/services/foreground_upload.service_test.dart b/mobile/test/services/foreground_upload.service_test.dart deleted file mode 100644 index 4f0a01dfd0..0000000000 --- a/mobile/test/services/foreground_upload.service_test.dart +++ /dev/null @@ -1,200 +0,0 @@ -import 'dart:io'; - -import 'package:drift/drift.dart' hide isNotNull, isNull; -import 'package:drift/native.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/repositories/upload.repository.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../api.mocks.dart'; -import '../fixtures/asset.stub.dart'; -import '../infrastructure/repository.mock.dart'; -import '../mocks/asset_entity.mock.dart'; -import '../repository.mocks.dart'; - -void main() { - late ForegroundUploadService sut; - late MockUploadRepository mockUploadRepository; - late MockStorageRepository mockStorageRepository; - late MockBackupRepository mockBackupRepository; - late MockConnectivityApi mockConnectivityApi; - late MockAssetMediaRepository mockAssetMediaRepository; - late Drift db; - - setUpAll(() async { - TestWidgetsFlutterBinding.ensureInitialized(); - TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( - const MethodChannel('plugins.flutter.io/path_provider'), - (MethodCall methodCall) async => 'test', - ); - db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); - await SettingsRepository.ensureInitialized(db); - - await Store.put(StoreKey.serverEndpoint, 'http://demo.immich.app'); - await Store.put(StoreKey.deviceId, 'device-id'); - - registerFallbackValue(File('file')); - registerFallbackValue({}); - }); - - setUp(() { - mockUploadRepository = MockUploadRepository(); - mockStorageRepository = MockStorageRepository(); - mockBackupRepository = MockBackupRepository(); - mockConnectivityApi = MockConnectivityApi(); - mockAssetMediaRepository = MockAssetMediaRepository(); - - sut = ForegroundUploadService( - mockUploadRepository, - mockStorageRepository, - mockBackupRepository, - mockConnectivityApi, - mockAssetMediaRepository, - ); - }); - - List> captureFields() { - final captured = >[]; - when( - () => mockUploadRepository.uploadFile( - file: any(named: 'file'), - originalFileName: any(named: 'originalFileName'), - fields: any(named: 'fields'), - cancelToken: any(named: 'cancelToken'), - onProgress: any(named: 'onProgress'), - logContext: any(named: 'logContext'), - ), - ).thenAnswer((invocation) async { - final fields = invocation.namedArguments[#fields] as Map; - captured.add(Map.of(fields)); - return UploadResult.success(remoteAssetId: 'remote-${captured.length}'); - }); - return captured; - } - - List captureOriginalFileNames() { - final captured = []; - when( - () => mockUploadRepository.uploadFile( - file: any(named: 'file'), - originalFileName: any(named: 'originalFileName'), - fields: any(named: 'fields'), - cancelToken: any(named: 'cancelToken'), - onProgress: any(named: 'onProgress'), - logContext: any(named: 'logContext'), - ), - ).thenAnswer((invocation) async { - captured.add(invocation.namedArguments[#originalFileName] as String); - return UploadResult.success(remoteAssetId: 'remote-${captured.length}'); - }); - return captured; - } - - group('uploadSingleAsset', () { - test('should upload the motion part hidden and keep the still image visible', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final stillFile = File('/path/to/still.heic'); - final videoFile = File('/path/to/motion.mov'); - - when(() => mockEntity.isLivePhoto).thenReturn(true); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.isAssetAvailableLocally(asset.id)).thenAnswer((_) async => true); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => stillFile); - when(() => mockStorageRepository.getMotionFileForAsset(asset)).thenAnswer((_) async => videoFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'live.heic'); - - final captured = captureFields(); - - await sut.uploadSingleAsset(asset, null, callbacks: const UploadCallbacks()); - - expect(captured, hasLength(2)); - expect(captured[0]['visibility'], equals('hidden')); - expect(captured[0].containsKey('livePhotoVideoId'), isFalse); - expect(captured[1].containsKey('visibility'), isFalse); - expect(captured[1]['livePhotoVideoId'], equals('remote-1')); - }); - - test('should not set visibility for a regular photo', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final stillFile = File('/path/to/photo.jpg'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.isAssetAvailableLocally(asset.id)).thenAnswer((_) async => true); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => stillFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'photo.jpg'); - - final captured = captureFields(); - - await sut.uploadSingleAsset(asset, null, callbacks: const UploadCallbacks()); - - expect(captured, hasLength(1)); - expect(captured[0].containsKey('visibility'), isFalse); - }); - - test('corrects the extension when iOS returns a rendered file for a .dng asset', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final stillFile = File('/path/to/IMG_6499.jpg'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.isAssetAvailableLocally(asset.id)).thenAnswer((_) async => true); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => stillFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'IMG_6499.dng'); - - final names = captureOriginalFileNames(); - - await sut.uploadSingleAsset(asset, null, callbacks: const UploadCallbacks()); - - expect(names, equals(['IMG_6499.jpg'])); - }); - - test('keeps the .dng extension for a genuine RAW original', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final stillFile = File('/path/to/IMG_5210.dng'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.isAssetAvailableLocally(asset.id)).thenAnswer((_) async => true); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => stillFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'IMG_5210.dng'); - - final names = captureOriginalFileNames(); - - await sut.uploadSingleAsset(asset, null, callbacks: const UploadCallbacks()); - - expect(names, equals(['IMG_5210.dng'])); - }); - - test('borrows the extension from the asset name for an extensionless name (DJI/Fusion)', () async { - final asset = LocalAssetStub.image1; - final mockEntity = MockAssetEntity(); - final stillFile = File('/path/to/DJI_0001'); - - when(() => mockEntity.isLivePhoto).thenReturn(false); - when(() => mockStorageRepository.getAssetEntityForAsset(asset)).thenAnswer((_) async => mockEntity); - when(() => mockStorageRepository.isAssetAvailableLocally(asset.id)).thenAnswer((_) async => true); - when(() => mockStorageRepository.getFileForAsset(asset.id)).thenAnswer((_) async => stillFile); - when(() => mockAssetMediaRepository.getOriginalFilename(asset.id)).thenAnswer((_) async => 'DJI_0001'); - - final names = captureOriginalFileNames(); - - await sut.uploadSingleAsset(asset, null, callbacks: const UploadCallbacks()); - - expect(names, equals(['DJI_0001.jpg'])); - }); - }); -} diff --git a/mobile/test/services/hash_service_test.dart b/mobile/test/services/hash_service_test.dart new file mode 100644 index 0000000000..9429d434b0 --- /dev/null +++ b/mobile/test/services/hash_service_test.dart @@ -0,0 +1,349 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:file/memory.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:immich_mobile/domain/models/device_asset.model.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/infrastructure/repositories/device_asset.repository.dart'; +import 'package:immich_mobile/services/background.service.dart'; +import 'package:immich_mobile/services/hash.service.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../fixtures/asset.stub.dart'; +import '../infrastructure/repository.mock.dart'; +import '../service.mocks.dart'; +import '../mocks/asset_entity.mock.dart'; + +class MockAsset extends Mock implements Asset {} + +void main() { + late HashService sut; + late BackgroundService mockBackgroundService; + late IsarDeviceAssetRepository mockDeviceAssetRepository; + + setUp(() { + mockBackgroundService = MockBackgroundService(); + mockDeviceAssetRepository = MockDeviceAssetRepository(); + + sut = HashService(deviceAssetRepository: mockDeviceAssetRepository, backgroundService: mockBackgroundService); + + when(() => mockDeviceAssetRepository.transaction(any())).thenAnswer((_) async { + final capturedCallback = verify(() => mockDeviceAssetRepository.transaction(captureAny())).captured; + // Invoke the transaction callback + await (capturedCallback.firstOrNull as Future Function()?)?.call(); + }); + when(() => mockDeviceAssetRepository.updateAll(any())).thenAnswer((_) async => true); + when(() => mockDeviceAssetRepository.deleteIds(any())).thenAnswer((_) async => true); + }); + + group("HashService: No DeviceAsset entry", () { + test("hash successfully", () async { + final (mockAsset, file, deviceAsset, hash) = await _createAssetMock(AssetStub.image1); + + when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer((_) async => [hash]); + // No DB entries for this asset + when(() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!])).thenAnswer((_) async => []); + + final result = await sut.hashAssets([mockAsset]); + + // Verify we stored the new hash in DB + when(() => mockDeviceAssetRepository.transaction(any())).thenAnswer((_) async { + final capturedCallback = verify(() => mockDeviceAssetRepository.transaction(captureAny())).captured; + // Invoke the transaction callback + await (capturedCallback.firstOrNull as Future Function()?)?.call(); + verify( + () => mockDeviceAssetRepository.updateAll([ + deviceAsset.copyWith(modifiedTime: AssetStub.image1.fileModifiedAt), + ]), + ).called(1); + verify(() => mockDeviceAssetRepository.deleteIds([])).called(1); + }); + expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]); + }); + }); + + group("HashService: Has DeviceAsset entry", () { + test("when the asset is not modified", () async { + final hash = utf8.encode("image1-hash"); + + when(() => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!])).thenAnswer( + (_) async => [ + DeviceAsset(assetId: AssetStub.image1.localId!, hash: hash, modifiedTime: AssetStub.image1.fileModifiedAt), + ], + ); + final result = await sut.hashAssets([AssetStub.image1]); + + verifyNever(() => mockBackgroundService.digestFiles(any())); + verifyNever(() => mockBackgroundService.digestFile(any())); + verifyNever(() => mockDeviceAssetRepository.updateAll(any())); + verifyNever(() => mockDeviceAssetRepository.deleteIds(any())); + + expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]); + }); + + test("hashed successful when asset is modified", () async { + final (mockAsset, file, deviceAsset, hash) = await _createAssetMock(AssetStub.image1); + + when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer((_) async => [hash]); + when( + () => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!]), + ).thenAnswer((_) async => [deviceAsset]); + + final result = await sut.hashAssets([mockAsset]); + + when(() => mockDeviceAssetRepository.transaction(any())).thenAnswer((_) async { + final capturedCallback = verify(() => mockDeviceAssetRepository.transaction(captureAny())).captured; + // Invoke the transaction callback + await (capturedCallback.firstOrNull as Future Function()?)?.call(); + verify( + () => mockDeviceAssetRepository.updateAll([ + deviceAsset.copyWith(modifiedTime: AssetStub.image1.fileModifiedAt), + ]), + ).called(1); + verify(() => mockDeviceAssetRepository.deleteIds([])).called(1); + }); + + verify(() => mockBackgroundService.digestFiles([file.path])).called(1); + + expect(result, [AssetStub.image1.copyWith(checksum: base64.encode(hash))]); + }); + }); + + group("HashService: Cleanup", () { + late Asset mockAsset; + late Uint8List hash; + late DeviceAsset deviceAsset; + late File file; + + setUp(() async { + (mockAsset, file, deviceAsset, hash) = await _createAssetMock(AssetStub.image1); + + when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer((_) async => [hash]); + when( + () => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!]), + ).thenAnswer((_) async => [deviceAsset]); + }); + + test("cleanups DeviceAsset when local file cannot be obtained", () async { + when(() => mockAsset.local).thenThrow(Exception("File not found")); + final result = await sut.hashAssets([mockAsset]); + + verifyNever(() => mockBackgroundService.digestFiles(any())); + verifyNever(() => mockBackgroundService.digestFile(any())); + verifyNever(() => mockDeviceAssetRepository.updateAll(any())); + verify(() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!])).called(1); + + expect(result, isEmpty); + }); + + test("cleanups DeviceAsset when hashing failed", () async { + when(() => mockDeviceAssetRepository.transaction(any())).thenAnswer((_) async { + final capturedCallback = verify(() => mockDeviceAssetRepository.transaction(captureAny())).captured; + // Invoke the transaction callback + await (capturedCallback.firstOrNull as Future Function()?)?.call(); + + // Verify the callback inside the transaction because, doing it outside results + // in a small delay before the callback is invoked, resulting in other LOCs getting executed + // resulting in an incorrect state + // + // i.e, consider the following piece of code + // await _deviceAssetRepository.transaction(() async { + // await _deviceAssetRepository.updateAll(toBeAdded); + // await _deviceAssetRepository.deleteIds(toBeDeleted); + // }); + // toBeDeleted.clear(); + // since the transaction method is mocked, the callback is not invoked until it is captured + // and executed manually in the next event loop. However, the toBeDeleted.clear() is executed + // immediately once the transaction stub is executed, resulting in the deleteIds method being + // called with an empty list. + // + // To avoid this, we capture the callback and execute it within the transaction stub itself + // and verify the results inside the transaction stub + verify(() => mockDeviceAssetRepository.updateAll([])).called(1); + verify(() => mockDeviceAssetRepository.deleteIds([AssetStub.image1.localId!])).called(1); + }); + + when(() => mockBackgroundService.digestFiles([file.path])).thenAnswer( + // Invalid hash, length != 20 + (_) async => [Uint8List.fromList(hash.slice(2).toList())], + ); + + final result = await sut.hashAssets([mockAsset]); + + verify(() => mockBackgroundService.digestFiles([file.path])).called(1); + expect(result, isEmpty); + }); + }); + + group("HashService: Batch processing", () { + test("processes assets in batches when size limit is reached", () async { + // Setup multiple assets with large file sizes + final (mock1, mock2, mock3) = await ( + _createAssetMock(AssetStub.image1), + _createAssetMock(AssetStub.image2), + _createAssetMock(AssetStub.image3), + ).wait; + + final (asset1, file1, deviceAsset1, hash1) = mock1; + final (asset2, file2, deviceAsset2, hash2) = mock2; + final (asset3, file3, deviceAsset3, hash3) = mock3; + + when(() => mockDeviceAssetRepository.getByIds(any())).thenAnswer((_) async => []); + + // Setup for multiple batch processing calls + when(() => mockBackgroundService.digestFiles([file1.path, file2.path])).thenAnswer((_) async => [hash1, hash2]); + when(() => mockBackgroundService.digestFiles([file3.path])).thenAnswer((_) async => [hash3]); + + final size = await file1.length() + await file2.length(); + + sut = HashService( + deviceAssetRepository: mockDeviceAssetRepository, + backgroundService: mockBackgroundService, + batchSizeLimit: size, + ); + final result = await sut.hashAssets([asset1, asset2, asset3]); + + // Verify multiple batch process calls + verify(() => mockBackgroundService.digestFiles([file1.path, file2.path])).called(1); + verify(() => mockBackgroundService.digestFiles([file3.path])).called(1); + + expect(result, [ + AssetStub.image1.copyWith(checksum: base64.encode(hash1)), + AssetStub.image2.copyWith(checksum: base64.encode(hash2)), + AssetStub.image3.copyWith(checksum: base64.encode(hash3)), + ]); + }); + + test("processes assets in batches when file limit is reached", () async { + // Setup multiple assets with large file sizes + final (mock1, mock2, mock3) = await ( + _createAssetMock(AssetStub.image1), + _createAssetMock(AssetStub.image2), + _createAssetMock(AssetStub.image3), + ).wait; + + final (asset1, file1, deviceAsset1, hash1) = mock1; + final (asset2, file2, deviceAsset2, hash2) = mock2; + final (asset3, file3, deviceAsset3, hash3) = mock3; + + when(() => mockDeviceAssetRepository.getByIds(any())).thenAnswer((_) async => []); + + when(() => mockBackgroundService.digestFiles([file1.path])).thenAnswer((_) async => [hash1]); + when(() => mockBackgroundService.digestFiles([file2.path])).thenAnswer((_) async => [hash2]); + when(() => mockBackgroundService.digestFiles([file3.path])).thenAnswer((_) async => [hash3]); + + sut = HashService( + deviceAssetRepository: mockDeviceAssetRepository, + backgroundService: mockBackgroundService, + batchFileLimit: 1, + ); + final result = await sut.hashAssets([asset1, asset2, asset3]); + + // Verify multiple batch process calls + verify(() => mockBackgroundService.digestFiles([file1.path])).called(1); + verify(() => mockBackgroundService.digestFiles([file2.path])).called(1); + verify(() => mockBackgroundService.digestFiles([file3.path])).called(1); + + expect(result, [ + AssetStub.image1.copyWith(checksum: base64.encode(hash1)), + AssetStub.image2.copyWith(checksum: base64.encode(hash2)), + AssetStub.image3.copyWith(checksum: base64.encode(hash3)), + ]); + }); + + test("HashService: Sort & Process different states", () async { + final (asset1, file1, deviceAsset1, hash1) = await _createAssetMock(AssetStub.image1); // Will need rehashing + final (asset2, file2, deviceAsset2, hash2) = await _createAssetMock(AssetStub.image2); // Will have matching hash + final (asset3, file3, deviceAsset3, hash3) = await _createAssetMock(AssetStub.image3); // No DB entry + final asset4 = AssetStub.image3.copyWith(localId: "image4"); // Cannot be hashed + + when(() => mockBackgroundService.digestFiles([file1.path, file3.path])).thenAnswer((_) async => [hash1, hash3]); + // DB entries are not sorted and a dummy entry added + when( + () => mockDeviceAssetRepository.getByIds([ + AssetStub.image1.localId!, + AssetStub.image2.localId!, + AssetStub.image3.localId!, + asset4.localId!, + ]), + ).thenAnswer( + (_) async => [ + // Same timestamp to reuse deviceAsset + deviceAsset2.copyWith(modifiedTime: asset2.fileModifiedAt), + deviceAsset1, + deviceAsset3.copyWith(assetId: asset4.localId!), + ], + ); + + final result = await sut.hashAssets([asset1, asset2, asset3, asset4]); + + // Verify correct processing of all assets + verify(() => mockBackgroundService.digestFiles([file1.path, file3.path])).called(1); + expect(result.length, 3); + expect(result, [ + AssetStub.image2.copyWith(checksum: base64.encode(hash2)), + AssetStub.image1.copyWith(checksum: base64.encode(hash1)), + AssetStub.image3.copyWith(checksum: base64.encode(hash3)), + ]); + }); + + group("HashService: Edge cases", () { + test("handles empty list of assets", () async { + when(() => mockDeviceAssetRepository.getByIds(any())).thenAnswer((_) async => []); + + final result = await sut.hashAssets([]); + + verifyNever(() => mockBackgroundService.digestFiles(any())); + verifyNever(() => mockDeviceAssetRepository.updateAll(any())); + verifyNever(() => mockDeviceAssetRepository.deleteIds(any())); + + expect(result, isEmpty); + }); + + test("handles all file access failures", () async { + // No DB entries + when( + () => mockDeviceAssetRepository.getByIds([AssetStub.image1.localId!, AssetStub.image2.localId!]), + ).thenAnswer((_) async => []); + + final result = await sut.hashAssets([AssetStub.image1, AssetStub.image2]); + + verifyNever(() => mockBackgroundService.digestFiles(any())); + verifyNever(() => mockDeviceAssetRepository.updateAll(any())); + expect(result, isEmpty); + }); + }); + }); +} + +Future<(Asset, File, DeviceAsset, Uint8List)> _createAssetMock(Asset asset) async { + final random = Random(); + final hash = Uint8List.fromList(List.generate(20, (i) => random.nextInt(255))); + final mockAsset = MockAsset(); + final mockAssetEntity = MockAssetEntity(); + final fs = MemoryFileSystem(); + final deviceAsset = DeviceAsset( + assetId: asset.localId!, + hash: Uint8List.fromList(hash), + modifiedTime: DateTime.now(), + ); + final tmp = await fs.systemTempDirectory.createTemp(); + final file = tmp.childFile("${asset.fileName}-path"); + await file.writeAsString("${asset.fileName}-content"); + + when(() => mockAsset.localId).thenReturn(asset.localId); + when(() => mockAsset.fileName).thenReturn(asset.fileName); + when(() => mockAsset.fileCreatedAt).thenReturn(asset.fileCreatedAt); + when(() => mockAsset.fileModifiedAt).thenReturn(asset.fileModifiedAt); + when( + () => mockAsset.copyWith(checksum: any(named: "checksum")), + ).thenReturn(asset.copyWith(checksum: base64.encode(hash))); + when(() => mockAsset.local).thenAnswer((_) => mockAssetEntity); + when(() => mockAssetEntity.originFile).thenAnswer((_) async => file); + + return (mockAsset, file, deviceAsset, hash); +} diff --git a/mobile/test/services/view_intent_asset_resolver_test.dart b/mobile/test/services/view_intent_asset_resolver_test.dart deleted file mode 100644 index 36c343180a..0000000000 --- a/mobile/test/services/view_intent_asset_resolver_test.dart +++ /dev/null @@ -1,126 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/timeline.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/providers/infrastructure/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; -import 'package:immich_mobile/services/view_intent_asset_resolver.service.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../infrastructure/repository.mock.dart'; - -class MockTimelineFactory extends Mock implements TimelineFactory {} - -void main() { - late MockLocalAssetRepository mockLocalAssetRepository; - late MockTimelineFactory timelineFactory; - late List createdTimelineServices; - late ProviderContainer container; - - setUp(() { - mockLocalAssetRepository = MockLocalAssetRepository(); - timelineFactory = MockTimelineFactory(); - createdTimelineServices = []; - - when(() => timelineFactory.fromAssets(any(), TimelineOrigin.deepLink)).thenAnswer((invocation) { - final assets = List.from(invocation.positionalArguments[0] as List); - final timelineService = _timelineServiceFromAssets(assets, TimelineOrigin.deepLink); - createdTimelineServices.add(timelineService); - return timelineService; - }); - - final drift = MockDrift(); - when(() => drift.localAssetRepository).thenReturn(mockLocalAssetRepository); - - container = ProviderContainer( - overrides: [ - driftProvider.overrideWithValue(drift), - timelineFactoryProvider.overrideWith((ref) => timelineFactory), - ], - ); - - addTearDown(() async { - for (final timelineService in createdTimelineServices) { - await timelineService.dispose(); - } - container.dispose(); - }); - }); - - test('returns DB-backed local asset wrapped in a 1-element deep-link timeline', () async { - final localAsset = _localAsset(id: 'local-1', checksum: 'checksum-1'); - when(() => mockLocalAssetRepository.getById('local-1')).thenAnswer((_) async => localAsset); - - final result = await _resolve(container, _payload(localAssetId: 'local-1')); - - expect(result.asset, equals(localAsset)); - expect(result.timelineService.origin, TimelineOrigin.deepLink); - expect(result.viewIntentFilePath, isNull, reason: 'DB-backed assets carry their own source — no temp file needed'); - }); - - test('returns transient asset with temp file path when localAssetId has no DB row', () async { - when(() => mockLocalAssetRepository.getById('local-1')).thenAnswer((_) async => null); - - final result = await _resolve(container, _payload(localAssetId: 'local-1', path: '/tmp/incoming.jpg')); - - expect(result.asset, isA()); - expect(result.timelineService.origin, TimelineOrigin.deepLink); - expect(result.viewIntentFilePath, '/tmp/incoming.jpg'); - }); - - test('returns transient asset for path-only attachment', () async { - final result = await _resolve( - container, - _payload(localAssetId: null, path: '/tmp/incoming.webp', mimeType: 'image/webp'), - ); - - expect(result.asset, isA()); - expect(result.timelineService.origin, TimelineOrigin.deepLink); - expect(result.viewIntentFilePath, '/tmp/incoming.webp'); - - final asset = result.asset as LocalAsset; - expect(asset.localId, startsWith('-')); - expect(asset.name, 'incoming.webp'); - expect(asset.playbackStyle, AssetPlaybackStyle.imageAnimated); - }); - - test('throws when neither localAssetId nor path is provided', () async { - await expectLater( - _resolve(container, _payload(localAssetId: null, path: null)), - throwsA(isA()), - ); - }); -} - -Future _resolve(ProviderContainer container, ViewIntentPayload payload) { - return container.read(viewIntentAssetResolverProvider).resolve(payload); -} - -ViewIntentPayload _payload({String? localAssetId = 'local-1', String? path, String mimeType = 'image/jpeg'}) { - return ViewIntentPayload(path: path, mimeType: mimeType, localAssetId: localAssetId); -} - -LocalAsset _localAsset({required String id, String? checksum}) { - return LocalAsset( - id: id, - name: '$id.jpg', - checksum: checksum, - type: AssetType.image, - createdAt: DateTime(2026, 4, 20), - updatedAt: DateTime(2026, 4, 20), - playbackStyle: AssetPlaybackStyle.image, - isEdited: false, - ); -} - -TimelineService _timelineServiceFromAssets(List assets, TimelineOrigin origin) { - return TimelineService(( - assetSource: (index, count) async => assets.skip(index).take(count).toList(), - bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]), - origin: origin, - )); -} diff --git a/mobile/test/services/view_intent_service_test.dart b/mobile/test/services/view_intent_service_test.dart deleted file mode 100644 index fd8f5f725c..0000000000 --- a/mobile/test/services/view_intent_service_test.dart +++ /dev/null @@ -1,117 +0,0 @@ -// ignore_for_file: avoid_slow_async_io - -import 'dart:io'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/platform/view_intent_api.g.dart'; -import 'package:immich_mobile/services/view_intent.service.dart'; -import 'package:mocktail/mocktail.dart'; - -class MockViewIntentHostApi extends Mock implements ViewIntentHostApi {} - -void main() { - late MockViewIntentHostApi hostApi; - late ViewIntentService service; - late Directory tempRoot; - late Directory cacheDir; - - final attachment = ViewIntentPayload(path: '/tmp/file.jpg', mimeType: 'image/jpeg', localAssetId: '42'); - - setUp(() { - hostApi = MockViewIntentHostApi(); - tempRoot = Directory.systemTemp.createTempSync('view-intent-root'); - cacheDir = Directory('${tempRoot.path}/cache')..createSync(); - service = ViewIntentService(hostApi, temporaryDirectory: () async => cacheDir); - }); - - tearDown(() async { - clearInteractions(hostApi); - if (await tempRoot.exists()) { - await tempRoot.delete(recursive: true); - } - }); - - test('consumeViewIntent returns null when no attachment', () async { - when(() => hostApi.consumeViewIntent()).thenAnswer((_) async => null); - - final result = await service.consumeViewIntent(); - - expect(result, isNull); - verify(() => hostApi.consumeViewIntent()).called(1); - }); - - test('consumeViewIntent returns attachment when present', () async { - when(() => hostApi.consumeViewIntent()).thenAnswer((_) async => attachment); - - final result = await service.consumeViewIntent(); - - expect(result, attachment); - verify(() => hostApi.consumeViewIntent()).called(1); - }); - - test('consumeViewIntent swallows host api errors', () async { - when(() => hostApi.consumeViewIntent()).thenThrow(Exception('boom')); - - final result = await service.consumeViewIntent(); - - expect(result, isNull); - verify(() => hostApi.consumeViewIntent()).called(1); - }); - - test('setManagedTempFilePath cleans previous managed temp file', () async { - final firstFile = File('${cacheDir.path}/view_intent_first.jpg')..writeAsStringSync('first'); - final secondFile = File('${cacheDir.path}/view_intent_second.jpg')..writeAsStringSync('second'); - - await service.setManagedTempFilePath(firstFile.path); - await service.setManagedTempFilePath(secondFile.path); - - expect(await firstFile.exists(), isFalse); - expect(await secondFile.exists(), isTrue); - - await service.cleanupManagedTempFile(); - expect(await secondFile.exists(), isFalse); - }); - - test('cleanupTempFile defers deletion while an upload is active', () async { - final tempFile = File('${cacheDir.path}/view_intent_in_flight.jpg')..writeAsStringSync('bytes'); - - service.markUploadActive(tempFile.path); - await service.cleanupTempFile(tempFile.path); - - expect(await tempFile.exists(), isTrue, reason: 'active uploads block cleanup'); - - await service.markUploadInactive(tempFile.path); - expect(await tempFile.exists(), isFalse); - }); - - test('cleanupTempFile ignores non-managed paths', () async { - final nonManagedFile = File('${tempRoot.path}/plain_file.jpg')..writeAsStringSync('content'); - - await service.cleanupTempFile(nonManagedFile.path); - - expect(await nonManagedFile.exists(), isTrue); - }); - - test('cleanupStaleTempFiles removes view-intent temp files and keeps unrelated files', () async { - final firstFile = File('${cacheDir.path}/view_intent_first.jpg')..writeAsStringSync('first'); - final secondFile = File('${cacheDir.path}/view_intent_second.jpg')..writeAsStringSync('second'); - final unrelatedFile = File('${cacheDir.path}/plain_file.jpg')..writeAsStringSync('plain'); - - await service.cleanupStaleTempFiles(); - - expect(await firstFile.exists(), isFalse); - expect(await secondFile.exists(), isFalse); - expect(await unrelatedFile.exists(), isTrue); - }); - - test('cleanupStaleTempFiles skips paths with active uploads', () async { - final stale = File('${cacheDir.path}/view_intent_stale.jpg')..writeAsStringSync('stale'); - final active = File('${cacheDir.path}/view_intent_active.jpg')..writeAsStringSync('active'); - service.markUploadActive(active.path); - - await service.cleanupStaleTempFiles(); - - expect(await stale.exists(), isFalse); - expect(await active.exists(), isTrue); - }); -} diff --git a/mobile/test/test_utils.dart b/mobile/test/test_utils.dart index 17faed4a27..30d4e2e6d4 100644 --- a/mobile/test/test_utils.dart +++ b/mobile/test/test_utils.dart @@ -4,13 +4,82 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:fake_async/fake_async.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart' as domain; +import 'package:immich_mobile/entities/album.entity.dart'; +import 'package:immich_mobile/entities/android_device_asset.entity.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/entities/duplicated_asset.entity.dart'; +import 'package:immich_mobile/entities/etag.entity.dart'; +import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; +import 'package:isar/isar.dart'; +import 'package:mocktail/mocktail.dart'; import 'mock_http_override.dart'; +// Listener Mock to test when a provider notifies its listeners +class ListenerMock extends Mock { + void call(T? previous, T next); +} + abstract final class TestUtils { const TestUtils._(); + /// Downloads Isar binaries (if required) and initializes a new Isar db + static Future initIsar() async { + await Isar.initializeIsarCore(download: true); + + final instance = Isar.getInstance(); + if (instance != null) { + return instance; + } + + final db = await Isar.open( + [ + StoreValueSchema, + ExifInfoSchema, + AssetSchema, + AlbumSchema, + UserSchema, + BackupAlbumSchema, + DuplicatedAssetSchema, + ETagSchema, + AndroidDeviceAssetSchema, + IOSDeviceAssetSchema, + DeviceAssetEntitySchema, + ], + directory: "test/", + maxSizeMiB: 1024, + inspector: false, + ); + + // Clear and close db on test end + addTearDown(() async { + await db.writeTxn(() async => await db.clear()); + await db.close(); + }); + return db; + } + + /// Creates a new ProviderContainer to test Riverpod providers + static ProviderContainer createContainer({ + ProviderContainer? parent, + List overrides = const [], + List? observers, + }) { + final container = ProviderContainer(parent: parent, overrides: overrides, observers: observers); + + // Dispose on test end + addTearDown(container.dispose); + + return container; + } + static void init() { // Turn off easy localization logging EasyLocalization.logger.enableBuildModes = []; @@ -58,8 +127,7 @@ abstract final class TestUtils { type: domain.AssetType.image, createdAt: DateTime(2024, 1, 1), updatedAt: DateTime(2024, 1, 1), - uploadedAt: DateTime(2024, 1, 1), - durationMs: 0, + durationInSeconds: 0, isFavorite: false, width: width, height: height, @@ -82,7 +150,7 @@ abstract final class TestUtils { type: domain.AssetType.image, createdAt: DateTime(2024, 1, 1), updatedAt: DateTime(2024, 1, 1), - durationMs: 0, + durationInSeconds: 0, isFavorite: false, width: width, height: height, diff --git a/mobile/test/test_utils/medium_factory.dart b/mobile/test/test_utils/medium_factory.dart index fa40de0160..50e73e5b5e 100644 --- a/mobile/test/test_utils/medium_factory.dart +++ b/mobile/test/test_utils/medium_factory.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:immich_mobile/domain/models/album/local_album.model.dart'; +import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart'; @@ -9,6 +10,28 @@ class MediumFactory { const MediumFactory(Drift db) : _db = db; + LocalAsset localAsset({ + String? id, + String? name, + AssetType? type, + DateTime? createdAt, + DateTime? updatedAt, + String? checksum, + }) { + final random = Random(); + + return LocalAsset( + id: id ?? '${random.nextInt(1000000)}', + name: name ?? 'Asset ${random.nextInt(1000000)}', + checksum: checksum ?? '${random.nextInt(1000000)}', + type: type ?? AssetType.image, + createdAt: createdAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)), + updatedAt: updatedAt ?? DateTime.fromMillisecondsSinceEpoch(random.nextInt(1000000000)), + playbackStyle: AssetPlaybackStyle.image, + isEdited: false, + ); + } + LocalAlbum localAlbum({ String? id, String? name, @@ -31,8 +54,8 @@ class MediumFactory { T getRepository() { switch (T) { - case const (LocalAlbumRepository): - return LocalAlbumRepository(_db) as T; + case const (DriftLocalAlbumRepository): + return DriftLocalAlbumRepository(_db) as T; default: throw Exception('Unknown repository: $T'); } diff --git a/mobile/test/unit/factories/local_album_factory.dart b/mobile/test/unit/factories/local_album_factory.dart deleted file mode 100644 index 639237c858..0000000000 --- a/mobile/test/unit/factories/local_album_factory.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; - -import '../../utils.dart'; - -class LocalAlbumFactory { - const LocalAlbumFactory(); - - static LocalAlbum create({ - String? id, - String? name, - DateTime? updatedAt, - BackupSelection? backupSelection, - bool? isIosSharedAlbum, - String? linkedRemoteAlbumId, - int? assetCount, - }) { - final albumId = TestUtils.uuid(id); - return LocalAlbum( - id: albumId, - name: name ?? 'local_album_$albumId', - updatedAt: TestUtils.date(updatedAt), - backupSelection: backupSelection ?? .none, - isIosSharedAlbum: isIosSharedAlbum ?? false, - linkedRemoteAlbumId: linkedRemoteAlbumId, - assetCount: assetCount ?? 10, - ); - } -} diff --git a/mobile/test/unit/factories/local_asset_factory.dart b/mobile/test/unit/factories/local_asset_factory.dart deleted file mode 100644 index 5b16635d48..0000000000 --- a/mobile/test/unit/factories/local_asset_factory.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; - -import '../../utils.dart'; - -class LocalAssetFactory { - const LocalAssetFactory(); - - static LocalAsset create({String? id, String? name, String? remoteId}) { - final assetId = TestUtils.uuid(id); - - return LocalAsset( - id: assetId, - name: name ?? 'local_$assetId.jpg', - remoteId: remoteId, - type: AssetType.image, - createdAt: TestUtils.yesterday(), - updatedAt: TestUtils.now(), - playbackStyle: .image, - isEdited: false, - ); - } -} diff --git a/mobile/test/unit/factories/partner_user_factory.dart b/mobile/test/unit/factories/partner_user_factory.dart deleted file mode 100644 index 2ee0fa0773..0000000000 --- a/mobile/test/unit/factories/partner_user_factory.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:immich_mobile/domain/models/user.model.dart'; - -import '../../utils.dart'; - -class PartnerFactory { - const PartnerFactory(); - - static Partner create({String? id, String? email, String? name, bool? inTimeline}) { - final partnerId = TestUtils.uuid(id); - return Partner( - id: partnerId, - email: email ?? '$partnerId@test.com', - name: name ?? 'user_$partnerId', - inTimeline: inTimeline ?? false, - hasProfileImage: false, - profileChangedAt: DateTime.now(), - ); - } -} diff --git a/mobile/test/unit/factories/remote_album_factory.dart b/mobile/test/unit/factories/remote_album_factory.dart deleted file mode 100644 index 70976b4485..0000000000 --- a/mobile/test/unit/factories/remote_album_factory.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:immich_mobile/domain/models/album/album.model.dart'; - -import '../../utils.dart'; - -class RemoteAlbumFactory { - const RemoteAlbumFactory(); - - static RemoteAlbum create({ - String? id, - String? name, - String? ownerId, - String? description, - DateTime? createdAt, - DateTime? updatedAt, - String? thumbnailAssetId, - bool isActivityEnabled = false, - AlbumAssetOrder order = AlbumAssetOrder.desc, - int assetCount = 0, - String? ownerName, - bool isShared = false, - }) { - final albumId = TestUtils.uuid(id); - return RemoteAlbum( - id: albumId, - name: name ?? 'remote_album_$albumId', - ownerId: TestUtils.uuid(ownerId), - description: description ?? '', - createdAt: TestUtils.date(createdAt), - updatedAt: TestUtils.date(updatedAt), - thumbnailAssetId: thumbnailAssetId, - isActivityEnabled: isActivityEnabled, - order: order, - assetCount: assetCount, - ownerName: ownerName ?? 'owner_$albumId', - isShared: isShared, - ); - } -} diff --git a/mobile/test/unit/factories/remote_asset_factory.dart b/mobile/test/unit/factories/remote_asset_factory.dart deleted file mode 100644 index 2936708ab4..0000000000 --- a/mobile/test/unit/factories/remote_asset_factory.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; - -import '../../utils.dart'; - -class RemoteAssetFactory { - const RemoteAssetFactory(); - - static RemoteAsset create({ - String? id, - String? name, - String? ownerId, - bool isFavorite = false, - AssetVisibility visibility = .timeline, - AssetType type = .image, - String? stackId, - DateTime? deletedAt, - String? localId, - }) { - final assetId = TestUtils.uuid(id); - - return RemoteAsset( - id: assetId, - name: name ?? 'remote_$assetId.jpg', - ownerId: TestUtils.uuid(ownerId), - checksum: 'checksum-$assetId', - type: type, - createdAt: TestUtils.yesterday(), - updatedAt: TestUtils.now(), - isFavorite: isFavorite, - visibility: visibility, - stackId: stackId, - isEdited: false, - deletedAt: deletedAt, - localId: localId, - ); - } -} diff --git a/mobile/test/unit/factories/user_factory.dart b/mobile/test/unit/factories/user_factory.dart deleted file mode 100644 index d3df9556ef..0000000000 --- a/mobile/test/unit/factories/user_factory.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:immich_mobile/domain/models/user.model.dart'; - -import '../../utils.dart'; - -class UserFactory { - const UserFactory(); - - static User create({ - String? id, - String? name, - String? email, - DateTime? profileChangedAt, - bool? hasProfileImage, - AvatarColor? avatarColor, - }) { - final userId = TestUtils.uuid(id); - return User( - id: userId, - name: name ?? 'user_$userId', - email: email ?? '$userId@test.com', - profileChangedAt: TestUtils.date(profileChangedAt), - hasProfileImage: hasProfileImage ?? false, - avatarColor: avatarColor ?? .primary, - ); - } - - static UserDto createDto({ - String? id, - String? name, - String? email, - DateTime? profileChangedAt, - bool? hasProfileImage, - AvatarColor? avatarColor, - }) { - final userId = TestUtils.uuid(id); - return UserDto( - id: userId, - name: name ?? 'user_$userId', - email: email ?? '$userId@test.com', - profileChangedAt: TestUtils.date(profileChangedAt), - hasProfileImage: hasProfileImage ?? false, - avatarColor: avatarColor ?? .primary, - ); - } -} diff --git a/mobile/test/unit/mocks.dart b/mobile/test/unit/mocks.dart deleted file mode 100644 index e8ef01d3d2..0000000000 --- a/mobile/test/unit/mocks.dart +++ /dev/null @@ -1,447 +0,0 @@ -import 'dart:typed_data'; - -import 'package:flutter/widgets.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/album/album.model.dart'; -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/domain/models/exif.model.dart'; -import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:maplibre_gl/maplibre_gl.dart'; -import 'package:mocktail/mocktail.dart' as mock; -import 'package:mocktail/mocktail.dart'; - -import '../infrastructure/repository.mock.dart'; -import '../repository.mocks.dart'; -import '../service.mocks.dart'; -import 'factories/local_album_factory.dart'; -import 'factories/local_asset_factory.dart'; -import 'factories/remote_album_factory.dart'; -import 'factories/user_factory.dart'; - -class RepositoryMocks { - final localAlbum = LocalAlbumRepositoryStub(MockLocalAlbumRepository()); - final localAsset = LocalAssetRepositoryStub(MockLocalAssetRepository()); - final remoteAsset = RemoteAssetRepositoryStub(MockRemoteAssetRepository()); - final remoteExif = RemoteExifRepositoryStub(MockRemoteExifRepository()); - final trashedAsset = MockTrashedLocalAssetRepository(); - final remoteAlbum = MockRemoteAlbumRepository(); - final albumApi = MockDriftAlbumApiRepository(); - final permission = PermissionRepositoryStub(MockPermissionRepository()); - - final nativeApi = NativeSyncApiStub(MockNativeSyncApi()); - final assetApi = AssetApiRepositoryStub(MockAssetApiRepository()); - final assetMedia = AssetMediaRepositoryStub(MockAssetMediaRepository()); - final download = DownloadRepositoryStub(MockDownloadRepository()); - - RepositoryMocks() { - resetAll(); - } - - void resetAll() { - _registerFallbacks(); - localAlbum.reset(); - localAsset.reset(); - remoteAsset.reset(); - remoteExif.reset(); - reset(trashedAsset); - reset(remoteAlbum); - reset(albumApi); - nativeApi.reset(); - assetApi.reset(); - assetMedia.reset(); - download.reset(); - permission.reset(); - _stubLocalAlbumRepository(); - _stubLocalAssetRepository(); - _stubRemoteAssetRepository(); - _stubRemoteExifRepository(); - _stubNativeSyncApi(); - _stubAssetApiRepository(); - _stubAssetMediaRepository(); - _stubDownloadRepository(); - _stubTrashedAssetRepository(); - _stubPermissionRepository(); - } - - void _stubRemoteAssetRepository() { - when(remoteAsset.getExif).thenAnswer((_) async => null); - when(remoteAsset.getAssetEdits).thenAnswer((_) async => const []); - when(remoteAsset.update).thenAnswer((_) async {}); - } - - void _stubRemoteExifRepository() { - when(remoteExif.update).thenAnswer((_) async {}); - } - - void _stubLocalAlbumRepository() { - when(localAlbum.getBackupAlbums).thenAnswer((_) async => []); - when(localAlbum.getAssetsToHash).thenAnswer((_) async => []); - } - - void _stubLocalAssetRepository() { - when(localAsset.reconcileHashesFromCloudId).thenAnswer((_) async => {}); - when(localAsset.updateHashes).thenAnswer((_) async => {}); - when(localAsset.deleteAssets).thenAnswer((_) async => {}); - } - - void _stubNativeSyncApi() { - when(nativeApi.hashAssets).thenAnswer((_) async => []); - } - - void _stubAssetApiRepository() { - when(assetApi.update).thenAnswer((_) async => {}); - } - - void _stubAssetMediaRepository() { - when(assetMedia.deleteAll).thenAnswer((inv) async => inv.positionalArguments.first as List); - when(assetMedia.shareAssets).thenAnswer((_) async => 1); - when(assetMedia.getOriginalFilename).thenAnswer((_) async => null); - } - - void _stubDownloadRepository() { - when(download.downloadAllAssets).thenAnswer((_) async => const []); - } - - void _stubTrashedAssetRepository() { - when(() => trashedAsset.applyTrashedAssets(any())).thenAnswer((_) async => {}); - } - - void _stubPermissionRepository() { - when(permission.getStatus).thenAnswer((_) async => DevicePermissionStatus.denied); - when(permission.request).thenAnswer((_) async => DevicePermissionStatus.denied); - when(permission.getAndroidSdkVersion).thenAnswer((_) async => 34); - } -} - -class ServiceMocks { - final partner = PartnerServiceStub(MockPartnerService()); - final user = UserServiceStub(MockUserService()); - final asset = AssetServiceStub(MockAssetService()); - final album = RemoteAlbumServiceStub(MockRemoteAlbumService()); - final cleanup = CleanupServiceStub(MockCleanupService()); - final tag = TagServiceStub(MockTagService()); - final backgroundSync = MockBackgroundSyncManager(); - final upload = MockForegroundUploadService(); - final cast = MockGCastService(); - final serverInfo = MockServerInfoService(); - final toast = MockToastService(); - - ServiceMocks() { - resetAll(); - } - - void resetAll() { - _registerFallbacks(); - partner.reset(); - user.reset(); - asset.reset(); - album.reset(); - cleanup.reset(); - tag.reset(); - reset(cast); - reset(serverInfo); - reset(backgroundSync); - reset(upload); - reset(toast); - _stubUserService(); - _stubPartnerService(); - _stubAssetService(); - _stubRemoteAlbumService(); - _stubCleanupService(); - _stubTagService(); - _stubBackgroundSync(); - _stubForegroundUpload(); - } - - void _stubUserService() { - when(user.getMyUser).thenReturn(UserFactory.createDto()); - when(user.tryGetMyUser).thenReturn(null); - when(user.watchMyUser).thenAnswer((_) => const Stream.empty()); - when(user.refreshMyUser).thenAnswer((_) async => null); - when(user.createProfileImage).thenAnswer((_) async => null); - } - - void _stubPartnerService() { - registerFallbackValue(PartnerDirection.sharedBy); - when(partner.getCandidates).thenAnswer((_) => const Stream.empty()); - when(partner.search).thenAnswer((_) => const Stream.empty()); - when(partner.update).thenAnswer((_) async {}); - when(partner.create).thenAnswer((_) async {}); - when(partner.delete).thenAnswer((_) async {}); - } - - void _stubAssetService() { - when(asset.update).thenAnswer((_) async {}); - when(asset.stack).thenAnswer((_) async {}); - when(asset.unstack).thenAnswer((_) async {}); - when(asset.restoreTrash).thenAnswer((_) async {}); - when(asset.trash).thenAnswer((_) async {}); - when(asset.delete).thenAnswer((_) async {}); - when(asset.applyEdits).thenAnswer((_) async {}); - when(asset.deleteLocal).thenAnswer((_) async => 0); - } - - void _stubRemoteAlbumService() { - when(album.removeAssets).thenAnswer((_) async => 0); - when(album.updateAlbum).thenAnswer((_) async => RemoteAlbumFactory.create()); - } - - void _stubCleanupService() { - when(cleanup.deleteLocalAssets).thenAnswer((_) async => 0); - } - - void _stubTagService() { - when(tag.bulkTagAssets).thenAnswer((_) async => 0); - when(tag.upsertTags).thenAnswer((_) async => const []); - when(tag.getAllTags).thenAnswer((_) async => const {}); - } - - void _stubBackgroundSync() { - when(() => backgroundSync.syncLocal()).thenAnswer((_) async {}); - when(() => backgroundSync.hashAssets()).thenAnswer((_) async {}); - } - - void _stubForegroundUpload() { - when( - () => upload.uploadManual( - any(), - cancelToken: any(named: 'cancelToken'), - callbacks: any(named: 'callbacks'), - ), - ).thenAnswer((_) async {}); - } -} - -void _registerFallbacks() { - registerFallbackValue(LocalAlbumFactory.create()); - registerFallbackValue(LocalAssetFactory.create()); - registerFallbackValue(Uint8List(0)); - registerFallbackValue(AssetVisibility.timeline); - registerFallbackValue(const LatLng(0, 0)); - registerFallbackValue([]); - registerFallbackValue(const Option.none()); - registerFallbackValue(const Option.none()); - registerFallbackValue(const Option.none()); - registerFallbackValue(const Option.none()); - registerFallbackValue(const Option.none()); - registerFallbackValue([]); - registerFallbackValue([]); - registerFallbackValue([]); - registerFallbackValue(ShareAssetType.original); - registerFallbackValue(const UploadCallbacks()); - registerFallbackValue(_FakeBuildContext()); - registerFallbackValue(DevicePermissionStatus.granted); - registerFallbackValue(DevicePermission.photos); -} - -class _FakeBuildContext extends Fake implements BuildContext {} - -extension type const Stub(T mockedClass) { - void reset() => mock.reset(mockedClass); -} - -extension type const LocalAlbumRepositoryStub(MockLocalAlbumRepository repo) implements Stub { - Future> Function() get getBackupAlbums => - () => repo.getBackupAlbums(); - - Future> Function() get getAssetsToHash => - () => repo.getAssetsToHash(any()); -} - -extension type const LocalAssetRepositoryStub(MockLocalAssetRepository repo) - implements Stub { - Future Function() get reconcileHashesFromCloudId => - () => repo.reconcileHashesFromCloudId(); - - Future Function() get updateHashes => - () => repo.updateHashes(any()); - - Future Function() get deleteAssets => - () => repo.deleteAssets(any()); -} - -extension type const RemoteAssetRepositoryStub(MockRemoteAssetRepository repo) - implements Stub { - Future Function() get getExif => - () => repo.getExif(any()); - - Future> Function() get getAssetEdits => - () => repo.getAssetEdits(any()); - - Future Function() get update => - () => repo.updateAssets( - any(), - isFavorite: any(named: 'isFavorite'), - visibility: any(named: 'visibility'), - createdAt: any(named: 'createdAt'), - ); -} - -extension type const RemoteExifRepositoryStub(MockRemoteExifRepository repo) implements Stub { - Future Function() get update => - () => repo.updateExif( - any(), - dateTimeOriginal: any(named: 'dateTimeOriginal'), - timeZone: any(named: 'timeZone'), - location: any(named: 'location'), - ); -} - -extension type const PartnerServiceStub(MockPartnerService service) implements Stub { - Stream> Function() get getCandidates => - () => service.getCandidates(any()); - - Stream> Function() get search => - () => service.search(any(), any()); - - Future Function() get create => - () => service.create( - sharedById: any(named: 'sharedById'), - sharedWithId: any(named: 'sharedWithId'), - inTimeline: any(named: 'inTimeline'), - ); - - Future Function() get update => - () => service.update( - sharedById: any(named: 'sharedById'), - sharedWithId: any(named: 'sharedWithId'), - inTimeline: any(named: 'inTimeline'), - ); - - Future Function() get delete => - () => service.delete( - sharedById: any(named: 'sharedById'), - sharedWithId: any(named: 'sharedWithId'), - ); -} - -extension type const UserServiceStub(MockUserService service) implements Stub { - UserDto Function() get getMyUser => - () => service.getMyUser(); - - UserDto? Function() get tryGetMyUser => - () => service.tryGetMyUser(); - - Stream Function() get watchMyUser => - () => service.watchMyUser(); - - Future Function() get refreshMyUser => - () => service.refreshMyUser(); - - Future Function() get createProfileImage => - () => service.createProfileImage(any(), any()); -} - -extension type const AssetServiceStub(MockAssetService service) implements Stub { - Future Function() get update => - () => service.update( - any(), - isFavorite: any(named: 'isFavorite'), - visibility: any(named: 'visibility'), - dateTime: any(named: 'dateTime'), - location: any(named: 'location'), - ); - - Future Function() get stack => - () => service.stack(any(), any()); - - Future Function() get unstack => - () => service.unstack(any()); - - Future Function() get restoreTrash => - () => service.restoreTrash(any()); - - Future Function() get trash => - () => service.trash(any()); - - Future Function() get delete => - () => service.delete(any()); - - Future Function() get applyEdits => - () => service.applyEdits(any(), any()); - - Future Function() get deleteLocal => - () => service.deleteLocal(any(), trash: any(named: 'trash')); -} - -extension type const RemoteAlbumServiceStub(MockRemoteAlbumService service) implements Stub { - Future Function() get removeAssets => - () => service.removeAssets( - albumId: any(named: 'albumId'), - assetIds: any(named: 'assetIds'), - ); - - Future Function() get updateAlbum => - () => service.updateAlbum(any(), thumbnailAssetId: any(named: 'thumbnailAssetId')); -} - -extension type const CleanupServiceStub(MockCleanupService service) implements Stub { - Future Function() get deleteLocalAssets => - () => service.deleteLocalAssets(any()); -} - -extension type const NativeSyncApiStub(MockNativeSyncApi api) implements Stub { - Future> Function() get hashAssets => - () => api.hashAssets(any(), allowNetworkAccess: any(named: 'allowNetworkAccess')); -} - -extension type const AssetApiRepositoryStub(MockAssetApiRepository api) implements Stub { - Future Function() get update => - () => api.update( - any(), - isFavorite: any(named: 'isFavorite'), - visibility: any(named: 'visibility'), - dateTimeOriginal: any(named: 'dateTimeOriginal'), - location: any(named: 'location'), - ); -} - -extension type const AssetMediaRepositoryStub(MockAssetMediaRepository api) implements Stub { - Future> Function() get deleteAll => - () => api.deleteAll(any(), trash: any(named: 'trash')); - - Future Function() get shareAssets => - () => api.shareAssets( - any(), - any(), - fileType: any(named: 'fileType'), - cancelCompleter: any(named: 'cancelCompleter'), - onAssetDownloadProgress: any(named: 'onAssetDownloadProgress'), - ); - - Future Function() get getOriginalFilename => - () => api.getOriginalFilename(any()); -} - -extension type const DownloadRepositoryStub(MockDownloadRepository repo) implements Stub { - Future> Function() get downloadAllAssets => - () => repo.downloadAllAssets(any()); -} - -extension type const PermissionRepositoryStub(MockPermissionRepository repo) implements Stub { - Future Function() get getStatus => - () => repo.getStatus(any()); - - Future Function() get request => - () => repo.request(any()); - - Future Function() get getAndroidSdkVersion => - () => repo.getAndroidSdkVersion(); -} - -extension type const TagServiceStub(MockTagService service) implements Stub { - Future Function() get bulkTagAssets => - () => service.bulkTagAssets(any(), any()); - - Future> Function() get upsertTags => - () => service.upsertTags(any()); - - Future> Function() get getAllTags => - () => service.getAllTags(); -} diff --git a/mobile/test/unit/presentation/actions/album_action_test.dart b/mobile/test/unit/presentation/actions/album_action_test.dart deleted file mode 100644 index 7baa14a4eb..0000000000 --- a/mobile/test/unit/presentation/actions/album_action_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/remove_from_album.action.dart'; -import 'package:immich_mobile/presentation/actions/set_album_cover.action.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/local_asset_factory.dart'; -import '../../factories/remote_album_factory.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - const albumId = 'album-1'; - - late PresentationContext context; - late MockRemoteAlbumService albumService; - - setUp(() async { - context = await PresentationContext.create(); - albumService = context.service.album.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - List withMockToast() => [toastServiceProvider.overrideWithValue(context.service.toast)]; - - group('RemoveFromAlbumAction', () { - Future pumpRemove(WidgetTester tester, Set selection) => tester.pumpTestAction( - context, - const RemoveFromAlbumAction(source: .timeline, albumId: albumId), - overrides: [...context.selected(selection), ...withMockToast()], - ); - - testWidgets('removes every selected remote asset from the album', (tester) async { - final first = RemoteAssetFactory.create(); - final second = RemoteAssetFactory.create(); - - await pumpRemove(tester, {first, second}); - await tester.pumpAndSettle(); - - verify(() => albumService.removeAssets(albumId: albumId, assetIds: [first.id, second.id])).called(1); - }); - - testWidgets('reports the count the server actually removed', (tester) async { - when( - () => albumService.removeAssets( - albumId: any(named: 'albumId'), - assetIds: any(named: 'assetIds'), - ), - ).thenAnswer((_) async => 1); - - await pumpRemove(tester, {RemoteAssetFactory.create(), RemoteAssetFactory.create()}); - await tester.pumpAndSettle(); - - final message = verify(() => context.service.toast.success(captureAny())).captured.single as String; - expect(message, StaticTranslations.instance.remove_from_album_action_prompt(count: 1)); - }); - - testWidgets('is hidden for a local-only asset', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton( - action: RemoveFromAlbumAction(source: .timeline, albumId: albumId), - ), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); - - group('SetAlbumCoverAction', () { - testWidgets('sets the single selected asset as the cover', (tester) async { - final asset = RemoteAssetFactory.create(); - when( - () => albumService.updateAlbum(any(), thumbnailAssetId: any(named: 'thumbnailAssetId')), - ).thenAnswer((_) async => RemoteAlbumFactory.create(id: albumId)); - - await tester.pumpTestAction( - context, - const SetAlbumCoverAction(source: .timeline, albumId: albumId), - overrides: [ - ...context.selected({asset}), - ...withMockToast(), - ], - ); - await tester.pumpAndSettle(); - - verify(() => albumService.updateAlbum(albumId, thumbnailAssetId: asset.id)).called(1); - - final message = verify(() => context.service.toast.success(captureAny())).captured.single as String; - expect(message, StaticTranslations.instance.album_cover_updated); - }); - - testWidgets('is hidden for more than one asset', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton( - action: SetAlbumCoverAction(source: .timeline, albumId: albumId), - ), - overrides: context.selected({RemoteAssetFactory.create(), RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden for a local-only asset', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton( - action: SetAlbumCoverAction(source: .timeline, albumId: albumId), - ), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/archive_action_test.dart b/mobile/test/unit/presentation/actions/archive_action_test.dart deleted file mode 100644 index 80f1556e39..0000000000 --- a/mobile/test/unit/presentation/actions/archive_action_test.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/archive.action.dart'; -import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({AssetVisibility visibility = .timeline}) => - RemoteAssetFactory.create(ownerId: context.currentUser.id, visibility: visibility); - - Future pumpArchive(WidgetTester tester, Set selection) => - tester.pumpTestAction(context, const ArchiveAction(source: .timeline), overrides: context.selected(selection)); - - group('ArchiveAction', () { - testWidgets('archives the eligible owned assets', (tester) async { - final asset = owned(); - - await pumpArchive(tester, {asset}); - - verify(() => assetService.update([asset.id], visibility: const Option.some(AssetVisibility.archive))).called(1); - }); - - testWidgets('unarchive the eligible owned assets', (tester) async { - final asset = owned(visibility: .archive); - - await pumpArchive(tester, {asset}); - - verify(() => assetService.update([asset.id], visibility: const .some(.timeline))).called(1); - }); - - testWidgets('prioritizes archive when mixed state', (tester) async { - final onTimeline = owned(); - final archived = owned(visibility: .archive); - - await pumpArchive(tester, {onTimeline, archived}); - - verify(() => assetService.update([onTimeline.id], visibility: const .some(.archive))).called(1); - verifyNever(() => assetService.update(any(), visibility: const .some(.timeline))); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpArchive(tester, {mine, theirs}); - - verify(() => assetService.update([mine.id], visibility: const .some(.archive))).called(1); - }); - - testWidgets('skips owned assets already in the target state', (tester) async { - final stale = owned(); - final alreadyArchived = owned(visibility: .archive); - - await pumpArchive(tester, {stale, alreadyArchived}); - - verify(() => assetService.update([stale.id], visibility: const .some(.archive))).called(1); - }); - - testWidgets('clears the selection once the update succeeds', (tester) async { - await pumpArchive(tester, {owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(ImmichIconButton), findsNothing, reason: 'an empty selection hides the action'); - }); - - testWidgets('is hidden for locked assets, which belong to neither direction', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: ArchiveAction(source: .timeline)), - overrides: context.selected({owned(visibility: .locked)}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('offers an undo that puts the archived assets back on the timeline', (tester) async { - final asset = owned(); - - await pumpArchive(tester, {asset}); - await tester.pumpAndSettle(); - await tester.tap(find.text('Undo')); - await tester.pump(); - - verify(() => assetService.update([asset.id], visibility: const .some(.timeline))).called(1); - }); - - testWidgets('offers an undo that re-archives the unarchived assets', (tester) async { - final asset = owned(visibility: .archive); - - await pumpArchive(tester, {asset}); - await tester.pumpAndSettle(); - await tester.tap(find.text('Undo')); - await tester.pump(); - - verify(() => assetService.update([asset.id], visibility: const .some(.archive))).called(1); - }); - - testWidgets('is hidden inside the locked folder view', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: ArchiveAction(source: .timeline)), - overrides: [ - ...context.selected({owned()}), - inLockedViewProvider.overrideWithValue(true), - ], - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden when none of the selected assets are owned', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: ArchiveAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/asset_debug_action_test.dart b/mobile/test/unit/presentation/actions/asset_debug_action_test.dart deleted file mode 100644 index f545075809..0000000000 --- a/mobile/test/unit/presentation/actions/asset_debug_action_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/asset_debug.action.dart'; -import 'package:immich_ui/immich_ui.dart'; - -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - - setUp(() async { - context = await PresentationContext.create(); - await StoreService.I.put(StoreKey.advancedTroubleshooting, true); - }); - - tearDown(() async { - await context.dispose(); - }); - - group('AssetDebugAction', () { - testWidgets('visible for a single asset when advanced troubleshooting is on', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: AssetDebugAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('hidden for multiple assets', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: AssetDebugAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create(), RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('hidden when advanced troubleshooting is off', (tester) async { - await StoreService.I.put(StoreKey.advancedTroubleshooting, false); - - await tester.pumpTestWidget( - context, - const ActionIconButton(action: AssetDebugAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/cast_action_test.dart b/mobile/test/unit/presentation/actions/cast_action_test.dart deleted file mode 100644 index 7c16ea398e..0000000000 --- a/mobile/test/unit/presentation/actions/cast_action_test.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/cast.action.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - - setUp(() async { - context = await PresentationContext.create(); - }); - - tearDown(() async { - await context.dispose(); - }); - - void Function(bool) captureConnectionListener() => - verify(() => context.service.cast.onConnectionState = captureAny()).captured.single as void Function(bool); - - group('CastAction', () { - testWidgets('offers to cast when nothing is connected', (tester) async { - await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction())); - - expect(find.byIcon(Icons.cast_rounded), findsOneWidget); - }); - - testWidgets('switches to the connected icon once casting starts', (tester) async { - await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction())); - - captureConnectionListener()(true); - await tester.pump(); - - expect(find.byIcon(Icons.cast_connected_rounded), findsOneWidget); - expect(find.byIcon(Icons.cast_rounded), findsNothing); - }); - - testWidgets('switches back when casting stops', (tester) async { - await tester.pumpTestWidget(context, const ActionIconButton(action: CastAction())); - - final onConnectionState = captureConnectionListener(); - onConnectionState(true); - await tester.pump(); - onConnectionState(false); - await tester.pump(); - - expect(find.byIcon(Icons.cast_rounded), findsOneWidget); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/delete_action_test.dart b/mobile/test/unit/presentation/actions/delete_action_test.dart deleted file mode 100644 index b68e2096da..0000000000 --- a/mobile/test/unit/presentation/actions/delete_action_test.dart +++ /dev/null @@ -1,288 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/delete.action.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/local_asset_factory.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - late MockCleanupService cleanupService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - cleanupService = context.service.cleanup.service; - }); - - tearDown(() async { - debugDefaultTargetPlatformOverride = null; - await StoreService.I.put(StoreKey.manageLocalMediaAndroid, false); - await context.dispose(); - }); - - RemoteAsset owned({AssetVisibility visibility = .timeline, DateTime? deletedAt, String? localId}) => - RemoteAssetFactory.create( - ownerId: context.currentUser.id, - visibility: visibility, - deletedAt: deletedAt, - localId: localId, - ); - - Future pumpDelete(WidgetTester tester, Set selection, {bool trashEnabled = true}) async { - if (!trashEnabled) { - when( - () => context.service.serverInfo.getServerFeatures(), - ).thenAnswer((_) async => const .new(trash: false, map: true, oauthEnabled: false, passwordLogin: true)); - } - - await tester.pumpTestWidget( - context, - const ActionIconButton(action: DeleteAction(source: .timeline)), - overrides: context.selected(selection), - ); - - if (!trashEnabled) { - final scope = ProviderScope.containerOf(tester.element(find.byType(ActionIconButton)), listen: false); - await scope.read(serverInfoProvider.notifier).getServerFeatures(); - await tester.pumpAndSettle(); - } - - await tester.tap(find.byType(ImmichIconButton)); - await tester.pump(); - } - - Future respondToDialog(WidgetTester tester, {required bool confirm}) async { - await tester.pump(const Duration(milliseconds: 300)); - expect(find.byType(ConfirmDialog), findsOneWidget); - await tester.tap(find.byType(TextButton).at(confirm ? 1 : 0)); // [cancel, ok] - await tester.pumpAndSettle(); - } - - group('DeleteAction', () { - group('trash', () { - testWidgets('trashes a remote-only owned asset', (tester) async { - final asset = owned(); - - await pumpDelete(tester, {asset}); - await tester.pumpAndSettle(); - - verify(() => assetService.trash([asset.id])).called(1); - verifyNever(() => assetService.delete(any())); - verifyNever(() => cleanupService.deleteLocalAssets(any())); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpDelete(tester, {mine, theirs}); - await tester.pumpAndSettle(); - - verify(() => assetService.trash([mine.id])).called(1); - }); - - testWidgets('trashes a merged asset and removes its device copy', (tester) async { - final asset = owned(localId: 'local'); - - await pumpDelete(tester, {asset}); - await tester.pumpAndSettle(); - - verify(() => cleanupService.deleteLocalAssets(['local'])).called(1); - verify(() => assetService.trash([asset.id])).called(1); - }); - - testWidgets('offers an undo that restores the trashed assets', (tester) async { - final asset = owned(); - - await pumpDelete(tester, {asset}); - await tester.pumpAndSettle(); - await tester.tap(find.text('Undo')); - await tester.pump(); - - verify(() => assetService.restoreTrash([asset.id])).called(1); - }); - }); - - group('permanent', () { - testWidgets('permanently deletes when the trash feature is disabled', (tester) async { - final asset = owned(); - - await pumpDelete(tester, {asset}, trashEnabled: false); - await respondToDialog(tester, confirm: true); - - verify(() => assetService.delete([asset.id])).called(1); - verifyNever(() => assetService.trash(any())); - }); - - testWidgets('offers no undo for a permanent delete', (tester) async { - await pumpDelete(tester, {owned()}, trashEnabled: false); - await respondToDialog(tester, confirm: true); - await tester.pumpAndSettle(); - - expect(find.byType(SnackBar), findsOneWidget); - expect(find.text('Undo'), findsNothing); - }); - - testWidgets('permanently deletes a merged asset and removes its device copy', (tester) async { - final asset = owned(localId: 'local'); - - await pumpDelete(tester, {asset}, trashEnabled: false); - await respondToDialog(tester, confirm: true); - - verify(() => assetService.delete([asset.id])).called(1); - verify(() => cleanupService.deleteLocalAssets(['local'])).called(1); - }); - - testWidgets('permanently deletes already trashed assets even with trash enabled', (tester) async { - final asset = owned(deletedAt: DateTime(2024)); - - await pumpDelete(tester, {asset}); - await respondToDialog(tester, confirm: true); - - verify(() => assetService.delete([asset.id])).called(1); - verifyNever(() => assetService.trash(any())); - }); - - testWidgets('permanently deletes locked folder assets even with trash enabled', (tester) async { - final asset = owned(visibility: .locked, localId: 'local'); - - await pumpDelete(tester, {asset}); - await respondToDialog(tester, confirm: true); - - verify(() => assetService.delete([asset.id])).called(1); - verify(() => cleanupService.deleteLocalAssets(['local'])).called(1); - }); - - testWidgets('does nothing when the confirmation is cancelled', (tester) async { - final asset = owned(visibility: .locked, localId: 'local'); - - await pumpDelete(tester, {asset}); - await respondToDialog(tester, confirm: false); - - verifyNever(() => assetService.delete(any())); - verifyNever(() => cleanupService.deleteLocalAssets(any())); - }); - }); - - group('local only', () { - testWidgets('removes the device copy with no remote call', (tester) async { - final asset = LocalAssetFactory.create(); - - await pumpDelete(tester, {asset}); - await tester.pumpAndSettle(); - - verify(() => cleanupService.deleteLocalAssets([asset.id])).called(1); - verifyNever(() => assetService.trash(any())); - verifyNever(() => assetService.delete(any())); - }); - - testWidgets('is labelled trash', (tester) async { - await tester.pumpTestWidget( - context, - const ActionButton(action: DeleteAction(source: .timeline)), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.text(StaticTranslations.instance.trash), findsOneWidget); - expect(find.text(StaticTranslations.instance.delete), findsNothing); - }); - }); - - group('prompt handling', () { - testWidgets('permanent delete shows a single app dialog', (tester) async { - final asset = owned(localId: 'local'); - - await pumpDelete(tester, {asset}, trashEnabled: false); - await tester.pump(const Duration(milliseconds: 300)); - - expect(find.text(StaticTranslations.instance.delete_dialog_title), findsOneWidget); - await tester.tap(find.byType(TextButton).at(1)); - await tester.pumpAndSettle(); - - expect(find.text(StaticTranslations.instance.move_to_device_trash), findsNothing); - verify(() => assetService.delete([asset.id])).called(1); - verify(() => cleanupService.deleteLocalAssets(['local'])).called(1); - }); - - testWidgets('local only delete on Android with MANAGE_MEDIA shows the prompt', (tester) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - await StoreService.I.put(StoreKey.manageLocalMediaAndroid, true); - final asset = LocalAssetFactory.create(); - - await pumpDelete(tester, {asset}); - await tester.pump(const Duration(milliseconds: 300)); - - expect(find.text(StaticTranslations.instance.move_to_device_trash), findsOneWidget); - await tester.tap(find.byType(TextButton).at(1)); // confirm - await tester.pumpAndSettle(); - - verify(() => cleanupService.deleteLocalAssets([asset.id])).called(1); - // Has to be cleared inside the body; the framework asserts on it before tearDown runs. - debugDefaultTargetPlatformOverride = null; - }); - - testWidgets('local only delete on Android with MANAGE_MEDIA deletes nothing when cancelled', (tester) async { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - await StoreService.I.put(StoreKey.manageLocalMediaAndroid, true); - final asset = LocalAssetFactory.create(); - - await pumpDelete(tester, {asset}); - await respondToDialog(tester, confirm: false); - - verifyNever(() => cleanupService.deleteLocalAssets(any())); - debugDefaultTargetPlatformOverride = null; - }); - }); - - testWidgets('is hidden when nothing can be deleted', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: DeleteAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); - - group('CleanupLocalAction', () { - testWidgets('deletes only backed up device copies', (tester) async { - final backedUp = LocalAssetFactory.create(remoteId: 'remote'); - final localOnly = LocalAssetFactory.create(); - - await tester.pumpTestAction( - context, - const CleanupLocalAction(source: .timeline), - overrides: context.selected({backedUp, localOnly}), - ); - await tester.pumpAndSettle(); - - verify(() => cleanupService.deleteLocalAssets([backedUp.id])).called(1); - }); - - testWidgets('is hidden when no backed up assets are selected', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: CleanupLocalAction(source: .timeline)), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/download_tag_action_test.dart b/mobile/test/unit/presentation/actions/download_tag_action_test.dart deleted file mode 100644 index de003ffed5..0000000000 --- a/mobile/test/unit/presentation/actions/download_tag_action_test.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/tag.model.dart'; -import 'package:immich_mobile/domain/services/tag.service.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/download.action.dart'; -import 'package:immich_mobile/presentation/actions/tag.action.dart'; -import 'package:immich_mobile/providers/background_sync.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user_metadata.provider.dart'; -import 'package:immich_mobile/repositories/download.repository.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../repository.mocks.dart'; -import '../../factories/local_asset_factory.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockTagService tagService; - - setUp(() async { - context = await PresentationContext.create(); - tagService = context.service.tag.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned() => RemoteAssetFactory.create(ownerId: context.currentUser.id); - - group('DownloadAction', () { - Future pumpDownload(WidgetTester tester, Set selection) => tester.pumpTestAction( - context, - const DownloadAction(source: .timeline), - overrides: [ - ...context.selected(selection), - downloadRepositoryProvider.overrideWithValue(context.repository.download.repo), - backgroundSyncProvider.overrideWithValue(context.service.backgroundSync), - ], - ); - - Future settleDownload(WidgetTester tester) async { - await tester.pump(); - await tester.pump(const .new(seconds: 1)); - await tester.pumpAndSettle(); - } - - testWidgets('downloads every selected remote asset', (tester) async { - final asset = owned(); - - await pumpDownload(tester, {asset}); - await settleDownload(tester); - - verify(() => context.repository.download.repo.downloadAllAssets([asset])).called(1); - }); - - testWidgets('ignores local-only assets, which are already on the device', (tester) async { - final remote = owned(); - - await pumpDownload(tester, {remote, LocalAssetFactory.create()}); - await settleDownload(tester); - - verify(() => context.repository.download.repo.downloadAllAssets([remote])).called(1); - }); - - testWidgets('is hidden when nothing remote is selected', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: DownloadAction(source: .timeline)), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); - - group('TagAction', () { - late BuildContext actionContext; - late WidgetRef actionRef; - - Future pumpTag(WidgetTester tester, Set selection) => tester.pumpTestWidget( - context, - Consumer( - builder: (widgetContext, ref, _) { - actionContext = widgetContext; - actionRef = ref; - return const ActionIconButton(action: TagAction(source: .timeline)); - }, - ), - overrides: [ - ...context.selected(selection), - toastServiceProvider.overrideWithValue(context.service.toast), - tagServiceProvider.overrideWithValue(tagService), - userMetadataPreferencesProvider.overrideWith((ref) async => const .new(tagsEnabled: true)), - ], - ); - - Future applyTags(List assetIds, {Set selected = const {}, Set created = const {}}) => - tagAssets(actionContext, actionRef, assetIds, selected: selected, created: created); - - testWidgets('offers tagging for an owned asset', (tester) async { - await pumpTag(tester, {owned()}); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('is hidden without any owned asset', (tester) async { - await pumpTag(tester, {RemoteAssetFactory.create()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('applies the picked tags and reports the count', (tester) async { - final asset = owned(); - when(() => tagService.bulkTagAssets(any(), any())).thenAnswer((_) async => 1); - - await pumpTag(tester, {asset}); - await applyTags([asset.id], selected: {'tag-1'}); - await tester.pumpAndSettle(); - - verify(() => tagService.bulkTagAssets([asset.id], ['tag-1'])).called(1); - - final message = verify(() => context.service.toast.success(captureAny())).captured.single as String; - expect(message, StaticTranslations.instance.tagged_assets(count: 1)); - }); - - testWidgets('creates new tags first and applies them alongside the picked ones', (tester) async { - final asset = owned(); - when(() => tagService.upsertTags(any())).thenAnswer((_) async => [const Tag(id: 'made-1', value: 'brand new')]); - when(() => tagService.bulkTagAssets(any(), any())).thenAnswer((_) async => 1); - - await pumpTag(tester, {asset}); - await applyTags([asset.id], selected: {'tag-1'}, created: {'brand new'}); - await tester.pumpAndSettle(); - - verify(() => tagService.upsertTags(['brand new'])).called(1); - final tagIds = verify(() => tagService.bulkTagAssets([asset.id], captureAny())).captured.single as List; - expect(tagIds, containsAll(['tag-1', 'made-1'])); - }); - - testWidgets('does nothing when no tag was chosen or created', (tester) async { - final asset = owned(); - - await pumpTag(tester, {asset}); - await applyTags([asset.id]); - await tester.pumpAndSettle(); - - verifyNever(() => tagService.bulkTagAssets(any(), any())); - verifyNever(() => context.service.toast.success(any())); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/edit_action_test.dart b/mobile/test/unit/presentation/actions/edit_action_test.dart deleted file mode 100644 index 9e8dcfb4b2..0000000000 --- a/mobile/test/unit/presentation/actions/edit_action_test.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/models/server_info/server_version.model.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/edit_asset.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_datetime.action.dart'; -import 'package:immich_mobile/presentation/actions/edit_location.action.dart'; -import 'package:immich_mobile/providers/server_info.provider.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:maplibre_gl/maplibre_gl.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../riverpod_mocks.dart'; -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({AssetType type = .image}) => - RemoteAssetFactory.create(ownerId: context.currentUser.id, type: type); - - const supportedVersion = ServerVersion(major: 2, minor: 6, patch: 0); - - List reportedVersion(ServerVersion version) => [ - serverInfoProvider.overrideWith((ref) => StubServerInfoNotifier(context.service.serverInfo, version: version)), - ]; - - late BuildContext actionContext; - late WidgetRef actionRef; - - Future pumpAction( - WidgetTester tester, - ActionBuilder action, - Set selection, { - List overrides = const [], - }) => tester.pumpTestWidget( - context, - Consumer( - builder: (widgetContext, ref, _) { - actionContext = widgetContext; - actionRef = ref; - return ActionIconButton(action: action); - }, - ), - overrides: [...context.selected(selection), ...overrides], - ); - - group('EditAssetAction', () { - Future pumpEditAsset( - WidgetTester tester, - Set selection, { - ServerVersion version = supportedVersion, - }) => pumpAction( - tester, - const EditAssetAction(source: .timeline), - selection, - overrides: [...reportedVersion(version)], - ); - - testWidgets('offers to edit a single owned editable asset', (tester) async { - await pumpEditAsset(tester, {owned()}); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('is hidden when the server predates edit sync', (tester) async { - await pumpEditAsset(tester, {owned()}, version: const ServerVersion(major: 2, minor: 5, patch: 9)); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden for more than one asset', (tester) async { - await pumpEditAsset(tester, {owned(), owned()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden for an asset owned by someone else', (tester) async { - await pumpEditAsset(tester, {RemoteAssetFactory.create()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden for a non-editable asset', (tester) async { - await pumpEditAsset(tester, {owned(type: .video)}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('reads the edits and exif for the asset it is about to open', (tester) async { - final asset = owned(); - - await pumpEditAsset(tester, {asset}); - await tester.tap(find.byType(ImmichIconButton)); - await tester.pump(); - - verify(() => context.repository.remoteAsset.repo.getAssetEdits(asset.id)).called(1); - verify(() => context.repository.remoteAsset.repo.getExif(asset.id)).called(1); - }); - }); - - group('EditLocationAction', () { - testWidgets('offers to edit an owned remote asset', (tester) async { - await pumpAction(tester, const EditLocationAction(source: .timeline), {owned()}); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('is hidden without any owned remote asset', (tester) async { - await pumpAction(tester, const EditLocationAction(source: .timeline), {RemoteAssetFactory.create()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('saves the location against every owned asset and toasts the count', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpAction(tester, const EditLocationAction(source: .timeline), {mine, theirs}); - await saveLocation(actionContext, actionRef, [mine.id], const LatLng(1, 2)); - await tester.pumpAndSettle(); - - final location = - verify(() => assetService.update([mine.id], location: captureAny(named: 'location'))).captured.single - as Option; - expect(location.unwrapOrNull?.latitude, 1); - expect(location.unwrapOrNull?.longitude, 2); - }); - }); - - group('EditDateTimeAction', () { - testWidgets('offers to edit an owned remote asset', (tester) async { - await pumpAction(tester, const EditDateTimeAction(source: .timeline), {owned()}); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('is hidden without any owned remote asset', (tester) async { - await pumpAction(tester, const EditDateTimeAction(source: .timeline), {RemoteAssetFactory.create()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('saves the date against every owned asset and toasts the count', (tester) async { - const picked = '2026-06-10T19:15:00.000+06:00'; - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpAction(tester, const EditDateTimeAction(source: .timeline), {mine, theirs}); - await saveDateTime(actionContext, actionRef, [mine.id], picked); - await tester.pumpAndSettle(); - - verify(() => assetService.update([mine.id], dateTime: const Some(picked))).called(1); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/favorite_action_test.dart b/mobile/test/unit/presentation/actions/favorite_action_test.dart deleted file mode 100644 index 4d61965b1f..0000000000 --- a/mobile/test/unit/presentation/actions/favorite_action_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/favorite.action.dart'; -import 'package:immich_mobile/utils/option.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({bool isFavorite = false}) => - RemoteAssetFactory.create(ownerId: context.currentUser.id, isFavorite: isFavorite); - - Future pumpFavorite(WidgetTester tester, Set selection) => - tester.pumpTestAction(context, const FavoriteAction(source: .timeline), overrides: context.selected(selection)); - - group('FavoriteAction', () { - testWidgets('favorites the eligible owned assets', (tester) async { - final asset = owned(); - - await pumpFavorite(tester, {asset}); - - verify(() => assetService.update([asset.id], isFavorite: const Option.some(true))).called(1); - }); - - testWidgets('unfavorite the eligible owned assets', (tester) async { - final asset = owned(isFavorite: true); - - await pumpFavorite(tester, {asset}); - - verify(() => assetService.update([asset.id], isFavorite: const Option.some(false))).called(1); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpFavorite(tester, {mine, theirs}); - - verify(() => assetService.update([mine.id], isFavorite: const Option.some(true))).called(1); - }); - - testWidgets('skips owned assets already in the target state', (tester) async { - final stale = owned(); - final alreadyFavorite = owned(isFavorite: true); - - await pumpFavorite(tester, {stale, alreadyFavorite}); - - verify(() => assetService.update([stale.id], isFavorite: const Option.some(true))).called(1); - }); - - testWidgets('shows a confirmation snackbar on success', (tester) async { - await pumpFavorite(tester, {owned()}); - await tester.pumpUntilFound(find.byType(SnackBar)); - - expect(find.byType(SnackBar), findsOneWidget); - }); - - testWidgets('clears the selection once the update succeeds', (tester) async { - await pumpFavorite(tester, {owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(ImmichIconButton), findsNothing, reason: 'an empty selection hides the action'); - }); - - testWidgets('is hidden when none of the selected assets are owned', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: FavoriteAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/lock_action_test.dart b/mobile/test/unit/presentation/actions/lock_action_test.dart deleted file mode 100644 index eeb7a752ed..0000000000 --- a/mobile/test/unit/presentation/actions/lock_action_test.dart +++ /dev/null @@ -1,163 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/lock.action.dart'; -import 'package:immich_mobile/widgets/common/confirm_dialog.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({AssetVisibility visibility = .timeline}) => - RemoteAssetFactory.create(ownerId: context.currentUser.id, visibility: visibility); - - Future pumpLock(WidgetTester tester, Set selection) => - tester.pumpTestAction(context, const LockAction(source: .timeline), overrides: context.selected(selection)); - - Future respondToDialog(WidgetTester tester, {required bool confirm}) async { - await tester.pumpUntilFound(find.byType(ConfirmDialog)); - expect(find.text(StaticTranslations.instance.move_to_locked_folder), findsOneWidget); - await tester.tap(find.text(confirm ? StaticTranslations.instance.confirm : StaticTranslations.instance.cancel)); - await tester.pumpAndSettle(); - } - - group('LockAction', () { - testWidgets('locks the eligible owned assets', (tester) async { - final asset = owned(); - - await pumpLock(tester, {asset}); - - verify(() => assetService.update([asset.id], visibility: const .some(.locked))).called(1); - }); - - testWidgets('unlocks the eligible owned assets', (tester) async { - final asset = owned(visibility: .locked); - - await pumpLock(tester, {asset}); - - verify(() => assetService.update([asset.id], visibility: const .some(.timeline))).called(1); - }); - - testWidgets('prioritizes lock when mixed state', (tester) async { - final unlocked = owned(); - final locked = owned(visibility: .locked); - - await pumpLock(tester, {unlocked, locked}); - - verify(() => assetService.update([unlocked.id], visibility: const .some(.locked))).called(1); - verifyNever(() => assetService.update(any(), visibility: const .some(.timeline))); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpLock(tester, {mine, theirs}); - - verify(() => assetService.update([mine.id], visibility: const .some(.locked))).called(1); - }); - - testWidgets('locks only the owned assets not already locked', (tester) async { - final stale = owned(); - final alreadyLocked = owned(visibility: .locked); - - await pumpLock(tester, {stale, alreadyLocked}); - - verify(() => assetService.update([stale.id], visibility: const .some(.locked))).called(1); - }); - - testWidgets('removes the local copies of the assets it locks', (tester) async { - final merged = RemoteAssetFactory.create(ownerId: context.currentUser.id, localId: 'local-1'); - final remoteOnly = owned(); - - await pumpLock(tester, {merged, remoteOnly}); - await respondToDialog(tester, confirm: true); - - verify(() => assetService.deleteLocal(['local-1'], trash: false)).called(1); - }); - - testWidgets('does nothing when the warning is cancelled', (tester) async { - final merged = RemoteAssetFactory.create(ownerId: context.currentUser.id, localId: 'local-1'); - - await pumpLock(tester, {merged}); - await respondToDialog(tester, confirm: false); - - verifyNever(() => assetService.update(any(), visibility: any(named: 'visibility'))); - verifyNever(() => assetService.deleteLocal(any(), trash: any(named: 'trash'))); - }); - - testWidgets('locks remote-only assets without a warning', (tester) async { - final remoteOnly = owned(); - - await pumpLock(tester, {remoteOnly}); - - expect(find.byType(ConfirmDialog), findsNothing); - verify(() => assetService.update([remoteOnly.id], visibility: const .some(.locked))).called(1); - }); - - testWidgets('leaves the local copies alone when unlocking', (tester) async { - final merged = RemoteAssetFactory.create( - ownerId: context.currentUser.id, - localId: 'local-1', - visibility: .locked, - ); - - await pumpLock(tester, {merged}); - - verifyNever(() => assetService.deleteLocal(any())); - }); - - testWidgets('offers an undo that locks the unlocked assets again', (tester) async { - final asset = owned(visibility: .locked); - - await pumpLock(tester, {asset}); - await tester.pumpAndSettle(); - await tester.tap(find.text('Undo')); - await tester.pump(); - - verify(() => assetService.update([asset.id], visibility: const .some(.locked))).called(1); - }); - - testWidgets('offers no undo for locking', (tester) async { - await pumpLock(tester, {owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(SnackBar), findsOneWidget); - expect(find.text('Undo'), findsNothing); - }); - - testWidgets('clears the selection once the update succeeds', (tester) async { - await pumpLock(tester, {owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(ImmichIconButton), findsNothing, reason: 'an empty selection hides the action'); - }); - - testWidgets('is hidden when none of the selected assets are owned', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: LockAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/open_in_browser_action_test.dart b/mobile/test/unit/presentation/actions/open_in_browser_action_test.dart deleted file mode 100644 index edd9ec4a7c..0000000000 --- a/mobile/test/unit/presentation/actions/open_in_browser_action_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/open_in_browser.action.dart'; -import 'package:immich_ui/immich_ui.dart'; - -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - - setUp(() async { - context = await PresentationContext.create(); - }); - - tearDown(() async { - await context.dispose(); - }); - - group('webPathFor', () { - const dedicatedPages = { - TimelineOrigin.favorite: '/favorites', - TimelineOrigin.trash: '/trash', - TimelineOrigin.archive: '/archive', - }; - - for (final origin in TimelineOrigin.values) { - final expected = dedicatedPages[origin] ?? ''; - - test('opens ${origin.name} on ${expected.isEmpty ? 'the main timeline' : expected}', () { - expect(webPathFor(origin), expected); - }); - } - }); - - group('OpenInBrowserAction', () { - testWidgets('always renders, since the kebab menu decides whether to offer it', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton( - action: OpenInBrowserAction(remoteId: 'remote-1', origin: .main), - ), - ); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/partner_action_test.dart b/mobile/test/unit/presentation/actions/partner_action_test.dart deleted file mode 100644 index 786d2d7f60..0000000000 --- a/mobile/test/unit/presentation/actions/partner_action_test.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/presentation/actions/partner.action.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/user_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockPartnerService partnerService; - - setUp(() async { - context = await PresentationContext.create(); - partnerService = context.service.partner.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - List overrides({List candidates = const []}) => [ - candidatesStateProvider.overrideWith((ref) => Stream>.value(candidates)), - ]; - - group('PartnerAddAction', () { - testWidgets('creates a partner for the selected candidate', (tester) async { - final candidate = UserFactory.create(); - - await tester.pumpTestAction(context, const PartnerAddAction(), overrides: overrides(candidates: [candidate])); - await tester.pumpUntilFound(find.text(candidate.name)); - await tester.tap(find.text(candidate.name)); - await tester.pumpAndSettle(); - - verify(() => partnerService.create(sharedById: context.currentUser.id, sharedWithId: candidate.id)).called(1); - }); - - testWidgets('creates nothing when the selection dialog is dismissed', (tester) async { - await tester.pumpTestAction( - context, - const PartnerAddAction(), - overrides: overrides(candidates: [UserFactory.create()]), - ); - await tester.sendKeyEvent(LogicalKeyboardKey.escape); // dismiss without selecting - await tester.pumpAndSettle(); - - verifyNever(context.service.partner.create); - }); - }); - - group('PartnerRemoveAction', () { - testWidgets('deletes the partner after confirmation', (tester) async { - final partner = UserFactory.create(); - await tester.pumpTestAction( - context, - PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), - overrides: overrides(), - ); - await tester.tap(find.byType(TextButton).last); // confirm - await tester.pumpAndSettle(); - - verify(() => partnerService.delete(sharedById: context.currentUser.id, sharedWithId: partner.id)).called(1); - }); - - testWidgets('deletes nothing when the confirmation is cancelled', (tester) async { - final partner = UserFactory.create(); - await tester.pumpTestAction( - context, - PartnerRemoveAction(sharedWithId: partner.id, partnerName: partner.name), - overrides: overrides(), - ); - await tester.tap(find.byType(TextButton).first); // cancel - await tester.pumpAndSettle(); - - verifyNever(context.service.partner.delete); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/restore_action_test.dart b/mobile/test/unit/presentation/actions/restore_action_test.dart deleted file mode 100644 index 5c11d20403..0000000000 --- a/mobile/test/unit/presentation/actions/restore_action_test.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/restore.action.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({bool trashed = true}) => - RemoteAssetFactory.create(ownerId: context.currentUser.id, deletedAt: trashed ? .new(2020) : null); - - Future pumpRestore(WidgetTester tester, Set selection) => - tester.pumpTestAction(context, const RestoreAction(source: .timeline), overrides: context.selected(selection)); - - group('RestoreAction', () { - testWidgets('restores the eligible owned trashed assets', (tester) async { - final asset = owned(); - - await pumpRestore(tester, {asset}); - - verify(() => assetService.restoreTrash([asset.id])).called(1); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final theirs = RemoteAssetFactory.create(deletedAt: .new(2020)); - - await pumpRestore(tester, {mine, theirs}); - - verify(() => assetService.restoreTrash([mine.id])).called(1); - }); - - testWidgets('skips owned assets that are not trashed', (tester) async { - final trashed = owned(); - final live = owned(trashed: false); - - await pumpRestore(tester, {trashed, live}); - - verify(() => assetService.restoreTrash([trashed.id])).called(1); - }); - - testWidgets('clears the selection once the restore succeeds', (tester) async { - await pumpRestore(tester, {owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(ImmichIconButton), findsNothing, reason: 'an empty selection hides the action'); - }); - - testWidgets('offers an undo that puts the assets back in the trash', (tester) async { - final asset = owned(); - - await pumpRestore(tester, {asset}); - await tester.pumpAndSettle(); - await tester.tap(find.text('Undo')); - await tester.pump(); - - verify(() => assetService.trash([asset.id])).called(1); - }); - - testWidgets('is hidden when no owned asset is trashed', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: RestoreAction(source: .timeline)), - overrides: context.selected({owned(trashed: false)}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/share_action_test.dart b/mobile/test/unit/presentation/actions/share_action_test.dart deleted file mode 100644 index 20cc25b73b..0000000000 --- a/mobile/test/unit/presentation/actions/share_action_test.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/share.action.dart'; -import 'package:immich_mobile/presentation/actions/share_link.action.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../factories/local_asset_factory.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - - setUp(() async { - context = await PresentationContext.create(); - }); - - tearDown(() async { - await context.dispose(); - }); - - late BuildContext actionContext; - late WidgetRef actionRef; - - Future pumpShare(WidgetTester tester, {Set? selection}) => tester.pumpTestWidget( - context, - Consumer( - builder: (widgetContext, ref, _) { - actionContext = widgetContext; - actionRef = ref; - return const ActionIconButton(action: ShareAction(source: .timeline)); - }, - ), - overrides: [ - ...context.selected(selection ?? {RemoteAssetFactory.create(ownerId: context.currentUser.id)}), - ], - ); - - // TODO: Replace with button tap once long press support in ui is merged - Future invokeSecondaryAction() async { - final resolved = const ShareAction(source: .timeline).create(actionContext, actionRef); - await resolved!.onSecondaryAction!(); - } - - List sharedFileTypes() => verify( - () => context.repository.assetMedia.api.shareAssets( - any(), - any(), - fileType: captureAny(named: 'fileType'), - cancelCompleter: any(named: 'cancelCompleter'), - onAssetDownloadProgress: any(named: 'onAssetDownloadProgress'), - ), - ).captured.cast(); - - Future settle(WidgetTester tester) async { - await tester.pump(); - await tester.pump(const .new(milliseconds: 300)); - } - - Future pickPreviewQuality(WidgetTester tester) async { - final shared = invokeSecondaryAction(); - await settle(tester); - await tester.tap(find.byIcon(Icons.photo_size_select_large_rounded)); - await settle(tester); - await shared; - } - - group('ShareAction', () { - testWidgets('single press shares with the configured default quality', (tester) async { - await pumpShare(tester); - - await tester.tap(find.byType(ImmichIconButton)); - await settle(tester); - - expect(sharedFileTypes(), [ShareAssetType.original]); - }); - - testWidgets('the secondary action shares with the quality picked in the dialog', (tester) async { - await pumpShare(tester); - - await pickPreviewQuality(tester); - - expect(sharedFileTypes(), [ShareAssetType.preview]); - }); - - testWidgets('quality picked there is a one-time choice and does not change the default', (tester) async { - await pumpShare(tester); - - await pickPreviewQuality(tester); - await tester.tap(find.byType(ImmichIconButton)); - await settle(tester); - - expect(sharedFileTypes(), [ShareAssetType.preview, ShareAssetType.original]); - expect(SettingsRepository.instance.appConfig.share.fileType, ShareAssetType.original); - }); - - testWidgets('offers no preview option for a video, which has none to share', (tester) async { - await pumpShare( - tester, - selection: {RemoteAssetFactory.create(ownerId: context.currentUser.id, type: .video)}, - ); - - final shared = invokeSecondaryAction(); - await settle(tester); - - expect(find.byIcon(Icons.high_quality_rounded), findsOneWidget); - expect(find.byIcon(Icons.photo_size_select_large_rounded), findsNothing); - - await tester.tap(find.byIcon(Icons.high_quality_rounded)); - await settle(tester); - await shared; - expect(find.byIcon(Icons.photo_size_select_large_rounded), findsNothing); - }); - - testWidgets('is hidden when nothing is selected', (tester) async { - await pumpShare(tester, selection: const {}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); - - group('ShareLinkAction', () { - testWidgets('offers a link for a remote asset', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: ShareLinkAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsOneWidget); - }); - - testWidgets('is hidden for a local-only asset', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: ShareLinkAction(source: .timeline)), - overrides: context.selected({LocalAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/stack_action_test.dart b/mobile/test/unit/presentation/actions/stack_action_test.dart deleted file mode 100644 index 11321c28af..0000000000 --- a/mobile/test/unit/presentation/actions/stack_action_test.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/stack.action.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockAssetService assetService; - - setUp(() async { - context = await PresentationContext.create(); - assetService = context.service.asset.service; - }); - - tearDown(() async { - await context.dispose(); - }); - - RemoteAsset owned({String? stackId}) => RemoteAssetFactory.create(ownerId: context.currentUser.id, stackId: stackId); - - Future pumpStack(WidgetTester tester, Set selection) => - tester.pumpTestAction(context, const StackAction(source: .timeline), overrides: context.selected(selection)); - - group('StackAction', () { - testWidgets('stacks the eligible owned assets', (tester) async { - final first = owned(); - final second = owned(); - - await pumpStack(tester, {first, second}); - - verify(() => assetService.stack(context.currentUser.id, [first.id, second.id])).called(1); - }); - - testWidgets('unstacks the eligible owned assets', (tester) async { - final asset = owned(stackId: 'stack'); - - await pumpStack(tester, {asset}); - - verify(() => assetService.unstack(['stack'])).called(1); - }); - - testWidgets('prioritizes stack when mixed state', (tester) async { - final first = owned(); - final second = owned(stackId: 'stack'); - - await pumpStack(tester, {first, second}); - - verify(() => assetService.stack(context.currentUser.id, [first.id, second.id])).called(1); - }); - - testWidgets('ignores assets owned by someone else', (tester) async { - final mine = owned(); - final other = owned(); - final theirs = RemoteAssetFactory.create(); - - await pumpStack(tester, {mine, other, theirs}); - - verify(() => assetService.stack(context.currentUser.id, [mine.id, other.id])).called(1); - }); - - testWidgets('clears the selection once the stack succeeds', (tester) async { - await pumpStack(tester, {owned(), owned()}); - await tester.pumpAndSettle(); - - expect(find.byType(ImmichIconButton), findsNothing, reason: 'an empty selection hides the action'); - }); - - testWidgets('is hidden when a unstacked asset has nothing to stack onto', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: StackAction(source: .timeline)), - overrides: context.selected({owned()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden when none of the selected assets are owned', (tester) async { - await tester.pumpTestWidget( - context, - const ActionIconButton(action: StackAction(source: .timeline)), - overrides: context.selected({RemoteAssetFactory.create(), RemoteAssetFactory.create()}), - ); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); -} diff --git a/mobile/test/unit/presentation/actions/upload_action_test.dart b/mobile/test/unit/presentation/actions/upload_action_test.dart deleted file mode 100644 index 8aeb7a915f..0000000000 --- a/mobile/test/unit/presentation/actions/upload_action_test.dart +++ /dev/null @@ -1,213 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/generated/translations.g.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/upload.action.dart'; -import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/toast.provider.dart'; -import 'package:immich_mobile/services/foreground_upload.service.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../../service.mocks.dart'; -import '../../factories/local_asset_factory.dart'; -import '../../factories/remote_asset_factory.dart'; -import '../presentation_context.dart'; - -void main() { - late PresentationContext context; - late MockForegroundUploadService uploadService; - - setUp(() async { - context = await PresentationContext.create(); - uploadService = context.service.upload; - }); - - tearDown(() async { - await context.dispose(); - }); - - List uploadOverrides() => [ - foregroundUploadServiceProvider.overrideWithValue(uploadService), - toastServiceProvider.overrideWithValue(context.service.toast), - ]; - - Future pumpUpload(WidgetTester tester, Set selection, {bool showProgress = false}) => - tester.pumpTestWidget( - context, - ActionIconButton( - action: UploadAction(source: .timeline, showProgress: showProgress), - ), - overrides: [...context.selected(selection), ...uploadOverrides()], - ); - - Future settleUpload(WidgetTester tester) async { - await tester.pump(); - await tester.pump(const .new(seconds: 2)); - await tester.pumpAndSettle(); - } - - void answerUploadWith({Set succeeded = const {}, Set failed = const {}, Completer? until}) { - when( - () => uploadService.uploadManual( - any(), - cancelToken: any(named: 'cancelToken'), - callbacks: any(named: 'callbacks'), - ), - ).thenAnswer((invocation) async { - if (until != null) { - await until.future; - } - final callbacks = invocation.namedArguments[#callbacks] as UploadCallbacks; - for (final id in succeeded) { - callbacks.onSuccess?.call(id, id); - } - for (final id in failed) { - callbacks.onError?.call(id, 'boom'); - } - }); - } - - group('UploadAction', () { - testWidgets('uploads the selected local assets', (tester) async { - final asset = LocalAssetFactory.create(); - answerUploadWith(succeeded: {asset.id}); - - await pumpUpload(tester, {asset}); - await tester.tap(find.byType(ImmichIconButton)); - await settleUpload(tester); - - final uploaded = - verify( - () => uploadService.uploadManual( - captureAny(), - cancelToken: any(named: 'cancelToken'), - callbacks: any(named: 'callbacks'), - ), - ).captured.single - as List; - expect(uploaded.map((a) => a.id), [asset.id]); - }); - - testWidgets('ignores assets that are already backed up', (tester) async { - final notBackedUp = LocalAssetFactory.create(); - answerUploadWith(succeeded: {notBackedUp.id}); - - await pumpUpload(tester, {notBackedUp, LocalAssetFactory.create(remoteId: 'already-there')}); - await tester.tap(find.byType(ImmichIconButton)); - await settleUpload(tester); - - final uploaded = - verify( - () => uploadService.uploadManual( - captureAny(), - cancelToken: any(named: 'cancelToken'), - callbacks: any(named: 'callbacks'), - ), - ).captured.single - as List; - expect(uploaded.map((a) => a.id), [notBackedUp.id]); - }); - - testWidgets('reports an error when an asset fails to upload', (tester) async { - final asset = LocalAssetFactory.create(); - answerUploadWith(failed: {asset.id}); - - await pumpUpload(tester, {asset}); - await tester.tap(find.byType(ImmichIconButton)); - await settleUpload(tester); - - final message = verify(() => context.service.toast.error(captureAny())).captured.single as String; - expect(message, StaticTranslations.instance.scaffold_body_error_occurred); - }); - - testWidgets('treats a cancelled upload as deliberate, not a failure', (tester) async { - final asset = LocalAssetFactory.create(); - when( - () => uploadService.uploadManual( - any(), - cancelToken: any(named: 'cancelToken'), - callbacks: any(named: 'callbacks'), - ), - ).thenAnswer((invocation) async { - (invocation.namedArguments[#cancelToken] as Completer).complete(); - }); - - await pumpUpload(tester, {asset}); - await tester.tap(find.byType(ImmichIconButton)); - await settleUpload(tester); - - verifyNever(() => context.service.toast.error(any())); - }); - - testWidgets('shows the progress dialog while uploading and closes it after', (tester) async { - final asset = LocalAssetFactory.create(); - final uploading = Completer(); - answerUploadWith(succeeded: {asset.id}, until: uploading); - - await pumpUpload(tester, {asset}, showProgress: true); - await tester.tap(find.byType(ImmichIconButton)); - await tester.pump(); - - expect(find.text(StaticTranslations.instance.uploading), findsOneWidget); - - uploading.complete(); - await settleUpload(tester); - - expect(find.text(StaticTranslations.instance.uploading), findsNothing); - }); - - testWidgets('shows no dialog when not asked to', (tester) async { - final asset = LocalAssetFactory.create(); - answerUploadWith(succeeded: {asset.id}); - - await pumpUpload(tester, {asset}); - await tester.tap(find.byType(ImmichIconButton)); - await tester.pump(); - - expect(find.text(StaticTranslations.instance.uploading), findsNothing); - await settleUpload(tester); - }); - - testWidgets('is hidden for a remote asset, which has nothing to upload', (tester) async { - await pumpUpload(tester, {RemoteAssetFactory.create()}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - - testWidgets('is hidden when every local asset is already backed up', (tester) async { - await pumpUpload(tester, {LocalAssetFactory.create(remoteId: 'already-there')}); - - expect(find.byType(ImmichIconButton), findsNothing); - }); - }); - - group('uploadAssets', () { - testWidgets('clears the tracked progress once the upload settles', (tester) async { - final asset = LocalAssetFactory.create(); - answerUploadWith(succeeded: {asset.id}); - - late WidgetRef capturedRef; - await tester.pumpTestWidget( - context, - Consumer( - builder: (_, ref, _) { - capturedRef = ref; - return const SizedBox.shrink(); - }, - ), - overrides: uploadOverrides(), - ); - - await uploadAssets(tester.element(find.byType(SizedBox)), capturedRef, [asset]); - await settleUpload(tester); - - expect(capturedRef.read(assetUploadProgressProvider), isEmpty); - expect(capturedRef.read(manualUploadCancelTokenProvider), isNull); - }); - }); -} diff --git a/mobile/test/unit/presentation/partner_page_test.dart b/mobile/test/unit/presentation/partner_page_test.dart deleted file mode 100644 index 2cabbb9d4c..0000000000 --- a/mobile/test/unit/presentation/partner_page_test.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/pages/library/partner/partner.page.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.dart'; -import 'package:immich_mobile/presentation/actions/partner.action.dart'; -import 'package:immich_ui/immich_ui.dart'; - -import '../factories/partner_user_factory.dart'; -import '../factories/user_factory.dart'; -import 'presentation_context.dart'; - -void main() { - late PresentationContext context; - - setUp(() async => context = await PresentationContext.create()); - tearDown(() async => await context.dispose()); - - group('PartnerSharedByList', () { - testWidgets('shows the empty-state add button when there are no partners', (tester) async { - await tester.pumpTestWidget(context, const PartnerSharedByList(partners: [])); - - expect(find.byType(ListView), findsNothing); - expect(find.descendant(of: find.byType(ActionButton), matching: find.byType(ImmichTextButton)), findsOneWidget); - }); - - testWidgets('renders a tile per partner with name and email', (tester) async { - final partner1 = PartnerFactory.create(); - final partner2 = PartnerFactory.create(); - await tester.pumpTestWidget(context, PartnerSharedByList(partners: [partner1, partner2])); - expect(find.byType(ListTile), findsNWidgets(2)); - expect(find.text(partner1.name), findsOneWidget); - expect(find.text(partner1.email), findsOneWidget); - expect(find.text(partner2.name), findsOneWidget); - expect(find.text(partner2.email), findsOneWidget); - }); - - testWidgets('renders a remove action for each partner', (tester) async { - final partner1 = PartnerFactory.create(inTimeline: true); - final partner2 = PartnerFactory.create(); - await tester.pumpTestWidget(context, PartnerSharedByList(partners: [partner1, partner2])); - expect( - find.descendant(of: find.byType(ActionIconButton), matching: find.byType(ImmichIconButton)), - findsNWidgets(2), - ); - }); - }); - - group('PartnerSelectionDialog', () { - final dialogButtonKey = UniqueKey(); - - Widget dialogWidget({void Function(User?)? onClosed}) { - return Builder( - builder: (context) => ElevatedButton( - onPressed: () async { - final selected = await showDialog(context: context, builder: (_) => const PartnerSelectionDialog()); - onClosed?.call(selected); - }, - child: Text(key: dialogButtonKey, 'open'), - ), - ); - } - - List withCandidates(List candidates) => [ - candidatesStateProvider.overrideWith((ref) => Stream>.value(candidates)), - ]; - - testWidgets('renders an option per candidate fetched from the provider', (tester) async { - final user = UserFactory.create(); - await tester.pumpTestWidget(context, dialogWidget(), overrides: withCandidates([user])); - - await tester.tap(find.byKey(dialogButtonKey)); - await tester.pumpAndSettle(); - - expect(find.byType(SimpleDialogOption), findsOneWidget); - expect(find.text(user.name), findsOneWidget); - }); - - testWidgets('shows no options when the provider returns no candidates', (tester) async { - await tester.pumpTestWidget(context, dialogWidget(), overrides: withCandidates(const [])); - - await tester.tap(find.byKey(dialogButtonKey)); - await tester.pumpAndSettle(); - - expect(find.byType(SimpleDialogOption), findsNothing); - }); - - testWidgets('pops the selected candidate when an option is tapped', (tester) async { - final user = UserFactory.create(); - User? selected; - await tester.pumpTestWidget( - context, - dialogWidget(onClosed: (user) => selected = user), - overrides: withCandidates([user]), - ); - - await tester.tap(find.byKey(dialogButtonKey)); - await tester.pumpAndSettle(); - - await tester.tap(find.text(user.name)); - await tester.pumpAndSettle(); - - expect(selected, user); - }); - }); -} diff --git a/mobile/test/unit/presentation/presentation_context.dart b/mobile/test/unit/presentation/presentation_context.dart deleted file mode 100644 index 1d46260435..0000000000 --- a/mobile/test/unit/presentation/presentation_context.dart +++ /dev/null @@ -1,151 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/models/user.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/generated/codegen_loader.g.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/presentation/actions/action.dart'; -import 'package:immich_mobile/presentation/actions/action.widget.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/db.provider.dart'; -import 'package:immich_mobile/providers/infrastructure/user.provider.dart'; -import 'package:immich_mobile/providers/routes.provider.dart'; -import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; -import 'package:immich_mobile/providers/user.provider.dart'; -import 'package:immich_mobile/repositories/asset_media.repository.dart'; -import 'package:immich_mobile/services/cleanup.service.dart'; -import 'package:immich_mobile/services/gcast.service.dart'; -import 'package:immich_mobile/services/server_info.service.dart'; -import 'package:immich_ui/immich_ui.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../infrastructure/repository.mock.dart'; -import '../../test_utils.dart'; -import '../factories/user_factory.dart'; -import '../mocks.dart'; - -class PresentationContext { - PresentationContext._({required UserDto user}) - : currentUser = user, - service = ServiceMocks(), - repository = RepositoryMocks() { - setup(); - } - - static const String serverEndpoint = 'http://localhost:3000'; - - static Drift? _db; - - final UserDto currentUser; - final ServiceMocks service; - final RepositoryMocks repository; - - List get overrides => [ - driftProvider.overrideWithValue(_mockDrift()), - currentUserProvider.overrideWith((ref) => CurrentUserProvider(service.user.service)), - assetServiceProvider.overrideWithValue(service.asset.service), - cleanupServiceProvider.overrideWithValue(service.cleanup.service), - remoteAlbumServiceProvider.overrideWithValue(service.album.service), - partnerServiceProvider.overrideWithValue(service.partner.service), - gCastServiceProvider.overrideWithValue(service.cast), - serverInfoServiceProvider.overrideWithValue(service.serverInfo), - inLockedViewProvider.overrideWithValue(false), - assetMediaRepositoryProvider.overrideWithValue(repository.assetMedia.api), - ]; - - Drift _mockDrift() { - final drift = MockDrift(); - when(() => drift.remoteAssetRepository).thenReturn(repository.remoteAsset.repo); - return drift; - } - - List selected(Set assets) => [ - multiSelectProvider.overrideWith( - () => MultiSelectNotifier(MultiSelectState(selectedAssets: assets, lockedSelectionAssets: const {})), - ), - ]; - - static Future create() async { - TestUtils.init(); - if (_db == null) { - final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db), listenUpdates: false); - await StoreService.I.put(StoreKey.serverEndpoint, serverEndpoint); - _db = db; - } - await SettingsRepository.ensureInitialized(_db!); - return PresentationContext._(user: UserFactory.createDto()); - } - - void setup() { - when(service.user.tryGetMyUser).thenReturn(currentUser); - } - - Future dispose() async { - await SettingsRepository.reset(); - service.resetAll(); - } -} - -extension PumpPresentationWidget on WidgetTester { - Future pumpTestWidget(PresentationContext context, Widget widget, {List overrides = const []}) async { - await pumpWidget( - EasyLocalization( - supportedLocales: locales.values.toList(), - path: translationsPath, - startLocale: locales.values.first, - fallbackLocale: locales.values.first, - saveLocale: false, - useFallbackTranslations: true, - assetLoader: const CodegenLoader(), - child: ProviderScope( - overrides: context.overrides, - child: Builder( - builder: (context) => ProviderScope( - overrides: overrides, - child: MaterialApp( - debugShowCheckedModeBanner: false, - scaffoldMessengerKey: scaffoldMessengerKey, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - home: Scaffold(body: widget), - ), - ), - ), - ), - ), - ); - await pumpAndSettle(); - } - - Future pumpTestAction( - PresentationContext context, - ActionBuilder action, { - List overrides = const [], - }) async { - await pumpTestWidget(context, ActionIconButton(action: action), overrides: overrides); - await tap(find.byType(ImmichIconButton)); - await pump(); - } - - Future pumpUntilFound(Finder finder, {int maxFrames = 10}) async { - for (var i = 0; i < maxFrames; i++) { - await pump(); - if (finder.evaluate().isNotEmpty) { - return; - } - } - throw StateError('pumpUntilFound: $finder not found within $maxFrames frames'); - } -} diff --git a/mobile/test/unit/repositories/settings_repository_test.dart b/mobile/test/unit/repositories/settings_repository_test.dart deleted file mode 100644 index de9100df01..0000000000 --- a/mobile/test/unit/repositories/settings_repository_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/config/app_config.dart'; -import 'package:immich_mobile/domain/models/settings_key.dart'; - -void main() { - group('SettingsKey', () { - for (final key in SettingsKey.values) { - final defaultValue = defaultConfig.read(key); - // null is a valid value for some keys but we don't use the codec in that case - if (defaultValue == null) { - continue; - } - test('verify codec for $key', () { - final encoded = key.encode(defaultValue); - final decoded = key.decode(encoded); - expect(decoded, defaultValue, reason: 'round-trip failed for ${key.name}'); - }); - } - }); -} diff --git a/mobile/test/unit/services/asset_service_test.dart b/mobile/test/unit/services/asset_service_test.dart deleted file mode 100644 index dc1146da08..0000000000 --- a/mobile/test/unit/services/asset_service_test.dart +++ /dev/null @@ -1,122 +0,0 @@ -import 'package:drift/drift.dart' as drift; -import 'package:drift/native.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/asset.service.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../infrastructure/repository.mock.dart'; -import '../../repository.mocks.dart'; -import '../mocks.dart'; - -void main() { - late AssetService sut; - late RepositoryMocks mocks; - late MockAssetApiRepository apiRepository; - late MockRemoteAssetRepository remoteRepository; - late MockRemoteExifRepository exifRepository; - late Drift db; - - setUpAll(() async { - TestWidgetsFlutterBinding.ensureInitialized(); - debugDefaultTargetPlatformOverride = TargetPlatform.android; - db = Drift(drift.DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db)); - }); - - tearDownAll(() async { - debugDefaultTargetPlatformOverride = null; - await Store.clear(); - await db.close(); - }); - - setUp(() { - mocks = RepositoryMocks(); - apiRepository = mocks.assetApi.api; - remoteRepository = mocks.remoteAsset.repo; - exifRepository = mocks.remoteExif.repo; - - sut = AssetService( - remoteRepository: remoteRepository, - exifRepository: exifRepository, - localRepository: mocks.localAsset.repo, - apiRepository: apiRepository, - mediaRepository: mocks.assetMedia.api, - trashedLocalRepository: mocks.trashedAsset, - ); - }); - - tearDown(() async { - await Store.delete(StoreKey.manageLocalMediaAndroid); - }); - - group('AssetService.updateDateTime', () { - const ids = ['asset_id_1']; - - test('sends the picked value to the api with its offset intact', () async { - const picked = '2026-06-10T19:15:00.000+06:00'; - await sut.update(ids, dateTime: const .some(picked)); - - verify(() => apiRepository.update(ids, dateTimeOriginal: const .some(picked))).called(1); - verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1); - verify( - () => exifRepository.updateExif( - ids, - dateTimeOriginal: .some(DateTime.parse(picked)), - timeZone: const .some('UTC+06:00'), - ), - ).called(1); - }); - - test('handles negative offsets', () async { - const picked = '2026-01-05T08:00:00.000-05:30'; - await sut.update(ids, dateTime: const .some(picked)); - - verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1); - verify( - () => exifRepository.updateExif( - ids, - dateTimeOriginal: .some(DateTime.parse(picked)), - timeZone: const .some('UTC-05:30'), - ), - ).called(1); - }); - - test('writes no timezone when the value has no offset', () async { - const picked = '2026-06-10T13:15:00.000Z'; - await sut.update(ids, dateTime: const .some(picked)); - - verify(() => remoteRepository.updateAssets(ids, createdAt: .some(DateTime.parse(picked)))).called(1); - verify( - () => exifRepository.updateExif(ids, dateTimeOriginal: .some(DateTime.parse(picked)), timeZone: const .none()), - ).called(1); - }); - - test('is a no-op when there are no asset ids', () async { - await sut.update(const [], dateTime: const .some('2026-06-10T19:15:00.000+06:00')); - - verifyZeroInteractions(apiRepository); - verifyZeroInteractions(remoteRepository); - }); - }); - - group('AssetService.deleteLocal', () { - const ids = ['l1', 'l2']; - - test('permanently deletes local copies without trashing, even when Android trash handling is on', () async { - await Store.put(StoreKey.manageLocalMediaAndroid, true); - - final result = await sut.deleteLocal(ids, trash: false); - - expect(result, ids.length); - verify(() => mocks.assetMedia.api.deleteAll(ids, trash: false)).called(1); - verify(() => mocks.localAsset.repo.deleteAssets(ids)).called(1); - verifyNever(() => mocks.trashedAsset.applyTrashedAssets(any())); - }); - }); -} diff --git a/mobile/test/unit/services/gallery_permission_service_test.dart b/mobile/test/unit/services/gallery_permission_service_test.dart deleted file mode 100644 index 9a399f9948..0000000000 --- a/mobile/test/unit/services/gallery_permission_service_test.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/constants/enums.dart'; -import 'package:immich_mobile/domain/services/device_permission.service.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../mocks.dart'; - -void main() { - late RepositoryMocks mocks; - late DevicePermissionService sut; - - void stubPermissions(Map statuses) { - DevicePermissionStatus answer(Invocation i) => statuses[i.positionalArguments.first] ?? .denied; - when(mocks.permission.getStatus).thenAnswer((i) async => answer(i)); - when(mocks.permission.request).thenAnswer((i) async => answer(i)); - } - - setUp(() { - debugDefaultTargetPlatformOverride = .android; - mocks = .new(); - sut = .new(mocks.permission.repo); - }); - - tearDown(() { - debugDefaultTargetPlatformOverride = null; - }); - - group('DevicePermissionService', () { - group('Gallery', () { - test('is denied when media location is missing but photos and videos are granted', () async { - when(mocks.permission.getAndroidSdkVersion).thenAnswer((_) async => 34); - stubPermissions({.photos: .granted, .videos: .granted, .mediaLocation: .denied}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - verify(() => mocks.permission.repo.getStatus(DevicePermission.mediaLocation)).called(1); - }); - - test('is granted when media location is granted', () async { - when(mocks.permission.getAndroidSdkVersion).thenAnswer((_) async => 34); - stubPermissions({.photos: .granted, .videos: .granted, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.granted); - verify(() => mocks.permission.repo.getStatus(DevicePermission.mediaLocation)).called(1); - }); - - test('checks media location on the legacy storage path', () async { - when(mocks.permission.getAndroidSdkVersion).thenAnswer((_) async => 30); - stubPermissions({.storage: .granted, .mediaLocation: .denied}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - verify(() => mocks.permission.repo.getStatus(DevicePermission.mediaLocation)).called(1); - }); - - test('ignores media location below SDK 29', () async { - when(mocks.permission.getAndroidSdkVersion).thenAnswer((_) async => 28); - stubPermissions({.storage: .granted, .mediaLocation: .denied}); - expect(await sut.galleryStatus(), DevicePermissionStatus.granted); - verifyNever(() => mocks.permission.repo.getStatus(.mediaLocation)); - }); - - test('ignores media location on iOS', () async { - debugDefaultTargetPlatformOverride = .iOS; - stubPermissions({.photos: .granted, .mediaLocation: .denied}); - expect(await sut.galleryStatus(), DevicePermissionStatus.granted); - verifyNever(() => mocks.permission.repo.getStatus(.mediaLocation)); - }); - - test('is limited when only one of photos and videos is limited', () async { - stubPermissions({.photos: .granted, .videos: .limited, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.limited); - - stubPermissions({.photos: .limited, .videos: .granted, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.limited); - - stubPermissions({.photos: .limited, .videos: .limited, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.limited); - }); - - test('is denied when only one of photos and videos is denied', () async { - stubPermissions({.photos: .granted, .videos: .denied, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - - stubPermissions({.photos: .denied, .videos: .granted, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - - stubPermissions({.photos: .limited, .videos: .denied, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - }); - - test('is permanently denied when only one of photos and videos is permanently denied', () async { - stubPermissions({.photos: .granted, .videos: .permanentlyDenied, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.permanentlyDenied); - - stubPermissions({.photos: .permanentlyDenied, .videos: .denied, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.permanentlyDenied); - - stubPermissions({.photos: .permanentlyDenied, .videos: .granted, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.permanentlyDenied); - }); - - test('checks media location when access is only partial', () async { - stubPermissions({.photos: .limited, .videos: .limited, .mediaLocation: .denied}); - expect(await sut.galleryStatus(), DevicePermissionStatus.denied); - verify(() => mocks.permission.repo.getStatus(DevicePermission.mediaLocation)).called(1); - }); - - test('stays limited when media location is granted, but photos or video is limited', () async { - stubPermissions({.photos: .granted, .videos: .limited, .mediaLocation: .granted}); - expect(await sut.galleryStatus(), DevicePermissionStatus.limited); - }); - }); - - test('does not ask for videos when photos was refused', () async { - stubPermissions({.photos: .permanentlyDenied}); - - expect(await sut.requestGallery(), DevicePermissionStatus.permanentlyDenied); - verifyNever(() => mocks.permission.repo.request(.videos)); - }); - }); -} diff --git a/mobile/test/unit/services/hash_service_test.dart b/mobile/test/unit/services/hash_service_test.dart deleted file mode 100644 index 2623042f4b..0000000000 --- a/mobile/test/unit/services/hash_service_test.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/album/local_album.model.dart'; -import 'package:immich_mobile/domain/services/hash.service.dart'; -import 'package:immich_mobile/platform/native_sync_api.g.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../factories/local_album_factory.dart'; -import '../factories/local_asset_factory.dart'; -import '../mocks.dart'; - -void main() { - late HashService sut; - final mocks = RepositoryMocks(); - - setUp(() { - sut = HashService( - localAlbumRepository: mocks.localAlbum.repo, - localAssetRepository: mocks.localAsset.repo, - nativeSyncApi: mocks.nativeApi.api, - trashedLocalAssetRepository: mocks.trashedAsset, - ); - }); - - tearDown(() { - mocks.resetAll(); - }); - - group('HashService', () { - group('hashAssets', () { - test('skips albums with no assets to hash', () async { - final album = LocalAlbumFactory.create(assetCount: 0); - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - - await sut.hashAssets(); - - verifyNever(mocks.nativeApi.hashAssets); - }); - - test('skips empty batches', () async { - final album = LocalAlbumFactory.create(); - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - - await sut.hashAssets(); - - verifyNever(mocks.nativeApi.hashAssets); - }); - - test('processes assets when available', () async { - final album = LocalAlbumFactory.create(); - final asset = LocalAssetFactory.create(); - final result = HashResult(assetId: asset.id, hash: 'test-hash'); - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - when(() => mocks.localAlbum.repo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); - when( - () => mocks.nativeApi.api.hashAssets([asset.id], allowNetworkAccess: false), - ).thenAnswer((_) async => [result]); - - await sut.hashAssets(); - - verify(() => mocks.nativeApi.api.hashAssets([asset.id], allowNetworkAccess: false)).called(1); - final captured = - verify(() => mocks.localAsset.repo.updateHashes(captureAny())).captured.first as Map; - expect(captured.length, 1); - expect(captured[asset.id], result.hash); - }); - - test('handles failed hashes', () async { - final album = LocalAlbumFactory.create(); - final asset = LocalAssetFactory.create(); - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - when(() => mocks.localAlbum.repo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); - when( - () => mocks.nativeApi.api.hashAssets([asset.id], allowNetworkAccess: false), - ).thenAnswer((_) async => [HashResult(assetId: asset.id, error: 'Failed to hash')]); - - await sut.hashAssets(); - - final captured = - verify(() => mocks.localAsset.repo.updateHashes(captureAny())).captured.first as Map; - expect(captured.length, 0); - }); - - test('handles null hash results', () async { - final album = LocalAlbumFactory.create(); - final asset = LocalAssetFactory.create(); - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - when(() => mocks.localAlbum.repo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset]); - when( - () => mocks.nativeApi.api.hashAssets([asset.id], allowNetworkAccess: false), - ).thenAnswer((_) async => [HashResult(assetId: asset.id, hash: null)]); - - await sut.hashAssets(); - - final captured = - verify(() => mocks.localAsset.repo.updateHashes(captureAny())).captured.first as Map; - expect(captured.length, 0); - }); - - test('batches by size limit', () async { - const batchSize = 2; - final sut = HashService( - localAlbumRepository: mocks.localAlbum.repo, - localAssetRepository: mocks.localAsset.repo, - nativeSyncApi: mocks.nativeApi.api, - batchSize: batchSize, - trashedLocalAssetRepository: mocks.trashedAsset, - ); - - final album = LocalAlbumFactory.create(); - final asset1 = LocalAssetFactory.create(); - final asset2 = LocalAssetFactory.create(); - final asset3 = LocalAssetFactory.create(); - - final capturedCalls = >[]; - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - when(() => mocks.localAlbum.repo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset1, asset2, asset3]); - when(mocks.nativeApi.hashAssets).thenAnswer((invocation) async { - final assetIds = invocation.positionalArguments[0] as List; - capturedCalls.add(List.from(assetIds)); - return assetIds.map((id) => HashResult(assetId: id, hash: '$id-hash')).toList(); - }); - - await sut.hashAssets(); - - expect(capturedCalls.length, 2, reason: 'Should make exactly 2 calls to hashAssets'); - expect(capturedCalls[0], [asset1.id, asset2.id], reason: 'First call should batch the first two assets'); - expect(capturedCalls[1], [asset3.id], reason: 'Second call should have the remaining asset'); - - verify(() => mocks.localAsset.repo.updateHashes(any())).called(2); - }); - - test('handles mixed success and failure in batch', () async { - final album = LocalAlbumFactory.create(); - final asset1 = LocalAssetFactory.create(); - final asset2 = LocalAssetFactory.create(); - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [album]); - when(() => mocks.localAlbum.repo.getAssetsToHash(album.id)).thenAnswer((_) async => [asset1, asset2]); - when(() => mocks.nativeApi.api.hashAssets([asset1.id, asset2.id], allowNetworkAccess: false)).thenAnswer( - (_) async => [ - HashResult(assetId: asset1.id, hash: 'asset1-hash'), - HashResult(assetId: asset2.id, error: 'Failed to hash asset2'), - ], - ); - - await sut.hashAssets(); - - final captured = - verify(() => mocks.localAsset.repo.updateHashes(captureAny())).captured.first as Map; - expect(captured.length, 1); - expect(captured[asset1.id], 'asset1-hash'); - }); - - test('uses allowNetworkAccess based on album backup selection', () async { - final selectedAlbum = LocalAlbumFactory.create(backupSelection: BackupSelection.selected); - final nonSelectedAlbum = LocalAlbumFactory.create(id: 'album2', backupSelection: BackupSelection.excluded); - final asset1 = LocalAssetFactory.create(); - final asset2 = LocalAssetFactory.create(); - - when(mocks.localAlbum.getBackupAlbums).thenAnswer((_) async => [selectedAlbum, nonSelectedAlbum]); - when(() => mocks.localAlbum.repo.getAssetsToHash(selectedAlbum.id)).thenAnswer((_) async => [asset1]); - when(() => mocks.localAlbum.repo.getAssetsToHash(nonSelectedAlbum.id)).thenAnswer((_) async => [asset2]); - when(mocks.nativeApi.hashAssets).thenAnswer((invocation) async { - final assetIds = invocation.positionalArguments[0] as List; - return assetIds.map((id) => HashResult(assetId: id, hash: '$id-hash')).toList(); - }); - - await sut.hashAssets(); - - verify(() => mocks.nativeApi.api.hashAssets([asset1.id], allowNetworkAccess: true)).called(1); - verify(() => mocks.nativeApi.api.hashAssets([asset2.id], allowNetworkAccess: false)).called(1); - }); - }); - }); -} diff --git a/mobile/test/unit/services/remote_album_service_test.dart b/mobile/test/unit/services/remote_album_service_test.dart deleted file mode 100644 index 9417f14653..0000000000 --- a/mobile/test/unit/services/remote_album_service_test.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/services/remote_album.service.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../service.mocks.dart'; -import '../mocks.dart'; - -void main() { - late RemoteAlbumService sut; - final mocks = RepositoryMocks(); - - setUpAll(() { - registerFallbackValue([]); - }); - - setUp(() { - sut = RemoteAlbumService(mocks.remoteAlbum, mocks.albumApi, MockForegroundUploadService()); - }); - - tearDown(() { - mocks.resetAll(); - }); - - group('RemoteAlbumService', () { - group('removeAssets', () { - test('persists only the assets the server actually removed, not the whole request', () async { - const albumId = 'album-1'; - const requested = ['asset-1', 'asset-2', 'asset-3']; - const removed = ['asset-1', 'asset-3']; - - // The server rejected 'asset-2' - when( - () => mocks.albumApi.removeAssets(albumId, requested), - ).thenAnswer((_) async => (removed: removed, failed: ['asset-2'])); - when(() => mocks.remoteAlbum.removeAssets(albumId, any())).thenAnswer((_) async {}); - - final count = await sut.removeAssets(albumId: albumId, assetIds: requested); - - final persisted = - verify(() => mocks.remoteAlbum.removeAssets(albumId, captureAny())).captured.single as List; - expect(persisted, removed); - expect(persisted, isNot(contains('asset-2'))); - - expect(count, removed.length); - }); - }); - }); -} diff --git a/mobile/test/unit/utils/asset_filter_test.dart b/mobile/test/unit/utils/asset_filter_test.dart deleted file mode 100644 index 1db287b346..0000000000 --- a/mobile/test/unit/utils/asset_filter_test.dart +++ /dev/null @@ -1,200 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/utils/asset_filter.dart'; - -import '../factories/local_asset_factory.dart'; -import '../factories/remote_asset_factory.dart'; - -void main() { - group('AssetFilter', () { - group('type promotion', () { - test('a bare filter retains every BaseAsset', () { - final remoteAsset = RemoteAssetFactory.create(); - final localAsset = LocalAssetFactory.create(); - - final AssetFilter filter = AssetFilter([remoteAsset, localAsset]); - - expect(filter.toList(), [remoteAsset, localAsset]); - }); - - test('remote keeps only remote assets', () { - final remoteAsset = RemoteAssetFactory.create(); - final localAsset = LocalAssetFactory.create(); - - final AssetFilter remoteOnly = AssetFilter([remoteAsset, localAsset]).remote(); - - expect(remoteOnly.toList(), [remoteAsset]); - }); - - test('local keeps only local assets', () { - final remoteAsset = RemoteAssetFactory.create(); - final localAsset = LocalAssetFactory.create(); - - final AssetFilter localOnly = AssetFilter([remoteAsset, localAsset]).local(); - - expect(localOnly.toList(), [localAsset]); - }); - - test('owned promotes to RemoteAsset and drops local assets', () { - final remoteAsset = RemoteAssetFactory.create(); - final localAsset = LocalAssetFactory.create(); - - final AssetFilter remoteOnly = AssetFilter([ - remoteAsset, - localAsset, - ]).owned(remoteAsset.ownerId); - - expect(remoteOnly.toList(), [remoteAsset]); - }); - - test('backedUp promotes to LocalAsset and drops remote assets', () { - final syncedPhoto = LocalAssetFactory.create().copyWith(remoteId: 'remote'); - final offlinePhoto = LocalAssetFactory.create(); - final remotePhoto = RemoteAssetFactory.create(); - - final AssetFilter syncedPhotos = AssetFilter([ - syncedPhoto, - offlinePhoto, - remotePhoto, - ]).backedUp(); - - expect(syncedPhotos.toList(), [syncedPhoto]); - }); - }); - - group('named filters', () { - test('owned keeps only assets of the given owner', () { - final asset1 = RemoteAssetFactory.create(); - final asset2 = RemoteAssetFactory.create(); - - final alexPhotos = AssetFilter([asset1, asset2]).owned(asset1.ownerId); - - expect(alexPhotos.toList(), [asset1]); - }); - - test('favorites keeps only favorite assets', () { - final asset1 = RemoteAssetFactory.create(isFavorite: true); - final asset2 = RemoteAssetFactory.create(ownerId: asset1.ownerId); - - final favorites = AssetFilter([asset1, asset2]).favorite(); - - expect(favorites.toList(), [asset1]); - }); - - test('type keeps only assets of the given type', () { - final image = RemoteAssetFactory.create(); - final video = RemoteAssetFactory.create(ownerId: image.ownerId).copyWith(type: .video); - - final videos = AssetFilter([image, video]).type(.video); - - expect(videos.toList(), [video]); - }); - - test('visibility keeps only assets with the given visibility', () { - final locked = RemoteAssetFactory.create(visibility: AssetVisibility.locked); - final onTimeline = RemoteAssetFactory.create(ownerId: locked.ownerId); - - final lockedPhotos = AssetFilter([locked, onTimeline]).visibility(.locked); - - expect(lockedPhotos.toList(), [locked]); - }); - - test('archived keeps only archived assets', () { - final archived = RemoteAssetFactory.create(visibility: AssetVisibility.archive); - final onTimeline = RemoteAssetFactory.create(ownerId: archived.ownerId); - - final archivedPhotos = AssetFilter([archived, onTimeline]).archived(); - - expect(archivedPhotos.toList(), [archived]); - }); - - test('stacked keeps only assets belonging to a stack', () { - final stacked = RemoteAssetFactory.create(stackId: 'stack'); - final loose = RemoteAssetFactory.create(ownerId: stacked.ownerId); - - final stackedPhotos = AssetFilter([stacked, loose]).stacked(); - - expect(stackedPhotos.toList(), [stacked]); - }); - }); - - group('inversion', () { - test('notArchived keeps every non-archived visibility', () { - final archived = RemoteAssetFactory.create(visibility: AssetVisibility.archive); - final onTimeline = RemoteAssetFactory.create(ownerId: archived.ownerId, visibility: AssetVisibility.timeline); - final hidden = RemoteAssetFactory.create(ownerId: archived.ownerId, visibility: AssetVisibility.hidden); - final locked = RemoteAssetFactory.create(ownerId: archived.ownerId, visibility: AssetVisibility.locked); - - final visiblePhotos = AssetFilter([archived, onTimeline, hidden, locked]).archived(isArchived: false); - - expect(visiblePhotos.toSet(), {onTimeline, hidden, locked}); - }); - - test('notVisibility keeps every asset not at the target visibility', () { - final archived = RemoteAssetFactory.create(visibility: AssetVisibility.archive); - final onTimeline = RemoteAssetFactory.create(ownerId: archived.ownerId, visibility: AssetVisibility.timeline); - final locked = RemoteAssetFactory.create(ownerId: archived.ownerId, visibility: AssetVisibility.locked); - - final toArchive = AssetFilter([archived, onTimeline, locked]).notVisibility(.archive); - - expect(toArchive.toSet(), {onTimeline, locked}); - }); - - test('notStacked keeps only assets without a stack', () { - final stacked = RemoteAssetFactory.create(stackId: 'stack'); - final loose = RemoteAssetFactory.create(ownerId: stacked.ownerId); - - final loosePhotos = AssetFilter([stacked, loose]).stacked(isStacked: false); - - expect(loosePhotos.toList(), [loose]); - }); - - test('whereNot inverts an arbitrary predicate', () { - final favorite = RemoteAssetFactory.create(isFavorite: true); - final regular = RemoteAssetFactory.create(ownerId: favorite.ownerId); - - final nonFavorites = AssetFilter([favorite, regular]).whereNot((asset) => asset.isFavorite); - - expect(nonFavorites.toList(), [regular]); - }); - - test('notFavorites keeps only non-favorite assets', () { - final favorite = RemoteAssetFactory.create(isFavorite: true); - final regular = RemoteAssetFactory.create(ownerId: favorite.ownerId); - - final nonFavorites = AssetFilter([favorite, regular]).favorite(isFavorite: false); - - expect(nonFavorites.toList(), [regular]); - }); - }); - - group('chaining', () { - test('combines predicates across owner, visibility and stack', () { - final asset = RemoteAssetFactory.create(); - final wrongOwner = RemoteAssetFactory.create(); - final archived = RemoteAssetFactory.create(ownerId: asset.ownerId, visibility: AssetVisibility.archive); - final stacked = RemoteAssetFactory.create(ownerId: asset.ownerId, stackId: 'stack-1'); - final localPhoto = LocalAssetFactory.create(); - - final result = AssetFilter([ - asset, - wrongOwner, - archived, - stacked, - localPhoto, - ]).owned(asset.ownerId).archived(isArchived: false).stacked(isStacked: false); - - expect(result.toList(), [asset]); - }); - - test('a base filter after a promotion retains the promoted type', () { - final favorite = RemoteAssetFactory.create(isFavorite: true); - final regular = RemoteAssetFactory.create(ownerId: favorite.ownerId); - - final AssetFilter result = AssetFilter([favorite, regular]).owned(favorite.ownerId).favorite(); - - expect(result.toList(), [favorite]); - }); - }); - }); -} diff --git a/mobile/test/unit/utils/editor_test.dart b/mobile/test/unit/utils/editor_test.dart deleted file mode 100644 index 6f3f41f374..0000000000 --- a/mobile/test/unit/utils/editor_test.dart +++ /dev/null @@ -1,312 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/asset_edit.model.dart'; -import 'package:immich_mobile/utils/editor.utils.dart'; -import 'package:openapi/api.dart' show MirrorAxis, MirrorParameters, RotateParameters; - -List normalizedToEdits(NormalizedTransform transform) { - final List edits = []; - - if (transform.mirrorHorizontal) { - edits.add(MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal))); - } - - if (transform.mirrorVertical) { - edits.add(MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical))); - } - - if (transform.rotation != 0) { - edits.add(RotateEdit(RotateParameters(angle: transform.rotation))); - } - - return edits; -} - -bool compareEditAffines(List editsA, List editsB) { - final normA = buildAffineFromEdits(editsA); - final normB = buildAffineFromEdits(editsB); - - return (normA.a - normB.a).abs() < 0.0001 && - (normA.b - normB.b).abs() < 0.0001 && - (normA.c - normB.c).abs() < 0.0001 && - (normA.d - normB.d).abs() < 0.0001; -} - -void main() { - group('normalizeEdits', () { - test('should handle no edits', () { - final edits = []; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle a single 90° rotation', () { - final edits = [RotateEdit(RotateParameters(angle: 90))]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle a single 180° rotation', () { - final edits = [RotateEdit(RotateParameters(angle: 180))]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle a single 270° rotation', () { - final edits = [RotateEdit(RotateParameters(angle: 270))]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle a single horizontal mirror', () { - final edits = [MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal))]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle a single vertical mirror', () { - final edits = [MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical))]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 90° rotation + horizontal mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 90)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 90° rotation + vertical mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 90)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 90° rotation + both mirrors', () { - final edits = [ - RotateEdit(RotateParameters(angle: 90)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 180° rotation + horizontal mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 180)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 180° rotation + vertical mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 180)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 180° rotation + both mirrors', () { - final edits = [ - RotateEdit(RotateParameters(angle: 180)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 270° rotation + horizontal mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 270)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 270° rotation + vertical mirror', () { - final edits = [ - RotateEdit(RotateParameters(angle: 270)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle 270° rotation + both mirrors', () { - final edits = [ - RotateEdit(RotateParameters(angle: 270)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle horizontal mirror + 90° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - RotateEdit(RotateParameters(angle: 90)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle horizontal mirror + 180° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - RotateEdit(RotateParameters(angle: 180)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle horizontal mirror + 270° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - RotateEdit(RotateParameters(angle: 270)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle vertical mirror + 90° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 90)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle vertical mirror + 180° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 180)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle vertical mirror + 270° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 270)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle both mirrors + 90° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 90)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle both mirrors + 180° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 180)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - - test('should handle both mirrors + 270° rotation', () { - final edits = [ - MirrorEdit(MirrorParameters(axis: MirrorAxis.horizontal)), - MirrorEdit(MirrorParameters(axis: MirrorAxis.vertical)), - RotateEdit(RotateParameters(angle: 270)), - ]; - - final result = normalizeTransformEdits(edits); - final normalizedEdits = normalizedToEdits(result); - - expect(compareEditAffines(normalizedEdits, edits), true); - }); - }); -} diff --git a/mobile/test/unit/utils/isolate_worker_test.dart b/mobile/test/unit/utils/isolate_worker_test.dart deleted file mode 100644 index 5b79395f52..0000000000 --- a/mobile/test/unit/utils/isolate_worker_test.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/wm_executor.dart'; - -void main() { - tearDown(workerManagerPatch.dispose); - - test('dispose() drains a cancelled task and delivers its result', () async { - await workerManagerPatch.init(isolatesCount: 1, dynamicSpawning: false); - - final task = workerManagerPatch.executeGentle((onCancel) async { - await onCancel.future; - return 'drained'; - }); - - await workerManagerPatch.dispose(); - - expect( - await task.timeout(const Duration(seconds: 5)), - 'drained', - reason: 'the worker must finish and return its result, not be killed mid-task', - ); - }); -} diff --git a/mobile/test/unit/utils/value_codec_test.dart b/mobile/test/unit/utils/value_codec_test.dart deleted file mode 100644 index 8754af1e95..0000000000 --- a/mobile/test/unit/utils/value_codec_test.dart +++ /dev/null @@ -1,99 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/value_codec.dart'; - -enum _Fruit { apple, banana, cherry } - -void main() { - group('MapCodec', () { - group('encode', () { - test('serializes an empty map to an empty JSON object', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.encode({}), '{}'); - }); - - test('encodes a string-to-string map as a JSON object', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.encode({'a': '1', 'b': '2'}), '{"a":"1","b":"2"}'); - }); - - test('stringifies non-string values via the value codec', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.integer); - expect(codec.encode({'x': 10, 'y': 20}), '{"x":"10","y":"20"}'); - }); - - test('stringifies non-string keys via the key codec', () { - const codec = MapCodec(PrimitiveCodec.integer, PrimitiveCodec.string); - expect(codec.encode({1: 'one', 2: 'two'}), '{"1":"one","2":"two"}'); - }); - }); - - group('decode', () { - test('reconstructs a string-to-string map', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.decode('{"a":"1","b":"2"}'), {'a': '1', 'b': '2'}); - }); - - test('parses values back to their domain type', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.integer); - expect(codec.decode('{"x":"10","y":"20"}'), {'x': 10, 'y': 20}); - }); - - test('parses keys back to their domain type', () { - const codec = MapCodec(PrimitiveCodec.integer, PrimitiveCodec.string); - expect(codec.decode('{"1":"one","2":"two"}'), {1: 'one', 2: 'two'}); - }); - - test('returns an empty map for an empty JSON object', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.decode('{}'), isEmpty); - }); - - test('returns an empty map when the payload is not valid JSON', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.decode('not json'), isEmpty); - }); - - test('returns an empty map when the JSON root is not an object', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.decode('[]'), isEmpty); - expect(codec.decode('"a string"'), isEmpty); - expect(codec.decode('42'), isEmpty); - }); - - test('skips entries whose value is not a JSON string, keeping the rest', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.integer); - expect(codec.decode('{"x":1,"y":"20"}'), {'y': 20}); - }); - - test('skips entries whose value is a nested object, keeping the rest', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.string); - expect(codec.decode('{"a":{"nested":"value"},"b":"ok"}'), {'b': 'ok'}); - }); - - test('returns an empty map when every entry is malformed', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.integer); - expect(codec.decode('{"x":1,"y":2}'), isEmpty); - }); - }); - - group('round trip', () { - test('preserves a primitive map through encode then decode', () { - const codec = MapCodec(PrimitiveCodec.string, PrimitiveCodec.integer); - const original = {'one': 1, 'two': 2, 'three': 3}; - expect(codec.decode(codec.encode(original)), original); - }); - - test('preserves an enum-valued map by composing with EnumCodec', () { - const codec = MapCodec(PrimitiveCodec.string, EnumCodec(_Fruit.values)); - const original = {'breakfast': _Fruit.banana, 'snack': _Fruit.apple}; - expect(codec.decode(codec.encode(original)), original); - }); - - test('preserves a DateTime-valued map by composing with DateTimeCodec', () { - const codec = MapCodec(PrimitiveCodec.string, DateTimeCodec()); - final original = {'created': DateTime.utc(2024, 1, 1, 12, 30)}; - expect(codec.decode(codec.encode(original)), original); - }); - }); - }); -} diff --git a/mobile/test/utils.dart b/mobile/test/utils.dart deleted file mode 100644 index aa1f82dc2b..0000000000 --- a/mobile/test/utils.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'dart:math'; - -import 'package:uuid/uuid.dart'; - -class TestUtils { - static final _random = Random(); - - static String uuid([String? id]) => id ?? const Uuid().v4(); - - static DateTime date([DateTime? date]) => date ?? DateTime.now(); - static DateTime now() => DateTime.now(); - static DateTime yesterday() => DateTime.now().subtract(const Duration(days: 1)); - static DateTime tomorrow() => DateTime.now().add(const Duration(days: 1)); - - static T randElement(List list) => list[_random.nextInt(list.length)]; - static int randInt([int? max]) => max != null ? _random.nextInt(max) : _random.nextInt(1 << 32); - static double randDouble([int? max, int? min]) { - final minValue = min ?? 0; - final maxValue = max ?? 1; - return minValue + _random.nextDouble() * (maxValue - minValue); - } -} diff --git a/mobile/test/utils_legacy/action_button_utils_test.dart b/mobile/test/utils/action_button_utils_test.dart similarity index 95% rename from mobile/test/utils_legacy/action_button_utils_test.dart rename to mobile/test/utils/action_button_utils_test.dart index 65e1cad7f7..9956dfa2d0 100644 --- a/mobile/test/utils_legacy/action_button_utils_test.dart +++ b/mobile/test/utils/action_button_utils_test.dart @@ -3,7 +3,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:immich_mobile/constants/enums.dart'; import 'package:immich_mobile/domain/models/album/album.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; -import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/utils/action_button.utils.dart'; LocalAsset createLocalAsset({ @@ -36,9 +35,7 @@ RemoteAsset createRemoteAsset({ AssetType type = AssetType.image, DateTime? createdAt, DateTime? updatedAt, - DateTime? uploadedAt, bool isFavorite = false, - DateTime? deletedAt, }) { return RemoteAsset( id: 'remote-id', @@ -49,10 +46,8 @@ RemoteAsset createRemoteAsset({ ownerId: 'owner-id', createdAt: createdAt ?? DateTime.now(), updatedAt: updatedAt ?? DateTime.now(), - uploadedAt: uploadedAt ?? DateTime.now(), isFavorite: isFavorite, isEdited: false, - deletedAt: deletedAt, ); } @@ -427,8 +422,8 @@ void main() { }); }); - group('restoreTrash button', () { - test('should show when owner, not locked, has remote, and is in trash timeline', () { + group('trash button', () { + test('should show when owner, not locked, has remote, and trash enabled', () { final remoteAsset = createRemoteAsset(); final context = ActionButtonContext( asset: remoteAsset, @@ -440,13 +435,12 @@ void main() { advancedTroubleshooting: false, isStacked: false, source: ActionSource.timeline, - timelineOrigin: TimelineOrigin.trash, ); - expect(ActionButtonType.restoreTrash.shouldShow(context), isTrue); + expect(ActionButtonType.trash.shouldShow(context), isTrue); }); - test('should not show when not in trash timeline', () { + test('should not show when trash disabled', () { final remoteAsset = createRemoteAsset(); final context = ActionButtonContext( asset: remoteAsset, @@ -458,10 +452,45 @@ void main() { advancedTroubleshooting: false, isStacked: false, source: ActionSource.timeline, - timelineOrigin: TimelineOrigin.main, ); - expect(ActionButtonType.restoreTrash.shouldShow(context), isFalse); + expect(ActionButtonType.trash.shouldShow(context), isFalse); + }); + }); + + group('deletePermanent button', () { + test('should show when owner, not locked, has remote, and trash disabled', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: false, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.deletePermanent.shouldShow(context), isTrue); + }); + + test('should not show when trash enabled', () { + final remoteAsset = createRemoteAsset(); + final context = ActionButtonContext( + asset: remoteAsset, + isOwner: true, + isArchived: false, + isTrashEnabled: true, + isInLockedView: false, + currentAlbum: null, + advancedTroubleshooting: false, + isStacked: false, + source: ActionSource.timeline, + ); + + expect(ActionButtonType.deletePermanent.shouldShow(context), isFalse); }); }); @@ -504,7 +533,7 @@ void main() { }); group('deleteLocal button', () { - test('should not show when asset is local only, as there is no backup to fall back on', () { + test('should show when not locked and asset is local only', () { final localAsset = createLocalAsset(); final context = ActionButtonContext( asset: localAsset, @@ -518,7 +547,7 @@ void main() { source: ActionSource.timeline, ); - expect(ActionButtonType.deleteLocal.shouldShow(context), isFalse); + expect(ActionButtonType.deleteLocal.shouldShow(context), isTrue); }); test('should not show when asset is not local only', () { @@ -966,7 +995,7 @@ void main() { test('should build correct widget for each button type', () { for (final buttonType in ActionButtonType.values) { - final buttonContext = context; + var buttonContext = context; if (buttonType == ActionButtonType.removeFromAlbum) { final album = createRemoteAlbum(); diff --git a/mobile/test/utils/image_url_builder_test.dart b/mobile/test/utils/image_url_builder_test.dart deleted file mode 100644 index 055af185f7..0000000000 --- a/mobile/test/utils/image_url_builder_test.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/services/store.service.dart'; -import 'package:immich_mobile/infrastructure/repositories/db.repository.dart'; -import 'package:immich_mobile/infrastructure/repositories/store.repository.dart'; -import 'package:immich_mobile/utils/image_url_builder.dart'; - -void main() { - const endpoint = 'http://localhost:3000'; - - setUpAll(() async { - final db = Drift(DatabaseConnection(NativeDatabase.memory(), closeStreamsSynchronously: true)); - await StoreService.init(storeRepository: StoreRepository(db), listenUpdates: false); - await StoreService.I.put(StoreKey.serverEndpoint, endpoint); - }); - - group('getFaceThumbnailUrl', () { - test('omits the cache buster when updatedAt is null', () { - expect(getFaceThumbnailUrl('person-1'), '$endpoint/people/person-1/thumbnail'); - }); - - test('appends the updatedAt cache buster so a changed featured photo busts the cache (#27434)', () { - final url = getFaceThumbnailUrl('person-1', updatedAt: DateTime.fromMillisecondsSinceEpoch(1717000000000)); - expect(url, '$endpoint/people/person-1/thumbnail?c=1717000000000'); - }); - - test('a newer updatedAt yields a different url so the image cache key changes', () { - final before = getFaceThumbnailUrl('person-1', updatedAt: DateTime.fromMillisecondsSinceEpoch(1)); - final after = getFaceThumbnailUrl('person-1', updatedAt: DateTime.fromMillisecondsSinceEpoch(2)); - expect(before, isNot(after)); - }); - }); -} diff --git a/mobile/test/unit/utils/option_test.dart b/mobile/test/utils/option_test.dart similarity index 82% rename from mobile/test/unit/utils/option_test.dart rename to mobile/test/utils/option_test.dart index 26574a399a..4fa44a3865 100644 --- a/mobile/test/unit/utils/option_test.dart +++ b/mobile/test/utils/option_test.dart @@ -100,23 +100,17 @@ void main() { }); }); - group('NullableOptionExtension', () { - test('patch returns the current value when the option is null', () { - const Option? omitted = null; - expect(omitted.patch('existing'), 'existing'); + group('ObjectOptionExtension', () { + test('non-null value.toOption() returns Some', () { + final option = 'hello'.toOption(); + expect(option, isA>()); + expect((option as Some).value, 'hello'); }); - test('patch preserves a null current when the option is null', () { - const Option? omitted = null; - expect(omitted.patch(null), isNull); - }); - - test('patch returns the wrapped value for Some, overriding current', () { - expect(const Option.some('new').patch('existing'), 'new'); - }); - - test('patch returns null for None, clearing current', () { - expect(const Option.none().patch('existing'), isNull); + test('null value.toOption() returns None', () { + const String? value = null; + final option = value.toOption(); + expect(option, isA>()); }); }); } diff --git a/mobile/test/unit/utils/semver_test.dart b/mobile/test/utils/semver_test.dart similarity index 53% rename from mobile/test/unit/utils/semver_test.dart rename to mobile/test/utils/semver_test.dart index bbe8c9e7db..8f1958a879 100644 --- a/mobile/test/unit/utils/semver_test.dart +++ b/mobile/test/utils/semver_test.dart @@ -16,7 +16,7 @@ void main() { expect(() => SemVer.fromString('1.2.3.4'), throwsFormatException); }); - test('Compares equal versions correctly', () { + test('Compares equal versons correctly', () { final v1 = SemVer.fromString('1.2.3'); final v2 = SemVer.fromString('1.2.3'); expect(v1 == v2, isTrue); @@ -88,71 +88,5 @@ void main() { expect(version2.minor, 2); expect(version2.patch, 3); }); - - test('Orders later prerelease above earlier prerelease', () { - const rc1 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); - const rc2 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); - expect(rc2 > rc1, isTrue); - expect(rc1 < rc2, isTrue); - expect(rc1 == rc2, isFalse); - }); - - test('Final release outranks its prerelease of the same version', () { - const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); - const release = SemVer(major: 1, minor: 151, patch: 0); - expect(release > rc, isTrue); - expect(rc < release, isTrue); - }); - - test('Higher major outranks a prerelease regardless of ordinal', () { - const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 9); - const next = SemVer(major: 2, minor: 0, patch: 0); - expect(next > rc, isTrue); - }); - - test('Equal prerelease versions compare as equal', () { - const a = SemVer(major: 1, minor: 151, patch: 0, prerelease: 3); - const b = SemVer(major: 1, minor: 151, patch: 0, prerelease: 3); - expect(a == b, isTrue); - expect(a > b, isFalse); - expect(a < b, isFalse); - }); - - test('Reports prerelease difference type', () { - const rc1 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 1); - const rc2 = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); - expect(rc1.differenceType(rc2), SemVerType.prerelease); - }); - - test('toString includes prerelease suffix when present', () { - const rc = SemVer(major: 1, minor: 151, patch: 0, prerelease: 2); - expect(rc.toString(), '1.151.0-rc.2'); - }); - - test('Parses prerelease ordinal from -rc strings', () { - final dotted = SemVer.fromString('1.151.0-rc.2'); - expect(dotted.major, 1); - expect(dotted.minor, 151); - expect(dotted.patch, 0); - expect(dotted.prerelease, 2); - - expect(SemVer.fromString('v1.151.0-rc.3').prerelease, 3); - expect(SemVer.fromString('1.2.3-rc.2+build.5').prerelease, 2); - }); - - test('Plain version string has null prerelease', () { - expect(SemVer.fromString('3.0.0').prerelease, isNull); - }); - - test('Invalid rc suffixes parse without error and have null prerelease', () { - final debug = SemVer.fromString('1.2.3-debug'); - expect(debug.major, 1); - expect(debug.minor, 2); - expect(debug.patch, 3); - expect(debug.prerelease, isNull); - - expect(SemVer.fromString('1.2.3+build.5').prerelease, isNull); - expect(SemVer.fromString('1.151.0-rc4').prerelease, isNull); - }); }); } diff --git a/mobile/test/unit/utils/timezone_test.dart b/mobile/test/utils/timezone_test.dart similarity index 90% rename from mobile/test/unit/utils/timezone_test.dart rename to mobile/test/utils/timezone_test.dart index 90e30972f3..d1e89dc473 100644 --- a/mobile/test/unit/utils/timezone_test.dart +++ b/mobile/test/utils/timezone_test.dart @@ -177,32 +177,6 @@ void main() { expect(adjustedTime.minute, 30); expect(offset, const Duration(hours: 5, minutes: 30)); }); - - test('should handle UTC-05:30 format (negative offset with minutes)', () { - final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0); - - final (adjustedTime, offset) = applyTimezoneOffset( - dateTime: utcTime, - timeZone: 'UTC-05:30', - ); - - expect(adjustedTime.hour, 6); - expect(adjustedTime.minute, 30); // 12:00 UTC - 5:30 = 06:30 - expect(offset, const Duration(hours: -5, minutes: -30)); - }); - - test('should handle UTC-3:30 format (single digit hour with minutes)', () { - final utcTime = DateTime.utc(2024, 6, 15, 12, 0, 0); - - final (adjustedTime, offset) = applyTimezoneOffset( - dateTime: utcTime, - timeZone: 'UTC-3:30', - ); - - expect(adjustedTime.hour, 8); - expect(adjustedTime.minute, 30); // 12:00 UTC - 3:30 = 08:30 - expect(offset, const Duration(hours: -3, minutes: -30)); - }); }); group('with null or invalid timezone', () { diff --git a/mobile/test/widget_tester_extensions.dart b/mobile/test/widget_tester_extensions.dart index 5f43a80b89..bb3fc3f418 100644 --- a/mobile/test/widget_tester_extensions.dart +++ b/mobile/test/widget_tester_extensions.dart @@ -1,51 +1,27 @@ -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:immich_mobile/constants/locales.dart'; -import 'package:immich_mobile/generated/codegen_loader.g.dart'; extension PumpConsumerWidget on WidgetTester { - /// Wraps the provided [widget] with a localized Material app such that it - /// becomes: - /// - /// EasyLocalization - /// |-ProviderScope - /// |-MaterialApp (localization delegates wired up) - /// |-Material - /// |-[widget] + /// Wraps the provided [widget] with Material app such that it becomes: /// + /// ProviderScope + /// |-MaterialApp + /// |-Material + /// |-[widget] Future pumpConsumerWidget( Widget widget, { Duration? duration, EnginePhase phase = EnginePhase.sendSemanticsUpdate, List overrides = const [], }) async { - await pumpWidget( - EasyLocalization( - supportedLocales: locales.values.toList(), - path: translationsPath, - startLocale: locales.values.first, - fallbackLocale: locales.values.first, - saveLocale: false, - useFallbackTranslations: true, - assetLoader: const CodegenLoader(), - child: ProviderScope( - overrides: overrides, - child: Builder( - builder: (context) => MaterialApp( - debugShowCheckedModeBanner: false, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: context.locale, - home: Material(child: widget), - ), - ), - ), + return pumpWidget( + ProviderScope( + overrides: overrides, + child: MaterialApp(debugShowCheckedModeBanner: false, home: Material(child: widget)), ), duration: duration, phase: phase, ); - await pumpAndSettle(); } } diff --git a/open-api/bin/generate-dart-sdk.sh b/open-api/bin/generate-dart-sdk.sh deleted file mode 100755 index 200926afdf..0000000000 --- a/open-api/bin/generate-dart-sdk.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# usage: ./bin/generate-dart-sdk.sh - -TEMPLATE_DIR=$(mktemp -d) -trap 'rm -rf "$TEMPLATE_DIR"' EXIT - -# Installed via mise -openapi-generator-cli author template -g dart -o "$TEMPLATE_DIR" -patch --no-backup-if-mismatch -u "$TEMPLATE_DIR/api.mustache" <./templates/mobile/api.mustache.patch -patch --no-backup-if-mismatch -u "$TEMPLATE_DIR/serialization/native/native_class.mustache" <./templates/mobile/serialization/native/native_class.mustache.patch - -rm -rf ../mobile/generated/openapi - -openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/generated/openapi -t "$TEMPLATE_DIR" --additional-properties=useOptional=true - -# Post generate patches -patch --no-backup-if-mismatch -u ../mobile/generated/openapi/lib/api_client.dart <./patch/api_client.dart.patch -patch --no-backup-if-mismatch -u ../mobile/generated/openapi/lib/api.dart <./patch/api.dart.patch -patch --no-backup-if-mismatch -u ../mobile/generated/openapi/pubspec.yaml <./patch/pubspec_immich_mobile.yaml.patch -patch --no-backup-if-mismatch -u ../mobile/generated/openapi/lib/model/asset_edit_action_item_dto.dart <./patch/asset_edit_action_item_dto.dart.patch -# Don't include analysis_options.yaml for the generated openapi files -# so that language servers can properly exclude the mobile/generated/openapi directory -rm ../mobile/generated/openapi/analysis_options.yaml diff --git a/open-api/bin/generate-open-api.sh b/open-api/bin/generate-open-api.sh new file mode 100755 index 0000000000..522063185f --- /dev/null +++ b/open-api/bin/generate-open-api.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +OPENAPI_GENERATOR_VERSION=v7.12.0 + +# usage: ./bin/generate-open-api.sh + +function dart { + rm -rf ../mobile/openapi + cd ./templates/mobile/serialization/native + wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache + patch --no-backup-if-mismatch -u native_class.mustache